diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 66f20ccfb5fe4..9ad528d52286f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -4,4 +4,4 @@ ARG VARIANT="14-buster" FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} -RUN sudo -u node npm install -g gulp-cli +RUN sudo -u node npm install -g hereby diff --git a/.eslintignore b/.eslintignore index c85649450cd49..764d07c541387 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,5 @@ **/node_modules/** -/built/local/** +/built/** /tests/** /lib/** /src/lib/*.generated.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index 35901b12013ad..f78f3250020cf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -87,7 +87,7 @@ "local/simple-indent": "error", "local/debug-assert": "error", "local/no-keywords": "error", - "local/one-namespace-per-file": "error", + "local/jsdoc-format": "error", // eslint-plugin-import "import/no-extraneous-dependencies": ["error", { "optionalDependencies": false }], diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000000..6e2a702d8ab99 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,8 @@ +# Generated module conversion step - inlineImports +07758c08ab72481885e662c98d67a0e3a071b032 +# Generated module conversion step - stripNamespaces +b6c053882696af8ddd94a600429f30584d303d7f +# Generated module conversion step - explicitify +9a0b85ce2a3f85f498ab2c05474b4c0b96b111c9 +# Generated module conversion step - unindent +94724a8c2e68a4c7e267072ca79971f317c45e4a diff --git a/.github/codeql/codeql-configuration.yml b/.github/codeql/codeql-configuration.yml index a9e8b576b7c65..3470e9f25a6af 100644 --- a/.github/codeql/codeql-configuration.yml +++ b/.github/codeql/codeql-configuration.yml @@ -6,3 +6,29 @@ paths: - Gulpfile.mjs paths-ignore: - src/lib + +# These queries appear to time out after the module conversion. +# https://github.com/github/codeql/issues/10937 +query-filters: + - exclude: + id: js/path-injection # TaintedPath.ql + - exclude: + id: js/command-line-injection # CommandInjection.ql + - exclude: + id: js/code-injection # CodeInjection.ql + - exclude: + id: js/bad-code-sanitization # ImproperCodeSanitization.ql + - exclude: + id: js/unsafe-dynamic-method-access # UnsafeDynamicMethodAccess.ql + - exclude: + id: js/clear-text-logging # CleartextLogging.ql + - exclude: + id: js/regex-injection # RegExpInjection.ql + - exclude: + id: js/unvalidated-dynamic-method-call # UnvalidatedDynamicMethodCall.ql + - exclude: + id: js/insecure-download # InsecureDownload.ql + - exclude: + id: js/prototype-polluting-assignment # PrototypePollutingAssignment.ql + - exclude: + id: js/request-forgery # RequestForgery.ql diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 12b9aeb21e15a..96f77fc18d101 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,7 +4,7 @@ Thank you for submitting a pull request! Please verify that: * [ ] There is an associated issue in the `Backlog` milestone (**required**) * [ ] Code is up-to-date with the `main` branch -* [ ] You've successfully run `gulp runtests` locally +* [ ] You've successfully run `hereby runtests` locally * [ ] There are new or updated unit tests validating the change Refer to CONTRIBUTING.MD for more details. diff --git a/.github/workflows/accept-baselines-fix-lints.yaml b/.github/workflows/accept-baselines-fix-lints.yaml index 0294ead0b9e75..cb65de2c81ee8 100644 --- a/.github/workflows/accept-baselines-fix-lints.yaml +++ b/.github/workflows/accept-baselines-fix-lints.yaml @@ -17,8 +17,8 @@ jobs: git config user.name "TypeScript Bot" npm install git rm -r --quiet tests/baselines/reference :^tests/baselines/reference/docker :^tests/baselines/reference/user - gulp runtests-parallel --ci --fix || true - gulp baseline-accept + npx hereby runtests-parallel --ci --fix || true + npx hereby baseline-accept git add ./src git add ./tests/baselines/reference git diff --cached diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf6cb012289dd..1f014077728ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,13 @@ jobs: - "*" - lts/* - lts/-1 + bundle: + - "true" + include: + - node-version: "*" + bundle: "false" + + name: Test Node ${{ matrix.node-version }} with --bundle=${{ matrix.bundle }} steps: - uses: actions/checkout@v3 @@ -32,7 +39,7 @@ jobs: - run: npm ci - name: Tests - run: npm test + run: npm run test -- --bundle=${{ matrix.bundle }} lint: runs-on: ubuntu-latest @@ -62,11 +69,23 @@ jobs: - name: Adding playwright run: npm install --no-save --no-package-lock playwright - - name: Build local - run: gulp local - - name: Validate the browser can import TypeScript - run: gulp test-browser-integration + run: npx hereby test-browser-integration + + typecheck: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "*" + check-latest: true + - run: npm ci + + - name: Build src + run: npx hereby build-src + misc: runs-on: ubuntu-latest @@ -80,7 +99,27 @@ jobs: - run: npm ci - name: Build scripts - run: gulp scripts + run: npx hereby scripts - name: ESLint tests - run: gulp run-eslint-rules-tests + run: npx hereby run-eslint-rules-tests + + self-check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "*" + check-latest: true + - run: npm ci + + - name: Build tsc + run: npx hereby tsc + + - name: Clean + run: npx hereby clean-src + + - name: Self build + run: npx hereby build-src --built diff --git a/.github/workflows/new-release-branch.yaml b/.github/workflows/new-release-branch.yaml index 1800c36a4768e..9b5414a935b3e 100644 --- a/.github/workflows/new-release-branch.yaml +++ b/.github/workflows/new-release-branch.yaml @@ -21,7 +21,7 @@ jobs: sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' tests/baselines/reference/api/tsserverlibrary.d.ts sed -i -e 's/const version\(: string\)\{0,1\} = `${versionMajorMinor}.0-.*`/const version = `${versionMajorMinor}.0-${{ github.event.client_payload.core_tag || 'dev' }}`/g' src/compiler/corePublic.ts npm ci - gulp LKG + npx hereby LKG npm test git diff git add package.json diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 8ae588d43729b..39949c3e8ed0d 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -23,10 +23,10 @@ jobs: run: | npm whoami npm ci - gulp configure-nightly - gulp LKG - gulp runtests-parallel - gulp clean + npx hereby configure-nightly + npx hereby LKG + npx hereby runtests-parallel + npx hereby clean npm publish --tag next env: NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/.github/workflows/release-branch-artifact.yaml b/.github/workflows/release-branch-artifact.yaml index 9061c32d63b9c..bbe7fab646f8b 100644 --- a/.github/workflows/release-branch-artifact.yaml +++ b/.github/workflows/release-branch-artifact.yaml @@ -19,11 +19,11 @@ jobs: - name: Adding playwright run: npm install --no-save --no-package-lock playwright - name: Validate the browser can import TypeScript - run: gulp test-browser-integration + run: npx hereby test-browser-integration - name: LKG, clean, and pack run: | - gulp LKG - gulp clean + npx hereby LKG + npx hereby clean npm pack ./ mv typescript-*.tgz typescript.tgz - name: Upload built tarfile diff --git a/.github/workflows/set-version.yaml b/.github/workflows/set-version.yaml index 982a102a648ac..ce7da6726eb76 100644 --- a/.github/workflows/set-version.yaml +++ b/.github/workflows/set-version.yaml @@ -27,7 +27,7 @@ jobs: sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' tests/baselines/reference/api/tsserverlibrary.d.ts sed -i -e 's/const version\(: string\)\{0,1\} = .*;/const version = "${{ github.event.client_payload.package_version }}" as string;/g' src/compiler/corePublic.ts npm ci - gulp LKG + npx hereby LKG npm test git diff git add package.json diff --git a/.github/workflows/update-lkg.yml b/.github/workflows/update-lkg.yml index 3505fc28303d4..b2fd19c02d938 100644 --- a/.github/workflows/update-lkg.yml +++ b/.github/workflows/update-lkg.yml @@ -16,7 +16,7 @@ jobs: git config user.email "typescriptbot@microsoft.com" git config user.name "TypeScript Bot" npm ci - gulp LKG + npx hereby LKG npm test git diff git add ./lib diff --git a/.gulp.js b/.gulp.js new file mode 100644 index 0000000000000..009d8b66adda4 --- /dev/null +++ b/.gulp.js @@ -0,0 +1,19 @@ +const cp = require("child_process"); +const path = require("path"); +const chalk = require("chalk"); + +const argv = process.argv.slice(2); + +// --tasks-simple is used by VS Code to infer a task list; try and keep that working. +if (!argv.includes("--tasks-simple")) { + console.error(chalk.yellowBright("Warning: using gulp shim; please consider running hereby directly.")); +} + +const args = [ + ...process.execArgv, + path.join(__dirname, "node_modules", "hereby", "bin", "hereby.js"), + ...argv, +]; + +const { status } = cp.spawnSync(process.execPath, args, { stdio: "inherit" }); +process.exit(status ?? 1); diff --git a/.vscode/launch.template.json b/.vscode/launch.template.json index e4301b81e87b9..134216381d7a2 100644 --- a/.vscode/launch.template.json +++ b/.vscode/launch.template.json @@ -20,7 +20,6 @@ "configurations": [ { "type": "node", - "protocol": "inspector", "request": "launch", "name": "Mocha Tests (currently opened test)", "runtimeArgs": ["--nolazy"], @@ -43,12 +42,9 @@ }, "sourceMaps": true, "smartStep": true, - "preLaunchTask": "gulp: tests", + "preLaunchTask": "npm: build:tests", "console": "integratedTerminal", - "outFiles": [ - "${workspaceRoot}/built/local/run.js" - ], - "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue", + "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue" }, { // See: https://github.com/microsoft/TypeScript/wiki/Debugging-Language-Service-in-VS-Code @@ -56,7 +52,7 @@ "request": "attach", "name": "Attach to VS Code TS Server via Port", "processId": "${command:PickProcess}", - "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue", + "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue" } ] } diff --git a/.vscode/settings.template.json b/.vscode/settings.template.json index ccfcfc2631b00..4e85591e7eb06 100644 --- a/.vscode/settings.template.json +++ b/.vscode/settings.template.json @@ -6,4 +6,18 @@ // To use the locally built compiler, after 'npm run build': // "typescript.tsdk": "built/local" + + // To ignore commits listed in .git-blame-ignore-revs in GitLens: + // "gitlens.advanced.blame.customArguments": [ + // "--ignore-revs-file", + // ".git-blame-ignore-revs" + // ] + + // These options search the repo recursively and slow down + // the build task menu. We define our own in tasks.json. + "typescript.tsc.autoDetect": "off", + "npm.autoDetect": "off", + "grunt.autoDetect": "off", + "jake.autoDetect": "off", + "gulp.autoDetect": "off" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 807da9ce56e69..cd3687bae7317 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,42 +4,51 @@ "version": "2.0.0", "tasks": [ { - "type": "gulp", - "label": "gulp: local", - "task": "local", - "group": { - "kind": "build", - "isDefault": true - }, + // Kept for backwards compat for old launch.json files so it's + // less annoying if moving up to the new build or going back to + // the old build. + // + // This is first because the acutal "npm: build:tests" task + // below has the same script value, and VS Code ignores labels + // and deduplicates them. + // https://github.com/microsoft/vscode/issues/93001 + "label": "gulp: tests", + "type": "npm", + "script": "build:tests", + "group": "build", + "hide": true, "problemMatcher": [ "$tsc" ] }, { - "type": "gulp", - "label": "gulp: tsc", - "task": "tsc", + "label": "tsc: watch ./src", + "type": "shell", + "command": "node", + "args": ["${workspaceFolder}/lib/tsc.js", "--build", "${workspaceFolder}/src", "--watch"], "group": "build", + "isBackground": true, "problemMatcher": [ - "$tsc" + "$tsc-watch" ] }, { - "type": "gulp", - "label": "gulp: tests", - "task": "tests", + "label": "npm: build:compiler", + "type": "npm", + "script": "build:compiler", "group": "build", "problemMatcher": [ "$tsc" ] }, { - "type": "gulp", - "task": "services", - "label": "gulp: services", + "label": "npm: build:tests", + "type": "npm", + "script": "build:tests", + "group": "build", "problemMatcher": [ "$tsc" - ], - } + ] + }, ] -} \ No newline at end of file +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49d5f87ebd14e..de82cd7e67683 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,42 +54,42 @@ In general, things we find useful when reviewing suggestions are: 2. A copy of the TypeScript code. See the next steps for instructions. 3. [Node](https://nodejs.org), which runs JavaScript locally. Current or LTS will both work. 4. An editor. [VS Code](https://code.visualstudio.com) is the best place to start for TypeScript. -5. The gulp command line tool, for building and testing changes. See the next steps for how to install it. +5. The hereby command line tool, for building and testing changes. See the next steps for how to install it. ## Get Started 1. Install node using the version you downloaded from [nodejs.org](https://nodejs.org). 2. Open a terminal. 3. Make a fork—your own copy—of TypeScript on your GitHub account, then make a clone—a local copy—on your computer. ([Here are some step-by-step instructions](https://github.com/anitab-org/mentorship-android/wiki/Fork%2C-Clone-%26-Remote)). Add `--depth=1` to the end of the `git clone` command to save time. -4. Install the gulp command line tool: `npm install -g gulp-cli` +4. Install the hereby command line tool: `npm install -g hereby` 5. Change to the TypeScript folder you made: `cd TypeScript` 6. Install dependencies: `npm ci` -7. Make sure everything builds and tests pass: `gulp runtests-parallel` +7. Make sure everything builds and tests pass: `hereby runtests-parallel` 8. Open the TypeScript folder in your editor. 9. Follow the directions below to add and debug a test. ## Helpful tasks -Running `gulp --tasks --depth 1` provides the full listing, but here are a few common tasks you might use. +Running `hereby --tasks` provides the full listing, but here are a few common tasks you might use. ``` -gulp local # Build the compiler into built/local. -gulp clean # Delete the built compiler. -gulp LKG # Replace the last known good with the built one. - # Bootstrapping step to be executed when the built compiler reaches a stable state. -gulp tests # Build the test infrastructure using the built compiler. -gulp runtests # Run tests using the built compiler and test infrastructure. - # You can override the specific suite runner used or specify a test for this command. - # Use --tests= for a specific test and/or --runner= for a specific suite. - # Valid runners include conformance, compiler, fourslash, project, user, and docker - # The user and docker runners are extended test suite runners - the user runner - # works on disk in the tests/cases/user directory, while the docker runner works in containers. - # You'll need to have the docker executable in your system path for the docker runner to work. -gulp runtests-parallel # Like runtests, but split across multiple threads. Uses a number of threads equal to the system - # core count by default. Use --workers= to adjust this. -gulp baseline-accept # This replaces the baseline test results with the results obtained from gulp runtests. -gulp lint # Runs eslint on the TypeScript source. -gulp help # List the above commands. +hereby local # Build the compiler into built/local. +hereby clean # Delete the built compiler. +hereby LKG # Replace the last known good with the built one. + # Bootstrapping step to be executed when the built compiler reaches a stable state. +hereby tests # Build the test infrastructure using the built compiler. +hereby runtests # Run tests using the built compiler and test infrastructure. + # You can override the specific suite runner used or specify a test for this command. + # Use --tests= for a specific test and/or --runner= for a specific suite. + # Valid runners include conformance, compiler, fourslash, project, user, and docker + # The user and docker runners are extended test suite runners - the user runner + # works on disk in the tests/cases/user directory, while the docker runner works in containers. + # You'll need to have the docker executable in your system path for the docker runner to work. +hereby runtests-parallel # Like runtests, but split across multiple threads. Uses a number of threads equal to the system + # core count by default. Use --workers= to adjust this. +hereby baseline-accept # This replaces the baseline test results with the results obtained from hereby runtests. +hereby lint # Runs eslint on the TypeScript source. +hereby help # List the above commands. ``` ## Tips @@ -111,7 +111,7 @@ You might need to run `git config --global core.longpaths true` before cloning T ### Using local builds -Run `gulp` to build a version of the compiler/language service that reflects changes you've made. You can then run `node /built/local/tsc.js` in place of `tsc` in your project. For example, to run `tsc --watch` from within the root of the repository on a file called `test.ts`, you can run `node ./built/local/tsc.js --watch test.ts`. +Run `hereby` to build a version of the compiler/language service that reflects changes you've made. You can then run `node /built/local/tsc.js` in place of `tsc` in your project. For example, to run `tsc --watch` from within the root of the repository on a file called `test.ts`, you can run `node ./built/local/tsc.js --watch test.ts`. ## Contributing bug fixes @@ -163,7 +163,7 @@ Any changes should be made to [src/lib](https://github.com/Microsoft/TypeScript/ Library files in `built/local/` are updated automatically by running the standard build task: ```sh -gulp +hereby ``` The files in `lib/` are used to bootstrap compilation and usually **should not** be updated unless publishing a new version or updating the LKG. @@ -178,36 +178,36 @@ If you need a head start understanding how the compiler works, or how the code i ## Running the Tests -To run all tests, invoke the `runtests-parallel` target using gulp: +To run all tests, invoke the `runtests-parallel` target using hereby: ```Shell -gulp runtests-parallel +hereby runtests-parallel ``` This will run all tests; to run only a specific subset of tests, use: ```Shell -gulp runtests --tests= +hereby runtests --tests= ``` e.g. to run all compiler baseline tests: ```Shell -gulp runtests --tests=compiler +hereby runtests --tests=compiler ``` or to run a specific test: `tests\cases\compiler\2dArrays.ts` ```Shell -gulp runtests --tests=2dArrays +hereby runtests --tests=2dArrays ``` ## Debugging the tests -You can debug with VS Code or Node instead with `gulp runtests -i`: +You can debug with VS Code or Node instead with `hereby runtests -i`: ```Shell -gulp runtests --tests=2dArrays -i +hereby runtests --tests=2dArrays -i ``` You can also use the [provided VS Code launch configuration](./.vscode/launch.template.json) to launch a debug session for an open test file. Rename the file 'launch.json', open the test file of interest, and launch the debugger from the debug panel (or press F5). @@ -257,12 +257,12 @@ When a change in the baselines is detected, the test will fail. To inspect chang git diff --diff-filter=AM --no-index ./tests/baselines/reference ./tests/baselines/local ``` -Alternatively, you can set the `DIFF` environment variable and run `gulp diff`, or manually run your favorite folder diffing tool between `tests/baselines/reference` and `tests/baselines/local`. Our team largely uses Beyond Compare and WinMerge. +Alternatively, you can set the `DIFF` environment variable and run `hereby diff`, or manually run your favorite folder diffing tool between `tests/baselines/reference` and `tests/baselines/local`. Our team largely uses Beyond Compare and WinMerge. After verifying that the changes in the baselines are correct, run ```Shell -gulp baseline-accept +hereby baseline-accept ``` This will change the files in `tests\baselines\reference`, which should be included as part of your commit. @@ -271,6 +271,6 @@ Be sure to validate the changes carefully -- apparently unrelated changes to bas ## Localization All strings the user may see are stored in [`diagnosticMessages.json`](./src/compiler/diagnosticMessages.json). -If you make changes to it, run `gulp generate-diagnostics` to push them to the `Diagnostic` interface in `diagnosticInformationMap.generated.ts`. +If you make changes to it, run `hereby generate-diagnostics` to push them to the `Diagnostic` interface in `diagnosticInformationMap.generated.ts`. See [coding guidelines on diagnostic messages](https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines#diagnostic-messages). diff --git a/Dockerfile b/Dockerfile index 5606df961119b..d72ffc513fd01 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,5 +3,4 @@ FROM node:current COPY . /typescript WORKDIR /typescript RUN npm ci -RUN npm i -g gulp-cli -RUN gulp configure-insiders && gulp LKG && gulp clean && npm pack . \ No newline at end of file +RUN npx hereby configure-insiders && npx hereby LKG && npx hereby clean && npm pack . diff --git a/Gulpfile.mjs b/Gulpfile.mjs deleted file mode 100644 index 46c5827798893..0000000000000 --- a/Gulpfile.mjs +++ /dev/null @@ -1,588 +0,0 @@ -// @ts-check -import path from "path"; -import fs from "fs"; -import log from "fancy-log"; -import newer from "gulp-newer"; -import sourcemaps from "gulp-sourcemaps"; -import del from "del"; -import rename from "gulp-rename"; -import concat from "gulp-concat"; -import merge2 from "merge2"; -import gulp from "gulp"; -import { append, transform } from "gulp-insert"; -import { prependFile } from "./scripts/build/prepend.mjs"; -import { exec, readJson, needsUpdate, getDiffTool, getDirSize, rm } from "./scripts/build/utils.mjs"; -import { runConsoleTests, refBaseline, localBaseline, refRwcBaseline, localRwcBaseline } from "./scripts/build/tests.mjs"; -import { buildProject, cleanProject, watchProject } from "./scripts/build/projects.mjs"; -import cmdLineOptions from "./scripts/build/options.mjs"; - -const { src, dest, task, parallel, series, watch } = gulp; - -const copyright = "CopyrightNotice.txt"; -const cleanTasks = []; - -const buildScripts = () => buildProject("scripts"); -task("scripts", buildScripts); -task("scripts").description = "Builds files in the 'scripts' folder."; - -/** @type {{ libs: string[]; paths: Record; }} */ -const libraries = readJson("./src/lib/libs.json"); -const libs = libraries.libs.map(lib => { - const relativeSources = ["header.d.ts", lib + ".d.ts"]; - const relativeTarget = libraries.paths && libraries.paths[lib] || ("lib." + lib + ".d.ts"); - const sources = relativeSources.map(s => path.posix.join("src/lib", s)); - const target = `built/local/${relativeTarget}`; - return { target, relativeTarget, sources }; -}); - -const generateLibs = () => { - return merge2(libs.map(({ sources, target, relativeTarget }) => - src([copyright, ...sources]) - .pipe(newer(target)) - .pipe(concat(relativeTarget, { newLine: "\n\n" })) - .pipe(dest("built/local")))); -}; -task("lib", generateLibs); -task("lib").description = "Builds the library targets"; - -const cleanLib = () => del(libs.map(lib => lib.target)); -cleanTasks.push(cleanLib); - -const watchLib = () => watch(["src/lib/**/*"], generateLibs); - -const diagnosticInformationMapTs = "src/compiler/diagnosticInformationMap.generated.ts"; -const diagnosticMessagesJson = "src/compiler/diagnosticMessages.json"; -const diagnosticMessagesGeneratedJson = "src/compiler/diagnosticMessages.generated.json"; -const generateDiagnostics = async () => { - if (needsUpdate(diagnosticMessagesJson, [diagnosticMessagesGeneratedJson, diagnosticInformationMapTs])) { - await exec(process.execPath, ["scripts/processDiagnosticMessages.mjs", diagnosticMessagesJson]); - } -}; -task("generate-diagnostics", generateDiagnostics); -task("generate-diagnostics").description = "Generates a diagnostic file in TypeScript based on an input JSON file"; - -const cleanDiagnostics = () => del([diagnosticInformationMapTs, diagnosticMessagesGeneratedJson]); -cleanTasks.push(cleanDiagnostics); - -const watchDiagnostics = () => watch(["src/compiler/diagnosticMessages.json"], task("generate-diagnostics")); - -// Localize diagnostics -/** - * .lcg file is what localization team uses to know what messages to localize. - * The file is always generated in 'enu/diagnosticMessages.generated.json.lcg' - */ -const generatedLCGFile = "built/local/enu/diagnosticMessages.generated.json.lcg"; - -/** - * The localization target produces the two following transformations: - * 1. 'src\loc\lcl\\diagnosticMessages.generated.json.lcl' => 'built\local\\diagnosticMessages.generated.json' - * convert localized resources into a .json file the compiler can understand - * 2. 'src\compiler\diagnosticMessages.generated.json' => 'built\local\ENU\diagnosticMessages.generated.json.lcg' - * generate the lcg file (source of messages to localize) from the diagnosticMessages.generated.json - */ -const localizationTargets = ["cs", "de", "es", "fr", "it", "ja", "ko", "pl", "pt-br", "ru", "tr", "zh-cn", "zh-tw"] - .map(f => f.toLowerCase()) - .map(f => `built/local/${f}/diagnosticMessages.generated.json`) - .concat(generatedLCGFile); - -const localize = async () => { - if (needsUpdate(diagnosticMessagesGeneratedJson, generatedLCGFile)) { - return exec(process.execPath, ["scripts/generateLocalizedDiagnosticMessages.mjs", "src/loc/lcl", "built/local", diagnosticMessagesGeneratedJson], { ignoreExitCode: true }); - } -}; - -const buildDebugTools = () => buildProject("src/debug"); -const cleanDebugTools = () => cleanProject("src/debug"); -cleanTasks.push(cleanDebugTools); - -// Pre-build steps when targeting the LKG compiler -const lkgPreBuild = parallel(generateLibs, series(generateDiagnostics, buildDebugTools)); - -const buildTsc = () => buildProject("src/tsc"); -task("tsc", series(lkgPreBuild, buildTsc)); -task("tsc").description = "Builds the command-line compiler"; - -const cleanTsc = () => cleanProject("src/tsc"); -cleanTasks.push(cleanTsc); -task("clean-tsc", cleanTsc); -task("clean-tsc").description = "Cleans outputs for the command-line compiler"; - -const watchTsc = () => watchProject("src/tsc"); -task("watch-tsc", series(lkgPreBuild, parallel(watchLib, watchDiagnostics, watchTsc))); -task("watch-tsc").description = "Watch for changes and rebuild the command-line compiler only."; - -// Pre-build steps when targeting the built/local compiler. -const localPreBuild = parallel(generateLibs, series(generateDiagnostics, buildDebugTools, buildTsc)); - -// Pre-build steps to use based on supplied options. -const preBuild = cmdLineOptions.lkg ? lkgPreBuild : localPreBuild; - -const buildServices = (() => { - // build typescriptServices.out.js - const buildTypescriptServicesOut = () => buildProject("src/typescriptServices/tsconfig.json", cmdLineOptions); - - // create typescriptServices.js - const createTypescriptServicesJs = () => src("built/local/typescriptServices.out.js") - .pipe(newer("built/local/typescriptServices.js")) - .pipe(sourcemaps.init({ loadMaps: true })) - .pipe(prependFile(copyright)) - .pipe(rename("typescriptServices.js")) - .pipe(sourcemaps.write(".", { includeContent: false, destPath: "built/local" })) - .pipe(dest("built/local")); - - // create typescriptServices.d.ts - const createTypescriptServicesDts = () => src("built/local/typescriptServices.out.d.ts") - .pipe(newer("built/local/typescriptServices.d.ts")) - .pipe(prependFile(copyright)) - .pipe(transform(content => content.replace(/^(\s*)(export )?const enum (\S+) {(\s*)$/gm, "$1$2enum $3 {$4"))) - .pipe(rename("typescriptServices.d.ts")) - .pipe(dest("built/local")); - - // create typescript.js - const createTypescriptJs = () => src("built/local/typescriptServices.js") - .pipe(newer("built/local/typescript.js")) - .pipe(sourcemaps.init({ loadMaps: true })) - .pipe(rename("typescript.js")) - .pipe(sourcemaps.write(".", { includeContent: false, destPath: "built/local" })) - .pipe(dest("built/local")); - - // create typescript.d.ts - const createTypescriptDts = () => src("built/local/typescriptServices.d.ts") - .pipe(newer("built/local/typescript.d.ts")) - .pipe(append("\nexport = ts;")) - .pipe(rename("typescript.d.ts")) - .pipe(dest("built/local")); - - // create typescript_standalone.d.ts - const createTypescriptStandaloneDts = () => src("built/local/typescriptServices.d.ts") - .pipe(newer("built/local/typescript_standalone.d.ts")) - .pipe(transform(content => content.replace(/declare (namespace|module) ts/g, 'declare module "typescript"'))) - .pipe(rename("typescript_standalone.d.ts")) - .pipe(dest("built/local")); - - return series( - buildTypescriptServicesOut, - createTypescriptServicesJs, - createTypescriptServicesDts, - createTypescriptJs, - createTypescriptDts, - createTypescriptStandaloneDts, - ); -})(); -task("services", series(preBuild, buildServices)); -task("services").description = "Builds the language service"; -task("services").flags = { - " --built": "Compile using the built version of the compiler." -}; - -const cleanServices = async () => { - if (fs.existsSync("built/local/typescriptServices.tsconfig.json")) { - await cleanProject("built/local/typescriptServices.tsconfig.json"); - } - await del([ - "built/local/typescriptServices.out.js", - "built/local/typescriptServices.out.d.ts", - "built/local/typescriptServices.out.tsbuildinfo", - "built/local/typescriptServices.js", - "built/local/typescript.js", - "built/local/typescript.d.ts", - "built/local/typescript_standalone.d.ts" - ]); -}; -cleanTasks.push(cleanServices); -task("clean-services", cleanServices); -task("clean-services").description = "Cleans outputs for the language service"; - -const watchServices = () => watch([ - "src/compiler/tsconfig.json", - "src/compiler/**/*.ts", - "src/jsTyping/tsconfig.json", - "src/jsTyping/**/*.ts", - "src/services/tsconfig.json", - "src/services/**/*.ts", -], series(preBuild, buildServices)); -task("watch-services", series(preBuild, parallel(watchLib, watchDiagnostics, watchServices))); -task("watch-services").description = "Watches for changes and rebuild language service only"; -task("watch-services").flags = { - " --built": "Compile using the built version of the compiler." -}; - -const buildDynamicImportCompat = () => buildProject("src/dynamicImportCompat", cmdLineOptions); -task("dynamicImportCompat", buildDynamicImportCompat); - -const buildServerMain = () => buildProject("src/tsserver", cmdLineOptions); -const buildServer = series(buildDynamicImportCompat, buildServerMain); -buildServer.displayName = "buildServer"; -task("tsserver", series(preBuild, buildServer)); -task("tsserver").description = "Builds the language server"; -task("tsserver").flags = { - " --built": "Compile using the built version of the compiler." -}; - -const cleanDynamicImportCompat = () => cleanProject("src/dynamicImportCompat"); -const cleanServerMain = () => cleanProject("src/tsserver"); -const cleanServer = series(cleanDynamicImportCompat, cleanServerMain); -cleanServer.displayName = "cleanServer"; -cleanTasks.push(cleanServer); -task("clean-tsserver", cleanServer); -task("clean-tsserver").description = "Cleans outputs for the language server"; - -const watchDynamicImportCompat = () => watchProject("src/dynamicImportCompat", cmdLineOptions); -const watchServer = () => watchProject("src/tsserver", cmdLineOptions); -task("watch-tsserver", series(preBuild, parallel(watchLib, watchDiagnostics, watchDynamicImportCompat, watchServer))); -task("watch-tsserver").description = "Watch for changes and rebuild the language server only"; -task("watch-tsserver").flags = { - " --built": "Compile using the built version of the compiler." -}; - -task("min", series(preBuild, parallel(buildTsc, buildServer))); -task("min").description = "Builds only tsc and tsserver"; -task("min").flags = { - " --built": "Compile using the built version of the compiler." -}; - -task("clean-min", series(cleanTsc, cleanServer)); -task("clean-min").description = "Cleans outputs for tsc and tsserver"; - -task("watch-min", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServer))); -task("watch-min").description = "Watches for changes to a tsc and tsserver only"; -task("watch-min").flags = { - " --built": "Compile using the built version of the compiler." -}; - -const buildLssl = (() => { - // build tsserverlibrary.out.js - const buildServerLibraryOut = () => buildProject("src/tsserverlibrary/tsconfig.json", cmdLineOptions); - - // create tsserverlibrary.js - const createServerLibraryJs = () => src("built/local/tsserverlibrary.out.js") - .pipe(newer("built/local/tsserverlibrary.js")) - .pipe(sourcemaps.init({ loadMaps: true })) - .pipe(prependFile(copyright)) - .pipe(rename("tsserverlibrary.js")) - .pipe(sourcemaps.write(".", { includeContent: false, destPath: "built/local" })) - .pipe(dest("built/local")); - - // create tsserverlibrary.d.ts - const createServerLibraryDts = () => src("built/local/tsserverlibrary.out.d.ts") - .pipe(newer("built/local/tsserverlibrary.d.ts")) - .pipe(prependFile(copyright)) - .pipe(transform(content => content.replace(/^(\s*)(export )?const enum (\S+) {(\s*)$/gm, "$1$2enum $3 {$4"))) - .pipe(append("\nexport = ts;\nexport as namespace ts;")) - .pipe(rename("tsserverlibrary.d.ts")) - .pipe(dest("built/local")); - - return series( - buildServerLibraryOut, - createServerLibraryJs, - createServerLibraryDts, - ); -})(); -task("lssl", series(preBuild, buildLssl)); -task("lssl").description = "Builds language service server library"; -task("lssl").flags = { - " --built": "Compile using the built version of the compiler." -}; - -const cleanLssl = async () => { - if (fs.existsSync("built/local/tsserverlibrary.tsconfig.json")) { - await cleanProject("built/local/tsserverlibrary.tsconfig.json"); - } - await del([ - "built/local/tsserverlibrary.out.js", - "built/local/tsserverlibrary.out.d.ts", - "built/local/tsserverlibrary.out.tsbuildinfo", - "built/local/tsserverlibrary.js", - "built/local/tsserverlibrary.d.ts", - ]); -}; -cleanTasks.push(cleanLssl); -task("clean-lssl", cleanLssl); -task("clean-lssl").description = "Clean outputs for the language service server library"; - -const watchLssl = () => watch([ - "src/compiler/tsconfig.json", - "src/compiler/**/*.ts", - "src/jsTyping/tsconfig.json", - "src/jsTyping/**/*.ts", - "src/services/tsconfig.json", - "src/services/**/*.ts", - "src/server/tsconfig.json", - "src/server/**/*.ts", - "src/webServer/tsconfig.json", - "src/webServer/**/*.ts", - "src/tsserver/tsconfig.json", - "src/tsserver/**/*.ts", -], buildLssl); -task("watch-lssl", series(preBuild, parallel(watchLib, watchDiagnostics, watchLssl))); -task("watch-lssl").description = "Watch for changes and rebuild tsserverlibrary only"; -task("watch-lssl").flags = { - " --built": "Compile using the built version of the compiler." -}; - -const buildTests = () => buildProject("src/testRunner"); -task("tests", series(preBuild, parallel(buildLssl, buildTests))); -task("tests").description = "Builds the test infrastructure"; -task("tests").flags = { - " --built": "Compile using the built version of the compiler." -}; - -const cleanTests = () => cleanProject("src/testRunner"); -cleanTasks.push(cleanTests); -task("clean-tests", cleanTests); -task("clean-tests").description = "Cleans the outputs for the test infrastructure"; - -const watchTests = () => watchProject("src/testRunner", cmdLineOptions); - -const runEslintRulesTests = () => runConsoleTests("scripts/eslint/tests", "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ false); -task("run-eslint-rules-tests", runEslintRulesTests); -task("run-eslint-rules-tests").description = "Runs the eslint rule tests"; - -/** @type { (folder: string) => { (): Promise; displayName?: string } } */ -const eslint = (folder) => async () => { - const formatter = cmdLineOptions.ci ? "stylish" : "autolinkable-stylish"; - const args = [ - "node_modules/eslint/bin/eslint", - "--cache", - "--cache-location", `${folder}/.eslintcache`, - "--format", formatter, - ]; - - if (cmdLineOptions.fix) { - args.push("--fix"); - } - - args.push(folder); - - log(`Linting: ${args.join(" ")}`); - return exec(process.execPath, args); -}; - -const lint = eslint("."); -lint.displayName = "lint"; -task("lint", lint); -task("lint").description = "Runs eslint on the compiler and scripts sources."; - -const buildCancellationToken = () => buildProject("src/cancellationToken"); -const cleanCancellationToken = () => cleanProject("src/cancellationToken"); -cleanTasks.push(cleanCancellationToken); - -const buildTypingsInstaller = () => buildProject("src/typingsInstaller"); -const cleanTypingsInstaller = () => cleanProject("src/typingsInstaller"); -cleanTasks.push(cleanTypingsInstaller); - -const buildWatchGuard = () => buildProject("src/watchGuard"); -const cleanWatchGuard = () => cleanProject("src/watchGuard"); -cleanTasks.push(cleanWatchGuard); - -const generateTypesMap = () => src("src/server/typesMap.json") - .pipe(newer("built/local/typesMap.json")) - .pipe(transform(contents => (JSON.parse(contents), contents))) // validates typesMap.json is valid JSON - .pipe(dest("built/local")); -task("generate-types-map", generateTypesMap); - -const cleanTypesMap = () => del("built/local/typesMap.json"); -cleanTasks.push(cleanTypesMap); - -// Drop a copy of diagnosticMessages.generated.json into the built/local folder. This allows -// it to be synced to the Azure DevOps repo, so that it can get picked up by the build -// pipeline that generates the localization artifacts that are then fed into the translation process. -const builtLocalDiagnosticMessagesGeneratedJson = "built/local/diagnosticMessages.generated.json"; -const copyBuiltLocalDiagnosticMessages = () => src(diagnosticMessagesGeneratedJson) - .pipe(newer(builtLocalDiagnosticMessagesGeneratedJson)) - .pipe(dest("built/local")); - -const cleanBuiltLocalDiagnosticMessages = () => del(builtLocalDiagnosticMessagesGeneratedJson); -cleanTasks.push(cleanBuiltLocalDiagnosticMessages); - -const buildOtherOutputs = parallel(buildCancellationToken, buildTypingsInstaller, buildWatchGuard, generateTypesMap, copyBuiltLocalDiagnosticMessages); -task("other-outputs", series(preBuild, buildOtherOutputs)); -task("other-outputs").description = "Builds miscelaneous scripts and documents distributed with the LKG"; - -task("local", series(preBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs))); -task("local").description = "Builds the full compiler and services"; -task("local").flags = { - " --built": "Compile using the built version of the compiler." -}; - -task("watch-local", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServices, watchServer, watchLssl))); -task("watch-local").description = "Watches for changes to projects in src/ (but does not execute tests)."; -task("watch-local").flags = { - " --built": "Compile using the built version of the compiler." -}; - -const preTest = parallel(buildTsc, buildTests, buildServices, buildLssl); -preTest.displayName = "preTest"; - -const runTests = () => runConsoleTests("built/local/run.js", "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ false); -task("runtests", series(preBuild, preTest, runTests)); -task("runtests").description = "Runs the tests using the built run.js file."; -task("runtests").flags = { - "-t --tests=": "Pattern for tests to run.", - " --failed": "Runs tests listed in '.failed-tests'.", - "-r --reporter=": "The mocha reporter to use.", - "-i --break": "Runs tests in inspector mode (NodeJS 8 and later)", - " --keepFailed": "Keep tests in .failed-tests even if they pass", - " --light": "Run tests in light mode (fewer verifications, but tests run faster)", - " --dirty": "Run tests without first cleaning test output directories", - " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", - " --no-color": "Disables color", - " --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*/ cmdLineOptions.workers > 1, /*watchMode*/ false); -task("runtests-parallel", series(preBuild, preTest, runTestsParallel)); -task("runtests-parallel").description = "Runs all the tests in parallel using the built run.js file."; -task("runtests-parallel").flags = { - " --light": "Run tests in light mode (fewer verifications, but tests run faster).", - " --keepFailed": "Keep tests in .failed-tests even if they pass.", - " --dirty": "Run tests without first cleaning test output directories.", - " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", - " --workers=": "The number of parallel workers to use.", - " --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)", -}; - - -task("test-browser-integration", () => exec(process.execPath, ["scripts/browserIntegrationTest.mjs"])); -task("test-browser-integration").description = "Runs scripts/browserIntegrationTest.mjs which tests that typescript.js loads in a browser"; - - -task("diff", () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true, waitForExit: false })); -task("diff").description = "Diffs the compiler baselines using the diff tool specified by the 'DIFF' environment variable"; - -task("diff-rwc", () => exec(getDiffTool(), [refRwcBaseline, localRwcBaseline], { ignoreExitCode: true, waitForExit: false })); -task("diff-rwc").description = "Diffs the RWC baselines using the diff tool specified by the 'DIFF' environment variable"; - -/** - * @param {string} localBaseline Path to the local copy of the baselines - * @param {string} refBaseline Path to the reference copy of the baselines - */ -const baselineAccept = (localBaseline, refBaseline) => merge2( - src([`${localBaseline}/**`, `!${localBaseline}/**/*.delete`], { base: localBaseline }) - .pipe(dest(refBaseline)), - src([`${localBaseline}/**/*.delete`], { base: localBaseline, read: false }) - .pipe(rm()) - .pipe(rename({ extname: "" })) - .pipe(rm(refBaseline))); -task("baseline-accept", () => baselineAccept(localBaseline, refBaseline)); -task("baseline-accept").description = "Makes the most recent test results the new baseline, overwriting the old baseline"; - -task("baseline-accept-rwc", () => baselineAccept(localRwcBaseline, refRwcBaseline)); -task("baseline-accept-rwc").description = "Makes the most recent rwc test results the new baseline, overwriting the old baseline"; - -// TODO(rbuckton): Determine if we still need this task. Depending on a relative -// path here seems like a bad idea. -const updateSublime = () => src(["built/local/tsserver.js", "built/local/tsserver.js.map"]) - .pipe(dest("../TypeScript-Sublime-Plugin/tsserver/")); -task("update-sublime", updateSublime); -task("update-sublime").description = "Updates the sublime plugin's tsserver"; - -// TODO(rbuckton): Should the path to DefinitelyTyped be configurable via an environment variable? -const importDefinitelyTypedTests = () => exec(process.execPath, ["scripts/importDefinitelyTypedTests.mjs", "./", "../DefinitelyTyped"]); -task("importDefinitelyTypedTests", importDefinitelyTypedTests); -task("importDefinitelyTypedTests").description = "Runs the importDefinitelyTypedTests script to copy DT's tests to the TS-internal RWC tests"; - -const buildReleaseTsc = () => buildProject("src/tsc/tsconfig.release.json"); -const cleanReleaseTsc = () => cleanProject("src/tsc/tsconfig.release.json"); -cleanTasks.push(cleanReleaseTsc); - -const cleanBuilt = () => del("built"); - -const produceLKG = async () => { - const expectedFiles = [ - "built/local/tsc.release.js", - "built/local/typescriptServices.js", - "built/local/typescriptServices.d.ts", - "built/local/tsserver.js", - "built/local/dynamicImportCompat.js", - "built/local/typescript.js", - "built/local/typescript.d.ts", - "built/local/tsserverlibrary.js", - "built/local/tsserverlibrary.d.ts", - "built/local/typingsInstaller.js", - "built/local/cancellationToken.js" - ].concat(libs.map(lib => lib.target)); - const missingFiles = expectedFiles - .concat(localizationTargets) - .filter(f => !fs.existsSync(f)); - if (missingFiles.length > 0) { - throw new Error("Cannot replace the LKG unless all built targets are present in directory 'built/local/'. The following files are missing:\n" + missingFiles.join("\n")); - } - const sizeBefore = getDirSize("lib"); - await exec(process.execPath, ["scripts/produceLKG.mjs"]); - const sizeAfter = getDirSize("lib"); - if (sizeAfter > (sizeBefore * 1.10)) { - throw new Error("The lib folder increased by 10% or more. This likely indicates a bug."); - } -}; - -task("LKG", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs, buildReleaseTsc), produceLKG)); -task("LKG").description = "Makes a new LKG out of the built js files"; -task("LKG").flags = { - " --built": "Compile using the built version of the compiler.", -}; -task("lkg", series("LKG")); - -const generateSpec = () => exec("cscript", ["//nologo", "scripts/word2md.mjs", path.resolve("doc/TypeScript Language Specification - ARCHIVED.docx"), path.resolve("doc/spec-ARCHIVED.md")]); -task("generate-spec", generateSpec); -task("generate-spec").description = "Generates a Markdown version of the Language Specification"; - -task("clean", series(parallel(cleanTasks), cleanBuilt)); -task("clean").description = "Cleans build outputs"; - -const configureNightly = () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "dev", "package.json", "src/compiler/corePublic.ts"]); -task("configure-nightly", series(buildScripts, configureNightly)); -task("configure-nightly").description = "Runs scripts/configurePrerelease.ts to prepare a build for nightly publishing"; - -const configureInsiders = () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "insiders", "package.json", "src/compiler/corePublic.ts"]); -task("configure-insiders", configureInsiders); -task("configure-insiders").description = "Runs scripts/configurePrerelease.mjs to prepare a build for insiders publishing"; - -const configureExperimental = () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "experimental", "package.json", "src/compiler/corePublic.ts"]); -task("configure-experimental", configureExperimental); -task("configure-experimental").description = "Runs scripts/configurePrerelease.mjs to prepare a build for experimental publishing"; - -const publishNightly = () => exec("npm", ["publish", "--tag", "next"]); -task("publish-nightly", series(task("clean"), task("LKG"), task("clean"), task("runtests-parallel"), publishNightly)); -task("publish-nightly").description = "Runs `npm publish --tag next` to create a new nightly build on npm"; - -// TODO(rbuckton): The problem with watching in this way is that a change in compiler/ will result -// in cascading changes in other projects that may take differing amounts of times to complete. As -// a result, the watch may accidentally trigger early, so we have to set a significant delay. An -// alternative approach would be to leverage a builder API, or to have 'tsc -b' have an option to -// write some kind of trigger file that indicates build completion that we could listen for instead. -const watchRuntests = () => watch(["built/local/*.js", "tests/cases/**/*.ts", "tests/cases/**/tsconfig.json"], { delay: 5000 }, async () => { - if (cmdLineOptions.tests || cmdLineOptions.failed) { - await runConsoleTests("built/local/run.js", "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ true); - } - else { - await runConsoleTests("built/local/run.js", "min", /*runInParallel*/ true, /*watchMode*/ true); - } -}); -task("watch", series(preBuild, preTest, parallel(watchLib, watchDiagnostics, watchServices, watchLssl, watchTests, watchRuntests))); -task("watch").description = "Watches for changes and rebuilds and runs tests in parallel."; -task("watch").flags = { - "-t --tests=": "Pattern for tests to run. Forces tests to be run in a single worker.", - " --failed": "Runs tests listed in '.failed-tests'. Forces tests to be run in a single worker.", - "-r --reporter=": "The mocha reporter to use.", - " --keepFailed": "Keep tests in .failed-tests even if they pass", - " --light": "Run tests in light mode (fewer verifications, but tests run faster)", - " --dirty": "Run tests without first cleaning test output directories", - " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", - " --no-color": "Disables color", - " --timeout=": "Overrides the default test timeout.", - " --workers=": "The number of parallel workers to use.", - " --built": "Compile using the built version of the compiler.", -}; - -task("default", series("local")); -task("default").description = "Runs 'local'"; - -task("help", () => exec("gulp", ["--tasks", "--depth", "1", "--sort-tasks"], { hidePrompt: true })); -task("help").description = "Prints the top-level tasks."; diff --git a/Herebyfile.mjs b/Herebyfile.mjs new file mode 100644 index 0000000000000..26ad0998e72d1 --- /dev/null +++ b/Herebyfile.mjs @@ -0,0 +1,798 @@ +// @ts-check +import path from "path"; +import fs from "fs"; +import del from "del"; +import { task } from "hereby"; +import _glob from "glob"; +import util from "util"; +import chalk from "chalk"; +import { exec, readJson, getDiffTool, getDirSize, memoize, needsUpdate } from "./scripts/build/utils.mjs"; +import { runConsoleTests, refBaseline, localBaseline, refRwcBaseline, localRwcBaseline } from "./scripts/build/tests.mjs"; +import { buildProject as realBuildProject, cleanProject, watchProject } from "./scripts/build/projects.mjs"; +import { localizationDirectories } from "./scripts/build/localization.mjs"; +import cmdLineOptions from "./scripts/build/options.mjs"; +import esbuild from "esbuild"; + +const glob = util.promisify(_glob); + +/** @typedef {ReturnType} Task */ +void 0; + +const copyrightFilename = "CopyrightNotice.txt"; +const copyright = memoize(async () => { + const contents = await fs.promises.readFile(copyrightFilename, "utf-8"); + return contents.replace(/\r\n/g, "\n"); +}); + + +// TODO(jakebailey): This is really gross. If the build is cancelled (i.e. Ctrl+C), the modification will persist. +// Waiting on: https://github.com/microsoft/TypeScript/issues/51164 +let currentlyBuilding = 0; +let oldTsconfigBase; + +/** @type {typeof realBuildProject} */ +const buildProjectWithEmit = async (...args) => { + const tsconfigBasePath = "./src/tsconfig-base.json"; + + // Not using fs.promises here, to ensure we are synchronous until running the real build. + + if (currentlyBuilding === 0) { + oldTsconfigBase = fs.readFileSync(tsconfigBasePath, "utf-8"); + fs.writeFileSync(tsconfigBasePath, oldTsconfigBase.replace(`"emitDeclarationOnly": true,`, `"emitDeclarationOnly": false, // DO NOT COMMIT`)); + } + + currentlyBuilding++; + + await realBuildProject(...args); + + currentlyBuilding--; + + if (currentlyBuilding === 0) { + fs.writeFileSync(tsconfigBasePath, oldTsconfigBase); + } +}; + + +const buildProject = cmdLineOptions.bundle ? realBuildProject : buildProjectWithEmit; + + +export const buildScripts = task({ + name: "scripts", + description: "Builds files in the 'scripts' folder.", + run: () => buildProject("scripts") +}); + +const libs = memoize(() => { + /** @type {{ libs: string[]; paths: Record; }} */ + const libraries = readJson("./src/lib/libs.json"); + const libs = libraries.libs.map(lib => { + const relativeSources = ["header.d.ts", lib + ".d.ts"]; + const relativeTarget = libraries.paths && libraries.paths[lib] || ("lib." + lib + ".d.ts"); + const sources = relativeSources.map(s => path.posix.join("src/lib", s)); + const target = `built/local/${relativeTarget}`; + return { target, sources }; + }); + return libs; +}); + + +export const generateLibs = task({ + name: "lib", + description: "Builds the library targets", + run: async () => { + await fs.promises.mkdir("./built/local", { recursive: true }); + for (const lib of libs()) { + let output = await copyright(); + + for (const source of lib.sources) { + const contents = await fs.promises.readFile(source, "utf-8"); + // TODO(jakebailey): "\n\n" is for compatibility with our current tests; our test baselines + // are sensitive to the positions of things in the lib files. Eventually remove this, + // or remove lib.d.ts line numbers from our baselines. + output += "\n\n" + contents.replace(/\r\n/g, "\n"); + } + + await fs.promises.writeFile(lib.target, output); + } + }, +}); + + +const diagnosticInformationMapTs = "src/compiler/diagnosticInformationMap.generated.ts"; +const diagnosticMessagesJson = "src/compiler/diagnosticMessages.json"; +const diagnosticMessagesGeneratedJson = "src/compiler/diagnosticMessages.generated.json"; + +export const generateDiagnostics = task({ + name: "generate-diagnostics", + description: "Generates a diagnostic file in TypeScript based on an input JSON file", + run: async () => { + await exec(process.execPath, ["scripts/processDiagnosticMessages.mjs", diagnosticMessagesJson]); + } +}); + +const cleanDiagnostics = task({ + name: "clean-diagnostics", + description: "Generates a diagnostic file in TypeScript based on an input JSON file", + hiddenFromTaskList: true, + run: () => del([diagnosticInformationMapTs, diagnosticMessagesGeneratedJson]), +}); + + +// Localize diagnostics +/** + * .lcg file is what localization team uses to know what messages to localize. + * The file is always generated in 'enu/diagnosticMessages.generated.json.lcg' + */ +const generatedLCGFile = "built/local/enu/diagnosticMessages.generated.json.lcg"; + +/** + * The localization target produces the two following transformations: + * 1. 'src\loc\lcl\\diagnosticMessages.generated.json.lcl' => 'built\local\\diagnosticMessages.generated.json' + * convert localized resources into a .json file the compiler can understand + * 2. 'src\compiler\diagnosticMessages.generated.json' => 'built\local\ENU\diagnosticMessages.generated.json.lcg' + * generate the lcg file (source of messages to localize) from the diagnosticMessages.generated.json + */ +const localizationTargets = localizationDirectories + .map(f => `built/local/${f}/diagnosticMessages.generated.json`) + .concat(generatedLCGFile); + +const localize = task({ + name: "localize", + dependencies: [generateDiagnostics], + run: async () => { + if (needsUpdate(diagnosticMessagesGeneratedJson, generatedLCGFile)) { + return exec(process.execPath, ["scripts/generateLocalizedDiagnosticMessages.mjs", "src/loc/lcl", "built/local", diagnosticMessagesGeneratedJson], { ignoreExitCode: true }); + } + } +}); + +export const buildSrc = task({ + name: "build-src", + description: "Builds the src project (all code)", + dependencies: [generateDiagnostics], + run: () => buildProject("src"), +}); + +export const watchSrc = task({ + name: "watch-src", + description: "Watches the src project (all code)", + hiddenFromTaskList: true, + dependencies: [generateDiagnostics], + run: () => watchProject("src"), +}); + +export const cleanSrc = task({ + name: "clean-src", + hiddenFromTaskList: true, + run: () => cleanProject("src"), +}); + +/** + * @param {string} entrypoint + * @param {string} output + */ +async function runDtsBundler(entrypoint, output) { + await exec(process.execPath, [ + "./scripts/dtsBundler.mjs", + "--entrypoint", + entrypoint, + "--output", + output, + ]); +} + + +/** + * @param {string} entrypoint + * @param {string} outfile + * @param {BundlerTaskOptions} [taskOptions] + * + * @typedef BundlerTaskOptions + * @property {string[]} [external] + * @property {boolean} [exportIsTsObject] + * @property {boolean} [treeShaking] + */ +function createBundler(entrypoint, outfile, taskOptions = {}) { + const getOptions = memoize(async () => { + /** @type {esbuild.BuildOptions} */ + const options = { + entryPoints: [entrypoint], + banner: { js: await copyright() }, + bundle: true, + outfile, + platform: "node", + target: "es2018", + format: "cjs", + sourcemap: "linked", + sourcesContent: false, + treeShaking: taskOptions.treeShaking, + external: [ + ...(taskOptions.external ?? []), + "source-map-support", + ], + logLevel: "warning", + // legalComments: "none", // If we add copyright headers to the source files, uncomment. + plugins: [ + { + name: "no-node-modules", + setup: (build) => { + build.onLoad({ filter: /[\\/]node_modules[\\/]/ }, () => { + // Ideally, we'd use "--external:./node_modules/*" here, but that doesn't work; we + // will instead end up with paths to node_modules rather than the package names. + // Instead, we'll return a load error when we see that we're trying to bundle from + // node_modules, then explicitly declare which external dependencies we rely on, which + // ensures that the correct module specifier is kept in the output (the non-wildcard + // form works properly). It also helps us keep tabs on what external dependencies we + // may be importing, which is handy. + // + // See: https://github.com/evanw/esbuild/issues/1958 + return { + errors: [{ text: 'Attempted to bundle from node_modules; ensure "external" is set correctly.' }] + }; + }); + } + }, + { + name: "fix-require", + setup: (build) => { + build.onEnd(async () => { + // esbuild converts calls to "require" to "__require"; this function + // calls the real require if it exists, or throws if it does not (rather than + // throwing an error like "require not defined"). But, since we want typescript + // to be consumable by other bundlers, we need to convert these calls back to + // require so our imports are visible again. + // + // The leading spaces are to keep the offsets the same within the files to keep + // source maps working (though this only really matters for the line the require is on). + // + // See: https://github.com/evanw/esbuild/issues/1905 + let contents = await fs.promises.readFile(outfile, "utf-8"); + contents = contents.replace(/__require\(/g, " require("); + await fs.promises.writeFile(outfile, contents); + }); + }, + } + ] + }; + + if (taskOptions.exportIsTsObject) { + // We use an IIFE so we can inject the footer, and so that "ts" is global if not loaded as a module. + options.format = "iife"; + // Name the variable ts, matching our old big bundle and so we can use the code below. + options.globalName = "ts"; + // If we are in a CJS context, export the ts namespace. + options.footer = { js: `\nif (typeof module !== "undefined" && module.exports) { module.exports = ts; }` }; + } + + return options; + }); + + return { + build: async () => esbuild.build(await getOptions()), + watch: async () => esbuild.build({ ...await getOptions(), watch: true, logLevel: "info" }), + }; +} + +let printedWatchWarning = false; + +/** + * @param {object} options + * @param {string} options.name + * @param {string} [options.description] + * @param {Task[]} [options.buildDeps] + * @param {string} options.project + * @param {string} options.srcEntrypoint + * @param {string} options.builtEntrypoint + * @param {string} options.output + * @param {Task[]} [options.mainDeps] + * @param {BundlerTaskOptions} [options.bundlerOptions] + */ +function entrypointBuildTask(options) { + const build = task({ + name: `build-${options.name}`, + dependencies: options.buildDeps, + run: () => buildProject(options.project), + }); + + const bundler = createBundler(options.srcEntrypoint, options.output, options.bundlerOptions); + + // If we ever need to bundle our own output, change this to depend on build + // and run esbuild on builtEntrypoint. + const bundle = task({ + name: `bundle-${options.name}`, + dependencies: options.buildDeps, + run: () => bundler.build(), + }); + + /** + * Writes a CJS module that reexports another CJS file. E.g. given + * `options.builtEntrypoint = "./built/local/tsc/tsc.js"` and + * `options.output = "./built/local/tsc.js"`, this will create a file + * named "./built/local/tsc.js" containing: + * + * ``` + * module.exports = require("./tsc/tsc.js") + * ``` + */ + const shim = task({ + name: `shim-${options.name}`, + run: async () => { + const outDir = path.dirname(options.output); + await fs.promises.mkdir(outDir, { recursive: true }); + const moduleSpecifier = path.relative(outDir, options.builtEntrypoint); + await fs.promises.writeFile(options.output, `module.exports = require("./${moduleSpecifier}")`); + }, + }); + + const main = task({ + name: options.name, + description: options.description, + dependencies: (options.mainDeps ?? []).concat(cmdLineOptions.bundle ? [bundle] : [build, shim]), + }); + + const watch = task({ + name: `watch-${options.name}`, + hiddenFromTaskList: true, // This is best effort. + dependencies: (options.buildDeps ?? []).concat(options.mainDeps ?? []).concat(cmdLineOptions.bundle ? [] : [shim]), + run: () => { + // These watch functions return promises that resolve once watch mode has started, + // allowing them to operate as regular tasks, while creating unresolved promises + // in the background that keep the process running after all tasks have exited. + if (!printedWatchWarning) { + console.error(chalk.yellowBright("Warning: watch mode is incomplete and may not work as expected. Use at your own risk.")); + printedWatchWarning = true; + } + + if (!cmdLineOptions.bundle) { + return watchProject(options.project); + } + return bundler.watch(); + } + }); + + return { build, bundle, shim, main, watch }; +} + + +const { main: tsc, watch: watchTsc } = entrypointBuildTask({ + name: "tsc", + description: "Builds the command-line compiler", + buildDeps: [generateDiagnostics], + project: "src/tsc", + srcEntrypoint: "./src/tsc/tsc.ts", + builtEntrypoint: "./built/local/tsc/tsc.js", + output: "./built/local/tsc.js", + mainDeps: [generateLibs], +}); +export { tsc, watchTsc }; + + +const { main: services, build: buildServices, watch: watchServices } = entrypointBuildTask({ + name: "services", + description: "Builds the typescript.js library", + buildDeps: [generateDiagnostics], + project: "src/typescript", + srcEntrypoint: "./src/typescript/typescript.ts", + builtEntrypoint: "./built/local/typescript/typescript.js", + output: "./built/local/typescript.js", + mainDeps: [generateLibs], + bundlerOptions: { exportIsTsObject: true }, +}); +export { services, watchServices }; + +export const dtsServices = task({ + name: "dts-services", + description: "Bundles typescript.d.ts", + dependencies: [buildServices], + run: async () => { + if (needsUpdate("./built/local/typescript/tsconfig.tsbuildinfo", ["./built/local/typescript.d.ts", "./built/local/typescript.internal.d.ts"])) { + runDtsBundler("./built/local/typescript/typescript.d.ts", "./built/local/typescript.d.ts"); + } + }, +}); + + +const { main: tsserver, watch: watchTsserver } = entrypointBuildTask({ + name: "tsserver", + description: "Builds the language server", + buildDeps: [generateDiagnostics], + project: "src/tsserver", + srcEntrypoint: "./src/tsserver/server.ts", + builtEntrypoint: "./built/local/tsserver/server.js", + output: "./built/local/tsserver.js", + mainDeps: [generateLibs], + // Even though this seems like an exectuable, so could be the default CJS, + // this is used in the browser too. Do the same thing that we do for our + // libraries and generate an IIFE with name `ts`, as to not pollute the global + // scope. + bundlerOptions: { exportIsTsObject: true }, +}); +export { tsserver, watchTsserver }; + + +export const min = task({ + name: "min", + description: "Builds only tsc and tsserver", + dependencies: [tsc, tsserver], +}); + +export const watchMin = task({ + name: "watch-min", + description: "Watches only tsc and tsserver", + hiddenFromTaskList: true, + dependencies: [watchTsc, watchTsserver], +}); + + + +const { main: lssl, build: buildLssl, watch: watchLssl } = entrypointBuildTask({ + name: "lssl", + description: "Builds language service server library", + buildDeps: [generateDiagnostics], + project: "src/tsserverlibrary", + srcEntrypoint: "./src/tsserverlibrary/tsserverlibrary.ts", + builtEntrypoint: "./built/local/tsserverlibrary/tsserverlibrary.js", + output: "./built/local/tsserverlibrary.js", + mainDeps: [generateLibs], + bundlerOptions: { exportIsTsObject: true }, +}); +export { lssl, watchLssl }; + +export const dtsLssl = task({ + name: "dts-lssl", + description: "Bundles tsserverlibrary.d.ts", + dependencies: [buildLssl], + run: async () => { + if (needsUpdate("./built/local/tsserverlibrary/tsconfig.tsbuildinfo", ["./built/local/tsserverlibrary.d.ts", "./built/local/tsserverlibrary.internal.d.ts"])) { + await runDtsBundler("./built/local/tsserverlibrary/tsserverlibrary.d.ts", "./built/local/tsserverlibrary.d.ts"); + } + } +}); + +export const dts = task({ + name: "dts", + dependencies: [dtsServices, dtsLssl], +}); + + +const testRunner = "./built/local/run.js"; + +const { main: tests, watch: watchTests } = entrypointBuildTask({ + name: "tests", + description: "Builds the test infrastructure", + buildDeps: [generateDiagnostics], + project: "src/testRunner", + srcEntrypoint: "./src/testRunner/_namespaces/Harness.ts", + builtEntrypoint: "./built/local/testRunner/runner.js", + output: testRunner, + mainDeps: [generateLibs], + bundlerOptions: { + // Ensure we never drop any dead code, which might be helpful while debugging. + treeShaking: false, + // These are directly imported via import statements and should not be bundled. + external: [ + "chai", + "del", + "diff", + "mocha", + "ms", + ], + }, +}); +export { tests, watchTests }; + + +export const runEslintRulesTests = task({ + name: "run-eslint-rules-tests", + description: "Runs the eslint rule tests", + run: () => runConsoleTests("scripts/eslint/tests", "mocha-fivemat-progress-reporter", /*runInParallel*/ false), +}); + +export const lint = task({ + name: "lint", + description: "Runs eslint on the compiler and scripts sources.", + run: async () => { + const folder = "."; + const formatter = cmdLineOptions.ci ? "stylish" : "autolinkable-stylish"; + const args = [ + "node_modules/eslint/bin/eslint", + "--cache", + "--cache-location", `${folder}/.eslintcache`, + "--format", formatter, + ]; + + if (cmdLineOptions.fix) { + args.push("--fix"); + } + + args.push(folder); + + console.log(`Linting: ${args.join(" ")}`); + return exec(process.execPath, args); + } +}); + +const { main: cancellationToken, watch: watchCancellationToken } = entrypointBuildTask({ + name: "cancellation-token", + project: "src/cancellationToken", + srcEntrypoint: "./src/cancellationToken/cancellationToken.ts", + builtEntrypoint: "./built/local/cancellationToken/cancellationToken.js", + output: "./built/local/cancellationToken.js", +}); + +const { main: typingsInstaller, watch: watchTypingsInstaller } = entrypointBuildTask({ + name: "typings-installer", + buildDeps: [generateDiagnostics], + project: "src/typingsInstaller", + srcEntrypoint: "./src/typingsInstaller/nodeTypingsInstaller.ts", + builtEntrypoint: "./built/local/typingsInstaller/nodeTypingsInstaller.js", + output: "./built/local/typingsInstaller.js", +}); + +const { main: watchGuard, watch: watchWatchGuard } = entrypointBuildTask({ + name: "watch-guard", + project: "src/watchGuard", + srcEntrypoint: "./src/watchGuard/watchGuard.ts", + builtEntrypoint: "./built/local/watchGuard/watchGuard.js", + output: "./built/local/watchGuard.js", +}); + +export const generateTypesMap = task({ + name: "generate-types-map", + run: async () => { + const source = "src/server/typesMap.json"; + const target = "built/local/typesMap.json"; + const contents = await fs.promises.readFile(source, "utf-8"); + JSON.parse(contents); // Validates that the JSON parses. + await fs.promises.writeFile(target, contents); + } +}); + + +// Drop a copy of diagnosticMessages.generated.json into the built/local folder. This allows +// it to be synced to the Azure DevOps repo, so that it can get picked up by the build +// pipeline that generates the localization artifacts that are then fed into the translation process. +const builtLocalDiagnosticMessagesGeneratedJson = "built/local/diagnosticMessages.generated.json"; +const copyBuiltLocalDiagnosticMessages = task({ + name: "copy-built-local-diagnostic-messages", + dependencies: [generateDiagnostics], + run: async () => { + const contents = await fs.promises.readFile(diagnosticMessagesGeneratedJson, "utf-8"); + JSON.parse(contents); // Validates that the JSON parses. + await fs.promises.writeFile(builtLocalDiagnosticMessagesGeneratedJson, contents); + } +}); + + +export const otherOutputs = task({ + name: "other-outputs", + description: "Builds miscelaneous scripts and documents distributed with the LKG", + dependencies: [cancellationToken, typingsInstaller, watchGuard, generateTypesMap, copyBuiltLocalDiagnosticMessages], +}); + +export const watchOtherOutputs = task({ + name: "watch-other-outputs", + description: "Builds miscelaneous scripts and documents distributed with the LKG", + hiddenFromTaskList: true, + dependencies: [watchCancellationToken, watchTypingsInstaller, watchWatchGuard, generateTypesMap, copyBuiltLocalDiagnosticMessages], +}); + +export const local = task({ + name: "local", + description: "Builds the full compiler and services", + dependencies: [localize, tsc, tsserver, services, lssl, otherOutputs, dts], +}); +export default local; + +export const watchLocal = task({ + name: "watch-local", + description: "Watches the full compiler and services", + hiddenFromTaskList: true, + dependencies: [localize, watchTsc, watchTsserver, watchServices, watchLssl, watchOtherOutputs, dts], +}); + + +export const runTests = task({ + name: "runtests", + description: "Runs the tests using the built run.js file.", + dependencies: [tests, generateLibs, dts], + run: () => runConsoleTests(testRunner, "mocha-fivemat-progress-reporter", /*runInParallel*/ false), +}); +// task("runtests").flags = { +// "-t --tests=": "Pattern for tests to run.", +// " --failed": "Runs tests listed in '.failed-tests'.", +// "-r --reporter=": "The mocha reporter to use.", +// "-i --break": "Runs tests in inspector mode (NodeJS 8 and later)", +// " --keepFailed": "Keep tests in .failed-tests even if they pass", +// " --light": "Run tests in light mode (fewer verifications, but tests run faster)", +// " --dirty": "Run tests without first cleaning test output directories", +// " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", +// " --no-color": "Disables color", +// " --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)", +// }; + +export const runTestsParallel = task({ + name: "runtests-parallel", + description: "Runs all the tests in parallel using the built run.js file.", + dependencies: [tests, generateLibs, dts], + run: () => runConsoleTests(testRunner, "min", /*runInParallel*/ cmdLineOptions.workers > 1), +}); +// task("runtests-parallel").flags = { +// " --light": "Run tests in light mode (fewer verifications, but tests run faster).", +// " --keepFailed": "Keep tests in .failed-tests even if they pass.", +// " --dirty": "Run tests without first cleaning test output directories.", +// " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", +// " --workers=": "The number of parallel workers to use.", +// " --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)", +// }; + +export const testBrowserIntegration = task({ + name: "test-browser-integration", + description: "Runs scripts/browserIntegrationTest.mjs which tests that typescript.js loads in a browser", + dependencies: [services], + run: () => exec(process.execPath, ["scripts/browserIntegrationTest.mjs"]), +}); + +export const diff = task({ + name: "diff", + description: "Diffs the compiler baselines using the diff tool specified by the 'DIFF' environment variable", + run: () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true, waitForExit: false }), +}); + +export const diffRwc = task({ + name: "diff-rwc", + description: "Diffs the RWC baselines using the diff tool specified by the 'DIFF' environment variable", + run: () => exec(getDiffTool(), [refRwcBaseline, localRwcBaseline], { ignoreExitCode: true, waitForExit: false }), +}); + +/** + * @param {string} localBaseline Path to the local copy of the baselines + * @param {string} refBaseline Path to the reference copy of the baselines + */ +function baselineAcceptTask(localBaseline, refBaseline) { + /** + * @param {string} p + */ + function localPathToRefPath(p) { + const relative = path.relative(localBaseline, p); + return path.join(refBaseline, relative); + } + + return async () => { + const toCopy = await glob(`${localBaseline}/**`, { nodir: true, ignore: `${localBaseline}/**/*.delete` }); + for (const p of toCopy) { + const out = localPathToRefPath(p); + await fs.promises.mkdir(path.dirname(out), { recursive: true }); + await fs.promises.copyFile(p, out); + } + const toDelete = await glob(`${localBaseline}/**/*.delete`, { nodir: true }); + for (const p of toDelete) { + const out = localPathToRefPath(p); + await fs.promises.rm(out); + } + }; +} + +export const baselineAccept = task({ + name: "baseline-accept", + description: "Makes the most recent test results the new baseline, overwriting the old baseline", + run: baselineAcceptTask(localBaseline, refBaseline), +}); + +export const baselineAcceptRwc = task({ + name: "baseline-accept-rwc", + description: "Makes the most recent rwc test results the new baseline, overwriting the old baseline", + run: baselineAcceptTask(localRwcBaseline, refRwcBaseline), +}); + +// TODO(rbuckton): Determine if we still need this task. Depending on a relative +// path here seems like a bad idea. +export const updateSublime = task({ + name: "update-sublime", + description: "Updates the sublime plugin's tsserver", + dependencies: [tsserver], + run: async () => { + for (const file of ["built/local/tsserver.js", "built/local/tsserver.js.map"]) { + await fs.promises.copyFile(file, path.resolve("../TypeScript-Sublime-Plugin/tsserver/", path.basename(file))); + } + } +}); + +// TODO(rbuckton): Should the path to DefinitelyTyped be configurable via an environment variable? +export const importDefinitelyTypedTests = task({ + name: "importDefinitelyTypedTests", + description: "Runs the importDefinitelyTypedTests script to copy DT's tests to the TS-internal RWC tests", + run: () => exec(process.execPath, ["scripts/importDefinitelyTypedTests.mjs", "./", "../DefinitelyTyped"]), +}); + + +export const produceLKG = task({ + name: "LKG", + description: "Makes a new LKG out of the built js files", + dependencies: [localize, tsc, tsserver, services, lssl, otherOutputs, dts], + run: async () => { + if (!cmdLineOptions.bundle) { + throw new Error("LKG cannot be created when --bundle=false"); + } + + const expectedFiles = [ + "built/local/cancellationToken.js", + "built/local/tsc.js", + "built/local/tsserver.js", + "built/local/tsserverlibrary.js", + "built/local/tsserverlibrary.d.ts", + "built/local/typescript.js", + "built/local/typescript.d.ts", + "built/local/typingsInstaller.js", + "built/local/watchGuard.js", + ].concat(libs().map(lib => lib.target)); + const missingFiles = expectedFiles + .concat(localizationTargets) + .filter(f => !fs.existsSync(f)); + if (missingFiles.length > 0) { + throw new Error("Cannot replace the LKG unless all built targets are present in directory 'built/local/'. The following files are missing:\n" + missingFiles.join("\n")); + } + const sizeBefore = getDirSize("lib"); + await exec(process.execPath, ["scripts/produceLKG.mjs"]); + const sizeAfter = getDirSize("lib"); + if (sizeAfter > (sizeBefore * 1.10)) { + throw new Error("The lib folder increased by 10% or more. This likely indicates a bug."); + } + } +}); + +export const lkg = task({ + name: "lkg", + hiddenFromTaskList: true, + dependencies: [produceLKG], +}); + +export const generateSpec = task({ + name: "generate-spec", + description: "Generates a Markdown version of the Language Specification", + hiddenFromTaskList: true, + run: () => exec("cscript", ["//nologo", "scripts/word2md.mjs", path.resolve("doc/TypeScript Language Specification - ARCHIVED.docx"), path.resolve("doc/spec-ARCHIVED.md")]), +}); + +export const cleanBuilt = task({ + name: "clean-built", + hiddenFromTaskList: true, + run: () => del("built"), +}); + +export const clean = task({ + name: "clean", + description: "Cleans build outputs", + dependencies: [cleanBuilt, cleanDiagnostics], +}); + +export const configureNightly = task({ + name: "configure-nightly", + description: "Runs scripts/configurePrerelease.mjs to prepare a build for nightly publishing", + run: () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "dev", "package.json", "src/compiler/corePublic.ts"]), +}); + +export const configureInsiders = task({ + name: "configure-insiders", + description: "Runs scripts/configurePrerelease.mjs to prepare a build for insiders publishing", + run: () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "insiders", "package.json", "src/compiler/corePublic.ts"]), +}); + +export const configureExperimental = task({ + name: "configure-experimental", + description: "Runs scripts/configurePrerelease.mjs to prepare a build for experimental publishing", + run: () => exec(process.execPath, ["scripts/configurePrerelease.mjs", "experimental", "package.json", "src/compiler/corePublic.ts"]), +}); + +export const help = task({ + name: "help", + description: "Prints the top-level tasks.", + hiddenFromTaskList: true, + run: () => exec("hereby", ["--tasks"], { hidePrompt: true }), +}); diff --git a/lib/README.md b/lib/README.md index ce0455fa40d3c..b62f7bad5caec 100644 --- a/lib/README.md +++ b/lib/README.md @@ -2,4 +2,4 @@ **These files are not meant to be edited by hand.** If you need to make modifications, the respective files should be changed within the repository's top-level `src` directory. -Running `gulp LKG` will then appropriately update the files in this directory. +Running `hereby LKG` will then appropriately update the files in this directory. diff --git a/package-lock.json b/package-lock.json index a4bd45434708d..8fde39bac7184 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,24 +15,15 @@ "devDependencies": { "@octokit/rest": "latest", "@types/chai": "latest", - "@types/fancy-log": "^2.0.0", "@types/fs-extra": "^9.0.13", "@types/glob": "latest", - "@types/gulp": "^4.0.9", - "@types/gulp-concat": "latest", - "@types/gulp-newer": "latest", - "@types/gulp-rename": "latest", - "@types/gulp-sourcemaps": "latest", - "@types/merge2": "latest", "@types/microsoft__typescript-etw": "latest", "@types/minimist": "latest", - "@types/mkdirp": "latest", "@types/mocha": "latest", "@types/ms": "latest", "@types/node": "latest", "@types/source-map-support": "latest", "@types/which": "^2.0.1", - "@types/xml2js": "^0.4.11", "@typescript-eslint/eslint-plugin": "^5.33.1", "@typescript-eslint/parser": "^5.33.1", "@typescript-eslint/utils": "^5.33.1", @@ -41,33 +32,26 @@ "chalk": "^4.1.2", "del": "^6.1.1", "diff": "^5.1.0", + "esbuild": "^0.15.13", "eslint": "^8.22.0", "eslint-formatter-autolinkable-stylish": "^1.2.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsdoc": "^39.3.6", "eslint-plugin-local": "^1.0.0", "eslint-plugin-no-null": "^1.0.2", - "fancy-log": "latest", + "fast-xml-parser": "^4.0.11", "fs-extra": "^9.1.0", "glob": "latest", - "gulp": "^4.0.2", - "gulp-concat": "latest", - "gulp-insert": "latest", - "gulp-newer": "latest", - "gulp-rename": "latest", - "gulp-sourcemaps": "latest", - "merge2": "latest", + "hereby": "^1.6.4", + "jsonc-parser": "^3.2.0", "minimist": "latest", - "mkdirp": "latest", "mocha": "latest", "mocha-fivemat-progress-reporter": "latest", "ms": "^2.1.3", "node-fetch": "^3.2.10", "source-map-support": "latest", "typescript": "^4.8.4", - "vinyl": "latest", - "which": "^2.0.2", - "xml2js": "^0.4.23" + "which": "^2.0.2" }, "engines": { "node": ">=4.2.0" @@ -87,6 +71,38 @@ "node": "^14 || ^16 || ^17 || ^18 || ^19" } }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz", + "integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz", + "integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -110,69 +126,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@gulp-sourcemaps/identity-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", - "integrity": "sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==", - "dev": true, - "dependencies": { - "acorn": "^6.4.1", - "normalize-path": "^3.0.0", - "postcss": "^7.0.16", - "source-map": "^0.6.0", - "through2": "^3.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "node_modules/@gulp-sourcemaps/map-sources": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", - "integrity": "sha512-o/EatdaGt8+x2qpb0vFLC/2Gug/xYPRXb6a+ET1wGYKozKN3krDWC/zZFZAtrzxJHuDL12mwdfEFKcKMNvc55A==", - "dev": true, - "dependencies": { - "normalize-path": "^2.0.1", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/@gulp-sourcemaps/map-sources/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", @@ -426,18 +379,6 @@ "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", "dev": true }, - "node_modules/@types/expect": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", - "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "dev": true - }, - "node_modules/@types/fancy-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/fancy-log/-/fancy-log-2.0.0.tgz", - "integrity": "sha512-g39Vp8ZJ3D0gXhhkhDidVvdy4QajkF7/PV6HGn23FMaMqE/tLC1JNHUeQ7SshKLsBjucakZsXBLkWULbGLdL5g==", - "dev": true - }, "node_modules/@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -457,65 +398,6 @@ "@types/node": "*" } }, - "node_modules/@types/glob-stream": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/glob-stream/-/glob-stream-6.1.1.tgz", - "integrity": "sha512-AGOUTsTdbPkRS0qDeyeS+6KypmfVpbT5j23SN8UPG63qjKXNKjXn6V9wZUr8Fin0m9l8oGYaPK8b2WUMF8xI1A==", - "dev": true, - "dependencies": { - "@types/glob": "*", - "@types/node": "*" - } - }, - "node_modules/@types/gulp": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/gulp/-/gulp-4.0.9.tgz", - "integrity": "sha512-zzT+wfQ8uwoXjDhRK9Zkmmk09/fbLLmN/yDHFizJiEKIve85qutOnXcP/TM2sKPBTU+Jc16vfPbOMkORMUBN7Q==", - "dev": true, - "dependencies": { - "@types/undertaker": "*", - "@types/vinyl-fs": "*", - "chokidar": "^3.3.1" - } - }, - "node_modules/@types/gulp-concat": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/gulp-concat/-/gulp-concat-0.0.33.tgz", - "integrity": "sha512-8On3yjnwXFKmGWm+P9GKBLzuYW9kGnhd3+sTj6cdIPFchYAEdqhc7Q3egTLK/Oqr083w1Q3SyRPAWEHAuxj4DQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/gulp-newer": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/gulp-newer/-/gulp-newer-0.0.32.tgz", - "integrity": "sha512-H9KRWsNr4Z/HfUU82yuDT9Vshps8BsUWjOBqUImIsk9iBU2a19d/FIl6yUqCx1juSwzmKx8jbZ45S7zP49aWQw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/gulp-rename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/gulp-rename/-/gulp-rename-2.0.1.tgz", - "integrity": "sha512-9ZjeS2RHEnmBmTcyi2+oeye3BgCsWhvi4uv3qCnAg8i6plOuRdaeNxjOves0ELysEXYLBl7bCl5fbVs7AZtgTA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/vinyl": "*" - } - }, - "node_modules/@types/gulp-sourcemaps": { - "version": "0.0.35", - "resolved": "https://registry.npmjs.org/@types/gulp-sourcemaps/-/gulp-sourcemaps-0.0.35.tgz", - "integrity": "sha512-vUBuizwA4CAV3Mke0DJYHQxyN4YOB1aAql284qAO7Et7fe0hmnPi/R9Fhu2UhxMuSxAwFktsJUOQk5dJHOU1eA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/vinyl": "*" - } - }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -528,15 +410,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/merge2": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@types/merge2/-/merge2-1.3.1.tgz", - "integrity": "sha512-aDA8WfZHU/J1OeQmrP+DfGrl1tb0cwNIuiiIqK3+S21gQ+aWg2n4OIvmFHpd2A4yQdZfjikC4XIYVNtX+b9qgg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/microsoft__typescript-etw": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@types/microsoft__typescript-etw/-/microsoft__typescript-etw-0.1.1.tgz", @@ -555,15 +428,6 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, - "node_modules/@types/mkdirp": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.2.tgz", - "integrity": "sha512-o0K1tSO0Dx5X6xlU5F1D6625FawhC3dU3iqr25lluNv/+/QIVH8RLNEiVokgIZo+mz+87w/3Mkg/VvQS+J51fQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/mocha": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.0.tgz", @@ -597,59 +461,12 @@ "source-map": "^0.6.0" } }, - "node_modules/@types/undertaker": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@types/undertaker/-/undertaker-1.2.8.tgz", - "integrity": "sha512-gW3PRqCHYpo45XFQHJBhch7L6hytPsIe0QeLujlnFsjHPnXLhJcPdN6a9368d7aIQgH2I/dUTPFBlGeSNA3qOg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/undertaker-registry": "*", - "async-done": "~1.3.2" - } - }, - "node_modules/@types/undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==", - "dev": true - }, - "node_modules/@types/vinyl": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz", - "integrity": "sha512-ayJ0iOCDNHnKpKTgBG6Q6JOnHTj9zFta+3j2b8Ejza0e4cvRyMn0ZoLEmbPrTHe5YYRlDYPvPWVdV4cTaRyH7g==", - "dev": true, - "dependencies": { - "@types/expect": "^1.20.4", - "@types/node": "*" - } - }, - "node_modules/@types/vinyl-fs": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@types/vinyl-fs/-/vinyl-fs-2.4.12.tgz", - "integrity": "sha512-LgBpYIWuuGsihnlF+OOWWz4ovwCYlT03gd3DuLwex50cYZLmX3yrW+sFF9ndtmh7zcZpS6Ri47PrIu+fV+sbXw==", - "dev": true, - "dependencies": { - "@types/glob-stream": "*", - "@types/node": "*", - "@types/vinyl": "*" - } - }, "node_modules/@types/which": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.1.tgz", "integrity": "sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ==", "dev": true }, - "node_modules/@types/xml2js": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.11.tgz", - "integrity": "sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.42.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz", @@ -887,54 +704,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "dependencies": { - "ansi-wrap": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha512-eCjan3AVo/SxZ0/MyIYRtkpxIu/H3xZN7URr1vXVrISxeyz8fUFz0FJziamK4sS8I+t35y4rHg1b2PklyBe/7A==", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -959,15 +728,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -981,114 +741,59 @@ "node": ">= 8" } }, - "node_modules/append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", - "dev": true, - "dependencies": { - "buffer-equal": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-diff/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", + "node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==", + "node_modules/array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", "dev": true, "dependencies": { - "make-iterator": "^1.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", - "dev": true, - "dependencies": { - "make-iterator": "^1.0.0" + "node": ">= 0.4" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arr-union": { + "node_modules/array-union": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -1097,246 +802,40 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-initial": { + "node_modules/assertion-error": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, - "dependencies": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": "*" } }, - "node_modules/array-initial/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 4.0.0" } }, - "node_modules/array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "dependencies": { - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "dependencies": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "node_modules/async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==", - "dev": true, - "dependencies": { - "async-done": "^1.2.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/azure-devops-node-api": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz", - "integrity": "sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==", + "node_modules/azure-devops-node-api": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz", + "integrity": "sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==", "dev": true, "dependencies": { "tunnel": "0.0.6", "typed-rest-client": "^1.8.4" } }, - "node_modules/bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==", - "dev": true, - "dependencies": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/before-after-hook": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", @@ -1352,16 +851,6 @@ "node": ">=8" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1390,44 +879,12 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "node_modules/buffer-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", - "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", - "dev": true, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -1450,15 +907,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/chai": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", @@ -1529,361 +977,188 @@ "fsevents": "~2.3.2" } }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "is-descriptor": "^0.1.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=7.0.0" } }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=4.0.0" } }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/command-line-usage": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", + "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", "dev": true, "dependencies": { - "is-buffer": "^1.1.5" + "array-back": "^4.0.2", + "chalk": "^2.4.2", + "table-layout": "^1.0.2", + "typical": "^5.2.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.0.0" } }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "node_modules/command-line-usage/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/command-line-usage/node_modules/array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "node_modules/command-line-usage/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/command-line-usage/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "color-name": "1.1.3" } }, - "node_modules/cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", + "node_modules/command-line-usage/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/command-line-usage/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" + "engines": { + "node": ">=0.8.0" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "node_modules/command-line-usage/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "node_modules/command-line-usage/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "dependencies": { - "ansi-regex": "^2.0.0" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "node_modules/command-line-usage/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", "dev": true, "engines": { - "node": ">=0.8" + "node": ">=8" } }, - "node_modules/clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", + "node_modules/comment-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", + "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">= 12.0.0" } }, - "node_modules/clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "node_modules/data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", "dev": true, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==", - "dev": true, - "dependencies": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/comment-parser": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", - "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", - "dev": true, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", - "dev": true, - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/copy-props": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", - "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", - "dev": true, - "dependencies": { - "each-props": "^1.3.2", - "is-plain-object": "^5.0.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", - "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "source-map": "^0.6.1", - "source-map-resolve": "^0.6.0" - } - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", - "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", - "dev": true, - "engines": { - "node": ">= 12" + "node": ">= 12" } }, "node_modules/debug": { @@ -1903,50 +1178,12 @@ } } }, - "node_modules/debug-fabulous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", - "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", - "dev": true, - "dependencies": { - "debug": "3.X", - "memoizee": "0.4.X", - "object-assign": "4.X" - } - }, - "node_modules/debug-fabulous/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/debug/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, "node_modules/deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -1959,33 +1196,21 @@ "node": ">=0.12" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "dev": true, - "dependencies": { - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha512-2xaP6GiwVwOEbXCGoJ4ufgC76m8cj805jrghScewJC2ZDsb9U0b4BIrba+xt/Uytyd0HvQ6+WymSRTfnYj59GQ==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -2002,19 +1227,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/del": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", @@ -2043,24 +1255,6 @@ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", "dev": true }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/diff": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", @@ -2094,64 +1288,12 @@ "node": ">=6.0.0" } }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" - } - }, - "node_modules/each-props/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-abstract": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", @@ -2216,66 +1358,373 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "node_modules/esbuild": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz", + "integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==", "dev": true, - "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.13", + "@esbuild/linux-loong64": "0.15.13", + "esbuild-android-64": "0.15.13", + "esbuild-android-arm64": "0.15.13", + "esbuild-darwin-64": "0.15.13", + "esbuild-darwin-arm64": "0.15.13", + "esbuild-freebsd-64": "0.15.13", + "esbuild-freebsd-arm64": "0.15.13", + "esbuild-linux-32": "0.15.13", + "esbuild-linux-64": "0.15.13", + "esbuild-linux-arm": "0.15.13", + "esbuild-linux-arm64": "0.15.13", + "esbuild-linux-mips64le": "0.15.13", + "esbuild-linux-ppc64le": "0.15.13", + "esbuild-linux-riscv64": "0.15.13", + "esbuild-linux-s390x": "0.15.13", + "esbuild-netbsd-64": "0.15.13", + "esbuild-openbsd-64": "0.15.13", + "esbuild-sunos-64": "0.15.13", + "esbuild-windows-32": "0.15.13", + "esbuild-windows-64": "0.15.13", + "esbuild-windows-arm64": "0.15.13" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz", + "integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "node_modules/es5-ext/node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha512-mc/caHeUcdjnC/boPWJefDr4KUIWQNv+tlnFnJd38QMou86QtxQzBJfxgGRzvx8jazYRqrVlaHarfO72uNxPOg==", - "dev": true - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "node_modules/esbuild-android-arm64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz", + "integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "node_modules/esbuild-darwin-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz", + "integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" } }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz", + "integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" } }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/esbuild-freebsd-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz", + "integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/escape-string-regexp": { + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz", + "integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz", + "integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz", + "integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz", + "integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz", + "integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz", + "integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz", + "integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz", + "integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz", + "integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz", + "integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz", + "integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz", + "integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz", + "integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz", + "integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz", + "integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", @@ -2657,750 +2106,676 @@ "node": ">=0.10.0" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.6.0" } }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-xml-parser": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", + "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==", "dev": true, "dependencies": { - "ms": "2.0.0" + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 4.9.1" } }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" + "reusify": "^1.0.4" } }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], "dependencies": { - "kind-of": "^3.0.2" + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" }, "engines": { - "node": ">=0.10.0" + "node": "^12.20 || >= 14.13" } }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { - "is-buffer": "^1.1.5" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=0.10.0" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", "dev": true, "dependencies": { - "is-buffer": "^1.1.5" + "array-back": "^3.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=4.0.0" } }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, - "engines": { - "node": ">=0.10.0" + "bin": { + "flat": "cli.js" } }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "dependencies": { - "homedir-polyfill": "^1.0.1" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "dependencies": { - "type": "^2.7.2" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "node_modules/extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", - "dev": true, - "dependencies": { - "kind-of": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend-shallow/node_modules/kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.0.0" } }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "dev": true, "dependencies": { - "is-descriptor": "^1.0.0" + "fetch-blob": "^3.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=12.20.0" } }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "dependencies": { - "is-extendable": "^0.1.0" + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/extglob/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, - "node_modules/fancy-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-2.0.0.tgz", - "integrity": "sha512-9CzxZbACXMUXW13tS0tI8XsGGmxWzO2DmYrGuBJOJ8k8q2K7hwfJA5qHjuPPe8wtsco33YR9wc+Rlr5wYFvhSA==", + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, - "dependencies": { - "color-support": "^1.1.3" - }, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10.13.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-fifo": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.1.0.tgz", - "integrity": "sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g==", + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" }, "engines": { - "node": ">=8.6.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, - "dependencies": { - "reusify": "^1.0.4" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, "engines": { - "node": "^12.20 || >= 14.13" + "node": "*" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", "dev": true, "dependencies": { - "flat-cache": "^3.0.4" + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/file-uri-to-path": { + "node_modules/get-symbol-description": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", "dev": true, "dependencies": { - "to-regex-range": "^5.0.1" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", "dev": true, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" + "is-glob": "^4.0.1" }, "engines": { - "node": ">= 0.10" + "node": ">= 6" } }, - "node_modules/findup-sync/node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/findup-sync/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", "dev": true, "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/findup-sync/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "dev": true, "dependencies": { - "is-extendable": "^0.1.0" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/findup-sync/node_modules/braces/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/findup-sync/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "function-bind": "^1.1.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4.0" } }, - "node_modules/findup-sync/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/findup-sync/node_modules/fill-range/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/findup-sync/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "get-intrinsic": "^1.1.1" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/findup-sync/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "dev": true, "dependencies": { - "is-buffer": "^1.1.5" + "has-symbols": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/findup-sync/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, - "engines": { - "node": ">=0.10.0" + "bin": { + "he": "bin/he" } }, - "node_modules/findup-sync/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "node_modules/hereby": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/hereby/-/hereby-1.6.4.tgz", + "integrity": "sha512-QN79Mrr+xsGLgiEk2htGf5qP+mIHlan/yuM+tLeDx0BeWmmwo6fvy+Pl9QxYwXRVTrDKC0PRzvqF6hIF9dKCtw==", "dev": true, "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "command-line-args": "^5.2.1", + "command-line-usage": "^6.1.3", + "fastest-levenshtein": "^1.0.16", + "foreground-child": "^2.0.0", + "import-meta-resolve": "^2.1.0", + "picocolors": "^1.0.0", + "pretty-ms": "^8.0.0" + }, + "bin": { + "hereby": "bin/hereby.js" }, "engines": { - "node": ">=0.10.0" + "node": ">= 12.20" } }, - "node_modules/findup-sync/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "node_modules/hereby/node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 4" } }, - "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fined/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/import-meta-resolve": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-2.1.0.tgz", + "integrity": "sha512-yG9pxkWJVTy4cmRsNWE3ztFdtFuYIV8G4N+cbCkO8b+qngkLyIUhxQFuZ0qJm67+0nUOxjMPT7nfksPKza1v2g==", "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=0.8.19" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "bin": { - "flat": "cli.js" + "engines": { + "node": ">=8" } }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "node_modules/irregular-plurals": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.3.0.tgz", + "integrity": "sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "dependencies": { - "for-in": "^1.0.1" + "has-bigints": "^1.0.1" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "dependencies": { - "fetch-blob": "^3.1.2" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">=12.20.0" + "node": ">=8" } }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "dependencies": { - "map-cache": "^0.2.2" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==", + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dev": true, "dependencies": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" + "has": "^1.0.3" }, - "engines": { - "node": ">= 0.10" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -3409,53 +2784,32 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "engines": { - "node": "*" + "node": ">=0.10.0" } }, - "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "is-extglob": "^2.1.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, "engines": { "node": ">= 0.4" }, @@ -3463,815 +2817,667 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=0.12.0" } }, - "node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, "engines": { - "node": ">= 6" + "node": ">=6" } }, - "node_modules/glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "dependencies": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, "engines": { - "node": ">= 0.10" + "node": ">=8" } }, - "node_modules/glob-stream/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-stream/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "node": ">=8" } }, - "node_modules/glob-stream/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, "engines": { "node": ">=0.10.0" } }, - "node_modules/glob-watcher": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", - "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "dependencies": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "normalize-path": "^3.0.0", - "object.defaults": "^1.1.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-watcher/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", "dev": true, "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-watcher/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "dependencies": { - "remove-trailing-separator": "^1.0.1" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-watcher/node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-watcher/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glob-watcher/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "call-bind": "^1.0.2" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-watcher/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-sdsl": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", + "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" + "argparse": "^2.0.1" }, - "optionalDependencies": { - "fsevents": "^1.2.7" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/glob-watcher/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "node_modules/jsdoc-type-pratt-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", + "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=12.0.0" } }, - "node_modules/glob-watcher/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, - "node_modules/glob-watcher/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" + "minimist": "^1.2.0" }, - "engines": { - "node": ">= 4.0" + "bin": { + "json5": "lib/cli.js" } }, - "node_modules/glob-watcher/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/glob-watcher/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "dependencies": { - "is-extglob": "^2.1.0" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/glob-watcher/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "binary-extensions": "^1.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glob-watcher/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true }, - "node_modules/glob-watcher/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glob-watcher/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/loupe": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.5.tgz", + "integrity": "sha512-KNGVjhsXDxvY/cYE8GNi7SBaJSfJIT+/+/8GlprqBXpoU6cSR7/RT7OBJOsoYtyxq0L3q6oIcO8tX7dbEEXr3A==", "dev": true, "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" + "get-func-name": "^2.0.0" } }, - "node_modules/glob-watcher/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { - "is-buffer": "^1.1.5" + "yallist": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/glob-watcher/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, - "node_modules/glob-watcher/node_modules/micromatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.6" } }, - "node_modules/glob-watcher/node_modules/micromatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "is-plain-object": "^2.0.4" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=0.10.0" + "node": "*" } }, - "node_modules/glob-watcher/node_modules/micromatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-watcher/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "node_modules/mocha": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", + "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", "dev": true, "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" }, "engines": { - "node": ">=0.10" + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" } }, - "node_modules/glob-watcher/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "node_modules/mocha-fivemat-progress-reporter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/mocha-fivemat-progress-reporter/-/mocha-fivemat-progress-reporter-0.1.0.tgz", + "integrity": "sha512-nCf6dmCEHObJ8BBrcjW+UHYvVtHEL+FliYR/Mfc/v7dKenNmBQ0ZSuvlICgsyQy9Tt581ldvh+SReS4qp4LrQw==", + "dev": true + }, + "node_modules/mocha/node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=10" + "node": ">=0.3.1" } }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "node_modules/mocha/node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "isexe": "^2.0.0" + "brace-expansion": "^1.1.7" }, - "bin": { - "which": "bin/which" + "engines": { + "node": "*" } }, - "node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "node_modules/mocha/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dev": true, "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "sparkles": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" + "balanced-match": "^1.0.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "node_modules/mocha/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "dependencies": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" - }, - "bin": { - "gulp": "bin/gulp.js" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 0.10" + "node": ">=8" } }, - "node_modules/gulp-cli/node_modules/fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/gulp-concat": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", - "integrity": "sha512-a2scActrQrDBpBbR3WUZGyGS1JEPLg5PZJdIa7/Bi3GuKAmPYDK6SFhy/NZq5R8KsKKFvtfR0fakbUCcKGCCjg==", + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "dependencies": { - "concat-with-sourcemaps": "^1.0.0", - "through2": "^2.0.0", - "vinyl": "^2.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/gulp-concat/node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "node_modules/mocha/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=10" } }, - "node_modules/gulp-concat/node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-insert": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/gulp-insert/-/gulp-insert-0.5.0.tgz", - "integrity": "sha512-SDKCWmjomAo0N0Bzj9qEKIfURORJR/72p6AbDBIK9yKZw794ROTrQHliBem+NJzS2GsTWSm8dGWJ5L7KtjnMRA==", - "dev": true, - "dependencies": { - "readable-stream": "^1.0.26-4", - "streamqueue": "0.0.6" + "node": ">=10" } }, - "node_modules/gulp-insert/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/gulp-insert/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/gulp-insert/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/gulp-newer": { + "node_modules/natural-compare-lite": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/gulp-newer/-/gulp-newer-1.4.0.tgz", - "integrity": "sha512-h79fGO55S/P9eAADbLAP9aTtVYpLSR1ONj08VPaSdVVNVYhTS8p1CO1TW7kEMu+hC+sytmCqcUr5LesvZEtDoQ==", - "dev": true, - "dependencies": { - "glob": "^7.0.3", - "kew": "^0.7.0", - "plugin-error": "^0.1.2" - } - }, - "node_modules/gulp-newer/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true }, - "node_modules/gulp-rename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", - "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], "engines": { - "node": ">=4" + "node": ">=10.5.0" } }, - "node_modules/gulp-sourcemaps": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-3.0.0.tgz", - "integrity": "sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ==", + "node_modules/node-fetch": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", "dev": true, "dependencies": { - "@gulp-sourcemaps/identity-map": "^2.0.1", - "@gulp-sourcemaps/map-sources": "^1.0.0", - "acorn": "^6.4.1", - "convert-source-map": "^1.0.0", - "css": "^3.0.0", - "debug-fabulous": "^1.0.0", - "detect-newline": "^2.0.0", - "graceful-fs": "^4.0.0", - "source-map": "^0.6.0", - "strip-bom-string": "^1.0.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gulp-sourcemaps/node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==", - "dev": true, - "dependencies": { - "glogg": "^1.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "engines": { - "node": ">= 0.10" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, "engines": { - "node": ">= 0.4.0" + "node": ">=0.10.0" } }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -4279,13 +3485,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" }, "engines": { "node": ">= 0.4" @@ -4294,3400 +3502,544 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/interpret": { + "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/irregular-plurals": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.3.0.tgz", - "integrity": "sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "wrappy": "1" } }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "dependencies": { - "kind-of": "^6.0.0" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true - }, - "node_modules/is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-sdsl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", - "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", - "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/just-debounce": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", - "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", - "dev": true - }, - "node_modules/kew": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", - "integrity": "sha512-IG6nm0+QtAMdXt9KvbgbGdvY50RSrw+U4sGZg+KlrSKPJEwVE5JVoI3d7RWfSMdBQneRheeAOj3lIjX5VL/9RQ==", - "dev": true - }, - "node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha512-U/VxvpX4N/rFvPzr3qG5EtLKEnNI0emvIQB3/ecEwv+8GHaUKbIB8vxv1Oai5FAF0d0r7LXHhLLe5K/yChm5GQ==", - "dev": true, - "dependencies": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", - "dev": true, - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow==", - "dev": true, - "dependencies": { - "flush-write-stream": "^1.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "dev": true, - "dependencies": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/liftoff/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-json-file/node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.5.tgz", - "integrity": "sha512-KNGVjhsXDxvY/cYE8GNi7SBaJSfJIT+/+/8GlprqBXpoU6cSR7/RT7OBJOsoYtyxq0L3q6oIcO8tX7dbEEXr3A==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", - "dev": true, - "dependencies": { - "es5-ext": "~0.10.2" - } - }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/make-iterator/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", - "dev": true, - "dependencies": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/matchdep/node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/braces/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/fill-range/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/matchdep/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", - "es6-weak-map": "^2.0.3", - "event-emitter": "^0.3.5", - "is-promise": "^2.2.2", - "lru-queue": "^0.1.0", - "next-tick": "^1.1.0", - "timers-ext": "^0.1.7" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", - "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha-fivemat-progress-reporter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/mocha-fivemat-progress-reporter/-/mocha-fivemat-progress-reporter-0.1.0.tgz", - "integrity": "sha512-nCf6dmCEHObJ8BBrcjW+UHYvVtHEL+FliYR/Mfc/v7dKenNmBQ0ZSuvlICgsyQy9Tt581ldvh+SReS4qp4LrQw==", - "dev": true - }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/mocha/node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/mocha/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "dev": true, - "optional": true - }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.2.10", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", - "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "dependencies": { - "once": "^1.3.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "dev": true, - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha512-naLhxxpUESbNkRqc35oQ2scZSJueHGQNUfMW/0U37IgN6tE2dgDWg3whf+NEliy3F/QysrO48XKUz/nGPe+AQw==", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", - "dev": true, - "dependencies": { - "lcid": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", - "dev": true - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", - "dev": true, - "dependencies": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plur": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", - "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==", - "dev": true, - "dependencies": { - "irregular-plurals": "^3.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", - "dev": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA==", - "dev": true, - "dependencies": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha512-CHPV/GAglbIB1tnQgaiysb8H2yCy8WQ7lcEwQ/eT+kLj0QHV8LnJW0zpqpE7RSkrMSRoa+EBoag86clf7WAgSg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A==", - "dev": true, - "dependencies": { - "value-or-function": "^3.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha512-Ny/iyOzSSa8M5ML46IAx3iXc6tfOsYU2R4AXi2UpHk60Zrgyq6eqPj/xiOfS0rRl/iiQ/rdJkVjw/5cdUyCntQ==", - "dev": true, - "dependencies": { - "sver-compat": "^1.5.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "dependencies": { - "is-descriptor": "^1.0.0" + "aggregate-error": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "dependencies": { - "kind-of": "^3.2.0" + "callsites": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/parse-ms": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-3.0.0.tgz", + "integrity": "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==", "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, "engines": { "node": ">=0.10.0" } }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, "engines": { - "node": ">=0.10.0" + "node": "*" } }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/plur": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", + "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==", "dev": true, "dependencies": { - "is-buffer": "^1.1.5" + "irregular-plurals": "^3.2.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/snapdragon/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "node_modules/pretty-ms": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-8.0.0.tgz", + "integrity": "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==", "dev": true, + "dependencies": { + "parse-ms": "^3.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/snapdragon/node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dev": true, "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "node_modules/source-map-resolve": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", - "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" + "safe-buffer": "^5.1.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", - "dev": true - }, - "node_modules/sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "node_modules/reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=6" } }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dev": true, "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", - "dev": true - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/split-string/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", "dev": true, "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "engines": { - "node": "*" + "node": ">=4" } }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, "engines": { + "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "dependencies": { - "is-descriptor": "^0.1.0" + "glob": "^7.1.3" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" + "queue-microtask": "^1.2.2" } }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "dependencies": { - "is-buffer": "^1.1.5" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" + "randombytes": "^2.1.0" } }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "node_modules/streamqueue": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/streamqueue/-/streamqueue-0.0.6.tgz", - "integrity": "sha512-l09LNfTUkmLMckTB1Mm8Um5GMS1uTZ/KTodg/SMf5Nx758IOsmaqIQ/AJumAnNMkDgZBG39btq3LVkN90knq8w==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "dependencies": { - "readable-stream": "^1.0.26-2" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">= 0.10.0" + "node": ">=8" } }, - "node_modules/streamqueue/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "node_modules/streamqueue/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "engines": { + "node": ">=8" } }, - "node_modules/streamqueue/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true - }, - "node_modules/streamx": { - "version": "2.12.5", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.12.5.tgz", - "integrity": "sha512-Y+nkFw57Z5JHT3zLlqFm3GccOy2FeYdUrrqita6Dd8kr/8enPn9GKa8IYf3/DmEKfZl/E2sWoSKUnd4qhonrgg==", + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dev": true, "dependencies": { - "fast-fifo": "^1.0.0", - "queue-tick": "^1.0.0" + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" + "engines": { + "node": ">=8" } }, - "node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, "engines": { "node": ">=0.10.0" } }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/string-width/node_modules/strip-ansi": { + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, + "node_modules/spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, "node_modules/string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -7737,15 +4089,6 @@ "node": ">=4" } }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -7758,146 +4101,75 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha512-aFTHfmjwizMNlNE6dsGmoAM4lHjL0CyiobWaFiXWSlD7cIxshW422Nb8KbXCmR6z+0ZEPY+daXJrDyh/vuwTyg==", - "dev": true, - "dependencies": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", - "dev": true, - "dependencies": { - "streamx": "^2.12.5" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", "dev": true }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "dependencies": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "node_modules/time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "es5-ext": "~0.10.46", - "next-tick": "1" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "node_modules/table-layout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", + "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", "dev": true, "dependencies": { - "kind-of": "^3.0.2" + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.0.0" } }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/table-layout/node_modules/array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "node_modules/table-layout/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7910,31 +4182,6 @@ "node": ">=8.0" } }, - "node_modules/to-regex/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", - "dev": true, - "dependencies": { - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -7983,12 +4230,6 @@ "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8013,413 +4254,91 @@ "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-rest-client": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.9.tgz", - "integrity": "sha512-uSmjE38B80wjL85UFX3sTYEUlvZ1JgCRhsWj/fJ4rZ0FqDUFoIuodtiVeE+cUqiVTOKPdKrp/sdftD15MDek6g==", - "dev": true, - "dependencies": { - "qs": "^6.9.1", - "tunnel": "0.0.6", - "underscore": "^1.12.1" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true - }, - "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", - "dev": true - }, - "node_modules/undertaker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", - "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "fast-levenshtein": "^1.0.0", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/undertaker/node_modules/fast-levenshtein": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", - "integrity": "sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==", - "dev": true - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "dev": true, - "dependencies": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, - "node_modules/universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", - "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", - "dev": true, - "dependencies": { - "clone": "^2.1.2", - "clone-stats": "^1.0.0", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "dependencies": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" - }, "engines": { - "node": ">= 0.10" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vinyl-fs/node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "node_modules/typed-rest-client": { + "version": "1.8.9", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.9.tgz", + "integrity": "sha512-uSmjE38B80wjL85UFX3sTYEUlvZ1JgCRhsWj/fJ4rZ0FqDUFoIuodtiVeE+cUqiVTOKPdKrp/sdftD15MDek6g==", "dev": true, - "engines": { - "node": ">= 0.10" + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" } }, - "node_modules/vinyl-fs/node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "dev": true, - "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">= 0.10" + "node": ">=4.2.0" } }, - "node_modules/vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", + "node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", "dev": true, - "dependencies": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - }, "engines": { - "node": ">= 0.10" + "node": ">=8" } }, - "node_modules/vinyl-sourcemap/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "dependencies": { - "remove-trailing-separator": "^1.0.1" + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vinyl-sourcemap/node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", + "dev": true + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">= 10.0.0" } }, - "node_modules/vinyl-sourcemap/node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" + "punycode": "^2.1.0" } }, "node_modules/web-streams-polyfill": { @@ -8478,12 +4397,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "dev": true - }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -8493,45 +4406,33 @@ "node": ">=0.10.0" } }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", + "node_modules/wordwrapjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", + "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", "dev": true, "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "reduce-flatten": "^2.0.0", + "typical": "^5.2.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.0.0" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "node_modules/wordwrapjs/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true }, "node_modules/wrappy": { "version": "1.0.2", @@ -8539,70 +4440,12 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/yargs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", - "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.1" - } - }, "node_modules/yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", @@ -8651,16 +4494,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -8686,6 +4519,20 @@ "jsdoc-type-pratt-parser": "~3.1.0" } }, + "@esbuild/android-arm": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.13.tgz", + "integrity": "sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.13.tgz", + "integrity": "sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==", + "dev": true, + "optional": true + }, "@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -8703,58 +4550,6 @@ "strip-json-comments": "^3.1.1" } }, - "@gulp-sourcemaps/identity-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", - "integrity": "sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==", - "dev": true, - "requires": { - "acorn": "^6.4.1", - "normalize-path": "^3.0.0", - "postcss": "^7.0.16", - "source-map": "^0.6.0", - "through2": "^3.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - }, - "through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - } - } - }, - "@gulp-sourcemaps/map-sources": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", - "integrity": "sha512-o/EatdaGt8+x2qpb0vFLC/2Gug/xYPRXb6a+ET1wGYKozKN3krDWC/zZFZAtrzxJHuDL12mwdfEFKcKMNvc55A==", - "dev": true, - "requires": { - "normalize-path": "^2.0.1", - "through2": "^2.0.3" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, "@humanwhocodes/config-array": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", @@ -8945,18 +4740,6 @@ "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", "dev": true }, - "@types/expect": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", - "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "dev": true - }, - "@types/fancy-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/fancy-log/-/fancy-log-2.0.0.tgz", - "integrity": "sha512-g39Vp8ZJ3D0gXhhkhDidVvdy4QajkF7/PV6HGn23FMaMqE/tLC1JNHUeQ7SshKLsBjucakZsXBLkWULbGLdL5g==", - "dev": true - }, "@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -8976,65 +4759,6 @@ "@types/node": "*" } }, - "@types/glob-stream": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/glob-stream/-/glob-stream-6.1.1.tgz", - "integrity": "sha512-AGOUTsTdbPkRS0qDeyeS+6KypmfVpbT5j23SN8UPG63qjKXNKjXn6V9wZUr8Fin0m9l8oGYaPK8b2WUMF8xI1A==", - "dev": true, - "requires": { - "@types/glob": "*", - "@types/node": "*" - } - }, - "@types/gulp": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/gulp/-/gulp-4.0.9.tgz", - "integrity": "sha512-zzT+wfQ8uwoXjDhRK9Zkmmk09/fbLLmN/yDHFizJiEKIve85qutOnXcP/TM2sKPBTU+Jc16vfPbOMkORMUBN7Q==", - "dev": true, - "requires": { - "@types/undertaker": "*", - "@types/vinyl-fs": "*", - "chokidar": "^3.3.1" - } - }, - "@types/gulp-concat": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/gulp-concat/-/gulp-concat-0.0.33.tgz", - "integrity": "sha512-8On3yjnwXFKmGWm+P9GKBLzuYW9kGnhd3+sTj6cdIPFchYAEdqhc7Q3egTLK/Oqr083w1Q3SyRPAWEHAuxj4DQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/gulp-newer": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/gulp-newer/-/gulp-newer-0.0.32.tgz", - "integrity": "sha512-H9KRWsNr4Z/HfUU82yuDT9Vshps8BsUWjOBqUImIsk9iBU2a19d/FIl6yUqCx1juSwzmKx8jbZ45S7zP49aWQw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/gulp-rename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/gulp-rename/-/gulp-rename-2.0.1.tgz", - "integrity": "sha512-9ZjeS2RHEnmBmTcyi2+oeye3BgCsWhvi4uv3qCnAg8i6plOuRdaeNxjOves0ELysEXYLBl7bCl5fbVs7AZtgTA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/vinyl": "*" - } - }, - "@types/gulp-sourcemaps": { - "version": "0.0.35", - "resolved": "https://registry.npmjs.org/@types/gulp-sourcemaps/-/gulp-sourcemaps-0.0.35.tgz", - "integrity": "sha512-vUBuizwA4CAV3Mke0DJYHQxyN4YOB1aAql284qAO7Et7fe0hmnPi/R9Fhu2UhxMuSxAwFktsJUOQk5dJHOU1eA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/vinyl": "*" - } - }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -9047,15 +4771,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "@types/merge2": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@types/merge2/-/merge2-1.3.1.tgz", - "integrity": "sha512-aDA8WfZHU/J1OeQmrP+DfGrl1tb0cwNIuiiIqK3+S21gQ+aWg2n4OIvmFHpd2A4yQdZfjikC4XIYVNtX+b9qgg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/microsoft__typescript-etw": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@types/microsoft__typescript-etw/-/microsoft__typescript-etw-0.1.1.tgz", @@ -9074,15 +4789,6 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, - "@types/mkdirp": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.2.tgz", - "integrity": "sha512-o0K1tSO0Dx5X6xlU5F1D6625FawhC3dU3iqr25lluNv/+/QIVH8RLNEiVokgIZo+mz+87w/3Mkg/VvQS+J51fQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/mocha": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.0.tgz", @@ -9116,59 +4822,12 @@ "source-map": "^0.6.0" } }, - "@types/undertaker": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@types/undertaker/-/undertaker-1.2.8.tgz", - "integrity": "sha512-gW3PRqCHYpo45XFQHJBhch7L6hytPsIe0QeLujlnFsjHPnXLhJcPdN6a9368d7aIQgH2I/dUTPFBlGeSNA3qOg==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/undertaker-registry": "*", - "async-done": "~1.3.2" - } - }, - "@types/undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==", - "dev": true - }, - "@types/vinyl": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz", - "integrity": "sha512-ayJ0iOCDNHnKpKTgBG6Q6JOnHTj9zFta+3j2b8Ejza0e4cvRyMn0ZoLEmbPrTHe5YYRlDYPvPWVdV4cTaRyH7g==", - "dev": true, - "requires": { - "@types/expect": "^1.20.4", - "@types/node": "*" - } - }, - "@types/vinyl-fs": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@types/vinyl-fs/-/vinyl-fs-2.4.12.tgz", - "integrity": "sha512-LgBpYIWuuGsihnlF+OOWWz4ovwCYlT03gd3DuLwex50cYZLmX3yrW+sFF9ndtmh7zcZpS6Ri47PrIu+fV+sbXw==", - "dev": true, - "requires": { - "@types/glob-stream": "*", - "@types/node": "*", - "@types/vinyl": "*" - } - }, "@types/which": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.1.tgz", "integrity": "sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ==", "dev": true }, - "@types/xml2js": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.11.tgz", - "integrity": "sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@typescript-eslint/eslint-plugin": { "version": "5.42.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz", @@ -9287,55 +4946,19 @@ "dev": true, "requires": { "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "requires": { - "ansi-wrap": "^0.1.0" - } - }, - "ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha512-eCjan3AVo/SxZ0/MyIYRtkpxIu/H3xZN7URr1vXVrISxeyz8fUFz0FJziamK4sS8I+t35y4rHg1b2PklyBe/7A==", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" + "indent-string": "^4.0.0" } }, - "ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==", + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { - "ansi-wrap": "0.1.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ansi-regex": { @@ -9353,12 +4976,6 @@ "color-convert": "^2.0.1" } }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", - "dev": true - }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -9369,79 +4986,16 @@ "picomatch": "^2.0.4" } }, - "append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", - "dev": true, - "requires": { - "buffer-equal": "^1.0.0" - } - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "dependencies": { - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", - "dev": true - } - } - }, - "arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==", - "dev": true, - "requires": { - "make-iterator": "^1.0.0" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", - "dev": true, - "requires": { - "make-iterator": "^1.0.0" - } - }, - "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", - "dev": true - }, - "array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", "dev": true }, "array-includes": { @@ -9457,70 +5011,12 @@ "is-string": "^1.0.7" } }, - "array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==", - "dev": true, - "requires": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true - }, - "array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "requires": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - } - }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true - }, "array.prototype.flat": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", @@ -9539,51 +5035,12 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true - }, - "async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - } - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==", - "dev": true, - "requires": { - "async-done": "^1.2.2" - } - }, "at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, "azure-devops-node-api": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz", @@ -9594,55 +5051,12 @@ "typed-rest-client": "^1.8.4" } }, - "bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==", - "dev": true, - "requires": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, "before-after-hook": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", @@ -9655,16 +5069,6 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -9690,274 +5094,81 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "buffer-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", - "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", - "dev": true - }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", - "dev": true - }, - "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - } - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "dev": true - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dev": true, "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==", + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", "dev": true, "requires": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" } }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -9973,11 +5184,99 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true + "command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "dev": true, + "requires": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + } + }, + "command-line-usage": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", + "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", + "dev": true, + "requires": { + "array-back": "^4.0.2", + "chalk": "^2.4.2", + "table-layout": "^1.0.2", + "typical": "^5.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } + } }, "comment-parser": { "version": "1.3.1", @@ -9985,67 +5284,12 @@ "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", "dev": true }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - } - }, - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "dev": true - }, - "copy-props": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", - "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", - "dev": true, - "requires": { - "each-props": "^1.3.2", - "is-plain-object": "^5.0.0" - } - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -10057,27 +5301,6 @@ "which": "^2.0.1" } }, - "css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", - "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "source-map": "^0.6.1", - "source-map-resolve": "^0.6.0" - } - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "0.10.53", - "type": "^1.0.1" - } - }, "data-uri-to-buffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", @@ -10101,40 +5324,6 @@ } } }, - "debug-fabulous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", - "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", - "dev": true, - "requires": { - "debug": "3.X", - "memoizee": "0.4.X", - "object-assign": "4.X" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", - "dev": true - }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -10144,27 +5333,18 @@ "type-detect": "^4.0.0" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "dev": true, - "requires": { - "kind-of": "^5.0.2" - } - }, - "default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha512-2xaP6GiwVwOEbXCGoJ4ufgC76m8cj805jrghScewJC2ZDsb9U0b4BIrba+xt/Uytyd0HvQ6+WymSRTfnYj59GQ==", - "dev": true - }, "define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -10175,16 +5355,6 @@ "object-keys": "^1.1.1" } }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, "del": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", @@ -10207,18 +5377,6 @@ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", "dev": true }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "dev": true - }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", - "dev": true - }, "diff": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", @@ -10243,63 +5401,12 @@ "esutils": "^2.0.2" } }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" - }, - "dependencies": { - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - } - } - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, "es-abstract": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", @@ -10337,72 +5444,190 @@ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", "dev": true, - "requires": { - "has": "^1.0.3" - } + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "esbuild": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.13.tgz", + "integrity": "sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.15.13", + "@esbuild/linux-loong64": "0.15.13", + "esbuild-android-64": "0.15.13", + "esbuild-android-arm64": "0.15.13", + "esbuild-darwin-64": "0.15.13", + "esbuild-darwin-arm64": "0.15.13", + "esbuild-freebsd-64": "0.15.13", + "esbuild-freebsd-arm64": "0.15.13", + "esbuild-linux-32": "0.15.13", + "esbuild-linux-64": "0.15.13", + "esbuild-linux-arm": "0.15.13", + "esbuild-linux-arm64": "0.15.13", + "esbuild-linux-mips64le": "0.15.13", + "esbuild-linux-ppc64le": "0.15.13", + "esbuild-linux-riscv64": "0.15.13", + "esbuild-linux-s390x": "0.15.13", + "esbuild-netbsd-64": "0.15.13", + "esbuild-openbsd-64": "0.15.13", + "esbuild-sunos-64": "0.15.13", + "esbuild-windows-32": "0.15.13", + "esbuild-windows-64": "0.15.13", + "esbuild-windows-arm64": "0.15.13" + } + }, + "esbuild-android-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.13.tgz", + "integrity": "sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.13.tgz", + "integrity": "sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.13.tgz", + "integrity": "sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.13.tgz", + "integrity": "sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.13.tgz", + "integrity": "sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.13.tgz", + "integrity": "sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.13.tgz", + "integrity": "sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.13.tgz", + "integrity": "sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.13.tgz", + "integrity": "sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.13.tgz", + "integrity": "sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.13.tgz", + "integrity": "sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.13.tgz", + "integrity": "sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.13.tgz", + "integrity": "sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.13.tgz", + "integrity": "sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==", + "dev": true, + "optional": true }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "esbuild-netbsd-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.13.tgz", + "integrity": "sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==", "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } + "optional": true }, - "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "esbuild-openbsd-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.13.tgz", + "integrity": "sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==", "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - }, - "dependencies": { - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha512-mc/caHeUcdjnC/boPWJefDr4KUIWQNv+tlnFnJd38QMou86QtxQzBJfxgGRzvx8jazYRqrVlaHarfO72uNxPOg==", - "dev": true - } - } + "optional": true }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "esbuild-sunos-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.13.tgz", + "integrity": "sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==", "dev": true, - "requires": { - "d": "1", - "es5-ext": "0.10.53", - "es6-symbol": "^3.1.1" - } + "optional": true }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "esbuild-windows-32": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.13.tgz", + "integrity": "sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==", "dev": true, - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } + "optional": true }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "esbuild-windows-64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.13.tgz", + "integrity": "sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==", "dev": true, - "requires": { - "d": "1", - "es5-ext": "0.10.53", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.13", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.13.tgz", + "integrity": "sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==", + "dev": true, + "optional": true }, "escalade": { "version": "3.1.1", @@ -10706,235 +5931,12 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "0.10.53" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "requires": { - "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", - "dev": true, - "requires": { - "kind-of": "^1.1.0" - }, - "dependencies": { - "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - } - } - }, - "fancy-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-2.0.0.tgz", - "integrity": "sha512-9CzxZbACXMUXW13tS0tI8XsGGmxWzO2DmYrGuBJOJ8k8q2K7hwfJA5qHjuPPe8wtsco33YR9wc+Rlr5wYFvhSA==", - "dev": true, - "requires": { - "color-support": "^1.1.3" - } - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "fast-fifo": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.1.0.tgz", - "integrity": "sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g==", - "dev": true - }, "fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", @@ -10960,6 +5962,21 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-xml-parser": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", + "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==", + "dev": true, + "requires": { + "strnum": "^1.0.5" + } + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -10988,13 +6005,6 @@ "flat-cache": "^3.0.4" } }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -11004,6 +6014,15 @@ "to-regex-range": "^5.0.1" } }, + "find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "requires": { + "array-back": "^3.0.1" + } + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -11014,187 +6033,6 @@ "path-exists": "^4.0.0" } }, - "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "dependencies": { - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - } - } - }, - "flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true - }, "flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -11217,29 +6055,14 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true - }, - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "requires": { - "for-in": "^1.0.1" + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" } }, "formdata-polyfill": { @@ -11251,15 +6074,6 @@ "fetch-blob": "^3.1.2" } }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -11272,16 +6086,6 @@ "universalify": "^2.0.0" } }, - "fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -11319,12 +6123,6 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", @@ -11352,12 +6150,6 @@ "get-intrinsic": "^1.1.1" } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "dev": true - }, "glob": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", @@ -11400,346 +6192,6 @@ "is-glob": "^4.0.1" } }, - "glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", - "dev": true, - "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "glob-watcher": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", - "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "normalize-path": "^3.0.0", - "object.defaults": "^1.1.0" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", - "dev": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "dependencies": { - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, "globals": { "version": "13.17.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", @@ -11763,15 +6215,6 @@ "slash": "^3.0.0" } }, - "glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -11784,196 +6227,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", - "dev": true, - "requires": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - } - }, - "gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" - }, - "dependencies": { - "fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - } - } - } - }, - "gulp-concat": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", - "integrity": "sha512-a2scActrQrDBpBbR3WUZGyGS1JEPLg5PZJdIa7/Bi3GuKAmPYDK6SFhy/NZq5R8KsKKFvtfR0fakbUCcKGCCjg==", - "dev": true, - "requires": { - "concat-with-sourcemaps": "^1.0.0", - "through2": "^2.0.0", - "vinyl": "^2.0.0" - }, - "dependencies": { - "replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "dev": true - }, - "vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - } - } - }, - "gulp-insert": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/gulp-insert/-/gulp-insert-0.5.0.tgz", - "integrity": "sha512-SDKCWmjomAo0N0Bzj9qEKIfURORJR/72p6AbDBIK9yKZw794ROTrQHliBem+NJzS2GsTWSm8dGWJ5L7KtjnMRA==", - "dev": true, - "requires": { - "readable-stream": "^1.0.26-4", - "streamqueue": "0.0.6" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true - } - } - }, - "gulp-newer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/gulp-newer/-/gulp-newer-1.4.0.tgz", - "integrity": "sha512-h79fGO55S/P9eAADbLAP9aTtVYpLSR1ONj08VPaSdVVNVYhTS8p1CO1TW7kEMu+hC+sytmCqcUr5LesvZEtDoQ==", - "dev": true, - "requires": { - "glob": "^7.0.3", - "kew": "^0.7.0", - "plugin-error": "^0.1.2" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "gulp-rename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", - "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", - "dev": true - }, - "gulp-sourcemaps": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-3.0.0.tgz", - "integrity": "sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ==", - "dev": true, - "requires": { - "@gulp-sourcemaps/identity-map": "^2.0.1", - "@gulp-sourcemaps/map-sources": "^1.0.0", - "acorn": "^6.4.1", - "convert-source-map": "^1.0.0", - "css": "^3.0.0", - "debug-fabulous": "^1.0.0", - "detect-newline": "^2.0.0", - "graceful-fs": "^4.0.0", - "source-map": "^0.6.0", - "strip-bom-string": "^1.0.0", - "through2": "^2.0.0" - }, - "dependencies": { - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - } - } - }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==", - "dev": true, - "requires": { - "glogg": "^1.0.0" - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -12019,79 +6272,35 @@ "has-symbols": "^1.0.2" } }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "hereby": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/hereby/-/hereby-1.6.4.tgz", + "integrity": "sha512-QN79Mrr+xsGLgiEk2htGf5qP+mIHlan/yuM+tLeDx0BeWmmwo6fvy+Pl9QxYwXRVTrDKC0PRzvqF6hIF9dKCtw==", "dev": true, "requires": { - "parse-passwd": "^1.0.0" + "command-line-args": "^5.2.1", + "command-line-usage": "^6.1.3", + "fastest-levenshtein": "^1.0.16", + "foreground-child": "^2.0.0", + "import-meta-resolve": "^2.1.0", + "picocolors": "^1.0.0", + "pretty-ms": "^8.0.0" + }, + "dependencies": { + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + } } }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, "ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -12108,6 +6317,12 @@ "resolve-from": "^4.0.0" } }, + "import-meta-resolve": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-2.1.0.tgz", + "integrity": "sha512-yG9pxkWJVTy4cmRsNWE3ztFdtFuYIV8G4N+cbCkO8b+qngkLyIUhxQFuZ0qJm67+0nUOxjMPT7nfksPKza1v2g==", + "dev": true + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -12136,12 +6351,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, "internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -12153,57 +6362,12 @@ "side-channel": "^1.0.4" } }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", - "dev": true - }, "irregular-plurals": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.3.0.tgz", "integrity": "sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==", "dev": true }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, "is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -12232,12 +6396,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -12253,23 +6411,6 @@ "has": "^1.0.3" } }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, "is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -12279,60 +6420,12 @@ "has-tostringtag": "^1.0.0" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - }, - "dependencies": { - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - } - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -12342,12 +6435,6 @@ "is-extglob": "^2.1.1" } }, - "is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", - "dev": true - }, "is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -12393,12 +6480,6 @@ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true }, - "is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true - }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -12409,15 +6490,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "requires": { - "is-unc-path": "^1.0.0" - } - }, "is-shared-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", @@ -12445,33 +6517,12 @@ "has-symbols": "^1.0.2" } }, - "is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "requires": { - "unc-path-regex": "^0.1.2" - } - }, "is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true - }, - "is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", - "dev": true - }, "is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -12481,30 +6532,12 @@ "call-bind": "^1.0.2" } }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, "js-sdsl": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", @@ -12547,6 +6580,12 @@ "minimist": "^1.2.0" } }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -12557,61 +6596,6 @@ "universalify": "^2.0.0" } }, - "just-debounce": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", - "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", - "dev": true - }, - "kew": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", - "integrity": "sha512-IG6nm0+QtAMdXt9KvbgbGdvY50RSrw+U4sGZg+KlrSKPJEwVE5JVoI3d7RWfSMdBQneRheeAOj3lIjX5VL/9RQ==", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - }, - "last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha512-U/VxvpX4N/rFvPzr3qG5EtLKEnNI0emvIQB3/ecEwv+8GHaUKbIB8vxv1Oai5FAF0d0r7LXHhLLe5K/yChm5GQ==", - "dev": true, - "requires": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - } - }, - "lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dev": true, - "requires": { - "readable-stream": "^2.0.5" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow==", - "dev": true, - "requires": { - "flush-write-stream": "^1.0.2" - } - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -12622,57 +6606,6 @@ "type-check": "~0.4.0" } }, - "liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "dev": true, - "requires": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - }, - "dependencies": { - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - } - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - } - } - }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -12682,6 +6615,12 @@ "p-locate": "^5.0.0" } }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -12716,235 +6655,6 @@ "yallist": "^4.0.0" } }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", - "dev": true, - "requires": { - "es5-ext": "0.10.53" - } - }, - "make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", - "dev": true, - "requires": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - } - } - }, - "findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - } - }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", - "dev": true, - "requires": { - "d": "^1.0.1", - "es5-ext": "0.10.53", - "es6-weak-map": "^2.0.3", - "event-emitter": "^0.3.5", - "is-promise": "^2.2.2", - "lru-queue": "^0.1.0", - "next-tick": "^1.1.0", - "timers-ext": "^0.1.7" - } - }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -12971,25 +6681,9 @@ } }, "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, "mocha": { @@ -13167,68 +6861,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", - "dev": true - }, - "nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "dev": true, - "optional": true - }, "nanoid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -13241,12 +6879,6 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, - "next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, "node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -13264,121 +6896,12 @@ "formdata-polyfill": "^4.0.10" } }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "requires": { - "once": "^1.3.2" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -13391,15 +6914,6 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, "object.assign": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", @@ -13412,47 +6926,6 @@ "object-keys": "^1.1.1" } }, - "object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "dev": true, - "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", - "dev": true, - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha512-naLhxxpUESbNkRqc35oQ2scZSJueHGQNUfMW/0U37IgN6tE2dgDWg3whf+NEliy3F/QysrO48XKUz/nGPe+AQw==", - "dev": true, - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } - }, "object.values": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", @@ -13487,24 +6960,6 @@ "word-wrap": "^1.2.3" } }, - "ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -13541,48 +6996,10 @@ "callsites": "^3.0.0" } }, - "parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, - "requires": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "parse-ms": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-3.0.0.tgz", + "integrity": "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==", "dev": true }, "path-exists": { @@ -13609,21 +7026,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, - "requires": { - "path-root-regex": "^0.1.0" - } - }, - "path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true - }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -13636,52 +7038,12 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", - "dev": true, - "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - } - }, "plur": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", @@ -13691,59 +7053,19 @@ "irregular-plurals": "^3.2.0" } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "pretty-ms": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-8.0.0.tgz", + "integrity": "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==", "dev": true, "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" + "parse-ms": "^3.0.0" } }, "punycode": { @@ -13767,12 +7089,6 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, - "queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", - "dev": true - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -13782,76 +7098,6 @@ "safe-buffer": "^5.1.0" } }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "dependencies": { - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - } - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -13861,36 +7107,11 @@ "picomatch": "^2.2.1" } }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - } - } + "reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "dev": true }, "regexp.prototype.flags": { "version": "1.4.3", @@ -13903,80 +7124,18 @@ "functions-have-names": "^1.2.2" } }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - } - }, - "remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA==", - "dev": true, - "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true - }, - "replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true - }, - "replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha512-CHPV/GAglbIB1tnQgaiysb8H2yCy8WQ7lcEwQ/eT+kLj0QHV8LnJW0zpqpE7RSkrMSRoa+EBoag86clf7WAgSg==", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - } - }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -13988,43 +7147,12 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", - "dev": true, - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - } - }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A==", - "dev": true, - "requires": { - "value-or-function": "^3.0.0" - } - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -14071,15 +7199,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, "safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -14091,12 +7210,6 @@ "is-regex": "^1.1.4" } }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -14106,15 +7219,6 @@ "lru-cache": "^6.0.0" } }, - "semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha512-Ny/iyOzSSa8M5ML46IAx3iXc6tfOsYU2R4AXi2UpHk60Zrgyq6eqPj/xiOfS0rRl/iiQ/rdJkVjw/5cdUyCntQ==", - "dev": true, - "requires": { - "sver-compat": "^1.5.0" - } - }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -14124,50 +7228,6 @@ "randombytes": "^2.1.0" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - } - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -14194,444 +7254,56 @@ "object-inspect": "^1.9.0" } }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "source-map-resolve": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", - "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" - } - }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - } - } - }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "dev": true - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - } - } - }, - "stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "streamqueue": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/streamqueue/-/streamqueue-0.0.6.tgz", - "integrity": "sha512-l09LNfTUkmLMckTB1Mm8Um5GMS1uTZ/KTodg/SMf5Nx758IOsmaqIQ/AJumAnNMkDgZBG39btq3LVkN90knq8w==", - "dev": true, - "requires": { - "readable-stream": "^1.0.26-2" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true - } - } - }, - "streamx": { - "version": "2.12.5", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.12.5.tgz", - "integrity": "sha512-Y+nkFw57Z5JHT3zLlqFm3GccOy2FeYdUrrqita6Dd8kr/8enPn9GKa8IYf3/DmEKfZl/E2sWoSKUnd4qhonrgg==", - "dev": true, - "requires": { - "fast-fifo": "^1.0.0", - "queue-tick": "^1.0.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, + "spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, "string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -14669,18 +7341,18 @@ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true }, - "strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "dev": true - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14696,23 +7368,30 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, - "sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha512-aFTHfmjwizMNlNE6dsGmoAM4lHjL0CyiobWaFiXWSlD7cIxshW422Nb8KbXCmR6z+0ZEPY+daXJrDyh/vuwTyg==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "table-layout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", + "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", "dev": true, "requires": { - "streamx": "^2.12.5" + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + }, + "dependencies": { + "array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "dev": true + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } } }, "text-table": { @@ -14721,96 +7400,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==", - "dev": true - }, - "timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dev": true, - "requires": { - "es5-ext": "0.10.53", - "next-tick": "1" - } - }, - "to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", - "dev": true, - "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - } - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - } - } - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14820,15 +7409,6 @@ "is-number": "^7.0.0" } }, - "to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", - "dev": true, - "requires": { - "through2": "^2.0.3" - } - }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -14868,12 +7448,6 @@ "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", "dev": true }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -14906,18 +7480,18 @@ "underscore": "^1.12.1" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true - }, "typescript": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "dev": true }, + "typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true + }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -14930,86 +7504,12 @@ "which-boxed-primitive": "^1.0.2" } }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true - }, "underscore": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, - "undertaker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", - "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "fast-levenshtein": "^1.0.0", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" - }, - "dependencies": { - "fast-levenshtein": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", - "integrity": "sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==", - "dev": true - } - } - }, - "undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", - "dev": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "dependencies": { - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - } - } - }, - "unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "dev": true, - "requires": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, "universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", @@ -15022,52 +7522,6 @@ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "dev": true - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -15077,155 +7531,6 @@ "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", - "dev": true - }, - "vinyl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", - "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", - "dev": true, - "requires": { - "clone": "^2.1.2", - "clone-stats": "^1.0.0", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - } - }, - "vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", - "dev": true, - "requires": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" - }, - "dependencies": { - "replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "dev": true - }, - "vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - } - } - }, - "vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", - "dev": true, - "requires": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "dev": true - }, - "vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - } - } - }, "web-streams-polyfill": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", @@ -15270,124 +7575,48 @@ "is-symbol": "^1.0.3" } }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "dev": true - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", + "wordwrapjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", + "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "reduce-flatten": "^2.0.0", + "typical": "^5.2.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } } } }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } - }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true - }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "yargs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", - "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.1" - }, - "dependencies": { - "yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } - } - } - }, "yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", diff --git a/package.json b/package.json index 215a104ee59db..559555ba47917 100644 --- a/package.json +++ b/package.json @@ -41,24 +41,15 @@ "devDependencies": { "@octokit/rest": "latest", "@types/chai": "latest", - "@types/fancy-log": "^2.0.0", "@types/fs-extra": "^9.0.13", "@types/glob": "latest", - "@types/gulp": "^4.0.9", - "@types/gulp-concat": "latest", - "@types/gulp-newer": "latest", - "@types/gulp-rename": "latest", - "@types/gulp-sourcemaps": "latest", - "@types/merge2": "latest", "@types/microsoft__typescript-etw": "latest", "@types/minimist": "latest", - "@types/mkdirp": "latest", "@types/mocha": "latest", "@types/ms": "latest", "@types/node": "latest", "@types/source-map-support": "latest", "@types/which": "^2.0.1", - "@types/xml2js": "^0.4.11", "@typescript-eslint/eslint-plugin": "^5.33.1", "@typescript-eslint/parser": "^5.33.1", "@typescript-eslint/utils": "^5.33.1", @@ -67,47 +58,37 @@ "chalk": "^4.1.2", "del": "^6.1.1", "diff": "^5.1.0", + "esbuild": "^0.15.13", "eslint": "^8.22.0", "eslint-formatter-autolinkable-stylish": "^1.2.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsdoc": "^39.3.6", "eslint-plugin-local": "^1.0.0", "eslint-plugin-no-null": "^1.0.2", - "fancy-log": "latest", + "fast-xml-parser": "^4.0.11", "fs-extra": "^9.1.0", "glob": "latest", - "gulp": "^4.0.2", - "gulp-concat": "latest", - "gulp-insert": "latest", - "gulp-newer": "latest", - "gulp-rename": "latest", - "gulp-sourcemaps": "latest", - "merge2": "latest", + "hereby": "^1.6.4", + "jsonc-parser": "^3.2.0", "minimist": "latest", - "mkdirp": "latest", "mocha": "latest", "mocha-fivemat-progress-reporter": "latest", "ms": "^2.1.3", "node-fetch": "^3.2.10", "source-map-support": "latest", "typescript": "^4.8.4", - "vinyl": "latest", - "which": "^2.0.2", - "xml2js": "^0.4.23" - }, - "overrides": { - "es5-ext": "0.10.53" + "which": "^2.0.2" }, "scripts": { - "test": "gulp runtests-parallel --light=false", - "test:eslint-rules": "gulp run-eslint-rules-tests", + "test": "hereby runtests-parallel --light=false", + "test:eslint-rules": "hereby run-eslint-rules-tests", "build": "npm run build:compiler && npm run build:tests", - "build:compiler": "gulp local", - "build:tests": "gulp tests", + "build:compiler": "hereby local", + "build:tests": "hereby tests", "start": "node lib/tsc", - "clean": "gulp clean", - "gulp": "gulp", - "lint": "gulp lint", + "clean": "hereby clean", + "gulp": "hereby", + "lint": "hereby lint", "setup-hooks": "node scripts/link-hooks.mjs" }, "browser": { diff --git a/scripts/build/findUpDir.mjs b/scripts/build/findUpDir.mjs index 5a03844b7a4c2..b88efd92fe10f 100644 --- a/scripts/build/findUpDir.mjs +++ b/scripts/build/findUpDir.mjs @@ -26,4 +26,4 @@ export function findUpFile(name) { /** @type {string | undefined} */ let findUpRootCache; -export const findUpRoot = () => findUpRootCache || (findUpRootCache = dirname(findUpFile("Gulpfile.mjs"))); +export const findUpRoot = () => findUpRootCache || (findUpRootCache = dirname(findUpFile("Herebyfile.mjs"))); diff --git a/scripts/build/localization.mjs b/scripts/build/localization.mjs new file mode 100644 index 0000000000000..142bd14c1a073 --- /dev/null +++ b/scripts/build/localization.mjs @@ -0,0 +1 @@ +export const localizationDirectories = ["cs", "de", "es", "fr", "it", "ja", "ko", "pl", "pt-br", "ru", "tr", "zh-cn", "zh-tw"].map(f => f.toLowerCase()); diff --git a/scripts/build/options.mjs b/scripts/build/options.mjs index 78240aa9aecca..314d9f6802673 100644 --- a/scripts/build/options.mjs +++ b/scripts/build/options.mjs @@ -4,7 +4,7 @@ import os from "os"; const ci = ["1", "true"].includes(process.env.CI ?? ""); const parsed = minimist(process.argv.slice(2), { - boolean: ["dirty", "light", "colors", "lkg", "soft", "fix", "failed", "keepFailed", "force", "built", "ci"], + boolean: ["dirty", "light", "colors", "lkg", "soft", "fix", "failed", "keepFailed", "force", "built", "ci", "bundle"], string: ["browser", "tests", "break", "host", "reporter", "stackTraceLimit", "timeout", "shards", "shardId"], alias: { /* eslint-disable quote-props */ @@ -39,6 +39,7 @@ const parsed = minimist(process.argv.slice(2), { dirty: false, built: false, ci, + bundle: true } }); @@ -77,5 +78,7 @@ export default options; * @property {boolean} ci * @property {string} shards * @property {string} shardId + * @property {string} break + * @property {boolean} bundle */ void 0; diff --git a/scripts/build/prepend.mjs b/scripts/build/prepend.mjs deleted file mode 100644 index 115cd3b3d599c..0000000000000 --- a/scripts/build/prepend.mjs +++ /dev/null @@ -1,61 +0,0 @@ -import stream from "stream"; -import ts from "../../lib/typescript.js"; -import fs from "fs"; -import { base64VLQFormatEncode } from "./sourcemaps.mjs"; - -/** - * @param {string | ((file: import("vinyl")) => string)} data - */ -export function prepend(data) { - return new stream.Transform({ - objectMode: true, - /** - * @param {string | Buffer | import("vinyl")} input - * @param {(error: Error | null, data?: any) => void} cb - */ - transform(input, _, cb) { - if (typeof input === "string" || Buffer.isBuffer(input)) return cb(new Error("Only Vinyl files are supported.")); - if (!input.isBuffer()) return cb(new Error("Streams not supported.")); - try { - const output = input.clone(); - const prependContent = typeof data === "function" ? data(input) : data; - output.contents = Buffer.concat([Buffer.from(prependContent, "utf8"), input.contents]); - if (input.sourceMap) { - if (typeof input.sourceMap === "string") input.sourceMap = /**@type {import("./sourcemaps.mjs").RawSourceMap}*/(JSON.parse(input.sourceMap)); - const lineStarts = /**@type {*}*/(ts).computeLineStarts(prependContent); - let prependMappings = ""; - for (let i = 1; i < lineStarts.length; i++) { - prependMappings += ";"; - } - const offset = prependContent.length - lineStarts[lineStarts.length - 1]; - if (offset > 0) { - prependMappings += base64VLQFormatEncode(offset) + ","; - } - output.sourceMap = { - version: input.sourceMap.version, - file: input.sourceMap.file, - sources: input.sourceMap.sources, - sourceRoot: input.sourceMap.sourceRoot, - mappings: prependMappings + input.sourceMap.mappings, - names: input.names, - sourcesContent: input.sourcesContent - }; - } - // eslint-disable-next-line local/boolean-trivia, no-null/no-null - return cb(null, output); - } - catch (e) { - return cb(/** @type {Error} */(e)); - } - } - }); -} - -/** - * @param {string | ((file: import("vinyl")) => string)} file - */ -export function prependFile(file) { - const data = typeof file === "string" ? fs.readFileSync(file, "utf8") : - (/** @type {import("vinyl")} */ vinyl) => fs.readFileSync(file(vinyl), "utf8"); - return prepend(data); -} diff --git a/scripts/build/projects.mjs b/scripts/build/projects.mjs index 1061f5521b2b5..df5a4906e1702 100644 --- a/scripts/build/projects.mjs +++ b/scripts/build/projects.mjs @@ -1,69 +1,57 @@ import { exec, Debouncer } from "./utils.mjs"; import { resolve } from "path"; import { findUpRoot } from "./findUpDir.mjs"; -import assert from "assert"; +import cmdLineOptions from "./options.mjs"; class ProjectQueue { /** - * @param {(projects: string[], lkg: boolean, force: boolean) => Promise} action + * @param {(projects: string[]) => Promise} action */ constructor(action) { - /** @type {{ lkg: boolean, force: boolean, projects?: string[], debouncer: Debouncer }[]} */ - this._debouncers = []; - this._action = action; + /** @type {string[] | undefined} */ + this._projects = undefined; + this._debouncer = new Debouncer(100, async () => { + const projects = this._projects; + if (projects) { + this._projects = undefined; + await action(projects); + } + }); } /** * @param {string} project - * @param {{ lkg?: boolean; force?: boolean; }} options */ - enqueue(project, { lkg = true, force = false } = {}) { - let entry = this._debouncers.find(entry => entry.lkg === lkg && entry.force === force); - if (!entry) { - const debouncer = new Debouncer(100, async () => { - assert(entry); - const projects = entry.projects; - if (projects) { - entry.projects = undefined; - await this._action(projects, lkg, force); - } - }); - this._debouncers.push(entry = { lkg, force, debouncer }); - } - if (!entry.projects) entry.projects = []; - entry.projects.push(project); - return entry.debouncer.enqueue(); + enqueue(project) { + if (!this._projects) this._projects = []; + this._projects.push(project); + return this._debouncer.enqueue(); } } -const execTsc = (/** @type {boolean} */ lkg, /** @type {string[]} */ ...args) => +const execTsc = (/** @type {string[]} */ ...args) => exec(process.execPath, - [resolve(findUpRoot(), lkg ? "./lib/tsc" : "./built/local/tsc"), + [resolve(findUpRoot(), cmdLineOptions.lkg ? "./lib/tsc.js" : "./built/local/tsc.js"), "-b", ...args], { hidePrompt: true }); -const projectBuilder = new ProjectQueue((projects, lkg, force) => execTsc(lkg, ...(force ? ["--force"] : []), ...projects)); +const projectBuilder = new ProjectQueue((projects) => execTsc(...projects)); /** * @param {string} project - * @param {object} options - * @param {boolean} [options.lkg=true] - * @param {boolean} [options.force=false] */ -export const buildProject = (project, { lkg, force } = {}) => projectBuilder.enqueue(project, { lkg, force }); +export const buildProject = (project) => projectBuilder.enqueue(project); -const projectCleaner = new ProjectQueue((projects, lkg) => execTsc(lkg, "--clean", ...projects)); +const projectCleaner = new ProjectQueue((projects) => execTsc("--clean", ...projects)); /** * @param {string} project */ export const cleanProject = (project) => projectCleaner.enqueue(project); -const projectWatcher = new ProjectQueue((projects) => execTsc(/*lkg*/ true, "--watch", ...projects)); +const projectWatcher = new ProjectQueue((projects) => execTsc("--watch", "--preserveWatchOutput", ...projects)); /** * @param {string} project - * @param {object} options - * @param {boolean} [options.lkg=true] */ -export const watchProject = (project, { lkg } = {}) => projectWatcher.enqueue(project, { lkg }); +export const watchProject = (project) => projectWatcher.enqueue(project); diff --git a/scripts/build/sourcemaps.mjs b/scripts/build/sourcemaps.mjs deleted file mode 100644 index 6fd152c258c3f..0000000000000 --- a/scripts/build/sourcemaps.mjs +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @param {string} message - * @returns {never} - */ -function fail(message) { - throw new Error(message); -} - -/** - * @param {number} value - */ -function base64FormatEncode(value) { - return value < 0 ? fail("Invalid value") : - value < 26 ? 0x41 /*A*/ + value : - value < 52 ? 0x61 /*a*/ + value - 26 : - value < 62 ? 0x30 /*0*/ + value - 52 : - value === 62 ? 0x2B /*+*/ : - value === 63 ? 0x2F /*/*/ : - fail("Invalid value"); -} - -/** - * @param {number} value - */ -export function base64VLQFormatEncode(value) { - if (value < 0) { - value = ((-value) << 1) + 1; - } - else { - value = value << 1; - } - - // Encode 5 bits at a time starting from least significant bits - let result = ""; - do { - let currentDigit = value & 31; // 11111 - value = value >> 5; - if (value > 0) { - // There are still more digits to decode, set the msb (6th bit) - currentDigit = currentDigit | 32; - } - result += String.fromCharCode(base64FormatEncode(currentDigit)); - } while (value > 0); - - return result; -} - -/** @typedef {object} RawSourceMap */ diff --git a/scripts/build/tests.mjs b/scripts/build/tests.mjs index dce50403c0d9c..82cb9b8873247 100644 --- a/scripts/build/tests.mjs +++ b/scripts/build/tests.mjs @@ -2,8 +2,6 @@ import del from "del"; import fs from "fs"; import os from "os"; import path from "path"; -import mkdirP from "mkdirp"; -import log from "fancy-log"; import cmdLineOptions from "./options.mjs"; import { exec } from "./utils.mjs"; import { findUpFile, findUpRoot } from "./findUpDir.mjs"; @@ -19,9 +17,8 @@ export const localTest262Baseline = "internal/baselines/test262/local"; * @param {string} runJs * @param {string} defaultReporter * @param {boolean} runInParallel - * @param {boolean} _watchMode */ -export async function runConsoleTests(runJs, defaultReporter, runInParallel, _watchMode) { +export async function runConsoleTests(runJs, defaultReporter, runInParallel) { let testTimeout = cmdLineOptions.timeout; const tests = cmdLineOptions.tests; const inspect = cmdLineOptions.break || cmdLineOptions.inspect; @@ -122,17 +119,6 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel, _wa errorStatus = exitCode; error = new Error(`Process exited with status code ${errorStatus}.`); } - else if (cmdLineOptions.ci && runJs.startsWith("built")) { - // finally, do a sanity check and build the compiler with the built version of itself - log.info("Starting sanity check build..."); - // Cleanup everything except lint rules (we'll need those later and would rather not waste time rebuilding them) - await exec("gulp", ["clean-tsc", "clean-services", "clean-tsserver", "clean-lssl", "clean-tests"]); - const { exitCode } = await exec("gulp", ["local", "--lkg=false"]); - if (exitCode !== 0) { - errorStatus = exitCode; - error = new Error(`Sanity check build process exited with status code ${errorStatus}.`); - } - } } catch (e) { errorStatus = undefined; @@ -153,12 +139,12 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel, _wa export async function cleanTestDirs() { await del([localBaseline, localRwcBaseline]); - mkdirP.sync(localRwcBaseline); - mkdirP.sync(localBaseline); + await fs.promises.mkdir(localRwcBaseline, { recursive: true }); + await fs.promises.mkdir(localBaseline, { recursive: true }); } /** - * used to pass data from gulp command line directly to run.js + * used to pass data from command line directly to run.js * @param {string} tests * @param {string} runners * @param {boolean} light @@ -184,7 +170,7 @@ export function writeTestConfigFile(tests, runners, light, taskConfigsFolder, wo shards, shardId }); - log.info("Running tests with config: " + testConfigContents); + console.info("Running tests with config: " + testConfigContents); fs.writeFileSync("test.config", testConfigContents); } diff --git a/scripts/build/utils.mjs b/scripts/build/utils.mjs index 59075df3a41c4..3bf99758d8d13 100644 --- a/scripts/build/utils.mjs +++ b/scripts/build/utils.mjs @@ -1,18 +1,12 @@ /* eslint-disable no-restricted-globals */ -// eslint-disable-next-line @typescript-eslint/triple-slash-reference -/// import fs from "fs"; import path from "path"; -import log from "fancy-log"; -import del from "del"; -import File from "vinyl"; -import ts from "../../lib/typescript.js"; import chalk from "chalk"; import which from "which"; import { spawn } from "child_process"; -import { Duplex } from "stream"; import assert from "assert"; +import JSONC from "jsonc-parser"; /** * Executes the provided command once with the supplied arguments. @@ -29,7 +23,7 @@ export async function exec(cmd, args, options = {}) { return /**@type {Promise<{exitCode?: number}>}*/(new Promise((resolve, reject) => { const { ignoreExitCode, waitForExit = true } = options; - if (!options.hidePrompt) log(`> ${chalk.green(cmd)} ${args.join(" ")}`); + if (!options.hidePrompt) console.log(`> ${chalk.green(cmd)} ${args.join(" ")}`); const proc = spawn(which.sync(cmd), args, { stdio: waitForExit ? "inherit" : "ignore" }); if (waitForExit) { proc.on("exit", exitCode => { @@ -52,48 +46,13 @@ export async function exec(cmd, args, options = {}) { })); } -/** - * @param {ts.Diagnostic[]} diagnostics - * @param {{ cwd?: string, pretty?: boolean }} [options] - */ -function formatDiagnostics(diagnostics, options) { - return options && options.pretty - ? ts.formatDiagnosticsWithColorAndContext(diagnostics, getFormatDiagnosticsHost(options && options.cwd)) - : ts.formatDiagnostics(diagnostics, getFormatDiagnosticsHost(options && options.cwd)); -} - -/** - * @param {ts.Diagnostic[]} diagnostics - * @param {{ cwd?: string }} [options] - */ -function reportDiagnostics(diagnostics, options) { - log(formatDiagnostics(diagnostics, { cwd: options && options.cwd, pretty: process.stdout.isTTY })); -} - -/** - * @param {string | undefined} cwd - * @returns {ts.FormatDiagnosticsHost} - */ -function getFormatDiagnosticsHost(cwd) { - return { - getCanonicalFileName: fileName => fileName, - getCurrentDirectory: () => cwd ?? process.cwd(), - getNewLine: () => ts.sys.newLine, - }; -} - /** * Reads JSON data with optional comments using the LKG TypeScript compiler * @param {string} jsonPath */ export function readJson(jsonPath) { const jsonText = fs.readFileSync(jsonPath, "utf8"); - const result = ts.parseConfigFileTextToJson(jsonPath, jsonText); - if (result.error) { - reportDiagnostics([result.error]); - throw new Error("An error occurred during parse."); - } - return result.config; + return JSONC.parse(jsonText); } /** @@ -167,7 +126,7 @@ export function needsUpdate(source, dest) { export function getDiffTool() { const program = process.env.DIFF; if (!program) { - log.warn("Add the 'DIFF' environment variable to the path of the program you want to use."); + console.warn("Add the 'DIFF' environment variable to the path of the program you want to use."); process.exit(1); } return program; @@ -191,82 +150,6 @@ export function getDirSize(root) { .reduce((acc, num) => acc + num, 0); } -/** - * @param {string | ((file: File) => string) | { cwd?: string }} [dest] - * @param {{ cwd?: string }} [opts] - */ -export function rm(dest, opts) { - if (dest && typeof dest === "object") { - opts = dest; - dest = undefined; - } - let failed = false; - - const cwd = path.resolve(opts && opts.cwd || process.cwd()); - - /** @type {{ file: File, deleted: boolean, promise: Promise, cb: Function }[]} */ - const pending = []; - - const processDeleted = () => { - if (failed) return; - while (pending.length && pending[0].deleted) { - const fileAndCallback = pending.shift(); - assert(fileAndCallback); - const { file, cb } = fileAndCallback; - duplex.push(file); - cb(); - } - }; - - const duplex = new Duplex({ - objectMode: true, - /** - * @param {string|Buffer|File} file - */ - write(file, _, cb) { - if (failed) return; - if (typeof file === "string" || Buffer.isBuffer(file)) return cb(new Error("Only Vinyl files are supported.")); - const basePath = typeof dest === "string" ? path.resolve(cwd, dest) : - typeof dest === "function" ? path.resolve(cwd, dest(file)) : - file.base; - const filePath = path.resolve(basePath, file.relative); - file.cwd = cwd; - file.base = basePath; - file.path = filePath; - const entry = { - file, - deleted: false, - cb, - promise: del(file.path).then(() => { - entry.deleted = true; - processDeleted(); - }, err => { - failed = true; - pending.length = 0; - cb(err); - }) - }; - pending.push(entry); - }, - final(cb) { - // eslint-disable-next-line no-null/no-null - const endThenCb = () => (duplex.push(null), cb()); // signal end of read queue - processDeleted(); - if (pending.length) { - Promise - .all(pending.map(entry => entry.promise)) - .then(() => processDeleted()) - .then(() => endThenCb(), endThenCb); - return; - } - endThenCb(); - }, - read() { - } - }); - return duplex; -} - class Deferred { constructor() { this.promise = new Promise((resolve, reject) => { @@ -317,3 +200,20 @@ export class Debouncer { } } } + +const unset = Symbol(); +/** + * @template T + * @param {() => T} fn + * @returns {() => T} + */ +export function memoize(fn) { + /** @type {T | unset} */ + let value = unset; + return () => { + if (value === unset) { + value = fn(); + } + return value; + }; +} diff --git a/scripts/buildProtocol.mjs b/scripts/buildProtocol.mjs deleted file mode 100644 index f7bc00c897b42..0000000000000 --- a/scripts/buildProtocol.mjs +++ /dev/null @@ -1,260 +0,0 @@ -import ts from "../lib/typescript.js"; -import path from "path"; -import assert from "assert"; - -/** - * - * @param {string} s - * @param {string} suffix - * @returns {boolean} - */ -function endsWith(s, suffix) { - return s.lastIndexOf(suffix, s.length - suffix.length) !== -1; -} - -/** - * @param {ts.EnumDeclaration} declaration - * @returns {boolean} - */ -function isStringEnum(declaration) { - return !!declaration.members.length && declaration.members.every(m => !!m.initializer && m.initializer.kind === ts.SyntaxKind.StringLiteral); -} - -class DeclarationsWalker { - /** - * @type {ts.Type[]} - * @private - */ - visitedTypes = []; - /** - * @type {string} - * @private - */ - text = ""; - /** - * @type {ts.Type[]} - * @private - */ - removedTypes = []; - - /** - * @param {ts.TypeChecker} typeChecker - * @param {ts.SourceFile} protocolFile - * @private - */ - constructor(typeChecker, protocolFile) { - this.typeChecker = typeChecker; - this.protocolFile = protocolFile; - } - - /** - * - * @param {ts.TypeChecker} typeChecker - * @param {ts.SourceFile} protocolFile - * @returns {string} - */ - static getExtraDeclarations(typeChecker, protocolFile) { - const walker = new DeclarationsWalker(typeChecker, protocolFile); - walker.visitTypeNodes(protocolFile); - let text = walker.text - ? `declare namespace ts.server.protocol {\n${walker.text}}` - : ""; - if (walker.removedTypes) { - text += "\ndeclare namespace ts {\n"; - text += " // these types are empty stubs for types from services and should not be used directly\n"; - for (const type of walker.removedTypes) { - text += ` export type ${type.symbol.name} = never;\n`; - } - text += "}"; - } - return text; - } - - /** - * @param {ts.Type} type - * @returns {void} - * @private - */ - processType(type) { - if (this.visitedTypes.indexOf(type) >= 0) { - return; - } - this.visitedTypes.push(type); - const s = type.aliasSymbol || type.getSymbol(); - if (!s) { - return; - } - if (s.name === "Array" || s.name === "ReadOnlyArray") { - // we should process type argument instead - return this.processType(/** @type {any} */(type).typeArguments[0]); - } - else { - const declarations = s.getDeclarations(); - if (declarations) { - for (const decl of declarations) { - const sourceFile = decl.getSourceFile(); - if (sourceFile === this.protocolFile || /lib(\..+)?\.d.ts/.test(path.basename(sourceFile.fileName))) { - return; - } - if (ts.isEnumDeclaration(decl) && !isStringEnum(decl)) { - this.removedTypes.push(type); - return; - } - else { - // splice declaration in final d.ts file - const text = decl.getFullText(); - this.text += `${text}\n`; - // recursively pull all dependencies into result dts file - - this.visitTypeNodes(decl); - } - } - } - } - } - - /** - * @param {ts.Node} node - * @private - */ - visitTypeNodes(node) { - if (node.parent) { - switch (node.parent.kind) { - case ts.SyntaxKind.VariableDeclaration: - case ts.SyntaxKind.MethodDeclaration: - case ts.SyntaxKind.MethodSignature: - case ts.SyntaxKind.PropertyDeclaration: - case ts.SyntaxKind.PropertySignature: - case ts.SyntaxKind.Parameter: - case ts.SyntaxKind.IndexSignature: - const parent = /** @type {ts.VariableDeclaration | ts.MethodDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration | ts.PropertySignature | ts.MethodSignature | ts.IndexSignatureDeclaration} */ (node.parent); - if (parent.type === node) { - this.processTypeOfNode(node); - } - break; - case ts.SyntaxKind.InterfaceDeclaration: - const heritageClauses = /** @type {ts.InterfaceDeclaration} */ (node.parent).heritageClauses; - if (heritageClauses) { - if (heritageClauses[0].token !== ts.SyntaxKind.ExtendsKeyword) { - throw new Error(`Unexpected kind of heritage clause: ${ts.SyntaxKind[heritageClauses[0].kind]}`); - } - for (const type of heritageClauses[0].types) { - this.processTypeOfNode(type); - } - } - break; - } - } - ts.forEachChild(node, n => this.visitTypeNodes(n)); - } - - /** - * @param {ts.Node} node - * @private - */ - processTypeOfNode(node) { - if (node.kind === ts.SyntaxKind.UnionType) { - for (const t of /** @type {ts.UnionTypeNode} */ (node).types) { - this.processTypeOfNode(t); - } - } - else { - const type = this.typeChecker.getTypeAtLocation(node); - if (type && !(type.flags & (ts.TypeFlags.TypeParameter))) { - this.processType(type); - } - } - } -} - -/** - * @param {string} outputFile - * @param {string} protocolTs - * @param {string} typeScriptServicesDts - */ -function writeProtocolFile(outputFile, protocolTs, typeScriptServicesDts) { - /** @type {ts.CompilerOptions} */ - const options = { target: ts.ScriptTarget.ES5, declaration: true, noResolve: false, types: [], stripInternal: true }; - - /** - * 1st pass - generate a program from protocol.ts and typescriptservices.d.ts and emit core version of protocol.d.ts with all internal members stripped - * @return text of protocol.d.t.s - */ - function getInitialDtsFileForProtocol() { - const program = ts.createProgram([protocolTs, typeScriptServicesDts, path.join(typeScriptServicesDts, "../lib.es5.d.ts")], options); - - /** @type {string | undefined} */ - let protocolDts; - const emitResult = program.emit(program.getSourceFile(protocolTs), (file, content) => { - if (endsWith(file, ".d.ts")) { - protocolDts = content; - } - }); - - if (protocolDts === undefined) { - /** @type {ts.FormatDiagnosticsHost} */ - const diagHost = { - getCanonicalFileName(f) { return f; }, - getCurrentDirectory() { return "."; }, - getNewLine() { return "\r\n"; } - }; - const diags = emitResult.diagnostics.map(d => ts.formatDiagnostic(d, diagHost)).join("\r\n"); - throw new Error(`Declaration file for protocol.ts is not generated:\r\n${diags}`); - } - return protocolDts; - } - - const protocolFileName = "protocol.d.ts"; - /** - * Second pass - generate a program from protocol.d.ts and typescriptservices.d.ts, then augment core protocol.d.ts with extra types from typescriptservices.d.ts - * @param {string} protocolDts - * @param {boolean} includeTypeScriptServices - */ - function getProgramWithProtocolText(protocolDts, includeTypeScriptServices) { - const host = ts.createCompilerHost(options); - const originalGetSourceFile = host.getSourceFile; - host.getSourceFile = (fileName) => { - if (fileName === protocolFileName) { - assert(options.target !== undefined); - return ts.createSourceFile(fileName, protocolDts, options.target); - } - return originalGetSourceFile.apply(host, [fileName, ts.ScriptTarget.Latest]); - }; - const rootFiles = includeTypeScriptServices ? [protocolFileName, typeScriptServicesDts] : [protocolFileName]; - return ts.createProgram(rootFiles, options, host); - } - - let protocolDts = getInitialDtsFileForProtocol(); - const program = getProgramWithProtocolText(protocolDts, /*includeTypeScriptServices*/ true); - - const protocolFile = program.getSourceFile("protocol.d.ts"); - assert(protocolFile); - const extraDeclarations = DeclarationsWalker.getExtraDeclarations(program.getTypeChecker(), protocolFile); - if (extraDeclarations) { - protocolDts += extraDeclarations; - } - protocolDts += "\nimport protocol = ts.server.protocol;"; - protocolDts += "\nexport = protocol;"; - protocolDts += "\nexport as namespace protocol;"; - - // do sanity check and try to compile generated text as standalone program - const sanityCheckProgram = getProgramWithProtocolText(protocolDts, /*includeTypeScriptServices*/ false); - const diagnostics = [...sanityCheckProgram.getSyntacticDiagnostics(), ...sanityCheckProgram.getSemanticDiagnostics(), ...sanityCheckProgram.getGlobalDiagnostics()]; - - ts.sys.writeFile(outputFile, protocolDts); - - if (diagnostics.length) { - const flattenedDiagnostics = diagnostics.map(d => `${ts.flattenDiagnosticMessageText(d.messageText, "\n")} at ${d.file ? d.file.fileName : ""} line ${d.start}`).join("\n"); - throw new Error(`Unexpected errors during sanity check: ${flattenedDiagnostics}`); - } -} - -if (process.argv.length < 5) { - console.log(`Expected 3 arguments: path to 'protocol.ts', path to 'typescriptservices.d.ts' and path to output file`); - process.exit(1); -} - -const protocolTs = process.argv[2]; -const typeScriptServicesDts = process.argv[3]; -const outputFile = process.argv[4]; -writeProtocolFile(outputFile, protocolTs, typeScriptServicesDts); diff --git a/scripts/dtsBundler.mjs b/scripts/dtsBundler.mjs new file mode 100644 index 0000000000000..fc17bbaebd09b --- /dev/null +++ b/scripts/dtsBundler.mjs @@ -0,0 +1,413 @@ +/** + * WARNING: this is a very, very rudimentary d.ts bundler; it only works + * in the TS project thanks to our history using namespaces, which has + * prevented us from duplicating names across files, and allows us to + * bundle as namespaces again, even though the project is modules. + */ + +import fs from "fs"; +import path from "path"; +import minimist from "minimist"; +import url from "url"; +import ts from "../lib/typescript.js"; +import assert, { fail } from "assert"; + +const __filename = url.fileURLToPath(new URL(import.meta.url)); +const __dirname = path.dirname(__filename); + +// /** @type {any} */ (ts).Debug.enableDebugInfo(); + +const dotDts = ".d.ts"; + +const options = minimist(process.argv.slice(2), { + string: ["project", "entrypoint", "output"], +}); + +const entrypoint = options.entrypoint; +const output = options.output; + +assert(typeof entrypoint === "string" && entrypoint); +assert(typeof output === "string" && output); +assert(output.endsWith(dotDts)); + +const internalOutput = output.substring(0, output.length - dotDts.length) + ".internal" + dotDts; + +console.log(`Bundling ${entrypoint} to ${output} and ${internalOutput}`); + +const newLineKind = ts.NewLineKind.LineFeed; +const newLine = newLineKind === ts.NewLineKind.LineFeed ? "\n" : "\r\n"; + +/** @type {(node: ts.Node) => node is ts.DeclarationStatement} */ +function isDeclarationStatement(node) { + return /** @type {any} */ (ts).isDeclarationStatement(node); +} + +/** @type {(node: ts.Node) => boolean} */ +function isInternalDeclaration(node) { + return /** @type {any} */ (ts).isInternalDeclaration(node, node.getSourceFile()); +} + +/** + * + * @param {ts.VariableDeclaration} node + * @returns {ts.VariableStatement} + */ +function getParentVariableStatement(node) { + const declarationList = node.parent; + assert(ts.isVariableDeclarationList(declarationList), `expected VariableDeclarationList at ${nodeToLocation(node)}`); + assert(declarationList.declarations.length === 1, `expected VariableDeclarationList of length 1 at ${nodeToLocation(node)}`); + const variableStatement = declarationList.parent; + assert(ts.isVariableStatement(variableStatement), `expected VariableStatement at ${nodeToLocation(node)}`); + return variableStatement; +} + +/** + * + * @param {ts.Declaration} node + * @returns {ts.Statement | undefined} + */ +function getDeclarationStatement(node) { + if (ts.isVariableDeclaration(node)) { + return getParentVariableStatement(node); + } + else if (isDeclarationStatement(node)) { + return node; + } + return undefined; +} + +/** @type {ts.TransformationContext} */ +const nullTransformationContext = /** @type {any} */ (ts).nullTransformationContext; + +const program = ts.createProgram([entrypoint], { target: ts.ScriptTarget.ES5 }); + +const typeChecker = program.getTypeChecker(); + +const sourceFile = program.getSourceFile(entrypoint); +assert(sourceFile, "Failed to load source file"); +const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile); +assert(moduleSymbol, "Failed to get module's symbol"); + +const printer = ts.createPrinter({ newLine: newLineKind }); + +/** @type {string[]} */ +const publicLines = []; +/** @type {string[]} */ +const internalLines = []; + +const indent = " "; +let currentIndent = ""; + +function increaseIndent() { + currentIndent += indent; +} + +function decreaseIndent() { + currentIndent = currentIndent.slice(indent.length); +} + +/** + * @enum {number} + */ +const WriteTarget = { + Public: 1 << 0, + Internal: 1 << 1, + Both: (1 << 0) | (1 << 1), +}; + +/** + * @param {string} s + * @param {WriteTarget} target + */ +function write(s, target) { + if (!target) { + return; + } + + const toPush = !s ? [""] : s.split(/\r?\n/).filter(line => line).map(line => (currentIndent + line).trimEnd()); + + if (target & WriteTarget.Public) { + publicLines.push(...toPush); + } + if (target & WriteTarget.Internal) { + internalLines.push(...toPush); + } +} + +/** + * @param {ts.Node} node + * @param {ts.SourceFile} sourceFile + * @param {WriteTarget} target + */ +function writeNode(node, sourceFile, target) { + write(printer.printNode(ts.EmitHint.Unspecified, node, sourceFile), target); +} + +/** @type {Map} */ +const containsPublicAPICache = new Map(); + +/** + * @param {ts.Symbol} symbol + * @returns {boolean} + */ +function containsPublicAPI(symbol) { + const cached = containsPublicAPICache.get(symbol); + if (cached !== undefined) { + return cached; + } + + const result = containsPublicAPIWorker(); + containsPublicAPICache.set(symbol, result); + return result; + + function containsPublicAPIWorker() { + if (!symbol.declarations?.length) { + return false; + } + + if (symbol.flags & ts.SymbolFlags.Alias) { + const resolved = typeChecker.getAliasedSymbol(symbol); + return containsPublicAPI(resolved); + } + + // Namespace barrel; actual namespaces are checked below. + if (symbol.flags & ts.SymbolFlags.ValueModule && symbol.valueDeclaration?.kind === ts.SyntaxKind.SourceFile) { + for (const me of typeChecker.getExportsOfModule(symbol)) { + if (containsPublicAPI(me)) { + return true; + } + } + return false; + } + + for (const decl of symbol.declarations) { + const statement = getDeclarationStatement(decl); + if (statement && !isInternalDeclaration(statement)) { + return true; + } + } + + return false; + } +} + +/** + * @param {ts.Node} node + */ +function nodeToLocation(node) { + const sourceFile = node.getSourceFile(); + const lc = sourceFile.getLineAndCharacterOfPosition(node.pos); + return `${sourceFile.fileName}:${lc.line+1}:${lc.character+1}`; +} + +/** + * @param {ts.Node} node + * @returns {ts.Node | undefined} + */ +function removeDeclareConstExport(node) { + switch (node.kind) { + case ts.SyntaxKind.DeclareKeyword: // No need to emit this in d.ts files. + case ts.SyntaxKind.ConstKeyword: // Remove const from const enums. + case ts.SyntaxKind.ExportKeyword: // No export modifier; we are already in the namespace. + return undefined; + } + return node; +} + +/** @type {Map[]} */ +const scopeStack = []; + +/** + * @param {string} name + */ +function findInScope(name) { + for (let i = scopeStack.length-1; i >= 0; i--) { + const scope = scopeStack[i]; + const symbol = scope.get(name); + if (symbol) { + return symbol; + } + } + return undefined; +} + +/** @type {(symbol: ts.Symbol | undefined, excludes?: ts.SymbolFlags) => boolean} */ +function isNonLocalAlias(symbol, excludes = ts.SymbolFlags.Value | ts.SymbolFlags.Type | ts.SymbolFlags.Namespace) { + if (!symbol) return false; + return (symbol.flags & (ts.SymbolFlags.Alias | excludes)) === ts.SymbolFlags.Alias || !!(symbol.flags & ts.SymbolFlags.Alias && symbol.flags & ts.SymbolFlags.Assignment); +} + +/** + * @param {ts.Symbol} symbol + */ +function resolveAlias(symbol) { + return typeChecker.getAliasedSymbol(symbol); +} + +/** + * @param {ts.Symbol} symbol + * @param {boolean | undefined} [dontResolveAlias] + */ +function resolveSymbol(symbol, dontResolveAlias = undefined) { + return !dontResolveAlias && isNonLocalAlias(symbol) ? resolveAlias(symbol) : symbol; +} + +/** + * @param {ts.Symbol} symbol + * @returns {ts.Symbol} + */ +function getMergedSymbol(symbol) { + return /** @type {any} */ (typeChecker).getMergedSymbol(symbol); +} + +/** + * @param {ts.Symbol} s1 + * @param {ts.Symbol} s2 + */ +function symbolsConflict(s1, s2) { + // See getSymbolIfSameReference in checker.ts + s1 = getMergedSymbol(resolveSymbol(getMergedSymbol(s1))); + s2 = getMergedSymbol(resolveSymbol(getMergedSymbol(s2))); + if (s1 === s2) { + return false; + } + + const s1Flags = s1.flags & (ts.SymbolFlags.Type | ts.SymbolFlags.Value); + const s2Flags = s2.flags & (ts.SymbolFlags.Type | ts.SymbolFlags.Value); + + // If the two symbols differ by type/value space, ignore. + if (!(s1Flags & s2Flags)) { + return false; + } + + return true; +} + +/** + * @param {ts.Node} node + * @returns {boolean} + */ +function isPartOfTypeNode(node) { + return /** @type {any} */ (ts).isPartOfTypeNode(node); +} + +/** + * @param {ts.Statement} decl + */ +function verifyMatchingSymbols(decl) { + ts.visitEachChild(decl, /** @type {(node: ts.Node) => ts.Node} */ function visit(node) { + if (ts.isIdentifier(node) && isPartOfTypeNode(node)) { + if (ts.isQualifiedName(node.parent) && node !== node.parent.left) { + return node; + } + if (ts.isParameter(node.parent) && node === node.parent.name) { + return node; + } + if (ts.isNamedTupleMember(node.parent) && node === node.parent.name) { + return node; + } + + const symbolOfNode = typeChecker.getSymbolAtLocation(node); + if (!symbolOfNode) { + fail(`No symbol for node at ${nodeToLocation(node)}`); + } + const symbolInScope = findInScope(symbolOfNode.name); + if (!symbolInScope) { + // We didn't find the symbol in scope at all. Just allow it and we'll fail at test time. + return node; + } + + if (symbolsConflict(symbolOfNode, symbolInScope)) { + fail(`Declaration at ${nodeToLocation(decl)}\n references ${symbolOfNode.name} at ${symbolOfNode.declarations && nodeToLocation(symbolOfNode.declarations[0])},\n but containing scope contains a symbol with the same name declared at ${symbolInScope.declarations && nodeToLocation(symbolInScope.declarations[0])}`); + } + } + + return ts.visitEachChild(node, visit, nullTransformationContext); + }, nullTransformationContext); +} + +/** + * @param {string} name + * @param {ts.Symbol} moduleSymbol + */ +function emitAsNamespace(name, moduleSymbol) { + assert(moduleSymbol.flags & ts.SymbolFlags.ValueModule, "moduleSymbol is not a module"); + + scopeStack.push(new Map()); + const currentScope = scopeStack[scopeStack.length-1]; + + const target = containsPublicAPI(moduleSymbol) ? WriteTarget.Both : WriteTarget.Internal; + + if (name === "ts") { + // We will write `export = ts` at the end. + write(`declare namespace ${name} {`, target); + } + else { + // No export modifier; we are already in the namespace. + write(`namespace ${name} {`, target); + } + increaseIndent(); + + const moduleExports = typeChecker.getExportsOfModule(moduleSymbol); + for (const me of moduleExports) { + currentScope.set(me.name, me); + } + + for (const me of moduleExports) { + assert(me.declarations?.length); + + if (me.flags & ts.SymbolFlags.Alias) { + const resolved = typeChecker.getAliasedSymbol(me); + emitAsNamespace(me.name, resolved); + continue; + } + + for (const decl of me.declarations) { + const statement = getDeclarationStatement(decl); + const sourceFile = decl.getSourceFile(); + + if (!statement) { + fail(`Unhandled declaration for ${me.name} at ${nodeToLocation(decl)}`); + } + + verifyMatchingSymbols(statement); + + const isInternal = isInternalDeclaration(statement); + if (!isInternal) { + const publicStatement = ts.visitEachChild(statement, (node) => { + // No @internal comments in the public API. + if (isInternalDeclaration(node)) { + return undefined; + } + return removeDeclareConstExport(node); + }, nullTransformationContext); + + writeNode(publicStatement, sourceFile, WriteTarget.Public); + } + + const internalStatement = ts.visitEachChild(statement, removeDeclareConstExport, nullTransformationContext); + + writeNode(internalStatement, sourceFile, WriteTarget.Internal); + } + } + + scopeStack.pop(); + + decreaseIndent(); + write(`}`, target); +} + +emitAsNamespace("ts", moduleSymbol); + +write("export = ts;", WriteTarget.Both); + +const copyrightNotice = fs.readFileSync(path.join(__dirname, "..", "CopyrightNotice.txt"), "utf-8"); +const publicContents = copyrightNotice + publicLines.join(newLine); +const internalContents = copyrightNotice + internalLines.join(newLine); + +if (publicContents.includes("@internal")) { + console.error("Output includes untrimmed @internal nodes!"); +} + +fs.writeFileSync(output, publicContents); +fs.writeFileSync(internalOutput, internalContents); diff --git a/scripts/eslint/rules/jsdoc-format.cjs b/scripts/eslint/rules/jsdoc-format.cjs new file mode 100644 index 0000000000000..86783b3547a76 --- /dev/null +++ b/scripts/eslint/rules/jsdoc-format.cjs @@ -0,0 +1,110 @@ +const { TSESTree } = require("@typescript-eslint/utils"); +const { createRule } = require("./utils.cjs"); + +module.exports = createRule({ + name: "jsdoc-format", + meta: { + docs: { + description: ``, + recommended: "error", + }, + messages: { + internalCommentInNonJSDocError: `@internal should not appear in non-JSDoc comment for declaration.`, + internalCommentNotLastError: `@internal should only appear in final JSDoc comment for declaration.`, + multipleJSDocError: `Declaration has multiple JSDoc comments.`, + internalCommentOnParameterProperty: `@internal cannot appear on a JSDoc comment; use a declared property and an assignment in the constructor instead.`, + }, + schema: [], + type: "problem", + }, + defaultOptions: [], + + create(context) { + const sourceCode = context.getSourceCode(); + const atInternal = "@internal"; + const jsdocStart = "/**"; + + /** @type {(c: TSESTree.Comment, indexInComment: number) => TSESTree.SourceLocation} */ + const getAtInternalLoc = (c, indexInComment) => { + const line = c.loc.start.line; + return { + start: { + line, + column: c.loc.start.column + indexInComment, + }, + end: { + line, + column: c.loc.start.column + indexInComment + atInternal.length, + }, + }; + }; + + /** @type {(c: TSESTree.Comment) => TSESTree.SourceLocation} */ + const getJSDocStartLoc = (c) => { + return { + start: c.loc.start, + end: { + line: c.loc.start.line, + column: c.loc.start.column + jsdocStart.length, + }, + }; + }; + + /** @type {(node: TSESTree.Node) => void} */ + const checkJSDocFormat = (node) => { + const blockComments = sourceCode.getCommentsBefore(node).filter(c => c.type === "Block"); + if (blockComments.length === 0) { + return; + } + + const last = blockComments.length - 1; + let seenJSDoc = false; + for (let i = 0; i < blockComments.length; i++) { + const c = blockComments[i]; + const rawComment = sourceCode.getText(c); + + const isJSDoc = rawComment.startsWith(jsdocStart); + if (isJSDoc && seenJSDoc) { + context.report({ messageId: "multipleJSDocError", node: c, loc: getJSDocStartLoc(c) }); + } + seenJSDoc = seenJSDoc || isJSDoc; + + const indexInComment = rawComment.indexOf(atInternal); + if (indexInComment === -1) { + continue; + } + + if (!isJSDoc) { + context.report({ messageId: "internalCommentInNonJSDocError", node: c, loc: getAtInternalLoc(c, indexInComment) }); + } + else if (i !== last) { + context.report({ messageId: "internalCommentNotLastError", node: c, loc: getAtInternalLoc(c, indexInComment) }); + } + else if (node.type === "TSParameterProperty") { + context.report({ messageId: "internalCommentOnParameterProperty", node: c, loc: getAtInternalLoc(c, indexInComment) }); + } + } + }; + + return { + ClassDeclaration: checkJSDocFormat, + FunctionDeclaration: checkJSDocFormat, + TSEnumDeclaration: checkJSDocFormat, + TSModuleDeclaration: checkJSDocFormat, + VariableDeclaration: checkJSDocFormat, + TSInterfaceDeclaration: checkJSDocFormat, + TSTypeAliasDeclaration: checkJSDocFormat, + TSCallSignatureDeclaration: checkJSDocFormat, + ExportAllDeclaration: checkJSDocFormat, + ExportNamedDeclaration: checkJSDocFormat, + TSImportEqualsDeclaration: checkJSDocFormat, + TSNamespaceExportDeclaration: checkJSDocFormat, + TSConstructSignatureDeclaration: checkJSDocFormat, + ExportDefaultDeclaration: checkJSDocFormat, + TSPropertySignature: checkJSDocFormat, + TSIndexSignature: checkJSDocFormat, + TSMethodSignature: checkJSDocFormat, + TSParameterProperty: checkJSDocFormat, + }; + }, +}); diff --git a/scripts/eslint/rules/one-namespace-per-file.cjs b/scripts/eslint/rules/one-namespace-per-file.cjs deleted file mode 100644 index 2b8772005a9c7..0000000000000 --- a/scripts/eslint/rules/one-namespace-per-file.cjs +++ /dev/null @@ -1,45 +0,0 @@ -const { AST_NODE_TYPES, TSESTree } = require("@typescript-eslint/utils"); -const { createRule } = require("./utils.cjs"); - -module.exports = createRule({ - name: "one-namespace-per-file", - meta: { - docs: { - description: `Limits each file to having at most one top-level namespace declaration`, - recommended: "error", - }, - messages: { - excessNamespaceError: `All but one of these namespaces should be moved into separate files.`, - }, - schema: [], - type: "problem", - }, - defaultOptions: [], - - create(context) { - /** @type {(node: TSESTree.Node) => node is TSESTree.TSModuleDeclaration} */ - const isNamespaceDeclaration = (node) => node.type === AST_NODE_TYPES.TSModuleDeclaration; - - /** @type {(node: TSESTree.Program) => void} */ - const checkSourceFile = (node) => { - if (context.getFilename().endsWith(".d.ts")) { - return; - } - const members = node.body; - const namespaces = members.filter(isNamespaceDeclaration); - if (namespaces.length <= 1) { - return; - } - - namespaces.forEach(n => { - context.report({ - messageId: "excessNamespaceError", node: n - }); - }); - }; - - return { - Program: checkSourceFile, - }; - }, -}); diff --git a/scripts/failed-tests.cjs b/scripts/failed-tests.cjs index 78e90fc97695d..d2f3078e7b765 100644 --- a/scripts/failed-tests.cjs +++ b/scripts/failed-tests.cjs @@ -10,6 +10,7 @@ const os = require("os"); reporter?: Mocha.ReporterConstructor | keyof Mocha.reporters; reporterOptions?: any; // TODO(jakebailey): what? }} ReporterOptions */ +void 0; /** * .failed-tests reporter diff --git a/scripts/generateLocalizedDiagnosticMessages.mjs b/scripts/generateLocalizedDiagnosticMessages.mjs index e32ba78d037a4..4970c9453f66e 100644 --- a/scripts/generateLocalizedDiagnosticMessages.mjs +++ b/scripts/generateLocalizedDiagnosticMessages.mjs @@ -1,8 +1,28 @@ import fs from "fs"; import path from "path"; -import xml2js from "xml2js"; +import { XMLParser } from "fast-xml-parser"; + +/** @typedef {{ + LCX: { + $_TgtCul: string; + Item: { + Item: { + Item: { + $_ItemId: string; + Str: { + Val: string; + Tgt: { + Val: string; + }; + }; + }[]; + }; + }; + } +}} ParsedLCL */ +void 0; -function main() { +async function main() { const args = process.argv.slice(2); if (args.length !== 3) { console.log("Usage:"); @@ -15,38 +35,33 @@ function main() { const diagnosticsMapFilePath = args[2]; // generate the lcg file for enu - generateLCGFile(); + await generateLCGFile(); // generate other langs - fs.readdir(inputPath, (err, files) => { - handleError(err); - files.forEach(visitDirectory); - }); + const files = await fs.promises.readdir(inputPath); + await Promise.all(files.map(visitDirectory)); return; /** * @param {string} name */ - function visitDirectory(name) { + async function visitDirectory(name) { const inputFilePath = path.join(inputPath, name, "diagnosticMessages", "diagnosticMessages.generated.json.lcl"); - - fs.readFile(inputFilePath, (err, data) => { - handleError(err); - xml2js.parseString(data.toString(), (err, result) => { - handleError(err); - if (!result || !result.LCX || !result.LCX.$ || !result.LCX.$.TgtCul) { - console.error("Unexpected XML file structure. Expected to find result.LCX.$.TgtCul."); - process.exit(1); - } - const outputDirectoryName = getPreferredLocaleName(result.LCX.$.TgtCul).toLowerCase(); - if (!outputDirectoryName) { - console.error(`Invalid output locale name for '${result.LCX.$.TgtCul}'.`); - process.exit(1); - } - writeFile(path.join(outputPath, outputDirectoryName, "diagnosticMessages.generated.json"), xmlObjectToString(result)); - }); - }); + const contents = await fs.promises.readFile(inputFilePath); + /** @type {ParsedLCL} */ + // eslint-disable-next-line local/object-literal-surrounding-space + const result = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "$_"}).parse(contents); + if (!result || !result.LCX || !result.LCX.$_TgtCul) { + console.error("Unexpected XML file structure. Expected to find result.LCX.$_TgtCul."); + process.exit(1); + } + const outputDirectoryName = getPreferredLocaleName(result.LCX.$_TgtCul).toLowerCase(); + if (!outputDirectoryName) { + console.error(`Invalid output locale name for '${result.LCX.$_TgtCul}'.`); + process.exit(1); + } + await writeFile(path.join(outputPath, outputDirectoryName, "diagnosticMessages.generated.json"), xmlObjectToString(result)); } /** @@ -81,24 +96,14 @@ function main() { } /** - * @param {null | object} err - */ - function handleError(err) { - if (err) { - console.error(err); - process.exit(1); - } - } - - /** - * @param {any} o + * @param {ParsedLCL} o */ function xmlObjectToString(o) { /** @type {any} */ const out = {}; - for (const item of o.LCX.Item[0].Item[0].Item) { - let ItemId = item.$.ItemId; - let val = item.Str[0].Tgt ? item.Str[0].Tgt[0].Val[0] : item.Str[0].Val[0]; + for (const item of o.LCX.Item.Item.Item) { + let ItemId = item.$_ItemId; + let val = item.Str.Tgt ? item.Str.Tgt.Val : item.Str.Val; if (typeof ItemId !== "string" || typeof val !== "string") { console.error("Unexpected XML file structure"); @@ -115,55 +120,26 @@ function main() { return JSON.stringify(out, undefined, 2); } - - /** - * @param {string} directoryPath - * @param {() => void} action - */ - function ensureDirectoryExists(directoryPath, action) { - fs.exists(directoryPath, exists => { - if (!exists) { - const basePath = path.dirname(directoryPath); - if (basePath !== directoryPath) { - return ensureDirectoryExists(basePath, () => fs.mkdir(directoryPath, action)); - } - } - action(); - }); - } - /** * @param {string} fileName * @param {string} contents */ - function writeFile(fileName, contents) { - ensureDirectoryExists(path.dirname(fileName), () => { - fs.writeFile(fileName, contents, handleError); - }); + async function writeFile(fileName, contents) { + await fs.promises.mkdir(path.dirname(fileName), { recursive: true }); + await fs.promises.writeFile(fileName, contents); } - /** - * @param {Record} o - */ - function objectToList(o) { - const list = []; - for (const key in o) { - list.push({ key, value: o[key] }); - } - return list; - } - - function generateLCGFile() { - return fs.readFile(diagnosticsMapFilePath, (err, data) => { - handleError(err); - writeFile( - path.join(outputPath, "enu", "diagnosticMessages.generated.json.lcg"), - getLCGFileXML( - objectToList(JSON.parse(data.toString())) - .sort((a, b) => a.key > b.key ? 1 : -1) // lcg sorted by property keys - .reduce((s, { key, value }) => s + getItemXML(key, value), "") - )); - }); + async function generateLCGFile() { + const contents = await fs.promises.readFile(diagnosticsMapFilePath, "utf-8"); + await writeFile( + path.join(outputPath, "enu", "diagnosticMessages.generated.json.lcg"), + getLCGFileXML( + Object.entries(JSON.parse(contents)) + .sort((a, b) => a[0] > b[0] ? 1 : -1) // lcg sorted by property keys + .reduce((s, [key, value]) => s + getItemXML(key, value), "") + ), + ); + return; /** * @param {string} key @@ -204,4 +180,4 @@ function main() { } } -main(); +await main(); diff --git a/scripts/hooks/post-checkout b/scripts/hooks/post-checkout index 36f5df5e92159..0e6180067defe 100755 --- a/scripts/hooks/post-checkout +++ b/scripts/hooks/post-checkout @@ -1,2 +1,2 @@ #!/bin/sh -npm run gulp -- generate-diagnostics +npm run npx hereby -- generate-diagnostics diff --git a/scripts/open-cherry-pick-pr.mjs b/scripts/open-cherry-pick-pr.mjs index 5402663b31bb1..d8b5f61a9ed25 100644 --- a/scripts/open-cherry-pick-pr.mjs +++ b/scripts/open-cherry-pick-pr.mjs @@ -71,7 +71,7 @@ ${logText.trim()}`; ]); if (produceLKG) { runSequence([ - ["gulp", ["LKG"]], + ["node", ["./node_modules/hereby/dist/cli.js", "LKG"]], ["git", ["add", "lib"]], ["git", ["commit", "-m", `"Update LKG"`]] ]); diff --git a/scripts/open-user-pr.mjs b/scripts/open-user-pr.mjs index 5594fe763ef95..f89d3a773c51d 100644 --- a/scripts/open-user-pr.mjs +++ b/scripts/open-user-pr.mjs @@ -15,7 +15,7 @@ runSequence([ ["git", ["checkout", "."]], // reset any changes ["git", ["fetch", baseRef === "main" ? "origin" : "fork", baseRef]], // fetch target ref in case it's not present locally ["git", ["checkout", baseRef]], // move head to target - ["node", ["./node_modules/gulp/bin/gulp.js", "baseline-accept"]], // accept baselines + ["node", ["./node_modules/hereby/dist/cli.js", "baseline-accept"]], // accept baselines ["git", ["checkout", "-b", branchName]], // create a branch ["git", ["add", "."]], // Add all changes ["git", ["commit", "-m", `"Update user baselines${+(process.env.SOURCE_ISSUE ?? 0) === 33716 ? " +cc @sandersn" : ""}"`]], // Commit all changes (ping nathan if we would post to CI thread) diff --git a/scripts/processDiagnosticMessages.mjs b/scripts/processDiagnosticMessages.mjs index cddd618d03abe..2b36942cc461a 100644 --- a/scripts/processDiagnosticMessages.mjs +++ b/scripts/processDiagnosticMessages.mjs @@ -9,10 +9,11 @@ import fs from "fs"; isEarly?: boolean; elidedInCompatabilityPyramid?: boolean; }} DiagnosticDetails */ +void 0; /** @typedef {Map} InputDiagnosticMessageTable */ -function main() { +async function main() { if (process.argv.length < 3) { console.log("Usage:"); console.log("\tnode processDiagnosticMessages.mjs "); @@ -23,15 +24,24 @@ function main() { * @param {string} fileName * @param {string} contents */ - function writeFile(fileName, contents) { - fs.writeFile(path.join(path.dirname(inputFilePath), fileName), contents, { encoding: "utf-8" }, err => { - if (err) throw err; - }); + async function writeFile(fileName, contents) { + const filePath = path.join(path.dirname(inputFilePath), fileName); + try { + const existingContents = await fs.promises.readFile(filePath, "utf-8"); + if (existingContents === contents) { + return; + } + } + catch { + // Just write the file. + } + + await fs.promises.writeFile(filePath, contents, { encoding: "utf-8" }); } const inputFilePath = process.argv[2].replace(/\\/g, "/"); console.log(`Reading diagnostics from ${inputFilePath}`); - const inputStr = fs.readFileSync(inputFilePath, { encoding: "utf-8" }); + const inputStr = await fs.promises.readFile(inputFilePath, { encoding: "utf-8" }); /** @type {{ [key: string]: DiagnosticDetails }} */ const diagnosticMessagesJson = JSON.parse(inputStr); @@ -44,15 +54,12 @@ function main() { } } - const outputFilesDir = path.dirname(inputFilePath); - const thisFilePathRel = path.relative(process.cwd(), outputFilesDir); - - const infoFileOutput = buildInfoFileOutput(diagnosticMessages, `./${path.basename(inputFilePath)}`, thisFilePathRel); + const infoFileOutput = buildInfoFileOutput(diagnosticMessages, inputFilePath); checkForUniqueCodes(diagnosticMessages); - writeFile("diagnosticInformationMap.generated.ts", infoFileOutput); + await writeFile("diagnosticInformationMap.generated.ts", infoFileOutput); const messageOutput = buildDiagnosticMessageOutput(diagnosticMessages); - writeFile("diagnosticMessages.generated.json", messageOutput); + await writeFile("diagnosticMessages.generated.json", messageOutput); } /** @@ -72,31 +79,34 @@ function checkForUniqueCodes(diagnosticTable) { /** * @param {InputDiagnosticMessageTable} messageTable * @param {string} inputFilePathRel - * @param {string} thisFilePathRel * @returns {string} */ -function buildInfoFileOutput(messageTable, inputFilePathRel, thisFilePathRel) { - let result = - "// \r\n" + - "// generated from '" + inputFilePathRel + "' in '" + thisFilePathRel.replace(/\\/g, "/") + "'\r\n" + - "/* @internal */\r\n" + - "namespace ts {\r\n" + - " function diag(code: number, category: DiagnosticCategory, key: string, message: string, reportsUnnecessary?: {}, elidedInCompatabilityPyramid?: boolean, reportsDeprecated?: {}): DiagnosticMessage {\r\n" + - " return { code, category, key, message, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated };\r\n" + - " }\r\n" + - " export const Diagnostics = {\r\n"; +function buildInfoFileOutput(messageTable, inputFilePathRel) { + const result = [ + "// ", + `// generated from '${inputFilePathRel}'`, + "", + "import { DiagnosticCategory, DiagnosticMessage } from \"./types\";", + "", + "function diag(code: number, category: DiagnosticCategory, key: string, message: string, reportsUnnecessary?: {}, elidedInCompatabilityPyramid?: boolean, reportsDeprecated?: {}): DiagnosticMessage {", + " return { code, category, key, message, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated };", + "}", + "", + "/** @internal */", + "export const Diagnostics = {", + ]; messageTable.forEach(({ code, category, reportsUnnecessary, elidedInCompatabilityPyramid, reportsDeprecated }, name) => { const propName = convertPropertyName(name); const argReportsUnnecessary = reportsUnnecessary ? `, /*reportsUnnecessary*/ ${reportsUnnecessary}` : ""; const argElidedInCompatabilityPyramid = elidedInCompatabilityPyramid ? `${!reportsUnnecessary ? ", /*reportsUnnecessary*/ undefined" : ""}, /*elidedInCompatabilityPyramid*/ ${elidedInCompatabilityPyramid}` : ""; const argReportsDeprecated = reportsDeprecated ? `${!argElidedInCompatabilityPyramid ? ", /*reportsUnnecessary*/ undefined, /*elidedInCompatabilityPyramid*/ undefined" : ""}, /*reportsDeprecated*/ ${reportsDeprecated}` : ""; - result += ` ${propName}: diag(${code}, DiagnosticCategory.${category}, "${createKey(propName, code)}", ${JSON.stringify(name)}${argReportsUnnecessary}${argElidedInCompatabilityPyramid}${argReportsDeprecated}),\r\n`; + result.push(` ${propName}: diag(${code}, DiagnosticCategory.${category}, "${createKey(propName, code)}", ${JSON.stringify(name)}${argReportsUnnecessary}${argElidedInCompatabilityPyramid}${argReportsDeprecated}),`); }); - result += " };\r\n}"; + result.push("};"); - return result; + return result.join("\r\n"); } /** diff --git a/scripts/produceLKG.mjs b/scripts/produceLKG.mjs index 6861f4f7d64c3..4f0133936c18b 100644 --- a/scripts/produceLKG.mjs +++ b/scripts/produceLKG.mjs @@ -1,8 +1,9 @@ -import childProcess from "child_process"; import fs from "fs-extra"; import path from "path"; import glob from "glob"; import url from "url"; +import del from "del"; +import { localizationDirectories } from "./build/localization.mjs"; const __filename = url.fileURLToPath(new URL(import.meta.url)); const __dirname = path.dirname(__filename); @@ -10,16 +11,16 @@ const __dirname = path.dirname(__filename); const root = path.join(__dirname, ".."); const source = path.join(root, "built/local"); const dest = path.join(root, "lib"); -const copyright = fs.readFileSync(path.join(__dirname, "../CopyrightNotice.txt"), "utf-8"); async function produceLKG() { console.log(`Building LKG from ${source} to ${dest}`); + await del(`${dest.replace(/\\/g, "/")}/**`, { ignore: ["**/README.md"] }); + await fs.mkdirp(dest); await copyLibFiles(); await copyLocalizedDiagnostics(); await copyTypesMap(); await copyScriptOutputs(); await copyDeclarationOutputs(); - await buildProtocol(); await writeGitAttributes(); } @@ -28,15 +29,9 @@ async function copyLibFiles() { } async function copyLocalizedDiagnostics() { - const dir = await fs.readdir(source); - const ignoredFolders = ["enu"]; - - for (const d of dir) { + for (const d of localizationDirectories) { const fileName = path.join(source, d); - if ( - fs.statSync(fileName).isDirectory() && - ignoredFolders.indexOf(d) < 0 - ) { + if (fs.statSync(fileName).isDirectory()) { await fs.copy(fileName, path.join(dest, d)); } } @@ -46,51 +41,25 @@ async function copyTypesMap() { await copyFromBuiltLocal("typesMap.json"); // Cannot accommodate copyright header } -async function buildProtocol() { - const protocolScript = path.join(__dirname, "buildProtocol.mjs"); - if (!fs.existsSync(protocolScript)) { - throw new Error(`Expected protocol script ${protocolScript} to exist`); - } - - const protocolInput = path.join(__dirname, "../src/server/protocol.ts"); - const protocolServices = path.join(source, "typescriptServices.d.ts"); - const protocolOutput = path.join(dest, "protocol.d.ts"); - - console.log(`Building ${protocolOutput}...`); - await exec(protocolScript, [protocolInput, protocolServices, protocolOutput]); -} - async function copyScriptOutputs() { - await copyWithCopyright("cancellationToken.js"); - await copyWithCopyright("tsc.release.js", "tsc.js"); - await copyWithCopyright("tsserver.js"); - await copyWithCopyright("dynamicImportCompat.js"); - await copyFromBuiltLocal("tsserverlibrary.js"); // copyright added by build - await copyFromBuiltLocal("typescript.js"); // copyright added by build - await copyFromBuiltLocal("typescriptServices.js"); // copyright added by build - await copyWithCopyright("typingsInstaller.js"); - await copyWithCopyright("watchGuard.js"); + await copyFromBuiltLocal("cancellationToken.js"); + await copyFromBuiltLocal("tsc.js"); + await copyFromBuiltLocal("tsserver.js"); + await copyFromBuiltLocal("tsserverlibrary.js"); + await copyFromBuiltLocal("typescript.js"); + await copyFromBuiltLocal("typingsInstaller.js"); + await copyFromBuiltLocal("watchGuard.js"); } async function copyDeclarationOutputs() { - await copyFromBuiltLocal("tsserverlibrary.d.ts"); // copyright added by build - await copyFromBuiltLocal("typescript.d.ts"); // copyright added by build - await copyFromBuiltLocal("typescriptServices.d.ts"); // copyright added by build + await copyFromBuiltLocal("tsserverlibrary.d.ts"); + await copyFromBuiltLocal("typescript.d.ts"); } async function writeGitAttributes() { await fs.writeFile(path.join(dest, ".gitattributes"), `* text eol=lf`, "utf-8"); } -/** - * @param {string} fileName - * @param {string} destName - */ -async function copyWithCopyright(fileName, destName = fileName) { - const content = await fs.readFile(path.join(source, fileName), "utf-8"); - await fs.writeFile(path.join(dest, destName), copyright + "\n" + content); -} - /** * @param {string} fileName */ @@ -109,16 +78,6 @@ async function copyFilesWithGlob(pattern) { console.log(`Copied ${files.length} files matching pattern ${pattern}`); } -/** - * @param {string} path - * @param {string[]} args - */ -async function exec(path, args = []) { - const cmdLine = ["node", path, ...args].join(" "); - console.log(cmdLine); - childProcess.execSync(cmdLine); -} - process.on("unhandledRejection", err => { throw err; }); diff --git a/scripts/types/ambient.d.ts b/scripts/types/ambient.d.ts deleted file mode 100644 index d48de7c05d7b4..0000000000000 --- a/scripts/types/ambient.d.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { TaskFunction } from "gulp"; - -declare module "gulp-insert" { - export function append(text: string | Buffer): NodeJS.ReadWriteStream; - export function prepend(text: string | Buffer): NodeJS.ReadWriteStream; - export function wrap(text: string | Buffer, tail: string | Buffer): NodeJS.ReadWriteStream; - export function transform(cb: (contents: string, file: {path: string, relative: string}) => string): NodeJS.ReadWriteStream; // file is a vinyl file -} - -declare module "sorcery"; - -declare module "vinyl" { - // NOTE: This makes it possible to correctly type vinyl Files under @ts-check. - export = File; - - declare class File { - constructor(options?: File.VinylOptions); - - cwd: string; - base: string; - path: string; - readonly history: readonly string[]; - contents: T; - relative: string; - dirname: string; - basename: string; - stem: string; - extname: string; - symlink: string | null; - stat: import("fs").Stats | null; - sourceMap?: import("./sourcemaps").RawSourceMap | string; - - [custom: string]: any; - - isBuffer(): this is T extends Buffer ? File : never; - isStream(): this is T extends NodeJS.ReadableStream ? File : never; - isNull(): this is T extends null ? File : never; - isDirectory(): this is T extends null ? File.Directory : never; - isSymbolic(): this is T extends null ? File.Symbolic : never; - clone(opts?: { contents?: boolean, deep?: boolean }): this; - } - - namespace File { - export interface VinylOptions { - cwd?: string; - base?: string; - path?: string; - history?: readonly string[]; - stat?: import("fs").Stats; - contents?: T; - sourceMap?: import("./sourcemaps").RawSourceMap | string; - [custom: string]: any; - } - - export type Contents = Buffer | NodeJS.ReadableStream | null; - export type File = import("./vinyl"); - export type NullFile = File; - export type BufferFile = File; - export type StreamFile = File; - - export interface Directory extends NullFile { - isNull(): true; - isDirectory(): true; - isSymbolic(): this is never; - } - - export interface Symbolic extends NullFile { - isNull(): true; - isDirectory(): this is never; - isSymbolic(): true; - } - } -} - -declare module "undertaker" { - interface TaskFunctionParams { - flags?: Record; - } - interface TaskFunctionWrapped { - description: string - flags: { [name: string]: string } - } -} - -declare module "gulp-sourcemaps" { - interface WriteOptions { - destPath?: string; - } - -} diff --git a/scripts/word2md.mjs b/scripts/word2md.mjs index 785bcf93f1475..1bed91726ad41 100644 --- a/scripts/word2md.mjs +++ b/scripts/word2md.mjs @@ -60,6 +60,7 @@ const sys = (() => { subscript?: boolean; }; }} FindReplaceOptions */ +void 0; /** * @param {Word.Document} doc diff --git a/src/.eslintrc.json b/src/.eslintrc.json index f7ec4c8a8f555..f29dc06406312 100644 --- a/src/.eslintrc.json +++ b/src/.eslintrc.json @@ -6,7 +6,21 @@ }, "rules": { "@typescript-eslint/no-unnecessary-qualifier": "error", - "@typescript-eslint/no-unnecessary-type-assertion": "error" + "@typescript-eslint/no-unnecessary-type-assertion": "error", + "no-restricted-globals": ["error", + { "name": "setTimeout" }, + { "name": "clearTimeout" }, + { "name": "setInterval" }, + { "name": "clearInterval" }, + { "name": "setImmediate" }, + { "name": "clearImmediate" }, + { "name": "performance" }, + { "name": "Iterator" }, + { "name": "Map" }, + { "name": "ReadonlyMap" }, + { "name": "Set" }, + { "name": "ReadonlySet" } + ] }, "overrides": [ { @@ -20,7 +34,8 @@ "local/no-keywords": "off", // eslint - "no-var": "off" + "no-var": "off", + "no-restricted-globals": "off" } }, { @@ -28,6 +43,12 @@ "rules": { "@typescript-eslint/array-type": "off" } + }, + { + "files": ["debug/**", "harness/**", "testRunner/**"], + "rules": { + "no-restricted-globals": "off" + } } ] } diff --git a/src/cancellationToken/cancellationToken.ts b/src/cancellationToken/cancellationToken.ts index aaa19750b8710..7f1b3998210e1 100644 --- a/src/cancellationToken/cancellationToken.ts +++ b/src/cancellationToken/cancellationToken.ts @@ -1,6 +1,4 @@ -/// - -import fs = require("fs"); +import * as fs from "fs"; interface ServerCancellationToken { isCancellationRequested(): boolean; diff --git a/src/cancellationToken/tsconfig.json b/src/cancellationToken/tsconfig.json index 6d27fe45cf37f..9249fae5d1795 100644 --- a/src/cancellationToken/tsconfig.json +++ b/src/cancellationToken/tsconfig.json @@ -1,13 +1,6 @@ { - "extends": "../tsconfig-noncomposite-base", + "extends": "../tsconfig-base", "compilerOptions": { - "outDir": "../../built/local/", - "rootDir": ".", - "composite": false, - "declaration": false, - "declarationMap": false, - "removeComments": true, - "incremental": false, "module": "commonjs", "types": [ "node" @@ -16,7 +9,5 @@ "es6" ] }, - "files": [ - "cancellationToken.ts" - ] + "include": ["**/*"] } diff --git a/src/compiler/_namespaces/ts.moduleSpecifiers.ts b/src/compiler/_namespaces/ts.moduleSpecifiers.ts new file mode 100644 index 0000000000000..47a204d4a72b9 --- /dev/null +++ b/src/compiler/_namespaces/ts.moduleSpecifiers.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the ts.moduleSpecifiers namespace. */ + +export * from "../moduleSpecifiers"; diff --git a/src/compiler/_namespaces/ts.performance.ts b/src/compiler/_namespaces/ts.performance.ts new file mode 100644 index 0000000000000..707f8f85ef697 --- /dev/null +++ b/src/compiler/_namespaces/ts.performance.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the ts.performance namespace. */ + +export * from "../performance"; diff --git a/src/compiler/_namespaces/ts.ts b/src/compiler/_namespaces/ts.ts new file mode 100644 index 0000000000000..b31b19cae7cb5 --- /dev/null +++ b/src/compiler/_namespaces/ts.ts @@ -0,0 +1,74 @@ +/* Generated file to emulate the ts namespace. */ + +export * from "../corePublic"; +export * from "../core"; +export * from "../debug"; +export * from "../semver"; +export * from "../performanceCore"; +export * from "../perfLogger"; +export * from "../tracing"; +export * from "../types"; +export * from "../sys"; +export * from "../path"; +export * from "../diagnosticInformationMap.generated"; +export * from "../scanner"; +export * from "../utilitiesPublic"; +export * from "../utilities"; +export * from "../factory/baseNodeFactory"; +export * from "../factory/parenthesizerRules"; +export * from "../factory/nodeConverters"; +export * from "../factory/nodeFactory"; +export * from "../factory/emitNode"; +export * from "../factory/emitHelpers"; +export * from "../factory/nodeTests"; +export * from "../factory/utilities"; +export * from "../factory/utilitiesPublic"; +export * from "../parser"; +export * from "../commandLineParser"; +export * from "../moduleNameResolver"; +export * from "../binder"; +export * from "../symbolWalker"; +export * from "../checker"; +export * from "../visitorPublic"; +export * from "../sourcemap"; +export * from "../transformers/utilities"; +export * from "../transformers/destructuring"; +export * from "../transformers/taggedTemplate"; +export * from "../transformers/ts"; +export * from "../transformers/classFields"; +export * from "../transformers/typeSerializer"; +export * from "../transformers/legacyDecorators"; +export * from "../transformers/es2017"; +export * from "../transformers/es2018"; +export * from "../transformers/es2019"; +export * from "../transformers/es2020"; +export * from "../transformers/es2021"; +export * from "../transformers/esnext"; +export * from "../transformers/jsx"; +export * from "../transformers/es2016"; +export * from "../transformers/es2015"; +export * from "../transformers/es5"; +export * from "../transformers/generators"; +export * from "../transformers/module/module"; +export * from "../transformers/module/system"; +export * from "../transformers/module/esnextAnd2015"; +export * from "../transformers/module/node"; +export * from "../transformers/declarations/diagnostics"; +export * from "../transformers/declarations"; +export * from "../transformer"; +export * from "../emitter"; +export * from "../watchUtilities"; +export * from "../program"; +export * from "../builderStatePublic"; +export * from "../builderState"; +export * from "../builder"; +export * from "../builderPublic"; +export * from "../resolutionCache"; +export * from "../watch"; +export * from "../watchPublic"; +export * from "../tsbuild"; +export * from "../tsbuildPublic"; +import * as moduleSpecifiers from "./ts.moduleSpecifiers"; +export { moduleSpecifiers }; +import * as performance from "./ts.performance"; +export { performance }; diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 4c3eac092b45a..ca6d9526fde1b 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1,3536 +1,3598 @@ +import { + __String, AccessExpression, addRelatedInfo, append, appendIfUnique, ArrayBindingElement, ArrayLiteralExpression, + ArrowFunction, AssignmentDeclarationKind, BinaryExpression, BinaryOperatorToken, BindableAccessExpression, + BindableObjectDefinePropertyCall, BindablePropertyAssignmentExpression, BindableStaticAccessExpression, + BindableStaticNameExpression, BindableStaticPropertyAssignmentExpression, BindingElement, Block, + BreakOrContinueStatement, CallChain, CallExpression, CaseBlock, CaseClause, cast, CatchClause, ClassLikeDeclaration, + ClassStaticBlockDeclaration, CompilerOptions, concatenate, ConditionalExpression, ConditionalTypeNode, contains, + createBinaryExpressionTrampoline, createDiagnosticForNodeInSourceFile, createFileDiagnostic, createQueue, + createSymbolTable, Debug, Declaration, declarationNameToString, DeleteExpression, DestructuringAssignment, + DiagnosticCategory, DiagnosticMessage, DiagnosticRelatedInformation, Diagnostics, DiagnosticWithLocation, + DoStatement, DynamicNamedDeclaration, ElementAccessChain, ElementAccessExpression, EntityNameExpression, + EnumDeclaration, escapeLeadingUnderscores, ESMap, every, ExportAssignment, exportAssignmentIsAlias, + ExportDeclaration, ExportSpecifier, Expression, ExpressionStatement, findAncestor, FlowFlags, FlowLabel, FlowNode, + FlowReduceLabel, forEach, forEachChild, ForInOrOfStatement, ForStatement, FunctionDeclaration, FunctionExpression, + FunctionLikeDeclaration, GetAccessorDeclaration, getAssignedExpandoInitializer, getAssignmentDeclarationKind, + getAssignmentDeclarationPropertyAccessKind, getCombinedModifierFlags, getCombinedNodeFlags, getContainingClass, + getEffectiveContainerForJSDocTemplateTag, getElementOrPropertyAccessName, getEmitScriptTarget, + getEnclosingBlockScopeContainer, getErrorSpanForNode, getEscapedTextOfIdentifierOrLiteral, getExpandoInitializer, + getHostSignatureFromJSDoc, getImmediatelyInvokedFunctionExpression, getJSDocTypeTag, getLeftmostAccessExpression, + getNameOfDeclaration, getNameOrArgument, getNodeId, getRangesWhere, getRightMostAssignedExpression, + getSourceFileOfNode, getSourceTextOfNodeFromSourceFile, getSpanOfTokenAtPosition, getStrictOptionValue, + getSymbolNameForPrivateIdentifier, getTextOfIdentifierOrLiteral, getThisContainer, getTokenPosOfNode, + hasDynamicName, hasJSDocNodes, hasSyntacticModifier, Identifier, idText, IfStatement, ImportClause, + InternalSymbolName, isAliasableExpression, isAmbientModule, isAssignmentExpression, isAssignmentOperator, + isAssignmentTarget, isAsyncFunction, isAutoAccessorPropertyDeclaration, isBinaryExpression, + isBindableObjectDefinePropertyCall, isBindableStaticAccessExpression, isBindableStaticNameExpression, + isBindingPattern, isBlock, isBlockOrCatchScoped, isCallExpression, isClassStaticBlockDeclaration, + isConditionalTypeNode, isDeclaration, isDeclarationStatement, isDestructuringAssignment, isDottedName, + isElementAccessExpression, isEmptyObjectLiteral, isEntityNameExpression, isEnumConst, isEnumDeclaration, + isExportAssignment, isExportDeclaration, isExportsIdentifier, isExportSpecifier, isExpression, + isExpressionOfOptionalChainRoot, isExternalModule, isExternalOrCommonJsModule, isForInOrOfStatement, + isFunctionDeclaration, isFunctionLike, isFunctionLikeDeclaration, isFunctionLikeOrClassStaticBlockDeclaration, + isFunctionSymbol, isGlobalScopeAugmentation, isIdentifier, isIdentifierName, isInJSFile, isInTopLevelContext, + isJSDocConstructSignature, isJSDocEnumTag, isJSDocTemplateTag, isJSDocTypeAlias, isJsonSourceFile, + isLeftHandSideExpression, isLogicalOrCoalescingAssignmentOperator, isModuleAugmentationExternal, isModuleBlock, + isModuleDeclaration, isModuleExportsAccessExpression, isNamedDeclaration, isNamespaceExport, isNonNullExpression, + isNullishCoalesce, isObjectLiteralExpression, isObjectLiteralMethod, + isObjectLiteralOrClassExpressionMethodOrAccessor, isOmittedExpression, isOptionalChain, isOptionalChainRoot, + isOutermostOptionalChain, isParameterDeclaration, isParameterPropertyDeclaration, isParenthesizedExpression, + isPartOfTypeQuery, isPrefixUnaryExpression, isPrivateIdentifier, isPrologueDirective, + isPropertyAccessEntityNameExpression, isPropertyAccessExpression, isPropertyNameLiteral, isPrototypeAccess, + isPushOrUnshiftIdentifier, isRequireCall, isShorthandPropertyAssignment, isSignedNumericLiteral, isSourceFile, + isSpecialPropertyDeclaration, isStatement, isStatementButNotDeclaration, isStatic, isString, isStringLiteralLike, + isStringOrNumericLiteralLike, isThisInitializedDeclaration, isTypeAliasDeclaration, isTypeOfExpression, + isVariableDeclaration, isVariableDeclarationInitializedToBareOrAccessedRequire, isVariableStatement, + JSDocCallbackTag, JSDocClassTag, JSDocEnumTag, JSDocFunctionType, JSDocParameterTag, JSDocPropertyLikeTag, + JSDocSignature, JSDocTypedefTag, JSDocTypeLiteral, JsxAttribute, JsxAttributes, LabeledStatement, length, + LiteralLikeElementAccessExpression, Map, MappedTypeNode, MethodDeclaration, ModifierFlags, ModuleBlock, + ModuleDeclaration, Mutable, NamespaceExportDeclaration, Node, NodeArray, NodeFlags, nodeHasName, nodeIsMissing, + nodeIsPresent, NonNullChain, NonNullExpression, NumericLiteral, objectAllocator, ObjectLiteralExpression, + OptionalChain, ParameterDeclaration, ParenthesizedExpression, Pattern, PatternAmbientModule, perfLogger, + PostfixUnaryExpression, PrefixUnaryExpression, PrivateIdentifier, PropertyAccessChain, PropertyAccessExpression, + PropertyDeclaration, PropertySignature, removeFileExtension, ReturnStatement, ScriptTarget, Set, + SetAccessorDeclaration, setParent, setParentRecursive, setValueDeclaration, ShorthandPropertyAssignment, + shouldPreserveConstEnums, SignatureDeclaration, skipParentheses, sliceAfter, some, SourceFile, SpreadElement, + Statement, StringLiteral, SwitchStatement, Symbol, SymbolFlags, symbolName, SymbolTable, SyntaxKind, TextRange, + ThrowStatement, TokenFlags, tokenToString, tracing, TracingNode, tryCast, tryParsePattern, TryStatement, + TypeLiteralNode, TypeOfExpression, TypeParameterDeclaration, unescapeLeadingUnderscores, unreachableCodeIsError, + unusedLabelIsError, VariableDeclaration, WhileStatement, WithStatement, +} from "./_namespaces/ts"; +import * as performance from "./_namespaces/ts.performance"; + +/** @internal */ +export const enum ModuleInstanceState { + NonInstantiated = 0, + Instantiated = 1, + ConstEnumOnly = 2 +} -/* @internal */ -namespace ts { - export const enum ModuleInstanceState { - NonInstantiated = 0, - Instantiated = 1, - ConstEnumOnly = 2 - } - - interface ActiveLabel { - next: ActiveLabel | undefined; - name: __String; - breakTarget: FlowLabel; - continueTarget: FlowLabel | undefined; - referenced: boolean; - } +interface ActiveLabel { + next: ActiveLabel | undefined; + name: __String; + breakTarget: FlowLabel; + continueTarget: FlowLabel | undefined; + referenced: boolean; +} - export function getModuleInstanceState(node: ModuleDeclaration, visited?: ESMap): ModuleInstanceState { - if (node.body && !node.body.parent) { - // getModuleInstanceStateForAliasTarget needs to walk up the parent chain, so parent pointers must be set on this tree already - setParent(node.body, node); - setParentRecursive(node.body, /*incremental*/ false); - } - return node.body ? getModuleInstanceStateCached(node.body, visited) : ModuleInstanceState.Instantiated; +/** @internal */ +export function getModuleInstanceState(node: ModuleDeclaration, visited?: ESMap): ModuleInstanceState { + if (node.body && !node.body.parent) { + // getModuleInstanceStateForAliasTarget needs to walk up the parent chain, so parent pointers must be set on this tree already + setParent(node.body, node); + setParentRecursive(node.body, /*incremental*/ false); } + return node.body ? getModuleInstanceStateCached(node.body, visited) : ModuleInstanceState.Instantiated; +} - function getModuleInstanceStateCached(node: Node, visited = new Map()) { - const nodeId = getNodeId(node); - if (visited.has(nodeId)) { - return visited.get(nodeId) || ModuleInstanceState.NonInstantiated; - } - visited.set(nodeId, undefined); - const result = getModuleInstanceStateWorker(node, visited); - visited.set(nodeId, result); - return result; +function getModuleInstanceStateCached(node: Node, visited = new Map()) { + const nodeId = getNodeId(node); + if (visited.has(nodeId)) { + return visited.get(nodeId) || ModuleInstanceState.NonInstantiated; } + visited.set(nodeId, undefined); + const result = getModuleInstanceStateWorker(node, visited); + visited.set(nodeId, result); + return result; +} - function getModuleInstanceStateWorker(node: Node, visited: ESMap): ModuleInstanceState { - // A module is uninstantiated if it contains only - switch (node.kind) { - // 1. interface declarations, type alias declarations - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: +function getModuleInstanceStateWorker(node: Node, visited: ESMap): ModuleInstanceState { + // A module is uninstantiated if it contains only + switch (node.kind) { + // 1. interface declarations, type alias declarations + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return ModuleInstanceState.NonInstantiated; + // 2. const enum declarations + case SyntaxKind.EnumDeclaration: + if (isEnumConst(node as EnumDeclaration)) { + return ModuleInstanceState.ConstEnumOnly; + } + break; + // 3. non-exported import declarations + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + if (!(hasSyntacticModifier(node, ModifierFlags.Export))) { return ModuleInstanceState.NonInstantiated; - // 2. const enum declarations - case SyntaxKind.EnumDeclaration: - if (isEnumConst(node as EnumDeclaration)) { - return ModuleInstanceState.ConstEnumOnly; - } - break; - // 3. non-exported import declarations - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - if (!(hasSyntacticModifier(node, ModifierFlags.Export))) { - return ModuleInstanceState.NonInstantiated; - } - break; - // 4. Export alias declarations pointing at only uninstantiated modules or things uninstantiated modules contain - case SyntaxKind.ExportDeclaration: - const exportDeclaration = node as ExportDeclaration; - if (!exportDeclaration.moduleSpecifier && exportDeclaration.exportClause && exportDeclaration.exportClause.kind === SyntaxKind.NamedExports) { - let state = ModuleInstanceState.NonInstantiated; - for (const specifier of exportDeclaration.exportClause.elements) { - const specifierState = getModuleInstanceStateForAliasTarget(specifier, visited); - if (specifierState > state) { - state = specifierState; - } - if (state === ModuleInstanceState.Instantiated) { - return state; - } - } - return state; - } - break; - // 5. other uninstantiated module declarations. - case SyntaxKind.ModuleBlock: { + } + break; + // 4. Export alias declarations pointing at only uninstantiated modules or things uninstantiated modules contain + case SyntaxKind.ExportDeclaration: + const exportDeclaration = node as ExportDeclaration; + if (!exportDeclaration.moduleSpecifier && exportDeclaration.exportClause && exportDeclaration.exportClause.kind === SyntaxKind.NamedExports) { let state = ModuleInstanceState.NonInstantiated; - forEachChild(node, n => { - const childState = getModuleInstanceStateCached(n, visited); - switch (childState) { - case ModuleInstanceState.NonInstantiated: - // child is non-instantiated - continue searching - return; - case ModuleInstanceState.ConstEnumOnly: - // child is const enum only - record state and continue searching - state = ModuleInstanceState.ConstEnumOnly; - return; - case ModuleInstanceState.Instantiated: - // child is instantiated - record state and stop - state = ModuleInstanceState.Instantiated; - return true; - default: - Debug.assertNever(childState); + for (const specifier of exportDeclaration.exportClause.elements) { + const specifierState = getModuleInstanceStateForAliasTarget(specifier, visited); + if (specifierState > state) { + state = specifierState; + } + if (state === ModuleInstanceState.Instantiated) { + return state; } - }); + } return state; } - case SyntaxKind.ModuleDeclaration: - return getModuleInstanceState(node as ModuleDeclaration, visited); - case SyntaxKind.Identifier: - // Only jsdoc typedef definition can exist in jsdoc namespace, and it should - // be considered the same as type alias - if ((node as Identifier).isInJSDocNamespace) { - return ModuleInstanceState.NonInstantiated; + break; + // 5. other uninstantiated module declarations. + case SyntaxKind.ModuleBlock: { + let state = ModuleInstanceState.NonInstantiated; + forEachChild(node, n => { + const childState = getModuleInstanceStateCached(n, visited); + switch (childState) { + case ModuleInstanceState.NonInstantiated: + // child is non-instantiated - continue searching + return; + case ModuleInstanceState.ConstEnumOnly: + // child is const enum only - record state and continue searching + state = ModuleInstanceState.ConstEnumOnly; + return; + case ModuleInstanceState.Instantiated: + // child is instantiated - record state and stop + state = ModuleInstanceState.Instantiated; + return true; + default: + Debug.assertNever(childState); } + }); + return state; } - return ModuleInstanceState.Instantiated; + case SyntaxKind.ModuleDeclaration: + return getModuleInstanceState(node as ModuleDeclaration, visited); + case SyntaxKind.Identifier: + // Only jsdoc typedef definition can exist in jsdoc namespace, and it should + // be considered the same as type alias + if ((node as Identifier).isInJSDocNamespace) { + return ModuleInstanceState.NonInstantiated; + } } + return ModuleInstanceState.Instantiated; +} - function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visited: ESMap) { - const name = specifier.propertyName || specifier.name; - let p: Node | undefined = specifier.parent; - while (p) { - if (isBlock(p) || isModuleBlock(p) || isSourceFile(p)) { - const statements = p.statements; - let found: ModuleInstanceState | undefined; - for (const statement of statements) { - if (nodeHasName(statement, name)) { - if (!statement.parent) { - setParent(statement, p); - setParentRecursive(statement, /*incremental*/ false); - } - const state = getModuleInstanceStateCached(statement, visited); - if (found === undefined || state > found) { - found = state; - } - if (found === ModuleInstanceState.Instantiated) { - return found; - } +function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visited: ESMap) { + const name = specifier.propertyName || specifier.name; + let p: Node | undefined = specifier.parent; + while (p) { + if (isBlock(p) || isModuleBlock(p) || isSourceFile(p)) { + const statements = p.statements; + let found: ModuleInstanceState | undefined; + for (const statement of statements) { + if (nodeHasName(statement, name)) { + if (!statement.parent) { + setParent(statement, p); + setParentRecursive(statement, /*incremental*/ false); + } + const state = getModuleInstanceStateCached(statement, visited); + if (found === undefined || state > found) { + found = state; + } + if (found === ModuleInstanceState.Instantiated) { + return found; } } - if (found !== undefined) { - return found; - } } - p = p.parent; + if (found !== undefined) { + return found; + } } - return ModuleInstanceState.Instantiated; // Couldn't locate, assume could refer to a value + p = p.parent; } + return ModuleInstanceState.Instantiated; // Couldn't locate, assume could refer to a value +} - const enum ContainerFlags { - // The current node is not a container, and no container manipulation should happen before - // recursing into it. - None = 0, +const enum ContainerFlags { + // The current node is not a container, and no container manipulation should happen before + // recursing into it. + None = 0, + + // The current node is a container. It should be set as the current container (and block- + // container) before recursing into it. The current node does not have locals. Examples: + // + // Classes, ObjectLiterals, TypeLiterals, Interfaces... + IsContainer = 1 << 0, + + // The current node is a block-scoped-container. It should be set as the current block- + // container before recursing into it. Examples: + // + // Blocks (when not parented by functions), Catch clauses, For/For-in/For-of statements... + IsBlockScopedContainer = 1 << 1, + + // The current node is the container of a control flow path. The current control flow should + // be saved and restored, and a new control flow initialized within the container. + IsControlFlowContainer = 1 << 2, + + IsFunctionLike = 1 << 3, + IsFunctionExpression = 1 << 4, + HasLocals = 1 << 5, + IsInterface = 1 << 6, + IsObjectLiteralOrClassExpressionMethodOrAccessor = 1 << 7, +} - // The current node is a container. It should be set as the current container (and block- - // container) before recursing into it. The current node does not have locals. Examples: - // - // Classes, ObjectLiterals, TypeLiterals, Interfaces... - IsContainer = 1 << 0, +function initFlowNode(node: T) { + Debug.attachFlowNodeDebugInfo(node); + return node; +} - // The current node is a block-scoped-container. It should be set as the current block- - // container before recursing into it. Examples: - // - // Blocks (when not parented by functions), Catch clauses, For/For-in/For-of statements... - IsBlockScopedContainer = 1 << 1, - - // The current node is the container of a control flow path. The current control flow should - // be saved and restored, and a new control flow initialized within the container. - IsControlFlowContainer = 1 << 2, - - IsFunctionLike = 1 << 3, - IsFunctionExpression = 1 << 4, - HasLocals = 1 << 5, - IsInterface = 1 << 6, - IsObjectLiteralOrClassExpressionMethodOrAccessor = 1 << 7, - } - - function initFlowNode(node: T) { - Debug.attachFlowNodeDebugInfo(node); - return node; - } - - const binder = createBinder(); - - export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - performance.mark("beforeBind"); - perfLogger.logStartBindFile("" + file.fileName); - binder(file, options); - perfLogger.logStopBindFile(); - performance.mark("afterBind"); - performance.measure("Bind", "beforeBind", "afterBind"); - } - - function createBinder(): (file: SourceFile, options: CompilerOptions) => void { - let file: SourceFile; - let options: CompilerOptions; - let languageVersion: ScriptTarget; - let parent: Node; - let container: Node; - let thisParentContainer: Node; // Container one level up - let blockScopeContainer: Node; - let lastContainer: Node; - let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag)[]; - let seenThisKeyword: boolean; - - // state used by control flow analysis - let currentFlow: FlowNode; - let currentBreakTarget: FlowLabel | undefined; - let currentContinueTarget: FlowLabel | undefined; - let currentReturnTarget: FlowLabel | undefined; - let currentTrueTarget: FlowLabel | undefined; - let currentFalseTarget: FlowLabel | undefined; - let currentExceptionTarget: FlowLabel | undefined; - let preSwitchCaseFlow: FlowNode | undefined; - let activeLabelList: ActiveLabel | undefined; - let hasExplicitReturn: boolean; - - // state used for emit helpers - let emitFlags: NodeFlags; - - // If this file is an external module, then it is automatically in strict-mode according to - // ES6. If it is not an external module, then we'll determine if it is in strict mode or - // not depending on if we see "use strict" in certain places or if we hit a class/namespace - // or if compiler options contain alwaysStrict. - let inStrictMode: boolean; - - // If we are binding an assignment pattern, we will bind certain expressions differently. - let inAssignmentPattern = false; - - let symbolCount = 0; - - let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; - let classifiableNames: Set<__String>; - - const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; - const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; - const bindBinaryExpressionFlow = createBindBinaryExpressionFlow(); - - /** - * Inside the binder, we may create a diagnostic for an as-yet unbound node (with potentially no parent pointers, implying no accessible source file) - * If so, the node _must_ be in the current file (as that's the only way anything could have traversed to it to yield it as the error node) - * This version of `createDiagnosticForNode` uses the binder's context to account for this, and always yields correct diagnostics even in these situations. - */ - function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { - return createDiagnosticForNodeInSourceFile(getSourceFileOfNode(node) || file, node, message, arg0, arg1, arg2); - } - - function bindSourceFile(f: SourceFile, opts: CompilerOptions) { - file = f; - options = opts; - languageVersion = getEmitScriptTarget(options); - inStrictMode = bindInStrictMode(file, opts); - classifiableNames = new Set(); - symbolCount = 0; - - Symbol = objectAllocator.getSymbolConstructor(); - - // Attach debugging information if necessary - Debug.attachFlowNodeDebugInfo(unreachableFlow); - Debug.attachFlowNodeDebugInfo(reportedUnreachableFlow); - - if (!file.locals) { - tracing?.push(tracing.Phase.Bind, "bindSourceFile", { path: file.path }, /*separateBeginAndEnd*/ true); - bind(file); - tracing?.pop(); - file.symbolCount = symbolCount; - file.classifiableNames = classifiableNames; - delayedBindJSDocTypedefTag(); - } - - file = undefined!; - options = undefined!; - languageVersion = undefined!; - parent = undefined!; - container = undefined!; - thisParentContainer = undefined!; - blockScopeContainer = undefined!; - lastContainer = undefined!; - delayedTypeAliases = undefined!; - seenThisKeyword = false; - currentFlow = undefined!; - currentBreakTarget = undefined; - currentContinueTarget = undefined; - currentReturnTarget = undefined; - currentTrueTarget = undefined; - currentFalseTarget = undefined; - currentExceptionTarget = undefined; - activeLabelList = undefined; - hasExplicitReturn = false; - inAssignmentPattern = false; - emitFlags = NodeFlags.None; - } +const binder = createBinder(); - return bindSourceFile; +/** @internal */ +export function bindSourceFile(file: SourceFile, options: CompilerOptions) { + performance.mark("beforeBind"); + perfLogger.logStartBindFile("" + file.fileName); + binder(file, options); + perfLogger.logStopBindFile(); + performance.mark("afterBind"); + performance.measure("Bind", "beforeBind", "afterBind"); +} - function bindInStrictMode(file: SourceFile, opts: CompilerOptions): boolean { - if (getStrictOptionValue(opts, "alwaysStrict") && !file.isDeclarationFile) { - // bind in strict mode source files with alwaysStrict option - return true; - } - else { - return !!file.externalModuleIndicator; - } - } +function createBinder(): (file: SourceFile, options: CompilerOptions) => void { + let file: SourceFile; + let options: CompilerOptions; + let languageVersion: ScriptTarget; + let parent: Node; + let container: Node; + let thisParentContainer: Node; // Container one level up + let blockScopeContainer: Node; + let lastContainer: Node; + let delayedTypeAliases: (JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag)[]; + let seenThisKeyword: boolean; + + // state used by control flow analysis + let currentFlow: FlowNode; + let currentBreakTarget: FlowLabel | undefined; + let currentContinueTarget: FlowLabel | undefined; + let currentReturnTarget: FlowLabel | undefined; + let currentTrueTarget: FlowLabel | undefined; + let currentFalseTarget: FlowLabel | undefined; + let currentExceptionTarget: FlowLabel | undefined; + let preSwitchCaseFlow: FlowNode | undefined; + let activeLabelList: ActiveLabel | undefined; + let hasExplicitReturn: boolean; + + // state used for emit helpers + let emitFlags: NodeFlags; + + // If this file is an external module, then it is automatically in strict-mode according to + // ES6. If it is not an external module, then we'll determine if it is in strict mode or + // not depending on if we see "use strict" in certain places or if we hit a class/namespace + // or if compiler options contain alwaysStrict. + let inStrictMode: boolean; + + // If we are binding an assignment pattern, we will bind certain expressions differently. + let inAssignmentPattern = false; + + let symbolCount = 0; + + let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; + let classifiableNames: Set<__String>; + + const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; + const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; + const bindBinaryExpressionFlow = createBindBinaryExpressionFlow(); + + /** + * Inside the binder, we may create a diagnostic for an as-yet unbound node (with potentially no parent pointers, implying no accessible source file) + * If so, the node _must_ be in the current file (as that's the only way anything could have traversed to it to yield it as the error node) + * This version of `createDiagnosticForNode` uses the binder's context to account for this, and always yields correct diagnostics even in these situations. + */ + function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { + return createDiagnosticForNodeInSourceFile(getSourceFileOfNode(node) || file, node, message, arg0, arg1, arg2); + } + + function bindSourceFile(f: SourceFile, opts: CompilerOptions) { + file = f; + options = opts; + languageVersion = getEmitScriptTarget(options); + inStrictMode = bindInStrictMode(file, opts); + classifiableNames = new Set(); + symbolCount = 0; + + Symbol = objectAllocator.getSymbolConstructor(); + + // Attach debugging information if necessary + Debug.attachFlowNodeDebugInfo(unreachableFlow); + Debug.attachFlowNodeDebugInfo(reportedUnreachableFlow); + + if (!file.locals) { + tracing?.push(tracing.Phase.Bind, "bindSourceFile", { path: file.path }, /*separateBeginAndEnd*/ true); + bind(file); + tracing?.pop(); + file.symbolCount = symbolCount; + file.classifiableNames = classifiableNames; + delayedBindJSDocTypedefTag(); + } + + file = undefined!; + options = undefined!; + languageVersion = undefined!; + parent = undefined!; + container = undefined!; + thisParentContainer = undefined!; + blockScopeContainer = undefined!; + lastContainer = undefined!; + delayedTypeAliases = undefined!; + seenThisKeyword = false; + currentFlow = undefined!; + currentBreakTarget = undefined; + currentContinueTarget = undefined; + currentReturnTarget = undefined; + currentTrueTarget = undefined; + currentFalseTarget = undefined; + currentExceptionTarget = undefined; + activeLabelList = undefined; + hasExplicitReturn = false; + inAssignmentPattern = false; + emitFlags = NodeFlags.None; + } + + return bindSourceFile; - function createSymbol(flags: SymbolFlags, name: __String): Symbol { - symbolCount++; - return new Symbol(flags, name); + function bindInStrictMode(file: SourceFile, opts: CompilerOptions): boolean { + if (getStrictOptionValue(opts, "alwaysStrict") && !file.isDeclarationFile) { + // bind in strict mode source files with alwaysStrict option + return true; } + else { + return !!file.externalModuleIndicator; + } + } - function addDeclarationToSymbol(symbol: Symbol, node: Declaration, symbolFlags: SymbolFlags) { - symbol.flags |= symbolFlags; + function createSymbol(flags: SymbolFlags, name: __String): Symbol { + symbolCount++; + return new Symbol(flags, name); + } - node.symbol = symbol; - symbol.declarations = appendIfUnique(symbol.declarations, node); + function addDeclarationToSymbol(symbol: Symbol, node: Declaration, symbolFlags: SymbolFlags) { + symbol.flags |= symbolFlags; - if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.Module | SymbolFlags.Variable) && !symbol.exports) { - symbol.exports = createSymbolTable(); - } + node.symbol = symbol; + symbol.declarations = appendIfUnique(symbol.declarations, node); - if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && !symbol.members) { - symbol.members = createSymbolTable(); - } + if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.Module | SymbolFlags.Variable) && !symbol.exports) { + symbol.exports = createSymbolTable(); + } - // On merge of const enum module with class or function, reset const enum only flag (namespaces will already recalculate) - if (symbol.constEnumOnlyModule && (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) { - symbol.constEnumOnlyModule = false; - } + if (symbolFlags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && !symbol.members) { + symbol.members = createSymbolTable(); + } - if (symbolFlags & SymbolFlags.Value) { - setValueDeclaration(symbol, node); - } + // On merge of const enum module with class or function, reset const enum only flag (namespaces will already recalculate) + if (symbol.constEnumOnlyModule && (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) { + symbol.constEnumOnlyModule = false; } - // Should not be called on a declaration with a computed property name, - // unless it is a well known Symbol. - function getDeclarationName(node: Declaration): __String | undefined { - if (node.kind === SyntaxKind.ExportAssignment) { - return (node as ExportAssignment).isExportEquals ? InternalSymbolName.ExportEquals : InternalSymbolName.Default; - } + if (symbolFlags & SymbolFlags.Value) { + setValueDeclaration(symbol, node); + } + } + + // Should not be called on a declaration with a computed property name, + // unless it is a well known Symbol. + function getDeclarationName(node: Declaration): __String | undefined { + if (node.kind === SyntaxKind.ExportAssignment) { + return (node as ExportAssignment).isExportEquals ? InternalSymbolName.ExportEquals : InternalSymbolName.Default; + } - const name = getNameOfDeclaration(node); - if (name) { - if (isAmbientModule(node)) { - const moduleName = getTextOfIdentifierOrLiteral(name as Identifier | StringLiteral); - return (isGlobalScopeAugmentation(node as ModuleDeclaration) ? "__global" : `"${moduleName}"`) as __String; + const name = getNameOfDeclaration(node); + if (name) { + if (isAmbientModule(node)) { + const moduleName = getTextOfIdentifierOrLiteral(name as Identifier | StringLiteral); + return (isGlobalScopeAugmentation(node as ModuleDeclaration) ? "__global" : `"${moduleName}"`) as __String; + } + if (name.kind === SyntaxKind.ComputedPropertyName) { + const nameExpression = name.expression; + // treat computed property names where expression is string/numeric literal as just string/numeric literal + if (isStringOrNumericLiteralLike(nameExpression)) { + return escapeLeadingUnderscores(nameExpression.text); } - if (name.kind === SyntaxKind.ComputedPropertyName) { - const nameExpression = name.expression; - // treat computed property names where expression is string/numeric literal as just string/numeric literal - if (isStringOrNumericLiteralLike(nameExpression)) { - return escapeLeadingUnderscores(nameExpression.text); - } - if (isSignedNumericLiteral(nameExpression)) { - return tokenToString(nameExpression.operator) + nameExpression.operand.text as __String; - } - else { - Debug.fail("Only computed properties with literal names have declaration names"); - } + if (isSignedNumericLiteral(nameExpression)) { + return tokenToString(nameExpression.operator) + nameExpression.operand.text as __String; } - if (isPrivateIdentifier(name)) { - // containingClass exists because private names only allowed inside classes - const containingClass = getContainingClass(node); - if (!containingClass) { - // we can get here in cases where there is already a parse error. - return undefined; - } - const containingClassSymbol = containingClass.symbol; - return getSymbolNameForPrivateIdentifier(containingClassSymbol, name.escapedText); - } - return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; - } - switch (node.kind) { - case SyntaxKind.Constructor: - return InternalSymbolName.Constructor; - case SyntaxKind.FunctionType: - case SyntaxKind.CallSignature: - case SyntaxKind.JSDocSignature: - return InternalSymbolName.Call; - case SyntaxKind.ConstructorType: - case SyntaxKind.ConstructSignature: - return InternalSymbolName.New; - case SyntaxKind.IndexSignature: - return InternalSymbolName.Index; - case SyntaxKind.ExportDeclaration: - return InternalSymbolName.ExportStar; - case SyntaxKind.SourceFile: - // json file should behave as + else { + Debug.fail("Only computed properties with literal names have declaration names"); + } + } + if (isPrivateIdentifier(name)) { + // containingClass exists because private names only allowed inside classes + const containingClass = getContainingClass(node); + if (!containingClass) { + // we can get here in cases where there is already a parse error. + return undefined; + } + const containingClassSymbol = containingClass.symbol; + return getSymbolNameForPrivateIdentifier(containingClassSymbol, name.escapedText); + } + return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; + } + switch (node.kind) { + case SyntaxKind.Constructor: + return InternalSymbolName.Constructor; + case SyntaxKind.FunctionType: + case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: + return InternalSymbolName.Call; + case SyntaxKind.ConstructorType: + case SyntaxKind.ConstructSignature: + return InternalSymbolName.New; + case SyntaxKind.IndexSignature: + return InternalSymbolName.Index; + case SyntaxKind.ExportDeclaration: + return InternalSymbolName.ExportStar; + case SyntaxKind.SourceFile: + // json file should behave as + // module.exports = ... + return InternalSymbolName.ExportEquals; + case SyntaxKind.BinaryExpression: + if (getAssignmentDeclarationKind(node as BinaryExpression) === AssignmentDeclarationKind.ModuleExports) { // module.exports = ... return InternalSymbolName.ExportEquals; - case SyntaxKind.BinaryExpression: - if (getAssignmentDeclarationKind(node as BinaryExpression) === AssignmentDeclarationKind.ModuleExports) { - // module.exports = ... - return InternalSymbolName.ExportEquals; - } - Debug.fail("Unknown binary declaration kind"); - break; - case SyntaxKind.JSDocFunctionType: - return (isJSDocConstructSignature(node) ? InternalSymbolName.New : InternalSymbolName.Call); - case SyntaxKind.Parameter: - // Parameters with names are handled at the top of this function. Parameters - // without names can only come from JSDocFunctionTypes. - Debug.assert(node.parent.kind === SyntaxKind.JSDocFunctionType, "Impossible parameter parent kind", () => `parent is: ${Debug.formatSyntaxKind(node.parent.kind)}, expected JSDocFunctionType`); - const functionType = node.parent as JSDocFunctionType; - const index = functionType.parameters.indexOf(node as ParameterDeclaration); - return "arg" + index as __String; - } + } + Debug.fail("Unknown binary declaration kind"); + break; + case SyntaxKind.JSDocFunctionType: + return (isJSDocConstructSignature(node) ? InternalSymbolName.New : InternalSymbolName.Call); + case SyntaxKind.Parameter: + // Parameters with names are handled at the top of this function. Parameters + // without names can only come from JSDocFunctionTypes. + Debug.assert(node.parent.kind === SyntaxKind.JSDocFunctionType, "Impossible parameter parent kind", () => `parent is: ${Debug.formatSyntaxKind(node.parent.kind)}, expected JSDocFunctionType`); + const functionType = node.parent as JSDocFunctionType; + const index = functionType.parameters.indexOf(node as ParameterDeclaration); + return "arg" + index as __String; } + } - function getDisplayName(node: Declaration): string { - return isNamedDeclaration(node) ? declarationNameToString(node.name) : unescapeLeadingUnderscores(Debug.checkDefined(getDeclarationName(node))); - } + function getDisplayName(node: Declaration): string { + return isNamedDeclaration(node) ? declarationNameToString(node.name) : unescapeLeadingUnderscores(Debug.checkDefined(getDeclarationName(node))); + } - /** - * Declares a Symbol for the node and adds it to symbols. Reports errors for conflicting identifier names. - * @param symbolTable - The symbol table which node will be added to. - * @param parent - node's parent declaration. - * @param node - The declaration to be added to the symbol table - * @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.) - * @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations. - */ - function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean, isComputedName?: boolean): Symbol { - Debug.assert(isComputedName || !hasDynamicName(node)); + /** + * Declares a Symbol for the node and adds it to symbols. Reports errors for conflicting identifier names. + * @param symbolTable - The symbol table which node will be added to. + * @param parent - node's parent declaration. + * @param node - The declaration to be added to the symbol table + * @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.) + * @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations. + */ + function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean, isComputedName?: boolean): Symbol { + Debug.assert(isComputedName || !hasDynamicName(node)); - const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && node.name.escapedText === "default"; + const isDefaultExport = hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node) && node.name.escapedText === "default"; - // The exported symbol for an export default function/class node is always named "default" - const name = isComputedName ? InternalSymbolName.Computed - : isDefaultExport && parent ? InternalSymbolName.Default - : getDeclarationName(node); + // The exported symbol for an export default function/class node is always named "default" + const name = isComputedName ? InternalSymbolName.Computed + : isDefaultExport && parent ? InternalSymbolName.Default + : getDeclarationName(node); - let symbol: Symbol | undefined; - if (name === undefined) { - symbol = createSymbol(SymbolFlags.None, InternalSymbolName.Missing); - } - else { - // Check and see if the symbol table already has a symbol with this name. If not, - // create a new symbol with this name and add it to the table. Note that we don't - // give the new symbol any flags *yet*. This ensures that it will not conflict - // with the 'excludes' flags we pass in. - // - // If we do get an existing symbol, see if it conflicts with the new symbol we're - // creating. For example, a 'var' symbol and a 'class' symbol will conflict within - // the same symbol table. If we have a conflict, report the issue on each - // declaration we have for this symbol, and then create a new symbol for this - // declaration. - // - // Note that when properties declared in Javascript constructors - // (marked by isReplaceableByMethod) conflict with another symbol, the property loses. - // Always. This allows the common Javascript pattern of overwriting a prototype method - // with an bound instance method of the same type: `this.method = this.method.bind(this)` - // - // If we created a new symbol, either because we didn't have a symbol with this name - // in the symbol table, or we conflicted with an existing symbol, then just add this - // node as the sole declaration of the new symbol. - // - // Otherwise, we'll be merging into a compatible existing symbol (for example when - // you have multiple 'vars' with the same name in the same container). In this case - // just add this node into the declarations list of the symbol. - symbol = symbolTable.get(name); + let symbol: Symbol | undefined; + if (name === undefined) { + symbol = createSymbol(SymbolFlags.None, InternalSymbolName.Missing); + } + else { + // Check and see if the symbol table already has a symbol with this name. If not, + // create a new symbol with this name and add it to the table. Note that we don't + // give the new symbol any flags *yet*. This ensures that it will not conflict + // with the 'excludes' flags we pass in. + // + // If we do get an existing symbol, see if it conflicts with the new symbol we're + // creating. For example, a 'var' symbol and a 'class' symbol will conflict within + // the same symbol table. If we have a conflict, report the issue on each + // declaration we have for this symbol, and then create a new symbol for this + // declaration. + // + // Note that when properties declared in Javascript constructors + // (marked by isReplaceableByMethod) conflict with another symbol, the property loses. + // Always. This allows the common Javascript pattern of overwriting a prototype method + // with an bound instance method of the same type: `this.method = this.method.bind(this)` + // + // If we created a new symbol, either because we didn't have a symbol with this name + // in the symbol table, or we conflicted with an existing symbol, then just add this + // node as the sole declaration of the new symbol. + // + // Otherwise, we'll be merging into a compatible existing symbol (for example when + // you have multiple 'vars' with the same name in the same container). In this case + // just add this node into the declarations list of the symbol. + symbol = symbolTable.get(name); - if (includes & SymbolFlags.Classifiable) { - classifiableNames.add(name); - } + if (includes & SymbolFlags.Classifiable) { + classifiableNames.add(name); + } - if (!symbol) { + if (!symbol) { + symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name)); + if (isReplaceableByMethod) symbol.isReplaceableByMethod = true; + } + else if (isReplaceableByMethod && !symbol.isReplaceableByMethod) { + // A symbol already exists, so don't add this as a declaration. + return symbol; + } + else if (symbol.flags & excludes) { + if (symbol.isReplaceableByMethod) { + // Javascript constructor-declared symbols can be discarded in favor of + // prototype symbols like methods. symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name)); - if (isReplaceableByMethod) symbol.isReplaceableByMethod = true; } - else if (isReplaceableByMethod && !symbol.isReplaceableByMethod) { - // A symbol already exists, so don't add this as a declaration. - return symbol; - } - else if (symbol.flags & excludes) { - if (symbol.isReplaceableByMethod) { - // Javascript constructor-declared symbols can be discarded in favor of - // prototype symbols like methods. - symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name)); - } - else if (!(includes & SymbolFlags.Variable && symbol.flags & SymbolFlags.Assignment)) { - // Assignment declarations are allowed to merge with variables, no matter what other flags they have. - if (isNamedDeclaration(node)) { - setParent(node.name, node); - } - // Report errors every position with duplicate declaration - // Report errors on previous encountered declarations - let message = symbol.flags & SymbolFlags.BlockScopedVariable - ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 - : Diagnostics.Duplicate_identifier_0; - let messageNeedsName = true; - - if (symbol.flags & SymbolFlags.Enum || includes & SymbolFlags.Enum) { - message = Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations; + else if (!(includes & SymbolFlags.Variable && symbol.flags & SymbolFlags.Assignment)) { + // Assignment declarations are allowed to merge with variables, no matter what other flags they have. + if (isNamedDeclaration(node)) { + setParent(node.name, node); + } + // Report errors every position with duplicate declaration + // Report errors on previous encountered declarations + let message = symbol.flags & SymbolFlags.BlockScopedVariable + ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 + : Diagnostics.Duplicate_identifier_0; + let messageNeedsName = true; + + if (symbol.flags & SymbolFlags.Enum || includes & SymbolFlags.Enum) { + message = Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations; + messageNeedsName = false; + } + + let multipleDefaultExports = false; + if (length(symbol.declarations)) { + // If the current node is a default export of some sort, then check if + // there are any other default exports that we need to error on. + // We'll know whether we have other default exports depending on if `symbol` already has a declaration list set. + if (isDefaultExport) { + message = Diagnostics.A_module_cannot_have_multiple_default_exports; messageNeedsName = false; + multipleDefaultExports = true; } - - let multipleDefaultExports = false; - if (length(symbol.declarations)) { - // If the current node is a default export of some sort, then check if - // there are any other default exports that we need to error on. - // We'll know whether we have other default exports depending on if `symbol` already has a declaration list set. - if (isDefaultExport) { + else { + // This is to properly report an error in the case "export default { }" is after export default of class declaration or function declaration. + // Error on multiple export default in the following case: + // 1. multiple export default of class declaration or function declaration by checking NodeFlags.Default + // 2. multiple export default of export assignment. This one doesn't have NodeFlags.Default on (as export default doesn't considered as modifiers) + if (symbol.declarations && symbol.declarations.length && + (node.kind === SyntaxKind.ExportAssignment && !(node as ExportAssignment).isExportEquals)) { message = Diagnostics.A_module_cannot_have_multiple_default_exports; messageNeedsName = false; multipleDefaultExports = true; } - else { - // This is to properly report an error in the case "export default { }" is after export default of class declaration or function declaration. - // Error on multiple export default in the following case: - // 1. multiple export default of class declaration or function declaration by checking NodeFlags.Default - // 2. multiple export default of export assignment. This one doesn't have NodeFlags.Default on (as export default doesn't considered as modifiers) - if (symbol.declarations && symbol.declarations.length && - (node.kind === SyntaxKind.ExportAssignment && !(node as ExportAssignment).isExportEquals)) { - message = Diagnostics.A_module_cannot_have_multiple_default_exports; - messageNeedsName = false; - multipleDefaultExports = true; - } - } } + } - const relatedInformation: DiagnosticRelatedInformation[] = []; - if (isTypeAliasDeclaration(node) && nodeIsMissing(node.type) && hasSyntacticModifier(node, ModifierFlags.Export) && symbol.flags & (SymbolFlags.Alias | SymbolFlags.Type | SymbolFlags.Namespace)) { - // export type T; - may have meant export type { T }? - relatedInformation.push(createDiagnosticForNode(node, Diagnostics.Did_you_mean_0, `export type { ${unescapeLeadingUnderscores(node.name.escapedText)} }`)); - } + const relatedInformation: DiagnosticRelatedInformation[] = []; + if (isTypeAliasDeclaration(node) && nodeIsMissing(node.type) && hasSyntacticModifier(node, ModifierFlags.Export) && symbol.flags & (SymbolFlags.Alias | SymbolFlags.Type | SymbolFlags.Namespace)) { + // export type T; - may have meant export type { T }? + relatedInformation.push(createDiagnosticForNode(node, Diagnostics.Did_you_mean_0, `export type { ${unescapeLeadingUnderscores(node.name.escapedText)} }`)); + } - const declarationName = getNameOfDeclaration(node) || node; - forEach(symbol.declarations, (declaration, index) => { - const decl = getNameOfDeclaration(declaration) || declaration; - const diag = createDiagnosticForNode(decl, message, messageNeedsName ? getDisplayName(declaration) : undefined); - file.bindDiagnostics.push( - multipleDefaultExports ? addRelatedInfo(diag, createDiagnosticForNode(declarationName, index === 0 ? Diagnostics.Another_export_default_is_here : Diagnostics.and_here)) : diag - ); - if (multipleDefaultExports) { - relatedInformation.push(createDiagnosticForNode(decl, Diagnostics.The_first_export_default_is_here)); - } - }); + const declarationName = getNameOfDeclaration(node) || node; + forEach(symbol.declarations, (declaration, index) => { + const decl = getNameOfDeclaration(declaration) || declaration; + const diag = createDiagnosticForNode(decl, message, messageNeedsName ? getDisplayName(declaration) : undefined); + file.bindDiagnostics.push( + multipleDefaultExports ? addRelatedInfo(diag, createDiagnosticForNode(declarationName, index === 0 ? Diagnostics.Another_export_default_is_here : Diagnostics.and_here)) : diag + ); + if (multipleDefaultExports) { + relatedInformation.push(createDiagnosticForNode(decl, Diagnostics.The_first_export_default_is_here)); + } + }); - const diag = createDiagnosticForNode(declarationName, message, messageNeedsName ? getDisplayName(node) : undefined); - file.bindDiagnostics.push(addRelatedInfo(diag, ...relatedInformation)); + const diag = createDiagnosticForNode(declarationName, message, messageNeedsName ? getDisplayName(node) : undefined); + file.bindDiagnostics.push(addRelatedInfo(diag, ...relatedInformation)); - symbol = createSymbol(SymbolFlags.None, name); - } + symbol = createSymbol(SymbolFlags.None, name); } } + } + + addDeclarationToSymbol(symbol, node, includes); + if (symbol.parent) { + Debug.assert(symbol.parent === parent, "Existing symbol parent should match new one"); + } + else { + symbol.parent = parent; + } - addDeclarationToSymbol(symbol, node, includes); - if (symbol.parent) { - Debug.assert(symbol.parent === parent, "Existing symbol parent should match new one"); + return symbol; + } + + function declareModuleMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol { + const hasExportModifier = !!(getCombinedModifierFlags(node) & ModifierFlags.Export) || jsdocTreatAsExported(node); + if (symbolFlags & SymbolFlags.Alias) { + if (node.kind === SyntaxKind.ExportSpecifier || (node.kind === SyntaxKind.ImportEqualsDeclaration && hasExportModifier)) { + return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); } else { - symbol.parent = parent; + return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } - - return symbol; } - - function declareModuleMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol { - const hasExportModifier = !!(getCombinedModifierFlags(node) & ModifierFlags.Export) || jsdocTreatAsExported(node); - if (symbolFlags & SymbolFlags.Alias) { - if (node.kind === SyntaxKind.ExportSpecifier || (node.kind === SyntaxKind.ImportEqualsDeclaration && hasExportModifier)) { - return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); - } - else { - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } + else { + // Exported module members are given 2 symbols: A local symbol that is classified with an ExportValue flag, + // and an associated export symbol with all the correct flags set on it. There are 2 main reasons: + // + // 1. We treat locals and exports of the same name as mutually exclusive within a container. + // That means the binder will issue a Duplicate Identifier error if you mix locals and exports + // with the same name in the same container. + // TODO: Make this a more specific error and decouple it from the exclusion logic. + // 2. When we checkIdentifier in the checker, we set its resolved symbol to the local symbol, + // but return the export symbol (by calling getExportSymbolOfValueSymbolIfExported). That way + // when the emitter comes back to it, it knows not to qualify the name if it was found in a containing scope. + + // NOTE: Nested ambient modules always should go to to 'locals' table to prevent their automatic merge + // during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation + // and this case is specially handled. Module augmentations should only be merged with original module definition + // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. + if (isJSDocTypeAlias(node)) Debug.assert(isInJSFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file. + if (!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) { + if (!container.locals || (hasSyntacticModifier(node, ModifierFlags.Default) && !getDeclarationName(node))) { + return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default! + } + const exportKind = symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0; + const local = declareSymbol(container.locals, /*parent*/ undefined, node, exportKind, symbolExcludes); + local.exportSymbol = declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); + node.localSymbol = local; + return local; } else { - // Exported module members are given 2 symbols: A local symbol that is classified with an ExportValue flag, - // and an associated export symbol with all the correct flags set on it. There are 2 main reasons: - // - // 1. We treat locals and exports of the same name as mutually exclusive within a container. - // That means the binder will issue a Duplicate Identifier error if you mix locals and exports - // with the same name in the same container. - // TODO: Make this a more specific error and decouple it from the exclusion logic. - // 2. When we checkIdentifier in the checker, we set its resolved symbol to the local symbol, - // but return the export symbol (by calling getExportSymbolOfValueSymbolIfExported). That way - // when the emitter comes back to it, it knows not to qualify the name if it was found in a containing scope. - - // NOTE: Nested ambient modules always should go to to 'locals' table to prevent their automatic merge - // during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation - // and this case is specially handled. Module augmentations should only be merged with original module definition - // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. - if (isJSDocTypeAlias(node)) Debug.assert(isInJSFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file. - if (!isAmbientModule(node) && (hasExportModifier || container.flags & NodeFlags.ExportContext)) { - if (!container.locals || (hasSyntacticModifier(node, ModifierFlags.Default) && !getDeclarationName(node))) { - return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); // No local symbol for an unnamed default! - } - const exportKind = symbolFlags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0; - const local = declareSymbol(container.locals, /*parent*/ undefined, node, exportKind, symbolExcludes); - local.exportSymbol = declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); - node.localSymbol = local; - return local; - } - else { - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } + return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } } + } - function jsdocTreatAsExported(node: Node) { - if (node.parent && isModuleDeclaration(node)) { - node = node.parent; - } - if (!isJSDocTypeAlias(node)) return false; - // jsdoc typedef handling is a bit of a doozy, but to summarize, treat the typedef as exported if: - // 1. It has an explicit name (since by default typedefs are always directly exported, either at the top level or in a container), or - if (!isJSDocEnumTag(node) && !!node.fullName) return true; - // 2. The thing a nameless typedef pulls its name from is implicitly a direct export (either by assignment or actual export flag). - const declName = getNameOfDeclaration(node); - if (!declName) return false; - if (isPropertyAccessEntityNameExpression(declName.parent) && isTopLevelNamespaceAssignment(declName.parent)) return true; - if (isDeclaration(declName.parent) && getCombinedModifierFlags(declName.parent) & ModifierFlags.Export) return true; - // This could potentially be simplified by having `delayedBindJSDocTypedefTag` pass in an override for `hasExportModifier`, since it should - // already have calculated and branched on most of this. - return false; - } + function jsdocTreatAsExported(node: Node) { + if (node.parent && isModuleDeclaration(node)) { + node = node.parent; + } + if (!isJSDocTypeAlias(node)) return false; + // jsdoc typedef handling is a bit of a doozy, but to summarize, treat the typedef as exported if: + // 1. It has an explicit name (since by default typedefs are always directly exported, either at the top level or in a container), or + if (!isJSDocEnumTag(node) && !!node.fullName) return true; + // 2. The thing a nameless typedef pulls its name from is implicitly a direct export (either by assignment or actual export flag). + const declName = getNameOfDeclaration(node); + if (!declName) return false; + if (isPropertyAccessEntityNameExpression(declName.parent) && isTopLevelNamespaceAssignment(declName.parent)) return true; + if (isDeclaration(declName.parent) && getCombinedModifierFlags(declName.parent) & ModifierFlags.Export) return true; + // This could potentially be simplified by having `delayedBindJSDocTypedefTag` pass in an override for `hasExportModifier`, since it should + // already have calculated and branched on most of this. + return false; + } - // All container nodes are kept on a linked list in declaration order. This list is used by - // the getLocalNameOfContainer function in the type checker to validate that the local name - // used for a container is unique. - function bindContainer(node: Mutable, containerFlags: ContainerFlags) { - // Before we recurse into a node's children, we first save the existing parent, container - // and block-container. Then after we pop out of processing the children, we restore - // these saved values. - const saveContainer = container; - const saveThisParentContainer = thisParentContainer; - const savedBlockScopeContainer = blockScopeContainer; - - // Depending on what kind of node this is, we may have to adjust the current container - // and block-container. If the current node is a container, then it is automatically - // considered the current block-container as well. Also, for containers that we know - // may contain locals, we eagerly initialize the .locals field. We do this because - // it's highly likely that the .locals will be needed to place some child in (for example, - // a parameter, or variable declaration). - // - // However, we do not proactively create the .locals for block-containers because it's - // totally normal and common for block-containers to never actually have a block-scoped - // variable in them. We don't want to end up allocating an object for every 'block' we - // run into when most of them won't be necessary. - // - // Finally, if this is a block-container, then we clear out any existing .locals object - // it may contain within it. This happens in incremental scenarios. Because we can be - // reusing a node from a previous compilation, that node may have had 'locals' created - // for it. We must clear this so we don't accidentally move any stale data forward from - // a previous compilation. - if (containerFlags & ContainerFlags.IsContainer) { - if (node.kind !== SyntaxKind.ArrowFunction) { - thisParentContainer = container; - } - container = blockScopeContainer = node; - if (containerFlags & ContainerFlags.HasLocals) { - container.locals = createSymbolTable(); - } - addToContainerChain(container); - } - else if (containerFlags & ContainerFlags.IsBlockScopedContainer) { - blockScopeContainer = node; - blockScopeContainer.locals = undefined; - } - if (containerFlags & ContainerFlags.IsControlFlowContainer) { - const saveCurrentFlow = currentFlow; - const saveBreakTarget = currentBreakTarget; - const saveContinueTarget = currentContinueTarget; - const saveReturnTarget = currentReturnTarget; - const saveExceptionTarget = currentExceptionTarget; - const saveActiveLabelList = activeLabelList; - const saveHasExplicitReturn = hasExplicitReturn; - const isImmediatelyInvoked = - (containerFlags & ContainerFlags.IsFunctionExpression && - !hasSyntacticModifier(node, ModifierFlags.Async) && - !(node as FunctionLikeDeclaration).asteriskToken && - !!getImmediatelyInvokedFunctionExpression(node)) || - node.kind === SyntaxKind.ClassStaticBlockDeclaration; - // A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave - // similarly to break statements that exit to a label just past the statement body. - if (!isImmediatelyInvoked) { - currentFlow = initFlowNode({ flags: FlowFlags.Start }); - if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) { - currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; - } - } - // We create a return control flow graph for IIFEs and constructors. For constructors - // we use the return control flow graph in strict property initialization checks. - currentReturnTarget = isImmediatelyInvoked || node.kind === SyntaxKind.Constructor || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined; - currentExceptionTarget = undefined; - currentBreakTarget = undefined; - currentContinueTarget = undefined; - activeLabelList = undefined; - hasExplicitReturn = false; - bindChildren(node); - // Reset all reachability check related flags on node (for incremental scenarios) - node.flags &= ~NodeFlags.ReachabilityAndEmitFlags; - if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).body)) { - node.flags |= NodeFlags.HasImplicitReturn; - if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn; - (node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).endFlowNode = currentFlow; - } - if (node.kind === SyntaxKind.SourceFile) { - node.flags |= emitFlags; - (node as SourceFile).endFlowNode = currentFlow; - } - - if (currentReturnTarget) { - addAntecedent(currentReturnTarget, currentFlow); - currentFlow = finishFlowLabel(currentReturnTarget); - if (node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ClassStaticBlockDeclaration || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) { - (node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).returnFlowNode = currentFlow; - } - } - if (!isImmediatelyInvoked) { - currentFlow = saveCurrentFlow; + // All container nodes are kept on a linked list in declaration order. This list is used by + // the getLocalNameOfContainer function in the type checker to validate that the local name + // used for a container is unique. + function bindContainer(node: Mutable, containerFlags: ContainerFlags) { + // Before we recurse into a node's children, we first save the existing parent, container + // and block-container. Then after we pop out of processing the children, we restore + // these saved values. + const saveContainer = container; + const saveThisParentContainer = thisParentContainer; + const savedBlockScopeContainer = blockScopeContainer; + + // Depending on what kind of node this is, we may have to adjust the current container + // and block-container. If the current node is a container, then it is automatically + // considered the current block-container as well. Also, for containers that we know + // may contain locals, we eagerly initialize the .locals field. We do this because + // it's highly likely that the .locals will be needed to place some child in (for example, + // a parameter, or variable declaration). + // + // However, we do not proactively create the .locals for block-containers because it's + // totally normal and common for block-containers to never actually have a block-scoped + // variable in them. We don't want to end up allocating an object for every 'block' we + // run into when most of them won't be necessary. + // + // Finally, if this is a block-container, then we clear out any existing .locals object + // it may contain within it. This happens in incremental scenarios. Because we can be + // reusing a node from a previous compilation, that node may have had 'locals' created + // for it. We must clear this so we don't accidentally move any stale data forward from + // a previous compilation. + if (containerFlags & ContainerFlags.IsContainer) { + if (node.kind !== SyntaxKind.ArrowFunction) { + thisParentContainer = container; + } + container = blockScopeContainer = node; + if (containerFlags & ContainerFlags.HasLocals) { + container.locals = createSymbolTable(); + } + addToContainerChain(container); + } + else if (containerFlags & ContainerFlags.IsBlockScopedContainer) { + blockScopeContainer = node; + blockScopeContainer.locals = undefined; + } + if (containerFlags & ContainerFlags.IsControlFlowContainer) { + const saveCurrentFlow = currentFlow; + const saveBreakTarget = currentBreakTarget; + const saveContinueTarget = currentContinueTarget; + const saveReturnTarget = currentReturnTarget; + const saveExceptionTarget = currentExceptionTarget; + const saveActiveLabelList = activeLabelList; + const saveHasExplicitReturn = hasExplicitReturn; + const isImmediatelyInvoked = + (containerFlags & ContainerFlags.IsFunctionExpression && + !hasSyntacticModifier(node, ModifierFlags.Async) && + !(node as FunctionLikeDeclaration).asteriskToken && + !!getImmediatelyInvokedFunctionExpression(node)) || + node.kind === SyntaxKind.ClassStaticBlockDeclaration; + // A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave + // similarly to break statements that exit to a label just past the statement body. + if (!isImmediatelyInvoked) { + currentFlow = initFlowNode({ flags: FlowFlags.Start }); + if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) { + currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; } - currentBreakTarget = saveBreakTarget; - currentContinueTarget = saveContinueTarget; - currentReturnTarget = saveReturnTarget; - currentExceptionTarget = saveExceptionTarget; - activeLabelList = saveActiveLabelList; - hasExplicitReturn = saveHasExplicitReturn; } - else if (containerFlags & ContainerFlags.IsInterface) { - seenThisKeyword = false; - bindChildren(node); - node.flags = seenThisKeyword ? node.flags | NodeFlags.ContainsThis : node.flags & ~NodeFlags.ContainsThis; + // We create a return control flow graph for IIFEs and constructors. For constructors + // we use the return control flow graph in strict property initialization checks. + currentReturnTarget = isImmediatelyInvoked || node.kind === SyntaxKind.Constructor || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined; + currentExceptionTarget = undefined; + currentBreakTarget = undefined; + currentContinueTarget = undefined; + activeLabelList = undefined; + hasExplicitReturn = false; + bindChildren(node); + // Reset all reachability check related flags on node (for incremental scenarios) + node.flags &= ~NodeFlags.ReachabilityAndEmitFlags; + if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).body)) { + node.flags |= NodeFlags.HasImplicitReturn; + if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn; + (node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).endFlowNode = currentFlow; } - else { - bindChildren(node); + if (node.kind === SyntaxKind.SourceFile) { + node.flags |= emitFlags; + (node as SourceFile).endFlowNode = currentFlow; } - container = saveContainer; - thisParentContainer = saveThisParentContainer; - blockScopeContainer = savedBlockScopeContainer; + if (currentReturnTarget) { + addAntecedent(currentReturnTarget, currentFlow); + currentFlow = finishFlowLabel(currentReturnTarget); + if (node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ClassStaticBlockDeclaration || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) { + (node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).returnFlowNode = currentFlow; + } + } + if (!isImmediatelyInvoked) { + currentFlow = saveCurrentFlow; + } + currentBreakTarget = saveBreakTarget; + currentContinueTarget = saveContinueTarget; + currentReturnTarget = saveReturnTarget; + currentExceptionTarget = saveExceptionTarget; + activeLabelList = saveActiveLabelList; + hasExplicitReturn = saveHasExplicitReturn; } - - function bindEachFunctionsFirst(nodes: NodeArray | undefined): void { - bindEach(nodes, n => n.kind === SyntaxKind.FunctionDeclaration ? bind(n) : undefined); - bindEach(nodes, n => n.kind !== SyntaxKind.FunctionDeclaration ? bind(n) : undefined); + else if (containerFlags & ContainerFlags.IsInterface) { + seenThisKeyword = false; + bindChildren(node); + node.flags = seenThisKeyword ? node.flags | NodeFlags.ContainsThis : node.flags & ~NodeFlags.ContainsThis; + } + else { + bindChildren(node); } - function bindEach(nodes: NodeArray | undefined, bindFunction: (node: Node) => void = bind): void { - if (nodes === undefined) { - return; - } + container = saveContainer; + thisParentContainer = saveThisParentContainer; + blockScopeContainer = savedBlockScopeContainer; + } - forEach(nodes, bindFunction); - } + function bindEachFunctionsFirst(nodes: NodeArray | undefined): void { + bindEach(nodes, n => n.kind === SyntaxKind.FunctionDeclaration ? bind(n) : undefined); + bindEach(nodes, n => n.kind !== SyntaxKind.FunctionDeclaration ? bind(n) : undefined); + } - function bindEachChild(node: Node) { - forEachChild(node, bind, bindEach); + function bindEach(nodes: NodeArray | undefined, bindFunction: (node: Node) => void = bind): void { + if (nodes === undefined) { + return; } - function bindChildren(node: Node): void { - const saveInAssignmentPattern = inAssignmentPattern; - // Most nodes aren't valid in an assignment pattern, so we clear the value here - // and set it before we descend into nodes that could actually be part of an assignment pattern. - inAssignmentPattern = false; - if (checkUnreachable(node)) { - bindEachChild(node); - bindJSDoc(node); - inAssignmentPattern = saveInAssignmentPattern; - return; - } - if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) { - node.flowNode = currentFlow; - } - switch (node.kind) { - case SyntaxKind.WhileStatement: - bindWhileStatement(node as WhileStatement); - break; - case SyntaxKind.DoStatement: - bindDoStatement(node as DoStatement); - break; - case SyntaxKind.ForStatement: - bindForStatement(node as ForStatement); - break; - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - bindForInOrForOfStatement(node as ForInOrOfStatement); - break; - case SyntaxKind.IfStatement: - bindIfStatement(node as IfStatement); - break; - case SyntaxKind.ReturnStatement: - case SyntaxKind.ThrowStatement: - bindReturnOrThrow(node as ReturnStatement | ThrowStatement); - break; - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - bindBreakOrContinueStatement(node as BreakOrContinueStatement); - break; - case SyntaxKind.TryStatement: - bindTryStatement(node as TryStatement); - break; - case SyntaxKind.SwitchStatement: - bindSwitchStatement(node as SwitchStatement); - break; - case SyntaxKind.CaseBlock: - bindCaseBlock(node as CaseBlock); - break; - case SyntaxKind.CaseClause: - bindCaseClause(node as CaseClause); - break; - case SyntaxKind.ExpressionStatement: - bindExpressionStatement(node as ExpressionStatement); - break; - case SyntaxKind.LabeledStatement: - bindLabeledStatement(node as LabeledStatement); - break; - case SyntaxKind.PrefixUnaryExpression: - bindPrefixUnaryExpressionFlow(node as PrefixUnaryExpression); - break; - case SyntaxKind.PostfixUnaryExpression: - bindPostfixUnaryExpressionFlow(node as PostfixUnaryExpression); - break; - case SyntaxKind.BinaryExpression: - if (isDestructuringAssignment(node)) { - // Carry over whether we are in an assignment pattern to - // binary expressions that could actually be an initializer - inAssignmentPattern = saveInAssignmentPattern; - bindDestructuringAssignmentFlow(node); - return; - } - bindBinaryExpressionFlow(node as BinaryExpression); - break; - case SyntaxKind.DeleteExpression: - bindDeleteExpressionFlow(node as DeleteExpression); - break; - case SyntaxKind.ConditionalExpression: - bindConditionalExpressionFlow(node as ConditionalExpression); - break; - case SyntaxKind.VariableDeclaration: - bindVariableDeclarationFlow(node as VariableDeclaration); - break; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - bindAccessExpressionFlow(node as AccessExpression); - break; - case SyntaxKind.CallExpression: - bindCallExpressionFlow(node as CallExpression); - break; - case SyntaxKind.NonNullExpression: - bindNonNullExpressionFlow(node as NonNullExpression); - break; - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag); - break; - // In source files and blocks, bind functions first to match hoisting that occurs at runtime - case SyntaxKind.SourceFile: { - bindEachFunctionsFirst((node as SourceFile).statements); - bind((node as SourceFile).endOfFileToken); - break; - } - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - bindEachFunctionsFirst((node as Block).statements); - break; - case SyntaxKind.BindingElement: - bindBindingElementFlow(node as BindingElement); - break; - case SyntaxKind.Parameter: - bindParameterFlow(node as ParameterDeclaration); - break; - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.SpreadElement: - // Carry over whether we are in an assignment pattern of Object and Array literals - // as well as their children that are valid assignment targets. - inAssignmentPattern = saveInAssignmentPattern; - // falls through - default: - bindEachChild(node); - break; - } + forEach(nodes, bindFunction); + } + + function bindEachChild(node: Node) { + forEachChild(node, bind, bindEach); + } + + function bindChildren(node: Node): void { + const saveInAssignmentPattern = inAssignmentPattern; + // Most nodes aren't valid in an assignment pattern, so we clear the value here + // and set it before we descend into nodes that could actually be part of an assignment pattern. + inAssignmentPattern = false; + if (checkUnreachable(node)) { + bindEachChild(node); bindJSDoc(node); inAssignmentPattern = saveInAssignmentPattern; + return; } - - function isNarrowingExpression(expr: Expression): boolean { - switch (expr.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PrivateIdentifier: - case SyntaxKind.ThisKeyword: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return containsNarrowableReference(expr); - case SyntaxKind.CallExpression: - return hasNarrowableArgument(expr as CallExpression); - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.NonNullExpression: - return isNarrowingExpression((expr as ParenthesizedExpression | NonNullExpression).expression); - case SyntaxKind.BinaryExpression: - return isNarrowingBinaryExpression(expr as BinaryExpression); - case SyntaxKind.PrefixUnaryExpression: - return (expr as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken && isNarrowingExpression((expr as PrefixUnaryExpression).operand); - case SyntaxKind.TypeOfExpression: - return isNarrowingExpression((expr as TypeOfExpression).expression); + if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) { + node.flowNode = currentFlow; + } + switch (node.kind) { + case SyntaxKind.WhileStatement: + bindWhileStatement(node as WhileStatement); + break; + case SyntaxKind.DoStatement: + bindDoStatement(node as DoStatement); + break; + case SyntaxKind.ForStatement: + bindForStatement(node as ForStatement); + break; + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + bindForInOrForOfStatement(node as ForInOrOfStatement); + break; + case SyntaxKind.IfStatement: + bindIfStatement(node as IfStatement); + break; + case SyntaxKind.ReturnStatement: + case SyntaxKind.ThrowStatement: + bindReturnOrThrow(node as ReturnStatement | ThrowStatement); + break; + case SyntaxKind.BreakStatement: + case SyntaxKind.ContinueStatement: + bindBreakOrContinueStatement(node as BreakOrContinueStatement); + break; + case SyntaxKind.TryStatement: + bindTryStatement(node as TryStatement); + break; + case SyntaxKind.SwitchStatement: + bindSwitchStatement(node as SwitchStatement); + break; + case SyntaxKind.CaseBlock: + bindCaseBlock(node as CaseBlock); + break; + case SyntaxKind.CaseClause: + bindCaseClause(node as CaseClause); + break; + case SyntaxKind.ExpressionStatement: + bindExpressionStatement(node as ExpressionStatement); + break; + case SyntaxKind.LabeledStatement: + bindLabeledStatement(node as LabeledStatement); + break; + case SyntaxKind.PrefixUnaryExpression: + bindPrefixUnaryExpressionFlow(node as PrefixUnaryExpression); + break; + case SyntaxKind.PostfixUnaryExpression: + bindPostfixUnaryExpressionFlow(node as PostfixUnaryExpression); + break; + case SyntaxKind.BinaryExpression: + if (isDestructuringAssignment(node)) { + // Carry over whether we are in an assignment pattern to + // binary expressions that could actually be an initializer + inAssignmentPattern = saveInAssignmentPattern; + bindDestructuringAssignmentFlow(node); + return; + } + bindBinaryExpressionFlow(node as BinaryExpression); + break; + case SyntaxKind.DeleteExpression: + bindDeleteExpressionFlow(node as DeleteExpression); + break; + case SyntaxKind.ConditionalExpression: + bindConditionalExpressionFlow(node as ConditionalExpression); + break; + case SyntaxKind.VariableDeclaration: + bindVariableDeclarationFlow(node as VariableDeclaration); + break; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + bindAccessExpressionFlow(node as AccessExpression); + break; + case SyntaxKind.CallExpression: + bindCallExpressionFlow(node as CallExpression); + break; + case SyntaxKind.NonNullExpression: + bindNonNullExpressionFlow(node as NonNullExpression); + break; + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag); + break; + // In source files and blocks, bind functions first to match hoisting that occurs at runtime + case SyntaxKind.SourceFile: { + bindEachFunctionsFirst((node as SourceFile).statements); + bind((node as SourceFile).endOfFileToken); + break; } - return false; + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + bindEachFunctionsFirst((node as Block).statements); + break; + case SyntaxKind.BindingElement: + bindBindingElementFlow(node as BindingElement); + break; + case SyntaxKind.Parameter: + bindParameterFlow(node as ParameterDeclaration); + break; + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.SpreadElement: + // Carry over whether we are in an assignment pattern of Object and Array literals + // as well as their children that are valid assignment targets. + inAssignmentPattern = saveInAssignmentPattern; + // falls through + default: + bindEachChild(node); + break; } + bindJSDoc(node); + inAssignmentPattern = saveInAssignmentPattern; + } - function isNarrowableReference(expr: Expression): boolean { - return isDottedName(expr) - || (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) - || isBinaryExpression(expr) && expr.operatorToken.kind === SyntaxKind.CommaToken && isNarrowableReference(expr.right) - || isElementAccessExpression(expr) && (isStringOrNumericLiteralLike(expr.argumentExpression) || isEntityNameExpression(expr.argumentExpression)) && isNarrowableReference(expr.expression) - || isAssignmentExpression(expr) && isNarrowableReference(expr.left); + function isNarrowingExpression(expr: Expression): boolean { + switch (expr.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: + case SyntaxKind.ThisKeyword: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return containsNarrowableReference(expr); + case SyntaxKind.CallExpression: + return hasNarrowableArgument(expr as CallExpression); + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.NonNullExpression: + return isNarrowingExpression((expr as ParenthesizedExpression | NonNullExpression).expression); + case SyntaxKind.BinaryExpression: + return isNarrowingBinaryExpression(expr as BinaryExpression); + case SyntaxKind.PrefixUnaryExpression: + return (expr as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken && isNarrowingExpression((expr as PrefixUnaryExpression).operand); + case SyntaxKind.TypeOfExpression: + return isNarrowingExpression((expr as TypeOfExpression).expression); } + return false; + } - function containsNarrowableReference(expr: Expression): boolean { - return isNarrowableReference(expr) || isOptionalChain(expr) && containsNarrowableReference(expr.expression); - } + function isNarrowableReference(expr: Expression): boolean { + return isDottedName(expr) + || (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) + || isBinaryExpression(expr) && expr.operatorToken.kind === SyntaxKind.CommaToken && isNarrowableReference(expr.right) + || isElementAccessExpression(expr) && (isStringOrNumericLiteralLike(expr.argumentExpression) || isEntityNameExpression(expr.argumentExpression)) && isNarrowableReference(expr.expression) + || isAssignmentExpression(expr) && isNarrowableReference(expr.left); + } - function hasNarrowableArgument(expr: CallExpression) { - if (expr.arguments) { - for (const argument of expr.arguments) { - if (containsNarrowableReference(argument)) { - return true; - } + function containsNarrowableReference(expr: Expression): boolean { + return isNarrowableReference(expr) || isOptionalChain(expr) && containsNarrowableReference(expr.expression); + } + + function hasNarrowableArgument(expr: CallExpression) { + if (expr.arguments) { + for (const argument of expr.arguments) { + if (containsNarrowableReference(argument)) { + return true; } } - if (expr.expression.kind === SyntaxKind.PropertyAccessExpression && - containsNarrowableReference((expr.expression as PropertyAccessExpression).expression)) { - return true; - } - return false; } + if (expr.expression.kind === SyntaxKind.PropertyAccessExpression && + containsNarrowableReference((expr.expression as PropertyAccessExpression).expression)) { + return true; + } + return false; + } - function isNarrowingTypeofOperands(expr1: Expression, expr2: Expression) { - return isTypeOfExpression(expr1) && isNarrowableOperand(expr1.expression) && isStringLiteralLike(expr2); - } - - function isNarrowingBinaryExpression(expr: BinaryExpression) { - switch (expr.operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.BarBarEqualsToken: - case SyntaxKind.AmpersandAmpersandEqualsToken: - case SyntaxKind.QuestionQuestionEqualsToken: - return containsNarrowableReference(expr.left); - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - return isNarrowableOperand(expr.left) || isNarrowableOperand(expr.right) || - isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right); - case SyntaxKind.InstanceOfKeyword: - return isNarrowableOperand(expr.left); - case SyntaxKind.InKeyword: - return isNarrowingExpression(expr.right); - case SyntaxKind.CommaToken: - return isNarrowingExpression(expr.right); - } - return false; + function isNarrowingTypeofOperands(expr1: Expression, expr2: Expression) { + return isTypeOfExpression(expr1) && isNarrowableOperand(expr1.expression) && isStringLiteralLike(expr2); + } + + function isNarrowingBinaryExpression(expr: BinaryExpression) { + switch (expr.operatorToken.kind) { + case SyntaxKind.EqualsToken: + case SyntaxKind.BarBarEqualsToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: + case SyntaxKind.QuestionQuestionEqualsToken: + return containsNarrowableReference(expr.left); + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + return isNarrowableOperand(expr.left) || isNarrowableOperand(expr.right) || + isNarrowingTypeofOperands(expr.right, expr.left) || isNarrowingTypeofOperands(expr.left, expr.right); + case SyntaxKind.InstanceOfKeyword: + return isNarrowableOperand(expr.left); + case SyntaxKind.InKeyword: + return isNarrowingExpression(expr.right); + case SyntaxKind.CommaToken: + return isNarrowingExpression(expr.right); } + return false; + } - function isNarrowableOperand(expr: Expression): boolean { - switch (expr.kind) { - case SyntaxKind.ParenthesizedExpression: - return isNarrowableOperand((expr as ParenthesizedExpression).expression); - case SyntaxKind.BinaryExpression: - switch ((expr as BinaryExpression).operatorToken.kind) { - case SyntaxKind.EqualsToken: - return isNarrowableOperand((expr as BinaryExpression).left); - case SyntaxKind.CommaToken: - return isNarrowableOperand((expr as BinaryExpression).right); - } - } - return containsNarrowableReference(expr); + function isNarrowableOperand(expr: Expression): boolean { + switch (expr.kind) { + case SyntaxKind.ParenthesizedExpression: + return isNarrowableOperand((expr as ParenthesizedExpression).expression); + case SyntaxKind.BinaryExpression: + switch ((expr as BinaryExpression).operatorToken.kind) { + case SyntaxKind.EqualsToken: + return isNarrowableOperand((expr as BinaryExpression).left); + case SyntaxKind.CommaToken: + return isNarrowableOperand((expr as BinaryExpression).right); + } } + return containsNarrowableReference(expr); + } + + function createBranchLabel(): FlowLabel { + return initFlowNode({ flags: FlowFlags.BranchLabel, antecedents: undefined }); + } + + function createLoopLabel(): FlowLabel { + return initFlowNode({ flags: FlowFlags.LoopLabel, antecedents: undefined }); + } + + function createReduceLabel(target: FlowLabel, antecedents: FlowNode[], antecedent: FlowNode): FlowReduceLabel { + return initFlowNode({ flags: FlowFlags.ReduceLabel, target, antecedents, antecedent }); + } - function createBranchLabel(): FlowLabel { - return initFlowNode({ flags: FlowFlags.BranchLabel, antecedents: undefined }); + function setFlowNodeReferenced(flow: FlowNode) { + // On first reference we set the Referenced flag, thereafter we set the Shared flag + flow.flags |= flow.flags & FlowFlags.Referenced ? FlowFlags.Shared : FlowFlags.Referenced; + } + + function addAntecedent(label: FlowLabel, antecedent: FlowNode): void { + if (!(antecedent.flags & FlowFlags.Unreachable) && !contains(label.antecedents, antecedent)) { + (label.antecedents || (label.antecedents = [])).push(antecedent); + setFlowNodeReferenced(antecedent); } + } - function createLoopLabel(): FlowLabel { - return initFlowNode({ flags: FlowFlags.LoopLabel, antecedents: undefined }); + function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { + if (antecedent.flags & FlowFlags.Unreachable) { + return antecedent; + } + if (!expression) { + return flags & FlowFlags.TrueCondition ? antecedent : unreachableFlow; + } + if ((expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition || + expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) && + !isExpressionOfOptionalChainRoot(expression) && !isNullishCoalesce(expression.parent)) { + return unreachableFlow; + } + if (!isNarrowingExpression(expression)) { + return antecedent; } + setFlowNodeReferenced(antecedent); + return initFlowNode({ flags, antecedent, node: expression }); + } + + function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { + setFlowNodeReferenced(antecedent); + return initFlowNode({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd }); + } - function createReduceLabel(target: FlowLabel, antecedents: FlowNode[], antecedent: FlowNode): FlowReduceLabel { - return initFlowNode({ flags: FlowFlags.ReduceLabel, target, antecedents, antecedent }); + function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode { + setFlowNodeReferenced(antecedent); + const result = initFlowNode({ flags, antecedent, node }); + if (currentExceptionTarget) { + addAntecedent(currentExceptionTarget, result); } + return result; + } + + function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode { + setFlowNodeReferenced(antecedent); + return initFlowNode({ flags: FlowFlags.Call, antecedent, node }); + } - function setFlowNodeReferenced(flow: FlowNode) { - // On first reference we set the Referenced flag, thereafter we set the Shared flag - flow.flags |= flow.flags & FlowFlags.Referenced ? FlowFlags.Shared : FlowFlags.Referenced; + function finishFlowLabel(flow: FlowLabel): FlowNode { + const antecedents = flow.antecedents; + if (!antecedents) { + return unreachableFlow; } + if (antecedents.length === 1) { + return antecedents[0]; + } + return flow; + } - function addAntecedent(label: FlowLabel, antecedent: FlowNode): void { - if (!(antecedent.flags & FlowFlags.Unreachable) && !contains(label.antecedents, antecedent)) { - (label.antecedents || (label.antecedents = [])).push(antecedent); - setFlowNodeReferenced(antecedent); - } + function isStatementCondition(node: Node) { + const parent = node.parent; + switch (parent.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.DoStatement: + return (parent as IfStatement | WhileStatement | DoStatement).expression === node; + case SyntaxKind.ForStatement: + case SyntaxKind.ConditionalExpression: + return (parent as ForStatement | ConditionalExpression).condition === node; } + return false; + } - function createFlowCondition(flags: FlowFlags, antecedent: FlowNode, expression: Expression | undefined): FlowNode { - if (antecedent.flags & FlowFlags.Unreachable) { - return antecedent; - } - if (!expression) { - return flags & FlowFlags.TrueCondition ? antecedent : unreachableFlow; + function isLogicalExpression(node: Node) { + while (true) { + if (node.kind === SyntaxKind.ParenthesizedExpression) { + node = (node as ParenthesizedExpression).expression; } - if ((expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition || - expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) && - !isExpressionOfOptionalChainRoot(expression) && !isNullishCoalesce(expression.parent)) { - return unreachableFlow; + else if (node.kind === SyntaxKind.PrefixUnaryExpression && (node as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken) { + node = (node as PrefixUnaryExpression).operand; } - if (!isNarrowingExpression(expression)) { - return antecedent; + else { + return node.kind === SyntaxKind.BinaryExpression && ( + (node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || + (node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || + (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken); } - setFlowNodeReferenced(antecedent); - return initFlowNode({ flags, antecedent, node: expression }); } + } - function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode { - setFlowNodeReferenced(antecedent); - return initFlowNode({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd }); - } + function isLogicalAssignmentExpression(node: Node) { + node = skipParentheses(node); + return isBinaryExpression(node) && isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind); + } - function createFlowMutation(flags: FlowFlags, antecedent: FlowNode, node: Expression | VariableDeclaration | ArrayBindingElement): FlowNode { - setFlowNodeReferenced(antecedent); - const result = initFlowNode({ flags, antecedent, node }); - if (currentExceptionTarget) { - addAntecedent(currentExceptionTarget, result); - } - return result; + function isTopLevelLogicalExpression(node: Node): boolean { + while (isParenthesizedExpression(node.parent) || + isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.ExclamationToken) { + node = node.parent; } + return !isStatementCondition(node) && + !isLogicalExpression(node.parent) && + !(isOptionalChain(node.parent) && node.parent.expression === node); + } - function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode { - setFlowNodeReferenced(antecedent); - return initFlowNode({ flags: FlowFlags.Call, antecedent, node }); - } + function doWithConditionalBranches(action: (value: T) => void, value: T, trueTarget: FlowLabel, falseTarget: FlowLabel) { + const savedTrueTarget = currentTrueTarget; + const savedFalseTarget = currentFalseTarget; + currentTrueTarget = trueTarget; + currentFalseTarget = falseTarget; + action(value); + currentTrueTarget = savedTrueTarget; + currentFalseTarget = savedFalseTarget; + } - function finishFlowLabel(flow: FlowLabel): FlowNode { - const antecedents = flow.antecedents; - if (!antecedents) { - return unreachableFlow; - } - if (antecedents.length === 1) { - return antecedents[0]; - } - return flow; + function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) { + doWithConditionalBranches(bind, node, trueTarget, falseTarget); + if (!node || !isLogicalAssignmentExpression(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) { + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } + } - function isStatementCondition(node: Node) { - const parent = node.parent; - switch (parent.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.DoStatement: - return (parent as IfStatement | WhileStatement | DoStatement).expression === node; - case SyntaxKind.ForStatement: - case SyntaxKind.ConditionalExpression: - return (parent as ForStatement | ConditionalExpression).condition === node; - } - return false; - } + function bindIterativeStatement(node: Statement, breakTarget: FlowLabel, continueTarget: FlowLabel): void { + const saveBreakTarget = currentBreakTarget; + const saveContinueTarget = currentContinueTarget; + currentBreakTarget = breakTarget; + currentContinueTarget = continueTarget; + bind(node); + currentBreakTarget = saveBreakTarget; + currentContinueTarget = saveContinueTarget; + } - function isLogicalExpression(node: Node) { - while (true) { - if (node.kind === SyntaxKind.ParenthesizedExpression) { - node = (node as ParenthesizedExpression).expression; - } - else if (node.kind === SyntaxKind.PrefixUnaryExpression && (node as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken) { - node = (node as PrefixUnaryExpression).operand; - } - else { - return node.kind === SyntaxKind.BinaryExpression && ( - (node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || - (node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || - (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken); - } - } + function setContinueTarget(node: Node, target: FlowLabel) { + let label = activeLabelList; + while (label && node.parent.kind === SyntaxKind.LabeledStatement) { + label.continueTarget = target; + label = label.next; + node = node.parent; } + return target; + } - function isLogicalAssignmentExpression(node: Node) { - node = skipParentheses(node); - return isBinaryExpression(node) && isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind); - } + function bindWhileStatement(node: WhileStatement): void { + const preWhileLabel = setContinueTarget(node, createLoopLabel()); + const preBodyLabel = createBranchLabel(); + const postWhileLabel = createBranchLabel(); + addAntecedent(preWhileLabel, currentFlow); + currentFlow = preWhileLabel; + bindCondition(node.expression, preBodyLabel, postWhileLabel); + currentFlow = finishFlowLabel(preBodyLabel); + bindIterativeStatement(node.statement, postWhileLabel, preWhileLabel); + addAntecedent(preWhileLabel, currentFlow); + currentFlow = finishFlowLabel(postWhileLabel); + } - function isTopLevelLogicalExpression(node: Node): boolean { - while (isParenthesizedExpression(node.parent) || - isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.ExclamationToken) { - node = node.parent; - } - return !isStatementCondition(node) && - !isLogicalExpression(node.parent) && - !(isOptionalChain(node.parent) && node.parent.expression === node); - } + function bindDoStatement(node: DoStatement): void { + const preDoLabel = createLoopLabel(); + const preConditionLabel = setContinueTarget(node, createBranchLabel()); + const postDoLabel = createBranchLabel(); + addAntecedent(preDoLabel, currentFlow); + currentFlow = preDoLabel; + bindIterativeStatement(node.statement, postDoLabel, preConditionLabel); + addAntecedent(preConditionLabel, currentFlow); + currentFlow = finishFlowLabel(preConditionLabel); + bindCondition(node.expression, preDoLabel, postDoLabel); + currentFlow = finishFlowLabel(postDoLabel); + } - function doWithConditionalBranches(action: (value: T) => void, value: T, trueTarget: FlowLabel, falseTarget: FlowLabel) { - const savedTrueTarget = currentTrueTarget; - const savedFalseTarget = currentFalseTarget; - currentTrueTarget = trueTarget; - currentFalseTarget = falseTarget; - action(value); - currentTrueTarget = savedTrueTarget; - currentFalseTarget = savedFalseTarget; - } + function bindForStatement(node: ForStatement): void { + const preLoopLabel = setContinueTarget(node, createLoopLabel()); + const preBodyLabel = createBranchLabel(); + const postLoopLabel = createBranchLabel(); + bind(node.initializer); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = preLoopLabel; + bindCondition(node.condition, preBodyLabel, postLoopLabel); + currentFlow = finishFlowLabel(preBodyLabel); + bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); + bind(node.incrementor); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = finishFlowLabel(postLoopLabel); + } - function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) { - doWithConditionalBranches(bind, node, trueTarget, falseTarget); - if (!node || !isLogicalAssignmentExpression(node) && !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) { - addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); - addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); - } - } + function bindForInOrForOfStatement(node: ForInOrOfStatement): void { + const preLoopLabel = setContinueTarget(node, createLoopLabel()); + const postLoopLabel = createBranchLabel(); + bind(node.expression); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = preLoopLabel; + if (node.kind === SyntaxKind.ForOfStatement) { + bind(node.awaitModifier); + } + addAntecedent(postLoopLabel, currentFlow); + bind(node.initializer); + if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) { + bindAssignmentTargetFlow(node.initializer); + } + bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); + addAntecedent(preLoopLabel, currentFlow); + currentFlow = finishFlowLabel(postLoopLabel); + } - function bindIterativeStatement(node: Statement, breakTarget: FlowLabel, continueTarget: FlowLabel): void { - const saveBreakTarget = currentBreakTarget; - const saveContinueTarget = currentContinueTarget; - currentBreakTarget = breakTarget; - currentContinueTarget = continueTarget; - bind(node); - currentBreakTarget = saveBreakTarget; - currentContinueTarget = saveContinueTarget; - } + function bindIfStatement(node: IfStatement): void { + const thenLabel = createBranchLabel(); + const elseLabel = createBranchLabel(); + const postIfLabel = createBranchLabel(); + bindCondition(node.expression, thenLabel, elseLabel); + currentFlow = finishFlowLabel(thenLabel); + bind(node.thenStatement); + addAntecedent(postIfLabel, currentFlow); + currentFlow = finishFlowLabel(elseLabel); + bind(node.elseStatement); + addAntecedent(postIfLabel, currentFlow); + currentFlow = finishFlowLabel(postIfLabel); + } - function setContinueTarget(node: Node, target: FlowLabel) { - let label = activeLabelList; - while (label && node.parent.kind === SyntaxKind.LabeledStatement) { - label.continueTarget = target; - label = label.next; - node = node.parent; - } - return target; - } - - function bindWhileStatement(node: WhileStatement): void { - const preWhileLabel = setContinueTarget(node, createLoopLabel()); - const preBodyLabel = createBranchLabel(); - const postWhileLabel = createBranchLabel(); - addAntecedent(preWhileLabel, currentFlow); - currentFlow = preWhileLabel; - bindCondition(node.expression, preBodyLabel, postWhileLabel); - currentFlow = finishFlowLabel(preBodyLabel); - bindIterativeStatement(node.statement, postWhileLabel, preWhileLabel); - addAntecedent(preWhileLabel, currentFlow); - currentFlow = finishFlowLabel(postWhileLabel); - } - - function bindDoStatement(node: DoStatement): void { - const preDoLabel = createLoopLabel(); - const preConditionLabel = setContinueTarget(node, createBranchLabel()); - const postDoLabel = createBranchLabel(); - addAntecedent(preDoLabel, currentFlow); - currentFlow = preDoLabel; - bindIterativeStatement(node.statement, postDoLabel, preConditionLabel); - addAntecedent(preConditionLabel, currentFlow); - currentFlow = finishFlowLabel(preConditionLabel); - bindCondition(node.expression, preDoLabel, postDoLabel); - currentFlow = finishFlowLabel(postDoLabel); - } - - function bindForStatement(node: ForStatement): void { - const preLoopLabel = setContinueTarget(node, createLoopLabel()); - const preBodyLabel = createBranchLabel(); - const postLoopLabel = createBranchLabel(); - bind(node.initializer); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = preLoopLabel; - bindCondition(node.condition, preBodyLabel, postLoopLabel); - currentFlow = finishFlowLabel(preBodyLabel); - bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); - bind(node.incrementor); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = finishFlowLabel(postLoopLabel); - } - - function bindForInOrForOfStatement(node: ForInOrOfStatement): void { - const preLoopLabel = setContinueTarget(node, createLoopLabel()); - const postLoopLabel = createBranchLabel(); - bind(node.expression); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = preLoopLabel; - if (node.kind === SyntaxKind.ForOfStatement) { - bind(node.awaitModifier); - } - addAntecedent(postLoopLabel, currentFlow); - bind(node.initializer); - if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) { - bindAssignmentTargetFlow(node.initializer); - } - bindIterativeStatement(node.statement, postLoopLabel, preLoopLabel); - addAntecedent(preLoopLabel, currentFlow); - currentFlow = finishFlowLabel(postLoopLabel); - } - - function bindIfStatement(node: IfStatement): void { - const thenLabel = createBranchLabel(); - const elseLabel = createBranchLabel(); - const postIfLabel = createBranchLabel(); - bindCondition(node.expression, thenLabel, elseLabel); - currentFlow = finishFlowLabel(thenLabel); - bind(node.thenStatement); - addAntecedent(postIfLabel, currentFlow); - currentFlow = finishFlowLabel(elseLabel); - bind(node.elseStatement); - addAntecedent(postIfLabel, currentFlow); - currentFlow = finishFlowLabel(postIfLabel); - } - - function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void { - bind(node.expression); - if (node.kind === SyntaxKind.ReturnStatement) { - hasExplicitReturn = true; - if (currentReturnTarget) { - addAntecedent(currentReturnTarget, currentFlow); - } + function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void { + bind(node.expression); + if (node.kind === SyntaxKind.ReturnStatement) { + hasExplicitReturn = true; + if (currentReturnTarget) { + addAntecedent(currentReturnTarget, currentFlow); } - currentFlow = unreachableFlow; } + currentFlow = unreachableFlow; + } - function findActiveLabel(name: __String) { - for (let label = activeLabelList; label; label = label.next) { - if (label.name === name) { - return label; - } + function findActiveLabel(name: __String) { + for (let label = activeLabelList; label; label = label.next) { + if (label.name === name) { + return label; } - return undefined; } + return undefined; + } - function bindBreakOrContinueFlow(node: BreakOrContinueStatement, breakTarget: FlowLabel | undefined, continueTarget: FlowLabel | undefined) { - const flowLabel = node.kind === SyntaxKind.BreakStatement ? breakTarget : continueTarget; - if (flowLabel) { - addAntecedent(flowLabel, currentFlow); - currentFlow = unreachableFlow; - } + function bindBreakOrContinueFlow(node: BreakOrContinueStatement, breakTarget: FlowLabel | undefined, continueTarget: FlowLabel | undefined) { + const flowLabel = node.kind === SyntaxKind.BreakStatement ? breakTarget : continueTarget; + if (flowLabel) { + addAntecedent(flowLabel, currentFlow); + currentFlow = unreachableFlow; } + } - function bindBreakOrContinueStatement(node: BreakOrContinueStatement): void { - bind(node.label); - if (node.label) { - const activeLabel = findActiveLabel(node.label.escapedText); - if (activeLabel) { - activeLabel.referenced = true; - bindBreakOrContinueFlow(node, activeLabel.breakTarget, activeLabel.continueTarget); - } - } - else { - bindBreakOrContinueFlow(node, currentBreakTarget, currentContinueTarget); + function bindBreakOrContinueStatement(node: BreakOrContinueStatement): void { + bind(node.label); + if (node.label) { + const activeLabel = findActiveLabel(node.label.escapedText); + if (activeLabel) { + activeLabel.referenced = true; + bindBreakOrContinueFlow(node, activeLabel.breakTarget, activeLabel.continueTarget); } } + else { + bindBreakOrContinueFlow(node, currentBreakTarget, currentContinueTarget); + } + } - function bindTryStatement(node: TryStatement): void { - // We conservatively assume that *any* code in the try block can cause an exception, but we only need - // to track code that causes mutations (because only mutations widen the possible control flow type of - // a variable). The exceptionLabel is the target label for control flows that result from exceptions. - // We add all mutation flow nodes as antecedents of this label such that we can analyze them as possible - // antecedents of the start of catch or finally blocks. Furthermore, we add the current control flow to - // represent exceptions that occur before any mutations. - const saveReturnTarget = currentReturnTarget; - const saveExceptionTarget = currentExceptionTarget; - const normalExitLabel = createBranchLabel(); - const returnLabel = createBranchLabel(); - let exceptionLabel = createBranchLabel(); - if (node.finallyBlock) { - currentReturnTarget = returnLabel; - } + function bindTryStatement(node: TryStatement): void { + // We conservatively assume that *any* code in the try block can cause an exception, but we only need + // to track code that causes mutations (because only mutations widen the possible control flow type of + // a variable). The exceptionLabel is the target label for control flows that result from exceptions. + // We add all mutation flow nodes as antecedents of this label such that we can analyze them as possible + // antecedents of the start of catch or finally blocks. Furthermore, we add the current control flow to + // represent exceptions that occur before any mutations. + const saveReturnTarget = currentReturnTarget; + const saveExceptionTarget = currentExceptionTarget; + const normalExitLabel = createBranchLabel(); + const returnLabel = createBranchLabel(); + let exceptionLabel = createBranchLabel(); + if (node.finallyBlock) { + currentReturnTarget = returnLabel; + } + addAntecedent(exceptionLabel, currentFlow); + currentExceptionTarget = exceptionLabel; + bind(node.tryBlock); + addAntecedent(normalExitLabel, currentFlow); + if (node.catchClause) { + // Start of catch clause is the target of exceptions from try block. + currentFlow = finishFlowLabel(exceptionLabel); + // The currentExceptionTarget now represents control flows from exceptions in the catch clause. + // Effectively, in a try-catch-finally, if an exception occurs in the try block, the catch block + // acts like a second try block. + exceptionLabel = createBranchLabel(); addAntecedent(exceptionLabel, currentFlow); currentExceptionTarget = exceptionLabel; - bind(node.tryBlock); + bind(node.catchClause); addAntecedent(normalExitLabel, currentFlow); - if (node.catchClause) { - // Start of catch clause is the target of exceptions from try block. - currentFlow = finishFlowLabel(exceptionLabel); - // The currentExceptionTarget now represents control flows from exceptions in the catch clause. - // Effectively, in a try-catch-finally, if an exception occurs in the try block, the catch block - // acts like a second try block. - exceptionLabel = createBranchLabel(); - addAntecedent(exceptionLabel, currentFlow); - currentExceptionTarget = exceptionLabel; - bind(node.catchClause); - addAntecedent(normalExitLabel, currentFlow); + } + currentReturnTarget = saveReturnTarget; + currentExceptionTarget = saveExceptionTarget; + if (node.finallyBlock) { + // Possible ways control can reach the finally block: + // 1) Normal completion of try block of a try-finally or try-catch-finally + // 2) Normal completion of catch block (following exception in try block) of a try-catch-finally + // 3) Return in try or catch block of a try-finally or try-catch-finally + // 4) Exception in try block of a try-finally + // 5) Exception in catch block of a try-catch-finally + // When analyzing a control flow graph that starts inside a finally block we want to consider all + // five possibilities above. However, when analyzing a control flow graph that starts outside (past) + // the finally block, we only want to consider the first two (if we're past a finally block then it + // must have completed normally). Likewise, when analyzing a control flow graph from return statements + // in try or catch blocks in an IIFE, we only want to consider the third. To make this possible, we + // inject a ReduceLabel node into the control flow graph. This node contains an alternate reduced + // set of antecedents for the pre-finally label. As control flow analysis passes by a ReduceLabel + // node, the pre-finally label is temporarily switched to the reduced antecedent set. + const finallyLabel = createBranchLabel(); + finallyLabel.antecedents = concatenate(concatenate(normalExitLabel.antecedents, exceptionLabel.antecedents), returnLabel.antecedents); + currentFlow = finallyLabel; + bind(node.finallyBlock); + if (currentFlow.flags & FlowFlags.Unreachable) { + // If the end of the finally block is unreachable, the end of the entire try statement is unreachable. + currentFlow = unreachableFlow; } - currentReturnTarget = saveReturnTarget; - currentExceptionTarget = saveExceptionTarget; - if (node.finallyBlock) { - // Possible ways control can reach the finally block: - // 1) Normal completion of try block of a try-finally or try-catch-finally - // 2) Normal completion of catch block (following exception in try block) of a try-catch-finally - // 3) Return in try or catch block of a try-finally or try-catch-finally - // 4) Exception in try block of a try-finally - // 5) Exception in catch block of a try-catch-finally - // When analyzing a control flow graph that starts inside a finally block we want to consider all - // five possibilities above. However, when analyzing a control flow graph that starts outside (past) - // the finally block, we only want to consider the first two (if we're past a finally block then it - // must have completed normally). Likewise, when analyzing a control flow graph from return statements - // in try or catch blocks in an IIFE, we only want to consider the third. To make this possible, we - // inject a ReduceLabel node into the control flow graph. This node contains an alternate reduced - // set of antecedents for the pre-finally label. As control flow analysis passes by a ReduceLabel - // node, the pre-finally label is temporarily switched to the reduced antecedent set. - const finallyLabel = createBranchLabel(); - finallyLabel.antecedents = concatenate(concatenate(normalExitLabel.antecedents, exceptionLabel.antecedents), returnLabel.antecedents); - currentFlow = finallyLabel; - bind(node.finallyBlock); - if (currentFlow.flags & FlowFlags.Unreachable) { - // If the end of the finally block is unreachable, the end of the entire try statement is unreachable. - currentFlow = unreachableFlow; + else { + // If we have an IIFE return target and return statements in the try or catch blocks, add a control + // flow that goes back through the finally block and back through only the return statements. + if (currentReturnTarget && returnLabel.antecedents) { + addAntecedent(currentReturnTarget, createReduceLabel(finallyLabel, returnLabel.antecedents, currentFlow)); } - else { - // If we have an IIFE return target and return statements in the try or catch blocks, add a control - // flow that goes back through the finally block and back through only the return statements. - if (currentReturnTarget && returnLabel.antecedents) { - addAntecedent(currentReturnTarget, createReduceLabel(finallyLabel, returnLabel.antecedents, currentFlow)); - } - // If we have an outer exception target (i.e. a containing try-finally or try-catch-finally), add a - // control flow that goes back through the finally blok and back through each possible exception source. - if (currentExceptionTarget && exceptionLabel.antecedents) { - addAntecedent(currentExceptionTarget, createReduceLabel(finallyLabel, exceptionLabel.antecedents, currentFlow)); - } - // If the end of the finally block is reachable, but the end of the try and catch blocks are not, - // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should - // result in an unreachable current control flow. - currentFlow = normalExitLabel.antecedents ? createReduceLabel(finallyLabel, normalExitLabel.antecedents, currentFlow) : unreachableFlow; + // If we have an outer exception target (i.e. a containing try-finally or try-catch-finally), add a + // control flow that goes back through the finally blok and back through each possible exception source. + if (currentExceptionTarget && exceptionLabel.antecedents) { + addAntecedent(currentExceptionTarget, createReduceLabel(finallyLabel, exceptionLabel.antecedents, currentFlow)); } - } - else { - currentFlow = finishFlowLabel(normalExitLabel); + // If the end of the finally block is reachable, but the end of the try and catch blocks are not, + // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should + // result in an unreachable current control flow. + currentFlow = normalExitLabel.antecedents ? createReduceLabel(finallyLabel, normalExitLabel.antecedents, currentFlow) : unreachableFlow; } } - - function bindSwitchStatement(node: SwitchStatement): void { - const postSwitchLabel = createBranchLabel(); - bind(node.expression); - const saveBreakTarget = currentBreakTarget; - const savePreSwitchCaseFlow = preSwitchCaseFlow; - currentBreakTarget = postSwitchLabel; - preSwitchCaseFlow = currentFlow; - bind(node.caseBlock); - addAntecedent(postSwitchLabel, currentFlow); - const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause); - // We mark a switch statement as possibly exhaustive if it has no default clause and if all - // case clauses have unreachable end points (e.g. they all return). Note, we no longer need - // this property in control flow analysis, it's there only for backwards compatibility. - node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedents; - if (!hasDefault) { - addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0)); - } - currentBreakTarget = saveBreakTarget; - preSwitchCaseFlow = savePreSwitchCaseFlow; - currentFlow = finishFlowLabel(postSwitchLabel); + else { + currentFlow = finishFlowLabel(normalExitLabel); } + } + + function bindSwitchStatement(node: SwitchStatement): void { + const postSwitchLabel = createBranchLabel(); + bind(node.expression); + const saveBreakTarget = currentBreakTarget; + const savePreSwitchCaseFlow = preSwitchCaseFlow; + currentBreakTarget = postSwitchLabel; + preSwitchCaseFlow = currentFlow; + bind(node.caseBlock); + addAntecedent(postSwitchLabel, currentFlow); + const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause); + // We mark a switch statement as possibly exhaustive if it has no default clause and if all + // case clauses have unreachable end points (e.g. they all return). Note, we no longer need + // this property in control flow analysis, it's there only for backwards compatibility. + node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedents; + if (!hasDefault) { + addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0)); + } + currentBreakTarget = saveBreakTarget; + preSwitchCaseFlow = savePreSwitchCaseFlow; + currentFlow = finishFlowLabel(postSwitchLabel); + } - function bindCaseBlock(node: CaseBlock): void { - const clauses = node.clauses; - const isNarrowingSwitch = isNarrowingExpression(node.parent.expression); - let fallthroughFlow = unreachableFlow; - for (let i = 0; i < clauses.length; i++) { - const clauseStart = i; - while (!clauses[i].statements.length && i + 1 < clauses.length) { - bind(clauses[i]); - i++; - } - const preCaseLabel = createBranchLabel(); - addAntecedent(preCaseLabel, isNarrowingSwitch ? createFlowSwitchClause(preSwitchCaseFlow!, node.parent, clauseStart, i + 1) : preSwitchCaseFlow!); - addAntecedent(preCaseLabel, fallthroughFlow); - currentFlow = finishFlowLabel(preCaseLabel); - const clause = clauses[i]; - bind(clause); - fallthroughFlow = currentFlow; - if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) { - clause.fallthroughFlowNode = currentFlow; - } + function bindCaseBlock(node: CaseBlock): void { + const clauses = node.clauses; + const isNarrowingSwitch = isNarrowingExpression(node.parent.expression); + let fallthroughFlow = unreachableFlow; + for (let i = 0; i < clauses.length; i++) { + const clauseStart = i; + while (!clauses[i].statements.length && i + 1 < clauses.length) { + bind(clauses[i]); + i++; + } + const preCaseLabel = createBranchLabel(); + addAntecedent(preCaseLabel, isNarrowingSwitch ? createFlowSwitchClause(preSwitchCaseFlow!, node.parent, clauseStart, i + 1) : preSwitchCaseFlow!); + addAntecedent(preCaseLabel, fallthroughFlow); + currentFlow = finishFlowLabel(preCaseLabel); + const clause = clauses[i]; + bind(clause); + fallthroughFlow = currentFlow; + if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) { + clause.fallthroughFlowNode = currentFlow; } } + } - function bindCaseClause(node: CaseClause): void { - const saveCurrentFlow = currentFlow; - currentFlow = preSwitchCaseFlow!; - bind(node.expression); - currentFlow = saveCurrentFlow; - bindEach(node.statements); - } + function bindCaseClause(node: CaseClause): void { + const saveCurrentFlow = currentFlow; + currentFlow = preSwitchCaseFlow!; + bind(node.expression); + currentFlow = saveCurrentFlow; + bindEach(node.statements); + } - function bindExpressionStatement(node: ExpressionStatement): void { - bind(node.expression); - maybeBindExpressionFlowIfCall(node.expression); - } + function bindExpressionStatement(node: ExpressionStatement): void { + bind(node.expression); + maybeBindExpressionFlowIfCall(node.expression); + } - function maybeBindExpressionFlowIfCall(node: Expression) { - // A top level or comma expression call expression with a dotted function name and at least one argument - // is potentially an assertion and is therefore included in the control flow. - if (node.kind === SyntaxKind.CallExpression) { - const call = node as CallExpression; - if (call.expression.kind !== SyntaxKind.SuperKeyword && isDottedName(call.expression)) { - currentFlow = createFlowCall(currentFlow, call); - } + function maybeBindExpressionFlowIfCall(node: Expression) { + // A top level or comma expression call expression with a dotted function name and at least one argument + // is potentially an assertion and is therefore included in the control flow. + if (node.kind === SyntaxKind.CallExpression) { + const call = node as CallExpression; + if (call.expression.kind !== SyntaxKind.SuperKeyword && isDottedName(call.expression)) { + currentFlow = createFlowCall(currentFlow, call); } } + } - function bindLabeledStatement(node: LabeledStatement): void { - const postStatementLabel = createBranchLabel(); - activeLabelList = { - next: activeLabelList, - name: node.label.escapedText, - breakTarget: postStatementLabel, - continueTarget: undefined, - referenced: false - }; - bind(node.label); - bind(node.statement); - if (!activeLabelList.referenced && !options.allowUnusedLabels) { - errorOrSuggestionOnNode(unusedLabelIsError(options), node.label, Diagnostics.Unused_label); - } - activeLabelList = activeLabelList.next; - addAntecedent(postStatementLabel, currentFlow); - currentFlow = finishFlowLabel(postStatementLabel); - } + function bindLabeledStatement(node: LabeledStatement): void { + const postStatementLabel = createBranchLabel(); + activeLabelList = { + next: activeLabelList, + name: node.label.escapedText, + breakTarget: postStatementLabel, + continueTarget: undefined, + referenced: false + }; + bind(node.label); + bind(node.statement); + if (!activeLabelList.referenced && !options.allowUnusedLabels) { + errorOrSuggestionOnNode(unusedLabelIsError(options), node.label, Diagnostics.Unused_label); + } + activeLabelList = activeLabelList.next; + addAntecedent(postStatementLabel, currentFlow); + currentFlow = finishFlowLabel(postStatementLabel); + } - function bindDestructuringTargetFlow(node: Expression) { - if (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - bindAssignmentTargetFlow((node as BinaryExpression).left); - } - else { - bindAssignmentTargetFlow(node); - } + function bindDestructuringTargetFlow(node: Expression) { + if (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + bindAssignmentTargetFlow((node as BinaryExpression).left); } + else { + bindAssignmentTargetFlow(node); + } + } - function bindAssignmentTargetFlow(node: Expression) { - if (isNarrowableReference(node)) { - currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); - } - else if (node.kind === SyntaxKind.ArrayLiteralExpression) { - for (const e of (node as ArrayLiteralExpression).elements) { - if (e.kind === SyntaxKind.SpreadElement) { - bindAssignmentTargetFlow((e as SpreadElement).expression); - } - else { - bindDestructuringTargetFlow(e); - } + function bindAssignmentTargetFlow(node: Expression) { + if (isNarrowableReference(node)) { + currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); + } + else if (node.kind === SyntaxKind.ArrayLiteralExpression) { + for (const e of (node as ArrayLiteralExpression).elements) { + if (e.kind === SyntaxKind.SpreadElement) { + bindAssignmentTargetFlow((e as SpreadElement).expression); + } + else { + bindDestructuringTargetFlow(e); } } - else if (node.kind === SyntaxKind.ObjectLiteralExpression) { - for (const p of (node as ObjectLiteralExpression).properties) { - if (p.kind === SyntaxKind.PropertyAssignment) { - bindDestructuringTargetFlow(p.initializer); - } - else if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { - bindAssignmentTargetFlow(p.name); - } - else if (p.kind === SyntaxKind.SpreadAssignment) { - bindAssignmentTargetFlow(p.expression); - } + } + else if (node.kind === SyntaxKind.ObjectLiteralExpression) { + for (const p of (node as ObjectLiteralExpression).properties) { + if (p.kind === SyntaxKind.PropertyAssignment) { + bindDestructuringTargetFlow(p.initializer); + } + else if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { + bindAssignmentTargetFlow(p.name); + } + else if (p.kind === SyntaxKind.SpreadAssignment) { + bindAssignmentTargetFlow(p.expression); } } } + } - function bindLogicalLikeExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) { - const preRightLabel = createBranchLabel(); - if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) { - bindCondition(node.left, preRightLabel, falseTarget); - } - else { - bindCondition(node.left, trueTarget, preRightLabel); - } - currentFlow = finishFlowLabel(preRightLabel); - bind(node.operatorToken); + function bindLogicalLikeExpression(node: BinaryExpression, trueTarget: FlowLabel, falseTarget: FlowLabel) { + const preRightLabel = createBranchLabel(); + if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === SyntaxKind.AmpersandAmpersandEqualsToken) { + bindCondition(node.left, preRightLabel, falseTarget); + } + else { + bindCondition(node.left, trueTarget, preRightLabel); + } + currentFlow = finishFlowLabel(preRightLabel); + bind(node.operatorToken); - if (isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind)) { - doWithConditionalBranches(bind, node.right, trueTarget, falseTarget); - bindAssignmentTargetFlow(node.left); + if (isLogicalOrCoalescingAssignmentOperator(node.operatorToken.kind)) { + doWithConditionalBranches(bind, node.right, trueTarget, falseTarget); + bindAssignmentTargetFlow(node.left); - addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); - addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); - } - else { - bindCondition(node.right, trueTarget, falseTarget); - } + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } - - function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) { - if (node.operator === SyntaxKind.ExclamationToken) { - const saveTrueTarget = currentTrueTarget; - currentTrueTarget = currentFalseTarget; - currentFalseTarget = saveTrueTarget; - bindEachChild(node); - currentFalseTarget = currentTrueTarget; - currentTrueTarget = saveTrueTarget; - } - else { - bindEachChild(node); - if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { - bindAssignmentTargetFlow(node.operand); - } - } + else { + bindCondition(node.right, trueTarget, falseTarget); } + } - function bindPostfixUnaryExpressionFlow(node: PostfixUnaryExpression) { + function bindPrefixUnaryExpressionFlow(node: PrefixUnaryExpression) { + if (node.operator === SyntaxKind.ExclamationToken) { + const saveTrueTarget = currentTrueTarget; + currentTrueTarget = currentFalseTarget; + currentFalseTarget = saveTrueTarget; + bindEachChild(node); + currentFalseTarget = currentTrueTarget; + currentTrueTarget = saveTrueTarget; + } + else { bindEachChild(node); if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { bindAssignmentTargetFlow(node.operand); } } + } - function bindDestructuringAssignmentFlow(node: DestructuringAssignment) { - if (inAssignmentPattern) { - inAssignmentPattern = false; - bind(node.operatorToken); - bind(node.right); - inAssignmentPattern = true; - bind(node.left); - } - else { - inAssignmentPattern = true; - bind(node.left); - inAssignmentPattern = false; - bind(node.operatorToken); - bind(node.right); - } - bindAssignmentTargetFlow(node.left); + function bindPostfixUnaryExpressionFlow(node: PostfixUnaryExpression) { + bindEachChild(node); + if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { + bindAssignmentTargetFlow(node.operand); } + } - function createBindBinaryExpressionFlow() { - interface WorkArea { - stackIndex: number; - skip: boolean; - inStrictModeStack: (boolean | undefined)[]; - parentStack: (Node | undefined)[]; - } + function bindDestructuringAssignmentFlow(node: DestructuringAssignment) { + if (inAssignmentPattern) { + inAssignmentPattern = false; + bind(node.operatorToken); + bind(node.right); + inAssignmentPattern = true; + bind(node.left); + } + else { + inAssignmentPattern = true; + bind(node.left); + inAssignmentPattern = false; + bind(node.operatorToken); + bind(node.right); + } + bindAssignmentTargetFlow(node.left); + } + + function createBindBinaryExpressionFlow() { + interface WorkArea { + stackIndex: number; + skip: boolean; + inStrictModeStack: (boolean | undefined)[]; + parentStack: (Node | undefined)[]; + } - return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined); + return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined); - function onEnter(node: BinaryExpression, state: WorkArea | undefined) { - if (state) { - state.stackIndex++; - // Emulate the work that `bind` does before reaching `bindChildren`. A normal call to - // `bindBinaryExpressionFlow` will already have done this work. - setParent(node, parent); - const saveInStrictMode = inStrictMode; - bindWorker(node); - const saveParent = parent; - parent = node; - state.skip = false; - state.inStrictModeStack[state.stackIndex] = saveInStrictMode; - state.parentStack[state.stackIndex] = saveParent; + function onEnter(node: BinaryExpression, state: WorkArea | undefined) { + if (state) { + state.stackIndex++; + // Emulate the work that `bind` does before reaching `bindChildren`. A normal call to + // `bindBinaryExpressionFlow` will already have done this work. + setParent(node, parent); + const saveInStrictMode = inStrictMode; + bindWorker(node); + const saveParent = parent; + parent = node; + state.skip = false; + state.inStrictModeStack[state.stackIndex] = saveInStrictMode; + state.parentStack[state.stackIndex] = saveParent; + } + else { + state = { + stackIndex: 0, + skip: false, + inStrictModeStack: [undefined], + parentStack: [undefined] + }; + } + // TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions + // we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too + // For now, though, since the common cases are chained `+`, leaving it recursive is fine + const operator = node.operatorToken.kind; + if (operator === SyntaxKind.AmpersandAmpersandToken || + operator === SyntaxKind.BarBarToken || + operator === SyntaxKind.QuestionQuestionToken || + isLogicalOrCoalescingAssignmentOperator(operator)) { + if (isTopLevelLogicalExpression(node)) { + const postExpressionLabel = createBranchLabel(); + bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel); + currentFlow = finishFlowLabel(postExpressionLabel); } else { - state = { - stackIndex: 0, - skip: false, - inStrictModeStack: [undefined], - parentStack: [undefined] - }; - } - // TODO: bindLogicalExpression is recursive - if we want to handle deeply nested `&&` expressions - // we'll need to handle the `bindLogicalExpression` scenarios in this state machine, too - // For now, though, since the common cases are chained `+`, leaving it recursive is fine - const operator = node.operatorToken.kind; - if (operator === SyntaxKind.AmpersandAmpersandToken || - operator === SyntaxKind.BarBarToken || - operator === SyntaxKind.QuestionQuestionToken || - isLogicalOrCoalescingAssignmentOperator(operator)) { - if (isTopLevelLogicalExpression(node)) { - const postExpressionLabel = createBranchLabel(); - bindLogicalLikeExpression(node, postExpressionLabel, postExpressionLabel); - currentFlow = finishFlowLabel(postExpressionLabel); - } - else { - bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!); - } - state.skip = true; + bindLogicalLikeExpression(node, currentTrueTarget!, currentFalseTarget!); } - return state; + state.skip = true; } + return state; + } - function onLeft(left: Expression, state: WorkArea, node: BinaryExpression) { - if (!state.skip) { - const maybeBound = maybeBind(left); - if (node.operatorToken.kind === SyntaxKind.CommaToken) { - maybeBindExpressionFlowIfCall(left); - } - return maybeBound; + function onLeft(left: Expression, state: WorkArea, node: BinaryExpression) { + if (!state.skip) { + const maybeBound = maybeBind(left); + if (node.operatorToken.kind === SyntaxKind.CommaToken) { + maybeBindExpressionFlowIfCall(left); } + return maybeBound; } + } - function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, _node: BinaryExpression) { - if (!state.skip) { - bind(operatorToken); - } + function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, _node: BinaryExpression) { + if (!state.skip) { + bind(operatorToken); } + } - function onRight(right: Expression, state: WorkArea, node: BinaryExpression) { - if (!state.skip) { - const maybeBound = maybeBind(right); - if (node.operatorToken.kind === SyntaxKind.CommaToken) { - maybeBindExpressionFlowIfCall(right); - } - return maybeBound; + function onRight(right: Expression, state: WorkArea, node: BinaryExpression) { + if (!state.skip) { + const maybeBound = maybeBind(right); + if (node.operatorToken.kind === SyntaxKind.CommaToken) { + maybeBindExpressionFlowIfCall(right); } + return maybeBound; } + } - function onExit(node: BinaryExpression, state: WorkArea) { - if (!state.skip) { - const operator = node.operatorToken.kind; - if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) { - bindAssignmentTargetFlow(node.left); - if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) { - const elementAccess = node.left as ElementAccessExpression; - if (isNarrowableOperand(elementAccess.expression)) { - currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); - } + function onExit(node: BinaryExpression, state: WorkArea) { + if (!state.skip) { + const operator = node.operatorToken.kind; + if (isAssignmentOperator(operator) && !isAssignmentTarget(node)) { + bindAssignmentTargetFlow(node.left); + if (operator === SyntaxKind.EqualsToken && node.left.kind === SyntaxKind.ElementAccessExpression) { + const elementAccess = node.left as ElementAccessExpression; + if (isNarrowableOperand(elementAccess.expression)) { + currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); } } } - const savedInStrictMode = state.inStrictModeStack[state.stackIndex]; - const savedParent = state.parentStack[state.stackIndex]; - if (savedInStrictMode !== undefined) { - inStrictMode = savedInStrictMode; - } - if (savedParent !== undefined) { - parent = savedParent; - } - state.skip = false; - state.stackIndex--; } - - function maybeBind(node: Node) { - if (node && isBinaryExpression(node) && !isDestructuringAssignment(node)) { - return node; - } - bind(node); + const savedInStrictMode = state.inStrictModeStack[state.stackIndex]; + const savedParent = state.parentStack[state.stackIndex]; + if (savedInStrictMode !== undefined) { + inStrictMode = savedInStrictMode; } + if (savedParent !== undefined) { + parent = savedParent; + } + state.skip = false; + state.stackIndex--; } - function bindDeleteExpressionFlow(node: DeleteExpression) { - bindEachChild(node); - if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { - bindAssignmentTargetFlow(node.expression); + function maybeBind(node: Node) { + if (node && isBinaryExpression(node) && !isDestructuringAssignment(node)) { + return node; } + bind(node); } + } - function bindConditionalExpressionFlow(node: ConditionalExpression) { - const trueLabel = createBranchLabel(); - const falseLabel = createBranchLabel(); - const postExpressionLabel = createBranchLabel(); - bindCondition(node.condition, trueLabel, falseLabel); - currentFlow = finishFlowLabel(trueLabel); - bind(node.questionToken); - bind(node.whenTrue); - addAntecedent(postExpressionLabel, currentFlow); - currentFlow = finishFlowLabel(falseLabel); - bind(node.colonToken); - bind(node.whenFalse); - addAntecedent(postExpressionLabel, currentFlow); - currentFlow = finishFlowLabel(postExpressionLabel); + function bindDeleteExpressionFlow(node: DeleteExpression) { + bindEachChild(node); + if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { + bindAssignmentTargetFlow(node.expression); } + } - function bindInitializedVariableFlow(node: VariableDeclaration | ArrayBindingElement) { - const name = !isOmittedExpression(node) ? node.name : undefined; - if (isBindingPattern(name)) { - for (const child of name.elements) { - bindInitializedVariableFlow(child); - } - } - else { - currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); + function bindConditionalExpressionFlow(node: ConditionalExpression) { + const trueLabel = createBranchLabel(); + const falseLabel = createBranchLabel(); + const postExpressionLabel = createBranchLabel(); + bindCondition(node.condition, trueLabel, falseLabel); + currentFlow = finishFlowLabel(trueLabel); + bind(node.questionToken); + bind(node.whenTrue); + addAntecedent(postExpressionLabel, currentFlow); + currentFlow = finishFlowLabel(falseLabel); + bind(node.colonToken); + bind(node.whenFalse); + addAntecedent(postExpressionLabel, currentFlow); + currentFlow = finishFlowLabel(postExpressionLabel); + } + + function bindInitializedVariableFlow(node: VariableDeclaration | ArrayBindingElement) { + const name = !isOmittedExpression(node) ? node.name : undefined; + if (isBindingPattern(name)) { + for (const child of name.elements) { + bindInitializedVariableFlow(child); } } + else { + currentFlow = createFlowMutation(FlowFlags.Assignment, currentFlow, node); + } + } - function bindVariableDeclarationFlow(node: VariableDeclaration) { - bindEachChild(node); - if (node.initializer || isForInOrOfStatement(node.parent.parent)) { - bindInitializedVariableFlow(node); - } + function bindVariableDeclarationFlow(node: VariableDeclaration) { + bindEachChild(node); + if (node.initializer || isForInOrOfStatement(node.parent.parent)) { + bindInitializedVariableFlow(node); } + } + + function bindBindingElementFlow(node: BindingElement) { + // When evaluating a binding pattern, the initializer is evaluated before the binding pattern, per: + // - https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization + // - `BindingElement: BindingPattern Initializer?` + // - https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization + // - `BindingElement: BindingPattern Initializer?` + bind(node.dotDotDotToken); + bind(node.propertyName); + bindInitializer(node.initializer); + bind(node.name); + } + + function bindParameterFlow(node: ParameterDeclaration) { + bindEach(node.modifiers); + bind(node.dotDotDotToken); + bind(node.questionToken); + bind(node.type); + bindInitializer(node.initializer); + bind(node.name); + } - function bindBindingElementFlow(node: BindingElement) { - // When evaluating a binding pattern, the initializer is evaluated before the binding pattern, per: - // - https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization - // - `BindingElement: BindingPattern Initializer?` - // - https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization - // - `BindingElement: BindingPattern Initializer?` - bind(node.dotDotDotToken); - bind(node.propertyName); - bindInitializer(node.initializer); - bind(node.name); + // a BindingElement/Parameter does not have side effects if initializers are not evaluated and used. (see GH#49759) + function bindInitializer(node: Expression | undefined) { + if (!node) { + return; } + const entryFlow = currentFlow; + bind(node); + if (entryFlow === unreachableFlow || entryFlow === currentFlow) { + return; + } + const exitFlow = createBranchLabel(); + addAntecedent(exitFlow, entryFlow); + addAntecedent(exitFlow, currentFlow); + currentFlow = finishFlowLabel(exitFlow); + } - function bindParameterFlow(node: ParameterDeclaration) { - bindEach(node.modifiers); - bind(node.dotDotDotToken); - bind(node.questionToken); - bind(node.type); - bindInitializer(node.initializer); - bind(node.name); + function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) { + bind(node.tagName); + if (node.kind !== SyntaxKind.JSDocEnumTag && node.fullName) { + // don't bind the type name yet; that's delayed until delayedBindJSDocTypedefTag + setParent(node.fullName, node); + setParentRecursive(node.fullName, /*incremental*/ false); } + if (typeof node.comment !== "string") { + bindEach(node.comment); + } + } - // a BindingElement/Parameter does not have side effects if initializers are not evaluated and used. (see GH#49759) - function bindInitializer(node: Expression | undefined) { - if (!node) { - return; - } - const entryFlow = currentFlow; - bind(node); - if (entryFlow === unreachableFlow || entryFlow === currentFlow) { - return; - } - const exitFlow = createBranchLabel(); - addAntecedent(exitFlow, entryFlow); - addAntecedent(exitFlow, currentFlow); - currentFlow = finishFlowLabel(exitFlow); + function bindJSDocClassTag(node: JSDocClassTag) { + bindEachChild(node); + const host = getHostSignatureFromJSDoc(node); + if (host && host.kind !== SyntaxKind.MethodDeclaration) { + addDeclarationToSymbol(host.symbol, host, SymbolFlags.Class); } + } - function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) { - bind(node.tagName); - if (node.kind !== SyntaxKind.JSDocEnumTag && node.fullName) { - // don't bind the type name yet; that's delayed until delayedBindJSDocTypedefTag - setParent(node.fullName, node); - setParentRecursive(node.fullName, /*incremental*/ false); - } - if (typeof node.comment !== "string") { - bindEach(node.comment); - } + function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) { + doWithConditionalBranches(bind, node, trueTarget, falseTarget); + if (!isOptionalChain(node) || isOutermostOptionalChain(node)) { + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } + } - function bindJSDocClassTag(node: JSDocClassTag) { - bindEachChild(node); - const host = getHostSignatureFromJSDoc(node); - if (host && host.kind !== SyntaxKind.MethodDeclaration) { - addDeclarationToSymbol(host.symbol, host, SymbolFlags.Class); - } + function bindOptionalChainRest(node: OptionalChain) { + switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + bind(node.questionDotToken); + bind(node.name); + break; + case SyntaxKind.ElementAccessExpression: + bind(node.questionDotToken); + bind(node.argumentExpression); + break; + case SyntaxKind.CallExpression: + bind(node.questionDotToken); + bindEach(node.typeArguments); + bindEach(node.arguments); + break; } + } - function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) { - doWithConditionalBranches(bind, node, trueTarget, falseTarget); - if (!isOptionalChain(node) || isOutermostOptionalChain(node)) { - addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); - addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); - } + function bindOptionalChain(node: OptionalChain, trueTarget: FlowLabel, falseTarget: FlowLabel) { + // For an optional chain, we emulate the behavior of a logical expression: + // + // a?.b -> a && a.b + // a?.b.c -> a && a.b.c + // a?.b?.c -> a && a.b && a.b.c + // a?.[x = 1] -> a && a[x = 1] + // + // To do this we descend through the chain until we reach the root of a chain (the expression with a `?.`) + // and build it's CFA graph as if it were the first condition (`a && ...`). Then we bind the rest + // of the node as part of the "true" branch, and continue to do so as we ascend back up to the outermost + // chain node. We then treat the entire node as the right side of the expression. + const preChainLabel = isOptionalChainRoot(node) ? createBranchLabel() : undefined; + bindOptionalExpression(node.expression, preChainLabel || trueTarget, falseTarget); + if (preChainLabel) { + currentFlow = finishFlowLabel(preChainLabel); + } + doWithConditionalBranches(bindOptionalChainRest, node, trueTarget, falseTarget); + if (isOutermostOptionalChain(node)) { + addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); + addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); } + } - function bindOptionalChainRest(node: OptionalChain) { - switch (node.kind) { - case SyntaxKind.PropertyAccessExpression: - bind(node.questionDotToken); - bind(node.name); - break; - case SyntaxKind.ElementAccessExpression: - bind(node.questionDotToken); - bind(node.argumentExpression); - break; - case SyntaxKind.CallExpression: - bind(node.questionDotToken); - bindEach(node.typeArguments); - bindEach(node.arguments); - break; - } + function bindOptionalChainFlow(node: OptionalChain) { + if (isTopLevelLogicalExpression(node)) { + const postExpressionLabel = createBranchLabel(); + bindOptionalChain(node, postExpressionLabel, postExpressionLabel); + currentFlow = finishFlowLabel(postExpressionLabel); + } + else { + bindOptionalChain(node, currentTrueTarget!, currentFalseTarget!); } + } - function bindOptionalChain(node: OptionalChain, trueTarget: FlowLabel, falseTarget: FlowLabel) { - // For an optional chain, we emulate the behavior of a logical expression: - // - // a?.b -> a && a.b - // a?.b.c -> a && a.b.c - // a?.b?.c -> a && a.b && a.b.c - // a?.[x = 1] -> a && a[x = 1] - // - // To do this we descend through the chain until we reach the root of a chain (the expression with a `?.`) - // and build it's CFA graph as if it were the first condition (`a && ...`). Then we bind the rest - // of the node as part of the "true" branch, and continue to do so as we ascend back up to the outermost - // chain node. We then treat the entire node as the right side of the expression. - const preChainLabel = isOptionalChainRoot(node) ? createBranchLabel() : undefined; - bindOptionalExpression(node.expression, preChainLabel || trueTarget, falseTarget); - if (preChainLabel) { - currentFlow = finishFlowLabel(preChainLabel); - } - doWithConditionalBranches(bindOptionalChainRest, node, trueTarget, falseTarget); - if (isOutermostOptionalChain(node)) { - addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node)); - addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node)); - } + function bindNonNullExpressionFlow(node: NonNullExpression | NonNullChain) { + if (isOptionalChain(node)) { + bindOptionalChainFlow(node); + } + else { + bindEachChild(node); } + } - function bindOptionalChainFlow(node: OptionalChain) { - if (isTopLevelLogicalExpression(node)) { - const postExpressionLabel = createBranchLabel(); - bindOptionalChain(node, postExpressionLabel, postExpressionLabel); - currentFlow = finishFlowLabel(postExpressionLabel); - } - else { - bindOptionalChain(node, currentTrueTarget!, currentFalseTarget!); - } + function bindAccessExpressionFlow(node: AccessExpression | PropertyAccessChain | ElementAccessChain) { + if (isOptionalChain(node)) { + bindOptionalChainFlow(node); } + else { + bindEachChild(node); + } + } - function bindNonNullExpressionFlow(node: NonNullExpression | NonNullChain) { - if (isOptionalChain(node)) { - bindOptionalChainFlow(node); + function bindCallExpressionFlow(node: CallExpression | CallChain) { + if (isOptionalChain(node)) { + bindOptionalChainFlow(node); + } + else { + // If the target of the call expression is a function expression or arrow function we have + // an immediately invoked function expression (IIFE). Initialize the flowNode property to + // the current control flow (which includes evaluation of the IIFE arguments). + const expr = skipParentheses(node.expression); + if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { + bindEach(node.typeArguments); + bindEach(node.arguments); + bind(node.expression); } else { bindEachChild(node); + if (node.expression.kind === SyntaxKind.SuperKeyword) { + currentFlow = createFlowCall(currentFlow, node); + } } } - - function bindAccessExpressionFlow(node: AccessExpression | PropertyAccessChain | ElementAccessChain) { - if (isOptionalChain(node)) { - bindOptionalChainFlow(node); - } - else { - bindEachChild(node); + if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { + const propertyAccess = node.expression as PropertyAccessExpression; + if (isIdentifier(propertyAccess.name) && isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) { + currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); } } + } - function bindCallExpressionFlow(node: CallExpression | CallChain) { - if (isOptionalChain(node)) { - bindOptionalChainFlow(node); - } - else { - // If the target of the call expression is a function expression or arrow function we have - // an immediately invoked function expression (IIFE). Initialize the flowNode property to - // the current control flow (which includes evaluation of the IIFE arguments). - const expr = skipParentheses(node.expression); - if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) { - bindEach(node.typeArguments); - bindEach(node.arguments); - bind(node.expression); - } - else { - bindEachChild(node); - if (node.expression.kind === SyntaxKind.SuperKeyword) { - currentFlow = createFlowCall(currentFlow, node); - } - } - } - if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { - const propertyAccess = node.expression as PropertyAccessExpression; - if (isIdentifier(propertyAccess.name) && isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) { - currentFlow = createFlowMutation(FlowFlags.ArrayMutation, currentFlow, node); - } - } + function getContainerFlags(node: Node): ContainerFlags { + switch (node.kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.JsxAttributes: + return ContainerFlags.IsContainer; + + case SyntaxKind.InterfaceDeclaration: + return ContainerFlags.IsContainer | ContainerFlags.IsInterface; + + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.MappedType: + case SyntaxKind.IndexSignature: + return ContainerFlags.IsContainer | ContainerFlags.HasLocals; + + case SyntaxKind.SourceFile: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals; + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + if (isObjectLiteralOrClassExpressionMethodOrAccessor(node)) { + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor; + } + // falls through + case SyntaxKind.Constructor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.JSDocSignature: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructSignature: + case SyntaxKind.ConstructorType: + case SyntaxKind.ClassStaticBlockDeclaration: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike; + + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsFunctionExpression; + + case SyntaxKind.ModuleBlock: + return ContainerFlags.IsControlFlowContainer; + case SyntaxKind.PropertyDeclaration: + return (node as PropertyDeclaration).initializer ? ContainerFlags.IsControlFlowContainer : 0; + + case SyntaxKind.CatchClause: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.CaseBlock: + return ContainerFlags.IsBlockScopedContainer; + + case SyntaxKind.Block: + // do not treat blocks directly inside a function as a block-scoped-container. + // Locals that reside in this block should go to the function locals. Otherwise 'x' + // would not appear to be a redeclaration of a block scoped local in the following + // example: + // + // function foo() { + // var x; + // let x; + // } + // + // If we placed 'var x' into the function locals and 'let x' into the locals of + // the block, then there would be no collision. + // + // By not creating a new block-scoped-container here, we ensure that both 'var x' + // and 'let x' go into the Function-container's locals, and we do get a collision + // conflict. + return isFunctionLike(node.parent) || isClassStaticBlockDeclaration(node.parent) ? ContainerFlags.None : ContainerFlags.IsBlockScopedContainer; } - function getContainerFlags(node: Node): ContainerFlags { - switch (node.kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.JsxAttributes: - return ContainerFlags.IsContainer; + return ContainerFlags.None; + } - case SyntaxKind.InterfaceDeclaration: - return ContainerFlags.IsContainer | ContainerFlags.IsInterface; + function addToContainerChain(next: Node) { + if (lastContainer) { + lastContainer.nextContainer = next; + } - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.MappedType: - case SyntaxKind.IndexSignature: - return ContainerFlags.IsContainer | ContainerFlags.HasLocals; + lastContainer = next; + } - case SyntaxKind.SourceFile: - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals; + function declareSymbolAndAddToSymbolTable(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol | undefined { + switch (container.kind) { + // Modules, source files, and classes need specialized handling for how their + // members are declared (for example, a member of a class will go into a specific + // symbol table depending on if it is static or not). We defer to specialized + // handlers to take care of declaring these child members. + case SyntaxKind.ModuleDeclaration: + return declareModuleMember(node, symbolFlags, symbolExcludes); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - if (isObjectLiteralOrClassExpressionMethodOrAccessor(node)) { - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor; - } - // falls through - case SyntaxKind.Constructor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.JSDocSignature: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructSignature: - case SyntaxKind.ConstructorType: - case SyntaxKind.ClassStaticBlockDeclaration: - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike; - - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsFunctionExpression; - - case SyntaxKind.ModuleBlock: - return ContainerFlags.IsControlFlowContainer; - case SyntaxKind.PropertyDeclaration: - return (node as PropertyDeclaration).initializer ? ContainerFlags.IsControlFlowContainer : 0; - - case SyntaxKind.CatchClause: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.CaseBlock: - return ContainerFlags.IsBlockScopedContainer; - - case SyntaxKind.Block: - // do not treat blocks directly inside a function as a block-scoped-container. - // Locals that reside in this block should go to the function locals. Otherwise 'x' - // would not appear to be a redeclaration of a block scoped local in the following - // example: - // - // function foo() { - // var x; - // let x; - // } - // - // If we placed 'var x' into the function locals and 'let x' into the locals of - // the block, then there would be no collision. - // - // By not creating a new block-scoped-container here, we ensure that both 'var x' - // and 'let x' go into the Function-container's locals, and we do get a collision - // conflict. - return isFunctionLike(node.parent) || isClassStaticBlockDeclaration(node.parent) ? ContainerFlags.None : ContainerFlags.IsBlockScopedContainer; - } - - return ContainerFlags.None; - } - - function addToContainerChain(next: Node) { - if (lastContainer) { - lastContainer.nextContainer = next; - } - - lastContainer = next; - } - - function declareSymbolAndAddToSymbolTable(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol | undefined { - switch (container.kind) { - // Modules, source files, and classes need specialized handling for how their - // members are declared (for example, a member of a class will go into a specific - // symbol table depending on if it is static or not). We defer to specialized - // handlers to take care of declaring these child members. - case SyntaxKind.ModuleDeclaration: - return declareModuleMember(node, symbolFlags, symbolExcludes); - - case SyntaxKind.SourceFile: - return declareSourceFileMember(node, symbolFlags, symbolExcludes); - - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - return declareClassMember(node, symbolFlags, symbolExcludes); - - case SyntaxKind.EnumDeclaration: - return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); - - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.JsxAttributes: - // Interface/Object-types always have their children added to the 'members' of - // their container. They are only accessible through an instance of their - // container, and are never in scope otherwise (even inside the body of the - // object / type / interface declaring them). An exception is type parameters, - // which are in scope without qualification (similar to 'locals'). - return declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); - - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.JSDocSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.ClassStaticBlockDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.MappedType: - // All the children of these container types are never visible through another - // symbol (i.e. through another symbol's 'exports' or 'members'). Instead, - // they're only accessed 'lexically' (i.e. from code that exists underneath - // their container in the tree). To accomplish this, we simply add their declared - // symbol to the 'locals' of the container. These symbols can then be found as - // the type checker walks up the containers, checking them for matching names. - return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } - } - - function declareClassMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - return isStatic(node) - ? declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes) - : declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); - } - - function declareSourceFileMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - return isExternalModule(file) - ? declareModuleMember(node, symbolFlags, symbolExcludes) - : declareSymbol(file.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); - } - - function hasExportDeclarations(node: ModuleDeclaration | SourceFile): boolean { - const body = isSourceFile(node) ? node : tryCast(node.body, isModuleBlock); - return !!body && body.statements.some(s => isExportDeclaration(s) || isExportAssignment(s)); - } - - function setExportContextFlag(node: Mutable) { - // A declaration source file or ambient module declaration that contains no export declarations (but possibly regular - // declarations with export modifiers) is an export context in which declarations are implicitly exported. - if (node.flags & NodeFlags.Ambient && !hasExportDeclarations(node)) { - node.flags |= NodeFlags.ExportContext; - } - else { - node.flags &= ~NodeFlags.ExportContext; - } + case SyntaxKind.SourceFile: + return declareSourceFileMember(node, symbolFlags, symbolExcludes); + + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + return declareClassMember(node, symbolFlags, symbolExcludes); + + case SyntaxKind.EnumDeclaration: + return declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes); + + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.JsxAttributes: + // Interface/Object-types always have their children added to the 'members' of + // their container. They are only accessible through an instance of their + // container, and are never in scope otherwise (even inside the body of the + // object / type / interface declaring them). An exception is type parameters, + // which are in scope without qualification (similar to 'locals'). + return declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); + + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.JSDocSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.ClassStaticBlockDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.MappedType: + // All the children of these container types are never visible through another + // symbol (i.e. through another symbol's 'exports' or 'members'). Instead, + // they're only accessed 'lexically' (i.e. from code that exists underneath + // their container in the tree). To accomplish this, we simply add their declared + // symbol to the 'locals' of the container. These symbols can then be found as + // the type checker walks up the containers, checking them for matching names. + return declareSymbol(container.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } + } - function bindModuleDeclaration(node: ModuleDeclaration) { - setExportContextFlag(node); - if (isAmbientModule(node)) { - if (hasSyntacticModifier(node, ModifierFlags.Export)) { - errorOnFirstToken(node, Diagnostics.export_modifier_cannot_be_applied_to_ambient_modules_and_module_augmentations_since_they_are_always_visible); - } - if (isModuleAugmentationExternal(node)) { - declareModuleSymbol(node); - } - else { - let pattern: string | Pattern | undefined; - if (node.name.kind === SyntaxKind.StringLiteral) { - const { text } = node.name; - pattern = tryParsePattern(text); - if (pattern === undefined) { - errorOnFirstToken(node.name, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, text); - } - } + function declareClassMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + return isStatic(node) + ? declareSymbol(container.symbol.exports!, container.symbol, node, symbolFlags, symbolExcludes) + : declareSymbol(container.symbol.members!, container.symbol, node, symbolFlags, symbolExcludes); + } - const symbol = declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes)!; - file.patternAmbientModules = append(file.patternAmbientModules, pattern && !isString(pattern) ? { pattern, symbol } : undefined); - } + function declareSourceFileMember(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + return isExternalModule(file) + ? declareModuleMember(node, symbolFlags, symbolExcludes) + : declareSymbol(file.locals!, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + } + + function hasExportDeclarations(node: ModuleDeclaration | SourceFile): boolean { + const body = isSourceFile(node) ? node : tryCast(node.body, isModuleBlock); + return !!body && body.statements.some(s => isExportDeclaration(s) || isExportAssignment(s)); + } + + function setExportContextFlag(node: Mutable) { + // A declaration source file or ambient module declaration that contains no export declarations (but possibly regular + // declarations with export modifiers) is an export context in which declarations are implicitly exported. + if (node.flags & NodeFlags.Ambient && !hasExportDeclarations(node)) { + node.flags |= NodeFlags.ExportContext; + } + else { + node.flags &= ~NodeFlags.ExportContext; + } + } + + function bindModuleDeclaration(node: ModuleDeclaration) { + setExportContextFlag(node); + if (isAmbientModule(node)) { + if (hasSyntacticModifier(node, ModifierFlags.Export)) { + errorOnFirstToken(node, Diagnostics.export_modifier_cannot_be_applied_to_ambient_modules_and_module_augmentations_since_they_are_always_visible); + } + if (isModuleAugmentationExternal(node)) { + declareModuleSymbol(node); } else { - const state = declareModuleSymbol(node); - if (state !== ModuleInstanceState.NonInstantiated) { - const { symbol } = node; - // if module was already merged with some function, class or non-const enum, treat it as non-const-enum-only - symbol.constEnumOnlyModule = (!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) - // Current must be `const enum` only - && state === ModuleInstanceState.ConstEnumOnly - // Can't have been set to 'false' in a previous merged symbol. ('undefined' OK) - && symbol.constEnumOnlyModule !== false; + let pattern: string | Pattern | undefined; + if (node.name.kind === SyntaxKind.StringLiteral) { + const { text } = node.name; + pattern = tryParsePattern(text); + if (pattern === undefined) { + errorOnFirstToken(node.name, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, text); + } } + + const symbol = declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes)!; + file.patternAmbientModules = append(file.patternAmbientModules, pattern && !isString(pattern) ? { pattern, symbol } : undefined); } } - - function declareModuleSymbol(node: ModuleDeclaration): ModuleInstanceState { - const state = getModuleInstanceState(node); - const instantiated = state !== ModuleInstanceState.NonInstantiated; - declareSymbolAndAddToSymbolTable(node, - instantiated ? SymbolFlags.ValueModule : SymbolFlags.NamespaceModule, - instantiated ? SymbolFlags.ValueModuleExcludes : SymbolFlags.NamespaceModuleExcludes); - return state; + else { + const state = declareModuleSymbol(node); + if (state !== ModuleInstanceState.NonInstantiated) { + const { symbol } = node; + // if module was already merged with some function, class or non-const enum, treat it as non-const-enum-only + symbol.constEnumOnlyModule = (!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.RegularEnum))) + // Current must be `const enum` only + && state === ModuleInstanceState.ConstEnumOnly + // Can't have been set to 'false' in a previous merged symbol. ('undefined' OK) + && symbol.constEnumOnlyModule !== false; + } } + } - function bindFunctionOrConstructorType(node: SignatureDeclaration | JSDocSignature): void { - // For a given function symbol "<...>(...) => T" we want to generate a symbol identical - // to the one we would get for: { <...>(...): T } - // - // We do that by making an anonymous type literal symbol, and then setting the function - // symbol as its sole member. To the rest of the system, this symbol will be indistinguishable - // from an actual type literal symbol you would have gotten had you used the long form. - const symbol = createSymbol(SymbolFlags.Signature, getDeclarationName(node)!); // TODO: GH#18217 - addDeclarationToSymbol(symbol, node, SymbolFlags.Signature); + function declareModuleSymbol(node: ModuleDeclaration): ModuleInstanceState { + const state = getModuleInstanceState(node); + const instantiated = state !== ModuleInstanceState.NonInstantiated; + declareSymbolAndAddToSymbolTable(node, + instantiated ? SymbolFlags.ValueModule : SymbolFlags.NamespaceModule, + instantiated ? SymbolFlags.ValueModuleExcludes : SymbolFlags.NamespaceModuleExcludes); + return state; + } - const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); - typeLiteralSymbol.members = createSymbolTable(); - typeLiteralSymbol.members.set(symbol.escapedName, symbol); - } + function bindFunctionOrConstructorType(node: SignatureDeclaration | JSDocSignature): void { + // For a given function symbol "<...>(...) => T" we want to generate a symbol identical + // to the one we would get for: { <...>(...): T } + // + // We do that by making an anonymous type literal symbol, and then setting the function + // symbol as its sole member. To the rest of the system, this symbol will be indistinguishable + // from an actual type literal symbol you would have gotten had you used the long form. + const symbol = createSymbol(SymbolFlags.Signature, getDeclarationName(node)!); // TODO: GH#18217 + addDeclarationToSymbol(symbol, node, SymbolFlags.Signature); + + const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); + typeLiteralSymbol.members = createSymbolTable(); + typeLiteralSymbol.members.set(symbol.escapedName, symbol); + } - function bindObjectLiteralExpression(node: ObjectLiteralExpression) { - return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.Object); - } + function bindObjectLiteralExpression(node: ObjectLiteralExpression) { + return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.Object); + } - function bindJsxAttributes(node: JsxAttributes) { - return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.JSXAttributes); - } + function bindJsxAttributes(node: JsxAttributes) { + return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, InternalSymbolName.JSXAttributes); + } - function bindJsxAttribute(node: JsxAttribute, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - return declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); + function bindJsxAttribute(node: JsxAttribute, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + return declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); + } + + function bindAnonymousDeclaration(node: Declaration, symbolFlags: SymbolFlags, name: __String) { + const symbol = createSymbol(symbolFlags, name); + if (symbolFlags & (SymbolFlags.EnumMember | SymbolFlags.ClassMember)) { + symbol.parent = container.symbol; } + addDeclarationToSymbol(symbol, node, symbolFlags); + return symbol; + } - function bindAnonymousDeclaration(node: Declaration, symbolFlags: SymbolFlags, name: __String) { - const symbol = createSymbol(symbolFlags, name); - if (symbolFlags & (SymbolFlags.EnumMember | SymbolFlags.ClassMember)) { - symbol.parent = container.symbol; - } - addDeclarationToSymbol(symbol, node, symbolFlags); - return symbol; + function bindBlockScopedDeclaration(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + switch (blockScopeContainer.kind) { + case SyntaxKind.ModuleDeclaration: + declareModuleMember(node, symbolFlags, symbolExcludes); + break; + case SyntaxKind.SourceFile: + if (isExternalOrCommonJsModule(container as SourceFile)) { + declareModuleMember(node, symbolFlags, symbolExcludes); + break; + } + // falls through + default: + if (!blockScopeContainer.locals) { + blockScopeContainer.locals = createSymbolTable(); + addToContainerChain(blockScopeContainer); + } + declareSymbol(blockScopeContainer.locals, /*parent*/ undefined, node, symbolFlags, symbolExcludes); } + } - function bindBlockScopedDeclaration(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - switch (blockScopeContainer.kind) { - case SyntaxKind.ModuleDeclaration: - declareModuleMember(node, symbolFlags, symbolExcludes); - break; - case SyntaxKind.SourceFile: - if (isExternalOrCommonJsModule(container as SourceFile)) { - declareModuleMember(node, symbolFlags, symbolExcludes); - break; + function delayedBindJSDocTypedefTag() { + if (!delayedTypeAliases) { + return; + } + const saveContainer = container; + const saveLastContainer = lastContainer; + const saveBlockScopeContainer = blockScopeContainer; + const saveParent = parent; + const saveCurrentFlow = currentFlow; + for (const typeAlias of delayedTypeAliases) { + const host = typeAlias.parent.parent; + container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; + blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; + currentFlow = initFlowNode({ flags: FlowFlags.Start }); + parent = typeAlias; + bind(typeAlias.typeExpression); + const declName = getNameOfDeclaration(typeAlias); + if ((isJSDocEnumTag(typeAlias) || !typeAlias.fullName) && declName && isPropertyAccessEntityNameExpression(declName.parent)) { + // typedef anchored to an A.B.C assignment - we need to bind into B's namespace under name C + const isTopLevel = isTopLevelNamespaceAssignment(declName.parent); + if (isTopLevel) { + bindPotentiallyMissingNamespaces(file.symbol, declName.parent, isTopLevel, + !!findAncestor(declName, d => isPropertyAccessExpression(d) && d.name.escapedText === "prototype"), /*containerIsClass*/ false); + const oldContainer = container; + switch (getAssignmentDeclarationPropertyAccessKind(declName.parent)) { + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.ModuleExports: + if (!isExternalOrCommonJsModule(file)) { + container = undefined!; + } + else { + container = file; + } + break; + case AssignmentDeclarationKind.ThisProperty: + container = declName.parent.expression; + break; + case AssignmentDeclarationKind.PrototypeProperty: + container = (declName.parent.expression as PropertyAccessExpression).name; + break; + case AssignmentDeclarationKind.Property: + container = isExportsOrModuleExportsOrAlias(file, declName.parent.expression) ? file + : isPropertyAccessExpression(declName.parent.expression) ? declName.parent.expression.name + : declName.parent.expression; + break; + case AssignmentDeclarationKind.None: + return Debug.fail("Shouldn't have detected typedef or enum on non-assignment declaration"); } - // falls through - default: - if (!blockScopeContainer.locals) { - blockScopeContainer.locals = createSymbolTable(); - addToContainerChain(blockScopeContainer); + if (container) { + declareModuleMember(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); } - declareSymbol(blockScopeContainer.locals, /*parent*/ undefined, node, symbolFlags, symbolExcludes); + container = oldContainer; + } } - } - - function delayedBindJSDocTypedefTag() { - if (!delayedTypeAliases) { - return; + else if (isJSDocEnumTag(typeAlias) || !typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) { + parent = typeAlias.parent; + bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); } - const saveContainer = container; - const saveLastContainer = lastContainer; - const saveBlockScopeContainer = blockScopeContainer; - const saveParent = parent; - const saveCurrentFlow = currentFlow; - for (const typeAlias of delayedTypeAliases) { - const host = typeAlias.parent.parent; - container = findAncestor(host.parent, n => !!(getContainerFlags(n) & ContainerFlags.IsContainer)) || file; - blockScopeContainer = getEnclosingBlockScopeContainer(host) || file; - currentFlow = initFlowNode({ flags: FlowFlags.Start }); - parent = typeAlias; - bind(typeAlias.typeExpression); - const declName = getNameOfDeclaration(typeAlias); - if ((isJSDocEnumTag(typeAlias) || !typeAlias.fullName) && declName && isPropertyAccessEntityNameExpression(declName.parent)) { - // typedef anchored to an A.B.C assignment - we need to bind into B's namespace under name C - const isTopLevel = isTopLevelNamespaceAssignment(declName.parent); - if (isTopLevel) { - bindPotentiallyMissingNamespaces(file.symbol, declName.parent, isTopLevel, - !!findAncestor(declName, d => isPropertyAccessExpression(d) && d.name.escapedText === "prototype"), /*containerIsClass*/ false); - const oldContainer = container; - switch (getAssignmentDeclarationPropertyAccessKind(declName.parent)) { - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.ModuleExports: - if (!isExternalOrCommonJsModule(file)) { - container = undefined!; - } - else { - container = file; - } - break; - case AssignmentDeclarationKind.ThisProperty: - container = declName.parent.expression; - break; - case AssignmentDeclarationKind.PrototypeProperty: - container = (declName.parent.expression as PropertyAccessExpression).name; - break; - case AssignmentDeclarationKind.Property: - container = isExportsOrModuleExportsOrAlias(file, declName.parent.expression) ? file - : isPropertyAccessExpression(declName.parent.expression) ? declName.parent.expression.name - : declName.parent.expression; - break; - case AssignmentDeclarationKind.None: - return Debug.fail("Shouldn't have detected typedef or enum on non-assignment declaration"); - } - if (container) { - declareModuleMember(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - } - container = oldContainer; - } - } - else if (isJSDocEnumTag(typeAlias) || !typeAlias.fullName || typeAlias.fullName.kind === SyntaxKind.Identifier) { - parent = typeAlias.parent; - bindBlockScopedDeclaration(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - } - else { - bind(typeAlias.fullName); - } + else { + bind(typeAlias.fullName); } - container = saveContainer; - lastContainer = saveLastContainer; - blockScopeContainer = saveBlockScopeContainer; - parent = saveParent; - currentFlow = saveCurrentFlow; } + container = saveContainer; + lastContainer = saveLastContainer; + blockScopeContainer = saveBlockScopeContainer; + parent = saveParent; + currentFlow = saveCurrentFlow; + } - // The binder visits every node in the syntax tree so it is a convenient place to perform a single localized - // check for reserved words used as identifiers in strict mode code, as well as `yield` or `await` in - // [Yield] or [Await] contexts, respectively. - function checkContextualIdentifier(node: Identifier) { - // Report error only if there are no parse errors in file - if (!file.parseDiagnostics.length && - !(node.flags & NodeFlags.Ambient) && - !(node.flags & NodeFlags.JSDoc) && - !isIdentifierName(node)) { - - // strict mode identifiers - if (inStrictMode && - node.originalKeywordKind! >= SyntaxKind.FirstFutureReservedWord && - node.originalKeywordKind! <= SyntaxKind.LastFutureReservedWord) { + // The binder visits every node in the syntax tree so it is a convenient place to perform a single localized + // check for reserved words used as identifiers in strict mode code, as well as `yield` or `await` in + // [Yield] or [Await] contexts, respectively. + function checkContextualIdentifier(node: Identifier) { + // Report error only if there are no parse errors in file + if (!file.parseDiagnostics.length && + !(node.flags & NodeFlags.Ambient) && + !(node.flags & NodeFlags.JSDoc) && + !isIdentifierName(node)) { + + // strict mode identifiers + if (inStrictMode && + node.originalKeywordKind! >= SyntaxKind.FirstFutureReservedWord && + node.originalKeywordKind! <= SyntaxKind.LastFutureReservedWord) { + file.bindDiagnostics.push(createDiagnosticForNode(node, + getStrictModeIdentifierMessage(node), declarationNameToString(node))); + } + else if (node.originalKeywordKind === SyntaxKind.AwaitKeyword) { + if (isExternalModule(file) && isInTopLevelContext(node)) { file.bindDiagnostics.push(createDiagnosticForNode(node, - getStrictModeIdentifierMessage(node), declarationNameToString(node))); - } - else if (node.originalKeywordKind === SyntaxKind.AwaitKeyword) { - if (isExternalModule(file) && isInTopLevelContext(node)) { - file.bindDiagnostics.push(createDiagnosticForNode(node, - Diagnostics.Identifier_expected_0_is_a_reserved_word_at_the_top_level_of_a_module, - declarationNameToString(node))); - } - else if (node.flags & NodeFlags.AwaitContext) { - file.bindDiagnostics.push(createDiagnosticForNode(node, - Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here, - declarationNameToString(node))); - } + Diagnostics.Identifier_expected_0_is_a_reserved_word_at_the_top_level_of_a_module, + declarationNameToString(node))); } - else if (node.originalKeywordKind === SyntaxKind.YieldKeyword && node.flags & NodeFlags.YieldContext) { + else if (node.flags & NodeFlags.AwaitContext) { file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here, declarationNameToString(node))); } } - } - - function getStrictModeIdentifierMessage(node: Node) { - // Provide specialized messages to help the user understand why we think they're in - // strict mode. - if (getContainingClass(node)) { - return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Class_definitions_are_automatically_in_strict_mode; + else if (node.originalKeywordKind === SyntaxKind.YieldKeyword && node.flags & NodeFlags.YieldContext) { + file.bindDiagnostics.push(createDiagnosticForNode(node, + Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here, + declarationNameToString(node))); } + } + } - if (file.externalModuleIndicator) { - return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode; - } + function getStrictModeIdentifierMessage(node: Node) { + // Provide specialized messages to help the user understand why we think they're in + // strict mode. + if (getContainingClass(node)) { + return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Class_definitions_are_automatically_in_strict_mode; + } - return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode; + if (file.externalModuleIndicator) { + return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode; } - // The binder visits every node, so this is a good place to check for - // the reserved private name (there is only one) - function checkPrivateIdentifier(node: PrivateIdentifier) { - if (node.escapedText === "#constructor") { - // Report error only if there are no parse errors in file - if (!file.parseDiagnostics.length) { - file.bindDiagnostics.push(createDiagnosticForNode(node, - Diagnostics.constructor_is_a_reserved_word, declarationNameToString(node))); - } + return Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode; + } + + // The binder visits every node, so this is a good place to check for + // the reserved private name (there is only one) + function checkPrivateIdentifier(node: PrivateIdentifier) { + if (node.escapedText === "#constructor") { + // Report error only if there are no parse errors in file + if (!file.parseDiagnostics.length) { + file.bindDiagnostics.push(createDiagnosticForNode(node, + Diagnostics.constructor_is_a_reserved_word, declarationNameToString(node))); } } + } - function checkStrictModeBinaryExpression(node: BinaryExpression) { - if (inStrictMode && isLeftHandSideExpression(node.left) && isAssignmentOperator(node.operatorToken.kind)) { - // ECMA 262 (Annex C) The identifier eval or arguments may not appear as the LeftHandSideExpression of an - // Assignment operator(11.13) or of a PostfixExpression(11.3) - checkStrictModeEvalOrArguments(node, node.left as Identifier); - } + function checkStrictModeBinaryExpression(node: BinaryExpression) { + if (inStrictMode && isLeftHandSideExpression(node.left) && isAssignmentOperator(node.operatorToken.kind)) { + // ECMA 262 (Annex C) The identifier eval or arguments may not appear as the LeftHandSideExpression of an + // Assignment operator(11.13) or of a PostfixExpression(11.3) + checkStrictModeEvalOrArguments(node, node.left as Identifier); } + } - function checkStrictModeCatchClause(node: CatchClause) { - // It is a SyntaxError if a TryStatement with a Catch occurs within strict code and the Identifier of the - // Catch production is eval or arguments - if (inStrictMode && node.variableDeclaration) { - checkStrictModeEvalOrArguments(node, node.variableDeclaration.name); - } + function checkStrictModeCatchClause(node: CatchClause) { + // It is a SyntaxError if a TryStatement with a Catch occurs within strict code and the Identifier of the + // Catch production is eval or arguments + if (inStrictMode && node.variableDeclaration) { + checkStrictModeEvalOrArguments(node, node.variableDeclaration.name); + } + } + + function checkStrictModeDeleteExpression(node: DeleteExpression) { + // Grammar checking + if (inStrictMode && node.expression.kind === SyntaxKind.Identifier) { + // When a delete operator occurs within strict mode code, a SyntaxError is thrown if its + // UnaryExpression is a direct reference to a variable, function argument, or function name + const span = getErrorSpanForNode(file, node.expression); + file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, Diagnostics.delete_cannot_be_called_on_an_identifier_in_strict_mode)); } + } + + function isEvalOrArgumentsIdentifier(node: Node): boolean { + return isIdentifier(node) && (node.escapedText === "eval" || node.escapedText === "arguments"); + } - function checkStrictModeDeleteExpression(node: DeleteExpression) { - // Grammar checking - if (inStrictMode && node.expression.kind === SyntaxKind.Identifier) { - // When a delete operator occurs within strict mode code, a SyntaxError is thrown if its - // UnaryExpression is a direct reference to a variable, function argument, or function name - const span = getErrorSpanForNode(file, node.expression); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, Diagnostics.delete_cannot_be_called_on_an_identifier_in_strict_mode)); + function checkStrictModeEvalOrArguments(contextNode: Node, name: Node | undefined) { + if (name && name.kind === SyntaxKind.Identifier) { + const identifier = name as Identifier; + if (isEvalOrArgumentsIdentifier(identifier)) { + // We check first if the name is inside class declaration or class expression; if so give explicit message + // otherwise report generic error message. + const span = getErrorSpanForNode(file, name); + file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, + getStrictModeEvalOrArgumentsMessage(contextNode), idText(identifier))); } } + } - function isEvalOrArgumentsIdentifier(node: Node): boolean { - return isIdentifier(node) && (node.escapedText === "eval" || node.escapedText === "arguments"); + function getStrictModeEvalOrArgumentsMessage(node: Node) { + // Provide specialized messages to help the user understand why we think they're in + // strict mode. + if (getContainingClass(node)) { + return Diagnostics.Code_contained_in_a_class_is_evaluated_in_JavaScript_s_strict_mode_which_does_not_allow_this_use_of_0_For_more_information_see_https_Colon_Slash_Slashdeveloper_mozilla_org_Slashen_US_Slashdocs_SlashWeb_SlashJavaScript_SlashReference_SlashStrict_mode; } - function checkStrictModeEvalOrArguments(contextNode: Node, name: Node | undefined) { - if (name && name.kind === SyntaxKind.Identifier) { - const identifier = name as Identifier; - if (isEvalOrArgumentsIdentifier(identifier)) { - // We check first if the name is inside class declaration or class expression; if so give explicit message - // otherwise report generic error message. - const span = getErrorSpanForNode(file, name); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, - getStrictModeEvalOrArgumentsMessage(contextNode), idText(identifier))); - } - } + if (file.externalModuleIndicator) { + return Diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode; } - function getStrictModeEvalOrArgumentsMessage(node: Node) { - // Provide specialized messages to help the user understand why we think they're in - // strict mode. - if (getContainingClass(node)) { - return Diagnostics.Code_contained_in_a_class_is_evaluated_in_JavaScript_s_strict_mode_which_does_not_allow_this_use_of_0_For_more_information_see_https_Colon_Slash_Slashdeveloper_mozilla_org_Slashen_US_Slashdocs_SlashWeb_SlashJavaScript_SlashReference_SlashStrict_mode; - } + return Diagnostics.Invalid_use_of_0_in_strict_mode; + } - if (file.externalModuleIndicator) { - return Diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode; - } + function checkStrictModeFunctionName(node: FunctionLikeDeclaration) { + if (inStrictMode) { + // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a strict mode FunctionDeclaration or FunctionExpression (13.1)) + checkStrictModeEvalOrArguments(node, node.name); + } + } - return Diagnostics.Invalid_use_of_0_in_strict_mode; + function getStrictModeBlockScopeFunctionDeclarationMessage(node: Node) { + // Provide specialized messages to help the user understand why we think they're in + // strict mode. + if (getContainingClass(node)) { + return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Class_definitions_are_automatically_in_strict_mode; } - function checkStrictModeFunctionName(node: FunctionLikeDeclaration) { - if (inStrictMode) { - // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a strict mode FunctionDeclaration or FunctionExpression (13.1)) - checkStrictModeEvalOrArguments(node, node.name); - } + if (file.externalModuleIndicator) { + return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Modules_are_automatically_in_strict_mode; } - function getStrictModeBlockScopeFunctionDeclarationMessage(node: Node) { - // Provide specialized messages to help the user understand why we think they're in - // strict mode. - if (getContainingClass(node)) { - return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Class_definitions_are_automatically_in_strict_mode; - } + return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5; + } - if (file.externalModuleIndicator) { - return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5_Modules_are_automatically_in_strict_mode; + function checkStrictModeFunctionDeclaration(node: FunctionDeclaration) { + if (languageVersion < ScriptTarget.ES2015) { + // Report error if function is not top level function declaration + if (blockScopeContainer.kind !== SyntaxKind.SourceFile && + blockScopeContainer.kind !== SyntaxKind.ModuleDeclaration && + !isFunctionLikeOrClassStaticBlockDeclaration(blockScopeContainer)) { + // We check first if the name is inside class declaration or class expression; if so give explicit message + // otherwise report generic error message. + const errorSpan = getErrorSpanForNode(file, node); + file.bindDiagnostics.push(createFileDiagnostic(file, errorSpan.start, errorSpan.length, + getStrictModeBlockScopeFunctionDeclarationMessage(node))); } - - return Diagnostics.Function_declarations_are_not_allowed_inside_blocks_in_strict_mode_when_targeting_ES3_or_ES5; } + } - function checkStrictModeFunctionDeclaration(node: FunctionDeclaration) { - if (languageVersion < ScriptTarget.ES2015) { - // Report error if function is not top level function declaration - if (blockScopeContainer.kind !== SyntaxKind.SourceFile && - blockScopeContainer.kind !== SyntaxKind.ModuleDeclaration && - !isFunctionLikeOrClassStaticBlockDeclaration(blockScopeContainer)) { - // We check first if the name is inside class declaration or class expression; if so give explicit message - // otherwise report generic error message. - const errorSpan = getErrorSpanForNode(file, node); - file.bindDiagnostics.push(createFileDiagnostic(file, errorSpan.start, errorSpan.length, - getStrictModeBlockScopeFunctionDeclarationMessage(node))); - } - } + function checkStrictModeNumericLiteral(node: NumericLiteral) { + if (languageVersion < ScriptTarget.ES5 && inStrictMode && node.numericLiteralFlags & TokenFlags.Octal) { + file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Octal_literals_are_not_allowed_in_strict_mode)); } + } - function checkStrictModeNumericLiteral(node: NumericLiteral) { - if (languageVersion < ScriptTarget.ES5 && inStrictMode && node.numericLiteralFlags & TokenFlags.Octal) { - file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Octal_literals_are_not_allowed_in_strict_mode)); - } + function checkStrictModePostfixUnaryExpression(node: PostfixUnaryExpression) { + // Grammar checking + // The identifier eval or arguments may not appear as the LeftHandSideExpression of an + // Assignment operator(11.13) or of a PostfixExpression(11.3) or as the UnaryExpression + // operated upon by a Prefix Increment(11.4.4) or a Prefix Decrement(11.4.5) operator. + if (inStrictMode) { + checkStrictModeEvalOrArguments(node, node.operand as Identifier); } + } - function checkStrictModePostfixUnaryExpression(node: PostfixUnaryExpression) { - // Grammar checking - // The identifier eval or arguments may not appear as the LeftHandSideExpression of an - // Assignment operator(11.13) or of a PostfixExpression(11.3) or as the UnaryExpression - // operated upon by a Prefix Increment(11.4.4) or a Prefix Decrement(11.4.5) operator. - if (inStrictMode) { + function checkStrictModePrefixUnaryExpression(node: PrefixUnaryExpression) { + // Grammar checking + if (inStrictMode) { + if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { checkStrictModeEvalOrArguments(node, node.operand as Identifier); } } + } - function checkStrictModePrefixUnaryExpression(node: PrefixUnaryExpression) { - // Grammar checking - if (inStrictMode) { - if (node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken) { - checkStrictModeEvalOrArguments(node, node.operand as Identifier); - } - } + function checkStrictModeWithStatement(node: WithStatement) { + // Grammar checking for withStatement + if (inStrictMode) { + errorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_strict_mode); } + } - function checkStrictModeWithStatement(node: WithStatement) { - // Grammar checking for withStatement - if (inStrictMode) { - errorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_strict_mode); + function checkStrictModeLabeledStatement(node: LabeledStatement) { + // Grammar checking for labeledStatement + if (inStrictMode && getEmitScriptTarget(options) >= ScriptTarget.ES2015) { + if (isDeclarationStatement(node.statement) || isVariableStatement(node.statement)) { + errorOnFirstToken(node.label, Diagnostics.A_label_is_not_allowed_here); } } + } - function checkStrictModeLabeledStatement(node: LabeledStatement) { - // Grammar checking for labeledStatement - if (inStrictMode && getEmitScriptTarget(options) >= ScriptTarget.ES2015) { - if (isDeclarationStatement(node.statement) || isVariableStatement(node.statement)) { - errorOnFirstToken(node.label, Diagnostics.A_label_is_not_allowed_here); - } - } - } + function errorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any) { + const span = getSpanOfTokenAtPosition(file, node.pos); + file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, message, arg0, arg1, arg2)); + } - function errorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any) { - const span = getSpanOfTokenAtPosition(file, node.pos); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, message, arg0, arg1, arg2)); - } + function errorOrSuggestionOnNode(isError: boolean, node: Node, message: DiagnosticMessage): void { + errorOrSuggestionOnRange(isError, node, node, message); + } - function errorOrSuggestionOnNode(isError: boolean, node: Node, message: DiagnosticMessage): void { - errorOrSuggestionOnRange(isError, node, node, message); - } + function errorOrSuggestionOnRange(isError: boolean, startNode: Node, endNode: Node, message: DiagnosticMessage): void { + addErrorOrSuggestionDiagnostic(isError, { pos: getTokenPosOfNode(startNode, file), end: endNode.end }, message); + } - function errorOrSuggestionOnRange(isError: boolean, startNode: Node, endNode: Node, message: DiagnosticMessage): void { - addErrorOrSuggestionDiagnostic(isError, { pos: getTokenPosOfNode(startNode, file), end: endNode.end }, message); + function addErrorOrSuggestionDiagnostic(isError: boolean, range: TextRange, message: DiagnosticMessage): void { + const diag = createFileDiagnostic(file, range.pos, range.end - range.pos, message); + if (isError) { + file.bindDiagnostics.push(diag); } - - function addErrorOrSuggestionDiagnostic(isError: boolean, range: TextRange, message: DiagnosticMessage): void { - const diag = createFileDiagnostic(file, range.pos, range.end - range.pos, message); - if (isError) { - file.bindDiagnostics.push(diag); - } - else { - file.bindSuggestionDiagnostics = append(file.bindSuggestionDiagnostics, { ...diag, category: DiagnosticCategory.Suggestion }); - } + else { + file.bindSuggestionDiagnostics = append(file.bindSuggestionDiagnostics, { ...diag, category: DiagnosticCategory.Suggestion }); } + } - function bind(node: Node | undefined): void { - if (!node) { - return; - } - setParent(node, parent); - if (tracing) (node as TracingNode).tracingPath = file.path; - const saveInStrictMode = inStrictMode; + function bind(node: Node | undefined): void { + if (!node) { + return; + } + setParent(node, parent); + if (tracing) (node as TracingNode).tracingPath = file.path; + const saveInStrictMode = inStrictMode; - // Even though in the AST the jsdoc @typedef node belongs to the current node, - // its symbol might be in the same scope with the current node's symbol. Consider: - // - // /** @typedef {string | number} MyType */ - // function foo(); - // - // Here the current node is "foo", which is a container, but the scope of "MyType" should - // not be inside "foo". Therefore we always bind @typedef before bind the parent node, - // and skip binding this tag later when binding all the other jsdoc tags. + // Even though in the AST the jsdoc @typedef node belongs to the current node, + // its symbol might be in the same scope with the current node's symbol. Consider: + // + // /** @typedef {string | number} MyType */ + // function foo(); + // + // Here the current node is "foo", which is a container, but the scope of "MyType" should + // not be inside "foo". Therefore we always bind @typedef before bind the parent node, + // and skip binding this tag later when binding all the other jsdoc tags. - // First we bind declaration nodes to a symbol if possible. We'll both create a symbol - // and then potentially add the symbol to an appropriate symbol table. Possible - // destination symbol tables are: - // - // 1) The 'exports' table of the current container's symbol. - // 2) The 'members' table of the current container's symbol. - // 3) The 'locals' table of the current container. - // - // However, not all symbols will end up in any of these tables. 'Anonymous' symbols - // (like TypeLiterals for example) will not be put in any table. - bindWorker(node); - // Then we recurse into the children of the node to bind them as well. For certain - // symbols we do specialized work when we recurse. For example, we'll keep track of - // the current 'container' node when it changes. This helps us know which symbol table - // a local should go into for example. Since terminal nodes are known not to have - // children, as an optimization we don't process those. - if (node.kind > SyntaxKind.LastToken) { - const saveParent = parent; - parent = node; - const containerFlags = getContainerFlags(node); - if (containerFlags === ContainerFlags.None) { - bindChildren(node); - } - else { - bindContainer(node, containerFlags); - } - parent = saveParent; + // First we bind declaration nodes to a symbol if possible. We'll both create a symbol + // and then potentially add the symbol to an appropriate symbol table. Possible + // destination symbol tables are: + // + // 1) The 'exports' table of the current container's symbol. + // 2) The 'members' table of the current container's symbol. + // 3) The 'locals' table of the current container. + // + // However, not all symbols will end up in any of these tables. 'Anonymous' symbols + // (like TypeLiterals for example) will not be put in any table. + bindWorker(node); + // Then we recurse into the children of the node to bind them as well. For certain + // symbols we do specialized work when we recurse. For example, we'll keep track of + // the current 'container' node when it changes. This helps us know which symbol table + // a local should go into for example. Since terminal nodes are known not to have + // children, as an optimization we don't process those. + if (node.kind > SyntaxKind.LastToken) { + const saveParent = parent; + parent = node; + const containerFlags = getContainerFlags(node); + if (containerFlags === ContainerFlags.None) { + bindChildren(node); } else { - const saveParent = parent; - if (node.kind === SyntaxKind.EndOfFileToken) parent = node; - bindJSDoc(node); - parent = saveParent; + bindContainer(node, containerFlags); } - inStrictMode = saveInStrictMode; + parent = saveParent; + } + else { + const saveParent = parent; + if (node.kind === SyntaxKind.EndOfFileToken) parent = node; + bindJSDoc(node); + parent = saveParent; } + inStrictMode = saveInStrictMode; + } - function bindJSDoc(node: Node) { - if (hasJSDocNodes(node)) { - if (isInJSFile(node)) { - for (const j of node.jsDoc!) { - bind(j); - } + function bindJSDoc(node: Node) { + if (hasJSDocNodes(node)) { + if (isInJSFile(node)) { + for (const j of node.jsDoc!) { + bind(j); } - else { - for (const j of node.jsDoc!) { - setParent(j, node); - setParentRecursive(j, /*incremental*/ false); - } + } + else { + for (const j of node.jsDoc!) { + setParent(j, node); + setParentRecursive(j, /*incremental*/ false); } } } + } - function updateStrictModeStatementList(statements: NodeArray) { - if (!inStrictMode) { - for (const statement of statements) { - if (!isPrologueDirective(statement)) { - return; - } + function updateStrictModeStatementList(statements: NodeArray) { + if (!inStrictMode) { + for (const statement of statements) { + if (!isPrologueDirective(statement)) { + return; + } - if (isUseStrictPrologueDirective(statement as ExpressionStatement)) { - inStrictMode = true; - return; - } + if (isUseStrictPrologueDirective(statement as ExpressionStatement)) { + inStrictMode = true; + return; } } } + } - /// Should be called only on prologue directives (isPrologueDirective(node) should be true) - function isUseStrictPrologueDirective(node: ExpressionStatement): boolean { - const nodeText = getSourceTextOfNodeFromSourceFile(file, node.expression); + /// Should be called only on prologue directives (isPrologueDirective(node) should be true) + function isUseStrictPrologueDirective(node: ExpressionStatement): boolean { + const nodeText = getSourceTextOfNodeFromSourceFile(file, node.expression); - // Note: the node text must be exactly "use strict" or 'use strict'. It is not ok for the - // string to contain unicode escapes (as per ES5). - return nodeText === '"use strict"' || nodeText === "'use strict'"; - } + // Note: the node text must be exactly "use strict" or 'use strict'. It is not ok for the + // string to contain unicode escapes (as per ES5). + return nodeText === '"use strict"' || nodeText === "'use strict'"; + } - function bindWorker(node: Node) { - switch (node.kind) { - /* Strict mode checks */ - case SyntaxKind.Identifier: - // for typedef type names with namespaces, bind the new jsdoc type symbol here - // because it requires all containing namespaces to be in effect, namely the - // current "blockScopeContainer" needs to be set to its immediate namespace parent. - if ((node as Identifier).isInJSDocNamespace) { - let parentNode = node.parent; - while (parentNode && !isJSDocTypeAlias(parentNode)) { - parentNode = parentNode.parent; - } - bindBlockScopedDeclaration(parentNode as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - break; - } - // falls through - case SyntaxKind.ThisKeyword: - if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) { - node.flowNode = currentFlow; - } - return checkContextualIdentifier(node as Identifier); - case SyntaxKind.QualifiedName: - if (currentFlow && isPartOfTypeQuery(node)) { - node.flowNode = currentFlow; + function bindWorker(node: Node) { + switch (node.kind) { + /* Strict mode checks */ + case SyntaxKind.Identifier: + // for typedef type names with namespaces, bind the new jsdoc type symbol here + // because it requires all containing namespaces to be in effect, namely the + // current "blockScopeContainer" needs to be set to its immediate namespace parent. + if ((node as Identifier).isInJSDocNamespace) { + let parentNode = node.parent; + while (parentNode && !isJSDocTypeAlias(parentNode)) { + parentNode = parentNode.parent; } + bindBlockScopedDeclaration(parentNode as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); break; - case SyntaxKind.MetaProperty: - case SyntaxKind.SuperKeyword: + } + // falls through + case SyntaxKind.ThisKeyword: + if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) { node.flowNode = currentFlow; - break; - case SyntaxKind.PrivateIdentifier: - return checkPrivateIdentifier(node as PrivateIdentifier); - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - const expr = node as PropertyAccessExpression | ElementAccessExpression; - if (currentFlow && isNarrowableReference(expr)) { - expr.flowNode = currentFlow; - } - if (isSpecialPropertyDeclaration(expr)) { - bindSpecialPropertyDeclaration(expr); - } - if (isInJSFile(expr) && - file.commonJsModuleIndicator && - isModuleExportsAccessExpression(expr) && - !lookupSymbolForName(blockScopeContainer, "module" as __String)) { - declareSymbol(file.locals!, /*parent*/ undefined, expr.expression, - SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes); - } - break; - case SyntaxKind.BinaryExpression: - const specialKind = getAssignmentDeclarationKind(node as BinaryExpression); - switch (specialKind) { - case AssignmentDeclarationKind.ExportsProperty: - bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); - break; - case AssignmentDeclarationKind.ModuleExports: - bindModuleExportsAssignment(node as BindablePropertyAssignmentExpression); - break; - case AssignmentDeclarationKind.PrototypeProperty: - bindPrototypePropertyAssignment((node as BindableStaticPropertyAssignmentExpression).left, node); - break; - case AssignmentDeclarationKind.Prototype: - bindPrototypeAssignment(node as BindableStaticPropertyAssignmentExpression); - break; - case AssignmentDeclarationKind.ThisProperty: - bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression); - break; - case AssignmentDeclarationKind.Property: - const expression = ((node as BinaryExpression).left as AccessExpression).expression; - if (isInJSFile(node) && isIdentifier(expression)) { - const symbol = lookupSymbolForName(blockScopeContainer, expression.escapedText); - if (isThisInitializedDeclaration(symbol?.valueDeclaration)) { - bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression); - break; - } - } - bindSpecialPropertyAssignment(node as BindablePropertyAssignmentExpression); - break; - case AssignmentDeclarationKind.None: - // Nothing to do - break; - default: - Debug.fail("Unknown binary expression special property assignment kind"); - } - return checkStrictModeBinaryExpression(node as BinaryExpression); - case SyntaxKind.CatchClause: - return checkStrictModeCatchClause(node as CatchClause); - case SyntaxKind.DeleteExpression: - return checkStrictModeDeleteExpression(node as DeleteExpression); - case SyntaxKind.NumericLiteral: - return checkStrictModeNumericLiteral(node as NumericLiteral); - case SyntaxKind.PostfixUnaryExpression: - return checkStrictModePostfixUnaryExpression(node as PostfixUnaryExpression); - case SyntaxKind.PrefixUnaryExpression: - return checkStrictModePrefixUnaryExpression(node as PrefixUnaryExpression); - case SyntaxKind.WithStatement: - return checkStrictModeWithStatement(node as WithStatement); - case SyntaxKind.LabeledStatement: - return checkStrictModeLabeledStatement(node as LabeledStatement); - case SyntaxKind.ThisType: - seenThisKeyword = true; - return; - case SyntaxKind.TypePredicate: - break; // Binding the children will handle everything - case SyntaxKind.TypeParameter: - return bindTypeParameter(node as TypeParameterDeclaration); - case SyntaxKind.Parameter: - return bindParameter(node as ParameterDeclaration); - case SyntaxKind.VariableDeclaration: - return bindVariableDeclarationOrBindingElement(node as VariableDeclaration); - case SyntaxKind.BindingElement: + } + return checkContextualIdentifier(node as Identifier); + case SyntaxKind.QualifiedName: + if (currentFlow && isPartOfTypeQuery(node)) { node.flowNode = currentFlow; - return bindVariableDeclarationOrBindingElement(node as BindingElement); - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return bindPropertyWorker(node as PropertyDeclaration | PropertySignature); - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.Property, SymbolFlags.PropertyExcludes); - case SyntaxKind.EnumMember: - return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.EnumMember, SymbolFlags.EnumMemberExcludes); - - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Signature, SymbolFlags.None); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - // If this is an ObjectLiteralExpression method, then it sits in the same space - // as other properties in the object literal. So we use SymbolFlags.PropertyExcludes - // so that it will conflict with any other object literal members with the same - // name. - return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.Method | ((node as MethodDeclaration).questionToken ? SymbolFlags.Optional : SymbolFlags.None), - isObjectLiteralMethod(node) ? SymbolFlags.PropertyExcludes : SymbolFlags.MethodExcludes); - case SyntaxKind.FunctionDeclaration: - return bindFunctionDeclaration(node as FunctionDeclaration); - case SyntaxKind.Constructor: - return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Constructor, /*symbolExcludes:*/ SymbolFlags.None); - case SyntaxKind.GetAccessor: - return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.GetAccessor, SymbolFlags.GetAccessorExcludes); - case SyntaxKind.SetAccessor: - return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes); - case SyntaxKind.FunctionType: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.JSDocSignature: - case SyntaxKind.ConstructorType: - return bindFunctionOrConstructorType(node as SignatureDeclaration | JSDocSignature); - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.MappedType: - return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral); - case SyntaxKind.JSDocClassTag: - return bindJSDocClassTag(node as JSDocClassTag); - case SyntaxKind.ObjectLiteralExpression: - return bindObjectLiteralExpression(node as ObjectLiteralExpression); - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return bindFunctionExpression(node as FunctionExpression); - - case SyntaxKind.CallExpression: - const assignmentKind = getAssignmentDeclarationKind(node as CallExpression); - switch (assignmentKind) { - case AssignmentDeclarationKind.ObjectDefinePropertyValue: - return bindObjectDefinePropertyAssignment(node as BindableObjectDefinePropertyCall); - case AssignmentDeclarationKind.ObjectDefinePropertyExports: - return bindObjectDefinePropertyExport(node as BindableObjectDefinePropertyCall); - case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: - return bindObjectDefinePrototypeProperty(node as BindableObjectDefinePropertyCall); - case AssignmentDeclarationKind.None: - break; // Nothing to do - default: - return Debug.fail("Unknown call expression assignment declaration kind"); - } - if (isInJSFile(node)) { - bindCallExpression(node as CallExpression); - } - break; + } + break; + case SyntaxKind.MetaProperty: + case SyntaxKind.SuperKeyword: + node.flowNode = currentFlow; + break; + case SyntaxKind.PrivateIdentifier: + return checkPrivateIdentifier(node as PrivateIdentifier); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const expr = node as PropertyAccessExpression | ElementAccessExpression; + if (currentFlow && isNarrowableReference(expr)) { + expr.flowNode = currentFlow; + } + if (isSpecialPropertyDeclaration(expr)) { + bindSpecialPropertyDeclaration(expr); + } + if (isInJSFile(expr) && + file.commonJsModuleIndicator && + isModuleExportsAccessExpression(expr) && + !lookupSymbolForName(blockScopeContainer, "module" as __String)) { + declareSymbol(file.locals!, /*parent*/ undefined, expr.expression, + SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes); + } + break; + case SyntaxKind.BinaryExpression: + const specialKind = getAssignmentDeclarationKind(node as BinaryExpression); + switch (specialKind) { + case AssignmentDeclarationKind.ExportsProperty: + bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); + break; + case AssignmentDeclarationKind.ModuleExports: + bindModuleExportsAssignment(node as BindablePropertyAssignmentExpression); + break; + case AssignmentDeclarationKind.PrototypeProperty: + bindPrototypePropertyAssignment((node as BindableStaticPropertyAssignmentExpression).left, node); + break; + case AssignmentDeclarationKind.Prototype: + bindPrototypeAssignment(node as BindableStaticPropertyAssignmentExpression); + break; + case AssignmentDeclarationKind.ThisProperty: + bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression); + break; + case AssignmentDeclarationKind.Property: + const expression = ((node as BinaryExpression).left as AccessExpression).expression; + if (isInJSFile(node) && isIdentifier(expression)) { + const symbol = lookupSymbolForName(blockScopeContainer, expression.escapedText); + if (isThisInitializedDeclaration(symbol?.valueDeclaration)) { + bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression); + break; + } + } + bindSpecialPropertyAssignment(node as BindablePropertyAssignmentExpression); + break; + case AssignmentDeclarationKind.None: + // Nothing to do + break; + default: + Debug.fail("Unknown binary expression special property assignment kind"); + } + return checkStrictModeBinaryExpression(node as BinaryExpression); + case SyntaxKind.CatchClause: + return checkStrictModeCatchClause(node as CatchClause); + case SyntaxKind.DeleteExpression: + return checkStrictModeDeleteExpression(node as DeleteExpression); + case SyntaxKind.NumericLiteral: + return checkStrictModeNumericLiteral(node as NumericLiteral); + case SyntaxKind.PostfixUnaryExpression: + return checkStrictModePostfixUnaryExpression(node as PostfixUnaryExpression); + case SyntaxKind.PrefixUnaryExpression: + return checkStrictModePrefixUnaryExpression(node as PrefixUnaryExpression); + case SyntaxKind.WithStatement: + return checkStrictModeWithStatement(node as WithStatement); + case SyntaxKind.LabeledStatement: + return checkStrictModeLabeledStatement(node as LabeledStatement); + case SyntaxKind.ThisType: + seenThisKeyword = true; + return; + case SyntaxKind.TypePredicate: + break; // Binding the children will handle everything + case SyntaxKind.TypeParameter: + return bindTypeParameter(node as TypeParameterDeclaration); + case SyntaxKind.Parameter: + return bindParameter(node as ParameterDeclaration); + case SyntaxKind.VariableDeclaration: + return bindVariableDeclarationOrBindingElement(node as VariableDeclaration); + case SyntaxKind.BindingElement: + node.flowNode = currentFlow; + return bindVariableDeclarationOrBindingElement(node as BindingElement); + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return bindPropertyWorker(node as PropertyDeclaration | PropertySignature); + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.Property, SymbolFlags.PropertyExcludes); + case SyntaxKind.EnumMember: + return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.EnumMember, SymbolFlags.EnumMemberExcludes); + + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Signature, SymbolFlags.None); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + // If this is an ObjectLiteralExpression method, then it sits in the same space + // as other properties in the object literal. So we use SymbolFlags.PropertyExcludes + // so that it will conflict with any other object literal members with the same + // name. + return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.Method | ((node as MethodDeclaration).questionToken ? SymbolFlags.Optional : SymbolFlags.None), + isObjectLiteralMethod(node) ? SymbolFlags.PropertyExcludes : SymbolFlags.MethodExcludes); + case SyntaxKind.FunctionDeclaration: + return bindFunctionDeclaration(node as FunctionDeclaration); + case SyntaxKind.Constructor: + return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Constructor, /*symbolExcludes:*/ SymbolFlags.None); + case SyntaxKind.GetAccessor: + return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.GetAccessor, SymbolFlags.GetAccessorExcludes); + case SyntaxKind.SetAccessor: + return bindPropertyOrMethodOrAccessor(node as Declaration, SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes); + case SyntaxKind.FunctionType: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocSignature: + case SyntaxKind.ConstructorType: + return bindFunctionOrConstructorType(node as SignatureDeclaration | JSDocSignature); + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.MappedType: + return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral); + case SyntaxKind.JSDocClassTag: + return bindJSDocClassTag(node as JSDocClassTag); + case SyntaxKind.ObjectLiteralExpression: + return bindObjectLiteralExpression(node as ObjectLiteralExpression); + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return bindFunctionExpression(node as FunctionExpression); + + case SyntaxKind.CallExpression: + const assignmentKind = getAssignmentDeclarationKind(node as CallExpression); + switch (assignmentKind) { + case AssignmentDeclarationKind.ObjectDefinePropertyValue: + return bindObjectDefinePropertyAssignment(node as BindableObjectDefinePropertyCall); + case AssignmentDeclarationKind.ObjectDefinePropertyExports: + return bindObjectDefinePropertyExport(node as BindableObjectDefinePropertyCall); + case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: + return bindObjectDefinePrototypeProperty(node as BindableObjectDefinePropertyCall); + case AssignmentDeclarationKind.None: + break; // Nothing to do + default: + return Debug.fail("Unknown call expression assignment declaration kind"); + } + if (isInJSFile(node)) { + bindCallExpression(node as CallExpression); + } + break; - // Members of classes, interfaces, and modules - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - // All classes are automatically in strict mode in ES6. - inStrictMode = true; - return bindClassLikeDeclaration(node as ClassLikeDeclaration); - case SyntaxKind.InterfaceDeclaration: - return bindBlockScopedDeclaration(node as Declaration, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes); - case SyntaxKind.TypeAliasDeclaration: - return bindBlockScopedDeclaration(node as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); - case SyntaxKind.EnumDeclaration: - return bindEnumDeclaration(node as EnumDeclaration); - case SyntaxKind.ModuleDeclaration: - return bindModuleDeclaration(node as ModuleDeclaration); - // Jsx-attributes - case SyntaxKind.JsxAttributes: - return bindJsxAttributes(node as JsxAttributes); - case SyntaxKind.JsxAttribute: - return bindJsxAttribute(node as JsxAttribute, SymbolFlags.Property, SymbolFlags.PropertyExcludes); - - // Imports and exports - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - case SyntaxKind.NamespaceExportDeclaration: - return bindNamespaceExportDeclaration(node as NamespaceExportDeclaration); - case SyntaxKind.ImportClause: - return bindImportClause(node as ImportClause); - case SyntaxKind.ExportDeclaration: - return bindExportDeclaration(node as ExportDeclaration); - case SyntaxKind.ExportAssignment: - return bindExportAssignment(node as ExportAssignment); - case SyntaxKind.SourceFile: - updateStrictModeStatementList((node as SourceFile).statements); - return bindSourceFileIfExternalModule(); - case SyntaxKind.Block: - if (!isFunctionLikeOrClassStaticBlockDeclaration(node.parent)) { - return; - } - // falls through - case SyntaxKind.ModuleBlock: - return updateStrictModeStatementList((node as Block | ModuleBlock).statements); + // Members of classes, interfaces, and modules + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + // All classes are automatically in strict mode in ES6. + inStrictMode = true; + return bindClassLikeDeclaration(node as ClassLikeDeclaration); + case SyntaxKind.InterfaceDeclaration: + return bindBlockScopedDeclaration(node as Declaration, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes); + case SyntaxKind.TypeAliasDeclaration: + return bindBlockScopedDeclaration(node as Declaration, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + case SyntaxKind.EnumDeclaration: + return bindEnumDeclaration(node as EnumDeclaration); + case SyntaxKind.ModuleDeclaration: + return bindModuleDeclaration(node as ModuleDeclaration); + // Jsx-attributes + case SyntaxKind.JsxAttributes: + return bindJsxAttributes(node as JsxAttributes); + case SyntaxKind.JsxAttribute: + return bindJsxAttribute(node as JsxAttribute, SymbolFlags.Property, SymbolFlags.PropertyExcludes); + + // Imports and exports + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + return declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes); + case SyntaxKind.NamespaceExportDeclaration: + return bindNamespaceExportDeclaration(node as NamespaceExportDeclaration); + case SyntaxKind.ImportClause: + return bindImportClause(node as ImportClause); + case SyntaxKind.ExportDeclaration: + return bindExportDeclaration(node as ExportDeclaration); + case SyntaxKind.ExportAssignment: + return bindExportAssignment(node as ExportAssignment); + case SyntaxKind.SourceFile: + updateStrictModeStatementList((node as SourceFile).statements); + return bindSourceFileIfExternalModule(); + case SyntaxKind.Block: + if (!isFunctionLikeOrClassStaticBlockDeclaration(node.parent)) { + return; + } + // falls through + case SyntaxKind.ModuleBlock: + return updateStrictModeStatementList((node as Block | ModuleBlock).statements); - case SyntaxKind.JSDocParameterTag: - if (node.parent.kind === SyntaxKind.JSDocSignature) { - return bindParameter(node as JSDocParameterTag); - } - if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) { - break; - } - // falls through - case SyntaxKind.JSDocPropertyTag: - const propTag = node as JSDocPropertyLikeTag; - const flags = propTag.isBracketed || propTag.typeExpression && propTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType ? - SymbolFlags.Property | SymbolFlags.Optional : - SymbolFlags.Property; - return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes); - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag); - } + case SyntaxKind.JSDocParameterTag: + if (node.parent.kind === SyntaxKind.JSDocSignature) { + return bindParameter(node as JSDocParameterTag); + } + if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) { + break; + } + // falls through + case SyntaxKind.JSDocPropertyTag: + const propTag = node as JSDocPropertyLikeTag; + const flags = propTag.isBracketed || propTag.typeExpression && propTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType ? + SymbolFlags.Property | SymbolFlags.Optional : + SymbolFlags.Property; + return declareSymbolAndAddToSymbolTable(propTag, flags, SymbolFlags.PropertyExcludes); + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + return (delayedTypeAliases || (delayedTypeAliases = [])).push(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag); } + } - function bindPropertyWorker(node: PropertyDeclaration | PropertySignature) { - const isAutoAccessor = isAutoAccessorPropertyDeclaration(node); - const includes = isAutoAccessor ? SymbolFlags.Accessor : SymbolFlags.Property; - const excludes = isAutoAccessor ? SymbolFlags.AccessorExcludes : SymbolFlags.PropertyExcludes; - return bindPropertyOrMethodOrAccessor(node, includes | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), excludes); - } + function bindPropertyWorker(node: PropertyDeclaration | PropertySignature) { + const isAutoAccessor = isAutoAccessorPropertyDeclaration(node); + const includes = isAutoAccessor ? SymbolFlags.Accessor : SymbolFlags.Property; + const excludes = isAutoAccessor ? SymbolFlags.AccessorExcludes : SymbolFlags.PropertyExcludes; + return bindPropertyOrMethodOrAccessor(node, includes | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), excludes); + } - function bindAnonymousTypeWorker(node: TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral) { - return bindAnonymousDeclaration(node as Declaration, SymbolFlags.TypeLiteral, InternalSymbolName.Type); - } + function bindAnonymousTypeWorker(node: TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral) { + return bindAnonymousDeclaration(node as Declaration, SymbolFlags.TypeLiteral, InternalSymbolName.Type); + } - function bindSourceFileIfExternalModule() { - setExportContextFlag(file); - if (isExternalModule(file)) { - bindSourceFileAsExternalModule(); - } - else if (isJsonSourceFile(file)) { - bindSourceFileAsExternalModule(); - // Create symbol equivalent for the module.exports = {} - const originalSymbol = file.symbol; - declareSymbol(file.symbol.exports!, file.symbol, file, SymbolFlags.Property, SymbolFlags.All); - file.symbol = originalSymbol; - } + function bindSourceFileIfExternalModule() { + setExportContextFlag(file); + if (isExternalModule(file)) { + bindSourceFileAsExternalModule(); } - - function bindSourceFileAsExternalModule() { - bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName)}"` as __String); + else if (isJsonSourceFile(file)) { + bindSourceFileAsExternalModule(); + // Create symbol equivalent for the module.exports = {} + const originalSymbol = file.symbol; + declareSymbol(file.symbol.exports!, file.symbol, file, SymbolFlags.Property, SymbolFlags.All); + file.symbol = originalSymbol; } + } - function bindExportAssignment(node: ExportAssignment) { - if (!container.symbol || !container.symbol.exports) { - // Incorrect export assignment in some sort of block construct - bindAnonymousDeclaration(node, SymbolFlags.Value, getDeclarationName(node)!); - } - else { - const flags = exportAssignmentIsAlias(node) - // An export default clause with an EntityNameExpression or a class expression exports all meanings of that identifier or expression; - ? SymbolFlags.Alias - // An export default clause with any other expression exports a value - : SymbolFlags.Property; - // If there is an `export default x;` alias declaration, can't `export default` anything else. - // (In contrast, you can still have `export default function f() {}` and `export default interface I {}`.) - const symbol = declareSymbol(container.symbol.exports, container.symbol, node, flags, SymbolFlags.All); + function bindSourceFileAsExternalModule() { + bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName)}"` as __String); + } - if (node.isExportEquals) { - // Will be an error later, since the module already has other exports. Just make sure this has a valueDeclaration set. - setValueDeclaration(symbol, node); - } + function bindExportAssignment(node: ExportAssignment) { + if (!container.symbol || !container.symbol.exports) { + // Incorrect export assignment in some sort of block construct + bindAnonymousDeclaration(node, SymbolFlags.Value, getDeclarationName(node)!); + } + else { + const flags = exportAssignmentIsAlias(node) + // An export default clause with an EntityNameExpression or a class expression exports all meanings of that identifier or expression; + ? SymbolFlags.Alias + // An export default clause with any other expression exports a value + : SymbolFlags.Property; + // If there is an `export default x;` alias declaration, can't `export default` anything else. + // (In contrast, you can still have `export default function f() {}` and `export default interface I {}`.) + const symbol = declareSymbol(container.symbol.exports, container.symbol, node, flags, SymbolFlags.All); + + if (node.isExportEquals) { + // Will be an error later, since the module already has other exports. Just make sure this has a valueDeclaration set. + setValueDeclaration(symbol, node); } } + } - function bindNamespaceExportDeclaration(node: NamespaceExportDeclaration) { - if (some(node.modifiers)) { - file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Modifiers_cannot_appear_here)); - } - const diag = !isSourceFile(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_at_top_level - : !isExternalModule(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_in_module_files - : !node.parent.isDeclarationFile ? Diagnostics.Global_module_exports_may_only_appear_in_declaration_files - : undefined; - if (diag) { - file.bindDiagnostics.push(createDiagnosticForNode(node, diag)); - } - else { - file.symbol.globalExports = file.symbol.globalExports || createSymbolTable(); - declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - } + function bindNamespaceExportDeclaration(node: NamespaceExportDeclaration) { + if (some(node.modifiers)) { + file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Modifiers_cannot_appear_here)); } + const diag = !isSourceFile(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_at_top_level + : !isExternalModule(node.parent) ? Diagnostics.Global_module_exports_may_only_appear_in_module_files + : !node.parent.isDeclarationFile ? Diagnostics.Global_module_exports_may_only_appear_in_declaration_files + : undefined; + if (diag) { + file.bindDiagnostics.push(createDiagnosticForNode(node, diag)); + } + else { + file.symbol.globalExports = file.symbol.globalExports || createSymbolTable(); + declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); + } + } - function bindExportDeclaration(node: ExportDeclaration) { - if (!container.symbol || !container.symbol.exports) { - // Export * in some sort of block construct - bindAnonymousDeclaration(node, SymbolFlags.ExportStar, getDeclarationName(node)!); - } - else if (!node.exportClause) { - // All export * declarations are collected in an __export symbol - declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.ExportStar, SymbolFlags.None); - } - else if (isNamespaceExport(node.exportClause)) { - // declareSymbol walks up parents to find name text, parent _must_ be set - // but won't be set by the normal binder walk until `bindChildren` later on. - setParent(node.exportClause, node); - declareSymbol(container.symbol.exports, container.symbol, node.exportClause, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - } + function bindExportDeclaration(node: ExportDeclaration) { + if (!container.symbol || !container.symbol.exports) { + // Export * in some sort of block construct + bindAnonymousDeclaration(node, SymbolFlags.ExportStar, getDeclarationName(node)!); + } + else if (!node.exportClause) { + // All export * declarations are collected in an __export symbol + declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.ExportStar, SymbolFlags.None); } + else if (isNamespaceExport(node.exportClause)) { + // declareSymbol walks up parents to find name text, parent _must_ be set + // but won't be set by the normal binder walk until `bindChildren` later on. + setParent(node.exportClause, node); + declareSymbol(container.symbol.exports, container.symbol, node.exportClause, SymbolFlags.Alias, SymbolFlags.AliasExcludes); + } + } - function bindImportClause(node: ImportClause) { - if (node.name) { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - } + function bindImportClause(node: ImportClause) { + if (node.name) { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } + } - function setCommonJsModuleIndicator(node: Node) { - if (file.externalModuleIndicator && file.externalModuleIndicator !== true) { - return false; - } - if (!file.commonJsModuleIndicator) { - file.commonJsModuleIndicator = node; - if (!file.externalModuleIndicator) { - bindSourceFileAsExternalModule(); - } + function setCommonJsModuleIndicator(node: Node) { + if (file.externalModuleIndicator && file.externalModuleIndicator !== true) { + return false; + } + if (!file.commonJsModuleIndicator) { + file.commonJsModuleIndicator = node; + if (!file.externalModuleIndicator) { + bindSourceFileAsExternalModule(); } - return true; } + return true; + } - function bindObjectDefinePropertyExport(node: BindableObjectDefinePropertyCall) { - if (!setCommonJsModuleIndicator(node)) { - return; - } - const symbol = forEachIdentifierInEntityName(node.arguments[0], /*parent*/ undefined, (id, symbol) => { - if (symbol) { - addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); - } - return symbol; - }); + function bindObjectDefinePropertyExport(node: BindableObjectDefinePropertyCall) { + if (!setCommonJsModuleIndicator(node)) { + return; + } + const symbol = forEachIdentifierInEntityName(node.arguments[0], /*parent*/ undefined, (id, symbol) => { if (symbol) { - const flags = SymbolFlags.Property | SymbolFlags.ExportValue; - declareSymbol(symbol.exports!, symbol, node, flags, SymbolFlags.None); + addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); } + return symbol; + }); + if (symbol) { + const flags = SymbolFlags.Property | SymbolFlags.ExportValue; + declareSymbol(symbol.exports!, symbol, node, flags, SymbolFlags.None); } + } - function bindExportsPropertyAssignment(node: BindableStaticPropertyAssignmentExpression) { - // When we create a property via 'exports.foo = bar', the 'exports.foo' property access - // expression is the declaration - if (!setCommonJsModuleIndicator(node)) { - return; - } - const symbol = forEachIdentifierInEntityName(node.left.expression, /*parent*/ undefined, (id, symbol) => { - if (symbol) { - addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); - } - return symbol; - }); + function bindExportsPropertyAssignment(node: BindableStaticPropertyAssignmentExpression) { + // When we create a property via 'exports.foo = bar', the 'exports.foo' property access + // expression is the declaration + if (!setCommonJsModuleIndicator(node)) { + return; + } + const symbol = forEachIdentifierInEntityName(node.left.expression, /*parent*/ undefined, (id, symbol) => { if (symbol) { - const isAlias = isAliasableExpression(node.right) && (isExportsIdentifier(node.left.expression) || isModuleExportsAccessExpression(node.left.expression)); - const flags = isAlias ? SymbolFlags.Alias : SymbolFlags.Property | SymbolFlags.ExportValue; - setParent(node.left, node); - declareSymbol(symbol.exports!, symbol, node.left, flags, SymbolFlags.None); + addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.Assignment); } + return symbol; + }); + if (symbol) { + const isAlias = isAliasableExpression(node.right) && (isExportsIdentifier(node.left.expression) || isModuleExportsAccessExpression(node.left.expression)); + const flags = isAlias ? SymbolFlags.Alias : SymbolFlags.Property | SymbolFlags.ExportValue; + setParent(node.left, node); + declareSymbol(symbol.exports!, symbol, node.left, flags, SymbolFlags.None); } + } - function bindModuleExportsAssignment(node: BindablePropertyAssignmentExpression) { - // A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports' - // is still pointing to 'module.exports'. - // We do not want to consider this as 'export=' since a module can have only one of these. - // Similarly we do not want to treat 'module.exports = exports' as an 'export='. - if (!setCommonJsModuleIndicator(node)) { - return; - } - const assignedExpression = getRightMostAssignedExpression(node.right); - if (isEmptyObjectLiteral(assignedExpression) || container === file && isExportsOrModuleExportsOrAlias(file, assignedExpression)) { - return; - } - - if (isObjectLiteralExpression(assignedExpression) && every(assignedExpression.properties, isShorthandPropertyAssignment)) { - forEach(assignedExpression.properties, bindExportAssignedObjectMemberAlias); - return; - } - - // 'module.exports = expr' assignment - const flags = exportAssignmentIsAlias(node) - ? SymbolFlags.Alias // An export= with an EntityNameExpression or a ClassExpression exports all meanings of that identifier or class - : SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule; - const symbol = declareSymbol(file.symbol.exports!, file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None); - setValueDeclaration(symbol, node); + function bindModuleExportsAssignment(node: BindablePropertyAssignmentExpression) { + // A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports' + // is still pointing to 'module.exports'. + // We do not want to consider this as 'export=' since a module can have only one of these. + // Similarly we do not want to treat 'module.exports = exports' as an 'export='. + if (!setCommonJsModuleIndicator(node)) { + return; + } + const assignedExpression = getRightMostAssignedExpression(node.right); + if (isEmptyObjectLiteral(assignedExpression) || container === file && isExportsOrModuleExportsOrAlias(file, assignedExpression)) { + return; } - function bindExportAssignedObjectMemberAlias(node: ShorthandPropertyAssignment) { - declareSymbol(file.symbol.exports!, file.symbol, node, SymbolFlags.Alias | SymbolFlags.Assignment, SymbolFlags.None); + if (isObjectLiteralExpression(assignedExpression) && every(assignedExpression.properties, isShorthandPropertyAssignment)) { + forEach(assignedExpression.properties, bindExportAssignedObjectMemberAlias); + return; } - function bindThisPropertyAssignment(node: BindablePropertyAssignmentExpression | PropertyAccessExpression | LiteralLikeElementAccessExpression) { - Debug.assert(isInJSFile(node)); - // private identifiers *must* be declared (even in JS files) - const hasPrivateIdentifier = (isBinaryExpression(node) && isPropertyAccessExpression(node.left) && isPrivateIdentifier(node.left.name)) - || (isPropertyAccessExpression(node) && isPrivateIdentifier(node.name)); - if (hasPrivateIdentifier) { - return; - } - const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false); - switch (thisContainer.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - let constructorSymbol: Symbol | undefined = thisContainer.symbol; - // For `f.prototype.m = function() { this.x = 0; }`, `this.x = 0` should modify `f`'s members, not the function expression. - if (isBinaryExpression(thisContainer.parent) && thisContainer.parent.operatorToken.kind === SyntaxKind.EqualsToken) { - const l = thisContainer.parent.left; - if (isBindableStaticAccessExpression(l) && isPrototypeAccess(l.expression)) { - constructorSymbol = lookupSymbolForPropertyAccess(l.expression.expression, thisParentContainer); - } - } + // 'module.exports = expr' assignment + const flags = exportAssignmentIsAlias(node) + ? SymbolFlags.Alias // An export= with an EntityNameExpression or a ClassExpression exports all meanings of that identifier or class + : SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule; + const symbol = declareSymbol(file.symbol.exports!, file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None); + setValueDeclaration(symbol, node); + } - if (constructorSymbol && constructorSymbol.valueDeclaration) { - // Declare a 'member' if the container is an ES5 class or ES6 constructor - constructorSymbol.members = constructorSymbol.members || createSymbolTable(); - // It's acceptable for multiple 'this' assignments of the same identifier to occur - if (hasDynamicName(node)) { - bindDynamicallyNamedThisPropertyAssignment(node, constructorSymbol, constructorSymbol.members); - } - else { - declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); - } - addDeclarationToSymbol(constructorSymbol, constructorSymbol.valueDeclaration, SymbolFlags.Class); - } - break; + function bindExportAssignedObjectMemberAlias(node: ShorthandPropertyAssignment) { + declareSymbol(file.symbol.exports!, file.symbol, node, SymbolFlags.Alias | SymbolFlags.Assignment, SymbolFlags.None); + } - case SyntaxKind.Constructor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.ClassStaticBlockDeclaration: - // this.foo assignment in a JavaScript class - // Bind this property to the containing class - const containingClass = thisContainer.parent; - const symbolTable = isStatic(thisContainer) ? containingClass.symbol.exports! : containingClass.symbol.members!; - if (hasDynamicName(node)) { - bindDynamicallyNamedThisPropertyAssignment(node, containingClass.symbol, symbolTable); - } - else { - declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.None, /*isReplaceableByMethod*/ true); - } - break; - case SyntaxKind.SourceFile: - // this.property = assignment in a source file -- declare symbol in exports for a module, in locals for a script + function bindThisPropertyAssignment(node: BindablePropertyAssignmentExpression | PropertyAccessExpression | LiteralLikeElementAccessExpression) { + Debug.assert(isInJSFile(node)); + // private identifiers *must* be declared (even in JS files) + const hasPrivateIdentifier = (isBinaryExpression(node) && isPropertyAccessExpression(node.left) && isPrivateIdentifier(node.left.name)) + || (isPropertyAccessExpression(node) && isPrivateIdentifier(node.name)); + if (hasPrivateIdentifier) { + return; + } + const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false); + switch (thisContainer.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + let constructorSymbol: Symbol | undefined = thisContainer.symbol; + // For `f.prototype.m = function() { this.x = 0; }`, `this.x = 0` should modify `f`'s members, not the function expression. + if (isBinaryExpression(thisContainer.parent) && thisContainer.parent.operatorToken.kind === SyntaxKind.EqualsToken) { + const l = thisContainer.parent.left; + if (isBindableStaticAccessExpression(l) && isPrototypeAccess(l.expression)) { + constructorSymbol = lookupSymbolForPropertyAccess(l.expression.expression, thisParentContainer); + } + } + + if (constructorSymbol && constructorSymbol.valueDeclaration) { + // Declare a 'member' if the container is an ES5 class or ES6 constructor + constructorSymbol.members = constructorSymbol.members || createSymbolTable(); + // It's acceptable for multiple 'this' assignments of the same identifier to occur if (hasDynamicName(node)) { - break; - } - else if ((thisContainer as SourceFile).commonJsModuleIndicator) { - declareSymbol(thisContainer.symbol.exports!, thisContainer.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None); + bindDynamicallyNamedThisPropertyAssignment(node, constructorSymbol, constructorSymbol.members); } else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); + declareSymbol(constructorSymbol.members, constructorSymbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); } + addDeclarationToSymbol(constructorSymbol, constructorSymbol.valueDeclaration, SymbolFlags.Class); + } + break; + + case SyntaxKind.Constructor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.ClassStaticBlockDeclaration: + // this.foo assignment in a JavaScript class + // Bind this property to the containing class + const containingClass = thisContainer.parent; + const symbolTable = isStatic(thisContainer) ? containingClass.symbol.exports! : containingClass.symbol.members!; + if (hasDynamicName(node)) { + bindDynamicallyNamedThisPropertyAssignment(node, containingClass.symbol, symbolTable); + } + else { + declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property | SymbolFlags.Assignment, SymbolFlags.None, /*isReplaceableByMethod*/ true); + } + break; + case SyntaxKind.SourceFile: + // this.property = assignment in a source file -- declare symbol in exports for a module, in locals for a script + if (hasDynamicName(node)) { break; + } + else if ((thisContainer as SourceFile).commonJsModuleIndicator) { + declareSymbol(thisContainer.symbol.exports!, thisContainer.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None); + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); + } + break; - default: - Debug.failBadSyntaxKind(thisContainer); - } + default: + Debug.failBadSyntaxKind(thisContainer); } + } - function bindDynamicallyNamedThisPropertyAssignment(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol, symbolTable: SymbolTable) { - declareSymbol(symbolTable, symbol, node, SymbolFlags.Property, SymbolFlags.None, /*isReplaceableByMethod*/ true, /*isComputedName*/ true); - addLateBoundAssignmentDeclarationToSymbol(node, symbol); - } + function bindDynamicallyNamedThisPropertyAssignment(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol, symbolTable: SymbolTable) { + declareSymbol(symbolTable, symbol, node, SymbolFlags.Property, SymbolFlags.None, /*isReplaceableByMethod*/ true, /*isComputedName*/ true); + addLateBoundAssignmentDeclarationToSymbol(node, symbol); + } - function addLateBoundAssignmentDeclarationToSymbol(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol | undefined) { - if (symbol) { - (symbol.assignmentDeclarationMembers || (symbol.assignmentDeclarationMembers = new Map())).set(getNodeId(node), node); - } + function addLateBoundAssignmentDeclarationToSymbol(node: BinaryExpression | DynamicNamedDeclaration, symbol: Symbol | undefined) { + if (symbol) { + (symbol.assignmentDeclarationMembers || (symbol.assignmentDeclarationMembers = new Map())).set(getNodeId(node), node); } + } - function bindSpecialPropertyDeclaration(node: PropertyAccessExpression | LiteralLikeElementAccessExpression) { - if (node.expression.kind === SyntaxKind.ThisKeyword) { - bindThisPropertyAssignment(node); + function bindSpecialPropertyDeclaration(node: PropertyAccessExpression | LiteralLikeElementAccessExpression) { + if (node.expression.kind === SyntaxKind.ThisKeyword) { + bindThisPropertyAssignment(node); + } + else if (isBindableStaticAccessExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) { + if (isPrototypeAccess(node.expression)) { + bindPrototypePropertyAssignment(node, node.parent); } - else if (isBindableStaticAccessExpression(node) && node.parent.parent.kind === SyntaxKind.SourceFile) { - if (isPrototypeAccess(node.expression)) { - bindPrototypePropertyAssignment(node, node.parent); - } - else { - bindStaticPropertyAssignment(node); - } + else { + bindStaticPropertyAssignment(node); } } + } - /** For `x.prototype = { p, ... }`, declare members p,... if `x` is function/class/{}, or not declared. */ - function bindPrototypeAssignment(node: BindableStaticPropertyAssignmentExpression) { - setParent(node.left, node); - setParent(node.right, node); - bindPropertyAssignment(node.left.expression, node.left, /*isPrototypeProperty*/ false, /*containerIsClass*/ true); - } + /** For `x.prototype = { p, ... }`, declare members p,... if `x` is function/class/{}, or not declared. */ + function bindPrototypeAssignment(node: BindableStaticPropertyAssignmentExpression) { + setParent(node.left, node); + setParent(node.right, node); + bindPropertyAssignment(node.left.expression, node.left, /*isPrototypeProperty*/ false, /*containerIsClass*/ true); + } - function bindObjectDefinePrototypeProperty(node: BindableObjectDefinePropertyCall) { - const namespaceSymbol = lookupSymbolForPropertyAccess((node.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression); - if (namespaceSymbol && namespaceSymbol.valueDeclaration) { - // Ensure the namespace symbol becomes class-like - addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); - } - bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ true); + function bindObjectDefinePrototypeProperty(node: BindableObjectDefinePropertyCall) { + const namespaceSymbol = lookupSymbolForPropertyAccess((node.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression); + if (namespaceSymbol && namespaceSymbol.valueDeclaration) { + // Ensure the namespace symbol becomes class-like + addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); } + bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ true); + } - /** - * For `x.prototype.y = z`, declare a member `y` on `x` if `x` is a function or class, or not declared. - * Note that jsdoc preceding an ExpressionStatement like `x.prototype.y;` is also treated as a declaration. - */ - function bindPrototypePropertyAssignment(lhs: BindableStaticAccessExpression, parent: Node) { - // Look up the function in the local scope, since prototype assignments should - // follow the function declaration - const classPrototype = lhs.expression as BindableStaticAccessExpression; - const constructorFunction = classPrototype.expression; + /** + * For `x.prototype.y = z`, declare a member `y` on `x` if `x` is a function or class, or not declared. + * Note that jsdoc preceding an ExpressionStatement like `x.prototype.y;` is also treated as a declaration. + */ + function bindPrototypePropertyAssignment(lhs: BindableStaticAccessExpression, parent: Node) { + // Look up the function in the local scope, since prototype assignments should + // follow the function declaration + const classPrototype = lhs.expression as BindableStaticAccessExpression; + const constructorFunction = classPrototype.expression; + + // Fix up parent pointers since we're going to use these nodes before we bind into them + setParent(constructorFunction, classPrototype); + setParent(classPrototype, lhs); + setParent(lhs, parent); + + bindPropertyAssignment(constructorFunction, lhs, /*isPrototypeProperty*/ true, /*containerIsClass*/ true); + } - // Fix up parent pointers since we're going to use these nodes before we bind into them - setParent(constructorFunction, classPrototype); - setParent(classPrototype, lhs); - setParent(lhs, parent); + function bindObjectDefinePropertyAssignment(node: BindableObjectDefinePropertyCall) { + let namespaceSymbol = lookupSymbolForPropertyAccess(node.arguments[0]); + const isToplevel = node.parent.parent.kind === SyntaxKind.SourceFile; + namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, node.arguments[0], isToplevel, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); + bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ false); + } - bindPropertyAssignment(constructorFunction, lhs, /*isPrototypeProperty*/ true, /*containerIsClass*/ true); + function bindSpecialPropertyAssignment(node: BindablePropertyAssignmentExpression) { + // Class declarations in Typescript do not allow property declarations + const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression, container) || lookupSymbolForPropertyAccess(node.left.expression, blockScopeContainer) ; + if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) { + return; + } + const rootExpr = getLeftmostAccessExpression(node.left); + if (isIdentifier(rootExpr) && lookupSymbolForName(container, rootExpr.escapedText)?.flags! & SymbolFlags.Alias) { + return; + } + // Fix up parent pointers since we're going to use these nodes before we bind into them + setParent(node.left, node); + setParent(node.right, node); + if (isIdentifier(node.left.expression) && container === file && isExportsOrModuleExportsOrAlias(file, node.left.expression)) { + // This can be an alias for the 'exports' or 'module.exports' names, e.g. + // var util = module.exports; + // util.property = function ... + bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); + } + else if (hasDynamicName(node)) { + bindAnonymousDeclaration(node, SymbolFlags.Property | SymbolFlags.Assignment, InternalSymbolName.Computed); + const sym = bindPotentiallyMissingNamespaces(parentSymbol, node.left.expression, isTopLevelNamespaceAssignment(node.left), /*isPrototype*/ false, /*containerIsClass*/ false); + addLateBoundAssignmentDeclarationToSymbol(node, sym); } - - function bindObjectDefinePropertyAssignment(node: BindableObjectDefinePropertyCall) { - let namespaceSymbol = lookupSymbolForPropertyAccess(node.arguments[0]); - const isToplevel = node.parent.parent.kind === SyntaxKind.SourceFile; - namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, node.arguments[0], isToplevel, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); - bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ false); + else { + bindStaticPropertyAssignment(cast(node.left, isBindableStaticNameExpression)); } + } - function bindSpecialPropertyAssignment(node: BindablePropertyAssignmentExpression) { - // Class declarations in Typescript do not allow property declarations - const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression, container) || lookupSymbolForPropertyAccess(node.left.expression, blockScopeContainer) ; - if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) { - return; - } - const rootExpr = getLeftmostAccessExpression(node.left); - if (isIdentifier(rootExpr) && lookupSymbolForName(container, rootExpr.escapedText)?.flags! & SymbolFlags.Alias) { - return; - } - // Fix up parent pointers since we're going to use these nodes before we bind into them - setParent(node.left, node); - setParent(node.right, node); - if (isIdentifier(node.left.expression) && container === file && isExportsOrModuleExportsOrAlias(file, node.left.expression)) { - // This can be an alias for the 'exports' or 'module.exports' names, e.g. - // var util = module.exports; - // util.property = function ... - bindExportsPropertyAssignment(node as BindableStaticPropertyAssignmentExpression); - } - else if (hasDynamicName(node)) { - bindAnonymousDeclaration(node, SymbolFlags.Property | SymbolFlags.Assignment, InternalSymbolName.Computed); - const sym = bindPotentiallyMissingNamespaces(parentSymbol, node.left.expression, isTopLevelNamespaceAssignment(node.left), /*isPrototype*/ false, /*containerIsClass*/ false); - addLateBoundAssignmentDeclarationToSymbol(node, sym); - } - else { - bindStaticPropertyAssignment(cast(node.left, isBindableStaticNameExpression)); - } + /** + * For nodes like `x.y = z`, declare a member 'y' on 'x' if x is a function (or IIFE) or class or {}, or not declared. + * Also works for expression statements preceded by JSDoc, like / ** @type number * / x.y; + */ + function bindStaticPropertyAssignment(node: BindableStaticNameExpression) { + Debug.assert(!isIdentifier(node)); + setParent(node.expression, node); + bindPropertyAssignment(node.expression, node, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); + } + + function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: BindableStaticNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) { + if (namespaceSymbol?.flags! & SymbolFlags.Alias) { + return namespaceSymbol; + } + if (isToplevel && !isPrototypeProperty) { + // make symbols or add declarations for intermediate containers + const flags = SymbolFlags.Module | SymbolFlags.Assignment; + const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.Assignment; + namespaceSymbol = forEachIdentifierInEntityName(entityName, namespaceSymbol, (id, symbol, parent) => { + if (symbol) { + addDeclarationToSymbol(symbol, id, flags); + return symbol; + } + else { + const table = parent ? parent.exports! : + file.jsGlobalAugmentations || (file.jsGlobalAugmentations = createSymbolTable()); + return declareSymbol(table, parent, id, flags, excludeFlags); + } + }); + } + if (containerIsClass && namespaceSymbol && namespaceSymbol.valueDeclaration) { + addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); } + return namespaceSymbol; + } - /** - * For nodes like `x.y = z`, declare a member 'y' on 'x' if x is a function (or IIFE) or class or {}, or not declared. - * Also works for expression statements preceded by JSDoc, like / ** @type number * / x.y; - */ - function bindStaticPropertyAssignment(node: BindableStaticNameExpression) { - Debug.assert(!isIdentifier(node)); - setParent(node.expression, node); - bindPropertyAssignment(node.expression, node, /*isPrototypeProperty*/ false, /*containerIsClass*/ false); + function bindPotentiallyNewExpandoMemberToNamespace(declaration: BindableStaticAccessExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) { + if (!namespaceSymbol || !isExpandoSymbol(namespaceSymbol)) { + return; } - function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: BindableStaticNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) { - if (namespaceSymbol?.flags! & SymbolFlags.Alias) { - return namespaceSymbol; - } - if (isToplevel && !isPrototypeProperty) { - // make symbols or add declarations for intermediate containers - const flags = SymbolFlags.Module | SymbolFlags.Assignment; - const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.Assignment; - namespaceSymbol = forEachIdentifierInEntityName(entityName, namespaceSymbol, (id, symbol, parent) => { - if (symbol) { - addDeclarationToSymbol(symbol, id, flags); - return symbol; - } - else { - const table = parent ? parent.exports! : - file.jsGlobalAugmentations || (file.jsGlobalAugmentations = createSymbolTable()); - return declareSymbol(table, parent, id, flags, excludeFlags); - } - }); + // Set up the members collection if it doesn't exist already + const symbolTable = isPrototypeProperty ? + (namespaceSymbol.members || (namespaceSymbol.members = createSymbolTable())) : + (namespaceSymbol.exports || (namespaceSymbol.exports = createSymbolTable())); + + let includes = SymbolFlags.None; + let excludes = SymbolFlags.None; + // Method-like + if (isFunctionLikeDeclaration(getAssignedExpandoInitializer(declaration)!)) { + includes = SymbolFlags.Method; + excludes = SymbolFlags.MethodExcludes; + } + // Maybe accessor-like + else if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { + if (some(declaration.arguments[2].properties, p => { + const id = getNameOfDeclaration(p); + return !!id && isIdentifier(id) && idText(id) === "set"; + })) { + // We mix in `SymbolFLags.Property` so in the checker `getTypeOfVariableParameterOrProperty` is used for this + // symbol, instead of `getTypeOfAccessor` (which will assert as there is no real accessor declaration) + includes |= SymbolFlags.SetAccessor | SymbolFlags.Property; + excludes |= SymbolFlags.SetAccessorExcludes; } - if (containerIsClass && namespaceSymbol && namespaceSymbol.valueDeclaration) { - addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); + if (some(declaration.arguments[2].properties, p => { + const id = getNameOfDeclaration(p); + return !!id && isIdentifier(id) && idText(id) === "get"; + })) { + includes |= SymbolFlags.GetAccessor | SymbolFlags.Property; + excludes |= SymbolFlags.GetAccessorExcludes; } - return namespaceSymbol; } - function bindPotentiallyNewExpandoMemberToNamespace(declaration: BindableStaticAccessExpression | CallExpression, namespaceSymbol: Symbol | undefined, isPrototypeProperty: boolean) { - if (!namespaceSymbol || !isExpandoSymbol(namespaceSymbol)) { - return; - } + if (includes === SymbolFlags.None) { + includes = SymbolFlags.Property; + excludes = SymbolFlags.PropertyExcludes; + } - // Set up the members collection if it doesn't exist already - const symbolTable = isPrototypeProperty ? - (namespaceSymbol.members || (namespaceSymbol.members = createSymbolTable())) : - (namespaceSymbol.exports || (namespaceSymbol.exports = createSymbolTable())); - - let includes = SymbolFlags.None; - let excludes = SymbolFlags.None; - // Method-like - if (isFunctionLikeDeclaration(getAssignedExpandoInitializer(declaration)!)) { - includes = SymbolFlags.Method; - excludes = SymbolFlags.MethodExcludes; - } - // Maybe accessor-like - else if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { - if (some(declaration.arguments[2].properties, p => { - const id = getNameOfDeclaration(p); - return !!id && isIdentifier(id) && idText(id) === "set"; - })) { - // We mix in `SymbolFLags.Property` so in the checker `getTypeOfVariableParameterOrProperty` is used for this - // symbol, instead of `getTypeOfAccessor` (which will assert as there is no real accessor declaration) - includes |= SymbolFlags.SetAccessor | SymbolFlags.Property; - excludes |= SymbolFlags.SetAccessorExcludes; - } - if (some(declaration.arguments[2].properties, p => { - const id = getNameOfDeclaration(p); - return !!id && isIdentifier(id) && idText(id) === "get"; - })) { - includes |= SymbolFlags.GetAccessor | SymbolFlags.Property; - excludes |= SymbolFlags.GetAccessorExcludes; - } - } - - if (includes === SymbolFlags.None) { - includes = SymbolFlags.Property; - excludes = SymbolFlags.PropertyExcludes; - } - - declareSymbol(symbolTable, namespaceSymbol, declaration, includes | SymbolFlags.Assignment, excludes & ~SymbolFlags.Assignment); - } - - function isTopLevelNamespaceAssignment(propertyAccess: BindableAccessExpression) { - return isBinaryExpression(propertyAccess.parent) - ? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile - : propertyAccess.parent.parent.kind === SyntaxKind.SourceFile; - } - - function bindPropertyAssignment(name: BindableStaticNameExpression, propertyAccess: BindableStaticAccessExpression, isPrototypeProperty: boolean, containerIsClass: boolean) { - let namespaceSymbol = lookupSymbolForPropertyAccess(name, container) || lookupSymbolForPropertyAccess(name, blockScopeContainer); - const isToplevel = isTopLevelNamespaceAssignment(propertyAccess); - namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty, containerIsClass); - bindPotentiallyNewExpandoMemberToNamespace(propertyAccess, namespaceSymbol, isPrototypeProperty); - } - - /** - * Javascript expando values are: - * - Functions - * - classes - * - namespaces - * - variables initialized with function expressions - * - with class expressions - * - with empty object literals - * - with non-empty object literals if assigned to the prototype property - */ - function isExpandoSymbol(symbol: Symbol): boolean { - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.NamespaceModule)) { - return true; - } - const node = symbol.valueDeclaration; - if (node && isCallExpression(node)) { - return !!getAssignedExpandoInitializer(node); - } - let init = !node ? undefined : - isVariableDeclaration(node) ? node.initializer : - isBinaryExpression(node) ? node.right : - isPropertyAccessExpression(node) && isBinaryExpression(node.parent) ? node.parent.right : - undefined; - init = init && getRightMostAssignedExpression(init); - if (init) { - const isPrototypeAssignment = isPrototypeAccess(isVariableDeclaration(node!) ? node.name : isBinaryExpression(node!) ? node.left : node!); - return !!getExpandoInitializer(isBinaryExpression(init) && (init.operatorToken.kind === SyntaxKind.BarBarToken || init.operatorToken.kind === SyntaxKind.QuestionQuestionToken) ? init.right : init, isPrototypeAssignment); - } - return false; + declareSymbol(symbolTable, namespaceSymbol, declaration, includes | SymbolFlags.Assignment, excludes & ~SymbolFlags.Assignment); + } + + function isTopLevelNamespaceAssignment(propertyAccess: BindableAccessExpression) { + return isBinaryExpression(propertyAccess.parent) + ? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile + : propertyAccess.parent.parent.kind === SyntaxKind.SourceFile; + } + + function bindPropertyAssignment(name: BindableStaticNameExpression, propertyAccess: BindableStaticAccessExpression, isPrototypeProperty: boolean, containerIsClass: boolean) { + let namespaceSymbol = lookupSymbolForPropertyAccess(name, container) || lookupSymbolForPropertyAccess(name, blockScopeContainer); + const isToplevel = isTopLevelNamespaceAssignment(propertyAccess); + namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty, containerIsClass); + bindPotentiallyNewExpandoMemberToNamespace(propertyAccess, namespaceSymbol, isPrototypeProperty); + } + + /** + * Javascript expando values are: + * - Functions + * - classes + * - namespaces + * - variables initialized with function expressions + * - with class expressions + * - with empty object literals + * - with non-empty object literals if assigned to the prototype property + */ + function isExpandoSymbol(symbol: Symbol): boolean { + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Class | SymbolFlags.NamespaceModule)) { + return true; } + const node = symbol.valueDeclaration; + if (node && isCallExpression(node)) { + return !!getAssignedExpandoInitializer(node); + } + let init = !node ? undefined : + isVariableDeclaration(node) ? node.initializer : + isBinaryExpression(node) ? node.right : + isPropertyAccessExpression(node) && isBinaryExpression(node.parent) ? node.parent.right : + undefined; + init = init && getRightMostAssignedExpression(init); + if (init) { + const isPrototypeAssignment = isPrototypeAccess(isVariableDeclaration(node!) ? node.name : isBinaryExpression(node!) ? node.left : node!); + return !!getExpandoInitializer(isBinaryExpression(init) && (init.operatorToken.kind === SyntaxKind.BarBarToken || init.operatorToken.kind === SyntaxKind.QuestionQuestionToken) ? init.right : init, isPrototypeAssignment); + } + return false; + } - function getParentOfBinaryExpression(expr: Node) { - while (isBinaryExpression(expr.parent)) { - expr = expr.parent; - } - return expr.parent; + function getParentOfBinaryExpression(expr: Node) { + while (isBinaryExpression(expr.parent)) { + expr = expr.parent; } + return expr.parent; + } - function lookupSymbolForPropertyAccess(node: BindableStaticNameExpression, lookupContainer: Node = container): Symbol | undefined { - if (isIdentifier(node)) { - return lookupSymbolForName(lookupContainer, node.escapedText); - } - else { - const symbol = lookupSymbolForPropertyAccess(node.expression); - return symbol && symbol.exports && symbol.exports.get(getElementOrPropertyAccessName(node)); - } + function lookupSymbolForPropertyAccess(node: BindableStaticNameExpression, lookupContainer: Node = container): Symbol | undefined { + if (isIdentifier(node)) { + return lookupSymbolForName(lookupContainer, node.escapedText); + } + else { + const symbol = lookupSymbolForPropertyAccess(node.expression); + return symbol && symbol.exports && symbol.exports.get(getElementOrPropertyAccessName(node)); } + } - function forEachIdentifierInEntityName(e: BindableStaticNameExpression, parent: Symbol | undefined, action: (e: Declaration, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined { - if (isExportsOrModuleExportsOrAlias(file, e)) { - return file.symbol; - } - else if (isIdentifier(e)) { - return action(e, lookupSymbolForPropertyAccess(e), parent); - } - else { - const s = forEachIdentifierInEntityName(e.expression, parent, action); - const name = getNameOrArgument(e); - // unreachable - if (isPrivateIdentifier(name)) { - Debug.fail("unexpected PrivateIdentifier"); - } - return action(name, s && s.exports && s.exports.get(getElementOrPropertyAccessName(e)), s); + function forEachIdentifierInEntityName(e: BindableStaticNameExpression, parent: Symbol | undefined, action: (e: Declaration, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined { + if (isExportsOrModuleExportsOrAlias(file, e)) { + return file.symbol; + } + else if (isIdentifier(e)) { + return action(e, lookupSymbolForPropertyAccess(e), parent); + } + else { + const s = forEachIdentifierInEntityName(e.expression, parent, action); + const name = getNameOrArgument(e); + // unreachable + if (isPrivateIdentifier(name)) { + Debug.fail("unexpected PrivateIdentifier"); } + return action(name, s && s.exports && s.exports.get(getElementOrPropertyAccessName(e)), s); } + } - function bindCallExpression(node: CallExpression) { - // We're only inspecting call expressions to detect CommonJS modules, so we can skip - // this check if we've already seen the module indicator - if (!file.commonJsModuleIndicator && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ false)) { - setCommonJsModuleIndicator(node); - } + function bindCallExpression(node: CallExpression) { + // We're only inspecting call expressions to detect CommonJS modules, so we can skip + // this check if we've already seen the module indicator + if (!file.commonJsModuleIndicator && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ false)) { + setCommonJsModuleIndicator(node); } + } - function bindClassLikeDeclaration(node: ClassLikeDeclaration) { - if (node.kind === SyntaxKind.ClassDeclaration) { - bindBlockScopedDeclaration(node, SymbolFlags.Class, SymbolFlags.ClassExcludes); - } - else { - const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Class; - bindAnonymousDeclaration(node, SymbolFlags.Class, bindingName); - // Add name of class expression into the map for semantic classifier - if (node.name) { - classifiableNames.add(node.name.escapedText); - } + function bindClassLikeDeclaration(node: ClassLikeDeclaration) { + if (node.kind === SyntaxKind.ClassDeclaration) { + bindBlockScopedDeclaration(node, SymbolFlags.Class, SymbolFlags.ClassExcludes); + } + else { + const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Class; + bindAnonymousDeclaration(node, SymbolFlags.Class, bindingName); + // Add name of class expression into the map for semantic classifier + if (node.name) { + classifiableNames.add(node.name.escapedText); } + } - const { symbol } = node; + const { symbol } = node; - // TypeScript 1.0 spec (April 2014): 8.4 - // Every class automatically contains a static property member named 'prototype', the - // type of which is an instantiation of the class type with type Any supplied as a type - // argument for each type parameter. It is an error to explicitly declare a static - // property member with the name 'prototype'. - // - // Note: we check for this here because this class may be merging into a module. The - // module might have an exported variable called 'prototype'. We can't allow that as - // that would clash with the built-in 'prototype' for the class. - const prototypeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Prototype, "prototype" as __String); - const symbolExport = symbol.exports!.get(prototypeSymbol.escapedName); - if (symbolExport) { - if (node.name) { - setParent(node.name, node); - } - file.bindDiagnostics.push(createDiagnosticForNode(symbolExport.declarations![0], Diagnostics.Duplicate_identifier_0, symbolName(prototypeSymbol))); - } - symbol.exports!.set(prototypeSymbol.escapedName, prototypeSymbol); - prototypeSymbol.parent = symbol; - } - - function bindEnumDeclaration(node: EnumDeclaration) { - return isEnumConst(node) - ? bindBlockScopedDeclaration(node, SymbolFlags.ConstEnum, SymbolFlags.ConstEnumExcludes) - : bindBlockScopedDeclaration(node, SymbolFlags.RegularEnum, SymbolFlags.RegularEnumExcludes); - } - - function bindVariableDeclarationOrBindingElement(node: VariableDeclaration | BindingElement) { - if (inStrictMode) { - checkStrictModeEvalOrArguments(node, node.name); - } - - if (!isBindingPattern(node.name)) { - const possibleVariableDecl = node.kind === SyntaxKind.VariableDeclaration ? node : node.parent.parent; - if (isInJSFile(node) && - isVariableDeclarationInitializedToBareOrAccessedRequire(possibleVariableDecl) && - !getJSDocTypeTag(node) && - !(getCombinedModifierFlags(node) & ModifierFlags.Export) - ) { - declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes); - } - else if (isBlockOrCatchScoped(node)) { - bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes); - } - else if (isParameterDeclaration(node)) { - // It is safe to walk up parent chain to find whether the node is a destructuring parameter declaration - // because its parent chain has already been set up, since parents are set before descending into children. - // - // If node is a binding element in parameter declaration, we need to use ParameterExcludes. - // Using ParameterExcludes flag allows the compiler to report an error on duplicate identifiers in Parameter Declaration - // For example: - // function foo([a,a]) {} // Duplicate Identifier error - // function bar(a,a) {} // Duplicate Identifier error, parameter declaration in this case is handled in bindParameter - // // which correctly set excluded symbols - declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); - } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); - } + // TypeScript 1.0 spec (April 2014): 8.4 + // Every class automatically contains a static property member named 'prototype', the + // type of which is an instantiation of the class type with type Any supplied as a type + // argument for each type parameter. It is an error to explicitly declare a static + // property member with the name 'prototype'. + // + // Note: we check for this here because this class may be merging into a module. The + // module might have an exported variable called 'prototype'. We can't allow that as + // that would clash with the built-in 'prototype' for the class. + const prototypeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Prototype, "prototype" as __String); + const symbolExport = symbol.exports!.get(prototypeSymbol.escapedName); + if (symbolExport) { + if (node.name) { + setParent(node.name, node); } + file.bindDiagnostics.push(createDiagnosticForNode(symbolExport.declarations![0], Diagnostics.Duplicate_identifier_0, symbolName(prototypeSymbol))); } + symbol.exports!.set(prototypeSymbol.escapedName, prototypeSymbol); + prototypeSymbol.parent = symbol; + } - function bindParameter(node: ParameterDeclaration | JSDocParameterTag) { - if (node.kind === SyntaxKind.JSDocParameterTag && container.kind !== SyntaxKind.JSDocSignature) { - return; - } - if (inStrictMode && !(node.flags & NodeFlags.Ambient)) { - // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a - // strict mode FunctionLikeDeclaration or FunctionExpression(13.1) - checkStrictModeEvalOrArguments(node, node.name); - } + function bindEnumDeclaration(node: EnumDeclaration) { + return isEnumConst(node) + ? bindBlockScopedDeclaration(node, SymbolFlags.ConstEnum, SymbolFlags.ConstEnumExcludes) + : bindBlockScopedDeclaration(node, SymbolFlags.RegularEnum, SymbolFlags.RegularEnumExcludes); + } + + function bindVariableDeclarationOrBindingElement(node: VariableDeclaration | BindingElement) { + if (inStrictMode) { + checkStrictModeEvalOrArguments(node, node.name); + } - if (isBindingPattern(node.name)) { - bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, "__" + (node as ParameterDeclaration).parent.parameters.indexOf(node as ParameterDeclaration) as __String); + if (!isBindingPattern(node.name)) { + const possibleVariableDecl = node.kind === SyntaxKind.VariableDeclaration ? node : node.parent.parent; + if (isInJSFile(node) && + isVariableDeclarationInitializedToBareOrAccessedRequire(possibleVariableDecl) && + !getJSDocTypeTag(node) && + !(getCombinedModifierFlags(node) & ModifierFlags.Export) + ) { + declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } - else { + else if (isBlockOrCatchScoped(node)) { + bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes); + } + else if (isParameterDeclaration(node)) { + // It is safe to walk up parent chain to find whether the node is a destructuring parameter declaration + // because its parent chain has already been set up, since parents are set before descending into children. + // + // If node is a binding element in parameter declaration, we need to use ParameterExcludes. + // Using ParameterExcludes flag allows the compiler to report an error on duplicate identifiers in Parameter Declaration + // For example: + // function foo([a,a]) {} // Duplicate Identifier error + // function bar(a,a) {} // Duplicate Identifier error, parameter declaration in this case is handled in bindParameter + // // which correctly set excluded symbols declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); } - - // If this is a property-parameter, then also declare the property symbol into the - // containing class. - if (isParameterPropertyDeclaration(node, node.parent)) { - const classDeclaration = node.parent.parent; - declareSymbol(classDeclaration.symbol.members!, classDeclaration.symbol, node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes); } } + } - function bindFunctionDeclaration(node: FunctionDeclaration) { - if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { - if (isAsyncFunction(node)) { - emitFlags |= NodeFlags.HasAsyncFunctions; - } - } + function bindParameter(node: ParameterDeclaration | JSDocParameterTag) { + if (node.kind === SyntaxKind.JSDocParameterTag && container.kind !== SyntaxKind.JSDocSignature) { + return; + } + if (inStrictMode && !(node.flags & NodeFlags.Ambient)) { + // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a + // strict mode FunctionLikeDeclaration or FunctionExpression(13.1) + checkStrictModeEvalOrArguments(node, node.name); + } - checkStrictModeFunctionName(node); - if (inStrictMode) { - checkStrictModeFunctionDeclaration(node); - bindBlockScopedDeclaration(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); - } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); - } + if (isBindingPattern(node.name)) { + bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, "__" + (node as ParameterDeclaration).parent.parameters.indexOf(node as ParameterDeclaration) as __String); + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); } - function bindFunctionExpression(node: FunctionExpression) { - if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { - if (isAsyncFunction(node)) { - emitFlags |= NodeFlags.HasAsyncFunctions; - } - } - if (currentFlow) { - node.flowNode = currentFlow; - } - checkStrictModeFunctionName(node); - const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Function; - return bindAnonymousDeclaration(node, SymbolFlags.Function, bindingName); + // If this is a property-parameter, then also declare the property symbol into the + // containing class. + if (isParameterPropertyDeclaration(node, node.parent)) { + const classDeclaration = node.parent.parent; + declareSymbol(classDeclaration.symbol.members!, classDeclaration.symbol, node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); } + } - function bindPropertyOrMethodOrAccessor(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { - if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient) && isAsyncFunction(node)) { + function bindFunctionDeclaration(node: FunctionDeclaration) { + if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { + if (isAsyncFunction(node)) { emitFlags |= NodeFlags.HasAsyncFunctions; } + } - if (currentFlow && isObjectLiteralOrClassExpressionMethodOrAccessor(node)) { - node.flowNode = currentFlow; + checkStrictModeFunctionName(node); + if (inStrictMode) { + checkStrictModeFunctionDeclaration(node); + bindBlockScopedDeclaration(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes); + } + } + + function bindFunctionExpression(node: FunctionExpression) { + if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient)) { + if (isAsyncFunction(node)) { + emitFlags |= NodeFlags.HasAsyncFunctions; } + } + if (currentFlow) { + node.flowNode = currentFlow; + } + checkStrictModeFunctionName(node); + const bindingName = node.name ? node.name.escapedText : InternalSymbolName.Function; + return bindAnonymousDeclaration(node, SymbolFlags.Function, bindingName); + } - return hasDynamicName(node) - ? bindAnonymousDeclaration(node, symbolFlags, InternalSymbolName.Computed) - : declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); + function bindPropertyOrMethodOrAccessor(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { + if (!file.isDeclarationFile && !(node.flags & NodeFlags.Ambient) && isAsyncFunction(node)) { + emitFlags |= NodeFlags.HasAsyncFunctions; } - function getInferTypeContainer(node: Node): ConditionalTypeNode | undefined { - const extendsType = findAncestor(node, n => n.parent && isConditionalTypeNode(n.parent) && n.parent.extendsType === n); - return extendsType && extendsType.parent as ConditionalTypeNode; + if (currentFlow && isObjectLiteralOrClassExpressionMethodOrAccessor(node)) { + node.flowNode = currentFlow; } - function bindTypeParameter(node: TypeParameterDeclaration) { - if (isJSDocTemplateTag(node.parent)) { - const container = getEffectiveContainerForJSDocTemplateTag(node.parent); - if (container) { - if (!container.locals) { - container.locals = createSymbolTable(); - } - declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); - } - else { - declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); - } - } - else if (node.parent.kind === SyntaxKind.InferType) { - const container = getInferTypeContainer(node.parent); - if (container) { - if (!container.locals) { - container.locals = createSymbolTable(); - } - declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); - } - else { - bindAnonymousDeclaration(node, SymbolFlags.TypeParameter, getDeclarationName(node)!); // TODO: GH#18217 + return hasDynamicName(node) + ? bindAnonymousDeclaration(node, symbolFlags, InternalSymbolName.Computed) + : declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); + } + + function getInferTypeContainer(node: Node): ConditionalTypeNode | undefined { + const extendsType = findAncestor(node, n => n.parent && isConditionalTypeNode(n.parent) && n.parent.extendsType === n); + return extendsType && extendsType.parent as ConditionalTypeNode; + } + + function bindTypeParameter(node: TypeParameterDeclaration) { + if (isJSDocTemplateTag(node.parent)) { + const container = getEffectiveContainerForJSDocTemplateTag(node.parent); + if (container) { + if (!container.locals) { + container.locals = createSymbolTable(); } + declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } else { declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } } - - // reachability checks - - function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean { - const instanceState = getModuleInstanceState(node); - return instanceState === ModuleInstanceState.Instantiated || (instanceState === ModuleInstanceState.ConstEnumOnly && shouldPreserveConstEnums(options)); - } - - function checkUnreachable(node: Node): boolean { - if (!(currentFlow.flags & FlowFlags.Unreachable)) { - return false; - } - if (currentFlow === unreachableFlow) { - const reportError = - // report error on all statements except empty ones - (isStatementButNotDeclaration(node) && node.kind !== SyntaxKind.EmptyStatement) || - // report error on class declarations - node.kind === SyntaxKind.ClassDeclaration || - // report error on instantiated modules or const-enums only modules if preserveConstEnums is set - (node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(node as ModuleDeclaration)); - - if (reportError) { - currentFlow = reportedUnreachableFlow; - - if (!options.allowUnreachableCode) { - // unreachable code is reported if - // - user has explicitly asked about it AND - // - statement is in not ambient context (statements in ambient context is already an error - // so we should not report extras) AND - // - node is not variable statement OR - // - node is block scoped variable statement OR - // - node is not block scoped variable statement and at least one variable declaration has initializer - // Rationale: we don't want to report errors on non-initialized var's since they are hoisted - // On the other side we do want to report errors on non-initialized 'lets' because of TDZ - const isError = - unreachableCodeIsError(options) && - !(node.flags & NodeFlags.Ambient) && - ( - !isVariableStatement(node) || - !!(getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) || - node.declarationList.declarations.some(d => !!d.initializer) - ); - - eachUnreachableRange(node, (start, end) => errorOrSuggestionOnRange(isError, start, end, Diagnostics.Unreachable_code_detected)); - } + else if (node.parent.kind === SyntaxKind.InferType) { + const container = getInferTypeContainer(node.parent); + if (container) { + if (!container.locals) { + container.locals = createSymbolTable(); } + declareSymbol(container.locals, /*parent*/ undefined, node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } - return true; + else { + bindAnonymousDeclaration(node, SymbolFlags.TypeParameter, getDeclarationName(node)!); // TODO: GH#18217 + } + } + else { + declareSymbolAndAddToSymbolTable(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes); } } - function eachUnreachableRange(node: Node, cb: (start: Node, last: Node) => void): void { - if (isStatement(node) && isExecutableStatement(node) && isBlock(node.parent)) { - const { statements } = node.parent; - const slice = sliceAfter(statements, node); - getRangesWhere(slice, isExecutableStatement, (start, afterEnd) => cb(slice[start], slice[afterEnd - 1])); - } - else { - cb(node, node); + // reachability checks + + function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean { + const instanceState = getModuleInstanceState(node); + return instanceState === ModuleInstanceState.Instantiated || (instanceState === ModuleInstanceState.ConstEnumOnly && shouldPreserveConstEnums(options)); + } + + function checkUnreachable(node: Node): boolean { + if (!(currentFlow.flags & FlowFlags.Unreachable)) { + return false; } + if (currentFlow === unreachableFlow) { + const reportError = + // report error on all statements except empty ones + (isStatementButNotDeclaration(node) && node.kind !== SyntaxKind.EmptyStatement) || + // report error on class declarations + node.kind === SyntaxKind.ClassDeclaration || + // report error on instantiated modules or const-enums only modules if preserveConstEnums is set + (node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(node as ModuleDeclaration)); + + if (reportError) { + currentFlow = reportedUnreachableFlow; + + if (!options.allowUnreachableCode) { + // unreachable code is reported if + // - user has explicitly asked about it AND + // - statement is in not ambient context (statements in ambient context is already an error + // so we should not report extras) AND + // - node is not variable statement OR + // - node is block scoped variable statement OR + // - node is not block scoped variable statement and at least one variable declaration has initializer + // Rationale: we don't want to report errors on non-initialized var's since they are hoisted + // On the other side we do want to report errors on non-initialized 'lets' because of TDZ + const isError = + unreachableCodeIsError(options) && + !(node.flags & NodeFlags.Ambient) && + ( + !isVariableStatement(node) || + !!(getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) || + node.declarationList.declarations.some(d => !!d.initializer) + ); + + eachUnreachableRange(node, (start, end) => errorOrSuggestionOnRange(isError, start, end, Diagnostics.Unreachable_code_detected)); + } + } + } + return true; + } +} + +function eachUnreachableRange(node: Node, cb: (start: Node, last: Node) => void): void { + if (isStatement(node) && isExecutableStatement(node) && isBlock(node.parent)) { + const { statements } = node.parent; + const slice = sliceAfter(statements, node); + getRangesWhere(slice, isExecutableStatement, (start, afterEnd) => cb(slice[start], slice[afterEnd - 1])); } - // As opposed to a pure declaration like an `interface` - function isExecutableStatement(s: Statement): boolean { - // Don't remove statements that can validly be used before they appear. - return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) && !isEnumDeclaration(s) && - // `var x;` may declare a variable used above - !(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.Let | NodeFlags.Const)) && s.declarationList.declarations.some(d => !d.initializer)); + else { + cb(node, node); } +} +// As opposed to a pure declaration like an `interface` +function isExecutableStatement(s: Statement): boolean { + // Don't remove statements that can validly be used before they appear. + return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) && !isEnumDeclaration(s) && + // `var x;` may declare a variable used above + !(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.Let | NodeFlags.Const)) && s.declarationList.declarations.some(d => !d.initializer)); +} - function isPurelyTypeDeclaration(s: Statement): boolean { - switch (s.kind) { - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - return true; - case SyntaxKind.ModuleDeclaration: - return getModuleInstanceState(s as ModuleDeclaration) !== ModuleInstanceState.Instantiated; - case SyntaxKind.EnumDeclaration: - return hasSyntacticModifier(s, ModifierFlags.Const); - default: - return false; - } - } - - export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean { - let i = 0; - const q = createQueue(); - q.enqueue(node); - while (!q.isEmpty() && i < 100) { - i++; - node = q.dequeue(); - if (isExportsIdentifier(node) || isModuleExportsAccessExpression(node)) { - return true; - } - else if (isIdentifier(node)) { - const symbol = lookupSymbolForName(sourceFile, node.escapedText); - if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) { - const init = symbol.valueDeclaration.initializer; - q.enqueue(init); - if (isAssignmentExpression(init, /*excludeCompoundAssignment*/ true)) { - q.enqueue(init.left); - q.enqueue(init.right); - } +function isPurelyTypeDeclaration(s: Statement): boolean { + switch (s.kind) { + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return true; + case SyntaxKind.ModuleDeclaration: + return getModuleInstanceState(s as ModuleDeclaration) !== ModuleInstanceState.Instantiated; + case SyntaxKind.EnumDeclaration: + return hasSyntacticModifier(s, ModifierFlags.Const); + default: + return false; + } +} + +/** @internal */ +export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean { + let i = 0; + const q = createQueue(); + q.enqueue(node); + while (!q.isEmpty() && i < 100) { + i++; + node = q.dequeue(); + if (isExportsIdentifier(node) || isModuleExportsAccessExpression(node)) { + return true; + } + else if (isIdentifier(node)) { + const symbol = lookupSymbolForName(sourceFile, node.escapedText); + if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) { + const init = symbol.valueDeclaration.initializer; + q.enqueue(init); + if (isAssignmentExpression(init, /*excludeCompoundAssignment*/ true)) { + q.enqueue(init.left); + q.enqueue(init.right); } } } - return false; } + return false; +} - function lookupSymbolForName(container: Node, name: __String): Symbol | undefined { - const local = container.locals && container.locals.get(name); - if (local) { - return local.exportSymbol || local; - } - if (isSourceFile(container) && container.jsGlobalAugmentations && container.jsGlobalAugmentations.has(name)) { - return container.jsGlobalAugmentations.get(name); - } - return container.symbol && container.symbol.exports && container.symbol.exports.get(name); +function lookupSymbolForName(container: Node, name: __String): Symbol | undefined { + const local = container.locals && container.locals.get(name); + if (local) { + return local.exportSymbol || local; + } + if (isSourceFile(container) && container.jsGlobalAugmentations && container.jsGlobalAugmentations.has(name)) { + return container.jsGlobalAugmentations.get(name); } + return container.symbol && container.symbol.exports && container.symbol.exports.get(name); } diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 5a599b5840885..a0ab1339bca19 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1,738 +1,741 @@ -/*@internal*/ -namespace ts { - export interface ReusableDiagnostic extends ReusableDiagnosticRelatedInformation { - /** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */ - reportsUnnecessary?: {}; - reportDeprecated?: {} - source?: string; - relatedInformation?: ReusableDiagnosticRelatedInformation[]; - skippedOn?: keyof CompilerOptions; - } +import * as ts from "./_namespaces/ts"; +import { + addRange, AffectedFileResult, arrayFrom, arrayToMap, BuilderProgram, BuilderProgramHost, BuilderState, BuildInfo, + BundleBuildInfo, CancellationToken, CommandLineOption, compareStringsCaseSensitive, compareValues, CompilerHost, + CompilerOptions, compilerOptionsAffectDeclarationPath, compilerOptionsAffectEmit, + compilerOptionsAffectSemanticDiagnostics, CompilerOptionsValue, concatenate, convertToOptionsWithAbsolutePaths, + createBuildInfo, createGetCanonicalFileName, createProgram, CustomTransformers, Debug, Diagnostic, + DiagnosticCategory, DiagnosticMessageChain, DiagnosticRelatedInformation, DiagnosticWithLocation, + EmitAndSemanticDiagnosticsBuilderProgram, EmitOnly, EmitResult, emitSkippedWithNoDiagnostics, emptyArray, + ensurePathIsNonModuleName, ESMap, filterSemanticDiagnostics, forEach, forEachEntry, forEachKey, generateDjb2Hash, + GetCanonicalFileName, getDirectoryPath, getEmitDeclarations, getNormalizedAbsolutePath, getOptionsNameMap, + getOwnKeys, getRelativePathFromDirectory, getTsBuildInfoEmitOutputFilePath, handleNoEmitOptions, isArray, + isDeclarationFileName, isJsonSourceFile, isNumber, isString, map, Map, mapDefined, maybeBind, noop, notImplemented, + outFile, Path, Program, ProjectReference, ReadBuildProgramHost, ReadonlyCollection, ReadonlyESMap, ReadonlySet, + returnFalse, returnUndefined, SemanticDiagnosticsBuilderProgram, Set, skipTypeChecking, some, SourceFile, + sourceFileMayBeEmitted, SourceMapEmitResult, toPath, tryAddToSet, WriteFileCallback, WriteFileCallbackData, +} from "./_namespaces/ts"; + +/** @internal */ +export interface ReusableDiagnostic extends ReusableDiagnosticRelatedInformation { + /** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */ + reportsUnnecessary?: {}; + reportDeprecated?: {} + source?: string; + relatedInformation?: ReusableDiagnosticRelatedInformation[]; + skippedOn?: keyof CompilerOptions; +} - export interface ReusableDiagnosticRelatedInformation { - category: DiagnosticCategory; - code: number; - file: string | undefined; - start: number | undefined; - length: number | undefined; - messageText: string | ReusableDiagnosticMessageChain; - } +/** @internal */ +export interface ReusableDiagnosticRelatedInformation { + category: DiagnosticCategory; + code: number; + file: string | undefined; + start: number | undefined; + length: number | undefined; + messageText: string | ReusableDiagnosticMessageChain; +} - export type ReusableDiagnosticMessageChain = DiagnosticMessageChain; - - /** Signature (Hash of d.ts emitted), is string if it was emitted using same d.ts.map option as what compilerOptions indicate, otherwise tuple of string */ - export type EmitSignature = string | [signature: string]; - - export interface ReusableBuilderProgramState extends BuilderState { - /** - * Cache of bind and check diagnostics for files with their Path being the key - */ - semanticDiagnosticsPerFile?: ESMap | undefined; - /** - * The map has key by source file's path that has been changed - */ - changedFilesSet?: Set; - /** - * program corresponding to this state - */ - program?: Program | undefined; - /** - * compilerOptions for the program - */ - compilerOptions: CompilerOptions; - /** - * Files pending to be emitted - */ - affectedFilesPendingEmit?: ReadonlyESMap; - /** - * emitKind pending for a program with --out - */ - programEmitPending?: BuilderFileEmit; - /* - * true if semantic diagnostics are ReusableDiagnostic instead of Diagnostic - */ - hasReusableDiagnostic?: true; - /** - * Hash of d.ts emitted for the file, use to track when emit of d.ts changes - */ - emitSignatures?: ESMap; - /** - * Hash of d.ts emit with --out - */ - outSignature?: EmitSignature; - /** - * Name of the file whose dts was the latest to change - */ - latestChangedDtsFile: string | undefined; - /** - * Bundle information either from oldState or current one so it can be used to complete the information in buildInfo when emitting only js or dts files - */ - bundle?: BundleBuildInfo; - } +/** @internal */ +export type ReusableDiagnosticMessageChain = DiagnosticMessageChain; - export const enum BuilderFileEmit { - None = 0, - Js = 1 << 0, // emit js file - JsMap = 1 << 1, // emit js.map file - JsInlineMap = 1 << 2, // emit inline source map in js file - Dts = 1 << 3, // emit d.ts file - DtsMap = 1 << 4, // emit d.ts.map file - - AllJs = Js | JsMap | JsInlineMap, - AllDts = Dts | DtsMap, - All = AllJs | AllDts, - } +/** + * Signature (Hash of d.ts emitted), is string if it was emitted using same d.ts.map option as what compilerOptions indicate, otherwise tuple of string + * + * @internal + */ +export type EmitSignature = string | [signature: string]; +/** @internal */ +export interface ReusableBuilderProgramState extends BuilderState { /** - * State to store the changed files, affected files and cache semantic diagnostics + * Cache of bind and check diagnostics for files with their Path being the key */ - // TODO: GH#18217 Properties of this interface are frequently asserted to be defined. - export interface BuilderProgramState extends BuilderState, ReusableBuilderProgramState { - /** - * Cache of bind and check diagnostics for files with their Path being the key - */ - semanticDiagnosticsPerFile: ESMap | undefined; - /** - * The map has key by source file's path that has been changed - */ - changedFilesSet: Set; - /** - * Set of affected files being iterated - */ - affectedFiles?: readonly SourceFile[] | undefined; - /** - * Current index to retrieve affected file from - */ - affectedFilesIndex: number | undefined; - /** - * Current changed file for iterating over affected files - */ - currentChangedFilePath?: Path | undefined; - /** - * Already seen affected files - */ - seenAffectedFiles: Set | undefined; - /** - * whether this program has cleaned semantic diagnostics cache for lib files - */ - cleanedDiagnosticsOfLibFiles?: boolean; - /** - * True if the semantic diagnostics were copied from the old state - */ - semanticDiagnosticsFromOldState?: Set; - /** - * Records if change in dts emit was detected - */ - hasChangedEmitSignature?: boolean; - /** - * Files pending to be emitted - */ - affectedFilesPendingEmit?: ESMap; - /** - * true if build info is emitted - */ - buildInfoEmitPending: boolean; - /** - * Already seen emitted files - */ - seenEmittedFiles: ESMap | undefined; - /** Stores list of files that change signature during emit - test only */ - filesChangingSignature?: Set; - } - - export type SavedBuildProgramEmitState = Pick & { changedFilesSet: BuilderProgramState["changedFilesSet"] | undefined }; - - /** Get flags determining what all needs to be emitted */ - export function getBuilderFileEmit(options: CompilerOptions) { - let result = BuilderFileEmit.Js; - if (options.sourceMap) result = result | BuilderFileEmit.JsMap; - if (options.inlineSourceMap) result = result | BuilderFileEmit.JsInlineMap; - if (getEmitDeclarations(options)) result = result | BuilderFileEmit.Dts; - if (options.declarationMap) result = result | BuilderFileEmit.DtsMap; - if (options.emitDeclarationOnly) result = result & BuilderFileEmit.AllDts; - return result; - } - - /** Determing what all is pending to be emitted based on previous options or previous file emit flags */ - export function getPendingEmitKind(optionsOrEmitKind: CompilerOptions | BuilderFileEmit, oldOptionsOrEmitKind: CompilerOptions | BuilderFileEmit | undefined): BuilderFileEmit { - const oldEmitKind = oldOptionsOrEmitKind && (isNumber(oldOptionsOrEmitKind) ? oldOptionsOrEmitKind : getBuilderFileEmit(oldOptionsOrEmitKind)); - const emitKind = isNumber(optionsOrEmitKind) ? optionsOrEmitKind : getBuilderFileEmit(optionsOrEmitKind); - if (oldEmitKind === emitKind) return BuilderFileEmit.None; - if (!oldEmitKind || !emitKind) return emitKind; - const diff = oldEmitKind ^ emitKind; - let result = BuilderFileEmit.None; - // If there is diff in Js emit, pending emit is js emit flags - if (diff & BuilderFileEmit.AllJs) result = emitKind & BuilderFileEmit.AllJs; - // If there is diff in Dts emit, pending emit is dts emit flags - if (diff & BuilderFileEmit.AllDts) result = result | (emitKind & BuilderFileEmit.AllDts); - return result; - } + semanticDiagnosticsPerFile?: ESMap | undefined; + /** + * The map has key by source file's path that has been changed + */ + changedFilesSet?: Set; + /** + * program corresponding to this state + */ + program?: Program | undefined; + /** + * compilerOptions for the program + */ + compilerOptions: CompilerOptions; + /** + * Files pending to be emitted + */ + affectedFilesPendingEmit?: ReadonlyESMap; + /** + * emitKind pending for a program with --out + */ + programEmitPending?: BuilderFileEmit; + /* + * true if semantic diagnostics are ReusableDiagnostic instead of Diagnostic + */ + hasReusableDiagnostic?: true; + /** + * Hash of d.ts emitted for the file, use to track when emit of d.ts changes + */ + emitSignatures?: ESMap; + /** + * Hash of d.ts emit with --out + */ + outSignature?: EmitSignature; + /** + * Name of the file whose dts was the latest to change + */ + latestChangedDtsFile: string | undefined; + /** + * Bundle information either from oldState or current one so it can be used to complete the information in buildInfo when emitting only js or dts files + */ + bundle?: BundleBuildInfo; +} - function hasSameKeys(map1: ReadonlyCollection | undefined, map2: ReadonlyCollection | undefined): boolean { - // Has same size and every key is present in both maps - return map1 === map2 || map1 !== undefined && map2 !== undefined && map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); - } +/** @internal */ +export const enum BuilderFileEmit { + None = 0, + Js = 1 << 0, // emit js file + JsMap = 1 << 1, // emit js.map file + JsInlineMap = 1 << 2, // emit inline source map in js file + Dts = 1 << 3, // emit d.ts file + DtsMap = 1 << 4, // emit d.ts.map file + + AllJs = Js | JsMap | JsInlineMap, + AllDts = Dts | DtsMap, + All = AllJs | AllDts, +} +/** + * State to store the changed files, affected files and cache semantic diagnostics + * + * @internal + */ +// TODO: GH#18217 Properties of this interface are frequently asserted to be defined. +export interface BuilderProgramState extends BuilderState, ReusableBuilderProgramState { + /** + * Cache of bind and check diagnostics for files with their Path being the key + */ + semanticDiagnosticsPerFile: ESMap | undefined; + /** + * The map has key by source file's path that has been changed + */ + changedFilesSet: Set; + /** + * Set of affected files being iterated + */ + affectedFiles?: readonly SourceFile[] | undefined; + /** + * Current index to retrieve affected file from + */ + affectedFilesIndex: number | undefined; + /** + * Current changed file for iterating over affected files + */ + currentChangedFilePath?: Path | undefined; + /** + * Already seen affected files + */ + seenAffectedFiles: Set | undefined; + /** + * whether this program has cleaned semantic diagnostics cache for lib files + */ + cleanedDiagnosticsOfLibFiles?: boolean; + /** + * True if the semantic diagnostics were copied from the old state + */ + semanticDiagnosticsFromOldState?: Set; + /** + * Records if change in dts emit was detected + */ + hasChangedEmitSignature?: boolean; /** - * Create the state so that we can iterate on changedFiles/affected files + * Files pending to be emitted */ - function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState: Readonly | undefined, disableUseFileVersionAsSignature: boolean | undefined): BuilderProgramState { - const state = BuilderState.create(newProgram, getCanonicalFileName, oldState, disableUseFileVersionAsSignature) as BuilderProgramState; - state.program = newProgram; - const compilerOptions = newProgram.getCompilerOptions(); - state.compilerOptions = compilerOptions; - const outFilePath = outFile(compilerOptions); - // With --out or --outFile, any change affects all semantic diagnostics so no need to cache them - if (!outFilePath) { - state.semanticDiagnosticsPerFile = new Map(); + affectedFilesPendingEmit?: ESMap; + /** + * true if build info is emitted + */ + buildInfoEmitPending: boolean; + /** + * Already seen emitted files + */ + seenEmittedFiles: ESMap | undefined; + /** Stores list of files that change signature during emit - test only */ + filesChangingSignature?: Set; +} + +/** @internal */ +export type SavedBuildProgramEmitState = Pick & { changedFilesSet: BuilderProgramState["changedFilesSet"] | undefined }; + +/** + * Get flags determining what all needs to be emitted + * + * @internal + */ +export function getBuilderFileEmit(options: CompilerOptions) { + let result = BuilderFileEmit.Js; + if (options.sourceMap) result = result | BuilderFileEmit.JsMap; + if (options.inlineSourceMap) result = result | BuilderFileEmit.JsInlineMap; + if (getEmitDeclarations(options)) result = result | BuilderFileEmit.Dts; + if (options.declarationMap) result = result | BuilderFileEmit.DtsMap; + if (options.emitDeclarationOnly) result = result & BuilderFileEmit.AllDts; + return result; +} + +/** + * Determing what all is pending to be emitted based on previous options or previous file emit flags + * + * @internal + */ +export function getPendingEmitKind(optionsOrEmitKind: CompilerOptions | BuilderFileEmit, oldOptionsOrEmitKind: CompilerOptions | BuilderFileEmit | undefined): BuilderFileEmit { + const oldEmitKind = oldOptionsOrEmitKind && (isNumber(oldOptionsOrEmitKind) ? oldOptionsOrEmitKind : getBuilderFileEmit(oldOptionsOrEmitKind)); + const emitKind = isNumber(optionsOrEmitKind) ? optionsOrEmitKind : getBuilderFileEmit(optionsOrEmitKind); + if (oldEmitKind === emitKind) return BuilderFileEmit.None; + if (!oldEmitKind || !emitKind) return emitKind; + const diff = oldEmitKind ^ emitKind; + let result = BuilderFileEmit.None; + // If there is diff in Js emit, pending emit is js emit flags + if (diff & BuilderFileEmit.AllJs) result = emitKind & BuilderFileEmit.AllJs; + // If there is diff in Dts emit, pending emit is dts emit flags + if (diff & BuilderFileEmit.AllDts) result = result | (emitKind & BuilderFileEmit.AllDts); + return result; +} + +function hasSameKeys(map1: ReadonlyCollection | undefined, map2: ReadonlyCollection | undefined): boolean { + // Has same size and every key is present in both maps + return map1 === map2 || map1 !== undefined && map2 !== undefined && map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); +} + +/** + * Create the state so that we can iterate on changedFiles/affected files + */ +function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState: Readonly | undefined, disableUseFileVersionAsSignature: boolean | undefined): BuilderProgramState { + const state = BuilderState.create(newProgram, getCanonicalFileName, oldState, disableUseFileVersionAsSignature) as BuilderProgramState; + state.program = newProgram; + const compilerOptions = newProgram.getCompilerOptions(); + state.compilerOptions = compilerOptions; + const outFilePath = outFile(compilerOptions); + // With --out or --outFile, any change affects all semantic diagnostics so no need to cache them + if (!outFilePath) { + state.semanticDiagnosticsPerFile = new Map(); + } + else if (compilerOptions.composite && oldState?.outSignature && outFilePath === outFile(oldState?.compilerOptions)) { + state.outSignature = oldState.outSignature && getEmitSignatureFromOldSignature(compilerOptions, oldState.compilerOptions, oldState.outSignature); + } + state.changedFilesSet = new Set(); + state.latestChangedDtsFile = compilerOptions.composite ? oldState?.latestChangedDtsFile : undefined; + + const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState); + const oldCompilerOptions = useOldState ? oldState!.compilerOptions : undefined; + const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile && + !compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!); + // We can only reuse emit signatures (i.e. .d.ts signatures) if the .d.ts file is unchanged, + // which will eg be depedent on change in options like declarationDir and outDir options are unchanged. + // We need to look in oldState.compilerOptions, rather than oldCompilerOptions (i.e.we need to disregard useOldState) because + // oldCompilerOptions can be undefined if there was change in say module from None to some other option + // which would make useOldState as false since we can now use reference maps that are needed to track what to emit, what to check etc + // but that option change does not affect d.ts file name so emitSignatures should still be reused. + const canCopyEmitSignatures = compilerOptions.composite && + oldState?.emitSignatures && + !outFilePath && + !compilerOptionsAffectDeclarationPath(compilerOptions, oldState.compilerOptions); + if (useOldState) { + // Copy old state's changed files set + oldState!.changedFilesSet?.forEach(value => state.changedFilesSet.add(value)); + if (!outFilePath && oldState!.affectedFilesPendingEmit?.size) { + state.affectedFilesPendingEmit = new Map(oldState!.affectedFilesPendingEmit); + state.seenAffectedFiles = new Set(); } - else if (compilerOptions.composite && oldState?.outSignature && outFilePath === outFile(oldState?.compilerOptions)) { - state.outSignature = oldState.outSignature && getEmitSignatureFromOldSignature(compilerOptions, oldState.compilerOptions, oldState.outSignature); + state.programEmitPending = oldState!.programEmitPending; + } + else { + // We arent using old state, so atleast emit buildInfo with current information + state.buildInfoEmitPending = true; + } + + // Update changed files and copy semantic diagnostics if we can + const referencedMap = state.referencedMap; + const oldReferencedMap = useOldState ? oldState!.referencedMap : undefined; + const copyDeclarationFileDiagnostics = canCopySemanticDiagnostics && !compilerOptions.skipLibCheck === !oldCompilerOptions!.skipLibCheck; + const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck; + state.fileInfos.forEach((info, sourceFilePath) => { + let oldInfo: Readonly | undefined; + let newReferences: ReadonlySet | undefined; + + // if not using old state, every file is changed + if (!useOldState || + // File wasn't present in old state + !(oldInfo = oldState!.fileInfos.get(sourceFilePath)) || + // versions dont match + oldInfo.version !== info.version || + // Implied formats dont match + oldInfo.impliedFormat !== info.impliedFormat || + // Referenced files changed + !hasSameKeys(newReferences = referencedMap && referencedMap.getValues(sourceFilePath), oldReferencedMap && oldReferencedMap.getValues(sourceFilePath)) || + // Referenced file was deleted in the new program + newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState!.fileInfos.has(path))) { + // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated + addFileToChangeSet(state, sourceFilePath); } - state.changedFilesSet = new Set(); - state.latestChangedDtsFile = compilerOptions.composite ? oldState?.latestChangedDtsFile : undefined; - - const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState); - const oldCompilerOptions = useOldState ? oldState!.compilerOptions : undefined; - const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile && - !compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!); - // We can only reuse emit signatures (i.e. .d.ts signatures) if the .d.ts file is unchanged, - // which will eg be depedent on change in options like declarationDir and outDir options are unchanged. - // We need to look in oldState.compilerOptions, rather than oldCompilerOptions (i.e.we need to disregard useOldState) because - // oldCompilerOptions can be undefined if there was change in say module from None to some other option - // which would make useOldState as false since we can now use reference maps that are needed to track what to emit, what to check etc - // but that option change does not affect d.ts file name so emitSignatures should still be reused. - const canCopyEmitSignatures = compilerOptions.composite && - oldState?.emitSignatures && - !outFilePath && - !compilerOptionsAffectDeclarationPath(compilerOptions, oldState.compilerOptions); - if (useOldState) { - // Copy old state's changed files set - oldState!.changedFilesSet?.forEach(value => state.changedFilesSet.add(value)); - if (!outFilePath && oldState!.affectedFilesPendingEmit?.size) { - state.affectedFilesPendingEmit = new Map(oldState!.affectedFilesPendingEmit); - state.seenAffectedFiles = new Set(); + else if (canCopySemanticDiagnostics) { + const sourceFile = newProgram.getSourceFileByPath(sourceFilePath)!; + + if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) return; + if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) return; + + // Unchanged file copy diagnostics + const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath); + if (diagnostics) { + state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram, getCanonicalFileName) : diagnostics as readonly Diagnostic[]); + if (!state.semanticDiagnosticsFromOldState) { + state.semanticDiagnosticsFromOldState = new Set(); + } + state.semanticDiagnosticsFromOldState.add(sourceFilePath); } - state.programEmitPending = oldState!.programEmitPending; } - else { - // We arent using old state, so atleast emit buildInfo with current information - state.buildInfoEmitPending = true; + if (canCopyEmitSignatures) { + const oldEmitSignature = oldState.emitSignatures.get(sourceFilePath); + if (oldEmitSignature) { + (state.emitSignatures ??= new Map()).set(sourceFilePath, getEmitSignatureFromOldSignature(compilerOptions, oldState.compilerOptions, oldEmitSignature)); + } } + }); - // Update changed files and copy semantic diagnostics if we can - const referencedMap = state.referencedMap; - const oldReferencedMap = useOldState ? oldState!.referencedMap : undefined; - const copyDeclarationFileDiagnostics = canCopySemanticDiagnostics && !compilerOptions.skipLibCheck === !oldCompilerOptions!.skipLibCheck; - const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck; - state.fileInfos.forEach((info, sourceFilePath) => { - let oldInfo: Readonly | undefined; - let newReferences: ReadonlySet | undefined; - - // if not using old state, every file is changed - if (!useOldState || - // File wasn't present in old state - !(oldInfo = oldState!.fileInfos.get(sourceFilePath)) || - // versions dont match - oldInfo.version !== info.version || - // Implied formats dont match - oldInfo.impliedFormat !== info.impliedFormat || - // Referenced files changed - !hasSameKeys(newReferences = referencedMap && referencedMap.getValues(sourceFilePath), oldReferencedMap && oldReferencedMap.getValues(sourceFilePath)) || - // Referenced file was deleted in the new program - newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState!.fileInfos.has(path))) { - // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated - addFileToChangeSet(state, sourceFilePath); - } - else if (canCopySemanticDiagnostics) { - const sourceFile = newProgram.getSourceFileByPath(sourceFilePath)!; - - if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) return; - if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) return; - - // Unchanged file copy diagnostics - const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath); - if (diagnostics) { - state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram, getCanonicalFileName) : diagnostics as readonly Diagnostic[]); - if (!state.semanticDiagnosticsFromOldState) { - state.semanticDiagnosticsFromOldState = new Set(); + // If the global file is removed, add all files as changed + if (useOldState && forEachEntry(oldState!.fileInfos, (info, sourceFilePath) => (outFilePath || info.affectsGlobalScope) && !state.fileInfos.has(sourceFilePath))) { + BuilderState.getAllFilesExcludingDefaultLibraryFile(state, newProgram, /*firstSourceFile*/ undefined) + .forEach(file => addFileToChangeSet(state, file.resolvedPath)); + } + else if (oldCompilerOptions) { + // If options affect emit, then we need to do complete emit per compiler options + // otherwise only the js or dts that needs to emitted because its different from previously emitted options + const pendingEmitKind = compilerOptionsAffectEmit(compilerOptions, oldCompilerOptions) ? + getBuilderFileEmit(compilerOptions) : + getPendingEmitKind(compilerOptions, oldCompilerOptions); + if (pendingEmitKind !== BuilderFileEmit.None) { + if (!outFilePath) { + // Add all files to affectedFilesPendingEmit since emit changed + newProgram.getSourceFiles().forEach(f => { + // Add to affectedFilesPending emit only if not changed since any changed file will do full emit + if (!state.changedFilesSet.has(f.resolvedPath)) { + addToAffectedFilesPendingEmit( + state, + f.resolvedPath, + pendingEmitKind + ); } - state.semanticDiagnosticsFromOldState.add(sourceFilePath); - } - } - if (canCopyEmitSignatures) { - const oldEmitSignature = oldState.emitSignatures.get(sourceFilePath); - if (oldEmitSignature) { - (state.emitSignatures ??= new Map()).set(sourceFilePath, getEmitSignatureFromOldSignature(compilerOptions, oldState.compilerOptions, oldEmitSignature)); - } + }); + Debug.assert(!state.seenAffectedFiles || !state.seenAffectedFiles.size); + state.seenAffectedFiles = state.seenAffectedFiles || new Set(); + state.buildInfoEmitPending = true; } - }); - - // If the global file is removed, add all files as changed - if (useOldState && forEachEntry(oldState!.fileInfos, (info, sourceFilePath) => (outFilePath || info.affectsGlobalScope) && !state.fileInfos.has(sourceFilePath))) { - BuilderState.getAllFilesExcludingDefaultLibraryFile(state, newProgram, /*firstSourceFile*/ undefined) - .forEach(file => addFileToChangeSet(state, file.resolvedPath)); - } - else if (oldCompilerOptions) { - // If options affect emit, then we need to do complete emit per compiler options - // otherwise only the js or dts that needs to emitted because its different from previously emitted options - const pendingEmitKind = compilerOptionsAffectEmit(compilerOptions, oldCompilerOptions) ? - getBuilderFileEmit(compilerOptions) : - getPendingEmitKind(compilerOptions, oldCompilerOptions); - if (pendingEmitKind !== BuilderFileEmit.None) { - if (!outFilePath) { - // Add all files to affectedFilesPendingEmit since emit changed - newProgram.getSourceFiles().forEach(f => { - // Add to affectedFilesPending emit only if not changed since any changed file will do full emit - if (!state.changedFilesSet.has(f.resolvedPath)) { - addToAffectedFilesPendingEmit( - state, - f.resolvedPath, - pendingEmitKind - ); - } - }); - Debug.assert(!state.seenAffectedFiles || !state.seenAffectedFiles.size); - state.seenAffectedFiles = state.seenAffectedFiles || new Set(); - state.buildInfoEmitPending = true; - } - else { - state.programEmitPending = state.programEmitPending ? - state.programEmitPending | pendingEmitKind : - pendingEmitKind; - } + else { + state.programEmitPending = state.programEmitPending ? + state.programEmitPending | pendingEmitKind : + pendingEmitKind; } } - if (outFilePath && !state.changedFilesSet.size) { - // Copy the bundle information from old state so we can patch it later if we are doing partial emit - if (useOldState) state.bundle = oldState!.bundle; - // If this program has prepend references, always emit since we wont know if files on disk are correct unless we check file hash for correctness - if (some(newProgram.getProjectReferences(), ref => !!ref.prepend)) state.programEmitPending = getBuilderFileEmit(compilerOptions); - } - return state; } - - function addFileToChangeSet(state: BuilderProgramState, path: Path) { - state.changedFilesSet.add(path); - state.buildInfoEmitPending = true; - // Setting this to undefined as changed files means full emit so no need to track emit explicitly - state.programEmitPending = undefined; + if (outFilePath && !state.changedFilesSet.size) { + // Copy the bundle information from old state so we can patch it later if we are doing partial emit + if (useOldState) state.bundle = oldState!.bundle; + // If this program has prepend references, always emit since we wont know if files on disk are correct unless we check file hash for correctness + if (some(newProgram.getProjectReferences(), ref => !!ref.prepend)) state.programEmitPending = getBuilderFileEmit(compilerOptions); } + return state; +} - /** - * Covert to Emit signature based on oldOptions and EmitSignature format - * If d.ts map options differ then swap the format, otherwise use as is - */ - function getEmitSignatureFromOldSignature(options: CompilerOptions, oldOptions: CompilerOptions, oldEmitSignature: EmitSignature): EmitSignature { - return !!options.declarationMap === !!oldOptions.declarationMap ? - // Use same format of signature - oldEmitSignature : - // Convert to different format - isString(oldEmitSignature) ? [oldEmitSignature] : oldEmitSignature[0]; - } +function addFileToChangeSet(state: BuilderProgramState, path: Path) { + state.changedFilesSet.add(path); + state.buildInfoEmitPending = true; + // Setting this to undefined as changed files means full emit so no need to track emit explicitly + state.programEmitPending = undefined; +} - function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program, getCanonicalFileName: GetCanonicalFileName): readonly Diagnostic[] { - if (!diagnostics.length) return emptyArray; - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(newProgram.getCompilerOptions())!, newProgram.getCurrentDirectory())); - return diagnostics.map(diagnostic => { - const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath); - result.reportsUnnecessary = diagnostic.reportsUnnecessary; - result.reportsDeprecated = diagnostic.reportDeprecated; - result.source = diagnostic.source; - result.skippedOn = diagnostic.skippedOn; - const { relatedInformation } = diagnostic; - result.relatedInformation = relatedInformation ? - relatedInformation.length ? - relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram, toPath)) : - [] : - undefined; - return result; - }); +/** + * Covert to Emit signature based on oldOptions and EmitSignature format + * If d.ts map options differ then swap the format, otherwise use as is + */ +function getEmitSignatureFromOldSignature(options: CompilerOptions, oldOptions: CompilerOptions, oldEmitSignature: EmitSignature): EmitSignature { + return !!options.declarationMap === !!oldOptions.declarationMap ? + // Use same format of signature + oldEmitSignature : + // Convert to different format + isString(oldEmitSignature) ? [oldEmitSignature] : oldEmitSignature[0]; +} - function toPath(path: string) { - return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); - } - } +function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program, getCanonicalFileName: GetCanonicalFileName): readonly Diagnostic[] { + if (!diagnostics.length) return emptyArray; + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(newProgram.getCompilerOptions())!, newProgram.getCurrentDirectory())); + return diagnostics.map(diagnostic => { + const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath); + result.reportsUnnecessary = diagnostic.reportsUnnecessary; + result.reportsDeprecated = diagnostic.reportDeprecated; + result.source = diagnostic.source; + result.skippedOn = diagnostic.skippedOn; + const { relatedInformation } = diagnostic; + result.relatedInformation = relatedInformation ? + relatedInformation.length ? + relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram, toPath)) : + [] : + undefined; + return result; + }); - function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation { - const { file } = diagnostic; - return { - ...diagnostic, - file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined - }; + function toPath(path: string) { + return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); } +} - /** - * Releases program and other related not needed properties - */ - function releaseCache(state: BuilderProgramState) { - BuilderState.releaseCache(state); - state.program = undefined; - } +function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation { + const { file } = diagnostic; + return { + ...diagnostic, + file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined + }; +} - function backupBuilderProgramEmitState(state: Readonly): SavedBuildProgramEmitState { - const outFilePath = outFile(state.compilerOptions); - // Only in --out changeFileSet is kept around till emit - Debug.assert(!state.changedFilesSet.size || outFilePath); - return { - affectedFilesPendingEmit: state.affectedFilesPendingEmit && new Map(state.affectedFilesPendingEmit), - seenEmittedFiles: state.seenEmittedFiles && new Map(state.seenEmittedFiles), - programEmitPending: state.programEmitPending, - emitSignatures: state.emitSignatures && new Map(state.emitSignatures), - outSignature: state.outSignature, - latestChangedDtsFile: state.latestChangedDtsFile, - hasChangedEmitSignature: state.hasChangedEmitSignature, - changedFilesSet: outFilePath ? new Set(state.changedFilesSet) : undefined, - }; - } +/** + * Releases program and other related not needed properties + */ +function releaseCache(state: BuilderProgramState) { + BuilderState.releaseCache(state); + state.program = undefined; +} - function restoreBuilderProgramEmitState(state: BuilderProgramState, savedEmitState: SavedBuildProgramEmitState) { - state.affectedFilesPendingEmit = savedEmitState.affectedFilesPendingEmit; - state.seenEmittedFiles = savedEmitState.seenEmittedFiles; - state.programEmitPending = savedEmitState.programEmitPending; - state.emitSignatures = savedEmitState.emitSignatures; - state.outSignature = savedEmitState.outSignature; - state.latestChangedDtsFile = savedEmitState.latestChangedDtsFile; - state.hasChangedEmitSignature = savedEmitState.hasChangedEmitSignature; - if (savedEmitState.changedFilesSet) state.changedFilesSet = savedEmitState.changedFilesSet; - } +function backupBuilderProgramEmitState(state: Readonly): SavedBuildProgramEmitState { + const outFilePath = outFile(state.compilerOptions); + // Only in --out changeFileSet is kept around till emit + Debug.assert(!state.changedFilesSet.size || outFilePath); + return { + affectedFilesPendingEmit: state.affectedFilesPendingEmit && new Map(state.affectedFilesPendingEmit), + seenEmittedFiles: state.seenEmittedFiles && new Map(state.seenEmittedFiles), + programEmitPending: state.programEmitPending, + emitSignatures: state.emitSignatures && new Map(state.emitSignatures), + outSignature: state.outSignature, + latestChangedDtsFile: state.latestChangedDtsFile, + hasChangedEmitSignature: state.hasChangedEmitSignature, + changedFilesSet: outFilePath ? new Set(state.changedFilesSet) : undefined, + }; +} - /** - * Verifies that source file is ok to be used in calls that arent handled by next - */ - function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, sourceFile: SourceFile | undefined) { - Debug.assert(!sourceFile || !state.affectedFiles || state.affectedFiles[state.affectedFilesIndex! - 1] !== sourceFile || !state.semanticDiagnosticsPerFile!.has(sourceFile.resolvedPath)); - } +function restoreBuilderProgramEmitState(state: BuilderProgramState, savedEmitState: SavedBuildProgramEmitState) { + state.affectedFilesPendingEmit = savedEmitState.affectedFilesPendingEmit; + state.seenEmittedFiles = savedEmitState.seenEmittedFiles; + state.programEmitPending = savedEmitState.programEmitPending; + state.emitSignatures = savedEmitState.emitSignatures; + state.outSignature = savedEmitState.outSignature; + state.latestChangedDtsFile = savedEmitState.latestChangedDtsFile; + state.hasChangedEmitSignature = savedEmitState.hasChangedEmitSignature; + if (savedEmitState.changedFilesSet) state.changedFilesSet = savedEmitState.changedFilesSet; +} - /** - * This function returns the next affected file to be processed. - * Note that until doneAffected is called it would keep reporting same result - * This is to allow the callers to be able to actually remove affected file only when the operation is complete - * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained - */ - function getNextAffectedFile( - state: BuilderProgramState, - cancellationToken: CancellationToken | undefined, - computeHash: BuilderState.ComputeHash, - getCanonicalFileName: GetCanonicalFileName, - host: BuilderProgramHost - ): SourceFile | Program | undefined { - while (true) { - const { affectedFiles } = state; - if (affectedFiles) { - const seenAffectedFiles = state.seenAffectedFiles!; - let affectedFilesIndex = state.affectedFilesIndex!; // TODO: GH#18217 - while (affectedFilesIndex < affectedFiles.length) { - const affectedFile = affectedFiles[affectedFilesIndex]; - if (!seenAffectedFiles.has(affectedFile.resolvedPath)) { - // Set the next affected file as seen and remove the cached semantic diagnostics - state.affectedFilesIndex = affectedFilesIndex; - addToAffectedFilesPendingEmit(state, affectedFile.resolvedPath, getBuilderFileEmit(state.compilerOptions)); - handleDtsMayChangeOfAffectedFile( - state, - affectedFile, - cancellationToken, - computeHash, - getCanonicalFileName, - host - ); - return affectedFile; - } - affectedFilesIndex++; - } +/** + * Verifies that source file is ok to be used in calls that arent handled by next + */ +function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, sourceFile: SourceFile | undefined) { + Debug.assert(!sourceFile || !state.affectedFiles || state.affectedFiles[state.affectedFilesIndex! - 1] !== sourceFile || !state.semanticDiagnosticsPerFile!.has(sourceFile.resolvedPath)); +} - // Remove the changed file from the change set - state.changedFilesSet.delete(state.currentChangedFilePath!); - state.currentChangedFilePath = undefined; - // Commit the changes in file signature - state.oldSignatures?.clear(); - state.oldExportedModulesMap?.clear(); - state.affectedFiles = undefined; +/** + * This function returns the next affected file to be processed. + * Note that until doneAffected is called it would keep reporting same result + * This is to allow the callers to be able to actually remove affected file only when the operation is complete + * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained + */ +function getNextAffectedFile( + state: BuilderProgramState, + cancellationToken: CancellationToken | undefined, + computeHash: BuilderState.ComputeHash, + getCanonicalFileName: GetCanonicalFileName, + host: BuilderProgramHost +): SourceFile | Program | undefined { + while (true) { + const { affectedFiles } = state; + if (affectedFiles) { + const seenAffectedFiles = state.seenAffectedFiles!; + let affectedFilesIndex = state.affectedFilesIndex!; // TODO: GH#18217 + while (affectedFilesIndex < affectedFiles.length) { + const affectedFile = affectedFiles[affectedFilesIndex]; + if (!seenAffectedFiles.has(affectedFile.resolvedPath)) { + // Set the next affected file as seen and remove the cached semantic diagnostics + state.affectedFilesIndex = affectedFilesIndex; + addToAffectedFilesPendingEmit(state, affectedFile.resolvedPath, getBuilderFileEmit(state.compilerOptions)); + handleDtsMayChangeOfAffectedFile( + state, + affectedFile, + cancellationToken, + computeHash, + getCanonicalFileName, + host + ); + return affectedFile; + } + affectedFilesIndex++; } - // Get next changed file - const nextKey = state.changedFilesSet.keys().next(); - if (nextKey.done) { - // Done - return undefined; - } + // Remove the changed file from the change set + state.changedFilesSet.delete(state.currentChangedFilePath!); + state.currentChangedFilePath = undefined; + // Commit the changes in file signature + state.oldSignatures?.clear(); + state.oldExportedModulesMap?.clear(); + state.affectedFiles = undefined; + } - // With --out or --outFile all outputs go into single file - // so operations are performed directly on program, return program - const program = Debug.checkDefined(state.program); - const compilerOptions = program.getCompilerOptions(); - if (outFile(compilerOptions)) { - Debug.assert(!state.semanticDiagnosticsPerFile); - return program; - } + // Get next changed file + const nextKey = state.changedFilesSet.keys().next(); + if (nextKey.done) { + // Done + return undefined; + } - // Get next batch of affected files - state.affectedFiles = BuilderState.getFilesAffectedByWithOldState( - state, - program, - nextKey.value, - cancellationToken, - computeHash, - getCanonicalFileName, - ); - state.currentChangedFilePath = nextKey.value; - state.affectedFilesIndex = 0; - if (!state.seenAffectedFiles) state.seenAffectedFiles = new Set(); + // With --out or --outFile all outputs go into single file + // so operations are performed directly on program, return program + const program = Debug.checkDefined(state.program); + const compilerOptions = program.getCompilerOptions(); + if (outFile(compilerOptions)) { + Debug.assert(!state.semanticDiagnosticsPerFile); + return program; } - } - function clearAffectedFilesPendingEmit(state: BuilderProgramState, emitOnlyDtsFiles: boolean | undefined) { - if (!state.affectedFilesPendingEmit?.size) return; - if (!emitOnlyDtsFiles) return state.affectedFilesPendingEmit = undefined; - state.affectedFilesPendingEmit.forEach((emitKind, path) => { - // Mark the files as pending only if they are pending on js files, remove the dts emit pending flag - const pending = emitKind & BuilderFileEmit.AllJs; - if (!pending) state.affectedFilesPendingEmit!.delete(path); - else state.affectedFilesPendingEmit!.set(path, pending); - }); + // Get next batch of affected files + state.affectedFiles = BuilderState.getFilesAffectedByWithOldState( + state, + program, + nextKey.value, + cancellationToken, + computeHash, + getCanonicalFileName, + ); + state.currentChangedFilePath = nextKey.value; + state.affectedFilesIndex = 0; + if (!state.seenAffectedFiles) state.seenAffectedFiles = new Set(); } +} - /** - * Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet - */ - function getNextAffectedFilePendingEmit(state: BuilderProgramState, emitOnlyDtsFiles: boolean | undefined) { - if (!state.affectedFilesPendingEmit?.size) return undefined; - return forEachEntry(state.affectedFilesPendingEmit, (emitKind, path) => { - const affectedFile = state.program!.getSourceFileByPath(path); - if (!affectedFile || !sourceFileMayBeEmitted(affectedFile, state.program!)) { - state.affectedFilesPendingEmit!.delete(path); - return undefined; - } - const seenKind = state.seenEmittedFiles?.get(affectedFile.resolvedPath); - let pendingKind = getPendingEmitKind(emitKind, seenKind); - if (emitOnlyDtsFiles) pendingKind = pendingKind & BuilderFileEmit.AllDts; - if (pendingKind) return { affectedFile, emitKind: pendingKind }; - }); - } +function clearAffectedFilesPendingEmit(state: BuilderProgramState, emitOnlyDtsFiles: boolean | undefined) { + if (!state.affectedFilesPendingEmit?.size) return; + if (!emitOnlyDtsFiles) return state.affectedFilesPendingEmit = undefined; + state.affectedFilesPendingEmit.forEach((emitKind, path) => { + // Mark the files as pending only if they are pending on js files, remove the dts emit pending flag + const pending = emitKind & BuilderFileEmit.AllJs; + if (!pending) state.affectedFilesPendingEmit!.delete(path); + else state.affectedFilesPendingEmit!.set(path, pending); + }); +} - function removeDiagnosticsOfLibraryFiles(state: BuilderProgramState) { - if (!state.cleanedDiagnosticsOfLibFiles) { - state.cleanedDiagnosticsOfLibFiles = true; - const program = Debug.checkDefined(state.program); - const options = program.getCompilerOptions(); - forEach(program.getSourceFiles(), f => - program.isSourceFileDefaultLibrary(f) && - !skipTypeChecking(f, options, program) && - removeSemanticDiagnosticsOf(state, f.resolvedPath) - ); +/** + * Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet + */ +function getNextAffectedFilePendingEmit(state: BuilderProgramState, emitOnlyDtsFiles: boolean | undefined) { + if (!state.affectedFilesPendingEmit?.size) return undefined; + return forEachEntry(state.affectedFilesPendingEmit, (emitKind, path) => { + const affectedFile = state.program!.getSourceFileByPath(path); + if (!affectedFile || !sourceFileMayBeEmitted(affectedFile, state.program!)) { + state.affectedFilesPendingEmit!.delete(path); + return undefined; } + const seenKind = state.seenEmittedFiles?.get(affectedFile.resolvedPath); + let pendingKind = getPendingEmitKind(emitKind, seenKind); + if (emitOnlyDtsFiles) pendingKind = pendingKind & BuilderFileEmit.AllDts; + if (pendingKind) return { affectedFile, emitKind: pendingKind }; + }); +} + +function removeDiagnosticsOfLibraryFiles(state: BuilderProgramState) { + if (!state.cleanedDiagnosticsOfLibFiles) { + state.cleanedDiagnosticsOfLibFiles = true; + const program = Debug.checkDefined(state.program); + const options = program.getCompilerOptions(); + forEach(program.getSourceFiles(), f => + program.isSourceFileDefaultLibrary(f) && + !skipTypeChecking(f, options, program) && + removeSemanticDiagnosticsOf(state, f.resolvedPath) + ); } +} - /** - * Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file - * This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change - */ - function handleDtsMayChangeOfAffectedFile( - state: BuilderProgramState, - affectedFile: SourceFile, - cancellationToken: CancellationToken | undefined, - computeHash: BuilderState.ComputeHash, - getCanonicalFileName: GetCanonicalFileName, - host: BuilderProgramHost, - ) { - removeSemanticDiagnosticsOf(state, affectedFile.resolvedPath); - - // If affected files is everything except default library, then nothing more to do - if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { - removeDiagnosticsOfLibraryFiles(state); - // When a change affects the global scope, all files are considered to be affected without updating their signature - // That means when affected file is handled, its signature can be out of date - // To avoid this, ensure that we update the signature for any affected file in this scenario. - BuilderState.updateShapeSignature( - state, - Debug.checkDefined(state.program), - affectedFile, - cancellationToken, - computeHash, - getCanonicalFileName, - ); - return; - } - if (state.compilerOptions.assumeChangesOnlyAffectDirectDependencies) return; - handleDtsMayChangeOfReferencingExportOfAffectedFile( +/** + * Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file + * This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change + */ +function handleDtsMayChangeOfAffectedFile( + state: BuilderProgramState, + affectedFile: SourceFile, + cancellationToken: CancellationToken | undefined, + computeHash: BuilderState.ComputeHash, + getCanonicalFileName: GetCanonicalFileName, + host: BuilderProgramHost, +) { + removeSemanticDiagnosticsOf(state, affectedFile.resolvedPath); + + // If affected files is everything except default library, then nothing more to do + if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { + removeDiagnosticsOfLibraryFiles(state); + // When a change affects the global scope, all files are considered to be affected without updating their signature + // That means when affected file is handled, its signature can be out of date + // To avoid this, ensure that we update the signature for any affected file in this scenario. + BuilderState.updateShapeSignature( state, + Debug.checkDefined(state.program), affectedFile, cancellationToken, computeHash, getCanonicalFileName, - host, ); + return; } + if (state.compilerOptions.assumeChangesOnlyAffectDirectDependencies) return; + handleDtsMayChangeOfReferencingExportOfAffectedFile( + state, + affectedFile, + cancellationToken, + computeHash, + getCanonicalFileName, + host, + ); +} - /** - * Handle the dts may change, so they need to be added to pending emit if dts emit is enabled, - * Also we need to make sure signature is updated for these files - */ - function handleDtsMayChangeOf( - state: BuilderProgramState, - path: Path, - cancellationToken: CancellationToken | undefined, - computeHash: BuilderState.ComputeHash, - getCanonicalFileName: GetCanonicalFileName, - host: BuilderProgramHost - ): void { - removeSemanticDiagnosticsOf(state, path); - - if (!state.changedFilesSet.has(path)) { - const program = Debug.checkDefined(state.program); - const sourceFile = program.getSourceFileByPath(path); - if (sourceFile) { - // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics - // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file - // This ensures that we dont later during incremental builds considering wrong signature. - // Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build - // But we avoid expensive full shape computation, as using file version as shape is enough for correctness. - BuilderState.updateShapeSignature( - state, - program, - sourceFile, - cancellationToken, - computeHash, - getCanonicalFileName, - !host.disableUseFileVersionAsSignature - ); - // If not dts emit, nothing more to do - if (getEmitDeclarations(state.compilerOptions)) { - addToAffectedFilesPendingEmit(state, path, state.compilerOptions.declarationMap ? BuilderFileEmit.AllDts : BuilderFileEmit.Dts); - } +/** + * Handle the dts may change, so they need to be added to pending emit if dts emit is enabled, + * Also we need to make sure signature is updated for these files + */ +function handleDtsMayChangeOf( + state: BuilderProgramState, + path: Path, + cancellationToken: CancellationToken | undefined, + computeHash: BuilderState.ComputeHash, + getCanonicalFileName: GetCanonicalFileName, + host: BuilderProgramHost +): void { + removeSemanticDiagnosticsOf(state, path); + + if (!state.changedFilesSet.has(path)) { + const program = Debug.checkDefined(state.program); + const sourceFile = program.getSourceFileByPath(path); + if (sourceFile) { + // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics + // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file + // This ensures that we dont later during incremental builds considering wrong signature. + // Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build + // But we avoid expensive full shape computation, as using file version as shape is enough for correctness. + BuilderState.updateShapeSignature( + state, + program, + sourceFile, + cancellationToken, + computeHash, + getCanonicalFileName, + !host.disableUseFileVersionAsSignature + ); + // If not dts emit, nothing more to do + if (getEmitDeclarations(state.compilerOptions)) { + addToAffectedFilesPendingEmit(state, path, state.compilerOptions.declarationMap ? BuilderFileEmit.AllDts : BuilderFileEmit.Dts); } } } +} - /** - * Removes semantic diagnostics for path and - * returns true if there are no more semantic diagnostics from the old state - */ - function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { - if (!state.semanticDiagnosticsFromOldState) { - return true; - } - state.semanticDiagnosticsFromOldState.delete(path); - state.semanticDiagnosticsPerFile!.delete(path); - return !state.semanticDiagnosticsFromOldState.size; +/** + * Removes semantic diagnostics for path and + * returns true if there are no more semantic diagnostics from the old state + */ +function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { + if (!state.semanticDiagnosticsFromOldState) { + return true; } + state.semanticDiagnosticsFromOldState.delete(path); + state.semanticDiagnosticsPerFile!.delete(path); + return !state.semanticDiagnosticsFromOldState.size; +} - function isChangedSignature(state: BuilderProgramState, path: Path) { - const oldSignature = Debug.checkDefined(state.oldSignatures).get(path) || undefined; - const newSignature = Debug.checkDefined(state.fileInfos.get(path)).signature; - return newSignature !== oldSignature; - } +function isChangedSignature(state: BuilderProgramState, path: Path) { + const oldSignature = Debug.checkDefined(state.oldSignatures).get(path) || undefined; + const newSignature = Debug.checkDefined(state.fileInfos.get(path)).signature; + return newSignature !== oldSignature; +} - function handleDtsMayChangeOfGlobalScope( - state: BuilderProgramState, - filePath: Path, - cancellationToken: CancellationToken | undefined, - computeHash: BuilderState.ComputeHash, - getCanonicalFileName: GetCanonicalFileName, - host: BuilderProgramHost, - ): boolean { - if (!state.fileInfos.get(filePath)?.affectsGlobalScope) return false; - // Every file needs to be handled - BuilderState.getAllFilesExcludingDefaultLibraryFile(state, state.program!, /*firstSourceFile*/ undefined) - .forEach(file => handleDtsMayChangeOf( - state, - file.resolvedPath, - cancellationToken, - computeHash, - getCanonicalFileName, - host, - )); - removeDiagnosticsOfLibraryFiles(state); - return true; - } +function handleDtsMayChangeOfGlobalScope( + state: BuilderProgramState, + filePath: Path, + cancellationToken: CancellationToken | undefined, + computeHash: BuilderState.ComputeHash, + getCanonicalFileName: GetCanonicalFileName, + host: BuilderProgramHost, +): boolean { + if (!state.fileInfos.get(filePath)?.affectsGlobalScope) return false; + // Every file needs to be handled + BuilderState.getAllFilesExcludingDefaultLibraryFile(state, state.program!, /*firstSourceFile*/ undefined) + .forEach(file => handleDtsMayChangeOf( + state, + file.resolvedPath, + cancellationToken, + computeHash, + getCanonicalFileName, + host, + )); + removeDiagnosticsOfLibraryFiles(state); + return true; +} - /** - * Iterate on referencing modules that export entities from affected file and delete diagnostics and add pending emit - */ - function handleDtsMayChangeOfReferencingExportOfAffectedFile( - state: BuilderProgramState, - affectedFile: SourceFile, - cancellationToken: CancellationToken | undefined, - computeHash: BuilderState.ComputeHash, - getCanonicalFileName: GetCanonicalFileName, - host: BuilderProgramHost - ) { - // If there was change in signature (dts output) for the changed file, - // then only we need to handle pending file emit - if (!state.exportedModulesMap || !state.changedFilesSet.has(affectedFile.resolvedPath)) return; - if (!isChangedSignature(state, affectedFile.resolvedPath)) return; - - // Since isolated modules dont change js files, files affected by change in signature is itself - // But we need to cleanup semantic diagnostics and queue dts emit for affected files - if (state.compilerOptions.isolatedModules) { - const seenFileNamesMap = new Map(); - seenFileNamesMap.set(affectedFile.resolvedPath, true); - const queue = BuilderState.getReferencedByPaths(state, affectedFile.resolvedPath); - while (queue.length > 0) { - const currentPath = queue.pop()!; - if (!seenFileNamesMap.has(currentPath)) { - seenFileNamesMap.set(currentPath, true); - if (handleDtsMayChangeOfGlobalScope(state, currentPath, cancellationToken, computeHash, getCanonicalFileName, host)) return; - handleDtsMayChangeOf(state, currentPath, cancellationToken, computeHash, getCanonicalFileName, host); - if (isChangedSignature(state, currentPath)) { - const currentSourceFile = Debug.checkDefined(state.program).getSourceFileByPath(currentPath)!; - queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath)); - } +/** + * Iterate on referencing modules that export entities from affected file and delete diagnostics and add pending emit + */ +function handleDtsMayChangeOfReferencingExportOfAffectedFile( + state: BuilderProgramState, + affectedFile: SourceFile, + cancellationToken: CancellationToken | undefined, + computeHash: BuilderState.ComputeHash, + getCanonicalFileName: GetCanonicalFileName, + host: BuilderProgramHost +) { + // If there was change in signature (dts output) for the changed file, + // then only we need to handle pending file emit + if (!state.exportedModulesMap || !state.changedFilesSet.has(affectedFile.resolvedPath)) return; + if (!isChangedSignature(state, affectedFile.resolvedPath)) return; + + // Since isolated modules dont change js files, files affected by change in signature is itself + // But we need to cleanup semantic diagnostics and queue dts emit for affected files + if (state.compilerOptions.isolatedModules) { + const seenFileNamesMap = new Map(); + seenFileNamesMap.set(affectedFile.resolvedPath, true); + const queue = BuilderState.getReferencedByPaths(state, affectedFile.resolvedPath); + while (queue.length > 0) { + const currentPath = queue.pop()!; + if (!seenFileNamesMap.has(currentPath)) { + seenFileNamesMap.set(currentPath, true); + if (handleDtsMayChangeOfGlobalScope(state, currentPath, cancellationToken, computeHash, getCanonicalFileName, host)) return; + handleDtsMayChangeOf(state, currentPath, cancellationToken, computeHash, getCanonicalFileName, host); + if (isChangedSignature(state, currentPath)) { + const currentSourceFile = Debug.checkDefined(state.program).getSourceFileByPath(currentPath)!; + queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath)); } } } - - const seenFileAndExportsOfFile = new Set(); - // Go through exported modules from cache first - // If exported modules has path, all files referencing file exported from are affected - state.exportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => { - if (handleDtsMayChangeOfGlobalScope(state, exportedFromPath, cancellationToken, computeHash, getCanonicalFileName, host)) return true; - const references = state.referencedMap!.getKeys(exportedFromPath); - return references && forEachKey(references, filePath => - handleDtsMayChangeOfFileAndExportsOfFile( - state, - filePath, - seenFileAndExportsOfFile, - cancellationToken, - computeHash, - getCanonicalFileName, - host, - ) - ); - }); } - /** - * handle dts and semantic diagnostics on file and iterate on anything that exports this file - * return true when all work is done and we can exit handling dts emit and semantic diagnostics - */ - function handleDtsMayChangeOfFileAndExportsOfFile( - state: BuilderProgramState, - filePath: Path, - seenFileAndExportsOfFile: Set, - cancellationToken: CancellationToken | undefined, - computeHash: BuilderState.ComputeHash, - getCanonicalFileName: GetCanonicalFileName, - host: BuilderProgramHost, - ): boolean | undefined { - if (!tryAddToSet(seenFileAndExportsOfFile, filePath)) return undefined; - - if (handleDtsMayChangeOfGlobalScope(state, filePath, cancellationToken, computeHash, getCanonicalFileName, host)) return true; - handleDtsMayChangeOf(state, filePath, cancellationToken, computeHash, getCanonicalFileName, host); - - // If exported modules has path, all files referencing file exported from are affected - state.exportedModulesMap!.getKeys(filePath)?.forEach(exportedFromPath => + const seenFileAndExportsOfFile = new Set(); + // Go through exported modules from cache first + // If exported modules has path, all files referencing file exported from are affected + state.exportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => { + if (handleDtsMayChangeOfGlobalScope(state, exportedFromPath, cancellationToken, computeHash, getCanonicalFileName, host)) return true; + const references = state.referencedMap!.getKeys(exportedFromPath); + return references && forEachKey(references, filePath => handleDtsMayChangeOfFileAndExportsOfFile( state, - exportedFromPath, + filePath, seenFileAndExportsOfFile, cancellationToken, computeHash, @@ -740,969 +743,1034 @@ namespace ts { host, ) ); + }); +} - // Remove diagnostics of files that import this file (without going to exports of referencing files) - state.referencedMap!.getKeys(filePath)?.forEach(referencingFilePath => - !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file - handleDtsMayChangeOf( // Dont add to seen since this is not yet done with the export removal - state, - referencingFilePath, - cancellationToken, - computeHash, - getCanonicalFileName, - host, - ) - ); - return undefined; - } +/** + * handle dts and semantic diagnostics on file and iterate on anything that exports this file + * return true when all work is done and we can exit handling dts emit and semantic diagnostics + */ +function handleDtsMayChangeOfFileAndExportsOfFile( + state: BuilderProgramState, + filePath: Path, + seenFileAndExportsOfFile: Set, + cancellationToken: CancellationToken | undefined, + computeHash: BuilderState.ComputeHash, + getCanonicalFileName: GetCanonicalFileName, + host: BuilderProgramHost, +): boolean | undefined { + if (!tryAddToSet(seenFileAndExportsOfFile, filePath)) return undefined; + + if (handleDtsMayChangeOfGlobalScope(state, filePath, cancellationToken, computeHash, getCanonicalFileName, host)) return true; + handleDtsMayChangeOf(state, filePath, cancellationToken, computeHash, getCanonicalFileName, host); + + // If exported modules has path, all files referencing file exported from are affected + state.exportedModulesMap!.getKeys(filePath)?.forEach(exportedFromPath => + handleDtsMayChangeOfFileAndExportsOfFile( + state, + exportedFromPath, + seenFileAndExportsOfFile, + cancellationToken, + computeHash, + getCanonicalFileName, + host, + ) + ); - /** - * Gets semantic diagnostics for the file which are - * bindAndCheckDiagnostics (from cache) and program diagnostics - */ - function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - return concatenate( - getBinderAndCheckerDiagnosticsOfFile(state, sourceFile, cancellationToken), - Debug.checkDefined(state.program).getProgramDiagnostics(sourceFile) - ); - } + // Remove diagnostics of files that import this file (without going to exports of referencing files) + state.referencedMap!.getKeys(filePath)?.forEach(referencingFilePath => + !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file + handleDtsMayChangeOf( // Dont add to seen since this is not yet done with the export removal + state, + referencingFilePath, + cancellationToken, + computeHash, + getCanonicalFileName, + host, + ) + ); + return undefined; +} - /** - * Gets the binder and checker diagnostics either from cache if present, or otherwise from program and caches it - * Note that it is assumed that when asked about binder and checker diagnostics, the file has been taken out of affected files/changed file set - */ - function getBinderAndCheckerDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - const path = sourceFile.resolvedPath; - if (state.semanticDiagnosticsPerFile) { - const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); - // Report the bind and check diagnostics from the cache if we already have those diagnostics present - if (cachedDiagnostics) { - return filterSemanticDiagnostics(cachedDiagnostics, state.compilerOptions); - } - } +/** + * Gets semantic diagnostics for the file which are + * bindAndCheckDiagnostics (from cache) and program diagnostics + */ +function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + return concatenate( + getBinderAndCheckerDiagnosticsOfFile(state, sourceFile, cancellationToken), + Debug.checkDefined(state.program).getProgramDiagnostics(sourceFile) + ); +} - // Diagnostics werent cached, get them from program, and cache the result - const diagnostics = Debug.checkDefined(state.program).getBindAndCheckDiagnostics(sourceFile, cancellationToken); - if (state.semanticDiagnosticsPerFile) { - state.semanticDiagnosticsPerFile.set(path, diagnostics); +/** + * Gets the binder and checker diagnostics either from cache if present, or otherwise from program and caches it + * Note that it is assumed that when asked about binder and checker diagnostics, the file has been taken out of affected files/changed file set + */ +function getBinderAndCheckerDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + const path = sourceFile.resolvedPath; + if (state.semanticDiagnosticsPerFile) { + const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); + // Report the bind and check diagnostics from the cache if we already have those diagnostics present + if (cachedDiagnostics) { + return filterSemanticDiagnostics(cachedDiagnostics, state.compilerOptions); } - return filterSemanticDiagnostics(diagnostics, state.compilerOptions); } - export type ProgramBuildInfoFileId = number & { __programBuildInfoFileIdBrand: any }; - export type ProgramBuildInfoFileIdListId = number & { __programBuildInfoFileIdListIdBrand: any }; - export type ProgramBuildInfoDiagnostic = ProgramBuildInfoFileId | [fileId: ProgramBuildInfoFileId, diagnostics: readonly ReusableDiagnostic[]]; - /** - * fileId if pending emit is same as what compilerOptions suggest - * [fileId] if pending emit is only dts file emit - * [fileId, emitKind] if any other type emit is pending - */ - export type ProgramBuilderInfoFilePendingEmit = ProgramBuildInfoFileId | [fileId: ProgramBuildInfoFileId] | [fileId: ProgramBuildInfoFileId, emitKind: BuilderFileEmit]; - export type ProgramBuildInfoReferencedMap = [fileId: ProgramBuildInfoFileId, fileIdListId: ProgramBuildInfoFileIdListId][]; - export type ProgramMultiFileEmitBuildInfoBuilderStateFileInfo = Omit & { - /** - * Signature is - * - undefined if FileInfo.version === FileInfo.signature - * - false if FileInfo has signature as undefined (not calculated) - * - string actual signature - */ - signature: string | false | undefined; - }; - /** - * [fileId, signature] if different from file's signature - * fileId if file wasnt emitted - */ - export type ProgramBuildInfoEmitSignature = ProgramBuildInfoFileId | [fileId: ProgramBuildInfoFileId, signature: EmitSignature | []]; - /** - * ProgramMultiFileEmitBuildInfoFileInfo is string if FileInfo.version === FileInfo.signature && !FileInfo.affectsGlobalScope otherwise encoded FileInfo - */ - export type ProgramMultiFileEmitBuildInfoFileInfo = string | ProgramMultiFileEmitBuildInfoBuilderStateFileInfo; - export interface ProgramMultiFileEmitBuildInfo { - fileNames: readonly string[]; - fileInfos: readonly ProgramMultiFileEmitBuildInfoFileInfo[]; - options: CompilerOptions | undefined; - fileIdsList: readonly (readonly ProgramBuildInfoFileId[])[] | undefined; - referencedMap: ProgramBuildInfoReferencedMap | undefined; - exportedModulesMap: ProgramBuildInfoReferencedMap | undefined; - semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] | undefined; - affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] | undefined; - changeFileSet: readonly ProgramBuildInfoFileId[] | undefined; - emitSignatures: readonly ProgramBuildInfoEmitSignature[] | undefined; - // Because this is only output file in the program, we dont need fileId to deduplicate name - latestChangedDtsFile?: string | undefined; + // Diagnostics werent cached, get them from program, and cache the result + const diagnostics = Debug.checkDefined(state.program).getBindAndCheckDiagnostics(sourceFile, cancellationToken); + if (state.semanticDiagnosticsPerFile) { + state.semanticDiagnosticsPerFile.set(path, diagnostics); } + return filterSemanticDiagnostics(diagnostics, state.compilerOptions); +} + +/** @internal */ +export type ProgramBuildInfoFileId = number & { __programBuildInfoFileIdBrand: any }; +/** @internal */ +export type ProgramBuildInfoFileIdListId = number & { __programBuildInfoFileIdListIdBrand: any }; +/** @internal */ +export type ProgramBuildInfoDiagnostic = ProgramBuildInfoFileId | [fileId: ProgramBuildInfoFileId, diagnostics: readonly ReusableDiagnostic[]]; +/** + * fileId if pending emit is same as what compilerOptions suggest + * [fileId] if pending emit is only dts file emit + * [fileId, emitKind] if any other type emit is pending + * + * @internal + */ +export type ProgramBuilderInfoFilePendingEmit = ProgramBuildInfoFileId | [fileId: ProgramBuildInfoFileId] | [fileId: ProgramBuildInfoFileId, emitKind: BuilderFileEmit]; +/** @internal */ +export type ProgramBuildInfoReferencedMap = [fileId: ProgramBuildInfoFileId, fileIdListId: ProgramBuildInfoFileIdListId][]; +/** @internal */ +export type ProgramMultiFileEmitBuildInfoBuilderStateFileInfo = Omit & { /** - * ProgramBundleEmitBuildInfoFileInfo is string if !FileInfo.impliedFormat otherwise encoded FileInfo - */ - export type ProgramBundleEmitBuildInfoFileInfo = string | BuilderState.FileInfo; - /** - * false if it is the emit corresponding to compilerOptions - * value otherwise + * Signature is + * - undefined if FileInfo.version === FileInfo.signature + * - false if FileInfo has signature as undefined (not calculated) + * - string actual signature */ - export type ProgramBuildInfoBundlePendingEmit = BuilderFileEmit | false; - export interface ProgramBundleEmitBuildInfo { - fileNames: readonly string[]; - fileInfos: readonly ProgramBundleEmitBuildInfoFileInfo[]; - options: CompilerOptions | undefined; - outSignature: EmitSignature | undefined; - latestChangedDtsFile: string | undefined; - pendingEmit: ProgramBuildInfoBundlePendingEmit | undefined; - } - - export type ProgramBuildInfo = ProgramMultiFileEmitBuildInfo | ProgramBundleEmitBuildInfo; + signature: string | false | undefined; +}; +/** + * [fileId, signature] if different from file's signature + * fileId if file wasnt emitted + * + * @internal + */ +export type ProgramBuildInfoEmitSignature = ProgramBuildInfoFileId | [fileId: ProgramBuildInfoFileId, signature: EmitSignature | []]; +/** + * ProgramMultiFileEmitBuildInfoFileInfo is string if FileInfo.version === FileInfo.signature && !FileInfo.affectsGlobalScope otherwise encoded FileInfo + * + * @internal + */ +export type ProgramMultiFileEmitBuildInfoFileInfo = string | ProgramMultiFileEmitBuildInfoBuilderStateFileInfo; +/** @internal */ +export interface ProgramMultiFileEmitBuildInfo { + fileNames: readonly string[]; + fileInfos: readonly ProgramMultiFileEmitBuildInfoFileInfo[]; + options: CompilerOptions | undefined; + fileIdsList: readonly (readonly ProgramBuildInfoFileId[])[] | undefined; + referencedMap: ProgramBuildInfoReferencedMap | undefined; + exportedModulesMap: ProgramBuildInfoReferencedMap | undefined; + semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] | undefined; + affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] | undefined; + changeFileSet: readonly ProgramBuildInfoFileId[] | undefined; + emitSignatures: readonly ProgramBuildInfoEmitSignature[] | undefined; + // Because this is only output file in the program, we dont need fileId to deduplicate name + latestChangedDtsFile?: string | undefined; +} +/** + * ProgramBundleEmitBuildInfoFileInfo is string if !FileInfo.impliedFormat otherwise encoded FileInfo + * + * @internal + */ +export type ProgramBundleEmitBuildInfoFileInfo = string | BuilderState.FileInfo; +/** + * false if it is the emit corresponding to compilerOptions + * value otherwise + * + * @internal + */ +export type ProgramBuildInfoBundlePendingEmit = BuilderFileEmit | false; +/** @internal */ +export interface ProgramBundleEmitBuildInfo { + fileNames: readonly string[]; + fileInfos: readonly ProgramBundleEmitBuildInfoFileInfo[]; + options: CompilerOptions | undefined; + outSignature: EmitSignature | undefined; + latestChangedDtsFile: string | undefined; + pendingEmit: ProgramBuildInfoBundlePendingEmit | undefined; +} - export function isProgramBundleEmitBuildInfo(info: ProgramBuildInfo): info is ProgramBundleEmitBuildInfo { - return !!outFile(info.options || {}); - } +/** @internal */ +export type ProgramBuildInfo = ProgramMultiFileEmitBuildInfo | ProgramBundleEmitBuildInfo; - /** - * Gets the program information to be emitted in buildInfo so that we can use it to create new program - */ - function getBuildInfo(state: BuilderProgramState, getCanonicalFileName: GetCanonicalFileName, bundle: BundleBuildInfo | undefined): BuildInfo { - const currentDirectory = Debug.checkDefined(state.program).getCurrentDirectory(); - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory)); - // Convert the file name to Path here if we set the fileName instead to optimize multiple d.ts file emits and having to compute Canonical path - const latestChangedDtsFile = state.latestChangedDtsFile ? relativeToBuildInfoEnsuringAbsolutePath(state.latestChangedDtsFile) : undefined; - const fileNames: string[] = []; - const fileNameToFileId = new Map(); - if (outFile(state.compilerOptions)) { - // Copy all fileInfo, version and impliedFormat - // Affects global scope and signature doesnt matter because with --out they arent calculated or needed to determine upto date ness - const fileInfos = arrayFrom(state.fileInfos.entries(), ([key, value]): ProgramBundleEmitBuildInfoFileInfo => { - // Ensure fileId - toFileId(key); - return value.impliedFormat ? - { version: value.version, impliedFormat: value.impliedFormat, signature: undefined, affectsGlobalScope: undefined } : - value.version; - }); - const program: ProgramBundleEmitBuildInfo = { - fileNames, - fileInfos, - options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions), - outSignature: state.outSignature, - latestChangedDtsFile, - pendingEmit: !state.programEmitPending ? - undefined : // Pending is undefined or None is encoded as undefined - state.programEmitPending === getBuilderFileEmit(state.compilerOptions) ? - false : // Pending emit is same as deteremined by compilerOptions - state.programEmitPending, // Actual value - }; - // Complete the bundle information if we are doing partial emit (only js or only dts) - const { js, dts, commonSourceDirectory, sourceFiles } = bundle!; - state.bundle = bundle = { - commonSourceDirectory, - sourceFiles, - js: js || (!state.compilerOptions.emitDeclarationOnly ? state.bundle?.js : undefined), - dts: dts || (getEmitDeclarations(state.compilerOptions) ? state.bundle?.dts : undefined), - }; - return createBuildInfo(program, bundle); - } +/** @internal */ +export function isProgramBundleEmitBuildInfo(info: ProgramBuildInfo): info is ProgramBundleEmitBuildInfo { + return !!outFile(info.options || {}); +} - let fileIdsList: (readonly ProgramBuildInfoFileId[])[] | undefined; - let fileNamesToFileIdListId: ESMap | undefined; - let emitSignatures: ProgramBuildInfoEmitSignature[] | undefined; - const fileInfos = arrayFrom(state.fileInfos.entries(), ([key, value]): ProgramMultiFileEmitBuildInfoFileInfo => { +/** + * Gets the program information to be emitted in buildInfo so that we can use it to create new program + */ +function getBuildInfo(state: BuilderProgramState, getCanonicalFileName: GetCanonicalFileName, bundle: BundleBuildInfo | undefined): BuildInfo { + const currentDirectory = Debug.checkDefined(state.program).getCurrentDirectory(); + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory)); + // Convert the file name to Path here if we set the fileName instead to optimize multiple d.ts file emits and having to compute Canonical path + const latestChangedDtsFile = state.latestChangedDtsFile ? relativeToBuildInfoEnsuringAbsolutePath(state.latestChangedDtsFile) : undefined; + const fileNames: string[] = []; + const fileNameToFileId = new Map(); + if (outFile(state.compilerOptions)) { + // Copy all fileInfo, version and impliedFormat + // Affects global scope and signature doesnt matter because with --out they arent calculated or needed to determine upto date ness + const fileInfos = arrayFrom(state.fileInfos.entries(), ([key, value]): ProgramBundleEmitBuildInfoFileInfo => { // Ensure fileId - const fileId = toFileId(key); - Debug.assert(fileNames[fileId - 1] === relativeToBuildInfo(key)); - const oldSignature = state.oldSignatures?.get(key); - const actualSignature = oldSignature !== undefined ? oldSignature || undefined : value.signature; - if (state.compilerOptions.composite) { - const file = state.program!.getSourceFileByPath(key)!; - if (!isJsonSourceFile(file) && sourceFileMayBeEmitted(file, state.program!)) { - const emitSignature = state.emitSignatures?.get(key); - if (emitSignature !== actualSignature) { - (emitSignatures ||= []).push(emitSignature === undefined ? - fileId : // There is no emit, encode as false - // fileId, signature: emptyArray if signature only differs in dtsMap option than our own compilerOptions otherwise EmitSignature - [fileId, !isString(emitSignature) && emitSignature[0] === actualSignature ? emptyArray as [] : emitSignature]); - } - } - } - return value.version === actualSignature ? - value.affectsGlobalScope || value.impliedFormat ? - // If file version is same as signature, dont serialize signature - { version: value.version, signature: undefined, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat } : - // If file info only contains version and signature and both are same we can just write string - value.version : - actualSignature !== undefined ? // If signature is not same as version, encode signature in the fileInfo - oldSignature === undefined ? - // If we havent computed signature, use fileInfo as is - value : - // Serialize fileInfo with new updated signature - { version: value.version, signature: actualSignature, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat } : - // Signature of the FileInfo is undefined, serialize it as false - { version: value.version, signature: false, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat }; + toFileId(key); + return value.impliedFormat ? + { version: value.version, impliedFormat: value.impliedFormat, signature: undefined, affectsGlobalScope: undefined } : + value.version; }); - - let referencedMap: ProgramBuildInfoReferencedMap | undefined; - if (state.referencedMap) { - referencedMap = arrayFrom(state.referencedMap.keys()).sort(compareStringsCaseSensitive).map(key => [ - toFileId(key), - toFileIdListId(state.referencedMap!.getValues(key)!) - ]); - } - - let exportedModulesMap: ProgramBuildInfoReferencedMap | undefined; - if (state.exportedModulesMap) { - exportedModulesMap = mapDefined(arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive), key => { - const oldValue = state.oldExportedModulesMap?.get(key); - // Not in temporary cache, use existing value - if (oldValue === undefined) return [toFileId(key), toFileIdListId(state.exportedModulesMap!.getValues(key)!)]; - if (oldValue) return [toFileId(key), toFileIdListId(oldValue)]; - return undefined; - }); - } - - let semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] | undefined; - if (state.semanticDiagnosticsPerFile) { - for (const key of arrayFrom(state.semanticDiagnosticsPerFile.keys()).sort(compareStringsCaseSensitive)) { - const value = state.semanticDiagnosticsPerFile.get(key)!; - (semanticDiagnosticsPerFile ||= []).push( - value.length ? - [ - toFileId(key), - convertToReusableDiagnostics(value, relativeToBuildInfo) - ] : - toFileId(key) - ); - } - } - - let affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] | undefined; - if (state.affectedFilesPendingEmit?.size) { - const fullEmitForOptions = getBuilderFileEmit(state.compilerOptions); - const seenFiles = new Set(); - for (const path of arrayFrom(state.affectedFilesPendingEmit.keys()).sort(compareStringsCaseSensitive)) { - if (tryAddToSet(seenFiles, path)) { - const file = state.program!.getSourceFileByPath(path)!; - if (!sourceFileMayBeEmitted(file, state.program!)) continue; - const fileId = toFileId(path), pendingEmit = state.affectedFilesPendingEmit.get(path)!; - (affectedFilesPendingEmit ||= []).push( - pendingEmit === fullEmitForOptions ? - fileId : // Pending full emit per options - pendingEmit === BuilderFileEmit.Dts ? - [fileId] : // Pending on Dts only - [fileId, pendingEmit] // Anything else - ); - } - } - } - - let changeFileSet: ProgramBuildInfoFileId[] | undefined; - if (state.changedFilesSet.size) { - for (const path of arrayFrom(state.changedFilesSet.keys()).sort(compareStringsCaseSensitive)) { - (changeFileSet ||= []).push(toFileId(path)); - } - } - - const program: ProgramMultiFileEmitBuildInfo = { + const program: ProgramBundleEmitBuildInfo = { fileNames, fileInfos, options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions), - fileIdsList, - referencedMap, - exportedModulesMap, - semanticDiagnosticsPerFile, - affectedFilesPendingEmit, - changeFileSet, - emitSignatures, + outSignature: state.outSignature, latestChangedDtsFile, + pendingEmit: !state.programEmitPending ? + undefined : // Pending is undefined or None is encoded as undefined + state.programEmitPending === getBuilderFileEmit(state.compilerOptions) ? + false : // Pending emit is same as deteremined by compilerOptions + state.programEmitPending, // Actual value + }; + // Complete the bundle information if we are doing partial emit (only js or only dts) + const { js, dts, commonSourceDirectory, sourceFiles } = bundle!; + state.bundle = bundle = { + commonSourceDirectory, + sourceFiles, + js: js || (!state.compilerOptions.emitDeclarationOnly ? state.bundle?.js : undefined), + dts: dts || (getEmitDeclarations(state.compilerOptions) ? state.bundle?.dts : undefined), }; return createBuildInfo(program, bundle); + } - function relativeToBuildInfoEnsuringAbsolutePath(path: string) { - return relativeToBuildInfo(getNormalizedAbsolutePath(path, currentDirectory)); - } - - function relativeToBuildInfo(path: string) { - return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName)); - } - - function toFileId(path: Path): ProgramBuildInfoFileId { - let fileId = fileNameToFileId.get(path); - if (fileId === undefined) { - fileNames.push(relativeToBuildInfo(path)); - fileNameToFileId.set(path, fileId = fileNames.length as ProgramBuildInfoFileId); + let fileIdsList: (readonly ProgramBuildInfoFileId[])[] | undefined; + let fileNamesToFileIdListId: ESMap | undefined; + let emitSignatures: ProgramBuildInfoEmitSignature[] | undefined; + const fileInfos = arrayFrom(state.fileInfos.entries(), ([key, value]): ProgramMultiFileEmitBuildInfoFileInfo => { + // Ensure fileId + const fileId = toFileId(key); + Debug.assert(fileNames[fileId - 1] === relativeToBuildInfo(key)); + const oldSignature = state.oldSignatures?.get(key); + const actualSignature = oldSignature !== undefined ? oldSignature || undefined : value.signature; + if (state.compilerOptions.composite) { + const file = state.program!.getSourceFileByPath(key)!; + if (!isJsonSourceFile(file) && sourceFileMayBeEmitted(file, state.program!)) { + const emitSignature = state.emitSignatures?.get(key); + if (emitSignature !== actualSignature) { + (emitSignatures ||= []).push(emitSignature === undefined ? + fileId : // There is no emit, encode as false + // fileId, signature: emptyArray if signature only differs in dtsMap option than our own compilerOptions otherwise EmitSignature + [fileId, !isString(emitSignature) && emitSignature[0] === actualSignature ? emptyArray as [] : emitSignature]); + } } - return fileId; } + return value.version === actualSignature ? + value.affectsGlobalScope || value.impliedFormat ? + // If file version is same as signature, dont serialize signature + { version: value.version, signature: undefined, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat } : + // If file info only contains version and signature and both are same we can just write string + value.version : + actualSignature !== undefined ? // If signature is not same as version, encode signature in the fileInfo + oldSignature === undefined ? + // If we havent computed signature, use fileInfo as is + value : + // Serialize fileInfo with new updated signature + { version: value.version, signature: actualSignature, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat } : + // Signature of the FileInfo is undefined, serialize it as false + { version: value.version, signature: false, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat }; + }); + + let referencedMap: ProgramBuildInfoReferencedMap | undefined; + if (state.referencedMap) { + referencedMap = arrayFrom(state.referencedMap.keys()).sort(compareStringsCaseSensitive).map(key => [ + toFileId(key), + toFileIdListId(state.referencedMap!.getValues(key)!) + ]); + } - function toFileIdListId(set: ReadonlySet): ProgramBuildInfoFileIdListId { - const fileIds = arrayFrom(set.keys(), toFileId).sort(compareValues); - const key = fileIds.join(); - let fileIdListId = fileNamesToFileIdListId?.get(key); - if (fileIdListId === undefined) { - (fileIdsList ||= []).push(fileIds); - (fileNamesToFileIdListId ||= new Map()).set(key, fileIdListId = fileIdsList.length as ProgramBuildInfoFileIdListId); - } - return fileIdListId; + let exportedModulesMap: ProgramBuildInfoReferencedMap | undefined; + if (state.exportedModulesMap) { + exportedModulesMap = mapDefined(arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive), key => { + const oldValue = state.oldExportedModulesMap?.get(key); + // Not in temporary cache, use existing value + if (oldValue === undefined) return [toFileId(key), toFileIdListId(state.exportedModulesMap!.getValues(key)!)]; + if (oldValue) return [toFileId(key), toFileIdListId(oldValue)]; + return undefined; + }); + } + + let semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] | undefined; + if (state.semanticDiagnosticsPerFile) { + for (const key of arrayFrom(state.semanticDiagnosticsPerFile.keys()).sort(compareStringsCaseSensitive)) { + const value = state.semanticDiagnosticsPerFile.get(key)!; + (semanticDiagnosticsPerFile ||= []).push( + value.length ? + [ + toFileId(key), + convertToReusableDiagnostics(value, relativeToBuildInfo) + ] : + toFileId(key) + ); } + } - /** - * @param optionKey key of CommandLineOption to use to determine if the option should be serialized in tsbuildinfo - */ - function convertToProgramBuildInfoCompilerOptions(options: CompilerOptions) { - let result: CompilerOptions | undefined; - const { optionsNameMap } = getOptionsNameMap(); - for (const name of getOwnKeys(options).sort(compareStringsCaseSensitive)) { - const optionInfo = optionsNameMap.get(name.toLowerCase()); - if (optionInfo?.affectsBuildInfo) { - (result ||= {})[name] = convertToReusableCompilerOptionValue( - optionInfo, - options[name] as CompilerOptionsValue, - relativeToBuildInfoEnsuringAbsolutePath - ); - } + let affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] | undefined; + if (state.affectedFilesPendingEmit?.size) { + const fullEmitForOptions = getBuilderFileEmit(state.compilerOptions); + const seenFiles = new Set(); + for (const path of arrayFrom(state.affectedFilesPendingEmit.keys()).sort(compareStringsCaseSensitive)) { + if (tryAddToSet(seenFiles, path)) { + const file = state.program!.getSourceFileByPath(path)!; + if (!sourceFileMayBeEmitted(file, state.program!)) continue; + const fileId = toFileId(path), pendingEmit = state.affectedFilesPendingEmit.get(path)!; + (affectedFilesPendingEmit ||= []).push( + pendingEmit === fullEmitForOptions ? + fileId : // Pending full emit per options + pendingEmit === BuilderFileEmit.Dts ? + [fileId] : // Pending on Dts only + [fileId, pendingEmit] // Anything else + ); } - return result; } } - function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) { - if (option) { - if (option.type === "list") { - const values = value as readonly (string | number)[]; - if (option.element.isFilePath && values.length) { - return values.map(relativeToBuildInfo); - } - } - else if (option.isFilePath) { - return relativeToBuildInfo(value as string); - } + let changeFileSet: ProgramBuildInfoFileId[] | undefined; + if (state.changedFilesSet.size) { + for (const path of arrayFrom(state.changedFilesSet.keys()).sort(compareStringsCaseSensitive)) { + (changeFileSet ||= []).push(toFileId(path)); } - return value; } - function convertToReusableDiagnostics(diagnostics: readonly Diagnostic[], relativeToBuildInfo: (path: string) => string): readonly ReusableDiagnostic[] { - Debug.assert(!!diagnostics.length); - return diagnostics.map(diagnostic => { - const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo); - result.reportsUnnecessary = diagnostic.reportsUnnecessary; - result.reportDeprecated = diagnostic.reportsDeprecated; - result.source = diagnostic.source; - result.skippedOn = diagnostic.skippedOn; - const { relatedInformation } = diagnostic; - result.relatedInformation = relatedInformation ? - relatedInformation.length ? - relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) : - [] : - undefined; - return result; - }); + const program: ProgramMultiFileEmitBuildInfo = { + fileNames, + fileInfos, + options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions), + fileIdsList, + referencedMap, + exportedModulesMap, + semanticDiagnosticsPerFile, + affectedFilesPendingEmit, + changeFileSet, + emitSignatures, + latestChangedDtsFile, + }; + return createBuildInfo(program, bundle); + + function relativeToBuildInfoEnsuringAbsolutePath(path: string) { + return relativeToBuildInfo(getNormalizedAbsolutePath(path, currentDirectory)); } - function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation, relativeToBuildInfo: (path: string) => string): ReusableDiagnosticRelatedInformation { - const { file } = diagnostic; - return { - ...diagnostic, - file: file ? relativeToBuildInfo(file.resolvedPath) : undefined - }; + function relativeToBuildInfo(path: string) { + return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName)); } - export enum BuilderProgramKind { - SemanticDiagnosticsBuilderProgram, - EmitAndSemanticDiagnosticsBuilderProgram + function toFileId(path: Path): ProgramBuildInfoFileId { + let fileId = fileNameToFileId.get(path); + if (fileId === undefined) { + fileNames.push(relativeToBuildInfo(path)); + fileNameToFileId.set(path, fileId = fileNames.length as ProgramBuildInfoFileId); + } + return fileId; } - export interface BuilderCreationParameters { - newProgram: Program; - host: BuilderProgramHost; - oldProgram: BuilderProgram | undefined; - configFileParsingDiagnostics: readonly Diagnostic[]; + function toFileIdListId(set: ReadonlySet): ProgramBuildInfoFileIdListId { + const fileIds = arrayFrom(set.keys(), toFileId).sort(compareValues); + const key = fileIds.join(); + let fileIdListId = fileNamesToFileIdListId?.get(key); + if (fileIdListId === undefined) { + (fileIdsList ||= []).push(fileIds); + (fileNamesToFileIdListId ||= new Map()).set(key, fileIdListId = fileIdsList.length as ProgramBuildInfoFileIdListId); + } + return fileIdListId; } - export function getBuilderCreationParameters(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: BuilderProgram | CompilerHost, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderCreationParameters { - let host: BuilderProgramHost; - let newProgram: Program; - let oldProgram: BuilderProgram; - if (newProgramOrRootNames === undefined) { - Debug.assert(hostOrOptions === undefined); - host = oldProgramOrHost as CompilerHost; - oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; - Debug.assert(!!oldProgram); - newProgram = oldProgram.getProgram(); + /** + * @param optionKey key of CommandLineOption to use to determine if the option should be serialized in tsbuildinfo + */ + function convertToProgramBuildInfoCompilerOptions(options: CompilerOptions) { + let result: CompilerOptions | undefined; + const { optionsNameMap } = getOptionsNameMap(); + for (const name of getOwnKeys(options).sort(compareStringsCaseSensitive)) { + const optionInfo = optionsNameMap.get(name.toLowerCase()); + if (optionInfo?.affectsBuildInfo) { + (result ||= {})[name] = convertToReusableCompilerOptionValue( + optionInfo, + options[name] as CompilerOptionsValue, + relativeToBuildInfoEnsuringAbsolutePath + ); + } } - else if (isArray(newProgramOrRootNames)) { - oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; - newProgram = createProgram({ - rootNames: newProgramOrRootNames, - options: hostOrOptions as CompilerOptions, - host: oldProgramOrHost as CompilerHost, - oldProgram: oldProgram && oldProgram.getProgramOrUndefined(), - configFileParsingDiagnostics, - projectReferences - }); - host = oldProgramOrHost as CompilerHost; + return result; + } +} + +function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) { + if (option) { + if (option.type === "list") { + const values = value as readonly (string | number)[]; + if (option.element.isFilePath && values.length) { + return values.map(relativeToBuildInfo); + } } - else { - newProgram = newProgramOrRootNames; - host = hostOrOptions as BuilderProgramHost; - oldProgram = oldProgramOrHost as BuilderProgram; - configFileParsingDiagnostics = configFileParsingDiagnosticsOrOldProgram as readonly Diagnostic[]; + else if (option.isFilePath) { + return relativeToBuildInfo(value as string); } - return { host, newProgram, oldProgram, configFileParsingDiagnostics: configFileParsingDiagnostics || emptyArray }; } + return value; +} + +function convertToReusableDiagnostics(diagnostics: readonly Diagnostic[], relativeToBuildInfo: (path: string) => string): readonly ReusableDiagnostic[] { + Debug.assert(!!diagnostics.length); + return diagnostics.map(diagnostic => { + const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo); + result.reportsUnnecessary = diagnostic.reportsUnnecessary; + result.reportDeprecated = diagnostic.reportsDeprecated; + result.source = diagnostic.source; + result.skippedOn = diagnostic.skippedOn; + const { relatedInformation } = diagnostic; + result.relatedInformation = relatedInformation ? + relatedInformation.length ? + relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) : + [] : + undefined; + return result; + }); +} + +function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation, relativeToBuildInfo: (path: string) => string): ReusableDiagnosticRelatedInformation { + const { file } = diagnostic; + return { + ...diagnostic, + file: file ? relativeToBuildInfo(file.resolvedPath) : undefined + }; +} + +/** @internal */ +export enum BuilderProgramKind { + SemanticDiagnosticsBuilderProgram, + EmitAndSemanticDiagnosticsBuilderProgram +} + +/** @internal */ +export interface BuilderCreationParameters { + newProgram: Program; + host: BuilderProgramHost; + oldProgram: BuilderProgram | undefined; + configFileParsingDiagnostics: readonly Diagnostic[]; +} - function getTextHandlingSourceMapForSignature(text: string, data: WriteFileCallbackData | undefined) { - return data?.sourceMapUrlPos !== undefined ? text.substring(0, data.sourceMapUrlPos) : text; +/** @internal */ +export function getBuilderCreationParameters(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: BuilderProgram | CompilerHost, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderCreationParameters { + let host: BuilderProgramHost; + let newProgram: Program; + let oldProgram: BuilderProgram; + if (newProgramOrRootNames === undefined) { + Debug.assert(hostOrOptions === undefined); + host = oldProgramOrHost as CompilerHost; + oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; + Debug.assert(!!oldProgram); + newProgram = oldProgram.getProgram(); + } + else if (isArray(newProgramOrRootNames)) { + oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; + newProgram = createProgram({ + rootNames: newProgramOrRootNames, + options: hostOrOptions as CompilerOptions, + host: oldProgramOrHost as CompilerHost, + oldProgram: oldProgram && oldProgram.getProgramOrUndefined(), + configFileParsingDiagnostics, + projectReferences + }); + host = oldProgramOrHost as CompilerHost; + } + else { + newProgram = newProgramOrRootNames; + host = hostOrOptions as BuilderProgramHost; + oldProgram = oldProgramOrHost as BuilderProgram; + configFileParsingDiagnostics = configFileParsingDiagnosticsOrOldProgram as readonly Diagnostic[]; } + return { host, newProgram, oldProgram, configFileParsingDiagnostics: configFileParsingDiagnostics || emptyArray }; +} - export function computeSignatureWithDiagnostics( - sourceFile: SourceFile, - text: string, - computeHash: BuilderState.ComputeHash | undefined, - getCanonicalFileName: GetCanonicalFileName, - data: WriteFileCallbackData | undefined - ) { - text = getTextHandlingSourceMapForSignature(text, data); - let sourceFileDirectory: string | undefined; - if (data?.diagnostics?.length) { - text += data.diagnostics.map(diagnostic => - `${locationInfo(diagnostic)}${DiagnosticCategory[diagnostic.category]}${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText)}` - ).join("\n"); - } - return (computeHash ?? generateDjb2Hash)(text); - - function flattenDiagnosticMessageText(diagnostic: string | DiagnosticMessageChain | undefined): string { - return isString(diagnostic) ? - diagnostic : - diagnostic === undefined ? - "" : - !diagnostic.next ? - diagnostic.messageText : - diagnostic.messageText + diagnostic.next.map(flattenDiagnosticMessageText).join("\n"); - } +function getTextHandlingSourceMapForSignature(text: string, data: WriteFileCallbackData | undefined) { + return data?.sourceMapUrlPos !== undefined ? text.substring(0, data.sourceMapUrlPos) : text; +} - function locationInfo(diagnostic: DiagnosticWithLocation) { - if (diagnostic.file.resolvedPath === sourceFile.resolvedPath) return `(${diagnostic.start},${diagnostic.length})`; - if (sourceFileDirectory === undefined) sourceFileDirectory = getDirectoryPath(sourceFile.resolvedPath); - return `${ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceFileDirectory, diagnostic.file.resolvedPath, getCanonicalFileName))}(${diagnostic.start},${diagnostic.length})`; - } +/** @internal */ +export function computeSignatureWithDiagnostics( + sourceFile: SourceFile, + text: string, + computeHash: BuilderState.ComputeHash | undefined, + getCanonicalFileName: GetCanonicalFileName, + data: WriteFileCallbackData | undefined +) { + text = getTextHandlingSourceMapForSignature(text, data); + let sourceFileDirectory: string | undefined; + if (data?.diagnostics?.length) { + text += data.diagnostics.map(diagnostic => + `${locationInfo(diagnostic)}${DiagnosticCategory[diagnostic.category]}${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText)}` + ).join("\n"); + } + return (computeHash ?? generateDjb2Hash)(text); + + function flattenDiagnosticMessageText(diagnostic: string | DiagnosticMessageChain | undefined): string { + return isString(diagnostic) ? + diagnostic : + diagnostic === undefined ? + "" : + !diagnostic.next ? + diagnostic.messageText : + diagnostic.messageText + diagnostic.next.map(flattenDiagnosticMessageText).join("\n"); } - export function computeSignature(text: string, computeHash: BuilderState.ComputeHash | undefined, data?: WriteFileCallbackData) { - return (computeHash ?? generateDjb2Hash)(getTextHandlingSourceMapForSignature(text, data)); + function locationInfo(diagnostic: DiagnosticWithLocation) { + if (diagnostic.file.resolvedPath === sourceFile.resolvedPath) return `(${diagnostic.start},${diagnostic.length})`; + if (sourceFileDirectory === undefined) sourceFileDirectory = getDirectoryPath(sourceFile.resolvedPath); + return `${ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceFileDirectory, diagnostic.file.resolvedPath, getCanonicalFileName))}(${diagnostic.start},${diagnostic.length})`; } +} - export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram; - export function createBuilderProgram(kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): EmitAndSemanticDiagnosticsBuilderProgram; - export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters) { - // Return same program if underlying program doesnt change - let oldState = oldProgram && oldProgram.getState(); - if (oldState && newProgram === oldState.program && configFileParsingDiagnostics === newProgram.getConfigFileParsingDiagnostics()) { - newProgram = undefined!; // TODO: GH#18217 - oldState = undefined; - return oldProgram; - } +/** @internal */ +export function computeSignature(text: string, computeHash: BuilderState.ComputeHash | undefined, data?: WriteFileCallbackData) { + return (computeHash ?? generateDjb2Hash)(getTextHandlingSourceMapForSignature(text, data)); +} - /** - * Create the canonical file name for identity - */ - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - /** - * Computing hash to for signature verification - */ - const computeHash = maybeBind(host, host.createHash); - const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState, host.disableUseFileVersionAsSignature); - newProgram.getBuildInfo = bundle => getBuildInfo(state, getCanonicalFileName, bundle); - - // To ensure that we arent storing any references to old program or new program without state +/** @internal */ +export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram; +/** @internal */ +export function createBuilderProgram(kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): EmitAndSemanticDiagnosticsBuilderProgram; +/** @internal */ +export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters) { + // Return same program if underlying program doesnt change + let oldState = oldProgram && oldProgram.getState(); + if (oldState && newProgram === oldState.program && configFileParsingDiagnostics === newProgram.getConfigFileParsingDiagnostics()) { newProgram = undefined!; // TODO: GH#18217 - oldProgram = undefined; oldState = undefined; + return oldProgram; + } - const getState = () => state; - const builderProgram = createRedirectedBuilderProgram(getState, configFileParsingDiagnostics); - builderProgram.getState = getState; - builderProgram.saveEmitState = () => backupBuilderProgramEmitState(state); - builderProgram.restoreEmitState = (saved) => restoreBuilderProgramEmitState(state, saved); - builderProgram.hasChangedEmitSignature = () => !!state.hasChangedEmitSignature; - builderProgram.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.checkDefined(state.program), sourceFile); - builderProgram.getSemanticDiagnostics = getSemanticDiagnostics; - builderProgram.emit = emit; - builderProgram.releaseProgram = () => releaseCache(state); - - if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { - (builderProgram as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; - } - else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { - (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; - (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile; - builderProgram.emitBuildInfo = emitBuildInfo; - } - else { - notImplemented(); - } + /** + * Create the canonical file name for identity + */ + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + /** + * Computing hash to for signature verification + */ + const computeHash = maybeBind(host, host.createHash); + const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState, host.disableUseFileVersionAsSignature); + newProgram.getBuildInfo = bundle => getBuildInfo(state, getCanonicalFileName, bundle); + + // To ensure that we arent storing any references to old program or new program without state + newProgram = undefined!; // TODO: GH#18217 + oldProgram = undefined; + oldState = undefined; + + const getState = () => state; + const builderProgram = createRedirectedBuilderProgram(getState, configFileParsingDiagnostics); + builderProgram.getState = getState; + builderProgram.saveEmitState = () => backupBuilderProgramEmitState(state); + builderProgram.restoreEmitState = (saved) => restoreBuilderProgramEmitState(state, saved); + builderProgram.hasChangedEmitSignature = () => !!state.hasChangedEmitSignature; + builderProgram.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.checkDefined(state.program), sourceFile); + builderProgram.getSemanticDiagnostics = getSemanticDiagnostics; + builderProgram.emit = emit; + builderProgram.releaseProgram = () => releaseCache(state); + + if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { + (builderProgram as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; + } + else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; + (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile; + builderProgram.emitBuildInfo = emitBuildInfo; + } + else { + notImplemented(); + } - return builderProgram; + return builderProgram; - function emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult { - if (state.buildInfoEmitPending) { - const result = Debug.checkDefined(state.program).emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken); - state.buildInfoEmitPending = false; - return result; - } - return emitSkippedWithNoDiagnostics; + function emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult { + if (state.buildInfoEmitPending) { + const result = Debug.checkDefined(state.program).emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken); + state.buildInfoEmitPending = false; + return result; } + return emitSkippedWithNoDiagnostics; + } - /** - * 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 - * in that order would be used to write the files - */ - function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult { - let affected = getNextAffectedFile(state, cancellationToken, computeHash, getCanonicalFileName, host); - const programEmitKind = getBuilderFileEmit(state.compilerOptions); - let emitKind: BuilderFileEmit = emitOnlyDtsFiles ? - programEmitKind & BuilderFileEmit.AllDts : programEmitKind; - if (!affected) { - if (!outFile(state.compilerOptions)) { - const pendingAffectedFile = getNextAffectedFilePendingEmit(state, emitOnlyDtsFiles); - if (!pendingAffectedFile) { - // Emit buildinfo if pending - if (!state.buildInfoEmitPending) return undefined; - const affected = state.program!; - const result = affected.emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken); - state.buildInfoEmitPending = false; - return { result, affected }; - } - // Emit pending affected file - ({ affectedFile: affected, emitKind } = pendingAffectedFile); - } - else { - // Emit program if it was pending emit - if (!state.programEmitPending) return undefined; - emitKind = state.programEmitPending; - if (emitOnlyDtsFiles) emitKind = emitKind & BuilderFileEmit.AllDts; - if (!emitKind) return undefined; - affected = state.program!; + /** + * 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 + * in that order would be used to write the files + */ + function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult { + let affected = getNextAffectedFile(state, cancellationToken, computeHash, getCanonicalFileName, host); + const programEmitKind = getBuilderFileEmit(state.compilerOptions); + let emitKind: BuilderFileEmit = emitOnlyDtsFiles ? + programEmitKind & BuilderFileEmit.AllDts : programEmitKind; + if (!affected) { + if (!outFile(state.compilerOptions)) { + const pendingAffectedFile = getNextAffectedFilePendingEmit(state, emitOnlyDtsFiles); + if (!pendingAffectedFile) { + // Emit buildinfo if pending + if (!state.buildInfoEmitPending) return undefined; + const affected = state.program!; + const result = affected.emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken); + state.buildInfoEmitPending = false; + return { result, affected }; } - } - // Determine if we can do partial emit - let emitOnly: EmitOnly | undefined; - if (emitKind & BuilderFileEmit.AllJs) emitOnly = EmitOnly.Js; - if (emitKind & BuilderFileEmit.AllDts) emitOnly = emitOnly === undefined ? EmitOnly.Dts : undefined; - if (affected === state.program) { - // Set up programEmit before calling emit so that its set in buildInfo - state.programEmitPending = state.changedFilesSet.size ? - getPendingEmitKind(programEmitKind, emitKind) : - state.programEmitPending ? - getPendingEmitKind(state.programEmitPending, emitKind) : - undefined; - } - // Actual emit - const result = state.program!.emit( - affected === state.program ? undefined : affected as SourceFile, - getWriteFileCallback(writeFile, customTransformers), - cancellationToken, - emitOnly, - customTransformers - ); - if (affected !== state.program) { - // update affected files - const affectedSourceFile = affected as SourceFile; - state.seenAffectedFiles!.add(affectedSourceFile.resolvedPath); - if (state.affectedFilesIndex !== undefined) state.affectedFilesIndex++; - // Change in changeSet/affectedFilesPendingEmit, buildInfo needs to be emitted - state.buildInfoEmitPending = true; - // Update the pendingEmit for the file - const existing = state.seenEmittedFiles?.get(affectedSourceFile.resolvedPath) || BuilderFileEmit.None; - (state.seenEmittedFiles ??= new Map()).set(affectedSourceFile.resolvedPath, emitKind | existing); - const existingPending = state.affectedFilesPendingEmit?.get(affectedSourceFile.resolvedPath) || programEmitKind; - const pendingKind = getPendingEmitKind(existingPending, emitKind | existing); - if (pendingKind) (state.affectedFilesPendingEmit ??= new Map()).set(affectedSourceFile.resolvedPath, pendingKind); - else state.affectedFilesPendingEmit?.delete(affectedSourceFile.resolvedPath); + // Emit pending affected file + ({ affectedFile: affected, emitKind } = pendingAffectedFile); } else { - // In program clear our changed files since any emit handles all changes - state.changedFilesSet.clear(); + // Emit program if it was pending emit + if (!state.programEmitPending) return undefined; + emitKind = state.programEmitPending; + if (emitOnlyDtsFiles) emitKind = emitKind & BuilderFileEmit.AllDts; + if (!emitKind) return undefined; + affected = state.program!; } - return { result, affected }; } + // Determine if we can do partial emit + let emitOnly: EmitOnly | undefined; + if (emitKind & BuilderFileEmit.AllJs) emitOnly = EmitOnly.Js; + if (emitKind & BuilderFileEmit.AllDts) emitOnly = emitOnly === undefined ? EmitOnly.Dts : undefined; + if (affected === state.program) { + // Set up programEmit before calling emit so that its set in buildInfo + state.programEmitPending = state.changedFilesSet.size ? + getPendingEmitKind(programEmitKind, emitKind) : + state.programEmitPending ? + getPendingEmitKind(state.programEmitPending, emitKind) : + undefined; + } + // Actual emit + const result = state.program!.emit( + affected === state.program ? undefined : affected as SourceFile, + getWriteFileCallback(writeFile, customTransformers), + cancellationToken, + emitOnly, + customTransformers + ); + if (affected !== state.program) { + // update affected files + const affectedSourceFile = affected as SourceFile; + state.seenAffectedFiles!.add(affectedSourceFile.resolvedPath); + if (state.affectedFilesIndex !== undefined) state.affectedFilesIndex++; + // Change in changeSet/affectedFilesPendingEmit, buildInfo needs to be emitted + state.buildInfoEmitPending = true; + // Update the pendingEmit for the file + const existing = state.seenEmittedFiles?.get(affectedSourceFile.resolvedPath) || BuilderFileEmit.None; + (state.seenEmittedFiles ??= new Map()).set(affectedSourceFile.resolvedPath, emitKind | existing); + const existingPending = state.affectedFilesPendingEmit?.get(affectedSourceFile.resolvedPath) || programEmitKind; + const pendingKind = getPendingEmitKind(existingPending, emitKind | existing); + if (pendingKind) (state.affectedFilesPendingEmit ??= new Map()).set(affectedSourceFile.resolvedPath, pendingKind); + else state.affectedFilesPendingEmit?.delete(affectedSourceFile.resolvedPath); + } + else { + // In program clear our changed files since any emit handles all changes + state.changedFilesSet.clear(); + } + return { result, affected }; + } - function getWriteFileCallback(writeFile: WriteFileCallback | undefined, customTransformers: CustomTransformers | undefined): WriteFileCallback | undefined { - if (!getEmitDeclarations(state.compilerOptions)) return writeFile || maybeBind(host, host.writeFile); - return (fileName, text, writeByteOrderMark, onError, sourceFiles, data) => { - if (isDeclarationFileName(fileName)) { - if (!outFile(state.compilerOptions)) { - Debug.assert(sourceFiles?.length === 1); - let emitSignature; - if (!customTransformers) { - const file = sourceFiles[0]; - const info = state.fileInfos.get(file.resolvedPath)!; - if (info.signature === file.version) { - const signature = computeSignatureWithDiagnostics( - file, - text, - computeHash, - getCanonicalFileName, - data, - ); - // With d.ts diagnostics they are also part of the signature so emitSignature will be different from it since its just hash of d.ts - if (!data?.diagnostics?.length) emitSignature = signature; - if (signature !== file.version) { // Update it - if (host.storeFilesChangingSignatureDuringEmit) (state.filesChangingSignature ??= new Set()).add(file.resolvedPath); - if (state.exportedModulesMap) BuilderState.updateExportedModules(state, file, file.exportedModulesFromDeclarationEmit); - if (state.affectedFiles) { - // Keep old signature so we know what to undo if cancellation happens - const existing = state.oldSignatures?.get(file.resolvedPath); - if (existing === undefined) (state.oldSignatures ??= new Map()).set(file.resolvedPath, info.signature || false); - info.signature = signature; - } - else { - // These are directly commited - info.signature = signature; - state.oldExportedModulesMap?.clear(); - } + function getWriteFileCallback(writeFile: WriteFileCallback | undefined, customTransformers: CustomTransformers | undefined): WriteFileCallback | undefined { + if (!getEmitDeclarations(state.compilerOptions)) return writeFile || maybeBind(host, host.writeFile); + return (fileName, text, writeByteOrderMark, onError, sourceFiles, data) => { + if (isDeclarationFileName(fileName)) { + if (!outFile(state.compilerOptions)) { + Debug.assert(sourceFiles?.length === 1); + let emitSignature; + if (!customTransformers) { + const file = sourceFiles[0]; + const info = state.fileInfos.get(file.resolvedPath)!; + if (info.signature === file.version) { + const signature = computeSignatureWithDiagnostics( + file, + text, + computeHash, + getCanonicalFileName, + data, + ); + // With d.ts diagnostics they are also part of the signature so emitSignature will be different from it since its just hash of d.ts + if (!data?.diagnostics?.length) emitSignature = signature; + if (signature !== file.version) { // Update it + if (host.storeFilesChangingSignatureDuringEmit) (state.filesChangingSignature ??= new Set()).add(file.resolvedPath); + if (state.exportedModulesMap) BuilderState.updateExportedModules(state, file, file.exportedModulesFromDeclarationEmit); + if (state.affectedFiles) { + // Keep old signature so we know what to undo if cancellation happens + const existing = state.oldSignatures?.get(file.resolvedPath); + if (existing === undefined) (state.oldSignatures ??= new Map()).set(file.resolvedPath, info.signature || false); + info.signature = signature; + } + else { + // These are directly commited + info.signature = signature; + state.oldExportedModulesMap?.clear(); } } } - - // Store d.ts emit hash so later can be compared to check if d.ts has changed. - // Currently we do this only for composite projects since these are the only projects that can be referenced by other projects - // and would need their d.ts change time in --build mode - if (state.compilerOptions.composite) { - const filePath = sourceFiles[0].resolvedPath; - emitSignature = handleNewSignature(state.emitSignatures?.get(filePath), emitSignature); - if (!emitSignature) return; - (state.emitSignatures ??= new Map()).set(filePath, emitSignature); - } } - else if (state.compilerOptions.composite) { - const newSignature = handleNewSignature(state.outSignature, /*newSignature*/ undefined); - if (!newSignature) return; - state.outSignature = newSignature; + + // Store d.ts emit hash so later can be compared to check if d.ts has changed. + // Currently we do this only for composite projects since these are the only projects that can be referenced by other projects + // and would need their d.ts change time in --build mode + if (state.compilerOptions.composite) { + const filePath = sourceFiles[0].resolvedPath; + emitSignature = handleNewSignature(state.emitSignatures?.get(filePath), emitSignature); + if (!emitSignature) return; + (state.emitSignatures ??= new Map()).set(filePath, emitSignature); } } - if (writeFile) writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); - else if (host.writeFile) host.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); - else state.program!.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); - - /** - * Compare to existing computed signature and store it or handle the changes in d.ts map option from before - * returning undefined means that, we dont need to emit this d.ts file since its contents didnt change - */ - function handleNewSignature(oldSignatureFormat: EmitSignature | undefined, newSignature: string | undefined) { - const oldSignature = !oldSignatureFormat || isString(oldSignatureFormat) ? oldSignatureFormat : oldSignatureFormat[0]; - newSignature ??= computeSignature(text, computeHash, data); - // Dont write dts files if they didn't change - if (newSignature === oldSignature) { - // If the signature was encoded as string the dts map options match so nothing to do - if (oldSignatureFormat === oldSignature) return undefined; - // Mark as differsOnlyInMap so that --build can reverse the timestamp so that - // the downstream projects dont detect this as change in d.ts file - else if (data) data.differsOnlyInMap = true; - else data = { differsOnlyInMap: true }; - } - else { - state.hasChangedEmitSignature = true; - state.latestChangedDtsFile = fileName; - } - return newSignature; + else if (state.compilerOptions.composite) { + const newSignature = handleNewSignature(state.outSignature, /*newSignature*/ undefined); + if (!newSignature) return; + state.outSignature = newSignature; } - }; - } - - /** - * Emits the JavaScript and declaration files. - * When targetSource file is specified, emits the files corresponding to that source file, - * otherwise for the whole program. - * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, - * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, - * it will only emit all the affected files instead of whole program - * - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { - if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { - assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile); } - const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken); - if (result) return result; - - // Emit only affected files if using builder for emit - if (!targetSourceFile) { - if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { - // Emit and report any errors we ran into. - let sourceMaps: SourceMapEmitResult[] = []; - let emitSkipped = false; - let diagnostics: Diagnostic[] | undefined; - let emittedFiles: string[] = []; - - let affectedEmitResult: AffectedFileResult; - while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) { - emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; - diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); - emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); - sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); - } - return { - emitSkipped, - diagnostics: diagnostics || emptyArray, - emittedFiles, - sourceMaps - }; + if (writeFile) writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); + else if (host.writeFile) host.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); + else state.program!.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); + + /** + * Compare to existing computed signature and store it or handle the changes in d.ts map option from before + * returning undefined means that, we dont need to emit this d.ts file since its contents didnt change + */ + function handleNewSignature(oldSignatureFormat: EmitSignature | undefined, newSignature: string | undefined) { + const oldSignature = !oldSignatureFormat || isString(oldSignatureFormat) ? oldSignatureFormat : oldSignatureFormat[0]; + newSignature ??= computeSignature(text, computeHash, data); + // Dont write dts files if they didn't change + if (newSignature === oldSignature) { + // If the signature was encoded as string the dts map options match so nothing to do + if (oldSignatureFormat === oldSignature) return undefined; + // Mark as differsOnlyInMap so that --build can reverse the timestamp so that + // the downstream projects dont detect this as change in d.ts file + else if (data) data.differsOnlyInMap = true; + else data = { differsOnlyInMap: true }; } - // In non Emit builder, clear affected files pending emit else { - clearAffectedFilesPendingEmit(state, emitOnlyDtsFiles); + state.hasChangedEmitSignature = true; + state.latestChangedDtsFile = fileName; } + return newSignature; } - return Debug.checkDefined(state.program).emit( - targetSourceFile, - getWriteFileCallback(writeFile, customTransformers), - cancellationToken, - emitOnlyDtsFiles, - customTransformers - ); + }; + } + + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { + if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile); } + const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken); + if (result) return result; - /** - * Return the semantic diagnostics for the next affected file or undefined if iteration is complete - * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true - */ - function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult { - while (true) { - const affected = getNextAffectedFile(state, cancellationToken, computeHash, getCanonicalFileName, host); - let result; - if (!affected) return undefined; // Done - else if (affected !== state.program) { - // Get diagnostics for the affected file if its not ignored - const affectedSourceFile = affected as SourceFile; - if (!ignoreSourceFile || !ignoreSourceFile(affectedSourceFile)) { - result = getSemanticDiagnosticsOfFile(state, affectedSourceFile, cancellationToken); - } - state.seenAffectedFiles!.add(affectedSourceFile.resolvedPath); - state.affectedFilesIndex!++; - // Change in changeSet, buildInfo needs to be emitted - state.buildInfoEmitPending = true; - if (!result) continue; - } - else { - // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) - result = state.program.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken); - state.changedFilesSet.clear(); - state.programEmitPending = getBuilderFileEmit(state.compilerOptions); + // Emit only affected files if using builder for emit + if (!targetSourceFile) { + if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + // Emit and report any errors we ran into. + let sourceMaps: SourceMapEmitResult[] = []; + let emitSkipped = false; + let diagnostics: Diagnostic[] | undefined; + let emittedFiles: string[] = []; + + let affectedEmitResult: AffectedFileResult; + while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) { + emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; + diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); + emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); + sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); } - return { result, affected }; + return { + emitSkipped, + diagnostics: diagnostics || emptyArray, + emittedFiles, + sourceMaps + }; + } + // In non Emit builder, clear affected files pending emit + else { + clearAffectedFilesPendingEmit(state, emitOnlyDtsFiles); } } + return Debug.checkDefined(state.program).emit( + targetSourceFile, + getWriteFileCallback(writeFile, customTransformers), + cancellationToken, + emitOnlyDtsFiles, + customTransformers + ); + } - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, - * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics - */ - function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - assertSourceFileOkWithoutNextAffectedCall(state, sourceFile); - const compilerOptions = Debug.checkDefined(state.program).getCompilerOptions(); - if (outFile(compilerOptions)) { - Debug.assert(!state.semanticDiagnosticsPerFile); - // We dont need to cache the diagnostics just return them from program - return Debug.checkDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken); + /** + * Return the semantic diagnostics for the next affected file or undefined if iteration is complete + * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true + */ + function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult { + while (true) { + const affected = getNextAffectedFile(state, cancellationToken, computeHash, getCanonicalFileName, host); + let result; + if (!affected) return undefined; // Done + else if (affected !== state.program) { + // Get diagnostics for the affected file if its not ignored + const affectedSourceFile = affected as SourceFile; + if (!ignoreSourceFile || !ignoreSourceFile(affectedSourceFile)) { + result = getSemanticDiagnosticsOfFile(state, affectedSourceFile, cancellationToken); + } + state.seenAffectedFiles!.add(affectedSourceFile.resolvedPath); + state.affectedFilesIndex!++; + // Change in changeSet, buildInfo needs to be emitted + state.buildInfoEmitPending = true; + if (!result) continue; } - - if (sourceFile) { - return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken); + else { + // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) + result = state.program.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken); + state.changedFilesSet.clear(); + state.programEmitPending = getBuilderFileEmit(state.compilerOptions); } + return { result, affected }; + } + } - // When semantic builder asks for diagnostics of the whole program, - // ensure that all the affected files are handled - // eslint-disable-next-line no-empty - while (getSemanticDiagnosticsOfNextAffectedFile(cancellationToken)) { - } + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics + */ + function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + assertSourceFileOkWithoutNextAffectedCall(state, sourceFile); + const compilerOptions = Debug.checkDefined(state.program).getCompilerOptions(); + if (outFile(compilerOptions)) { + Debug.assert(!state.semanticDiagnosticsPerFile); + // We dont need to cache the diagnostics just return them from program + return Debug.checkDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken); + } - let diagnostics: Diagnostic[] | undefined; - for (const sourceFile of Debug.checkDefined(state.program).getSourceFiles()) { - diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken)); - } - return diagnostics || emptyArray; + if (sourceFile) { + return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken); } - } - function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) { - const existingKind = state.affectedFilesPendingEmit?.get(affectedFilePendingEmit) || BuilderFileEmit.None; - (state.affectedFilesPendingEmit ??= new Map()).set(affectedFilePendingEmit, existingKind | kind); - } + // When semantic builder asks for diagnostics of the whole program, + // ensure that all the affected files are handled + // eslint-disable-next-line no-empty + while (getSemanticDiagnosticsOfNextAffectedFile(cancellationToken)) { + } - export function toBuilderStateFileInfoForMultiEmit(fileInfo: ProgramMultiFileEmitBuildInfoFileInfo): BuilderState.FileInfo { - return isString(fileInfo) ? - { version: fileInfo, signature: fileInfo, affectsGlobalScope: undefined, impliedFormat: undefined } : - isString(fileInfo.signature) ? - fileInfo as BuilderState.FileInfo : - { version: fileInfo.version, signature: fileInfo.signature === false ? undefined : fileInfo.version, affectsGlobalScope: fileInfo.affectsGlobalScope, impliedFormat: fileInfo.impliedFormat }; + let diagnostics: Diagnostic[] | undefined; + for (const sourceFile of Debug.checkDefined(state.program).getSourceFiles()) { + diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken)); + } + return diagnostics || emptyArray; } +} - export function toBuilderFileEmit(value: ProgramBuilderInfoFilePendingEmit, fullEmitForOptions: BuilderFileEmit): BuilderFileEmit{ - return isNumber(value) ? fullEmitForOptions : value[1] || BuilderFileEmit.Dts; - } +function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) { + const existingKind = state.affectedFilesPendingEmit?.get(affectedFilePendingEmit) || BuilderFileEmit.None; + (state.affectedFilesPendingEmit ??= new Map()).set(affectedFilePendingEmit, existingKind | kind); +} - export function toProgramEmitPending(value: ProgramBuildInfoBundlePendingEmit, options: CompilerOptions | undefined): BuilderFileEmit | undefined { - return !value ? getBuilderFileEmit(options || {}) : value; - } +/** @internal */ +export function toBuilderStateFileInfoForMultiEmit(fileInfo: ProgramMultiFileEmitBuildInfoFileInfo): BuilderState.FileInfo { + return isString(fileInfo) ? + { version: fileInfo, signature: fileInfo, affectsGlobalScope: undefined, impliedFormat: undefined } : + isString(fileInfo.signature) ? + fileInfo as BuilderState.FileInfo : + { version: fileInfo.version, signature: fileInfo.signature === false ? undefined : fileInfo.version, affectsGlobalScope: fileInfo.affectsGlobalScope, impliedFormat: fileInfo.impliedFormat }; +} - export function createBuilderProgramUsingProgramBuildInfo(buildInfo: BuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram { - const program = buildInfo.program!; - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - - let state: ReusableBuilderProgramState; - const filePaths = program.fileNames?.map(toPath); - let filePathsSetList: Set[] | undefined; - const latestChangedDtsFile = program.latestChangedDtsFile ? toAbsolutePath(program.latestChangedDtsFile) : undefined; - if (isProgramBundleEmitBuildInfo(program)) { - const fileInfos = new Map(); - program.fileInfos.forEach((fileInfo, index) => { - const path = toFilePath(index + 1 as ProgramBuildInfoFileId); - fileInfos.set(path, isString(fileInfo) ? { version: fileInfo, signature: undefined, affectsGlobalScope: undefined, impliedFormat: undefined } : fileInfo); - }); - state = { - fileInfos, - compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {}, - latestChangedDtsFile, - outSignature: program.outSignature, - programEmitPending: program.pendingEmit === undefined ? undefined : toProgramEmitPending(program.pendingEmit, program.options), - bundle: buildInfo.bundle, - }; - } - else { - filePathsSetList = program.fileIdsList?.map(fileIds => new Set(fileIds.map(toFilePath))); - const fileInfos = new Map(); - const emitSignatures = program.options?.composite && !outFile(program.options) ? new Map() : undefined; - program.fileInfos.forEach((fileInfo, index) => { - const path = toFilePath(index + 1 as ProgramBuildInfoFileId); - const stateFileInfo = toBuilderStateFileInfoForMultiEmit(fileInfo); - fileInfos.set(path, stateFileInfo); - if (emitSignatures && stateFileInfo.signature) emitSignatures.set(path, stateFileInfo.signature); - }); - program.emitSignatures?.forEach(value => { - if (isNumber(value)) emitSignatures!.delete(toFilePath(value)); - else { - const key = toFilePath(value[0]); - emitSignatures!.set(key, !isString(value[1]) && !value[1].length ? - // File signature is emit signature but differs in map - [emitSignatures!.get(key)! as string] : - value[1] - ); - } - }); - const fullEmitForOptions = program.affectedFilesPendingEmit ? getBuilderFileEmit(program.options || {}) : undefined; - state = { - fileInfos, - compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {}, - referencedMap: toManyToManyPathMap(program.referencedMap), - exportedModulesMap: toManyToManyPathMap(program.exportedModulesMap), - semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]), - hasReusableDiagnostic: true, - affectedFilesPendingEmit: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toFilePath(isNumber(value) ? value : value[0]), value => toBuilderFileEmit(value, fullEmitForOptions!)), - changedFilesSet: new Set(map(program.changeFileSet, toFilePath)), - latestChangedDtsFile, - emitSignatures: emitSignatures?.size ? emitSignatures : undefined, - }; - } +/** @internal */ +export function toBuilderFileEmit(value: ProgramBuilderInfoFilePendingEmit, fullEmitForOptions: BuilderFileEmit): BuilderFileEmit{ + return isNumber(value) ? fullEmitForOptions : value[1] || BuilderFileEmit.Dts; +} - return { - getState: () => state, - saveEmitState: noop as BuilderProgram["saveEmitState"], - restoreEmitState: noop, - getProgram: notImplemented, - getProgramOrUndefined: returnUndefined, - releaseProgram: noop, - getCompilerOptions: () => state.compilerOptions, - getSourceFile: notImplemented, - getSourceFiles: notImplemented, - getOptionsDiagnostics: notImplemented, - getGlobalDiagnostics: notImplemented, - getConfigFileParsingDiagnostics: notImplemented, - getSyntacticDiagnostics: notImplemented, - getDeclarationDiagnostics: notImplemented, - getSemanticDiagnostics: notImplemented, - emit: notImplemented, - getAllDependencies: notImplemented, - getCurrentDirectory: notImplemented, - emitNextAffectedFile: notImplemented, - getSemanticDiagnosticsOfNextAffectedFile: notImplemented, - emitBuildInfo: notImplemented, - close: noop, - hasChangedEmitSignature: returnFalse, +/** @internal */ +export function toProgramEmitPending(value: ProgramBuildInfoBundlePendingEmit, options: CompilerOptions | undefined): BuilderFileEmit | undefined { + return !value ? getBuilderFileEmit(options || {}) : value; +} + +/** @internal */ +export function createBuilderProgramUsingProgramBuildInfo(buildInfo: BuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram { + const program = buildInfo.program!; + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + + let state: ReusableBuilderProgramState; + const filePaths = program.fileNames?.map(toPath); + let filePathsSetList: Set[] | undefined; + const latestChangedDtsFile = program.latestChangedDtsFile ? toAbsolutePath(program.latestChangedDtsFile) : undefined; + if (isProgramBundleEmitBuildInfo(program)) { + const fileInfos = new Map(); + program.fileInfos.forEach((fileInfo, index) => { + const path = toFilePath(index + 1 as ProgramBuildInfoFileId); + fileInfos.set(path, isString(fileInfo) ? { version: fileInfo, signature: undefined, affectsGlobalScope: undefined, impliedFormat: undefined } : fileInfo); + }); + state = { + fileInfos, + compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {}, + latestChangedDtsFile, + outSignature: program.outSignature, + programEmitPending: program.pendingEmit === undefined ? undefined : toProgramEmitPending(program.pendingEmit, program.options), + bundle: buildInfo.bundle, + }; + } + else { + filePathsSetList = program.fileIdsList?.map(fileIds => new Set(fileIds.map(toFilePath))); + const fileInfos = new Map(); + const emitSignatures = program.options?.composite && !outFile(program.options) ? new Map() : undefined; + program.fileInfos.forEach((fileInfo, index) => { + const path = toFilePath(index + 1 as ProgramBuildInfoFileId); + const stateFileInfo = toBuilderStateFileInfoForMultiEmit(fileInfo); + fileInfos.set(path, stateFileInfo); + if (emitSignatures && stateFileInfo.signature) emitSignatures.set(path, stateFileInfo.signature); + }); + program.emitSignatures?.forEach(value => { + if (isNumber(value)) emitSignatures!.delete(toFilePath(value)); + else { + const key = toFilePath(value[0]); + emitSignatures!.set(key, !isString(value[1]) && !value[1].length ? + // File signature is emit signature but differs in map + [emitSignatures!.get(key)! as string] : + value[1] + ); + } + }); + const fullEmitForOptions = program.affectedFilesPendingEmit ? getBuilderFileEmit(program.options || {}) : undefined; + state = { + fileInfos, + compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {}, + referencedMap: toManyToManyPathMap(program.referencedMap), + exportedModulesMap: toManyToManyPathMap(program.exportedModulesMap), + semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]), + hasReusableDiagnostic: true, + affectedFilesPendingEmit: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toFilePath(isNumber(value) ? value : value[0]), value => toBuilderFileEmit(value, fullEmitForOptions!)), + changedFilesSet: new Set(map(program.changeFileSet, toFilePath)), + latestChangedDtsFile, + emitSignatures: emitSignatures?.size ? emitSignatures : undefined, }; + } - function toPath(path: string) { - return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); - } + return { + getState: () => state, + saveEmitState: noop as BuilderProgram["saveEmitState"], + restoreEmitState: noop, + getProgram: notImplemented, + getProgramOrUndefined: returnUndefined, + releaseProgram: noop, + getCompilerOptions: () => state.compilerOptions, + getSourceFile: notImplemented, + getSourceFiles: notImplemented, + getOptionsDiagnostics: notImplemented, + getGlobalDiagnostics: notImplemented, + getConfigFileParsingDiagnostics: notImplemented, + getSyntacticDiagnostics: notImplemented, + getDeclarationDiagnostics: notImplemented, + getSemanticDiagnostics: notImplemented, + emit: notImplemented, + getAllDependencies: notImplemented, + getCurrentDirectory: notImplemented, + emitNextAffectedFile: notImplemented, + getSemanticDiagnosticsOfNextAffectedFile: notImplemented, + emitBuildInfo: notImplemented, + close: noop, + hasChangedEmitSignature: returnFalse, + }; - function toAbsolutePath(path: string) { - return getNormalizedAbsolutePath(path, buildInfoDirectory); - } + function toPath(path: string) { + return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); + } - function toFilePath(fileId: ProgramBuildInfoFileId) { - return filePaths[fileId - 1]; - } + function toAbsolutePath(path: string) { + return getNormalizedAbsolutePath(path, buildInfoDirectory); + } - function toFilePathsSet(fileIdsListId: ProgramBuildInfoFileIdListId) { - return filePathsSetList![fileIdsListId - 1]; - } + function toFilePath(fileId: ProgramBuildInfoFileId) { + return filePaths[fileId - 1]; + } - function toManyToManyPathMap(referenceMap: ProgramBuildInfoReferencedMap | undefined): BuilderState.ManyToManyPathMap | undefined { - if (!referenceMap) { - return undefined; - } + function toFilePathsSet(fileIdsListId: ProgramBuildInfoFileIdListId) { + return filePathsSetList![fileIdsListId - 1]; + } - const map = BuilderState.createManyToManyPathMap(); - referenceMap.forEach(([fileId, fileIdListId]) => - map.set(toFilePath(fileId), toFilePathsSet(fileIdListId)) - ); - return map; + function toManyToManyPathMap(referenceMap: ProgramBuildInfoReferencedMap | undefined): BuilderState.ManyToManyPathMap | undefined { + if (!referenceMap) { + return undefined; } - } - export function getBuildInfoFileVersionMap( - program: ProgramBuildInfo, - buildInfoPath: string, - host: Pick - ): ESMap { - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - const fileInfos = new Map(); - program.fileInfos.forEach((fileInfo, index) => { - const path = toPath(program.fileNames[index], buildInfoDirectory, getCanonicalFileName); - const version = isString(fileInfo) ? fileInfo : fileInfo.version; - fileInfos.set(path, version); - }); - return fileInfos; + const map = BuilderState.createManyToManyPathMap(); + referenceMap.forEach(([fileId, fileIdListId]) => + map.set(toFilePath(fileId), toFilePathsSet(fileIdListId)) + ); + return map; } +} - export function createRedirectedBuilderProgram(getState: () => { program?: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram { - return { - getState: notImplemented, - saveEmitState: noop as BuilderProgram["saveEmitState"], - restoreEmitState: noop, - getProgram, - getProgramOrUndefined: () => getState().program, - releaseProgram: () => getState().program = undefined, - getCompilerOptions: () => getState().compilerOptions, - getSourceFile: fileName => getProgram().getSourceFile(fileName), - getSourceFiles: () => getProgram().getSourceFiles(), - getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken), - getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken), - getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics, - getSyntacticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSyntacticDiagnostics(sourceFile, cancellationToken), - getDeclarationDiagnostics: (sourceFile, cancellationToken) => getProgram().getDeclarationDiagnostics(sourceFile, cancellationToken), - getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken), - emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), - emitBuildInfo: (writeFile, cancellationToken) => getProgram().emitBuildInfo(writeFile, cancellationToken), - getAllDependencies: notImplemented, - getCurrentDirectory: () => getProgram().getCurrentDirectory(), - close: noop, - }; +/** @internal */ +export function getBuildInfoFileVersionMap( + program: ProgramBuildInfo, + buildInfoPath: string, + host: Pick +): ESMap { + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + const fileInfos = new Map(); + program.fileInfos.forEach((fileInfo, index) => { + const path = toPath(program.fileNames[index], buildInfoDirectory, getCanonicalFileName); + const version = isString(fileInfo) ? fileInfo : fileInfo.version; + fileInfos.set(path, version); + }); + return fileInfos; +} - function getProgram() { - return Debug.checkDefined(getState().program); - } +/** @internal */ +export function createRedirectedBuilderProgram(getState: () => { program?: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram { + return { + getState: notImplemented, + saveEmitState: noop as BuilderProgram["saveEmitState"], + restoreEmitState: noop, + getProgram, + getProgramOrUndefined: () => getState().program, + releaseProgram: () => getState().program = undefined, + getCompilerOptions: () => getState().compilerOptions, + getSourceFile: fileName => getProgram().getSourceFile(fileName), + getSourceFiles: () => getProgram().getSourceFiles(), + getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken), + getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken), + getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics, + getSyntacticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSyntacticDiagnostics(sourceFile, cancellationToken), + getDeclarationDiagnostics: (sourceFile, cancellationToken) => getProgram().getDeclarationDiagnostics(sourceFile, cancellationToken), + getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken), + emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), + emitBuildInfo: (writeFile, cancellationToken) => getProgram().emitBuildInfo(writeFile, cancellationToken), + getAllDependencies: notImplemented, + getCurrentDirectory: () => getProgram().getCurrentDirectory(), + close: noop, + }; + + function getProgram() { + return Debug.checkDefined(getState().program); } } diff --git a/src/compiler/builderPublic.ts b/src/compiler/builderPublic.ts index cc1a971753f3b..89c605eee370b 100644 --- a/src/compiler/builderPublic.ts +++ b/src/compiler/builderPublic.ts @@ -1,181 +1,191 @@ -namespace ts { - export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; +import { + BuilderProgramKind, CancellationToken, CompilerHost, CompilerOptions, createBuilderProgram, + createRedirectedBuilderProgram, CustomTransformers, Diagnostic, DiagnosticWithLocation, EmitResult, + getBuilderCreationParameters, Program, ProjectReference, ReusableBuilderProgramState, SavedBuildProgramEmitState, + SourceFile, WriteFileCallback, +} from "./_namespaces/ts"; - export interface BuilderProgramHost { - /** - * return true if file names are treated with case sensitivity - */ - useCaseSensitiveFileNames(): boolean; - /** - * If provided this would be used this hash instead of actual file shape text for detecting changes - */ - createHash?: (data: string) => string; - /** - * When emit or emitNextAffectedFile are called without writeFile, - * this callback if present would be used to write files - */ - writeFile?: WriteFileCallback; - /** - * disable using source file version as signature for testing - */ - /*@internal*/ - disableUseFileVersionAsSignature?: boolean; - /** - * Store the list of files that update signature during the emit - */ - /*@internal*/ - storeFilesChangingSignatureDuringEmit?: boolean; - /** - * Gets the current time - */ - /*@internal*/ - now?(): Date; - } +export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; +export interface BuilderProgramHost { /** - * Builder to manage the program state changes - */ - export interface BuilderProgram { - /*@internal*/ - getState(): ReusableBuilderProgramState; - /*@internal*/ - saveEmitState(): SavedBuildProgramEmitState; - /*@internal*/ - restoreEmitState(saved: SavedBuildProgramEmitState): void; - /*@internal*/ - hasChangedEmitSignature?(): boolean; - /** - * Returns current program - */ - getProgram(): Program; - /** - * Returns current program that could be undefined if the program was released - */ - /*@internal*/ - getProgramOrUndefined(): Program | undefined; - /** - * Releases reference to the program, making all the other operations that need program to fail. - */ - /*@internal*/ - releaseProgram(): void; - /** - * Get compiler options of the program - */ - getCompilerOptions(): CompilerOptions; - /** - * Get the source file in the program with file name - */ - getSourceFile(fileName: string): SourceFile | undefined; - /** - * Get a list of files in the program - */ - getSourceFiles(): readonly SourceFile[]; - /** - * Get the diagnostics for compiler options - */ - getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the diagnostics that dont belong to any file - */ - getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the diagnostics from config file parsing - */ - getConfigFileParsingDiagnostics(): readonly Diagnostic[]; - /** - * Get the syntax diagnostics, for all source files if source file is not supplied - */ - getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Get the declaration diagnostics, for all source files if source file is not supplied - */ - getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[]; - /** - * Get all the dependencies of the file - */ - getAllDependencies(sourceFile: SourceFile): readonly string[]; - - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, - * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics - */ - getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - /** - * Emits the JavaScript and declaration files. - * When targetSource file is specified, emits the files corresponding to that source file, - * otherwise for the whole program. - * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, - * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, - * it will only emit all the affected files instead of whole program - * - * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host - * in that order would be used to write the files - */ - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; - /*@internal*/ - emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult; - /** - * Get the current directory of the program - */ - getCurrentDirectory(): string; - /*@internal*/ - close(): void; - } - + * return true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames(): boolean; + /** + * If provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; + /** + * When emit or emitNextAffectedFile are called without writeFile, + * this callback if present would be used to write files + */ + writeFile?: WriteFileCallback; + /** + * disable using source file version as signature for testing + * + * @internal + */ + disableUseFileVersionAsSignature?: boolean; /** - * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + * Store the list of files that update signature during the emit + * + * @internal */ - export interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { - /** - * Gets the semantic diagnostics from the program for the next affected file and caches it - * Returns undefined if the iteration is complete - */ - getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult; - } + storeFilesChangingSignatureDuringEmit?: boolean; + /** + * Gets the current time + * + * @internal + */ + now?(): Date; +} +/** + * Builder to manage the program state changes + */ +export interface BuilderProgram { + /** @internal */ + getState(): ReusableBuilderProgramState; + /** @internal */ + saveEmitState(): SavedBuildProgramEmitState; + /** @internal */ + restoreEmitState(saved: SavedBuildProgramEmitState): void; + /** @internal */ + hasChangedEmitSignature?(): boolean; + /** + * Returns current program + */ + getProgram(): Program; + /** + * Returns current program that could be undefined if the program was released + * + * @internal + */ + getProgramOrUndefined(): Program | undefined; + /** + * Releases reference to the program, making all the other operations that need program to fail. + * + * @internal + */ + releaseProgram(): void; + /** + * Get compiler options of the program + */ + getCompilerOptions(): CompilerOptions; + /** + * Get the source file in the program with file name + */ + getSourceFile(fileName: string): SourceFile | undefined; + /** + * Get a list of files in the program + */ + getSourceFiles(): readonly SourceFile[]; /** - * 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 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 - * in that order would be used to write the files - */ - emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; - } + * Get the diagnostics for compiler options + */ + getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Get the diagnostics that dont belong to any file + */ + getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Get the diagnostics from config file parsing + */ + getConfigFileParsingDiagnostics(): readonly Diagnostic[]; + /** + * Get the syntax diagnostics, for all source files if source file is not supplied + */ + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Get the declaration diagnostics, for all source files if source file is not supplied + */ + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[]; + /** + * Get all the dependencies of the file + */ + getAllDependencies(sourceFile: SourceFile): readonly string[]; /** - * Create the builder to manage semantic diagnostics and cache them + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics */ - export function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): SemanticDiagnosticsBuilderProgram; - export function createSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): SemanticDiagnosticsBuilderProgram; - export function createSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { - return createBuilderProgram(BuilderProgramKind.SemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); - } + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; + /** @internal */ + emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult; + /** + * Get the current directory of the program + */ + getCurrentDirectory(): string; + /** @internal */ + close(): void; +} +/** + * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + */ +export interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { /** - * Create the builder that can handle the changes in program and iterate through changed files - * to emit the those files and manage semantic diagnostics cache as well + * Gets the semantic diagnostics from the program for the next affected file and caches it + * Returns undefined if the iteration is complete */ - export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): EmitAndSemanticDiagnosticsBuilderProgram; - export function createEmitAndSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): EmitAndSemanticDiagnosticsBuilderProgram; - export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { - return createBuilderProgram(BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); - } + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult; +} +/** + * 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 SemanticDiagnosticsBuilderProgram { /** - * Creates a builder thats just abstraction over program and can be used with watch + * 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 + * in that order would be used to write the files */ - export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): BuilderProgram; - export function createAbstractBuilder(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram; - export function createAbstractBuilder(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram { - const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); - return createRedirectedBuilderProgram(() => ({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }), newConfigFileParsingDiagnostics); - } + emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; +} + +/** + * Create the builder to manage semantic diagnostics and cache them + */ +export function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): SemanticDiagnosticsBuilderProgram; +export function createSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): SemanticDiagnosticsBuilderProgram; +export function createSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { + return createBuilderProgram(BuilderProgramKind.SemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); +} + +/** + * Create the builder that can handle the changes in program and iterate through changed files + * to emit the those files and manage semantic diagnostics cache as well + */ +export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): EmitAndSemanticDiagnosticsBuilderProgram; +export function createEmitAndSemanticDiagnosticsBuilderProgram(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): EmitAndSemanticDiagnosticsBuilderProgram; +export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | EmitAndSemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]) { + return createBuilderProgram(BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences)); +} + +/** + * Creates a builder thats just abstraction over program and can be used with watch + */ +export function createAbstractBuilder(newProgram: Program, host: BuilderProgramHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[]): BuilderProgram; +export function createAbstractBuilder(rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram; +export function createAbstractBuilder(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: CompilerHost | BuilderProgram, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderProgram { + const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters(newProgramOrRootNames, hostOrOptions, oldProgramOrHost, configFileParsingDiagnosticsOrOldProgram, configFileParsingDiagnostics, projectReferences); + return createRedirectedBuilderProgram(() => ({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }), newConfigFileParsingDiagnostics); } diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 176ddcd810bda..6257d8a311b07 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -1,632 +1,641 @@ -/*@internal*/ -namespace ts { - export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, - cancellationToken?: CancellationToken, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitOutput { - const outputFiles: OutputFile[] = []; - const { emitSkipped, diagnostics } = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit); - return { outputFiles, emitSkipped, diagnostics }; - - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { - outputFiles.push({ name: fileName, writeByteOrderMark, text }); - } +import { + arrayFrom, CancellationToken, computeSignatureWithDiagnostics, CustomTransformers, Debug, EmitOutput, emptyArray, + ESMap, ExportedModulesFromDeclarationEmit, GetCanonicalFileName, getDirectoryPath, getSourceFileOfNode, + isDeclarationFileName, isExternalOrCommonJsModule, isGlobalScopeAugmentation, isJsonSourceFile, + isModuleWithStringLiteralName, isStringLiteral, Iterator, Map, mapDefined, mapDefinedIterator, ModuleDeclaration, + ModuleKind, outFile, OutputFile, Path, Program, ReadonlySet, Set, some, SourceFile, StringLiteralLike, Symbol, + toPath, TypeChecker, +} from "./_namespaces/ts"; + +/** @internal */ +export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, + cancellationToken?: CancellationToken, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitOutput { + const outputFiles: OutputFile[] = []; + const { emitSkipped, diagnostics } = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit); + return { outputFiles, emitSkipped, diagnostics }; + + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { + outputFiles.push({ name: fileName, writeByteOrderMark, text }); } - export interface BuilderState { - /** - * Information of the file eg. its version, signature etc - */ - fileInfos: ESMap; - /** - * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - * Otherwise undefined - * Thus non undefined value indicates, module emit - */ - readonly referencedMap?: BuilderState.ReadonlyManyToManyPathMap | undefined; - /** - * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled - * Otherwise undefined - * - * This is equivalent to referencedMap, but for the emitted .d.ts file. - */ - readonly exportedModulesMap?: BuilderState.ManyToManyPathMap | undefined; - - /** - * true if file version is used as signature - * This helps in delaying the calculation of the d.ts hash as version for the file till reasonable time - */ - useFileVersionAsSignature?: boolean; - /** - * Map of files that have already called update signature. - * That means hence forth these files are assumed to have - * no change in their signature for this version of the program - */ - hasCalledUpdateShapeSignature?: Set; - /** - * Stores signatures before before the update till affected file is commited - */ - oldSignatures?: ESMap; - /** - * Stores exportedModulesMap before the update till affected file is commited - */ - oldExportedModulesMap?: ESMap | false>; - /** - * Cache of all files excluding default library file for the current program - */ - allFilesExcludingDefaultLibraryFile?: readonly SourceFile[]; - /** - * Cache of all the file names - */ - allFileNames?: readonly string[]; +} +/** @internal */ +export interface BuilderState { + /** + * Information of the file eg. its version, signature etc + */ + fileInfos: ESMap; + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + * Thus non undefined value indicates, module emit + */ + readonly referencedMap?: BuilderState.ReadonlyManyToManyPathMap | undefined; + /** + * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled + * Otherwise undefined + * + * This is equivalent to referencedMap, but for the emitted .d.ts file. + */ + readonly exportedModulesMap?: BuilderState.ManyToManyPathMap | undefined; + + /** + * true if file version is used as signature + * This helps in delaying the calculation of the d.ts hash as version for the file till reasonable time + */ + useFileVersionAsSignature?: boolean; + /** + * Map of files that have already called update signature. + * That means hence forth these files are assumed to have + * no change in their signature for this version of the program + */ + hasCalledUpdateShapeSignature?: Set; + /** + * Stores signatures before before the update till affected file is commited + */ + oldSignatures?: ESMap; + /** + * Stores exportedModulesMap before the update till affected file is commited + */ + oldExportedModulesMap?: ESMap | false>; + /** + * Cache of all files excluding default library file for the current program + */ + allFilesExcludingDefaultLibraryFile?: readonly SourceFile[]; + /** + * Cache of all the file names + */ + allFileNames?: readonly string[]; +} +/** @internal */ +export namespace BuilderState { + /** + * Information about the source file: Its version and optional signature from last emit + */ + export interface FileInfo { + readonly version: string; + signature: string | undefined; + affectsGlobalScope: true | undefined; + impliedFormat: SourceFile["impliedNodeFormat"]; } - export namespace BuilderState { - /** - * Information about the source file: Its version and optional signature from last emit - */ - export interface FileInfo { - readonly version: string; - signature: string | undefined; - affectsGlobalScope: true | undefined; - impliedFormat: SourceFile["impliedNodeFormat"]; - } - export interface ReadonlyManyToManyPathMap { - getKeys(v: Path): ReadonlySet | undefined; - getValues(k: Path): ReadonlySet | undefined; - keys(): Iterator; - } + export interface ReadonlyManyToManyPathMap { + getKeys(v: Path): ReadonlySet | undefined; + getValues(k: Path): ReadonlySet | undefined; + keys(): Iterator; + } - export interface ManyToManyPathMap extends ReadonlyManyToManyPathMap { - deleteKey(k: Path): boolean; - set(k: Path, v: ReadonlySet): void; - } + export interface ManyToManyPathMap extends ReadonlyManyToManyPathMap { + deleteKey(k: Path): boolean; + set(k: Path, v: ReadonlySet): void; + } - export function createManyToManyPathMap(): ManyToManyPathMap { - function create(forward: ESMap>, reverse: ESMap>, deleted: Set | undefined): ManyToManyPathMap { - const map: ManyToManyPathMap = { - getKeys: v => reverse.get(v), - getValues: k => forward.get(k), - keys: () => forward.keys(), + export function createManyToManyPathMap(): ManyToManyPathMap { + function create(forward: ESMap>, reverse: ESMap>, deleted: Set | undefined): ManyToManyPathMap { + const map: ManyToManyPathMap = { + getKeys: v => reverse.get(v), + getValues: k => forward.get(k), + keys: () => forward.keys(), - deleteKey: k => { - (deleted ||= new Set()).add(k); + deleteKey: k => { + (deleted ||= new Set()).add(k); - const set = forward.get(k); - if (!set) { - return false; - } + const set = forward.get(k); + if (!set) { + return false; + } - set.forEach(v => deleteFromMultimap(reverse, v, k)); - forward.delete(k); - return true; - }, - set: (k, vSet) => { - deleted?.delete(k); - - const existingVSet = forward.get(k); - forward.set(k, vSet); - - existingVSet?.forEach(v => { - if (!vSet.has(v)) { - deleteFromMultimap(reverse, v, k); - } - }); - - vSet.forEach(v => { - if (!existingVSet?.has(v)) { - addToMultimap(reverse, v, k); - } - }); - - return map; - }, - }; - - return map; - } + set.forEach(v => deleteFromMultimap(reverse, v, k)); + forward.delete(k); + return true; + }, + set: (k, vSet) => { + deleted?.delete(k); - return create(new Map>(), new Map>(), /*deleted*/ undefined); - } + const existingVSet = forward.get(k); + forward.set(k, vSet); - function addToMultimap(map: ESMap>, k: K, v: V): void { - let set = map.get(k); - if (!set) { - set = new Set(); - map.set(k, set); - } - set.add(v); - } + existingVSet?.forEach(v => { + if (!vSet.has(v)) { + deleteFromMultimap(reverse, v, k); + } + }); - function deleteFromMultimap(map: ESMap>, k: K, v: V): boolean { - const set = map.get(k); + vSet.forEach(v => { + if (!existingVSet?.has(v)) { + addToMultimap(reverse, v, k); + } + }); - if (set?.delete(v)) { - if (!set.size) { - map.delete(k); - } - return true; - } + return map; + }, + }; - return false; + return map; } - /** - * Compute the hash to store the shape of the file - */ - export type ComputeHash = ((data: string) => string) | undefined; + return create(new Map>(), new Map>(), /*deleted*/ undefined); + } - function getReferencedFilesFromImportedModuleSymbol(symbol: Symbol): Path[] { - return mapDefined(symbol.declarations, declaration => getSourceFileOfNode(declaration)?.resolvedPath); + function addToMultimap(map: ESMap>, k: K, v: V): void { + let set = map.get(k); + if (!set) { + set = new Set(); + map.set(k, set); } + set.add(v); + } - /** - * Get the module source file and all augmenting files from the import name node from file - */ - function getReferencedFilesFromImportLiteral(checker: TypeChecker, importName: StringLiteralLike): Path[] | undefined { - const symbol = checker.getSymbolAtLocation(importName); - return symbol && getReferencedFilesFromImportedModuleSymbol(symbol); - } + function deleteFromMultimap(map: ESMap>, k: K, v: V): boolean { + const set = map.get(k); - /** - * Gets the path to reference file from file name, it could be resolvedPath if present otherwise path - */ - function getReferencedFileFromFileName(program: Program, fileName: string, sourceFileDirectory: Path, getCanonicalFileName: GetCanonicalFileName): Path { - return toPath(program.getProjectReferenceRedirect(fileName) || fileName, sourceFileDirectory, getCanonicalFileName); + if (set?.delete(v)) { + if (!set.size) { + map.delete(k); + } + return true; } - /** - * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true - */ - function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): Set | undefined { - let referencedFiles: Set | undefined; - - // We need to use a set here since the code can contain the same import twice, - // but that will only be one dependency. - // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - if (sourceFile.imports && sourceFile.imports.length > 0) { - const checker: TypeChecker = program.getTypeChecker(); - for (const importName of sourceFile.imports) { - const declarationSourceFilePaths = getReferencedFilesFromImportLiteral(checker, importName); - declarationSourceFilePaths?.forEach(addReferencedFile); - } - } + return false; + } - const sourceFileDirectory = getDirectoryPath(sourceFile.resolvedPath); - // Handle triple slash references - if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { - for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = getReferencedFileFromFileName(program, referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(referencedPath); - } - } + /** + * Compute the hash to store the shape of the file + */ + export type ComputeHash = ((data: string) => string) | undefined; - // Handle type reference directives - if (sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { - if (!resolvedTypeReferenceDirective) { - return; - } + function getReferencedFilesFromImportedModuleSymbol(symbol: Symbol): Path[] { + return mapDefined(symbol.declarations, declaration => getSourceFileOfNode(declaration)?.resolvedPath); + } - const fileName = resolvedTypeReferenceDirective.resolvedFileName!; // TODO: GH#18217 - const typeFilePath = getReferencedFileFromFileName(program, fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(typeFilePath); - }); - } + /** + * Get the module source file and all augmenting files from the import name node from file + */ + function getReferencedFilesFromImportLiteral(checker: TypeChecker, importName: StringLiteralLike): Path[] | undefined { + const symbol = checker.getSymbolAtLocation(importName); + return symbol && getReferencedFilesFromImportedModuleSymbol(symbol); + } - // Add module augmentation as references - if (sourceFile.moduleAugmentations.length) { - const checker = program.getTypeChecker(); - for (const moduleName of sourceFile.moduleAugmentations) { - if (!isStringLiteral(moduleName)) continue; - const symbol = checker.getSymbolAtLocation(moduleName); - if (!symbol) continue; + /** + * Gets the path to reference file from file name, it could be resolvedPath if present otherwise path + */ + function getReferencedFileFromFileName(program: Program, fileName: string, sourceFileDirectory: Path, getCanonicalFileName: GetCanonicalFileName): Path { + return toPath(program.getProjectReferenceRedirect(fileName) || fileName, sourceFileDirectory, getCanonicalFileName); + } - // Add any file other than our own as reference - addReferenceFromAmbientModule(symbol); - } + /** + * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true + */ + function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): Set | undefined { + let referencedFiles: Set | undefined; + + // We need to use a set here since the code can contain the same import twice, + // but that will only be one dependency. + // To avoid invernal conversion, the key of the referencedFiles map must be of type Path + if (sourceFile.imports && sourceFile.imports.length > 0) { + const checker: TypeChecker = program.getTypeChecker(); + for (const importName of sourceFile.imports) { + const declarationSourceFilePaths = getReferencedFilesFromImportLiteral(checker, importName); + declarationSourceFilePaths?.forEach(addReferencedFile); } + } - // From ambient modules - for (const ambientModule of program.getTypeChecker().getAmbientModules()) { - if (ambientModule.declarations && ambientModule.declarations.length > 1) { - addReferenceFromAmbientModule(ambientModule); - } + const sourceFileDirectory = getDirectoryPath(sourceFile.resolvedPath); + // Handle triple slash references + if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { + for (const referencedFile of sourceFile.referencedFiles) { + const referencedPath = getReferencedFileFromFileName(program, referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(referencedPath); } + } - return referencedFiles; - - function addReferenceFromAmbientModule(symbol: Symbol) { - if (!symbol.declarations) { + // Handle type reference directives + if (sourceFile.resolvedTypeReferenceDirectiveNames) { + sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { + if (!resolvedTypeReferenceDirective) { return; } + + const fileName = resolvedTypeReferenceDirective.resolvedFileName!; // TODO: GH#18217 + const typeFilePath = getReferencedFileFromFileName(program, fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(typeFilePath); + }); + } + + // Add module augmentation as references + if (sourceFile.moduleAugmentations.length) { + const checker = program.getTypeChecker(); + for (const moduleName of sourceFile.moduleAugmentations) { + if (!isStringLiteral(moduleName)) continue; + const symbol = checker.getSymbolAtLocation(moduleName); + if (!symbol) continue; + // Add any file other than our own as reference - for (const declaration of symbol.declarations) { - const declarationSourceFile = getSourceFileOfNode(declaration); - if (declarationSourceFile && - declarationSourceFile !== sourceFile) { - addReferencedFile(declarationSourceFile.resolvedPath); - } - } + addReferenceFromAmbientModule(symbol); } + } - function addReferencedFile(referencedPath: Path) { - (referencedFiles || (referencedFiles = new Set())).add(referencedPath); + // From ambient modules + for (const ambientModule of program.getTypeChecker().getAmbientModules()) { + if (ambientModule.declarations && ambientModule.declarations.length > 1) { + addReferenceFromAmbientModule(ambientModule); } } - /** - * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed - */ - export function canReuseOldState(newReferencedMap: ReadonlyManyToManyPathMap | undefined, oldState: BuilderState | undefined) { - return oldState && !oldState.referencedMap === !newReferencedMap; - } + return referencedFiles; - /** - * Creates the state of file references and signature for the new program from oldState if it is safe - */ - export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly, disableUseFileVersionAsSignature?: boolean): BuilderState { - const fileInfos = new Map(); - const options = newProgram.getCompilerOptions(); - const isOutFile = outFile(options); - const referencedMap = options.module !== ModuleKind.None && !isOutFile ? - createManyToManyPathMap() : undefined; - const exportedModulesMap = referencedMap ? createManyToManyPathMap() : undefined; - const useOldState = canReuseOldState(referencedMap, oldState); - - // Ensure source files have parent pointers set - newProgram.getTypeChecker(); - - // Create the reference map, and set the file infos - for (const sourceFile of newProgram.getSourceFiles()) { - const version = Debug.checkDefined(sourceFile.version, "Program intended to be used with Builder should have source files with versions set"); - const oldUncommittedSignature = useOldState ? oldState!.oldSignatures?.get(sourceFile.resolvedPath) : undefined; - const signature = oldUncommittedSignature === undefined ? - useOldState ? oldState!.fileInfos.get(sourceFile.resolvedPath)?.signature : undefined : - oldUncommittedSignature || undefined; - if (referencedMap) { - const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); - if (newReferences) { - referencedMap.set(sourceFile.resolvedPath, newReferences); - } - // Copy old visible to outside files map - if (useOldState) { - const oldUncommittedExportedModules = oldState!.oldExportedModulesMap?.get(sourceFile.resolvedPath); - const exportedModules = oldUncommittedExportedModules === undefined ? - oldState!.exportedModulesMap!.getValues(sourceFile.resolvedPath) : - oldUncommittedExportedModules || undefined; - if (exportedModules) { - exportedModulesMap!.set(sourceFile.resolvedPath, exportedModules); - } - } + function addReferenceFromAmbientModule(symbol: Symbol) { + if (!symbol.declarations) { + return; + } + // Add any file other than our own as reference + for (const declaration of symbol.declarations) { + const declarationSourceFile = getSourceFileOfNode(declaration); + if (declarationSourceFile && + declarationSourceFile !== sourceFile) { + addReferencedFile(declarationSourceFile.resolvedPath); } - fileInfos.set(sourceFile.resolvedPath, { - version, - signature, - // No need to calculate affectsGlobalScope with --out since its not used at all - affectsGlobalScope: !isOutFile ? isFileAffectingGlobalScope(sourceFile) || undefined : undefined, - impliedFormat: sourceFile.impliedNodeFormat - }); } - - return { - fileInfos, - referencedMap, - exportedModulesMap, - useFileVersionAsSignature: !disableUseFileVersionAsSignature && !useOldState - }; } - /** - * Releases needed properties - */ - export function releaseCache(state: BuilderState) { - state.allFilesExcludingDefaultLibraryFile = undefined; - state.allFileNames = undefined; + function addReferencedFile(referencedPath: Path) { + (referencedFiles || (referencedFiles = new Set())).add(referencedPath); } + } - /** - * Gets the files affected by the path from the program - */ - export function getFilesAffectedBy( - state: BuilderState, - programOfThisState: Program, - path: Path, - cancellationToken: CancellationToken | undefined, - computeHash: ComputeHash, - getCanonicalFileName: GetCanonicalFileName, - ): readonly SourceFile[] { - const result = getFilesAffectedByWithOldState( - state, - programOfThisState, - path, - cancellationToken, - computeHash, - getCanonicalFileName, - ); - state.oldSignatures?.clear(); - state.oldExportedModulesMap?.clear(); - return result; - } + /** + * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed + */ + export function canReuseOldState(newReferencedMap: ReadonlyManyToManyPathMap | undefined, oldState: BuilderState | undefined) { + return oldState && !oldState.referencedMap === !newReferencedMap; + } - export function getFilesAffectedByWithOldState( - state: BuilderState, - programOfThisState: Program, - path: Path, - cancellationToken: CancellationToken | undefined, - computeHash: ComputeHash, - getCanonicalFileName: GetCanonicalFileName, - ): readonly SourceFile[] { - const sourceFile = programOfThisState.getSourceFileByPath(path); - if (!sourceFile) { - return emptyArray; + /** + * Creates the state of file references and signature for the new program from oldState if it is safe + */ + export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly, disableUseFileVersionAsSignature?: boolean): BuilderState { + const fileInfos = new Map(); + const options = newProgram.getCompilerOptions(); + const isOutFile = outFile(options); + const referencedMap = options.module !== ModuleKind.None && !isOutFile ? + createManyToManyPathMap() : undefined; + const exportedModulesMap = referencedMap ? createManyToManyPathMap() : undefined; + const useOldState = canReuseOldState(referencedMap, oldState); + + // Ensure source files have parent pointers set + newProgram.getTypeChecker(); + + // Create the reference map, and set the file infos + for (const sourceFile of newProgram.getSourceFiles()) { + const version = Debug.checkDefined(sourceFile.version, "Program intended to be used with Builder should have source files with versions set"); + const oldUncommittedSignature = useOldState ? oldState!.oldSignatures?.get(sourceFile.resolvedPath) : undefined; + const signature = oldUncommittedSignature === undefined ? + useOldState ? oldState!.fileInfos.get(sourceFile.resolvedPath)?.signature : undefined : + oldUncommittedSignature || undefined; + if (referencedMap) { + const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); + if (newReferences) { + referencedMap.set(sourceFile.resolvedPath, newReferences); + } + // Copy old visible to outside files map + if (useOldState) { + const oldUncommittedExportedModules = oldState!.oldExportedModulesMap?.get(sourceFile.resolvedPath); + const exportedModules = oldUncommittedExportedModules === undefined ? + oldState!.exportedModulesMap!.getValues(sourceFile.resolvedPath) : + oldUncommittedExportedModules || undefined; + if (exportedModules) { + exportedModulesMap!.set(sourceFile.resolvedPath, exportedModules); + } + } } + fileInfos.set(sourceFile.resolvedPath, { + version, + signature, + // No need to calculate affectsGlobalScope with --out since its not used at all + affectsGlobalScope: !isOutFile ? isFileAffectingGlobalScope(sourceFile) || undefined : undefined, + impliedFormat: sourceFile.impliedNodeFormat + }); + } - if (!updateShapeSignature(state, programOfThisState, sourceFile, cancellationToken, computeHash, getCanonicalFileName)) { - return [sourceFile]; - } + return { + fileInfos, + referencedMap, + exportedModulesMap, + useFileVersionAsSignature: !disableUseFileVersionAsSignature && !useOldState + }; + } - return (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, cancellationToken, computeHash, getCanonicalFileName); - } + /** + * Releases needed properties + */ + export function releaseCache(state: BuilderState) { + state.allFilesExcludingDefaultLibraryFile = undefined; + state.allFileNames = undefined; + } - export function updateSignatureOfFile(state: BuilderState, signature: string | undefined, path: Path) { - state.fileInfos.get(path)!.signature = signature; - (state.hasCalledUpdateShapeSignature ||= new Set()).add(path); + /** + * Gets the files affected by the path from the program + */ + export function getFilesAffectedBy( + state: BuilderState, + programOfThisState: Program, + path: Path, + cancellationToken: CancellationToken | undefined, + computeHash: ComputeHash, + getCanonicalFileName: GetCanonicalFileName, + ): readonly SourceFile[] { + const result = getFilesAffectedByWithOldState( + state, + programOfThisState, + path, + cancellationToken, + computeHash, + getCanonicalFileName, + ); + state.oldSignatures?.clear(); + state.oldExportedModulesMap?.clear(); + return result; + } + + export function getFilesAffectedByWithOldState( + state: BuilderState, + programOfThisState: Program, + path: Path, + cancellationToken: CancellationToken | undefined, + computeHash: ComputeHash, + getCanonicalFileName: GetCanonicalFileName, + ): readonly SourceFile[] { + const sourceFile = programOfThisState.getSourceFileByPath(path); + if (!sourceFile) { + return emptyArray; } - /** - * Returns if the shape of the signature has changed since last emit - */ - export function updateShapeSignature( - state: BuilderState, - programOfThisState: Program, - sourceFile: SourceFile, - cancellationToken: CancellationToken | undefined, - computeHash: ComputeHash, - getCanonicalFileName: GetCanonicalFileName, - useFileVersionAsSignature = state.useFileVersionAsSignature - ) { - // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (state.hasCalledUpdateShapeSignature?.has(sourceFile.resolvedPath)) return false; - - const info = state.fileInfos.get(sourceFile.resolvedPath)!; - const prevSignature = info.signature; - let latestSignature: string | undefined; - if (!sourceFile.isDeclarationFile && !useFileVersionAsSignature) { - programOfThisState.emit( - sourceFile, - (fileName, text, _writeByteOrderMark, _onError, sourceFiles, data) => { - Debug.assert(isDeclarationFileName(fileName), `File extension for signature expected to be dts: Got:: ${fileName}`); - latestSignature = computeSignatureWithDiagnostics( - sourceFile, - text, - computeHash, - getCanonicalFileName, - data, - ); - if (latestSignature !== prevSignature) { - updateExportedModules(state, sourceFile, sourceFiles![0].exportedModulesFromDeclarationEmit); - } - }, - cancellationToken, - /*emitOnlyDtsFiles*/ true, - /*customTransformers*/ undefined, - /*forceDtsEmit*/ true - ); - } - // Default is to use file version as signature - if (latestSignature === undefined) { - latestSignature = sourceFile.version; - if (state.exportedModulesMap && latestSignature !== prevSignature) { - (state.oldExportedModulesMap ||= new Map()).set(sourceFile.resolvedPath, state.exportedModulesMap.getValues(sourceFile.resolvedPath) || false); - // All the references in this file are exported - const references = state.referencedMap ? state.referencedMap.getValues(sourceFile.resolvedPath) : undefined; - if (references) { - state.exportedModulesMap.set(sourceFile.resolvedPath, references); - } - else { - state.exportedModulesMap.deleteKey(sourceFile.resolvedPath); - } - } - } - (state.oldSignatures ||= new Map()).set(sourceFile.resolvedPath, prevSignature || false); - (state.hasCalledUpdateShapeSignature ||= new Set()).add(sourceFile.resolvedPath); - info.signature = latestSignature; - return latestSignature !== prevSignature; + if (!updateShapeSignature(state, programOfThisState, sourceFile, cancellationToken, computeHash, getCanonicalFileName)) { + return [sourceFile]; } - /** - * Coverts the declaration emit result into exported modules map - */ - export function updateExportedModules(state: BuilderState, sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined) { - if (!state.exportedModulesMap) return; - (state.oldExportedModulesMap ||= new Map()).set(sourceFile.resolvedPath, state.exportedModulesMap.getValues(sourceFile.resolvedPath) || false); - if (!exportedModulesFromDeclarationEmit) { - state.exportedModulesMap.deleteKey(sourceFile.resolvedPath); - return; - } + return (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, cancellationToken, computeHash, getCanonicalFileName); + } - let exportedModules: Set | undefined; - exportedModulesFromDeclarationEmit.forEach(symbol => addExportedModule(getReferencedFilesFromImportedModuleSymbol(symbol))); - if (exportedModules) { - state.exportedModulesMap.set(sourceFile.resolvedPath, exportedModules); - } - else { - state.exportedModulesMap.deleteKey(sourceFile.resolvedPath); - } + export function updateSignatureOfFile(state: BuilderState, signature: string | undefined, path: Path) { + state.fileInfos.get(path)!.signature = signature; + (state.hasCalledUpdateShapeSignature ||= new Set()).add(path); + } - function addExportedModule(exportedModulePaths: Path[] | undefined) { - if (exportedModulePaths?.length) { - if (!exportedModules) { - exportedModules = new Set(); + /** + * Returns if the shape of the signature has changed since last emit + */ + export function updateShapeSignature( + state: BuilderState, + programOfThisState: Program, + sourceFile: SourceFile, + cancellationToken: CancellationToken | undefined, + computeHash: ComputeHash, + getCanonicalFileName: GetCanonicalFileName, + useFileVersionAsSignature = state.useFileVersionAsSignature + ) { + // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate + if (state.hasCalledUpdateShapeSignature?.has(sourceFile.resolvedPath)) return false; + + const info = state.fileInfos.get(sourceFile.resolvedPath)!; + const prevSignature = info.signature; + let latestSignature: string | undefined; + if (!sourceFile.isDeclarationFile && !useFileVersionAsSignature) { + programOfThisState.emit( + sourceFile, + (fileName, text, _writeByteOrderMark, _onError, sourceFiles, data) => { + Debug.assert(isDeclarationFileName(fileName), `File extension for signature expected to be dts: Got:: ${fileName}`); + latestSignature = computeSignatureWithDiagnostics( + sourceFile, + text, + computeHash, + getCanonicalFileName, + data, + ); + if (latestSignature !== prevSignature) { + updateExportedModules(state, sourceFile, sourceFiles![0].exportedModulesFromDeclarationEmit); } - exportedModulePaths.forEach(path => exportedModules!.add(path)); + }, + cancellationToken, + /*emitOnlyDtsFiles*/ true, + /*customTransformers*/ undefined, + /*forceDtsEmit*/ true + ); + } + // Default is to use file version as signature + if (latestSignature === undefined) { + latestSignature = sourceFile.version; + if (state.exportedModulesMap && latestSignature !== prevSignature) { + (state.oldExportedModulesMap ||= new Map()).set(sourceFile.resolvedPath, state.exportedModulesMap.getValues(sourceFile.resolvedPath) || false); + // All the references in this file are exported + const references = state.referencedMap ? state.referencedMap.getValues(sourceFile.resolvedPath) : undefined; + if (references) { + state.exportedModulesMap.set(sourceFile.resolvedPath, references); + } + else { + state.exportedModulesMap.deleteKey(sourceFile.resolvedPath); } } } + (state.oldSignatures ||= new Map()).set(sourceFile.resolvedPath, prevSignature || false); + (state.hasCalledUpdateShapeSignature ||= new Set()).add(sourceFile.resolvedPath); + info.signature = latestSignature; + return latestSignature !== prevSignature; + } - /** - * Get all the dependencies of the sourceFile - */ - export function getAllDependencies(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile): readonly string[] { - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file, all files depend on each other - if (outFile(compilerOptions)) { - return getAllFileNames(state, programOfThisState); - } + /** + * Coverts the declaration emit result into exported modules map + */ + export function updateExportedModules(state: BuilderState, sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined) { + if (!state.exportedModulesMap) return; + (state.oldExportedModulesMap ||= new Map()).set(sourceFile.resolvedPath, state.exportedModulesMap.getValues(sourceFile.resolvedPath) || false); + if (!exportedModulesFromDeclarationEmit) { + state.exportedModulesMap.deleteKey(sourceFile.resolvedPath); + return; + } - // If this is non module emit, or its a global file, it depends on all the source files - if (!state.referencedMap || isFileAffectingGlobalScope(sourceFile)) { - return getAllFileNames(state, programOfThisState); - } + let exportedModules: Set | undefined; + exportedModulesFromDeclarationEmit.forEach(symbol => addExportedModule(getReferencedFilesFromImportedModuleSymbol(symbol))); + if (exportedModules) { + state.exportedModulesMap.set(sourceFile.resolvedPath, exportedModules); + } + else { + state.exportedModulesMap.deleteKey(sourceFile.resolvedPath); + } - // Get the references, traversing deep from the referenceMap - const seenMap = new Set(); - const queue = [sourceFile.resolvedPath]; - while (queue.length) { - const path = queue.pop()!; - if (!seenMap.has(path)) { - seenMap.add(path); - const references = state.referencedMap.getValues(path); - if (references) { - const iterator = references.keys(); - for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { - queue.push(iterResult.value); - } - } + function addExportedModule(exportedModulePaths: Path[] | undefined) { + if (exportedModulePaths?.length) { + if (!exportedModules) { + exportedModules = new Set(); } + exportedModulePaths.forEach(path => exportedModules!.add(path)); } - - return arrayFrom(mapDefinedIterator(seenMap.keys(), path => programOfThisState.getSourceFileByPath(path)?.fileName ?? path)); } + } - /** - * Gets the names of all files from the program - */ - function getAllFileNames(state: BuilderState, programOfThisState: Program): readonly string[] { - if (!state.allFileNames) { - const sourceFiles = programOfThisState.getSourceFiles(); - state.allFileNames = sourceFiles === emptyArray ? emptyArray : sourceFiles.map(file => file.fileName); - } - return state.allFileNames; + /** + * Get all the dependencies of the sourceFile + */ + export function getAllDependencies(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile): readonly string[] { + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file, all files depend on each other + if (outFile(compilerOptions)) { + return getAllFileNames(state, programOfThisState); } - /** - * Gets the files referenced by the the file path - */ - export function getReferencedByPaths(state: Readonly, referencedFilePath: Path) { - const keys = state.referencedMap!.getKeys(referencedFilePath); - return keys ? arrayFrom(keys.keys()) : []; + // If this is non module emit, or its a global file, it depends on all the source files + if (!state.referencedMap || isFileAffectingGlobalScope(sourceFile)) { + return getAllFileNames(state, programOfThisState); } - /** - * For script files that contains only ambient external modules, although they are not actually external module files, - * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - * there are no point to rebuild all script files if these special files have changed. However, if any statement - * in the file is not ambient external module, we treat it as a regular script file. - */ - function containsOnlyAmbientModules(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (!isModuleWithStringLiteralName(statement)) { - return false; + // Get the references, traversing deep from the referenceMap + const seenMap = new Set(); + const queue = [sourceFile.resolvedPath]; + while (queue.length) { + const path = queue.pop()!; + if (!seenMap.has(path)) { + seenMap.add(path); + const references = state.referencedMap.getValues(path); + if (references) { + const iterator = references.keys(); + for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { + queue.push(iterResult.value); + } } } - return true; } - /** - * Return true if file contains anything that augments to global scope we need to build them as if - * they are global files as well as module - */ - function containsGlobalScopeAugmentation(sourceFile: SourceFile) { - return some(sourceFile.moduleAugmentations, augmentation => isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)); - } + return arrayFrom(mapDefinedIterator(seenMap.keys(), path => programOfThisState.getSourceFileByPath(path)?.fileName ?? path)); + } - /** - * Return true if the file will invalidate all files because it affectes global scope - */ - function isFileAffectingGlobalScope(sourceFile: SourceFile) { - return containsGlobalScopeAugmentation(sourceFile) || - !isExternalOrCommonJsModule(sourceFile) && !isJsonSourceFile(sourceFile) && !containsOnlyAmbientModules(sourceFile); + /** + * Gets the names of all files from the program + */ + function getAllFileNames(state: BuilderState, programOfThisState: Program): readonly string[] { + if (!state.allFileNames) { + const sourceFiles = programOfThisState.getSourceFiles(); + state.allFileNames = sourceFiles === emptyArray ? emptyArray : sourceFiles.map(file => file.fileName); } + return state.allFileNames; + } - /** - * Gets all files of the program excluding the default library file - */ - export function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile | undefined): readonly SourceFile[] { - // Use cached result - if (state.allFilesExcludingDefaultLibraryFile) { - return state.allFilesExcludingDefaultLibraryFile; - } + /** + * Gets the files referenced by the the file path + */ + export function getReferencedByPaths(state: Readonly, referencedFilePath: Path) { + const keys = state.referencedMap!.getKeys(referencedFilePath); + return keys ? arrayFrom(keys.keys()) : []; + } - let result: SourceFile[] | undefined; - if (firstSourceFile) addSourceFile(firstSourceFile); - for (const sourceFile of programOfThisState.getSourceFiles()) { - if (sourceFile !== firstSourceFile) { - addSourceFile(sourceFile); - } + /** + * For script files that contains only ambient external modules, although they are not actually external module files, + * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, + * there are no point to rebuild all script files if these special files have changed. However, if any statement + * in the file is not ambient external module, we treat it as a regular script file. + */ + function containsOnlyAmbientModules(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (!isModuleWithStringLiteralName(statement)) { + return false; } - state.allFilesExcludingDefaultLibraryFile = result || emptyArray; + } + return true; + } + + /** + * Return true if file contains anything that augments to global scope we need to build them as if + * they are global files as well as module + */ + function containsGlobalScopeAugmentation(sourceFile: SourceFile) { + return some(sourceFile.moduleAugmentations, augmentation => isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)); + } + + /** + * Return true if the file will invalidate all files because it affectes global scope + */ + function isFileAffectingGlobalScope(sourceFile: SourceFile) { + return containsGlobalScopeAugmentation(sourceFile) || + !isExternalOrCommonJsModule(sourceFile) && !isJsonSourceFile(sourceFile) && !containsOnlyAmbientModules(sourceFile); + } + + /** + * Gets all files of the program excluding the default library file + */ + export function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile | undefined): readonly SourceFile[] { + // Use cached result + if (state.allFilesExcludingDefaultLibraryFile) { return state.allFilesExcludingDefaultLibraryFile; + } - function addSourceFile(sourceFile: SourceFile) { - if (!programOfThisState.isSourceFileDefaultLibrary(sourceFile)) { - (result || (result = [])).push(sourceFile); - } + let result: SourceFile[] | undefined; + if (firstSourceFile) addSourceFile(firstSourceFile); + for (const sourceFile of programOfThisState.getSourceFiles()) { + if (sourceFile !== firstSourceFile) { + addSourceFile(sourceFile); } } + state.allFilesExcludingDefaultLibraryFile = result || emptyArray; + return state.allFilesExcludingDefaultLibraryFile; - /** - * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { - const compilerOptions = programOfThisState.getCompilerOptions(); - // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, - // so returning the file itself is good enough. - if (compilerOptions && outFile(compilerOptions)) { - return [sourceFileWithUpdatedShape]; + function addSourceFile(sourceFile: SourceFile) { + if (!programOfThisState.isSourceFileDefaultLibrary(sourceFile)) { + (result || (result = [])).push(sourceFile); } - return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); } + } - /** - * When program emits modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit( - state: BuilderState, - programOfThisState: Program, - sourceFileWithUpdatedShape: SourceFile, - cancellationToken: CancellationToken | undefined, - computeHash: ComputeHash, - getCanonicalFileName: GetCanonicalFileName, - ) { - if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) { - return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); - } + /** + * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + const compilerOptions = programOfThisState.getCompilerOptions(); + // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, + // so returning the file itself is good enough. + if (compilerOptions && outFile(compilerOptions)) { + return [sourceFileWithUpdatedShape]; + } + return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); + } - const compilerOptions = programOfThisState.getCompilerOptions(); - if (compilerOptions && (compilerOptions.isolatedModules || outFile(compilerOptions))) { - return [sourceFileWithUpdatedShape]; - } + /** + * When program emits modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenModuleEmit( + state: BuilderState, + programOfThisState: Program, + sourceFileWithUpdatedShape: SourceFile, + cancellationToken: CancellationToken | undefined, + computeHash: ComputeHash, + getCanonicalFileName: GetCanonicalFileName, + ) { + if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) { + return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); + } - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - const seenFileNamesMap = new Map(); - - // Start with the paths this file was referenced by - seenFileNamesMap.set(sourceFileWithUpdatedShape.resolvedPath, sourceFileWithUpdatedShape); - const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath); - while (queue.length > 0) { - const currentPath = queue.pop()!; - if (!seenFileNamesMap.has(currentPath)) { - const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!; - seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cancellationToken, computeHash, getCanonicalFileName)) { - queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath)); - } + const compilerOptions = programOfThisState.getCompilerOptions(); + if (compilerOptions && (compilerOptions.isolatedModules || outFile(compilerOptions))) { + return [sourceFileWithUpdatedShape]; + } + + // Now we need to if each file in the referencedBy list has a shape change as well. + // Because if so, its own referencedBy files need to be saved as well to make the + // emitting result consistent with files on disk. + const seenFileNamesMap = new Map(); + + // Start with the paths this file was referenced by + seenFileNamesMap.set(sourceFileWithUpdatedShape.resolvedPath, sourceFileWithUpdatedShape); + const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath); + while (queue.length > 0) { + const currentPath = queue.pop()!; + if (!seenFileNamesMap.has(currentPath)) { + const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!; + seenFileNamesMap.set(currentPath, currentSourceFile); + if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cancellationToken, computeHash, getCanonicalFileName)) { + queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath)); } } - - // Return array of values that needs emit - return arrayFrom(mapDefinedIterator(seenFileNamesMap.values(), value => value)); } + + // Return array of values that needs emit + return arrayFrom(mapDefinedIterator(seenFileNamesMap.values(), value => value)); } } diff --git a/src/compiler/builderStatePublic.ts b/src/compiler/builderStatePublic.ts index 0b4ad73acc4d4..d520c76ccc56d 100644 --- a/src/compiler/builderStatePublic.ts +++ b/src/compiler/builderStatePublic.ts @@ -1,14 +1,14 @@ -namespace ts { - export interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - /* @internal */ diagnostics: readonly Diagnostic[]; - } +import { Diagnostic, WriteFileCallbackData } from "./_namespaces/ts"; - export interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - /* @internal */ data?: WriteFileCallbackData; - } +export interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + /** @internal */ diagnostics: readonly Diagnostic[]; +} + +export interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + /** @internal */ data?: WriteFileCallbackData; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d7b46aa6c91cd..b293a001029af 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1,25043 +1,25268 @@ -/* @internal */ -namespace ts { - const ambientModuleSymbolRegex = /^".+"$/; - const anon = "(anonymous)" as __String & string; - - let nextSymbolId = 1; - let nextNodeId = 1; - let nextMergeId = 1; - let nextFlowId = 1; - - const enum IterationUse { - AllowsSyncIterablesFlag = 1 << 0, - AllowsAsyncIterablesFlag = 1 << 1, - AllowsStringInputFlag = 1 << 2, - ForOfFlag = 1 << 3, - YieldStarFlag = 1 << 4, - SpreadFlag = 1 << 5, - DestructuringFlag = 1 << 6, - PossiblyOutOfBounds = 1 << 7, - - // Spread, Destructuring, Array element assignment - Element = AllowsSyncIterablesFlag, - Spread = AllowsSyncIterablesFlag | SpreadFlag, - Destructuring = AllowsSyncIterablesFlag | DestructuringFlag, - - ForOf = AllowsSyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, - ForAwaitOf = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, - - YieldStar = AllowsSyncIterablesFlag | YieldStarFlag, - AsyncYieldStar = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | YieldStarFlag, - - GeneratorReturnType = AllowsSyncIterablesFlag, - AsyncGeneratorReturnType = AllowsAsyncIterablesFlag, - - } - - const enum IterationTypeKind { - Yield, - Return, - Next, - } - - interface IterationTypesResolver { - iterableCacheKey: "iterationTypesOfAsyncIterable" | "iterationTypesOfIterable"; - iteratorCacheKey: "iterationTypesOfAsyncIterator" | "iterationTypesOfIterator"; - iteratorSymbolName: "asyncIterator" | "iterator"; - getGlobalIteratorType: (reportErrors: boolean) => GenericType; - getGlobalIterableType: (reportErrors: boolean) => GenericType; - getGlobalIterableIteratorType: (reportErrors: boolean) => GenericType; - getGlobalGeneratorType: (reportErrors: boolean) => GenericType; - resolveIterationType: (type: Type, errorNode: Node | undefined) => Type | undefined; - mustHaveANextMethodDiagnostic: DiagnosticMessage; - mustBeAMethodDiagnostic: DiagnosticMessage; - mustHaveAValueDiagnostic: DiagnosticMessage; - } - - const enum WideningKind { - Normal, - FunctionReturn, - GeneratorNext, - GeneratorYield, - } - - export const enum TypeFacts { - None = 0, - TypeofEQString = 1 << 0, // typeof x === "string" - TypeofEQNumber = 1 << 1, // typeof x === "number" - TypeofEQBigInt = 1 << 2, // typeof x === "bigint" - TypeofEQBoolean = 1 << 3, // typeof x === "boolean" - TypeofEQSymbol = 1 << 4, // typeof x === "symbol" - TypeofEQObject = 1 << 5, // typeof x === "object" - TypeofEQFunction = 1 << 6, // typeof x === "function" - TypeofEQHostObject = 1 << 7, // typeof x === "xxx" - TypeofNEString = 1 << 8, // typeof x !== "string" - TypeofNENumber = 1 << 9, // typeof x !== "number" - TypeofNEBigInt = 1 << 10, // typeof x !== "bigint" - TypeofNEBoolean = 1 << 11, // typeof x !== "boolean" - TypeofNESymbol = 1 << 12, // typeof x !== "symbol" - TypeofNEObject = 1 << 13, // typeof x !== "object" - TypeofNEFunction = 1 << 14, // typeof x !== "function" - TypeofNEHostObject = 1 << 15, // typeof x !== "xxx" - EQUndefined = 1 << 16, // x === undefined - EQNull = 1 << 17, // x === null - EQUndefinedOrNull = 1 << 18, // x === undefined / x === null - NEUndefined = 1 << 19, // x !== undefined - NENull = 1 << 20, // x !== null - NEUndefinedOrNull = 1 << 21, // x != undefined / x != null - Truthy = 1 << 22, // x - Falsy = 1 << 23, // !x - IsUndefined = 1 << 24, // Contains undefined or intersection with undefined - IsNull = 1 << 25, // Contains null or intersection with null - IsUndefinedOrNull = IsUndefined | IsNull, - All = (1 << 27) - 1, - // The following members encode facts about particular kinds of types for use in the getTypeFacts function. - // The presence of a particular fact means that the given test is true for some (and possibly all) values - // of that kind of type. - BaseStringStrictFacts = TypeofEQString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseStringFacts = BaseStringStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - StringStrictFacts = BaseStringStrictFacts | Truthy | Falsy, - StringFacts = BaseStringFacts | Truthy, - EmptyStringStrictFacts = BaseStringStrictFacts | Falsy, - EmptyStringFacts = BaseStringFacts, - NonEmptyStringStrictFacts = BaseStringStrictFacts | Truthy, - NonEmptyStringFacts = BaseStringFacts | Truthy, - BaseNumberStrictFacts = TypeofEQNumber | TypeofNEString | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseNumberFacts = BaseNumberStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - NumberStrictFacts = BaseNumberStrictFacts | Truthy | Falsy, - NumberFacts = BaseNumberFacts | Truthy, - ZeroNumberStrictFacts = BaseNumberStrictFacts | Falsy, - ZeroNumberFacts = BaseNumberFacts, - NonZeroNumberStrictFacts = BaseNumberStrictFacts | Truthy, - NonZeroNumberFacts = BaseNumberFacts | Truthy, - BaseBigIntStrictFacts = TypeofEQBigInt | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseBigIntFacts = BaseBigIntStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - BigIntStrictFacts = BaseBigIntStrictFacts | Truthy | Falsy, - BigIntFacts = BaseBigIntFacts | Truthy, - ZeroBigIntStrictFacts = BaseBigIntStrictFacts | Falsy, - ZeroBigIntFacts = BaseBigIntFacts, - NonZeroBigIntStrictFacts = BaseBigIntStrictFacts | Truthy, - NonZeroBigIntFacts = BaseBigIntFacts | Truthy, - BaseBooleanStrictFacts = TypeofEQBoolean | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, - BaseBooleanFacts = BaseBooleanStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - BooleanStrictFacts = BaseBooleanStrictFacts | Truthy | Falsy, - BooleanFacts = BaseBooleanFacts | Truthy, - FalseStrictFacts = BaseBooleanStrictFacts | Falsy, - FalseFacts = BaseBooleanFacts, - TrueStrictFacts = BaseBooleanStrictFacts | Truthy, - TrueFacts = BaseBooleanFacts | Truthy, - SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, - SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy, - ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, - FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, - VoidFacts = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, - UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy | IsUndefined, - NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy | IsNull, - EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull | IsUndefinedOrNull), - EmptyObjectFacts = All & ~IsUndefinedOrNull, - UnknownFacts = All & ~IsUndefinedOrNull, - AllTypeofNE = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | NEUndefined, - // Masks - OrFactsMask = TypeofEQFunction | TypeofNEObject, - AndFactsMask = All & ~OrFactsMask, - } - - const typeofNEFacts: ReadonlyESMap = new Map(getEntries({ - string: TypeFacts.TypeofNEString, - number: TypeFacts.TypeofNENumber, - bigint: TypeFacts.TypeofNEBigInt, - boolean: TypeFacts.TypeofNEBoolean, - symbol: TypeFacts.TypeofNESymbol, - undefined: TypeFacts.NEUndefined, - object: TypeFacts.TypeofNEObject, - function: TypeFacts.TypeofNEFunction - })); - - type TypeSystemEntity = Node | Symbol | Type | Signature; - - const enum TypeSystemPropertyName { - Type, - ResolvedBaseConstructorType, - DeclaredType, - ResolvedReturnType, - ImmediateBaseConstraint, - EnumTagType, - ResolvedTypeArguments, - ResolvedBaseTypes, - WriteType, - } - - export const enum CheckMode { - Normal = 0, // Normal type checking - Contextual = 1 << 0, // Explicitly assigned contextual type, therefore not cacheable - Inferential = 1 << 1, // Inferential typing - SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions - SkipGenericFunctions = 1 << 3, // Skip single signature generic functions - IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help - IsForStringLiteralArgumentCompletions = 1 << 5, // Do not infer from the argument currently being typed - RestBindingElement = 1 << 6, // Checking a type that is going to be used to determine the type of a rest binding element - // e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`, - // we need to preserve generic types instead of substituting them for constraints - } - - export const enum SignatureCheckMode { - BivariantCallback = 1 << 0, - StrictCallback = 1 << 1, - IgnoreReturnTypes = 1 << 2, - StrictArity = 1 << 3, - Callback = BivariantCallback | StrictCallback, - } - - const enum IntersectionState { - None = 0, - Source = 1 << 0, - Target = 1 << 1, - } - - const enum RecursionFlags { - None = 0, - Source = 1 << 0, - Target = 1 << 1, - Both = Source | Target, - } - - const enum MappedTypeModifiers { - IncludeReadonly = 1 << 0, - ExcludeReadonly = 1 << 1, - IncludeOptional = 1 << 2, - ExcludeOptional = 1 << 3, - } - - const enum ExpandingFlags { - None = 0, - Source = 1, - Target = 1 << 1, - Both = Source | Target, - } - - const enum MembersOrExportsResolutionKind { - resolvedExports = "resolvedExports", - resolvedMembers = "resolvedMembers" - } +import * as ts from "./_namespaces/ts"; +import { + __String, AccessExpression, AccessFlags, AccessorDeclaration, addRange, addRelatedInfo, addSyntheticLeadingComment, + AllAccessorDeclarations, and, AnonymousType, AnyImportOrReExport, AnyImportSyntax, append, appendIfUnique, + ArrayBindingPattern, arrayFrom, arrayIsHomogeneous, ArrayLiteralExpression, arrayOf, arraysEqual, arrayToMultiMap, + ArrayTypeNode, ArrowFunction, AssertionExpression, AssignmentDeclarationKind, AssignmentKind, AssignmentPattern, + AwaitExpression, BaseType, BigIntLiteral, BigIntLiteralType, BinaryExpression, BinaryOperatorToken, binarySearch, + BindableObjectDefinePropertyCall, BindingElement, BindingElementGrandparent, BindingName, BindingPattern, + bindSourceFile, Block, BreakOrContinueStatement, CallChain, CallExpression, CallLikeExpression, + CallSignatureDeclaration, CancellationToken, canHaveDecorators, canHaveExportModifier, canHaveIllegalDecorators, + canHaveIllegalModifiers, canHaveModifiers, cartesianProduct, CaseBlock, CaseClause, CaseOrDefaultClause, cast, + chainDiagnosticMessages, CharacterCodes, CheckFlags, ClassDeclaration, ClassElement, ClassExpression, + ClassLikeDeclaration, ClassStaticBlockDeclaration, clear, combinePaths, compareDiagnostics, comparePaths, + compareValues, Comparison, CompilerOptions, ComputedPropertyName, concatenate, concatenateDiagnosticMessageChains, + ConditionalExpression, ConditionalRoot, ConditionalType, ConditionalTypeNode, ConstructorDeclaration, + ConstructorTypeNode, ConstructSignatureDeclaration, contains, containsParseError, ContextFlags, copyEntries, + countWhere, createBinaryExpressionTrampoline, createCompilerDiagnostic, createDiagnosticCollection, + createDiagnosticForFileFromMessageChain, createDiagnosticForNode, createDiagnosticForNodeArray, + createDiagnosticForNodeFromMessageChain, createDiagnosticMessageChainFromDiagnostic, createEmptyExports, + createFileDiagnostic, createGetCanonicalFileName, createGetSymbolWalker, createPrinter, + createPropertyNameNodeForIdentifierOrLiteral, createScanner, createSymbolTable, createTextWriter, + createUnderscoreEscapedMultiMap, Debug, Declaration, DeclarationName, declarationNameToString, DeclarationStatement, + DeclarationWithTypeParameterChildren, DeclarationWithTypeParameters, Decorator, deduplicate, DefaultClause, + defaultMaximumTruncationLength, DeferredTypeReference, DeleteExpression, Diagnostic, DiagnosticCategory, + DiagnosticMessage, DiagnosticMessageChain, DiagnosticRelatedInformation, Diagnostics, DiagnosticWithLocation, + DoStatement, DynamicNamedDeclaration, ElementAccessChain, ElementAccessExpression, ElementFlags, EmitFlags, + EmitHint, EmitResolver, EmitTextWriter, emptyArray, endsWith, EntityName, EntityNameExpression, + EntityNameOrEntityNameExpression, entityNameToString, EnumDeclaration, EnumMember, equateValues, + escapeLeadingUnderscores, escapeString, ESMap, every, EvolvingArrayType, ExclamationToken, ExportAssignment, + exportAssignmentIsAlias, ExportDeclaration, ExportSpecifier, Expression, expressionResultIsUnused, + ExpressionStatement, ExpressionWithTypeArguments, Extension, ExternalEmitHelpers, externalHelpersModuleNameText, + factory, fileExtensionIs, fileExtensionIsOneOf, filter, find, findAncestor, findBestPatternMatch, findIndex, + findLast, findLastIndex, findUseStrictPrologue, first, firstDefined, firstOrUndefined, flatMap, flatten, + FlowArrayMutation, FlowAssignment, FlowCall, FlowCondition, FlowFlags, FlowLabel, FlowNode, FlowReduceLabel, + FlowStart, FlowSwitchClause, FlowType, forEach, forEachChild, forEachChildRecursively, + forEachEnclosingBlockScopeContainer, forEachEntry, forEachImportClauseDeclaration, forEachKey, + forEachReturnStatement, forEachYieldExpression, ForInOrOfStatement, ForInStatement, formatMessage, ForOfStatement, + ForStatement, FreshableIntrinsicType, FreshableType, FreshObjectLiteralType, FunctionDeclaration, + FunctionExpression, FunctionFlags, FunctionLikeDeclaration, FunctionTypeNode, GenericType, GetAccessorDeclaration, + getAliasDeclarationFromName, getAllAccessorDeclarations, getAllowSyntheticDefaultImports, getAncestor, + getAssignedExpandoInitializer, getAssignmentDeclarationKind, getAssignmentDeclarationPropertyAccessKind, + getAssignmentTargetKind, getCheckFlags, getClassExtendsHeritageElement, getClassLikeDeclarationOfSymbol, + getCombinedLocalAndExportSymbolFlags, getCombinedModifierFlags, getCombinedNodeFlags, getContainingClass, + getContainingFunction, getContainingFunctionOrClassStaticBlock, getDeclarationModifierFlagsFromSymbol, + getDeclarationOfKind, getDeclarationsOfKind, getDeclaredExpandoInitializer, getDirectoryPath, + getEffectiveBaseTypeNode, getEffectiveConstraintOfTypeParameter, getEffectiveContainerForJSDocTemplateTag, + getEffectiveImplementsTypeNodes, getEffectiveInitializer, getEffectiveJSDocHost, getEffectiveModifierFlags, + getEffectiveReturnTypeNode, getEffectiveSetAccessorTypeAnnotationNode, getEffectiveTypeAnnotationNode, + getEffectiveTypeParameterDeclarations, getElementOrPropertyAccessName, getEmitDeclarations, getEmitModuleKind, + getEmitModuleResolutionKind, getEmitScriptTarget, getEnclosingBlockScopeContainer, getEntityNameFromTypeNode, + getEntries, getErrorSpanForNode, getEscapedTextOfIdentifierOrLiteral, getESModuleInterop, getExpandoInitializer, + getExportAssignmentExpression, getExternalModuleImportEqualsDeclarationExpression, getExternalModuleName, + getExternalModuleRequireArgument, getFirstConstructorWithBody, getFirstIdentifier, getFunctionFlags, + getHostSignatureFromJSDoc, getImmediatelyInvokedFunctionExpression, getInitializerOfBinaryExpression, + getInterfaceBaseTypeNodes, getInvokedExpression, getJSDocClassTag, getJSDocDeprecatedTag, getJSDocEnumTag, + getJSDocHost, getJSDocParameterTags, getJSDocRoot, getJSDocTags, getJSDocThisTag, getJSDocType, + getJSDocTypeAssertionType, getJSDocTypeParameterDeclarations, getJSDocTypeTag, getJSXImplicitImportBase, + getJSXRuntimeImport, getJSXTransformEnabled, getLeftmostAccessExpression, getLineAndCharacterOfPosition, + getLocalSymbolForExportDefault, getMembersOfDeclaration, getModeForUsageLocation, getModifiers, + getModuleInstanceState, getNameFromIndexInfo, getNameOfDeclaration, getNameOfExpando, getNamespaceDeclarationNode, + getNewTargetContainer, getNonAugmentationDeclaration, getNormalizedAbsolutePath, getObjectFlags, getOriginalNode, + getOrUpdate, getOwnKeys, getParameterSymbolFromJSDoc, getParseTreeNode, getPropertyAssignmentAliasLikeExpression, + getPropertyNameForPropertyNameNode, getResolutionDiagnostic, getResolutionModeOverrideForClause, + getResolvedExternalModuleName, getResolvedModule, getRestParameterElementType, getRootDeclaration, + getScriptTargetFeatures, getSelectedEffectiveModifierFlags, getSemanticJsxChildren, getSetAccessorValueParameter, + getSingleVariableOfVariableStatement, getSourceFileOfModule, getSourceFileOfNode, getSpanOfTokenAtPosition, + getSpellingSuggestion, getStrictOptionValue, getSuperContainer, getSymbolNameForPrivateIdentifier, + getTextOfIdentifierOrLiteral, getTextOfJSDocComment, getTextOfNode, getTextOfPropertyName, getThisContainer, + getThisParameter, getTrailingSemicolonDeferringWriter, getTypeParameterFromJsDoc, getTypesPackageName, + getUseDefineForClassFields, group, hasAbstractModifier, hasAccessorModifier, hasAmbientModifier, + hasContextSensitiveParameters, hasDecorators, HasDecorators, hasDynamicName, hasEffectiveModifier, + hasEffectiveModifiers, hasEffectiveReadonlyModifier, HasExpressionInitializer, hasExtension, HasIllegalDecorators, + HasIllegalModifiers, hasInitializer, HasInitializer, hasJSDocNodes, hasJSDocParameterTags, hasJsonModuleEmitEnabled, + HasModifiers, hasOnlyExpressionInitializer, hasOverrideModifier, hasPossibleExternalModuleReference, + hasQuestionToken, hasRestParameter, hasScopeMarker, hasStaticModifier, hasSyntacticModifier, hasSyntacticModifiers, + HeritageClause, Identifier, IdentifierTypePredicate, idText, IfStatement, ImportCall, ImportClause, + ImportDeclaration, ImportEqualsDeclaration, ImportOrExportSpecifier, ImportsNotUsedAsValues, ImportSpecifier, + ImportTypeAssertionContainer, ImportTypeNode, IncompleteType, IndexedAccessType, IndexedAccessTypeNode, IndexInfo, + IndexKind, indexOfNode, IndexSignatureDeclaration, IndexType, indicesOf, InferenceContext, InferenceFlags, + InferenceInfo, InferencePriority, InferTypeNode, InstantiableType, InstantiationExpressionType, + InterfaceDeclaration, InterfaceType, InterfaceTypeWithDeclaredMembers, InternalSymbolName, IntersectionType, + IntersectionTypeNode, IntrinsicType, introducesArgumentsExoticObject, isAccessExpression, isAccessor, + isAliasableExpression, isAmbientModule, isArray, isArrayBindingPattern, isArrayLiteralExpression, isArrowFunction, + isAssertionExpression, isAssignmentDeclaration, isAssignmentExpression, isAssignmentOperator, isAssignmentPattern, + isAssignmentTarget, isAsyncFunction, isAutoAccessorPropertyDeclaration, isBinaryExpression, + isBindableObjectDefinePropertyCall, isBindableStaticElementAccessExpression, isBindableStaticNameExpression, + isBindingElement, isBindingPattern, isBlock, isBlockOrCatchScoped, isBlockScopedContainerTopLevel, isCallChain, + isCallExpression, isCallLikeExpression, isCallOrNewExpression, isCallSignatureDeclaration, isCatchClause, + isCatchClauseVariableDeclarationOrBindingElement, isCheckJsEnabledForFile, isChildOfNodeWithKind, + isClassDeclaration, isClassElement, isClassExpression, isClassLike, isClassStaticBlockDeclaration, isCommaSequence, + isCommonJsExportedExpression, isCommonJsExportPropertyAssignment, isComputedNonLiteralName, isComputedPropertyName, + isConstructorDeclaration, isConstructorTypeNode, isConstTypeReference, isDeclaration, isDeclarationName, + isDeclarationReadonly, isDecorator, isDefaultedExpandoInitializer, isDeleteTarget, isDottedName, isDynamicName, + isEffectiveExternalModule, isElementAccessExpression, isEntityName, isEntityNameExpression, isEnumConst, + isEnumDeclaration, isEnumMember, isExclusivelyTypeOnlyImportOrExport, isExportAssignment, isExportDeclaration, + isExportsIdentifier, isExportSpecifier, isExpression, isExpressionNode, isExpressionOfOptionalChainRoot, + isExpressionStatement, isExpressionWithTypeArguments, isExpressionWithTypeArgumentsInClassExtendsClause, + isExternalModule, isExternalModuleAugmentation, isExternalModuleImportEqualsDeclaration, isExternalModuleIndicator, + isExternalModuleNameRelative, isExternalModuleReference, isExternalOrCommonJsModule, isForInOrOfStatement, + isForInStatement, isForOfStatement, isForStatement, isFunctionDeclaration, isFunctionExpression, + isFunctionExpressionOrArrowFunction, isFunctionLike, isFunctionLikeDeclaration, + isFunctionLikeOrClassStaticBlockDeclaration, isFunctionOrModuleBlock, isFunctionTypeNode, isGeneratedIdentifier, + isGetAccessor, isGetAccessorDeclaration, isGetOrSetAccessorDeclaration, isGlobalScopeAugmentation, isHeritageClause, + isIdentifier, isIdentifierStart, isIdentifierText, isIdentifierTypePredicate, isIdentifierTypeReference, + isIfStatement, isImportCall, isImportClause, isImportDeclaration, isImportEqualsDeclaration, isImportKeyword, + isImportOrExportSpecifier, isImportSpecifier, isImportTypeNode, isIndexedAccessTypeNode, isInExpressionContext, + isInfinityOrNaNString, isInJSDoc, isInJSFile, isInJsonFile, isInterfaceDeclaration, + isInternalModuleImportEqualsDeclaration, isInTopLevelContext, isIntrinsicJsxName, isIterationStatement, + isJSDocAllType, isJSDocAugmentsTag, isJSDocCallbackTag, isJSDocConstructSignature, isJSDocFunctionType, + isJSDocIndexSignature, isJSDocLinkLike, isJSDocMemberName, isJSDocNameReference, isJSDocNode, + isJSDocNonNullableType, isJSDocNullableType, isJSDocOptionalType, isJSDocParameterTag, isJSDocPropertyLikeTag, + isJSDocReturnTag, isJSDocSignature, isJSDocTemplateTag, isJSDocTypeAlias, isJSDocTypeAssertion, isJSDocTypedefTag, + isJSDocTypeExpression, isJSDocTypeLiteral, isJSDocTypeTag, isJSDocUnknownType, isJSDocVariadicType, + isJsonSourceFile, isJsxAttribute, isJsxAttributeLike, isJsxAttributes, isJsxElement, isJsxOpeningElement, + isJsxOpeningFragment, isJsxOpeningLikeElement, isJsxSelfClosingElement, isJsxSpreadAttribute, isJSXTagName, + isKnownSymbol, isLateVisibilityPaintedStatement, isLeftHandSideExpression, isLet, isLineBreak, + isLiteralComputedPropertyDeclarationName, isLiteralExpressionOfObject, isLiteralImportTypeNode, isLiteralTypeNode, + isMetaProperty, isMethodDeclaration, isMethodSignature, isModifier, isModuleBlock, isModuleDeclaration, + isModuleExportsAccessExpression, isModuleIdentifier, isModuleOrEnumDeclaration, isModuleWithStringLiteralName, + isNamedDeclaration, isNamedExports, isNamedTupleMember, isNamespaceExport, isNamespaceExportDeclaration, + isNamespaceReexportDeclaration, isNewExpression, isNightly, isNodeDescendantOf, isNullishCoalesce, isNumericLiteral, + isNumericLiteralName, isObjectBindingPattern, isObjectLiteralElementLike, isObjectLiteralExpression, + isObjectLiteralMethod, isObjectLiteralOrClassExpressionMethodOrAccessor, isOmittedExpression, isOptionalChain, + isOptionalChainRoot, isOptionalJSDocPropertyLikeTag, isOptionalTypeNode, isOutermostOptionalChain, isParameter, + isParameterDeclaration, isParameterOrCatchClauseVariable, isParameterPropertyDeclaration, isParenthesizedExpression, + isParenthesizedTypeNode, isPartOfTypeNode, isPartOfTypeQuery, isPlainJsFile, isPrefixUnaryExpression, + isPrivateIdentifier, isPrivateIdentifierClassElementDeclaration, isPrivateIdentifierPropertyAccessExpression, + isPropertyAccessEntityNameExpression, isPropertyAccessExpression, isPropertyAccessOrQualifiedNameOrImportTypeNode, + isPropertyAssignment, isPropertyDeclaration, isPropertyName, isPropertyNameLiteral, isPropertySignature, + isPrototypeAccess, isPrototypePropertyAssignment, isPushOrUnshiftIdentifier, isQualifiedName, isRequireCall, + isRestParameter, isRestTypeNode, isRightSideOfQualifiedNameOrPropertyAccess, + isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName, isSameEntityName, isSetAccessor, + isShorthandAmbientModuleSymbol, isShorthandPropertyAssignment, isSingleOrDoubleQuote, isSourceFile, isSourceFileJS, + isSpreadAssignment, isSpreadElement, isStatement, isStatementWithLocals, isStatic, isString, + isStringANonContextualKeyword, isStringLiteral, isStringLiteralLike, isStringOrNumericLiteralLike, isSuperCall, + isSuperProperty, isTaggedTemplateExpression, isTemplateSpan, isThisContainerOrFunctionBlock, isThisIdentifier, + isThisInitializedDeclaration, isThisInitializedObjectBindingExpression, isThisInTypeQuery, isThisProperty, + isThisTypeParameter, isTransientSymbol, isTupleTypeNode, isTypeAlias, isTypeAliasDeclaration, isTypeDeclaration, + isTypeLiteralNode, isTypeNode, isTypeNodeKind, isTypeOfExpression, isTypeOnlyImportOrExportDeclaration, + isTypeOperatorNode, isTypeParameterDeclaration, isTypePredicateNode, isTypeQueryNode, isTypeReferenceNode, + isTypeReferenceType, isUMDExportSymbol, isValidESSymbolDeclaration, isValidTypeOnlyAliasUseSite, + isValueSignatureDeclaration, isVarConst, isVariableDeclaration, + isVariableDeclarationInitializedToBareOrAccessedRequire, isVariableDeclarationInVariableStatement, + isVariableDeclarationList, isVariableLike, isVariableLikeOrAccessor, isVariableStatement, isWriteAccess, + isWriteOnlyAccess, IterableOrIteratorType, IterationTypes, JSDoc, JSDocAugmentsTag, JSDocCallbackTag, JSDocComment, + JSDocContainer, JSDocEnumTag, JSDocFunctionType, JSDocImplementsTag, JSDocLink, JSDocLinkCode, JSDocLinkPlain, + JSDocMemberName, JSDocNullableType, JSDocOptionalType, JSDocParameterTag, JSDocPrivateTag, JSDocPropertyLikeTag, + JSDocPropertyTag, JSDocProtectedTag, JSDocPublicTag, JSDocSignature, JSDocTemplateTag, JSDocTypedefTag, + JSDocTypeExpression, JSDocTypeReferencingNode, JSDocTypeTag, JSDocVariadicType, JsxAttribute, JsxAttributeLike, + JsxAttributes, JsxChild, JsxClosingElement, JsxElement, JsxEmit, JsxExpression, JsxFlags, JsxFragment, + JsxOpeningElement, JsxOpeningFragment, JsxOpeningLikeElement, JsxReferenceKind, JsxSelfClosingElement, + JsxSpreadAttribute, JsxTagNameExpression, KeywordTypeNode, LabeledStatement, last, lastOrUndefined, + LateBoundBinaryExpressionDeclaration, LateBoundDeclaration, LateBoundName, LateVisibilityPaintedStatement, + LeftHandSideExpression, length, LiteralExpression, LiteralType, LiteralTypeNode, mangleScopedPackageName, map, Map, + mapDefined, MappedSymbol, MappedType, MappedTypeNode, MatchingKeys, maybeBind, MemberName, MemberOverrideStatus, + memoize, MetaProperty, MethodDeclaration, MethodSignature, minAndMax, MinusToken, Modifier, ModifierFlags, + modifiersToFlags, modifierToFlag, ModuleBlock, ModuleDeclaration, ModuleInstanceState, ModuleKind, + ModuleResolutionKind, NamedDeclaration, NamedExports, NamedImportsOrExports, NamedTupleMember, + NamespaceDeclaration, NamespaceExport, NamespaceExportDeclaration, NamespaceImport, needsScopeMarker, NewExpression, + Node, NodeArray, NodeBuilderFlags, nodeCanBeDecorated, NodeCheckFlags, NodeFlags, nodeHasName, nodeIsDecorated, + nodeIsMissing, nodeIsPresent, nodeIsSynthesized, NodeLinks, nodeStartsNewLexicalEnvironment, NodeWithTypeArguments, + NonNullChain, NonNullExpression, not, noTruncationMaximumTruncationLength, nullTransformationContext, + NumberLiteralType, NumericLiteral, objectAllocator, ObjectBindingPattern, ObjectFlags, ObjectFlagsType, + ObjectLiteralElementLike, ObjectLiteralExpression, ObjectType, OptionalChain, OptionalTypeNode, or, + orderedRemoveItemAt, OuterExpressionKinds, outFile, ParameterDeclaration, parameterIsThisKeyword, + ParameterPropertyDeclaration, ParenthesizedExpression, ParenthesizedTypeNode, parseIsolatedEntityName, + parseNodeFactory, parsePseudoBigInt, Path, pathIsRelative, PatternAmbientModule, PlusToken, PostfixUnaryExpression, + PrefixUnaryExpression, PrivateIdentifier, Program, PromiseOrAwaitableType, PropertyAccessChain, + PropertyAccessEntityNameExpression, PropertyAccessExpression, PropertyAssignment, PropertyDeclaration, PropertyName, + PropertySignature, PseudoBigInt, pseudoBigIntToString, pushIfUnique, QualifiedName, QuestionToken, rangeEquals, + rangeOfNode, rangeOfTypeParameters, ReadonlyESMap, ReadonlyKeyword, reduceLeft, RelationComparisonResult, + relativeComplement, removeExtension, removePrefix, replaceElement, resolutionExtensionIsTSOrJson, + ResolvedModuleFull, ResolvedType, resolveTripleslashReference, resolvingEmptyArray, RestTypeNode, ReturnStatement, + ReverseMappedSymbol, ReverseMappedType, sameMap, SatisfiesExpression, ScriptKind, ScriptTarget, Set, + SetAccessorDeclaration, setCommentRange, setEmitFlags, setNodeFlags, setOriginalNode, setParent, + setSyntheticLeadingComments, setTextRange, setTextRangePosEnd, setValueDeclaration, ShorthandPropertyAssignment, + shouldPreserveConstEnums, Signature, SignatureDeclaration, SignatureFlags, SignatureKind, singleElementArray, + skipOuterExpressions, skipParentheses, skipTrivia, skipTypeChecking, some, SourceFile, SpreadAssignment, + SpreadElement, startsWith, Statement, stringContains, StringLiteral, StringLiteralLike, StringLiteralType, + StringMappingType, stripQuotes, StructuredType, SubstitutionType, sum, SuperCall, SwitchStatement, Symbol, + SymbolAccessibility, SymbolAccessibilityResult, SymbolFlags, SymbolFormatFlags, SymbolId, SymbolLinks, symbolName, + SymbolTable, SymbolTracker, SymbolVisibilityResult, SyntaxKind, SyntheticDefaultModuleType, SyntheticExpression, + TaggedTemplateExpression, TemplateExpression, TemplateLiteralType, TemplateLiteralTypeNode, Ternary, + textRangeContainsPositionInclusive, TextSpan, textSpanContainsPosition, textSpanEnd, ThisExpression, ThisTypeNode, + ThrowStatement, TokenFlags, tokenToString, tracing, TracingNode, TransientSymbol, tryAddToSet, tryCast, + tryExtractTSExtension, tryGetClassImplementingOrExtendingExpressionWithTypeArguments, tryGetExtensionFromPath, + tryGetModuleSpecifierFromDeclaration, tryGetPropertyAccessOrIdentifierToString, TryStatement, TupleType, + TupleTypeNode, TupleTypeReference, Type, TypeAliasDeclaration, TypeAssertion, TypeChecker, TypeCheckerHost, + TypeComparer, TypeElement, TypeFlags, TypeFormatFlags, TypeId, TypeLiteralNode, TypeMapKind, TypeMapper, TypeNode, + TypeNodeSyntaxKind, TypeOfExpression, TypeOnlyAliasDeclaration, TypeOnlyCompatibleAliasDeclaration, + TypeOperatorNode, TypeParameter, TypeParameterDeclaration, TypePredicate, TypePredicateKind, TypePredicateNode, + TypeQueryNode, TypeReference, TypeReferenceNode, TypeReferenceSerializationKind, TypeReferenceType, TypeVariable, + UnaryExpression, UnderscoreEscapedMap, unescapeLeadingUnderscores, UnionOrIntersectionType, + UnionOrIntersectionTypeNode, UnionReduction, UnionType, UnionTypeNode, UniqueESSymbolType, + usingSingleLineStringWriter, VariableDeclaration, VariableDeclarationList, VariableLikeDeclaration, + VariableStatement, VarianceFlags, visitEachChild, visitNode, visitNodes, Visitor, VisitResult, VoidExpression, + walkUpBindingElementsAndPatterns, walkUpParenthesizedExpressions, walkUpParenthesizedTypes, + walkUpParenthesizedTypesAndGetParentAndChild, WhileStatement, WideningContext, WithStatement, YieldExpression, +} from "./_namespaces/ts"; +import * as performance from "./_namespaces/ts.performance"; +import * as moduleSpecifiers from "./_namespaces/ts.moduleSpecifiers"; + +const ambientModuleSymbolRegex = /^".+"$/; +const anon = "(anonymous)" as __String & string; + +let nextSymbolId = 1; +let nextNodeId = 1; +let nextMergeId = 1; +let nextFlowId = 1; + +const enum IterationUse { + AllowsSyncIterablesFlag = 1 << 0, + AllowsAsyncIterablesFlag = 1 << 1, + AllowsStringInputFlag = 1 << 2, + ForOfFlag = 1 << 3, + YieldStarFlag = 1 << 4, + SpreadFlag = 1 << 5, + DestructuringFlag = 1 << 6, + PossiblyOutOfBounds = 1 << 7, + + // Spread, Destructuring, Array element assignment + Element = AllowsSyncIterablesFlag, + Spread = AllowsSyncIterablesFlag | SpreadFlag, + Destructuring = AllowsSyncIterablesFlag | DestructuringFlag, + + ForOf = AllowsSyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, + ForAwaitOf = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, + + YieldStar = AllowsSyncIterablesFlag | YieldStarFlag, + AsyncYieldStar = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | YieldStarFlag, + + GeneratorReturnType = AllowsSyncIterablesFlag, + AsyncGeneratorReturnType = AllowsAsyncIterablesFlag, - const enum UnusedKind { - Local, - Parameter, - } +} - /** @param containingNode Node to check for parse error */ - type AddUnusedDiagnostic = (containingNode: Node, type: UnusedKind, diagnostic: DiagnosticWithLocation) => void; +const enum IterationTypeKind { + Yield, + Return, + Next, +} - const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); +interface IterationTypesResolver { + iterableCacheKey: "iterationTypesOfAsyncIterable" | "iterationTypesOfIterable"; + iteratorCacheKey: "iterationTypesOfAsyncIterator" | "iterationTypesOfIterator"; + iteratorSymbolName: "asyncIterator" | "iterator"; + getGlobalIteratorType: (reportErrors: boolean) => GenericType; + getGlobalIterableType: (reportErrors: boolean) => GenericType; + getGlobalIterableIteratorType: (reportErrors: boolean) => GenericType; + getGlobalGeneratorType: (reportErrors: boolean) => GenericType; + resolveIterationType: (type: Type, errorNode: Node | undefined) => Type | undefined; + mustHaveANextMethodDiagnostic: DiagnosticMessage; + mustBeAMethodDiagnostic: DiagnosticMessage; + mustHaveAValueDiagnostic: DiagnosticMessage; +} - const enum DeclarationMeaning { - GetAccessor = 1, - SetAccessor = 2, - PropertyAssignment = 4, - Method = 8, - PrivateStatic = 16, - GetOrSetAccessor = GetAccessor | SetAccessor, - PropertyAssignmentOrMethod = PropertyAssignment | Method, - } +const enum WideningKind { + Normal, + FunctionReturn, + GeneratorNext, + GeneratorYield, +} - const enum DeclarationSpaces { - None = 0, - ExportValue = 1 << 0, - ExportType = 1 << 1, - ExportNamespace = 1 << 2, - } +/** @internal */ +export const enum TypeFacts { + None = 0, + TypeofEQString = 1 << 0, // typeof x === "string" + TypeofEQNumber = 1 << 1, // typeof x === "number" + TypeofEQBigInt = 1 << 2, // typeof x === "bigint" + TypeofEQBoolean = 1 << 3, // typeof x === "boolean" + TypeofEQSymbol = 1 << 4, // typeof x === "symbol" + TypeofEQObject = 1 << 5, // typeof x === "object" + TypeofEQFunction = 1 << 6, // typeof x === "function" + TypeofEQHostObject = 1 << 7, // typeof x === "xxx" + TypeofNEString = 1 << 8, // typeof x !== "string" + TypeofNENumber = 1 << 9, // typeof x !== "number" + TypeofNEBigInt = 1 << 10, // typeof x !== "bigint" + TypeofNEBoolean = 1 << 11, // typeof x !== "boolean" + TypeofNESymbol = 1 << 12, // typeof x !== "symbol" + TypeofNEObject = 1 << 13, // typeof x !== "object" + TypeofNEFunction = 1 << 14, // typeof x !== "function" + TypeofNEHostObject = 1 << 15, // typeof x !== "xxx" + EQUndefined = 1 << 16, // x === undefined + EQNull = 1 << 17, // x === null + EQUndefinedOrNull = 1 << 18, // x === undefined / x === null + NEUndefined = 1 << 19, // x !== undefined + NENull = 1 << 20, // x !== null + NEUndefinedOrNull = 1 << 21, // x != undefined / x != null + Truthy = 1 << 22, // x + Falsy = 1 << 23, // !x + IsUndefined = 1 << 24, // Contains undefined or intersection with undefined + IsNull = 1 << 25, // Contains null or intersection with null + IsUndefinedOrNull = IsUndefined | IsNull, + All = (1 << 27) - 1, + // The following members encode facts about particular kinds of types for use in the getTypeFacts function. + // The presence of a particular fact means that the given test is true for some (and possibly all) values + // of that kind of type. + BaseStringStrictFacts = TypeofEQString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseStringFacts = BaseStringStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + StringStrictFacts = BaseStringStrictFacts | Truthy | Falsy, + StringFacts = BaseStringFacts | Truthy, + EmptyStringStrictFacts = BaseStringStrictFacts | Falsy, + EmptyStringFacts = BaseStringFacts, + NonEmptyStringStrictFacts = BaseStringStrictFacts | Truthy, + NonEmptyStringFacts = BaseStringFacts | Truthy, + BaseNumberStrictFacts = TypeofEQNumber | TypeofNEString | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseNumberFacts = BaseNumberStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + NumberStrictFacts = BaseNumberStrictFacts | Truthy | Falsy, + NumberFacts = BaseNumberFacts | Truthy, + ZeroNumberStrictFacts = BaseNumberStrictFacts | Falsy, + ZeroNumberFacts = BaseNumberFacts, + NonZeroNumberStrictFacts = BaseNumberStrictFacts | Truthy, + NonZeroNumberFacts = BaseNumberFacts | Truthy, + BaseBigIntStrictFacts = TypeofEQBigInt | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseBigIntFacts = BaseBigIntStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + BigIntStrictFacts = BaseBigIntStrictFacts | Truthy | Falsy, + BigIntFacts = BaseBigIntFacts | Truthy, + ZeroBigIntStrictFacts = BaseBigIntStrictFacts | Falsy, + ZeroBigIntFacts = BaseBigIntFacts, + NonZeroBigIntStrictFacts = BaseBigIntStrictFacts | Truthy, + NonZeroBigIntFacts = BaseBigIntFacts | Truthy, + BaseBooleanStrictFacts = TypeofEQBoolean | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, + BaseBooleanFacts = BaseBooleanStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + BooleanStrictFacts = BaseBooleanStrictFacts | Truthy | Falsy, + BooleanFacts = BaseBooleanFacts | Truthy, + FalseStrictFacts = BaseBooleanStrictFacts | Falsy, + FalseFacts = BaseBooleanFacts, + TrueStrictFacts = BaseBooleanStrictFacts | Truthy, + TrueFacts = BaseBooleanFacts | Truthy, + SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + VoidFacts = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, + UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy | IsUndefined, + NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy | IsNull, + EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull | IsUndefinedOrNull), + EmptyObjectFacts = All & ~IsUndefinedOrNull, + UnknownFacts = All & ~IsUndefinedOrNull, + AllTypeofNE = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | NEUndefined, + // Masks + OrFactsMask = TypeofEQFunction | TypeofNEObject, + AndFactsMask = All & ~OrFactsMask, +} - const enum MinArgumentCountFlags { - None = 0, - StrongArityForUntypedJS = 1 << 0, - VoidIsNonOptional = 1 << 1, - } +const typeofNEFacts: ReadonlyESMap = new Map(getEntries({ + string: TypeFacts.TypeofNEString, + number: TypeFacts.TypeofNENumber, + bigint: TypeFacts.TypeofNEBigInt, + boolean: TypeFacts.TypeofNEBoolean, + symbol: TypeFacts.TypeofNESymbol, + undefined: TypeFacts.NEUndefined, + object: TypeFacts.TypeofNEObject, + function: TypeFacts.TypeofNEFunction +})); + +type TypeSystemEntity = Node | Symbol | Type | Signature; + +const enum TypeSystemPropertyName { + Type, + ResolvedBaseConstructorType, + DeclaredType, + ResolvedReturnType, + ImmediateBaseConstraint, + EnumTagType, + ResolvedTypeArguments, + ResolvedBaseTypes, + WriteType, +} - const enum IntrinsicTypeKind { - Uppercase, - Lowercase, - Capitalize, - Uncapitalize - } +/** @internal */ +export const enum CheckMode { + Normal = 0, // Normal type checking + Contextual = 1 << 0, // Explicitly assigned contextual type, therefore not cacheable + Inferential = 1 << 1, // Inferential typing + SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions + SkipGenericFunctions = 1 << 3, // Skip single signature generic functions + IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help + IsForStringLiteralArgumentCompletions = 1 << 5, // Do not infer from the argument currently being typed + RestBindingElement = 1 << 6, // Checking a type that is going to be used to determine the type of a rest binding element + // e.g. in `const { a, ...rest } = foo`, when checking the type of `foo` to determine the type of `rest`, + // we need to preserve generic types instead of substituting them for constraints +} - const intrinsicTypeKinds: ReadonlyESMap = new Map(getEntries({ - Uppercase: IntrinsicTypeKind.Uppercase, - Lowercase: IntrinsicTypeKind.Lowercase, - Capitalize: IntrinsicTypeKind.Capitalize, - Uncapitalize: IntrinsicTypeKind.Uncapitalize - })); +/** @internal */ +export const enum SignatureCheckMode { + BivariantCallback = 1 << 0, + StrictCallback = 1 << 1, + IgnoreReturnTypes = 1 << 2, + StrictArity = 1 << 3, + Callback = BivariantCallback | StrictCallback, +} - function SymbolLinks(this: SymbolLinks) { - } +const enum IntersectionState { + None = 0, + Source = 1 << 0, + Target = 1 << 1, +} - function NodeLinks(this: NodeLinks) { - this.flags = 0; - } +const enum RecursionFlags { + None = 0, + Source = 1 << 0, + Target = 1 << 1, + Both = Source | Target, +} - export function getNodeId(node: Node): number { - if (!node.id) { - node.id = nextNodeId; - nextNodeId++; - } - return node.id; - } +const enum MappedTypeModifiers { + IncludeReadonly = 1 << 0, + ExcludeReadonly = 1 << 1, + IncludeOptional = 1 << 2, + ExcludeOptional = 1 << 3, +} - export function getSymbolId(symbol: Symbol): SymbolId { - if (!symbol.id) { - symbol.id = nextSymbolId; - nextSymbolId++; - } +const enum ExpandingFlags { + None = 0, + Source = 1, + Target = 1 << 1, + Both = Source | Target, +} - return symbol.id; - } +const enum MembersOrExportsResolutionKind { + resolvedExports = "resolvedExports", + resolvedMembers = "resolvedMembers" +} - export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { - const moduleState = getModuleInstanceState(node); - return moduleState === ModuleInstanceState.Instantiated || - (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); - } +const enum UnusedKind { + Local, + Parameter, +} - export function createTypeChecker(host: TypeCheckerHost): TypeChecker { - const getPackagesMap = memoize(() => { - // A package name maps to true when we detect it has .d.ts files. - // This is useful as an approximation of whether a package bundles its own types. - // Note: we only look at files already found by module resolution, - // so there may be files we did not consider. - const map = new Map(); - host.getSourceFiles().forEach(sf => { - if (!sf.resolvedModules) return; +/** @param containingNode Node to check for parse error */ +type AddUnusedDiagnostic = (containingNode: Node, type: UnusedKind, diagnostic: DiagnosticWithLocation) => void; - sf.resolvedModules.forEach(r => { - if (r && r.packageId) map.set(r.packageId.name, r.extension === Extension.Dts || !!map.get(r.packageId.name)); - }); - }); - return map; - }); +const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); - let deferredDiagnosticsCallbacks: (() => void)[] = []; +const enum DeclarationMeaning { + GetAccessor = 1, + SetAccessor = 2, + PropertyAssignment = 4, + Method = 8, + PrivateStatic = 16, + GetOrSetAccessor = GetAccessor | SetAccessor, + PropertyAssignmentOrMethod = PropertyAssignment | Method, +} - let addLazyDiagnostic = (arg: () => void) => { - deferredDiagnosticsCallbacks.push(arg); - }; +const enum DeclarationSpaces { + None = 0, + ExportValue = 1 << 0, + ExportType = 1 << 1, + ExportNamespace = 1 << 2, +} - // Cancellation that controls whether or not we can cancel in the middle of type checking. - // In general cancelling is *not* safe for the type checker. We might be in the middle of - // computing something, and we will leave our internals in an inconsistent state. Callers - // who set the cancellation token should catch if a cancellation exception occurs, and - // should throw away and create a new TypeChecker. - // - // Currently we only support setting the cancellation token when getting diagnostics. This - // is because diagnostics can be quite expensive, and we want to allow hosts to bail out if - // they no longer need the information (for example, if the user started editing again). - let cancellationToken: CancellationToken | undefined; - let requestedExternalEmitHelpers: ExternalEmitHelpers; - let externalHelpersModule: Symbol; - - const Symbol = objectAllocator.getSymbolConstructor(); - const Type = objectAllocator.getTypeConstructor(); - const Signature = objectAllocator.getSignatureConstructor(); - - let typeCount = 0; - let symbolCount = 0; - let totalInstantiationCount = 0; - let instantiationCount = 0; - let instantiationDepth = 0; - let inlineLevel = 0; - let currentNode: Node | undefined; - let varianceTypeParameter: TypeParameter | undefined; - - const emptySymbols = createSymbolTable(); - const arrayVariances = [VarianceFlags.Covariant]; - - const compilerOptions = host.getCompilerOptions(); - const languageVersion = getEmitScriptTarget(compilerOptions); - const moduleKind = getEmitModuleKind(compilerOptions); - const useDefineForClassFields = getUseDefineForClassFields(compilerOptions); - const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); - const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); - const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); - const strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply"); - const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); - const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); - const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); - const useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables"); - const keyofStringsOnly = !!compilerOptions.keyofStringsOnly; - const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral; - const exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes; - - const checkBinaryExpression = createCheckBinaryExpression(); - const emitResolver = createResolver(); - const nodeBuilder = createNodeBuilder(); - - const globals = createSymbolTable(); - const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); - undefinedSymbol.declarations = []; - - const globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); - globalThisSymbol.exports = globals; - globalThisSymbol.declarations = []; - globals.set(globalThisSymbol.escapedName, globalThisSymbol); - - const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); - const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); - - /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ - let apparentArgumentCount: number | undefined; - - // for public members that accept a Node or one of its subtypes, we must guard against - // synthetic nodes created during transformations by calling `getParseTreeNode`. - // for most of these, we perform the guard only on `checker` to avoid any possible - // extra cost of calling `getParseTreeNode` when calling these functions from inside the - // checker. - const checker: TypeChecker = { - getNodeCount: () => sum(host.getSourceFiles(), "nodeCount"), - getIdentifierCount: () => sum(host.getSourceFiles(), "identifierCount"), - getSymbolCount: () => sum(host.getSourceFiles(), "symbolCount") + symbolCount, - getTypeCount: () => typeCount, - getInstantiationCount: () => totalInstantiationCount, - getRelationCacheSizes: () => ({ - assignable: assignableRelation.size, - identity: identityRelation.size, - subtype: subtypeRelation.size, - strictSubtype: strictSubtypeRelation.size, - }), - isUndefinedSymbol: symbol => symbol === undefinedSymbol, - isArgumentsSymbol: symbol => symbol === argumentsSymbol, - isUnknownSymbol: symbol => symbol === unknownSymbol, - getMergedSymbol, - getDiagnostics, - getGlobalDiagnostics, - getRecursionIdentity, - getUnmatchedProperties, - getTypeOfSymbolAtLocation: (symbol, locationIn) => { - const location = getParseTreeNode(locationIn); - return location ? getTypeOfSymbolAtLocation(symbol, location) : errorType; - }, - getTypeOfSymbol, - getSymbolsOfParameterPropertyDeclaration: (parameterIn, parameterName) => { - const parameter = getParseTreeNode(parameterIn, isParameter); - if (parameter === undefined) return Debug.fail("Cannot get symbols of a synthetic parameter that cannot be resolved to a parse-tree node."); - return getSymbolsOfParameterPropertyDeclaration(parameter, escapeLeadingUnderscores(parameterName)); - }, - getDeclaredTypeOfSymbol, - getPropertiesOfType, - getPropertyOfType: (type, name) => getPropertyOfType(type, escapeLeadingUnderscores(name)), - getPrivateIdentifierPropertyOfType: (leftType: Type, name: string, location: Node) => { - const node = getParseTreeNode(location); - if (!node) { - return undefined; - } - const propName = escapeLeadingUnderscores(name); - const lexicallyScopedIdentifier = lookupSymbolForPrivateIdentifierDeclaration(propName, node); - return lexicallyScopedIdentifier ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedIdentifier) : undefined; - }, - getTypeOfPropertyOfType: (type, name) => getTypeOfPropertyOfType(type, escapeLeadingUnderscores(name)), - getIndexInfoOfType: (type, kind) => getIndexInfoOfType(type, kind === IndexKind.String ? stringType : numberType), - getIndexInfosOfType, - getIndexInfosOfIndexSymbol, - getSignaturesOfType, - getIndexTypeOfType: (type, kind) => getIndexTypeOfType(type, kind === IndexKind.String ? stringType : numberType), - getIndexType: type => getIndexType(type), - getBaseTypes, - getBaseTypeOfLiteralType, - getWidenedType, - getTypeFromTypeNode: nodeIn => { - const node = getParseTreeNode(nodeIn, isTypeNode); - return node ? getTypeFromTypeNode(node) : errorType; - }, - getParameterType: getTypeAtPosition, - getParameterIdentifierNameAtPosition, - getPromisedTypeOfPromise, - getAwaitedType: type => getAwaitedType(type), - getReturnTypeOfSignature, - isNullableType, - getNullableType, - getNonNullableType, - getNonOptionalType: removeOptionalTypeMarker, - getTypeArguments, - typeToTypeNode: nodeBuilder.typeToTypeNode, - indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, - signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, - symbolToEntityName: nodeBuilder.symbolToEntityName, - symbolToExpression: nodeBuilder.symbolToExpression, - symbolToNode: nodeBuilder.symbolToNode, - symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations, - symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration, - typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration, - getSymbolsInScope: (locationIn, meaning) => { - const location = getParseTreeNode(locationIn); - return location ? getSymbolsInScope(location, meaning) : []; - }, - getSymbolAtLocation: nodeIn => { - const node = getParseTreeNode(nodeIn); - // set ignoreErrors: true because any lookups invoked by the API shouldn't cause any new errors - return node ? getSymbolAtLocation(node, /*ignoreErrors*/ true) : undefined; - }, - getIndexInfosAtLocation: nodeIn => { - const node = getParseTreeNode(nodeIn); - return node ? getIndexInfosAtLocation(node) : undefined; - }, - getShorthandAssignmentValueSymbol: nodeIn => { - const node = getParseTreeNode(nodeIn); - return node ? getShorthandAssignmentValueSymbol(node) : undefined; - }, - getExportSpecifierLocalTargetSymbol: nodeIn => { - const node = getParseTreeNode(nodeIn, isExportSpecifier); - return node ? getExportSpecifierLocalTargetSymbol(node) : undefined; - }, - getExportSymbolOfSymbol(symbol) { - return getMergedSymbol(symbol.exportSymbol || symbol); - }, - getTypeAtLocation: nodeIn => { - const node = getParseTreeNode(nodeIn); - 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; - }, - signatureToString: (signature, enclosingDeclaration, flags, kind) => { - return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind); - }, - typeToString: (type, enclosingDeclaration, flags) => { - return typeToString(type, getParseTreeNode(enclosingDeclaration), flags); - }, - symbolToString: (symbol, enclosingDeclaration, meaning, flags) => { - return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags); - }, - typePredicateToString: (predicate, enclosingDeclaration, flags) => { - return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags); - }, - writeSignature: (signature, enclosingDeclaration, flags, kind, writer) => { - return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind, writer); - }, - writeType: (type, enclosingDeclaration, flags, writer) => { - return typeToString(type, getParseTreeNode(enclosingDeclaration), flags, writer); - }, - writeSymbol: (symbol, enclosingDeclaration, meaning, flags, writer) => { - return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags, writer); - }, - writeTypePredicate: (predicate, enclosingDeclaration, flags, writer) => { - return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags, writer); - }, - getAugmentedPropertiesOfType, - getRootSymbols, - getSymbolOfExpando, - getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => { - const node = getParseTreeNode(nodeIn, isExpression); - if (!node) { - return undefined; - } - if (contextFlags! & ContextFlags.Completions) { - return runWithInferenceBlockedFromSourceNode(node, () => getContextualType(node, contextFlags)); - } - return getContextualType(node, contextFlags); - }, - getContextualTypeForObjectLiteralElement: nodeIn => { - const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike); - return node ? getContextualTypeForObjectLiteralElement(node, /*contextFlags*/ undefined) : undefined; - }, - getContextualTypeForArgumentAtIndex: (nodeIn, argIndex) => { - const node = getParseTreeNode(nodeIn, isCallLikeExpression); - return node && getContextualTypeForArgumentAtIndex(node, argIndex); - }, - getContextualTypeForJsxAttribute: (nodeIn) => { - const node = getParseTreeNode(nodeIn, isJsxAttributeLike); - return node && getContextualTypeForJsxAttribute(node, /*contextFlags*/ undefined); - }, - isContextSensitive, - getTypeOfPropertyOfContextualType, - getFullyQualifiedName, - getResolvedSignature: (node, candidatesOutArray, argumentCount) => - getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), - getResolvedSignatureForStringLiteralCompletions: (call, editingArgument, candidatesOutArray) => - getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions, editingArgument), - getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => - getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp), - getExpandedParameters, - hasEffectiveRestParameter, - containsArgumentsReference, - getConstantValue: nodeIn => { - const node = getParseTreeNode(nodeIn, canHaveConstantValue); - return node ? getConstantValue(node) : undefined; - }, - isValidPropertyAccess: (nodeIn, propertyName) => { - const node = getParseTreeNode(nodeIn, isPropertyAccessOrQualifiedNameOrImportTypeNode); - return !!node && isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName)); - }, - isValidPropertyAccessForCompletions: (nodeIn, type, property) => { - const node = getParseTreeNode(nodeIn, isPropertyAccessExpression); - return !!node && isValidPropertyAccessForCompletions(node, type, property); - }, - getSignatureFromDeclaration: declarationIn => { - const declaration = getParseTreeNode(declarationIn, isFunctionLike); - return declaration ? getSignatureFromDeclaration(declaration) : undefined; - }, - isImplementationOfOverload: nodeIn => { - const node = getParseTreeNode(nodeIn, isFunctionLike); - return node ? isImplementationOfOverload(node) : undefined; - }, - getImmediateAliasedSymbol, - getAliasedSymbol: resolveAlias, - getEmitResolver, - getExportsOfModule: getExportsOfModuleAsArray, - getExportsAndPropertiesOfModule, - forEachExportAndPropertyOfModule, - getSymbolWalker: createGetSymbolWalker( - getRestTypeOfSignature, - getTypePredicateOfSignature, - getReturnTypeOfSignature, - getBaseTypes, - resolveStructuredTypeMembers, - getTypeOfSymbol, - getResolvedSymbol, - getConstraintOfTypeParameter, - getFirstIdentifier, - getTypeArguments, - ), - getAmbientModules, - getJsxIntrinsicTagNamesAt, - isOptionalParameter: nodeIn => { - const node = getParseTreeNode(nodeIn, isParameter); - return node ? isOptionalParameter(node) : false; - }, - tryGetMemberInModuleExports: (name, symbol) => tryGetMemberInModuleExports(escapeLeadingUnderscores(name), symbol), - tryGetMemberInModuleExportsAndProperties: (name, symbol) => tryGetMemberInModuleExportsAndProperties(escapeLeadingUnderscores(name), symbol), - tryFindAmbientModule: moduleName => tryFindAmbientModule(moduleName, /*withAugmentations*/ true), - tryFindAmbientModuleWithoutAugmentations: moduleName => { - // we deliberately exclude augmentations - // since we are only interested in declarations of the module itself - return tryFindAmbientModule(moduleName, /*withAugmentations*/ false); - }, - getApparentType, - getUnionType, - isTypeAssignableTo, - createAnonymousType, - createSignature, - createSymbol, - createIndexInfo, - getAnyType: () => anyType, - getStringType: () => stringType, - getNumberType: () => numberType, - createPromiseType, - createArrayType, - getElementTypeOfArrayType, - getBooleanType: () => booleanType, - getFalseType: (fresh?) => fresh ? falseType : regularFalseType, - getTrueType: (fresh?) => fresh ? trueType : regularTrueType, - getVoidType: () => voidType, - getUndefinedType: () => undefinedType, - getNullType: () => nullType, - getESSymbolType: () => esSymbolType, - getNeverType: () => neverType, - getOptionalType: () => optionalType, - getPromiseType: () => getGlobalPromiseType(/*reportErrors*/ false), - getPromiseLikeType: () => getGlobalPromiseLikeType(/*reportErrors*/ false), - getAsyncIterableType: () => { - const type = getGlobalAsyncIterableType(/*reportErrors*/ false); - if (type === emptyGenericType) return undefined; - return type; - }, - isSymbolAccessible, - isArrayType, - isTupleType, - isArrayLikeType, - isTypeInvalidDueToUnionDiscriminant, - getExactOptionalProperties, - getAllPossiblePropertiesOfTypes, - getSuggestedSymbolForNonexistentProperty, - getSuggestionForNonexistentProperty, - getSuggestedSymbolForNonexistentJSXAttribute, - getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), - getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), - getSuggestedSymbolForNonexistentModule, - getSuggestionForNonexistentExport, - getSuggestedSymbolForNonexistentClassMember, - getBaseConstraintOfType, - getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter(type as TypeParameter) : undefined, - resolveName(name, location, meaning, excludeGlobals) { - return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals); - }, - getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)), - getJsxFragmentFactory: n => { - const jsxFragmentFactory = getJsxFragmentFactoryEntity(n); - return jsxFragmentFactory && unescapeLeadingUnderscores(getFirstIdentifier(jsxFragmentFactory).escapedText); - }, - getAccessibleSymbolChain, - getTypePredicateOfSignature, - resolveExternalModuleName: moduleSpecifierIn => { - const moduleSpecifier = getParseTreeNode(moduleSpecifierIn, isExpression); - return moduleSpecifier && resolveExternalModuleName(moduleSpecifier, moduleSpecifier, /*ignoreErrors*/ true); - }, - resolveExternalModuleSymbol, - tryGetThisTypeAt: (nodeIn, includeGlobalThis, container) => { - const node = getParseTreeNode(nodeIn); - return node && tryGetThisTypeAt(node, includeGlobalThis, container); - }, - getTypeArgumentConstraint: nodeIn => { - const node = getParseTreeNode(nodeIn, isTypeNode); - return node && getTypeArgumentConstraint(node); - }, - getSuggestionDiagnostics: (fileIn, ct) => { - const file = getParseTreeNode(fileIn, isSourceFile) || Debug.fail("Could not determine parsed source file."); - if (skipTypeChecking(file, compilerOptions, host)) { - return emptyArray; - } +const enum MinArgumentCountFlags { + None = 0, + StrongArityForUntypedJS = 1 << 0, + VoidIsNonOptional = 1 << 1, +} - let diagnostics: DiagnosticWithLocation[] | undefined; - try { - // Record the cancellation token so it can be checked later on during checkSourceElement. - // Do this in a finally block so we can ensure that it gets reset back to nothing after - // this call is done. - cancellationToken = ct; +const enum IntrinsicTypeKind { + Uppercase, + Lowercase, + Capitalize, + Uncapitalize +} - // Ensure file is type checked, with _eager_ diagnostic production, so identifiers are registered as potentially unused - checkSourceFileWithEagerDiagnostics(file); - Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked)); +const intrinsicTypeKinds: ReadonlyESMap = new Map(getEntries({ + Uppercase: IntrinsicTypeKind.Uppercase, + Lowercase: IntrinsicTypeKind.Lowercase, + Capitalize: IntrinsicTypeKind.Capitalize, + Uncapitalize: IntrinsicTypeKind.Uncapitalize +})); - diagnostics = addRange(diagnostics, suggestionDiagnostics.getDiagnostics(file.fileName)); - checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(file), (containingNode, kind, diag) => { - if (!containsParseError(containingNode) && !unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { - (diagnostics || (diagnostics = [])).push({ ...diag, category: DiagnosticCategory.Suggestion }); - } - }); +function SymbolLinks(this: SymbolLinks) { +} - return diagnostics || emptyArray; - } - finally { - cancellationToken = undefined; - } - }, +function NodeLinks(this: NodeLinks) { + this.flags = 0; +} - runWithCancellationToken: (token, callback) => { - try { - cancellationToken = token; - return callback(checker); - } - finally { - cancellationToken = undefined; - } - }, +/** @internal */ +export function getNodeId(node: Node): number { + if (!node.id) { + node.id = nextNodeId; + nextNodeId++; + } + return node.id; +} - getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, - isDeclarationVisible, - isPropertyAccessible, - getTypeOnlyAliasDeclaration, - getMemberOverrideModifierStatus, - isTypeParameterPossiblyReferenced, - }; +/** @internal */ +export function getSymbolId(symbol: Symbol): SymbolId { + if (!symbol.id) { + symbol.id = nextSymbolId; + nextSymbolId++; + } - function runWithInferenceBlockedFromSourceNode(node: Node | undefined, fn: () => T): T { - const containingCall = findAncestor(node, isCallLikeExpression); - const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature; - if (containingCall) { - let toMarkSkip = node!; - do { - getNodeLinks(toMarkSkip).skipDirectInference = true; - toMarkSkip = toMarkSkip.parent; - } while (toMarkSkip && toMarkSkip !== containingCall); - getNodeLinks(containingCall).resolvedSignature = undefined; - } - const result = fn(); - if (containingCall) { - let toMarkSkip = node!; - do { - getNodeLinks(toMarkSkip).skipDirectInference = undefined; - toMarkSkip = toMarkSkip.parent; - } while (toMarkSkip && toMarkSkip !== containingCall); - getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature; - } - return result; - } + return symbol.id; +} - function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode, editingArgument?: Node): Signature | undefined { - const node = getParseTreeNode(nodeIn, isCallLikeExpression); - apparentArgumentCount = argumentCount; - const res = - !node ? undefined : - editingArgument ? runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignature(node, candidatesOutArray, checkMode)) : - getResolvedSignature(node, candidatesOutArray, checkMode); - apparentArgumentCount = undefined; - return res; - } +/** @internal */ +export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { + const moduleState = getModuleInstanceState(node); + return moduleState === ModuleInstanceState.Instantiated || + (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); +} - const tupleTypes = new Map(); - const unionTypes = new Map(); - const intersectionTypes = new Map(); - const stringLiteralTypes = new Map(); - const numberLiteralTypes = new Map(); - const bigIntLiteralTypes = new Map(); - const enumLiteralTypes = new Map(); - const indexedAccessTypes = new Map(); - const templateLiteralTypes = new Map(); - const stringMappingTypes = new Map(); - const substitutionTypes = new Map(); - const subtypeReductionCache = new Map(); - const cachedTypes = new Map(); - const evolvingArrayTypes: EvolvingArrayType[] = []; - const undefinedProperties: SymbolTable = new Map(); - const markerTypes = new Set(); - - const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); - const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); - const unresolvedSymbols = new Map(); - const errorTypes = new Map(); - - const anyType = createIntrinsicType(TypeFlags.Any, "any"); - const autoType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.NonInferrableType); - const wildcardType = createIntrinsicType(TypeFlags.Any, "any"); - const errorType = createIntrinsicType(TypeFlags.Any, "error"); - const unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved"); - const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType); - const intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); - const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); - const nonNullUnknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); - const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); - const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); - const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); - const missingType = exactOptionalPropertyTypes ? createIntrinsicType(TypeFlags.Undefined, "undefined") : undefinedType; - const nullType = createIntrinsicType(TypeFlags.Null, "null"); - const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); - const stringType = createIntrinsicType(TypeFlags.String, "string"); - const numberType = createIntrinsicType(TypeFlags.Number, "number"); - const bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); - const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; - const regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; - const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; - const regularTrueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; - trueType.regularType = regularTrueType; - trueType.freshType = trueType; - regularTrueType.regularType = regularTrueType; - regularTrueType.freshType = trueType; - falseType.regularType = regularFalseType; - falseType.freshType = falseType; - regularFalseType.regularType = regularFalseType; - regularFalseType.freshType = falseType; - const booleanType = getUnionType([regularFalseType, regularTrueType]); - const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); - const voidType = createIntrinsicType(TypeFlags.Void, "void"); - const neverType = createIntrinsicType(TypeFlags.Never, "never"); - const silentNeverType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType); - const implicitNeverType = createIntrinsicType(TypeFlags.Never, "never"); - const unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never"); - const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); - const stringOrNumberType = getUnionType([stringType, numberType]); - const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); - const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType; - const numberOrBigIntType = getUnionType([numberType, bigintType]); - const templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; - const numericStringType = getTemplateLiteralType(["", ""], [numberType]); // The `${number}` type - - const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t, () => "(restrictive mapper)"); - const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t, () => "(permissive mapper)"); - const uniqueLiteralType = createIntrinsicType(TypeFlags.Never, "never"); // `uniqueLiteralType` is a special `never` flagged by union reduction to behave as a literal - const uniqueLiteralMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? uniqueLiteralType : t, () => "(unique literal mapper)"); // replace all type parameters with the unique literal type (disregarding constraints) - let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; - const reportUnreliableMapper = makeFunctionTypeMapper(t => { - if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { - outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); +/** @internal */ +export function createTypeChecker(host: TypeCheckerHost): TypeChecker { + const getPackagesMap = memoize(() => { + // A package name maps to true when we detect it has .d.ts files. + // This is useful as an approximation of whether a package bundles its own types. + // Note: we only look at files already found by module resolution, + // so there may be files we did not consider. + const map = new Map(); + host.getSourceFiles().forEach(sf => { + if (!sf.resolvedModules) return; + + sf.resolvedModules.forEach(r => { + if (r && r.packageId) map.set(r.packageId.name, r.extension === Extension.Dts || !!map.get(r.packageId.name)); + }); + }); + return map; + }); + + let deferredDiagnosticsCallbacks: (() => void)[] = []; + + let addLazyDiagnostic = (arg: () => void) => { + deferredDiagnosticsCallbacks.push(arg); + }; + + // Cancellation that controls whether or not we can cancel in the middle of type checking. + // In general cancelling is *not* safe for the type checker. We might be in the middle of + // computing something, and we will leave our internals in an inconsistent state. Callers + // who set the cancellation token should catch if a cancellation exception occurs, and + // should throw away and create a new TypeChecker. + // + // Currently we only support setting the cancellation token when getting diagnostics. This + // is because diagnostics can be quite expensive, and we want to allow hosts to bail out if + // they no longer need the information (for example, if the user started editing again). + let cancellationToken: CancellationToken | undefined; + let requestedExternalEmitHelpers: ExternalEmitHelpers; + let externalHelpersModule: Symbol; + + const Symbol = objectAllocator.getSymbolConstructor(); + const Type = objectAllocator.getTypeConstructor(); + const Signature = objectAllocator.getSignatureConstructor(); + + let typeCount = 0; + let symbolCount = 0; + let totalInstantiationCount = 0; + let instantiationCount = 0; + let instantiationDepth = 0; + let inlineLevel = 0; + let currentNode: Node | undefined; + let varianceTypeParameter: TypeParameter | undefined; + + const emptySymbols = createSymbolTable(); + const arrayVariances = [VarianceFlags.Covariant]; + + const compilerOptions = host.getCompilerOptions(); + const languageVersion = getEmitScriptTarget(compilerOptions); + const moduleKind = getEmitModuleKind(compilerOptions); + const useDefineForClassFields = getUseDefineForClassFields(compilerOptions); + const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); + const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); + const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); + const strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply"); + const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); + const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); + const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); + const useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables"); + const keyofStringsOnly = !!compilerOptions.keyofStringsOnly; + const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral; + const exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes; + + const checkBinaryExpression = createCheckBinaryExpression(); + const emitResolver = createResolver(); + const nodeBuilder = createNodeBuilder(); + + const globals = createSymbolTable(); + const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); + undefinedSymbol.declarations = []; + + const globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); + globalThisSymbol.exports = globals; + globalThisSymbol.declarations = []; + globals.set(globalThisSymbol.escapedName, globalThisSymbol); + + const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); + const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); + + /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ + let apparentArgumentCount: number | undefined; + + // for public members that accept a Node or one of its subtypes, we must guard against + // synthetic nodes created during transformations by calling `getParseTreeNode`. + // for most of these, we perform the guard only on `checker` to avoid any possible + // extra cost of calling `getParseTreeNode` when calling these functions from inside the + // checker. + const checker: TypeChecker = { + getNodeCount: () => sum(host.getSourceFiles(), "nodeCount"), + getIdentifierCount: () => sum(host.getSourceFiles(), "identifierCount"), + getSymbolCount: () => sum(host.getSourceFiles(), "symbolCount") + symbolCount, + getTypeCount: () => typeCount, + getInstantiationCount: () => totalInstantiationCount, + getRelationCacheSizes: () => ({ + assignable: assignableRelation.size, + identity: identityRelation.size, + subtype: subtypeRelation.size, + strictSubtype: strictSubtypeRelation.size, + }), + isUndefinedSymbol: symbol => symbol === undefinedSymbol, + isArgumentsSymbol: symbol => symbol === argumentsSymbol, + isUnknownSymbol: symbol => symbol === unknownSymbol, + getMergedSymbol, + getDiagnostics, + getGlobalDiagnostics, + getRecursionIdentity, + getUnmatchedProperties, + getTypeOfSymbolAtLocation: (symbol, locationIn) => { + const location = getParseTreeNode(locationIn); + return location ? getTypeOfSymbolAtLocation(symbol, location) : errorType; + }, + getTypeOfSymbol, + getSymbolsOfParameterPropertyDeclaration: (parameterIn, parameterName) => { + const parameter = getParseTreeNode(parameterIn, isParameter); + if (parameter === undefined) return Debug.fail("Cannot get symbols of a synthetic parameter that cannot be resolved to a parse-tree node."); + return getSymbolsOfParameterPropertyDeclaration(parameter, escapeLeadingUnderscores(parameterName)); + }, + getDeclaredTypeOfSymbol, + getPropertiesOfType, + getPropertyOfType: (type, name) => getPropertyOfType(type, escapeLeadingUnderscores(name)), + getPrivateIdentifierPropertyOfType: (leftType: Type, name: string, location: Node) => { + const node = getParseTreeNode(location); + if (!node) { + return undefined; } - return t; - }, () => "(unmeasurable reporter)"); - const reportUnmeasurableMapper = makeFunctionTypeMapper(t => { - if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { - outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); + const propName = escapeLeadingUnderscores(name); + const lexicallyScopedIdentifier = lookupSymbolForPrivateIdentifierDeclaration(propName, node); + return lexicallyScopedIdentifier ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedIdentifier) : undefined; + }, + getTypeOfPropertyOfType: (type, name) => getTypeOfPropertyOfType(type, escapeLeadingUnderscores(name)), + getIndexInfoOfType: (type, kind) => getIndexInfoOfType(type, kind === IndexKind.String ? stringType : numberType), + getIndexInfosOfType, + getIndexInfosOfIndexSymbol, + getSignaturesOfType, + getIndexTypeOfType: (type, kind) => getIndexTypeOfType(type, kind === IndexKind.String ? stringType : numberType), + getIndexType: type => getIndexType(type), + getBaseTypes, + getBaseTypeOfLiteralType, + getWidenedType, + getTypeFromTypeNode: nodeIn => { + const node = getParseTreeNode(nodeIn, isTypeNode); + return node ? getTypeFromTypeNode(node) : errorType; + }, + getParameterType: getTypeAtPosition, + getParameterIdentifierNameAtPosition, + getPromisedTypeOfPromise, + getAwaitedType: type => getAwaitedType(type), + getReturnTypeOfSignature, + isNullableType, + getNullableType, + getNonNullableType, + getNonOptionalType: removeOptionalTypeMarker, + getTypeArguments, + typeToTypeNode: nodeBuilder.typeToTypeNode, + indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, + signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, + symbolToEntityName: nodeBuilder.symbolToEntityName, + symbolToExpression: nodeBuilder.symbolToExpression, + symbolToNode: nodeBuilder.symbolToNode, + symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations, + symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration, + typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration, + getSymbolsInScope: (locationIn, meaning) => { + const location = getParseTreeNode(locationIn); + return location ? getSymbolsInScope(location, meaning) : []; + }, + getSymbolAtLocation: nodeIn => { + const node = getParseTreeNode(nodeIn); + // set ignoreErrors: true because any lookups invoked by the API shouldn't cause any new errors + return node ? getSymbolAtLocation(node, /*ignoreErrors*/ true) : undefined; + }, + getIndexInfosAtLocation: nodeIn => { + const node = getParseTreeNode(nodeIn); + return node ? getIndexInfosAtLocation(node) : undefined; + }, + getShorthandAssignmentValueSymbol: nodeIn => { + const node = getParseTreeNode(nodeIn); + return node ? getShorthandAssignmentValueSymbol(node) : undefined; + }, + getExportSpecifierLocalTargetSymbol: nodeIn => { + const node = getParseTreeNode(nodeIn, isExportSpecifier); + return node ? getExportSpecifierLocalTargetSymbol(node) : undefined; + }, + getExportSymbolOfSymbol(symbol) { + return getMergedSymbol(symbol.exportSymbol || symbol); + }, + getTypeAtLocation: nodeIn => { + const node = getParseTreeNode(nodeIn); + 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; + }, + signatureToString: (signature, enclosingDeclaration, flags, kind) => { + return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind); + }, + typeToString: (type, enclosingDeclaration, flags) => { + return typeToString(type, getParseTreeNode(enclosingDeclaration), flags); + }, + symbolToString: (symbol, enclosingDeclaration, meaning, flags) => { + return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags); + }, + typePredicateToString: (predicate, enclosingDeclaration, flags) => { + return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags); + }, + writeSignature: (signature, enclosingDeclaration, flags, kind, writer) => { + return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind, writer); + }, + writeType: (type, enclosingDeclaration, flags, writer) => { + return typeToString(type, getParseTreeNode(enclosingDeclaration), flags, writer); + }, + writeSymbol: (symbol, enclosingDeclaration, meaning, flags, writer) => { + return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags, writer); + }, + writeTypePredicate: (predicate, enclosingDeclaration, flags, writer) => { + return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags, writer); + }, + getAugmentedPropertiesOfType, + getRootSymbols, + getSymbolOfExpando, + getContextualType: (nodeIn: Expression, contextFlags?: ContextFlags) => { + const node = getParseTreeNode(nodeIn, isExpression); + if (!node) { + return undefined; + } + if (contextFlags! & ContextFlags.Completions) { + return runWithInferenceBlockedFromSourceNode(node, () => getContextualType(node, contextFlags)); + } + return getContextualType(node, contextFlags); + }, + getContextualTypeForObjectLiteralElement: nodeIn => { + const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike); + return node ? getContextualTypeForObjectLiteralElement(node, /*contextFlags*/ undefined) : undefined; + }, + getContextualTypeForArgumentAtIndex: (nodeIn, argIndex) => { + const node = getParseTreeNode(nodeIn, isCallLikeExpression); + return node && getContextualTypeForArgumentAtIndex(node, argIndex); + }, + getContextualTypeForJsxAttribute: (nodeIn) => { + const node = getParseTreeNode(nodeIn, isJsxAttributeLike); + return node && getContextualTypeForJsxAttribute(node, /*contextFlags*/ undefined); + }, + isContextSensitive, + getTypeOfPropertyOfContextualType, + getFullyQualifiedName, + getResolvedSignature: (node, candidatesOutArray, argumentCount) => + getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), + getResolvedSignatureForStringLiteralCompletions: (call, editingArgument, candidatesOutArray) => + getResolvedSignatureWorker(call, candidatesOutArray, /*argumentCount*/ undefined, CheckMode.IsForStringLiteralArgumentCompletions, editingArgument), + getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => + getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp), + getExpandedParameters, + hasEffectiveRestParameter, + containsArgumentsReference, + getConstantValue: nodeIn => { + const node = getParseTreeNode(nodeIn, canHaveConstantValue); + return node ? getConstantValue(node) : undefined; + }, + isValidPropertyAccess: (nodeIn, propertyName) => { + const node = getParseTreeNode(nodeIn, isPropertyAccessOrQualifiedNameOrImportTypeNode); + return !!node && isValidPropertyAccess(node, escapeLeadingUnderscores(propertyName)); + }, + isValidPropertyAccessForCompletions: (nodeIn, type, property) => { + const node = getParseTreeNode(nodeIn, isPropertyAccessExpression); + return !!node && isValidPropertyAccessForCompletions(node, type, property); + }, + getSignatureFromDeclaration: declarationIn => { + const declaration = getParseTreeNode(declarationIn, isFunctionLike); + return declaration ? getSignatureFromDeclaration(declaration) : undefined; + }, + isImplementationOfOverload: nodeIn => { + const node = getParseTreeNode(nodeIn, isFunctionLike); + return node ? isImplementationOfOverload(node) : undefined; + }, + getImmediateAliasedSymbol, + getAliasedSymbol: resolveAlias, + getEmitResolver, + getExportsOfModule: getExportsOfModuleAsArray, + getExportsAndPropertiesOfModule, + forEachExportAndPropertyOfModule, + getSymbolWalker: createGetSymbolWalker( + getRestTypeOfSignature, + getTypePredicateOfSignature, + getReturnTypeOfSignature, + getBaseTypes, + resolveStructuredTypeMembers, + getTypeOfSymbol, + getResolvedSymbol, + getConstraintOfTypeParameter, + getFirstIdentifier, + getTypeArguments, + ), + getAmbientModules, + getJsxIntrinsicTagNamesAt, + isOptionalParameter: nodeIn => { + const node = getParseTreeNode(nodeIn, isParameter); + return node ? isOptionalParameter(node) : false; + }, + tryGetMemberInModuleExports: (name, symbol) => tryGetMemberInModuleExports(escapeLeadingUnderscores(name), symbol), + tryGetMemberInModuleExportsAndProperties: (name, symbol) => tryGetMemberInModuleExportsAndProperties(escapeLeadingUnderscores(name), symbol), + tryFindAmbientModule: moduleName => tryFindAmbientModule(moduleName, /*withAugmentations*/ true), + tryFindAmbientModuleWithoutAugmentations: moduleName => { + // we deliberately exclude augmentations + // since we are only interested in declarations of the module itself + return tryFindAmbientModule(moduleName, /*withAugmentations*/ false); + }, + getApparentType, + getUnionType, + isTypeAssignableTo, + createAnonymousType, + createSignature, + createSymbol, + createIndexInfo, + getAnyType: () => anyType, + getStringType: () => stringType, + getNumberType: () => numberType, + createPromiseType, + createArrayType, + getElementTypeOfArrayType, + getBooleanType: () => booleanType, + getFalseType: (fresh?) => fresh ? falseType : regularFalseType, + getTrueType: (fresh?) => fresh ? trueType : regularTrueType, + getVoidType: () => voidType, + getUndefinedType: () => undefinedType, + getNullType: () => nullType, + getESSymbolType: () => esSymbolType, + getNeverType: () => neverType, + getOptionalType: () => optionalType, + getPromiseType: () => getGlobalPromiseType(/*reportErrors*/ false), + getPromiseLikeType: () => getGlobalPromiseLikeType(/*reportErrors*/ false), + getAsyncIterableType: () => { + const type = getGlobalAsyncIterableType(/*reportErrors*/ false); + if (type === emptyGenericType) return undefined; + return type; + }, + isSymbolAccessible, + isArrayType, + isTupleType, + isArrayLikeType, + isTypeInvalidDueToUnionDiscriminant, + getExactOptionalProperties, + getAllPossiblePropertiesOfTypes, + getSuggestedSymbolForNonexistentProperty, + getSuggestionForNonexistentProperty, + getSuggestedSymbolForNonexistentJSXAttribute, + getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), + getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), + getSuggestedSymbolForNonexistentModule, + getSuggestionForNonexistentExport, + getSuggestedSymbolForNonexistentClassMember, + getBaseConstraintOfType, + getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter(type as TypeParameter) : undefined, + resolveName(name, location, meaning, excludeGlobals) { + return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals); + }, + getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)), + getJsxFragmentFactory: n => { + const jsxFragmentFactory = getJsxFragmentFactoryEntity(n); + return jsxFragmentFactory && unescapeLeadingUnderscores(getFirstIdentifier(jsxFragmentFactory).escapedText); + }, + getAccessibleSymbolChain, + getTypePredicateOfSignature, + resolveExternalModuleName: moduleSpecifierIn => { + const moduleSpecifier = getParseTreeNode(moduleSpecifierIn, isExpression); + return moduleSpecifier && resolveExternalModuleName(moduleSpecifier, moduleSpecifier, /*ignoreErrors*/ true); + }, + resolveExternalModuleSymbol, + tryGetThisTypeAt: (nodeIn, includeGlobalThis, container) => { + const node = getParseTreeNode(nodeIn); + return node && tryGetThisTypeAt(node, includeGlobalThis, container); + }, + getTypeArgumentConstraint: nodeIn => { + const node = getParseTreeNode(nodeIn, isTypeNode); + return node && getTypeArgumentConstraint(node); + }, + getSuggestionDiagnostics: (fileIn, ct) => { + const file = getParseTreeNode(fileIn, isSourceFile) || Debug.fail("Could not determine parsed source file."); + if (skipTypeChecking(file, compilerOptions, host)) { + return emptyArray; } - return t; - }, () => "(unreliable reporter)"); - - const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - const emptyJsxObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; - - const emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - emptyTypeLiteralSymbol.members = createSymbolTable(); - const emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, emptyArray); - - const unknownEmptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - const unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, unknownEmptyObjectType]) : unknownType; - - const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType; - emptyGenericType.instantiations = new Map(); - - const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated - // in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes. - anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType; - - const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - const resolvingDefaultType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - - const markerSuperType = createTypeParameter(); - const markerSubType = createTypeParameter(); - markerSubType.constraint = markerSuperType; - const markerOtherType = createTypeParameter(); - const markerSuperTypeForCheck = createTypeParameter(); - const markerSubTypeForCheck = createTypeParameter(); - markerSubTypeForCheck.constraint = markerSuperTypeForCheck; + let diagnostics: DiagnosticWithLocation[] | undefined; + try { + // Record the cancellation token so it can be checked later on during checkSourceElement. + // Do this in a finally block so we can ensure that it gets reset back to nothing after + // this call is done. + cancellationToken = ct; - const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); + // Ensure file is type checked, with _eager_ diagnostic production, so identifiers are registered as potentially unused + checkSourceFileWithEagerDiagnostics(file); + Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked)); - const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + diagnostics = addRange(diagnostics, suggestionDiagnostics.getDiagnostics(file.fileName)); + checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(file), (containingNode, kind, diag) => { + if (!containsParseError(containingNode) && !unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { + (diagnostics || (diagnostics = [])).push({ ...diag, category: DiagnosticCategory.Suggestion }); + } + }); - const enumNumberIndexInfo = createIndexInfo(numberType, stringType, /*isReadonly*/ true); + return diagnostics || emptyArray; + } + finally { + cancellationToken = undefined; + } + }, - const iterationTypesCache = new Map(); // cache for common IterationTypes instances - const noIterationTypes: IterationTypes = { - get yieldType(): Type { return Debug.fail("Not supported"); }, - get returnType(): Type { return Debug.fail("Not supported"); }, - get nextType(): Type { return Debug.fail("Not supported"); }, - }; + runWithCancellationToken: (token, callback) => { + try { + cancellationToken = token; + return callback(checker); + } + finally { + cancellationToken = undefined; + } + }, + + getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, + isDeclarationVisible, + isPropertyAccessible, + getTypeOnlyAliasDeclaration, + getMemberOverrideModifierStatus, + isTypeParameterPossiblyReferenced, + }; + + function runWithInferenceBlockedFromSourceNode(node: Node | undefined, fn: () => T): T { + const containingCall = findAncestor(node, isCallLikeExpression); + const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature; + if (containingCall) { + let toMarkSkip = node!; + do { + getNodeLinks(toMarkSkip).skipDirectInference = true; + toMarkSkip = toMarkSkip.parent; + } while (toMarkSkip && toMarkSkip !== containingCall); + getNodeLinks(containingCall).resolvedSignature = undefined; + } + const result = fn(); + if (containingCall) { + let toMarkSkip = node!; + do { + getNodeLinks(toMarkSkip).skipDirectInference = undefined; + toMarkSkip = toMarkSkip.parent; + } while (toMarkSkip && toMarkSkip !== containingCall); + getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature; + } + return result; + } - const anyIterationTypes = createIterationTypes(anyType, anyType, anyType); - const anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); - const defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. - - const asyncIterationTypesResolver: IterationTypesResolver = { - iterableCacheKey: "iterationTypesOfAsyncIterable", - iteratorCacheKey: "iterationTypesOfAsyncIterator", - iteratorSymbolName: "asyncIterator", - getGlobalIteratorType: getGlobalAsyncIteratorType, - getGlobalIterableType: getGlobalAsyncIterableType, - getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType, - getGlobalGeneratorType: getGlobalAsyncGeneratorType, - resolveIterationType: getAwaitedType, - mustHaveANextMethodDiagnostic: Diagnostics.An_async_iterator_must_have_a_next_method, - mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_async_iterator_must_be_a_method, - mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_async_iterator_must_be_a_promise_for_a_type_with_a_value_property, - }; + function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode, editingArgument?: Node): Signature | undefined { + const node = getParseTreeNode(nodeIn, isCallLikeExpression); + apparentArgumentCount = argumentCount; + const res = + !node ? undefined : + editingArgument ? runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignature(node, candidatesOutArray, checkMode)) : + getResolvedSignature(node, candidatesOutArray, checkMode); + apparentArgumentCount = undefined; + return res; + } - const syncIterationTypesResolver: IterationTypesResolver = { - iterableCacheKey: "iterationTypesOfIterable", - iteratorCacheKey: "iterationTypesOfIterator", - iteratorSymbolName: "iterator", - getGlobalIteratorType, - getGlobalIterableType, - getGlobalIterableIteratorType, - getGlobalGeneratorType, - resolveIterationType: (type, _errorNode) => type, - mustHaveANextMethodDiagnostic: Diagnostics.An_iterator_must_have_a_next_method, - mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_iterator_must_be_a_method, - mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_iterator_must_have_a_value_property, - }; + const tupleTypes = new Map(); + const unionTypes = new Map(); + const intersectionTypes = new Map(); + const stringLiteralTypes = new Map(); + const numberLiteralTypes = new Map(); + const bigIntLiteralTypes = new Map(); + const enumLiteralTypes = new Map(); + const indexedAccessTypes = new Map(); + const templateLiteralTypes = new Map(); + const stringMappingTypes = new Map(); + const substitutionTypes = new Map(); + const subtypeReductionCache = new Map(); + const cachedTypes = new Map(); + const evolvingArrayTypes: EvolvingArrayType[] = []; + const undefinedProperties: SymbolTable = new Map(); + const markerTypes = new Set(); + + const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); + const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); + const unresolvedSymbols = new Map(); + const errorTypes = new Map(); + + const anyType = createIntrinsicType(TypeFlags.Any, "any"); + const autoType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.NonInferrableType); + const wildcardType = createIntrinsicType(TypeFlags.Any, "any"); + const errorType = createIntrinsicType(TypeFlags.Any, "error"); + const unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved"); + const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType); + const intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); + const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); + const nonNullUnknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); + const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); + const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + const missingType = exactOptionalPropertyTypes ? createIntrinsicType(TypeFlags.Undefined, "undefined") : undefinedType; + const nullType = createIntrinsicType(TypeFlags.Null, "null"); + const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); + const stringType = createIntrinsicType(TypeFlags.String, "string"); + const numberType = createIntrinsicType(TypeFlags.Number, "number"); + const bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); + const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; + const regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; + const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; + const regularTrueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; + trueType.regularType = regularTrueType; + trueType.freshType = trueType; + regularTrueType.regularType = regularTrueType; + regularTrueType.freshType = trueType; + falseType.regularType = regularFalseType; + falseType.freshType = falseType; + regularFalseType.regularType = regularFalseType; + regularFalseType.freshType = falseType; + const booleanType = getUnionType([regularFalseType, regularTrueType]); + const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); + const voidType = createIntrinsicType(TypeFlags.Void, "void"); + const neverType = createIntrinsicType(TypeFlags.Never, "never"); + const silentNeverType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType); + const implicitNeverType = createIntrinsicType(TypeFlags.Never, "never"); + const unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never"); + const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); + const stringOrNumberType = getUnionType([stringType, numberType]); + const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); + const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType; + const numberOrBigIntType = getUnionType([numberType, bigintType]); + const templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; + const numericStringType = getTemplateLiteralType(["", ""], [numberType]); // The `${number}` type + + const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t, () => "(restrictive mapper)"); + const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t, () => "(permissive mapper)"); + const uniqueLiteralType = createIntrinsicType(TypeFlags.Never, "never"); // `uniqueLiteralType` is a special `never` flagged by union reduction to behave as a literal + const uniqueLiteralMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? uniqueLiteralType : t, () => "(unique literal mapper)"); // replace all type parameters with the unique literal type (disregarding constraints) + let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; + const reportUnreliableMapper = makeFunctionTypeMapper(t => { + if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); + } + return t; + }, () => "(unmeasurable reporter)"); + const reportUnmeasurableMapper = makeFunctionTypeMapper(t => { + if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); + } + return t; + }, () => "(unreliable reporter)"); + + const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + const emptyJsxObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; + + const emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + emptyTypeLiteralSymbol.members = createSymbolTable(); + const emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, emptyArray); + + const unknownEmptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + const unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, unknownEmptyObjectType]) : unknownType; + + const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType; + emptyGenericType.instantiations = new Map(); + + const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated + // in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes. + anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType; + + const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + const resolvingDefaultType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + + const markerSuperType = createTypeParameter(); + const markerSubType = createTypeParameter(); + markerSubType.constraint = markerSuperType; + const markerOtherType = createTypeParameter(); + + const markerSuperTypeForCheck = createTypeParameter(); + const markerSubTypeForCheck = createTypeParameter(); + markerSubTypeForCheck.constraint = markerSuperTypeForCheck; + + const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); + + const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + + const enumNumberIndexInfo = createIndexInfo(numberType, stringType, /*isReadonly*/ true); + + const iterationTypesCache = new Map(); // cache for common IterationTypes instances + const noIterationTypes: IterationTypes = { + get yieldType(): Type { return Debug.fail("Not supported"); }, + get returnType(): Type { return Debug.fail("Not supported"); }, + get nextType(): Type { return Debug.fail("Not supported"); }, + }; + + const anyIterationTypes = createIterationTypes(anyType, anyType, anyType); + const anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); + const defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. + + const asyncIterationTypesResolver: IterationTypesResolver = { + iterableCacheKey: "iterationTypesOfAsyncIterable", + iteratorCacheKey: "iterationTypesOfAsyncIterator", + iteratorSymbolName: "asyncIterator", + getGlobalIteratorType: getGlobalAsyncIteratorType, + getGlobalIterableType: getGlobalAsyncIterableType, + getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType, + getGlobalGeneratorType: getGlobalAsyncGeneratorType, + resolveIterationType: getAwaitedType, + mustHaveANextMethodDiagnostic: Diagnostics.An_async_iterator_must_have_a_next_method, + mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_async_iterator_must_be_a_method, + mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_async_iterator_must_be_a_promise_for_a_type_with_a_value_property, + }; + + const syncIterationTypesResolver: IterationTypesResolver = { + iterableCacheKey: "iterationTypesOfIterable", + iteratorCacheKey: "iterationTypesOfIterator", + iteratorSymbolName: "iterator", + getGlobalIteratorType, + getGlobalIterableType, + getGlobalIterableIteratorType, + getGlobalGeneratorType, + resolveIterationType: (type, _errorNode) => type, + mustHaveANextMethodDiagnostic: Diagnostics.An_iterator_must_have_a_next_method, + mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_iterator_must_be_a_method, + mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_iterator_must_have_a_value_property, + }; + + interface DuplicateInfoForSymbol { + readonly firstFileLocations: Declaration[]; + readonly secondFileLocations: Declaration[]; + readonly isBlockScoped: boolean; + } + interface DuplicateInfoForFiles { + readonly firstFile: SourceFile; + readonly secondFile: SourceFile; + /** Key is symbol name. */ + readonly conflictingSymbols: ESMap; + } + /** Key is "/path/to/a.ts|/path/to/b.ts". */ + let amalgamatedDuplicates: ESMap | undefined; + const reverseMappedCache = new Map(); + let inInferTypeForHomomorphicMappedType = false; + let ambientModulesCache: Symbol[] | undefined; + /** + * List of every ambient module with a "*" wildcard. + * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. + * This is only used if there is no exact match. + */ + let patternAmbientModules: PatternAmbientModule[]; + let patternAmbientModuleAugmentations: ESMap | undefined; + + let globalObjectType: ObjectType; + let globalFunctionType: ObjectType; + let globalCallableFunctionType: ObjectType; + let globalNewableFunctionType: ObjectType; + let globalArrayType: GenericType; + let globalReadonlyArrayType: GenericType; + let globalStringType: ObjectType; + let globalNumberType: ObjectType; + let globalBooleanType: ObjectType; + let globalRegExpType: ObjectType; + let globalThisType: GenericType; + let anyArrayType: Type; + let autoArrayType: Type; + let anyReadonlyArrayType: Type; + let deferredGlobalNonNullableTypeAlias: Symbol; + + // The library files are only loaded when the feature is used. + // This allows users to just specify library files they want to used through --lib + // and they will not get an error from not having unrelated library files + let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; + let deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined; + let deferredGlobalESSymbolType: ObjectType | undefined; + let deferredGlobalTypedPropertyDescriptorType: GenericType; + let deferredGlobalPromiseType: GenericType | undefined; + let deferredGlobalPromiseLikeType: GenericType | undefined; + let deferredGlobalPromiseConstructorSymbol: Symbol | undefined; + let deferredGlobalPromiseConstructorLikeType: ObjectType | undefined; + let deferredGlobalIterableType: GenericType | undefined; + let deferredGlobalIteratorType: GenericType | undefined; + let deferredGlobalIterableIteratorType: GenericType | undefined; + let deferredGlobalGeneratorType: GenericType | undefined; + let deferredGlobalIteratorYieldResultType: GenericType | undefined; + let deferredGlobalIteratorReturnResultType: GenericType | undefined; + let deferredGlobalAsyncIterableType: GenericType | undefined; + let deferredGlobalAsyncIteratorType: GenericType | undefined; + let deferredGlobalAsyncIterableIteratorType: GenericType | undefined; + let deferredGlobalAsyncGeneratorType: GenericType | undefined; + let deferredGlobalTemplateStringsArrayType: ObjectType | undefined; + let deferredGlobalImportMetaType: ObjectType; + let deferredGlobalImportMetaExpressionType: ObjectType; + let deferredGlobalImportCallOptionsType: ObjectType | undefined; + let deferredGlobalExtractSymbol: Symbol | undefined; + let deferredGlobalOmitSymbol: Symbol | undefined; + let deferredGlobalAwaitedSymbol: Symbol | undefined; + let deferredGlobalBigIntType: ObjectType | undefined; + let deferredGlobalNaNSymbol: Symbol | undefined; + let deferredGlobalRecordSymbol: Symbol | undefined; + + const allPotentiallyUnusedIdentifiers = new Map(); // key is file name + + let flowLoopStart = 0; + let flowLoopCount = 0; + let sharedFlowCount = 0; + let flowAnalysisDisabled = false; + let flowInvocationCount = 0; + let lastFlowNode: FlowNode | undefined; + let lastFlowNodeReachable: boolean; + let flowTypeCache: Type[] | undefined; + + const emptyStringType = getStringLiteralType(""); + const zeroType = getNumberLiteralType(0); + const zeroBigIntType = getBigIntLiteralType({ negative: false, base10Value: "0" }); + + const resolutionTargets: TypeSystemEntity[] = []; + const resolutionResults: boolean[] = []; + const resolutionPropertyNames: TypeSystemPropertyName[] = []; + + let suggestionCount = 0; + const maximumSuggestionCount = 10; + const mergedSymbols: Symbol[] = []; + const symbolLinks: SymbolLinks[] = []; + const nodeLinks: NodeLinks[] = []; + const flowLoopCaches: ESMap[] = []; + const flowLoopNodes: FlowNode[] = []; + const flowLoopKeys: string[] = []; + const flowLoopTypes: Type[][] = []; + const sharedFlowNodes: FlowNode[] = []; + const sharedFlowTypes: FlowType[] = []; + const flowNodeReachable: (boolean | undefined)[] = []; + const flowNodePostSuper: (boolean | undefined)[] = []; + const potentialThisCollisions: Node[] = []; + const potentialNewTargetCollisions: Node[] = []; + const potentialWeakMapSetCollisions: Node[] = []; + const potentialReflectCollisions: Node[] = []; + const potentialUnusedRenamedBindingElementsInTypes: BindingElement[] = []; + const awaitedTypeStack: number[] = []; + + const diagnostics = createDiagnosticCollection(); + const suggestionDiagnostics = createDiagnosticCollection(); + + const typeofType = createTypeofType(); + + let _jsxNamespace: __String; + let _jsxFactoryEntity: EntityName | undefined; + + const subtypeRelation = new Map(); + const strictSubtypeRelation = new Map(); + const assignableRelation = new Map(); + const comparableRelation = new Map(); + const identityRelation = new Map(); + const enumRelation = new Map(); + + const builtinGlobals = createSymbolTable(); + builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); + + // Extensions suggested for path imports when module resolution is node16 or higher. + // The first element of each tuple is the extension a file has. + // The second element of each tuple is the extension that should be used in a path import. + // e.g. if we want to import file `foo.mts`, we should write `import {} from "./foo.mjs". + const suggestedExtensions: [string, string][] = [ + [".mts", ".mjs"], + [".ts", ".js"], + [".cts", ".cjs"], + [".mjs", ".mjs"], + [".js", ".js"], + [".cjs", ".cjs"], + [".tsx", compilerOptions.jsx === JsxEmit.Preserve ? ".jsx" : ".js"], + [".jsx", ".jsx"], + [".json", ".json"], + ]; + + initializeTypeChecker(); + + return checker; + + function getCachedType(key: string | undefined) { + return key ? cachedTypes.get(key) : undefined; + } - interface DuplicateInfoForSymbol { - readonly firstFileLocations: Declaration[]; - readonly secondFileLocations: Declaration[]; - readonly isBlockScoped: boolean; - } - interface DuplicateInfoForFiles { - readonly firstFile: SourceFile; - readonly secondFile: SourceFile; - /** Key is symbol name. */ - readonly conflictingSymbols: ESMap; - } - /** Key is "/path/to/a.ts|/path/to/b.ts". */ - let amalgamatedDuplicates: ESMap | undefined; - const reverseMappedCache = new Map(); - let inInferTypeForHomomorphicMappedType = false; - let ambientModulesCache: Symbol[] | undefined; - /** - * List of every ambient module with a "*" wildcard. - * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. - * This is only used if there is no exact match. - */ - let patternAmbientModules: PatternAmbientModule[]; - let patternAmbientModuleAugmentations: ESMap | undefined; - - let globalObjectType: ObjectType; - let globalFunctionType: ObjectType; - let globalCallableFunctionType: ObjectType; - let globalNewableFunctionType: ObjectType; - let globalArrayType: GenericType; - let globalReadonlyArrayType: GenericType; - let globalStringType: ObjectType; - let globalNumberType: ObjectType; - let globalBooleanType: ObjectType; - let globalRegExpType: ObjectType; - let globalThisType: GenericType; - let anyArrayType: Type; - let autoArrayType: Type; - let anyReadonlyArrayType: Type; - let deferredGlobalNonNullableTypeAlias: Symbol; - - // The library files are only loaded when the feature is used. - // This allows users to just specify library files they want to used through --lib - // and they will not get an error from not having unrelated library files - let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; - let deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined; - let deferredGlobalESSymbolType: ObjectType | undefined; - let deferredGlobalTypedPropertyDescriptorType: GenericType; - let deferredGlobalPromiseType: GenericType | undefined; - let deferredGlobalPromiseLikeType: GenericType | undefined; - let deferredGlobalPromiseConstructorSymbol: Symbol | undefined; - let deferredGlobalPromiseConstructorLikeType: ObjectType | undefined; - let deferredGlobalIterableType: GenericType | undefined; - let deferredGlobalIteratorType: GenericType | undefined; - let deferredGlobalIterableIteratorType: GenericType | undefined; - let deferredGlobalGeneratorType: GenericType | undefined; - let deferredGlobalIteratorYieldResultType: GenericType | undefined; - let deferredGlobalIteratorReturnResultType: GenericType | undefined; - let deferredGlobalAsyncIterableType: GenericType | undefined; - let deferredGlobalAsyncIteratorType: GenericType | undefined; - let deferredGlobalAsyncIterableIteratorType: GenericType | undefined; - let deferredGlobalAsyncGeneratorType: GenericType | undefined; - let deferredGlobalTemplateStringsArrayType: ObjectType | undefined; - let deferredGlobalImportMetaType: ObjectType; - let deferredGlobalImportMetaExpressionType: ObjectType; - let deferredGlobalImportCallOptionsType: ObjectType | undefined; - let deferredGlobalExtractSymbol: Symbol | undefined; - let deferredGlobalOmitSymbol: Symbol | undefined; - let deferredGlobalAwaitedSymbol: Symbol | undefined; - let deferredGlobalBigIntType: ObjectType | undefined; - let deferredGlobalNaNSymbol: Symbol | undefined; - let deferredGlobalRecordSymbol: Symbol | undefined; - - const allPotentiallyUnusedIdentifiers = new Map(); // key is file name - - let flowLoopStart = 0; - let flowLoopCount = 0; - let sharedFlowCount = 0; - let flowAnalysisDisabled = false; - let flowInvocationCount = 0; - let lastFlowNode: FlowNode | undefined; - let lastFlowNodeReachable: boolean; - let flowTypeCache: Type[] | undefined; - - const emptyStringType = getStringLiteralType(""); - const zeroType = getNumberLiteralType(0); - const zeroBigIntType = getBigIntLiteralType({ negative: false, base10Value: "0" }); - - const resolutionTargets: TypeSystemEntity[] = []; - const resolutionResults: boolean[] = []; - const resolutionPropertyNames: TypeSystemPropertyName[] = []; - - let suggestionCount = 0; - const maximumSuggestionCount = 10; - const mergedSymbols: Symbol[] = []; - const symbolLinks: SymbolLinks[] = []; - const nodeLinks: NodeLinks[] = []; - const flowLoopCaches: ESMap[] = []; - const flowLoopNodes: FlowNode[] = []; - const flowLoopKeys: string[] = []; - const flowLoopTypes: Type[][] = []; - const sharedFlowNodes: FlowNode[] = []; - const sharedFlowTypes: FlowType[] = []; - const flowNodeReachable: (boolean | undefined)[] = []; - const flowNodePostSuper: (boolean | undefined)[] = []; - const potentialThisCollisions: Node[] = []; - const potentialNewTargetCollisions: Node[] = []; - const potentialWeakMapSetCollisions: Node[] = []; - const potentialReflectCollisions: Node[] = []; - const potentialUnusedRenamedBindingElementsInTypes: BindingElement[] = []; - const awaitedTypeStack: number[] = []; - - const diagnostics = createDiagnosticCollection(); - const suggestionDiagnostics = createDiagnosticCollection(); - - const typeofType = createTypeofType(); - - let _jsxNamespace: __String; - let _jsxFactoryEntity: EntityName | undefined; - - const subtypeRelation = new Map(); - const strictSubtypeRelation = new Map(); - const assignableRelation = new Map(); - const comparableRelation = new Map(); - const identityRelation = new Map(); - const enumRelation = new Map(); - - const builtinGlobals = createSymbolTable(); - builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); - - // Extensions suggested for path imports when module resolution is node16 or higher. - // The first element of each tuple is the extension a file has. - // The second element of each tuple is the extension that should be used in a path import. - // e.g. if we want to import file `foo.mts`, we should write `import {} from "./foo.mjs". - const suggestedExtensions: [string, string][] = [ - [".mts", ".mjs"], - [".ts", ".js"], - [".cts", ".cjs"], - [".mjs", ".mjs"], - [".js", ".js"], - [".cjs", ".cjs"], - [".tsx", compilerOptions.jsx === JsxEmit.Preserve ? ".jsx" : ".js"], - [".jsx", ".jsx"], - [".json", ".json"], - ]; - - initializeTypeChecker(); - - return checker; - - function getCachedType(key: string | undefined) { - return key ? cachedTypes.get(key) : undefined; - } - - function setCachedType(key: string | undefined, type: Type) { - if (key) cachedTypes.set(key, type); - return type; - } + function setCachedType(key: string | undefined, type: Type) { + if (key) cachedTypes.set(key, type); + return type; + } - function getJsxNamespace(location: Node | undefined): __String { - if (location) { - const file = getSourceFileOfNode(location); - if (file) { - if (isJsxOpeningFragment(location)) { - if (file.localJsxFragmentNamespace) { - return file.localJsxFragmentNamespace; - } - const jsxFragmentPragma = file.pragmas.get("jsxfrag"); - if (jsxFragmentPragma) { - const chosenPragma = isArray(jsxFragmentPragma) ? jsxFragmentPragma[0] : jsxFragmentPragma; - file.localJsxFragmentFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); - visitNode(file.localJsxFragmentFactory, markAsSynthetic); - if (file.localJsxFragmentFactory) { - return file.localJsxFragmentNamespace = getFirstIdentifier(file.localJsxFragmentFactory).escapedText; - } - } - const entity = getJsxFragmentFactoryEntity(location); - if (entity) { - file.localJsxFragmentFactory = entity; - return file.localJsxFragmentNamespace = getFirstIdentifier(entity).escapedText; - } + function getJsxNamespace(location: Node | undefined): __String { + if (location) { + const file = getSourceFileOfNode(location); + if (file) { + if (isJsxOpeningFragment(location)) { + if (file.localJsxFragmentNamespace) { + return file.localJsxFragmentNamespace; } - else { - const localJsxNamespace = getLocalJsxNamespace(file); - if (localJsxNamespace) { - return file.localJsxNamespace = localJsxNamespace; + const jsxFragmentPragma = file.pragmas.get("jsxfrag"); + if (jsxFragmentPragma) { + const chosenPragma = isArray(jsxFragmentPragma) ? jsxFragmentPragma[0] : jsxFragmentPragma; + file.localJsxFragmentFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); + visitNode(file.localJsxFragmentFactory, markAsSynthetic); + if (file.localJsxFragmentFactory) { + return file.localJsxFragmentNamespace = getFirstIdentifier(file.localJsxFragmentFactory).escapedText; } } - } - } - if (!_jsxNamespace) { - _jsxNamespace = "React" as __String; - if (compilerOptions.jsxFactory) { - _jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion); - visitNode(_jsxFactoryEntity, markAsSynthetic); - if (_jsxFactoryEntity) { - _jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText; + const entity = getJsxFragmentFactoryEntity(location); + if (entity) { + file.localJsxFragmentFactory = entity; + return file.localJsxFragmentNamespace = getFirstIdentifier(entity).escapedText; } } - else if (compilerOptions.reactNamespace) { - _jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace); + else { + const localJsxNamespace = getLocalJsxNamespace(file); + if (localJsxNamespace) { + return file.localJsxNamespace = localJsxNamespace; + } } } - if (!_jsxFactoryEntity) { - _jsxFactoryEntity = factory.createQualifiedName(factory.createIdentifier(unescapeLeadingUnderscores(_jsxNamespace)), "createElement"); - } - return _jsxNamespace; } - - function getLocalJsxNamespace(file: SourceFile): __String | undefined { - if (file.localJsxNamespace) { - return file.localJsxNamespace; - } - const jsxPragma = file.pragmas.get("jsx"); - if (jsxPragma) { - const chosenPragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; - file.localJsxFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); - visitNode(file.localJsxFactory, markAsSynthetic); - if (file.localJsxFactory) { - return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; + if (!_jsxNamespace) { + _jsxNamespace = "React" as __String; + if (compilerOptions.jsxFactory) { + _jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion); + visitNode(_jsxFactoryEntity, markAsSynthetic); + if (_jsxFactoryEntity) { + _jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText; } } + else if (compilerOptions.reactNamespace) { + _jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace); + } } - - function markAsSynthetic(node: Node): VisitResult { - setTextRangePosEnd(node, -1, -1); - return visitEachChild(node, markAsSynthetic, nullTransformationContext); + if (!_jsxFactoryEntity) { + _jsxFactoryEntity = factory.createQualifiedName(factory.createIdentifier(unescapeLeadingUnderscores(_jsxNamespace)), "createElement"); } + return _jsxNamespace; + } - function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { - // Ensure we have all the type information in place for this file so that all the - // emitter questions of this resolver will return the right information. - getDiagnostics(sourceFile, cancellationToken); - return emitResolver; + function getLocalJsxNamespace(file: SourceFile): __String | undefined { + if (file.localJsxNamespace) { + return file.localJsxNamespace; } - - function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { - const diagnostic = location - ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) - : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); - const existing = diagnostics.lookup(diagnostic); - if (existing) { - return existing; - } - else { - diagnostics.add(diagnostic); - return diagnostic; + const jsxPragma = file.pragmas.get("jsx"); + if (jsxPragma) { + const chosenPragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; + file.localJsxFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); + visitNode(file.localJsxFactory, markAsSynthetic); + if (file.localJsxFactory) { + return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; } } + } - function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { - const diagnostic = error(location, message, arg0, arg1, arg2, arg3); - diagnostic.skippedOn = key; - return diagnostic; - } + function markAsSynthetic(node: Node): VisitResult { + setTextRangePosEnd(node, -1, -1); + return visitEachChild(node, markAsSynthetic, nullTransformationContext); + } - function createError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { - return location - ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) - : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); - } + function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { + // Ensure we have all the type information in place for this file so that all the + // emitter questions of this resolver will return the right information. + getDiagnostics(sourceFile, cancellationToken); + return emitResolver; + } - function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { - const diagnostic = createError(location, message, arg0, arg1, arg2, arg3); + function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { + const diagnostic = location + ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) + : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); + const existing = diagnostics.lookup(diagnostic); + if (existing) { + return existing; + } + else { diagnostics.add(diagnostic); return diagnostic; } + } - function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) { - if (isError) { - diagnostics.add(diagnostic); - } - else { - suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); - } + function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { + const diagnostic = error(location, message, arg0, arg1, arg2, arg3); + diagnostic.skippedOn = key; + return diagnostic; + } + + function createError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { + return location + ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) + : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); + } + + function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { + const diagnostic = createError(location, message, arg0, arg1, arg2, arg3); + diagnostics.add(diagnostic); + return diagnostic; + } + + function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) { + if (isError) { + diagnostics.add(diagnostic); } - function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { - // Pseudo-synthesized input node - if (location.pos < 0 || location.end < 0) { - if (!isError) { - return; // Drop suggestions (we have no span to suggest on) - } - // Issue errors globally - const file = getSourceFileOfNode(location); - addErrorOrSuggestion(isError, "message" in message ? createFileDiagnostic(file, 0, 0, message, arg0, arg1, arg2, arg3) : createDiagnosticForFileFromMessageChain(file, message)); // eslint-disable-line local/no-in-operator - return; - } - addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message)); // eslint-disable-line local/no-in-operator + else { + suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); } - - function errorAndMaybeSuggestAwait( - location: Node, - maybeMissingAwait: boolean, - message: DiagnosticMessage, - arg0?: string | number | undefined, arg1?: string | number | undefined, arg2?: string | number | undefined, arg3?: string | number | undefined): Diagnostic { - const diagnostic = error(location, message, arg0, arg1, arg2, arg3); - if (maybeMissingAwait) { - const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); - addRelatedInfo(diagnostic, related); - } - return diagnostic; + } + function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { + // Pseudo-synthesized input node + if (location.pos < 0 || location.end < 0) { + if (!isError) { + return; // Drop suggestions (we have no span to suggest on) + } + // Issue errors globally + const file = getSourceFileOfNode(location); + addErrorOrSuggestion(isError, "message" in message ? createFileDiagnostic(file, 0, 0, message, arg0, arg1, arg2, arg3) : createDiagnosticForFileFromMessageChain(file, message)); // eslint-disable-line local/no-in-operator + return; } + addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message)); // eslint-disable-line local/no-in-operator + } - function addDeprecatedSuggestionWorker(declarations: Node | Node[], diagnostic: DiagnosticWithLocation) { - const deprecatedTag = Array.isArray(declarations) ? forEach(declarations, getJSDocDeprecatedTag) : getJSDocDeprecatedTag(declarations); - if (deprecatedTag) { - addRelatedInfo( - diagnostic, - createDiagnosticForNode(deprecatedTag, Diagnostics.The_declaration_was_marked_as_deprecated_here) - ); - } - // We call `addRelatedInfo()` before adding the diagnostic to prevent duplicates. - suggestionDiagnostics.add(diagnostic); - return diagnostic; - } + function errorAndMaybeSuggestAwait( + location: Node, + maybeMissingAwait: boolean, + message: DiagnosticMessage, + arg0?: string | number | undefined, arg1?: string | number | undefined, arg2?: string | number | undefined, arg3?: string | number | undefined): Diagnostic { + const diagnostic = error(location, message, arg0, arg1, arg2, arg3); + if (maybeMissingAwait) { + const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); + addRelatedInfo(diagnostic, related); + } + return diagnostic; + } - function isDeprecatedSymbol(symbol: Symbol) { - return !!(getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Deprecated); + function addDeprecatedSuggestionWorker(declarations: Node | Node[], diagnostic: DiagnosticWithLocation) { + const deprecatedTag = Array.isArray(declarations) ? forEach(declarations, getJSDocDeprecatedTag) : getJSDocDeprecatedTag(declarations); + if (deprecatedTag) { + addRelatedInfo( + diagnostic, + createDiagnosticForNode(deprecatedTag, Diagnostics.The_declaration_was_marked_as_deprecated_here) + ); } + // We call `addRelatedInfo()` before adding the diagnostic to prevent duplicates. + suggestionDiagnostics.add(diagnostic); + return diagnostic; + } - function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { - const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); - return addDeprecatedSuggestionWorker(declarations, diagnostic); - } + function isDeprecatedSymbol(symbol: Symbol) { + return !!(getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Deprecated); + } - function addDeprecatedSuggestionWithSignature(location: Node, declaration: Node, deprecatedEntity: string | undefined, signatureString: string) { - const diagnostic = deprecatedEntity - ? createDiagnosticForNode(location, Diagnostics.The_signature_0_of_1_is_deprecated, signatureString, deprecatedEntity) - : createDiagnosticForNode(location, Diagnostics._0_is_deprecated, signatureString); - return addDeprecatedSuggestionWorker(declaration, diagnostic); - } + function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { + const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); + return addDeprecatedSuggestionWorker(declarations, diagnostic); + } - function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { - symbolCount++; - const symbol = (new Symbol(flags | SymbolFlags.Transient, name) as TransientSymbol); - symbol.checkFlags = checkFlags || 0; - return symbol; - } + function addDeprecatedSuggestionWithSignature(location: Node, declaration: Node, deprecatedEntity: string | undefined, signatureString: string) { + const diagnostic = deprecatedEntity + ? createDiagnosticForNode(location, Diagnostics.The_signature_0_of_1_is_deprecated, signatureString, deprecatedEntity) + : createDiagnosticForNode(location, Diagnostics._0_is_deprecated, signatureString); + return addDeprecatedSuggestionWorker(declaration, diagnostic); + } - function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags { - let result: SymbolFlags = 0; - if (flags & SymbolFlags.BlockScopedVariable) result |= SymbolFlags.BlockScopedVariableExcludes; - if (flags & SymbolFlags.FunctionScopedVariable) result |= SymbolFlags.FunctionScopedVariableExcludes; - if (flags & SymbolFlags.Property) result |= SymbolFlags.PropertyExcludes; - if (flags & SymbolFlags.EnumMember) result |= SymbolFlags.EnumMemberExcludes; - if (flags & SymbolFlags.Function) result |= SymbolFlags.FunctionExcludes; - if (flags & SymbolFlags.Class) result |= SymbolFlags.ClassExcludes; - if (flags & SymbolFlags.Interface) result |= SymbolFlags.InterfaceExcludes; - if (flags & SymbolFlags.RegularEnum) result |= SymbolFlags.RegularEnumExcludes; - if (flags & SymbolFlags.ConstEnum) result |= SymbolFlags.ConstEnumExcludes; - if (flags & SymbolFlags.ValueModule) result |= SymbolFlags.ValueModuleExcludes; - if (flags & SymbolFlags.Method) result |= SymbolFlags.MethodExcludes; - if (flags & SymbolFlags.GetAccessor) result |= SymbolFlags.GetAccessorExcludes; - if (flags & SymbolFlags.SetAccessor) result |= SymbolFlags.SetAccessorExcludes; - if (flags & SymbolFlags.TypeParameter) result |= SymbolFlags.TypeParameterExcludes; - if (flags & SymbolFlags.TypeAlias) result |= SymbolFlags.TypeAliasExcludes; - if (flags & SymbolFlags.Alias) result |= SymbolFlags.AliasExcludes; - return result; - } + function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { + symbolCount++; + const symbol = (new Symbol(flags | SymbolFlags.Transient, name) as TransientSymbol); + symbol.checkFlags = checkFlags || 0; + return symbol; + } - function recordMergedSymbol(target: Symbol, source: Symbol) { - if (!source.mergeId) { - source.mergeId = nextMergeId; - nextMergeId++; - } - mergedSymbols[source.mergeId] = target; - } + function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags { + let result: SymbolFlags = 0; + if (flags & SymbolFlags.BlockScopedVariable) result |= SymbolFlags.BlockScopedVariableExcludes; + if (flags & SymbolFlags.FunctionScopedVariable) result |= SymbolFlags.FunctionScopedVariableExcludes; + if (flags & SymbolFlags.Property) result |= SymbolFlags.PropertyExcludes; + if (flags & SymbolFlags.EnumMember) result |= SymbolFlags.EnumMemberExcludes; + if (flags & SymbolFlags.Function) result |= SymbolFlags.FunctionExcludes; + if (flags & SymbolFlags.Class) result |= SymbolFlags.ClassExcludes; + if (flags & SymbolFlags.Interface) result |= SymbolFlags.InterfaceExcludes; + if (flags & SymbolFlags.RegularEnum) result |= SymbolFlags.RegularEnumExcludes; + if (flags & SymbolFlags.ConstEnum) result |= SymbolFlags.ConstEnumExcludes; + if (flags & SymbolFlags.ValueModule) result |= SymbolFlags.ValueModuleExcludes; + if (flags & SymbolFlags.Method) result |= SymbolFlags.MethodExcludes; + if (flags & SymbolFlags.GetAccessor) result |= SymbolFlags.GetAccessorExcludes; + if (flags & SymbolFlags.SetAccessor) result |= SymbolFlags.SetAccessorExcludes; + if (flags & SymbolFlags.TypeParameter) result |= SymbolFlags.TypeParameterExcludes; + if (flags & SymbolFlags.TypeAlias) result |= SymbolFlags.TypeAliasExcludes; + if (flags & SymbolFlags.Alias) result |= SymbolFlags.AliasExcludes; + return result; + } - function cloneSymbol(symbol: Symbol): Symbol { - const result = createSymbol(symbol.flags, symbol.escapedName); - result.declarations = symbol.declarations ? symbol.declarations.slice() : []; - result.parent = symbol.parent; - if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; - if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = new Map(symbol.members); - if (symbol.exports) result.exports = new Map(symbol.exports); - recordMergedSymbol(result, symbol); - return result; + function recordMergedSymbol(target: Symbol, source: Symbol) { + if (!source.mergeId) { + source.mergeId = nextMergeId; + nextMergeId++; } + mergedSymbols[source.mergeId] = target; + } - /** - * Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it. - * If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it. - */ - function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol { - if (!(target.flags & getExcludedSymbolFlags(source.flags)) || - (source.flags | target.flags) & SymbolFlags.Assignment) { - if (source === target) { - // This can happen when an export assigned namespace exports something also erroneously exported at the top level - // See `declarationFileNoCrashOnExtraExportModifier` for an example - return target; - } - if (!(target.flags & SymbolFlags.Transient)) { - const resolvedTarget = resolveSymbol(target); - if (resolvedTarget === unknownSymbol) { - return source; - } - target = cloneSymbol(resolvedTarget); - } - // Javascript static-property-assignment declarations always merge, even though they are also values - if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) { - // reset flag when merging instantiated module into value module that has only const enums - target.constEnumOnlyModule = false; - } - target.flags |= source.flags; - if (source.valueDeclaration) { - setValueDeclaration(target, source.valueDeclaration); - } - addRange(target.declarations, source.declarations); - if (source.members) { - if (!target.members) target.members = createSymbolTable(); - mergeSymbolTable(target.members, source.members, unidirectional); - } - if (source.exports) { - if (!target.exports) target.exports = createSymbolTable(); - mergeSymbolTable(target.exports, source.exports, unidirectional); - } - if (!unidirectional) { - recordMergedSymbol(target, source); - } - } - else if (target.flags & SymbolFlags.NamespaceModule) { - // Do not report an error when merging `var globalThis` with the built-in `globalThis`, - // as we will already report a "Declaration name conflicts..." error, and this error - // won't make much sense. - if (target !== globalThisSymbol) { - error( - source.declarations && getNameOfDeclaration(source.declarations[0]), - Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, - symbolToString(target)); - } - } - else { // error - const isEitherEnum = !!(target.flags & SymbolFlags.Enum || source.flags & SymbolFlags.Enum); - const isEitherBlockScoped = !!(target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable); - const message = isEitherEnum ? Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations - : isEitherBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 - : Diagnostics.Duplicate_identifier_0; - const sourceSymbolFile = source.declarations && getSourceFileOfNode(source.declarations[0]); - const targetSymbolFile = target.declarations && getSourceFileOfNode(target.declarations[0]); - - const isSourcePlainJs = isPlainJsFile(sourceSymbolFile, compilerOptions.checkJs); - const isTargetPlainJs = isPlainJsFile(targetSymbolFile, compilerOptions.checkJs); - const symbolName = symbolToString(source); - - // Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch - if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) { - const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile; - const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile; - const filesDuplicates = getOrUpdate(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, () => - ({ firstFile, secondFile, conflictingSymbols: new Map() } as DuplicateInfoForFiles)); - const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, () => - ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] } as DuplicateInfoForSymbol)); - if (!isSourcePlainJs) addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); - if (!isTargetPlainJs) addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); - } - else { - if (!isSourcePlainJs) addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); - if (!isTargetPlainJs) addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); - } - } - return target; + function cloneSymbol(symbol: Symbol): Symbol { + const result = createSymbol(symbol.flags, symbol.escapedName); + result.declarations = symbol.declarations ? symbol.declarations.slice() : []; + result.parent = symbol.parent; + if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; + if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; + if (symbol.members) result.members = new Map(symbol.members); + if (symbol.exports) result.exports = new Map(symbol.exports); + recordMergedSymbol(result, symbol); + return result; + } - function addDuplicateLocations(locs: Declaration[], symbol: Symbol): void { - if (symbol.declarations) { - for (const decl of symbol.declarations) { - pushIfUnique(locs, decl); - } + /** + * Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it. + * If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it. + */ + function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol { + if (!(target.flags & getExcludedSymbolFlags(source.flags)) || + (source.flags | target.flags) & SymbolFlags.Assignment) { + if (source === target) { + // This can happen when an export assigned namespace exports something also erroneously exported at the top level + // See `declarationFileNoCrashOnExtraExportModifier` for an example + return target; + } + if (!(target.flags & SymbolFlags.Transient)) { + const resolvedTarget = resolveSymbol(target); + if (resolvedTarget === unknownSymbol) { + return source; } + target = cloneSymbol(resolvedTarget); + } + // Javascript static-property-assignment declarations always merge, even though they are also values + if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) { + // reset flag when merging instantiated module into value module that has only const enums + target.constEnumOnlyModule = false; + } + target.flags |= source.flags; + if (source.valueDeclaration) { + setValueDeclaration(target, source.valueDeclaration); + } + addRange(target.declarations, source.declarations); + if (source.members) { + if (!target.members) target.members = createSymbolTable(); + mergeSymbolTable(target.members, source.members, unidirectional); + } + if (source.exports) { + if (!target.exports) target.exports = createSymbolTable(); + mergeSymbolTable(target.exports, source.exports, unidirectional); + } + if (!unidirectional) { + recordMergedSymbol(target, source); } } - - function addDuplicateDeclarationErrorsForSymbols(target: Symbol, message: DiagnosticMessage, symbolName: string, source: Symbol) { - forEach(target.declarations, node => { - addDuplicateDeclarationError(node, message, symbolName, source.declarations); - }); + else if (target.flags & SymbolFlags.NamespaceModule) { + // Do not report an error when merging `var globalThis` with the built-in `globalThis`, + // as we will already report a "Declaration name conflicts..." error, and this error + // won't make much sense. + if (target !== globalThisSymbol) { + error( + source.declarations && getNameOfDeclaration(source.declarations[0]), + Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, + symbolToString(target)); + } + } + else { // error + const isEitherEnum = !!(target.flags & SymbolFlags.Enum || source.flags & SymbolFlags.Enum); + const isEitherBlockScoped = !!(target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable); + const message = isEitherEnum ? Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations + : isEitherBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 + : Diagnostics.Duplicate_identifier_0; + const sourceSymbolFile = source.declarations && getSourceFileOfNode(source.declarations[0]); + const targetSymbolFile = target.declarations && getSourceFileOfNode(target.declarations[0]); + + const isSourcePlainJs = isPlainJsFile(sourceSymbolFile, compilerOptions.checkJs); + const isTargetPlainJs = isPlainJsFile(targetSymbolFile, compilerOptions.checkJs); + const symbolName = symbolToString(source); + + // Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch + if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) { + const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile; + const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile; + const filesDuplicates = getOrUpdate(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, () => + ({ firstFile, secondFile, conflictingSymbols: new Map() } as DuplicateInfoForFiles)); + const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, () => + ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] } as DuplicateInfoForSymbol)); + if (!isSourcePlainJs) addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); + if (!isTargetPlainJs) addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); + } + else { + if (!isSourcePlainJs) addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); + if (!isTargetPlainJs) addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); + } } + return target; - function addDuplicateDeclarationError(node: Declaration, message: DiagnosticMessage, symbolName: string, relatedNodes: readonly Declaration[] | undefined) { - const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node; - const err = lookupOrIssueError(errorNode, message, symbolName); - for (const relatedNode of relatedNodes || emptyArray) { - const adjustedNode = (getExpandoInitializer(relatedNode, /*isPrototypeAssignment*/ false) ? getNameOfExpando(relatedNode) : getNameOfDeclaration(relatedNode)) || relatedNode; - if (adjustedNode === errorNode) continue; - err.relatedInformation = err.relatedInformation || []; - const leadingMessage = createDiagnosticForNode(adjustedNode, Diagnostics._0_was_also_declared_here, symbolName); - const followOnMessage = createDiagnosticForNode(adjustedNode, Diagnostics.and_here); - if (length(err.relatedInformation) >= 5 || some(err.relatedInformation, r => compareDiagnostics(r, followOnMessage) === Comparison.EqualTo || compareDiagnostics(r, leadingMessage) === Comparison.EqualTo)) continue; - addRelatedInfo(err, !length(err.relatedInformation) ? leadingMessage : followOnMessage); + function addDuplicateLocations(locs: Declaration[], symbol: Symbol): void { + if (symbol.declarations) { + for (const decl of symbol.declarations) { + pushIfUnique(locs, decl); + } } } + } + + function addDuplicateDeclarationErrorsForSymbols(target: Symbol, message: DiagnosticMessage, symbolName: string, source: Symbol) { + forEach(target.declarations, node => { + addDuplicateDeclarationError(node, message, symbolName, source.declarations); + }); + } - function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined { - if (!first?.size) return second; - if (!second?.size) return first; - const combined = createSymbolTable(); - mergeSymbolTable(combined, first); - mergeSymbolTable(combined, second); - return combined; + function addDuplicateDeclarationError(node: Declaration, message: DiagnosticMessage, symbolName: string, relatedNodes: readonly Declaration[] | undefined) { + const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node; + const err = lookupOrIssueError(errorNode, message, symbolName); + for (const relatedNode of relatedNodes || emptyArray) { + const adjustedNode = (getExpandoInitializer(relatedNode, /*isPrototypeAssignment*/ false) ? getNameOfExpando(relatedNode) : getNameOfDeclaration(relatedNode)) || relatedNode; + if (adjustedNode === errorNode) continue; + err.relatedInformation = err.relatedInformation || []; + const leadingMessage = createDiagnosticForNode(adjustedNode, Diagnostics._0_was_also_declared_here, symbolName); + const followOnMessage = createDiagnosticForNode(adjustedNode, Diagnostics.and_here); + if (length(err.relatedInformation) >= 5 || some(err.relatedInformation, r => compareDiagnostics(r, followOnMessage) === Comparison.EqualTo || compareDiagnostics(r, leadingMessage) === Comparison.EqualTo)) continue; + addRelatedInfo(err, !length(err.relatedInformation) ? leadingMessage : followOnMessage); } + } - function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false) { - source.forEach((sourceSymbol, id) => { - const targetSymbol = target.get(id); - target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : getMergedSymbol(sourceSymbol)); - }); + function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined { + if (!first?.size) return second; + if (!second?.size) return first; + const combined = createSymbolTable(); + mergeSymbolTable(combined, first); + mergeSymbolTable(combined, second); + return combined; + } + + function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false) { + source.forEach((sourceSymbol, id) => { + const targetSymbol = target.get(id); + target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : getMergedSymbol(sourceSymbol)); + }); + } + + function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { + const moduleAugmentation = moduleName.parent as ModuleDeclaration; + if (moduleAugmentation.symbol.declarations?.[0] !== moduleAugmentation) { + // this is a combined symbol for multiple augmentations within the same file. + // its symbol already has accumulated information for all declarations + // so we need to add it just once - do the work only for first declaration + Debug.assert(moduleAugmentation.symbol.declarations!.length > 1); + return; } - function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { - const moduleAugmentation = moduleName.parent as ModuleDeclaration; - if (moduleAugmentation.symbol.declarations?.[0] !== moduleAugmentation) { - // this is a combined symbol for multiple augmentations within the same file. - // its symbol already has accumulated information for all declarations - // so we need to add it just once - do the work only for first declaration - Debug.assert(moduleAugmentation.symbol.declarations!.length > 1); + if (isGlobalScopeAugmentation(moduleAugmentation)) { + mergeSymbolTable(globals, moduleAugmentation.symbol.exports!); + } + else { + // find a module that about to be augmented + // do not validate names of augmentations that are defined in ambient context + const moduleNotFoundError = !(moduleName.parent.parent.flags & NodeFlags.Ambient) + ? Diagnostics.Invalid_module_name_in_augmentation_module_0_cannot_be_found + : undefined; + let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*isForAugmentation*/ true); + if (!mainModule) { return; } - - if (isGlobalScopeAugmentation(moduleAugmentation)) { - mergeSymbolTable(globals, moduleAugmentation.symbol.exports!); - } - else { - // find a module that about to be augmented - // do not validate names of augmentations that are defined in ambient context - const moduleNotFoundError = !(moduleName.parent.parent.flags & NodeFlags.Ambient) - ? Diagnostics.Invalid_module_name_in_augmentation_module_0_cannot_be_found - : undefined; - let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*isForAugmentation*/ true); - if (!mainModule) { - return; - } - // obtain item referenced by 'export=' - mainModule = resolveExternalModuleSymbol(mainModule); - if (mainModule.flags & SymbolFlags.Namespace) { - // If we're merging an augmentation to a pattern ambient module, we want to - // perform the merge unidirectionally from the augmentation ('a.foo') to - // the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you - // all the exports both from the pattern and from the augmentation, but - // 'getMergedSymbol()' on *.foo only gives you exports from *.foo. - if (some(patternAmbientModules, module => mainModule === module.symbol)) { - const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true); - if (!patternAmbientModuleAugmentations) { - patternAmbientModuleAugmentations = new Map(); - } - // moduleName will be a StringLiteral since this is not `declare global`. - patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged); + // obtain item referenced by 'export=' + mainModule = resolveExternalModuleSymbol(mainModule); + if (mainModule.flags & SymbolFlags.Namespace) { + // If we're merging an augmentation to a pattern ambient module, we want to + // perform the merge unidirectionally from the augmentation ('a.foo') to + // the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you + // all the exports both from the pattern and from the augmentation, but + // 'getMergedSymbol()' on *.foo only gives you exports from *.foo. + if (some(patternAmbientModules, module => mainModule === module.symbol)) { + const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true); + if (!patternAmbientModuleAugmentations) { + patternAmbientModuleAugmentations = new Map(); } - else { - if (mainModule.exports?.get(InternalSymbolName.ExportStar) && moduleAugmentation.symbol.exports?.size) { - // We may need to merge the module augmentation's exports into the target symbols of the resolved exports - const resolvedExports = getResolvedMembersOrExportsOfSymbol(mainModule, MembersOrExportsResolutionKind.resolvedExports); - for (const [key, value] of arrayFrom(moduleAugmentation.symbol.exports.entries())) { - if (resolvedExports.has(key) && !mainModule.exports.has(key)) { - mergeSymbol(resolvedExports.get(key)!, value); - } + // moduleName will be a StringLiteral since this is not `declare global`. + patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged); + } + else { + if (mainModule.exports?.get(InternalSymbolName.ExportStar) && moduleAugmentation.symbol.exports?.size) { + // We may need to merge the module augmentation's exports into the target symbols of the resolved exports + const resolvedExports = getResolvedMembersOrExportsOfSymbol(mainModule, MembersOrExportsResolutionKind.resolvedExports); + for (const [key, value] of arrayFrom(moduleAugmentation.symbol.exports.entries())) { + if (resolvedExports.has(key) && !mainModule.exports.has(key)) { + mergeSymbol(resolvedExports.get(key)!, value); } } - mergeSymbol(mainModule, moduleAugmentation.symbol); } + mergeSymbol(mainModule, moduleAugmentation.symbol); } - else { - // moduleName will be a StringLiteral since this is not `declare global`. - error(moduleName, Diagnostics.Cannot_augment_module_0_because_it_resolves_to_a_non_module_entity, (moduleName as StringLiteral).text); - } + } + else { + // moduleName will be a StringLiteral since this is not `declare global`. + error(moduleName, Diagnostics.Cannot_augment_module_0_because_it_resolves_to_a_non_module_entity, (moduleName as StringLiteral).text); } } + } - function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) { - source.forEach((sourceSymbol, id) => { - const targetSymbol = target.get(id); - if (targetSymbol) { - // Error on redeclarations - forEach(targetSymbol.declarations, addDeclarationDiagnostic(unescapeLeadingUnderscores(id), message)); - } - else { - target.set(id, sourceSymbol); - } - }); - - function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) { - return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id)); + function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) { + source.forEach((sourceSymbol, id) => { + const targetSymbol = target.get(id); + if (targetSymbol) { + // Error on redeclarations + forEach(targetSymbol.declarations, addDeclarationDiagnostic(unescapeLeadingUnderscores(id), message)); } - } + else { + target.set(id, sourceSymbol); + } + }); - function getSymbolLinks(symbol: Symbol): SymbolLinks { - if (symbol.flags & SymbolFlags.Transient) return symbol as TransientSymbol; - const id = getSymbolId(symbol); - return symbolLinks[id] || (symbolLinks[id] = new (SymbolLinks as any)()); + function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) { + return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id)); } + } - function getNodeLinks(node: Node): NodeLinks { - const nodeId = getNodeId(node); - return nodeLinks[nodeId] || (nodeLinks[nodeId] = new (NodeLinks as any)()); - } + function getSymbolLinks(symbol: Symbol): SymbolLinks { + if (symbol.flags & SymbolFlags.Transient) return symbol as TransientSymbol; + const id = getSymbolId(symbol); + return symbolLinks[id] || (symbolLinks[id] = new (SymbolLinks as any)()); + } - function isGlobalSourceFile(node: Node) { - return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node as SourceFile); - } + function getNodeLinks(node: Node): NodeLinks { + const nodeId = getNodeId(node); + return nodeLinks[nodeId] || (nodeLinks[nodeId] = new (NodeLinks as any)()); + } - function getSymbol(symbols: SymbolTable, name: __String, meaning: SymbolFlags): Symbol | undefined { - if (meaning) { - const symbol = getMergedSymbol(symbols.get(name)); - if (symbol) { - Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (symbol.flags & meaning) { + function isGlobalSourceFile(node: Node) { + return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node as SourceFile); + } + + function getSymbol(symbols: SymbolTable, name: __String, meaning: SymbolFlags): Symbol | undefined { + if (meaning) { + const symbol = getMergedSymbol(symbols.get(name)); + if (symbol) { + Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); + if (symbol.flags & meaning) { + return symbol; + } + if (symbol.flags & SymbolFlags.Alias) { + const targetFlags = getAllSymbolFlags(symbol); + // `targetFlags` will be `SymbolFlags.All` if an error occurred in alias resolution; this avoids cascading errors + if (targetFlags & meaning) { return symbol; } - if (symbol.flags & SymbolFlags.Alias) { - const targetFlags = getAllSymbolFlags(symbol); - // `targetFlags` will be `SymbolFlags.All` if an error occurred in alias resolution; this avoids cascading errors - if (targetFlags & meaning) { - return symbol; - } - } } } - // return undefined if we can't find a symbol. } + // return undefined if we can't find a symbol. + } - /** - * Get symbols that represent parameter-property-declaration as parameter and as property declaration - * @param parameter a parameterDeclaration node - * @param parameterName a name of the parameter to get the symbols for. - * @return a tuple of two symbols - */ - function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: __String): [Symbol, Symbol] { - const constructorDeclaration = parameter.parent; - const classDeclaration = parameter.parent.parent; - - const parameterSymbol = getSymbol(constructorDeclaration.locals!, parameterName, SymbolFlags.Value); - const propertySymbol = getSymbol(getMembersOfSymbol(classDeclaration.symbol), parameterName, SymbolFlags.Value); + /** + * Get symbols that represent parameter-property-declaration as parameter and as property declaration + * @param parameter a parameterDeclaration node + * @param parameterName a name of the parameter to get the symbols for. + * @return a tuple of two symbols + */ + function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: __String): [Symbol, Symbol] { + const constructorDeclaration = parameter.parent; + const classDeclaration = parameter.parent.parent; - if (parameterSymbol && propertySymbol) { - return [parameterSymbol, propertySymbol]; - } + const parameterSymbol = getSymbol(constructorDeclaration.locals!, parameterName, SymbolFlags.Value); + const propertySymbol = getSymbol(getMembersOfSymbol(classDeclaration.symbol), parameterName, SymbolFlags.Value); - return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); + if (parameterSymbol && propertySymbol) { + return [parameterSymbol, propertySymbol]; } - function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean { - const declarationFile = getSourceFileOfNode(declaration); - const useFile = getSourceFileOfNode(usage); - const declContainer = getEnclosingBlockScopeContainer(declaration); - if (declarationFile !== useFile) { - if ((moduleKind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || - (!outFile(compilerOptions)) || - isInTypeQuery(usage) || - declaration.flags & NodeFlags.Ambient) { - // nodes are in different files and order cannot be determined - return true; - } - // declaration is after usage - // can be legal if usage is deferred (i.e. inside function or in initializer of instance property) - if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { - return true; - } - const sourceFiles = host.getSourceFiles(); - return sourceFiles.indexOf(declarationFile) <= sourceFiles.indexOf(useFile); - } - - if (declaration.pos <= usage.pos && !(isPropertyDeclaration(declaration) && isThisProperty(usage.parent) && !declaration.initializer && !declaration.exclamationToken)) { - // declaration is before usage - if (declaration.kind === SyntaxKind.BindingElement) { - // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) - const errorBindingElement = getAncestor(usage, SyntaxKind.BindingElement) as BindingElement; - if (errorBindingElement) { - return findAncestor(errorBindingElement, isBindingElement) !== findAncestor(declaration, isBindingElement) || - declaration.pos < errorBindingElement.pos; - } - // or it might be illegal if usage happens before parent variable is declared (eg var [a] = a) - return isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, SyntaxKind.VariableDeclaration) as Declaration, usage); - } - else if (declaration.kind === SyntaxKind.VariableDeclaration) { - // still might be illegal if usage is in the initializer of the variable declaration (eg var a = a) - return !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration as VariableDeclaration, usage); - } - else if (isClassDeclaration(declaration)) { - // still might be illegal if the usage is within a computed property name in the class (eg class A { static p = "a"; [A.p]() {} }) - return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration); - } - else if (isPropertyDeclaration(declaration)) { - // still might be illegal if a self-referencing property initializer (eg private x = this.x) - return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false); - } - else if (isParameterPropertyDeclaration(declaration, declaration.parent)) { - // foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property - return !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields - && getContainingClass(declaration) === getContainingClass(usage) - && isUsedInFunctionOrInstanceProperty(usage, declaration)); - } - return true; - } - + return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); + } - // declaration is after usage, but it can still be legal if usage is deferred: - // 1. inside an export specifier - // 2. inside a function - // 3. inside an instance property initializer, a reference to a non-instance property - // (except when target: "esnext" and useDefineForClassFields: true and the reference is to a parameter property) - // 4. inside a static property initializer, a reference to a static method in the same class - // 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ) - // or if usage is in a type context: - // 1. inside a type query (typeof in type position) - // 2. inside a jsdoc comment - if (usage.parent.kind === SyntaxKind.ExportSpecifier || (usage.parent.kind === SyntaxKind.ExportAssignment && (usage.parent as ExportAssignment).isExportEquals)) { - // export specifiers do not use the variable, they only make it available for use + function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean { + const declarationFile = getSourceFileOfNode(declaration); + const useFile = getSourceFileOfNode(usage); + const declContainer = getEnclosingBlockScopeContainer(declaration); + if (declarationFile !== useFile) { + if ((moduleKind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || + (!outFile(compilerOptions)) || + isInTypeQuery(usage) || + declaration.flags & NodeFlags.Ambient) { + // nodes are in different files and order cannot be determined return true; } - // When resolving symbols for exports, the `usage` location passed in can be the export site directly - if (usage.kind === SyntaxKind.ExportAssignment && (usage as ExportAssignment).isExportEquals) { + // declaration is after usage + // can be legal if usage is deferred (i.e. inside function or in initializer of instance property) + if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { return true; } + const sourceFiles = host.getSourceFiles(); + return sourceFiles.indexOf(declarationFile) <= sourceFiles.indexOf(useFile); + } - if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || usageInTypeDeclaration()) { - return true; - } - if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { - if (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields - && getContainingClass(declaration) - && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent))) { - return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); - } - else { - return true; + if (declaration.pos <= usage.pos && !(isPropertyDeclaration(declaration) && isThisProperty(usage.parent) && !declaration.initializer && !declaration.exclamationToken)) { + // declaration is before usage + if (declaration.kind === SyntaxKind.BindingElement) { + // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) + const errorBindingElement = getAncestor(usage, SyntaxKind.BindingElement) as BindingElement; + if (errorBindingElement) { + return findAncestor(errorBindingElement, isBindingElement) !== findAncestor(declaration, isBindingElement) || + declaration.pos < errorBindingElement.pos; } + // or it might be illegal if usage happens before parent variable is declared (eg var [a] = a) + return isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, SyntaxKind.VariableDeclaration) as Declaration, usage); } - return false; - - function usageInTypeDeclaration() { - return !!findAncestor(usage, node => isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)); + else if (declaration.kind === SyntaxKind.VariableDeclaration) { + // still might be illegal if usage is in the initializer of the variable declaration (eg var a = a) + return !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration as VariableDeclaration, usage); + } + else if (isClassDeclaration(declaration)) { + // still might be illegal if the usage is within a computed property name in the class (eg class A { static p = "a"; [A.p]() {} }) + return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration); + } + else if (isPropertyDeclaration(declaration)) { + // still might be illegal if a self-referencing property initializer (eg private x = this.x) + return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false); + } + else if (isParameterPropertyDeclaration(declaration, declaration.parent)) { + // foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property + return !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields + && getContainingClass(declaration) === getContainingClass(usage) + && isUsedInFunctionOrInstanceProperty(usage, declaration)); } + return true; + } - function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { - switch (declaration.parent.parent.kind) { - case SyntaxKind.VariableStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForOfStatement: - // variable statement/for/for-of statement case, - // use site should not be inside variable declaration (initializer of declaration or binding element) - if (isSameScopeDescendentOf(usage, declaration, declContainer)) { - return true; - } - break; - } - // ForIn/ForOf case - use site should not be used in expression part - const grandparent = declaration.parent.parent; - return isForInOrOfStatement(grandparent) && isSameScopeDescendentOf(usage, grandparent.expression, declContainer); + // declaration is after usage, but it can still be legal if usage is deferred: + // 1. inside an export specifier + // 2. inside a function + // 3. inside an instance property initializer, a reference to a non-instance property + // (except when target: "esnext" and useDefineForClassFields: true and the reference is to a parameter property) + // 4. inside a static property initializer, a reference to a static method in the same class + // 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ) + // or if usage is in a type context: + // 1. inside a type query (typeof in type position) + // 2. inside a jsdoc comment + if (usage.parent.kind === SyntaxKind.ExportSpecifier || (usage.parent.kind === SyntaxKind.ExportAssignment && (usage.parent as ExportAssignment).isExportEquals)) { + // export specifiers do not use the variable, they only make it available for use + return true; + } + // When resolving symbols for exports, the `usage` location passed in can be the export site directly + if (usage.kind === SyntaxKind.ExportAssignment && (usage as ExportAssignment).isExportEquals) { + return true; + } + + if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || usageInTypeDeclaration()) { + return true; + } + if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { + if (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields + && getContainingClass(declaration) + && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent))) { + return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); + } + else { + return true; } + } + return false; - function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node): boolean { - return !!findAncestor(usage, current => { - if (current === declContainer) { - return "quit"; - } - if (isFunctionLike(current)) { + function usageInTypeDeclaration() { + return !!findAncestor(usage, node => isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)); + } + + function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { + switch (declaration.parent.parent.kind) { + case SyntaxKind.VariableStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForOfStatement: + // variable statement/for/for-of statement case, + // use site should not be inside variable declaration (initializer of declaration or binding element) + if (isSameScopeDescendentOf(usage, declaration, declContainer)) { return true; } - if (isClassStaticBlockDeclaration(current)) { - return declaration.pos < usage.pos; - } + break; + } - const propertyDeclaration = tryCast(current.parent, isPropertyDeclaration); - if (propertyDeclaration) { - const initializerOfProperty = propertyDeclaration.initializer === current; - if (initializerOfProperty) { - if (isStatic(current.parent)) { - if (declaration.kind === SyntaxKind.MethodDeclaration) { - return true; - } - if (isPropertyDeclaration(declaration) && getContainingClass(usage) === getContainingClass(declaration)) { - const propName = declaration.name; - if (isIdentifier(propName) || isPrivateIdentifier(propName)) { - const type = getTypeOfSymbol(getSymbolOfNode(declaration)); - const staticBlocks = filter(declaration.parent.members, isClassStaticBlockDeclaration); - if (isPropertyInitializedInStaticBlocks(propName, type, staticBlocks, declaration.parent.pos, current.pos)) { - return true; - } + // ForIn/ForOf case - use site should not be used in expression part + const grandparent = declaration.parent.parent; + return isForInOrOfStatement(grandparent) && isSameScopeDescendentOf(usage, grandparent.expression, declContainer); + } + + function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node): boolean { + return !!findAncestor(usage, current => { + if (current === declContainer) { + return "quit"; + } + if (isFunctionLike(current)) { + return true; + } + if (isClassStaticBlockDeclaration(current)) { + return declaration.pos < usage.pos; + } + + const propertyDeclaration = tryCast(current.parent, isPropertyDeclaration); + if (propertyDeclaration) { + const initializerOfProperty = propertyDeclaration.initializer === current; + if (initializerOfProperty) { + if (isStatic(current.parent)) { + if (declaration.kind === SyntaxKind.MethodDeclaration) { + return true; + } + if (isPropertyDeclaration(declaration) && getContainingClass(usage) === getContainingClass(declaration)) { + const propName = declaration.name; + if (isIdentifier(propName) || isPrivateIdentifier(propName)) { + const type = getTypeOfSymbol(getSymbolOfNode(declaration)); + const staticBlocks = filter(declaration.parent.members, isClassStaticBlockDeclaration); + if (isPropertyInitializedInStaticBlocks(propName, type, staticBlocks, declaration.parent.pos, current.pos)) { + return true; } } } - else { - const isDeclarationInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !isStatic(declaration); - if (!isDeclarationInstanceProperty || getContainingClass(usage) !== getContainingClass(declaration)) { - return true; - } + } + else { + const isDeclarationInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !isStatic(declaration); + if (!isDeclarationInstanceProperty || getContainingClass(usage) !== getContainingClass(declaration)) { + return true; } } } - return false; - }); - } - - /** stopAtAnyPropertyDeclaration is used for detecting ES-standard class field use-before-def errors */ - function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration | ParameterPropertyDeclaration, usage: Node, stopAtAnyPropertyDeclaration: boolean) { - // always legal if usage is after declaration - if (usage.end > declaration.end) { - return false; } - - // still might be legal if usage is deferred (e.g. x: any = () => this.x) - // otherwise illegal if immediately referenced within the declaration (e.g. x: any = this.x) - const ancestorChangingReferenceScope = findAncestor(usage, (node: Node) => { - if (node === declaration) { - return "quit"; - } - - switch (node.kind) { - case SyntaxKind.ArrowFunction: - return true; - case SyntaxKind.PropertyDeclaration: - // even when stopping at any property declaration, they need to come from the same class - return stopAtAnyPropertyDeclaration && - (isPropertyDeclaration(declaration) && node.parent === declaration.parent - || isParameterPropertyDeclaration(declaration, declaration.parent) && node.parent === declaration.parent.parent) - ? "quit": true; - case SyntaxKind.Block: - switch (node.parent.kind) { - case SyntaxKind.GetAccessor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.SetAccessor: - return true; - default: - return false; - } - default: - return false; - } - }); - - return ancestorChangingReferenceScope === undefined; - } + return false; + }); } - function useOuterVariableScopeInParameter(result: Symbol, location: Node, lastLocation: Node) { - const target = getEmitScriptTarget(compilerOptions); - const functionLocation = location as FunctionLikeDeclaration; - if (isParameter(lastLocation) - && functionLocation.body - && result.valueDeclaration - && result.valueDeclaration.pos >= functionLocation.body.pos - && result.valueDeclaration.end <= functionLocation.body.end) { - // check for several cases where we introduce temporaries that require moving the name/initializer of the parameter to the body - // - static field in a class expression - // - optional chaining pre-es2020 - // - nullish coalesce pre-es2020 - // - spread assignment in binding pattern pre-es2017 - if (target >= ScriptTarget.ES2015) { - const links = getNodeLinks(functionLocation); - if (links.declarationRequiresScopeChange === undefined) { - links.declarationRequiresScopeChange = forEach(functionLocation.parameters, requiresScopeChange) || false; - } - return !links.declarationRequiresScopeChange; - } + /** stopAtAnyPropertyDeclaration is used for detecting ES-standard class field use-before-def errors */ + function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration | ParameterPropertyDeclaration, usage: Node, stopAtAnyPropertyDeclaration: boolean) { + // always legal if usage is after declaration + if (usage.end > declaration.end) { + return false; } - return false; - function requiresScopeChange(node: ParameterDeclaration): boolean { - return requiresScopeChangeWorker(node.name) - || !!node.initializer && requiresScopeChangeWorker(node.initializer); - } + // still might be legal if usage is deferred (e.g. x: any = () => this.x) + // otherwise illegal if immediately referenced within the declaration (e.g. x: any = this.x) + const ancestorChangingReferenceScope = findAncestor(usage, (node: Node) => { + if (node === declaration) { + return "quit"; + } - function requiresScopeChangeWorker(node: Node): boolean { switch (node.kind) { case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.Constructor: - // do not descend into these - return false; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyAssignment: - return requiresScopeChangeWorker((node as MethodDeclaration | AccessorDeclaration | PropertyAssignment).name); + return true; case SyntaxKind.PropertyDeclaration: - // static properties in classes introduce temporary variables - if (hasStaticModifier(node)) { - return target < ScriptTarget.ESNext || !useDefineForClassFields; + // even when stopping at any property declaration, they need to come from the same class + return stopAtAnyPropertyDeclaration && + (isPropertyDeclaration(declaration) && node.parent === declaration.parent + || isParameterPropertyDeclaration(declaration, declaration.parent) && node.parent === declaration.parent.parent) + ? "quit": true; + case SyntaxKind.Block: + switch (node.parent.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.SetAccessor: + return true; + default: + return false; } - return requiresScopeChangeWorker((node as PropertyDeclaration).name); default: - // null coalesce and optional chain pre-es2020 produce temporary variables - if (isNullishCoalesce(node) || isOptionalChain(node)) { - return target < ScriptTarget.ES2020; - } - if (isBindingElement(node) && node.dotDotDotToken && isObjectBindingPattern(node.parent)) { - return target < ScriptTarget.ES2017; - } - if (isTypeNode(node)) return false; - return forEachChild(node, requiresScopeChangeWorker) || false; + return false; + } + }); + + return ancestorChangingReferenceScope === undefined; + } + } + + function useOuterVariableScopeInParameter(result: Symbol, location: Node, lastLocation: Node) { + const target = getEmitScriptTarget(compilerOptions); + const functionLocation = location as FunctionLikeDeclaration; + if (isParameter(lastLocation) + && functionLocation.body + && result.valueDeclaration + && result.valueDeclaration.pos >= functionLocation.body.pos + && result.valueDeclaration.end <= functionLocation.body.end) { + // check for several cases where we introduce temporaries that require moving the name/initializer of the parameter to the body + // - static field in a class expression + // - optional chaining pre-es2020 + // - nullish coalesce pre-es2020 + // - spread assignment in binding pattern pre-es2017 + if (target >= ScriptTarget.ES2015) { + const links = getNodeLinks(functionLocation); + if (links.declarationRequiresScopeChange === undefined) { + links.declarationRequiresScopeChange = forEach(functionLocation.parameters, requiresScopeChange) || false; } + return !links.declarationRequiresScopeChange; } } + return false; - function isConstAssertion(location: Node) { - return (isAssertionExpression(location) && isConstTypeReference(location.type)) - || (isJSDocTypeTag(location) && isConstTypeReference(location.typeExpression)); + function requiresScopeChange(node: ParameterDeclaration): boolean { + return requiresScopeChangeWorker(node.name) + || !!node.initializer && requiresScopeChangeWorker(node.initializer); } - /** - * Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and - * the nameNotFoundMessage argument is not undefined. Returns the resolved symbol, or undefined if no symbol with - * the given name can be found. - * - * @param nameNotFoundMessage If defined, we will report errors found during resolve. - * @param isUse If true, this will count towards --noUnusedLocals / --noUnusedParameters. - */ - function resolveName( - location: Node | undefined, - name: __String, - meaning: SymbolFlags, - nameNotFoundMessage: DiagnosticMessage | undefined, - nameArg: __String | Identifier | undefined, - isUse: boolean, - excludeGlobals = false, - getSpellingSuggestions = true): Symbol | undefined { - return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSpellingSuggestions, getSymbol); - } - - function resolveNameHelper( - location: Node | undefined, - name: __String, - meaning: SymbolFlags, - nameNotFoundMessage: DiagnosticMessage | undefined, - nameArg: __String | Identifier | undefined, - isUse: boolean, - excludeGlobals: boolean, - getSpellingSuggestions: boolean, - lookup: typeof getSymbol): Symbol | undefined { - const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location - let result: Symbol | undefined; - let lastLocation: Node | undefined; - let lastSelfReferenceLocation: Node | undefined; - let propertyWithInvalidInitializer: PropertyDeclaration | undefined; - let associatedDeclarationForContainingInitializerOrBindingName: ParameterDeclaration | BindingElement | undefined; - let withinDeferredContext = false; - const errorLocation = location; - let grandparent: Node; - let isInExternalModule = false; - - loop: while (location) { - if (name === "const" && isConstAssertion(location)) { - // `const` in an `as const` has no symbol, but issues no error because there is no *actual* lookup of the type - // (it refers to the constant type of the expression instead) - return undefined; - } - // Locals of a source file are not in scope (because they get merged into the global symbol table) - if (location.locals && !isGlobalSourceFile(location)) { - if (result = lookup(location.locals, name, meaning)) { - let useResult = true; - if (isFunctionLike(location) && lastLocation && lastLocation !== (location as FunctionLikeDeclaration).body) { - // symbol lookup restrictions for function-like declarations - // - Type parameters of a function are in scope in the entire function declaration, including the parameter - // list and return type. However, local types are only in scope in the function body. - // - parameters are only in the scope of function body - // This restriction does not apply to JSDoc comment types because they are parented - // at a higher level than type parameters would normally be - if (meaning & result.flags & SymbolFlags.Type && lastLocation.kind !== SyntaxKind.JSDoc) { - useResult = result.flags & SymbolFlags.TypeParameter - // type parameters are visible in parameter list, return type and type parameter list - ? lastLocation === (location as FunctionLikeDeclaration).type || + function requiresScopeChangeWorker(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.Constructor: + // do not descend into these + return false; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyAssignment: + return requiresScopeChangeWorker((node as MethodDeclaration | AccessorDeclaration | PropertyAssignment).name); + case SyntaxKind.PropertyDeclaration: + // static properties in classes introduce temporary variables + if (hasStaticModifier(node)) { + return target < ScriptTarget.ESNext || !useDefineForClassFields; + } + return requiresScopeChangeWorker((node as PropertyDeclaration).name); + default: + // null coalesce and optional chain pre-es2020 produce temporary variables + if (isNullishCoalesce(node) || isOptionalChain(node)) { + return target < ScriptTarget.ES2020; + } + if (isBindingElement(node) && node.dotDotDotToken && isObjectBindingPattern(node.parent)) { + return target < ScriptTarget.ES2017; + } + if (isTypeNode(node)) return false; + return forEachChild(node, requiresScopeChangeWorker) || false; + } + } + } + + function isConstAssertion(location: Node) { + return (isAssertionExpression(location) && isConstTypeReference(location.type)) + || (isJSDocTypeTag(location) && isConstTypeReference(location.typeExpression)); + } + + /** + * Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and + * the nameNotFoundMessage argument is not undefined. Returns the resolved symbol, or undefined if no symbol with + * the given name can be found. + * + * @param nameNotFoundMessage If defined, we will report errors found during resolve. + * @param isUse If true, this will count towards --noUnusedLocals / --noUnusedParameters. + */ + function resolveName( + location: Node | undefined, + name: __String, + meaning: SymbolFlags, + nameNotFoundMessage: DiagnosticMessage | undefined, + nameArg: __String | Identifier | undefined, + isUse: boolean, + excludeGlobals = false, + getSpellingSuggestions = true): Symbol | undefined { + return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSpellingSuggestions, getSymbol); + } + + function resolveNameHelper( + location: Node | undefined, + name: __String, + meaning: SymbolFlags, + nameNotFoundMessage: DiagnosticMessage | undefined, + nameArg: __String | Identifier | undefined, + isUse: boolean, + excludeGlobals: boolean, + getSpellingSuggestions: boolean, + lookup: typeof getSymbol): Symbol | undefined { + const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location + let result: Symbol | undefined; + let lastLocation: Node | undefined; + let lastSelfReferenceLocation: Node | undefined; + let propertyWithInvalidInitializer: PropertyDeclaration | undefined; + let associatedDeclarationForContainingInitializerOrBindingName: ParameterDeclaration | BindingElement | undefined; + let withinDeferredContext = false; + const errorLocation = location; + let grandparent: Node; + let isInExternalModule = false; + + loop: while (location) { + if (name === "const" && isConstAssertion(location)) { + // `const` in an `as const` has no symbol, but issues no error because there is no *actual* lookup of the type + // (it refers to the constant type of the expression instead) + return undefined; + } + // Locals of a source file are not in scope (because they get merged into the global symbol table) + if (location.locals && !isGlobalSourceFile(location)) { + if (result = lookup(location.locals, name, meaning)) { + let useResult = true; + if (isFunctionLike(location) && lastLocation && lastLocation !== (location as FunctionLikeDeclaration).body) { + // symbol lookup restrictions for function-like declarations + // - Type parameters of a function are in scope in the entire function declaration, including the parameter + // list and return type. However, local types are only in scope in the function body. + // - parameters are only in the scope of function body + // This restriction does not apply to JSDoc comment types because they are parented + // at a higher level than type parameters would normally be + if (meaning & result.flags & SymbolFlags.Type && lastLocation.kind !== SyntaxKind.JSDoc) { + useResult = result.flags & SymbolFlags.TypeParameter + // type parameters are visible in parameter list, return type and type parameter list + ? lastLocation === (location as FunctionLikeDeclaration).type || + lastLocation.kind === SyntaxKind.Parameter || + lastLocation.kind === SyntaxKind.JSDocParameterTag || + lastLocation.kind === SyntaxKind.JSDocReturnTag || + lastLocation.kind === SyntaxKind.TypeParameter + // local types not visible outside the function body + : false; + } + if (meaning & result.flags & SymbolFlags.Variable) { + // expression inside parameter will lookup as normal variable scope when targeting es2015+ + if (useOuterVariableScopeInParameter(result, location, lastLocation)) { + useResult = false; + } + else if (result.flags & SymbolFlags.FunctionScopedVariable) { + // parameters are visible only inside function body, parameter list and return type + // technically for parameter list case here we might mix parameters and variables declared in function, + // however it is detected separately when checking initializers of parameters + // to make sure that they reference no variables declared after them. + useResult = lastLocation.kind === SyntaxKind.Parameter || - lastLocation.kind === SyntaxKind.JSDocParameterTag || - lastLocation.kind === SyntaxKind.JSDocReturnTag || - lastLocation.kind === SyntaxKind.TypeParameter - // local types not visible outside the function body - : false; + ( + lastLocation === (location as FunctionLikeDeclaration).type && + !!findAncestor(result.valueDeclaration, isParameter) + ); } - if (meaning & result.flags & SymbolFlags.Variable) { - // expression inside parameter will lookup as normal variable scope when targeting es2015+ - if (useOuterVariableScopeInParameter(result, location, lastLocation)) { - useResult = false; - } - else if (result.flags & SymbolFlags.FunctionScopedVariable) { - // parameters are visible only inside function body, parameter list and return type - // technically for parameter list case here we might mix parameters and variables declared in function, - // however it is detected separately when checking initializers of parameters - // to make sure that they reference no variables declared after them. - useResult = - lastLocation.kind === SyntaxKind.Parameter || - ( - lastLocation === (location as FunctionLikeDeclaration).type && - !!findAncestor(result.valueDeclaration, isParameter) - ); - } + } + } + else if (location.kind === SyntaxKind.ConditionalType) { + // A type parameter declared using 'infer T' in a conditional type is visible only in + // the true branch of the conditional type. + useResult = lastLocation === (location as ConditionalTypeNode).trueType; + } + + if (useResult) { + break loop; + } + else { + result = undefined; + } + } + } + withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation); + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalOrCommonJsModule(location as SourceFile)) break; + isInExternalModule = true; + // falls through + case SyntaxKind.ModuleDeclaration: + const moduleExports = getSymbolOfNode(location as SourceFile | ModuleDeclaration)?.exports || emptySymbols; + if (location.kind === SyntaxKind.SourceFile || (isModuleDeclaration(location) && location.flags & NodeFlags.Ambient && !isGlobalScopeAugmentation(location))) { + + // It's an external module. First see if the module has an export default and if the local + // name of that export default matches. + if (result = moduleExports.get(InternalSymbolName.Default)) { + const localSymbol = getLocalSymbolForExportDefault(result); + if (localSymbol && (result.flags & meaning) && localSymbol.escapedName === name) { + break loop; } + result = undefined; } - else if (location.kind === SyntaxKind.ConditionalType) { - // A type parameter declared using 'infer T' in a conditional type is visible only in - // the true branch of the conditional type. - useResult = lastLocation === (location as ConditionalTypeNode).trueType; + + // Because of module/namespace merging, a module's exports are in scope, + // yet we never want to treat an export specifier as putting a member in scope. + // Therefore, if the name we find is purely an export specifier, it is not actually considered in scope. + // Two things to note about this: + // 1. We have to check this without calling getSymbol. The problem with calling getSymbol + // on an export specifier is that it might find the export specifier itself, and try to + // resolve it as an alias. This will cause the checker to consider the export specifier + // a circular alias reference when it might not be. + // 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely* + // an alias. If we used &, we'd be throwing out symbols that have non alias aspects, + // which is not the desired behavior. + const moduleExport = moduleExports.get(name); + if (moduleExport && + moduleExport.flags === SymbolFlags.Alias && + (getDeclarationOfKind(moduleExport, SyntaxKind.ExportSpecifier) || getDeclarationOfKind(moduleExport, SyntaxKind.NamespaceExport))) { + break; } + } - if (useResult) { - break loop; + // ES6 exports are also visible locally (except for 'default'), but commonjs exports are not (except typedefs) + if (name !== InternalSymbolName.Default && (result = lookup(moduleExports, name, meaning & SymbolFlags.ModuleMember))) { + if (isSourceFile(location) && location.commonJsModuleIndicator && !result.declarations?.some(isJSDocTypeAlias)) { + result = undefined; } else { - result = undefined; + break loop; } } - } - withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation); - switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalOrCommonJsModule(location as SourceFile)) break; - isInExternalModule = true; - // falls through - case SyntaxKind.ModuleDeclaration: - const moduleExports = getSymbolOfNode(location as SourceFile | ModuleDeclaration)?.exports || emptySymbols; - if (location.kind === SyntaxKind.SourceFile || (isModuleDeclaration(location) && location.flags & NodeFlags.Ambient && !isGlobalScopeAugmentation(location))) { - - // It's an external module. First see if the module has an export default and if the local - // name of that export default matches. - if (result = moduleExports.get(InternalSymbolName.Default)) { - const localSymbol = getLocalSymbolForExportDefault(result); - if (localSymbol && (result.flags & meaning) && localSymbol.escapedName === name) { - break loop; - } - result = undefined; - } - - // Because of module/namespace merging, a module's exports are in scope, - // yet we never want to treat an export specifier as putting a member in scope. - // Therefore, if the name we find is purely an export specifier, it is not actually considered in scope. - // Two things to note about this: - // 1. We have to check this without calling getSymbol. The problem with calling getSymbol - // on an export specifier is that it might find the export specifier itself, and try to - // resolve it as an alias. This will cause the checker to consider the export specifier - // a circular alias reference when it might not be. - // 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely* - // an alias. If we used &, we'd be throwing out symbols that have non alias aspects, - // which is not the desired behavior. - const moduleExport = moduleExports.get(name); - if (moduleExport && - moduleExport.flags === SymbolFlags.Alias && - (getDeclarationOfKind(moduleExport, SyntaxKind.ExportSpecifier) || getDeclarationOfKind(moduleExport, SyntaxKind.NamespaceExport))) { - break; - } - } - - // ES6 exports are also visible locally (except for 'default'), but commonjs exports are not (except typedefs) - if (name !== InternalSymbolName.Default && (result = lookup(moduleExports, name, meaning & SymbolFlags.ModuleMember))) { - if (isSourceFile(location) && location.commonJsModuleIndicator && !result.declarations?.some(isJSDocTypeAlias)) { - result = undefined; - } - else { - break loop; + break; + case SyntaxKind.EnumDeclaration: + if (result = lookup(getSymbolOfNode(location)?.exports || emptySymbols, name, meaning & SymbolFlags.EnumMember)) { + break loop; + } + break; + case SyntaxKind.PropertyDeclaration: + // TypeScript 1.0 spec (April 2014): 8.4.1 + // Initializer expressions for instance member variables are evaluated in the scope + // of the class constructor body but are not permitted to reference parameters or + // local variables of the constructor. This effectively means that entities from outer scopes + // by the same name as a constructor parameter or local variable are inaccessible + // in initializer expressions for instance member variables. + if (!isStatic(location)) { + const ctor = findConstructorDeclaration(location.parent as ClassLikeDeclaration); + if (ctor && ctor.locals) { + if (lookup(ctor.locals, name, meaning & SymbolFlags.Value)) { + // Remember the property node, it will be used later to report appropriate error + Debug.assertNode(location, isPropertyDeclaration); + propertyWithInvalidInitializer = location; } } - break; - case SyntaxKind.EnumDeclaration: - if (result = lookup(getSymbolOfNode(location)?.exports || emptySymbols, name, meaning & SymbolFlags.EnumMember)) { - break loop; + } + break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals + // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would + // trigger resolving late-bound names, which we may already be in the process of doing while we're here! + if (result = lookup(getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols, name, meaning & SymbolFlags.Type)) { + if (!isTypeParameterSymbolDeclaredInContainer(result, location)) { + // ignore type parameters not declared in this container + result = undefined; + break; } - break; - case SyntaxKind.PropertyDeclaration: - // TypeScript 1.0 spec (April 2014): 8.4.1 - // Initializer expressions for instance member variables are evaluated in the scope - // of the class constructor body but are not permitted to reference parameters or - // local variables of the constructor. This effectively means that entities from outer scopes - // by the same name as a constructor parameter or local variable are inaccessible - // in initializer expressions for instance member variables. - if (!isStatic(location)) { - const ctor = findConstructorDeclaration(location.parent as ClassLikeDeclaration); - if (ctor && ctor.locals) { - if (lookup(ctor.locals, name, meaning & SymbolFlags.Value)) { - // Remember the property node, it will be used later to report appropriate error - Debug.assertNode(location, isPropertyDeclaration); - propertyWithInvalidInitializer = location; - } + if (lastLocation && isStatic(lastLocation)) { + // TypeScript 1.0 spec (April 2014): 3.4.1 + // The scope of a type parameter extends over the entire declaration with which the type + // parameter list is associated, with the exception of static member declarations in classes. + if (nameNotFoundMessage) { + error(errorLocation, Diagnostics.Static_members_cannot_reference_class_type_parameters); } + return undefined; } - break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals - // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would - // trigger resolving late-bound names, which we may already be in the process of doing while we're here! - if (result = lookup(getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols, name, meaning & SymbolFlags.Type)) { - if (!isTypeParameterSymbolDeclaredInContainer(result, location)) { - // ignore type parameters not declared in this container - result = undefined; - break; - } - if (lastLocation && isStatic(lastLocation)) { - // TypeScript 1.0 spec (April 2014): 3.4.1 - // The scope of a type parameter extends over the entire declaration with which the type - // parameter list is associated, with the exception of static member declarations in classes. - if (nameNotFoundMessage) { - error(errorLocation, Diagnostics.Static_members_cannot_reference_class_type_parameters); - } - return undefined; - } + break loop; + } + if (location.kind === SyntaxKind.ClassExpression && meaning & SymbolFlags.Class) { + const className = (location as ClassExpression).name; + if (className && name === className.escapedText) { + result = location.symbol; break loop; } - if (location.kind === SyntaxKind.ClassExpression && meaning & SymbolFlags.Class) { - const className = (location as ClassExpression).name; - if (className && name === className.escapedText) { - result = location.symbol; - break loop; + } + break; + case SyntaxKind.ExpressionWithTypeArguments: + // The type parameters of a class are not in scope in the base class expression. + if (lastLocation === (location as ExpressionWithTypeArguments).expression && (location.parent as HeritageClause).token === SyntaxKind.ExtendsKeyword) { + const container = location.parent.parent; + if (isClassLike(container) && (result = lookup(getSymbolOfNode(container).members!, name, meaning & SymbolFlags.Type))) { + if (nameNotFoundMessage) { + error(errorLocation, Diagnostics.Base_class_expressions_cannot_reference_class_type_parameters); } + return undefined; } - break; - case SyntaxKind.ExpressionWithTypeArguments: - // The type parameters of a class are not in scope in the base class expression. - if (lastLocation === (location as ExpressionWithTypeArguments).expression && (location.parent as HeritageClause).token === SyntaxKind.ExtendsKeyword) { - const container = location.parent.parent; - if (isClassLike(container) && (result = lookup(getSymbolOfNode(container).members!, name, meaning & SymbolFlags.Type))) { - if (nameNotFoundMessage) { - error(errorLocation, Diagnostics.Base_class_expressions_cannot_reference_class_type_parameters); - } - return undefined; + } + break; + // It is not legal to reference a class's own type parameters from a computed property name that + // belongs to the class. For example: + // + // function foo() { return '' } + // class C { // <-- Class's own type parameter T + // [foo()]() { } // <-- Reference to T from class's own computed property + // } + // + case SyntaxKind.ComputedPropertyName: + grandparent = location.parent.parent; + if (isClassLike(grandparent) || grandparent.kind === SyntaxKind.InterfaceDeclaration) { + // A reference to this grandparent's type parameters would be an error + if (result = lookup(getSymbolOfNode(grandparent as ClassLikeDeclaration | InterfaceDeclaration).members!, name, meaning & SymbolFlags.Type)) { + if (nameNotFoundMessage) { + error(errorLocation, Diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type); } + return undefined; } + } + break; + case SyntaxKind.ArrowFunction: + // when targeting ES6 or higher there is no 'arguments' in an arrow function + // for lower compile targets the resolved symbol is used to emit an error + if (getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015) { break; - // It is not legal to reference a class's own type parameters from a computed property name that - // belongs to the class. For example: + } + // falls through + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + if (meaning & SymbolFlags.Variable && name === "arguments") { + result = argumentsSymbol; + break loop; + } + break; + case SyntaxKind.FunctionExpression: + if (meaning & SymbolFlags.Variable && name === "arguments") { + result = argumentsSymbol; + break loop; + } + + if (meaning & SymbolFlags.Function) { + const functionName = (location as FunctionExpression).name; + if (functionName && name === functionName.escapedText) { + result = location.symbol; + break loop; + } + } + break; + case SyntaxKind.Decorator: + // Decorators are resolved at the class declaration. Resolving at the parameter + // or member would result in looking up locals in the method. // - // function foo() { return '' } - // class C { // <-- Class's own type parameter T - // [foo()]() { } // <-- Reference to T from class's own computed property + // function y() {} + // class C { + // method(@y x, y) {} // <-- decorator y should be resolved at the class declaration, not the parameter. + // } + // + if (location.parent && location.parent.kind === SyntaxKind.Parameter) { + location = location.parent; + } + // + // function y() {} + // class C { + // @y method(x, y) {} // <-- decorator y should be resolved at the class declaration, not the method. // } // - case SyntaxKind.ComputedPropertyName: - grandparent = location.parent.parent; - if (isClassLike(grandparent) || grandparent.kind === SyntaxKind.InterfaceDeclaration) { - // A reference to this grandparent's type parameters would be an error - if (result = lookup(getSymbolOfNode(grandparent as ClassLikeDeclaration | InterfaceDeclaration).members!, name, meaning & SymbolFlags.Type)) { - if (nameNotFoundMessage) { - error(errorLocation, Diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type); - } - return undefined; - } - } - break; - case SyntaxKind.ArrowFunction: - // when targeting ES6 or higher there is no 'arguments' in an arrow function - // for lower compile targets the resolved symbol is used to emit an error - if (getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015) { - break; - } - // falls through - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionDeclaration: - if (meaning & SymbolFlags.Variable && name === "arguments") { - result = argumentsSymbol; - break loop; - } - break; - case SyntaxKind.FunctionExpression: - if (meaning & SymbolFlags.Variable && name === "arguments") { - result = argumentsSymbol; - break loop; - } - - if (meaning & SymbolFlags.Function) { - const functionName = (location as FunctionExpression).name; - if (functionName && name === functionName.escapedText) { - result = location.symbol; - break loop; - } - } - break; - case SyntaxKind.Decorator: - // Decorators are resolved at the class declaration. Resolving at the parameter - // or member would result in looking up locals in the method. - // - // function y() {} - // class C { - // method(@y x, y) {} // <-- decorator y should be resolved at the class declaration, not the parameter. - // } - // - if (location.parent && location.parent.kind === SyntaxKind.Parameter) { - location = location.parent; - } - // - // function y() {} - // class C { - // @y method(x, y) {} // <-- decorator y should be resolved at the class declaration, not the method. - // } - // - // class Decorators are resolved outside of the class to avoid referencing type parameters of that class. - // - // type T = number; - // declare function y(x: T): any; - // @param(1 as T) // <-- T should resolve to the type alias outside of class C - // class C {} - if (location.parent && (isClassElement(location.parent) || location.parent.kind === SyntaxKind.ClassDeclaration)) { - location = location.parent; - } - break; - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - // js type aliases do not resolve names from their host, so skip past it - const root = getJSDocRoot(location); - if (root) { - location = root.parent; - } - break; - case SyntaxKind.Parameter: - if (lastLocation && ( - lastLocation === (location as ParameterDeclaration).initializer || - lastLocation === (location as ParameterDeclaration).name && isBindingPattern(lastLocation))) { - if (!associatedDeclarationForContainingInitializerOrBindingName) { - associatedDeclarationForContainingInitializerOrBindingName = location as ParameterDeclaration; - } + // class Decorators are resolved outside of the class to avoid referencing type parameters of that class. + // + // type T = number; + // declare function y(x: T): any; + // @param(1 as T) // <-- T should resolve to the type alias outside of class C + // class C {} + if (location.parent && (isClassElement(location.parent) || location.parent.kind === SyntaxKind.ClassDeclaration)) { + location = location.parent; + } + break; + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + // js type aliases do not resolve names from their host, so skip past it + const root = getJSDocRoot(location); + if (root) { + location = root.parent; + } + break; + case SyntaxKind.Parameter: + if (lastLocation && ( + lastLocation === (location as ParameterDeclaration).initializer || + lastLocation === (location as ParameterDeclaration).name && isBindingPattern(lastLocation))) { + if (!associatedDeclarationForContainingInitializerOrBindingName) { + associatedDeclarationForContainingInitializerOrBindingName = location as ParameterDeclaration; } - break; - case SyntaxKind.BindingElement: - if (lastLocation && ( - lastLocation === (location as BindingElement).initializer || - lastLocation === (location as BindingElement).name && isBindingPattern(lastLocation))) { - if (isParameterDeclaration(location as BindingElement) && !associatedDeclarationForContainingInitializerOrBindingName) { - associatedDeclarationForContainingInitializerOrBindingName = location as BindingElement; - } + } + break; + case SyntaxKind.BindingElement: + if (lastLocation && ( + lastLocation === (location as BindingElement).initializer || + lastLocation === (location as BindingElement).name && isBindingPattern(lastLocation))) { + if (isParameterDeclaration(location as BindingElement) && !associatedDeclarationForContainingInitializerOrBindingName) { + associatedDeclarationForContainingInitializerOrBindingName = location as BindingElement; } - break; - case SyntaxKind.InferType: - if (meaning & SymbolFlags.TypeParameter) { - const parameterName = (location as InferTypeNode).typeParameter.name; - if (parameterName && name === parameterName.escapedText) { - result = (location as InferTypeNode).typeParameter.symbol; - break loop; - } + } + break; + case SyntaxKind.InferType: + if (meaning & SymbolFlags.TypeParameter) { + const parameterName = (location as InferTypeNode).typeParameter.name; + if (parameterName && name === parameterName.escapedText) { + result = (location as InferTypeNode).typeParameter.symbol; + break loop; } - break; - } - if (isSelfReferenceLocation(location)) { - lastSelfReferenceLocation = location; - } - lastLocation = location; - location = isJSDocTemplateTag(location) ? getEffectiveContainerForJSDocTemplateTag(location) || location.parent : - isJSDocParameterTag(location) || isJSDocReturnTag(location) ? getHostSignatureFromJSDoc(location) || location.parent : - location.parent; + } + break; } - - // We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`. - // If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself. - // That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used. - if (isUse && result && (!lastSelfReferenceLocation || result !== lastSelfReferenceLocation.symbol)) { - result.isReferenced! |= meaning; + if (isSelfReferenceLocation(location)) { + lastSelfReferenceLocation = location; } + lastLocation = location; + location = isJSDocTemplateTag(location) ? getEffectiveContainerForJSDocTemplateTag(location) || location.parent : + isJSDocParameterTag(location) || isJSDocReturnTag(location) ? getHostSignatureFromJSDoc(location) || location.parent : + location.parent; + } - if (!result) { - if (lastLocation) { - Debug.assert(lastLocation.kind === SyntaxKind.SourceFile); - if ((lastLocation as SourceFile).commonJsModuleIndicator && name === "exports" && meaning & lastLocation.symbol.flags) { - return lastLocation.symbol; - } - } + // We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`. + // If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself. + // That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used. + if (isUse && result && (!lastSelfReferenceLocation || result !== lastSelfReferenceLocation.symbol)) { + result.isReferenced! |= meaning; + } - if (!excludeGlobals) { - result = lookup(globals, name, meaning); + if (!result) { + if (lastLocation) { + Debug.assert(lastLocation.kind === SyntaxKind.SourceFile); + if ((lastLocation as SourceFile).commonJsModuleIndicator && name === "exports" && meaning & lastLocation.symbol.flags) { + return lastLocation.symbol; } } - if (!result) { - if (originalLocation && isInJSFile(originalLocation) && originalLocation.parent) { - if (isRequireCall(originalLocation.parent, /*checkArgumentIsStringLiteralLike*/ false)) { - return requireSymbol; - } - } - } - - // The invalid initializer error is needed in two situation: - // 1. When result is undefined, after checking for a missing "this." - // 2. When result is defined - function checkAndReportErrorForInvalidInitializer() { - if (propertyWithInvalidInitializer && !(useDefineForClassFields && getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2022)) { - // We have a match, but the reference occurred within a property initializer and the identifier also binds - // to a local variable in the constructor where the code will be emitted. Note that this is actually allowed - // with ESNext+useDefineForClassFields because the scope semantics are different. - error(errorLocation, - errorLocation && propertyWithInvalidInitializer.type && textRangeContainsPositionInclusive(propertyWithInvalidInitializer.type, errorLocation.pos) - ? Diagnostics.Type_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor - : Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor, - declarationNameToString(propertyWithInvalidInitializer.name), diagnosticName(nameArg!)); - return true; - } - return false; - } - if (!result) { - if (nameNotFoundMessage) { - addLazyDiagnostic(() => { - if (!errorLocation || - !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217 - !checkAndReportErrorForInvalidInitializer() && - !checkAndReportErrorForExtendingInterface(errorLocation) && - !checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) && - !checkAndReportErrorForExportingPrimitiveType(errorLocation, name) && - !checkAndReportErrorForUsingNamespaceAsTypeOrValue(errorLocation, name, meaning) && - !checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) && - !checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning)) { - let suggestion: Symbol | undefined; - let suggestedLib: string | undefined; - // Report missing lib first - if (nameArg) { - suggestedLib = getSuggestedLibForNonExistentName(nameArg); - if (suggestedLib) { - error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg), suggestedLib); - } - } - // then spelling suggestions - if (!suggestedLib && getSpellingSuggestions && suggestionCount < maximumSuggestionCount) { - suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning); - const isGlobalScopeAugmentationDeclaration = suggestion?.valueDeclaration && isAmbientModule(suggestion.valueDeclaration) && isGlobalScopeAugmentation(suggestion.valueDeclaration); - if (isGlobalScopeAugmentationDeclaration) { - suggestion = undefined; - } - if (suggestion) { - const suggestionName = symbolToString(suggestion); - const isUncheckedJS = isUncheckedJSSuggestion(originalLocation, suggestion, /*excludeClasses*/ false); - const message = meaning === SymbolFlags.Namespace || nameArg && typeof nameArg !== "string" && nodeIsSynthesized(nameArg) ? Diagnostics.Cannot_find_namespace_0_Did_you_mean_1 - : isUncheckedJS ? Diagnostics.Could_not_find_name_0_Did_you_mean_1 - : Diagnostics.Cannot_find_name_0_Did_you_mean_1; - const diagnostic = createError(errorLocation, message, diagnosticName(nameArg!), suggestionName); - addErrorOrSuggestion(!isUncheckedJS, diagnostic); - if (suggestion.valueDeclaration) { - addRelatedInfo( - diagnostic, - createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) - ); - } - } - } - // And then fall back to unspecified "not found" - if (!suggestion && !suggestedLib && nameArg) { - error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg)); - } - suggestionCount++; - } - }); + if (!excludeGlobals) { + result = lookup(globals, name, meaning); + } + } + if (!result) { + if (originalLocation && isInJSFile(originalLocation) && originalLocation.parent) { + if (isRequireCall(originalLocation.parent, /*checkArgumentIsStringLiteralLike*/ false)) { + return requireSymbol; } - return undefined; } - else if (nameNotFoundMessage && checkAndReportErrorForInvalidInitializer()) { - return undefined; + } + + // The invalid initializer error is needed in two situation: + // 1. When result is undefined, after checking for a missing "this." + // 2. When result is defined + function checkAndReportErrorForInvalidInitializer() { + if (propertyWithInvalidInitializer && !(useDefineForClassFields && getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2022)) { + // We have a match, but the reference occurred within a property initializer and the identifier also binds + // to a local variable in the constructor where the code will be emitted. Note that this is actually allowed + // with ESNext+useDefineForClassFields because the scope semantics are different. + error(errorLocation, + errorLocation && propertyWithInvalidInitializer.type && textRangeContainsPositionInclusive(propertyWithInvalidInitializer.type, errorLocation.pos) + ? Diagnostics.Type_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor + : Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor, + declarationNameToString(propertyWithInvalidInitializer.name), diagnosticName(nameArg!)); + return true; } + return false; + } - // Perform extra checks only if error reporting was requested + if (!result) { if (nameNotFoundMessage) { addLazyDiagnostic(() => { - // Only check for block-scoped variable if we have an error location and are looking for the - // name with variable meaning - // For example, - // declare module foo { - // interface bar {} - // } - // const foo/*1*/: foo/*2*/.bar; - // The foo at /*1*/ and /*2*/ will share same symbol with two meanings: - // block-scoped variable and namespace module. However, only when we - // try to resolve name in /*1*/ which is used in variable position, - // we want to check for block-scoped - if (errorLocation && - (meaning & SymbolFlags.BlockScopedVariable || - ((meaning & SymbolFlags.Class || meaning & SymbolFlags.Enum) && (meaning & SymbolFlags.Value) === SymbolFlags.Value))) { - const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result!); - if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable || exportOrLocalSymbol.flags & SymbolFlags.Class || exportOrLocalSymbol.flags & SymbolFlags.Enum) { - checkResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation); - } - } - - // If we're in an external module, we can't reference value symbols created from UMD export declarations - if (result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(originalLocation!.flags & NodeFlags.JSDoc)) { - const merged = getMergedSymbol(result); - if (length(merged.declarations) && every(merged.declarations, d => isNamespaceExportDeclaration(d) || isSourceFile(d) && !!d.symbol.globalExports)) { - errorOrSuggestion(!compilerOptions.allowUmdGlobalAccess, errorLocation!, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead, unescapeLeadingUnderscores(name)); - } - } - - // If we're in a parameter initializer or binding name, we can't reference the values of the parameter whose initializer we're within or parameters to the right - if (result && associatedDeclarationForContainingInitializerOrBindingName && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { - const candidate = getMergedSymbol(getLateBoundSymbol(result)); - const root = (getRootDeclaration(associatedDeclarationForContainingInitializerOrBindingName) as ParameterDeclaration); - // A parameter initializer or binding pattern initializer within a parameter cannot refer to itself - if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializerOrBindingName)) { - error(errorLocation, Diagnostics.Parameter_0_cannot_reference_itself, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name)); - } - // And it cannot refer to any declarations which come after it - else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializerOrBindingName.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) { - error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(errorLocation as Identifier)); - } - } - if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias && !(result.flags & SymbolFlags.Value) && !isValidTypeOnlyAliasUseSite(errorLocation)) { - const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(result, SymbolFlags.Value); - if (typeOnlyDeclaration) { - const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier - ? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type - : Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type; - const unescapedName = unescapeLeadingUnderscores(name); - addTypeOnlyDeclarationRelatedInfo( - error(errorLocation, message, unescapedName), - typeOnlyDeclaration, - unescapedName); + if (!errorLocation || + !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217 + !checkAndReportErrorForInvalidInitializer() && + !checkAndReportErrorForExtendingInterface(errorLocation) && + !checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) && + !checkAndReportErrorForExportingPrimitiveType(errorLocation, name) && + !checkAndReportErrorForUsingNamespaceAsTypeOrValue(errorLocation, name, meaning) && + !checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) && + !checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning)) { + let suggestion: Symbol | undefined; + let suggestedLib: string | undefined; + // Report missing lib first + if (nameArg) { + suggestedLib = getSuggestedLibForNonExistentName(nameArg); + if (suggestedLib) { + error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg), suggestedLib); + } + } + // then spelling suggestions + if (!suggestedLib && getSpellingSuggestions && suggestionCount < maximumSuggestionCount) { + suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning); + const isGlobalScopeAugmentationDeclaration = suggestion?.valueDeclaration && isAmbientModule(suggestion.valueDeclaration) && isGlobalScopeAugmentation(suggestion.valueDeclaration); + if (isGlobalScopeAugmentationDeclaration) { + suggestion = undefined; + } + if (suggestion) { + const suggestionName = symbolToString(suggestion); + const isUncheckedJS = isUncheckedJSSuggestion(originalLocation, suggestion, /*excludeClasses*/ false); + const message = meaning === SymbolFlags.Namespace || nameArg && typeof nameArg !== "string" && nodeIsSynthesized(nameArg) ? Diagnostics.Cannot_find_namespace_0_Did_you_mean_1 + : isUncheckedJS ? Diagnostics.Could_not_find_name_0_Did_you_mean_1 + : Diagnostics.Cannot_find_name_0_Did_you_mean_1; + const diagnostic = createError(errorLocation, message, diagnosticName(nameArg!), suggestionName); + addErrorOrSuggestion(!isUncheckedJS, diagnostic); + if (suggestion.valueDeclaration) { + addRelatedInfo( + diagnostic, + createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) + ); + } + } } + // And then fall back to unspecified "not found" + if (!suggestion && !suggestedLib && nameArg) { + error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg)); + } + suggestionCount++; } }); } - return result; + return undefined; } - - function addTypeOnlyDeclarationRelatedInfo(diagnostic: Diagnostic, typeOnlyDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, unescapedName: string) { - if (!typeOnlyDeclaration) return diagnostic; - return addRelatedInfo( - diagnostic, - createDiagnosticForNode( - typeOnlyDeclaration, - typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here, - unescapedName)); + else if (nameNotFoundMessage && checkAndReportErrorForInvalidInitializer()) { + return undefined; } - function getIsDeferredContext(location: Node, lastLocation: Node | undefined): boolean { - if (location.kind !== SyntaxKind.ArrowFunction && location.kind !== SyntaxKind.FunctionExpression) { - // initializers in instance property declaration of class like entities are executed in constructor and thus deferred - return isTypeQueryNode(location) || (( - isFunctionLikeDeclaration(location) || - (location.kind === SyntaxKind.PropertyDeclaration && !isStatic(location)) - ) && (!lastLocation || lastLocation !== (location as SignatureDeclaration | PropertyDeclaration).name)); // A name is evaluated within the enclosing scope - so it shouldn't count as deferred - } - if (lastLocation && lastLocation === (location as FunctionExpression | ArrowFunction).name) { - return false; - } - // generator functions and async functions are not inlined in control flow when immediately invoked - if ((location as FunctionExpression | ArrowFunction).asteriskToken || hasSyntacticModifier(location, ModifierFlags.Async)) { - return true; - } - return !getImmediatelyInvokedFunctionExpression(location); + // Perform extra checks only if error reporting was requested + if (nameNotFoundMessage) { + addLazyDiagnostic(() => { + // Only check for block-scoped variable if we have an error location and are looking for the + // name with variable meaning + // For example, + // declare module foo { + // interface bar {} + // } + // const foo/*1*/: foo/*2*/.bar; + // The foo at /*1*/ and /*2*/ will share same symbol with two meanings: + // block-scoped variable and namespace module. However, only when we + // try to resolve name in /*1*/ which is used in variable position, + // we want to check for block-scoped + if (errorLocation && + (meaning & SymbolFlags.BlockScopedVariable || + ((meaning & SymbolFlags.Class || meaning & SymbolFlags.Enum) && (meaning & SymbolFlags.Value) === SymbolFlags.Value))) { + const exportOrLocalSymbol = getExportSymbolOfValueSymbolIfExported(result!); + if (exportOrLocalSymbol.flags & SymbolFlags.BlockScopedVariable || exportOrLocalSymbol.flags & SymbolFlags.Class || exportOrLocalSymbol.flags & SymbolFlags.Enum) { + checkResolvedBlockScopedVariable(exportOrLocalSymbol, errorLocation); + } + } + + // If we're in an external module, we can't reference value symbols created from UMD export declarations + if (result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(originalLocation!.flags & NodeFlags.JSDoc)) { + const merged = getMergedSymbol(result); + if (length(merged.declarations) && every(merged.declarations, d => isNamespaceExportDeclaration(d) || isSourceFile(d) && !!d.symbol.globalExports)) { + errorOrSuggestion(!compilerOptions.allowUmdGlobalAccess, errorLocation!, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead, unescapeLeadingUnderscores(name)); + } + } + + // If we're in a parameter initializer or binding name, we can't reference the values of the parameter whose initializer we're within or parameters to the right + if (result && associatedDeclarationForContainingInitializerOrBindingName && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { + const candidate = getMergedSymbol(getLateBoundSymbol(result)); + const root = (getRootDeclaration(associatedDeclarationForContainingInitializerOrBindingName) as ParameterDeclaration); + // A parameter initializer or binding pattern initializer within a parameter cannot refer to itself + if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializerOrBindingName)) { + error(errorLocation, Diagnostics.Parameter_0_cannot_reference_itself, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name)); + } + // And it cannot refer to any declarations which come after it + else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializerOrBindingName.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) { + error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(errorLocation as Identifier)); + } + } + if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias && !(result.flags & SymbolFlags.Value) && !isValidTypeOnlyAliasUseSite(errorLocation)) { + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(result, SymbolFlags.Value); + if (typeOnlyDeclaration) { + const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier + ? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type + : Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type; + const unescapedName = unescapeLeadingUnderscores(name); + addTypeOnlyDeclarationRelatedInfo( + error(errorLocation, message, unescapedName), + typeOnlyDeclaration, + unescapedName); + } + } + }); } + return result; + } - function isSelfReferenceLocation(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ModuleDeclaration: // For `namespace N { N; }` - return true; - default: - return false; - } + function addTypeOnlyDeclarationRelatedInfo(diagnostic: Diagnostic, typeOnlyDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, unescapedName: string) { + if (!typeOnlyDeclaration) return diagnostic; + return addRelatedInfo( + diagnostic, + createDiagnosticForNode( + typeOnlyDeclaration, + typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here, + unescapedName)); + } + + function getIsDeferredContext(location: Node, lastLocation: Node | undefined): boolean { + if (location.kind !== SyntaxKind.ArrowFunction && location.kind !== SyntaxKind.FunctionExpression) { + // initializers in instance property declaration of class like entities are executed in constructor and thus deferred + return isTypeQueryNode(location) || (( + isFunctionLikeDeclaration(location) || + (location.kind === SyntaxKind.PropertyDeclaration && !isStatic(location)) + ) && (!lastLocation || lastLocation !== (location as SignatureDeclaration | PropertyDeclaration).name)); // A name is evaluated within the enclosing scope - so it shouldn't count as deferred + } + if (lastLocation && lastLocation === (location as FunctionExpression | ArrowFunction).name) { + return false; + } + // generator functions and async functions are not inlined in control flow when immediately invoked + if ((location as FunctionExpression | ArrowFunction).asteriskToken || hasSyntacticModifier(location, ModifierFlags.Async)) { + return true; } + return !getImmediatelyInvokedFunctionExpression(location); + } - function diagnosticName(nameArg: __String | Identifier | PrivateIdentifier) { - return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); + function isSelfReferenceLocation(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ModuleDeclaration: // For `namespace N { N; }` + return true; + default: + return false; } + } - function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) { - if (symbol.declarations) { - for (const decl of symbol.declarations) { - if (decl.kind === SyntaxKind.TypeParameter) { - const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; - if (parent === container) { - return !(isJSDocTemplateTag(decl.parent) && find((decl.parent.parent as JSDoc).tags, isJSDocTypeAlias)); - } + function diagnosticName(nameArg: __String | Identifier | PrivateIdentifier) { + return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); + } + + function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) { + if (symbol.declarations) { + for (const decl of symbol.declarations) { + if (decl.kind === SyntaxKind.TypeParameter) { + const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; + if (parent === container) { + return !(isJSDocTemplateTag(decl.parent) && find((decl.parent.parent as JSDoc).tags, isJSDocTypeAlias)); } } } + } + + return false; + } + function checkAndReportErrorForMissingPrefix(errorLocation: Node, name: __String, nameArg: __String | Identifier): boolean { + if (!isIdentifier(errorLocation) || errorLocation.escapedText !== name || isTypeReferenceIdentifier(errorLocation) || isInTypeQuery(errorLocation)) { return false; } - function checkAndReportErrorForMissingPrefix(errorLocation: Node, name: __String, nameArg: __String | Identifier): boolean { - if (!isIdentifier(errorLocation) || errorLocation.escapedText !== name || isTypeReferenceIdentifier(errorLocation) || isInTypeQuery(errorLocation)) { - return false; - } + const container = getThisContainer(errorLocation, /*includeArrowFunctions*/ false); + let location = container; + while (location) { + if (isClassLike(location.parent)) { + const classSymbol = getSymbolOfNode(location.parent); + if (!classSymbol) { + break; + } - const container = getThisContainer(errorLocation, /*includeArrowFunctions*/ false); - let location = container; - while (location) { - if (isClassLike(location.parent)) { - const classSymbol = getSymbolOfNode(location.parent); - if (!classSymbol) { - break; - } + // Check to see if a static member exists. + const constructorType = getTypeOfSymbol(classSymbol); + if (getPropertyOfType(constructorType, name)) { + error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0, diagnosticName(nameArg), symbolToString(classSymbol)); + return true; + } - // Check to see if a static member exists. - const constructorType = getTypeOfSymbol(classSymbol); - if (getPropertyOfType(constructorType, name)) { - error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0, diagnosticName(nameArg), symbolToString(classSymbol)); + // No static member is present. + // Check if we're in an instance method and look for a relevant instance member. + if (location === container && !isStatic(location)) { + const instanceType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType!; // TODO: GH#18217 + if (getPropertyOfType(instanceType, name)) { + error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0, diagnosticName(nameArg)); return true; } - - // No static member is present. - // Check if we're in an instance method and look for a relevant instance member. - if (location === container && !isStatic(location)) { - const instanceType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType!; // TODO: GH#18217 - if (getPropertyOfType(instanceType, name)) { - error(errorLocation, Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0, diagnosticName(nameArg)); - return true; - } - } } - - location = location.parent; } - return false; + + location = location.parent; } + return false; + } - function checkAndReportErrorForExtendingInterface(errorLocation: Node): boolean { - const expression = getEntityNameForExtendingInterface(errorLocation); - if (expression && resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)) { - error(errorLocation, Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements, getTextOfNode(expression)); - return true; - } - return false; + function checkAndReportErrorForExtendingInterface(errorLocation: Node): boolean { + const expression = getEntityNameForExtendingInterface(errorLocation); + if (expression && resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)) { + error(errorLocation, Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements, getTextOfNode(expression)); + return true; } - /** - * Climbs up parents to an ExpressionWithTypeArguments, and returns its expression, - * but returns undefined if that expression is not an EntityNameExpression. - */ - function getEntityNameForExtendingInterface(node: Node): EntityNameExpression | undefined { - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: - return node.parent ? getEntityNameForExtendingInterface(node.parent) : undefined; - case SyntaxKind.ExpressionWithTypeArguments: - if (isEntityNameExpression((node as ExpressionWithTypeArguments).expression)) { - return (node as ExpressionWithTypeArguments).expression as EntityNameExpression; - } - // falls through - default: - return undefined; - } + return false; + } + /** + * Climbs up parents to an ExpressionWithTypeArguments, and returns its expression, + * but returns undefined if that expression is not an EntityNameExpression. + */ + function getEntityNameForExtendingInterface(node: Node): EntityNameExpression | undefined { + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: + return node.parent ? getEntityNameForExtendingInterface(node.parent) : undefined; + case SyntaxKind.ExpressionWithTypeArguments: + if (isEntityNameExpression((node as ExpressionWithTypeArguments).expression)) { + return (node as ExpressionWithTypeArguments).expression as EntityNameExpression; + } + // falls through + default: + return undefined; } + } - function checkAndReportErrorForUsingTypeAsNamespace(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(errorLocation) ? SymbolFlags.Value : 0); - if (meaning === namespaceMeaning) { - const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~namespaceMeaning, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - const parent = errorLocation.parent; - if (symbol) { - if (isQualifiedName(parent)) { - Debug.assert(parent.left === errorLocation, "Should only be resolving left side of qualified name as a namespace"); - const propName = parent.right.escapedText; - const propType = getPropertyOfType(getDeclaredTypeOfSymbol(symbol), propName); - if (propType) { - error( - parent, - Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1, - unescapeLeadingUnderscores(name), - unescapeLeadingUnderscores(propName), - ); - return true; - } + function checkAndReportErrorForUsingTypeAsNamespace(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(errorLocation) ? SymbolFlags.Value : 0); + if (meaning === namespaceMeaning) { + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~namespaceMeaning, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); + const parent = errorLocation.parent; + if (symbol) { + if (isQualifiedName(parent)) { + Debug.assert(parent.left === errorLocation, "Should only be resolving left side of qualified name as a namespace"); + const propName = parent.right.escapedText; + const propType = getPropertyOfType(getDeclaredTypeOfSymbol(symbol), propName); + if (propType) { + error( + parent, + Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1, + unescapeLeadingUnderscores(name), + unescapeLeadingUnderscores(propName), + ); + return true; } - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_namespace_here, unescapeLeadingUnderscores(name)); - return true; } + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_namespace_here, unescapeLeadingUnderscores(name)); + return true; } - - return false; } - function checkAndReportErrorForUsingValueAsType(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - if (meaning & (SymbolFlags.Type & ~SymbolFlags.Namespace)) { - const symbol = resolveSymbol(resolveName(errorLocation, name, ~SymbolFlags.Type & SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - if (symbol && !(symbol.flags & SymbolFlags.Namespace)) { - error(errorLocation, Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here_Did_you_mean_typeof_0, unescapeLeadingUnderscores(name)); - return true; - } + return false; + } + + function checkAndReportErrorForUsingValueAsType(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & (SymbolFlags.Type & ~SymbolFlags.Namespace)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, ~SymbolFlags.Type & SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); + if (symbol && !(symbol.flags & SymbolFlags.Namespace)) { + error(errorLocation, Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here_Did_you_mean_typeof_0, unescapeLeadingUnderscores(name)); + return true; } - return false; } + return false; + } - function isPrimitiveTypeName(name: __String) { - return name === "any" || name === "string" || name === "number" || name === "boolean" || name === "never" || name === "unknown"; + function isPrimitiveTypeName(name: __String) { + return name === "any" || name === "string" || name === "number" || name === "boolean" || name === "never" || name === "unknown"; + } + + function checkAndReportErrorForExportingPrimitiveType(errorLocation: Node, name: __String): boolean { + if (isPrimitiveTypeName(name) && errorLocation.parent.kind === SyntaxKind.ExportSpecifier) { + error(errorLocation, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, name as string); + return true; } + return false; + } - function checkAndReportErrorForExportingPrimitiveType(errorLocation: Node, name: __String): boolean { - if (isPrimitiveTypeName(name) && errorLocation.parent.kind === SyntaxKind.ExportSpecifier) { - error(errorLocation, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, name as string); + function checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & SymbolFlags.Value) { + if (isPrimitiveTypeName(name)) { + if (isExtendedByInterface(errorLocation)) { + error(errorLocation, Diagnostics.An_interface_cannot_extend_a_primitive_type_like_0_an_interface_can_only_extend_named_types_and_classes, unescapeLeadingUnderscores(name)); + } + else { + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, unescapeLeadingUnderscores(name)); + } return true; } - return false; - } - - function checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - if (meaning & SymbolFlags.Value) { - if (isPrimitiveTypeName(name)) { - if (isExtendedByInterface(errorLocation)) { - error(errorLocation, Diagnostics.An_interface_cannot_extend_a_primitive_type_like_0_an_interface_can_only_extend_named_types_and_classes, unescapeLeadingUnderscores(name)); - } - else { - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, unescapeLeadingUnderscores(name)); - } - return true; + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); + const allFlags = symbol && getAllSymbolFlags(symbol); + if (symbol && allFlags !== undefined && !(allFlags & SymbolFlags.Value)) { + const rawName = unescapeLeadingUnderscores(name); + if (isES2015OrLaterConstructorName(name)) { + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later, rawName); } - const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Type & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - const allFlags = symbol && getAllSymbolFlags(symbol); - if (symbol && allFlags !== undefined && !(allFlags & SymbolFlags.Value)) { - const rawName = unescapeLeadingUnderscores(name); - if (isES2015OrLaterConstructorName(name)) { - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later, rawName); - } - else if (maybeMappedType(errorLocation, symbol)) { - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Did_you_mean_to_use_1_in_0, rawName, rawName === "K" ? "P" : "K"); - } - else { - error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, rawName); - } - return true; + else if (maybeMappedType(errorLocation, symbol)) { + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here_Did_you_mean_to_use_1_in_0, rawName, rawName === "K" ? "P" : "K"); } + else { + error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, rawName); + } + return true; } - return false; } + return false; + } - function isExtendedByInterface(node: Node): boolean { - const grandparent = node.parent.parent; - const parentOfGrandparent = grandparent.parent; - if(grandparent && parentOfGrandparent){ - const isExtending = isHeritageClause(grandparent) && grandparent.token === SyntaxKind.ExtendsKeyword; - const isInterface = isInterfaceDeclaration(parentOfGrandparent); - return isExtending && isInterface; - } - return false; + function isExtendedByInterface(node: Node): boolean { + const grandparent = node.parent.parent; + const parentOfGrandparent = grandparent.parent; + if(grandparent && parentOfGrandparent){ + const isExtending = isHeritageClause(grandparent) && grandparent.token === SyntaxKind.ExtendsKeyword; + const isInterface = isInterfaceDeclaration(parentOfGrandparent); + return isExtending && isInterface; } + return false; + } - function maybeMappedType(node: Node, symbol: Symbol) { - const container = findAncestor(node.parent, n => - isComputedPropertyName(n) || isPropertySignature(n) ? false : isTypeLiteralNode(n) || "quit") as TypeLiteralNode | undefined; - if (container && container.members.length === 1) { - const type = getDeclaredTypeOfSymbol(symbol); - return !!(type.flags & TypeFlags.Union) && allTypesAssignableToKind(type, TypeFlags.StringOrNumberLiteral, /*strict*/ true); - } - return false; + function maybeMappedType(node: Node, symbol: Symbol) { + const container = findAncestor(node.parent, n => + isComputedPropertyName(n) || isPropertySignature(n) ? false : isTypeLiteralNode(n) || "quit") as TypeLiteralNode | undefined; + if (container && container.members.length === 1) { + const type = getDeclaredTypeOfSymbol(symbol); + return !!(type.flags & TypeFlags.Union) && allTypesAssignableToKind(type, TypeFlags.StringOrNumberLiteral, /*strict*/ true); } + return false; + } - function isES2015OrLaterConstructorName(n: __String) { - switch (n) { - case "Promise": - case "Symbol": - case "Map": - case "WeakMap": - case "Set": - case "WeakSet": - return true; - } - return false; + function isES2015OrLaterConstructorName(n: __String) { + switch (n) { + case "Promise": + case "Symbol": + case "Map": + case "WeakMap": + case "Set": + case "WeakSet": + return true; } + return false; + } - function checkAndReportErrorForUsingNamespaceAsTypeOrValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { - if (meaning & (SymbolFlags.Value & ~SymbolFlags.Type)) { - const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - if (symbol) { - error( - errorLocation, - Diagnostics.Cannot_use_namespace_0_as_a_value, - unescapeLeadingUnderscores(name)); - return true; - } + function checkAndReportErrorForUsingNamespaceAsTypeOrValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & (SymbolFlags.Value & ~SymbolFlags.Type)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); + if (symbol) { + error( + errorLocation, + Diagnostics.Cannot_use_namespace_0_as_a_value, + unescapeLeadingUnderscores(name)); + return true; } - else if (meaning & (SymbolFlags.Type & ~SymbolFlags.Value)) { - const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Module, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); - if (symbol) { - error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_type, unescapeLeadingUnderscores(name)); - return true; - } + } + else if (meaning & (SymbolFlags.Type & ~SymbolFlags.Value)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.Module, /*nameNotFoundMessage*/undefined, /*nameArg*/ undefined, /*isUse*/ false)); + if (symbol) { + error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_type, unescapeLeadingUnderscores(name)); + return true; } - return false; } + return false; + } - function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void { - Debug.assert(!!(result.flags & SymbolFlags.BlockScopedVariable || result.flags & SymbolFlags.Class || result.flags & SymbolFlags.Enum)); - if (result.flags & (SymbolFlags.Function | SymbolFlags.FunctionScopedVariable | SymbolFlags.Assignment) && result.flags & SymbolFlags.Class) { - // constructor functions aren't block scoped - return; - } - // Block-scoped variables cannot be used before their definition - const declaration = result.declarations?.find( - d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration)); + function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void { + Debug.assert(!!(result.flags & SymbolFlags.BlockScopedVariable || result.flags & SymbolFlags.Class || result.flags & SymbolFlags.Enum)); + if (result.flags & (SymbolFlags.Function | SymbolFlags.FunctionScopedVariable | SymbolFlags.Assignment) && result.flags & SymbolFlags.Class) { + // constructor functions aren't block scoped + return; + } + // Block-scoped variables cannot be used before their definition + const declaration = result.declarations?.find( + d => isBlockOrCatchScoped(d) || isClassLike(d) || (d.kind === SyntaxKind.EnumDeclaration)); - if (declaration === undefined) return Debug.fail("checkResolvedBlockScopedVariable could not find block-scoped declaration"); + if (declaration === undefined) return Debug.fail("checkResolvedBlockScopedVariable could not find block-scoped declaration"); - if (!(declaration.flags & NodeFlags.Ambient) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) { - let diagnosticMessage; - const declarationName = declarationNameToString(getNameOfDeclaration(declaration)); - if (result.flags & SymbolFlags.BlockScopedVariable) { - diagnosticMessage = error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationName); - } - else if (result.flags & SymbolFlags.Class) { - diagnosticMessage = error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationName); - } - else if (result.flags & SymbolFlags.RegularEnum) { + if (!(declaration.flags & NodeFlags.Ambient) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) { + let diagnosticMessage; + const declarationName = declarationNameToString(getNameOfDeclaration(declaration)); + if (result.flags & SymbolFlags.BlockScopedVariable) { + diagnosticMessage = error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationName); + } + else if (result.flags & SymbolFlags.Class) { + diagnosticMessage = error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationName); + } + else if (result.flags & SymbolFlags.RegularEnum) { + diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); + } + else { + Debug.assert(!!(result.flags & SymbolFlags.ConstEnum)); + if (shouldPreserveConstEnums(compilerOptions)) { diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); } - else { - Debug.assert(!!(result.flags & SymbolFlags.ConstEnum)); - if (shouldPreserveConstEnums(compilerOptions)) { - diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); - } - } + } - if (diagnosticMessage) { - addRelatedInfo(diagnosticMessage, - createDiagnosticForNode(declaration, Diagnostics._0_is_declared_here, declarationName) - ); - } + if (diagnosticMessage) { + addRelatedInfo(diagnosticMessage, + createDiagnosticForNode(declaration, Diagnostics._0_is_declared_here, declarationName) + ); } } + } - /* Starting from 'initial' node walk up the parent chain until 'stopAt' node is reached. - * If at any point current node is equal to 'parent' node - return true. - * If current node is an IIFE, continue walking up. - * Return false if 'stopAt' node is reached or isFunctionLike(current) === true. - */ - function isSameScopeDescendentOf(initial: Node, parent: Node | undefined, stopAt: Node): boolean { - return !!parent && !!findAncestor(initial, n => n === parent - || (n === stopAt || isFunctionLike(n) && (!getImmediatelyInvokedFunctionExpression(n) || isAsyncFunction(n)) ? "quit" : false)); - } + /* Starting from 'initial' node walk up the parent chain until 'stopAt' node is reached. + * If at any point current node is equal to 'parent' node - return true. + * If current node is an IIFE, continue walking up. + * Return false if 'stopAt' node is reached or isFunctionLike(current) === true. + */ + function isSameScopeDescendentOf(initial: Node, parent: Node | undefined, stopAt: Node): boolean { + return !!parent && !!findAncestor(initial, n => n === parent + || (n === stopAt || isFunctionLike(n) && (!getImmediatelyInvokedFunctionExpression(n) || isAsyncFunction(n)) ? "quit" : false)); + } - function getAnyImportSyntax(node: Node): AnyImportSyntax | undefined { - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - return node as ImportEqualsDeclaration; - case SyntaxKind.ImportClause: - return (node as ImportClause).parent; - case SyntaxKind.NamespaceImport: - return (node as NamespaceImport).parent.parent; - case SyntaxKind.ImportSpecifier: - return (node as ImportSpecifier).parent.parent.parent; - default: - return undefined; - } + function getAnyImportSyntax(node: Node): AnyImportSyntax | undefined { + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return node as ImportEqualsDeclaration; + case SyntaxKind.ImportClause: + return (node as ImportClause).parent; + case SyntaxKind.NamespaceImport: + return (node as NamespaceImport).parent.parent; + case SyntaxKind.ImportSpecifier: + return (node as ImportSpecifier).parent.parent.parent; + default: + return undefined; } + } - function getDeclarationOfAliasSymbol(symbol: Symbol): Declaration | undefined { - return symbol.declarations && findLast(symbol.declarations, isAliasSymbolDeclaration); - } + function getDeclarationOfAliasSymbol(symbol: Symbol): Declaration | undefined { + return symbol.declarations && findLast(symbol.declarations, isAliasSymbolDeclaration); + } - /** - * An alias symbol is created by one of the following declarations: - * import = ... - * import from ... - * import * as from ... - * import { x as } from ... - * export { x as } from ... - * export * as ns from ... - * export = - * export default - * module.exports = - * {} - * {name: } - * const { x } = require ... - */ - function isAliasSymbolDeclaration(node: Node): boolean { - return node.kind === SyntaxKind.ImportEqualsDeclaration - || node.kind === SyntaxKind.NamespaceExportDeclaration - || node.kind === SyntaxKind.ImportClause && !!(node as ImportClause).name - || node.kind === SyntaxKind.NamespaceImport - || node.kind === SyntaxKind.NamespaceExport - || node.kind === SyntaxKind.ImportSpecifier - || node.kind === SyntaxKind.ExportSpecifier - || node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node as ExportAssignment) - || isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node) - || isAccessExpression(node) - && isBinaryExpression(node.parent) - && node.parent.left === node - && node.parent.operatorToken.kind === SyntaxKind.EqualsToken - && isAliasableOrJsExpression(node.parent.right) - || node.kind === SyntaxKind.ShorthandPropertyAssignment - || node.kind === SyntaxKind.PropertyAssignment && isAliasableOrJsExpression((node as PropertyAssignment).initializer) - || node.kind === SyntaxKind.VariableDeclaration && isVariableDeclarationInitializedToBareOrAccessedRequire(node) - || node.kind === SyntaxKind.BindingElement && isVariableDeclarationInitializedToBareOrAccessedRequire(node.parent.parent); - } - - function isAliasableOrJsExpression(e: Expression) { - return isAliasableExpression(e) || isFunctionExpression(e) && isJSConstructor(e); - } - - function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration | VariableDeclaration, dontResolveAlias: boolean): Symbol | undefined { - const commonJSPropertyAccess = getCommonJSPropertyAccess(node); - if (commonJSPropertyAccess) { - const name = (getLeftmostAccessExpression(commonJSPropertyAccess.expression) as CallExpression).arguments[0] as StringLiteral; - return isIdentifier(commonJSPropertyAccess.name) - ? resolveSymbol(getPropertyOfType(resolveExternalModuleTypeByLiteral(name), commonJSPropertyAccess.name.escapedText)) - : undefined; - } - if (isVariableDeclaration(node) || node.moduleReference.kind === SyntaxKind.ExternalModuleReference) { - const immediate = resolveExternalModuleName( - node, - getExternalModuleRequireArgument(node) || getExternalModuleImportEqualsDeclarationExpression(node)); - const resolved = resolveExternalModuleSymbol(immediate); - markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); - return resolved; - } - const resolved = getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); - checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node, resolved); - return resolved; - } + /** + * An alias symbol is created by one of the following declarations: + * import = ... + * import from ... + * import * as from ... + * import { x as } from ... + * export { x as } from ... + * export * as ns from ... + * export = + * export default + * module.exports = + * {} + * {name: } + * const { x } = require ... + */ + function isAliasSymbolDeclaration(node: Node): boolean { + return node.kind === SyntaxKind.ImportEqualsDeclaration + || node.kind === SyntaxKind.NamespaceExportDeclaration + || node.kind === SyntaxKind.ImportClause && !!(node as ImportClause).name + || node.kind === SyntaxKind.NamespaceImport + || node.kind === SyntaxKind.NamespaceExport + || node.kind === SyntaxKind.ImportSpecifier + || node.kind === SyntaxKind.ExportSpecifier + || node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node as ExportAssignment) + || isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node) + || isAccessExpression(node) + && isBinaryExpression(node.parent) + && node.parent.left === node + && node.parent.operatorToken.kind === SyntaxKind.EqualsToken + && isAliasableOrJsExpression(node.parent.right) + || node.kind === SyntaxKind.ShorthandPropertyAssignment + || node.kind === SyntaxKind.PropertyAssignment && isAliasableOrJsExpression((node as PropertyAssignment).initializer) + || node.kind === SyntaxKind.VariableDeclaration && isVariableDeclarationInitializedToBareOrAccessedRequire(node) + || node.kind === SyntaxKind.BindingElement && isVariableDeclarationInitializedToBareOrAccessedRequire(node.parent.parent); + } - function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { - if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) { - const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!; - const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier; - const message = isExport - ? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type - : Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type; - const relatedMessage = isExport - ? Diagnostics._0_was_exported_here - : Diagnostics._0_was_imported_here; + function isAliasableOrJsExpression(e: Expression) { + return isAliasableExpression(e) || isFunctionExpression(e) && isJSConstructor(e); + } - const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText); - addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); - } + function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration | VariableDeclaration, dontResolveAlias: boolean): Symbol | undefined { + const commonJSPropertyAccess = getCommonJSPropertyAccess(node); + if (commonJSPropertyAccess) { + const name = (getLeftmostAccessExpression(commonJSPropertyAccess.expression) as CallExpression).arguments[0] as StringLiteral; + return isIdentifier(commonJSPropertyAccess.name) + ? resolveSymbol(getPropertyOfType(resolveExternalModuleTypeByLiteral(name), commonJSPropertyAccess.name.escapedText)) + : undefined; } - - function resolveExportByName(moduleSymbol: Symbol, name: __String, sourceNode: TypeOnlyCompatibleAliasDeclaration | undefined, dontResolveAlias: boolean) { - const exportValue = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); - const exportSymbol = exportValue ? getPropertyOfType(getTypeOfSymbol(exportValue), name) : moduleSymbol.exports!.get(name); - const resolved = resolveSymbol(exportSymbol, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, /*overwriteEmpty*/ false); + if (isVariableDeclaration(node) || node.moduleReference.kind === SyntaxKind.ExternalModuleReference) { + const immediate = resolveExternalModuleName( + node, + getExternalModuleRequireArgument(node) || getExternalModuleImportEqualsDeclarationExpression(node)); + const resolved = resolveExternalModuleSymbol(immediate); + markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); return resolved; } + const resolved = getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); + checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node, resolved); + return resolved; + } - function isSyntacticDefault(node: Node) { - return ((isExportAssignment(node) && !node.isExportEquals) || hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node)); - } + function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { + if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) { + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!; + const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier; + const message = isExport + ? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type + : Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type; + const relatedMessage = isExport + ? Diagnostics._0_was_exported_here + : Diagnostics._0_was_imported_here; - function getUsageModeForExpression(usage: Expression) { - return isStringLiteralLike(usage) ? getModeForUsageLocation(getSourceFileOfNode(usage), usage) : undefined; + const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText); + addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); } + } - function isESMFormatImportImportingCommonjsFormatFile(usageMode: SourceFile["impliedNodeFormat"], targetMode: SourceFile["impliedNodeFormat"]) { - return usageMode === ModuleKind.ESNext && targetMode === ModuleKind.CommonJS; - } + function resolveExportByName(moduleSymbol: Symbol, name: __String, sourceNode: TypeOnlyCompatibleAliasDeclaration | undefined, dontResolveAlias: boolean) { + const exportValue = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); + const exportSymbol = exportValue ? getPropertyOfType(getTypeOfSymbol(exportValue), name) : moduleSymbol.exports!.get(name); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, /*overwriteEmpty*/ false); + return resolved; + } - function isOnlyImportedAsDefault(usage: Expression) { - const usageMode = getUsageModeForExpression(usage); - return usageMode === ModuleKind.ESNext && endsWith((usage as StringLiteralLike).text, Extension.Json); - } + function isSyntacticDefault(node: Node) { + return ((isExportAssignment(node) && !node.isExportEquals) || hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node)); + } - function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean, usage: Expression) { - const usageMode = file && getUsageModeForExpression(usage); - if (file && usageMode !== undefined) { - const result = isESMFormatImportImportingCommonjsFormatFile(usageMode, file.impliedNodeFormat); - if (usageMode === ModuleKind.ESNext || result) { - return result; - } - // fallthrough on cjs usages so we imply defaults for interop'd imports, too + function getUsageModeForExpression(usage: Expression) { + return isStringLiteralLike(usage) ? getModeForUsageLocation(getSourceFileOfNode(usage), usage) : undefined; + } + + function isESMFormatImportImportingCommonjsFormatFile(usageMode: SourceFile["impliedNodeFormat"], targetMode: SourceFile["impliedNodeFormat"]) { + return usageMode === ModuleKind.ESNext && targetMode === ModuleKind.CommonJS; + } + + function isOnlyImportedAsDefault(usage: Expression) { + const usageMode = getUsageModeForExpression(usage); + return usageMode === ModuleKind.ESNext && endsWith((usage as StringLiteralLike).text, Extension.Json); + } + + function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean, usage: Expression) { + const usageMode = file && getUsageModeForExpression(usage); + if (file && usageMode !== undefined) { + const result = isESMFormatImportImportingCommonjsFormatFile(usageMode, file.impliedNodeFormat); + if (usageMode === ModuleKind.ESNext || result) { + return result; } - if (!allowSyntheticDefaultImports) { + // fallthrough on cjs usages so we imply defaults for interop'd imports, too + } + if (!allowSyntheticDefaultImports) { + return false; + } + // Declaration files (and ambient modules) + if (!file || file.isDeclarationFile) { + // Definitely cannot have a synthetic default if they have a syntactic default member specified + const defaultExportSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, /*sourceNode*/ undefined, /*dontResolveAlias*/ true); // Dont resolve alias because we want the immediately exported symbol's declaration + if (defaultExportSymbol && some(defaultExportSymbol.declarations, isSyntacticDefault)) { return false; } - // Declaration files (and ambient modules) - if (!file || file.isDeclarationFile) { - // Definitely cannot have a synthetic default if they have a syntactic default member specified - const defaultExportSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, /*sourceNode*/ undefined, /*dontResolveAlias*/ true); // Dont resolve alias because we want the immediately exported symbol's declaration - if (defaultExportSymbol && some(defaultExportSymbol.declarations, isSyntacticDefault)) { - return false; - } - // It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member - // So we check a bit more, - if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias)) { - // If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code), - // it definitely is a module and does not have a synthetic default - return false; - } - // There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set - // Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member - // as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm - return true; - } - // TypeScript files never have a synthetic default (as they are always emitted with an __esModule marker) _unless_ they contain an export= statement - if (!isSourceFileJS(file)) { - return hasExportAssignmentSymbol(moduleSymbol); + // It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member + // So we check a bit more, + if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias)) { + // If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code), + // it definitely is a module and does not have a synthetic default + return false; } - // JS files have a synthetic default if they do not contain ES2015+ module syntax (export = is not valid in js) _and_ do not have an __esModule marker - return typeof file.externalModuleIndicator !== "object" && !resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias); + // There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set + // Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member + // as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm + return true; + } + // TypeScript files never have a synthetic default (as they are always emitted with an __esModule marker) _unless_ they contain an export= statement + if (!isSourceFileJS(file)) { + return hasExportAssignmentSymbol(moduleSymbol); } + // JS files have a synthetic default if they do not contain ES2015+ module syntax (export = is not valid in js) _and_ do not have an __esModule marker + return typeof file.externalModuleIndicator !== "object" && !resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias); + } - function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol | undefined { - const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier); - if (moduleSymbol) { - return getTargetofModuleDefault(moduleSymbol, node, dontResolveAlias); - } + function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol | undefined { + const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier); + if (moduleSymbol) { + return getTargetofModuleDefault(moduleSymbol, node, dontResolveAlias); } + } - function getTargetofModuleDefault(moduleSymbol: Symbol, node: ImportClause | ImportOrExportSpecifier, dontResolveAlias: boolean) { - let exportDefaultSymbol: Symbol | undefined; - if (isShorthandAmbientModuleSymbol(moduleSymbol)) { - exportDefaultSymbol = moduleSymbol; - } - else { - exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, node, dontResolveAlias); - } + function getTargetofModuleDefault(moduleSymbol: Symbol, node: ImportClause | ImportOrExportSpecifier, dontResolveAlias: boolean) { + let exportDefaultSymbol: Symbol | undefined; + if (isShorthandAmbientModuleSymbol(moduleSymbol)) { + exportDefaultSymbol = moduleSymbol; + } + else { + exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, node, dontResolveAlias); + } - const file = moduleSymbol.declarations?.find(isSourceFile); - const specifier = getModuleSpecifierForImportOrExport(node); - if (!specifier) { - return exportDefaultSymbol; - } - const hasDefaultOnly = isOnlyImportedAsDefault(specifier); - const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, specifier); - if (!exportDefaultSymbol && !hasSyntheticDefault && !hasDefaultOnly) { - if (hasExportAssignmentSymbol(moduleSymbol)) { - const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop"; - const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); - const exportAssignment = exportEqualsSymbol!.valueDeclaration; - const err = error(node.name, Diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, symbolToString(moduleSymbol), compilerOptionName); - - if (exportAssignment) { - addRelatedInfo(err, createDiagnosticForNode( - exportAssignment, - Diagnostics.This_module_is_declared_with_export_and_can_only_be_used_with_a_default_import_when_using_the_0_flag, - compilerOptionName - )); - } - } - else if (isImportClause(node)) { - reportNonDefaultExport(moduleSymbol, node); - } - else { - errorNoModuleMemberSymbol(moduleSymbol, moduleSymbol, node, isImportOrExportSpecifier(node) && node.propertyName || node.name); - } - } - else if (hasSyntheticDefault || hasDefaultOnly) { - // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present - const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false); - return resolved; - } - markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteTypeOnly*/ false); + const file = moduleSymbol.declarations?.find(isSourceFile); + const specifier = getModuleSpecifierForImportOrExport(node); + if (!specifier) { return exportDefaultSymbol; } - - function getModuleSpecifierForImportOrExport(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportOrExportSpecifier): Expression | undefined { - switch (node.kind) { - case SyntaxKind.ImportClause: return node.parent.moduleSpecifier; - case SyntaxKind.ImportEqualsDeclaration: return isExternalModuleReference(node.moduleReference) ? node.moduleReference.expression : undefined; - case SyntaxKind.NamespaceImport: return node.parent.parent.moduleSpecifier; - case SyntaxKind.ImportSpecifier: return node.parent.parent.parent.moduleSpecifier; - case SyntaxKind.ExportSpecifier: return node.parent.parent.moduleSpecifier; - default: return Debug.assertNever(node); + const hasDefaultOnly = isOnlyImportedAsDefault(specifier); + const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, specifier); + if (!exportDefaultSymbol && !hasSyntheticDefault && !hasDefaultOnly) { + if (hasExportAssignmentSymbol(moduleSymbol)) { + const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop"; + const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); + const exportAssignment = exportEqualsSymbol!.valueDeclaration; + const err = error(node.name, Diagnostics.Module_0_can_only_be_default_imported_using_the_1_flag, symbolToString(moduleSymbol), compilerOptionName); + + if (exportAssignment) { + addRelatedInfo(err, createDiagnosticForNode( + exportAssignment, + Diagnostics.This_module_is_declared_with_export_and_can_only_be_used_with_a_default_import_when_using_the_0_flag, + compilerOptionName + )); + } } - } - - function reportNonDefaultExport(moduleSymbol: Symbol, node: ImportClause) { - if (moduleSymbol.exports?.has(node.symbol.escapedName)) { - error( - node.name, - Diagnostics.Module_0_has_no_default_export_Did_you_mean_to_use_import_1_from_0_instead, - symbolToString(moduleSymbol), - symbolToString(node.symbol), - ); + else if (isImportClause(node)) { + reportNonDefaultExport(moduleSymbol, node); } else { - const diagnostic = error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); - const exportStar = moduleSymbol.exports?.get(InternalSymbolName.ExportStar); - if (exportStar) { - const defaultExport = exportStar.declarations?.find(decl => !!( - isExportDeclaration(decl) && decl.moduleSpecifier && - resolveExternalModuleName(decl, decl.moduleSpecifier)?.exports?.has(InternalSymbolName.Default) - )); - if (defaultExport) { - addRelatedInfo(diagnostic, createDiagnosticForNode(defaultExport, Diagnostics.export_Asterisk_does_not_re_export_a_default)); - } - } + errorNoModuleMemberSymbol(moduleSymbol, moduleSymbol, node, isImportOrExportSpecifier(node) && node.propertyName || node.name); } } - - function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { - const moduleSpecifier = node.parent.parent.moduleSpecifier; - const immediate = resolveExternalModuleName(node, moduleSpecifier); - const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); - markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); - return resolved; - } - - function getTargetOfNamespaceExport(node: NamespaceExport, dontResolveAlias: boolean): Symbol | undefined { - const moduleSpecifier = node.parent.moduleSpecifier; - const immediate = moduleSpecifier && resolveExternalModuleName(node, moduleSpecifier); - const resolved = moduleSpecifier && resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); - markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); + else if (hasSyntheticDefault || hasDefaultOnly) { + // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present + const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false); return resolved; } + markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteTypeOnly*/ false); + return exportDefaultSymbol; + } - // This function creates a synthetic symbol that combines the value side of one symbol with the - // type/namespace side of another symbol. Consider this example: - // - // declare module graphics { - // interface Point { - // x: number; - // y: number; - // } - // } - // declare var graphics: { - // Point: new (x: number, y: number) => graphics.Point; - // } - // declare module "graphics" { - // export = graphics; - // } - // - // An 'import { Point } from "graphics"' needs to create a symbol that combines the value side 'Point' - // property with the type/namespace side interface 'Point'. - function combineValueAndTypeSymbols(valueSymbol: Symbol, typeSymbol: Symbol): Symbol { - if (valueSymbol === unknownSymbol && typeSymbol === unknownSymbol) { - return unknownSymbol; - } - if (valueSymbol.flags & (SymbolFlags.Type | SymbolFlags.Namespace)) { - return valueSymbol; - } - const result = createSymbol(valueSymbol.flags | typeSymbol.flags, valueSymbol.escapedName); - result.declarations = deduplicate(concatenate(valueSymbol.declarations, typeSymbol.declarations), equateValues); - result.parent = valueSymbol.parent || typeSymbol.parent; - if (valueSymbol.valueDeclaration) result.valueDeclaration = valueSymbol.valueDeclaration; - if (typeSymbol.members) result.members = new Map(typeSymbol.members); - if (valueSymbol.exports) result.exports = new Map(valueSymbol.exports); - return result; + function getModuleSpecifierForImportOrExport(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportOrExportSpecifier): Expression | undefined { + switch (node.kind) { + case SyntaxKind.ImportClause: return node.parent.moduleSpecifier; + case SyntaxKind.ImportEqualsDeclaration: return isExternalModuleReference(node.moduleReference) ? node.moduleReference.expression : undefined; + case SyntaxKind.NamespaceImport: return node.parent.parent.moduleSpecifier; + case SyntaxKind.ImportSpecifier: return node.parent.parent.parent.moduleSpecifier; + case SyntaxKind.ExportSpecifier: return node.parent.parent.moduleSpecifier; + default: return Debug.assertNever(node); } + } - function getExportOfModule(symbol: Symbol, name: Identifier, specifier: Declaration, dontResolveAlias: boolean): Symbol | undefined { - if (symbol.flags & SymbolFlags.Module) { - const exportSymbol = getExportsOfSymbol(symbol).get(name.escapedText); - const resolved = resolveSymbol(exportSymbol, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false); - return resolved; - } + function reportNonDefaultExport(moduleSymbol: Symbol, node: ImportClause) { + if (moduleSymbol.exports?.has(node.symbol.escapedName)) { + error( + node.name, + Diagnostics.Module_0_has_no_default_export_Did_you_mean_to_use_import_1_from_0_instead, + symbolToString(moduleSymbol), + symbolToString(node.symbol), + ); } - - function getPropertyOfVariable(symbol: Symbol, name: __String): Symbol | undefined { - if (symbol.flags & SymbolFlags.Variable) { - const typeAnnotation = (symbol.valueDeclaration as VariableDeclaration).type; - if (typeAnnotation) { - return resolveSymbol(getPropertyOfType(getTypeFromTypeNode(typeAnnotation), name)); + else { + const diagnostic = error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); + const exportStar = moduleSymbol.exports?.get(InternalSymbolName.ExportStar); + if (exportStar) { + const defaultExport = exportStar.declarations?.find(decl => !!( + isExportDeclaration(decl) && decl.moduleSpecifier && + resolveExternalModuleName(decl, decl.moduleSpecifier)?.exports?.has(InternalSymbolName.Default) + )); + if (defaultExport) { + addRelatedInfo(diagnostic, createDiagnosticForNode(defaultExport, Diagnostics.export_Asterisk_does_not_re_export_a_default)); } } } + } - function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined { - const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration).moduleSpecifier!; - const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217 - const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name; - if (!isIdentifier(name)) { - return undefined; - } - const suppressInteropError = name.escapedText === InternalSymbolName.Default && !!(compilerOptions.allowSyntheticDefaultImports || getESModuleInterop(compilerOptions)); - const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, /*dontResolveAlias*/ false, suppressInteropError); - if (targetSymbol) { - if (name.escapedText) { - if (isShorthandAmbientModuleSymbol(moduleSymbol)) { - return moduleSymbol; - } + function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { + const moduleSpecifier = node.parent.parent.moduleSpecifier; + const immediate = resolveExternalModuleName(node, moduleSpecifier); + const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); + markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); + return resolved; + } - let symbolFromVariable: Symbol | undefined; - // First check if module was specified with "export=". If so, get the member from the resolved type - if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get(InternalSymbolName.ExportEquals)) { - symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText, /*skipObjectFunctionPropertyAugment*/ true); - } - else { - symbolFromVariable = getPropertyOfVariable(targetSymbol, name.escapedText); - } - // if symbolFromVariable is export - get its final target - symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); + function getTargetOfNamespaceExport(node: NamespaceExport, dontResolveAlias: boolean): Symbol | undefined { + const moduleSpecifier = node.parent.moduleSpecifier; + const immediate = moduleSpecifier && resolveExternalModuleName(node, moduleSpecifier); + const resolved = moduleSpecifier && resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressUsageError*/ false); + markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); + return resolved; + } - let symbolFromModule = getExportOfModule(targetSymbol, name, specifier, dontResolveAlias); - if (symbolFromModule === undefined && name.escapedText === InternalSymbolName.Default) { - const file = moduleSymbol.declarations?.find(isSourceFile); - if (isOnlyImportedAsDefault(moduleSpecifier) || canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) { - symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); - } - } + // This function creates a synthetic symbol that combines the value side of one symbol with the + // type/namespace side of another symbol. Consider this example: + // + // declare module graphics { + // interface Point { + // x: number; + // y: number; + // } + // } + // declare var graphics: { + // Point: new (x: number, y: number) => graphics.Point; + // } + // declare module "graphics" { + // export = graphics; + // } + // + // An 'import { Point } from "graphics"' needs to create a symbol that combines the value side 'Point' + // property with the type/namespace side interface 'Point'. + function combineValueAndTypeSymbols(valueSymbol: Symbol, typeSymbol: Symbol): Symbol { + if (valueSymbol === unknownSymbol && typeSymbol === unknownSymbol) { + return unknownSymbol; + } + if (valueSymbol.flags & (SymbolFlags.Type | SymbolFlags.Namespace)) { + return valueSymbol; + } + const result = createSymbol(valueSymbol.flags | typeSymbol.flags, valueSymbol.escapedName); + result.declarations = deduplicate(concatenate(valueSymbol.declarations, typeSymbol.declarations), equateValues); + result.parent = valueSymbol.parent || typeSymbol.parent; + if (valueSymbol.valueDeclaration) result.valueDeclaration = valueSymbol.valueDeclaration; + if (typeSymbol.members) result.members = new Map(typeSymbol.members); + if (valueSymbol.exports) result.exports = new Map(valueSymbol.exports); + return result; + } - const symbol = symbolFromModule && symbolFromVariable && symbolFromModule !== symbolFromVariable ? - combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) : - symbolFromModule || symbolFromVariable; - if (!symbol) { - errorNoModuleMemberSymbol(moduleSymbol, targetSymbol, node, name); - } - return symbol; - } + function getExportOfModule(symbol: Symbol, name: Identifier, specifier: Declaration, dontResolveAlias: boolean): Symbol | undefined { + if (symbol.flags & SymbolFlags.Module) { + const exportSymbol = getExportsOfSymbol(symbol).get(name.escapedText); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false); + return resolved; + } + } + + function getPropertyOfVariable(symbol: Symbol, name: __String): Symbol | undefined { + if (symbol.flags & SymbolFlags.Variable) { + const typeAnnotation = (symbol.valueDeclaration as VariableDeclaration).type; + if (typeAnnotation) { + return resolveSymbol(getPropertyOfType(getTypeFromTypeNode(typeAnnotation), name)); } } + } - function errorNoModuleMemberSymbol(moduleSymbol: Symbol, targetSymbol: Symbol, node: Node, name: Identifier) { - const moduleName = getFullyQualifiedName(moduleSymbol, node); - const declarationName = declarationNameToString(name); - const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol); - if (suggestion !== undefined) { - const suggestionName = symbolToString(suggestion); - const diagnostic = error(name, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, moduleName, declarationName, suggestionName); - if (suggestion.valueDeclaration) { - addRelatedInfo(diagnostic, - createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) - ); + function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined { + const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration).moduleSpecifier!; + const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217 + const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name; + if (!isIdentifier(name)) { + return undefined; + } + const suppressInteropError = name.escapedText === InternalSymbolName.Default && !!(compilerOptions.allowSyntheticDefaultImports || getESModuleInterop(compilerOptions)); + const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, /*dontResolveAlias*/ false, suppressInteropError); + if (targetSymbol) { + if (name.escapedText) { + if (isShorthandAmbientModuleSymbol(moduleSymbol)) { + return moduleSymbol; } - } - else { - if (moduleSymbol.exports?.has(InternalSymbolName.Default)) { - error( - name, - Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_to_use_import_1_from_0_instead, - moduleName, - declarationName - ); + + let symbolFromVariable: Symbol | undefined; + // First check if module was specified with "export=". If so, get the member from the resolved type + if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get(InternalSymbolName.ExportEquals)) { + symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText, /*skipObjectFunctionPropertyAugment*/ true); } else { - reportNonExportedMember(node, name, declarationName, moduleSymbol, moduleName); + symbolFromVariable = getPropertyOfVariable(targetSymbol, name.escapedText); } - } - } + // if symbolFromVariable is export - get its final target + symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); - function reportNonExportedMember(node: Node, name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { - const localSymbol = moduleSymbol.valueDeclaration?.locals?.get(name.escapedText); - const exports = moduleSymbol.exports; - if (localSymbol) { - const exportedEqualsSymbol = exports?.get(InternalSymbolName.ExportEquals); - if (exportedEqualsSymbol) { - getSymbolIfSameReference(exportedEqualsSymbol, localSymbol) ? reportInvalidImportEqualsExportMember(node, name, declarationName, moduleName) : - error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); - } - else { - const exportedSymbol = exports ? find(symbolsToArray(exports), symbol => !!getSymbolIfSameReference(symbol, localSymbol)) : undefined; - const diagnostic = exportedSymbol ? error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_exported_as_2, moduleName, declarationName, symbolToString(exportedSymbol)) : - error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_not_exported, moduleName, declarationName); - if (localSymbol.declarations) { - addRelatedInfo(diagnostic, - ...map(localSymbol.declarations, (decl, index) => - createDiagnosticForNode(decl, index === 0 ? Diagnostics._0_is_declared_here : Diagnostics.and_here, declarationName))); + let symbolFromModule = getExportOfModule(targetSymbol, name, specifier, dontResolveAlias); + if (symbolFromModule === undefined && name.escapedText === InternalSymbolName.Default) { + const file = moduleSymbol.declarations?.find(isSourceFile); + if (isOnlyImportedAsDefault(moduleSpecifier) || canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) { + symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); } } + + const symbol = symbolFromModule && symbolFromVariable && symbolFromModule !== symbolFromVariable ? + combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) : + symbolFromModule || symbolFromVariable; + if (!symbol) { + errorNoModuleMemberSymbol(moduleSymbol, targetSymbol, node, name); + } + return symbol; + } + } + } + + function errorNoModuleMemberSymbol(moduleSymbol: Symbol, targetSymbol: Symbol, node: Node, name: Identifier) { + const moduleName = getFullyQualifiedName(moduleSymbol, node); + const declarationName = declarationNameToString(name); + const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol); + if (suggestion !== undefined) { + const suggestionName = symbolToString(suggestion); + const diagnostic = error(name, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, moduleName, declarationName, suggestionName); + if (suggestion.valueDeclaration) { + addRelatedInfo(diagnostic, + createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) + ); + } + } + else { + if (moduleSymbol.exports?.has(InternalSymbolName.Default)) { + error( + name, + Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_to_use_import_1_from_0_instead, + moduleName, + declarationName + ); } else { - error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); + reportNonExportedMember(node, name, declarationName, moduleSymbol, moduleName); } } + } - function reportInvalidImportEqualsExportMember(node: Node, name: Identifier, declarationName: string, moduleName: string) { - if (moduleKind >= ModuleKind.ES2015) { - const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_default_import : - Diagnostics._0_can_only_be_imported_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; - error(name, message, declarationName); + function reportNonExportedMember(node: Node, name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { + const localSymbol = moduleSymbol.valueDeclaration?.locals?.get(name.escapedText); + const exports = moduleSymbol.exports; + if (localSymbol) { + const exportedEqualsSymbol = exports?.get(InternalSymbolName.ExportEquals); + if (exportedEqualsSymbol) { + getSymbolIfSameReference(exportedEqualsSymbol, localSymbol) ? reportInvalidImportEqualsExportMember(node, name, declarationName, moduleName) : + error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); } else { - if (isInJSFile(node)) { - const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_require_call_or_by_using_a_default_import : - Diagnostics._0_can_only_be_imported_by_using_a_require_call_or_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; - error(name, message, declarationName); - } - else { - const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_import_1_require_2_or_a_default_import : - Diagnostics._0_can_only_be_imported_by_using_import_1_require_2_or_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; - error(name, message, declarationName, declarationName, moduleName); + const exportedSymbol = exports ? find(symbolsToArray(exports), symbol => !!getSymbolIfSameReference(symbol, localSymbol)) : undefined; + const diagnostic = exportedSymbol ? error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_exported_as_2, moduleName, declarationName, symbolToString(exportedSymbol)) : + error(name, Diagnostics.Module_0_declares_1_locally_but_it_is_not_exported, moduleName, declarationName); + if (localSymbol.declarations) { + addRelatedInfo(diagnostic, + ...map(localSymbol.declarations, (decl, index) => + createDiagnosticForNode(decl, index === 0 ? Diagnostics._0_is_declared_here : Diagnostics.and_here, declarationName))); } } } + else { + error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); + } + } - function getTargetOfImportSpecifier(node: ImportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined { - if (isImportSpecifier(node) && idText(node.propertyName || node.name) === InternalSymbolName.Default) { - const specifier = getModuleSpecifierForImportOrExport(node); - const moduleSymbol = specifier && resolveExternalModuleName(node, specifier); - if (moduleSymbol) { - return getTargetofModuleDefault(moduleSymbol, node, dontResolveAlias); - } + function reportInvalidImportEqualsExportMember(node: Node, name: Identifier, declarationName: string, moduleName: string) { + if (moduleKind >= ModuleKind.ES2015) { + const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_default_import : + Diagnostics._0_can_only_be_imported_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; + error(name, message, declarationName); + } + else { + if (isInJSFile(node)) { + const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_a_require_call_or_by_using_a_default_import : + Diagnostics._0_can_only_be_imported_by_using_a_require_call_or_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; + error(name, message, declarationName); } - const root = isBindingElement(node) ? getRootDeclaration(node) as VariableDeclaration : node.parent.parent.parent; - const commonJSPropertyAccess = getCommonJSPropertyAccess(root); - const resolved = getExternalModuleMember(root, commonJSPropertyAccess || node, dontResolveAlias); - const name = node.propertyName || node.name; - if (commonJSPropertyAccess && resolved && isIdentifier(name)) { - return resolveSymbol(getPropertyOfType(getTypeOfSymbol(resolved), name.escapedText), dontResolveAlias); + else { + const message = getESModuleInterop(compilerOptions) ? Diagnostics._0_can_only_be_imported_by_using_import_1_require_2_or_a_default_import : + Diagnostics._0_can_only_be_imported_by_using_import_1_require_2_or_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import; + error(name, message, declarationName, declarationName, moduleName); } - markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); - return resolved; } + } - function getCommonJSPropertyAccess(node: Node) { - if (isVariableDeclaration(node) && node.initializer && isPropertyAccessExpression(node.initializer)) { - return node.initializer; + function getTargetOfImportSpecifier(node: ImportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined { + if (isImportSpecifier(node) && idText(node.propertyName || node.name) === InternalSymbolName.Default) { + const specifier = getModuleSpecifierForImportOrExport(node); + const moduleSymbol = specifier && resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + return getTargetofModuleDefault(moduleSymbol, node, dontResolveAlias); } } - - function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol { - const resolved = resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); - return resolved; + const root = isBindingElement(node) ? getRootDeclaration(node) as VariableDeclaration : node.parent.parent.parent; + const commonJSPropertyAccess = getCommonJSPropertyAccess(root); + const resolved = getExternalModuleMember(root, commonJSPropertyAccess || node, dontResolveAlias); + const name = node.propertyName || node.name; + if (commonJSPropertyAccess && resolved && isIdentifier(name)) { + return resolveSymbol(getPropertyOfType(getTypeOfSymbol(resolved), name.escapedText), dontResolveAlias); } + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } - function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { - if (idText(node.propertyName || node.name) === InternalSymbolName.Default) { - const specifier = getModuleSpecifierForImportOrExport(node); - const moduleSymbol = specifier && resolveExternalModuleName(node, specifier); - if (moduleSymbol) { - return getTargetofModuleDefault(moduleSymbol, node, !!dontResolveAlias); - } - } - const resolved = node.parent.parent.moduleSpecifier ? - getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : - resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); - return resolved; + function getCommonJSPropertyAccess(node: Node) { + if (isVariableDeclaration(node) && node.initializer && isPropertyAccessExpression(node.initializer)) { + return node.initializer; } + } - function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined { - const expression = isExportAssignment(node) ? node.expression : node.right; - const resolved = getTargetOfAliasLikeExpression(expression, dontResolveAlias); - markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); - return resolved; - } + function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol { + const resolved = resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } - function getTargetOfAliasLikeExpression(expression: Expression, dontResolveAlias: boolean) { - if (isClassExpression(expression)) { - return checkExpressionCached(expression).symbol; - } - if (!isEntityName(expression) && !isEntityNameExpression(expression)) { - return undefined; - } - const aliasLike = resolveEntityName(expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias); - if (aliasLike) { - return aliasLike; + function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { + if (idText(node.propertyName || node.name) === InternalSymbolName.Default) { + const specifier = getModuleSpecifierForImportOrExport(node); + const moduleSymbol = specifier && resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + return getTargetofModuleDefault(moduleSymbol, node, !!dontResolveAlias); } - checkExpressionCached(expression); - return getNodeLinks(expression).resolvedSymbol; } + const resolved = node.parent.parent.moduleSpecifier ? + getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : + resolveEntityName(node.propertyName || node.name, meaning, /*ignoreErrors*/ false, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } - function getTargetOfAccessExpression(node: AccessExpression, dontRecursivelyResolve: boolean): Symbol | undefined { - if (!(isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken)) { - return undefined; - } + function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined { + const expression = isExportAssignment(node) ? node.expression : node.right; + const resolved = getTargetOfAliasLikeExpression(expression, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } - return getTargetOfAliasLikeExpression(node.parent.right, dontRecursivelyResolve); + function getTargetOfAliasLikeExpression(expression: Expression, dontResolveAlias: boolean) { + if (isClassExpression(expression)) { + return checkExpressionCached(expression).symbol; } - - function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve = false): Symbol | undefined { - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.VariableDeclaration: - return getTargetOfImportEqualsDeclaration(node as ImportEqualsDeclaration | VariableDeclaration, dontRecursivelyResolve); - case SyntaxKind.ImportClause: - return getTargetOfImportClause(node as ImportClause, dontRecursivelyResolve); - case SyntaxKind.NamespaceImport: - return getTargetOfNamespaceImport(node as NamespaceImport, dontRecursivelyResolve); - case SyntaxKind.NamespaceExport: - return getTargetOfNamespaceExport(node as NamespaceExport, dontRecursivelyResolve); - case SyntaxKind.ImportSpecifier: - case SyntaxKind.BindingElement: - return getTargetOfImportSpecifier(node as ImportSpecifier | BindingElement, dontRecursivelyResolve); - case SyntaxKind.ExportSpecifier: - return getTargetOfExportSpecifier(node as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve); - case SyntaxKind.ExportAssignment: - case SyntaxKind.BinaryExpression: - return getTargetOfExportAssignment((node as ExportAssignment | BinaryExpression), dontRecursivelyResolve); - case SyntaxKind.NamespaceExportDeclaration: - return getTargetOfNamespaceExportDeclaration(node as NamespaceExportDeclaration, dontRecursivelyResolve); - case SyntaxKind.ShorthandPropertyAssignment: - return resolveEntityName((node as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontRecursivelyResolve); - case SyntaxKind.PropertyAssignment: - return getTargetOfAliasLikeExpression((node as PropertyAssignment).initializer, dontRecursivelyResolve); - case SyntaxKind.ElementAccessExpression: - case SyntaxKind.PropertyAccessExpression: - return getTargetOfAccessExpression(node as AccessExpression, dontRecursivelyResolve); - default: - return Debug.fail(); - } + if (!isEntityName(expression) && !isEntityNameExpression(expression)) { + return undefined; } + const aliasLike = resolveEntityName(expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontResolveAlias); + if (aliasLike) { + return aliasLike; + } + checkExpressionCached(expression); + return getNodeLinks(expression).resolvedSymbol; + } - /** - * Indicates that a symbol is an alias that does not merge with a local declaration. - * OR Is a JSContainer which may merge an alias with a local declaration - */ - function isNonLocalAlias(symbol: Symbol | undefined, excludes = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace): symbol is Symbol { - if (!symbol) return false; - return (symbol.flags & (SymbolFlags.Alias | excludes)) === SymbolFlags.Alias || !!(symbol.flags & SymbolFlags.Alias && symbol.flags & SymbolFlags.Assignment); + function getTargetOfAccessExpression(node: AccessExpression, dontRecursivelyResolve: boolean): Symbol | undefined { + if (!(isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken)) { + return undefined; } - function resolveSymbol(symbol: Symbol, dontResolveAlias?: boolean): Symbol; - function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; - function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined { - return !dontResolveAlias && isNonLocalAlias(symbol) ? resolveAlias(symbol) : symbol; + return getTargetOfAliasLikeExpression(node.parent.right, dontRecursivelyResolve); + } + + function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve = false): Symbol | undefined { + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.VariableDeclaration: + return getTargetOfImportEqualsDeclaration(node as ImportEqualsDeclaration | VariableDeclaration, dontRecursivelyResolve); + case SyntaxKind.ImportClause: + return getTargetOfImportClause(node as ImportClause, dontRecursivelyResolve); + case SyntaxKind.NamespaceImport: + return getTargetOfNamespaceImport(node as NamespaceImport, dontRecursivelyResolve); + case SyntaxKind.NamespaceExport: + return getTargetOfNamespaceExport(node as NamespaceExport, dontRecursivelyResolve); + case SyntaxKind.ImportSpecifier: + case SyntaxKind.BindingElement: + return getTargetOfImportSpecifier(node as ImportSpecifier | BindingElement, dontRecursivelyResolve); + case SyntaxKind.ExportSpecifier: + return getTargetOfExportSpecifier(node as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve); + case SyntaxKind.ExportAssignment: + case SyntaxKind.BinaryExpression: + return getTargetOfExportAssignment((node as ExportAssignment | BinaryExpression), dontRecursivelyResolve); + case SyntaxKind.NamespaceExportDeclaration: + return getTargetOfNamespaceExportDeclaration(node as NamespaceExportDeclaration, dontRecursivelyResolve); + case SyntaxKind.ShorthandPropertyAssignment: + return resolveEntityName((node as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontRecursivelyResolve); + case SyntaxKind.PropertyAssignment: + return getTargetOfAliasLikeExpression((node as PropertyAssignment).initializer, dontRecursivelyResolve); + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: + return getTargetOfAccessExpression(node as AccessExpression, dontRecursivelyResolve); + default: + return Debug.fail(); } + } - function resolveAlias(symbol: Symbol): Symbol { - Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); - const links = getSymbolLinks(symbol); - if (!links.aliasTarget) { - links.aliasTarget = resolvingSymbol; - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - const target = getTargetOfAliasDeclaration(node); - if (links.aliasTarget === resolvingSymbol) { - links.aliasTarget = target || unknownSymbol; - } - else { - error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); - } + /** + * Indicates that a symbol is an alias that does not merge with a local declaration. + * OR Is a JSContainer which may merge an alias with a local declaration + */ + function isNonLocalAlias(symbol: Symbol | undefined, excludes = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace): symbol is Symbol { + if (!symbol) return false; + return (symbol.flags & (SymbolFlags.Alias | excludes)) === SymbolFlags.Alias || !!(symbol.flags & SymbolFlags.Alias && symbol.flags & SymbolFlags.Assignment); + } + + function resolveSymbol(symbol: Symbol, dontResolveAlias?: boolean): Symbol; + function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; + function resolveSymbol(symbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined { + return !dontResolveAlias && isNonLocalAlias(symbol) ? resolveAlias(symbol) : symbol; + } + + function resolveAlias(symbol: Symbol): Symbol { + Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); + const links = getSymbolLinks(symbol); + if (!links.aliasTarget) { + links.aliasTarget = resolvingSymbol; + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) return Debug.fail(); + const target = getTargetOfAliasDeclaration(node); + if (links.aliasTarget === resolvingSymbol) { + links.aliasTarget = target || unknownSymbol; } - else if (links.aliasTarget === resolvingSymbol) { - links.aliasTarget = unknownSymbol; + else { + error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); } - return links.aliasTarget; } + else if (links.aliasTarget === resolvingSymbol) { + links.aliasTarget = unknownSymbol; + } + return links.aliasTarget; + } - function tryResolveAlias(symbol: Symbol): Symbol | undefined { - const links = getSymbolLinks(symbol); - if (links.aliasTarget !== resolvingSymbol) { - return resolveAlias(symbol); - } - - return undefined; + function tryResolveAlias(symbol: Symbol): Symbol | undefined { + const links = getSymbolLinks(symbol); + if (links.aliasTarget !== resolvingSymbol) { + return resolveAlias(symbol); } - /** - * Gets combined flags of a `symbol` and all alias targets it resolves to. `resolveAlias` - * is typically recursive over chains of aliases, but stops mid-chain if an alias is merged - * with another exported symbol, e.g. - * ```ts - * // a.ts - * export const a = 0; - * // b.ts - * export { a } from "./a"; - * export type a = number; - * // c.ts - * import { a } from "./b"; - * ``` - * Calling `resolveAlias` on the `a` in c.ts would stop at the merged symbol exported - * from b.ts, even though there is still more alias to resolve. Consequently, if we were - * trying to determine if the `a` in c.ts has a value meaning, looking at the flags on - * the local symbol and on the symbol returned by `resolveAlias` is not enough. - * @returns SymbolFlags.All if `symbol` is an alias that ultimately resolves to `unknown`; - * combined flags of all alias targets otherwise. - */ - function getAllSymbolFlags(symbol: Symbol): SymbolFlags { - let flags = symbol.flags; - let seenSymbols; - while (symbol.flags & SymbolFlags.Alias) { - const target = resolveAlias(symbol); - if (target === unknownSymbol) { - return SymbolFlags.All; - } + return undefined; + } + + /** + * Gets combined flags of a `symbol` and all alias targets it resolves to. `resolveAlias` + * is typically recursive over chains of aliases, but stops mid-chain if an alias is merged + * with another exported symbol, e.g. + * ```ts + * // a.ts + * export const a = 0; + * // b.ts + * export { a } from "./a"; + * export type a = number; + * // c.ts + * import { a } from "./b"; + * ``` + * Calling `resolveAlias` on the `a` in c.ts would stop at the merged symbol exported + * from b.ts, even though there is still more alias to resolve. Consequently, if we were + * trying to determine if the `a` in c.ts has a value meaning, looking at the flags on + * the local symbol and on the symbol returned by `resolveAlias` is not enough. + * @returns SymbolFlags.All if `symbol` is an alias that ultimately resolves to `unknown`; + * combined flags of all alias targets otherwise. + */ + function getAllSymbolFlags(symbol: Symbol): SymbolFlags { + let flags = symbol.flags; + let seenSymbols; + while (symbol.flags & SymbolFlags.Alias) { + const target = resolveAlias(symbol); + if (target === unknownSymbol) { + return SymbolFlags.All; + } - // Optimizations - try to avoid creating or adding to - // `seenSymbols` if possible - if (target === symbol || seenSymbols?.has(target)) { - break; + // Optimizations - try to avoid creating or adding to + // `seenSymbols` if possible + if (target === symbol || seenSymbols?.has(target)) { + break; + } + if (target.flags & SymbolFlags.Alias) { + if (seenSymbols) { + seenSymbols.add(target); } - if (target.flags & SymbolFlags.Alias) { - if (seenSymbols) { - seenSymbols.add(target); - } - else { - seenSymbols = new Set([symbol, target]); - } + else { + seenSymbols = new Set([symbol, target]); } - flags |= target.flags; - symbol = target; } - return flags; - } - - /** - * Marks a symbol as type-only if its declaration is syntactically type-only. - * If it is not itself marked type-only, but resolves to a type-only alias - * somewhere in its resolution chain, save a reference to the type-only alias declaration - * so the alias _not_ marked type-only can be identified as _transitively_ type-only. - * - * This function is called on each alias declaration that could be type-only or resolve to - * another type-only alias during `resolveAlias`, so that later, when an alias is used in a - * JS-emitting expression, we can quickly determine if that symbol is effectively type-only - * and issue an error if so. - * - * @param aliasDeclaration The alias declaration not marked as type-only - * @param immediateTarget The symbol to which the alias declaration immediately resolves - * @param finalTarget The symbol to which the alias declaration ultimately resolves - * @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration` - * has already been marked as not resolving to a type-only alias. Used when recursively resolving qualified - * names of import aliases, e.g. `import C = a.b.C`. If namespace `a` is not found to be type-only, the - * import declaration will initially be marked as not resolving to a type-only symbol. But, namespace `b` - * must still be checked for a type-only marker, overwriting the previous negative result if found. - */ - function markSymbolOfAliasDeclarationIfTypeOnly( - aliasDeclaration: Declaration | undefined, - immediateTarget: Symbol | undefined, - finalTarget: Symbol | undefined, - overwriteEmpty: boolean, - ): boolean { - if (!aliasDeclaration || isPropertyAccessExpression(aliasDeclaration)) return false; - - // If the declaration itself is type-only, mark it and return. - // No need to check what it resolves to. - const sourceSymbol = getSymbolOfNode(aliasDeclaration); - if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) { - const links = getSymbolLinks(sourceSymbol); - links.typeOnlyDeclaration = aliasDeclaration; - return true; - } + flags |= target.flags; + symbol = target; + } + return flags; + } + /** + * Marks a symbol as type-only if its declaration is syntactically type-only. + * If it is not itself marked type-only, but resolves to a type-only alias + * somewhere in its resolution chain, save a reference to the type-only alias declaration + * so the alias _not_ marked type-only can be identified as _transitively_ type-only. + * + * This function is called on each alias declaration that could be type-only or resolve to + * another type-only alias during `resolveAlias`, so that later, when an alias is used in a + * JS-emitting expression, we can quickly determine if that symbol is effectively type-only + * and issue an error if so. + * + * @param aliasDeclaration The alias declaration not marked as type-only + * @param immediateTarget The symbol to which the alias declaration immediately resolves + * @param finalTarget The symbol to which the alias declaration ultimately resolves + * @param overwriteEmpty Checks `resolvesToSymbol` for type-only declarations even if `aliasDeclaration` + * has already been marked as not resolving to a type-only alias. Used when recursively resolving qualified + * names of import aliases, e.g. `import C = a.b.C`. If namespace `a` is not found to be type-only, the + * import declaration will initially be marked as not resolving to a type-only symbol. But, namespace `b` + * must still be checked for a type-only marker, overwriting the previous negative result if found. + */ + function markSymbolOfAliasDeclarationIfTypeOnly( + aliasDeclaration: Declaration | undefined, + immediateTarget: Symbol | undefined, + finalTarget: Symbol | undefined, + overwriteEmpty: boolean, + ): boolean { + if (!aliasDeclaration || isPropertyAccessExpression(aliasDeclaration)) return false; + + // If the declaration itself is type-only, mark it and return. + // No need to check what it resolves to. + const sourceSymbol = getSymbolOfNode(aliasDeclaration); + if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) { const links = getSymbolLinks(sourceSymbol); - return markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, immediateTarget, overwriteEmpty) - || markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, finalTarget, overwriteEmpty); + links.typeOnlyDeclaration = aliasDeclaration; + return true; } - function markSymbolOfAliasDeclarationIfTypeOnlyWorker(aliasDeclarationLinks: SymbolLinks, target: Symbol | undefined, overwriteEmpty: boolean): boolean { - if (target && (aliasDeclarationLinks.typeOnlyDeclaration === undefined || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration === false)) { - const exportSymbol = target.exports?.get(InternalSymbolName.ExportEquals) ?? target; - const typeOnly = exportSymbol.declarations && find(exportSymbol.declarations, isTypeOnlyImportOrExportDeclaration); - aliasDeclarationLinks.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(exportSymbol).typeOnlyDeclaration ?? false; - } - return !!aliasDeclarationLinks.typeOnlyDeclaration; + const links = getSymbolLinks(sourceSymbol); + return markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, immediateTarget, overwriteEmpty) + || markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, finalTarget, overwriteEmpty); + } + + function markSymbolOfAliasDeclarationIfTypeOnlyWorker(aliasDeclarationLinks: SymbolLinks, target: Symbol | undefined, overwriteEmpty: boolean): boolean { + if (target && (aliasDeclarationLinks.typeOnlyDeclaration === undefined || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration === false)) { + const exportSymbol = target.exports?.get(InternalSymbolName.ExportEquals) ?? target; + const typeOnly = exportSymbol.declarations && find(exportSymbol.declarations, isTypeOnlyImportOrExportDeclaration); + aliasDeclarationLinks.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(exportSymbol).typeOnlyDeclaration ?? false; } + return !!aliasDeclarationLinks.typeOnlyDeclaration; + } - /** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */ - function getTypeOnlyAliasDeclaration(symbol: Symbol, include?: SymbolFlags): TypeOnlyAliasDeclaration | undefined { - if (!(symbol.flags & SymbolFlags.Alias)) { - return undefined; - } - const links = getSymbolLinks(symbol); - if (include === undefined) { - return links.typeOnlyDeclaration || undefined; - } - if (links.typeOnlyDeclaration) { - return getAllSymbolFlags(resolveAlias(links.typeOnlyDeclaration.symbol)) & include ? links.typeOnlyDeclaration : undefined; - } + /** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */ + function getTypeOnlyAliasDeclaration(symbol: Symbol, include?: SymbolFlags): TypeOnlyAliasDeclaration | undefined { + if (!(symbol.flags & SymbolFlags.Alias)) { return undefined; } + const links = getSymbolLinks(symbol); + if (include === undefined) { + return links.typeOnlyDeclaration || undefined; + } + if (links.typeOnlyDeclaration) { + return getAllSymbolFlags(resolveAlias(links.typeOnlyDeclaration.symbol)) & include ? links.typeOnlyDeclaration : undefined; + } + return undefined; + } - function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { - const symbol = getSymbolOfNode(node); - const target = resolveAlias(symbol); - if (target) { - const markAlias = target === unknownSymbol || - ((getAllSymbolFlags(target) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value)); + function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { + const symbol = getSymbolOfNode(node); + const target = resolveAlias(symbol); + if (target) { + const markAlias = target === unknownSymbol || + ((getAllSymbolFlags(target) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value)); - if (markAlias) { - markAliasSymbolAsReferenced(symbol); - } + if (markAlias) { + markAliasSymbolAsReferenced(symbol); } } + } - // When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until - // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of - // the alias as an expression (which recursively takes us back here if the target references another alias). - function markAliasSymbolAsReferenced(symbol: Symbol) { - const links = getSymbolLinks(symbol); - if (!links.referenced) { - links.referenced = true; - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - // We defer checking of the reference of an `import =` until the import itself is referenced, - // This way a chain of imports can be elided if ultimately the final input is only used in a type - // position. - if (isInternalModuleImportEqualsDeclaration(node)) { - if (getAllSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) { - // import foo = - checkExpressionCached(node.moduleReference as Expression); - } + // When an alias symbol is referenced, we need to mark the entity it references as referenced and in turn repeat that until + // we reach a non-alias or an exported entity (which is always considered referenced). We do this by checking the target of + // the alias as an expression (which recursively takes us back here if the target references another alias). + function markAliasSymbolAsReferenced(symbol: Symbol) { + const links = getSymbolLinks(symbol); + if (!links.referenced) { + links.referenced = true; + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) return Debug.fail(); + // We defer checking of the reference of an `import =` until the import itself is referenced, + // This way a chain of imports can be elided if ultimately the final input is only used in a type + // position. + if (isInternalModuleImportEqualsDeclaration(node)) { + if (getAllSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) { + // import foo = + checkExpressionCached(node.moduleReference as Expression); } } } + } - // Aliases that resolve to const enums are not marked as referenced because they are not emitted, - // but their usage in value positions must be tracked to determine if the import can be type-only. - function markConstEnumAliasAsReferenced(symbol: Symbol) { - const links = getSymbolLinks(symbol); - if (!links.constEnumReferenced) { - links.constEnumReferenced = true; - } + // Aliases that resolve to const enums are not marked as referenced because they are not emitted, + // but their usage in value positions must be tracked to determine if the import can be type-only. + function markConstEnumAliasAsReferenced(symbol: Symbol) { + const links = getSymbolLinks(symbol); + if (!links.constEnumReferenced) { + links.constEnumReferenced = true; } + } - // This function is only for imports with entity names - function getSymbolOfPartOfRightHandSideOfImportEquals(entityName: EntityName, dontResolveAlias?: boolean): Symbol | undefined { - // There are three things we might try to look for. In the following examples, - // the search term is enclosed in |...|: - // - // import a = |b|; // Namespace - // import a = |b.c|; // Value, type, namespace - // import a = |b.c|.d; // Namespace - if (entityName.kind === SyntaxKind.Identifier && isRightSideOfQualifiedNameOrPropertyAccess(entityName)) { - entityName = entityName.parent as QualifiedName; - } - // Check for case 1 and 3 in the above example - if (entityName.kind === SyntaxKind.Identifier || entityName.parent.kind === SyntaxKind.QualifiedName) { - return resolveEntityName(entityName, SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); - } - else { - // Case 2 in above example - // entityName.kind could be a QualifiedName or a Missing identifier - Debug.assert(entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration); - return resolveEntityName(entityName, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); - } + // This function is only for imports with entity names + function getSymbolOfPartOfRightHandSideOfImportEquals(entityName: EntityName, dontResolveAlias?: boolean): Symbol | undefined { + // There are three things we might try to look for. In the following examples, + // the search term is enclosed in |...|: + // + // import a = |b|; // Namespace + // import a = |b.c|; // Value, type, namespace + // import a = |b.c|.d; // Namespace + if (entityName.kind === SyntaxKind.Identifier && isRightSideOfQualifiedNameOrPropertyAccess(entityName)) { + entityName = entityName.parent as QualifiedName; + } + // Check for case 1 and 3 in the above example + if (entityName.kind === SyntaxKind.Identifier || entityName.parent.kind === SyntaxKind.QualifiedName) { + return resolveEntityName(entityName, SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); + } + else { + // Case 2 in above example + // entityName.kind could be a QualifiedName or a Missing identifier + Debug.assert(entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration); + return resolveEntityName(entityName, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ false, dontResolveAlias); } + } - function getFullyQualifiedName(symbol: Symbol, containingLocation?: Node): string { - return symbol.parent ? getFullyQualifiedName(symbol.parent, containingLocation) + "." + symbolToString(symbol) : symbolToString(symbol, containingLocation, /*meaning*/ undefined, SymbolFormatFlags.DoNotIncludeSymbolChain | SymbolFormatFlags.AllowAnyNodeKind); - } + function getFullyQualifiedName(symbol: Symbol, containingLocation?: Node): string { + return symbol.parent ? getFullyQualifiedName(symbol.parent, containingLocation) + "." + symbolToString(symbol) : symbolToString(symbol, containingLocation, /*meaning*/ undefined, SymbolFormatFlags.DoNotIncludeSymbolChain | SymbolFormatFlags.AllowAnyNodeKind); + } - function getContainingQualifiedNameNode(node: QualifiedName) { - while (isQualifiedName(node.parent)) { - node = node.parent; - } - return node; + function getContainingQualifiedNameNode(node: QualifiedName) { + while (isQualifiedName(node.parent)) { + node = node.parent; } + return node; + } - function tryGetQualifiedNameAsValue(node: QualifiedName) { - let left: Identifier | QualifiedName = getFirstIdentifier(node); - let symbol = resolveName(left, left.escapedText, SymbolFlags.Value, undefined, left, /*isUse*/ true); + function tryGetQualifiedNameAsValue(node: QualifiedName) { + let left: Identifier | QualifiedName = getFirstIdentifier(node); + let symbol = resolveName(left, left.escapedText, SymbolFlags.Value, undefined, left, /*isUse*/ true); + if (!symbol) { + return undefined; + } + while (isQualifiedName(left.parent)) { + const type = getTypeOfSymbol(symbol); + symbol = getPropertyOfType(type, left.parent.right.escapedText); if (!symbol) { return undefined; } - while (isQualifiedName(left.parent)) { - const type = getTypeOfSymbol(symbol); - symbol = getPropertyOfType(type, left.parent.right.escapedText); - if (!symbol) { - return undefined; - } - left = left.parent; - } - return symbol; + left = left.parent; } + return symbol; + } - /** - * Resolves a qualified name and any involved aliases. - */ - function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): Symbol | undefined { - if (nodeIsMissing(name)) { + /** + * Resolves a qualified name and any involved aliases. + */ + function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean, location?: Node): Symbol | undefined { + if (nodeIsMissing(name)) { + return undefined; + } + + const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(name) ? meaning & SymbolFlags.Value : 0); + let symbol: Symbol | undefined; + if (name.kind === SyntaxKind.Identifier) { + const message = meaning === namespaceMeaning || nodeIsSynthesized(name) ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name)); + const symbolFromJSPrototype = isInJSFile(name) && !nodeIsSynthesized(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined; + symbol = getMergedSymbol(resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true, false)); + if (!symbol) { + return getMergedSymbol(symbolFromJSPrototype); + } + } + else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) { + const left = name.kind === SyntaxKind.QualifiedName ? name.left : name.expression; + const right = name.kind === SyntaxKind.QualifiedName ? name.right : name.name; + let namespace = resolveEntityName(left, namespaceMeaning, ignoreErrors, /*dontResolveAlias*/ false, location); + if (!namespace || nodeIsMissing(right)) { return undefined; } - - const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(name) ? meaning & SymbolFlags.Value : 0); - let symbol: Symbol | undefined; - if (name.kind === SyntaxKind.Identifier) { - const message = meaning === namespaceMeaning || nodeIsSynthesized(name) ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name)); - const symbolFromJSPrototype = isInJSFile(name) && !nodeIsSynthesized(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined; - symbol = getMergedSymbol(resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true, false)); - if (!symbol) { - return getMergedSymbol(symbolFromJSPrototype); - } + else if (namespace === unknownSymbol) { + return namespace; } - else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) { - const left = name.kind === SyntaxKind.QualifiedName ? name.left : name.expression; - const right = name.kind === SyntaxKind.QualifiedName ? name.right : name.name; - let namespace = resolveEntityName(left, namespaceMeaning, ignoreErrors, /*dontResolveAlias*/ false, location); - if (!namespace || nodeIsMissing(right)) { - return undefined; - } - else if (namespace === unknownSymbol) { - return namespace; - } - if ( - namespace.valueDeclaration && - isInJSFile(namespace.valueDeclaration) && - isVariableDeclaration(namespace.valueDeclaration) && - namespace.valueDeclaration.initializer && - isCommonJsRequire(namespace.valueDeclaration.initializer) - ) { - const moduleName = (namespace.valueDeclaration.initializer as CallExpression).arguments[0] as StringLiteral; - const moduleSym = resolveExternalModuleName(moduleName, moduleName); - if (moduleSym) { - const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); - if (resolvedModuleSymbol) { - namespace = resolvedModuleSymbol; - } + if ( + namespace.valueDeclaration && + isInJSFile(namespace.valueDeclaration) && + isVariableDeclaration(namespace.valueDeclaration) && + namespace.valueDeclaration.initializer && + isCommonJsRequire(namespace.valueDeclaration.initializer) + ) { + const moduleName = (namespace.valueDeclaration.initializer as CallExpression).arguments[0] as StringLiteral; + const moduleSym = resolveExternalModuleName(moduleName, moduleName); + if (moduleSym) { + const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); + if (resolvedModuleSymbol) { + namespace = resolvedModuleSymbol; } } - symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(namespace), right.escapedText, meaning)); - if (!symbol) { - if (!ignoreErrors) { - const namespaceName = getFullyQualifiedName(namespace); - const declarationName = declarationNameToString(right); - const suggestionForNonexistentModule = getSuggestedSymbolForNonexistentModule(right, namespace); - if (suggestionForNonexistentModule) { - error(right, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, namespaceName, declarationName, symbolToString(suggestionForNonexistentModule)); - return undefined; - } + } + symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(namespace), right.escapedText, meaning)); + if (!symbol) { + if (!ignoreErrors) { + const namespaceName = getFullyQualifiedName(namespace); + const declarationName = declarationNameToString(right); + const suggestionForNonexistentModule = getSuggestedSymbolForNonexistentModule(right, namespace); + if (suggestionForNonexistentModule) { + error(right, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, namespaceName, declarationName, symbolToString(suggestionForNonexistentModule)); + return undefined; + } + + const containingQualifiedName = isQualifiedName(name) && getContainingQualifiedNameNode(name); + const canSuggestTypeof = globalObjectType // <-- can't pull on types if global types aren't initialized yet + && (meaning & SymbolFlags.Type) + && containingQualifiedName + && !isTypeOfExpression(containingQualifiedName.parent) + && tryGetQualifiedNameAsValue(containingQualifiedName); + if (canSuggestTypeof) { + error( + containingQualifiedName, + Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here_Did_you_mean_typeof_0, + entityNameToString(containingQualifiedName) + ); + return undefined; + } - const containingQualifiedName = isQualifiedName(name) && getContainingQualifiedNameNode(name); - const canSuggestTypeof = globalObjectType // <-- can't pull on types if global types aren't initialized yet - && (meaning & SymbolFlags.Type) - && containingQualifiedName - && !isTypeOfExpression(containingQualifiedName.parent) - && tryGetQualifiedNameAsValue(containingQualifiedName); - if (canSuggestTypeof) { + if (meaning & SymbolFlags.Namespace && isQualifiedName(name.parent)) { + const exportedTypeSymbol = getMergedSymbol(getSymbol(getExportsOfSymbol(namespace), right.escapedText, SymbolFlags.Type)); + if (exportedTypeSymbol) { error( - containingQualifiedName, - Diagnostics._0_refers_to_a_value_but_is_being_used_as_a_type_here_Did_you_mean_typeof_0, - entityNameToString(containingQualifiedName) + name.parent.right, + Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1, + symbolToString(exportedTypeSymbol), + unescapeLeadingUnderscores(name.parent.right.escapedText) ); return undefined; } - - if (meaning & SymbolFlags.Namespace && isQualifiedName(name.parent)) { - const exportedTypeSymbol = getMergedSymbol(getSymbol(getExportsOfSymbol(namespace), right.escapedText, SymbolFlags.Type)); - if (exportedTypeSymbol) { - error( - name.parent.right, - Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1, - symbolToString(exportedTypeSymbol), - unescapeLeadingUnderscores(name.parent.right.escapedText) - ); - return undefined; - } - } - - error(right, Diagnostics.Namespace_0_has_no_exported_member_1, namespaceName, declarationName); } - return undefined; + + error(right, Diagnostics.Namespace_0_has_no_exported_member_1, namespaceName, declarationName); } + return undefined; } - else { - throw Debug.assertNever(name, "Unknown entity name kind."); - } - Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (!nodeIsSynthesized(name) && isEntityName(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { - markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), symbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ true); - } - return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } + else { + throw Debug.assertNever(name, "Unknown entity name kind."); + } + Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); + if (!nodeIsSynthesized(name) && isEntityName(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { + markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), symbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ true); + } + return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); + } - /** - * 1. For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too. - * Note that prototype-property assignment to locations outside the current file (eg globals) doesn't work, so - * name resolution won't work either. - * 2. For property assignments like `{ x: function f () { } }`, try to resolve names in the scope of `f` too. - */ - function resolveEntityNameFromAssignmentDeclaration(name: Identifier, meaning: SymbolFlags) { - if (isJSDocTypeReference(name.parent)) { - const secondaryLocation = getAssignmentDeclarationLocation(name.parent); - if (secondaryLocation) { - return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true); - } + /** + * 1. For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too. + * Note that prototype-property assignment to locations outside the current file (eg globals) doesn't work, so + * name resolution won't work either. + * 2. For property assignments like `{ x: function f () { } }`, try to resolve names in the scope of `f` too. + */ + function resolveEntityNameFromAssignmentDeclaration(name: Identifier, meaning: SymbolFlags) { + if (isJSDocTypeReference(name.parent)) { + const secondaryLocation = getAssignmentDeclarationLocation(name.parent); + if (secondaryLocation) { + return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true); } } + } - function getAssignmentDeclarationLocation(node: TypeReferenceNode): Node | undefined { - const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node)); - if (typeAlias) { - return; - } - const host = getJSDocHost(node); - if (host && isExpressionStatement(host) && isPrototypePropertyAssignment(host.expression)) { - // /** @param {K} p */ X.prototype.m = function () { } <-- look for K on X's declaration - const symbol = getSymbolOfNode(host.expression.left); - if (symbol) { - return getDeclarationOfJSPrototypeContainer(symbol); - } - } - if (host && isFunctionExpression(host) && isPrototypePropertyAssignment(host.parent) && isExpressionStatement(host.parent.parent)) { - // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration - const symbol = getSymbolOfNode(host.parent.left); - if (symbol) { - return getDeclarationOfJSPrototypeContainer(symbol); - } + function getAssignmentDeclarationLocation(node: TypeReferenceNode): Node | undefined { + const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node)); + if (typeAlias) { + return; + } + const host = getJSDocHost(node); + if (host && isExpressionStatement(host) && isPrototypePropertyAssignment(host.expression)) { + // /** @param {K} p */ X.prototype.m = function () { } <-- look for K on X's declaration + const symbol = getSymbolOfNode(host.expression.left); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); } - if (host && (isObjectLiteralMethod(host) || isPropertyAssignment(host)) && - isBinaryExpression(host.parent.parent) && - getAssignmentDeclarationKind(host.parent.parent) === AssignmentDeclarationKind.Prototype) { - // X.prototype = { /** @param {K} p */m() { } } <-- look for K on X's declaration - const symbol = getSymbolOfNode(host.parent.parent.left); - if (symbol) { - return getDeclarationOfJSPrototypeContainer(symbol); - } + } + if (host && isFunctionExpression(host) && isPrototypePropertyAssignment(host.parent) && isExpressionStatement(host.parent.parent)) { + // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration + const symbol = getSymbolOfNode(host.parent.left); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); } - const sig = getEffectiveJSDocHost(node); - if (sig && isFunctionLike(sig)) { - const symbol = getSymbolOfNode(sig); - return symbol && symbol.valueDeclaration; + } + if (host && (isObjectLiteralMethod(host) || isPropertyAssignment(host)) && + isBinaryExpression(host.parent.parent) && + getAssignmentDeclarationKind(host.parent.parent) === AssignmentDeclarationKind.Prototype) { + // X.prototype = { /** @param {K} p */m() { } } <-- look for K on X's declaration + const symbol = getSymbolOfNode(host.parent.parent.left); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); } } + const sig = getEffectiveJSDocHost(node); + if (sig && isFunctionLike(sig)) { + const symbol = getSymbolOfNode(sig); + return symbol && symbol.valueDeclaration; + } + } - function getDeclarationOfJSPrototypeContainer(symbol: Symbol) { - const decl = symbol.parent!.valueDeclaration; - if (!decl) { - return undefined; - } - const initializer = isAssignmentDeclaration(decl) ? getAssignedExpandoInitializer(decl) : - hasOnlyExpressionInitializer(decl) ? getDeclaredExpandoInitializer(decl) : - undefined; - return initializer || decl; + function getDeclarationOfJSPrototypeContainer(symbol: Symbol) { + const decl = symbol.parent!.valueDeclaration; + if (!decl) { + return undefined; } + const initializer = isAssignmentDeclaration(decl) ? getAssignedExpandoInitializer(decl) : + hasOnlyExpressionInitializer(decl) ? getDeclaredExpandoInitializer(decl) : + undefined; + return initializer || decl; + } - /** - * Get the real symbol of a declaration with an expando initializer. - * - * Normally, declarations have an associated symbol, but when a declaration has an expando - * initializer, the expando's symbol is the one that has all the members merged into it. - */ - function getExpandoSymbol(symbol: Symbol): Symbol | undefined { - const decl = symbol.valueDeclaration; - if (!decl || !isInJSFile(decl) || symbol.flags & SymbolFlags.TypeAlias || getExpandoInitializer(decl, /*isPrototypeAssignment*/ false)) { - return undefined; - } - const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl); - if (init) { - const initSymbol = getSymbolOfNode(init); - if (initSymbol) { - return mergeJSSymbols(initSymbol, symbol); - } + /** + * Get the real symbol of a declaration with an expando initializer. + * + * Normally, declarations have an associated symbol, but when a declaration has an expando + * initializer, the expando's symbol is the one that has all the members merged into it. + */ + function getExpandoSymbol(symbol: Symbol): Symbol | undefined { + const decl = symbol.valueDeclaration; + if (!decl || !isInJSFile(decl) || symbol.flags & SymbolFlags.TypeAlias || getExpandoInitializer(decl, /*isPrototypeAssignment*/ false)) { + return undefined; + } + const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl); + if (init) { + const initSymbol = getSymbolOfNode(init); + if (initSymbol) { + return mergeJSSymbols(initSymbol, symbol); } } + } - function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression, ignoreErrors?: boolean): Symbol | undefined { - const isClassic = getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic; - const errorMessage = isClassic? - Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option - : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; - return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : errorMessage); - } + function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression, ignoreErrors?: boolean): Symbol | undefined { + const isClassic = getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic; + const errorMessage = isClassic? + Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option + : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; + return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : errorMessage); + } - function resolveExternalModuleNameWorker(location: Node, moduleReferenceExpression: Expression, moduleNotFoundError: DiagnosticMessage | undefined, isForAugmentation = false): Symbol | undefined { - return isStringLiteralLike(moduleReferenceExpression) - ? resolveExternalModule(location, moduleReferenceExpression.text, moduleNotFoundError, moduleReferenceExpression, isForAugmentation) - : undefined; - } + function resolveExternalModuleNameWorker(location: Node, moduleReferenceExpression: Expression, moduleNotFoundError: DiagnosticMessage | undefined, isForAugmentation = false): Symbol | undefined { + return isStringLiteralLike(moduleReferenceExpression) + ? resolveExternalModule(location, moduleReferenceExpression.text, moduleNotFoundError, moduleReferenceExpression, isForAugmentation) + : undefined; + } - function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node, isForAugmentation = false): Symbol | undefined { - if (startsWith(moduleReference, "@types/")) { - const diag = Diagnostics.Cannot_import_type_declaration_files_Consider_importing_0_instead_of_1; - const withoutAtTypePrefix = removePrefix(moduleReference, "@types/"); - error(errorNode, diag, withoutAtTypePrefix, moduleReference); - } - - const ambientModule = tryFindAmbientModule(moduleReference, /*withAugmentations*/ true); - if (ambientModule) { - return ambientModule; - } - const currentSourceFile = getSourceFileOfNode(location); - const contextSpecifier = isStringLiteralLike(location) - ? location - : findAncestor(location, isImportCall)?.arguments[0] || - findAncestor(location, isImportDeclaration)?.moduleSpecifier || - findAncestor(location, isExternalModuleImportEqualsDeclaration)?.moduleReference.expression || - findAncestor(location, isExportDeclaration)?.moduleSpecifier || - (isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name || - (isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal; - const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat; - const resolvedModule = getResolvedModule(currentSourceFile, moduleReference, mode); - const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule); - const sourceFile = resolvedModule - && (!resolutionDiagnostic || resolutionDiagnostic === Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set) - && host.getSourceFile(resolvedModule.resolvedFileName); - if (sourceFile) { - // If there's a resolutionDiagnostic we need to report it even if a sourceFile is found. - if (resolutionDiagnostic) { - error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); - } - if (sourceFile.symbol) { - if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) { - errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference); - } - if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) { - const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration); - const overrideClauseHost = findAncestor(location, l => isImportTypeNode(l) || isExportDeclaration(l) || isImportDeclaration(l)) as ImportTypeNode | ImportDeclaration | ExportDeclaration | undefined; - const overrideClause = overrideClauseHost && isImportTypeNode(overrideClauseHost) ? overrideClauseHost.assertions?.assertClause : overrideClauseHost?.assertClause; - // An override clause will take effect for type-only imports and import types, and allows importing the types across formats, regardless of - // normal mode restrictions - if (isSyncImport && sourceFile.impliedNodeFormat === ModuleKind.ESNext && !getResolutionModeOverrideForClause(overrideClause)) { - if (findAncestor(location, isImportEqualsDeclaration)) { - // ImportEquals in a ESM file resolving to another ESM file - error(errorNode, Diagnostics.Module_0_cannot_be_imported_using_this_construct_The_specifier_only_resolves_to_an_ES_module_which_cannot_be_imported_with_require_Use_an_ECMAScript_import_instead, moduleReference); - } - else { - // CJS file resolving to an ESM file - let diagnosticDetails; - const ext = tryGetExtensionFromPath(currentSourceFile.fileName); - if (ext === Extension.Ts || ext === Extension.Js || ext === Extension.Tsx || ext === Extension.Jsx) { - const scope = currentSourceFile.packageJsonScope; - const targetExt = ext === Extension.Ts ? Extension.Mts : ext === Extension.Js ? Extension.Mjs : undefined; - if (scope && !scope.contents.packageJsonContent.type) { - if (targetExt) { - diagnosticDetails = chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.To_convert_this_file_to_an_ECMAScript_module_change_its_file_extension_to_0_or_add_the_field_type_Colon_module_to_1, - targetExt, - combinePaths(scope.packageDirectory, "package.json")); - } - else { - diagnosticDetails = chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.To_convert_this_file_to_an_ECMAScript_module_add_the_field_type_Colon_module_to_0, - combinePaths(scope.packageDirectory, "package.json")); - } + function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node, isForAugmentation = false): Symbol | undefined { + if (startsWith(moduleReference, "@types/")) { + const diag = Diagnostics.Cannot_import_type_declaration_files_Consider_importing_0_instead_of_1; + const withoutAtTypePrefix = removePrefix(moduleReference, "@types/"); + error(errorNode, diag, withoutAtTypePrefix, moduleReference); + } + + const ambientModule = tryFindAmbientModule(moduleReference, /*withAugmentations*/ true); + if (ambientModule) { + return ambientModule; + } + const currentSourceFile = getSourceFileOfNode(location); + const contextSpecifier = isStringLiteralLike(location) + ? location + : findAncestor(location, isImportCall)?.arguments[0] || + findAncestor(location, isImportDeclaration)?.moduleSpecifier || + findAncestor(location, isExternalModuleImportEqualsDeclaration)?.moduleReference.expression || + findAncestor(location, isExportDeclaration)?.moduleSpecifier || + (isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name || + (isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal; + const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat; + const resolvedModule = getResolvedModule(currentSourceFile, moduleReference, mode); + const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule); + const sourceFile = resolvedModule + && (!resolutionDiagnostic || resolutionDiagnostic === Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set) + && host.getSourceFile(resolvedModule.resolvedFileName); + if (sourceFile) { + // If there's a resolutionDiagnostic we need to report it even if a sourceFile is found. + if (resolutionDiagnostic) { + error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); + } + if (sourceFile.symbol) { + if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) { + errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference); + } + if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) { + const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration); + const overrideClauseHost = findAncestor(location, l => isImportTypeNode(l) || isExportDeclaration(l) || isImportDeclaration(l)) as ImportTypeNode | ImportDeclaration | ExportDeclaration | undefined; + const overrideClause = overrideClauseHost && isImportTypeNode(overrideClauseHost) ? overrideClauseHost.assertions?.assertClause : overrideClauseHost?.assertClause; + // An override clause will take effect for type-only imports and import types, and allows importing the types across formats, regardless of + // normal mode restrictions + if (isSyncImport && sourceFile.impliedNodeFormat === ModuleKind.ESNext && !getResolutionModeOverrideForClause(overrideClause)) { + if (findAncestor(location, isImportEqualsDeclaration)) { + // ImportEquals in a ESM file resolving to another ESM file + error(errorNode, Diagnostics.Module_0_cannot_be_imported_using_this_construct_The_specifier_only_resolves_to_an_ES_module_which_cannot_be_imported_with_require_Use_an_ECMAScript_import_instead, moduleReference); + } + else { + // CJS file resolving to an ESM file + let diagnosticDetails; + const ext = tryGetExtensionFromPath(currentSourceFile.fileName); + if (ext === Extension.Ts || ext === Extension.Js || ext === Extension.Tsx || ext === Extension.Jsx) { + const scope = currentSourceFile.packageJsonScope; + const targetExt = ext === Extension.Ts ? Extension.Mts : ext === Extension.Js ? Extension.Mjs : undefined; + if (scope && !scope.contents.packageJsonContent.type) { + if (targetExt) { + diagnosticDetails = chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.To_convert_this_file_to_an_ECMAScript_module_change_its_file_extension_to_0_or_add_the_field_type_Colon_module_to_1, + targetExt, + combinePaths(scope.packageDirectory, "package.json")); + } + else { + diagnosticDetails = chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.To_convert_this_file_to_an_ECMAScript_module_add_the_field_type_Colon_module_to_0, + combinePaths(scope.packageDirectory, "package.json")); + } + } + else { + if (targetExt) { + diagnosticDetails = chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.To_convert_this_file_to_an_ECMAScript_module_change_its_file_extension_to_0_or_create_a_local_package_json_file_with_type_Colon_module, + targetExt); } else { - if (targetExt) { - diagnosticDetails = chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.To_convert_this_file_to_an_ECMAScript_module_change_its_file_extension_to_0_or_create_a_local_package_json_file_with_type_Colon_module, - targetExt); - } - else { - diagnosticDetails = chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.To_convert_this_file_to_an_ECMAScript_module_create_a_local_package_json_file_with_type_Colon_module); - } + diagnosticDetails = chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.To_convert_this_file_to_an_ECMAScript_module_create_a_local_package_json_file_with_type_Colon_module); } } - diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode, chainDiagnosticMessages( - diagnosticDetails, - Diagnostics.The_current_file_is_a_CommonJS_module_whose_imports_will_produce_require_calls_however_the_referenced_file_is_an_ECMAScript_module_and_cannot_be_imported_with_require_Consider_writing_a_dynamic_import_0_call_instead, - moduleReference))); } + diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode, chainDiagnosticMessages( + diagnosticDetails, + Diagnostics.The_current_file_is_a_CommonJS_module_whose_imports_will_produce_require_calls_however_the_referenced_file_is_an_ECMAScript_module_and_cannot_be_imported_with_require_Consider_writing_a_dynamic_import_0_call_instead, + moduleReference))); } } - // merged symbol is module declaration symbol combined with all augmentations - return getMergedSymbol(sourceFile.symbol); - } - if (moduleNotFoundError) { - // report errors only if it was requested - error(errorNode, Diagnostics.File_0_is_not_a_module, sourceFile.fileName); } - return undefined; + // merged symbol is module declaration symbol combined with all augmentations + return getMergedSymbol(sourceFile.symbol); + } + if (moduleNotFoundError) { + // report errors only if it was requested + error(errorNode, Diagnostics.File_0_is_not_a_module, sourceFile.fileName); } + return undefined; + } - if (patternAmbientModules) { - const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference); - if (pattern) { - // If the module reference matched a pattern ambient module ('*.foo') but there's also a - // module augmentation by the specific name requested ('a.foo'), we store the merged symbol - // by the augmentation name ('a.foo'), because asking for *.foo should not give you exports - // from a.foo. - const augmentation = patternAmbientModuleAugmentations && patternAmbientModuleAugmentations.get(moduleReference); - if (augmentation) { - return getMergedSymbol(augmentation); - } - return getMergedSymbol(pattern.symbol); + if (patternAmbientModules) { + const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference); + if (pattern) { + // If the module reference matched a pattern ambient module ('*.foo') but there's also a + // module augmentation by the specific name requested ('a.foo'), we store the merged symbol + // by the augmentation name ('a.foo'), because asking for *.foo should not give you exports + // from a.foo. + const augmentation = patternAmbientModuleAugmentations && patternAmbientModuleAugmentations.get(moduleReference); + if (augmentation) { + return getMergedSymbol(augmentation); } + return getMergedSymbol(pattern.symbol); } + } - // May be an untyped module. If so, ignore resolutionDiagnostic. - if (resolvedModule && !resolutionExtensionIsTSOrJson(resolvedModule.extension) && resolutionDiagnostic === undefined || resolutionDiagnostic === Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type) { - if (isForAugmentation) { - const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented; - error(errorNode, diag, moduleReference, resolvedModule!.resolvedFileName); - } - else { - errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule!, moduleReference); + // May be an untyped module. If so, ignore resolutionDiagnostic. + if (resolvedModule && !resolutionExtensionIsTSOrJson(resolvedModule.extension) && resolutionDiagnostic === undefined || resolutionDiagnostic === Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type) { + if (isForAugmentation) { + const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented; + error(errorNode, diag, moduleReference, resolvedModule!.resolvedFileName); + } + else { + errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule!, moduleReference); + } + // Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first. + return undefined; + } + + if (moduleNotFoundError) { + // See if this was possibly a projectReference redirect + if (resolvedModule) { + const redirect = host.getProjectReferenceRedirect(resolvedModule.resolvedFileName); + if (redirect) { + error(errorNode, Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, resolvedModule.resolvedFileName); + return undefined; } - // Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first. - return undefined; } - if (moduleNotFoundError) { - // See if this was possibly a projectReference redirect - if (resolvedModule) { - const redirect = host.getProjectReferenceRedirect(resolvedModule.resolvedFileName); - if (redirect) { - error(errorNode, Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, resolvedModule.resolvedFileName); - return undefined; + if (resolutionDiagnostic) { + error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); + } + else { + const tsExtension = tryExtractTSExtension(moduleReference); + const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference); + const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions); + const resolutionIsNode16OrNext = moduleResolutionKind === ModuleResolutionKind.Node16 || + moduleResolutionKind === ModuleResolutionKind.NodeNext; + if (tsExtension) { + const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead; + const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension); + let replacedImportSource = importSourceWithoutExtension; + /** + * Direct users to import source with .js extension if outputting an ES module. + * @see https://github.com/microsoft/TypeScript/issues/42151 + */ + if (moduleKind >= ModuleKind.ES2015) { + replacedImportSource += tsExtension === Extension.Mts ? ".mjs" : tsExtension === Extension.Cts ? ".cjs" : ".js"; } + error(errorNode, diag, tsExtension, replacedImportSource); } - - if (resolutionDiagnostic) { - error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); + else if (!compilerOptions.resolveJsonModule && + fileExtensionIs(moduleReference, Extension.Json) && + getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && + hasJsonModuleEmitEnabled(compilerOptions)) { + error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference); } - else { - const tsExtension = tryExtractTSExtension(moduleReference); - const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference); - const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions); - const resolutionIsNode16OrNext = moduleResolutionKind === ModuleResolutionKind.Node16 || - moduleResolutionKind === ModuleResolutionKind.NodeNext; - if (tsExtension) { - const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead; - const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension); - let replacedImportSource = importSourceWithoutExtension; - /** - * Direct users to import source with .js extension if outputting an ES module. - * @see https://github.com/microsoft/TypeScript/issues/42151 - */ - if (moduleKind >= ModuleKind.ES2015) { - replacedImportSource += tsExtension === Extension.Mts ? ".mjs" : tsExtension === Extension.Cts ? ".cjs" : ".js"; - } - error(errorNode, diag, tsExtension, replacedImportSource); - } - else if (!compilerOptions.resolveJsonModule && - fileExtensionIs(moduleReference, Extension.Json) && - getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && - hasJsonModuleEmitEnabled(compilerOptions)) { - error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference); - } - else if (mode === ModuleKind.ESNext && resolutionIsNode16OrNext && isExtensionlessRelativePathImport) { - const absoluteRef = getNormalizedAbsolutePath(moduleReference, getDirectoryPath(currentSourceFile.path)); - const suggestedExt = suggestedExtensions.find(([actualExt, _importExt]) => host.fileExists(absoluteRef + actualExt))?.[1]; - if (suggestedExt) { - error(errorNode, - Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_EcmaScript_imports_when_moduleResolution_is_node16_or_nodenext_Did_you_mean_0, - moduleReference + suggestedExt); - } - else { - error(errorNode, Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_EcmaScript_imports_when_moduleResolution_is_node16_or_nodenext_Consider_adding_an_extension_to_the_import_path); - } + else if (mode === ModuleKind.ESNext && resolutionIsNode16OrNext && isExtensionlessRelativePathImport) { + const absoluteRef = getNormalizedAbsolutePath(moduleReference, getDirectoryPath(currentSourceFile.path)); + const suggestedExt = suggestedExtensions.find(([actualExt, _importExt]) => host.fileExists(absoluteRef + actualExt))?.[1]; + if (suggestedExt) { + error(errorNode, + Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_EcmaScript_imports_when_moduleResolution_is_node16_or_nodenext_Did_you_mean_0, + moduleReference + suggestedExt); } else { - error(errorNode, moduleNotFoundError, moduleReference); + error(errorNode, Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_EcmaScript_imports_when_moduleResolution_is_node16_or_nodenext_Consider_adding_an_extension_to_the_import_path); } } + else { + error(errorNode, moduleNotFoundError, moduleReference); + } } - return undefined; } + return undefined; + } - function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void { - const errorInfo = !isExternalModuleNameRelative(moduleReference) && packageId - ? typesPackageExists(packageId.name) + function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void { + const errorInfo = !isExternalModuleNameRelative(moduleReference) && packageId + ? typesPackageExists(packageId.name) + ? chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1, + packageId.name, mangleScopedPackageName(packageId.name)) + : packageBundlesTypes(packageId.name) ? chainDiagnosticMessages( /*details*/ undefined, - Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1, - packageId.name, mangleScopedPackageName(packageId.name)) - : packageBundlesTypes(packageId.name) - ? chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1, - packageId.name, - moduleReference) - : chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0, - moduleReference, - mangleScopedPackageName(packageId.name)) - : undefined; - errorOrSuggestion(isError, errorNode, chainDiagnosticMessages( - errorInfo, - Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, - moduleReference, - resolvedFileName)); - } - function typesPackageExists(packageName: string): boolean { - return getPackagesMap().has(getTypesPackageName(packageName)); - } - function packageBundlesTypes(packageName: string): boolean { - return !!getPackagesMap().get(packageName); - } + Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1, + packageId.name, + moduleReference) + : chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0, + moduleReference, + mangleScopedPackageName(packageId.name)) + : undefined; + errorOrSuggestion(isError, errorNode, chainDiagnosticMessages( + errorInfo, + Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, + moduleReference, + resolvedFileName)); + } + function typesPackageExists(packageName: string): boolean { + return getPackagesMap().has(getTypesPackageName(packageName)); + } + function packageBundlesTypes(packageName: string): boolean { + return !!getPackagesMap().get(packageName); + } - function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol; - function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; - function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol | undefined { - if (moduleSymbol?.exports) { - const exportEquals = resolveSymbol(moduleSymbol.exports.get(InternalSymbolName.ExportEquals), dontResolveAlias); - const exported = getCommonJsExportEquals(getMergedSymbol(exportEquals), getMergedSymbol(moduleSymbol)); - return getMergedSymbol(exported) || moduleSymbol; - } - return undefined; + function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol; + function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; + function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol | undefined { + if (moduleSymbol?.exports) { + const exportEquals = resolveSymbol(moduleSymbol.exports.get(InternalSymbolName.ExportEquals), dontResolveAlias); + const exported = getCommonJsExportEquals(getMergedSymbol(exportEquals), getMergedSymbol(moduleSymbol)); + return getMergedSymbol(exported) || moduleSymbol; } + return undefined; + } - function getCommonJsExportEquals(exported: Symbol | undefined, moduleSymbol: Symbol): Symbol | undefined { - if (!exported || exported === unknownSymbol || exported === moduleSymbol || moduleSymbol.exports!.size === 1 || exported.flags & SymbolFlags.Alias) { - return exported; - } - const links = getSymbolLinks(exported); - if (links.cjsExportMerged) { - return links.cjsExportMerged; - } - const merged = exported.flags & SymbolFlags.Transient ? exported : cloneSymbol(exported); - merged.flags = merged.flags | SymbolFlags.ValueModule; - if (merged.exports === undefined) { - merged.exports = createSymbolTable(); - } - moduleSymbol.exports!.forEach((s, name) => { - if (name === InternalSymbolName.ExportEquals) return; - merged.exports!.set(name, merged.exports!.has(name) ? mergeSymbol(merged.exports!.get(name)!, s) : s); - }); - getSymbolLinks(merged).cjsExportMerged = merged; - return links.cjsExportMerged = merged; + function getCommonJsExportEquals(exported: Symbol | undefined, moduleSymbol: Symbol): Symbol | undefined { + if (!exported || exported === unknownSymbol || exported === moduleSymbol || moduleSymbol.exports!.size === 1 || exported.flags & SymbolFlags.Alias) { + return exported; } - - // An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' - // references a symbol that is at least declared as a module or a variable. The target of the 'export =' may - // combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). - function resolveESModuleSymbol(moduleSymbol: Symbol | undefined, referencingLocation: Node, dontResolveAlias: boolean, suppressInteropError: boolean): Symbol | undefined { - const symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias); - - if (!dontResolveAlias && symbol) { - if (!suppressInteropError && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable)) && !getDeclarationOfKind(symbol, SyntaxKind.SourceFile)) { - const compilerOptionName = moduleKind >= ModuleKind.ES2015 - ? "allowSyntheticDefaultImports" - : "esModuleInterop"; - - error(referencingLocation, Diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, compilerOptionName); - - return symbol; - } - - const referenceParent = referencingLocation.parent; - if ( - (isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || - isImportCall(referenceParent) - ) { - const reference = isImportCall(referenceParent) ? referenceParent.arguments[0] : referenceParent.moduleSpecifier; - const type = getTypeOfSymbol(symbol); - const defaultOnlyType = getTypeWithSyntheticDefaultOnly(type, symbol, moduleSymbol!, reference); - if (defaultOnlyType) { - return cloneTypeAsModuleType(symbol, defaultOnlyType, referenceParent); - } - - const targetFile = moduleSymbol?.declarations?.find(isSourceFile); - const isEsmCjsRef = targetFile && isESMFormatImportImportingCommonjsFormatFile(getUsageModeForExpression(reference), targetFile.impliedNodeFormat); - if (getESModuleInterop(compilerOptions) || isEsmCjsRef) { - let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call); - if (!sigs || !sigs.length) { - sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct); - } - if ( - (sigs && sigs.length) || - getPropertyOfType(type, InternalSymbolName.Default, /*skipObjectFunctionPropertyAugment*/ true) || - isEsmCjsRef - ) { - const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, reference); - return cloneTypeAsModuleType(symbol, moduleType, referenceParent); - } - } - } - } - return symbol; + const links = getSymbolLinks(exported); + if (links.cjsExportMerged) { + return links.cjsExportMerged; } - - /** - * Create a new symbol which has the module's type less the call and construct signatures - */ - function cloneTypeAsModuleType(symbol: Symbol, moduleType: Type, referenceParent: ImportDeclaration | ImportCall) { - const result = createSymbol(symbol.flags, symbol.escapedName); - result.declarations = symbol.declarations ? symbol.declarations.slice() : []; - result.parent = symbol.parent; - result.target = symbol; - result.originatingImport = referenceParent; - if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; - if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = new Map(symbol.members); - if (symbol.exports) result.exports = new Map(symbol.exports); - const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above - result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); - return result; + const merged = exported.flags & SymbolFlags.Transient ? exported : cloneSymbol(exported); + merged.flags = merged.flags | SymbolFlags.ValueModule; + if (merged.exports === undefined) { + merged.exports = createSymbolTable(); } + moduleSymbol.exports!.forEach((s, name) => { + if (name === InternalSymbolName.ExportEquals) return; + merged.exports!.set(name, merged.exports!.has(name) ? mergeSymbol(merged.exports!.get(name)!, s) : s); + }); + getSymbolLinks(merged).cjsExportMerged = merged; + return links.cjsExportMerged = merged; + } - function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { - return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== undefined; - } + // An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' + // references a symbol that is at least declared as a module or a variable. The target of the 'export =' may + // combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). + function resolveESModuleSymbol(moduleSymbol: Symbol | undefined, referencingLocation: Node, dontResolveAlias: boolean, suppressInteropError: boolean): Symbol | undefined { + const symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias); - function getExportsOfModuleAsArray(moduleSymbol: Symbol): Symbol[] { - return symbolsToArray(getExportsOfModule(moduleSymbol)); - } + if (!dontResolveAlias && symbol) { + if (!suppressInteropError && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable)) && !getDeclarationOfKind(symbol, SyntaxKind.SourceFile)) { + const compilerOptionName = moduleKind >= ModuleKind.ES2015 + ? "allowSyntheticDefaultImports" + : "esModuleInterop"; - function getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[] { - const exports = getExportsOfModuleAsArray(moduleSymbol); - const exportEquals = resolveExternalModuleSymbol(moduleSymbol); - if (exportEquals !== moduleSymbol) { - const type = getTypeOfSymbol(exportEquals); - if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { - addRange(exports, getPropertiesOfType(type)); - } + error(referencingLocation, Diagnostics.This_module_can_only_be_referenced_with_ECMAScript_imports_Slashexports_by_turning_on_the_0_flag_and_referencing_its_default_export, compilerOptionName); + + return symbol; } - return exports; - } - function forEachExportAndPropertyOfModule(moduleSymbol: Symbol, cb: (symbol: Symbol, key: __String) => void): void { - const exports = getExportsOfModule(moduleSymbol); - exports.forEach((symbol, key) => { - if (!isReservedMemberName(key)) { - cb(symbol, key); + const referenceParent = referencingLocation.parent; + if ( + (isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || + isImportCall(referenceParent) + ) { + const reference = isImportCall(referenceParent) ? referenceParent.arguments[0] : referenceParent.moduleSpecifier; + const type = getTypeOfSymbol(symbol); + const defaultOnlyType = getTypeWithSyntheticDefaultOnly(type, symbol, moduleSymbol!, reference); + if (defaultOnlyType) { + return cloneTypeAsModuleType(symbol, defaultOnlyType, referenceParent); } - }); - const exportEquals = resolveExternalModuleSymbol(moduleSymbol); - if (exportEquals !== moduleSymbol) { - const type = getTypeOfSymbol(exportEquals); - if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { - forEachPropertyOfType(type, (symbol, escapedName) => { - cb(symbol, escapedName); - }); + + const targetFile = moduleSymbol?.declarations?.find(isSourceFile); + const isEsmCjsRef = targetFile && isESMFormatImportImportingCommonjsFormatFile(getUsageModeForExpression(reference), targetFile.impliedNodeFormat); + if (getESModuleInterop(compilerOptions) || isEsmCjsRef) { + let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call); + if (!sigs || !sigs.length) { + sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct); + } + if ( + (sigs && sigs.length) || + getPropertyOfType(type, InternalSymbolName.Default, /*skipObjectFunctionPropertyAugment*/ true) || + isEsmCjsRef + ) { + const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, reference); + return cloneTypeAsModuleType(symbol, moduleType, referenceParent); + } } } } + return symbol; + } - function tryGetMemberInModuleExports(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { - const symbolTable = getExportsOfModule(moduleSymbol); - if (symbolTable) { - return symbolTable.get(memberName); - } - } + /** + * Create a new symbol which has the module's type less the call and construct signatures + */ + function cloneTypeAsModuleType(symbol: Symbol, moduleType: Type, referenceParent: ImportDeclaration | ImportCall) { + const result = createSymbol(symbol.flags, symbol.escapedName); + result.declarations = symbol.declarations ? symbol.declarations.slice() : []; + result.parent = symbol.parent; + result.target = symbol; + result.originatingImport = referenceParent; + if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; + if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; + if (symbol.members) result.members = new Map(symbol.members); + if (symbol.exports) result.exports = new Map(symbol.exports); + const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above + result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); + return result; + } - function tryGetMemberInModuleExportsAndProperties(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { - const symbol = tryGetMemberInModuleExports(memberName, moduleSymbol); - if (symbol) { - return symbol; - } + function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { + return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== undefined; + } - const exportEquals = resolveExternalModuleSymbol(moduleSymbol); - if (exportEquals === moduleSymbol) { - return undefined; - } + function getExportsOfModuleAsArray(moduleSymbol: Symbol): Symbol[] { + return symbolsToArray(getExportsOfModule(moduleSymbol)); + } + function getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[] { + const exports = getExportsOfModuleAsArray(moduleSymbol); + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals !== moduleSymbol) { const type = getTypeOfSymbol(exportEquals); - return shouldTreatPropertiesOfExternalModuleAsExports(type) ? getPropertyOfType(type, memberName) : undefined; + if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { + addRange(exports, getPropertiesOfType(type)); + } } + return exports; + } - function shouldTreatPropertiesOfExternalModuleAsExports(resolvedExternalModuleType: Type) { - return !(resolvedExternalModuleType.flags & TypeFlags.Primitive || - getObjectFlags(resolvedExternalModuleType) & ObjectFlags.Class || - // `isArrayOrTupleLikeType` is too expensive to use in this auto-imports hot path - isArrayType(resolvedExternalModuleType) || - isTupleType(resolvedExternalModuleType)); + function forEachExportAndPropertyOfModule(moduleSymbol: Symbol, cb: (symbol: Symbol, key: __String) => void): void { + const exports = getExportsOfModule(moduleSymbol); + exports.forEach((symbol, key) => { + if (!isReservedMemberName(key)) { + cb(symbol, key); + } + }); + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals !== moduleSymbol) { + const type = getTypeOfSymbol(exportEquals); + if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { + forEachPropertyOfType(type, (symbol, escapedName) => { + cb(symbol, escapedName); + }); + } } + } - function getExportsOfSymbol(symbol: Symbol): SymbolTable { - return symbol.flags & SymbolFlags.LateBindingContainer ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) : - symbol.flags & SymbolFlags.Module ? getExportsOfModule(symbol) : - symbol.exports || emptySymbols; + function tryGetMemberInModuleExports(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { + const symbolTable = getExportsOfModule(moduleSymbol); + if (symbolTable) { + return symbolTable.get(memberName); } + } - function getExportsOfModule(moduleSymbol: Symbol): SymbolTable { - const links = getSymbolLinks(moduleSymbol); - return links.resolvedExports || (links.resolvedExports = getExportsOfModuleWorker(moduleSymbol)); + function tryGetMemberInModuleExportsAndProperties(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { + const symbol = tryGetMemberInModuleExports(memberName, moduleSymbol); + if (symbol) { + return symbol; } - interface ExportCollisionTracker { - specifierText: string; - exportsWithDuplicate: ExportDeclaration[]; + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals === moduleSymbol) { + return undefined; } - type ExportCollisionTrackerTable = UnderscoreEscapedMap; + const type = getTypeOfSymbol(exportEquals); + return shouldTreatPropertiesOfExternalModuleAsExports(type) ? getPropertyOfType(type, memberName) : undefined; + } - /** - * Extends one symbol table with another while collecting information on name collisions for error message generation into the `lookupTable` argument - * Not passing `lookupTable` and `exportNode` disables this collection, and just extends the tables - */ - function extendExportSymbols(target: SymbolTable, source: SymbolTable | undefined, lookupTable?: ExportCollisionTrackerTable, exportNode?: ExportDeclaration) { - if (!source) return; - source.forEach((sourceSymbol, id) => { - if (id === InternalSymbolName.Default) return; + function shouldTreatPropertiesOfExternalModuleAsExports(resolvedExternalModuleType: Type) { + return !(resolvedExternalModuleType.flags & TypeFlags.Primitive || + getObjectFlags(resolvedExternalModuleType) & ObjectFlags.Class || + // `isArrayOrTupleLikeType` is too expensive to use in this auto-imports hot path + isArrayType(resolvedExternalModuleType) || + isTupleType(resolvedExternalModuleType)); + } - const targetSymbol = target.get(id); - if (!targetSymbol) { - target.set(id, sourceSymbol); - if (lookupTable && exportNode) { - lookupTable.set(id, { - specifierText: getTextOfNode(exportNode.moduleSpecifier!) - } as ExportCollisionTracker); - } - } - else if (lookupTable && exportNode && targetSymbol && resolveSymbol(targetSymbol) !== resolveSymbol(sourceSymbol)) { - const collisionTracker = lookupTable.get(id)!; - if (!collisionTracker.exportsWithDuplicate) { - collisionTracker.exportsWithDuplicate = [exportNode]; - } - else { - collisionTracker.exportsWithDuplicate.push(exportNode); - } - } - }); - } + function getExportsOfSymbol(symbol: Symbol): SymbolTable { + return symbol.flags & SymbolFlags.LateBindingContainer ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedExports) : + symbol.flags & SymbolFlags.Module ? getExportsOfModule(symbol) : + symbol.exports || emptySymbols; + } + + function getExportsOfModule(moduleSymbol: Symbol): SymbolTable { + const links = getSymbolLinks(moduleSymbol); + return links.resolvedExports || (links.resolvedExports = getExportsOfModuleWorker(moduleSymbol)); + } - function getExportsOfModuleWorker(moduleSymbol: Symbol): SymbolTable { - const visitedSymbols: Symbol[] = []; + interface ExportCollisionTracker { + specifierText: string; + exportsWithDuplicate: ExportDeclaration[]; + } - // A module defined by an 'export=' consists of one export that needs to be resolved - moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); + type ExportCollisionTrackerTable = UnderscoreEscapedMap; - return visit(moduleSymbol) || emptySymbols; + /** + * Extends one symbol table with another while collecting information on name collisions for error message generation into the `lookupTable` argument + * Not passing `lookupTable` and `exportNode` disables this collection, and just extends the tables + */ + function extendExportSymbols(target: SymbolTable, source: SymbolTable | undefined, lookupTable?: ExportCollisionTrackerTable, exportNode?: ExportDeclaration) { + if (!source) return; + source.forEach((sourceSymbol, id) => { + if (id === InternalSymbolName.Default) return; - // The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example, - // module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error. - function visit(symbol: Symbol | undefined): SymbolTable | undefined { - if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) { - return; + const targetSymbol = target.get(id); + if (!targetSymbol) { + target.set(id, sourceSymbol); + if (lookupTable && exportNode) { + lookupTable.set(id, { + specifierText: getTextOfNode(exportNode.moduleSpecifier!) + } as ExportCollisionTracker); } - const symbols = new Map(symbol.exports); - // All export * declarations are collected in an __export symbol by the binder - const exportStars = symbol.exports.get(InternalSymbolName.ExportStar); - if (exportStars) { - const nestedSymbols = createSymbolTable(); - const lookupTable: ExportCollisionTrackerTable = new Map(); - if (exportStars.declarations) { - for (const node of exportStars.declarations) { - const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); - const exportedSymbols = visit(resolvedModule); - extendExportSymbols( - nestedSymbols, - exportedSymbols, - lookupTable, - node as ExportDeclaration - ); - } - } - lookupTable.forEach(({ exportsWithDuplicate }, id) => { - // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself - if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols.has(id)) { - return; - } - for (const node of exportsWithDuplicate) { - diagnostics.add(createDiagnosticForNode( - node, - Diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, - lookupTable.get(id)!.specifierText, - unescapeLeadingUnderscores(id) - )); - } - }); - extendExportSymbols(symbols, nestedSymbols); + } + else if (lookupTable && exportNode && targetSymbol && resolveSymbol(targetSymbol) !== resolveSymbol(sourceSymbol)) { + const collisionTracker = lookupTable.get(id)!; + if (!collisionTracker.exportsWithDuplicate) { + collisionTracker.exportsWithDuplicate = [exportNode]; + } + else { + collisionTracker.exportsWithDuplicate.push(exportNode); } - return symbols; } - } - - function getMergedSymbol(symbol: Symbol): Symbol; - function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined; - function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined { - let merged: Symbol; - return symbol && symbol.mergeId && (merged = mergedSymbols[symbol.mergeId]) ? merged : symbol; - } + }); + } - function getSymbolOfNode(node: Declaration): Symbol; - function getSymbolOfNode(node: Node): Symbol | undefined; - function getSymbolOfNode(node: Node): Symbol | undefined { - return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol)); - } + function getExportsOfModuleWorker(moduleSymbol: Symbol): SymbolTable { + const visitedSymbols: Symbol[] = []; - function getParentOfSymbol(symbol: Symbol): Symbol | undefined { - return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); - } + // A module defined by an 'export=' consists of one export that needs to be resolved + moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); - function getAlternativeContainingModules(symbol: Symbol, enclosingDeclaration: Node): Symbol[] { - const containingFile = getSourceFileOfNode(enclosingDeclaration); - const id = getNodeId(containingFile); - const links = getSymbolLinks(symbol); - let results: Symbol[] | undefined; - if (links.extendedContainersByFile && (results = links.extendedContainersByFile.get(id))) { - return results; - } - if (containingFile && containingFile.imports) { - // Try to make an import using an import already in the enclosing file, if possible - for (const importRef of containingFile.imports) { - if (nodeIsSynthesized(importRef)) continue; // Synthetic names can't be resolved by `resolveExternalModuleName` - they'll cause a debug assert if they error - const resolvedModule = resolveExternalModuleName(enclosingDeclaration, importRef, /*ignoreErrors*/ true); - if (!resolvedModule) continue; - const ref = getAliasForSymbolInContainer(resolvedModule, symbol); - if (!ref) continue; - results = append(results, resolvedModule); - } - if (length(results)) { - (links.extendedContainersByFile || (links.extendedContainersByFile = new Map())).set(id, results!); - return results!; - } - } - if (links.extendedContainers) { - return links.extendedContainers; - } - // No results from files already being imported by this file - expand search (expensive, but not location-specific, so cached) - const otherFiles = host.getSourceFiles(); - for (const file of otherFiles) { - if (!isExternalModule(file)) continue; - const sym = getSymbolOfNode(file); - const ref = getAliasForSymbolInContainer(sym, symbol); - if (!ref) continue; - results = append(results, sym); - } - return links.extendedContainers = results || emptyArray; - } + return visit(moduleSymbol) || emptySymbols; - /** - * Attempts to find the symbol corresponding to the container a symbol is in - usually this - * is just its' `.parent`, but for locals, this value is `undefined` - */ - function getContainersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): Symbol[] | undefined { - const container = getParentOfSymbol(symbol); - // Type parameters end up in the `members` lists but are not externally visible - if (container && !(symbol.flags & SymbolFlags.TypeParameter)) { - const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer); - const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration); - const objectLiteralContainer = getVariableDeclarationOfObjectLiteral(container, meaning); - if ( - enclosingDeclaration && - container.flags & getQualifiedLeftMeaning(meaning) && - getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false) - ) { - return append(concatenate(concatenate([container], additionalContainers), reexportContainers), objectLiteralContainer); // This order expresses a preference for the real container if it is in scope - } - // we potentially have a symbol which is a member of the instance side of something - look for a variable in scope with the container's type - // which may be acting like a namespace (eg, `Symbol` acts like a namespace when looking up `Symbol.toStringTag`) - const firstVariableMatch = !(container.flags & getQualifiedLeftMeaning(meaning)) - && container.flags & SymbolFlags.Type - && getDeclaredTypeOfSymbol(container).flags & TypeFlags.Object - && meaning === SymbolFlags.Value - ? forEachSymbolTableInScope(enclosingDeclaration, t => { - return forEachEntry(t, s => { - if (s.flags & getQualifiedLeftMeaning(meaning) && getTypeOfSymbol(s) === getDeclaredTypeOfSymbol(container)) { - return s; - } - }); - }) : undefined; - let res = firstVariableMatch ? [firstVariableMatch, ...additionalContainers, container] : [...additionalContainers, container]; - res = append(res, objectLiteralContainer); - res = addRange(res, reexportContainers); - return res; + // The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example, + // module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error. + function visit(symbol: Symbol | undefined): SymbolTable | undefined { + if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) { + return; } - const candidates = mapDefined(symbol.declarations, d => { - if (!isAmbientModule(d) && d.parent){ - // direct children of a module - if (hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { - return getSymbolOfNode(d.parent); - } - // export ='d member of an ambient module - if (isModuleBlock(d.parent) && d.parent.parent && resolveExternalModuleSymbol(getSymbolOfNode(d.parent.parent)) === symbol) { - return getSymbolOfNode(d.parent.parent); + const symbols = new Map(symbol.exports); + // All export * declarations are collected in an __export symbol by the binder + const exportStars = symbol.exports.get(InternalSymbolName.ExportStar); + if (exportStars) { + const nestedSymbols = createSymbolTable(); + const lookupTable: ExportCollisionTrackerTable = new Map(); + if (exportStars.declarations) { + for (const node of exportStars.declarations) { + const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); + const exportedSymbols = visit(resolvedModule); + extendExportSymbols( + nestedSymbols, + exportedSymbols, + lookupTable, + node as ExportDeclaration + ); } } - if (isClassExpression(d) && isBinaryExpression(d.parent) && d.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAccessExpression(d.parent.left) && isEntityNameExpression(d.parent.left.expression)) { - if (isModuleExportsAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) { - return getSymbolOfNode(getSourceFileOfNode(d)); + lookupTable.forEach(({ exportsWithDuplicate }, id) => { + // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself + if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols.has(id)) { + return; } - checkExpressionCached(d.parent.left.expression); - return getNodeLinks(d.parent.left.expression).resolvedSymbol; - } - }); - if (!length(candidates)) { - return undefined; - } - return mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined); - - function fileSymbolIfFileSymbolExportEqualsContainer(d: Declaration) { - return container && getFileSymbolIfFileSymbolExportEqualsContainer(d, container); + for (const node of exportsWithDuplicate) { + diagnostics.add(createDiagnosticForNode( + node, + Diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, + lookupTable.get(id)!.specifierText, + unescapeLeadingUnderscores(id) + )); + } + }); + extendExportSymbols(symbols, nestedSymbols); } + return symbols; } + } - function getVariableDeclarationOfObjectLiteral(symbol: Symbol, meaning: SymbolFlags) { - // If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct - // from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however, - // we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal. - const firstDecl: Node | false = !!length(symbol.declarations) && first(symbol.declarations!); - if (meaning & SymbolFlags.Value && firstDecl && firstDecl.parent && isVariableDeclaration(firstDecl.parent)) { - if (isObjectLiteralExpression(firstDecl) && firstDecl === firstDecl.parent.initializer || isTypeLiteralNode(firstDecl) && firstDecl === firstDecl.parent.type) { - return getSymbolOfNode(firstDecl.parent); - } - } - } + function getMergedSymbol(symbol: Symbol): Symbol; + function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined; + function getMergedSymbol(symbol: Symbol | undefined): Symbol | undefined { + let merged: Symbol; + return symbol && symbol.mergeId && (merged = mergedSymbols[symbol.mergeId]) ? merged : symbol; + } - function getFileSymbolIfFileSymbolExportEqualsContainer(d: Declaration, container: Symbol) { - const fileSymbol = getExternalModuleContainer(d); - const exported = fileSymbol && fileSymbol.exports && fileSymbol.exports.get(InternalSymbolName.ExportEquals); - return exported && getSymbolIfSameReference(exported, container) ? fileSymbol : undefined; - } + function getSymbolOfNode(node: Declaration): Symbol; + function getSymbolOfNode(node: Node): Symbol | undefined; + function getSymbolOfNode(node: Node): Symbol | undefined { + return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol)); + } - function getAliasForSymbolInContainer(container: Symbol, symbol: Symbol) { - if (container === getParentOfSymbol(symbol)) { - // fast path, `symbol` is either already the alias or isn't aliased - return symbol; - } - // Check if container is a thing with an `export=` which points directly at `symbol`, and if so, return - // the container itself as the alias for the symbol - const exportEquals = container.exports && container.exports.get(InternalSymbolName.ExportEquals); - if (exportEquals && getSymbolIfSameReference(exportEquals, symbol)) { - return container; + function getParentOfSymbol(symbol: Symbol): Symbol | undefined { + return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); + } + + function getAlternativeContainingModules(symbol: Symbol, enclosingDeclaration: Node): Symbol[] { + const containingFile = getSourceFileOfNode(enclosingDeclaration); + const id = getNodeId(containingFile); + const links = getSymbolLinks(symbol); + let results: Symbol[] | undefined; + if (links.extendedContainersByFile && (results = links.extendedContainersByFile.get(id))) { + return results; + } + if (containingFile && containingFile.imports) { + // Try to make an import using an import already in the enclosing file, if possible + for (const importRef of containingFile.imports) { + if (nodeIsSynthesized(importRef)) continue; // Synthetic names can't be resolved by `resolveExternalModuleName` - they'll cause a debug assert if they error + const resolvedModule = resolveExternalModuleName(enclosingDeclaration, importRef, /*ignoreErrors*/ true); + if (!resolvedModule) continue; + const ref = getAliasForSymbolInContainer(resolvedModule, symbol); + if (!ref) continue; + results = append(results, resolvedModule); } - const exports = getExportsOfSymbol(container); - const quick = exports.get(symbol.escapedName); - if (quick && getSymbolIfSameReference(quick, symbol)) { - return quick; + if (length(results)) { + (links.extendedContainersByFile || (links.extendedContainersByFile = new Map())).set(id, results!); + return results!; } - return forEachEntry(exports, exported => { - if (getSymbolIfSameReference(exported, symbol)) { - return exported; - } - }); } - - /** - * Checks if two symbols, through aliasing and/or merging, refer to the same thing - */ - function getSymbolIfSameReference(s1: Symbol, s2: Symbol) { - if (getMergedSymbol(resolveSymbol(getMergedSymbol(s1))) === getMergedSymbol(resolveSymbol(getMergedSymbol(s2)))) { - return s1; - } + if (links.extendedContainers) { + return links.extendedContainers; } - - function getExportSymbolOfValueSymbolIfExported(symbol: Symbol): Symbol; - function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined; - function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined { - return getMergedSymbol(symbol && (symbol.flags & SymbolFlags.ExportValue) !== 0 && symbol.exportSymbol || symbol); + // No results from files already being imported by this file - expand search (expensive, but not location-specific, so cached) + const otherFiles = host.getSourceFiles(); + for (const file of otherFiles) { + if (!isExternalModule(file)) continue; + const sym = getSymbolOfNode(file); + const ref = getAliasForSymbolInContainer(sym, symbol); + if (!ref) continue; + results = append(results, sym); } + return links.extendedContainers = results || emptyArray; + } - function symbolIsValue(symbol: Symbol, includeTypeOnlyMembers?: boolean): boolean { - return !!( - symbol.flags & SymbolFlags.Value || - symbol.flags & SymbolFlags.Alias && getAllSymbolFlags(symbol) & SymbolFlags.Value && (includeTypeOnlyMembers || !getTypeOnlyAliasDeclaration(symbol))); + /** + * Attempts to find the symbol corresponding to the container a symbol is in - usually this + * is just its' `.parent`, but for locals, this value is `undefined` + */ + function getContainersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): Symbol[] | undefined { + const container = getParentOfSymbol(symbol); + // Type parameters end up in the `members` lists but are not externally visible + if (container && !(symbol.flags & SymbolFlags.TypeParameter)) { + const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer); + const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration); + const objectLiteralContainer = getVariableDeclarationOfObjectLiteral(container, meaning); + if ( + enclosingDeclaration && + container.flags & getQualifiedLeftMeaning(meaning) && + getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false) + ) { + return append(concatenate(concatenate([container], additionalContainers), reexportContainers), objectLiteralContainer); // This order expresses a preference for the real container if it is in scope + } + // we potentially have a symbol which is a member of the instance side of something - look for a variable in scope with the container's type + // which may be acting like a namespace (eg, `Symbol` acts like a namespace when looking up `Symbol.toStringTag`) + const firstVariableMatch = !(container.flags & getQualifiedLeftMeaning(meaning)) + && container.flags & SymbolFlags.Type + && getDeclaredTypeOfSymbol(container).flags & TypeFlags.Object + && meaning === SymbolFlags.Value + ? forEachSymbolTableInScope(enclosingDeclaration, t => { + return forEachEntry(t, s => { + if (s.flags & getQualifiedLeftMeaning(meaning) && getTypeOfSymbol(s) === getDeclaredTypeOfSymbol(container)) { + return s; + } + }); + }) : undefined; + let res = firstVariableMatch ? [firstVariableMatch, ...additionalContainers, container] : [...additionalContainers, container]; + res = append(res, objectLiteralContainer); + res = addRange(res, reexportContainers); + return res; } - - function findConstructorDeclaration(node: ClassLikeDeclaration): ConstructorDeclaration | undefined { - const members = node.members; - for (const member of members) { - if (member.kind === SyntaxKind.Constructor && nodeIsPresent((member as ConstructorDeclaration).body)) { - return member as ConstructorDeclaration; + const candidates = mapDefined(symbol.declarations, d => { + if (!isAmbientModule(d) && d.parent){ + // direct children of a module + if (hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { + return getSymbolOfNode(d.parent); + } + // export ='d member of an ambient module + if (isModuleBlock(d.parent) && d.parent.parent && resolveExternalModuleSymbol(getSymbolOfNode(d.parent.parent)) === symbol) { + return getSymbolOfNode(d.parent.parent); + } + } + if (isClassExpression(d) && isBinaryExpression(d.parent) && d.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAccessExpression(d.parent.left) && isEntityNameExpression(d.parent.left.expression)) { + if (isModuleExportsAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) { + return getSymbolOfNode(getSourceFileOfNode(d)); } + checkExpressionCached(d.parent.left.expression); + return getNodeLinks(d.parent.left.expression).resolvedSymbol; } + }); + if (!length(candidates)) { + return undefined; } + return mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined); - function createType(flags: TypeFlags): Type { - const result = new Type(checker, flags); - typeCount++; - result.id = typeCount; - tracing?.recordType(result); - return result; + function fileSymbolIfFileSymbolExportEqualsContainer(d: Declaration) { + return container && getFileSymbolIfFileSymbolExportEqualsContainer(d, container); } + } - function createTypeWithSymbol(flags: TypeFlags, symbol: Symbol): Type { - const result = createType(flags); - result.symbol = symbol; - return result; + function getVariableDeclarationOfObjectLiteral(symbol: Symbol, meaning: SymbolFlags) { + // If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct + // from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however, + // we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal. + const firstDecl: Node | false = !!length(symbol.declarations) && first(symbol.declarations!); + if (meaning & SymbolFlags.Value && firstDecl && firstDecl.parent && isVariableDeclaration(firstDecl.parent)) { + if (isObjectLiteralExpression(firstDecl) && firstDecl === firstDecl.parent.initializer || isTypeLiteralNode(firstDecl) && firstDecl === firstDecl.parent.type) { + return getSymbolOfNode(firstDecl.parent); + } } + } - function createOriginType(flags: TypeFlags): Type { - return new Type(checker, flags); - } + function getFileSymbolIfFileSymbolExportEqualsContainer(d: Declaration, container: Symbol) { + const fileSymbol = getExternalModuleContainer(d); + const exported = fileSymbol && fileSymbol.exports && fileSymbol.exports.get(InternalSymbolName.ExportEquals); + return exported && getSymbolIfSameReference(exported, container) ? fileSymbol : undefined; + } - function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags: ObjectFlags = 0): IntrinsicType { - const type = createType(kind) as IntrinsicType; - type.intrinsicName = intrinsicName; - type.objectFlags = objectFlags; - return type; + function getAliasForSymbolInContainer(container: Symbol, symbol: Symbol) { + if (container === getParentOfSymbol(symbol)) { + // fast path, `symbol` is either already the alias or isn't aliased + return symbol; } - - function createObjectType(objectFlags: ObjectFlags, symbol?: Symbol): ObjectType { - const type = createTypeWithSymbol(TypeFlags.Object, symbol!) as ObjectType; - type.objectFlags = objectFlags; - type.members = undefined; - type.properties = undefined; - type.callSignatures = undefined; - type.constructSignatures = undefined; - type.indexInfos = undefined; - return type; + // Check if container is a thing with an `export=` which points directly at `symbol`, and if so, return + // the container itself as the alias for the symbol + const exportEquals = container.exports && container.exports.get(InternalSymbolName.ExportEquals); + if (exportEquals && getSymbolIfSameReference(exportEquals, symbol)) { + return container; } - - function createTypeofType() { - return getUnionType(arrayFrom(typeofNEFacts.keys(), getStringLiteralType)); + const exports = getExportsOfSymbol(container); + const quick = exports.get(symbol.escapedName); + if (quick && getSymbolIfSameReference(quick, symbol)) { + return quick; } + return forEachEntry(exports, exported => { + if (getSymbolIfSameReference(exported, symbol)) { + return exported; + } + }); + } - function createTypeParameter(symbol?: Symbol) { - return createTypeWithSymbol(TypeFlags.TypeParameter, symbol!) as TypeParameter; + /** + * Checks if two symbols, through aliasing and/or merging, refer to the same thing + */ + function getSymbolIfSameReference(s1: Symbol, s2: Symbol) { + if (getMergedSymbol(resolveSymbol(getMergedSymbol(s1))) === getMergedSymbol(resolveSymbol(getMergedSymbol(s2)))) { + return s1; } + } - // A reserved member name starts with two underscores, but the third character cannot be an underscore, - // @, or #. A third underscore indicates an escaped form of an identifier that started - // with at least two underscores. The @ character indicates that the name is denoted by a well known ES - // Symbol instance and the # character indicates that the name is a PrivateIdentifier. - function isReservedMemberName(name: __String) { - return (name as string).charCodeAt(0) === CharacterCodes._ && - (name as string).charCodeAt(1) === CharacterCodes._ && - (name as string).charCodeAt(2) !== CharacterCodes._ && - (name as string).charCodeAt(2) !== CharacterCodes.at && - (name as string).charCodeAt(2) !== CharacterCodes.hash; - } + function getExportSymbolOfValueSymbolIfExported(symbol: Symbol): Symbol; + function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined; + function getExportSymbolOfValueSymbolIfExported(symbol: Symbol | undefined): Symbol | undefined { + return getMergedSymbol(symbol && (symbol.flags & SymbolFlags.ExportValue) !== 0 && symbol.exportSymbol || symbol); + } - function getNamedMembers(members: SymbolTable): Symbol[] { - let result: Symbol[] | undefined; - members.forEach((symbol, id) => { - if (isNamedMember(symbol, id)) { - (result || (result = [])).push(symbol); - } - }); - return result || emptyArray; - } + function symbolIsValue(symbol: Symbol, includeTypeOnlyMembers?: boolean): boolean { + return !!( + symbol.flags & SymbolFlags.Value || + symbol.flags & SymbolFlags.Alias && getAllSymbolFlags(symbol) & SymbolFlags.Value && (includeTypeOnlyMembers || !getTypeOnlyAliasDeclaration(symbol))); + } - function isNamedMember(member: Symbol, escapedName: __String) { - return !isReservedMemberName(escapedName) && symbolIsValue(member); + function findConstructorDeclaration(node: ClassLikeDeclaration): ConstructorDeclaration | undefined { + const members = node.members; + for (const member of members) { + if (member.kind === SyntaxKind.Constructor && nodeIsPresent((member as ConstructorDeclaration).body)) { + return member as ConstructorDeclaration; + } } + } - function getNamedOrIndexSignatureMembers(members: SymbolTable): Symbol[] { - const result = getNamedMembers(members); - const index = getIndexSymbolFromSymbolTable(members); - return index ? concatenate(result, [index]) : result; - } + function createType(flags: TypeFlags): Type { + const result = new Type(checker, flags); + typeCount++; + result.id = typeCount; + tracing?.recordType(result); + return result; + } - function setStructuredTypeMembers(type: StructuredType, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], indexInfos: readonly IndexInfo[]): ResolvedType { - const resolved = type as ResolvedType; - resolved.members = members; - resolved.properties = emptyArray; - resolved.callSignatures = callSignatures; - resolved.constructSignatures = constructSignatures; - resolved.indexInfos = indexInfos; - // This can loop back to getPropertyOfType() which would crash if `callSignatures` & `constructSignatures` are not initialized. - if (members !== emptySymbols) resolved.properties = getNamedMembers(members); - return resolved; - } + function createTypeWithSymbol(flags: TypeFlags, symbol: Symbol): Type { + const result = createType(flags); + result.symbol = symbol; + return result; + } - function createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], indexInfos: readonly IndexInfo[]): ResolvedType { - return setStructuredTypeMembers(createObjectType(ObjectFlags.Anonymous, symbol), - members, callSignatures, constructSignatures, indexInfos); - } + function createOriginType(flags: TypeFlags): Type { + return new Type(checker, flags); + } - function getResolvedTypeWithoutAbstractConstructSignatures(type: ResolvedType) { - if (type.constructSignatures.length === 0) return type; - if (type.objectTypeWithoutAbstractConstructSignatures) return type.objectTypeWithoutAbstractConstructSignatures; - const constructSignatures = filter(type.constructSignatures, signature => !(signature.flags & SignatureFlags.Abstract)); - if (type.constructSignatures === constructSignatures) return type; - const typeCopy = createAnonymousType( - type.symbol, - type.members, - type.callSignatures, - some(constructSignatures) ? constructSignatures : emptyArray, - type.indexInfos); - type.objectTypeWithoutAbstractConstructSignatures = typeCopy; - typeCopy.objectTypeWithoutAbstractConstructSignatures = typeCopy; - return typeCopy; - } + function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags: ObjectFlags = 0): IntrinsicType { + const type = createType(kind) as IntrinsicType; + type.intrinsicName = intrinsicName; + type.objectFlags = objectFlags; + return type; + } - function forEachSymbolTableInScope(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable, ignoreQualification?: boolean, isLocalNameLookup?: boolean, scopeNode?: Node) => T): T { - let result: T; - for (let location = enclosingDeclaration; location; location = location.parent) { - // Locals of a source file are not in scope (because they get merged into the global symbol table) - if (location.locals && !isGlobalSourceFile(location)) { - if (result = callback(location.locals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { - return result; - } - } - switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalOrCommonJsModule(location as SourceFile)) { - break; - } - // falls through - case SyntaxKind.ModuleDeclaration: - const sym = getSymbolOfNode(location as ModuleDeclaration); - // `sym` may not have exports if this module declaration is backed by the symbol for a `const` that's being rewritten - // into a namespace - in such cases, it's best to just let the namespace appear empty (the const members couldn't have referred - // to one another anyway) - if (result = callback(sym?.exports || emptySymbols, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { - return result; - } - break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - // Type parameters are bound into `members` lists so they can merge across declarations - // This is troublesome, since in all other respects, they behave like locals :cries: - // TODO: the below is shared with similar code in `resolveName` - in fact, rephrasing all this symbol - // lookup logic in terms of `resolveName` would be nice - // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals - // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would - // trigger resolving late-bound names, which we may already be in the process of doing while we're here! - let table: UnderscoreEscapedMap | undefined; - // TODO: Should this filtered table be cached in some way? - (getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols).forEach((memberSymbol, key) => { - if (memberSymbol.flags & (SymbolFlags.Type & ~SymbolFlags.Assignment)) { - (table || (table = createSymbolTable())).set(key, memberSymbol); - } - }); - if (table && (result = callback(table, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ false, location))) { - return result; - } - break; - } - } + function createObjectType(objectFlags: ObjectFlags, symbol?: Symbol): ObjectType { + const type = createTypeWithSymbol(TypeFlags.Object, symbol!) as ObjectType; + type.objectFlags = objectFlags; + type.members = undefined; + type.properties = undefined; + type.callSignatures = undefined; + type.constructSignatures = undefined; + type.indexInfos = undefined; + return type; + } - return callback(globals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); - } + function createTypeofType() { + return getUnionType(arrayFrom(typeofNEFacts.keys(), getStringLiteralType)); + } - function getQualifiedLeftMeaning(rightMeaning: SymbolFlags) { - // If we are looking in value space, the parent meaning is value, other wise it is namespace - return rightMeaning === SymbolFlags.Value ? SymbolFlags.Value : SymbolFlags.Namespace; - } + function createTypeParameter(symbol?: Symbol) { + return createTypeWithSymbol(TypeFlags.TypeParameter, symbol!) as TypeParameter; + } - function getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap: ESMap = new Map()): Symbol[] | undefined { - if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) { - return undefined; - } - const links = getSymbolLinks(symbol); - const cache = (links.accessibleChainCache ||= new Map()); - // Go from enclosingDeclaration to the first scope we check, so the cache is keyed off the scope and thus shared more - const firstRelevantLocation = forEachSymbolTableInScope(enclosingDeclaration, (_, __, ___, node) => node); - const key = `${useOnlyExternalAliasing ? 0 : 1}|${firstRelevantLocation && getNodeId(firstRelevantLocation)}|${meaning}`; - if (cache.has(key)) { - return cache.get(key); - } + // A reserved member name starts with two underscores, but the third character cannot be an underscore, + // @, or #. A third underscore indicates an escaped form of an identifier that started + // with at least two underscores. The @ character indicates that the name is denoted by a well known ES + // Symbol instance and the # character indicates that the name is a PrivateIdentifier. + function isReservedMemberName(name: __String) { + return (name as string).charCodeAt(0) === CharacterCodes._ && + (name as string).charCodeAt(1) === CharacterCodes._ && + (name as string).charCodeAt(2) !== CharacterCodes._ && + (name as string).charCodeAt(2) !== CharacterCodes.at && + (name as string).charCodeAt(2) !== CharacterCodes.hash; + } - const id = getSymbolId(symbol); - let visitedSymbolTables = visitedSymbolTablesMap.get(id); - if (!visitedSymbolTables) { - visitedSymbolTablesMap.set(id, visitedSymbolTables = []); + function getNamedMembers(members: SymbolTable): Symbol[] { + let result: Symbol[] | undefined; + members.forEach((symbol, id) => { + if (isNamedMember(symbol, id)) { + (result || (result = [])).push(symbol); } - const result = forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable); - cache.set(key, result); - return result; - - /** - * @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already) - */ - function getAccessibleSymbolChainFromSymbolTable(symbols: SymbolTable, ignoreQualification?: boolean, isLocalNameLookup?: boolean): Symbol[] | undefined { - if (!pushIfUnique(visitedSymbolTables!, symbols)) { - return undefined; - } + }); + return result || emptyArray; + } - const result = trySymbolTable(symbols, ignoreQualification, isLocalNameLookup); - visitedSymbolTables!.pop(); - return result; - } + function isNamedMember(member: Symbol, escapedName: __String) { + return !isReservedMemberName(escapedName) && symbolIsValue(member); + } - function canQualifySymbol(symbolFromSymbolTable: Symbol, meaning: SymbolFlags) { - // If the symbol is equivalent and doesn't need further qualification, this symbol is accessible - return !needsQualification(symbolFromSymbolTable, enclosingDeclaration, meaning) || - // If symbol needs qualification, make sure that parent is accessible, if it is then this symbol is accessible too - !!getAccessibleSymbolChain(symbolFromSymbolTable.parent, enclosingDeclaration, getQualifiedLeftMeaning(meaning), useOnlyExternalAliasing, visitedSymbolTablesMap); - } - - function isAccessible(symbolFromSymbolTable: Symbol, resolvedAliasSymbol?: Symbol, ignoreQualification?: boolean) { - return (symbol === (resolvedAliasSymbol || symbolFromSymbolTable) || getMergedSymbol(symbol) === getMergedSymbol(resolvedAliasSymbol || symbolFromSymbolTable)) && - // if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table) - // and if symbolFromSymbolTable or alias resolution matches the symbol, - // check the symbol can be qualified, it is only then this symbol is accessible - !some(symbolFromSymbolTable.declarations, hasNonGlobalAugmentationExternalModuleSymbol) && - (ignoreQualification || canQualifySymbol(getMergedSymbol(symbolFromSymbolTable), meaning)); - } - - function trySymbolTable(symbols: SymbolTable, ignoreQualification: boolean | undefined, isLocalNameLookup: boolean | undefined): Symbol[] | undefined { - // If symbol is directly available by its name in the symbol table - if (isAccessible(symbols.get(symbol!.escapedName)!, /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { - return [symbol!]; - } - - // Check if symbol is any of the aliases in scope - const result = forEachEntry(symbols, symbolFromSymbolTable => { - if (symbolFromSymbolTable.flags & SymbolFlags.Alias - && symbolFromSymbolTable.escapedName !== InternalSymbolName.ExportEquals - && symbolFromSymbolTable.escapedName !== InternalSymbolName.Default - && !(isUMDExportSymbol(symbolFromSymbolTable) && enclosingDeclaration && isExternalModule(getSourceFileOfNode(enclosingDeclaration))) - // If `!useOnlyExternalAliasing`, we can use any type of alias to get the name - && (!useOnlyExternalAliasing || some(symbolFromSymbolTable.declarations, isExternalModuleImportEqualsDeclaration)) - // If we're looking up a local name to reference directly, omit namespace reexports, otherwise when we're trawling through an export list to make a dotted name, we can keep it - && (isLocalNameLookup ? !some(symbolFromSymbolTable.declarations, isNamespaceReexportDeclaration) : true) - // While exports are generally considered to be in scope, export-specifier declared symbols are _not_ - // See similar comment in `resolveName` for details - && (ignoreQualification || !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) - ) { + function getNamedOrIndexSignatureMembers(members: SymbolTable): Symbol[] { + const result = getNamedMembers(members); + const index = getIndexSymbolFromSymbolTable(members); + return index ? concatenate(result, [index]) : result; + } - const resolvedImportedSymbol = resolveAlias(symbolFromSymbolTable); - const candidate = getCandidateListForSymbol(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification); - if (candidate) { - return candidate; - } - } - if (symbolFromSymbolTable.escapedName === symbol!.escapedName && symbolFromSymbolTable.exportSymbol) { - if (isAccessible(getMergedSymbol(symbolFromSymbolTable.exportSymbol), /*aliasSymbol*/ undefined, ignoreQualification)) { - return [symbol!]; - } - } - }); + function setStructuredTypeMembers(type: StructuredType, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], indexInfos: readonly IndexInfo[]): ResolvedType { + const resolved = type as ResolvedType; + resolved.members = members; + resolved.properties = emptyArray; + resolved.callSignatures = callSignatures; + resolved.constructSignatures = constructSignatures; + resolved.indexInfos = indexInfos; + // This can loop back to getPropertyOfType() which would crash if `callSignatures` & `constructSignatures` are not initialized. + if (members !== emptySymbols) resolved.properties = getNamedMembers(members); + return resolved; + } - // If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that - return result || (symbols === globals ? getCandidateListForSymbol(globalThisSymbol, globalThisSymbol, ignoreQualification) : undefined); - } + function createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], indexInfos: readonly IndexInfo[]): ResolvedType { + return setStructuredTypeMembers(createObjectType(ObjectFlags.Anonymous, symbol), + members, callSignatures, constructSignatures, indexInfos); + } - function getCandidateListForSymbol(symbolFromSymbolTable: Symbol, resolvedImportedSymbol: Symbol, ignoreQualification: boolean | undefined) { - if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) { - return [symbolFromSymbolTable]; - } + function getResolvedTypeWithoutAbstractConstructSignatures(type: ResolvedType) { + if (type.constructSignatures.length === 0) return type; + if (type.objectTypeWithoutAbstractConstructSignatures) return type.objectTypeWithoutAbstractConstructSignatures; + const constructSignatures = filter(type.constructSignatures, signature => !(signature.flags & SignatureFlags.Abstract)); + if (type.constructSignatures === constructSignatures) return type; + const typeCopy = createAnonymousType( + type.symbol, + type.members, + type.callSignatures, + some(constructSignatures) ? constructSignatures : emptyArray, + type.indexInfos); + type.objectTypeWithoutAbstractConstructSignatures = typeCopy; + typeCopy.objectTypeWithoutAbstractConstructSignatures = typeCopy; + return typeCopy; + } - // Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain - // but only if the symbolFromSymbolTable can be qualified - const candidateTable = getExportsOfSymbol(resolvedImportedSymbol); - const accessibleSymbolsFromExports = candidateTable && getAccessibleSymbolChainFromSymbolTable(candidateTable, /*ignoreQualification*/ true); - if (accessibleSymbolsFromExports && canQualifySymbol(symbolFromSymbolTable, getQualifiedLeftMeaning(meaning))) { - return [symbolFromSymbolTable].concat(accessibleSymbolsFromExports); + function forEachSymbolTableInScope(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable, ignoreQualification?: boolean, isLocalNameLookup?: boolean, scopeNode?: Node) => T): T { + let result: T; + for (let location = enclosingDeclaration; location; location = location.parent) { + // Locals of a source file are not in scope (because they get merged into the global symbol table) + if (location.locals && !isGlobalSourceFile(location)) { + if (result = callback(location.locals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { + return result; } } + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalOrCommonJsModule(location as SourceFile)) { + break; + } + // falls through + case SyntaxKind.ModuleDeclaration: + const sym = getSymbolOfNode(location as ModuleDeclaration); + // `sym` may not have exports if this module declaration is backed by the symbol for a `const` that's being rewritten + // into a namespace - in such cases, it's best to just let the namespace appear empty (the const members couldn't have referred + // to one another anyway) + if (result = callback(sym?.exports || emptySymbols, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true, location)) { + return result; + } + break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + // Type parameters are bound into `members` lists so they can merge across declarations + // This is troublesome, since in all other respects, they behave like locals :cries: + // TODO: the below is shared with similar code in `resolveName` - in fact, rephrasing all this symbol + // lookup logic in terms of `resolveName` would be nice + // The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals + // These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would + // trigger resolving late-bound names, which we may already be in the process of doing while we're here! + let table: UnderscoreEscapedMap | undefined; + // TODO: Should this filtered table be cached in some way? + (getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols).forEach((memberSymbol, key) => { + if (memberSymbol.flags & (SymbolFlags.Type & ~SymbolFlags.Assignment)) { + (table || (table = createSymbolTable())).set(key, memberSymbol); + } + }); + if (table && (result = callback(table, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ false, location))) { + return result; + } + break; + } } - function needsQualification(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags) { - let qualify = false; - forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { - // If symbol of this name is not available in the symbol table we are ok - let symbolFromSymbolTable = getMergedSymbol(symbolTable.get(symbol.escapedName)); - if (!symbolFromSymbolTable) { - // Continue to the next symbol table - return false; - } - // If the symbol with this name is present it should refer to the symbol - if (symbolFromSymbolTable === symbol) { - // No need to qualify - return true; - } - - // Qualify if the symbol from symbol table has same meaning as expected - const shouldResolveAlias = (symbolFromSymbolTable.flags & SymbolFlags.Alias && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)); - symbolFromSymbolTable = shouldResolveAlias ? resolveAlias(symbolFromSymbolTable) : symbolFromSymbolTable; - const flags = shouldResolveAlias ? getAllSymbolFlags(symbolFromSymbolTable) : symbolFromSymbolTable.flags; - if (flags & meaning) { - qualify = true; - return true; - } + return callback(globals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); + } - // Continue to the next symbol table - return false; - }); + function getQualifiedLeftMeaning(rightMeaning: SymbolFlags) { + // If we are looking in value space, the parent meaning is value, other wise it is namespace + return rightMeaning === SymbolFlags.Value ? SymbolFlags.Value : SymbolFlags.Namespace; + } - return qualify; + function getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap: ESMap = new Map()): Symbol[] | undefined { + if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) { + return undefined; } - - function isPropertyOrMethodDeclarationSymbol(symbol: Symbol) { - if (symbol.declarations && symbol.declarations.length) { - for (const declaration of symbol.declarations) { - switch (declaration.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - continue; - default: - return false; - } - } - return true; - } - return false; + const links = getSymbolLinks(symbol); + const cache = (links.accessibleChainCache ||= new Map()); + // Go from enclosingDeclaration to the first scope we check, so the cache is keyed off the scope and thus shared more + const firstRelevantLocation = forEachSymbolTableInScope(enclosingDeclaration, (_, __, ___, node) => node); + const key = `${useOnlyExternalAliasing ? 0 : 1}|${firstRelevantLocation && getNodeId(firstRelevantLocation)}|${meaning}`; + if (cache.has(key)) { + return cache.get(key); } - function isTypeSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { - const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ true); - return access.accessibility === SymbolAccessibility.Accessible; + const id = getSymbolId(symbol); + let visitedSymbolTables = visitedSymbolTablesMap.get(id); + if (!visitedSymbolTables) { + visitedSymbolTablesMap.set(id, visitedSymbolTables = []); } + const result = forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable); + cache.set(key, result); + return result; - function isValueSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { - const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Value, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ true); - return access.accessibility === SymbolAccessibility.Accessible; - } + /** + * @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already) + */ + function getAccessibleSymbolChainFromSymbolTable(symbols: SymbolTable, ignoreQualification?: boolean, isLocalNameLookup?: boolean): Symbol[] | undefined { + if (!pushIfUnique(visitedSymbolTables!, symbols)) { + return undefined; + } - function isSymbolAccessibleByFlags(typeSymbol: Symbol, enclosingDeclaration: Node | undefined, flags: SymbolFlags): boolean { - const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, flags, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ false); - return access.accessibility === SymbolAccessibility.Accessible; + const result = trySymbolTable(symbols, ignoreQualification, isLocalNameLookup); + visitedSymbolTables!.pop(); + return result; } - function isAnySymbolAccessible(symbols: Symbol[] | undefined, enclosingDeclaration: Node | undefined, initialSymbol: Symbol, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult | undefined { - if (!length(symbols)) return; + function canQualifySymbol(symbolFromSymbolTable: Symbol, meaning: SymbolFlags) { + // If the symbol is equivalent and doesn't need further qualification, this symbol is accessible + return !needsQualification(symbolFromSymbolTable, enclosingDeclaration, meaning) || + // If symbol needs qualification, make sure that parent is accessible, if it is then this symbol is accessible too + !!getAccessibleSymbolChain(symbolFromSymbolTable.parent, enclosingDeclaration, getQualifiedLeftMeaning(meaning), useOnlyExternalAliasing, visitedSymbolTablesMap); + } + + function isAccessible(symbolFromSymbolTable: Symbol, resolvedAliasSymbol?: Symbol, ignoreQualification?: boolean) { + return (symbol === (resolvedAliasSymbol || symbolFromSymbolTable) || getMergedSymbol(symbol) === getMergedSymbol(resolvedAliasSymbol || symbolFromSymbolTable)) && + // if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table) + // and if symbolFromSymbolTable or alias resolution matches the symbol, + // check the symbol can be qualified, it is only then this symbol is accessible + !some(symbolFromSymbolTable.declarations, hasNonGlobalAugmentationExternalModuleSymbol) && + (ignoreQualification || canQualifySymbol(getMergedSymbol(symbolFromSymbolTable), meaning)); + } + + function trySymbolTable(symbols: SymbolTable, ignoreQualification: boolean | undefined, isLocalNameLookup: boolean | undefined): Symbol[] | undefined { + // If symbol is directly available by its name in the symbol table + if (isAccessible(symbols.get(symbol!.escapedName)!, /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { + return [symbol!]; + } + + // Check if symbol is any of the aliases in scope + const result = forEachEntry(symbols, symbolFromSymbolTable => { + if (symbolFromSymbolTable.flags & SymbolFlags.Alias + && symbolFromSymbolTable.escapedName !== InternalSymbolName.ExportEquals + && symbolFromSymbolTable.escapedName !== InternalSymbolName.Default + && !(isUMDExportSymbol(symbolFromSymbolTable) && enclosingDeclaration && isExternalModule(getSourceFileOfNode(enclosingDeclaration))) + // If `!useOnlyExternalAliasing`, we can use any type of alias to get the name + && (!useOnlyExternalAliasing || some(symbolFromSymbolTable.declarations, isExternalModuleImportEqualsDeclaration)) + // If we're looking up a local name to reference directly, omit namespace reexports, otherwise when we're trawling through an export list to make a dotted name, we can keep it + && (isLocalNameLookup ? !some(symbolFromSymbolTable.declarations, isNamespaceReexportDeclaration) : true) + // While exports are generally considered to be in scope, export-specifier declared symbols are _not_ + // See similar comment in `resolveName` for details + && (ignoreQualification || !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) + ) { - let hadAccessibleChain: Symbol | undefined; - let earlyModuleBail = false; - for (const symbol of symbols!) { - // Symbol is accessible if it by itself is accessible - const accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/ false); - if (accessibleSymbolChain) { - hadAccessibleChain = symbol; - const hasAccessibleDeclarations = hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible); - if (hasAccessibleDeclarations) { - return hasAccessibleDeclarations; - } - } - if (allowModules) { - if (some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - if (shouldComputeAliasesToMakeVisible) { - earlyModuleBail = true; - // Generally speaking, we want to use the aliases that already exist to refer to a module, if present - // In order to do so, we need to find those aliases in order to retain them in declaration emit; so - // if we are in declaration emit, we cannot use the fast path for module visibility until we've exhausted - // all other visibility options (in order to capture the possible aliases used to reference the module) - continue; - } - // Any meaning of a module symbol is always accessible via an `import` type - return { - accessibility: SymbolAccessibility.Accessible - }; + const resolvedImportedSymbol = resolveAlias(symbolFromSymbolTable); + const candidate = getCandidateListForSymbol(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification); + if (candidate) { + return candidate; } } + if (symbolFromSymbolTable.escapedName === symbol!.escapedName && symbolFromSymbolTable.exportSymbol) { + if (isAccessible(getMergedSymbol(symbolFromSymbolTable.exportSymbol), /*aliasSymbol*/ undefined, ignoreQualification)) { + return [symbol!]; + } + } + }); - // If we haven't got the accessible symbol, it doesn't mean the symbol is actually inaccessible. - // It could be a qualified symbol and hence verify the path - // e.g.: - // module m { - // export class c { - // } - // } - // const x: typeof m.c - // In the above example when we start with checking if typeof m.c symbol is accessible, - // we are going to see if c can be accessed in scope directly. - // But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible - // It is accessible if the parent m is accessible because then m.c can be accessed through qualification + // If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that + return result || (symbols === globals ? getCandidateListForSymbol(globalThisSymbol, globalThisSymbol, ignoreQualification) : undefined); + } - const containers = getContainersOfSymbol(symbol, enclosingDeclaration, meaning); - const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible, allowModules); - if (parentResult) { - return parentResult; - } + function getCandidateListForSymbol(symbolFromSymbolTable: Symbol, resolvedImportedSymbol: Symbol, ignoreQualification: boolean | undefined) { + if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) { + return [symbolFromSymbolTable]; } - if (earlyModuleBail) { - return { - accessibility: SymbolAccessibility.Accessible - }; + // Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain + // but only if the symbolFromSymbolTable can be qualified + const candidateTable = getExportsOfSymbol(resolvedImportedSymbol); + const accessibleSymbolsFromExports = candidateTable && getAccessibleSymbolChainFromSymbolTable(candidateTable, /*ignoreQualification*/ true); + if (accessibleSymbolsFromExports && canQualifySymbol(symbolFromSymbolTable, getQualifiedLeftMeaning(meaning))) { + return [symbolFromSymbolTable].concat(accessibleSymbolsFromExports); } + } + } - if (hadAccessibleChain) { - return { - accessibility: SymbolAccessibility.NotAccessible, - errorSymbolName: symbolToString(initialSymbol, enclosingDeclaration, meaning), - errorModuleName: hadAccessibleChain !== initialSymbol ? symbolToString(hadAccessibleChain, enclosingDeclaration, SymbolFlags.Namespace) : undefined, - }; + function needsQualification(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags) { + let qualify = false; + forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { + // If symbol of this name is not available in the symbol table we are ok + let symbolFromSymbolTable = getMergedSymbol(symbolTable.get(symbol.escapedName)); + if (!symbolFromSymbolTable) { + // Continue to the next symbol table + return false; + } + // If the symbol with this name is present it should refer to the symbol + if (symbolFromSymbolTable === symbol) { + // No need to qualify + return true; } - } - /** - * Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested - * - * @param symbol a Symbol to check if accessible - * @param enclosingDeclaration a Node containing reference to the symbol - * @param meaning a SymbolFlags to check if such meaning of the symbol is accessible - * @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible - */ - function isSymbolAccessible(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult { - return isSymbolAccessibleWorker(symbol, enclosingDeclaration, meaning, shouldComputeAliasesToMakeVisible, /*allowModules*/ true); - } + // Qualify if the symbol from symbol table has same meaning as expected + const shouldResolveAlias = (symbolFromSymbolTable.flags & SymbolFlags.Alias && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)); + symbolFromSymbolTable = shouldResolveAlias ? resolveAlias(symbolFromSymbolTable) : symbolFromSymbolTable; + const flags = shouldResolveAlias ? getAllSymbolFlags(symbolFromSymbolTable) : symbolFromSymbolTable.flags; + if (flags & meaning) { + qualify = true; + return true; + } - function isSymbolAccessibleWorker(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult { - if (symbol && enclosingDeclaration) { - const result = isAnySymbolAccessible([symbol], enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible, allowModules); - if (result) { - return result; + // Continue to the next symbol table + return false; + }); + + return qualify; + } + + function isPropertyOrMethodDeclarationSymbol(symbol: Symbol) { + if (symbol.declarations && symbol.declarations.length) { + for (const declaration of symbol.declarations) { + switch (declaration.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + continue; + default: + return false; } + } + return true; + } + return false; + } + + function isTypeSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { + const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ true); + return access.accessibility === SymbolAccessibility.Accessible; + } + + function isValueSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node | undefined): boolean { + const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, SymbolFlags.Value, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ true); + return access.accessibility === SymbolAccessibility.Accessible; + } + + function isSymbolAccessibleByFlags(typeSymbol: Symbol, enclosingDeclaration: Node | undefined, flags: SymbolFlags): boolean { + const access = isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, flags, /*shouldComputeAliasesToMakeVisible*/ false, /*allowModules*/ false); + return access.accessibility === SymbolAccessibility.Accessible; + } - // This could be a symbol that is not exported in the external module - // or it could be a symbol from different external module that is not aliased and hence cannot be named - const symbolExternalModule = forEach(symbol.declarations, getExternalModuleContainer); - if (symbolExternalModule) { - const enclosingExternalModule = getExternalModuleContainer(enclosingDeclaration); - if (symbolExternalModule !== enclosingExternalModule) { - // name from different external module that is not visible - return { - accessibility: SymbolAccessibility.CannotBeNamed, - errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), - errorModuleName: symbolToString(symbolExternalModule), - errorNode: isInJSFile(enclosingDeclaration) ? enclosingDeclaration : undefined, - }; + function isAnySymbolAccessible(symbols: Symbol[] | undefined, enclosingDeclaration: Node | undefined, initialSymbol: Symbol, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult | undefined { + if (!length(symbols)) return; + + let hadAccessibleChain: Symbol | undefined; + let earlyModuleBail = false; + for (const symbol of symbols!) { + // Symbol is accessible if it by itself is accessible + const accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/ false); + if (accessibleSymbolChain) { + hadAccessibleChain = symbol; + const hasAccessibleDeclarations = hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible); + if (hasAccessibleDeclarations) { + return hasAccessibleDeclarations; + } + } + if (allowModules) { + if (some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + if (shouldComputeAliasesToMakeVisible) { + earlyModuleBail = true; + // Generally speaking, we want to use the aliases that already exist to refer to a module, if present + // In order to do so, we need to find those aliases in order to retain them in declaration emit; so + // if we are in declaration emit, we cannot use the fast path for module visibility until we've exhausted + // all other visibility options (in order to capture the possible aliases used to reference the module) + continue; } + // Any meaning of a module symbol is always accessible via an `import` type + return { + accessibility: SymbolAccessibility.Accessible + }; } - - // Just a local name that is not accessible - return { - accessibility: SymbolAccessibility.NotAccessible, - errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), - }; } - return { accessibility: SymbolAccessibility.Accessible }; - } + // If we haven't got the accessible symbol, it doesn't mean the symbol is actually inaccessible. + // It could be a qualified symbol and hence verify the path + // e.g.: + // module m { + // export class c { + // } + // } + // const x: typeof m.c + // In the above example when we start with checking if typeof m.c symbol is accessible, + // we are going to see if c can be accessed in scope directly. + // But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible + // It is accessible if the parent m is accessible because then m.c can be accessed through qualification - function getExternalModuleContainer(declaration: Node) { - const node = findAncestor(declaration, hasExternalModuleSymbol); - return node && getSymbolOfNode(node); + const containers = getContainersOfSymbol(symbol, enclosingDeclaration, meaning); + const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible, allowModules); + if (parentResult) { + return parentResult; + } } - function hasExternalModuleSymbol(declaration: Node) { - return isAmbientModule(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration as SourceFile)); + if (earlyModuleBail) { + return { + accessibility: SymbolAccessibility.Accessible + }; } - function hasNonGlobalAugmentationExternalModuleSymbol(declaration: Node) { - return isModuleWithStringLiteralName(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration as SourceFile)); + if (hadAccessibleChain) { + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(initialSymbol, enclosingDeclaration, meaning), + errorModuleName: hadAccessibleChain !== initialSymbol ? symbolToString(hadAccessibleChain, enclosingDeclaration, SymbolFlags.Namespace) : undefined, + }; } + } - function hasVisibleDeclarations(symbol: Symbol, shouldComputeAliasToMakeVisible: boolean): SymbolVisibilityResult | undefined { - let aliasesToMakeVisible: LateVisibilityPaintedStatement[] | undefined; - if (!every(filter(symbol.declarations, d => d.kind !== SyntaxKind.Identifier), getIsDeclarationVisible)) { - return undefined; - } - return { accessibility: SymbolAccessibility.Accessible, aliasesToMakeVisible }; - - function getIsDeclarationVisible(declaration: Declaration) { - if (!isDeclarationVisible(declaration)) { - // Mark the unexported alias as visible if its parent is visible - // because these kind of aliases can be used to name types in declaration file - - const anyImportSyntax = getAnyImportSyntax(declaration); - if (anyImportSyntax && - !hasSyntacticModifier(anyImportSyntax, ModifierFlags.Export) && // import clause without export - isDeclarationVisible(anyImportSyntax.parent)) { - return addVisibleAlias(declaration, anyImportSyntax); - } - else if (isVariableDeclaration(declaration) && isVariableStatement(declaration.parent.parent) && - !hasSyntacticModifier(declaration.parent.parent, ModifierFlags.Export) && // unexported variable statement - isDeclarationVisible(declaration.parent.parent.parent)) { - return addVisibleAlias(declaration, declaration.parent.parent); - } - else if (isLateVisibilityPaintedStatement(declaration) // unexported top-level statement - && !hasSyntacticModifier(declaration, ModifierFlags.Export) - && isDeclarationVisible(declaration.parent)) { - return addVisibleAlias(declaration, declaration); - } - else if (isBindingElement(declaration)) { - if (symbol.flags & SymbolFlags.Alias && isInJSFile(declaration) && declaration.parent?.parent // exported import-like top-level JS require statement - && isVariableDeclaration(declaration.parent.parent) - && declaration.parent.parent.parent?.parent && isVariableStatement(declaration.parent.parent.parent.parent) - && !hasSyntacticModifier(declaration.parent.parent.parent.parent, ModifierFlags.Export) - && declaration.parent.parent.parent.parent.parent // check if the thing containing the variable statement is visible (ie, the file) - && isDeclarationVisible(declaration.parent.parent.parent.parent.parent)) { - return addVisibleAlias(declaration, declaration.parent.parent.parent.parent); - } - else if (symbol.flags & SymbolFlags.BlockScopedVariable) { - const variableStatement = findAncestor(declaration, isVariableStatement)!; - if (hasSyntacticModifier(variableStatement, ModifierFlags.Export)) { - return true; - } - if (!isDeclarationVisible(variableStatement.parent)) { - return false; - } - return addVisibleAlias(declaration, variableStatement); - } - } - - // Declaration is not visible - return false; - } + /** + * Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested + * + * @param symbol a Symbol to check if accessible + * @param enclosingDeclaration a Node containing reference to the symbol + * @param meaning a SymbolFlags to check if such meaning of the symbol is accessible + * @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible + */ + function isSymbolAccessible(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean): SymbolAccessibilityResult { + return isSymbolAccessibleWorker(symbol, enclosingDeclaration, meaning, shouldComputeAliasesToMakeVisible, /*allowModules*/ true); + } - return true; + function isSymbolAccessibleWorker(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, shouldComputeAliasesToMakeVisible: boolean, allowModules: boolean): SymbolAccessibilityResult { + if (symbol && enclosingDeclaration) { + const result = isAnySymbolAccessible([symbol], enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible, allowModules); + if (result) { + return result; } - function addVisibleAlias(declaration: Declaration, aliasingStatement: LateVisibilityPaintedStatement) { - // In function "buildTypeDisplay" where we decide whether to write type-alias or serialize types, - // we want to just check if type- alias is accessible or not but we don't care about emitting those alias at that time - // since we will do the emitting later in trackSymbol. - if (shouldComputeAliasToMakeVisible) { - getNodeLinks(declaration).isVisible = true; - aliasesToMakeVisible = appendIfUnique(aliasesToMakeVisible, aliasingStatement); + // This could be a symbol that is not exported in the external module + // or it could be a symbol from different external module that is not aliased and hence cannot be named + const symbolExternalModule = forEach(symbol.declarations, getExternalModuleContainer); + if (symbolExternalModule) { + const enclosingExternalModule = getExternalModuleContainer(enclosingDeclaration); + if (symbolExternalModule !== enclosingExternalModule) { + // name from different external module that is not visible + return { + accessibility: SymbolAccessibility.CannotBeNamed, + errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), + errorModuleName: symbolToString(symbolExternalModule), + errorNode: isInJSFile(enclosingDeclaration) ? enclosingDeclaration : undefined, + }; } - return true; } + + // Just a local name that is not accessible + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), + }; } - function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult { - // get symbol of the first identifier of the entityName - let meaning: SymbolFlags; - if (entityName.parent.kind === SyntaxKind.TypeQuery || - entityName.parent.kind === SyntaxKind.ExpressionWithTypeArguments && !isPartOfTypeNode(entityName.parent) || - entityName.parent.kind === SyntaxKind.ComputedPropertyName) { - // Typeof value - meaning = SymbolFlags.Value | SymbolFlags.ExportValue; - } - else if (entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || - entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration) { - // Left identifier from type reference or TypeAlias - // Entity name of the import declaration - meaning = SymbolFlags.Namespace; - } - else { - // Type Reference or TypeAlias entity = Identifier - meaning = SymbolFlags.Type; - } + return { accessibility: SymbolAccessibility.Accessible }; + } - const firstIdentifier = getFirstIdentifier(entityName); - const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); - if (symbol && symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) { - return { accessibility: SymbolAccessibility.Accessible }; - } - if (!symbol && isThisIdentifier(firstIdentifier) && isSymbolAccessible(getSymbolOfNode(getThisContainer(firstIdentifier, /*includeArrowFunctions*/ false)), firstIdentifier, meaning, /*computeAliases*/ false).accessibility === SymbolAccessibility.Accessible) { - return { accessibility: SymbolAccessibility.Accessible }; - } + function getExternalModuleContainer(declaration: Node) { + const node = findAncestor(declaration, hasExternalModuleSymbol); + return node && getSymbolOfNode(node); + } - // Verify if the symbol is accessible - return (symbol && hasVisibleDeclarations(symbol, /*shouldComputeAliasToMakeVisible*/ true)) || { - accessibility: SymbolAccessibility.NotAccessible, - errorSymbolName: getTextOfNode(firstIdentifier), - errorNode: firstIdentifier - }; + function hasExternalModuleSymbol(declaration: Node) { + return isAmbientModule(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration as SourceFile)); + } + + function hasNonGlobalAugmentationExternalModuleSymbol(declaration: Node) { + return isModuleWithStringLiteralName(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration as SourceFile)); + } + + function hasVisibleDeclarations(symbol: Symbol, shouldComputeAliasToMakeVisible: boolean): SymbolVisibilityResult | undefined { + let aliasesToMakeVisible: LateVisibilityPaintedStatement[] | undefined; + if (!every(filter(symbol.declarations, d => d.kind !== SyntaxKind.Identifier), getIsDeclarationVisible)) { + return undefined; } + return { accessibility: SymbolAccessibility.Accessible, aliasesToMakeVisible }; + + function getIsDeclarationVisible(declaration: Declaration) { + if (!isDeclarationVisible(declaration)) { + // Mark the unexported alias as visible if its parent is visible + // because these kind of aliases can be used to name types in declaration file + + const anyImportSyntax = getAnyImportSyntax(declaration); + if (anyImportSyntax && + !hasSyntacticModifier(anyImportSyntax, ModifierFlags.Export) && // import clause without export + isDeclarationVisible(anyImportSyntax.parent)) { + return addVisibleAlias(declaration, anyImportSyntax); + } + else if (isVariableDeclaration(declaration) && isVariableStatement(declaration.parent.parent) && + !hasSyntacticModifier(declaration.parent.parent, ModifierFlags.Export) && // unexported variable statement + isDeclarationVisible(declaration.parent.parent.parent)) { + return addVisibleAlias(declaration, declaration.parent.parent); + } + else if (isLateVisibilityPaintedStatement(declaration) // unexported top-level statement + && !hasSyntacticModifier(declaration, ModifierFlags.Export) + && isDeclarationVisible(declaration.parent)) { + return addVisibleAlias(declaration, declaration); + } + else if (isBindingElement(declaration)) { + if (symbol.flags & SymbolFlags.Alias && isInJSFile(declaration) && declaration.parent?.parent // exported import-like top-level JS require statement + && isVariableDeclaration(declaration.parent.parent) + && declaration.parent.parent.parent?.parent && isVariableStatement(declaration.parent.parent.parent.parent) + && !hasSyntacticModifier(declaration.parent.parent.parent.parent, ModifierFlags.Export) + && declaration.parent.parent.parent.parent.parent // check if the thing containing the variable statement is visible (ie, the file) + && isDeclarationVisible(declaration.parent.parent.parent.parent.parent)) { + return addVisibleAlias(declaration, declaration.parent.parent.parent.parent); + } + else if (symbol.flags & SymbolFlags.BlockScopedVariable) { + const variableStatement = findAncestor(declaration, isVariableStatement)!; + if (hasSyntacticModifier(variableStatement, ModifierFlags.Export)) { + return true; + } + if (!isDeclarationVisible(variableStatement.parent)) { + return false; + } + return addVisibleAlias(declaration, variableStatement); + } + } - function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.AllowAnyNodeKind, writer?: EmitTextWriter): string { - let nodeFlags = NodeBuilderFlags.IgnoreErrors; - if (flags & SymbolFormatFlags.UseOnlyExternalAliasing) { - nodeFlags |= NodeBuilderFlags.UseOnlyExternalAliasing; - } - if (flags & SymbolFormatFlags.WriteTypeParametersOrArguments) { - nodeFlags |= NodeBuilderFlags.WriteTypeParametersInQualifiedName; - } - if (flags & SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope) { - nodeFlags |= NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; - } - if (flags & SymbolFormatFlags.DoNotIncludeSymbolChain) { - nodeFlags |= NodeBuilderFlags.DoNotIncludeSymbolChain; - } - if (flags & SymbolFormatFlags.WriteComputedProps) { - nodeFlags |= NodeBuilderFlags.WriteComputedProps; + // Declaration is not visible + return false; } - const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToNode : nodeBuilder.symbolToEntityName; - return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker); - function symbolToStringWorker(writer: EmitTextWriter) { - const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags)!; // TODO: GH#18217 - // add neverAsciiEscape for GH#39027 - const printer = enclosingDeclaration?.kind === SyntaxKind.SourceFile ? createPrinter({ removeComments: true, neverAsciiEscape: true }) : createPrinter({ removeComments: true }); - const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer); - return writer; + return true; + } + + function addVisibleAlias(declaration: Declaration, aliasingStatement: LateVisibilityPaintedStatement) { + // In function "buildTypeDisplay" where we decide whether to write type-alias or serialize types, + // we want to just check if type- alias is accessible or not but we don't care about emitting those alias at that time + // since we will do the emitting later in trackSymbol. + if (shouldComputeAliasToMakeVisible) { + getNodeLinks(declaration).isVisible = true; + aliasesToMakeVisible = appendIfUnique(aliasesToMakeVisible, aliasingStatement); } + return true; } + } - function signatureToString(signature: Signature, enclosingDeclaration?: Node, flags = TypeFormatFlags.None, kind?: SignatureKind, writer?: EmitTextWriter): string { - return writer ? signatureToStringWorker(writer).getText() : usingSingleLineStringWriter(signatureToStringWorker); + function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult { + // get symbol of the first identifier of the entityName + let meaning: SymbolFlags; + if (entityName.parent.kind === SyntaxKind.TypeQuery || + entityName.parent.kind === SyntaxKind.ExpressionWithTypeArguments && !isPartOfTypeNode(entityName.parent) || + entityName.parent.kind === SyntaxKind.ComputedPropertyName) { + // Typeof value + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + } + else if (entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || + entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration) { + // Left identifier from type reference or TypeAlias + // Entity name of the import declaration + meaning = SymbolFlags.Namespace; + } + else { + // Type Reference or TypeAlias entity = Identifier + meaning = SymbolFlags.Type; + } + + const firstIdentifier = getFirstIdentifier(entityName); + const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + if (symbol && symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) { + return { accessibility: SymbolAccessibility.Accessible }; + } + if (!symbol && isThisIdentifier(firstIdentifier) && isSymbolAccessible(getSymbolOfNode(getThisContainer(firstIdentifier, /*includeArrowFunctions*/ false)), firstIdentifier, meaning, /*computeAliases*/ false).accessibility === SymbolAccessibility.Accessible) { + return { accessibility: SymbolAccessibility.Accessible }; + } - function signatureToStringWorker(writer: EmitTextWriter) { - let sigOutput: SyntaxKind; - if (flags & TypeFormatFlags.WriteArrowStyleSignature) { - sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructorType : SyntaxKind.FunctionType; - } - else { - sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructSignature : SyntaxKind.CallSignature; - } - const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); - const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); - const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217 - return writer; - } + // Verify if the symbol is accessible + return (symbol && hasVisibleDeclarations(symbol, /*shouldComputeAliasToMakeVisible*/ true)) || { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: getTextOfNode(firstIdentifier), + errorNode: firstIdentifier + }; + } + + function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.AllowAnyNodeKind, writer?: EmitTextWriter): string { + let nodeFlags = NodeBuilderFlags.IgnoreErrors; + if (flags & SymbolFormatFlags.UseOnlyExternalAliasing) { + nodeFlags |= NodeBuilderFlags.UseOnlyExternalAliasing; + } + if (flags & SymbolFormatFlags.WriteTypeParametersOrArguments) { + nodeFlags |= NodeBuilderFlags.WriteTypeParametersInQualifiedName; + } + if (flags & SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope) { + nodeFlags |= NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; + } + if (flags & SymbolFormatFlags.DoNotIncludeSymbolChain) { + nodeFlags |= NodeBuilderFlags.DoNotIncludeSymbolChain; + } + if (flags & SymbolFormatFlags.WriteComputedProps) { + nodeFlags |= NodeBuilderFlags.WriteComputedProps; } + const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToNode : nodeBuilder.symbolToEntityName; + return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker); - function typeToString(type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer: EmitTextWriter = createTextWriter("")): string { - const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation; - const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), writer); - if (typeNode === undefined) return Debug.fail("should always get typenode"); - // The unresolved type gets a synthesized comment on `any` to hint to users that it's not a plain `any`. - // Otherwise, we always strip comments out. - const options = { removeComments: type !== unresolvedType }; - const printer = createPrinter(options); + function symbolToStringWorker(writer: EmitTextWriter) { + const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags)!; // TODO: GH#18217 + // add neverAsciiEscape for GH#39027 + const printer = enclosingDeclaration?.kind === SyntaxKind.SourceFile ? createPrinter({ removeComments: true, neverAsciiEscape: true }) : createPrinter({ removeComments: true }); const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); - const result = writer.getText(); - - const maxLength = noTruncation ? noTruncationMaximumTruncationLength * 2 : defaultMaximumTruncationLength * 2; - if (maxLength && result && result.length >= maxLength) { - return result.substr(0, maxLength - "...".length) + "..."; - } - return result; + printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer); + return writer; } + } - function getTypeNamesForErrorDisplay(left: Type, right: Type): [string, string] { - let leftStr = symbolValueDeclarationIsContextSensitive(left.symbol) ? typeToString(left, left.symbol.valueDeclaration) : typeToString(left); - let rightStr = symbolValueDeclarationIsContextSensitive(right.symbol) ? typeToString(right, right.symbol.valueDeclaration) : typeToString(right); - if (leftStr === rightStr) { - leftStr = getTypeNameForErrorDisplay(left); - rightStr = getTypeNameForErrorDisplay(right); + function signatureToString(signature: Signature, enclosingDeclaration?: Node, flags = TypeFormatFlags.None, kind?: SignatureKind, writer?: EmitTextWriter): string { + return writer ? signatureToStringWorker(writer).getText() : usingSingleLineStringWriter(signatureToStringWorker); + + function signatureToStringWorker(writer: EmitTextWriter) { + let sigOutput: SyntaxKind; + if (flags & TypeFormatFlags.WriteArrowStyleSignature) { + sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructorType : SyntaxKind.FunctionType; + } + else { + sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructSignature : SyntaxKind.CallSignature; } - return [leftStr, rightStr]; + const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); + const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217 + return writer; } + } - function getTypeNameForErrorDisplay(type: Type) { - return typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType); - } + function typeToString(type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer: EmitTextWriter = createTextWriter("")): string { + const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation; + const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), writer); + if (typeNode === undefined) return Debug.fail("should always get typenode"); + // The unresolved type gets a synthesized comment on `any` to hint to users that it's not a plain `any`. + // Otherwise, we always strip comments out. + const options = { removeComments: type !== unresolvedType }; + const printer = createPrinter(options); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); + const result = writer.getText(); + + const maxLength = noTruncation ? noTruncationMaximumTruncationLength * 2 : defaultMaximumTruncationLength * 2; + if (maxLength && result && result.length >= maxLength) { + return result.substr(0, maxLength - "...".length) + "..."; + } + return result; + } - function symbolValueDeclarationIsContextSensitive(symbol: Symbol): boolean { - return symbol && !!symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !isContextSensitive(symbol.valueDeclaration); + function getTypeNamesForErrorDisplay(left: Type, right: Type): [string, string] { + let leftStr = symbolValueDeclarationIsContextSensitive(left.symbol) ? typeToString(left, left.symbol.valueDeclaration) : typeToString(left); + let rightStr = symbolValueDeclarationIsContextSensitive(right.symbol) ? typeToString(right, right.symbol.valueDeclaration) : typeToString(right); + if (leftStr === rightStr) { + leftStr = getTypeNameForErrorDisplay(left); + rightStr = getTypeNameForErrorDisplay(right); } + return [leftStr, rightStr]; + } - function toNodeBuilderFlags(flags = TypeFormatFlags.None): NodeBuilderFlags { - return flags & TypeFormatFlags.NodeBuilderFlagsMask; - } + function getTypeNameForErrorDisplay(type: Type) { + return typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType); + } + + function symbolValueDeclarationIsContextSensitive(symbol: Symbol): boolean { + return symbol && !!symbol.valueDeclaration && isExpression(symbol.valueDeclaration) && !isContextSensitive(symbol.valueDeclaration); + } - function isClassInstanceSide(type: Type) { - return !!type.symbol && !!(type.symbol.flags & SymbolFlags.Class) && (type === getDeclaredTypeOfClassOrInterface(type.symbol) || (!!(type.flags & TypeFlags.Object) && !!(getObjectFlags(type) & ObjectFlags.IsClassInstanceClone))); + function toNodeBuilderFlags(flags = TypeFormatFlags.None): NodeBuilderFlags { + return flags & TypeFormatFlags.NodeBuilderFlagsMask; + } + + function isClassInstanceSide(type: Type) { + return !!type.symbol && !!(type.symbol.flags & SymbolFlags.Class) && (type === getDeclaredTypeOfClassOrInterface(type.symbol) || (!!(type.flags & TypeFlags.Object) && !!(getObjectFlags(type) & ObjectFlags.IsClassInstanceClone))); + } + + function createNodeBuilder() { + return { + typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)), + indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => indexInfoToIndexSignatureDeclarationHelper(indexInfo, context, /*typeNode*/ undefined)), + signatureToSignatureDeclaration: (signature: Signature, kind: SignatureDeclaration["kind"], enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => signatureToSignatureDeclarationHelper(signature, kind, context)), + symbolToEntityName: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => symbolToName(symbol, context, meaning, /*expectsIdentifier*/ false)), + symbolToExpression: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => symbolToExpression(symbol, context, meaning)), + symbolToTypeParameterDeclarations: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => typeParametersToTypeParameterDeclarations(symbol, context)), + symbolToParameterDeclaration: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => symbolToParameterDeclaration(symbol, context)), + typeParameterToDeclaration: (parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => typeParameterToDeclaration(parameter, context)), + symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker, bundled?: boolean) => + withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context, bundled)), + symbolToNode: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => symbolToNode(symbol, context, meaning)), + }; + + function symbolToNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { + if (context.flags & NodeBuilderFlags.WriteComputedProps) { + if (symbol.valueDeclaration) { + const name = getNameOfDeclaration(symbol.valueDeclaration); + if (name && isComputedPropertyName(name)) return name; + } + const nameType = getSymbolLinks(symbol).nameType; + if (nameType && nameType.flags & (TypeFlags.EnumLiteral | TypeFlags.UniqueESSymbol)) { + context.enclosingDeclaration = nameType.symbol.valueDeclaration; + return factory.createComputedPropertyName(symbolToExpression(nameType.symbol, context, meaning)); + } + } + return symbolToExpression(symbol, context, meaning); + } + + function withContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined { + Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); + const context: NodeBuilderContext = { + enclosingDeclaration, + flags: flags || NodeBuilderFlags.None, + // If no full tracker is provided, fake up a dummy one with a basic limited-functionality moduleResolverHost + tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: () => false, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? { + getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", + getCurrentDirectory: () => host.getCurrentDirectory(), + getSymlinkCache: maybeBind(host, host.getSymlinkCache), + getPackageJsonInfoCache: () => host.getPackageJsonInfoCache?.(), + useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), + redirectTargetsMap: host.redirectTargetsMap, + getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName), + isSourceOfProjectReferenceRedirect: fileName => host.isSourceOfProjectReferenceRedirect(fileName), + fileExists: fileName => host.fileExists(fileName), + getFileIncludeReasons: () => host.getFileIncludeReasons(), + readFile: host.readFile ? (fileName => host.readFile!(fileName)) : undefined, + } : undefined }, + encounteredError: false, + reportedDiagnostic: false, + visitedTypes: undefined, + symbolDepth: undefined, + inferTypeParameters: undefined, + approximateLength: 0 + }; + context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); + const resultingNode = cb(context); + if (context.truncating && context.flags & NodeBuilderFlags.NoTruncation) { + context.tracker?.reportTruncationError?.(); + } + return context.encounteredError ? undefined : resultingNode; } - function createNodeBuilder() { + function wrapSymbolTrackerToReportForContext(context: NodeBuilderContext, tracker: SymbolTracker): SymbolTracker { + const oldTrackSymbol = tracker.trackSymbol; return { - typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)), - indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => indexInfoToIndexSignatureDeclarationHelper(indexInfo, context, /*typeNode*/ undefined)), - signatureToSignatureDeclaration: (signature: Signature, kind: SignatureDeclaration["kind"], enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => signatureToSignatureDeclarationHelper(signature, kind, context)), - symbolToEntityName: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => symbolToName(symbol, context, meaning, /*expectsIdentifier*/ false)), - symbolToExpression: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => symbolToExpression(symbol, context, meaning)), - symbolToTypeParameterDeclarations: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => typeParametersToTypeParameterDeclarations(symbol, context)), - symbolToParameterDeclaration: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => symbolToParameterDeclaration(symbol, context)), - typeParameterToDeclaration: (parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => typeParameterToDeclaration(parameter, context)), - symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker, bundled?: boolean) => - withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context, bundled)), - symbolToNode: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => - withContext(enclosingDeclaration, flags, tracker, context => symbolToNode(symbol, context, meaning)), + ...tracker, + reportCyclicStructureError: wrapReportedDiagnostic(tracker.reportCyclicStructureError), + reportInaccessibleThisError: wrapReportedDiagnostic(tracker.reportInaccessibleThisError), + reportInaccessibleUniqueSymbolError: wrapReportedDiagnostic(tracker.reportInaccessibleUniqueSymbolError), + reportLikelyUnsafeImportRequiredError: wrapReportedDiagnostic(tracker.reportLikelyUnsafeImportRequiredError), + reportNonlocalAugmentation: wrapReportedDiagnostic(tracker.reportNonlocalAugmentation), + reportPrivateInBaseOfClassExpression: wrapReportedDiagnostic(tracker.reportPrivateInBaseOfClassExpression), + reportNonSerializableProperty: wrapReportedDiagnostic(tracker.reportNonSerializableProperty), + trackSymbol: oldTrackSymbol && ((...args) => { + const result = oldTrackSymbol(...args); + if (result) { + context.reportedDiagnostic = true; + } + return result; + }), }; - function symbolToNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { - if (context.flags & NodeBuilderFlags.WriteComputedProps) { - if (symbol.valueDeclaration) { - const name = getNameOfDeclaration(symbol.valueDeclaration); - if (name && isComputedPropertyName(name)) return name; - } - const nameType = getSymbolLinks(symbol).nameType; - if (nameType && nameType.flags & (TypeFlags.EnumLiteral | TypeFlags.UniqueESSymbol)) { - context.enclosingDeclaration = nameType.symbol.valueDeclaration; - return factory.createComputedPropertyName(symbolToExpression(nameType.symbol, context, meaning)); - } - } - return symbolToExpression(symbol, context, meaning); - } - - function withContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined { - Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); - const context: NodeBuilderContext = { - enclosingDeclaration, - flags: flags || NodeBuilderFlags.None, - // If no full tracker is provided, fake up a dummy one with a basic limited-functionality moduleResolverHost - tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: () => false, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? { - getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", - getCurrentDirectory: () => host.getCurrentDirectory(), - getSymlinkCache: maybeBind(host, host.getSymlinkCache), - getPackageJsonInfoCache: () => host.getPackageJsonInfoCache?.(), - useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), - redirectTargetsMap: host.redirectTargetsMap, - getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName), - isSourceOfProjectReferenceRedirect: fileName => host.isSourceOfProjectReferenceRedirect(fileName), - fileExists: fileName => host.fileExists(fileName), - getFileIncludeReasons: () => host.getFileIncludeReasons(), - readFile: host.readFile ? (fileName => host.readFile!(fileName)) : undefined, - } : undefined }, - encounteredError: false, - reportedDiagnostic: false, - visitedTypes: undefined, - symbolDepth: undefined, - inferTypeParameters: undefined, - approximateLength: 0 - }; - context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); - const resultingNode = cb(context); - if (context.truncating && context.flags & NodeBuilderFlags.NoTruncation) { - context.tracker?.reportTruncationError?.(); + function wrapReportedDiagnostic any>(method: T | undefined): T | undefined { + if (!method) { + return method; } - return context.encounteredError ? undefined : resultingNode; + return (((...args) => { + context.reportedDiagnostic = true; + return method(...args); + }) as T); } + } - function wrapSymbolTrackerToReportForContext(context: NodeBuilderContext, tracker: SymbolTracker): SymbolTracker { - const oldTrackSymbol = tracker.trackSymbol; - return { - ...tracker, - reportCyclicStructureError: wrapReportedDiagnostic(tracker.reportCyclicStructureError), - reportInaccessibleThisError: wrapReportedDiagnostic(tracker.reportInaccessibleThisError), - reportInaccessibleUniqueSymbolError: wrapReportedDiagnostic(tracker.reportInaccessibleUniqueSymbolError), - reportLikelyUnsafeImportRequiredError: wrapReportedDiagnostic(tracker.reportLikelyUnsafeImportRequiredError), - reportNonlocalAugmentation: wrapReportedDiagnostic(tracker.reportNonlocalAugmentation), - reportPrivateInBaseOfClassExpression: wrapReportedDiagnostic(tracker.reportPrivateInBaseOfClassExpression), - reportNonSerializableProperty: wrapReportedDiagnostic(tracker.reportNonSerializableProperty), - trackSymbol: oldTrackSymbol && ((...args) => { - const result = oldTrackSymbol(...args); - if (result) { - context.reportedDiagnostic = true; - } - return result; - }), - }; + function checkTruncationLength(context: NodeBuilderContext): boolean { + if (context.truncating) return context.truncating; + return context.truncating = context.approximateLength > ((context.flags & NodeBuilderFlags.NoTruncation) ? noTruncationMaximumTruncationLength : defaultMaximumTruncationLength); + } - function wrapReportedDiagnostic any>(method: T | undefined): T | undefined { - if (!method) { - return method; - } - return (((...args) => { - context.reportedDiagnostic = true; - return method(...args); - }) as T); - } - } + function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { + const savedFlags = context.flags; + const typeNode = typeToTypeNodeWorker(type, context); + context.flags = savedFlags; + return typeNode; + } - function checkTruncationLength(context: NodeBuilderContext): boolean { - if (context.truncating) return context.truncating; - return context.truncating = context.approximateLength > ((context.flags & NodeBuilderFlags.NoTruncation) ? noTruncationMaximumTruncationLength : defaultMaximumTruncationLength); + function typeToTypeNodeWorker(type: Type, context: NodeBuilderContext): TypeNode { + if (cancellationToken && cancellationToken.throwIfCancellationRequested) { + cancellationToken.throwIfCancellationRequested(); } + const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias; + context.flags &= ~NodeBuilderFlags.InTypeAlias; - function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { - const savedFlags = context.flags; - const typeNode = typeToTypeNodeWorker(type, context); - context.flags = savedFlags; - return typeNode; + if (!type) { + if (!(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { + context.encounteredError = true; + return undefined!; // TODO: GH#18217 + } + context.approximateLength += 3; + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } - function typeToTypeNodeWorker(type: Type, context: NodeBuilderContext): TypeNode { - if (cancellationToken && cancellationToken.throwIfCancellationRequested) { - cancellationToken.throwIfCancellationRequested(); - } - const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias; - context.flags &= ~NodeBuilderFlags.InTypeAlias; + if (!(context.flags & NodeBuilderFlags.NoTypeReduction)) { + type = getReducedType(type); + } - if (!type) { - if (!(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { - context.encounteredError = true; - return undefined!; // TODO: GH#18217 - } - context.approximateLength += 3; - return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + if (type.flags & TypeFlags.Any) { + if (type.aliasSymbol) { + return factory.createTypeReferenceNode(symbolToEntityNameNode(type.aliasSymbol), mapToTypeNodes(type.aliasTypeArguments, context)); } - - if (!(context.flags & NodeBuilderFlags.NoTypeReduction)) { - type = getReducedType(type); + if (type === unresolvedType) { + return addSyntheticLeadingComment(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), SyntaxKind.MultiLineCommentTrivia, "unresolved"); } - - if (type.flags & TypeFlags.Any) { - if (type.aliasSymbol) { - return factory.createTypeReferenceNode(symbolToEntityNameNode(type.aliasSymbol), mapToTypeNodes(type.aliasTypeArguments, context)); + context.approximateLength += 3; + return factory.createKeywordTypeNode(type === intrinsicMarkerType ? SyntaxKind.IntrinsicKeyword : SyntaxKind.AnyKeyword); + } + if (type.flags & TypeFlags.Unknown) { + return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword); + } + if (type.flags & TypeFlags.String) { + context.approximateLength += 6; + return factory.createKeywordTypeNode(SyntaxKind.StringKeyword); + } + if (type.flags & TypeFlags.Number) { + context.approximateLength += 6; + return factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); + } + if (type.flags & TypeFlags.BigInt) { + context.approximateLength += 6; + return factory.createKeywordTypeNode(SyntaxKind.BigIntKeyword); + } + if (type.flags & TypeFlags.Boolean && !type.aliasSymbol) { + context.approximateLength += 7; + return factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword); + } + if (type.flags & TypeFlags.EnumLike) { + if (type.symbol.flags & SymbolFlags.EnumMember) { + const parentSymbol = getParentOfSymbol(type.symbol)!; + const parentName = symbolToTypeNode(parentSymbol, context, SymbolFlags.Type); + if (getDeclaredTypeOfSymbol(parentSymbol) === type) { + return parentName; + } + const memberName = symbolName(type.symbol); + if (isIdentifierText(memberName, ScriptTarget.ES3)) { + return appendReferenceToType( + parentName as TypeReferenceNode | ImportTypeNode, + factory.createTypeReferenceNode(memberName, /*typeArguments*/ undefined) + ); } - if (type === unresolvedType) { - return addSyntheticLeadingComment(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), SyntaxKind.MultiLineCommentTrivia, "unresolved"); + if (isImportTypeNode(parentName)) { + (parentName as any).isTypeOf = true; // mutably update, node is freshly manufactured anyhow + return factory.createIndexedAccessTypeNode(parentName, factory.createLiteralTypeNode(factory.createStringLiteral(memberName))); } - context.approximateLength += 3; - return factory.createKeywordTypeNode(type === intrinsicMarkerType ? SyntaxKind.IntrinsicKeyword : SyntaxKind.AnyKeyword); - } - if (type.flags & TypeFlags.Unknown) { - return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword); - } - if (type.flags & TypeFlags.String) { - context.approximateLength += 6; - return factory.createKeywordTypeNode(SyntaxKind.StringKeyword); - } - if (type.flags & TypeFlags.Number) { - context.approximateLength += 6; - return factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); - } - if (type.flags & TypeFlags.BigInt) { - context.approximateLength += 6; - return factory.createKeywordTypeNode(SyntaxKind.BigIntKeyword); - } - if (type.flags & TypeFlags.Boolean && !type.aliasSymbol) { - context.approximateLength += 7; - return factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword); - } - if (type.flags & TypeFlags.EnumLike) { - if (type.symbol.flags & SymbolFlags.EnumMember) { - const parentSymbol = getParentOfSymbol(type.symbol)!; - const parentName = symbolToTypeNode(parentSymbol, context, SymbolFlags.Type); - if (getDeclaredTypeOfSymbol(parentSymbol) === type) { - return parentName; - } - const memberName = symbolName(type.symbol); - if (isIdentifierText(memberName, ScriptTarget.ES3)) { - return appendReferenceToType( - parentName as TypeReferenceNode | ImportTypeNode, - factory.createTypeReferenceNode(memberName, /*typeArguments*/ undefined) - ); - } - if (isImportTypeNode(parentName)) { - (parentName as any).isTypeOf = true; // mutably update, node is freshly manufactured anyhow - return factory.createIndexedAccessTypeNode(parentName, factory.createLiteralTypeNode(factory.createStringLiteral(memberName))); - } - else if (isTypeReferenceNode(parentName)) { - return factory.createIndexedAccessTypeNode(factory.createTypeQueryNode(parentName.typeName), factory.createLiteralTypeNode(factory.createStringLiteral(memberName))); - } - else { - return Debug.fail("Unhandled type node kind returned from `symbolToTypeNode`."); - } + else if (isTypeReferenceNode(parentName)) { + return factory.createIndexedAccessTypeNode(factory.createTypeQueryNode(parentName.typeName), factory.createLiteralTypeNode(factory.createStringLiteral(memberName))); } - return symbolToTypeNode(type.symbol, context, SymbolFlags.Type); - } - if (type.flags & TypeFlags.StringLiteral) { - context.approximateLength += ((type as StringLiteralType).value.length + 2); - return factory.createLiteralTypeNode(setEmitFlags(factory.createStringLiteral((type as StringLiteralType).value, !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType)), EmitFlags.NoAsciiEscaping)); - } - if (type.flags & TypeFlags.NumberLiteral) { - const value = (type as NumberLiteralType).value; - context.approximateLength += ("" + value).length; - return factory.createLiteralTypeNode(value < 0 ? factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-value)) : factory.createNumericLiteral(value)); - } - if (type.flags & TypeFlags.BigIntLiteral) { - context.approximateLength += (pseudoBigIntToString((type as BigIntLiteralType).value).length) + 1; - return factory.createLiteralTypeNode((factory.createBigIntLiteral((type as BigIntLiteralType).value))); - } - if (type.flags & TypeFlags.BooleanLiteral) { - context.approximateLength += (type as IntrinsicType).intrinsicName.length; - return factory.createLiteralTypeNode((type as IntrinsicType).intrinsicName === "true" ? factory.createTrue() : factory.createFalse()); - } - if (type.flags & TypeFlags.UniqueESSymbol) { - if (!(context.flags & NodeBuilderFlags.AllowUniqueESSymbolType)) { - if (isValueSymbolAccessible(type.symbol, context.enclosingDeclaration)) { - context.approximateLength += 6; - return symbolToTypeNode(type.symbol, context, SymbolFlags.Value); - } - if (context.tracker.reportInaccessibleUniqueSymbolError) { - context.tracker.reportInaccessibleUniqueSymbolError(); - } + else { + return Debug.fail("Unhandled type node kind returned from `symbolToTypeNode`."); } - context.approximateLength += 13; - return factory.createTypeOperatorNode(SyntaxKind.UniqueKeyword, factory.createKeywordTypeNode(SyntaxKind.SymbolKeyword)); - } - if (type.flags & TypeFlags.Void) { - context.approximateLength += 4; - return factory.createKeywordTypeNode(SyntaxKind.VoidKeyword); - } - if (type.flags & TypeFlags.Undefined) { - context.approximateLength += 9; - return factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword); - } - if (type.flags & TypeFlags.Null) { - context.approximateLength += 4; - return factory.createLiteralTypeNode(factory.createNull()); - } - if (type.flags & TypeFlags.Never) { - context.approximateLength += 5; - return factory.createKeywordTypeNode(SyntaxKind.NeverKeyword); - } - if (type.flags & TypeFlags.ESSymbol) { - context.approximateLength += 6; - return factory.createKeywordTypeNode(SyntaxKind.SymbolKeyword); } - if (type.flags & TypeFlags.NonPrimitive) { - context.approximateLength += 6; - return factory.createKeywordTypeNode(SyntaxKind.ObjectKeyword); + return symbolToTypeNode(type.symbol, context, SymbolFlags.Type); + } + if (type.flags & TypeFlags.StringLiteral) { + context.approximateLength += ((type as StringLiteralType).value.length + 2); + return factory.createLiteralTypeNode(setEmitFlags(factory.createStringLiteral((type as StringLiteralType).value, !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType)), EmitFlags.NoAsciiEscaping)); + } + if (type.flags & TypeFlags.NumberLiteral) { + const value = (type as NumberLiteralType).value; + context.approximateLength += ("" + value).length; + return factory.createLiteralTypeNode(value < 0 ? factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-value)) : factory.createNumericLiteral(value)); + } + if (type.flags & TypeFlags.BigIntLiteral) { + context.approximateLength += (pseudoBigIntToString((type as BigIntLiteralType).value).length) + 1; + return factory.createLiteralTypeNode((factory.createBigIntLiteral((type as BigIntLiteralType).value))); + } + if (type.flags & TypeFlags.BooleanLiteral) { + context.approximateLength += (type as IntrinsicType).intrinsicName.length; + return factory.createLiteralTypeNode((type as IntrinsicType).intrinsicName === "true" ? factory.createTrue() : factory.createFalse()); + } + if (type.flags & TypeFlags.UniqueESSymbol) { + if (!(context.flags & NodeBuilderFlags.AllowUniqueESSymbolType)) { + if (isValueSymbolAccessible(type.symbol, context.enclosingDeclaration)) { + context.approximateLength += 6; + return symbolToTypeNode(type.symbol, context, SymbolFlags.Value); + } + if (context.tracker.reportInaccessibleUniqueSymbolError) { + context.tracker.reportInaccessibleUniqueSymbolError(); + } } - if (isThisTypeParameter(type)) { - if (context.flags & NodeBuilderFlags.InObjectTypeLiteral) { - if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowThisInObjectLiteral)) { - context.encounteredError = true; - } - if (context.tracker.reportInaccessibleThisError) { - context.tracker.reportInaccessibleThisError(); - } + context.approximateLength += 13; + return factory.createTypeOperatorNode(SyntaxKind.UniqueKeyword, factory.createKeywordTypeNode(SyntaxKind.SymbolKeyword)); + } + if (type.flags & TypeFlags.Void) { + context.approximateLength += 4; + return factory.createKeywordTypeNode(SyntaxKind.VoidKeyword); + } + if (type.flags & TypeFlags.Undefined) { + context.approximateLength += 9; + return factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword); + } + if (type.flags & TypeFlags.Null) { + context.approximateLength += 4; + return factory.createLiteralTypeNode(factory.createNull()); + } + if (type.flags & TypeFlags.Never) { + context.approximateLength += 5; + return factory.createKeywordTypeNode(SyntaxKind.NeverKeyword); + } + if (type.flags & TypeFlags.ESSymbol) { + context.approximateLength += 6; + return factory.createKeywordTypeNode(SyntaxKind.SymbolKeyword); + } + if (type.flags & TypeFlags.NonPrimitive) { + context.approximateLength += 6; + return factory.createKeywordTypeNode(SyntaxKind.ObjectKeyword); + } + if (isThisTypeParameter(type)) { + if (context.flags & NodeBuilderFlags.InObjectTypeLiteral) { + if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowThisInObjectLiteral)) { + context.encounteredError = true; + } + if (context.tracker.reportInaccessibleThisError) { + context.tracker.reportInaccessibleThisError(); } - context.approximateLength += 4; - return factory.createThisTypeNode(); } + context.approximateLength += 4; + return factory.createThisTypeNode(); + } - if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) { - const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context); - if (isReservedMemberName(type.aliasSymbol.escapedName) && !(type.aliasSymbol.flags & SymbolFlags.Class)) return factory.createTypeReferenceNode(factory.createIdentifier(""), typeArgumentNodes); - if (length(typeArgumentNodes) === 1 && type.aliasSymbol === globalArrayType.symbol) { - return factory.createArrayTypeNode(typeArgumentNodes![0]); - } - return symbolToTypeNode(type.aliasSymbol, context, SymbolFlags.Type, typeArgumentNodes); + if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) { + const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context); + if (isReservedMemberName(type.aliasSymbol.escapedName) && !(type.aliasSymbol.flags & SymbolFlags.Class)) return factory.createTypeReferenceNode(factory.createIdentifier(""), typeArgumentNodes); + if (length(typeArgumentNodes) === 1 && type.aliasSymbol === globalArrayType.symbol) { + return factory.createArrayTypeNode(typeArgumentNodes![0]); } + return symbolToTypeNode(type.aliasSymbol, context, SymbolFlags.Type, typeArgumentNodes); + } - const objectFlags = getObjectFlags(type); + const objectFlags = getObjectFlags(type); - if (objectFlags & ObjectFlags.Reference) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - return (type as TypeReference).node ? visitAndTransformType(type, typeReferenceToTypeNode) : typeReferenceToTypeNode(type as TypeReference); + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + return (type as TypeReference).node ? visitAndTransformType(type, typeReferenceToTypeNode) : typeReferenceToTypeNode(type as TypeReference); + } + if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) { + if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) { + context.approximateLength += (symbolName(type.symbol).length + 6); + let constraintNode: TypeNode | undefined; + const constraint = getConstraintOfTypeParameter(type as TypeParameter); + if (constraint) { + // If the infer type has a constraint that is not the same as the constraint + // we would have normally inferred based on context, we emit the constraint + // using `infer T extends ?`. We omit inferred constraints from type references + // as they may be elided. + const inferredConstraint = getInferredTypeParameterConstraint(type as TypeParameter, /*omitTypeReferences*/ true); + if (!(inferredConstraint && isTypeIdenticalTo(constraint, inferredConstraint))) { + context.approximateLength += 9; + constraintNode = constraint && typeToTypeNodeHelper(constraint, context); + } + } + return factory.createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, constraintNode)); + } + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && + type.flags & TypeFlags.TypeParameter && + !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) { + const name = typeParameterToName(type, context); + context.approximateLength += idText(name).length; + return factory.createTypeReferenceNode(factory.createIdentifier(idText(name)), /*typeArguments*/ undefined); + } + // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. + if (type.symbol) { + return symbolToTypeNode(type.symbol, context, SymbolFlags.Type); } - if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) { - if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) { - context.approximateLength += (symbolName(type.symbol).length + 6); - let constraintNode: TypeNode | undefined; - const constraint = getConstraintOfTypeParameter(type as TypeParameter); - if (constraint) { - // If the infer type has a constraint that is not the same as the constraint - // we would have normally inferred based on context, we emit the constraint - // using `infer T extends ?`. We omit inferred constraints from type references - // as they may be elided. - const inferredConstraint = getInferredTypeParameterConstraint(type as TypeParameter, /*omitTypeReferences*/ true); - if (!(inferredConstraint && isTypeIdenticalTo(constraint, inferredConstraint))) { - context.approximateLength += 9; - constraintNode = constraint && typeToTypeNodeHelper(constraint, context); - } - } - return factory.createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, constraintNode)); - } - if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && - type.flags & TypeFlags.TypeParameter && - !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) { - const name = typeParameterToName(type, context); - context.approximateLength += idText(name).length; - return factory.createTypeReferenceNode(factory.createIdentifier(idText(name)), /*typeArguments*/ undefined); - } - // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. - if (type.symbol) { - return symbolToTypeNode(type.symbol, context, SymbolFlags.Type); - } - const name = (type === markerSuperTypeForCheck || type === markerSubTypeForCheck) && varianceTypeParameter && varianceTypeParameter.symbol ? - (type === markerSubTypeForCheck ? "sub-" : "super-") + symbolName(varianceTypeParameter.symbol) : "?"; - return factory.createTypeReferenceNode(factory.createIdentifier(name), /*typeArguments*/ undefined); + const name = (type === markerSuperTypeForCheck || type === markerSubTypeForCheck) && varianceTypeParameter && varianceTypeParameter.symbol ? + (type === markerSubTypeForCheck ? "sub-" : "super-") + symbolName(varianceTypeParameter.symbol) : "?"; + return factory.createTypeReferenceNode(factory.createIdentifier(name), /*typeArguments*/ undefined); + } + if (type.flags & TypeFlags.Union && (type as UnionType).origin) { + type = (type as UnionType).origin!; + } + if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) { + const types = type.flags & TypeFlags.Union ? formatUnionTypes((type as UnionType).types) : (type as IntersectionType).types; + if (length(types) === 1) { + return typeToTypeNodeHelper(types[0], context); } - if (type.flags & TypeFlags.Union && (type as UnionType).origin) { - type = (type as UnionType).origin!; + const typeNodes = mapToTypeNodes(types, context, /*isBareList*/ true); + if (typeNodes && typeNodes.length > 0) { + return type.flags & TypeFlags.Union ? factory.createUnionTypeNode(typeNodes) : factory.createIntersectionTypeNode(typeNodes); } - if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) { - const types = type.flags & TypeFlags.Union ? formatUnionTypes((type as UnionType).types) : (type as IntersectionType).types; - if (length(types) === 1) { - return typeToTypeNodeHelper(types[0], context); - } - const typeNodes = mapToTypeNodes(types, context, /*isBareList*/ true); - if (typeNodes && typeNodes.length > 0) { - return type.flags & TypeFlags.Union ? factory.createUnionTypeNode(typeNodes) : factory.createIntersectionTypeNode(typeNodes); - } - else { - if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { - context.encounteredError = true; - } - return undefined!; // TODO: GH#18217 + else { + if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { + context.encounteredError = true; } + return undefined!; // TODO: GH#18217 } - if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // The type is an object literal type. - return createAnonymousTypeNode(type as ObjectType); - } - if (type.flags & TypeFlags.Index) { - const indexedType = (type as IndexType).type; - context.approximateLength += 6; - const indexTypeNode = typeToTypeNodeHelper(indexedType, context); - return factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, indexTypeNode); - } - if (type.flags & TypeFlags.TemplateLiteral) { - const texts = (type as TemplateLiteralType).texts; - const types = (type as TemplateLiteralType).types; - const templateHead = factory.createTemplateHead(texts[0]); - const templateSpans = factory.createNodeArray( - map(types, (t, i) => factory.createTemplateLiteralTypeSpan( - typeToTypeNodeHelper(t, context), - (i < types.length - 1 ? factory.createTemplateMiddle : factory.createTemplateTail)(texts[i + 1])))); - context.approximateLength += 2; - return factory.createTemplateLiteralType(templateHead, templateSpans); - } - if (type.flags & TypeFlags.StringMapping) { - const typeNode = typeToTypeNodeHelper((type as StringMappingType).type, context); - return symbolToTypeNode((type as StringMappingType).symbol, context, SymbolFlags.Type, [typeNode]); - } - if (type.flags & TypeFlags.IndexedAccess) { - const objectTypeNode = typeToTypeNodeHelper((type as IndexedAccessType).objectType, context); - const indexTypeNode = typeToTypeNodeHelper((type as IndexedAccessType).indexType, context); - context.approximateLength += 2; - return factory.createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); - } - if (type.flags & TypeFlags.Conditional) { - return visitAndTransformType(type, type => conditionalTypeToTypeNode(type as ConditionalType)); - } - if (type.flags & TypeFlags.Substitution) { - return typeToTypeNodeHelper((type as SubstitutionType).baseType, context); - } + } + if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // The type is an object literal type. + return createAnonymousTypeNode(type as ObjectType); + } + if (type.flags & TypeFlags.Index) { + const indexedType = (type as IndexType).type; + context.approximateLength += 6; + const indexTypeNode = typeToTypeNodeHelper(indexedType, context); + return factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, indexTypeNode); + } + if (type.flags & TypeFlags.TemplateLiteral) { + const texts = (type as TemplateLiteralType).texts; + const types = (type as TemplateLiteralType).types; + const templateHead = factory.createTemplateHead(texts[0]); + const templateSpans = factory.createNodeArray( + map(types, (t, i) => factory.createTemplateLiteralTypeSpan( + typeToTypeNodeHelper(t, context), + (i < types.length - 1 ? factory.createTemplateMiddle : factory.createTemplateTail)(texts[i + 1])))); + context.approximateLength += 2; + return factory.createTemplateLiteralType(templateHead, templateSpans); + } + if (type.flags & TypeFlags.StringMapping) { + const typeNode = typeToTypeNodeHelper((type as StringMappingType).type, context); + return symbolToTypeNode((type as StringMappingType).symbol, context, SymbolFlags.Type, [typeNode]); + } + if (type.flags & TypeFlags.IndexedAccess) { + const objectTypeNode = typeToTypeNodeHelper((type as IndexedAccessType).objectType, context); + const indexTypeNode = typeToTypeNodeHelper((type as IndexedAccessType).indexType, context); + context.approximateLength += 2; + return factory.createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); + } + if (type.flags & TypeFlags.Conditional) { + return visitAndTransformType(type, type => conditionalTypeToTypeNode(type as ConditionalType)); + } + if (type.flags & TypeFlags.Substitution) { + return typeToTypeNodeHelper((type as SubstitutionType).baseType, context); + } - return Debug.fail("Should be unreachable."); + return Debug.fail("Should be unreachable."); - function conditionalTypeToTypeNode(type: ConditionalType) { - const checkTypeNode = typeToTypeNodeHelper(type.checkType, context); - context.approximateLength += 15; - if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && type.root.isDistributive && !(type.checkType.flags & TypeFlags.TypeParameter)) { - const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String)); - const name = typeParameterToName(newParam, context); - const newTypeVariable = factory.createTypeReferenceNode(name); - context.approximateLength += 37; // 15 each for two added conditionals, 7 for an added infer type - const newMapper = prependTypeMapping(type.root.checkType, newParam, type.mapper); - const saveInferTypeParameters = context.inferTypeParameters; - context.inferTypeParameters = type.root.inferTypeParameters; - const extendsTypeNode = typeToTypeNodeHelper(instantiateType(type.root.extendsType, newMapper), context); - context.inferTypeParameters = saveInferTypeParameters; - const trueTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(type.root.node.trueType), newMapper)); - const falseTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(type.root.node.falseType), newMapper)); - - - // outermost conditional makes `T` a type parameter, allowing the inner conditionals to be distributive - // second conditional makes `T` have `T & checkType` substitution, so it is correctly usable as the checkType - // inner conditional runs the check the user provided on the check type (distributively) and returns the result - // checkType extends infer T ? T extends checkType ? T extends extendsType ? trueType : falseType : never : never; - // this is potentially simplifiable to - // checkType extends infer T ? T extends checkType & extendsType ? trueType : falseType : never; - // but that may confuse users who read the output more. - // On the other hand, - // checkType extends infer T extends checkType ? T extends extendsType ? trueType : falseType : never; - // may also work with `infer ... extends ...` in, but would produce declarations only compatible with the latest TS. - return factory.createConditionalTypeNode( - checkTypeNode, - factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable.typeName) as Identifier)), - factory.createConditionalTypeNode( - factory.createTypeReferenceNode(factory.cloneNode(name)), - typeToTypeNodeHelper(type.checkType, context), - factory.createConditionalTypeNode(newTypeVariable, extendsTypeNode, trueTypeNode, falseTypeNode), - factory.createKeywordTypeNode(SyntaxKind.NeverKeyword) - ), - factory.createKeywordTypeNode(SyntaxKind.NeverKeyword) - ); - } + function conditionalTypeToTypeNode(type: ConditionalType) { + const checkTypeNode = typeToTypeNodeHelper(type.checkType, context); + context.approximateLength += 15; + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && type.root.isDistributive && !(type.checkType.flags & TypeFlags.TypeParameter)) { + const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String)); + const name = typeParameterToName(newParam, context); + const newTypeVariable = factory.createTypeReferenceNode(name); + context.approximateLength += 37; // 15 each for two added conditionals, 7 for an added infer type + const newMapper = prependTypeMapping(type.root.checkType, newParam, type.mapper); const saveInferTypeParameters = context.inferTypeParameters; context.inferTypeParameters = type.root.inferTypeParameters; - const extendsTypeNode = typeToTypeNodeHelper(type.extendsType, context); + const extendsTypeNode = typeToTypeNodeHelper(instantiateType(type.root.extendsType, newMapper), context); context.inferTypeParameters = saveInferTypeParameters; - const trueTypeNode = typeToTypeNodeOrCircularityElision(getTrueTypeFromConditionalType(type)); - const falseTypeNode = typeToTypeNodeOrCircularityElision(getFalseTypeFromConditionalType(type)); - return factory.createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); + const trueTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(type.root.node.trueType), newMapper)); + const falseTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(type.root.node.falseType), newMapper)); + + + // outermost conditional makes `T` a type parameter, allowing the inner conditionals to be distributive + // second conditional makes `T` have `T & checkType` substitution, so it is correctly usable as the checkType + // inner conditional runs the check the user provided on the check type (distributively) and returns the result + // checkType extends infer T ? T extends checkType ? T extends extendsType ? trueType : falseType : never : never; + // this is potentially simplifiable to + // checkType extends infer T ? T extends checkType & extendsType ? trueType : falseType : never; + // but that may confuse users who read the output more. + // On the other hand, + // checkType extends infer T extends checkType ? T extends extendsType ? trueType : falseType : never; + // may also work with `infer ... extends ...` in, but would produce declarations only compatible with the latest TS. + return factory.createConditionalTypeNode( + checkTypeNode, + factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable.typeName) as Identifier)), + factory.createConditionalTypeNode( + factory.createTypeReferenceNode(factory.cloneNode(name)), + typeToTypeNodeHelper(type.checkType, context), + factory.createConditionalTypeNode(newTypeVariable, extendsTypeNode, trueTypeNode, falseTypeNode), + factory.createKeywordTypeNode(SyntaxKind.NeverKeyword) + ), + factory.createKeywordTypeNode(SyntaxKind.NeverKeyword) + ); } + const saveInferTypeParameters = context.inferTypeParameters; + context.inferTypeParameters = type.root.inferTypeParameters; + const extendsTypeNode = typeToTypeNodeHelper(type.extendsType, context); + context.inferTypeParameters = saveInferTypeParameters; + const trueTypeNode = typeToTypeNodeOrCircularityElision(getTrueTypeFromConditionalType(type)); + const falseTypeNode = typeToTypeNodeOrCircularityElision(getFalseTypeFromConditionalType(type)); + return factory.createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); + } - function typeToTypeNodeOrCircularityElision(type: Type) { - if (type.flags & TypeFlags.Union) { - if (context.visitedTypes?.has(getTypeId(type))) { - if (!(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { - context.encounteredError = true; - context.tracker?.reportCyclicStructureError?.(); - } - return createElidedInformationPlaceholder(context); + function typeToTypeNodeOrCircularityElision(type: Type) { + if (type.flags & TypeFlags.Union) { + if (context.visitedTypes?.has(getTypeId(type))) { + if (!(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { + context.encounteredError = true; + context.tracker?.reportCyclicStructureError?.(); } - return visitAndTransformType(type, type => typeToTypeNodeHelper(type, context)); + return createElidedInformationPlaceholder(context); } - return typeToTypeNodeHelper(type, context); + return visitAndTransformType(type, type => typeToTypeNodeHelper(type, context)); } + return typeToTypeNodeHelper(type, context); + } - function isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type: MappedType) { - return isMappedTypeWithKeyofConstraintDeclaration(type) - && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter); - } + function isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type: MappedType) { + return isMappedTypeWithKeyofConstraintDeclaration(type) + && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.TypeParameter); + } - function createMappedTypeNodeFromType(type: MappedType) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const readonlyToken = type.declaration.readonlyToken ? factory.createToken(type.declaration.readonlyToken.kind) as ReadonlyKeyword | PlusToken | MinusToken : undefined; - const questionToken = type.declaration.questionToken ? factory.createToken(type.declaration.questionToken.kind) as QuestionToken | PlusToken | MinusToken : undefined; - let appropriateConstraintTypeNode: TypeNode; - let newTypeVariable: TypeReferenceNode | undefined; - if (isMappedTypeWithKeyofConstraintDeclaration(type)) { - // We have a { [P in keyof T]: X } - // We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType` - if (isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { - const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String)); - const name = typeParameterToName(newParam, context); - newTypeVariable = factory.createTypeReferenceNode(name); - } - appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, newTypeVariable || typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)); - } - else { - appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context); - } - const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode); - const nameTypeNode = type.declaration.nameType ? typeToTypeNodeHelper(getNameTypeFromMappedType(type)!, context) : undefined; - const templateTypeNode = typeToTypeNodeHelper(removeMissingType(getTemplateTypeFromMappedType(type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), context); - const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode, /*members*/ undefined); - context.approximateLength += 10; - const result = setEmitFlags(mappedTypeNode, EmitFlags.SingleLine); + function createMappedTypeNodeFromType(type: MappedType) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + const readonlyToken = type.declaration.readonlyToken ? factory.createToken(type.declaration.readonlyToken.kind) as ReadonlyKeyword | PlusToken | MinusToken : undefined; + const questionToken = type.declaration.questionToken ? factory.createToken(type.declaration.questionToken.kind) as QuestionToken | PlusToken | MinusToken : undefined; + let appropriateConstraintTypeNode: TypeNode; + let newTypeVariable: TypeReferenceNode | undefined; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } + // We do this to ensure we retain the toplevel keyof-ness of the type which may be lost due to keyof distribution during `getConstraintTypeFromMappedType` if (isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { - // homomorphic mapped type with a non-homomorphic naive inlining - // wrap it with a conditional like `SomeModifiersType extends infer U ? {..the mapped type...} : never` to ensure the resulting - // type stays homomorphic - const originalConstraint = instantiateType(getConstraintOfTypeParameter(getTypeFromTypeNode((type.declaration.typeParameter.constraint! as TypeOperatorNode).type) as TypeParameter) || unknownType, type.mapper); - return factory.createConditionalTypeNode( - typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context), - factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier, originalConstraint.flags & TypeFlags.Unknown ? undefined : typeToTypeNodeHelper(originalConstraint, context))), - result, - factory.createKeywordTypeNode(SyntaxKind.NeverKeyword) - ); + const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String)); + const name = typeParameterToName(newParam, context); + newTypeVariable = factory.createTypeReferenceNode(name); } - return result; + appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, newTypeVariable || typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)); + } + else { + appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context); + } + const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode); + const nameTypeNode = type.declaration.nameType ? typeToTypeNodeHelper(getNameTypeFromMappedType(type)!, context) : undefined; + const templateTypeNode = typeToTypeNodeHelper(removeMissingType(getTemplateTypeFromMappedType(type), !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), context); + const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode, /*members*/ undefined); + context.approximateLength += 10; + const result = setEmitFlags(mappedTypeNode, EmitFlags.SingleLine); + if (isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type) && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { + // homomorphic mapped type with a non-homomorphic naive inlining + // wrap it with a conditional like `SomeModifiersType extends infer U ? {..the mapped type...} : never` to ensure the resulting + // type stays homomorphic + const originalConstraint = instantiateType(getConstraintOfTypeParameter(getTypeFromTypeNode((type.declaration.typeParameter.constraint! as TypeOperatorNode).type) as TypeParameter) || unknownType, type.mapper); + return factory.createConditionalTypeNode( + typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context), + factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier, originalConstraint.flags & TypeFlags.Unknown ? undefined : typeToTypeNodeHelper(originalConstraint, context))), + result, + factory.createKeywordTypeNode(SyntaxKind.NeverKeyword) + ); } + return result; + } - function createAnonymousTypeNode(type: ObjectType): TypeNode { - const typeId = type.id; - const symbol = type.symbol; - if (symbol) { - const isInstanceType = isClassInstanceSide(type) ? SymbolFlags.Type : SymbolFlags.Value; - if (isJSConstructor(symbol.valueDeclaration)) { - // Instance and static types share the same symbol; only add 'typeof' for the static side. - return symbolToTypeNode(symbol, context, isInstanceType); - } - // Always use 'typeof T' for type of class, enum, and module objects - else if (symbol.flags & SymbolFlags.Class - && !getBaseTypeVariableOfClass(symbol) - && !(symbol.valueDeclaration && isClassLike(symbol.valueDeclaration) && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && (!isClassDeclaration(symbol.valueDeclaration) || isSymbolAccessible(symbol, context.enclosingDeclaration, isInstanceType, /*computeAliases*/ false).accessibility !== SymbolAccessibility.Accessible)) || - symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || - shouldWriteTypeOfFunctionSymbol()) { - return symbolToTypeNode(symbol, context, isInstanceType); - } - else if (context.visitedTypes?.has(typeId)) { - // If type is an anonymous type literal in a type alias declaration, use type alias name - const typeAlias = getTypeAliasForTypeLiteral(type); - if (typeAlias) { - // The specified symbol flags need to be reinterpreted as type flags - return symbolToTypeNode(typeAlias, context, SymbolFlags.Type); - } - else { - return createElidedInformationPlaceholder(context); - } + function createAnonymousTypeNode(type: ObjectType): TypeNode { + const typeId = type.id; + const symbol = type.symbol; + if (symbol) { + const isInstanceType = isClassInstanceSide(type) ? SymbolFlags.Type : SymbolFlags.Value; + if (isJSConstructor(symbol.valueDeclaration)) { + // Instance and static types share the same symbol; only add 'typeof' for the static side. + return symbolToTypeNode(symbol, context, isInstanceType); + } + // Always use 'typeof T' for type of class, enum, and module objects + else if (symbol.flags & SymbolFlags.Class + && !getBaseTypeVariableOfClass(symbol) + && !(symbol.valueDeclaration && isClassLike(symbol.valueDeclaration) && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && (!isClassDeclaration(symbol.valueDeclaration) || isSymbolAccessible(symbol, context.enclosingDeclaration, isInstanceType, /*computeAliases*/ false).accessibility !== SymbolAccessibility.Accessible)) || + symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || + shouldWriteTypeOfFunctionSymbol()) { + return symbolToTypeNode(symbol, context, isInstanceType); + } + else if (context.visitedTypes?.has(typeId)) { + // If type is an anonymous type literal in a type alias declaration, use type alias name + const typeAlias = getTypeAliasForTypeLiteral(type); + if (typeAlias) { + // The specified symbol flags need to be reinterpreted as type flags + return symbolToTypeNode(typeAlias, context, SymbolFlags.Type); } else { - return visitAndTransformType(type, createTypeNodeFromObjectType); + return createElidedInformationPlaceholder(context); } } else { - // Anonymous types without a symbol are never circular. - return createTypeNodeFromObjectType(type); - } - function shouldWriteTypeOfFunctionSymbol() { - const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method) && // typeof static method - some(symbol.declarations, declaration => isStatic(declaration)); - const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && - (symbol.parent || // is exported function symbol - forEach(symbol.declarations, declaration => - declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); - if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { - // typeof is allowed only for static/non local functions - return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedTypes?.has(typeId))) && // it is type of the symbol uses itself recursively - (!(context.flags & NodeBuilderFlags.UseStructuralFallback) || isValueSymbolAccessible(symbol, context.enclosingDeclaration)); // And the build is going to succeed without visibility error or there is no structural fallback allowed - } - } - } - - function visitAndTransformType(type: Type, transform: (type: Type) => T) { - const typeId = type.id; - const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; - const id = getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).node ? "N" + getNodeId((type as TypeReference).node!) : - type.flags & TypeFlags.Conditional ? "N" + getNodeId((type as ConditionalType).root.node) : - type.symbol ? (isConstructorObject ? "+" : "") + getSymbolId(type.symbol) : - undefined; - // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead - // of types allows us to catch circular references to instantiations of the same anonymous type - if (!context.visitedTypes) { - context.visitedTypes = new Set(); - } - if (id && !context.symbolDepth) { - context.symbolDepth = new Map(); + return visitAndTransformType(type, createTypeNodeFromObjectType); } + } + else { + // Anonymous types without a symbol are never circular. + return createTypeNodeFromObjectType(type); + } + function shouldWriteTypeOfFunctionSymbol() { + const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method) && // typeof static method + some(symbol.declarations, declaration => isStatic(declaration)); + const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && + (symbol.parent || // is exported function symbol + forEach(symbol.declarations, declaration => + declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); + if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { + // typeof is allowed only for static/non local functions + return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || (context.visitedTypes?.has(typeId))) && // it is type of the symbol uses itself recursively + (!(context.flags & NodeBuilderFlags.UseStructuralFallback) || isValueSymbolAccessible(symbol, context.enclosingDeclaration)); // And the build is going to succeed without visibility error or there is no structural fallback allowed + } + } + } + + function visitAndTransformType(type: Type, transform: (type: Type) => T) { + const typeId = type.id; + const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; + const id = getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).node ? "N" + getNodeId((type as TypeReference).node!) : + type.flags & TypeFlags.Conditional ? "N" + getNodeId((type as ConditionalType).root.node) : + type.symbol ? (isConstructorObject ? "+" : "") + getSymbolId(type.symbol) : + undefined; + // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead + // of types allows us to catch circular references to instantiations of the same anonymous type + if (!context.visitedTypes) { + context.visitedTypes = new Set(); + } + if (id && !context.symbolDepth) { + context.symbolDepth = new Map(); + } - const links = context.enclosingDeclaration && getNodeLinks(context.enclosingDeclaration); - const key = `${getTypeId(type)}|${context.flags}`; - if (links) { - links.serializedTypes ||= new Map(); - } - const cachedResult = links?.serializedTypes?.get(key); - if (cachedResult) { - if (cachedResult.truncating) { - context.truncating = true; - } - context.approximateLength += cachedResult.addedLength; - return deepCloneOrReuseNode(cachedResult) as TypeNode as T; + const links = context.enclosingDeclaration && getNodeLinks(context.enclosingDeclaration); + const key = `${getTypeId(type)}|${context.flags}`; + if (links) { + links.serializedTypes ||= new Map(); + } + const cachedResult = links?.serializedTypes?.get(key); + if (cachedResult) { + if (cachedResult.truncating) { + context.truncating = true; } + context.approximateLength += cachedResult.addedLength; + return deepCloneOrReuseNode(cachedResult) as TypeNode as T; + } - let depth: number | undefined; - if (id) { - depth = context.symbolDepth!.get(id) || 0; - if (depth > 10) { - return createElidedInformationPlaceholder(context); - } - context.symbolDepth!.set(id, depth + 1); + let depth: number | undefined; + if (id) { + depth = context.symbolDepth!.get(id) || 0; + if (depth > 10) { + return createElidedInformationPlaceholder(context); } - context.visitedTypes.add(typeId); - const startLength = context.approximateLength; - const result = transform(type); - const addedLength = context.approximateLength - startLength; - if (!context.reportedDiagnostic && !context.encounteredError) { - if (context.truncating) { - (result as any).truncating = true; - } - (result as any).addedLength = addedLength; - links?.serializedTypes?.set(key, result as TypeNode as TypeNode & {truncating?: boolean, addedLength: number}); - } - context.visitedTypes.delete(typeId); - if (id) { - context.symbolDepth!.set(id, depth!); + context.symbolDepth!.set(id, depth + 1); + } + context.visitedTypes.add(typeId); + const startLength = context.approximateLength; + const result = transform(type); + const addedLength = context.approximateLength - startLength; + if (!context.reportedDiagnostic && !context.encounteredError) { + if (context.truncating) { + (result as any).truncating = true; } - return result; + (result as any).addedLength = addedLength; + links?.serializedTypes?.set(key, result as TypeNode as TypeNode & {truncating?: boolean, addedLength: number}); + } + context.visitedTypes.delete(typeId); + if (id) { + context.symbolDepth!.set(id, depth!); + } + return result; - function deepCloneOrReuseNode(node: Node): Node { - if (!nodeIsSynthesized(node) && getParseTreeNode(node) === node) { - return node; - } - return setTextRange(factory.cloneNode(visitEachChild(node, deepCloneOrReuseNode, nullTransformationContext, deepCloneOrReuseNodes)), node); - } - - function deepCloneOrReuseNodes(nodes: NodeArray, visitor: Visitor | undefined, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray; - function deepCloneOrReuseNodes(nodes: NodeArray | undefined, visitor: Visitor | undefined, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined; - function deepCloneOrReuseNodes(nodes: NodeArray | undefined, visitor: Visitor | undefined, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined { - if (nodes && nodes.length === 0) { - // Ensure we explicitly make a copy of an empty array; visitNodes will not do this unless the array has elements, - // which can lead to us reusing the same empty NodeArray more than once within the same AST during type noding. - return setTextRange(factory.createNodeArray(/*nodes*/ undefined, nodes.hasTrailingComma), nodes); - } - return visitNodes(nodes, visitor, test, start, count); - } - } - - function createTypeNodeFromObjectType(type: ObjectType): TypeNode { - if (isGenericMappedType(type) || (type as MappedType).containsError) { - return createMappedTypeNodeFromType(type as MappedType); - } - - const resolved = resolveStructuredTypeMembers(type); - if (!resolved.properties.length && !resolved.indexInfos.length) { - if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { - context.approximateLength += 2; - return setEmitFlags(factory.createTypeLiteralNode(/*members*/ undefined), EmitFlags.SingleLine); - } - - if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { - const signature = resolved.callSignatures[0]; - const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, context) as FunctionTypeNode; - return signatureNode; - - } - - if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { - const signature = resolved.constructSignatures[0]; - const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, context) as ConstructorTypeNode; - return signatureNode; - } - } - - const abstractSignatures = filter(resolved.constructSignatures, signature => !!(signature.flags & SignatureFlags.Abstract)); - if (some(abstractSignatures)) { - const types = map(abstractSignatures, getOrCreateTypeFromSignature); - // count the number of type elements excluding abstract constructors - const typeElementCount = - resolved.callSignatures.length + - (resolved.constructSignatures.length - abstractSignatures.length) + - resolved.indexInfos.length + - // exclude `prototype` when writing a class expression as a type literal, as per - // the logic in `createTypeNodesFromResolvedType`. - (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral ? - countWhere(resolved.properties, p => !(p.flags & SymbolFlags.Prototype)) : - length(resolved.properties)); - // don't include an empty object literal if there were no other static-side - // properties to write, i.e. `abstract class C { }` becomes `abstract new () => {}` - // and not `(abstract new () => {}) & {}` - if (typeElementCount) { - // create a copy of the object type without any abstract construct signatures. - types.push(getResolvedTypeWithoutAbstractConstructSignatures(resolved)); - } - return typeToTypeNodeHelper(getIntersectionType(types), context); - } - - const savedFlags = context.flags; - context.flags |= NodeBuilderFlags.InObjectTypeLiteral; - const members = createTypeNodesFromResolvedType(resolved); - context.flags = savedFlags; - const typeLiteralNode = factory.createTypeLiteralNode(members); - context.approximateLength += 2; - setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine); - return typeLiteralNode; - } - - function typeReferenceToTypeNode(type: TypeReference) { - let typeArguments: readonly Type[] = getTypeArguments(type); - if (type.target === globalArrayType || type.target === globalReadonlyArrayType) { - if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) { - const typeArgumentNode = typeToTypeNodeHelper(typeArguments[0], context); - return factory.createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); - } - const elementType = typeToTypeNodeHelper(typeArguments[0], context); - const arrayType = factory.createArrayTypeNode(elementType); - return type.target === globalArrayType ? arrayType : factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType); - } - else if (type.target.objectFlags & ObjectFlags.Tuple) { - typeArguments = sameMap(typeArguments, (t, i) => removeMissingType(t, !!((type.target as TupleType).elementFlags[i] & ElementFlags.Optional))); - if (typeArguments.length > 0) { - const arity = getTypeReferenceArity(type); - const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context); - if (tupleConstituentNodes) { - if ((type.target as TupleType).labeledElementDeclarations) { - for (let i = 0; i < tupleConstituentNodes.length; i++) { - const flags = (type.target as TupleType).elementFlags[i]; - tupleConstituentNodes[i] = factory.createNamedTupleMember( - flags & ElementFlags.Variable ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined, - factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel((type.target as TupleType).labeledElementDeclarations![i]))), - flags & ElementFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, - flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : - tupleConstituentNodes[i] - ); - } - } - else { - for (let i = 0; i < Math.min(arity, tupleConstituentNodes.length); i++) { - const flags = (type.target as TupleType).elementFlags[i]; - tupleConstituentNodes[i] = - flags & ElementFlags.Variable ? factory.createRestTypeNode(flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : tupleConstituentNodes[i]) : - flags & ElementFlags.Optional ? factory.createOptionalTypeNode(tupleConstituentNodes[i]) : - tupleConstituentNodes[i]; - } - } - const tupleTypeNode = setEmitFlags(factory.createTupleTypeNode(tupleConstituentNodes), EmitFlags.SingleLine); - return (type.target as TupleType).readonly ? factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; - } - } - if (context.encounteredError || (context.flags & NodeBuilderFlags.AllowEmptyTuple)) { - const tupleTypeNode = setEmitFlags(factory.createTupleTypeNode([]), EmitFlags.SingleLine); - return (type.target as TupleType).readonly ? factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; - } - context.encounteredError = true; - return undefined!; // TODO: GH#18217 - } - else if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && - type.symbol.valueDeclaration && - isClassLike(type.symbol.valueDeclaration) && - !isValueSymbolAccessible(type.symbol, context.enclosingDeclaration) - ) { - return createAnonymousTypeNode(type); - } - else { - const outerTypeParameters = type.target.outerTypeParameters; - let i = 0; - let resultType: TypeReferenceNode | ImportTypeNode | undefined; - if (outerTypeParameters) { - const length = outerTypeParameters.length; - while (i < length) { - // Find group of type arguments for type parameters with the same declaring container. - const start = i; - const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i])!; - do { - i++; - } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); - // When type parameters are their own type arguments for the whole group (i.e. we have - // the default outer type arguments), we don't show the group. - if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - const typeArgumentSlice = mapToTypeNodes(typeArguments.slice(start, i), context); - const flags = context.flags; - context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; - const ref = symbolToTypeNode(parent, context, SymbolFlags.Type, typeArgumentSlice) as TypeReferenceNode | ImportTypeNode; - context.flags = flags; - resultType = !resultType ? ref : appendReferenceToType(resultType, ref as TypeReferenceNode); - } - } - } - let typeArgumentNodes: readonly TypeNode[] | undefined; - if (typeArguments.length > 0) { - const typeParameterCount = (type.target.typeParameters || emptyArray).length; - typeArgumentNodes = mapToTypeNodes(typeArguments.slice(i, typeParameterCount), context); - } - const flags = context.flags; - context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; - const finalRef = symbolToTypeNode(type.symbol, context, SymbolFlags.Type, typeArgumentNodes); - context.flags = flags; - return !resultType ? finalRef : appendReferenceToType(resultType, finalRef as TypeReferenceNode); + function deepCloneOrReuseNode(node: Node): Node { + if (!nodeIsSynthesized(node) && getParseTreeNode(node) === node) { + return node; } + return setTextRange(factory.cloneNode(visitEachChild(node, deepCloneOrReuseNode, nullTransformationContext, deepCloneOrReuseNodes)), node); } - - function appendReferenceToType(root: TypeReferenceNode | ImportTypeNode, ref: TypeReferenceNode): TypeReferenceNode | ImportTypeNode { - if (isImportTypeNode(root)) { - // first shift type arguments - let typeArguments = root.typeArguments; - let qualifier = root.qualifier; - if (qualifier) { - if (isIdentifier(qualifier)) { - qualifier = factory.updateIdentifier(qualifier, typeArguments); - } - else { - qualifier = factory.updateQualifiedName(qualifier, - qualifier.left, - factory.updateIdentifier(qualifier.right, typeArguments)); - } - } - typeArguments = ref.typeArguments; - // then move qualifiers - const ids = getAccessStack(ref); - for (const id of ids) { - qualifier = qualifier ? factory.createQualifiedName(qualifier, id) : id; - } - return factory.updateImportTypeNode( - root, - root.argument, - root.assertions, - qualifier, - typeArguments, - root.isTypeOf); - } - else { - // first shift type arguments - let typeArguments = root.typeArguments; - let typeName = root.typeName; - if (isIdentifier(typeName)) { - typeName = factory.updateIdentifier(typeName, typeArguments); - } - else { - typeName = factory.updateQualifiedName(typeName, - typeName.left, - factory.updateIdentifier(typeName.right, typeArguments)); - } - typeArguments = ref.typeArguments; - // then move qualifiers - const ids = getAccessStack(ref); - for (const id of ids) { - typeName = factory.createQualifiedName(typeName, id); - } - return factory.updateTypeReferenceNode( - root, - typeName, - typeArguments); + function deepCloneOrReuseNodes(nodes: NodeArray, visitor: Visitor | undefined, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray; + function deepCloneOrReuseNodes(nodes: NodeArray | undefined, visitor: Visitor | undefined, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined; + function deepCloneOrReuseNodes(nodes: NodeArray | undefined, visitor: Visitor | undefined, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined { + if (nodes && nodes.length === 0) { + // Ensure we explicitly make a copy of an empty array; visitNodes will not do this unless the array has elements, + // which can lead to us reusing the same empty NodeArray more than once within the same AST during type noding. + return setTextRange(factory.createNodeArray(/*nodes*/ undefined, nodes.hasTrailingComma), nodes); } + return visitNodes(nodes, visitor, test, start, count); } + } - function getAccessStack(ref: TypeReferenceNode): Identifier[] { - let state = ref.typeName; - const ids = []; - while (!isIdentifier(state)) { - ids.unshift(state.right); - state = state.left; - } - ids.unshift(state); - return ids; + function createTypeNodeFromObjectType(type: ObjectType): TypeNode { + if (isGenericMappedType(type) || (type as MappedType).containsError) { + return createMappedTypeNodeFromType(type as MappedType); } - function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined { - if (checkTruncationLength(context)) { - return [factory.createPropertySignature(/*modifiers*/ undefined, "...", /*questionToken*/ undefined, /*type*/ undefined)]; - } - const typeElements: TypeElement[] = []; - for (const signature of resolvedType.callSignatures) { - typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context) as CallSignatureDeclaration); - } - for (const signature of resolvedType.constructSignatures) { - if (signature.flags & SignatureFlags.Abstract) continue; - typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context) as ConstructSignatureDeclaration); - } - for (const info of resolvedType.indexInfos) { - typeElements.push(indexInfoToIndexSignatureDeclarationHelper(info, context, resolvedType.objectFlags & ObjectFlags.ReverseMapped ? createElidedInformationPlaceholder(context) : undefined)); + const resolved = resolveStructuredTypeMembers(type); + if (!resolved.properties.length && !resolved.indexInfos.length) { + if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { + context.approximateLength += 2; + return setEmitFlags(factory.createTypeLiteralNode(/*members*/ undefined), EmitFlags.SingleLine); } - const properties = resolvedType.properties; - if (!properties) { - return typeElements; - } + if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { + const signature = resolved.callSignatures[0]; + const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, context) as FunctionTypeNode; + return signatureNode; - let i = 0; - for (const propertySymbol of properties) { - i++; - if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { - if (propertySymbol.flags & SymbolFlags.Prototype) { - continue; - } - if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { - context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); - } - } - if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) { - typeElements.push(factory.createPropertySignature(/*modifiers*/ undefined, `... ${properties.length - i} more ...`, /*questionToken*/ undefined, /*type*/ undefined)); - addPropertyToElementList(properties[properties.length - 1], context, typeElements); - break; - } - addPropertyToElementList(propertySymbol, context, typeElements); + } + if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { + const signature = resolved.constructSignatures[0]; + const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, context) as ConstructorTypeNode; + return signatureNode; } - return typeElements.length ? typeElements : undefined; } - } - function createElidedInformationPlaceholder(context: NodeBuilderContext) { - context.approximateLength += 3; - if (!(context.flags & NodeBuilderFlags.NoTruncation)) { - return factory.createTypeReferenceNode(factory.createIdentifier("..."), /*typeArguments*/ undefined); + const abstractSignatures = filter(resolved.constructSignatures, signature => !!(signature.flags & SignatureFlags.Abstract)); + if (some(abstractSignatures)) { + const types = map(abstractSignatures, getOrCreateTypeFromSignature); + // count the number of type elements excluding abstract constructors + const typeElementCount = + resolved.callSignatures.length + + (resolved.constructSignatures.length - abstractSignatures.length) + + resolved.indexInfos.length + + // exclude `prototype` when writing a class expression as a type literal, as per + // the logic in `createTypeNodesFromResolvedType`. + (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral ? + countWhere(resolved.properties, p => !(p.flags & SymbolFlags.Prototype)) : + length(resolved.properties)); + // don't include an empty object literal if there were no other static-side + // properties to write, i.e. `abstract class C { }` becomes `abstract new () => {}` + // and not `(abstract new () => {}) & {}` + if (typeElementCount) { + // create a copy of the object type without any abstract construct signatures. + types.push(getResolvedTypeWithoutAbstractConstructSignatures(resolved)); + } + return typeToTypeNodeHelper(getIntersectionType(types), context); } - return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - - function shouldUsePlaceholderForProperty(propertySymbol: Symbol, context: NodeBuilderContext) { - // Use placeholders for reverse mapped types we've either already descended into, or which - // are nested reverse mappings within a mapping over a non-anonymous type. The later is a restriction mostly just to - // reduce the blowup in printback size from doing, eg, a deep reverse mapping over `Window`. - // Since anonymous types usually come from expressions, this allows us to preserve the output - // for deep mappings which likely come from expressions, while truncating those parts which - // come from mappings over library functions. - return !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped) - && ( - contains(context.reverseMappedStack, propertySymbol as ReverseMappedSymbol) - || ( - context.reverseMappedStack?.[0] - && !(getObjectFlags(last(context.reverseMappedStack).propertyType) & ObjectFlags.Anonymous) - ) - ); - } - function addPropertyToElementList(propertySymbol: Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) { - const propertyIsReverseMapped = !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped); - const propertyType = shouldUsePlaceholderForProperty(propertySymbol, context) ? - anyType : getNonMissingTypeOfSymbol(propertySymbol); - const saveEnclosingDeclaration = context.enclosingDeclaration; - context.enclosingDeclaration = undefined; - if (context.tracker.trackSymbol && isLateBoundName(propertySymbol.escapedName)) { - if (propertySymbol.declarations) { - const decl = first(propertySymbol.declarations); - if (hasLateBindableName(decl)) { - if (isBinaryExpression(decl)) { - const name = getNameOfDeclaration(decl); - if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) { - trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context); + const savedFlags = context.flags; + context.flags |= NodeBuilderFlags.InObjectTypeLiteral; + const members = createTypeNodesFromResolvedType(resolved); + context.flags = savedFlags; + const typeLiteralNode = factory.createTypeLiteralNode(members); + context.approximateLength += 2; + setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine); + return typeLiteralNode; + } + + function typeReferenceToTypeNode(type: TypeReference) { + let typeArguments: readonly Type[] = getTypeArguments(type); + if (type.target === globalArrayType || type.target === globalReadonlyArrayType) { + if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) { + const typeArgumentNode = typeToTypeNodeHelper(typeArguments[0], context); + return factory.createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); + } + const elementType = typeToTypeNodeHelper(typeArguments[0], context); + const arrayType = factory.createArrayTypeNode(elementType); + return type.target === globalArrayType ? arrayType : factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType); + } + else if (type.target.objectFlags & ObjectFlags.Tuple) { + typeArguments = sameMap(typeArguments, (t, i) => removeMissingType(t, !!((type.target as TupleType).elementFlags[i] & ElementFlags.Optional))); + if (typeArguments.length > 0) { + const arity = getTypeReferenceArity(type); + const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context); + if (tupleConstituentNodes) { + if ((type.target as TupleType).labeledElementDeclarations) { + for (let i = 0; i < tupleConstituentNodes.length; i++) { + const flags = (type.target as TupleType).elementFlags[i]; + tupleConstituentNodes[i] = factory.createNamedTupleMember( + flags & ElementFlags.Variable ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined, + factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel((type.target as TupleType).labeledElementDeclarations![i]))), + flags & ElementFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : + tupleConstituentNodes[i] + ); } } else { - trackComputedName(decl.name.expression, saveEnclosingDeclaration, context); + for (let i = 0; i < Math.min(arity, tupleConstituentNodes.length); i++) { + const flags = (type.target as TupleType).elementFlags[i]; + tupleConstituentNodes[i] = + flags & ElementFlags.Variable ? factory.createRestTypeNode(flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : tupleConstituentNodes[i]) : + flags & ElementFlags.Optional ? factory.createOptionalTypeNode(tupleConstituentNodes[i]) : + tupleConstituentNodes[i]; + } } + const tupleTypeNode = setEmitFlags(factory.createTupleTypeNode(tupleConstituentNodes), EmitFlags.SingleLine); + return (type.target as TupleType).readonly ? factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; } } - else if (context.tracker?.reportNonSerializableProperty) { - context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol)); + if (context.encounteredError || (context.flags & NodeBuilderFlags.AllowEmptyTuple)) { + const tupleTypeNode = setEmitFlags(factory.createTupleTypeNode([]), EmitFlags.SingleLine); + return (type.target as TupleType).readonly ? factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; } + context.encounteredError = true; + return undefined!; // TODO: GH#18217 } - context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration; - const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context); - context.enclosingDeclaration = saveEnclosingDeclaration; - context.approximateLength += (symbolName(propertySymbol).length + 1); - const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; - if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length && !isReadonlySymbol(propertySymbol)) { - const signatures = getSignaturesOfType(filterType(propertyType, t => !(t.flags & TypeFlags.Undefined)), SignatureKind.Call); - for (const signature of signatures) { - const methodDeclaration = signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, context, { name: propertyName, questionToken: optionalToken }) as MethodSignature; - typeElements.push(preserveCommentsOn(methodDeclaration)); - } + else if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && + type.symbol.valueDeclaration && + isClassLike(type.symbol.valueDeclaration) && + !isValueSymbolAccessible(type.symbol, context.enclosingDeclaration) + ) { + return createAnonymousTypeNode(type); } else { - let propertyTypeNode: TypeNode; - if (shouldUsePlaceholderForProperty(propertySymbol, context)) { - propertyTypeNode = createElidedInformationPlaceholder(context); - } - else { - if (propertyIsReverseMapped) { - context.reverseMappedStack ||= []; - context.reverseMappedStack.push(propertySymbol as ReverseMappedSymbol); + const outerTypeParameters = type.target.outerTypeParameters; + let i = 0; + let resultType: TypeReferenceNode | ImportTypeNode | undefined; + if (outerTypeParameters) { + const length = outerTypeParameters.length; + while (i < length) { + // Find group of type arguments for type parameters with the same declaring container. + const start = i; + const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i])!; + do { + i++; + } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); + // When type parameters are their own type arguments for the whole group (i.e. we have + // the default outer type arguments), we don't show the group. + if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { + const typeArgumentSlice = mapToTypeNodes(typeArguments.slice(start, i), context); + const flags = context.flags; + context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; + const ref = symbolToTypeNode(parent, context, SymbolFlags.Type, typeArgumentSlice) as TypeReferenceNode | ImportTypeNode; + context.flags = flags; + resultType = !resultType ? ref : appendReferenceToType(resultType, ref as TypeReferenceNode); + } + } + } + let typeArgumentNodes: readonly TypeNode[] | undefined; + if (typeArguments.length > 0) { + const typeParameterCount = (type.target.typeParameters || emptyArray).length; + typeArgumentNodes = mapToTypeNodes(typeArguments.slice(i, typeParameterCount), context); + } + const flags = context.flags; + context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; + const finalRef = symbolToTypeNode(type.symbol, context, SymbolFlags.Type, typeArgumentNodes); + context.flags = flags; + return !resultType ? finalRef : appendReferenceToType(resultType, finalRef as TypeReferenceNode); + } + } + + + function appendReferenceToType(root: TypeReferenceNode | ImportTypeNode, ref: TypeReferenceNode): TypeReferenceNode | ImportTypeNode { + if (isImportTypeNode(root)) { + // first shift type arguments + let typeArguments = root.typeArguments; + let qualifier = root.qualifier; + if (qualifier) { + if (isIdentifier(qualifier)) { + qualifier = factory.updateIdentifier(qualifier, typeArguments); } - propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, propertyType, propertySymbol, saveEnclosingDeclaration) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - if (propertyIsReverseMapped) { - context.reverseMappedStack!.pop(); + else { + qualifier = factory.updateQualifiedName(qualifier, + qualifier.left, + factory.updateIdentifier(qualifier.right, typeArguments)); } } - - const modifiers = isReadonlySymbol(propertySymbol) ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined; - if (modifiers) { - context.approximateLength += 9; + typeArguments = ref.typeArguments; + // then move qualifiers + const ids = getAccessStack(ref); + for (const id of ids) { + qualifier = qualifier ? factory.createQualifiedName(qualifier, id) : id; } - const propertySignature = factory.createPropertySignature( - modifiers, - propertyName, - optionalToken, - propertyTypeNode); - - typeElements.push(preserveCommentsOn(propertySignature)); + return factory.updateImportTypeNode( + root, + root.argument, + root.assertions, + qualifier, + typeArguments, + root.isTypeOf); } - - function preserveCommentsOn(node: T) { - if (some(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)) { - const d = propertySymbol.declarations?.find(d => d.kind === SyntaxKind.JSDocPropertyTag)! as JSDocPropertyTag; - const commentText = getTextOfJSDocComment(d.comment); - if (commentText) { - setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]); - } + else { + // first shift type arguments + let typeArguments = root.typeArguments; + let typeName = root.typeName; + if (isIdentifier(typeName)) { + typeName = factory.updateIdentifier(typeName, typeArguments); + } + else { + typeName = factory.updateQualifiedName(typeName, + typeName.left, + factory.updateIdentifier(typeName.right, typeArguments)); } - else if (propertySymbol.valueDeclaration) { - // Copy comments to node for declaration emit - setCommentRange(node, propertySymbol.valueDeclaration); + typeArguments = ref.typeArguments; + // then move qualifiers + const ids = getAccessStack(ref); + for (const id of ids) { + typeName = factory.createQualifiedName(typeName, id); } - return node; + return factory.updateTypeReferenceNode( + root, + typeName, + typeArguments); } } - function mapToTypeNodes(types: readonly Type[] | undefined, context: NodeBuilderContext, isBareList?: boolean): TypeNode[] | undefined { - if (some(types)) { - if (checkTruncationLength(context)) { - if (!isBareList) { - return [factory.createTypeReferenceNode("...", /*typeArguments*/ undefined)]; - } - else if (types.length > 2) { - return [ - typeToTypeNodeHelper(types[0], context), - factory.createTypeReferenceNode(`... ${types.length - 2} more ...`, /*typeArguments*/ undefined), - typeToTypeNodeHelper(types[types.length - 1], context) - ]; - } - } - const mayHaveNameCollisions = !(context.flags & NodeBuilderFlags.UseFullyQualifiedType); - /** Map from type reference identifier text to [type, index in `result` where the type node is] */ - const seenNames = mayHaveNameCollisions ? createUnderscoreEscapedMultiMap<[Type, number]>() : undefined; - const result: TypeNode[] = []; - let i = 0; - for (const type of types) { - i++; - if (checkTruncationLength(context) && (i + 2 < types.length - 1)) { - result.push(factory.createTypeReferenceNode(`... ${types.length - i} more ...`, /*typeArguments*/ undefined)); - const typeNode = typeToTypeNodeHelper(types[types.length - 1], context); - if (typeNode) { - result.push(typeNode); - } - break; + function getAccessStack(ref: TypeReferenceNode): Identifier[] { + let state = ref.typeName; + const ids = []; + while (!isIdentifier(state)) { + ids.unshift(state.right); + state = state.left; + } + ids.unshift(state); + return ids; + } + + function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined { + if (checkTruncationLength(context)) { + return [factory.createPropertySignature(/*modifiers*/ undefined, "...", /*questionToken*/ undefined, /*type*/ undefined)]; + } + const typeElements: TypeElement[] = []; + for (const signature of resolvedType.callSignatures) { + typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context) as CallSignatureDeclaration); + } + for (const signature of resolvedType.constructSignatures) { + if (signature.flags & SignatureFlags.Abstract) continue; + typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context) as ConstructSignatureDeclaration); + } + for (const info of resolvedType.indexInfos) { + typeElements.push(indexInfoToIndexSignatureDeclarationHelper(info, context, resolvedType.objectFlags & ObjectFlags.ReverseMapped ? createElidedInformationPlaceholder(context) : undefined)); + } + + const properties = resolvedType.properties; + if (!properties) { + return typeElements; + } + + let i = 0; + for (const propertySymbol of properties) { + i++; + if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { + if (propertySymbol.flags & SymbolFlags.Prototype) { + continue; } - context.approximateLength += 2; // Account for whitespace + separator - const typeNode = typeToTypeNodeHelper(type, context); - if (typeNode) { - result.push(typeNode); - if (seenNames && isIdentifierTypeReference(typeNode)) { - seenNames.add(typeNode.typeName.escapedText, [type, result.length - 1]); - } + if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { + context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); } } - - if (seenNames) { - // To avoid printing types like `[Foo, Foo]` or `Bar & Bar` where - // occurrences of the same name actually come from different - // namespaces, go through the single-identifier type reference nodes - // we just generated, and see if any names were generated more than - // once while referring to different types. If so, regenerate the - // type node for each entry by that name with the - // `UseFullyQualifiedType` flag enabled. - const saveContextFlags = context.flags; - context.flags |= NodeBuilderFlags.UseFullyQualifiedType; - seenNames.forEach(types => { - if (!arrayIsHomogeneous(types, ([a], [b]) => typesAreSameReference(a, b))) { - for (const [type, resultIndex] of types) { - result[resultIndex] = typeToTypeNodeHelper(type, context); - } - } - }); - context.flags = saveContextFlags; + if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) { + typeElements.push(factory.createPropertySignature(/*modifiers*/ undefined, `... ${properties.length - i} more ...`, /*questionToken*/ undefined, /*type*/ undefined)); + addPropertyToElementList(properties[properties.length - 1], context, typeElements); + break; } + addPropertyToElementList(propertySymbol, context, typeElements); - return result; } + return typeElements.length ? typeElements : undefined; } + } - function typesAreSameReference(a: Type, b: Type): boolean { - return a === b - || !!a.symbol && a.symbol === b.symbol - || !!a.aliasSymbol && a.aliasSymbol === b.aliasSymbol; + function createElidedInformationPlaceholder(context: NodeBuilderContext) { + context.approximateLength += 3; + if (!(context.flags & NodeBuilderFlags.NoTruncation)) { + return factory.createTypeReferenceNode(factory.createIdentifier("..."), /*typeArguments*/ undefined); } + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } - function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, context: NodeBuilderContext, typeNode: TypeNode | undefined): IndexSignatureDeclaration { - const name = getNameFromIndexInfo(indexInfo) || "x"; - const indexerTypeNode = typeToTypeNodeHelper(indexInfo.keyType, context); + function shouldUsePlaceholderForProperty(propertySymbol: Symbol, context: NodeBuilderContext) { + // Use placeholders for reverse mapped types we've either already descended into, or which + // are nested reverse mappings within a mapping over a non-anonymous type. The later is a restriction mostly just to + // reduce the blowup in printback size from doing, eg, a deep reverse mapping over `Window`. + // Since anonymous types usually come from expressions, this allows us to preserve the output + // for deep mappings which likely come from expressions, while truncating those parts which + // come from mappings over library functions. + return !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped) + && ( + contains(context.reverseMappedStack, propertySymbol as ReverseMappedSymbol) + || ( + context.reverseMappedStack?.[0] + && !(getObjectFlags(last(context.reverseMappedStack).propertyType) & ObjectFlags.Anonymous) + ) + ); + } - const indexingParameter = factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - name, - /*questionToken*/ undefined, - indexerTypeNode, - /*initializer*/ undefined); - if (!typeNode) { - typeNode = typeToTypeNodeHelper(indexInfo.type || anyType, context); + function addPropertyToElementList(propertySymbol: Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) { + const propertyIsReverseMapped = !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped); + const propertyType = shouldUsePlaceholderForProperty(propertySymbol, context) ? + anyType : getNonMissingTypeOfSymbol(propertySymbol); + const saveEnclosingDeclaration = context.enclosingDeclaration; + context.enclosingDeclaration = undefined; + if (context.tracker.trackSymbol && isLateBoundName(propertySymbol.escapedName)) { + if (propertySymbol.declarations) { + const decl = first(propertySymbol.declarations); + if (hasLateBindableName(decl)) { + if (isBinaryExpression(decl)) { + const name = getNameOfDeclaration(decl); + if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) { + trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context); + } + } + else { + trackComputedName(decl.name.expression, saveEnclosingDeclaration, context); + } + } } - if (!indexInfo.type && !(context.flags & NodeBuilderFlags.AllowEmptyIndexInfoType)) { - context.encounteredError = true; + else if (context.tracker?.reportNonSerializableProperty) { + context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol)); } - context.approximateLength += (name.length + 4); - return factory.createIndexSignature( - indexInfo.isReadonly ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined, - [indexingParameter], - typeNode); } - - interface SignatureToSignatureDeclarationOptions { - modifiers?: readonly Modifier[]; - name?: PropertyName; - questionToken?: QuestionToken; - privateSymbolVisitor?: (s: Symbol) => void; - bundledImports?: boolean; + context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration; + const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context); + context.enclosingDeclaration = saveEnclosingDeclaration; + context.approximateLength += (symbolName(propertySymbol).length + 1); + const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; + if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length && !isReadonlySymbol(propertySymbol)) { + const signatures = getSignaturesOfType(filterType(propertyType, t => !(t.flags & TypeFlags.Undefined)), SignatureKind.Call); + for (const signature of signatures) { + const methodDeclaration = signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, context, { name: propertyName, questionToken: optionalToken }) as MethodSignature; + typeElements.push(preserveCommentsOn(methodDeclaration)); + } } - - function signatureToSignatureDeclarationHelper(signature: Signature, kind: SignatureDeclaration["kind"], context: NodeBuilderContext, options?: SignatureToSignatureDeclarationOptions): SignatureDeclaration { - const suppressAny = context.flags & NodeBuilderFlags.SuppressAnyReturnType; - if (suppressAny) context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // suppress only toplevel `any`s - context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum - let typeParameters: TypeParameterDeclaration[] | undefined; - let typeArguments: TypeNode[] | undefined; - if (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature && signature.target && signature.mapper && signature.target.typeParameters) { - typeArguments = signature.target.typeParameters.map(parameter => typeToTypeNodeHelper(instantiateType(parameter, signature.mapper), context)); + else { + let propertyTypeNode: TypeNode; + if (shouldUsePlaceholderForProperty(propertySymbol, context)) { + propertyTypeNode = createElidedInformationPlaceholder(context); } else { - typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context)); + if (propertyIsReverseMapped) { + context.reverseMappedStack ||= []; + context.reverseMappedStack.push(propertySymbol as ReverseMappedSymbol); + } + propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, propertyType, propertySymbol, saveEnclosingDeclaration) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + if (propertyIsReverseMapped) { + context.reverseMappedStack!.pop(); + } } - const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0]; - // If the expanded parameter list had a variadic in a non-trailing position, don't expand it - const parameters = (some(expandedParams, p => p !== expandedParams[expandedParams.length - 1] && !!(getCheckFlags(p) & CheckFlags.RestParameter)) ? signature.parameters : expandedParams).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor, options?.privateSymbolVisitor, options?.bundledImports)); - const thisParameter = context.flags & NodeBuilderFlags.OmitThisParameter ? undefined : tryGetThisParameterDeclaration(signature, context); - if (thisParameter) { - parameters.unshift(thisParameter); + const modifiers = isReadonlySymbol(propertySymbol) ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined; + if (modifiers) { + context.approximateLength += 9; + } + const propertySignature = factory.createPropertySignature( + modifiers, + propertyName, + optionalToken, + propertyTypeNode); + + typeElements.push(preserveCommentsOn(propertySignature)); + } + + function preserveCommentsOn(node: T) { + if (some(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)) { + const d = propertySymbol.declarations?.find(d => d.kind === SyntaxKind.JSDocPropertyTag)! as JSDocPropertyTag; + const commentText = getTextOfJSDocComment(d.comment); + if (commentText) { + setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]); + } + } + else if (propertySymbol.valueDeclaration) { + // Copy comments to node for declaration emit + setCommentRange(node, propertySymbol.valueDeclaration); } + return node; + } + } - let returnTypeNode: TypeNode | undefined; - const typePredicate = getTypePredicateOfSignature(signature); - if (typePredicate) { - const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? - factory.createToken(SyntaxKind.AssertsKeyword) : - undefined; - const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? - setEmitFlags(factory.createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) : - factory.createThisTypeNode(); - const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context); - returnTypeNode = factory.createTypePredicateNode(assertsModifier, parameterName, typeNode); + function mapToTypeNodes(types: readonly Type[] | undefined, context: NodeBuilderContext, isBareList?: boolean): TypeNode[] | undefined { + if (some(types)) { + if (checkTruncationLength(context)) { + if (!isBareList) { + return [factory.createTypeReferenceNode("...", /*typeArguments*/ undefined)]; + } + else if (types.length > 2) { + return [ + typeToTypeNodeHelper(types[0], context), + factory.createTypeReferenceNode(`... ${types.length - 2} more ...`, /*typeArguments*/ undefined), + typeToTypeNodeHelper(types[types.length - 1], context) + ]; + } } - else { - const returnType = getReturnTypeOfSignature(signature); - if (returnType && !(suppressAny && isTypeAny(returnType))) { - returnTypeNode = serializeReturnTypeForSignature(context, returnType, signature, options?.privateSymbolVisitor, options?.bundledImports); + const mayHaveNameCollisions = !(context.flags & NodeBuilderFlags.UseFullyQualifiedType); + /** Map from type reference identifier text to [type, index in `result` where the type node is] */ + const seenNames = mayHaveNameCollisions ? createUnderscoreEscapedMultiMap<[Type, number]>() : undefined; + const result: TypeNode[] = []; + let i = 0; + for (const type of types) { + i++; + if (checkTruncationLength(context) && (i + 2 < types.length - 1)) { + result.push(factory.createTypeReferenceNode(`... ${types.length - i} more ...`, /*typeArguments*/ undefined)); + const typeNode = typeToTypeNodeHelper(types[types.length - 1], context); + if (typeNode) { + result.push(typeNode); + } + break; } - else if (!suppressAny) { - returnTypeNode = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + context.approximateLength += 2; // Account for whitespace + separator + const typeNode = typeToTypeNodeHelper(type, context); + if (typeNode) { + result.push(typeNode); + if (seenNames && isIdentifierTypeReference(typeNode)) { + seenNames.add(typeNode.typeName.escapedText, [type, result.length - 1]); + } } } - let modifiers = options?.modifiers; - if ((kind === SyntaxKind.ConstructorType) && signature.flags & SignatureFlags.Abstract) { - const flags = modifiersToFlags(modifiers); - modifiers = factory.createModifiersFromModifierFlags(flags | ModifierFlags.Abstract); + + if (seenNames) { + // To avoid printing types like `[Foo, Foo]` or `Bar & Bar` where + // occurrences of the same name actually come from different + // namespaces, go through the single-identifier type reference nodes + // we just generated, and see if any names were generated more than + // once while referring to different types. If so, regenerate the + // type node for each entry by that name with the + // `UseFullyQualifiedType` flag enabled. + const saveContextFlags = context.flags; + context.flags |= NodeBuilderFlags.UseFullyQualifiedType; + seenNames.forEach(types => { + if (!arrayIsHomogeneous(types, ([a], [b]) => typesAreSameReference(a, b))) { + for (const [type, resultIndex] of types) { + result[resultIndex] = typeToTypeNodeHelper(type, context); + } + } + }); + context.flags = saveContextFlags; } - const node = - kind === SyntaxKind.CallSignature ? factory.createCallSignature(typeParameters, parameters, returnTypeNode) : - kind === SyntaxKind.ConstructSignature ? factory.createConstructSignature(typeParameters, parameters, returnTypeNode) : - kind === SyntaxKind.MethodSignature ? factory.createMethodSignature(modifiers, options?.name ?? factory.createIdentifier(""), options?.questionToken, typeParameters, parameters, returnTypeNode) : - kind === SyntaxKind.MethodDeclaration ? factory.createMethodDeclaration(modifiers, /*asteriskToken*/ undefined, options?.name ?? factory.createIdentifier(""), /*questionToken*/ undefined, typeParameters, parameters, returnTypeNode, /*body*/ undefined) : - kind === SyntaxKind.Constructor ? factory.createConstructorDeclaration(modifiers, parameters, /*body*/ undefined) : - kind === SyntaxKind.GetAccessor ? factory.createGetAccessorDeclaration(modifiers, options?.name ?? factory.createIdentifier(""), parameters, returnTypeNode, /*body*/ undefined) : - kind === SyntaxKind.SetAccessor ? factory.createSetAccessorDeclaration(modifiers, options?.name ?? factory.createIdentifier(""), parameters, /*body*/ undefined) : - kind === SyntaxKind.IndexSignature ? factory.createIndexSignature(modifiers, parameters, returnTypeNode) : - kind === SyntaxKind.JSDocFunctionType ? factory.createJSDocFunctionType(parameters, returnTypeNode) : - kind === SyntaxKind.FunctionType ? factory.createFunctionTypeNode(typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) : - kind === SyntaxKind.ConstructorType ? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) : - kind === SyntaxKind.FunctionDeclaration ? factory.createFunctionDeclaration(modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, /*body*/ undefined) : - kind === SyntaxKind.FunctionExpression ? factory.createFunctionExpression(modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, factory.createBlock([])) : - kind === SyntaxKind.ArrowFunction ? factory.createArrowFunction(modifiers, typeParameters, parameters, returnTypeNode, /*equalsGreaterThanToken*/ undefined, factory.createBlock([])) : - Debug.assertNever(kind); + return result; + } + } + + function typesAreSameReference(a: Type, b: Type): boolean { + return a === b + || !!a.symbol && a.symbol === b.symbol + || !!a.aliasSymbol && a.aliasSymbol === b.aliasSymbol; + } + + function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, context: NodeBuilderContext, typeNode: TypeNode | undefined): IndexSignatureDeclaration { + const name = getNameFromIndexInfo(indexInfo) || "x"; + const indexerTypeNode = typeToTypeNodeHelper(indexInfo.keyType, context); - if (typeArguments) { - node.typeArguments = factory.createNodeArray(typeArguments); - } + const indexingParameter = factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + name, + /*questionToken*/ undefined, + indexerTypeNode, + /*initializer*/ undefined); + if (!typeNode) { + typeNode = typeToTypeNodeHelper(indexInfo.type || anyType, context); + } + if (!indexInfo.type && !(context.flags & NodeBuilderFlags.AllowEmptyIndexInfoType)) { + context.encounteredError = true; + } + context.approximateLength += (name.length + 4); + return factory.createIndexSignature( + indexInfo.isReadonly ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined, + [indexingParameter], + typeNode); + } - return node; + interface SignatureToSignatureDeclarationOptions { + modifiers?: readonly Modifier[]; + name?: PropertyName; + questionToken?: QuestionToken; + privateSymbolVisitor?: (s: Symbol) => void; + bundledImports?: boolean; + } + + function signatureToSignatureDeclarationHelper(signature: Signature, kind: SignatureDeclaration["kind"], context: NodeBuilderContext, options?: SignatureToSignatureDeclarationOptions): SignatureDeclaration { + const suppressAny = context.flags & NodeBuilderFlags.SuppressAnyReturnType; + if (suppressAny) context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // suppress only toplevel `any`s + context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum + let typeParameters: TypeParameterDeclaration[] | undefined; + let typeArguments: TypeNode[] | undefined; + if (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature && signature.target && signature.mapper && signature.target.typeParameters) { + typeArguments = signature.target.typeParameters.map(parameter => typeToTypeNodeHelper(instantiateType(parameter, signature.mapper), context)); + } + else { + typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context)); + } + + const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0]; + // If the expanded parameter list had a variadic in a non-trailing position, don't expand it + const parameters = (some(expandedParams, p => p !== expandedParams[expandedParams.length - 1] && !!(getCheckFlags(p) & CheckFlags.RestParameter)) ? signature.parameters : expandedParams).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor, options?.privateSymbolVisitor, options?.bundledImports)); + const thisParameter = context.flags & NodeBuilderFlags.OmitThisParameter ? undefined : tryGetThisParameterDeclaration(signature, context); + if (thisParameter) { + parameters.unshift(thisParameter); } - function tryGetThisParameterDeclaration(signature: Signature, context: NodeBuilderContext) { - if (signature.thisParameter) { - return symbolToParameterDeclaration(signature.thisParameter, context); + let returnTypeNode: TypeNode | undefined; + const typePredicate = getTypePredicateOfSignature(signature); + if (typePredicate) { + const assertsModifier = typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? + factory.createToken(SyntaxKind.AssertsKeyword) : + undefined; + const parameterName = typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? + setEmitFlags(factory.createIdentifier(typePredicate.parameterName), EmitFlags.NoAsciiEscaping) : + factory.createThisTypeNode(); + const typeNode = typePredicate.type && typeToTypeNodeHelper(typePredicate.type, context); + returnTypeNode = factory.createTypePredicateNode(assertsModifier, parameterName, typeNode); + } + else { + const returnType = getReturnTypeOfSignature(signature); + if (returnType && !(suppressAny && isTypeAny(returnType))) { + returnTypeNode = serializeReturnTypeForSignature(context, returnType, signature, options?.privateSymbolVisitor, options?.bundledImports); } - if (signature.declaration) { - const thisTag = getJSDocThisTag(signature.declaration); - if (thisTag && thisTag.typeExpression) { - return factory.createParameterDeclaration( - /* modifiers */ undefined, - /* dotDotDotToken */ undefined, - "this", - /* questionToken */ undefined, - typeToTypeNodeHelper(getTypeFromTypeNode(thisTag.typeExpression), context) - ); - } + else if (!suppressAny) { + returnTypeNode = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } } - - function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode | undefined): TypeParameterDeclaration { - const savedContextFlags = context.flags; - context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic - const modifiers = factory.createModifiersFromModifierFlags(getVarianceModifiers(type)); - const name = typeParameterToName(type, context); - const defaultParameter = getDefaultFromTypeParameter(type); - const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context); - context.flags = savedContextFlags; - return factory.createTypeParameterDeclaration(modifiers, name, constraintNode, defaultParameterNode); + let modifiers = options?.modifiers; + if ((kind === SyntaxKind.ConstructorType) && signature.flags & SignatureFlags.Abstract) { + const flags = modifiersToFlags(modifiers); + modifiers = factory.createModifiersFromModifierFlags(flags | ModifierFlags.Abstract); } - function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { - const constraintNode = constraint && typeToTypeNodeHelper(constraint, context); - return typeParameterToDeclarationWithConstraint(type, context, constraintNode); + const node = + kind === SyntaxKind.CallSignature ? factory.createCallSignature(typeParameters, parameters, returnTypeNode) : + kind === SyntaxKind.ConstructSignature ? factory.createConstructSignature(typeParameters, parameters, returnTypeNode) : + kind === SyntaxKind.MethodSignature ? factory.createMethodSignature(modifiers, options?.name ?? factory.createIdentifier(""), options?.questionToken, typeParameters, parameters, returnTypeNode) : + kind === SyntaxKind.MethodDeclaration ? factory.createMethodDeclaration(modifiers, /*asteriskToken*/ undefined, options?.name ?? factory.createIdentifier(""), /*questionToken*/ undefined, typeParameters, parameters, returnTypeNode, /*body*/ undefined) : + kind === SyntaxKind.Constructor ? factory.createConstructorDeclaration(modifiers, parameters, /*body*/ undefined) : + kind === SyntaxKind.GetAccessor ? factory.createGetAccessorDeclaration(modifiers, options?.name ?? factory.createIdentifier(""), parameters, returnTypeNode, /*body*/ undefined) : + kind === SyntaxKind.SetAccessor ? factory.createSetAccessorDeclaration(modifiers, options?.name ?? factory.createIdentifier(""), parameters, /*body*/ undefined) : + kind === SyntaxKind.IndexSignature ? factory.createIndexSignature(modifiers, parameters, returnTypeNode) : + kind === SyntaxKind.JSDocFunctionType ? factory.createJSDocFunctionType(parameters, returnTypeNode) : + kind === SyntaxKind.FunctionType ? factory.createFunctionTypeNode(typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) : + kind === SyntaxKind.ConstructorType ? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) : + kind === SyntaxKind.FunctionDeclaration ? factory.createFunctionDeclaration(modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, /*body*/ undefined) : + kind === SyntaxKind.FunctionExpression ? factory.createFunctionExpression(modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, factory.createBlock([])) : + kind === SyntaxKind.ArrowFunction ? factory.createArrowFunction(modifiers, typeParameters, parameters, returnTypeNode, /*equalsGreaterThanToken*/ undefined, factory.createBlock([])) : + Debug.assertNever(kind); + + if (typeArguments) { + node.typeArguments = factory.createNodeArray(typeArguments); } - function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean, privateSymbolVisitor?: (s: Symbol) => void, bundledImports?: boolean): ParameterDeclaration { - let parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); - if (!parameterDeclaration && !isTransientSymbol(parameterSymbol)) { - parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); - } + return node; + } - let parameterType = getTypeOfSymbol(parameterSymbol); - if (parameterDeclaration && isRequiredInitializedParameter(parameterDeclaration)) { - parameterType = getOptionalType(parameterType); + function tryGetThisParameterDeclaration(signature: Signature, context: NodeBuilderContext) { + if (signature.thisParameter) { + return symbolToParameterDeclaration(signature.thisParameter, context); + } + if (signature.declaration) { + const thisTag = getJSDocThisTag(signature.declaration); + if (thisTag && thisTag.typeExpression) { + return factory.createParameterDeclaration( + /* modifiers */ undefined, + /* dotDotDotToken */ undefined, + "this", + /* questionToken */ undefined, + typeToTypeNodeHelper(getTypeFromTypeNode(thisTag.typeExpression), context) + ); } - const parameterTypeNode = serializeTypeForDeclaration(context, parameterType, parameterSymbol, context.enclosingDeclaration, privateSymbolVisitor, bundledImports); + } + } - const modifiers = !(context.flags & NodeBuilderFlags.OmitParameterModifiers) && preserveModifierFlags && parameterDeclaration && canHaveModifiers(parameterDeclaration) ? map(getModifiers(parameterDeclaration), factory.cloneNode) : undefined; - const isRest = parameterDeclaration && isRestParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.RestParameter; - const dotDotDotToken = isRest ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined; - const name = parameterDeclaration ? parameterDeclaration.name ? - parameterDeclaration.name.kind === SyntaxKind.Identifier ? setEmitFlags(factory.cloneNode(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) : - parameterDeclaration.name.kind === SyntaxKind.QualifiedName ? setEmitFlags(factory.cloneNode(parameterDeclaration.name.right), EmitFlags.NoAsciiEscaping) : - cloneBindingName(parameterDeclaration.name) : - symbolName(parameterSymbol) : - symbolName(parameterSymbol); - const isOptional = parameterDeclaration && isOptionalParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.OptionalParameter; - const questionToken = isOptional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; - const parameterNode = factory.createParameterDeclaration( - modifiers, - dotDotDotToken, - name, - questionToken, - parameterTypeNode, - /*initializer*/ undefined); - context.approximateLength += symbolName(parameterSymbol).length + 3; - return parameterNode; + function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode | undefined): TypeParameterDeclaration { + const savedContextFlags = context.flags; + context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic + const modifiers = factory.createModifiersFromModifierFlags(getVarianceModifiers(type)); + const name = typeParameterToName(type, context); + const defaultParameter = getDefaultFromTypeParameter(type); + const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context); + context.flags = savedContextFlags; + return factory.createTypeParameterDeclaration(modifiers, name, constraintNode, defaultParameterNode); + } - function cloneBindingName(node: BindingName): BindingName { - return elideInitializerAndSetEmitFlags(node) as BindingName; - function elideInitializerAndSetEmitFlags(node: Node): Node { - if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { - trackComputedName(node.expression, context.enclosingDeclaration, context); - } - let visited = visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags)!; - if (isBindingElement(visited)) { - visited = factory.updateBindingElement( - visited, - visited.dotDotDotToken, - visited.propertyName, - visited.name, - /*initializer*/ undefined); - } - if (!nodeIsSynthesized(visited)) { - visited = factory.cloneNode(visited); - } - return setEmitFlags(visited, EmitFlags.SingleLine | EmitFlags.NoAsciiEscaping); - } - } + function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { + const constraintNode = constraint && typeToTypeNodeHelper(constraint, context); + return typeParameterToDeclarationWithConstraint(type, context, constraintNode); + } + + function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean, privateSymbolVisitor?: (s: Symbol) => void, bundledImports?: boolean): ParameterDeclaration { + let parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); + if (!parameterDeclaration && !isTransientSymbol(parameterSymbol)) { + parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); + } + + let parameterType = getTypeOfSymbol(parameterSymbol); + if (parameterDeclaration && isRequiredInitializedParameter(parameterDeclaration)) { + parameterType = getOptionalType(parameterType); } + const parameterTypeNode = serializeTypeForDeclaration(context, parameterType, parameterSymbol, context.enclosingDeclaration, privateSymbolVisitor, bundledImports); + + const modifiers = !(context.flags & NodeBuilderFlags.OmitParameterModifiers) && preserveModifierFlags && parameterDeclaration && canHaveModifiers(parameterDeclaration) ? map(getModifiers(parameterDeclaration), factory.cloneNode) : undefined; + const isRest = parameterDeclaration && isRestParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.RestParameter; + const dotDotDotToken = isRest ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined; + const name = parameterDeclaration ? parameterDeclaration.name ? + parameterDeclaration.name.kind === SyntaxKind.Identifier ? setEmitFlags(factory.cloneNode(parameterDeclaration.name), EmitFlags.NoAsciiEscaping) : + parameterDeclaration.name.kind === SyntaxKind.QualifiedName ? setEmitFlags(factory.cloneNode(parameterDeclaration.name.right), EmitFlags.NoAsciiEscaping) : + cloneBindingName(parameterDeclaration.name) : + symbolName(parameterSymbol) : + symbolName(parameterSymbol); + const isOptional = parameterDeclaration && isOptionalParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.OptionalParameter; + const questionToken = isOptional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; + const parameterNode = factory.createParameterDeclaration( + modifiers, + dotDotDotToken, + name, + questionToken, + parameterTypeNode, + /*initializer*/ undefined); + context.approximateLength += symbolName(parameterSymbol).length + 3; + return parameterNode; - function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { - if (!context.tracker.trackSymbol) return; - // get symbol of the first identifier of the entityName - const firstIdentifier = getFirstIdentifier(accessExpression); - const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); - if (name) { - context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); + function cloneBindingName(node: BindingName): BindingName { + return elideInitializerAndSetEmitFlags(node) as BindingName; + function elideInitializerAndSetEmitFlags(node: Node): Node { + if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { + trackComputedName(node.expression, context.enclosingDeclaration, context); + } + let visited = visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*nodesVisitor*/ undefined, elideInitializerAndSetEmitFlags)!; + if (isBindingElement(visited)) { + visited = factory.updateBindingElement( + visited, + visited.dotDotDotToken, + visited.propertyName, + visited.name, + /*initializer*/ undefined); + } + if (!nodeIsSynthesized(visited)) { + visited = factory.cloneNode(visited); + } + return setEmitFlags(visited, EmitFlags.SingleLine | EmitFlags.NoAsciiEscaping); } } + } - function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { - context.tracker.trackSymbol!(symbol, context.enclosingDeclaration, meaning); // TODO: GH#18217 - return lookupSymbolChainWorker(symbol, context, meaning, yieldModuleSymbol); + function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { + if (!context.tracker.trackSymbol) return; + // get symbol of the first identifier of the entityName + const firstIdentifier = getFirstIdentifier(accessExpression); + const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + if (name) { + context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); } + } - function lookupSymbolChainWorker(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { - // Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration. - let chain: Symbol[]; - const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; - if (!isTypeParameter && (context.enclosingDeclaration || context.flags & NodeBuilderFlags.UseFullyQualifiedType) && !(context.flags & NodeBuilderFlags.DoNotIncludeSymbolChain)) { - chain = Debug.checkDefined(getSymbolChain(symbol, meaning, /*endOfChain*/ true)); - Debug.assert(chain && chain.length > 0); - } - else { - chain = [symbol]; - } - return chain; - - /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ - function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { - let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, !!(context.flags & NodeBuilderFlags.UseOnlyExternalAliasing)); - let parentSpecifiers: (string | undefined)[]; - if (!accessibleSymbolChain || - needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { - - // Go up and add our parent. - const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration, meaning); - if (length(parents)) { - parentSpecifiers = parents!.map(symbol => - some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol) - ? getSpecifierForModuleSymbol(symbol, context) - : undefined); - const indices = parents!.map((_, i) => i); - indices.sort(sortByBestName); - const sortedParents = indices.map(i => parents![i]); - for (const parent of sortedParents) { - const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); - if (parentChain) { - if (parent.exports && parent.exports.get(InternalSymbolName.ExportEquals) && - getSymbolIfSameReference(parent.exports.get(InternalSymbolName.ExportEquals)!, symbol)) { - // parentChain root _is_ symbol - symbol is a module export=, so it kinda looks like it's own parent - // No need to lookup an alias for the symbol in itself - accessibleSymbolChain = parentChain; - break; - } - accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [getAliasForSymbolInContainer(parent, symbol) || symbol]); + function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { + context.tracker.trackSymbol!(symbol, context.enclosingDeclaration, meaning); // TODO: GH#18217 + return lookupSymbolChainWorker(symbol, context, meaning, yieldModuleSymbol); + } + + function lookupSymbolChainWorker(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { + // Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration. + let chain: Symbol[]; + const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; + if (!isTypeParameter && (context.enclosingDeclaration || context.flags & NodeBuilderFlags.UseFullyQualifiedType) && !(context.flags & NodeBuilderFlags.DoNotIncludeSymbolChain)) { + chain = Debug.checkDefined(getSymbolChain(symbol, meaning, /*endOfChain*/ true)); + Debug.assert(chain && chain.length > 0); + } + else { + chain = [symbol]; + } + return chain; + + /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ + function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { + let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, !!(context.flags & NodeBuilderFlags.UseOnlyExternalAliasing)); + let parentSpecifiers: (string | undefined)[]; + if (!accessibleSymbolChain || + needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { + + // Go up and add our parent. + const parents = getContainersOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol, context.enclosingDeclaration, meaning); + if (length(parents)) { + parentSpecifiers = parents!.map(symbol => + some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol) + ? getSpecifierForModuleSymbol(symbol, context) + : undefined); + const indices = parents!.map((_, i) => i); + indices.sort(sortByBestName); + const sortedParents = indices.map(i => parents![i]); + for (const parent of sortedParents) { + const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); + if (parentChain) { + if (parent.exports && parent.exports.get(InternalSymbolName.ExportEquals) && + getSymbolIfSameReference(parent.exports.get(InternalSymbolName.ExportEquals)!, symbol)) { + // parentChain root _is_ symbol - symbol is a module export=, so it kinda looks like it's own parent + // No need to lookup an alias for the symbol in itself + accessibleSymbolChain = parentChain; break; } + accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [getAliasForSymbolInContainer(parent, symbol) || symbol]); + break; } } } + } - if (accessibleSymbolChain) { - return accessibleSymbolChain; - } - if ( - // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. - endOfChain || - // If a parent symbol is an anonymous type, don't write it. - !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { - // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) - if (!endOfChain && !yieldModuleSymbol && !!forEach(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - return; - } - return [symbol]; + if (accessibleSymbolChain) { + return accessibleSymbolChain; + } + if ( + // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. + endOfChain || + // If a parent symbol is an anonymous type, don't write it. + !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { + // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) + if (!endOfChain && !yieldModuleSymbol && !!forEach(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + return; } + return [symbol]; + } - function sortByBestName(a: number, b: number) { - const specifierA = parentSpecifiers[a]; - const specifierB = parentSpecifiers[b]; - if (specifierA && specifierB) { - const isBRelative = pathIsRelative(specifierB); - if (pathIsRelative(specifierA) === isBRelative) { - // Both relative or both non-relative, sort by number of parts - return moduleSpecifiers.countPathComponents(specifierA) - moduleSpecifiers.countPathComponents(specifierB); - } - if (isBRelative) { - // A is non-relative, B is relative: prefer A - return -1; - } - // A is relative, B is non-relative: prefer B - return 1; + function sortByBestName(a: number, b: number) { + const specifierA = parentSpecifiers[a]; + const specifierB = parentSpecifiers[b]; + if (specifierA && specifierB) { + const isBRelative = pathIsRelative(specifierB); + if (pathIsRelative(specifierA) === isBRelative) { + // Both relative or both non-relative, sort by number of parts + return moduleSpecifiers.countPathComponents(specifierA) - moduleSpecifiers.countPathComponents(specifierB); } - return 0; + if (isBRelative) { + // A is non-relative, B is relative: prefer A + return -1; + } + // A is relative, B is non-relative: prefer B + return 1; } + return 0; } } + } - function typeParametersToTypeParameterDeclarations(symbol: Symbol, context: NodeBuilderContext) { - let typeParameterNodes: NodeArray | undefined; - const targetSymbol = getTargetSymbol(symbol); - if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { - typeParameterNodes = factory.createNodeArray(map(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol), tp => typeParameterToDeclaration(tp, context))); - } - return typeParameterNodes; + function typeParametersToTypeParameterDeclarations(symbol: Symbol, context: NodeBuilderContext) { + let typeParameterNodes: NodeArray | undefined; + const targetSymbol = getTargetSymbol(symbol); + if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { + typeParameterNodes = factory.createNodeArray(map(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol), tp => typeParameterToDeclaration(tp, context))); } + return typeParameterNodes; + } - 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?.has(symbolId)) { - return undefined; + 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?.has(symbolId)) { + return undefined; + } + (context.typeParameterSymbolList || (context.typeParameterSymbolList = new Set())).add(symbolId); + let typeParameterNodes: readonly TypeNode[] | readonly TypeParameterDeclaration[] | undefined; + if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index < (chain.length - 1)) { + const parentSymbol = symbol; + const nextSymbol = chain[index + 1]; + if (getCheckFlags(nextSymbol) & CheckFlags.Instantiated) { + const params = getTypeParametersOfClassOrInterface( + parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol + ); + typeParameterNodes = mapToTypeNodes(map(params, t => getMappedType(t, (nextSymbol as TransientSymbol).mapper!)), context); } - (context.typeParameterSymbolList || (context.typeParameterSymbolList = new Set())).add(symbolId); - let typeParameterNodes: readonly TypeNode[] | readonly TypeParameterDeclaration[] | undefined; - if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index < (chain.length - 1)) { - const parentSymbol = symbol; - const nextSymbol = chain[index + 1]; - if (getCheckFlags(nextSymbol) & CheckFlags.Instantiated) { - const params = getTypeParametersOfClassOrInterface( - parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol - ); - typeParameterNodes = mapToTypeNodes(map(params, t => getMappedType(t, (nextSymbol as TransientSymbol).mapper!)), context); - } - else { - typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); - } + else { + typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); } - return typeParameterNodes; } + return typeParameterNodes; + } - /** - * Given A[B][C][D], finds A[B] - */ - function getTopmostIndexedAccessType(top: IndexedAccessTypeNode): IndexedAccessTypeNode { - if (isIndexedAccessTypeNode(top.objectType)) { - return getTopmostIndexedAccessType(top.objectType); - } - return top; + /** + * Given A[B][C][D], finds A[B] + */ + function getTopmostIndexedAccessType(top: IndexedAccessTypeNode): IndexedAccessTypeNode { + if (isIndexedAccessTypeNode(top.objectType)) { + return getTopmostIndexedAccessType(top.objectType); } + return top; + } - function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext, overrideImportMode?: SourceFile["impliedNodeFormat"]) { - let file = getDeclarationOfKind(symbol, SyntaxKind.SourceFile); - if (!file) { - const equivalentFileSymbol = firstDefined(symbol.declarations, d => getFileSymbolIfFileSymbolExportEqualsContainer(d, symbol)); - if (equivalentFileSymbol) { - file = getDeclarationOfKind(equivalentFileSymbol, SyntaxKind.SourceFile); - } + function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext, overrideImportMode?: SourceFile["impliedNodeFormat"]) { + let file = getDeclarationOfKind(symbol, SyntaxKind.SourceFile); + if (!file) { + const equivalentFileSymbol = firstDefined(symbol.declarations, d => getFileSymbolIfFileSymbolExportEqualsContainer(d, symbol)); + if (equivalentFileSymbol) { + file = getDeclarationOfKind(equivalentFileSymbol, SyntaxKind.SourceFile); } - if (file && file.moduleName !== undefined) { - // Use the amd name if it is available - return file.moduleName; - } - if (!file) { - if (context.tracker.trackReferencedAmbientModule) { - const ambientDecls = filter(symbol.declarations, isAmbientModule); - if (length(ambientDecls)) { - for (const decl of ambientDecls!) { - context.tracker.trackReferencedAmbientModule(decl, symbol); - } + } + if (file && file.moduleName !== undefined) { + // Use the amd name if it is available + return file.moduleName; + } + if (!file) { + if (context.tracker.trackReferencedAmbientModule) { + const ambientDecls = filter(symbol.declarations, isAmbientModule); + if (length(ambientDecls)) { + for (const decl of ambientDecls!) { + context.tracker.trackReferencedAmbientModule(decl, symbol); } } - if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { - return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); - } - } - if (!context.enclosingDeclaration || !context.tracker.moduleResolverHost) { - // If there's no context declaration, we can't lookup a non-ambient specifier, so we just use the symbol name - if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { - return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); - } - return getSourceFileOfNode(getNonAugmentationDeclaration(symbol)!).fileName; // A resolver may not be provided for baselines and errors - in those cases we use the fileName in full } - const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration)); - const resolutionMode = overrideImportMode || contextFile?.impliedNodeFormat; - const cacheKey = getSpecifierCacheKey(contextFile.path, resolutionMode); - const links = getSymbolLinks(symbol); - let specifier = links.specifierCache && links.specifierCache.get(cacheKey); - if (!specifier) { - const isBundle = !!outFile(compilerOptions); - // For declaration bundles, we need to generate absolute paths relative to the common source dir for imports, - // just like how the declaration emitter does for the ambient module declarations - we can easily accomplish this - // using the `baseUrl` compiler option (which we would otherwise never use in declaration emit) and a non-relative - // specifier preference - const { moduleResolverHost } = context.tracker; - const specifierCompilerOptions = isBundle ? { ...compilerOptions, baseUrl: moduleResolverHost.getCommonSourceDirectory() } : compilerOptions; - specifier = first(moduleSpecifiers.getModuleSpecifiers( - symbol, - checker, - specifierCompilerOptions, - contextFile, - moduleResolverHost, - { - importModuleSpecifierPreference: isBundle ? "non-relative" : "project-relative", - importModuleSpecifierEnding: isBundle ? "minimal" - : resolutionMode === ModuleKind.ESNext ? "js" - : undefined, - }, - { overrideImportMode } - )); - links.specifierCache ??= new Map(); - links.specifierCache.set(cacheKey, specifier); + if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { + return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); } - return specifier; - - function getSpecifierCacheKey(path: string, mode: SourceFile["impliedNodeFormat"] | undefined) { - return mode === undefined ? path : `${mode}|${path}`; + } + if (!context.enclosingDeclaration || !context.tracker.moduleResolverHost) { + // If there's no context declaration, we can't lookup a non-ambient specifier, so we just use the symbol name + if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { + return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); } + return getSourceFileOfNode(getNonAugmentationDeclaration(symbol)!).fileName; // A resolver may not be provided for baselines and errors - in those cases we use the fileName in full + } + const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration)); + const resolutionMode = overrideImportMode || contextFile?.impliedNodeFormat; + const cacheKey = getSpecifierCacheKey(contextFile.path, resolutionMode); + const links = getSymbolLinks(symbol); + let specifier = links.specifierCache && links.specifierCache.get(cacheKey); + if (!specifier) { + const isBundle = !!outFile(compilerOptions); + // For declaration bundles, we need to generate absolute paths relative to the common source dir for imports, + // just like how the declaration emitter does for the ambient module declarations - we can easily accomplish this + // using the `baseUrl` compiler option (which we would otherwise never use in declaration emit) and a non-relative + // specifier preference + const { moduleResolverHost } = context.tracker; + const specifierCompilerOptions = isBundle ? { ...compilerOptions, baseUrl: moduleResolverHost.getCommonSourceDirectory() } : compilerOptions; + specifier = first(moduleSpecifiers.getModuleSpecifiers( + symbol, + checker, + specifierCompilerOptions, + contextFile, + moduleResolverHost, + { + importModuleSpecifierPreference: isBundle ? "non-relative" : "project-relative", + importModuleSpecifierEnding: isBundle ? "minimal" + : resolutionMode === ModuleKind.ESNext ? "js" + : undefined, + }, + { overrideImportMode } + )); + links.specifierCache ??= new Map(); + links.specifierCache.set(cacheKey, specifier); } + return specifier; - function symbolToEntityNameNode(symbol: Symbol): EntityName { - const identifier = factory.createIdentifier(unescapeLeadingUnderscores(symbol.escapedName)); - return symbol.parent ? factory.createQualifiedName(symbolToEntityNameNode(symbol.parent), identifier) : identifier; + function getSpecifierCacheKey(path: string, mode: SourceFile["impliedNodeFormat"] | undefined) { + return mode === undefined ? path : `${mode}|${path}`; } + } + + function symbolToEntityNameNode(symbol: Symbol): EntityName { + const identifier = factory.createIdentifier(unescapeLeadingUnderscores(symbol.escapedName)); + return symbol.parent ? factory.createQualifiedName(symbolToEntityNameNode(symbol.parent), identifier) : identifier; + } - function symbolToTypeNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, overrideTypeArguments?: readonly TypeNode[]): TypeNode { - const chain = lookupSymbolChain(symbol, context, meaning, !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope)); // If we're using aliases outside the current scope, dont bother with the module + function symbolToTypeNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, overrideTypeArguments?: readonly TypeNode[]): TypeNode { + const chain = lookupSymbolChain(symbol, context, meaning, !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope)); // If we're using aliases outside the current scope, dont bother with the module - const isTypeOf = meaning === SymbolFlags.Value; - if (some(chain[0].declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - // module is root, must use `ImportTypeNode` - const nonRootParts = chain.length > 1 ? createAccessFromSymbolChain(chain, chain.length - 1, 1) : undefined; - const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context); - const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration)); - const targetFile = getSourceFileOfModule(chain[0]); - let specifier: string | undefined; - let assertion: ImportTypeAssertionContainer | undefined; + const isTypeOf = meaning === SymbolFlags.Value; + if (some(chain[0].declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + // module is root, must use `ImportTypeNode` + const nonRootParts = chain.length > 1 ? createAccessFromSymbolChain(chain, chain.length - 1, 1) : undefined; + const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context); + const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration)); + const targetFile = getSourceFileOfModule(chain[0]); + let specifier: string | undefined; + let assertion: ImportTypeAssertionContainer | undefined; + if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) { + // An `import` type directed at an esm format file is only going to resolve in esm mode - set the esm mode assertion + if (targetFile?.impliedNodeFormat === ModuleKind.ESNext && targetFile.impliedNodeFormat !== contextFile?.impliedNodeFormat) { + specifier = getSpecifierForModuleSymbol(chain[0], context, ModuleKind.ESNext); + assertion = factory.createImportTypeAssertionContainer(factory.createAssertClause(factory.createNodeArray([ + factory.createAssertEntry( + factory.createStringLiteral("resolution-mode"), + factory.createStringLiteral("import") + ) + ]))); + context.tracker.reportImportTypeNodeResolutionModeOverride?.(); + } + } + if (!specifier) { + specifier = getSpecifierForModuleSymbol(chain[0], context); + } + if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && specifier.indexOf("/node_modules/") >= 0) { + const oldSpecifier = specifier; if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) { - // An `import` type directed at an esm format file is only going to resolve in esm mode - set the esm mode assertion - if (targetFile?.impliedNodeFormat === ModuleKind.ESNext && targetFile.impliedNodeFormat !== contextFile?.impliedNodeFormat) { - specifier = getSpecifierForModuleSymbol(chain[0], context, ModuleKind.ESNext); + // We might be able to write a portable import type using a mode override; try specifier generation again, but with a different mode set + const swappedMode = contextFile?.impliedNodeFormat === ModuleKind.ESNext ? ModuleKind.CommonJS : ModuleKind.ESNext; + specifier = getSpecifierForModuleSymbol(chain[0], context, swappedMode); + + if (specifier.indexOf("/node_modules/") >= 0) { + // Still unreachable :( + specifier = oldSpecifier; + } + else { assertion = factory.createImportTypeAssertionContainer(factory.createAssertClause(factory.createNodeArray([ factory.createAssertEntry( factory.createStringLiteral("resolution-mode"), - factory.createStringLiteral("import") + factory.createStringLiteral(swappedMode === ModuleKind.ESNext ? "import" : "require") ) ]))); context.tracker.reportImportTypeNodeResolutionModeOverride?.(); } } - if (!specifier) { - specifier = getSpecifierForModuleSymbol(chain[0], context); - } - if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && specifier.indexOf("/node_modules/") >= 0) { - const oldSpecifier = specifier; - if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) { - // We might be able to write a portable import type using a mode override; try specifier generation again, but with a different mode set - const swappedMode = contextFile?.impliedNodeFormat === ModuleKind.ESNext ? ModuleKind.CommonJS : ModuleKind.ESNext; - specifier = getSpecifierForModuleSymbol(chain[0], context, swappedMode); - - if (specifier.indexOf("/node_modules/") >= 0) { - // Still unreachable :( - specifier = oldSpecifier; - } - else { - assertion = factory.createImportTypeAssertionContainer(factory.createAssertClause(factory.createNodeArray([ - factory.createAssertEntry( - factory.createStringLiteral("resolution-mode"), - factory.createStringLiteral(swappedMode === ModuleKind.ESNext ? "import" : "require") - ) - ]))); - context.tracker.reportImportTypeNodeResolutionModeOverride?.(); - } - } - if (!assertion) { - // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error - // since declaration files with these kinds of references are liable to fail when published :( - context.encounteredError = true; - if (context.tracker.reportLikelyUnsafeImportRequiredError) { - context.tracker.reportLikelyUnsafeImportRequiredError(oldSpecifier); - } - } - } - const lit = factory.createLiteralTypeNode(factory.createStringLiteral(specifier)); - if (context.tracker.trackExternalModuleSymbolOfImportTypeNode) context.tracker.trackExternalModuleSymbolOfImportTypeNode(chain[0]); - context.approximateLength += specifier.length + 10; // specifier + import("") - if (!nonRootParts || isEntityName(nonRootParts)) { - if (nonRootParts) { - const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; - lastId.typeArguments = undefined; + if (!assertion) { + // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error + // since declaration files with these kinds of references are liable to fail when published :( + context.encounteredError = true; + if (context.tracker.reportLikelyUnsafeImportRequiredError) { + context.tracker.reportLikelyUnsafeImportRequiredError(oldSpecifier); } - return factory.createImportTypeNode(lit, assertion, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf); - } - else { - const splitNode = getTopmostIndexedAccessType(nonRootParts); - const qualifier = (splitNode.objectType as TypeReferenceNode).typeName; - return factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, assertion, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType); } } - - const entityName = createAccessFromSymbolChain(chain, chain.length - 1, 0); - if (isIndexedAccessTypeNode(entityName)) { - return entityName; // Indexed accesses can never be `typeof` - } - if (isTypeOf) { - return factory.createTypeQueryNode(entityName); + const lit = factory.createLiteralTypeNode(factory.createStringLiteral(specifier)); + if (context.tracker.trackExternalModuleSymbolOfImportTypeNode) context.tracker.trackExternalModuleSymbolOfImportTypeNode(chain[0]); + context.approximateLength += specifier.length + 10; // specifier + import("") + if (!nonRootParts || isEntityName(nonRootParts)) { + if (nonRootParts) { + const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; + lastId.typeArguments = undefined; + } + return factory.createImportTypeNode(lit, assertion, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf); } else { - const lastId = isIdentifier(entityName) ? entityName : entityName.right; - const lastTypeArgs = lastId.typeArguments; - lastId.typeArguments = undefined; - return factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray); + const splitNode = getTopmostIndexedAccessType(nonRootParts); + const qualifier = (splitNode.objectType as TypeReferenceNode).typeName; + return factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, assertion, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType); } + } - function createAccessFromSymbolChain(chain: Symbol[], index: number, stopper: number): EntityName | IndexedAccessTypeNode { - const typeParameterNodes = index === (chain.length - 1) ? overrideTypeArguments : lookupTypeParameterNodes(chain, index, context); - const symbol = chain[index]; - const parent = chain[index - 1]; + const entityName = createAccessFromSymbolChain(chain, chain.length - 1, 0); + if (isIndexedAccessTypeNode(entityName)) { + return entityName; // Indexed accesses can never be `typeof` + } + if (isTypeOf) { + return factory.createTypeQueryNode(entityName); + } + else { + const lastId = isIdentifier(entityName) ? entityName : entityName.right; + const lastTypeArgs = lastId.typeArguments; + lastId.typeArguments = undefined; + return factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray); + } - let symbolName: string | undefined; - if (index === 0) { - context.flags |= NodeBuilderFlags.InInitialEntityName; - symbolName = getNameOfSymbolAsWritten(symbol, context); - context.approximateLength += (symbolName ? symbolName.length : 0) + 1; - context.flags ^= NodeBuilderFlags.InInitialEntityName; - } - else { - if (parent && getExportsOfSymbol(parent)) { - const exports = getExportsOfSymbol(parent); - forEachEntry(exports, (ex, name) => { - if (getSymbolIfSameReference(ex, symbol) && !isLateBoundName(name) && name !== InternalSymbolName.ExportEquals) { - symbolName = unescapeLeadingUnderscores(name); - return true; - } - }); - } - } + function createAccessFromSymbolChain(chain: Symbol[], index: number, stopper: number): EntityName | IndexedAccessTypeNode { + const typeParameterNodes = index === (chain.length - 1) ? overrideTypeArguments : lookupTypeParameterNodes(chain, index, context); + const symbol = chain[index]; + const parent = chain[index - 1]; - if (symbolName === undefined) { - const name = firstDefined(symbol.declarations, getNameOfDeclaration); - if (name && isComputedPropertyName(name) && isEntityName(name.expression)) { - const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); - if (isEntityName(LHS)) { - return factory.createIndexedAccessTypeNode(factory.createParenthesizedType(factory.createTypeQueryNode(LHS)), factory.createTypeQueryNode(name.expression)); + let symbolName: string | undefined; + if (index === 0) { + context.flags |= NodeBuilderFlags.InInitialEntityName; + symbolName = getNameOfSymbolAsWritten(symbol, context); + context.approximateLength += (symbolName ? symbolName.length : 0) + 1; + context.flags ^= NodeBuilderFlags.InInitialEntityName; + } + else { + if (parent && getExportsOfSymbol(parent)) { + const exports = getExportsOfSymbol(parent); + forEachEntry(exports, (ex, name) => { + if (getSymbolIfSameReference(ex, symbol) && !isLateBoundName(name) && name !== InternalSymbolName.ExportEquals) { + symbolName = unescapeLeadingUnderscores(name); + return true; } - return LHS; - } - symbolName = getNameOfSymbolAsWritten(symbol, context); + }); } - context.approximateLength += symbolName.length + 1; + } - if (!(context.flags & NodeBuilderFlags.ForbidIndexedAccessSymbolReferences) && parent && - getMembersOfSymbol(parent) && getMembersOfSymbol(parent).get(symbol.escapedName) && - getSymbolIfSameReference(getMembersOfSymbol(parent).get(symbol.escapedName)!, symbol)) { - // Should use an indexed access + if (symbolName === undefined) { + const name = firstDefined(symbol.declarations, getNameOfDeclaration); + if (name && isComputedPropertyName(name) && isEntityName(name.expression)) { const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); - if (isIndexedAccessTypeNode(LHS)) { - return factory.createIndexedAccessTypeNode(LHS, factory.createLiteralTypeNode(factory.createStringLiteral(symbolName))); - } - else { - return factory.createIndexedAccessTypeNode(factory.createTypeReferenceNode(LHS, typeParameterNodes as readonly TypeNode[]), factory.createLiteralTypeNode(factory.createStringLiteral(symbolName))); + if (isEntityName(LHS)) { + return factory.createIndexedAccessTypeNode(factory.createParenthesizedType(factory.createTypeQueryNode(LHS)), factory.createTypeQueryNode(name.expression)); } + return LHS; } + symbolName = getNameOfSymbolAsWritten(symbol, context); + } + context.approximateLength += symbolName.length + 1; - const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); - identifier.symbol = symbol; + if (!(context.flags & NodeBuilderFlags.ForbidIndexedAccessSymbolReferences) && parent && + getMembersOfSymbol(parent) && getMembersOfSymbol(parent).get(symbol.escapedName) && + getSymbolIfSameReference(getMembersOfSymbol(parent).get(symbol.escapedName)!, symbol)) { + // Should use an indexed access + const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); + if (isIndexedAccessTypeNode(LHS)) { + return factory.createIndexedAccessTypeNode(LHS, factory.createLiteralTypeNode(factory.createStringLiteral(symbolName))); + } + else { + return factory.createIndexedAccessTypeNode(factory.createTypeReferenceNode(LHS, typeParameterNodes as readonly TypeNode[]), factory.createLiteralTypeNode(factory.createStringLiteral(symbolName))); + } + } - if (index > stopper) { - const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); - if (!isEntityName(LHS)) { - return Debug.fail("Impossible construct - an export of an indexed access cannot be reachable"); - } - return factory.createQualifiedName(LHS, identifier); + const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + identifier.symbol = symbol; + + if (index > stopper) { + const LHS = createAccessFromSymbolChain(chain, index - 1, stopper); + if (!isEntityName(LHS)) { + return Debug.fail("Impossible construct - an export of an indexed access cannot be reachable"); } - return identifier; + return factory.createQualifiedName(LHS, identifier); } + return identifier; } + } - function typeParameterShadowsNameInScope(escapedName: __String, context: NodeBuilderContext, type: TypeParameter) { - const result = resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundArg*/ undefined, escapedName, /*isUse*/ false); - if (result) { - if (result.flags & SymbolFlags.TypeParameter && result === type.symbol) { - return false; - } - return true; + function typeParameterShadowsNameInScope(escapedName: __String, context: NodeBuilderContext, type: TypeParameter) { + const result = resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundArg*/ undefined, escapedName, /*isUse*/ false); + if (result) { + if (result.flags & SymbolFlags.TypeParameter && result === type.symbol) { + return false; } - return false; + return true; } + return false; + } - function typeParameterToName(type: TypeParameter, context: NodeBuilderContext) { - if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) { - const cached = context.typeParameterNames.get(getTypeId(type)); - if (cached) { - return cached; - } + function typeParameterToName(type: TypeParameter, context: NodeBuilderContext) { + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) { + const cached = context.typeParameterNames.get(getTypeId(type)); + if (cached) { + return cached; } - let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true); - if (!(result.kind & SyntaxKind.Identifier)) { - return factory.createIdentifier("(Missing type parameter)"); + } + let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true); + if (!(result.kind & SyntaxKind.Identifier)) { + return factory.createIdentifier("(Missing type parameter)"); + } + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { + const rawtext = result.escapedText as string; + let i = context.typeParameterNamesByTextNextNameCount?.get(rawtext) || 0; + let text = rawtext; + while (context.typeParameterNamesByText?.has(text) || typeParameterShadowsNameInScope(text as __String, context, type)) { + i++; + text = `${rawtext}_${i}`; } - if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { - const rawtext = result.escapedText as string; - let i = context.typeParameterNamesByTextNextNameCount?.get(rawtext) || 0; - let text = rawtext; - while (context.typeParameterNamesByText?.has(text) || typeParameterShadowsNameInScope(text as __String, context, type)) { - i++; - text = `${rawtext}_${i}`; - } - if (text !== rawtext) { - result = factory.createIdentifier(text, result.typeArguments); - } - // avoiding iterations of the above loop turns out to be worth it when `i` starts to get large, so we cache the max - // `i` we've used thus far, to save work later - (context.typeParameterNamesByTextNextNameCount ||= new Map()).set(rawtext, i); - (context.typeParameterNames ||= new Map()).set(getTypeId(type), result); - (context.typeParameterNamesByText ||= new Set()).add(rawtext); + if (text !== rawtext) { + result = factory.createIdentifier(text, result.typeArguments); } - return result; + // avoiding iterations of the above loop turns out to be worth it when `i` starts to get large, so we cache the max + // `i` we've used thus far, to save work later + (context.typeParameterNamesByTextNextNameCount ||= new Map()).set(rawtext, i); + (context.typeParameterNames ||= new Map()).set(getTypeId(type), result); + (context.typeParameterNamesByText ||= new Set()).add(rawtext); } + return result; + } - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: true): Identifier; - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: false): EntityName; - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: boolean): EntityName { - const chain = lookupSymbolChain(symbol, context, meaning); + function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: true): Identifier; + function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: false): EntityName; + function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: boolean): EntityName { + const chain = lookupSymbolChain(symbol, context, meaning); - if (expectsIdentifier && chain.length !== 1 - && !context.encounteredError - && !(context.flags & NodeBuilderFlags.AllowQualifiedNameInPlaceOfIdentifier)) { - context.encounteredError = true; + if (expectsIdentifier && chain.length !== 1 + && !context.encounteredError + && !(context.flags & NodeBuilderFlags.AllowQualifiedNameInPlaceOfIdentifier)) { + context.encounteredError = true; + } + return createEntityNameFromSymbolChain(chain, chain.length - 1); + + function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { + const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); + const symbol = chain[index]; + + if (index === 0) { + context.flags |= NodeBuilderFlags.InInitialEntityName; + } + const symbolName = getNameOfSymbolAsWritten(symbol, context); + if (index === 0) { + context.flags ^= NodeBuilderFlags.InInitialEntityName; } - return createEntityNameFromSymbolChain(chain, chain.length - 1); - function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { - const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); - const symbol = chain[index]; + const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + identifier.symbol = symbol; - if (index === 0) { - context.flags |= NodeBuilderFlags.InInitialEntityName; - } - const symbolName = getNameOfSymbolAsWritten(symbol, context); - if (index === 0) { - context.flags ^= NodeBuilderFlags.InInitialEntityName; - } + return index > 0 ? factory.createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; + } + } + function symbolToExpression(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { + const chain = lookupSymbolChain(symbol, context, meaning); + + return createExpressionFromSymbolChain(chain, chain.length - 1); + + function createExpressionFromSymbolChain(chain: Symbol[], index: number): Expression { + const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); + const symbol = chain[index]; + + if (index === 0) { + context.flags |= NodeBuilderFlags.InInitialEntityName; + } + let symbolName = getNameOfSymbolAsWritten(symbol, context); + if (index === 0) { + context.flags ^= NodeBuilderFlags.InInitialEntityName; + } + let firstChar = symbolName.charCodeAt(0); + + if (isSingleOrDoubleQuote(firstChar) && some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { + return factory.createStringLiteral(getSpecifierForModuleSymbol(symbol, context)); + } + const canUsePropertyAccess = firstChar === CharacterCodes.hash ? + symbolName.length > 1 && isIdentifierStart(symbolName.charCodeAt(1), languageVersion) : + isIdentifierStart(firstChar, languageVersion); + if (index === 0 || canUsePropertyAccess) { const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); identifier.symbol = symbol; - return index > 0 ? factory.createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; + return index > 0 ? factory.createPropertyAccessExpression(createExpressionFromSymbolChain(chain, index - 1), identifier) : identifier; + } + else { + if (firstChar === CharacterCodes.openBracket) { + symbolName = symbolName.substring(1, symbolName.length - 1); + firstChar = symbolName.charCodeAt(0); + } + let expression: Expression | undefined; + if (isSingleOrDoubleQuote(firstChar) && !(symbol.flags & SymbolFlags.EnumMember)) { + expression = factory.createStringLiteral(stripQuotes(symbolName).replace(/\\./g, s => s.substring(1)), firstChar === CharacterCodes.singleQuote); + } + else if (("" + +symbolName) === symbolName) { + expression = factory.createNumericLiteral(+symbolName); + } + if (!expression) { + expression = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + expression.symbol = symbol; + } + return factory.createElementAccessExpression(createExpressionFromSymbolChain(chain, index - 1), expression); } } + } - function symbolToExpression(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { - const chain = lookupSymbolChain(symbol, context, meaning); + function isStringNamed(d: Declaration) { + const name = getNameOfDeclaration(d); + return !!name && isStringLiteral(name); + } - return createExpressionFromSymbolChain(chain, chain.length - 1); + function isSingleQuotedStringNamed(d: Declaration) { + const name = getNameOfDeclaration(d); + return !!(name && isStringLiteral(name) && (name.singleQuote || !nodeIsSynthesized(name) && startsWith(getTextOfNode(name, /*includeTrivia*/ false), "'"))); + } - function createExpressionFromSymbolChain(chain: Symbol[], index: number): Expression { - const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); - const symbol = chain[index]; + function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) { + const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); + const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote); + if (fromNameType) { + return fromNameType; + } + const rawName = unescapeLeadingUnderscores(symbol.escapedName); + const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed); + return createPropertyNameNodeForIdentifierOrLiteral(rawName, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed); + } - if (index === 0) { - context.flags |= NodeBuilderFlags.InInitialEntityName; + // See getNameForSymbolFromNameType for a stringy equivalent + function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext, singleQuote?: boolean) { + const nameType = getSymbolLinks(symbol).nameType; + if (nameType) { + if (nameType.flags & TypeFlags.StringOrNumberLiteral) { + const name = "" + (nameType as StringLiteralType | NumberLiteralType).value; + if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions)) && !isNumericLiteralName(name)) { + return factory.createStringLiteral(name, !!singleQuote); } - let symbolName = getNameOfSymbolAsWritten(symbol, context); - if (index === 0) { - context.flags ^= NodeBuilderFlags.InInitialEntityName; + if (isNumericLiteralName(name) && startsWith(name, "-")) { + return factory.createComputedPropertyName(factory.createNumericLiteral(+name)); } - let firstChar = symbolName.charCodeAt(0); + return createPropertyNameNodeForIdentifierOrLiteral(name, getEmitScriptTarget(compilerOptions)); + } + if (nameType.flags & TypeFlags.UniqueESSymbol) { + return factory.createComputedPropertyName(symbolToExpression((nameType as UniqueESSymbolType).symbol, context, SymbolFlags.Value)); + } + } + } - if (isSingleOrDoubleQuote(firstChar) && some(symbol.declarations, hasNonGlobalAugmentationExternalModuleSymbol)) { - return factory.createStringLiteral(getSpecifierForModuleSymbol(symbol, context)); - } - const canUsePropertyAccess = firstChar === CharacterCodes.hash ? - symbolName.length > 1 && isIdentifierStart(symbolName.charCodeAt(1), languageVersion) : - isIdentifierStart(firstChar, languageVersion); - if (index === 0 || canUsePropertyAccess) { - const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); - identifier.symbol = symbol; + function cloneNodeBuilderContext(context: NodeBuilderContext): NodeBuilderContext { + const initial: NodeBuilderContext = { ...context }; + // Make type parameters created within this context not consume the name outside this context + // The symbol serializer ends up creating many sibling scopes that all need "separate" contexts when + // it comes to naming things - within a normal `typeToTypeNode` call, the node builder only ever descends + // through the type tree, so the only cases where we could have used distinct sibling scopes was when there + // were multiple generic overloads with similar generated type parameter names + // The effect: + // When we write out + // export const x: (x: T) => T + // export const y: (x: T) => T + // we write it out like that, rather than as + // export const x: (x: T) => T + // export const y: (x: T_1) => T_1 + if (initial.typeParameterNames) { + initial.typeParameterNames = new Map(initial.typeParameterNames); + } + if (initial.typeParameterNamesByText) { + initial.typeParameterNamesByText = new Set(initial.typeParameterNamesByText); + } + if (initial.typeParameterSymbolList) { + initial.typeParameterSymbolList = new Set(initial.typeParameterSymbolList); + } + initial.tracker = wrapSymbolTrackerToReportForContext(initial, initial.tracker); + return initial; + } - return index > 0 ? factory.createPropertyAccessExpression(createExpressionFromSymbolChain(chain, index - 1), identifier) : identifier; - } - else { - if (firstChar === CharacterCodes.openBracket) { - symbolName = symbolName.substring(1, symbolName.length - 1); - firstChar = symbolName.charCodeAt(0); - } - let expression: Expression | undefined; - if (isSingleOrDoubleQuote(firstChar) && !(symbol.flags & SymbolFlags.EnumMember)) { - expression = factory.createStringLiteral(stripQuotes(symbolName).replace(/\\./g, s => s.substring(1)), firstChar === CharacterCodes.singleQuote); - } - else if (("" + +symbolName) === symbolName) { - expression = factory.createNumericLiteral(+symbolName); - } - if (!expression) { - expression = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); - expression.symbol = symbol; + + function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration: Node | undefined) { + return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration))); + } + + function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) { + return !(getObjectFlags(type) & ObjectFlags.Reference) || !isTypeReferenceNode(existing) || length(existing.typeArguments) >= getMinTypeArgumentCount((type as TypeReference).target.typeParameters); + } + + /** + * Unlike `typeToTypeNodeHelper`, this handles setting up the `AllowUniqueESSymbolType` flag + * so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym` + */ + function serializeTypeForDeclaration(context: NodeBuilderContext, type: Type, symbol: Symbol, enclosingDeclaration: Node | undefined, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { + if (!isErrorType(type) && enclosingDeclaration) { + const declWithExistingAnnotation = getDeclarationWithTypeAnnotation(symbol, enclosingDeclaration); + if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation) && !isGetAccessorDeclaration(declWithExistingAnnotation)) { + // try to reuse the existing annotation + const existing = getEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; + if (typeNodeIsEquivalentToType(existing, declWithExistingAnnotation, type) && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) { + const result = serializeExistingTypeNode(context, existing, includePrivateSymbol, bundled); + if (result) { + return result; } - return factory.createElementAccessExpression(createExpressionFromSymbolChain(chain, index - 1), expression); } } } - - function isStringNamed(d: Declaration) { - const name = getNameOfDeclaration(d); - return !!name && isStringLiteral(name); + const oldFlags = context.flags; + if (type.flags & TypeFlags.UniqueESSymbol && + type.symbol === symbol && (!context.enclosingDeclaration || some(symbol.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!)))) { + context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType; } + const result = typeToTypeNodeHelper(type, context); + context.flags = oldFlags; + return result; + } - function isSingleQuotedStringNamed(d: Declaration) { - const name = getNameOfDeclaration(d); - return !!(name && isStringLiteral(name) && (name.singleQuote || !nodeIsSynthesized(name) && startsWith(getTextOfNode(name, /*includeTrivia*/ false), "'"))); + function typeNodeIsEquivalentToType(typeNode: TypeNode, annotatedDeclaration: Declaration, type: Type) { + const typeFromTypeNode = getTypeFromTypeNode(typeNode); + if (typeFromTypeNode === type) { + return true; } - - function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) { - const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); - const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote); - if (fromNameType) { - return fromNameType; - } - const rawName = unescapeLeadingUnderscores(symbol.escapedName); - const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed); - return createPropertyNameNodeForIdentifierOrLiteral(rawName, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed); + if (isParameter(annotatedDeclaration) && annotatedDeclaration.questionToken) { + return getTypeWithFacts(type, TypeFacts.NEUndefined) === typeFromTypeNode; } + return false; + } - // See getNameForSymbolFromNameType for a stringy equivalent - function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext, singleQuote?: boolean) { - const nameType = getSymbolLinks(symbol).nameType; - if (nameType) { - if (nameType.flags & TypeFlags.StringOrNumberLiteral) { - const name = "" + (nameType as StringLiteralType | NumberLiteralType).value; - if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions)) && !isNumericLiteralName(name)) { - return factory.createStringLiteral(name, !!singleQuote); - } - if (isNumericLiteralName(name) && startsWith(name, "-")) { - return factory.createComputedPropertyName(factory.createNumericLiteral(+name)); + function serializeReturnTypeForSignature(context: NodeBuilderContext, type: Type, signature: Signature, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { + if (!isErrorType(type) && context.enclosingDeclaration) { + const annotation = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); + if (!!findAncestor(annotation, n => n === context.enclosingDeclaration) && annotation) { + const annotated = getTypeFromTypeNode(annotation); + const thisInstantiated = annotated.flags & TypeFlags.TypeParameter && (annotated as TypeParameter).isThisType ? instantiateType(annotated, signature.mapper) : annotated; + if (thisInstantiated === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(annotation, type)) { + const result = serializeExistingTypeNode(context, annotation, includePrivateSymbol, bundled); + if (result) { + return result; } - return createPropertyNameNodeForIdentifierOrLiteral(name, getEmitScriptTarget(compilerOptions)); - } - if (nameType.flags & TypeFlags.UniqueESSymbol) { - return factory.createComputedPropertyName(symbolToExpression((nameType as UniqueESSymbolType).symbol, context, SymbolFlags.Value)); } } } + return typeToTypeNodeHelper(type, context); + } - function cloneNodeBuilderContext(context: NodeBuilderContext): NodeBuilderContext { - const initial: NodeBuilderContext = { ...context }; - // Make type parameters created within this context not consume the name outside this context - // The symbol serializer ends up creating many sibling scopes that all need "separate" contexts when - // it comes to naming things - within a normal `typeToTypeNode` call, the node builder only ever descends - // through the type tree, so the only cases where we could have used distinct sibling scopes was when there - // were multiple generic overloads with similar generated type parameter names - // The effect: - // When we write out - // export const x: (x: T) => T - // export const y: (x: T) => T - // we write it out like that, rather than as - // export const x: (x: T) => T - // export const y: (x: T_1) => T_1 - if (initial.typeParameterNames) { - initial.typeParameterNames = new Map(initial.typeParameterNames); + function trackExistingEntityName(node: T, context: NodeBuilderContext, includePrivateSymbol?: (s: Symbol) => void) { + let introducesError = false; + const leftmost = getFirstIdentifier(node); + if (isInJSFile(node) && (isExportsIdentifier(leftmost) || isModuleExportsAccessExpression(leftmost.parent) || (isQualifiedName(leftmost.parent) && isModuleIdentifier(leftmost.parent.left) && isExportsIdentifier(leftmost.parent.right)))) { + introducesError = true; + return { introducesError, node }; + } + const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true); + if (sym) { + if (isSymbolAccessible(sym, context.enclosingDeclaration, SymbolFlags.All, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { + introducesError = true; } - if (initial.typeParameterNamesByText) { - initial.typeParameterNamesByText = new Set(initial.typeParameterNamesByText); + else { + context.tracker?.trackSymbol?.(sym, context.enclosingDeclaration, SymbolFlags.All); + includePrivateSymbol?.(sym); } - if (initial.typeParameterSymbolList) { - initial.typeParameterSymbolList = new Set(initial.typeParameterSymbolList); + if (isIdentifier(node)) { + const type = getDeclaredTypeOfSymbol(sym); + const name = sym.flags & SymbolFlags.TypeParameter && !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration) ? typeParameterToName(type, context) : factory.cloneNode(node); + name.symbol = sym; // for quickinfo, which uses identifier symbol information + return { introducesError, node: setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping) }; } - initial.tracker = wrapSymbolTrackerToReportForContext(initial, initial.tracker); - return initial; } + return { introducesError, node }; + } - function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration: Node | undefined) { - return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration))); + function serializeExistingTypeNode(context: NodeBuilderContext, existing: TypeNode, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { + if (cancellationToken && cancellationToken.throwIfCancellationRequested) { + cancellationToken.throwIfCancellationRequested(); } - - function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) { - return !(getObjectFlags(type) & ObjectFlags.Reference) || !isTypeReferenceNode(existing) || length(existing.typeArguments) >= getMinTypeArgumentCount((type as TypeReference).target.typeParameters); + let hadError = false; + const file = getSourceFileOfNode(existing); + const transformed = visitNode(existing, visitExistingNodeTreeSymbols); + if (hadError) { + return undefined; } + return transformed === existing ? setTextRange(factory.cloneNode(existing), existing) : transformed; - /** - * Unlike `typeToTypeNodeHelper`, this handles setting up the `AllowUniqueESSymbolType` flag - * so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym` - */ - function serializeTypeForDeclaration(context: NodeBuilderContext, type: Type, symbol: Symbol, enclosingDeclaration: Node | undefined, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { - if (!isErrorType(type) && enclosingDeclaration) { - const declWithExistingAnnotation = getDeclarationWithTypeAnnotation(symbol, enclosingDeclaration); - if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation) && !isGetAccessorDeclaration(declWithExistingAnnotation)) { - // try to reuse the existing annotation - const existing = getEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; - if (typeNodeIsEquivalentToType(existing, declWithExistingAnnotation, type) && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) { - const result = serializeExistingTypeNode(context, existing, includePrivateSymbol, bundled); - if (result) { - return result; - } - } - } + function visitExistingNodeTreeSymbols(node: T): Node { + // We don't _actually_ support jsdoc namepath types, emit `any` instead + if (isJSDocAllType(node) || node.kind === SyntaxKind.JSDocNamepathType) { + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } - const oldFlags = context.flags; - if (type.flags & TypeFlags.UniqueESSymbol && - type.symbol === symbol && (!context.enclosingDeclaration || some(symbol.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!)))) { - context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType; + if (isJSDocUnknownType(node)) { + return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword); } - const result = typeToTypeNodeHelper(type, context); - context.flags = oldFlags; - return result; - } - - function typeNodeIsEquivalentToType(typeNode: TypeNode, annotatedDeclaration: Declaration, type: Type) { - const typeFromTypeNode = getTypeFromTypeNode(typeNode); - if (typeFromTypeNode === type) { - return true; + if (isJSDocNullableType(node)) { + return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), factory.createLiteralTypeNode(factory.createNull())]); } - if (isParameter(annotatedDeclaration) && annotatedDeclaration.questionToken) { - return getTypeWithFacts(type, TypeFacts.NEUndefined) === typeFromTypeNode; + if (isJSDocOptionalType(node)) { + return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); } - return false; - } - - function serializeReturnTypeForSignature(context: NodeBuilderContext, type: Type, signature: Signature, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { - if (!isErrorType(type) && context.enclosingDeclaration) { - const annotation = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); - if (!!findAncestor(annotation, n => n === context.enclosingDeclaration) && annotation) { - const annotated = getTypeFromTypeNode(annotation); - const thisInstantiated = annotated.flags & TypeFlags.TypeParameter && (annotated as TypeParameter).isThisType ? instantiateType(annotated, signature.mapper) : annotated; - if (thisInstantiated === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(annotation, type)) { - const result = serializeExistingTypeNode(context, annotation, includePrivateSymbol, bundled); - if (result) { - return result; - } - } - } + if (isJSDocNonNullableType(node)) { + return visitNode(node.type, visitExistingNodeTreeSymbols); } - return typeToTypeNodeHelper(type, context); - } + if (isJSDocVariadicType(node)) { + return factory.createArrayTypeNode(visitNode((node as JSDocVariadicType).type, visitExistingNodeTreeSymbols)); + } + if (isJSDocTypeLiteral(node)) { + return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => { + const name = isIdentifier(t.name) ? t.name : t.name.right; + const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(node), name.escapedText); + const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined; - function trackExistingEntityName(node: T, context: NodeBuilderContext, includePrivateSymbol?: (s: Symbol) => void) { - let introducesError = false; - const leftmost = getFirstIdentifier(node); - if (isInJSFile(node) && (isExportsIdentifier(leftmost) || isModuleExportsAccessExpression(leftmost.parent) || (isQualifiedName(leftmost.parent) && isModuleIdentifier(leftmost.parent.left) && isExportsIdentifier(leftmost.parent.right)))) { - introducesError = true; - return { introducesError, node }; + return factory.createPropertySignature( + /*modifiers*/ undefined, + name, + t.isBracketed || t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + overrideTypeNode || (t.typeExpression && visitNode(t.typeExpression.type, visitExistingNodeTreeSymbols)) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ); + })); } - const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true); - if (sym) { - if (isSymbolAccessible(sym, context.enclosingDeclaration, SymbolFlags.All, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { - introducesError = true; + if (isTypeReferenceNode(node) && isIdentifier(node.typeName) && node.typeName.escapedText === "") { + return setOriginalNode(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), node); + } + if ((isExpressionWithTypeArguments(node) || isTypeReferenceNode(node)) && isJSDocIndexSignature(node)) { + return factory.createTypeLiteralNode([factory.createIndexSignature( + /*modifiers*/ undefined, + [factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotdotdotToken*/ undefined, + "x", + /*questionToken*/ undefined, + visitNode(node.typeArguments![0], visitExistingNodeTreeSymbols) + )], + visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols) + )]); + } + if (isJSDocFunctionType(node)) { + if (isJSDocConstructSignature(node)) { + let newTypeNode: TypeNode | undefined; + return factory.createConstructorTypeNode( + /*modifiers*/ undefined, + visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), + mapDefined(node.parameters, (p, i) => p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration( + /*modifiers*/ undefined, + getEffectiveDotDotDotForParameter(p), + getNameForJSDocFunctionParameter(p, i), + p.questionToken, + visitNode(p.type, visitExistingNodeTreeSymbols), + /*initializer*/ undefined + )), + visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ); } else { - context.tracker?.trackSymbol?.(sym, context.enclosingDeclaration, SymbolFlags.All); - includePrivateSymbol?.(sym); + return factory.createFunctionTypeNode( + visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), + map(node.parameters, (p, i) => factory.createParameterDeclaration( + /*modifiers*/ undefined, + getEffectiveDotDotDotForParameter(p), + getNameForJSDocFunctionParameter(p, i), + p.questionToken, + visitNode(p.type, visitExistingNodeTreeSymbols), + /*initializer*/ undefined + )), + visitNode(node.type, visitExistingNodeTreeSymbols) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ); } - if (isIdentifier(node)) { - const type = getDeclaredTypeOfSymbol(sym); - const name = sym.flags & SymbolFlags.TypeParameter && !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration) ? typeParameterToName(type, context) : factory.cloneNode(node); - name.symbol = sym; // for quickinfo, which uses identifier symbol information - return { introducesError, node: setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping) }; + } + if (isTypeReferenceNode(node) && isInJSDoc(node) && (!existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(node, getTypeFromTypeNode(node)) || getIntendedTypeFromJSDocTypeReference(node) || unknownSymbol === resolveTypeReferenceName(node, SymbolFlags.Type, /*ignoreErrors*/ true))) { + return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node); + } + if (isLiteralImportTypeNode(node)) { + const nodeSymbol = getNodeLinks(node).resolvedSymbol; + if (isInJSDoc(node) && + nodeSymbol && + ( + // The import type resolved using jsdoc fallback logic + (!node.isTypeOf && !(nodeSymbol.flags & SymbolFlags.Type)) || + // The import type had type arguments autofilled by js fallback logic + !(length(node.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol))) + ) + ) { + return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node); } + return factory.updateImportTypeNode( + node, + factory.updateLiteralTypeNode(node.argument, rewriteModuleSpecifier(node, node.argument.literal)), + node.assertions, + node.qualifier, + visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), + node.isTypeOf + ); } - return { introducesError, node }; - } + if (isEntityName(node) || isEntityNameExpression(node)) { + const { introducesError, node: result } = trackExistingEntityName(node, context, includePrivateSymbol); + hadError = hadError || introducesError; + if (result !== node) { + return result; + } + } - function serializeExistingTypeNode(context: NodeBuilderContext, existing: TypeNode, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { - if (cancellationToken && cancellationToken.throwIfCancellationRequested) { - cancellationToken.throwIfCancellationRequested(); + if (file && isTupleTypeNode(node) && (getLineAndCharacterOfPosition(file, node.pos).line === getLineAndCharacterOfPosition(file, node.end).line)) { + setEmitFlags(node, EmitFlags.SingleLine); } - let hadError = false; - const file = getSourceFileOfNode(existing); - const transformed = visitNode(existing, visitExistingNodeTreeSymbols); - if (hadError) { - return undefined; + + return visitEachChild(node, visitExistingNodeTreeSymbols, nullTransformationContext); + + function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) { + return p.dotDotDotToken || (p.type && isJSDocVariadicType(p.type) ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined); } - return transformed === existing ? setTextRange(factory.cloneNode(existing), existing) : transformed; - function visitExistingNodeTreeSymbols(node: T): Node { - // We don't _actually_ support jsdoc namepath types, emit `any` instead - if (isJSDocAllType(node) || node.kind === SyntaxKind.JSDocNamepathType) { - return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - if (isJSDocUnknownType(node)) { - return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword); - } - if (isJSDocNullableType(node)) { - return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), factory.createLiteralTypeNode(factory.createNull())]); - } - if (isJSDocOptionalType(node)) { - return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); - } - if (isJSDocNonNullableType(node)) { - return visitNode(node.type, visitExistingNodeTreeSymbols); - } - if (isJSDocVariadicType(node)) { - return factory.createArrayTypeNode(visitNode((node as JSDocVariadicType).type, visitExistingNodeTreeSymbols)); - } - if (isJSDocTypeLiteral(node)) { - return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => { - const name = isIdentifier(t.name) ? t.name : t.name.right; - const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(node), name.escapedText); - const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined; + /** Note that `new:T` parameters are not handled, but should be before calling this function. */ + function getNameForJSDocFunctionParameter(p: ParameterDeclaration, index: number) { + return p.name && isIdentifier(p.name) && p.name.escapedText === "this" ? "this" + : getEffectiveDotDotDotForParameter(p) ? `args` + : `arg${index}`; + } - return factory.createPropertySignature( - /*modifiers*/ undefined, - name, - t.isBracketed || t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? factory.createToken(SyntaxKind.QuestionToken) : undefined, - overrideTypeNode || (t.typeExpression && visitNode(t.typeExpression.type, visitExistingNodeTreeSymbols)) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ); - })); - } - if (isTypeReferenceNode(node) && isIdentifier(node.typeName) && node.typeName.escapedText === "") { - return setOriginalNode(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), node); - } - if ((isExpressionWithTypeArguments(node) || isTypeReferenceNode(node)) && isJSDocIndexSignature(node)) { - return factory.createTypeLiteralNode([factory.createIndexSignature( - /*modifiers*/ undefined, - [factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotdotdotToken*/ undefined, - "x", - /*questionToken*/ undefined, - visitNode(node.typeArguments![0], visitExistingNodeTreeSymbols) - )], - visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols) - )]); - } - if (isJSDocFunctionType(node)) { - if (isJSDocConstructSignature(node)) { - let newTypeNode: TypeNode | undefined; - return factory.createConstructorTypeNode( - /*modifiers*/ undefined, - visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), - mapDefined(node.parameters, (p, i) => p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration( - /*modifiers*/ undefined, - getEffectiveDotDotDotForParameter(p), - getNameForJSDocFunctionParameter(p, i), - p.questionToken, - visitNode(p.type, visitExistingNodeTreeSymbols), - /*initializer*/ undefined - )), - visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ); - } - else { - return factory.createFunctionTypeNode( - visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), - map(node.parameters, (p, i) => factory.createParameterDeclaration( - /*modifiers*/ undefined, - getEffectiveDotDotDotForParameter(p), - getNameForJSDocFunctionParameter(p, i), - p.questionToken, - visitNode(p.type, visitExistingNodeTreeSymbols), - /*initializer*/ undefined - )), - visitNode(node.type, visitExistingNodeTreeSymbols) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ); - } - } - if (isTypeReferenceNode(node) && isInJSDoc(node) && (!existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(node, getTypeFromTypeNode(node)) || getIntendedTypeFromJSDocTypeReference(node) || unknownSymbol === resolveTypeReferenceName(node, SymbolFlags.Type, /*ignoreErrors*/ true))) { - return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node); - } - if (isLiteralImportTypeNode(node)) { - const nodeSymbol = getNodeLinks(node).resolvedSymbol; - if (isInJSDoc(node) && - nodeSymbol && - ( - // The import type resolved using jsdoc fallback logic - (!node.isTypeOf && !(nodeSymbol.flags & SymbolFlags.Type)) || - // The import type had type arguments autofilled by js fallback logic - !(length(node.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol))) - ) - ) { - return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node); + function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) { + if (bundled) { + if (context.tracker && context.tracker.moduleResolverHost) { + const targetFile = getExternalModuleFileFromDeclaration(parent); + if (targetFile) { + const getCanonicalFileName = createGetCanonicalFileName(!!host.useCaseSensitiveFileNames); + const resolverHost = { + getCanonicalFileName, + getCurrentDirectory: () => context.tracker.moduleResolverHost!.getCurrentDirectory(), + getCommonSourceDirectory: () => context.tracker.moduleResolverHost!.getCommonSourceDirectory() + }; + const newName = getResolvedExternalModuleName(resolverHost, targetFile); + return factory.createStringLiteral(newName); + } } - return factory.updateImportTypeNode( - node, - factory.updateLiteralTypeNode(node.argument, rewriteModuleSpecifier(node, node.argument.literal)), - node.assertions, - node.qualifier, - visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), - node.isTypeOf - ); } - - if (isEntityName(node) || isEntityNameExpression(node)) { - const { introducesError, node: result } = trackExistingEntityName(node, context, includePrivateSymbol); - hadError = hadError || introducesError; - if (result !== node) { - return result; + else { + if (context.tracker && context.tracker.trackExternalModuleSymbolOfImportTypeNode) { + const moduleSym = resolveExternalModuleNameWorker(lit, lit, /*moduleNotFoundError*/ undefined); + if (moduleSym) { + context.tracker.trackExternalModuleSymbolOfImportTypeNode(moduleSym); + } } } + return lit; + } + } + } - if (file && isTupleTypeNode(node) && (getLineAndCharacterOfPosition(file, node.pos).line === getLineAndCharacterOfPosition(file, node.end).line)) { - setEmitFlags(node, EmitFlags.SingleLine); - } - - return visitEachChild(node, visitExistingNodeTreeSymbols, nullTransformationContext); - - function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) { - return p.dotDotDotToken || (p.type && isJSDocVariadicType(p.type) ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined); - } + function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext, bundled?: boolean): Statement[] { + const serializePropertySymbolForClass = makeSerializePropertySymbol(factory.createPropertyDeclaration, SyntaxKind.MethodDeclaration, /*useAcessors*/ true); + const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((mods, name, question, type) => factory.createPropertySignature(mods, name, question, type), SyntaxKind.MethodSignature, /*useAcessors*/ false); - /** Note that `new:T` parameters are not handled, but should be before calling this function. */ - function getNameForJSDocFunctionParameter(p: ParameterDeclaration, index: number) { - return p.name && isIdentifier(p.name) && p.name.escapedText === "this" ? "this" - : getEffectiveDotDotDotForParameter(p) ? `args` - : `arg${index}`; - } + // TODO: Use `setOriginalNode` on original declaration names where possible so these declarations see some kind of + // declaration mapping - function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) { - if (bundled) { - if (context.tracker && context.tracker.moduleResolverHost) { - const targetFile = getExternalModuleFileFromDeclaration(parent); - if (targetFile) { - const getCanonicalFileName = createGetCanonicalFileName(!!host.useCaseSensitiveFileNames); - const resolverHost = { - getCanonicalFileName, - getCurrentDirectory: () => context.tracker.moduleResolverHost!.getCurrentDirectory(), - getCommonSourceDirectory: () => context.tracker.moduleResolverHost!.getCommonSourceDirectory() - }; - const newName = getResolvedExternalModuleName(resolverHost, targetFile); - return factory.createStringLiteral(newName); - } + // We save the enclosing declaration off here so it's not adjusted by well-meaning declaration + // emit codepaths which want to apply more specific contexts (so we can still refer to the root real declaration + // we're trying to emit from later on) + const enclosingDeclaration = context.enclosingDeclaration!; + let results: Statement[] = []; + const visitedSymbols = new Set(); + const deferredPrivatesStack: ESMap[] = []; + const oldcontext = context; + context = { + ...oldcontext, + usedSymbolNames: new Set(oldcontext.usedSymbolNames), + remappedSymbolNames: new Map(), + tracker: { + ...oldcontext.tracker, + trackSymbol: (sym, decl, meaning) => { + const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*computeAliases*/ false); + if (accessibleResult.accessibility === SymbolAccessibility.Accessible) { + // Lookup the root symbol of the chain of refs we'll use to access it and serialize it + const chain = lookupSymbolChainWorker(sym, context, meaning); + if (!(sym.flags & SymbolFlags.Property)) { + includePrivateSymbol(chain[0]); } } - else { - if (context.tracker && context.tracker.trackExternalModuleSymbolOfImportTypeNode) { - const moduleSym = resolveExternalModuleNameWorker(lit, lit, /*moduleNotFoundError*/ undefined); - if (moduleSym) { - context.tracker.trackExternalModuleSymbolOfImportTypeNode(moduleSym); - } - } + else if (oldcontext.tracker && oldcontext.tracker.trackSymbol) { + return oldcontext.tracker.trackSymbol(sym, decl, meaning); } - return lit; - } - } - } - - function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext, bundled?: boolean): Statement[] { - const serializePropertySymbolForClass = makeSerializePropertySymbol(factory.createPropertyDeclaration, SyntaxKind.MethodDeclaration, /*useAcessors*/ true); - const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((mods, name, question, type) => factory.createPropertySignature(mods, name, question, type), SyntaxKind.MethodSignature, /*useAcessors*/ false); - - // TODO: Use `setOriginalNode` on original declaration names where possible so these declarations see some kind of - // declaration mapping - - // We save the enclosing declaration off here so it's not adjusted by well-meaning declaration - // emit codepaths which want to apply more specific contexts (so we can still refer to the root real declaration - // we're trying to emit from later on) - const enclosingDeclaration = context.enclosingDeclaration!; - let results: Statement[] = []; - const visitedSymbols = new Set(); - const deferredPrivatesStack: ESMap[] = []; - const oldcontext = context; - context = { - ...oldcontext, - usedSymbolNames: new Set(oldcontext.usedSymbolNames), - remappedSymbolNames: new Map(), - tracker: { - ...oldcontext.tracker, - trackSymbol: (sym, decl, meaning) => { - const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*computeAliases*/ false); - if (accessibleResult.accessibility === SymbolAccessibility.Accessible) { - // Lookup the root symbol of the chain of refs we'll use to access it and serialize it - const chain = lookupSymbolChainWorker(sym, context, meaning); - if (!(sym.flags & SymbolFlags.Property)) { - includePrivateSymbol(chain[0]); - } - } - else if (oldcontext.tracker && oldcontext.tracker.trackSymbol) { - return oldcontext.tracker.trackSymbol(sym, decl, meaning); - } - return false; - }, + return false; }, - }; - context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); - forEachEntry(symbolTable, (symbol, name) => { - const baseName = unescapeLeadingUnderscores(name); - void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` - }); - let addingDeclare = !bundled; - const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); - if (exportEquals && symbolTable.size > 1 && exportEquals.flags & SymbolFlags.Alias) { - symbolTable = createSymbolTable(); - // Remove extraneous elements from root symbol table (they'll be mixed back in when the target of the `export=` is looked up) - symbolTable.set(InternalSymbolName.ExportEquals, exportEquals); - } - - visitSymbolTable(symbolTable); - return mergeRedundantStatements(results); - - function isIdentifierAndNotUndefined(node: Node | undefined): node is Identifier { - return !!node && node.kind === SyntaxKind.Identifier; - } - - function getNamesOfDeclaration(statement: Statement): Identifier[] { - if (isVariableStatement(statement)) { - return filter(map(statement.declarationList.declarations, getNameOfDeclaration), isIdentifierAndNotUndefined); - } - return filter([getNameOfDeclaration(statement as DeclarationStatement)], isIdentifierAndNotUndefined); - } - - function flattenExportAssignedNamespace(statements: Statement[]) { - const exportAssignment = find(statements, isExportAssignment); - const nsIndex = findIndex(statements, isModuleDeclaration); - let ns = nsIndex !== -1 ? statements[nsIndex] as ModuleDeclaration : undefined; - if (ns && exportAssignment && exportAssignment.isExportEquals && - isIdentifier(exportAssignment.expression) && isIdentifier(ns.name) && idText(ns.name) === idText(exportAssignment.expression) && - ns.body && isModuleBlock(ns.body)) { - // Pass 0: Correct situations where a module has both an `export = ns` and multiple top-level exports by stripping the export modifiers from - // the top-level exports and exporting them in the targeted ns, as can occur when a js file has both typedefs and `module.export` assignments - const excessExports = filter(statements, s => !!(getEffectiveModifierFlags(s) & ModifierFlags.Export)); - const name = ns.name; - let body = ns.body; - if (length(excessExports)) { - ns = factory.updateModuleDeclaration( - ns, - ns.modifiers, - ns.name, - body = factory.updateModuleBlock( - body, - factory.createNodeArray([...ns.body.statements, factory.createExportDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*isTypeOnly*/ false, /*alias*/ undefined, id))), - /*moduleSpecifier*/ undefined - )]) - ) - ); - statements = [...statements.slice(0, nsIndex), ns, ...statements.slice(nsIndex + 1)]; - } + }, + }; + context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); + forEachEntry(symbolTable, (symbol, name) => { + const baseName = unescapeLeadingUnderscores(name); + void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` + }); + let addingDeclare = !bundled; + const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); + if (exportEquals && symbolTable.size > 1 && exportEquals.flags & SymbolFlags.Alias) { + symbolTable = createSymbolTable(); + // Remove extraneous elements from root symbol table (they'll be mixed back in when the target of the `export=` is looked up) + symbolTable.set(InternalSymbolName.ExportEquals, exportEquals); + } + + visitSymbolTable(symbolTable); + return mergeRedundantStatements(results); + + function isIdentifierAndNotUndefined(node: Node | undefined): node is Identifier { + return !!node && node.kind === SyntaxKind.Identifier; + } + + function getNamesOfDeclaration(statement: Statement): Identifier[] { + if (isVariableStatement(statement)) { + return filter(map(statement.declarationList.declarations, getNameOfDeclaration), isIdentifierAndNotUndefined); + } + return filter([getNameOfDeclaration(statement as DeclarationStatement)], isIdentifierAndNotUndefined); + } + + function flattenExportAssignedNamespace(statements: Statement[]) { + const exportAssignment = find(statements, isExportAssignment); + const nsIndex = findIndex(statements, isModuleDeclaration); + let ns = nsIndex !== -1 ? statements[nsIndex] as ModuleDeclaration : undefined; + if (ns && exportAssignment && exportAssignment.isExportEquals && + isIdentifier(exportAssignment.expression) && isIdentifier(ns.name) && idText(ns.name) === idText(exportAssignment.expression) && + ns.body && isModuleBlock(ns.body)) { + // Pass 0: Correct situations where a module has both an `export = ns` and multiple top-level exports by stripping the export modifiers from + // the top-level exports and exporting them in the targeted ns, as can occur when a js file has both typedefs and `module.export` assignments + const excessExports = filter(statements, s => !!(getEffectiveModifierFlags(s) & ModifierFlags.Export)); + const name = ns.name; + let body = ns.body; + if (length(excessExports)) { + ns = factory.updateModuleDeclaration( + ns, + ns.modifiers, + ns.name, + body = factory.updateModuleBlock( + body, + factory.createNodeArray([...ns.body.statements, factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*isTypeOnly*/ false, /*alias*/ undefined, id))), + /*moduleSpecifier*/ undefined + )]) + ) + ); + statements = [...statements.slice(0, nsIndex), ns, ...statements.slice(nsIndex + 1)]; + } - // Pass 1: Flatten `export namespace _exports {} export = _exports;` so long as the `export=` only points at a single namespace declaration - if (!find(statements, s => s !== ns && nodeHasName(s, name))) { - results = []; - // If the namespace contains no export assignments or declarations, and no declarations flagged with `export`, then _everything_ is exported - - // to respect this as the top level, we need to add an `export` modifier to everything - const mixinExportFlag = !some(body.statements, s => hasSyntacticModifier(s, ModifierFlags.Export) || isExportAssignment(s) || isExportDeclaration(s)); - forEach(body.statements, s => { - addResult(s, mixinExportFlag ? ModifierFlags.Export : ModifierFlags.None); // Recalculates the ambient (and export, if applicable from above) flag - }); - statements = [...filter(statements, s => s !== ns && s !== exportAssignment), ...results]; - } + // Pass 1: Flatten `export namespace _exports {} export = _exports;` so long as the `export=` only points at a single namespace declaration + if (!find(statements, s => s !== ns && nodeHasName(s, name))) { + results = []; + // If the namespace contains no export assignments or declarations, and no declarations flagged with `export`, then _everything_ is exported - + // to respect this as the top level, we need to add an `export` modifier to everything + const mixinExportFlag = !some(body.statements, s => hasSyntacticModifier(s, ModifierFlags.Export) || isExportAssignment(s) || isExportDeclaration(s)); + forEach(body.statements, s => { + addResult(s, mixinExportFlag ? ModifierFlags.Export : ModifierFlags.None); // Recalculates the ambient (and export, if applicable from above) flag + }); + statements = [...filter(statements, s => s !== ns && s !== exportAssignment), ...results]; } - return statements; } + return statements; + } - function mergeExportDeclarations(statements: Statement[]) { - // Pass 2: Combine all `export {}` declarations - const exports = filter(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[]; - if (length(exports) > 1) { - const nonExports = filter(statements, d => !isExportDeclaration(d) || !!d.moduleSpecifier || !d.exportClause); - statements = [...nonExports, factory.createExportDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports(flatMap(exports, e => cast(e.exportClause, isNamedExports).elements)), - /*moduleSpecifier*/ undefined - )]; - } - // Pass 2b: Also combine all `export {} from "..."` declarations as needed - const reexports = filter(statements, d => isExportDeclaration(d) && !!d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[]; - if (length(reexports) > 1) { - const groups = group(reexports, decl => isStringLiteral(decl.moduleSpecifier!) ? ">" + decl.moduleSpecifier.text : ">"); - if (groups.length !== reexports.length) { - for (const group of groups) { - if (group.length > 1) { - // remove group members from statements and then merge group members and add back to statements - statements = [ - ...filter(statements, s => group.indexOf(s as ExportDeclaration) === -1), - factory.createExportDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports(flatMap(group, e => cast(e.exportClause, isNamedExports).elements)), - group[0].moduleSpecifier - ) - ]; - } + function mergeExportDeclarations(statements: Statement[]) { + // Pass 2: Combine all `export {}` declarations + const exports = filter(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[]; + if (length(exports) > 1) { + const nonExports = filter(statements, d => !isExportDeclaration(d) || !!d.moduleSpecifier || !d.exportClause); + statements = [...nonExports, factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(flatMap(exports, e => cast(e.exportClause, isNamedExports).elements)), + /*moduleSpecifier*/ undefined + )]; + } + // Pass 2b: Also combine all `export {} from "..."` declarations as needed + const reexports = filter(statements, d => isExportDeclaration(d) && !!d.moduleSpecifier && !!d.exportClause && isNamedExports(d.exportClause)) as ExportDeclaration[]; + if (length(reexports) > 1) { + const groups = group(reexports, decl => isStringLiteral(decl.moduleSpecifier!) ? ">" + decl.moduleSpecifier.text : ">"); + if (groups.length !== reexports.length) { + for (const group of groups) { + if (group.length > 1) { + // remove group members from statements and then merge group members and add back to statements + statements = [ + ...filter(statements, s => group.indexOf(s as ExportDeclaration) === -1), + factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(flatMap(group, e => cast(e.exportClause, isNamedExports).elements)), + group[0].moduleSpecifier + ) + ]; } } } - return statements; } + return statements; + } - function inlineExportModifiers(statements: Statement[]) { - // Pass 3: Move all `export {}`'s to `export` modifiers where possible - const index = findIndex(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !d.assertClause && !!d.exportClause && isNamedExports(d.exportClause)); - if (index >= 0) { - const exportDecl = statements[index] as ExportDeclaration & { readonly exportClause: NamedExports }; - const replacements = mapDefined(exportDecl.exportClause.elements, e => { - if (!e.propertyName) { - // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it - const indices = indicesOf(statements); - const associatedIndices = filter(indices, i => nodeHasName(statements[i], e.name)); - if (length(associatedIndices) && every(associatedIndices, i => canHaveExportModifier(statements[i]))) { - for (const index of associatedIndices) { - statements[index] = addExportModifier(statements[index] as Extract); - } - return undefined; + function inlineExportModifiers(statements: Statement[]) { + // Pass 3: Move all `export {}`'s to `export` modifiers where possible + const index = findIndex(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !d.assertClause && !!d.exportClause && isNamedExports(d.exportClause)); + if (index >= 0) { + const exportDecl = statements[index] as ExportDeclaration & { readonly exportClause: NamedExports }; + const replacements = mapDefined(exportDecl.exportClause.elements, e => { + if (!e.propertyName) { + // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it + const indices = indicesOf(statements); + const associatedIndices = filter(indices, i => nodeHasName(statements[i], e.name)); + if (length(associatedIndices) && every(associatedIndices, i => canHaveExportModifier(statements[i]))) { + for (const index of associatedIndices) { + statements[index] = addExportModifier(statements[index] as Extract); } + return undefined; } - return e; - }); - if (!length(replacements)) { - // all clauses removed, remove the export declaration - orderedRemoveItemAt(statements, index); - } - else { - // some items filtered, others not - update the export declaration - statements[index] = factory.updateExportDeclaration( - exportDecl, - exportDecl.modifiers, - exportDecl.isTypeOnly, - factory.updateNamedExports( - exportDecl.exportClause, - replacements - ), - exportDecl.moduleSpecifier, - exportDecl.assertClause - ); } + return e; + }); + if (!length(replacements)) { + // all clauses removed, remove the export declaration + orderedRemoveItemAt(statements, index); + } + else { + // some items filtered, others not - update the export declaration + statements[index] = factory.updateExportDeclaration( + exportDecl, + exportDecl.modifiers, + exportDecl.isTypeOnly, + factory.updateNamedExports( + exportDecl.exportClause, + replacements + ), + exportDecl.moduleSpecifier, + exportDecl.assertClause + ); } - return statements; } + return statements; + } - function mergeRedundantStatements(statements: Statement[]) { - statements = flattenExportAssignedNamespace(statements); - statements = mergeExportDeclarations(statements); - statements = inlineExportModifiers(statements); + function mergeRedundantStatements(statements: Statement[]) { + statements = flattenExportAssignedNamespace(statements); + statements = mergeExportDeclarations(statements); + statements = inlineExportModifiers(statements); - // Not a cleanup, but as a final step: If there is a mix of `export` and non-`export` declarations, but no `export =` or `export {}` add a `export {};` so - // declaration privacy is respected. - if (enclosingDeclaration && - ((isSourceFile(enclosingDeclaration) && isExternalOrCommonJsModule(enclosingDeclaration)) || isModuleDeclaration(enclosingDeclaration)) && - (!some(statements, isExternalModuleIndicator) || (!hasScopeMarker(statements) && some(statements, needsScopeMarker)))) { - statements.push(createEmptyExports(factory)); - } - return statements; + // Not a cleanup, but as a final step: If there is a mix of `export` and non-`export` declarations, but no `export =` or `export {}` add a `export {};` so + // declaration privacy is respected. + if (enclosingDeclaration && + ((isSourceFile(enclosingDeclaration) && isExternalOrCommonJsModule(enclosingDeclaration)) || isModuleDeclaration(enclosingDeclaration)) && + (!some(statements, isExternalModuleIndicator) || (!hasScopeMarker(statements) && some(statements, needsScopeMarker)))) { + statements.push(createEmptyExports(factory)); } + return statements; + } - function addExportModifier(node: Extract) { - const flags = (getEffectiveModifierFlags(node) | ModifierFlags.Export) & ~ModifierFlags.Ambient; - return factory.updateModifiers(node, flags); - } + function addExportModifier(node: Extract) { + const flags = (getEffectiveModifierFlags(node) | ModifierFlags.Export) & ~ModifierFlags.Ambient; + return factory.updateModifiers(node, flags); + } - function removeExportModifier(node: Extract) { - const flags = getEffectiveModifierFlags(node) & ~ModifierFlags.Export; - return factory.updateModifiers(node, flags); - } + function removeExportModifier(node: Extract) { + const flags = getEffectiveModifierFlags(node) & ~ModifierFlags.Export; + return factory.updateModifiers(node, flags); + } - function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) { - if (!suppressNewPrivateContext) { - deferredPrivatesStack.push(new Map()); - } - symbolTable.forEach((symbol: Symbol) => { - serializeSymbol(symbol, /*isPrivate*/ false, !!propertyAsAlias); + function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) { + if (!suppressNewPrivateContext) { + deferredPrivatesStack.push(new Map()); + } + symbolTable.forEach((symbol: Symbol) => { + serializeSymbol(symbol, /*isPrivate*/ false, !!propertyAsAlias); + }); + if (!suppressNewPrivateContext) { + // deferredPrivates will be filled up by visiting the symbol table + // And will continue to iterate as elements are added while visited `deferredPrivates` + // (As that's how a map iterator is defined to work) + deferredPrivatesStack[deferredPrivatesStack.length - 1].forEach((symbol: Symbol) => { + serializeSymbol(symbol, /*isPrivate*/ true, !!propertyAsAlias); }); - if (!suppressNewPrivateContext) { - // deferredPrivates will be filled up by visiting the symbol table - // And will continue to iterate as elements are added while visited `deferredPrivates` - // (As that's how a map iterator is defined to work) - deferredPrivatesStack[deferredPrivatesStack.length - 1].forEach((symbol: Symbol) => { - serializeSymbol(symbol, /*isPrivate*/ true, !!propertyAsAlias); - }); - deferredPrivatesStack.pop(); - } - } - - function serializeSymbol(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean): void { - // cache visited list based on merged symbol, since we want to use the unmerged top-level symbol, but - // still skip reserializing it if we encounter the merged product later on - const visitedSym = getMergedSymbol(symbol); - if (visitedSymbols.has(getSymbolId(visitedSym))) { - return; // Already printed - } - visitedSymbols.add(getSymbolId(visitedSym)); - // Only actually serialize symbols within the correct enclosing declaration, otherwise do nothing with the out-of-context symbol - const skipMembershipCheck = !isPrivate; // We only call this on exported symbols when we know they're in the correct scope - if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => !!findAncestor(d, n => n === enclosingDeclaration)))) { - const oldContext = context; - context = cloneNodeBuilderContext(context); - serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); - if (context.reportedDiagnostic) { - oldcontext.reportedDiagnostic = context.reportedDiagnostic; // hoist diagnostic result into outer context - } - context = oldContext; - } - } - - - // Synthesize declarations for a symbol - might be an Interface, a Class, a Namespace, a Type, a Variable (const, let, or var), an Alias - // or a merge of some number of those. - // An interesting challenge is ensuring that when classes merge with namespaces and interfaces, is keeping - // each symbol in only one of the representations - // Also, synthesizing a default export of some kind - // If it's an alias: emit `export default ref` - // If it's a property: emit `export default _default` with a `_default` prop - // If it's a class/interface/function: emit a class/interface/function with a `default` modifier - // These forms can merge, eg (`export default 12; export default interface A {}`) - function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean): void { - const symbolName = unescapeLeadingUnderscores(symbol.escapedName); - const isDefault = symbol.escapedName === InternalSymbolName.Default; - if (isPrivate && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier) && isStringANonContextualKeyword(symbolName) && !isDefault) { - // Oh no. We cannot use this symbol's name as it's name... It's likely some jsdoc had an invalid name like `export` or `default` :( - context.encounteredError = true; - // TODO: Issue error via symbol tracker? - return; // If we need to emit a private with a keyword name, we're done for, since something else will try to refer to it by that name - } - let needsPostExportDefault = isDefault && !!( - symbol.flags & SymbolFlags.ExportDoesNotSupportDefaultModifier - || (symbol.flags & SymbolFlags.Function && length(getPropertiesOfType(getTypeOfSymbol(symbol)))) - ) && !(symbol.flags & SymbolFlags.Alias); // An alias symbol should preclude needing to make an alias ourselves - let needsExportDeclaration = !needsPostExportDefault && !isPrivate && isStringANonContextualKeyword(symbolName) && !isDefault; - // `serializeVariableOrProperty` will handle adding the export declaration if it is run (since `getInternalSymbolName` will create the name mapping), so we need to ensuer we unset `needsExportDeclaration` if it is - if (needsPostExportDefault || needsExportDeclaration) { - isPrivate = true; - } - const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0); - const isConstMergedWithNS = symbol.flags & SymbolFlags.Module && - symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) && - symbol.escapedName !== InternalSymbolName.ExportEquals; - const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol); - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) || isConstMergedWithNSPrintableAsSignatureMerge) { - serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); - } - if (symbol.flags & SymbolFlags.TypeAlias) { - serializeTypeAlias(symbol, symbolName, modifierFlags); - } - // Need to skip over export= symbols below - json source files get a single `Property` flagged - // symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is. - if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) - && symbol.escapedName !== InternalSymbolName.ExportEquals - && !(symbol.flags & SymbolFlags.Prototype) - && !(symbol.flags & SymbolFlags.Class) - && !(symbol.flags & SymbolFlags.Method) - && !isConstMergedWithNSPrintableAsSignatureMerge) { - if (propertyAsAlias) { - const createdExport = serializeMaybeAliasAssignment(symbol); - if (createdExport) { - needsExportDeclaration = false; - needsPostExportDefault = false; - } + deferredPrivatesStack.pop(); + } + } + + function serializeSymbol(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean): void { + // cache visited list based on merged symbol, since we want to use the unmerged top-level symbol, but + // still skip reserializing it if we encounter the merged product later on + const visitedSym = getMergedSymbol(symbol); + if (visitedSymbols.has(getSymbolId(visitedSym))) { + return; // Already printed + } + visitedSymbols.add(getSymbolId(visitedSym)); + // Only actually serialize symbols within the correct enclosing declaration, otherwise do nothing with the out-of-context symbol + const skipMembershipCheck = !isPrivate; // We only call this on exported symbols when we know they're in the correct scope + if (skipMembershipCheck || (!!length(symbol.declarations) && some(symbol.declarations, d => !!findAncestor(d, n => n === enclosingDeclaration)))) { + const oldContext = context; + context = cloneNodeBuilderContext(context); + serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); + if (context.reportedDiagnostic) { + oldcontext.reportedDiagnostic = context.reportedDiagnostic; // hoist diagnostic result into outer context + } + context = oldContext; + } + } + + + // Synthesize declarations for a symbol - might be an Interface, a Class, a Namespace, a Type, a Variable (const, let, or var), an Alias + // or a merge of some number of those. + // An interesting challenge is ensuring that when classes merge with namespaces and interfaces, is keeping + // each symbol in only one of the representations + // Also, synthesizing a default export of some kind + // If it's an alias: emit `export default ref` + // If it's a property: emit `export default _default` with a `_default` prop + // If it's a class/interface/function: emit a class/interface/function with a `default` modifier + // These forms can merge, eg (`export default 12; export default interface A {}`) + function serializeSymbolWorker(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean): void { + const symbolName = unescapeLeadingUnderscores(symbol.escapedName); + const isDefault = symbol.escapedName === InternalSymbolName.Default; + if (isPrivate && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier) && isStringANonContextualKeyword(symbolName) && !isDefault) { + // Oh no. We cannot use this symbol's name as it's name... It's likely some jsdoc had an invalid name like `export` or `default` :( + context.encounteredError = true; + // TODO: Issue error via symbol tracker? + return; // If we need to emit a private with a keyword name, we're done for, since something else will try to refer to it by that name + } + let needsPostExportDefault = isDefault && !!( + symbol.flags & SymbolFlags.ExportDoesNotSupportDefaultModifier + || (symbol.flags & SymbolFlags.Function && length(getPropertiesOfType(getTypeOfSymbol(symbol)))) + ) && !(symbol.flags & SymbolFlags.Alias); // An alias symbol should preclude needing to make an alias ourselves + let needsExportDeclaration = !needsPostExportDefault && !isPrivate && isStringANonContextualKeyword(symbolName) && !isDefault; + // `serializeVariableOrProperty` will handle adding the export declaration if it is run (since `getInternalSymbolName` will create the name mapping), so we need to ensuer we unset `needsExportDeclaration` if it is + if (needsPostExportDefault || needsExportDeclaration) { + isPrivate = true; + } + const modifierFlags = (!isPrivate ? ModifierFlags.Export : 0) | (isDefault && !needsPostExportDefault ? ModifierFlags.Default : 0); + const isConstMergedWithNS = symbol.flags & SymbolFlags.Module && + symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) && + symbol.escapedName !== InternalSymbolName.ExportEquals; + const isConstMergedWithNSPrintableAsSignatureMerge = isConstMergedWithNS && isTypeRepresentableAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol); + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) || isConstMergedWithNSPrintableAsSignatureMerge) { + serializeAsFunctionNamespaceMerge(getTypeOfSymbol(symbol), symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); + } + if (symbol.flags & SymbolFlags.TypeAlias) { + serializeTypeAlias(symbol, symbolName, modifierFlags); + } + // Need to skip over export= symbols below - json source files get a single `Property` flagged + // symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is. + if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property) + && symbol.escapedName !== InternalSymbolName.ExportEquals + && !(symbol.flags & SymbolFlags.Prototype) + && !(symbol.flags & SymbolFlags.Class) + && !(symbol.flags & SymbolFlags.Method) + && !isConstMergedWithNSPrintableAsSignatureMerge) { + if (propertyAsAlias) { + const createdExport = serializeMaybeAliasAssignment(symbol); + if (createdExport) { + needsExportDeclaration = false; + needsPostExportDefault = false; + } + } + else { + const type = getTypeOfSymbol(symbol); + const localName = getInternalSymbolName(symbol, symbolName); + if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) { + // If the type looks like a function declaration + ns could represent it, and it's type is sourced locally, rewrite it into a function declaration + ns + serializeAsFunctionNamespaceMerge(type, symbol, localName, modifierFlags); } else { - const type = getTypeOfSymbol(symbol); - const localName = getInternalSymbolName(symbol, symbolName); - if (!(symbol.flags & SymbolFlags.Function) && isTypeRepresentableAsFunctionNamespaceMerge(type, symbol)) { - // If the type looks like a function declaration + ns could represent it, and it's type is sourced locally, rewrite it into a function declaration + ns - serializeAsFunctionNamespaceMerge(type, symbol, localName, modifierFlags); + // A Class + Property merge is made for a `module.exports.Member = class {}`, and it doesn't serialize well as either a class _or_ a property symbol - in fact, _it behaves like an alias!_ + // `var` is `FunctionScopedVariable`, `const` and `let` are `BlockScopedVariable`, and `module.exports.thing =` is `Property` + const flags = !(symbol.flags & SymbolFlags.BlockScopedVariable) + ? symbol.parent?.valueDeclaration && isSourceFile(symbol.parent?.valueDeclaration) + ? NodeFlags.Const // exports are immutable in es6, which is what we emulate and check; so it's safe to mark all exports as `const` (there's no difference to consumers, but it allows unique symbol type declarations) + : undefined + : isConstVariable(symbol) + ? NodeFlags.Const + : NodeFlags.Let; + const name = (needsPostExportDefault || !(symbol.flags & SymbolFlags.Property)) ? localName : getUnusedName(localName, symbol); + let textRange: Node | undefined = symbol.declarations && find(symbol.declarations, d => isVariableDeclaration(d)); + if (textRange && isVariableDeclarationList(textRange.parent) && textRange.parent.declarations.length === 1) { + textRange = textRange.parent.parent; + } + const propertyAccessRequire = symbol.declarations?.find(isPropertyAccessExpression); + if (propertyAccessRequire && isBinaryExpression(propertyAccessRequire.parent) && isIdentifier(propertyAccessRequire.parent.right) + && type.symbol?.valueDeclaration && isSourceFile(type.symbol.valueDeclaration)) { + const alias = localName === propertyAccessRequire.parent.right.escapedText ? undefined : propertyAccessRequire.parent.right; + addResult( + factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, alias, localName)]) + ), + ModifierFlags.None + ); + context.tracker.trackSymbol!(type.symbol, context.enclosingDeclaration, SymbolFlags.Value); } else { - // A Class + Property merge is made for a `module.exports.Member = class {}`, and it doesn't serialize well as either a class _or_ a property symbol - in fact, _it behaves like an alias!_ - // `var` is `FunctionScopedVariable`, `const` and `let` are `BlockScopedVariable`, and `module.exports.thing =` is `Property` - const flags = !(symbol.flags & SymbolFlags.BlockScopedVariable) - ? symbol.parent?.valueDeclaration && isSourceFile(symbol.parent?.valueDeclaration) - ? NodeFlags.Const // exports are immutable in es6, which is what we emulate and check; so it's safe to mark all exports as `const` (there's no difference to consumers, but it allows unique symbol type declarations) - : undefined - : isConstVariable(symbol) - ? NodeFlags.Const - : NodeFlags.Let; - const name = (needsPostExportDefault || !(symbol.flags & SymbolFlags.Property)) ? localName : getUnusedName(localName, symbol); - let textRange: Node | undefined = symbol.declarations && find(symbol.declarations, d => isVariableDeclaration(d)); - if (textRange && isVariableDeclarationList(textRange.parent) && textRange.parent.declarations.length === 1) { - textRange = textRange.parent.parent; - } - const propertyAccessRequire = symbol.declarations?.find(isPropertyAccessExpression); - if (propertyAccessRequire && isBinaryExpression(propertyAccessRequire.parent) && isIdentifier(propertyAccessRequire.parent.right) - && type.symbol?.valueDeclaration && isSourceFile(type.symbol.valueDeclaration)) { - const alias = localName === propertyAccessRequire.parent.right.escapedText ? undefined : propertyAccessRequire.parent.right; + const statement = setTextRange(factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ + factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, type, symbol, enclosingDeclaration, includePrivateSymbol, bundled)) + ], flags)), textRange); + addResult(statement, name !== localName ? modifierFlags & ~ModifierFlags.Export : modifierFlags); + if (name !== localName && !isPrivate) { + // We rename the variable declaration we generate for Property symbols since they may have a name which + // conflicts with a local declaration. For example, given input: + // ``` + // function g() {} + // module.exports.g = g + // ``` + // In such a situation, we have a local variable named `g`, and a separate exported variable named `g`. + // Naively, we would emit + // ``` + // function g() {} + // export const g: typeof g; + // ``` + // That's obviously incorrect - the `g` in the type annotation needs to refer to the local `g`, but + // the export declaration shadows it. + // To work around that, we instead write + // ``` + // function g() {} + // const g_1: typeof g; + // export { g_1 as g }; + // ``` + // To create an export named `g` that does _not_ shadow the local `g` addResult( factory.createExportDeclaration( /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, alias, localName)]) + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, name, localName)]) ), ModifierFlags.None ); - context.tracker.trackSymbol!(type.symbol, context.enclosingDeclaration, SymbolFlags.Value); - } - else { - const statement = setTextRange(factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ - factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, type, symbol, enclosingDeclaration, includePrivateSymbol, bundled)) - ], flags)), textRange); - addResult(statement, name !== localName ? modifierFlags & ~ModifierFlags.Export : modifierFlags); - if (name !== localName && !isPrivate) { - // We rename the variable declaration we generate for Property symbols since they may have a name which - // conflicts with a local declaration. For example, given input: - // ``` - // function g() {} - // module.exports.g = g - // ``` - // In such a situation, we have a local variable named `g`, and a separate exported variable named `g`. - // Naively, we would emit - // ``` - // function g() {} - // export const g: typeof g; - // ``` - // That's obviously incorrect - the `g` in the type annotation needs to refer to the local `g`, but - // the export declaration shadows it. - // To work around that, we instead write - // ``` - // function g() {} - // const g_1: typeof g; - // export { g_1 as g }; - // ``` - // To create an export named `g` that does _not_ shadow the local `g` - addResult( - factory.createExportDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, name, localName)]) - ), - ModifierFlags.None - ); - needsExportDeclaration = false; - needsPostExportDefault = false; - } + needsExportDeclaration = false; + needsPostExportDefault = false; } } } } - if (symbol.flags & SymbolFlags.Enum) { - serializeEnum(symbol, symbolName, modifierFlags); - } - if (symbol.flags & SymbolFlags.Class) { - if (symbol.flags & SymbolFlags.Property - && symbol.valueDeclaration - && isBinaryExpression(symbol.valueDeclaration.parent) - && isClassExpression(symbol.valueDeclaration.parent.right)) { - // Looks like a `module.exports.Sub = class {}` - if we serialize `symbol` as a class, the result will have no members, - // since the classiness is actually from the target of the effective alias the symbol is. yes. A BlockScopedVariable|Class|Property - // _really_ acts like an Alias, and none of a BlockScopedVariable, Class, or Property. This is the travesty of JS binding today. - serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); - } - else { - serializeAsClass(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); - } - } - if ((symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && (!isConstMergedWithNS || isTypeOnlyNamespace(symbol))) || isConstMergedWithNSPrintableAsSignatureMerge) { - serializeModule(symbol, symbolName, modifierFlags); - } - // The class meaning serialization should handle serializing all interface members - if (symbol.flags & SymbolFlags.Interface && !(symbol.flags & SymbolFlags.Class)) { - serializeInterface(symbol, symbolName, modifierFlags); - } - if (symbol.flags & SymbolFlags.Alias) { + } + if (symbol.flags & SymbolFlags.Enum) { + serializeEnum(symbol, symbolName, modifierFlags); + } + if (symbol.flags & SymbolFlags.Class) { + if (symbol.flags & SymbolFlags.Property + && symbol.valueDeclaration + && isBinaryExpression(symbol.valueDeclaration.parent) + && isClassExpression(symbol.valueDeclaration.parent.right)) { + // Looks like a `module.exports.Sub = class {}` - if we serialize `symbol` as a class, the result will have no members, + // since the classiness is actually from the target of the effective alias the symbol is. yes. A BlockScopedVariable|Class|Property + // _really_ acts like an Alias, and none of a BlockScopedVariable, Class, or Property. This is the travesty of JS binding today. serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); } - if (symbol.flags & SymbolFlags.Property && symbol.escapedName === InternalSymbolName.ExportEquals) { - serializeMaybeAliasAssignment(symbol); + else { + serializeAsClass(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); } - if (symbol.flags & SymbolFlags.ExportStar) { - // synthesize export * from "moduleReference" - // Straightforward - only one thing to do - make an export declaration - if (symbol.declarations) { - for (const node of symbol.declarations) { - const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); - if (!resolvedModule) continue; - addResult(factory.createExportDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); - } + } + if ((symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && (!isConstMergedWithNS || isTypeOnlyNamespace(symbol))) || isConstMergedWithNSPrintableAsSignatureMerge) { + serializeModule(symbol, symbolName, modifierFlags); + } + // The class meaning serialization should handle serializing all interface members + if (symbol.flags & SymbolFlags.Interface && !(symbol.flags & SymbolFlags.Class)) { + serializeInterface(symbol, symbolName, modifierFlags); + } + if (symbol.flags & SymbolFlags.Alias) { + serializeAsAlias(symbol, getInternalSymbolName(symbol, symbolName), modifierFlags); + } + if (symbol.flags & SymbolFlags.Property && symbol.escapedName === InternalSymbolName.ExportEquals) { + serializeMaybeAliasAssignment(symbol); + } + if (symbol.flags & SymbolFlags.ExportStar) { + // synthesize export * from "moduleReference" + // Straightforward - only one thing to do - make an export declaration + if (symbol.declarations) { + for (const node of symbol.declarations) { + const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); + if (!resolvedModule) continue; + addResult(factory.createExportDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); } } - if (needsPostExportDefault) { - addResult(factory.createExportAssignment(/*modifiers*/ undefined, /*isExportAssignment*/ false, factory.createIdentifier(getInternalSymbolName(symbol, symbolName))), ModifierFlags.None); + } + if (needsPostExportDefault) { + addResult(factory.createExportAssignment(/*modifiers*/ undefined, /*isExportAssignment*/ false, factory.createIdentifier(getInternalSymbolName(symbol, symbolName))), ModifierFlags.None); + } + else if (needsExportDeclaration) { + addResult(factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, getInternalSymbolName(symbol, symbolName), symbolName)]) + ), ModifierFlags.None); + } + } + + function includePrivateSymbol(symbol: Symbol) { + if (some(symbol.declarations, isParameterDeclaration)) return; + Debug.assertIsDefined(deferredPrivatesStack[deferredPrivatesStack.length - 1]); + getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol + // Blanket moving (import) aliases into the root private context should work, since imports are not valid within namespaces + // (so they must have been in the root to begin with if they were real imports) cjs `require` aliases (an upcoming feature) + // will throw a wrench in this, since those may have been nested, but we'll need to synthesize them in the outer scope + // anyway, as that's the only place the import they translate to is valid. In such a case, we might need to use a unique name + // for the moved import; which hopefully the above `getUnusedName` call should produce. + const isExternalImportAlias = !!(symbol.flags & SymbolFlags.Alias) && !some(symbol.declarations, d => + !!findAncestor(d, isExportDeclaration) || + isNamespaceExport(d) || + (isImportEqualsDeclaration(d) && !isExternalModuleReference(d.moduleReference)) + ); + deferredPrivatesStack[isExternalImportAlias ? 0 : (deferredPrivatesStack.length - 1)].set(getSymbolId(symbol), symbol); + } + + function isExportingScope(enclosingDeclaration: Node) { + return ((isSourceFile(enclosingDeclaration) && (isExternalOrCommonJsModule(enclosingDeclaration) || isJsonSourceFile(enclosingDeclaration))) || + (isAmbientModule(enclosingDeclaration) && !isGlobalScopeAugmentation(enclosingDeclaration))); + } + + // Prepends a `declare` and/or `export` modifier if the context requires it, and then adds `node` to `result` and returns `node` + function addResult(node: Statement, additionalModifierFlags: ModifierFlags) { + if (canHaveModifiers(node)) { + let newModifierFlags: ModifierFlags = ModifierFlags.None; + const enclosingDeclaration = context.enclosingDeclaration && + (isJSDocTypeAlias(context.enclosingDeclaration) ? getSourceFileOfNode(context.enclosingDeclaration) : context.enclosingDeclaration); + if (additionalModifierFlags & ModifierFlags.Export && + enclosingDeclaration && (isExportingScope(enclosingDeclaration) || isModuleDeclaration(enclosingDeclaration)) && + canHaveExportModifier(node) + ) { + // Classes, namespaces, variables, functions, interfaces, and types should all be `export`ed in a module context if not private + newModifierFlags |= ModifierFlags.Export; + } + if (addingDeclare && !(newModifierFlags & ModifierFlags.Export) && + (!enclosingDeclaration || !(enclosingDeclaration.flags & NodeFlags.Ambient)) && + (isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) || isModuleDeclaration(node))) { + // Classes, namespaces, variables, enums, and functions all need `declare` modifiers to be valid in a declaration file top-level scope + newModifierFlags |= ModifierFlags.Ambient; } - else if (needsExportDeclaration) { - addResult(factory.createExportDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, getInternalSymbolName(symbol, symbolName), symbolName)]) - ), ModifierFlags.None); + if ((additionalModifierFlags & ModifierFlags.Default) && (isClassDeclaration(node) || isInterfaceDeclaration(node) || isFunctionDeclaration(node))) { + newModifierFlags |= ModifierFlags.Default; + } + if (newModifierFlags) { + node = factory.updateModifiers(node, newModifierFlags | getEffectiveModifierFlags(node)); } } + results.push(node); + } - function includePrivateSymbol(symbol: Symbol) { - if (some(symbol.declarations, isParameterDeclaration)) return; - Debug.assertIsDefined(deferredPrivatesStack[deferredPrivatesStack.length - 1]); - getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol - // Blanket moving (import) aliases into the root private context should work, since imports are not valid within namespaces - // (so they must have been in the root to begin with if they were real imports) cjs `require` aliases (an upcoming feature) - // will throw a wrench in this, since those may have been nested, but we'll need to synthesize them in the outer scope - // anyway, as that's the only place the import they translate to is valid. In such a case, we might need to use a unique name - // for the moved import; which hopefully the above `getUnusedName` call should produce. - const isExternalImportAlias = !!(symbol.flags & SymbolFlags.Alias) && !some(symbol.declarations, d => - !!findAncestor(d, isExportDeclaration) || - isNamespaceExport(d) || - (isImportEqualsDeclaration(d) && !isExternalModuleReference(d.moduleReference)) - ); - deferredPrivatesStack[isExternalImportAlias ? 0 : (deferredPrivatesStack.length - 1)].set(getSymbolId(symbol), symbol); - } - - function isExportingScope(enclosingDeclaration: Node) { - return ((isSourceFile(enclosingDeclaration) && (isExternalOrCommonJsModule(enclosingDeclaration) || isJsonSourceFile(enclosingDeclaration))) || - (isAmbientModule(enclosingDeclaration) && !isGlobalScopeAugmentation(enclosingDeclaration))); - } - - // Prepends a `declare` and/or `export` modifier if the context requires it, and then adds `node` to `result` and returns `node` - function addResult(node: Statement, additionalModifierFlags: ModifierFlags) { - if (canHaveModifiers(node)) { - let newModifierFlags: ModifierFlags = ModifierFlags.None; - const enclosingDeclaration = context.enclosingDeclaration && - (isJSDocTypeAlias(context.enclosingDeclaration) ? getSourceFileOfNode(context.enclosingDeclaration) : context.enclosingDeclaration); - if (additionalModifierFlags & ModifierFlags.Export && - enclosingDeclaration && (isExportingScope(enclosingDeclaration) || isModuleDeclaration(enclosingDeclaration)) && - canHaveExportModifier(node) - ) { - // Classes, namespaces, variables, functions, interfaces, and types should all be `export`ed in a module context if not private - newModifierFlags |= ModifierFlags.Export; - } - if (addingDeclare && !(newModifierFlags & ModifierFlags.Export) && - (!enclosingDeclaration || !(enclosingDeclaration.flags & NodeFlags.Ambient)) && - (isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) || isModuleDeclaration(node))) { - // Classes, namespaces, variables, enums, and functions all need `declare` modifiers to be valid in a declaration file top-level scope - newModifierFlags |= ModifierFlags.Ambient; - } - if ((additionalModifierFlags & ModifierFlags.Default) && (isClassDeclaration(node) || isInterfaceDeclaration(node) || isFunctionDeclaration(node))) { - newModifierFlags |= ModifierFlags.Default; - } - if (newModifierFlags) { - node = factory.updateModifiers(node, newModifierFlags | getEffectiveModifierFlags(node)); - } - } - results.push(node); - } - - function serializeTypeAlias(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - const aliasType = getDeclaredTypeOfTypeAlias(symbol); - const typeParams = getSymbolLinks(symbol).typeParameters; - const typeParamDecls = map(typeParams, p => typeParameterToDeclaration(p, context)); - const jsdocAliasDecl = symbol.declarations?.find(isJSDocTypeAlias); - const commentText = getTextOfJSDocComment(jsdocAliasDecl ? jsdocAliasDecl.comment || jsdocAliasDecl.parent.comment : undefined); - const oldFlags = context.flags; - context.flags |= NodeBuilderFlags.InTypeAlias; - const oldEnclosingDecl = context.enclosingDeclaration; - context.enclosingDeclaration = jsdocAliasDecl; - const typeNode = jsdocAliasDecl && jsdocAliasDecl.typeExpression - && isJSDocTypeExpression(jsdocAliasDecl.typeExpression) - && serializeExistingTypeNode(context, jsdocAliasDecl.typeExpression.type, includePrivateSymbol, bundled) - || typeToTypeNodeHelper(aliasType, context); - addResult(setSyntheticLeadingComments( - factory.createTypeAliasDeclaration(/*modifiers*/ undefined, getInternalSymbolName(symbol, symbolName), typeParamDecls, typeNode), - !commentText ? [] : [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }] - ), modifierFlags); - context.flags = oldFlags; - context.enclosingDeclaration = oldEnclosingDecl; - } - - function serializeInterface(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - const interfaceType = getDeclaredTypeOfClassOrInterface(symbol); - const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); - const baseTypes = getBaseTypes(interfaceType); - const baseType = length(baseTypes) ? getIntersectionType(baseTypes) : undefined; - const members = flatMap(getPropertiesOfType(interfaceType), p => serializePropertySymbolForInterface(p, baseType)); - const callSignatures = serializeSignatures(SignatureKind.Call, interfaceType, baseType, SyntaxKind.CallSignature) as CallSignatureDeclaration[]; - const constructSignatures = serializeSignatures(SignatureKind.Construct, interfaceType, baseType, SyntaxKind.ConstructSignature) as ConstructSignatureDeclaration[]; - const indexSignatures = serializeIndexSignatures(interfaceType, baseType); - - const heritageClauses = !length(baseTypes) ? undefined : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, mapDefined(baseTypes, b => trySerializeAsTypeReference(b, SymbolFlags.Value)))]; - addResult(factory.createInterfaceDeclaration( + function serializeTypeAlias(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { + const aliasType = getDeclaredTypeOfTypeAlias(symbol); + const typeParams = getSymbolLinks(symbol).typeParameters; + const typeParamDecls = map(typeParams, p => typeParameterToDeclaration(p, context)); + const jsdocAliasDecl = symbol.declarations?.find(isJSDocTypeAlias); + const commentText = getTextOfJSDocComment(jsdocAliasDecl ? jsdocAliasDecl.comment || jsdocAliasDecl.parent.comment : undefined); + const oldFlags = context.flags; + context.flags |= NodeBuilderFlags.InTypeAlias; + const oldEnclosingDecl = context.enclosingDeclaration; + context.enclosingDeclaration = jsdocAliasDecl; + const typeNode = jsdocAliasDecl && jsdocAliasDecl.typeExpression + && isJSDocTypeExpression(jsdocAliasDecl.typeExpression) + && serializeExistingTypeNode(context, jsdocAliasDecl.typeExpression.type, includePrivateSymbol, bundled) + || typeToTypeNodeHelper(aliasType, context); + addResult(setSyntheticLeadingComments( + factory.createTypeAliasDeclaration(/*modifiers*/ undefined, getInternalSymbolName(symbol, symbolName), typeParamDecls, typeNode), + !commentText ? [] : [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }] + ), modifierFlags); + context.flags = oldFlags; + context.enclosingDeclaration = oldEnclosingDecl; + } + + function serializeInterface(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { + const interfaceType = getDeclaredTypeOfClassOrInterface(symbol); + const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); + const baseTypes = getBaseTypes(interfaceType); + const baseType = length(baseTypes) ? getIntersectionType(baseTypes) : undefined; + const members = flatMap(getPropertiesOfType(interfaceType), p => serializePropertySymbolForInterface(p, baseType)); + const callSignatures = serializeSignatures(SignatureKind.Call, interfaceType, baseType, SyntaxKind.CallSignature) as CallSignatureDeclaration[]; + const constructSignatures = serializeSignatures(SignatureKind.Construct, interfaceType, baseType, SyntaxKind.ConstructSignature) as ConstructSignatureDeclaration[]; + const indexSignatures = serializeIndexSignatures(interfaceType, baseType); + + const heritageClauses = !length(baseTypes) ? undefined : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, mapDefined(baseTypes, b => trySerializeAsTypeReference(b, SymbolFlags.Value)))]; + addResult(factory.createInterfaceDeclaration( + /*modifiers*/ undefined, + getInternalSymbolName(symbol, symbolName), + typeParamDecls, + heritageClauses, + [...indexSignatures, ...constructSignatures, ...callSignatures, ...members] + ), modifierFlags); + } + + function getNamespaceMembersForSerialization(symbol: Symbol) { + return !symbol.exports ? [] : filter(arrayFrom(symbol.exports.values()), isNamespaceMember); + } + + function isTypeOnlyNamespace(symbol: Symbol) { + return every(getNamespaceMembersForSerialization(symbol), m => !(getAllSymbolFlags(resolveSymbol(m)) & SymbolFlags.Value)); + } + + function serializeModule(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { + const members = getNamespaceMembersForSerialization(symbol); + // Split NS members up by declaration - members whose parent symbol is the ns symbol vs those whose is not (but were added in later via merging) + const locationMap = arrayToMultiMap(members, m => m.parent && m.parent === symbol ? "real" : "merged"); + const realMembers = locationMap.get("real") || emptyArray; + const mergedMembers = locationMap.get("merged") || emptyArray; + // TODO: `suppressNewPrivateContext` is questionable -we need to simply be emitting privates in whatever scope they were declared in, rather + // than whatever scope we traverse to them in. That's a bit of a complex rewrite, since we're not _actually_ tracking privates at all in advance, + // so we don't even have placeholders to fill in. + if (length(realMembers)) { + const localName = getInternalSymbolName(symbol, symbolName); + serializeAsNamespaceDeclaration(realMembers, localName, modifierFlags, !!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Assignment))); + } + if (length(mergedMembers)) { + const containingFile = getSourceFileOfNode(context.enclosingDeclaration); + const localName = getInternalSymbolName(symbol, symbolName); + const nsBody = factory.createModuleBlock([factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(mapDefined(filter(mergedMembers, n => n.escapedName !== InternalSymbolName.ExportEquals), s => { + const name = unescapeLeadingUnderscores(s.escapedName); + const localName = getInternalSymbolName(s, name); + const aliasDecl = s.declarations && getDeclarationOfAliasSymbol(s); + if (containingFile && (aliasDecl ? containingFile !== getSourceFileOfNode(aliasDecl) : !some(s.declarations, d => getSourceFileOfNode(d) === containingFile))) { + context.tracker?.reportNonlocalAugmentation?.(containingFile, symbol, s); + return undefined; + } + const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); + includePrivateSymbol(target || s); + const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName; + return factory.createExportSpecifier(/*isTypeOnly*/ false, name === targetName ? undefined : targetName, name); + })) + )]); + addResult(factory.createModuleDeclaration( /*modifiers*/ undefined, - getInternalSymbolName(symbol, symbolName), - typeParamDecls, - heritageClauses, - [...indexSignatures, ...constructSignatures, ...callSignatures, ...members] - ), modifierFlags); + factory.createIdentifier(localName), + nsBody, + NodeFlags.Namespace + ), ModifierFlags.None); } + } - function getNamespaceMembersForSerialization(symbol: Symbol) { - return !symbol.exports ? [] : filter(arrayFrom(symbol.exports.values()), isNamespaceMember); - } + function serializeEnum(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { + addResult(factory.createEnumDeclaration( + factory.createModifiersFromModifierFlags(isConstEnumSymbol(symbol) ? ModifierFlags.Const : 0), + getInternalSymbolName(symbol, symbolName), + map(filter(getPropertiesOfType(getTypeOfSymbol(symbol)), p => !!(p.flags & SymbolFlags.EnumMember)), p => { + // TODO: Handle computed names + // I hate that to get the initialized value we need to walk back to the declarations here; but there's no + // other way to get the possible const value of an enum member that I'm aware of, as the value is cached + // _on the declaration_, not on the declaration's symbol... + const initializedValue = p.declarations && p.declarations[0] && isEnumMember(p.declarations[0]) ? getConstantValue(p.declarations[0]) : undefined; + return factory.createEnumMember(unescapeLeadingUnderscores(p.escapedName), initializedValue === undefined ? undefined : + typeof initializedValue === "string" ? factory.createStringLiteral(initializedValue) : + factory.createNumericLiteral(initializedValue)); + }) + ), modifierFlags); + } - function isTypeOnlyNamespace(symbol: Symbol) { - return every(getNamespaceMembersForSerialization(symbol), m => !(getAllSymbolFlags(resolveSymbol(m)) & SymbolFlags.Value)); + function serializeAsFunctionNamespaceMerge(type: Type, symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { + const signatures = getSignaturesOfType(type, SignatureKind.Call); + for (const sig of signatures) { + // Each overload becomes a separate function declaration, in order + const decl = signatureToSignatureDeclarationHelper(sig, SyntaxKind.FunctionDeclaration, context, { name: factory.createIdentifier(localName), privateSymbolVisitor: includePrivateSymbol, bundledImports: bundled }) as FunctionDeclaration; + addResult(setTextRange(decl, getSignatureTextRangeLocation(sig)), modifierFlags); } + // Module symbol emit will take care of module-y members, provided it has exports + if (!(symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && !!symbol.exports && !!symbol.exports.size)) { + const props = filter(getPropertiesOfType(type), isNamespaceMember); + serializeAsNamespaceDeclaration(props, localName, modifierFlags, /*suppressNewPrivateContext*/ true); + } + } - function serializeModule(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - const members = getNamespaceMembersForSerialization(symbol); - // Split NS members up by declaration - members whose parent symbol is the ns symbol vs those whose is not (but were added in later via merging) - const locationMap = arrayToMultiMap(members, m => m.parent && m.parent === symbol ? "real" : "merged"); - const realMembers = locationMap.get("real") || emptyArray; - const mergedMembers = locationMap.get("merged") || emptyArray; - // TODO: `suppressNewPrivateContext` is questionable -we need to simply be emitting privates in whatever scope they were declared in, rather - // than whatever scope we traverse to them in. That's a bit of a complex rewrite, since we're not _actually_ tracking privates at all in advance, - // so we don't even have placeholders to fill in. - if (length(realMembers)) { - const localName = getInternalSymbolName(symbol, symbolName); - serializeAsNamespaceDeclaration(realMembers, localName, modifierFlags, !!(symbol.flags & (SymbolFlags.Function | SymbolFlags.Assignment))); + function getSignatureTextRangeLocation(signature: Signature) { + if (signature.declaration && signature.declaration.parent) { + if (isBinaryExpression(signature.declaration.parent) && getAssignmentDeclarationKind(signature.declaration.parent) === AssignmentDeclarationKind.Property) { + return signature.declaration.parent; } - if (length(mergedMembers)) { - const containingFile = getSourceFileOfNode(context.enclosingDeclaration); - const localName = getInternalSymbolName(symbol, symbolName); - const nsBody = factory.createModuleBlock([factory.createExportDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports(mapDefined(filter(mergedMembers, n => n.escapedName !== InternalSymbolName.ExportEquals), s => { - const name = unescapeLeadingUnderscores(s.escapedName); - const localName = getInternalSymbolName(s, name); - const aliasDecl = s.declarations && getDeclarationOfAliasSymbol(s); - if (containingFile && (aliasDecl ? containingFile !== getSourceFileOfNode(aliasDecl) : !some(s.declarations, d => getSourceFileOfNode(d) === containingFile))) { - context.tracker?.reportNonlocalAugmentation?.(containingFile, symbol, s); - return undefined; - } - const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); - includePrivateSymbol(target || s); - const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName; - return factory.createExportSpecifier(/*isTypeOnly*/ false, name === targetName ? undefined : targetName, name); - })) - )]); - addResult(factory.createModuleDeclaration( - /*modifiers*/ undefined, - factory.createIdentifier(localName), - nsBody, - NodeFlags.Namespace - ), ModifierFlags.None); + // for expressions assigned to `var`s, use the `var` as the text range + if (isVariableDeclaration(signature.declaration.parent) && signature.declaration.parent.parent) { + return signature.declaration.parent.parent; } } + return signature.declaration; + } - function serializeEnum(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { - addResult(factory.createEnumDeclaration( - factory.createModifiersFromModifierFlags(isConstEnumSymbol(symbol) ? ModifierFlags.Const : 0), - getInternalSymbolName(symbol, symbolName), - map(filter(getPropertiesOfType(getTypeOfSymbol(symbol)), p => !!(p.flags & SymbolFlags.EnumMember)), p => { - // TODO: Handle computed names - // I hate that to get the initialized value we need to walk back to the declarations here; but there's no - // other way to get the possible const value of an enum member that I'm aware of, as the value is cached - // _on the declaration_, not on the declaration's symbol... - const initializedValue = p.declarations && p.declarations[0] && isEnumMember(p.declarations[0]) ? getConstantValue(p.declarations[0]) : undefined; - return factory.createEnumMember(unescapeLeadingUnderscores(p.escapedName), initializedValue === undefined ? undefined : - typeof initializedValue === "string" ? factory.createStringLiteral(initializedValue) : - factory.createNumericLiteral(initializedValue)); - }) - ), modifierFlags); + function serializeAsNamespaceDeclaration(props: readonly Symbol[], localName: string, modifierFlags: ModifierFlags, suppressNewPrivateContext: boolean) { + if (length(props)) { + const localVsRemoteMap = arrayToMultiMap(props, p => + !length(p.declarations) || some(p.declarations, d => + getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!) + ) ? "local" : "remote" + ); + const localProps = localVsRemoteMap.get("local") || emptyArray; + // handle remote props first - we need to make an `import` declaration that points at the module containing each remote + // prop in the outermost scope (TODO: a namespace within a namespace would need to be appropriately handled by this) + // Example: + // import Foo_1 = require("./exporter"); + // export namespace ns { + // import Foo = Foo_1.Foo; + // export { Foo }; + // export const c: number; + // } + // This is needed because in JS, statements like `const x = require("./f")` support both type and value lookup, even if they're + // normally just value lookup (so it functions kinda like an alias even when it's not an alias) + // _Usually_, we'll simply print the top-level as an alias instead of a `var` in such situations, however is is theoretically + // possible to encounter a situation where a type has members from both the current file and other files - in those situations, + // emit akin to the above would be needed. + + // Add a namespace + // Create namespace as non-synthetic so it is usable as an enclosing declaration + let fakespace = parseNodeFactory.createModuleDeclaration(/*modifiers*/ undefined, factory.createIdentifier(localName), factory.createModuleBlock([]), NodeFlags.Namespace); + setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration); + fakespace.locals = createSymbolTable(props); + fakespace.symbol = props[0].parent!; + + const oldResults = results; + results = []; + const oldAddingDeclare = addingDeclare; + addingDeclare = false; + const subcontext = { ...context, enclosingDeclaration: fakespace }; + const oldContext = context; + context = subcontext; + // TODO: implement handling for the localVsRemoteMap.get("remote") - should be difficult to trigger (see comment above), as only interesting cross-file js merges should make this possible + visitSymbolTable(createSymbolTable(localProps), suppressNewPrivateContext, /*propertyAsAlias*/ true); + context = oldContext; + addingDeclare = oldAddingDeclare; + const declarations = results; + results = oldResults; + // replace namespace with synthetic version + const defaultReplaced = map(declarations, d => isExportAssignment(d) && !d.isExportEquals && isIdentifier(d.expression) ? factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, d.expression, factory.createIdentifier(InternalSymbolName.Default))]) + ) : d); + const exportModifierStripped = every(defaultReplaced, d => hasSyntacticModifier(d, ModifierFlags.Export)) ? map(defaultReplaced, removeExportModifier) : defaultReplaced; + fakespace = factory.updateModuleDeclaration( + fakespace, + fakespace.modifiers, + fakespace.name, + factory.createModuleBlock(exportModifierStripped)); + addResult(fakespace, modifierFlags); // namespaces can never be default exported } + } - function serializeAsFunctionNamespaceMerge(type: Type, symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { - const signatures = getSignaturesOfType(type, SignatureKind.Call); - for (const sig of signatures) { - // Each overload becomes a separate function declaration, in order - const decl = signatureToSignatureDeclarationHelper(sig, SyntaxKind.FunctionDeclaration, context, { name: factory.createIdentifier(localName), privateSymbolVisitor: includePrivateSymbol, bundledImports: bundled }) as FunctionDeclaration; - addResult(setTextRange(decl, getSignatureTextRangeLocation(sig)), modifierFlags); - } - // Module symbol emit will take care of module-y members, provided it has exports - if (!(symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && !!symbol.exports && !!symbol.exports.size)) { - const props = filter(getPropertiesOfType(type), isNamespaceMember); - serializeAsNamespaceDeclaration(props, localName, modifierFlags, /*suppressNewPrivateContext*/ true); - } - } + function isNamespaceMember(p: Symbol) { + return !!(p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias)) || + !(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && isStatic(p.valueDeclaration) && isClassLike(p.valueDeclaration.parent)); + } - function getSignatureTextRangeLocation(signature: Signature) { - if (signature.declaration && signature.declaration.parent) { - if (isBinaryExpression(signature.declaration.parent) && getAssignmentDeclarationKind(signature.declaration.parent) === AssignmentDeclarationKind.Property) { - return signature.declaration.parent; + function sanitizeJSDocImplements(clauses: readonly ExpressionWithTypeArguments[]): ExpressionWithTypeArguments[] | undefined { + const result = mapDefined(clauses, e => { + const oldEnclosing = context.enclosingDeclaration; + context.enclosingDeclaration = e; + let expr = e.expression; + if (isEntityNameExpression(expr)) { + if (isIdentifier(expr) && idText(expr) === "") { + return cleanup(/*result*/ undefined); // Empty heritage clause, should be an error, but prefer emitting no heritage clauses to reemitting the empty one } - // for expressions assigned to `var`s, use the `var` as the text range - if (isVariableDeclaration(signature.declaration.parent) && signature.declaration.parent.parent) { - return signature.declaration.parent.parent; + let introducesError: boolean; + ({ introducesError, node: expr } = trackExistingEntityName(expr, context, includePrivateSymbol)); + if (introducesError) { + return cleanup(/*result*/ undefined); } } - return signature.declaration; - } - - function serializeAsNamespaceDeclaration(props: readonly Symbol[], localName: string, modifierFlags: ModifierFlags, suppressNewPrivateContext: boolean) { - if (length(props)) { - const localVsRemoteMap = arrayToMultiMap(props, p => - !length(p.declarations) || some(p.declarations, d => - getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!) - ) ? "local" : "remote" - ); - const localProps = localVsRemoteMap.get("local") || emptyArray; - // handle remote props first - we need to make an `import` declaration that points at the module containing each remote - // prop in the outermost scope (TODO: a namespace within a namespace would need to be appropriately handled by this) - // Example: - // import Foo_1 = require("./exporter"); - // export namespace ns { - // import Foo = Foo_1.Foo; - // export { Foo }; - // export const c: number; - // } - // This is needed because in JS, statements like `const x = require("./f")` support both type and value lookup, even if they're - // normally just value lookup (so it functions kinda like an alias even when it's not an alias) - // _Usually_, we'll simply print the top-level as an alias instead of a `var` in such situations, however is is theoretically - // possible to encounter a situation where a type has members from both the current file and other files - in those situations, - // emit akin to the above would be needed. - - // Add a namespace - // Create namespace as non-synthetic so it is usable as an enclosing declaration - let fakespace = parseNodeFactory.createModuleDeclaration(/*modifiers*/ undefined, factory.createIdentifier(localName), factory.createModuleBlock([]), NodeFlags.Namespace); - setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration); - fakespace.locals = createSymbolTable(props); - fakespace.symbol = props[0].parent!; - - const oldResults = results; - results = []; - const oldAddingDeclare = addingDeclare; - addingDeclare = false; - const subcontext = { ...context, enclosingDeclaration: fakespace }; - const oldContext = context; - context = subcontext; - // TODO: implement handling for the localVsRemoteMap.get("remote") - should be difficult to trigger (see comment above), as only interesting cross-file js merges should make this possible - visitSymbolTable(createSymbolTable(localProps), suppressNewPrivateContext, /*propertyAsAlias*/ true); - context = oldContext; - addingDeclare = oldAddingDeclare; - const declarations = results; - results = oldResults; - // replace namespace with synthetic version - const defaultReplaced = map(declarations, d => isExportAssignment(d) && !d.isExportEquals && isIdentifier(d.expression) ? factory.createExportDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, d.expression, factory.createIdentifier(InternalSymbolName.Default))]) - ) : d); - const exportModifierStripped = every(defaultReplaced, d => hasSyntacticModifier(d, ModifierFlags.Export)) ? map(defaultReplaced, removeExportModifier) : defaultReplaced; - fakespace = factory.updateModuleDeclaration( - fakespace, - fakespace.modifiers, - fakespace.name, - factory.createModuleBlock(exportModifierStripped)); - addResult(fakespace, modifierFlags); // namespaces can never be default exported - } - } - - function isNamespaceMember(p: Symbol) { - return !!(p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias)) || - !(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && isStatic(p.valueDeclaration) && isClassLike(p.valueDeclaration.parent)); - } - - function sanitizeJSDocImplements(clauses: readonly ExpressionWithTypeArguments[]): ExpressionWithTypeArguments[] | undefined { - const result = mapDefined(clauses, e => { - const oldEnclosing = context.enclosingDeclaration; - context.enclosingDeclaration = e; - let expr = e.expression; - if (isEntityNameExpression(expr)) { - if (isIdentifier(expr) && idText(expr) === "") { - return cleanup(/*result*/ undefined); // Empty heritage clause, should be an error, but prefer emitting no heritage clauses to reemitting the empty one - } - let introducesError: boolean; - ({ introducesError, node: expr } = trackExistingEntityName(expr, context, includePrivateSymbol)); - if (introducesError) { - return cleanup(/*result*/ undefined); - } - } - return cleanup(factory.createExpressionWithTypeArguments(expr, - map(e.typeArguments, a => - serializeExistingTypeNode(context, a, includePrivateSymbol, bundled) - || typeToTypeNodeHelper(getTypeFromTypeNode(a), context) - ) - )); + return cleanup(factory.createExpressionWithTypeArguments(expr, + map(e.typeArguments, a => + serializeExistingTypeNode(context, a, includePrivateSymbol, bundled) + || typeToTypeNodeHelper(getTypeFromTypeNode(a), context) + ) + )); - function cleanup(result: T): T { - context.enclosingDeclaration = oldEnclosing; - return result; - } - }); - if (result.length === clauses.length) { + function cleanup(result: T): T { + context.enclosingDeclaration = oldEnclosing; return result; } - return undefined; + }); + if (result.length === clauses.length) { + return result; } + return undefined; + } - function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { - const originalDecl = symbol.declarations?.find(isClassLike); - const oldEnclosing = context.enclosingDeclaration; - context.enclosingDeclaration = originalDecl || oldEnclosing; - const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); - const classType = getDeclaredTypeOfClassOrInterface(symbol); - const baseTypes = getBaseTypes(classType); - const originalImplements = originalDecl && getEffectiveImplementsTypeNodes(originalDecl); - const implementsExpressions = originalImplements && sanitizeJSDocImplements(originalImplements) - || mapDefined(getImplementsTypes(classType), serializeImplementedType); - const staticType = getTypeOfSymbol(symbol); - const isClass = !!staticType.symbol?.valueDeclaration && isClassLike(staticType.symbol.valueDeclaration); - const staticBaseType = isClass - ? getBaseConstructorTypeOfClass(staticType as InterfaceType) - : anyType; - const heritageClauses = [ - ...!length(baseTypes) ? [] : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))], - ...!length(implementsExpressions) ? [] : [factory.createHeritageClause(SyntaxKind.ImplementsKeyword, implementsExpressions)] - ]; - const symbolProps = getNonInheritedProperties(classType, baseTypes, getPropertiesOfType(classType)); - const publicSymbolProps = filter(symbolProps, s => { - // `valueDeclaration` could be undefined if inherited from - // a union/intersection base type, but inherited properties - // don't matter here. - const valueDecl = s.valueDeclaration; - return !!valueDecl && !(isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name)); - }); - const hasPrivateIdentifier = some(symbolProps, s => { - // `valueDeclaration` could be undefined if inherited from - // a union/intersection base type, but inherited properties - // don't matter here. - const valueDecl = s.valueDeclaration; - return !!valueDecl && isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name); - }); - // Boil down all private properties into a single one. - const privateProperties = hasPrivateIdentifier ? - [factory.createPropertyDeclaration( - /*modifiers*/ undefined, - factory.createPrivateIdentifier("#private"), - /*questionOrExclamationToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined, - )] : - emptyArray; - const publicProperties = flatMap(publicSymbolProps, p => serializePropertySymbolForClass(p, /*isStatic*/ false, baseTypes[0])); - // Consider static members empty if symbol also has function or module meaning - function namespacey emit will handle statics - const staticMembers = flatMap( - filter(getPropertiesOfType(staticType), p => !(p.flags & SymbolFlags.Prototype) && p.escapedName !== "prototype" && !isNamespaceMember(p)), - p => serializePropertySymbolForClass(p, /*isStatic*/ true, staticBaseType)); - // When we encounter an `X.prototype.y` assignment in a JS file, we bind `X` as a class regardless as to whether - // the value is ever initialized with a class or function-like value. For cases where `X` could never be - // created via `new`, we will inject a `private constructor()` declaration to indicate it is not createable. - const isNonConstructableClassLikeInJsFile = - !isClass && - !!symbol.valueDeclaration && - isInJSFile(symbol.valueDeclaration) && - !some(getSignaturesOfType(staticType, SignatureKind.Construct)); - const constructors = isNonConstructableClassLikeInJsFile ? - [factory.createConstructorDeclaration(factory.createModifiersFromModifierFlags(ModifierFlags.Private), [], /*body*/ undefined)] : - serializeSignatures(SignatureKind.Construct, staticType, staticBaseType, SyntaxKind.Constructor) as ConstructorDeclaration[]; - const indexSignatures = serializeIndexSignatures(classType, baseTypes[0]); - context.enclosingDeclaration = oldEnclosing; - addResult(setTextRange(factory.createClassDeclaration( + function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { + const originalDecl = symbol.declarations?.find(isClassLike); + const oldEnclosing = context.enclosingDeclaration; + context.enclosingDeclaration = originalDecl || oldEnclosing; + const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); + const classType = getDeclaredTypeOfClassOrInterface(symbol); + const baseTypes = getBaseTypes(classType); + const originalImplements = originalDecl && getEffectiveImplementsTypeNodes(originalDecl); + const implementsExpressions = originalImplements && sanitizeJSDocImplements(originalImplements) + || mapDefined(getImplementsTypes(classType), serializeImplementedType); + const staticType = getTypeOfSymbol(symbol); + const isClass = !!staticType.symbol?.valueDeclaration && isClassLike(staticType.symbol.valueDeclaration); + const staticBaseType = isClass + ? getBaseConstructorTypeOfClass(staticType as InterfaceType) + : anyType; + const heritageClauses = [ + ...!length(baseTypes) ? [] : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))], + ...!length(implementsExpressions) ? [] : [factory.createHeritageClause(SyntaxKind.ImplementsKeyword, implementsExpressions)] + ]; + const symbolProps = getNonInheritedProperties(classType, baseTypes, getPropertiesOfType(classType)); + const publicSymbolProps = filter(symbolProps, s => { + // `valueDeclaration` could be undefined if inherited from + // a union/intersection base type, but inherited properties + // don't matter here. + const valueDecl = s.valueDeclaration; + return !!valueDecl && !(isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name)); + }); + const hasPrivateIdentifier = some(symbolProps, s => { + // `valueDeclaration` could be undefined if inherited from + // a union/intersection base type, but inherited properties + // don't matter here. + const valueDecl = s.valueDeclaration; + return !!valueDecl && isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name); + }); + // Boil down all private properties into a single one. + const privateProperties = hasPrivateIdentifier ? + [factory.createPropertyDeclaration( /*modifiers*/ undefined, - localName, - typeParamDecls, - heritageClauses, - [...indexSignatures, ...staticMembers, ...constructors, ...publicProperties, ...privateProperties] - ), symbol.declarations && filter(symbol.declarations, d => isClassDeclaration(d) || isClassExpression(d))[0]), modifierFlags); - } - - function getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) { - return firstDefined(declarations, d => { - if (isImportSpecifier(d) || isExportSpecifier(d)) { - return idText(d.propertyName || d.name); - } - if (isBinaryExpression(d) || isExportAssignment(d)) { - const expression = isExportAssignment(d) ? d.expression : d.right; - if (isPropertyAccessExpression(expression)) { - return idText(expression.name); - } - } - if (isAliasSymbolDeclaration(d)) { - // This is... heuristic, at best. But it's probably better than always printing the name of the shorthand ambient module. - const name = getNameOfDeclaration(d); - if (name && isIdentifier(name)) { - return idText(name); - } - } - return undefined; - }); - } + factory.createPrivateIdentifier("#private"), + /*questionOrExclamationToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined, + )] : + emptyArray; + const publicProperties = flatMap(publicSymbolProps, p => serializePropertySymbolForClass(p, /*isStatic*/ false, baseTypes[0])); + // Consider static members empty if symbol also has function or module meaning - function namespacey emit will handle statics + const staticMembers = flatMap( + filter(getPropertiesOfType(staticType), p => !(p.flags & SymbolFlags.Prototype) && p.escapedName !== "prototype" && !isNamespaceMember(p)), + p => serializePropertySymbolForClass(p, /*isStatic*/ true, staticBaseType)); + // When we encounter an `X.prototype.y` assignment in a JS file, we bind `X` as a class regardless as to whether + // the value is ever initialized with a class or function-like value. For cases where `X` could never be + // created via `new`, we will inject a `private constructor()` declaration to indicate it is not createable. + const isNonConstructableClassLikeInJsFile = + !isClass && + !!symbol.valueDeclaration && + isInJSFile(symbol.valueDeclaration) && + !some(getSignaturesOfType(staticType, SignatureKind.Construct)); + const constructors = isNonConstructableClassLikeInJsFile ? + [factory.createConstructorDeclaration(factory.createModifiersFromModifierFlags(ModifierFlags.Private), [], /*body*/ undefined)] : + serializeSignatures(SignatureKind.Construct, staticType, staticBaseType, SyntaxKind.Constructor) as ConstructorDeclaration[]; + const indexSignatures = serializeIndexSignatures(classType, baseTypes[0]); + context.enclosingDeclaration = oldEnclosing; + addResult(setTextRange(factory.createClassDeclaration( + /*modifiers*/ undefined, + localName, + typeParamDecls, + heritageClauses, + [...indexSignatures, ...staticMembers, ...constructors, ...publicProperties, ...privateProperties] + ), symbol.declarations && filter(symbol.declarations, d => isClassDeclaration(d) || isClassExpression(d))[0]), modifierFlags); + } - function serializeAsAlias(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { - // synthesize an alias, eg `export { symbolName as Name }` - // need to mark the alias `symbol` points at - // as something we need to serialize as a private declaration as well - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - const target = getMergedSymbol(getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true)); - if (!target) { - return; + function getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) { + return firstDefined(declarations, d => { + if (isImportSpecifier(d) || isExportSpecifier(d)) { + return idText(d.propertyName || d.name); } - // If `target` refers to a shorthand module symbol, the name we're trying to pull out isn;t recoverable from the target symbol - // In such a scenario, we must fall back to looking for an alias declaration on `symbol` and pulling the target name from that - let verbatimTargetName = isShorthandAmbientModuleSymbol(target) && getSomeTargetNameFromDeclarations(symbol.declarations) || unescapeLeadingUnderscores(target.escapedName); - if (verbatimTargetName === InternalSymbolName.ExportEquals && (getESModuleInterop(compilerOptions) || compilerOptions.allowSyntheticDefaultImports)) { - // target refers to an `export=` symbol that was hoisted into a synthetic default - rename here to match - verbatimTargetName = InternalSymbolName.Default; + if (isBinaryExpression(d) || isExportAssignment(d)) { + const expression = isExportAssignment(d) ? d.expression : d.right; + if (isPropertyAccessExpression(expression)) { + return idText(expression.name); + } } - const targetName = getInternalSymbolName(target, verbatimTargetName); - includePrivateSymbol(target); // the target may be within the same scope - attempt to serialize it first - switch (node.kind) { - case SyntaxKind.BindingElement: - if (node.parent?.parent?.kind === SyntaxKind.VariableDeclaration) { - // const { SomeClass } = require('./lib'); - const specifier = getSpecifierForModuleSymbol(target.parent || target, context); // './lib' - const { propertyName } = node as BindingElement; - addResult(factory.createImportDeclaration( - /*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamedImports([factory.createImportSpecifier( - /*isTypeOnly*/ false, - propertyName && isIdentifier(propertyName) ? factory.createIdentifier(idText(propertyName)) : undefined, - factory.createIdentifier(localName) - )])), - factory.createStringLiteral(specifier), - /*importClause*/ undefined - ), ModifierFlags.None); - break; - } - // We don't know how to serialize this (nested?) binding element - Debug.failBadSyntaxKind(node.parent?.parent || node, "Unhandled binding element grandparent kind in declaration serialization"); - break; - case SyntaxKind.ShorthandPropertyAssignment: - if (node.parent?.parent?.kind === SyntaxKind.BinaryExpression) { - // module.exports = { SomeClass } - serializeExportSpecifier( - unescapeLeadingUnderscores(symbol.escapedName), - targetName - ); - } - break; - case SyntaxKind.VariableDeclaration: - // commonjs require: const x = require('y') - if (isPropertyAccessExpression((node as VariableDeclaration).initializer!)) { - // const x = require('y').z - const initializer = (node as VariableDeclaration).initializer! as PropertyAccessExpression; // require('y').z - const uniqueName = factory.createUniqueName(localName); // _x - const specifier = getSpecifierForModuleSymbol(target.parent || target, context); // 'y' - // import _x = require('y'); - addResult(factory.createImportEqualsDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - uniqueName, - factory.createExternalModuleReference(factory.createStringLiteral(specifier)) - ), ModifierFlags.None); - // import x = _x.z - addResult(factory.createImportEqualsDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createIdentifier(localName), - factory.createQualifiedName(uniqueName, initializer.name as Identifier), - ), modifierFlags); - break; - } - // else fall through and treat commonjs require just like import= - case SyntaxKind.ImportEqualsDeclaration: - // This _specifically_ only exists to handle json declarations - where we make aliases, but since - // we emit no declarations for the json document, must not refer to it in the declarations - if (target.escapedName === InternalSymbolName.ExportEquals && some(target.declarations, isJsonSourceFile)) { - serializeMaybeAliasAssignment(symbol); - break; - } - // Could be a local `import localName = ns.member` or - // an external `import localName = require("whatever")` - const isLocalImport = !(target.flags & SymbolFlags.ValueModule) && !isVariableDeclaration(node); - addResult(factory.createImportEqualsDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createIdentifier(localName), - isLocalImport - ? symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) - : factory.createExternalModuleReference(factory.createStringLiteral(getSpecifierForModuleSymbol(target, context))) - ), isLocalImport ? modifierFlags : ModifierFlags.None); - break; - case SyntaxKind.NamespaceExportDeclaration: - // export as namespace foo - // TODO: Not part of a file's local or export symbol tables - // Is bound into file.symbol.globalExports instead, which we don't currently traverse - addResult(factory.createNamespaceExportDeclaration(idText((node as NamespaceExportDeclaration).name)), ModifierFlags.None); - break; - case SyntaxKind.ImportClause: - addResult(factory.createImportDeclaration( - /*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, factory.createIdentifier(localName), /*namedBindings*/ undefined), - // We use `target.parent || target` below as `target.parent` is unset when the target is a module which has been export assigned - // And then made into a default by the `esModuleInterop` or `allowSyntheticDefaultImports` flag - // In such cases, the `target` refers to the module itself already - factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)), - /*assertClause*/ undefined - ), ModifierFlags.None); - break; - case SyntaxKind.NamespaceImport: - addResult(factory.createImportDeclaration( - /*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, /*importClause*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))), - factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)), - /*assertClause*/ undefined - ), ModifierFlags.None); - break; - case SyntaxKind.NamespaceExport: - addResult(factory.createExportDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamespaceExport(factory.createIdentifier(localName)), - factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)) - ), ModifierFlags.None); - break; - case SyntaxKind.ImportSpecifier: + if (isAliasSymbolDeclaration(d)) { + // This is... heuristic, at best. But it's probably better than always printing the name of the shorthand ambient module. + const name = getNameOfDeclaration(d); + if (name && isIdentifier(name)) { + return idText(name); + } + } + return undefined; + }); + } + + function serializeAsAlias(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { + // synthesize an alias, eg `export { symbolName as Name }` + // need to mark the alias `symbol` points at + // as something we need to serialize as a private declaration as well + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) return Debug.fail(); + const target = getMergedSymbol(getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true)); + if (!target) { + return; + } + // If `target` refers to a shorthand module symbol, the name we're trying to pull out isn;t recoverable from the target symbol + // In such a scenario, we must fall back to looking for an alias declaration on `symbol` and pulling the target name from that + let verbatimTargetName = isShorthandAmbientModuleSymbol(target) && getSomeTargetNameFromDeclarations(symbol.declarations) || unescapeLeadingUnderscores(target.escapedName); + if (verbatimTargetName === InternalSymbolName.ExportEquals && (getESModuleInterop(compilerOptions) || compilerOptions.allowSyntheticDefaultImports)) { + // target refers to an `export=` symbol that was hoisted into a synthetic default - rename here to match + verbatimTargetName = InternalSymbolName.Default; + } + const targetName = getInternalSymbolName(target, verbatimTargetName); + includePrivateSymbol(target); // the target may be within the same scope - attempt to serialize it first + switch (node.kind) { + case SyntaxKind.BindingElement: + if (node.parent?.parent?.kind === SyntaxKind.VariableDeclaration) { + // const { SomeClass } = require('./lib'); + const specifier = getSpecifierForModuleSymbol(target.parent || target, context); // './lib' + const { propertyName } = node as BindingElement; addResult(factory.createImportDeclaration( /*modifiers*/ undefined, - factory.createImportClause( + factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamedImports([factory.createImportSpecifier( /*isTypeOnly*/ false, - /*importClause*/ undefined, - factory.createNamedImports([ - factory.createImportSpecifier( - /*isTypeOnly*/ false, - localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined, - factory.createIdentifier(localName) - ) - ])), - factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)), - /*assertClause*/ undefined + propertyName && isIdentifier(propertyName) ? factory.createIdentifier(idText(propertyName)) : undefined, + factory.createIdentifier(localName) + )])), + factory.createStringLiteral(specifier), + /*importClause*/ undefined ), ModifierFlags.None); break; - case SyntaxKind.ExportSpecifier: - // does not use localName because the symbol name in this case refers to the name in the exports table, - // which we must exactly preserve - const specifier = (node.parent.parent as ExportDeclaration).moduleSpecifier; - // targetName is only used when the target is local, as otherwise the target is an alias that points at - // another file + } + // We don't know how to serialize this (nested?) binding element + Debug.failBadSyntaxKind(node.parent?.parent || node, "Unhandled binding element grandparent kind in declaration serialization"); + break; + case SyntaxKind.ShorthandPropertyAssignment: + if (node.parent?.parent?.kind === SyntaxKind.BinaryExpression) { + // module.exports = { SomeClass } serializeExportSpecifier( unescapeLeadingUnderscores(symbol.escapedName), - specifier ? verbatimTargetName : targetName, - specifier && isStringLiteralLike(specifier) ? factory.createStringLiteral(specifier.text) : undefined + targetName ); + } + break; + case SyntaxKind.VariableDeclaration: + // commonjs require: const x = require('y') + if (isPropertyAccessExpression((node as VariableDeclaration).initializer!)) { + // const x = require('y').z + const initializer = (node as VariableDeclaration).initializer! as PropertyAccessExpression; // require('y').z + const uniqueName = factory.createUniqueName(localName); // _x + const specifier = getSpecifierForModuleSymbol(target.parent || target, context); // 'y' + // import _x = require('y'); + addResult(factory.createImportEqualsDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + uniqueName, + factory.createExternalModuleReference(factory.createStringLiteral(specifier)) + ), ModifierFlags.None); + // import x = _x.z + addResult(factory.createImportEqualsDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createIdentifier(localName), + factory.createQualifiedName(uniqueName, initializer.name as Identifier), + ), modifierFlags); break; - case SyntaxKind.ExportAssignment: + } + // else fall through and treat commonjs require just like import= + case SyntaxKind.ImportEqualsDeclaration: + // This _specifically_ only exists to handle json declarations - where we make aliases, but since + // we emit no declarations for the json document, must not refer to it in the declarations + if (target.escapedName === InternalSymbolName.ExportEquals && some(target.declarations, isJsonSourceFile)) { serializeMaybeAliasAssignment(symbol); break; - case SyntaxKind.BinaryExpression: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - // Could be best encoded as though an export specifier or as though an export assignment - // If name is default or export=, do an export assignment - // Otherwise do an export specifier - if (symbol.escapedName === InternalSymbolName.Default || symbol.escapedName === InternalSymbolName.ExportEquals) { - serializeMaybeAliasAssignment(symbol); - } - else { - serializeExportSpecifier(localName, targetName); - } - break; - default: - return Debug.failBadSyntaxKind(node, "Unhandled alias declaration kind in symbol serializer!"); - } - } - - function serializeExportSpecifier(localName: string, targetName: string, specifier?: Expression) { - addResult(factory.createExportDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, localName !== targetName ? targetName : undefined, localName)]), - specifier - ), ModifierFlags.None); - } - - /** - * Returns `true` if an export assignment or declaration was produced for the symbol - */ - function serializeMaybeAliasAssignment(symbol: Symbol): boolean { - if (symbol.flags & SymbolFlags.Prototype) { - return false; - } - const name = unescapeLeadingUnderscores(symbol.escapedName); - const isExportEquals = name === InternalSymbolName.ExportEquals; - const isDefault = name === InternalSymbolName.Default; - const isExportAssignmentCompatibleSymbolName = isExportEquals || isDefault; - // synthesize export = ref - // ref should refer to either be a locally scoped symbol which we need to emit, or - // a reference to another namespace/module which we may need to emit an `import` statement for - const aliasDecl = symbol.declarations && getDeclarationOfAliasSymbol(symbol); - // serialize what the alias points to, preserve the declaration's initializer - const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); - // If the target resolves and resolves to a thing defined in this file, emit as an alias, otherwise emit as a const - if (target && length(target.declarations) && some(target.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(enclosingDeclaration))) { - // In case `target` refers to a namespace member, look at the declaration and serialize the leftmost symbol in it - // eg, `namespace A { export class B {} }; exports = A.B;` - // Technically, this is all that's required in the case where the assignment is an entity name expression - const expr = aliasDecl && ((isExportAssignment(aliasDecl) || isBinaryExpression(aliasDecl)) ? getExportAssignmentExpression(aliasDecl) : getPropertyAssignmentAliasLikeExpression(aliasDecl as ShorthandPropertyAssignment | PropertyAssignment | PropertyAccessExpression)); - const first = expr && isEntityNameExpression(expr) ? getFirstNonModuleExportsIdentifier(expr) : undefined; - const referenced = first && resolveEntityName(first, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, enclosingDeclaration); - if (referenced || target) { - includePrivateSymbol(referenced || target); - } - - // We disable the context's symbol tracker for the duration of this name serialization - // as, by virtue of being here, the name is required to print something, and we don't want to - // issue a visibility error on it. Only anonymous classes that an alias points at _would_ issue - // a visibility error here (as they're not visible within any scope), but we want to hoist them - // into the containing scope anyway, so we want to skip the visibility checks. - const oldTrack = context.tracker.trackSymbol; - context.tracker.trackSymbol = () => false; - if (isExportAssignmentCompatibleSymbolName) { - results.push(factory.createExportAssignment( - /*modifiers*/ undefined, - isExportEquals, - symbolToExpression(target, context, SymbolFlags.All) - )); + } + // Could be a local `import localName = ns.member` or + // an external `import localName = require("whatever")` + const isLocalImport = !(target.flags & SymbolFlags.ValueModule) && !isVariableDeclaration(node); + addResult(factory.createImportEqualsDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createIdentifier(localName), + isLocalImport + ? symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) + : factory.createExternalModuleReference(factory.createStringLiteral(getSpecifierForModuleSymbol(target, context))) + ), isLocalImport ? modifierFlags : ModifierFlags.None); + break; + case SyntaxKind.NamespaceExportDeclaration: + // export as namespace foo + // TODO: Not part of a file's local or export symbol tables + // Is bound into file.symbol.globalExports instead, which we don't currently traverse + addResult(factory.createNamespaceExportDeclaration(idText((node as NamespaceExportDeclaration).name)), ModifierFlags.None); + break; + case SyntaxKind.ImportClause: + addResult(factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause(/*isTypeOnly*/ false, factory.createIdentifier(localName), /*namedBindings*/ undefined), + // We use `target.parent || target` below as `target.parent` is unset when the target is a module which has been export assigned + // And then made into a default by the `esModuleInterop` or `allowSyntheticDefaultImports` flag + // In such cases, the `target` refers to the module itself already + factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)), + /*assertClause*/ undefined + ), ModifierFlags.None); + break; + case SyntaxKind.NamespaceImport: + addResult(factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause(/*isTypeOnly*/ false, /*importClause*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))), + factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)), + /*assertClause*/ undefined + ), ModifierFlags.None); + break; + case SyntaxKind.NamespaceExport: + addResult(factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamespaceExport(factory.createIdentifier(localName)), + factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)) + ), ModifierFlags.None); + break; + case SyntaxKind.ImportSpecifier: + addResult(factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause( + /*isTypeOnly*/ false, + /*importClause*/ undefined, + factory.createNamedImports([ + factory.createImportSpecifier( + /*isTypeOnly*/ false, + localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined, + factory.createIdentifier(localName) + ) + ])), + factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)), + /*assertClause*/ undefined + ), ModifierFlags.None); + break; + case SyntaxKind.ExportSpecifier: + // does not use localName because the symbol name in this case refers to the name in the exports table, + // which we must exactly preserve + const specifier = (node.parent.parent as ExportDeclaration).moduleSpecifier; + // targetName is only used when the target is local, as otherwise the target is an alias that points at + // another file + serializeExportSpecifier( + unescapeLeadingUnderscores(symbol.escapedName), + specifier ? verbatimTargetName : targetName, + specifier && isStringLiteralLike(specifier) ? factory.createStringLiteral(specifier.text) : undefined + ); + break; + case SyntaxKind.ExportAssignment: + serializeMaybeAliasAssignment(symbol); + break; + case SyntaxKind.BinaryExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + // Could be best encoded as though an export specifier or as though an export assignment + // If name is default or export=, do an export assignment + // Otherwise do an export specifier + if (symbol.escapedName === InternalSymbolName.Default || symbol.escapedName === InternalSymbolName.ExportEquals) { + serializeMaybeAliasAssignment(symbol); } else { - if (first === expr && first) { - // serialize as `export {target as name}` - serializeExportSpecifier(name, idText(first)); - } - else if (expr && isClassExpression(expr)) { - serializeExportSpecifier(name, getInternalSymbolName(target, symbolName(target))); - } - else { - // serialize as `import _Ref = t.arg.et; export { _Ref as name }` - const varName = getUnusedName(name, symbol); - addResult(factory.createImportEqualsDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createIdentifier(varName), - symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) - ), ModifierFlags.None); - serializeExportSpecifier(name, varName); - } + serializeExportSpecifier(localName, targetName); } - context.tracker.trackSymbol = oldTrack; - return true; + break; + default: + return Debug.failBadSyntaxKind(node, "Unhandled alias declaration kind in symbol serializer!"); + } + } + + function serializeExportSpecifier(localName: string, targetName: string, specifier?: Expression) { + addResult(factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, localName !== targetName ? targetName : undefined, localName)]), + specifier + ), ModifierFlags.None); + } + + /** + * Returns `true` if an export assignment or declaration was produced for the symbol + */ + function serializeMaybeAliasAssignment(symbol: Symbol): boolean { + if (symbol.flags & SymbolFlags.Prototype) { + return false; + } + const name = unescapeLeadingUnderscores(symbol.escapedName); + const isExportEquals = name === InternalSymbolName.ExportEquals; + const isDefault = name === InternalSymbolName.Default; + const isExportAssignmentCompatibleSymbolName = isExportEquals || isDefault; + // synthesize export = ref + // ref should refer to either be a locally scoped symbol which we need to emit, or + // a reference to another namespace/module which we may need to emit an `import` statement for + const aliasDecl = symbol.declarations && getDeclarationOfAliasSymbol(symbol); + // serialize what the alias points to, preserve the declaration's initializer + const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); + // If the target resolves and resolves to a thing defined in this file, emit as an alias, otherwise emit as a const + if (target && length(target.declarations) && some(target.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(enclosingDeclaration))) { + // In case `target` refers to a namespace member, look at the declaration and serialize the leftmost symbol in it + // eg, `namespace A { export class B {} }; exports = A.B;` + // Technically, this is all that's required in the case where the assignment is an entity name expression + const expr = aliasDecl && ((isExportAssignment(aliasDecl) || isBinaryExpression(aliasDecl)) ? getExportAssignmentExpression(aliasDecl) : getPropertyAssignmentAliasLikeExpression(aliasDecl as ShorthandPropertyAssignment | PropertyAssignment | PropertyAccessExpression)); + const first = expr && isEntityNameExpression(expr) ? getFirstNonModuleExportsIdentifier(expr) : undefined; + const referenced = first && resolveEntityName(first, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, enclosingDeclaration); + if (referenced || target) { + includePrivateSymbol(referenced || target); + } + + // We disable the context's symbol tracker for the duration of this name serialization + // as, by virtue of being here, the name is required to print something, and we don't want to + // issue a visibility error on it. Only anonymous classes that an alias points at _would_ issue + // a visibility error here (as they're not visible within any scope), but we want to hoist them + // into the containing scope anyway, so we want to skip the visibility checks. + const oldTrack = context.tracker.trackSymbol; + context.tracker.trackSymbol = () => false; + if (isExportAssignmentCompatibleSymbolName) { + results.push(factory.createExportAssignment( + /*modifiers*/ undefined, + isExportEquals, + symbolToExpression(target, context, SymbolFlags.All) + )); } else { - // serialize as an anonymous property declaration - const varName = getUnusedName(name, symbol); - // We have to use `getWidenedType` here since the object within a json file is unwidened within the file - // (Unwidened types can only exist in expression contexts and should never be serialized) - const typeToSerialize = getWidenedType(getTypeOfSymbol(getMergedSymbol(symbol))); - if (isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize, symbol)) { - // If there are no index signatures and `typeToSerialize` is an object type, emit as a namespace instead of a const - serializeAsFunctionNamespaceMerge(typeToSerialize, symbol, varName, isExportAssignmentCompatibleSymbolName ? ModifierFlags.None : ModifierFlags.Export); + if (first === expr && first) { + // serialize as `export {target as name}` + serializeExportSpecifier(name, idText(first)); + } + else if (expr && isClassExpression(expr)) { + serializeExportSpecifier(name, getInternalSymbolName(target, symbolName(target))); } else { - const statement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ - factory.createVariableDeclaration(varName, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, typeToSerialize, symbol, enclosingDeclaration, includePrivateSymbol, bundled)) - ], NodeFlags.Const)); - // Inlined JSON types exported with [module.]exports= will already emit an export=, so should use `declare`. - // Otherwise, the type itself should be exported. - addResult(statement, - target && target.flags & SymbolFlags.Property && target.escapedName === InternalSymbolName.ExportEquals ? ModifierFlags.Ambient - : name === varName ? ModifierFlags.Export - : ModifierFlags.None); - } - if (isExportAssignmentCompatibleSymbolName) { - results.push(factory.createExportAssignment( + // serialize as `import _Ref = t.arg.et; export { _Ref as name }` + const varName = getUnusedName(name, symbol); + addResult(factory.createImportEqualsDeclaration( /*modifiers*/ undefined, - isExportEquals, - factory.createIdentifier(varName) - )); - return true; - } - else if (name !== varName) { + /*isTypeOnly*/ false, + factory.createIdentifier(varName), + symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) + ), ModifierFlags.None); serializeExportSpecifier(name, varName); - return true; } - return false; } + context.tracker.trackSymbol = oldTrack; + return true; + } + else { + // serialize as an anonymous property declaration + const varName = getUnusedName(name, symbol); + // We have to use `getWidenedType` here since the object within a json file is unwidened within the file + // (Unwidened types can only exist in expression contexts and should never be serialized) + const typeToSerialize = getWidenedType(getTypeOfSymbol(getMergedSymbol(symbol))); + if (isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize, symbol)) { + // If there are no index signatures and `typeToSerialize` is an object type, emit as a namespace instead of a const + serializeAsFunctionNamespaceMerge(typeToSerialize, symbol, varName, isExportAssignmentCompatibleSymbolName ? ModifierFlags.None : ModifierFlags.Export); + } + else { + const statement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ + factory.createVariableDeclaration(varName, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, typeToSerialize, symbol, enclosingDeclaration, includePrivateSymbol, bundled)) + ], NodeFlags.Const)); + // Inlined JSON types exported with [module.]exports= will already emit an export=, so should use `declare`. + // Otherwise, the type itself should be exported. + addResult(statement, + target && target.flags & SymbolFlags.Property && target.escapedName === InternalSymbolName.ExportEquals ? ModifierFlags.Ambient + : name === varName ? ModifierFlags.Export + : ModifierFlags.None); + } + if (isExportAssignmentCompatibleSymbolName) { + results.push(factory.createExportAssignment( + /*modifiers*/ undefined, + isExportEquals, + factory.createIdentifier(varName) + )); + return true; + } + else if (name !== varName) { + serializeExportSpecifier(name, varName); + return true; + } + return false; } + } - function isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize: Type, hostSymbol: Symbol) { - // Only object types which are not constructable, or indexable, whose members all come from the - // context source file, and whose property names are all valid identifiers and not late-bound, _and_ - // whose input is not type annotated (if the input symbol has an annotation we can reuse, we should prefer it) - const ctxSrc = getSourceFileOfNode(context.enclosingDeclaration); - return getObjectFlags(typeToSerialize) & (ObjectFlags.Anonymous | ObjectFlags.Mapped) && - !length(getIndexInfosOfType(typeToSerialize)) && - !isClassInstanceSide(typeToSerialize) && // While a class instance is potentially representable as a NS, prefer printing a reference to the instance type and serializing the class - !!(length(filter(getPropertiesOfType(typeToSerialize), isNamespaceMember)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) && - !length(getSignaturesOfType(typeToSerialize, SignatureKind.Construct)) && // TODO: could probably serialize as function + ns + class, now that that's OK - !getDeclarationWithTypeAnnotation(hostSymbol, enclosingDeclaration) && - !(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && - !some(getPropertiesOfType(typeToSerialize), p => isLateBoundName(p.escapedName)) && - !some(getPropertiesOfType(typeToSerialize), p => some(p.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && - every(getPropertiesOfType(typeToSerialize), p => isIdentifierText(symbolName(p), languageVersion)); - } - - function makeSerializePropertySymbol(createProperty: ( - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: true): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]); - function makeSerializePropertySymbol(createProperty: ( - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: false): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | T[]); - function makeSerializePropertySymbol(createProperty: ( - modifiers: readonly Modifier[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: boolean): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]) { - return function serializePropertySymbol(p: Symbol, isStatic: boolean, baseType: Type | undefined): (T | AccessorDeclaration | (T | AccessorDeclaration)[]) { - const modifierFlags = getDeclarationModifierFlagsFromSymbol(p); - const isPrivate = !!(modifierFlags & ModifierFlags.Private); - if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) { - // Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols - // need to be merged namespace members - return []; - } - if (p.flags & SymbolFlags.Prototype || - (baseType && getPropertyOfType(baseType, p.escapedName) - && isReadonlySymbol(getPropertyOfType(baseType, p.escapedName)!) === isReadonlySymbol(p) - && (p.flags & SymbolFlags.Optional) === (getPropertyOfType(baseType, p.escapedName)!.flags & SymbolFlags.Optional) - && isTypeIdenticalTo(getTypeOfSymbol(p), getTypeOfPropertyOfType(baseType, p.escapedName)!))) { - return []; - } - const flag = (modifierFlags & ~ModifierFlags.Async) | (isStatic ? ModifierFlags.Static : 0); - const name = getPropertyNameNodeForSymbol(p, context); - const firstPropertyLikeDecl = p.declarations?.find(or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression)); - if (p.flags & SymbolFlags.Accessor && useAccessors) { - const result: AccessorDeclaration[] = []; - if (p.flags & SymbolFlags.SetAccessor) { - result.push(setTextRange(factory.createSetAccessorDeclaration( - factory.createModifiersFromModifierFlags(flag), - name, - [factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - "arg", - /*questionToken*/ undefined, - isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled) - )], - /*body*/ undefined - ), p.declarations?.find(isSetAccessor) || firstPropertyLikeDecl)); - } - if (p.flags & SymbolFlags.GetAccessor) { - const isPrivate = modifierFlags & ModifierFlags.Private; - result.push(setTextRange(factory.createGetAccessorDeclaration( - factory.createModifiersFromModifierFlags(flag), - name, - [], - isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled), - /*body*/ undefined - ), p.declarations?.find(isGetAccessor) || firstPropertyLikeDecl)); - } - return result; + function isTypeRepresentableAsFunctionNamespaceMerge(typeToSerialize: Type, hostSymbol: Symbol) { + // Only object types which are not constructable, or indexable, whose members all come from the + // context source file, and whose property names are all valid identifiers and not late-bound, _and_ + // whose input is not type annotated (if the input symbol has an annotation we can reuse, we should prefer it) + const ctxSrc = getSourceFileOfNode(context.enclosingDeclaration); + return getObjectFlags(typeToSerialize) & (ObjectFlags.Anonymous | ObjectFlags.Mapped) && + !length(getIndexInfosOfType(typeToSerialize)) && + !isClassInstanceSide(typeToSerialize) && // While a class instance is potentially representable as a NS, prefer printing a reference to the instance type and serializing the class + !!(length(filter(getPropertiesOfType(typeToSerialize), isNamespaceMember)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) && + !length(getSignaturesOfType(typeToSerialize, SignatureKind.Construct)) && // TODO: could probably serialize as function + ns + class, now that that's OK + !getDeclarationWithTypeAnnotation(hostSymbol, enclosingDeclaration) && + !(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && + !some(getPropertiesOfType(typeToSerialize), p => isLateBoundName(p.escapedName)) && + !some(getPropertiesOfType(typeToSerialize), p => some(p.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) && + every(getPropertiesOfType(typeToSerialize), p => isIdentifierText(symbolName(p), languageVersion)); + } + + function makeSerializePropertySymbol(createProperty: ( + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: true): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]); + function makeSerializePropertySymbol(createProperty: ( + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: false): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | T[]); + function makeSerializePropertySymbol(createProperty: ( + modifiers: readonly Modifier[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) => T, methodKind: SignatureDeclaration["kind"], useAccessors: boolean): (p: Symbol, isStatic: boolean, baseType: Type | undefined) => (T | AccessorDeclaration | (T | AccessorDeclaration)[]) { + return function serializePropertySymbol(p: Symbol, isStatic: boolean, baseType: Type | undefined): (T | AccessorDeclaration | (T | AccessorDeclaration)[]) { + const modifierFlags = getDeclarationModifierFlagsFromSymbol(p); + const isPrivate = !!(modifierFlags & ModifierFlags.Private); + if (isStatic && (p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias))) { + // Only value-only-meaning symbols can be correctly encoded as class statics, type/namespace/alias meaning symbols + // need to be merged namespace members + return []; + } + if (p.flags & SymbolFlags.Prototype || + (baseType && getPropertyOfType(baseType, p.escapedName) + && isReadonlySymbol(getPropertyOfType(baseType, p.escapedName)!) === isReadonlySymbol(p) + && (p.flags & SymbolFlags.Optional) === (getPropertyOfType(baseType, p.escapedName)!.flags & SymbolFlags.Optional) + && isTypeIdenticalTo(getTypeOfSymbol(p), getTypeOfPropertyOfType(baseType, p.escapedName)!))) { + return []; + } + const flag = (modifierFlags & ~ModifierFlags.Async) | (isStatic ? ModifierFlags.Static : 0); + const name = getPropertyNameNodeForSymbol(p, context); + const firstPropertyLikeDecl = p.declarations?.find(or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression)); + if (p.flags & SymbolFlags.Accessor && useAccessors) { + const result: AccessorDeclaration[] = []; + if (p.flags & SymbolFlags.SetAccessor) { + result.push(setTextRange(factory.createSetAccessorDeclaration( + factory.createModifiersFromModifierFlags(flag), + name, + [factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + "arg", + /*questionToken*/ undefined, + isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled) + )], + /*body*/ undefined + ), p.declarations?.find(isSetAccessor) || firstPropertyLikeDecl)); + } + if (p.flags & SymbolFlags.GetAccessor) { + const isPrivate = modifierFlags & ModifierFlags.Private; + result.push(setTextRange(factory.createGetAccessorDeclaration( + factory.createModifiersFromModifierFlags(flag), + name, + [], + isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled), + /*body*/ undefined + ), p.declarations?.find(isGetAccessor) || firstPropertyLikeDecl)); } - // This is an else/if as accessors and properties can't merge in TS, but might in JS - // If this happens, we assume the accessor takes priority, as it imposes more constraints - else if (p.flags & (SymbolFlags.Property | SymbolFlags.Variable | SymbolFlags.Accessor)) { + return result; + } + // This is an else/if as accessors and properties can't merge in TS, but might in JS + // If this happens, we assume the accessor takes priority, as it imposes more constraints + else if (p.flags & (SymbolFlags.Property | SymbolFlags.Variable | SymbolFlags.Accessor)) { + return setTextRange(createProperty( + factory.createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag), + name, + p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + isPrivate ? undefined : serializeTypeForDeclaration(context, getWriteTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled), + // TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357 + // interface members can't have initializers, however class members _can_ + /*initializer*/ undefined + ), p.declarations?.find(or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl); + } + if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) { + const type = getTypeOfSymbol(p); + const signatures = getSignaturesOfType(type, SignatureKind.Call); + if (flag & ModifierFlags.Private) { return setTextRange(createProperty( factory.createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag), name, p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, - isPrivate ? undefined : serializeTypeForDeclaration(context, getWriteTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled), - // TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357 - // interface members can't have initializers, however class members _can_ + /*type*/ undefined, /*initializer*/ undefined - ), p.declarations?.find(or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl); - } - if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) { - const type = getTypeOfSymbol(p); - const signatures = getSignaturesOfType(type, SignatureKind.Call); - if (flag & ModifierFlags.Private) { - return setTextRange(createProperty( - factory.createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag), - name, - p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, - /*type*/ undefined, - /*initializer*/ undefined - ), p.declarations?.find(isFunctionLikeDeclaration) || signatures[0] && signatures[0].declaration || p.declarations && p.declarations[0]); - } + ), p.declarations?.find(isFunctionLikeDeclaration) || signatures[0] && signatures[0].declaration || p.declarations && p.declarations[0]); + } - const results = []; - for (const sig of signatures) { - // Each overload becomes a separate method declaration, in order - const decl = signatureToSignatureDeclarationHelper( - sig, - methodKind, - context, - { - name, - questionToken: p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, - modifiers: flag ? factory.createModifiersFromModifierFlags(flag) : undefined - } - ); - const location = sig.declaration && isPrototypePropertyAssignment(sig.declaration.parent) ? sig.declaration.parent : sig.declaration; - results.push(setTextRange(decl, location)); - } - return results as unknown as T[]; + const results = []; + for (const sig of signatures) { + // Each overload becomes a separate method declaration, in order + const decl = signatureToSignatureDeclarationHelper( + sig, + methodKind, + context, + { + name, + questionToken: p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + modifiers: flag ? factory.createModifiersFromModifierFlags(flag) : undefined + } + ); + const location = sig.declaration && isPrototypePropertyAssignment(sig.declaration.parent) ? sig.declaration.parent : sig.declaration; + results.push(setTextRange(decl, location)); } - // The `Constructor`'s symbol isn't in the class's properties lists, obviously, since it's a signature on the static - return Debug.fail(`Unhandled class member kind! ${(p as any).__debugFlags || p.flags}`); - }; - } + return results as unknown as T[]; + } + // The `Constructor`'s symbol isn't in the class's properties lists, obviously, since it's a signature on the static + return Debug.fail(`Unhandled class member kind! ${(p as any).__debugFlags || p.flags}`); + }; + } - function serializePropertySymbolForInterface(p: Symbol, baseType: Type | undefined) { - return serializePropertySymbolForInterfaceWorker(p, /*isStatic*/ false, baseType); - } + function serializePropertySymbolForInterface(p: Symbol, baseType: Type | undefined) { + return serializePropertySymbolForInterfaceWorker(p, /*isStatic*/ false, baseType); + } - function serializeSignatures(kind: SignatureKind, input: Type, baseType: Type | undefined, outputKind: SignatureDeclaration["kind"]) { - const signatures = getSignaturesOfType(input, kind); - if (kind === SignatureKind.Construct) { - if (!baseType && every(signatures, s => length(s.parameters) === 0)) { - return []; // No base type, every constructor is empty - elide the extraneous `constructor()` + function serializeSignatures(kind: SignatureKind, input: Type, baseType: Type | undefined, outputKind: SignatureDeclaration["kind"]) { + const signatures = getSignaturesOfType(input, kind); + if (kind === SignatureKind.Construct) { + if (!baseType && every(signatures, s => length(s.parameters) === 0)) { + return []; // No base type, every constructor is empty - elide the extraneous `constructor()` + } + if (baseType) { + // If there is a base type, if every signature in the class is identical to a signature in the baseType, elide all the declarations + const baseSigs = getSignaturesOfType(baseType, SignatureKind.Construct); + if (!length(baseSigs) && every(signatures, s => length(s.parameters) === 0)) { + return []; // Base had no explicit signatures, if all our signatures are also implicit, return an empty list } - if (baseType) { - // If there is a base type, if every signature in the class is identical to a signature in the baseType, elide all the declarations - const baseSigs = getSignaturesOfType(baseType, SignatureKind.Construct); - if (!length(baseSigs) && every(signatures, s => length(s.parameters) === 0)) { - return []; // Base had no explicit signatures, if all our signatures are also implicit, return an empty list - } - if (baseSigs.length === signatures.length) { - let failed = false; - for (let i = 0; i < baseSigs.length; i++) { - if (!compareSignaturesIdentical(signatures[i], baseSigs[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { - failed = true; - break; - } - } - if (!failed) { - return []; // Every signature was identical - elide constructor list as it is inherited + if (baseSigs.length === signatures.length) { + let failed = false; + for (let i = 0; i < baseSigs.length; i++) { + if (!compareSignaturesIdentical(signatures[i], baseSigs[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { + failed = true; + break; } } - } - let privateProtected: ModifierFlags = 0; - for (const s of signatures) { - if (s.declaration) { - privateProtected |= getSelectedEffectiveModifierFlags(s.declaration, ModifierFlags.Private | ModifierFlags.Protected); + if (!failed) { + return []; // Every signature was identical - elide constructor list as it is inherited } } - if (privateProtected) { - return [setTextRange(factory.createConstructorDeclaration( - factory.createModifiersFromModifierFlags(privateProtected), - /*parameters*/ [], - /*body*/ undefined, - ), signatures[0].declaration)]; + } + let privateProtected: ModifierFlags = 0; + for (const s of signatures) { + if (s.declaration) { + privateProtected |= getSelectedEffectiveModifierFlags(s.declaration, ModifierFlags.Private | ModifierFlags.Protected); } } - - const results = []; - for (const sig of signatures) { - // Each overload becomes a separate constructor declaration, in order - const decl = signatureToSignatureDeclarationHelper(sig, outputKind, context); - results.push(setTextRange(decl, sig.declaration)); + if (privateProtected) { + return [setTextRange(factory.createConstructorDeclaration( + factory.createModifiersFromModifierFlags(privateProtected), + /*parameters*/ [], + /*body*/ undefined, + ), signatures[0].declaration)]; } - return results; } - function serializeIndexSignatures(input: Type, baseType: Type | undefined) { - const results: IndexSignatureDeclaration[] = []; - for (const info of getIndexInfosOfType(input)) { - if (baseType) { - const baseInfo = getIndexInfoOfType(baseType, info.keyType); - if (baseInfo) { - if (isTypeIdenticalTo(info.type, baseInfo.type)) { - continue; // elide identical index signatures - } + const results = []; + for (const sig of signatures) { + // Each overload becomes a separate constructor declaration, in order + const decl = signatureToSignatureDeclarationHelper(sig, outputKind, context); + results.push(setTextRange(decl, sig.declaration)); + } + return results; + } + + function serializeIndexSignatures(input: Type, baseType: Type | undefined) { + const results: IndexSignatureDeclaration[] = []; + for (const info of getIndexInfosOfType(input)) { + if (baseType) { + const baseInfo = getIndexInfoOfType(baseType, info.keyType); + if (baseInfo) { + if (isTypeIdenticalTo(info.type, baseInfo.type)) { + continue; // elide identical index signatures } } - results.push(indexInfoToIndexSignatureDeclarationHelper(info, context, /*typeNode*/ undefined)); } - return results; + results.push(indexInfoToIndexSignatureDeclarationHelper(info, context, /*typeNode*/ undefined)); } + return results; + } - function serializeBaseType(t: Type, staticType: Type, rootName: string) { - const ref = trySerializeAsTypeReference(t, SymbolFlags.Value); - if (ref) { - return ref; - } - const tempName = getUnusedName(`${rootName}_base`); - const statement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ - factory.createVariableDeclaration(tempName, /*exclamationToken*/ undefined, typeToTypeNodeHelper(staticType, context)) - ], NodeFlags.Const)); - addResult(statement, ModifierFlags.None); - return factory.createExpressionWithTypeArguments(factory.createIdentifier(tempName), /*typeArgs*/ undefined); + function serializeBaseType(t: Type, staticType: Type, rootName: string) { + const ref = trySerializeAsTypeReference(t, SymbolFlags.Value); + if (ref) { + return ref; } + const tempName = getUnusedName(`${rootName}_base`); + const statement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ + factory.createVariableDeclaration(tempName, /*exclamationToken*/ undefined, typeToTypeNodeHelper(staticType, context)) + ], NodeFlags.Const)); + addResult(statement, ModifierFlags.None); + return factory.createExpressionWithTypeArguments(factory.createIdentifier(tempName), /*typeArgs*/ undefined); + } - function trySerializeAsTypeReference(t: Type, flags: SymbolFlags) { - let typeArgs: TypeNode[] | undefined; - let reference: Expression | undefined; + function trySerializeAsTypeReference(t: Type, flags: SymbolFlags) { + let typeArgs: TypeNode[] | undefined; + let reference: Expression | undefined; - // We don't use `isValueSymbolAccessible` below. since that considers alternative containers (like modules) - // which we can't write out in a syntactically valid way as an expression - if ((t as TypeReference).target && isSymbolAccessibleByFlags((t as TypeReference).target.symbol, enclosingDeclaration, flags)) { - typeArgs = map(getTypeArguments(t as TypeReference), t => typeToTypeNodeHelper(t, context)); - reference = symbolToExpression((t as TypeReference).target.symbol, context, SymbolFlags.Type); - } - else if (t.symbol && isSymbolAccessibleByFlags(t.symbol, enclosingDeclaration, flags)) { - reference = symbolToExpression(t.symbol, context, SymbolFlags.Type); - } - if (reference) { - return factory.createExpressionWithTypeArguments(reference, typeArgs); - } + // We don't use `isValueSymbolAccessible` below. since that considers alternative containers (like modules) + // which we can't write out in a syntactically valid way as an expression + if ((t as TypeReference).target && isSymbolAccessibleByFlags((t as TypeReference).target.symbol, enclosingDeclaration, flags)) { + typeArgs = map(getTypeArguments(t as TypeReference), t => typeToTypeNodeHelper(t, context)); + reference = symbolToExpression((t as TypeReference).target.symbol, context, SymbolFlags.Type); } - - function serializeImplementedType(t: Type) { - const ref = trySerializeAsTypeReference(t, SymbolFlags.Type); - if (ref) { - return ref; - } - if (t.symbol) { - return factory.createExpressionWithTypeArguments(symbolToExpression(t.symbol, context, SymbolFlags.Type), /*typeArgs*/ undefined); - } + else if (t.symbol && isSymbolAccessibleByFlags(t.symbol, enclosingDeclaration, flags)) { + reference = symbolToExpression(t.symbol, context, SymbolFlags.Type); } - - function getUnusedName(input: string, symbol?: Symbol): string { - const id = symbol ? getSymbolId(symbol) : undefined; - if (id) { - if (context.remappedSymbolNames!.has(id)) { - return context.remappedSymbolNames!.get(id)!; - } - } - if (symbol) { - input = getNameCandidateWorker(symbol, input); - } - let i = 0; - const original = input; - while (context.usedSymbolNames?.has(input)) { - i++; - input = `${original}_${i}`; - } - context.usedSymbolNames?.add(input); - if (id) { - context.remappedSymbolNames!.set(id, input); - } - return input; + if (reference) { + return factory.createExpressionWithTypeArguments(reference, typeArgs); } + } - function getNameCandidateWorker(symbol: Symbol, localName: string) { - if (localName === InternalSymbolName.Default || localName === InternalSymbolName.Class || localName === InternalSymbolName.Function) { - const flags = context.flags; - context.flags |= NodeBuilderFlags.InInitialEntityName; - const nameCandidate = getNameOfSymbolAsWritten(symbol, context); - context.flags = flags; - localName = nameCandidate.length > 0 && isSingleOrDoubleQuote(nameCandidate.charCodeAt(0)) ? stripQuotes(nameCandidate) : nameCandidate; - } - if (localName === InternalSymbolName.Default) { - localName = "_default"; - } - else if (localName === InternalSymbolName.ExportEquals) { - localName = "_exports"; - } - localName = isIdentifierText(localName, languageVersion) && !isStringANonContextualKeyword(localName) ? localName : "_" + localName.replace(/[^a-zA-Z0-9]/g, "_"); - return localName; + function serializeImplementedType(t: Type) { + const ref = trySerializeAsTypeReference(t, SymbolFlags.Type); + if (ref) { + return ref; + } + if (t.symbol) { + return factory.createExpressionWithTypeArguments(symbolToExpression(t.symbol, context, SymbolFlags.Type), /*typeArgs*/ undefined); } + } - function getInternalSymbolName(symbol: Symbol, localName: string) { - const id = getSymbolId(symbol); + function getUnusedName(input: string, symbol?: Symbol): string { + const id = symbol ? getSymbolId(symbol) : undefined; + if (id) { if (context.remappedSymbolNames!.has(id)) { return context.remappedSymbolNames!.get(id)!; } - localName = getNameCandidateWorker(symbol, localName); - // The result of this is going to be used as the symbol's name - lock it in, so `getUnusedName` will also pick it up - context.remappedSymbolNames!.set(id, localName); - return localName; } + if (symbol) { + input = getNameCandidateWorker(symbol, input); + } + let i = 0; + const original = input; + while (context.usedSymbolNames?.has(input)) { + i++; + input = `${original}_${i}`; + } + context.usedSymbolNames?.add(input); + if (id) { + context.remappedSymbolNames!.set(id, input); + } + return input; } - } - function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string { - return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); + function getNameCandidateWorker(symbol: Symbol, localName: string) { + if (localName === InternalSymbolName.Default || localName === InternalSymbolName.Class || localName === InternalSymbolName.Function) { + const flags = context.flags; + context.flags |= NodeBuilderFlags.InInitialEntityName; + const nameCandidate = getNameOfSymbolAsWritten(symbol, context); + context.flags = flags; + localName = nameCandidate.length > 0 && isSingleOrDoubleQuote(nameCandidate.charCodeAt(0)) ? stripQuotes(nameCandidate) : nameCandidate; + } + if (localName === InternalSymbolName.Default) { + localName = "_default"; + } + else if (localName === InternalSymbolName.ExportEquals) { + localName = "_exports"; + } + localName = isIdentifierText(localName, languageVersion) && !isStringANonContextualKeyword(localName) ? localName : "_" + localName.replace(/[^a-zA-Z0-9]/g, "_"); + return localName; + } - function typePredicateToStringWorker(writer: EmitTextWriter) { - const predicate = factory.createTypePredicateNode( - typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createToken(SyntaxKind.AssertsKeyword) : undefined, - typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createIdentifier(typePredicate.parameterName) : factory.createThisTypeNode(), - typePredicate.type && nodeBuilder.typeToTypeNode(typePredicate.type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName)! // TODO: GH#18217 - ); - const printer = createPrinter({ removeComments: true }); - const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer); - return writer; + function getInternalSymbolName(symbol: Symbol, localName: string) { + const id = getSymbolId(symbol); + if (context.remappedSymbolNames!.has(id)) { + return context.remappedSymbolNames!.get(id)!; + } + localName = getNameCandidateWorker(symbol, localName); + // The result of this is going to be used as the symbol's name - lock it in, so `getUnusedName` will also pick it up + context.remappedSymbolNames!.set(id, localName); + return localName; } } + } - function formatUnionTypes(types: readonly Type[]): Type[] { - const result: Type[] = []; - let flags: TypeFlags = 0; - for (let i = 0; i < types.length; i++) { - const t = types[i]; - flags |= t.flags; - if (!(t.flags & TypeFlags.Nullable)) { - if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLiteral)) { - const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLiteralType(t as LiteralType); - if (baseType.flags & TypeFlags.Union) { - const count = (baseType as UnionType).types.length; - if (i + count <= types.length && getRegularTypeOfLiteralType(types[i + count - 1]) === getRegularTypeOfLiteralType((baseType as UnionType).types[count - 1])) { - result.push(baseType); - i += count - 1; - continue; - } + function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string { + return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); + + function typePredicateToStringWorker(writer: EmitTextWriter) { + const predicate = factory.createTypePredicateNode( + typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createToken(SyntaxKind.AssertsKeyword) : undefined, + typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createIdentifier(typePredicate.parameterName) : factory.createThisTypeNode(), + typePredicate.type && nodeBuilder.typeToTypeNode(typePredicate.type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName)! // TODO: GH#18217 + ); + const printer = createPrinter({ removeComments: true }); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer); + return writer; + } + } + + function formatUnionTypes(types: readonly Type[]): Type[] { + const result: Type[] = []; + let flags: TypeFlags = 0; + for (let i = 0; i < types.length; i++) { + const t = types[i]; + flags |= t.flags; + if (!(t.flags & TypeFlags.Nullable)) { + if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLiteral)) { + const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLiteralType(t as LiteralType); + if (baseType.flags & TypeFlags.Union) { + const count = (baseType as UnionType).types.length; + if (i + count <= types.length && getRegularTypeOfLiteralType(types[i + count - 1]) === getRegularTypeOfLiteralType((baseType as UnionType).types[count - 1])) { + result.push(baseType); + i += count - 1; + continue; } } - result.push(t); } + result.push(t); } - if (flags & TypeFlags.Null) result.push(nullType); - if (flags & TypeFlags.Undefined) result.push(undefinedType); - return result || types; } + if (flags & TypeFlags.Null) result.push(nullType); + if (flags & TypeFlags.Undefined) result.push(undefinedType); + return result || types; + } - function visibilityToString(flags: ModifierFlags): string | undefined { - if (flags === ModifierFlags.Private) { - return "private"; - } - if (flags === ModifierFlags.Protected) { - return "protected"; - } - return "public"; + function visibilityToString(flags: ModifierFlags): string | undefined { + if (flags === ModifierFlags.Private) { + return "private"; } - - function getTypeAliasForTypeLiteral(type: Type): Symbol | undefined { - if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && type.symbol.declarations) { - const node = walkUpParenthesizedTypes(type.symbol.declarations[0].parent); - if (node.kind === SyntaxKind.TypeAliasDeclaration) { - return getSymbolOfNode(node); - } - } - return undefined; + if (flags === ModifierFlags.Protected) { + return "protected"; } + return "public"; + } - function isTopLevelInExternalModuleAugmentation(node: Node): boolean { - return node && node.parent && - node.parent.kind === SyntaxKind.ModuleBlock && - isExternalModuleAugmentation(node.parent.parent); + function getTypeAliasForTypeLiteral(type: Type): Symbol | undefined { + if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && type.symbol.declarations) { + const node = walkUpParenthesizedTypes(type.symbol.declarations[0].parent); + if (node.kind === SyntaxKind.TypeAliasDeclaration) { + return getSymbolOfNode(node); + } } + return undefined; + } - interface NodeBuilderContext { - enclosingDeclaration: Node | undefined; - flags: NodeBuilderFlags; - tracker: SymbolTracker; + function isTopLevelInExternalModuleAugmentation(node: Node): boolean { + return node && node.parent && + node.parent.kind === SyntaxKind.ModuleBlock && + isExternalModuleAugmentation(node.parent.parent); + } - // State - encounteredError: boolean; - reportedDiagnostic: boolean; - visitedTypes: Set | undefined; - symbolDepth: ESMap | undefined; - inferTypeParameters: TypeParameter[] | undefined; - approximateLength: number; - truncating?: boolean; - typeParameterSymbolList?: Set; - typeParameterNames?: ESMap; - typeParameterNamesByText?: Set; - typeParameterNamesByTextNextNameCount?: ESMap; - usedSymbolNames?: Set; - remappedSymbolNames?: ESMap; - reverseMappedStack?: ReverseMappedSymbol[]; - } + interface NodeBuilderContext { + enclosingDeclaration: Node | undefined; + flags: NodeBuilderFlags; + tracker: SymbolTracker; + + // State + encounteredError: boolean; + reportedDiagnostic: boolean; + visitedTypes: Set | undefined; + symbolDepth: ESMap | undefined; + inferTypeParameters: TypeParameter[] | undefined; + approximateLength: number; + truncating?: boolean; + typeParameterSymbolList?: Set; + typeParameterNames?: ESMap; + typeParameterNamesByText?: Set; + typeParameterNamesByTextNextNameCount?: ESMap; + usedSymbolNames?: Set; + remappedSymbolNames?: ESMap; + reverseMappedStack?: ReverseMappedSymbol[]; + } - function isDefaultBindingContext(location: Node) { - return location.kind === SyntaxKind.SourceFile || isAmbientModule(location); - } + function isDefaultBindingContext(location: Node) { + return location.kind === SyntaxKind.SourceFile || isAmbientModule(location); + } - function getNameOfSymbolFromNameType(symbol: Symbol, context?: NodeBuilderContext) { - const nameType = getSymbolLinks(symbol).nameType; - if (nameType) { - if (nameType.flags & TypeFlags.StringOrNumberLiteral) { - const name = "" + (nameType as StringLiteralType | NumberLiteralType).value; - if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions)) && !isNumericLiteralName(name)) { - return `"${escapeString(name, CharacterCodes.doubleQuote)}"`; - } - if (isNumericLiteralName(name) && startsWith(name, "-")) { - return `[${name}]`; - } - return name; + function getNameOfSymbolFromNameType(symbol: Symbol, context?: NodeBuilderContext) { + const nameType = getSymbolLinks(symbol).nameType; + if (nameType) { + if (nameType.flags & TypeFlags.StringOrNumberLiteral) { + const name = "" + (nameType as StringLiteralType | NumberLiteralType).value; + if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions)) && !isNumericLiteralName(name)) { + return `"${escapeString(name, CharacterCodes.doubleQuote)}"`; } - if (nameType.flags & TypeFlags.UniqueESSymbol) { - return `[${getNameOfSymbolAsWritten((nameType as UniqueESSymbolType).symbol, context)}]`; + if (isNumericLiteralName(name) && startsWith(name, "-")) { + return `[${name}]`; } + return name; + } + if (nameType.flags & TypeFlags.UniqueESSymbol) { + return `[${getNameOfSymbolAsWritten((nameType as UniqueESSymbolType).symbol, context)}]`; } } + } - /** - * Gets a human-readable name for a symbol. - * Should *not* be used for the right-hand side of a `.` -- use `symbolName(symbol)` for that instead. - * - * Unlike `symbolName(symbol)`, this will include quotes if the name is from a string literal. - * It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`. - */ - function getNameOfSymbolAsWritten(symbol: Symbol, context?: NodeBuilderContext): string { - if (context && symbol.escapedName === InternalSymbolName.Default && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) && - // If it's not the first part of an entity name, it must print as `default` - (!(context.flags & NodeBuilderFlags.InInitialEntityName) || - // if the symbol is synthesized, it will only be referenced externally it must print as `default` - !symbol.declarations || - // if not in the same binding context (source file, module declaration), it must print as `default` - (context.enclosingDeclaration && findAncestor(symbol.declarations[0], isDefaultBindingContext) !== findAncestor(context.enclosingDeclaration, isDefaultBindingContext)))) { - return "default"; - } - if (symbol.declarations && symbol.declarations.length) { - let declaration = firstDefined(symbol.declarations, d => getNameOfDeclaration(d) ? d : undefined); // Try using a declaration with a name, first - const name = declaration && getNameOfDeclaration(declaration); - if (declaration && name) { - if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { - return symbolName(symbol); - } - if (isComputedPropertyName(name) && !(getCheckFlags(symbol) & CheckFlags.Late)) { - const nameType = getSymbolLinks(symbol).nameType; - if (nameType && nameType.flags & TypeFlags.StringOrNumberLiteral) { - // Computed property name isn't late bound, but has a well-known name type - use name type to generate a symbol name - const result = getNameOfSymbolFromNameType(symbol, context); - if (result !== undefined) { - return result; - } + /** + * Gets a human-readable name for a symbol. + * Should *not* be used for the right-hand side of a `.` -- use `symbolName(symbol)` for that instead. + * + * Unlike `symbolName(symbol)`, this will include quotes if the name is from a string literal. + * It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`. + */ + function getNameOfSymbolAsWritten(symbol: Symbol, context?: NodeBuilderContext): string { + if (context && symbol.escapedName === InternalSymbolName.Default && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) && + // If it's not the first part of an entity name, it must print as `default` + (!(context.flags & NodeBuilderFlags.InInitialEntityName) || + // if the symbol is synthesized, it will only be referenced externally it must print as `default` + !symbol.declarations || + // if not in the same binding context (source file, module declaration), it must print as `default` + (context.enclosingDeclaration && findAncestor(symbol.declarations[0], isDefaultBindingContext) !== findAncestor(context.enclosingDeclaration, isDefaultBindingContext)))) { + return "default"; + } + if (symbol.declarations && symbol.declarations.length) { + let declaration = firstDefined(symbol.declarations, d => getNameOfDeclaration(d) ? d : undefined); // Try using a declaration with a name, first + const name = declaration && getNameOfDeclaration(declaration); + if (declaration && name) { + if (isCallExpression(declaration) && isBindableObjectDefinePropertyCall(declaration)) { + return symbolName(symbol); + } + if (isComputedPropertyName(name) && !(getCheckFlags(symbol) & CheckFlags.Late)) { + const nameType = getSymbolLinks(symbol).nameType; + if (nameType && nameType.flags & TypeFlags.StringOrNumberLiteral) { + // Computed property name isn't late bound, but has a well-known name type - use name type to generate a symbol name + const result = getNameOfSymbolFromNameType(symbol, context); + if (result !== undefined) { + return result; } } - return declarationNameToString(name); - } - if (!declaration) { - declaration = symbol.declarations[0]; // Declaration may be nameless, but we'll try anyway - } - if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { - return declarationNameToString((declaration.parent as VariableDeclaration).name); - } - switch (declaration.kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - if (context && !context.encounteredError && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { - context.encounteredError = true; - } - return declaration.kind === SyntaxKind.ClassExpression ? "(Anonymous class)" : "(Anonymous function)"; } + return declarationNameToString(name); + } + if (!declaration) { + declaration = symbol.declarations[0]; // Declaration may be nameless, but we'll try anyway + } + if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { + return declarationNameToString((declaration.parent as VariableDeclaration).name); + } + switch (declaration.kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + if (context && !context.encounteredError && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { + context.encounteredError = true; + } + return declaration.kind === SyntaxKind.ClassExpression ? "(Anonymous class)" : "(Anonymous function)"; } - const name = getNameOfSymbolFromNameType(symbol, context); - return name !== undefined ? name : symbolName(symbol); } + const name = getNameOfSymbolFromNameType(symbol, context); + return name !== undefined ? name : symbolName(symbol); + } - function isDeclarationVisible(node: Node): boolean { - if (node) { - const links = getNodeLinks(node); - if (links.isVisible === undefined) { - links.isVisible = !!determineIfDeclarationIsVisible(); - } - return links.isVisible; + function isDeclarationVisible(node: Node): boolean { + if (node) { + const links = getNodeLinks(node); + if (links.isVisible === undefined) { + links.isVisible = !!determineIfDeclarationIsVisible(); } + return links.isVisible; + } - return false; - - function determineIfDeclarationIsVisible() { - switch (node.kind) { - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocEnumTag: - // Top-level jsdoc type aliases are considered exported - // First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file - return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent)); - case SyntaxKind.BindingElement: - return isDeclarationVisible(node.parent.parent); - case SyntaxKind.VariableDeclaration: - if (isBindingPattern((node as VariableDeclaration).name) && - !((node as VariableDeclaration).name as BindingPattern).elements.length) { - // If the binding pattern is empty, this variable declaration is not visible - return false; - } - // falls through - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - // external module augmentation is always visible - if (isExternalModuleAugmentation(node)) { - return true; - } - const parent = getDeclarationContainer(node); - // If the node is not exported or it is not ambient module element (except import declaration) - if (!(getCombinedModifierFlags(node as Declaration) & ModifierFlags.Export) && - !(node.kind !== SyntaxKind.ImportEqualsDeclaration && parent.kind !== SyntaxKind.SourceFile && parent.flags & NodeFlags.Ambient)) { - return isGlobalSourceFile(parent); - } - // Exported members/ambient module elements (exception import declaration) are visible if parent is visible - return isDeclarationVisible(parent); - - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (hasEffectiveModifier(node, ModifierFlags.Private | ModifierFlags.Protected)) { - // Private/protected properties/methods are not visible - return false; - } - // Public properties/methods are visible if its parents are visible, so: - // falls through + return false; - case SyntaxKind.Constructor: - case SyntaxKind.ConstructSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.Parameter: - case SyntaxKind.ModuleBlock: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.TypeLiteral: - case SyntaxKind.TypeReference: - case SyntaxKind.ArrayType: - case SyntaxKind.TupleType: - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - case SyntaxKind.ParenthesizedType: - case SyntaxKind.NamedTupleMember: - return isDeclarationVisible(node.parent); - - // Default binding, import specifier and namespace import is visible - // only on demand so by default it is not visible - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportSpecifier: + function determineIfDeclarationIsVisible() { + switch (node.kind) { + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocEnumTag: + // Top-level jsdoc type aliases are considered exported + // First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file + return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent)); + case SyntaxKind.BindingElement: + return isDeclarationVisible(node.parent.parent); + case SyntaxKind.VariableDeclaration: + if (isBindingPattern((node as VariableDeclaration).name) && + !((node as VariableDeclaration).name as BindingPattern).elements.length) { + // If the binding pattern is empty, this variable declaration is not visible return false; - - // Type parameters are always visible - case SyntaxKind.TypeParameter: - - // Source file and namespace export are always visible + } // falls through - case SyntaxKind.SourceFile: - case SyntaxKind.NamespaceExportDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + // external module augmentation is always visible + if (isExternalModuleAugmentation(node)) { return true; + } + const parent = getDeclarationContainer(node); + // If the node is not exported or it is not ambient module element (except import declaration) + if (!(getCombinedModifierFlags(node as Declaration) & ModifierFlags.Export) && + !(node.kind !== SyntaxKind.ImportEqualsDeclaration && parent.kind !== SyntaxKind.SourceFile && parent.flags & NodeFlags.Ambient)) { + return isGlobalSourceFile(parent); + } + // Exported members/ambient module elements (exception import declaration) are visible if parent is visible + return isDeclarationVisible(parent); - // Export assignments do not create name bindings outside the module - case SyntaxKind.ExportAssignment: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (hasEffectiveModifier(node, ModifierFlags.Private | ModifierFlags.Protected)) { + // Private/protected properties/methods are not visible return false; + } + // Public properties/methods are visible if its parents are visible, so: + // falls through - default: - return false; - } + case SyntaxKind.Constructor: + case SyntaxKind.ConstructSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.Parameter: + case SyntaxKind.ModuleBlock: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeLiteral: + case SyntaxKind.TypeReference: + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.ParenthesizedType: + case SyntaxKind.NamedTupleMember: + return isDeclarationVisible(node.parent); + + // Default binding, import specifier and namespace import is visible + // only on demand so by default it is not visible + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + return false; + + // Type parameters are always visible + case SyntaxKind.TypeParameter: + + // Source file and namespace export are always visible + // falls through + case SyntaxKind.SourceFile: + case SyntaxKind.NamespaceExportDeclaration: + return true; + + // Export assignments do not create name bindings outside the module + case SyntaxKind.ExportAssignment: + return false; + + default: + return false; } } + } - function collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined { - let exportSymbol: Symbol | undefined; - if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { - exportSymbol = resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, node, /*isUse*/ false); - } - else if (node.parent.kind === SyntaxKind.ExportSpecifier) { - exportSymbol = getTargetOfExportSpecifier(node.parent as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); - } - let result: Node[] | undefined; - let visited: Set | undefined; - if (exportSymbol) { - visited = new Set(); - visited.add(getSymbolId(exportSymbol)); - buildVisibleNodeList(exportSymbol.declarations); - } - return result; + function collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined { + let exportSymbol: Symbol | undefined; + if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { + exportSymbol = resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, node, /*isUse*/ false); + } + else if (node.parent.kind === SyntaxKind.ExportSpecifier) { + exportSymbol = getTargetOfExportSpecifier(node.parent as ExportSpecifier, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); + } + let result: Node[] | undefined; + let visited: Set | undefined; + if (exportSymbol) { + visited = new Set(); + visited.add(getSymbolId(exportSymbol)); + buildVisibleNodeList(exportSymbol.declarations); + } + return result; - function buildVisibleNodeList(declarations: Declaration[] | undefined) { - forEach(declarations, declaration => { - const resultNode = getAnyImportSyntax(declaration) || declaration; - if (setVisibility) { - getNodeLinks(declaration).isVisible = true; - } - else { - result = result || []; - pushIfUnique(result, resultNode); - } - - if (isInternalModuleImportEqualsDeclaration(declaration)) { - // Add the referenced top container visible - const internalModuleReference = declaration.moduleReference as Identifier | QualifiedName; - const firstIdentifier = getFirstIdentifier(internalModuleReference); - const importSymbol = resolveName(declaration, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, - undefined, undefined, /*isUse*/ false); - if (importSymbol && visited) { - if (tryAddToSet(visited, getSymbolId(importSymbol))) { - buildVisibleNodeList(importSymbol.declarations); - } + function buildVisibleNodeList(declarations: Declaration[] | undefined) { + forEach(declarations, declaration => { + const resultNode = getAnyImportSyntax(declaration) || declaration; + if (setVisibility) { + getNodeLinks(declaration).isVisible = true; + } + else { + result = result || []; + pushIfUnique(result, resultNode); + } + + if (isInternalModuleImportEqualsDeclaration(declaration)) { + // Add the referenced top container visible + const internalModuleReference = declaration.moduleReference as Identifier | QualifiedName; + const firstIdentifier = getFirstIdentifier(internalModuleReference); + const importSymbol = resolveName(declaration, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, + undefined, undefined, /*isUse*/ false); + if (importSymbol && visited) { + if (tryAddToSet(visited, getSymbolId(importSymbol))) { + buildVisibleNodeList(importSymbol.declarations); } } - }); + } + }); + } + } + + /** + * Push an entry on the type resolution stack. If an entry with the given target and the given property name + * is already on the stack, and no entries in between already have a type, then a circularity has occurred. + * In this case, the result values of the existing entry and all entries pushed after it are changed to false, + * and the value false is returned. Otherwise, the new entry is just pushed onto the stack, and true is returned. + * In order to see if the same query has already been done before, the target object and the propertyName both + * must match the one passed in. + * + * @param target The symbol, type, or signature whose type is being queried + * @param propertyName The property name that should be used to query the target for its type + */ + function pushTypeResolution(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { + const resolutionCycleStartIndex = findResolutionCycleStartIndex(target, propertyName); + if (resolutionCycleStartIndex >= 0) { + // A cycle was found + const { length } = resolutionTargets; + for (let i = resolutionCycleStartIndex; i < length; i++) { + resolutionResults[i] = false; } + return false; } + resolutionTargets.push(target); + resolutionResults.push(/*items*/ true); + resolutionPropertyNames.push(propertyName); + return true; + } - /** - * Push an entry on the type resolution stack. If an entry with the given target and the given property name - * is already on the stack, and no entries in between already have a type, then a circularity has occurred. - * In this case, the result values of the existing entry and all entries pushed after it are changed to false, - * and the value false is returned. Otherwise, the new entry is just pushed onto the stack, and true is returned. - * In order to see if the same query has already been done before, the target object and the propertyName both - * must match the one passed in. - * - * @param target The symbol, type, or signature whose type is being queried - * @param propertyName The property name that should be used to query the target for its type - */ - function pushTypeResolution(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { - const resolutionCycleStartIndex = findResolutionCycleStartIndex(target, propertyName); - if (resolutionCycleStartIndex >= 0) { - // A cycle was found - const { length } = resolutionTargets; - for (let i = resolutionCycleStartIndex; i < length; i++) { - resolutionResults[i] = false; - } - return false; + function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number { + for (let i = resolutionTargets.length - 1; i >= 0; i--) { + if (hasType(resolutionTargets[i], resolutionPropertyNames[i])) { + return -1; + } + if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) { + return i; } - resolutionTargets.push(target); - resolutionResults.push(/*items*/ true); - resolutionPropertyNames.push(propertyName); - return true; } + return -1; + } + + function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { + switch (propertyName) { + case TypeSystemPropertyName.Type: + return !!getSymbolLinks(target as Symbol).type; + case TypeSystemPropertyName.EnumTagType: + return !!(getNodeLinks(target as JSDocEnumTag).resolvedEnumType); + case TypeSystemPropertyName.DeclaredType: + return !!getSymbolLinks(target as Symbol).declaredType; + case TypeSystemPropertyName.ResolvedBaseConstructorType: + return !!(target as InterfaceType).resolvedBaseConstructorType; + case TypeSystemPropertyName.ResolvedReturnType: + return !!(target as Signature).resolvedReturnType; + case TypeSystemPropertyName.ImmediateBaseConstraint: + return !!(target as Type).immediateBaseConstraint; + case TypeSystemPropertyName.ResolvedTypeArguments: + return !!(target as TypeReference).resolvedTypeArguments; + case TypeSystemPropertyName.ResolvedBaseTypes: + return !!(target as InterfaceType).baseTypesResolved; + case TypeSystemPropertyName.WriteType: + return !!getSymbolLinks(target as Symbol).writeType; + } + return Debug.assertNever(propertyName); + } + + /** + * Pop an entry from the type resolution stack and return its associated result value. The result value will + * be true if no circularities were detected, or false if a circularity was found. + */ + function popTypeResolution(): boolean { + resolutionTargets.pop(); + resolutionPropertyNames.pop(); + return resolutionResults.pop()!; + } + + function getDeclarationContainer(node: Node): Node { + return findAncestor(getRootDeclaration(node), node => { + switch (node.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.VariableDeclarationList: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.NamedImports: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportClause: + return false; + default: + return true; + } + })!.parent; + } + + function getTypeOfPrototypeProperty(prototype: Symbol): Type { + // TypeScript 1.0 spec (April 2014): 8.4 + // Every class automatically contains a static property member named 'prototype', + // the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter. + // It is an error to explicitly declare a static property member with the name 'prototype'. + const classType = getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!) as InterfaceType; + return classType.typeParameters ? createTypeReference(classType as GenericType, map(classType.typeParameters, _ => anyType)) : classType; + } - function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number { - for (let i = resolutionTargets.length - 1; i >= 0; i--) { - if (hasType(resolutionTargets[i], resolutionPropertyNames[i])) { - return -1; - } - if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) { - return i; - } - } - return -1; - } + // Return the type of the given property in the given type, or undefined if no such property exists + function getTypeOfPropertyOfType(type: Type, name: __String): Type | undefined { + const prop = getPropertyOfType(type, name); + return prop ? getTypeOfSymbol(prop) : undefined; + } - function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { - switch (propertyName) { - case TypeSystemPropertyName.Type: - return !!getSymbolLinks(target as Symbol).type; - case TypeSystemPropertyName.EnumTagType: - return !!(getNodeLinks(target as JSDocEnumTag).resolvedEnumType); - case TypeSystemPropertyName.DeclaredType: - return !!getSymbolLinks(target as Symbol).declaredType; - case TypeSystemPropertyName.ResolvedBaseConstructorType: - return !!(target as InterfaceType).resolvedBaseConstructorType; - case TypeSystemPropertyName.ResolvedReturnType: - return !!(target as Signature).resolvedReturnType; - case TypeSystemPropertyName.ImmediateBaseConstraint: - return !!(target as Type).immediateBaseConstraint; - case TypeSystemPropertyName.ResolvedTypeArguments: - return !!(target as TypeReference).resolvedTypeArguments; - case TypeSystemPropertyName.ResolvedBaseTypes: - return !!(target as InterfaceType).baseTypesResolved; - case TypeSystemPropertyName.WriteType: - return !!getSymbolLinks(target as Symbol).writeType; - } - return Debug.assertNever(propertyName); - } + function getTypeOfPropertyOrIndexSignature(type: Type, name: __String): Type { + return getTypeOfPropertyOfType(type, name) || getApplicableIndexInfoForName(type, name)?.type || unknownType; + } - /** - * Pop an entry from the type resolution stack and return its associated result value. The result value will - * be true if no circularities were detected, or false if a circularity was found. - */ - function popTypeResolution(): boolean { - resolutionTargets.pop(); - resolutionPropertyNames.pop(); - return resolutionResults.pop()!; - } + function isTypeAny(type: Type | undefined) { + return type && (type.flags & TypeFlags.Any) !== 0; + } - function getDeclarationContainer(node: Node): Node { - return findAncestor(getRootDeclaration(node), node => { - switch (node.kind) { - case SyntaxKind.VariableDeclaration: - case SyntaxKind.VariableDeclarationList: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.NamedImports: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportClause: - return false; - default: - return true; - } - })!.parent; - } + function isErrorType(type: Type) { + // The only 'any' types that have alias symbols are those manufactured by getTypeFromTypeAliasReference for + // a reference to an unresolved symbol. We want those to behave like the errorType. + return type === errorType || !!(type.flags & TypeFlags.Any && type.aliasSymbol); + } - function getTypeOfPrototypeProperty(prototype: Symbol): Type { - // TypeScript 1.0 spec (April 2014): 8.4 - // Every class automatically contains a static property member named 'prototype', - // the type of which is an instantiation of the class type with type Any supplied as a type argument for each type parameter. - // It is an error to explicitly declare a static property member with the name 'prototype'. - const classType = getDeclaredTypeOfSymbol(getParentOfSymbol(prototype)!) as InterfaceType; - return classType.typeParameters ? createTypeReference(classType as GenericType, map(classType.typeParameters, _ => anyType)) : classType; + // Return the type of a binding element parent. We check SymbolLinks first to see if a type has been + // assigned by contextual typing. + function getTypeForBindingElementParent(node: BindingElementGrandparent, checkMode: CheckMode) { + if (checkMode !== CheckMode.Normal) { + return getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode); } + const symbol = getSymbolOfNode(node); + return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode); + } - // Return the type of the given property in the given type, or undefined if no such property exists - function getTypeOfPropertyOfType(type: Type, name: __String): Type | undefined { - const prop = getPropertyOfType(type, name); - return prop ? getTypeOfSymbol(prop) : undefined; + function getRestType(source: Type, properties: PropertyName[], symbol: Symbol | undefined): Type { + source = filterType(source, t => !(t.flags & TypeFlags.Nullable)); + if (source.flags & TypeFlags.Never) { + return emptyObjectType; } - - function getTypeOfPropertyOrIndexSignature(type: Type, name: __String): Type { - return getTypeOfPropertyOfType(type, name) || getApplicableIndexInfoForName(type, name)?.type || unknownType; + if (source.flags & TypeFlags.Union) { + return mapType(source, t => getRestType(t, properties, symbol)); } - function isTypeAny(type: Type | undefined) { - return type && (type.flags & TypeFlags.Any) !== 0; - } + let omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); - function isErrorType(type: Type) { - // The only 'any' types that have alias symbols are those manufactured by getTypeFromTypeAliasReference for - // a reference to an unresolved symbol. We want those to behave like the errorType. - return type === errorType || !!(type.flags & TypeFlags.Any && type.aliasSymbol); - } + const spreadableProperties: Symbol[] = []; + const unspreadableToRestKeys: Type[] = []; - // Return the type of a binding element parent. We check SymbolLinks first to see if a type has been - // assigned by contextual typing. - function getTypeForBindingElementParent(node: BindingElementGrandparent, checkMode: CheckMode) { - if (checkMode !== CheckMode.Normal) { - return getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode); + for (const prop of getPropertiesOfType(source)) { + const literalTypeFromProperty = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique); + if (!isTypeAssignableTo(literalTypeFromProperty, omitKeyType) + && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) + && isSpreadableProperty(prop)) { + spreadableProperties.push(prop); + } + else { + unspreadableToRestKeys.push(literalTypeFromProperty); } - const symbol = getSymbolOfNode(node); - return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode); } - function getRestType(source: Type, properties: PropertyName[], symbol: Symbol | undefined): Type { - source = filterType(source, t => !(t.flags & TypeFlags.Nullable)); - if (source.flags & TypeFlags.Never) { - return emptyObjectType; - } - if (source.flags & TypeFlags.Union) { - return mapType(source, t => getRestType(t, properties, symbol)); + if (isGenericObjectType(source) || isGenericIndexType(omitKeyType)) { + if (unspreadableToRestKeys.length) { + // If the type we're spreading from has properties that cannot + // be spread into the rest type (e.g. getters, methods), ensure + // they are explicitly omitted, as they would in the non-generic case. + omitKeyType = getUnionType([omitKeyType, ...unspreadableToRestKeys]); } - let omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); - - const spreadableProperties: Symbol[] = []; - const unspreadableToRestKeys: Type[] = []; + if (omitKeyType.flags & TypeFlags.Never) { + return source; + } - for (const prop of getPropertiesOfType(source)) { - const literalTypeFromProperty = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique); - if (!isTypeAssignableTo(literalTypeFromProperty, omitKeyType) - && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) - && isSpreadableProperty(prop)) { - spreadableProperties.push(prop); - } - else { - unspreadableToRestKeys.push(literalTypeFromProperty); - } + const omitTypeAlias = getGlobalOmitSymbol(); + if (!omitTypeAlias) { + return errorType; } + return getTypeAliasInstantiation(omitTypeAlias, [source, omitKeyType]); + } + const members = createSymbolTable(); + for (const prop of spreadableProperties) { + members.set(prop.escapedName, getSpreadSymbol(prop, /*readonly*/ false)); + } + const result = createAnonymousType(symbol, members, emptyArray, emptyArray, getIndexInfosOfType(source)); + result.objectFlags |= ObjectFlags.ObjectRestType; + return result; + } - if (isGenericObjectType(source) || isGenericIndexType(omitKeyType)) { - if (unspreadableToRestKeys.length) { - // If the type we're spreading from has properties that cannot - // be spread into the rest type (e.g. getters, methods), ensure - // they are explicitly omitted, as they would in the non-generic case. - omitKeyType = getUnionType([omitKeyType, ...unspreadableToRestKeys]); - } + function isGenericTypeWithUndefinedConstraint(type: Type) { + return !!(type.flags & TypeFlags.Instantiable) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Undefined); + } - if (omitKeyType.flags & TypeFlags.Never) { - return source; - } + function getNonUndefinedType(type: Type) { + const typeOrConstraint = someType(type, isGenericTypeWithUndefinedConstraint) ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; + return getTypeWithFacts(typeOrConstraint, TypeFacts.NEUndefined); + } - const omitTypeAlias = getGlobalOmitSymbol(); - if (!omitTypeAlias) { - return errorType; - } - return getTypeAliasInstantiation(omitTypeAlias, [source, omitKeyType]); - } - const members = createSymbolTable(); - for (const prop of spreadableProperties) { - members.set(prop.escapedName, getSpreadSymbol(prop, /*readonly*/ false)); + // Determine the control flow type associated with a destructuring declaration or assignment. The following + // forms of destructuring are possible: + // let { x } = obj; // BindingElement + // let [ x ] = obj; // BindingElement + // { x } = obj; // ShorthandPropertyAssignment + // { x: v } = obj; // PropertyAssignment + // [ x ] = obj; // Expression + // We construct a synthetic element access expression corresponding to 'obj.x' such that the control + // flow analyzer doesn't have to handle all the different syntactic forms. + function getFlowTypeOfDestructuring(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression, declaredType: Type) { + const reference = getSyntheticElementAccess(node); + return reference ? getFlowTypeOfReference(reference, declaredType) : declaredType; + } + + function getSyntheticElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression): ElementAccessExpression | undefined { + const parentAccess = getParentElementAccess(node); + if (parentAccess && parentAccess.flowNode) { + const propName = getDestructuringPropertyName(node); + if (propName) { + const literal = setTextRange(parseNodeFactory.createStringLiteral(propName), node); + const lhsExpr = isLeftHandSideExpression(parentAccess) ? parentAccess : parseNodeFactory.createParenthesizedExpression(parentAccess); + const result = setTextRange(parseNodeFactory.createElementAccessExpression(lhsExpr, literal), node); + setParent(literal, result); + setParent(result, node); + if (lhsExpr !== parentAccess) { + setParent(lhsExpr, result); + } + result.flowNode = parentAccess.flowNode; + return result; } - const result = createAnonymousType(symbol, members, emptyArray, emptyArray, getIndexInfosOfType(source)); - result.objectFlags |= ObjectFlags.ObjectRestType; - return result; } + } - function isGenericTypeWithUndefinedConstraint(type: Type) { - return !!(type.flags & TypeFlags.Instantiable) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Undefined); + function getParentElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { + const ancestor = node.parent.parent; + switch (ancestor.kind) { + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyAssignment: + return getSyntheticElementAccess(ancestor as BindingElement | PropertyAssignment); + case SyntaxKind.ArrayLiteralExpression: + return getSyntheticElementAccess(node.parent as Expression); + case SyntaxKind.VariableDeclaration: + return (ancestor as VariableDeclaration).initializer; + case SyntaxKind.BinaryExpression: + return (ancestor as BinaryExpression).right; } + } - function getNonUndefinedType(type: Type) { - const typeOrConstraint = someType(type, isGenericTypeWithUndefinedConstraint) ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; - return getTypeWithFacts(typeOrConstraint, TypeFacts.NEUndefined); + function getDestructuringPropertyName(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { + const parent = node.parent; + if (node.kind === SyntaxKind.BindingElement && parent.kind === SyntaxKind.ObjectBindingPattern) { + return getLiteralPropertyNameText((node as BindingElement).propertyName || (node as BindingElement).name as Identifier); } - - // Determine the control flow type associated with a destructuring declaration or assignment. The following - // forms of destructuring are possible: - // let { x } = obj; // BindingElement - // let [ x ] = obj; // BindingElement - // { x } = obj; // ShorthandPropertyAssignment - // { x: v } = obj; // PropertyAssignment - // [ x ] = obj; // Expression - // We construct a synthetic element access expression corresponding to 'obj.x' such that the control - // flow analyzer doesn't have to handle all the different syntactic forms. - function getFlowTypeOfDestructuring(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression, declaredType: Type) { - const reference = getSyntheticElementAccess(node); - return reference ? getFlowTypeOfReference(reference, declaredType) : declaredType; + if (node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) { + return getLiteralPropertyNameText((node as PropertyAssignment | ShorthandPropertyAssignment).name); } + return "" + ((parent as BindingPattern | ArrayLiteralExpression).elements as NodeArray).indexOf(node); + } - function getSyntheticElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression): ElementAccessExpression | undefined { - const parentAccess = getParentElementAccess(node); - if (parentAccess && parentAccess.flowNode) { - const propName = getDestructuringPropertyName(node); - if (propName) { - const literal = setTextRange(parseNodeFactory.createStringLiteral(propName), node); - const lhsExpr = isLeftHandSideExpression(parentAccess) ? parentAccess : parseNodeFactory.createParenthesizedExpression(parentAccess); - const result = setTextRange(parseNodeFactory.createElementAccessExpression(lhsExpr, literal), node); - setParent(literal, result); - setParent(result, node); - if (lhsExpr !== parentAccess) { - setParent(lhsExpr, result); - } - result.flowNode = parentAccess.flowNode; - return result; - } - } - } + function getLiteralPropertyNameText(name: PropertyName) { + const type = getLiteralTypeFromPropertyName(name); + return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type as StringLiteralType | NumberLiteralType).value : undefined; + } - function getParentElementAccess(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { - const ancestor = node.parent.parent; - switch (ancestor.kind) { - case SyntaxKind.BindingElement: - case SyntaxKind.PropertyAssignment: - return getSyntheticElementAccess(ancestor as BindingElement | PropertyAssignment); - case SyntaxKind.ArrayLiteralExpression: - return getSyntheticElementAccess(node.parent as Expression); - case SyntaxKind.VariableDeclaration: - return (ancestor as VariableDeclaration).initializer; - case SyntaxKind.BinaryExpression: - return (ancestor as BinaryExpression).right; - } - } + /** Return the inferred type for a binding element */ + function getTypeForBindingElement(declaration: BindingElement): Type | undefined { + const checkMode = declaration.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal; + const parentType = getTypeForBindingElementParent(declaration.parent.parent, checkMode); + return parentType && getBindingElementTypeFromParentType(declaration, parentType); + } - function getDestructuringPropertyName(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { - const parent = node.parent; - if (node.kind === SyntaxKind.BindingElement && parent.kind === SyntaxKind.ObjectBindingPattern) { - return getLiteralPropertyNameText((node as BindingElement).propertyName || (node as BindingElement).name as Identifier); - } - if (node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) { - return getLiteralPropertyNameText((node as PropertyAssignment | ShorthandPropertyAssignment).name); - } - return "" + ((parent as BindingPattern | ArrayLiteralExpression).elements as NodeArray).indexOf(node); + function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type): Type { + // If an any type was inferred for parent, infer that for the binding element + if (isTypeAny(parentType)) { + return parentType; } - - function getLiteralPropertyNameText(name: PropertyName) { - const type = getLiteralTypeFromPropertyName(name); - return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type as StringLiteralType | NumberLiteralType).value : undefined; + const pattern = declaration.parent; + // Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation + if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { + parentType = getNonNullableType(parentType); } - - /** Return the inferred type for a binding element */ - function getTypeForBindingElement(declaration: BindingElement): Type | undefined { - const checkMode = declaration.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal; - const parentType = getTypeForBindingElementParent(declaration.parent.parent, checkMode); - return parentType && getBindingElementTypeFromParentType(declaration, parentType); + // Filter `undefined` from the type we check against if the parent has an initializer and that initializer is not possibly `undefined` + else if (strictNullChecks && pattern.parent.initializer && !(getTypeFacts(getTypeOfInitializer(pattern.parent.initializer)) & TypeFacts.EQUndefined)) { + parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); } - function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type): Type { - // If an any type was inferred for parent, infer that for the binding element - if (isTypeAny(parentType)) { - return parentType; - } - const pattern = declaration.parent; - // Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation - if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { - parentType = getNonNullableType(parentType); - } - // Filter `undefined` from the type we check against if the parent has an initializer and that initializer is not possibly `undefined` - else if (strictNullChecks && pattern.parent.initializer && !(getTypeFacts(getTypeOfInitializer(pattern.parent.initializer)) & TypeFacts.EQUndefined)) { - parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); - } - - let type: Type | undefined; - if (pattern.kind === SyntaxKind.ObjectBindingPattern) { - if (declaration.dotDotDotToken) { - parentType = getReducedType(parentType); - if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType)) { - error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types); - return errorType; - } - const literalMembers: PropertyName[] = []; - for (const element of pattern.elements) { - if (!element.dotDotDotToken) { - literalMembers.push(element.propertyName || element.name as Identifier); - } - } - type = getRestType(parentType, literalMembers, declaration.symbol); + let type: Type | undefined; + if (pattern.kind === SyntaxKind.ObjectBindingPattern) { + if (declaration.dotDotDotToken) { + parentType = getReducedType(parentType); + if (parentType.flags & TypeFlags.Unknown || !isValidSpreadType(parentType)) { + error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types); + return errorType; } - else { - // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) - const name = declaration.propertyName || declaration.name as Identifier; - const indexType = getLiteralTypeFromPropertyName(name); - const declaredType = getIndexedAccessType(parentType, indexType, AccessFlags.ExpressionPosition, name); - type = getFlowTypeOfDestructuring(declaration, declaredType); + const literalMembers: PropertyName[] = []; + for (const element of pattern.elements) { + if (!element.dotDotDotToken) { + literalMembers.push(element.propertyName || element.name as Identifier); + } } + type = getRestType(parentType, literalMembers, declaration.symbol); } else { - // This elementType will be used if the specific property corresponding to this index is not - // present (aka the tuple element property). This call also checks that the parentType is in - // fact an iterable or array (depending on target language). - const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring | (declaration.dotDotDotToken ? 0 : IterationUse.PossiblyOutOfBounds), parentType, undefinedType, pattern); - const index = pattern.elements.indexOf(declaration); - if (declaration.dotDotDotToken) { - // If the parent is a tuple type, the rest element has a tuple type of the - // remaining tuple element types. Otherwise, the rest element has an array type with same - // element type as the parent type. - type = everyType(parentType, isTupleType) ? - mapType(parentType, t => sliceTupleType(t as TupleTypeReference, index)) : - createArrayType(elementType); - } - else if (isArrayLikeType(parentType)) { - const indexType = getNumberLiteralType(index); - const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0); - const declaredType = getIndexedAccessTypeOrUndefined(parentType, indexType, accessFlags, declaration.name) || errorType; - type = getFlowTypeOfDestructuring(declaration, declaredType); - } - else { - type = elementType; - } - } - if (!declaration.initializer) { - return type; - } - if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) { - // In strict null checking mode, if a default value of a non-undefined type is specified, remove - // undefined from the final type. - return strictNullChecks && !(getTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal)) & TypeFacts.IsUndefined) ? getNonUndefinedType(type) : type; + // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) + const name = declaration.propertyName || declaration.name as Identifier; + const indexType = getLiteralTypeFromPropertyName(name); + const declaredType = getIndexedAccessType(parentType, indexType, AccessFlags.ExpressionPosition, name); + type = getFlowTypeOfDestructuring(declaration, declaredType); } - return widenTypeInferredFromInitializer(declaration, getUnionType([getNonUndefinedType(type), checkDeclarationInitializer(declaration, CheckMode.Normal)], UnionReduction.Subtype)); } - - function getTypeForDeclarationFromJSDocComment(declaration: Node) { - const jsdocType = getJSDocType(declaration); - if (jsdocType) { - return getTypeFromTypeNode(jsdocType); + else { + // This elementType will be used if the specific property corresponding to this index is not + // present (aka the tuple element property). This call also checks that the parentType is in + // fact an iterable or array (depending on target language). + const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring | (declaration.dotDotDotToken ? 0 : IterationUse.PossiblyOutOfBounds), parentType, undefinedType, pattern); + const index = pattern.elements.indexOf(declaration); + if (declaration.dotDotDotToken) { + // If the parent is a tuple type, the rest element has a tuple type of the + // remaining tuple element types. Otherwise, the rest element has an array type with same + // element type as the parent type. + type = everyType(parentType, isTupleType) ? + mapType(parentType, t => sliceTupleType(t as TupleTypeReference, index)) : + createArrayType(elementType); + } + else if (isArrayLikeType(parentType)) { + const indexType = getNumberLiteralType(index); + const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0); + const declaredType = getIndexedAccessTypeOrUndefined(parentType, indexType, accessFlags, declaration.name) || errorType; + type = getFlowTypeOfDestructuring(declaration, declaredType); + } + else { + type = elementType; } - return undefined; } - - function isNullOrUndefined(node: Expression) { - const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); - return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr as Identifier) === undefinedSymbol; + if (!declaration.initializer) { + return type; } - - function isEmptyArrayLiteral(node: Expression) { - const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); - return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr as ArrayLiteralExpression).elements.length === 0; + if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) { + // In strict null checking mode, if a default value of a non-undefined type is specified, remove + // undefined from the final type. + return strictNullChecks && !(getTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal)) & TypeFacts.IsUndefined) ? getNonUndefinedType(type) : type; } + return widenTypeInferredFromInitializer(declaration, getUnionType([getNonUndefinedType(type), checkDeclarationInitializer(declaration, CheckMode.Normal)], UnionReduction.Subtype)); + } - function addOptionality(type: Type, isProperty = false, isOptional = true): Type { - return strictNullChecks && isOptional ? getOptionalType(type, isProperty) : type; + function getTypeForDeclarationFromJSDocComment(declaration: Node) { + const jsdocType = getJSDocType(declaration); + if (jsdocType) { + return getTypeFromTypeNode(jsdocType); } + return undefined; + } - // Return the inferred type for a variable, parameter, or property declaration - function getTypeForVariableLikeDeclaration( - declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, - includeOptionality: boolean, - checkMode: CheckMode, - ): Type | undefined { - // A variable declared in a for..in statement is of type string, or of type keyof T when the - // right hand expression is of a type parameter type. - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) { - const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression, /*checkMode*/ checkMode))); - return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType; - } - - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { - // checkRightHandSideOfForOf will return undefined if the for-of expression type was - // missing properties/signatures required to get its iteratedType (like - // [Symbol.iterator] or next). This may be because we accessed properties from anyType, - // or it may have led to an error inside getElementTypeOfIterable. - const forOfStatement = declaration.parent.parent; - return checkRightHandSideOfForOf(forOfStatement) || anyType; - } - - if (isBindingPattern(declaration.parent)) { - return getTypeForBindingElement(declaration as BindingElement); - } - - const isProperty = isPropertyDeclaration(declaration) && !hasAccessorModifier(declaration) || isPropertySignature(declaration); - const isOptional = includeOptionality && ( - isProperty && !!declaration.questionToken || - isParameter(declaration) && (!!declaration.questionToken || isJSDocOptionalParameter(declaration)) || - isOptionalJSDocPropertyLikeTag(declaration)); - - // Use type from type annotation if one is present - const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); - if (declaredType) { - return addOptionality(declaredType, isProperty, isOptional); - } + function isNullOrUndefined(node: Expression) { + const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr as Identifier) === undefinedSymbol; + } - if ((noImplicitAny || isInJSFile(declaration)) && - isVariableDeclaration(declaration) && !isBindingPattern(declaration.name) && - !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) && !(declaration.flags & NodeFlags.Ambient)) { - // If --noImplicitAny is on or the declaration is in a Javascript file, - // use control flow tracked 'any' type for non-ambient, non-exported var or let variables with no - // initializer or a 'null' or 'undefined' initializer. - if (!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && (!declaration.initializer || isNullOrUndefined(declaration.initializer))) { - return autoType; - } - // Use control flow tracked 'any[]' type for non-ambient, non-exported variables with an empty array - // literal initializer. - if (declaration.initializer && isEmptyArrayLiteral(declaration.initializer)) { - return autoArrayType; - } - } + function isEmptyArrayLiteral(node: Expression) { + const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr as ArrayLiteralExpression).elements.length === 0; + } - if (isParameter(declaration)) { - const func = declaration.parent as FunctionLikeDeclaration; - // For a parameter of a set accessor, use the type of the get accessor if one is present - if (func.kind === SyntaxKind.SetAccessor && hasBindableName(func)) { - const getter = getDeclarationOfKind(getSymbolOfNode(declaration.parent), SyntaxKind.GetAccessor); - if (getter) { - const getterSignature = getSignatureFromDeclaration(getter); - const thisParameter = getAccessorThisParameter(func as AccessorDeclaration); - if (thisParameter && declaration === thisParameter) { - // Use the type from the *getter* - Debug.assert(!thisParameter.type); - return getTypeOfSymbol(getterSignature.thisParameter!); - } - return getReturnTypeOfSignature(getterSignature); - } - } - const parameterTypeOfTypeTag = getParameterTypeOfTypeTag(func, declaration); - if (parameterTypeOfTypeTag) return parameterTypeOfTypeTag; - // Use contextual parameter type if one is available - const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration); - if (type) { - return addOptionality(type, /*isProperty*/ false, isOptional); - } - } + function addOptionality(type: Type, isProperty = false, isOptional = true): Type { + return strictNullChecks && isOptional ? getOptionalType(type, isProperty) : type; + } - // Use the type of the initializer expression if one is present and the declaration is - // not a parameter of a contextually typed function - if (hasOnlyExpressionInitializer(declaration) && !!declaration.initializer) { - if (isInJSFile(declaration) && !isParameter(declaration)) { - const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfNode(declaration), getDeclaredExpandoInitializer(declaration)); - if (containerObjectType) { - return containerObjectType; - } - } - const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode)); - return addOptionality(type, isProperty, isOptional); + // Return the inferred type for a variable, parameter, or property declaration + function getTypeForVariableLikeDeclaration( + declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, + includeOptionality: boolean, + checkMode: CheckMode, + ): Type | undefined { + // A variable declared in a for..in statement is of type string, or of type keyof T when the + // right hand expression is of a type parameter type. + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForInStatement) { + const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression, /*checkMode*/ checkMode))); + return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType; + } + + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { + // checkRightHandSideOfForOf will return undefined if the for-of expression type was + // missing properties/signatures required to get its iteratedType (like + // [Symbol.iterator] or next). This may be because we accessed properties from anyType, + // or it may have led to an error inside getElementTypeOfIterable. + const forOfStatement = declaration.parent.parent; + return checkRightHandSideOfForOf(forOfStatement) || anyType; + } + + if (isBindingPattern(declaration.parent)) { + return getTypeForBindingElement(declaration as BindingElement); + } + + const isProperty = isPropertyDeclaration(declaration) && !hasAccessorModifier(declaration) || isPropertySignature(declaration); + const isOptional = includeOptionality && ( + isProperty && !!declaration.questionToken || + isParameter(declaration) && (!!declaration.questionToken || isJSDocOptionalParameter(declaration)) || + isOptionalJSDocPropertyLikeTag(declaration)); + + // Use type from type annotation if one is present + const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); + if (declaredType) { + return addOptionality(declaredType, isProperty, isOptional); + } + + if ((noImplicitAny || isInJSFile(declaration)) && + isVariableDeclaration(declaration) && !isBindingPattern(declaration.name) && + !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) && !(declaration.flags & NodeFlags.Ambient)) { + // If --noImplicitAny is on or the declaration is in a Javascript file, + // use control flow tracked 'any' type for non-ambient, non-exported var or let variables with no + // initializer or a 'null' or 'undefined' initializer. + if (!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && (!declaration.initializer || isNullOrUndefined(declaration.initializer))) { + return autoType; + } + // Use control flow tracked 'any[]' type for non-ambient, non-exported variables with an empty array + // literal initializer. + if (declaration.initializer && isEmptyArrayLiteral(declaration.initializer)) { + return autoArrayType; + } + } + + if (isParameter(declaration)) { + const func = declaration.parent as FunctionLikeDeclaration; + // For a parameter of a set accessor, use the type of the get accessor if one is present + if (func.kind === SyntaxKind.SetAccessor && hasBindableName(func)) { + const getter = getDeclarationOfKind(getSymbolOfNode(declaration.parent), SyntaxKind.GetAccessor); + if (getter) { + const getterSignature = getSignatureFromDeclaration(getter); + const thisParameter = getAccessorThisParameter(func as AccessorDeclaration); + if (thisParameter && declaration === thisParameter) { + // Use the type from the *getter* + Debug.assert(!thisParameter.type); + return getTypeOfSymbol(getterSignature.thisParameter!); + } + return getReturnTypeOfSignature(getterSignature); + } + } + const parameterTypeOfTypeTag = getParameterTypeOfTypeTag(func, declaration); + if (parameterTypeOfTypeTag) return parameterTypeOfTypeTag; + // Use contextual parameter type if one is available + const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration); + if (type) { + return addOptionality(type, /*isProperty*/ false, isOptional); } + } - if (isPropertyDeclaration(declaration) && (noImplicitAny || isInJSFile(declaration))) { - // We have a property declaration with no type annotation or initializer, in noImplicitAny mode or a .js file. - // Use control flow analysis of this.xxx assignments in the constructor or static block to determine the type of the property. - if (!hasStaticModifier(declaration)) { - const constructor = findConstructorDeclaration(declaration.parent); - const type = constructor ? getFlowTypeInConstructor(declaration.symbol, constructor) : - getEffectiveModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : - undefined; - return type && addOptionality(type, /*isProperty*/ true, isOptional); - } - else { - const staticBlocks = filter(declaration.parent.members, isClassStaticBlockDeclaration); - const type = staticBlocks.length ? getFlowTypeInStaticBlocks(declaration.symbol, staticBlocks) : - getEffectiveModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : - undefined; - return type && addOptionality(type, /*isProperty*/ true, isOptional); + // Use the type of the initializer expression if one is present and the declaration is + // not a parameter of a contextually typed function + if (hasOnlyExpressionInitializer(declaration) && !!declaration.initializer) { + if (isInJSFile(declaration) && !isParameter(declaration)) { + const containerObjectType = getJSContainerObjectType(declaration, getSymbolOfNode(declaration), getDeclaredExpandoInitializer(declaration)); + if (containerObjectType) { + return containerObjectType; } } + const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode)); + return addOptionality(type, isProperty, isOptional); + } - if (isJsxAttribute(declaration)) { - // if JSX attribute doesn't have initializer, by default the attribute will have boolean value of true. - // I.e is sugar for - return trueType; + if (isPropertyDeclaration(declaration) && (noImplicitAny || isInJSFile(declaration))) { + // We have a property declaration with no type annotation or initializer, in noImplicitAny mode or a .js file. + // Use control flow analysis of this.xxx assignments in the constructor or static block to determine the type of the property. + if (!hasStaticModifier(declaration)) { + const constructor = findConstructorDeclaration(declaration.parent); + const type = constructor ? getFlowTypeInConstructor(declaration.symbol, constructor) : + getEffectiveModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : + undefined; + return type && addOptionality(type, /*isProperty*/ true, isOptional); } - - // If the declaration specifies a binding pattern and is not a parameter of a contextually - // typed function, use the type implied by the binding pattern - if (isBindingPattern(declaration.name)) { - return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true); + else { + const staticBlocks = filter(declaration.parent.members, isClassStaticBlockDeclaration); + const type = staticBlocks.length ? getFlowTypeInStaticBlocks(declaration.symbol, staticBlocks) : + getEffectiveModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : + undefined; + return type && addOptionality(type, /*isProperty*/ true, isOptional); } - - // No type specified and nothing can be inferred - return undefined; } - function isConstructorDeclaredProperty(symbol: Symbol) { - // A property is considered a constructor declared property when all declaration sites are this.xxx assignments, - // when no declaration sites have JSDoc type annotations, and when at least one declaration site is in the body of - // a class constructor. - if (symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration)) { - const links = getSymbolLinks(symbol); - if (links.isConstructorDeclaredProperty === undefined) { - links.isConstructorDeclaredProperty = false; - links.isConstructorDeclaredProperty = !!getDeclaringConstructor(symbol) && every(symbol.declarations, declaration => - isBinaryExpression(declaration) && - isPossiblyAliasedThisProperty(declaration) && - (declaration.left.kind !== SyntaxKind.ElementAccessExpression || isStringOrNumericLiteralLike((declaration.left as ElementAccessExpression).argumentExpression)) && - !getAnnotatedTypeForAssignmentDeclaration(/*declaredType*/ undefined, declaration, symbol, declaration)); - } - return links.isConstructorDeclaredProperty; - } - return false; + if (isJsxAttribute(declaration)) { + // if JSX attribute doesn't have initializer, by default the attribute will have boolean value of true. + // I.e is sugar for + return trueType; } - function isAutoTypedProperty(symbol: Symbol) { - // A property is auto-typed when its declaration has no type annotation or initializer and we're in - // noImplicitAny mode or a .js file. - const declaration = symbol.valueDeclaration; - return declaration && isPropertyDeclaration(declaration) && !getEffectiveTypeAnnotationNode(declaration) && - !declaration.initializer && (noImplicitAny || isInJSFile(declaration)); + // If the declaration specifies a binding pattern and is not a parameter of a contextually + // typed function, use the type implied by the binding pattern + if (isBindingPattern(declaration.name)) { + return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true); } - function getDeclaringConstructor(symbol: Symbol) { - if (!symbol.declarations) { - return; - } - for (const declaration of symbol.declarations) { - const container = getThisContainer(declaration, /*includeArrowFunctions*/ false); - if (container && (container.kind === SyntaxKind.Constructor || isJSConstructor(container))) { - return container as ConstructorDeclaration; - } - } - } + // No type specified and nothing can be inferred + return undefined; + } - /** Create a synthetic property access flow node after the last statement of the file */ - function getFlowTypeFromCommonJSExport(symbol: Symbol) { - const file = getSourceFileOfNode(symbol.declarations![0]); - const accessName = unescapeLeadingUnderscores(symbol.escapedName); - const areAllModuleExports = symbol.declarations!.every(d => isInJSFile(d) && isAccessExpression(d) && isModuleExportsAccessExpression(d.expression)); - const reference = areAllModuleExports - ? factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier("module"), factory.createIdentifier("exports")), accessName) - : factory.createPropertyAccessExpression(factory.createIdentifier("exports"), accessName); - if (areAllModuleExports) { - setParent((reference.expression as PropertyAccessExpression).expression, reference.expression); + function isConstructorDeclaredProperty(symbol: Symbol) { + // A property is considered a constructor declared property when all declaration sites are this.xxx assignments, + // when no declaration sites have JSDoc type annotations, and when at least one declaration site is in the body of + // a class constructor. + if (symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration)) { + const links = getSymbolLinks(symbol); + if (links.isConstructorDeclaredProperty === undefined) { + links.isConstructorDeclaredProperty = false; + links.isConstructorDeclaredProperty = !!getDeclaringConstructor(symbol) && every(symbol.declarations, declaration => + isBinaryExpression(declaration) && + isPossiblyAliasedThisProperty(declaration) && + (declaration.left.kind !== SyntaxKind.ElementAccessExpression || isStringOrNumericLiteralLike((declaration.left as ElementAccessExpression).argumentExpression)) && + !getAnnotatedTypeForAssignmentDeclaration(/*declaredType*/ undefined, declaration, symbol, declaration)); } - setParent(reference.expression, reference); - setParent(reference, file); - reference.flowNode = file.endFlowNode; - return getFlowTypeOfReference(reference, autoType, undefinedType); + return links.isConstructorDeclaredProperty; } + return false; + } - function getFlowTypeInStaticBlocks(symbol: Symbol, staticBlocks: readonly ClassStaticBlockDeclaration[]) { - const accessName = startsWith(symbol.escapedName as string, "__#") - ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) - : unescapeLeadingUnderscores(symbol.escapedName); - for (const staticBlock of staticBlocks) { - const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName); - setParent(reference.expression, reference); - setParent(reference, staticBlock); - reference.flowNode = staticBlock.returnFlowNode; - const flowType = getFlowTypeOfProperty(reference, symbol); - if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { - error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); - } - // We don't infer a type if assignments are only null or undefined. - if (everyType(flowType, isNullableType)) { - continue; - } - return convertAutoToAny(flowType); + function isAutoTypedProperty(symbol: Symbol) { + // A property is auto-typed when its declaration has no type annotation or initializer and we're in + // noImplicitAny mode or a .js file. + const declaration = symbol.valueDeclaration; + return declaration && isPropertyDeclaration(declaration) && !getEffectiveTypeAnnotationNode(declaration) && + !declaration.initializer && (noImplicitAny || isInJSFile(declaration)); + } + + function getDeclaringConstructor(symbol: Symbol) { + if (!symbol.declarations) { + return; + } + for (const declaration of symbol.declarations) { + const container = getThisContainer(declaration, /*includeArrowFunctions*/ false); + if (container && (container.kind === SyntaxKind.Constructor || isJSConstructor(container))) { + return container as ConstructorDeclaration; } } + } + + /** Create a synthetic property access flow node after the last statement of the file */ + function getFlowTypeFromCommonJSExport(symbol: Symbol) { + const file = getSourceFileOfNode(symbol.declarations![0]); + const accessName = unescapeLeadingUnderscores(symbol.escapedName); + const areAllModuleExports = symbol.declarations!.every(d => isInJSFile(d) && isAccessExpression(d) && isModuleExportsAccessExpression(d.expression)); + const reference = areAllModuleExports + ? factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier("module"), factory.createIdentifier("exports")), accessName) + : factory.createPropertyAccessExpression(factory.createIdentifier("exports"), accessName); + if (areAllModuleExports) { + setParent((reference.expression as PropertyAccessExpression).expression, reference.expression); + } + setParent(reference.expression, reference); + setParent(reference, file); + reference.flowNode = file.endFlowNode; + return getFlowTypeOfReference(reference, autoType, undefinedType); + } - function getFlowTypeInConstructor(symbol: Symbol, constructor: ConstructorDeclaration) { - const accessName = startsWith(symbol.escapedName as string, "__#") - ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) - : unescapeLeadingUnderscores(symbol.escapedName); + function getFlowTypeInStaticBlocks(symbol: Symbol, staticBlocks: readonly ClassStaticBlockDeclaration[]) { + const accessName = startsWith(symbol.escapedName as string, "__#") + ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) + : unescapeLeadingUnderscores(symbol.escapedName); + for (const staticBlock of staticBlocks) { const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName); setParent(reference.expression, reference); - setParent(reference, constructor); - reference.flowNode = constructor.returnFlowNode; + setParent(reference, staticBlock); + reference.flowNode = staticBlock.returnFlowNode; const flowType = getFlowTypeOfProperty(reference, symbol); if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); } // We don't infer a type if assignments are only null or undefined. - return everyType(flowType, isNullableType) ? undefined : convertAutoToAny(flowType); + if (everyType(flowType, isNullableType)) { + continue; + } + return convertAutoToAny(flowType); } + } - function getFlowTypeOfProperty(reference: Node, prop: Symbol | undefined) { - const initialType = prop?.valueDeclaration - && (!isAutoTypedProperty(prop) || getEffectiveModifierFlags(prop.valueDeclaration) & ModifierFlags.Ambient) - && getTypeOfPropertyInBaseClass(prop) - || undefinedType; - return getFlowTypeOfReference(reference, autoType, initialType); - } + function getFlowTypeInConstructor(symbol: Symbol, constructor: ConstructorDeclaration) { + const accessName = startsWith(symbol.escapedName as string, "__#") + ? factory.createPrivateIdentifier((symbol.escapedName as string).split("@")[1]) + : unescapeLeadingUnderscores(symbol.escapedName); + const reference = factory.createPropertyAccessExpression(factory.createThis(), accessName); + setParent(reference.expression, reference); + setParent(reference, constructor); + reference.flowNode = constructor.returnFlowNode; + const flowType = getFlowTypeOfProperty(reference, symbol); + if (noImplicitAny && (flowType === autoType || flowType === autoArrayType)) { + error(symbol.valueDeclaration, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); + } + // We don't infer a type if assignments are only null or undefined. + return everyType(flowType, isNullableType) ? undefined : convertAutoToAny(flowType); + } - function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) { - // function/class/{} initializers are themselves containers, so they won't merge in the same way as other initializers - const container = getAssignedExpandoInitializer(symbol.valueDeclaration); - if (container) { - const tag = getJSDocTypeTag(container); - if (tag && tag.typeExpression) { - return getTypeFromTypeNode(tag.typeExpression); - } - const containerObjectType = symbol.valueDeclaration && getJSContainerObjectType(symbol.valueDeclaration, symbol, container); - return containerObjectType || getWidenedLiteralType(checkExpressionCached(container)); - } - let type; - let definedInConstructor = false; - let definedInMethod = false; - // We use control flow analysis to determine the type of the property if the property qualifies as a constructor - // declared property and the resulting control flow type isn't just undefined or null. - if (isConstructorDeclaredProperty(symbol)) { - type = getFlowTypeInConstructor(symbol, getDeclaringConstructor(symbol)!); - } - if (!type) { - let types: Type[] | undefined; - if (symbol.declarations) { - let jsdocType: Type | undefined; - for (const declaration of symbol.declarations) { - const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : - isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : - undefined; - if (!expression) { - continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere - } - - const kind = isAccessExpression(expression) - ? getAssignmentDeclarationPropertyAccessKind(expression) - : getAssignmentDeclarationKind(expression); - if (kind === AssignmentDeclarationKind.ThisProperty || isBinaryExpression(expression) && isPossiblyAliasedThisProperty(expression, kind)) { - if (isDeclarationInConstructor(expression)) { - definedInConstructor = true; - } - else { - definedInMethod = true; - } - } - if (!isCallExpression(expression)) { - jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); - } - if (!jsdocType) { - (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); - } + function getFlowTypeOfProperty(reference: Node, prop: Symbol | undefined) { + const initialType = prop?.valueDeclaration + && (!isAutoTypedProperty(prop) || getEffectiveModifierFlags(prop.valueDeclaration) & ModifierFlags.Ambient) + && getTypeOfPropertyInBaseClass(prop) + || undefinedType; + return getFlowTypeOfReference(reference, autoType, initialType); + } + + function getWidenedTypeForAssignmentDeclaration(symbol: Symbol, resolvedSymbol?: Symbol) { + // function/class/{} initializers are themselves containers, so they won't merge in the same way as other initializers + const container = getAssignedExpandoInitializer(symbol.valueDeclaration); + if (container) { + const tag = getJSDocTypeTag(container); + if (tag && tag.typeExpression) { + return getTypeFromTypeNode(tag.typeExpression); + } + const containerObjectType = symbol.valueDeclaration && getJSContainerObjectType(symbol.valueDeclaration, symbol, container); + return containerObjectType || getWidenedLiteralType(checkExpressionCached(container)); + } + let type; + let definedInConstructor = false; + let definedInMethod = false; + // We use control flow analysis to determine the type of the property if the property qualifies as a constructor + // declared property and the resulting control flow type isn't just undefined or null. + if (isConstructorDeclaredProperty(symbol)) { + type = getFlowTypeInConstructor(symbol, getDeclaringConstructor(symbol)!); + } + if (!type) { + let types: Type[] | undefined; + if (symbol.declarations) { + let jsdocType: Type | undefined; + for (const declaration of symbol.declarations) { + const expression = (isBinaryExpression(declaration) || isCallExpression(declaration)) ? declaration : + isAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration : + undefined; + if (!expression) { + continue; // Non-assignment declaration merged in (eg, an Identifier to mark the thing as a namespace) - skip over it and pull type info from elsewhere } - type = jsdocType; - } - if (!type) { - if (!length(types)) { - return errorType; // No types from any declarations :( - } - let constructorTypes = definedInConstructor && symbol.declarations ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; - // use only the constructor types unless they were only assigned null | undefined (including widening variants) - if (definedInMethod) { - const propType = getTypeOfPropertyInBaseClass(symbol); - if (propType) { - (constructorTypes || (constructorTypes = [])).push(propType); + + const kind = isAccessExpression(expression) + ? getAssignmentDeclarationPropertyAccessKind(expression) + : getAssignmentDeclarationKind(expression); + if (kind === AssignmentDeclarationKind.ThisProperty || isBinaryExpression(expression) && isPossiblyAliasedThisProperty(expression, kind)) { + if (isDeclarationInConstructor(expression)) { definedInConstructor = true; } + else { + definedInMethod = true; + } + } + if (!isCallExpression(expression)) { + jsdocType = getAnnotatedTypeForAssignmentDeclaration(jsdocType, expression, symbol, declaration); + } + if (!jsdocType) { + (types || (types = [])).push((isBinaryExpression(expression) || isCallExpression(expression)) ? getInitializerTypeFromAssignmentDeclaration(symbol, resolvedSymbol, expression, kind) : neverType); } - const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217 - type = getUnionType(sourceTypes!); - } - } - const widened = getWidenedType(addOptionality(type, /*isProperty*/ false, definedInMethod && !definedInConstructor)); - if (symbol.valueDeclaration && filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) { - reportImplicitAny(symbol.valueDeclaration, anyType); - return anyType; - } - return widened; - } - - function getJSContainerObjectType(decl: Node, symbol: Symbol, init: Expression | undefined): Type | undefined { - if (!isInJSFile(decl) || !init || !isObjectLiteralExpression(init) || init.properties.length) { - return undefined; - } - const exports = createSymbolTable(); - while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) { - const s = getSymbolOfNode(decl); - if (s?.exports?.size) { - mergeSymbolTable(exports, s.exports); } - decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent; - } - const s = getSymbolOfNode(decl); - if (s?.exports?.size) { - mergeSymbolTable(exports, s.exports); + type = jsdocType; } - const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, emptyArray); - type.objectFlags |= ObjectFlags.JSLiteral; - return type; - } - - function getAnnotatedTypeForAssignmentDeclaration(declaredType: Type | undefined, expression: Expression, symbol: Symbol, declaration: Declaration) { - const typeNode = getEffectiveTypeAnnotationNode(expression.parent); - if (typeNode) { - const type = getWidenedType(getTypeFromTypeNode(typeNode)); - if (!declaredType) { - return type; - } - else if (!isErrorType(declaredType) && !isErrorType(type) && !isTypeIdenticalTo(declaredType, type)) { - errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type); + if (!type) { + if (!length(types)) { + return errorType; // No types from any declarations :( } - } - if (symbol.parent?.valueDeclaration) { - const typeNode = getEffectiveTypeAnnotationNode(symbol.parent.valueDeclaration); - if (typeNode) { - const annotationSymbol = getPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); - if (annotationSymbol) { - return getNonMissingTypeOfSymbol(annotationSymbol); + let constructorTypes = definedInConstructor && symbol.declarations ? getConstructorDefinedThisAssignmentTypes(types!, symbol.declarations) : undefined; + // use only the constructor types unless they were only assigned null | undefined (including widening variants) + if (definedInMethod) { + const propType = getTypeOfPropertyInBaseClass(symbol); + if (propType) { + (constructorTypes || (constructorTypes = [])).push(propType); + definedInConstructor = true; } } + const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217 + type = getUnionType(sourceTypes!); } - - return declaredType; } + const widened = getWidenedType(addOptionality(type, /*isProperty*/ false, definedInMethod && !definedInConstructor)); + if (symbol.valueDeclaration && filterType(widened, t => !!(t.flags & ~TypeFlags.Nullable)) === neverType) { + reportImplicitAny(symbol.valueDeclaration, anyType); + return anyType; + } + return widened; + } - /** If we don't have an explicit JSDoc type, get the type from the initializer. */ - function getInitializerTypeFromAssignmentDeclaration(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: BinaryExpression | CallExpression, kind: AssignmentDeclarationKind) { - if (isCallExpression(expression)) { - if (resolvedSymbol) { - return getTypeOfSymbol(resolvedSymbol); // This shouldn't happen except under some hopefully forbidden merges of export assignments and object define assignments - } - const objectLitType = checkExpressionCached((expression as BindableObjectDefinePropertyCall).arguments[2]); - const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); - if (valueType) { - return valueType; - } - const getFunc = getTypeOfPropertyOfType(objectLitType, "get" as __String); - if (getFunc) { - const getSig = getSingleCallSignature(getFunc); - if (getSig) { - return getReturnTypeOfSignature(getSig); - } - } - const setFunc = getTypeOfPropertyOfType(objectLitType, "set" as __String); - if (setFunc) { - const setSig = getSingleCallSignature(setFunc); - if (setSig) { - return getTypeOfFirstParameterOfSignature(setSig); - } - } - return anyType; - } - if (containsSameNamedThisProperty(expression.left, expression.right)) { - return anyType; - } - const isDirectExport = kind === AssignmentDeclarationKind.ExportsProperty && (isPropertyAccessExpression(expression.left) || isElementAccessExpression(expression.left)) && (isModuleExportsAccessExpression(expression.left.expression) || (isIdentifier(expression.left.expression) && isExportsIdentifier(expression.left.expression))); - const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) - : isDirectExport ? getRegularTypeOfLiteralType(checkExpressionCached(expression.right)) - : getWidenedLiteralType(checkExpressionCached(expression.right)); - if (type.flags & TypeFlags.Object && - kind === AssignmentDeclarationKind.ModuleExports && - symbol.escapedName === InternalSymbolName.ExportEquals) { - const exportedType = resolveStructuredTypeMembers(type as ObjectType); - const members = createSymbolTable(); - copyEntries(exportedType.members, members); - const initialSize = members.size; - if (resolvedSymbol && !resolvedSymbol.exports) { - resolvedSymbol.exports = createSymbolTable(); - } - (resolvedSymbol || symbol).exports!.forEach((s, name) => { - const exportedMember = members.get(name)!; - if (exportedMember && exportedMember !== s && !(s.flags & SymbolFlags.Alias)) { - if (s.flags & SymbolFlags.Value && exportedMember.flags & SymbolFlags.Value) { - // If the member has an additional value-like declaration, union the types from the two declarations, - // but issue an error if they occurred in two different files. The purpose is to support a JS file with - // a pattern like: - // - // module.exports = { a: true }; - // module.exports.a = 3; - // - // but we may have a JS file with `module.exports = { a: true }` along with a TypeScript module augmentation - // declaring an `export const a: number`. In that case, we issue a duplicate identifier error, because - // it's unclear what that's supposed to mean, so it's probably a mistake. - if (s.valueDeclaration && exportedMember.valueDeclaration && getSourceFileOfNode(s.valueDeclaration) !== getSourceFileOfNode(exportedMember.valueDeclaration)) { - const unescapedName = unescapeLeadingUnderscores(s.escapedName); - const exportedMemberName = tryCast(exportedMember.valueDeclaration, isNamedDeclaration)?.name || exportedMember.valueDeclaration; - addRelatedInfo( - error(s.valueDeclaration, Diagnostics.Duplicate_identifier_0, unescapedName), - createDiagnosticForNode(exportedMemberName, Diagnostics._0_was_also_declared_here, unescapedName)); - addRelatedInfo( - error(exportedMemberName, Diagnostics.Duplicate_identifier_0, unescapedName), - createDiagnosticForNode(s.valueDeclaration, Diagnostics._0_was_also_declared_here, unescapedName)); - } - const union = createSymbol(s.flags | exportedMember.flags, name); - union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]); - union.valueDeclaration = exportedMember.valueDeclaration; - union.declarations = concatenate(exportedMember.declarations, s.declarations); - members.set(name, union); - } - else { - members.set(name, mergeSymbol(s, exportedMember)); - } - } - else { - members.set(name, s); - } - }); - const result = createAnonymousType( - initialSize !== members.size ? undefined : exportedType.symbol, // Only set the type's symbol if it looks to be the same as the original type - members, - exportedType.callSignatures, - exportedType.constructSignatures, - exportedType.indexInfos); - if (initialSize === members.size) { - if (type.aliasSymbol) { - result.aliasSymbol = type.aliasSymbol; - result.aliasTypeArguments = type.aliasTypeArguments; - } - if (getObjectFlags(type) & ObjectFlags.Reference) { - result.aliasSymbol = (type as TypeReference).symbol; - const args = getTypeArguments(type as TypeReference); - result.aliasTypeArguments = length(args) ? args : undefined; - } - } - result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag - if (result.symbol && result.symbol.flags & SymbolFlags.Class && type === getDeclaredTypeOfClassOrInterface(result.symbol)) { - result.objectFlags |= ObjectFlags.IsClassInstanceClone; // Propagate the knowledge that this type is equivalent to the symbol's class instance type - } - return result; - } - if (isEmptyArrayLiteralType(type)) { - reportImplicitAny(expression, anyArrayType); - return anyArrayType; + function getJSContainerObjectType(decl: Node, symbol: Symbol, init: Expression | undefined): Type | undefined { + if (!isInJSFile(decl) || !init || !isObjectLiteralExpression(init) || init.properties.length) { + return undefined; + } + const exports = createSymbolTable(); + while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) { + const s = getSymbolOfNode(decl); + if (s?.exports?.size) { + mergeSymbolTable(exports, s.exports); } - return type; + decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent; } - - function containsSameNamedThisProperty(thisProperty: Expression, expression: Expression) { - return isPropertyAccessExpression(thisProperty) - && thisProperty.expression.kind === SyntaxKind.ThisKeyword - && forEachChildRecursively(expression, n => isMatchingReference(thisProperty, n)); + const s = getSymbolOfNode(decl); + if (s?.exports?.size) { + mergeSymbolTable(exports, s.exports); } + const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, emptyArray); + type.objectFlags |= ObjectFlags.JSLiteral; + return type; + } - function isDeclarationInConstructor(expression: Expression) { - const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false); - // Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added. - // Function expressions that are assigned to the prototype count as methods. - return thisContainer.kind === SyntaxKind.Constructor || - thisContainer.kind === SyntaxKind.FunctionDeclaration || - (thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent)); + function getAnnotatedTypeForAssignmentDeclaration(declaredType: Type | undefined, expression: Expression, symbol: Symbol, declaration: Declaration) { + const typeNode = getEffectiveTypeAnnotationNode(expression.parent); + if (typeNode) { + const type = getWidenedType(getTypeFromTypeNode(typeNode)); + if (!declaredType) { + return type; + } + else if (!isErrorType(declaredType) && !isErrorType(type) && !isTypeIdenticalTo(declaredType, type)) { + errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type); + } } - - function getConstructorDefinedThisAssignmentTypes(types: Type[], declarations: Declaration[]): Type[] | undefined { - Debug.assert(types.length === declarations.length); - return types.filter((_, i) => { - const declaration = declarations[i]; - const expression = isBinaryExpression(declaration) ? declaration : - isBinaryExpression(declaration.parent) ? declaration.parent : undefined; - return expression && isDeclarationInConstructor(expression); - }); + if (symbol.parent?.valueDeclaration) { + const typeNode = getEffectiveTypeAnnotationNode(symbol.parent.valueDeclaration); + if (typeNode) { + const annotationSymbol = getPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); + if (annotationSymbol) { + return getNonMissingTypeOfSymbol(annotationSymbol); + } + } } - // Return the type implied by a binding pattern element. This is the type of the initializer of the element if - // one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding - // pattern. Otherwise, it is the type any. - function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type { - if (element.initializer) { - // The type implied by a binding pattern is independent of context, so we check the initializer with no - // contextual type or, if the element itself is a binding pattern, with the type implied by that binding - // pattern. - const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType; - return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType))); + return declaredType; + } + + /** If we don't have an explicit JSDoc type, get the type from the initializer. */ + function getInitializerTypeFromAssignmentDeclaration(symbol: Symbol, resolvedSymbol: Symbol | undefined, expression: BinaryExpression | CallExpression, kind: AssignmentDeclarationKind) { + if (isCallExpression(expression)) { + if (resolvedSymbol) { + return getTypeOfSymbol(resolvedSymbol); // This shouldn't happen except under some hopefully forbidden merges of export assignments and object define assignments + } + const objectLitType = checkExpressionCached((expression as BindableObjectDefinePropertyCall).arguments[2]); + const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); + if (valueType) { + return valueType; } - if (isBindingPattern(element.name)) { - return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors); + const getFunc = getTypeOfPropertyOfType(objectLitType, "get" as __String); + if (getFunc) { + const getSig = getSingleCallSignature(getFunc); + if (getSig) { + return getReturnTypeOfSignature(getSig); + } } - if (reportErrors && !declarationBelongsToPrivateAmbientMember(element)) { - reportImplicitAny(element, anyType); + const setFunc = getTypeOfPropertyOfType(objectLitType, "set" as __String); + if (setFunc) { + const setSig = getSingleCallSignature(setFunc); + if (setSig) { + return getTypeOfFirstParameterOfSignature(setSig); + } } - // When we're including the pattern in the type (an indication we're obtaining a contextual type), we - // use a non-inferrable any type. Inference will never directly infer this type, but it is possible - // to infer a type that contains it, e.g. for a binding pattern like [foo] or { foo }. In such cases, - // widening of the binding pattern type substitutes a regular any for the non-inferrable any. - return includePatternInType ? nonInferrableAnyType : anyType; + return anyType; } - - // Return the type implied by an object binding pattern - function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { + if (containsSameNamedThisProperty(expression.left, expression.right)) { + return anyType; + } + const isDirectExport = kind === AssignmentDeclarationKind.ExportsProperty && (isPropertyAccessExpression(expression.left) || isElementAccessExpression(expression.left)) && (isModuleExportsAccessExpression(expression.left.expression) || (isIdentifier(expression.left.expression) && isExportsIdentifier(expression.left.expression))); + const type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) + : isDirectExport ? getRegularTypeOfLiteralType(checkExpressionCached(expression.right)) + : getWidenedLiteralType(checkExpressionCached(expression.right)); + if (type.flags & TypeFlags.Object && + kind === AssignmentDeclarationKind.ModuleExports && + symbol.escapedName === InternalSymbolName.ExportEquals) { + const exportedType = resolveStructuredTypeMembers(type as ObjectType); const members = createSymbolTable(); - let stringIndexInfo: IndexInfo | undefined; - let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - forEach(pattern.elements, e => { - const name = e.propertyName || e.name as Identifier; - if (e.dotDotDotToken) { - stringIndexInfo = createIndexInfo(stringType, anyType, /*isReadonly*/ false); - return; + copyEntries(exportedType.members, members); + const initialSize = members.size; + if (resolvedSymbol && !resolvedSymbol.exports) { + resolvedSymbol.exports = createSymbolTable(); + } + (resolvedSymbol || symbol).exports!.forEach((s, name) => { + const exportedMember = members.get(name)!; + if (exportedMember && exportedMember !== s && !(s.flags & SymbolFlags.Alias)) { + if (s.flags & SymbolFlags.Value && exportedMember.flags & SymbolFlags.Value) { + // If the member has an additional value-like declaration, union the types from the two declarations, + // but issue an error if they occurred in two different files. The purpose is to support a JS file with + // a pattern like: + // + // module.exports = { a: true }; + // module.exports.a = 3; + // + // but we may have a JS file with `module.exports = { a: true }` along with a TypeScript module augmentation + // declaring an `export const a: number`. In that case, we issue a duplicate identifier error, because + // it's unclear what that's supposed to mean, so it's probably a mistake. + if (s.valueDeclaration && exportedMember.valueDeclaration && getSourceFileOfNode(s.valueDeclaration) !== getSourceFileOfNode(exportedMember.valueDeclaration)) { + const unescapedName = unescapeLeadingUnderscores(s.escapedName); + const exportedMemberName = tryCast(exportedMember.valueDeclaration, isNamedDeclaration)?.name || exportedMember.valueDeclaration; + addRelatedInfo( + error(s.valueDeclaration, Diagnostics.Duplicate_identifier_0, unescapedName), + createDiagnosticForNode(exportedMemberName, Diagnostics._0_was_also_declared_here, unescapedName)); + addRelatedInfo( + error(exportedMemberName, Diagnostics.Duplicate_identifier_0, unescapedName), + createDiagnosticForNode(s.valueDeclaration, Diagnostics._0_was_also_declared_here, unescapedName)); + } + const union = createSymbol(s.flags | exportedMember.flags, name); + union.type = getUnionType([getTypeOfSymbol(s), getTypeOfSymbol(exportedMember)]); + union.valueDeclaration = exportedMember.valueDeclaration; + union.declarations = concatenate(exportedMember.declarations, s.declarations); + members.set(name, union); + } + else { + members.set(name, mergeSymbol(s, exportedMember)); + } } - - const exprType = getLiteralTypeFromPropertyName(name); - if (!isTypeUsableAsPropertyName(exprType)) { - // do not include computed properties in the implied type - objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; - return; + else { + members.set(name, s); } - const text = getPropertyNameFromType(exprType); - const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); - const symbol = createSymbol(flags, text); - symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); - symbol.bindingElement = e; - members.set(symbol.escapedName, symbol); }); - const result = createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo ? [stringIndexInfo] : emptyArray); - result.objectFlags |= objectFlags; - if (includePatternInType) { - result.pattern = pattern; - result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; + const result = createAnonymousType( + initialSize !== members.size ? undefined : exportedType.symbol, // Only set the type's symbol if it looks to be the same as the original type + members, + exportedType.callSignatures, + exportedType.constructSignatures, + exportedType.indexInfos); + if (initialSize === members.size) { + if (type.aliasSymbol) { + result.aliasSymbol = type.aliasSymbol; + result.aliasTypeArguments = type.aliasTypeArguments; + } + if (getObjectFlags(type) & ObjectFlags.Reference) { + result.aliasSymbol = (type as TypeReference).symbol; + const args = getTypeArguments(type as TypeReference); + result.aliasTypeArguments = length(args) ? args : undefined; + } + } + result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag + if (result.symbol && result.symbol.flags & SymbolFlags.Class && type === getDeclaredTypeOfClassOrInterface(result.symbol)) { + result.objectFlags |= ObjectFlags.IsClassInstanceClone; // Propagate the knowledge that this type is equivalent to the symbol's class instance type } return result; } + if (isEmptyArrayLiteralType(type)) { + reportImplicitAny(expression, anyArrayType); + return anyArrayType; + } + return type; + } + + function containsSameNamedThisProperty(thisProperty: Expression, expression: Expression) { + return isPropertyAccessExpression(thisProperty) + && thisProperty.expression.kind === SyntaxKind.ThisKeyword + && forEachChildRecursively(expression, n => isMatchingReference(thisProperty, n)); + } + + function isDeclarationInConstructor(expression: Expression) { + const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false); + // Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added. + // Function expressions that are assigned to the prototype count as methods. + return thisContainer.kind === SyntaxKind.Constructor || + thisContainer.kind === SyntaxKind.FunctionDeclaration || + (thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypePropertyAssignment(thisContainer.parent)); + } + + function getConstructorDefinedThisAssignmentTypes(types: Type[], declarations: Declaration[]): Type[] | undefined { + Debug.assert(types.length === declarations.length); + return types.filter((_, i) => { + const declaration = declarations[i]; + const expression = isBinaryExpression(declaration) ? declaration : + isBinaryExpression(declaration.parent) ? declaration.parent : undefined; + return expression && isDeclarationInConstructor(expression); + }); + } + + // Return the type implied by a binding pattern element. This is the type of the initializer of the element if + // one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding + // pattern. Otherwise, it is the type any. + function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean, reportErrors?: boolean): Type { + if (element.initializer) { + // The type implied by a binding pattern is independent of context, so we check the initializer with no + // contextual type or, if the element itself is a binding pattern, with the type implied by that binding + // pattern. + const contextualType = isBindingPattern(element.name) ? getTypeFromBindingPattern(element.name, /*includePatternInType*/ true, /*reportErrors*/ false) : unknownType; + return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType))); + } + if (isBindingPattern(element.name)) { + return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors); + } + if (reportErrors && !declarationBelongsToPrivateAmbientMember(element)) { + reportImplicitAny(element, anyType); + } + // When we're including the pattern in the type (an indication we're obtaining a contextual type), we + // use a non-inferrable any type. Inference will never directly infer this type, but it is possible + // to infer a type that contains it, e.g. for a binding pattern like [foo] or { foo }. In such cases, + // widening of the binding pattern type substitutes a regular any for the non-inferrable any. + return includePatternInType ? nonInferrableAnyType : anyType; + } - // Return the type implied by an array binding pattern - function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { - const elements = pattern.elements; - const lastElement = lastOrUndefined(elements); - const restElement = lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken ? lastElement : undefined; - if (elements.length === 0 || elements.length === 1 && restElement) { - return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType; + // Return the type implied by an object binding pattern + function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { + const members = createSymbolTable(); + let stringIndexInfo: IndexInfo | undefined; + let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + forEach(pattern.elements, e => { + const name = e.propertyName || e.name as Identifier; + if (e.dotDotDotToken) { + stringIndexInfo = createIndexInfo(stringType, anyType, /*isReadonly*/ false); + return; } - const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors)); - const minLength = findLastIndex(elements, e => !(e === restElement || isOmittedExpression(e) || hasDefaultValue(e)), elements.length - 1) + 1; - const elementFlags = map(elements, (e, i) => e === restElement ? ElementFlags.Rest : i >= minLength ? ElementFlags.Optional : ElementFlags.Required); - let result = createTupleType(elementTypes, elementFlags) as TypeReference; - if (includePatternInType) { - result = cloneTypeReference(result); - result.pattern = pattern; - result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; + + const exprType = getLiteralTypeFromPropertyName(name); + if (!isTypeUsableAsPropertyName(exprType)) { + // do not include computed properties in the implied type + objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; + return; } - return result; + const text = getPropertyNameFromType(exprType); + const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); + const symbol = createSymbol(flags, text); + symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); + symbol.bindingElement = e; + members.set(symbol.escapedName, symbol); + }); + const result = createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo ? [stringIndexInfo] : emptyArray); + result.objectFlags |= objectFlags; + if (includePatternInType) { + result.pattern = pattern; + result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; } + return result; + } - // Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself - // and without regard to its context (i.e. without regard any type annotation or initializer associated with the - // declaration in which the binding pattern is contained). For example, the implied type of [x, y] is [any, any] - // and the implied type of { x, y: z = 1 } is { x: any; y: number; }. The type implied by a binding pattern is - // used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring - // parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of - // the parameter. - function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false): Type { - return pattern.kind === SyntaxKind.ObjectBindingPattern - ? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors) - : getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors); - } + // Return the type implied by an array binding pattern + function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { + const elements = pattern.elements; + const lastElement = lastOrUndefined(elements); + const restElement = lastElement && lastElement.kind === SyntaxKind.BindingElement && lastElement.dotDotDotToken ? lastElement : undefined; + if (elements.length === 0 || elements.length === 1 && restElement) { + return languageVersion >= ScriptTarget.ES2015 ? createIterableType(anyType) : anyArrayType; + } + const elementTypes = map(elements, e => isOmittedExpression(e) ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors)); + const minLength = findLastIndex(elements, e => !(e === restElement || isOmittedExpression(e) || hasDefaultValue(e)), elements.length - 1) + 1; + const elementFlags = map(elements, (e, i) => e === restElement ? ElementFlags.Rest : i >= minLength ? ElementFlags.Optional : ElementFlags.Required); + let result = createTupleType(elementTypes, elementFlags) as TypeReference; + if (includePatternInType) { + result = cloneTypeReference(result); + result.pattern = pattern; + result.objectFlags |= ObjectFlags.ContainsObjectOrArrayLiteral; + } + return result; + } - // Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type - // specified in a type annotation or inferred from an initializer. However, in the case of a destructuring declaration it - // is a bit more involved. For example: - // - // var [x, s = ""] = [1, "one"]; - // - // Here, the array literal [1, "one"] is contextually typed by the type [any, string], which is the implied type of the - // binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the - // tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string. - function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, reportErrors?: boolean): Type { - return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal), declaration, reportErrors); - } + // Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself + // and without regard to its context (i.e. without regard any type annotation or initializer associated with the + // declaration in which the binding pattern is contained). For example, the implied type of [x, y] is [any, any] + // and the implied type of { x, y: z = 1 } is { x: any; y: number; }. The type implied by a binding pattern is + // used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring + // parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of + // the parameter. + function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType = false, reportErrors = false): Type { + return pattern.kind === SyntaxKind.ObjectBindingPattern + ? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors) + : getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors); + } - function isGlobalSymbolConstructor(node: Node) { - const symbol = getSymbolOfNode(node); - const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false); - return globalSymbol && symbol && symbol === globalSymbol; - } + // Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type + // specified in a type annotation or inferred from an initializer. However, in the case of a destructuring declaration it + // is a bit more involved. For example: + // + // var [x, s = ""] = [1, "one"]; + // + // Here, the array literal [1, "one"] is contextually typed by the type [any, string], which is the implied type of the + // binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the + // tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string. + function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, reportErrors?: boolean): Type { + return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal), declaration, reportErrors); + } - function widenTypeForVariableLikeDeclaration(type: Type | undefined, declaration: any, reportErrors?: boolean) { - if (type) { - // TODO: If back compat with pre-3.0/4.0 libs isn't required, remove the following SymbolConstructor special case transforming `symbol` into `unique symbol` - if (type.flags & TypeFlags.ESSymbol && isGlobalSymbolConstructor(declaration.parent)) { - type = getESSymbolLikeTypeForNode(declaration); - } - if (reportErrors) { - reportErrorsFromWidening(declaration, type); - } + function isGlobalSymbolConstructor(node: Node) { + const symbol = getSymbolOfNode(node); + const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false); + return globalSymbol && symbol && symbol === globalSymbol; + } - // always widen a 'unique symbol' type if the type was created for a different declaration. - if (type.flags & TypeFlags.UniqueESSymbol && (isBindingElement(declaration) || !declaration.type) && type.symbol !== getSymbolOfNode(declaration)) { - type = esSymbolType; - } + function widenTypeForVariableLikeDeclaration(type: Type | undefined, declaration: any, reportErrors?: boolean) { + if (type) { + // TODO: If back compat with pre-3.0/4.0 libs isn't required, remove the following SymbolConstructor special case transforming `symbol` into `unique symbol` + if (type.flags & TypeFlags.ESSymbol && isGlobalSymbolConstructor(declaration.parent)) { + type = getESSymbolLikeTypeForNode(declaration); + } + if (reportErrors) { + reportErrorsFromWidening(declaration, type); + } - return getWidenedType(type); + // always widen a 'unique symbol' type if the type was created for a different declaration. + if (type.flags & TypeFlags.UniqueESSymbol && (isBindingElement(declaration) || !declaration.type) && type.symbol !== getSymbolOfNode(declaration)) { + type = esSymbolType; } - // Rest parameters default to type any[], other parameters default to type any - type = isParameter(declaration) && declaration.dotDotDotToken ? anyArrayType : anyType; + return getWidenedType(type); + } + + // Rest parameters default to type any[], other parameters default to type any + type = isParameter(declaration) && declaration.dotDotDotToken ? anyArrayType : anyType; - // Report implicit any errors unless this is a private property within an ambient declaration - if (reportErrors) { - if (!declarationBelongsToPrivateAmbientMember(declaration)) { - reportImplicitAny(declaration, type); - } + // Report implicit any errors unless this is a private property within an ambient declaration + if (reportErrors) { + if (!declarationBelongsToPrivateAmbientMember(declaration)) { + reportImplicitAny(declaration, type); } - return type; } + return type; + } - function declarationBelongsToPrivateAmbientMember(declaration: VariableLikeDeclaration) { - const root = getRootDeclaration(declaration); - const memberDeclaration = root.kind === SyntaxKind.Parameter ? root.parent : root; - return isPrivateWithinAmbient(memberDeclaration); - } + function declarationBelongsToPrivateAmbientMember(declaration: VariableLikeDeclaration) { + const root = getRootDeclaration(declaration); + const memberDeclaration = root.kind === SyntaxKind.Parameter ? root.parent : root; + return isPrivateWithinAmbient(memberDeclaration); + } - function tryGetTypeFromEffectiveTypeNode(node: Node) { - const typeNode = getEffectiveTypeAnnotationNode(node); - if (typeNode) { - return getTypeFromTypeNode(typeNode); - } + function tryGetTypeFromEffectiveTypeNode(node: Node) { + const typeNode = getEffectiveTypeAnnotationNode(node); + if (typeNode) { + return getTypeFromTypeNode(typeNode); } + } - function isParameterOfContextSensitiveSignature(symbol: Symbol) { - let decl = symbol.valueDeclaration; - if (!decl) { - return false; - } - if (isBindingElement(decl)) { - decl = walkUpBindingElementsAndPatterns(decl); - } - if (isParameter(decl)) { - return isContextSensitiveFunctionOrObjectLiteralMethod(decl.parent); - } + function isParameterOfContextSensitiveSignature(symbol: Symbol) { + let decl = symbol.valueDeclaration; + if (!decl) { return false; } + if (isBindingElement(decl)) { + decl = walkUpBindingElementsAndPatterns(decl); + } + if (isParameter(decl)) { + return isContextSensitiveFunctionOrObjectLiteralMethod(decl.parent); + } + return false; + } - function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.type) { - const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol); - // For a contextually typed parameter it is possible that a type has already - // been assigned (in assignTypeToParameterAndFixTypeParameters), and we want - // to preserve this type. In fact, we need to _prefer_ that type, but it won't - // be assigned until contextual typing is complete, so we need to defer in - // cases where contextual typing may take place. - if (!links.type && !isParameterOfContextSensitiveSignature(symbol)) { - links.type = type; - } - return type; + function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol); + // For a contextually typed parameter it is possible that a type has already + // been assigned (in assignTypeToParameterAndFixTypeParameters), and we want + // to preserve this type. In fact, we need to _prefer_ that type, but it won't + // be assigned until contextual typing is complete, so we need to defer in + // cases where contextual typing may take place. + if (!links.type && !isParameterOfContextSensitiveSignature(symbol)) { + links.type = type; } - return links.type; + return type; } + return links.type; + } - function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol): Type { - // Handle prototype property - if (symbol.flags & SymbolFlags.Prototype) { - return getTypeOfPrototypeProperty(symbol); - } - // CommonsJS require and module both have type any. - if (symbol === requireSymbol) { - return anyType; - } - if (symbol.flags & SymbolFlags.ModuleExports && symbol.valueDeclaration) { - const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration)); - const result = createSymbol(fileSymbol.flags, "exports" as __String); - result.declarations = fileSymbol.declarations ? fileSymbol.declarations.slice() : []; - result.parent = symbol; - result.target = fileSymbol; - if (fileSymbol.valueDeclaration) result.valueDeclaration = fileSymbol.valueDeclaration; - if (fileSymbol.members) result.members = new Map(fileSymbol.members); - if (fileSymbol.exports) result.exports = new Map(fileSymbol.exports); - const members = createSymbolTable(); - members.set("exports" as __String, result); - return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); - } - // Handle catch clause variables - Debug.assertIsDefined(symbol.valueDeclaration); - const declaration = symbol.valueDeclaration; - if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode === undefined) { - return useUnknownInCatchVariables ? unknownType : anyType; - } - const type = getTypeOfNode(typeNode); - // an errorType will make `checkTryStatement` issue an error - return isTypeAny(type) || type === unknownType ? type : errorType; - } - // Handle export default expressions - if (isSourceFile(declaration) && isJsonSourceFile(declaration)) { - if (!declaration.statements.length) { - return emptyObjectType; - } - return getWidenedType(getWidenedLiteralType(checkExpression(declaration.statements[0].expression))); + function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol): Type { + // Handle prototype property + if (symbol.flags & SymbolFlags.Prototype) { + return getTypeOfPrototypeProperty(symbol); + } + // CommonsJS require and module both have type any. + if (symbol === requireSymbol) { + return anyType; + } + if (symbol.flags & SymbolFlags.ModuleExports && symbol.valueDeclaration) { + const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration)); + const result = createSymbol(fileSymbol.flags, "exports" as __String); + result.declarations = fileSymbol.declarations ? fileSymbol.declarations.slice() : []; + result.parent = symbol; + result.target = fileSymbol; + if (fileSymbol.valueDeclaration) result.valueDeclaration = fileSymbol.valueDeclaration; + if (fileSymbol.members) result.members = new Map(fileSymbol.members); + if (fileSymbol.exports) result.exports = new Map(fileSymbol.exports); + const members = createSymbolTable(); + members.set("exports" as __String, result); + return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + } + // Handle catch clause variables + Debug.assertIsDefined(symbol.valueDeclaration); + const declaration = symbol.valueDeclaration; + if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode === undefined) { + return useUnknownInCatchVariables ? unknownType : anyType; } - if (isAccessor(declaration)) { - // Binding of certain patterns in JS code will occasionally mark symbols as both properties - // and accessors. Here we dispatch to accessor resolution if needed. - return getTypeOfAccessors(symbol); + const type = getTypeOfNode(typeNode); + // an errorType will make `checkTryStatement` issue an error + return isTypeAny(type) || type === unknownType ? type : errorType; + } + // Handle export default expressions + if (isSourceFile(declaration) && isJsonSourceFile(declaration)) { + if (!declaration.statements.length) { + return emptyObjectType; } + return getWidenedType(getWidenedLiteralType(checkExpression(declaration.statements[0].expression))); + } + if (isAccessor(declaration)) { + // Binding of certain patterns in JS code will occasionally mark symbols as both properties + // and accessors. Here we dispatch to accessor resolution if needed. + return getTypeOfAccessors(symbol); + } - // Handle variable, parameter or property - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` - if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { - return getTypeOfFuncClassEnumModule(symbol); - } - return reportCircularityError(symbol); - } - let type: Type; - if (declaration.kind === SyntaxKind.ExportAssignment) { - type = widenTypeForVariableLikeDeclaration(tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionCached((declaration as ExportAssignment).expression), declaration); - } - else if ( - isBinaryExpression(declaration) || - (isInJSFile(declaration) && - (isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableStaticElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent)))) { - type = getWidenedTypeForAssignmentDeclaration(symbol); - } - else if (isPropertyAccessExpression(declaration) - || isElementAccessExpression(declaration) - || isIdentifier(declaration) - || isStringLiteralLike(declaration) - || isNumericLiteral(declaration) - || isClassDeclaration(declaration) - || isFunctionDeclaration(declaration) - || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) - || isMethodSignature(declaration) - || isSourceFile(declaration)) { - // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { - return getTypeOfFuncClassEnumModule(symbol); - } - type = isBinaryExpression(declaration.parent) ? - getWidenedTypeForAssignmentDeclaration(symbol) : - tryGetTypeFromEffectiveTypeNode(declaration) || anyType; - } - else if (isPropertyAssignment(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration); - } - else if (isJsxAttribute(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration); - } - else if (isShorthandPropertyAssignment(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal); - } - else if (isObjectLiteralMethod(declaration)) { - type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal); - } - else if (isParameter(declaration) - || isPropertyDeclaration(declaration) - || isPropertySignature(declaration) - || isVariableDeclaration(declaration) - || isBindingElement(declaration) - || isJSDocPropertyLikeTag(declaration)) { - type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); - } - // getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive. - // Re-dispatch based on valueDeclaration.kind instead. - else if (isEnumDeclaration(declaration)) { - type = getTypeOfFuncClassEnumModule(symbol); - } - else if (isEnumMember(declaration)) { - type = getTypeOfEnumMember(symbol); + // Handle variable, parameter or property + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` + if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { + return getTypeOfFuncClassEnumModule(symbol); } - else { - return Debug.fail("Unhandled declaration kind! " + Debug.formatSyntaxKind(declaration.kind) + " for " + Debug.formatSymbol(symbol)); + return reportCircularityError(symbol); + } + let type: Type; + if (declaration.kind === SyntaxKind.ExportAssignment) { + type = widenTypeForVariableLikeDeclaration(tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionCached((declaration as ExportAssignment).expression), declaration); + } + else if ( + isBinaryExpression(declaration) || + (isInJSFile(declaration) && + (isCallExpression(declaration) || (isPropertyAccessExpression(declaration) || isBindableStaticElementAccessExpression(declaration)) && isBinaryExpression(declaration.parent)))) { + type = getWidenedTypeForAssignmentDeclaration(symbol); + } + else if (isPropertyAccessExpression(declaration) + || isElementAccessExpression(declaration) + || isIdentifier(declaration) + || isStringLiteralLike(declaration) + || isNumericLiteral(declaration) + || isClassDeclaration(declaration) + || isFunctionDeclaration(declaration) + || (isMethodDeclaration(declaration) && !isObjectLiteralMethod(declaration)) + || isMethodSignature(declaration) + || isSourceFile(declaration)) { + // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { + return getTypeOfFuncClassEnumModule(symbol); } + type = isBinaryExpression(declaration.parent) ? + getWidenedTypeForAssignmentDeclaration(symbol) : + tryGetTypeFromEffectiveTypeNode(declaration) || anyType; + } + else if (isPropertyAssignment(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkPropertyAssignment(declaration); + } + else if (isJsxAttribute(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkJsxAttribute(declaration); + } + else if (isShorthandPropertyAssignment(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkExpressionForMutableLocation(declaration.name, CheckMode.Normal); + } + else if (isObjectLiteralMethod(declaration)) { + type = tryGetTypeFromEffectiveTypeNode(declaration) || checkObjectLiteralMethod(declaration, CheckMode.Normal); + } + else if (isParameter(declaration) + || isPropertyDeclaration(declaration) + || isPropertySignature(declaration) + || isVariableDeclaration(declaration) + || isBindingElement(declaration) + || isJSDocPropertyLikeTag(declaration)) { + type = getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); + } + // getTypeOfSymbol dispatches some JS merges incorrectly because their symbol flags are not mutually exclusive. + // Re-dispatch based on valueDeclaration.kind instead. + else if (isEnumDeclaration(declaration)) { + type = getTypeOfFuncClassEnumModule(symbol); + } + else if (isEnumMember(declaration)) { + type = getTypeOfEnumMember(symbol); + } + else { + return Debug.fail("Unhandled declaration kind! " + Debug.formatSyntaxKind(declaration.kind) + " for " + Debug.formatSymbol(symbol)); + } - if (!popTypeResolution()) { - // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` - if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { - return getTypeOfFuncClassEnumModule(symbol); - } - return reportCircularityError(symbol); + if (!popTypeResolution()) { + // Symbol is property of some kind that is merged with something - should use `getTypeOfFuncClassEnumModule` and not `getTypeOfVariableOrParameterOrProperty` + if (symbol.flags & SymbolFlags.ValueModule && !(symbol.flags & SymbolFlags.Assignment)) { + return getTypeOfFuncClassEnumModule(symbol); } - return type; + return reportCircularityError(symbol); } + return type; + } - function getAnnotatedAccessorTypeNode(accessor: AccessorDeclaration | PropertyDeclaration | undefined): TypeNode | undefined { - if (accessor) { - switch (accessor.kind) { - case SyntaxKind.GetAccessor: - const getterTypeAnnotation = getEffectiveReturnTypeNode(accessor); - return getterTypeAnnotation; - case SyntaxKind.SetAccessor: - const setterTypeAnnotation = getEffectiveSetAccessorTypeAnnotationNode(accessor); - return setterTypeAnnotation; - case SyntaxKind.PropertyDeclaration: - Debug.assert(hasAccessorModifier(accessor)); - const accessorTypeAnnotation = getEffectiveTypeAnnotationNode(accessor); - return accessorTypeAnnotation; - } + function getAnnotatedAccessorTypeNode(accessor: AccessorDeclaration | PropertyDeclaration | undefined): TypeNode | undefined { + if (accessor) { + switch (accessor.kind) { + case SyntaxKind.GetAccessor: + const getterTypeAnnotation = getEffectiveReturnTypeNode(accessor); + return getterTypeAnnotation; + case SyntaxKind.SetAccessor: + const setterTypeAnnotation = getEffectiveSetAccessorTypeAnnotationNode(accessor); + return setterTypeAnnotation; + case SyntaxKind.PropertyDeclaration: + Debug.assert(hasAccessorModifier(accessor)); + const accessorTypeAnnotation = getEffectiveTypeAnnotationNode(accessor); + return accessorTypeAnnotation; } - return undefined; } + return undefined; + } - function getAnnotatedAccessorType(accessor: AccessorDeclaration | PropertyDeclaration | undefined): Type | undefined { - const node = getAnnotatedAccessorTypeNode(accessor); - return node && getTypeFromTypeNode(node); - } + function getAnnotatedAccessorType(accessor: AccessorDeclaration | PropertyDeclaration | undefined): Type | undefined { + const node = getAnnotatedAccessorTypeNode(accessor); + return node && getTypeFromTypeNode(node); + } - function getAnnotatedAccessorThisParameter(accessor: AccessorDeclaration): Symbol | undefined { - const parameter = getAccessorThisParameter(accessor); - return parameter && parameter.symbol; - } + function getAnnotatedAccessorThisParameter(accessor: AccessorDeclaration): Symbol | undefined { + const parameter = getAccessorThisParameter(accessor); + return parameter && parameter.symbol; + } - function getThisTypeOfDeclaration(declaration: SignatureDeclaration): Type | undefined { - return getThisTypeOfSignature(getSignatureFromDeclaration(declaration)); - } + function getThisTypeOfDeclaration(declaration: SignatureDeclaration): Type | undefined { + return getThisTypeOfSignature(getSignatureFromDeclaration(declaration)); + } - function getTypeOfAccessors(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.type) { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return errorType; + function getTypeOfAccessors(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; + } + const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); + const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); + const accessor = tryCast(getDeclarationOfKind(symbol, SyntaxKind.PropertyDeclaration), isAutoAccessorPropertyDeclaration); + + // We try to resolve a getter type annotation, a setter type annotation, or a getter function + // body return type inference, in that order. + let type = getter && isInJSFile(getter) && getTypeForDeclarationFromJSDocComment(getter) || + getAnnotatedAccessorType(getter) || + getAnnotatedAccessorType(setter) || + getAnnotatedAccessorType(accessor) || + getter && getter.body && getReturnTypeFromBody(getter) || + accessor && accessor.initializer && getWidenedTypeForVariableLikeDeclaration(accessor, /*includeOptionality*/ true); + if (!type) { + if (setter && !isPrivateWithinAmbient(setter)) { + errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol)); } - const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); - const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); - const accessor = tryCast(getDeclarationOfKind(symbol, SyntaxKind.PropertyDeclaration), isAutoAccessorPropertyDeclaration); - - // We try to resolve a getter type annotation, a setter type annotation, or a getter function - // body return type inference, in that order. - let type = getter && isInJSFile(getter) && getTypeForDeclarationFromJSDocComment(getter) || - getAnnotatedAccessorType(getter) || - getAnnotatedAccessorType(setter) || - getAnnotatedAccessorType(accessor) || - getter && getter.body && getReturnTypeFromBody(getter) || - accessor && accessor.initializer && getWidenedTypeForVariableLikeDeclaration(accessor, /*includeOptionality*/ true); - if (!type) { - if (setter && !isPrivateWithinAmbient(setter)) { - errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol)); - } - else if (getter && !isPrivateWithinAmbient(getter)) { - errorOrSuggestion(noImplicitAny, getter, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol)); - } - else if (accessor && !isPrivateWithinAmbient(accessor)) { - errorOrSuggestion(noImplicitAny, accessor, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), "any"); - } - type = anyType; + else if (getter && !isPrivateWithinAmbient(getter)) { + errorOrSuggestion(noImplicitAny, getter, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol)); } - if (!popTypeResolution()) { - if (getAnnotatedAccessorTypeNode(getter)) { - error(getter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); - } - else if (getAnnotatedAccessorTypeNode(setter)) { - error(setter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); - } - else if (getAnnotatedAccessorTypeNode(accessor)) { - error(setter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); - } - else if (getter && noImplicitAny) { - error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol)); - } - type = anyType; + else if (accessor && !isPrivateWithinAmbient(accessor)) { + errorOrSuggestion(noImplicitAny, accessor, Diagnostics.Member_0_implicitly_has_an_1_type, symbolToString(symbol), "any"); } - links.type = type; + type = anyType; } - return links.type; - } - - function getWriteTypeOfAccessors(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.writeType) { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.WriteType)) { - return errorType; + if (!popTypeResolution()) { + if (getAnnotatedAccessorTypeNode(getter)) { + error(getter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); } - - const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor) - ?? tryCast(getDeclarationOfKind(symbol, SyntaxKind.PropertyDeclaration), isAutoAccessorPropertyDeclaration); - let writeType = getAnnotatedAccessorType(setter); - if (!popTypeResolution()) { - if (getAnnotatedAccessorTypeNode(setter)) { - error(setter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); - } - writeType = anyType; + else if (getAnnotatedAccessorTypeNode(setter)) { + error(setter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); } - // Absent an explicit setter type annotation we use the read type of the accessor. - links.writeType = writeType || getTypeOfAccessors(symbol); + else if (getAnnotatedAccessorTypeNode(accessor)) { + error(setter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); + } + else if (getter && noImplicitAny) { + error(getter, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, symbolToString(symbol)); + } + type = anyType; } - return links.writeType; + links.type = type; } + return links.type; + } - function getBaseTypeVariableOfClass(symbol: Symbol) { - const baseConstructorType = getBaseConstructorTypeOfClass(getDeclaredTypeOfClassOrInterface(symbol)); - return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType : - baseConstructorType.flags & TypeFlags.Intersection ? find((baseConstructorType as IntersectionType).types, t => !!(t.flags & TypeFlags.TypeVariable)) : - undefined; - } + function getWriteTypeOfAccessors(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.writeType) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.WriteType)) { + return errorType; + } - function getTypeOfFuncClassEnumModule(symbol: Symbol): Type { - let links = getSymbolLinks(symbol); - const originalLinks = links; - if (!links.type) { - const expando = symbol.valueDeclaration && getSymbolOfExpando(symbol.valueDeclaration, /*allowDeclaration*/ false); - if (expando) { - const merged = mergeJSSymbols(symbol, expando); - if (merged) { - // note:we overwrite links because we just cloned the symbol - symbol = links = merged; - } + const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor) + ?? tryCast(getDeclarationOfKind(symbol, SyntaxKind.PropertyDeclaration), isAutoAccessorPropertyDeclaration); + let writeType = getAnnotatedAccessorType(setter); + if (!popTypeResolution()) { + if (getAnnotatedAccessorTypeNode(setter)) { + error(setter, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol)); } - originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol); + writeType = anyType; } - return links.type; + // Absent an explicit setter type annotation we use the read type of the accessor. + links.writeType = writeType || getTypeOfAccessors(symbol); } + return links.writeType; + } - function getTypeOfFuncClassEnumModuleWorker(symbol: Symbol): Type { - const declaration = symbol.valueDeclaration; - if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) { - return anyType; - } - else if (declaration && (declaration.kind === SyntaxKind.BinaryExpression || - isAccessExpression(declaration) && - declaration.parent.kind === SyntaxKind.BinaryExpression)) { - return getWidenedTypeForAssignmentDeclaration(symbol); - } - else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) { - const resolvedModule = resolveExternalModuleSymbol(symbol); - if (resolvedModule !== symbol) { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return errorType; - } - const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!); - const type = getWidenedTypeForAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule); - if (!popTypeResolution()) { - return reportCircularityError(symbol); - } - return type; + function getBaseTypeVariableOfClass(symbol: Symbol) { + const baseConstructorType = getBaseConstructorTypeOfClass(getDeclaredTypeOfClassOrInterface(symbol)); + return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType : + baseConstructorType.flags & TypeFlags.Intersection ? find((baseConstructorType as IntersectionType).types, t => !!(t.flags & TypeFlags.TypeVariable)) : + undefined; + } + + function getTypeOfFuncClassEnumModule(symbol: Symbol): Type { + let links = getSymbolLinks(symbol); + const originalLinks = links; + if (!links.type) { + const expando = symbol.valueDeclaration && getSymbolOfExpando(symbol.valueDeclaration, /*allowDeclaration*/ false); + if (expando) { + const merged = mergeJSSymbols(symbol, expando); + if (merged) { + // note:we overwrite links because we just cloned the symbol + symbol = links = merged; } } - const type = createObjectType(ObjectFlags.Anonymous, symbol); - if (symbol.flags & SymbolFlags.Class) { - const baseTypeVariable = getBaseTypeVariableOfClass(symbol); - return baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type; - } - else { - return strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type; - } - } - - function getTypeOfEnumMember(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - return links.type || (links.type = getDeclaredTypeOfEnumMember(symbol)); + originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol); } + return links.type; + } - function getTypeOfAlias(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.type) { - const targetSymbol = resolveAlias(symbol); - const exportSymbol = symbol.declarations && getTargetOfAliasDeclaration(getDeclarationOfAliasSymbol(symbol)!, /*dontResolveAlias*/ true); - const declaredType = firstDefined(exportSymbol?.declarations, d => isExportAssignment(d) ? tryGetTypeFromEffectiveTypeNode(d) : undefined); - // It only makes sense to get the type of a value symbol. If the result of resolving - // the alias is not a value, then it has no type. To get the type associated with a - // type symbol, call getDeclaredTypeOfSymbol. - // This check is important because without it, a call to getTypeOfSymbol could end - // up recursively calling getTypeOfAlias, causing a stack overflow. - links.type = exportSymbol?.declarations && isDuplicatedCommonJSExport(exportSymbol.declarations) && symbol.declarations!.length ? getFlowTypeFromCommonJSExport(exportSymbol) - : isDuplicatedCommonJSExport(symbol.declarations) ? autoType - : declaredType ? declaredType - : getAllSymbolFlags(targetSymbol) & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol) - : errorType; - } - return links.type; - } - - function getTypeOfInstantiatedSymbol(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - return links.type || (links.type = instantiateType(getTypeOfSymbol(links.target!), links.mapper)); + function getTypeOfFuncClassEnumModuleWorker(symbol: Symbol): Type { + const declaration = symbol.valueDeclaration; + if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) { + return anyType; } - - function getWriteTypeOfInstantiatedSymbol(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - return links.writeType || (links.writeType = instantiateType(getWriteTypeOfSymbol(links.target!), links.mapper)); + else if (declaration && (declaration.kind === SyntaxKind.BinaryExpression || + isAccessExpression(declaration) && + declaration.parent.kind === SyntaxKind.BinaryExpression)) { + return getWidenedTypeForAssignmentDeclaration(symbol); } - - function reportCircularityError(symbol: Symbol) { - const declaration = symbol.valueDeclaration as VariableLikeDeclaration; - // Check if variable has type annotation that circularly references the variable itself - if (getEffectiveTypeAnnotationNode(declaration)) { - error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, - symbolToString(symbol)); - return errorType; - } - // Check if variable has initializer that circularly references the variable itself - if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (declaration as HasInitializer).initializer)) { - error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer, - symbolToString(symbol)); + else if (symbol.flags & SymbolFlags.ValueModule && declaration && isSourceFile(declaration) && declaration.commonJsModuleIndicator) { + const resolvedModule = resolveExternalModuleSymbol(symbol); + if (resolvedModule !== symbol) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; + } + const exportEquals = getMergedSymbol(symbol.exports!.get(InternalSymbolName.ExportEquals)!); + const type = getWidenedTypeForAssignmentDeclaration(exportEquals, exportEquals === resolvedModule ? undefined : resolvedModule); + if (!popTypeResolution()) { + return reportCircularityError(symbol); + } + return type; } - // Circularities could also result from parameters in function expressions that end up - // having themselves as contextual types following type argument inference. In those cases - // we have already reported an implicit any error so we don't report anything here. - return anyType; } - - function getTypeOfSymbolWithDeferredType(symbol: Symbol) { - const links = getSymbolLinks(symbol); - if (!links.type) { - Debug.assertIsDefined(links.deferralParent); - Debug.assertIsDefined(links.deferralConstituents); - links.type = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents) : getIntersectionType(links.deferralConstituents); - } - return links.type; + const type = createObjectType(ObjectFlags.Anonymous, symbol); + if (symbol.flags & SymbolFlags.Class) { + const baseTypeVariable = getBaseTypeVariableOfClass(symbol); + return baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type; } - - function getWriteTypeOfSymbolWithDeferredType(symbol: Symbol): Type | undefined { - const links = getSymbolLinks(symbol); - if (!links.writeType && links.deferralWriteConstituents) { - Debug.assertIsDefined(links.deferralParent); - Debug.assertIsDefined(links.deferralConstituents); - links.writeType = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralWriteConstituents) : getIntersectionType(links.deferralWriteConstituents); - } - return links.writeType; + else { + return strictNullChecks && symbol.flags & SymbolFlags.Optional ? getOptionalType(type) : type; } + } - /** - * Distinct write types come only from set accessors, but synthetic union and intersection - * properties deriving from set accessors will either pre-compute or defer the union or - * intersection of the writeTypes of their constituents. - */ - function getWriteTypeOfSymbol(symbol: Symbol): Type { - const checkFlags = getCheckFlags(symbol); - if (symbol.flags & SymbolFlags.Property) { - return checkFlags & CheckFlags.SyntheticProperty ? - checkFlags & CheckFlags.DeferredType ? - getWriteTypeOfSymbolWithDeferredType(symbol) || getTypeOfSymbolWithDeferredType(symbol) : - (symbol as TransientSymbol).writeType || (symbol as TransientSymbol).type! : - getTypeOfSymbol(symbol); - } - if (symbol.flags & SymbolFlags.Accessor) { - return checkFlags & CheckFlags.Instantiated ? - getWriteTypeOfInstantiatedSymbol(symbol) : - getWriteTypeOfAccessors(symbol); - } - return getTypeOfSymbol(symbol); + function getTypeOfEnumMember(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + return links.type || (links.type = getDeclaredTypeOfEnumMember(symbol)); + } + + function getTypeOfAlias(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + const targetSymbol = resolveAlias(symbol); + const exportSymbol = symbol.declarations && getTargetOfAliasDeclaration(getDeclarationOfAliasSymbol(symbol)!, /*dontResolveAlias*/ true); + const declaredType = firstDefined(exportSymbol?.declarations, d => isExportAssignment(d) ? tryGetTypeFromEffectiveTypeNode(d) : undefined); + // It only makes sense to get the type of a value symbol. If the result of resolving + // the alias is not a value, then it has no type. To get the type associated with a + // type symbol, call getDeclaredTypeOfSymbol. + // This check is important because without it, a call to getTypeOfSymbol could end + // up recursively calling getTypeOfAlias, causing a stack overflow. + links.type = exportSymbol?.declarations && isDuplicatedCommonJSExport(exportSymbol.declarations) && symbol.declarations!.length ? getFlowTypeFromCommonJSExport(exportSymbol) + : isDuplicatedCommonJSExport(symbol.declarations) ? autoType + : declaredType ? declaredType + : getAllSymbolFlags(targetSymbol) & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol) + : errorType; } + return links.type; + } - function getTypeOfSymbol(symbol: Symbol): Type { - const checkFlags = getCheckFlags(symbol); - if (checkFlags & CheckFlags.DeferredType) { - return getTypeOfSymbolWithDeferredType(symbol); - } - if (checkFlags & CheckFlags.Instantiated) { - return getTypeOfInstantiatedSymbol(symbol); - } - if (checkFlags & CheckFlags.Mapped) { - return getTypeOfMappedSymbol(symbol as MappedSymbol); - } - if (checkFlags & CheckFlags.ReverseMapped) { - return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol); - } - if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { - return getTypeOfVariableOrParameterOrProperty(symbol); - } - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { - return getTypeOfFuncClassEnumModule(symbol); - } - if (symbol.flags & SymbolFlags.EnumMember) { - return getTypeOfEnumMember(symbol); - } - if (symbol.flags & SymbolFlags.Accessor) { - return getTypeOfAccessors(symbol); - } - if (symbol.flags & SymbolFlags.Alias) { - return getTypeOfAlias(symbol); - } + function getTypeOfInstantiatedSymbol(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + return links.type || (links.type = instantiateType(getTypeOfSymbol(links.target!), links.mapper)); + } + + function getWriteTypeOfInstantiatedSymbol(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + return links.writeType || (links.writeType = instantiateType(getWriteTypeOfSymbol(links.target!), links.mapper)); + } + + function reportCircularityError(symbol: Symbol) { + const declaration = symbol.valueDeclaration as VariableLikeDeclaration; + // Check if variable has type annotation that circularly references the variable itself + if (getEffectiveTypeAnnotationNode(declaration)) { + error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, + symbolToString(symbol)); return errorType; } + // Check if variable has initializer that circularly references the variable itself + if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (declaration as HasInitializer).initializer)) { + error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer, + symbolToString(symbol)); + } + // Circularities could also result from parameters in function expressions that end up + // having themselves as contextual types following type argument inference. In those cases + // we have already reported an implicit any error so we don't report anything here. + return anyType; + } - function getNonMissingTypeOfSymbol(symbol: Symbol) { - return removeMissingType(getTypeOfSymbol(symbol), !!(symbol.flags & SymbolFlags.Optional)); + function getTypeOfSymbolWithDeferredType(symbol: Symbol) { + const links = getSymbolLinks(symbol); + if (!links.type) { + Debug.assertIsDefined(links.deferralParent); + Debug.assertIsDefined(links.deferralConstituents); + links.type = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents) : getIntersectionType(links.deferralConstituents); } + return links.type; + } - function isReferenceToType(type: Type, target: Type) { - return type !== undefined - && target !== undefined - && (getObjectFlags(type) & ObjectFlags.Reference) !== 0 - && (type as TypeReference).target === target; + function getWriteTypeOfSymbolWithDeferredType(symbol: Symbol): Type | undefined { + const links = getSymbolLinks(symbol); + if (!links.writeType && links.deferralWriteConstituents) { + Debug.assertIsDefined(links.deferralParent); + Debug.assertIsDefined(links.deferralConstituents); + links.writeType = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralWriteConstituents) : getIntersectionType(links.deferralWriteConstituents); } + return links.writeType; + } + + /** + * Distinct write types come only from set accessors, but synthetic union and intersection + * properties deriving from set accessors will either pre-compute or defer the union or + * intersection of the writeTypes of their constituents. + */ + function getWriteTypeOfSymbol(symbol: Symbol): Type { + const checkFlags = getCheckFlags(symbol); + if (symbol.flags & SymbolFlags.Property) { + return checkFlags & CheckFlags.SyntheticProperty ? + checkFlags & CheckFlags.DeferredType ? + getWriteTypeOfSymbolWithDeferredType(symbol) || getTypeOfSymbolWithDeferredType(symbol) : + (symbol as TransientSymbol).writeType || (symbol as TransientSymbol).type! : + getTypeOfSymbol(symbol); + } + if (symbol.flags & SymbolFlags.Accessor) { + return checkFlags & CheckFlags.Instantiated ? + getWriteTypeOfInstantiatedSymbol(symbol) : + getWriteTypeOfAccessors(symbol); + } + return getTypeOfSymbol(symbol); + } - function getTargetType(type: Type): Type { - return getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target : type; + function getTypeOfSymbol(symbol: Symbol): Type { + const checkFlags = getCheckFlags(symbol); + if (checkFlags & CheckFlags.DeferredType) { + return getTypeOfSymbolWithDeferredType(symbol); + } + if (checkFlags & CheckFlags.Instantiated) { + return getTypeOfInstantiatedSymbol(symbol); } + if (checkFlags & CheckFlags.Mapped) { + return getTypeOfMappedSymbol(symbol as MappedSymbol); + } + if (checkFlags & CheckFlags.ReverseMapped) { + return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol); + } + if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { + return getTypeOfVariableOrParameterOrProperty(symbol); + } + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { + return getTypeOfFuncClassEnumModule(symbol); + } + if (symbol.flags & SymbolFlags.EnumMember) { + return getTypeOfEnumMember(symbol); + } + if (symbol.flags & SymbolFlags.Accessor) { + return getTypeOfAccessors(symbol); + } + if (symbol.flags & SymbolFlags.Alias) { + return getTypeOfAlias(symbol); + } + return errorType; + } - // TODO: GH#18217 If `checkBase` is undefined, we should not call this because this will always return false. - function hasBaseType(type: Type, checkBase: Type | undefined) { - return check(type); - function check(type: Type): boolean { - if (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) { - const target = getTargetType(type) as InterfaceType; - return target === checkBase || some(getBaseTypes(target), check); - } - else if (type.flags & TypeFlags.Intersection) { - return some((type as IntersectionType).types, check); - } - return false; + function getNonMissingTypeOfSymbol(symbol: Symbol) { + return removeMissingType(getTypeOfSymbol(symbol), !!(symbol.flags & SymbolFlags.Optional)); + } + + function isReferenceToType(type: Type, target: Type) { + return type !== undefined + && target !== undefined + && (getObjectFlags(type) & ObjectFlags.Reference) !== 0 + && (type as TypeReference).target === target; + } + + function getTargetType(type: Type): Type { + return getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target : type; + } + + // TODO: GH#18217 If `checkBase` is undefined, we should not call this because this will always return false. + function hasBaseType(type: Type, checkBase: Type | undefined) { + return check(type); + function check(type: Type): boolean { + if (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) { + const target = getTargetType(type) as InterfaceType; + return target === checkBase || some(getBaseTypes(target), check); + } + else if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, check); } + return false; } + } - // Appends the type parameters given by a list of declarations to a set of type parameters and returns the resulting set. - // The function allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set - // in-place and returns the same array. - function appendTypeParameters(typeParameters: TypeParameter[] | undefined, declarations: readonly TypeParameterDeclaration[]): TypeParameter[] | undefined { - for (const declaration of declarations) { - typeParameters = appendIfUnique(typeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration))); - } - return typeParameters; + // Appends the type parameters given by a list of declarations to a set of type parameters and returns the resulting set. + // The function allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set + // in-place and returns the same array. + function appendTypeParameters(typeParameters: TypeParameter[] | undefined, declarations: readonly TypeParameterDeclaration[]): TypeParameter[] | undefined { + for (const declaration of declarations) { + typeParameters = appendIfUnique(typeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration))); } + return typeParameters; + } - // Return the outer type parameters of a node or undefined if the node has no outer type parameters. - function getOuterTypeParameters(node: Node, includeThisTypes?: boolean): TypeParameter[] | undefined { - while (true) { - node = node.parent; // TODO: GH#18217 Use SourceFile kind check instead - if (node && isBinaryExpression(node)) { - // prototype assignments get the outer type parameters of their constructor function - const assignmentKind = getAssignmentDeclarationKind(node); - if (assignmentKind === AssignmentDeclarationKind.Prototype || assignmentKind === AssignmentDeclarationKind.PrototypeProperty) { - const symbol = getSymbolOfNode(node.left); - if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) { - node = symbol.parent.valueDeclaration!; - } + // Return the outer type parameters of a node or undefined if the node has no outer type parameters. + function getOuterTypeParameters(node: Node, includeThisTypes?: boolean): TypeParameter[] | undefined { + while (true) { + node = node.parent; // TODO: GH#18217 Use SourceFile kind check instead + if (node && isBinaryExpression(node)) { + // prototype assignments get the outer type parameters of their constructor function + const assignmentKind = getAssignmentDeclarationKind(node); + if (assignmentKind === AssignmentDeclarationKind.Prototype || assignmentKind === AssignmentDeclarationKind.PrototypeProperty) { + const symbol = getSymbolOfNode(node.left); + if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) { + node = symbol.parent.valueDeclaration!; } } - if (!node) { - return undefined; + } + if (!node) { + return undefined; + } + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocTemplateTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocEnumTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.MappedType: + case SyntaxKind.ConditionalType: { + const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); + if (node.kind === SyntaxKind.MappedType) { + return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((node as MappedTypeNode).typeParameter))); + } + else if (node.kind === SyntaxKind.ConditionalType) { + return concatenate(outerTypeParameters, getInferTypeParameters(node as ConditionalTypeNode)); + } + const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node as DeclarationWithTypeParameters)); + const thisType = includeThisTypes && + (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) && + getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node as ClassLikeDeclaration | InterfaceDeclaration)).thisType; + return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters; } - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.JSDocTemplateTag: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocEnumTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.MappedType: - case SyntaxKind.ConditionalType: { - const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); - if (node.kind === SyntaxKind.MappedType) { - return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((node as MappedTypeNode).typeParameter))); - } - else if (node.kind === SyntaxKind.ConditionalType) { - return concatenate(outerTypeParameters, getInferTypeParameters(node as ConditionalTypeNode)); - } - const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node as DeclarationWithTypeParameters)); - const thisType = includeThisTypes && - (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) && - getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node as ClassLikeDeclaration | InterfaceDeclaration)).thisType; - return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters; - } - case SyntaxKind.JSDocParameterTag: - const paramSymbol = getParameterSymbolFromJSDoc(node as JSDocParameterTag); - if (paramSymbol) { - node = paramSymbol.valueDeclaration!; - } - break; - case SyntaxKind.JSDoc: { - const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); - return (node as JSDoc).tags - ? appendTypeParameters(outerTypeParameters, flatMap((node as JSDoc).tags, t => isJSDocTemplateTag(t) ? t.typeParameters : undefined)) - : outerTypeParameters; + case SyntaxKind.JSDocParameterTag: + const paramSymbol = getParameterSymbolFromJSDoc(node as JSDocParameterTag); + if (paramSymbol) { + node = paramSymbol.valueDeclaration!; } + break; + case SyntaxKind.JSDoc: { + const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); + return (node as JSDoc).tags + ? appendTypeParameters(outerTypeParameters, flatMap((node as JSDoc).tags, t => isJSDocTemplateTag(t) ? t.typeParameters : undefined)) + : outerTypeParameters; } } } + } - // The outer type parameters are those defined by enclosing generic classes, methods, or functions. - function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { - const declaration = symbol.flags & SymbolFlags.Class ? symbol.valueDeclaration : getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration)!; - Debug.assert(!!declaration, "Class was missing valueDeclaration -OR- non-class had no interface declarations"); - return getOuterTypeParameters(declaration); - } + // The outer type parameters are those defined by enclosing generic classes, methods, or functions. + function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { + const declaration = symbol.flags & SymbolFlags.Class ? symbol.valueDeclaration : getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration)!; + Debug.assert(!!declaration, "Class was missing valueDeclaration -OR- non-class had no interface declarations"); + return getOuterTypeParameters(declaration); + } - // The local type parameters are the combined set of type parameters from all declarations of the class, - // interface, or type alias. - function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] | undefined { - if (!symbol.declarations) { - return; - } - let result: TypeParameter[] | undefined; - for (const node of symbol.declarations) { - if (node.kind === SyntaxKind.InterfaceDeclaration || - node.kind === SyntaxKind.ClassDeclaration || - node.kind === SyntaxKind.ClassExpression || - isJSConstructor(node) || - isTypeAlias(node)) { - const declaration = node as InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag; - result = appendTypeParameters(result, getEffectiveTypeParameterDeclarations(declaration)); - } + // The local type parameters are the combined set of type parameters from all declarations of the class, + // interface, or type alias. + function getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): TypeParameter[] | undefined { + if (!symbol.declarations) { + return; + } + let result: TypeParameter[] | undefined; + for (const node of symbol.declarations) { + if (node.kind === SyntaxKind.InterfaceDeclaration || + node.kind === SyntaxKind.ClassDeclaration || + node.kind === SyntaxKind.ClassExpression || + isJSConstructor(node) || + isTypeAlias(node)) { + const declaration = node as InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag; + result = appendTypeParameters(result, getEffectiveTypeParameterDeclarations(declaration)); } - return result; } + return result; + } - // The full set of type parameters for a generic class or interface type consists of its outer type parameters plus - // its locally declared type parameters. - function getTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { - return concatenate(getOuterTypeParametersOfClassOrInterface(symbol), getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)); - } + // The full set of type parameters for a generic class or interface type consists of its outer type parameters plus + // its locally declared type parameters. + function getTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { + return concatenate(getOuterTypeParametersOfClassOrInterface(symbol), getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)); + } - // A type is a mixin constructor if it has a single construct signature taking no type parameters and a single - // rest parameter of type any[]. - function isMixinConstructorType(type: Type) { - const signatures = getSignaturesOfType(type, SignatureKind.Construct); - if (signatures.length === 1) { - const s = signatures[0]; - if (!s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s)) { - const paramType = getTypeOfParameter(s.parameters[0]); - return isTypeAny(paramType) || getElementTypeOfArrayType(paramType) === anyType; - } + // A type is a mixin constructor if it has a single construct signature taking no type parameters and a single + // rest parameter of type any[]. + function isMixinConstructorType(type: Type) { + const signatures = getSignaturesOfType(type, SignatureKind.Construct); + if (signatures.length === 1) { + const s = signatures[0]; + if (!s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s)) { + const paramType = getTypeOfParameter(s.parameters[0]); + return isTypeAny(paramType) || getElementTypeOfArrayType(paramType) === anyType; } - return false; } + return false; + } - function isConstructorType(type: Type): boolean { - if (getSignaturesOfType(type, SignatureKind.Construct).length > 0) { - return true; - } - if (type.flags & TypeFlags.TypeVariable) { - const constraint = getBaseConstraintOfType(type); - return !!constraint && isMixinConstructorType(constraint); - } - return false; + function isConstructorType(type: Type): boolean { + if (getSignaturesOfType(type, SignatureKind.Construct).length > 0) { + return true; } - - function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { - const decl = getClassLikeDeclarationOfSymbol(type.symbol); - return decl && getEffectiveBaseTypeNode(decl); + if (type.flags & TypeFlags.TypeVariable) { + const constraint = getBaseConstraintOfType(type); + return !!constraint && isMixinConstructorType(constraint); } + return false; + } - function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { - const typeArgCount = length(typeArgumentNodes); - const isJavascript = isInJSFile(location); - return filter(getSignaturesOfType(type, SignatureKind.Construct), - sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters)); - } + function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { + const decl = getClassLikeDeclarationOfSymbol(type.symbol); + return decl && getEffectiveBaseTypeNode(decl); + } - function getInstantiatedConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { - const signatures = getConstructorsForTypeArguments(type, typeArgumentNodes, location); - const typeArguments = map(typeArgumentNodes, getTypeFromTypeNode); - return sameMap(signatures, sig => some(sig.typeParameters) ? getSignatureInstantiation(sig, typeArguments, isInJSFile(location)) : sig); - } + function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { + const typeArgCount = length(typeArgumentNodes); + const isJavascript = isInJSFile(location); + return filter(getSignaturesOfType(type, SignatureKind.Construct), + sig => (isJavascript || typeArgCount >= getMinTypeArgumentCount(sig.typeParameters)) && typeArgCount <= length(sig.typeParameters)); + } - /** - * The base constructor of a class can resolve to - * * undefinedType if the class has no extends clause, - * * unknownType if an error occurred during resolution of the extends expression, - * * nullType if the extends expression is the null value, - * * anyType if the extends expression has type any, or - * * an object type with at least one construct signature. - */ - function getBaseConstructorTypeOfClass(type: InterfaceType): Type { - if (!type.resolvedBaseConstructorType) { - const decl = getClassLikeDeclarationOfSymbol(type.symbol); - const extended = decl && getEffectiveBaseTypeNode(decl); - const baseTypeNode = getBaseTypeNodeOfClass(type); - if (!baseTypeNode) { - return type.resolvedBaseConstructorType = undefinedType; - } - if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseConstructorType)) { - return errorType; - } - const baseConstructorType = checkExpression(baseTypeNode.expression); - if (extended && baseTypeNode !== extended) { - Debug.assert(!extended.typeArguments); // Because this is in a JS file, and baseTypeNode is in an @extends tag - checkExpression(extended.expression); - } - if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection)) { - // Resolving the members of a class requires us to resolve the base class of that class. - // We force resolution here such that we catch circularities now. - resolveStructuredTypeMembers(baseConstructorType as ObjectType); - } - if (!popTypeResolution()) { - error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol)); - return type.resolvedBaseConstructorType = errorType; - } - if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) { - const err = error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType)); - if (baseConstructorType.flags & TypeFlags.TypeParameter) { - const constraint = getConstraintFromTypeParameter(baseConstructorType); - let ctorReturn: Type = unknownType; - if (constraint) { - const ctorSig = getSignaturesOfType(constraint, SignatureKind.Construct); - if (ctorSig[0]) { - ctorReturn = getReturnTypeOfSignature(ctorSig[0]); - } - } - if (baseConstructorType.symbol.declarations) { - addRelatedInfo(err, createDiagnosticForNode(baseConstructorType.symbol.declarations[0], Diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, symbolToString(baseConstructorType.symbol), typeToString(ctorReturn))); + function getInstantiatedConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { + const signatures = getConstructorsForTypeArguments(type, typeArgumentNodes, location); + const typeArguments = map(typeArgumentNodes, getTypeFromTypeNode); + return sameMap(signatures, sig => some(sig.typeParameters) ? getSignatureInstantiation(sig, typeArguments, isInJSFile(location)) : sig); + } + + /** + * The base constructor of a class can resolve to + * * undefinedType if the class has no extends clause, + * * unknownType if an error occurred during resolution of the extends expression, + * * nullType if the extends expression is the null value, + * * anyType if the extends expression has type any, or + * * an object type with at least one construct signature. + */ + function getBaseConstructorTypeOfClass(type: InterfaceType): Type { + if (!type.resolvedBaseConstructorType) { + const decl = getClassLikeDeclarationOfSymbol(type.symbol); + const extended = decl && getEffectiveBaseTypeNode(decl); + const baseTypeNode = getBaseTypeNodeOfClass(type); + if (!baseTypeNode) { + return type.resolvedBaseConstructorType = undefinedType; + } + if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseConstructorType)) { + return errorType; + } + const baseConstructorType = checkExpression(baseTypeNode.expression); + if (extended && baseTypeNode !== extended) { + Debug.assert(!extended.typeArguments); // Because this is in a JS file, and baseTypeNode is in an @extends tag + checkExpression(extended.expression); + } + if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection)) { + // Resolving the members of a class requires us to resolve the base class of that class. + // We force resolution here such that we catch circularities now. + resolveStructuredTypeMembers(baseConstructorType as ObjectType); + } + if (!popTypeResolution()) { + error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol)); + return type.resolvedBaseConstructorType = errorType; + } + if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) { + const err = error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType)); + if (baseConstructorType.flags & TypeFlags.TypeParameter) { + const constraint = getConstraintFromTypeParameter(baseConstructorType); + let ctorReturn: Type = unknownType; + if (constraint) { + const ctorSig = getSignaturesOfType(constraint, SignatureKind.Construct); + if (ctorSig[0]) { + ctorReturn = getReturnTypeOfSignature(ctorSig[0]); } } - return type.resolvedBaseConstructorType = errorType; + if (baseConstructorType.symbol.declarations) { + addRelatedInfo(err, createDiagnosticForNode(baseConstructorType.symbol.declarations[0], Diagnostics.Did_you_mean_for_0_to_be_constrained_to_type_new_args_Colon_any_1, symbolToString(baseConstructorType.symbol), typeToString(ctorReturn))); + } } - type.resolvedBaseConstructorType = baseConstructorType; + return type.resolvedBaseConstructorType = errorType; } - return type.resolvedBaseConstructorType; + type.resolvedBaseConstructorType = baseConstructorType; } + return type.resolvedBaseConstructorType; + } - function getImplementsTypes(type: InterfaceType): BaseType[] { - let resolvedImplementsTypes: BaseType[] = emptyArray; - if (type.symbol.declarations) { - for (const declaration of type.symbol.declarations) { - const implementsTypeNodes = getEffectiveImplementsTypeNodes(declaration as ClassLikeDeclaration); - if (!implementsTypeNodes) continue; - for (const node of implementsTypeNodes) { - const implementsType = getTypeFromTypeNode(node); - if (!isErrorType(implementsType)) { - if (resolvedImplementsTypes === emptyArray) { - resolvedImplementsTypes = [implementsType as ObjectType]; - } - else { - resolvedImplementsTypes.push(implementsType); - } + function getImplementsTypes(type: InterfaceType): BaseType[] { + let resolvedImplementsTypes: BaseType[] = emptyArray; + if (type.symbol.declarations) { + for (const declaration of type.symbol.declarations) { + const implementsTypeNodes = getEffectiveImplementsTypeNodes(declaration as ClassLikeDeclaration); + if (!implementsTypeNodes) continue; + for (const node of implementsTypeNodes) { + const implementsType = getTypeFromTypeNode(node); + if (!isErrorType(implementsType)) { + if (resolvedImplementsTypes === emptyArray) { + resolvedImplementsTypes = [implementsType as ObjectType]; + } + else { + resolvedImplementsTypes.push(implementsType); } } } } - return resolvedImplementsTypes; } + return resolvedImplementsTypes; + } - function reportCircularBaseType(node: Node, type: Type) { - error(node, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); - } + function reportCircularBaseType(node: Node, type: Type) { + error(node, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + } - function getBaseTypes(type: InterfaceType): BaseType[] { - if (!type.baseTypesResolved) { - if (pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseTypes)) { - if (type.objectFlags & ObjectFlags.Tuple) { - type.resolvedBaseTypes = [getTupleBaseType(type as TupleType)]; - } - else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - if (type.symbol.flags & SymbolFlags.Class) { - resolveBaseTypesOfClass(type); - } - if (type.symbol.flags & SymbolFlags.Interface) { - resolveBaseTypesOfInterface(type); - } + function getBaseTypes(type: InterfaceType): BaseType[] { + if (!type.baseTypesResolved) { + if (pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseTypes)) { + if (type.objectFlags & ObjectFlags.Tuple) { + type.resolvedBaseTypes = [getTupleBaseType(type as TupleType)]; + } + else if (type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + if (type.symbol.flags & SymbolFlags.Class) { + resolveBaseTypesOfClass(type); } - else { - Debug.fail("type must be class or interface"); + if (type.symbol.flags & SymbolFlags.Interface) { + resolveBaseTypesOfInterface(type); } - if (!popTypeResolution() && type.symbol.declarations) { - for (const declaration of type.symbol.declarations) { - if (declaration.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.InterfaceDeclaration) { - reportCircularBaseType(declaration, type); - } + } + else { + Debug.fail("type must be class or interface"); + } + if (!popTypeResolution() && type.symbol.declarations) { + for (const declaration of type.symbol.declarations) { + if (declaration.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.InterfaceDeclaration) { + reportCircularBaseType(declaration, type); } } } - type.baseTypesResolved = true; } - return type.resolvedBaseTypes; + type.baseTypesResolved = true; } + return type.resolvedBaseTypes; + } - function getTupleBaseType(type: TupleType) { - const elementTypes = sameMap(type.typeParameters, (t, i) => type.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t); - return createArrayType(getUnionType(elementTypes || emptyArray), type.readonly); - } + function getTupleBaseType(type: TupleType) { + const elementTypes = sameMap(type.typeParameters, (t, i) => type.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t); + return createArrayType(getUnionType(elementTypes || emptyArray), type.readonly); + } - function resolveBaseTypesOfClass(type: InterfaceType) { - type.resolvedBaseTypes = resolvingEmptyArray; - const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type)); - if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) { + function resolveBaseTypesOfClass(type: InterfaceType) { + type.resolvedBaseTypes = resolvingEmptyArray; + const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type)); + if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) { + return type.resolvedBaseTypes = emptyArray; + } + const baseTypeNode = getBaseTypeNodeOfClass(type)!; + let baseType: Type; + const originalBaseType = baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined; + if (baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class && + areAllOuterTypeParametersApplied(originalBaseType!)) { + // When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the + // class and all return the instance type of the class. There is no need for further checks and we can apply the + // type arguments in the same manner as a type reference to get the same error reporting experience. + baseType = getTypeFromClassOrInterfaceReference(baseTypeNode, baseConstructorType.symbol); + } + else if (baseConstructorType.flags & TypeFlags.Any) { + baseType = baseConstructorType; + } + else { + // The class derives from a "class-like" constructor function, check that we have at least one construct signature + // with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere + // we check that all instantiated signatures return the same type. + const constructors = getInstantiatedConstructorsForTypeArguments(baseConstructorType, baseTypeNode.typeArguments, baseTypeNode); + if (!constructors.length) { + error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments); return type.resolvedBaseTypes = emptyArray; } - const baseTypeNode = getBaseTypeNodeOfClass(type)!; - let baseType: Type; - const originalBaseType = baseConstructorType.symbol ? getDeclaredTypeOfSymbol(baseConstructorType.symbol) : undefined; - if (baseConstructorType.symbol && baseConstructorType.symbol.flags & SymbolFlags.Class && - areAllOuterTypeParametersApplied(originalBaseType!)) { - // When base constructor type is a class with no captured type arguments we know that the constructors all have the same type parameters as the - // class and all return the instance type of the class. There is no need for further checks and we can apply the - // type arguments in the same manner as a type reference to get the same error reporting experience. - baseType = getTypeFromClassOrInterfaceReference(baseTypeNode, baseConstructorType.symbol); - } - else if (baseConstructorType.flags & TypeFlags.Any) { - baseType = baseConstructorType; - } - else { - // The class derives from a "class-like" constructor function, check that we have at least one construct signature - // with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere - // we check that all instantiated signatures return the same type. - const constructors = getInstantiatedConstructorsForTypeArguments(baseConstructorType, baseTypeNode.typeArguments, baseTypeNode); - if (!constructors.length) { - error(baseTypeNode.expression, Diagnostics.No_base_constructor_has_the_specified_number_of_type_arguments); - return type.resolvedBaseTypes = emptyArray; - } - baseType = getReturnTypeOfSignature(constructors[0]); - } + baseType = getReturnTypeOfSignature(constructors[0]); + } - if (isErrorType(baseType)) { - return type.resolvedBaseTypes = emptyArray; - } - const reducedBaseType = getReducedType(baseType); - if (!isValidBaseType(reducedBaseType)) { - const elaboration = elaborateNeverIntersection(/*errorInfo*/ undefined, baseType); - const diagnostic = chainDiagnosticMessages(elaboration, Diagnostics.Base_constructor_return_type_0_is_not_an_object_type_or_intersection_of_object_types_with_statically_known_members, typeToString(reducedBaseType)); - diagnostics.add(createDiagnosticForNodeFromMessageChain(baseTypeNode.expression, diagnostic)); - return type.resolvedBaseTypes = emptyArray; - } - if (type === reducedBaseType || hasBaseType(reducedBaseType, type)) { - error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, - typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); - return type.resolvedBaseTypes = emptyArray; - } - if (type.resolvedBaseTypes === resolvingEmptyArray) { - // Circular reference, likely through instantiation of default parameters - // (otherwise there'd be an error from hasBaseType) - this is fine, but `.members` should be reset - // as `getIndexedAccessType` via `instantiateType` via `getTypeFromClassOrInterfaceReference` forces a - // partial instantiation of the members without the base types fully resolved - type.members = undefined; - } - return type.resolvedBaseTypes = [reducedBaseType]; + if (isErrorType(baseType)) { + return type.resolvedBaseTypes = emptyArray; + } + const reducedBaseType = getReducedType(baseType); + if (!isValidBaseType(reducedBaseType)) { + const elaboration = elaborateNeverIntersection(/*errorInfo*/ undefined, baseType); + const diagnostic = chainDiagnosticMessages(elaboration, Diagnostics.Base_constructor_return_type_0_is_not_an_object_type_or_intersection_of_object_types_with_statically_known_members, typeToString(reducedBaseType)); + diagnostics.add(createDiagnosticForNodeFromMessageChain(baseTypeNode.expression, diagnostic)); + return type.resolvedBaseTypes = emptyArray; + } + if (type === reducedBaseType || hasBaseType(reducedBaseType, type)) { + error(type.symbol.valueDeclaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, + typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + return type.resolvedBaseTypes = emptyArray; + } + if (type.resolvedBaseTypes === resolvingEmptyArray) { + // Circular reference, likely through instantiation of default parameters + // (otherwise there'd be an error from hasBaseType) - this is fine, but `.members` should be reset + // as `getIndexedAccessType` via `instantiateType` via `getTypeFromClassOrInterfaceReference` forces a + // partial instantiation of the members without the base types fully resolved + type.members = undefined; + } + return type.resolvedBaseTypes = [reducedBaseType]; + } + + function areAllOuterTypeParametersApplied(type: Type): boolean { // TODO: GH#18217 Shouldn't this take an InterfaceType? + // An unapplied type parameter has its symbol still the same as the matching argument symbol. + // Since parameters are applied outer-to-inner, only the last outer parameter needs to be checked. + const outerTypeParameters = (type as InterfaceType).outerTypeParameters; + if (outerTypeParameters) { + const last = outerTypeParameters.length - 1; + const typeArguments = getTypeArguments(type as TypeReference); + return outerTypeParameters[last].symbol !== typeArguments[last].symbol; } + return true; + } - function areAllOuterTypeParametersApplied(type: Type): boolean { // TODO: GH#18217 Shouldn't this take an InterfaceType? - // An unapplied type parameter has its symbol still the same as the matching argument symbol. - // Since parameters are applied outer-to-inner, only the last outer parameter needs to be checked. - const outerTypeParameters = (type as InterfaceType).outerTypeParameters; - if (outerTypeParameters) { - const last = outerTypeParameters.length - 1; - const typeArguments = getTypeArguments(type as TypeReference); - return outerTypeParameters[last].symbol !== typeArguments[last].symbol; + // A valid base type is `any`, an object type or intersection of object types. + function isValidBaseType(type: Type): type is BaseType { + if (type.flags & TypeFlags.TypeParameter) { + const constraint = getBaseConstraintOfType(type); + if (constraint) { + return isValidBaseType(constraint); } - return true; } + // TODO: Given that we allow type parmeters here now, is this `!isGenericMappedType(type)` check really needed? + // There's no reason a `T` should be allowed while a `Readonly` should not. + return !!(type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any) && !isGenericMappedType(type) || + type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isValidBaseType)); + } - // A valid base type is `any`, an object type or intersection of object types. - function isValidBaseType(type: Type): type is BaseType { - if (type.flags & TypeFlags.TypeParameter) { - const constraint = getBaseConstraintOfType(type); - if (constraint) { - return isValidBaseType(constraint); - } - } - // TODO: Given that we allow type parmeters here now, is this `!isGenericMappedType(type)` check really needed? - // There's no reason a `T` should be allowed while a `Readonly` should not. - return !!(type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any) && !isGenericMappedType(type) || - type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isValidBaseType)); - } - - function resolveBaseTypesOfInterface(type: InterfaceType): void { - type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray; - if (type.symbol.declarations) { - for (const declaration of type.symbol.declarations) { - if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)) { - for (const node of getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)!) { - const baseType = getReducedType(getTypeFromTypeNode(node)); - if (!isErrorType(baseType)) { - if (isValidBaseType(baseType)) { - if (type !== baseType && !hasBaseType(baseType, type)) { - if (type.resolvedBaseTypes === emptyArray) { - type.resolvedBaseTypes = [baseType as ObjectType]; - } - else { - type.resolvedBaseTypes.push(baseType); - } + function resolveBaseTypesOfInterface(type: InterfaceType): void { + type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray; + if (type.symbol.declarations) { + for (const declaration of type.symbol.declarations) { + if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)) { + for (const node of getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)!) { + const baseType = getReducedType(getTypeFromTypeNode(node)); + if (!isErrorType(baseType)) { + if (isValidBaseType(baseType)) { + if (type !== baseType && !hasBaseType(baseType, type)) { + if (type.resolvedBaseTypes === emptyArray) { + type.resolvedBaseTypes = [baseType as ObjectType]; } else { - reportCircularBaseType(declaration, type); + type.resolvedBaseTypes.push(baseType); } } else { - error(node, Diagnostics.An_interface_can_only_extend_an_object_type_or_intersection_of_object_types_with_statically_known_members); + reportCircularBaseType(declaration, type); } } + else { + error(node, Diagnostics.An_interface_can_only_extend_an_object_type_or_intersection_of_object_types_with_statically_known_members); + } } } } } } + } - /** - * Returns true if the interface given by the symbol is free of "this" references. - * - * Specifically, the result is true if the interface itself contains no references - * to "this" in its body, if all base types are interfaces, - * and if none of the base interfaces have a "this" type. - */ - function isThislessInterface(symbol: Symbol): boolean { - if (!symbol.declarations) { - return true; - } - for (const declaration of symbol.declarations) { - if (declaration.kind === SyntaxKind.InterfaceDeclaration) { - if (declaration.flags & NodeFlags.ContainsThis) { - return false; - } - const baseTypeNodes = getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration); - if (baseTypeNodes) { - for (const node of baseTypeNodes) { - if (isEntityNameExpression(node.expression)) { - const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true); - if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) { - return false; - } + /** + * Returns true if the interface given by the symbol is free of "this" references. + * + * Specifically, the result is true if the interface itself contains no references + * to "this" in its body, if all base types are interfaces, + * and if none of the base interfaces have a "this" type. + */ + function isThislessInterface(symbol: Symbol): boolean { + if (!symbol.declarations) { + return true; + } + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.InterfaceDeclaration) { + if (declaration.flags & NodeFlags.ContainsThis) { + return false; + } + const baseTypeNodes = getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration); + if (baseTypeNodes) { + for (const node of baseTypeNodes) { + if (isEntityNameExpression(node.expression)) { + const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true); + if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) { + return false; } } } } } - return true; } + return true; + } - function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType { - let links = getSymbolLinks(symbol); - const originalLinks = links; - if (!links.declaredType) { - const kind = symbol.flags & SymbolFlags.Class ? ObjectFlags.Class : ObjectFlags.Interface; - const merged = mergeJSSymbols(symbol, symbol.valueDeclaration && getAssignedClassSymbol(symbol.valueDeclaration)); - if (merged) { - // note:we overwrite links because we just cloned the symbol - symbol = links = merged; - } + function getDeclaredTypeOfClassOrInterface(symbol: Symbol): InterfaceType { + let links = getSymbolLinks(symbol); + const originalLinks = links; + if (!links.declaredType) { + const kind = symbol.flags & SymbolFlags.Class ? ObjectFlags.Class : ObjectFlags.Interface; + const merged = mergeJSSymbols(symbol, symbol.valueDeclaration && getAssignedClassSymbol(symbol.valueDeclaration)); + if (merged) { + // note:we overwrite links because we just cloned the symbol + symbol = links = merged; + } + + const type = originalLinks.declaredType = links.declaredType = createObjectType(kind, symbol) as InterfaceType; + const outerTypeParameters = getOuterTypeParametersOfClassOrInterface(symbol); + const localTypeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + // A class or interface is generic if it has type parameters or a "this" type. We always give classes a "this" type + // because it is not feasible to analyze all members to determine if the "this" type escapes the class (in particular, + // property types inferred from initializers and method return types inferred from return statements are very hard + // to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of + // "this" references. + if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) { + type.objectFlags |= ObjectFlags.Reference; + type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); + type.outerTypeParameters = outerTypeParameters; + type.localTypeParameters = localTypeParameters; + (type as GenericType).instantiations = new Map(); + (type as GenericType).instantiations.set(getTypeListId(type.typeParameters), type as GenericType); + (type as GenericType).target = type as GenericType; + (type as GenericType).resolvedTypeArguments = type.typeParameters; + type.thisType = createTypeParameter(symbol); + type.thisType.isThisType = true; + type.thisType.constraint = type; + } + } + return links.declaredType as InterfaceType; + } - const type = originalLinks.declaredType = links.declaredType = createObjectType(kind, symbol) as InterfaceType; - const outerTypeParameters = getOuterTypeParametersOfClassOrInterface(symbol); - const localTypeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - // A class or interface is generic if it has type parameters or a "this" type. We always give classes a "this" type - // because it is not feasible to analyze all members to determine if the "this" type escapes the class (in particular, - // property types inferred from initializers and method return types inferred from return statements are very hard - // to exhaustively analyze). We give interfaces a "this" type if we can't definitely determine that they are free of - // "this" references. - if (outerTypeParameters || localTypeParameters || kind === ObjectFlags.Class || !isThislessInterface(symbol)) { - type.objectFlags |= ObjectFlags.Reference; - type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); - type.outerTypeParameters = outerTypeParameters; - type.localTypeParameters = localTypeParameters; - (type as GenericType).instantiations = new Map(); - (type as GenericType).instantiations.set(getTypeListId(type.typeParameters), type as GenericType); - (type as GenericType).target = type as GenericType; - (type as GenericType).resolvedTypeArguments = type.typeParameters; - type.thisType = createTypeParameter(symbol); - type.thisType.isThisType = true; - type.thisType.constraint = type; - } - } - return links.declaredType as InterfaceType; - } - - function getDeclaredTypeOfTypeAlias(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.declaredType) { - // Note that we use the links object as the target here because the symbol object is used as the unique - // identity for resolution of the 'type' property in SymbolLinks. - if (!pushTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) { - return errorType; - } + function getDeclaredTypeOfTypeAlias(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.declaredType) { + // Note that we use the links object as the target here because the symbol object is used as the unique + // identity for resolution of the 'type' property in SymbolLinks. + if (!pushTypeResolution(symbol, TypeSystemPropertyName.DeclaredType)) { + return errorType; + } - const declaration = Debug.checkDefined(symbol.declarations?.find(isTypeAlias), "Type alias symbol with no valid declaration found"); - const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type; - // If typeNode is missing, we will error in checkJSDocTypedefTag. - let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType; + const declaration = Debug.checkDefined(symbol.declarations?.find(isTypeAlias), "Type alias symbol with no valid declaration found"); + const typeNode = isJSDocTypeAlias(declaration) ? declaration.typeExpression : declaration.type; + // If typeNode is missing, we will error in checkJSDocTypedefTag. + let type = typeNode ? getTypeFromTypeNode(typeNode) : errorType; - if (popTypeResolution()) { - const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - if (typeParameters) { - // Initialize the instantiation cache for generic type aliases. The declared type corresponds to - // an instantiation of the type alias with the type parameters supplied as type arguments. - links.typeParameters = typeParameters; - links.instantiations = new Map(); - links.instantiations.set(getTypeListId(typeParameters), type); - } + if (popTypeResolution()) { + const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + if (typeParameters) { + // Initialize the instantiation cache for generic type aliases. The declared type corresponds to + // an instantiation of the type alias with the type parameters supplied as type arguments. + links.typeParameters = typeParameters; + links.instantiations = new Map(); + links.instantiations.set(getTypeListId(typeParameters), type); + } + } + else { + type = errorType; + if (declaration.kind === SyntaxKind.JSDocEnumTag) { + error(declaration.typeExpression.type, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); } else { - type = errorType; - if (declaration.kind === SyntaxKind.JSDocEnumTag) { - error(declaration.typeExpression.type, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); - } - else { - error(isNamedDeclaration(declaration) ? declaration.name || declaration : declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); - } + error(isNamedDeclaration(declaration) ? declaration.name || declaration : declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); } - links.declaredType = type; } - return links.declaredType; + links.declaredType = type; } + return links.declaredType; + } - function getBaseTypeOfEnumLiteralType(type: Type) { - return type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union) ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)!) : type; - } + function getBaseTypeOfEnumLiteralType(type: Type) { + return type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union) ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)!) : type; + } - function getDeclaredTypeOfEnum(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - if (!links.declaredType) { - const memberTypeList: Type[] = []; - if (symbol.declarations) { - for (const declaration of symbol.declarations) { - if (declaration.kind === SyntaxKind.EnumDeclaration) { - for (const member of (declaration as EnumDeclaration).members) { - if (hasBindableName(member)) { - const memberSymbol = getSymbolOfNode(member); - const value = getEnumMemberValue(member); - const memberType = value !== undefined ? - getFreshTypeOfLiteralType(getEnumLiteralType(value, getSymbolId(symbol), memberSymbol)) : - createTypeWithSymbol(TypeFlags.Enum, memberSymbol); - getSymbolLinks(memberSymbol).declaredType = memberType; - memberTypeList.push(getRegularTypeOfLiteralType(memberType)); - } + function getDeclaredTypeOfEnum(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.declaredType) { + const memberTypeList: Type[] = []; + if (symbol.declarations) { + for (const declaration of symbol.declarations) { + if (declaration.kind === SyntaxKind.EnumDeclaration) { + for (const member of (declaration as EnumDeclaration).members) { + if (hasBindableName(member)) { + const memberSymbol = getSymbolOfNode(member); + const value = getEnumMemberValue(member); + const memberType = value !== undefined ? + getFreshTypeOfLiteralType(getEnumLiteralType(value, getSymbolId(symbol), memberSymbol)) : + createTypeWithSymbol(TypeFlags.Enum, memberSymbol); + getSymbolLinks(memberSymbol).declaredType = memberType; + memberTypeList.push(getRegularTypeOfLiteralType(memberType)); } } } } - const enumType = memberTypeList.length ? - getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined) : - createTypeWithSymbol(TypeFlags.Enum, symbol); - if (enumType.flags & TypeFlags.Union) { - enumType.flags |= TypeFlags.EnumLiteral; - enumType.symbol = symbol; - } - links.declaredType = enumType; } - return links.declaredType; + const enumType = memberTypeList.length ? + getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined) : + createTypeWithSymbol(TypeFlags.Enum, symbol); + if (enumType.flags & TypeFlags.Union) { + enumType.flags |= TypeFlags.EnumLiteral; + enumType.symbol = symbol; + } + links.declaredType = enumType; } + return links.declaredType; + } - function getDeclaredTypeOfEnumMember(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); + function getDeclaredTypeOfEnumMember(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.declaredType) { + const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)!); if (!links.declaredType) { - const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)!); - if (!links.declaredType) { - links.declaredType = enumType; - } + links.declaredType = enumType; } - return links.declaredType; } + return links.declaredType; + } - function getDeclaredTypeOfTypeParameter(symbol: Symbol): TypeParameter { - const links = getSymbolLinks(symbol); - return links.declaredType || (links.declaredType = createTypeParameter(symbol)); - } + function getDeclaredTypeOfTypeParameter(symbol: Symbol): TypeParameter { + const links = getSymbolLinks(symbol); + return links.declaredType || (links.declaredType = createTypeParameter(symbol)); + } - function getDeclaredTypeOfAlias(symbol: Symbol): Type { - const links = getSymbolLinks(symbol); - return links.declaredType || (links.declaredType = getDeclaredTypeOfSymbol(resolveAlias(symbol))); - } + function getDeclaredTypeOfAlias(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + return links.declaredType || (links.declaredType = getDeclaredTypeOfSymbol(resolveAlias(symbol))); + } - function getDeclaredTypeOfSymbol(symbol: Symbol): Type { - return tryGetDeclaredTypeOfSymbol(symbol) || errorType; - } + function getDeclaredTypeOfSymbol(symbol: Symbol): Type { + return tryGetDeclaredTypeOfSymbol(symbol) || errorType; + } - function tryGetDeclaredTypeOfSymbol(symbol: Symbol): Type | undefined { - if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - return getDeclaredTypeOfClassOrInterface(symbol); - } - if (symbol.flags & SymbolFlags.TypeAlias) { - return getDeclaredTypeOfTypeAlias(symbol); - } - if (symbol.flags & SymbolFlags.TypeParameter) { - return getDeclaredTypeOfTypeParameter(symbol); - } - if (symbol.flags & SymbolFlags.Enum) { - return getDeclaredTypeOfEnum(symbol); - } - if (symbol.flags & SymbolFlags.EnumMember) { - return getDeclaredTypeOfEnumMember(symbol); - } - if (symbol.flags & SymbolFlags.Alias) { - return getDeclaredTypeOfAlias(symbol); - } - return undefined; + function tryGetDeclaredTypeOfSymbol(symbol: Symbol): Type | undefined { + if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + return getDeclaredTypeOfClassOrInterface(symbol); } - - /** - * A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string - * literal type, an array with an element type that is free of this references, or a type reference that is - * free of this references. - */ - function isThislessType(node: TypeNode): boolean { - switch (node.kind) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.UnknownKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.UndefinedKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.LiteralType: - return true; - case SyntaxKind.ArrayType: - return isThislessType((node as ArrayTypeNode).elementType); - case SyntaxKind.TypeReference: - return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments!.every(isThislessType); - } - return false; + if (symbol.flags & SymbolFlags.TypeAlias) { + return getDeclaredTypeOfTypeAlias(symbol); + } + if (symbol.flags & SymbolFlags.TypeParameter) { + return getDeclaredTypeOfTypeParameter(symbol); + } + if (symbol.flags & SymbolFlags.Enum) { + return getDeclaredTypeOfEnum(symbol); } - - /** A type parameter is thisless if its constraint is thisless, or if it has no constraint. */ - function isThislessTypeParameter(node: TypeParameterDeclaration) { - const constraint = getEffectiveConstraintOfTypeParameter(node); - return !constraint || isThislessType(constraint); + if (symbol.flags & SymbolFlags.EnumMember) { + return getDeclaredTypeOfEnumMember(symbol); } - - /** - * A variable-like declaration is free of this references if it has a type annotation - * that is thisless, or if it has no type annotation and no initializer (and is thus of type any). - */ - function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean { - const typeNode = getEffectiveTypeAnnotationNode(node); - return typeNode ? isThislessType(typeNode) : !hasInitializer(node); + if (symbol.flags & SymbolFlags.Alias) { + return getDeclaredTypeOfAlias(symbol); } + return undefined; + } - /** - * A function-like declaration is considered free of `this` references if it has a return type - * annotation that is free of this references and if each parameter is thisless and if - * each type parameter (if present) is thisless. - */ - function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { - const returnType = getEffectiveReturnTypeNode(node); - const typeParameters = getEffectiveTypeParameterDeclarations(node); - return (node.kind === SyntaxKind.Constructor || (!!returnType && isThislessType(returnType))) && - node.parameters.every(isThislessVariableLikeDeclaration) && - typeParameters.every(isThislessTypeParameter); + /** + * A type is free of this references if it's the any, string, number, boolean, symbol, or void keyword, a string + * literal type, an array with an element type that is free of this references, or a type reference that is + * free of this references. + */ + function isThislessType(node: TypeNode): boolean { + switch (node.kind) { + case SyntaxKind.AnyKeyword: + case SyntaxKind.UnknownKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.LiteralType: + return true; + case SyntaxKind.ArrayType: + return isThislessType((node as ArrayTypeNode).elementType); + case SyntaxKind.TypeReference: + return !(node as TypeReferenceNode).typeArguments || (node as TypeReferenceNode).typeArguments!.every(isThislessType); } + return false; + } - /** - * Returns true if the class or interface member given by the symbol is free of "this" references. The - * function may return false for symbols that are actually free of "this" references because it is not - * feasible to perform a complete analysis in all cases. In particular, property members with types - * inferred from their initializers and function members with inferred return types are conservatively - * assumed not to be free of "this" references. - */ - function isThisless(symbol: Symbol): boolean { - if (symbol.declarations && symbol.declarations.length === 1) { - const declaration = symbol.declarations[0]; - if (declaration) { - switch (declaration.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return isThislessVariableLikeDeclaration(declaration as VariableLikeDeclaration); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return isThislessFunctionLikeDeclaration(declaration as FunctionLikeDeclaration | AccessorDeclaration); - } + /** A type parameter is thisless if its constraint is thisless, or if it has no constraint. */ + function isThislessTypeParameter(node: TypeParameterDeclaration) { + const constraint = getEffectiveConstraintOfTypeParameter(node); + return !constraint || isThislessType(constraint); + } + + /** + * A variable-like declaration is free of this references if it has a type annotation + * that is thisless, or if it has no type annotation and no initializer (and is thus of type any). + */ + function isThislessVariableLikeDeclaration(node: VariableLikeDeclaration): boolean { + const typeNode = getEffectiveTypeAnnotationNode(node); + return typeNode ? isThislessType(typeNode) : !hasInitializer(node); + } + + /** + * A function-like declaration is considered free of `this` references if it has a return type + * annotation that is free of this references and if each parameter is thisless and if + * each type parameter (if present) is thisless. + */ + function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { + const returnType = getEffectiveReturnTypeNode(node); + const typeParameters = getEffectiveTypeParameterDeclarations(node); + return (node.kind === SyntaxKind.Constructor || (!!returnType && isThislessType(returnType))) && + node.parameters.every(isThislessVariableLikeDeclaration) && + typeParameters.every(isThislessTypeParameter); + } + + /** + * Returns true if the class or interface member given by the symbol is free of "this" references. The + * function may return false for symbols that are actually free of "this" references because it is not + * feasible to perform a complete analysis in all cases. In particular, property members with types + * inferred from their initializers and function members with inferred return types are conservatively + * assumed not to be free of "this" references. + */ + function isThisless(symbol: Symbol): boolean { + if (symbol.declarations && symbol.declarations.length === 1) { + const declaration = symbol.declarations[0]; + if (declaration) { + switch (declaration.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return isThislessVariableLikeDeclaration(declaration as VariableLikeDeclaration); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return isThislessFunctionLikeDeclaration(declaration as FunctionLikeDeclaration | AccessorDeclaration); } } - return false; } + return false; + } - // The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, - // we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. - function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable { - const result = createSymbolTable(); - for (const symbol of symbols) { - result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper)); - } - return result; + // The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, + // we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. + function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable { + const result = createSymbolTable(); + for (const symbol of symbols) { + result.set(symbol.escapedName, mappingThisOnly && isThisless(symbol) ? symbol : instantiateSymbol(symbol, mapper)); } + return result; + } - function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) { - for (const s of baseSymbols) { - if (!symbols.has(s.escapedName) && !isStaticPrivateIdentifierProperty(s)) { - symbols.set(s.escapedName, s); - } + function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) { + for (const s of baseSymbols) { + if (!symbols.has(s.escapedName) && !isStaticPrivateIdentifierProperty(s)) { + symbols.set(s.escapedName, s); } } + } - function isStaticPrivateIdentifierProperty(s: Symbol): boolean { - return !!s.valueDeclaration && isPrivateIdentifierClassElementDeclaration(s.valueDeclaration) && isStatic(s.valueDeclaration); - } + function isStaticPrivateIdentifierProperty(s: Symbol): boolean { + return !!s.valueDeclaration && isPrivateIdentifierClassElementDeclaration(s.valueDeclaration) && isStatic(s.valueDeclaration); + } - function resolveDeclaredMembers(type: InterfaceType): InterfaceTypeWithDeclaredMembers { - if (!(type as InterfaceTypeWithDeclaredMembers).declaredProperties) { - const symbol = type.symbol; - const members = getMembersOfSymbol(symbol); - (type as InterfaceTypeWithDeclaredMembers).declaredProperties = getNamedMembers(members); - // Start with signatures at empty array in case of recursive types - (type as InterfaceTypeWithDeclaredMembers).declaredCallSignatures = emptyArray; - (type as InterfaceTypeWithDeclaredMembers).declaredConstructSignatures = emptyArray; - (type as InterfaceTypeWithDeclaredMembers).declaredIndexInfos = emptyArray; + function resolveDeclaredMembers(type: InterfaceType): InterfaceTypeWithDeclaredMembers { + if (!(type as InterfaceTypeWithDeclaredMembers).declaredProperties) { + const symbol = type.symbol; + const members = getMembersOfSymbol(symbol); + (type as InterfaceTypeWithDeclaredMembers).declaredProperties = getNamedMembers(members); + // Start with signatures at empty array in case of recursive types + (type as InterfaceTypeWithDeclaredMembers).declaredCallSignatures = emptyArray; + (type as InterfaceTypeWithDeclaredMembers).declaredConstructSignatures = emptyArray; + (type as InterfaceTypeWithDeclaredMembers).declaredIndexInfos = emptyArray; + + (type as InterfaceTypeWithDeclaredMembers).declaredCallSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); + (type as InterfaceTypeWithDeclaredMembers).declaredConstructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); + (type as InterfaceTypeWithDeclaredMembers).declaredIndexInfos = getIndexInfosOfSymbol(symbol); + } + return type as InterfaceTypeWithDeclaredMembers; + } - (type as InterfaceTypeWithDeclaredMembers).declaredCallSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); - (type as InterfaceTypeWithDeclaredMembers).declaredConstructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); - (type as InterfaceTypeWithDeclaredMembers).declaredIndexInfos = getIndexInfosOfSymbol(symbol); - } - return type as InterfaceTypeWithDeclaredMembers; - } + /** + * Indicates whether a type can be used as a property name. + */ + function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType { + return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique); + } - /** - * Indicates whether a type can be used as a property name. - */ - function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType { - return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique); + /** + * Indicates whether a declaration name is definitely late-bindable. + * A declaration name is only late-bindable if: + * - It is a `ComputedPropertyName`. + * - Its expression is an `Identifier` or either a `PropertyAccessExpression` an + * `ElementAccessExpression` consisting only of these same three types of nodes. + * - The type of its expression is a string or numeric literal type, or is a `unique symbol` type. + */ + function isLateBindableName(node: DeclarationName): node is LateBoundName { + if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) { + return false; } + const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression; + return isEntityNameExpression(expr) + && isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached(expr)); + } - /** - * Indicates whether a declaration name is definitely late-bindable. - * A declaration name is only late-bindable if: - * - It is a `ComputedPropertyName`. - * - Its expression is an `Identifier` or either a `PropertyAccessExpression` an - * `ElementAccessExpression` consisting only of these same three types of nodes. - * - The type of its expression is a string or numeric literal type, or is a `unique symbol` type. - */ - function isLateBindableName(node: DeclarationName): node is LateBoundName { - if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) { - return false; - } - const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression; - return isEntityNameExpression(expr) - && isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached(expr)); - } + function isLateBoundName(name: __String): boolean { + return (name as string).charCodeAt(0) === CharacterCodes._ && + (name as string).charCodeAt(1) === CharacterCodes._ && + (name as string).charCodeAt(2) === CharacterCodes.at; + } - function isLateBoundName(name: __String): boolean { - return (name as string).charCodeAt(0) === CharacterCodes._ && - (name as string).charCodeAt(1) === CharacterCodes._ && - (name as string).charCodeAt(2) === CharacterCodes.at; - } + /** + * Indicates whether a declaration has a late-bindable dynamic name. + */ + function hasLateBindableName(node: Declaration): node is LateBoundDeclaration | LateBoundBinaryExpressionDeclaration { + const name = getNameOfDeclaration(node); + return !!name && isLateBindableName(name); + } - /** - * Indicates whether a declaration has a late-bindable dynamic name. - */ - function hasLateBindableName(node: Declaration): node is LateBoundDeclaration | LateBoundBinaryExpressionDeclaration { - const name = getNameOfDeclaration(node); - return !!name && isLateBindableName(name); - } + /** + * Indicates whether a declaration has an early-bound name or a dynamic name that can be late-bound. + */ + function hasBindableName(node: Declaration) { + return !hasDynamicName(node) || hasLateBindableName(node); + } - /** - * Indicates whether a declaration has an early-bound name or a dynamic name that can be late-bound. - */ - function hasBindableName(node: Declaration) { - return !hasDynamicName(node) || hasLateBindableName(node); - } + /** + * Indicates whether a declaration name is a dynamic name that cannot be late-bound. + */ + function isNonBindableDynamicName(node: DeclarationName) { + return isDynamicName(node) && !isLateBindableName(node); + } - /** - * Indicates whether a declaration name is a dynamic name that cannot be late-bound. - */ - function isNonBindableDynamicName(node: DeclarationName) { - return isDynamicName(node) && !isLateBindableName(node); + /** + * Gets the symbolic name for a member from its type. + */ + function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String { + if (type.flags & TypeFlags.UniqueESSymbol) { + return (type as UniqueESSymbolType).escapedName; } - - /** - * Gets the symbolic name for a member from its type. - */ - function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String { - if (type.flags & TypeFlags.UniqueESSymbol) { - return (type as UniqueESSymbolType).escapedName; - } - if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { - return escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value); - } - return Debug.fail(); + if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { + return escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value); } + return Debug.fail(); + } - /** - * Adds a declaration to a late-bound dynamic member. This performs the same function for - * late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound - * members. - */ - function addDeclarationToLateBoundSymbol(symbol: Symbol, member: LateBoundDeclaration | BinaryExpression, symbolFlags: SymbolFlags) { - Debug.assert(!!(getCheckFlags(symbol) & CheckFlags.Late), "Expected a late-bound symbol."); - symbol.flags |= symbolFlags; - getSymbolLinks(member.symbol).lateSymbol = symbol; - if (!symbol.declarations) { - symbol.declarations = [member]; - } - else if(!member.symbol.isReplaceableByMethod) { - symbol.declarations.push(member); - } - if (symbolFlags & SymbolFlags.Value) { - if (!symbol.valueDeclaration || symbol.valueDeclaration.kind !== member.kind) { - symbol.valueDeclaration = member; - } + /** + * Adds a declaration to a late-bound dynamic member. This performs the same function for + * late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound + * members. + */ + function addDeclarationToLateBoundSymbol(symbol: Symbol, member: LateBoundDeclaration | BinaryExpression, symbolFlags: SymbolFlags) { + Debug.assert(!!(getCheckFlags(symbol) & CheckFlags.Late), "Expected a late-bound symbol."); + symbol.flags |= symbolFlags; + getSymbolLinks(member.symbol).lateSymbol = symbol; + if (!symbol.declarations) { + symbol.declarations = [member]; + } + else if(!member.symbol.isReplaceableByMethod) { + symbol.declarations.push(member); + } + if (symbolFlags & SymbolFlags.Value) { + if (!symbol.valueDeclaration || symbol.valueDeclaration.kind !== member.kind) { + symbol.valueDeclaration = member; } } + } - /** - * Performs late-binding of a dynamic member. This performs the same function for - * late-bound members that `declareSymbol` in binder.ts performs for early-bound - * members. - * - * If a symbol is a dynamic name from a computed property, we perform an additional "late" - * binding phase to attempt to resolve the name for the symbol from the type of the computed - * property's expression. If the type of the expression is a string-literal, numeric-literal, - * or unique symbol type, we can use that type as the name of the symbol. - * - * For example, given: - * - * const x = Symbol(); - * - * interface I { - * [x]: number; - * } - * - * The binder gives the property `[x]: number` a special symbol with the name "__computed". - * In the late-binding phase we can type-check the expression `x` and see that it has a - * unique symbol type which we can then use as the name of the member. This allows users - * to define custom symbols that can be used in the members of an object type. - * - * @param parent The containing symbol for the member. - * @param earlySymbols The early-bound symbols of the parent. - * @param lateSymbols The late-bound symbols of the parent. - * @param decl The member to bind. - */ - function lateBindMember(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: UnderscoreEscapedMap, decl: LateBoundDeclaration | LateBoundBinaryExpressionDeclaration) { - Debug.assert(!!decl.symbol, "The member is expected to have a symbol."); - const links = getNodeLinks(decl); - if (!links.resolvedSymbol) { - // In the event we attempt to resolve the late-bound name of this member recursively, - // fall back to the early-bound name of this member. - links.resolvedSymbol = decl.symbol; - const declName = isBinaryExpression(decl) ? decl.left : decl.name; - const type = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName); - if (isTypeUsableAsPropertyName(type)) { - const memberName = getPropertyNameFromType(type); - const symbolFlags = decl.symbol.flags; - - // Get or add a late-bound symbol for the member. This allows us to merge late-bound accessor declarations. - let lateSymbol = lateSymbols.get(memberName); - if (!lateSymbol) lateSymbols.set(memberName, lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late)); - - // Report an error if a late-bound member has the same name as an early-bound member, - // or if we have another early-bound symbol declaration with the same name and - // conflicting flags. - const earlySymbol = earlySymbols && earlySymbols.get(memberName); - if (lateSymbol.flags & getExcludedSymbolFlags(symbolFlags) || earlySymbol) { - // If we have an existing early-bound member, combine its declarations so that we can - // report an error at each declaration. - const declarations = earlySymbol ? concatenate(earlySymbol.declarations, lateSymbol.declarations) : lateSymbol.declarations; - const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(declName); - forEach(declarations, declaration => error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Property_0_was_also_declared_here, name)); - error(declName || decl, Diagnostics.Duplicate_property_0, name); - lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late); - } - lateSymbol.nameType = type; - addDeclarationToLateBoundSymbol(lateSymbol, decl, symbolFlags); - if (lateSymbol.parent) { - Debug.assert(lateSymbol.parent === parent, "Existing symbol parent should match new one"); - } - else { - lateSymbol.parent = parent; - } - return links.resolvedSymbol = lateSymbol; + /** + * Performs late-binding of a dynamic member. This performs the same function for + * late-bound members that `declareSymbol` in binder.ts performs for early-bound + * members. + * + * If a symbol is a dynamic name from a computed property, we perform an additional "late" + * binding phase to attempt to resolve the name for the symbol from the type of the computed + * property's expression. If the type of the expression is a string-literal, numeric-literal, + * or unique symbol type, we can use that type as the name of the symbol. + * + * For example, given: + * + * const x = Symbol(); + * + * interface I { + * [x]: number; + * } + * + * The binder gives the property `[x]: number` a special symbol with the name "__computed". + * In the late-binding phase we can type-check the expression `x` and see that it has a + * unique symbol type which we can then use as the name of the member. This allows users + * to define custom symbols that can be used in the members of an object type. + * + * @param parent The containing symbol for the member. + * @param earlySymbols The early-bound symbols of the parent. + * @param lateSymbols The late-bound symbols of the parent. + * @param decl The member to bind. + */ + function lateBindMember(parent: Symbol, earlySymbols: SymbolTable | undefined, lateSymbols: UnderscoreEscapedMap, decl: LateBoundDeclaration | LateBoundBinaryExpressionDeclaration) { + Debug.assert(!!decl.symbol, "The member is expected to have a symbol."); + const links = getNodeLinks(decl); + if (!links.resolvedSymbol) { + // In the event we attempt to resolve the late-bound name of this member recursively, + // fall back to the early-bound name of this member. + links.resolvedSymbol = decl.symbol; + const declName = isBinaryExpression(decl) ? decl.left : decl.name; + const type = isElementAccessExpression(declName) ? checkExpressionCached(declName.argumentExpression) : checkComputedPropertyName(declName); + if (isTypeUsableAsPropertyName(type)) { + const memberName = getPropertyNameFromType(type); + const symbolFlags = decl.symbol.flags; + + // Get or add a late-bound symbol for the member. This allows us to merge late-bound accessor declarations. + let lateSymbol = lateSymbols.get(memberName); + if (!lateSymbol) lateSymbols.set(memberName, lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late)); + + // Report an error if a late-bound member has the same name as an early-bound member, + // or if we have another early-bound symbol declaration with the same name and + // conflicting flags. + const earlySymbol = earlySymbols && earlySymbols.get(memberName); + if (lateSymbol.flags & getExcludedSymbolFlags(symbolFlags) || earlySymbol) { + // If we have an existing early-bound member, combine its declarations so that we can + // report an error at each declaration. + const declarations = earlySymbol ? concatenate(earlySymbol.declarations, lateSymbol.declarations) : lateSymbol.declarations; + const name = !(type.flags & TypeFlags.UniqueESSymbol) && unescapeLeadingUnderscores(memberName) || declarationNameToString(declName); + forEach(declarations, declaration => error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Property_0_was_also_declared_here, name)); + error(declName || decl, Diagnostics.Duplicate_property_0, name); + lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late); + } + lateSymbol.nameType = type; + addDeclarationToLateBoundSymbol(lateSymbol, decl, symbolFlags); + if (lateSymbol.parent) { + Debug.assert(lateSymbol.parent === parent, "Existing symbol parent should match new one"); + } + else { + lateSymbol.parent = parent; } + return links.resolvedSymbol = lateSymbol; } - return links.resolvedSymbol; } + return links.resolvedSymbol; + } - function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): UnderscoreEscapedMap { - const links = getSymbolLinks(symbol); - if (!links[resolutionKind]) { - const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports; - const earlySymbols = !isStatic ? symbol.members : - symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol) : - symbol.exports; - - // In the event we recursively resolve the members/exports of the symbol, we - // set the initial value of resolvedMembers/resolvedExports to the early-bound - // members/exports of the symbol. - links[resolutionKind] = earlySymbols || emptySymbols; - - // fill in any as-yet-unresolved late-bound members. - const lateSymbols = createSymbolTable() as UnderscoreEscapedMap; - for (const decl of symbol.declarations || emptyArray) { - const members = getMembersOfDeclaration(decl); - if (members) { - for (const member of members) { - if (isStatic === hasStaticModifier(member)) { - if (hasLateBindableName(member)) { - lateBindMember(symbol, earlySymbols, lateSymbols, member); - } + function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): UnderscoreEscapedMap { + const links = getSymbolLinks(symbol); + if (!links[resolutionKind]) { + const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports; + const earlySymbols = !isStatic ? symbol.members : + symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol) : + symbol.exports; + + // In the event we recursively resolve the members/exports of the symbol, we + // set the initial value of resolvedMembers/resolvedExports to the early-bound + // members/exports of the symbol. + links[resolutionKind] = earlySymbols || emptySymbols; + + // fill in any as-yet-unresolved late-bound members. + const lateSymbols = createSymbolTable() as UnderscoreEscapedMap; + for (const decl of symbol.declarations || emptyArray) { + const members = getMembersOfDeclaration(decl); + if (members) { + for (const member of members) { + if (isStatic === hasStaticModifier(member)) { + if (hasLateBindableName(member)) { + lateBindMember(symbol, earlySymbols, lateSymbols, member); } } } } - const assignments = symbol.assignmentDeclarationMembers; - if (assignments) { - const decls = arrayFrom(assignments.values()); - for (const member of decls) { - const assignmentKind = getAssignmentDeclarationKind(member as BinaryExpression | CallExpression); - const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty - || isBinaryExpression(member) && isPossiblyAliasedThisProperty(member, assignmentKind) - || assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty - || assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name - if (isStatic === !isInstanceMember) { - if (hasLateBindableName(member)) { - lateBindMember(symbol, earlySymbols, lateSymbols, member); - } + } + const assignments = symbol.assignmentDeclarationMembers; + if (assignments) { + const decls = arrayFrom(assignments.values()); + for (const member of decls) { + const assignmentKind = getAssignmentDeclarationKind(member as BinaryExpression | CallExpression); + const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty + || isBinaryExpression(member) && isPossiblyAliasedThisProperty(member, assignmentKind) + || assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty + || assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name + if (isStatic === !isInstanceMember) { + if (hasLateBindableName(member)) { + lateBindMember(symbol, earlySymbols, lateSymbols, member); } } } - - links[resolutionKind] = combineSymbolTables(earlySymbols, lateSymbols) || emptySymbols; } - return links[resolutionKind]!; + links[resolutionKind] = combineSymbolTables(earlySymbols, lateSymbols) || emptySymbols; } - /** - * Gets a SymbolTable containing both the early- and late-bound members of a symbol. - * - * For a description of late-binding, see `lateBindMember`. - */ - function getMembersOfSymbol(symbol: Symbol) { - return symbol.flags & SymbolFlags.LateBindingContainer - ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedMembers) - : symbol.members || emptySymbols; - } + return links[resolutionKind]!; + } - /** - * If a symbol is the dynamic name of the member of an object type, get the late-bound - * symbol of the member. - * - * For a description of late-binding, see `lateBindMember`. - */ - function getLateBoundSymbol(symbol: Symbol): Symbol { - if (symbol.flags & SymbolFlags.ClassMember && symbol.escapedName === InternalSymbolName.Computed) { - const links = getSymbolLinks(symbol); - if (!links.lateSymbol && some(symbol.declarations, hasLateBindableName)) { - // force late binding of members/exports. This will set the late-bound symbol - const parent = getMergedSymbol(symbol.parent)!; - if (some(symbol.declarations, hasStaticModifier)) { - getExportsOfSymbol(parent); - } - else { - getMembersOfSymbol(parent); - } + /** + * Gets a SymbolTable containing both the early- and late-bound members of a symbol. + * + * For a description of late-binding, see `lateBindMember`. + */ + function getMembersOfSymbol(symbol: Symbol) { + return symbol.flags & SymbolFlags.LateBindingContainer + ? getResolvedMembersOrExportsOfSymbol(symbol, MembersOrExportsResolutionKind.resolvedMembers) + : symbol.members || emptySymbols; + } + + /** + * If a symbol is the dynamic name of the member of an object type, get the late-bound + * symbol of the member. + * + * For a description of late-binding, see `lateBindMember`. + */ + function getLateBoundSymbol(symbol: Symbol): Symbol { + if (symbol.flags & SymbolFlags.ClassMember && symbol.escapedName === InternalSymbolName.Computed) { + const links = getSymbolLinks(symbol); + if (!links.lateSymbol && some(symbol.declarations, hasLateBindableName)) { + // force late binding of members/exports. This will set the late-bound symbol + const parent = getMergedSymbol(symbol.parent)!; + if (some(symbol.declarations, hasStaticModifier)) { + getExportsOfSymbol(parent); + } + else { + getMembersOfSymbol(parent); } - return links.lateSymbol || (links.lateSymbol = symbol); } - return symbol; + return links.lateSymbol || (links.lateSymbol = symbol); } + return symbol; + } - function getTypeWithThisArgument(type: Type, thisArgument?: Type, needApparentType?: boolean): Type { - if (getObjectFlags(type) & ObjectFlags.Reference) { - const target = (type as TypeReference).target; - const typeArguments = getTypeArguments(type as TypeReference); - if (length(target.typeParameters) === length(typeArguments)) { - const ref = createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])); - return needApparentType ? getApparentType(ref) : ref; - } - } - else if (type.flags & TypeFlags.Intersection) { - const types = sameMap((type as IntersectionType).types, t => getTypeWithThisArgument(t, thisArgument, needApparentType)); - return types !== (type as IntersectionType).types ? getIntersectionType(types) : type; + function getTypeWithThisArgument(type: Type, thisArgument?: Type, needApparentType?: boolean): Type { + if (getObjectFlags(type) & ObjectFlags.Reference) { + const target = (type as TypeReference).target; + const typeArguments = getTypeArguments(type as TypeReference); + if (length(target.typeParameters) === length(typeArguments)) { + const ref = createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])); + return needApparentType ? getApparentType(ref) : ref; } - return needApparentType ? getApparentType(type) : type; } + else if (type.flags & TypeFlags.Intersection) { + const types = sameMap((type as IntersectionType).types, t => getTypeWithThisArgument(t, thisArgument, needApparentType)); + return types !== (type as IntersectionType).types ? getIntersectionType(types) : type; + } + return needApparentType ? getApparentType(type) : type; + } - function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: readonly TypeParameter[], typeArguments: readonly Type[]) { - let mapper: TypeMapper | undefined; - let members: SymbolTable; - let callSignatures: readonly Signature[]; - let constructSignatures: readonly Signature[]; - let indexInfos: readonly IndexInfo[]; - if (rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) { - members = source.symbol ? getMembersOfSymbol(source.symbol) : createSymbolTable(source.declaredProperties); - callSignatures = source.declaredCallSignatures; - constructSignatures = source.declaredConstructSignatures; - indexInfos = source.declaredIndexInfos; - } - else { - mapper = createTypeMapper(typeParameters, typeArguments); - members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1); - callSignatures = instantiateSignatures(source.declaredCallSignatures, mapper); - constructSignatures = instantiateSignatures(source.declaredConstructSignatures, mapper); - indexInfos = instantiateIndexInfos(source.declaredIndexInfos, mapper); - } - const baseTypes = getBaseTypes(source); - if (baseTypes.length) { - if (source.symbol && members === getMembersOfSymbol(source.symbol)) { - members = createSymbolTable(source.declaredProperties); - } - setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); - const thisArgument = lastOrUndefined(typeArguments); - for (const baseType of baseTypes) { - const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) : baseType; - addInheritedMembers(members, getPropertiesOfType(instantiatedBaseType)); - callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call)); - constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct)); - const inheritedIndexInfos = instantiatedBaseType !== anyType ? getIndexInfosOfType(instantiatedBaseType) : [createIndexInfo(stringType, anyType, /*isReadonly*/ false)]; - indexInfos = concatenate(indexInfos, filter(inheritedIndexInfos, info => !findIndexInfo(indexInfos, info.keyType))); - } + function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: readonly TypeParameter[], typeArguments: readonly Type[]) { + let mapper: TypeMapper | undefined; + let members: SymbolTable; + let callSignatures: readonly Signature[]; + let constructSignatures: readonly Signature[]; + let indexInfos: readonly IndexInfo[]; + if (rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) { + members = source.symbol ? getMembersOfSymbol(source.symbol) : createSymbolTable(source.declaredProperties); + callSignatures = source.declaredCallSignatures; + constructSignatures = source.declaredConstructSignatures; + indexInfos = source.declaredIndexInfos; + } + else { + mapper = createTypeMapper(typeParameters, typeArguments); + members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1); + callSignatures = instantiateSignatures(source.declaredCallSignatures, mapper); + constructSignatures = instantiateSignatures(source.declaredConstructSignatures, mapper); + indexInfos = instantiateIndexInfos(source.declaredIndexInfos, mapper); + } + const baseTypes = getBaseTypes(source); + if (baseTypes.length) { + if (source.symbol && members === getMembersOfSymbol(source.symbol)) { + members = createSymbolTable(source.declaredProperties); } setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); + const thisArgument = lastOrUndefined(typeArguments); + for (const baseType of baseTypes) { + const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) : baseType; + addInheritedMembers(members, getPropertiesOfType(instantiatedBaseType)); + callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call)); + constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct)); + const inheritedIndexInfos = instantiatedBaseType !== anyType ? getIndexInfosOfType(instantiatedBaseType) : [createIndexInfo(stringType, anyType, /*isReadonly*/ false)]; + indexInfos = concatenate(indexInfos, filter(inheritedIndexInfos, info => !findIndexInfo(indexInfos, info.keyType))); + } } + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); + } - function resolveClassOrInterfaceMembers(type: InterfaceType): void { - resolveObjectTypeMembers(type, resolveDeclaredMembers(type), emptyArray, emptyArray); - } + function resolveClassOrInterfaceMembers(type: InterfaceType): void { + resolveObjectTypeMembers(type, resolveDeclaredMembers(type), emptyArray, emptyArray); + } - function resolveTypeReferenceMembers(type: TypeReference): void { - const source = resolveDeclaredMembers(type.target); - const typeParameters = concatenate(source.typeParameters!, [source.thisType!]); - const typeArguments = getTypeArguments(type); - const paddedTypeArguments = typeArguments.length === typeParameters.length ? typeArguments : concatenate(typeArguments, [type]); - resolveObjectTypeMembers(type, source, typeParameters, paddedTypeArguments); - } - - function createSignature( - declaration: SignatureDeclaration | JSDocSignature | undefined, - typeParameters: readonly TypeParameter[] | undefined, - thisParameter: Symbol | undefined, - parameters: readonly Symbol[], - resolvedReturnType: Type | undefined, - resolvedTypePredicate: TypePredicate | undefined, - minArgumentCount: number, - flags: SignatureFlags - ): Signature { - const sig = new Signature(checker, flags); - sig.declaration = declaration; - sig.typeParameters = typeParameters; - sig.parameters = parameters; - sig.thisParameter = thisParameter; - sig.resolvedReturnType = resolvedReturnType; - sig.resolvedTypePredicate = resolvedTypePredicate; - sig.minArgumentCount = minArgumentCount; - sig.resolvedMinArgumentCount = undefined; - sig.target = undefined; - sig.mapper = undefined; - sig.compositeSignatures = undefined; - sig.compositeKind = undefined; - return sig; - } - - function cloneSignature(sig: Signature): Signature { - const result = createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags); - result.target = sig.target; - result.mapper = sig.mapper; - result.compositeSignatures = sig.compositeSignatures; - result.compositeKind = sig.compositeKind; - return result; - } + function resolveTypeReferenceMembers(type: TypeReference): void { + const source = resolveDeclaredMembers(type.target); + const typeParameters = concatenate(source.typeParameters!, [source.thisType!]); + const typeArguments = getTypeArguments(type); + const paddedTypeArguments = typeArguments.length === typeParameters.length ? typeArguments : concatenate(typeArguments, [type]); + resolveObjectTypeMembers(type, source, typeParameters, paddedTypeArguments); + } - function createUnionSignature(signature: Signature, unionSignatures: Signature[]) { - const result = cloneSignature(signature); - result.compositeSignatures = unionSignatures; - result.compositeKind = TypeFlags.Union; - result.target = undefined; - result.mapper = undefined; - return result; - } + function createSignature( + declaration: SignatureDeclaration | JSDocSignature | undefined, + typeParameters: readonly TypeParameter[] | undefined, + thisParameter: Symbol | undefined, + parameters: readonly Symbol[], + resolvedReturnType: Type | undefined, + resolvedTypePredicate: TypePredicate | undefined, + minArgumentCount: number, + flags: SignatureFlags + ): Signature { + const sig = new Signature(checker, flags); + sig.declaration = declaration; + sig.typeParameters = typeParameters; + sig.parameters = parameters; + sig.thisParameter = thisParameter; + sig.resolvedReturnType = resolvedReturnType; + sig.resolvedTypePredicate = resolvedTypePredicate; + sig.minArgumentCount = minArgumentCount; + sig.resolvedMinArgumentCount = undefined; + sig.target = undefined; + sig.mapper = undefined; + sig.compositeSignatures = undefined; + sig.compositeKind = undefined; + return sig; + } - function getOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags): Signature { - if ((signature.flags & SignatureFlags.CallChainFlags) === callChainFlags) { - return signature; - } - if (!signature.optionalCallSignatureCache) { - signature.optionalCallSignatureCache = {}; - } - const key = callChainFlags === SignatureFlags.IsInnerCallChain ? "inner" : "outer"; - return signature.optionalCallSignatureCache[key] - || (signature.optionalCallSignatureCache[key] = createOptionalCallSignature(signature, callChainFlags)); - } + function cloneSignature(sig: Signature): Signature { + const result = createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags); + result.target = sig.target; + result.mapper = sig.mapper; + result.compositeSignatures = sig.compositeSignatures; + result.compositeKind = sig.compositeKind; + return result; + } - function createOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags) { - Debug.assert(callChainFlags === SignatureFlags.IsInnerCallChain || callChainFlags === SignatureFlags.IsOuterCallChain, - "An optional call signature can either be for an inner call chain or an outer call chain, but not both."); - const result = cloneSignature(signature); - result.flags |= callChainFlags; - return result; - } + function createUnionSignature(signature: Signature, unionSignatures: Signature[]) { + const result = cloneSignature(signature); + result.compositeSignatures = unionSignatures; + result.compositeKind = TypeFlags.Union; + result.target = undefined; + result.mapper = undefined; + return result; + } - function getExpandedParameters(sig: Signature, skipUnionExpanding?: boolean): readonly (readonly Symbol[])[] { - if (signatureHasRestParameter(sig)) { - const restIndex = sig.parameters.length - 1; - const restType = getTypeOfSymbol(sig.parameters[restIndex]); - if (isTupleType(restType)) { - return [expandSignatureParametersWithTupleMembers(restType, restIndex)]; - } - else if (!skipUnionExpanding && restType.flags & TypeFlags.Union && every((restType as UnionType).types, isTupleType)) { - return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex)); - } - } - return [sig.parameters]; - - function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number) { - const elementTypes = getTypeArguments(restType); - const associatedNames = restType.target.labeledElementDeclarations; - const restParams = map(elementTypes, (t, i) => { - // Lookup the label from the individual tuple passed in before falling back to the signature `rest` parameter name - const tupleLabelName = !!associatedNames && getTupleElementLabel(associatedNames[i]); - const name = tupleLabelName || getParameterNameAtPosition(sig, restIndex + i, restType); - const flags = restType.target.elementFlags[i]; - const checkFlags = flags & ElementFlags.Variable ? CheckFlags.RestParameter : - flags & ElementFlags.Optional ? CheckFlags.OptionalParameter : 0; - const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name, checkFlags); - symbol.type = flags & ElementFlags.Rest ? createArrayType(t) : t; - return symbol; - }); - return concatenate(sig.parameters.slice(0, restIndex), restParams); - } + function getOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags): Signature { + if ((signature.flags & SignatureFlags.CallChainFlags) === callChainFlags) { + return signature; } + if (!signature.optionalCallSignatureCache) { + signature.optionalCallSignatureCache = {}; + } + const key = callChainFlags === SignatureFlags.IsInnerCallChain ? "inner" : "outer"; + return signature.optionalCallSignatureCache[key] + || (signature.optionalCallSignatureCache[key] = createOptionalCallSignature(signature, callChainFlags)); + } - function getDefaultConstructSignatures(classType: InterfaceType): Signature[] { - const baseConstructorType = getBaseConstructorTypeOfClass(classType); - const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); - const declaration = getClassLikeDeclarationOfSymbol(classType.symbol); - const isAbstract = !!declaration && hasSyntacticModifier(declaration, ModifierFlags.Abstract); - if (baseSignatures.length === 0) { - return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, isAbstract ? SignatureFlags.Abstract : SignatureFlags.None)]; - } - const baseTypeNode = getBaseTypeNodeOfClass(classType)!; - const isJavaScript = isInJSFile(baseTypeNode); - const typeArguments = typeArgumentsFromTypeReferenceNode(baseTypeNode); - const typeArgCount = length(typeArguments); - const result: Signature[] = []; - for (const baseSig of baseSignatures) { - const minTypeArgumentCount = getMinTypeArgumentCount(baseSig.typeParameters); - const typeParamCount = length(baseSig.typeParameters); - if (isJavaScript || typeArgCount >= minTypeArgumentCount && typeArgCount <= typeParamCount) { - const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount, isJavaScript)) : cloneSignature(baseSig); - sig.typeParameters = classType.localTypeParameters; - sig.resolvedReturnType = classType; - sig.flags = isAbstract ? sig.flags | SignatureFlags.Abstract : sig.flags & ~SignatureFlags.Abstract; - result.push(sig); - } - } - return result; + function createOptionalCallSignature(signature: Signature, callChainFlags: SignatureFlags) { + Debug.assert(callChainFlags === SignatureFlags.IsInnerCallChain || callChainFlags === SignatureFlags.IsOuterCallChain, + "An optional call signature can either be for an inner call chain or an outer call chain, but not both."); + const result = cloneSignature(signature); + result.flags |= callChainFlags; + return result; + } + + function getExpandedParameters(sig: Signature, skipUnionExpanding?: boolean): readonly (readonly Symbol[])[] { + if (signatureHasRestParameter(sig)) { + const restIndex = sig.parameters.length - 1; + const restType = getTypeOfSymbol(sig.parameters[restIndex]); + if (isTupleType(restType)) { + return [expandSignatureParametersWithTupleMembers(restType, restIndex)]; + } + else if (!skipUnionExpanding && restType.flags & TypeFlags.Union && every((restType as UnionType).types, isTupleType)) { + return map((restType as UnionType).types, t => expandSignatureParametersWithTupleMembers(t as TupleTypeReference, restIndex)); + } + } + return [sig.parameters]; + + function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number) { + const elementTypes = getTypeArguments(restType); + const associatedNames = restType.target.labeledElementDeclarations; + const restParams = map(elementTypes, (t, i) => { + // Lookup the label from the individual tuple passed in before falling back to the signature `rest` parameter name + const tupleLabelName = !!associatedNames && getTupleElementLabel(associatedNames[i]); + const name = tupleLabelName || getParameterNameAtPosition(sig, restIndex + i, restType); + const flags = restType.target.elementFlags[i]; + const checkFlags = flags & ElementFlags.Variable ? CheckFlags.RestParameter : + flags & ElementFlags.Optional ? CheckFlags.OptionalParameter : 0; + const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name, checkFlags); + symbol.type = flags & ElementFlags.Rest ? createArrayType(t) : t; + return symbol; + }); + return concatenate(sig.parameters.slice(0, restIndex), restParams); } + } - function findMatchingSignature(signatureList: readonly Signature[], signature: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): Signature | undefined { - for (const s of signatureList) { - if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, partialMatch ? compareTypesSubtypeOf : compareTypesIdentical)) { - return s; - } + function getDefaultConstructSignatures(classType: InterfaceType): Signature[] { + const baseConstructorType = getBaseConstructorTypeOfClass(classType); + const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); + const declaration = getClassLikeDeclarationOfSymbol(classType.symbol); + const isAbstract = !!declaration && hasSyntacticModifier(declaration, ModifierFlags.Abstract); + if (baseSignatures.length === 0) { + return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, isAbstract ? SignatureFlags.Abstract : SignatureFlags.None)]; + } + const baseTypeNode = getBaseTypeNodeOfClass(classType)!; + const isJavaScript = isInJSFile(baseTypeNode); + const typeArguments = typeArgumentsFromTypeReferenceNode(baseTypeNode); + const typeArgCount = length(typeArguments); + const result: Signature[] = []; + for (const baseSig of baseSignatures) { + const minTypeArgumentCount = getMinTypeArgumentCount(baseSig.typeParameters); + const typeParamCount = length(baseSig.typeParameters); + if (isJavaScript || typeArgCount >= minTypeArgumentCount && typeArgCount <= typeParamCount) { + const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount, isJavaScript)) : cloneSignature(baseSig); + sig.typeParameters = classType.localTypeParameters; + sig.resolvedReturnType = classType; + sig.flags = isAbstract ? sig.flags | SignatureFlags.Abstract : sig.flags & ~SignatureFlags.Abstract; + result.push(sig); + } + } + return result; + } + + function findMatchingSignature(signatureList: readonly Signature[], signature: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): Signature | undefined { + for (const s of signatureList) { + if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, partialMatch ? compareTypesSubtypeOf : compareTypesIdentical)) { + return s; } } + } - function findMatchingSignatures(signatureLists: readonly (readonly Signature[])[], signature: Signature, listIndex: number): Signature[] | undefined { - if (signature.typeParameters) { - // We require an exact match for generic signatures, so we only return signatures from the first - // signature list and only if they have exact matches in the other signature lists. - if (listIndex > 0) { - return undefined; - } - for (let i = 1; i < signatureLists.length; i++) { - if (!findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false)) { - return undefined; - } - } - return [signature]; + function findMatchingSignatures(signatureLists: readonly (readonly Signature[])[], signature: Signature, listIndex: number): Signature[] | undefined { + if (signature.typeParameters) { + // We require an exact match for generic signatures, so we only return signatures from the first + // signature list and only if they have exact matches in the other signature lists. + if (listIndex > 0) { + return undefined; } - let result: Signature[] | undefined; - for (let i = 0; i < signatureLists.length; i++) { - // Allow matching non-generic signatures to have excess parameters and different return types. - // Prefer matching this types if possible. - const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true); - if (!match) { + for (let i = 1; i < signatureLists.length; i++) { + if (!findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false)) { return undefined; } - result = appendIfUnique(result, match); } - return result; + return [signature]; } - - // The signatures of a union type are those signatures that are present in each of the constituent types. - // Generic signatures must match exactly, but non-generic signatures are allowed to have extra optional - // parameters and may differ in return types. When signatures differ in return types, the resulting return - // type is the union of the constituent return types. - function getUnionSignatures(signatureLists: readonly (readonly Signature[])[]): Signature[] { - let result: Signature[] | undefined; - let indexWithLengthOverOne: number | undefined; - for (let i = 0; i < signatureLists.length; i++) { - if (signatureLists[i].length === 0) return emptyArray; - if (signatureLists[i].length > 1) { - indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets - } - for (const signature of signatureLists[i]) { - // Only process signatures with parameter lists that aren't already in the result list - if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true)) { - const unionSignatures = findMatchingSignatures(signatureLists, signature, i); - if (unionSignatures) { - let s = signature; - // Union the result types when more than one signature matches - if (unionSignatures.length > 1) { - let thisParameter = signature.thisParameter; - const firstThisParameterOfUnionSignatures = forEach(unionSignatures, sig => sig.thisParameter); - if (firstThisParameterOfUnionSignatures) { - const thisType = getIntersectionType(mapDefined(unionSignatures, sig => sig.thisParameter && getTypeOfSymbol(sig.thisParameter))); - thisParameter = createSymbolWithType(firstThisParameterOfUnionSignatures, thisType); - } - s = createUnionSignature(signature, unionSignatures); - s.thisParameter = thisParameter; - } - (result || (result = [])).push(s); - } - } - } + let result: Signature[] | undefined; + for (let i = 0; i < signatureLists.length; i++) { + // Allow matching non-generic signatures to have excess parameters and different return types. + // Prefer matching this types if possible. + const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true); + if (!match) { + return undefined; } - if (!length(result) && indexWithLengthOverOne !== -1) { - // No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single - // signature that handles all over them. We only do this when there are overloads in only one constituent. - // (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of - // signatures from the type, whose ordering would be non-obvious) - const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0]; - let results: Signature[] | undefined = masterList.slice(); - for (const signatures of signatureLists) { - if (signatures !== masterList) { - const signature = signatures[0]; - Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass"); - results = !!signature.typeParameters && some(results, s => !!s.typeParameters && !compareTypeParametersIdentical(signature.typeParameters, s.typeParameters)) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature)); - if (!results) { - break; - } + result = appendIfUnique(result, match); + } + return result; + } + + // The signatures of a union type are those signatures that are present in each of the constituent types. + // Generic signatures must match exactly, but non-generic signatures are allowed to have extra optional + // parameters and may differ in return types. When signatures differ in return types, the resulting return + // type is the union of the constituent return types. + function getUnionSignatures(signatureLists: readonly (readonly Signature[])[]): Signature[] { + let result: Signature[] | undefined; + let indexWithLengthOverOne: number | undefined; + for (let i = 0; i < signatureLists.length; i++) { + if (signatureLists[i].length === 0) return emptyArray; + if (signatureLists[i].length > 1) { + indexWithLengthOverOne = indexWithLengthOverOne === undefined ? i : -1; // -1 is a signal there are multiple overload sets + } + for (const signature of signatureLists[i]) { + // Only process signatures with parameter lists that aren't already in the result list + if (!result || !findMatchingSignature(result, signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true)) { + const unionSignatures = findMatchingSignatures(signatureLists, signature, i); + if (unionSignatures) { + let s = signature; + // Union the result types when more than one signature matches + if (unionSignatures.length > 1) { + let thisParameter = signature.thisParameter; + const firstThisParameterOfUnionSignatures = forEach(unionSignatures, sig => sig.thisParameter); + if (firstThisParameterOfUnionSignatures) { + const thisType = getIntersectionType(mapDefined(unionSignatures, sig => sig.thisParameter && getTypeOfSymbol(sig.thisParameter))); + thisParameter = createSymbolWithType(firstThisParameterOfUnionSignatures, thisType); + } + s = createUnionSignature(signature, unionSignatures); + s.thisParameter = thisParameter; + } + (result || (result = [])).push(s); + } + } + } + } + if (!length(result) && indexWithLengthOverOne !== -1) { + // No sufficiently similar signature existed to subsume all the other signatures in the union - time to see if we can make a single + // signature that handles all over them. We only do this when there are overloads in only one constituent. + // (Overloads are conditional in nature and having overloads in multiple constituents would necessitate making a power set of + // signatures from the type, whose ordering would be non-obvious) + const masterList = signatureLists[indexWithLengthOverOne !== undefined ? indexWithLengthOverOne : 0]; + let results: Signature[] | undefined = masterList.slice(); + for (const signatures of signatureLists) { + if (signatures !== masterList) { + const signature = signatures[0]; + Debug.assert(!!signature, "getUnionSignatures bails early on empty signature lists and should not have empty lists on second pass"); + results = !!signature.typeParameters && some(results, s => !!s.typeParameters && !compareTypeParametersIdentical(signature.typeParameters, s.typeParameters)) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature)); + if (!results) { + break; } } - result = results; } - return result || emptyArray; + result = results; } + return result || emptyArray; + } - function compareTypeParametersIdentical(sourceParams: readonly TypeParameter[] | undefined, targetParams: readonly TypeParameter[] | undefined): boolean { - if (length(sourceParams) !== length(targetParams)) { - return false; - } - if (!sourceParams || !targetParams) { - return true; - } - - const mapper = createTypeMapper(targetParams, sourceParams); - for (let i = 0; i < sourceParams.length; i++) { - const source = sourceParams[i]; - const target = targetParams[i]; - if (source === target) continue; - // We instantiate the target type parameter constraints into the source types so we can recognize `` as the same as `` - if (!isTypeIdenticalTo(getConstraintFromTypeParameter(source) || unknownType, instantiateType(getConstraintFromTypeParameter(target) || unknownType, mapper))) return false; - // We don't compare defaults - we just use the type parameter defaults from the first signature that seems to match. - // It might make sense to combine these defaults in the future, but doing so intelligently requires knowing - // if the parameter is used covariantly or contravariantly (so we intersect if it's used like a parameter or union if used like a return type) - // and, since it's just an inference _default_, just picking one arbitrarily works OK. - } - - return true; + function compareTypeParametersIdentical(sourceParams: readonly TypeParameter[] | undefined, targetParams: readonly TypeParameter[] | undefined): boolean { + if (length(sourceParams) !== length(targetParams)) { + return false; } - - function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined, mapper: TypeMapper | undefined): Symbol | undefined { - if (!left || !right) { - return left || right; - } - // A signature `this` type might be a read or a write position... It's very possible that it should be invariant - // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be - // permissive when calling, for now, we'll intersect the `this` types just like we do for param types in union signatures. - const thisType = getIntersectionType([getTypeOfSymbol(left), instantiateType(getTypeOfSymbol(right), mapper)]); - return createSymbolWithType(left, thisType); - } - - function combineUnionParameters(left: Signature, right: Signature, mapper: TypeMapper | undefined) { - const leftCount = getParameterCount(left); - const rightCount = getParameterCount(right); - const longest = leftCount >= rightCount ? left : right; - const shorter = longest === left ? right : left; - const longestCount = longest === left ? leftCount : rightCount; - const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); - const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); - const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); - for (let i = 0; i < longestCount; i++) { - let longestParamType = tryGetTypeAtPosition(longest, i)!; - if (longest === right) { - longestParamType = instantiateType(longestParamType, mapper); - } - let shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; - if (shorter === right) { - shorterParamType = instantiateType(shorterParamType, mapper); - } - const unionParamType = getIntersectionType([longestParamType, shorterParamType]); - const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); - const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); - const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i); - const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i); - - const paramName = leftName === rightName ? leftName : - !leftName ? rightName : - !rightName ? leftName : - undefined; - const paramSymbol = createSymbol( - SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), - paramName || `arg${i}` as __String - ); - paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; - params[i] = paramSymbol; - } - if (needsExtraRestElement) { - const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); - restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); - if (shorter === right) { - restParamSymbol.type = instantiateType(restParamSymbol.type, mapper); - } - params[longestCount] = restParamSymbol; - } - return params; - } - - function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature { - const typeParams = left.typeParameters || right.typeParameters; - let paramMapper: TypeMapper | undefined; - if (left.typeParameters && right.typeParameters) { - paramMapper = createTypeMapper(right.typeParameters, left.typeParameters); - // We just use the type parameter defaults from the first signature - } - const declaration = left.declaration; - const params = combineUnionParameters(left, right, paramMapper); - const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter, paramMapper); - const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); - const result = createSignature( - declaration, - typeParams, - thisParam, - params, - /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, - minArgCount, - (left.flags | right.flags) & SignatureFlags.PropagatingFlags - ); - result.compositeKind = TypeFlags.Union; - result.compositeSignatures = concatenate(left.compositeKind !== TypeFlags.Intersection && left.compositeSignatures || [left], [right]); - if (paramMapper) { - result.mapper = left.compositeKind !== TypeFlags.Intersection && left.mapper && left.compositeSignatures ? combineTypeMappers(left.mapper, paramMapper) : paramMapper; - } - return result; + if (!sourceParams || !targetParams) { + return true; } - function getUnionIndexInfos(types: readonly Type[]): IndexInfo[] { - const sourceInfos = getIndexInfosOfType(types[0]); - if (sourceInfos) { - const result = []; - for (const info of sourceInfos) { - const indexType = info.keyType; - if (every(types, t => !!getIndexInfoOfType(t, indexType))) { - result.push(createIndexInfo(indexType, getUnionType(map(types, t => getIndexTypeOfType(t, indexType)!)), - some(types, t => getIndexInfoOfType(t, indexType)!.isReadonly))); - } - } - return result; - } - return emptyArray; + const mapper = createTypeMapper(targetParams, sourceParams); + for (let i = 0; i < sourceParams.length; i++) { + const source = sourceParams[i]; + const target = targetParams[i]; + if (source === target) continue; + // We instantiate the target type parameter constraints into the source types so we can recognize `` as the same as `` + if (!isTypeIdenticalTo(getConstraintFromTypeParameter(source) || unknownType, instantiateType(getConstraintFromTypeParameter(target) || unknownType, mapper))) return false; + // We don't compare defaults - we just use the type parameter defaults from the first signature that seems to match. + // It might make sense to combine these defaults in the future, but doing so intelligently requires knowing + // if the parameter is used covariantly or contravariantly (so we intersect if it's used like a parameter or union if used like a return type) + // and, since it's just an inference _default_, just picking one arbitrarily works OK. } - function resolveUnionTypeMembers(type: UnionType) { - // The members and properties collections are empty for union types. To get all properties of a union - // type use getPropertiesOfType (only the language service uses this). - const callSignatures = getUnionSignatures(map(type.types, t => t === globalFunctionType ? [unknownSignature] : getSignaturesOfType(t, SignatureKind.Call))); - const constructSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Construct))); - const indexInfos = getUnionIndexInfos(type.types); - setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, indexInfos); - } + return true; + } - function intersectTypes(type1: Type, type2: Type): Type; - function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined; - function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined { - return !type1 ? type2 : !type2 ? type1 : getIntersectionType([type1, type2]); + function combineUnionThisParam(left: Symbol | undefined, right: Symbol | undefined, mapper: TypeMapper | undefined): Symbol | undefined { + if (!left || !right) { + return left || right; } + // A signature `this` type might be a read or a write position... It's very possible that it should be invariant + // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be + // permissive when calling, for now, we'll intersect the `this` types just like we do for param types in union signatures. + const thisType = getIntersectionType([getTypeOfSymbol(left), instantiateType(getTypeOfSymbol(right), mapper)]); + return createSymbolWithType(left, thisType); + } - function findMixins(types: readonly Type[]): readonly boolean[] { - const constructorTypeCount = countWhere(types, (t) => getSignaturesOfType(t, SignatureKind.Construct).length > 0); - const mixinFlags = map(types, isMixinConstructorType); - if (constructorTypeCount > 0 && constructorTypeCount === countWhere(mixinFlags, (b) => b)) { - const firstMixinIndex = mixinFlags.indexOf(/*searchElement*/ true); - mixinFlags[firstMixinIndex] = false; - } - return mixinFlags; + function combineUnionParameters(left: Signature, right: Signature, mapper: TypeMapper | undefined) { + const leftCount = getParameterCount(left); + const rightCount = getParameterCount(right); + const longest = leftCount >= rightCount ? left : right; + const shorter = longest === left ? right : left; + const longestCount = longest === left ? leftCount : rightCount; + const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); + const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); + const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); + for (let i = 0; i < longestCount; i++) { + let longestParamType = tryGetTypeAtPosition(longest, i)!; + if (longest === right) { + longestParamType = instantiateType(longestParamType, mapper); + } + let shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; + if (shorter === right) { + shorterParamType = instantiateType(shorterParamType, mapper); + } + const unionParamType = getIntersectionType([longestParamType, shorterParamType]); + const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); + const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); + const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i); + const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i); + + const paramName = leftName === rightName ? leftName : + !leftName ? rightName : + !rightName ? leftName : + undefined; + const paramSymbol = createSymbol( + SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), + paramName || `arg${i}` as __String + ); + paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; } - - function includeMixinType(type: Type, types: readonly Type[], mixinFlags: readonly boolean[], index: number): Type { - const mixedTypes: Type[] = []; - for (let i = 0; i < types.length; i++) { - if (i === index) { - mixedTypes.push(type); - } - else if (mixinFlags[i]) { - mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0])); - } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); + restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + if (shorter === right) { + restParamSymbol.type = instantiateType(restParamSymbol.type, mapper); } - return getIntersectionType(mixedTypes); + params[longestCount] = restParamSymbol; } + return params; + } - 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: Signature[] | undefined; - let constructSignatures: Signature[] | undefined; - let indexInfos: IndexInfo[] | undefined; - const types = type.types; - const mixinFlags = findMixins(types); - const mixinCount = countWhere(mixinFlags, (b) => b); - for (let i = 0; i < types.length; i++) { - const t = type.types[i]; - // When an intersection type contains mixin constructor types, the construct signatures from - // those types are discarded and their return types are mixed into the return types of all - // other construct signatures in the intersection type. For example, the intersection type - // '{ new(...args: any[]) => A } & { new(s: string) => B }' has a single construct signature - // 'new(s: string) => A & B'. - if (!mixinFlags[i]) { - let signatures = getSignaturesOfType(t, SignatureKind.Construct); - if (signatures.length && mixinCount > 0) { - signatures = map(signatures, s => { - const clone = cloneSignature(s); - clone.resolvedReturnType = includeMixinType(getReturnTypeOfSignature(s), types, mixinFlags, i); - return clone; - }); - } - constructSignatures = appendSignatures(constructSignatures, signatures); - } - callSignatures = appendSignatures(callSignatures, getSignaturesOfType(t, SignatureKind.Call)); - indexInfos = reduceLeft(getIndexInfosOfType(t), (infos, newInfo) => appendIndexInfo(infos, newInfo, /*union*/ false), indexInfos); - } - setStructuredTypeMembers(type, emptySymbols, callSignatures || emptyArray, constructSignatures || emptyArray, indexInfos || emptyArray); - } + function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature { + const typeParams = left.typeParameters || right.typeParameters; + let paramMapper: TypeMapper | undefined; + if (left.typeParameters && right.typeParameters) { + paramMapper = createTypeMapper(right.typeParameters, left.typeParameters); + // We just use the type parameter defaults from the first signature + } + const declaration = left.declaration; + const params = combineUnionParameters(left, right, paramMapper); + const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter, paramMapper); + const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); + const result = createSignature( + declaration, + typeParams, + thisParam, + params, + /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, + minArgCount, + (left.flags | right.flags) & SignatureFlags.PropagatingFlags + ); + result.compositeKind = TypeFlags.Union; + result.compositeSignatures = concatenate(left.compositeKind !== TypeFlags.Intersection && left.compositeSignatures || [left], [right]); + if (paramMapper) { + result.mapper = left.compositeKind !== TypeFlags.Intersection && left.mapper && left.compositeSignatures ? combineTypeMappers(left.mapper, paramMapper) : paramMapper; + } + return result; + } - 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); + function getUnionIndexInfos(types: readonly Type[]): IndexInfo[] { + const sourceInfos = getIndexInfosOfType(types[0]); + if (sourceInfos) { + const result = []; + for (const info of sourceInfos) { + const indexType = info.keyType; + if (every(types, t => !!getIndexInfoOfType(t, indexType))) { + result.push(createIndexInfo(indexType, getUnionType(map(types, t => getIndexTypeOfType(t, indexType)!)), + some(types, t => getIndexInfoOfType(t, indexType)!.isReadonly))); } } - return signatures; + return result; } + return emptyArray; + } - function appendIndexInfo(indexInfos: IndexInfo[] | undefined, newInfo: IndexInfo, union: boolean) { - if (indexInfos) { - for (let i = 0; i < indexInfos.length; i++) { - const info = indexInfos[i]; - if (info.keyType === newInfo.keyType) { - indexInfos[i] = createIndexInfo(info.keyType, - union ? getUnionType([info.type, newInfo.type]) : getIntersectionType([info.type, newInfo.type]), - union ? info.isReadonly || newInfo.isReadonly : info.isReadonly && newInfo.isReadonly); - return indexInfos; - } - } - } - return append(indexInfos, newInfo); - } + function resolveUnionTypeMembers(type: UnionType) { + // The members and properties collections are empty for union types. To get all properties of a union + // type use getPropertiesOfType (only the language service uses this). + const callSignatures = getUnionSignatures(map(type.types, t => t === globalFunctionType ? [unknownSignature] : getSignaturesOfType(t, SignatureKind.Call))); + const constructSignatures = getUnionSignatures(map(type.types, t => getSignaturesOfType(t, SignatureKind.Construct))); + const indexInfos = getUnionIndexInfos(type.types); + setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, indexInfos); + } - /** - * Converts an AnonymousType to a ResolvedType. - */ - function resolveAnonymousTypeMembers(type: AnonymousType) { - if (type.target) { - setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); - const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper!, /*mappingThisOnly*/ false); - const callSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Call), type.mapper!); - const constructSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Construct), type.mapper!); - const indexInfos = instantiateIndexInfos(getIndexInfosOfType(type.target), type.mapper!); - setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); - return; - } - const symbol = getMergedSymbol(type.symbol); - if (symbol.flags & SymbolFlags.TypeLiteral) { - setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); - const members = getMembersOfSymbol(symbol); - const callSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); - const constructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); - const indexInfos = getIndexInfosOfSymbol(symbol); - setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); - return; - } - // Combinations of function, class, enum and module - let members = emptySymbols; - let indexInfos: IndexInfo[] | undefined; - if (symbol.exports) { - members = getExportsOfSymbol(symbol); - if (symbol === globalThisSymbol) { - const varsOnly = new Map() as SymbolTable; - members.forEach(p => { - if (!(p.flags & SymbolFlags.BlockScoped) && !(p.flags & SymbolFlags.ValueModule && p.declarations?.length && every(p.declarations, isAmbientModule))) { - varsOnly.set(p.escapedName, p); - } - }); - members = varsOnly; - } - } - let baseConstructorIndexInfo: IndexInfo | undefined; - setStructuredTypeMembers(type, members, emptyArray, emptyArray, emptyArray); - if (symbol.flags & SymbolFlags.Class) { - const classType = getDeclaredTypeOfClassOrInterface(symbol); - const baseConstructorType = getBaseConstructorTypeOfClass(classType); - if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) { - members = createSymbolTable(getNamedOrIndexSignatureMembers(members)); - addInheritedMembers(members, getPropertiesOfType(baseConstructorType)); - } - else if (baseConstructorType === anyType) { - baseConstructorIndexInfo = createIndexInfo(stringType, anyType, /*isReadonly*/ false); - } - } + function intersectTypes(type1: Type, type2: Type): Type; + function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined; + function intersectTypes(type1: Type | undefined, type2: Type | undefined): Type | undefined { + return !type1 ? type2 : !type2 ? type1 : getIntersectionType([type1, type2]); + } - const indexSymbol = getIndexSymbolFromSymbolTable(members); - if (indexSymbol) { - indexInfos = getIndexInfosOfIndexSymbol(indexSymbol); - } - else { - if (baseConstructorIndexInfo) { - indexInfos = append(indexInfos, baseConstructorIndexInfo); - } - if (symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum || - some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike)))) { - indexInfos = append(indexInfos, enumNumberIndexInfo); - } + function findMixins(types: readonly Type[]): readonly boolean[] { + const constructorTypeCount = countWhere(types, (t) => getSignaturesOfType(t, SignatureKind.Construct).length > 0); + const mixinFlags = map(types, isMixinConstructorType); + if (constructorTypeCount > 0 && constructorTypeCount === countWhere(mixinFlags, (b) => b)) { + const firstMixinIndex = mixinFlags.indexOf(/*searchElement*/ true); + mixinFlags[firstMixinIndex] = false; + } + return mixinFlags; + } + + function includeMixinType(type: Type, types: readonly Type[], mixinFlags: readonly boolean[], index: number): Type { + const mixedTypes: Type[] = []; + for (let i = 0; i < types.length; i++) { + if (i === index) { + mixedTypes.push(type); } - setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos || emptyArray); - // 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 - // in the process of resolving (see issue #6072). The temporarily empty signature list - // will never be observed because a qualified name can't reference signatures. - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { - type.callSignatures = getSignaturesOfSymbol(symbol); + else if (mixinFlags[i]) { + mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0])); } - // And likewise for construct signatures for classes - if (symbol.flags & SymbolFlags.Class) { - const classType = getDeclaredTypeOfClassOrInterface(symbol); - let constructSignatures = symbol.members ? getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor)) : emptyArray; - if (symbol.flags & SymbolFlags.Function) { - constructSignatures = addRange(constructSignatures.slice(), mapDefined( - type.callSignatures, - sig => isJSConstructor(sig.declaration) ? - createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags) : - undefined)); - } - if (!constructSignatures.length) { - constructSignatures = getDefaultConstructSignatures(classType); + } + return getIntersectionType(mixedTypes); + } + + 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: Signature[] | undefined; + let constructSignatures: Signature[] | undefined; + let indexInfos: IndexInfo[] | undefined; + const types = type.types; + const mixinFlags = findMixins(types); + const mixinCount = countWhere(mixinFlags, (b) => b); + for (let i = 0; i < types.length; i++) { + const t = type.types[i]; + // When an intersection type contains mixin constructor types, the construct signatures from + // those types are discarded and their return types are mixed into the return types of all + // other construct signatures in the intersection type. For example, the intersection type + // '{ new(...args: any[]) => A } & { new(s: string) => B }' has a single construct signature + // 'new(s: string) => A & B'. + if (!mixinFlags[i]) { + let signatures = getSignaturesOfType(t, SignatureKind.Construct); + if (signatures.length && mixinCount > 0) { + signatures = map(signatures, s => { + const clone = cloneSignature(s); + clone.resolvedReturnType = includeMixinType(getReturnTypeOfSignature(s), types, mixinFlags, i); + return clone; + }); } - type.constructSignatures = constructSignatures; + constructSignatures = appendSignatures(constructSignatures, signatures); } + callSignatures = appendSignatures(callSignatures, getSignaturesOfType(t, SignatureKind.Call)); + indexInfos = reduceLeft(getIndexInfosOfType(t), (infos, newInfo) => appendIndexInfo(infos, newInfo, /*union*/ false), indexInfos); } + setStructuredTypeMembers(type, emptySymbols, callSignatures || emptyArray, constructSignatures || emptyArray, indexInfos || emptyArray); + } - type ReplaceableIndexedAccessType = IndexedAccessType & { objectType: TypeParameter, indexType: TypeParameter }; - function replaceIndexedAccess(instantiable: Type, type: ReplaceableIndexedAccessType, replacement: Type) { - // map type.indexType to 0 - // map type.objectType to `[TReplacement]` - // thus making the indexed access `[TReplacement][0]` or `TReplacement` - return instantiateType(instantiable, createTypeMapper([type.indexType, type.objectType], [getNumberLiteralType(0), createTupleType([replacement])])); + 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; + } - function resolveReverseMappedTypeMembers(type: ReverseMappedType) { - const indexInfo = getIndexInfoOfType(type.source, stringType); - const modifiers = getMappedTypeModifiers(type.mappedType); - const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true; - const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional; - const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly)] : emptyArray; - const members = createSymbolTable(); - for (const prop of getPropertiesOfType(type.source)) { - const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); - const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol; - inferredProp.declarations = prop.declarations; - inferredProp.nameType = getSymbolLinks(prop).nameType; - inferredProp.propertyType = getTypeOfSymbol(prop); - if (type.constraintType.type.flags & TypeFlags.IndexedAccess - && (type.constraintType.type as IndexedAccessType).objectType.flags & TypeFlags.TypeParameter - && (type.constraintType.type as IndexedAccessType).indexType.flags & TypeFlags.TypeParameter) { - // A reverse mapping of `{[K in keyof T[K_1]]: T[K_1]}` is the same as that of `{[K in keyof T]: T}`, since all we care about is - // inferring to the "type parameter" (or indexed access) shared by the constraint and template. So, to reduce the number of - // type identities produced, we simplify such indexed access occurences - const newTypeParam = (type.constraintType.type as IndexedAccessType).objectType; - const newMappedType = replaceIndexedAccess(type.mappedType, type.constraintType.type as ReplaceableIndexedAccessType, newTypeParam); - inferredProp.mappedType = newMappedType as MappedType; - inferredProp.constraintType = getIndexType(newTypeParam) as IndexType; - } - else { - inferredProp.mappedType = type.mappedType; - inferredProp.constraintType = type.constraintType; + function appendIndexInfo(indexInfos: IndexInfo[] | undefined, newInfo: IndexInfo, union: boolean) { + if (indexInfos) { + for (let i = 0; i < indexInfos.length; i++) { + const info = indexInfos[i]; + if (info.keyType === newInfo.keyType) { + indexInfos[i] = createIndexInfo(info.keyType, + union ? getUnionType([info.type, newInfo.type]) : getIntersectionType([info.type, newInfo.type]), + union ? info.isReadonly || newInfo.isReadonly : info.isReadonly && newInfo.isReadonly); + return indexInfos; } - members.set(prop.escapedName, inferredProp); } - setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos); } + return append(indexInfos, newInfo); + } - // Return the lower bound of the key type in a mapped type. Intuitively, the lower - // bound includes those keys that are known to always be present, for example because - // because of constraints on type parameters (e.g. 'keyof T' for a constrained T). - function getLowerBoundOfKeyType(type: Type): Type { - if (type.flags & TypeFlags.Index) { - const t = getApparentType((type as IndexType).type); - return isGenericTupleType(t) ? getKnownKeysOfTupleType(t) : getIndexType(t); - } - if (type.flags & TypeFlags.Conditional) { - if ((type as ConditionalType).root.isDistributive) { - const checkType = (type as ConditionalType).checkType; - const constraint = getLowerBoundOfKeyType(checkType); - if (constraint !== checkType) { - return getConditionalTypeInstantiation(type as ConditionalType, prependTypeMapping((type as ConditionalType).root.checkType, constraint, (type as ConditionalType).mapper)); + /** + * Converts an AnonymousType to a ResolvedType. + */ + function resolveAnonymousTypeMembers(type: AnonymousType) { + if (type.target) { + setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); + const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper!, /*mappingThisOnly*/ false); + const callSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Call), type.mapper!); + const constructSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Construct), type.mapper!); + const indexInfos = instantiateIndexInfos(getIndexInfosOfType(type.target), type.mapper!); + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); + return; + } + const symbol = getMergedSymbol(type.symbol); + if (symbol.flags & SymbolFlags.TypeLiteral) { + setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); + const members = getMembersOfSymbol(symbol); + const callSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); + const constructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); + const indexInfos = getIndexInfosOfSymbol(symbol); + setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); + return; + } + // Combinations of function, class, enum and module + let members = emptySymbols; + let indexInfos: IndexInfo[] | undefined; + if (symbol.exports) { + members = getExportsOfSymbol(symbol); + if (symbol === globalThisSymbol) { + const varsOnly = new Map() as SymbolTable; + members.forEach(p => { + if (!(p.flags & SymbolFlags.BlockScoped) && !(p.flags & SymbolFlags.ValueModule && p.declarations?.length && every(p.declarations, isAmbientModule))) { + varsOnly.set(p.escapedName, p); } - } - return type; + }); + members = varsOnly; } - if (type.flags & TypeFlags.Union) { - return mapType(type as UnionType, getLowerBoundOfKeyType); + } + let baseConstructorIndexInfo: IndexInfo | undefined; + setStructuredTypeMembers(type, members, emptyArray, emptyArray, emptyArray); + if (symbol.flags & SymbolFlags.Class) { + const classType = getDeclaredTypeOfClassOrInterface(symbol); + const baseConstructorType = getBaseConstructorTypeOfClass(classType); + if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) { + members = createSymbolTable(getNamedOrIndexSignatureMembers(members)); + addInheritedMembers(members, getPropertiesOfType(baseConstructorType)); } - if (type.flags & TypeFlags.Intersection) { - // Similarly to getTypeFromIntersectionTypeNode, we preserve the special string & {}, number & {}, - // and bigint & {} intersections that are used to prevent subtype reduction in union types. - const types = (type as IntersectionType).types; - if (types.length === 2 && !!(types[0].flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && types[1] === emptyTypeLiteralType) { - return type; - } - return getIntersectionType(sameMap((type as UnionType).types, getLowerBoundOfKeyType)); + else if (baseConstructorType === anyType) { + baseConstructorIndexInfo = createIndexInfo(stringType, anyType, /*isReadonly*/ false); } - return type; } - function getIsLateCheckFlag(s: Symbol): CheckFlags { - return getCheckFlags(s) & CheckFlags.Late; + const indexSymbol = getIndexSymbolFromSymbolTable(members); + if (indexSymbol) { + indexInfos = getIndexInfosOfIndexSymbol(indexSymbol); } - - function forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(type: Type, include: TypeFlags, stringsOnly: boolean, cb: (keyType: Type) => void) { - for (const prop of getPropertiesOfType(type)) { - cb(getLiteralTypeFromProperty(prop, include)); + else { + if (baseConstructorIndexInfo) { + indexInfos = append(indexInfos, baseConstructorIndexInfo); } - if (type.flags & TypeFlags.Any) { - cb(stringType); + if (symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum || + some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike)))) { + indexInfos = append(indexInfos, enumNumberIndexInfo); } - else { - for (const info of getIndexInfosOfType(type)) { - if (!stringsOnly || info.keyType.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) { - cb(info.keyType); - } - } + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos || emptyArray); + // 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 + // in the process of resolving (see issue #6072). The temporarily empty signature list + // will never be observed because a qualified name can't reference signatures. + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { + type.callSignatures = getSignaturesOfSymbol(symbol); + } + // And likewise for construct signatures for classes + if (symbol.flags & SymbolFlags.Class) { + const classType = getDeclaredTypeOfClassOrInterface(symbol); + let constructSignatures = symbol.members ? getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor)) : emptyArray; + if (symbol.flags & SymbolFlags.Function) { + constructSignatures = addRange(constructSignatures.slice(), mapDefined( + type.callSignatures, + sig => isJSConstructor(sig.declaration) ? + createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags) : + undefined)); } + if (!constructSignatures.length) { + constructSignatures = getDefaultConstructSignatures(classType); + } + type.constructSignatures = constructSignatures; } + } - /** Resolve the members of a mapped type { [P in K]: T } */ - function resolveMappedTypeMembers(type: MappedType) { - const members: SymbolTable = createSymbolTable(); - let indexInfos: IndexInfo[] | undefined; - // Resolve upfront such that recursive references see an empty object type. - setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); - // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, - // and T as the template type. - const typeParameter = getTypeParameterFromMappedType(type); - const constraintType = getConstraintTypeFromMappedType(type); - const nameType = getNameTypeFromMappedType(type.target as MappedType || type); - const templateType = getTemplateTypeFromMappedType(type.target as MappedType || type); - const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' - const templateModifiers = getMappedTypeModifiers(type); - const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique; - if (isMappedTypeWithKeyofConstraintDeclaration(type)) { - // We have a { [P in keyof T]: X } - forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, include, keyofStringsOnly, addMemberForKeyType); + type ReplaceableIndexedAccessType = IndexedAccessType & { objectType: TypeParameter, indexType: TypeParameter }; + function replaceIndexedAccess(instantiable: Type, type: ReplaceableIndexedAccessType, replacement: Type) { + // map type.indexType to 0 + // map type.objectType to `[TReplacement]` + // thus making the indexed access `[TReplacement][0]` or `TReplacement` + return instantiateType(instantiable, createTypeMapper([type.indexType, type.objectType], [getNumberLiteralType(0), createTupleType([replacement])])); + } + + function resolveReverseMappedTypeMembers(type: ReverseMappedType) { + const indexInfo = getIndexInfoOfType(type.source, stringType); + const modifiers = getMappedTypeModifiers(type.mappedType); + const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true; + const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional; + const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly)] : emptyArray; + const members = createSymbolTable(); + for (const prop of getPropertiesOfType(type.source)) { + const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0); + const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol; + inferredProp.declarations = prop.declarations; + inferredProp.nameType = getSymbolLinks(prop).nameType; + inferredProp.propertyType = getTypeOfSymbol(prop); + if (type.constraintType.type.flags & TypeFlags.IndexedAccess + && (type.constraintType.type as IndexedAccessType).objectType.flags & TypeFlags.TypeParameter + && (type.constraintType.type as IndexedAccessType).indexType.flags & TypeFlags.TypeParameter) { + // A reverse mapping of `{[K in keyof T[K_1]]: T[K_1]}` is the same as that of `{[K in keyof T]: T}`, since all we care about is + // inferring to the "type parameter" (or indexed access) shared by the constraint and template. So, to reduce the number of + // type identities produced, we simplify such indexed access occurences + const newTypeParam = (type.constraintType.type as IndexedAccessType).objectType; + const newMappedType = replaceIndexedAccess(type.mappedType, type.constraintType.type as ReplaceableIndexedAccessType, newTypeParam); + inferredProp.mappedType = newMappedType as MappedType; + inferredProp.constraintType = getIndexType(newTypeParam) as IndexType; } else { - forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); - } - setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos || emptyArray); - - function addMemberForKeyType(keyType: Type) { - const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType; - forEachType(propNameType, t => addMemberForKeyTypeWorker(keyType, t)); + inferredProp.mappedType = type.mappedType; + inferredProp.constraintType = type.constraintType; } + members.set(prop.escapedName, inferredProp); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos); + } - function addMemberForKeyTypeWorker(keyType: Type, propNameType: Type) { - // If the current iteration type constituent is a string literal type, create a property. - // Otherwise, for type string create a string index signature. - if (isTypeUsableAsPropertyName(propNameType)) { - const propName = getPropertyNameFromType(propNameType); - // String enum members from separate enums with identical values - // are distinct types with the same property name. Make the resulting - // property symbol's name type be the union of those enum member types. - const existingProp = members.get(propName) as MappedSymbol | undefined; - if (existingProp) { - existingProp.nameType = getUnionType([existingProp.nameType!, propNameType]); - existingProp.keyType = getUnionType([existingProp.keyType, keyType]); - } - else { - const modifiersProp = isTypeUsableAsPropertyName(keyType) ? getPropertyOfType(modifiersType, getPropertyNameFromType(keyType)) : undefined; - const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional || - !(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional); - const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly || - !(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersProp && isReadonlySymbol(modifiersProp)); - const stripOptional = strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional; - const lateFlag: CheckFlags = modifiersProp ? getIsLateCheckFlag(modifiersProp) : 0; - const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName, - lateFlag | CheckFlags.Mapped | (isReadonly ? CheckFlags.Readonly : 0) | (stripOptional ? CheckFlags.StripOptional : 0)) as MappedSymbol; - prop.mappedType = type; - prop.nameType = propNameType; - prop.keyType = keyType; - if (modifiersProp) { - prop.syntheticOrigin = modifiersProp; - // If the mapped type has an `as XXX` clause, the property name likely won't match the declaration name and - // multiple properties may map to the same name. Thus, we attach no declarations to the symbol. - prop.declarations = nameType ? undefined : modifiersProp.declarations; - } - members.set(propName, prop); - } - } - else if (isValidIndexKeyType(propNameType) || propNameType.flags & (TypeFlags.Any | TypeFlags.Enum)) { - const indexKeyType = propNameType.flags & (TypeFlags.Any | TypeFlags.String) ? stringType : - propNameType.flags & (TypeFlags.Number | TypeFlags.Enum) ? numberType : - propNameType; - const propType = instantiateType(templateType, appendTypeMapping(type.mapper, typeParameter, keyType)); - const indexInfo = createIndexInfo(indexKeyType, propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); - indexInfos = appendIndexInfo(indexInfos, indexInfo, /*union*/ true); - } - } - } - - function getTypeOfMappedSymbol(symbol: MappedSymbol) { - if (!symbol.type) { - const mappedType = symbol.mappedType; - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - mappedType.containsError = true; - return errorType; - } - const templateType = getTemplateTypeFromMappedType(mappedType.target as MappedType || mappedType); - const mapper = appendTypeMapping(mappedType.mapper, getTypeParameterFromMappedType(mappedType), symbol.keyType); - const propType = instantiateType(templateType, mapper); - // When creating an optional property in strictNullChecks mode, if 'undefined' isn't assignable to the - // type, we include 'undefined' in the type. Similarly, when creating a non-optional property in strictNullChecks - // mode, if the underlying property is optional we remove 'undefined' from the type. - let type = strictNullChecks && symbol.flags & SymbolFlags.Optional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) : - symbol.checkFlags & CheckFlags.StripOptional ? removeMissingOrUndefinedType(propType) : - propType; - if (!popTypeResolution()) { - error(currentNode, Diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, symbolToString(symbol), typeToString(mappedType)); - type = errorType; + // Return the lower bound of the key type in a mapped type. Intuitively, the lower + // bound includes those keys that are known to always be present, for example because + // because of constraints on type parameters (e.g. 'keyof T' for a constrained T). + function getLowerBoundOfKeyType(type: Type): Type { + if (type.flags & TypeFlags.Index) { + const t = getApparentType((type as IndexType).type); + return isGenericTupleType(t) ? getKnownKeysOfTupleType(t) : getIndexType(t); + } + if (type.flags & TypeFlags.Conditional) { + if ((type as ConditionalType).root.isDistributive) { + const checkType = (type as ConditionalType).checkType; + const constraint = getLowerBoundOfKeyType(checkType); + if (constraint !== checkType) { + return getConditionalTypeInstantiation(type as ConditionalType, prependTypeMapping((type as ConditionalType).root.checkType, constraint, (type as ConditionalType).mapper)); } - symbol.type = type; } - return symbol.type; + return type; } - - function getTypeParameterFromMappedType(type: MappedType) { - return type.typeParameter || - (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(type.declaration.typeParameter))); + if (type.flags & TypeFlags.Union) { + return mapType(type as UnionType, getLowerBoundOfKeyType); } - - function getConstraintTypeFromMappedType(type: MappedType) { - return type.constraintType || - (type.constraintType = getConstraintOfTypeParameter(getTypeParameterFromMappedType(type)) || errorType); + if (type.flags & TypeFlags.Intersection) { + // Similarly to getTypeFromIntersectionTypeNode, we preserve the special string & {}, number & {}, + // and bigint & {} intersections that are used to prevent subtype reduction in union types. + const types = (type as IntersectionType).types; + if (types.length === 2 && !!(types[0].flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && types[1] === emptyTypeLiteralType) { + return type; + } + return getIntersectionType(sameMap((type as UnionType).types, getLowerBoundOfKeyType)); } + return type; + } - function getNameTypeFromMappedType(type: MappedType) { - return type.declaration.nameType ? - type.nameType || (type.nameType = instantiateType(getTypeFromTypeNode(type.declaration.nameType), type.mapper)) : - undefined; - } + function getIsLateCheckFlag(s: Symbol): CheckFlags { + return getCheckFlags(s) & CheckFlags.Late; + } - function getTemplateTypeFromMappedType(type: MappedType) { - return type.templateType || - (type.templateType = type.declaration.type ? - instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), /*isProperty*/ true, !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), type.mapper) : - errorType); + function forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(type: Type, include: TypeFlags, stringsOnly: boolean, cb: (keyType: Type) => void) { + for (const prop of getPropertiesOfType(type)) { + cb(getLiteralTypeFromProperty(prop, include)); } - - function getConstraintDeclarationForMappedType(type: MappedType) { - return getEffectiveConstraintOfTypeParameter(type.declaration.typeParameter); + if (type.flags & TypeFlags.Any) { + cb(stringType); } - - function isMappedTypeWithKeyofConstraintDeclaration(type: MappedType) { - const constraintDeclaration = getConstraintDeclarationForMappedType(type)!; // TODO: GH#18217 - return constraintDeclaration.kind === SyntaxKind.TypeOperator && - (constraintDeclaration as TypeOperatorNode).operator === SyntaxKind.KeyOfKeyword; + else { + for (const info of getIndexInfosOfType(type)) { + if (!stringsOnly || info.keyType.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) { + cb(info.keyType); + } + } } + } - function getModifiersTypeFromMappedType(type: MappedType) { - if (!type.modifiersType) { - if (isMappedTypeWithKeyofConstraintDeclaration(type)) { - // If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check - // AST nodes here because, when T is a non-generic type, the logic below eagerly resolves - // 'keyof T' to a literal union type and we can't recover T from that type. - type.modifiersType = instantiateType(getTypeFromTypeNode((getConstraintDeclarationForMappedType(type) as TypeOperatorNode).type), type.mapper); + /** Resolve the members of a mapped type { [P in K]: T } */ + function resolveMappedTypeMembers(type: MappedType) { + const members: SymbolTable = createSymbolTable(); + let indexInfos: IndexInfo[] | undefined; + // Resolve upfront such that recursive references see an empty object type. + setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); + // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, + // and T as the template type. + const typeParameter = getTypeParameterFromMappedType(type); + const constraintType = getConstraintTypeFromMappedType(type); + const nameType = getNameTypeFromMappedType(type.target as MappedType || type); + const templateType = getTemplateTypeFromMappedType(type.target as MappedType || type); + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + const templateModifiers = getMappedTypeModifiers(type); + const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, include, keyofStringsOnly, addMemberForKeyType); + } + else { + forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos || emptyArray); + + function addMemberForKeyType(keyType: Type) { + const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType; + forEachType(propNameType, t => addMemberForKeyTypeWorker(keyType, t)); + } + + function addMemberForKeyTypeWorker(keyType: Type, propNameType: Type) { + // If the current iteration type constituent is a string literal type, create a property. + // Otherwise, for type string create a string index signature. + if (isTypeUsableAsPropertyName(propNameType)) { + const propName = getPropertyNameFromType(propNameType); + // String enum members from separate enums with identical values + // are distinct types with the same property name. Make the resulting + // property symbol's name type be the union of those enum member types. + const existingProp = members.get(propName) as MappedSymbol | undefined; + if (existingProp) { + existingProp.nameType = getUnionType([existingProp.nameType!, propNameType]); + existingProp.keyType = getUnionType([existingProp.keyType, keyType]); } else { - // Otherwise, get the declared constraint type, and if the constraint type is a type parameter, - // get the constraint of that type parameter. If the resulting type is an indexed type 'keyof T', - // the modifiers type is T. Otherwise, the modifiers type is unknown. - const declaredType = getTypeFromMappedTypeNode(type.declaration) as MappedType; - const constraint = getConstraintTypeFromMappedType(declaredType); - const extendedConstraint = constraint && constraint.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(constraint as TypeParameter) : constraint; - type.modifiersType = extendedConstraint && extendedConstraint.flags & TypeFlags.Index ? instantiateType((extendedConstraint as IndexType).type, type.mapper) : unknownType; - } + const modifiersProp = isTypeUsableAsPropertyName(keyType) ? getPropertyOfType(modifiersType, getPropertyNameFromType(keyType)) : undefined; + const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional || + !(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional); + const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly || + !(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersProp && isReadonlySymbol(modifiersProp)); + const stripOptional = strictNullChecks && !isOptional && modifiersProp && modifiersProp.flags & SymbolFlags.Optional; + const lateFlag: CheckFlags = modifiersProp ? getIsLateCheckFlag(modifiersProp) : 0; + const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName, + lateFlag | CheckFlags.Mapped | (isReadonly ? CheckFlags.Readonly : 0) | (stripOptional ? CheckFlags.StripOptional : 0)) as MappedSymbol; + prop.mappedType = type; + prop.nameType = propNameType; + prop.keyType = keyType; + if (modifiersProp) { + prop.syntheticOrigin = modifiersProp; + // If the mapped type has an `as XXX` clause, the property name likely won't match the declaration name and + // multiple properties may map to the same name. Thus, we attach no declarations to the symbol. + prop.declarations = nameType ? undefined : modifiersProp.declarations; + } + members.set(propName, prop); + } + } + else if (isValidIndexKeyType(propNameType) || propNameType.flags & (TypeFlags.Any | TypeFlags.Enum)) { + const indexKeyType = propNameType.flags & (TypeFlags.Any | TypeFlags.String) ? stringType : + propNameType.flags & (TypeFlags.Number | TypeFlags.Enum) ? numberType : + propNameType; + const propType = instantiateType(templateType, appendTypeMapping(type.mapper, typeParameter, keyType)); + const indexInfo = createIndexInfo(indexKeyType, propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); + indexInfos = appendIndexInfo(indexInfos, indexInfo, /*union*/ true); } - return type.modifiersType; } + } - function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers { - const declaration = type.declaration; - return (declaration.readonlyToken ? declaration.readonlyToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeReadonly : MappedTypeModifiers.IncludeReadonly : 0) | - (declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0); + function getTypeOfMappedSymbol(symbol: MappedSymbol) { + if (!symbol.type) { + const mappedType = symbol.mappedType; + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + mappedType.containsError = true; + return errorType; + } + const templateType = getTemplateTypeFromMappedType(mappedType.target as MappedType || mappedType); + const mapper = appendTypeMapping(mappedType.mapper, getTypeParameterFromMappedType(mappedType), symbol.keyType); + const propType = instantiateType(templateType, mapper); + // When creating an optional property in strictNullChecks mode, if 'undefined' isn't assignable to the + // type, we include 'undefined' in the type. Similarly, when creating a non-optional property in strictNullChecks + // mode, if the underlying property is optional we remove 'undefined' from the type. + let type = strictNullChecks && symbol.flags & SymbolFlags.Optional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) : + symbol.checkFlags & CheckFlags.StripOptional ? removeMissingOrUndefinedType(propType) : + propType; + if (!popTypeResolution()) { + error(currentNode, Diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, symbolToString(symbol), typeToString(mappedType)); + type = errorType; + } + symbol.type = type; } + return symbol.type; + } - function getMappedTypeOptionality(type: MappedType): number { - const modifiers = getMappedTypeModifiers(type); - return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; - } + function getTypeParameterFromMappedType(type: MappedType) { + return type.typeParameter || + (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(type.declaration.typeParameter))); + } - function getCombinedMappedTypeOptionality(type: MappedType): number { - const optionality = getMappedTypeOptionality(type); - const modifiersType = getModifiersTypeFromMappedType(type); - return optionality || (isGenericMappedType(modifiersType) ? getMappedTypeOptionality(modifiersType) : 0); - } + function getConstraintTypeFromMappedType(type: MappedType) { + return type.constraintType || + (type.constraintType = getConstraintOfTypeParameter(getTypeParameterFromMappedType(type)) || errorType); + } + + function getNameTypeFromMappedType(type: MappedType) { + return type.declaration.nameType ? + type.nameType || (type.nameType = instantiateType(getTypeFromTypeNode(type.declaration.nameType), type.mapper)) : + undefined; + } + + function getTemplateTypeFromMappedType(type: MappedType) { + return type.templateType || + (type.templateType = type.declaration.type ? + instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), /*isProperty*/ true, !!(getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional)), type.mapper) : + errorType); + } - function isPartialMappedType(type: Type) { - return !!(getObjectFlags(type) & ObjectFlags.Mapped && getMappedTypeModifiers(type as MappedType) & MappedTypeModifiers.IncludeOptional); + function getConstraintDeclarationForMappedType(type: MappedType) { + return getEffectiveConstraintOfTypeParameter(type.declaration.typeParameter); + } + + function isMappedTypeWithKeyofConstraintDeclaration(type: MappedType) { + const constraintDeclaration = getConstraintDeclarationForMappedType(type)!; // TODO: GH#18217 + return constraintDeclaration.kind === SyntaxKind.TypeOperator && + (constraintDeclaration as TypeOperatorNode).operator === SyntaxKind.KeyOfKeyword; + } + + function getModifiersTypeFromMappedType(type: MappedType) { + if (!type.modifiersType) { + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check + // AST nodes here because, when T is a non-generic type, the logic below eagerly resolves + // 'keyof T' to a literal union type and we can't recover T from that type. + type.modifiersType = instantiateType(getTypeFromTypeNode((getConstraintDeclarationForMappedType(type) as TypeOperatorNode).type), type.mapper); + } + else { + // Otherwise, get the declared constraint type, and if the constraint type is a type parameter, + // get the constraint of that type parameter. If the resulting type is an indexed type 'keyof T', + // the modifiers type is T. Otherwise, the modifiers type is unknown. + const declaredType = getTypeFromMappedTypeNode(type.declaration) as MappedType; + const constraint = getConstraintTypeFromMappedType(declaredType); + const extendedConstraint = constraint && constraint.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(constraint as TypeParameter) : constraint; + type.modifiersType = extendedConstraint && extendedConstraint.flags & TypeFlags.Index ? instantiateType((extendedConstraint as IndexType).type, type.mapper) : unknownType; + } } + return type.modifiersType; + } - function isGenericMappedType(type: Type): type is MappedType { - if (getObjectFlags(type) & ObjectFlags.Mapped) { - const constraint = getConstraintTypeFromMappedType(type as MappedType); - if (isGenericIndexType(constraint)) { - return true; - } - // A mapped type is generic if the 'as' clause references generic types other than the iteration type. - // To determine this, we substitute the constraint type (that we now know isn't generic) for the iteration - // type and check whether the resulting type is generic. - const nameType = getNameTypeFromMappedType(type as MappedType); - if (nameType && isGenericIndexType(instantiateType(nameType, makeUnaryTypeMapper(getTypeParameterFromMappedType(type as MappedType), constraint)))) { - return true; - } + function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers { + const declaration = type.declaration; + return (declaration.readonlyToken ? declaration.readonlyToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeReadonly : MappedTypeModifiers.IncludeReadonly : 0) | + (declaration.questionToken ? declaration.questionToken.kind === SyntaxKind.MinusToken ? MappedTypeModifiers.ExcludeOptional : MappedTypeModifiers.IncludeOptional : 0); + } + + function getMappedTypeOptionality(type: MappedType): number { + const modifiers = getMappedTypeModifiers(type); + return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; + } + + function getCombinedMappedTypeOptionality(type: MappedType): number { + const optionality = getMappedTypeOptionality(type); + const modifiersType = getModifiersTypeFromMappedType(type); + return optionality || (isGenericMappedType(modifiersType) ? getMappedTypeOptionality(modifiersType) : 0); + } + + function isPartialMappedType(type: Type) { + return !!(getObjectFlags(type) & ObjectFlags.Mapped && getMappedTypeModifiers(type as MappedType) & MappedTypeModifiers.IncludeOptional); + } + + function isGenericMappedType(type: Type): type is MappedType { + if (getObjectFlags(type) & ObjectFlags.Mapped) { + const constraint = getConstraintTypeFromMappedType(type as MappedType); + if (isGenericIndexType(constraint)) { + return true; + } + // A mapped type is generic if the 'as' clause references generic types other than the iteration type. + // To determine this, we substitute the constraint type (that we now know isn't generic) for the iteration + // type and check whether the resulting type is generic. + const nameType = getNameTypeFromMappedType(type as MappedType); + if (nameType && isGenericIndexType(instantiateType(nameType, makeUnaryTypeMapper(getTypeParameterFromMappedType(type as MappedType), constraint)))) { + return true; } - return false; } + return false; + } - function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { - if (!(type as ResolvedType).members) { - if (type.flags & TypeFlags.Object) { - if ((type as ObjectType).objectFlags & ObjectFlags.Reference) { - resolveTypeReferenceMembers(type as TypeReference); - } - else if ((type as ObjectType).objectFlags & ObjectFlags.ClassOrInterface) { - resolveClassOrInterfaceMembers(type as InterfaceType); - } - else if ((type as ReverseMappedType).objectFlags & ObjectFlags.ReverseMapped) { - resolveReverseMappedTypeMembers(type as ReverseMappedType); - } - else if ((type as ObjectType).objectFlags & ObjectFlags.Anonymous) { - resolveAnonymousTypeMembers(type as AnonymousType); - } - else if ((type as MappedType).objectFlags & ObjectFlags.Mapped) { - resolveMappedTypeMembers(type as MappedType); - } - else { - Debug.fail("Unhandled object type " + Debug.formatObjectFlags(type.objectFlags)); - } + function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { + if (!(type as ResolvedType).members) { + if (type.flags & TypeFlags.Object) { + if ((type as ObjectType).objectFlags & ObjectFlags.Reference) { + resolveTypeReferenceMembers(type as TypeReference); } - else if (type.flags & TypeFlags.Union) { - resolveUnionTypeMembers(type as UnionType); + else if ((type as ObjectType).objectFlags & ObjectFlags.ClassOrInterface) { + resolveClassOrInterfaceMembers(type as InterfaceType); } - else if (type.flags & TypeFlags.Intersection) { - resolveIntersectionTypeMembers(type as IntersectionType); + else if ((type as ReverseMappedType).objectFlags & ObjectFlags.ReverseMapped) { + resolveReverseMappedTypeMembers(type as ReverseMappedType); + } + else if ((type as ObjectType).objectFlags & ObjectFlags.Anonymous) { + resolveAnonymousTypeMembers(type as AnonymousType); + } + else if ((type as MappedType).objectFlags & ObjectFlags.Mapped) { + resolveMappedTypeMembers(type as MappedType); } else { - Debug.fail("Unhandled type " + Debug.formatTypeFlags(type.flags)); + Debug.fail("Unhandled object type " + Debug.formatObjectFlags(type.objectFlags)); } } - return type as ResolvedType; + else if (type.flags & TypeFlags.Union) { + resolveUnionTypeMembers(type as UnionType); + } + else if (type.flags & TypeFlags.Intersection) { + resolveIntersectionTypeMembers(type as IntersectionType); + } + else { + Debug.fail("Unhandled type " + Debug.formatTypeFlags(type.flags)); + } } + return type as ResolvedType; + } - /** Return properties of an object type or an empty array for other types */ - function getPropertiesOfObjectType(type: Type): Symbol[] { - if (type.flags & TypeFlags.Object) { - return resolveStructuredTypeMembers(type as ObjectType).properties; - } - return emptyArray; + /** Return properties of an object type or an empty array for other types */ + function getPropertiesOfObjectType(type: Type): Symbol[] { + if (type.flags & TypeFlags.Object) { + return resolveStructuredTypeMembers(type as ObjectType).properties; } + return emptyArray; + } - /** If the given type is an object type and that type has a property by the given name, - * return the symbol for that property. Otherwise return undefined. - */ - function getPropertyOfObjectType(type: Type, name: __String): Symbol | undefined { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - const symbol = resolved.members.get(name); - if (symbol && symbolIsValue(symbol)) { - return symbol; - } + /** If the given type is an object type and that type has a property by the given name, + * return the symbol for that property. Otherwise return undefined. + */ + function getPropertyOfObjectType(type: Type, name: __String): Symbol | undefined { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + const symbol = resolved.members.get(name); + if (symbol && symbolIsValue(symbol)) { + return symbol; } } + } - function getPropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType): Symbol[] { - if (!type.resolvedProperties) { - const members = createSymbolTable(); - for (const current of type.types) { - for (const prop of getPropertiesOfType(current)) { - if (!members.has(prop.escapedName)) { - const combinedProp = getPropertyOfUnionOrIntersectionType(type, prop.escapedName); - if (combinedProp) { - members.set(prop.escapedName, combinedProp); - } + function getPropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType): Symbol[] { + if (!type.resolvedProperties) { + const members = createSymbolTable(); + for (const current of type.types) { + for (const prop of getPropertiesOfType(current)) { + if (!members.has(prop.escapedName)) { + const combinedProp = getPropertyOfUnionOrIntersectionType(type, prop.escapedName); + if (combinedProp) { + members.set(prop.escapedName, combinedProp); } } - // The properties of a union type are those that are present in all constituent types, so - // we only need to check the properties of the first type without index signature - if (type.flags & TypeFlags.Union && getIndexInfosOfType(current).length === 0) { - break; - } } - type.resolvedProperties = getNamedMembers(members); + // The properties of a union type are those that are present in all constituent types, so + // we only need to check the properties of the first type without index signature + if (type.flags & TypeFlags.Union && getIndexInfosOfType(current).length === 0) { + break; + } } - return type.resolvedProperties; - } - - function getPropertiesOfType(type: Type): Symbol[] { - type = getReducedApparentType(type); - return type.flags & TypeFlags.UnionOrIntersection ? - getPropertiesOfUnionOrIntersectionType(type as UnionType) : - getPropertiesOfObjectType(type); + type.resolvedProperties = getNamedMembers(members); } + return type.resolvedProperties; + } - function forEachPropertyOfType(type: Type, action: (symbol: Symbol, escapedName: __String) => void): void { - type = getReducedApparentType(type); - if (type.flags & TypeFlags.StructuredType) { - resolveStructuredTypeMembers(type as StructuredType).members.forEach((symbol, escapedName) => { - if (isNamedMember(symbol, escapedName)) { - action(symbol, escapedName); - } - }); - } - } + function getPropertiesOfType(type: Type): Symbol[] { + type = getReducedApparentType(type); + return type.flags & TypeFlags.UnionOrIntersection ? + getPropertiesOfUnionOrIntersectionType(type as UnionType) : + getPropertiesOfObjectType(type); + } - function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { - const list = obj.properties as NodeArray; - return list.some(property => { - const nameType = property.name && getLiteralTypeFromPropertyName(property.name); - const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; - const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name); - return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected); + function forEachPropertyOfType(type: Type, action: (symbol: Symbol, escapedName: __String) => void): void { + type = getReducedApparentType(type); + if (type.flags & TypeFlags.StructuredType) { + resolveStructuredTypeMembers(type as StructuredType).members.forEach((symbol, escapedName) => { + if (isNamedMember(symbol, escapedName)) { + action(symbol, escapedName); + } }); } + } - function getAllPossiblePropertiesOfTypes(types: readonly Type[]): Symbol[] { - const unionType = getUnionType(types); - if (!(unionType.flags & TypeFlags.Union)) { - return getAugmentedPropertiesOfType(unionType); - } + function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { + const list = obj.properties as NodeArray; + return list.some(property => { + const nameType = property.name && getLiteralTypeFromPropertyName(property.name); + const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; + const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name); + return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected); + }); + } - const props = createSymbolTable(); - for (const memberType of types) { - for (const { escapedName } of getAugmentedPropertiesOfType(memberType)) { - if (!props.has(escapedName)) { - const prop = createUnionOrIntersectionProperty(unionType as UnionType, escapedName); - // May be undefined if the property is private - if (prop) props.set(escapedName, prop); - } + function getAllPossiblePropertiesOfTypes(types: readonly Type[]): Symbol[] { + const unionType = getUnionType(types); + if (!(unionType.flags & TypeFlags.Union)) { + return getAugmentedPropertiesOfType(unionType); + } + + const props = createSymbolTable(); + for (const memberType of types) { + for (const { escapedName } of getAugmentedPropertiesOfType(memberType)) { + if (!props.has(escapedName)) { + const prop = createUnionOrIntersectionProperty(unionType as UnionType, escapedName); + // May be undefined if the property is private + if (prop) props.set(escapedName, prop); } } - return arrayFrom(props.values()); } + return arrayFrom(props.values()); + } - function getConstraintOfType(type: InstantiableType | UnionOrIntersectionType): Type | undefined { - return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(type as TypeParameter) : - type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(type as IndexedAccessType) : - type.flags & TypeFlags.Conditional ? getConstraintOfConditionalType(type as ConditionalType) : - getBaseConstraintOfType(type); - } + function getConstraintOfType(type: InstantiableType | UnionOrIntersectionType): Type | undefined { + return type.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(type as TypeParameter) : + type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(type as IndexedAccessType) : + type.flags & TypeFlags.Conditional ? getConstraintOfConditionalType(type as ConditionalType) : + getBaseConstraintOfType(type); + } - function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type | undefined { - return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined; - } + function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type | undefined { + return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined; + } - function getConstraintOfIndexedAccess(type: IndexedAccessType) { - return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined; - } + function getConstraintOfIndexedAccess(type: IndexedAccessType) { + return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined; + } - function getSimplifiedTypeOrConstraint(type: Type) { - const simplified = getSimplifiedType(type, /*writing*/ false); - return simplified !== type ? simplified : getConstraintOfType(type); - } + function getSimplifiedTypeOrConstraint(type: Type) { + const simplified = getSimplifiedType(type, /*writing*/ false); + return simplified !== type ? simplified : getConstraintOfType(type); + } - function getConstraintFromIndexedAccess(type: IndexedAccessType) { - if (isMappedTypeGenericIndexedAccess(type)) { - // For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic, - // we substitute an instantiation of E where P is replaced with X. - return substituteIndexedMappedType(type.objectType as MappedType, type.indexType); - } - const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); - if (indexConstraint && indexConstraint !== type.indexType) { - const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.accessFlags); - if (indexedAccess) { - return indexedAccess; - } - } - const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType); - if (objectConstraint && objectConstraint !== type.objectType) { - return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType, type.accessFlags); - } - return undefined; + function getConstraintFromIndexedAccess(type: IndexedAccessType) { + if (isMappedTypeGenericIndexedAccess(type)) { + // For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic, + // we substitute an instantiation of E where P is replaced with X. + return substituteIndexedMappedType(type.objectType as MappedType, type.indexType); } - - function getDefaultConstraintOfConditionalType(type: ConditionalType) { - if (!type.resolvedDefaultConstraint) { - // An `any` branch of a conditional type would normally be viral - specifically, without special handling here, - // a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to - // 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 = getFalseTypeFromConditionalType(type); - type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]); - } - return type.resolvedDefaultConstraint; - } - - function getConstraintOfDistributiveConditionalType(type: ConditionalType): Type | undefined { - // Check if we have a conditional type of the form 'T extends U ? X : Y', where T is a constrained - // type parameter. If so, create an instantiation of the conditional type where T is replaced - // with its constraint. We do this because if the constraint is a union type it will be distributed - // over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T' - // removes 'undefined' from T. - // We skip returning a distributive constraint for a restrictive instantiation of a conditional type - // as the constraint for all type params (check type included) have been replace with `unknown`, which - // is going to produce even more false positive/negative results than the distribute constraint already does. - // Please note: the distributive constraint is a kludge for emulating what a negated type could to do filter - // a union - once negated types exist and are applied to the conditional false branch, this "constraint" - // likely doesn't need to exist. - if (type.root.isDistributive && type.restrictiveInstantiation !== type) { - const simplified = getSimplifiedType(type.checkType, /*writing*/ false); - const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified; - if (constraint && constraint !== type.checkType) { - const instantiated = getConditionalTypeInstantiation(type, prependTypeMapping(type.root.checkType, constraint, type.mapper)); - if (!(instantiated.flags & TypeFlags.Never)) { - return instantiated; - } - } + const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); + if (indexConstraint && indexConstraint !== type.indexType) { + const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.accessFlags); + if (indexedAccess) { + return indexedAccess; } - return undefined; } - - function getConstraintFromConditionalType(type: ConditionalType) { - return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type); + const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType); + if (objectConstraint && objectConstraint !== type.objectType) { + return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType, type.accessFlags); } + return undefined; + } - function getConstraintOfConditionalType(type: ConditionalType) { - return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined; - } + function getDefaultConstraintOfConditionalType(type: ConditionalType) { + if (!type.resolvedDefaultConstraint) { + // An `any` branch of a conditional type would normally be viral - specifically, without special handling here, + // a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to + // 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 = getFalseTypeFromConditionalType(type); + type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]); + } + return type.resolvedDefaultConstraint; + } - function getEffectiveConstraintOfIntersection(types: readonly Type[], targetIsUnion: boolean) { - let constraints: Type[] | undefined; - let hasDisjointDomainType = false; - for (const t of types) { - if (t.flags & TypeFlags.Instantiable) { - // We keep following constraints as long as we have an instantiable type that is known - // not to be circular or infinite (hence we stop on index access types). - let constraint = getConstraintOfType(t); - while (constraint && constraint.flags & (TypeFlags.TypeParameter | TypeFlags.Index | TypeFlags.Conditional)) { - constraint = getConstraintOfType(constraint); - } - if (constraint) { - constraints = append(constraints, constraint); - if (targetIsUnion) { - constraints = append(constraints, t); - } - } + function getConstraintOfDistributiveConditionalType(type: ConditionalType): Type | undefined { + // Check if we have a conditional type of the form 'T extends U ? X : Y', where T is a constrained + // type parameter. If so, create an instantiation of the conditional type where T is replaced + // with its constraint. We do this because if the constraint is a union type it will be distributed + // over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T' + // removes 'undefined' from T. + // We skip returning a distributive constraint for a restrictive instantiation of a conditional type + // as the constraint for all type params (check type included) have been replace with `unknown`, which + // is going to produce even more false positive/negative results than the distribute constraint already does. + // Please note: the distributive constraint is a kludge for emulating what a negated type could to do filter + // a union - once negated types exist and are applied to the conditional false branch, this "constraint" + // likely doesn't need to exist. + if (type.root.isDistributive && type.restrictiveInstantiation !== type) { + const simplified = getSimplifiedType(type.checkType, /*writing*/ false); + const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified; + if (constraint && constraint !== type.checkType) { + const instantiated = getConditionalTypeInstantiation(type, prependTypeMapping(type.root.checkType, constraint, type.mapper)); + if (!(instantiated.flags & TypeFlags.Never)) { + return instantiated; + } + } + } + return undefined; + } + + function getConstraintFromConditionalType(type: ConditionalType) { + return getConstraintOfDistributiveConditionalType(type) || getDefaultConstraintOfConditionalType(type); + } + + function getConstraintOfConditionalType(type: ConditionalType) { + return hasNonCircularBaseConstraint(type) ? getConstraintFromConditionalType(type) : undefined; + } + + function getEffectiveConstraintOfIntersection(types: readonly Type[], targetIsUnion: boolean) { + let constraints: Type[] | undefined; + let hasDisjointDomainType = false; + for (const t of types) { + if (t.flags & TypeFlags.Instantiable) { + // We keep following constraints as long as we have an instantiable type that is known + // not to be circular or infinite (hence we stop on index access types). + let constraint = getConstraintOfType(t); + while (constraint && constraint.flags & (TypeFlags.TypeParameter | TypeFlags.Index | TypeFlags.Conditional)) { + constraint = getConstraintOfType(constraint); } - else if (t.flags & TypeFlags.DisjointDomains || isEmptyAnonymousObjectType(t)) { - hasDisjointDomainType = true; + if (constraint) { + constraints = append(constraints, constraint); + if (targetIsUnion) { + constraints = append(constraints, t); + } } } - // If the target is a union type or if we are intersecting with types belonging to one of the - // disjoint domains, we may end up producing a constraint that hasn't been examined before. - if (constraints && (targetIsUnion || hasDisjointDomainType)) { - if (hasDisjointDomainType) { - // We add any types belong to one of the disjoint domains because they might cause the final - // intersection operation to reduce the union constraints. - for (const t of types) { - if (t.flags & TypeFlags.DisjointDomains || isEmptyAnonymousObjectType(t)) { - constraints = append(constraints, t); - } + else if (t.flags & TypeFlags.DisjointDomains || isEmptyAnonymousObjectType(t)) { + hasDisjointDomainType = true; + } + } + // If the target is a union type or if we are intersecting with types belonging to one of the + // disjoint domains, we may end up producing a constraint that hasn't been examined before. + if (constraints && (targetIsUnion || hasDisjointDomainType)) { + if (hasDisjointDomainType) { + // We add any types belong to one of the disjoint domains because they might cause the final + // intersection operation to reduce the union constraints. + for (const t of types) { + if (t.flags & TypeFlags.DisjointDomains || isEmptyAnonymousObjectType(t)) { + constraints = append(constraints, t); } } - // The source types were normalized; ensure the result is normalized too. - return getNormalizedType(getIntersectionType(constraints), /*writing*/ false); } - return undefined; + // The source types were normalized; ensure the result is normalized too. + return getNormalizedType(getIntersectionType(constraints), /*writing*/ false); } + return undefined; + } - function getBaseConstraintOfType(type: Type): Type | undefined { - if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) { - const constraint = getResolvedBaseConstraint(type as InstantiableType | UnionOrIntersectionType); - return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined; - } - return type.flags & TypeFlags.Index ? keyofConstraintType : undefined; + function getBaseConstraintOfType(type: Type): Type | undefined { + if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) { + const constraint = getResolvedBaseConstraint(type as InstantiableType | UnionOrIntersectionType); + return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined; } + return type.flags & TypeFlags.Index ? keyofConstraintType : undefined; + } - /** - * This is similar to `getBaseConstraintOfType` except it returns the input type if there's no base constraint, instead of `undefined` - * It also doesn't map indexes to `string`, as where this is used this would be unneeded (and likely undesirable) - */ - function getBaseConstraintOrType(type: Type) { - return getBaseConstraintOfType(type) || type; - } + /** + * This is similar to `getBaseConstraintOfType` except it returns the input type if there's no base constraint, instead of `undefined` + * It also doesn't map indexes to `string`, as where this is used this would be unneeded (and likely undesirable) + */ + function getBaseConstraintOrType(type: Type) { + return getBaseConstraintOfType(type) || type; + } - function hasNonCircularBaseConstraint(type: InstantiableType): boolean { - return getResolvedBaseConstraint(type) !== circularConstraintType; - } + function hasNonCircularBaseConstraint(type: InstantiableType): boolean { + return getResolvedBaseConstraint(type) !== circularConstraintType; + } - /** - * Return the resolved base constraint of a type variable. The noConstraintType singleton is returned if the - * type variable has no constraint, and the circularConstraintType singleton is returned if the constraint - * circularly references the type variable. - */ - function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type { - if (type.resolvedBaseConstraint) { - return type.resolvedBaseConstraint; - } - const stack: object[] = []; - return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type); - - function getImmediateBaseConstraint(t: Type): Type { - if (!t.immediateBaseConstraint) { - if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) { - return circularConstraintType; - } - let result; - // We always explore at least 10 levels of nested constraints. Thereafter, we continue to explore - // up to 50 levels of nested constraints provided there are no "deeply nested" types on the stack - // (i.e. no types for which five instantiations have been recorded on the stack). If we reach 50 - // levels of nesting, we are presumably exploring a repeating pattern with a long cycle that hasn't - // yet triggered the deeply nested limiter. We have no test cases that actually get to 50 levels of - // nesting, so it is effectively just a safety stop. - const identity = getRecursionIdentity(t); - if (stack.length < 10 || stack.length < 50 && !contains(stack, identity)) { - stack.push(identity); - result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false)); - stack.pop(); - } - if (!popTypeResolution()) { - if (t.flags & TypeFlags.TypeParameter) { - const errorNode = getConstraintDeclaration(t as TypeParameter); - if (errorNode) { - const diagnostic = error(errorNode, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(t)); - if (currentNode && !isNodeDescendantOf(errorNode, currentNode) && !isNodeDescendantOf(currentNode, errorNode)) { - addRelatedInfo(diagnostic, createDiagnosticForNode(currentNode, Diagnostics.Circularity_originates_in_type_at_this_location)); - } + /** + * Return the resolved base constraint of a type variable. The noConstraintType singleton is returned if the + * type variable has no constraint, and the circularConstraintType singleton is returned if the constraint + * circularly references the type variable. + */ + function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type { + if (type.resolvedBaseConstraint) { + return type.resolvedBaseConstraint; + } + const stack: object[] = []; + return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type); + + function getImmediateBaseConstraint(t: Type): Type { + if (!t.immediateBaseConstraint) { + if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) { + return circularConstraintType; + } + let result; + // We always explore at least 10 levels of nested constraints. Thereafter, we continue to explore + // up to 50 levels of nested constraints provided there are no "deeply nested" types on the stack + // (i.e. no types for which five instantiations have been recorded on the stack). If we reach 50 + // levels of nesting, we are presumably exploring a repeating pattern with a long cycle that hasn't + // yet triggered the deeply nested limiter. We have no test cases that actually get to 50 levels of + // nesting, so it is effectively just a safety stop. + const identity = getRecursionIdentity(t); + if (stack.length < 10 || stack.length < 50 && !contains(stack, identity)) { + stack.push(identity); + result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false)); + stack.pop(); + } + if (!popTypeResolution()) { + if (t.flags & TypeFlags.TypeParameter) { + const errorNode = getConstraintDeclaration(t as TypeParameter); + if (errorNode) { + const diagnostic = error(errorNode, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(t)); + if (currentNode && !isNodeDescendantOf(errorNode, currentNode) && !isNodeDescendantOf(currentNode, errorNode)) { + addRelatedInfo(diagnostic, createDiagnosticForNode(currentNode, Diagnostics.Circularity_originates_in_type_at_this_location)); } } - result = circularConstraintType; } - t.immediateBaseConstraint = result || noConstraintType; + result = circularConstraintType; } - return t.immediateBaseConstraint; + t.immediateBaseConstraint = result || noConstraintType; } + return t.immediateBaseConstraint; + } - function getBaseConstraint(t: Type): Type | undefined { - const c = getImmediateBaseConstraint(t); - return c !== noConstraintType && c !== circularConstraintType ? c : undefined; - } + function getBaseConstraint(t: Type): Type | undefined { + const c = getImmediateBaseConstraint(t); + return c !== noConstraintType && c !== circularConstraintType ? c : undefined; + } - function computeBaseConstraint(t: Type): Type | undefined { - if (t.flags & TypeFlags.TypeParameter) { - const constraint = getConstraintFromTypeParameter(t as TypeParameter); - return (t as TypeParameter).isThisType || !constraint ? - constraint : - getBaseConstraint(constraint); - } - if (t.flags & TypeFlags.UnionOrIntersection) { - const types = (t as UnionOrIntersectionType).types; - const baseTypes: Type[] = []; - let different = false; - for (const type of types) { - const baseType = getBaseConstraint(type); - if (baseType) { - if (baseType !== type) { - different = true; - } - baseTypes.push(baseType); - } - else { + function computeBaseConstraint(t: Type): Type | undefined { + if (t.flags & TypeFlags.TypeParameter) { + const constraint = getConstraintFromTypeParameter(t as TypeParameter); + return (t as TypeParameter).isThisType || !constraint ? + constraint : + getBaseConstraint(constraint); + } + if (t.flags & TypeFlags.UnionOrIntersection) { + const types = (t as UnionOrIntersectionType).types; + const baseTypes: Type[] = []; + let different = false; + for (const type of types) { + const baseType = getBaseConstraint(type); + if (baseType) { + if (baseType !== type) { different = true; } + baseTypes.push(baseType); } - if (!different) { - return t; - } - return t.flags & TypeFlags.Union && baseTypes.length === types.length ? getUnionType(baseTypes) : - t.flags & TypeFlags.Intersection && baseTypes.length ? getIntersectionType(baseTypes) : - undefined; - } - if (t.flags & TypeFlags.Index) { - return keyofConstraintType; - } - if (t.flags & TypeFlags.TemplateLiteral) { - const types = (t as TemplateLiteralType).types; - const constraints = mapDefined(types, getBaseConstraint); - return constraints.length === types.length ? getTemplateLiteralType((t as TemplateLiteralType).texts, constraints) : stringType; - } - if (t.flags & TypeFlags.StringMapping) { - const constraint = getBaseConstraint((t as StringMappingType).type); - return constraint && constraint !== (t as StringMappingType).type ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType; - } - if (t.flags & TypeFlags.IndexedAccess) { - if (isMappedTypeGenericIndexedAccess(t)) { - // For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic, - // we substitute an instantiation of E where P is replaced with X. - return getBaseConstraint(substituteIndexedMappedType((t as IndexedAccessType).objectType as MappedType, (t as IndexedAccessType).indexType)); + else { + different = true; } - const baseObjectType = getBaseConstraint((t as IndexedAccessType).objectType); - const baseIndexType = getBaseConstraint((t as IndexedAccessType).indexType); - const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t as IndexedAccessType).accessFlags); - return baseIndexedAccess && getBaseConstraint(baseIndexedAccess); } - if (t.flags & TypeFlags.Conditional) { - const constraint = getConstraintFromConditionalType(t as ConditionalType); - return constraint && getBaseConstraint(constraint); + if (!different) { + return t; } - if (t.flags & TypeFlags.Substitution) { - return getBaseConstraint(getSubstitutionIntersection(t as SubstitutionType)); + return t.flags & TypeFlags.Union && baseTypes.length === types.length ? getUnionType(baseTypes) : + t.flags & TypeFlags.Intersection && baseTypes.length ? getIntersectionType(baseTypes) : + undefined; + } + if (t.flags & TypeFlags.Index) { + return keyofConstraintType; + } + if (t.flags & TypeFlags.TemplateLiteral) { + const types = (t as TemplateLiteralType).types; + const constraints = mapDefined(types, getBaseConstraint); + return constraints.length === types.length ? getTemplateLiteralType((t as TemplateLiteralType).texts, constraints) : stringType; + } + if (t.flags & TypeFlags.StringMapping) { + const constraint = getBaseConstraint((t as StringMappingType).type); + return constraint && constraint !== (t as StringMappingType).type ? getStringMappingType((t as StringMappingType).symbol, constraint) : stringType; + } + if (t.flags & TypeFlags.IndexedAccess) { + if (isMappedTypeGenericIndexedAccess(t)) { + // For indexed access types of the form { [P in K]: E }[X], where K is non-generic and X is generic, + // we substitute an instantiation of E where P is replaced with X. + return getBaseConstraint(substituteIndexedMappedType((t as IndexedAccessType).objectType as MappedType, (t as IndexedAccessType).indexType)); } - return t; + const baseObjectType = getBaseConstraint((t as IndexedAccessType).objectType); + const baseIndexType = getBaseConstraint((t as IndexedAccessType).indexType); + const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t as IndexedAccessType).accessFlags); + return baseIndexedAccess && getBaseConstraint(baseIndexedAccess); + } + if (t.flags & TypeFlags.Conditional) { + const constraint = getConstraintFromConditionalType(t as ConditionalType); + return constraint && getBaseConstraint(constraint); } + if (t.flags & TypeFlags.Substitution) { + return getBaseConstraint(getSubstitutionIntersection(t as SubstitutionType)); + } + return t; } + } - function getApparentTypeOfIntersectionType(type: IntersectionType) { - return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type, /*apparentType*/ true)); - } + function getApparentTypeOfIntersectionType(type: IntersectionType) { + return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type, /*apparentType*/ true)); + } - function getResolvedTypeParameterDefault(typeParameter: TypeParameter): Type | undefined { - if (!typeParameter.default) { - if (typeParameter.target) { - const targetDefault = getResolvedTypeParameterDefault(typeParameter.target); - typeParameter.default = targetDefault ? instantiateType(targetDefault, typeParameter.mapper) : noConstraintType; - } - else { - // To block recursion, set the initial value to the resolvingDefaultType. - typeParameter.default = resolvingDefaultType; - const defaultDeclaration = typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default); - const defaultType = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType; - if (typeParameter.default === resolvingDefaultType) { - // If we have not been called recursively, set the correct default type. - typeParameter.default = defaultType; - } - } + function getResolvedTypeParameterDefault(typeParameter: TypeParameter): Type | undefined { + if (!typeParameter.default) { + if (typeParameter.target) { + const targetDefault = getResolvedTypeParameterDefault(typeParameter.target); + typeParameter.default = targetDefault ? instantiateType(targetDefault, typeParameter.mapper) : noConstraintType; } - else if (typeParameter.default === resolvingDefaultType) { - // If we are called recursively for this type parameter, mark the default as circular. - typeParameter.default = circularConstraintType; + else { + // To block recursion, set the initial value to the resolvingDefaultType. + typeParameter.default = resolvingDefaultType; + const defaultDeclaration = typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default); + const defaultType = defaultDeclaration ? getTypeFromTypeNode(defaultDeclaration) : noConstraintType; + if (typeParameter.default === resolvingDefaultType) { + // If we have not been called recursively, set the correct default type. + typeParameter.default = defaultType; + } } - return typeParameter.default; } - - /** - * Gets the default type for a type parameter. - * - * If the type parameter is the result of an instantiation, this gets the instantiated - * default type of its target. If the type parameter has no default type or the default is - * circular, `undefined` is returned. - */ - function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined { - const defaultType = getResolvedTypeParameterDefault(typeParameter); - return defaultType !== noConstraintType && defaultType !== circularConstraintType ? defaultType : undefined; + else if (typeParameter.default === resolvingDefaultType) { + // If we are called recursively for this type parameter, mark the default as circular. + typeParameter.default = circularConstraintType; } + return typeParameter.default; + } - function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) { - return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType; - } + /** + * Gets the default type for a type parameter. + * + * If the type parameter is the result of an instantiation, this gets the instantiated + * default type of its target. If the type parameter has no default type or the default is + * circular, `undefined` is returned. + */ + function getDefaultFromTypeParameter(typeParameter: TypeParameter): Type | undefined { + const defaultType = getResolvedTypeParameterDefault(typeParameter); + return defaultType !== noConstraintType && defaultType !== circularConstraintType ? defaultType : undefined; + } - /** - * Indicates whether the declaration of a typeParameter has a default type. - */ - function hasTypeParameterDefault(typeParameter: TypeParameter): boolean { - return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default)); - } + function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) { + return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType; + } - function getApparentTypeOfMappedType(type: MappedType) { - return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); - } + /** + * Indicates whether the declaration of a typeParameter has a default type. + */ + function hasTypeParameterDefault(typeParameter: TypeParameter): boolean { + return !!(typeParameter.symbol && forEach(typeParameter.symbol.declarations, decl => isTypeParameterDeclaration(decl) && decl.default)); + } - function getResolvedApparentTypeOfMappedType(type: MappedType) { - const typeVariable = getHomomorphicTypeVariable(type); - if (typeVariable && !type.declaration.nameType) { - const constraint = getConstraintOfTypeParameter(typeVariable); - if (constraint && isArrayOrTupleType(constraint)) { - return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper)); - } + function getApparentTypeOfMappedType(type: MappedType) { + return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); + } + + function getResolvedApparentTypeOfMappedType(type: MappedType) { + const typeVariable = getHomomorphicTypeVariable(type); + if (typeVariable && !type.declaration.nameType) { + const constraint = getConstraintOfTypeParameter(typeVariable); + if (constraint && isArrayOrTupleType(constraint)) { + return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper)); } - return type; } + return type; + } - function isMappedTypeGenericIndexedAccess(type: Type) { - let objectType; - return !!(type.flags & TypeFlags.IndexedAccess && getObjectFlags(objectType = (type as IndexedAccessType).objectType) & ObjectFlags.Mapped && - !isGenericMappedType(objectType) && isGenericIndexType((type as IndexedAccessType).indexType) && - !(getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.ExcludeOptional) && !(objectType as MappedType).declaration.nameType); - } + function isMappedTypeGenericIndexedAccess(type: Type) { + let objectType; + return !!(type.flags & TypeFlags.IndexedAccess && getObjectFlags(objectType = (type as IndexedAccessType).objectType) & ObjectFlags.Mapped && + !isGenericMappedType(objectType) && isGenericIndexType((type as IndexedAccessType).indexType) && + !(getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.ExcludeOptional) && !(objectType as MappedType).declaration.nameType); + } - /** - * For a type parameter, return the base constraint of the type parameter. For the string, number, - * boolean, and symbol primitive types, return the corresponding object types. Otherwise return the - * type itself. - */ - function getApparentType(type: Type): Type { - const t = !(type.flags & TypeFlags.Instantiable) ? type : getBaseConstraintOfType(type) || unknownType; - return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) : - t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType) : - t.flags & TypeFlags.StringLike ? globalStringType : - t.flags & TypeFlags.NumberLike ? globalNumberType : - t.flags & TypeFlags.BigIntLike ? getGlobalBigIntType() : - t.flags & TypeFlags.BooleanLike ? globalBooleanType : - t.flags & TypeFlags.ESSymbolLike ? getGlobalESSymbolType() : - t.flags & TypeFlags.NonPrimitive ? emptyObjectType : - t.flags & TypeFlags.Index ? keyofConstraintType : - t.flags & TypeFlags.Unknown && !strictNullChecks ? emptyObjectType : - t; - } - - function getReducedApparentType(type: Type): Type { - // Since getApparentType may return a non-reduced union or intersection type, we need to perform - // type reduction both before and after obtaining the apparent type. For example, given a type parameter - // 'T extends A | B', the type 'T & X' becomes 'A & X | B & X' after obtaining the apparent type, and - // that type may need further reduction to remove empty intersections. - return getReducedType(getApparentType(getReducedType(type))); - } - - function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { - let singleProp: Symbol | undefined; - let propSet: ESMap | undefined; - let indexTypes: Type[] | undefined; - const isUnion = containingType.flags & TypeFlags.Union; - // Flags we want to propagate to the result if they exist in all source symbols - let optionalFlag: SymbolFlags | undefined; - let syntheticFlag = CheckFlags.SyntheticMethod; - let checkFlags = isUnion ? 0 : CheckFlags.Readonly; - let mergedInstantiations = false; - for (const current of containingType.types) { - const type = getApparentType(current); - if (!(isErrorType(type) || type.flags & TypeFlags.Never)) { - const prop = getPropertyOfType(type, name, skipObjectFunctionPropertyAugment); - const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; - if (prop) { - if (prop.flags & SymbolFlags.ClassMember) { - optionalFlag ??= isUnion ? SymbolFlags.None : SymbolFlags.Optional; - if (isUnion) { - optionalFlag |= (prop.flags & SymbolFlags.Optional); - } - else { - optionalFlag &= prop.flags; - } - } - if (!singleProp) { - singleProp = prop; - } - else if (prop !== singleProp) { - const isInstantiation = (getTargetSymbol(prop) || prop) === (getTargetSymbol(singleProp) || singleProp); - // If the symbols are instances of one another with identical types - consider the symbols - // equivalent and just use the first one, which thus allows us to avoid eliding private - // members when intersecting a (this-)instantiations of a class with its raw base or another instance - if (isInstantiation && compareProperties(singleProp, prop, (a, b) => a === b ? Ternary.True : Ternary.False) === Ternary.True) { - // If we merged instantiations of a generic type, we replicate the symbol parent resetting behavior we used - // to do when we recorded multiple distinct symbols so that we still get, eg, `Array.length` printed - // back and not `Array.length` when we're looking at a `.length` access on a `string[] | number[]` - mergedInstantiations = !!singleProp.parent && !!length(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(singleProp.parent)); - } - else { - if (!propSet) { - propSet = new Map(); - propSet.set(getSymbolId(singleProp), singleProp); - } - const id = getSymbolId(prop); - if (!propSet.has(id)) { - propSet.set(id, prop); - } - } - } - if (isUnion && isReadonlySymbol(prop)) { - checkFlags |= CheckFlags.Readonly; - } - else if (!isUnion && !isReadonlySymbol(prop)) { - checkFlags &= ~CheckFlags.Readonly; + /** + * For a type parameter, return the base constraint of the type parameter. For the string, number, + * boolean, and symbol primitive types, return the corresponding object types. Otherwise return the + * type itself. + */ + function getApparentType(type: Type): Type { + const t = !(type.flags & TypeFlags.Instantiable) ? type : getBaseConstraintOfType(type) || unknownType; + return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) : + t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType) : + t.flags & TypeFlags.StringLike ? globalStringType : + t.flags & TypeFlags.NumberLike ? globalNumberType : + t.flags & TypeFlags.BigIntLike ? getGlobalBigIntType() : + t.flags & TypeFlags.BooleanLike ? globalBooleanType : + t.flags & TypeFlags.ESSymbolLike ? getGlobalESSymbolType() : + t.flags & TypeFlags.NonPrimitive ? emptyObjectType : + t.flags & TypeFlags.Index ? keyofConstraintType : + t.flags & TypeFlags.Unknown && !strictNullChecks ? emptyObjectType : + t; + } + + function getReducedApparentType(type: Type): Type { + // Since getApparentType may return a non-reduced union or intersection type, we need to perform + // type reduction both before and after obtaining the apparent type. For example, given a type parameter + // 'T extends A | B', the type 'T & X' becomes 'A & X | B & X' after obtaining the apparent type, and + // that type may need further reduction to remove empty intersections. + return getReducedType(getApparentType(getReducedType(type))); + } + + function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + let singleProp: Symbol | undefined; + let propSet: ESMap | undefined; + let indexTypes: Type[] | undefined; + const isUnion = containingType.flags & TypeFlags.Union; + // Flags we want to propagate to the result if they exist in all source symbols + let optionalFlag: SymbolFlags | undefined; + let syntheticFlag = CheckFlags.SyntheticMethod; + let checkFlags = isUnion ? 0 : CheckFlags.Readonly; + let mergedInstantiations = false; + for (const current of containingType.types) { + const type = getApparentType(current); + if (!(isErrorType(type) || type.flags & TypeFlags.Never)) { + const prop = getPropertyOfType(type, name, skipObjectFunctionPropertyAugment); + const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; + if (prop) { + if (prop.flags & SymbolFlags.ClassMember) { + optionalFlag ??= isUnion ? SymbolFlags.None : SymbolFlags.Optional; + if (isUnion) { + optionalFlag |= (prop.flags & SymbolFlags.Optional); } - checkFlags |= (!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) | - (modifiers & ModifierFlags.Protected ? CheckFlags.ContainsProtected : 0) | - (modifiers & ModifierFlags.Private ? CheckFlags.ContainsPrivate : 0) | - (modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0); - if (!isPrototypeProperty(prop)) { - syntheticFlag = CheckFlags.SyntheticProperty; + else { + optionalFlag &= prop.flags; } } - else if (isUnion) { - const indexInfo = !isLateBoundName(name) && getApplicableIndexInfoForName(type, name); - if (indexInfo) { - checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0); - indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type); - } - else if (isObjectLiteralType(type) && !(getObjectFlags(type) & ObjectFlags.ContainsSpread)) { - checkFlags |= CheckFlags.WritePartial; - indexTypes = append(indexTypes, undefinedType); + if (!singleProp) { + singleProp = prop; + } + else if (prop !== singleProp) { + const isInstantiation = (getTargetSymbol(prop) || prop) === (getTargetSymbol(singleProp) || singleProp); + // If the symbols are instances of one another with identical types - consider the symbols + // equivalent and just use the first one, which thus allows us to avoid eliding private + // members when intersecting a (this-)instantiations of a class with its raw base or another instance + if (isInstantiation && compareProperties(singleProp, prop, (a, b) => a === b ? Ternary.True : Ternary.False) === Ternary.True) { + // If we merged instantiations of a generic type, we replicate the symbol parent resetting behavior we used + // to do when we recorded multiple distinct symbols so that we still get, eg, `Array.length` printed + // back and not `Array.length` when we're looking at a `.length` access on a `string[] | number[]` + mergedInstantiations = !!singleProp.parent && !!length(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(singleProp.parent)); } else { - checkFlags |= CheckFlags.ReadPartial; + if (!propSet) { + propSet = new Map(); + propSet.set(getSymbolId(singleProp), singleProp); + } + const id = getSymbolId(prop); + if (!propSet.has(id)) { + propSet.set(id, prop); + } } } + if (isUnion && isReadonlySymbol(prop)) { + checkFlags |= CheckFlags.Readonly; + } + else if (!isUnion && !isReadonlySymbol(prop)) { + checkFlags &= ~CheckFlags.Readonly; + } + checkFlags |= (!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) | + (modifiers & ModifierFlags.Protected ? CheckFlags.ContainsProtected : 0) | + (modifiers & ModifierFlags.Private ? CheckFlags.ContainsPrivate : 0) | + (modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0); + if (!isPrototypeProperty(prop)) { + syntheticFlag = CheckFlags.SyntheticProperty; + } + } + else if (isUnion) { + const indexInfo = !isLateBoundName(name) && getApplicableIndexInfoForName(type, name); + if (indexInfo) { + checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0); + indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type); + } + else if (isObjectLiteralType(type) && !(getObjectFlags(type) & ObjectFlags.ContainsSpread)) { + checkFlags |= CheckFlags.WritePartial; + indexTypes = append(indexTypes, undefinedType); + } + else { + checkFlags |= CheckFlags.ReadPartial; + } } } - if (!singleProp || - isUnion && - (propSet || checkFlags & CheckFlags.Partial) && - checkFlags & (CheckFlags.ContainsPrivate | CheckFlags.ContainsProtected) && - !(propSet && getCommonDeclarationsOfSymbols(arrayFrom(propSet.values()))) - ) { - // No property was found, or, in a union, a property has a private or protected declaration in one - // constituent, but is missing or has a different declaration in another constituent. - return undefined; + } + if (!singleProp || + isUnion && + (propSet || checkFlags & CheckFlags.Partial) && + checkFlags & (CheckFlags.ContainsPrivate | CheckFlags.ContainsProtected) && + !(propSet && getCommonDeclarationsOfSymbols(arrayFrom(propSet.values()))) + ) { + // No property was found, or, in a union, a property has a private or protected declaration in one + // constituent, but is missing or has a different declaration in another constituent. + return undefined; + } + if (!propSet && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) { + if (mergedInstantiations) { + // No symbol from a union/intersection should have a `.parent` set (since unions/intersections don't act as symbol parents) + // Unless that parent is "reconstituted" from the "first value declaration" on the symbol (which is likely different than its instantiated parent!) + // They also have a `.containingType` set, which affects some services endpoints behavior, like `getRootSymbol` + const clone = createSymbolWithType(singleProp, (singleProp as TransientSymbol).type); + clone.parent = singleProp.valueDeclaration?.symbol?.parent; + clone.containingType = containingType; + clone.mapper = (singleProp as TransientSymbol).mapper; + return clone; } - if (!propSet && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) { - if (mergedInstantiations) { - // No symbol from a union/intersection should have a `.parent` set (since unions/intersections don't act as symbol parents) - // Unless that parent is "reconstituted" from the "first value declaration" on the symbol (which is likely different than its instantiated parent!) - // They also have a `.containingType` set, which affects some services endpoints behavior, like `getRootSymbol` - const clone = createSymbolWithType(singleProp, (singleProp as TransientSymbol).type); - clone.parent = singleProp.valueDeclaration?.symbol?.parent; - clone.containingType = containingType; - clone.mapper = (singleProp as TransientSymbol).mapper; - return clone; - } - else { - return singleProp; - } + else { + return singleProp; + } + } + const props = propSet ? arrayFrom(propSet.values()) : [singleProp]; + let declarations: Declaration[] | undefined; + let firstType: Type | undefined; + let nameType: Type | undefined; + const propTypes: Type[] = []; + let writeTypes: Type[] | undefined; + let firstValueDeclaration: Declaration | undefined; + let hasNonUniformValueDeclaration = false; + for (const prop of props) { + if (!firstValueDeclaration) { + firstValueDeclaration = prop.valueDeclaration; + } + else if (prop.valueDeclaration && prop.valueDeclaration !== firstValueDeclaration) { + hasNonUniformValueDeclaration = true; + } + declarations = addRange(declarations, prop.declarations); + const type = getTypeOfSymbol(prop); + if (!firstType) { + firstType = type; + nameType = getSymbolLinks(prop).nameType; } - const props = propSet ? arrayFrom(propSet.values()) : [singleProp]; - let declarations: Declaration[] | undefined; - let firstType: Type | undefined; - let nameType: Type | undefined; - const propTypes: Type[] = []; - let writeTypes: Type[] | undefined; - let firstValueDeclaration: Declaration | undefined; - let hasNonUniformValueDeclaration = false; - for (const prop of props) { - if (!firstValueDeclaration) { - firstValueDeclaration = prop.valueDeclaration; - } - else if (prop.valueDeclaration && prop.valueDeclaration !== firstValueDeclaration) { - hasNonUniformValueDeclaration = true; - } - declarations = addRange(declarations, prop.declarations); - const type = getTypeOfSymbol(prop); - if (!firstType) { - firstType = type; - nameType = getSymbolLinks(prop).nameType; - } - const writeType = getWriteTypeOfSymbol(prop); - if (writeTypes || writeType !== type) { - writeTypes = append(!writeTypes ? propTypes.slice() : writeTypes, writeType); - } - else if (type !== firstType) { - checkFlags |= CheckFlags.HasNonUniformType; - } - if (isLiteralType(type) || isPatternLiteralType(type) || type === uniqueLiteralType) { - checkFlags |= CheckFlags.HasLiteralType; - } - if (type.flags & TypeFlags.Never && type !== uniqueLiteralType) { - checkFlags |= CheckFlags.HasNeverType; - } - propTypes.push(type); + const writeType = getWriteTypeOfSymbol(prop); + if (writeTypes || writeType !== type) { + writeTypes = append(!writeTypes ? propTypes.slice() : writeTypes, writeType); } - addRange(propTypes, indexTypes); - const result = createSymbol(SymbolFlags.Property | (optionalFlag ?? 0), name, syntheticFlag | checkFlags); - result.containingType = containingType; - if (!hasNonUniformValueDeclaration && firstValueDeclaration) { - result.valueDeclaration = firstValueDeclaration; - - // Inherit information about parent type. - if (firstValueDeclaration.symbol.parent) { - result.parent = firstValueDeclaration.symbol.parent; - } + else if (type !== firstType) { + checkFlags |= CheckFlags.HasNonUniformType; } - - result.declarations = declarations; - result.nameType = nameType; - 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; - result.deferralWriteConstituents = writeTypes; + if (isLiteralType(type) || isPatternLiteralType(type) || type === uniqueLiteralType) { + checkFlags |= CheckFlags.HasLiteralType; } - else { - result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); - if (writeTypes) { - result.writeType = isUnion ? getUnionType(writeTypes) : getIntersectionType(writeTypes); - } + if (type.flags & TypeFlags.Never && type !== uniqueLiteralType) { + checkFlags |= CheckFlags.HasNeverType; } - return result; + propTypes.push(type); } + addRange(propTypes, indexTypes); + const result = createSymbol(SymbolFlags.Property | (optionalFlag ?? 0), name, syntheticFlag | checkFlags); + result.containingType = containingType; + if (!hasNonUniformValueDeclaration && firstValueDeclaration) { + result.valueDeclaration = firstValueDeclaration; - // Return the symbol for a given property in a union or intersection type, or undefined if the property - // does not exist in any constituent type. Note that the returned property may only be present in some - // constituents, in which case the isPartial flag is set when the containing type is union type. We need - // these partial properties when identifying discriminant properties, but otherwise they are filtered out - // and do not appear to be present in the union type. - function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { - let property = type.propertyCacheWithoutObjectFunctionPropertyAugment?.get(name) || - !skipObjectFunctionPropertyAugment ? type.propertyCache?.get(name) : undefined; - if (!property) { - property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); - if (property) { - const properties = skipObjectFunctionPropertyAugment ? - type.propertyCacheWithoutObjectFunctionPropertyAugment ||= createSymbolTable() : - type.propertyCache ||= createSymbolTable(); - properties.set(name, property); - } + // Inherit information about parent type. + if (firstValueDeclaration.symbol.parent) { + result.parent = firstValueDeclaration.symbol.parent; } - return property; } - function getCommonDeclarationsOfSymbols(symbols: readonly Symbol[]) { - let commonDeclarations: Set | undefined; - for (const symbol of symbols) { - if (!symbol.declarations) { - return undefined; - } - if (!commonDeclarations) { - commonDeclarations = new Set(symbol.declarations); - continue; - } - commonDeclarations.forEach(declaration => { - if (!contains(symbol.declarations, declaration)) { - commonDeclarations!.delete(declaration); - } - }); - if (commonDeclarations.size === 0) { - return undefined; - } + result.declarations = declarations; + result.nameType = nameType; + 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; + result.deferralWriteConstituents = writeTypes; + } + else { + result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); + if (writeTypes) { + result.writeType = isUnion ? getUnionType(writeTypes) : getIntersectionType(writeTypes); } - return commonDeclarations; } + return result; + } - function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { - const property = getUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); - // We need to filter out partial properties in union types - return property && !(getCheckFlags(property) & CheckFlags.ReadPartial) ? property : undefined; - } + // Return the symbol for a given property in a union or intersection type, or undefined if the property + // does not exist in any constituent type. Note that the returned property may only be present in some + // constituents, in which case the isPartial flag is set when the containing type is union type. We need + // these partial properties when identifying discriminant properties, but otherwise they are filtered out + // and do not appear to be present in the union type. + function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + let property = type.propertyCacheWithoutObjectFunctionPropertyAugment?.get(name) || + !skipObjectFunctionPropertyAugment ? type.propertyCache?.get(name) : undefined; + if (!property) { + property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); + if (property) { + const properties = skipObjectFunctionPropertyAugment ? + type.propertyCacheWithoutObjectFunctionPropertyAugment ||= createSymbolTable() : + type.propertyCache ||= createSymbolTable(); + properties.set(name, property); + } + } + return property; + } - /** - * Return the reduced form of the given type. For a union type, it is a union of the normalized constituent types. - * For an intersection of types containing one or more mututally exclusive discriminant properties, it is 'never'. - * For all other types, it is simply the type itself. Discriminant properties are considered mutually exclusive when - * no constituent property has type 'never', but the intersection of the constituent property types is 'never'. - */ - function getReducedType(type: Type): Type { - if (type.flags & TypeFlags.Union && (type as UnionType).objectFlags & ObjectFlags.ContainsIntersections) { - return (type as UnionType).resolvedReducedType || ((type as UnionType).resolvedReducedType = getReducedUnionType(type as UnionType)); + function getCommonDeclarationsOfSymbols(symbols: readonly Symbol[]) { + let commonDeclarations: Set | undefined; + for (const symbol of symbols) { + if (!symbol.declarations) { + return undefined; } - else if (type.flags & TypeFlags.Intersection) { - if (!((type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersectionComputed)) { - (type as IntersectionType).objectFlags |= ObjectFlags.IsNeverIntersectionComputed | - (some(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isNeverReducedProperty) ? ObjectFlags.IsNeverIntersection : 0); + if (!commonDeclarations) { + commonDeclarations = new Set(symbol.declarations); + continue; + } + commonDeclarations.forEach(declaration => { + if (!contains(symbol.declarations, declaration)) { + commonDeclarations!.delete(declaration); } - return (type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type; + }); + if (commonDeclarations.size === 0) { + return undefined; } - return type; } + return commonDeclarations; + } - function getReducedUnionType(unionType: UnionType) { - const reducedTypes = sameMap(unionType.types, getReducedType); - if (reducedTypes === unionType.types) { - return unionType; - } - const reduced = getUnionType(reducedTypes); - if (reduced.flags & TypeFlags.Union) { - (reduced as UnionType).resolvedReducedType = reduced; + function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + const property = getUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); + // We need to filter out partial properties in union types + return property && !(getCheckFlags(property) & CheckFlags.ReadPartial) ? property : undefined; + } + + /** + * Return the reduced form of the given type. For a union type, it is a union of the normalized constituent types. + * For an intersection of types containing one or more mututally exclusive discriminant properties, it is 'never'. + * For all other types, it is simply the type itself. Discriminant properties are considered mutually exclusive when + * no constituent property has type 'never', but the intersection of the constituent property types is 'never'. + */ + function getReducedType(type: Type): Type { + if (type.flags & TypeFlags.Union && (type as UnionType).objectFlags & ObjectFlags.ContainsIntersections) { + return (type as UnionType).resolvedReducedType || ((type as UnionType).resolvedReducedType = getReducedUnionType(type as UnionType)); + } + else if (type.flags & TypeFlags.Intersection) { + if (!((type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersectionComputed)) { + (type as IntersectionType).objectFlags |= ObjectFlags.IsNeverIntersectionComputed | + (some(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isNeverReducedProperty) ? ObjectFlags.IsNeverIntersection : 0); } - return reduced; + return (type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type; } + return type; + } - function isNeverReducedProperty(prop: Symbol) { - return isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop); + function getReducedUnionType(unionType: UnionType) { + const reducedTypes = sameMap(unionType.types, getReducedType); + if (reducedTypes === unionType.types) { + return unionType; } - - function isDiscriminantWithNeverType(prop: Symbol) { - // Return true for a synthetic non-optional property with non-uniform types, where at least one is - // a literal type and none is never, that reduces to never. - return !(prop.flags & SymbolFlags.Optional) && - (getCheckFlags(prop) & (CheckFlags.Discriminant | CheckFlags.HasNeverType)) === CheckFlags.Discriminant && - !!(getTypeOfSymbol(prop).flags & TypeFlags.Never); + const reduced = getUnionType(reducedTypes); + if (reduced.flags & TypeFlags.Union) { + (reduced as UnionType).resolvedReducedType = reduced; } + return reduced; + } - function isConflictingPrivateProperty(prop: Symbol) { - // Return true for a synthetic property with multiple declarations, at least one of which is private. - return !prop.valueDeclaration && !!(getCheckFlags(prop) & CheckFlags.ContainsPrivate); - } + function isNeverReducedProperty(prop: Symbol) { + return isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop); + } - function elaborateNeverIntersection(errorInfo: DiagnosticMessageChain | undefined, type: Type) { - if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsNeverIntersection) { - const neverProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isDiscriminantWithNeverType); - if (neverProp) { - return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_has_conflicting_types_in_some_constituents, - typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(neverProp)); - } - const privateProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isConflictingPrivateProperty); - if (privateProp) { - return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_exists_in_multiple_constituents_and_is_private_in_some, - typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(privateProp)); - } + function isDiscriminantWithNeverType(prop: Symbol) { + // Return true for a synthetic non-optional property with non-uniform types, where at least one is + // a literal type and none is never, that reduces to never. + return !(prop.flags & SymbolFlags.Optional) && + (getCheckFlags(prop) & (CheckFlags.Discriminant | CheckFlags.HasNeverType)) === CheckFlags.Discriminant && + !!(getTypeOfSymbol(prop).flags & TypeFlags.Never); + } + + function isConflictingPrivateProperty(prop: Symbol) { + // Return true for a synthetic property with multiple declarations, at least one of which is private. + return !prop.valueDeclaration && !!(getCheckFlags(prop) & CheckFlags.ContainsPrivate); + } + + function elaborateNeverIntersection(errorInfo: DiagnosticMessageChain | undefined, type: Type) { + if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsNeverIntersection) { + const neverProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isDiscriminantWithNeverType); + if (neverProp) { + return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_has_conflicting_types_in_some_constituents, + typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(neverProp)); + } + const privateProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isConflictingPrivateProperty); + if (privateProp) { + return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_exists_in_multiple_constituents_and_is_private_in_some, + typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(privateProp)); } - return errorInfo; } + return errorInfo; + } - /** - * Return the symbol for the property with the given name in the given type. Creates synthetic union properties when - * necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from - * Object and Function as appropriate. - * - * @param type a type to look up property from - * @param name a name of property to look up in a given type - */ - function getPropertyOfType(type: Type, name: __String, skipObjectFunctionPropertyAugment?: boolean, includeTypeOnlyMembers?: boolean): Symbol | undefined { - type = getReducedApparentType(type); - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - const symbol = resolved.members.get(name); - if (symbol && symbolIsValue(symbol, includeTypeOnlyMembers)) { + /** + * Return the symbol for the property with the given name in the given type. Creates synthetic union properties when + * necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from + * Object and Function as appropriate. + * + * @param type a type to look up property from + * @param name a name of property to look up in a given type + */ + function getPropertyOfType(type: Type, name: __String, skipObjectFunctionPropertyAugment?: boolean, includeTypeOnlyMembers?: boolean): Symbol | undefined { + type = getReducedApparentType(type); + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + const symbol = resolved.members.get(name); + if (symbol && symbolIsValue(symbol, includeTypeOnlyMembers)) { + return symbol; + } + if (skipObjectFunctionPropertyAugment) return undefined; + const functionType = resolved === anyFunctionType ? globalFunctionType : + resolved.callSignatures.length ? globalCallableFunctionType : + resolved.constructSignatures.length ? globalNewableFunctionType : + undefined; + if (functionType) { + const symbol = getPropertyOfObjectType(functionType, name); + if (symbol) { return symbol; } - if (skipObjectFunctionPropertyAugment) return undefined; - const functionType = resolved === anyFunctionType ? globalFunctionType : - resolved.callSignatures.length ? globalCallableFunctionType : - resolved.constructSignatures.length ? globalNewableFunctionType : - undefined; - if (functionType) { - const symbol = getPropertyOfObjectType(functionType, name); - if (symbol) { - return symbol; - } - } - return getPropertyOfObjectType(globalObjectType, name); } - if (type.flags & TypeFlags.UnionOrIntersection) { - return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); - } - return undefined; + return getPropertyOfObjectType(globalObjectType, name); } - - function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): readonly Signature[] { - if (type.flags & TypeFlags.StructuredType) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - return kind === SignatureKind.Call ? resolved.callSignatures : resolved.constructSignatures; - } - return emptyArray; + if (type.flags & TypeFlags.UnionOrIntersection) { + return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); } + return undefined; + } - /** - * Return the signatures of the given kind in the given type. Creates synthetic union signatures when necessary and - * maps primitive types and type parameters are to their apparent types. - */ - function getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] { - return getSignaturesOfStructuredType(getReducedApparentType(type), kind); + function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): readonly Signature[] { + if (type.flags & TypeFlags.StructuredType) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + return kind === SignatureKind.Call ? resolved.callSignatures : resolved.constructSignatures; } + return emptyArray; + } - function findIndexInfo(indexInfos: readonly IndexInfo[], keyType: Type) { - return find(indexInfos, info => info.keyType === keyType); - } + /** + * Return the signatures of the given kind in the given type. Creates synthetic union signatures when necessary and + * maps primitive types and type parameters are to their apparent types. + */ + function getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] { + return getSignaturesOfStructuredType(getReducedApparentType(type), kind); + } - function findApplicableIndexInfo(indexInfos: readonly IndexInfo[], keyType: Type) { - // Index signatures for type 'string' are considered only when no other index signatures apply. - let stringIndexInfo: IndexInfo | undefined; - let applicableInfo: IndexInfo | undefined; - let applicableInfos: IndexInfo[] | undefined; - for (const info of indexInfos) { - if (info.keyType === stringType) { - stringIndexInfo = info; + function findIndexInfo(indexInfos: readonly IndexInfo[], keyType: Type) { + return find(indexInfos, info => info.keyType === keyType); + } + + function findApplicableIndexInfo(indexInfos: readonly IndexInfo[], keyType: Type) { + // Index signatures for type 'string' are considered only when no other index signatures apply. + let stringIndexInfo: IndexInfo | undefined; + let applicableInfo: IndexInfo | undefined; + let applicableInfos: IndexInfo[] | undefined; + for (const info of indexInfos) { + if (info.keyType === stringType) { + stringIndexInfo = info; + } + else if (isApplicableIndexType(keyType, info.keyType)) { + if (!applicableInfo) { + applicableInfo = info; } - else if (isApplicableIndexType(keyType, info.keyType)) { - if (!applicableInfo) { - applicableInfo = info; - } - else { - (applicableInfos || (applicableInfos = [applicableInfo])).push(info); - } + else { + (applicableInfos || (applicableInfos = [applicableInfo])).push(info); } } - // When more than one index signature is applicable we create a synthetic IndexInfo. Instead of computing - // the intersected key type, we just use unknownType for the key type as nothing actually depends on the - // keyType property of the returned IndexInfo. - return applicableInfos ? createIndexInfo(unknownType, getIntersectionType(map(applicableInfos, info => info.type)), - reduceLeft(applicableInfos, (isReadonly, info) => isReadonly && info.isReadonly, /*initial*/ true)) : - applicableInfo ? applicableInfo : - stringIndexInfo && isApplicableIndexType(keyType, stringType) ? stringIndexInfo : - undefined; } + // When more than one index signature is applicable we create a synthetic IndexInfo. Instead of computing + // the intersected key type, we just use unknownType for the key type as nothing actually depends on the + // keyType property of the returned IndexInfo. + return applicableInfos ? createIndexInfo(unknownType, getIntersectionType(map(applicableInfos, info => info.type)), + reduceLeft(applicableInfos, (isReadonly, info) => isReadonly && info.isReadonly, /*initial*/ true)) : + applicableInfo ? applicableInfo : + stringIndexInfo && isApplicableIndexType(keyType, stringType) ? stringIndexInfo : + undefined; + } - function isApplicableIndexType(source: Type, target: Type): boolean { - // A 'string' index signature applies to types assignable to 'string' or 'number', and a 'number' index - // signature applies to types assignable to 'number', `${number}` and numeric string literal types. - return isTypeAssignableTo(source, target) || - target === stringType && isTypeAssignableTo(source, numberType) || - target === numberType && (source === numericStringType || !!(source.flags & TypeFlags.StringLiteral) && isNumericLiteralName((source as StringLiteralType).value)); - } + function isApplicableIndexType(source: Type, target: Type): boolean { + // A 'string' index signature applies to types assignable to 'string' or 'number', and a 'number' index + // signature applies to types assignable to 'number', `${number}` and numeric string literal types. + return isTypeAssignableTo(source, target) || + target === stringType && isTypeAssignableTo(source, numberType) || + target === numberType && (source === numericStringType || !!(source.flags & TypeFlags.StringLiteral) && isNumericLiteralName((source as StringLiteralType).value)); + } - function getIndexInfosOfStructuredType(type: Type): readonly IndexInfo[] { - if (type.flags & TypeFlags.StructuredType) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - return resolved.indexInfos; - } - return emptyArray; + function getIndexInfosOfStructuredType(type: Type): readonly IndexInfo[] { + if (type.flags & TypeFlags.StructuredType) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + return resolved.indexInfos; } + return emptyArray; + } - function getIndexInfosOfType(type: Type): readonly IndexInfo[] { - return getIndexInfosOfStructuredType(getReducedApparentType(type)); - } + function getIndexInfosOfType(type: Type): readonly IndexInfo[] { + return getIndexInfosOfStructuredType(getReducedApparentType(type)); + } - // Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and - // maps primitive types and type parameters are to their apparent types. - function getIndexInfoOfType(type: Type, keyType: Type): IndexInfo | undefined { - return findIndexInfo(getIndexInfosOfType(type), keyType); - } + // Return the indexing info of the given kind in the given type. Creates synthetic union index types when necessary and + // maps primitive types and type parameters are to their apparent types. + function getIndexInfoOfType(type: Type, keyType: Type): IndexInfo | undefined { + return findIndexInfo(getIndexInfosOfType(type), keyType); + } - // Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and - // maps primitive types and type parameters are to their apparent types. - function getIndexTypeOfType(type: Type, keyType: Type): Type | undefined { - return getIndexInfoOfType(type, keyType)?.type; - } + // Return the index type of the given kind in the given type. Creates synthetic union index types when necessary and + // maps primitive types and type parameters are to their apparent types. + function getIndexTypeOfType(type: Type, keyType: Type): Type | undefined { + return getIndexInfoOfType(type, keyType)?.type; + } - function getApplicableIndexInfos(type: Type, keyType: Type): IndexInfo[] { - return getIndexInfosOfType(type).filter(info => isApplicableIndexType(keyType, info.keyType)); - } + function getApplicableIndexInfos(type: Type, keyType: Type): IndexInfo[] { + return getIndexInfosOfType(type).filter(info => isApplicableIndexType(keyType, info.keyType)); + } - function getApplicableIndexInfo(type: Type, keyType: Type): IndexInfo | undefined { - return findApplicableIndexInfo(getIndexInfosOfType(type), keyType); - } + function getApplicableIndexInfo(type: Type, keyType: Type): IndexInfo | undefined { + return findApplicableIndexInfo(getIndexInfosOfType(type), keyType); + } - function getApplicableIndexInfoForName(type: Type, name: __String): IndexInfo | undefined { - return getApplicableIndexInfo(type, isLateBoundName(name) ? esSymbolType : getStringLiteralType(unescapeLeadingUnderscores(name))); + function getApplicableIndexInfoForName(type: Type, name: __String): IndexInfo | undefined { + return getApplicableIndexInfo(type, isLateBoundName(name) ? esSymbolType : getStringLiteralType(unescapeLeadingUnderscores(name))); + } + + // Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual + // type checking functions). + function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): readonly TypeParameter[] | undefined { + let result: TypeParameter[] | undefined; + for (const node of getEffectiveTypeParameterDeclarations(declaration)) { + result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol)); } + return result?.length ? result + : isFunctionDeclaration(declaration) ? getSignatureOfTypeTag(declaration)?.typeParameters + : undefined; + } - // Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual - // type checking functions). - function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): readonly TypeParameter[] | undefined { - let result: TypeParameter[] | undefined; - for (const node of getEffectiveTypeParameterDeclarations(declaration)) { - result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol)); + function symbolsToArray(symbols: SymbolTable): Symbol[] { + const result: Symbol[] = []; + symbols.forEach((symbol, id) => { + if (!isReservedMemberName(id)) { + result.push(symbol); } - return result?.length ? result - : isFunctionDeclaration(declaration) ? getSignatureOfTypeTag(declaration)?.typeParameters - : undefined; - } + }); + return result; + } - function symbolsToArray(symbols: SymbolTable): Symbol[] { - const result: Symbol[] = []; - symbols.forEach((symbol, id) => { - if (!isReservedMemberName(id)) { - result.push(symbol); - } - }); - return result; - } + function isJSDocOptionalParameter(node: ParameterDeclaration) { + return isInJSFile(node) && ( + // node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType + node.type && node.type.kind === SyntaxKind.JSDocOptionalType + || getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) => + isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType)); + } - function isJSDocOptionalParameter(node: ParameterDeclaration) { - return isInJSFile(node) && ( - // node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType - node.type && node.type.kind === SyntaxKind.JSDocOptionalType - || getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) => - isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType)); + function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) { + if (isExternalModuleNameRelative(moduleName)) { + return undefined; } + const symbol = getSymbol(globals, '"' + moduleName + '"' as __String, SymbolFlags.ValueModule); + // merged symbol is module declaration symbol combined with all augmentations + return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol; + } - function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) { - if (isExternalModuleNameRelative(moduleName)) { - return undefined; - } - const symbol = getSymbol(globals, '"' + moduleName + '"' as __String, SymbolFlags.ValueModule); - // merged symbol is module declaration symbol combined with all augmentations - return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol; + function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { + if (hasQuestionToken(node) || isOptionalJSDocPropertyLikeTag(node) || isJSDocOptionalParameter(node)) { + return true; } - function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { - if (hasQuestionToken(node) || isOptionalJSDocPropertyLikeTag(node) || isJSDocOptionalParameter(node)) { - return true; - } - - if (node.initializer) { - const signature = getSignatureFromDeclaration(node.parent); - const parameterIndex = node.parent.parameters.indexOf(node); - Debug.assert(parameterIndex >= 0); - // Only consider syntactic or instantiated parameters as optional, not `void` parameters as this function is used - // in grammar checks and checking for `void` too early results in parameter types widening too early - // and causes some noImplicitAny errors to be lost. - return parameterIndex >= getMinArgumentCount(signature, MinArgumentCountFlags.StrongArityForUntypedJS | MinArgumentCountFlags.VoidIsNonOptional); - } - const iife = getImmediatelyInvokedFunctionExpression(node.parent); - if (iife) { - return !node.type && - !node.dotDotDotToken && - node.parent.parameters.indexOf(node) >= iife.arguments.length; - } - - return false; + if (node.initializer) { + const signature = getSignatureFromDeclaration(node.parent); + const parameterIndex = node.parent.parameters.indexOf(node); + Debug.assert(parameterIndex >= 0); + // Only consider syntactic or instantiated parameters as optional, not `void` parameters as this function is used + // in grammar checks and checking for `void` too early results in parameter types widening too early + // and causes some noImplicitAny errors to be lost. + return parameterIndex >= getMinArgumentCount(signature, MinArgumentCountFlags.StrongArityForUntypedJS | MinArgumentCountFlags.VoidIsNonOptional); } - - function isOptionalPropertyDeclaration(node: Declaration) { - return isPropertyDeclaration(node) && !hasAccessorModifier(node) && node.questionToken; + const iife = getImmediatelyInvokedFunctionExpression(node.parent); + if (iife) { + return !node.type && + !node.dotDotDotToken && + node.parent.parameters.indexOf(node) >= iife.arguments.length; } - function createTypePredicate(kind: TypePredicateKind, parameterName: string | undefined, parameterIndex: number | undefined, type: Type | undefined): TypePredicate { - return { kind, parameterName, parameterIndex, type } as TypePredicate; - } + return false; + } - /** - * Gets the minimum number of type arguments needed to satisfy all non-optional type - * parameters. - */ - function getMinTypeArgumentCount(typeParameters: readonly TypeParameter[] | undefined): number { - let minTypeArgumentCount = 0; - if (typeParameters) { - for (let i = 0; i < typeParameters.length; i++) { - if (!hasTypeParameterDefault(typeParameters[i])) { - minTypeArgumentCount = i + 1; - } + function isOptionalPropertyDeclaration(node: Declaration) { + return isPropertyDeclaration(node) && !hasAccessorModifier(node) && node.questionToken; + } + + function createTypePredicate(kind: TypePredicateKind, parameterName: string | undefined, parameterIndex: number | undefined, type: Type | undefined): TypePredicate { + return { kind, parameterName, parameterIndex, type } as TypePredicate; + } + + /** + * Gets the minimum number of type arguments needed to satisfy all non-optional type + * parameters. + */ + function getMinTypeArgumentCount(typeParameters: readonly TypeParameter[] | undefined): number { + let minTypeArgumentCount = 0; + if (typeParameters) { + for (let i = 0; i < typeParameters.length; i++) { + if (!hasTypeParameterDefault(typeParameters[i])) { + minTypeArgumentCount = i + 1; } } - return minTypeArgumentCount; } + return minTypeArgumentCount; + } - /** - * Fill in default types for unsupplied type arguments. If `typeArguments` is undefined - * when a default type is supplied, a new array will be created and returned. - * - * @param typeArguments The supplied type arguments. - * @param typeParameters The requested type parameters. - * @param minTypeArgumentCount The minimum number of required type arguments. - */ - function fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[]; - function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[] | undefined; - function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) { - const numTypeParameters = length(typeParameters); - if (!numTypeParameters) { - return []; - } - const numTypeArguments = length(typeArguments); - if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) { - const result = typeArguments ? typeArguments.slice() : []; - // Map invalid forward references in default types to the error type - for (let i = numTypeArguments; i < numTypeParameters; i++) { - result[i] = errorType; - } - const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny); - for (let i = numTypeArguments; i < numTypeParameters; i++) { - let defaultType = getDefaultFromTypeParameter(typeParameters![i]); - if (isJavaScriptImplicitAny && defaultType && (isTypeIdenticalTo(defaultType, unknownType) || isTypeIdenticalTo(defaultType, emptyObjectType))) { - defaultType = anyType; - } - result[i] = defaultType ? instantiateType(defaultType, createTypeMapper(typeParameters!, result)) : baseDefaultType; - } - result.length = typeParameters!.length; - return result; - } - return typeArguments && typeArguments.slice(); - } - - function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature { - const links = getNodeLinks(declaration); - if (!links.resolvedSignature) { - const parameters: Symbol[] = []; - let flags = SignatureFlags.None; - let minArgumentCount = 0; - let thisParameter: Symbol | undefined; - let hasThisParameter = false; - const iife = getImmediatelyInvokedFunctionExpression(declaration); - const isJSConstructSignature = isJSDocConstructSignature(declaration); - const isUntypedSignatureInJSFile = !iife && - isInJSFile(declaration) && - isValueSignatureDeclaration(declaration) && - !hasJSDocParameterTags(declaration) && - !getJSDocType(declaration); - if (isUntypedSignatureInJSFile) { - flags |= SignatureFlags.IsUntypedSignatureInJSFile; - } - - // If this is a JSDoc construct signature, then skip the first parameter in the - // parameter list. The first parameter represents the return type of the construct - // signature. - for (let i = isJSConstructSignature ? 1 : 0; i < declaration.parameters.length; i++) { - const param = declaration.parameters[i]; - - let paramSymbol = param.symbol; - const type = isJSDocParameterTag(param) ? (param.typeExpression && param.typeExpression.type) : param.type; - // Include parameter symbol instead of property symbol in the signature - if (paramSymbol && !!(paramSymbol.flags & SymbolFlags.Property) && !isBindingPattern(param.name)) { - const resolvedSymbol = resolveName(param, paramSymbol.escapedName, SymbolFlags.Value, undefined, undefined, /*isUse*/ false); - paramSymbol = resolvedSymbol!; - } - if (i === 0 && paramSymbol.escapedName === InternalSymbolName.This) { - hasThisParameter = true; - thisParameter = param.symbol; - } - else { - parameters.push(paramSymbol); - } - - if (type && type.kind === SyntaxKind.LiteralType) { - flags |= SignatureFlags.HasLiteralTypes; - } + /** + * Fill in default types for unsupplied type arguments. If `typeArguments` is undefined + * when a default type is supplied, a new array will be created and returned. + * + * @param typeArguments The supplied type arguments. + * @param typeParameters The requested type parameters. + * @param minTypeArgumentCount The minimum number of required type arguments. + */ + function fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[]; + function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[] | undefined; + function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) { + const numTypeParameters = length(typeParameters); + if (!numTypeParameters) { + return []; + } + const numTypeArguments = length(typeArguments); + if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) { + const result = typeArguments ? typeArguments.slice() : []; + // Map invalid forward references in default types to the error type + for (let i = numTypeArguments; i < numTypeParameters; i++) { + result[i] = errorType; + } + const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny); + for (let i = numTypeArguments; i < numTypeParameters; i++) { + let defaultType = getDefaultFromTypeParameter(typeParameters![i]); + if (isJavaScriptImplicitAny && defaultType && (isTypeIdenticalTo(defaultType, unknownType) || isTypeIdenticalTo(defaultType, emptyObjectType))) { + defaultType = anyType; + } + result[i] = defaultType ? instantiateType(defaultType, createTypeMapper(typeParameters!, result)) : baseDefaultType; + } + result.length = typeParameters!.length; + return result; + } + return typeArguments && typeArguments.slice(); + } - // Record a new minimum argument count if this is not an optional parameter - const isOptionalParameter = isOptionalJSDocPropertyLikeTag(param) || - param.initializer || param.questionToken || isRestParameter(param) || - iife && parameters.length > iife.arguments.length && !type || - isJSDocOptionalParameter(param); - if (!isOptionalParameter) { - minArgumentCount = parameters.length; - } + function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature { + const links = getNodeLinks(declaration); + if (!links.resolvedSignature) { + const parameters: Symbol[] = []; + let flags = SignatureFlags.None; + let minArgumentCount = 0; + let thisParameter: Symbol | undefined; + let hasThisParameter = false; + const iife = getImmediatelyInvokedFunctionExpression(declaration); + const isJSConstructSignature = isJSDocConstructSignature(declaration); + const isUntypedSignatureInJSFile = !iife && + isInJSFile(declaration) && + isValueSignatureDeclaration(declaration) && + !hasJSDocParameterTags(declaration) && + !getJSDocType(declaration); + if (isUntypedSignatureInJSFile) { + flags |= SignatureFlags.IsUntypedSignatureInJSFile; + } + + // If this is a JSDoc construct signature, then skip the first parameter in the + // parameter list. The first parameter represents the return type of the construct + // signature. + for (let i = isJSConstructSignature ? 1 : 0; i < declaration.parameters.length; i++) { + const param = declaration.parameters[i]; + + let paramSymbol = param.symbol; + const type = isJSDocParameterTag(param) ? (param.typeExpression && param.typeExpression.type) : param.type; + // Include parameter symbol instead of property symbol in the signature + if (paramSymbol && !!(paramSymbol.flags & SymbolFlags.Property) && !isBindingPattern(param.name)) { + const resolvedSymbol = resolveName(param, paramSymbol.escapedName, SymbolFlags.Value, undefined, undefined, /*isUse*/ false); + paramSymbol = resolvedSymbol!; + } + if (i === 0 && paramSymbol.escapedName === InternalSymbolName.This) { + hasThisParameter = true; + thisParameter = param.symbol; } - - // If only one accessor includes a this-type annotation, the other behaves as if it had the same type annotation - if ((declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) && - hasBindableName(declaration) && - (!hasThisParameter || !thisParameter)) { - const otherKind = declaration.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; - const other = getDeclarationOfKind(getSymbolOfNode(declaration), otherKind); - if (other) { - thisParameter = getAnnotatedAccessorThisParameter(other); - } + else { + parameters.push(paramSymbol); } - const classType = declaration.kind === SyntaxKind.Constructor ? - getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)) - : undefined; - const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); - if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { - flags |= SignatureFlags.HasRestParameter; + if (type && type.kind === SyntaxKind.LiteralType) { + flags |= SignatureFlags.HasLiteralTypes; } - if (isConstructorTypeNode(declaration) && hasSyntacticModifier(declaration, ModifierFlags.Abstract) || - isConstructorDeclaration(declaration) && hasSyntacticModifier(declaration.parent, ModifierFlags.Abstract)) { - flags |= SignatureFlags.Abstract; + + // Record a new minimum argument count if this is not an optional parameter + const isOptionalParameter = isOptionalJSDocPropertyLikeTag(param) || + param.initializer || param.questionToken || isRestParameter(param) || + iife && parameters.length > iife.arguments.length && !type || + isJSDocOptionalParameter(param); + if (!isOptionalParameter) { + minArgumentCount = parameters.length; } - links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, - /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, - minArgumentCount, flags); } - return links.resolvedSignature; - } - /** - * A JS function gets a synthetic rest parameter if it references `arguments` AND: - * 1. It has no parameters but at least one `@param` with a type that starts with `...` - * OR - * 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...` - */ - function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: Symbol[]): boolean { - if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) { - return false; + // If only one accessor includes a this-type annotation, the other behaves as if it had the same type annotation + if ((declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) && + hasBindableName(declaration) && + (!hasThisParameter || !thisParameter)) { + const otherKind = declaration.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const other = getDeclarationOfKind(getSymbolOfNode(declaration), otherKind); + if (other) { + thisParameter = getAnnotatedAccessorThisParameter(other); + } } - const lastParam = lastOrUndefined(declaration.parameters); - const lastParamTags = lastParam ? getJSDocParameterTags(lastParam) : getJSDocTags(declaration).filter(isJSDocParameterTag); - const lastParamVariadicType = firstDefined(lastParamTags, p => - p.typeExpression && isJSDocVariadicType(p.typeExpression.type) ? p.typeExpression.type : undefined); - const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args" as __String, CheckFlags.RestParameter); - if (lastParamVariadicType) { - // Parameter has effective annotation, lock in type - syntheticArgsSymbol.type = createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)); + const classType = declaration.kind === SyntaxKind.Constructor ? + getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)) + : undefined; + const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); + if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { + flags |= SignatureFlags.HasRestParameter; } - else { - // Parameter has no annotation - // By using a `DeferredType` symbol, we allow the type of this rest arg to be overriden by contextual type assignment so long as its type hasn't been - // cached by `getTypeOfSymbol` yet. - syntheticArgsSymbol.checkFlags |= CheckFlags.DeferredType; - syntheticArgsSymbol.deferralParent = neverType; - syntheticArgsSymbol.deferralConstituents = [anyArrayType]; - syntheticArgsSymbol.deferralWriteConstituents = [anyArrayType]; - } - if (lastParamVariadicType) { - // Replace the last parameter with a rest parameter. - parameters.pop(); - } - parameters.push(syntheticArgsSymbol); - return true; + if (isConstructorTypeNode(declaration) && hasSyntacticModifier(declaration, ModifierFlags.Abstract) || + isConstructorDeclaration(declaration) && hasSyntacticModifier(declaration.parent, ModifierFlags.Abstract)) { + flags |= SignatureFlags.Abstract; + } + links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, + /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, + minArgumentCount, flags); } + return links.resolvedSignature; + } - function getSignatureOfTypeTag(node: SignatureDeclaration | JSDocSignature) { - // should be attached to a function declaration or expression - if (!(isInJSFile(node) && isFunctionLikeDeclaration(node))) return undefined; - const typeTag = getJSDocTypeTag(node); - return typeTag?.typeExpression && getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression)); + /** + * A JS function gets a synthetic rest parameter if it references `arguments` AND: + * 1. It has no parameters but at least one `@param` with a type that starts with `...` + * OR + * 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...` + */ + function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: Symbol[]): boolean { + if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) { + return false; } + const lastParam = lastOrUndefined(declaration.parameters); + const lastParamTags = lastParam ? getJSDocParameterTags(lastParam) : getJSDocTags(declaration).filter(isJSDocParameterTag); + const lastParamVariadicType = firstDefined(lastParamTags, p => + p.typeExpression && isJSDocVariadicType(p.typeExpression.type) ? p.typeExpression.type : undefined); + + const syntheticArgsSymbol = createSymbol(SymbolFlags.Variable, "args" as __String, CheckFlags.RestParameter); + if (lastParamVariadicType) { + // Parameter has effective annotation, lock in type + syntheticArgsSymbol.type = createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)); + } + else { + // Parameter has no annotation + // By using a `DeferredType` symbol, we allow the type of this rest arg to be overriden by contextual type assignment so long as its type hasn't been + // cached by `getTypeOfSymbol` yet. + syntheticArgsSymbol.checkFlags |= CheckFlags.DeferredType; + syntheticArgsSymbol.deferralParent = neverType; + syntheticArgsSymbol.deferralConstituents = [anyArrayType]; + syntheticArgsSymbol.deferralWriteConstituents = [anyArrayType]; + } + if (lastParamVariadicType) { + // Replace the last parameter with a rest parameter. + parameters.pop(); + } + parameters.push(syntheticArgsSymbol); + return true; + } - function getParameterTypeOfTypeTag(func: FunctionLikeDeclaration, parameter: ParameterDeclaration) { - const signature = getSignatureOfTypeTag(func); - if (!signature) return undefined; - const pos = func.parameters.indexOf(parameter); - return parameter.dotDotDotToken ? getRestTypeAtPosition(signature, pos) : getTypeAtPosition(signature, pos); - } + function getSignatureOfTypeTag(node: SignatureDeclaration | JSDocSignature) { + // should be attached to a function declaration or expression + if (!(isInJSFile(node) && isFunctionLikeDeclaration(node))) return undefined; + const typeTag = getJSDocTypeTag(node); + return typeTag?.typeExpression && getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression)); + } - function getReturnTypeOfTypeTag(node: SignatureDeclaration | JSDocSignature) { - const signature = getSignatureOfTypeTag(node); - return signature && getReturnTypeOfSignature(signature); - } + function getParameterTypeOfTypeTag(func: FunctionLikeDeclaration, parameter: ParameterDeclaration) { + const signature = getSignatureOfTypeTag(func); + if (!signature) return undefined; + const pos = func.parameters.indexOf(parameter); + return parameter.dotDotDotToken ? getRestTypeAtPosition(signature, pos) : getTypeAtPosition(signature, pos); + } + + function getReturnTypeOfTypeTag(node: SignatureDeclaration | JSDocSignature) { + const signature = getSignatureOfTypeTag(node); + return signature && getReturnTypeOfSignature(signature); + } - function containsArgumentsReference(declaration: SignatureDeclaration): boolean { - const links = getNodeLinks(declaration); - if (links.containsArgumentsReference === undefined) { - if (links.flags & NodeCheckFlags.CaptureArguments) { - links.containsArgumentsReference = true; - } - else { - links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body!); - } + function containsArgumentsReference(declaration: SignatureDeclaration): boolean { + const links = getNodeLinks(declaration); + if (links.containsArgumentsReference === undefined) { + if (links.flags & NodeCheckFlags.CaptureArguments) { + links.containsArgumentsReference = true; + } + else { + links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body!); } - return links.containsArgumentsReference; + } + return links.containsArgumentsReference; - function traverse(node: Node): boolean { - if (!node) return false; - switch (node.kind) { - case SyntaxKind.Identifier: - return (node as Identifier).escapedText === argumentsSymbol.escapedName && getReferencedValueSymbol(node as Identifier) === argumentsSymbol; + function traverse(node: Node): boolean { + if (!node) return false; + switch (node.kind) { + case SyntaxKind.Identifier: + return (node as Identifier).escapedText === argumentsSymbol.escapedName && getReferencedValueSymbol(node as Identifier) === argumentsSymbol; - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return (node as NamedDeclaration).name!.kind === SyntaxKind.ComputedPropertyName - && traverse((node as NamedDeclaration).name!); + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return (node as NamedDeclaration).name!.kind === SyntaxKind.ComputedPropertyName + && traverse((node as NamedDeclaration).name!); - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return traverse((node as PropertyAccessExpression | ElementAccessExpression).expression); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return traverse((node as PropertyAccessExpression | ElementAccessExpression).expression); - case SyntaxKind.PropertyAssignment: - return traverse((node as PropertyAssignment).initializer); + case SyntaxKind.PropertyAssignment: + return traverse((node as PropertyAssignment).initializer); - default: - return !nodeStartsNewLexicalEnvironment(node) && !isPartOfTypeNode(node) && !!forEachChild(node, traverse); - } + default: + return !nodeStartsNewLexicalEnvironment(node) && !isPartOfTypeNode(node) && !!forEachChild(node, traverse); } } + } - function getSignaturesOfSymbol(symbol: Symbol | undefined): Signature[] { - if (!symbol || !symbol.declarations) return emptyArray; - const result: Signature[] = []; - for (let i = 0; i < symbol.declarations.length; i++) { - const decl = symbol.declarations[i]; - if (!isFunctionLike(decl)) continue; - // Don't include signature if node is the implementation of an overloaded function. A node is considered - // an implementation node if it has a body and the previous node is of the same kind and immediately - // precedes the implementation node (i.e. has the same parent and ends where the implementation starts). - if (i > 0 && (decl as FunctionLikeDeclaration).body) { - const previous = symbol.declarations[i - 1]; - if (decl.parent === previous.parent && decl.kind === previous.kind && decl.pos === previous.end) { - continue; - } + function getSignaturesOfSymbol(symbol: Symbol | undefined): Signature[] { + if (!symbol || !symbol.declarations) return emptyArray; + const result: Signature[] = []; + for (let i = 0; i < symbol.declarations.length; i++) { + const decl = symbol.declarations[i]; + if (!isFunctionLike(decl)) continue; + // Don't include signature if node is the implementation of an overloaded function. A node is considered + // an implementation node if it has a body and the previous node is of the same kind and immediately + // precedes the implementation node (i.e. has the same parent and ends where the implementation starts). + if (i > 0 && (decl as FunctionLikeDeclaration).body) { + const previous = symbol.declarations[i - 1]; + if (decl.parent === previous.parent && decl.kind === previous.kind && decl.pos === previous.end) { + continue; } - // If this is a function or method declaration, get the signature from the @type tag for the sake of optional parameters. - // Exclude contextually-typed kinds because we already apply the @type tag to the context, plus applying it here to the initializer would supress checks that the two are compatible. - result.push( - (!isFunctionExpressionOrArrowFunction(decl) && - !isObjectLiteralMethod(decl) && - getSignatureOfTypeTag(decl)) || - getSignatureFromDeclaration(decl) - ); } - return result; + // If this is a function or method declaration, get the signature from the @type tag for the sake of optional parameters. + // Exclude contextually-typed kinds because we already apply the @type tag to the context, plus applying it here to the initializer would supress checks that the two are compatible. + result.push( + (!isFunctionExpressionOrArrowFunction(decl) && + !isObjectLiteralMethod(decl) && + getSignatureOfTypeTag(decl)) || + getSignatureFromDeclaration(decl) + ); } + return result; + } - function resolveExternalModuleTypeByLiteral(name: StringLiteral) { - const moduleSym = resolveExternalModuleName(name, name); - if (moduleSym) { - const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); - if (resolvedModuleSymbol) { - return getTypeOfSymbol(resolvedModuleSymbol); - } + function resolveExternalModuleTypeByLiteral(name: StringLiteral) { + const moduleSym = resolveExternalModuleName(name, name); + if (moduleSym) { + const resolvedModuleSymbol = resolveExternalModuleSymbol(moduleSym); + if (resolvedModuleSymbol) { + return getTypeOfSymbol(resolvedModuleSymbol); } - - return anyType; } - function getThisTypeOfSignature(signature: Signature): Type | undefined { - if (signature.thisParameter) { - return getTypeOfSymbol(signature.thisParameter); - } + return anyType; + } + + function getThisTypeOfSignature(signature: Signature): Type | undefined { + if (signature.thisParameter) { + return getTypeOfSymbol(signature.thisParameter); } + } - function getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined { - if (!signature.resolvedTypePredicate) { - if (signature.target) { - const targetTypePredicate = getTypePredicateOfSignature(signature.target); - signature.resolvedTypePredicate = targetTypePredicate ? instantiateTypePredicate(targetTypePredicate, signature.mapper!) : noTypePredicate; - } - else if (signature.compositeSignatures) { - signature.resolvedTypePredicate = getUnionOrIntersectionTypePredicate(signature.compositeSignatures, signature.compositeKind) || noTypePredicate; - } - else { - const type = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); - let jsdocPredicate: TypePredicate | undefined; - if (!type) { - const jsdocSignature = getSignatureOfTypeTag(signature.declaration!); - if (jsdocSignature && signature !== jsdocSignature) { - jsdocPredicate = getTypePredicateOfSignature(jsdocSignature); - } + function getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined { + if (!signature.resolvedTypePredicate) { + if (signature.target) { + const targetTypePredicate = getTypePredicateOfSignature(signature.target); + signature.resolvedTypePredicate = targetTypePredicate ? instantiateTypePredicate(targetTypePredicate, signature.mapper!) : noTypePredicate; + } + else if (signature.compositeSignatures) { + signature.resolvedTypePredicate = getUnionOrIntersectionTypePredicate(signature.compositeSignatures, signature.compositeKind) || noTypePredicate; + } + else { + const type = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); + let jsdocPredicate: TypePredicate | undefined; + if (!type) { + const jsdocSignature = getSignatureOfTypeTag(signature.declaration!); + if (jsdocSignature && signature !== jsdocSignature) { + jsdocPredicate = getTypePredicateOfSignature(jsdocSignature); } - signature.resolvedTypePredicate = type && isTypePredicateNode(type) ? - createTypePredicateFromTypePredicateNode(type, signature) : - jsdocPredicate || noTypePredicate; } - Debug.assert(!!signature.resolvedTypePredicate); + signature.resolvedTypePredicate = type && isTypePredicateNode(type) ? + createTypePredicateFromTypePredicateNode(type, signature) : + jsdocPredicate || noTypePredicate; } - return signature.resolvedTypePredicate === noTypePredicate ? undefined : signature.resolvedTypePredicate; + Debug.assert(!!signature.resolvedTypePredicate); } + return signature.resolvedTypePredicate === noTypePredicate ? undefined : signature.resolvedTypePredicate; + } - function createTypePredicateFromTypePredicateNode(node: TypePredicateNode, signature: Signature): TypePredicate { - const parameterName = node.parameterName; - const type = node.type && getTypeFromTypeNode(node.type); - return parameterName.kind === SyntaxKind.ThisType ? - createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsThis : TypePredicateKind.This, /*parameterName*/ undefined, /*parameterIndex*/ undefined, type) : - createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsIdentifier : TypePredicateKind.Identifier, parameterName.escapedText as string, - findIndex(signature.parameters, p => p.escapedName === parameterName.escapedText), type); - } + function createTypePredicateFromTypePredicateNode(node: TypePredicateNode, signature: Signature): TypePredicate { + const parameterName = node.parameterName; + const type = node.type && getTypeFromTypeNode(node.type); + return parameterName.kind === SyntaxKind.ThisType ? + createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsThis : TypePredicateKind.This, /*parameterName*/ undefined, /*parameterIndex*/ undefined, type) : + createTypePredicate(node.assertsModifier ? TypePredicateKind.AssertsIdentifier : TypePredicateKind.Identifier, parameterName.escapedText as string, + findIndex(signature.parameters, p => p.escapedName === parameterName.escapedText), type); + } - function getUnionOrIntersectionType(types: Type[], kind: TypeFlags | undefined, unionReduction?: UnionReduction) { - return kind !== TypeFlags.Intersection ? getUnionType(types, unionReduction) : getIntersectionType(types); - } + function getUnionOrIntersectionType(types: Type[], kind: TypeFlags | undefined, unionReduction?: UnionReduction) { + return kind !== TypeFlags.Intersection ? getUnionType(types, unionReduction) : getIntersectionType(types); + } - function getReturnTypeOfSignature(signature: Signature): Type { - if (!signature.resolvedReturnType) { - if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) { - return errorType; - } - let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper) : - signature.compositeSignatures ? instantiateType(getUnionOrIntersectionType(map(signature.compositeSignatures, getReturnTypeOfSignature), signature.compositeKind, UnionReduction.Subtype), signature.mapper) : - getReturnTypeFromAnnotation(signature.declaration!) || - (nodeIsMissing((signature.declaration as FunctionLikeDeclaration).body) ? anyType : getReturnTypeFromBody(signature.declaration as FunctionLikeDeclaration)); - if (signature.flags & SignatureFlags.IsInnerCallChain) { - type = addOptionalTypeMarker(type); - } - else if (signature.flags & SignatureFlags.IsOuterCallChain) { - type = getOptionalType(type); - } - if (!popTypeResolution()) { - if (signature.declaration) { - const typeNode = getEffectiveReturnTypeNode(signature.declaration); - if (typeNode) { - error(typeNode, Diagnostics.Return_type_annotation_circularly_references_itself); + function getReturnTypeOfSignature(signature: Signature): Type { + if (!signature.resolvedReturnType) { + if (!pushTypeResolution(signature, TypeSystemPropertyName.ResolvedReturnType)) { + return errorType; + } + let type = signature.target ? instantiateType(getReturnTypeOfSignature(signature.target), signature.mapper) : + signature.compositeSignatures ? instantiateType(getUnionOrIntersectionType(map(signature.compositeSignatures, getReturnTypeOfSignature), signature.compositeKind, UnionReduction.Subtype), signature.mapper) : + getReturnTypeFromAnnotation(signature.declaration!) || + (nodeIsMissing((signature.declaration as FunctionLikeDeclaration).body) ? anyType : getReturnTypeFromBody(signature.declaration as FunctionLikeDeclaration)); + if (signature.flags & SignatureFlags.IsInnerCallChain) { + type = addOptionalTypeMarker(type); + } + else if (signature.flags & SignatureFlags.IsOuterCallChain) { + type = getOptionalType(type); + } + if (!popTypeResolution()) { + if (signature.declaration) { + const typeNode = getEffectiveReturnTypeNode(signature.declaration); + if (typeNode) { + error(typeNode, Diagnostics.Return_type_annotation_circularly_references_itself); + } + else if (noImplicitAny) { + const declaration = signature.declaration as Declaration; + const name = getNameOfDeclaration(declaration); + if (name) { + error(name, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, declarationNameToString(name)); } - else if (noImplicitAny) { - const declaration = signature.declaration as Declaration; - const name = getNameOfDeclaration(declaration); - if (name) { - error(name, Diagnostics._0_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions, declarationNameToString(name)); - } - else { - error(declaration, Diagnostics.Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions); - } + else { + error(declaration, Diagnostics.Function_implicitly_has_return_type_any_because_it_does_not_have_a_return_type_annotation_and_is_referenced_directly_or_indirectly_in_one_of_its_return_expressions); } } - type = anyType; } - signature.resolvedReturnType = type; + type = anyType; } - return signature.resolvedReturnType; + signature.resolvedReturnType = type; } + return signature.resolvedReturnType; + } - function getReturnTypeFromAnnotation(declaration: SignatureDeclaration | JSDocSignature) { - if (declaration.kind === SyntaxKind.Constructor) { - return getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)); - } - if (isJSDocConstructSignature(declaration)) { - return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217 - } - const typeNode = getEffectiveReturnTypeNode(declaration); - if (typeNode) { - return getTypeFromTypeNode(typeNode); - } - if (declaration.kind === SyntaxKind.GetAccessor && hasBindableName(declaration)) { - const jsDocType = isInJSFile(declaration) && getTypeForDeclarationFromJSDocComment(declaration); - if (jsDocType) { - return jsDocType; - } - const setter = getDeclarationOfKind(getSymbolOfNode(declaration), SyntaxKind.SetAccessor); - const setterType = getAnnotatedAccessorType(setter); - if (setterType) { - return setterType; - } - } - return getReturnTypeOfTypeTag(declaration); + function getReturnTypeFromAnnotation(declaration: SignatureDeclaration | JSDocSignature) { + if (declaration.kind === SyntaxKind.Constructor) { + return getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)); } - - function isResolvingReturnTypeOfSignature(signature: Signature) { - return !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0; + if (isJSDocConstructSignature(declaration)) { + return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217 } - - function getRestTypeOfSignature(signature: Signature): Type { - return tryGetRestTypeOfSignature(signature) || anyType; + const typeNode = getEffectiveReturnTypeNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); } - - function tryGetRestTypeOfSignature(signature: Signature): Type | undefined { - if (signatureHasRestParameter(signature)) { - const sigRestType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - const restType = isTupleType(sigRestType) ? getRestTypeOfTupleType(sigRestType) : sigRestType; - return restType && getIndexTypeOfType(restType, numberType); + if (declaration.kind === SyntaxKind.GetAccessor && hasBindableName(declaration)) { + const jsDocType = isInJSFile(declaration) && getTypeForDeclarationFromJSDocComment(declaration); + if (jsDocType) { + return jsDocType; + } + const setter = getDeclarationOfKind(getSymbolOfNode(declaration), SyntaxKind.SetAccessor); + const setterType = getAnnotatedAccessorType(setter); + if (setterType) { + return setterType; } - return undefined; } + return getReturnTypeOfTypeTag(declaration); + } - function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): Signature { - const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript)); - if (inferredTypeParameters) { - const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature)); - if (returnSignature) { - const newReturnSignature = cloneSignature(returnSignature); - newReturnSignature.typeParameters = inferredTypeParameters; - const newInstantiatedSignature = cloneSignature(instantiatedSignature); - newInstantiatedSignature.resolvedReturnType = getOrCreateTypeFromSignature(newReturnSignature); - return newInstantiatedSignature; - } - } - return instantiatedSignature; + function isResolvingReturnTypeOfSignature(signature: Signature) { + return !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0; + } + + function getRestTypeOfSignature(signature: Signature): Type { + return tryGetRestTypeOfSignature(signature) || anyType; + } + + function tryGetRestTypeOfSignature(signature: Signature): Type | undefined { + if (signatureHasRestParameter(signature)) { + const sigRestType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + const restType = isTupleType(sigRestType) ? getRestTypeOfTupleType(sigRestType) : sigRestType; + return restType && getIndexTypeOfType(restType, numberType); } + return undefined; + } - function getSignatureInstantiationWithoutFillingInTypeArguments(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { - const instantiations = signature.instantiations || (signature.instantiations = new Map()); - const id = getTypeListId(typeArguments); - let instantiation = instantiations.get(id); - if (!instantiation) { - instantiations.set(id, instantiation = createSignatureInstantiation(signature, typeArguments)); + function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: readonly TypeParameter[]): Signature { + const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript)); + if (inferredTypeParameters) { + const returnSignature = getSingleCallOrConstructSignature(getReturnTypeOfSignature(instantiatedSignature)); + if (returnSignature) { + const newReturnSignature = cloneSignature(returnSignature); + newReturnSignature.typeParameters = inferredTypeParameters; + const newInstantiatedSignature = cloneSignature(instantiatedSignature); + newInstantiatedSignature.resolvedReturnType = getOrCreateTypeFromSignature(newReturnSignature); + return newInstantiatedSignature; } - return instantiation; } + return instantiatedSignature; + } - function createSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { - return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true); + function getSignatureInstantiationWithoutFillingInTypeArguments(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { + const instantiations = signature.instantiations || (signature.instantiations = new Map()); + const id = getTypeListId(typeArguments); + let instantiation = instantiations.get(id); + if (!instantiation) { + instantiations.set(id, instantiation = createSignatureInstantiation(signature, typeArguments)); } + return instantiation; + } - function createSignatureTypeMapper(signature: Signature, typeArguments: readonly Type[] | undefined): TypeMapper { - return createTypeMapper(signature.typeParameters!, typeArguments); - } + function createSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { + return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true); + } - function getErasedSignature(signature: Signature): Signature { - return signature.typeParameters ? - signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(signature)) : - signature; - } + function createSignatureTypeMapper(signature: Signature, typeArguments: readonly Type[] | undefined): TypeMapper { + return createTypeMapper(signature.typeParameters!, typeArguments); + } - function createErasedSignature(signature: Signature) { - // Create an instantiation of the signature where all type arguments are the any type. - return instantiateSignature(signature, createTypeEraser(signature.typeParameters!), /*eraseTypeParameters*/ true); - } + function getErasedSignature(signature: Signature): Signature { + return signature.typeParameters ? + signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(signature)) : + signature; + } - function getCanonicalSignature(signature: Signature): Signature { - return signature.typeParameters ? - signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) : - signature; - } + function createErasedSignature(signature: Signature) { + // Create an instantiation of the signature where all type arguments are the any type. + return instantiateSignature(signature, createTypeEraser(signature.typeParameters!), /*eraseTypeParameters*/ true); + } - function createCanonicalSignature(signature: Signature) { - // Create an instantiation of the signature where each unconstrained type parameter is replaced with - // its original. When a generic class or interface is instantiated, each generic method in the class or - // interface is instantiated with a fresh set of cloned type parameters (which we need to handle scenarios - // where different generations of the same type parameter are in scope). This leads to a lot of new type - // identities, and potentially a lot of work comparing those identities, so here we create an instantiation - // that uses the original type identities for all unconstrained type parameters. - return getSignatureInstantiation( - signature, - map(signature.typeParameters, tp => tp.target && !getConstraintOfTypeParameter(tp.target) ? tp.target : tp), - isInJSFile(signature.declaration)); - } + function getCanonicalSignature(signature: Signature): Signature { + return signature.typeParameters ? + signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) : + signature; + } - function getBaseSignature(signature: Signature) { - const typeParameters = signature.typeParameters; - if (typeParameters) { - if (signature.baseSignatureCache) { - return signature.baseSignatureCache; - } - const typeEraser = createTypeEraser(typeParameters); - const baseConstraintMapper = createTypeMapper(typeParameters, map(typeParameters, tp => getConstraintOfTypeParameter(tp) || unknownType)); - let baseConstraints: readonly Type[] = map(typeParameters, tp => instantiateType(tp, baseConstraintMapper) || unknownType); - // Run N type params thru the immediate constraint mapper up to N times - // This way any noncircular interdependent type parameters are definitely resolved to their external dependencies - for (let i = 0; i < typeParameters.length - 1; i++) { - baseConstraints = instantiateTypes(baseConstraints, baseConstraintMapper); - } - // and then apply a type eraser to remove any remaining circularly dependent type parameters - baseConstraints = instantiateTypes(baseConstraints, typeEraser); - return signature.baseSignatureCache = instantiateSignature(signature, createTypeMapper(typeParameters, baseConstraints), /*eraseTypeParameters*/ true); - } - return signature; - } + function createCanonicalSignature(signature: Signature) { + // Create an instantiation of the signature where each unconstrained type parameter is replaced with + // its original. When a generic class or interface is instantiated, each generic method in the class or + // interface is instantiated with a fresh set of cloned type parameters (which we need to handle scenarios + // where different generations of the same type parameter are in scope). This leads to a lot of new type + // identities, and potentially a lot of work comparing those identities, so here we create an instantiation + // that uses the original type identities for all unconstrained type parameters. + return getSignatureInstantiation( + signature, + map(signature.typeParameters, tp => tp.target && !getConstraintOfTypeParameter(tp.target) ? tp.target : tp), + isInJSFile(signature.declaration)); + } - function getOrCreateTypeFromSignature(signature: Signature): ObjectType { - // There are two ways to declare a construct signature, one is by declaring a class constructor - // using the constructor keyword, and the other is declaring a bare construct signature in an - // object type literal or interface (using the new keyword). Each way of declaring a constructor - // will result in a different declaration kind. - if (!signature.isolatedSignatureType) { - const kind = signature.declaration?.kind; + function getBaseSignature(signature: Signature) { + const typeParameters = signature.typeParameters; + if (typeParameters) { + if (signature.baseSignatureCache) { + return signature.baseSignatureCache; + } + const typeEraser = createTypeEraser(typeParameters); + const baseConstraintMapper = createTypeMapper(typeParameters, map(typeParameters, tp => getConstraintOfTypeParameter(tp) || unknownType)); + let baseConstraints: readonly Type[] = map(typeParameters, tp => instantiateType(tp, baseConstraintMapper) || unknownType); + // Run N type params thru the immediate constraint mapper up to N times + // This way any noncircular interdependent type parameters are definitely resolved to their external dependencies + for (let i = 0; i < typeParameters.length - 1; i++) { + baseConstraints = instantiateTypes(baseConstraints, baseConstraintMapper); + } + // and then apply a type eraser to remove any remaining circularly dependent type parameters + baseConstraints = instantiateTypes(baseConstraints, typeEraser); + return signature.baseSignatureCache = instantiateSignature(signature, createTypeMapper(typeParameters, baseConstraints), /*eraseTypeParameters*/ true); + } + return signature; + } - // If declaration is undefined, it is likely to be the signature of the default constructor. - const isConstructor = kind === undefined || kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType; + function getOrCreateTypeFromSignature(signature: Signature): ObjectType { + // There are two ways to declare a construct signature, one is by declaring a class constructor + // using the constructor keyword, and the other is declaring a bare construct signature in an + // object type literal or interface (using the new keyword). Each way of declaring a constructor + // will result in a different declaration kind. + if (!signature.isolatedSignatureType) { + const kind = signature.declaration?.kind; - const type = createObjectType(ObjectFlags.Anonymous); - type.members = emptySymbols; - type.properties = emptyArray; - type.callSignatures = !isConstructor ? [signature] : emptyArray; - type.constructSignatures = isConstructor ? [signature] : emptyArray; - type.indexInfos = emptyArray; - signature.isolatedSignatureType = type; - } + // If declaration is undefined, it is likely to be the signature of the default constructor. + const isConstructor = kind === undefined || kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType; - return signature.isolatedSignatureType; + const type = createObjectType(ObjectFlags.Anonymous); + type.members = emptySymbols; + type.properties = emptyArray; + type.callSignatures = !isConstructor ? [signature] : emptyArray; + type.constructSignatures = isConstructor ? [signature] : emptyArray; + type.indexInfos = emptyArray; + signature.isolatedSignatureType = type; } - function getIndexSymbol(symbol: Symbol): Symbol | undefined { - return symbol.members ? getIndexSymbolFromSymbolTable(symbol.members) : undefined; - } + return signature.isolatedSignatureType; + } - function getIndexSymbolFromSymbolTable(symbolTable: SymbolTable): Symbol | undefined { - return symbolTable.get(InternalSymbolName.Index); - } + function getIndexSymbol(symbol: Symbol): Symbol | undefined { + return symbol.members ? getIndexSymbolFromSymbolTable(symbol.members) : undefined; + } - function createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo { - return { keyType, type, isReadonly, declaration }; - } + function getIndexSymbolFromSymbolTable(symbolTable: SymbolTable): Symbol | undefined { + return symbolTable.get(InternalSymbolName.Index); + } - function getIndexInfosOfSymbol(symbol: Symbol): IndexInfo[] { - const indexSymbol = getIndexSymbol(symbol); - return indexSymbol ? getIndexInfosOfIndexSymbol(indexSymbol) : emptyArray; - } + function createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo { + return { keyType, type, isReadonly, declaration }; + } - function getIndexInfosOfIndexSymbol(indexSymbol: Symbol): IndexInfo[] { - if (indexSymbol.declarations) { - const indexInfos: IndexInfo[] = []; - for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) { - if (declaration.parameters.length === 1) { - const parameter = declaration.parameters[0]; - if (parameter.type) { - forEachType(getTypeFromTypeNode(parameter.type), keyType => { - if (isValidIndexKeyType(keyType) && !findIndexInfo(indexInfos, keyType)) { - indexInfos.push(createIndexInfo(keyType, declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, - hasEffectiveModifier(declaration, ModifierFlags.Readonly), declaration)); - } - }); - } + function getIndexInfosOfSymbol(symbol: Symbol): IndexInfo[] { + const indexSymbol = getIndexSymbol(symbol); + return indexSymbol ? getIndexInfosOfIndexSymbol(indexSymbol) : emptyArray; + } + + function getIndexInfosOfIndexSymbol(indexSymbol: Symbol): IndexInfo[] { + if (indexSymbol.declarations) { + const indexInfos: IndexInfo[] = []; + for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) { + if (declaration.parameters.length === 1) { + const parameter = declaration.parameters[0]; + if (parameter.type) { + forEachType(getTypeFromTypeNode(parameter.type), keyType => { + if (isValidIndexKeyType(keyType) && !findIndexInfo(indexInfos, keyType)) { + indexInfos.push(createIndexInfo(keyType, declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, + hasEffectiveModifier(declaration, ModifierFlags.Readonly), declaration)); + } + }); } } - return indexInfos; } - return emptyArray; + return indexInfos; } + return emptyArray; + } - function isValidIndexKeyType(type: Type): boolean { - return !!(type.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.ESSymbol)) || isPatternLiteralType(type) || - !!(type.flags & TypeFlags.Intersection) && !isGenericType(type) && some((type as IntersectionType).types, isValidIndexKeyType); - } - - function getConstraintDeclaration(type: TypeParameter): TypeNode | undefined { - return mapDefined(filter(type.symbol && type.symbol.declarations, isTypeParameterDeclaration), getEffectiveConstraintOfTypeParameter)[0]; - } - - function getInferredTypeParameterConstraint(typeParameter: TypeParameter, omitTypeReferences?: boolean) { - let inferences: Type[] | undefined; - if (typeParameter.symbol?.declarations) { - for (const declaration of typeParameter.symbol.declarations) { - if (declaration.parent.kind === SyntaxKind.InferType) { - // When an 'infer T' declaration is immediately contained in a type reference node - // (such as 'Foo'), T's constraint is inferred from the constraint of the - // corresponding type parameter in 'Foo'. When multiple 'infer T' declarations are - // present, we form an intersection of the inferred constraint types. - const [childTypeParameter = declaration.parent, grandParent] = walkUpParenthesizedTypesAndGetParentAndChild(declaration.parent.parent); - if (grandParent.kind === SyntaxKind.TypeReference && !omitTypeReferences) { - const typeReference = grandParent as TypeReferenceNode; - const typeParameters = getTypeParametersForTypeReference(typeReference); - if (typeParameters) { - const index = typeReference.typeArguments!.indexOf(childTypeParameter as TypeNode); - if (index < typeParameters.length) { - const declaredConstraint = getConstraintOfTypeParameter(typeParameters[index]); - if (declaredConstraint) { - // Type parameter constraints can reference other type parameters so - // constraints need to be instantiated. If instantiation produces the - // type parameter itself, we discard that inference. For example, in - // type Foo = [T, U]; - // type Bar = T extends Foo ? Foo : T; - // the instantiated constraint for U is X, so we discard that inference. - const mapper = makeDeferredTypeMapper(typeParameters, typeParameters.map((_, index) => () => { - return getEffectiveTypeArgumentAtIndex(typeReference, typeParameters, index); - })); - const constraint = instantiateType(declaredConstraint, mapper); - if (constraint !== typeParameter) { - inferences = append(inferences, constraint); - } + function isValidIndexKeyType(type: Type): boolean { + return !!(type.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.ESSymbol)) || isPatternLiteralType(type) || + !!(type.flags & TypeFlags.Intersection) && !isGenericType(type) && some((type as IntersectionType).types, isValidIndexKeyType); + } + + function getConstraintDeclaration(type: TypeParameter): TypeNode | undefined { + return mapDefined(filter(type.symbol && type.symbol.declarations, isTypeParameterDeclaration), getEffectiveConstraintOfTypeParameter)[0]; + } + + function getInferredTypeParameterConstraint(typeParameter: TypeParameter, omitTypeReferences?: boolean) { + let inferences: Type[] | undefined; + if (typeParameter.symbol?.declarations) { + for (const declaration of typeParameter.symbol.declarations) { + if (declaration.parent.kind === SyntaxKind.InferType) { + // When an 'infer T' declaration is immediately contained in a type reference node + // (such as 'Foo'), T's constraint is inferred from the constraint of the + // corresponding type parameter in 'Foo'. When multiple 'infer T' declarations are + // present, we form an intersection of the inferred constraint types. + const [childTypeParameter = declaration.parent, grandParent] = walkUpParenthesizedTypesAndGetParentAndChild(declaration.parent.parent); + if (grandParent.kind === SyntaxKind.TypeReference && !omitTypeReferences) { + const typeReference = grandParent as TypeReferenceNode; + const typeParameters = getTypeParametersForTypeReference(typeReference); + if (typeParameters) { + const index = typeReference.typeArguments!.indexOf(childTypeParameter as TypeNode); + if (index < typeParameters.length) { + const declaredConstraint = getConstraintOfTypeParameter(typeParameters[index]); + if (declaredConstraint) { + // Type parameter constraints can reference other type parameters so + // constraints need to be instantiated. If instantiation produces the + // type parameter itself, we discard that inference. For example, in + // type Foo = [T, U]; + // type Bar = T extends Foo ? Foo : T; + // the instantiated constraint for U is X, so we discard that inference. + const mapper = makeDeferredTypeMapper(typeParameters, typeParameters.map((_, index) => () => { + return getEffectiveTypeArgumentAtIndex(typeReference, typeParameters, index); + })); + const constraint = instantiateType(declaredConstraint, mapper); + if (constraint !== typeParameter) { + inferences = append(inferences, constraint); } } } } - // When an 'infer T' declaration is immediately contained in a rest parameter declaration, a rest type - // or a named rest tuple element, we infer an 'unknown[]' constraint. - else if (grandParent.kind === SyntaxKind.Parameter && (grandParent as ParameterDeclaration).dotDotDotToken || - grandParent.kind === SyntaxKind.RestType || - grandParent.kind === SyntaxKind.NamedTupleMember && (grandParent as NamedTupleMember).dotDotDotToken) { - inferences = append(inferences, createArrayType(unknownType)); - } - // When an 'infer T' declaration is immediately contained in a string template type, we infer a 'string' - // constraint. - else if (grandParent.kind === SyntaxKind.TemplateLiteralTypeSpan) { - inferences = append(inferences, stringType); - } - // When an 'infer T' declaration is in the constraint position of a mapped type, we infer a 'keyof any' - // constraint. - else if (grandParent.kind === SyntaxKind.TypeParameter && grandParent.parent.kind === SyntaxKind.MappedType) { - inferences = append(inferences, keyofConstraintType); - } - // When an 'infer T' declaration is the template of a mapped type, and that mapped type is the extends - // clause of a conditional whose check type is also a mapped type, give it a constraint equal to the template - // of the check type's mapped type - else if (grandParent.kind === SyntaxKind.MappedType && (grandParent as MappedTypeNode).type && - skipParentheses((grandParent as MappedTypeNode).type!) === declaration.parent && grandParent.parent.kind === SyntaxKind.ConditionalType && - (grandParent.parent as ConditionalTypeNode).extendsType === grandParent && (grandParent.parent as ConditionalTypeNode).checkType.kind === SyntaxKind.MappedType && - ((grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode).type) { - const checkMappedType = (grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode; - const nodeType = getTypeFromTypeNode(checkMappedType.type!); - inferences = append(inferences, instantiateType(nodeType, - makeUnaryTypeMapper(getDeclaredTypeOfTypeParameter(getSymbolOfNode(checkMappedType.typeParameter)), checkMappedType.typeParameter.constraint ? getTypeFromTypeNode(checkMappedType.typeParameter.constraint) : keyofConstraintType) - )); - } + } + // When an 'infer T' declaration is immediately contained in a rest parameter declaration, a rest type + // or a named rest tuple element, we infer an 'unknown[]' constraint. + else if (grandParent.kind === SyntaxKind.Parameter && (grandParent as ParameterDeclaration).dotDotDotToken || + grandParent.kind === SyntaxKind.RestType || + grandParent.kind === SyntaxKind.NamedTupleMember && (grandParent as NamedTupleMember).dotDotDotToken) { + inferences = append(inferences, createArrayType(unknownType)); + } + // When an 'infer T' declaration is immediately contained in a string template type, we infer a 'string' + // constraint. + else if (grandParent.kind === SyntaxKind.TemplateLiteralTypeSpan) { + inferences = append(inferences, stringType); + } + // When an 'infer T' declaration is in the constraint position of a mapped type, we infer a 'keyof any' + // constraint. + else if (grandParent.kind === SyntaxKind.TypeParameter && grandParent.parent.kind === SyntaxKind.MappedType) { + inferences = append(inferences, keyofConstraintType); + } + // When an 'infer T' declaration is the template of a mapped type, and that mapped type is the extends + // clause of a conditional whose check type is also a mapped type, give it a constraint equal to the template + // of the check type's mapped type + else if (grandParent.kind === SyntaxKind.MappedType && (grandParent as MappedTypeNode).type && + skipParentheses((grandParent as MappedTypeNode).type!) === declaration.parent && grandParent.parent.kind === SyntaxKind.ConditionalType && + (grandParent.parent as ConditionalTypeNode).extendsType === grandParent && (grandParent.parent as ConditionalTypeNode).checkType.kind === SyntaxKind.MappedType && + ((grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode).type) { + const checkMappedType = (grandParent.parent as ConditionalTypeNode).checkType as MappedTypeNode; + const nodeType = getTypeFromTypeNode(checkMappedType.type!); + inferences = append(inferences, instantiateType(nodeType, + makeUnaryTypeMapper(getDeclaredTypeOfTypeParameter(getSymbolOfNode(checkMappedType.typeParameter)), checkMappedType.typeParameter.constraint ? getTypeFromTypeNode(checkMappedType.typeParameter.constraint) : keyofConstraintType) + )); } } } - return inferences && getIntersectionType(inferences); } + return inferences && getIntersectionType(inferences); + } - /** This is a worker function. Use getConstraintOfTypeParameter which guards against circular constraints. */ - function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type | undefined { - if (!typeParameter.constraint) { - if (typeParameter.target) { - const targetConstraint = getConstraintOfTypeParameter(typeParameter.target); - typeParameter.constraint = targetConstraint ? instantiateType(targetConstraint, typeParameter.mapper) : noConstraintType; + /** This is a worker function. Use getConstraintOfTypeParameter which guards against circular constraints. */ + function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type | undefined { + if (!typeParameter.constraint) { + if (typeParameter.target) { + const targetConstraint = getConstraintOfTypeParameter(typeParameter.target); + typeParameter.constraint = targetConstraint ? instantiateType(targetConstraint, typeParameter.mapper) : noConstraintType; + } + else { + const constraintDeclaration = getConstraintDeclaration(typeParameter); + if (!constraintDeclaration) { + typeParameter.constraint = getInferredTypeParameterConstraint(typeParameter) || noConstraintType; } else { - const constraintDeclaration = getConstraintDeclaration(typeParameter); - if (!constraintDeclaration) { - typeParameter.constraint = getInferredTypeParameterConstraint(typeParameter) || noConstraintType; - } - else { - let type = getTypeFromTypeNode(constraintDeclaration); - if (type.flags & TypeFlags.Any && !isErrorType(type)) { // Allow errorType to propegate to keep downstream errors suppressed - // use keyofConstraintType as the base constraint for mapped type key constraints (unknown isn;t assignable to that, but `any` was), - // use unknown otherwise - type = constraintDeclaration.parent.parent.kind === SyntaxKind.MappedType ? keyofConstraintType : unknownType; - } - typeParameter.constraint = type; + let type = getTypeFromTypeNode(constraintDeclaration); + if (type.flags & TypeFlags.Any && !isErrorType(type)) { // Allow errorType to propegate to keep downstream errors suppressed + // use keyofConstraintType as the base constraint for mapped type key constraints (unknown isn;t assignable to that, but `any` was), + // use unknown otherwise + type = constraintDeclaration.parent.parent.kind === SyntaxKind.MappedType ? keyofConstraintType : unknownType; } + typeParameter.constraint = type; } } - return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint; } + return typeParameter.constraint === noConstraintType ? undefined : typeParameter.constraint; + } - function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined { - const tp = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; - const host = isJSDocTemplateTag(tp.parent) ? getEffectiveContainerForJSDocTemplateTag(tp.parent) : tp.parent; - return host && getSymbolOfNode(host); - } + function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined { + const tp = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; + const host = isJSDocTemplateTag(tp.parent) ? getEffectiveContainerForJSDocTemplateTag(tp.parent) : tp.parent; + return host && getSymbolOfNode(host); + } - function getTypeListId(types: readonly Type[] | undefined) { - let result = ""; - if (types) { - const length = types.length; - let i = 0; - while (i < length) { - const startId = types[i].id; - let count = 1; - while (i + count < length && types[i + count].id === startId + count) { - count++; - } - if (result.length) { - result += ","; - } - result += startId; - if (count > 1) { - result += ":" + count; - } - i += count; + function getTypeListId(types: readonly Type[] | undefined) { + let result = ""; + if (types) { + const length = types.length; + let i = 0; + while (i < length) { + const startId = types[i].id; + let count = 1; + while (i + count < length && types[i + count].id === startId + count) { + count++; } - } - return result; - } - - function getAliasId(aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { - return aliasSymbol ? `@${getSymbolId(aliasSymbol)}` + (aliasTypeArguments ? `:${getTypeListId(aliasTypeArguments)}` : "") : ""; - } - - // This function is used to propagate certain flags when creating new object type references and union types. - // It is only necessary to do so if a constituent type might be the undefined type, the null type, the type - // of an object literal or a non-inferrable type. This is because there are operations in the type checker - // that care about the presence of such types at arbitrary depth in a containing type. - function getPropagatingFlagsOfTypes(types: readonly Type[], excludeKinds?: TypeFlags): ObjectFlags { - let result: ObjectFlags = 0; - for (const type of types) { - if (excludeKinds === undefined || !(type.flags & excludeKinds)) { - result |= getObjectFlags(type); + if (result.length) { + result += ","; } + result += startId; + if (count > 1) { + result += ":" + count; + } + i += count; } - return result & ObjectFlags.PropagatingFlags; - } - - function createTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined): TypeReference { - const id = getTypeListId(typeArguments); - let type = target.instantiations.get(id); - if (!type) { - type = createObjectType(ObjectFlags.Reference, target.symbol) as TypeReference; - target.instantiations.set(id, type); - type.objectFlags |= typeArguments ? getPropagatingFlagsOfTypes(typeArguments) : 0; - type.target = target; - type.resolvedTypeArguments = typeArguments; - } - return type; } + return result; + } - function cloneTypeReference(source: TypeReference): TypeReference { - const type = createTypeWithSymbol(source.flags, source.symbol) as TypeReference; - type.objectFlags = source.objectFlags; - type.target = source.target; - type.resolvedTypeArguments = source.resolvedTypeArguments; - return type; - } + function getAliasId(aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { + return aliasSymbol ? `@${getSymbolId(aliasSymbol)}` + (aliasTypeArguments ? `:${getTypeListId(aliasTypeArguments)}` : "") : ""; + } - function createDeferredTypeReference(target: GenericType, node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, mapper?: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): DeferredTypeReference { - if (!aliasSymbol) { - aliasSymbol = getAliasSymbolForTypeNode(node); - const localAliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); - aliasTypeArguments = mapper ? instantiateTypes(localAliasTypeArguments, mapper) : localAliasTypeArguments; + // This function is used to propagate certain flags when creating new object type references and union types. + // It is only necessary to do so if a constituent type might be the undefined type, the null type, the type + // of an object literal or a non-inferrable type. This is because there are operations in the type checker + // that care about the presence of such types at arbitrary depth in a containing type. + function getPropagatingFlagsOfTypes(types: readonly Type[], excludeKinds?: TypeFlags): ObjectFlags { + let result: ObjectFlags = 0; + for (const type of types) { + if (excludeKinds === undefined || !(type.flags & excludeKinds)) { + result |= getObjectFlags(type); } - const type = createObjectType(ObjectFlags.Reference, target.symbol) as DeferredTypeReference; - type.target = target; - type.node = node; - type.mapper = mapper; - type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = aliasTypeArguments; - return type; } + return result & ObjectFlags.PropagatingFlags; + } - function getTypeArguments(type: TypeReference): readonly Type[] { - if (!type.resolvedTypeArguments) { - if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedTypeArguments)) { - return type.target.localTypeParameters?.map(() => errorType) || emptyArray; - } - const node = type.node; - const typeArguments = !node ? emptyArray : - node.kind === SyntaxKind.TypeReference ? concatenate(type.target.outerTypeParameters, getEffectiveTypeArguments(node, type.target.localTypeParameters!)) : - node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : - map(node.elements, getTypeFromTypeNode); - if (popTypeResolution()) { - type.resolvedTypeArguments = type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments; - } - else { - type.resolvedTypeArguments = type.target.localTypeParameters?.map(() => errorType) || emptyArray; - error( - type.node || currentNode, - type.target.symbol ? Diagnostics.Type_arguments_for_0_circularly_reference_themselves : Diagnostics.Tuple_type_arguments_circularly_reference_themselves, - type.target.symbol && symbolToString(type.target.symbol) - ); - } - } - return type.resolvedTypeArguments; + function createTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined): TypeReference { + const id = getTypeListId(typeArguments); + let type = target.instantiations.get(id); + if (!type) { + type = createObjectType(ObjectFlags.Reference, target.symbol) as TypeReference; + target.instantiations.set(id, type); + type.objectFlags |= typeArguments ? getPropagatingFlagsOfTypes(typeArguments) : 0; + type.target = target; + type.resolvedTypeArguments = typeArguments; } + return type; + } - function getTypeReferenceArity(type: TypeReference): number { - return length(type.target.typeParameters); - } + function cloneTypeReference(source: TypeReference): TypeReference { + const type = createTypeWithSymbol(source.flags, source.symbol) as TypeReference; + type.objectFlags = source.objectFlags; + type.target = source.target; + type.resolvedTypeArguments = source.resolvedTypeArguments; + return type; + } + function createDeferredTypeReference(target: GenericType, node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, mapper?: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): DeferredTypeReference { + if (!aliasSymbol) { + aliasSymbol = getAliasSymbolForTypeNode(node); + const localAliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); + aliasTypeArguments = mapper ? instantiateTypes(localAliasTypeArguments, mapper) : localAliasTypeArguments; + } + const type = createObjectType(ObjectFlags.Reference, target.symbol) as DeferredTypeReference; + type.target = target; + type.node = node; + type.mapper = mapper; + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = aliasTypeArguments; + return type; + } - /** - * Get type from type-reference that reference to class or interface - */ - function getTypeFromClassOrInterfaceReference(node: NodeWithTypeArguments, symbol: Symbol): Type { - const type = getDeclaredTypeOfSymbol(getMergedSymbol(symbol)) as InterfaceType; - const typeParameters = type.localTypeParameters; - if (typeParameters) { - const numTypeArguments = length(node.typeArguments); - const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); - const isJs = isInJSFile(node); - const isJsImplicitAny = !noImplicitAny && isJs; - if (!isJsImplicitAny && (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length)) { - const missingAugmentsTag = isJs && isExpressionWithTypeArguments(node) && !isJSDocAugmentsTag(node.parent); - const diag = minTypeArgumentCount === typeParameters.length ? - missingAugmentsTag ? - Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag : - Diagnostics.Generic_type_0_requires_1_type_argument_s : - missingAugmentsTag ? - Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag : - Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments; - - const typeStr = typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType); - error(node, diag, typeStr, minTypeArgumentCount, typeParameters.length); - if (!isJs) { - // TODO: Adopt same permissive behavior in TS as in JS to reduce follow-on editing experience failures (requires editing fillMissingTypeArguments) - return errorType; - } - } - if (node.kind === SyntaxKind.TypeReference && isDeferredTypeReferenceNode(node as TypeReferenceNode, length(node.typeArguments) !== typeParameters.length)) { - return createDeferredTypeReference(type as GenericType, node as TypeReferenceNode, /*mapper*/ undefined); - } - // In a type reference, the outer type parameters of the referenced class or interface are automatically - // supplied as type arguments and the type reference only specifies arguments for the local type parameters - // of the class or interface. - const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(typeArgumentsFromTypeReferenceNode(node), typeParameters, minTypeArgumentCount, isJs)); - return createTypeReference(type as GenericType, typeArguments); + function getTypeArguments(type: TypeReference): readonly Type[] { + if (!type.resolvedTypeArguments) { + if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedTypeArguments)) { + return type.target.localTypeParameters?.map(() => errorType) || emptyArray; } - return checkNoTypeArguments(node, symbol) ? type : errorType; - } - - function getTypeAliasInstantiation(symbol: Symbol, typeArguments: readonly Type[] | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - const type = getDeclaredTypeOfSymbol(symbol); - if (type === intrinsicMarkerType && intrinsicTypeKinds.has(symbol.escapedName as string) && typeArguments && typeArguments.length === 1) { - return getStringMappingType(symbol, typeArguments[0]); + const node = type.node; + const typeArguments = !node ? emptyArray : + node.kind === SyntaxKind.TypeReference ? concatenate(type.target.outerTypeParameters, getEffectiveTypeArguments(node, type.target.localTypeParameters!)) : + node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : + map(node.elements, getTypeFromTypeNode); + if (popTypeResolution()) { + type.resolvedTypeArguments = type.mapper ? instantiateTypes(typeArguments, type.mapper) : typeArguments; } - const links = getSymbolLinks(symbol); - const typeParameters = links.typeParameters!; - const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); - let instantiation = links.instantiations!.get(id); - if (!instantiation) { - links.instantiations!.set(id, instantiation = instantiateTypeWithAlias(type, - createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(symbol.valueDeclaration))), - aliasSymbol, aliasTypeArguments)); + else { + type.resolvedTypeArguments = type.target.localTypeParameters?.map(() => errorType) || emptyArray; + error( + type.node || currentNode, + type.target.symbol ? Diagnostics.Type_arguments_for_0_circularly_reference_themselves : Diagnostics.Tuple_type_arguments_circularly_reference_themselves, + type.target.symbol && symbolToString(type.target.symbol) + ); } - return instantiation; } + return type.resolvedTypeArguments; + } - /** - * Get type from reference to type alias. When a type alias is generic, the declared type of the type alias may include - * references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the - * declared type. Instantiations are cached using the type identities of the type arguments as the key. - */ - function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol): Type { - if (getCheckFlags(symbol) & CheckFlags.Unresolved) { - const typeArguments = typeArgumentsFromTypeReferenceNode(node); - const id = getAliasId(symbol, typeArguments); - let errorType = errorTypes.get(id); - if (!errorType) { - errorType = createIntrinsicType(TypeFlags.Any, "error"); - errorType.aliasSymbol = symbol; - errorType.aliasTypeArguments = typeArguments; - errorTypes.set(id, errorType); - } - return errorType; - } - const type = getDeclaredTypeOfSymbol(symbol); - const typeParameters = getSymbolLinks(symbol).typeParameters; - if (typeParameters) { - const numTypeArguments = length(node.typeArguments); - const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); - if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) { - error(node, - minTypeArgumentCount === typeParameters.length ? - Diagnostics.Generic_type_0_requires_1_type_argument_s : - Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments, - symbolToString(symbol), - minTypeArgumentCount, - typeParameters.length); + function getTypeReferenceArity(type: TypeReference): number { + return length(type.target.typeParameters); + } + + + /** + * Get type from type-reference that reference to class or interface + */ + function getTypeFromClassOrInterfaceReference(node: NodeWithTypeArguments, symbol: Symbol): Type { + const type = getDeclaredTypeOfSymbol(getMergedSymbol(symbol)) as InterfaceType; + const typeParameters = type.localTypeParameters; + if (typeParameters) { + const numTypeArguments = length(node.typeArguments); + const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); + const isJs = isInJSFile(node); + const isJsImplicitAny = !noImplicitAny && isJs; + if (!isJsImplicitAny && (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length)) { + const missingAugmentsTag = isJs && isExpressionWithTypeArguments(node) && !isJSDocAugmentsTag(node.parent); + const diag = minTypeArgumentCount === typeParameters.length ? + missingAugmentsTag ? + Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag : + Diagnostics.Generic_type_0_requires_1_type_argument_s : + missingAugmentsTag ? + Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag : + Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments; + + const typeStr = typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType); + error(node, diag, typeStr, minTypeArgumentCount, typeParameters.length); + if (!isJs) { + // TODO: Adopt same permissive behavior in TS as in JS to reduce follow-on editing experience failures (requires editing fillMissingTypeArguments) return errorType; } - // We refrain from associating a local type alias with an instantiation of a top-level type alias - // because the local alias may end up being referenced in an inferred return type where it is not - // accessible--which in turn may lead to a large structural expansion of the type when generating - // a .d.ts file. See #43622 for an example. - const aliasSymbol = getAliasSymbolForTypeNode(node); - const newAliasSymbol = aliasSymbol && (isLocalTypeAlias(symbol) || !isLocalTypeAlias(aliasSymbol)) ? aliasSymbol : undefined; - return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node), newAliasSymbol, getTypeArgumentsForAliasSymbol(newAliasSymbol)); } - return checkNoTypeArguments(node, symbol) ? type : errorType; - } - - function isLocalTypeAlias(symbol: Symbol) { - const declaration = symbol.declarations?.find(isTypeAlias); - return !!(declaration && getContainingFunction(declaration)); - } - - function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined { - switch (node.kind) { - case SyntaxKind.TypeReference: - return node.typeName; - case SyntaxKind.ExpressionWithTypeArguments: - // We only support expressions that are simple qualified names. For other - // expressions this produces undefined. - const expr = node.expression; - if (isEntityNameExpression(expr)) { - return expr; - } - // fall through; + if (node.kind === SyntaxKind.TypeReference && isDeferredTypeReferenceNode(node as TypeReferenceNode, length(node.typeArguments) !== typeParameters.length)) { + return createDeferredTypeReference(type as GenericType, node as TypeReferenceNode, /*mapper*/ undefined); } - - return undefined; - } - - function getSymbolPath(symbol: Symbol): string { - return symbol.parent ? `${getSymbolPath(symbol.parent)}.${symbol.escapedName}` : symbol.escapedName as string; + // In a type reference, the outer type parameters of the referenced class or interface are automatically + // supplied as type arguments and the type reference only specifies arguments for the local type parameters + // of the class or interface. + const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(typeArgumentsFromTypeReferenceNode(node), typeParameters, minTypeArgumentCount, isJs)); + return createTypeReference(type as GenericType, typeArguments); } + return checkNoTypeArguments(node, symbol) ? type : errorType; + } - function getUnresolvedSymbolForEntityName(name: EntityNameOrEntityNameExpression) { - const identifier = name.kind === SyntaxKind.QualifiedName ? name.right : - name.kind === SyntaxKind.PropertyAccessExpression ? name.name : - name; - const text = identifier.escapedText; - if (text) { - const parentSymbol = name.kind === SyntaxKind.QualifiedName ? getUnresolvedSymbolForEntityName(name.left) : - name.kind === SyntaxKind.PropertyAccessExpression ? getUnresolvedSymbolForEntityName(name.expression) : - undefined; - const path = parentSymbol ? `${getSymbolPath(parentSymbol)}.${text}` : text as string; - let result = unresolvedSymbols.get(path); - if (!result) { - unresolvedSymbols.set(path, result = createSymbol(SymbolFlags.TypeAlias, text, CheckFlags.Unresolved)); - result.parent = parentSymbol; - result.declaredType = unresolvedType; - } - return result; - } - return unknownSymbol; - } + function getTypeAliasInstantiation(symbol: Symbol, typeArguments: readonly Type[] | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + const type = getDeclaredTypeOfSymbol(symbol); + if (type === intrinsicMarkerType && intrinsicTypeKinds.has(symbol.escapedName as string) && typeArguments && typeArguments.length === 1) { + return getStringMappingType(symbol, typeArguments[0]); + } + const links = getSymbolLinks(symbol); + const typeParameters = links.typeParameters!; + const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); + let instantiation = links.instantiations!.get(id); + if (!instantiation) { + links.instantiations!.set(id, instantiation = instantiateTypeWithAlias(type, + createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(symbol.valueDeclaration))), + aliasSymbol, aliasTypeArguments)); + } + return instantiation; + } - function resolveTypeReferenceName(typeReference: TypeReferenceType, meaning: SymbolFlags, ignoreErrors?: boolean) { - const name = getTypeReferenceName(typeReference); - if (!name) { - return unknownSymbol; + /** + * Get type from reference to type alias. When a type alias is generic, the declared type of the type alias may include + * references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the + * declared type. Instantiations are cached using the type identities of the type arguments as the key. + */ + function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol): Type { + if (getCheckFlags(symbol) & CheckFlags.Unresolved) { + const typeArguments = typeArgumentsFromTypeReferenceNode(node); + const id = getAliasId(symbol, typeArguments); + let errorType = errorTypes.get(id); + if (!errorType) { + errorType = createIntrinsicType(TypeFlags.Any, "error"); + errorType.aliasSymbol = symbol; + errorType.aliasTypeArguments = typeArguments; + errorTypes.set(id, errorType); } - const symbol = resolveEntityName(name, meaning, ignoreErrors); - return symbol && symbol !== unknownSymbol ? symbol : - ignoreErrors ? unknownSymbol : getUnresolvedSymbolForEntityName(name); + return errorType; } - - function getTypeReferenceType(node: NodeWithTypeArguments, symbol: Symbol): Type { - if (symbol === unknownSymbol) { + const type = getDeclaredTypeOfSymbol(symbol); + const typeParameters = getSymbolLinks(symbol).typeParameters; + if (typeParameters) { + const numTypeArguments = length(node.typeArguments); + const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); + if (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length) { + error(node, + minTypeArgumentCount === typeParameters.length ? + Diagnostics.Generic_type_0_requires_1_type_argument_s : + Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments, + symbolToString(symbol), + minTypeArgumentCount, + typeParameters.length); return errorType; } - symbol = getExpandoSymbol(symbol) || symbol; - if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - return getTypeFromClassOrInterfaceReference(node, symbol); - } - if (symbol.flags & SymbolFlags.TypeAlias) { - return getTypeFromTypeAliasReference(node, symbol); - } - // Get type from reference to named type that cannot be generic (enum or type parameter) - const res = tryGetDeclaredTypeOfSymbol(symbol); - if (res) { - return checkNoTypeArguments(node, symbol) ? getRegularTypeOfLiteralType(res) : errorType; - } - if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) { - const jsdocType = getTypeFromJSDocValueReference(node, symbol); - if (jsdocType) { - return jsdocType; - } - else { - // Resolve the type reference as a Type for the purpose of reporting errors. - resolveTypeReferenceName(node, SymbolFlags.Type); - return getTypeOfSymbol(symbol); - } - } - return errorType; + // We refrain from associating a local type alias with an instantiation of a top-level type alias + // because the local alias may end up being referenced in an inferred return type where it is not + // accessible--which in turn may lead to a large structural expansion of the type when generating + // a .d.ts file. See #43622 for an example. + const aliasSymbol = getAliasSymbolForTypeNode(node); + const newAliasSymbol = aliasSymbol && (isLocalTypeAlias(symbol) || !isLocalTypeAlias(aliasSymbol)) ? aliasSymbol : undefined; + return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node), newAliasSymbol, getTypeArgumentsForAliasSymbol(newAliasSymbol)); } + return checkNoTypeArguments(node, symbol) ? type : errorType; + } + + function isLocalTypeAlias(symbol: Symbol) { + const declaration = symbol.declarations?.find(isTypeAlias); + return !!(declaration && getContainingFunction(declaration)); + } - /** - * A JSdoc TypeReference may be to a value, but resolve it as a type anyway. - * Example: import('./b').ConstructorFunction - */ - function getTypeFromJSDocValueReference(node: NodeWithTypeArguments, symbol: Symbol): Type | undefined { - const links = getNodeLinks(node); - if (!links.resolvedJSDocType) { - const valueType = getTypeOfSymbol(symbol); - let typeType = valueType; - if (symbol.valueDeclaration) { - const isImportTypeWithQualifier = node.kind === SyntaxKind.ImportType && (node as ImportTypeNode).qualifier; - // valueType might not have a symbol, eg, {import('./b').STRING_LITERAL} - if (valueType.symbol && valueType.symbol !== symbol && isImportTypeWithQualifier) { - typeType = getTypeReferenceType(node, valueType.symbol); - } + function getTypeReferenceName(node: TypeReferenceType): EntityNameOrEntityNameExpression | undefined { + switch (node.kind) { + case SyntaxKind.TypeReference: + return node.typeName; + case SyntaxKind.ExpressionWithTypeArguments: + // We only support expressions that are simple qualified names. For other + // expressions this produces undefined. + const expr = node.expression; + if (isEntityNameExpression(expr)) { + return expr; } - links.resolvedJSDocType = typeType; - } - return links.resolvedJSDocType; + // fall through; } - function getSubstitutionType(baseType: Type, constraint: Type) { - if (constraint.flags & TypeFlags.AnyOrUnknown || constraint === baseType || - !isGenericType(baseType) && !isGenericType(constraint)) { - return baseType; - } - const id = `${getTypeId(baseType)}>${getTypeId(constraint)}`; - const cached = substitutionTypes.get(id); - if (cached) { - return cached; + return undefined; + } + + function getSymbolPath(symbol: Symbol): string { + return symbol.parent ? `${getSymbolPath(symbol.parent)}.${symbol.escapedName}` : symbol.escapedName as string; + } + + function getUnresolvedSymbolForEntityName(name: EntityNameOrEntityNameExpression) { + const identifier = name.kind === SyntaxKind.QualifiedName ? name.right : + name.kind === SyntaxKind.PropertyAccessExpression ? name.name : + name; + const text = identifier.escapedText; + if (text) { + const parentSymbol = name.kind === SyntaxKind.QualifiedName ? getUnresolvedSymbolForEntityName(name.left) : + name.kind === SyntaxKind.PropertyAccessExpression ? getUnresolvedSymbolForEntityName(name.expression) : + undefined; + const path = parentSymbol ? `${getSymbolPath(parentSymbol)}.${text}` : text as string; + let result = unresolvedSymbols.get(path); + if (!result) { + unresolvedSymbols.set(path, result = createSymbol(SymbolFlags.TypeAlias, text, CheckFlags.Unresolved)); + result.parent = parentSymbol; + result.declaredType = unresolvedType; } - const result = createType(TypeFlags.Substitution) as SubstitutionType; - result.baseType = baseType; - result.constraint = constraint; - substitutionTypes.set(id, result); return result; } + return unknownSymbol; + } - function getSubstitutionIntersection(substitutionType: SubstitutionType) { - return getIntersectionType([substitutionType.constraint, substitutionType.baseType]); + function resolveTypeReferenceName(typeReference: TypeReferenceType, meaning: SymbolFlags, ignoreErrors?: boolean) { + const name = getTypeReferenceName(typeReference); + if (!name) { + return unknownSymbol; } + const symbol = resolveEntityName(name, meaning, ignoreErrors); + return symbol && symbol !== unknownSymbol ? symbol : + ignoreErrors ? unknownSymbol : getUnresolvedSymbolForEntityName(name); + } - function isUnaryTupleTypeNode(node: TypeNode) { - return node.kind === SyntaxKind.TupleType && (node as TupleTypeNode).elements.length === 1; + function getTypeReferenceType(node: NodeWithTypeArguments, symbol: Symbol): Type { + if (symbol === unknownSymbol) { + return errorType; } - - function getImpliedConstraint(type: Type, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined { - return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(type, (checkNode as TupleTypeNode).elements[0], (extendsNode as TupleTypeNode).elements[0]) : - getActualTypeVariable(getTypeFromTypeNode(checkNode)) === getActualTypeVariable(type) ? getTypeFromTypeNode(extendsNode) : - undefined; + symbol = getExpandoSymbol(symbol) || symbol; + if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + return getTypeFromClassOrInterfaceReference(node, symbol); } - - function getConditionalFlowTypeOfType(type: Type, node: Node) { - let constraints: Type[] | undefined; - let covariant = true; - while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDoc) { - const parent = node.parent; - // only consider variance flipped by parameter locations - `keyof` types would usually be considered variance inverting, but - // often get used in indexed accesses where they behave sortof invariantly, but our checking is lax - if (parent.kind === SyntaxKind.Parameter) { - covariant = !covariant; - } - // Always substitute on type parameters, regardless of variance, since even - // in contravariant positions, they may rely on substituted constraints to be valid - if ((covariant || type.flags & TypeFlags.TypeVariable) && parent.kind === SyntaxKind.ConditionalType && node === (parent as ConditionalTypeNode).trueType) { - const constraint = getImpliedConstraint(type, (parent as ConditionalTypeNode).checkType, (parent as ConditionalTypeNode).extendsType); - if (constraint) { - constraints = append(constraints, constraint); - } - } - // Given a homomorphic mapped type { [K in keyof T]: XXX }, where T is constrained to an array or tuple type, in the - // template type XXX, K has an added constraint of number | `${number}`. - else if (type.flags & TypeFlags.TypeParameter && parent.kind === SyntaxKind.MappedType && node === (parent as MappedTypeNode).type) { - const mappedType = getTypeFromTypeNode(parent as TypeNode) as MappedType; - if (getTypeParameterFromMappedType(mappedType) === getActualTypeVariable(type)) { - const typeParameter = getHomomorphicTypeVariable(mappedType); - if (typeParameter) { - const constraint = getConstraintOfTypeParameter(typeParameter); - if (constraint && everyType(constraint, isArrayOrTupleType)) { - constraints = append(constraints, getUnionType([numberType, numericStringType])); - } - } - } - } - node = parent; - } - return constraints ? getSubstitutionType(type, getIntersectionType(constraints)) : type; + if (symbol.flags & SymbolFlags.TypeAlias) { + return getTypeFromTypeAliasReference(node, symbol); } - - function isJSDocTypeReference(node: Node): node is TypeReferenceNode { - return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType); + // Get type from reference to named type that cannot be generic (enum or type parameter) + const res = tryGetDeclaredTypeOfSymbol(symbol); + if (res) { + return checkNoTypeArguments(node, symbol) ? getRegularTypeOfLiteralType(res) : errorType; } - - function checkNoTypeArguments(node: NodeWithTypeArguments, symbol?: Symbol) { - if (node.typeArguments) { - error(node, Diagnostics.Type_0_is_not_generic, symbol ? symbolToString(symbol) : (node as TypeReferenceNode).typeName ? declarationNameToString((node as TypeReferenceNode).typeName) : anon); - return false; + if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) { + const jsdocType = getTypeFromJSDocValueReference(node, symbol); + if (jsdocType) { + return jsdocType; + } + else { + // Resolve the type reference as a Type for the purpose of reporting errors. + resolveTypeReferenceName(node, SymbolFlags.Type); + return getTypeOfSymbol(symbol); } - return true; } + return errorType; + } - function getIntendedTypeFromJSDocTypeReference(node: TypeReferenceNode): Type | undefined { - if (isIdentifier(node.typeName)) { - const typeArgs = node.typeArguments; - switch (node.typeName.escapedText) { - case "String": - checkNoTypeArguments(node); - return stringType; - case "Number": - checkNoTypeArguments(node); - return numberType; - case "Boolean": - checkNoTypeArguments(node); - return booleanType; - case "Void": - checkNoTypeArguments(node); - return voidType; - case "Undefined": - checkNoTypeArguments(node); - return undefinedType; - case "Null": - checkNoTypeArguments(node); - return nullType; - case "Function": - case "function": - checkNoTypeArguments(node); - return globalFunctionType; - case "array": - return (!typeArgs || !typeArgs.length) && !noImplicitAny ? anyArrayType : undefined; - case "promise": - return (!typeArgs || !typeArgs.length) && !noImplicitAny ? createPromiseType(anyType) : undefined; - case "Object": - if (typeArgs && typeArgs.length === 2) { - if (isJSDocIndexSignature(node)) { - const indexed = getTypeFromTypeNode(typeArgs[0]); - const target = getTypeFromTypeNode(typeArgs[1]); - const indexInfo = indexed === stringType || indexed === numberType ? [createIndexInfo(indexed, target, /*isReadonly*/ false)] : emptyArray; - return createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, indexInfo); - } - return anyType; - } - checkNoTypeArguments(node); - return !noImplicitAny ? anyType : undefined; + /** + * A JSdoc TypeReference may be to a value, but resolve it as a type anyway. + * Example: import('./b').ConstructorFunction + */ + function getTypeFromJSDocValueReference(node: NodeWithTypeArguments, symbol: Symbol): Type | undefined { + const links = getNodeLinks(node); + if (!links.resolvedJSDocType) { + const valueType = getTypeOfSymbol(symbol); + let typeType = valueType; + if (symbol.valueDeclaration) { + const isImportTypeWithQualifier = node.kind === SyntaxKind.ImportType && (node as ImportTypeNode).qualifier; + // valueType might not have a symbol, eg, {import('./b').STRING_LITERAL} + if (valueType.symbol && valueType.symbol !== symbol && isImportTypeWithQualifier) { + typeType = getTypeReferenceType(node, valueType.symbol); } } + links.resolvedJSDocType = typeType; } + return links.resolvedJSDocType; + } - function getTypeFromJSDocNullableTypeNode(node: JSDocNullableType) { - const type = getTypeFromTypeNode(node.type); - return strictNullChecks ? getNullableType(type, TypeFlags.Null) : type; - } + function getSubstitutionType(baseType: Type, constraint: Type) { + if (constraint.flags & TypeFlags.AnyOrUnknown || constraint === baseType || + !isGenericType(baseType) && !isGenericType(constraint)) { + return baseType; + } + const id = `${getTypeId(baseType)}>${getTypeId(constraint)}`; + const cached = substitutionTypes.get(id); + if (cached) { + return cached; + } + const result = createType(TypeFlags.Substitution) as SubstitutionType; + result.baseType = baseType; + result.constraint = constraint; + substitutionTypes.set(id, result); + return result; + } - function getTypeFromTypeReference(node: TypeReferenceType): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - // handle LS queries on the `const` in `x as const` by resolving to the type of `x` - if (isConstTypeReference(node) && isAssertionExpression(node.parent)) { - links.resolvedSymbol = unknownSymbol; - return links.resolvedType = checkExpressionCached(node.parent.expression); + function getSubstitutionIntersection(substitutionType: SubstitutionType) { + return getIntersectionType([substitutionType.constraint, substitutionType.baseType]); + } + + function isUnaryTupleTypeNode(node: TypeNode) { + return node.kind === SyntaxKind.TupleType && (node as TupleTypeNode).elements.length === 1; + } + + function getImpliedConstraint(type: Type, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined { + return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(type, (checkNode as TupleTypeNode).elements[0], (extendsNode as TupleTypeNode).elements[0]) : + getActualTypeVariable(getTypeFromTypeNode(checkNode)) === getActualTypeVariable(type) ? getTypeFromTypeNode(extendsNode) : + undefined; + } + + function getConditionalFlowTypeOfType(type: Type, node: Node) { + let constraints: Type[] | undefined; + let covariant = true; + while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDoc) { + const parent = node.parent; + // only consider variance flipped by parameter locations - `keyof` types would usually be considered variance inverting, but + // often get used in indexed accesses where they behave sortof invariantly, but our checking is lax + if (parent.kind === SyntaxKind.Parameter) { + covariant = !covariant; + } + // Always substitute on type parameters, regardless of variance, since even + // in contravariant positions, they may rely on substituted constraints to be valid + if ((covariant || type.flags & TypeFlags.TypeVariable) && parent.kind === SyntaxKind.ConditionalType && node === (parent as ConditionalTypeNode).trueType) { + const constraint = getImpliedConstraint(type, (parent as ConditionalTypeNode).checkType, (parent as ConditionalTypeNode).extendsType); + if (constraint) { + constraints = append(constraints, constraint); } - let symbol: Symbol | undefined; - let type: Type | undefined; - const meaning = SymbolFlags.Type; - if (isJSDocTypeReference(node)) { - type = getIntendedTypeFromJSDocTypeReference(node); - if (!type) { - symbol = resolveTypeReferenceName(node, meaning, /*ignoreErrors*/ true); - if (symbol === unknownSymbol) { - symbol = resolveTypeReferenceName(node, meaning | SymbolFlags.Value); - } - else { - resolveTypeReferenceName(node, meaning); // Resolve again to mark errors, if any + } + // Given a homomorphic mapped type { [K in keyof T]: XXX }, where T is constrained to an array or tuple type, in the + // template type XXX, K has an added constraint of number | `${number}`. + else if (type.flags & TypeFlags.TypeParameter && parent.kind === SyntaxKind.MappedType && node === (parent as MappedTypeNode).type) { + const mappedType = getTypeFromTypeNode(parent as TypeNode) as MappedType; + if (getTypeParameterFromMappedType(mappedType) === getActualTypeVariable(type)) { + const typeParameter = getHomomorphicTypeVariable(mappedType); + if (typeParameter) { + const constraint = getConstraintOfTypeParameter(typeParameter); + if (constraint && everyType(constraint, isArrayOrTupleType)) { + constraints = append(constraints, getUnionType([numberType, numericStringType])); } - type = getTypeReferenceType(node, symbol); } } - if (!type) { - symbol = resolveTypeReferenceName(node, meaning); - type = getTypeReferenceType(node, symbol); - } - // Cache both the resolved symbol and the resolved type. The resolved symbol is needed when we check the - // type reference in checkTypeReferenceNode. - links.resolvedSymbol = symbol; - links.resolvedType = type; } - return links.resolvedType; + node = parent; } + return constraints ? getSubstitutionType(type, getIntersectionType(constraints)) : type; + } - function typeArgumentsFromTypeReferenceNode(node: NodeWithTypeArguments): Type[] | undefined { - return map(node.typeArguments, getTypeFromTypeNode); - } + function isJSDocTypeReference(node: Node): node is TypeReferenceNode { + return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType); + } - function getTypeFromTypeQueryNode(node: TypeQueryNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - // TypeScript 1.0 spec (April 2014): 3.6.3 - // The expression is processed as an identifier expression (section 4.3) - // or property access expression(section 4.10), - // the widened type(section 3.9) of which becomes the result. - const type = checkExpressionWithTypeArguments(node); - links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(type)); - } - return links.resolvedType; + function checkNoTypeArguments(node: NodeWithTypeArguments, symbol?: Symbol) { + if (node.typeArguments) { + error(node, Diagnostics.Type_0_is_not_generic, symbol ? symbolToString(symbol) : (node as TypeReferenceNode).typeName ? declarationNameToString((node as TypeReferenceNode).typeName) : anon); + return false; } + return true; + } - function getTypeOfGlobalSymbol(symbol: Symbol | undefined, arity: number): ObjectType { - - function getTypeDeclaration(symbol: Symbol): Declaration | undefined { - const declarations = symbol.declarations; - if (declarations) { - for (const declaration of declarations) { - switch (declaration.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - return declaration; + function getIntendedTypeFromJSDocTypeReference(node: TypeReferenceNode): Type | undefined { + if (isIdentifier(node.typeName)) { + const typeArgs = node.typeArguments; + switch (node.typeName.escapedText) { + case "String": + checkNoTypeArguments(node); + return stringType; + case "Number": + checkNoTypeArguments(node); + return numberType; + case "Boolean": + checkNoTypeArguments(node); + return booleanType; + case "Void": + checkNoTypeArguments(node); + return voidType; + case "Undefined": + checkNoTypeArguments(node); + return undefinedType; + case "Null": + checkNoTypeArguments(node); + return nullType; + case "Function": + case "function": + checkNoTypeArguments(node); + return globalFunctionType; + case "array": + return (!typeArgs || !typeArgs.length) && !noImplicitAny ? anyArrayType : undefined; + case "promise": + return (!typeArgs || !typeArgs.length) && !noImplicitAny ? createPromiseType(anyType) : undefined; + case "Object": + if (typeArgs && typeArgs.length === 2) { + if (isJSDocIndexSignature(node)) { + const indexed = getTypeFromTypeNode(typeArgs[0]); + const target = getTypeFromTypeNode(typeArgs[1]); + const indexInfo = indexed === stringType || indexed === numberType ? [createIndexInfo(indexed, target, /*isReadonly*/ false)] : emptyArray; + return createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, indexInfo); } + return anyType; } - } + checkNoTypeArguments(node); + return !noImplicitAny ? anyType : undefined; } + } + } - if (!symbol) { - return arity ? emptyGenericType : emptyObjectType; + function getTypeFromJSDocNullableTypeNode(node: JSDocNullableType) { + const type = getTypeFromTypeNode(node.type); + return strictNullChecks ? getNullableType(type, TypeFlags.Null) : type; + } + + function getTypeFromTypeReference(node: TypeReferenceType): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + // handle LS queries on the `const` in `x as const` by resolving to the type of `x` + if (isConstTypeReference(node) && isAssertionExpression(node.parent)) { + links.resolvedSymbol = unknownSymbol; + return links.resolvedType = checkExpressionCached(node.parent.expression); } - const type = getDeclaredTypeOfSymbol(symbol); - if (!(type.flags & TypeFlags.Object)) { - error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_be_a_class_or_interface_type, symbolName(symbol)); - return arity ? emptyGenericType : emptyObjectType; + let symbol: Symbol | undefined; + let type: Type | undefined; + const meaning = SymbolFlags.Type; + if (isJSDocTypeReference(node)) { + type = getIntendedTypeFromJSDocTypeReference(node); + if (!type) { + symbol = resolveTypeReferenceName(node, meaning, /*ignoreErrors*/ true); + if (symbol === unknownSymbol) { + symbol = resolveTypeReferenceName(node, meaning | SymbolFlags.Value); + } + else { + resolveTypeReferenceName(node, meaning); // Resolve again to mark errors, if any + } + type = getTypeReferenceType(node, symbol); + } } - if (length((type as InterfaceType).typeParameters) !== arity) { - error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); - return arity ? emptyGenericType : emptyObjectType; + if (!type) { + symbol = resolveTypeReferenceName(node, meaning); + type = getTypeReferenceType(node, symbol); } - return type as ObjectType; + // Cache both the resolved symbol and the resolved type. The resolved symbol is needed when we check the + // type reference in checkTypeReferenceNode. + links.resolvedSymbol = symbol; + links.resolvedType = type; } + return links.resolvedType; + } - function getGlobalValueSymbol(name: __String, reportErrors: boolean): Symbol | undefined { - return getGlobalSymbol(name, SymbolFlags.Value, reportErrors ? Diagnostics.Cannot_find_global_value_0 : undefined); - } + function typeArgumentsFromTypeReferenceNode(node: NodeWithTypeArguments): Type[] | undefined { + return map(node.typeArguments, getTypeFromTypeNode); + } - function getGlobalTypeSymbol(name: __String, reportErrors: boolean): Symbol | undefined { - return getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); + function getTypeFromTypeQueryNode(node: TypeQueryNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + // TypeScript 1.0 spec (April 2014): 3.6.3 + // The expression is processed as an identifier expression (section 4.3) + // or property access expression(section 4.10), + // the widened type(section 3.9) of which becomes the result. + const type = checkExpressionWithTypeArguments(node); + links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(type)); } + return links.resolvedType; + } - function getGlobalTypeAliasSymbol(name: __String, arity: number, reportErrors: boolean): Symbol | undefined { - const symbol = getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); - if (symbol) { - // Resolve the declared type of the symbol. This resolves type parameters for the type - // alias so that we can check arity. - getDeclaredTypeOfSymbol(symbol); - if (length(getSymbolLinks(symbol).typeParameters) !== arity) { - const decl = symbol.declarations && find(symbol.declarations, isTypeAliasDeclaration); - error(decl, Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); - return undefined; + function getTypeOfGlobalSymbol(symbol: Symbol | undefined, arity: number): ObjectType { + + function getTypeDeclaration(symbol: Symbol): Declaration | undefined { + const declarations = symbol.declarations; + if (declarations) { + for (const declaration of declarations) { + switch (declaration.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + return declaration; + } } } - return symbol; } - function getGlobalSymbol(name: __String, meaning: SymbolFlags, diagnostic: DiagnosticMessage | undefined): Symbol | undefined { - // Don't track references for global symbols anyway, so value if `isReference` is arbitrary - return resolveName(undefined, name, meaning, diagnostic, name, /*isUse*/ false, /*excludeGlobals*/ false, /*getSpellingSuggestions*/ false); + if (!symbol) { + return arity ? emptyGenericType : emptyObjectType; } - - function getGlobalType(name: __String, arity: 0, reportErrors: true): ObjectType; - function getGlobalType(name: __String, arity: 0, reportErrors: boolean): ObjectType | undefined; - function getGlobalType(name: __String, arity: number, reportErrors: true): GenericType; - function getGlobalType(name: __String, arity: number, reportErrors: boolean): GenericType | undefined; - function getGlobalType(name: __String, arity: number, reportErrors: boolean): ObjectType | undefined { - const symbol = getGlobalTypeSymbol(name, reportErrors); - return symbol || reportErrors ? getTypeOfGlobalSymbol(symbol, arity) : undefined; + const type = getDeclaredTypeOfSymbol(symbol); + if (!(type.flags & TypeFlags.Object)) { + error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_be_a_class_or_interface_type, symbolName(symbol)); + return arity ? emptyGenericType : emptyObjectType; } - - function getGlobalTypedPropertyDescriptorType() { - // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times - return deferredGlobalTypedPropertyDescriptorType ||= getGlobalType("TypedPropertyDescriptor" as __String, /*arity*/ 1, /*reportErrors*/ true) || emptyGenericType; + if (length((type as InterfaceType).typeParameters) !== arity) { + error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); + return arity ? emptyGenericType : emptyObjectType; } + return type as ObjectType; + } - function getGlobalTemplateStringsArrayType() { - // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times - return deferredGlobalTemplateStringsArrayType ||= getGlobalType("TemplateStringsArray" as __String, /*arity*/ 0, /*reportErrors*/ true) || emptyObjectType; - } + function getGlobalValueSymbol(name: __String, reportErrors: boolean): Symbol | undefined { + return getGlobalSymbol(name, SymbolFlags.Value, reportErrors ? Diagnostics.Cannot_find_global_value_0 : undefined); + } + + function getGlobalTypeSymbol(name: __String, reportErrors: boolean): Symbol | undefined { + return getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); + } - function getGlobalImportMetaType() { - // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times - return deferredGlobalImportMetaType ||= getGlobalType("ImportMeta" as __String, /*arity*/ 0, /*reportErrors*/ true) || emptyObjectType; + function getGlobalTypeAliasSymbol(name: __String, arity: number, reportErrors: boolean): Symbol | undefined { + const symbol = getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); + if (symbol) { + // Resolve the declared type of the symbol. This resolves type parameters for the type + // alias so that we can check arity. + getDeclaredTypeOfSymbol(symbol); + if (length(getSymbolLinks(symbol).typeParameters) !== arity) { + const decl = symbol.declarations && find(symbol.declarations, isTypeAliasDeclaration); + error(decl, Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); + return undefined; + } } + return symbol; + } - function getGlobalImportMetaExpressionType() { - if (!deferredGlobalImportMetaExpressionType) { - // Create a synthetic type `ImportMetaExpression { meta: MetaProperty }` - const symbol = createSymbol(SymbolFlags.None, "ImportMetaExpression" as __String); - const importMetaType = getGlobalImportMetaType(); + function getGlobalSymbol(name: __String, meaning: SymbolFlags, diagnostic: DiagnosticMessage | undefined): Symbol | undefined { + // Don't track references for global symbols anyway, so value if `isReference` is arbitrary + return resolveName(undefined, name, meaning, diagnostic, name, /*isUse*/ false, /*excludeGlobals*/ false, /*getSpellingSuggestions*/ false); + } - const metaPropertySymbol = createSymbol(SymbolFlags.Property, "meta" as __String, CheckFlags.Readonly); - metaPropertySymbol.parent = symbol; - metaPropertySymbol.type = importMetaType; + function getGlobalType(name: __String, arity: 0, reportErrors: true): ObjectType; + function getGlobalType(name: __String, arity: 0, reportErrors: boolean): ObjectType | undefined; + function getGlobalType(name: __String, arity: number, reportErrors: true): GenericType; + function getGlobalType(name: __String, arity: number, reportErrors: boolean): GenericType | undefined; + function getGlobalType(name: __String, arity: number, reportErrors: boolean): ObjectType | undefined { + const symbol = getGlobalTypeSymbol(name, reportErrors); + return symbol || reportErrors ? getTypeOfGlobalSymbol(symbol, arity) : undefined; + } - const members = createSymbolTable([metaPropertySymbol]); - symbol.members = members; + function getGlobalTypedPropertyDescriptorType() { + // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times + return deferredGlobalTypedPropertyDescriptorType ||= getGlobalType("TypedPropertyDescriptor" as __String, /*arity*/ 1, /*reportErrors*/ true) || emptyGenericType; + } - deferredGlobalImportMetaExpressionType = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); - } - return deferredGlobalImportMetaExpressionType; - } + function getGlobalTemplateStringsArrayType() { + // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times + return deferredGlobalTemplateStringsArrayType ||= getGlobalType("TemplateStringsArray" as __String, /*arity*/ 0, /*reportErrors*/ true) || emptyObjectType; + } - function getGlobalImportCallOptionsType(reportErrors: boolean) { - return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; - } + function getGlobalImportMetaType() { + // We always report an error, so store a result in the event we could not resolve the symbol to prevent reporting it multiple times + return deferredGlobalImportMetaType ||= getGlobalType("ImportMeta" as __String, /*arity*/ 0, /*reportErrors*/ true) || emptyObjectType; + } - function getGlobalESSymbolConstructorSymbol(reportErrors: boolean): Symbol | undefined { - return deferredGlobalESSymbolConstructorSymbol ||= getGlobalValueSymbol("Symbol" as __String, reportErrors); - } + function getGlobalImportMetaExpressionType() { + if (!deferredGlobalImportMetaExpressionType) { + // Create a synthetic type `ImportMetaExpression { meta: MetaProperty }` + const symbol = createSymbol(SymbolFlags.None, "ImportMetaExpression" as __String); + const importMetaType = getGlobalImportMetaType(); - function getGlobalESSymbolConstructorTypeSymbol(reportErrors: boolean): Symbol | undefined { - return deferredGlobalESSymbolConstructorTypeSymbol ||= getGlobalTypeSymbol("SymbolConstructor" as __String, reportErrors); - } + const metaPropertySymbol = createSymbol(SymbolFlags.Property, "meta" as __String, CheckFlags.Readonly); + metaPropertySymbol.parent = symbol; + metaPropertySymbol.type = importMetaType; - function getGlobalESSymbolType() { - return (deferredGlobalESSymbolType ||= getGlobalType("Symbol" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; - } + const members = createSymbolTable([metaPropertySymbol]); + symbol.members = members; - function getGlobalPromiseType(reportErrors: boolean) { - return (deferredGlobalPromiseType ||= getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + deferredGlobalImportMetaExpressionType = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); } + return deferredGlobalImportMetaExpressionType; + } - function getGlobalPromiseLikeType(reportErrors: boolean) { - return (deferredGlobalPromiseLikeType ||= getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalImportCallOptionsType(reportErrors: boolean) { + return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } - function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined { - return deferredGlobalPromiseConstructorSymbol ||= getGlobalValueSymbol("Promise" as __String, reportErrors); - } + function getGlobalESSymbolConstructorSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalESSymbolConstructorSymbol ||= getGlobalValueSymbol("Symbol" as __String, reportErrors); + } - function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { - return (deferredGlobalPromiseConstructorLikeType ||= getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; - } + function getGlobalESSymbolConstructorTypeSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalESSymbolConstructorTypeSymbol ||= getGlobalTypeSymbol("SymbolConstructor" as __String, reportErrors); + } - function getGlobalAsyncIterableType(reportErrors: boolean) { - return (deferredGlobalAsyncIterableType ||= getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalESSymbolType() { + return (deferredGlobalESSymbolType ||= getGlobalType("Symbol" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; + } - function getGlobalAsyncIteratorType(reportErrors: boolean) { - return (deferredGlobalAsyncIteratorType ||= getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } + function getGlobalPromiseType(reportErrors: boolean) { + return (deferredGlobalPromiseType ||= getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { - return (deferredGlobalAsyncIterableIteratorType ||= getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalPromiseLikeType(reportErrors: boolean) { + return (deferredGlobalPromiseLikeType ||= getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalAsyncGeneratorType(reportErrors: boolean) { - return (deferredGlobalAsyncGeneratorType ||= getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } + function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalPromiseConstructorSymbol ||= getGlobalValueSymbol("Promise" as __String, reportErrors); + } - function getGlobalIterableType(reportErrors: boolean) { - return (deferredGlobalIterableType ||= getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { + return (deferredGlobalPromiseConstructorLikeType ||= getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } - function getGlobalIteratorType(reportErrors: boolean) { - return (deferredGlobalIteratorType ||= getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } + function getGlobalAsyncIterableType(reportErrors: boolean) { + return (deferredGlobalAsyncIterableType ||= getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalIterableIteratorType(reportErrors: boolean) { - return (deferredGlobalIterableIteratorType ||= getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalAsyncIteratorType(reportErrors: boolean) { + return (deferredGlobalAsyncIteratorType ||= getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } - function getGlobalGeneratorType(reportErrors: boolean) { - return (deferredGlobalGeneratorType ||= getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; - } + function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { + return (deferredGlobalAsyncIterableIteratorType ||= getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalIteratorYieldResultType(reportErrors: boolean) { - return (deferredGlobalIteratorYieldResultType ||= getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalAsyncGeneratorType(reportErrors: boolean) { + return (deferredGlobalAsyncGeneratorType ||= getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } - function getGlobalIteratorReturnResultType(reportErrors: boolean) { - return (deferredGlobalIteratorReturnResultType ||= getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; - } + function getGlobalIterableType(reportErrors: boolean) { + return (deferredGlobalIterableType ||= getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { - const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); - return symbol && getTypeOfGlobalSymbol(symbol, arity) as GenericType; - } + function getGlobalIteratorType(reportErrors: boolean) { + return (deferredGlobalIteratorType ||= getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } - function getGlobalExtractSymbol(): Symbol | undefined { - // We always report an error, so cache a result in the event we could not resolve the symbol to prevent reporting it multiple times - deferredGlobalExtractSymbol ||= getGlobalTypeAliasSymbol("Extract" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; - return deferredGlobalExtractSymbol === unknownSymbol ? undefined : deferredGlobalExtractSymbol; - } + function getGlobalIterableIteratorType(reportErrors: boolean) { + return (deferredGlobalIterableIteratorType ||= getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalOmitSymbol(): Symbol | undefined { - // We always report an error, so cache a result in the event we could not resolve the symbol to prevent reporting it multiple times - deferredGlobalOmitSymbol ||= getGlobalTypeAliasSymbol("Omit" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; - return deferredGlobalOmitSymbol === unknownSymbol ? undefined : deferredGlobalOmitSymbol; - } + function getGlobalGeneratorType(reportErrors: boolean) { + return (deferredGlobalGeneratorType ||= getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } - function getGlobalAwaitedSymbol(reportErrors: boolean): Symbol | undefined { - // Only cache `unknownSymbol` if we are reporting errors so that we don't report the error more than once. - deferredGlobalAwaitedSymbol ||= getGlobalTypeAliasSymbol("Awaited" as __String, /*arity*/ 1, reportErrors) || (reportErrors ? unknownSymbol : undefined); - return deferredGlobalAwaitedSymbol === unknownSymbol ? undefined : deferredGlobalAwaitedSymbol; - } + function getGlobalIteratorYieldResultType(reportErrors: boolean) { + return (deferredGlobalIteratorYieldResultType ||= getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalBigIntType() { - return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; - } + function getGlobalIteratorReturnResultType(reportErrors: boolean) { + return (deferredGlobalIteratorReturnResultType ||= getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } - function getGlobalNaNSymbol(): Symbol | undefined { - return (deferredGlobalNaNSymbol ||= getGlobalValueSymbol("NaN" as __String, /*reportErrors*/ false)); - } + function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { + const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); + return symbol && getTypeOfGlobalSymbol(symbol, arity) as GenericType; + } - function getGlobalRecordSymbol(): Symbol | undefined { - deferredGlobalRecordSymbol ||= getGlobalTypeAliasSymbol("Record" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; - return deferredGlobalRecordSymbol === unknownSymbol ? undefined : deferredGlobalRecordSymbol; - } + function getGlobalExtractSymbol(): Symbol | undefined { + // We always report an error, so cache a result in the event we could not resolve the symbol to prevent reporting it multiple times + deferredGlobalExtractSymbol ||= getGlobalTypeAliasSymbol("Extract" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; + return deferredGlobalExtractSymbol === unknownSymbol ? undefined : deferredGlobalExtractSymbol; + } - /** - * Instantiates a global type that is generic with some element type, and returns that instantiation. - */ - function createTypeFromGenericGlobalType(genericGlobalType: GenericType, typeArguments: readonly Type[]): ObjectType { - return genericGlobalType !== emptyGenericType ? createTypeReference(genericGlobalType, typeArguments) : emptyObjectType; - } + function getGlobalOmitSymbol(): Symbol | undefined { + // We always report an error, so cache a result in the event we could not resolve the symbol to prevent reporting it multiple times + deferredGlobalOmitSymbol ||= getGlobalTypeAliasSymbol("Omit" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; + return deferredGlobalOmitSymbol === unknownSymbol ? undefined : deferredGlobalOmitSymbol; + } - function createTypedPropertyDescriptorType(propertyType: Type): Type { - return createTypeFromGenericGlobalType(getGlobalTypedPropertyDescriptorType(), [propertyType]); - } + function getGlobalAwaitedSymbol(reportErrors: boolean): Symbol | undefined { + // Only cache `unknownSymbol` if we are reporting errors so that we don't report the error more than once. + deferredGlobalAwaitedSymbol ||= getGlobalTypeAliasSymbol("Awaited" as __String, /*arity*/ 1, reportErrors) || (reportErrors ? unknownSymbol : undefined); + return deferredGlobalAwaitedSymbol === unknownSymbol ? undefined : deferredGlobalAwaitedSymbol; + } - function createIterableType(iteratedType: Type): Type { - return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType]); - } + function getGlobalBigIntType() { + return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; + } - function createArrayType(elementType: Type, readonly?: boolean): ObjectType { - return createTypeFromGenericGlobalType(readonly ? globalReadonlyArrayType : globalArrayType, [elementType]); - } + function getGlobalNaNSymbol(): Symbol | undefined { + return (deferredGlobalNaNSymbol ||= getGlobalValueSymbol("NaN" as __String, /*reportErrors*/ false)); + } - function getTupleElementFlags(node: TypeNode) { - switch (node.kind) { - case SyntaxKind.OptionalType: - return ElementFlags.Optional; - case SyntaxKind.RestType: - return getRestTypeElementFlags(node as RestTypeNode); - case SyntaxKind.NamedTupleMember: - return (node as NamedTupleMember).questionToken ? ElementFlags.Optional : - (node as NamedTupleMember).dotDotDotToken ? getRestTypeElementFlags(node as NamedTupleMember) : - ElementFlags.Required; - default: - return ElementFlags.Required; - } - } + function getGlobalRecordSymbol(): Symbol | undefined { + deferredGlobalRecordSymbol ||= getGlobalTypeAliasSymbol("Record" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; + return deferredGlobalRecordSymbol === unknownSymbol ? undefined : deferredGlobalRecordSymbol; + } - function getRestTypeElementFlags(node: RestTypeNode | NamedTupleMember) { - return getArrayElementTypeNode(node.type) ? ElementFlags.Rest : ElementFlags.Variadic; - } + /** + * Instantiates a global type that is generic with some element type, and returns that instantiation. + */ + function createTypeFromGenericGlobalType(genericGlobalType: GenericType, typeArguments: readonly Type[]): ObjectType { + return genericGlobalType !== emptyGenericType ? createTypeReference(genericGlobalType, typeArguments) : emptyObjectType; + } - function getArrayOrTupleTargetType(node: ArrayTypeNode | TupleTypeNode): GenericType { - const readonly = isReadonlyTypeOperator(node.parent); - const elementType = getArrayElementTypeNode(node); - if (elementType) { - return readonly ? globalReadonlyArrayType : globalArrayType; - } - const elementFlags = map((node as TupleTypeNode).elements, getTupleElementFlags); - const missingName = some((node as TupleTypeNode).elements, e => e.kind !== SyntaxKind.NamedTupleMember); - return getTupleTargetType(elementFlags, readonly, /*associatedNames*/ missingName ? undefined : (node as TupleTypeNode).elements as readonly NamedTupleMember[]); - } + function createTypedPropertyDescriptorType(propertyType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalTypedPropertyDescriptorType(), [propertyType]); + } + + function createIterableType(iteratedType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType]); + } - // Return true if the given type reference node is directly aliased or if it needs to be deferred - // because it is possibly contained in a circular chain of eagerly resolved types. - function isDeferredTypeReferenceNode(node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, hasDefaultTypeArguments?: boolean) { - return !!getAliasSymbolForTypeNode(node) || isResolvedByTypeAlias(node) && ( - node.kind === SyntaxKind.ArrayType ? mayResolveTypeAlias(node.elementType) : - node.kind === SyntaxKind.TupleType ? some(node.elements, mayResolveTypeAlias) : - hasDefaultTypeArguments || some(node.typeArguments, mayResolveTypeAlias)); + function createArrayType(elementType: Type, readonly?: boolean): ObjectType { + return createTypeFromGenericGlobalType(readonly ? globalReadonlyArrayType : globalArrayType, [elementType]); + } + + function getTupleElementFlags(node: TypeNode) { + switch (node.kind) { + case SyntaxKind.OptionalType: + return ElementFlags.Optional; + case SyntaxKind.RestType: + return getRestTypeElementFlags(node as RestTypeNode); + case SyntaxKind.NamedTupleMember: + return (node as NamedTupleMember).questionToken ? ElementFlags.Optional : + (node as NamedTupleMember).dotDotDotToken ? getRestTypeElementFlags(node as NamedTupleMember) : + ElementFlags.Required; + default: + return ElementFlags.Required; } + } - // Return true when the given node is transitively contained in type constructs that eagerly - // resolve their constituent types. We include SyntaxKind.TypeReference because type arguments - // of type aliases are eagerly resolved. - function isResolvedByTypeAlias(node: Node): boolean { - const parent = node.parent; - switch (parent.kind) { - case SyntaxKind.ParenthesizedType: - case SyntaxKind.NamedTupleMember: - case SyntaxKind.TypeReference: - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - case SyntaxKind.IndexedAccessType: - case SyntaxKind.ConditionalType: - case SyntaxKind.TypeOperator: - case SyntaxKind.ArrayType: - case SyntaxKind.TupleType: - return isResolvedByTypeAlias(parent); - case SyntaxKind.TypeAliasDeclaration: - return true; - } - return false; + function getRestTypeElementFlags(node: RestTypeNode | NamedTupleMember) { + return getArrayElementTypeNode(node.type) ? ElementFlags.Rest : ElementFlags.Variadic; + } + + function getArrayOrTupleTargetType(node: ArrayTypeNode | TupleTypeNode): GenericType { + const readonly = isReadonlyTypeOperator(node.parent); + const elementType = getArrayElementTypeNode(node); + if (elementType) { + return readonly ? globalReadonlyArrayType : globalArrayType; } + const elementFlags = map((node as TupleTypeNode).elements, getTupleElementFlags); + const missingName = some((node as TupleTypeNode).elements, e => e.kind !== SyntaxKind.NamedTupleMember); + return getTupleTargetType(elementFlags, readonly, /*associatedNames*/ missingName ? undefined : (node as TupleTypeNode).elements as readonly NamedTupleMember[]); + } - // Return true if resolving the given node (i.e. getTypeFromTypeNode) possibly causes resolution - // of a type alias. - function mayResolveTypeAlias(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.TypeReference: - return isJSDocTypeReference(node) || !!(resolveTypeReferenceName(node as TypeReferenceNode, SymbolFlags.Type).flags & SymbolFlags.TypeAlias); - case SyntaxKind.TypeQuery: - return true; - case SyntaxKind.TypeOperator: - return (node as TypeOperatorNode).operator !== SyntaxKind.UniqueKeyword && mayResolveTypeAlias((node as TypeOperatorNode).type); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.OptionalType: - case SyntaxKind.NamedTupleMember: - case SyntaxKind.JSDocOptionalType: - case SyntaxKind.JSDocNullableType: - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocTypeExpression: - return mayResolveTypeAlias((node as ParenthesizedTypeNode | OptionalTypeNode | JSDocTypeReferencingNode | NamedTupleMember).type); - case SyntaxKind.RestType: - return (node as RestTypeNode).type.kind !== SyntaxKind.ArrayType || mayResolveTypeAlias(((node as RestTypeNode).type as ArrayTypeNode).elementType); - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - return some((node as UnionOrIntersectionTypeNode).types, mayResolveTypeAlias); - case SyntaxKind.IndexedAccessType: - return mayResolveTypeAlias((node as IndexedAccessTypeNode).objectType) || mayResolveTypeAlias((node as IndexedAccessTypeNode).indexType); - case SyntaxKind.ConditionalType: - return mayResolveTypeAlias((node as ConditionalTypeNode).checkType) || mayResolveTypeAlias((node as ConditionalTypeNode).extendsType) || - mayResolveTypeAlias((node as ConditionalTypeNode).trueType) || mayResolveTypeAlias((node as ConditionalTypeNode).falseType); - } - return false; + // Return true if the given type reference node is directly aliased or if it needs to be deferred + // because it is possibly contained in a circular chain of eagerly resolved types. + function isDeferredTypeReferenceNode(node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, hasDefaultTypeArguments?: boolean) { + return !!getAliasSymbolForTypeNode(node) || isResolvedByTypeAlias(node) && ( + node.kind === SyntaxKind.ArrayType ? mayResolveTypeAlias(node.elementType) : + node.kind === SyntaxKind.TupleType ? some(node.elements, mayResolveTypeAlias) : + hasDefaultTypeArguments || some(node.typeArguments, mayResolveTypeAlias)); + } + + // Return true when the given node is transitively contained in type constructs that eagerly + // resolve their constituent types. We include SyntaxKind.TypeReference because type arguments + // of type aliases are eagerly resolved. + function isResolvedByTypeAlias(node: Node): boolean { + const parent = node.parent; + switch (parent.kind) { + case SyntaxKind.ParenthesizedType: + case SyntaxKind.NamedTupleMember: + case SyntaxKind.TypeReference: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.IndexedAccessType: + case SyntaxKind.ConditionalType: + case SyntaxKind.TypeOperator: + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + return isResolvedByTypeAlias(parent); + case SyntaxKind.TypeAliasDeclaration: + return true; } + return false; + } - function getTypeFromArrayOrTupleTypeNode(node: ArrayTypeNode | TupleTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const target = getArrayOrTupleTargetType(node); - if (target === emptyGenericType) { - links.resolvedType = emptyObjectType; - } - else if (!(node.kind === SyntaxKind.TupleType && some(node.elements, e => !!(getTupleElementFlags(e) & ElementFlags.Variadic))) && isDeferredTypeReferenceNode(node)) { - links.resolvedType = node.kind === SyntaxKind.TupleType && node.elements.length === 0 ? target : - createDeferredTypeReference(target, node, /*mapper*/ undefined); - } - else { - const elementTypes = node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : map(node.elements, getTypeFromTypeNode); - links.resolvedType = createNormalizedTypeReference(target, elementTypes); - } + // Return true if resolving the given node (i.e. getTypeFromTypeNode) possibly causes resolution + // of a type alias. + function mayResolveTypeAlias(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.TypeReference: + return isJSDocTypeReference(node) || !!(resolveTypeReferenceName(node as TypeReferenceNode, SymbolFlags.Type).flags & SymbolFlags.TypeAlias); + case SyntaxKind.TypeQuery: + return true; + case SyntaxKind.TypeOperator: + return (node as TypeOperatorNode).operator !== SyntaxKind.UniqueKeyword && mayResolveTypeAlias((node as TypeOperatorNode).type); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.OptionalType: + case SyntaxKind.NamedTupleMember: + case SyntaxKind.JSDocOptionalType: + case SyntaxKind.JSDocNullableType: + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocTypeExpression: + return mayResolveTypeAlias((node as ParenthesizedTypeNode | OptionalTypeNode | JSDocTypeReferencingNode | NamedTupleMember).type); + case SyntaxKind.RestType: + return (node as RestTypeNode).type.kind !== SyntaxKind.ArrayType || mayResolveTypeAlias(((node as RestTypeNode).type as ArrayTypeNode).elementType); + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + return some((node as UnionOrIntersectionTypeNode).types, mayResolveTypeAlias); + case SyntaxKind.IndexedAccessType: + return mayResolveTypeAlias((node as IndexedAccessTypeNode).objectType) || mayResolveTypeAlias((node as IndexedAccessTypeNode).indexType); + case SyntaxKind.ConditionalType: + return mayResolveTypeAlias((node as ConditionalTypeNode).checkType) || mayResolveTypeAlias((node as ConditionalTypeNode).extendsType) || + mayResolveTypeAlias((node as ConditionalTypeNode).trueType) || mayResolveTypeAlias((node as ConditionalTypeNode).falseType); + } + return false; + } + + function getTypeFromArrayOrTupleTypeNode(node: ArrayTypeNode | TupleTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const target = getArrayOrTupleTargetType(node); + if (target === emptyGenericType) { + links.resolvedType = emptyObjectType; + } + else if (!(node.kind === SyntaxKind.TupleType && some(node.elements, e => !!(getTupleElementFlags(e) & ElementFlags.Variadic))) && isDeferredTypeReferenceNode(node)) { + links.resolvedType = node.kind === SyntaxKind.TupleType && node.elements.length === 0 ? target : + createDeferredTypeReference(target, node, /*mapper*/ undefined); + } + else { + const elementTypes = node.kind === SyntaxKind.ArrayType ? [getTypeFromTypeNode(node.elementType)] : map(node.elements, getTypeFromTypeNode); + links.resolvedType = createNormalizedTypeReference(target, elementTypes); } - return links.resolvedType; } + return links.resolvedType; + } - function isReadonlyTypeOperator(node: Node) { - return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword; + function isReadonlyTypeOperator(node: Node) { + return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword; + } + + function createTupleType(elementTypes: readonly Type[], elementFlags?: readonly ElementFlags[], readonly = false, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]) { + const tupleTarget = getTupleTargetType(elementFlags || map(elementTypes, _ => ElementFlags.Required), readonly, namedMemberDeclarations); + return tupleTarget === emptyGenericType ? emptyObjectType : + elementTypes.length ? createNormalizedTypeReference(tupleTarget, elementTypes) : + tupleTarget; + } + + function getTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]): GenericType { + if (elementFlags.length === 1 && elementFlags[0] & ElementFlags.Rest) { + // [...X[]] is equivalent to just X[] + return readonly ? globalReadonlyArrayType : globalArrayType; } + const key = map(elementFlags, f => f & ElementFlags.Required ? "#" : f & ElementFlags.Optional ? "?" : f & ElementFlags.Rest ? "." : "*").join() + + (readonly ? "R" : "") + + (namedMemberDeclarations && namedMemberDeclarations.length ? "," + map(namedMemberDeclarations, getNodeId).join(",") : ""); + let type = tupleTypes.get(key); + if (!type) { + tupleTypes.set(key, type = createTupleTargetType(elementFlags, readonly, namedMemberDeclarations)); + } + return type; + } + + // We represent tuple types as type references to synthesized generic interface types created by + // this function. The types are of the form: + // + // interface Tuple extends Array { 0: T0, 1: T1, 2: T2, ... } + // + // Note that the generic type created by this function has no symbol associated with it. The same + // is true for each of the synthesized type parameters. + function createTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration)[] | undefined): TupleType { + const arity = elementFlags.length; + const minLength = countWhere(elementFlags, f => !!(f & (ElementFlags.Required | ElementFlags.Variadic))); + let typeParameters: TypeParameter[] | undefined; + const properties: Symbol[] = []; + let combinedFlags: ElementFlags = 0; + if (arity) { + typeParameters = new Array(arity); + for (let i = 0; i < arity; i++) { + const typeParameter = typeParameters[i] = createTypeParameter(); + const flags = elementFlags[i]; + combinedFlags |= flags; + if (!(combinedFlags & ElementFlags.Variable)) { + const property = createSymbol(SymbolFlags.Property | (flags & ElementFlags.Optional ? SymbolFlags.Optional : 0), + "" + i as __String, readonly ? CheckFlags.Readonly : 0); + property.tupleLabelDeclaration = namedMemberDeclarations?.[i]; + property.type = typeParameter; + properties.push(property); + } + } + } + const fixedLength = properties.length; + const lengthSymbol = createSymbol(SymbolFlags.Property, "length" as __String, readonly ? CheckFlags.Readonly : 0); + if (combinedFlags & ElementFlags.Variable) { + lengthSymbol.type = numberType; + } + else { + const literalTypes = []; + for (let i = minLength; i <= arity; i++) literalTypes.push(getNumberLiteralType(i)); + lengthSymbol.type = getUnionType(literalTypes); + } + properties.push(lengthSymbol); + const type = createObjectType(ObjectFlags.Tuple | ObjectFlags.Reference) as TupleType & InterfaceTypeWithDeclaredMembers; + type.typeParameters = typeParameters; + type.outerTypeParameters = undefined; + type.localTypeParameters = typeParameters; + type.instantiations = new Map(); + type.instantiations.set(getTypeListId(type.typeParameters), type as GenericType); + type.target = type as GenericType; + type.resolvedTypeArguments = type.typeParameters; + type.thisType = createTypeParameter(); + type.thisType.isThisType = true; + type.thisType.constraint = type; + type.declaredProperties = properties; + type.declaredCallSignatures = emptyArray; + type.declaredConstructSignatures = emptyArray; + type.declaredIndexInfos = emptyArray; + type.elementFlags = elementFlags; + type.minLength = minLength; + type.fixedLength = fixedLength; + type.hasRestElement = !!(combinedFlags & ElementFlags.Variable); + type.combinedFlags = combinedFlags; + type.readonly = readonly; + type.labeledElementDeclarations = namedMemberDeclarations; + return type; + } - function createTupleType(elementTypes: readonly Type[], elementFlags?: readonly ElementFlags[], readonly = false, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]) { - const tupleTarget = getTupleTargetType(elementFlags || map(elementTypes, _ => ElementFlags.Required), readonly, namedMemberDeclarations); - return tupleTarget === emptyGenericType ? emptyObjectType : - elementTypes.length ? createNormalizedTypeReference(tupleTarget, elementTypes) : - tupleTarget; - } + function createNormalizedTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined) { + return target.objectFlags & ObjectFlags.Tuple ? createNormalizedTupleType(target as TupleType, typeArguments!) : createTypeReference(target, typeArguments); + } - function getTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations?: readonly (NamedTupleMember | ParameterDeclaration)[]): GenericType { - if (elementFlags.length === 1 && elementFlags[0] & ElementFlags.Rest) { - // [...X[]] is equivalent to just X[] - return readonly ? globalReadonlyArrayType : globalArrayType; - } - const key = map(elementFlags, f => f & ElementFlags.Required ? "#" : f & ElementFlags.Optional ? "?" : f & ElementFlags.Rest ? "." : "*").join() + - (readonly ? "R" : "") + - (namedMemberDeclarations && namedMemberDeclarations.length ? "," + map(namedMemberDeclarations, getNodeId).join(",") : ""); - let type = tupleTypes.get(key); - if (!type) { - tupleTypes.set(key, type = createTupleTargetType(elementFlags, readonly, namedMemberDeclarations)); - } - return type; + function createNormalizedTupleType(target: TupleType, elementTypes: readonly Type[]): Type { + if (!(target.combinedFlags & ElementFlags.NonRequired)) { + // No need to normalize when we only have regular required elements + return createTypeReference(target, elementTypes); } - - // We represent tuple types as type references to synthesized generic interface types created by - // this function. The types are of the form: - // - // interface Tuple extends Array { 0: T0, 1: T1, 2: T2, ... } - // - // Note that the generic type created by this function has no symbol associated with it. The same - // is true for each of the synthesized type parameters. - function createTupleTargetType(elementFlags: readonly ElementFlags[], readonly: boolean, namedMemberDeclarations: readonly (NamedTupleMember | ParameterDeclaration)[] | undefined): TupleType { - const arity = elementFlags.length; - const minLength = countWhere(elementFlags, f => !!(f & (ElementFlags.Required | ElementFlags.Variadic))); - let typeParameters: TypeParameter[] | undefined; - const properties: Symbol[] = []; - let combinedFlags: ElementFlags = 0; - if (arity) { - typeParameters = new Array(arity); - for (let i = 0; i < arity; i++) { - const typeParameter = typeParameters[i] = createTypeParameter(); - const flags = elementFlags[i]; - combinedFlags |= flags; - if (!(combinedFlags & ElementFlags.Variable)) { - const property = createSymbol(SymbolFlags.Property | (flags & ElementFlags.Optional ? SymbolFlags.Optional : 0), - "" + i as __String, readonly ? CheckFlags.Readonly : 0); - property.tupleLabelDeclaration = namedMemberDeclarations?.[i]; - property.type = typeParameter; - properties.push(property); - } - } - } - const fixedLength = properties.length; - const lengthSymbol = createSymbol(SymbolFlags.Property, "length" as __String, readonly ? CheckFlags.Readonly : 0); - if (combinedFlags & ElementFlags.Variable) { - lengthSymbol.type = numberType; + if (target.combinedFlags & ElementFlags.Variadic) { + // Transform [A, ...(X | Y | Z)] into [A, ...X] | [A, ...Y] | [A, ...Z] + const unionIndex = findIndex(elementTypes, (t, i) => !!(target.elementFlags[i] & ElementFlags.Variadic && t.flags & (TypeFlags.Never | TypeFlags.Union))); + if (unionIndex >= 0) { + return checkCrossProductUnion(map(elementTypes, (t, i) => target.elementFlags[i] & ElementFlags.Variadic ? t : unknownType)) ? + mapType(elementTypes[unionIndex], t => createNormalizedTupleType(target, replaceElement(elementTypes, unionIndex, t))) : + errorType; } - else { - const literalTypes = []; - for (let i = minLength; i <= arity; i++) literalTypes.push(getNumberLiteralType(i)); - lengthSymbol.type = getUnionType(literalTypes); - } - properties.push(lengthSymbol); - const type = createObjectType(ObjectFlags.Tuple | ObjectFlags.Reference) as TupleType & InterfaceTypeWithDeclaredMembers; - type.typeParameters = typeParameters; - type.outerTypeParameters = undefined; - type.localTypeParameters = typeParameters; - type.instantiations = new Map(); - type.instantiations.set(getTypeListId(type.typeParameters), type as GenericType); - type.target = type as GenericType; - type.resolvedTypeArguments = type.typeParameters; - type.thisType = createTypeParameter(); - type.thisType.isThisType = true; - type.thisType.constraint = type; - type.declaredProperties = properties; - type.declaredCallSignatures = emptyArray; - type.declaredConstructSignatures = emptyArray; - type.declaredIndexInfos = emptyArray; - type.elementFlags = elementFlags; - type.minLength = minLength; - type.fixedLength = fixedLength; - type.hasRestElement = !!(combinedFlags & ElementFlags.Variable); - type.combinedFlags = combinedFlags; - type.readonly = readonly; - type.labeledElementDeclarations = namedMemberDeclarations; - return type; } - - function createNormalizedTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined) { - return target.objectFlags & ObjectFlags.Tuple ? createNormalizedTupleType(target as TupleType, typeArguments!) : createTypeReference(target, typeArguments); - } - - function createNormalizedTupleType(target: TupleType, elementTypes: readonly Type[]): Type { - if (!(target.combinedFlags & ElementFlags.NonRequired)) { - // No need to normalize when we only have regular required elements - return createTypeReference(target, elementTypes); - } - if (target.combinedFlags & ElementFlags.Variadic) { - // Transform [A, ...(X | Y | Z)] into [A, ...X] | [A, ...Y] | [A, ...Z] - const unionIndex = findIndex(elementTypes, (t, i) => !!(target.elementFlags[i] & ElementFlags.Variadic && t.flags & (TypeFlags.Never | TypeFlags.Union))); - if (unionIndex >= 0) { - return checkCrossProductUnion(map(elementTypes, (t, i) => target.elementFlags[i] & ElementFlags.Variadic ? t : unknownType)) ? - mapType(elementTypes[unionIndex], t => createNormalizedTupleType(target, replaceElement(elementTypes, unionIndex, t))) : - errorType; - } - } - // We have optional, rest, or variadic elements that may need normalizing. Normalization ensures that all variadic - // elements are generic and that the tuple type has one of the following layouts, disregarding variadic elements: - // (1) Zero or more required elements, followed by zero or more optional elements, followed by zero or one rest element. - // (2) Zero or more required elements, followed by a rest element, followed by zero or more required elements. - // In either layout, zero or more generic variadic elements may be present at any location. - const expandedTypes: Type[] = []; - const expandedFlags: ElementFlags[] = []; - let expandedDeclarations: (NamedTupleMember | ParameterDeclaration)[] | undefined = []; - let lastRequiredIndex = -1; - let firstRestIndex = -1; - let lastOptionalOrRestIndex = -1; - for (let i = 0; i < elementTypes.length; i++) { - const type = elementTypes[i]; - const flags = target.elementFlags[i]; - if (flags & ElementFlags.Variadic) { - if (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type)) { - // Generic variadic elements stay as they are. - addElement(type, ElementFlags.Variadic, target.labeledElementDeclarations?.[i]); - } - else if (isTupleType(type)) { - const elements = getTypeArguments(type); - if (elements.length + expandedTypes.length >= 10_000) { - error(currentNode, isPartOfTypeNode(currentNode!) - ? Diagnostics.Type_produces_a_tuple_type_that_is_too_large_to_represent - : Diagnostics.Expression_produces_a_tuple_type_that_is_too_large_to_represent); - return errorType; - } - // Spread variadic elements with tuple types into the resulting tuple. - forEach(elements, (t, n) => addElement(t, type.target.elementFlags[n], type.target.labeledElementDeclarations?.[n])); - } - else { - // Treat everything else as an array type and create a rest element. - addElement(isArrayLikeType(type) && getIndexTypeOfType(type, numberType) || errorType, ElementFlags.Rest, target.labeledElementDeclarations?.[i]); + // We have optional, rest, or variadic elements that may need normalizing. Normalization ensures that all variadic + // elements are generic and that the tuple type has one of the following layouts, disregarding variadic elements: + // (1) Zero or more required elements, followed by zero or more optional elements, followed by zero or one rest element. + // (2) Zero or more required elements, followed by a rest element, followed by zero or more required elements. + // In either layout, zero or more generic variadic elements may be present at any location. + const expandedTypes: Type[] = []; + const expandedFlags: ElementFlags[] = []; + let expandedDeclarations: (NamedTupleMember | ParameterDeclaration)[] | undefined = []; + let lastRequiredIndex = -1; + let firstRestIndex = -1; + let lastOptionalOrRestIndex = -1; + for (let i = 0; i < elementTypes.length; i++) { + const type = elementTypes[i]; + const flags = target.elementFlags[i]; + if (flags & ElementFlags.Variadic) { + if (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type)) { + // Generic variadic elements stay as they are. + addElement(type, ElementFlags.Variadic, target.labeledElementDeclarations?.[i]); + } + else if (isTupleType(type)) { + const elements = getTypeArguments(type); + if (elements.length + expandedTypes.length >= 10_000) { + error(currentNode, isPartOfTypeNode(currentNode!) + ? Diagnostics.Type_produces_a_tuple_type_that_is_too_large_to_represent + : Diagnostics.Expression_produces_a_tuple_type_that_is_too_large_to_represent); + return errorType; } + // Spread variadic elements with tuple types into the resulting tuple. + forEach(elements, (t, n) => addElement(t, type.target.elementFlags[n], type.target.labeledElementDeclarations?.[n])); } else { - // Copy other element kinds with no change. - addElement(type, flags, target.labeledElementDeclarations?.[i]); + // Treat everything else as an array type and create a rest element. + addElement(isArrayLikeType(type) && getIndexTypeOfType(type, numberType) || errorType, ElementFlags.Rest, target.labeledElementDeclarations?.[i]); } } - // Turn optional elements preceding the last required element into required elements - for (let i = 0; i < lastRequiredIndex; i++) { - if (expandedFlags[i] & ElementFlags.Optional) expandedFlags[i] = ElementFlags.Required; - } - if (firstRestIndex >= 0 && firstRestIndex < lastOptionalOrRestIndex) { - // Turn elements between first rest and last optional/rest into a single rest element - expandedTypes[firstRestIndex] = getUnionType(sameMap(expandedTypes.slice(firstRestIndex, lastOptionalOrRestIndex + 1), - (t, i) => expandedFlags[firstRestIndex + i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t)); - expandedTypes.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); - expandedFlags.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); - expandedDeclarations?.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); + else { + // Copy other element kinds with no change. + addElement(type, flags, target.labeledElementDeclarations?.[i]); } - const tupleTarget = getTupleTargetType(expandedFlags, target.readonly, expandedDeclarations); - return tupleTarget === emptyGenericType ? emptyObjectType : - expandedFlags.length ? createTypeReference(tupleTarget, expandedTypes) : - tupleTarget; + } + // Turn optional elements preceding the last required element into required elements + for (let i = 0; i < lastRequiredIndex; i++) { + if (expandedFlags[i] & ElementFlags.Optional) expandedFlags[i] = ElementFlags.Required; + } + if (firstRestIndex >= 0 && firstRestIndex < lastOptionalOrRestIndex) { + // Turn elements between first rest and last optional/rest into a single rest element + expandedTypes[firstRestIndex] = getUnionType(sameMap(expandedTypes.slice(firstRestIndex, lastOptionalOrRestIndex + 1), + (t, i) => expandedFlags[firstRestIndex + i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t)); + expandedTypes.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); + expandedFlags.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); + expandedDeclarations?.splice(firstRestIndex + 1, lastOptionalOrRestIndex - firstRestIndex); + } + const tupleTarget = getTupleTargetType(expandedFlags, target.readonly, expandedDeclarations); + return tupleTarget === emptyGenericType ? emptyObjectType : + expandedFlags.length ? createTypeReference(tupleTarget, expandedTypes) : + tupleTarget; - function addElement(type: Type, flags: ElementFlags, declaration: NamedTupleMember | ParameterDeclaration | undefined) { - if (flags & ElementFlags.Required) { - lastRequiredIndex = expandedFlags.length; - } - if (flags & ElementFlags.Rest && firstRestIndex < 0) { - firstRestIndex = expandedFlags.length; - } - if (flags & (ElementFlags.Optional | ElementFlags.Rest)) { - lastOptionalOrRestIndex = expandedFlags.length; - } - expandedTypes.push(flags & ElementFlags.Optional ? addOptionality(type, /*isProperty*/ true) : type); - expandedFlags.push(flags); - if (expandedDeclarations && declaration) { - expandedDeclarations.push(declaration); - } - else { - expandedDeclarations = undefined; - } + function addElement(type: Type, flags: ElementFlags, declaration: NamedTupleMember | ParameterDeclaration | undefined) { + if (flags & ElementFlags.Required) { + lastRequiredIndex = expandedFlags.length; + } + if (flags & ElementFlags.Rest && firstRestIndex < 0) { + firstRestIndex = expandedFlags.length; + } + if (flags & (ElementFlags.Optional | ElementFlags.Rest)) { + lastOptionalOrRestIndex = expandedFlags.length; + } + expandedTypes.push(flags & ElementFlags.Optional ? addOptionality(type, /*isProperty*/ true) : type); + expandedFlags.push(flags); + if (expandedDeclarations && declaration) { + expandedDeclarations.push(declaration); + } + else { + expandedDeclarations = undefined; } } + } - function sliceTupleType(type: TupleTypeReference, index: number, endSkipCount = 0) { - const target = type.target; - const endIndex = getTypeReferenceArity(type) - endSkipCount; - return index > target.fixedLength ? getRestArrayTypeOfTupleType(type) || createTupleType(emptyArray) : - createTupleType(getTypeArguments(type).slice(index, endIndex), target.elementFlags.slice(index, endIndex), - /*readonly*/ false, target.labeledElementDeclarations && target.labeledElementDeclarations.slice(index, endIndex)); - } + function sliceTupleType(type: TupleTypeReference, index: number, endSkipCount = 0) { + const target = type.target; + const endIndex = getTypeReferenceArity(type) - endSkipCount; + return index > target.fixedLength ? getRestArrayTypeOfTupleType(type) || createTupleType(emptyArray) : + createTupleType(getTypeArguments(type).slice(index, endIndex), target.elementFlags.slice(index, endIndex), + /*readonly*/ false, target.labeledElementDeclarations && target.labeledElementDeclarations.slice(index, endIndex)); + } - function getKnownKeysOfTupleType(type: TupleTypeReference) { - return getUnionType(append(arrayOf(type.target.fixedLength, i => getStringLiteralType("" + i)), - getIndexType(type.target.readonly ? globalReadonlyArrayType : globalArrayType))); - } + function getKnownKeysOfTupleType(type: TupleTypeReference) { + return getUnionType(append(arrayOf(type.target.fixedLength, i => getStringLiteralType("" + i)), + getIndexType(type.target.readonly ? globalReadonlyArrayType : globalArrayType))); + } - // Return count of starting consecutive tuple elements of the given kind(s) - function getStartElementCount(type: TupleType, flags: ElementFlags) { - const index = findIndex(type.elementFlags, f => !(f & flags)); - return index >= 0 ? index : type.elementFlags.length; - } + // Return count of starting consecutive tuple elements of the given kind(s) + function getStartElementCount(type: TupleType, flags: ElementFlags) { + const index = findIndex(type.elementFlags, f => !(f & flags)); + return index >= 0 ? index : type.elementFlags.length; + } - // Return count of ending consecutive tuple elements of the given kind(s) - function getEndElementCount(type: TupleType, flags: ElementFlags) { - return type.elementFlags.length - findLastIndex(type.elementFlags, f => !(f & flags)) - 1; - } + // Return count of ending consecutive tuple elements of the given kind(s) + function getEndElementCount(type: TupleType, flags: ElementFlags) { + return type.elementFlags.length - findLastIndex(type.elementFlags, f => !(f & flags)) - 1; + } - function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type { - return addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true); - } + function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type { + return addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true); + } - function getTypeId(type: Type): TypeId { - return type.id; - } + function getTypeId(type: Type): TypeId { + return type.id; + } - function containsType(types: readonly Type[], type: Type): boolean { - return binarySearch(types, type, getTypeId, compareValues) >= 0; - } + function containsType(types: readonly Type[], type: Type): boolean { + return binarySearch(types, type, getTypeId, compareValues) >= 0; + } - function insertType(types: Type[], type: Type): boolean { - const index = binarySearch(types, type, getTypeId, compareValues); - if (index < 0) { - types.splice(~index, 0, type); - return true; - } - return false; + function insertType(types: Type[], type: Type): boolean { + const index = binarySearch(types, type, getTypeId, compareValues); + if (index < 0) { + types.splice(~index, 0, type); + return true; } + return false; + } - function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { - const flags = type.flags; - if (flags & TypeFlags.Union) { - return addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types); + function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { + const flags = type.flags; + if (flags & TypeFlags.Union) { + return addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types); + } + // We ignore 'never' types in unions + if (!(flags & TypeFlags.Never)) { + includes |= flags & TypeFlags.IncludesMask; + if (flags & TypeFlags.Instantiable) includes |= TypeFlags.IncludesInstantiable; + if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; + if (!strictNullChecks && flags & TypeFlags.Nullable) { + if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType; } - // We ignore 'never' types in unions - if (!(flags & TypeFlags.Never)) { - includes |= flags & TypeFlags.IncludesMask; - if (flags & TypeFlags.Instantiable) includes |= TypeFlags.IncludesInstantiable; - if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; - if (!strictNullChecks && flags & TypeFlags.Nullable) { - if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType; - } - else { - const len = typeSet.length; - const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues); - if (index < 0) { - typeSet.splice(~index, 0, type); - } + else { + const len = typeSet.length; + const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues); + if (index < 0) { + typeSet.splice(~index, 0, type); } } - return includes; } + return includes; + } - // Add the given types to the given type set. Order is preserved, duplicates are removed, - // and nested types of the given kind are flattened into the set. - function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: readonly Type[]): TypeFlags { - for (const type of types) { - includes = addTypeToUnion(typeSet, includes, type); - } - return includes; + // Add the given types to the given type set. Order is preserved, duplicates are removed, + // and nested types of the given kind are flattened into the set. + function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: readonly Type[]): TypeFlags { + for (const type of types) { + includes = addTypeToUnion(typeSet, includes, type); } + return includes; + } - function removeSubtypes(types: Type[], hasObjectTypes: boolean): Type[] | undefined { - // [] and [T] immediately reduce to [] and [T] respectively - if (types.length < 2) { - return types; - } - - const id = getTypeListId(types); - const match = subtypeReductionCache.get(id); - if (match) { - return match; - } + function removeSubtypes(types: Type[], hasObjectTypes: boolean): Type[] | undefined { + // [] and [T] immediately reduce to [] and [T] respectively + if (types.length < 2) { + return types; + } - // We assume that redundant primitive types have already been removed from the types array and that there - // are no any and unknown types in the array. Thus, the only possible supertypes for primitive types are empty - // object types, and if none of those are present we can exclude primitive types from the subtype check. - const hasEmptyObject = hasObjectTypes && some(types, t => !!(t.flags & TypeFlags.Object) && !isGenericMappedType(t) && isEmptyResolvedType(resolveStructuredTypeMembers(t as ObjectType))); - const len = types.length; - let i = len; - let count = 0; - while (i > 0) { - i--; - const source = types[i]; - if (hasEmptyObject || source.flags & TypeFlags.StructuredOrInstantiable) { - // Find the first property with a unit type, if any. When constituents have a property by the same name - // but of a different unit type, we can quickly disqualify them from subtype checks. This helps subtype - // reduction of large discriminated union types. - const keyProperty = source.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive) ? - find(getPropertiesOfType(source), p => isUnitType(getTypeOfSymbol(p))) : - undefined; - const keyPropertyType = keyProperty && getRegularTypeOfLiteralType(getTypeOfSymbol(keyProperty)); - for (const target of types) { - if (source !== target) { - if (count === 100000) { - // After 100000 subtype checks we estimate the remaining amount of work by assuming the - // same ratio of checks per element. If the estimated number of remaining type checks is - // greater than 1M we deem the union type too complex to represent. This for example - // caps union types at 1000 unique object types. - const estimatedCount = (count / (len - i)) * len; - if (estimatedCount > 1000000) { - tracing?.instant(tracing.Phase.CheckTypes, "removeSubtypes_DepthLimit", { typeIds: types.map(t => t.id) }); - error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); - return undefined; - } - } - count++; - if (keyProperty && target.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) { - const t = getTypeOfPropertyOfType(target, keyProperty.escapedName); - if (t && isUnitType(t) && getRegularTypeOfLiteralType(t) !== keyPropertyType) { - continue; - } + const id = getTypeListId(types); + const match = subtypeReductionCache.get(id); + if (match) { + return match; + } + + // We assume that redundant primitive types have already been removed from the types array and that there + // are no any and unknown types in the array. Thus, the only possible supertypes for primitive types are empty + // object types, and if none of those are present we can exclude primitive types from the subtype check. + const hasEmptyObject = hasObjectTypes && some(types, t => !!(t.flags & TypeFlags.Object) && !isGenericMappedType(t) && isEmptyResolvedType(resolveStructuredTypeMembers(t as ObjectType))); + const len = types.length; + let i = len; + let count = 0; + while (i > 0) { + i--; + const source = types[i]; + if (hasEmptyObject || source.flags & TypeFlags.StructuredOrInstantiable) { + // Find the first property with a unit type, if any. When constituents have a property by the same name + // but of a different unit type, we can quickly disqualify them from subtype checks. This helps subtype + // reduction of large discriminated union types. + const keyProperty = source.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive) ? + find(getPropertiesOfType(source), p => isUnitType(getTypeOfSymbol(p))) : + undefined; + const keyPropertyType = keyProperty && getRegularTypeOfLiteralType(getTypeOfSymbol(keyProperty)); + for (const target of types) { + if (source !== target) { + if (count === 100000) { + // After 100000 subtype checks we estimate the remaining amount of work by assuming the + // same ratio of checks per element. If the estimated number of remaining type checks is + // greater than 1M we deem the union type too complex to represent. This for example + // caps union types at 1000 unique object types. + const estimatedCount = (count / (len - i)) * len; + if (estimatedCount > 1000000) { + tracing?.instant(tracing.Phase.CheckTypes, "removeSubtypes_DepthLimit", { typeIds: types.map(t => t.id) }); + error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); + return undefined; } - if (isTypeRelatedTo(source, target, strictSubtypeRelation) && ( - !(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) || - !(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) || - isTypeDerivedFrom(source, target))) { - orderedRemoveItemAt(types, i); - break; + } + count++; + if (keyProperty && target.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) { + const t = getTypeOfPropertyOfType(target, keyProperty.escapedName); + if (t && isUnitType(t) && getRegularTypeOfLiteralType(t) !== keyPropertyType) { + continue; } } + if (isTypeRelatedTo(source, target, strictSubtypeRelation) && ( + !(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) || + !(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) || + isTypeDerivedFrom(source, target))) { + orderedRemoveItemAt(types, i); + break; + } } } } - subtypeReductionCache.set(id, types); - return types; } + subtypeReductionCache.set(id, types); + return types; + } + + function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags, reduceVoidUndefined: boolean) { + let i = types.length; + while (i > 0) { + i--; + const t = types[i]; + const flags = t.flags; + const remove = + flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && includes & TypeFlags.String || + flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number || + flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt || + flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol || + reduceVoidUndefined && flags & TypeFlags.Undefined && includes & TypeFlags.Void || + isFreshLiteralType(t) && containsType(types, (t as LiteralType).regularType); + if (remove) { + orderedRemoveItemAt(types, i); + } + } + } - function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags, reduceVoidUndefined: boolean) { + function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { + const templates = filter(types, isPatternLiteralType) as TemplateLiteralType[]; + if (templates.length) { let i = types.length; while (i > 0) { i--; const t = types[i]; - const flags = t.flags; - const remove = - flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && includes & TypeFlags.String || - flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number || - flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt || - flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol || - reduceVoidUndefined && flags & TypeFlags.Undefined && includes & TypeFlags.Void || - isFreshLiteralType(t) && containsType(types, (t as LiteralType).regularType); - if (remove) { + if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeMatchedByTemplateLiteralType(t, template))) { orderedRemoveItemAt(types, i); } } } + } - function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { - const templates = filter(types, isPatternLiteralType) as TemplateLiteralType[]; - if (templates.length) { - let i = types.length; - while (i > 0) { - i--; - const t = types[i]; - if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeMatchedByTemplateLiteralType(t, template))) { - orderedRemoveItemAt(types, i); - } + function isNamedUnionType(type: Type) { + return !!(type.flags & TypeFlags.Union && (type.aliasSymbol || (type as UnionType).origin)); + } + + function addNamedUnions(namedUnions: Type[], types: readonly Type[]) { + for (const t of types) { + if (t.flags & TypeFlags.Union) { + const origin = (t as UnionType).origin; + if (t.aliasSymbol || origin && !(origin.flags & TypeFlags.Union)) { + pushIfUnique(namedUnions, t); + } + else if (origin && origin.flags & TypeFlags.Union) { + addNamedUnions(namedUnions, (origin as UnionType).types); } } } + } - function isNamedUnionType(type: Type) { - return !!(type.flags & TypeFlags.Union && (type.aliasSymbol || (type as UnionType).origin)); - } + function createOriginUnionOrIntersectionType(flags: TypeFlags, types: Type[]) { + const result = createOriginType(flags) as UnionOrIntersectionType; + result.types = types; + return result; + } - function addNamedUnions(namedUnions: Type[], types: readonly Type[]) { - for (const t of types) { - if (t.flags & TypeFlags.Union) { - const origin = (t as UnionType).origin; - if (t.aliasSymbol || origin && !(origin.flags & TypeFlags.Union)) { - pushIfUnique(namedUnions, t); - } - else if (origin && origin.flags & TypeFlags.Union) { - addNamedUnions(namedUnions, (origin as UnionType).types); - } - } - } + // We sort and deduplicate the constituent types based on object identity. If the subtypeReduction + // flag is specified we also reduce the constituent type set to only include types that aren't subtypes + // of other types. Subtype reduction is expensive for large union types and is possible only when union + // types are known not to circularly reference themselves (as is the case with union types created by + // expression constructs such as array literals and the || and ?: operators). Named types can + // circularly reference themselves and therefore cannot be subtype reduced during their declaration. + // For example, "type Item = string | (() => Item" is a named type that circularly references itself. + function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { + if (types.length === 0) { + return neverType; } - - function createOriginUnionOrIntersectionType(flags: TypeFlags, types: Type[]) { - const result = createOriginType(flags) as UnionOrIntersectionType; - result.types = types; - return result; + if (types.length === 1) { + return types[0]; } - - // We sort and deduplicate the constituent types based on object identity. If the subtypeReduction - // flag is specified we also reduce the constituent type set to only include types that aren't subtypes - // of other types. Subtype reduction is expensive for large union types and is possible only when union - // types are known not to circularly reference themselves (as is the case with union types created by - // expression constructs such as array literals and the || and ?: operators). Named types can - // circularly reference themselves and therefore cannot be subtype reduced during their declaration. - // For example, "type Item = string | (() => Item" is a named type that circularly references itself. - function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { - if (types.length === 0) { - return neverType; - } - if (types.length === 1) { - return types[0]; + let typeSet: Type[] | undefined = []; + const includes = addTypesToUnion(typeSet, 0, types); + if (unionReduction !== UnionReduction.None) { + if (includes & TypeFlags.AnyOrUnknown) { + return includes & TypeFlags.Any ? + includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : + includes & TypeFlags.Null || containsType(typeSet, unknownType) ? unknownType : nonNullUnknownType; } - let typeSet: Type[] | undefined = []; - const includes = addTypesToUnion(typeSet, 0, types); - if (unionReduction !== UnionReduction.None) { - if (includes & TypeFlags.AnyOrUnknown) { - return includes & TypeFlags.Any ? - includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : - includes & TypeFlags.Null || containsType(typeSet, unknownType) ? unknownType : nonNullUnknownType; - } - if (exactOptionalPropertyTypes && includes & TypeFlags.Undefined) { - const missingIndex = binarySearch(typeSet, missingType, getTypeId, compareValues); - if (missingIndex >= 0 && containsType(typeSet, undefinedType)) { - orderedRemoveItemAt(typeSet, missingIndex); - } - } - if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) { - removeRedundantLiteralTypes(typeSet, includes, !!(unionReduction & UnionReduction.Subtype)); - } - if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) { - removeStringLiteralsMatchedByTemplateLiterals(typeSet); - } - if (unionReduction === UnionReduction.Subtype) { - typeSet = removeSubtypes(typeSet, !!(includes & TypeFlags.Object)); - if (!typeSet) { - return errorType; - } - } - if (typeSet.length === 0) { - return includes & TypeFlags.Null ? includes & TypeFlags.IncludesNonWideningType ? nullType : nullWideningType : - includes & TypeFlags.Undefined ? includes & TypeFlags.IncludesNonWideningType ? undefinedType : undefinedWideningType : - neverType; + if (exactOptionalPropertyTypes && includes & TypeFlags.Undefined) { + const missingIndex = binarySearch(typeSet, missingType, getTypeId, compareValues); + if (missingIndex >= 0 && containsType(typeSet, undefinedType)) { + orderedRemoveItemAt(typeSet, missingIndex); } } - if (!origin && includes & TypeFlags.Union) { - const namedUnions: Type[] = []; - addNamedUnions(namedUnions, types); - const reducedTypes: Type[] = []; - for (const t of typeSet) { - if (!some(namedUnions, union => containsType((union as UnionType).types, t))) { - reducedTypes.push(t); - } - } - if (!aliasSymbol && namedUnions.length === 1 && reducedTypes.length === 0) { - return namedUnions[0]; - } - // We create a denormalized origin type only when the union was created from one or more named unions - // (unions with alias symbols or origins) and when there is no overlap between those named unions. - const namedTypesCount = reduceLeft(namedUnions, (sum, union) => sum + (union as UnionType).types.length, 0); - if (namedTypesCount + reducedTypes.length === typeSet.length) { - for (const t of namedUnions) { - insertType(reducedTypes, t); - } - origin = createOriginUnionOrIntersectionType(TypeFlags.Union, reducedTypes); - } + if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) { + removeRedundantLiteralTypes(typeSet, includes, !!(unionReduction & UnionReduction.Subtype)); } - const objectFlags = (includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion) | - (includes & TypeFlags.Intersection ? ObjectFlags.ContainsIntersections : 0); - return getUnionTypeFromSortedList(typeSet, objectFlags, aliasSymbol, aliasTypeArguments, origin); - } - - function getUnionOrIntersectionTypePredicate(signatures: readonly Signature[], kind: TypeFlags | undefined): TypePredicate | undefined { - let first: TypePredicate | undefined; - const types: Type[] = []; - for (const sig of signatures) { - const pred = getTypePredicateOfSignature(sig); - if (!pred || pred.kind === TypePredicateKind.AssertsThis || pred.kind === TypePredicateKind.AssertsIdentifier) { - if (kind !== TypeFlags.Intersection) { - continue; - } - else { - return; // intersections demand all members be type predicates for the result to have a predicate - } - } - - if (first) { - if (!typePredicateKindsMatch(first, pred)) { - // No common type predicate. - return undefined; - } - } - else { - first = pred; + if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) { + removeStringLiteralsMatchedByTemplateLiterals(typeSet); + } + if (unionReduction === UnionReduction.Subtype) { + typeSet = removeSubtypes(typeSet, !!(includes & TypeFlags.Object)); + if (!typeSet) { + return errorType; } - types.push(pred.type); } - if (!first) { - // No signatures had a type predicate. - return undefined; + if (typeSet.length === 0) { + return includes & TypeFlags.Null ? includes & TypeFlags.IncludesNonWideningType ? nullType : nullWideningType : + includes & TypeFlags.Undefined ? includes & TypeFlags.IncludesNonWideningType ? undefinedType : undefinedWideningType : + neverType; } - const compositeType = getUnionOrIntersectionType(types, kind); - return createTypePredicate(first.kind, first.parameterName, first.parameterIndex, compositeType); - } - - function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean { - return a.kind === b.kind && a.parameterIndex === b.parameterIndex; } - - // This function assumes the constituent type list is sorted and deduplicated. - function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { - if (types.length === 0) { - return neverType; + if (!origin && includes & TypeFlags.Union) { + const namedUnions: Type[] = []; + addNamedUnions(namedUnions, types); + const reducedTypes: Type[] = []; + for (const t of typeSet) { + if (!some(namedUnions, union => containsType((union as UnionType).types, t))) { + reducedTypes.push(t); + } } - if (types.length === 1) { - return types[0]; + if (!aliasSymbol && namedUnions.length === 1 && reducedTypes.length === 0) { + return namedUnions[0]; } - const typeKey = !origin ? getTypeListId(types) : - origin.flags & TypeFlags.Union ? `|${getTypeListId((origin as UnionType).types)}` : - origin.flags & TypeFlags.Intersection ? `&${getTypeListId((origin as IntersectionType).types)}` : - `#${(origin as IndexType).type.id}|${getTypeListId(types)}`; // origin type id alone is insufficient, as `keyof x` may resolve to multiple WIP values while `x` is still resolving - const id = typeKey + getAliasId(aliasSymbol, aliasTypeArguments); - let type = unionTypes.get(id); - if (!type) { - type = createType(TypeFlags.Union) as UnionType; - type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); - type.types = types; - type.origin = origin; - type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = aliasTypeArguments; - if (types.length === 2 && types[0].flags & TypeFlags.BooleanLiteral && types[1].flags & TypeFlags.BooleanLiteral) { - type.flags |= TypeFlags.Boolean; - (type as UnionType & IntrinsicType).intrinsicName = "boolean"; + // We create a denormalized origin type only when the union was created from one or more named unions + // (unions with alias symbols or origins) and when there is no overlap between those named unions. + const namedTypesCount = reduceLeft(namedUnions, (sum, union) => sum + (union as UnionType).types.length, 0); + if (namedTypesCount + reducedTypes.length === typeSet.length) { + for (const t of namedUnions) { + insertType(reducedTypes, t); } - unionTypes.set(id, type); + origin = createOriginUnionOrIntersectionType(TypeFlags.Union, reducedTypes); } - return type; } + const objectFlags = (includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion) | + (includes & TypeFlags.Intersection ? ObjectFlags.ContainsIntersections : 0); + return getUnionTypeFromSortedList(typeSet, objectFlags, aliasSymbol, aliasTypeArguments, origin); + } - function getTypeFromUnionTypeNode(node: UnionTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const aliasSymbol = getAliasSymbolForTypeNode(node); - links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, - aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); + function getUnionOrIntersectionTypePredicate(signatures: readonly Signature[], kind: TypeFlags | undefined): TypePredicate | undefined { + let first: TypePredicate | undefined; + const types: Type[] = []; + for (const sig of signatures) { + const pred = getTypePredicateOfSignature(sig); + if (!pred || pred.kind === TypePredicateKind.AssertsThis || pred.kind === TypePredicateKind.AssertsIdentifier) { + if (kind !== TypeFlags.Intersection) { + continue; + } + else { + return; // intersections demand all members be type predicates for the result to have a predicate + } } - return links.resolvedType; - } - function addTypeToIntersection(typeSet: ESMap, includes: TypeFlags, type: Type) { - const flags = type.flags; - if (flags & TypeFlags.Intersection) { - return addTypesToIntersection(typeSet, includes, (type as IntersectionType).types); - } - if (isEmptyAnonymousObjectType(type)) { - if (!(includes & TypeFlags.IncludesEmptyObject)) { - includes |= TypeFlags.IncludesEmptyObject; - typeSet.set(type.id.toString(), type); + if (first) { + if (!typePredicateKindsMatch(first, pred)) { + // No common type predicate. + return undefined; } } else { - if (flags & TypeFlags.AnyOrUnknown) { - if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; - } - else if (strictNullChecks || !(flags & TypeFlags.Nullable)) { - if (exactOptionalPropertyTypes && type === missingType) { - includes |= TypeFlags.IncludesMissingType; - type = undefinedType; - } - if (!typeSet.has(type.id.toString())) { - 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.set(type.id.toString(), type); - } - } - includes |= flags & TypeFlags.IncludesMask; + first = pred; } - return includes; + types.push(pred.type); } - - // Add the given types to the given type set. Order is preserved, freshness is removed from literal - // types, duplicates are removed, and nested types of the given kind are flattened into the set. - function addTypesToIntersection(typeSet: ESMap, includes: TypeFlags, types: readonly Type[]) { - for (const type of types) { - includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); - } - return includes; + if (!first) { + // No signatures had a type predicate. + return undefined; } + const compositeType = getUnionOrIntersectionType(types, kind); + return createTypePredicate(first.kind, first.parameterName, first.parameterIndex, compositeType); + } - function removeRedundantSupertypes(types: Type[], includes: TypeFlags) { - let i = types.length; - while (i > 0) { - i--; - const t = types[i]; - const remove = - t.flags & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || - t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral || - t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || - t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol || - t.flags & TypeFlags.Void && includes & TypeFlags.Undefined || - isEmptyAnonymousObjectType(t) && includes & TypeFlags.DefinitelyNonNullable; - if (remove) { - orderedRemoveItemAt(types, i); - } - } - } + function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean { + return a.kind === b.kind && a.parameterIndex === b.parameterIndex; + } - // Check that the given type has a match in every union. A given type is matched by - // an identical type, and a literal type is additionally matched by its corresponding - // primitive type. - function eachUnionContains(unionTypes: UnionType[], type: Type) { - for (const u of unionTypes) { - if (!containsType(u.types, type)) { - const primitive = type.flags & TypeFlags.StringLiteral ? stringType : - type.flags & TypeFlags.NumberLiteral ? numberType : - type.flags & TypeFlags.BigIntLiteral ? bigintType : - type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : - undefined; - if (!primitive || !containsType(u.types, primitive)) { - return false; - } - } - } - return true; + // This function assumes the constituent type list is sorted and deduplicated. + function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { + if (types.length === 0) { + return neverType; } - - /** - * Returns `true` if the intersection of the template literals and string literals is the empty set, eg `get${string}` & "setX", and should reduce to `never` - */ - function extractRedundantTemplateLiterals(types: Type[]): boolean { - let i = types.length; - const literals = filter(types, t => !!(t.flags & TypeFlags.StringLiteral)); - while (i > 0) { - i--; - const t = types[i]; - if (!(t.flags & TypeFlags.TemplateLiteral)) continue; - for (const t2 of literals) { - if (isTypeSubtypeOf(t2, t)) { - // eg, ``get${T}` & "getX"` is just `"getX"` - orderedRemoveItemAt(types, i); - break; - } - else if (isPatternLiteralType(t)) { - return true; - } - } + if (types.length === 1) { + return types[0]; + } + const typeKey = !origin ? getTypeListId(types) : + origin.flags & TypeFlags.Union ? `|${getTypeListId((origin as UnionType).types)}` : + origin.flags & TypeFlags.Intersection ? `&${getTypeListId((origin as IntersectionType).types)}` : + `#${(origin as IndexType).type.id}|${getTypeListId(types)}`; // origin type id alone is insufficient, as `keyof x` may resolve to multiple WIP values while `x` is still resolving + const id = typeKey + getAliasId(aliasSymbol, aliasTypeArguments); + let type = unionTypes.get(id); + if (!type) { + type = createType(TypeFlags.Union) as UnionType; + type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + type.types = types; + type.origin = origin; + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = aliasTypeArguments; + if (types.length === 2 && types[0].flags & TypeFlags.BooleanLiteral && types[1].flags & TypeFlags.BooleanLiteral) { + type.flags |= TypeFlags.Boolean; + (type as UnionType & IntrinsicType).intrinsicName = "boolean"; } - return false; + unionTypes.set(id, type); } + return type; + } - function eachIsUnionContaining(types: Type[], flag: TypeFlags) { - return every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag))); + function getTypeFromUnionTypeNode(node: UnionTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const aliasSymbol = getAliasSymbolForTypeNode(node); + links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), UnionReduction.Literal, + aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); } + return links.resolvedType; + } - function removeFromEach(types: Type[], flag: TypeFlags) { - for (let i = 0; i < types.length; i++) { - types[i] = filterType(types[i], t => !(t.flags & flag)); + function addTypeToIntersection(typeSet: ESMap, includes: TypeFlags, type: Type) { + const flags = type.flags; + if (flags & TypeFlags.Intersection) { + return addTypesToIntersection(typeSet, includes, (type as IntersectionType).types); + } + if (isEmptyAnonymousObjectType(type)) { + if (!(includes & TypeFlags.IncludesEmptyObject)) { + includes |= TypeFlags.IncludesEmptyObject; + typeSet.set(type.id.toString(), type); } } - - // If the given list of types contains more than one union of primitive types, replace the - // first with a union containing an intersection of those primitive types, then remove the - // other unions and return true. Otherwise, do nothing and return false. - function intersectUnionsOfPrimitiveTypes(types: Type[]) { - let unionTypes: UnionType[] | undefined; - const index = findIndex(types, t => !!(getObjectFlags(t) & ObjectFlags.PrimitiveUnion)); - if (index < 0) { - return false; + else { + if (flags & TypeFlags.AnyOrUnknown) { + if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; } - let i = index + 1; - // Remove all but the first union of primitive types and collect them in - // the unionTypes array. - while (i < types.length) { - const t = types[i]; - if (getObjectFlags(t) & ObjectFlags.PrimitiveUnion) { - (unionTypes || (unionTypes = [types[index] as UnionType])).push(t as UnionType); - orderedRemoveItemAt(types, i); - } - else { - i++; + else if (strictNullChecks || !(flags & TypeFlags.Nullable)) { + if (exactOptionalPropertyTypes && type === missingType) { + includes |= TypeFlags.IncludesMissingType; + type = undefinedType; } - } - // Return false if there was only one union of primitive types - if (!unionTypes) { - return false; - } - // We have more than one union of primitive types, now intersect them. For each - // type in each union we check if the type is matched in every union and if so - // we include it in the result. - const checked: Type[] = []; - const result: Type[] = []; - for (const u of unionTypes) { - for (const t of u.types) { - if (insertType(checked, t)) { - if (eachUnionContains(unionTypes, t)) { - insertType(result, t); - } + if (!typeSet.has(type.id.toString())) { + 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.set(type.id.toString(), type); } } - // Finally replace the first union with the result - types[index] = getUnionTypeFromSortedList(result, ObjectFlags.PrimitiveUnion); - return true; + includes |= flags & TypeFlags.IncludesMask; } + return includes; + } - function createIntersectionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { - const result = createType(TypeFlags.Intersection) as IntersectionType; - result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); - result.types = types; - result.aliasSymbol = aliasSymbol; - result.aliasTypeArguments = aliasTypeArguments; - return result; + // Add the given types to the given type set. Order is preserved, freshness is removed from literal + // types, duplicates are removed, and nested types of the given kind are flattened into the set. + function addTypesToIntersection(typeSet: ESMap, includes: TypeFlags, types: readonly Type[]) { + for (const type of types) { + includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); } + return includes; + } - // 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 - // effectively ensure that union types are always at the top level in type representations. - // - // We do not perform structural deduplication on intersection types. Intersection types are created only by the & - // type operator and we can't reduce those because we want to support recursive intersection types. For example, - // a type alias of the form "type List = T & { next: List }" cannot be reduced during its declaration. - // Also, unlike union types, the order of the constituent types is preserved in order that overload resolution - // for intersections of types with signatures can be deterministic. - function getIntersectionType(types: readonly Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], noSupertypeReduction?: boolean): Type { - const typeMembershipMap: ESMap = new Map(); - const includes = addTypesToIntersection(typeMembershipMap, 0, types); - const typeSet: Type[] = arrayFrom(typeMembershipMap.values()); - // 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) { - return contains(typeSet, silentNeverType) ? silentNeverType : neverType; - } - if (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.TemplateLiteral && includes & TypeFlags.StringLiteral && extractRedundantTemplateLiterals(typeSet)) { - return neverType; - } - if (includes & TypeFlags.Any) { - return includes & TypeFlags.IncludesWildcard ? wildcardType : anyType; - } - if (!strictNullChecks && includes & TypeFlags.Nullable) { - return includes & TypeFlags.IncludesEmptyObject ? neverType : includes & TypeFlags.Undefined ? undefinedType : nullType; - } - if (includes & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || - includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral || - includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || - includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol || - includes & TypeFlags.Void && includes & TypeFlags.Undefined || - includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.DefinitelyNonNullable) { - if (!noSupertypeReduction) removeRedundantSupertypes(typeSet, includes); - } - if (includes & TypeFlags.IncludesMissingType) { - typeSet[typeSet.indexOf(undefinedType)] = missingType; - } - if (typeSet.length === 0) { - return unknownType; + function removeRedundantSupertypes(types: Type[], includes: TypeFlags) { + let i = types.length; + while (i > 0) { + i--; + const t = types[i]; + const remove = + t.flags & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || + t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral || + t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || + t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol || + t.flags & TypeFlags.Void && includes & TypeFlags.Undefined || + isEmptyAnonymousObjectType(t) && includes & TypeFlags.DefinitelyNonNullable; + if (remove) { + orderedRemoveItemAt(types, i); } - if (typeSet.length === 1) { - return typeSet[0]; + } + } + + // Check that the given type has a match in every union. A given type is matched by + // an identical type, and a literal type is additionally matched by its corresponding + // primitive type. + function eachUnionContains(unionTypes: UnionType[], type: Type) { + for (const u of unionTypes) { + if (!containsType(u.types, type)) { + const primitive = type.flags & TypeFlags.StringLiteral ? stringType : + type.flags & TypeFlags.NumberLiteral ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + undefined; + if (!primitive || !containsType(u.types, primitive)) { + return false; + } } - const id = getTypeListId(typeSet) + getAliasId(aliasSymbol, 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 if (eachIsUnionContaining(typeSet, TypeFlags.Undefined)) { - const undefinedOrMissingType = exactOptionalPropertyTypes && some(typeSet, t => containsType((t as UnionType).types, missingType)) ? missingType : undefinedType; - removeFromEach(typeSet, TypeFlags.Undefined); - result = getUnionType([getIntersectionType(typeSet), undefinedOrMissingType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); - } - else if (eachIsUnionContaining(typeSet, TypeFlags.Null)) { - removeFromEach(typeSet, TypeFlags.Null); - result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); - } - else { - // We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of - // the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type - // exceeds 100000 constituents, report an error. - if (!checkCrossProductUnion(typeSet)) { - return errorType; - } - const constituents = getCrossProductIntersections(typeSet); - // We attach a denormalized origin type when at least one constituent of the cross-product union is an - // intersection (i.e. when the intersection didn't just reduce one or more unions to smaller unions) and - // the denormalized origin has fewer constituents than the union itself. - const origin = some(constituents, t => !!(t.flags & TypeFlags.Intersection)) && getConstituentCountOfTypes(constituents) > getConstituentCountOfTypes(typeSet) ? createOriginUnionOrIntersectionType(TypeFlags.Intersection, typeSet) : undefined; - result = getUnionType(constituents, UnionReduction.Literal, aliasSymbol, aliasTypeArguments, origin); - } + } + return true; + } + + /** + * Returns `true` if the intersection of the template literals and string literals is the empty set, eg `get${string}` & "setX", and should reduce to `never` + */ + function extractRedundantTemplateLiterals(types: Type[]): boolean { + let i = types.length; + const literals = filter(types, t => !!(t.flags & TypeFlags.StringLiteral)); + while (i > 0) { + i--; + const t = types[i]; + if (!(t.flags & TypeFlags.TemplateLiteral)) continue; + for (const t2 of literals) { + if (isTypeSubtypeOf(t2, t)) { + // eg, ``get${T}` & "getX"` is just `"getX"` + orderedRemoveItemAt(types, i); + break; } - else { - result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); + else if (isPatternLiteralType(t)) { + return true; } - intersectionTypes.set(id, result); } - return result; } + return false; + } + + function eachIsUnionContaining(types: Type[], flag: TypeFlags) { + return every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag))); + } - function getCrossProductUnionSize(types: readonly Type[]) { - return reduceLeft(types, (n, t) => t.flags & TypeFlags.Union ? n * (t as UnionType).types.length : t.flags & TypeFlags.Never ? 0 : n, 1); + function removeFromEach(types: Type[], flag: TypeFlags) { + for (let i = 0; i < types.length; i++) { + types[i] = filterType(types[i], t => !(t.flags & flag)); } + } - function checkCrossProductUnion(types: readonly Type[]) { - const size = getCrossProductUnionSize(types); - if (size >= 100000) { - tracing?.instant(tracing.Phase.CheckTypes, "checkCrossProductUnion_DepthLimit", { typeIds: types.map(t => t.id), size }); - error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); - return false; + // If the given list of types contains more than one union of primitive types, replace the + // first with a union containing an intersection of those primitive types, then remove the + // other unions and return true. Otherwise, do nothing and return false. + function intersectUnionsOfPrimitiveTypes(types: Type[]) { + let unionTypes: UnionType[] | undefined; + const index = findIndex(types, t => !!(getObjectFlags(t) & ObjectFlags.PrimitiveUnion)); + if (index < 0) { + return false; + } + let i = index + 1; + // Remove all but the first union of primitive types and collect them in + // the unionTypes array. + while (i < types.length) { + const t = types[i]; + if (getObjectFlags(t) & ObjectFlags.PrimitiveUnion) { + (unionTypes || (unionTypes = [types[index] as UnionType])).push(t as UnionType); + orderedRemoveItemAt(types, i); + } + else { + i++; } - return true; } - - function getCrossProductIntersections(types: readonly Type[]) { - const count = getCrossProductUnionSize(types); - const intersections: Type[] = []; - for (let i = 0; i < count; i++) { - const constituents = types.slice(); - let n = i; - for (let j = types.length - 1; j >= 0; j--) { - if (types[j].flags & TypeFlags.Union) { - const sourceTypes = (types[j] as UnionType).types; - const length = sourceTypes.length; - constituents[j] = sourceTypes[n % length]; - n = Math.floor(n / length); + // Return false if there was only one union of primitive types + if (!unionTypes) { + return false; + } + // We have more than one union of primitive types, now intersect them. For each + // type in each union we check if the type is matched in every union and if so + // we include it in the result. + const checked: Type[] = []; + const result: Type[] = []; + for (const u of unionTypes) { + for (const t of u.types) { + if (insertType(checked, t)) { + if (eachUnionContains(unionTypes, t)) { + insertType(result, t); } } - const t = getIntersectionType(constituents); - if (!(t.flags & TypeFlags.Never)) intersections.push(t); } - return intersections; } + // Finally replace the first union with the result + types[index] = getUnionTypeFromSortedList(result, ObjectFlags.PrimitiveUnion); + return true; + } - function getConstituentCount(type: Type): number { - return !(type.flags & TypeFlags.UnionOrIntersection) || type.aliasSymbol ? 1 : - type.flags & TypeFlags.Union && (type as UnionType).origin ? getConstituentCount((type as UnionType).origin!) : - getConstituentCountOfTypes((type as UnionOrIntersectionType).types); - } + function createIntersectionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { + const result = createType(TypeFlags.Intersection) as IntersectionType; + result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + result.types = types; + result.aliasSymbol = aliasSymbol; + result.aliasTypeArguments = aliasTypeArguments; + return result; + } - function getConstituentCountOfTypes(types: Type[]): number { - return reduceLeft(types, (n, t) => n + getConstituentCount(t), 0); + // 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 + // effectively ensure that union types are always at the top level in type representations. + // + // We do not perform structural deduplication on intersection types. Intersection types are created only by the & + // type operator and we can't reduce those because we want to support recursive intersection types. For example, + // a type alias of the form "type List = T & { next: List }" cannot be reduced during its declaration. + // Also, unlike union types, the order of the constituent types is preserved in order that overload resolution + // for intersections of types with signatures can be deterministic. + function getIntersectionType(types: readonly Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], noSupertypeReduction?: boolean): Type { + const typeMembershipMap: ESMap = new Map(); + const includes = addTypesToIntersection(typeMembershipMap, 0, types); + const typeSet: Type[] = arrayFrom(typeMembershipMap.values()); + // 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) { + return contains(typeSet, silentNeverType) ? silentNeverType : neverType; + } + if (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; } - - function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const aliasSymbol = getAliasSymbolForTypeNode(node); - const types = map(node.types, getTypeFromTypeNode); - const noSupertypeReduction = types.length === 2 && !!(types[0].flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && types[1] === emptyTypeLiteralType; - links.resolvedType = getIntersectionType(types, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol), noSupertypeReduction); - } - return links.resolvedType; + if (includes & TypeFlags.TemplateLiteral && includes & TypeFlags.StringLiteral && extractRedundantTemplateLiterals(typeSet)) { + return neverType; } - - function createIndexType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { - const result = createType(TypeFlags.Index) as IndexType; - result.type = type; - result.stringsOnly = stringsOnly; - return result; + if (includes & TypeFlags.Any) { + return includes & TypeFlags.IncludesWildcard ? wildcardType : anyType; } - - function createOriginIndexType(type: InstantiableType | UnionOrIntersectionType) { - const result = createOriginType(TypeFlags.Index) as IndexType; - result.type = type; - return result; + if (!strictNullChecks && includes & TypeFlags.Nullable) { + return includes & TypeFlags.IncludesEmptyObject ? neverType : includes & TypeFlags.Undefined ? undefinedType : nullType; } - - function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { - return stringsOnly ? - type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, /*stringsOnly*/ true)) : - type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false)); + if (includes & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || + includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral || + includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || + includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol || + includes & TypeFlags.Void && includes & TypeFlags.Undefined || + includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.DefinitelyNonNullable) { + if (!noSupertypeReduction) removeRedundantSupertypes(typeSet, includes); } - - /** - * This roughly mirrors `resolveMappedTypeMembers` in the nongeneric case, except only reports a union of the keys calculated, - * rather than manufacturing the properties. We can't just fetch the `constraintType` since that would ignore mappings - * and mapping the `constraintType` directly ignores how mapped types map _properties_ and not keys (thus ignoring subtype - * reduction in the constraintType) when possible. - * @param noIndexSignatures Indicates if _string_ index signatures should be elided. (other index signatures are always reported) - */ - function getIndexTypeForMappedType(type: MappedType, stringsOnly: boolean, noIndexSignatures: boolean | undefined) { - const typeParameter = getTypeParameterFromMappedType(type); - const constraintType = getConstraintTypeFromMappedType(type); - const nameType = getNameTypeFromMappedType(type.target as MappedType || type); - if (!nameType && !noIndexSignatures) { - // no mapping and no filtering required, just quickly bail to returning the constraint in the common case - return constraintType; - } - const keyTypes: Type[] = []; - if (isMappedTypeWithKeyofConstraintDeclaration(type)) { - // We have a { [P in keyof T]: X } - - // `getApparentType` on the T in a generic mapped type can trigger a circularity - // (conditionals and `infer` types create a circular dependency in the constraint resolution) - // so we only eagerly manifest the keys if the constraint is nongeneric - if (!isGenericIndexType(constraintType)) { - const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' - forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, stringsOnly, addMemberForKeyType); + if (includes & TypeFlags.IncludesMissingType) { + typeSet[typeSet.indexOf(undefinedType)] = missingType; + } + if (typeSet.length === 0) { + return unknownType; + } + if (typeSet.length === 1) { + return typeSet[0]; + } + const id = getTypeListId(typeSet) + getAliasId(aliasSymbol, 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 if (eachIsUnionContaining(typeSet, TypeFlags.Undefined)) { + const undefinedOrMissingType = exactOptionalPropertyTypes && some(typeSet, t => containsType((t as UnionType).types, missingType)) ? missingType : undefinedType; + removeFromEach(typeSet, TypeFlags.Undefined); + result = getUnionType([getIntersectionType(typeSet), undefinedOrMissingType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + else if (eachIsUnionContaining(typeSet, TypeFlags.Null)) { + removeFromEach(typeSet, TypeFlags.Null); + result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } else { - // we have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer the whole `keyof whatever` for later - // since it's not safe to resolve the shape of modifier type - return getIndexTypeForGenericType(type, stringsOnly); + // We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of + // the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type + // exceeds 100000 constituents, report an error. + if (!checkCrossProductUnion(typeSet)) { + return errorType; + } + const constituents = getCrossProductIntersections(typeSet); + // We attach a denormalized origin type when at least one constituent of the cross-product union is an + // intersection (i.e. when the intersection didn't just reduce one or more unions to smaller unions) and + // the denormalized origin has fewer constituents than the union itself. + const origin = some(constituents, t => !!(t.flags & TypeFlags.Intersection)) && getConstituentCountOfTypes(constituents) > getConstituentCountOfTypes(typeSet) ? createOriginUnionOrIntersectionType(TypeFlags.Intersection, typeSet) : undefined; + result = getUnionType(constituents, UnionReduction.Literal, aliasSymbol, aliasTypeArguments, origin); } } else { - forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); - } - if (isGenericIndexType(constraintType)) { // include the generic component in the resulting type - forEachType(constraintType, addMemberForKeyType); - } - // we had to pick apart the constraintType to potentially map/filter it - compare the final resulting list with the original constraintType, - // so we can return the union that preserves aliases/origin data if possible - const result = noIndexSignatures ? filterType(getUnionType(keyTypes), t => !(t.flags & (TypeFlags.Any | TypeFlags.String))) : getUnionType(keyTypes); - if (result.flags & TypeFlags.Union && constraintType.flags & TypeFlags.Union && getTypeListId((result as UnionType).types) === getTypeListId((constraintType as UnionType).types)){ - return constraintType; - } - return result; - - function addMemberForKeyType(keyType: Type) { - const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType; - // `keyof` currently always returns `string | number` for concrete `string` index signatures - the below ternary keeps that behavior for mapped types - // See `getLiteralTypeFromProperties` where there's a similar ternary to cause the same behavior. - keyTypes.push(propNameType === stringType ? stringOrNumberType : propNameType); + result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); } + intersectionTypes.set(id, result); } + return result; + } - // Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N

]: X }, to simply N. This however presumes - // that N distributes over union types, i.e. that N is equivalent to N | N | N. Specifically, we only - // want to perform the reduction when the name type of a mapped type is distributive with respect to the type variable - // introduced by the 'in' clause of the mapped type. Note that non-generic types are considered to be distributive because - // they're the same type regardless of what's being distributed over. - function hasDistributiveNameType(mappedType: MappedType) { - const typeVariable = getTypeParameterFromMappedType(mappedType); - return isDistributive(getNameTypeFromMappedType(mappedType) || typeVariable); - function isDistributive(type: Type): boolean { - return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Primitive | TypeFlags.Never | TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.NonPrimitive) ? true : - type.flags & TypeFlags.Conditional ? (type as ConditionalType).root.isDistributive && (type as ConditionalType).checkType === typeVariable : - type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) ? every((type as UnionOrIntersectionType | TemplateLiteralType).types, isDistributive) : - type.flags & TypeFlags.IndexedAccess ? isDistributive((type as IndexedAccessType).objectType) && isDistributive((type as IndexedAccessType).indexType) : - type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).baseType) && isDistributive((type as SubstitutionType).constraint): - type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) : - false; - } - } + function getCrossProductUnionSize(types: readonly Type[]) { + return reduceLeft(types, (n, t) => t.flags & TypeFlags.Union ? n * (t as UnionType).types.length : t.flags & TypeFlags.Never ? 0 : n, 1); + } - function getLiteralTypeFromPropertyName(name: PropertyName) { - if (isPrivateIdentifier(name)) { - return neverType; - } - return isIdentifier(name) ? getStringLiteralType(unescapeLeadingUnderscores(name.escapedText)) : - getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name)); + function checkCrossProductUnion(types: readonly Type[]) { + const size = getCrossProductUnionSize(types); + if (size >= 100000) { + tracing?.instant(tracing.Phase.CheckTypes, "checkCrossProductUnion_DepthLimit", { typeIds: types.map(t => t.id), size }); + error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); + return false; } + return true; + } - function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags, includeNonPublic?: boolean) { - if (includeNonPublic || !(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) { - let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType; - if (!type) { - const name = getNameOfDeclaration(prop.valueDeclaration) as PropertyName; - type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") : - name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined); - } - if (type && type.flags & include) { - return type; + function getCrossProductIntersections(types: readonly Type[]) { + const count = getCrossProductUnionSize(types); + const intersections: Type[] = []; + for (let i = 0; i < count; i++) { + const constituents = types.slice(); + let n = i; + for (let j = types.length - 1; j >= 0; j--) { + if (types[j].flags & TypeFlags.Union) { + const sourceTypes = (types[j] as UnionType).types; + const length = sourceTypes.length; + constituents[j] = sourceTypes[n % length]; + n = Math.floor(n / length); } } - return neverType; + const t = getIntersectionType(constituents); + if (!(t.flags & TypeFlags.Never)) intersections.push(t); } + return intersections; + } - function isKeyTypeIncluded(keyType: Type, include: TypeFlags): boolean { - return !!(keyType.flags & include || keyType.flags & TypeFlags.Intersection && some((keyType as IntersectionType).types, t => isKeyTypeIncluded(t, include))); - } + function getConstituentCount(type: Type): number { + return !(type.flags & TypeFlags.UnionOrIntersection) || type.aliasSymbol ? 1 : + type.flags & TypeFlags.Union && (type as UnionType).origin ? getConstituentCount((type as UnionType).origin!) : + getConstituentCountOfTypes((type as UnionOrIntersectionType).types); + } + + function getConstituentCountOfTypes(types: Type[]): number { + return reduceLeft(types, (n, t) => n + getConstituentCount(t), 0); + } - function getLiteralTypeFromProperties(type: Type, include: TypeFlags, includeOrigin: boolean) { - const origin = includeOrigin && (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference) || type.aliasSymbol) ? createOriginIndexType(type) : undefined; - const propertyTypes = map(getPropertiesOfType(type), prop => getLiteralTypeFromProperty(prop, include)); - const indexKeyTypes = map(getIndexInfosOfType(type), info => info !== enumNumberIndexInfo && isKeyTypeIncluded(info.keyType, include) ? - info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType : neverType); - return getUnionType(concatenate(propertyTypes, indexKeyTypes), UnionReduction.Literal, - /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin); + function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const aliasSymbol = getAliasSymbolForTypeNode(node); + const types = map(node.types, getTypeFromTypeNode); + const noSupertypeReduction = types.length === 2 && !!(types[0].flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && types[1] === emptyTypeLiteralType; + links.resolvedType = getIntersectionType(types, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol), noSupertypeReduction); } + return links.resolvedType; + } - /** - * A union type which is reducible upon instantiation (meaning some members are removed under certain instantiations) - * must be kept generic, as that instantiation information needs to flow through the type system. By replacing all - * type parameters in the union with a special never type that is treated as a literal in `getReducedType`, we can cause the `getReducedType` logic - * to reduce the resulting type if possible (since only intersections with conflicting literal-typed properties are reducible). - */ - function isPossiblyReducibleByInstantiation(type: Type): boolean { - const uniqueFilled = getUniqueLiteralFilledInstantiation(type); - return getReducedType(uniqueFilled) !== uniqueFilled; - } - - function shouldDeferIndexType(type: Type) { - return !!(type.flags & TypeFlags.InstantiableNonPrimitive || - isGenericTupleType(type) || - isGenericMappedType(type) && !hasDistributiveNameType(type) || - type.flags & TypeFlags.Union && some((type as UnionType).types, isPossiblyReducibleByInstantiation) || - type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); - } - - function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type { - type = getReducedType(type); - return shouldDeferIndexType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) : - type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : - type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : - getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) : - type === wildcardType ? wildcardType : - type.flags & TypeFlags.Unknown ? neverType : - type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType : - getLiteralTypeFromProperties(type, (noIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (stringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), - stringsOnly === keyofStringsOnly && !noIndexSignatures); - } - - function getExtractStringType(type: Type) { - if (keyofStringsOnly) { - return type; + function createIndexType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { + const result = createType(TypeFlags.Index) as IndexType; + result.type = type; + result.stringsOnly = stringsOnly; + return result; + } + + function createOriginIndexType(type: InstantiableType | UnionOrIntersectionType) { + const result = createOriginType(TypeFlags.Index) as IndexType; + result.type = type; + return result; + } + + function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { + return stringsOnly ? + type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, /*stringsOnly*/ true)) : + type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false)); + } + + /** + * This roughly mirrors `resolveMappedTypeMembers` in the nongeneric case, except only reports a union of the keys calculated, + * rather than manufacturing the properties. We can't just fetch the `constraintType` since that would ignore mappings + * and mapping the `constraintType` directly ignores how mapped types map _properties_ and not keys (thus ignoring subtype + * reduction in the constraintType) when possible. + * @param noIndexSignatures Indicates if _string_ index signatures should be elided. (other index signatures are always reported) + */ + function getIndexTypeForMappedType(type: MappedType, stringsOnly: boolean, noIndexSignatures: boolean | undefined) { + const typeParameter = getTypeParameterFromMappedType(type); + const constraintType = getConstraintTypeFromMappedType(type); + const nameType = getNameTypeFromMappedType(type.target as MappedType || type); + if (!nameType && !noIndexSignatures) { + // no mapping and no filtering required, just quickly bail to returning the constraint in the common case + return constraintType; + } + const keyTypes: Type[] = []; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } + + // `getApparentType` on the T in a generic mapped type can trigger a circularity + // (conditionals and `infer` types create a circular dependency in the constraint resolution) + // so we only eagerly manifest the keys if the constraint is nongeneric + if (!isGenericIndexType(constraintType)) { + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, stringsOnly, addMemberForKeyType); + } + else { + // we have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer the whole `keyof whatever` for later + // since it's not safe to resolve the shape of modifier type + return getIndexTypeForGenericType(type, stringsOnly); } - const extractTypeAlias = getGlobalExtractSymbol(); - return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType; } + else { + forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); + } + if (isGenericIndexType(constraintType)) { // include the generic component in the resulting type + forEachType(constraintType, addMemberForKeyType); + } + // we had to pick apart the constraintType to potentially map/filter it - compare the final resulting list with the original constraintType, + // so we can return the union that preserves aliases/origin data if possible + const result = noIndexSignatures ? filterType(getUnionType(keyTypes), t => !(t.flags & (TypeFlags.Any | TypeFlags.String))) : getUnionType(keyTypes); + if (result.flags & TypeFlags.Union && constraintType.flags & TypeFlags.Union && getTypeListId((result as UnionType).types) === getTypeListId((constraintType as UnionType).types)){ + return constraintType; + } + return result; - function getIndexTypeOrString(type: Type): Type { - const indexType = getExtractStringType(getIndexType(type)); - return indexType.flags & TypeFlags.Never ? stringType : indexType; + function addMemberForKeyType(keyType: Type) { + const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType; + // `keyof` currently always returns `string | number` for concrete `string` index signatures - the below ternary keeps that behavior for mapped types + // See `getLiteralTypeFromProperties` where there's a similar ternary to cause the same behavior. + keyTypes.push(propNameType === stringType ? stringOrNumberType : propNameType); } + } - function getTypeFromTypeOperatorNode(node: TypeOperatorNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - switch (node.operator) { - case SyntaxKind.KeyOfKeyword: - links.resolvedType = getIndexType(getTypeFromTypeNode(node.type)); - break; - case SyntaxKind.UniqueKeyword: - links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword - ? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent)) - : errorType; - break; - case SyntaxKind.ReadonlyKeyword: - links.resolvedType = getTypeFromTypeNode(node.type); - break; - default: - throw Debug.assertNever(node.operator); - } - } - return links.resolvedType; + // Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N

]: X }, to simply N. This however presumes + // that N distributes over union types, i.e. that N is equivalent to N | N | N. Specifically, we only + // want to perform the reduction when the name type of a mapped type is distributive with respect to the type variable + // introduced by the 'in' clause of the mapped type. Note that non-generic types are considered to be distributive because + // they're the same type regardless of what's being distributed over. + function hasDistributiveNameType(mappedType: MappedType) { + const typeVariable = getTypeParameterFromMappedType(mappedType); + return isDistributive(getNameTypeFromMappedType(mappedType) || typeVariable); + function isDistributive(type: Type): boolean { + return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Primitive | TypeFlags.Never | TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.NonPrimitive) ? true : + type.flags & TypeFlags.Conditional ? (type as ConditionalType).root.isDistributive && (type as ConditionalType).checkType === typeVariable : + type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) ? every((type as UnionOrIntersectionType | TemplateLiteralType).types, isDistributive) : + type.flags & TypeFlags.IndexedAccess ? isDistributive((type as IndexedAccessType).objectType) && isDistributive((type as IndexedAccessType).indexType) : + type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).baseType) && isDistributive((type as SubstitutionType).constraint): + type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) : + false; } + } - function getTypeFromTemplateTypeNode(node: TemplateLiteralTypeNode) { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getTemplateLiteralType( - [node.head.text, ...map(node.templateSpans, span => span.literal.text)], - map(node.templateSpans, span => getTypeFromTypeNode(span.type))); - } - return links.resolvedType; + function getLiteralTypeFromPropertyName(name: PropertyName) { + if (isPrivateIdentifier(name)) { + return neverType; } + return isIdentifier(name) ? getStringLiteralType(unescapeLeadingUnderscores(name.escapedText)) : + getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name)); + } - function getTemplateLiteralType(texts: readonly string[], types: readonly Type[]): Type { - const unionIndex = findIndex(types, t => !!(t.flags & (TypeFlags.Never | TypeFlags.Union))); - if (unionIndex >= 0) { - return checkCrossProductUnion(types) ? - mapType(types[unionIndex], t => getTemplateLiteralType(texts, replaceElement(types, unionIndex, t))) : - errorType; - } - if (contains(types, wildcardType)) { - return wildcardType; - } - const newTypes: Type[] = []; - const newTexts: string[] = []; - let text = texts[0]; - if (!addSpans(texts, types)) { - return stringType; - } - if (newTypes.length === 0) { - return getStringLiteralType(text); - } - newTexts.push(text); - if (every(newTexts, t => t === "")) { - if (every(newTypes, t => !!(t.flags & TypeFlags.String))) { - return stringType; - } - // Normalize `${Mapping}` into Mapping - if (newTypes.length === 1 && isPatternLiteralType(newTypes[0])) { - return newTypes[0]; - } - } - const id = `${getTypeListId(newTypes)}|${map(newTexts, t => t.length).join(",")}|${newTexts.join("")}`; - let type = templateLiteralTypes.get(id); + function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags, includeNonPublic?: boolean) { + if (includeNonPublic || !(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) { + let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType; if (!type) { - templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes)); + const name = getNameOfDeclaration(prop.valueDeclaration) as PropertyName; + type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") : + name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined); } - return type; - - function addSpans(texts: readonly string[] | string, types: readonly Type[]): boolean { - const isTextsArray = isArray(texts); - for (let i = 0; i < types.length; i++) { - const t = types[i]; - const addText = isTextsArray ? texts[i + 1] : texts; - if (t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) { - text += getTemplateStringForType(t) || ""; - text += addText; - if (!isTextsArray) return true; - } - else if (t.flags & TypeFlags.TemplateLiteral) { - text += (t as TemplateLiteralType).texts[0]; - if (!addSpans((t as TemplateLiteralType).texts, (t as TemplateLiteralType).types)) return false; - text += addText; - if (!isTextsArray) return true; - } - else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t)) { - newTypes.push(t); - newTexts.push(text); - text = addText; - } - else if (t.flags & TypeFlags.Intersection) { - const added = addSpans(texts[i + 1], (t as IntersectionType).types); - if (!added) return false; - } - else if (isTextsArray) { - return false; - } - } - return true; + if (type && type.flags & include) { + return type; } } + return neverType; + } - function getTemplateStringForType(type: Type) { - return type.flags & TypeFlags.StringLiteral ? (type as StringLiteralType).value : - type.flags & TypeFlags.NumberLiteral ? "" + (type as NumberLiteralType).value : - type.flags & TypeFlags.BigIntLiteral ? pseudoBigIntToString((type as BigIntLiteralType).value) : - type.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) ? (type as IntrinsicType).intrinsicName : - undefined; - } + function isKeyTypeIncluded(keyType: Type, include: TypeFlags): boolean { + return !!(keyType.flags & include || keyType.flags & TypeFlags.Intersection && some((keyType as IntersectionType).types, t => isKeyTypeIncluded(t, include))); + } - function createTemplateLiteralType(texts: readonly string[], types: readonly Type[]) { - const type = createType(TypeFlags.TemplateLiteral) as TemplateLiteralType; - type.texts = texts; - type.types = types; + function getLiteralTypeFromProperties(type: Type, include: TypeFlags, includeOrigin: boolean) { + const origin = includeOrigin && (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference) || type.aliasSymbol) ? createOriginIndexType(type) : undefined; + const propertyTypes = map(getPropertiesOfType(type), prop => getLiteralTypeFromProperty(prop, include)); + const indexKeyTypes = map(getIndexInfosOfType(type), info => info !== enumNumberIndexInfo && isKeyTypeIncluded(info.keyType, include) ? + info.keyType === stringType && include & TypeFlags.Number ? stringOrNumberType : info.keyType : neverType); + return getUnionType(concatenate(propertyTypes, indexKeyTypes), UnionReduction.Literal, + /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin); + } + + /** + * A union type which is reducible upon instantiation (meaning some members are removed under certain instantiations) + * must be kept generic, as that instantiation information needs to flow through the type system. By replacing all + * type parameters in the union with a special never type that is treated as a literal in `getReducedType`, we can cause the `getReducedType` logic + * to reduce the resulting type if possible (since only intersections with conflicting literal-typed properties are reducible). + */ + function isPossiblyReducibleByInstantiation(type: Type): boolean { + const uniqueFilled = getUniqueLiteralFilledInstantiation(type); + return getReducedType(uniqueFilled) !== uniqueFilled; + } + + function shouldDeferIndexType(type: Type) { + return !!(type.flags & TypeFlags.InstantiableNonPrimitive || + isGenericTupleType(type) || + isGenericMappedType(type) && !hasDistributiveNameType(type) || + type.flags & TypeFlags.Union && some((type as UnionType).types, isPossiblyReducibleByInstantiation) || + type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); + } + + function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type { + type = getReducedType(type); + return shouldDeferIndexType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) : + type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : + type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : + getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) : + type === wildcardType ? wildcardType : + type.flags & TypeFlags.Unknown ? neverType : + type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType : + getLiteralTypeFromProperties(type, (noIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (stringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), + stringsOnly === keyofStringsOnly && !noIndexSignatures); + } + + function getExtractStringType(type: Type) { + if (keyofStringsOnly) { return type; } + const extractTypeAlias = getGlobalExtractSymbol(); + return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType; + } - function getStringMappingType(symbol: Symbol, type: Type): Type { - return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) : - type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) : - type.flags & TypeFlags.TemplateLiteral ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) : - // Mapping> === Mapping - type.flags & TypeFlags.StringMapping && symbol === type.symbol ? type : - type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.StringMapping) || isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) : - // This handles Mapping<`${number}`> and Mapping<`${bigint}`> - isPatternLiteralPlaceholderType(type) ? getStringMappingTypeForGenericType(symbol, getTemplateLiteralType(["", ""], [type])) : - type; - } + function getIndexTypeOrString(type: Type): Type { + const indexType = getExtractStringType(getIndexType(type)); + return indexType.flags & TypeFlags.Never ? stringType : indexType; + } - function applyStringMapping(symbol: Symbol, str: string) { - switch (intrinsicTypeKinds.get(symbol.escapedName as string)) { - case IntrinsicTypeKind.Uppercase: return str.toUpperCase(); - case IntrinsicTypeKind.Lowercase: return str.toLowerCase(); - case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1); - case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1); + function getTypeFromTypeOperatorNode(node: TypeOperatorNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + switch (node.operator) { + case SyntaxKind.KeyOfKeyword: + links.resolvedType = getIndexType(getTypeFromTypeNode(node.type)); + break; + case SyntaxKind.UniqueKeyword: + links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword + ? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent)) + : errorType; + break; + case SyntaxKind.ReadonlyKeyword: + links.resolvedType = getTypeFromTypeNode(node.type); + break; + default: + throw Debug.assertNever(node.operator); } - return str; } + return links.resolvedType; + } + + function getTypeFromTemplateTypeNode(node: TemplateLiteralTypeNode) { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getTemplateLiteralType( + [node.head.text, ...map(node.templateSpans, span => span.literal.text)], + map(node.templateSpans, span => getTypeFromTypeNode(span.type))); + } + return links.resolvedType; + } - function applyTemplateStringMapping(symbol: Symbol, texts: readonly string[], types: readonly Type[]): [texts: readonly string[], types: readonly Type[]] { - switch (intrinsicTypeKinds.get(symbol.escapedName as string)) { - case IntrinsicTypeKind.Uppercase: return [texts.map(t => t.toUpperCase()), types.map(t => getStringMappingType(symbol, t))]; - case IntrinsicTypeKind.Lowercase: return [texts.map(t => t.toLowerCase()), types.map(t => getStringMappingType(symbol, t))]; - case IntrinsicTypeKind.Capitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toUpperCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types]; - case IntrinsicTypeKind.Uncapitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toLowerCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types]; + function getTemplateLiteralType(texts: readonly string[], types: readonly Type[]): Type { + const unionIndex = findIndex(types, t => !!(t.flags & (TypeFlags.Never | TypeFlags.Union))); + if (unionIndex >= 0) { + return checkCrossProductUnion(types) ? + mapType(types[unionIndex], t => getTemplateLiteralType(texts, replaceElement(types, unionIndex, t))) : + errorType; + } + if (contains(types, wildcardType)) { + return wildcardType; + } + const newTypes: Type[] = []; + const newTexts: string[] = []; + let text = texts[0]; + if (!addSpans(texts, types)) { + return stringType; + } + if (newTypes.length === 0) { + return getStringLiteralType(text); + } + newTexts.push(text); + if (every(newTexts, t => t === "")) { + if (every(newTypes, t => !!(t.flags & TypeFlags.String))) { + return stringType; + } + // Normalize `${Mapping}` into Mapping + if (newTypes.length === 1 && isPatternLiteralType(newTypes[0])) { + return newTypes[0]; } - return [texts, types]; } + const id = `${getTypeListId(newTypes)}|${map(newTexts, t => t.length).join(",")}|${newTexts.join("")}`; + let type = templateLiteralTypes.get(id); + if (!type) { + templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes)); + } + return type; - function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type { - const id = `${getSymbolId(symbol)},${getTypeId(type)}`; - let result = stringMappingTypes.get(id); - if (!result) { - stringMappingTypes.set(id, result = createStringMappingType(symbol, type)); + function addSpans(texts: readonly string[] | string, types: readonly Type[]): boolean { + const isTextsArray = isArray(texts); + for (let i = 0; i < types.length; i++) { + const t = types[i]; + const addText = isTextsArray ? texts[i + 1] : texts; + if (t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) { + text += getTemplateStringForType(t) || ""; + text += addText; + if (!isTextsArray) return true; + } + else if (t.flags & TypeFlags.TemplateLiteral) { + text += (t as TemplateLiteralType).texts[0]; + if (!addSpans((t as TemplateLiteralType).texts, (t as TemplateLiteralType).types)) return false; + text += addText; + if (!isTextsArray) return true; + } + else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t)) { + newTypes.push(t); + newTexts.push(text); + text = addText; + } + else if (t.flags & TypeFlags.Intersection) { + const added = addSpans(texts[i + 1], (t as IntersectionType).types); + if (!added) return false; + } + else if (isTextsArray) { + return false; + } } - return result; + return true; } + } - function createStringMappingType(symbol: Symbol, type: Type) { - const result = createTypeWithSymbol(TypeFlags.StringMapping, symbol) as StringMappingType; - result.type = type; - return result; + function getTemplateStringForType(type: Type) { + return type.flags & TypeFlags.StringLiteral ? (type as StringLiteralType).value : + type.flags & TypeFlags.NumberLiteral ? "" + (type as NumberLiteralType).value : + type.flags & TypeFlags.BigIntLiteral ? pseudoBigIntToString((type as BigIntLiteralType).value) : + type.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) ? (type as IntrinsicType).intrinsicName : + undefined; + } + + function createTemplateLiteralType(texts: readonly string[], types: readonly Type[]) { + const type = createType(TypeFlags.TemplateLiteral) as TemplateLiteralType; + type.texts = texts; + type.types = types; + return type; + } + + function getStringMappingType(symbol: Symbol, type: Type): Type { + return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) : + type.flags & TypeFlags.StringLiteral ? getStringLiteralType(applyStringMapping(symbol, (type as StringLiteralType).value)) : + type.flags & TypeFlags.TemplateLiteral ? getTemplateLiteralType(...applyTemplateStringMapping(symbol, (type as TemplateLiteralType).texts, (type as TemplateLiteralType).types)) : + // Mapping> === Mapping + type.flags & TypeFlags.StringMapping && symbol === type.symbol ? type : + type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.StringMapping) || isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) : + // This handles Mapping<`${number}`> and Mapping<`${bigint}`> + isPatternLiteralPlaceholderType(type) ? getStringMappingTypeForGenericType(symbol, getTemplateLiteralType(["", ""], [type])) : + type; + } + + function applyStringMapping(symbol: Symbol, str: string) { + switch (intrinsicTypeKinds.get(symbol.escapedName as string)) { + case IntrinsicTypeKind.Uppercase: return str.toUpperCase(); + case IntrinsicTypeKind.Lowercase: return str.toLowerCase(); + case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1); + case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1); } + return str; + } - function createIndexedAccessType(objectType: Type, indexType: Type, accessFlags: AccessFlags, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { - const type = createType(TypeFlags.IndexedAccess) as IndexedAccessType; - type.objectType = objectType; - type.indexType = indexType; - type.accessFlags = accessFlags; - type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = aliasTypeArguments; - return type; + function applyTemplateStringMapping(symbol: Symbol, texts: readonly string[], types: readonly Type[]): [texts: readonly string[], types: readonly Type[]] { + switch (intrinsicTypeKinds.get(symbol.escapedName as string)) { + case IntrinsicTypeKind.Uppercase: return [texts.map(t => t.toUpperCase()), types.map(t => getStringMappingType(symbol, t))]; + case IntrinsicTypeKind.Lowercase: return [texts.map(t => t.toLowerCase()), types.map(t => getStringMappingType(symbol, t))]; + case IntrinsicTypeKind.Capitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toUpperCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types]; + case IntrinsicTypeKind.Uncapitalize: return [texts[0] === "" ? texts : [texts[0].charAt(0).toLowerCase() + texts[0].slice(1), ...texts.slice(1)], texts[0] === "" ? [getStringMappingType(symbol, types[0]), ...types.slice(1)] : types]; } + return [texts, types]; + } - /** - * Returns if a type is or consists of a JSLiteral object type - * In addition to objects which are directly literals, - * * unions where every element is a jsliteral - * * intersections where at least one element is a jsliteral - * * and instantiable types constrained to a jsliteral - * Should all count as literals and not print errors on access or assignment of possibly existing properties. - * This mirrors the behavior of the index signature propagation, to which this behaves similarly (but doesn't affect assignability or inference). - */ - function isJSLiteralType(type: Type): boolean { - if (noImplicitAny) { - return false; // Flag is meaningless under `noImplicitAny` mode - } - if (getObjectFlags(type) & ObjectFlags.JSLiteral) { - return true; - } - if (type.flags & TypeFlags.Union) { - return every((type as UnionType).types, isJSLiteralType); - } - if (type.flags & TypeFlags.Intersection) { - return some((type as IntersectionType).types, isJSLiteralType); - } - if (type.flags & TypeFlags.Instantiable) { - const constraint = getResolvedBaseConstraint(type); - return constraint !== type && isJSLiteralType(constraint); - } - return false; + function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type { + const id = `${getSymbolId(symbol)},${getTypeId(type)}`; + let result = stringMappingTypes.get(id); + if (!result) { + stringMappingTypes.set(id, result = createStringMappingType(symbol, type)); } + return result; + } - function getPropertyNameFromIndex(indexType: Type, accessNode: StringLiteral | Identifier | PrivateIdentifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) { - return isTypeUsableAsPropertyName(indexType) ? - getPropertyNameFromType(indexType) : - accessNode && isPropertyName(accessNode) ? - // late bound names are handled in the first branch, so here we only need to handle normal names - getPropertyNameForPropertyNameNode(accessNode) : - undefined; + function createStringMappingType(symbol: Symbol, type: Type) { + const result = createTypeWithSymbol(TypeFlags.StringMapping, symbol) as StringMappingType; + result.type = type; + return result; + } + + function createIndexedAccessType(objectType: Type, indexType: Type, accessFlags: AccessFlags, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { + const type = createType(TypeFlags.IndexedAccess) as IndexedAccessType; + type.objectType = objectType; + type.indexType = indexType; + type.accessFlags = accessFlags; + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = aliasTypeArguments; + return type; + } + + /** + * Returns if a type is or consists of a JSLiteral object type + * In addition to objects which are directly literals, + * * unions where every element is a jsliteral + * * intersections where at least one element is a jsliteral + * * and instantiable types constrained to a jsliteral + * Should all count as literals and not print errors on access or assignment of possibly existing properties. + * This mirrors the behavior of the index signature propagation, to which this behaves similarly (but doesn't affect assignability or inference). + */ + function isJSLiteralType(type: Type): boolean { + if (noImplicitAny) { + return false; // Flag is meaningless under `noImplicitAny` mode + } + if (getObjectFlags(type) & ObjectFlags.JSLiteral) { + return true; + } + if (type.flags & TypeFlags.Union) { + return every((type as UnionType).types, isJSLiteralType); } + if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, isJSLiteralType); + } + if (type.flags & TypeFlags.Instantiable) { + const constraint = getResolvedBaseConstraint(type); + return constraint !== type && isJSLiteralType(constraint); + } + return false; + } - function isUncalledFunctionReference(node: Node, symbol: Symbol) { - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { - const parent = findAncestor(node.parent, n => !isAccessExpression(n)) || node.parent; - if (isCallLikeExpression(parent)) { - return isCallOrNewExpression(parent) && isIdentifier(node) && hasMatchingArgument(parent, node); - } - return every(symbol.declarations, d => !isFunctionLike(d) || !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); + function getPropertyNameFromIndex(indexType: Type, accessNode: StringLiteral | Identifier | PrivateIdentifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) { + return isTypeUsableAsPropertyName(indexType) ? + getPropertyNameFromType(indexType) : + accessNode && isPropertyName(accessNode) ? + // late bound names are handled in the first branch, so here we only need to handle normal names + getPropertyNameForPropertyNameNode(accessNode) : + undefined; + } + + function isUncalledFunctionReference(node: Node, symbol: Symbol) { + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) { + const parent = findAncestor(node.parent, n => !isAccessExpression(n)) || node.parent; + if (isCallLikeExpression(parent)) { + return isCallOrNewExpression(parent) && isIdentifier(node) && hasMatchingArgument(parent, node); } - return true; + return every(symbol.declarations, d => !isFunctionLike(d) || !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); } + return true; + } - function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) { - const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; - const propName = accessNode && isPrivateIdentifier(accessNode) ? undefined : getPropertyNameFromIndex(indexType, accessNode); + function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) { + const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; + const propName = accessNode && isPrivateIdentifier(accessNode) ? undefined : getPropertyNameFromIndex(indexType, accessNode); - if (propName !== undefined) { - if (accessFlags & AccessFlags.Contextual) { - return getTypeOfPropertyOfContextualType(objectType, propName) || anyType; + if (propName !== undefined) { + if (accessFlags & AccessFlags.Contextual) { + return getTypeOfPropertyOfContextualType(objectType, propName) || anyType; + } + const prop = getPropertyOfType(objectType, propName); + if (prop) { + if (accessFlags & AccessFlags.ReportDeprecated && accessNode && prop.declarations && isDeprecatedSymbol(prop) && isUncalledFunctionReference(accessNode, prop)) { + const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode); + addDeprecatedSuggestion(deprecatedNode, prop.declarations, propName as string); } - const prop = getPropertyOfType(objectType, propName); - if (prop) { - if (accessFlags & AccessFlags.ReportDeprecated && accessNode && prop.declarations && isDeprecatedSymbol(prop) && isUncalledFunctionReference(accessNode, prop)) { - const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode); - addDeprecatedSuggestion(deprecatedNode, prop.declarations, propName as string); + if (accessExpression) { + markPropertyAsReferenced(prop, accessExpression, isSelfTypeAccess(accessExpression.expression, objectType.symbol)); + if (isAssignmentToReadonlyEntity(accessExpression, prop, getAssignmentTargetKind(accessExpression))) { + error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop)); + return undefined; } - if (accessExpression) { - markPropertyAsReferenced(prop, accessExpression, isSelfTypeAccess(accessExpression.expression, objectType.symbol)); - if (isAssignmentToReadonlyEntity(accessExpression, prop, getAssignmentTargetKind(accessExpression))) { - error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop)); - return undefined; - } - if (accessFlags & AccessFlags.CacheSymbol) { - getNodeLinks(accessNode!).resolvedSymbol = prop; - } - if (isThisPropertyAccessInConstructor(accessExpression, prop)) { - return autoType; - } + if (accessFlags & AccessFlags.CacheSymbol) { + getNodeLinks(accessNode!).resolvedSymbol = prop; + } + if (isThisPropertyAccessInConstructor(accessExpression, prop)) { + return autoType; } - const propType = getTypeOfSymbol(prop); - return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? - getFlowTypeOfReference(accessExpression, propType) : - propType; } - if (everyType(objectType, isTupleType) && isNumericLiteralName(propName)) { - const index = +propName; - if (accessNode && everyType(objectType, t => !(t as TupleTypeReference).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) { - const indexNode = getIndexNodeForAccessExpression(accessNode); - if (isTupleType(objectType)) { - if (index < 0) { - error(indexNode, Diagnostics.A_tuple_type_cannot_be_indexed_with_a_negative_value); - return undefinedType; - } - error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2, - typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName)); - } - else { - error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); + const propType = getTypeOfSymbol(prop); + return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? + getFlowTypeOfReference(accessExpression, propType) : + propType; + } + if (everyType(objectType, isTupleType) && isNumericLiteralName(propName)) { + const index = +propName; + if (accessNode && everyType(objectType, t => !(t as TupleTypeReference).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) { + const indexNode = getIndexNodeForAccessExpression(accessNode); + if (isTupleType(objectType)) { + if (index < 0) { + error(indexNode, Diagnostics.A_tuple_type_cannot_be_indexed_with_a_negative_value); + return undefinedType; } + error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2, + typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName)); } - if (index >= 0) { - errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType)); - return mapType(objectType, t => { - const restType = getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType; - return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([restType, undefinedType]) : restType; - }); + else { + error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); } } - } - if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { - if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) { - return objectType; + if (index >= 0) { + errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType)); + return mapType(objectType, t => { + const restType = getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType; + return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([restType, undefinedType]) : restType; + }); } - // If no index signature is applicable, we default to the string index signature. In effect, this means the string - // index signature applies even when accessing with a symbol-like type. - const indexInfo = getApplicableIndexInfo(objectType, indexType) || getIndexInfoOfType(objectType, stringType); - if (indexInfo) { - if (accessFlags & AccessFlags.NoIndexSignatures && indexInfo.keyType !== numberType) { - if (accessExpression) { - error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType)); - } - return undefined; + } + } + if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { + if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) { + return objectType; + } + // If no index signature is applicable, we default to the string index signature. In effect, this means the string + // index signature applies even when accessing with a symbol-like type. + const indexInfo = getApplicableIndexInfo(objectType, indexType) || getIndexInfoOfType(objectType, stringType); + if (indexInfo) { + if (accessFlags & AccessFlags.NoIndexSignatures && indexInfo.keyType !== numberType) { + if (accessExpression) { + error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType)); } - if (accessNode && indexInfo.keyType === stringType && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { - const indexNode = getIndexNodeForAccessExpression(accessNode); - error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); - return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; - } - errorIfWritingToReadonlyIndex(indexInfo); - // When accessing an enum object with its own type, - // e.g. E[E.A] for enum E { A }, undefined shouldn't - // be included in the result type - if ((accessFlags & AccessFlags.IncludeUndefined) && - !(objectType.symbol && - objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum) && - (indexType.symbol && - indexType.flags & TypeFlags.EnumLiteral && - getParentOfSymbol(indexType.symbol) === objectType.symbol))) { - return getUnionType([indexInfo.type, undefinedType]); - } - return indexInfo.type; - } - if (indexType.flags & TypeFlags.Never) { - return neverType; - } - if (isJSLiteralType(objectType)) { - return anyType; + return undefined; } - if (accessExpression && !isConstEnumObjectType(objectType)) { - if (isObjectLiteralType(objectType)) { - if (noImplicitAny && indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { - diagnostics.add(createDiagnosticForNode(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType))); - return undefinedType; - } - else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { - const types = map((objectType as ResolvedType).properties, property => { - return getTypeOfSymbol(property); - }); - return getUnionType(append(types, undefinedType)); - } + if (accessNode && indexInfo.keyType === stringType && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { + const indexNode = getIndexNodeForAccessExpression(accessNode); + error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); + return accessFlags & AccessFlags.IncludeUndefined ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; + } + errorIfWritingToReadonlyIndex(indexInfo); + // When accessing an enum object with its own type, + // e.g. E[E.A] for enum E { A }, undefined shouldn't + // be included in the result type + if ((accessFlags & AccessFlags.IncludeUndefined) && + !(objectType.symbol && + objectType.symbol.flags & (SymbolFlags.RegularEnum | SymbolFlags.ConstEnum) && + (indexType.symbol && + indexType.flags & TypeFlags.EnumLiteral && + getParentOfSymbol(indexType.symbol) === objectType.symbol))) { + return getUnionType([indexInfo.type, undefinedType]); + } + return indexInfo.type; + } + if (indexType.flags & TypeFlags.Never) { + return neverType; + } + if (isJSLiteralType(objectType)) { + return anyType; + } + if (accessExpression && !isConstEnumObjectType(objectType)) { + if (isObjectLiteralType(objectType)) { + if (noImplicitAny && indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { + diagnostics.add(createDiagnosticForNode(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType))); + return undefinedType; + } + else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { + const types = map((objectType as ResolvedType).properties, property => { + return getTypeOfSymbol(property); + }); + return getUnionType(append(types, undefinedType)); } + } - 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)); + 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 && !(accessFlags & AccessFlags.SuppressNoImplicitAnyError)) { + if (propName !== undefined && typeHasStaticProperty(propName, objectType)) { + const typeName = typeToString(objectType); + error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName as string, typeName, typeName + "[" + getTextOfNode(accessExpression.argumentExpression) + "]"); } - else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !(accessFlags & AccessFlags.SuppressNoImplicitAnyError)) { - if (propName !== undefined && typeHasStaticProperty(propName, objectType)) { - const typeName = typeToString(objectType); - error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName as string, typeName, typeName + "[" + getTextOfNode(accessExpression.argumentExpression) + "]"); - } - else if (getIndexTypeOfType(objectType, numberType)) { - error(accessExpression.argumentExpression, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number); + else if (getIndexTypeOfType(objectType, numberType)) { + error(accessExpression.argumentExpression, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number); + } + else { + let suggestion: string | undefined; + if (propName !== undefined && (suggestion = getSuggestionForNonexistentProperty(propName as string, objectType))) { + if (suggestion !== undefined) { + error(accessExpression.argumentExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName as string, typeToString(objectType), suggestion); + } } else { - let suggestion: string | undefined; - if (propName !== undefined && (suggestion = getSuggestionForNonexistentProperty(propName as string, objectType))) { - if (suggestion !== undefined) { - error(accessExpression.argumentExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName as string, typeToString(objectType), suggestion); - } + const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression, indexType); + if (suggestion !== undefined) { + 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 { - const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression, indexType); - if (suggestion !== undefined) { - 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); + 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 { - 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)); + 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)); } } } - return undefined; } + return undefined; } - if (isJSLiteralType(objectType)) { - return anyType; + } + if (isJSLiteralType(objectType)) { + return anyType; + } + if (accessNode) { + const indexNode = getIndexNodeForAccessExpression(accessNode); + if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { + error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (indexType as StringLiteralType | NumberLiteralType).value, typeToString(objectType)); } - if (accessNode) { - const indexNode = getIndexNodeForAccessExpression(accessNode); - if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { - error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (indexType as StringLiteralType | NumberLiteralType).value, typeToString(objectType)); - } - else if (indexType.flags & (TypeFlags.String | TypeFlags.Number)) { - error(indexNode, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(indexType)); - } - else { - error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); - } + else if (indexType.flags & (TypeFlags.String | TypeFlags.Number)) { + error(indexNode, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(indexType)); } - if (isTypeAny(indexType)) { - return indexType; + else { + error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); } - return undefined; + } + if (isTypeAny(indexType)) { + 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 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) { - return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : - accessNode.kind === SyntaxKind.IndexedAccessType ? accessNode.indexType : - accessNode.kind === SyntaxKind.ComputedPropertyName ? accessNode.expression : - accessNode; - } + function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) { + return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : + accessNode.kind === SyntaxKind.IndexedAccessType ? accessNode.indexType : + accessNode.kind === SyntaxKind.ComputedPropertyName ? accessNode.expression : + accessNode; + } - function isPatternLiteralPlaceholderType(type: Type): boolean { - return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type); - } + function isPatternLiteralPlaceholderType(type: Type): boolean { + return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type); + } - function isPatternLiteralType(type: Type) { - return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) || - !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type); - } + function isPatternLiteralType(type: Type) { + return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) || + !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type); + } - function isGenericType(type: Type): boolean { - return !!getGenericObjectFlags(type); - } + function isGenericType(type: Type): boolean { + return !!getGenericObjectFlags(type); + } - function isGenericObjectType(type: Type): boolean { - return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericObjectType); - } + function isGenericObjectType(type: Type): boolean { + return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericObjectType); + } - function isGenericIndexType(type: Type): boolean { - return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericIndexType); - } + function isGenericIndexType(type: Type): boolean { + return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericIndexType); + } - function getGenericObjectFlags(type: Type): ObjectFlags { - if (type.flags & TypeFlags.UnionOrIntersection) { - if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) { - (type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericTypeComputed | - reduceLeft((type as UnionOrIntersectionType).types, (flags, t) => flags | getGenericObjectFlags(t), 0); - } - return (type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericType; + function getGenericObjectFlags(type: Type): ObjectFlags { + if (type.flags & TypeFlags.UnionOrIntersection) { + if (!((type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) { + (type as UnionOrIntersectionType).objectFlags |= ObjectFlags.IsGenericTypeComputed | + reduceLeft((type as UnionOrIntersectionType).types, (flags, t) => flags | getGenericObjectFlags(t), 0); } - if (type.flags & TypeFlags.Substitution) { - if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) { - (type as SubstitutionType).objectFlags |= ObjectFlags.IsGenericTypeComputed | - getGenericObjectFlags((type as SubstitutionType).baseType) | getGenericObjectFlags((type as SubstitutionType).constraint); - } - return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType; + return (type as UnionOrIntersectionType).objectFlags & ObjectFlags.IsGenericType; + } + if (type.flags & TypeFlags.Substitution) { + if (!((type as SubstitutionType).objectFlags & ObjectFlags.IsGenericTypeComputed)) { + (type as SubstitutionType).objectFlags |= ObjectFlags.IsGenericTypeComputed | + getGenericObjectFlags((type as SubstitutionType).baseType) | getGenericObjectFlags((type as SubstitutionType).constraint); } - return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) | - (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0); + return (type as SubstitutionType).objectFlags & ObjectFlags.IsGenericType; } + return (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) | + (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0); + } - function getSimplifiedType(type: Type, writing: boolean): Type { - return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type as IndexedAccessType, writing) : - type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type as ConditionalType, writing) : - type; + function getSimplifiedType(type: Type, writing: boolean): Type { + return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type as IndexedAccessType, writing) : + type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type as ConditionalType, writing) : + type; + } + + function distributeIndexOverObjectType(objectType: Type, indexType: Type, writing: boolean) { + // (T | U)[K] -> T[K] | U[K] (reading) + // (T | U)[K] -> T[K] & U[K] (writing) + // (T & U)[K] -> T[K] & U[K] + if (objectType.flags & TypeFlags.Union || objectType.flags & TypeFlags.Intersection && !shouldDeferIndexType(objectType)) { + const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing)); + return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types); } + } + + function distributeObjectOverIndexType(objectType: Type, indexType: Type, writing: boolean) { + // T[A | B] -> T[A] | T[B] (reading) + // T[A | B] -> T[A] & T[B] (writing) + if (indexType.flags & TypeFlags.Union) { + const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing)); + return writing ? getIntersectionType(types) : getUnionType(types); + } + } - function distributeIndexOverObjectType(objectType: Type, indexType: Type, writing: boolean) { + // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return + // the type itself if no transformation is possible. The writing flag indicates that the type is + // the target of an assignment. + function getSimplifiedIndexedAccessType(type: IndexedAccessType, writing: boolean): Type { + const cache = writing ? "simplifiedForWriting" : "simplifiedForReading"; + if (type[cache]) { + return type[cache] === circularConstraintType ? type : type[cache]!; + } + type[cache] = circularConstraintType; + // We recursively simplify the object type as it may in turn be an indexed access type. For example, with + // '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type. + const objectType = getSimplifiedType(type.objectType, writing); + const indexType = getSimplifiedType(type.indexType, writing); + // T[A | B] -> T[A] | T[B] (reading) + // T[A | B] -> T[A] & T[B] (writing) + const distributedOverIndex = distributeObjectOverIndexType(objectType, indexType, writing); + if (distributedOverIndex) { + return type[cache] = distributedOverIndex; + } + // Only do the inner distributions if the index can no longer be instantiated to cause index distribution again + if (!(indexType.flags & TypeFlags.Instantiable)) { // (T | U)[K] -> T[K] | U[K] (reading) // (T | U)[K] -> T[K] & U[K] (writing) // (T & U)[K] -> T[K] & U[K] - if (objectType.flags & TypeFlags.Union || objectType.flags & TypeFlags.Intersection && !shouldDeferIndexType(objectType)) { - const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing)); - return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types); + const distributedOverObject = distributeIndexOverObjectType(objectType, indexType, writing); + if (distributedOverObject) { + return type[cache] = distributedOverObject; } } + // So ultimately (reading): + // ((A & B) | C)[K1 | K2] -> ((A & B) | C)[K1] | ((A & B) | C)[K2] -> (A & B)[K1] | C[K1] | (A & B)[K2] | C[K2] -> (A[K1] & B[K1]) | C[K1] | (A[K2] & B[K2]) | C[K2] - function distributeObjectOverIndexType(objectType: Type, indexType: Type, writing: boolean) { - // T[A | B] -> T[A] | T[B] (reading) - // T[A | B] -> T[A] & T[B] (writing) - if (indexType.flags & TypeFlags.Union) { - const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing)); - return writing ? getIntersectionType(types) : getUnionType(types); + // A generic tuple type indexed by a number exists only when the index type doesn't select a + // fixed element. We simplify to either the combined type of all elements (when the index type + // the actual number type) or to the combined type of all non-fixed elements. + if (isGenericTupleType(objectType) && indexType.flags & TypeFlags.NumberLike) { + const elementType = getElementTypeOfSliceOfTupleType(objectType, indexType.flags & TypeFlags.Number ? 0 : objectType.target.fixedLength, /*endSkipCount*/ 0, writing); + if (elementType) { + return type[cache] = elementType; } } - - // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return - // the type itself if no transformation is possible. The writing flag indicates that the type is - // the target of an assignment. - function getSimplifiedIndexedAccessType(type: IndexedAccessType, writing: boolean): Type { - const cache = writing ? "simplifiedForWriting" : "simplifiedForReading"; - if (type[cache]) { - return type[cache] === circularConstraintType ? type : type[cache]!; - } - type[cache] = circularConstraintType; - // We recursively simplify the object type as it may in turn be an indexed access type. For example, with - // '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type. - const objectType = getSimplifiedType(type.objectType, writing); - const indexType = getSimplifiedType(type.indexType, writing); - // T[A | B] -> T[A] | T[B] (reading) - // T[A | B] -> T[A] & T[B] (writing) - const distributedOverIndex = distributeObjectOverIndexType(objectType, indexType, writing); - if (distributedOverIndex) { - return type[cache] = distributedOverIndex; - } - // Only do the inner distributions if the index can no longer be instantiated to cause index distribution again - if (!(indexType.flags & TypeFlags.Instantiable)) { - // (T | U)[K] -> T[K] | U[K] (reading) - // (T | U)[K] -> T[K] & U[K] (writing) - // (T & U)[K] -> T[K] & U[K] - const distributedOverObject = distributeIndexOverObjectType(objectType, indexType, writing); - if (distributedOverObject) { - return type[cache] = distributedOverObject; - } + // If the object type is a mapped type { [P in K]: E }, where K is generic, or { [P in K as N]: E }, where + // K is generic and N is assignable to P, instantiate E using a mapper that substitutes the index type for P. + // For example, for an index access { [P in K]: Box }[X], we construct the type Box. + if (isGenericMappedType(objectType)) { + const nameType = getNameTypeFromMappedType(objectType); + if (!nameType || isTypeAssignableTo(nameType, getTypeParameterFromMappedType(objectType))) { + return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing)); } - // So ultimately (reading): - // ((A & B) | C)[K1 | K2] -> ((A & B) | C)[K1] | ((A & B) | C)[K2] -> (A & B)[K1] | C[K1] | (A & B)[K2] | C[K2] -> (A[K1] & B[K1]) | C[K1] | (A[K2] & B[K2]) | C[K2] + } + return type[cache] = type; + } - // A generic tuple type indexed by a number exists only when the index type doesn't select a - // fixed element. We simplify to either the combined type of all elements (when the index type - // the actual number type) or to the combined type of all non-fixed elements. - if (isGenericTupleType(objectType) && indexType.flags & TypeFlags.NumberLike) { - const elementType = getElementTypeOfSliceOfTupleType(objectType, indexType.flags & TypeFlags.Number ? 0 : objectType.target.fixedLength, /*endSkipCount*/ 0, writing); - if (elementType) { - return type[cache] = elementType; - } - } - // If the object type is a mapped type { [P in K]: E }, where K is generic, or { [P in K as N]: E }, where - // K is generic and N is assignable to P, instantiate E using a mapper that substitutes the index type for P. - // For example, for an index access { [P in K]: Box }[X], we construct the type Box. - if (isGenericMappedType(objectType)) { - const nameType = getNameTypeFromMappedType(objectType); - if (!nameType || isTypeAssignableTo(nameType, getTypeParameterFromMappedType(objectType))) { - return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing)); - } + 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; } - 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 (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); - } + 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); - return instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); } + return type; + } - function getIndexedAccessType(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - return getIndexedAccessTypeOrUndefined(objectType, indexType, accessFlags, accessNode, aliasSymbol, aliasTypeArguments) || (accessNode ? errorType : unknownType); - } + /** + * 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 indexTypeLessThan(indexType: Type, limit: number) { - return everyType(indexType, t => { - if (t.flags & TypeFlags.StringOrNumberLiteral) { - const propName = getPropertyNameFromType(t as StringLiteralType | NumberLiteralType); - if (isNumericLiteralName(propName)) { - const index = +propName; - return index >= 0 && index < limit; - } - } - return false; - }); - } + function substituteIndexedMappedType(objectType: MappedType, index: Type) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); + const templateMapper = combineTypeMappers(objectType.mapper, mapper); + return instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); + } - function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined { - if (objectType === wildcardType || indexType === wildcardType) { - return wildcardType; - } - // If the object type has a string index signature and no other members we know that the result will - // always be the type of that index signature and we can simplify accordingly. - if (isStringIndexSignatureOnlyType(objectType) && !(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { - indexType = stringType; - } - // In noUncheckedIndexedAccess mode, indexed access operations that occur in an expression in a read position and resolve to - // an index signature have 'undefined' included in their type. - if (compilerOptions.noUncheckedIndexedAccess && accessFlags & AccessFlags.ExpressionPosition) accessFlags |= AccessFlags.IncludeUndefined; - // If the index type is generic, or if the object type is generic and doesn't originate in an expression and - // the operation isn't exclusively indexing the fixed (non-variadic) portion of a tuple type, we are performing - // a higher-order index access where we cannot meaningfully access the properties of the object type. Note that - // for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in an expression. This is to - // preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved - // eagerly using the constraint type of 'this' at the given location. - if (isGenericIndexType(indexType) || (accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType ? - isGenericTupleType(objectType) && !indexTypeLessThan(indexType, objectType.target.fixedLength) : - isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)))) { - if (objectType.flags & TypeFlags.AnyOrUnknown) { - return objectType; - } - // Defer the operation by creating an indexed access type. - const persistentAccessFlags = accessFlags & AccessFlags.Persistent; - const id = objectType.id + "," + indexType.id + "," + persistentAccessFlags + getAliasId(aliasSymbol, aliasTypeArguments); - let type = indexedAccessTypes.get(id); - if (!type) { - indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, persistentAccessFlags, aliasSymbol, aliasTypeArguments)); - } + function getIndexedAccessType(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + return getIndexedAccessTypeOrUndefined(objectType, indexType, accessFlags, accessNode, aliasSymbol, aliasTypeArguments) || (accessNode ? errorType : unknownType); + } - return type; - } - // In the following we resolve T[K] to the type of the property in T selected by K. - // We treat boolean as different from other unions to improve errors; - // skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'. - const apparentObjectType = getReducedApparentType(objectType); - if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) { - const propTypes: Type[] = []; - let wasMissingProp = false; - for (const t of (indexType as UnionType).types) { - const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, accessNode, accessFlags | (wasMissingProp ? AccessFlags.SuppressNoImplicitAnyError : 0)); - if (propType) { - propTypes.push(propType); - } - else if (!accessNode) { - // If there's no error node, we can immeditely stop, since error reporting is off - return undefined; - } - else { - // Otherwise we set a flag and return at the end of the loop so we still mark all errors - wasMissingProp = true; - } - } - if (wasMissingProp) { - return undefined; + function indexTypeLessThan(indexType: Type, limit: number) { + return everyType(indexType, t => { + if (t.flags & TypeFlags.StringOrNumberLiteral) { + const propName = getPropertyNameFromType(t as StringLiteralType | NumberLiteralType); + if (isNumericLiteralName(propName)) { + const index = +propName; + return index >= 0 && index < limit; } - return accessFlags & AccessFlags.Writing - ? getIntersectionType(propTypes, aliasSymbol, aliasTypeArguments) - : getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } - return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol | AccessFlags.ReportDeprecated); - } + return false; + }); + } - function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const objectType = getTypeFromTypeNode(node.objectType); - const indexType = getTypeFromTypeNode(node.indexType); - const potentialAlias = getAliasSymbolForTypeNode(node); - links.resolvedType = getIndexedAccessType(objectType, indexType, AccessFlags.None, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias)); + function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, accessFlags = AccessFlags.None, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined { + if (objectType === wildcardType || indexType === wildcardType) { + return wildcardType; + } + // If the object type has a string index signature and no other members we know that the result will + // always be the type of that index signature and we can simplify accordingly. + if (isStringIndexSignatureOnlyType(objectType) && !(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { + indexType = stringType; + } + // In noUncheckedIndexedAccess mode, indexed access operations that occur in an expression in a read position and resolve to + // an index signature have 'undefined' included in their type. + if (compilerOptions.noUncheckedIndexedAccess && accessFlags & AccessFlags.ExpressionPosition) accessFlags |= AccessFlags.IncludeUndefined; + // If the index type is generic, or if the object type is generic and doesn't originate in an expression and + // the operation isn't exclusively indexing the fixed (non-variadic) portion of a tuple type, we are performing + // a higher-order index access where we cannot meaningfully access the properties of the object type. Note that + // for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in an expression. This is to + // preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved + // eagerly using the constraint type of 'this' at the given location. + if (isGenericIndexType(indexType) || (accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType ? + isGenericTupleType(objectType) && !indexTypeLessThan(indexType, objectType.target.fixedLength) : + isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)))) { + if (objectType.flags & TypeFlags.AnyOrUnknown) { + return objectType; } - return links.resolvedType; - } - - function getTypeFromMappedTypeNode(node: MappedTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const type = createObjectType(ObjectFlags.Mapped, node.symbol) as MappedType; - type.declaration = node; - type.aliasSymbol = getAliasSymbolForTypeNode(node); - type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol); - links.resolvedType = type; - // Eagerly resolve the constraint type which forces an error if the constraint type circularly - // references itself through one or more type aliases. - getConstraintTypeFromMappedType(type); + // Defer the operation by creating an indexed access type. + const persistentAccessFlags = accessFlags & AccessFlags.Persistent; + const id = objectType.id + "," + indexType.id + "," + persistentAccessFlags + getAliasId(aliasSymbol, aliasTypeArguments); + let type = indexedAccessTypes.get(id); + if (!type) { + indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, persistentAccessFlags, aliasSymbol, aliasTypeArguments)); } - return links.resolvedType; - } - function getActualTypeVariable(type: Type): Type { - if (type.flags & TypeFlags.Substitution) { - return (type as SubstitutionType).baseType; - } - if (type.flags & TypeFlags.IndexedAccess && ( - (type as IndexedAccessType).objectType.flags & TypeFlags.Substitution || - (type as IndexedAccessType).indexType.flags & TypeFlags.Substitution)) { - return getIndexedAccessType(getActualTypeVariable((type as IndexedAccessType).objectType), getActualTypeVariable((type as IndexedAccessType).indexType)); - } return type; } - - function maybeCloneTypeParameter(p: TypeParameter) { - const constraint = getConstraintOfTypeParameter(p); - return constraint && (isGenericObjectType(constraint) || isGenericIndexType(constraint)) ? cloneTypeParameter(p) : p; - } - - function isTypicalNondistributiveConditional(root: ConditionalRoot) { - return !root.isDistributive && isSingletonTupleType(root.node.checkType) && isSingletonTupleType(root.node.extendsType); - } - - function isSingletonTupleType(node: TypeNode) { - return isTupleTypeNode(node) && - length(node.elements) === 1 && - !isOptionalTypeNode(node.elements[0]) && - !isRestTypeNode(node.elements[0]) && - !(isNamedTupleMember(node.elements[0]) && (node.elements[0].questionToken || node.elements[0].dotDotDotToken)); - } - - /** - * We syntactually check for common nondistributive conditional shapes and unwrap them into - * the intended comparison - we do this so we can check if the unwrapped types are generic or - * not and appropriately defer condition calculation - */ - function unwrapNondistributiveConditionalTuple(root: ConditionalRoot, type: Type) { - return isTypicalNondistributiveConditional(root) && isTupleType(type) ? getTypeArguments(type)[0] : type; - } - - function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - let result; - let extraTypes: Type[] | undefined; - let tailCount = 0; - // We loop here for an immediately nested conditional type in the false position, effectively treating - // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for - // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of - // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive - // cases we increment the tail recursion counter and stop after 1000 iterations. - while (true) { - if (tailCount === 1000) { - error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); - result = errorType; - break; + // In the following we resolve T[K] to the type of the property in T selected by K. + // We treat boolean as different from other unions to improve errors; + // skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'. + const apparentObjectType = getReducedApparentType(objectType); + if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) { + const propTypes: Type[] = []; + let wasMissingProp = false; + for (const t of (indexType as UnionType).types) { + const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, accessNode, accessFlags | (wasMissingProp ? AccessFlags.SuppressNoImplicitAnyError : 0)); + if (propType) { + propTypes.push(propType); + } + else if (!accessNode) { + // If there's no error node, we can immeditely stop, since error reporting is off + return undefined; } - const isUnwrapped = isTypicalNondistributiveConditional(root); - const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper); - const checkTypeInstantiable = isGenericType(checkType); - const extendsType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), mapper); - if (checkType === wildcardType || extendsType === wildcardType) { - return wildcardType; - } - let combinedMapper: TypeMapper | undefined; - if (root.inferTypeParameters) { - // When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be - // instantiated with the context, so it doesn't need the mapper for the inference contex - however the constraint - // may refer to another _root_, _uncloned_ `infer` type parameter [1], or to something mapped by `mapper` [2]. - // [1] Eg, if we have `Foo` and `Foo` - `B` is constrained to `T`, which, in turn, has been instantiated - // as `number` - // Conversely, if we have `Foo`, `B` is still constrained to `T` and `T` is instantiated as `A` - // [2] Eg, if we have `Foo` and `Foo` where `Q` is mapped by `mapper` into `number` - `B` is constrained to `T` - // which is in turn instantiated as `Q`, which is in turn instantiated as `number`. - // So we need to: - // * Clone the type parameters so their constraints can be instantiated in the context of `mapper` (otherwise theyd only get inference context information) - // * Set the clones to both map the conditional's enclosing `mapper` and the original params - // * instantiate the extends type with the clones - // * incorporate all of the component mappers into the combined mapper for the true and false members - // This means we have three mappers that need applying: - // * The original `mapper` used to create this conditional - // * The mapper that maps the old root type parameter to the clone (`freshMapper`) - // * The mapper that maps the clone to its inference result (`context.mapper`) - const freshParams = sameMap(root.inferTypeParameters, maybeCloneTypeParameter); - const freshMapper = freshParams !== root.inferTypeParameters ? createTypeMapper(root.inferTypeParameters, freshParams) : undefined; - const context = createInferenceContext(freshParams, /*signature*/ undefined, InferenceFlags.None); - if (freshMapper) { - const freshCombinedMapper = combineTypeMappers(mapper, freshMapper); - for (const p of freshParams) { - if (root.inferTypeParameters.indexOf(p) === -1) { - p.mapper = freshCombinedMapper; - } - } - } - if (!checkTypeInstantiable) { - // We don't want inferences from constraints as they may cause us to eagerly resolve the - // conditional type instead of deferring resolution. Also, we always want strict function - // types rules (i.e. proper contravariance) for inferences. - inferTypes(context.inferences, checkType, instantiateType(extendsType, freshMapper), InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); - } - const innerMapper = combineTypeMappers(freshMapper, context.mapper); - // It's possible for 'infer T' type paramteters to be given uninstantiated constraints when the - // those type parameters are used in type references (see getInferredTypeParameterConstraint). For - // that reason we need context.mapper to be first in the combined mapper. See #42636 for examples. - combinedMapper = mapper ? combineTypeMappers(innerMapper, mapper) : innerMapper; - } - // Instantiate the extends type including inferences for 'infer T' type parameters - const inferredExtendsType = combinedMapper ? instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), combinedMapper) : extendsType; - // We attempt to resolve the conditional type only when the check and extends types are non-generic - if (!checkTypeInstantiable && !isGenericType(inferredExtendsType)) { - // 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 (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && ((checkType.flags & TypeFlags.Any && !isUnwrapped) || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { - // Return union of trueType and falseType for 'any' since it matches anything - if (checkType.flags & TypeFlags.Any && !isUnwrapped) { - (extraTypes || (extraTypes = [])).push(instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper)); - } - // If falseType is an immediately nested conditional type that isn't distributive or has an - // identical checkType, switch to that type and loop. - const falseType = getTypeFromTypeNode(root.node.falseType); - if (falseType.flags & TypeFlags.Conditional) { - const newRoot = (falseType as ConditionalType).root; - if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) { - root = newRoot; - continue; - } - if (canTailRecurse(falseType, mapper)) { - continue; - } - } - result = instantiateType(falseType, mapper); - break; - } - // 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 - // that has no constraint. This ensures that, for example, the type - // type Foo = T extends { x: string } ? string : number - // doesn't immediately resolve to 'string' instead of being deferred. - if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { - const trueType = getTypeFromTypeNode(root.node.trueType); - const trueMapper = combinedMapper || mapper; - if (canTailRecurse(trueType, trueMapper)) { - continue; - } - result = instantiateType(trueType, trueMapper); - break; - } + else { + // Otherwise we set a flag and return at the end of the loop so we still mark all errors + wasMissingProp = true; } - // Return a deferred type for a check that is neither definitely true nor definitely false - result = createType(TypeFlags.Conditional) as ConditionalType; - result.root = root; - result.checkType = instantiateType(root.checkType, mapper); - result.extendsType = instantiateType(root.extendsType, mapper); - result.mapper = mapper; - result.combinedMapper = combinedMapper; - result.aliasSymbol = aliasSymbol || root.aliasSymbol; - result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 - break; } - return extraTypes ? getUnionType(append(extraTypes, result)) : result; - // We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and - // (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check - // type. Note that recursion is possible only through aliased conditional types, so we only increment the tail - // recursion counter for those. - function canTailRecurse(newType: Type, newMapper: TypeMapper | undefined) { - if (newType.flags & TypeFlags.Conditional && newMapper) { - const newRoot = (newType as ConditionalType).root; - if (newRoot.outerTypeParameters) { - const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper); - const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)); - const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); - const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined; - if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { - root = newRoot; - mapper = newRootMapper; - aliasSymbol = undefined; - aliasTypeArguments = undefined; - if (newRoot.aliasSymbol) { - tailCount++; - } - return true; - } - } - } - return false; + if (wasMissingProp) { + return undefined; } + return accessFlags & AccessFlags.Writing + ? getIntersectionType(propTypes, aliasSymbol, aliasTypeArguments) + : getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } + return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol | AccessFlags.ReportDeprecated); + } - function getTrueTypeFromConditionalType(type: ConditionalType) { - return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper)); + function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const objectType = getTypeFromTypeNode(node.objectType); + const indexType = getTypeFromTypeNode(node.indexType); + const potentialAlias = getAliasSymbolForTypeNode(node); + links.resolvedType = getIndexedAccessType(objectType, indexType, AccessFlags.None, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias)); } + return links.resolvedType; + } - function getFalseTypeFromConditionalType(type: ConditionalType) { - return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(getTypeFromTypeNode(type.root.node.falseType), type.mapper)); - } + function getTypeFromMappedTypeNode(node: MappedTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const type = createObjectType(ObjectFlags.Mapped, node.symbol) as MappedType; + type.declaration = node; + type.aliasSymbol = getAliasSymbolForTypeNode(node); + type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol); + links.resolvedType = type; + // Eagerly resolve the constraint type which forces an error if the constraint type circularly + // references itself through one or more type aliases. + getConstraintTypeFromMappedType(type); + } + return links.resolvedType; + } - function getInferredTrueTypeFromConditionalType(type: ConditionalType) { - return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.combinedMapper) : getTrueTypeFromConditionalType(type)); + function getActualTypeVariable(type: Type): Type { + if (type.flags & TypeFlags.Substitution) { + return (type as SubstitutionType).baseType; } - - function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined { - let result: TypeParameter[] | undefined; - if (node.locals) { - node.locals.forEach(symbol => { - if (symbol.flags & SymbolFlags.TypeParameter) { - result = append(result, getDeclaredTypeOfSymbol(symbol)); - } - }); - } - return result; + if (type.flags & TypeFlags.IndexedAccess && ( + (type as IndexedAccessType).objectType.flags & TypeFlags.Substitution || + (type as IndexedAccessType).indexType.flags & TypeFlags.Substitution)) { + return getIndexedAccessType(getActualTypeVariable((type as IndexedAccessType).objectType), getActualTypeVariable((type as IndexedAccessType).indexType)); } + return type; + } - function isDistributionDependent(root: ConditionalRoot) { - return root.isDistributive && ( - isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.trueType) || - isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.falseType)); - } + function maybeCloneTypeParameter(p: TypeParameter) { + const constraint = getConstraintOfTypeParameter(p); + return constraint && (isGenericObjectType(constraint) || isGenericIndexType(constraint)) ? cloneTypeParameter(p) : p; + } - function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - const checkType = getTypeFromTypeNode(node.checkType); - const aliasSymbol = getAliasSymbolForTypeNode(node); - const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); - const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); - const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node)); - const root: ConditionalRoot = { - node, - checkType, - extendsType: getTypeFromTypeNode(node.extendsType), - isDistributive: !!(checkType.flags & TypeFlags.TypeParameter), - inferTypeParameters: getInferTypeParameters(node), - outerTypeParameters, - instantiations: undefined, - aliasSymbol, - aliasTypeArguments - }; - links.resolvedType = getConditionalType(root, /*mapper*/ undefined); - if (outerTypeParameters) { - root.instantiations = new Map(); - root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); - } - } - return links.resolvedType; - } + function isTypicalNondistributiveConditional(root: ConditionalRoot) { + return !root.isDistributive && isSingletonTupleType(root.node.checkType) && isSingletonTupleType(root.node.extendsType); + } - function getTypeFromInferTypeNode(node: InferTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter)); - } - return links.resolvedType; - } + function isSingletonTupleType(node: TypeNode) { + return isTupleTypeNode(node) && + length(node.elements) === 1 && + !isOptionalTypeNode(node.elements[0]) && + !isRestTypeNode(node.elements[0]) && + !(isNamedTupleMember(node.elements[0]) && (node.elements[0].questionToken || node.elements[0].dotDotDotToken)); + } - function getIdentifierChain(node: EntityName): Identifier[] { - if (isIdentifier(node)) { - return [node]; + /** + * We syntactually check for common nondistributive conditional shapes and unwrap them into + * the intended comparison - we do this so we can check if the unwrapped types are generic or + * not and appropriately defer condition calculation + */ + function unwrapNondistributiveConditionalTuple(root: ConditionalRoot, type: Type) { + return isTypicalNondistributiveConditional(root) && isTupleType(type) ? getTypeArguments(type)[0] : type; + } + + function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + let result; + let extraTypes: Type[] | undefined; + let tailCount = 0; + // We loop here for an immediately nested conditional type in the false position, effectively treating + // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for + // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of + // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive + // cases we increment the tail recursion counter and stop after 1000 iterations. + while (true) { + if (tailCount === 1000) { + error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); + result = errorType; + break; } - else { - return append(getIdentifierChain(node.left), node.right); + const isUnwrapped = isTypicalNondistributiveConditional(root); + const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper); + const checkTypeInstantiable = isGenericType(checkType); + const extendsType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), mapper); + if (checkType === wildcardType || extendsType === wildcardType) { + return wildcardType; } - } - - function getTypeFromImportTypeNode(node: ImportTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - if (node.isTypeOf && node.typeArguments) { // Only the non-typeof form can make use of type arguments - error(node, Diagnostics.Type_arguments_cannot_be_used_here); - links.resolvedSymbol = unknownSymbol; - return links.resolvedType = errorType; - } - if (!isLiteralImportTypeNode(node)) { - error(node.argument, Diagnostics.String_literal_expected); - links.resolvedSymbol = unknownSymbol; - return links.resolvedType = errorType; - } - const targetMeaning = node.isTypeOf ? SymbolFlags.Value : node.flags & NodeFlags.JSDoc ? SymbolFlags.Value | SymbolFlags.Type : SymbolFlags.Type; - // TODO: Future work: support unions/generics/whatever via a deferred import-type - const innerModuleSymbol = resolveExternalModuleName(node, node.argument.literal); - if (!innerModuleSymbol) { - links.resolvedSymbol = unknownSymbol; - return links.resolvedType = errorType; - } - const isExportEquals = !!innerModuleSymbol.exports?.get(InternalSymbolName.ExportEquals); - const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false); - if (!nodeIsMissing(node.qualifier)) { - const nameStack: Identifier[] = getIdentifierChain(node.qualifier!); - let currentNamespace = moduleSymbol; - let current: Identifier | undefined; - while (current = nameStack.shift()) { - const meaning = nameStack.length ? SymbolFlags.Namespace : targetMeaning; - // typeof a.b.c is normally resolved using `checkExpression` which in turn defers to `checkQualifiedName` - // That, in turn, ultimately uses `getPropertyOfType` on the type of the symbol, which differs slightly from - // the `exports` lookup process that only looks up namespace members which is used for most type references - const mergedResolvedSymbol = getMergedSymbol(resolveSymbol(currentNamespace)); - const symbolFromVariable = node.isTypeOf || isInJSFile(node) && isExportEquals - ? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText, /*skipObjectFunctionPropertyAugment*/ false, /*includeTypeOnlyMembers*/ true) - : undefined; - const symbolFromModule = node.isTypeOf ? undefined : getSymbol(getExportsOfSymbol(mergedResolvedSymbol), current.escapedText, meaning); - const next = symbolFromModule ?? symbolFromVariable; - if (!next) { - error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current)); - return links.resolvedType = errorType; + let combinedMapper: TypeMapper | undefined; + if (root.inferTypeParameters) { + // When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be + // instantiated with the context, so it doesn't need the mapper for the inference contex - however the constraint + // may refer to another _root_, _uncloned_ `infer` type parameter [1], or to something mapped by `mapper` [2]. + // [1] Eg, if we have `Foo` and `Foo` - `B` is constrained to `T`, which, in turn, has been instantiated + // as `number` + // Conversely, if we have `Foo`, `B` is still constrained to `T` and `T` is instantiated as `A` + // [2] Eg, if we have `Foo` and `Foo` where `Q` is mapped by `mapper` into `number` - `B` is constrained to `T` + // which is in turn instantiated as `Q`, which is in turn instantiated as `number`. + // So we need to: + // * Clone the type parameters so their constraints can be instantiated in the context of `mapper` (otherwise theyd only get inference context information) + // * Set the clones to both map the conditional's enclosing `mapper` and the original params + // * instantiate the extends type with the clones + // * incorporate all of the component mappers into the combined mapper for the true and false members + // This means we have three mappers that need applying: + // * The original `mapper` used to create this conditional + // * The mapper that maps the old root type parameter to the clone (`freshMapper`) + // * The mapper that maps the clone to its inference result (`context.mapper`) + const freshParams = sameMap(root.inferTypeParameters, maybeCloneTypeParameter); + const freshMapper = freshParams !== root.inferTypeParameters ? createTypeMapper(root.inferTypeParameters, freshParams) : undefined; + const context = createInferenceContext(freshParams, /*signature*/ undefined, InferenceFlags.None); + if (freshMapper) { + const freshCombinedMapper = combineTypeMappers(mapper, freshMapper); + for (const p of freshParams) { + if (root.inferTypeParameters.indexOf(p) === -1) { + p.mapper = freshCombinedMapper; + } + } + } + if (!checkTypeInstantiable) { + // We don't want inferences from constraints as they may cause us to eagerly resolve the + // conditional type instead of deferring resolution. Also, we always want strict function + // types rules (i.e. proper contravariance) for inferences. + inferTypes(context.inferences, checkType, instantiateType(extendsType, freshMapper), InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + } + const innerMapper = combineTypeMappers(freshMapper, context.mapper); + // It's possible for 'infer T' type paramteters to be given uninstantiated constraints when the + // those type parameters are used in type references (see getInferredTypeParameterConstraint). For + // that reason we need context.mapper to be first in the combined mapper. See #42636 for examples. + combinedMapper = mapper ? combineTypeMappers(innerMapper, mapper) : innerMapper; + } + // Instantiate the extends type including inferences for 'infer T' type parameters + const inferredExtendsType = combinedMapper ? instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), combinedMapper) : extendsType; + // We attempt to resolve the conditional type only when the check and extends types are non-generic + if (!checkTypeInstantiable && !isGenericType(inferredExtendsType)) { + // 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 (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && ((checkType.flags & TypeFlags.Any && !isUnwrapped) || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { + // Return union of trueType and falseType for 'any' since it matches anything + if (checkType.flags & TypeFlags.Any && !isUnwrapped) { + (extraTypes || (extraTypes = [])).push(instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper)); + } + // If falseType is an immediately nested conditional type that isn't distributive or has an + // identical checkType, switch to that type and loop. + const falseType = getTypeFromTypeNode(root.node.falseType); + if (falseType.flags & TypeFlags.Conditional) { + const newRoot = (falseType as ConditionalType).root; + if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) { + root = newRoot; + continue; + } + if (canTailRecurse(falseType, mapper)) { + continue; } - getNodeLinks(current).resolvedSymbol = next; - getNodeLinks(current.parent).resolvedSymbol = next; - currentNamespace = next; } - links.resolvedType = resolveImportSymbolType(node, links, currentNamespace, targetMeaning); + result = instantiateType(falseType, mapper); + break; } - else { - if (moduleSymbol.flags & targetMeaning) { - links.resolvedType = resolveImportSymbolType(node, links, moduleSymbol, targetMeaning); - } - else { - const errorMessage = targetMeaning === SymbolFlags.Value - ? Diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here - : Diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here_Did_you_mean_typeof_import_0; - - error(node, errorMessage, node.argument.literal.text); - - links.resolvedSymbol = unknownSymbol; - links.resolvedType = errorType; + // 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 + // that has no constraint. This ensures that, for example, the type + // type Foo = T extends { x: string } ? string : number + // doesn't immediately resolve to 'string' instead of being deferred. + if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { + const trueType = getTypeFromTypeNode(root.node.trueType); + const trueMapper = combinedMapper || mapper; + if (canTailRecurse(trueType, trueMapper)) { + continue; } + result = instantiateType(trueType, trueMapper); + break; } } - return links.resolvedType; - } - - function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) { - const resolvedSymbol = resolveSymbol(symbol); - links.resolvedSymbol = resolvedSymbol; - if (meaning === SymbolFlags.Value) { - return getTypeOfSymbol(symbol); // intentionally doesn't use resolved symbol so type is cached as expected on the alias - } - else { - return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol - } - } - - function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - // Deferred resolution of members is handled by resolveObjectTypeMembers - const aliasSymbol = getAliasSymbolForTypeNode(node); - if (getMembersOfSymbol(node.symbol).size === 0 && !aliasSymbol) { - links.resolvedType = emptyTypeLiteralType; - } - else { - let type = createObjectType(ObjectFlags.Anonymous, node.symbol); - type.aliasSymbol = aliasSymbol; - type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); - if (isJSDocTypeLiteral(node) && node.isArrayType) { - type = createArrayType(type); + // Return a deferred type for a check that is neither definitely true nor definitely false + result = createType(TypeFlags.Conditional) as ConditionalType; + result.root = root; + result.checkType = instantiateType(root.checkType, mapper); + result.extendsType = instantiateType(root.extendsType, mapper); + result.mapper = mapper; + result.combinedMapper = combinedMapper; + result.aliasSymbol = aliasSymbol || root.aliasSymbol; + result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 + break; + } + return extraTypes ? getUnionType(append(extraTypes, result)) : result; + // We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and + // (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check + // type. Note that recursion is possible only through aliased conditional types, so we only increment the tail + // recursion counter for those. + function canTailRecurse(newType: Type, newMapper: TypeMapper | undefined) { + if (newType.flags & TypeFlags.Conditional && newMapper) { + const newRoot = (newType as ConditionalType).root; + if (newRoot.outerTypeParameters) { + const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper); + const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)); + const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); + const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined; + if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { + root = newRoot; + mapper = newRootMapper; + aliasSymbol = undefined; + aliasTypeArguments = undefined; + if (newRoot.aliasSymbol) { + tailCount++; + } + return true; } - links.resolvedType = type; } } - return links.resolvedType; + return false; } + } - function getAliasSymbolForTypeNode(node: Node) { - let host = node.parent; - while (isParenthesizedTypeNode(host) || isJSDocTypeExpression(host) || isTypeOperatorNode(host) && host.operator === SyntaxKind.ReadonlyKeyword) { - host = host.parent; - } - return isTypeAlias(host) ? getSymbolOfNode(host) : undefined; - } + function getTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper)); + } - function getTypeArgumentsForAliasSymbol(symbol: Symbol | undefined) { - return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined; - } + function getFalseTypeFromConditionalType(type: ConditionalType) { + return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(getTypeFromTypeNode(type.root.node.falseType), type.mapper)); + } - function isNonGenericObjectType(type: Type) { - return !!(type.flags & TypeFlags.Object) && !isGenericMappedType(type); - } + function getInferredTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.combinedMapper) : getTrueTypeFromConditionalType(type)); + } - function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: Type) { - return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)); + function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined { + let result: TypeParameter[] | undefined; + if (node.locals) { + node.locals.forEach(symbol => { + if (symbol.flags & SymbolFlags.TypeParameter) { + result = append(result, getDeclaredTypeOfSymbol(symbol)); + } + }); } + return result; + } - function tryMergeUnionOfObjectTypeAndEmptyObject(type: Type, readonly: boolean): Type { - if (!(type.flags & TypeFlags.Union)) { - return type; - } - if (every((type as UnionType).types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) { - return find((type as UnionType).types, isEmptyObjectType) || emptyObjectType; - } - const firstType = find((type as UnionType).types, t => !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t)); - if (!firstType) { - return type; - } - const secondType = find((type as UnionType).types, t => t !== firstType && !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t)); - if (secondType) { - return type; - } - return getAnonymousPartialType(firstType); + function isDistributionDependent(root: ConditionalRoot) { + return root.isDistributive && ( + isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.trueType) || + isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.falseType)); + } - function getAnonymousPartialType(type: Type) { - // gets the type as if it had been spread, but where everything in the spread is made optional - const members = createSymbolTable(); - for (const prop of getPropertiesOfType(type)) { - if (getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) { - // do nothing, skip privates - } - else if (isSpreadableProperty(prop)) { - const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); - const flags = SymbolFlags.Property | SymbolFlags.Optional; - const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0)); - result.type = isSetonlyAccessor ? undefinedType : addOptionality(getTypeOfSymbol(prop), /*isProperty*/ true); - result.declarations = prop.declarations; - result.nameType = getSymbolLinks(prop).nameType; - result.syntheticOrigin = prop; - members.set(prop.escapedName, result); - } - } - const spread = createAnonymousType(type.symbol, members, emptyArray, emptyArray, getIndexInfosOfType(type)); - spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - return spread; + function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const checkType = getTypeFromTypeNode(node.checkType); + const aliasSymbol = getAliasSymbolForTypeNode(node); + const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); + const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); + const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node)); + const root: ConditionalRoot = { + node, + checkType, + extendsType: getTypeFromTypeNode(node.extendsType), + isDistributive: !!(checkType.flags & TypeFlags.TypeParameter), + inferTypeParameters: getInferTypeParameters(node), + outerTypeParameters, + instantiations: undefined, + aliasSymbol, + aliasTypeArguments + }; + links.resolvedType = getConditionalType(root, /*mapper*/ undefined); + if (outerTypeParameters) { + root.instantiations = new Map(); + root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); } } + return links.resolvedType; + } - /** - * Since the source of spread types are object literals, which are not binary, - * this function should be called in a left folding style, with left = previous result of getSpreadType - * and right = the new element to be spread. - */ - function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, objectFlags: ObjectFlags, readonly: boolean): Type { - if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) { - return anyType; - } - if (left.flags & TypeFlags.Unknown || right.flags & TypeFlags.Unknown) { - return unknownType; - } - if (left.flags & TypeFlags.Never) { - return right; - } - if (right.flags & TypeFlags.Never) { - return left; - } - left = tryMergeUnionOfObjectTypeAndEmptyObject(left, readonly); - if (left.flags & TypeFlags.Union) { - return checkCrossProductUnion([left, right]) - ? mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)) - : errorType; - } - right = tryMergeUnionOfObjectTypeAndEmptyObject(right, readonly); - if (right.flags & TypeFlags.Union) { - return checkCrossProductUnion([left, right]) - ? mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly)) - : errorType; - } - if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) { - return left; - } - - if (isGenericObjectType(left) || isGenericObjectType(right)) { - if (isEmptyObjectType(left)) { - return right; - } - // When the left type is an intersection, we may need to merge the last constituent of the - // intersection with the right type. For example when the left type is 'T & { a: string }' - // and the right type is '{ b: string }' we produce 'T & { a: string, b: string }'. - if (left.flags & TypeFlags.Intersection) { - const types = (left as IntersectionType).types; - const lastLeft = types[types.length - 1]; - if (isNonGenericObjectType(lastLeft) && isNonGenericObjectType(right)) { - return getIntersectionType(concatenate(types.slice(0, types.length - 1), [getSpreadType(lastLeft, right, symbol, objectFlags, readonly)])); - } - } - return getIntersectionType([left, right]); - } + function getTypeFromInferTypeNode(node: InferTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter)); + } + return links.resolvedType; + } - const members = createSymbolTable(); - const skippedPrivateMembers = new Set<__String>(); - const indexInfos = left === emptyObjectType ? getIndexInfosOfType(right) : getUnionIndexInfos([left, right]); + function getIdentifierChain(node: EntityName): Identifier[] { + if (isIdentifier(node)) { + return [node]; + } + else { + return append(getIdentifierChain(node.left), node.right); + } + } - for (const rightProp of getPropertiesOfType(right)) { - if (getDeclarationModifierFlagsFromSymbol(rightProp) & (ModifierFlags.Private | ModifierFlags.Protected)) { - skippedPrivateMembers.add(rightProp.escapedName); - } - else if (isSpreadableProperty(rightProp)) { - members.set(rightProp.escapedName, getSpreadSymbol(rightProp, readonly)); - } + function getTypeFromImportTypeNode(node: ImportTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + if (node.isTypeOf && node.typeArguments) { // Only the non-typeof form can make use of type arguments + error(node, Diagnostics.Type_arguments_cannot_be_used_here); + links.resolvedSymbol = unknownSymbol; + return links.resolvedType = errorType; + } + if (!isLiteralImportTypeNode(node)) { + error(node.argument, Diagnostics.String_literal_expected); + links.resolvedSymbol = unknownSymbol; + return links.resolvedType = errorType; + } + const targetMeaning = node.isTypeOf ? SymbolFlags.Value : node.flags & NodeFlags.JSDoc ? SymbolFlags.Value | SymbolFlags.Type : SymbolFlags.Type; + // TODO: Future work: support unions/generics/whatever via a deferred import-type + const innerModuleSymbol = resolveExternalModuleName(node, node.argument.literal); + if (!innerModuleSymbol) { + links.resolvedSymbol = unknownSymbol; + return links.resolvedType = errorType; + } + const isExportEquals = !!innerModuleSymbol.exports?.get(InternalSymbolName.ExportEquals); + const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false); + if (!nodeIsMissing(node.qualifier)) { + const nameStack: Identifier[] = getIdentifierChain(node.qualifier!); + let currentNamespace = moduleSymbol; + let current: Identifier | undefined; + while (current = nameStack.shift()) { + const meaning = nameStack.length ? SymbolFlags.Namespace : targetMeaning; + // typeof a.b.c is normally resolved using `checkExpression` which in turn defers to `checkQualifiedName` + // That, in turn, ultimately uses `getPropertyOfType` on the type of the symbol, which differs slightly from + // the `exports` lookup process that only looks up namespace members which is used for most type references + const mergedResolvedSymbol = getMergedSymbol(resolveSymbol(currentNamespace)); + const symbolFromVariable = node.isTypeOf || isInJSFile(node) && isExportEquals + ? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText, /*skipObjectFunctionPropertyAugment*/ false, /*includeTypeOnlyMembers*/ true) + : undefined; + const symbolFromModule = node.isTypeOf ? undefined : getSymbol(getExportsOfSymbol(mergedResolvedSymbol), current.escapedText, meaning); + const next = symbolFromModule ?? symbolFromVariable; + if (!next) { + error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current)); + return links.resolvedType = errorType; + } + getNodeLinks(current).resolvedSymbol = next; + getNodeLinks(current.parent).resolvedSymbol = next; + currentNamespace = next; + } + links.resolvedType = resolveImportSymbolType(node, links, currentNamespace, targetMeaning); } - - for (const leftProp of getPropertiesOfType(left)) { - if (skippedPrivateMembers.has(leftProp.escapedName) || !isSpreadableProperty(leftProp)) { - continue; - } - if (members.has(leftProp.escapedName)) { - const rightProp = members.get(leftProp.escapedName)!; - const rightType = getTypeOfSymbol(rightProp); - if (rightProp.flags & SymbolFlags.Optional) { - const declarations = concatenate(leftProp.declarations, rightProp.declarations); - const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional); - const result = createSymbol(flags, leftProp.escapedName); - result.type = getUnionType([getTypeOfSymbol(leftProp), removeMissingOrUndefinedType(rightType)], UnionReduction.Subtype); - result.leftSpread = leftProp; - result.rightSpread = rightProp; - result.declarations = declarations; - result.nameType = getSymbolLinks(leftProp).nameType; - members.set(leftProp.escapedName, result); - } + else { + if (moduleSymbol.flags & targetMeaning) { + links.resolvedType = resolveImportSymbolType(node, links, moduleSymbol, targetMeaning); } else { - members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly)); + const errorMessage = targetMeaning === SymbolFlags.Value + ? Diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here + : Diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here_Did_you_mean_typeof_import_0; + + error(node, errorMessage, node.argument.literal.text); + + links.resolvedSymbol = unknownSymbol; + links.resolvedType = errorType; } } - - const spread = createAnonymousType(symbol, members, emptyArray, emptyArray, sameMap(indexInfos, info => getIndexInfoWithReadonly(info, readonly))); - spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; - return spread; } + return links.resolvedType; + } - /** We approximate own properties as non-methods plus methods that are inside the object literal */ - function isSpreadableProperty(prop: Symbol): boolean { - return !some(prop.declarations, isPrivateIdentifierClassElementDeclaration) && - (!(prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor | SymbolFlags.SetAccessor)) || - !prop.declarations?.some(decl => isClassLike(decl.parent))); + function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) { + const resolvedSymbol = resolveSymbol(symbol); + links.resolvedSymbol = resolvedSymbol; + if (meaning === SymbolFlags.Value) { + return getTypeOfSymbol(symbol); // intentionally doesn't use resolved symbol so type is cached as expected on the alias } + else { + return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol + } + } - function getSpreadSymbol(prop: Symbol, readonly: boolean) { - const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); - if (!isSetonlyAccessor && readonly === isReadonlySymbol(prop)) { - return prop; + function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + // Deferred resolution of members is handled by resolveObjectTypeMembers + const aliasSymbol = getAliasSymbolForTypeNode(node); + if (getMembersOfSymbol(node.symbol).size === 0 && !aliasSymbol) { + links.resolvedType = emptyTypeLiteralType; + } + else { + let type = createObjectType(ObjectFlags.Anonymous, node.symbol); + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); + if (isJSDocTypeLiteral(node) && node.isArrayType) { + type = createArrayType(type); + } + links.resolvedType = type; } - const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional); - const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0)); - result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); - result.declarations = prop.declarations; - result.nameType = getSymbolLinks(prop).nameType; - result.syntheticOrigin = prop; - return result; } + return links.resolvedType; + } - function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) { - return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration) : info; + function getAliasSymbolForTypeNode(node: Node) { + let host = node.parent; + while (isParenthesizedTypeNode(host) || isJSDocTypeExpression(host) || isTypeOperatorNode(host) && host.operator === SyntaxKind.ReadonlyKeyword) { + host = host.parent; } + return isTypeAlias(host) ? getSymbolOfNode(host) : undefined; + } + + function getTypeArgumentsForAliasSymbol(symbol: Symbol | undefined) { + return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined; + } + + function isNonGenericObjectType(type: Type) { + return !!(type.flags & TypeFlags.Object) && !isGenericMappedType(type); + } + + function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: Type) { + return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)); + } - function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol?: Symbol, regularType?: LiteralType) { - const type = createTypeWithSymbol(flags, symbol!) as LiteralType; - type.value = value; - type.regularType = regularType || type; + function tryMergeUnionOfObjectTypeAndEmptyObject(type: Type, readonly: boolean): Type { + if (!(type.flags & TypeFlags.Union)) { return type; } - - function getFreshTypeOfLiteralType(type: Type): Type { - if (type.flags & TypeFlags.Literal) { - if (!(type as LiteralType).freshType) { - const freshType = createLiteralType(type.flags, (type as LiteralType).value, (type as LiteralType).symbol, type as LiteralType); - freshType.freshType = freshType; - (type as LiteralType).freshType = freshType; - } - return (type as LiteralType).freshType; - } + if (every((type as UnionType).types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) { + return find((type as UnionType).types, isEmptyObjectType) || emptyObjectType; + } + const firstType = find((type as UnionType).types, t => !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t)); + if (!firstType) { return type; } - - function getRegularTypeOfLiteralType(type: Type): Type { - return type.flags & TypeFlags.Literal ? (type as LiteralType).regularType : - type.flags & TypeFlags.Union ? ((type as UnionType).regularType || ((type as UnionType).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) : - type; + const secondType = find((type as UnionType).types, t => t !== firstType && !isEmptyObjectTypeOrSpreadsIntoEmptyObject(t)); + if (secondType) { + return type; } + return getAnonymousPartialType(firstType); - function isFreshLiteralType(type: Type) { - return !!(type.flags & TypeFlags.Literal) && (type as LiteralType).freshType === type; + function getAnonymousPartialType(type: Type) { + // gets the type as if it had been spread, but where everything in the spread is made optional + const members = createSymbolTable(); + for (const prop of getPropertiesOfType(type)) { + if (getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) { + // do nothing, skip privates + } + else if (isSpreadableProperty(prop)) { + const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + const flags = SymbolFlags.Property | SymbolFlags.Optional; + const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0)); + result.type = isSetonlyAccessor ? undefinedType : addOptionality(getTypeOfSymbol(prop), /*isProperty*/ true); + result.declarations = prop.declarations; + result.nameType = getSymbolLinks(prop).nameType; + result.syntheticOrigin = prop; + members.set(prop.escapedName, result); + } + } + const spread = createAnonymousType(type.symbol, members, emptyArray, emptyArray, getIndexInfosOfType(type)); + spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + return spread; } + } - function getStringLiteralType(value: string): StringLiteralType { - let type; - return stringLiteralTypes.get(value) || - (stringLiteralTypes.set(value, type = createLiteralType(TypeFlags.StringLiteral, value) as StringLiteralType), type); + /** + * Since the source of spread types are object literals, which are not binary, + * this function should be called in a left folding style, with left = previous result of getSpreadType + * and right = the new element to be spread. + */ + function getSpreadType(left: Type, right: Type, symbol: Symbol | undefined, objectFlags: ObjectFlags, readonly: boolean): Type { + if (left.flags & TypeFlags.Any || right.flags & TypeFlags.Any) { + return anyType; } - - function getNumberLiteralType(value: number): NumberLiteralType { - let type; - return numberLiteralTypes.get(value) || - (numberLiteralTypes.set(value, type = createLiteralType(TypeFlags.NumberLiteral, value) as NumberLiteralType), type); + if (left.flags & TypeFlags.Unknown || right.flags & TypeFlags.Unknown) { + return unknownType; } - - function getBigIntLiteralType(value: PseudoBigInt): BigIntLiteralType { - let type; - const key = pseudoBigIntToString(value); - return bigIntLiteralTypes.get(key) || - (bigIntLiteralTypes.set(key, type = createLiteralType(TypeFlags.BigIntLiteral, value) as BigIntLiteralType), type); + if (left.flags & TypeFlags.Never) { + return right; } - - function getEnumLiteralType(value: string | number, enumId: number, symbol: Symbol): LiteralType { - let type; - const key = `${enumId}${typeof value === "string" ? "@" : "#"}${value}`; - const flags = TypeFlags.EnumLiteral | (typeof value === "string" ? TypeFlags.StringLiteral : TypeFlags.NumberLiteral); - return enumLiteralTypes.get(key) || - (enumLiteralTypes.set(key, type = createLiteralType(flags, value, symbol)), type); + if (right.flags & TypeFlags.Never) { + return left; } - - function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { - if (node.literal.kind === SyntaxKind.NullKeyword) { - return nullType; - } - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getRegularTypeOfLiteralType(checkExpression(node.literal)); - } - return links.resolvedType; + left = tryMergeUnionOfObjectTypeAndEmptyObject(left, readonly); + if (left.flags & TypeFlags.Union) { + return checkCrossProductUnion([left, right]) + ? mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)) + : errorType; } - - function createUniqueESSymbolType(symbol: Symbol) { - const type = createTypeWithSymbol(TypeFlags.UniqueESSymbol, symbol) as UniqueESSymbolType; - type.escapedName = `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String; - return type; + right = tryMergeUnionOfObjectTypeAndEmptyObject(right, readonly); + if (right.flags & TypeFlags.Union) { + return checkCrossProductUnion([left, right]) + ? mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly)) + : errorType; + } + if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) { + return left; } - function getESSymbolLikeTypeForNode(node: Node) { - if (isValidESSymbolDeclaration(node)) { - const symbol = isCommonJsExportPropertyAssignment(node) ? getSymbolOfNode((node as BinaryExpression).left) : getSymbolOfNode(node); - if (symbol) { - const links = getSymbolLinks(symbol); - return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol)); + if (isGenericObjectType(left) || isGenericObjectType(right)) { + if (isEmptyObjectType(left)) { + return right; + } + // When the left type is an intersection, we may need to merge the last constituent of the + // intersection with the right type. For example when the left type is 'T & { a: string }' + // and the right type is '{ b: string }' we produce 'T & { a: string, b: string }'. + if (left.flags & TypeFlags.Intersection) { + const types = (left as IntersectionType).types; + const lastLeft = types[types.length - 1]; + if (isNonGenericObjectType(lastLeft) && isNonGenericObjectType(right)) { + return getIntersectionType(concatenate(types.slice(0, types.length - 1), [getSpreadType(lastLeft, right, symbol, objectFlags, readonly)])); } } - return esSymbolType; + return getIntersectionType([left, right]); } - function getThisType(node: Node): Type { - const container = getThisContainer(node, /*includeArrowFunctions*/ false); - const parent = container && container.parent; - if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { - if (!isStatic(container) && - (!isConstructorDeclaration(container) || isNodeDescendantOf(node, container.body))) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!; - } - } + const members = createSymbolTable(); + const skippedPrivateMembers = new Set<__String>(); + const indexInfos = left === emptyObjectType ? getIndexInfosOfType(right) : getUnionIndexInfos([left, right]); - // inside x.prototype = { ... } - if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!; + for (const rightProp of getPropertiesOfType(right)) { + if (getDeclarationModifierFlagsFromSymbol(rightProp) & (ModifierFlags.Private | ModifierFlags.Protected)) { + skippedPrivateMembers.add(rightProp.escapedName); } - // /** @return {this} */ - // x.prototype.m = function() { ... } - const host = node.flags & NodeFlags.JSDoc ? getHostSignatureFromJSDoc(node) : undefined; - if (host && isFunctionExpression(host) && isBinaryExpression(host.parent) && getAssignmentDeclarationKind(host.parent) === AssignmentDeclarationKind.PrototypeProperty) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(host.parent.left)!.parent!).thisType!; + else if (isSpreadableProperty(rightProp)) { + members.set(rightProp.escapedName, getSpreadSymbol(rightProp, readonly)); } - // inside constructor function C() { ... } - if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { - return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(container)).thisType!; - } - error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); - return errorType; } - function getTypeFromThisTypeNode(node: ThisExpression | ThisTypeNode): Type { - const links = getNodeLinks(node); - if (!links.resolvedType) { - links.resolvedType = getThisType(node); + for (const leftProp of getPropertiesOfType(left)) { + if (skippedPrivateMembers.has(leftProp.escapedName) || !isSpreadableProperty(leftProp)) { + continue; + } + if (members.has(leftProp.escapedName)) { + const rightProp = members.get(leftProp.escapedName)!; + const rightType = getTypeOfSymbol(rightProp); + if (rightProp.flags & SymbolFlags.Optional) { + const declarations = concatenate(leftProp.declarations, rightProp.declarations); + const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional); + const result = createSymbol(flags, leftProp.escapedName); + result.type = getUnionType([getTypeOfSymbol(leftProp), removeMissingOrUndefinedType(rightType)], UnionReduction.Subtype); + result.leftSpread = leftProp; + result.rightSpread = rightProp; + result.declarations = declarations; + result.nameType = getSymbolLinks(leftProp).nameType; + members.set(leftProp.escapedName, result); + } + } + else { + members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly)); } - return links.resolvedType; } - function getTypeFromRestTypeNode(node: RestTypeNode | NamedTupleMember) { - return getTypeFromTypeNode(getArrayElementTypeNode(node.type) || node.type); - } + const spread = createAnonymousType(symbol, members, emptyArray, emptyArray, sameMap(indexInfos, info => getIndexInfoWithReadonly(info, readonly))); + spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; + return spread; + } - function getArrayElementTypeNode(node: TypeNode): TypeNode | undefined { - switch (node.kind) { - case SyntaxKind.ParenthesizedType: - return getArrayElementTypeNode((node as ParenthesizedTypeNode).type); - case SyntaxKind.TupleType: - if ((node as TupleTypeNode).elements.length === 1) { - node = (node as TupleTypeNode).elements[0]; - if (node.kind === SyntaxKind.RestType || node.kind === SyntaxKind.NamedTupleMember && (node as NamedTupleMember).dotDotDotToken) { - return getArrayElementTypeNode((node as RestTypeNode | NamedTupleMember).type); - } - } - break; - case SyntaxKind.ArrayType: - return (node as ArrayTypeNode).elementType; - } - return undefined; - } + /** We approximate own properties as non-methods plus methods that are inside the object literal */ + function isSpreadableProperty(prop: Symbol): boolean { + return !some(prop.declarations, isPrivateIdentifierClassElementDeclaration) && + (!(prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor | SymbolFlags.SetAccessor)) || + !prop.declarations?.some(decl => isClassLike(decl.parent))); + } - function getTypeFromNamedTupleTypeNode(node: NamedTupleMember): Type { - const links = getNodeLinks(node); - return links.resolvedType || (links.resolvedType = - node.dotDotDotToken ? getTypeFromRestTypeNode(node) : - addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true, !!node.questionToken)); - } + function getSpreadSymbol(prop: Symbol, readonly: boolean) { + const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + if (!isSetonlyAccessor && readonly === isReadonlySymbol(prop)) { + return prop; + } + const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional); + const result = createSymbol(flags, prop.escapedName, getIsLateCheckFlag(prop) | (readonly ? CheckFlags.Readonly : 0)); + result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); + result.declarations = prop.declarations; + result.nameType = getSymbolLinks(prop).nameType; + result.syntheticOrigin = prop; + return result; + } - function getTypeFromTypeNode(node: TypeNode): Type { - return getConditionalFlowTypeOfType(getTypeFromTypeNodeWorker(node), node); - } + function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) { + return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration) : info; + } - function getTypeFromTypeNodeWorker(node: TypeNode): Type { - switch (node.kind) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.JSDocAllType: - case SyntaxKind.JSDocUnknownType: - return anyType; - case SyntaxKind.UnknownKeyword: - return unknownType; - case SyntaxKind.StringKeyword: - return stringType; - case SyntaxKind.NumberKeyword: - return numberType; - case SyntaxKind.BigIntKeyword: - return bigintType; - case SyntaxKind.BooleanKeyword: - return booleanType; - case SyntaxKind.SymbolKeyword: - return esSymbolType; - case SyntaxKind.VoidKeyword: - return voidType; - case SyntaxKind.UndefinedKeyword: - return undefinedType; - case SyntaxKind.NullKeyword as TypeNodeSyntaxKind: - // TODO(rbuckton): `NullKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service. - return nullType; - case SyntaxKind.NeverKeyword: - return neverType; - case SyntaxKind.ObjectKeyword: - return node.flags & NodeFlags.JavaScriptFile && !noImplicitAny ? anyType : nonPrimitiveType; - case SyntaxKind.IntrinsicKeyword: - return intrinsicMarkerType; - case SyntaxKind.ThisType: - case SyntaxKind.ThisKeyword as TypeNodeSyntaxKind: - // TODO(rbuckton): `ThisKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service and because of `isPartOfTypeNode`. - return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode); - case SyntaxKind.LiteralType: - return getTypeFromLiteralTypeNode(node as LiteralTypeNode); - case SyntaxKind.TypeReference: - return getTypeFromTypeReference(node as TypeReferenceNode); - case SyntaxKind.TypePredicate: - return (node as TypePredicateNode).assertsModifier ? voidType : booleanType; - case SyntaxKind.ExpressionWithTypeArguments: - return getTypeFromTypeReference(node as ExpressionWithTypeArguments); - case SyntaxKind.TypeQuery: - return getTypeFromTypeQueryNode(node as TypeQueryNode); - case SyntaxKind.ArrayType: - case SyntaxKind.TupleType: - return getTypeFromArrayOrTupleTypeNode(node as ArrayTypeNode | TupleTypeNode); - case SyntaxKind.OptionalType: - return getTypeFromOptionalTypeNode(node as OptionalTypeNode); - case SyntaxKind.UnionType: - return getTypeFromUnionTypeNode(node as UnionTypeNode); - case SyntaxKind.IntersectionType: - return getTypeFromIntersectionTypeNode(node as IntersectionTypeNode); - case SyntaxKind.JSDocNullableType: - return getTypeFromJSDocNullableTypeNode(node as JSDocNullableType); - case SyntaxKind.JSDocOptionalType: - return addOptionality(getTypeFromTypeNode((node as JSDocOptionalType).type)); - case SyntaxKind.NamedTupleMember: - return getTypeFromNamedTupleTypeNode(node as NamedTupleMember); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocTypeExpression: - return getTypeFromTypeNode((node as ParenthesizedTypeNode | JSDocTypeReferencingNode | JSDocTypeExpression | NamedTupleMember).type); - case SyntaxKind.RestType: - return getTypeFromRestTypeNode(node as RestTypeNode); - case SyntaxKind.JSDocVariadicType: - return getTypeFromJSDocVariadicType(node as JSDocVariadicType); - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.JSDocFunctionType: - case SyntaxKind.JSDocSignature: - return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); - case SyntaxKind.TypeOperator: - return getTypeFromTypeOperatorNode(node as TypeOperatorNode); - case SyntaxKind.IndexedAccessType: - return getTypeFromIndexedAccessTypeNode(node as IndexedAccessTypeNode); - case SyntaxKind.MappedType: - return getTypeFromMappedTypeNode(node as MappedTypeNode); - case SyntaxKind.ConditionalType: - return getTypeFromConditionalTypeNode(node as ConditionalTypeNode); - case SyntaxKind.InferType: - return getTypeFromInferTypeNode(node as InferTypeNode); - case SyntaxKind.TemplateLiteralType: - return getTypeFromTemplateTypeNode(node as TemplateLiteralTypeNode); - case SyntaxKind.ImportType: - return getTypeFromImportTypeNode(node as ImportTypeNode); - // This function assumes that an identifier, qualified name, or property access expression is a type expression - // Callers should first ensure this by calling `isPartOfTypeNode` - // TODO(rbuckton): These aren't valid TypeNodes, but we treat them as such because of `isPartOfTypeNode`, which returns `true` for things that aren't `TypeNode`s. - case SyntaxKind.Identifier as TypeNodeSyntaxKind: - case SyntaxKind.QualifiedName as TypeNodeSyntaxKind: - case SyntaxKind.PropertyAccessExpression as TypeNodeSyntaxKind: - const symbol = getSymbolAtLocation(node); - return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; - default: - return errorType; - } - } + function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol?: Symbol, regularType?: LiteralType) { + const type = createTypeWithSymbol(flags, symbol!) as LiteralType; + type.value = value; + type.regularType = regularType || type; + return type; + } - function instantiateList(items: readonly T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[]; - function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined; - function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined { - if (items && items.length) { - for (let i = 0; i < items.length; i++) { - const item = items[i]; - const mapped = instantiator(item, mapper); - if (item !== mapped) { - const result = i === 0 ? [] : items.slice(0, i); - result.push(mapped); - for (i++; i < items.length; i++) { - result.push(instantiator(items[i], mapper)); - } - return result; - } - } + function getFreshTypeOfLiteralType(type: Type): Type { + if (type.flags & TypeFlags.Literal) { + if (!(type as LiteralType).freshType) { + const freshType = createLiteralType(type.flags, (type as LiteralType).value, (type as LiteralType).symbol, type as LiteralType); + freshType.freshType = freshType; + (type as LiteralType).freshType = freshType; } - return items; + return (type as LiteralType).freshType; } + return type; + } - function instantiateTypes(types: readonly Type[], mapper: TypeMapper): readonly Type[]; - function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined; - function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined { - return instantiateList(types, mapper, instantiateType); - } + function getRegularTypeOfLiteralType(type: Type): Type { + return type.flags & TypeFlags.Literal ? (type as LiteralType).regularType : + type.flags & TypeFlags.Union ? ((type as UnionType).regularType || ((type as UnionType).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) : + type; + } - function instantiateSignatures(signatures: readonly Signature[], mapper: TypeMapper): readonly Signature[] { - return instantiateList(signatures, mapper, instantiateSignature); - } + function isFreshLiteralType(type: Type) { + return !!(type.flags & TypeFlags.Literal) && (type as LiteralType).freshType === type; + } - function instantiateIndexInfos(indexInfos: readonly IndexInfo[], mapper: TypeMapper): readonly IndexInfo[] { - return instantiateList(indexInfos, mapper, instantiateIndexInfo); - } + function getStringLiteralType(value: string): StringLiteralType { + let type; + return stringLiteralTypes.get(value) || + (stringLiteralTypes.set(value, type = createLiteralType(TypeFlags.StringLiteral, value) as StringLiteralType), type); + } - function createTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { - return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : makeArrayTypeMapper(sources, targets); - } + function getNumberLiteralType(value: number): NumberLiteralType { + let type; + return numberLiteralTypes.get(value) || + (numberLiteralTypes.set(value, type = createLiteralType(TypeFlags.NumberLiteral, value) as NumberLiteralType), type); + } - function getMappedType(type: Type, mapper: TypeMapper): Type { - switch (mapper.kind) { - case TypeMapKind.Simple: - return type === mapper.source ? mapper.target : type; - case TypeMapKind.Array: { - const sources = mapper.sources; - const targets = mapper.targets; - for (let i = 0; i < sources.length; i++) { - if (type === sources[i]) { - return targets ? targets[i] : anyType; - } - } - return type; - } - case TypeMapKind.Deferred: { - const sources = mapper.sources; - const targets = mapper.targets; - for (let i = 0; i < sources.length; i++) { - if (type === sources[i]) { - return targets[i](); - } - } - return type; - } - case TypeMapKind.Function: - return mapper.func(type); - case TypeMapKind.Composite: - case TypeMapKind.Merged: - const t1 = getMappedType(type, mapper.mapper1); - return t1 !== type && mapper.kind === TypeMapKind.Composite ? instantiateType(t1, mapper.mapper2) : getMappedType(t1, mapper.mapper2); - } - } + function getBigIntLiteralType(value: PseudoBigInt): BigIntLiteralType { + let type; + const key = pseudoBigIntToString(value); + return bigIntLiteralTypes.get(key) || + (bigIntLiteralTypes.set(key, type = createLiteralType(TypeFlags.BigIntLiteral, value) as BigIntLiteralType), type); + } - function makeUnaryTypeMapper(source: Type, target: Type): TypeMapper { - return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Simple, source, target }); - } + function getEnumLiteralType(value: string | number, enumId: number, symbol: Symbol): LiteralType { + let type; + const key = `${enumId}${typeof value === "string" ? "@" : "#"}${value}`; + const flags = TypeFlags.EnumLiteral | (typeof value === "string" ? TypeFlags.StringLiteral : TypeFlags.NumberLiteral); + return enumLiteralTypes.get(key) || + (enumLiteralTypes.set(key, type = createLiteralType(flags, value, symbol)), type); + } - function makeArrayTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { - return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Array, sources, targets }); + function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { + if (node.literal.kind === SyntaxKind.NullKeyword) { + return nullType; } - - function makeFunctionTypeMapper(func: (t: Type) => Type, debugInfo: () => string): TypeMapper { - return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Function, func, debugInfo: Debug.isDebugging ? debugInfo : undefined }); + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getRegularTypeOfLiteralType(checkExpression(node.literal)); } + return links.resolvedType; + } - function makeDeferredTypeMapper(sources: readonly TypeParameter[], targets: (() => Type)[]) { - return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Deferred, sources, targets }); - } + function createUniqueESSymbolType(symbol: Symbol) { + const type = createTypeWithSymbol(TypeFlags.UniqueESSymbol, symbol) as UniqueESSymbolType; + type.escapedName = `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String; + return type; + } - function makeCompositeTypeMapper(kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { - return Debug.attachDebugPrototypeIfDebug({ kind, mapper1, mapper2 }); + function getESSymbolLikeTypeForNode(node: Node) { + if (isValidESSymbolDeclaration(node)) { + const symbol = isCommonJsExportPropertyAssignment(node) ? getSymbolOfNode((node as BinaryExpression).left) : getSymbolOfNode(node); + if (symbol) { + const links = getSymbolLinks(symbol); + return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol)); + } } + return esSymbolType; + } - function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper { - return createTypeMapper(sources, /*targets*/ undefined); + function getThisType(node: Node): Type { + const container = getThisContainer(node, /*includeArrowFunctions*/ false); + const parent = container && container.parent; + if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { + if (!isStatic(container) && + (!isConstructorDeclaration(container) || isNodeDescendantOf(node, container.body))) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!; + } } - /** - * Maps forward-references to later types parameters to the empty object type. - * This is used during inference when instantiating type parameter defaults. - */ - function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper { - const forwardInferences = context.inferences.slice(index); - return createTypeMapper(map(forwardInferences, i => i.typeParameter), map(forwardInferences, () => unknownType)); + // inside x.prototype = { ... } + if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!; } - - function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { - return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2; + // /** @return {this} */ + // x.prototype.m = function() { ... } + const host = node.flags & NodeFlags.JSDoc ? getHostSignatureFromJSDoc(node) : undefined; + if (host && isFunctionExpression(host) && isBinaryExpression(host.parent) && getAssignmentDeclarationKind(host.parent) === AssignmentDeclarationKind.PrototypeProperty) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(host.parent.left)!.parent!).thisType!; } - - function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { - return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Merged, mapper1, mapper2) : mapper2; + // inside constructor function C() { ... } + if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(container)).thisType!; } + error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); + return errorType; + } - function prependTypeMapping(source: Type, target: Type, mapper: TypeMapper | undefined) { - return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, makeUnaryTypeMapper(source, target), mapper); + function getTypeFromThisTypeNode(node: ThisExpression | ThisTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getThisType(node); } + return links.resolvedType; + } - function appendTypeMapping(mapper: TypeMapper | undefined, source: Type, target: Type) { - return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, mapper, makeUnaryTypeMapper(source, target)); - } + function getTypeFromRestTypeNode(node: RestTypeNode | NamedTupleMember) { + return getTypeFromTypeNode(getArrayElementTypeNode(node.type) || node.type); + } - function getRestrictiveTypeParameter(tp: TypeParameter) { - return !tp.constraint && !getConstraintDeclaration(tp) || tp.constraint === noConstraintType ? tp : tp.restrictiveInstantiation || ( - tp.restrictiveInstantiation = createTypeParameter(tp.symbol), - (tp.restrictiveInstantiation as TypeParameter).constraint = noConstraintType, - tp.restrictiveInstantiation - ); + function getArrayElementTypeNode(node: TypeNode): TypeNode | undefined { + switch (node.kind) { + case SyntaxKind.ParenthesizedType: + return getArrayElementTypeNode((node as ParenthesizedTypeNode).type); + case SyntaxKind.TupleType: + if ((node as TupleTypeNode).elements.length === 1) { + node = (node as TupleTypeNode).elements[0]; + if (node.kind === SyntaxKind.RestType || node.kind === SyntaxKind.NamedTupleMember && (node as NamedTupleMember).dotDotDotToken) { + return getArrayElementTypeNode((node as RestTypeNode | NamedTupleMember).type); + } + } + break; + case SyntaxKind.ArrayType: + return (node as ArrayTypeNode).elementType; } + return undefined; + } - function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter { - const result = createTypeParameter(typeParameter.symbol); - result.target = typeParameter; - return result; - } + function getTypeFromNamedTupleTypeNode(node: NamedTupleMember): Type { + const links = getNodeLinks(node); + return links.resolvedType || (links.resolvedType = + node.dotDotDotToken ? getTypeFromRestTypeNode(node) : + addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true, !!node.questionToken)); + } - function instantiateTypePredicate(predicate: TypePredicate, mapper: TypeMapper): TypePredicate { - return createTypePredicate(predicate.kind, predicate.parameterName, predicate.parameterIndex, instantiateType(predicate.type, mapper)); - } - - function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature { - let freshTypeParameters: TypeParameter[] | undefined; - if (signature.typeParameters && !eraseTypeParameters) { - // First create a fresh set of type parameters, then include a mapping from the old to the - // new type parameters in the mapper function. Finally store this mapper in the new type - // parameters such that we can use it when instantiating constraints. - freshTypeParameters = map(signature.typeParameters, cloneTypeParameter); - mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper); - for (const tp of freshTypeParameters) { - tp.mapper = mapper; - } - } - // Don't compute resolvedReturnType and resolvedTypePredicate now, - // because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.) - // See GH#17600. - const result = createSignature(signature.declaration, freshTypeParameters, - signature.thisParameter && instantiateSymbol(signature.thisParameter, mapper), - instantiateList(signature.parameters, mapper, instantiateSymbol), - /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, - signature.minArgumentCount, - signature.flags & SignatureFlags.PropagatingFlags); - result.target = signature; - result.mapper = mapper; - return result; - } + function getTypeFromTypeNode(node: TypeNode): Type { + return getConditionalFlowTypeOfType(getTypeFromTypeNodeWorker(node), node); + } - function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { - const links = getSymbolLinks(symbol); - if (links.type && !couldContainTypeVariables(links.type)) { - // If the type of the symbol is already resolved, and if that type could not possibly - // be affected by instantiation, simply return the symbol itself. - return symbol; - } - if (getCheckFlags(symbol) & CheckFlags.Instantiated) { - // If symbol being instantiated is itself a instantiation, fetch the original target and combine the - // type mappers. This ensures that original type identities are properly preserved and that aliases - // always reference a non-aliases. - symbol = links.target!; - mapper = combineTypeMappers(links.mapper, mapper); - } - // Keep the flags from the symbol we're instantiating. Mark that is instantiated, and - // also transient so that we can just store data on it directly. - const result = createSymbol(symbol.flags, symbol.escapedName, CheckFlags.Instantiated | getCheckFlags(symbol) & (CheckFlags.Readonly | CheckFlags.Late | CheckFlags.OptionalParameter | CheckFlags.RestParameter)); - result.declarations = symbol.declarations; - result.parent = symbol.parent; - result.target = symbol; - result.mapper = mapper; - if (symbol.valueDeclaration) { - result.valueDeclaration = symbol.valueDeclaration; - } - if (links.nameType) { - result.nameType = links.nameType; - } - return result; + function getTypeFromTypeNodeWorker(node: TypeNode): Type { + switch (node.kind) { + case SyntaxKind.AnyKeyword: + case SyntaxKind.JSDocAllType: + case SyntaxKind.JSDocUnknownType: + return anyType; + case SyntaxKind.UnknownKeyword: + return unknownType; + case SyntaxKind.StringKeyword: + return stringType; + case SyntaxKind.NumberKeyword: + return numberType; + case SyntaxKind.BigIntKeyword: + return bigintType; + case SyntaxKind.BooleanKeyword: + return booleanType; + case SyntaxKind.SymbolKeyword: + return esSymbolType; + case SyntaxKind.VoidKeyword: + return voidType; + case SyntaxKind.UndefinedKeyword: + return undefinedType; + case SyntaxKind.NullKeyword as TypeNodeSyntaxKind: + // TODO(rbuckton): `NullKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service. + return nullType; + case SyntaxKind.NeverKeyword: + return neverType; + case SyntaxKind.ObjectKeyword: + return node.flags & NodeFlags.JavaScriptFile && !noImplicitAny ? anyType : nonPrimitiveType; + case SyntaxKind.IntrinsicKeyword: + return intrinsicMarkerType; + case SyntaxKind.ThisType: + case SyntaxKind.ThisKeyword as TypeNodeSyntaxKind: + // TODO(rbuckton): `ThisKeyword` is no longer a `TypeNode`, but we defensively allow it here because of incorrect casts in the Language Service and because of `isPartOfTypeNode`. + return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode); + case SyntaxKind.LiteralType: + return getTypeFromLiteralTypeNode(node as LiteralTypeNode); + case SyntaxKind.TypeReference: + return getTypeFromTypeReference(node as TypeReferenceNode); + case SyntaxKind.TypePredicate: + return (node as TypePredicateNode).assertsModifier ? voidType : booleanType; + case SyntaxKind.ExpressionWithTypeArguments: + return getTypeFromTypeReference(node as ExpressionWithTypeArguments); + case SyntaxKind.TypeQuery: + return getTypeFromTypeQueryNode(node as TypeQueryNode); + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + return getTypeFromArrayOrTupleTypeNode(node as ArrayTypeNode | TupleTypeNode); + case SyntaxKind.OptionalType: + return getTypeFromOptionalTypeNode(node as OptionalTypeNode); + case SyntaxKind.UnionType: + return getTypeFromUnionTypeNode(node as UnionTypeNode); + case SyntaxKind.IntersectionType: + return getTypeFromIntersectionTypeNode(node as IntersectionTypeNode); + case SyntaxKind.JSDocNullableType: + return getTypeFromJSDocNullableTypeNode(node as JSDocNullableType); + case SyntaxKind.JSDocOptionalType: + return addOptionality(getTypeFromTypeNode((node as JSDocOptionalType).type)); + case SyntaxKind.NamedTupleMember: + return getTypeFromNamedTupleTypeNode(node as NamedTupleMember); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocTypeExpression: + return getTypeFromTypeNode((node as ParenthesizedTypeNode | JSDocTypeReferencingNode | JSDocTypeExpression | NamedTupleMember).type); + case SyntaxKind.RestType: + return getTypeFromRestTypeNode(node as RestTypeNode); + case SyntaxKind.JSDocVariadicType: + return getTypeFromJSDocVariadicType(node as JSDocVariadicType); + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.JSDocSignature: + return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); + case SyntaxKind.TypeOperator: + return getTypeFromTypeOperatorNode(node as TypeOperatorNode); + case SyntaxKind.IndexedAccessType: + return getTypeFromIndexedAccessTypeNode(node as IndexedAccessTypeNode); + case SyntaxKind.MappedType: + return getTypeFromMappedTypeNode(node as MappedTypeNode); + case SyntaxKind.ConditionalType: + return getTypeFromConditionalTypeNode(node as ConditionalTypeNode); + case SyntaxKind.InferType: + return getTypeFromInferTypeNode(node as InferTypeNode); + case SyntaxKind.TemplateLiteralType: + return getTypeFromTemplateTypeNode(node as TemplateLiteralTypeNode); + case SyntaxKind.ImportType: + return getTypeFromImportTypeNode(node as ImportTypeNode); + // This function assumes that an identifier, qualified name, or property access expression is a type expression + // Callers should first ensure this by calling `isPartOfTypeNode` + // TODO(rbuckton): These aren't valid TypeNodes, but we treat them as such because of `isPartOfTypeNode`, which returns `true` for things that aren't `TypeNode`s. + case SyntaxKind.Identifier as TypeNodeSyntaxKind: + case SyntaxKind.QualifiedName as TypeNodeSyntaxKind: + case SyntaxKind.PropertyAccessExpression as TypeNodeSyntaxKind: + const symbol = getSymbolAtLocation(node); + return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; + default: + return errorType; } + } - function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { - const declaration = type.objectFlags & ObjectFlags.Reference ? (type as TypeReference).node! : - type.objectFlags & ObjectFlags.InstantiationExpressionType ? (type as InstantiationExpressionType).node : - type.symbol.declarations![0]; - const links = getNodeLinks(declaration); - const target = type.objectFlags & ObjectFlags.Reference ? links.resolvedType! as DeferredTypeReference : - type.objectFlags & ObjectFlags.Instantiated ? type.target! : type; - let typeParameters = links.outerTypeParameters; - if (!typeParameters) { - // The first time an anonymous type is instantiated we compute and store a list of the type - // parameters that are in scope (and therefore potentially referenced). For type literals that - // aren't the right hand side of a generic type alias declaration we optimize by reducing the - // set of type parameters to those that are possibly referenced in the literal. - let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true); - if (isJSConstructor(declaration)) { - const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters); - outerTypeParameters = addRange(outerTypeParameters, templateTagParameters); - } - typeParameters = outerTypeParameters || emptyArray; - const allDeclarations = type.objectFlags & (ObjectFlags.Reference | ObjectFlags.InstantiationExpressionType) ? [declaration] : type.symbol.declarations!; - typeParameters = (target.objectFlags & (ObjectFlags.Reference | ObjectFlags.InstantiationExpressionType) || target.symbol.flags & SymbolFlags.Method || target.symbol.flags & SymbolFlags.TypeLiteral) && !target.aliasTypeArguments ? - filter(typeParameters, tp => some(allDeclarations, d => isTypeParameterPossiblyReferenced(tp, d))) : - typeParameters; - links.outerTypeParameters = typeParameters; - } - if (typeParameters.length) { - // We are instantiating an anonymous type that has one or more type parameters in scope. Apply the - // mapper to the type parameters to produce the effective list of type arguments, and compute the - // instantiation cache key from the type IDs of the type arguments. - const combinedMapper = combineTypeMappers(type.mapper, mapper); - const typeArguments = map(typeParameters, t => getMappedType(t, combinedMapper)); - const newAliasSymbol = aliasSymbol || type.aliasSymbol; - const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - const id = getTypeListId(typeArguments) + getAliasId(newAliasSymbol, newAliasTypeArguments); - if (!target.instantiations) { - target.instantiations = new Map(); - target.instantiations.set(getTypeListId(typeParameters) + getAliasId(target.aliasSymbol, target.aliasTypeArguments), target); - } - let result = target.instantiations.get(id); - if (!result) { - const newMapper = createTypeMapper(typeParameters, typeArguments); - result = target.objectFlags & ObjectFlags.Reference ? createDeferredTypeReference((type as DeferredTypeReference).target, (type as DeferredTypeReference).node, newMapper, newAliasSymbol, newAliasTypeArguments) : - target.objectFlags & ObjectFlags.Mapped ? instantiateMappedType(target as MappedType, newMapper, newAliasSymbol, newAliasTypeArguments) : - instantiateAnonymousType(target, newMapper, newAliasSymbol, newAliasTypeArguments); - target.instantiations.set(id, result); + function instantiateList(items: readonly T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[]; + function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined; + function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined { + if (items && items.length) { + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const mapped = instantiator(item, mapper); + if (item !== mapped) { + const result = i === 0 ? [] : items.slice(0, i); + result.push(mapped); + for (i++; i < items.length; i++) { + result.push(instantiator(items[i], mapper)); + } + return result; } - return result; } - return type; } + return items; + } - function maybeTypeParameterReference(node: Node) { - return !(node.parent.kind === SyntaxKind.TypeReference && (node.parent as TypeReferenceNode).typeArguments && node === (node.parent as TypeReferenceNode).typeName || - node.parent.kind === SyntaxKind.ImportType && (node.parent as ImportTypeNode).typeArguments && node === (node.parent as ImportTypeNode).qualifier); - } + function instantiateTypes(types: readonly Type[], mapper: TypeMapper): readonly Type[]; + function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined; + function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined { + return instantiateList(types, mapper, instantiateType); + } - function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) { - // If the type parameter doesn't have exactly one declaration, if there are intervening statement blocks - // between the node and the type parameter declaration, if the node contains actual references to the - // type parameter, or if the node contains type queries that we can't prove couldn't contain references to the type parameter, - // 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; - for (let n = node; n !== container; n = n.parent) { - if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n as ConditionalTypeNode).extendsType, containsReference)) { - return true; + function instantiateSignatures(signatures: readonly Signature[], mapper: TypeMapper): readonly Signature[] { + return instantiateList(signatures, mapper, instantiateSignature); + } + + function instantiateIndexInfos(indexInfos: readonly IndexInfo[], mapper: TypeMapper): readonly IndexInfo[] { + return instantiateList(indexInfos, mapper, instantiateIndexInfo); + } + + function createTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { + return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : makeArrayTypeMapper(sources, targets); + } + + function getMappedType(type: Type, mapper: TypeMapper): Type { + switch (mapper.kind) { + case TypeMapKind.Simple: + return type === mapper.source ? mapper.target : type; + case TypeMapKind.Array: { + const sources = mapper.sources; + const targets = mapper.targets; + for (let i = 0; i < sources.length; i++) { + if (type === sources[i]) { + return targets ? targets[i] : anyType; } } - return containsReference(node); + return type; } - return true; - function containsReference(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ThisType: - return !!tp.isThisType; - case SyntaxKind.Identifier: - return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) && - getTypeFromTypeNodeWorker(node as TypeNode) === tp; // use worker because we're looking for === equality - case SyntaxKind.TypeQuery: - const entityName = (node as TypeQueryNode).exprName; - const firstIdentifier = getFirstIdentifier(entityName); - const firstIdentifierSymbol = getResolvedSymbol(firstIdentifier); - const tpDeclaration = tp.symbol.declarations![0]; // There is exactly one declaration, otherwise `containsReference` is not called - let tpScope: Node; - if (tpDeclaration.kind === SyntaxKind.TypeParameter) { // Type parameter is a regular type parameter, e.g. foo - tpScope = tpDeclaration.parent; - } - else if (tp.isThisType) { - // Type parameter is the this type, and its declaration is the class declaration. - tpScope = tpDeclaration; - } - else { - // Type parameter's declaration was unrecognized. - // This could happen if the type parameter comes from e.g. a JSDoc annotation, so we default to returning true. - return true; - } - - if (firstIdentifierSymbol.declarations) { - return some(firstIdentifierSymbol.declarations, idDecl => isNodeDescendantOf(idDecl, tpScope)) || - some((node as TypeQueryNode).typeArguments, containsReference); - } - return true; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return !(node as FunctionLikeDeclaration).type && !!(node as FunctionLikeDeclaration).body || - some((node as FunctionLikeDeclaration).typeParameters, containsReference) || - some((node as FunctionLikeDeclaration).parameters, containsReference) || - !!(node as FunctionLikeDeclaration).type && containsReference((node as FunctionLikeDeclaration).type!); + case TypeMapKind.Deferred: { + const sources = mapper.sources; + const targets = mapper.targets; + for (let i = 0; i < sources.length; i++) { + if (type === sources[i]) { + return targets[i](); + } } - return !!forEachChild(node, containsReference); + return type; } + case TypeMapKind.Function: + return mapper.func(type); + case TypeMapKind.Composite: + case TypeMapKind.Merged: + const t1 = getMappedType(type, mapper.mapper1); + return t1 !== type && mapper.kind === TypeMapKind.Composite ? instantiateType(t1, mapper.mapper2) : getMappedType(t1, mapper.mapper2); + } + } + + function makeUnaryTypeMapper(source: Type, target: Type): TypeMapper { + return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Simple, source, target }); + } + + function makeArrayTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { + return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Array, sources, targets }); + } + + function makeFunctionTypeMapper(func: (t: Type) => Type, debugInfo: () => string): TypeMapper { + return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Function, func, debugInfo: Debug.isDebugging ? debugInfo : undefined }); + } + + function makeDeferredTypeMapper(sources: readonly TypeParameter[], targets: (() => Type)[]) { + return Debug.attachDebugPrototypeIfDebug({ kind: TypeMapKind.Deferred, sources, targets }); + } + + function makeCompositeTypeMapper(kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { + return Debug.attachDebugPrototypeIfDebug({ kind, mapper1, mapper2 }); + } + + function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper { + return createTypeMapper(sources, /*targets*/ undefined); + } + + /** + * Maps forward-references to later types parameters to the empty object type. + * This is used during inference when instantiating type parameter defaults. + */ + function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper { + const forwardInferences = context.inferences.slice(index); + return createTypeMapper(map(forwardInferences, i => i.typeParameter), map(forwardInferences, () => unknownType)); + } + + function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { + return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2; + } + + function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { + return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Merged, mapper1, mapper2) : mapper2; + } + + function prependTypeMapping(source: Type, target: Type, mapper: TypeMapper | undefined) { + return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, makeUnaryTypeMapper(source, target), mapper); + } + + function appendTypeMapping(mapper: TypeMapper | undefined, source: Type, target: Type) { + return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, mapper, makeUnaryTypeMapper(source, target)); + } + + function getRestrictiveTypeParameter(tp: TypeParameter) { + return !tp.constraint && !getConstraintDeclaration(tp) || tp.constraint === noConstraintType ? tp : tp.restrictiveInstantiation || ( + tp.restrictiveInstantiation = createTypeParameter(tp.symbol), + (tp.restrictiveInstantiation as TypeParameter).constraint = noConstraintType, + tp.restrictiveInstantiation + ); + } + + function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter { + const result = createTypeParameter(typeParameter.symbol); + result.target = typeParameter; + return result; + } + + function instantiateTypePredicate(predicate: TypePredicate, mapper: TypeMapper): TypePredicate { + return createTypePredicate(predicate.kind, predicate.parameterName, predicate.parameterIndex, instantiateType(predicate.type, mapper)); + } + + function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature { + let freshTypeParameters: TypeParameter[] | undefined; + if (signature.typeParameters && !eraseTypeParameters) { + // First create a fresh set of type parameters, then include a mapping from the old to the + // new type parameters in the mapper function. Finally store this mapper in the new type + // parameters such that we can use it when instantiating constraints. + freshTypeParameters = map(signature.typeParameters, cloneTypeParameter); + mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper); + for (const tp of freshTypeParameters) { + tp.mapper = mapper; + } + } + // Don't compute resolvedReturnType and resolvedTypePredicate now, + // because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.) + // See GH#17600. + const result = createSignature(signature.declaration, freshTypeParameters, + signature.thisParameter && instantiateSymbol(signature.thisParameter, mapper), + instantiateList(signature.parameters, mapper, instantiateSymbol), + /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, + signature.minArgumentCount, + signature.flags & SignatureFlags.PropagatingFlags); + result.target = signature; + result.mapper = mapper; + return result; + } + + function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { + const links = getSymbolLinks(symbol); + if (links.type && !couldContainTypeVariables(links.type)) { + // If the type of the symbol is already resolved, and if that type could not possibly + // be affected by instantiation, simply return the symbol itself. + return symbol; } + if (getCheckFlags(symbol) & CheckFlags.Instantiated) { + // If symbol being instantiated is itself a instantiation, fetch the original target and combine the + // type mappers. This ensures that original type identities are properly preserved and that aliases + // always reference a non-aliases. + symbol = links.target!; + mapper = combineTypeMappers(links.mapper, mapper); + } + // Keep the flags from the symbol we're instantiating. Mark that is instantiated, and + // also transient so that we can just store data on it directly. + const result = createSymbol(symbol.flags, symbol.escapedName, CheckFlags.Instantiated | getCheckFlags(symbol) & (CheckFlags.Readonly | CheckFlags.Late | CheckFlags.OptionalParameter | CheckFlags.RestParameter)); + result.declarations = symbol.declarations; + result.parent = symbol.parent; + result.target = symbol; + result.mapper = mapper; + if (symbol.valueDeclaration) { + result.valueDeclaration = symbol.valueDeclaration; + } + if (links.nameType) { + result.nameType = links.nameType; + } + return result; + } - function getHomomorphicTypeVariable(type: MappedType) { - const constraintType = getConstraintTypeFromMappedType(type); - if (constraintType.flags & TypeFlags.Index) { - const typeVariable = getActualTypeVariable((constraintType as IndexType).type); - if (typeVariable.flags & TypeFlags.TypeParameter) { - return typeVariable as TypeParameter; - } + function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { + const declaration = type.objectFlags & ObjectFlags.Reference ? (type as TypeReference).node! : + type.objectFlags & ObjectFlags.InstantiationExpressionType ? (type as InstantiationExpressionType).node : + type.symbol.declarations![0]; + const links = getNodeLinks(declaration); + const target = type.objectFlags & ObjectFlags.Reference ? links.resolvedType! as DeferredTypeReference : + type.objectFlags & ObjectFlags.Instantiated ? type.target! : type; + let typeParameters = links.outerTypeParameters; + if (!typeParameters) { + // The first time an anonymous type is instantiated we compute and store a list of the type + // parameters that are in scope (and therefore potentially referenced). For type literals that + // aren't the right hand side of a generic type alias declaration we optimize by reducing the + // set of type parameters to those that are possibly referenced in the literal. + let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true); + if (isJSConstructor(declaration)) { + const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters); + outerTypeParameters = addRange(outerTypeParameters, templateTagParameters); + } + typeParameters = outerTypeParameters || emptyArray; + const allDeclarations = type.objectFlags & (ObjectFlags.Reference | ObjectFlags.InstantiationExpressionType) ? [declaration] : type.symbol.declarations!; + typeParameters = (target.objectFlags & (ObjectFlags.Reference | ObjectFlags.InstantiationExpressionType) || target.symbol.flags & SymbolFlags.Method || target.symbol.flags & SymbolFlags.TypeLiteral) && !target.aliasTypeArguments ? + filter(typeParameters, tp => some(allDeclarations, d => isTypeParameterPossiblyReferenced(tp, d))) : + typeParameters; + links.outerTypeParameters = typeParameters; + } + if (typeParameters.length) { + // We are instantiating an anonymous type that has one or more type parameters in scope. Apply the + // mapper to the type parameters to produce the effective list of type arguments, and compute the + // instantiation cache key from the type IDs of the type arguments. + const combinedMapper = combineTypeMappers(type.mapper, mapper); + const typeArguments = map(typeParameters, t => getMappedType(t, combinedMapper)); + const newAliasSymbol = aliasSymbol || type.aliasSymbol; + const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + const id = getTypeListId(typeArguments) + getAliasId(newAliasSymbol, newAliasTypeArguments); + if (!target.instantiations) { + target.instantiations = new Map(); + target.instantiations.set(getTypeListId(typeParameters) + getAliasId(target.aliasSymbol, target.aliasTypeArguments), target); + } + let result = target.instantiations.get(id); + if (!result) { + const newMapper = createTypeMapper(typeParameters, typeArguments); + result = target.objectFlags & ObjectFlags.Reference ? createDeferredTypeReference((type as DeferredTypeReference).target, (type as DeferredTypeReference).node, newMapper, newAliasSymbol, newAliasTypeArguments) : + target.objectFlags & ObjectFlags.Mapped ? instantiateMappedType(target as MappedType, newMapper, newAliasSymbol, newAliasTypeArguments) : + instantiateAnonymousType(target, newMapper, newAliasSymbol, newAliasTypeArguments); + target.instantiations.set(id, result); } - return undefined; + return result; } + return type; + } - function instantiateMappedType(type: MappedType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - // For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping - // operation depends on T as follows: - // * If T is a primitive type no mapping is performed and the result is simply T. - // * If T is a union type we distribute the mapped type over the union. - // * If T is an array we map to an array where the element type has been transformed. - // * If T is a tuple we map to a tuple where the element types have been transformed. - // * Otherwise we map to an object type where the type of each property has been transformed. - // For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } | - // { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce - // { [P in keyof A]: X } | undefined. - const typeVariable = getHomomorphicTypeVariable(type); - if (typeVariable) { - const mappedTypeVariable = instantiateType(typeVariable, mapper); - if (typeVariable !== mappedTypeVariable) { - return mapTypeWithAlias(getReducedType(mappedTypeVariable), t => { - if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) { - if (!type.declaration.nameType) { - let constraint; - if (isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 && - (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, isArrayOrTupleType)) { - return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper)); - } - if (isGenericTupleType(t)) { - return instantiateMappedGenericTupleType(t, type, typeVariable, mapper); - } - if (isTupleType(t)) { - return instantiateMappedTupleType(t, type, prependTypeMapping(typeVariable, t, mapper)); - } - } - return instantiateAnonymousType(type, prependTypeMapping(typeVariable, t, mapper)); - } - return t; - }, aliasSymbol, aliasTypeArguments); + function maybeTypeParameterReference(node: Node) { + return !(node.parent.kind === SyntaxKind.TypeReference && (node.parent as TypeReferenceNode).typeArguments && node === (node.parent as TypeReferenceNode).typeName || + node.parent.kind === SyntaxKind.ImportType && (node.parent as ImportTypeNode).typeArguments && node === (node.parent as ImportTypeNode).qualifier); + } + + function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) { + // If the type parameter doesn't have exactly one declaration, if there are intervening statement blocks + // between the node and the type parameter declaration, if the node contains actual references to the + // type parameter, or if the node contains type queries that we can't prove couldn't contain references to the type parameter, + // 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; + for (let n = node; n !== container; n = n.parent) { + if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n as ConditionalTypeNode).extendsType, containsReference)) { + return true; } } - // If the constraint type of the instantiation is the wildcard type, return the wildcard type. - return instantiateType(getConstraintTypeFromMappedType(type), mapper) === wildcardType ? wildcardType : instantiateAnonymousType(type, mapper, aliasSymbol, aliasTypeArguments); + return containsReference(node); } + return true; + function containsReference(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ThisType: + return !!tp.isThisType; + case SyntaxKind.Identifier: + return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) && + getTypeFromTypeNodeWorker(node as TypeNode) === tp; // use worker because we're looking for === equality + case SyntaxKind.TypeQuery: + const entityName = (node as TypeQueryNode).exprName; + const firstIdentifier = getFirstIdentifier(entityName); + const firstIdentifierSymbol = getResolvedSymbol(firstIdentifier); + const tpDeclaration = tp.symbol.declarations![0]; // There is exactly one declaration, otherwise `containsReference` is not called + let tpScope: Node; + if (tpDeclaration.kind === SyntaxKind.TypeParameter) { // Type parameter is a regular type parameter, e.g. foo + tpScope = tpDeclaration.parent; + } + else if (tp.isThisType) { + // Type parameter is the this type, and its declaration is the class declaration. + tpScope = tpDeclaration; + } + else { + // Type parameter's declaration was unrecognized. + // This could happen if the type parameter comes from e.g. a JSDoc annotation, so we default to returning true. + return true; + } - function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) { - return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state; + if (firstIdentifierSymbol.declarations) { + return some(firstIdentifierSymbol.declarations, idDecl => isNodeDescendantOf(idDecl, tpScope)) || + some((node as TypeQueryNode).typeArguments, containsReference); + } + return true; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return !(node as FunctionLikeDeclaration).type && !!(node as FunctionLikeDeclaration).body || + some((node as FunctionLikeDeclaration).typeParameters, containsReference) || + some((node as FunctionLikeDeclaration).parameters, containsReference) || + !!(node as FunctionLikeDeclaration).type && containsReference((node as FunctionLikeDeclaration).type!); + } + return !!forEachChild(node, containsReference); } + } - function instantiateMappedGenericTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) { - // When a tuple type is generic (i.e. when it contains variadic elements), we want to eagerly map the - // non-generic elements and defer mapping the generic elements. In order to facilitate this, we transform - // M<[A, B?, ...T, ...C[]] into [...M<[A]>, ...M<[B?]>, ...M, ...M] and then rely on tuple type - // normalization to resolve the non-generic parts of the resulting tuple. - const elementFlags = tupleType.target.elementFlags; - const elementTypes = map(getTypeArguments(tupleType), (t, i) => { - const singleton = elementFlags[i] & ElementFlags.Variadic ? t : - elementFlags[i] & ElementFlags.Rest ? createArrayType(t) : - createTupleType([t], [elementFlags[i]]); - // The singleton is never a generic tuple type, so it is safe to recurse here. - return instantiateMappedType(mappedType, prependTypeMapping(typeVariable, singleton, mapper)); - }); - const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, getMappedTypeModifiers(mappedType)); - return createTupleType(elementTypes, map(elementTypes, _ => ElementFlags.Variadic), newReadonly); - } - - function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) { - const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper); - return isErrorType(elementType) ? errorType : - createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType))); - } - - function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) { - const elementFlags = tupleType.target.elementFlags; - const elementTypes = map(getTypeArguments(tupleType), (_, i) => - instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(elementFlags[i] & ElementFlags.Optional), mapper)); - const modifiers = getMappedTypeModifiers(mappedType); - const newTupleModifiers = modifiers & MappedTypeModifiers.IncludeOptional ? map(elementFlags, f => f & ElementFlags.Required ? ElementFlags.Optional : f) : - modifiers & MappedTypeModifiers.ExcludeOptional ? map(elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : - elementFlags; - const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers); - return contains(elementTypes, errorType) ? errorType : - createTupleType(elementTypes, newTupleModifiers, newReadonly, tupleType.target.labeledElementDeclarations); - } - - function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) { - const templateMapper = appendTypeMapping(mapper, getTypeParameterFromMappedType(type), key); - const propType = instantiateType(getTemplateTypeFromMappedType(type.target as MappedType || type), templateMapper); - const modifiers = getMappedTypeModifiers(type); - return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) : - strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : - propType; + function getHomomorphicTypeVariable(type: MappedType) { + const constraintType = getConstraintTypeFromMappedType(type); + if (constraintType.flags & TypeFlags.Index) { + const typeVariable = getActualTypeVariable((constraintType as IndexType).type); + if (typeVariable.flags & TypeFlags.TypeParameter) { + return typeVariable as TypeParameter; + } } + return undefined; + } - function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): AnonymousType { - const result = createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol) as AnonymousType; - if (type.objectFlags & ObjectFlags.Mapped) { - (result as MappedType).declaration = (type as MappedType).declaration; - // C.f. instantiateSignature - const origTypeParameter = getTypeParameterFromMappedType(type as MappedType); - const freshTypeParameter = cloneTypeParameter(origTypeParameter); - (result as MappedType).typeParameter = freshTypeParameter; - mapper = combineTypeMappers(makeUnaryTypeMapper(origTypeParameter, freshTypeParameter), mapper); - freshTypeParameter.mapper = mapper; + function instantiateMappedType(type: MappedType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + // For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping + // operation depends on T as follows: + // * If T is a primitive type no mapping is performed and the result is simply T. + // * If T is a union type we distribute the mapped type over the union. + // * If T is an array we map to an array where the element type has been transformed. + // * If T is a tuple we map to a tuple where the element types have been transformed. + // * Otherwise we map to an object type where the type of each property has been transformed. + // For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } | + // { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce + // { [P in keyof A]: X } | undefined. + const typeVariable = getHomomorphicTypeVariable(type); + if (typeVariable) { + const mappedTypeVariable = instantiateType(typeVariable, mapper); + if (typeVariable !== mappedTypeVariable) { + return mapTypeWithAlias(getReducedType(mappedTypeVariable), t => { + if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) { + if (!type.declaration.nameType) { + let constraint; + if (isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 && + (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, isArrayOrTupleType)) { + return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper)); + } + if (isGenericTupleType(t)) { + return instantiateMappedGenericTupleType(t, type, typeVariable, mapper); + } + if (isTupleType(t)) { + return instantiateMappedTupleType(t, type, prependTypeMapping(typeVariable, t, mapper)); + } + } + return instantiateAnonymousType(type, prependTypeMapping(typeVariable, t, mapper)); + } + return t; + }, aliasSymbol, aliasTypeArguments); } - if (type.objectFlags & ObjectFlags.InstantiationExpressionType) { - (result as InstantiationExpressionType).node = (type as InstantiationExpressionType).node; + } + // If the constraint type of the instantiation is the wildcard type, return the wildcard type. + return instantiateType(getConstraintTypeFromMappedType(type), mapper) === wildcardType ? wildcardType : instantiateAnonymousType(type, mapper, aliasSymbol, aliasTypeArguments); + } + + function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) { + return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state; + } + + function instantiateMappedGenericTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) { + // When a tuple type is generic (i.e. when it contains variadic elements), we want to eagerly map the + // non-generic elements and defer mapping the generic elements. In order to facilitate this, we transform + // M<[A, B?, ...T, ...C[]] into [...M<[A]>, ...M<[B?]>, ...M, ...M] and then rely on tuple type + // normalization to resolve the non-generic parts of the resulting tuple. + const elementFlags = tupleType.target.elementFlags; + const elementTypes = map(getTypeArguments(tupleType), (t, i) => { + const singleton = elementFlags[i] & ElementFlags.Variadic ? t : + elementFlags[i] & ElementFlags.Rest ? createArrayType(t) : + createTupleType([t], [elementFlags[i]]); + // The singleton is never a generic tuple type, so it is safe to recurse here. + return instantiateMappedType(mappedType, prependTypeMapping(typeVariable, singleton, mapper)); + }); + const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, getMappedTypeModifiers(mappedType)); + return createTupleType(elementTypes, map(elementTypes, _ => ElementFlags.Variadic), newReadonly); + } + + function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) { + const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper); + return isErrorType(elementType) ? errorType : + createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType))); + } + + function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) { + const elementFlags = tupleType.target.elementFlags; + const elementTypes = map(getTypeArguments(tupleType), (_, i) => + instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(elementFlags[i] & ElementFlags.Optional), mapper)); + const modifiers = getMappedTypeModifiers(mappedType); + const newTupleModifiers = modifiers & MappedTypeModifiers.IncludeOptional ? map(elementFlags, f => f & ElementFlags.Required ? ElementFlags.Optional : f) : + modifiers & MappedTypeModifiers.ExcludeOptional ? map(elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : + elementFlags; + const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers); + return contains(elementTypes, errorType) ? errorType : + createTupleType(elementTypes, newTupleModifiers, newReadonly, tupleType.target.labeledElementDeclarations); + } + + function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) { + const templateMapper = appendTypeMapping(mapper, getTypeParameterFromMappedType(type), key); + const propType = instantiateType(getTemplateTypeFromMappedType(type.target as MappedType || type), templateMapper); + const modifiers = getMappedTypeModifiers(type); + return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType, /*isProperty*/ true) : + strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : + propType; + } + + function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): AnonymousType { + const result = createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol) as AnonymousType; + if (type.objectFlags & ObjectFlags.Mapped) { + (result as MappedType).declaration = (type as MappedType).declaration; + // C.f. instantiateSignature + const origTypeParameter = getTypeParameterFromMappedType(type as MappedType); + const freshTypeParameter = cloneTypeParameter(origTypeParameter); + (result as MappedType).typeParameter = freshTypeParameter; + mapper = combineTypeMappers(makeUnaryTypeMapper(origTypeParameter, freshTypeParameter), mapper); + freshTypeParameter.mapper = mapper; + } + if (type.objectFlags & ObjectFlags.InstantiationExpressionType) { + (result as InstantiationExpressionType).node = (type as InstantiationExpressionType).node; + } + result.target = type; + result.mapper = mapper; + result.aliasSymbol = aliasSymbol || type.aliasSymbol; + result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + result.objectFlags |= result.aliasTypeArguments ? getPropagatingFlagsOfTypes(result.aliasTypeArguments) : 0; + return result; + } + + function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + const root = type.root; + if (root.outerTypeParameters) { + // We are instantiating a conditional type that has one or more type parameters in scope. Apply the + // mapper to the type parameters to produce the effective list of type arguments, and compute the + // instantiation cache key from the type IDs of the type arguments. + const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper)); + const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); + let result = root.instantiations!.get(id); + if (!result) { + const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); + const checkType = root.checkType; + const distributionType = root.isDistributive ? getMappedType(checkType, newMapper) : undefined; + // Distributive conditional types are distributed over union types. For example, when the + // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the + // result is (A extends U ? X : Y) | (B extends U ? X : Y). + result = distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never) ? + mapTypeWithAlias(getReducedType(distributionType), t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper)), aliasSymbol, aliasTypeArguments) : + getConditionalType(root, newMapper, aliasSymbol, aliasTypeArguments); + root.instantiations!.set(id, result); } - result.target = type; - result.mapper = mapper; - result.aliasSymbol = aliasSymbol || type.aliasSymbol; - result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - result.objectFlags |= result.aliasTypeArguments ? getPropagatingFlagsOfTypes(result.aliasTypeArguments) : 0; return result; } + return type; + } - function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - const root = type.root; - if (root.outerTypeParameters) { - // We are instantiating a conditional type that has one or more type parameters in scope. Apply the - // mapper to the type parameters to produce the effective list of type arguments, and compute the - // instantiation cache key from the type IDs of the type arguments. - const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper)); - const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); - let result = root.instantiations!.get(id); - if (!result) { - const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); - const checkType = root.checkType; - const distributionType = root.isDistributive ? getMappedType(checkType, newMapper) : undefined; - // Distributive conditional types are distributed over union types. For example, when the - // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the - // result is (A extends U ? X : Y) | (B extends U ? X : Y). - result = distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never) ? - mapTypeWithAlias(getReducedType(distributionType), t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper)), aliasSymbol, aliasTypeArguments) : - getConditionalType(root, newMapper, aliasSymbol, aliasTypeArguments); - root.instantiations!.set(id, result); - } - return result; - } + function instantiateType(type: Type, mapper: TypeMapper | undefined): Type; + function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined; + function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined { + return type && mapper ? instantiateTypeWithAlias(type, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type; + } + + function instantiateTypeWithAlias(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { + if (!couldContainTypeVariables(type)) { return type; } - - function instantiateType(type: Type, mapper: TypeMapper | undefined): Type; - function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined; - function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined { - return type && mapper ? instantiateTypeWithAlias(type, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type; + if (instantiationDepth === 100 || instantiationCount >= 5000000) { + // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement + // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types + // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. + tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); + error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); + return errorType; } + totalInstantiationCount++; + instantiationCount++; + instantiationDepth++; + const result = instantiateTypeWorker(type, mapper, aliasSymbol, aliasTypeArguments); + instantiationDepth--; + return result; + } - function instantiateTypeWithAlias(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { - if (!couldContainTypeVariables(type)) { - return type; - } - if (instantiationDepth === 100 || instantiationCount >= 5000000) { - // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement - // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types - // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. - tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); - error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); - return errorType; - } - totalInstantiationCount++; - instantiationCount++; - instantiationDepth++; - const result = instantiateTypeWorker(type, mapper, aliasSymbol, aliasTypeArguments); - instantiationDepth--; - return result; + function instantiateTypeWorker(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { + const flags = type.flags; + if (flags & TypeFlags.TypeParameter) { + return getMappedType(type, mapper); } - - function instantiateTypeWorker(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { - const flags = type.flags; - if (flags & TypeFlags.TypeParameter) { - return getMappedType(type, mapper); - } - if (flags & TypeFlags.Object) { - const objectFlags = (type as ObjectType).objectFlags; - if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) { - const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments; - const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); - return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type; - } - if (objectFlags & ObjectFlags.ReverseMapped) { - return instantiateReverseMappedType(type as ReverseMappedType, mapper); - } - return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments); - } - return type; - } - if (flags & TypeFlags.UnionOrIntersection) { - const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined; - const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types; - const newTypes = instantiateTypes(types, mapper); - if (newTypes === types && aliasSymbol === type.aliasSymbol) { - return type; - } - const newAliasSymbol = aliasSymbol || type.aliasSymbol; - const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ? - getIntersectionType(newTypes, newAliasSymbol, newAliasTypeArguments) : - getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments); - } - if (flags & TypeFlags.Index) { - return getIndexType(instantiateType((type as IndexType).type, mapper)); - } - if (flags & TypeFlags.TemplateLiteral) { - return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper)); - } - if (flags & TypeFlags.StringMapping) { - return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper)); - } - if (flags & TypeFlags.IndexedAccess) { - const newAliasSymbol = aliasSymbol || type.aliasSymbol; - const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - return getIndexedAccessType(instantiateType((type as IndexedAccessType).objectType, mapper), instantiateType((type as IndexedAccessType).indexType, mapper), (type as IndexedAccessType).accessFlags, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments); - } - if (flags & TypeFlags.Conditional) { - return getConditionalTypeInstantiation(type as ConditionalType, combineTypeMappers((type as ConditionalType).mapper, mapper), aliasSymbol, aliasTypeArguments); - } - if (flags & TypeFlags.Substitution) { - const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); - const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper); - // A substitution type originates in the true branch of a conditional type and can be resolved - // to just the base type in the same cases as the conditional type resolves to its true branch - // (because the base type is then known to satisfy the constraint). - if (newBaseType.flags & TypeFlags.TypeVariable && isGenericType(newConstraint)) { - return getSubstitutionType(newBaseType, newConstraint); + if (flags & TypeFlags.Object) { + const objectFlags = (type as ObjectType).objectFlags; + if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) { + const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments; + const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); + return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type; } - if (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { - return newBaseType; + if (objectFlags & ObjectFlags.ReverseMapped) { + return instantiateReverseMappedType(type as ReverseMappedType, mapper); } - return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); + return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments); } return type; } - - function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { - const innerMappedType = instantiateType(type.mappedType, mapper); - if (!(getObjectFlags(innerMappedType) & ObjectFlags.Mapped)) { - return type; - } - const innerIndexType = instantiateType(type.constraintType, mapper); - if (!(innerIndexType.flags & TypeFlags.Index)) { + if (flags & TypeFlags.UnionOrIntersection) { + const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined; + const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types; + const newTypes = instantiateTypes(types, mapper); + if (newTypes === types && aliasSymbol === type.aliasSymbol) { return type; } - const instantiated = inferTypeForHomomorphicMappedType( - instantiateType(type.source, mapper), - innerMappedType as MappedType, - innerIndexType as IndexType - ); - if (instantiated) { - return instantiated; - } - return type; // Nested invocation of `inferTypeForHomomorphicMappedType` or the `source` instantiated into something unmappable + const newAliasSymbol = aliasSymbol || type.aliasSymbol; + const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ? + getIntersectionType(newTypes, newAliasSymbol, newAliasTypeArguments) : + getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments); } - - function getUniqueLiteralFilledInstantiation(type: Type) { - return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : - type.uniqueLiteralFilledInstantiation || (type.uniqueLiteralFilledInstantiation = instantiateType(type, uniqueLiteralMapper)); + if (flags & TypeFlags.Index) { + return getIndexType(instantiateType((type as IndexType).type, mapper)); } - - function getPermissiveInstantiation(type: Type) { - return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : - type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); + if (flags & TypeFlags.TemplateLiteral) { + return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper)); } - - function getRestrictiveInstantiation(type: Type) { - if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) { - return type; + if (flags & TypeFlags.StringMapping) { + return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper)); + } + if (flags & TypeFlags.IndexedAccess) { + const newAliasSymbol = aliasSymbol || type.aliasSymbol; + const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + return getIndexedAccessType(instantiateType((type as IndexedAccessType).objectType, mapper), instantiateType((type as IndexedAccessType).indexType, mapper), (type as IndexedAccessType).accessFlags, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments); + } + if (flags & TypeFlags.Conditional) { + return getConditionalTypeInstantiation(type as ConditionalType, combineTypeMappers((type as ConditionalType).mapper, mapper), aliasSymbol, aliasTypeArguments); + } + if (flags & TypeFlags.Substitution) { + const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); + const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper); + // A substitution type originates in the true branch of a conditional type and can be resolved + // to just the base type in the same cases as the conditional type resolves to its true branch + // (because the base type is then known to satisfy the constraint). + if (newBaseType.flags & TypeFlags.TypeVariable && isGenericType(newConstraint)) { + return getSubstitutionType(newBaseType, newConstraint); } - if (type.restrictiveInstantiation) { - return type.restrictiveInstantiation; + if (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { + return newBaseType; } - type.restrictiveInstantiation = instantiateType(type, restrictiveMapper); - // We set the following so we don't attempt to set the restrictive instance of a restrictive instance - // which is redundant - we'll produce new type identities, but all type params have already been mapped. - // This also gives us a way to detect restrictive instances upon comparisons and _disable_ the "distributeive constraint" - // assignability check for them, which is distinctly unsafe, as once you have a restrctive instance, all the type parameters - // are constrained to `unknown` and produce tons of false positives/negatives! - type.restrictiveInstantiation.restrictiveInstantiation = type.restrictiveInstantiation; - return type.restrictiveInstantiation; + return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); } + return type; + } - function instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper) { - return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration); + function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { + const innerMappedType = instantiateType(type.mappedType, mapper); + if (!(getObjectFlags(innerMappedType) & ObjectFlags.Mapped)) { + return type; + } + const innerIndexType = instantiateType(type.constraintType, mapper); + if (!(innerIndexType.flags & TypeFlags.Index)) { + return type; + } + const instantiated = inferTypeForHomomorphicMappedType( + instantiateType(type.source, mapper), + innerMappedType as MappedType, + innerIndexType as IndexType + ); + if (instantiated) { + return instantiated; } + return type; // Nested invocation of `inferTypeForHomomorphicMappedType` or the `source` instantiated into something unmappable + } - // Returns true if the given expression contains (at any level of nesting) a function or arrow expression - // that is subject to contextual typing. - function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - switch (node.kind) { - 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 as FunctionExpression | ArrowFunction | MethodDeclaration); - case SyntaxKind.ObjectLiteralExpression: - return some((node as ObjectLiteralExpression).properties, isContextSensitive); - case SyntaxKind.ArrayLiteralExpression: - return some((node as ArrayLiteralExpression).elements, isContextSensitive); - case SyntaxKind.ConditionalExpression: - return isContextSensitive((node as ConditionalExpression).whenTrue) || - isContextSensitive((node as ConditionalExpression).whenFalse); - case SyntaxKind.BinaryExpression: - return ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken) && - (isContextSensitive((node as BinaryExpression).left) || isContextSensitive((node as BinaryExpression).right)); - case SyntaxKind.PropertyAssignment: - return isContextSensitive((node as PropertyAssignment).initializer); - case SyntaxKind.ParenthesizedExpression: - return isContextSensitive((node as ParenthesizedExpression).expression); - case SyntaxKind.JsxAttributes: - return some((node as JsxAttributes).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive); - case SyntaxKind.JsxAttribute: { - // If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive. - const { initializer } = node as JsxAttribute; - return !!initializer && isContextSensitive(initializer); - } - case SyntaxKind.JsxExpression: { - // It is possible to that node.expression is undefined (e.g

) - const { expression } = node as JsxExpression; - return !!expression && isContextSensitive(expression); - } - } + function getUniqueLiteralFilledInstantiation(type: Type) { + return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : + type.uniqueLiteralFilledInstantiation || (type.uniqueLiteralFilledInstantiation = instantiateType(type, uniqueLiteralMapper)); + } - return false; - } + function getPermissiveInstantiation(type: Type) { + return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : + type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); + } - function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { - return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node); + function getRestrictiveInstantiation(type: Type) { + if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) { + return type; } - - function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) { - // TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value. - return !node.typeParameters && !getEffectiveReturnTypeNode(node) && !!node.body && node.body.kind !== SyntaxKind.Block && isContextSensitive(node.body); + if (type.restrictiveInstantiation) { + return type.restrictiveInstantiation; } + type.restrictiveInstantiation = instantiateType(type, restrictiveMapper); + // We set the following so we don't attempt to set the restrictive instance of a restrictive instance + // which is redundant - we'll produce new type identities, but all type params have already been mapped. + // This also gives us a way to detect restrictive instances upon comparisons and _disable_ the "distributeive constraint" + // assignability check for them, which is distinctly unsafe, as once you have a restrctive instance, all the type parameters + // are constrained to `unknown` and produce tons of false positives/negatives! + type.restrictiveInstantiation.restrictiveInstantiation = type.restrictiveInstantiation; + return type.restrictiveInstantiation; + } - function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { - return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && - isContextSensitiveFunctionLikeDeclaration(func); - } + function instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper) { + return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration); + } - function getTypeWithoutSignatures(type: Type): Type { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - if (resolved.constructSignatures.length || resolved.callSignatures.length) { - const result = createObjectType(ObjectFlags.Anonymous, type.symbol); - result.members = resolved.members; - result.properties = resolved.properties; - result.callSignatures = emptyArray; - result.constructSignatures = emptyArray; - result.indexInfos = emptyArray; - return result; - } - } - else if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(map((type as IntersectionType).types, getTypeWithoutSignatures)); + // Returns true if the given expression contains (at any level of nesting) a function or arrow expression + // that is subject to contextual typing. + function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + switch (node.kind) { + 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 as FunctionExpression | ArrowFunction | MethodDeclaration); + case SyntaxKind.ObjectLiteralExpression: + return some((node as ObjectLiteralExpression).properties, isContextSensitive); + case SyntaxKind.ArrayLiteralExpression: + return some((node as ArrayLiteralExpression).elements, isContextSensitive); + case SyntaxKind.ConditionalExpression: + return isContextSensitive((node as ConditionalExpression).whenTrue) || + isContextSensitive((node as ConditionalExpression).whenFalse); + case SyntaxKind.BinaryExpression: + return ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken || (node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken) && + (isContextSensitive((node as BinaryExpression).left) || isContextSensitive((node as BinaryExpression).right)); + case SyntaxKind.PropertyAssignment: + return isContextSensitive((node as PropertyAssignment).initializer); + case SyntaxKind.ParenthesizedExpression: + return isContextSensitive((node as ParenthesizedExpression).expression); + case SyntaxKind.JsxAttributes: + return some((node as JsxAttributes).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive); + case SyntaxKind.JsxAttribute: { + // If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive. + const { initializer } = node as JsxAttribute; + return !!initializer && isContextSensitive(initializer); + } + case SyntaxKind.JsxExpression: { + // It is possible to that node.expression is undefined (e.g
) + const { expression } = node as JsxExpression; + return !!expression && isContextSensitive(expression); + } + } + + return false; + } + + function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { + return hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node); + } + + function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) { + // TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value. + return !node.typeParameters && !getEffectiveReturnTypeNode(node) && !!node.body && node.body.kind !== SyntaxKind.Block && isContextSensitive(node.body); + } + + function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { + return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && + isContextSensitiveFunctionLikeDeclaration(func); + } + + function getTypeWithoutSignatures(type: Type): Type { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + if (resolved.constructSignatures.length || resolved.callSignatures.length) { + const result = createObjectType(ObjectFlags.Anonymous, type.symbol); + result.members = resolved.members; + result.properties = resolved.properties; + result.callSignatures = emptyArray; + result.constructSignatures = emptyArray; + result.indexInfos = emptyArray; + return result; } - return type; } + else if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(map((type as IntersectionType).types, getTypeWithoutSignatures)); + } + return type; + } - // TYPE CHECKING + // TYPE CHECKING - function isTypeIdenticalTo(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, identityRelation); - } + function isTypeIdenticalTo(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, identityRelation); + } - function compareTypesIdentical(source: Type, target: Type): Ternary { - return isTypeRelatedTo(source, target, identityRelation) ? Ternary.True : Ternary.False; - } + function compareTypesIdentical(source: Type, target: Type): Ternary { + return isTypeRelatedTo(source, target, identityRelation) ? Ternary.True : Ternary.False; + } - function compareTypesAssignable(source: Type, target: Type): Ternary { - return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False; - } + function compareTypesAssignable(source: Type, target: Type): Ternary { + return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False; + } - function compareTypesSubtypeOf(source: Type, target: Type): Ternary { - return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False; - } + function compareTypesSubtypeOf(source: Type, target: Type): Ternary { + return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False; + } - function isTypeSubtypeOf(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, subtypeRelation); - } + function isTypeSubtypeOf(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, subtypeRelation); + } - function isTypeAssignableTo(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, assignableRelation); - } + function isTypeAssignableTo(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, assignableRelation); + } - // An object type S is considered to be derived from an object type T if - // S is a union type and every constituent of S is derived from T, - // T is a union type and S is derived from at least one constituent of T, or - // S is a type variable with a base constraint that is derived from T, - // T is one of the global types Object and Function and S is a subtype of T, or - // T occurs directly or indirectly in an 'extends' clause of S. - // Note that this check ignores type parameters and only considers the - // inheritance hierarchy. - function isTypeDerivedFrom(source: Type, target: Type): boolean { - return source.flags & TypeFlags.Union ? every((source as UnionType).types, t => isTypeDerivedFrom(t, target)) : - target.flags & TypeFlags.Union ? some((target as UnionType).types, t => isTypeDerivedFrom(source, t)) : - source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : - target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : - target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) : - hasBaseType(source, getTargetType(target)) || (isArrayType(target) && !isReadonlyArrayType(target) && isTypeDerivedFrom(source, globalReadonlyArrayType)); - } + // An object type S is considered to be derived from an object type T if + // S is a union type and every constituent of S is derived from T, + // T is a union type and S is derived from at least one constituent of T, or + // S is a type variable with a base constraint that is derived from T, + // T is one of the global types Object and Function and S is a subtype of T, or + // T occurs directly or indirectly in an 'extends' clause of S. + // Note that this check ignores type parameters and only considers the + // inheritance hierarchy. + function isTypeDerivedFrom(source: Type, target: Type): boolean { + return source.flags & TypeFlags.Union ? every((source as UnionType).types, t => isTypeDerivedFrom(t, target)) : + target.flags & TypeFlags.Union ? some((target as UnionType).types, t => isTypeDerivedFrom(source, t)) : + source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : + target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : + target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) : + hasBaseType(source, getTargetType(target)) || (isArrayType(target) && !isReadonlyArrayType(target) && isTypeDerivedFrom(source, globalReadonlyArrayType)); + } - /** - * This is *not* a bi-directional relationship. - * If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'. - * - * A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T. - * It is used to check following cases: - * - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`). - * - the types of `case` clause expressions and their respective `switch` expressions. - * - the type of an expression in a type assertion with the type being asserted. - */ - function isTypeComparableTo(source: Type, target: Type): boolean { - return isTypeRelatedTo(source, target, comparableRelation); - } + /** + * This is *not* a bi-directional relationship. + * If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'. + * + * A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T. + * It is used to check following cases: + * - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`). + * - the types of `case` clause expressions and their respective `switch` expressions. + * - the type of an expression in a type assertion with the type being asserted. + */ + function isTypeComparableTo(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, comparableRelation); + } - function areTypesComparable(type1: Type, type2: Type): boolean { - return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1); - } + function areTypesComparable(type1: Type, type2: Type): boolean { + return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1); + } - function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { errors?: Diagnostic[] }): boolean { - return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject); - } + function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { errors?: Diagnostic[] }): boolean { + return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject); + } - /** - * Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to - * attempt to issue more specific errors on, for example, specific object literal properties or tuple members. - */ - function checkTypeAssignableToAndOptionallyElaborate(source: Type, target: Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { - return checkTypeRelatedToAndOptionallyElaborate(source, target, assignableRelation, errorNode, expr, headMessage, containingMessageChain, /*errorOutputContainer*/ undefined); - } - - function checkTypeRelatedToAndOptionallyElaborate( - source: Type, - target: Type, - relation: ESMap, - errorNode: Node | undefined, - expr: Expression | undefined, - headMessage: DiagnosticMessage | undefined, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - if (isTypeRelatedTo(source, target, relation)) return true; - if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { - return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer); - } - return false; - } + /** + * Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to + * attempt to issue more specific errors on, for example, specific object literal properties or tuple members. + */ + function checkTypeAssignableToAndOptionallyElaborate(source: Type, target: Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { + return checkTypeRelatedToAndOptionallyElaborate(source, target, assignableRelation, errorNode, expr, headMessage, containingMessageChain, /*errorOutputContainer*/ undefined); + } + + function checkTypeRelatedToAndOptionallyElaborate( + source: Type, + target: Type, + relation: ESMap, + errorNode: Node | undefined, + expr: Expression | undefined, + headMessage: DiagnosticMessage | undefined, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ): boolean { + if (isTypeRelatedTo(source, target, relation)) return true; + if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { + return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer); + } + return false; + } - function isOrHasGenericConditional(type: Type): boolean { - return !!(type.flags & TypeFlags.Conditional || (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isOrHasGenericConditional))); + function isOrHasGenericConditional(type: Type): boolean { + return !!(type.flags & TypeFlags.Conditional || (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isOrHasGenericConditional))); + } + + function elaborateError( + node: Expression | undefined, + source: Type, + target: Type, + relation: ESMap, + headMessage: DiagnosticMessage | undefined, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ): boolean { + if (!node || isOrHasGenericConditional(target)) return false; + if (!checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined) + && elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { + return true; } + switch (node.kind) { + case SyntaxKind.JsxExpression: + case SyntaxKind.ParenthesizedExpression: + return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); + case SyntaxKind.BinaryExpression: + switch ((node as BinaryExpression).operatorToken.kind) { + case SyntaxKind.EqualsToken: + case SyntaxKind.CommaToken: + return elaborateError((node as BinaryExpression).right, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); + } + break; + case SyntaxKind.ObjectLiteralExpression: + return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); + case SyntaxKind.ArrayLiteralExpression: + return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); + case SyntaxKind.JsxAttributes: + return elaborateJsxComponents(node as JsxAttributes, source, target, relation, containingMessageChain, errorOutputContainer); + case SyntaxKind.ArrowFunction: + return elaborateArrowFunction(node as ArrowFunction, source, target, relation, containingMessageChain, errorOutputContainer); + } + return false; + } - function elaborateError( - node: Expression | undefined, - source: Type, - target: Type, - relation: ESMap, - headMessage: DiagnosticMessage | undefined, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - if (!node || isOrHasGenericConditional(target)) return false; - if (!checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined) - && elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { + function elaborateDidYouMeanToCallOrConstruct( + node: Expression, + source: Type, + target: Type, + relation: ESMap, + headMessage: DiagnosticMessage | undefined, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ): boolean { + const callSignatures = getSignaturesOfType(source, SignatureKind.Call); + const constructSignatures = getSignaturesOfType(source, SignatureKind.Construct); + for (const signatures of [constructSignatures, callSignatures]) { + if (some(signatures, s => { + const returnType = getReturnTypeOfSignature(s); + return !(returnType.flags & (TypeFlags.Any | TypeFlags.Never)) && checkTypeRelatedTo(returnType, target, relation, /*errorNode*/ undefined); + })) { + const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; + checkTypeAssignableTo(source, target, node, headMessage, containingMessageChain, resultObj); + const diagnostic = resultObj.errors![resultObj.errors!.length - 1]; + addRelatedInfo(diagnostic, createDiagnosticForNode( + node, + signatures === constructSignatures ? Diagnostics.Did_you_mean_to_use_new_with_this_expression : Diagnostics.Did_you_mean_to_call_this_expression + )); return true; } - switch (node.kind) { - case SyntaxKind.JsxExpression: - case SyntaxKind.ParenthesizedExpression: - return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); - case SyntaxKind.BinaryExpression: - switch ((node as BinaryExpression).operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.CommaToken: - return elaborateError((node as BinaryExpression).right, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); - } - break; - case SyntaxKind.ObjectLiteralExpression: - return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); - case SyntaxKind.ArrayLiteralExpression: - return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); - case SyntaxKind.JsxAttributes: - return elaborateJsxComponents(node as JsxAttributes, source, target, relation, containingMessageChain, errorOutputContainer); - case SyntaxKind.ArrowFunction: - return elaborateArrowFunction(node as ArrowFunction, source, target, relation, containingMessageChain, errorOutputContainer); - } - return false; } + return false; + } - function elaborateDidYouMeanToCallOrConstruct( - node: Expression, - source: Type, - target: Type, - relation: ESMap, - headMessage: DiagnosticMessage | undefined, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - const callSignatures = getSignaturesOfType(source, SignatureKind.Call); - const constructSignatures = getSignaturesOfType(source, SignatureKind.Construct); - for (const signatures of [constructSignatures, callSignatures]) { - if (some(signatures, s => { - const returnType = getReturnTypeOfSignature(s); - return !(returnType.flags & (TypeFlags.Any | TypeFlags.Never)) && checkTypeRelatedTo(returnType, target, relation, /*errorNode*/ undefined); - })) { - const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; - checkTypeAssignableTo(source, target, node, headMessage, containingMessageChain, resultObj); - const diagnostic = resultObj.errors![resultObj.errors!.length - 1]; - addRelatedInfo(diagnostic, createDiagnosticForNode( - node, - signatures === constructSignatures ? Diagnostics.Did_you_mean_to_use_new_with_this_expression : Diagnostics.Did_you_mean_to_call_this_expression - )); - return true; - } - } + function elaborateArrowFunction( + node: ArrowFunction, + source: Type, + target: Type, + relation: ESMap, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ): boolean { + // Don't elaborate blocks + if (isBlock(node.body)) { return false; } - - function elaborateArrowFunction( - node: ArrowFunction, - source: Type, - target: Type, - relation: ESMap, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ): boolean { - // Don't elaborate blocks - if (isBlock(node.body)) { - return false; - } - // Or functions with annotated parameter types - if (some(node.parameters, ts.hasType)) { - return false; - } - const sourceSig = getSingleCallSignature(source); - if (!sourceSig) { - return false; - } - const targetSignatures = getSignaturesOfType(target, SignatureKind.Call); - if (!length(targetSignatures)) { - return false; - } - const returnExpression = node.body; - const sourceReturn = getReturnTypeOfSignature(sourceSig); - const targetReturn = getUnionType(map(targetSignatures, getReturnTypeOfSignature)); - if (!checkTypeRelatedTo(sourceReturn, targetReturn, relation, /*errorNode*/ undefined)) { - const elaborated = returnExpression && elaborateError(returnExpression, sourceReturn, targetReturn, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); - if (elaborated) { - return elaborated; - } - const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; - checkTypeRelatedTo(sourceReturn, targetReturn, relation, returnExpression, /*message*/ undefined, containingMessageChain, resultObj); - if (resultObj.errors) { - if (target.symbol && length(target.symbol.declarations)) { - addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( - target.symbol.declarations![0], - Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature, - )); - } - if ((getFunctionFlags(node) & FunctionFlags.Async) === 0 - // exclude cases where source itself is promisy - this way we don't make a suggestion when relating - // an IPromise and a Promise that are slightly different - && !getTypeOfPropertyOfType(sourceReturn, "then" as __String) - && checkTypeRelatedTo(createPromiseType(sourceReturn), targetReturn, relation, /*errorNode*/ undefined) - ) { - addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( - node, - Diagnostics.Did_you_mean_to_mark_this_function_as_async - )); - } - return true; - } - } + // Or functions with annotated parameter types + if (some(node.parameters, ts.hasType)) { return false; } - - function getBestMatchIndexedAccessTypeOrUndefined(source: Type, target: Type, nameType: Type) { - const idx = getIndexedAccessTypeOrUndefined(target, nameType); - if (idx) { - return idx; - } - if (target.flags & TypeFlags.Union) { - const best = getBestMatchingType(source, target as UnionType); - if (best) { - return getIndexedAccessTypeOrUndefined(best, nameType); - } - } + const sourceSig = getSingleCallSignature(source); + if (!sourceSig) { + return false; } - - function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) { - next.contextualType = sourcePropType; - try { - return checkExpressionForMutableLocation(next, CheckMode.Contextual, sourcePropType); - } - finally { - next.contextualType = undefined; - } + const targetSignatures = getSignaturesOfType(target, SignatureKind.Call); + if (!length(targetSignatures)) { + return false; } - - type ElaborationIterator = IterableIterator<{ errorNode: Node, innerExpression: Expression | undefined, nameType: Type, errorMessage?: DiagnosticMessage | undefined }>; - /** - * For every element returned from the iterator, checks that element to issue an error on a property of that element's type - * If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError` - * Otherwise, we issue an error on _every_ element which fail the assignability check - */ - function elaborateElementwise( - iterator: ElaborationIterator, - source: Type, - target: Type, - relation: ESMap, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - // Assignability failure - check each prop individually, and if that fails, fall back on the bad error span - let reportedError = false; - for (let status = iterator.next(); !status.done; status = iterator.next()) { - const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; - let targetPropType = getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType); - if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables - let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); - if (!sourcePropType) continue; - const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined); - if (!checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { - const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); - reportedError = true; - if (!elaborated) { - // Issue error on the prop itself, since the prop couldn't elaborate the error - const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; - // Use the expression type, if available - const specificSource = next ? checkExpressionForMutableLocationWithContextualType(next, sourcePropType) : sourcePropType; - if (exactOptionalPropertyTypes && isExactOptionalPropertyMismatch(specificSource, targetPropType)) { - const diag = createDiagnosticForNode(prop, Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, typeToString(specificSource), typeToString(targetPropType)); - diagnostics.add(diag); - resultObj.errors = [diag]; - } - else { - const targetIsOptional = !!(propName && (getPropertyOfType(target, propName) || unknownSymbol).flags & SymbolFlags.Optional); - const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional); - targetPropType = removeMissingType(targetPropType, targetIsOptional); - sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional); - const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); - if (result && specificSource !== sourcePropType) { - // If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType - checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); - } - } - if (resultObj.errors) { - const reportedDiag = resultObj.errors[resultObj.errors.length - 1]; - const propertyName = isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; - const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined; - - let issuedElaboration = false; - if (!targetProp) { - const indexInfo = getApplicableIndexInfo(target, nameType); - if (indexInfo && indexInfo.declaration && !getSourceFileOfNode(indexInfo.declaration).hasNoDefaultLib) { - issuedElaboration = true; - addRelatedInfo(reportedDiag, createDiagnosticForNode(indexInfo.declaration, Diagnostics.The_expected_type_comes_from_this_index_signature)); - } - } - - if (!issuedElaboration && (targetProp && length(targetProp.declarations) || target.symbol && length(target.symbol.declarations))) { - const targetNode = targetProp && length(targetProp.declarations) ? targetProp.declarations![0] : target.symbol.declarations![0]; - if (!getSourceFileOfNode(targetNode).hasNoDefaultLib) { - addRelatedInfo(reportedDiag, createDiagnosticForNode( - targetNode, - Diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1, - propertyName && !(nameType.flags & TypeFlags.UniqueESSymbol) ? unescapeLeadingUnderscores(propertyName) : typeToString(nameType), - typeToString(target) - )); - } - } - } - } + const returnExpression = node.body; + const sourceReturn = getReturnTypeOfSignature(sourceSig); + const targetReturn = getUnionType(map(targetSignatures, getReturnTypeOfSignature)); + if (!checkTypeRelatedTo(sourceReturn, targetReturn, relation, /*errorNode*/ undefined)) { + const elaborated = returnExpression && elaborateError(returnExpression, sourceReturn, targetReturn, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); + if (elaborated) { + return elaborated; + } + const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; + checkTypeRelatedTo(sourceReturn, targetReturn, relation, returnExpression, /*message*/ undefined, containingMessageChain, resultObj); + if (resultObj.errors) { + if (target.symbol && length(target.symbol.declarations)) { + addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( + target.symbol.declarations![0], + Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature, + )); + } + if ((getFunctionFlags(node) & FunctionFlags.Async) === 0 + // exclude cases where source itself is promisy - this way we don't make a suggestion when relating + // an IPromise and a Promise that are slightly different + && !getTypeOfPropertyOfType(sourceReturn, "then" as __String) + && checkTypeRelatedTo(createPromiseType(sourceReturn), targetReturn, relation, /*errorNode*/ undefined) + ) { + addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( + node, + Diagnostics.Did_you_mean_to_mark_this_function_as_async + )); } + return true; } - return reportedError; } + return false; + } - function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator { - if (!length(node.properties)) return; - for (const prop of node.properties) { - if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue; - yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) }; - } + function getBestMatchIndexedAccessTypeOrUndefined(source: Type, target: Type, nameType: Type) { + const idx = getIndexedAccessTypeOrUndefined(target, nameType); + if (idx) { + return idx; } - - function *generateJsxChildren(node: JsxElement, getInvalidTextDiagnostic: () => DiagnosticMessage): ElaborationIterator { - if (!length(node.children)) return; - let memberOffset = 0; - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i]; - const nameType = getNumberLiteralType(i - memberOffset); - const elem = getElaborationElementForJsxChild(child, nameType, getInvalidTextDiagnostic); - if (elem) { - yield elem; - } - else { - memberOffset++; - } + if (target.flags & TypeFlags.Union) { + const best = getBestMatchingType(source, target as UnionType); + if (best) { + return getIndexedAccessTypeOrUndefined(best, nameType); } } + } - function getElaborationElementForJsxChild(child: JsxChild, nameType: LiteralType, getInvalidTextDiagnostic: () => DiagnosticMessage) { - switch (child.kind) { - case SyntaxKind.JsxExpression: - // child is of the type of the expression - return { errorNode: child, innerExpression: child.expression, nameType }; - case SyntaxKind.JsxText: - if (child.containsOnlyTriviaWhiteSpaces) { - break; // Whitespace only jsx text isn't real jsx text - } - // child is a string - return { errorNode: child, innerExpression: undefined, nameType, errorMessage: getInvalidTextDiagnostic() }; - case SyntaxKind.JsxElement: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxFragment: - // child is of type JSX.Element - return { errorNode: child, innerExpression: child, nameType }; - default: - return Debug.assertNever(child, "Found invalid jsx child"); - } + function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) { + next.contextualType = sourcePropType; + try { + return checkExpressionForMutableLocation(next, CheckMode.Contextual, sourcePropType); } + finally { + next.contextualType = undefined; + } + } - function elaborateJsxComponents( - node: JsxAttributes, - source: Type, - target: Type, - relation: ESMap, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation, containingMessageChain, errorOutputContainer); - let invalidTextDiagnostic: DiagnosticMessage | undefined; - if (isJsxOpeningElement(node.parent) && isJsxElement(node.parent.parent)) { - const containingElement = node.parent.parent; - const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); - const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); - const childrenNameType = getStringLiteralType(childrenPropName); - const childrenTargetType = getIndexedAccessType(target, childrenNameType); - const validChildren = getSemanticJsxChildren(containingElement.children); - if (!length(validChildren)) { - return result; - } - const moreThanOneRealChildren = length(validChildren) > 1; - const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); - const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); - if (moreThanOneRealChildren) { - if (arrayLikeTargetParts !== neverType) { - const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal)); - const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic); - result = elaborateElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result; - } - else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { - // arity mismatch - result = true; - const diag = error( - containingElement.openingElement.tagName, - Diagnostics.This_JSX_tag_s_0_prop_expects_a_single_child_of_type_1_but_multiple_children_were_provided, - childrenPropName, - typeToString(childrenTargetType) - ); - if (errorOutputContainer && errorOutputContainer.skipLogging) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + type ElaborationIterator = IterableIterator<{ errorNode: Node, innerExpression: Expression | undefined, nameType: Type, errorMessage?: DiagnosticMessage | undefined }>; + /** + * For every element returned from the iterator, checks that element to issue an error on a property of that element's type + * If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError` + * Otherwise, we issue an error on _every_ element which fail the assignability check + */ + function elaborateElementwise( + iterator: ElaborationIterator, + source: Type, + target: Type, + relation: ESMap, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ) { + // Assignability failure - check each prop individually, and if that fails, fall back on the bad error span + let reportedError = false; + for (let status = iterator.next(); !status.done; status = iterator.next()) { + const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; + let targetPropType = getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType); + if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables + let sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); + if (!sourcePropType) continue; + const propName = getPropertyNameFromIndex(nameType, /*accessNode*/ undefined); + if (!checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { + const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); + reportedError = true; + if (!elaborated) { + // Issue error on the prop itself, since the prop couldn't elaborate the error + const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; + // Use the expression type, if available + const specificSource = next ? checkExpressionForMutableLocationWithContextualType(next, sourcePropType) : sourcePropType; + if (exactOptionalPropertyTypes && isExactOptionalPropertyMismatch(specificSource, targetPropType)) { + const diag = createDiagnosticForNode(prop, Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, typeToString(specificSource), typeToString(targetPropType)); + diagnostics.add(diag); + resultObj.errors = [diag]; + } + else { + const targetIsOptional = !!(propName && (getPropertyOfType(target, propName) || unknownSymbol).flags & SymbolFlags.Optional); + const sourceIsOptional = !!(propName && (getPropertyOfType(source, propName) || unknownSymbol).flags & SymbolFlags.Optional); + targetPropType = removeMissingType(targetPropType, targetIsOptional); + sourcePropType = removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional); + const result = checkTypeRelatedTo(specificSource, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); + if (result && specificSource !== sourcePropType) { + // If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType + checkTypeRelatedTo(sourcePropType, targetPropType, relation, prop, errorMessage, containingMessageChain, resultObj); } } - } - else { - if (nonArrayLikeTargetParts !== neverType) { - const child = validChildren[0]; - const elem = getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic); - if (elem) { - result = elaborateElementwise( - (function*() { yield elem; })(), - source, - target, - relation, - containingMessageChain, - errorOutputContainer - ) || result; - } - } - else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { - // arity mismatch - result = true; - const diag = error( - containingElement.openingElement.tagName, - Diagnostics.This_JSX_tag_s_0_prop_expects_type_1_which_requires_multiple_children_but_only_a_single_child_was_provided, - childrenPropName, - typeToString(childrenTargetType) - ); - if (errorOutputContainer && errorOutputContainer.skipLogging) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + if (resultObj.errors) { + const reportedDiag = resultObj.errors[resultObj.errors.length - 1]; + const propertyName = isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined; + const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined; + + let issuedElaboration = false; + if (!targetProp) { + const indexInfo = getApplicableIndexInfo(target, nameType); + if (indexInfo && indexInfo.declaration && !getSourceFileOfNode(indexInfo.declaration).hasNoDefaultLib) { + issuedElaboration = true; + addRelatedInfo(reportedDiag, createDiagnosticForNode(indexInfo.declaration, Diagnostics.The_expected_type_comes_from_this_index_signature)); + } + } + + if (!issuedElaboration && (targetProp && length(targetProp.declarations) || target.symbol && length(target.symbol.declarations))) { + const targetNode = targetProp && length(targetProp.declarations) ? targetProp.declarations![0] : target.symbol.declarations![0]; + if (!getSourceFileOfNode(targetNode).hasNoDefaultLib) { + addRelatedInfo(reportedDiag, createDiagnosticForNode( + targetNode, + Diagnostics.The_expected_type_comes_from_property_0_which_is_declared_here_on_type_1, + propertyName && !(nameType.flags & TypeFlags.UniqueESSymbol) ? unescapeLeadingUnderscores(propertyName) : typeToString(nameType), + typeToString(target) + )); + } } } } } - return result; + } + return reportedError; + } - function getInvalidTextualChildDiagnostic() { - if (!invalidTextDiagnostic) { - const tagNameText = getTextOfNode(node.parent.tagName); - const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); - const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); - const childrenTargetType = getIndexedAccessType(target, getStringLiteralType(childrenPropName)); - const diagnostic = Diagnostics._0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2; - invalidTextDiagnostic = { ...diagnostic, key: "!!ALREADY FORMATTED!!", message: formatMessage(/*_dummy*/ undefined, diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) }; - } - return invalidTextDiagnostic; - } + function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator { + if (!length(node.properties)) return; + for (const prop of node.properties) { + if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue; + yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) }; } + } - function *generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator { - const len = length(node.elements); - if (!len) return; - for (let i = 0; i < len; i++) { - // Skip elements which do not exist in the target - a length error on the tuple overall is likely better than an error on a mismatched index signature - if (isTupleLikeType(target) && !getPropertyOfType(target, ("" + i) as __String)) continue; - const elem = node.elements[i]; - if (isOmittedExpression(elem)) continue; - const nameType = getNumberLiteralType(i); - yield { errorNode: elem, innerExpression: elem, nameType }; - } - } - - function elaborateArrayLiteral( - node: ArrayLiteralExpression, - source: Type, - target: Type, - relation: ESMap, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - if (target.flags & (TypeFlags.Primitive | TypeFlags.Never)) return false; - if (isTupleLikeType(source)) { - return elaborateElementwise(generateLimitedTupleElements(node, target), source, target, relation, containingMessageChain, errorOutputContainer); - } - // recreate a tuple from the elements, if possible - // Since we're re-doing the expression type, we need to reapply the contextual type - const oldContext = node.contextualType; - node.contextualType = target; - try { - const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); - node.contextualType = oldContext; - if (isTupleLikeType(tupleizedType)) { - return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); - } - return false; + function *generateJsxChildren(node: JsxElement, getInvalidTextDiagnostic: () => DiagnosticMessage): ElaborationIterator { + if (!length(node.children)) return; + let memberOffset = 0; + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const nameType = getNumberLiteralType(i - memberOffset); + const elem = getElaborationElementForJsxChild(child, nameType, getInvalidTextDiagnostic); + if (elem) { + yield elem; } - finally { - node.contextualType = oldContext; + else { + memberOffset++; } } + } - function *generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator { - if (!length(node.properties)) return; - for (const prop of node.properties) { - if (isSpreadAssignment(prop)) continue; - const type = getLiteralTypeFromProperty(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique); - if (!type || (type.flags & TypeFlags.Never)) { - continue; + function getElaborationElementForJsxChild(child: JsxChild, nameType: LiteralType, getInvalidTextDiagnostic: () => DiagnosticMessage) { + switch (child.kind) { + case SyntaxKind.JsxExpression: + // child is of the type of the expression + return { errorNode: child, innerExpression: child.expression, nameType }; + case SyntaxKind.JsxText: + if (child.containsOnlyTriviaWhiteSpaces) { + break; // Whitespace only jsx text isn't real jsx text + } + // child is a string + return { errorNode: child, innerExpression: undefined, nameType, errorMessage: getInvalidTextDiagnostic() }; + case SyntaxKind.JsxElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxFragment: + // child is of type JSX.Element + return { errorNode: child, innerExpression: child, nameType }; + default: + return Debug.assertNever(child, "Found invalid jsx child"); + } + } + + function elaborateJsxComponents( + node: JsxAttributes, + source: Type, + target: Type, + relation: ESMap, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ) { + let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation, containingMessageChain, errorOutputContainer); + let invalidTextDiagnostic: DiagnosticMessage | undefined; + if (isJsxOpeningElement(node.parent) && isJsxElement(node.parent.parent)) { + const containingElement = node.parent.parent; + const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); + const childrenNameType = getStringLiteralType(childrenPropName); + const childrenTargetType = getIndexedAccessType(target, childrenNameType); + const validChildren = getSemanticJsxChildren(containingElement.children); + if (!length(validChildren)) { + return result; + } + const moreThanOneRealChildren = length(validChildren) > 1; + const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); + const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); + if (moreThanOneRealChildren) { + if (arrayLikeTargetParts !== neverType) { + const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal)); + const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic); + result = elaborateElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result; + } + else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { + // arity mismatch + result = true; + const diag = error( + containingElement.openingElement.tagName, + Diagnostics.This_JSX_tag_s_0_prop_expects_a_single_child_of_type_1_but_multiple_children_were_provided, + childrenPropName, + typeToString(childrenTargetType) + ); + if (errorOutputContainer && errorOutputContainer.skipLogging) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } } - switch (prop.kind) { - case SyntaxKind.SetAccessor: - case SyntaxKind.GetAccessor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.ShorthandPropertyAssignment: - yield { errorNode: prop.name, innerExpression: undefined, nameType: type }; - break; - case SyntaxKind.PropertyAssignment: - yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined }; - break; - default: - Debug.assertNever(prop); + } + else { + if (nonArrayLikeTargetParts !== neverType) { + const child = validChildren[0]; + const elem = getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic); + if (elem) { + result = elaborateElementwise( + (function*() { yield elem; })(), + source, + target, + relation, + containingMessageChain, + errorOutputContainer + ) || result; + } + } + else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { + // arity mismatch + result = true; + const diag = error( + containingElement.openingElement.tagName, + Diagnostics.This_JSX_tag_s_0_prop_expects_type_1_which_requires_multiple_children_but_only_a_single_child_was_provided, + childrenPropName, + typeToString(childrenTargetType) + ); + if (errorOutputContainer && errorOutputContainer.skipLogging) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } } } } + return result; - function elaborateObjectLiteral( - node: ObjectLiteralExpression, - source: Type, - target: Type, - relation: ESMap, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined - ) { - if (target.flags & (TypeFlags.Primitive | TypeFlags.Never)) return false; - return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation, containingMessageChain, errorOutputContainer); + function getInvalidTextualChildDiagnostic() { + if (!invalidTextDiagnostic) { + const tagNameText = getTextOfNode(node.parent.tagName); + const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); + const childrenTargetType = getIndexedAccessType(target, getStringLiteralType(childrenPropName)); + const diagnostic = Diagnostics._0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2; + invalidTextDiagnostic = { ...diagnostic, key: "!!ALREADY FORMATTED!!", message: formatMessage(/*_dummy*/ undefined, diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) }; + } + return invalidTextDiagnostic; } + } - /** - * This is *not* a bi-directional relationship. - * If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'. - */ - function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { - return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); + function *generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator { + const len = length(node.elements); + if (!len) return; + for (let i = 0; i < len; i++) { + // Skip elements which do not exist in the target - a length error on the tuple overall is likely better than an error on a mismatched index signature + if (isTupleLikeType(target) && !getPropertyOfType(target, ("" + i) as __String)) continue; + const elem = node.elements[i]; + if (isOmittedExpression(elem)) continue; + const nameType = getNumberLiteralType(i); + yield { errorNode: elem, innerExpression: elem, nameType }; } + } - function isSignatureAssignableTo(source: Signature, - target: Signature, - ignoreReturnTypes: boolean): boolean { - return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : 0, /*reportErrors*/ false, - /*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; + function elaborateArrayLiteral( + node: ArrayLiteralExpression, + source: Type, + target: Type, + relation: ESMap, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ) { + if (target.flags & (TypeFlags.Primitive | TypeFlags.Never)) return false; + if (isTupleLikeType(source)) { + return elaborateElementwise(generateLimitedTupleElements(node, target), source, target, relation, containingMessageChain, errorOutputContainer); + } + // recreate a tuple from the elements, if possible + // Since we're re-doing the expression type, we need to reapply the contextual type + const oldContext = node.contextualType; + node.contextualType = target; + try { + const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); + node.contextualType = oldContext; + if (isTupleLikeType(tupleizedType)) { + return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); + } + return false; } - - type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void; - - /** - * Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any` - */ - function isAnySignature(s: Signature) { - return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && - signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) && - isTypeAny(getReturnTypeOfSignature(s)); + finally { + node.contextualType = oldContext; } + } - /** - * See signatureRelatedTo, compareSignaturesIdentical - */ - function compareSignaturesRelated(source: Signature, - target: Signature, - checkMode: SignatureCheckMode, - reportErrors: boolean, - errorReporter: ErrorReporter | undefined, - incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined, - compareTypes: TypeComparer, - reportUnreliableMarkers: TypeMapper | undefined): Ternary { - // TODO (drosen): De-duplicate code between related functions. - if (source === target) { - return Ternary.True; + function *generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator { + if (!length(node.properties)) return; + for (const prop of node.properties) { + if (isSpreadAssignment(prop)) continue; + const type = getLiteralTypeFromProperty(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique); + if (!type || (type.flags & TypeFlags.Never)) { + continue; } - - if (isAnySignature(target)) { - return Ternary.True; + switch (prop.kind) { + case SyntaxKind.SetAccessor: + case SyntaxKind.GetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.ShorthandPropertyAssignment: + yield { errorNode: prop.name, innerExpression: undefined, nameType: type }; + break; + case SyntaxKind.PropertyAssignment: + yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined }; + break; + default: + Debug.assertNever(prop); } + } + } - const targetCount = getParameterCount(target); - const sourceHasMoreParameters = !hasEffectiveRestParameter(target) && - (checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount); - if (sourceHasMoreParameters) { - return Ternary.False; - } + function elaborateObjectLiteral( + node: ObjectLiteralExpression, + source: Type, + target: Type, + relation: ESMap, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined + ) { + if (target.flags & (TypeFlags.Primitive | TypeFlags.Never)) return false; + return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation, containingMessageChain, errorOutputContainer); + } - if (source.typeParameters && source.typeParameters !== target.typeParameters) { - target = getCanonicalSignature(target); - source = instantiateSignatureInContextOf(source, target, /*inferenceContext*/ undefined, compareTypes); - } + /** + * This is *not* a bi-directional relationship. + * If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'. + */ + function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { + return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); + } - const sourceCount = getParameterCount(source); - const sourceRestType = getNonArrayRestType(source); - const targetRestType = getNonArrayRestType(target); - if (sourceRestType || targetRestType) { - void instantiateType(sourceRestType || targetRestType, reportUnreliableMarkers); - } + function isSignatureAssignableTo(source: Signature, + target: Signature, + ignoreReturnTypes: boolean): boolean { + return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : 0, /*reportErrors*/ false, + /*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; + } - const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; - const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && - kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor; - let result = Ternary.True; + type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void; - const sourceThisType = getThisTypeOfSignature(source); - if (sourceThisType && sourceThisType !== voidType) { - const targetThisType = getThisTypeOfSignature(target); - if (targetThisType) { - // void sources are assignable to anything. - const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false) - || compareTypes(targetThisType, sourceThisType, reportErrors); - if (!related) { - if (reportErrors) { - errorReporter!(Diagnostics.The_this_types_of_each_signature_are_incompatible); - } - return Ternary.False; - } - result &= related; - } - } + /** + * Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any` + */ + function isAnySignature(s: Signature) { + return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && + signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) && + isTypeAny(getReturnTypeOfSignature(s)); + } - const paramCount = sourceRestType || targetRestType ? Math.min(sourceCount, targetCount) : Math.max(sourceCount, targetCount); - const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1; - - for (let i = 0; i < paramCount; i++) { - const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i); - const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i); - if (sourceType && targetType) { - // In order to ensure that any generic type Foo is at least co-variant with respect to T no matter - // how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions, - // they naturally relate only contra-variantly). However, if the source and target parameters both have - // function types with a single call signature, we know we are relating two callback parameters. In - // that case it is sufficient to only relate the parameters of the signatures co-variantly because, - // similar to return values, callback parameters are output positions. This means that a Promise, - // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) - // with respect to T. - const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); - const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType)); - const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && - (getTypeFacts(sourceType) & TypeFacts.IsUndefinedOrNull) === (getTypeFacts(targetType) & TypeFacts.IsUndefinedOrNull); - let related = callbacks ? - compareSignaturesRelated(targetSig, sourceSig, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : - !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); - // With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void - if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) { - related = Ternary.False; - } - if (!related) { - if (reportErrors) { - errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, - unescapeLeadingUnderscores(getParameterNameAtPosition(source, i)), - unescapeLeadingUnderscores(getParameterNameAtPosition(target, i))); - } - return Ternary.False; + /** + * See signatureRelatedTo, compareSignaturesIdentical + */ + function compareSignaturesRelated(source: Signature, + target: Signature, + checkMode: SignatureCheckMode, + reportErrors: boolean, + errorReporter: ErrorReporter | undefined, + incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined, + compareTypes: TypeComparer, + reportUnreliableMarkers: TypeMapper | undefined): Ternary { + // TODO (drosen): De-duplicate code between related functions. + if (source === target) { + return Ternary.True; + } + + if (isAnySignature(target)) { + return Ternary.True; + } + + const targetCount = getParameterCount(target); + const sourceHasMoreParameters = !hasEffectiveRestParameter(target) && + (checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount); + if (sourceHasMoreParameters) { + return Ternary.False; + } + + if (source.typeParameters && source.typeParameters !== target.typeParameters) { + target = getCanonicalSignature(target); + source = instantiateSignatureInContextOf(source, target, /*inferenceContext*/ undefined, compareTypes); + } + + const sourceCount = getParameterCount(source); + const sourceRestType = getNonArrayRestType(source); + const targetRestType = getNonArrayRestType(target); + if (sourceRestType || targetRestType) { + void instantiateType(sourceRestType || targetRestType, reportUnreliableMarkers); + } + + const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; + const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && + kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor; + let result = Ternary.True; + + const sourceThisType = getThisTypeOfSignature(source); + if (sourceThisType && sourceThisType !== voidType) { + const targetThisType = getThisTypeOfSignature(target); + if (targetThisType) { + // void sources are assignable to anything. + const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false) + || compareTypes(targetThisType, sourceThisType, reportErrors); + if (!related) { + if (reportErrors) { + errorReporter!(Diagnostics.The_this_types_of_each_signature_are_incompatible); } - result &= related; + return Ternary.False; } + result &= related; } + } - if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) { - // If a signature resolution is already in-flight, skip issuing a circularity error - // here and just use the `any` type directly - const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType - : target.declaration && isJSConstructor(target.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(target.declaration.symbol)) - : getReturnTypeOfSignature(target); - if (targetReturnType === voidType || targetReturnType === anyType) { - return result; - } - const sourceReturnType = isResolvingReturnTypeOfSignature(source) ? anyType - : source.declaration && isJSConstructor(source.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(source.declaration.symbol)) - : getReturnTypeOfSignature(source); - - // The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions - const targetTypePredicate = getTypePredicateOfSignature(target); - if (targetTypePredicate) { - const sourceTypePredicate = getTypePredicateOfSignature(source); - if (sourceTypePredicate) { - result &= compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes); - } - else if (isIdentifierTypePredicate(targetTypePredicate)) { - if (reportErrors) { - errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source)); - } - return Ternary.False; - } + const paramCount = sourceRestType || targetRestType ? Math.min(sourceCount, targetCount) : Math.max(sourceCount, targetCount); + const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1; + + for (let i = 0; i < paramCount; i++) { + const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i); + const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i); + if (sourceType && targetType) { + // In order to ensure that any generic type Foo is at least co-variant with respect to T no matter + // how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions, + // they naturally relate only contra-variantly). However, if the source and target parameters both have + // function types with a single call signature, we know we are relating two callback parameters. In + // that case it is sufficient to only relate the parameters of the signatures co-variantly because, + // similar to return values, callback parameters are output positions. This means that a Promise, + // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) + // with respect to T. + const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); + const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType)); + const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && + (getTypeFacts(sourceType) & TypeFacts.IsUndefinedOrNull) === (getTypeFacts(targetType) & TypeFacts.IsUndefinedOrNull); + let related = callbacks ? + compareSignaturesRelated(targetSig, sourceSig, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : + !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); + // With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void + if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) { + related = Ternary.False; } - else { - // When relating callback signatures, we still need to relate return types bi-variantly as otherwise - // the containing type wouldn't be co-variant. For example, interface Foo { add(cb: () => T): void } - // wouldn't be co-variant for T without this rule. - result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) || - compareTypes(sourceReturnType, targetReturnType, reportErrors); - if (!result && reportErrors && incompatibleErrorReporter) { - incompatibleErrorReporter(sourceReturnType, targetReturnType); + if (!related) { + if (reportErrors) { + errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, + unescapeLeadingUnderscores(getParameterNameAtPosition(source, i)), + unescapeLeadingUnderscores(getParameterNameAtPosition(target, i))); } + return Ternary.False; } - + result &= related; } - - return result; } - function compareTypePredicateRelatedTo( - source: TypePredicate, - target: TypePredicate, - reportErrors: boolean, - errorReporter: ErrorReporter | undefined, - compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary): Ternary { - if (source.kind !== target.kind) { - if (reportErrors) { - errorReporter!(Diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard); - errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); - } - return Ternary.False; + if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) { + // If a signature resolution is already in-flight, skip issuing a circularity error + // here and just use the `any` type directly + const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType + : target.declaration && isJSConstructor(target.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(target.declaration.symbol)) + : getReturnTypeOfSignature(target); + if (targetReturnType === voidType || targetReturnType === anyType) { + return result; } + const sourceReturnType = isResolvingReturnTypeOfSignature(source) ? anyType + : source.declaration && isJSConstructor(source.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(source.declaration.symbol)) + : getReturnTypeOfSignature(source); - if (source.kind === TypePredicateKind.Identifier || source.kind === TypePredicateKind.AssertsIdentifier) { - if (source.parameterIndex !== (target as IdentifierTypePredicate).parameterIndex) { + // The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions + const targetTypePredicate = getTypePredicateOfSignature(target); + if (targetTypePredicate) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + if (sourceTypePredicate) { + result &= compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes); + } + else if (isIdentifierTypePredicate(targetTypePredicate)) { if (reportErrors) { - errorReporter!(Diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, (target as IdentifierTypePredicate).parameterName); - errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); + errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source)); } return Ternary.False; } } - - const related = source.type === target.type ? Ternary.True : - source.type && target.type ? compareTypes(source.type, target.type, reportErrors) : - Ternary.False; - if (related === Ternary.False && reportErrors) { - errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); + else { + // When relating callback signatures, we still need to relate return types bi-variantly as otherwise + // the containing type wouldn't be co-variant. For example, interface Foo { add(cb: () => T): void } + // wouldn't be co-variant for T without this rule. + result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) || + compareTypes(sourceReturnType, targetReturnType, reportErrors); + if (!result && reportErrors && incompatibleErrorReporter) { + incompatibleErrorReporter(sourceReturnType, targetReturnType); + } } - return related; - } - function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean { - const erasedSource = getErasedSignature(implementation); - const erasedTarget = getErasedSignature(overload); + } - // First see if the return types are compatible in either direction. - const sourceReturnType = getReturnTypeOfSignature(erasedSource); - const targetReturnType = getReturnTypeOfSignature(erasedTarget); - if (targetReturnType === voidType - || isTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation) - || isTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation)) { + return result; + } - return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true); + function compareTypePredicateRelatedTo( + source: TypePredicate, + target: TypePredicate, + reportErrors: boolean, + errorReporter: ErrorReporter | undefined, + compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary): Ternary { + if (source.kind !== target.kind) { + if (reportErrors) { + errorReporter!(Diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard); + errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } - - return false; + return Ternary.False; } - function isEmptyResolvedType(t: ResolvedType) { - return t !== anyFunctionType && - t.properties.length === 0 && - t.callSignatures.length === 0 && - t.constructSignatures.length === 0 && - t.indexInfos.length === 0; + if (source.kind === TypePredicateKind.Identifier || source.kind === TypePredicateKind.AssertsIdentifier) { + if (source.parameterIndex !== (target as IdentifierTypePredicate).parameterIndex) { + if (reportErrors) { + errorReporter!(Diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, (target as IdentifierTypePredicate).parameterName); + errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); + } + return Ternary.False; + } } - function isEmptyObjectType(type: Type): boolean { - return type.flags & TypeFlags.Object ? !isGenericMappedType(type) && isEmptyResolvedType(resolveStructuredTypeMembers(type as ObjectType)) : - type.flags & TypeFlags.NonPrimitive ? true : - type.flags & TypeFlags.Union ? some((type as UnionType).types, isEmptyObjectType) : - type.flags & TypeFlags.Intersection ? every((type as UnionType).types, isEmptyObjectType) : - false; + const related = source.type === target.type ? Ternary.True : + source.type && target.type ? compareTypes(source.type, target.type, reportErrors) : + Ternary.False; + if (related === Ternary.False && reportErrors) { + errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } + return related; + } - function isEmptyAnonymousObjectType(type: Type) { - return !!(getObjectFlags(type) & ObjectFlags.Anonymous && ( - (type as ResolvedType).members && isEmptyResolvedType(type as ResolvedType) || - type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && getMembersOfSymbol(type.symbol).size === 0)); + function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean { + const erasedSource = getErasedSignature(implementation); + const erasedTarget = getErasedSignature(overload); + + // First see if the return types are compatible in either direction. + const sourceReturnType = getReturnTypeOfSignature(erasedSource); + const targetReturnType = getReturnTypeOfSignature(erasedTarget); + if (targetReturnType === voidType + || isTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation) + || isTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation)) { + + return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true); } - function isUnknownLikeUnionType(type: Type) { - if (strictNullChecks && type.flags & TypeFlags.Union) { - if (!((type as UnionType).objectFlags & ObjectFlags.IsUnknownLikeUnionComputed)) { - const types = (type as UnionType).types; - (type as UnionType).objectFlags |= ObjectFlags.IsUnknownLikeUnionComputed | (types.length >= 3 && types[0].flags & TypeFlags.Undefined && - types[1].flags & TypeFlags.Null && some(types, isEmptyAnonymousObjectType) ? ObjectFlags.IsUnknownLikeUnion : 0); - } - return !!((type as UnionType).objectFlags & ObjectFlags.IsUnknownLikeUnion); + return false; + } + + function isEmptyResolvedType(t: ResolvedType) { + return t !== anyFunctionType && + t.properties.length === 0 && + t.callSignatures.length === 0 && + t.constructSignatures.length === 0 && + t.indexInfos.length === 0; + } + + function isEmptyObjectType(type: Type): boolean { + return type.flags & TypeFlags.Object ? !isGenericMappedType(type) && isEmptyResolvedType(resolveStructuredTypeMembers(type as ObjectType)) : + type.flags & TypeFlags.NonPrimitive ? true : + type.flags & TypeFlags.Union ? some((type as UnionType).types, isEmptyObjectType) : + type.flags & TypeFlags.Intersection ? every((type as UnionType).types, isEmptyObjectType) : + false; + } + + function isEmptyAnonymousObjectType(type: Type) { + return !!(getObjectFlags(type) & ObjectFlags.Anonymous && ( + (type as ResolvedType).members && isEmptyResolvedType(type as ResolvedType) || + type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && getMembersOfSymbol(type.symbol).size === 0)); + } + + function isUnknownLikeUnionType(type: Type) { + if (strictNullChecks && type.flags & TypeFlags.Union) { + if (!((type as UnionType).objectFlags & ObjectFlags.IsUnknownLikeUnionComputed)) { + const types = (type as UnionType).types; + (type as UnionType).objectFlags |= ObjectFlags.IsUnknownLikeUnionComputed | (types.length >= 3 && types[0].flags & TypeFlags.Undefined && + types[1].flags & TypeFlags.Null && some(types, isEmptyAnonymousObjectType) ? ObjectFlags.IsUnknownLikeUnion : 0); } - return false; + return !!((type as UnionType).objectFlags & ObjectFlags.IsUnknownLikeUnion); } + return false; + } - function containsUndefinedType(type: Type) { - return !!((type.flags & TypeFlags.Union ? (type as UnionType).types[0] : type).flags & TypeFlags.Undefined); - } + function containsUndefinedType(type: Type) { + return !!((type.flags & TypeFlags.Union ? (type as UnionType).types[0] : type).flags & TypeFlags.Undefined); + } - function isStringIndexSignatureOnlyType(type: Type): boolean { - return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, stringType) || - type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isStringIndexSignatureOnlyType) || - false; - } + function isStringIndexSignatureOnlyType(type: Type): boolean { + return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, stringType) || + type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isStringIndexSignatureOnlyType) || + false; + } - function isEnumTypeRelatedTo(source: Symbol, target: Symbol, errorReporter?: ErrorReporter) { - const sourceSymbol = source.flags & SymbolFlags.EnumMember ? getParentOfSymbol(source)! : source; - const targetSymbol = target.flags & SymbolFlags.EnumMember ? getParentOfSymbol(target)! : target; - if (sourceSymbol === targetSymbol) { - return true; - } - if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) { - return false; - } - const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol); - const entry = enumRelation.get(id); - if (entry !== undefined && !(!(entry & RelationComparisonResult.Reported) && entry & RelationComparisonResult.Failed && errorReporter)) { - return !!(entry & RelationComparisonResult.Succeeded); - } - const targetEnumType = getTypeOfSymbol(targetSymbol); - for (const property of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) { - if (property.flags & SymbolFlags.EnumMember) { - const targetProperty = getPropertyOfType(targetEnumType, property.escapedName); - if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) { - if (errorReporter) { - errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property), - typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); - enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); - } - else { - enumRelation.set(id, RelationComparisonResult.Failed); - } - return false; + function isEnumTypeRelatedTo(source: Symbol, target: Symbol, errorReporter?: ErrorReporter) { + const sourceSymbol = source.flags & SymbolFlags.EnumMember ? getParentOfSymbol(source)! : source; + const targetSymbol = target.flags & SymbolFlags.EnumMember ? getParentOfSymbol(target)! : target; + if (sourceSymbol === targetSymbol) { + return true; + } + if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) { + return false; + } + const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol); + const entry = enumRelation.get(id); + if (entry !== undefined && !(!(entry & RelationComparisonResult.Reported) && entry & RelationComparisonResult.Failed && errorReporter)) { + return !!(entry & RelationComparisonResult.Succeeded); + } + const targetEnumType = getTypeOfSymbol(targetSymbol); + for (const property of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) { + if (property.flags & SymbolFlags.EnumMember) { + const targetProperty = getPropertyOfType(targetEnumType, property.escapedName); + if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) { + if (errorReporter) { + errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property), + typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + } + else { + enumRelation.set(id, RelationComparisonResult.Failed); } + return false; } } - enumRelation.set(id, RelationComparisonResult.Succeeded); - return true; } + enumRelation.set(id, RelationComparisonResult.Succeeded); + return true; + } - function isSimpleTypeRelatedTo(source: Type, target: Type, relation: ESMap, errorReporter?: ErrorReporter) { - const s = source.flags; - const t = target.flags; - if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType) return true; - if (t & TypeFlags.Never) return false; - if (s & TypeFlags.StringLike && t & TypeFlags.String) return true; - if (s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral && - t & TypeFlags.StringLiteral && !(t & TypeFlags.EnumLiteral) && - (source as StringLiteralType).value === (target as StringLiteralType).value) return true; - if (s & TypeFlags.NumberLike && t & TypeFlags.Number) return true; - if (s & TypeFlags.NumberLiteral && s & TypeFlags.EnumLiteral && - t & TypeFlags.NumberLiteral && !(t & TypeFlags.EnumLiteral) && - (source as NumberLiteralType).value === (target as NumberLiteralType).value) return true; - if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true; - if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true; - if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true; - if (s & TypeFlags.Enum && t & TypeFlags.Enum && source.symbol.escapedName === target.symbol.escapedName && + function isSimpleTypeRelatedTo(source: Type, target: Type, relation: ESMap, errorReporter?: ErrorReporter) { + const s = source.flags; + const t = target.flags; + if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType) return true; + if (t & TypeFlags.Never) return false; + if (s & TypeFlags.StringLike && t & TypeFlags.String) return true; + if (s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral && + t & TypeFlags.StringLiteral && !(t & TypeFlags.EnumLiteral) && + (source as StringLiteralType).value === (target as StringLiteralType).value) return true; + if (s & TypeFlags.NumberLike && t & TypeFlags.Number) return true; + if (s & TypeFlags.NumberLiteral && s & TypeFlags.EnumLiteral && + t & TypeFlags.NumberLiteral && !(t & TypeFlags.EnumLiteral) && + (source as NumberLiteralType).value === (target as NumberLiteralType).value) return true; + if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true; + if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true; + if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true; + if (s & TypeFlags.Enum && t & TypeFlags.Enum && source.symbol.escapedName === target.symbol.escapedName && + isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; + if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) { + if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; + if (s & TypeFlags.Literal && t & TypeFlags.Literal && (source as LiteralType).value === (target as LiteralType).value && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; - if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) { - if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; - if (s & TypeFlags.Literal && t & TypeFlags.Literal && (source as LiteralType).value === (target as LiteralType).value && - isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; - } - // In non-strictNullChecks mode, `undefined` and `null` are assignable to anything except `never`. - // Since unions and intersections may reduce to `never`, we exclude them here. - if (s & TypeFlags.Undefined && (!strictNullChecks && !(t & TypeFlags.UnionOrIntersection) || t & (TypeFlags.Undefined | TypeFlags.Void))) return true; - if (s & TypeFlags.Null && (!strictNullChecks && !(t & TypeFlags.UnionOrIntersection) || t & TypeFlags.Null)) return true; - if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive && !(relation === strictSubtypeRelation && isEmptyAnonymousObjectType(source) && !(getObjectFlags(source) & ObjectFlags.FreshLiteral))) return true; - if (relation === assignableRelation || relation === comparableRelation) { - if (s & TypeFlags.Any) return true; - // Type number or any numeric literal type is assignable to any numeric enum type or any - // numeric enum literal type. This rule exists for backwards compatibility reasons because - // bit-flag enum types sometimes look like literal enum types with numeric literal values. - if (s & (TypeFlags.Number | TypeFlags.NumberLiteral) && !(s & TypeFlags.EnumLiteral) && ( - t & TypeFlags.Enum || relation === assignableRelation && t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true; - // Anything is assignable to a union containing undefined, null, and {} - if (isUnknownLikeUnionType(target)) return true; - } - return false; } + // In non-strictNullChecks mode, `undefined` and `null` are assignable to anything except `never`. + // Since unions and intersections may reduce to `never`, we exclude them here. + if (s & TypeFlags.Undefined && (!strictNullChecks && !(t & TypeFlags.UnionOrIntersection) || t & (TypeFlags.Undefined | TypeFlags.Void))) return true; + if (s & TypeFlags.Null && (!strictNullChecks && !(t & TypeFlags.UnionOrIntersection) || t & TypeFlags.Null)) return true; + if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive && !(relation === strictSubtypeRelation && isEmptyAnonymousObjectType(source) && !(getObjectFlags(source) & ObjectFlags.FreshLiteral))) return true; + if (relation === assignableRelation || relation === comparableRelation) { + if (s & TypeFlags.Any) return true; + // Type number or any numeric literal type is assignable to any numeric enum type or any + // numeric enum literal type. This rule exists for backwards compatibility reasons because + // bit-flag enum types sometimes look like literal enum types with numeric literal values. + if (s & (TypeFlags.Number | TypeFlags.NumberLiteral) && !(s & TypeFlags.EnumLiteral) && ( + t & TypeFlags.Enum || relation === assignableRelation && t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true; + // Anything is assignable to a union containing undefined, null, and {} + if (isUnknownLikeUnionType(target)) return true; + } + return false; + } - function isTypeRelatedTo(source: Type, target: Type, relation: ESMap) { - if (isFreshLiteralType(source)) { - source = (source as FreshableType).regularType; - } - if (isFreshLiteralType(target)) { - target = (target as FreshableType).regularType; - } - if (source === target) { + function isTypeRelatedTo(source: Type, target: Type, relation: ESMap) { + if (isFreshLiteralType(source)) { + source = (source as FreshableType).regularType; + } + if (isFreshLiteralType(target)) { + target = (target as FreshableType).regularType; + } + if (source === target) { + return true; + } + if (relation !== identityRelation) { + if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation)) { return true; } - if (relation !== identityRelation) { - if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation)) { - return true; - } - } - else if (!((source.flags | target.flags) & (TypeFlags.UnionOrIntersection | TypeFlags.IndexedAccess | TypeFlags.Conditional | TypeFlags.Substitution))) { - // We have excluded types that may simplify to other forms, so types must have identical flags - if (source.flags !== target.flags) return false; - if (source.flags & TypeFlags.Singleton) return true; - } - if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { - const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation, /*ignoreConstraints*/ false)); - if (related !== undefined) { - return !!(related & RelationComparisonResult.Succeeded); - } - } - if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { - return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined); - } - return false; } - - function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) { - return getObjectFlags(source) & ObjectFlags.JsxAttributes && isHyphenatedJsxName(sourceProp.escapedName); + else if (!((source.flags | target.flags) & (TypeFlags.UnionOrIntersection | TypeFlags.IndexedAccess | TypeFlags.Conditional | TypeFlags.Substitution))) { + // We have excluded types that may simplify to other forms, so types must have identical flags + if (source.flags !== target.flags) return false; + if (source.flags & TypeFlags.Singleton) return true; } - - function getNormalizedType(type: Type, writing: boolean): Type { - while (true) { - const t = isFreshLiteralType(type) ? (type as FreshableType).regularType : - getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).node ? createTypeReference((type as TypeReference).target, getTypeArguments(type as TypeReference)) : getSingleBaseForNonAugmentingSubtype(type) || type : - type.flags & TypeFlags.UnionOrIntersection ? getNormalizedUnionOrIntersectionType(type as UnionOrIntersectionType, writing) : - type.flags & TypeFlags.Substitution ? writing ? (type as SubstitutionType).baseType : getSubstitutionIntersection(type as SubstitutionType) : - type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) : - type; - if (t === type) return t; - type = t; + if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { + const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation, /*ignoreConstraints*/ false)); + if (related !== undefined) { + return !!(related & RelationComparisonResult.Succeeded); } } + if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { + return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined); + } + return false; + } - function getNormalizedUnionOrIntersectionType(type: UnionOrIntersectionType, writing: boolean) { - const reduced = getReducedType(type); - if (reduced !== type) { - return reduced; - } - if (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isEmptyAnonymousObjectType)) { - const normalizedTypes = sameMap(type.types, t => getNormalizedType(t, writing)); - if (normalizedTypes !== type.types) { - return getIntersectionType(normalizedTypes); - } - } - return type; + function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) { + return getObjectFlags(source) & ObjectFlags.JsxAttributes && isHyphenatedJsxName(sourceProp.escapedName); + } + + function getNormalizedType(type: Type, writing: boolean): Type { + while (true) { + const t = isFreshLiteralType(type) ? (type as FreshableType).regularType : + getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).node ? createTypeReference((type as TypeReference).target, getTypeArguments(type as TypeReference)) : getSingleBaseForNonAugmentingSubtype(type) || type : + type.flags & TypeFlags.UnionOrIntersection ? getNormalizedUnionOrIntersectionType(type as UnionOrIntersectionType, writing) : + type.flags & TypeFlags.Substitution ? writing ? (type as SubstitutionType).baseType : getSubstitutionIntersection(type as SubstitutionType) : + type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) : + type; + if (t === type) return t; + type = t; } + } - /** - * Checks if 'source' is related to 'target' (e.g.: is a assignable to). - * @param source The left-hand-side of the relation. - * @param target The right-hand-side of the relation. - * @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'. - * Used as both to determine which checks are performed and as a cache of previously computed results. - * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used. - * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. - * @param containingMessageChain A chain of errors to prepend any new errors found. - * @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy. - */ - function checkTypeRelatedTo( - source: Type, - target: Type, - relation: ESMap, - errorNode: Node | undefined, - headMessage?: DiagnosticMessage, - containingMessageChain?: () => DiagnosticMessageChain | undefined, - errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean }, - ): boolean { - - let errorInfo: DiagnosticMessageChain | undefined; - let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined; - let maybeKeys: string[]; - let sourceStack: Type[]; - let targetStack: Type[]; - let maybeCount = 0; - let sourceDepth = 0; - let targetDepth = 0; - let expandingFlags = ExpandingFlags.None; - let overflow = false; - let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid - let lastSkippedInfo: [Type, Type] | undefined; - let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] | undefined; - let inPropertyCheck = false; - - Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); - - const result = isRelatedTo(source, target, RecursionFlags.Both, /*reportErrors*/ !!errorNode, headMessage); - if (incompatibleStack) { - reportIncompatibleStack(); - } - if (overflow) { - tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth }); - const diag = error(errorNode || currentNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target)); - if (errorOutputContainer) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } + function getNormalizedUnionOrIntersectionType(type: UnionOrIntersectionType, writing: boolean) { + const reduced = getReducedType(type); + if (reduced !== type) { + return reduced; + } + if (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isEmptyAnonymousObjectType)) { + const normalizedTypes = sameMap(type.types, t => getNormalizedType(t, writing)); + if (normalizedTypes !== type.types) { + return getIntersectionType(normalizedTypes); } - else if (errorInfo) { - if (containingMessageChain) { - const chain = containingMessageChain(); - if (chain) { - concatenateDiagnosticMessageChains(chain, errorInfo); - errorInfo = chain; - } - } + } + return type; + } - let relatedInformation: DiagnosticRelatedInformation[] | undefined; - // Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement - if (headMessage && errorNode && !result && source.symbol) { - const links = getSymbolLinks(source.symbol); - if (links.originatingImport && !isImportCall(links.originatingImport)) { - const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined); - if (helpfulRetry) { - // Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import - const diag = createDiagnosticForNode(links.originatingImport, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead); - relatedInformation = append(relatedInformation, diag); // Cause the error to appear with the error that triggered it - } - } - } - const diag = createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation); - if (relatedInfo) { - addRelatedInfo(diag, ...relatedInfo); - } - if (errorOutputContainer) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } - if (!errorOutputContainer || !errorOutputContainer.skipLogging) { - diagnostics.add(diag); - } + /** + * Checks if 'source' is related to 'target' (e.g.: is a assignable to). + * @param source The left-hand-side of the relation. + * @param target The right-hand-side of the relation. + * @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'. + * Used as both to determine which checks are performed and as a cache of previously computed results. + * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used. + * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. + * @param containingMessageChain A chain of errors to prepend any new errors found. + * @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy. + */ + function checkTypeRelatedTo( + source: Type, + target: Type, + relation: ESMap, + errorNode: Node | undefined, + headMessage?: DiagnosticMessage, + containingMessageChain?: () => DiagnosticMessageChain | undefined, + errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean }, + ): boolean { + + let errorInfo: DiagnosticMessageChain | undefined; + let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined; + let maybeKeys: string[]; + let sourceStack: Type[]; + let targetStack: Type[]; + let maybeCount = 0; + let sourceDepth = 0; + let targetDepth = 0; + let expandingFlags = ExpandingFlags.None; + let overflow = false; + let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid + let lastSkippedInfo: [Type, Type] | undefined; + let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] | undefined; + let inPropertyCheck = false; + + Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); + + const result = isRelatedTo(source, target, RecursionFlags.Both, /*reportErrors*/ !!errorNode, headMessage); + if (incompatibleStack) { + reportIncompatibleStack(); + } + if (overflow) { + tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth }); + const diag = error(errorNode || currentNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target)); + if (errorOutputContainer) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } + } + else if (errorInfo) { + if (containingMessageChain) { + const chain = containingMessageChain(); + if (chain) { + concatenateDiagnosticMessageChains(chain, errorInfo); + errorInfo = chain; + } + } + + let relatedInformation: DiagnosticRelatedInformation[] | undefined; + // Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement + if (headMessage && errorNode && !result && source.symbol) { + const links = getSymbolLinks(source.symbol); + if (links.originatingImport && !isImportCall(links.originatingImport)) { + const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined); + if (helpfulRetry) { + // Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import + const diag = createDiagnosticForNode(links.originatingImport, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead); + relatedInformation = append(relatedInformation, diag); // Cause the error to appear with the error that triggered it + } + } + } + const diag = createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation); + if (relatedInfo) { + addRelatedInfo(diag, ...relatedInfo); } - if (errorNode && errorOutputContainer && errorOutputContainer.skipLogging && result === Ternary.False) { - Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error."); + if (errorOutputContainer) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } + if (!errorOutputContainer || !errorOutputContainer.skipLogging) { + diagnostics.add(diag); } + } + if (errorNode && errorOutputContainer && errorOutputContainer.skipLogging && result === Ternary.False) { + Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error."); + } - return result !== Ternary.False; + return result !== Ternary.False; - function resetErrorInfo(saved: ReturnType) { - errorInfo = saved.errorInfo; - lastSkippedInfo = saved.lastSkippedInfo; - incompatibleStack = saved.incompatibleStack; - overrideNextErrorInfo = saved.overrideNextErrorInfo; - relatedInfo = saved.relatedInfo; - } + function resetErrorInfo(saved: ReturnType) { + errorInfo = saved.errorInfo; + lastSkippedInfo = saved.lastSkippedInfo; + incompatibleStack = saved.incompatibleStack; + overrideNextErrorInfo = saved.overrideNextErrorInfo; + relatedInfo = saved.relatedInfo; + } - function captureErrorCalculationState() { - return { - errorInfo, - lastSkippedInfo, - incompatibleStack: incompatibleStack?.slice(), - overrideNextErrorInfo, - relatedInfo: relatedInfo?.slice() as [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined, - }; - } + function captureErrorCalculationState() { + return { + errorInfo, + lastSkippedInfo, + incompatibleStack: incompatibleStack?.slice(), + overrideNextErrorInfo, + relatedInfo: relatedInfo?.slice() as [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined, + }; + } - function reportIncompatibleError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number) { - overrideNextErrorInfo++; // Suppress the next relation error - lastSkippedInfo = undefined; // Reset skipped info cache - (incompatibleStack ||= []).push([message, arg0, arg1, arg2, arg3]); - } + function reportIncompatibleError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number) { + overrideNextErrorInfo++; // Suppress the next relation error + lastSkippedInfo = undefined; // Reset skipped info cache + (incompatibleStack ||= []).push([message, arg0, arg1, arg2, arg3]); + } - function reportIncompatibleStack() { - const stack = incompatibleStack || []; - incompatibleStack = undefined; - const info = lastSkippedInfo; - lastSkippedInfo = undefined; - if (stack.length === 1) { - reportError(...stack[0]); - if (info) { - // Actually do the last relation error - reportRelationError(/*headMessage*/ undefined, ...info); - } - return; + function reportIncompatibleStack() { + const stack = incompatibleStack || []; + incompatibleStack = undefined; + const info = lastSkippedInfo; + lastSkippedInfo = undefined; + if (stack.length === 1) { + reportError(...stack[0]); + if (info) { + // Actually do the last relation error + reportRelationError(/*headMessage*/ undefined, ...info); } - // The first error will be the innermost, while the last will be the outermost - so by popping off the end, - // we can build from left to right - let path = ""; - const secondaryRootErrors: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = []; - while (stack.length) { - const [msg, ...args] = stack.pop()!; - switch (msg.code) { - case Diagnostics.Types_of_property_0_are_incompatible.code: { - // Parenthesize a `new` if there is one - if (path.indexOf("new ") === 0) { - path = `(${path})`; - } - const str = "" + args[0]; - // If leading, just print back the arg (irrespective of if it's a valid identifier) - if (path.length === 0) { - path = `${str}`; - } - // Otherwise write a dotted name if possible - else if (isIdentifierText(str, getEmitScriptTarget(compilerOptions))) { - path = `${path}.${str}`; - } - // Failing that, check if the name is already a computed name - else if (str[0] === "[" && str[str.length - 1] === "]") { - path = `${path}${str}`; - } - // And finally write out a computed name as a last resort - else { - path = `${path}[${str}]`; - } - break; + return; + } + // The first error will be the innermost, while the last will be the outermost - so by popping off the end, + // we can build from left to right + let path = ""; + const secondaryRootErrors: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = []; + while (stack.length) { + const [msg, ...args] = stack.pop()!; + switch (msg.code) { + case Diagnostics.Types_of_property_0_are_incompatible.code: { + // Parenthesize a `new` if there is one + if (path.indexOf("new ") === 0) { + path = `(${path})`; + } + const str = "" + args[0]; + // If leading, just print back the arg (irrespective of if it's a valid identifier) + if (path.length === 0) { + path = `${str}`; + } + // Otherwise write a dotted name if possible + else if (isIdentifierText(str, getEmitScriptTarget(compilerOptions))) { + path = `${path}.${str}`; + } + // Failing that, check if the name is already a computed name + else if (str[0] === "[" && str[str.length - 1] === "]") { + path = `${path}${str}`; + } + // And finally write out a computed name as a last resort + else { + path = `${path}[${str}]`; } - case Diagnostics.Call_signature_return_types_0_and_1_are_incompatible.code: - case Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code: - case Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: - case Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: { - if (path.length === 0) { - // Don't flatten signature compatability errors at the start of a chain - instead prefer - // to unify (the with no arguments bit is excessive for printback) and print them back - let mappedMsg = msg; - if (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { - mappedMsg = Diagnostics.Call_signature_return_types_0_and_1_are_incompatible; - } - else if (msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { - mappedMsg = Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible; - } - secondaryRootErrors.unshift([mappedMsg, args[0], args[1]]); + break; + } + case Diagnostics.Call_signature_return_types_0_and_1_are_incompatible.code: + case Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code: + case Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: + case Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code: { + if (path.length === 0) { + // Don't flatten signature compatability errors at the start of a chain - instead prefer + // to unify (the with no arguments bit is excessive for printback) and print them back + let mappedMsg = msg; + if (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { + mappedMsg = Diagnostics.Call_signature_return_types_0_and_1_are_incompatible; } - else { - const prefix = (msg.code === Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code || - msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) - ? "new " - : ""; - const params = (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code || - msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) - ? "" - : "..."; - path = `${prefix}${path}(${params})`; + else if (msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) { + mappedMsg = Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible; } - break; - } - case Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target.code: { - secondaryRootErrors.unshift([Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, args[0], args[1]]); - break; + secondaryRootErrors.unshift([mappedMsg, args[0], args[1]]); } - case Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target.code: { - secondaryRootErrors.unshift([Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, args[0], args[1], args[2]]); - break; + else { + const prefix = (msg.code === Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible.code || + msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) + ? "new " + : ""; + const params = (msg.code === Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code || + msg.code === Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1.code) + ? "" + : "..."; + path = `${prefix}${path}(${params})`; } - default: - return Debug.fail(`Unhandled Diagnostic: ${msg.code}`); + break; } + case Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target.code: { + secondaryRootErrors.unshift([Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, args[0], args[1]]); + break; + } + case Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target.code: { + secondaryRootErrors.unshift([Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, args[0], args[1], args[2]]); + break; + } + default: + return Debug.fail(`Unhandled Diagnostic: ${msg.code}`); } - if (path) { - reportError(path[path.length - 1] === ")" - ? Diagnostics.The_types_returned_by_0_are_incompatible_between_these_types - : Diagnostics.The_types_of_0_are_incompatible_between_these_types, - path - ); - } - else { - // Remove the innermost secondary error as it will duplicate the error already reported by `reportRelationError` on entry - secondaryRootErrors.shift(); - } - for (const [msg, ...args] of secondaryRootErrors) { - const originalValue = msg.elidedInCompatabilityPyramid; - msg.elidedInCompatabilityPyramid = false; // Temporarily override elision to ensure error is reported - reportError(msg, ...args); - msg.elidedInCompatabilityPyramid = originalValue; - } - if (info) { - // Actually do the last relation error - reportRelationError(/*headMessage*/ undefined, ...info); - } } + if (path) { + reportError(path[path.length - 1] === ")" + ? Diagnostics.The_types_returned_by_0_are_incompatible_between_these_types + : Diagnostics.The_types_of_0_are_incompatible_between_these_types, + path + ); + } + else { + // Remove the innermost secondary error as it will duplicate the error already reported by `reportRelationError` on entry + secondaryRootErrors.shift(); + } + for (const [msg, ...args] of secondaryRootErrors) { + const originalValue = msg.elidedInCompatabilityPyramid; + msg.elidedInCompatabilityPyramid = false; // Temporarily override elision to ensure error is reported + reportError(msg, ...args); + msg.elidedInCompatabilityPyramid = originalValue; + } + if (info) { + // Actually do the last relation error + reportRelationError(/*headMessage*/ undefined, ...info); + } + } + + function reportError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { + Debug.assert(!!errorNode); + if (incompatibleStack) reportIncompatibleStack(); + if (message.elidedInCompatabilityPyramid) return; + errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2, arg3); + } + + function associateRelatedInfo(info: DiagnosticRelatedInformation) { + Debug.assert(!!errorInfo); + if (!relatedInfo) { + relatedInfo = [info]; + } + else { + relatedInfo.push(info); + } + } + + function reportRelationError(message: DiagnosticMessage | undefined, source: Type, target: Type) { + if (incompatibleStack) reportIncompatibleStack(); + const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target); + let generalizedSource = source; + let generalizedSourceType = sourceType; - function reportError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { - Debug.assert(!!errorNode); - if (incompatibleStack) reportIncompatibleStack(); - if (message.elidedInCompatabilityPyramid) return; - errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2, arg3); + if (isLiteralType(source) && !typeCouldHaveTopLevelSingletonTypes(target)) { + generalizedSource = getBaseTypeOfLiteralType(source); + Debug.assert(!isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable"); + generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource); } - function associateRelatedInfo(info: DiagnosticRelatedInformation) { - Debug.assert(!!errorInfo); - if (!relatedInfo) { - relatedInfo = [info]; + if (target.flags & TypeFlags.TypeParameter && target !== markerSuperTypeForCheck && target !== markerSubTypeForCheck) { + const constraint = getBaseConstraintOfType(target); + let needsOriginalSource; + if (constraint && (isTypeAssignableTo(generalizedSource, constraint) || (needsOriginalSource = isTypeAssignableTo(source, constraint)))) { + reportError( + Diagnostics._0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, + needsOriginalSource ? sourceType : generalizedSourceType, + targetType, + typeToString(constraint), + ); } else { - relatedInfo.push(info); + errorInfo = undefined; + reportError( + Diagnostics._0_could_be_instantiated_with_an_arbitrary_type_which_could_be_unrelated_to_1, + targetType, + generalizedSourceType + ); } } - function reportRelationError(message: DiagnosticMessage | undefined, source: Type, target: Type) { - if (incompatibleStack) reportIncompatibleStack(); - const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target); - let generalizedSource = source; - let generalizedSourceType = sourceType; - - if (isLiteralType(source) && !typeCouldHaveTopLevelSingletonTypes(target)) { - generalizedSource = getBaseTypeOfLiteralType(source); - Debug.assert(!isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable"); - generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource); + if (!message) { + if (relation === comparableRelation) { + message = Diagnostics.Type_0_is_not_comparable_to_type_1; } - - if (target.flags & TypeFlags.TypeParameter && target !== markerSuperTypeForCheck && target !== markerSubTypeForCheck) { - const constraint = getBaseConstraintOfType(target); - let needsOriginalSource; - if (constraint && (isTypeAssignableTo(generalizedSource, constraint) || (needsOriginalSource = isTypeAssignableTo(source, constraint)))) { - reportError( - Diagnostics._0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, - needsOriginalSource ? sourceType : generalizedSourceType, - targetType, - typeToString(constraint), - ); - } - else { - errorInfo = undefined; - reportError( - Diagnostics._0_could_be_instantiated_with_an_arbitrary_type_which_could_be_unrelated_to_1, - targetType, - generalizedSourceType - ); - } + else if (sourceType === targetType) { + message = Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated; } - - if (!message) { - if (relation === comparableRelation) { - message = Diagnostics.Type_0_is_not_comparable_to_type_1; - } - else if (sourceType === targetType) { - message = Diagnostics.Type_0_is_not_assignable_to_type_1_Two_different_types_with_this_name_exist_but_they_are_unrelated; - } - else if (exactOptionalPropertyTypes && getExactOptionalUnassignableProperties(source, target).length) { - message = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties; - } - else { - if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.Union) { - const suggestedType = getSuggestedTypeForNonexistentStringLiteralType(source as StringLiteralType, target as UnionType); - if (suggestedType) { - reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, generalizedSourceType, targetType, typeToString(suggestedType)); - return; - } + else if (exactOptionalPropertyTypes && getExactOptionalUnassignableProperties(source, target).length) { + message = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties; + } + else { + if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.Union) { + const suggestedType = getSuggestedTypeForNonexistentStringLiteralType(source as StringLiteralType, target as UnionType); + if (suggestedType) { + reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, generalizedSourceType, targetType, typeToString(suggestedType)); + return; } - message = Diagnostics.Type_0_is_not_assignable_to_type_1; } + message = Diagnostics.Type_0_is_not_assignable_to_type_1; } - else if (message === Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1 - && exactOptionalPropertyTypes - && getExactOptionalUnassignableProperties(source, target).length) { - message = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties; - } - - reportError(message, generalizedSourceType, targetType); } + else if (message === Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1 + && exactOptionalPropertyTypes + && getExactOptionalUnassignableProperties(source, target).length) { + message = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties; + } + + reportError(message, generalizedSourceType, targetType); + } - function tryElaborateErrorsForPrimitivesAndObjects(source: Type, target: Type) { - const sourceType = symbolValueDeclarationIsContextSensitive(source.symbol) ? typeToString(source, source.symbol.valueDeclaration) : typeToString(source); - const targetType = symbolValueDeclarationIsContextSensitive(target.symbol) ? typeToString(target, target.symbol.valueDeclaration) : typeToString(target); + function tryElaborateErrorsForPrimitivesAndObjects(source: Type, target: Type) { + const sourceType = symbolValueDeclarationIsContextSensitive(source.symbol) ? typeToString(source, source.symbol.valueDeclaration) : typeToString(source); + const targetType = symbolValueDeclarationIsContextSensitive(target.symbol) ? typeToString(target, target.symbol.valueDeclaration) : typeToString(target); - if ((globalStringType === source && stringType === target) || - (globalNumberType === source && numberType === target) || - (globalBooleanType === source && booleanType === target) || - (getGlobalESSymbolType() === source && esSymbolType === target)) { - reportError(Diagnostics._0_is_a_primitive_but_1_is_a_wrapper_object_Prefer_using_0_when_possible, targetType, sourceType); - } + if ((globalStringType === source && stringType === target) || + (globalNumberType === source && numberType === target) || + (globalBooleanType === source && booleanType === target) || + (getGlobalESSymbolType() === source && esSymbolType === target)) { + reportError(Diagnostics._0_is_a_primitive_but_1_is_a_wrapper_object_Prefer_using_0_when_possible, targetType, sourceType); } + } + /** + * Try and elaborate array and tuple errors. Returns false + * if we have found an elaboration, or we should ignore + * any other elaborations when relating the `source` and + * `target` types. + */ + function tryElaborateArrayLikeErrors(source: Type, target: Type, reportErrors: boolean): boolean { /** - * Try and elaborate array and tuple errors. Returns false - * if we have found an elaboration, or we should ignore - * any other elaborations when relating the `source` and - * `target` types. + * The spec for elaboration is: + * - If the source is a readonly tuple and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. + * - If the source is a tuple then skip property elaborations if the target is an array or tuple. + * - If the source is a readonly array and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. + * - If the source an array then skip property elaborations if the target is a tuple. */ - function tryElaborateArrayLikeErrors(source: Type, target: Type, reportErrors: boolean): boolean { - /** - * The spec for elaboration is: - * - If the source is a readonly tuple and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. - * - If the source is a tuple then skip property elaborations if the target is an array or tuple. - * - If the source is a readonly array and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. - * - If the source an array then skip property elaborations if the target is a tuple. - */ - if (isTupleType(source)) { - if (source.target.readonly && isMutableArrayOrTuple(target)) { - if (reportErrors) { - reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); - } - return false; - } - return isArrayOrTupleType(target); - } - if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) { + if (isTupleType(source)) { + if (source.target.readonly && isMutableArrayOrTuple(target)) { if (reportErrors) { reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); } return false; } - if (isTupleType(target)) { - return isArrayType(source); + return isArrayOrTupleType(target); + } + if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) { + if (reportErrors) { + reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); } - return true; + return false; } - - function isRelatedToWorker(source: Type, target: Type, reportErrors: boolean) { - return isRelatedTo(source, target, RecursionFlags.Both, reportErrors); + if (isTupleType(target)) { + return isArrayType(source); } + return true; + } - /** - * Compare two types and return - * * Ternary.True if they are related with no assumptions, - * * Ternary.Maybe if they are related with assumptions of other relationships, or - * * Ternary.False if they are not related. - */ - function isRelatedTo(originalSource: Type, originalTarget: Type, recursionFlags: RecursionFlags = RecursionFlags.Both, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary { - // Before normalization: if `source` is type an object type, and `target` is primitive, - // skip all the checks we don't need and just return `isSimpleTypeRelatedTo` result - if (originalSource.flags & TypeFlags.Object && originalTarget.flags & TypeFlags.Primitive) { - if (relation === comparableRelation && !(originalTarget.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(originalTarget, originalSource, relation) || - isSimpleTypeRelatedTo(originalSource, originalTarget, relation, reportErrors ? reportError : undefined)) { - return Ternary.True; - } - if (reportErrors) { - reportErrorResults(originalSource, originalTarget, originalSource, originalTarget, headMessage); - } - return Ternary.False; + function isRelatedToWorker(source: Type, target: Type, reportErrors: boolean) { + return isRelatedTo(source, target, RecursionFlags.Both, reportErrors); + } + + /** + * Compare two types and return + * * Ternary.True if they are related with no assumptions, + * * Ternary.Maybe if they are related with assumptions of other relationships, or + * * Ternary.False if they are not related. + */ + function isRelatedTo(originalSource: Type, originalTarget: Type, recursionFlags: RecursionFlags = RecursionFlags.Both, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary { + // Before normalization: if `source` is type an object type, and `target` is primitive, + // skip all the checks we don't need and just return `isSimpleTypeRelatedTo` result + if (originalSource.flags & TypeFlags.Object && originalTarget.flags & TypeFlags.Primitive) { + if (relation === comparableRelation && !(originalTarget.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(originalTarget, originalSource, relation) || + isSimpleTypeRelatedTo(originalSource, originalTarget, relation, reportErrors ? reportError : undefined)) { + return Ternary.True; + } + if (reportErrors) { + reportErrorResults(originalSource, originalTarget, originalSource, originalTarget, headMessage); } + return Ternary.False; + } - // Normalize the source and target types: Turn fresh literal types into regular literal types, - // turn deferred type references into regular type references, simplify indexed access and - // conditional types, and resolve substitution types to either the substitution (on the source - // side) or the type variable (on the target side). - const source = getNormalizedType(originalSource, /*writing*/ false); - let target = getNormalizedType(originalTarget, /*writing*/ true); + // Normalize the source and target types: Turn fresh literal types into regular literal types, + // turn deferred type references into regular type references, simplify indexed access and + // conditional types, and resolve substitution types to either the substitution (on the source + // side) or the type variable (on the target side). + const source = getNormalizedType(originalSource, /*writing*/ false); + let target = getNormalizedType(originalTarget, /*writing*/ true); - if (source === target) return Ternary.True; + if (source === target) return Ternary.True; - if (relation === identityRelation) { - if (source.flags !== target.flags) return Ternary.False; - if (source.flags & TypeFlags.Singleton) return Ternary.True; - traceUnionsOrIntersectionsTooLarge(source, target); - return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None, recursionFlags); - } + if (relation === identityRelation) { + if (source.flags !== target.flags) return Ternary.False; + if (source.flags & TypeFlags.Singleton) return Ternary.True; + traceUnionsOrIntersectionsTooLarge(source, target); + return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None, recursionFlags); + } - // We fastpath comparing a type parameter to exactly its constraint, as this is _super_ common, - // and otherwise, for type parameters in large unions, causes us to need to compare the union to itself, - // as we break down the _target_ union first, _then_ get the source constraint - so for every - // member of the target, we attempt to find a match in the source. This avoids that in cases where - // the target is exactly the constraint. - if (source.flags & TypeFlags.TypeParameter && getConstraintOfType(source) === target) { - return Ternary.True; - } + // We fastpath comparing a type parameter to exactly its constraint, as this is _super_ common, + // and otherwise, for type parameters in large unions, causes us to need to compare the union to itself, + // as we break down the _target_ union first, _then_ get the source constraint - so for every + // member of the target, we attempt to find a match in the source. This avoids that in cases where + // the target is exactly the constraint. + if (source.flags & TypeFlags.TypeParameter && getConstraintOfType(source) === target) { + return Ternary.True; + } - // See if we're relating a definitely non-nullable type to a union that includes null and/or undefined - // plus a single non-nullable type. If so, remove null and/or undefined from the target type. - if (source.flags & TypeFlags.DefinitelyNonNullable && target.flags & TypeFlags.Union) { - const types = (target as UnionType).types; - const candidate = types.length === 2 && types[0].flags & TypeFlags.Nullable ? types[1] : - types.length === 3 && types[0].flags & TypeFlags.Nullable && types[1].flags & TypeFlags.Nullable ? types[2] : - undefined; - if (candidate && !(candidate.flags & TypeFlags.Nullable)) { - target = getNormalizedType(candidate, /*writing*/ true); - if (source === target) return Ternary.True; - } + // See if we're relating a definitely non-nullable type to a union that includes null and/or undefined + // plus a single non-nullable type. If so, remove null and/or undefined from the target type. + if (source.flags & TypeFlags.DefinitelyNonNullable && target.flags & TypeFlags.Union) { + const types = (target as UnionType).types; + const candidate = types.length === 2 && types[0].flags & TypeFlags.Nullable ? types[1] : + types.length === 3 && types[0].flags & TypeFlags.Nullable && types[1].flags & TypeFlags.Nullable ? types[2] : + undefined; + if (candidate && !(candidate.flags & TypeFlags.Nullable)) { + target = getNormalizedType(candidate, /*writing*/ true); + if (source === target) return Ternary.True; } + } - if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || - isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True; - - if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { - const isPerformingExcessPropertyChecks = !(intersectionState & IntersectionState.Target) && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral); - if (isPerformingExcessPropertyChecks) { - if (hasExcessProperties(source as FreshObjectLiteralType, target, reportErrors)) { - if (reportErrors) { - reportRelationError(headMessage, source, originalTarget.aliasSymbol ? originalTarget : target); - } - return Ternary.False; - } - } + if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || + isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True; - const isPerformingCommonPropertyChecks = (relation !== comparableRelation || isUnitType(source)) && - !(intersectionState & IntersectionState.Target) && - source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType && - target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) && - (getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)); - const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); - if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) { + if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { + const isPerformingExcessPropertyChecks = !(intersectionState & IntersectionState.Target) && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral); + if (isPerformingExcessPropertyChecks) { + if (hasExcessProperties(source as FreshObjectLiteralType, target, reportErrors)) { if (reportErrors) { - const sourceString = typeToString(originalSource.aliasSymbol ? originalSource : source); - const targetString = typeToString(originalTarget.aliasSymbol ? originalTarget : target); - const calls = getSignaturesOfType(source, SignatureKind.Call); - const constructs = getSignaturesOfType(source, SignatureKind.Construct); - if (calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, RecursionFlags.Source, /*reportErrors*/ false) || - constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, RecursionFlags.Source, /*reportErrors*/ false)) { - reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, sourceString, targetString); - } - else { - reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, sourceString, targetString); - } + reportRelationError(headMessage, source, originalTarget.aliasSymbol ? originalTarget : target); } return Ternary.False; } + } - traceUnionsOrIntersectionsTooLarge(source, target); - - const skipCaching = source.flags & TypeFlags.Union && (source as UnionType).types.length < 4 && !(target.flags & TypeFlags.Union) || - target.flags & TypeFlags.Union && (target as UnionType).types.length < 4 && !(source.flags & TypeFlags.StructuredOrInstantiable); - const result = skipCaching ? - unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState) : - recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags); - if (result) { - return result; + const isPerformingCommonPropertyChecks = (relation !== comparableRelation || isUnitType(source)) && + !(intersectionState & IntersectionState.Target) && + source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType && + target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) && + (getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)); + const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); + if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) { + if (reportErrors) { + const sourceString = typeToString(originalSource.aliasSymbol ? originalSource : source); + const targetString = typeToString(originalTarget.aliasSymbol ? originalTarget : target); + const calls = getSignaturesOfType(source, SignatureKind.Call); + const constructs = getSignaturesOfType(source, SignatureKind.Construct); + if (calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, RecursionFlags.Source, /*reportErrors*/ false) || + constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, RecursionFlags.Source, /*reportErrors*/ false)) { + reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, sourceString, targetString); + } + else { + reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, sourceString, targetString); + } } + return Ternary.False; } - if (reportErrors) { - reportErrorResults(originalSource, originalTarget, source, target, headMessage); + traceUnionsOrIntersectionsTooLarge(source, target); + + const skipCaching = source.flags & TypeFlags.Union && (source as UnionType).types.length < 4 && !(target.flags & TypeFlags.Union) || + target.flags & TypeFlags.Union && (target as UnionType).types.length < 4 && !(source.flags & TypeFlags.StructuredOrInstantiable); + const result = skipCaching ? + unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState) : + recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags); + if (result) { + return result; } - return Ternary.False; } - function reportErrorResults(originalSource: Type, originalTarget: Type, source: Type, target: Type, headMessage: DiagnosticMessage | undefined) { - const sourceHasBase = !!getSingleBaseForNonAugmentingSubtype(originalSource); - const targetHasBase = !!getSingleBaseForNonAugmentingSubtype(originalTarget); - source = (originalSource.aliasSymbol || sourceHasBase) ? originalSource : source; - target = (originalTarget.aliasSymbol || targetHasBase) ? originalTarget : target; - let maybeSuppress = overrideNextErrorInfo > 0; - if (maybeSuppress) { - overrideNextErrorInfo--; - } - if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { - const currentError = errorInfo; - tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ true); - if (errorInfo !== currentError) { - maybeSuppress = !!errorInfo; - } - } - if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) { - tryElaborateErrorsForPrimitivesAndObjects(source, target); - } - else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) { - reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead); - } - else if (getObjectFlags(source) & ObjectFlags.JsxAttributes && target.flags & TypeFlags.Intersection) { - const targetTypes = (target as IntersectionType).types; - const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode); - const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode); - if (!isErrorType(intrinsicAttributes) && !isErrorType(intrinsicClassAttributes) && - (contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) { - // do not report top error - return; - } - } - else { - errorInfo = elaborateNeverIntersection(errorInfo, originalTarget); + if (reportErrors) { + reportErrorResults(originalSource, originalTarget, source, target, headMessage); + } + return Ternary.False; + } + + function reportErrorResults(originalSource: Type, originalTarget: Type, source: Type, target: Type, headMessage: DiagnosticMessage | undefined) { + const sourceHasBase = !!getSingleBaseForNonAugmentingSubtype(originalSource); + const targetHasBase = !!getSingleBaseForNonAugmentingSubtype(originalTarget); + source = (originalSource.aliasSymbol || sourceHasBase) ? originalSource : source; + target = (originalTarget.aliasSymbol || targetHasBase) ? originalTarget : target; + let maybeSuppress = overrideNextErrorInfo > 0; + if (maybeSuppress) { + overrideNextErrorInfo--; + } + if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { + const currentError = errorInfo; + tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ true); + if (errorInfo !== currentError) { + maybeSuppress = !!errorInfo; } - if (!headMessage && maybeSuppress) { - lastSkippedInfo = [source, target]; - // Used by, eg, missing property checking to replace the top-level message with a more informative one + } + if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) { + tryElaborateErrorsForPrimitivesAndObjects(source, target); + } + else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) { + reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead); + } + else if (getObjectFlags(source) & ObjectFlags.JsxAttributes && target.flags & TypeFlags.Intersection) { + const targetTypes = (target as IntersectionType).types; + const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode); + const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode); + if (!isErrorType(intrinsicAttributes) && !isErrorType(intrinsicClassAttributes) && + (contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) { + // do not report top error return; } - reportRelationError(headMessage, source, target); - if (source.flags & TypeFlags.TypeParameter && source.symbol?.declarations?.[0] && !getConstraintOfType(source as TypeVariable)) { - const syntheticParam = cloneTypeParameter(source as TypeParameter); - syntheticParam.constraint = instantiateType(target, makeUnaryTypeMapper(source, syntheticParam)); - if (hasNonCircularBaseConstraint(syntheticParam)) { - const targetConstraintString = typeToString(target, source.symbol.declarations[0]); - associateRelatedInfo(createDiagnosticForNode(source.symbol.declarations[0], Diagnostics.This_type_parameter_might_need_an_extends_0_constraint, targetConstraintString)); - } + } + else { + errorInfo = elaborateNeverIntersection(errorInfo, originalTarget); + } + if (!headMessage && maybeSuppress) { + lastSkippedInfo = [source, target]; + // Used by, eg, missing property checking to replace the top-level message with a more informative one + return; + } + reportRelationError(headMessage, source, target); + if (source.flags & TypeFlags.TypeParameter && source.symbol?.declarations?.[0] && !getConstraintOfType(source as TypeVariable)) { + const syntheticParam = cloneTypeParameter(source as TypeParameter); + syntheticParam.constraint = instantiateType(target, makeUnaryTypeMapper(source, syntheticParam)); + if (hasNonCircularBaseConstraint(syntheticParam)) { + const targetConstraintString = typeToString(target, source.symbol.declarations[0]); + associateRelatedInfo(createDiagnosticForNode(source.symbol.declarations[0], Diagnostics.This_type_parameter_might_need_an_extends_0_constraint, targetConstraintString)); } } + } - function traceUnionsOrIntersectionsTooLarge(source: Type, target: Type): void { - if (!tracing) { - return; - } + function traceUnionsOrIntersectionsTooLarge(source: Type, target: Type): void { + if (!tracing) { + return; + } - if ((source.flags & TypeFlags.UnionOrIntersection) && (target.flags & TypeFlags.UnionOrIntersection)) { - const sourceUnionOrIntersection = source as UnionOrIntersectionType; - const targetUnionOrIntersection = target as UnionOrIntersectionType; + if ((source.flags & TypeFlags.UnionOrIntersection) && (target.flags & TypeFlags.UnionOrIntersection)) { + const sourceUnionOrIntersection = source as UnionOrIntersectionType; + const targetUnionOrIntersection = target as UnionOrIntersectionType; - if (sourceUnionOrIntersection.objectFlags & targetUnionOrIntersection.objectFlags & ObjectFlags.PrimitiveUnion) { - // There's a fast path for comparing primitive unions - return; - } + if (sourceUnionOrIntersection.objectFlags & targetUnionOrIntersection.objectFlags & ObjectFlags.PrimitiveUnion) { + // There's a fast path for comparing primitive unions + return; + } - const sourceSize = sourceUnionOrIntersection.types.length; - const targetSize = targetUnionOrIntersection.types.length; - if (sourceSize * targetSize > 1E6) { - tracing.instant(tracing.Phase.CheckTypes, "traceUnionsOrIntersectionsTooLarge_DepthLimit", { - sourceId: source.id, - sourceSize, - targetId: target.id, - targetSize, - pos: errorNode?.pos, - end: errorNode?.end - }); - } + const sourceSize = sourceUnionOrIntersection.types.length; + const targetSize = targetUnionOrIntersection.types.length; + if (sourceSize * targetSize > 1E6) { + tracing.instant(tracing.Phase.CheckTypes, "traceUnionsOrIntersectionsTooLarge_DepthLimit", { + sourceId: source.id, + sourceSize, + targetId: target.id, + targetSize, + pos: errorNode?.pos, + end: errorNode?.end + }); } } + } - function getTypeOfPropertyInTypes(types: Type[], name: __String) { - const appendPropType = (propTypes: Type[] | undefined, type: Type) => { - type = getApparentType(type); - const prop = type.flags & TypeFlags.UnionOrIntersection ? getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name) : getPropertyOfObjectType(type, name); - const propType = prop && getTypeOfSymbol(prop) || getApplicableIndexInfoForName(type, name)?.type || undefinedType; - return append(propTypes, propType); - }; - return getUnionType(reduceLeft(types, appendPropType, /*initial*/ undefined) || emptyArray); - } + function getTypeOfPropertyInTypes(types: Type[], name: __String) { + const appendPropType = (propTypes: Type[] | undefined, type: Type) => { + type = getApparentType(type); + const prop = type.flags & TypeFlags.UnionOrIntersection ? getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name) : getPropertyOfObjectType(type, name); + const propType = prop && getTypeOfSymbol(prop) || getApplicableIndexInfoForName(type, name)?.type || undefinedType; + return append(propTypes, propType); + }; + return getUnionType(reduceLeft(types, appendPropType, /*initial*/ undefined) || emptyArray); + } - function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean { - if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) { - return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny - } - const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); - if ((relation === assignableRelation || relation === comparableRelation) && - (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) { - return false; - } - let reducedTarget = target; - let checkTypes: Type[] | undefined; - if (target.flags & TypeFlags.Union) { - reducedTarget = findMatchingDiscriminantType(source, target as UnionType, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(target as UnionType); - checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget as UnionType).types : [reducedTarget]; - } - for (const prop of getPropertiesOfType(source)) { - if (shouldCheckAsExcessProperty(prop, source.symbol) && !isIgnoredJsxProperty(source, prop)) { - if (!isKnownProperty(reducedTarget, prop.escapedName, isComparingJsxAttributes)) { - if (reportErrors) { - // Report error in terms of object types in the target as those are the only ones - // we check in isKnownProperty. - const errorTarget = filterType(reducedTarget, isExcessPropertyCheckTarget); - // We know *exactly* where things went wrong when comparing the types. - // Use this property as the error node as this will be more helpful in - // reasoning about what went wrong. - if (!errorNode) return Debug.fail(); - if (isJsxAttributes(errorNode) || isJsxOpeningLikeElement(errorNode) || isJsxOpeningLikeElement(errorNode.parent)) { - // JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal. - // However, using an object-literal error message will be very confusing to the users so we give different a message. - if (prop.valueDeclaration && isJsxAttribute(prop.valueDeclaration) && getSourceFileOfNode(errorNode) === getSourceFileOfNode(prop.valueDeclaration.name)) { - // Note that extraneous children (as in `extra`) don't pass this check, - // since `children` is a SyntaxKind.PropertySignature instead of a SyntaxKind.JsxAttribute. - errorNode = prop.valueDeclaration.name; - } - const propName = symbolToString(prop); - const suggestionSymbol = getSuggestedSymbolForNonexistentJSXAttribute(propName, errorTarget); - const suggestion = suggestionSymbol ? symbolToString(suggestionSymbol) : undefined; - if (suggestion) { - reportError(Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName, typeToString(errorTarget), suggestion); - } - else { - reportError(Diagnostics.Property_0_does_not_exist_on_type_1, propName, typeToString(errorTarget)); - } + function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean { + if (!isExcessPropertyCheckTarget(target) || !noImplicitAny && getObjectFlags(target) & ObjectFlags.JSLiteral) { + return false; // Disable excess property checks on JS literals to simulate having an implicit "index signature" - but only outside of noImplicitAny + } + const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); + if ((relation === assignableRelation || relation === comparableRelation) && + (isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) { + return false; + } + let reducedTarget = target; + let checkTypes: Type[] | undefined; + if (target.flags & TypeFlags.Union) { + reducedTarget = findMatchingDiscriminantType(source, target as UnionType, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(target as UnionType); + checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget as UnionType).types : [reducedTarget]; + } + for (const prop of getPropertiesOfType(source)) { + if (shouldCheckAsExcessProperty(prop, source.symbol) && !isIgnoredJsxProperty(source, prop)) { + if (!isKnownProperty(reducedTarget, prop.escapedName, isComparingJsxAttributes)) { + if (reportErrors) { + // Report error in terms of object types in the target as those are the only ones + // we check in isKnownProperty. + const errorTarget = filterType(reducedTarget, isExcessPropertyCheckTarget); + // We know *exactly* where things went wrong when comparing the types. + // Use this property as the error node as this will be more helpful in + // reasoning about what went wrong. + if (!errorNode) return Debug.fail(); + if (isJsxAttributes(errorNode) || isJsxOpeningLikeElement(errorNode) || isJsxOpeningLikeElement(errorNode.parent)) { + // JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal. + // However, using an object-literal error message will be very confusing to the users so we give different a message. + if (prop.valueDeclaration && isJsxAttribute(prop.valueDeclaration) && getSourceFileOfNode(errorNode) === getSourceFileOfNode(prop.valueDeclaration.name)) { + // Note that extraneous children (as in `extra`) don't pass this check, + // since `children` is a SyntaxKind.PropertySignature instead of a SyntaxKind.JsxAttribute. + errorNode = prop.valueDeclaration.name; + } + const propName = symbolToString(prop); + const suggestionSymbol = getSuggestedSymbolForNonexistentJSXAttribute(propName, errorTarget); + const suggestion = suggestionSymbol ? symbolToString(suggestionSymbol) : undefined; + if (suggestion) { + reportError(Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName, typeToString(errorTarget), suggestion); } else { - // use the property's value declaration if the property is assigned inside the literal itself - const objectLiteralDeclaration = source.symbol?.declarations && firstOrUndefined(source.symbol.declarations); - let suggestion: string | undefined; - if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) { - const propDeclaration = prop.valueDeclaration as ObjectLiteralElementLike; - Debug.assertNode(propDeclaration, isObjectLiteralElementLike); - - errorNode = propDeclaration; - - const name = propDeclaration.name!; - if (isIdentifier(name)) { - suggestion = getSuggestionForNonexistentProperty(name, errorTarget); - } - } - if (suggestion !== undefined) { - reportError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2, - symbolToString(prop), typeToString(errorTarget), suggestion); - } - else { - reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, - symbolToString(prop), typeToString(errorTarget)); - } + reportError(Diagnostics.Property_0_does_not_exist_on_type_1, propName, typeToString(errorTarget)); } } - return true; - } - if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), RecursionFlags.Both, reportErrors)) { - if (reportErrors) { - reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(prop)); + else { + // use the property's value declaration if the property is assigned inside the literal itself + const objectLiteralDeclaration = source.symbol?.declarations && firstOrUndefined(source.symbol.declarations); + let suggestion: string | undefined; + if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) { + const propDeclaration = prop.valueDeclaration as ObjectLiteralElementLike; + Debug.assertNode(propDeclaration, isObjectLiteralElementLike); + + errorNode = propDeclaration; + + const name = propDeclaration.name!; + if (isIdentifier(name)) { + suggestion = getSuggestionForNonexistentProperty(name, errorTarget); + } + } + if (suggestion !== undefined) { + reportError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2, + symbolToString(prop), typeToString(errorTarget), suggestion); + } + else { + reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, + symbolToString(prop), typeToString(errorTarget)); + } } - return true; } + return true; + } + if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), RecursionFlags.Both, reportErrors)) { + if (reportErrors) { + reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(prop)); + } + return true; } } - return false; } + return false; + } - function shouldCheckAsExcessProperty(prop: Symbol, container: Symbol) { - return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; - } + function shouldCheckAsExcessProperty(prop: Symbol, container: Symbol) { + return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; + } - function unionOrIntersectionRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - // Note that these checks are specifically ordered to produce correct results. In particular, - // we need to deconstruct unions before intersections (because unions are always at the top), - // and we need to handle "each" relations before "some" relations for the same kind of type. - if (source.flags & TypeFlags.Union) { - return relation === comparableRelation ? - someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) : - eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState); - } - if (target.flags & TypeFlags.Union) { - return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target as UnionType, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); - } - if (target.flags & TypeFlags.Intersection) { - return typeRelatedToEachType(source, target as IntersectionType, reportErrors, IntersectionState.Target); - } - // Source is an intersection. For the comparable relation, if the target is a primitive type we hoist the - // constraints of all non-primitive types in the source into a new intersection. We do this because the - // intersection may further constrain the constraints of the non-primitive types. For example, given a type - // parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't - // appear to be comparable to '2'. - if (relation === comparableRelation && target.flags & TypeFlags.Primitive) { - const constraints = sameMap((source as IntersectionType).types, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(t) || unknownType : t); - if (constraints !== (source as IntersectionType).types) { - source = getIntersectionType(constraints); - if (source.flags & TypeFlags.Never) { - return Ternary.False; - } - if (!(source.flags & TypeFlags.Intersection)) { - return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false) || - isRelatedTo(target, source, RecursionFlags.Source, /*reportErrors*/ false); - } + function unionOrIntersectionRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + // Note that these checks are specifically ordered to produce correct results. In particular, + // we need to deconstruct unions before intersections (because unions are always at the top), + // and we need to handle "each" relations before "some" relations for the same kind of type. + if (source.flags & TypeFlags.Union) { + return relation === comparableRelation ? + someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) : + eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState); + } + if (target.flags & TypeFlags.Union) { + return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target as UnionType, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); + } + if (target.flags & TypeFlags.Intersection) { + return typeRelatedToEachType(source, target as IntersectionType, reportErrors, IntersectionState.Target); + } + // Source is an intersection. For the comparable relation, if the target is a primitive type we hoist the + // constraints of all non-primitive types in the source into a new intersection. We do this because the + // intersection may further constrain the constraints of the non-primitive types. For example, given a type + // parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't + // appear to be comparable to '2'. + if (relation === comparableRelation && target.flags & TypeFlags.Primitive) { + const constraints = sameMap((source as IntersectionType).types, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(t) || unknownType : t); + if (constraints !== (source as IntersectionType).types) { + source = getIntersectionType(constraints); + if (source.flags & TypeFlags.Never) { + return Ternary.False; + } + if (!(source.flags & TypeFlags.Intersection)) { + return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false) || + isRelatedTo(target, source, RecursionFlags.Source, /*reportErrors*/ false); } } - // Check to see if any constituents of the intersection are immediately related to the target. - // Don't report errors though. Elaborating on whether a source constituent is related to the target is - // not actually useful and leads to some confusing error messages. Instead, we rely on the caller - // checking whether the full intersection viewed as an object is related to the target. - return someTypeRelatedToType(source as IntersectionType, target, /*reportErrors*/ false, IntersectionState.Source); } + // Check to see if any constituents of the intersection are immediately related to the target. + // Don't report errors though. Elaborating on whether a source constituent is related to the target is + // not actually useful and leads to some confusing error messages. Instead, we rely on the caller + // checking whether the full intersection viewed as an object is related to the target. + return someTypeRelatedToType(source as IntersectionType, target, /*reportErrors*/ false, IntersectionState.Source); + } - function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary { - let result = Ternary.True; - const sourceTypes = source.types; - for (const sourceType of sourceTypes) { - const related = typeRelatedToSomeType(sourceType, target, /*reportErrors*/ false); - if (!related) { - return Ternary.False; - } - result &= related; + function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary { + let result = Ternary.True; + const sourceTypes = source.types; + for (const sourceType of sourceTypes) { + const related = typeRelatedToSomeType(sourceType, target, /*reportErrors*/ false); + if (!related) { + return Ternary.False; } - return result; + result &= related; } + return result; + } - function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { - const targetTypes = target.types; - if (target.flags & TypeFlags.Union) { - if (containsType(targetTypes, source)) { - return Ternary.True; - } - const match = getMatchingUnionConstituentForType(target as UnionType, source); - if (match) { - const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false); - if (related) { - return related; - } - } + function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { + const targetTypes = target.types; + if (target.flags & TypeFlags.Union) { + if (containsType(targetTypes, source)) { + return Ternary.True; } - for (const type of targetTypes) { - const related = isRelatedTo(source, type, RecursionFlags.Target, /*reportErrors*/ false); + const match = getMatchingUnionConstituentForType(target as UnionType, source); + if (match) { + const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false); if (related) { return related; } } - if (reportErrors) { - // Elaborate only if we can find a best matching type in the target union - const bestMatchingType = getBestMatchingType(source, target, isRelatedTo); - if (bestMatchingType) { - isRelatedTo(source, bestMatchingType, RecursionFlags.Target, /*reportErrors*/ true); - } + } + for (const type of targetTypes) { + const related = isRelatedTo(source, type, RecursionFlags.Target, /*reportErrors*/ false); + if (related) { + return related; + } + } + if (reportErrors) { + // Elaborate only if we can find a best matching type in the target union + const bestMatchingType = getBestMatchingType(source, target, isRelatedTo); + if (bestMatchingType) { + isRelatedTo(source, bestMatchingType, RecursionFlags.Target, /*reportErrors*/ true); } - return Ternary.False; } + return Ternary.False; + } - function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - let result = Ternary.True; - const targetTypes = target.types; - for (const targetType of targetTypes) { - const related = isRelatedTo(source, targetType, RecursionFlags.Target, reportErrors, /*headMessage*/ undefined, intersectionState); - if (!related) { - return Ternary.False; - } - result &= related; + function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + let result = Ternary.True; + const targetTypes = target.types; + for (const targetType of targetTypes) { + const related = isRelatedTo(source, targetType, RecursionFlags.Target, reportErrors, /*headMessage*/ undefined, intersectionState); + if (!related) { + return Ternary.False; } - return result; + result &= related; } + return result; + } - function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - const sourceTypes = source.types; - if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) { - return Ternary.True; + function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + const sourceTypes = source.types; + if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) { + return Ternary.True; + } + const len = sourceTypes.length; + for (let i = 0; i < len; i++) { + const related = isRelatedTo(sourceTypes[i], target, RecursionFlags.Source, reportErrors && i === len - 1, /*headMessage*/ undefined, intersectionState); + if (related) { + return related; } - const len = sourceTypes.length; - for (let i = 0; i < len; i++) { - const related = isRelatedTo(sourceTypes[i], target, RecursionFlags.Source, reportErrors && i === len - 1, /*headMessage*/ undefined, intersectionState); + } + return Ternary.False; + } + + function getUndefinedStrippedTargetIfNeeded(source: Type, target: Type) { + // As a builtin type, `undefined` is a very low type ID - making it almsot always first, making this a very fast check to see + // if we need to strip `undefined` from the target + if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union && + !((source as UnionType).types[0].flags & TypeFlags.Undefined) && (target as UnionType).types[0].flags & TypeFlags.Undefined) { + return extractTypesOfKind(target, ~TypeFlags.Undefined); + } + return target; + } + + function eachTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + let result = Ternary.True; + const sourceTypes = source.types; + // We strip `undefined` from the target if the `source` trivially doesn't contain it for our correspondence-checking fastpath + // since `undefined` is frequently added by optionality and would otherwise spoil a potentially useful correspondence + const undefinedStrippedTarget = getUndefinedStrippedTargetIfNeeded(source, target as UnionType); + for (let i = 0; i < sourceTypes.length; i++) { + const sourceType = sourceTypes[i]; + if (undefinedStrippedTarget.flags & TypeFlags.Union && sourceTypes.length >= (undefinedStrippedTarget as UnionType).types.length && sourceTypes.length % (undefinedStrippedTarget as UnionType).types.length === 0) { + // many unions are mappings of one another; in such cases, simply comparing members at the same index can shortcut the comparison + // such unions will have identical lengths, and their corresponding elements will match up. Another common scenario is where a large + // union has a union of objects intersected with it. In such cases, if the input was, eg `("a" | "b" | "c") & (string | boolean | {} | {whatever})`, + // the result will have the structure `"a" | "b" | "c" | "a" & {} | "b" & {} | "c" & {} | "a" & {whatever} | "b" & {whatever} | "c" & {whatever}` + // - the resulting union has a length which is a multiple of the original union, and the elements correspond modulo the length of the original union + const related = isRelatedTo(sourceType, (undefinedStrippedTarget as UnionType).types[i % (undefinedStrippedTarget as UnionType).types.length], RecursionFlags.Both, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); if (related) { - return related; + result &= related; + continue; } } - return Ternary.False; - } - - function getUndefinedStrippedTargetIfNeeded(source: Type, target: Type) { - // As a builtin type, `undefined` is a very low type ID - making it almsot always first, making this a very fast check to see - // if we need to strip `undefined` from the target - if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union && - !((source as UnionType).types[0].flags & TypeFlags.Undefined) && (target as UnionType).types[0].flags & TypeFlags.Undefined) { - return extractTypesOfKind(target, ~TypeFlags.Undefined); + const related = isRelatedTo(sourceType, target, RecursionFlags.Source, reportErrors, /*headMessage*/ undefined, intersectionState); + if (!related) { + return Ternary.False; } - return target; + result &= related; } + return result; + } - function eachTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - let result = Ternary.True; - const sourceTypes = source.types; - // We strip `undefined` from the target if the `source` trivially doesn't contain it for our correspondence-checking fastpath - // since `undefined` is frequently added by optionality and would otherwise spoil a potentially useful correspondence - const undefinedStrippedTarget = getUndefinedStrippedTargetIfNeeded(source, target as UnionType); - for (let i = 0; i < sourceTypes.length; i++) { - const sourceType = sourceTypes[i]; - if (undefinedStrippedTarget.flags & TypeFlags.Union && sourceTypes.length >= (undefinedStrippedTarget as UnionType).types.length && sourceTypes.length % (undefinedStrippedTarget as UnionType).types.length === 0) { - // many unions are mappings of one another; in such cases, simply comparing members at the same index can shortcut the comparison - // such unions will have identical lengths, and their corresponding elements will match up. Another common scenario is where a large - // union has a union of objects intersected with it. In such cases, if the input was, eg `("a" | "b" | "c") & (string | boolean | {} | {whatever})`, - // the result will have the structure `"a" | "b" | "c" | "a" & {} | "b" & {} | "c" & {} | "a" & {whatever} | "b" & {whatever} | "c" & {whatever}` - // - the resulting union has a length which is a multiple of the original union, and the elements correspond modulo the length of the original union - const related = isRelatedTo(sourceType, (undefinedStrippedTarget as UnionType).types[i % (undefinedStrippedTarget as UnionType).types.length], RecursionFlags.Both, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + function typeArgumentsRelatedTo(sources: readonly Type[] = emptyArray, targets: readonly Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + if (sources.length !== targets.length && relation === identityRelation) { + return Ternary.False; + } + const length = sources.length <= targets.length ? sources.length : targets.length; + let result = Ternary.True; + for (let i = 0; i < length; i++) { + // When variance information isn't available we default to covariance. This happens + // in the process of computing variance information for recursive types and when + // comparing 'this' type arguments. + const varianceFlags = i < variances.length ? variances[i] : VarianceFlags.Covariant; + const variance = varianceFlags & VarianceFlags.VarianceMask; + // We ignore arguments for independent type parameters (because they're never witnessed). + if (variance !== VarianceFlags.Independent) { + const s = sources[i]; + const t = targets[i]; + let related = Ternary.True; + if (varianceFlags & VarianceFlags.Unmeasurable) { + // Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_. + // We can't simply assume invariance, because `Unmeasurable` marks nonlinear relations, for example, a relation tained by + // the `-?` modifier in a mapped type (where, no matter how the inputs are related, the outputs still might not be) + related = relation === identityRelation ? isRelatedTo(s, t, RecursionFlags.Both, /*reportErrors*/ false) : compareTypesIdentical(s, t); + } + else if (variance === VarianceFlags.Covariant) { + related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } + else if (variance === VarianceFlags.Contravariant) { + related = isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } + else if (variance === VarianceFlags.Bivariant) { + // In the bivariant case we first compare contravariantly without reporting + // errors. Then, if that doesn't succeed, we compare covariantly with error + // reporting. Thus, error elaboration will be based on the the covariant check, + // which is generally easier to reason about. + related = isRelatedTo(t, s, RecursionFlags.Both, /*reportErrors*/ false); + if (!related) { + related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } + } + else { + // In the invariant case we first compare covariantly, and only when that + // succeeds do we proceed to compare contravariantly. Thus, error elaboration + // will typically be based on the covariant check. + related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); if (related) { - result &= related; - continue; + related &= isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); } } - const related = isRelatedTo(sourceType, target, RecursionFlags.Source, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related) { return Ternary.False; } result &= related; } - return result; } + return result; + } - function typeArgumentsRelatedTo(sources: readonly Type[] = emptyArray, targets: readonly Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - if (sources.length !== targets.length && relation === identityRelation) { - return Ternary.False; + // 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 + // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion + // and issue an error. Otherwise, actually compare the structure of the two types. + function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, recursionFlags: RecursionFlags): Ternary { + if (overflow) { + return Ternary.False; + } + const id = getRelationKey(source, target, intersectionState, relation, /*ingnoreConstraints*/ false); + const entry = relation.get(id); + if (entry !== undefined) { + if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) { + // We are elaborating errors and the cached result is an unreported failure. The result will be reported + // as a failure, and should be updated as a reported failure by the bottom of this function. } - const length = sources.length <= targets.length ? sources.length : targets.length; - let result = Ternary.True; - for (let i = 0; i < length; i++) { - // When variance information isn't available we default to covariance. This happens - // in the process of computing variance information for recursive types and when - // comparing 'this' type arguments. - const varianceFlags = i < variances.length ? variances[i] : VarianceFlags.Covariant; - const variance = varianceFlags & VarianceFlags.VarianceMask; - // We ignore arguments for independent type parameters (because they're never witnessed). - if (variance !== VarianceFlags.Independent) { - const s = sources[i]; - const t = targets[i]; - let related = Ternary.True; - if (varianceFlags & VarianceFlags.Unmeasurable) { - // Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_. - // We can't simply assume invariance, because `Unmeasurable` marks nonlinear relations, for example, a relation tained by - // the `-?` modifier in a mapped type (where, no matter how the inputs are related, the outputs still might not be) - related = relation === identityRelation ? isRelatedTo(s, t, RecursionFlags.Both, /*reportErrors*/ false) : compareTypesIdentical(s, t); - } - else if (variance === VarianceFlags.Covariant) { - related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - } - else if (variance === VarianceFlags.Contravariant) { - related = isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - } - else if (variance === VarianceFlags.Bivariant) { - // In the bivariant case we first compare contravariantly without reporting - // errors. Then, if that doesn't succeed, we compare covariantly with error - // reporting. Thus, error elaboration will be based on the the covariant check, - // which is generally easier to reason about. - related = isRelatedTo(t, s, RecursionFlags.Both, /*reportErrors*/ false); - if (!related) { - related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - } - } - else { - // In the invariant case we first compare covariantly, and only when that - // succeeds do we proceed to compare contravariantly. Thus, error elaboration - // will typically be based on the covariant check. - related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - if (related) { - related &= isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - } + else { + if (outofbandVarianceMarkerHandler) { + // We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component + const saved = entry & RelationComparisonResult.ReportsMask; + if (saved & RelationComparisonResult.ReportsUnmeasurable) { + instantiateType(source, reportUnmeasurableMapper); } - if (!related) { - return Ternary.False; + if (saved & RelationComparisonResult.ReportsUnreliable) { + instantiateType(source, reportUnreliableMapper); } - result &= related; } + return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False; } - return result; } - - // 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 - // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion - // and issue an error. Otherwise, actually compare the structure of the two types. - function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, recursionFlags: RecursionFlags): Ternary { - if (overflow) { - return Ternary.False; - } - const id = getRelationKey(source, target, intersectionState, relation, /*ingnoreConstraints*/ false); - const entry = relation.get(id); - if (entry !== undefined) { - if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) { - // We are elaborating errors and the cached result is an unreported failure. The result will be reported - // 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 - const saved = entry & RelationComparisonResult.ReportsMask; - if (saved & RelationComparisonResult.ReportsUnmeasurable) { - instantiateType(source, reportUnmeasurableMapper); - } - if (saved & RelationComparisonResult.ReportsUnreliable) { - instantiateType(source, reportUnreliableMapper); - } - } - return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False; + if (!maybeKeys) { + maybeKeys = []; + sourceStack = []; + targetStack = []; + } + else { + // A key that starts with "*" is an indication that we have type references that reference constrained + // type parameters. For such keys we also check against the key we would have gotten if all type parameters + // were unconstrained. + const broadestEquivalentId = id.startsWith("*") ? getRelationKey(source, target, intersectionState, relation, /*ignoreConstraints*/ true) : undefined; + for (let i = 0; i < maybeCount; i++) { + // If source and target are already being compared, consider them related with assumptions + if (id === maybeKeys[i] || broadestEquivalentId && broadestEquivalentId === maybeKeys[i]) { + return Ternary.Maybe; } } - if (!maybeKeys) { - maybeKeys = []; - sourceStack = []; - targetStack = []; + if (sourceDepth === 100 || targetDepth === 100) { + overflow = true; + return Ternary.False; } - else { - // A key that starts with "*" is an indication that we have type references that reference constrained - // type parameters. For such keys we also check against the key we would have gotten if all type parameters - // were unconstrained. - const broadestEquivalentId = id.startsWith("*") ? getRelationKey(source, target, intersectionState, relation, /*ignoreConstraints*/ true) : undefined; - for (let i = 0; i < maybeCount; i++) { - // If source and target are already being compared, consider them related with assumptions - if (id === maybeKeys[i] || broadestEquivalentId && broadestEquivalentId === maybeKeys[i]) { - return Ternary.Maybe; - } - } - if (sourceDepth === 100 || targetDepth === 100) { - overflow = true; - return Ternary.False; + } + const maybeStart = maybeCount; + maybeKeys[maybeCount] = id; + maybeCount++; + const saveExpandingFlags = expandingFlags; + if (recursionFlags & RecursionFlags.Source) { + sourceStack[sourceDepth] = source; + sourceDepth++; + if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, sourceDepth)) expandingFlags |= ExpandingFlags.Source; + } + if (recursionFlags & RecursionFlags.Target) { + targetStack[targetDepth] = target; + targetDepth++; + if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, targetDepth)) expandingFlags |= ExpandingFlags.Target; + } + let originalHandler: typeof outofbandVarianceMarkerHandler; + let propagatingVarianceFlags: RelationComparisonResult = 0; + if (outofbandVarianceMarkerHandler) { + originalHandler = outofbandVarianceMarkerHandler; + outofbandVarianceMarkerHandler = onlyUnreliable => { + propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable; + return originalHandler!(onlyUnreliable); + }; + } + + let result: Ternary; + if (expandingFlags === ExpandingFlags.Both) { + tracing?.instant(tracing.Phase.CheckTypes, "recursiveTypeRelatedTo_DepthLimit", { + sourceId: source.id, + sourceIdStack: sourceStack.map(t => t.id), + targetId: target.id, + targetIdStack: targetStack.map(t => t.id), + depth: sourceDepth, + targetDepth + }); + result = Ternary.Maybe; + } + else { + tracing?.push(tracing.Phase.CheckTypes, "structuredTypeRelatedTo", { sourceId: source.id, targetId: target.id }); + result = structuredTypeRelatedTo(source, target, reportErrors, intersectionState); + tracing?.pop(); + } + + if (outofbandVarianceMarkerHandler) { + outofbandVarianceMarkerHandler = originalHandler; + } + if (recursionFlags & RecursionFlags.Source) { + sourceDepth--; + } + if (recursionFlags & RecursionFlags.Target) { + targetDepth--; + } + expandingFlags = saveExpandingFlags; + if (result) { + if (result === Ternary.True || (sourceDepth === 0 && targetDepth === 0)) { + if (result === Ternary.True || result === Ternary.Maybe) { + // If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe + // results as having succeeded once we reach depth 0, but never record Ternary.Unknown results. + for (let i = maybeStart; i < maybeCount; i++) { + relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags); + } } + maybeCount = maybeStart; } - const maybeStart = maybeCount; - maybeKeys[maybeCount] = id; - maybeCount++; - const saveExpandingFlags = expandingFlags; - if (recursionFlags & RecursionFlags.Source) { - sourceStack[sourceDepth] = source; - sourceDepth++; - if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, sourceDepth)) expandingFlags |= ExpandingFlags.Source; - } - if (recursionFlags & RecursionFlags.Target) { - targetStack[targetDepth] = target; - targetDepth++; - if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, targetDepth)) expandingFlags |= ExpandingFlags.Target; - } - let originalHandler: typeof outofbandVarianceMarkerHandler; - let propagatingVarianceFlags: RelationComparisonResult = 0; - if (outofbandVarianceMarkerHandler) { - originalHandler = outofbandVarianceMarkerHandler; - outofbandVarianceMarkerHandler = onlyUnreliable => { - propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable; - return originalHandler!(onlyUnreliable); - }; - } + } + else { + // A false result goes straight into global cache (when something is false under + // assumptions it will also be false without assumptions) + relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags); + maybeCount = maybeStart; + } + return result; + } - let result: Ternary; - if (expandingFlags === ExpandingFlags.Both) { - tracing?.instant(tracing.Phase.CheckTypes, "recursiveTypeRelatedTo_DepthLimit", { - sourceId: source.id, - sourceIdStack: sourceStack.map(t => t.id), - targetId: target.id, - targetIdStack: targetStack.map(t => t.id), - depth: sourceDepth, - targetDepth - }); - result = Ternary.Maybe; - } - else { - tracing?.push(tracing.Phase.CheckTypes, "structuredTypeRelatedTo", { sourceId: source.id, targetId: target.id }); - result = structuredTypeRelatedTo(source, target, reportErrors, intersectionState); - tracing?.pop(); + function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + const saveErrorInfo = captureErrorCalculationState(); + let result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState, saveErrorInfo); + if (relation !== identityRelation) { + // The combined constraint of an intersection type is the intersection of the constraints of + // the constituents. When an intersection type contains instantiable types with union type + // constraints, there are situations where we need to examine the combined constraint. One is + // when the target is a union type. Another is when the intersection contains types belonging + // to one of the disjoint domains. For example, given type variables T and U, each with the + // constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and + // we need to check this constraint against a union on the target side. Also, given a type + // variable V constrained to 'string | number', 'V & number' has a combined constraint of + // 'string & number | number & number' which reduces to just 'number'. + // This also handles type parameters, as a type parameter with a union constraint compared against a union + // needs to have its constraint hoisted into an intersection with said type parameter, this way + // the type param can be compared with itself in the target (with the influence of its constraint to match other parts) + // For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)` + if (!result && (source.flags & TypeFlags.Intersection || source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.Union)) { + const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union)); + if (constraint && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself + // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this + result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + } + } + // For certain combinations involving intersections and optional, excess, or mismatched properties we need + // an extra property check where the intersection is viewed as a single object. The following are motivating + // examples that all should be errors, but aren't without this extra property check: + // + // let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property + // + // declare let wrong: { a: { y: string } }; + // let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type + // + // function foo(x: { a?: string }, y: T & { a: boolean }) { + // x = y; // Mismatched property in source intersection + // } + // + // We suppress recursive intersection property checks because they can generate lots of work when relating + // recursive intersections that are structurally similar but not exactly identical. See #37854. + if (result && !inPropertyCheck && ( + target.flags & TypeFlags.Intersection && !isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection) || + isNonGenericObjectType(target) && !isArrayOrTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { + inPropertyCheck = true; + result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None); + inPropertyCheck = false; } + } + if (result) { + resetErrorInfo(saveErrorInfo); + } + return result; + } - if (outofbandVarianceMarkerHandler) { - outofbandVarianceMarkerHandler = originalHandler; + function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, saveErrorInfo: ReturnType): Ternary { + let result: Ternary; + let originalErrorInfo: DiagnosticMessageChain | undefined; + let varianceCheckFailed = false; + let sourceFlags = source.flags; + const targetFlags = target.flags; + if (relation === identityRelation) { + // We've already checked that source.flags and target.flags are identical + if (sourceFlags & TypeFlags.UnionOrIntersection) { + let result = eachTypeRelatedToSomeType(source as UnionOrIntersectionType, target as UnionOrIntersectionType); + if (result) { + result &= eachTypeRelatedToSomeType(target as UnionOrIntersectionType, source as UnionOrIntersectionType); + } + return result; } - if (recursionFlags & RecursionFlags.Source) { - sourceDepth--; + if (sourceFlags & TypeFlags.Index) { + return isRelatedTo((source as IndexType).type, (target as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false); } - if (recursionFlags & RecursionFlags.Target) { - targetDepth--; + if (sourceFlags & TypeFlags.IndexedAccess) { + if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, /*reportErrors*/ false)) { + if (result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, /*reportErrors*/ false)) { + return result; + } + } } - expandingFlags = saveExpandingFlags; - if (result) { - if (result === Ternary.True || (sourceDepth === 0 && targetDepth === 0)) { - if (result === Ternary.True || result === Ternary.Maybe) { - // If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe - // results as having succeeded once we reach depth 0, but never record Ternary.Unknown results. - for (let i = maybeStart; i < maybeCount; i++) { - relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags); + if (sourceFlags & TypeFlags.Conditional) { + if ((source as ConditionalType).root.isDistributive === (target as ConditionalType).root.isDistributive) { + if (result = isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both, /*reportErrors*/ false)) { + if (result &= isRelatedTo((source as ConditionalType).extendsType, (target as ConditionalType).extendsType, RecursionFlags.Both, /*reportErrors*/ false)) { + if (result &= isRelatedTo(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) { + if (result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) { + return result; + } + } } } - maybeCount = maybeStart; } } - else { - // A false result goes straight into global cache (when something is false under - // assumptions it will also be false without assumptions) - relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags); - maybeCount = maybeStart; + if (sourceFlags & TypeFlags.Substitution) { + if (result = isRelatedTo((source as SubstitutionType).baseType, (target as SubstitutionType).baseType, RecursionFlags.Both, /*reportErrors*/ false)) { + if (result &= isRelatedTo((source as SubstitutionType).constraint, (target as SubstitutionType).constraint, RecursionFlags.Both, /*reportErrors*/ false)) { + return result; + } + } + } + if (!(sourceFlags & TypeFlags.Object)) { + return Ternary.False; + } + } + else if (sourceFlags & TypeFlags.UnionOrIntersection || targetFlags & TypeFlags.UnionOrIntersection) { + if (result = unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState)) { + return result; + } + // The ordered decomposition above doesn't handle all cases. Specifically, we also need to handle: + // Source is instantiable (e.g. source has union or intersection constraint). + // Source is an object, target is a union (e.g. { a, b: boolean } <=> { a, b: true } | { a, b: false }). + // Source is an intersection, target is an object (e.g. { a } & { b } <=> { a, b }). + // Source is an intersection, target is a union (e.g. { a } & { b: boolean } <=> { a, b: true } | { a, b: false }). + // Source is an intersection, target instantiable (e.g. string & { tag } <=> T["a"] constrained to string & { tag }). + if (!(sourceFlags & TypeFlags.Instantiable || + sourceFlags & TypeFlags.Object && targetFlags & TypeFlags.Union || + sourceFlags & TypeFlags.Intersection && targetFlags & (TypeFlags.Object | TypeFlags.Union | TypeFlags.Instantiable))) { + return Ternary.False; } - return result; } - function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - const saveErrorInfo = captureErrorCalculationState(); - let result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState, saveErrorInfo); - if (relation !== identityRelation) { - // The combined constraint of an intersection type is the intersection of the constraints of - // the constituents. When an intersection type contains instantiable types with union type - // constraints, there are situations where we need to examine the combined constraint. One is - // when the target is a union type. Another is when the intersection contains types belonging - // to one of the disjoint domains. For example, given type variables T and U, each with the - // constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and - // we need to check this constraint against a union on the target side. Also, given a type - // variable V constrained to 'string | number', 'V & number' has a combined constraint of - // 'string & number | number & number' which reduces to just 'number'. - // This also handles type parameters, as a type parameter with a union constraint compared against a union - // needs to have its constraint hoisted into an intersection with said type parameter, this way - // the type param can be compared with itself in the target (with the influence of its constraint to match other parts) - // For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)` - if (!result && (source.flags & TypeFlags.Intersection || source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.Union)) { - const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union)); - if (constraint && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself - // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this - result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); - } - } - // For certain combinations involving intersections and optional, excess, or mismatched properties we need - // an extra property check where the intersection is viewed as a single object. The following are motivating - // examples that all should be errors, but aren't without this extra property check: - // - // let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property - // - // declare let wrong: { a: { y: string } }; - // let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type - // - // function foo(x: { a?: string }, y: T & { a: boolean }) { - // x = y; // Mismatched property in source intersection - // } - // - // We suppress recursive intersection property checks because they can generate lots of work when relating - // recursive intersections that are structurally similar but not exactly identical. See #37854. - if (result && !inPropertyCheck && ( - target.flags & TypeFlags.Intersection && !isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection) || - isNonGenericObjectType(target) && !isArrayOrTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { - inPropertyCheck = true; - result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None); - inPropertyCheck = false; - } + // We limit alias variance probing to only object and conditional types since their alias behavior + // is more predictable than other, interned types, which may or may not have an alias depending on + // the order in which things were checked. + if (sourceFlags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && source.aliasTypeArguments && + source.aliasSymbol === target.aliasSymbol && !(isMarkerType(source) || isMarkerType(target))) { + const variances = getAliasVariances(source.aliasSymbol); + if (variances === emptyArray) { + return Ternary.Unknown; } - if (result) { - resetErrorInfo(saveErrorInfo); + const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState); + if (varianceResult !== undefined) { + return varianceResult; } + } + + // For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T], + // and U is assignable to [...T] when U is constrained to a mutable array or tuple type. + if (isSingleElementGenericTupleType(source) && !source.target.readonly && (result = isRelatedTo(getTypeArguments(source)[0], target, RecursionFlags.Source)) || + isSingleElementGenericTupleType(target) && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source)) && (result = isRelatedTo(source, getTypeArguments(target)[0], RecursionFlags.Target))) { return result; } - function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, saveErrorInfo: ReturnType): Ternary { - let result: Ternary; - let originalErrorInfo: DiagnosticMessageChain | undefined; - let varianceCheckFailed = false; - let sourceFlags = source.flags; - const targetFlags = target.flags; - if (relation === identityRelation) { - // We've already checked that source.flags and target.flags are identical - if (sourceFlags & TypeFlags.UnionOrIntersection) { - let result = eachTypeRelatedToSomeType(source as UnionOrIntersectionType, target as UnionOrIntersectionType); - if (result) { - result &= eachTypeRelatedToSomeType(target as UnionOrIntersectionType, source as UnionOrIntersectionType); - } - return result; - } - if (sourceFlags & TypeFlags.Index) { - return isRelatedTo((source as IndexType).type, (target as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false); - } - if (sourceFlags & TypeFlags.IndexedAccess) { - if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, /*reportErrors*/ false)) { - if (result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, /*reportErrors*/ false)) { - return result; - } - } - } - if (sourceFlags & TypeFlags.Conditional) { - if ((source as ConditionalType).root.isDistributive === (target as ConditionalType).root.isDistributive) { - if (result = isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both, /*reportErrors*/ false)) { - if (result &= isRelatedTo((source as ConditionalType).extendsType, (target as ConditionalType).extendsType, RecursionFlags.Both, /*reportErrors*/ false)) { - if (result &= isRelatedTo(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) { - if (result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, /*reportErrors*/ false)) { - return result; - } - } - } - } + if (targetFlags & TypeFlags.TypeParameter) { + // A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q]. + if (getObjectFlags(source) & ObjectFlags.Mapped && !(source as MappedType).declaration.nameType && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(source as MappedType), RecursionFlags.Both)) { + + if (!(getMappedTypeModifiers(source as MappedType) & MappedTypeModifiers.IncludeOptional)) { + const templateType = getTemplateTypeFromMappedType(source as MappedType); + const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source as MappedType)); + if (result = isRelatedTo(templateType, indexedAccessType, RecursionFlags.Both, reportErrors)) { + return result; } } - if (sourceFlags & TypeFlags.Substitution) { - if (result = isRelatedTo((source as SubstitutionType).baseType, (target as SubstitutionType).baseType, RecursionFlags.Both, /*reportErrors*/ false)) { - if (result &= isRelatedTo((source as SubstitutionType).constraint, (target as SubstitutionType).constraint, RecursionFlags.Both, /*reportErrors*/ false)) { + } + if (relation === comparableRelation && sourceFlags & TypeFlags.TypeParameter) { + // This is a carve-out in comparability to essentially forbid comparing a type parameter + // with another type parameter unless one extends the other. (Remember: comparability is mostly bidirectional!) + let constraint = getConstraintOfTypeParameter(source); + if (constraint && hasNonCircularBaseConstraint(source)) { + while (constraint && someType(constraint, c => !!(c.flags & TypeFlags.TypeParameter))) { + if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false)) { return result; } + constraint = getConstraintOfTypeParameter(constraint); } } - if (!(sourceFlags & TypeFlags.Object)) { - return Ternary.False; - } + return Ternary.False; } - else if (sourceFlags & TypeFlags.UnionOrIntersection || targetFlags & TypeFlags.UnionOrIntersection) { - if (result = unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState)) { + } + else if (targetFlags & TypeFlags.Index) { + const targetType = (target as IndexType).type; + // A keyof S is related to a keyof T if T is related to S. + if (sourceFlags & TypeFlags.Index) { + if (result = isRelatedTo(targetType, (source as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false)) { return result; } - // The ordered decomposition above doesn't handle all cases. Specifically, we also need to handle: - // Source is instantiable (e.g. source has union or intersection constraint). - // Source is an object, target is a union (e.g. { a, b: boolean } <=> { a, b: true } | { a, b: false }). - // Source is an intersection, target is an object (e.g. { a } & { b } <=> { a, b }). - // Source is an intersection, target is a union (e.g. { a } & { b: boolean } <=> { a, b: true } | { a, b: false }). - // Source is an intersection, target instantiable (e.g. string & { tag } <=> T["a"] constrained to string & { tag }). - if (!(sourceFlags & TypeFlags.Instantiable || - sourceFlags & TypeFlags.Object && targetFlags & TypeFlags.Union || - sourceFlags & TypeFlags.Intersection && targetFlags & (TypeFlags.Object | TypeFlags.Union | TypeFlags.Instantiable))) { - return Ternary.False; - } } - - // We limit alias variance probing to only object and conditional types since their alias behavior - // is more predictable than other, interned types, which may or may not have an alias depending on - // the order in which things were checked. - if (sourceFlags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && source.aliasTypeArguments && - source.aliasSymbol === target.aliasSymbol && !(isMarkerType(source) || isMarkerType(target))) { - const variances = getAliasVariances(source.aliasSymbol); - if (variances === emptyArray) { - return Ternary.Unknown; - } - const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState); - if (varianceResult !== undefined) { - return varianceResult; + if (isTupleType(targetType)) { + // An index type can have a tuple type target when the tuple type contains variadic elements. + // Check if the source is related to the known keys of the tuple type. + if (result = isRelatedTo(source, getKnownKeysOfTupleType(targetType), RecursionFlags.Target, reportErrors)) { + return result; } } - - // For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T], - // and U is assignable to [...T] when U is constrained to a mutable array or tuple type. - if (isSingleElementGenericTupleType(source) && !source.target.readonly && (result = isRelatedTo(getTypeArguments(source)[0], target, RecursionFlags.Source)) || - isSingleElementGenericTupleType(target) && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source)) && (result = isRelatedTo(source, getTypeArguments(target)[0], RecursionFlags.Target))) { - return result; - } - - if (targetFlags & TypeFlags.TypeParameter) { - // A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q]. - if (getObjectFlags(source) & ObjectFlags.Mapped && !(source as MappedType).declaration.nameType && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(source as MappedType), RecursionFlags.Both)) { - - if (!(getMappedTypeModifiers(source as MappedType) & MappedTypeModifiers.IncludeOptional)) { - const templateType = getTemplateTypeFromMappedType(source as MappedType); - const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source as MappedType)); - if (result = isRelatedTo(templateType, indexedAccessType, RecursionFlags.Both, reportErrors)) { - return result; - } + else { + // 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 constraint = getSimplifiedTypeOrConstraint(targetType); + 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 }', + // 'keyof T' has itself as its constraint and produces a Ternary.Maybe when + // related to other types. + if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), RecursionFlags.Target, reportErrors) === Ternary.True) { + return Ternary.True; } } - if (relation === comparableRelation && sourceFlags & TypeFlags.TypeParameter) { - // This is a carve-out in comparability to essentially forbid comparing a type parameter - // with another type parameter unless one extends the other. (Remember: comparability is mostly bidirectional!) - let constraint = getConstraintOfTypeParameter(source); - if (constraint && hasNonCircularBaseConstraint(source)) { - while (constraint && someType(constraint, c => !!(c.flags & TypeFlags.TypeParameter))) { - if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false)) { - return result; - } - constraint = getConstraintOfTypeParameter(constraint); - } + else if (isGenericMappedType(targetType)) { + // generic mapped types that don't simplify or have a constraint still have a very simple set of keys we can compare against + // - their nameType or constraintType. + // In many ways, this comparison is a deferred version of what `getIndexTypeForMappedType` does to actually resolve the keys for _non_-generic types + + const nameType = getNameTypeFromMappedType(targetType); + const constraintType = getConstraintTypeFromMappedType(targetType); + let targetKeys; + if (nameType && isMappedTypeWithKeyofConstraintDeclaration(targetType)) { + // we need to get the apparent mappings and union them with the generic mappings, since some properties may be + // missing from the `constraintType` which will otherwise be mapped in the object + const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType)); + const mappedKeys: Type[] = []; + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType( + modifiersType, + TypeFlags.StringOrNumberLiteralOrUnique, + /*stringsOnly*/ false, + t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t))) + ); + // We still need to include the non-apparent (and thus still generic) keys in the target side of the comparison (in case they're in the source side) + targetKeys = getUnionType([...mappedKeys, nameType]); + } + else { + targetKeys = nameType || constraintType; + } + if (isRelatedTo(source, targetKeys, RecursionFlags.Target, reportErrors) === Ternary.True) { + return Ternary.True; } - return Ternary.False; } } - else if (targetFlags & TypeFlags.Index) { - const targetType = (target as IndexType).type; - // A keyof S is related to a keyof T if T is related to S. - if (sourceFlags & TypeFlags.Index) { - if (result = isRelatedTo(targetType, (source as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false)) { - return result; - } + } + else if (targetFlags & TypeFlags.IndexedAccess) { + if (sourceFlags & TypeFlags.IndexedAccess) { + // Relate components directly before falling back to constraint relationships + // A type S[K] is related to a type T[J] if S is related to T and K is related to J. + if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, reportErrors)) { + result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, reportErrors); } - if (isTupleType(targetType)) { - // An index type can have a tuple type target when the tuple type contains variadic elements. - // Check if the source is related to the known keys of the tuple type. - if (result = isRelatedTo(source, getKnownKeysOfTupleType(targetType), RecursionFlags.Target, reportErrors)) { - return result; - } + if (result) { + return result; } - else { - // 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 constraint = getSimplifiedTypeOrConstraint(targetType); + if (reportErrors) { + originalErrorInfo = errorInfo; + } + } + // A type S is related to a type T[K] if S is related to C, where C is the base + // constraint of T[K] for writing. + if (relation === assignableRelation || relation === comparableRelation) { + const objectType = (target as IndexedAccessType).objectType; + const indexType = (target as IndexedAccessType).indexType; + const baseObjectType = getBaseConstraintOfType(objectType) || objectType; + const baseIndexType = getBaseConstraintOfType(indexType) || indexType; + if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseIndexType)) { + const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0); + const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, accessFlags); 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 }', - // 'keyof T' has itself as its constraint and produces a Ternary.Maybe when - // related to other types. - if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), RecursionFlags.Target, reportErrors) === Ternary.True) { - return Ternary.True; - } - } - else if (isGenericMappedType(targetType)) { - // generic mapped types that don't simplify or have a constraint still have a very simple set of keys we can compare against - // - their nameType or constraintType. - // In many ways, this comparison is a deferred version of what `getIndexTypeForMappedType` does to actually resolve the keys for _non_-generic types - - const nameType = getNameTypeFromMappedType(targetType); - const constraintType = getConstraintTypeFromMappedType(targetType); - let targetKeys; - if (nameType && isMappedTypeWithKeyofConstraintDeclaration(targetType)) { - // we need to get the apparent mappings and union them with the generic mappings, since some properties may be - // missing from the `constraintType` which will otherwise be mapped in the object - const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType)); - const mappedKeys: Type[] = []; - forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType( - modifiersType, - TypeFlags.StringOrNumberLiteralOrUnique, - /*stringsOnly*/ false, - t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t))) - ); - // We still need to include the non-apparent (and thus still generic) keys in the target side of the comparison (in case they're in the source side) - targetKeys = getUnionType([...mappedKeys, nameType]); + if (reportErrors && originalErrorInfo) { + // create a new chain for the constraint error + resetErrorInfo(saveErrorInfo); } - else { - targetKeys = nameType || constraintType; + if (result = isRelatedTo(source, constraint, RecursionFlags.Target, reportErrors, /* headMessage */ undefined, intersectionState)) { + return result; } - if (isRelatedTo(source, targetKeys, RecursionFlags.Target, reportErrors) === Ternary.True) { - return Ternary.True; + // prefer the shorter chain of the constraint comparison chain, and the direct comparison chain + if (reportErrors && originalErrorInfo && errorInfo) { + errorInfo = countMessageChainBreadth([originalErrorInfo]) <= countMessageChainBreadth([errorInfo]) ? originalErrorInfo : errorInfo; } } } } - else if (targetFlags & TypeFlags.IndexedAccess) { - if (sourceFlags & TypeFlags.IndexedAccess) { - // Relate components directly before falling back to constraint relationships - // A type S[K] is related to a type T[J] if S is related to T and K is related to J. - if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, RecursionFlags.Both, reportErrors)) { - result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, RecursionFlags.Both, reportErrors); - } - if (result) { - return result; - } - if (reportErrors) { - originalErrorInfo = errorInfo; - } - } - // A type S is related to a type T[K] if S is related to C, where C is the base - // constraint of T[K] for writing. - if (relation === assignableRelation || relation === comparableRelation) { - const objectType = (target as IndexedAccessType).objectType; - const indexType = (target as IndexedAccessType).indexType; - const baseObjectType = getBaseConstraintOfType(objectType) || objectType; - const baseIndexType = getBaseConstraintOfType(indexType) || indexType; - if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseIndexType)) { - const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0); - const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, accessFlags); - if (constraint) { - if (reportErrors && originalErrorInfo) { - // create a new chain for the constraint error - resetErrorInfo(saveErrorInfo); - } - if (result = isRelatedTo(source, constraint, RecursionFlags.Target, reportErrors, /* headMessage */ undefined, intersectionState)) { + if (reportErrors) { + originalErrorInfo = undefined; + } + } + else if (isGenericMappedType(target) && relation !== identityRelation) { + // Check if source type `S` is related to target type `{ [P in Q]: T }` or `{ [P in Q as R]: T}`. + const keysRemapped = !!target.declaration.nameType; + const templateType = getTemplateTypeFromMappedType(target); + const modifiers = getMappedTypeModifiers(target); + if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { + // If the mapped type has shape `{ [P in Q]: T[P] }`, + // source `S` is related to target if `T` = `S`, i.e. `S` is related to `{ [P in Q]: S[P] }`. + if (!keysRemapped && templateType.flags & TypeFlags.IndexedAccess && (templateType as IndexedAccessType).objectType === source && + (templateType as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) { + return Ternary.True; + } + if (!isGenericMappedType(source)) { + // If target has shape `{ [P in Q as R]: T}`, then its keys have type `R`. + // If target has shape `{ [P in Q]: T }`, then its keys have type `Q`. + const targetKeys = keysRemapped ? getNameTypeFromMappedType(target)! : getConstraintTypeFromMappedType(target); + // Type of the keys of source type `S`, i.e. `keyof S`. + const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); + const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; + const filteredByApplicability = includeOptional ? intersectTypes(targetKeys, sourceKeys) : undefined; + // A source type `S` is related to a target type `{ [P in Q]: T }` if `Q` is related to `keyof S` and `S[Q]` is related to `T`. + // A source type `S` is related to a target type `{ [P in Q as R]: T }` if `R` is related to `keyof S` and `S[R]` is related to `T. + // A source type `S` is related to a target type `{ [P in Q]?: T }` if some constituent `Q'` of `Q` is related to `keyof S` and `S[Q']` is related to `T`. + // A source type `S` is related to a target type `{ [P in Q as R]?: T }` if some constituent `R'` of `R` is related to `keyof S` and `S[R']` is related to `T`. + if (includeOptional + ? !(filteredByApplicability!.flags & TypeFlags.Never) + : isRelatedTo(targetKeys, sourceKeys, RecursionFlags.Both)) { + const templateType = getTemplateTypeFromMappedType(target); + const typeParameter = getTypeParameterFromMappedType(target); + + // Fastpath: When the template type has the form `Obj[P]` where `P` is the mapped type parameter, directly compare source `S` with `Obj` + // to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `S[P]`. + const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable); + if (!keysRemapped && nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) { + if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, RecursionFlags.Target, reportErrors)) { return result; } - // prefer the shorter chain of the constraint comparison chain, and the direct comparison chain - if (reportErrors && originalErrorInfo && errorInfo) { - errorInfo = countMessageChainBreadth([originalErrorInfo]) <= countMessageChainBreadth([errorInfo]) ? originalErrorInfo : errorInfo; - } } - } - } - if (reportErrors) { - originalErrorInfo = undefined; - } - } - else if (isGenericMappedType(target) && relation !== identityRelation) { - // Check if source type `S` is related to target type `{ [P in Q]: T }` or `{ [P in Q as R]: T}`. - const keysRemapped = !!target.declaration.nameType; - const templateType = getTemplateTypeFromMappedType(target); - const modifiers = getMappedTypeModifiers(target); - if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { - // If the mapped type has shape `{ [P in Q]: T[P] }`, - // source `S` is related to target if `T` = `S`, i.e. `S` is related to `{ [P in Q]: S[P] }`. - if (!keysRemapped && templateType.flags & TypeFlags.IndexedAccess && (templateType as IndexedAccessType).objectType === source && - (templateType as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) { - return Ternary.True; - } - if (!isGenericMappedType(source)) { - // If target has shape `{ [P in Q as R]: T}`, then its keys have type `R`. - // If target has shape `{ [P in Q]: T }`, then its keys have type `Q`. - const targetKeys = keysRemapped ? getNameTypeFromMappedType(target)! : getConstraintTypeFromMappedType(target); - // Type of the keys of source type `S`, i.e. `keyof S`. - const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); - const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; - const filteredByApplicability = includeOptional ? intersectTypes(targetKeys, sourceKeys) : undefined; - // A source type `S` is related to a target type `{ [P in Q]: T }` if `Q` is related to `keyof S` and `S[Q]` is related to `T`. - // A source type `S` is related to a target type `{ [P in Q as R]: T }` if `R` is related to `keyof S` and `S[R]` is related to `T. - // A source type `S` is related to a target type `{ [P in Q]?: T }` if some constituent `Q'` of `Q` is related to `keyof S` and `S[Q']` is related to `T`. - // A source type `S` is related to a target type `{ [P in Q as R]?: T }` if some constituent `R'` of `R` is related to `keyof S` and `S[R']` is related to `T`. - if (includeOptional - ? !(filteredByApplicability!.flags & TypeFlags.Never) - : isRelatedTo(targetKeys, sourceKeys, RecursionFlags.Both)) { - const templateType = getTemplateTypeFromMappedType(target); - const typeParameter = getTypeParameterFromMappedType(target); - - // Fastpath: When the template type has the form `Obj[P]` where `P` is the mapped type parameter, directly compare source `S` with `Obj` - // to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `S[P]`. - const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable); - if (!keysRemapped && nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) { - if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, RecursionFlags.Target, reportErrors)) { - return result; - } - } - else { - // We need to compare the type of a property on the source type `S` to the type of the same property on the target type, - // so we need to construct an indexing type representing a property, and then use indexing type to index the source type for comparison. - - // If the target type has shape `{ [P in Q]: T }`, then a property of the target has type `P`. - // If the target type has shape `{ [P in Q]?: T }`, then a property of the target has type `P`, - // but the property is optional, so we only want to compare properties `P` that are common between `keyof S` and `Q`. - // If the target type has shape `{ [P in Q as R]: T }`, then a property of the target has type `R`. - // If the target type has shape `{ [P in Q as R]?: T }`, then a property of the target has type `R`, - // but the property is optional, so we only want to compare properties `R` that are common between `keyof S` and `R`. - const indexingType = keysRemapped - ? (filteredByApplicability || targetKeys) - : filteredByApplicability - ? getIntersectionType([filteredByApplicability, typeParameter]) - : typeParameter; - const indexedAccessType = getIndexedAccessType(source, indexingType); - // Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type. - if (result = isRelatedTo(indexedAccessType, templateType, RecursionFlags.Both, reportErrors)) { - return result; - } + else { + // We need to compare the type of a property on the source type `S` to the type of the same property on the target type, + // so we need to construct an indexing type representing a property, and then use indexing type to index the source type for comparison. + + // If the target type has shape `{ [P in Q]: T }`, then a property of the target has type `P`. + // If the target type has shape `{ [P in Q]?: T }`, then a property of the target has type `P`, + // but the property is optional, so we only want to compare properties `P` that are common between `keyof S` and `Q`. + // If the target type has shape `{ [P in Q as R]: T }`, then a property of the target has type `R`. + // If the target type has shape `{ [P in Q as R]?: T }`, then a property of the target has type `R`, + // but the property is optional, so we only want to compare properties `R` that are common between `keyof S` and `R`. + const indexingType = keysRemapped + ? (filteredByApplicability || targetKeys) + : filteredByApplicability + ? getIntersectionType([filteredByApplicability, typeParameter]) + : typeParameter; + const indexedAccessType = getIndexedAccessType(source, indexingType); + // Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type. + if (result = isRelatedTo(indexedAccessType, templateType, RecursionFlags.Both, reportErrors)) { + return result; } } - originalErrorInfo = errorInfo; - resetErrorInfo(saveErrorInfo); } + originalErrorInfo = errorInfo; + resetErrorInfo(saveErrorInfo); } } - else if (targetFlags & TypeFlags.Conditional) { - // If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive - // conditional type and bail out with a Ternary.Maybe result. - if (isDeeplyNestedType(target, targetStack, targetDepth, 10)) { - return Ternary.Maybe; - } - const c = target as ConditionalType; - // We check for a relationship to a conditional type target only when the conditional type has no - // 'infer' positions and is not distributive or is distributive but doesn't reference the check type - // parameter in either of the result types. - if (!c.root.inferTypeParameters && !isDistributionDependent(c.root)) { - // Check if the conditional is always true or always false but still deferred for distribution purposes. - const skipTrue = !isTypeAssignableTo(getPermissiveInstantiation(c.checkType), getPermissiveInstantiation(c.extendsType)); - const skipFalse = !skipTrue && isTypeAssignableTo(getRestrictiveInstantiation(c.checkType), getRestrictiveInstantiation(c.extendsType)); - // TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't) - if (result = skipTrue ? Ternary.True : isRelatedTo(source, getTrueTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { - result &= skipFalse ? Ternary.True : isRelatedTo(source, getFalseTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); - if (result) { - return result; - } + } + else if (targetFlags & TypeFlags.Conditional) { + // If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive + // conditional type and bail out with a Ternary.Maybe result. + if (isDeeplyNestedType(target, targetStack, targetDepth, 10)) { + return Ternary.Maybe; + } + const c = target as ConditionalType; + // We check for a relationship to a conditional type target only when the conditional type has no + // 'infer' positions and is not distributive or is distributive but doesn't reference the check type + // parameter in either of the result types. + if (!c.root.inferTypeParameters && !isDistributionDependent(c.root)) { + // Check if the conditional is always true or always false but still deferred for distribution purposes. + const skipTrue = !isTypeAssignableTo(getPermissiveInstantiation(c.checkType), getPermissiveInstantiation(c.extendsType)); + const skipFalse = !skipTrue && isTypeAssignableTo(getRestrictiveInstantiation(c.checkType), getRestrictiveInstantiation(c.extendsType)); + // TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't) + if (result = skipTrue ? Ternary.True : isRelatedTo(source, getTrueTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { + result &= skipFalse ? Ternary.True : isRelatedTo(source, getFalseTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + if (result) { + return result; } } } - else if (targetFlags & TypeFlags.TemplateLiteral) { - if (sourceFlags & TypeFlags.TemplateLiteral) { - if (relation === comparableRelation) { - return templateLiteralTypesDefinitelyUnrelated(source as TemplateLiteralType, target as TemplateLiteralType) ? Ternary.False : Ternary.True; - } - // Report unreliable variance for type variables referenced in template literal type placeholders. - // For example, `foo-${number}` is related to `foo-${string}` even though number isn't related to string. - instantiateType(source, reportUnreliableMapper); - } - if (isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)) { - return Ternary.True; + } + else if (targetFlags & TypeFlags.TemplateLiteral) { + if (sourceFlags & TypeFlags.TemplateLiteral) { + if (relation === comparableRelation) { + return templateLiteralTypesDefinitelyUnrelated(source as TemplateLiteralType, target as TemplateLiteralType) ? Ternary.False : Ternary.True; } + // Report unreliable variance for type variables referenced in template literal type placeholders. + // For example, `foo-${number}` is related to `foo-${string}` even though number isn't related to string. + instantiateType(source, reportUnreliableMapper); } - else if (target.flags & TypeFlags.StringMapping) { - if (!(source.flags & TypeFlags.StringMapping)) { - if (isMemberOfStringMapping(source, target)) { - return Ternary.True; - } + if (isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)) { + return Ternary.True; + } + } + else if (target.flags & TypeFlags.StringMapping) { + if (!(source.flags & TypeFlags.StringMapping)) { + if (isMemberOfStringMapping(source, target)) { + return Ternary.True; } } + } - if (sourceFlags & TypeFlags.TypeVariable) { - // IndexedAccess comparisons are handled above in the `targetFlags & TypeFlage.IndexedAccess` branch - if (!(sourceFlags & TypeFlags.IndexedAccess && targetFlags & TypeFlags.IndexedAccess)) { - const constraint = getConstraintOfType(source as TypeVariable) || unknownType; - // hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed - if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { - return result; - } - // slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example - else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && constraint !== unknownType && !(targetFlags & sourceFlags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) { - return result; - } - if (isMappedTypeGenericIndexedAccess(source)) { - // For an indexed access type { [P in K]: E}[X], above we have already explored an instantiation of E with X - // substituted for P. We also want to explore type { [P in K]: E }[C], where C is the constraint of X. - const indexConstraint = getConstraintOfType((source as IndexedAccessType).indexType); - if (indexConstraint) { - if (result = isRelatedTo(getIndexedAccessType((source as IndexedAccessType).objectType, indexConstraint), target, RecursionFlags.Source, reportErrors)) { - return result; - } + if (sourceFlags & TypeFlags.TypeVariable) { + // IndexedAccess comparisons are handled above in the `targetFlags & TypeFlage.IndexedAccess` branch + if (!(sourceFlags & TypeFlags.IndexedAccess && targetFlags & TypeFlags.IndexedAccess)) { + const constraint = getConstraintOfType(source as TypeVariable) || unknownType; + // hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed + if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { + return result; + } + // slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example + else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && constraint !== unknownType && !(targetFlags & sourceFlags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) { + return result; + } + if (isMappedTypeGenericIndexedAccess(source)) { + // For an indexed access type { [P in K]: E}[X], above we have already explored an instantiation of E with X + // substituted for P. We also want to explore type { [P in K]: E }[C], where C is the constraint of X. + const indexConstraint = getConstraintOfType((source as IndexedAccessType).indexType); + if (indexConstraint) { + if (result = isRelatedTo(getIndexedAccessType((source as IndexedAccessType).objectType, indexConstraint), target, RecursionFlags.Source, reportErrors)) { + return result; } } } } - else if (sourceFlags & TypeFlags.Index) { - if (result = isRelatedTo(keyofConstraintType, target, RecursionFlags.Source, reportErrors)) { + } + else if (sourceFlags & TypeFlags.Index) { + if (result = isRelatedTo(keyofConstraintType, target, RecursionFlags.Source, reportErrors)) { + return result; + } + } + else if (sourceFlags & TypeFlags.TemplateLiteral && !(targetFlags & TypeFlags.Object)) { + if (!(targetFlags & TypeFlags.TemplateLiteral)) { + const constraint = getBaseConstraintOfType(source); + if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) { return result; } } - else if (sourceFlags & TypeFlags.TemplateLiteral && !(targetFlags & TypeFlags.Object)) { - if (!(targetFlags & TypeFlags.TemplateLiteral)) { - const constraint = getBaseConstraintOfType(source); - if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) { - return result; - } + } + else if (sourceFlags & TypeFlags.StringMapping) { + if (targetFlags & TypeFlags.StringMapping) { + if ((source as StringMappingType).symbol !== (target as StringMappingType).symbol) { + return Ternary.False; + } + if (result = isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, RecursionFlags.Both, reportErrors)) { + return result; } } - else if (sourceFlags & TypeFlags.StringMapping) { - if (targetFlags & TypeFlags.StringMapping) { - if ((source as StringMappingType).symbol !== (target as StringMappingType).symbol) { - return Ternary.False; + else { + const constraint = getBaseConstraintOfType(source); + if (constraint && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) { + return result; + } + } + } + else if (sourceFlags & TypeFlags.Conditional) { + // If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive + // conditional type and bail out with a Ternary.Maybe result. + if (isDeeplyNestedType(source, sourceStack, sourceDepth, 10)) { + return Ternary.Maybe; + } + if (targetFlags & TypeFlags.Conditional) { + // Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if + // one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2, + // and Y1 is related to Y2. + const sourceParams = (source as ConditionalType).root.inferTypeParameters; + let sourceExtends = (source as ConditionalType).extendsType; + let mapper: TypeMapper | undefined; + if (sourceParams) { + // If the source has infer type parameters, we instantiate them in the context of the target + const ctx = createInferenceContext(sourceParams, /*signature*/ undefined, InferenceFlags.None, isRelatedToWorker); + inferTypes(ctx.inferences, (target as ConditionalType).extendsType, sourceExtends, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + sourceExtends = instantiateType(sourceExtends, ctx.mapper); + mapper = ctx.mapper; + } + if (isTypeIdenticalTo(sourceExtends, (target as ConditionalType).extendsType) && + (isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both) || isRelatedTo((target as ConditionalType).checkType, (source as ConditionalType).checkType, RecursionFlags.Both))) { + if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType(source as ConditionalType), mapper), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors)) { + result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors); } - if (result = isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, RecursionFlags.Both, reportErrors)) { + if (result) { return result; } } - else { - const constraint = getBaseConstraintOfType(source); - if (constraint && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) { + } + else { + // conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way + // more assignments than are desirable (since it maps the source check type to its constraint, it loses information) + const distributiveConstraint = hasNonCircularBaseConstraint(source) ? getConstraintOfDistributiveConditionalType(source as ConditionalType) : undefined; + if (distributiveConstraint) { + if (result = isRelatedTo(distributiveConstraint, target, RecursionFlags.Source, reportErrors)) { return result; } } } - else if (sourceFlags & TypeFlags.Conditional) { - // If we reach 10 levels of nesting for the same conditional type, assume it is an infinitely expanding recursive - // conditional type and bail out with a Ternary.Maybe result. - if (isDeeplyNestedType(source, sourceStack, sourceDepth, 10)) { - return Ternary.Maybe; - } - if (targetFlags & TypeFlags.Conditional) { - // Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if - // one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2, - // and Y1 is related to Y2. - const sourceParams = (source as ConditionalType).root.inferTypeParameters; - let sourceExtends = (source as ConditionalType).extendsType; - let mapper: TypeMapper | undefined; - if (sourceParams) { - // If the source has infer type parameters, we instantiate them in the context of the target - const ctx = createInferenceContext(sourceParams, /*signature*/ undefined, InferenceFlags.None, isRelatedToWorker); - inferTypes(ctx.inferences, (target as ConditionalType).extendsType, sourceExtends, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); - sourceExtends = instantiateType(sourceExtends, ctx.mapper); - mapper = ctx.mapper; - } - if (isTypeIdenticalTo(sourceExtends, (target as ConditionalType).extendsType) && - (isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both) || isRelatedTo((target as ConditionalType).checkType, (source as ConditionalType).checkType, RecursionFlags.Both))) { - if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType(source as ConditionalType), mapper), getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors)) { - result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Both, reportErrors); - } - if (result) { - return result; - } - } - } - else { - // conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way - // more assignments than are desirable (since it maps the source check type to its constraint, it loses information) - const distributiveConstraint = hasNonCircularBaseConstraint(source) ? getConstraintOfDistributiveConditionalType(source as ConditionalType) : undefined; - if (distributiveConstraint) { - if (result = isRelatedTo(distributiveConstraint, target, RecursionFlags.Source, reportErrors)) { - return result; - } - } - } - // conditionals _can_ be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O` - // when `O` is a conditional (`never` is trivially assignable to `O`, as is `O`!). - const defaultConstraint = getDefaultConstraintOfConditionalType(source as ConditionalType); - if (defaultConstraint) { - if (result = isRelatedTo(defaultConstraint, target, RecursionFlags.Source, reportErrors)) { + // conditionals _can_ be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O` + // when `O` is a conditional (`never` is trivially assignable to `O`, as is `O`!). + const defaultConstraint = getDefaultConstraintOfConditionalType(source as ConditionalType); + if (defaultConstraint) { + if (result = isRelatedTo(defaultConstraint, target, RecursionFlags.Source, reportErrors)) { + return result; + } + } + } + else { + // An empty object type is related to any mapped type that includes a '?' modifier. + if (relation !== subtypeRelation && relation !== strictSubtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) { + return Ternary.True; + } + if (isGenericMappedType(target)) { + if (isGenericMappedType(source)) { + if (result = mappedTypeRelatedTo(source, target, reportErrors)) { return result; } } + return Ternary.False; } - else { - // An empty object type is related to any mapped type that includes a '?' modifier. - if (relation !== subtypeRelation && relation !== strictSubtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) { + const sourceIsPrimitive = !!(sourceFlags & TypeFlags.Primitive); + if (relation !== identityRelation) { + source = getApparentType(source); + sourceFlags = source.flags; + } + else if (isGenericMappedType(source)) { + return Ternary.False; + } + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target && + !isTupleType(source) && !(isMarkerType(source) || isMarkerType(target))) { + // When strictNullChecks is disabled, the element type of the empty array literal is undefinedWideningType, + // and an empty array literal wouldn't be assignable to a `never[]` without this check. + if (isEmptyArrayLiteralType(source)) { return Ternary.True; } - if (isGenericMappedType(target)) { - if (isGenericMappedType(source)) { - if (result = mappedTypeRelatedTo(source, target, reportErrors)) { - return result; - } - } - return Ternary.False; + // We have type references to the same generic type, and the type references are not marker + // type references (which are intended by be compared structurally). Obtain the variance + // information for the type parameters and relate the type arguments accordingly. + const variances = getVariances((source as TypeReference).target); + // We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This + // effectively means we measure variance only from type parameter occurrences that aren't nested in + // recursive instantiations of the generic type. + if (variances === emptyArray) { + return Ternary.Unknown; } - const sourceIsPrimitive = !!(sourceFlags & TypeFlags.Primitive); + const varianceResult = relateVariances(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), variances, intersectionState); + if (varianceResult !== undefined) { + return varianceResult; + } + } + else if (isReadonlyArrayType(target) ? isArrayOrTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { if (relation !== identityRelation) { - source = getApparentType(source); - sourceFlags = source.flags; + return isRelatedTo(getIndexTypeOfType(source, numberType) || anyType, getIndexTypeOfType(target, numberType) || anyType, RecursionFlags.Both, reportErrors); } - else if (isGenericMappedType(source)) { + 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; } - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target && - !isTupleType(source) && !(isMarkerType(source) || isMarkerType(target))) { - // When strictNullChecks is disabled, the element type of the empty array literal is undefinedWideningType, - // and an empty array literal wouldn't be assignable to a `never[]` without this check. - if (isEmptyArrayLiteralType(source)) { - return Ternary.True; - } - // We have type references to the same generic type, and the type references are not marker - // type references (which are intended by be compared structurally). Obtain the variance - // information for the type parameters and relate the type arguments accordingly. - const variances = getVariances((source as TypeReference).target); - // We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This - // effectively means we measure variance only from type parameter occurrences that aren't nested in - // recursive instantiations of the generic type. - if (variances === emptyArray) { - return Ternary.Unknown; - } - const varianceResult = relateVariances(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), variances, intersectionState); - if (varianceResult !== undefined) { - return varianceResult; + } + // Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})` + // and not `{} <- fresh({}) <- {[idx: string]: any}` + else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) { + return Ternary.False; + } + // Even if relationship doesn't hold for unions, intersections, or generic type references, + // it may hold in a structural comparison. + // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates + // to X. Failing both of those we want to check if the aggregation of A and B's members structurally + // relates to X. Thus, we include intersection types on the source side here. + if (sourceFlags & (TypeFlags.Object | TypeFlags.Intersection) && targetFlags & TypeFlags.Object) { + // Report structural errors only if we haven't reported any errors yet + const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; + result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, intersectionState); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); + if (result) { + result &= indexSignaturesRelatedTo(source, target, sourceIsPrimitive, reportStructuralErrors, intersectionState); + } } } - else if (isReadonlyArrayType(target) ? isArrayOrTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { - if (relation !== identityRelation) { - return isRelatedTo(getIndexTypeOfType(source, numberType) || anyType, getIndexTypeOfType(target, numberType) || anyType, RecursionFlags.Both, 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; - } + if (varianceCheckFailed && result) { + errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return 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}` - else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) { - return Ternary.False; + else if (result) { + return result; } - // Even if relationship doesn't hold for unions, intersections, or generic type references, - // it may hold in a structural comparison. - // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates - // to X. Failing both of those we want to check if the aggregation of A and B's members structurally - // relates to X. Thus, we include intersection types on the source side here. - if (sourceFlags & (TypeFlags.Object | TypeFlags.Intersection) && targetFlags & TypeFlags.Object) { - // Report structural errors only if we haven't reported any errors yet - const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; - result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, intersectionState); + } + // If S is an object type and T is a discriminated union, S may be related to T if + // there exists a constituent of T for every combination of the discriminants of S + // with respect to T. We do not report errors here, as we will use the existing + // error result from checking each constituent of the union. + if (sourceFlags & (TypeFlags.Object | TypeFlags.Intersection) && targetFlags & TypeFlags.Union) { + const objectOnlyTarget = extractTypesOfKind(target, TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Substitution); + if (objectOnlyTarget.flags & TypeFlags.Union) { + const result = typeRelatedToDiscriminatedType(source, objectOnlyTarget as UnionType); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); - if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); - if (result) { - result &= indexSignaturesRelatedTo(source, target, sourceIsPrimitive, reportStructuralErrors, intersectionState); - } - } - } - if (varianceCheckFailed && result) { - errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return false - } - else if (result) { return result; } } - // If S is an object type and T is a discriminated union, S may be related to T if - // there exists a constituent of T for every combination of the discriminants of S - // with respect to T. We do not report errors here, as we will use the existing - // error result from checking each constituent of the union. - if (sourceFlags & (TypeFlags.Object | TypeFlags.Intersection) && targetFlags & TypeFlags.Union) { - const objectOnlyTarget = extractTypesOfKind(target, TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Substitution); - if (objectOnlyTarget.flags & TypeFlags.Union) { - const result = typeRelatedToDiscriminatedType(source, objectOnlyTarget as UnionType); - if (result) { - return result; - } - } - } } - return Ternary.False; + } + return Ternary.False; - function countMessageChainBreadth(info: DiagnosticMessageChain[] | undefined): number { - if (!info) return 0; - return reduceLeft(info, (value, chain) => value + 1 + countMessageChainBreadth(chain.next), 0); - } + function countMessageChainBreadth(info: DiagnosticMessageChain[] | undefined): number { + if (!info) return 0; + return reduceLeft(info, (value, chain) => value + 1 + countMessageChainBreadth(chain.next), 0); + } - function relateVariances(sourceTypeArguments: readonly Type[] | undefined, targetTypeArguments: readonly Type[] | undefined, variances: VarianceFlags[], intersectionState: IntersectionState) { - if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState)) { - return result; - } - if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) { - // If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we - // have to allow a structural fallback check - // We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially - // be assuming identity of the type parameter. - originalErrorInfo = undefined; - resetErrorInfo(saveErrorInfo); - return undefined; - } - const allowStructuralFallback = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances); - varianceCheckFailed = !allowStructuralFallback; - // The type arguments did not relate appropriately, but it may be because we have no variance - // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type - // arguments). It might also be the case that the target type has a 'void' type argument for - // a covariant type parameter that is only used in return positions within the generic type - // (in which case any type argument is permitted on the source side). In those cases we proceed - // with a structural comparison. Otherwise, we know for certain the instantiations aren't - // related and we can return here. - if (variances !== emptyArray && !allowStructuralFallback) { - // In some cases generic types that are covariant in regular type checking mode become - // invariant in --strictFunctionTypes mode because one or more type parameters are used in - // both co- and contravariant positions. In order to make it easier to diagnose *why* such - // types are invariant, if any of the type parameters are invariant we reset the reported - // errors and instead force a structural comparison (which will include elaborations that - // reveal the reason). - // We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`, - // we can return `False` early here to skip calculating the structural error message we don't need. - if (varianceCheckFailed && !(reportErrors && some(variances, v => (v & VarianceFlags.VarianceMask) === VarianceFlags.Invariant))) { - return Ternary.False; - } - // We remember the original error information so we can restore it in case the structural - // comparison unexpectedly succeeds. This can happen when the structural comparison result - // is a Ternary.Maybe for example caused by the recursion depth limiter. - originalErrorInfo = errorInfo; - resetErrorInfo(saveErrorInfo); + function relateVariances(sourceTypeArguments: readonly Type[] | undefined, targetTypeArguments: readonly Type[] | undefined, variances: VarianceFlags[], intersectionState: IntersectionState) { + if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState)) { + return result; + } + if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) { + // If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we + // have to allow a structural fallback check + // We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially + // be assuming identity of the type parameter. + originalErrorInfo = undefined; + resetErrorInfo(saveErrorInfo); + return undefined; + } + const allowStructuralFallback = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances); + varianceCheckFailed = !allowStructuralFallback; + // The type arguments did not relate appropriately, but it may be because we have no variance + // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type + // arguments). It might also be the case that the target type has a 'void' type argument for + // a covariant type parameter that is only used in return positions within the generic type + // (in which case any type argument is permitted on the source side). In those cases we proceed + // with a structural comparison. Otherwise, we know for certain the instantiations aren't + // related and we can return here. + if (variances !== emptyArray && !allowStructuralFallback) { + // In some cases generic types that are covariant in regular type checking mode become + // invariant in --strictFunctionTypes mode because one or more type parameters are used in + // both co- and contravariant positions. In order to make it easier to diagnose *why* such + // types are invariant, if any of the type parameters are invariant we reset the reported + // errors and instead force a structural comparison (which will include elaborations that + // reveal the reason). + // We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`, + // we can return `False` early here to skip calculating the structural error message we don't need. + if (varianceCheckFailed && !(reportErrors && some(variances, v => (v & VarianceFlags.VarianceMask) === VarianceFlags.Invariant))) { + return Ternary.False; } + // We remember the original error information so we can restore it in case the structural + // comparison unexpectedly succeeds. This can happen when the structural comparison result + // is a Ternary.Maybe for example caused by the recursion depth limiter. + originalErrorInfo = errorInfo; + resetErrorInfo(saveErrorInfo); } } + } - // A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is - // related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice - // that S and T are contra-variant whereas X and Y are co-variant. - function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary { - const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) : - getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target)); - if (modifiersRelated) { - let result: Ternary; - const targetConstraint = getConstraintTypeFromMappedType(target); - const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMapper : reportUnreliableMapper); - if (result = isRelatedTo(targetConstraint, sourceConstraint, RecursionFlags.Both, reportErrors)) { - const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]); - if (instantiateType(getNameTypeFromMappedType(source), mapper) === instantiateType(getNameTypeFromMappedType(target), mapper)) { - return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), RecursionFlags.Both, reportErrors); - } + // A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is + // related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice + // that S and T are contra-variant whereas X and Y are co-variant. + function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary { + const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) : + getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target)); + if (modifiersRelated) { + let result: Ternary; + const targetConstraint = getConstraintTypeFromMappedType(target); + const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMapper : reportUnreliableMapper); + if (result = isRelatedTo(targetConstraint, sourceConstraint, RecursionFlags.Both, reportErrors)) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]); + if (instantiateType(getNameTypeFromMappedType(source), mapper) === instantiateType(getNameTypeFromMappedType(target), mapper)) { + return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), RecursionFlags.Both, reportErrors); } } - return Ternary.False; } + return Ternary.False; + } - function typeRelatedToDiscriminatedType(source: Type, target: UnionType) { - // 1. Generate the combinations of discriminant properties & types 'source' can satisfy. - // a. If the number of combinations is above a set limit, the comparison is too complex. - // 2. Filter 'target' to the subset of types whose discriminants exist in the matrix. - // a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related. - // 3. For each type in the filtered 'target', determine if all non-discriminant properties of - // 'target' are related to a property in 'source'. - // - // NOTE: See ~/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts - // for examples. - - const sourceProperties = getPropertiesOfType(source); - const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); - if (!sourcePropertiesFiltered) return Ternary.False; - - // Though we could compute the number of combinations as we generate - // the matrix, this would incur additional memory overhead due to - // array allocations. To reduce this overhead, we first compute - // the number of combinations to ensure we will not surpass our - // fixed limit before incurring the cost of any allocations: - let numCombinations = 1; - for (const sourceProperty of sourcePropertiesFiltered) { - numCombinations *= countTypes(getNonMissingTypeOfSymbol(sourceProperty)); - if (numCombinations > 25) { - // We've reached the complexity limit. - tracing?.instant(tracing.Phase.CheckTypes, "typeRelatedToDiscriminatedType_DepthLimit", { sourceId: source.id, targetId: target.id, numCombinations }); - return Ternary.False; - } + function typeRelatedToDiscriminatedType(source: Type, target: UnionType) { + // 1. Generate the combinations of discriminant properties & types 'source' can satisfy. + // a. If the number of combinations is above a set limit, the comparison is too complex. + // 2. Filter 'target' to the subset of types whose discriminants exist in the matrix. + // a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related. + // 3. For each type in the filtered 'target', determine if all non-discriminant properties of + // 'target' are related to a property in 'source'. + // + // NOTE: See ~/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts + // for examples. + + const sourceProperties = getPropertiesOfType(source); + const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); + if (!sourcePropertiesFiltered) return Ternary.False; + + // Though we could compute the number of combinations as we generate + // the matrix, this would incur additional memory overhead due to + // array allocations. To reduce this overhead, we first compute + // the number of combinations to ensure we will not surpass our + // fixed limit before incurring the cost of any allocations: + let numCombinations = 1; + for (const sourceProperty of sourcePropertiesFiltered) { + numCombinations *= countTypes(getNonMissingTypeOfSymbol(sourceProperty)); + if (numCombinations > 25) { + // We've reached the complexity limit. + tracing?.instant(tracing.Phase.CheckTypes, "typeRelatedToDiscriminatedType_DepthLimit", { sourceId: source.id, targetId: target.id, numCombinations }); + return Ternary.False; } + } - // Compute the set of types for each discriminant property. - const sourceDiscriminantTypes: Type[][] = new Array(sourcePropertiesFiltered.length); - const excludedProperties = new Set<__String>(); - for (let i = 0; i < sourcePropertiesFiltered.length; i++) { - const sourceProperty = sourcePropertiesFiltered[i]; - const sourcePropertyType = getNonMissingTypeOfSymbol(sourceProperty); - sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union - ? (sourcePropertyType as UnionType).types - : [sourcePropertyType]; - excludedProperties.add(sourceProperty.escapedName); - } - - // Match each combination of the cartesian product of discriminant properties to one or more - // constituents of 'target'. If any combination does not have a match then 'source' is not relatable. - const discriminantCombinations = cartesianProduct(sourceDiscriminantTypes); - const matchingTypes: Type[] = []; - for (const combination of discriminantCombinations) { - let hasMatch = false; - outer: for (const type of target.types) { - for (let i = 0; i < sourcePropertiesFiltered.length; i++) { - const sourceProperty = sourcePropertiesFiltered[i]; - const targetProperty = getPropertyOfType(type, sourceProperty.escapedName); - if (!targetProperty) continue outer; - if (sourceProperty === targetProperty) continue; - // We compare the source property to the target in the context of a single discriminant type. - const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, IntersectionState.None, /*skipOptional*/ strictNullChecks || relation === comparableRelation); - // If the target property could not be found, or if the properties were not related, - // then this constituent is not a match. - if (!related) { - continue outer; - } + // Compute the set of types for each discriminant property. + const sourceDiscriminantTypes: Type[][] = new Array(sourcePropertiesFiltered.length); + const excludedProperties = new Set<__String>(); + for (let i = 0; i < sourcePropertiesFiltered.length; i++) { + const sourceProperty = sourcePropertiesFiltered[i]; + const sourcePropertyType = getNonMissingTypeOfSymbol(sourceProperty); + sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union + ? (sourcePropertyType as UnionType).types + : [sourcePropertyType]; + excludedProperties.add(sourceProperty.escapedName); + } + + // Match each combination of the cartesian product of discriminant properties to one or more + // constituents of 'target'. If any combination does not have a match then 'source' is not relatable. + const discriminantCombinations = cartesianProduct(sourceDiscriminantTypes); + const matchingTypes: Type[] = []; + for (const combination of discriminantCombinations) { + let hasMatch = false; + outer: for (const type of target.types) { + for (let i = 0; i < sourcePropertiesFiltered.length; i++) { + const sourceProperty = sourcePropertiesFiltered[i]; + const targetProperty = getPropertyOfType(type, sourceProperty.escapedName); + if (!targetProperty) continue outer; + if (sourceProperty === targetProperty) continue; + // We compare the source property to the target in the context of a single discriminant type. + const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, IntersectionState.None, /*skipOptional*/ strictNullChecks || relation === comparableRelation); + // If the target property could not be found, or if the properties were not related, + // then this constituent is not a match. + if (!related) { + continue outer; } - pushIfUnique(matchingTypes, type, equateValues); - hasMatch = true; - } - if (!hasMatch) { - // We failed to match any type for this combination. - return Ternary.False; } + pushIfUnique(matchingTypes, type, equateValues); + hasMatch = true; + } + if (!hasMatch) { + // We failed to match any type for this combination. + return Ternary.False; } + } - // Compare the remaining non-discriminant properties of each match. - let result = Ternary.True; - for (const type of matchingTypes) { - result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, IntersectionState.None); + // Compare the remaining non-discriminant properties of each match. + let result = Ternary.True; + for (const type of matchingTypes) { + result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, IntersectionState.None); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false); if (result) { - result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false); - if (result) { - result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false); - if (result && !(isTupleType(source) && isTupleType(type))) { - // Comparing numeric index types when both `source` and `type` are tuples is unnecessary as the - // element types should be sufficiently covered by `propertiesRelatedTo`. It also causes problems - // with index type assignability as the types for the excluded discriminants are still included - // in the index type. - result &= indexSignaturesRelatedTo(source, type, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None); - } + result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false); + if (result && !(isTupleType(source) && isTupleType(type))) { + // Comparing numeric index types when both `source` and `type` are tuples is unnecessary as the + // element types should be sufficiently covered by `propertiesRelatedTo`. It also causes problems + // with index type assignability as the types for the excluded discriminants are still included + // in the index type. + result &= indexSignaturesRelatedTo(source, type, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None); } } - if (!result) { - return result; + } + if (!result) { + return result; + } + } + return result; + } + + function excludeProperties(properties: Symbol[], excludedProperties: Set<__String> | undefined) { + if (!excludedProperties || properties.length === 0) return properties; + let result: Symbol[] | undefined; + for (let i = 0; i < properties.length; i++) { + if (!excludedProperties.has(properties[i].escapedName)) { + if (result) { + result.push(properties[i]); } } - return result; + else if (!result) { + result = properties.slice(0, i); + } } + return result || properties; + } - function excludeProperties(properties: Symbol[], excludedProperties: Set<__String> | undefined) { - if (!excludedProperties || properties.length === 0) return properties; - let result: Symbol[] | undefined; - for (let i = 0; i < properties.length; i++) { - if (!excludedProperties.has(properties[i].escapedName)) { - if (result) { - result.push(properties[i]); + function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial); + const effectiveTarget = addOptionality(getNonMissingTypeOfSymbol(targetProp), /*isProperty*/ false, targetIsOptional); + const effectiveSource = getTypeOfSourceProperty(sourceProp); + return isRelatedTo(effectiveSource, effectiveTarget, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } + + function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState, skipOptional: boolean): Ternary { + const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); + const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); + if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { + if (sourceProp.valueDeclaration !== targetProp.valueDeclaration) { + if (reportErrors) { + if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { + reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp)); + } + else { + reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), + typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), + typeToString(sourcePropFlags & ModifierFlags.Private ? target : source)); } } - else if (!result) { - result = properties.slice(0, i); + return Ternary.False; + } + } + else if (targetPropFlags & ModifierFlags.Protected) { + if (!isValidOverrideOf(sourceProp, targetProp)) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), + typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target)); } + return Ternary.False; + } + } + else if (sourcePropFlags & ModifierFlags.Protected) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, + symbolToString(targetProp), typeToString(source), typeToString(target)); + } + return Ternary.False; + } + + // Ensure {readonly a: whatever} is not a subtype of {a: whatever}, + // while {a: whatever} is a subtype of {readonly a: whatever}. + // This ensures the subtype relationship is ordered, and preventing declaration order + // from deciding which type "wins" in union subtype reduction. + // They're still assignable to one another, since `readonly` doesn't affect assignability. + // This is only applied during the strictSubtypeRelation -- currently used in subtype reduction + if ( + relation === strictSubtypeRelation && + isReadonlySymbol(sourceProp) && !isReadonlySymbol(targetProp) + ) { + return Ternary.False; + } + // If the target comes from a partial union prop, allow `undefined` in the target type + const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, intersectionState); + if (!related) { + if (reportErrors) { + reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); + } + return Ternary.False; + } + // When checking for comparability, be more lenient with optional properties. + if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && targetProp.flags & SymbolFlags.ClassMember && !(targetProp.flags & SymbolFlags.Optional)) { + // TypeScript 1.0 spec (April 2014): 3.8.3 + // S is a subtype of a type T, and T is a supertype of S if ... + // S' and T are object types and, for each member M in T.. + // M is a property and S' contains a property N where + // if M is a required property, N is also a required property + // (M - property in T) + // (N - property in S) + if (reportErrors) { + reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, + symbolToString(targetProp), typeToString(source), typeToString(target)); + } + return Ternary.False; + } + return related; + } + + function reportUnmatchedProperty(source: Type, target: Type, unmatchedProperty: Symbol, requireOptionalProperties: boolean) { + let shouldSkipElaboration = false; + // give specific error in case where private names have the same description + if (unmatchedProperty.valueDeclaration + && isNamedDeclaration(unmatchedProperty.valueDeclaration) + && isPrivateIdentifier(unmatchedProperty.valueDeclaration.name) + && source.symbol + && source.symbol.flags & SymbolFlags.Class) { + const privateIdentifierDescription = unmatchedProperty.valueDeclaration.name.escapedText; + const symbolTableKey = getSymbolNameForPrivateIdentifier(source.symbol, privateIdentifierDescription); + if (symbolTableKey && getPropertyOfType(source, symbolTableKey)) { + const sourceName = factory.getDeclarationName(source.symbol.valueDeclaration); + const targetName = factory.getDeclarationName(target.symbol.valueDeclaration); + reportError( + Diagnostics.Property_0_in_type_1_refers_to_a_different_member_that_cannot_be_accessed_from_within_type_2, + diagnosticName(privateIdentifierDescription), + diagnosticName(sourceName.escapedText === "" ? anon : sourceName), + diagnosticName(targetName.escapedText === "" ? anon : targetName)); + return; + } + } + const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ 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)) { + shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it + } + if (props.length === 1) { + const propName = symbolToString(unmatchedProperty, /*enclosingDeclaration*/ undefined, SymbolFlags.None, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteComputedProps); + reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target)); + if (length(unmatchedProperty.declarations)) { + associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations![0], Diagnostics._0_is_declared_here, propName)); + } + if (shouldSkipElaboration && errorInfo) { + overrideNextErrorInfo++; + } + } + else if (tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ false)) { + if (props.length > 5) { // arbitrary cutoff for too-long list form + reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more, typeToString(source), typeToString(target), map(props.slice(0, 4), p => symbolToString(p)).join(", "), props.length - 4); + } + 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 && errorInfo) { + overrideNextErrorInfo++; } - return result || properties; } + // No array like or unmatched property error - just issue top level error (errorInfo = undefined) + } - function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial); - const effectiveTarget = addOptionality(getNonMissingTypeOfSymbol(targetProp), /*isProperty*/ false, targetIsOptional); - const effectiveSource = getTypeOfSourceProperty(sourceProp); - return isRelatedTo(effectiveSource, effectiveTarget, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, intersectionState: IntersectionState): Ternary { + if (relation === identityRelation) { + return propertiesIdenticalTo(source, target, excludedProperties); } - - function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState, skipOptional: boolean): Ternary { - const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); - const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); - if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { - if (sourceProp.valueDeclaration !== targetProp.valueDeclaration) { + let result = Ternary.True; + if (isTupleType(target)) { + if (isArrayOrTupleType(source)) { + if (!target.target.readonly && (isReadonlyArrayType(source) || isTupleType(source) && source.target.readonly)) { + return Ternary.False; + } + const sourceArity = getTypeReferenceArity(source); + const targetArity = getTypeReferenceArity(target); + const sourceRestFlag = isTupleType(source) ? source.target.combinedFlags & ElementFlags.Rest : ElementFlags.Rest; + const targetRestFlag = target.target.combinedFlags & ElementFlags.Rest; + const sourceMinLength = isTupleType(source) ? source.target.minLength : 0; + const targetMinLength = target.target.minLength; + if (!sourceRestFlag && sourceArity < targetMinLength) { if (reportErrors) { - if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { - reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp)); - } - else { - reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), - typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), - typeToString(sourcePropFlags & ModifierFlags.Private ? target : source)); - } + reportError(Diagnostics.Source_has_0_element_s_but_target_requires_1, sourceArity, targetMinLength); } return Ternary.False; } - } - else if (targetPropFlags & ModifierFlags.Protected) { - if (!isValidOverrideOf(sourceProp, targetProp)) { + if (!targetRestFlag && targetArity < sourceMinLength) { if (reportErrors) { - reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), - typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target)); + reportError(Diagnostics.Source_has_0_element_s_but_target_allows_only_1, sourceMinLength, targetArity); } return Ternary.False; } - } - else if (sourcePropFlags & ModifierFlags.Protected) { - if (reportErrors) { - reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, - symbolToString(targetProp), typeToString(source), typeToString(target)); + if (!targetRestFlag && (sourceRestFlag || targetArity < sourceArity)) { + if (reportErrors) { + if (sourceMinLength < targetMinLength) { + reportError(Diagnostics.Target_requires_0_element_s_but_source_may_have_fewer, targetMinLength); + } + else { + reportError(Diagnostics.Target_allows_only_0_element_s_but_source_may_have_more, targetArity); + } + } + return Ternary.False; } - return Ternary.False; - } + const sourceTypeArguments = getTypeArguments(source); + const targetTypeArguments = getTypeArguments(target); + const targetStartCount = getStartElementCount(target.target, ElementFlags.NonRest); + const targetEndCount = getEndElementCount(target.target, ElementFlags.NonRest); + const targetHasRestElement = target.target.hasRestElement; + let canExcludeDiscriminants = !!excludedProperties; + for (let sourcePosition = 0; sourcePosition < sourceArity; sourcePosition++) { + const sourceFlags = isTupleType(source) ? source.target.elementFlags[sourcePosition] : ElementFlags.Rest; + const sourcePositionFromEnd = sourceArity - 1 - sourcePosition; - // Ensure {readonly a: whatever} is not a subtype of {a: whatever}, - // while {a: whatever} is a subtype of {readonly a: whatever}. - // This ensures the subtype relationship is ordered, and preventing declaration order - // from deciding which type "wins" in union subtype reduction. - // They're still assignable to one another, since `readonly` doesn't affect assignability. - // This is only applied during the strictSubtypeRelation -- currently used in subtype reduction - if ( - relation === strictSubtypeRelation && - isReadonlySymbol(sourceProp) && !isReadonlySymbol(targetProp) - ) { - return Ternary.False; - } - // If the target comes from a partial union prop, allow `undefined` in the target type - const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, intersectionState); - if (!related) { - if (reportErrors) { - reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); - } - return Ternary.False; - } - // When checking for comparability, be more lenient with optional properties. - if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && targetProp.flags & SymbolFlags.ClassMember && !(targetProp.flags & SymbolFlags.Optional)) { - // TypeScript 1.0 spec (April 2014): 3.8.3 - // S is a subtype of a type T, and T is a supertype of S if ... - // S' and T are object types and, for each member M in T.. - // M is a property and S' contains a property N where - // if M is a required property, N is also a required property - // (M - property in T) - // (N - property in S) - if (reportErrors) { - reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, - symbolToString(targetProp), typeToString(source), typeToString(target)); - } - return Ternary.False; - } - return related; - } - - function reportUnmatchedProperty(source: Type, target: Type, unmatchedProperty: Symbol, requireOptionalProperties: boolean) { - let shouldSkipElaboration = false; - // give specific error in case where private names have the same description - if (unmatchedProperty.valueDeclaration - && isNamedDeclaration(unmatchedProperty.valueDeclaration) - && isPrivateIdentifier(unmatchedProperty.valueDeclaration.name) - && source.symbol - && source.symbol.flags & SymbolFlags.Class) { - const privateIdentifierDescription = unmatchedProperty.valueDeclaration.name.escapedText; - const symbolTableKey = getSymbolNameForPrivateIdentifier(source.symbol, privateIdentifierDescription); - if (symbolTableKey && getPropertyOfType(source, symbolTableKey)) { - const sourceName = factory.getDeclarationName(source.symbol.valueDeclaration); - const targetName = factory.getDeclarationName(target.symbol.valueDeclaration); - reportError( - Diagnostics.Property_0_in_type_1_refers_to_a_different_member_that_cannot_be_accessed_from_within_type_2, - diagnosticName(privateIdentifierDescription), - diagnosticName(sourceName.escapedText === "" ? anon : sourceName), - diagnosticName(targetName.escapedText === "" ? anon : targetName)); - return; - } - } - const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ 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)) { - shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it - } - if (props.length === 1) { - const propName = symbolToString(unmatchedProperty, /*enclosingDeclaration*/ undefined, SymbolFlags.None, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteComputedProps); - reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target)); - if (length(unmatchedProperty.declarations)) { - associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations![0], Diagnostics._0_is_declared_here, propName)); - } - if (shouldSkipElaboration && errorInfo) { - overrideNextErrorInfo++; - } - } - else if (tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ false)) { - if (props.length > 5) { // arbitrary cutoff for too-long list form - reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more, typeToString(source), typeToString(target), map(props.slice(0, 4), p => symbolToString(p)).join(", "), props.length - 4); - } - 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 && errorInfo) { - overrideNextErrorInfo++; - } - } - // No array like or unmatched property error - just issue top level error (errorInfo = undefined) - } + const targetPosition = targetHasRestElement && sourcePosition >= targetStartCount + ? targetArity - 1 - Math.min(sourcePositionFromEnd, targetEndCount) + : sourcePosition; - function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, intersectionState: IntersectionState): Ternary { - if (relation === identityRelation) { - return propertiesIdenticalTo(source, target, excludedProperties); - } - let result = Ternary.True; - if (isTupleType(target)) { - if (isArrayOrTupleType(source)) { - if (!target.target.readonly && (isReadonlyArrayType(source) || isTupleType(source) && source.target.readonly)) { - return Ternary.False; - } - const sourceArity = getTypeReferenceArity(source); - const targetArity = getTypeReferenceArity(target); - const sourceRestFlag = isTupleType(source) ? source.target.combinedFlags & ElementFlags.Rest : ElementFlags.Rest; - const targetRestFlag = target.target.combinedFlags & ElementFlags.Rest; - const sourceMinLength = isTupleType(source) ? source.target.minLength : 0; - const targetMinLength = target.target.minLength; - if (!sourceRestFlag && sourceArity < targetMinLength) { + const targetFlags = target.target.elementFlags[targetPosition]; + + if (targetFlags & ElementFlags.Variadic && !(sourceFlags & ElementFlags.Variadic)) { if (reportErrors) { - reportError(Diagnostics.Source_has_0_element_s_but_target_requires_1, sourceArity, targetMinLength); + reportError(Diagnostics.Source_provides_no_match_for_variadic_element_at_position_0_in_target, targetPosition); } return Ternary.False; } - if (!targetRestFlag && targetArity < sourceMinLength) { + if (sourceFlags & ElementFlags.Variadic && !(targetFlags & ElementFlags.Variable)) { if (reportErrors) { - reportError(Diagnostics.Source_has_0_element_s_but_target_allows_only_1, sourceMinLength, targetArity); + reportError(Diagnostics.Variadic_element_at_position_0_in_source_does_not_match_element_at_position_1_in_target, sourcePosition, targetPosition); } return Ternary.False; } - if (!targetRestFlag && (sourceRestFlag || targetArity < sourceArity)) { + if (targetFlags & ElementFlags.Required && !(sourceFlags & ElementFlags.Required)) { if (reportErrors) { - if (sourceMinLength < targetMinLength) { - reportError(Diagnostics.Target_requires_0_element_s_but_source_may_have_fewer, targetMinLength); - } - else { - reportError(Diagnostics.Target_allows_only_0_element_s_but_source_may_have_more, targetArity); - } + reportError(Diagnostics.Source_provides_no_match_for_required_element_at_position_0_in_target, targetPosition); } return Ternary.False; } - const sourceTypeArguments = getTypeArguments(source); - const targetTypeArguments = getTypeArguments(target); - const targetStartCount = getStartElementCount(target.target, ElementFlags.NonRest); - const targetEndCount = getEndElementCount(target.target, ElementFlags.NonRest); - const targetHasRestElement = target.target.hasRestElement; - let canExcludeDiscriminants = !!excludedProperties; - for (let sourcePosition = 0; sourcePosition < sourceArity; sourcePosition++) { - const sourceFlags = isTupleType(source) ? source.target.elementFlags[sourcePosition] : ElementFlags.Rest; - const sourcePositionFromEnd = sourceArity - 1 - sourcePosition; - - const targetPosition = targetHasRestElement && sourcePosition >= targetStartCount - ? targetArity - 1 - Math.min(sourcePositionFromEnd, targetEndCount) - : sourcePosition; - - const targetFlags = target.target.elementFlags[targetPosition]; - - if (targetFlags & ElementFlags.Variadic && !(sourceFlags & ElementFlags.Variadic)) { - if (reportErrors) { - reportError(Diagnostics.Source_provides_no_match_for_variadic_element_at_position_0_in_target, targetPosition); - } - return Ternary.False; + // We can only exclude discriminant properties if we have not yet encountered a variable-length element. + if (canExcludeDiscriminants) { + if (sourceFlags & ElementFlags.Variable || targetFlags & ElementFlags.Variable) { + canExcludeDiscriminants = false; } - if (sourceFlags & ElementFlags.Variadic && !(targetFlags & ElementFlags.Variable)) { - if (reportErrors) { - reportError(Diagnostics.Variadic_element_at_position_0_in_source_does_not_match_element_at_position_1_in_target, sourcePosition, targetPosition); - } - return Ternary.False; - } - if (targetFlags & ElementFlags.Required && !(sourceFlags & ElementFlags.Required)) { - if (reportErrors) { - reportError(Diagnostics.Source_provides_no_match_for_required_element_at_position_0_in_target, targetPosition); - } - return Ternary.False; - } - // We can only exclude discriminant properties if we have not yet encountered a variable-length element. - if (canExcludeDiscriminants) { - if (sourceFlags & ElementFlags.Variable || targetFlags & ElementFlags.Variable) { - canExcludeDiscriminants = false; - } - if (canExcludeDiscriminants && excludedProperties?.has(("" + sourcePosition) as __String)) { - continue; - } + if (canExcludeDiscriminants && excludedProperties?.has(("" + sourcePosition) as __String)) { + continue; } + } - const sourceType = removeMissingType(sourceTypeArguments[sourcePosition], !!(sourceFlags & targetFlags & ElementFlags.Optional)); - const targetType = targetTypeArguments[targetPosition]; + const sourceType = removeMissingType(sourceTypeArguments[sourcePosition], !!(sourceFlags & targetFlags & ElementFlags.Optional)); + const targetType = targetTypeArguments[targetPosition]; - const targetCheckType = sourceFlags & ElementFlags.Variadic && targetFlags & ElementFlags.Rest ? createArrayType(targetType) : - removeMissingType(targetType, !!(targetFlags & ElementFlags.Optional)); - const related = isRelatedTo(sourceType, targetCheckType, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); - if (!related) { - if (reportErrors && (targetArity > 1 || sourceArity > 1)) { - if (targetHasRestElement && sourcePosition >= targetStartCount && sourcePositionFromEnd >= targetEndCount && targetStartCount !== sourceArity - targetEndCount - 1) { - reportIncompatibleError(Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, targetStartCount, sourceArity - targetEndCount - 1, targetPosition); - } - else { - reportIncompatibleError(Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, sourcePosition, targetPosition); - } + const targetCheckType = sourceFlags & ElementFlags.Variadic && targetFlags & ElementFlags.Rest ? createArrayType(targetType) : + removeMissingType(targetType, !!(targetFlags & ElementFlags.Optional)); + const related = isRelatedTo(sourceType, targetCheckType, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + if (!related) { + if (reportErrors && (targetArity > 1 || sourceArity > 1)) { + if (targetHasRestElement && sourcePosition >= targetStartCount && sourcePositionFromEnd >= targetEndCount && targetStartCount !== sourceArity - targetEndCount - 1) { + reportIncompatibleError(Diagnostics.Type_at_positions_0_through_1_in_source_is_not_compatible_with_type_at_position_2_in_target, targetStartCount, sourceArity - targetEndCount - 1, targetPosition); + } + else { + reportIncompatibleError(Diagnostics.Type_at_position_0_in_source_is_not_compatible_with_type_at_position_1_in_target, sourcePosition, targetPosition); } - return Ternary.False; } - result &= related; + return Ternary.False; } - return result; - } - if (target.target.combinedFlags & ElementFlags.Variable) { - return Ternary.False; + result &= related; } + return result; } - const requireOptionalProperties = (relation === subtypeRelation || relation === strictSubtypeRelation) && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source); - const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false); - if (unmatchedProperty) { - if (reportErrors && shouldReportUnmatchedPropertyError(source, target)) { - reportUnmatchedProperty(source, target, unmatchedProperty, requireOptionalProperties); - } + if (target.target.combinedFlags & ElementFlags.Variable) { return Ternary.False; } - if (isObjectLiteralType(target)) { - for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) { - if (!getPropertyOfObjectType(target, sourceProp.escapedName)) { - const sourceType = getTypeOfSymbol(sourceProp); - if (!(sourceType.flags & TypeFlags.Undefined)) { - if (reportErrors) { - reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target)); - } - return Ternary.False; + } + const requireOptionalProperties = (relation === subtypeRelation || relation === strictSubtypeRelation) && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source); + const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false); + if (unmatchedProperty) { + if (reportErrors && shouldReportUnmatchedPropertyError(source, target)) { + reportUnmatchedProperty(source, target, unmatchedProperty, requireOptionalProperties); + } + return Ternary.False; + } + if (isObjectLiteralType(target)) { + for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) { + if (!getPropertyOfObjectType(target, sourceProp.escapedName)) { + const sourceType = getTypeOfSymbol(sourceProp); + if (!(sourceType.flags & TypeFlags.Undefined)) { + if (reportErrors) { + reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target)); } + return Ternary.False; } } } - // We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_ - // from the target union, across all members - const properties = getPropertiesOfType(target); - const numericNamesOnly = isTupleType(source) && isTupleType(target); - for (const targetProp of excludeProperties(properties, excludedProperties)) { - const name = targetProp.escapedName; - if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) { - const sourceProp = getPropertyOfType(source, name); - if (sourceProp && sourceProp !== targetProp) { - const related = propertyRelatedTo(source, target, sourceProp, targetProp, getNonMissingTypeOfSymbol, reportErrors, intersectionState, relation === comparableRelation); - if (!related) { - return Ternary.False; - } - result &= related; + } + // We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_ + // from the target union, across all members + const properties = getPropertiesOfType(target); + const numericNamesOnly = isTupleType(source) && isTupleType(target); + for (const targetProp of excludeProperties(properties, excludedProperties)) { + const name = targetProp.escapedName; + if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) { + const sourceProp = getPropertyOfType(source, name); + if (sourceProp && sourceProp !== targetProp) { + const related = propertyRelatedTo(source, target, sourceProp, targetProp, getNonMissingTypeOfSymbol, reportErrors, intersectionState, relation === comparableRelation); + if (!related) { + return Ternary.False; } + result &= related; } } - return result; } + return result; + } - function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: Set<__String> | undefined): Ternary { - if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) { + function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: Set<__String> | undefined): Ternary { + if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) { + return Ternary.False; + } + const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties); + const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties); + if (sourceProperties.length !== targetProperties.length) { + return Ternary.False; + } + let result = Ternary.True; + for (const sourceProp of sourceProperties) { + const targetProp = getPropertyOfObjectType(target, sourceProp.escapedName); + if (!targetProp) { return Ternary.False; } - const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties); - const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties); - if (sourceProperties.length !== targetProperties.length) { + const related = compareProperties(sourceProp, targetProp, isRelatedTo); + if (!related) { return Ternary.False; } - let result = Ternary.True; - for (const sourceProp of sourceProperties) { - const targetProp = getPropertyOfObjectType(target, sourceProp.escapedName); - if (!targetProp) { - return Ternary.False; - } - const related = compareProperties(sourceProp, targetProp, isRelatedTo); - if (!related) { - return Ternary.False; - } - result &= related; - } - return result; + result &= related; } + return result; + } - function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean): Ternary { - if (relation === identityRelation) { - return signaturesIdenticalTo(source, target, kind); - } - if (target === anyFunctionType || source === anyFunctionType) { - return Ternary.True; - } + function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean): Ternary { + if (relation === identityRelation) { + return signaturesIdenticalTo(source, target, kind); + } + if (target === anyFunctionType || source === anyFunctionType) { + return Ternary.True; + } - const sourceIsJSConstructor = source.symbol && isJSConstructor(source.symbol.valueDeclaration); - const targetIsJSConstructor = target.symbol && isJSConstructor(target.symbol.valueDeclaration); + const sourceIsJSConstructor = source.symbol && isJSConstructor(source.symbol.valueDeclaration); + const targetIsJSConstructor = target.symbol && isJSConstructor(target.symbol.valueDeclaration); - const sourceSignatures = getSignaturesOfType(source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ? - SignatureKind.Call : kind); - const targetSignatures = getSignaturesOfType(target, (targetIsJSConstructor && kind === SignatureKind.Construct) ? - SignatureKind.Call : kind); + const sourceSignatures = getSignaturesOfType(source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ? + SignatureKind.Call : kind); + const targetSignatures = getSignaturesOfType(target, (targetIsJSConstructor && kind === SignatureKind.Construct) ? + SignatureKind.Call : kind); - if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) { - const sourceIsAbstract = !!(sourceSignatures[0].flags & SignatureFlags.Abstract); - const targetIsAbstract = !!(targetSignatures[0].flags & SignatureFlags.Abstract); - if (sourceIsAbstract && !targetIsAbstract) { - // An abstract constructor type is not assignable to a non-abstract constructor type - // as it would otherwise be possible to new an abstract class. Note that the assignability - // check we perform for an extends clause excludes construct signatures from the target, - // so this check never proceeds. - if (reportErrors) { - reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); - } - return Ternary.False; - } - if (!constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors)) { - return Ternary.False; + if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) { + const sourceIsAbstract = !!(sourceSignatures[0].flags & SignatureFlags.Abstract); + const targetIsAbstract = !!(targetSignatures[0].flags & SignatureFlags.Abstract); + if (sourceIsAbstract && !targetIsAbstract) { + // An abstract constructor type is not assignable to a non-abstract constructor type + // as it would otherwise be possible to new an abstract class. Note that the assignability + // check we perform for an extends clause excludes construct signatures from the target, + // so this check never proceeds. + if (reportErrors) { + reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); } + return Ternary.False; + } + if (!constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors)) { + return Ternary.False; } + } - let result = Ternary.True; - const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn; - const sourceObjectFlags = getObjectFlags(source); - const targetObjectFlags = getObjectFlags(target); - if (sourceObjectFlags & ObjectFlags.Instantiated && targetObjectFlags & ObjectFlags.Instantiated && source.symbol === target.symbol || - sourceObjectFlags & ObjectFlags.Reference && targetObjectFlags & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target) { - // We have instantiations of the same anonymous type (which typically will be the type of a - // method). Simply do a pairwise comparison of the signatures in the two signature lists instead - // of the much more expensive N * M comparison matrix we explore below. We erase type parameters - // as they are known to always be the same. - for (let i = 0; i < targetSignatures.length; i++) { - const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, incompatibleReporter(sourceSignatures[i], targetSignatures[i])); - if (!related) { - return Ternary.False; - } - result &= related; + let result = Ternary.True; + const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn; + const sourceObjectFlags = getObjectFlags(source); + const targetObjectFlags = getObjectFlags(target); + if (sourceObjectFlags & ObjectFlags.Instantiated && targetObjectFlags & ObjectFlags.Instantiated && source.symbol === target.symbol || + sourceObjectFlags & ObjectFlags.Reference && targetObjectFlags & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target) { + // We have instantiations of the same anonymous type (which typically will be the type of a + // method). Simply do a pairwise comparison of the signatures in the two signature lists instead + // of the much more expensive N * M comparison matrix we explore below. We erase type parameters + // as they are known to always be the same. + for (let i = 0; i < targetSignatures.length; i++) { + const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, incompatibleReporter(sourceSignatures[i], targetSignatures[i])); + if (!related) { + return Ternary.False; } + result &= related; } - else if (sourceSignatures.length === 1 && targetSignatures.length === 1) { - // For simple functions (functions with a single signature) we only erase type parameters for - // the comparable relation. Otherwise, if the source signature is generic, we instantiate it - // in the context of the target signature before checking the relationship. Ideally we'd do - // this regardless of the number of signatures, but the potential costs are prohibitive due - // to the quadratic nature of the logic below. - const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks; - const sourceSignature = first(sourceSignatures); - const targetSignature = first(targetSignatures); - result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, incompatibleReporter(sourceSignature, targetSignature)); - if (!result && reportErrors && kind === SignatureKind.Construct && (sourceObjectFlags & targetObjectFlags) && - (targetSignature.declaration?.kind === SyntaxKind.Constructor || sourceSignature.declaration?.kind === SyntaxKind.Constructor)) { - const constructSignatureToString = (signature: Signature) => - signatureToString(signature, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrowStyleSignature, kind); - reportError(Diagnostics.Type_0_is_not_assignable_to_type_1, constructSignatureToString(sourceSignature), constructSignatureToString(targetSignature)); - reportError(Diagnostics.Types_of_construct_signatures_are_incompatible); - return result; - } + } + else if (sourceSignatures.length === 1 && targetSignatures.length === 1) { + // For simple functions (functions with a single signature) we only erase type parameters for + // the comparable relation. Otherwise, if the source signature is generic, we instantiate it + // in the context of the target signature before checking the relationship. Ideally we'd do + // this regardless of the number of signatures, but the potential costs are prohibitive due + // to the quadratic nature of the logic below. + const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks; + const sourceSignature = first(sourceSignatures); + const targetSignature = first(targetSignatures); + result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, incompatibleReporter(sourceSignature, targetSignature)); + if (!result && reportErrors && kind === SignatureKind.Construct && (sourceObjectFlags & targetObjectFlags) && + (targetSignature.declaration?.kind === SyntaxKind.Constructor || sourceSignature.declaration?.kind === SyntaxKind.Constructor)) { + const constructSignatureToString = (signature: Signature) => + signatureToString(signature, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrowStyleSignature, kind); + reportError(Diagnostics.Type_0_is_not_assignable_to_type_1, constructSignatureToString(sourceSignature), constructSignatureToString(targetSignature)); + reportError(Diagnostics.Types_of_construct_signatures_are_incompatible); + return result; } - else { - outer: for (const t of targetSignatures) { - const saveErrorInfo = captureErrorCalculationState(); - // Only elaborate errors from the first failure - let shouldElaborateErrors = reportErrors; - for (const s of sourceSignatures) { - const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, incompatibleReporter(s, t)); - if (related) { - result &= related; - resetErrorInfo(saveErrorInfo); - continue outer; - } - shouldElaborateErrors = false; - } - if (shouldElaborateErrors) { - reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1, - typeToString(source), - signatureToString(t, /*enclosingDeclaration*/ undefined, /*flags*/ undefined, kind)); + } + else { + outer: for (const t of targetSignatures) { + const saveErrorInfo = captureErrorCalculationState(); + // Only elaborate errors from the first failure + let shouldElaborateErrors = reportErrors; + for (const s of sourceSignatures) { + const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, incompatibleReporter(s, t)); + if (related) { + result &= related; + resetErrorInfo(saveErrorInfo); + continue outer; } - return Ternary.False; + shouldElaborateErrors = false; } - } - return result; - } - - function shouldReportUnmatchedPropertyError(source: Type, target: Type): boolean { - const typeCallSignatures = getSignaturesOfStructuredType(source, SignatureKind.Call); - const typeConstructSignatures = getSignaturesOfStructuredType(source, SignatureKind.Construct); - const typeProperties = getPropertiesOfObjectType(source); - if ((typeCallSignatures.length || typeConstructSignatures.length) && !typeProperties.length) { - if ((getSignaturesOfType(target, SignatureKind.Call).length && typeCallSignatures.length) || - (getSignaturesOfType(target, SignatureKind.Construct).length && typeConstructSignatures.length)) { - return true; // target has similar signature kinds to source, still focus on the unmatched property + if (shouldElaborateErrors) { + reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1, + typeToString(source), + signatureToString(t, /*enclosingDeclaration*/ undefined, /*flags*/ undefined, kind)); } - return false; + return Ternary.False; } - return true; } + return result; + } - function reportIncompatibleCallSignatureReturn(siga: Signature, sigb: Signature) { - if (siga.parameters.length === 0 && sigb.parameters.length === 0) { - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); + function shouldReportUnmatchedPropertyError(source: Type, target: Type): boolean { + const typeCallSignatures = getSignaturesOfStructuredType(source, SignatureKind.Call); + const typeConstructSignatures = getSignaturesOfStructuredType(source, SignatureKind.Construct); + const typeProperties = getPropertiesOfObjectType(source); + if ((typeCallSignatures.length || typeConstructSignatures.length) && !typeProperties.length) { + if ((getSignaturesOfType(target, SignatureKind.Call).length && typeCallSignatures.length) || + (getSignaturesOfType(target, SignatureKind.Construct).length && typeConstructSignatures.length)) { + return true; // target has similar signature kinds to source, still focus on the unmatched property } - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); + return false; } + return true; + } - function reportIncompatibleConstructSignatureReturn(siga: Signature, sigb: Signature) { - if (siga.parameters.length === 0 && sigb.parameters.length === 0) { - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); - } - return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); + function reportIncompatibleCallSignatureReturn(siga: Signature, sigb: Signature) { + if (siga.parameters.length === 0 && sigb.parameters.length === 0) { + return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); } + return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); + } - /** - * See signatureAssignableTo, compareSignaturesIdentical - */ - function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { - return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, - relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, reportUnreliableMapper); + function reportIncompatibleConstructSignatureReturn(siga: Signature, sigb: Signature) { + if (siga.parameters.length === 0 && sigb.parameters.length === 0) { + return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); } + return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); + } + + /** + * See signatureAssignableTo, compareSignaturesIdentical + */ + function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { + return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, + relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, reportUnreliableMapper); + } - function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { - const sourceSignatures = getSignaturesOfType(source, kind); - const targetSignatures = getSignaturesOfType(target, kind); - if (sourceSignatures.length !== targetSignatures.length) { + function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { + const sourceSignatures = getSignaturesOfType(source, kind); + const targetSignatures = getSignaturesOfType(target, kind); + if (sourceSignatures.length !== targetSignatures.length) { + return Ternary.False; + } + let result = Ternary.True; + for (let i = 0; i < sourceSignatures.length; i++) { + const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); + if (!related) { return Ternary.False; } - let result = Ternary.True; - for (let i = 0; i < sourceSignatures.length; i++) { - const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); - if (!related) { - return Ternary.False; - } - result &= related; - } - return result; + result &= related; } + return result; + } - function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean): Ternary { - let result = Ternary.True; - const keyType = targetInfo.keyType; - const props = source.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(source as IntersectionType) : getPropertiesOfObjectType(source); - for (const prop of props) { - // Skip over ignored JSX and symbol-named members - if (isIgnoredJsxProperty(source, prop)) { - continue; - } - if (isApplicableIndexType(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), keyType)) { - const propType = getNonMissingTypeOfSymbol(prop); - const type = exactOptionalPropertyTypes || propType.flags & TypeFlags.Undefined || keyType === numberType || !(prop.flags & SymbolFlags.Optional) - ? propType - : getTypeWithFacts(propType, TypeFacts.NEUndefined); - const related = isRelatedTo(type, targetInfo.type, RecursionFlags.Both, reportErrors); - if (!related) { - if (reportErrors) { - reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); - } - return Ternary.False; - } - result &= related; - } + function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean): Ternary { + let result = Ternary.True; + const keyType = targetInfo.keyType; + const props = source.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(source as IntersectionType) : getPropertiesOfObjectType(source); + for (const prop of props) { + // Skip over ignored JSX and symbol-named members + if (isIgnoredJsxProperty(source, prop)) { + continue; } - for (const info of getIndexInfosOfType(source)) { - if (isApplicableIndexType(info.keyType, keyType)) { - const related = indexInfoRelatedTo(info, targetInfo, reportErrors); - if (!related) { - return Ternary.False; + if (isApplicableIndexType(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), keyType)) { + const propType = getNonMissingTypeOfSymbol(prop); + const type = exactOptionalPropertyTypes || propType.flags & TypeFlags.Undefined || keyType === numberType || !(prop.flags & SymbolFlags.Optional) + ? propType + : getTypeWithFacts(propType, TypeFacts.NEUndefined); + const related = isRelatedTo(type, targetInfo.type, RecursionFlags.Both, reportErrors); + if (!related) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); } - result &= related; - } - } - return result; - } - - function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean) { - const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors); - if (!related && reportErrors) { - if (sourceInfo.keyType === targetInfo.keyType) { - reportError(Diagnostics._0_index_signatures_are_incompatible, typeToString(sourceInfo.keyType)); - } - else { - reportError(Diagnostics._0_and_1_index_signatures_are_incompatible, typeToString(sourceInfo.keyType), typeToString(targetInfo.keyType)); + return Ternary.False; } + result &= related; } - return related; } - - function indexSignaturesRelatedTo(source: Type, target: Type, sourceIsPrimitive: boolean, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - if (relation === identityRelation) { - return indexSignaturesIdenticalTo(source, target); - } - const indexInfos = getIndexInfosOfType(target); - const targetHasStringIndex = some(indexInfos, info => info.keyType === stringType); - let result = Ternary.True; - for (const targetInfo of indexInfos) { - const related = !sourceIsPrimitive && targetHasStringIndex && targetInfo.type.flags & TypeFlags.Any ? Ternary.True : - isGenericMappedType(source) && targetHasStringIndex ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, RecursionFlags.Both, reportErrors) : - typeRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState); + for (const info of getIndexInfosOfType(source)) { + if (isApplicableIndexType(info.keyType, keyType)) { + const related = indexInfoRelatedTo(info, targetInfo, reportErrors); if (!related) { return Ternary.False; } result &= related; } - return result; } + return result; + } - function typeRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType); - if (sourceInfo) { - return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors); + function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean) { + const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors); + if (!related && reportErrors) { + if (sourceInfo.keyType === targetInfo.keyType) { + reportError(Diagnostics._0_index_signatures_are_incompatible, typeToString(sourceInfo.keyType)); } - if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) { - // Intersection constituents are never considered to have an inferred index signature - return membersRelatedToIndexInfo(source, targetInfo, reportErrors); - } - if (reportErrors) { - reportError(Diagnostics.Index_signature_for_type_0_is_missing_in_type_1, typeToString(targetInfo.keyType), typeToString(source)); + else { + reportError(Diagnostics._0_and_1_index_signatures_are_incompatible, typeToString(sourceInfo.keyType), typeToString(targetInfo.keyType)); } - return Ternary.False; } + return related; + } - function indexSignaturesIdenticalTo(source: Type, target: Type): Ternary { - const sourceInfos = getIndexInfosOfType(source); - const targetInfos = getIndexInfosOfType(target); - if (sourceInfos.length !== targetInfos.length) { + function indexSignaturesRelatedTo(source: Type, target: Type, sourceIsPrimitive: boolean, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + if (relation === identityRelation) { + return indexSignaturesIdenticalTo(source, target); + } + const indexInfos = getIndexInfosOfType(target); + const targetHasStringIndex = some(indexInfos, info => info.keyType === stringType); + let result = Ternary.True; + for (const targetInfo of indexInfos) { + const related = !sourceIsPrimitive && targetHasStringIndex && targetInfo.type.flags & TypeFlags.Any ? Ternary.True : + isGenericMappedType(source) && targetHasStringIndex ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, RecursionFlags.Both, reportErrors) : + typeRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState); + if (!related) { return Ternary.False; } - for (const targetInfo of targetInfos) { - const sourceInfo = getIndexInfoOfType(source, targetInfo.keyType); - if (!(sourceInfo && isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both) && sourceInfo.isReadonly === targetInfo.isReadonly)) { - return Ternary.False; - } - } - return Ternary.True; + result &= related; } + return result; + } - function constructorVisibilitiesAreCompatible(sourceSignature: Signature, targetSignature: Signature, reportErrors: boolean) { - if (!sourceSignature.declaration || !targetSignature.declaration) { - return true; - } - - const sourceAccessibility = getSelectedEffectiveModifierFlags(sourceSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); - const targetAccessibility = getSelectedEffectiveModifierFlags(targetSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); - - // A public, protected and private signature is assignable to a private signature. - if (targetAccessibility === ModifierFlags.Private) { - return true; - } - - // A public and protected signature is assignable to a protected signature. - if (targetAccessibility === ModifierFlags.Protected && sourceAccessibility !== ModifierFlags.Private) { - return true; - } + function typeRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType); + if (sourceInfo) { + return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors); + } + if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) { + // Intersection constituents are never considered to have an inferred index signature + return membersRelatedToIndexInfo(source, targetInfo, reportErrors); + } + if (reportErrors) { + reportError(Diagnostics.Index_signature_for_type_0_is_missing_in_type_1, typeToString(targetInfo.keyType), typeToString(source)); + } + return Ternary.False; + } - // Only a public signature is assignable to public signature. - if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) { - return true; + function indexSignaturesIdenticalTo(source: Type, target: Type): Ternary { + const sourceInfos = getIndexInfosOfType(source); + const targetInfos = getIndexInfosOfType(target); + if (sourceInfos.length !== targetInfos.length) { + return Ternary.False; + } + for (const targetInfo of targetInfos) { + const sourceInfo = getIndexInfoOfType(source, targetInfo.keyType); + if (!(sourceInfo && isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both) && sourceInfo.isReadonly === targetInfo.isReadonly)) { + return Ternary.False; } + } + return Ternary.True; + } - if (reportErrors) { - reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility)); - } + function constructorVisibilitiesAreCompatible(sourceSignature: Signature, targetSignature: Signature, reportErrors: boolean) { + if (!sourceSignature.declaration || !targetSignature.declaration) { + return true; + } - return false; + const sourceAccessibility = getSelectedEffectiveModifierFlags(sourceSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); + const targetAccessibility = getSelectedEffectiveModifierFlags(targetSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); + + // A public, protected and private signature is assignable to a private signature. + if (targetAccessibility === ModifierFlags.Private) { + return true; } - } - function typeCouldHaveTopLevelSingletonTypes(type: Type): boolean { - // Okay, yes, 'boolean' is a union of 'true | false', but that's not useful - // in error reporting scenarios. If you need to use this function but that detail matters, - // feel free to add a flag. - if (type.flags & TypeFlags.Boolean) { - return false; + // A public and protected signature is assignable to a protected signature. + if (targetAccessibility === ModifierFlags.Protected && sourceAccessibility !== ModifierFlags.Private) { + return true; } - if (type.flags & TypeFlags.UnionOrIntersection) { - return !!forEach((type as IntersectionType).types, typeCouldHaveTopLevelSingletonTypes); + // Only a public signature is assignable to public signature. + if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) { + return true; } - if (type.flags & TypeFlags.Instantiable) { - const constraint = getConstraintOfType(type); - if (constraint && constraint !== type) { - return typeCouldHaveTopLevelSingletonTypes(constraint); - } + if (reportErrors) { + reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility)); } - return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral) || !!(type.flags & TypeFlags.StringMapping); + return false; } + } - function getExactOptionalUnassignableProperties(source: Type, target: Type) { - if (isTupleType(source) && isTupleType(target)) return emptyArray; - return getPropertiesOfType(target) - .filter(targetProp => isExactOptionalPropertyMismatch(getTypeOfPropertyOfType(source, targetProp.escapedName), getTypeOfSymbol(targetProp))); + function typeCouldHaveTopLevelSingletonTypes(type: Type): boolean { + // Okay, yes, 'boolean' is a union of 'true | false', but that's not useful + // in error reporting scenarios. If you need to use this function but that detail matters, + // feel free to add a flag. + if (type.flags & TypeFlags.Boolean) { + return false; } - function isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined) { - return !!source && !!target && maybeTypeOfKind(source, TypeFlags.Undefined) && !!containsMissingType(target); + if (type.flags & TypeFlags.UnionOrIntersection) { + return !!forEach((type as IntersectionType).types, typeCouldHaveTopLevelSingletonTypes); } - function getExactOptionalProperties(type: Type) { - return getPropertiesOfType(type).filter(targetProp => containsMissingType(getTypeOfSymbol(targetProp))); + if (type.flags & TypeFlags.Instantiable) { + const constraint = getConstraintOfType(type); + if (constraint && constraint !== type) { + return typeCouldHaveTopLevelSingletonTypes(constraint); + } } - function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { - return findMatchingDiscriminantType(source, target, isRelatedTo, /*skipPartial*/ true) || - findMatchingTypeReferenceOrTypeAliasReference(source, target) || - findBestTypeForObjectLiteral(source, target) || - findBestTypeForInvokable(source, target) || - findMostOverlappyType(source, target); - } + return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral) || !!(type.flags & TypeFlags.StringMapping); + } - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: undefined, skipPartial?: boolean): Type | undefined; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type, skipPartial?: boolean): Type; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type, skipPartial?: boolean) { - // undefined=unknown, true=discriminated, false=not discriminated - // The state of each type progresses from left to right. Discriminated types stop at 'true'. - const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; - for (const [getDiscriminatingType, propertyName] of discriminators) { - const targetProp = getUnionOrIntersectionProperty(target, propertyName); - if (skipPartial && targetProp && getCheckFlags(targetProp) & CheckFlags.ReadPartial) { - continue; + function getExactOptionalUnassignableProperties(source: Type, target: Type) { + if (isTupleType(source) && isTupleType(target)) return emptyArray; + return getPropertiesOfType(target) + .filter(targetProp => isExactOptionalPropertyMismatch(getTypeOfPropertyOfType(source, targetProp.escapedName), getTypeOfSymbol(targetProp))); + } + + function isExactOptionalPropertyMismatch(source: Type | undefined, target: Type | undefined) { + return !!source && !!target && maybeTypeOfKind(source, TypeFlags.Undefined) && !!containsMissingType(target); + } + + function getExactOptionalProperties(type: Type) { + return getPropertiesOfType(type).filter(targetProp => containsMissingType(getTypeOfSymbol(targetProp))); + } + + function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { + return findMatchingDiscriminantType(source, target, isRelatedTo, /*skipPartial*/ true) || + findMatchingTypeReferenceOrTypeAliasReference(source, target) || + findBestTypeForObjectLiteral(source, target) || + findBestTypeForInvokable(source, target) || + findMostOverlappyType(source, target); + } + + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: undefined, skipPartial?: boolean): Type | undefined; + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type, skipPartial?: boolean): Type; + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type, skipPartial?: boolean) { + // undefined=unknown, true=discriminated, false=not discriminated + // The state of each type progresses from left to right. Discriminated types stop at 'true'. + const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; + for (const [getDiscriminatingType, propertyName] of discriminators) { + const targetProp = getUnionOrIntersectionProperty(target, propertyName); + if (skipPartial && targetProp && getCheckFlags(targetProp) & CheckFlags.ReadPartial) { + continue; + } + let i = 0; + for (const type of target.types) { + const targetType = getTypeOfPropertyOfType(type, propertyName); + if (targetType && related(getDiscriminatingType(), targetType)) { + discriminable[i] = discriminable[i] === undefined ? true : discriminable[i]; } - let i = 0; - for (const type of target.types) { - const targetType = getTypeOfPropertyOfType(type, propertyName); - if (targetType && related(getDiscriminatingType(), targetType)) { - discriminable[i] = discriminable[i] === undefined ? true : discriminable[i]; - } - else { - discriminable[i] = false; - } - i++; + else { + discriminable[i] = false; } + i++; } - const match = discriminable.indexOf(/*searchElement*/ true); - if (match === -1) { + } + const match = discriminable.indexOf(/*searchElement*/ true); + if (match === -1) { + return defaultValue; + } + // make sure exactly 1 matches before returning it + let nextMatch = discriminable.indexOf(/*searchElement*/ true, match + 1); + while (nextMatch !== -1) { + if (!isTypeIdenticalTo(target.types[match], target.types[nextMatch])) { return defaultValue; } - // make sure exactly 1 matches before returning it - let nextMatch = discriminable.indexOf(/*searchElement*/ true, match + 1); - while (nextMatch !== -1) { - if (!isTypeIdenticalTo(target.types[match], target.types[nextMatch])) { - return defaultValue; - } - nextMatch = discriminable.indexOf(/*searchElement*/ true, nextMatch + 1); - } - return target.types[match]; + nextMatch = discriminable.indexOf(/*searchElement*/ true, nextMatch + 1); } + return target.types[match]; + } - /** - * A type is 'weak' if it is an object type with at least one optional property - * and no required properties, call/construct signatures or index signatures - */ - function isWeakType(type: Type): boolean { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 && resolved.indexInfos.length === 0 && - resolved.properties.length > 0 && every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional)); - } - if (type.flags & TypeFlags.Intersection) { - return every((type as IntersectionType).types, isWeakType); - } - return false; + /** + * A type is 'weak' if it is an object type with at least one optional property + * and no required properties, call/construct signatures or index signatures + */ + function isWeakType(type: Type): boolean { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 && resolved.indexInfos.length === 0 && + resolved.properties.length > 0 && every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional)); + } + if (type.flags & TypeFlags.Intersection) { + return every((type as IntersectionType).types, isWeakType); } + return false; + } - function hasCommonProperties(source: Type, target: Type, isComparingJsxAttributes: boolean) { - for (const prop of getPropertiesOfType(source)) { - if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) { - return true; - } + function hasCommonProperties(source: Type, target: Type, isComparingJsxAttributes: boolean) { + for (const prop of getPropertiesOfType(source)) { + if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) { + return true; } - return false; } + return false; + } - function getVariances(type: GenericType): VarianceFlags[] { - // Arrays and tuples are known to be covariant, no need to spend time computing this. - return type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple ? - arrayVariances : - getVariancesWorker(type.symbol, type.typeParameters); - } + function getVariances(type: GenericType): VarianceFlags[] { + // Arrays and tuples are known to be covariant, no need to spend time computing this. + return type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple ? + arrayVariances : + getVariancesWorker(type.symbol, type.typeParameters); + } - function getAliasVariances(symbol: Symbol) { - return getVariancesWorker(symbol, getSymbolLinks(symbol).typeParameters); - } + function getAliasVariances(symbol: Symbol) { + return getVariancesWorker(symbol, getSymbolLinks(symbol).typeParameters); + } - // Return an array containing the variance of each type parameter. The variance is effectively - // a digest of the type comparisons that occur for each type argument when instantiations of the - // generic type are structurally compared. We infer the variance information by comparing - // instantiations of the generic type for type arguments with known relations. The function - // returns the emptyArray singleton when invoked recursively for the given generic type. - function getVariancesWorker(symbol: Symbol, typeParameters: readonly TypeParameter[] = emptyArray): VarianceFlags[] { - const links = getSymbolLinks(symbol); - if (!links.variances) { - tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: getTypeId(getDeclaredTypeOfSymbol(symbol)) }); - links.variances = emptyArray; - const variances = []; - for (const tp of typeParameters) { - const modifiers = getVarianceModifiers(tp); - let variance = modifiers & ModifierFlags.Out ? - modifiers & ModifierFlags.In ? VarianceFlags.Invariant : VarianceFlags.Covariant : - modifiers & ModifierFlags.In ? VarianceFlags.Contravariant : undefined; - if (variance === undefined) { - let unmeasurable = false; - let unreliable = false; - const oldHandler = outofbandVarianceMarkerHandler; - outofbandVarianceMarkerHandler = (onlyUnreliable) => onlyUnreliable ? unreliable = true : unmeasurable = true; - // We first compare instantiations where the type parameter is replaced with - // marker types that have a known subtype relationship. From this we can infer - // invariance, covariance, contravariance or bivariance. - const typeWithSuper = createMarkerType(symbol, tp, markerSuperType); - const typeWithSub = createMarkerType(symbol, tp, markerSubType); - variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) | - (isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0); - // If the instantiations appear to be related bivariantly it may be because the - // type parameter is independent (i.e. it isn't witnessed anywhere in the generic - // type). To determine this we compare instantiations where the type parameter is - // replaced with marker types that are known to be unrelated. - if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(symbol, tp, markerOtherType), typeWithSuper)) { - variance = VarianceFlags.Independent; - } - outofbandVarianceMarkerHandler = oldHandler; - if (unmeasurable || unreliable) { - if (unmeasurable) { - variance |= VarianceFlags.Unmeasurable; - } - if (unreliable) { - variance |= VarianceFlags.Unreliable; - } - } - } - variances.push(variance); - } - links.variances = variances; - tracing?.pop({ variances: variances.map(Debug.formatVariance) }); - } - return links.variances; - } + // Return an array containing the variance of each type parameter. The variance is effectively + // a digest of the type comparisons that occur for each type argument when instantiations of the + // generic type are structurally compared. We infer the variance information by comparing + // instantiations of the generic type for type arguments with known relations. The function + // returns the emptyArray singleton when invoked recursively for the given generic type. + function getVariancesWorker(symbol: Symbol, typeParameters: readonly TypeParameter[] = emptyArray): VarianceFlags[] { + const links = getSymbolLinks(symbol); + if (!links.variances) { + tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: getTypeId(getDeclaredTypeOfSymbol(symbol)) }); + links.variances = emptyArray; + const variances = []; + for (const tp of typeParameters) { + const modifiers = getVarianceModifiers(tp); + let variance = modifiers & ModifierFlags.Out ? + modifiers & ModifierFlags.In ? VarianceFlags.Invariant : VarianceFlags.Covariant : + modifiers & ModifierFlags.In ? VarianceFlags.Contravariant : undefined; + if (variance === undefined) { + let unmeasurable = false; + let unreliable = false; + const oldHandler = outofbandVarianceMarkerHandler; + outofbandVarianceMarkerHandler = (onlyUnreliable) => onlyUnreliable ? unreliable = true : unmeasurable = true; + // We first compare instantiations where the type parameter is replaced with + // marker types that have a known subtype relationship. From this we can infer + // invariance, covariance, contravariance or bivariance. + const typeWithSuper = createMarkerType(symbol, tp, markerSuperType); + const typeWithSub = createMarkerType(symbol, tp, markerSubType); + variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) | + (isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0); + // If the instantiations appear to be related bivariantly it may be because the + // type parameter is independent (i.e. it isn't witnessed anywhere in the generic + // type). To determine this we compare instantiations where the type parameter is + // replaced with marker types that are known to be unrelated. + if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(symbol, tp, markerOtherType), typeWithSuper)) { + variance = VarianceFlags.Independent; + } + outofbandVarianceMarkerHandler = oldHandler; + if (unmeasurable || unreliable) { + if (unmeasurable) { + variance |= VarianceFlags.Unmeasurable; + } + if (unreliable) { + variance |= VarianceFlags.Unreliable; + } + } + } + variances.push(variance); + } + links.variances = variances; + tracing?.pop({ variances: variances.map(Debug.formatVariance) }); + } + return links.variances; + } - function createMarkerType(symbol: Symbol, source: TypeParameter, target: Type) { - const mapper = makeUnaryTypeMapper(source, target); - const type = getDeclaredTypeOfSymbol(symbol); - if (isErrorType(type)) { - return type; - } - const result = symbol.flags & SymbolFlags.TypeAlias ? - getTypeAliasInstantiation(symbol, instantiateTypes(getSymbolLinks(symbol).typeParameters!, mapper)) : - createTypeReference(type as GenericType, instantiateTypes((type as GenericType).typeParameters, mapper)); - markerTypes.add(getTypeId(result)); - return result; + function createMarkerType(symbol: Symbol, source: TypeParameter, target: Type) { + const mapper = makeUnaryTypeMapper(source, target); + const type = getDeclaredTypeOfSymbol(symbol); + if (isErrorType(type)) { + return type; } + const result = symbol.flags & SymbolFlags.TypeAlias ? + getTypeAliasInstantiation(symbol, instantiateTypes(getSymbolLinks(symbol).typeParameters!, mapper)) : + createTypeReference(type as GenericType, instantiateTypes((type as GenericType).typeParameters, mapper)); + markerTypes.add(getTypeId(result)); + return result; + } - function isMarkerType(type: Type) { - return markerTypes.has(getTypeId(type)); - } + function isMarkerType(type: Type) { + return markerTypes.has(getTypeId(type)); + } - function getVarianceModifiers(tp: TypeParameter): ModifierFlags { - return (some(tp.symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.In)) ? ModifierFlags.In : 0) | - (some(tp.symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.Out)) ? ModifierFlags.Out: 0); - } + function getVarianceModifiers(tp: TypeParameter): ModifierFlags { + return (some(tp.symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.In)) ? ModifierFlags.In : 0) | + (some(tp.symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.Out)) ? ModifierFlags.Out: 0); + } - // Return true if the given type reference has a 'void' type argument for a covariant type parameter. - // See comment at call in recursiveTypeRelatedTo for when this case matters. - function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean { - for (let i = 0; i < variances.length; i++) { - if ((variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Covariant && typeArguments[i].flags & TypeFlags.Void) { - return true; - } + // Return true if the given type reference has a 'void' type argument for a covariant type parameter. + // See comment at call in recursiveTypeRelatedTo for when this case matters. + function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean { + for (let i = 0; i < variances.length; i++) { + if ((variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Covariant && typeArguments[i].flags & TypeFlags.Void) { + return true; } - return false; } + return false; + } - function isUnconstrainedTypeParameter(type: Type) { - return type.flags & TypeFlags.TypeParameter && !getConstraintOfTypeParameter(type as TypeParameter); - } + function isUnconstrainedTypeParameter(type: Type) { + return type.flags & TypeFlags.TypeParameter && !getConstraintOfTypeParameter(type as TypeParameter); + } - function isNonDeferredTypeReference(type: Type): type is TypeReference { - return !!(getObjectFlags(type) & ObjectFlags.Reference) && !(type as TypeReference).node; - } + function isNonDeferredTypeReference(type: Type): type is TypeReference { + return !!(getObjectFlags(type) & ObjectFlags.Reference) && !(type as TypeReference).node; + } - function isTypeReferenceWithGenericArguments(type: Type): boolean { - return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => !!(t.flags & TypeFlags.TypeParameter) || isTypeReferenceWithGenericArguments(t)); - } + function isTypeReferenceWithGenericArguments(type: Type): boolean { + return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => !!(t.flags & TypeFlags.TypeParameter) || isTypeReferenceWithGenericArguments(t)); + } - function getGenericTypeReferenceRelationKey(source: TypeReference, target: TypeReference, postFix: string, ignoreConstraints: boolean) { - const typeParameters: Type[] = []; - let constraintMarker = ""; - const sourceId = getTypeReferenceId(source, 0); - const targetId = getTypeReferenceId(target, 0); - return `${constraintMarker}${sourceId},${targetId}${postFix}`; - // getTypeReferenceId(A) returns "111=0-12=1" - // where A.id=111 and number.id=12 - function getTypeReferenceId(type: TypeReference, depth = 0) { - let result = "" + type.target.id; - for (const t of getTypeArguments(type)) { - if (t.flags & TypeFlags.TypeParameter) { - if (ignoreConstraints || isUnconstrainedTypeParameter(t)) { - let index = typeParameters.indexOf(t); - if (index < 0) { - index = typeParameters.length; - typeParameters.push(t); - } - result += "=" + index; - continue; + function getGenericTypeReferenceRelationKey(source: TypeReference, target: TypeReference, postFix: string, ignoreConstraints: boolean) { + const typeParameters: Type[] = []; + let constraintMarker = ""; + const sourceId = getTypeReferenceId(source, 0); + const targetId = getTypeReferenceId(target, 0); + return `${constraintMarker}${sourceId},${targetId}${postFix}`; + // getTypeReferenceId(A) returns "111=0-12=1" + // where A.id=111 and number.id=12 + function getTypeReferenceId(type: TypeReference, depth = 0) { + let result = "" + type.target.id; + for (const t of getTypeArguments(type)) { + if (t.flags & TypeFlags.TypeParameter) { + if (ignoreConstraints || isUnconstrainedTypeParameter(t)) { + let index = typeParameters.indexOf(t); + if (index < 0) { + index = typeParameters.length; + typeParameters.push(t); } - // We mark type references that reference constrained type parameters such that we know to obtain - // and look for a "broadest equivalent key" in the cache. - constraintMarker = "*"; - } - else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) { - result += "<" + getTypeReferenceId(t as TypeReference, depth + 1) + ">"; + result += "=" + index; continue; } - result += "-" + t.id; + // We mark type references that reference constrained type parameters such that we know to obtain + // and look for a "broadest equivalent key" in the cache. + constraintMarker = "*"; } - return result; + else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) { + result += "<" + getTypeReferenceId(t as TypeReference, depth + 1) + ">"; + continue; + } + result += "-" + t.id; } + return result; } + } - /** - * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. - * For other cases, the types ids are used. - */ - function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: ESMap, ignoreConstraints: boolean) { - if (relation === identityRelation && source.id > target.id) { - const temp = source; - source = target; - target = temp; - } - const postFix = intersectionState ? ":" + intersectionState : ""; - return isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target) ? - getGenericTypeReferenceRelationKey(source as TypeReference, target as TypeReference, postFix, ignoreConstraints) : - `${source.id},${target.id}${postFix}`; - } - - // Invoke the callback for each underlying property symbol of the given symbol and return the first - // value that isn't undefined. - function forEachProperty(prop: Symbol, callback: (p: Symbol) => T): T | undefined { - if (getCheckFlags(prop) & CheckFlags.Synthetic) { - for (const t of (prop as TransientSymbol).containingType!.types) { - const p = getPropertyOfType(t, prop.escapedName); - const result = p && forEachProperty(p, callback); - if (result) { - return result; - } + /** + * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. + * For other cases, the types ids are used. + */ + function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: ESMap, ignoreConstraints: boolean) { + if (relation === identityRelation && source.id > target.id) { + const temp = source; + source = target; + target = temp; + } + const postFix = intersectionState ? ":" + intersectionState : ""; + return isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target) ? + getGenericTypeReferenceRelationKey(source as TypeReference, target as TypeReference, postFix, ignoreConstraints) : + `${source.id},${target.id}${postFix}`; + } + + // Invoke the callback for each underlying property symbol of the given symbol and return the first + // value that isn't undefined. + function forEachProperty(prop: Symbol, callback: (p: Symbol) => T): T | undefined { + if (getCheckFlags(prop) & CheckFlags.Synthetic) { + for (const t of (prop as TransientSymbol).containingType!.types) { + const p = getPropertyOfType(t, prop.escapedName); + const result = p && forEachProperty(p, callback); + if (result) { + return result; } - return undefined; } - return callback(prop); + return undefined; } + return callback(prop); + } - // Return the declaring class type of a property or undefined if property not declared in class - function getDeclaringClass(prop: Symbol) { - return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)!) as InterfaceType : undefined; - } + // Return the declaring class type of a property or undefined if property not declared in class + function getDeclaringClass(prop: Symbol) { + return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)!) as InterfaceType : undefined; + } - // Return the inherited type of the given property or undefined if property doesn't exist in a base class. - function getTypeOfPropertyInBaseClass(property: Symbol) { - const classType = getDeclaringClass(property); - const baseClassType = classType && getBaseTypes(classType)[0]; - return baseClassType && getTypeOfPropertyOfType(baseClassType, property.escapedName); - } + // Return the inherited type of the given property or undefined if property doesn't exist in a base class. + function getTypeOfPropertyInBaseClass(property: Symbol) { + const classType = getDeclaringClass(property); + const baseClassType = classType && getBaseTypes(classType)[0]; + return baseClassType && getTypeOfPropertyOfType(baseClassType, property.escapedName); + } - // Return true if some underlying source property is declared in a class that derives - // from the given base class. - function isPropertyInClassDerivedFrom(prop: Symbol, baseClass: Type | undefined) { - return forEachProperty(prop, sp => { - const sourceClass = getDeclaringClass(sp); - return sourceClass ? hasBaseType(sourceClass, baseClass) : false; - }); - } + // Return true if some underlying source property is declared in a class that derives + // from the given base class. + function isPropertyInClassDerivedFrom(prop: Symbol, baseClass: Type | undefined) { + return forEachProperty(prop, sp => { + const sourceClass = getDeclaringClass(sp); + return sourceClass ? hasBaseType(sourceClass, baseClass) : false; + }); + } - // Return true if source property is a valid override of protected parts of target property. - function isValidOverrideOf(sourceProp: Symbol, targetProp: Symbol) { - return !forEachProperty(targetProp, tp => getDeclarationModifierFlagsFromSymbol(tp) & ModifierFlags.Protected ? - !isPropertyInClassDerivedFrom(sourceProp, getDeclaringClass(tp)) : false); - } - - // Return true if the given class derives from each of the declaring classes of the protected - // constituents of the given property. - function isClassDerivedFromDeclaringClasses(checkClass: T, prop: Symbol, writing: boolean) { - return forEachProperty(prop, p => getDeclarationModifierFlagsFromSymbol(p, writing) & ModifierFlags.Protected ? - !hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass; - } - - // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons - // for maxDepth or more occurrences or instantiations of the type have been recorded on the given stack. It is possible, - // though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely - // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth - // levels, but unequal at some level beyond that. - // In addition, this will also detect when an indexed access has been chained off of maxDepth more times (which is - // essentially the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding - // false positives for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`). - // It also detects when a recursive type reference has expanded maxDepth or more times, e.g. if the true branch of - // `type A = null extends T ? [A>] : [T]` - // has expanded into `[A>>>>>]`. In such cases we need - // to terminate the expansion, and we do so here. - function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean { - if (depth >= maxDepth) { - const identity = getRecursionIdentity(type); - let count = 0; - let lastTypeId = 0; - for (let i = 0; i < depth; i++) { - const t = stack[i]; - if (getRecursionIdentity(t) === identity) { - // We only count occurrences with a higher type id than the previous occurrence, since higher - // type ids are an indicator of newer instantiations caused by recursion. - if (t.id >= lastTypeId) { - count++; - if (count >= maxDepth) { - return true; - } + // Return true if source property is a valid override of protected parts of target property. + function isValidOverrideOf(sourceProp: Symbol, targetProp: Symbol) { + return !forEachProperty(targetProp, tp => getDeclarationModifierFlagsFromSymbol(tp) & ModifierFlags.Protected ? + !isPropertyInClassDerivedFrom(sourceProp, getDeclaringClass(tp)) : false); + } + + // Return true if the given class derives from each of the declaring classes of the protected + // constituents of the given property. + function isClassDerivedFromDeclaringClasses(checkClass: T, prop: Symbol, writing: boolean) { + return forEachProperty(prop, p => getDeclarationModifierFlagsFromSymbol(p, writing) & ModifierFlags.Protected ? + !hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass; + } + + // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons + // for maxDepth or more occurrences or instantiations of the type have been recorded on the given stack. It is possible, + // though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely + // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth + // levels, but unequal at some level beyond that. + // In addition, this will also detect when an indexed access has been chained off of maxDepth more times (which is + // essentially the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding + // false positives for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`). + // It also detects when a recursive type reference has expanded maxDepth or more times, e.g. if the true branch of + // `type A = null extends T ? [A>] : [T]` + // has expanded into `[A>>>>>]`. In such cases we need + // to terminate the expansion, and we do so here. + function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean { + if (depth >= maxDepth) { + const identity = getRecursionIdentity(type); + let count = 0; + let lastTypeId = 0; + for (let i = 0; i < depth; i++) { + const t = stack[i]; + if (getRecursionIdentity(t) === identity) { + // We only count occurrences with a higher type id than the previous occurrence, since higher + // type ids are an indicator of newer instantiations caused by recursion. + if (t.id >= lastTypeId) { + count++; + if (count >= maxDepth) { + return true; } - lastTypeId = t.id; } + lastTypeId = t.id; } } - return false; } + return false; + } - // The recursion identity of a type is an object identity that is shared among multiple instantiations of the type. - // We track recursion identities in order to identify deeply nested and possibly infinite type instantiations with - // the same origin. For example, when type parameters are in scope in an object type such as { x: T }, all - // instantiations of that type have the same recursion identity. The default recursion identity is the object - // identity of the type, meaning that every type is unique. Generally, types with constituents that could circularly - // reference the type have a recursion identity that differs from the object identity. - function getRecursionIdentity(type: Type): object { - // Object and array literals are known not to contain recursive references and don't need a recursion identity. - if (type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) { - if (getObjectFlags(type) && ObjectFlags.Reference && (type as TypeReference).node) { - // Deferred type references are tracked through their associated AST node. This gives us finer - // granularity than using their associated target because each manifest type reference has a - // unique AST node. - return (type as TypeReference).node!; - } - if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) { - // We track all object types that have an associated symbol (representing the origin of the type), but - // exclude the static side of classes from this check since it shares its symbol with the instance side. - return type.symbol; - } - if (isTupleType(type)) { - // Tuple types are tracked through their target type - return type.target; - } - } - if (type.flags & TypeFlags.TypeParameter) { + // The recursion identity of a type is an object identity that is shared among multiple instantiations of the type. + // We track recursion identities in order to identify deeply nested and possibly infinite type instantiations with + // the same origin. For example, when type parameters are in scope in an object type such as { x: T }, all + // instantiations of that type have the same recursion identity. The default recursion identity is the object + // identity of the type, meaning that every type is unique. Generally, types with constituents that could circularly + // reference the type have a recursion identity that differs from the object identity. + function getRecursionIdentity(type: Type): object { + // Object and array literals are known not to contain recursive references and don't need a recursion identity. + if (type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) { + if (getObjectFlags(type) && ObjectFlags.Reference && (type as TypeReference).node) { + // Deferred type references are tracked through their associated AST node. This gives us finer + // granularity than using their associated target because each manifest type reference has a + // unique AST node. + return (type as TypeReference).node!; + } + if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) { + // We track all object types that have an associated symbol (representing the origin of the type), but + // exclude the static side of classes from this check since it shares its symbol with the instance side. return type.symbol; } - if (type.flags & TypeFlags.IndexedAccess) { - // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P][Q] it is A - do { - type = (type as IndexedAccessType).objectType; - } while (type.flags & TypeFlags.IndexedAccess); - return type; - } - if (type.flags & TypeFlags.Conditional) { - // The root object represents the origin of the conditional type - return (type as ConditionalType).root; + if (isTupleType(type)) { + // Tuple types are tracked through their target type + return type.target; } + } + if (type.flags & TypeFlags.TypeParameter) { + return type.symbol; + } + if (type.flags & TypeFlags.IndexedAccess) { + // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P][Q] it is A + do { + type = (type as IndexedAccessType).objectType; + } while (type.flags & TypeFlags.IndexedAccess); return type; } - - function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean { - return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False; + if (type.flags & TypeFlags.Conditional) { + // The root object represents the origin of the conditional type + return (type as ConditionalType).root; } + return type; + } - function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary { - // Two members are considered identical when - // - they are public properties with identical names, optionality, and types, - // - they are private or protected properties originating in the same declaration and having identical types - if (sourceProp === targetProp) { - return Ternary.True; - } - const sourcePropAccessibility = getDeclarationModifierFlagsFromSymbol(sourceProp) & ModifierFlags.NonPublicAccessibilityModifier; - const targetPropAccessibility = getDeclarationModifierFlagsFromSymbol(targetProp) & ModifierFlags.NonPublicAccessibilityModifier; - if (sourcePropAccessibility !== targetPropAccessibility) { + function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean { + return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False; + } + + function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary { + // Two members are considered identical when + // - they are public properties with identical names, optionality, and types, + // - they are private or protected properties originating in the same declaration and having identical types + if (sourceProp === targetProp) { + return Ternary.True; + } + const sourcePropAccessibility = getDeclarationModifierFlagsFromSymbol(sourceProp) & ModifierFlags.NonPublicAccessibilityModifier; + const targetPropAccessibility = getDeclarationModifierFlagsFromSymbol(targetProp) & ModifierFlags.NonPublicAccessibilityModifier; + if (sourcePropAccessibility !== targetPropAccessibility) { + return Ternary.False; + } + if (sourcePropAccessibility) { + if (getTargetSymbol(sourceProp) !== getTargetSymbol(targetProp)) { return Ternary.False; } - if (sourcePropAccessibility) { - if (getTargetSymbol(sourceProp) !== getTargetSymbol(targetProp)) { - return Ternary.False; - } - } - else { - if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { - return Ternary.False; - } - } - if (isReadonlySymbol(sourceProp) !== isReadonlySymbol(targetProp)) { + } + else { + if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { return Ternary.False; } - return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); - } - - function isMatchingSignature(source: Signature, target: Signature, partialMatch: boolean) { - const sourceParameterCount = getParameterCount(source); - const targetParameterCount = getParameterCount(target); - const sourceMinArgumentCount = getMinArgumentCount(source); - const targetMinArgumentCount = getMinArgumentCount(target); - const sourceHasRestParameter = hasEffectiveRestParameter(source); - const targetHasRestParameter = hasEffectiveRestParameter(target); - // A source signature matches a target signature if the two signatures have the same number of required, - // optional, and rest parameters. - if (sourceParameterCount === targetParameterCount && - sourceMinArgumentCount === targetMinArgumentCount && - sourceHasRestParameter === targetHasRestParameter) { - return true; - } - // A source signature partially matches a target signature if the target signature has no fewer required - // parameters - if (partialMatch && sourceMinArgumentCount <= targetMinArgumentCount) { - return true; - } - return false; } + if (isReadonlySymbol(sourceProp) !== isReadonlySymbol(targetProp)) { + return Ternary.False; + } + return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); + } + + function isMatchingSignature(source: Signature, target: Signature, partialMatch: boolean) { + const sourceParameterCount = getParameterCount(source); + const targetParameterCount = getParameterCount(target); + const sourceMinArgumentCount = getMinArgumentCount(source); + const targetMinArgumentCount = getMinArgumentCount(target); + const sourceHasRestParameter = hasEffectiveRestParameter(source); + const targetHasRestParameter = hasEffectiveRestParameter(target); + // A source signature matches a target signature if the two signatures have the same number of required, + // optional, and rest parameters. + if (sourceParameterCount === targetParameterCount && + sourceMinArgumentCount === targetMinArgumentCount && + sourceHasRestParameter === targetHasRestParameter) { + return true; + } + // A source signature partially matches a target signature if the target signature has no fewer required + // parameters + if (partialMatch && sourceMinArgumentCount <= targetMinArgumentCount) { + return true; + } + return false; + } - /** - * See signatureRelatedTo, compareSignaturesIdentical - */ - function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { - // TODO (drosen): De-duplicate code between related functions. - if (source === target) { - return Ternary.True; - } - if (!(isMatchingSignature(source, target, partialMatch))) { - return Ternary.False; - } - // Check that the two signatures have the same number of type parameters. - if (length(source.typeParameters) !== length(target.typeParameters)) { - return Ternary.False; - } - // 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; - } + /** + * See signatureRelatedTo, compareSignaturesIdentical + */ + function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { + // TODO (drosen): De-duplicate code between related functions. + if (source === target) { + return Ternary.True; + } + if (!(isMatchingSignature(source, target, partialMatch))) { + return Ternary.False; + } + // Check that the two signatures have the same number of type parameters. + if (length(source.typeParameters) !== length(target.typeParameters)) { + return Ternary.False; + } + // 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) { - const targetThisType = getThisTypeOfSignature(target); - if (targetThisType) { - const related = compareTypes(sourceThisType, targetThisType); - if (!related) { - return Ternary.False; - } - result &= related; + source = instantiateSignature(source, mapper, /*eraseTypeParameters*/ true); + } + let result = Ternary.True; + if (!ignoreThisTypes) { + const sourceThisType = getThisTypeOfSignature(source); + if (sourceThisType) { + const targetThisType = getThisTypeOfSignature(target); + if (targetThisType) { + const related = compareTypes(sourceThisType, targetThisType); + if (!related) { + return Ternary.False; } + result &= related; } } - const targetLen = getParameterCount(target); - for (let i = 0; i < targetLen; i++) { - const s = getTypeAtPosition(source, i); - const t = getTypeAtPosition(target, i); - const related = compareTypes(t, s); - if (!related) { - return Ternary.False; - } - result &= related; - } - if (!ignoreReturnTypes) { - const sourceTypePredicate = getTypePredicateOfSignature(source); - const targetTypePredicate = getTypePredicateOfSignature(target); - result &= sourceTypePredicate || targetTypePredicate ? - compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes) : - compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); + } + const targetLen = getParameterCount(target); + for (let i = 0; i < targetLen; i++) { + const s = getTypeAtPosition(source, i); + const t = getTypeAtPosition(target, i); + const related = compareTypes(t, s); + if (!related) { + return Ternary.False; } - return result; + result &= related; } - - function compareTypePredicatesIdentical(source: TypePredicate | undefined, target: TypePredicate | undefined, compareTypes: (s: Type, t: Type) => Ternary): Ternary { - return !(source && target && typePredicateKindsMatch(source, target)) ? Ternary.False : - source.type === target.type ? Ternary.True : - source.type && target.type ? compareTypes(source.type, target.type) : - Ternary.False; + if (!ignoreReturnTypes) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + const targetTypePredicate = getTypePredicateOfSignature(target); + result &= sourceTypePredicate || targetTypePredicate ? + compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes) : + compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); } + return result; + } - function literalTypesWithSameBaseType(types: Type[]): boolean { - let commonBaseType: Type | undefined; - for (const t of types) { - if (!(t.flags & TypeFlags.Never)) { - const baseType = getBaseTypeOfLiteralType(t); - commonBaseType ??= baseType; - if (baseType === t || baseType !== commonBaseType) { - return false; - } + function compareTypePredicatesIdentical(source: TypePredicate | undefined, target: TypePredicate | undefined, compareTypes: (s: Type, t: Type) => Ternary): Ternary { + return !(source && target && typePredicateKindsMatch(source, target)) ? Ternary.False : + source.type === target.type ? Ternary.True : + source.type && target.type ? compareTypes(source.type, target.type) : + Ternary.False; + } + + function literalTypesWithSameBaseType(types: Type[]): boolean { + let commonBaseType: Type | undefined; + for (const t of types) { + if (!(t.flags & TypeFlags.Never)) { + const baseType = getBaseTypeOfLiteralType(t); + commonBaseType ??= baseType; + if (baseType === t || baseType !== commonBaseType) { + return false; } } - return true; } + return true; + } - function getCombinedTypeFlags(types: Type[]): TypeFlags { - return reduceLeft(types, (flags, t) => flags | (t.flags & TypeFlags.Union ? getCombinedTypeFlags((t as UnionType).types) : t.flags), 0); - } + function getCombinedTypeFlags(types: Type[]): TypeFlags { + return reduceLeft(types, (flags, t) => flags | (t.flags & TypeFlags.Union ? getCombinedTypeFlags((t as UnionType).types) : t.flags), 0); + } - function getCommonSupertype(types: Type[]): Type { - if (types.length === 1) { - return types[0]; - } - // Remove nullable types from each of the candidates. - const primaryTypes = strictNullChecks ? sameMap(types, t => filterType(t, u => !(u.flags & TypeFlags.Nullable))) : types; - // When the candidate types are all literal types with the same base type, return a union - // of those literal types. Otherwise, return the leftmost type for which no type to the - // right is a supertype. - const superTypeOrUnion = literalTypesWithSameBaseType(primaryTypes) ? - getUnionType(primaryTypes) : - reduceLeft(primaryTypes, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!; - // Add any nullable types that occurred in the candidates back to the result. - return primaryTypes === types ? superTypeOrUnion : getNullableType(superTypeOrUnion, getCombinedTypeFlags(types) & TypeFlags.Nullable); - } + function getCommonSupertype(types: Type[]): Type { + if (types.length === 1) { + return types[0]; + } + // Remove nullable types from each of the candidates. + const primaryTypes = strictNullChecks ? sameMap(types, t => filterType(t, u => !(u.flags & TypeFlags.Nullable))) : types; + // When the candidate types are all literal types with the same base type, return a union + // of those literal types. Otherwise, return the leftmost type for which no type to the + // right is a supertype. + const superTypeOrUnion = literalTypesWithSameBaseType(primaryTypes) ? + getUnionType(primaryTypes) : + reduceLeft(primaryTypes, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!; + // Add any nullable types that occurred in the candidates back to the result. + return primaryTypes === types ? superTypeOrUnion : getNullableType(superTypeOrUnion, getCombinedTypeFlags(types) & TypeFlags.Nullable); + } - // Return the leftmost type for which no type to the right is a subtype. - function getCommonSubtype(types: Type[]) { - return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s)!; - } + // Return the leftmost type for which no type to the right is a subtype. + function getCommonSubtype(types: Type[]) { + return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s)!; + } - function isArrayType(type: Type): type is TypeReference { - return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((type as TypeReference).target === globalArrayType || (type as TypeReference).target === globalReadonlyArrayType); - } + function isArrayType(type: Type): type is TypeReference { + return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((type as TypeReference).target === globalArrayType || (type as TypeReference).target === globalReadonlyArrayType); + } - function isReadonlyArrayType(type: Type): boolean { - return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type as TypeReference).target === globalReadonlyArrayType; - } + function isReadonlyArrayType(type: Type): boolean { + return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type as TypeReference).target === globalReadonlyArrayType; + } - function isArrayOrTupleType(type: Type): type is TypeReference { - return isArrayType(type) || isTupleType(type); - } + function isArrayOrTupleType(type: Type): type is TypeReference { + return isArrayType(type) || isTupleType(type); + } - function isMutableArrayOrTuple(type: Type): boolean { - return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly; - } + function isMutableArrayOrTuple(type: Type): boolean { + return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly; + } - function getElementTypeOfArrayType(type: Type): Type | undefined { - return isArrayType(type) ? getTypeArguments(type)[0] : undefined; - } + function getElementTypeOfArrayType(type: Type): Type | undefined { + return isArrayType(type) ? getTypeArguments(type)[0] : undefined; + } - function isArrayLikeType(type: Type): boolean { - // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, - // or if it is not the undefined or null type and if it is assignable to ReadonlyArray - return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType); - } + function isArrayLikeType(type: Type): boolean { + // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, + // or if it is not the undefined or null type and if it is assignable to ReadonlyArray + return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType); + } - function getSingleBaseForNonAugmentingSubtype(type: Type) { - if (!(getObjectFlags(type) & ObjectFlags.Reference) || !(getObjectFlags((type as TypeReference).target) & ObjectFlags.ClassOrInterface)) { - return undefined; - } - if (getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeCalculated) { - return getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeExists ? (type as TypeReference).cachedEquivalentBaseType : undefined; - } - (type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeCalculated; - const target = (type as TypeReference).target as InterfaceType; - if (getObjectFlags(target) & ObjectFlags.Class) { - const baseTypeNode = getBaseTypeNodeOfClass(target); - // A base type expression may circularly reference the class itself (e.g. as an argument to function call), so we only - // check for base types specified as simple qualified names. - if (baseTypeNode && baseTypeNode.expression.kind !== SyntaxKind.Identifier && baseTypeNode.expression.kind !== SyntaxKind.PropertyAccessExpression) { - return undefined; - } - } - const bases = getBaseTypes(target); - if (bases.length !== 1) { + function getSingleBaseForNonAugmentingSubtype(type: Type) { + if (!(getObjectFlags(type) & ObjectFlags.Reference) || !(getObjectFlags((type as TypeReference).target) & ObjectFlags.ClassOrInterface)) { + return undefined; + } + if (getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeCalculated) { + return getObjectFlags(type) & ObjectFlags.IdenticalBaseTypeExists ? (type as TypeReference).cachedEquivalentBaseType : undefined; + } + (type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeCalculated; + const target = (type as TypeReference).target as InterfaceType; + if (getObjectFlags(target) & ObjectFlags.Class) { + const baseTypeNode = getBaseTypeNodeOfClass(target); + // A base type expression may circularly reference the class itself (e.g. as an argument to function call), so we only + // check for base types specified as simple qualified names. + if (baseTypeNode && baseTypeNode.expression.kind !== SyntaxKind.Identifier && baseTypeNode.expression.kind !== SyntaxKind.PropertyAccessExpression) { return undefined; } - if (getMembersOfSymbol(type.symbol).size) { - return undefined; // If the interface has any members, they may subtype members in the base, so we should do a full structural comparison - } - let instantiatedBase = !length(target.typeParameters) ? bases[0] : instantiateType(bases[0], createTypeMapper(target.typeParameters!, getTypeArguments(type as TypeReference).slice(0, target.typeParameters!.length))); - if (length(getTypeArguments(type as TypeReference)) > length(target.typeParameters)) { - instantiatedBase = getTypeWithThisArgument(instantiatedBase, last(getTypeArguments(type as TypeReference))); - } - (type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeExists; - return (type as TypeReference).cachedEquivalentBaseType = instantiatedBase; } + const bases = getBaseTypes(target); + if (bases.length !== 1) { + return undefined; + } + if (getMembersOfSymbol(type.symbol).size) { + return undefined; // If the interface has any members, they may subtype members in the base, so we should do a full structural comparison + } + let instantiatedBase = !length(target.typeParameters) ? bases[0] : instantiateType(bases[0], createTypeMapper(target.typeParameters!, getTypeArguments(type as TypeReference).slice(0, target.typeParameters!.length))); + if (length(getTypeArguments(type as TypeReference)) > length(target.typeParameters)) { + instantiatedBase = getTypeWithThisArgument(instantiatedBase, last(getTypeArguments(type as TypeReference))); + } + (type as TypeReference).objectFlags |= ObjectFlags.IdenticalBaseTypeExists; + return (type as TypeReference).cachedEquivalentBaseType = instantiatedBase; + } + + function isEmptyLiteralType(type: Type): boolean { + return strictNullChecks ? type === implicitNeverType : type === undefinedWideningType; + } + + function isEmptyArrayLiteralType(type: Type): boolean { + const elementType = getElementTypeOfArrayType(type); + return !!elementType && isEmptyLiteralType(elementType); + } + + function isTupleLikeType(type: Type): boolean { + return isTupleType(type) || !!getPropertyOfType(type, "0" as __String); + } + + function isArrayOrTupleLikeType(type: Type): boolean { + return isArrayLikeType(type) || isTupleLikeType(type); + } - function isEmptyLiteralType(type: Type): boolean { - return strictNullChecks ? type === implicitNeverType : type === undefinedWideningType; + function getTupleElementType(type: Type, index: number) { + const propType = getTypeOfPropertyOfType(type, "" + index as __String); + if (propType) { + return propType; + } + if (everyType(type, isTupleType)) { + return mapType(type, t => getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType); } + return undefined; + } - function isEmptyArrayLiteralType(type: Type): boolean { - const elementType = getElementTypeOfArrayType(type); - return !!elementType && isEmptyLiteralType(elementType); - } + function isNeitherUnitTypeNorNever(type: Type): boolean { + return !(type.flags & (TypeFlags.Unit | TypeFlags.Never)); + } - function isTupleLikeType(type: Type): boolean { - return isTupleType(type) || !!getPropertyOfType(type, "0" as __String); - } + function isUnitType(type: Type): boolean { + return !!(type.flags & TypeFlags.Unit); + } - function isArrayOrTupleLikeType(type: Type): boolean { - return isArrayLikeType(type) || isTupleLikeType(type); - } + function isUnitLikeType(type: Type): boolean { + return isUnitType(getBaseConstraintOrType(type)); + } - function getTupleElementType(type: Type, index: number) { - const propType = getTypeOfPropertyOfType(type, "" + index as __String); - if (propType) { - return propType; - } - if (everyType(type, isTupleType)) { - return mapType(type, t => getRestTypeOfTupleType(t as TupleTypeReference) || undefinedType); - } - return undefined; - } + function extractUnitType(type: Type) { + return type.flags & TypeFlags.Intersection ? find((type as IntersectionType).types, isUnitType) || type : type; + } - function isNeitherUnitTypeNorNever(type: Type): boolean { - return !(type.flags & (TypeFlags.Unit | TypeFlags.Never)); - } + function isLiteralType(type: Type): boolean { + return type.flags & TypeFlags.Boolean ? true : + type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((type as UnionType).types, isUnitType) : + isUnitType(type); + } - function isUnitType(type: Type): boolean { - return !!(type.flags & TypeFlags.Unit); - } + function getBaseTypeOfLiteralType(type: Type): Type { + return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type as LiteralType) : + type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType : + type.flags & TypeFlags.NumberLiteral ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.BooleanLiteral ? booleanType : + type.flags & TypeFlags.Union ? getBaseTypeOfLiteralTypeUnion(type as UnionType) : + type; + } - function isUnitLikeType(type: Type): boolean { - return isUnitType(getBaseConstraintOrType(type)); - } + function getBaseTypeOfLiteralTypeUnion(type: UnionType) { + const key = `B${getTypeId(type)}`; + return getCachedType(key) ?? setCachedType(key, mapType(type, getBaseTypeOfLiteralType)); + } - function extractUnitType(type: Type) { - return type.flags & TypeFlags.Intersection ? find((type as IntersectionType).types, isUnitType) || type : type; - } + function getWidenedLiteralType(type: Type): Type { + return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(type as LiteralType) : + type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType : + type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType : + type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType : + type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType : + type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedLiteralType) : + type; + } - function isLiteralType(type: Type): boolean { - return type.flags & TypeFlags.Boolean ? true : - type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((type as UnionType).types, isUnitType) : - isUnitType(type); - } + function getWidenedUniqueESSymbolType(type: Type): Type { + return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedUniqueESSymbolType) : + type; + } - function getBaseTypeOfLiteralType(type: Type): Type { - return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type as LiteralType) : - type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType : - type.flags & TypeFlags.NumberLiteral ? numberType : - type.flags & TypeFlags.BigIntLiteral ? bigintType : - type.flags & TypeFlags.BooleanLiteral ? booleanType : - type.flags & TypeFlags.Union ? getBaseTypeOfLiteralTypeUnion(type as UnionType) : - type; + function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) { + if (!isLiteralOfContextualType(type, contextualType)) { + type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); } + return getRegularTypeOfLiteralType(type); + } - function getBaseTypeOfLiteralTypeUnion(type: UnionType) { - const key = `B${getTypeId(type)}`; - return getCachedType(key) ?? setCachedType(key, mapType(type, getBaseTypeOfLiteralType)); + function getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, isAsync: boolean) { + if (type && isUnitType(type)) { + const contextualType = !contextualSignatureReturnType ? undefined : + isAsync ? getPromisedTypeOfPromise(contextualSignatureReturnType) : + contextualSignatureReturnType; + type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); } + return type; + } - function getWidenedLiteralType(type: Type): Type { - return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(type as LiteralType) : - type.flags & TypeFlags.StringLiteral && isFreshLiteralType(type) ? stringType : - type.flags & TypeFlags.NumberLiteral && isFreshLiteralType(type) ? numberType : - type.flags & TypeFlags.BigIntLiteral && isFreshLiteralType(type) ? bigintType : - type.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(type) ? booleanType : - type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedLiteralType) : - type; + function getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, kind: IterationTypeKind, isAsyncGenerator: boolean) { + if (type && isUnitType(type)) { + const contextualType = !contextualSignatureReturnType ? undefined : + getIterationTypeOfGeneratorFunctionReturnType(kind, contextualSignatureReturnType, isAsyncGenerator); + type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); } + return type; + } - function getWidenedUniqueESSymbolType(type: Type): Type { - return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : - type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedUniqueESSymbolType) : - type; - } + /** + * Check if a Type was written as a tuple type literal. + * Prefer using isTupleLikeType() unless the use of `elementTypes`/`getTypeArguments` is required. + */ + function isTupleType(type: Type): type is TupleTypeReference { + return !!(getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target.objectFlags & ObjectFlags.Tuple); + } - function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) { - if (!isLiteralOfContextualType(type, contextualType)) { - type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); - } - return getRegularTypeOfLiteralType(type); - } + function isGenericTupleType(type: Type): type is TupleTypeReference { + return isTupleType(type) && !!(type.target.combinedFlags & ElementFlags.Variadic); + } - function getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, isAsync: boolean) { - if (type && isUnitType(type)) { - const contextualType = !contextualSignatureReturnType ? undefined : - isAsync ? getPromisedTypeOfPromise(contextualSignatureReturnType) : - contextualSignatureReturnType; - type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); - } - return type; - } + function isSingleElementGenericTupleType(type: Type): type is TupleTypeReference { + return isGenericTupleType(type) && type.target.elementFlags.length === 1; + } - function getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, kind: IterationTypeKind, isAsyncGenerator: boolean) { - if (type && isUnitType(type)) { - const contextualType = !contextualSignatureReturnType ? undefined : - getIterationTypeOfGeneratorFunctionReturnType(kind, contextualSignatureReturnType, isAsyncGenerator); - type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); - } - return type; - } + function getRestTypeOfTupleType(type: TupleTypeReference) { + return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength); + } - /** - * Check if a Type was written as a tuple type literal. - * Prefer using isTupleLikeType() unless the use of `elementTypes`/`getTypeArguments` is required. - */ - function isTupleType(type: Type): type is TupleTypeReference { - return !!(getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target.objectFlags & ObjectFlags.Tuple); - } + function getRestArrayTypeOfTupleType(type: TupleTypeReference) { + const restType = getRestTypeOfTupleType(type); + return restType && createArrayType(restType); + } - function isGenericTupleType(type: Type): type is TupleTypeReference { - return isTupleType(type) && !!(type.target.combinedFlags & ElementFlags.Variadic); + function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false) { + const length = getTypeReferenceArity(type) - endSkipCount; + if (index < length) { + const typeArguments = getTypeArguments(type); + const elementTypes: Type[] = []; + for (let i = index; i < length; i++) { + const t = typeArguments[i]; + elementTypes.push(type.target.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t); + } + return writing ? getIntersectionType(elementTypes) : getUnionType(elementTypes); } + return undefined; + } - function isSingleElementGenericTupleType(type: Type): type is TupleTypeReference { - return isGenericTupleType(type) && type.target.elementFlags.length === 1; - } + function isTupleTypeStructureMatching(t1: TupleTypeReference, t2: TupleTypeReference) { + return getTypeReferenceArity(t1) === getTypeReferenceArity(t2) && + every(t1.target.elementFlags, (f, i) => (f & ElementFlags.Variable) === (t2.target.elementFlags[i] & ElementFlags.Variable)); + } - function getRestTypeOfTupleType(type: TupleTypeReference) { - return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength); - } + function isZeroBigInt({value}: BigIntLiteralType) { + return value.base10Value === "0"; + } - function getRestArrayTypeOfTupleType(type: TupleTypeReference) { - const restType = getRestTypeOfTupleType(type); - return restType && createArrayType(restType); - } + function removeDefinitelyFalsyTypes(type: Type): Type { + return filterType(type, t => !!(getTypeFacts(t) & TypeFacts.Truthy)); + } - function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false) { - const length = getTypeReferenceArity(type) - endSkipCount; - if (index < length) { - const typeArguments = getTypeArguments(type); - const elementTypes: Type[] = []; - for (let i = index; i < length; i++) { - const t = typeArguments[i]; - elementTypes.push(type.target.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t); - } - return writing ? getIntersectionType(elementTypes) : getUnionType(elementTypes); - } - return undefined; - } + function extractDefinitelyFalsyTypes(type: Type): Type { + return mapType(type, getDefinitelyFalsyPartOfType); + } - function isTupleTypeStructureMatching(t1: TupleTypeReference, t2: TupleTypeReference) { - return getTypeReferenceArity(t1) === getTypeReferenceArity(t2) && - every(t1.target.elementFlags, (f, i) => (f & ElementFlags.Variable) === (t2.target.elementFlags[i] & ElementFlags.Variable)); - } + function getDefinitelyFalsyPartOfType(type: Type): Type { + return type.flags & TypeFlags.String ? emptyStringType : + type.flags & TypeFlags.Number ? zeroType : + type.flags & TypeFlags.BigInt ? zeroBigIntType : + type === regularFalseType || + type === falseType || + type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null | TypeFlags.AnyOrUnknown) || + type.flags & TypeFlags.StringLiteral && (type as StringLiteralType).value === "" || + type.flags & TypeFlags.NumberLiteral && (type as NumberLiteralType).value === 0 || + type.flags & TypeFlags.BigIntLiteral && isZeroBigInt(type as BigIntLiteralType) ? type : + neverType; + } - function isZeroBigInt({value}: BigIntLiteralType) { - return value.base10Value === "0"; - } + /** + * Add undefined or null or both to a type if they are missing. + * @param type - type to add undefined and/or null to if not present + * @param flags - Either TypeFlags.Undefined or TypeFlags.Null, or both + */ + function getNullableType(type: Type, flags: TypeFlags): Type { + const missing = (flags & ~type.flags) & (TypeFlags.Undefined | TypeFlags.Null); + return missing === 0 ? type : + missing === TypeFlags.Undefined ? getUnionType([type, undefinedType]) : + missing === TypeFlags.Null ? getUnionType([type, nullType]) : + getUnionType([type, undefinedType, nullType]); + } - function removeDefinitelyFalsyTypes(type: Type): Type { - return filterType(type, t => !!(getTypeFacts(t) & TypeFacts.Truthy)); - } + function getOptionalType(type: Type, isProperty = false): Type { + Debug.assert(strictNullChecks); + const missingOrUndefined = isProperty ? missingType : undefinedType; + return type.flags & TypeFlags.Undefined || type.flags & TypeFlags.Union && (type as UnionType).types[0] === missingOrUndefined ? type : getUnionType([type, missingOrUndefined]); + } - function extractDefinitelyFalsyTypes(type: Type): Type { - return mapType(type, getDefinitelyFalsyPartOfType); + function getGlobalNonNullableTypeInstantiation(type: Type) { + if (!deferredGlobalNonNullableTypeAlias) { + deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol; } + return deferredGlobalNonNullableTypeAlias !== unknownSymbol ? + getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]) : + getIntersectionType([type, emptyObjectType]); + } - function getDefinitelyFalsyPartOfType(type: Type): Type { - return type.flags & TypeFlags.String ? emptyStringType : - type.flags & TypeFlags.Number ? zeroType : - type.flags & TypeFlags.BigInt ? zeroBigIntType : - type === regularFalseType || - type === falseType || - type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null | TypeFlags.AnyOrUnknown) || - type.flags & TypeFlags.StringLiteral && (type as StringLiteralType).value === "" || - type.flags & TypeFlags.NumberLiteral && (type as NumberLiteralType).value === 0 || - type.flags & TypeFlags.BigIntLiteral && isZeroBigInt(type as BigIntLiteralType) ? type : - neverType; - } + function getNonNullableType(type: Type): Type { + return strictNullChecks ? getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } - /** - * Add undefined or null or both to a type if they are missing. - * @param type - type to add undefined and/or null to if not present - * @param flags - Either TypeFlags.Undefined or TypeFlags.Null, or both - */ - function getNullableType(type: Type, flags: TypeFlags): Type { - const missing = (flags & ~type.flags) & (TypeFlags.Undefined | TypeFlags.Null); - return missing === 0 ? type : - missing === TypeFlags.Undefined ? getUnionType([type, undefinedType]) : - missing === TypeFlags.Null ? getUnionType([type, nullType]) : - getUnionType([type, undefinedType, nullType]); - } + function addOptionalTypeMarker(type: Type) { + return strictNullChecks ? getUnionType([type, optionalType]) : type; + } - function getOptionalType(type: Type, isProperty = false): Type { - Debug.assert(strictNullChecks); - const missingOrUndefined = isProperty ? missingType : undefinedType; - return type.flags & TypeFlags.Undefined || type.flags & TypeFlags.Union && (type as UnionType).types[0] === missingOrUndefined ? type : getUnionType([type, missingOrUndefined]); - } + function removeOptionalTypeMarker(type: Type): Type { + return strictNullChecks ? removeType(type, optionalType) : type; + } - function getGlobalNonNullableTypeInstantiation(type: Type) { - if (!deferredGlobalNonNullableTypeAlias) { - deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol; - } - return deferredGlobalNonNullableTypeAlias !== unknownSymbol ? - getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]) : - getIntersectionType([type, emptyObjectType]); - } + function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) { + return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type; + } - function getNonNullableType(type: Type): Type { - return strictNullChecks ? getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; - } + function getOptionalExpressionType(exprType: Type, expression: Expression) { + return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) : + isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : + exprType; + } - function addOptionalTypeMarker(type: Type) { - return strictNullChecks ? getUnionType([type, optionalType]) : type; - } + function removeMissingType(type: Type, isOptional: boolean) { + return exactOptionalPropertyTypes && isOptional ? removeType(type, missingType) : type; + } - function removeOptionalTypeMarker(type: Type): Type { - return strictNullChecks ? removeType(type, optionalType) : type; - } + function containsMissingType(type: Type) { + return exactOptionalPropertyTypes && (type === missingType || type.flags & TypeFlags.Union && containsType((type as UnionType).types, missingType)); + } - function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) { - return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type; - } + function removeMissingOrUndefinedType(type: Type): Type { + return exactOptionalPropertyTypes ? removeType(type, missingType) : getTypeWithFacts(type, TypeFacts.NEUndefined); + } - function getOptionalExpressionType(exprType: Type, expression: Expression) { - return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) : - isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : - exprType; - } + /** + * Is source potentially coercible to target type under `==`. + * Assumes that `source` is a constituent of a union, hence + * the boolean literal flag on the LHS, but not on the RHS. + * + * This does not fully replicate the semantics of `==`. The + * intention is to catch cases that are clearly not right. + * + * Comparing (string | number) to number should not remove the + * string element. + * + * Comparing (string | number) to 1 will remove the string + * element, though this is not sound. This is a pragmatic + * choice. + * + * @see narrowTypeByEquality + * + * @param source + * @param target + */ + function isCoercibleUnderDoubleEquals(source: Type, target: Type): boolean { + return ((source.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.BooleanLiteral)) !== 0) + && ((target.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Boolean)) !== 0); + } - function removeMissingType(type: Type, isOptional: boolean) { - return exactOptionalPropertyTypes && isOptional ? removeType(type, missingType) : type; - } + /** + * Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module + * with no call or construct signatures. + */ + function isObjectTypeWithInferableIndex(type: Type): boolean { + const objectFlags = getObjectFlags(type); + return type.flags & TypeFlags.Intersection + ? every((type as IntersectionType).types, isObjectTypeWithInferableIndex) + : !!( + type.symbol + && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 + && !(type.symbol.flags & SymbolFlags.Class) + && !typeHasCallOrConstructSignatures(type) + ) || !!( + objectFlags & ObjectFlags.ObjectRestType + ) || !!(objectFlags & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); + } - function containsMissingType(type: Type) { - return exactOptionalPropertyTypes && (type === missingType || type.flags & TypeFlags.Union && containsType((type as UnionType).types, missingType)); + function createSymbolWithType(source: Symbol, type: Type | undefined) { + const symbol = createSymbol(source.flags, source.escapedName, getCheckFlags(source) & CheckFlags.Readonly); + symbol.declarations = source.declarations; + symbol.parent = source.parent; + symbol.type = type; + symbol.target = source; + if (source.valueDeclaration) { + symbol.valueDeclaration = source.valueDeclaration; } - - function removeMissingOrUndefinedType(type: Type): Type { - return exactOptionalPropertyTypes ? removeType(type, missingType) : getTypeWithFacts(type, TypeFacts.NEUndefined); + const nameType = getSymbolLinks(source).nameType; + if (nameType) { + symbol.nameType = nameType; } + return symbol; + } - /** - * Is source potentially coercible to target type under `==`. - * Assumes that `source` is a constituent of a union, hence - * the boolean literal flag on the LHS, but not on the RHS. - * - * This does not fully replicate the semantics of `==`. The - * intention is to catch cases that are clearly not right. - * - * Comparing (string | number) to number should not remove the - * string element. - * - * Comparing (string | number) to 1 will remove the string - * element, though this is not sound. This is a pragmatic - * choice. - * - * @see narrowTypeByEquality - * - * @param source - * @param target - */ - function isCoercibleUnderDoubleEquals(source: Type, target: Type): boolean { - return ((source.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.BooleanLiteral)) !== 0) - && ((target.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Boolean)) !== 0); + function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { + const members = createSymbolTable(); + for (const property of getPropertiesOfObjectType(type)) { + const original = getTypeOfSymbol(property); + const updated = f(original); + members.set(property.escapedName, updated === original ? property : createSymbolWithType(property, updated)); } + return members; + } - /** - * Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module - * with no call or construct signatures. - */ - function isObjectTypeWithInferableIndex(type: Type): boolean { - const objectFlags = getObjectFlags(type); - return type.flags & TypeFlags.Intersection - ? every((type as IntersectionType).types, isObjectTypeWithInferableIndex) - : !!( - type.symbol - && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 - && !(type.symbol.flags & SymbolFlags.Class) - && !typeHasCallOrConstructSignatures(type) - ) || !!( - objectFlags & ObjectFlags.ObjectRestType - ) || !!(objectFlags & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); - } - - function createSymbolWithType(source: Symbol, type: Type | undefined) { - const symbol = createSymbol(source.flags, source.escapedName, getCheckFlags(source) & CheckFlags.Readonly); - symbol.declarations = source.declarations; - symbol.parent = source.parent; - symbol.type = type; - symbol.target = source; - if (source.valueDeclaration) { - symbol.valueDeclaration = source.valueDeclaration; - } - const nameType = getSymbolLinks(source).nameType; - if (nameType) { - symbol.nameType = nameType; - } - return symbol; + /** + * If the the provided object literal is subject to the excess properties check, + * create a new that is exempt. Recursively mark object literal members as exempt. + * Leave signatures alone since they are not subject to the check. + */ + function getRegularTypeOfObjectLiteral(type: Type): Type { + if (!(isObjectLiteralType(type) && getObjectFlags(type) & ObjectFlags.FreshLiteral)) { + return type; } - - function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { - const members = createSymbolTable(); - for (const property of getPropertiesOfObjectType(type)) { - const original = getTypeOfSymbol(property); - const updated = f(original); - members.set(property.escapedName, updated === original ? property : createSymbolWithType(property, updated)); - } - return members; + const regularType = (type as FreshObjectLiteralType).regularType; + if (regularType) { + return regularType; } - /** - * If the the provided object literal is subject to the excess properties check, - * create a new that is exempt. Recursively mark object literal members as exempt. - * Leave signatures alone since they are not subject to the check. - */ - function getRegularTypeOfObjectLiteral(type: Type): Type { - if (!(isObjectLiteralType(type) && getObjectFlags(type) & ObjectFlags.FreshLiteral)) { - return type; - } - const regularType = (type as FreshObjectLiteralType).regularType; - if (regularType) { - return regularType; - } - - const resolved = type as ResolvedType; - const members = transformTypeOfMembers(type, getRegularTypeOfObjectLiteral); - const regularNew = createAnonymousType(resolved.symbol, members, resolved.callSignatures, resolved.constructSignatures, resolved.indexInfos); - regularNew.flags = resolved.flags; - regularNew.objectFlags |= resolved.objectFlags & ~ObjectFlags.FreshLiteral; - (type as FreshObjectLiteralType).regularType = regularNew; - return regularNew; - } + const resolved = type as ResolvedType; + const members = transformTypeOfMembers(type, getRegularTypeOfObjectLiteral); + const regularNew = createAnonymousType(resolved.symbol, members, resolved.callSignatures, resolved.constructSignatures, resolved.indexInfos); + regularNew.flags = resolved.flags; + regularNew.objectFlags |= resolved.objectFlags & ~ObjectFlags.FreshLiteral; + (type as FreshObjectLiteralType).regularType = regularNew; + return regularNew; + } - function createWideningContext(parent: WideningContext | undefined, propertyName: __String | undefined, siblings: Type[] | undefined): WideningContext { - return { parent, propertyName, siblings, resolvedProperties: undefined }; - } + function createWideningContext(parent: WideningContext | undefined, propertyName: __String | undefined, siblings: Type[] | undefined): WideningContext { + return { parent, propertyName, siblings, resolvedProperties: undefined }; + } - function getSiblingsOfContext(context: WideningContext): Type[] { - if (!context.siblings) { - const siblings: Type[] = []; - for (const type of getSiblingsOfContext(context.parent!)) { - if (isObjectLiteralType(type)) { - const prop = getPropertyOfObjectType(type, context.propertyName!); - if (prop) { - forEachType(getTypeOfSymbol(prop), t => { - siblings.push(t); - }); - } + function getSiblingsOfContext(context: WideningContext): Type[] { + if (!context.siblings) { + const siblings: Type[] = []; + for (const type of getSiblingsOfContext(context.parent!)) { + if (isObjectLiteralType(type)) { + const prop = getPropertyOfObjectType(type, context.propertyName!); + if (prop) { + forEachType(getTypeOfSymbol(prop), t => { + siblings.push(t); + }); } } - context.siblings = siblings; } - return context.siblings; + context.siblings = siblings; } + return context.siblings; + } - function getPropertiesOfContext(context: WideningContext): Symbol[] { - if (!context.resolvedProperties) { - const names = new Map() as UnderscoreEscapedMap; - for (const t of getSiblingsOfContext(context)) { - if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) { - for (const prop of getPropertiesOfType(t)) { - names.set(prop.escapedName, prop); - } + function getPropertiesOfContext(context: WideningContext): Symbol[] { + if (!context.resolvedProperties) { + const names = new Map() as UnderscoreEscapedMap; + for (const t of getSiblingsOfContext(context)) { + if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) { + for (const prop of getPropertiesOfType(t)) { + names.set(prop.escapedName, prop); } } - context.resolvedProperties = arrayFrom(names.values()); } - return context.resolvedProperties; + context.resolvedProperties = arrayFrom(names.values()); } + return context.resolvedProperties; + } - function getWidenedProperty(prop: Symbol, context: WideningContext | undefined): Symbol { - if (!(prop.flags & SymbolFlags.Property)) { - // Since get accessors already widen their return value there is no need to - // widen accessor based properties here. - return prop; - } - const original = getTypeOfSymbol(prop); - const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined); - const widened = getWidenedTypeWithContext(original, propContext); - return widened === original ? prop : createSymbolWithType(prop, widened); + function getWidenedProperty(prop: Symbol, context: WideningContext | undefined): Symbol { + if (!(prop.flags & SymbolFlags.Property)) { + // Since get accessors already widen their return value there is no need to + // widen accessor based properties here. + return prop; } + const original = getTypeOfSymbol(prop); + const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined); + const widened = getWidenedTypeWithContext(original, propContext); + return widened === original ? prop : createSymbolWithType(prop, widened); + } - function getUndefinedProperty(prop: Symbol) { - const cached = undefinedProperties.get(prop.escapedName); - if (cached) { - return cached; - } - const result = createSymbolWithType(prop, missingType); - result.flags |= SymbolFlags.Optional; - undefinedProperties.set(prop.escapedName, result); - return result; + function getUndefinedProperty(prop: Symbol) { + const cached = undefinedProperties.get(prop.escapedName); + if (cached) { + return cached; } + const result = createSymbolWithType(prop, missingType); + result.flags |= SymbolFlags.Optional; + undefinedProperties.set(prop.escapedName, result); + return result; + } - function getWidenedTypeOfObjectLiteral(type: Type, context: WideningContext | undefined): Type { - const members = createSymbolTable(); - for (const prop of getPropertiesOfObjectType(type)) { - members.set(prop.escapedName, getWidenedProperty(prop, context)); - } - if (context) { - for (const prop of getPropertiesOfContext(context)) { - if (!members.has(prop.escapedName)) { - members.set(prop.escapedName, getUndefinedProperty(prop)); - } + function getWidenedTypeOfObjectLiteral(type: Type, context: WideningContext | undefined): Type { + const members = createSymbolTable(); + for (const prop of getPropertiesOfObjectType(type)) { + members.set(prop.escapedName, getWidenedProperty(prop, context)); + } + if (context) { + for (const prop of getPropertiesOfContext(context)) { + if (!members.has(prop.escapedName)) { + members.set(prop.escapedName, getUndefinedProperty(prop)); } } - const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, - sameMap(getIndexInfosOfType(type), info => createIndexInfo(info.keyType, getWidenedType(info.type), info.isReadonly))); - result.objectFlags |= (getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType)); // Retain js literal flag through widening - return result; } + const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, + sameMap(getIndexInfosOfType(type), info => createIndexInfo(info.keyType, getWidenedType(info.type), info.isReadonly))); + result.objectFlags |= (getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType)); // Retain js literal flag through widening + return result; + } - function getWidenedType(type: Type) { - return getWidenedTypeWithContext(type, /*context*/ undefined); - } + function getWidenedType(type: Type) { + return getWidenedTypeWithContext(type, /*context*/ undefined); + } - 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.Any | TypeFlags.Nullable)) { - result = anyType; - } - else if (isObjectLiteralType(type)) { - result = getWidenedTypeOfObjectLiteral(type, context); - } - else if (type.flags & TypeFlags.Union) { - const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type as UnionType).types); - const widenedTypes = sameMap((type as UnionType).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 {}). - result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal); - } - else if (type.flags & TypeFlags.Intersection) { - result = getIntersectionType(sameMap((type as IntersectionType).types, getWidenedType)); - } - else if (isArrayOrTupleType(type)) { - result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType)); - } - if (result && context === undefined) { - type.widened = result; - } - return result || type; + function getWidenedTypeWithContext(type: Type, context: WideningContext | undefined): Type { + if (getObjectFlags(type) & ObjectFlags.RequiresWidening) { + if (context === undefined && type.widened) { + return type.widened; } - return type; + let result: Type | undefined; + if (type.flags & (TypeFlags.Any | TypeFlags.Nullable)) { + result = anyType; + } + else if (isObjectLiteralType(type)) { + result = getWidenedTypeOfObjectLiteral(type, context); + } + else if (type.flags & TypeFlags.Union) { + const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type as UnionType).types); + const widenedTypes = sameMap((type as UnionType).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 {}). + result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal); + } + else if (type.flags & TypeFlags.Intersection) { + result = getIntersectionType(sameMap((type as IntersectionType).types, getWidenedType)); + } + else if (isArrayOrTupleType(type)) { + result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType)); + } + if (result && context === undefined) { + type.widened = result; + } + return result || type; } + return type; + } - /** - * Reports implicit any errors that occur as a result of widening 'null' and 'undefined' - * to 'any'. A call to reportWideningErrorsInType is normally accompanied by a call to - * getWidenedType. But in some cases getWidenedType is called without reporting errors - * (type argument inference is an example). - * - * The return value indicates whether an error was in fact reported. The particular circumstances - * are on a best effort basis. Currently, if the null or undefined that causes widening is inside - * an object literal property (arbitrarily deeply), this function reports an error. If no error is - * reported, reportImplicitAnyError is a suitable fallback to report a general error. - */ - function reportWideningErrorsInType(type: Type): boolean { - let errorReported = false; - if (getObjectFlags(type) & ObjectFlags.ContainsWideningType) { - if (type.flags & TypeFlags.Union) { - if (some((type as UnionType).types, isEmptyObjectType)) { - errorReported = true; - } - else { - for (const t of (type as UnionType).types) { - if (reportWideningErrorsInType(t)) { - errorReported = true; - } - } - } + /** + * Reports implicit any errors that occur as a result of widening 'null' and 'undefined' + * to 'any'. A call to reportWideningErrorsInType is normally accompanied by a call to + * getWidenedType. But in some cases getWidenedType is called without reporting errors + * (type argument inference is an example). + * + * The return value indicates whether an error was in fact reported. The particular circumstances + * are on a best effort basis. Currently, if the null or undefined that causes widening is inside + * an object literal property (arbitrarily deeply), this function reports an error. If no error is + * reported, reportImplicitAnyError is a suitable fallback to report a general error. + */ + function reportWideningErrorsInType(type: Type): boolean { + let errorReported = false; + if (getObjectFlags(type) & ObjectFlags.ContainsWideningType) { + if (type.flags & TypeFlags.Union) { + if (some((type as UnionType).types, isEmptyObjectType)) { + errorReported = true; } - if (isArrayOrTupleType(type)) { - for (const t of getTypeArguments(type)) { + else { + for (const t of (type as UnionType).types) { if (reportWideningErrorsInType(t)) { errorReported = true; } } } - if (isObjectLiteralType(type)) { - for (const p of getPropertiesOfObjectType(type)) { - const t = getTypeOfSymbol(p); - if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) { - if (!reportWideningErrorsInType(t)) { - error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t))); - } - errorReported = true; + } + if (isArrayOrTupleType(type)) { + for (const t of getTypeArguments(type)) { + if (reportWideningErrorsInType(t)) { + errorReported = true; + } + } + } + if (isObjectLiteralType(type)) { + for (const p of getPropertiesOfObjectType(type)) { + const t = getTypeOfSymbol(p); + if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) { + if (!reportWideningErrorsInType(t)) { + error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t))); } + errorReported = true; } } } - return errorReported; } + return errorReported; + } - function reportImplicitAny(declaration: Declaration, type: Type, wideningKind?: WideningKind) { - const typeAsString = typeToString(getWidenedType(type)); - if (isInJSFile(declaration) && !isCheckJsEnabledForFile(getSourceFileOfNode(declaration), compilerOptions)) { - // Only report implicit any errors/suggestions in TS and ts-check JS files - return; - } - let diagnostic: DiagnosticMessage; - switch (declaration.kind) { - case SyntaxKind.BinaryExpression: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - diagnostic = noImplicitAny ? Diagnostics.Member_0_implicitly_has_an_1_type : Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; - break; - case SyntaxKind.Parameter: - const param = declaration as ParameterDeclaration; - if (isIdentifier(param.name) && - (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && - param.parent.parameters.indexOf(param) > -1 && - (resolveName(param, param.name.escapedText, SymbolFlags.Type, undefined, param.name.escapedText, /*isUse*/ true) || - param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) { - const newName = "arg" + param.parent.parameters.indexOf(param); - const typeName = declarationNameToString(param.name) + (param.dotDotDotToken ? "[]" : ""); - errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, typeName); - return; - } - diagnostic = (declaration as ParameterDeclaration).dotDotDotToken ? - noImplicitAny ? Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage : - noImplicitAny ? Diagnostics.Parameter_0_implicitly_has_an_1_type : Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; - break; - case SyntaxKind.BindingElement: - diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type; - if (!noImplicitAny) { - // Don't issue a suggestion for binding elements since the codefix doesn't yet support them. - return; - } - break; - case SyntaxKind.JSDocFunctionType: - error(declaration, Diagnostics.Function_type_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); + function reportImplicitAny(declaration: Declaration, type: Type, wideningKind?: WideningKind) { + const typeAsString = typeToString(getWidenedType(type)); + if (isInJSFile(declaration) && !isCheckJsEnabledForFile(getSourceFileOfNode(declaration), compilerOptions)) { + // Only report implicit any errors/suggestions in TS and ts-check JS files + return; + } + let diagnostic: DiagnosticMessage; + switch (declaration.kind) { + case SyntaxKind.BinaryExpression: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + diagnostic = noImplicitAny ? Diagnostics.Member_0_implicitly_has_an_1_type : Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; + break; + case SyntaxKind.Parameter: + const param = declaration as ParameterDeclaration; + if (isIdentifier(param.name) && + (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && + param.parent.parameters.indexOf(param) > -1 && + (resolveName(param, param.name.escapedText, SymbolFlags.Type, undefined, param.name.escapedText, /*isUse*/ true) || + param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) { + const newName = "arg" + param.parent.parameters.indexOf(param); + const typeName = declarationNameToString(param.name) + (param.dotDotDotToken ? "[]" : ""); + errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, typeName); return; - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - if (noImplicitAny && !(declaration as NamedDeclaration).name) { - if (wideningKind === WideningKind.GeneratorYield) { - error(declaration, Diagnostics.Generator_implicitly_has_yield_type_0_because_it_does_not_yield_any_values_Consider_supplying_a_return_type_annotation, typeAsString); - } - else { - error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); - } - return; + } + diagnostic = (declaration as ParameterDeclaration).dotDotDotToken ? + noImplicitAny ? Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage : + noImplicitAny ? Diagnostics.Parameter_0_implicitly_has_an_1_type : Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; + break; + case SyntaxKind.BindingElement: + diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type; + if (!noImplicitAny) { + // Don't issue a suggestion for binding elements since the codefix doesn't yet support them. + return; + } + break; + case SyntaxKind.JSDocFunctionType: + error(declaration, Diagnostics.Function_type_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); + return; + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + if (noImplicitAny && !(declaration as NamedDeclaration).name) { + if (wideningKind === WideningKind.GeneratorYield) { + error(declaration, Diagnostics.Generator_implicitly_has_yield_type_0_because_it_does_not_yield_any_values_Consider_supplying_a_return_type_annotation, typeAsString); } - diagnostic = !noImplicitAny ? Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage : - wideningKind === WideningKind.GeneratorYield ? Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_yield_type : - Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type; - break; - case SyntaxKind.MappedType: - if (noImplicitAny) { - error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type); + else { + error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); } return; - default: - diagnostic = noImplicitAny ? Diagnostics.Variable_0_implicitly_has_an_1_type : Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; - } - errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString); - } - - function reportErrorsFromWidening(declaration: Declaration, type: Type, wideningKind?: WideningKind) { - addLazyDiagnostic(() => { - if (noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType && (!wideningKind || !getContextualSignatureForFunctionLikeDeclaration(declaration as FunctionLikeDeclaration))) { - // Report implicit any error within type if possible, otherwise report error on declaration - if (!reportWideningErrorsInType(type)) { - reportImplicitAny(declaration, type, wideningKind); - } } - }); + diagnostic = !noImplicitAny ? Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage : + wideningKind === WideningKind.GeneratorYield ? Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_yield_type : + Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type; + break; + case SyntaxKind.MappedType: + if (noImplicitAny) { + error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type); + } + return; + default: + diagnostic = noImplicitAny ? Diagnostics.Variable_0_implicitly_has_an_1_type : Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; } + errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString); + } - function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { - const sourceCount = getParameterCount(source); - const targetCount = getParameterCount(target); - const sourceRestType = getEffectiveRestType(source); - const targetRestType = getEffectiveRestType(target); - const targetNonRestCount = targetRestType ? targetCount - 1 : targetCount; - const paramCount = sourceRestType ? targetNonRestCount : Math.min(sourceCount, targetNonRestCount); - const sourceThisType = getThisTypeOfSignature(source); - if (sourceThisType) { - const targetThisType = getThisTypeOfSignature(target); - if (targetThisType) { - callback(sourceThisType, targetThisType); + function reportErrorsFromWidening(declaration: Declaration, type: Type, wideningKind?: WideningKind) { + addLazyDiagnostic(() => { + if (noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType && (!wideningKind || !getContextualSignatureForFunctionLikeDeclaration(declaration as FunctionLikeDeclaration))) { + // Report implicit any error within type if possible, otherwise report error on declaration + if (!reportWideningErrorsInType(type)) { + reportImplicitAny(declaration, type, wideningKind); } } - for (let i = 0; i < paramCount; i++) { - callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i)); - } - if (targetRestType) { - callback(getRestTypeAtPosition(source, paramCount), targetRestType); - } - } + }); + } - function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { - const sourceTypePredicate = getTypePredicateOfSignature(source); - const targetTypePredicate = getTypePredicateOfSignature(target); - if (sourceTypePredicate && targetTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) { - callback(sourceTypePredicate.type, targetTypePredicate.type); - } - else { - callback(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); + function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { + const sourceCount = getParameterCount(source); + const targetCount = getParameterCount(target); + const sourceRestType = getEffectiveRestType(source); + const targetRestType = getEffectiveRestType(target); + const targetNonRestCount = targetRestType ? targetCount - 1 : targetCount; + const paramCount = sourceRestType ? targetNonRestCount : Math.min(sourceCount, targetNonRestCount); + const sourceThisType = getThisTypeOfSignature(source); + if (sourceThisType) { + const targetThisType = getThisTypeOfSignature(target); + if (targetThisType) { + callback(sourceThisType, targetThisType); } } + for (let i = 0; i < paramCount; i++) { + callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i)); + } + if (targetRestType) { + callback(getRestTypeAtPosition(source, paramCount), targetRestType); + } + } - function createInferenceContext(typeParameters: readonly TypeParameter[], signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer): InferenceContext { - return createInferenceContextWorker(typeParameters.map(createInferenceInfo), signature, flags, compareTypes || compareTypesAssignable); + function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + const targetTypePredicate = getTypePredicateOfSignature(target); + if (sourceTypePredicate && targetTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) { + callback(sourceTypePredicate.type, targetTypePredicate.type); } + else { + callback(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); + } + } + + function createInferenceContext(typeParameters: readonly TypeParameter[], signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer): InferenceContext { + return createInferenceContextWorker(typeParameters.map(createInferenceInfo), signature, flags, compareTypes || compareTypesAssignable); + } - function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined { - return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes); - } + function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined { + return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes); + } - function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext { - const context: InferenceContext = { - inferences, - signature, - flags, - compareTypes, - mapper: reportUnmeasurableMapper, // initialize to a noop mapper so the context object is available, but the underlying object shape is right upon construction - nonFixingMapper: reportUnmeasurableMapper, - }; - context.mapper = makeFixingMapperForContext(context); - context.nonFixingMapper = makeNonFixingMapperForContext(context); - return context; - } + function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext { + const context: InferenceContext = { + inferences, + signature, + flags, + compareTypes, + mapper: reportUnmeasurableMapper, // initialize to a noop mapper so the context object is available, but the underlying object shape is right upon construction + nonFixingMapper: reportUnmeasurableMapper, + }; + context.mapper = makeFixingMapperForContext(context); + context.nonFixingMapper = makeNonFixingMapperForContext(context); + return context; + } - function makeFixingMapperForContext(context: InferenceContext) { - return makeDeferredTypeMapper(map(context.inferences, i => i.typeParameter), map(context.inferences, (inference, i) => () => { - if (!inference.isFixed) { - // Before we commit to a particular inference (and thus lock out any further inferences), - // we infer from any intra-expression inference sites we have collected. - inferFromIntraExpressionSites(context); - clearCachedInferences(context.inferences); - inference.isFixed = true; - } - return getInferredType(context, i); - })); - } + function makeFixingMapperForContext(context: InferenceContext) { + return makeDeferredTypeMapper(map(context.inferences, i => i.typeParameter), map(context.inferences, (inference, i) => () => { + if (!inference.isFixed) { + // Before we commit to a particular inference (and thus lock out any further inferences), + // we infer from any intra-expression inference sites we have collected. + inferFromIntraExpressionSites(context); + clearCachedInferences(context.inferences); + inference.isFixed = true; + } + return getInferredType(context, i); + })); + } - function makeNonFixingMapperForContext(context: InferenceContext) { - return makeDeferredTypeMapper(map(context.inferences, i => i.typeParameter), map(context.inferences, (_, i) => () => { - return getInferredType(context, i); - })); - } + function makeNonFixingMapperForContext(context: InferenceContext) { + return makeDeferredTypeMapper(map(context.inferences, i => i.typeParameter), map(context.inferences, (_, i) => () => { + return getInferredType(context, i); + })); + } - function clearCachedInferences(inferences: InferenceInfo[]) { - for (const inference of inferences) { - if (!inference.isFixed) { - inference.inferredType = undefined; - } + function clearCachedInferences(inferences: InferenceInfo[]) { + for (const inference of inferences) { + if (!inference.isFixed) { + inference.inferredType = undefined; } } + } - function addIntraExpressionInferenceSite(context: InferenceContext, node: Expression | MethodDeclaration, type: Type) { - (context.intraExpressionInferenceSites ??= []).push({ node, type }); - } + function addIntraExpressionInferenceSite(context: InferenceContext, node: Expression | MethodDeclaration, type: Type) { + (context.intraExpressionInferenceSites ??= []).push({ node, type }); + } - // We collect intra-expression inference sites within object and array literals to handle cases where - // inferred types flow between context sensitive element expressions. For example: - // - // declare function foo(arg: [(n: number) => T, (x: T) => void]): void; - // foo([_a => 0, n => n.toFixed()]); - // - // Above, both arrow functions in the tuple argument are context sensitive, thus both are omitted from the - // pass that collects inferences from the non-context sensitive parts of the arguments. In the subsequent - // pass where nothing is omitted, we need to commit to an inference for T in order to contextually type the - // parameter in the second arrow function, but we want to first infer from the return type of the first - // arrow function. This happens automatically when the arrow functions are discrete arguments (because we - // infer from each argument before processing the next), but when the arrow functions are elements of an - // object or array literal, we need to perform intra-expression inferences early. - function inferFromIntraExpressionSites(context: InferenceContext) { - if (context.intraExpressionInferenceSites) { - for (const { node, type } of context.intraExpressionInferenceSites) { - const contextualType = node.kind === SyntaxKind.MethodDeclaration ? - getContextualTypeForObjectLiteralMethod(node as MethodDeclaration, ContextFlags.NoConstraints) : - getContextualType(node, ContextFlags.NoConstraints); - if (contextualType) { - inferTypes(context.inferences, type, contextualType); - } + // We collect intra-expression inference sites within object and array literals to handle cases where + // inferred types flow between context sensitive element expressions. For example: + // + // declare function foo(arg: [(n: number) => T, (x: T) => void]): void; + // foo([_a => 0, n => n.toFixed()]); + // + // Above, both arrow functions in the tuple argument are context sensitive, thus both are omitted from the + // pass that collects inferences from the non-context sensitive parts of the arguments. In the subsequent + // pass where nothing is omitted, we need to commit to an inference for T in order to contextually type the + // parameter in the second arrow function, but we want to first infer from the return type of the first + // arrow function. This happens automatically when the arrow functions are discrete arguments (because we + // infer from each argument before processing the next), but when the arrow functions are elements of an + // object or array literal, we need to perform intra-expression inferences early. + function inferFromIntraExpressionSites(context: InferenceContext) { + if (context.intraExpressionInferenceSites) { + for (const { node, type } of context.intraExpressionInferenceSites) { + const contextualType = node.kind === SyntaxKind.MethodDeclaration ? + getContextualTypeForObjectLiteralMethod(node as MethodDeclaration, ContextFlags.NoConstraints) : + getContextualType(node, ContextFlags.NoConstraints); + if (contextualType) { + inferTypes(context.inferences, type, contextualType); } - context.intraExpressionInferenceSites = undefined; } + context.intraExpressionInferenceSites = undefined; } + } - function createInferenceInfo(typeParameter: TypeParameter): InferenceInfo { - return { - typeParameter, - candidates: undefined, - contraCandidates: undefined, - inferredType: undefined, - priority: undefined, - topLevel: true, - isFixed: false, - impliedArity: undefined - }; - } - - function cloneInferenceInfo(inference: InferenceInfo): InferenceInfo { - return { - typeParameter: inference.typeParameter, - candidates: inference.candidates && inference.candidates.slice(), - contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(), - inferredType: inference.inferredType, - priority: inference.priority, - topLevel: inference.topLevel, - isFixed: inference.isFixed, - impliedArity: inference.impliedArity - }; - } + function createInferenceInfo(typeParameter: TypeParameter): InferenceInfo { + return { + typeParameter, + candidates: undefined, + contraCandidates: undefined, + inferredType: undefined, + priority: undefined, + topLevel: true, + isFixed: false, + impliedArity: undefined + }; + } - 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 cloneInferenceInfo(inference: InferenceInfo): InferenceInfo { + return { + typeParameter: inference.typeParameter, + candidates: inference.candidates && inference.candidates.slice(), + contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(), + inferredType: inference.inferredType, + priority: inference.priority, + topLevel: inference.topLevel, + isFixed: inference.isFixed, + impliedArity: inference.impliedArity + }; + } - function getMapperFromContext(context: T): TypeMapper | T & undefined { - return context && context.mapper; - } + 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; + } - // Return true if the given type could possibly reference a type parameter for which - // we perform type inference (i.e. a type parameter of a generic function). We cache - // results for union and intersection types for performance reasons. - function couldContainTypeVariables(type: Type): boolean { - const objectFlags = getObjectFlags(type); - if (objectFlags & ObjectFlags.CouldContainTypeVariablesComputed) { - return !!(objectFlags & ObjectFlags.CouldContainTypeVariables); - } - const result = !!(type.flags & TypeFlags.Instantiable || - type.flags & TypeFlags.Object && !isNonGenericTopLevelType(type) && ( - objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || forEach(getTypeArguments(type as TypeReference), couldContainTypeVariables)) || - objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations || - objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType | ObjectFlags.InstantiationExpressionType)) || - type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType).types, couldContainTypeVariables)); - if (type.flags & TypeFlags.ObjectFlagsType) { - (type as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0); - } - return result; - } + function getMapperFromContext(context: T): TypeMapper | T & undefined { + return context && context.mapper; + } - function isNonGenericTopLevelType(type: Type) { - if (type.aliasSymbol && !type.aliasTypeArguments) { - const declaration = getDeclarationOfKind(type.aliasSymbol, SyntaxKind.TypeAliasDeclaration); - return !!(declaration && findAncestor(declaration.parent, n => n.kind === SyntaxKind.SourceFile ? true : n.kind === SyntaxKind.ModuleDeclaration ? false : "quit")); - } - return false; - } + // Return true if the given type could possibly reference a type parameter for which + // we perform type inference (i.e. a type parameter of a generic function). We cache + // results for union and intersection types for performance reasons. + function couldContainTypeVariables(type: Type): boolean { + const objectFlags = getObjectFlags(type); + if (objectFlags & ObjectFlags.CouldContainTypeVariablesComputed) { + return !!(objectFlags & ObjectFlags.CouldContainTypeVariables); + } + const result = !!(type.flags & TypeFlags.Instantiable || + type.flags & TypeFlags.Object && !isNonGenericTopLevelType(type) && ( + objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || forEach(getTypeArguments(type as TypeReference), couldContainTypeVariables)) || + objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations || + objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType | ObjectFlags.InstantiationExpressionType)) || + type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type as UnionOrIntersectionType).types, couldContainTypeVariables)); + if (type.flags & TypeFlags.ObjectFlagsType) { + (type as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0); + } + return result; + } - function isTypeParameterAtTopLevel(type: Type, typeParameter: TypeParameter): boolean { - return !!(type === typeParameter || - type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, typeParameter)) || - type.flags & TypeFlags.Conditional && (getTrueTypeFromConditionalType(type as ConditionalType) === typeParameter || getFalseTypeFromConditionalType(type as ConditionalType) === typeParameter)); + function isNonGenericTopLevelType(type: Type) { + if (type.aliasSymbol && !type.aliasTypeArguments) { + const declaration = getDeclarationOfKind(type.aliasSymbol, SyntaxKind.TypeAliasDeclaration); + return !!(declaration && findAncestor(declaration.parent, n => n.kind === SyntaxKind.SourceFile ? true : n.kind === SyntaxKind.ModuleDeclaration ? false : "quit")); } + return false; + } - /** Create an object with properties named in the string literal type. Every property has type `any` */ - function createEmptyObjectTypeFromStringLiteral(type: Type) { - const members = createSymbolTable(); - forEachType(type, t => { - if (!(t.flags & TypeFlags.StringLiteral)) { - return; - } - const name = escapeLeadingUnderscores((t as StringLiteralType).value); - const literalProp = createSymbol(SymbolFlags.Property, name); - literalProp.type = anyType; - if (t.symbol) { - literalProp.declarations = t.symbol.declarations; - literalProp.valueDeclaration = t.symbol.valueDeclaration; - } - members.set(name, literalProp); - }); - const indexInfos = type.flags & TypeFlags.String ? [createIndexInfo(stringType, emptyObjectType, /*isReadonly*/ false)] : emptyArray; - return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfos); - } + function isTypeParameterAtTopLevel(type: Type, typeParameter: TypeParameter): boolean { + return !!(type === typeParameter || + type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, typeParameter)) || + type.flags & TypeFlags.Conditional && (getTrueTypeFromConditionalType(type as ConditionalType) === typeParameter || getFalseTypeFromConditionalType(type as ConditionalType) === typeParameter)); + } - /** - * Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct - * an object type with the same set of properties as the source type, where the type of each - * property is computed by inferring from the source property type to X for the type - * variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). - */ - function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined { - if (inInferTypeForHomomorphicMappedType) { - return undefined; + /** Create an object with properties named in the string literal type. Every property has type `any` */ + function createEmptyObjectTypeFromStringLiteral(type: Type) { + const members = createSymbolTable(); + forEachType(type, t => { + if (!(t.flags & TypeFlags.StringLiteral)) { + return; } - const key = source.id + "," + target.id + "," + constraint.id; - if (reverseMappedCache.has(key)) { - return reverseMappedCache.get(key); + const name = escapeLeadingUnderscores((t as StringLiteralType).value); + const literalProp = createSymbol(SymbolFlags.Property, name); + literalProp.type = anyType; + if (t.symbol) { + literalProp.declarations = t.symbol.declarations; + literalProp.valueDeclaration = t.symbol.valueDeclaration; } - inInferTypeForHomomorphicMappedType = true; - const type = createReverseMappedType(source, target, constraint); - inInferTypeForHomomorphicMappedType = false; - reverseMappedCache.set(key, type); - return type; - } + members.set(name, literalProp); + }); + const indexInfos = type.flags & TypeFlags.String ? [createIndexInfo(stringType, emptyObjectType, /*isReadonly*/ false)] : emptyArray; + return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfos); + } - // 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))) || - isTupleType(type) && some(getTypeArguments(type), isPartiallyInferableType); + /** + * Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct + * an object type with the same set of properties as the source type, where the type of each + * property is computed by inferring from the source property type to X for the type + * variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). + */ + function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined { + if (inInferTypeForHomomorphicMappedType) { + return undefined; } + const key = source.id + "," + target.id + "," + constraint.id; + if (reverseMappedCache.has(key)) { + return reverseMappedCache.get(key); + } + inInferTypeForHomomorphicMappedType = true; + const type = createReverseMappedType(source, target, constraint); + inInferTypeForHomomorphicMappedType = false; + reverseMappedCache.set(key, type); + return type; + } - function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) { - // 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, stringType) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) { - return undefined; - } - // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been - // applied to the element type(s). - if (isArrayType(source)) { - return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source)); - } - if (isTupleType(source)) { - const elementTypes = map(getTypeArguments(source), t => inferReverseMappedType(t, target, constraint)); - const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ? - sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : - source.target.elementFlags; - return createTupleType(elementTypes, elementFlags, source.target.readonly, source.target.labeledElementDeclarations); - } - // For all other object types we infer a new object type where the reverse mapping has been - // applied to the type of each property. - const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType; - reversed.source = source; - reversed.mappedType = target; - reversed.constraintType = constraint; - return reversed; - } - - function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) { - const links = getSymbolLinks(symbol); - if (!links.type) { - links.type = inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType); - } - return links.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))) || + isTupleType(type) && some(getTypeArguments(type), isPartiallyInferableType); + } + + function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) { + // 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, stringType) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) { + return undefined; } + // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been + // applied to the element type(s). + if (isArrayType(source)) { + return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source)); + } + if (isTupleType(source)) { + const elementTypes = map(getTypeArguments(source), t => inferReverseMappedType(t, target, constraint)); + const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ? + sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : + source.target.elementFlags; + return createTupleType(elementTypes, elementFlags, source.target.readonly, source.target.labeledElementDeclarations); + } + // For all other object types we infer a new object type where the reverse mapping has been + // applied to the type of each property. + const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType; + reversed.source = source; + reversed.mappedType = target; + reversed.constraintType = constraint; + return reversed; + } - function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type { - const typeParameter = getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target)) as TypeParameter; - const templateType = getTemplateTypeFromMappedType(target); - const inference = createInferenceInfo(typeParameter); - inferTypes([inference], sourceType, templateType); - return getTypeFromInference(inference) || unknownType; + function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) { + const links = getSymbolLinks(symbol); + if (!links.type) { + links.type = inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType); } + return links.type; + } - function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator { - const properties = getPropertiesOfType(target); - for (const targetProp of properties) { - // TODO: remove this when we support static private identifier fields and find other solutions to get privateNamesAndStaticFields test to pass - if (isStaticPrivateIdentifierProperty(targetProp)) { - continue; + function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type { + const typeParameter = getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target)) as TypeParameter; + const templateType = getTemplateTypeFromMappedType(target); + const inference = createInferenceInfo(typeParameter); + inferTypes([inference], sourceType, templateType); + return getTypeFromInference(inference) || unknownType; + } + + function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator { + const properties = getPropertiesOfType(target); + for (const targetProp of properties) { + // TODO: remove this when we support static private identifier fields and find other solutions to get privateNamesAndStaticFields test to pass + if (isStaticPrivateIdentifierProperty(targetProp)) { + continue; + } + if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) { + const sourceProp = getPropertyOfType(source, targetProp.escapedName); + if (!sourceProp) { + yield targetProp; } - if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) { - const sourceProp = getPropertyOfType(source, targetProp.escapedName); - if (!sourceProp) { - yield targetProp; - } - else if (matchDiscriminantProperties) { - const targetType = getTypeOfSymbol(targetProp); - if (targetType.flags & TypeFlags.Unit) { - const sourceType = getTypeOfSymbol(sourceProp); - if (!(sourceType.flags & TypeFlags.Any || getRegularTypeOfLiteralType(sourceType) === getRegularTypeOfLiteralType(targetType))) { - yield targetProp; - } + else if (matchDiscriminantProperties) { + const targetType = getTypeOfSymbol(targetProp); + if (targetType.flags & TypeFlags.Unit) { + const sourceType = getTypeOfSymbol(sourceProp); + if (!(sourceType.flags & TypeFlags.Any || getRegularTypeOfLiteralType(sourceType) === getRegularTypeOfLiteralType(targetType))) { + yield targetProp; } } } } } + } - function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined { - const result = getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties).next(); - if (!result.done) return result.value; - } + function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined { + const result = getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties).next(); + if (!result.done) return result.value; + } - function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) { - return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength || - !target.target.hasRestElement && (source.target.hasRestElement || target.target.fixedLength < source.target.fixedLength); - } + function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) { + return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength || + !target.target.hasRestElement && (source.target.hasRestElement || target.target.fixedLength < source.target.fixedLength); + } - function typesDefinitelyUnrelated(source: Type, target: Type) { - // Two tuple types with incompatible arities are definitely unrelated. - // Two object types that each have a property that is unmatched in the other are definitely unrelated. - return isTupleType(source) && isTupleType(target) ? tupleTypesDefinitelyUnrelated(source, target) : - !!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true) && - !!getUnmatchedProperty(target, source, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ false); - } + function typesDefinitelyUnrelated(source: Type, target: Type) { + // Two tuple types with incompatible arities are definitely unrelated. + // Two object types that each have a property that is unmatched in the other are definitely unrelated. + return isTupleType(source) && isTupleType(target) ? tupleTypesDefinitelyUnrelated(source, target) : + !!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true) && + !!getUnmatchedProperty(target, source, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ false); + } - function getTypeFromInference(inference: InferenceInfo) { - return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : - inference.contraCandidates ? getIntersectionType(inference.contraCandidates) : - undefined; - } + function getTypeFromInference(inference: InferenceInfo) { + return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : + inference.contraCandidates ? getIntersectionType(inference.contraCandidates) : + undefined; + } - function hasSkipDirectInferenceFlag(node: Node) { - return !!getNodeLinks(node).skipDirectInference; - } + function hasSkipDirectInferenceFlag(node: Node) { + return !!getNodeLinks(node).skipDirectInference; + } - function isFromInferenceBlockedSource(type: Type) { - return !!(type.symbol && some(type.symbol.declarations, hasSkipDirectInferenceFlag)); - } + function isFromInferenceBlockedSource(type: Type) { + return !!(type.symbol && some(type.symbol.declarations, hasSkipDirectInferenceFlag)); + } - function templateLiteralTypesDefinitelyUnrelated(source: TemplateLiteralType, target: TemplateLiteralType) { - // Two template literal types with diffences in their starting or ending text spans are definitely unrelated. - const sourceStart = source.texts[0]; - const targetStart = target.texts[0]; - const sourceEnd = source.texts[source.texts.length - 1]; - const targetEnd = target.texts[target.texts.length - 1]; - const startLen = Math.min(sourceStart.length, targetStart.length); - const endLen = Math.min(sourceEnd.length, targetEnd.length); - return sourceStart.slice(0, startLen) !== targetStart.slice(0, startLen) || - sourceEnd.slice(sourceEnd.length - endLen) !== targetEnd.slice(targetEnd.length - endLen); - } + function templateLiteralTypesDefinitelyUnrelated(source: TemplateLiteralType, target: TemplateLiteralType) { + // Two template literal types with diffences in their starting or ending text spans are definitely unrelated. + const sourceStart = source.texts[0]; + const targetStart = target.texts[0]; + const sourceEnd = source.texts[source.texts.length - 1]; + const targetEnd = target.texts[target.texts.length - 1]; + const startLen = Math.min(sourceStart.length, targetStart.length); + const endLen = Math.min(sourceEnd.length, targetEnd.length); + return sourceStart.slice(0, startLen) !== targetStart.slice(0, startLen) || + sourceEnd.slice(sourceEnd.length - endLen) !== targetEnd.slice(targetEnd.length - endLen); + } - /** - * Tests whether the provided string can be parsed as a number. - * @param s The string to test. - * @param roundTripOnly Indicates the resulting number matches the input when converted back to a string. - */ - function isValidNumberString(s: string, roundTripOnly: boolean): boolean { - if (s === "") return false; - const n = +s; - return isFinite(n) && (!roundTripOnly || "" + n === s); + /** + * Tests whether the provided string can be parsed as a number. + * @param s The string to test. + * @param roundTripOnly Indicates the resulting number matches the input when converted back to a string. + */ + function isValidNumberString(s: string, roundTripOnly: boolean): boolean { + if (s === "") return false; + const n = +s; + return isFinite(n) && (!roundTripOnly || "" + n === s); + } + + /** + * @param text a valid bigint string excluding a trailing `n`, but including a possible prefix `-`. Use `isValidBigIntString(text, roundTripOnly)` before calling this function. + */ + function parseBigIntLiteralType(text: string) { + const negative = text.startsWith("-"); + const base10Value = parsePseudoBigInt(`${negative ? text.slice(1) : text}n`); + return getBigIntLiteralType({ negative, base10Value }); + } + + /** + * Tests whether the provided string can be parsed as a bigint. + * @param s The string to test. + * @param roundTripOnly Indicates the resulting bigint matches the input when converted back to a string. + */ + function isValidBigIntString(s: string, roundTripOnly: boolean): boolean { + if (s === "") return false; + const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false); + let success = true; + scanner.setOnError(() => success = false); + scanner.setText(s + "n"); + let result = scanner.scan(); + const negative = result === SyntaxKind.MinusToken; + if (negative) { + result = scanner.scan(); + } + const flags = scanner.getTokenFlags(); + // validate that + // * scanning proceeded without error + // * a bigint can be scanned, and that when it is scanned, it is + // * the full length of the input string (so the scanner is one character beyond the augmented input length) + // * it does not contain a numeric seperator (the `BigInt` constructor does not accept a numeric seperator in its input) + return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator) + && (!roundTripOnly || s === pseudoBigIntToString({ negative, base10Value: parsePseudoBigInt(scanner.getTokenValue()) })); + } + + function isMemberOfStringMapping(source: Type, target: Type): boolean { + if (target.flags & (TypeFlags.String | TypeFlags.Any)) { + return true; + } + if (target.flags & TypeFlags.TemplateLiteral) { + return isTypeAssignableTo(source, target); } + if (target.flags & TypeFlags.StringMapping) { + // We need to see whether applying the same mappings of the target + // onto the source would produce an identical type *and* that + // it's compatible with the inner-most non-string-mapped type. + // + // The intuition here is that if same mappings don't affect the source at all, + // and the source is compatible with the unmapped target, then they must + // still reside in the same domain. + const mappingStack = []; + while (target.flags & TypeFlags.StringMapping) { + mappingStack.unshift(target.symbol); + target = (target as StringMappingType).type; + } + const mappedSource = reduceLeft(mappingStack, (memo, value) => getStringMappingType(value, memo), source); + return mappedSource === source && isMemberOfStringMapping(source, target); + } + return false; + } - /** - * @param text a valid bigint string excluding a trailing `n`, but including a possible prefix `-`. Use `isValidBigIntString(text, roundTripOnly)` before calling this function. - */ - function parseBigIntLiteralType(text: string) { - const negative = text.startsWith("-"); - const base10Value = parsePseudoBigInt(`${negative ? text.slice(1) : text}n`); - return getBigIntLiteralType({ negative, base10Value }); + function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean { + if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) { + return true; + } + if (source.flags & TypeFlags.StringLiteral) { + const value = (source as StringLiteralType).value; + return !!(target.flags & TypeFlags.Number && isValidNumberString(value, /*roundTripOnly*/ false) || + target.flags & TypeFlags.BigInt && isValidBigIntString(value, /*roundTripOnly*/ false) || + target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName || + target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(getStringLiteralType(value), target)); + } + if (source.flags & TypeFlags.TemplateLiteral) { + const texts = (source as TemplateLiteralType).texts; + return texts.length === 2 && texts[0] === "" && texts[1] === "" && isTypeAssignableTo((source as TemplateLiteralType).types[0], target); } + return isTypeAssignableTo(source, target); + } - /** - * Tests whether the provided string can be parsed as a bigint. - * @param s The string to test. - * @param roundTripOnly Indicates the resulting bigint matches the input when converted back to a string. - */ - function isValidBigIntString(s: string, roundTripOnly: boolean): boolean { - if (s === "") return false; - const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false); - let success = true; - scanner.setOnError(() => success = false); - scanner.setText(s + "n"); - let result = scanner.scan(); - const negative = result === SyntaxKind.MinusToken; - if (negative) { - result = scanner.scan(); - } - const flags = scanner.getTokenFlags(); - // validate that - // * scanning proceeded without error - // * a bigint can be scanned, and that when it is scanned, it is - // * the full length of the input string (so the scanner is one character beyond the augmented input length) - // * it does not contain a numeric seperator (the `BigInt` constructor does not accept a numeric seperator in its input) - return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator) - && (!roundTripOnly || s === pseudoBigIntToString({ negative, base10Value: parsePseudoBigInt(scanner.getTokenValue()) })); - } - - function isMemberOfStringMapping(source: Type, target: Type): boolean { - if (target.flags & (TypeFlags.String | TypeFlags.Any)) { - return true; - } - if (target.flags & TypeFlags.TemplateLiteral) { - return isTypeAssignableTo(source, target); - } - if (target.flags & TypeFlags.StringMapping) { - // We need to see whether applying the same mappings of the target - // onto the source would produce an identical type *and* that - // it's compatible with the inner-most non-string-mapped type. - // - // The intuition here is that if same mappings don't affect the source at all, - // and the source is compatible with the unmapped target, then they must - // still reside in the same domain. - const mappingStack = []; - while (target.flags & TypeFlags.StringMapping) { - mappingStack.unshift(target.symbol); - target = (target as StringMappingType).type; + function inferTypesFromTemplateLiteralType(source: Type, target: TemplateLiteralType): Type[] | undefined { + return source.flags & TypeFlags.StringLiteral ? inferFromLiteralPartsToTemplateLiteral([(source as StringLiteralType).value], emptyArray, target) : + source.flags & TypeFlags.TemplateLiteral ? + arraysEqual((source as TemplateLiteralType).texts, target.texts) ? map((source as TemplateLiteralType).types, getStringLikeTypeForType) : + inferFromLiteralPartsToTemplateLiteral((source as TemplateLiteralType).texts, (source as TemplateLiteralType).types, target) : + undefined; + } + + function isTypeMatchedByTemplateLiteralType(source: Type, target: TemplateLiteralType): boolean { + const inferences = inferTypesFromTemplateLiteralType(source, target); + return !!inferences && every(inferences, (r, i) => isValidTypeForTemplateLiteralPlaceholder(r, target.types[i])); + } + + function getStringLikeTypeForType(type: Type) { + return type.flags & (TypeFlags.Any | TypeFlags.StringLike) ? type : getTemplateLiteralType(["", ""], [type]); + } + + // This function infers from the text parts and type parts of a source literal to a target template literal. The number + // of text parts is always one more than the number of type parts, and a source string literal is treated as a source + // with one text part and zero type parts. The function returns an array of inferred string or template literal types + // corresponding to the placeholders in the target template literal, or undefined if the source doesn't match the target. + // + // We first check that the starting source text part matches the starting target text part, and that the ending source + // text part ends matches the ending target text part. We then iterate through the remaining target text parts, finding + // a match for each in the source and inferring string or template literal types created from the segments of the source + // that occur between the matches. During this iteration, seg holds the index of the current text part in the sourceTexts + // array and pos holds the current character position in the current text part. + // + // Consider inference from type `<<${string}>.<${number}-${number}>>` to type `<${string}.${string}>`, i.e. + // sourceTexts = ['<<', '>.<', '-', '>>'] + // sourceTypes = [string, number, number] + // target.texts = ['<', '.', '>'] + // We first match '<' in the target to the start of '<<' in the source and '>' in the target to the end of '>>' in + // the source. The first match for the '.' in target occurs at character 1 in the source text part at index 1, and thus + // the first inference is the template literal type `<${string}>`. The remainder of the source makes up the second + // inference, the template literal type `<${number}-${number}>`. + function inferFromLiteralPartsToTemplateLiteral(sourceTexts: readonly string[], sourceTypes: readonly Type[], target: TemplateLiteralType): Type[] | undefined { + const lastSourceIndex = sourceTexts.length - 1; + const sourceStartText = sourceTexts[0]; + const sourceEndText = sourceTexts[lastSourceIndex]; + const targetTexts = target.texts; + const lastTargetIndex = targetTexts.length - 1; + const targetStartText = targetTexts[0]; + const targetEndText = targetTexts[lastTargetIndex]; + if (lastSourceIndex === 0 && sourceStartText.length < targetStartText.length + targetEndText.length || + !sourceStartText.startsWith(targetStartText) || !sourceEndText.endsWith(targetEndText)) return undefined; + const remainingEndText = sourceEndText.slice(0, sourceEndText.length - targetEndText.length); + const matches: Type[] = []; + let seg = 0; + let pos = targetStartText.length; + for (let i = 1; i < lastTargetIndex; i++) { + const delim = targetTexts[i]; + if (delim.length > 0) { + let s = seg; + let p = pos; + while (true) { + p = getSourceText(s).indexOf(delim, p); + if (p >= 0) break; + s++; + if (s === sourceTexts.length) return undefined; + p = 0; } - const mappedSource = reduceLeft(mappingStack, (memo, value) => getStringMappingType(value, memo), source); - return mappedSource === source && isMemberOfStringMapping(source, target); + addMatch(s, p); + pos += delim.length; } - return false; - } - - function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean { - if (source === target || target.flags & (TypeFlags.Any | TypeFlags.String)) { - return true; + else if (pos < getSourceText(seg).length) { + addMatch(seg, pos + 1); } - if (source.flags & TypeFlags.StringLiteral) { - const value = (source as StringLiteralType).value; - return !!(target.flags & TypeFlags.Number && isValidNumberString(value, /*roundTripOnly*/ false) || - target.flags & TypeFlags.BigInt && isValidBigIntString(value, /*roundTripOnly*/ false) || - target.flags & (TypeFlags.BooleanLiteral | TypeFlags.Nullable) && value === (target as IntrinsicType).intrinsicName || - target.flags & TypeFlags.StringMapping && isMemberOfStringMapping(getStringLiteralType(value), target)); + else if (seg < lastSourceIndex) { + addMatch(seg + 1, 0); } - if (source.flags & TypeFlags.TemplateLiteral) { - const texts = (source as TemplateLiteralType).texts; - return texts.length === 2 && texts[0] === "" && texts[1] === "" && isTypeAssignableTo((source as TemplateLiteralType).types[0], target); + else { + return undefined; } - return isTypeAssignableTo(source, target); - } - - function inferTypesFromTemplateLiteralType(source: Type, target: TemplateLiteralType): Type[] | undefined { - return source.flags & TypeFlags.StringLiteral ? inferFromLiteralPartsToTemplateLiteral([(source as StringLiteralType).value], emptyArray, target) : - source.flags & TypeFlags.TemplateLiteral ? - arraysEqual((source as TemplateLiteralType).texts, target.texts) ? map((source as TemplateLiteralType).types, getStringLikeTypeForType) : - inferFromLiteralPartsToTemplateLiteral((source as TemplateLiteralType).texts, (source as TemplateLiteralType).types, target) : - undefined; } - - function isTypeMatchedByTemplateLiteralType(source: Type, target: TemplateLiteralType): boolean { - const inferences = inferTypesFromTemplateLiteralType(source, target); - return !!inferences && every(inferences, (r, i) => isValidTypeForTemplateLiteralPlaceholder(r, target.types[i])); + addMatch(lastSourceIndex, getSourceText(lastSourceIndex).length); + return matches; + function getSourceText(index: number) { + return index < lastSourceIndex ? sourceTexts[index] : remainingEndText; } - - function getStringLikeTypeForType(type: Type) { - return type.flags & (TypeFlags.Any | TypeFlags.StringLike) ? type : getTemplateLiteralType(["", ""], [type]); + function addMatch(s: number, p: number) { + const matchType = s === seg ? + getStringLiteralType(getSourceText(s).slice(pos, p)) : + getTemplateLiteralType( + [sourceTexts[seg].slice(pos), ...sourceTexts.slice(seg + 1, s), getSourceText(s).slice(0, p)], + sourceTypes.slice(seg, s)); + matches.push(matchType); + seg = s; + pos = p; } + } - // This function infers from the text parts and type parts of a source literal to a target template literal. The number - // of text parts is always one more than the number of type parts, and a source string literal is treated as a source - // with one text part and zero type parts. The function returns an array of inferred string or template literal types - // corresponding to the placeholders in the target template literal, or undefined if the source doesn't match the target. - // - // We first check that the starting source text part matches the starting target text part, and that the ending source - // text part ends matches the ending target text part. We then iterate through the remaining target text parts, finding - // a match for each in the source and inferring string or template literal types created from the segments of the source - // that occur between the matches. During this iteration, seg holds the index of the current text part in the sourceTexts - // array and pos holds the current character position in the current text part. - // - // Consider inference from type `<<${string}>.<${number}-${number}>>` to type `<${string}.${string}>`, i.e. - // sourceTexts = ['<<', '>.<', '-', '>>'] - // sourceTypes = [string, number, number] - // target.texts = ['<', '.', '>'] - // We first match '<' in the target to the start of '<<' in the source and '>' in the target to the end of '>>' in - // the source. The first match for the '.' in target occurs at character 1 in the source text part at index 1, and thus - // the first inference is the template literal type `<${string}>`. The remainder of the source makes up the second - // inference, the template literal type `<${number}-${number}>`. - function inferFromLiteralPartsToTemplateLiteral(sourceTexts: readonly string[], sourceTypes: readonly Type[], target: TemplateLiteralType): Type[] | undefined { - const lastSourceIndex = sourceTexts.length - 1; - const sourceStartText = sourceTexts[0]; - const sourceEndText = sourceTexts[lastSourceIndex]; - const targetTexts = target.texts; - const lastTargetIndex = targetTexts.length - 1; - const targetStartText = targetTexts[0]; - const targetEndText = targetTexts[lastTargetIndex]; - if (lastSourceIndex === 0 && sourceStartText.length < targetStartText.length + targetEndText.length || - !sourceStartText.startsWith(targetStartText) || !sourceEndText.endsWith(targetEndText)) return undefined; - const remainingEndText = sourceEndText.slice(0, sourceEndText.length - targetEndText.length); - const matches: Type[] = []; - let seg = 0; - let pos = targetStartText.length; - for (let i = 1; i < lastTargetIndex; i++) { - const delim = targetTexts[i]; - if (delim.length > 0) { - let s = seg; - let p = pos; - while (true) { - p = getSourceText(s).indexOf(delim, p); - if (p >= 0) break; - s++; - if (s === sourceTexts.length) return undefined; - p = 0; - } - addMatch(s, p); - pos += delim.length; - } - else if (pos < getSourceText(seg).length) { - addMatch(seg, pos + 1); - } - else if (seg < lastSourceIndex) { - addMatch(seg + 1, 0); - } - else { - return undefined; - } + function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) { + let bivariant = false; + let propagationType: Type; + let inferencePriority: number = InferencePriority.MaxValue; + let allowComplexConstraintInference = true; + let visited: ESMap; + let sourceStack: object[]; + let targetStack: object[]; + let expandingFlags = ExpandingFlags.None; + inferFromTypes(originalSource, originalTarget); + + function inferFromTypes(source: Type, target: Type): void { + if (!couldContainTypeVariables(target)) { + return; } - addMatch(lastSourceIndex, getSourceText(lastSourceIndex).length); - return matches; - function getSourceText(index: number) { - return index < lastSourceIndex ? sourceTexts[index] : remainingEndText; - } - function addMatch(s: number, p: number) { - const matchType = s === seg ? - getStringLiteralType(getSourceText(s).slice(pos, p)) : - getTemplateLiteralType( - [sourceTexts[seg].slice(pos), ...sourceTexts.slice(seg + 1, s), getSourceText(s).slice(0, p)], - sourceTypes.slice(seg, s)); - matches.push(matchType); - seg = s; - pos = p; - } - } - - function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) { - let bivariant = false; - let propagationType: Type; - let inferencePriority: number = InferencePriority.MaxValue; - let allowComplexConstraintInference = true; - let visited: ESMap; - let sourceStack: object[]; - let targetStack: object[]; - let expandingFlags = ExpandingFlags.None; - inferFromTypes(originalSource, originalTarget); - - function inferFromTypes(source: Type, target: Type): void { - if (!couldContainTypeVariables(target)) { - return; + if (source === wildcardType) { + // We are inferring from an 'any' type. We want to infer this type for every type parameter + // referenced in the target type, so we record it as the propagation type and infer from the + // target to itself. Then, as we find candidates we substitute the propagation type. + const savePropagationType = propagationType; + propagationType = source; + inferFromTypes(target, target); + propagationType = savePropagationType; + return; + } + if (source.aliasSymbol && source.aliasSymbol === target.aliasSymbol) { + if (source.aliasTypeArguments) { + // Source and target are types originating in the same generic type alias declaration. + // Simply infer from source type arguments to target type arguments. + inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol)); } - if (source === wildcardType) { - // We are inferring from an 'any' type. We want to infer this type for every type parameter - // referenced in the target type, so we record it as the propagation type and infer from the - // target to itself. Then, as we find candidates we substitute the propagation type. - const savePropagationType = propagationType; - propagationType = source; - inferFromTypes(target, target); - propagationType = savePropagationType; - return; + // And if there weren't any type arguments, there's no reason to run inference as the types must be the same. + return; + } + if (source === target && source.flags & TypeFlags.UnionOrIntersection) { + // When source and target are the same union or intersection type, just relate each constituent + // type to itself. + for (const t of (source as UnionOrIntersectionType).types) { + inferFromTypes(t, t); } - if (source.aliasSymbol && source.aliasSymbol === target.aliasSymbol) { - if (source.aliasTypeArguments) { - // Source and target are types originating in the same generic type alias declaration. - // Simply infer from source type arguments to target type arguments. - inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol)); - } - // And if there weren't any type arguments, there's no reason to run inference as the types must be the same. + return; + } + if (target.flags & TypeFlags.Union) { + // First, infer between identically matching source and target constituents and remove the + // matching types. + const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (source as UnionType).types : [source], (target as UnionType).types, isTypeOrBaseIdenticalTo); + // Next, infer between closely matching source and target constituents and remove + // the matching types. Types closely match when they are instantiations of the same + // object type or instantiations of the same type alias. + const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy); + if (targets.length === 0) { return; } - if (source === target && source.flags & TypeFlags.UnionOrIntersection) { - // When source and target are the same union or intersection type, just relate each constituent - // type to itself. - for (const t of (source as UnionOrIntersectionType).types) { - inferFromTypes(t, t); - } + target = getUnionType(targets); + if (sources.length === 0) { + // All source constituents have been matched and there is nothing further to infer from. + // However, simply making no inferences is undesirable because it could ultimately mean + // inferring a type parameter constraint. Instead, make a lower priority inference from + // the full source to whatever remains in the target. For example, when inferring from + // string to 'string | T', make a lower priority inference of string for T. + inferWithPriority(source, target, InferencePriority.NakedTypeVariable); return; } - if (target.flags & TypeFlags.Union) { - // First, infer between identically matching source and target constituents and remove the - // matching types. - const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (source as UnionType).types : [source], (target as UnionType).types, isTypeOrBaseIdenticalTo); - // Next, infer between closely matching source and target constituents and remove - // the matching types. Types closely match when they are instantiations of the same - // object type or instantiations of the same type alias. - const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy); - if (targets.length === 0) { - return; - } - target = getUnionType(targets); - if (sources.length === 0) { - // All source constituents have been matched and there is nothing further to infer from. - // However, simply making no inferences is undesirable because it could ultimately mean - // inferring a type parameter constraint. Instead, make a lower priority inference from - // the full source to whatever remains in the target. For example, when inferring from - // string to 'string | T', make a lower priority inference of string for T. - inferWithPriority(source, target, InferencePriority.NakedTypeVariable); + source = getUnionType(sources); + } + else if (target.flags & TypeFlags.Intersection && !every((target as IntersectionType).types, isNonGenericObjectType)) { + // We reduce intersection types unless they're simple combinations of object types. For example, + // when inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and + // infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the + // string[] on the source side and infer string for T. + if (!(source.flags & TypeFlags.Union)) { + // Infer between identically matching source and target constituents and remove the matching types. + const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types : [source], (target as IntersectionType).types, isTypeIdenticalTo); + if (sources.length === 0 || targets.length === 0) { return; } - source = getUnionType(sources); - } - else if (target.flags & TypeFlags.Intersection && !every((target as IntersectionType).types, isNonGenericObjectType)) { - // We reduce intersection types unless they're simple combinations of object types. For example, - // when inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and - // infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the - // string[] on the source side and infer string for T. - if (!(source.flags & TypeFlags.Union)) { - // Infer between identically matching source and target constituents and remove the matching types. - const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types : [source], (target as IntersectionType).types, isTypeIdenticalTo); - if (sources.length === 0 || targets.length === 0) { - return; - } - source = getIntersectionType(sources); - target = getIntersectionType(targets); - } + source = getIntersectionType(sources); + target = getIntersectionType(targets); } - else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { - target = getActualTypeVariable(target); + } + else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { + target = getActualTypeVariable(target); + } + if (target.flags & TypeFlags.TypeVariable) { + // Skip inference if the source is "blocked", which is used by the language service to + // prevent inference on nodes currently being edited. + if (isFromInferenceBlockedSource(source)) { + return; } - if (target.flags & TypeFlags.TypeVariable) { - // Skip inference if the source is "blocked", which is used by the language service to - // prevent inference on nodes currently being edited. - if (isFromInferenceBlockedSource(source)) { + const inference = getInferenceInfoForType(target); + if (inference) { + // If target is a type parameter, make an inference, unless the source type contains + // a "non-inferrable" type. Types with this flag set are markers used to prevent inference. + // + // For example: + // - anyFunctionType is a wildcard type that's used to avoid contextually typing functions; + // it's internal, so should not be exposed to the user by adding it as a candidate. + // - autoType (and autoArrayType) is a special "any" used in control flow; like anyFunctionType, + // it's internal and should not be observable. + // - silentNeverType is returned by getInferredType when instantiating a generic function for + // inference (and a type variable has no mapping). + // + // This flag is infectious; if we produce Box (where never is silentNeverType), Box is + // also non-inferrable. + // + // As a special case, also ignore nonInferrableAnyType, which is a special form of the any type + // used as a stand-in for binding elements when they are being inferred. + if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType) { return; } - const inference = getInferenceInfoForType(target); - if (inference) { - // If target is a type parameter, make an inference, unless the source type contains - // a "non-inferrable" type. Types with this flag set are markers used to prevent inference. - // - // For example: - // - anyFunctionType is a wildcard type that's used to avoid contextually typing functions; - // it's internal, so should not be exposed to the user by adding it as a candidate. - // - autoType (and autoArrayType) is a special "any" used in control flow; like anyFunctionType, - // it's internal and should not be observable. - // - silentNeverType is returned by getInferredType when instantiating a generic function for - // inference (and a type variable has no mapping). - // - // This flag is infectious; if we produce Box (where never is silentNeverType), Box is - // also non-inferrable. - // - // As a special case, also ignore nonInferrableAnyType, which is a special form of the any type - // used as a stand-in for binding elements when they are being inferred. - if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType) { - return; - } - if (!inference.isFixed) { - if (inference.priority === undefined || priority < inference.priority) { - inference.candidates = undefined; - inference.contraCandidates = undefined; - inference.topLevel = true; - inference.priority = priority; - } - if (priority === inference.priority) { - const candidate = propagationType || source; - // We make contravariant inferences only if we are in a pure contravariant position, - // i.e. only if we have not descended into a bivariant position. - if (contravariant && !bivariant) { - if (!contains(inference.contraCandidates, candidate)) { - inference.contraCandidates = append(inference.contraCandidates, candidate); - clearCachedInferences(inferences); - } - } - else if (!contains(inference.candidates, candidate)) { - inference.candidates = append(inference.candidates, candidate); + if (!inference.isFixed) { + if (inference.priority === undefined || priority < inference.priority) { + inference.candidates = undefined; + inference.contraCandidates = undefined; + inference.topLevel = true; + inference.priority = priority; + } + if (priority === inference.priority) { + const candidate = propagationType || source; + // We make contravariant inferences only if we are in a pure contravariant position, + // i.e. only if we have not descended into a bivariant position. + if (contravariant && !bivariant) { + if (!contains(inference.contraCandidates, candidate)) { + inference.contraCandidates = append(inference.contraCandidates, candidate); clearCachedInferences(inferences); } } - if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && inference.topLevel && !isTypeParameterAtTopLevel(originalTarget, target as TypeParameter)) { - inference.topLevel = false; + else if (!contains(inference.candidates, candidate)) { + inference.candidates = append(inference.candidates, candidate); clearCachedInferences(inferences); } } - inferencePriority = Math.min(inferencePriority, priority); - return; - } - // Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine - const simplified = getSimplifiedType(target, /*writing*/ false); - if (simplified !== target) { - inferFromTypes(source, simplified); - } - else if (target.flags & TypeFlags.IndexedAccess) { - const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false); - // Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider - // that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can. - if (indexType.flags & TypeFlags.Instantiable) { - const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false); - if (simplified && simplified !== target) { - inferFromTypes(source, simplified); - } + if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && inference.topLevel && !isTypeParameterAtTopLevel(originalTarget, target as TypeParameter)) { + inference.topLevel = false; + clearCachedInferences(inferences); } } + inferencePriority = Math.min(inferencePriority, priority); + return; } - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( - (source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target)) && - !((source as TypeReference).node && (target as TypeReference).node)) { - // If source and target are references to the same generic type, infer from type arguments - inferFromTypeArguments(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), getVariances((source as TypeReference).target)); - } - else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) { - inferFromContravariantTypes((source as IndexType).type, (target as IndexType).type); - } - else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) { - const empty = createEmptyObjectTypeFromStringLiteral(source); - inferFromContravariantTypesWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof); - } - else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { - inferFromTypes((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType); - inferFromTypes((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType); + // Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine + const simplified = getSimplifiedType(target, /*writing*/ false); + if (simplified !== target) { + inferFromTypes(source, simplified); } - else if (source.flags & TypeFlags.StringMapping && target.flags & TypeFlags.StringMapping) { - if ((source as StringMappingType).symbol === (target as StringMappingType).symbol) { - inferFromTypes((source as StringMappingType).type, (target as StringMappingType).type); + else if (target.flags & TypeFlags.IndexedAccess) { + const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false); + // Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider + // that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can. + if (indexType.flags & TypeFlags.Instantiable) { + const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false); + if (simplified && simplified !== target) { + inferFromTypes(source, simplified); + } } } - else if (source.flags & TypeFlags.Substitution) { - inferFromTypes((source as SubstitutionType).baseType, target); - inferWithPriority(getSubstitutionIntersection(source as SubstitutionType), target, InferencePriority.SubstituteSource); // Make substitute inference at a lower priority - } - else if (target.flags & TypeFlags.Conditional) { - invokeOnce(source, target, inferToConditionalType); + } + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( + (source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target)) && + !((source as TypeReference).node && (target as TypeReference).node)) { + // If source and target are references to the same generic type, infer from type arguments + inferFromTypeArguments(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), getVariances((source as TypeReference).target)); + } + else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) { + inferFromContravariantTypes((source as IndexType).type, (target as IndexType).type); + } + else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) { + const empty = createEmptyObjectTypeFromStringLiteral(source); + inferFromContravariantTypesWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof); + } + else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { + inferFromTypes((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType); + inferFromTypes((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType); + } + else if (source.flags & TypeFlags.StringMapping && target.flags & TypeFlags.StringMapping) { + if ((source as StringMappingType).symbol === (target as StringMappingType).symbol) { + inferFromTypes((source as StringMappingType).type, (target as StringMappingType).type); } - else if (target.flags & TypeFlags.UnionOrIntersection) { - inferToMultipleTypes(source, (target as UnionOrIntersectionType).types, target.flags); + } + else if (source.flags & TypeFlags.Substitution) { + inferFromTypes((source as SubstitutionType).baseType, target); + inferWithPriority(getSubstitutionIntersection(source as SubstitutionType), target, InferencePriority.SubstituteSource); // Make substitute inference at a lower priority + } + else if (target.flags & TypeFlags.Conditional) { + invokeOnce(source, target, inferToConditionalType); + } + else if (target.flags & TypeFlags.UnionOrIntersection) { + inferToMultipleTypes(source, (target as UnionOrIntersectionType).types, target.flags); + } + else if (source.flags & TypeFlags.Union) { + // Source is a union or intersection type, infer from each constituent type + const sourceTypes = (source as UnionOrIntersectionType).types; + for (const sourceType of sourceTypes) { + inferFromTypes(sourceType, target); } - else if (source.flags & TypeFlags.Union) { - // Source is a union or intersection type, infer from each constituent type - const sourceTypes = (source as UnionOrIntersectionType).types; - for (const sourceType of sourceTypes) { - inferFromTypes(sourceType, target); + } + else if (target.flags & TypeFlags.TemplateLiteral) { + inferToTemplateLiteralType(source, target as TemplateLiteralType); + } + else { + source = getReducedType(source); + if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) { + const apparentSource = getApparentType(source); + // getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type. + // If that occurs and it doesn't simplify to an object or intersection, we'll need to restart `inferFromTypes` + // with the simplified source. + if (apparentSource !== source && allowComplexConstraintInference && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) { + // TODO: The `allowComplexConstraintInference` flag is a hack! This forbids inference from complex constraints within constraints! + // This isn't required algorithmically, but rather is used to lower the memory burden caused by performing inference + // that is _too good_ in projects with complicated constraints (eg, fp-ts). In such cases, if we did not limit ourselves + // here, we might produce more valid inferences for types, causing us to do more checks and perform more instantiations + // (in addition to the extra stack depth here) which, in turn, can push the already close process over its limit. + // TL;DR: If we ever become generally more memory efficient (or our resource budget ever increases), we should just + // remove this `allowComplexConstraintInference` flag. + allowComplexConstraintInference = false; + return inferFromTypes(apparentSource, target); } + source = apparentSource; } - else if (target.flags & TypeFlags.TemplateLiteral) { - inferToTemplateLiteralType(source, target as TemplateLiteralType); + if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { + invokeOnce(source, target, inferFromObjectTypes); } - else { - source = getReducedType(source); - if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) { - const apparentSource = getApparentType(source); - // getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type. - // If that occurs and it doesn't simplify to an object or intersection, we'll need to restart `inferFromTypes` - // with the simplified source. - if (apparentSource !== source && allowComplexConstraintInference && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) { - // TODO: The `allowComplexConstraintInference` flag is a hack! This forbids inference from complex constraints within constraints! - // This isn't required algorithmically, but rather is used to lower the memory burden caused by performing inference - // that is _too good_ in projects with complicated constraints (eg, fp-ts). In such cases, if we did not limit ourselves - // here, we might produce more valid inferences for types, causing us to do more checks and perform more instantiations - // (in addition to the extra stack depth here) which, in turn, can push the already close process over its limit. - // TL;DR: If we ever become generally more memory efficient (or our resource budget ever increases), we should just - // remove this `allowComplexConstraintInference` flag. - allowComplexConstraintInference = false; - return inferFromTypes(apparentSource, target); - } - source = apparentSource; - } - if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { - invokeOnce(source, target, inferFromObjectTypes); - } - } - } - - function inferWithPriority(source: Type, target: Type, newPriority: InferencePriority) { - const savePriority = priority; - priority |= newPriority; - inferFromTypes(source, target); - priority = savePriority; } + } - function inferFromContravariantTypesWithPriority(source: Type, target: Type, newPriority: InferencePriority) { - const savePriority = priority; - priority |= newPriority; - inferFromContravariantTypes(source, target); - priority = savePriority; - } + function inferWithPriority(source: Type, target: Type, newPriority: InferencePriority) { + const savePriority = priority; + priority |= newPriority; + inferFromTypes(source, target); + priority = savePriority; + } - function inferToMultipleTypesWithPriority(source: Type, targets: Type[], targetFlags: TypeFlags, newPriority: InferencePriority) { - const savePriority = priority; - priority |= newPriority; - inferToMultipleTypes(source, targets, targetFlags); - priority = savePriority; - } + function inferFromContravariantTypesWithPriority(source: Type, target: Type, newPriority: InferencePriority) { + const savePriority = priority; + priority |= newPriority; + inferFromContravariantTypes(source, target); + priority = savePriority; + } - function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) { - const key = source.id + "," + target.id; - const status = visited && visited.get(key); - if (status !== undefined) { - inferencePriority = Math.min(inferencePriority, status); - return; - } - (visited || (visited = new Map())).set(key, InferencePriority.Circularity); - const saveInferencePriority = inferencePriority; - inferencePriority = InferencePriority.MaxValue; - // We stop inferring and report a circularity if we encounter duplicate recursion identities on both - // the source side and the target side. - const saveExpandingFlags = expandingFlags; - const sourceIdentity = getRecursionIdentity(source); - const targetIdentity = getRecursionIdentity(target); - if (contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source; - if (contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target; - if (expandingFlags !== ExpandingFlags.Both) { - (sourceStack || (sourceStack = [])).push(sourceIdentity); - (targetStack || (targetStack = [])).push(targetIdentity); - action(source, target); - targetStack.pop(); - sourceStack.pop(); - } - else { - inferencePriority = InferencePriority.Circularity; - } - expandingFlags = saveExpandingFlags; - visited.set(key, inferencePriority); - inferencePriority = Math.min(inferencePriority, saveInferencePriority); - } + function inferToMultipleTypesWithPriority(source: Type, targets: Type[], targetFlags: TypeFlags, newPriority: InferencePriority) { + const savePriority = priority; + priority |= newPriority; + inferToMultipleTypes(source, targets, targetFlags); + priority = savePriority; + } - function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] { - let matchedSources: Type[] | undefined; - let matchedTargets: Type[] | undefined; - for (const t of targets) { - for (const s of sources) { - if (matches(s, t)) { - inferFromTypes(s, t); - matchedSources = appendIfUnique(matchedSources, s); - matchedTargets = appendIfUnique(matchedTargets, t); - } - } - } - return [ - matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources, - matchedTargets ? filter(targets, t => !contains(matchedTargets, t)) : targets, - ]; + function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) { + const key = source.id + "," + target.id; + const status = visited && visited.get(key); + if (status !== undefined) { + inferencePriority = Math.min(inferencePriority, status); + return; + } + (visited || (visited = new Map())).set(key, InferencePriority.Circularity); + const saveInferencePriority = inferencePriority; + inferencePriority = InferencePriority.MaxValue; + // We stop inferring and report a circularity if we encounter duplicate recursion identities on both + // the source side and the target side. + const saveExpandingFlags = expandingFlags; + const sourceIdentity = getRecursionIdentity(source); + const targetIdentity = getRecursionIdentity(target); + if (contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source; + if (contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target; + if (expandingFlags !== ExpandingFlags.Both) { + (sourceStack || (sourceStack = [])).push(sourceIdentity); + (targetStack || (targetStack = [])).push(targetIdentity); + action(source, target); + targetStack.pop(); + sourceStack.pop(); + } + else { + inferencePriority = InferencePriority.Circularity; } + expandingFlags = saveExpandingFlags; + visited.set(key, inferencePriority); + inferencePriority = Math.min(inferencePriority, saveInferencePriority); + } - 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 inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] { + let matchedSources: Type[] | undefined; + let matchedTargets: Type[] | undefined; + for (const t of targets) { + for (const s of sources) { + if (matches(s, t)) { + inferFromTypes(s, t); + matchedSources = appendIfUnique(matchedSources, s); + matchedTargets = appendIfUnique(matchedTargets, t); } } } + return [ + matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources, + matchedTargets ? filter(targets, t => !contains(matchedTargets, t)) : targets, + ]; + } - function inferFromContravariantTypes(source: Type, target: Type) { - contravariant = !contravariant; - inferFromTypes(source, target); - contravariant = !contravariant; - } - - function inferFromContravariantTypesIfStrictFunctionTypes(source: Type, target: Type) { - if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) { - inferFromContravariantTypes(source, target); + 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(source, target); + inferFromTypes(sourceTypes[i], targetTypes[i]); } } + } - function getInferenceInfoForType(type: Type) { - if (type.flags & TypeFlags.TypeVariable) { - for (const inference of inferences) { - if (type === inference.typeParameter) { - return inference; - } + function inferFromContravariantTypes(source: Type, target: Type) { + contravariant = !contravariant; + inferFromTypes(source, target); + contravariant = !contravariant; + } + + function inferFromContravariantTypesIfStrictFunctionTypes(source: Type, target: Type) { + if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) { + inferFromContravariantTypes(source, target); + } + else { + inferFromTypes(source, target); + } + } + + function getInferenceInfoForType(type: Type) { + if (type.flags & TypeFlags.TypeVariable) { + for (const inference of inferences) { + if (type === inference.typeParameter) { + return inference; } } - return undefined; } + return undefined; + } - function getSingleTypeVariableFromIntersectionTypes(types: Type[]) { - let typeVariable: Type | undefined; - for (const type of types) { - const t = type.flags & TypeFlags.Intersection && find((type as IntersectionType).types, t => !!getInferenceInfoForType(t)); - if (!t || typeVariable && t !== typeVariable) { - return undefined; - } - typeVariable = t; + function getSingleTypeVariableFromIntersectionTypes(types: Type[]) { + let typeVariable: Type | undefined; + for (const type of types) { + const t = type.flags & TypeFlags.Intersection && find((type as IntersectionType).types, t => !!getInferenceInfoForType(t)); + if (!t || typeVariable && t !== typeVariable) { + return undefined; } - return typeVariable; + typeVariable = t; } + return typeVariable; + } - function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { - let typeVariableCount = 0; - if (targetFlags & TypeFlags.Union) { - let nakedTypeVariable: Type | undefined; - const sources = source.flags & TypeFlags.Union ? (source as UnionType).types : [source]; - const matched = new Array(sources.length); - let inferenceCircularity = false; - // First infer to types that are not naked type variables. For each source type we - // track whether inferences were made from that particular type to some target with - // equal priority (i.e. of equal quality) to what we would infer for a naked type - // parameter. - for (const t of targets) { - if (getInferenceInfoForType(t)) { - nakedTypeVariable = t; - typeVariableCount++; - } - else { - for (let i = 0; i < sources.length; i++) { - const saveInferencePriority = inferencePriority; - inferencePriority = InferencePriority.MaxValue; - inferFromTypes(sources[i], t); - if (inferencePriority === priority) matched[i] = true; - inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity; - inferencePriority = Math.min(inferencePriority, saveInferencePriority); - } - } - } - if (typeVariableCount === 0) { - // If every target is an intersection of types containing a single naked type variable, - // make a lower priority inference to that type variable. This handles inferring from - // 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T. - const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets); - if (intersectionTypeVariable) { - inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable); - } - return; + function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { + let typeVariableCount = 0; + if (targetFlags & TypeFlags.Union) { + let nakedTypeVariable: Type | undefined; + const sources = source.flags & TypeFlags.Union ? (source as UnionType).types : [source]; + const matched = new Array(sources.length); + let inferenceCircularity = false; + // First infer to types that are not naked type variables. For each source type we + // track whether inferences were made from that particular type to some target with + // equal priority (i.e. of equal quality) to what we would infer for a naked type + // parameter. + for (const t of targets) { + if (getInferenceInfoForType(t)) { + nakedTypeVariable = t; + typeVariableCount++; } - // If the target has a single naked type variable and no inference circularities were - // encountered above (meaning we explored the types fully), create a union of the source - // types from which no inferences have been made so far and infer from that union to the - // naked type variable. - if (typeVariableCount === 1 && !inferenceCircularity) { - const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); - if (unmatched.length) { - inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); - return; + else { + for (let i = 0; i < sources.length; i++) { + const saveInferencePriority = inferencePriority; + inferencePriority = InferencePriority.MaxValue; + inferFromTypes(sources[i], t); + if (inferencePriority === priority) matched[i] = true; + inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity; + inferencePriority = Math.min(inferencePriority, saveInferencePriority); } } } - else { - // We infer from types that are not naked type variables first so that inferences we - // make from nested naked type variables and given slightly higher priority by virtue - // of being first in the candidates array. - for (const t of targets) { - if (getInferenceInfoForType(t)) { - typeVariableCount++; - } - else { - inferFromTypes(source, t); - } + if (typeVariableCount === 0) { + // If every target is an intersection of types containing a single naked type variable, + // make a lower priority inference to that type variable. This handles inferring from + // 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T. + const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets); + if (intersectionTypeVariable) { + inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable); } + return; } - // Inferences directly to naked type variables are given lower priority as they are - // less specific. For example, when inferring from Promise to T | Promise, - // we want to infer string for T, not Promise | string. For intersection types - // we only infer to single naked type variables. - if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { - for (const t of targets) { - if (getInferenceInfoForType(t)) { - inferWithPriority(source, t, InferencePriority.NakedTypeVariable); - } + // If the target has a single naked type variable and no inference circularities were + // encountered above (meaning we explored the types fully), create a union of the source + // types from which no inferences have been made so far and infer from that union to the + // naked type variable. + if (typeVariableCount === 1 && !inferenceCircularity) { + const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); + if (unmatched.length) { + inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); + return; } } } - - function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { - if (constraintType.flags & TypeFlags.Union) { - let result = false; - for (const type of (constraintType as UnionType).types) { - result = inferToMappedType(source, target, type) || result; + else { + // We infer from types that are not naked type variables first so that inferences we + // make from nested naked type variables and given slightly higher priority by virtue + // of being first in the candidates array. + for (const t of targets) { + if (getInferenceInfoForType(t)) { + typeVariableCount++; + } + else { + inferFromTypes(source, t); } - return result; } - if (constraintType.flags & TypeFlags.Index) { - // We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X }, - // where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source - // type and then make a secondary inference from that type to T. We make a secondary inference - // such that direct inferences to T get priority over inferences to Partial, for example. - const inference = getInferenceInfoForType((constraintType as IndexType).type); - if (inference && !inference.isFixed && !isFromInferenceBlockedSource(source)) { - const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType as IndexType); - if (inferredType) { - // 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). - inferWithPriority(inferredType, inference.typeParameter, - getObjectFlags(source) & ObjectFlags.NonInferrableType ? - InferencePriority.PartialHomomorphicMappedType : - InferencePriority.HomomorphicMappedType); - } + } + // Inferences directly to naked type variables are given lower priority as they are + // less specific. For example, when inferring from Promise to T | Promise, + // we want to infer string for T, not Promise | string. For intersection types + // we only infer to single naked type variables. + if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { + for (const t of targets) { + if (getInferenceInfoForType(t)) { + inferWithPriority(source, t, InferencePriority.NakedTypeVariable); } - return true; } - if (constraintType.flags & TypeFlags.TypeParameter) { - // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type - // parameter. First infer from 'keyof S' to K. - inferWithPriority(getIndexType(source), constraintType, InferencePriority.MappedTypeConstraint); - // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, - // where K extends keyof T, we make the same inferences as for a homomorphic mapped type - // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a - // Pick. - const extendedConstraint = getConstraintOfType(constraintType); - if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) { - return true; + } + } + + function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { + if (constraintType.flags & TypeFlags.Union) { + let result = false; + for (const type of (constraintType as UnionType).types) { + result = inferToMappedType(source, target, type) || result; + } + return result; + } + if (constraintType.flags & TypeFlags.Index) { + // We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X }, + // where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source + // type and then make a secondary inference from that type to T. We make a secondary inference + // such that direct inferences to T get priority over inferences to Partial, for example. + const inference = getInferenceInfoForType((constraintType as IndexType).type); + if (inference && !inference.isFixed && !isFromInferenceBlockedSource(source)) { + const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType as IndexType); + if (inferredType) { + // 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). + inferWithPriority(inferredType, inference.typeParameter, + getObjectFlags(source) & ObjectFlags.NonInferrableType ? + InferencePriority.PartialHomomorphicMappedType : + InferencePriority.HomomorphicMappedType); } - // If no inferences can be made to K's constraint, infer from a union of the property types - // in the source to the template type X. - const propTypes = map(getPropertiesOfType(source), getTypeOfSymbol); - const indexTypes = map(getIndexInfosOfType(source), info => info !== enumNumberIndexInfo ? info.type : neverType); - inferFromTypes(getUnionType(concatenate(propTypes, indexTypes)), getTemplateTypeFromMappedType(target)); + } + return true; + } + if (constraintType.flags & TypeFlags.TypeParameter) { + // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type + // parameter. First infer from 'keyof S' to K. + inferWithPriority(getIndexType(source), constraintType, InferencePriority.MappedTypeConstraint); + // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, + // where K extends keyof T, we make the same inferences as for a homomorphic mapped type + // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a + // Pick. + const extendedConstraint = getConstraintOfType(constraintType); + if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) { return true; } - return false; + // If no inferences can be made to K's constraint, infer from a union of the property types + // in the source to the template type X. + const propTypes = map(getPropertiesOfType(source), getTypeOfSymbol); + const indexTypes = map(getIndexInfosOfType(source), info => info !== enumNumberIndexInfo ? info.type : neverType); + inferFromTypes(getUnionType(concatenate(propTypes, indexTypes)), getTemplateTypeFromMappedType(target)); + return true; } + return false; + } - function inferToConditionalType(source: Type, target: ConditionalType) { - if (source.flags & TypeFlags.Conditional) { - inferFromTypes((source as ConditionalType).checkType, target.checkType); - inferFromTypes((source as ConditionalType).extendsType, target.extendsType); - inferFromTypes(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target)); - inferFromTypes(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target)); - } - else { - const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; - inferToMultipleTypesWithPriority(source, targetTypes, target.flags, contravariant ? InferencePriority.ContravariantConditional : 0); - } - } - - function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) { - const matches = inferTypesFromTemplateLiteralType(source, target); - const types = target.types; - // When the target template literal contains only placeholders (meaning that inference is intended to extract - // single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for - // each placeholder such that instantiation with the inferred value(s) produces 'never', a type for which an - // assignment check will fail. If we make no inferences, we'll likely end up with the constraint 'string' which, - // upon instantiation, would collapse all the placeholders to just 'string', and an assignment check might - // succeed. That would be a pointless and confusing outcome. - if (matches || every(target.texts, s => s.length === 0)) { - for (let i = 0; i < types.length; i++) { - const source = matches ? matches[i] : neverType; - const target = types[i]; - - // If we are inferring from a string literal type to a type variable whose constraint includes one of the - // allowed template literal placeholder types, infer from a literal type corresponding to the constraint. - if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.TypeVariable) { - const inferenceContext = getInferenceInfoForType(target); - const constraint = inferenceContext ? getBaseConstraintOfType(inferenceContext.typeParameter) : undefined; - if (constraint && !isTypeAny(constraint)) { - const constraintTypes = constraint.flags & TypeFlags.Union ? (constraint as UnionType).types : [constraint]; - let allTypeFlags: TypeFlags = reduceLeft(constraintTypes, (flags, t) => flags | t.flags, 0 as TypeFlags); - - // If the constraint contains `string`, we don't need to look for a more preferred type - if (!(allTypeFlags & TypeFlags.String)) { - const str = (source as StringLiteralType).value; - - // If the type contains `number` or a number literal and the string isn't a valid number, exclude numbers - if (allTypeFlags & TypeFlags.NumberLike && !isValidNumberString(str, /*roundTripOnly*/ true)) { - allTypeFlags &= ~TypeFlags.NumberLike; - } + function inferToConditionalType(source: Type, target: ConditionalType) { + if (source.flags & TypeFlags.Conditional) { + inferFromTypes((source as ConditionalType).checkType, target.checkType); + inferFromTypes((source as ConditionalType).extendsType, target.extendsType); + inferFromTypes(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target)); + inferFromTypes(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target)); + } + else { + const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; + inferToMultipleTypesWithPriority(source, targetTypes, target.flags, contravariant ? InferencePriority.ContravariantConditional : 0); + } + } - // If the type contains `bigint` or a bigint literal and the string isn't a valid bigint, exclude bigints - if (allTypeFlags & TypeFlags.BigIntLike && !isValidBigIntString(str, /*roundTripOnly*/ true)) { - allTypeFlags &= ~TypeFlags.BigIntLike; - } + function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) { + const matches = inferTypesFromTemplateLiteralType(source, target); + const types = target.types; + // When the target template literal contains only placeholders (meaning that inference is intended to extract + // single characters and remainder strings) and inference fails to produce matches, we want to infer 'never' for + // each placeholder such that instantiation with the inferred value(s) produces 'never', a type for which an + // assignment check will fail. If we make no inferences, we'll likely end up with the constraint 'string' which, + // upon instantiation, would collapse all the placeholders to just 'string', and an assignment check might + // succeed. That would be a pointless and confusing outcome. + if (matches || every(target.texts, s => s.length === 0)) { + for (let i = 0; i < types.length; i++) { + const source = matches ? matches[i] : neverType; + const target = types[i]; + + // If we are inferring from a string literal type to a type variable whose constraint includes one of the + // allowed template literal placeholder types, infer from a literal type corresponding to the constraint. + if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.TypeVariable) { + const inferenceContext = getInferenceInfoForType(target); + const constraint = inferenceContext ? getBaseConstraintOfType(inferenceContext.typeParameter) : undefined; + if (constraint && !isTypeAny(constraint)) { + const constraintTypes = constraint.flags & TypeFlags.Union ? (constraint as UnionType).types : [constraint]; + let allTypeFlags: TypeFlags = reduceLeft(constraintTypes, (flags, t) => flags | t.flags, 0 as TypeFlags); + + // If the constraint contains `string`, we don't need to look for a more preferred type + if (!(allTypeFlags & TypeFlags.String)) { + const str = (source as StringLiteralType).value; + + // If the type contains `number` or a number literal and the string isn't a valid number, exclude numbers + if (allTypeFlags & TypeFlags.NumberLike && !isValidNumberString(str, /*roundTripOnly*/ true)) { + allTypeFlags &= ~TypeFlags.NumberLike; + } - // for each type in the constraint, find the highest priority matching type - const matchingType = reduceLeft(constraintTypes, (left, right) => - !(right.flags & allTypeFlags) ? left : - left.flags & TypeFlags.String ? left : right.flags & TypeFlags.String ? source : - left.flags & TypeFlags.TemplateLiteral ? left : right.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, right as TemplateLiteralType) ? source : - left.flags & TypeFlags.StringMapping ? left : right.flags & TypeFlags.StringMapping && str === applyStringMapping(right.symbol, str) ? source : - left.flags & TypeFlags.StringLiteral ? left : right.flags & TypeFlags.StringLiteral && (right as StringLiteralType).value === str ? right : - left.flags & TypeFlags.Number ? left : right.flags & TypeFlags.Number ? getNumberLiteralType(+str) : - left.flags & TypeFlags.Enum ? left : right.flags & TypeFlags.Enum ? getNumberLiteralType(+str) : - left.flags & TypeFlags.NumberLiteral ? left : right.flags & TypeFlags.NumberLiteral && (right as NumberLiteralType).value === +str ? right : - left.flags & TypeFlags.BigInt ? left : right.flags & TypeFlags.BigInt ? parseBigIntLiteralType(str) : - left.flags & TypeFlags.BigIntLiteral ? left : right.flags & TypeFlags.BigIntLiteral && pseudoBigIntToString((right as BigIntLiteralType).value) === str ? right : - left.flags & TypeFlags.Boolean ? left : right.flags & TypeFlags.Boolean ? str === "true" ? trueType : str === "false" ? falseType : booleanType : - left.flags & TypeFlags.BooleanLiteral ? left : right.flags & TypeFlags.BooleanLiteral && (right as IntrinsicType).intrinsicName === str ? right : - left.flags & TypeFlags.Undefined ? left : right.flags & TypeFlags.Undefined && (right as IntrinsicType).intrinsicName === str ? right : - left.flags & TypeFlags.Null ? left : right.flags & TypeFlags.Null && (right as IntrinsicType).intrinsicName === str ? right : - left, - neverType as Type); - - if (!(matchingType.flags & TypeFlags.Never)) { - inferFromTypes(matchingType, target); - continue; - } + // If the type contains `bigint` or a bigint literal and the string isn't a valid bigint, exclude bigints + if (allTypeFlags & TypeFlags.BigIntLike && !isValidBigIntString(str, /*roundTripOnly*/ true)) { + allTypeFlags &= ~TypeFlags.BigIntLike; + } + + // for each type in the constraint, find the highest priority matching type + const matchingType = reduceLeft(constraintTypes, (left, right) => + !(right.flags & allTypeFlags) ? left : + left.flags & TypeFlags.String ? left : right.flags & TypeFlags.String ? source : + left.flags & TypeFlags.TemplateLiteral ? left : right.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, right as TemplateLiteralType) ? source : + left.flags & TypeFlags.StringMapping ? left : right.flags & TypeFlags.StringMapping && str === applyStringMapping(right.symbol, str) ? source : + left.flags & TypeFlags.StringLiteral ? left : right.flags & TypeFlags.StringLiteral && (right as StringLiteralType).value === str ? right : + left.flags & TypeFlags.Number ? left : right.flags & TypeFlags.Number ? getNumberLiteralType(+str) : + left.flags & TypeFlags.Enum ? left : right.flags & TypeFlags.Enum ? getNumberLiteralType(+str) : + left.flags & TypeFlags.NumberLiteral ? left : right.flags & TypeFlags.NumberLiteral && (right as NumberLiteralType).value === +str ? right : + left.flags & TypeFlags.BigInt ? left : right.flags & TypeFlags.BigInt ? parseBigIntLiteralType(str) : + left.flags & TypeFlags.BigIntLiteral ? left : right.flags & TypeFlags.BigIntLiteral && pseudoBigIntToString((right as BigIntLiteralType).value) === str ? right : + left.flags & TypeFlags.Boolean ? left : right.flags & TypeFlags.Boolean ? str === "true" ? trueType : str === "false" ? falseType : booleanType : + left.flags & TypeFlags.BooleanLiteral ? left : right.flags & TypeFlags.BooleanLiteral && (right as IntrinsicType).intrinsicName === str ? right : + left.flags & TypeFlags.Undefined ? left : right.flags & TypeFlags.Undefined && (right as IntrinsicType).intrinsicName === str ? right : + left.flags & TypeFlags.Null ? left : right.flags & TypeFlags.Null && (right as IntrinsicType).intrinsicName === str ? right : + left, + neverType as Type); + + if (!(matchingType.flags & TypeFlags.Never)) { + inferFromTypes(matchingType, target); + continue; } } } - - inferFromTypes(source, target); } + + inferFromTypes(source, target); } } + } - function inferFromObjectTypes(source: Type, target: Type) { - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( - (source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target))) { - // If source and target are references to the same generic type, infer from type arguments - inferFromTypeArguments(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), getVariances((source as TypeReference).target)); + function inferFromObjectTypes(source: Type, target: Type) { + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( + (source as TypeReference).target === (target as TypeReference).target || isArrayType(source) && isArrayType(target))) { + // If source and target are references to the same generic type, infer from type arguments + inferFromTypeArguments(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), getVariances((source as TypeReference).target)); + return; + } + if (isGenericMappedType(source) && isGenericMappedType(target)) { + // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer + // from S to T and from X to Y. + inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target)); + inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target)); + const sourceNameType = getNameTypeFromMappedType(source); + const targetNameType = getNameTypeFromMappedType(target); + if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType); + } + if (getObjectFlags(target) & ObjectFlags.Mapped && !(target as MappedType).declaration.nameType) { + const constraintType = getConstraintTypeFromMappedType(target as MappedType); + if (inferToMappedType(source, target as MappedType, constraintType)) { return; } - if (isGenericMappedType(source) && isGenericMappedType(target)) { - // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer - // from S to T and from X to Y. - inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target)); - inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target)); - const sourceNameType = getNameTypeFromMappedType(source); - const targetNameType = getNameTypeFromMappedType(target); - if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType); - } - if (getObjectFlags(target) & ObjectFlags.Mapped && !(target as MappedType).declaration.nameType) { - const constraintType = getConstraintTypeFromMappedType(target as MappedType); - if (inferToMappedType(source, target as MappedType, constraintType)) { - return; - } - } - // Infer from the members of source and target only if the two types are possibly related - if (!typesDefinitelyUnrelated(source, target)) { - if (isArrayOrTupleType(source)) { - if (isTupleType(target)) { - const sourceArity = getTypeReferenceArity(source); - const targetArity = getTypeReferenceArity(target); - const elementTypes = getTypeArguments(target); - const elementFlags = target.target.elementFlags; - // When source and target are tuple types with the same structure (fixed, variadic, and rest are matched - // to the same kind in each position), simply infer between the element types. - if (isTupleType(source) && isTupleTypeStructureMatching(source, target)) { - for (let i = 0; i < targetArity; i++) { - inferFromTypes(getTypeArguments(source)[i], elementTypes[i]); - } - return; - } - const startLength = isTupleType(source) ? Math.min(source.target.fixedLength, target.target.fixedLength) : 0; - const endLength = Math.min(isTupleType(source) ? getEndElementCount(source.target, ElementFlags.Fixed) : 0, - target.target.hasRestElement ? getEndElementCount(target.target, ElementFlags.Fixed) : 0); - // Infer between starting fixed elements. - for (let i = 0; i < startLength; i++) { + } + // Infer from the members of source and target only if the two types are possibly related + if (!typesDefinitelyUnrelated(source, target)) { + if (isArrayOrTupleType(source)) { + if (isTupleType(target)) { + const sourceArity = getTypeReferenceArity(source); + const targetArity = getTypeReferenceArity(target); + const elementTypes = getTypeArguments(target); + const elementFlags = target.target.elementFlags; + // When source and target are tuple types with the same structure (fixed, variadic, and rest are matched + // to the same kind in each position), simply infer between the element types. + if (isTupleType(source) && isTupleTypeStructureMatching(source, target)) { + for (let i = 0; i < targetArity; i++) { inferFromTypes(getTypeArguments(source)[i], elementTypes[i]); } - if (!isTupleType(source) || sourceArity - startLength - endLength === 1 && source.target.elementFlags[startLength] & ElementFlags.Rest) { - // Single rest element remains in source, infer from that to every element in target - const restType = getTypeArguments(source)[startLength]; - for (let i = startLength; i < targetArity - endLength; i++) { - inferFromTypes(elementFlags[i] & ElementFlags.Variadic ? createArrayType(restType) : restType, elementTypes[i]); - } + return; + } + const startLength = isTupleType(source) ? Math.min(source.target.fixedLength, target.target.fixedLength) : 0; + const endLength = Math.min(isTupleType(source) ? getEndElementCount(source.target, ElementFlags.Fixed) : 0, + target.target.hasRestElement ? getEndElementCount(target.target, ElementFlags.Fixed) : 0); + // Infer between starting fixed elements. + for (let i = 0; i < startLength; i++) { + inferFromTypes(getTypeArguments(source)[i], elementTypes[i]); + } + if (!isTupleType(source) || sourceArity - startLength - endLength === 1 && source.target.elementFlags[startLength] & ElementFlags.Rest) { + // Single rest element remains in source, infer from that to every element in target + const restType = getTypeArguments(source)[startLength]; + for (let i = startLength; i < targetArity - endLength; i++) { + inferFromTypes(elementFlags[i] & ElementFlags.Variadic ? createArrayType(restType) : restType, elementTypes[i]); } - else { - const middleLength = targetArity - startLength - endLength; - if (middleLength === 2 && elementFlags[startLength] & elementFlags[startLength + 1] & ElementFlags.Variadic && isTupleType(source)) { - // Middle of target is [...T, ...U] and source is tuple type - const targetInfo = getInferenceInfoForType(elementTypes[startLength]); - if (targetInfo && targetInfo.impliedArity !== undefined) { - // Infer slices from source based on implied arity of T. - inferFromTypes(sliceTupleType(source, startLength, endLength + sourceArity - targetInfo.impliedArity), elementTypes[startLength]); - inferFromTypes(sliceTupleType(source, startLength + targetInfo.impliedArity, endLength), elementTypes[startLength + 1]); - } - } - else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Variadic) { - // Middle of target is exactly one variadic element. Infer the slice between the fixed parts in the source. - // If target ends in optional element(s), make a lower priority a speculative inference. - const endsInOptional = target.target.elementFlags[targetArity - 1] & ElementFlags.Optional; - const sourceSlice = isTupleType(source) ? sliceTupleType(source, startLength, endLength) : createArrayType(getTypeArguments(source)[0]); - inferWithPriority(sourceSlice, elementTypes[startLength], endsInOptional ? InferencePriority.SpeculativeTuple : 0); - } - else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Rest) { - // Middle of target is exactly one rest element. If middle of source is not empty, infer union of middle element types. - const restType = isTupleType(source) ? getElementTypeOfSliceOfTupleType(source, startLength, endLength) : getTypeArguments(source)[0]; - if (restType) { - inferFromTypes(restType, elementTypes[startLength]); - } + } + else { + const middleLength = targetArity - startLength - endLength; + if (middleLength === 2 && elementFlags[startLength] & elementFlags[startLength + 1] & ElementFlags.Variadic && isTupleType(source)) { + // Middle of target is [...T, ...U] and source is tuple type + const targetInfo = getInferenceInfoForType(elementTypes[startLength]); + if (targetInfo && targetInfo.impliedArity !== undefined) { + // Infer slices from source based on implied arity of T. + inferFromTypes(sliceTupleType(source, startLength, endLength + sourceArity - targetInfo.impliedArity), elementTypes[startLength]); + inferFromTypes(sliceTupleType(source, startLength + targetInfo.impliedArity, endLength), elementTypes[startLength + 1]); } } - // Infer between ending fixed elements - for (let i = 0; i < endLength; i++) { - inferFromTypes(getTypeArguments(source)[sourceArity - i - 1], elementTypes[targetArity - i - 1]); + else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Variadic) { + // Middle of target is exactly one variadic element. Infer the slice between the fixed parts in the source. + // If target ends in optional element(s), make a lower priority a speculative inference. + const endsInOptional = target.target.elementFlags[targetArity - 1] & ElementFlags.Optional; + const sourceSlice = isTupleType(source) ? sliceTupleType(source, startLength, endLength) : createArrayType(getTypeArguments(source)[0]); + inferWithPriority(sourceSlice, elementTypes[startLength], endsInOptional ? InferencePriority.SpeculativeTuple : 0); + } + else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Rest) { + // Middle of target is exactly one rest element. If middle of source is not empty, infer union of middle element types. + const restType = isTupleType(source) ? getElementTypeOfSliceOfTupleType(source, startLength, endLength) : getTypeArguments(source)[0]; + if (restType) { + inferFromTypes(restType, elementTypes[startLength]); + } } - return; } - if (isArrayType(target)) { - inferFromIndexTypes(source, target); - return; + // Infer between ending fixed elements + for (let i = 0; i < endLength; i++) { + inferFromTypes(getTypeArguments(source)[sourceArity - i - 1], elementTypes[targetArity - i - 1]); } + return; } - inferFromProperties(source, target); - inferFromSignatures(source, target, SignatureKind.Call); - inferFromSignatures(source, target, SignatureKind.Construct); - inferFromIndexTypes(source, target); - } - } - - function inferFromProperties(source: Type, target: Type) { - const properties = getPropertiesOfObjectType(target); - for (const targetProp of properties) { - const sourceProp = getPropertyOfType(source, targetProp.escapedName); - if (sourceProp && !some(sourceProp.declarations, hasSkipDirectInferenceFlag)) { - inferFromTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); + if (isArrayType(target)) { + inferFromIndexTypes(source, target); + return; } } + inferFromProperties(source, target); + inferFromSignatures(source, target, SignatureKind.Call); + inferFromSignatures(source, target, SignatureKind.Construct); + inferFromIndexTypes(source, target); } + } - function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) { - const sourceSignatures = getSignaturesOfType(source, kind); - const targetSignatures = getSignaturesOfType(target, kind); - const sourceLen = sourceSignatures.length; - const targetLen = targetSignatures.length; - const len = sourceLen < targetLen ? sourceLen : targetLen; - for (let i = 0; i < len; i++) { - inferFromSignature(getBaseSignature(sourceSignatures[sourceLen - len + i]), getErasedSignature(targetSignatures[targetLen - len + i])); + function inferFromProperties(source: Type, target: Type) { + const properties = getPropertiesOfObjectType(target); + for (const targetProp of properties) { + const sourceProp = getPropertyOfType(source, targetProp.escapedName); + if (sourceProp && !some(sourceProp.declarations, hasSkipDirectInferenceFlag)) { + inferFromTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); } } + } - function inferFromSignature(source: Signature, target: Signature) { - const saveBivariant = bivariant; - const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; - // Once we descend into a bivariant signature we remain bivariant for all nested inferences - bivariant = bivariant || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.Constructor; - applyToParameterTypes(source, target, inferFromContravariantTypesIfStrictFunctionTypes); - bivariant = saveBivariant; - applyToReturnTypes(source, target, inferFromTypes); + function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) { + const sourceSignatures = getSignaturesOfType(source, kind); + const targetSignatures = getSignaturesOfType(target, kind); + const sourceLen = sourceSignatures.length; + const targetLen = targetSignatures.length; + const len = sourceLen < targetLen ? sourceLen : targetLen; + for (let i = 0; i < len; i++) { + inferFromSignature(getBaseSignature(sourceSignatures[sourceLen - len + i]), getErasedSignature(targetSignatures[targetLen - len + i])); } + } - function inferFromIndexTypes(source: Type, target: Type) { - // Inferences across mapped type index signatures are pretty much the same a inferences to homomorphic variables - const priority = (getObjectFlags(source) & getObjectFlags(target) & ObjectFlags.Mapped) ? InferencePriority.HomomorphicMappedType : 0; - const indexInfos = getIndexInfosOfType(target); - if (isObjectTypeWithInferableIndex(source)) { - for (const targetInfo of indexInfos) { - const propTypes: Type[] = []; - for (const prop of getPropertiesOfType(source)) { - if (isApplicableIndexType(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), targetInfo.keyType)) { - const propType = getTypeOfSymbol(prop); - propTypes.push(prop.flags & SymbolFlags.Optional ? removeMissingOrUndefinedType(propType) : propType); - } - } - for (const info of getIndexInfosOfType(source)) { - if (isApplicableIndexType(info.keyType, targetInfo.keyType)) { - propTypes.push(info.type); - } + function inferFromSignature(source: Signature, target: Signature) { + const saveBivariant = bivariant; + const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; + // Once we descend into a bivariant signature we remain bivariant for all nested inferences + bivariant = bivariant || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.Constructor; + applyToParameterTypes(source, target, inferFromContravariantTypesIfStrictFunctionTypes); + bivariant = saveBivariant; + applyToReturnTypes(source, target, inferFromTypes); + } + + function inferFromIndexTypes(source: Type, target: Type) { + // Inferences across mapped type index signatures are pretty much the same a inferences to homomorphic variables + const priority = (getObjectFlags(source) & getObjectFlags(target) & ObjectFlags.Mapped) ? InferencePriority.HomomorphicMappedType : 0; + const indexInfos = getIndexInfosOfType(target); + if (isObjectTypeWithInferableIndex(source)) { + for (const targetInfo of indexInfos) { + const propTypes: Type[] = []; + for (const prop of getPropertiesOfType(source)) { + if (isApplicableIndexType(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), targetInfo.keyType)) { + const propType = getTypeOfSymbol(prop); + propTypes.push(prop.flags & SymbolFlags.Optional ? removeMissingOrUndefinedType(propType) : propType); } - if (propTypes.length) { - inferWithPriority(getUnionType(propTypes), targetInfo.type, priority); + } + for (const info of getIndexInfosOfType(source)) { + if (isApplicableIndexType(info.keyType, targetInfo.keyType)) { + propTypes.push(info.type); } } - } - for (const targetInfo of indexInfos) { - const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType); - if (sourceInfo) { - inferWithPriority(sourceInfo.type, targetInfo.type, priority); + if (propTypes.length) { + inferWithPriority(getUnionType(propTypes), targetInfo.type, priority); } } } + for (const targetInfo of indexInfos) { + const sourceInfo = getApplicableIndexInfo(source, targetInfo.keyType); + if (sourceInfo) { + inferWithPriority(sourceInfo.type, targetInfo.type, priority); + } + } } + } - function isTypeOrBaseIdenticalTo(s: Type, t: Type) { - return exactOptionalPropertyTypes && t === missingType ? s === t : - (isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral)); - } + function isTypeOrBaseIdenticalTo(s: Type, t: Type) { + return exactOptionalPropertyTypes && t === missingType ? s === t : + (isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral)); + } - function isTypeCloselyMatchedBy(s: Type, t: Type) { - return !!(s.flags & TypeFlags.Object && t.flags & TypeFlags.Object && s.symbol && s.symbol === t.symbol || - s.aliasSymbol && s.aliasTypeArguments && s.aliasSymbol === t.aliasSymbol); - } + function isTypeCloselyMatchedBy(s: Type, t: Type) { + return !!(s.flags & TypeFlags.Object && t.flags & TypeFlags.Object && s.symbol && s.symbol === t.symbol || + s.aliasSymbol && s.aliasTypeArguments && s.aliasSymbol === t.aliasSymbol); + } - function hasPrimitiveConstraint(type: TypeParameter): boolean { - const constraint = getConstraintOfTypeParameter(type); - return !!constraint && maybeTypeOfKind(constraint.flags & TypeFlags.Conditional ? getDefaultConstraintOfConditionalType(constraint as ConditionalType) : constraint, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping); - } + function hasPrimitiveConstraint(type: TypeParameter): boolean { + const constraint = getConstraintOfTypeParameter(type); + return !!constraint && maybeTypeOfKind(constraint.flags & TypeFlags.Conditional ? getDefaultConstraintOfConditionalType(constraint as ConditionalType) : constraint, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping); + } - function isObjectLiteralType(type: Type) { - return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral); - } + function isObjectLiteralType(type: Type) { + return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral); + } - function isObjectOrArrayLiteralType(type: Type) { - return !!(getObjectFlags(type) & (ObjectFlags.ObjectLiteral | ObjectFlags.ArrayLiteral)); - } + function isObjectOrArrayLiteralType(type: Type) { + return !!(getObjectFlags(type) & (ObjectFlags.ObjectLiteral | ObjectFlags.ArrayLiteral)); + } - function unionObjectAndArrayLiteralCandidates(candidates: Type[]): Type[] { - if (candidates.length > 1) { - const objectLiterals = filter(candidates, isObjectOrArrayLiteralType); - if (objectLiterals.length) { - const literalsType = getUnionType(objectLiterals, UnionReduction.Subtype); - return concatenate(filter(candidates, t => !isObjectOrArrayLiteralType(t)), [literalsType]); - } + function unionObjectAndArrayLiteralCandidates(candidates: Type[]): Type[] { + if (candidates.length > 1) { + const objectLiterals = filter(candidates, isObjectOrArrayLiteralType); + if (objectLiterals.length) { + const literalsType = getUnionType(objectLiterals, UnionReduction.Subtype); + return concatenate(filter(candidates, t => !isObjectOrArrayLiteralType(t)), [literalsType]); } - return candidates; } + return candidates; + } - function getContravariantInference(inference: InferenceInfo) { - return inference.priority! & InferencePriority.PriorityImpliesCombination ? getIntersectionType(inference.contraCandidates!) : getCommonSubtype(inference.contraCandidates!); - } + function getContravariantInference(inference: InferenceInfo) { + return inference.priority! & InferencePriority.PriorityImpliesCombination ? getIntersectionType(inference.contraCandidates!) : getCommonSubtype(inference.contraCandidates!); + } - function getCovariantInference(inference: InferenceInfo, signature: Signature) { - // 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 - // the type parameter was fixed during inference or does not occur at top-level in the return type. - const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter); - const widenLiteralTypes = !primitiveConstraint && inference.topLevel && - (inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter)); - const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) : - widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) : - candidates; - // If all inferences were made from a position that implies a combined result, infer a union type. - // Otherwise, infer a common supertype. - const unwidenedType = inference.priority! & InferencePriority.PriorityImpliesCombination ? - getUnionType(baseCandidates, UnionReduction.Subtype) : - getCommonSupertype(baseCandidates); - return getWidenedType(unwidenedType); - } + function getCovariantInference(inference: InferenceInfo, signature: Signature) { + // 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 + // the type parameter was fixed during inference or does not occur at top-level in the return type. + const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter); + const widenLiteralTypes = !primitiveConstraint && inference.topLevel && + (inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter)); + const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) : + widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) : + candidates; + // If all inferences were made from a position that implies a combined result, infer a union type. + // Otherwise, infer a common supertype. + const unwidenedType = inference.priority! & InferencePriority.PriorityImpliesCombination ? + getUnionType(baseCandidates, UnionReduction.Subtype) : + getCommonSupertype(baseCandidates); + return getWidenedType(unwidenedType); + } - function getInferredType(context: InferenceContext, index: number): Type { - const inference = context.inferences[index]; - if (!inference.inferredType) { - let inferredType: Type | undefined; - const signature = context.signature; - if (signature) { - const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined; - if (inference.contraCandidates) { - // If we have both co- and contra-variant inferences, we prefer the contra-variant inference - // unless the co-variant inference is a subtype of some contra-variant inference and not 'never'. - inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) && - some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) ? - inferredCovariantType : getContravariantInference(inference); - } - else if (inferredCovariantType) { - inferredType = inferredCovariantType; - } - else if (context.flags & InferenceFlags.NoDefault) { - // We use silentNeverType as the wildcard that signals no inferences. - inferredType = silentNeverType; - } - else { - // Infer either the default or the empty object type when no inferences were - // made. It is important to remember that in this case, inference still - // succeeds, meaning there is no error for not having inference candidates. An - // inference error only occurs when there are *conflicting* candidates, i.e. - // candidates with no common supertype. - const defaultType = getDefaultFromTypeParameter(inference.typeParameter); - if (defaultType) { - // Instantiate the default type. Any forward reference to a type - // parameter should be instantiated to the empty object type. - inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper)); - } - } + function getInferredType(context: InferenceContext, index: number): Type { + const inference = context.inferences[index]; + if (!inference.inferredType) { + let inferredType: Type | undefined; + const signature = context.signature; + if (signature) { + const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined; + if (inference.contraCandidates) { + // If we have both co- and contra-variant inferences, we prefer the contra-variant inference + // unless the co-variant inference is a subtype of some contra-variant inference and not 'never'. + inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) && + some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) ? + inferredCovariantType : getContravariantInference(inference); + } + else if (inferredCovariantType) { + inferredType = inferredCovariantType; + } + else if (context.flags & InferenceFlags.NoDefault) { + // We use silentNeverType as the wildcard that signals no inferences. + inferredType = silentNeverType; } else { - inferredType = getTypeFromInference(inference); - } - - inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault)); - - const constraint = getConstraintOfTypeParameter(inference.typeParameter); - if (constraint) { - const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); - if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { - inference.inferredType = inferredType = instantiatedConstraint; + // Infer either the default or the empty object type when no inferences were + // made. It is important to remember that in this case, inference still + // succeeds, meaning there is no error for not having inference candidates. An + // inference error only occurs when there are *conflicting* candidates, i.e. + // candidates with no common supertype. + const defaultType = getDefaultFromTypeParameter(inference.typeParameter); + if (defaultType) { + // Instantiate the default type. Any forward reference to a type + // parameter should be instantiated to the empty object type. + inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper)); } } } - - return inference.inferredType; - } - - function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type { - return isInJavaScriptFile ? anyType : unknownType; - } - - function getInferredTypes(context: InferenceContext): Type[] { - const result: Type[] = []; - for (let i = 0; i < context.inferences.length; i++) { - result.push(getInferredType(context, i)); - } - return result; - } - - // EXPRESSION TYPE CHECKING - - function getCannotFindNameDiagnosticForName(node: Identifier): DiagnosticMessage { - switch (node.escapedText) { - case "document": - case "console": - return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_include_dom; - case "$": - return compilerOptions.types - ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery_and_then_add_jquery_to_the_types_field_in_your_tsconfig - : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery; - case "describe": - case "suite": - case "it": - case "test": - return compilerOptions.types - ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha_and_then_add_jest_or_mocha_to_the_types_field_in_your_tsconfig - : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha; - case "process": - case "require": - case "Buffer": - case "module": - return compilerOptions.types - ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode_and_then_add_node_to_the_types_field_in_your_tsconfig - : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode; - case "Map": - case "Set": - case "Promise": - case "Symbol": - case "WeakMap": - case "WeakSet": - case "Iterator": - case "AsyncIterator": - case "SharedArrayBuffer": - case "Atomics": - case "AsyncIterable": - case "AsyncIterableIterator": - case "AsyncGenerator": - case "AsyncGeneratorFunction": - case "BigInt": - case "Reflect": - case "BigInt64Array": - case "BigUint64Array": - return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_1_or_later; - case "await": - if (isCallExpression(node.parent)) { - return Diagnostics.Cannot_find_name_0_Did_you_mean_to_write_this_in_an_async_function; - } - // falls through - default: - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - return Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer; - } - else { - return Diagnostics.Cannot_find_name_0; - } - } - } - - function getResolvedSymbol(node: Identifier): Symbol { - const links = getNodeLinks(node); - if (!links.resolvedSymbol) { - links.resolvedSymbol = !nodeIsMissing(node) && - resolveName( - node, - node.escapedText, - SymbolFlags.Value | SymbolFlags.ExportValue, - getCannotFindNameDiagnosticForName(node), - node, - !isWriteOnlyAccess(node), - /*excludeGlobals*/ false) || unknownSymbol; + else { + inferredType = getTypeFromInference(inference); } - return links.resolvedSymbol; - } - function isInTypeQuery(node: Node): boolean { - // TypeScript 1.0 spec (April 2014): 3.6.3 - // A type query consists of the keyword typeof followed by an expression. - // The expression is restricted to a single identifier or a sequence of identifiers separated by periods - return !!findAncestor( - node, - n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit"); - } + inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault)); - // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers - // separated by dots). The key consists of the id of the symbol referenced by the - // leftmost identifier followed by zero or more property names separated by dots. - // The result is undefined if the reference isn't a dotted name. - function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined { - switch (node.kind) { - case SyntaxKind.Identifier: - if (!isThisInTypeQuery(node)) { - const symbol = getResolvedSymbol(node as Identifier); - return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${getSymbolId(symbol)}` : undefined; - } - // falls through - case SyntaxKind.ThisKeyword: - return `0|${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}`; - case SyntaxKind.NonNullExpression: - case SyntaxKind.ParenthesizedExpression: - return getFlowCacheKey((node as NonNullExpression | ParenthesizedExpression).expression, declaredType, initialType, flowContainer); - case SyntaxKind.QualifiedName: - const left = getFlowCacheKey((node as QualifiedName).left, declaredType, initialType, flowContainer); - return left && left + "." + (node as QualifiedName).right.escapedText; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - const propName = getAccessedPropertyName(node as AccessExpression); - if (propName !== undefined) { - const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); - return key && key + "." + propName; - } - break; - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - // Handle pseudo-references originating in getNarrowedTypeOfSymbol. - return `${getNodeId(node)}#${getTypeId(declaredType)}`; + const constraint = getConstraintOfTypeParameter(inference.typeParameter); + if (constraint) { + const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); + if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { + inference.inferredType = inferredType = instantiatedConstraint; + } } - return undefined; } - function isMatchingReference(source: Node, target: Node): boolean { - switch (target.kind) { - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.NonNullExpression: - return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression); - case SyntaxKind.BinaryExpression: - return (isAssignmentExpression(target) && isMatchingReference(source, target.left)) || - (isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source, target.right)); - } - switch (source.kind) { - case SyntaxKind.MetaProperty: - return target.kind === SyntaxKind.MetaProperty - && (source as MetaProperty).keywordToken === (target as MetaProperty).keywordToken - && (source as MetaProperty).name.escapedText === (target as MetaProperty).name.escapedText; - case SyntaxKind.Identifier: - case SyntaxKind.PrivateIdentifier: - return isThisInTypeQuery(source) ? - target.kind === SyntaxKind.ThisKeyword : - target.kind === SyntaxKind.Identifier && getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier) || - (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && - getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target); - case SyntaxKind.ThisKeyword: - return target.kind === SyntaxKind.ThisKeyword; - case SyntaxKind.SuperKeyword: - return target.kind === SyntaxKind.SuperKeyword; - case SyntaxKind.NonNullExpression: - case SyntaxKind.ParenthesizedExpression: - return isMatchingReference((source as NonNullExpression | ParenthesizedExpression).expression, target); - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - const sourcePropertyName = getAccessedPropertyName(source as AccessExpression); - const targetPropertyName = isAccessExpression(target) ? getAccessedPropertyName(target) : undefined; - return sourcePropertyName !== undefined && targetPropertyName !== undefined && targetPropertyName === sourcePropertyName && - isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression); - case SyntaxKind.QualifiedName: - return isAccessExpression(target) && - (source as QualifiedName).right.escapedText === getAccessedPropertyName(target) && - isMatchingReference((source as QualifiedName).left, target.expression); - case SyntaxKind.BinaryExpression: - return (isBinaryExpression(source) && source.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source.right, target)); - } - return false; - } + return inference.inferredType; + } - function getAccessedPropertyName(access: AccessExpression | BindingElement | ParameterDeclaration): __String | undefined { - if (isPropertyAccessExpression(access)) { - return access.name.escapedText; - } - if (isElementAccessExpression(access)) { - return tryGetElementAccessExpressionName(access); - } - if (isBindingElement(access)) { - const name = getDestructuringPropertyName(access); - return name ? escapeLeadingUnderscores(name) : undefined; - } - if (isParameter(access)) { - return ("" + access.parent.parameters.indexOf(access)) as __String; - } - return undefined; - } + function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type { + return isInJavaScriptFile ? anyType : unknownType; + } - function tryGetNameFromType(type: Type) { - return type.flags & TypeFlags.UniqueESSymbol ? (type as UniqueESSymbolType).escapedName : - type.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value) : undefined; + function getInferredTypes(context: InferenceContext): Type[] { + const result: Type[] = []; + for (let i = 0; i < context.inferences.length; i++) { + result.push(getInferredType(context, i)); } + return result; + } - function tryGetElementAccessExpressionName(node: ElementAccessExpression) { - if (isStringOrNumericLiteralLike(node.argumentExpression)) { - return escapeLeadingUnderscores(node.argumentExpression.text); - } - if (isEntityNameExpression(node.argumentExpression)) { - const symbol = resolveEntityName(node.argumentExpression, SymbolFlags.Value, /*ignoreErrors*/ true); - if (!symbol || !(isConstVariable(symbol) || (symbol.flags & SymbolFlags.EnumMember))) return undefined; - - const declaration = symbol.valueDeclaration; - if (declaration === undefined) return undefined; - - const type = tryGetTypeFromEffectiveTypeNode(declaration); - if (type) { - const name = tryGetNameFromType(type); - if (name !== undefined) { - return name; - } + // EXPRESSION TYPE CHECKING + + function getCannotFindNameDiagnosticForName(node: Identifier): DiagnosticMessage { + switch (node.escapedText) { + case "document": + case "console": + return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_include_dom; + case "$": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery_and_then_add_jquery_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery; + case "describe": + case "suite": + case "it": + case "test": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha_and_then_add_jest_or_mocha_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha; + case "process": + case "require": + case "Buffer": + case "module": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode_and_then_add_node_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode; + case "Map": + case "Set": + case "Promise": + case "Symbol": + case "WeakMap": + case "WeakSet": + case "Iterator": + case "AsyncIterator": + case "SharedArrayBuffer": + case "Atomics": + case "AsyncIterable": + case "AsyncIterableIterator": + case "AsyncGenerator": + case "AsyncGeneratorFunction": + case "BigInt": + case "Reflect": + case "BigInt64Array": + case "BigUint64Array": + return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_1_or_later; + case "await": + if (isCallExpression(node.parent)) { + return Diagnostics.Cannot_find_name_0_Did_you_mean_to_write_this_in_an_async_function; + } + // falls through + default: + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + return Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer; } - - if (hasOnlyExpressionInitializer(declaration) && isBlockScopedNameDeclaredBeforeUse(declaration, node.argumentExpression)) { - const initializer = getEffectiveInitializer(declaration); - if (initializer) { - return tryGetNameFromType(getTypeOfExpression(initializer)); - } - if (isEnumMember(declaration)) { - return getTextOfPropertyName(declaration.name); - } + else { + return Diagnostics.Cannot_find_name_0; } - } - return undefined; } + } - function containsMatchingReference(source: Node, target: Node) { - while (isAccessExpression(source)) { - source = source.expression; - if (isMatchingReference(source, target)) { - return true; - } - } - return false; + function getResolvedSymbol(node: Identifier): Symbol { + const links = getNodeLinks(node); + if (!links.resolvedSymbol) { + links.resolvedSymbol = !nodeIsMissing(node) && + resolveName( + node, + node.escapedText, + SymbolFlags.Value | SymbolFlags.ExportValue, + getCannotFindNameDiagnosticForName(node), + node, + !isWriteOnlyAccess(node), + /*excludeGlobals*/ false) || unknownSymbol; } + return links.resolvedSymbol; + } - function optionalChainContainsReference(source: Node, target: Node) { - while (isOptionalChain(source)) { - source = source.expression; - if (isMatchingReference(source, target)) { - return true; - } - } - return false; - } + function isInTypeQuery(node: Node): boolean { + // TypeScript 1.0 spec (April 2014): 3.6.3 + // A type query consists of the keyword typeof followed by an expression. + // The expression is restricted to a single identifier or a sequence of identifiers separated by periods + return !!findAncestor( + node, + n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit"); + } - function isDiscriminantProperty(type: Type | undefined, name: __String) { - if (type && type.flags & TypeFlags.Union) { - const prop = getUnionOrIntersectionProperty(type as UnionType, name); - if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { - if ((prop as TransientSymbol).isDiscriminantProperty === undefined) { - (prop as TransientSymbol).isDiscriminantProperty = - ((prop as TransientSymbol).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && - !isGenericType(getTypeOfSymbol(prop)); - } - return !!(prop as TransientSymbol).isDiscriminantProperty; + // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers + // separated by dots). The key consists of the id of the symbol referenced by the + // leftmost identifier followed by zero or more property names separated by dots. + // The result is undefined if the reference isn't a dotted name. + function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined { + switch (node.kind) { + case SyntaxKind.Identifier: + if (!isThisInTypeQuery(node)) { + const symbol = getResolvedSymbol(node as Identifier); + return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${getSymbolId(symbol)}` : undefined; + } + // falls through + case SyntaxKind.ThisKeyword: + return `0|${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}`; + case SyntaxKind.NonNullExpression: + case SyntaxKind.ParenthesizedExpression: + return getFlowCacheKey((node as NonNullExpression | ParenthesizedExpression).expression, declaredType, initialType, flowContainer); + case SyntaxKind.QualifiedName: + const left = getFlowCacheKey((node as QualifiedName).left, declaredType, initialType, flowContainer); + return left && left + "." + (node as QualifiedName).right.escapedText; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const propName = getAccessedPropertyName(node as AccessExpression); + if (propName !== undefined) { + const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); + return key && key + "." + propName; } - } - return false; - } + break; + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + // Handle pseudo-references originating in getNarrowedTypeOfSymbol. + return `${getNodeId(node)}#${getTypeId(declaredType)}`; + } + return undefined; + } - function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined { - let result: Symbol[] | undefined; - for (const sourceProperty of sourceProperties) { - if (isDiscriminantProperty(target, sourceProperty.escapedName)) { - if (result) { - result.push(sourceProperty); - continue; - } - result = [sourceProperty]; - } - } - return result; - } + function isMatchingReference(source: Node, target: Node): boolean { + switch (target.kind) { + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.NonNullExpression: + return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression); + case SyntaxKind.BinaryExpression: + return (isAssignmentExpression(target) && isMatchingReference(source, target.left)) || + (isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source, target.right)); + } + switch (source.kind) { + case SyntaxKind.MetaProperty: + return target.kind === SyntaxKind.MetaProperty + && (source as MetaProperty).keywordToken === (target as MetaProperty).keywordToken + && (source as MetaProperty).name.escapedText === (target as MetaProperty).name.escapedText; + case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: + return isThisInTypeQuery(source) ? + target.kind === SyntaxKind.ThisKeyword : + target.kind === SyntaxKind.Identifier && getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier) || + (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && + getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target); + case SyntaxKind.ThisKeyword: + return target.kind === SyntaxKind.ThisKeyword; + case SyntaxKind.SuperKeyword: + return target.kind === SyntaxKind.SuperKeyword; + case SyntaxKind.NonNullExpression: + case SyntaxKind.ParenthesizedExpression: + return isMatchingReference((source as NonNullExpression | ParenthesizedExpression).expression, target); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const sourcePropertyName = getAccessedPropertyName(source as AccessExpression); + const targetPropertyName = isAccessExpression(target) ? getAccessedPropertyName(target) : undefined; + return sourcePropertyName !== undefined && targetPropertyName !== undefined && targetPropertyName === sourcePropertyName && + isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression); + case SyntaxKind.QualifiedName: + return isAccessExpression(target) && + (source as QualifiedName).right.escapedText === getAccessedPropertyName(target) && + isMatchingReference((source as QualifiedName).left, target.expression); + case SyntaxKind.BinaryExpression: + return (isBinaryExpression(source) && source.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source.right, target)); + } + return false; + } - // Given a set of constituent types and a property name, create and return a map keyed by the literal - // types of the property by that name in each constituent type. No map is returned if some key property - // has a non-literal type or if less than 10 or less than 50% of the constituents have a unique key. - // Entries with duplicate keys have unknownType as the value. - function mapTypesByKeyProperty(types: Type[], name: __String) { - const map = new Map(); - let count = 0; - for (const type of types) { - if (type.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) { - const discriminant = getTypeOfPropertyOfType(type, name); - if (discriminant) { - if (!isLiteralType(discriminant)) { - return undefined; - } - let duplicate = false; - forEachType(discriminant, t => { - const id = getTypeId(getRegularTypeOfLiteralType(t)); - const existing = map.get(id); - if (!existing) { - map.set(id, type); - } - else if (existing !== unknownType) { - map.set(id, unknownType); - duplicate = true; - } - }); - if (!duplicate) count++; - } - } - } - return count >= 10 && count * 2 >= types.length ? map : undefined; + function getAccessedPropertyName(access: AccessExpression | BindingElement | ParameterDeclaration): __String | undefined { + if (isPropertyAccessExpression(access)) { + return access.name.escapedText; + } + if (isElementAccessExpression(access)) { + return tryGetElementAccessExpressionName(access); } - - // Return the name of a discriminant property for which it was possible and feasible to construct a map of - // constituent types keyed by the literal types of the property by that name in each constituent type. - function getKeyPropertyName(unionType: UnionType): __String | undefined { - const types = unionType.types; - // We only construct maps for unions with many non-primitive constituents. - if (types.length < 10 || getObjectFlags(unionType) & ObjectFlags.PrimitiveUnion || - countWhere(types, t => !!(t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive))) < 10) { - return undefined; - } - if (unionType.keyPropertyName === undefined) { - // The candidate key property name is the name of the first property with a unit type in one of the - // constituent types. - const keyPropertyName = forEach(types, t => - t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) ? - forEach(getPropertiesOfType(t), p => isUnitType(getTypeOfSymbol(p)) ? p.escapedName : undefined) : - undefined); - const mapByKeyProperty = keyPropertyName && mapTypesByKeyProperty(types, keyPropertyName); - unionType.keyPropertyName = mapByKeyProperty ? keyPropertyName : "" as __String; - unionType.constituentMap = mapByKeyProperty; - } - return (unionType.keyPropertyName as string).length ? unionType.keyPropertyName : undefined; + if (isBindingElement(access)) { + const name = getDestructuringPropertyName(access); + return name ? escapeLeadingUnderscores(name) : undefined; } - - // Given a union type for which getKeyPropertyName returned a non-undefined result, return the constituent - // that corresponds to the given key type for that property name. - function getConstituentTypeForKeyType(unionType: UnionType, keyType: Type) { - const result = unionType.constituentMap?.get(getTypeId(getRegularTypeOfLiteralType(keyType))); - return result !== unknownType ? result : undefined; + if (isParameter(access)) { + return ("" + access.parent.parameters.indexOf(access)) as __String; } + return undefined; + } - function getMatchingUnionConstituentForType(unionType: UnionType, type: Type) { - const keyPropertyName = getKeyPropertyName(unionType); - const propType = keyPropertyName && getTypeOfPropertyOfType(type, keyPropertyName); - return propType && getConstituentTypeForKeyType(unionType, propType); - } + function tryGetNameFromType(type: Type) { + return type.flags & TypeFlags.UniqueESSymbol ? (type as UniqueESSymbolType).escapedName : + type.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value) : undefined; + } - function getMatchingUnionConstituentForObjectLiteral(unionType: UnionType, node: ObjectLiteralExpression) { - const keyPropertyName = getKeyPropertyName(unionType); - const propNode = keyPropertyName && find(node.properties, p => p.symbol && p.kind === SyntaxKind.PropertyAssignment && - p.symbol.escapedName === keyPropertyName && isPossiblyDiscriminantValue(p.initializer)); - const propType = propNode && getContextFreeTypeOfExpression((propNode as PropertyAssignment).initializer); - return propType && getConstituentTypeForKeyType(unionType, propType); + function tryGetElementAccessExpressionName(node: ElementAccessExpression) { + if (isStringOrNumericLiteralLike(node.argumentExpression)) { + return escapeLeadingUnderscores(node.argumentExpression.text); } + if (isEntityNameExpression(node.argumentExpression)) { + const symbol = resolveEntityName(node.argumentExpression, SymbolFlags.Value, /*ignoreErrors*/ true); + if (!symbol || !(isConstVariable(symbol) || (symbol.flags & SymbolFlags.EnumMember))) return undefined; - function isOrContainsMatchingReference(source: Node, target: Node) { - return isMatchingReference(source, target) || containsMatchingReference(source, target); - } + const declaration = symbol.valueDeclaration; + if (declaration === undefined) return undefined; - function hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) { - if (expression.arguments) { - for (const argument of expression.arguments) { - if (isOrContainsMatchingReference(reference, argument)) { - return true; - } + const type = tryGetTypeFromEffectiveTypeNode(declaration); + if (type) { + const name = tryGetNameFromType(type); + if (name !== undefined) { + return name; } } - if (expression.expression.kind === SyntaxKind.PropertyAccessExpression && - isOrContainsMatchingReference(reference, (expression.expression as PropertyAccessExpression).expression)) { - return true; + + if (hasOnlyExpressionInitializer(declaration) && isBlockScopedNameDeclaredBeforeUse(declaration, node.argumentExpression)) { + const initializer = getEffectiveInitializer(declaration); + if (initializer) { + return tryGetNameFromType(getTypeOfExpression(initializer)); + } + if (isEnumMember(declaration)) { + return getTextOfPropertyName(declaration.name); + } } - return false; } + return undefined; + } - function getFlowNodeId(flow: FlowNode): number { - if (!flow.id || flow.id < 0) { - flow.id = nextFlowId; - nextFlowId++; + function containsMatchingReference(source: Node, target: Node) { + while (isAccessExpression(source)) { + source = source.expression; + if (isMatchingReference(source, target)) { + return true; } - return flow.id; } + return false; + } - function typeMaybeAssignableTo(source: Type, target: Type) { - if (!(source.flags & TypeFlags.Union)) { - return isTypeAssignableTo(source, target); + function optionalChainContainsReference(source: Node, target: Node) { + while (isOptionalChain(source)) { + source = source.expression; + if (isMatchingReference(source, target)) { + return true; } - for (const t of (source as UnionType).types) { - if (isTypeAssignableTo(t, target)) { - return true; + } + return false; + } + + function isDiscriminantProperty(type: Type | undefined, name: __String) { + if (type && type.flags & TypeFlags.Union) { + const prop = getUnionOrIntersectionProperty(type as UnionType, name); + if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { + if ((prop as TransientSymbol).isDiscriminantProperty === undefined) { + (prop as TransientSymbol).isDiscriminantProperty = + ((prop as TransientSymbol).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && + !isGenericType(getTypeOfSymbol(prop)); } + return !!(prop as TransientSymbol).isDiscriminantProperty; } - return false; } + return false; + } - // Remove those constituent types of declaredType to which no constituent type of assignedType is assignable. - // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, - // we remove type string. - function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) { - if (declaredType === assignedType) { - return declaredType; - } - if (assignedType.flags & TypeFlags.Never) { - return assignedType; + function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined { + let result: Symbol[] | undefined; + for (const sourceProperty of sourceProperties) { + if (isDiscriminantProperty(target, sourceProperty.escapedName)) { + if (result) { + result.push(sourceProperty); + continue; + } + result = [sourceProperty]; } - const key = `A${getTypeId(declaredType)},${getTypeId(assignedType)}`; - return getCachedType(key) ?? setCachedType(key, getAssignmentReducedTypeWorker(declaredType, assignedType)); } + return result; + } - function getAssignmentReducedTypeWorker(declaredType: UnionType, assignedType: Type) { - const filteredType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); - // Ensure that we narrow to fresh types if the assignment is a fresh boolean literal type. - const reducedType = assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType) ? mapType(filteredType, getFreshTypeOfLiteralType) : filteredType; - // Our crude heuristic produces an invalid result in some cases: see GH#26130. - // For now, when that happens, we give up and don't narrow at all. (This also - // means we'll never narrow for erroneous assignments where the assigned type - // is not assignable to the declared type.) - return isTypeAssignableTo(assignedType, reducedType) ? reducedType : declaredType; + // Given a set of constituent types and a property name, create and return a map keyed by the literal + // types of the property by that name in each constituent type. No map is returned if some key property + // has a non-literal type or if less than 10 or less than 50% of the constituents have a unique key. + // Entries with duplicate keys have unknownType as the value. + function mapTypesByKeyProperty(types: Type[], name: __String) { + const map = new Map(); + let count = 0; + for (const type of types) { + if (type.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.InstantiableNonPrimitive)) { + const discriminant = getTypeOfPropertyOfType(type, name); + if (discriminant) { + if (!isLiteralType(discriminant)) { + return undefined; + } + let duplicate = false; + forEachType(discriminant, t => { + const id = getTypeId(getRegularTypeOfLiteralType(t)); + const existing = map.get(id); + if (!existing) { + map.set(id, type); + } + else if (existing !== unknownType) { + map.set(id, unknownType); + duplicate = true; + } + }); + if (!duplicate) count++; + } + } } + return count >= 10 && count * 2 >= types.length ? map : undefined; + } - function isFunctionObjectType(type: ObjectType): boolean { - // We do a quick check for a "bind" property before performing the more expensive subtype - // check. This gives us a quicker out in the common case where an object type is not a function. - const resolved = resolveStructuredTypeMembers(type); - return !!(resolved.callSignatures.length || resolved.constructSignatures.length || - resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType)); + // Return the name of a discriminant property for which it was possible and feasible to construct a map of + // constituent types keyed by the literal types of the property by that name in each constituent type. + function getKeyPropertyName(unionType: UnionType): __String | undefined { + const types = unionType.types; + // We only construct maps for unions with many non-primitive constituents. + if (types.length < 10 || getObjectFlags(unionType) & ObjectFlags.PrimitiveUnion || + countWhere(types, t => !!(t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive))) < 10) { + return undefined; } + if (unionType.keyPropertyName === undefined) { + // The candidate key property name is the name of the first property with a unit type in one of the + // constituent types. + const keyPropertyName = forEach(types, t => + t.flags & (TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) ? + forEach(getPropertiesOfType(t), p => isUnitType(getTypeOfSymbol(p)) ? p.escapedName : undefined) : + undefined); + const mapByKeyProperty = keyPropertyName && mapTypesByKeyProperty(types, keyPropertyName); + unionType.keyPropertyName = mapByKeyProperty ? keyPropertyName : "" as __String; + unionType.constituentMap = mapByKeyProperty; + } + return (unionType.keyPropertyName as string).length ? unionType.keyPropertyName : undefined; + } - function getTypeFacts(type: Type): TypeFacts { - if (type.flags & (TypeFlags.Intersection | TypeFlags.Instantiable)) { - type = getBaseConstraintOfType(type) || unknownType; - } - const flags = type.flags; - if (flags & (TypeFlags.String | TypeFlags.StringMapping)) { - return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts; - } - if (flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral)) { - const isEmpty = flags & TypeFlags.StringLiteral && (type as StringLiteralType).value === ""; - return strictNullChecks ? - isEmpty ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts : - isEmpty ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts; - } - if (flags & (TypeFlags.Number | TypeFlags.Enum)) { - return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts; - } - if (flags & TypeFlags.NumberLiteral) { - const isZero = (type as NumberLiteralType).value === 0; - return strictNullChecks ? - isZero ? TypeFacts.ZeroNumberStrictFacts : TypeFacts.NonZeroNumberStrictFacts : - isZero ? TypeFacts.ZeroNumberFacts : TypeFacts.NonZeroNumberFacts; - } - if (flags & TypeFlags.BigInt) { - return strictNullChecks ? TypeFacts.BigIntStrictFacts : TypeFacts.BigIntFacts; - } - if (flags & TypeFlags.BigIntLiteral) { - const isZero = isZeroBigInt(type as BigIntLiteralType); - return strictNullChecks ? - isZero ? TypeFacts.ZeroBigIntStrictFacts : TypeFacts.NonZeroBigIntStrictFacts : - isZero ? TypeFacts.ZeroBigIntFacts : TypeFacts.NonZeroBigIntFacts; - } - if (flags & TypeFlags.Boolean) { - return strictNullChecks ? TypeFacts.BooleanStrictFacts : TypeFacts.BooleanFacts; - } - if (flags & TypeFlags.BooleanLike) { - return strictNullChecks ? - (type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts : - (type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; - } - if (flags & TypeFlags.Object) { - return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ? - strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : - isFunctionObjectType(type as ObjectType) ? - strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts : - strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; - } - if (flags & TypeFlags.Void) { - return TypeFacts.VoidFacts; - } - if (flags & TypeFlags.Undefined) { - return TypeFacts.UndefinedFacts; - } - if (flags & TypeFlags.Null) { - return TypeFacts.NullFacts; - } - if (flags & TypeFlags.ESSymbolLike) { - return strictNullChecks ? TypeFacts.SymbolStrictFacts : TypeFacts.SymbolFacts; - } - if (flags & TypeFlags.NonPrimitive) { - return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; - } - if (flags & TypeFlags.Never) { - return TypeFacts.None; - } - if (flags & TypeFlags.Union) { - return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFacts(t), TypeFacts.None); - } - if (flags & TypeFlags.Intersection) { - return getIntersectionTypeFacts(type as IntersectionType); - } - return TypeFacts.UnknownFacts; - } + // Given a union type for which getKeyPropertyName returned a non-undefined result, return the constituent + // that corresponds to the given key type for that property name. + function getConstituentTypeForKeyType(unionType: UnionType, keyType: Type) { + const result = unionType.constituentMap?.get(getTypeId(getRegularTypeOfLiteralType(keyType))); + return result !== unknownType ? result : undefined; + } + + function getMatchingUnionConstituentForType(unionType: UnionType, type: Type) { + const keyPropertyName = getKeyPropertyName(unionType); + const propType = keyPropertyName && getTypeOfPropertyOfType(type, keyPropertyName); + return propType && getConstituentTypeForKeyType(unionType, propType); + } + + function getMatchingUnionConstituentForObjectLiteral(unionType: UnionType, node: ObjectLiteralExpression) { + const keyPropertyName = getKeyPropertyName(unionType); + const propNode = keyPropertyName && find(node.properties, p => p.symbol && p.kind === SyntaxKind.PropertyAssignment && + p.symbol.escapedName === keyPropertyName && isPossiblyDiscriminantValue(p.initializer)); + const propType = propNode && getContextFreeTypeOfExpression((propNode as PropertyAssignment).initializer); + return propType && getConstituentTypeForKeyType(unionType, propType); + } + + function isOrContainsMatchingReference(source: Node, target: Node) { + return isMatchingReference(source, target) || containsMatchingReference(source, target); + } - function getIntersectionTypeFacts(type: IntersectionType): TypeFacts { - // When an intersection contains a primitive type we ignore object type constituents as they are - // presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type. - const ignoreObjects = maybeTypeOfKind(type, TypeFlags.Primitive); - // When computing the type facts of an intersection type, certain type facts are computed as `and` - // and others are computed as `or`. - let oredFacts = TypeFacts.None; - let andedFacts = TypeFacts.All; - for (const t of type.types) { - if (!(ignoreObjects && t.flags & TypeFlags.Object)) { - const f = getTypeFacts(t); - oredFacts |= f; - andedFacts &= f; + function hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) { + if (expression.arguments) { + for (const argument of expression.arguments) { + if (isOrContainsMatchingReference(reference, argument)) { + return true; } } - return oredFacts & TypeFacts.OrFactsMask | andedFacts & TypeFacts.AndFactsMask; } + if (expression.expression.kind === SyntaxKind.PropertyAccessExpression && + isOrContainsMatchingReference(reference, (expression.expression as PropertyAccessExpression).expression)) { + return true; + } + return false; + } - function getTypeWithFacts(type: Type, include: TypeFacts) { - return filterType(type, t => (getTypeFacts(t) & include) !== 0); + function getFlowNodeId(flow: FlowNode): number { + if (!flow.id || flow.id < 0) { + flow.id = nextFlowId; + nextFlowId++; } + return flow.id; + } - // This function is similar to getTypeWithFacts, except that in strictNullChecks mode it replaces type - // unknown with the union {} | null | undefined (and reduces that accordingly), and it intersects remaining - // instantiable types with {}, {} | null, or {} | undefined in order to remove null and/or undefined. - function getAdjustedTypeWithFacts(type: Type, facts: TypeFacts) { - const reduced = recombineUnknownType(getTypeWithFacts(strictNullChecks && type.flags & TypeFlags.Unknown ? unknownUnionType : type, facts)); - if (strictNullChecks) { - switch (facts) { - case TypeFacts.NEUndefined: - return mapType(reduced, t => getTypeFacts(t) & TypeFacts.EQUndefined ? getIntersectionType([t, getTypeFacts(t) & TypeFacts.EQNull && !maybeTypeOfKind(reduced, TypeFlags.Null) ? getUnionType([emptyObjectType, nullType]) : emptyObjectType]): t); - case TypeFacts.NENull: - return mapType(reduced, t => getTypeFacts(t) & TypeFacts.EQNull ? getIntersectionType([t, getTypeFacts(t) & TypeFacts.EQUndefined && !maybeTypeOfKind(reduced, TypeFlags.Undefined) ? getUnionType([emptyObjectType, undefinedType]) : emptyObjectType]): t); - case TypeFacts.NEUndefinedOrNull: - case TypeFacts.Truthy: - return mapType(reduced, t => getTypeFacts(t) & TypeFacts.EQUndefinedOrNull ? getGlobalNonNullableTypeInstantiation(t): t); - } + function typeMaybeAssignableTo(source: Type, target: Type) { + if (!(source.flags & TypeFlags.Union)) { + return isTypeAssignableTo(source, target); + } + for (const t of (source as UnionType).types) { + if (isTypeAssignableTo(t, target)) { + return true; } - return reduced; } + return false; + } - function recombineUnknownType(type: Type) { - return type === unknownUnionType ? unknownType : type; + // Remove those constituent types of declaredType to which no constituent type of assignedType is assignable. + // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, + // we remove type string. + function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) { + if (declaredType === assignedType) { + return declaredType; } - - function getTypeWithDefault(type: Type, defaultExpression: Expression) { - return defaultExpression ? - getUnionType([getNonUndefinedType(type), getTypeOfExpression(defaultExpression)]) : - type; + if (assignedType.flags & TypeFlags.Never) { + return assignedType; } + const key = `A${getTypeId(declaredType)},${getTypeId(assignedType)}`; + return getCachedType(key) ?? setCachedType(key, getAssignmentReducedTypeWorker(declaredType, assignedType)); + } - function getTypeOfDestructuredProperty(type: Type, name: PropertyName) { - const nameType = getLiteralTypeFromPropertyName(name); - if (!isTypeUsableAsPropertyName(nameType)) return errorType; - const text = getPropertyNameFromType(nameType); - return getTypeOfPropertyOfType(type, text) || includeUndefinedInIndexSignature(getApplicableIndexInfoForName(type, text)?.type) || errorType; - } + function getAssignmentReducedTypeWorker(declaredType: UnionType, assignedType: Type) { + const filteredType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); + // Ensure that we narrow to fresh types if the assignment is a fresh boolean literal type. + const reducedType = assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType) ? mapType(filteredType, getFreshTypeOfLiteralType) : filteredType; + // Our crude heuristic produces an invalid result in some cases: see GH#26130. + // For now, when that happens, we give up and don't narrow at all. (This also + // means we'll never narrow for erroneous assignments where the assigned type + // is not assignable to the declared type.) + return isTypeAssignableTo(assignedType, reducedType) ? reducedType : declaredType; + } - function getTypeOfDestructuredArrayElement(type: Type, index: number) { - return everyType(type, isTupleLikeType) && getTupleElementType(type, index) || - includeUndefinedInIndexSignature(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined)) || - errorType; - } + function isFunctionObjectType(type: ObjectType): boolean { + // We do a quick check for a "bind" property before performing the more expensive subtype + // check. This gives us a quicker out in the common case where an object type is not a function. + const resolved = resolveStructuredTypeMembers(type); + return !!(resolved.callSignatures.length || resolved.constructSignatures.length || + resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType)); + } - function includeUndefinedInIndexSignature(type: Type | undefined): Type | undefined { - if (!type) return type; - return compilerOptions.noUncheckedIndexedAccess ? - getUnionType([type, undefinedType]) : - type; + function getTypeFacts(type: Type): TypeFacts { + if (type.flags & (TypeFlags.Intersection | TypeFlags.Instantiable)) { + type = getBaseConstraintOfType(type) || unknownType; } - - function getTypeOfDestructuredSpreadExpression(type: Type) { - return createArrayType(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || errorType); + const flags = type.flags; + if (flags & (TypeFlags.String | TypeFlags.StringMapping)) { + return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts; } - - function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type { - const isDestructuringDefaultAssignment = - node.parent.kind === SyntaxKind.ArrayLiteralExpression && isDestructuringAssignmentTarget(node.parent) || - node.parent.kind === SyntaxKind.PropertyAssignment && isDestructuringAssignmentTarget(node.parent.parent); - return isDestructuringDefaultAssignment ? - getTypeWithDefault(getAssignedType(node), node.right) : - getTypeOfExpression(node.right); + if (flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral)) { + const isEmpty = flags & TypeFlags.StringLiteral && (type as StringLiteralType).value === ""; + return strictNullChecks ? + isEmpty ? TypeFacts.EmptyStringStrictFacts : TypeFacts.NonEmptyStringStrictFacts : + isEmpty ? TypeFacts.EmptyStringFacts : TypeFacts.NonEmptyStringFacts; } - - function isDestructuringAssignmentTarget(parent: Node) { - return parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent as BinaryExpression).left === parent || - parent.parent.kind === SyntaxKind.ForOfStatement && (parent.parent as ForOfStatement).initializer === parent; + if (flags & (TypeFlags.Number | TypeFlags.Enum)) { + return strictNullChecks ? TypeFacts.NumberStrictFacts : TypeFacts.NumberFacts; } - - function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): Type { - return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element)); + if (flags & TypeFlags.NumberLiteral) { + const isZero = (type as NumberLiteralType).value === 0; + return strictNullChecks ? + isZero ? TypeFacts.ZeroNumberStrictFacts : TypeFacts.NonZeroNumberStrictFacts : + isZero ? TypeFacts.ZeroNumberFacts : TypeFacts.NonZeroNumberFacts; } - - function getAssignedTypeOfSpreadExpression(node: SpreadElement): Type { - return getTypeOfDestructuredSpreadExpression(getAssignedType(node.parent as ArrayLiteralExpression)); + if (flags & TypeFlags.BigInt) { + return strictNullChecks ? TypeFacts.BigIntStrictFacts : TypeFacts.BigIntFacts; } - - function getAssignedTypeOfPropertyAssignment(node: PropertyAssignment | ShorthandPropertyAssignment): Type { - return getTypeOfDestructuredProperty(getAssignedType(node.parent), node.name); + if (flags & TypeFlags.BigIntLiteral) { + const isZero = isZeroBigInt(type as BigIntLiteralType); + return strictNullChecks ? + isZero ? TypeFacts.ZeroBigIntStrictFacts : TypeFacts.NonZeroBigIntStrictFacts : + isZero ? TypeFacts.ZeroBigIntFacts : TypeFacts.NonZeroBigIntFacts; } - - function getAssignedTypeOfShorthandPropertyAssignment(node: ShorthandPropertyAssignment): Type { - return getTypeWithDefault(getAssignedTypeOfPropertyAssignment(node), node.objectAssignmentInitializer!); + if (flags & TypeFlags.Boolean) { + return strictNullChecks ? TypeFacts.BooleanStrictFacts : TypeFacts.BooleanFacts; } - - function getAssignedType(node: Expression): Type { - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.ForInStatement: - return stringType; - case SyntaxKind.ForOfStatement: - return checkRightHandSideOfForOf(parent as ForOfStatement) || errorType; - case SyntaxKind.BinaryExpression: - return getAssignedTypeOfBinaryExpression(parent as BinaryExpression); - case SyntaxKind.DeleteExpression: - return undefinedType; - case SyntaxKind.ArrayLiteralExpression: - return getAssignedTypeOfArrayLiteralElement(parent as ArrayLiteralExpression, node); - case SyntaxKind.SpreadElement: - return getAssignedTypeOfSpreadExpression(parent as SpreadElement); - case SyntaxKind.PropertyAssignment: - return getAssignedTypeOfPropertyAssignment(parent as PropertyAssignment); - case SyntaxKind.ShorthandPropertyAssignment: - return getAssignedTypeOfShorthandPropertyAssignment(parent as ShorthandPropertyAssignment); - } - return errorType; + if (flags & TypeFlags.BooleanLike) { + return strictNullChecks ? + (type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts : + (type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; } - - function getInitialTypeOfBindingElement(node: BindingElement): Type { - const pattern = node.parent; - const parentType = getInitialType(pattern.parent as VariableDeclaration | BindingElement); - const type = pattern.kind === SyntaxKind.ObjectBindingPattern ? - getTypeOfDestructuredProperty(parentType, node.propertyName || node.name as Identifier) : - !node.dotDotDotToken ? - getTypeOfDestructuredArrayElement(parentType, pattern.elements.indexOf(node)) : - getTypeOfDestructuredSpreadExpression(parentType); - return getTypeWithDefault(type, node.initializer!); + if (flags & TypeFlags.Object) { + return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ? + strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : + isFunctionObjectType(type as ObjectType) ? + strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts : + strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } - - function getTypeOfInitializer(node: Expression) { - // Return the cached type if one is available. If the type of the variable was inferred - // from its initializer, we'll already have cached the type. Otherwise we compute it now - // without caching such that transient types are reflected. - const links = getNodeLinks(node); - return links.resolvedType || getTypeOfExpression(node); + if (flags & TypeFlags.Void) { + return TypeFacts.VoidFacts; + } + if (flags & TypeFlags.Undefined) { + return TypeFacts.UndefinedFacts; + } + if (flags & TypeFlags.Null) { + return TypeFacts.NullFacts; + } + if (flags & TypeFlags.ESSymbolLike) { + return strictNullChecks ? TypeFacts.SymbolStrictFacts : TypeFacts.SymbolFacts; } + if (flags & TypeFlags.NonPrimitive) { + return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; + } + if (flags & TypeFlags.Never) { + return TypeFacts.None; + } + if (flags & TypeFlags.Union) { + return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFacts(t), TypeFacts.None); + } + if (flags & TypeFlags.Intersection) { + return getIntersectionTypeFacts(type as IntersectionType); + } + return TypeFacts.UnknownFacts; + } - function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { - if (node.initializer) { - return getTypeOfInitializer(node.initializer); - } - if (node.parent.parent.kind === SyntaxKind.ForInStatement) { + function getIntersectionTypeFacts(type: IntersectionType): TypeFacts { + // When an intersection contains a primitive type we ignore object type constituents as they are + // presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type. + const ignoreObjects = maybeTypeOfKind(type, TypeFlags.Primitive); + // When computing the type facts of an intersection type, certain type facts are computed as `and` + // and others are computed as `or`. + let oredFacts = TypeFacts.None; + let andedFacts = TypeFacts.All; + for (const t of type.types) { + if (!(ignoreObjects && t.flags & TypeFlags.Object)) { + const f = getTypeFacts(t); + oredFacts |= f; + andedFacts &= f; + } + } + return oredFacts & TypeFacts.OrFactsMask | andedFacts & TypeFacts.AndFactsMask; + } + + function getTypeWithFacts(type: Type, include: TypeFacts) { + return filterType(type, t => (getTypeFacts(t) & include) !== 0); + } + + // This function is similar to getTypeWithFacts, except that in strictNullChecks mode it replaces type + // unknown with the union {} | null | undefined (and reduces that accordingly), and it intersects remaining + // instantiable types with {}, {} | null, or {} | undefined in order to remove null and/or undefined. + function getAdjustedTypeWithFacts(type: Type, facts: TypeFacts) { + const reduced = recombineUnknownType(getTypeWithFacts(strictNullChecks && type.flags & TypeFlags.Unknown ? unknownUnionType : type, facts)); + if (strictNullChecks) { + switch (facts) { + case TypeFacts.NEUndefined: + return mapType(reduced, t => getTypeFacts(t) & TypeFacts.EQUndefined ? getIntersectionType([t, getTypeFacts(t) & TypeFacts.EQNull && !maybeTypeOfKind(reduced, TypeFlags.Null) ? getUnionType([emptyObjectType, nullType]) : emptyObjectType]): t); + case TypeFacts.NENull: + return mapType(reduced, t => getTypeFacts(t) & TypeFacts.EQNull ? getIntersectionType([t, getTypeFacts(t) & TypeFacts.EQUndefined && !maybeTypeOfKind(reduced, TypeFlags.Undefined) ? getUnionType([emptyObjectType, undefinedType]) : emptyObjectType]): t); + case TypeFacts.NEUndefinedOrNull: + case TypeFacts.Truthy: + return mapType(reduced, t => getTypeFacts(t) & TypeFacts.EQUndefinedOrNull ? getGlobalNonNullableTypeInstantiation(t): t); + } + } + return reduced; + } + + function recombineUnknownType(type: Type) { + return type === unknownUnionType ? unknownType : type; + } + + function getTypeWithDefault(type: Type, defaultExpression: Expression) { + return defaultExpression ? + getUnionType([getNonUndefinedType(type), getTypeOfExpression(defaultExpression)]) : + type; + } + + function getTypeOfDestructuredProperty(type: Type, name: PropertyName) { + const nameType = getLiteralTypeFromPropertyName(name); + if (!isTypeUsableAsPropertyName(nameType)) return errorType; + const text = getPropertyNameFromType(nameType); + return getTypeOfPropertyOfType(type, text) || includeUndefinedInIndexSignature(getApplicableIndexInfoForName(type, text)?.type) || errorType; + } + + function getTypeOfDestructuredArrayElement(type: Type, index: number) { + return everyType(type, isTupleLikeType) && getTupleElementType(type, index) || + includeUndefinedInIndexSignature(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined)) || + errorType; + } + + function includeUndefinedInIndexSignature(type: Type | undefined): Type | undefined { + if (!type) return type; + return compilerOptions.noUncheckedIndexedAccess ? + getUnionType([type, undefinedType]) : + type; + } + + function getTypeOfDestructuredSpreadExpression(type: Type) { + return createArrayType(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || errorType); + } + + function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type { + const isDestructuringDefaultAssignment = + node.parent.kind === SyntaxKind.ArrayLiteralExpression && isDestructuringAssignmentTarget(node.parent) || + node.parent.kind === SyntaxKind.PropertyAssignment && isDestructuringAssignmentTarget(node.parent.parent); + return isDestructuringDefaultAssignment ? + getTypeWithDefault(getAssignedType(node), node.right) : + getTypeOfExpression(node.right); + } + + function isDestructuringAssignmentTarget(parent: Node) { + return parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent as BinaryExpression).left === parent || + parent.parent.kind === SyntaxKind.ForOfStatement && (parent.parent as ForOfStatement).initializer === parent; + } + + function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): Type { + return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element)); + } + + function getAssignedTypeOfSpreadExpression(node: SpreadElement): Type { + return getTypeOfDestructuredSpreadExpression(getAssignedType(node.parent as ArrayLiteralExpression)); + } + + function getAssignedTypeOfPropertyAssignment(node: PropertyAssignment | ShorthandPropertyAssignment): Type { + return getTypeOfDestructuredProperty(getAssignedType(node.parent), node.name); + } + + function getAssignedTypeOfShorthandPropertyAssignment(node: ShorthandPropertyAssignment): Type { + return getTypeWithDefault(getAssignedTypeOfPropertyAssignment(node), node.objectAssignmentInitializer!); + } + + function getAssignedType(node: Expression): Type { + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.ForInStatement: return stringType; - } - if (node.parent.parent.kind === SyntaxKind.ForOfStatement) { - return checkRightHandSideOfForOf(node.parent.parent) || errorType; - } - return errorType; - } + case SyntaxKind.ForOfStatement: + return checkRightHandSideOfForOf(parent as ForOfStatement) || errorType; + case SyntaxKind.BinaryExpression: + return getAssignedTypeOfBinaryExpression(parent as BinaryExpression); + case SyntaxKind.DeleteExpression: + return undefinedType; + case SyntaxKind.ArrayLiteralExpression: + return getAssignedTypeOfArrayLiteralElement(parent as ArrayLiteralExpression, node); + case SyntaxKind.SpreadElement: + return getAssignedTypeOfSpreadExpression(parent as SpreadElement); + case SyntaxKind.PropertyAssignment: + return getAssignedTypeOfPropertyAssignment(parent as PropertyAssignment); + case SyntaxKind.ShorthandPropertyAssignment: + return getAssignedTypeOfShorthandPropertyAssignment(parent as ShorthandPropertyAssignment); + } + return errorType; + } - function getInitialType(node: VariableDeclaration | BindingElement) { - return node.kind === SyntaxKind.VariableDeclaration ? - getInitialTypeOfVariableDeclaration(node) : - getInitialTypeOfBindingElement(node); - } + function getInitialTypeOfBindingElement(node: BindingElement): Type { + const pattern = node.parent; + const parentType = getInitialType(pattern.parent as VariableDeclaration | BindingElement); + const type = pattern.kind === SyntaxKind.ObjectBindingPattern ? + getTypeOfDestructuredProperty(parentType, node.propertyName || node.name as Identifier) : + !node.dotDotDotToken ? + getTypeOfDestructuredArrayElement(parentType, pattern.elements.indexOf(node)) : + getTypeOfDestructuredSpreadExpression(parentType); + return getTypeWithDefault(type, node.initializer!); + } - function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) { - return node.kind === SyntaxKind.VariableDeclaration && (node as VariableDeclaration).initializer && - isEmptyArrayLiteral((node as VariableDeclaration).initializer!) || - node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression && - isEmptyArrayLiteral((node.parent as BinaryExpression).right); - } + function getTypeOfInitializer(node: Expression) { + // Return the cached type if one is available. If the type of the variable was inferred + // from its initializer, we'll already have cached the type. Otherwise we compute it now + // without caching such that transient types are reflected. + const links = getNodeLinks(node); + return links.resolvedType || getTypeOfExpression(node); + } - function getReferenceCandidate(node: Expression): Expression { - switch (node.kind) { - case SyntaxKind.ParenthesizedExpression: - return getReferenceCandidate((node as ParenthesizedExpression).expression); - case SyntaxKind.BinaryExpression: - switch ((node as BinaryExpression).operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.BarBarEqualsToken: - case SyntaxKind.AmpersandAmpersandEqualsToken: - case SyntaxKind.QuestionQuestionEqualsToken: - return getReferenceCandidate((node as BinaryExpression).left); - case SyntaxKind.CommaToken: - return getReferenceCandidate((node as BinaryExpression).right); - } - } - return node; + function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { + if (node.initializer) { + return getTypeOfInitializer(node.initializer); } - - function getReferenceRoot(node: Node): Node { - const { parent } = node; - return parent.kind === SyntaxKind.ParenthesizedExpression || - parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && (parent as BinaryExpression).left === node || - parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken && (parent as BinaryExpression).right === node ? - getReferenceRoot(parent) : node; + if (node.parent.parent.kind === SyntaxKind.ForInStatement) { + return stringType; } - - function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { - if (clause.kind === SyntaxKind.CaseClause) { - return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression)); - } - return neverType; + if (node.parent.parent.kind === SyntaxKind.ForOfStatement) { + return checkRightHandSideOfForOf(node.parent.parent) || errorType; } + return errorType; + } - function getSwitchClauseTypes(switchStatement: SwitchStatement): Type[] { - const links = getNodeLinks(switchStatement); - if (!links.switchTypes) { - links.switchTypes = []; - for (const clause of switchStatement.caseBlock.clauses) { - links.switchTypes.push(getTypeOfSwitchClause(clause)); - } - } - return links.switchTypes; - } + function getInitialType(node: VariableDeclaration | BindingElement) { + return node.kind === SyntaxKind.VariableDeclaration ? + getInitialTypeOfVariableDeclaration(node) : + getInitialTypeOfBindingElement(node); + } - // Get the type names from all cases in a switch on `typeof`. The default clause and/or duplicate type names are - // represented as undefined. Return undefined if one or more case clause expressions are not string literals. - function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement): (string | undefined)[] | undefined { - if (some(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.CaseClause && !isStringLiteralLike(clause.expression))) { - return undefined; - } - const witnesses: (string | undefined)[] = []; - for (const clause of switchStatement.caseBlock.clauses) { - const text = clause.kind === SyntaxKind.CaseClause ? (clause.expression as StringLiteralLike).text : undefined; - witnesses.push(text && !contains(witnesses, text) ? text : undefined); - } - return witnesses; - } + function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) { + return node.kind === SyntaxKind.VariableDeclaration && (node as VariableDeclaration).initializer && + isEmptyArrayLiteral((node as VariableDeclaration).initializer!) || + node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression && + isEmptyArrayLiteral((node.parent as BinaryExpression).right); + } - function eachTypeContainedIn(source: Type, types: Type[]) { - return source.flags & TypeFlags.Union ? !forEach((source as UnionType).types, t => !contains(types, t)) : contains(types, source); + function getReferenceCandidate(node: Expression): Expression { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: + return getReferenceCandidate((node as ParenthesizedExpression).expression); + case SyntaxKind.BinaryExpression: + switch ((node as BinaryExpression).operatorToken.kind) { + case SyntaxKind.EqualsToken: + case SyntaxKind.BarBarEqualsToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: + case SyntaxKind.QuestionQuestionEqualsToken: + return getReferenceCandidate((node as BinaryExpression).left); + case SyntaxKind.CommaToken: + return getReferenceCandidate((node as BinaryExpression).right); + } } + return node; + } + + function getReferenceRoot(node: Node): Node { + const { parent } = node; + return parent.kind === SyntaxKind.ParenthesizedExpression || + parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && (parent as BinaryExpression).left === node || + parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken && (parent as BinaryExpression).right === node ? + getReferenceRoot(parent) : node; + } - function isTypeSubsetOf(source: Type, target: Type) { - return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType); + function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { + if (clause.kind === SyntaxKind.CaseClause) { + return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression)); } + return neverType; + } - function isTypeSubsetOfUnion(source: Type, target: UnionType) { - if (source.flags & TypeFlags.Union) { - for (const t of (source as UnionType).types) { - if (!containsType(target.types, t)) { - return false; - } - } - return true; - } - if (source.flags & TypeFlags.EnumLiteral && getBaseTypeOfEnumLiteralType(source as LiteralType) === target) { - return true; + function getSwitchClauseTypes(switchStatement: SwitchStatement): Type[] { + const links = getNodeLinks(switchStatement); + if (!links.switchTypes) { + links.switchTypes = []; + for (const clause of switchStatement.caseBlock.clauses) { + links.switchTypes.push(getTypeOfSwitchClause(clause)); } - return containsType(target.types, source); } + return links.switchTypes; + } - function forEachType(type: Type, f: (t: Type) => T | undefined): T | undefined { - return type.flags & TypeFlags.Union ? forEach((type as UnionType).types, f) : f(type); + // Get the type names from all cases in a switch on `typeof`. The default clause and/or duplicate type names are + // represented as undefined. Return undefined if one or more case clause expressions are not string literals. + function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement): (string | undefined)[] | undefined { + if (some(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.CaseClause && !isStringLiteralLike(clause.expression))) { + return undefined; } - - function someType(type: Type, f: (t: Type) => boolean): boolean { - return type.flags & TypeFlags.Union ? some((type as UnionType).types, f) : f(type); + const witnesses: (string | undefined)[] = []; + for (const clause of switchStatement.caseBlock.clauses) { + const text = clause.kind === SyntaxKind.CaseClause ? (clause.expression as StringLiteralLike).text : undefined; + witnesses.push(text && !contains(witnesses, text) ? text : undefined); } + return witnesses; + } - function everyType(type: Type, f: (t: Type) => boolean): boolean { - return type.flags & TypeFlags.Union ? every((type as UnionType).types, f) : f(type); - } + function eachTypeContainedIn(source: Type, types: Type[]) { + return source.flags & TypeFlags.Union ? !forEach((source as UnionType).types, t => !contains(types, t)) : contains(types, source); + } - function everyContainedType(type: Type, f: (t: Type) => boolean): boolean { - return type.flags & TypeFlags.UnionOrIntersection ? every((type as UnionOrIntersectionType).types, f) : f(type); - } + function isTypeSubsetOf(source: Type, target: Type) { + return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType); + } - function filterType(type: Type, f: (t: Type) => boolean): Type { - if (type.flags & TypeFlags.Union) { - const types = (type as UnionType).types; - const filtered = filter(types, f); - if (filtered === types) { - return type; - } - const origin = (type as UnionType).origin; - let newOrigin: Type | undefined; - if (origin && origin.flags & TypeFlags.Union) { - // If the origin type is a (denormalized) union type, filter its non-union constituents. If that ends - // up removing a smaller number of types than in the normalized constituent set (meaning some of the - // filtered types are within nested unions in the origin), then we can't construct a new origin type. - // Otherwise, if we have exactly one type left in the origin set, return that as the filtered type. - // Otherwise, construct a new filtered origin type. - const originTypes = (origin as UnionType).types; - const originFiltered = filter(originTypes, t => !!(t.flags & TypeFlags.Union) || f(t)); - if (originTypes.length - originFiltered.length === types.length - filtered.length) { - if (originFiltered.length === 1) { - return originFiltered[0]; - } - newOrigin = createOriginUnionOrIntersectionType(TypeFlags.Union, originFiltered); - } + function isTypeSubsetOfUnion(source: Type, target: UnionType) { + if (source.flags & TypeFlags.Union) { + for (const t of (source as UnionType).types) { + if (!containsType(target.types, t)) { + return false; } - return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); } - return type.flags & TypeFlags.Never || f(type) ? type : neverType; + return true; } - - function removeType(type: Type, targetType: Type) { - return filterType(type, t => t !== targetType); + if (source.flags & TypeFlags.EnumLiteral && getBaseTypeOfEnumLiteralType(source as LiteralType) === target) { + return true; } + return containsType(target.types, source); + } - function countTypes(type: Type) { - return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1; - } + function forEachType(type: Type, f: (t: Type) => T | undefined): T | undefined { + return type.flags & TypeFlags.Union ? forEach((type as UnionType).types, f) : f(type); + } - // Apply a mapping function to a type and return the resulting type. If the source type - // is a union type, the mapping function is applied to each constituent type and a union - // of the resulting types is returned. - function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined { - if (type.flags & TypeFlags.Never) { + function someType(type: Type, f: (t: Type) => boolean): boolean { + return type.flags & TypeFlags.Union ? some((type as UnionType).types, f) : f(type); + } + + function everyType(type: Type, f: (t: Type) => boolean): boolean { + return type.flags & TypeFlags.Union ? every((type as UnionType).types, f) : f(type); + } + + function everyContainedType(type: Type, f: (t: Type) => boolean): boolean { + return type.flags & TypeFlags.UnionOrIntersection ? every((type as UnionOrIntersectionType).types, f) : f(type); + } + + function filterType(type: Type, f: (t: Type) => boolean): Type { + if (type.flags & TypeFlags.Union) { + const types = (type as UnionType).types; + const filtered = filter(types, f); + if (filtered === types) { return type; } - if (!(type.flags & TypeFlags.Union)) { - return mapper(type); - } const origin = (type as UnionType).origin; - const types = origin && origin.flags & TypeFlags.Union ? (origin as UnionType).types : (type as UnionType).types; - let mappedTypes: Type[] | undefined; - let changed = false; - for (const t of types) { - const mapped = t.flags & TypeFlags.Union ? mapType(t, mapper, noReductions) : mapper(t); - changed ||= t !== mapped; - if (mapped) { - if (!mappedTypes) { - mappedTypes = [mapped]; - } - else { - mappedTypes.push(mapped); + let newOrigin: Type | undefined; + if (origin && origin.flags & TypeFlags.Union) { + // If the origin type is a (denormalized) union type, filter its non-union constituents. If that ends + // up removing a smaller number of types than in the normalized constituent set (meaning some of the + // filtered types are within nested unions in the origin), then we can't construct a new origin type. + // Otherwise, if we have exactly one type left in the origin set, return that as the filtered type. + // Otherwise, construct a new filtered origin type. + const originTypes = (origin as UnionType).types; + const originFiltered = filter(originTypes, t => !!(t.flags & TypeFlags.Union) || f(t)); + if (originTypes.length - originFiltered.length === types.length - filtered.length) { + if (originFiltered.length === 1) { + return originFiltered[0]; } + newOrigin = createOriginUnionOrIntersectionType(TypeFlags.Union, originFiltered); } } - return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; + return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); } + return type.flags & TypeFlags.Never || f(type) ? type : neverType; + } - function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { - return type.flags & TypeFlags.Union && aliasSymbol ? - getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments) : - mapType(type, mapper); - } + function removeType(type: Type, targetType: Type) { + return filterType(type, t => t !== targetType); + } + + function countTypes(type: Type) { + return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1; + } - function extractTypesOfKind(type: Type, kind: TypeFlags) { - return filterType(type, t => (t.flags & kind) !== 0); + // Apply a mapping function to a type and return the resulting type. If the source type + // is a union type, the mapping function is applied to each constituent type and a union + // of the resulting types is returned. + function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined { + if (type.flags & TypeFlags.Never) { + return type; + } + if (!(type.flags & TypeFlags.Union)) { + return mapper(type); + } + const origin = (type as UnionType).origin; + const types = origin && origin.flags & TypeFlags.Union ? (origin as UnionType).types : (type as UnionType).types; + let mappedTypes: Type[] | undefined; + let changed = false; + for (const t of types) { + const mapped = t.flags & TypeFlags.Union ? mapType(t, mapper, noReductions) : mapper(t); + changed ||= t !== mapped; + if (mapped) { + if (!mappedTypes) { + mappedTypes = [mapped]; + } + else { + mappedTypes.push(mapped); + } + } } + return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; + } + + function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { + return type.flags & TypeFlags.Union && aliasSymbol ? + getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments) : + mapType(type, mapper); + } + + function extractTypesOfKind(type: Type, kind: TypeFlags) { + return filterType(type, t => (t.flags & kind) !== 0); + } - // Return a new type in which occurrences of the string, number and bigint primitives and placeholder template - // literal types in typeWithPrimitives have been replaced with occurrences of compatible and more specific types - // from typeWithLiterals. This is essentially a limited form of intersection between the two types. We avoid a - // true intersection because it is more costly and, when applied to union types, generates a large number of - // types we don't actually care about. - function replacePrimitivesWithLiterals(typeWithPrimitives: Type, typeWithLiterals: Type) { - if (maybeTypeOfKind(typeWithPrimitives, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.Number | TypeFlags.BigInt) && - maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.NumberLiteral | TypeFlags.BigIntLiteral)) { - return mapType(typeWithPrimitives, t => - t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) : - isPatternLiteralType(t) && !maybeTypeOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? extractTypesOfKind(typeWithLiterals, TypeFlags.StringLiteral) : - t.flags & TypeFlags.Number ? extractTypesOfKind(typeWithLiterals, TypeFlags.Number | TypeFlags.NumberLiteral) : - t.flags & TypeFlags.BigInt ? extractTypesOfKind(typeWithLiterals, TypeFlags.BigInt | TypeFlags.BigIntLiteral) : t); - } - return typeWithPrimitives; - } + // Return a new type in which occurrences of the string, number and bigint primitives and placeholder template + // literal types in typeWithPrimitives have been replaced with occurrences of compatible and more specific types + // from typeWithLiterals. This is essentially a limited form of intersection between the two types. We avoid a + // true intersection because it is more costly and, when applied to union types, generates a large number of + // types we don't actually care about. + function replacePrimitivesWithLiterals(typeWithPrimitives: Type, typeWithLiterals: Type) { + if (maybeTypeOfKind(typeWithPrimitives, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.Number | TypeFlags.BigInt) && + maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.NumberLiteral | TypeFlags.BigIntLiteral)) { + return mapType(typeWithPrimitives, t => + t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) : + isPatternLiteralType(t) && !maybeTypeOfKind(typeWithLiterals, TypeFlags.String | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? extractTypesOfKind(typeWithLiterals, TypeFlags.StringLiteral) : + t.flags & TypeFlags.Number ? extractTypesOfKind(typeWithLiterals, TypeFlags.Number | TypeFlags.NumberLiteral) : + t.flags & TypeFlags.BigInt ? extractTypesOfKind(typeWithLiterals, TypeFlags.BigInt | TypeFlags.BigIntLiteral) : t); + } + return typeWithPrimitives; + } - function isIncomplete(flowType: FlowType) { - return flowType.flags === 0; - } + function isIncomplete(flowType: FlowType) { + return flowType.flags === 0; + } - function getTypeFromFlowType(flowType: FlowType) { - return flowType.flags === 0 ? (flowType as IncompleteType).type : flowType as Type; - } + function getTypeFromFlowType(flowType: FlowType) { + return flowType.flags === 0 ? (flowType as IncompleteType).type : flowType as Type; + } - function createFlowType(type: Type, incomplete: boolean): FlowType { - return incomplete ? { flags: 0, type: type.flags & TypeFlags.Never ? silentNeverType : type } : type; - } + function createFlowType(type: Type, incomplete: boolean): FlowType { + return incomplete ? { flags: 0, type: type.flags & TypeFlags.Never ? silentNeverType : type } : type; + } - // An evolving array type tracks the element types that have so far been seen in an - // 'x.push(value)' or 'x[n] = value' operation along the control flow graph. Evolving - // array types are ultimately converted into manifest array types (using getFinalArrayType) - // and never escape the getFlowTypeOfReference function. - function createEvolvingArrayType(elementType: Type): EvolvingArrayType { - const result = createObjectType(ObjectFlags.EvolvingArray) as EvolvingArrayType; - result.elementType = elementType; - return result; - } + // An evolving array type tracks the element types that have so far been seen in an + // 'x.push(value)' or 'x[n] = value' operation along the control flow graph. Evolving + // array types are ultimately converted into manifest array types (using getFinalArrayType) + // and never escape the getFlowTypeOfReference function. + function createEvolvingArrayType(elementType: Type): EvolvingArrayType { + const result = createObjectType(ObjectFlags.EvolvingArray) as EvolvingArrayType; + result.elementType = elementType; + return result; + } - function getEvolvingArrayType(elementType: Type): EvolvingArrayType { - return evolvingArrayTypes[elementType.id] || (evolvingArrayTypes[elementType.id] = createEvolvingArrayType(elementType)); - } + function getEvolvingArrayType(elementType: Type): EvolvingArrayType { + return evolvingArrayTypes[elementType.id] || (evolvingArrayTypes[elementType.id] = createEvolvingArrayType(elementType)); + } - // When adding evolving array element types we do not perform subtype reduction. Instead, - // we defer subtype reduction until the evolving array type is finalized into a manifest - // array type. - function addEvolvingArrayElementType(evolvingArrayType: EvolvingArrayType, node: Expression): EvolvingArrayType { - const elementType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(getContextFreeTypeOfExpression(node))); - return isTypeSubsetOf(elementType, evolvingArrayType.elementType) ? evolvingArrayType : getEvolvingArrayType(getUnionType([evolvingArrayType.elementType, elementType])); - } + // When adding evolving array element types we do not perform subtype reduction. Instead, + // we defer subtype reduction until the evolving array type is finalized into a manifest + // array type. + function addEvolvingArrayElementType(evolvingArrayType: EvolvingArrayType, node: Expression): EvolvingArrayType { + const elementType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(getContextFreeTypeOfExpression(node))); + return isTypeSubsetOf(elementType, evolvingArrayType.elementType) ? evolvingArrayType : getEvolvingArrayType(getUnionType([evolvingArrayType.elementType, elementType])); + } - function createFinalArrayType(elementType: Type) { - return elementType.flags & TypeFlags.Never ? - autoArrayType : - createArrayType(elementType.flags & TypeFlags.Union ? - getUnionType((elementType as UnionType).types, UnionReduction.Subtype) : - elementType); - } + function createFinalArrayType(elementType: Type) { + return elementType.flags & TypeFlags.Never ? + autoArrayType : + createArrayType(elementType.flags & TypeFlags.Union ? + getUnionType((elementType as UnionType).types, UnionReduction.Subtype) : + elementType); + } - // We perform subtype reduction upon obtaining the final array type from an evolving array type. - function getFinalArrayType(evolvingArrayType: EvolvingArrayType): Type { - return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createFinalArrayType(evolvingArrayType.elementType)); - } + // We perform subtype reduction upon obtaining the final array type from an evolving array type. + function getFinalArrayType(evolvingArrayType: EvolvingArrayType): Type { + return evolvingArrayType.finalArrayType || (evolvingArrayType.finalArrayType = createFinalArrayType(evolvingArrayType.elementType)); + } - function finalizeEvolvingArrayType(type: Type): Type { - return getObjectFlags(type) & ObjectFlags.EvolvingArray ? getFinalArrayType(type as EvolvingArrayType) : type; - } + function finalizeEvolvingArrayType(type: Type): Type { + return getObjectFlags(type) & ObjectFlags.EvolvingArray ? getFinalArrayType(type as EvolvingArrayType) : type; + } - function getElementTypeOfEvolvingArrayType(type: Type) { - return getObjectFlags(type) & ObjectFlags.EvolvingArray ? (type as EvolvingArrayType).elementType : neverType; - } + function getElementTypeOfEvolvingArrayType(type: Type) { + return getObjectFlags(type) & ObjectFlags.EvolvingArray ? (type as EvolvingArrayType).elementType : neverType; + } - function isEvolvingArrayTypeList(types: Type[]) { - let hasEvolvingArrayType = false; - for (const t of types) { - if (!(t.flags & TypeFlags.Never)) { - if (!(getObjectFlags(t) & ObjectFlags.EvolvingArray)) { - return false; - } - hasEvolvingArrayType = true; + function isEvolvingArrayTypeList(types: Type[]) { + let hasEvolvingArrayType = false; + for (const t of types) { + if (!(t.flags & TypeFlags.Never)) { + if (!(getObjectFlags(t) & ObjectFlags.EvolvingArray)) { + return false; } + hasEvolvingArrayType = true; } - return hasEvolvingArrayType; } + return hasEvolvingArrayType; + } - // Return true if the given node is 'x' in an 'x.length', x.push(value)', 'x.unshift(value)' or - // 'x[n] = value' operation, where 'n' is an expression of type any, undefined, or a number-like type. - function isEvolvingArrayOperationTarget(node: Node) { - const root = getReferenceRoot(node); - const parent = root.parent; - const isLengthPushOrUnshift = isPropertyAccessExpression(parent) && ( - parent.name.escapedText === "length" || - parent.parent.kind === SyntaxKind.CallExpression - && isIdentifier(parent.name) - && isPushOrUnshiftIdentifier(parent.name)); - const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression && - (parent as ElementAccessExpression).expression === root && - parent.parent.kind === SyntaxKind.BinaryExpression && - (parent.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && - (parent.parent as BinaryExpression).left === parent && - !isAssignmentTarget(parent.parent) && - isTypeAssignableToKind(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression), TypeFlags.NumberLike); - return isLengthPushOrUnshift || isElementAssignment; - } + // Return true if the given node is 'x' in an 'x.length', x.push(value)', 'x.unshift(value)' or + // 'x[n] = value' operation, where 'n' is an expression of type any, undefined, or a number-like type. + function isEvolvingArrayOperationTarget(node: Node) { + const root = getReferenceRoot(node); + const parent = root.parent; + const isLengthPushOrUnshift = isPropertyAccessExpression(parent) && ( + parent.name.escapedText === "length" || + parent.parent.kind === SyntaxKind.CallExpression + && isIdentifier(parent.name) + && isPushOrUnshiftIdentifier(parent.name)); + const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression && + (parent as ElementAccessExpression).expression === root && + parent.parent.kind === SyntaxKind.BinaryExpression && + (parent.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken && + (parent.parent as BinaryExpression).left === parent && + !isAssignmentTarget(parent.parent) && + isTypeAssignableToKind(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression), TypeFlags.NumberLike); + return isLengthPushOrUnshift || isElementAssignment; + } - function isDeclarationWithExplicitTypeAnnotation(node: Declaration) { - return (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isParameter(node)) && - !!(getEffectiveTypeAnnotationNode(node) || - isInJSFile(node) && hasInitializer(node) && node.initializer && isFunctionExpressionOrArrowFunction(node.initializer) && getEffectiveReturnTypeNode(node.initializer)); - } + function isDeclarationWithExplicitTypeAnnotation(node: Declaration) { + return (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isParameter(node)) && + !!(getEffectiveTypeAnnotationNode(node) || + isInJSFile(node) && hasInitializer(node) && node.initializer && isFunctionExpressionOrArrowFunction(node.initializer) && getEffectiveReturnTypeNode(node.initializer)); + } - function getExplicitTypeOfSymbol(symbol: Symbol, diagnostic?: Diagnostic) { - symbol = resolveSymbol(symbol); - if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule)) { - return getTypeOfSymbol(symbol); + function getExplicitTypeOfSymbol(symbol: Symbol, diagnostic?: Diagnostic) { + symbol = resolveSymbol(symbol); + if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule)) { + return getTypeOfSymbol(symbol); + } + if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { + if (getCheckFlags(symbol) & CheckFlags.Mapped) { + const origin = (symbol as MappedSymbol).syntheticOrigin; + if (origin && getExplicitTypeOfSymbol(origin)) { + return getTypeOfSymbol(symbol); + } } - if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { - if (getCheckFlags(symbol) & CheckFlags.Mapped) { - const origin = (symbol as MappedSymbol).syntheticOrigin; - if (origin && getExplicitTypeOfSymbol(origin)) { - return getTypeOfSymbol(symbol); - } + const declaration = symbol.valueDeclaration; + if (declaration) { + if (isDeclarationWithExplicitTypeAnnotation(declaration)) { + return getTypeOfSymbol(symbol); } - const declaration = symbol.valueDeclaration; - if (declaration) { - if (isDeclarationWithExplicitTypeAnnotation(declaration)) { - return getTypeOfSymbol(symbol); - } - if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { - const statement = declaration.parent.parent; - const expressionType = getTypeOfDottedName(statement.expression, /*diagnostic*/ undefined); - if (expressionType) { - const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; - return checkIteratedTypeOrElementType(use, expressionType, undefinedType, /*errorNode*/ undefined); - } - } - if (diagnostic) { - addRelatedInfo(diagnostic, createDiagnosticForNode(declaration, Diagnostics._0_needs_an_explicit_type_annotation, symbolToString(symbol))); + if (isVariableDeclaration(declaration) && declaration.parent.parent.kind === SyntaxKind.ForOfStatement) { + const statement = declaration.parent.parent; + const expressionType = getTypeOfDottedName(statement.expression, /*diagnostic*/ undefined); + if (expressionType) { + const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; + return checkIteratedTypeOrElementType(use, expressionType, undefinedType, /*errorNode*/ undefined); } } + if (diagnostic) { + addRelatedInfo(diagnostic, createDiagnosticForNode(declaration, Diagnostics._0_needs_an_explicit_type_annotation, symbolToString(symbol))); + } } } + } - // We require the dotted function name in an assertion expression to be comprised of identifiers - // that reference function, method, class or value module symbols; or variable, property or - // parameter symbols with declarations that have explicit type annotations. Such references are - // resolvable with no possibility of triggering circularities in control flow analysis. - function getTypeOfDottedName(node: Expression, diagnostic: Diagnostic | undefined): Type | undefined { - if (!(node.flags & NodeFlags.InWithStatement)) { - switch (node.kind) { - case SyntaxKind.Identifier: - const symbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(node as Identifier)); - return getExplicitTypeOfSymbol(symbol, diagnostic); - case SyntaxKind.ThisKeyword: - return getExplicitThisType(node); - case SyntaxKind.SuperKeyword: - return checkSuperExpression(node); - case SyntaxKind.PropertyAccessExpression: { - const type = getTypeOfDottedName((node as PropertyAccessExpression).expression, diagnostic); - if (type) { - const name = (node as PropertyAccessExpression).name; - let prop: Symbol | undefined; - if (isPrivateIdentifier(name)) { - if (!type.symbol) { - return undefined; - } - prop = getPropertyOfType(type, getSymbolNameForPrivateIdentifier(type.symbol, name.escapedText)); - } - else { - prop = getPropertyOfType(type, name.escapedText); + // We require the dotted function name in an assertion expression to be comprised of identifiers + // that reference function, method, class or value module symbols; or variable, property or + // parameter symbols with declarations that have explicit type annotations. Such references are + // resolvable with no possibility of triggering circularities in control flow analysis. + function getTypeOfDottedName(node: Expression, diagnostic: Diagnostic | undefined): Type | undefined { + if (!(node.flags & NodeFlags.InWithStatement)) { + switch (node.kind) { + case SyntaxKind.Identifier: + const symbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(node as Identifier)); + return getExplicitTypeOfSymbol(symbol, diagnostic); + case SyntaxKind.ThisKeyword: + return getExplicitThisType(node); + case SyntaxKind.SuperKeyword: + return checkSuperExpression(node); + case SyntaxKind.PropertyAccessExpression: { + const type = getTypeOfDottedName((node as PropertyAccessExpression).expression, diagnostic); + if (type) { + const name = (node as PropertyAccessExpression).name; + let prop: Symbol | undefined; + if (isPrivateIdentifier(name)) { + if (!type.symbol) { + return undefined; } - return prop && getExplicitTypeOfSymbol(prop, diagnostic); + prop = getPropertyOfType(type, getSymbolNameForPrivateIdentifier(type.symbol, name.escapedText)); } - return undefined; + else { + prop = getPropertyOfType(type, name.escapedText); + } + return prop && getExplicitTypeOfSymbol(prop, diagnostic); } - case SyntaxKind.ParenthesizedExpression: - return getTypeOfDottedName((node as ParenthesizedExpression).expression, diagnostic); + return undefined; } + case SyntaxKind.ParenthesizedExpression: + return getTypeOfDottedName((node as ParenthesizedExpression).expression, diagnostic); } } + } - function getEffectsSignature(node: CallExpression) { - const links = getNodeLinks(node); - let signature = links.effectsSignature; - if (signature === undefined) { - // A call expression parented by an expression statement is a potential assertion. Other call - // expressions are potential type predicate function calls. In order to avoid triggering - // circularities in control flow analysis, we use getTypeOfDottedName when resolving the call - // target expression of an assertion. - let funcType: Type | undefined; - if (node.parent.kind === SyntaxKind.ExpressionStatement) { - funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined); - } - else if (node.expression.kind !== SyntaxKind.SuperKeyword) { - if (isOptionalChain(node)) { - funcType = checkNonNullType( - getOptionalExpressionType(checkExpression(node.expression), node.expression), - node.expression - ); - } - else { - funcType = checkNonNullExpression(node.expression); - } + function getEffectsSignature(node: CallExpression) { + const links = getNodeLinks(node); + let signature = links.effectsSignature; + if (signature === undefined) { + // A call expression parented by an expression statement is a potential assertion. Other call + // expressions are potential type predicate function calls. In order to avoid triggering + // circularities in control flow analysis, we use getTypeOfDottedName when resolving the call + // target expression of an assertion. + let funcType: Type | undefined; + if (node.parent.kind === SyntaxKind.ExpressionStatement) { + funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined); + } + else if (node.expression.kind !== SyntaxKind.SuperKeyword) { + if (isOptionalChain(node)) { + funcType = checkNonNullType( + getOptionalExpressionType(checkExpression(node.expression), node.expression), + node.expression + ); + } + else { + funcType = checkNonNullExpression(node.expression); } - const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call); - const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] : - some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) : - undefined; - signature = links.effectsSignature = candidate && hasTypePredicateOrNeverReturnType(candidate) ? candidate : unknownSignature; } - return signature === unknownSignature ? undefined : signature; + const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call); + const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] : + some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) : + undefined; + signature = links.effectsSignature = candidate && hasTypePredicateOrNeverReturnType(candidate) ? candidate : unknownSignature; } + return signature === unknownSignature ? undefined : signature; + } - function hasTypePredicateOrNeverReturnType(signature: Signature) { - return !!(getTypePredicateOfSignature(signature) || - signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never); - } + function hasTypePredicateOrNeverReturnType(signature: Signature) { + return !!(getTypePredicateOfSignature(signature) || + signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never); + } - function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) { - if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) { - return callExpression.arguments[predicate.parameterIndex]; - } - const invokedExpression = skipParentheses(callExpression.expression); - return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined; + function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) { + if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) { + return callExpression.arguments[predicate.parameterIndex]; } + const invokedExpression = skipParentheses(callExpression.expression); + return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined; + } - function reportFlowControlError(node: Node) { - const block = findAncestor(node, isFunctionOrModuleBlock) as Block | ModuleBlock | SourceFile; - const sourceFile = getSourceFileOfNode(node); - const span = getSpanOfTokenAtPosition(sourceFile, block.statements.pos); - diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis)); - } + function reportFlowControlError(node: Node) { + const block = findAncestor(node, isFunctionOrModuleBlock) as Block | ModuleBlock | SourceFile; + const sourceFile = getSourceFileOfNode(node); + const span = getSpanOfTokenAtPosition(sourceFile, block.statements.pos); + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis)); + } - function isReachableFlowNode(flow: FlowNode) { - const result = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ false); - lastFlowNode = flow; - lastFlowNodeReachable = result; - return result; - } + function isReachableFlowNode(flow: FlowNode) { + const result = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ false); + lastFlowNode = flow; + lastFlowNodeReachable = result; + return result; + } - function isFalseExpression(expr: Expression): boolean { - const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); - return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && ( - (node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((node as BinaryExpression).left) || isFalseExpression((node as BinaryExpression).right)) || - (node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((node as BinaryExpression).left) && isFalseExpression((node as BinaryExpression).right)); - } + function isFalseExpression(expr: Expression): boolean { + const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && ( + (node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((node as BinaryExpression).left) || isFalseExpression((node as BinaryExpression).right)) || + (node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((node as BinaryExpression).left) && isFalseExpression((node as BinaryExpression).right)); + } - function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean { - while (true) { - if (flow === lastFlowNode) { - return lastFlowNodeReachable; - } - const flags = flow.flags; - if (flags & FlowFlags.Shared) { - if (!noCacheCheck) { - const id = getFlowNodeId(flow); - const reachable = flowNodeReachable[id]; - return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ true)); - } - noCacheCheck = false; - } - if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation)) { - flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation).antecedent; + function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean { + while (true) { + if (flow === lastFlowNode) { + return lastFlowNodeReachable; + } + const flags = flow.flags; + if (flags & FlowFlags.Shared) { + if (!noCacheCheck) { + const id = getFlowNodeId(flow); + const reachable = flowNodeReachable[id]; + return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ true)); } - else if (flags & FlowFlags.Call) { - const signature = getEffectsSignature((flow as FlowCall).node); - if (signature) { - const predicate = getTypePredicateOfSignature(signature); - if (predicate && predicate.kind === TypePredicateKind.AssertsIdentifier && !predicate.type) { - const predicateArgument = (flow as FlowCall).node.arguments[predicate.parameterIndex]; - if (predicateArgument && isFalseExpression(predicateArgument)) { - return false; - } - } - if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { + noCacheCheck = false; + } + if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation)) { + flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation).antecedent; + } + else if (flags & FlowFlags.Call) { + const signature = getEffectsSignature((flow as FlowCall).node); + if (signature) { + const predicate = getTypePredicateOfSignature(signature); + if (predicate && predicate.kind === TypePredicateKind.AssertsIdentifier && !predicate.type) { + const predicateArgument = (flow as FlowCall).node.arguments[predicate.parameterIndex]; + if (predicateArgument && isFalseExpression(predicateArgument)) { return false; } } - flow = (flow as FlowCall).antecedent; - } - else if (flags & FlowFlags.BranchLabel) { - // A branching point is reachable if any branch is reachable. - return some((flow as FlowLabel).antecedents, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); - } - else if (flags & FlowFlags.LoopLabel) { - const antecedents = (flow as FlowLabel).antecedents; - if (antecedents === undefined || antecedents.length === 0) { + if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { return false; } - // A loop is reachable if the control flow path that leads to the top is reachable. - flow = antecedents[0]; } - else if (flags & FlowFlags.SwitchClause) { - // The control flow path representing an unmatched value in a switch statement with - // no default clause is unreachable if the switch statement is exhaustive. - if ((flow as FlowSwitchClause).clauseStart === (flow as FlowSwitchClause).clauseEnd && isExhaustiveSwitchStatement((flow as FlowSwitchClause).switchStatement)) { - return false; - } - flow = (flow as FlowSwitchClause).antecedent; + flow = (flow as FlowCall).antecedent; + } + else if (flags & FlowFlags.BranchLabel) { + // A branching point is reachable if any branch is reachable. + return some((flow as FlowLabel).antecedents, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); + } + else if (flags & FlowFlags.LoopLabel) { + const antecedents = (flow as FlowLabel).antecedents; + if (antecedents === undefined || antecedents.length === 0) { + return false; } - else if (flags & FlowFlags.ReduceLabel) { - // Cache is unreliable once we start adjusting labels - lastFlowNode = undefined; - const target = (flow as FlowReduceLabel).target; - const saveAntecedents = target.antecedents; - target.antecedents = (flow as FlowReduceLabel).antecedents; - const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); - target.antecedents = saveAntecedents; - return result; + // A loop is reachable if the control flow path that leads to the top is reachable. + flow = antecedents[0]; + } + else if (flags & FlowFlags.SwitchClause) { + // The control flow path representing an unmatched value in a switch statement with + // no default clause is unreachable if the switch statement is exhaustive. + if ((flow as FlowSwitchClause).clauseStart === (flow as FlowSwitchClause).clauseEnd && isExhaustiveSwitchStatement((flow as FlowSwitchClause).switchStatement)) { + return false; } - else { - return !(flags & FlowFlags.Unreachable); + flow = (flow as FlowSwitchClause).antecedent; + } + else if (flags & FlowFlags.ReduceLabel) { + // Cache is unreliable once we start adjusting labels + lastFlowNode = undefined; + const target = (flow as FlowReduceLabel).target; + const saveAntecedents = target.antecedents; + target.antecedents = (flow as FlowReduceLabel).antecedents; + const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + target.antecedents = saveAntecedents; + return result; + } + else { + return !(flags & FlowFlags.Unreachable); + } + } + } + + // Return true if the given flow node is preceded by a 'super(...)' call in every possible code path + // leading to the node. + function isPostSuperFlowNode(flow: FlowNode, noCacheCheck: boolean): boolean { + while (true) { + const flags = flow.flags; + if (flags & FlowFlags.Shared) { + if (!noCacheCheck) { + const id = getFlowNodeId(flow); + const postSuper = flowNodePostSuper[id]; + return postSuper !== undefined ? postSuper : (flowNodePostSuper[id] = isPostSuperFlowNode(flow, /*noCacheCheck*/ true)); + } + noCacheCheck = false; + } + if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.SwitchClause)) { + flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation | FlowSwitchClause).antecedent; + } + else if (flags & FlowFlags.Call) { + if ((flow as FlowCall).node.expression.kind === SyntaxKind.SuperKeyword) { + return true; } + flow = (flow as FlowCall).antecedent; + } + else if (flags & FlowFlags.BranchLabel) { + // A branching point is post-super if every branch is post-super. + return every((flow as FlowLabel).antecedents, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false)); + } + else if (flags & FlowFlags.LoopLabel) { + // A loop is post-super if the control flow path that leads to the top is post-super. + flow = (flow as FlowLabel).antecedents![0]; + } + else if (flags & FlowFlags.ReduceLabel) { + const target = (flow as FlowReduceLabel).target; + const saveAntecedents = target.antecedents; + target.antecedents = (flow as FlowReduceLabel).antecedents; + const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + target.antecedents = saveAntecedents; + return result; + } + else { + // Unreachable nodes are considered post-super to silence errors + return !!(flags & FlowFlags.Unreachable); + } + } + } + + function isConstantReference(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.Identifier: { + const symbol = getResolvedSymbol(node as Identifier); + return isConstVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSymbolAssigned(symbol); + } + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + // The resolvedSymbol property is initialized by checkPropertyAccess or checkElementAccess before we get here. + return isConstantReference((node as AccessExpression).expression) && isReadonlySymbol(getNodeLinks(node).resolvedSymbol || unknownSymbol); + } + return false; + } + + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = reference.flowNode) { + let key: string | undefined; + let isKeySet = false; + let flowDepth = 0; + if (flowAnalysisDisabled) { + return errorType; + } + if (!flowNode) { + return declaredType; + } + flowInvocationCount++; + const sharedFlowStart = sharedFlowCount; + const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode)); + sharedFlowCount = sharedFlowStart; + // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation, + // we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations + // on empty arrays are possible without implicit any errors and new element types can be inferred without + // type mismatch errors. + const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType); + if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && !(resultType.flags & TypeFlags.Never) && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { + return declaredType; + } + // The non-null unknown type should never escape control flow analysis. + return resultType === nonNullUnknownType ? unknownType : resultType; + + function getOrSetCacheKey() { + if (isKeySet) { + return key; } + isKeySet = true; + return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer); } - // Return true if the given flow node is preceded by a 'super(...)' call in every possible code path - // leading to the node. - function isPostSuperFlowNode(flow: FlowNode, noCacheCheck: boolean): boolean { + function getTypeAtFlowNode(flow: FlowNode): FlowType { + if (flowDepth === 2000) { + // We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error + // and disable further control flow analysis in the containing function or module body. + tracing?.instant(tracing.Phase.CheckTypes, "getTypeAtFlowNode_DepthLimit", { flowId: flow.id }); + flowAnalysisDisabled = true; + reportFlowControlError(reference); + return errorType; + } + flowDepth++; + let sharedFlow: FlowNode | undefined; while (true) { const flags = flow.flags; if (flags & FlowFlags.Shared) { - if (!noCacheCheck) { - const id = getFlowNodeId(flow); - const postSuper = flowNodePostSuper[id]; - return postSuper !== undefined ? postSuper : (flowNodePostSuper[id] = isPostSuperFlowNode(flow, /*noCacheCheck*/ true)); + // 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 + // antecedent of more than one node. + for (let i = sharedFlowStart; i < sharedFlowCount; i++) { + if (sharedFlowNodes[i] === flow) { + flowDepth--; + return sharedFlowTypes[i]; + } } - noCacheCheck = false; + sharedFlow = flow; } - if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.SwitchClause)) { - flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation | FlowSwitchClause).antecedent; + let type: FlowType | undefined; + if (flags & FlowFlags.Assignment) { + type = getTypeAtFlowAssignment(flow as FlowAssignment); + if (!type) { + flow = (flow as FlowAssignment).antecedent; + continue; + } } else if (flags & FlowFlags.Call) { - if ((flow as FlowCall).node.expression.kind === SyntaxKind.SuperKeyword) { - return true; + type = getTypeAtFlowCall(flow as FlowCall); + if (!type) { + flow = (flow as FlowCall).antecedent; + continue; } - flow = (flow as FlowCall).antecedent; } - else if (flags & FlowFlags.BranchLabel) { - // A branching point is post-super if every branch is post-super. - return every((flow as FlowLabel).antecedents, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false)); + else if (flags & FlowFlags.Condition) { + type = getTypeAtFlowCondition(flow as FlowCondition); + } + else if (flags & FlowFlags.SwitchClause) { + type = getTypeAtSwitchClause(flow as FlowSwitchClause); + } + else if (flags & FlowFlags.Label) { + if ((flow as FlowLabel).antecedents!.length === 1) { + flow = (flow as FlowLabel).antecedents![0]; + continue; + } + type = flags & FlowFlags.BranchLabel ? + getTypeAtFlowBranchLabel(flow as FlowLabel) : + getTypeAtFlowLoopLabel(flow as FlowLabel); } - else if (flags & FlowFlags.LoopLabel) { - // A loop is post-super if the control flow path that leads to the top is post-super. - flow = (flow as FlowLabel).antecedents![0]; + else if (flags & FlowFlags.ArrayMutation) { + type = getTypeAtFlowArrayMutation(flow as FlowArrayMutation); + if (!type) { + flow = (flow as FlowArrayMutation).antecedent; + continue; + } } else if (flags & FlowFlags.ReduceLabel) { const target = (flow as FlowReduceLabel).target; const saveAntecedents = target.antecedents; target.antecedents = (flow as FlowReduceLabel).antecedents; - const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + type = getTypeAtFlowNode((flow as FlowReduceLabel).antecedent); target.antecedents = saveAntecedents; - return result; + } + else if (flags & FlowFlags.Start) { + // Check if we should continue with the control flow of the containing function. + const container = (flow as FlowStart).node; + if (container && container !== flowContainer && + reference.kind !== SyntaxKind.PropertyAccessExpression && + reference.kind !== SyntaxKind.ElementAccessExpression && + reference.kind !== SyntaxKind.ThisKeyword) { + flow = container.flowNode!; + continue; + } + // At the top of the flow we have the initial type. + type = initialType; } else { - // Unreachable nodes are considered post-super to silence errors - return !!(flags & FlowFlags.Unreachable); + // Unreachable code errors are reported in the binding phase. Here we + // simply return the non-auto declared type to reduce follow-on errors. + type = convertAutoToAny(declaredType); } - } - } - - function isConstantReference(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.Identifier: { - const symbol = getResolvedSymbol(node as Identifier); - return isConstVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSymbolAssigned(symbol); + if (sharedFlow) { + // Record visited node and the associated type in the cache. + sharedFlowNodes[sharedFlowCount] = sharedFlow; + sharedFlowTypes[sharedFlowCount] = type; + sharedFlowCount++; } - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - // The resolvedSymbol property is initialized by checkPropertyAccess or checkElementAccess before we get here. - return isConstantReference((node as AccessExpression).expression) && isReadonlySymbol(getNodeLinks(node).resolvedSymbol || unknownSymbol); + flowDepth--; + return type; } - return false; } - function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = reference.flowNode) { - let key: string | undefined; - let isKeySet = false; - let flowDepth = 0; - if (flowAnalysisDisabled) { - return errorType; - } - if (!flowNode) { - return declaredType; - } - flowInvocationCount++; - const sharedFlowStart = sharedFlowCount; - const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode)); - sharedFlowCount = sharedFlowStart; - // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation, - // we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations - // on empty arrays are possible without implicit any errors and new element types can be inferred without - // type mismatch errors. - const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType); - if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && !(resultType.flags & TypeFlags.Never) && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { - return declaredType; - } - // The non-null unknown type should never escape control flow analysis. - return resultType === nonNullUnknownType ? unknownType : resultType; + function getInitialOrAssignedType(flow: FlowAssignment) { + const node = flow.node; + return getNarrowableTypeForReference(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? + getInitialType(node as VariableDeclaration | BindingElement) : + getAssignedType(node), reference); + } - function getOrSetCacheKey() { - if (isKeySet) { - return key; + function getTypeAtFlowAssignment(flow: FlowAssignment) { + const node = flow.node; + // Assignments only narrow the computed type if the declared type is a union type. Thus, we + // only need to evaluate the assigned type if the declared type is a union type. + if (isMatchingReference(reference, node)) { + if (!isReachableFlowNode(flow)) { + return unreachableNeverType; } - isKeySet = 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 - // and disable further control flow analysis in the containing function or module body. - tracing?.instant(tracing.Phase.CheckTypes, "getTypeAtFlowNode_DepthLimit", { flowId: flow.id }); - flowAnalysisDisabled = true; - reportFlowControlError(reference); - return errorType; + if (getAssignmentTargetKind(node) === AssignmentKind.Compound) { + const flowType = getTypeAtFlowNode(flow.antecedent); + return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType)); } - flowDepth++; - let sharedFlow: FlowNode | undefined; - while (true) { - const flags = flow.flags; - 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 - // antecedent of more than one node. - for (let i = sharedFlowStart; i < sharedFlowCount; i++) { - if (sharedFlowNodes[i] === flow) { - flowDepth--; - return sharedFlowTypes[i]; - } - } - sharedFlow = flow; - } - let type: FlowType | undefined; - if (flags & FlowFlags.Assignment) { - type = getTypeAtFlowAssignment(flow as FlowAssignment); - if (!type) { - flow = (flow as FlowAssignment).antecedent; - continue; - } - } - else if (flags & FlowFlags.Call) { - type = getTypeAtFlowCall(flow as FlowCall); - if (!type) { - flow = (flow as FlowCall).antecedent; - continue; - } - } - else if (flags & FlowFlags.Condition) { - type = getTypeAtFlowCondition(flow as FlowCondition); - } - else if (flags & FlowFlags.SwitchClause) { - type = getTypeAtSwitchClause(flow as FlowSwitchClause); - } - else if (flags & FlowFlags.Label) { - if ((flow as FlowLabel).antecedents!.length === 1) { - flow = (flow as FlowLabel).antecedents![0]; - continue; - } - type = flags & FlowFlags.BranchLabel ? - getTypeAtFlowBranchLabel(flow as FlowLabel) : - getTypeAtFlowLoopLabel(flow as FlowLabel); - } - else if (flags & FlowFlags.ArrayMutation) { - type = getTypeAtFlowArrayMutation(flow as FlowArrayMutation); - if (!type) { - flow = (flow as FlowArrayMutation).antecedent; - continue; - } - } - else if (flags & FlowFlags.ReduceLabel) { - const target = (flow as FlowReduceLabel).target; - const saveAntecedents = target.antecedents; - target.antecedents = (flow as FlowReduceLabel).antecedents; - type = getTypeAtFlowNode((flow as FlowReduceLabel).antecedent); - target.antecedents = saveAntecedents; - } - else if (flags & FlowFlags.Start) { - // Check if we should continue with the control flow of the containing function. - const container = (flow as FlowStart).node; - if (container && container !== flowContainer && - reference.kind !== SyntaxKind.PropertyAccessExpression && - reference.kind !== SyntaxKind.ElementAccessExpression && - reference.kind !== SyntaxKind.ThisKeyword) { - flow = container.flowNode!; - continue; - } - // At the top of the flow we have the initial type. - type = initialType; - } - else { - // Unreachable code errors are reported in the binding phase. Here we - // simply return the non-auto declared type to reduce follow-on errors. - type = convertAutoToAny(declaredType); - } - if (sharedFlow) { - // Record visited node and the associated type in the cache. - sharedFlowNodes[sharedFlowCount] = sharedFlow; - sharedFlowTypes[sharedFlowCount] = type; - sharedFlowCount++; + if (declaredType === autoType || declaredType === autoArrayType) { + if (isEmptyArrayAssignment(node)) { + return getEvolvingArrayType(neverType); } - flowDepth--; - return type; + const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow)); + return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; } + if (declaredType.flags & TypeFlags.Union) { + return getAssignmentReducedType(declaredType as UnionType, getInitialOrAssignedType(flow)); + } + return declaredType; } - - function getInitialOrAssignedType(flow: FlowAssignment) { - const node = flow.node; - return getNarrowableTypeForReference(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? - getInitialType(node as VariableDeclaration | BindingElement) : - getAssignedType(node), reference); - } - - function getTypeAtFlowAssignment(flow: FlowAssignment) { - const node = flow.node; - // Assignments only narrow the computed type if the declared type is a union type. Thus, we - // only need to evaluate the assigned type if the declared type is a union type. - if (isMatchingReference(reference, node)) { - if (!isReachableFlowNode(flow)) { - return unreachableNeverType; - } - if (getAssignmentTargetKind(node) === AssignmentKind.Compound) { - const flowType = getTypeAtFlowNode(flow.antecedent); - return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType)); - } - if (declaredType === autoType || declaredType === autoArrayType) { - if (isEmptyArrayAssignment(node)) { - return getEvolvingArrayType(neverType); - } - const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow)); - return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; - } - if (declaredType.flags & TypeFlags.Union) { - return getAssignmentReducedType(declaredType as UnionType, getInitialOrAssignedType(flow)); - } - return declaredType; + // We didn't have a direct match. However, if the reference is a dotted name, this + // may be an assignment to a left hand part of the reference. For example, for a + // reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case, + // return the declared type. + if (containsMatchingReference(reference, node)) { + if (!isReachableFlowNode(flow)) { + return unreachableNeverType; } - // We didn't have a direct match. However, if the reference is a dotted name, this - // may be an assignment to a left hand part of the reference. For example, for a - // reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case, - // return the declared type. - if (containsMatchingReference(reference, node)) { - if (!isReachableFlowNode(flow)) { - return unreachableNeverType; + // A matching dotted name might also be an expando property on a function *expression*, + // in which case we continue control flow analysis back to the function's declaration + if (isVariableDeclaration(node) && (isInJSFile(node) || isVarConst(node))) { + const init = getDeclaredExpandoInitializer(node); + if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) { + return getTypeAtFlowNode(flow.antecedent); } - // A matching dotted name might also be an expando property on a function *expression*, - // in which case we continue control flow analysis back to the function's declaration - if (isVariableDeclaration(node) && (isInJSFile(node) || isVarConst(node))) { - const init = getDeclaredExpandoInitializer(node); - if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) { - return getTypeAtFlowNode(flow.antecedent); - } - } - return declaredType; } - // for (const _ in ref) acts as a nonnull on ref - if (isVariableDeclaration(node) && node.parent.parent.kind === SyntaxKind.ForInStatement && isMatchingReference(reference, node.parent.parent.expression)) { - return getNonNullableTypeIfNeeded(finalizeEvolvingArrayType(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent)))); - } - // Assignment doesn't affect reference - return undefined; + return declaredType; + } + // for (const _ in ref) acts as a nonnull on ref + if (isVariableDeclaration(node) && node.parent.parent.kind === SyntaxKind.ForInStatement && isMatchingReference(reference, node.parent.parent.expression)) { + return getNonNullableTypeIfNeeded(finalizeEvolvingArrayType(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent)))); } + // Assignment doesn't affect reference + return undefined; + } - function narrowTypeByAssertion(type: Type, expr: Expression): Type { - const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); - if (node.kind === SyntaxKind.FalseKeyword) { - return unreachableNeverType; + function narrowTypeByAssertion(type: Type, expr: Expression): Type { + const node = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + if (node.kind === SyntaxKind.FalseKeyword) { + return unreachableNeverType; + } + if (node.kind === SyntaxKind.BinaryExpression) { + if ((node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + return narrowTypeByAssertion(narrowTypeByAssertion(type, (node as BinaryExpression).left), (node as BinaryExpression).right); } - if (node.kind === SyntaxKind.BinaryExpression) { - if ((node as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { - return narrowTypeByAssertion(narrowTypeByAssertion(type, (node as BinaryExpression).left), (node as BinaryExpression).right); - } - if ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken) { - return getUnionType([narrowTypeByAssertion(type, (node as BinaryExpression).left), narrowTypeByAssertion(type, (node as BinaryExpression).right)]); - } + if ((node as BinaryExpression).operatorToken.kind === SyntaxKind.BarBarToken) { + return getUnionType([narrowTypeByAssertion(type, (node as BinaryExpression).left), narrowTypeByAssertion(type, (node as BinaryExpression).right)]); } - return narrowType(type, node, /*assumeTrue*/ true); } - - function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined { - const signature = getEffectsSignature(flow.node); - if (signature) { - const predicate = getTypePredicateOfSignature(signature); - if (predicate && (predicate.kind === TypePredicateKind.AssertsThis || predicate.kind === TypePredicateKind.AssertsIdentifier)) { - const flowType = getTypeAtFlowNode(flow.antecedent); - const type = finalizeEvolvingArrayType(getTypeFromFlowType(flowType)); - const narrowedType = predicate.type ? narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) : - predicate.kind === TypePredicateKind.AssertsIdentifier && predicate.parameterIndex >= 0 && predicate.parameterIndex < flow.node.arguments.length ? narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]) : - type; - return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType)); - } - if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { - return unreachableNeverType; - } + return narrowType(type, node, /*assumeTrue*/ true); + } + + function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined { + const signature = getEffectsSignature(flow.node); + if (signature) { + const predicate = getTypePredicateOfSignature(signature); + if (predicate && (predicate.kind === TypePredicateKind.AssertsThis || predicate.kind === TypePredicateKind.AssertsIdentifier)) { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = finalizeEvolvingArrayType(getTypeFromFlowType(flowType)); + const narrowedType = predicate.type ? narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) : + predicate.kind === TypePredicateKind.AssertsIdentifier && predicate.parameterIndex >= 0 && predicate.parameterIndex < flow.node.arguments.length ? narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]) : + type; + return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType)); + } + if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { + return unreachableNeverType; } - return undefined; } + return undefined; + } - function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined { - if (declaredType === autoType || declaredType === autoArrayType) { - const node = flow.node; - const expr = node.kind === SyntaxKind.CallExpression ? - (node.expression as PropertyAccessExpression).expression : - (node.left as ElementAccessExpression).expression; - if (isMatchingReference(reference, getReferenceCandidate(expr))) { - const flowType = getTypeAtFlowNode(flow.antecedent); - const type = getTypeFromFlowType(flowType); - if (getObjectFlags(type) & ObjectFlags.EvolvingArray) { - let evolvedType = type as EvolvingArrayType; - if (node.kind === SyntaxKind.CallExpression) { - for (const arg of node.arguments) { - evolvedType = addEvolvingArrayElementType(evolvedType, arg); - } + function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined { + if (declaredType === autoType || declaredType === autoArrayType) { + const node = flow.node; + const expr = node.kind === SyntaxKind.CallExpression ? + (node.expression as PropertyAccessExpression).expression : + (node.left as ElementAccessExpression).expression; + if (isMatchingReference(reference, getReferenceCandidate(expr))) { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + if (getObjectFlags(type) & ObjectFlags.EvolvingArray) { + let evolvedType = type as EvolvingArrayType; + if (node.kind === SyntaxKind.CallExpression) { + for (const arg of node.arguments) { + evolvedType = addEvolvingArrayElementType(evolvedType, arg); } - else { - // We must get the context free expression type so as to not recur in an uncached fashion on the LHS (which causes exponential blowup in compile time) - const indexType = getContextFreeTypeOfExpression((node.left as ElementAccessExpression).argumentExpression); - if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { - evolvedType = addEvolvingArrayElementType(evolvedType, node.right); - } + } + else { + // We must get the context free expression type so as to not recur in an uncached fashion on the LHS (which causes exponential blowup in compile time) + const indexType = getContextFreeTypeOfExpression((node.left as ElementAccessExpression).argumentExpression); + if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { + evolvedType = addEvolvingArrayElementType(evolvedType, node.right); } - return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); } - return flowType; + return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); } + return flowType; } - return undefined; } + return undefined; + } - function getTypeAtFlowCondition(flow: FlowCondition): FlowType { - const flowType = getTypeAtFlowNode(flow.antecedent); - const type = getTypeFromFlowType(flowType); - if (type.flags & TypeFlags.Never) { - return flowType; + function getTypeAtFlowCondition(flow: FlowCondition): FlowType { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + if (type.flags & TypeFlags.Never) { + return flowType; + } + // If we have an antecedent type (meaning we're reachable in some way), we first + // attempt to narrow the antecedent type. If that produces the never type, and if + // the antecedent type is incomplete (i.e. a transient type in a loop), then we + // take the type guard as an indication that control *could* reach here once we + // have the complete type. We proceed by switching to the silent never type which + // doesn't report errors when operators are applied to it. Note that this is the + // *only* place a silent never type is ever generated. + const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0; + const nonEvolvingType = finalizeEvolvingArrayType(type); + const narrowedType = narrowType(nonEvolvingType, flow.node, assumeTrue); + if (narrowedType === nonEvolvingType) { + return flowType; + } + return createFlowType(narrowedType, isIncomplete(flowType)); + } + + function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { + const expr = flow.switchStatement.expression; + const flowType = getTypeAtFlowNode(flow.antecedent); + let type = getTypeFromFlowType(flowType); + if (isMatchingReference(reference, expr)) { + type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { + type = narrowTypeBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + } + else { + if (strictNullChecks) { + if (optionalChainContainsReference(expr, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, + t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, + t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); + } } - // If we have an antecedent type (meaning we're reachable in some way), we first - // attempt to narrow the antecedent type. If that produces the never type, and if - // the antecedent type is incomplete (i.e. a transient type in a loop), then we - // take the type guard as an indication that control *could* reach here once we - // have the complete type. We proceed by switching to the silent never type which - // doesn't report errors when operators are applied to it. Note that this is the - // *only* place a silent never type is ever generated. - const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0; - const nonEvolvingType = finalizeEvolvingArrayType(type); - const narrowedType = narrowType(nonEvolvingType, flow.node, assumeTrue); - if (narrowedType === nonEvolvingType) { - return flowType; + const access = getDiscriminantPropertyAccess(expr, type); + if (access) { + type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.switchStatement, flow.clauseStart, flow.clauseEnd); } - return createFlowType(narrowedType, isIncomplete(flowType)); } + return createFlowType(type, isIncomplete(flowType)); + } - function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { - const expr = flow.switchStatement.expression; - const flowType = getTypeAtFlowNode(flow.antecedent); - let type = getTypeFromFlowType(flowType); - if (isMatchingReference(reference, expr)) { - type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType { + const antecedentTypes: Type[] = []; + let subtypeReduction = false; + let seenIncomplete = false; + let bypassFlow: FlowSwitchClause | undefined; + for (const antecedent of flow.antecedents!) { + if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).clauseStart === (antecedent as FlowSwitchClause).clauseEnd) { + // The antecedent is the bypass branch of a potentially exhaustive switch statement. + bypassFlow = antecedent as FlowSwitchClause; + continue; } - else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { - type = narrowTypeBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + const flowType = getTypeAtFlowNode(antecedent); + const type = getTypeFromFlowType(flowType); + // If the type at a particular antecedent path is the declared type and the + // reference is known to always be assigned (i.e. when declared and initial types + // are the same), there is no reason to process more antecedents since the only + // possible outcome is subtypes that will be removed in the final union type anyway. + if (type === declaredType && declaredType === initialType) { + return type; } - else { - if (strictNullChecks) { - if (optionalChainContainsReference(expr, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, - t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); - } - else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, - t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); - } - } - const access = getDiscriminantPropertyAccess(expr, type); - if (access) { - type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.switchStatement, flow.clauseStart, flow.clauseEnd); - } + pushIfUnique(antecedentTypes, type); + // If an antecedent type is not a subset of the declared type, we need to perform + // subtype reduction. This happens when a "foreign" type is injected into the control + // flow using the instanceof operator or a user defined type predicate. + if (!isTypeSubsetOf(type, declaredType)) { + subtypeReduction = true; + } + if (isIncomplete(flowType)) { + seenIncomplete = true; } - return createFlowType(type, isIncomplete(flowType)); } - - function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType { - const antecedentTypes: Type[] = []; - let subtypeReduction = false; - let seenIncomplete = false; - let bypassFlow: FlowSwitchClause | undefined; - for (const antecedent of flow.antecedents!) { - if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).clauseStart === (antecedent as FlowSwitchClause).clauseEnd) { - // The antecedent is the bypass branch of a potentially exhaustive switch statement. - bypassFlow = antecedent as FlowSwitchClause; - continue; - } - const flowType = getTypeAtFlowNode(antecedent); - const type = getTypeFromFlowType(flowType); - // If the type at a particular antecedent path is the declared type and the - // reference is known to always be assigned (i.e. when declared and initial types - // are the same), there is no reason to process more antecedents since the only - // possible outcome is subtypes that will be removed in the final union type anyway. + if (bypassFlow) { + const flowType = getTypeAtFlowNode(bypassFlow); + const type = getTypeFromFlowType(flowType); + // If the bypass flow contributes a type we haven't seen yet and the switch statement + // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase + // the risk of circularities, we only want to perform them when they make a difference. + if (!contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) { if (type === declaredType && declaredType === initialType) { return type; } - pushIfUnique(antecedentTypes, type); - // If an antecedent type is not a subset of the declared type, we need to perform - // subtype reduction. This happens when a "foreign" type is injected into the control - // flow using the instanceof operator or a user defined type predicate. + antecedentTypes.push(type); if (!isTypeSubsetOf(type, declaredType)) { subtypeReduction = true; } @@ -25045,6332 +25270,6337 @@ namespace ts { seenIncomplete = true; } } - if (bypassFlow) { - const flowType = getTypeAtFlowNode(bypassFlow); - const type = getTypeFromFlowType(flowType); - // If the bypass flow contributes a type we haven't seen yet and the switch statement - // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase - // the risk of circularities, we only want to perform them when they make a difference. - if (!contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) { - if (type === declaredType && declaredType === initialType) { - return type; - } - antecedentTypes.push(type); - if (!isTypeSubsetOf(type, declaredType)) { - subtypeReduction = true; - } - if (isIncomplete(flowType)) { - seenIncomplete = true; - } - } - } - return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); } + return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); + } - function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType { - // If we have previously computed the control flow type for the reference at - // this flow loop junction, return the cached type. - const id = getFlowNodeId(flow); - const cache = flowLoopCaches[id] || (flowLoopCaches[id] = new Map()); - const key = getOrSetCacheKey(); - if (!key) { - // No cache key is generated when binding patterns are in unnarrowable situations - return declaredType; - } - const cached = cache.get(key); - if (cached) { - return cached; + function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType { + // If we have previously computed the control flow type for the reference at + // this flow loop junction, return the cached type. + const id = getFlowNodeId(flow); + const cache = flowLoopCaches[id] || (flowLoopCaches[id] = new Map()); + const key = getOrSetCacheKey(); + if (!key) { + // No cache key is generated when binding patterns are in unnarrowable situations + return declaredType; + } + const cached = cache.get(key); + if (cached) { + return cached; + } + // If this flow loop junction and reference are already being processed, return + // the union of the types computed for each branch so far, marked as incomplete. + // It is possible to see an empty array in cases where loops are nested and the + // back edge of the outer loop reaches an inner loop that is already being analyzed. + // In such cases we restart the analysis of the inner loop, which will then see + // a non-empty in-process array for the outer loop and eventually terminate because + // the first antecedent of a loop junction is always the non-looping control flow + // path that leads to the top. + for (let i = flowLoopStart; i < flowLoopCount; i++) { + if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) { + return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], UnionReduction.Literal), /*incomplete*/ true); + } + } + // Add the flow loop junction and reference to the in-process stack and analyze + // each antecedent code path. + const antecedentTypes: Type[] = []; + let subtypeReduction = false; + let firstAntecedentType: FlowType | undefined; + for (const antecedent of flow.antecedents!) { + let flowType; + if (!firstAntecedentType) { + // The first antecedent of a loop junction is always the non-looping control + // flow path that leads to the top. + flowType = firstAntecedentType = getTypeAtFlowNode(antecedent); } - // If this flow loop junction and reference are already being processed, return - // the union of the types computed for each branch so far, marked as incomplete. - // It is possible to see an empty array in cases where loops are nested and the - // back edge of the outer loop reaches an inner loop that is already being analyzed. - // In such cases we restart the analysis of the inner loop, which will then see - // a non-empty in-process array for the outer loop and eventually terminate because - // the first antecedent of a loop junction is always the non-looping control flow - // path that leads to the top. - for (let i = flowLoopStart; i < flowLoopCount; i++) { - if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) { - return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], UnionReduction.Literal), /*incomplete*/ true); - } - } - // Add the flow loop junction and reference to the in-process stack and analyze - // each antecedent code path. - const antecedentTypes: Type[] = []; - let subtypeReduction = false; - let firstAntecedentType: FlowType | undefined; - for (const antecedent of flow.antecedents!) { - let flowType; - if (!firstAntecedentType) { - // The first antecedent of a loop junction is always the non-looping control - // flow path that leads to the top. - flowType = firstAntecedentType = getTypeAtFlowNode(antecedent); - } - else { - // All but the first antecedent are the looping control flow paths that lead - // back to the loop junction. We track these on the flow loop stack. - flowLoopNodes[flowLoopCount] = flow; - flowLoopKeys[flowLoopCount] = key; - flowLoopTypes[flowLoopCount] = antecedentTypes; - flowLoopCount++; - const saveFlowTypeCache = flowTypeCache; - flowTypeCache = undefined; - flowType = getTypeAtFlowNode(antecedent); - flowTypeCache = saveFlowTypeCache; - flowLoopCount--; - // If we see a value appear in the cache it is a sign that control flow analysis - // was restarted and completed by checkExpressionCached. We can simply pick up - // the resulting type and bail out. - const cached = cache.get(key); - if (cached) { - return cached; - } - } - const type = getTypeFromFlowType(flowType); - pushIfUnique(antecedentTypes, type); - // If an antecedent type is not a subset of the declared type, we need to perform - // subtype reduction. This happens when a "foreign" type is injected into the control - // flow using the instanceof operator or a user defined type predicate. - if (!isTypeSubsetOf(type, declaredType)) { - subtypeReduction = true; - } - // If the type at a particular antecedent path is the declared type there is no - // reason to process more antecedents since the only possible outcome is subtypes - // that will be removed in the final union type anyway. - if (type === declaredType) { - break; + else { + // All but the first antecedent are the looping control flow paths that lead + // back to the loop junction. We track these on the flow loop stack. + flowLoopNodes[flowLoopCount] = flow; + flowLoopKeys[flowLoopCount] = key; + flowLoopTypes[flowLoopCount] = antecedentTypes; + flowLoopCount++; + const saveFlowTypeCache = flowTypeCache; + flowTypeCache = undefined; + flowType = getTypeAtFlowNode(antecedent); + flowTypeCache = saveFlowTypeCache; + flowLoopCount--; + // If we see a value appear in the cache it is a sign that control flow analysis + // was restarted and completed by checkExpressionCached. We can simply pick up + // the resulting type and bail out. + const cached = cache.get(key); + if (cached) { + return cached; } } - // The result is incomplete if the first antecedent (the non-looping control flow path) - // is incomplete. - const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal); - if (isIncomplete(firstAntecedentType!)) { - return createFlowType(result, /*incomplete*/ true); + const type = getTypeFromFlowType(flowType); + pushIfUnique(antecedentTypes, type); + // If an antecedent type is not a subset of the declared type, we need to perform + // subtype reduction. This happens when a "foreign" type is injected into the control + // flow using the instanceof operator or a user defined type predicate. + if (!isTypeSubsetOf(type, declaredType)) { + subtypeReduction = true; + } + // If the type at a particular antecedent path is the declared type there is no + // reason to process more antecedents since the only possible outcome is subtypes + // that will be removed in the final union type anyway. + if (type === declaredType) { + break; } - cache.set(key, result); - return result; } + // The result is incomplete if the first antecedent (the non-looping control flow path) + // is incomplete. + const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal); + if (isIncomplete(firstAntecedentType!)) { + return createFlowType(result, /*incomplete*/ true); + } + cache.set(key, result); + return result; + } - // At flow control branch or loop junctions, if the type along every antecedent code path - // is an evolving array type, we construct a combined evolving array type. Otherwise we - // finalize all evolving array types. - function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: UnionReduction) { - if (isEvolvingArrayTypeList(types)) { - return getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))); - } - const result = recombineUnknownType(getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction)); - if (result !== declaredType && result.flags & declaredType.flags & TypeFlags.Union && arraysEqual((result as UnionType).types, (declaredType as UnionType).types)) { - return declaredType; - } - return result; + // At flow control branch or loop junctions, if the type along every antecedent code path + // is an evolving array type, we construct a combined evolving array type. Otherwise we + // finalize all evolving array types. + function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: UnionReduction) { + if (isEvolvingArrayTypeList(types)) { + return getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))); + } + const result = recombineUnknownType(getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction)); + if (result !== declaredType && result.flags & declaredType.flags & TypeFlags.Union && arraysEqual((result as UnionType).types, (declaredType as UnionType).types)) { + return declaredType; } + return result; + } - function getCandidateDiscriminantPropertyAccess(expr: Expression) { - if (isBindingPattern(reference) || isFunctionExpressionOrArrowFunction(reference) || isObjectLiteralMethod(reference)) { - // When the reference is a binding pattern or function or arrow expression, we are narrowing a pesudo-reference in - // getNarrowedTypeOfSymbol. An identifier for a destructuring variable declared in the same binding pattern or - // parameter declared in the same parameter list is a candidate. - if (isIdentifier(expr)) { - const symbol = getResolvedSymbol(expr); - const declaration = symbol.valueDeclaration; - if (declaration && (isBindingElement(declaration) || isParameter(declaration)) && reference === declaration.parent && !declaration.initializer && !declaration.dotDotDotToken) { - return declaration; - } + function getCandidateDiscriminantPropertyAccess(expr: Expression) { + if (isBindingPattern(reference) || isFunctionExpressionOrArrowFunction(reference) || isObjectLiteralMethod(reference)) { + // When the reference is a binding pattern or function or arrow expression, we are narrowing a pesudo-reference in + // getNarrowedTypeOfSymbol. An identifier for a destructuring variable declared in the same binding pattern or + // parameter declared in the same parameter list is a candidate. + if (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + const declaration = symbol.valueDeclaration; + if (declaration && (isBindingElement(declaration) || isParameter(declaration)) && reference === declaration.parent && !declaration.initializer && !declaration.dotDotDotToken) { + return declaration; } } - else if (isAccessExpression(expr)) { - // An access expression is a candidate if the reference matches the left hand expression. - if (isMatchingReference(reference, expr.expression)) { - return expr; - } + } + else if (isAccessExpression(expr)) { + // An access expression is a candidate if the reference matches the left hand expression. + if (isMatchingReference(reference, expr.expression)) { + return expr; } - else if (isIdentifier(expr)) { - const symbol = getResolvedSymbol(expr); - if (isConstVariable(symbol)) { - const declaration = symbol.valueDeclaration!; - // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind' - if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) && - isMatchingReference(reference, declaration.initializer.expression)) { - return declaration.initializer; - } - // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind' - if (isBindingElement(declaration) && !declaration.initializer) { - const parent = declaration.parent.parent; - if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) && - isMatchingReference(reference, parent.initializer)) { - return declaration; - } + } + else if (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + if (isConstVariable(symbol)) { + const declaration = symbol.valueDeclaration!; + // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind' + if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) && + isMatchingReference(reference, declaration.initializer.expression)) { + return declaration.initializer; + } + // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind' + if (isBindingElement(declaration) && !declaration.initializer) { + const parent = declaration.parent.parent; + if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) && + isMatchingReference(reference, parent.initializer)) { + return declaration; } } } - return undefined; } + return undefined; + } - function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { - const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType; - if (type.flags & TypeFlags.Union) { - const access = getCandidateDiscriminantPropertyAccess(expr); - if (access) { - const name = getAccessedPropertyName(access); - if (name && isDiscriminantProperty(type, name)) { - return access; - } + function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { + const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType; + if (type.flags & TypeFlags.Union) { + const access = getCandidateDiscriminantPropertyAccess(expr); + if (access) { + const name = getAccessedPropertyName(access); + if (name && isDiscriminantProperty(type, name)) { + return access; } } - return undefined; } + return undefined; + } - function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, narrowType: (t: Type) => Type): Type { - const propName = getAccessedPropertyName(access); - if (propName === undefined) { - return type; - } - const removeNullable = strictNullChecks && isOptionalChain(access) && maybeTypeOfKind(type, TypeFlags.Nullable); - let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName); - if (!propType) { - return type; - } - propType = removeNullable ? getOptionalType(propType) : propType; - const narrowedPropType = narrowType(propType); - return filterType(type, t => { - const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName); - return !(discriminantType.flags & TypeFlags.Never) && !(narrowedPropType.flags & TypeFlags.Never) && areTypesComparable(narrowedPropType, discriminantType); - }); + function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, narrowType: (t: Type) => Type): Type { + const propName = getAccessedPropertyName(access); + if (propName === undefined) { + return type; } - - function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { - if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && type.flags & TypeFlags.Union) { - const keyPropertyName = getKeyPropertyName(type as UnionType); - if (keyPropertyName && keyPropertyName === getAccessedPropertyName(access)) { - const candidate = getConstituentTypeForKeyType(type as UnionType, getTypeOfExpression(value)); - if (candidate) { - return operator === (assumeTrue ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken) ? candidate : - isUnitType(getTypeOfPropertyOfType(candidate, keyPropertyName) || unknownType) ? removeType(type, candidate) : - type; - } - } - } - return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); + const removeNullable = strictNullChecks && isOptionalChain(access) && maybeTypeOfKind(type, TypeFlags.Nullable); + let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName); + if (!propType) { + return type; } + propType = removeNullable ? getOptionalType(propType) : propType; + const narrowedPropType = narrowType(propType); + return filterType(type, t => { + const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName); + return !(discriminantType.flags & TypeFlags.Never) && !(narrowedPropType.flags & TypeFlags.Never) && areTypesComparable(narrowedPropType, discriminantType); + }); + } - function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { - if (clauseStart < clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { - const clauseTypes = getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd); - const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); - if (candidate !== unknownType) { - return candidate; + function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { + if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && type.flags & TypeFlags.Union) { + const keyPropertyName = getKeyPropertyName(type as UnionType); + if (keyPropertyName && keyPropertyName === getAccessedPropertyName(access)) { + const candidate = getConstituentTypeForKeyType(type as UnionType, getTypeOfExpression(value)); + if (candidate) { + return operator === (assumeTrue ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken) ? candidate : + isUnitType(getTypeOfPropertyOfType(candidate, keyPropertyName) || unknownType) ? removeType(type, candidate) : + type; } } - return narrowTypeByDiscriminant(type, access, t => narrowTypeBySwitchOnDiscriminant(t, switchStatement, clauseStart, clauseEnd)); } + return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); + } - function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { - if (isMatchingReference(reference, expr)) { - return getAdjustedTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); - } - if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) { - type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); - } - const access = getDiscriminantPropertyAccess(expr, type); - if (access) { - return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); + function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { + if (clauseStart < clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { + const clauseTypes = getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd); + const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); + if (candidate !== unknownType) { + return candidate; } - return type; } + return narrowTypeByDiscriminant(type, access, t => narrowTypeBySwitchOnDiscriminant(t, switchStatement, clauseStart, clauseEnd)); + } - function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { - const prop = getPropertyOfType(type, propName); - return prop ? - !!(prop.flags & SymbolFlags.Optional) || assumeTrue : - !!getApplicableIndexInfoForName(type, propName) || !assumeTrue; + function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { + if (isMatchingReference(reference, expr)) { + return getAdjustedTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); } + if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) { + type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } + const access = getDiscriminantPropertyAccess(expr, type); + if (access) { + return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); + } + return type; + } - function narrowTypeByInKeyword(type: Type, nameType: StringLiteralType | NumberLiteralType | UniqueESSymbolType, assumeTrue: boolean) { - const name = getPropertyNameFromType(nameType); - const isKnownProperty = someType(type, t => isTypePresencePossible(t, name, /*assumeTrue*/ true)); - if (isKnownProperty) { - // If the check is for a known property (i.e. a property declared in some constituent of - // the target type), we filter the target type by presence of absence of the property. - return filterType(type, t => isTypePresencePossible(t, name, assumeTrue)); - } - if (assumeTrue) { - // If the check is for an unknown property, we intersect the target type with `Record`, - // where X is the name of the property. - const recordSymbol = getGlobalRecordSymbol(); - if (recordSymbol) { - return getIntersectionType([type, getTypeAliasInstantiation(recordSymbol, [nameType, unknownType])]); - } + function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { + const prop = getPropertyOfType(type, propName); + return prop ? + !!(prop.flags & SymbolFlags.Optional) || assumeTrue : + !!getApplicableIndexInfoForName(type, propName) || !assumeTrue; + } + + function narrowTypeByInKeyword(type: Type, nameType: StringLiteralType | NumberLiteralType | UniqueESSymbolType, assumeTrue: boolean) { + const name = getPropertyNameFromType(nameType); + const isKnownProperty = someType(type, t => isTypePresencePossible(t, name, /*assumeTrue*/ true)); + if (isKnownProperty) { + // If the check is for a known property (i.e. a property declared in some constituent of + // the target type), we filter the target type by presence of absence of the property. + return filterType(type, t => isTypePresencePossible(t, name, assumeTrue)); + } + if (assumeTrue) { + // If the check is for an unknown property, we intersect the target type with `Record`, + // where X is the name of the property. + const recordSymbol = getGlobalRecordSymbol(); + if (recordSymbol) { + return getIntersectionType([type, getTypeAliasInstantiation(recordSymbol, [nameType, unknownType])]); } - return type; } + return type; + } - function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - switch (expr.operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.BarBarEqualsToken: - case SyntaxKind.AmpersandAmpersandEqualsToken: - case SyntaxKind.QuestionQuestionEqualsToken: - return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue); - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - const operator = expr.operatorToken.kind; - const left = getReferenceCandidate(expr.left); - const right = getReferenceCandidate(expr.right); - if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) { - return narrowTypeByTypeof(type, left as TypeOfExpression, operator, right, assumeTrue); - } - if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) { - return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue); - } - if (isMatchingReference(reference, left)) { - return narrowTypeByEquality(type, operator, right, assumeTrue); - } - if (isMatchingReference(reference, right)) { - return narrowTypeByEquality(type, operator, left, assumeTrue); - } - if (strictNullChecks) { - if (optionalChainContainsReference(left, reference)) { - type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue); - } - else if (optionalChainContainsReference(right, reference)) { - type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue); - } - } - const leftAccess = getDiscriminantPropertyAccess(left, type); - if (leftAccess) { - return narrowTypeByDiscriminantProperty(type, leftAccess, operator, right, assumeTrue); - } - const rightAccess = getDiscriminantPropertyAccess(right, type); - if (rightAccess) { - return narrowTypeByDiscriminantProperty(type, rightAccess, operator, left, assumeTrue); + function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + switch (expr.operatorToken.kind) { + case SyntaxKind.EqualsToken: + case SyntaxKind.BarBarEqualsToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: + case SyntaxKind.QuestionQuestionEqualsToken: + return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue); + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + const operator = expr.operatorToken.kind; + const left = getReferenceCandidate(expr.left); + const right = getReferenceCandidate(expr.right); + if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) { + return narrowTypeByTypeof(type, left as TypeOfExpression, operator, right, assumeTrue); + } + if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) { + return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue); + } + if (isMatchingReference(reference, left)) { + return narrowTypeByEquality(type, operator, right, assumeTrue); + } + if (isMatchingReference(reference, right)) { + return narrowTypeByEquality(type, operator, left, assumeTrue); + } + if (strictNullChecks) { + if (optionalChainContainsReference(left, reference)) { + type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue); } - if (isMatchingConstructorReference(left)) { - return narrowTypeByConstructor(type, operator, right, assumeTrue); + else if (optionalChainContainsReference(right, reference)) { + type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue); } - if (isMatchingConstructorReference(right)) { - return narrowTypeByConstructor(type, operator, left, assumeTrue); + } + const leftAccess = getDiscriminantPropertyAccess(left, type); + if (leftAccess) { + return narrowTypeByDiscriminantProperty(type, leftAccess, operator, right, assumeTrue); + } + const rightAccess = getDiscriminantPropertyAccess(right, type); + if (rightAccess) { + return narrowTypeByDiscriminantProperty(type, rightAccess, operator, left, assumeTrue); + } + if (isMatchingConstructorReference(left)) { + return narrowTypeByConstructor(type, operator, right, assumeTrue); + } + if (isMatchingConstructorReference(right)) { + return narrowTypeByConstructor(type, operator, left, assumeTrue); + } + break; + case SyntaxKind.InstanceOfKeyword: + return narrowTypeByInstanceof(type, expr, assumeTrue); + case SyntaxKind.InKeyword: + if (isPrivateIdentifier(expr.left)) { + return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue); + } + const target = getReferenceCandidate(expr.right); + const leftType = getTypeOfExpression(expr.left); + if (leftType.flags & TypeFlags.StringOrNumberLiteralOrUnique) { + if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target) && + getAccessedPropertyName(reference) === getPropertyNameFromType(leftType as StringLiteralType | NumberLiteralType | UniqueESSymbolType)) { + return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); } - break; - case SyntaxKind.InstanceOfKeyword: - return narrowTypeByInstanceof(type, expr, assumeTrue); - case SyntaxKind.InKeyword: - if (isPrivateIdentifier(expr.left)) { - return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue); - } - const target = getReferenceCandidate(expr.right); - const leftType = getTypeOfExpression(expr.left); - if (leftType.flags & TypeFlags.StringOrNumberLiteralOrUnique) { - if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target) && - getAccessedPropertyName(reference) === getPropertyNameFromType(leftType as StringLiteralType | NumberLiteralType | UniqueESSymbolType)) { - return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); - } - if (isMatchingReference(reference, target)) { - return narrowTypeByInKeyword(type, leftType as StringLiteralType | NumberLiteralType | UniqueESSymbolType, assumeTrue); - } + if (isMatchingReference(reference, target)) { + return narrowTypeByInKeyword(type, leftType as StringLiteralType | NumberLiteralType | UniqueESSymbolType, assumeTrue); } - break; - case SyntaxKind.CommaToken: - return narrowType(type, expr.right, assumeTrue); - // Ordinarily we won't see && and || expressions in control flow analysis because the Binder breaks those - // expressions down to individual conditional control flows. However, we may encounter them when analyzing - // aliased conditional expressions. - case SyntaxKind.AmpersandAmpersandToken: - return assumeTrue ? - narrowType(narrowType(type, expr.left, /*assumeTrue*/ true), expr.right, /*assumeTrue*/ true) : - getUnionType([narrowType(type, expr.left, /*assumeTrue*/ false), narrowType(type, expr.right, /*assumeTrue*/ false)]); - case SyntaxKind.BarBarToken: - return assumeTrue ? - getUnionType([narrowType(type, expr.left, /*assumeTrue*/ true), narrowType(type, expr.right, /*assumeTrue*/ true)]) : - narrowType(narrowType(type, expr.left, /*assumeTrue*/ false), expr.right, /*assumeTrue*/ false); - } - return type; + } + break; + case SyntaxKind.CommaToken: + return narrowType(type, expr.right, assumeTrue); + // Ordinarily we won't see && and || expressions in control flow analysis because the Binder breaks those + // expressions down to individual conditional control flows. However, we may encounter them when analyzing + // aliased conditional expressions. + case SyntaxKind.AmpersandAmpersandToken: + return assumeTrue ? + narrowType(narrowType(type, expr.left, /*assumeTrue*/ true), expr.right, /*assumeTrue*/ true) : + getUnionType([narrowType(type, expr.left, /*assumeTrue*/ false), narrowType(type, expr.right, /*assumeTrue*/ false)]); + case SyntaxKind.BarBarToken: + return assumeTrue ? + getUnionType([narrowType(type, expr.left, /*assumeTrue*/ true), narrowType(type, expr.right, /*assumeTrue*/ true)]) : + narrowType(narrowType(type, expr.left, /*assumeTrue*/ false), expr.right, /*assumeTrue*/ false); } + return type; + } - function narrowTypeByPrivateIdentifierInInExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - const target = getReferenceCandidate(expr.right); - if (!isMatchingReference(reference, target)) { - return type; - } + function narrowTypeByPrivateIdentifierInInExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + const target = getReferenceCandidate(expr.right); + if (!isMatchingReference(reference, target)) { + return type; + } - Debug.assertNode(expr.left, isPrivateIdentifier); - const symbol = getSymbolForPrivateIdentifierExpression(expr.left); - if (symbol === undefined) { - return type; - } - const classSymbol = symbol.parent!; - const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration")) - ? getTypeOfSymbol(classSymbol) as InterfaceType - : getDeclaredTypeOfSymbol(classSymbol); - return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true); - } - - function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { - // We are in a branch of obj?.foo === value (or any one of the other equality operators). We narrow obj as follows: - // When operator is === and type of value excludes undefined, null and undefined is removed from type of obj in true branch. - // When operator is !== and type of value excludes undefined, null and undefined is removed from type of obj in false branch. - // When operator is == and type of value excludes null and undefined, null and undefined is removed from type of obj in true branch. - // When operator is != and type of value excludes null and undefined, null and undefined is removed from type of obj in false branch. - // When operator is === and type of value is undefined, null and undefined is removed from type of obj in false branch. - // When operator is !== and type of value is undefined, null and undefined is removed from type of obj in true branch. - // When operator is == and type of value is null or undefined, null and undefined is removed from type of obj in false branch. - // When operator is != and type of value is null or undefined, null and undefined is removed from type of obj in true branch. - const equalsOperator = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; - const nullableFlags = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? TypeFlags.Nullable : TypeFlags.Undefined; - const valueType = getTypeOfExpression(value); - // Note that we include any and unknown in the exclusion test because their domain includes null and undefined. - const removeNullable = equalsOperator !== assumeTrue && everyType(valueType, t => !!(t.flags & nullableFlags)) || - equalsOperator === assumeTrue && everyType(valueType, t => !(t.flags & (TypeFlags.AnyOrUnknown | nullableFlags))); - return removeNullable ? getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; - } - - function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { - if (type.flags & TypeFlags.Any) { + Debug.assertNode(expr.left, isPrivateIdentifier); + const symbol = getSymbolForPrivateIdentifierExpression(expr.left); + if (symbol === undefined) { + return type; + } + const classSymbol = symbol.parent!; + const targetType = hasStaticModifier(Debug.checkDefined(symbol.valueDeclaration, "should always have a declaration")) + ? getTypeOfSymbol(classSymbol) as InterfaceType + : getDeclaredTypeOfSymbol(classSymbol); + return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true); + } + + function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { + // We are in a branch of obj?.foo === value (or any one of the other equality operators). We narrow obj as follows: + // When operator is === and type of value excludes undefined, null and undefined is removed from type of obj in true branch. + // When operator is !== and type of value excludes undefined, null and undefined is removed from type of obj in false branch. + // When operator is == and type of value excludes null and undefined, null and undefined is removed from type of obj in true branch. + // When operator is != and type of value excludes null and undefined, null and undefined is removed from type of obj in false branch. + // When operator is === and type of value is undefined, null and undefined is removed from type of obj in false branch. + // When operator is !== and type of value is undefined, null and undefined is removed from type of obj in true branch. + // When operator is == and type of value is null or undefined, null and undefined is removed from type of obj in false branch. + // When operator is != and type of value is null or undefined, null and undefined is removed from type of obj in true branch. + const equalsOperator = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; + const nullableFlags = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? TypeFlags.Nullable : TypeFlags.Undefined; + const valueType = getTypeOfExpression(value); + // Note that we include any and unknown in the exclusion test because their domain includes null and undefined. + const removeNullable = equalsOperator !== assumeTrue && everyType(valueType, t => !!(t.flags & nullableFlags)) || + equalsOperator === assumeTrue && everyType(valueType, t => !(t.flags & (TypeFlags.AnyOrUnknown | nullableFlags))); + return removeNullable ? getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } + + function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { + if (type.flags & TypeFlags.Any) { + return type; + } + if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + assumeTrue = !assumeTrue; + } + const valueType = getTypeOfExpression(value); + const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; + if (valueType.flags & TypeFlags.Nullable) { + if (!strictNullChecks) { return type; } - if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { - assumeTrue = !assumeTrue; - } - const valueType = getTypeOfExpression(value); - const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; - if (valueType.flags & TypeFlags.Nullable) { - if (!strictNullChecks) { - return type; + const facts = doubleEquals ? + assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull : + valueType.flags & TypeFlags.Null ? + assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull : + assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; + return getAdjustedTypeWithFacts(type, facts); + } + if (assumeTrue) { + if (!doubleEquals && (type.flags & TypeFlags.Unknown || someType(type, isEmptyAnonymousObjectType))) { + if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) || isEmptyAnonymousObjectType(valueType)) { + return valueType; } - const facts = doubleEquals ? - assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull : - valueType.flags & TypeFlags.Null ? - assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull : - assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; - return getAdjustedTypeWithFacts(type, facts); - } - if (assumeTrue) { - if (!doubleEquals && (type.flags & TypeFlags.Unknown || someType(type, isEmptyAnonymousObjectType))) { - if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) || isEmptyAnonymousObjectType(valueType)) { - return valueType; - } - if (valueType.flags & TypeFlags.Object) { - return nonPrimitiveType; - } + if (valueType.flags & TypeFlags.Object) { + return nonPrimitiveType; } - const filteredType = filterType(type, t => areTypesComparable(t, valueType) || doubleEquals && isCoercibleUnderDoubleEquals(t, valueType)); - return replacePrimitivesWithLiterals(filteredType, valueType); } - if (isUnitType(valueType)) { - return filterType(type, t => !(isUnitLikeType(t) && areTypesComparable(t, valueType))); + const filteredType = filterType(type, t => areTypesComparable(t, valueType) || doubleEquals && isCoercibleUnderDoubleEquals(t, valueType)); + return replacePrimitivesWithLiterals(filteredType, valueType); + } + if (isUnitType(valueType)) { + return filterType(type, t => !(isUnitLikeType(t) && areTypesComparable(t, valueType))); + } + return type; + } + + function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { + // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands + if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + assumeTrue = !assumeTrue; + } + const target = getReferenceCandidate(typeOfExpr.expression); + if (!isMatchingReference(reference, target)) { + const propertyAccess = getDiscriminantPropertyAccess(typeOfExpr.expression, type); + if (propertyAccess) { + return narrowTypeByDiscriminant(type, propertyAccess, t => narrowTypeByLiteralExpression(t, literal, assumeTrue)); + } + if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { + return getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } return type; } + return narrowTypeByLiteralExpression(type, literal, assumeTrue); + } - function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { - // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands - if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { - assumeTrue = !assumeTrue; - } - const target = getReferenceCandidate(typeOfExpr.expression); - if (!isMatchingReference(reference, target)) { - const propertyAccess = getDiscriminantPropertyAccess(typeOfExpr.expression, type); - if (propertyAccess) { - return narrowTypeByDiscriminant(type, propertyAccess, t => narrowTypeByLiteralExpression(t, literal, assumeTrue)); + function narrowTypeByLiteralExpression(type: Type, literal: LiteralExpression, assumeTrue: boolean) { + return assumeTrue ? + narrowTypeByTypeName(type, literal.text) : + getTypeWithFacts(type, typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject); + } + + function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) { + const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck); + return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } + + function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { + // We only narrow if all case expressions specify + // values with unit types, except for the case where + // `type` is unknown. In this instance we map object + // types to the nonPrimitive type and narrow with that. + const switchTypes = getSwitchClauseTypes(switchStatement); + if (!switchTypes.length) { + return type; + } + const clauseTypes = switchTypes.slice(clauseStart, clauseEnd); + const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType); + if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) { + let groundClauseTypes: Type[] | undefined; + for (let i = 0; i < clauseTypes.length; i += 1) { + const t = clauseTypes[i]; + if (t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { + if (groundClauseTypes !== undefined) { + groundClauseTypes.push(t); + } + } + else if (t.flags & TypeFlags.Object) { + if (groundClauseTypes === undefined) { + groundClauseTypes = clauseTypes.slice(0, i); + } + groundClauseTypes.push(nonPrimitiveType); } - if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { - return getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + else { + return type; } - return type; } - return narrowTypeByLiteralExpression(type, literal, assumeTrue); + return getUnionType(groundClauseTypes === undefined ? clauseTypes : groundClauseTypes); + } + const discriminantType = getUnionType(clauseTypes); + const caseType = + discriminantType.flags & TypeFlags.Never ? neverType : + replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType); + if (!hasDefaultClause) { + return caseType; + } + const defaultType = filterType(type, t => !(isUnitLikeType(t) && contains(switchTypes, getRegularTypeOfLiteralType(extractUnitType(t))))); + return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]); + } + + function narrowTypeByTypeName(type: Type, typeName: string) { + switch (typeName) { + case "string": return narrowTypeByTypeFacts(type, stringType, TypeFacts.TypeofEQString); + case "number": return narrowTypeByTypeFacts(type, numberType, TypeFacts.TypeofEQNumber); + case "bigint": return narrowTypeByTypeFacts(type, bigintType, TypeFacts.TypeofEQBigInt); + case "boolean": return narrowTypeByTypeFacts(type, booleanType, TypeFacts.TypeofEQBoolean); + case "symbol": return narrowTypeByTypeFacts(type, esSymbolType, TypeFacts.TypeofEQSymbol); + case "object": return type.flags & TypeFlags.Any ? type : getUnionType([narrowTypeByTypeFacts(type, nonPrimitiveType, TypeFacts.TypeofEQObject), narrowTypeByTypeFacts(type, nullType, TypeFacts.EQNull)]); + case "function": return type.flags & TypeFlags.Any ? type : narrowTypeByTypeFacts(type, globalFunctionType, TypeFacts.TypeofEQFunction); + case "undefined": return narrowTypeByTypeFacts(type, undefinedType, TypeFacts.EQUndefined); + } + return narrowTypeByTypeFacts(type, nonPrimitiveType, TypeFacts.TypeofEQHostObject); + } + + function narrowTypeByTypeFacts(type: Type, impliedType: Type, facts: TypeFacts) { + return mapType(type, t => + // We first check if a constituent is a subtype of the implied type. If so, we either keep or eliminate + // the constituent based on its type facts. We use the strict subtype relation because it treats `object` + // as a subtype of `{}`, and we need the type facts check because function types are subtypes of `object`, + // but are classified as "function" according to `typeof`. + isTypeRelatedTo(t, impliedType, strictSubtypeRelation) ? getTypeFacts(t) & facts ? t : neverType : + // We next check if the consituent is a supertype of the implied type. If so, we substitute the implied + // type. This handles top types like `unknown` and `{}`, and supertypes like `{ toString(): string }`. + isTypeSubtypeOf(impliedType, t) ? impliedType : + // Neither the constituent nor the implied type is a subtype of the other, however their domains may still + // overlap. For example, an unconstrained type parameter and type `string`. If the type facts indicate + // possible overlap, we form an intersection. Otherwise, we eliminate the constituent. + getTypeFacts(t) & facts ? getIntersectionType([t, impliedType]) : + neverType); + } + + function narrowTypeBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type { + const witnesses = getSwitchClauseTypeOfWitnesses(switchStatement); + if (!witnesses) { + return type; + } + // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause. + const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause); + const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex < clauseEnd); + if (hasDefaultClause) { + // In the default clause we filter constituents down to those that are not-equal to all handled cases. + const notEqualFacts = getNotEqualFactsFromTypeofSwitch(clauseStart, clauseEnd, witnesses); + return filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts); } + // In the non-default cause we create a union of the type narrowed by each of the listed cases. + const clauseWitnesses = witnesses.slice(clauseStart, clauseEnd); + return getUnionType(map(clauseWitnesses, text => text ? narrowTypeByTypeName(type, text) : neverType)); + } + + function isMatchingConstructorReference(expr: Expression) { + return (isPropertyAccessExpression(expr) && idText(expr.name) === "constructor" || + isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor") && + isMatchingReference(reference, expr.expression); + } - function narrowTypeByLiteralExpression(type: Type, literal: LiteralExpression, assumeTrue: boolean) { - return assumeTrue ? - narrowTypeByTypeName(type, literal.text) : - getTypeWithFacts(type, typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject); + function narrowTypeByConstructor(type: Type, operator: SyntaxKind, identifier: Expression, assumeTrue: boolean): Type { + // Do not narrow when checking inequality. + if (assumeTrue ? (operator !== SyntaxKind.EqualsEqualsToken && operator !== SyntaxKind.EqualsEqualsEqualsToken) : (operator !== SyntaxKind.ExclamationEqualsToken && operator !== SyntaxKind.ExclamationEqualsEqualsToken)) { + return type; } - function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) { - const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck); - return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + // Get the type of the constructor identifier expression, if it is not a function then do not narrow. + const identifierType = getTypeOfExpression(identifier); + if (!isFunctionType(identifierType) && !isConstructorType(identifierType)) { + return type; } - function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { - // We only narrow if all case expressions specify - // values with unit types, except for the case where - // `type` is unknown. In this instance we map object - // types to the nonPrimitive type and narrow with that. - const switchTypes = getSwitchClauseTypes(switchStatement); - if (!switchTypes.length) { - return type; - } - const clauseTypes = switchTypes.slice(clauseStart, clauseEnd); - const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType); - if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) { - let groundClauseTypes: Type[] | undefined; - for (let i = 0; i < clauseTypes.length; i += 1) { - const t = clauseTypes[i]; - if (t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { - if (groundClauseTypes !== undefined) { - groundClauseTypes.push(t); - } - } - else if (t.flags & TypeFlags.Object) { - if (groundClauseTypes === undefined) { - groundClauseTypes = clauseTypes.slice(0, i); - } - groundClauseTypes.push(nonPrimitiveType); - } - else { - return type; - } - } - return getUnionType(groundClauseTypes === undefined ? clauseTypes : groundClauseTypes); - } - const discriminantType = getUnionType(clauseTypes); - const caseType = - discriminantType.flags & TypeFlags.Never ? neverType : - replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType); - if (!hasDefaultClause) { - return caseType; - } - const defaultType = filterType(type, t => !(isUnitLikeType(t) && contains(switchTypes, getRegularTypeOfLiteralType(extractUnitType(t))))); - return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]); - } - - function narrowTypeByTypeName(type: Type, typeName: string) { - switch (typeName) { - case "string": return narrowTypeByTypeFacts(type, stringType, TypeFacts.TypeofEQString); - case "number": return narrowTypeByTypeFacts(type, numberType, TypeFacts.TypeofEQNumber); - case "bigint": return narrowTypeByTypeFacts(type, bigintType, TypeFacts.TypeofEQBigInt); - case "boolean": return narrowTypeByTypeFacts(type, booleanType, TypeFacts.TypeofEQBoolean); - case "symbol": return narrowTypeByTypeFacts(type, esSymbolType, TypeFacts.TypeofEQSymbol); - case "object": return type.flags & TypeFlags.Any ? type : getUnionType([narrowTypeByTypeFacts(type, nonPrimitiveType, TypeFacts.TypeofEQObject), narrowTypeByTypeFacts(type, nullType, TypeFacts.EQNull)]); - case "function": return type.flags & TypeFlags.Any ? type : narrowTypeByTypeFacts(type, globalFunctionType, TypeFacts.TypeofEQFunction); - case "undefined": return narrowTypeByTypeFacts(type, undefinedType, TypeFacts.EQUndefined); - } - return narrowTypeByTypeFacts(type, nonPrimitiveType, TypeFacts.TypeofEQHostObject); - } - - function narrowTypeByTypeFacts(type: Type, impliedType: Type, facts: TypeFacts) { - return mapType(type, t => - // We first check if a constituent is a subtype of the implied type. If so, we either keep or eliminate - // the constituent based on its type facts. We use the strict subtype relation because it treats `object` - // as a subtype of `{}`, and we need the type facts check because function types are subtypes of `object`, - // but are classified as "function" according to `typeof`. - isTypeRelatedTo(t, impliedType, strictSubtypeRelation) ? getTypeFacts(t) & facts ? t : neverType : - // We next check if the consituent is a supertype of the implied type. If so, we substitute the implied - // type. This handles top types like `unknown` and `{}`, and supertypes like `{ toString(): string }`. - isTypeSubtypeOf(impliedType, t) ? impliedType : - // Neither the constituent nor the implied type is a subtype of the other, however their domains may still - // overlap. For example, an unconstrained type parameter and type `string`. If the type facts indicate - // possible overlap, we form an intersection. Otherwise, we eliminate the constituent. - getTypeFacts(t) & facts ? getIntersectionType([t, impliedType]) : - neverType); - } - - function narrowTypeBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type { - const witnesses = getSwitchClauseTypeOfWitnesses(switchStatement); - if (!witnesses) { - return type; - } - // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause. - const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause); - const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex < clauseEnd); - if (hasDefaultClause) { - // In the default clause we filter constituents down to those that are not-equal to all handled cases. - const notEqualFacts = getNotEqualFactsFromTypeofSwitch(clauseStart, clauseEnd, witnesses); - return filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts); - } - // In the non-default cause we create a union of the type narrowed by each of the listed cases. - const clauseWitnesses = witnesses.slice(clauseStart, clauseEnd); - return getUnionType(map(clauseWitnesses, text => text ? narrowTypeByTypeName(type, text) : neverType)); + // Get the prototype property of the type identifier so we can find out its type. + const prototypeProperty = getPropertyOfType(identifierType, "prototype" as __String); + if (!prototypeProperty) { + return type; } - function isMatchingConstructorReference(expr: Expression) { - return (isPropertyAccessExpression(expr) && idText(expr.name) === "constructor" || - isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor") && - isMatchingReference(reference, expr.expression); + // Get the type of the prototype, if it is undefined, or the global `Object` or `Function` types then do not narrow. + const prototypeType = getTypeOfSymbol(prototypeProperty); + const candidate = !isTypeAny(prototypeType) ? prototypeType : undefined; + if (!candidate || candidate === globalObjectType || candidate === globalFunctionType) { + return type; } - function narrowTypeByConstructor(type: Type, operator: SyntaxKind, identifier: Expression, assumeTrue: boolean): Type { - // Do not narrow when checking inequality. - if (assumeTrue ? (operator !== SyntaxKind.EqualsEqualsToken && operator !== SyntaxKind.EqualsEqualsEqualsToken) : (operator !== SyntaxKind.ExclamationEqualsToken && operator !== SyntaxKind.ExclamationEqualsEqualsToken)) { - return type; - } + // If the type that is being narrowed is `any` then just return the `candidate` type since every type is a subtype of `any`. + if (isTypeAny(type)) { + return candidate; + } - // Get the type of the constructor identifier expression, if it is not a function then do not narrow. - const identifierType = getTypeOfExpression(identifier); - if (!isFunctionType(identifierType) && !isConstructorType(identifierType)) { - return type; - } + // Filter out types that are not considered to be "constructed by" the `candidate` type. + return filterType(type, t => isConstructedBy(t, candidate)); - // Get the prototype property of the type identifier so we can find out its type. - const prototypeProperty = getPropertyOfType(identifierType, "prototype" as __String); - if (!prototypeProperty) { - return type; + function isConstructedBy(source: Type, target: Type) { + // If either the source or target type are a class type then we need to check that they are the same exact type. + // This is because you may have a class `A` that defines some set of properties, and another class `B` + // that defines the same set of properties as class `A`, in that case they are structurally the same + // type, but when you do something like `instanceOfA.constructor === B` it will return false. + if (source.flags & TypeFlags.Object && getObjectFlags(source) & ObjectFlags.Class || + target.flags & TypeFlags.Object && getObjectFlags(target) & ObjectFlags.Class) { + return source.symbol === target.symbol; } - // Get the type of the prototype, if it is undefined, or the global `Object` or `Function` types then do not narrow. - const prototypeType = getTypeOfSymbol(prototypeProperty); - const candidate = !isTypeAny(prototypeType) ? prototypeType : undefined; - if (!candidate || candidate === globalObjectType || candidate === globalFunctionType) { - return type; - } + // For all other types just check that the `source` type is a subtype of the `target` type. + return isTypeSubtypeOf(source, target); + } + } - // If the type that is being narrowed is `any` then just return the `candidate` type since every type is a subtype of `any`. - if (isTypeAny(type)) { - return candidate; + function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { + const left = getReferenceCandidate(expr.left); + if (!isMatchingReference(reference, left)) { + if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) { + return getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } + return type; + } - // Filter out types that are not considered to be "constructed by" the `candidate` type. - return filterType(type, t => isConstructedBy(t, candidate)); - - function isConstructedBy(source: Type, target: Type) { - // If either the source or target type are a class type then we need to check that they are the same exact type. - // This is because you may have a class `A` that defines some set of properties, and another class `B` - // that defines the same set of properties as class `A`, in that case they are structurally the same - // type, but when you do something like `instanceOfA.constructor === B` it will return false. - if (source.flags & TypeFlags.Object && getObjectFlags(source) & ObjectFlags.Class || - target.flags & TypeFlags.Object && getObjectFlags(target) & ObjectFlags.Class) { - return source.symbol === target.symbol; - } - - // For all other types just check that the `source` type is a subtype of the `target` type. - return isTypeSubtypeOf(source, target); - } + // Check that right operand is a function type with a prototype property + const rightType = getTypeOfExpression(expr.right); + if (!isTypeDerivedFrom(rightType, globalFunctionType)) { + return type; } - function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { - const left = getReferenceCandidate(expr.left); - if (!isMatchingReference(reference, left)) { - if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) { - return getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); - } - return type; + let targetType: Type | undefined; + const prototypeProperty = getPropertyOfType(rightType, "prototype" as __String); + if (prototypeProperty) { + // Target type is type of the prototype property + const prototypePropertyType = getTypeOfSymbol(prototypeProperty); + if (!isTypeAny(prototypePropertyType)) { + targetType = prototypePropertyType; } + } - // Check that right operand is a function type with a prototype property - const rightType = getTypeOfExpression(expr.right); - if (!isTypeDerivedFrom(rightType, globalFunctionType)) { - return type; - } + // Don't narrow from 'any' if the target type is exactly 'Object' or 'Function' + if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) { + return type; + } - let targetType: Type | undefined; - const prototypeProperty = getPropertyOfType(rightType, "prototype" as __String); - if (prototypeProperty) { - // Target type is type of the prototype property - const prototypePropertyType = getTypeOfSymbol(prototypeProperty); - if (!isTypeAny(prototypePropertyType)) { - targetType = prototypePropertyType; - } - } + if (!targetType) { + const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct); + targetType = constructSignatures.length ? + getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) : + emptyObjectType; + } - // Don't narrow from 'any' if the target type is exactly 'Object' or 'Function' - if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) { - return type; - } + // We can't narrow a union based off instanceof without negated types see #31576 for more info + if (!assumeTrue && rightType.flags & TypeFlags.Union) { + const nonConstructorTypeInUnion = find((rightType as UnionType).types, (t) => !isConstructorType(t)); + if (!nonConstructorTypeInUnion) return type; + } - if (!targetType) { - const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct); - targetType = constructSignatures.length ? - getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) : - emptyObjectType; - } + return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true); + } - // We can't narrow a union based off instanceof without negated types see #31576 for more info - if (!assumeTrue && rightType.flags & TypeFlags.Union) { - const nonConstructorTypeInUnion = find((rightType as UnionType).types, (t) => !isConstructorType(t)); - if (!nonConstructorTypeInUnion) return type; - } + function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) { + const key = type.flags & TypeFlags.Union ? `N${getTypeId(type)},${getTypeId(candidate)},${(assumeTrue ? 1 : 0) | (checkDerived ? 2 : 0)}` : undefined; + return getCachedType(key) ?? setCachedType(key, getNarrowedTypeWorker(type, candidate, assumeTrue, checkDerived)); + } - return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true); + function getNarrowedTypeWorker(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) { + const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf; + if (!assumeTrue) { + return filterType(type, t => !isRelated(t, candidate)); } - - function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) { - const key = type.flags & TypeFlags.Union ? `N${getTypeId(type)},${getTypeId(candidate)},${(assumeTrue ? 1 : 0) | (checkDerived ? 2 : 0)}` : undefined; - return getCachedType(key) ?? setCachedType(key, getNarrowedTypeWorker(type, candidate, assumeTrue, checkDerived)); + if (type.flags & TypeFlags.AnyOrUnknown) { + return candidate; } + // We first attempt to filter the current type, narrowing constituents as appropriate and removing + // constituents that are unrelated to the candidate. + const keyPropertyName = type.flags & TypeFlags.Union ? getKeyPropertyName(type as UnionType) : undefined; + const narrowedType = mapType(candidate, c => { + // If a discriminant property is available, use that to reduce the type. + const discriminant = keyPropertyName && getTypeOfPropertyOfType(c, keyPropertyName); + const matching = discriminant && getConstituentTypeForKeyType(type as UnionType, discriminant); + // For each constituent t in the current type, if t and and c are directly related, pick the most + // specific of the two. When t and c are related in both directions, we prefer c for type predicates + // because that is the asserted type, but t for `instanceof` because generics aren't reflected in + // prototype object types. + const directlyRelated = mapType(matching || type, checkDerived ? + t => isTypeDerivedFrom(t, c) ? t : isTypeDerivedFrom(c, t) ? c : neverType : + t => isTypeSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : neverType); + // If no constituents are directly related, create intersections for any generic constituents that + // are related by constraint. + return directlyRelated.flags & TypeFlags.Never ? + mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) ? getIntersectionType([t, c]) : neverType) : + directlyRelated; + }); + // If filtering produced a non-empty type, return that. Otherwise, pick the most specific of the two + // based on assignability, or as a last resort produce an intersection. + return !(narrowedType.flags & TypeFlags.Never) ? narrowedType : + isTypeSubtypeOf(candidate, type) ? candidate : + isTypeAssignableTo(type, candidate) ? type : + isTypeAssignableTo(candidate, type) ? candidate : + getIntersectionType([type, candidate]); + } - function getNarrowedTypeWorker(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) { - const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf; - if (!assumeTrue) { - return filterType(type, t => !isRelated(t, candidate)); + function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { + if (hasMatchingArgument(callExpression, reference)) { + const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined; + const predicate = signature && getTypePredicateOfSignature(signature); + if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) { + return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue); } - if (type.flags & TypeFlags.AnyOrUnknown) { - return candidate; - } - // We first attempt to filter the current type, narrowing constituents as appropriate and removing - // constituents that are unrelated to the candidate. - const keyPropertyName = type.flags & TypeFlags.Union ? getKeyPropertyName(type as UnionType) : undefined; - const narrowedType = mapType(candidate, c => { - // If a discriminant property is available, use that to reduce the type. - const discriminant = keyPropertyName && getTypeOfPropertyOfType(c, keyPropertyName); - const matching = discriminant && getConstituentTypeForKeyType(type as UnionType, discriminant); - // For each constituent t in the current type, if t and and c are directly related, pick the most - // specific of the two. When t and c are related in both directions, we prefer c for type predicates - // because that is the asserted type, but t for `instanceof` because generics aren't reflected in - // prototype object types. - const directlyRelated = mapType(matching || type, checkDerived ? - t => isTypeDerivedFrom(t, c) ? t : isTypeDerivedFrom(c, t) ? c : neverType : - t => isTypeSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : neverType); - // If no constituents are directly related, create intersections for any generic constituents that - // are related by constraint. - return directlyRelated.flags & TypeFlags.Never ? - mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) ? getIntersectionType([t, c]) : neverType) : - directlyRelated; - }); - // If filtering produced a non-empty type, return that. Otherwise, pick the most specific of the two - // based on assignability, or as a last resort produce an intersection. - return !(narrowedType.flags & TypeFlags.Never) ? narrowedType : - isTypeSubtypeOf(candidate, type) ? candidate : - isTypeAssignableTo(type, candidate) ? type : - isTypeAssignableTo(candidate, type) ? candidate : - getIntersectionType([type, candidate]); - } - - function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { - if (hasMatchingArgument(callExpression, reference)) { - const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined; - const predicate = signature && getTypePredicateOfSignature(signature); - if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) { - return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue); - } - } - if (containsMissingType(type) && isAccessExpression(reference) && isPropertyAccessExpression(callExpression.expression)) { - const callAccess = callExpression.expression; - if (isMatchingReference(reference.expression, getReferenceCandidate(callAccess.expression)) && - isIdentifier(callAccess.name) && callAccess.name.escapedText === "hasOwnProperty" && callExpression.arguments.length === 1) { - const argument = callExpression.arguments[0]; - if (isStringLiteralLike(argument) && getAccessedPropertyName(reference) === escapeLeadingUnderscores(argument.text)) { - return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); - } + } + if (containsMissingType(type) && isAccessExpression(reference) && isPropertyAccessExpression(callExpression.expression)) { + const callAccess = callExpression.expression; + if (isMatchingReference(reference.expression, getReferenceCandidate(callAccess.expression)) && + isIdentifier(callAccess.name) && callAccess.name.escapedText === "hasOwnProperty" && callExpression.arguments.length === 1) { + const argument = callExpression.arguments[0]; + if (isStringLiteralLike(argument) && getAccessedPropertyName(reference) === escapeLeadingUnderscores(argument.text)) { + return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); } } - return type; } + return type; + } - function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type { - // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function' - if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) { - const predicateArgument = getTypePredicateArgument(predicate, callExpression); - if (predicateArgument) { - if (isMatchingReference(reference, predicateArgument)) { - return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ false); - } - if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) && - !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) { - type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); - } - const access = getDiscriminantPropertyAccess(predicateArgument, type); - if (access) { - return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, /*checkDerived*/ false)); - } + function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type { + // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function' + if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) { + const predicateArgument = getTypePredicateArgument(predicate, callExpression); + if (predicateArgument) { + if (isMatchingReference(reference, predicateArgument)) { + return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ false); + } + if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) && + !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) { + type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } + const access = getDiscriminantPropertyAccess(predicateArgument, type); + if (access) { + return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, /*checkDerived*/ false)); } } - return type; } + return type; + } - // Narrow the given type based on the given expression having the assumed boolean value. The returned type - // will be a subtype or the same type as the argument. - function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { - // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` - if (isExpressionOfOptionalChainRoot(expr) || - isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) { - return narrowTypeByOptionality(type, expr, assumeTrue); - } - switch (expr.kind) { - case SyntaxKind.Identifier: - // When narrowing a reference to a const variable, non-assigned parameter, or readonly property, we inline - // up to five levels of aliased conditional expressions that are themselves declared as const variables. - if (!isMatchingReference(reference, expr) && inlineLevel < 5) { - const symbol = getResolvedSymbol(expr as Identifier); - if (isConstVariable(symbol)) { - const declaration = symbol.valueDeclaration; - if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isConstantReference(reference)) { - inlineLevel++; - const result = narrowType(type, declaration.initializer, assumeTrue); - inlineLevel--; - return result; - } + // Narrow the given type based on the given expression having the assumed boolean value. The returned type + // will be a subtype or the same type as the argument. + function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { + // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` + if (isExpressionOfOptionalChainRoot(expr) || + isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) { + return narrowTypeByOptionality(type, expr, assumeTrue); + } + switch (expr.kind) { + case SyntaxKind.Identifier: + // When narrowing a reference to a const variable, non-assigned parameter, or readonly property, we inline + // up to five levels of aliased conditional expressions that are themselves declared as const variables. + if (!isMatchingReference(reference, expr) && inlineLevel < 5) { + const symbol = getResolvedSymbol(expr as Identifier); + if (isConstVariable(symbol)) { + const declaration = symbol.valueDeclaration; + if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isConstantReference(reference)) { + inlineLevel++; + const result = narrowType(type, declaration.initializer, assumeTrue); + inlineLevel--; + return result; } } - // falls through - case SyntaxKind.ThisKeyword: - case SyntaxKind.SuperKeyword: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return narrowTypeByTruthiness(type, expr, assumeTrue); - case SyntaxKind.CallExpression: - return narrowTypeByCallExpression(type, expr as CallExpression, assumeTrue); - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.NonNullExpression: - return narrowType(type, (expr as ParenthesizedExpression | NonNullExpression).expression, assumeTrue); - case SyntaxKind.BinaryExpression: - return narrowTypeByBinaryExpression(type, expr as BinaryExpression, assumeTrue); - case SyntaxKind.PrefixUnaryExpression: - if ((expr as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken) { - return narrowType(type, (expr as PrefixUnaryExpression).operand, !assumeTrue); - } - break; - } - return type; - } - - function narrowTypeByOptionality(type: Type, expr: Expression, assumePresent: boolean): Type { - if (isMatchingReference(reference, expr)) { - return getAdjustedTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull); - } - const access = getDiscriminantPropertyAccess(expr, type); - if (access) { - return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); - } - return type; + } + // falls through + case SyntaxKind.ThisKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return narrowTypeByTruthiness(type, expr, assumeTrue); + case SyntaxKind.CallExpression: + return narrowTypeByCallExpression(type, expr as CallExpression, assumeTrue); + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.NonNullExpression: + return narrowType(type, (expr as ParenthesizedExpression | NonNullExpression).expression, assumeTrue); + case SyntaxKind.BinaryExpression: + return narrowTypeByBinaryExpression(type, expr as BinaryExpression, assumeTrue); + case SyntaxKind.PrefixUnaryExpression: + if ((expr as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken) { + return narrowType(type, (expr as PrefixUnaryExpression).operand, !assumeTrue); + } + break; } + return type; } - function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { - symbol = symbol.exportSymbol || symbol; - - // If we have an identifier or a property access at the given location, if the location is - // an dotted name expression, and if the location is not an assignment target, obtain the type - // of the expression (which will reflect control flow analysis). If the expression indeed - // resolved to the given symbol, return the narrowed type. - if (location.kind === SyntaxKind.Identifier || location.kind === SyntaxKind.PrivateIdentifier) { - if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { - location = location.parent; - } - if (isExpressionNode(location) && (!isAssignmentTarget(location) || isWriteAccess(location))) { - const type = getTypeOfExpression(location as Expression); - if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { - return type; - } - } + function narrowTypeByOptionality(type: Type, expr: Expression, assumePresent: boolean): Type { + if (isMatchingReference(reference, expr)) { + return getAdjustedTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull); } - if (isDeclarationName(location) && isSetAccessor(location.parent) && getAnnotatedAccessorTypeNode(location.parent)) { - return getWriteTypeOfAccessors(location.parent.symbol); + const access = getDiscriminantPropertyAccess(expr, type); + if (access) { + return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); } - // The location isn't a reference to the given symbol, meaning we're being asked - // a hypothetical question of what type the symbol would have if there was a reference - // to it at the given location. Since we have no control flow information for the - // hypothetical reference (control flow information is created and attached by the - // binder), we simply return the declared type of the symbol. - return getNonMissingTypeOfSymbol(symbol); + return type; } + } - function getControlFlowContainer(node: Node): Node { - return findAncestor(node.parent, node => - isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || - node.kind === SyntaxKind.ModuleBlock || - node.kind === SyntaxKind.SourceFile || - node.kind === SyntaxKind.PropertyDeclaration)!; - } + function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { + symbol = symbol.exportSymbol || symbol; - // Check if a parameter or catch variable is assigned anywhere - function isSymbolAssigned(symbol: Symbol) { - if (!symbol.valueDeclaration) { - return false; + // If we have an identifier or a property access at the given location, if the location is + // an dotted name expression, and if the location is not an assignment target, obtain the type + // of the expression (which will reflect control flow analysis). If the expression indeed + // resolved to the given symbol, return the narrowed type. + if (location.kind === SyntaxKind.Identifier || location.kind === SyntaxKind.PrivateIdentifier) { + if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { + location = location.parent; } - const parent = getRootDeclaration(symbol.valueDeclaration).parent; - const links = getNodeLinks(parent); - if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { - links.flags |= NodeCheckFlags.AssignmentsMarked; - if (!hasParentWithAssignmentsMarked(parent)) { - markNodeAssignments(parent); + if (isExpressionNode(location) && (!isAssignmentTarget(location) || isWriteAccess(location))) { + const type = getTypeOfExpression(location as Expression); + if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { + return type; } } - return symbol.isAssigned || false; } + if (isDeclarationName(location) && isSetAccessor(location.parent) && getAnnotatedAccessorTypeNode(location.parent)) { + return getWriteTypeOfAccessors(location.parent.symbol); + } + // The location isn't a reference to the given symbol, meaning we're being asked + // a hypothetical question of what type the symbol would have if there was a reference + // to it at the given location. Since we have no control flow information for the + // hypothetical reference (control flow information is created and attached by the + // binder), we simply return the declared type of the symbol. + return getNonMissingTypeOfSymbol(symbol); + } - function hasParentWithAssignmentsMarked(node: Node) { - return !!findAncestor(node.parent, node => - (isFunctionLike(node) || isCatchClause(node)) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); + function getControlFlowContainer(node: Node): Node { + return findAncestor(node.parent, node => + isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || + node.kind === SyntaxKind.ModuleBlock || + node.kind === SyntaxKind.SourceFile || + node.kind === SyntaxKind.PropertyDeclaration)!; + } + + // Check if a parameter or catch variable is assigned anywhere + function isSymbolAssigned(symbol: Symbol) { + if (!symbol.valueDeclaration) { + return false; + } + const parent = getRootDeclaration(symbol.valueDeclaration).parent; + const links = getNodeLinks(parent); + if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { + links.flags |= NodeCheckFlags.AssignmentsMarked; + if (!hasParentWithAssignmentsMarked(parent)) { + markNodeAssignments(parent); + } } + return symbol.isAssigned || false; + } - function markNodeAssignments(node: Node) { - if (node.kind === SyntaxKind.Identifier) { - if (isAssignmentTarget(node)) { - const symbol = getResolvedSymbol(node as Identifier); - if (isParameterOrCatchClauseVariable(symbol)) { - symbol.isAssigned = true; - } + function hasParentWithAssignmentsMarked(node: Node) { + return !!findAncestor(node.parent, node => + (isFunctionLike(node) || isCatchClause(node)) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); + } + + function markNodeAssignments(node: Node) { + if (node.kind === SyntaxKind.Identifier) { + if (isAssignmentTarget(node)) { + const symbol = getResolvedSymbol(node as Identifier); + if (isParameterOrCatchClauseVariable(symbol)) { + symbol.isAssigned = true; } } - else { - forEachChild(node, markNodeAssignments); - } } + else { + forEachChild(node, markNodeAssignments); + } + } + + function isConstVariable(symbol: Symbol) { + return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0; + } + + /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ + function removeOptionalityFromDeclaredType(declaredType: Type, declaration: VariableLikeDeclaration): Type { + if (pushTypeResolution(declaration.symbol, TypeSystemPropertyName.DeclaredType)) { + const annotationIncludesUndefined = strictNullChecks && + declaration.kind === SyntaxKind.Parameter && + declaration.initializer && + getTypeFacts(declaredType) & TypeFacts.IsUndefined && + !(getTypeFacts(checkExpression(declaration.initializer)) & TypeFacts.IsUndefined); + popTypeResolution(); - function isConstVariable(symbol: Symbol) { - return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0; + return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType; + } + else { + reportCircularityError(declaration.symbol); + return declaredType; } + } + + function isConstraintPosition(type: Type, node: Node) { + const parent = node.parent; + // In an element access obj[x], we consider obj to be in a constraint position, except when obj is of + // a generic type without a nullable constraint and x is a generic type. This is because when both obj + // and x are of generic types T and K, we want the resulting type to be T[K]. + return parent.kind === SyntaxKind.PropertyAccessExpression || + parent.kind === SyntaxKind.QualifiedName || + parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === node || + parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === node && + !(someType(type, isGenericTypeWithoutNullableConstraint) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression))); + } + + function isGenericTypeWithUnionConstraint(type: Type): boolean { + return type.flags & TypeFlags.Intersection ? + some((type as IntersectionType).types, isGenericTypeWithUnionConstraint) : + !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union)); + } + + function isGenericTypeWithoutNullableConstraint(type: Type): boolean { + return type.flags & TypeFlags.Intersection ? + some((type as IntersectionType).types, isGenericTypeWithoutNullableConstraint) : + !!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable)); + } + + function hasContextualTypeWithNoGenericTypes(node: Node, checkMode: CheckMode | undefined) { + // Computing the contextual type for a child of a JSX element involves resolving the type of the + // element's tag name, so we exclude that here to avoid circularities. + // If check mode has `CheckMode.RestBindingElement`, we skip binding pattern contextual types, + // as we want the type of a rest element to be generic when possible. + const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) && + !((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) && + (checkMode && checkMode & CheckMode.RestBindingElement ? + getContextualType(node, ContextFlags.SkipBindingPatterns) + : getContextualType(node, /*contextFlags*/ undefined)); + return contextualType && !isGenericType(contextualType); + } - /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ - function removeOptionalityFromDeclaredType(declaredType: Type, declaration: VariableLikeDeclaration): Type { - if (pushTypeResolution(declaration.symbol, TypeSystemPropertyName.DeclaredType)) { - const annotationIncludesUndefined = strictNullChecks && - declaration.kind === SyntaxKind.Parameter && - declaration.initializer && - getTypeFacts(declaredType) & TypeFacts.IsUndefined && - !(getTypeFacts(checkExpression(declaration.initializer)) & TypeFacts.IsUndefined); - popTypeResolution(); + function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) { + // When the type of a reference is or contains an instantiable type with a union type constraint, and + // when the reference is in a constraint position (where it is known we'll obtain the apparent type) or + // has a contextual type containing no top-level instantiables (meaning constraints will determine + // assignability), we substitute constraints for all instantiables in the type of the reference to give + // control flow analysis an opportunity to narrow it further. For example, for a reference of a type + // parameter type 'T extends string | undefined' with a contextual type 'string', we substitute + // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. + const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) && + someType(type, isGenericTypeWithUnionConstraint) && + (isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode)); + return substituteConstraints ? mapType(type, getBaseConstraintOrType) : type; + } - return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType; + function isExportOrExportExpression(location: Node) { + return !!findAncestor(location, n => { + const parent = n.parent; + if (parent === undefined) { + return "quit"; } - else { - reportCircularityError(declaration.symbol); - return declaredType; + if (isExportAssignment(parent)) { + return parent.expression === n && isEntityNameExpression(n); } - } + if (isExportSpecifier(parent)) { + return parent.name === n || parent.propertyName === n; + } + return false; + }); + } - function isConstraintPosition(type: Type, node: Node) { - const parent = node.parent; - // In an element access obj[x], we consider obj to be in a constraint position, except when obj is of - // a generic type without a nullable constraint and x is a generic type. This is because when both obj - // and x are of generic types T and K, we want the resulting type to be T[K]. - return parent.kind === SyntaxKind.PropertyAccessExpression || - parent.kind === SyntaxKind.QualifiedName || - parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === node || - parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === node && - !(someType(type, isGenericTypeWithoutNullableConstraint) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression))); - } - - function isGenericTypeWithUnionConstraint(type: Type): boolean { - return type.flags & TypeFlags.Intersection ? - some((type as IntersectionType).types, isGenericTypeWithUnionConstraint) : - !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union)); - } - - function isGenericTypeWithoutNullableConstraint(type: Type): boolean { - return type.flags & TypeFlags.Intersection ? - some((type as IntersectionType).types, isGenericTypeWithoutNullableConstraint) : - !!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable)); - } - - function hasContextualTypeWithNoGenericTypes(node: Node, checkMode: CheckMode | undefined) { - // Computing the contextual type for a child of a JSX element involves resolving the type of the - // element's tag name, so we exclude that here to avoid circularities. - // If check mode has `CheckMode.RestBindingElement`, we skip binding pattern contextual types, - // as we want the type of a rest element to be generic when possible. - const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) && - !((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) && - (checkMode && checkMode & CheckMode.RestBindingElement ? - getContextualType(node, ContextFlags.SkipBindingPatterns) - : getContextualType(node, /*contextFlags*/ undefined)); - return contextualType && !isGenericType(contextualType); - } - - function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) { - // When the type of a reference is or contains an instantiable type with a union type constraint, and - // when the reference is in a constraint position (where it is known we'll obtain the apparent type) or - // has a contextual type containing no top-level instantiables (meaning constraints will determine - // assignability), we substitute constraints for all instantiables in the type of the reference to give - // control flow analysis an opportunity to narrow it further. For example, for a reference of a type - // parameter type 'T extends string | undefined' with a contextual type 'string', we substitute - // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. - const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) && - someType(type, isGenericTypeWithUnionConstraint) && - (isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode)); - return substituteConstraints ? mapType(type, getBaseConstraintOrType) : type; - } - - function isExportOrExportExpression(location: Node) { - return !!findAncestor(location, n => { - const parent = n.parent; - if (parent === undefined) { - return "quit"; - } - if (isExportAssignment(parent)) { - return parent.expression === n && isEntityNameExpression(n); - } - if (isExportSpecifier(parent)) { - return parent.name === n || parent.propertyName === n; + function markAliasReferenced(symbol: Symbol, location: Node) { + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value)) { + const target = resolveAlias(symbol); + if (getAllSymbolFlags(target) & (SymbolFlags.Value | SymbolFlags.ExportValue)) { + // An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled + // (because the const enum value will not be inlined), or if (2) the alias is an export + // of a const enum declaration that will be preserved. + if (compilerOptions.isolatedModules || + shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || + !isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target)) + ) { + markAliasSymbolAsReferenced(symbol); } - return false; - }); - } - - function markAliasReferenced(symbol: Symbol, location: Node) { - if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value)) { - const target = resolveAlias(symbol); - if (getAllSymbolFlags(target) & (SymbolFlags.Value | SymbolFlags.ExportValue)) { - // An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled - // (because the const enum value will not be inlined), or if (2) the alias is an export - // of a const enum declaration that will be preserved. - if (compilerOptions.isolatedModules || - shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || - !isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target)) - ) { - markAliasSymbolAsReferenced(symbol); - } - else { - markConstEnumAliasAsReferenced(symbol); - } + else { + markConstEnumAliasAsReferenced(symbol); } } } + } - function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) { - const declaration = symbol.valueDeclaration; - if (declaration) { - // If we have a non-rest binding element with no initializer declared as a const variable or a const-like - // parameter (a parameter for which there are no assignments in the function body), and if the parent type - // for the destructuring is a union type, one or more of the binding elements may represent discriminant - // properties, and we want the effects of conditional checks on such discriminants to affect the types of - // other binding elements from the same destructuring. Consider: - // - // type Action = - // | { kind: 'A', payload: number } - // | { kind: 'B', payload: string }; - // - // function f({ kind, payload }: Action) { - // if (kind === 'A') { - // payload.toFixed(); - // } - // if (kind === 'B') { - // payload.toUpperCase(); - // } - // } - // - // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use - // the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference - // as if it occurred in the specified location. We then recompute the narrowed binding element type by - // destructuring from the narrowed parent type. - if (isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) { - const parent = declaration.parent.parent; - if (parent.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlags(declaration) & NodeFlags.Const || parent.kind === SyntaxKind.Parameter) { - const links = getNodeLinks(parent); - if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) { - links.flags |= NodeCheckFlags.InCheckIdentifier; - const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal); - const parentTypeConstraint = parentType && mapType(parentType, getBaseConstraintOrType); - links.flags &= ~NodeCheckFlags.InCheckIdentifier; - if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(parent.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) { - const pattern = declaration.parent; - const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode); - if (narrowedType.flags & TypeFlags.Never) { - return neverType; - } - return getBindingElementTypeFromParentType(declaration, narrowedType); - } - } - } - } - // If we have a const-like parameter with no type annotation or initializer, and if the parameter is contextually - // typed by a signature with a single rest parameter of a union of tuple types, one or more of the parameters may - // represent discriminant tuple elements, and we want the effects of conditional checks on such discriminants to - // affect the types of other parameters in the same parameter list. Consider: - // - // type Action = [kind: 'A', payload: number] | [kind: 'B', payload: string]; - // - // const f: (...args: Action) => void = (kind, payload) => { - // if (kind === 'A') { - // payload.toFixed(); - // } - // if (kind === 'B') { - // payload.toUpperCase(); - // } - // } - // - // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use - // the arrow function AST node for '(kind, payload) => ...' as a pseudo-reference and narrow this reference as - // if it occurred in the specified location. We then recompute the narrowed parameter type by indexing into the - // narrowed tuple type. - if (isParameter(declaration) && !declaration.type && !declaration.initializer && !declaration.dotDotDotToken) { - const func = declaration.parent; - if (func.parameters.length >= 2 && isContextSensitiveFunctionOrObjectLiteralMethod(func)) { - const contextualSignature = getContextualSignature(func); - if (contextualSignature && contextualSignature.parameters.length === 1 && signatureHasRestParameter(contextualSignature)) { - const restType = getReducedApparentType(getTypeOfSymbol(contextualSignature.parameters[0])); - if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !isSymbolAssigned(symbol)) { - const narrowedType = getFlowTypeOfReference(func, restType, restType, /*flowContainer*/ undefined, location.flowNode); - const index = func.parameters.indexOf(declaration) - (getThisParameter(func) ? 1 : 0); - return getIndexedAccessType(narrowedType, getNumberLiteralType(index)); - } + function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) { + const declaration = symbol.valueDeclaration; + if (declaration) { + // If we have a non-rest binding element with no initializer declared as a const variable or a const-like + // parameter (a parameter for which there are no assignments in the function body), and if the parent type + // for the destructuring is a union type, one or more of the binding elements may represent discriminant + // properties, and we want the effects of conditional checks on such discriminants to affect the types of + // other binding elements from the same destructuring. Consider: + // + // type Action = + // | { kind: 'A', payload: number } + // | { kind: 'B', payload: string }; + // + // function f({ kind, payload }: Action) { + // if (kind === 'A') { + // payload.toFixed(); + // } + // if (kind === 'B') { + // payload.toUpperCase(); + // } + // } + // + // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use + // the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference + // as if it occurred in the specified location. We then recompute the narrowed binding element type by + // destructuring from the narrowed parent type. + if (isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) { + const parent = declaration.parent.parent; + if (parent.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlags(declaration) & NodeFlags.Const || parent.kind === SyntaxKind.Parameter) { + const links = getNodeLinks(parent); + if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) { + links.flags |= NodeCheckFlags.InCheckIdentifier; + const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal); + const parentTypeConstraint = parentType && mapType(parentType, getBaseConstraintOrType); + links.flags &= ~NodeCheckFlags.InCheckIdentifier; + if (parentTypeConstraint && parentTypeConstraint.flags & TypeFlags.Union && !(parent.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) { + const pattern = declaration.parent; + const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode); + if (narrowedType.flags & TypeFlags.Never) { + return neverType; + } + return getBindingElementTypeFromParentType(declaration, narrowedType); + } + } + } + } + // If we have a const-like parameter with no type annotation or initializer, and if the parameter is contextually + // typed by a signature with a single rest parameter of a union of tuple types, one or more of the parameters may + // represent discriminant tuple elements, and we want the effects of conditional checks on such discriminants to + // affect the types of other parameters in the same parameter list. Consider: + // + // type Action = [kind: 'A', payload: number] | [kind: 'B', payload: string]; + // + // const f: (...args: Action) => void = (kind, payload) => { + // if (kind === 'A') { + // payload.toFixed(); + // } + // if (kind === 'B') { + // payload.toUpperCase(); + // } + // } + // + // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use + // the arrow function AST node for '(kind, payload) => ...' as a pseudo-reference and narrow this reference as + // if it occurred in the specified location. We then recompute the narrowed parameter type by indexing into the + // narrowed tuple type. + if (isParameter(declaration) && !declaration.type && !declaration.initializer && !declaration.dotDotDotToken) { + const func = declaration.parent; + if (func.parameters.length >= 2 && isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + const contextualSignature = getContextualSignature(func); + if (contextualSignature && contextualSignature.parameters.length === 1 && signatureHasRestParameter(contextualSignature)) { + const restType = getReducedApparentType(getTypeOfSymbol(contextualSignature.parameters[0])); + if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !isSymbolAssigned(symbol)) { + const narrowedType = getFlowTypeOfReference(func, restType, restType, /*flowContainer*/ undefined, location.flowNode); + const index = func.parameters.indexOf(declaration) - (getThisParameter(func) ? 1 : 0); + return getIndexedAccessType(narrowedType, getNumberLiteralType(index)); } } } } - return getTypeOfSymbol(symbol); } + return getTypeOfSymbol(symbol); + } - function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { - if (isThisInTypeQuery(node)) { - return checkThisExpression(node); - } + function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { + if (isThisInTypeQuery(node)) { + return checkThisExpression(node); + } + + const symbol = getResolvedSymbol(node); + if (symbol === unknownSymbol) { + return errorType; + } - const symbol = getResolvedSymbol(node); - if (symbol === unknownSymbol) { + // As noted in ECMAScript 6 language spec, arrow functions never have an arguments objects. + // Although in down-level emit of arrow function, we emit it using function expression which means that + // arguments objects will be bound to the inner object; emitting arrow function natively in ES6, arguments objects + // will be bound to non-arrow function that contain this arrow function. This results in inconsistent behavior. + // To avoid that we will give an error to users if they use arguments objects in arrow function so that they + // can explicitly bound arguments objects + if (symbol === argumentsSymbol) { + if (isInPropertyInitializerOrClassStaticBlock(node)) { + error(node, Diagnostics.arguments_cannot_be_referenced_in_property_initializers); return errorType; } - // As noted in ECMAScript 6 language spec, arrow functions never have an arguments objects. - // Although in down-level emit of arrow function, we emit it using function expression which means that - // arguments objects will be bound to the inner object; emitting arrow function natively in ES6, arguments objects - // will be bound to non-arrow function that contain this arrow function. This results in inconsistent behavior. - // To avoid that we will give an error to users if they use arguments objects in arrow function so that they - // can explicitly bound arguments objects - if (symbol === argumentsSymbol) { - if (isInPropertyInitializerOrClassStaticBlock(node)) { - error(node, Diagnostics.arguments_cannot_be_referenced_in_property_initializers); - return errorType; + const container = getContainingFunction(node)!; + if (languageVersion < ScriptTarget.ES2015) { + if (container.kind === SyntaxKind.ArrowFunction) { + error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_ES3_and_ES5_Consider_using_a_standard_function_expression); } - - const container = getContainingFunction(node)!; - if (languageVersion < ScriptTarget.ES2015) { - if (container.kind === SyntaxKind.ArrowFunction) { - error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_ES3_and_ES5_Consider_using_a_standard_function_expression); - } - else if (hasSyntacticModifier(container, ModifierFlags.Async)) { - error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_async_function_or_method_in_ES3_and_ES5_Consider_using_a_standard_function_or_method); - } + else if (hasSyntacticModifier(container, ModifierFlags.Async)) { + error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_async_function_or_method_in_ES3_and_ES5_Consider_using_a_standard_function_or_method); } - - getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; - return getTypeOfSymbol(symbol); } - if (shouldMarkIdentifierAliasReferenced(node)) { - markAliasReferenced(symbol, node); - } + getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; + return getTypeOfSymbol(symbol); + } - const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); - const targetSymbol = checkDeprecatedAliasedSymbol(localOrExportSymbol, node); - if (isDeprecatedSymbol(targetSymbol) && isUncalledFunctionReference(node, targetSymbol) && targetSymbol.declarations) { - addDeprecatedSuggestion(node, targetSymbol.declarations, node.escapedText as string); - } + if (shouldMarkIdentifierAliasReferenced(node)) { + markAliasReferenced(symbol, node); + } - let declaration = localOrExportSymbol.valueDeclaration; - if (declaration && localOrExportSymbol.flags & SymbolFlags.Class) { - // 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. - if (declaration.kind === SyntaxKind.ClassDeclaration - && nodeIsDecorated(declaration as ClassDeclaration)) { - let container = getContainingClass(node); - while (container !== undefined) { - if (container === declaration && container.name !== node) { - getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; - getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; - break; - } + const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); + const targetSymbol = checkDeprecatedAliasedSymbol(localOrExportSymbol, node); + if (isDeprecatedSymbol(targetSymbol) && isUncalledFunctionReference(node, targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, node.escapedText as string); + } - container = getContainingClass(container); + let declaration = localOrExportSymbol.valueDeclaration; + if (declaration && localOrExportSymbol.flags & SymbolFlags.Class) { + // 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. + if (declaration.kind === SyntaxKind.ClassDeclaration + && nodeIsDecorated(declaration as ClassDeclaration)) { + let container = getContainingClass(node); + while (container !== undefined) { + if (container === declaration && container.name !== node) { + getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; + getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; + break; } + + container = getContainingClass(container); } - else if (declaration.kind === SyntaxKind.ClassExpression) { - // When we emit a class expression with static members that contain a reference - // to the constructor in the initializer, we will need to substitute that - // binding with an alias as the class name is not in scope. - let container = getThisContainer(node, /*includeArrowFunctions*/ false); - while (container.kind !== SyntaxKind.SourceFile) { - if (container.parent === declaration) { - if (isPropertyDeclaration(container) && isStatic(container) || isClassStaticBlockDeclaration(container)) { - getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; - getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; - } - break; + } + else if (declaration.kind === SyntaxKind.ClassExpression) { + // When we emit a class expression with static members that contain a reference + // to the constructor in the initializer, we will need to substitute that + // binding with an alias as the class name is not in scope. + let container = getThisContainer(node, /*includeArrowFunctions*/ false); + while (container.kind !== SyntaxKind.SourceFile) { + if (container.parent === declaration) { + if (isPropertyDeclaration(container) && isStatic(container) || isClassStaticBlockDeclaration(container)) { + getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; + getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; } - - container = getThisContainer(container, /*includeArrowFunctions*/ false); + break; } + + container = getThisContainer(container, /*includeArrowFunctions*/ false); } } + } - checkNestedBlockScopedBinding(node, symbol); + checkNestedBlockScopedBinding(node, symbol); - let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node); - const assignmentKind = getAssignmentTargetKind(node); + let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node); + const assignmentKind = getAssignmentTargetKind(node); - if (assignmentKind) { - if (!(localOrExportSymbol.flags & SymbolFlags.Variable) && - !(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)) { - const assignmentError = localOrExportSymbol.flags & SymbolFlags.Enum ? Diagnostics.Cannot_assign_to_0_because_it_is_an_enum - : localOrExportSymbol.flags & SymbolFlags.Class ? Diagnostics.Cannot_assign_to_0_because_it_is_a_class - : localOrExportSymbol.flags & SymbolFlags.Module ? Diagnostics.Cannot_assign_to_0_because_it_is_a_namespace - : localOrExportSymbol.flags & SymbolFlags.Function ? Diagnostics.Cannot_assign_to_0_because_it_is_a_function - : localOrExportSymbol.flags & SymbolFlags.Alias ? Diagnostics.Cannot_assign_to_0_because_it_is_an_import - : Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable; + if (assignmentKind) { + if (!(localOrExportSymbol.flags & SymbolFlags.Variable) && + !(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)) { + const assignmentError = localOrExportSymbol.flags & SymbolFlags.Enum ? Diagnostics.Cannot_assign_to_0_because_it_is_an_enum + : localOrExportSymbol.flags & SymbolFlags.Class ? Diagnostics.Cannot_assign_to_0_because_it_is_a_class + : localOrExportSymbol.flags & SymbolFlags.Module ? Diagnostics.Cannot_assign_to_0_because_it_is_a_namespace + : localOrExportSymbol.flags & SymbolFlags.Function ? Diagnostics.Cannot_assign_to_0_because_it_is_a_function + : localOrExportSymbol.flags & SymbolFlags.Alias ? Diagnostics.Cannot_assign_to_0_because_it_is_an_import + : Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable; - error(node, assignmentError, symbolToString(symbol)); - return errorType; - } - if (isReadonlySymbol(localOrExportSymbol)) { - if (localOrExportSymbol.flags & SymbolFlags.Variable) { - error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant, symbolToString(symbol)); - } - else { - error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(symbol)); - } - return errorType; - } + error(node, assignmentError, symbolToString(symbol)); + return errorType; } - - const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias; - - // We only narrow variables and parameters occurring in a non-assignment position. For all other - // entities we simply return the declared type. - if (localOrExportSymbol.flags & SymbolFlags.Variable) { - if (assignmentKind === AssignmentKind.Definite) { - return type; + if (isReadonlySymbol(localOrExportSymbol)) { + if (localOrExportSymbol.flags & SymbolFlags.Variable) { + error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant, symbolToString(symbol)); } + else { + error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(symbol)); + } + return errorType; } - else if (isAlias) { - declaration = getDeclarationOfAliasSymbol(symbol); - } - else { - return type; - } + } - if (!declaration) { - return type; - } + const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias; - type = getNarrowableTypeForReference(type, node, checkMode); - - // The declaration container is the innermost function that encloses the declaration of the variable - // or parameter. The flow container is the innermost function starting with which we analyze the control - // flow graph to determine the control flow based type. - const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter; - const declarationContainer = getControlFlowContainer(declaration); - let flowContainer = getControlFlowContainer(node); - const isOuterVariable = flowContainer !== declarationContainer; - const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent); - const isModuleExports = symbol.flags & SymbolFlags.ModuleExports; - // When the control flow originates in a function expression or arrow function and we are referencing - // a const variable or parameter from an outer function, we extend the origin of the control flow - // analysis to include the immediately enclosing function. - while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || - flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)) && - (isConstVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isSymbolAssigned(localOrExportSymbol))) { - flowContainer = getControlFlowContainer(flowContainer); - } - // We only look for uninitialized variables in strict null checking mode, and only when we can analyze - // the entire control flow graph from the variable's declaration (i.e. when the flow container and - // declaration container are the same). - const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isSameScopedBindingElement(node, declaration) || - type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 || - isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || - node.parent.kind === SyntaxKind.NonNullExpression || - declaration.kind === SyntaxKind.VariableDeclaration && (declaration as VariableDeclaration).exclamationToken || - declaration.flags & NodeFlags.Ambient; - const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : - type === autoType || type === autoArrayType ? undefinedType : - getOptionalType(type); - const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer); - // A variable is considered uninitialized when it is possible to analyze the entire control flow graph - // from declaration to use, and when the variable's declared type doesn't include undefined but the - // control flow based type does include undefined. - if (!isEvolvingArrayOperationTarget(node) && (type === autoType || type === autoArrayType)) { - if (flowType === autoType || flowType === autoArrayType) { - if (noImplicitAny) { - error(getNameOfDeclaration(declaration), Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(flowType)); - error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); - } - return convertAutoToAny(flowType); - } - } - else if (!assumeInitialized && !containsUndefinedType(type) && containsUndefinedType(flowType)) { - error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); - // Return the declared type to reduce follow-on errors + // We only narrow variables and parameters occurring in a non-assignment position. For all other + // entities we simply return the declared type. + if (localOrExportSymbol.flags & SymbolFlags.Variable) { + if (assignmentKind === AssignmentKind.Definite) { return type; } - return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + } + else if (isAlias) { + declaration = getDeclarationOfAliasSymbol(symbol); + } + else { + return type; } - function isSameScopedBindingElement(node: Identifier, declaration: Declaration) { - if (isBindingElement(declaration)) { - const bindingElement = findAncestor(node, isBindingElement); - return bindingElement && getRootDeclaration(bindingElement) === getRootDeclaration(declaration); - } + if (!declaration) { + return type; } - function shouldMarkIdentifierAliasReferenced(node: Identifier): boolean { - const parent = node.parent; - if (parent) { - // A property access expression LHS? checkPropertyAccessExpression will handle that. - if (isPropertyAccessExpression(parent) && parent.expression === node) { - return false; - } - // Next two check for an identifier inside a type only export. - if (isExportSpecifier(parent) && parent.isTypeOnly) { - return false; - } - const greatGrandparent = parent.parent?.parent; - if (greatGrandparent && isExportDeclaration(greatGrandparent) && greatGrandparent.isTypeOnly) { - return false; + type = getNarrowableTypeForReference(type, node, checkMode); + + // The declaration container is the innermost function that encloses the declaration of the variable + // or parameter. The flow container is the innermost function starting with which we analyze the control + // flow graph to determine the control flow based type. + const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter; + const declarationContainer = getControlFlowContainer(declaration); + let flowContainer = getControlFlowContainer(node); + const isOuterVariable = flowContainer !== declarationContainer; + const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent); + const isModuleExports = symbol.flags & SymbolFlags.ModuleExports; + // When the control flow originates in a function expression or arrow function and we are referencing + // a const variable or parameter from an outer function, we extend the origin of the control flow + // analysis to include the immediately enclosing function. + while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || + flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)) && + (isConstVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isSymbolAssigned(localOrExportSymbol))) { + flowContainer = getControlFlowContainer(flowContainer); + } + // We only look for uninitialized variables in strict null checking mode, and only when we can analyze + // the entire control flow graph from the variable's declaration (i.e. when the flow container and + // declaration container are the same). + const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isSameScopedBindingElement(node, declaration) || + type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 || + isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || + node.parent.kind === SyntaxKind.NonNullExpression || + declaration.kind === SyntaxKind.VariableDeclaration && (declaration as VariableDeclaration).exclamationToken || + declaration.flags & NodeFlags.Ambient; + const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : + type === autoType || type === autoArrayType ? undefinedType : + getOptionalType(type); + const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer); + // A variable is considered uninitialized when it is possible to analyze the entire control flow graph + // from declaration to use, and when the variable's declared type doesn't include undefined but the + // control flow based type does include undefined. + if (!isEvolvingArrayOperationTarget(node) && (type === autoType || type === autoArrayType)) { + if (flowType === autoType || flowType === autoArrayType) { + if (noImplicitAny) { + error(getNameOfDeclaration(declaration), Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(flowType)); + error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); } + return convertAutoToAny(flowType); } - return true; } + else if (!assumeInitialized && !containsUndefinedType(type) && containsUndefinedType(flowType)) { + error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); + // Return the declared type to reduce follow-on errors + return type; + } + return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + } - function isInsideFunctionOrInstancePropertyInitializer(node: Node, threshold: Node): boolean { - return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n) || ( - n.parent && isPropertyDeclaration(n.parent) && !hasStaticModifier(n.parent) && n.parent.initializer === n - )); + function isSameScopedBindingElement(node: Identifier, declaration: Declaration) { + if (isBindingElement(declaration)) { + const bindingElement = findAncestor(node, isBindingElement); + return bindingElement && getRootDeclaration(bindingElement) === getRootDeclaration(declaration); } + } - function getPartOfForStatementContainingNode(node: Node, container: ForStatement) { - return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement); + function shouldMarkIdentifierAliasReferenced(node: Identifier): boolean { + const parent = node.parent; + if (parent) { + // A property access expression LHS? checkPropertyAccessExpression will handle that. + if (isPropertyAccessExpression(parent) && parent.expression === node) { + return false; + } + // Next two check for an identifier inside a type only export. + if (isExportSpecifier(parent) && parent.isTypeOnly) { + return false; + } + const greatGrandparent = parent.parent?.parent; + if (greatGrandparent && isExportDeclaration(greatGrandparent) && greatGrandparent.isTypeOnly) { + return false; + } } + return true; + } + + function isInsideFunctionOrInstancePropertyInitializer(node: Node, threshold: Node): boolean { + return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n) || ( + n.parent && isPropertyDeclaration(n.parent) && !hasStaticModifier(n.parent) && n.parent.initializer === n + )); + } - function getEnclosingIterationStatement(node: Node): Node | undefined { - return findAncestor(node, n => (!n || nodeStartsNewLexicalEnvironment(n)) ? "quit" : isIterationStatement(n, /*lookInLabeledStatements*/ false)); + function getPartOfForStatementContainingNode(node: Node, container: ForStatement) { + return findAncestor(node, n => n === container ? "quit" : n === container.initializer || n === container.condition || n === container.incrementor || n === container.statement); + } + + function getEnclosingIterationStatement(node: Node): Node | undefined { + return findAncestor(node, n => (!n || nodeStartsNewLexicalEnvironment(n)) ? "quit" : isIterationStatement(n, /*lookInLabeledStatements*/ false)); + } + + function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void { + if (languageVersion >= ScriptTarget.ES2015 || + (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 || + !symbol.valueDeclaration || + isSourceFile(symbol.valueDeclaration) || + symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) { + return; } - function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void { - if (languageVersion >= ScriptTarget.ES2015 || - (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 || - !symbol.valueDeclaration || - isSourceFile(symbol.valueDeclaration) || - symbol.valueDeclaration.parent.kind === SyntaxKind.CatchClause) { - return; - } + // 1. walk from the use site up to the declaration and check + // if there is anything function like between declaration and use-site (is binding/class is captured in function). + // 2. walk from the declaration up to the boundary of lexical environment and check + // if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement) - // 1. walk from the use site up to the declaration and check - // if there is anything function like between declaration and use-site (is binding/class is captured in function). - // 2. walk from the declaration up to the boundary of lexical environment and check - // if there is an iteration statement in between declaration and boundary (is binding/class declared inside iteration statement) - - const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); - const isCaptured = isInsideFunctionOrInstancePropertyInitializer(node, container); - - const enclosingIterationStatement = getEnclosingIterationStatement(container); - if (enclosingIterationStatement) { - if (isCaptured) { - // mark iteration statement as containing block-scoped binding captured in some function - let capturesBlockScopeBindingInLoopBody = true; - if (isForStatement(container)) { - const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList); - if (varDeclList && varDeclList.parent === container) { - const part = getPartOfForStatementContainingNode(node.parent, container); - if (part) { - const links = getNodeLinks(part); - links.flags |= NodeCheckFlags.ContainsCapturedBlockScopeBinding; - - const capturedBindings = links.capturedBlockScopeBindings || (links.capturedBlockScopeBindings = []); - pushIfUnique(capturedBindings, symbol); - - if (part === container.initializer) { - capturesBlockScopeBindingInLoopBody = false; // Initializer is outside of loop body - } - } - } - } - if (capturesBlockScopeBindingInLoopBody) { - getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; - } - } + const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); + const isCaptured = isInsideFunctionOrInstancePropertyInitializer(node, container); - // mark variables that are declared in loop initializer and reassigned inside the body of ForStatement. - // if body of ForStatement will be converted to function then we'll need a extra machinery to propagate reassigned values back. + const enclosingIterationStatement = getEnclosingIterationStatement(container); + if (enclosingIterationStatement) { + if (isCaptured) { + // mark iteration statement as containing block-scoped binding captured in some function + let capturesBlockScopeBindingInLoopBody = true; if (isForStatement(container)) { const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList); - if (varDeclList && varDeclList.parent === container && isAssignedInBodyOfForStatement(node, container)) { - getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.NeedsLoopOutParameter; + if (varDeclList && varDeclList.parent === container) { + const part = getPartOfForStatementContainingNode(node.parent, container); + if (part) { + const links = getNodeLinks(part); + links.flags |= NodeCheckFlags.ContainsCapturedBlockScopeBinding; + + const capturedBindings = links.capturedBlockScopeBindings || (links.capturedBlockScopeBindings = []); + pushIfUnique(capturedBindings, symbol); + + if (part === container.initializer) { + capturesBlockScopeBindingInLoopBody = false; // Initializer is outside of loop body + } + } } } - - // set 'declared inside loop' bit on the block-scoped binding - getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + if (capturesBlockScopeBindingInLoopBody) { + getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + } } - if (isCaptured) { - getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; + // mark variables that are declared in loop initializer and reassigned inside the body of ForStatement. + // if body of ForStatement will be converted to function then we'll need a extra machinery to propagate reassigned values back. + if (isForStatement(container)) { + const varDeclList = getAncestor(symbol.valueDeclaration, SyntaxKind.VariableDeclarationList); + if (varDeclList && varDeclList.parent === container && isAssignedInBodyOfForStatement(node, container)) { + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.NeedsLoopOutParameter; + } } - } - function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { - const links = getNodeLinks(node); - return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfNode(decl)); + // set 'declared inside loop' bit on the block-scoped binding + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; } - function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean { - // skip parenthesized nodes - let current: Node = node; - while (current.parent.kind === SyntaxKind.ParenthesizedExpression) { - current = current.parent; - } - - // check if node is used as LHS in some assignment expression - let isAssigned = false; - if (isAssignmentTarget(current)) { - isAssigned = true; - } - else if ((current.parent.kind === SyntaxKind.PrefixUnaryExpression || current.parent.kind === SyntaxKind.PostfixUnaryExpression)) { - const expr = current.parent as PrefixUnaryExpression | PostfixUnaryExpression; - isAssigned = expr.operator === SyntaxKind.PlusPlusToken || expr.operator === SyntaxKind.MinusMinusToken; - } + if (isCaptured) { + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; + } + } - if (!isAssigned) { - return false; - } + function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { + const links = getNodeLinks(node); + return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfNode(decl)); + } - // at this point we know that node is the target of assignment - // now check that modification happens inside the statement part of the ForStatement - return !!findAncestor(current, n => n === container ? "quit" : n === container.statement); + function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean { + // skip parenthesized nodes + let current: Node = node; + while (current.parent.kind === SyntaxKind.ParenthesizedExpression) { + current = current.parent; } - function captureLexicalThis(node: Node, container: Node): void { - getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis; - if (container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.Constructor) { - const classNode = container.parent; - getNodeLinks(classNode).flags |= NodeCheckFlags.CaptureThis; - } - else { - getNodeLinks(container).flags |= NodeCheckFlags.CaptureThis; - } + // check if node is used as LHS in some assignment expression + let isAssigned = false; + if (isAssignmentTarget(current)) { + isAssigned = true; + } + else if ((current.parent.kind === SyntaxKind.PrefixUnaryExpression || current.parent.kind === SyntaxKind.PostfixUnaryExpression)) { + const expr = current.parent as PrefixUnaryExpression | PostfixUnaryExpression; + isAssigned = expr.operator === SyntaxKind.PlusPlusToken || expr.operator === SyntaxKind.MinusMinusToken; } - function findFirstSuperCall(node: Node): SuperCall | undefined { - return isSuperCall(node) ? node : - isFunctionLike(node) ? undefined : - forEachChild(node, findFirstSuperCall); + if (!isAssigned) { + return false; } - /** - * Check if the given class-declaration extends null then return true. - * Otherwise, return false - * @param classDecl a class declaration to check if it extends null - */ - function classDeclarationExtendsNull(classDecl: ClassDeclaration): boolean { - const classSymbol = getSymbolOfNode(classDecl); - const classInstanceType = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType; - const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType); + // at this point we know that node is the target of assignment + // now check that modification happens inside the statement part of the ForStatement + return !!findAncestor(current, n => n === container ? "quit" : n === container.statement); + } - return baseConstructorType === nullWideningType; + function captureLexicalThis(node: Node, container: Node): void { + getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis; + if (container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.Constructor) { + const classNode = container.parent; + getNodeLinks(classNode).flags |= NodeCheckFlags.CaptureThis; + } + else { + getNodeLinks(container).flags |= NodeCheckFlags.CaptureThis; } + } + + function findFirstSuperCall(node: Node): SuperCall | undefined { + return isSuperCall(node) ? node : + isFunctionLike(node) ? undefined : + forEachChild(node, findFirstSuperCall); + } + + /** + * Check if the given class-declaration extends null then return true. + * Otherwise, return false + * @param classDecl a class declaration to check if it extends null + */ + function classDeclarationExtendsNull(classDecl: ClassDeclaration): boolean { + const classSymbol = getSymbolOfNode(classDecl); + const classInstanceType = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType; + const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType); + + return baseConstructorType === nullWideningType; + } - function checkThisBeforeSuper(node: Node, container: Node, diagnosticMessage: DiagnosticMessage) { - const containingClassDecl = container.parent as ClassDeclaration; - const baseTypeNode = getClassExtendsHeritageElement(containingClassDecl); + function checkThisBeforeSuper(node: Node, container: Node, diagnosticMessage: DiagnosticMessage) { + const containingClassDecl = container.parent as ClassDeclaration; + const baseTypeNode = getClassExtendsHeritageElement(containingClassDecl); - // If a containing class does not have extends clause or the class extends null - // skip checking whether super statement is called before "this" accessing. - if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) { - if (node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { - error(node, diagnosticMessage); - } + // If a containing class does not have extends clause or the class extends null + // skip checking whether super statement is called before "this" accessing. + if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) { + if (node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { + error(node, diagnosticMessage); } } + } - function checkThisInStaticClassFieldInitializerInDecoratedClass(thisExpression: Node, container: Node) { - if (isPropertyDeclaration(container) && hasStaticModifier(container) && - container.initializer && textRangeContainsPositionInclusive(container.initializer, thisExpression.pos) && hasDecorators(container.parent)) { - error(thisExpression, Diagnostics.Cannot_use_this_in_a_static_property_initializer_of_a_decorated_class); - } + function checkThisInStaticClassFieldInitializerInDecoratedClass(thisExpression: Node, container: Node) { + if (isPropertyDeclaration(container) && hasStaticModifier(container) && + container.initializer && textRangeContainsPositionInclusive(container.initializer, thisExpression.pos) && hasDecorators(container.parent)) { + error(thisExpression, Diagnostics.Cannot_use_this_in_a_static_property_initializer_of_a_decorated_class); } + } - function checkThisExpression(node: Node): Type { - const isNodeInTypeQuery = isInTypeQuery(node); - // Stop at the first arrow function so that we can - // tell whether 'this' needs to be captured. - let container = getThisContainer(node, /* includeArrowFunctions */ true); - let capturedByArrowFunction = false; + function checkThisExpression(node: Node): Type { + const isNodeInTypeQuery = isInTypeQuery(node); + // Stop at the first arrow function so that we can + // tell whether 'this' needs to be captured. + let container = getThisContainer(node, /* includeArrowFunctions */ true); + let capturedByArrowFunction = false; - if (container.kind === SyntaxKind.Constructor) { - checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); - } + if (container.kind === SyntaxKind.Constructor) { + checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); + } - // Now skip arrow functions to get the "real" owner of 'this'. - if (container.kind === SyntaxKind.ArrowFunction) { - container = getThisContainer(container, /* includeArrowFunctions */ false); - capturedByArrowFunction = true; - } + // Now skip arrow functions to get the "real" owner of 'this'. + if (container.kind === SyntaxKind.ArrowFunction) { + container = getThisContainer(container, /* includeArrowFunctions */ false); + capturedByArrowFunction = true; + } - checkThisInStaticClassFieldInitializerInDecoratedClass(node, container); - switch (container.kind) { - case SyntaxKind.ModuleDeclaration: - error(node, Diagnostics.this_cannot_be_referenced_in_a_module_or_namespace_body); - // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks - break; - case SyntaxKind.EnumDeclaration: - error(node, Diagnostics.this_cannot_be_referenced_in_current_location); + checkThisInStaticClassFieldInitializerInDecoratedClass(node, container); + switch (container.kind) { + case SyntaxKind.ModuleDeclaration: + error(node, Diagnostics.this_cannot_be_referenced_in_a_module_or_namespace_body); + // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks + break; + case SyntaxKind.EnumDeclaration: + error(node, Diagnostics.this_cannot_be_referenced_in_current_location); + // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks + break; + case SyntaxKind.Constructor: + if (isInConstructorArgumentInitializer(node, container)) { + error(node, Diagnostics.this_cannot_be_referenced_in_constructor_arguments); // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks - break; - case SyntaxKind.Constructor: - if (isInConstructorArgumentInitializer(node, container)) { - error(node, Diagnostics.this_cannot_be_referenced_in_constructor_arguments); - // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks - } - break; - case SyntaxKind.ComputedPropertyName: - error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name); - break; - } + } + break; + case SyntaxKind.ComputedPropertyName: + error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name); + break; + } - // When targeting es6, mark that we'll need to capture `this` in its lexically bound scope. - if (!isNodeInTypeQuery && capturedByArrowFunction && languageVersion < ScriptTarget.ES2015) { - captureLexicalThis(node, container); - } + // When targeting es6, mark that we'll need to capture `this` in its lexically bound scope. + if (!isNodeInTypeQuery && capturedByArrowFunction && languageVersion < ScriptTarget.ES2015) { + captureLexicalThis(node, container); + } - const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container); - if (noImplicitThis) { - const globalThisType = getTypeOfSymbol(globalThisSymbol); - if (type === globalThisType && capturedByArrowFunction) { - error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this); - } - else if (!type) { - // With noImplicitThis, functions may not reference 'this' if it has type 'any' - const diag = error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation); - if (!isSourceFile(container)) { - const outsideThis = tryGetThisTypeAt(container); - if (outsideThis && outsideThis !== globalThisType) { - addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container)); - } + const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container); + if (noImplicitThis) { + const globalThisType = getTypeOfSymbol(globalThisSymbol); + if (type === globalThisType && capturedByArrowFunction) { + error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this); + } + else if (!type) { + // With noImplicitThis, functions may not reference 'this' if it has type 'any' + const diag = error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation); + if (!isSourceFile(container)) { + const outsideThis = tryGetThisTypeAt(container); + if (outsideThis && outsideThis !== globalThisType) { + addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container)); } } } - return type || anyType; } + return type || anyType; + } - function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined { - const isInJS = isInJSFile(node); - if (isFunctionLike(container) && - (!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) { - let thisType = getThisTypeOfDeclaration(container) || isInJS && getTypeForThisExpressionFromJSDoc(container); - // Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated. - // If this is a function in a JS file, it might be a class method. - if (!thisType) { - const className = getClassNameFromPrototypeMethod(container); - if (isInJS && className) { - const classSymbol = checkExpression(className).symbol; - if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { - thisType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType; - } - } - else if (isJSConstructor(container)) { - thisType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType; + function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined { + const isInJS = isInJSFile(node); + if (isFunctionLike(container) && + (!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) { + let thisType = getThisTypeOfDeclaration(container) || isInJS && getTypeForThisExpressionFromJSDoc(container); + // Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated. + // If this is a function in a JS file, it might be a class method. + if (!thisType) { + const className = getClassNameFromPrototypeMethod(container); + if (isInJS && className) { + const classSymbol = checkExpression(className).symbol; + if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { + thisType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType; } - thisType ||= getContextualThisParameterType(container); } - - if (thisType) { - return getFlowTypeOfReference(node, thisType); + else if (isJSConstructor(container)) { + thisType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType; } + thisType ||= getContextualThisParameterType(container); } - if (isClassLike(container.parent)) { - const symbol = getSymbolOfNode(container.parent); - const type = isStatic(container) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; - return getFlowTypeOfReference(node, type); - } - - if (isSourceFile(container)) { - // look up in the source file's locals or exports - if (container.commonJsModuleIndicator) { - const fileSymbol = getSymbolOfNode(container); - return fileSymbol && getTypeOfSymbol(fileSymbol); - } - else if (container.externalModuleIndicator) { - // TODO: Maybe issue a better error than 'object is possibly undefined' - return undefinedType; - } - else if (includeGlobalThis) { - return getTypeOfSymbol(globalThisSymbol); - } + if (thisType) { + return getFlowTypeOfReference(node, thisType); } } - function getExplicitThisType(node: Expression) { - const container = getThisContainer(node, /*includeArrowFunctions*/ false); - if (isFunctionLike(container)) { - const signature = getSignatureFromDeclaration(container); - if (signature.thisParameter) { - return getExplicitTypeOfSymbol(signature.thisParameter); - } - } - if (isClassLike(container.parent)) { - const symbol = getSymbolOfNode(container.parent); - return isStatic(container) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; - } + if (isClassLike(container.parent)) { + const symbol = getSymbolOfNode(container.parent); + const type = isStatic(container) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; + return getFlowTypeOfReference(node, type); } - function getClassNameFromPrototypeMethod(container: Node) { - // Check if it's the RHS of a x.prototype.y = function [name]() { .... } - if (container.kind === SyntaxKind.FunctionExpression && - isBinaryExpression(container.parent) && - getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty) { - // Get the 'x' of 'x.prototype.y = container' - return ((container.parent // x.prototype.y = container - .left as PropertyAccessExpression) // x.prototype.y - .expression as PropertyAccessExpression) // x.prototype - .expression; // x - } - // x.prototype = { method() { } } - else if (container.kind === SyntaxKind.MethodDeclaration && - container.parent.kind === SyntaxKind.ObjectLiteralExpression && - isBinaryExpression(container.parent.parent) && - getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype) { - return (container.parent.parent.left as PropertyAccessExpression).expression; - } - // x.prototype = { method: function() { } } - else if (container.kind === SyntaxKind.FunctionExpression && - container.parent.kind === SyntaxKind.PropertyAssignment && - container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression && - isBinaryExpression(container.parent.parent.parent) && - getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype) { - return (container.parent.parent.parent.left as PropertyAccessExpression).expression; + if (isSourceFile(container)) { + // look up in the source file's locals or exports + if (container.commonJsModuleIndicator) { + const fileSymbol = getSymbolOfNode(container); + return fileSymbol && getTypeOfSymbol(fileSymbol); } - // Object.defineProperty(x, "method", { value: function() { } }); - // Object.defineProperty(x, "method", { set: (x: () => void) => void }); - // Object.defineProperty(x, "method", { get: () => function() { }) }); - else if (container.kind === SyntaxKind.FunctionExpression && - isPropertyAssignment(container.parent) && - isIdentifier(container.parent.name) && - (container.parent.name.escapedText === "value" || container.parent.name.escapedText === "get" || container.parent.name.escapedText === "set") && - isObjectLiteralExpression(container.parent.parent) && - isCallExpression(container.parent.parent.parent) && - container.parent.parent.parent.arguments[2] === container.parent.parent && - getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { - return (container.parent.parent.parent.arguments[0] as PropertyAccessExpression).expression; + else if (container.externalModuleIndicator) { + // TODO: Maybe issue a better error than 'object is possibly undefined' + return undefinedType; } - // Object.defineProperty(x, "method", { value() { } }); - // Object.defineProperty(x, "method", { set(x: () => void) {} }); - // Object.defineProperty(x, "method", { get() { return () => {} } }); - else if (isMethodDeclaration(container) && - isIdentifier(container.name) && - (container.name.escapedText === "value" || container.name.escapedText === "get" || container.name.escapedText === "set") && - isObjectLiteralExpression(container.parent) && - isCallExpression(container.parent.parent) && - container.parent.parent.arguments[2] === container.parent && - getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { - return (container.parent.parent.arguments[0] as PropertyAccessExpression).expression; + else if (includeGlobalThis) { + return getTypeOfSymbol(globalThisSymbol); } } + } - function getTypeForThisExpressionFromJSDoc(node: Node) { - const jsdocType = getJSDocType(node); - if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) { - const jsDocFunctionType = jsdocType as JSDocFunctionType; - if (jsDocFunctionType.parameters.length > 0 && - jsDocFunctionType.parameters[0].name && - (jsDocFunctionType.parameters[0].name as Identifier).escapedText === InternalSymbolName.This) { - return getTypeFromTypeNode(jsDocFunctionType.parameters[0].type!); - } - } - const thisTag = getJSDocThisTag(node); - if (thisTag && thisTag.typeExpression) { - return getTypeFromTypeNode(thisTag.typeExpression); + function getExplicitThisType(node: Expression) { + const container = getThisContainer(node, /*includeArrowFunctions*/ false); + if (isFunctionLike(container)) { + const signature = getSignatureFromDeclaration(container); + if (signature.thisParameter) { + return getExplicitTypeOfSymbol(signature.thisParameter); } } + if (isClassLike(container.parent)) { + const symbol = getSymbolOfNode(container.parent); + return isStatic(container) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; + } + } + + function getClassNameFromPrototypeMethod(container: Node) { + // Check if it's the RHS of a x.prototype.y = function [name]() { .... } + if (container.kind === SyntaxKind.FunctionExpression && + isBinaryExpression(container.parent) && + getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty) { + // Get the 'x' of 'x.prototype.y = container' + return ((container.parent // x.prototype.y = container + .left as PropertyAccessExpression) // x.prototype.y + .expression as PropertyAccessExpression) // x.prototype + .expression; // x + } + // x.prototype = { method() { } } + else if (container.kind === SyntaxKind.MethodDeclaration && + container.parent.kind === SyntaxKind.ObjectLiteralExpression && + isBinaryExpression(container.parent.parent) && + getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype) { + return (container.parent.parent.left as PropertyAccessExpression).expression; + } + // x.prototype = { method: function() { } } + else if (container.kind === SyntaxKind.FunctionExpression && + container.parent.kind === SyntaxKind.PropertyAssignment && + container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression && + isBinaryExpression(container.parent.parent.parent) && + getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype) { + return (container.parent.parent.parent.left as PropertyAccessExpression).expression; + } + // Object.defineProperty(x, "method", { value: function() { } }); + // Object.defineProperty(x, "method", { set: (x: () => void) => void }); + // Object.defineProperty(x, "method", { get: () => function() { }) }); + else if (container.kind === SyntaxKind.FunctionExpression && + isPropertyAssignment(container.parent) && + isIdentifier(container.parent.name) && + (container.parent.name.escapedText === "value" || container.parent.name.escapedText === "get" || container.parent.name.escapedText === "set") && + isObjectLiteralExpression(container.parent.parent) && + isCallExpression(container.parent.parent.parent) && + container.parent.parent.parent.arguments[2] === container.parent.parent && + getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { + return (container.parent.parent.parent.arguments[0] as PropertyAccessExpression).expression; + } + // Object.defineProperty(x, "method", { value() { } }); + // Object.defineProperty(x, "method", { set(x: () => void) {} }); + // Object.defineProperty(x, "method", { get() { return () => {} } }); + else if (isMethodDeclaration(container) && + isIdentifier(container.name) && + (container.name.escapedText === "value" || container.name.escapedText === "get" || container.name.escapedText === "set") && + isObjectLiteralExpression(container.parent) && + isCallExpression(container.parent.parent) && + container.parent.parent.arguments[2] === container.parent && + getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { + return (container.parent.parent.arguments[0] as PropertyAccessExpression).expression; + } + } - function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { - return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl); + function getTypeForThisExpressionFromJSDoc(node: Node) { + const jsdocType = getJSDocType(node); + if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) { + const jsDocFunctionType = jsdocType as JSDocFunctionType; + if (jsDocFunctionType.parameters.length > 0 && + jsDocFunctionType.parameters[0].name && + (jsDocFunctionType.parameters[0].name as Identifier).escapedText === InternalSymbolName.This) { + return getTypeFromTypeNode(jsDocFunctionType.parameters[0].type!); + } + } + const thisTag = getJSDocThisTag(node); + if (thisTag && thisTag.typeExpression) { + return getTypeFromTypeNode(thisTag.typeExpression); } + } - function checkSuperExpression(node: Node): Type { - const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent as CallExpression).expression === node; + function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { + return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl); + } - const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true); - let container = immediateContainer; - let needToCaptureLexicalThis = false; - let inAsyncFunction = false; + function checkSuperExpression(node: Node): Type { + const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent as CallExpression).expression === node; - // adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting - if (!isCallExpression) { - while (container && container.kind === SyntaxKind.ArrowFunction) { - if (hasSyntacticModifier(container, ModifierFlags.Async)) inAsyncFunction = true; - container = getSuperContainer(container, /*stopOnFunctions*/ true); - needToCaptureLexicalThis = languageVersion < ScriptTarget.ES2015; - } - if (container && hasSyntacticModifier(container, ModifierFlags.Async)) inAsyncFunction = true; - } - - const canUseSuperExpression = isLegalUsageOfSuperExpression(container); - let nodeCheckFlag: NodeCheckFlags = 0; - - if (!canUseSuperExpression) { - // issue more specific error if super is used in computed property name - // class A { foo() { return "1" }} - // class B { - // [super.foo()]() {} - // } - const current = findAncestor(node, n => n === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName); - if (current && current.kind === SyntaxKind.ComputedPropertyName) { - error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name); - } - else if (isCallExpression) { - error(node, Diagnostics.Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors); - } - else if (!container || !container.parent || !(isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression)) { - error(node, Diagnostics.super_can_only_be_referenced_in_members_of_derived_classes_or_object_literal_expressions); - } - else { - error(node, Diagnostics.super_property_access_is_permitted_only_in_a_constructor_member_function_or_member_accessor_of_a_derived_class); - } - return errorType; - } + const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true); + let container = immediateContainer; + let needToCaptureLexicalThis = false; + let inAsyncFunction = false; - if (!isCallExpression && immediateContainer.kind === SyntaxKind.Constructor) { - checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class); + // adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting + if (!isCallExpression) { + while (container && container.kind === SyntaxKind.ArrowFunction) { + if (hasSyntacticModifier(container, ModifierFlags.Async)) inAsyncFunction = true; + container = getSuperContainer(container, /*stopOnFunctions*/ true); + needToCaptureLexicalThis = languageVersion < ScriptTarget.ES2015; } + if (container && hasSyntacticModifier(container, ModifierFlags.Async)) inAsyncFunction = true; + } - if (isStatic(container) || isCallExpression) { - nodeCheckFlag = NodeCheckFlags.SuperStatic; - if (!isCallExpression && - languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 && - (isPropertyDeclaration(container) || isClassStaticBlockDeclaration(container))) { - // for `super.x` or `super[x]` in a static initializer, mark all enclosing - // block scope containers so that we can report potential collisions with - // `Reflect`. - forEachEnclosingBlockScopeContainer(node.parent, current => { - if (!isSourceFile(current) || isExternalOrCommonJsModule(current)) { - getNodeLinks(current).flags |= NodeCheckFlags.ContainsSuperPropertyInStaticInitializer; - } - }); - } + const canUseSuperExpression = isLegalUsageOfSuperExpression(container); + let nodeCheckFlag: NodeCheckFlags = 0; + + if (!canUseSuperExpression) { + // issue more specific error if super is used in computed property name + // class A { foo() { return "1" }} + // class B { + // [super.foo()]() {} + // } + const current = findAncestor(node, n => n === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName); + if (current && current.kind === SyntaxKind.ComputedPropertyName) { + error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name); + } + else if (isCallExpression) { + error(node, Diagnostics.Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors); + } + else if (!container || !container.parent || !(isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression)) { + error(node, Diagnostics.super_can_only_be_referenced_in_members_of_derived_classes_or_object_literal_expressions); } else { - nodeCheckFlag = NodeCheckFlags.SuperInstance; + error(node, Diagnostics.super_property_access_is_permitted_only_in_a_constructor_member_function_or_member_accessor_of_a_derived_class); } + return errorType; + } - getNodeLinks(node).flags |= nodeCheckFlag; + if (!isCallExpression && immediateContainer.kind === SyntaxKind.Constructor) { + checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class); + } - // Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference. - // This is due to the fact that we emit the body of an async function inside of a generator function. As generator - // functions cannot reference `super`, we emit a helper inside of the method body, but outside of the generator. This helper - // uses an arrow function, which is permitted to reference `super`. - // - // There are two primary ways we can access `super` from within an async method. The first is getting the value of a property - // or indexed access on super, either as part of a right-hand-side expression or call expression. The second is when setting the value - // of a property or indexed access, either as part of an assignment expression or destructuring assignment. - // - // The simplest case is reading a value, in which case we will emit something like the following: - // - // // ts - // ... - // async asyncMethod() { - // let x = await super.asyncMethod(); - // return x; - // } - // ... - // - // // js - // ... - // asyncMethod() { - // const _super = Object.create(null, { - // asyncMethod: { get: () => super.asyncMethod }, - // }); - // return __awaiter(this, arguments, Promise, function *() { - // let x = yield _super.asyncMethod.call(this); - // return x; - // }); - // } - // ... - // - // The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases - // are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment: - // - // // ts - // ... - // async asyncMethod(ar: Promise) { - // [super.a, super.b] = await ar; - // } - // ... - // - // // js - // ... - // asyncMethod(ar) { - // const _super = Object.create(null, { - // a: { get: () => super.a, set: (v) => super.a = v }, - // b: { get: () => super.b, set: (v) => super.b = v } - // }; - // return __awaiter(this, arguments, Promise, function *() { - // [_super.a, _super.b] = yield ar; - // }); - // } - // ... - // - // Creating an object that has getter and setters instead of just an accessor function is required for destructuring assignments - // as a call expression cannot be used as the target of a destructuring assignment while a property access can. - // - // For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations. - if (container.kind === SyntaxKind.MethodDeclaration && inAsyncFunction) { - if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) { - getNodeLinks(container).flags |= NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync; - } - else { - getNodeLinks(container).flags |= NodeCheckFlags.MethodWithSuperPropertyAccessInAsync; - } + if (isStatic(container) || isCallExpression) { + nodeCheckFlag = NodeCheckFlags.SuperStatic; + if (!isCallExpression && + languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 && + (isPropertyDeclaration(container) || isClassStaticBlockDeclaration(container))) { + // for `super.x` or `super[x]` in a static initializer, mark all enclosing + // block scope containers so that we can report potential collisions with + // `Reflect`. + forEachEnclosingBlockScopeContainer(node.parent, current => { + if (!isSourceFile(current) || isExternalOrCommonJsModule(current)) { + getNodeLinks(current).flags |= NodeCheckFlags.ContainsSuperPropertyInStaticInitializer; + } + }); } + } + else { + nodeCheckFlag = NodeCheckFlags.SuperInstance; + } - if (needToCaptureLexicalThis) { - // call expressions are allowed only in constructors so they should always capture correct 'this' - // super property access expressions can also appear in arrow functions - - // in this case they should also use correct lexical this - captureLexicalThis(node.parent, container); - } + getNodeLinks(node).flags |= nodeCheckFlag; - if (container.parent.kind === SyntaxKind.ObjectLiteralExpression) { - if (languageVersion < ScriptTarget.ES2015) { - error(node, Diagnostics.super_is_only_allowed_in_members_of_object_literal_expressions_when_option_target_is_ES2015_or_higher); - return errorType; - } - else { - // for object literal assume that type of 'super' is 'any' - return anyType; - } + // Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference. + // This is due to the fact that we emit the body of an async function inside of a generator function. As generator + // functions cannot reference `super`, we emit a helper inside of the method body, but outside of the generator. This helper + // uses an arrow function, which is permitted to reference `super`. + // + // There are two primary ways we can access `super` from within an async method. The first is getting the value of a property + // or indexed access on super, either as part of a right-hand-side expression or call expression. The second is when setting the value + // of a property or indexed access, either as part of an assignment expression or destructuring assignment. + // + // The simplest case is reading a value, in which case we will emit something like the following: + // + // // ts + // ... + // async asyncMethod() { + // let x = await super.asyncMethod(); + // return x; + // } + // ... + // + // // js + // ... + // asyncMethod() { + // const _super = Object.create(null, { + // asyncMethod: { get: () => super.asyncMethod }, + // }); + // return __awaiter(this, arguments, Promise, function *() { + // let x = yield _super.asyncMethod.call(this); + // return x; + // }); + // } + // ... + // + // The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases + // are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment: + // + // // ts + // ... + // async asyncMethod(ar: Promise) { + // [super.a, super.b] = await ar; + // } + // ... + // + // // js + // ... + // asyncMethod(ar) { + // const _super = Object.create(null, { + // a: { get: () => super.a, set: (v) => super.a = v }, + // b: { get: () => super.b, set: (v) => super.b = v } + // }; + // return __awaiter(this, arguments, Promise, function *() { + // [_super.a, _super.b] = yield ar; + // }); + // } + // ... + // + // Creating an object that has getter and setters instead of just an accessor function is required for destructuring assignments + // as a call expression cannot be used as the target of a destructuring assignment while a property access can. + // + // For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations. + if (container.kind === SyntaxKind.MethodDeclaration && inAsyncFunction) { + if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) { + getNodeLinks(container).flags |= NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync; } - - // at this point the only legal case for parent is ClassLikeDeclaration - const classLikeDeclaration = container.parent as ClassLikeDeclaration; - if (!getClassExtendsHeritageElement(classLikeDeclaration)) { - error(node, Diagnostics.super_can_only_be_referenced_in_a_derived_class); - return errorType; + else { + getNodeLinks(container).flags |= NodeCheckFlags.MethodWithSuperPropertyAccessInAsync; } + } - const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(classLikeDeclaration)) as InterfaceType; - const baseClassType = classType && getBaseTypes(classType)[0]; - if (!baseClassType) { - return errorType; - } + if (needToCaptureLexicalThis) { + // call expressions are allowed only in constructors so they should always capture correct 'this' + // super property access expressions can also appear in arrow functions - + // in this case they should also use correct lexical this + captureLexicalThis(node.parent, container); + } - if (container.kind === SyntaxKind.Constructor && isInConstructorArgumentInitializer(node, container)) { - // issue custom error message for super property access in constructor arguments (to be aligned with old compiler) - error(node, Diagnostics.super_cannot_be_referenced_in_constructor_arguments); + if (container.parent.kind === SyntaxKind.ObjectLiteralExpression) { + if (languageVersion < ScriptTarget.ES2015) { + error(node, Diagnostics.super_is_only_allowed_in_members_of_object_literal_expressions_when_option_target_is_ES2015_or_higher); return errorType; } - - return nodeCheckFlag === NodeCheckFlags.SuperStatic - ? getBaseConstructorTypeOfClass(classType) - : getTypeWithThisArgument(baseClassType, classType.thisType); - - function isLegalUsageOfSuperExpression(container: Node): boolean { - if (!container) { - return false; - } - - if (isCallExpression) { - // TS 1.0 SPEC (April 2014): 4.8.1 - // Super calls are only permitted in constructors of derived classes - return container.kind === SyntaxKind.Constructor; - } - else { - // TS 1.0 SPEC (April 2014) - // 'super' property access is allowed - // - In a constructor, instance member function, instance member accessor, or instance member variable initializer where this references a derived class instance - // - In a static member function or static member accessor - - // topmost container must be something that is directly nested in the class declaration\object literal expression - if (isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression) { - if (isStatic(container)) { - return container.kind === SyntaxKind.MethodDeclaration || - container.kind === SyntaxKind.MethodSignature || - container.kind === SyntaxKind.GetAccessor || - container.kind === SyntaxKind.SetAccessor || - container.kind === SyntaxKind.PropertyDeclaration || - container.kind === SyntaxKind.ClassStaticBlockDeclaration; - } - else { - return container.kind === SyntaxKind.MethodDeclaration || - container.kind === SyntaxKind.MethodSignature || - container.kind === SyntaxKind.GetAccessor || - container.kind === SyntaxKind.SetAccessor || - container.kind === SyntaxKind.PropertyDeclaration || - container.kind === SyntaxKind.PropertySignature || - container.kind === SyntaxKind.Constructor; - } - } - } - - return false; + else { + // for object literal assume that type of 'super' is 'any' + return anyType; } } - function getContainingObjectLiteral(func: SignatureDeclaration): ObjectLiteralExpression | undefined { - return (func.kind === SyntaxKind.MethodDeclaration || - func.kind === SyntaxKind.GetAccessor || - func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? func.parent : - func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? func.parent.parent as ObjectLiteralExpression : - undefined; + // at this point the only legal case for parent is ClassLikeDeclaration + const classLikeDeclaration = container.parent as ClassLikeDeclaration; + if (!getClassExtendsHeritageElement(classLikeDeclaration)) { + error(node, Diagnostics.super_can_only_be_referenced_in_a_derived_class); + return errorType; } - function getThisTypeArgument(type: Type): Type | undefined { - return getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target === globalThisType ? getTypeArguments(type as TypeReference)[0] : undefined; + const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(classLikeDeclaration)) as InterfaceType; + const baseClassType = classType && getBaseTypes(classType)[0]; + if (!baseClassType) { + return errorType; } - function getThisTypeFromContextualType(type: Type): Type | undefined { - return mapType(type, t => { - return t.flags & TypeFlags.Intersection ? forEach((t as IntersectionType).types, getThisTypeArgument) : getThisTypeArgument(t); - }); + if (container.kind === SyntaxKind.Constructor && isInConstructorArgumentInitializer(node, container)) { + // issue custom error message for super property access in constructor arguments (to be aligned with old compiler) + error(node, Diagnostics.super_cannot_be_referenced_in_constructor_arguments); + return errorType; } - function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined { - if (func.kind === SyntaxKind.ArrowFunction) { - return undefined; + return nodeCheckFlag === NodeCheckFlags.SuperStatic + ? getBaseConstructorTypeOfClass(classType) + : getTypeWithThisArgument(baseClassType, classType.thisType); + + function isLegalUsageOfSuperExpression(container: Node): boolean { + if (!container) { + return false; } - if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { - const contextualSignature = getContextualSignature(func); - if (contextualSignature) { - const thisParameter = contextualSignature.thisParameter; - if (thisParameter) { - return getTypeOfSymbol(thisParameter); - } - } - } - const inJs = isInJSFile(func); - if (noImplicitThis || inJs) { - const containingLiteral = getContainingObjectLiteral(func); - if (containingLiteral) { - // We have an object literal method. Check if the containing object literal has a contextual type - // that includes a ThisType. If so, T is the contextual type for 'this'. We continue looking in - // any directly enclosing object literals. - const contextualType = getApparentTypeOfContextualType(containingLiteral, /*contextFlags*/ undefined); - let literal = containingLiteral; - let type = contextualType; - while (type) { - const thisType = getThisTypeFromContextualType(type); - if (thisType) { - return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); - } - if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { - break; - } - literal = literal.parent.parent as ObjectLiteralExpression; - type = getApparentTypeOfContextualType(literal, /*contextFlags*/ undefined); - } - // There was no contextual ThisType for the containing object literal, so the contextual type - // for 'this' is the non-null form of the contextual type for the containing object literal or - // the type of the object literal itself. - return getWidenedType(contextualType ? getNonNullableType(contextualType) : checkExpressionCached(containingLiteral)); - } - // In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the - // contextual type for 'this' is 'obj'. - const parent = walkUpParenthesizedExpressions(func.parent); - if (parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - const target = (parent as BinaryExpression).left; - if (isAccessExpression(target)) { - const { expression } = target; - // Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }` - if (inJs && isIdentifier(expression)) { - const sourceFile = getSourceFileOfNode(parent); - if (sourceFile.commonJsModuleIndicator && getResolvedSymbol(expression) === sourceFile.symbol) { - return undefined; - } - } - return getWidenedType(checkExpressionCached(expression)); + if (isCallExpression) { + // TS 1.0 SPEC (April 2014): 4.8.1 + // Super calls are only permitted in constructors of derived classes + return container.kind === SyntaxKind.Constructor; + } + else { + // TS 1.0 SPEC (April 2014) + // 'super' property access is allowed + // - In a constructor, instance member function, instance member accessor, or instance member variable initializer where this references a derived class instance + // - In a static member function or static member accessor + + // topmost container must be something that is directly nested in the class declaration\object literal expression + if (isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression) { + if (isStatic(container)) { + return container.kind === SyntaxKind.MethodDeclaration || + container.kind === SyntaxKind.MethodSignature || + container.kind === SyntaxKind.GetAccessor || + container.kind === SyntaxKind.SetAccessor || + container.kind === SyntaxKind.PropertyDeclaration || + container.kind === SyntaxKind.ClassStaticBlockDeclaration; + } + else { + return container.kind === SyntaxKind.MethodDeclaration || + container.kind === SyntaxKind.MethodSignature || + container.kind === SyntaxKind.GetAccessor || + container.kind === SyntaxKind.SetAccessor || + container.kind === SyntaxKind.PropertyDeclaration || + container.kind === SyntaxKind.PropertySignature || + container.kind === SyntaxKind.Constructor; } } } - return undefined; + + return false; } + } - // Return contextual type of parameter or undefined if no contextual type is available - function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined { - const func = parameter.parent; - if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) { - return undefined; - } - const iife = getImmediatelyInvokedFunctionExpression(func); - if (iife && iife.arguments) { - const args = getEffectiveCallArguments(iife); - const indexOfParameter = func.parameters.indexOf(parameter); - if (parameter.dotDotDotToken) { - return getSpreadArgumentType(args, indexOfParameter, args.length, anyType, /*context*/ undefined, CheckMode.Normal); - } - const links = getNodeLinks(iife); - const cached = links.resolvedSignature; - links.resolvedSignature = anySignature; - const type = indexOfParameter < args.length ? - getWidenedLiteralType(checkExpression(args[indexOfParameter])) : - parameter.initializer ? undefined : undefinedWideningType; - links.resolvedSignature = cached; - return type; - } + function getContainingObjectLiteral(func: SignatureDeclaration): ObjectLiteralExpression | undefined { + return (func.kind === SyntaxKind.MethodDeclaration || + func.kind === SyntaxKind.GetAccessor || + func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? func.parent : + func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? func.parent.parent as ObjectLiteralExpression : + undefined; + } + + function getThisTypeArgument(type: Type): Type | undefined { + return getObjectFlags(type) & ObjectFlags.Reference && (type as TypeReference).target === globalThisType ? getTypeArguments(type as TypeReference)[0] : undefined; + } + + function getThisTypeFromContextualType(type: Type): Type | undefined { + return mapType(type, t => { + return t.flags & TypeFlags.Intersection ? forEach((t as IntersectionType).types, getThisTypeArgument) : getThisTypeArgument(t); + }); + } + + function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined { + if (func.kind === SyntaxKind.ArrowFunction) { + return undefined; + } + if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { const contextualSignature = getContextualSignature(func); if (contextualSignature) { - const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0); - return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ? - getRestTypeAtPosition(contextualSignature, index) : - tryGetTypeAtPosition(contextualSignature, index); - } - } - - function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - return getTypeFromTypeNode(typeNode); - } - switch (declaration.kind) { - case SyntaxKind.Parameter: - return getContextuallyTypedParameterType(declaration); - case SyntaxKind.BindingElement: - return getContextualTypeForBindingElement(declaration, contextFlags); - case SyntaxKind.PropertyDeclaration: - if (isStatic(declaration)) { - return getContextualTypeForStaticPropertyDeclaration(declaration, contextFlags); + const thisParameter = contextualSignature.thisParameter; + if (thisParameter) { + return getTypeOfSymbol(thisParameter); + } + } + } + const inJs = isInJSFile(func); + if (noImplicitThis || inJs) { + const containingLiteral = getContainingObjectLiteral(func); + if (containingLiteral) { + // We have an object literal method. Check if the containing object literal has a contextual type + // that includes a ThisType. If so, T is the contextual type for 'this'. We continue looking in + // any directly enclosing object literals. + const contextualType = getApparentTypeOfContextualType(containingLiteral, /*contextFlags*/ undefined); + let literal = containingLiteral; + let type = contextualType; + while (type) { + const thisType = getThisTypeFromContextualType(type); + if (thisType) { + return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); + } + if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { + break; + } + literal = literal.parent.parent as ObjectLiteralExpression; + type = getApparentTypeOfContextualType(literal, /*contextFlags*/ undefined); + } + // There was no contextual ThisType for the containing object literal, so the contextual type + // for 'this' is the non-null form of the contextual type for the containing object literal or + // the type of the object literal itself. + return getWidenedType(contextualType ? getNonNullableType(contextualType) : checkExpressionCached(containingLiteral)); + } + // In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the + // contextual type for 'this' is 'obj'. + const parent = walkUpParenthesizedExpressions(func.parent); + if (parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + const target = (parent as BinaryExpression).left; + if (isAccessExpression(target)) { + const { expression } = target; + // Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }` + if (inJs && isIdentifier(expression)) { + const sourceFile = getSourceFileOfNode(parent); + if (sourceFile.commonJsModuleIndicator && getResolvedSymbol(expression) === sourceFile.symbol) { + return undefined; + } } - // By default, do nothing and return undefined - only the above cases have context implied by a parent + + return getWidenedType(checkExpressionCached(expression)); + } } } + return undefined; + } - function getContextualTypeForBindingElement(declaration: BindingElement, contextFlags: ContextFlags | undefined): Type | undefined { - const parent = declaration.parent.parent; - const name = declaration.propertyName || declaration.name; - const parentType = getContextualTypeForVariableLikeDeclaration(parent, contextFlags) || - parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent, declaration.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal); - if (!parentType || isBindingPattern(name) || isComputedNonLiteralName(name)) return undefined; - if (parent.name.kind === SyntaxKind.ArrayBindingPattern) { - const index = indexOfNode(declaration.parent.elements, declaration); - if (index < 0) return undefined; - return getContextualTypeForElementExpression(parentType, index); - } - const nameType = getLiteralTypeFromPropertyName(name); - if (isTypeUsableAsPropertyName(nameType)) { - const text = getPropertyNameFromType(nameType); - return getTypeOfPropertyOfType(parentType, text); + // Return contextual type of parameter or undefined if no contextual type is available + function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined { + const func = parameter.parent; + if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + return undefined; + } + const iife = getImmediatelyInvokedFunctionExpression(func); + if (iife && iife.arguments) { + const args = getEffectiveCallArguments(iife); + const indexOfParameter = func.parameters.indexOf(parameter); + if (parameter.dotDotDotToken) { + return getSpreadArgumentType(args, indexOfParameter, args.length, anyType, /*context*/ undefined, CheckMode.Normal); } + const links = getNodeLinks(iife); + const cached = links.resolvedSignature; + links.resolvedSignature = anySignature; + const type = indexOfParameter < args.length ? + getWidenedLiteralType(checkExpression(args[indexOfParameter])) : + parameter.initializer ? undefined : undefinedWideningType; + links.resolvedSignature = cached; + return type; } - - function getContextualTypeForStaticPropertyDeclaration(declaration: PropertyDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { - const parentType = isExpression(declaration.parent) && getContextualType(declaration.parent, contextFlags); - if (!parentType) return undefined; - return getTypeOfPropertyOfContextualType(parentType, getSymbolOfNode(declaration).escapedName); + const contextualSignature = getContextualSignature(func); + if (contextualSignature) { + const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0); + return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ? + getRestTypeAtPosition(contextualSignature, index) : + tryGetTypeAtPosition(contextualSignature, index); } + } - // In a variable, parameter or property declaration with a type annotation, - // the contextual type of an initializer expression is the type of the variable, parameter or property. - // Otherwise, in a parameter declaration of a contextually typed function expression, - // the contextual type of an initializer expression is the contextual type of the parameter. - // Otherwise, in a variable or parameter declaration with a binding pattern name, - // the contextual type of an initializer expression is the type implied by the binding pattern. - // Otherwise, in a binding pattern inside a variable or parameter declaration, - // the contextual type of an initializer expression is the type annotation of the containing declaration, if present. - function getContextualTypeForInitializerExpression(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { - const declaration = node.parent as VariableLikeDeclaration; - if (hasInitializer(declaration) && node === declaration.initializer) { - const result = getContextualTypeForVariableLikeDeclaration(declaration, contextFlags); - if (result) { - return result; - } - if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name) && declaration.name.elements.length > 0) { - return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); + function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); + } + switch (declaration.kind) { + case SyntaxKind.Parameter: + return getContextuallyTypedParameterType(declaration); + case SyntaxKind.BindingElement: + return getContextualTypeForBindingElement(declaration, contextFlags); + case SyntaxKind.PropertyDeclaration: + if (isStatic(declaration)) { + return getContextualTypeForStaticPropertyDeclaration(declaration, contextFlags); } - } - return undefined; + // By default, do nothing and return undefined - only the above cases have context implied by a parent } + } - function getContextualTypeForReturnExpression(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { - const func = getContainingFunction(node); - if (func) { - let contextualReturnType = getContextualReturnType(func, contextFlags); - if (contextualReturnType) { - const functionFlags = getFunctionFlags(func); - if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function - const isAsyncGenerator = (functionFlags & FunctionFlags.Async) !== 0; - if (contextualReturnType.flags & TypeFlags.Union) { - contextualReturnType = filterType(contextualReturnType, type => !!getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsyncGenerator)); - } - const iterationReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, contextualReturnType, (functionFlags & FunctionFlags.Async) !== 0); - if (!iterationReturnType) { - return undefined; - } - contextualReturnType = iterationReturnType; - // falls through to unwrap Promise for AsyncGenerators - } + function getContextualTypeForBindingElement(declaration: BindingElement, contextFlags: ContextFlags | undefined): Type | undefined { + const parent = declaration.parent.parent; + const name = declaration.propertyName || declaration.name; + const parentType = getContextualTypeForVariableLikeDeclaration(parent, contextFlags) || + parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent, declaration.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal); + if (!parentType || isBindingPattern(name) || isComputedNonLiteralName(name)) return undefined; + if (parent.name.kind === SyntaxKind.ArrayBindingPattern) { + const index = indexOfNode(declaration.parent.elements, declaration); + if (index < 0) return undefined; + return getContextualTypeForElementExpression(parentType, index); + } + const nameType = getLiteralTypeFromPropertyName(name); + if (isTypeUsableAsPropertyName(nameType)) { + const text = getPropertyNameFromType(nameType); + return getTypeOfPropertyOfType(parentType, text); + } + } - if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function - // Get the awaited type without the `Awaited` alias - const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias); - return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); - } + function getContextualTypeForStaticPropertyDeclaration(declaration: PropertyDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { + const parentType = isExpression(declaration.parent) && getContextualType(declaration.parent, contextFlags); + if (!parentType) return undefined; + return getTypeOfPropertyOfContextualType(parentType, getSymbolOfNode(declaration).escapedName); + } - return contextualReturnType; // Regular function or Generator function - } + // In a variable, parameter or property declaration with a type annotation, + // the contextual type of an initializer expression is the type of the variable, parameter or property. + // Otherwise, in a parameter declaration of a contextually typed function expression, + // the contextual type of an initializer expression is the contextual type of the parameter. + // Otherwise, in a variable or parameter declaration with a binding pattern name, + // the contextual type of an initializer expression is the type implied by the binding pattern. + // Otherwise, in a binding pattern inside a variable or parameter declaration, + // the contextual type of an initializer expression is the type annotation of the containing declaration, if present. + function getContextualTypeForInitializerExpression(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { + const declaration = node.parent as VariableLikeDeclaration; + if (hasInitializer(declaration) && node === declaration.initializer) { + const result = getContextualTypeForVariableLikeDeclaration(declaration, contextFlags); + if (result) { + return result; } - return undefined; - } - - function getContextualTypeForAwaitOperand(node: AwaitExpression, contextFlags: ContextFlags | undefined): Type | undefined { - const contextualType = getContextualType(node, contextFlags); - if (contextualType) { - const contextualAwaitedType = getAwaitedTypeNoAlias(contextualType); - return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); + if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name) && declaration.name.elements.length > 0) { + return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); } - return undefined; } + return undefined; + } - function getContextualTypeForYieldOperand(node: YieldExpression, contextFlags: ContextFlags | undefined): Type | undefined { - const func = getContainingFunction(node); - if (func) { + function getContextualTypeForReturnExpression(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { + const func = getContainingFunction(node); + if (func) { + let contextualReturnType = getContextualReturnType(func, contextFlags); + if (contextualReturnType) { const functionFlags = getFunctionFlags(func); - let contextualReturnType = getContextualReturnType(func, contextFlags); - if (contextualReturnType) { + if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function const isAsyncGenerator = (functionFlags & FunctionFlags.Async) !== 0; - if (!node.asteriskToken && contextualReturnType.flags & TypeFlags.Union) { + if (contextualReturnType.flags & TypeFlags.Union) { contextualReturnType = filterType(contextualReturnType, type => !!getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsyncGenerator)); } - return node.asteriskToken - ? contextualReturnType - : getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, isAsyncGenerator); + const iterationReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, contextualReturnType, (functionFlags & FunctionFlags.Async) !== 0); + if (!iterationReturnType) { + return undefined; + } + contextualReturnType = iterationReturnType; + // falls through to unwrap Promise for AsyncGenerators } - } - - return undefined; - } - function isInParameterInitializerBeforeContainingFunction(node: Node) { - let inBindingInitializer = false; - while (node.parent && !isFunctionLike(node.parent)) { - if (isParameter(node.parent) && (inBindingInitializer || node.parent.initializer === node)) { - return true; - } - if (isBindingElement(node.parent) && node.parent.initializer === node) { - inBindingInitializer = true; + if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function + // Get the awaited type without the `Awaited` alias + const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias); + return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } - node = node.parent; + return contextualReturnType; // Regular function or Generator function } + } + return undefined; + } - return false; + function getContextualTypeForAwaitOperand(node: AwaitExpression, contextFlags: ContextFlags | undefined): Type | undefined { + const contextualType = getContextualType(node, contextFlags); + if (contextualType) { + const contextualAwaitedType = getAwaitedTypeNoAlias(contextualType); + return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } + return undefined; + } - function getContextualIterationType(kind: IterationTypeKind, functionDecl: SignatureDeclaration): Type | undefined { - const isAsync = !!(getFunctionFlags(functionDecl) & FunctionFlags.Async); - const contextualReturnType = getContextualReturnType(functionDecl, /*contextFlags*/ undefined); + function getContextualTypeForYieldOperand(node: YieldExpression, contextFlags: ContextFlags | undefined): Type | undefined { + const func = getContainingFunction(node); + if (func) { + const functionFlags = getFunctionFlags(func); + let contextualReturnType = getContextualReturnType(func, contextFlags); if (contextualReturnType) { - return getIterationTypeOfGeneratorFunctionReturnType(kind, contextualReturnType, isAsync) - || undefined; + const isAsyncGenerator = (functionFlags & FunctionFlags.Async) !== 0; + if (!node.asteriskToken && contextualReturnType.flags & TypeFlags.Union) { + contextualReturnType = filterType(contextualReturnType, type => !!getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsyncGenerator)); + } + return node.asteriskToken + ? contextualReturnType + : getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, isAsyncGenerator); } - - return undefined; } - function getContextualReturnType(functionDecl: SignatureDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { - // If the containing function has a return type annotation, is a constructor, or is a get accessor whose - // corresponding set accessor has a type annotation, return statements in the function are contextually typed - const returnType = getReturnTypeFromAnnotation(functionDecl); - if (returnType) { - return returnType; - } - // Otherwise, if the containing function is contextually typed by a function type with exactly one call signature - // and that call signature is non-generic, return statements are contextually typed by the return type of the signature - const signature = getContextualSignatureForFunctionLikeDeclaration(functionDecl as FunctionExpression); - if (signature && !isResolvingReturnTypeOfSignature(signature)) { - return getReturnTypeOfSignature(signature); + return undefined; + } + + function isInParameterInitializerBeforeContainingFunction(node: Node) { + let inBindingInitializer = false; + while (node.parent && !isFunctionLike(node.parent)) { + if (isParameter(node.parent) && (inBindingInitializer || node.parent.initializer === node)) { + return true; } - const iife = getImmediatelyInvokedFunctionExpression(functionDecl); - if (iife) { - return getContextualType(iife, contextFlags); + if (isBindingElement(node.parent) && node.parent.initializer === node) { + inBindingInitializer = true; } - return undefined; - } - // In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter. - function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type | undefined { - const args = getEffectiveCallArguments(callTarget); - const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression - return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex); + node = node.parent; } - function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type { - if (isImportCall(callTarget)) { - return argIndex === 0 ? stringType : - argIndex === 1 ? getGlobalImportCallOptionsType(/*reportErrors*/ false) : - anyType; - } - - // If we're already in the process of resolving the given signature, don't resolve again as - // that could cause infinite recursion. Instead, return anySignature. - const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); + return false; + } - if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { - return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); - } - const restIndex = signature.parameters.length - 1; - return signatureHasRestParameter(signature) && argIndex >= restIndex ? - getIndexedAccessType(getTypeOfSymbol(signature.parameters[restIndex]), getNumberLiteralType(argIndex - restIndex), AccessFlags.Contextual) : - getTypeAtPosition(signature, argIndex); + function getContextualIterationType(kind: IterationTypeKind, functionDecl: SignatureDeclaration): Type | undefined { + const isAsync = !!(getFunctionFlags(functionDecl) & FunctionFlags.Async); + const contextualReturnType = getContextualReturnType(functionDecl, /*contextFlags*/ undefined); + if (contextualReturnType) { + return getIterationTypeOfGeneratorFunctionReturnType(kind, contextualReturnType, isAsync) + || undefined; } - function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { - if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) { - return getContextualTypeForArgument(template.parent as TaggedTemplateExpression, substitutionExpression); - } + return undefined; + } - return undefined; + function getContextualReturnType(functionDecl: SignatureDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { + // If the containing function has a return type annotation, is a constructor, or is a get accessor whose + // corresponding set accessor has a type annotation, return statements in the function are contextually typed + const returnType = getReturnTypeFromAnnotation(functionDecl); + if (returnType) { + return returnType; + } + // Otherwise, if the containing function is contextually typed by a function type with exactly one call signature + // and that call signature is non-generic, return statements are contextually typed by the return type of the signature + const signature = getContextualSignatureForFunctionLikeDeclaration(functionDecl as FunctionExpression); + if (signature && !isResolvingReturnTypeOfSignature(signature)) { + return getReturnTypeOfSignature(signature); } + const iife = getImmediatelyInvokedFunctionExpression(functionDecl); + if (iife) { + return getContextualType(iife, contextFlags); + } + return undefined; + } - function getContextualTypeForBinaryOperand(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { - const binaryExpression = node.parent as BinaryExpression; - const { left, operatorToken, right } = binaryExpression; - switch (operatorToken.kind) { - case SyntaxKind.EqualsToken: - case SyntaxKind.AmpersandAmpersandEqualsToken: - case SyntaxKind.BarBarEqualsToken: - case SyntaxKind.QuestionQuestionEqualsToken: - return node === right ? getContextualTypeForAssignmentDeclaration(binaryExpression) : undefined; - case SyntaxKind.BarBarToken: - case SyntaxKind.QuestionQuestionToken: - // 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 node === right && (type && type.pattern || !type && !isDefaultedExpandoInitializer(binaryExpression)) ? - getTypeOfExpression(left) : type; - case SyntaxKind.AmpersandAmpersandToken: - case SyntaxKind.CommaToken: - return node === right ? getContextualType(binaryExpression, contextFlags) : undefined; - default: - return undefined; - } + // In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter. + function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type | undefined { + const args = getEffectiveCallArguments(callTarget); + const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression + return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex); + } + + function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type { + if (isImportCall(callTarget)) { + return argIndex === 0 ? stringType : + argIndex === 1 ? getGlobalImportCallOptionsType(/*reportErrors*/ false) : + anyType; } - /** - * Try to find a resolved symbol for an expression without also resolving its type, as - * getSymbolAtLocation would (as that could be reentrant into contextual typing) - */ - function getSymbolForExpression(e: Expression) { - if (e.symbol) { - return e.symbol; - } - if (isIdentifier(e)) { - return getResolvedSymbol(e); - } - if (isPropertyAccessExpression(e)) { - const lhsType = getTypeOfExpression(e.expression); - return isPrivateIdentifier(e.name) ? tryGetPrivateIdentifierPropertyOfType(lhsType, e.name) : getPropertyOfType(lhsType, e.name.escapedText); - } - if (isElementAccessExpression(e)) { - const propType = checkExpressionCached(e.argumentExpression); - if (!isTypeUsableAsPropertyName(propType)) { - return undefined; - } - const lhsType = getTypeOfExpression(e.expression); - return getPropertyOfType(lhsType, getPropertyNameFromType(propType)); - } - return undefined; + // If we're already in the process of resolving the given signature, don't resolve again as + // that could cause infinite recursion. Instead, return anySignature. + const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget); - function tryGetPrivateIdentifierPropertyOfType(type: Type, id: PrivateIdentifier) { - const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(id.escapedText, id); - return lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(type, lexicallyScopedSymbol); - } + if (isJsxOpeningLikeElement(callTarget) && argIndex === 0) { + return getEffectiveFirstArgumentForJsxSignature(signature, callTarget); } + const restIndex = signature.parameters.length - 1; + return signatureHasRestParameter(signature) && argIndex >= restIndex ? + getIndexedAccessType(getTypeOfSymbol(signature.parameters[restIndex]), getNumberLiteralType(argIndex - restIndex), AccessFlags.Contextual) : + getTypeAtPosition(signature, argIndex); + } - // In an assignment expression, the right operand is contextually typed by the type of the left operand. - // Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand. - function getContextualTypeForAssignmentDeclaration(binaryExpression: BinaryExpression): Type | undefined { - const kind = getAssignmentDeclarationKind(binaryExpression); - switch (kind) { - case AssignmentDeclarationKind.None: - case AssignmentDeclarationKind.ThisProperty: - const lhsSymbol = getSymbolForExpression(binaryExpression.left); - const decl = lhsSymbol && lhsSymbol.valueDeclaration; - // Unannotated, uninitialized property declarations have a type implied by their usage in the constructor. - // We avoid calling back into `getTypeOfExpression` and reentering contextual typing to avoid a bogus circularity error in that case. - if (decl && (isPropertyDeclaration(decl) || isPropertySignature(decl))) { - const overallAnnotation = getEffectiveTypeAnnotationNode(decl); - return (overallAnnotation && instantiateType(getTypeFromTypeNode(overallAnnotation), getSymbolLinks(lhsSymbol).mapper)) || - (isPropertyDeclaration(decl) ? decl.initializer && getTypeOfExpression(binaryExpression.left) : undefined); - } - if (kind === AssignmentDeclarationKind.None) { - return getTypeOfExpression(binaryExpression.left); - } - return getContextualTypeForThisPropertyAssignment(binaryExpression); - case AssignmentDeclarationKind.Property: - if (isPossiblyAliasedThisProperty(binaryExpression, kind)) { - return getContextualTypeForThisPropertyAssignment(binaryExpression); - } - // If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration. - // See `bindStaticPropertyAssignment` in `binder.ts`. - else if (!binaryExpression.left.symbol) { - return getTypeOfExpression(binaryExpression.left); - } - else { - const decl = binaryExpression.left.symbol.valueDeclaration; - if (!decl) { - return undefined; - } - const lhs = cast(binaryExpression.left, isAccessExpression); - const overallAnnotation = getEffectiveTypeAnnotationNode(decl); - if (overallAnnotation) { - return getTypeFromTypeNode(overallAnnotation); - } - else if (isIdentifier(lhs.expression)) { - const id = lhs.expression; - const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true); - if (parentSymbol) { - const annotated = parentSymbol.valueDeclaration && getEffectiveTypeAnnotationNode(parentSymbol.valueDeclaration); - if (annotated) { - const nameStr = getElementOrPropertyAccessName(lhs); - if (nameStr !== undefined) { - return getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr); - } - } - return undefined; - } - } - return isInJSFile(decl) ? undefined : getTypeOfExpression(binaryExpression.left); - } - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.Prototype: - case AssignmentDeclarationKind.PrototypeProperty: - case AssignmentDeclarationKind.ModuleExports: - let valueDeclaration: Declaration | undefined; - if (kind !== AssignmentDeclarationKind.ModuleExports) { - valueDeclaration = binaryExpression.left.symbol?.valueDeclaration; - } - valueDeclaration ||= binaryExpression.symbol?.valueDeclaration; - const annotated = valueDeclaration && getEffectiveTypeAnnotationNode(valueDeclaration); - return annotated ? getTypeFromTypeNode(annotated) : undefined; - case AssignmentDeclarationKind.ObjectDefinePropertyValue: - case AssignmentDeclarationKind.ObjectDefinePropertyExports: - case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: - return Debug.fail("Does not apply"); - default: - return Debug.assertNever(kind); - } + function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { + if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) { + return getContextualTypeForArgument(template.parent as TaggedTemplateExpression, substitutionExpression); } - function isPossiblyAliasedThisProperty(declaration: BinaryExpression, kind = getAssignmentDeclarationKind(declaration)) { - if (kind === AssignmentDeclarationKind.ThisProperty) { - return true; - } - if (!isInJSFile(declaration) || kind !== AssignmentDeclarationKind.Property || !isIdentifier((declaration.left as AccessExpression).expression)) { - return false; - } - const name = ((declaration.left as AccessExpression).expression as Identifier).escapedText; - const symbol = resolveName(declaration.left, name, SymbolFlags.Value, undefined, undefined, /*isUse*/ true, /*excludeGlobals*/ true); - return isThisInitializedDeclaration(symbol?.valueDeclaration); + return undefined; + } + + function getContextualTypeForBinaryOperand(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { + const binaryExpression = node.parent as BinaryExpression; + const { left, operatorToken, right } = binaryExpression; + switch (operatorToken.kind) { + case SyntaxKind.EqualsToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: + case SyntaxKind.BarBarEqualsToken: + case SyntaxKind.QuestionQuestionEqualsToken: + return node === right ? getContextualTypeForAssignmentDeclaration(binaryExpression) : undefined; + case SyntaxKind.BarBarToken: + case SyntaxKind.QuestionQuestionToken: + // 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 node === right && (type && type.pattern || !type && !isDefaultedExpandoInitializer(binaryExpression)) ? + getTypeOfExpression(left) : type; + case SyntaxKind.AmpersandAmpersandToken: + case SyntaxKind.CommaToken: + return node === right ? getContextualType(binaryExpression, contextFlags) : undefined; + default: + return undefined; } + } - function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression): Type | undefined { - if (!binaryExpression.symbol) return getTypeOfExpression(binaryExpression.left); - if (binaryExpression.symbol.valueDeclaration) { - const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration); - if (annotated) { - const type = getTypeFromTypeNode(annotated); - if (type) { - return type; - } - } - } - const thisAccess = cast(binaryExpression.left, isAccessExpression); - if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) { + /** + * Try to find a resolved symbol for an expression without also resolving its type, as + * getSymbolAtLocation would (as that could be reentrant into contextual typing) + */ + function getSymbolForExpression(e: Expression) { + if (e.symbol) { + return e.symbol; + } + if (isIdentifier(e)) { + return getResolvedSymbol(e); + } + if (isPropertyAccessExpression(e)) { + const lhsType = getTypeOfExpression(e.expression); + return isPrivateIdentifier(e.name) ? tryGetPrivateIdentifierPropertyOfType(lhsType, e.name) : getPropertyOfType(lhsType, e.name.escapedText); + } + if (isElementAccessExpression(e)) { + const propType = checkExpressionCached(e.argumentExpression); + if (!isTypeUsableAsPropertyName(propType)) { return undefined; } - const thisType = checkThisExpression(thisAccess.expression); - const nameStr = getElementOrPropertyAccessName(thisAccess); - return nameStr !== undefined && getTypeOfPropertyOfContextualType(thisType, nameStr) || undefined; - + const lhsType = getTypeOfExpression(e.expression); + return getPropertyOfType(lhsType, getPropertyNameFromType(propType)); } + return undefined; - function isCircularMappedProperty(symbol: Symbol) { - return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0); + function tryGetPrivateIdentifierPropertyOfType(type: Type, id: PrivateIdentifier) { + const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(id.escapedText, id); + return lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(type, lexicallyScopedSymbol); } + } - function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) { - return mapType(type, t => { - if (isGenericMappedType(t) && !t.declaration.nameType) { - const constraint = getConstraintTypeFromMappedType(t); - const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint; - const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name)); - if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) { - return substituteIndexedMappedType(t, propertyNameType); - } + // In an assignment expression, the right operand is contextually typed by the type of the left operand. + // Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand. + function getContextualTypeForAssignmentDeclaration(binaryExpression: BinaryExpression): Type | undefined { + const kind = getAssignmentDeclarationKind(binaryExpression); + switch (kind) { + case AssignmentDeclarationKind.None: + case AssignmentDeclarationKind.ThisProperty: + const lhsSymbol = getSymbolForExpression(binaryExpression.left); + const decl = lhsSymbol && lhsSymbol.valueDeclaration; + // Unannotated, uninitialized property declarations have a type implied by their usage in the constructor. + // We avoid calling back into `getTypeOfExpression` and reentering contextual typing to avoid a bogus circularity error in that case. + if (decl && (isPropertyDeclaration(decl) || isPropertySignature(decl))) { + const overallAnnotation = getEffectiveTypeAnnotationNode(decl); + return (overallAnnotation && instantiateType(getTypeFromTypeNode(overallAnnotation), getSymbolLinks(lhsSymbol).mapper)) || + (isPropertyDeclaration(decl) ? decl.initializer && getTypeOfExpression(binaryExpression.left) : undefined); + } + if (kind === AssignmentDeclarationKind.None) { + return getTypeOfExpression(binaryExpression.left); + } + return getContextualTypeForThisPropertyAssignment(binaryExpression); + case AssignmentDeclarationKind.Property: + if (isPossiblyAliasedThisProperty(binaryExpression, kind)) { + return getContextualTypeForThisPropertyAssignment(binaryExpression); } - else if (t.flags & TypeFlags.StructuredType) { - const prop = getPropertyOfType(t, name); - if (prop) { - return isCircularMappedProperty(prop) ? undefined : getTypeOfSymbol(prop); + // If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration. + // See `bindStaticPropertyAssignment` in `binder.ts`. + else if (!binaryExpression.left.symbol) { + return getTypeOfExpression(binaryExpression.left); + } + else { + const decl = binaryExpression.left.symbol.valueDeclaration; + if (!decl) { + return undefined; } - if (isTupleType(t)) { - const restType = getRestTypeOfTupleType(t); - if (restType && isNumericLiteralName(name) && +name >= 0) { - return restType; + const lhs = cast(binaryExpression.left, isAccessExpression); + const overallAnnotation = getEffectiveTypeAnnotationNode(decl); + if (overallAnnotation) { + return getTypeFromTypeNode(overallAnnotation); + } + else if (isIdentifier(lhs.expression)) { + const id = lhs.expression; + const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true); + if (parentSymbol) { + const annotated = parentSymbol.valueDeclaration && getEffectiveTypeAnnotationNode(parentSymbol.valueDeclaration); + if (annotated) { + const nameStr = getElementOrPropertyAccessName(lhs); + if (nameStr !== undefined) { + return getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr); + } + } + return undefined; } } - return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type; + return isInJSFile(decl) ? undefined : getTypeOfExpression(binaryExpression.left); } - return undefined; - }, /*noReductions*/ true); + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.Prototype: + case AssignmentDeclarationKind.PrototypeProperty: + case AssignmentDeclarationKind.ModuleExports: + let valueDeclaration: Declaration | undefined; + if (kind !== AssignmentDeclarationKind.ModuleExports) { + valueDeclaration = binaryExpression.left.symbol?.valueDeclaration; + } + valueDeclaration ||= binaryExpression.symbol?.valueDeclaration; + const annotated = valueDeclaration && getEffectiveTypeAnnotationNode(valueDeclaration); + return annotated ? getTypeFromTypeNode(annotated) : undefined; + case AssignmentDeclarationKind.ObjectDefinePropertyValue: + case AssignmentDeclarationKind.ObjectDefinePropertyExports: + case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: + return Debug.fail("Does not apply"); + default: + return Debug.assertNever(kind); } + } - // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of - // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one - // exists. Otherwise, it is the type of the string index signature in T, if one exists. - function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { - Debug.assert(isObjectLiteralMethod(node)); - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; - } - return getContextualTypeForObjectLiteralElement(node, contextFlags); + function isPossiblyAliasedThisProperty(declaration: BinaryExpression, kind = getAssignmentDeclarationKind(declaration)) { + if (kind === AssignmentDeclarationKind.ThisProperty) { + return true; + } + if (!isInJSFile(declaration) || kind !== AssignmentDeclarationKind.Property || !isIdentifier((declaration.left as AccessExpression).expression)) { + return false; } + const name = ((declaration.left as AccessExpression).expression as Identifier).escapedText; + const symbol = resolveName(declaration.left, name, SymbolFlags.Value, undefined, undefined, /*isUse*/ true, /*excludeGlobals*/ true); + return isThisInitializedDeclaration(symbol?.valueDeclaration); + } - function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike, contextFlags: ContextFlags | undefined) { - const objectLiteral = element.parent as ObjectLiteralExpression; - const propertyAssignmentType = isPropertyAssignment(element) && getContextualTypeForVariableLikeDeclaration(element, contextFlags); - if (propertyAssignmentType) { - return propertyAssignmentType; - } - const type = getApparentTypeOfContextualType(objectLiteral, contextFlags); - if (type) { - if (hasBindableName(element)) { - // For a (non-symbol) computed property, there is no reason to look up the name - // in the type. It will just be "__computed", which does not appear in any - // SymbolTable. - const symbol = getSymbolOfNode(element); - return getTypeOfPropertyOfContextualType(type, symbol.escapedName, getSymbolLinks(symbol).nameType); - } - if (element.name) { - const nameType = getLiteralTypeFromPropertyName(element.name); - // We avoid calling getApplicableIndexInfo here because it performs potentially expensive intersection reduction. - return mapType(type, t => findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType)?.type, /*noReductions*/ true); + function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression): Type | undefined { + if (!binaryExpression.symbol) return getTypeOfExpression(binaryExpression.left); + if (binaryExpression.symbol.valueDeclaration) { + const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration); + if (annotated) { + const type = getTypeFromTypeNode(annotated); + if (type) { + return type; } } + } + const thisAccess = cast(binaryExpression.left, isAccessExpression); + if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) { return undefined; } + const thisType = checkThisExpression(thisAccess.expression); + const nameStr = getElementOrPropertyAccessName(thisAccess); + return nameStr !== undefined && getTypeOfPropertyOfContextualType(thisType, nameStr) || undefined; - // In an array literal contextually typed by a type T, the contextual type of an element expression at index N is - // the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature, - // it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated - // type of T. - function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined { - return arrayContextualType && ( - getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String) - || mapType( - arrayContextualType, - t => getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false), - /*noReductions*/ true)); - } + } - // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type. - function getContextualTypeForConditionalOperand(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { - const conditional = node.parent as ConditionalExpression; - return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional, contextFlags) : undefined; - } + function isCircularMappedProperty(symbol: Symbol) { + return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0); + } - function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild, contextFlags: ContextFlags | undefined) { - const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName, contextFlags); - // JSX expression is in children of JSX Element, we will look for an "children" attribute (we get the name from JSX.ElementAttributesProperty) - const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); - if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) { - return undefined; + function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) { + return mapType(type, t => { + if (isGenericMappedType(t) && !t.declaration.nameType) { + const constraint = getConstraintTypeFromMappedType(t); + const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint; + const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name)); + if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) { + return substituteIndexedMappedType(t, propertyNameType); + } } - const realChildren = getSemanticJsxChildren(node.children); - const childIndex = realChildren.indexOf(child); - const childFieldType = getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName); - return childFieldType && (realChildren.length === 1 ? childFieldType : mapType(childFieldType, t => { - if (isArrayLikeType(t)) { - return getIndexedAccessType(t, getNumberLiteralType(childIndex)); + else if (t.flags & TypeFlags.StructuredType) { + const prop = getPropertyOfType(t, name); + if (prop) { + return isCircularMappedProperty(prop) ? undefined : getTypeOfSymbol(prop); } - else { - return t; + if (isTupleType(t)) { + const restType = getRestTypeOfTupleType(t); + if (restType && isNumericLiteralName(name) && +name >= 0) { + return restType; + } } - }, /*noReductions*/ true)); + return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type; + } + return undefined; + }, /*noReductions*/ true); + } + + // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of + // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one + // exists. Otherwise, it is the type of the string index signature in T, if one exists. + function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { + Debug.assert(isObjectLiteralMethod(node)); + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; } + return getContextualTypeForObjectLiteralElement(node, contextFlags); + } - function getContextualTypeForJsxExpression(node: JsxExpression, contextFlags: ContextFlags | undefined): Type | undefined { - const exprParent = node.parent; - return isJsxAttributeLike(exprParent) - ? getContextualType(node, contextFlags) - : isJsxElement(exprParent) - ? getContextualTypeForChildJsxExpression(exprParent, node, contextFlags) - : undefined; + function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike, contextFlags: ContextFlags | undefined) { + const objectLiteral = element.parent as ObjectLiteralExpression; + const propertyAssignmentType = isPropertyAssignment(element) && getContextualTypeForVariableLikeDeclaration(element, contextFlags); + if (propertyAssignmentType) { + return propertyAssignmentType; } + const type = getApparentTypeOfContextualType(objectLiteral, contextFlags); + if (type) { + if (hasBindableName(element)) { + // For a (non-symbol) computed property, there is no reason to look up the name + // in the type. It will just be "__computed", which does not appear in any + // SymbolTable. + const symbol = getSymbolOfNode(element); + return getTypeOfPropertyOfContextualType(type, symbol.escapedName, getSymbolLinks(symbol).nameType); + } + if (element.name) { + const nameType = getLiteralTypeFromPropertyName(element.name); + // We avoid calling getApplicableIndexInfo here because it performs potentially expensive intersection reduction. + return mapType(type, t => findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType)?.type, /*noReductions*/ true); + } + } + return undefined; + } - function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute, contextFlags: ContextFlags | undefined): Type | undefined { - // When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type - // which is a type of the parameter of the signature we are trying out. - // If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName - if (isJsxAttribute(attribute)) { - const attributesType = getApparentTypeOfContextualType(attribute.parent, contextFlags); - if (!attributesType || isTypeAny(attributesType)) { - return undefined; - } - return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText); + // In an array literal contextually typed by a type T, the contextual type of an element expression at index N is + // the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature, + // it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated + // type of T. + function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined { + return arrayContextualType && ( + getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String) + || mapType( + arrayContextualType, + t => getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false), + /*noReductions*/ true)); + } + + // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type. + function getContextualTypeForConditionalOperand(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { + const conditional = node.parent as ConditionalExpression; + return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional, contextFlags) : undefined; + } + + function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild, contextFlags: ContextFlags | undefined) { + const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName, contextFlags); + // JSX expression is in children of JSX Element, we will look for an "children" attribute (we get the name from JSX.ElementAttributesProperty) + const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) { + return undefined; + } + const realChildren = getSemanticJsxChildren(node.children); + const childIndex = realChildren.indexOf(child); + const childFieldType = getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName); + return childFieldType && (realChildren.length === 1 ? childFieldType : mapType(childFieldType, t => { + if (isArrayLikeType(t)) { + return getIndexedAccessType(t, getNumberLiteralType(childIndex)); } else { - return getContextualType(attribute.parent, contextFlags); + return t; } - } + }, /*noReductions*/ true)); + } - // Return true if the given expression is possibly a discriminant value. We limit the kinds of - // expressions we check to those that don't depend on their contextual type in order not to cause - // recursive (and possibly infinite) invocations of getContextualType. - function isPossiblyDiscriminantValue(node: Expression): boolean { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.Identifier: - case SyntaxKind.UndefinedKeyword: - return true; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ParenthesizedExpression: - return isPossiblyDiscriminantValue((node as PropertyAccessExpression | ParenthesizedExpression).expression); - case SyntaxKind.JsxExpression: - return !(node as JsxExpression).expression || isPossiblyDiscriminantValue((node as JsxExpression).expression!); + function getContextualTypeForJsxExpression(node: JsxExpression, contextFlags: ContextFlags | undefined): Type | undefined { + const exprParent = node.parent; + return isJsxAttributeLike(exprParent) + ? getContextualType(node, contextFlags) + : isJsxElement(exprParent) + ? getContextualTypeForChildJsxExpression(exprParent, node, contextFlags) + : undefined; + } + + function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute, contextFlags: ContextFlags | undefined): Type | undefined { + // When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type + // which is a type of the parameter of the signature we are trying out. + // If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName + if (isJsxAttribute(attribute)) { + const attributesType = getApparentTypeOfContextualType(attribute.parent, contextFlags); + if (!attributesType || isTypeAny(attributesType)) { + return undefined; } - return false; + return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText); } + else { + return getContextualType(attribute.parent, contextFlags); + } + } - function discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) { - return getMatchingUnionConstituentForObjectLiteral(contextualType, node) || discriminateTypeByDiscriminableItems(contextualType, - concatenate( - map( - filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)), - prop => ([() => getContextFreeTypeOfExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String]) - ), - map( - filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)), - s => [() => undefinedType, s.escapedName] as [() => Type, __String] - ) - ), - isTypeAssignableTo, - contextualType - ); + // Return true if the given expression is possibly a discriminant value. We limit the kinds of + // expressions we check to those that don't depend on their contextual type in order not to cause + // recursive (and possibly infinite) invocations of getContextualType. + function isPossiblyDiscriminantValue(node: Expression): boolean { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.Identifier: + case SyntaxKind.UndefinedKeyword: + return true; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ParenthesizedExpression: + return isPossiblyDiscriminantValue((node as PropertyAccessExpression | ParenthesizedExpression).expression); + case SyntaxKind.JsxExpression: + return !(node as JsxExpression).expression || isPossiblyDiscriminantValue((node as JsxExpression).expression!); } + return false; + } - function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) { - return discriminateTypeByDiscriminableItems(contextualType, - concatenate( - map( - filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.JsxAttribute && isDiscriminantProperty(contextualType, p.symbol.escapedName) && (!p.initializer || isPossiblyDiscriminantValue(p.initializer))), - prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => getContextFreeTypeOfExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String]) - ), - map( - filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)), - s => [() => undefinedType, s.escapedName] as [() => Type, __String] - ) + function discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) { + return getMatchingUnionConstituentForObjectLiteral(contextualType, node) || discriminateTypeByDiscriminableItems(contextualType, + concatenate( + map( + filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)), + prop => ([() => getContextFreeTypeOfExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String]) ), - isTypeAssignableTo, - contextualType - ); + map( + filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)), + s => [() => undefinedType, s.escapedName] as [() => Type, __String] + ) + ), + isTypeAssignableTo, + contextualType + ); + } + + function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) { + return discriminateTypeByDiscriminableItems(contextualType, + concatenate( + map( + filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.JsxAttribute && isDiscriminantProperty(contextualType, p.symbol.escapedName) && (!p.initializer || isPossiblyDiscriminantValue(p.initializer))), + prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => getContextFreeTypeOfExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String]) + ), + map( + filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)), + s => [() => undefinedType, s.escapedName] as [() => Type, __String] + ) + ), + isTypeAssignableTo, + contextualType + ); + } + + // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily + // be "pushed" onto a node using the contextualType property. + function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { + const contextualType = isObjectLiteralMethod(node) ? + getContextualTypeForObjectLiteralMethod(node, contextFlags) : + getContextualType(node, contextFlags); + const instantiatedType = instantiateContextualType(contextualType, node, contextFlags); + if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) { + const apparentType = mapType(instantiatedType, getApparentType, /*noReductions*/ true); + return apparentType.flags & TypeFlags.Union && isObjectLiteralExpression(node) ? discriminateContextualTypeByObjectMembers(node, apparentType as UnionType) : + apparentType.flags & TypeFlags.Union && isJsxAttributes(node) ? discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType) : + apparentType; } + } - // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily - // be "pushed" onto a node using the contextualType property. - function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags: ContextFlags | undefined): Type | undefined { - const contextualType = isObjectLiteralMethod(node) ? - getContextualTypeForObjectLiteralMethod(node, contextFlags) : - getContextualType(node, contextFlags); - const instantiatedType = instantiateContextualType(contextualType, node, contextFlags); - if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) { - const apparentType = mapType(instantiatedType, getApparentType, /*noReductions*/ true); - return apparentType.flags & TypeFlags.Union && isObjectLiteralExpression(node) ? discriminateContextualTypeByObjectMembers(node, apparentType as UnionType) : - apparentType.flags & TypeFlags.Union && isJsxAttributes(node) ? discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType) : - apparentType; - } - } - - // If the given contextual type contains instantiable types and if a mapper representing - // return type inferences is available, instantiate those types using that mapper. - function instantiateContextualType(contextualType: Type | undefined, node: Node, contextFlags: ContextFlags | undefined): Type | undefined { - if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) { - const inferenceContext = getInferenceContext(node); - // If no inferences have been made, and none of the type parameters for which we are inferring - // specify default types, nothing is gained from instantiating as type parameters would just be - // replaced with their constraints similar to the apparent type. - if (inferenceContext && contextFlags! & ContextFlags.Signature && some(inferenceContext.inferences, hasInferenceCandidatesOrDefault)) { - // For contextual signatures we incorporate all inferences made so far, e.g. from return - // types as well as arguments to the left in a function call. - return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper); - } - if (inferenceContext?.returnMapper) { - // For other purposes (e.g. determining whether to produce literal types) we only - // incorporate inferences made from the return type in a function call. We remove - // the 'boolean' type from the contextual type such that contextually typed boolean - // literals actually end up widening to 'boolean' (see #48363). - const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); - return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ? - filterType(type, t => t !== regularFalseType && t !== regularTrueType) : - type; - } + // If the given contextual type contains instantiable types and if a mapper representing + // return type inferences is available, instantiate those types using that mapper. + function instantiateContextualType(contextualType: Type | undefined, node: Node, contextFlags: ContextFlags | undefined): Type | undefined { + if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) { + const inferenceContext = getInferenceContext(node); + // If no inferences have been made, and none of the type parameters for which we are inferring + // specify default types, nothing is gained from instantiating as type parameters would just be + // replaced with their constraints similar to the apparent type. + if (inferenceContext && contextFlags! & ContextFlags.Signature && some(inferenceContext.inferences, hasInferenceCandidatesOrDefault)) { + // For contextual signatures we incorporate all inferences made so far, e.g. from return + // types as well as arguments to the left in a function call. + return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper); + } + if (inferenceContext?.returnMapper) { + // For other purposes (e.g. determining whether to produce literal types) we only + // incorporate inferences made from the return type in a function call. We remove + // the 'boolean' type from the contextual type such that contextually typed boolean + // literals actually end up widening to 'boolean' (see #48363). + const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); + return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ? + filterType(type, t => t !== regularFalseType && t !== regularTrueType) : + type; } - return contextualType; } + return contextualType; + } - // This function is similar to instantiateType, except that (a) it only instantiates types that - // are classified as instantiable (i.e. it doesn't instantiate object types), and (b) it performs - // no reductions on instantiated union types. - function instantiateInstantiableTypes(type: Type, mapper: TypeMapper): Type { - if (type.flags & TypeFlags.Instantiable) { - return instantiateType(type, mapper); - } - if (type.flags & TypeFlags.Union) { - return getUnionType(map((type as UnionType).types, t => instantiateInstantiableTypes(t, mapper)), UnionReduction.None); - } - if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(map((type as IntersectionType).types, t => instantiateInstantiableTypes(t, mapper))); - } - return type; + // This function is similar to instantiateType, except that (a) it only instantiates types that + // are classified as instantiable (i.e. it doesn't instantiate object types), and (b) it performs + // no reductions on instantiated union types. + function instantiateInstantiableTypes(type: Type, mapper: TypeMapper): Type { + if (type.flags & TypeFlags.Instantiable) { + return instantiateType(type, mapper); + } + if (type.flags & TypeFlags.Union) { + return getUnionType(map((type as UnionType).types, t => instantiateInstantiableTypes(t, mapper)), UnionReduction.None); + } + if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(map((type as IntersectionType).types, t => instantiateInstantiableTypes(t, mapper))); } + return type; + } - /** - * Whoa! Do you really want to use this function? - * - * Unless you're trying to get the *non-apparent* type for a - * value-literal type or you're authoring relevant portions of this algorithm, - * you probably meant to use 'getApparentTypeOfContextualType'. - * Otherwise this may not be very useful. - * - * In cases where you *are* working on this function, you should understand - * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'. - * - * - Use 'getContextualType' when you are simply going to propagate the result to the expression. - * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type. - * - * @param node the expression whose contextual type will be returned. - * @returns the contextual type of an expression. - */ - function getContextualType(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; - } - if (node.contextualType) { - return node.contextualType; - } - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.VariableDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.BindingElement: - return getContextualTypeForInitializerExpression(node, contextFlags); - case SyntaxKind.ArrowFunction: - case SyntaxKind.ReturnStatement: - return getContextualTypeForReturnExpression(node, contextFlags); - case SyntaxKind.YieldExpression: - return getContextualTypeForYieldOperand(parent as YieldExpression, contextFlags); - case SyntaxKind.AwaitExpression: - return getContextualTypeForAwaitOperand(parent as AwaitExpression, contextFlags); - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - return getContextualTypeForArgument(parent as CallExpression | NewExpression, node); - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - return isConstTypeReference((parent as AssertionExpression).type) ? tryFindWhenConstTypeReference(parent as AssertionExpression) : getTypeFromTypeNode((parent as AssertionExpression).type); - case SyntaxKind.BinaryExpression: - return getContextualTypeForBinaryOperand(node, contextFlags); - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - return getContextualTypeForObjectLiteralElement(parent as PropertyAssignment | ShorthandPropertyAssignment, contextFlags); - case SyntaxKind.SpreadAssignment: - return getContextualType(parent.parent as ObjectLiteralExpression, contextFlags); - case SyntaxKind.ArrayLiteralExpression: { - const arrayLiteral = parent as ArrayLiteralExpression; - const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags); - return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node)); - } - case SyntaxKind.ConditionalExpression: - return getContextualTypeForConditionalOperand(node, contextFlags); - case SyntaxKind.TemplateSpan: - Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); - return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node); - case SyntaxKind.ParenthesizedExpression: { - // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. - const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined; - return !tag ? getContextualType(parent as ParenthesizedExpression, contextFlags) : - isJSDocTypeTag(tag) && isConstTypeReference(tag.typeExpression.type) ? tryFindWhenConstTypeReference(parent as ParenthesizedExpression) : - getTypeFromTypeNode(tag.typeExpression.type); - } - case SyntaxKind.NonNullExpression: - return getContextualType(parent as NonNullExpression, contextFlags); - case SyntaxKind.SatisfiesExpression: - return getTypeFromTypeNode((parent as SatisfiesExpression).type); - case SyntaxKind.ExportAssignment: - return tryGetTypeFromEffectiveTypeNode(parent as ExportAssignment); - case SyntaxKind.JsxExpression: - return getContextualTypeForJsxExpression(parent as JsxExpression, contextFlags); - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxSpreadAttribute: - return getContextualTypeForJsxAttribute(parent as JsxAttribute | JsxSpreadAttribute, contextFlags); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxSelfClosingElement: - return getContextualJsxElementAttributesType(parent as JsxOpeningLikeElement, contextFlags); - } + /** + * Whoa! Do you really want to use this function? + * + * Unless you're trying to get the *non-apparent* type for a + * value-literal type or you're authoring relevant portions of this algorithm, + * you probably meant to use 'getApparentTypeOfContextualType'. + * Otherwise this may not be very useful. + * + * In cases where you *are* working on this function, you should understand + * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'. + * + * - Use 'getContextualType' when you are simply going to propagate the result to the expression. + * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type. + * + * @param node the expression whose contextual type will be returned. + * @returns the contextual type of an expression. + */ + function getContextualType(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further return undefined; + } + if (node.contextualType) { + return node.contextualType; + } + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.BindingElement: + return getContextualTypeForInitializerExpression(node, contextFlags); + case SyntaxKind.ArrowFunction: + case SyntaxKind.ReturnStatement: + return getContextualTypeForReturnExpression(node, contextFlags); + case SyntaxKind.YieldExpression: + return getContextualTypeForYieldOperand(parent as YieldExpression, contextFlags); + case SyntaxKind.AwaitExpression: + return getContextualTypeForAwaitOperand(parent as AwaitExpression, contextFlags); + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + return getContextualTypeForArgument(parent as CallExpression | NewExpression, node); + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return isConstTypeReference((parent as AssertionExpression).type) ? tryFindWhenConstTypeReference(parent as AssertionExpression) : getTypeFromTypeNode((parent as AssertionExpression).type); + case SyntaxKind.BinaryExpression: + return getContextualTypeForBinaryOperand(node, contextFlags); + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + return getContextualTypeForObjectLiteralElement(parent as PropertyAssignment | ShorthandPropertyAssignment, contextFlags); + case SyntaxKind.SpreadAssignment: + return getContextualType(parent.parent as ObjectLiteralExpression, contextFlags); + case SyntaxKind.ArrayLiteralExpression: { + const arrayLiteral = parent as ArrayLiteralExpression; + const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags); + return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node)); + } + case SyntaxKind.ConditionalExpression: + return getContextualTypeForConditionalOperand(node, contextFlags); + case SyntaxKind.TemplateSpan: + Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); + return getContextualTypeForSubstitutionExpression(parent.parent as TemplateExpression, node); + case SyntaxKind.ParenthesizedExpression: { + // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. + const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined; + return !tag ? getContextualType(parent as ParenthesizedExpression, contextFlags) : + isJSDocTypeTag(tag) && isConstTypeReference(tag.typeExpression.type) ? tryFindWhenConstTypeReference(parent as ParenthesizedExpression) : + getTypeFromTypeNode(tag.typeExpression.type); + } + case SyntaxKind.NonNullExpression: + return getContextualType(parent as NonNullExpression, contextFlags); + case SyntaxKind.SatisfiesExpression: + return getTypeFromTypeNode((parent as SatisfiesExpression).type); + case SyntaxKind.ExportAssignment: + return tryGetTypeFromEffectiveTypeNode(parent as ExportAssignment); + case SyntaxKind.JsxExpression: + return getContextualTypeForJsxExpression(parent as JsxExpression, contextFlags); + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + return getContextualTypeForJsxAttribute(parent as JsxAttribute | JsxSpreadAttribute, contextFlags); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + return getContextualJsxElementAttributesType(parent as JsxOpeningLikeElement, contextFlags); + } + return undefined; + + function tryFindWhenConstTypeReference(node: Expression) { + return getContextualType(node, contextFlags); + } + } + + function getInferenceContext(node: Node) { + const ancestor = findAncestor(node, n => !!n.inferenceContext); + return ancestor && ancestor.inferenceContext!; + } - function tryFindWhenConstTypeReference(node: Expression) { - return getContextualType(node, contextFlags); - } + function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) { + if (isJsxOpeningElement(node) && node.parent.contextualType && contextFlags !== ContextFlags.Completions) { + // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit + // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type + // (as below) instead! + return node.parent.contextualType; } + return getContextualTypeForArgumentAtIndex(node, 0); + } + + function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) { + return getJsxReferenceKind(node) !== JsxReferenceKind.Component + ? getJsxPropsTypeFromCallSignature(signature, node) + : getJsxPropsTypeFromClassType(signature, node); + } - function getInferenceContext(node: Node) { - const ancestor = findAncestor(node, n => !!n.inferenceContext); - return ancestor && ancestor.inferenceContext!; + function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) { + let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType); + propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType); + const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); + if (!isErrorType(intrinsicAttribs)) { + propsType = intersectTypes(intrinsicAttribs, propsType); } + return propsType; + } - function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) { - if (isJsxOpeningElement(node) && node.parent.contextualType && contextFlags !== ContextFlags.Completions) { - // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit - // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type - // (as below) instead! - return node.parent.contextualType; + function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) { + if (sig.compositeSignatures) { + // JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input + // instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature, + // get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur + // for a union signature). It's an unfortunate quirk of looking in the output of the signature for the type we want to use for the input. + // The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane. + const results: Type[] = []; + for (const signature of sig.compositeSignatures) { + const instance = getReturnTypeOfSignature(signature); + if (isTypeAny(instance)) { + return instance; + } + const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation); + if (!propType) { + return; + } + results.push(propType); } - return getContextualTypeForArgumentAtIndex(node, 0); + return getIntersectionType(results); // Same result for both union and intersection signatures } + const instanceType = getReturnTypeOfSignature(sig); + return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); + } - function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) { - return getJsxReferenceKind(node) !== JsxReferenceKind.Component - ? getJsxPropsTypeFromCallSignature(signature, node) - : getJsxPropsTypeFromClassType(signature, node); + function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) { + if (isJsxIntrinsicIdentifier(context.tagName)) { + const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context); + const fakeSignature = createSignatureForJSXIntrinsic(context, result); + return getOrCreateTypeFromSignature(fakeSignature); } - - function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) { - let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType); - propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType); - const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); - if (!isErrorType(intrinsicAttribs)) { - propsType = intersectTypes(intrinsicAttribs, propsType); - } - return propsType; - } - - function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) { - if (sig.compositeSignatures) { - // JSX Elements using the legacy `props`-field based lookup (eg, react class components) need to treat the `props` member as an input - // instead of an output position when resolving the signature. We need to go back to the input signatures of the composite signature, - // get the type of `props` on each return type individually, and then _intersect them_, rather than union them (as would normally occur - // for a union signature). It's an unfortunate quirk of looking in the output of the signature for the type we want to use for the input. - // The default behavior of `getTypeOfFirstParameterOfSignatureWithFallback` when no `props` member name is defined is much more sane. - const results: Type[] = []; - for (const signature of sig.compositeSignatures) { - const instance = getReturnTypeOfSignature(signature); - if (isTypeAny(instance)) { - return instance; - } - const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation); - if (!propType) { - return; - } - results.push(propType); - } - return getIntersectionType(results); // Same result for both union and intersection signatures + const tagType = checkExpressionCached(context.tagName); + if (tagType.flags & TypeFlags.StringLiteral) { + const result = getIntrinsicAttributesTypeFromStringLiteralType(tagType as StringLiteralType, context); + if (!result) { + return errorType; } - const instanceType = getReturnTypeOfSignature(sig); - return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); + const fakeSignature = createSignatureForJSXIntrinsic(context, result); + return getOrCreateTypeFromSignature(fakeSignature); } + return tagType; + } - function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) { - if (isJsxIntrinsicIdentifier(context.tagName)) { - const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context); - const fakeSignature = createSignatureForJSXIntrinsic(context, result); - return getOrCreateTypeFromSignature(fakeSignature); - } - const tagType = checkExpressionCached(context.tagName); - if (tagType.flags & TypeFlags.StringLiteral) { - const result = getIntrinsicAttributesTypeFromStringLiteralType(tagType as StringLiteralType, context); - if (!result) { - return errorType; + function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) { + const managedSym = getJsxLibraryManagedAttributes(ns); + if (managedSym) { + const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); // fetches interface type, or initializes symbol links type parmaeters + const ctorType = getStaticTypeOfReferencedJsxConstructor(context); + if (managedSym.flags & SymbolFlags.TypeAlias) { + const params = getSymbolLinks(managedSym).typeParameters; + if (length(params) >= 2) { + const args = fillMissingTypeArguments([ctorType, attributesType], params, 2, isInJSFile(context)); + return getTypeAliasInstantiation(managedSym, args); } - const fakeSignature = createSignatureForJSXIntrinsic(context, result); - return getOrCreateTypeFromSignature(fakeSignature); } - return tagType; + if (length((declaredManagedType as GenericType).typeParameters) >= 2) { + const args = fillMissingTypeArguments([ctorType, attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJSFile(context)); + return createTypeReference((declaredManagedType as GenericType), args); + } } + return attributesType; + } - function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) { - const managedSym = getJsxLibraryManagedAttributes(ns); - if (managedSym) { - const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); // fetches interface type, or initializes symbol links type parmaeters - const ctorType = getStaticTypeOfReferencedJsxConstructor(context); - if (managedSym.flags & SymbolFlags.TypeAlias) { - const params = getSymbolLinks(managedSym).typeParameters; - if (length(params) >= 2) { - const args = fillMissingTypeArguments([ctorType, attributesType], params, 2, isInJSFile(context)); - return getTypeAliasInstantiation(managedSym, args); - } - } - if (length((declaredManagedType as GenericType).typeParameters) >= 2) { - const args = fillMissingTypeArguments([ctorType, attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJSFile(context)); - return createTypeReference((declaredManagedType as GenericType), args); - } + function getJsxPropsTypeFromClassType(sig: Signature, context: JsxOpeningLikeElement) { + const ns = getJsxNamespaceAt(context); + const forcedLookupLocation = getJsxElementPropertiesName(ns); + let attributesType = forcedLookupLocation === undefined + // If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type + ? getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType) + : forcedLookupLocation === "" + // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead + ? getReturnTypeOfSignature(sig) + // Otherwise get the type of the property on the signature return type + : getJsxPropsTypeForSignatureFromMember(sig, forcedLookupLocation); + + if (!attributesType) { + // There is no property named 'props' on this instance type + if (!!forcedLookupLocation && !!length(context.attributes.properties)) { + error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(forcedLookupLocation)); } - return attributesType; + return unknownType; } - function getJsxPropsTypeFromClassType(sig: Signature, context: JsxOpeningLikeElement) { - const ns = getJsxNamespaceAt(context); - const forcedLookupLocation = getJsxElementPropertiesName(ns); - let attributesType = forcedLookupLocation === undefined - // If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type - ? getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType) - : forcedLookupLocation === "" - // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead - ? getReturnTypeOfSignature(sig) - // Otherwise get the type of the property on the signature return type - : getJsxPropsTypeForSignatureFromMember(sig, forcedLookupLocation); + attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType); - if (!attributesType) { - // There is no property named 'props' on this instance type - if (!!forcedLookupLocation && !!length(context.attributes.properties)) { - error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(forcedLookupLocation)); + if (isTypeAny(attributesType)) { + // Props is of type 'any' or unknown + return attributesType; + } + else { + // Normal case -- add in IntrinsicClassElements and IntrinsicElements + let apparentAttributesType = attributesType; + const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context); + if (!isErrorType(intrinsicClassAttribs)) { + const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); + const hostClassType = getReturnTypeOfSignature(sig); + let libraryManagedAttributeType: Type; + if (typeParams) { + // apply JSX.IntrinsicClassElements + const inferredArgs = fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isInJSFile(context)); + libraryManagedAttributeType = instantiateType(intrinsicClassAttribs, createTypeMapper(typeParams, inferredArgs)); } - return unknownType; + // or JSX.IntrinsicClassElements has no generics. + else libraryManagedAttributeType = intrinsicClassAttribs; + apparentAttributesType = intersectTypes(libraryManagedAttributeType, apparentAttributesType); } - attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType); - - if (isTypeAny(attributesType)) { - // Props is of type 'any' or unknown - return attributesType; + const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); + if (!isErrorType(intrinsicAttribs)) { + apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); } - else { - // Normal case -- add in IntrinsicClassElements and IntrinsicElements - let apparentAttributesType = attributesType; - const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context); - if (!isErrorType(intrinsicClassAttribs)) { - const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); - const hostClassType = getReturnTypeOfSignature(sig); - let libraryManagedAttributeType: Type; - if (typeParams) { - // apply JSX.IntrinsicClassElements - const inferredArgs = fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isInJSFile(context)); - libraryManagedAttributeType = instantiateType(intrinsicClassAttribs, createTypeMapper(typeParams, inferredArgs)); - } - // or JSX.IntrinsicClassElements has no generics. - else libraryManagedAttributeType = intrinsicClassAttribs; - apparentAttributesType = intersectTypes(libraryManagedAttributeType, apparentAttributesType); - } - - const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); - if (!isErrorType(intrinsicAttribs)) { - apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); - } - return apparentAttributesType; - } + return apparentAttributesType; } + } - function getIntersectedSignatures(signatures: readonly Signature[]) { - return getStrictOptionValue(compilerOptions, "noImplicitAny") - ? reduceLeft( - signatures, - (left, right) => - left === right || !left ? left - : compareTypeParametersIdentical(left.typeParameters, right.typeParameters) ? combineSignaturesOfIntersectionMembers(left, right) - : undefined) - : undefined; + function getIntersectedSignatures(signatures: readonly Signature[]) { + return getStrictOptionValue(compilerOptions, "noImplicitAny") + ? reduceLeft( + signatures, + (left, right) => + left === right || !left ? left + : compareTypeParametersIdentical(left.typeParameters, right.typeParameters) ? combineSignaturesOfIntersectionMembers(left, right) + : undefined) + : undefined; + } + + function combineIntersectionThisParam(left: Symbol | undefined, right: Symbol | undefined, mapper: TypeMapper | undefined): Symbol | undefined { + if (!left || !right) { + return left || right; } + // A signature `this` type might be a read or a write position... It's very possible that it should be invariant + // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be + // pessimistic when contextual typing, for now, we'll union the `this` types. + const thisType = getUnionType([getTypeOfSymbol(left), instantiateType(getTypeOfSymbol(right), mapper)]); + return createSymbolWithType(left, thisType); + } - function combineIntersectionThisParam(left: Symbol | undefined, right: Symbol | undefined, mapper: TypeMapper | undefined): Symbol | undefined { - if (!left || !right) { - return left || right; - } - // A signature `this` type might be a read or a write position... It's very possible that it should be invariant - // and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be - // pessimistic when contextual typing, for now, we'll union the `this` types. - const thisType = getUnionType([getTypeOfSymbol(left), instantiateType(getTypeOfSymbol(right), mapper)]); - return createSymbolWithType(left, thisType); - } - - function combineIntersectionParameters(left: Signature, right: Signature, mapper: TypeMapper | undefined) { - const leftCount = getParameterCount(left); - const rightCount = getParameterCount(right); - const longest = leftCount >= rightCount ? left : right; - const shorter = longest === left ? right : left; - const longestCount = longest === left ? leftCount : rightCount; - const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); - const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); - const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); - for (let i = 0; i < longestCount; i++) { - let longestParamType = tryGetTypeAtPosition(longest, i)!; - if (longest === right) { - longestParamType = instantiateType(longestParamType, mapper); - } - let shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; - if (shorter === right) { - shorterParamType = instantiateType(shorterParamType, mapper); - } - const unionParamType = getUnionType([longestParamType, shorterParamType]); - const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); - const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); - const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i); - const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i); - - const paramName = leftName === rightName ? leftName : - !leftName ? rightName : - !rightName ? leftName : - undefined; - const paramSymbol = createSymbol( - SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), - paramName || `arg${i}` as __String - ); - paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; - params[i] = paramSymbol; - } - if (needsExtraRestElement) { - const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); - restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); - if (shorter === right) { - restParamSymbol.type = instantiateType(restParamSymbol.type, mapper); - } - params[longestCount] = restParamSymbol; - } - return params; - } - - function combineSignaturesOfIntersectionMembers(left: Signature, right: Signature): Signature { - const typeParams = left.typeParameters || right.typeParameters; - let paramMapper: TypeMapper | undefined; - if (left.typeParameters && right.typeParameters) { - paramMapper = createTypeMapper(right.typeParameters, left.typeParameters); - // We just use the type parameter defaults from the first signature - } - const declaration = left.declaration; - const params = combineIntersectionParameters(left, right, paramMapper); - const thisParam = combineIntersectionThisParam(left.thisParameter, right.thisParameter, paramMapper); - const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); - const result = createSignature( - declaration, - typeParams, - thisParam, - params, - /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, - minArgCount, - (left.flags | right.flags) & SignatureFlags.PropagatingFlags + function combineIntersectionParameters(left: Signature, right: Signature, mapper: TypeMapper | undefined) { + const leftCount = getParameterCount(left); + const rightCount = getParameterCount(right); + const longest = leftCount >= rightCount ? left : right; + const shorter = longest === left ? right : left; + const longestCount = longest === left ? leftCount : rightCount; + const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); + const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); + const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); + for (let i = 0; i < longestCount; i++) { + let longestParamType = tryGetTypeAtPosition(longest, i)!; + if (longest === right) { + longestParamType = instantiateType(longestParamType, mapper); + } + let shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; + if (shorter === right) { + shorterParamType = instantiateType(shorterParamType, mapper); + } + const unionParamType = getUnionType([longestParamType, shorterParamType]); + const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); + const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); + const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i); + const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i); + + const paramName = leftName === rightName ? leftName : + !leftName ? rightName : + !rightName ? leftName : + undefined; + const paramSymbol = createSymbol( + SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), + paramName || `arg${i}` as __String ); - result.compositeKind = TypeFlags.Intersection; - result.compositeSignatures = concatenate(left.compositeKind === TypeFlags.Intersection && left.compositeSignatures || [left], [right]); - if (paramMapper) { - result.mapper = left.compositeKind === TypeFlags.Intersection && left.mapper && left.compositeSignatures ? combineTypeMappers(left.mapper, paramMapper) : paramMapper; + paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; + } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); + restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + if (shorter === right) { + restParamSymbol.type = instantiateType(restParamSymbol.type, mapper); } - return result; + params[longestCount] = restParamSymbol; } + return params; + } - // If the given type is an object or union type with a single signature, and if that signature has at - // least as many parameters as the given function, return the signature. Otherwise return undefined. - function getContextualCallSignature(type: Type, node: SignatureDeclaration): Signature | undefined { - const signatures = getSignaturesOfType(type, SignatureKind.Call); - const applicableByArity = filter(signatures, s => !isAritySmaller(s, node)); - return applicableByArity.length === 1 ? applicableByArity[0] : getIntersectedSignatures(applicableByArity); - } + function combineSignaturesOfIntersectionMembers(left: Signature, right: Signature): Signature { + const typeParams = left.typeParameters || right.typeParameters; + let paramMapper: TypeMapper | undefined; + if (left.typeParameters && right.typeParameters) { + paramMapper = createTypeMapper(right.typeParameters, left.typeParameters); + // We just use the type parameter defaults from the first signature + } + const declaration = left.declaration; + const params = combineIntersectionParameters(left, right, paramMapper); + const thisParam = combineIntersectionThisParam(left.thisParameter, right.thisParameter, paramMapper); + const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); + const result = createSignature( + declaration, + typeParams, + thisParam, + params, + /*resolvedReturnType*/ undefined, + /*resolvedTypePredicate*/ undefined, + minArgCount, + (left.flags | right.flags) & SignatureFlags.PropagatingFlags + ); + result.compositeKind = TypeFlags.Intersection; + result.compositeSignatures = concatenate(left.compositeKind === TypeFlags.Intersection && left.compositeSignatures || [left], [right]); + if (paramMapper) { + result.mapper = left.compositeKind === TypeFlags.Intersection && left.mapper && left.compositeSignatures ? combineTypeMappers(left.mapper, paramMapper) : paramMapper; + } + return result; + } - /** If the contextual signature has fewer parameters than the function expression, do not use it */ - function isAritySmaller(signature: Signature, target: SignatureDeclaration) { - let targetParameterCount = 0; - for (; targetParameterCount < target.parameters.length; targetParameterCount++) { - const param = target.parameters[targetParameterCount]; - if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { - break; - } - } - if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { - targetParameterCount--; + // If the given type is an object or union type with a single signature, and if that signature has at + // least as many parameters as the given function, return the signature. Otherwise return undefined. + function getContextualCallSignature(type: Type, node: SignatureDeclaration): Signature | undefined { + const signatures = getSignaturesOfType(type, SignatureKind.Call); + const applicableByArity = filter(signatures, s => !isAritySmaller(s, node)); + return applicableByArity.length === 1 ? applicableByArity[0] : getIntersectedSignatures(applicableByArity); + } + + /** If the contextual signature has fewer parameters than the function expression, do not use it */ + function isAritySmaller(signature: Signature, target: SignatureDeclaration) { + let targetParameterCount = 0; + for (; targetParameterCount < target.parameters.length; targetParameterCount++) { + const param = target.parameters[targetParameterCount]; + if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { + break; } - return !hasEffectiveRestParameter(signature) && getParameterCount(signature) < targetParameterCount; } - - function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature | undefined { - // Only function expressions, arrow functions, and object literal methods are contextually typed. - return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node) - ? getContextualSignature(node as FunctionExpression) - : undefined; + if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { + targetParameterCount--; } + return !hasEffectiveRestParameter(signature) && getParameterCount(signature) < targetParameterCount; + } - // Return the contextual signature for a given expression node. A contextual type provides a - // contextual signature if it has a single call signature and if that call signature is non-generic. - // If the contextual type is a union type, get the signature from each type possible and if they are - // all identical ignoring their return type, the result is same signature but with return type as - // union type of return types from these signatures - function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature | undefined { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - const typeTagSignature = getSignatureOfTypeTag(node); - if (typeTagSignature) { - return typeTagSignature; - } - const type = getApparentTypeOfContextualType(node, ContextFlags.Signature); - if (!type) { - return undefined; - } - if (!(type.flags & TypeFlags.Union)) { - return getContextualCallSignature(type, node); - } - let signatureList: Signature[] | undefined; - const types = (type as UnionType).types; - for (const current of types) { - const signature = getContextualCallSignature(current, node); - if (signature) { - if (!signatureList) { - // This signature will contribute to contextual union signature - signatureList = [signature]; - } - else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { - // Signatures aren't identical, do not use - return undefined; - } - else { - // Use this signature for contextual union signature - signatureList.push(signature); - } + function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature | undefined { + // Only function expressions, arrow functions, and object literal methods are contextually typed. + return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node) + ? getContextualSignature(node as FunctionExpression) + : undefined; + } + + // Return the contextual signature for a given expression node. A contextual type provides a + // contextual signature if it has a single call signature and if that call signature is non-generic. + // If the contextual type is a union type, get the signature from each type possible and if they are + // all identical ignoring their return type, the result is same signature but with return type as + // union type of return types from these signatures + function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature | undefined { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + const typeTagSignature = getSignatureOfTypeTag(node); + if (typeTagSignature) { + return typeTagSignature; + } + const type = getApparentTypeOfContextualType(node, ContextFlags.Signature); + if (!type) { + return undefined; + } + if (!(type.flags & TypeFlags.Union)) { + return getContextualCallSignature(type, node); + } + let signatureList: Signature[] | undefined; + const types = (type as UnionType).types; + for (const current of types) { + const signature = getContextualCallSignature(current, node); + if (signature) { + if (!signatureList) { + // This signature will contribute to contextual union signature + signatureList = [signature]; + } + else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { + // Signatures aren't identical, do not use + return undefined; + } + else { + // Use this signature for contextual union signature + signatureList.push(signature); } - } - // Result is union of signatures collected (return type is union of return types of this signature set) - if (signatureList) { - return signatureList.length === 1 ? signatureList[0] : createUnionSignature(signatureList[0], signatureList); } } - - function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type { - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray); - } - - const arrayOrIterableType = checkExpression(node.expression, checkMode); - return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression); + // Result is union of signatures collected (return type is union of return types of this signature set) + if (signatureList) { + return signatureList.length === 1 ? signatureList[0] : createUnionSignature(signatureList[0], signatureList); } + } - function checkSyntheticExpression(node: SyntheticExpression): Type { - return node.isSpread ? getIndexedAccessType(node.type, numberType) : node.type; + function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type { + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray); } - function hasDefaultValue(node: BindingElement | Expression): boolean { - return (node.kind === SyntaxKind.BindingElement && !!(node as BindingElement).initializer) || - (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken); - } + const arrayOrIterableType = checkExpression(node.expression, checkMode); + return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression); + } - function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): Type { - const elements = node.elements; - const elementCount = elements.length; - const elementTypes: Type[] = []; - const elementFlags: ElementFlags[] = []; - const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined); - const inDestructuringPattern = isAssignmentTarget(node); - const inConstContext = isConstContext(node); - let hasOmittedExpression = false; - for (let i = 0; i < elementCount; i++) { - const e = elements[i]; - if (e.kind === SyntaxKind.SpreadElement) { - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(e, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray); - } - const spreadType = checkExpression((e as SpreadElement).expression, checkMode, forceTuple); - if (isArrayLikeType(spreadType)) { - elementTypes.push(spreadType); - elementFlags.push(ElementFlags.Variadic); - } - else if (inDestructuringPattern) { - // Given the following situation: - // var c: {}; - // [...c] = ["", 0]; - // - // c is represented in the tree as a spread element in an array literal. - // But c really functions as a rest element, and its purpose is to provide - // a contextual type for the right hand side of the assignment. Therefore, - // instead of calling checkExpression on "...c", which will give an error - // if c is not iterable/array-like, we need to act as if we are trying to - // get the contextual element type from it. So we do something similar to - // getContextualTypeForElementExpression, which will crucially not error - // if there is no index type / iterated type. - const restElementType = getIndexTypeOfType(spreadType, numberType) || - getIteratedTypeOrElementType(IterationUse.Destructuring, spreadType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false) || - unknownType; - elementTypes.push(restElementType); - elementFlags.push(ElementFlags.Rest); - } - else { - elementTypes.push(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, (e as SpreadElement).expression)); - elementFlags.push(ElementFlags.Rest); - } + function checkSyntheticExpression(node: SyntheticExpression): Type { + return node.isSpread ? getIndexedAccessType(node.type, numberType) : node.type; + } + + function hasDefaultValue(node: BindingElement | Expression): boolean { + return (node.kind === SyntaxKind.BindingElement && !!(node as BindingElement).initializer) || + (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken); + } + + function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): Type { + const elements = node.elements; + const elementCount = elements.length; + const elementTypes: Type[] = []; + const elementFlags: ElementFlags[] = []; + const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined); + const inDestructuringPattern = isAssignmentTarget(node); + const inConstContext = isConstContext(node); + let hasOmittedExpression = false; + for (let i = 0; i < elementCount; i++) { + const e = elements[i]; + if (e.kind === SyntaxKind.SpreadElement) { + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(e, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray); + } + const spreadType = checkExpression((e as SpreadElement).expression, checkMode, forceTuple); + if (isArrayLikeType(spreadType)) { + elementTypes.push(spreadType); + elementFlags.push(ElementFlags.Variadic); } - else if (exactOptionalPropertyTypes && e.kind === SyntaxKind.OmittedExpression) { - hasOmittedExpression = true; - elementTypes.push(missingType); - elementFlags.push(ElementFlags.Optional); + else if (inDestructuringPattern) { + // Given the following situation: + // var c: {}; + // [...c] = ["", 0]; + // + // c is represented in the tree as a spread element in an array literal. + // But c really functions as a rest element, and its purpose is to provide + // a contextual type for the right hand side of the assignment. Therefore, + // instead of calling checkExpression on "...c", which will give an error + // if c is not iterable/array-like, we need to act as if we are trying to + // get the contextual element type from it. So we do something similar to + // getContextualTypeForElementExpression, which will crucially not error + // if there is no index type / iterated type. + const restElementType = getIndexTypeOfType(spreadType, numberType) || + getIteratedTypeOrElementType(IterationUse.Destructuring, spreadType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false) || + unknownType; + elementTypes.push(restElementType); + elementFlags.push(ElementFlags.Rest); } else { - const elementContextualType = getContextualTypeForElementExpression(contextualType, elementTypes.length); - const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple); - elementTypes.push(addOptionality(type, /*isProperty*/ true, hasOmittedExpression)); - elementFlags.push(hasOmittedExpression ? ElementFlags.Optional : ElementFlags.Required); - if (contextualType && someType(contextualType, isTupleLikeType) && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(e)) { - const inferenceContext = getInferenceContext(node); - Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context - addIntraExpressionInferenceSite(inferenceContext, e, type); - } + elementTypes.push(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, (e as SpreadElement).expression)); + elementFlags.push(ElementFlags.Rest); } } - if (inDestructuringPattern) { - return createTupleType(elementTypes, elementFlags); + else if (exactOptionalPropertyTypes && e.kind === SyntaxKind.OmittedExpression) { + hasOmittedExpression = true; + elementTypes.push(missingType); + elementFlags.push(ElementFlags.Optional); } - if (forceTuple || inConstContext || contextualType && someType(contextualType, isTupleLikeType)) { - return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext)); + else { + const elementContextualType = getContextualTypeForElementExpression(contextualType, elementTypes.length); + const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple); + elementTypes.push(addOptionality(type, /*isProperty*/ true, hasOmittedExpression)); + elementFlags.push(hasOmittedExpression ? ElementFlags.Optional : ElementFlags.Required); + if (contextualType && someType(contextualType, isTupleLikeType) && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(e)) { + const inferenceContext = getInferenceContext(node); + Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context + addIntraExpressionInferenceSite(inferenceContext, e, type); + } } - return createArrayLiteralType(createArrayType(elementTypes.length ? - getUnionType(sameMap(elementTypes, (t, i) => elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessTypeOrUndefined(t, numberType) || anyType : t), UnionReduction.Subtype) : - strictNullChecks ? implicitNeverType : undefinedWideningType, inConstContext)); } - - function createArrayLiteralType(type: Type) { - if (!(getObjectFlags(type) & ObjectFlags.Reference)) { - return type; - } - let literalType = (type as TypeReference).literalType; - if (!literalType) { - literalType = (type as TypeReference).literalType = cloneTypeReference(type as TypeReference); - literalType.objectFlags |= ObjectFlags.ArrayLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - } - return literalType; + if (inDestructuringPattern) { + return createTupleType(elementTypes, elementFlags); } + if (forceTuple || inConstContext || contextualType && someType(contextualType, isTupleLikeType)) { + return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext)); + } + return createArrayLiteralType(createArrayType(elementTypes.length ? + getUnionType(sameMap(elementTypes, (t, i) => elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessTypeOrUndefined(t, numberType) || anyType : t), UnionReduction.Subtype) : + strictNullChecks ? implicitNeverType : undefinedWideningType, inConstContext)); + } - function isNumericName(name: DeclarationName): boolean { - switch (name.kind) { - case SyntaxKind.ComputedPropertyName: - return isNumericComputedName(name); - case SyntaxKind.Identifier: - return isNumericLiteralName(name.escapedText); - case SyntaxKind.NumericLiteral: - case SyntaxKind.StringLiteral: - return isNumericLiteralName(name.text); - default: - return false; - } + function createArrayLiteralType(type: Type) { + if (!(getObjectFlags(type) & ObjectFlags.Reference)) { + return type; } + let literalType = (type as TypeReference).literalType; + if (!literalType) { + literalType = (type as TypeReference).literalType = cloneTypeReference(type as TypeReference); + literalType.objectFlags |= ObjectFlags.ArrayLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + } + return literalType; + } - function isNumericComputedName(name: ComputedPropertyName): boolean { - // It seems odd to consider an expression of type Any to result in a numeric name, - // but this behavior is consistent with checkIndexedAccess - return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike); + function isNumericName(name: DeclarationName): boolean { + switch (name.kind) { + case SyntaxKind.ComputedPropertyName: + return isNumericComputedName(name); + case SyntaxKind.Identifier: + return isNumericLiteralName(name.escapedText); + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + return isNumericLiteralName(name.text); + default: + return false; } + } - function checkComputedPropertyName(node: ComputedPropertyName): Type { - const links = getNodeLinks(node.expression); - if (!links.resolvedType) { - if ((isTypeLiteralNode(node.parent.parent) || isClassLike(node.parent.parent) || isInterfaceDeclaration(node.parent.parent)) - && isBinaryExpression(node.expression) && node.expression.operatorToken.kind === SyntaxKind.InKeyword - && node.parent.kind !== SyntaxKind.GetAccessor && node.parent.kind !== SyntaxKind.SetAccessor) { - return links.resolvedType = errorType; - } - links.resolvedType = checkExpression(node.expression); - // The computed property name of a non-static class field within a loop must be stored in a block-scoped binding. - // (It needs to be bound at class evaluation time.) - if (isPropertyDeclaration(node.parent) && !hasStaticModifier(node.parent) && isClassExpression(node.parent.parent)) { - const container = getEnclosingBlockScopeContainer(node.parent.parent); - const enclosingIterationStatement = getEnclosingIterationStatement(container); - if (enclosingIterationStatement) { - // The computed field name will use a block scoped binding which can be unique for each iteration of the loop. - getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; - // The generated variable which stores the computed field name must be block-scoped. - getNodeLinks(node).flags |= NodeCheckFlags.BlockScopedBindingInLoop; - // The generated variable which stores the class must be block-scoped. - getNodeLinks(node.parent.parent).flags |= NodeCheckFlags.BlockScopedBindingInLoop; - } - } - // This will allow types number, string, symbol or any. It will also allow enums, the unknown - // type, and any union of these types (like string | number). - if (links.resolvedType.flags & TypeFlags.Nullable || - !isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) && - !isTypeAssignableTo(links.resolvedType, stringNumberSymbolType)) { - error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any); - } - } + function isNumericComputedName(name: ComputedPropertyName): boolean { + // It seems odd to consider an expression of type Any to result in a numeric name, + // but this behavior is consistent with checkIndexedAccess + return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike); + } - return links.resolvedType; - } + function checkComputedPropertyName(node: ComputedPropertyName): Type { + const links = getNodeLinks(node.expression); + if (!links.resolvedType) { + if ((isTypeLiteralNode(node.parent.parent) || isClassLike(node.parent.parent) || isInterfaceDeclaration(node.parent.parent)) + && isBinaryExpression(node.expression) && node.expression.operatorToken.kind === SyntaxKind.InKeyword + && node.parent.kind !== SyntaxKind.GetAccessor && node.parent.kind !== SyntaxKind.SetAccessor) { + return links.resolvedType = errorType; + } + links.resolvedType = checkExpression(node.expression); + // The computed property name of a non-static class field within a loop must be stored in a block-scoped binding. + // (It needs to be bound at class evaluation time.) + if (isPropertyDeclaration(node.parent) && !hasStaticModifier(node.parent) && isClassExpression(node.parent.parent)) { + const container = getEnclosingBlockScopeContainer(node.parent.parent); + const enclosingIterationStatement = getEnclosingIterationStatement(container); + if (enclosingIterationStatement) { + // The computed field name will use a block scoped binding which can be unique for each iteration of the loop. + getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + // The generated variable which stores the computed field name must be block-scoped. + getNodeLinks(node).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + // The generated variable which stores the class must be block-scoped. + getNodeLinks(node.parent.parent).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + } + } + // This will allow types number, string, symbol or any. It will also allow enums, the unknown + // type, and any union of these types (like string | number). + if (links.resolvedType.flags & TypeFlags.Nullable || + !isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) && + !isTypeAssignableTo(links.resolvedType, stringNumberSymbolType)) { + error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any); + } + } + + return links.resolvedType; + } - function isSymbolWithNumericName(symbol: Symbol) { - const firstDecl = symbol.declarations?.[0]; - return isNumericLiteralName(symbol.escapedName) || (firstDecl && isNamedDeclaration(firstDecl) && isNumericName(firstDecl.name)); - } + function isSymbolWithNumericName(symbol: Symbol) { + const firstDecl = symbol.declarations?.[0]; + return isNumericLiteralName(symbol.escapedName) || (firstDecl && isNamedDeclaration(firstDecl) && isNumericName(firstDecl.name)); + } - function isSymbolWithSymbolName(symbol: Symbol) { - const firstDecl = symbol.declarations?.[0]; - return isKnownSymbol(symbol) || (firstDecl && isNamedDeclaration(firstDecl) && isComputedPropertyName(firstDecl.name) && - isTypeAssignableToKind(checkComputedPropertyName(firstDecl.name), TypeFlags.ESSymbol)); - } + function isSymbolWithSymbolName(symbol: Symbol) { + const firstDecl = symbol.declarations?.[0]; + return isKnownSymbol(symbol) || (firstDecl && isNamedDeclaration(firstDecl) && isComputedPropertyName(firstDecl.name) && + isTypeAssignableToKind(checkComputedPropertyName(firstDecl.name), TypeFlags.ESSymbol)); + } - function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, offset: number, properties: Symbol[], keyType: Type): IndexInfo { - const propTypes: Type[] = []; - for (let i = offset; i < properties.length; i++) { - const prop = properties[i]; - if (keyType === stringType && !isSymbolWithSymbolName(prop) || - keyType === numberType && isSymbolWithNumericName(prop) || - keyType === esSymbolType && isSymbolWithSymbolName(prop)) { - propTypes.push(getTypeOfSymbol(properties[i])); - } + function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, offset: number, properties: Symbol[], keyType: Type): IndexInfo { + const propTypes: Type[] = []; + for (let i = offset; i < properties.length; i++) { + const prop = properties[i]; + if (keyType === stringType && !isSymbolWithSymbolName(prop) || + keyType === numberType && isSymbolWithNumericName(prop) || + keyType === esSymbolType && isSymbolWithSymbolName(prop)) { + propTypes.push(getTypeOfSymbol(properties[i])); } - const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType; - return createIndexInfo(keyType, unionType, isConstContext(node)); } + const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType; + return createIndexInfo(keyType, unionType, isConstContext(node)); + } - function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined { - Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); - const links = getSymbolLinks(symbol); - if (!links.immediateTarget) { - const node = getDeclarationOfAliasSymbol(symbol); - if (!node) return Debug.fail(); - links.immediateTarget = getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true); - } - - return links.immediateTarget; + function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined { + Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); + const links = getSymbolLinks(symbol); + if (!links.immediateTarget) { + const node = getDeclarationOfAliasSymbol(symbol); + if (!node) return Debug.fail(); + links.immediateTarget = getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true); } - function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type { - const inDestructuringPattern = isAssignmentTarget(node); - // Grammar checking - checkGrammarObjectLiteralExpression(node, inDestructuringPattern); - - const allPropertiesTable = strictNullChecks ? createSymbolTable() : undefined; - let propertiesTable = createSymbolTable(); - let propertiesArray: Symbol[] = []; - let spread: Type = emptyObjectType; - - const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined); - const contextualTypeHasPattern = contextualType && contextualType.pattern && - (contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression); - const inConstContext = isConstContext(node); - const checkFlags = inConstContext ? CheckFlags.Readonly : 0; - const isInJavascript = isInJSFile(node) && !isInJsonFile(node); - const enumTag = getJSDocEnumTag(node); - const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag; - let objectFlags: ObjectFlags = freshObjectLiteralFlag; - let patternWithComputedProperties = false; - let hasComputedStringProperty = false; - let hasComputedNumberProperty = false; - let hasComputedSymbolProperty = false; - - // Spreads may cause an early bail; ensure computed names are always checked (this is cached) - // As otherwise they may not be checked until exports for the type at this position are retrieved, - // which may never occur. - for (const elem of node.properties) { - if (elem.name && isComputedPropertyName(elem.name)) { - checkComputedPropertyName(elem.name); - } - } - - let offset = 0; - for (const memberDecl of node.properties) { - let member = getSymbolOfNode(memberDecl); - const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName ? - checkComputedPropertyName(memberDecl.name) : undefined; - if (memberDecl.kind === SyntaxKind.PropertyAssignment || - memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment || - isObjectLiteralMethod(memberDecl)) { - let type = memberDecl.kind === SyntaxKind.PropertyAssignment ? checkPropertyAssignment(memberDecl, checkMode) : - // avoid resolving the left side of the ShorthandPropertyAssignment outside of the destructuring - // for error recovery purposes. For example, if a user wrote `{ a = 100 }` instead of `{ a: 100 }`. - // we don't want to say "could not find 'a'". - memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ? checkExpressionForMutableLocation(!inDestructuringPattern && memberDecl.objectAssignmentInitializer ? memberDecl.objectAssignmentInitializer : memberDecl.name, checkMode) : - checkObjectLiteralMethod(memberDecl, checkMode); - if (isInJavascript) { - const jsDocType = getTypeForDeclarationFromJSDocComment(memberDecl); - if (jsDocType) { - checkTypeAssignableTo(type, jsDocType, memberDecl); - type = jsDocType; - } - else if (enumTag && enumTag.typeExpression) { - checkTypeAssignableTo(type, getTypeFromTypeNode(enumTag.typeExpression), memberDecl); - } - } - objectFlags |= getObjectFlags(type) & ObjectFlags.PropagatingFlags; - const nameType = computedNameType && isTypeUsableAsPropertyName(computedNameType) ? computedNameType : undefined; - const prop = nameType ? - createSymbol(SymbolFlags.Property | member.flags, getPropertyNameFromType(nameType), checkFlags | CheckFlags.Late) : - createSymbol(SymbolFlags.Property | member.flags, member.escapedName, checkFlags); - if (nameType) { - prop.nameType = nameType; - } + return links.immediateTarget; + } - if (inDestructuringPattern) { - // If object literal is an assignment pattern and if the assignment pattern specifies a default value - // for the property, make the property optional. - const isOptional = - (memberDecl.kind === SyntaxKind.PropertyAssignment && hasDefaultValue(memberDecl.initializer)) || - (memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment && memberDecl.objectAssignmentInitializer); - if (isOptional) { - prop.flags |= SymbolFlags.Optional; - } - } - else if (contextualTypeHasPattern && !(getObjectFlags(contextualType) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) { - // If object literal is contextually typed by the implied type of a binding pattern, and if the - // binding pattern specifies a default value for the property, make the property optional. - const impliedProp = getPropertyOfType(contextualType, member.escapedName); - if (impliedProp) { - prop.flags |= impliedProp.flags & SymbolFlags.Optional; - } + function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type { + const inDestructuringPattern = isAssignmentTarget(node); + // Grammar checking + checkGrammarObjectLiteralExpression(node, inDestructuringPattern); + + const allPropertiesTable = strictNullChecks ? createSymbolTable() : undefined; + let propertiesTable = createSymbolTable(); + let propertiesArray: Symbol[] = []; + let spread: Type = emptyObjectType; + + const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined); + const contextualTypeHasPattern = contextualType && contextualType.pattern && + (contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression); + const inConstContext = isConstContext(node); + const checkFlags = inConstContext ? CheckFlags.Readonly : 0; + const isInJavascript = isInJSFile(node) && !isInJsonFile(node); + const enumTag = getJSDocEnumTag(node); + const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag; + let objectFlags: ObjectFlags = freshObjectLiteralFlag; + let patternWithComputedProperties = false; + let hasComputedStringProperty = false; + let hasComputedNumberProperty = false; + let hasComputedSymbolProperty = false; + + // Spreads may cause an early bail; ensure computed names are always checked (this is cached) + // As otherwise they may not be checked until exports for the type at this position are retrieved, + // which may never occur. + for (const elem of node.properties) { + if (elem.name && isComputedPropertyName(elem.name)) { + checkComputedPropertyName(elem.name); + } + } + + let offset = 0; + for (const memberDecl of node.properties) { + let member = getSymbolOfNode(memberDecl); + const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName ? + checkComputedPropertyName(memberDecl.name) : undefined; + if (memberDecl.kind === SyntaxKind.PropertyAssignment || + memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment || + isObjectLiteralMethod(memberDecl)) { + let type = memberDecl.kind === SyntaxKind.PropertyAssignment ? checkPropertyAssignment(memberDecl, checkMode) : + // avoid resolving the left side of the ShorthandPropertyAssignment outside of the destructuring + // for error recovery purposes. For example, if a user wrote `{ a = 100 }` instead of `{ a: 100 }`. + // we don't want to say "could not find 'a'". + memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ? checkExpressionForMutableLocation(!inDestructuringPattern && memberDecl.objectAssignmentInitializer ? memberDecl.objectAssignmentInitializer : memberDecl.name, checkMode) : + checkObjectLiteralMethod(memberDecl, checkMode); + if (isInJavascript) { + const jsDocType = getTypeForDeclarationFromJSDocComment(memberDecl); + if (jsDocType) { + checkTypeAssignableTo(type, jsDocType, memberDecl); + type = jsDocType; + } + else if (enumTag && enumTag.typeExpression) { + checkTypeAssignableTo(type, getTypeFromTypeNode(enumTag.typeExpression), memberDecl); + } + } + objectFlags |= getObjectFlags(type) & ObjectFlags.PropagatingFlags; + const nameType = computedNameType && isTypeUsableAsPropertyName(computedNameType) ? computedNameType : undefined; + const prop = nameType ? + createSymbol(SymbolFlags.Property | member.flags, getPropertyNameFromType(nameType), checkFlags | CheckFlags.Late) : + createSymbol(SymbolFlags.Property | member.flags, member.escapedName, checkFlags); + if (nameType) { + prop.nameType = nameType; + } - else if (!compilerOptions.suppressExcessPropertyErrors && !getIndexInfoOfType(contextualType, stringType)) { - error(memberDecl.name, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, - symbolToString(member), typeToString(contextualType)); - } + if (inDestructuringPattern) { + // If object literal is an assignment pattern and if the assignment pattern specifies a default value + // for the property, make the property optional. + const isOptional = + (memberDecl.kind === SyntaxKind.PropertyAssignment && hasDefaultValue(memberDecl.initializer)) || + (memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment && memberDecl.objectAssignmentInitializer); + if (isOptional) { + prop.flags |= SymbolFlags.Optional; } - - prop.declarations = member.declarations; - prop.parent = member.parent; - if (member.valueDeclaration) { - prop.valueDeclaration = member.valueDeclaration; + } + else if (contextualTypeHasPattern && !(getObjectFlags(contextualType) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) { + // If object literal is contextually typed by the implied type of a binding pattern, and if the + // binding pattern specifies a default value for the property, make the property optional. + const impliedProp = getPropertyOfType(contextualType, member.escapedName); + if (impliedProp) { + prop.flags |= impliedProp.flags & SymbolFlags.Optional; } - prop.type = type; - prop.target = member; - member = prop; - allPropertiesTable?.set(prop.escapedName, prop); - - if (contextualType && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && - (memberDecl.kind === SyntaxKind.PropertyAssignment || memberDecl.kind === SyntaxKind.MethodDeclaration) && isContextSensitive(memberDecl)) { - const inferenceContext = getInferenceContext(node); - Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context - const inferenceNode = memberDecl.kind === SyntaxKind.PropertyAssignment ? memberDecl.initializer : memberDecl; - addIntraExpressionInferenceSite(inferenceContext, inferenceNode, type); - } - } - else if (memberDecl.kind === SyntaxKind.SpreadAssignment) { - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(memberDecl, ExternalEmitHelpers.Assign); - } - if (propertiesArray.length > 0) { - spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); - propertiesArray = []; - propertiesTable = createSymbolTable(); - hasComputedStringProperty = false; - hasComputedNumberProperty = false; - hasComputedSymbolProperty = false; - } - const type = getReducedType(checkExpression(memberDecl.expression)); - if (isValidSpreadType(type)) { - const mergedType = tryMergeUnionOfObjectTypeAndEmptyObject(type, inConstContext); - if (allPropertiesTable) { - checkSpreadPropOverrides(mergedType, allPropertiesTable, memberDecl); - } - offset = propertiesArray.length; - if (isErrorType(spread)) { - continue; - } - spread = getSpreadType(spread, mergedType, node.symbol, objectFlags, inConstContext); - } - else { - error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types); - spread = errorType; + else if (!compilerOptions.suppressExcessPropertyErrors && !getIndexInfoOfType(contextualType, stringType)) { + error(memberDecl.name, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, + symbolToString(member), typeToString(contextualType)); } - continue; - } - else { - // TypeScript 1.0 spec (April 2014) - // A get accessor declaration is processed in the same manner as - // an ordinary function declaration(section 6.1) with no parameters. - // A set accessor declaration is processed in the same manner - // as an ordinary function declaration with a single parameter and a Void return type. - Debug.assert(memberDecl.kind === SyntaxKind.GetAccessor || memberDecl.kind === SyntaxKind.SetAccessor); - checkNodeDeferred(memberDecl); } - if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) { - if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) { - if (isTypeAssignableTo(computedNameType, numberType)) { - hasComputedNumberProperty = true; - } - else if (isTypeAssignableTo(computedNameType, esSymbolType)) { - hasComputedSymbolProperty = true; - } - else { - hasComputedStringProperty = true; - } - if (inDestructuringPattern) { - patternWithComputedProperties = true; - } - } - } - else { - propertiesTable.set(member.escapedName, member); - } - propertiesArray.push(member); - } - - // If object literal is contextually typed by the implied type of a binding pattern, augment the result - // type with those properties for which the binding pattern specifies a default value. - // If the object literal is spread into another object literal, skip this step and let the top-level object - // literal handle it instead. Note that this might require full traversal to the root pattern's parent - // as it's the guaranteed to be the common ancestor of the pattern node and the current object node. - // It's not possible to check if the immediate parent node is a spread assignment - // since the type flows in non-obvious ways through conditional expressions, IIFEs and more. - if (contextualTypeHasPattern) { - const rootPatternParent = findAncestor(contextualType.pattern!.parent, n => - n.kind === SyntaxKind.VariableDeclaration || - n.kind === SyntaxKind.BinaryExpression || - n.kind === SyntaxKind.Parameter - ); - const spreadOrOutsideRootObject = findAncestor(node, n => - n === rootPatternParent || - n.kind === SyntaxKind.SpreadAssignment - )!; - - if (spreadOrOutsideRootObject.kind !== SyntaxKind.SpreadAssignment) { - for (const prop of getPropertiesOfType(contextualType)) { - if (!propertiesTable.get(prop.escapedName) && !getPropertyOfType(spread, prop.escapedName)) { - if (!(prop.flags & SymbolFlags.Optional)) { - error(prop.valueDeclaration || (prop as TransientSymbol).bindingElement, - Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value); - } - propertiesTable.set(prop.escapedName, prop); - propertiesArray.push(prop); - } - } + prop.declarations = member.declarations; + prop.parent = member.parent; + if (member.valueDeclaration) { + prop.valueDeclaration = member.valueDeclaration; } - } - if (isErrorType(spread)) { - return errorType; - } + prop.type = type; + prop.target = member; + member = prop; + allPropertiesTable?.set(prop.escapedName, prop); - if (spread !== emptyObjectType) { + if (contextualType && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && + (memberDecl.kind === SyntaxKind.PropertyAssignment || memberDecl.kind === SyntaxKind.MethodDeclaration) && isContextSensitive(memberDecl)) { + const inferenceContext = getInferenceContext(node); + Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context + const inferenceNode = memberDecl.kind === SyntaxKind.PropertyAssignment ? memberDecl.initializer : memberDecl; + addIntraExpressionInferenceSite(inferenceContext, inferenceNode, type); + } + } + else if (memberDecl.kind === SyntaxKind.SpreadAssignment) { + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(memberDecl, ExternalEmitHelpers.Assign); + } if (propertiesArray.length > 0) { spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); propertiesArray = []; propertiesTable = createSymbolTable(); hasComputedStringProperty = false; hasComputedNumberProperty = false; + hasComputedSymbolProperty = false; } - // remap the raw emptyObjectType fed in at the top into a fresh empty object literal type, unique to this use site - return mapType(spread, t => t === emptyObjectType ? createObjectLiteralType() : t); - } - - return createObjectLiteralType(); - - function createObjectLiteralType() { - const indexInfos = []; - if (hasComputedStringProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, stringType)); - if (hasComputedNumberProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, numberType)); - if (hasComputedSymbolProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, esSymbolType)); - const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, indexInfos); - result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - if (isJSObjectLiteral) { - result.objectFlags |= ObjectFlags.JSLiteral; + const type = getReducedType(checkExpression(memberDecl.expression)); + if (isValidSpreadType(type)) { + const mergedType = tryMergeUnionOfObjectTypeAndEmptyObject(type, inConstContext); + if (allPropertiesTable) { + checkSpreadPropOverrides(mergedType, allPropertiesTable, memberDecl); + } + offset = propertiesArray.length; + if (isErrorType(spread)) { + continue; + } + spread = getSpreadType(spread, mergedType, node.symbol, objectFlags, inConstContext); } - if (patternWithComputedProperties) { - result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; + else { + error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types); + spread = errorType; } - if (inDestructuringPattern) { - result.pattern = node; + continue; + } + else { + // TypeScript 1.0 spec (April 2014) + // A get accessor declaration is processed in the same manner as + // an ordinary function declaration(section 6.1) with no parameters. + // A set accessor declaration is processed in the same manner + // as an ordinary function declaration with a single parameter and a Void return type. + Debug.assert(memberDecl.kind === SyntaxKind.GetAccessor || memberDecl.kind === SyntaxKind.SetAccessor); + checkNodeDeferred(memberDecl); + } + + if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) { + if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) { + if (isTypeAssignableTo(computedNameType, numberType)) { + hasComputedNumberProperty = true; + } + else if (isTypeAssignableTo(computedNameType, esSymbolType)) { + hasComputedSymbolProperty = true; + } + else { + hasComputedStringProperty = true; + } + if (inDestructuringPattern) { + patternWithComputedProperties = true; + } } - return result; } - } + else { + propertiesTable.set(member.escapedName, member); + } + propertiesArray.push(member); + } + + // If object literal is contextually typed by the implied type of a binding pattern, augment the result + // type with those properties for which the binding pattern specifies a default value. + // If the object literal is spread into another object literal, skip this step and let the top-level object + // literal handle it instead. Note that this might require full traversal to the root pattern's parent + // as it's the guaranteed to be the common ancestor of the pattern node and the current object node. + // It's not possible to check if the immediate parent node is a spread assignment + // since the type flows in non-obvious ways through conditional expressions, IIFEs and more. + if (contextualTypeHasPattern) { + const rootPatternParent = findAncestor(contextualType.pattern!.parent, n => + n.kind === SyntaxKind.VariableDeclaration || + n.kind === SyntaxKind.BinaryExpression || + n.kind === SyntaxKind.Parameter + ); + const spreadOrOutsideRootObject = findAncestor(node, n => + n === rootPatternParent || + n.kind === SyntaxKind.SpreadAssignment + )!; - function isValidSpreadType(type: Type): boolean { - const t = removeDefinitelyFalsyTypes(mapType(type, getBaseConstraintOrType)); - return !!(t.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) || - t.flags & TypeFlags.UnionOrIntersection && every((t as UnionOrIntersectionType).types, isValidSpreadType)); + if (spreadOrOutsideRootObject.kind !== SyntaxKind.SpreadAssignment) { + for (const prop of getPropertiesOfType(contextualType)) { + if (!propertiesTable.get(prop.escapedName) && !getPropertyOfType(spread, prop.escapedName)) { + if (!(prop.flags & SymbolFlags.Optional)) { + error(prop.valueDeclaration || (prop as TransientSymbol).bindingElement, + Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value); + } + propertiesTable.set(prop.escapedName, prop); + propertiesArray.push(prop); + } + } + } } - function checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) { - checkJsxOpeningLikeElementOrOpeningFragment(node); + if (isErrorType(spread)) { + return errorType; } - function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type { - checkNodeDeferred(node); - return getJsxElementTypeAt(node) || anyType; + if (spread !== emptyObjectType) { + if (propertiesArray.length > 0) { + spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); + propertiesArray = []; + propertiesTable = createSymbolTable(); + hasComputedStringProperty = false; + hasComputedNumberProperty = false; + } + // remap the raw emptyObjectType fed in at the top into a fresh empty object literal type, unique to this use site + return mapType(spread, t => t === emptyObjectType ? createObjectLiteralType() : t); } - function checkJsxElementDeferred(node: JsxElement) { - // Check attributes - checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement); + return createObjectLiteralType(); - // Perform resolution on the closing tag so that rename/go to definition/etc work - if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) { - getIntrinsicTagSymbol(node.closingElement); + function createObjectLiteralType() { + const indexInfos = []; + if (hasComputedStringProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, stringType)); + if (hasComputedNumberProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, numberType)); + if (hasComputedSymbolProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, esSymbolType)); + const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, indexInfos); + result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + if (isJSObjectLiteral) { + result.objectFlags |= ObjectFlags.JSLiteral; } - else { - checkExpression(node.closingElement.tagName); + if (patternWithComputedProperties) { + result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; } - - checkJsxChildren(node); + if (inDestructuringPattern) { + result.pattern = node; + } + return result; } + } - function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type { - checkNodeDeferred(node); - - return getJsxElementTypeAt(node) || anyType; - } + function isValidSpreadType(type: Type): boolean { + const t = removeDefinitelyFalsyTypes(mapType(type, getBaseConstraintOrType)); + return !!(t.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) || + t.flags & TypeFlags.UnionOrIntersection && every((t as UnionOrIntersectionType).types, isValidSpreadType)); + } - function checkJsxFragment(node: JsxFragment): Type { - checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment); + function checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) { + checkJsxOpeningLikeElementOrOpeningFragment(node); + } - // by default, jsx:'react' will use jsxFactory = React.createElement and jsxFragmentFactory = React.Fragment - // if jsxFactory compiler option is provided, ensure jsxFragmentFactory compiler option or @jsxFrag pragma is provided too - const nodeSourceFile = getSourceFileOfNode(node); - if (getJSXTransformEnabled(compilerOptions) && (compilerOptions.jsxFactory || nodeSourceFile.pragmas.has("jsx")) - && !compilerOptions.jsxFragmentFactory && !nodeSourceFile.pragmas.has("jsxfrag")) { - error(node, compilerOptions.jsxFactory - ? Diagnostics.The_jsxFragmentFactory_compiler_option_must_be_provided_to_use_JSX_fragments_with_the_jsxFactory_compiler_option - : Diagnostics.An_jsxFrag_pragma_is_required_when_using_an_jsx_pragma_with_JSX_fragments); - } + function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type { + checkNodeDeferred(node); + return getJsxElementTypeAt(node) || anyType; + } - checkJsxChildren(node); - return getJsxElementTypeAt(node) || anyType; - } + function checkJsxElementDeferred(node: JsxElement) { + // Check attributes + checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement); - function isHyphenatedJsxName(name: string | __String) { - return stringContains(name as string, "-"); + // Perform resolution on the closing tag so that rename/go to definition/etc work + if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) { + getIntrinsicTagSymbol(node.closingElement); } - - /** - * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name - */ - function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): boolean { - return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText); + else { + checkExpression(node.closingElement.tagName); } - function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) { - return node.initializer - ? checkExpressionForMutableLocation(node.initializer, checkMode) - : trueType; // is sugar for - } + checkJsxChildren(node); + } - /** - * Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element. - * - * @param openingLikeElement a JSX opening-like element - * @param filter a function to remove attributes that will not participate in checking whether attributes are assignable - * @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property. - * @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral, - * which also calls getSpreadType. - */ - function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode | undefined) { - const attributes = openingLikeElement.attributes; - const attributesType = getContextualType(attributes, ContextFlags.None); - const allAttributesTable = strictNullChecks ? createSymbolTable() : undefined; - let attributesTable = createSymbolTable(); - let spread: Type = emptyJsxObjectType; - let hasSpreadAnyType = false; - let typeToIntersect: Type | undefined; - let explicitlySpecifyChildrenAttribute = false; - let objectFlags: ObjectFlags = ObjectFlags.JsxAttributes; - const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(openingLikeElement)); - - for (const attributeDecl of attributes.properties) { - const member = attributeDecl.symbol; - if (isJsxAttribute(attributeDecl)) { - const exprType = checkJsxAttribute(attributeDecl, checkMode); - objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags; - - const attributeSymbol = createSymbol(SymbolFlags.Property | member.flags, member.escapedName); - attributeSymbol.declarations = member.declarations; - attributeSymbol.parent = member.parent; - if (member.valueDeclaration) { - attributeSymbol.valueDeclaration = member.valueDeclaration; - } - attributeSymbol.type = exprType; - attributeSymbol.target = member; - attributesTable.set(attributeSymbol.escapedName, attributeSymbol); - allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol); - if (attributeDecl.name.escapedText === jsxChildrenPropertyName) { - explicitlySpecifyChildrenAttribute = true; - } - if (attributesType) { - const prop = getPropertyOfType(attributesType, member.escapedName); - if (prop && prop.declarations && isDeprecatedSymbol(prop)) { - addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string); - } - } - } - else { - Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute); - if (attributesTable.size > 0) { - spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); - attributesTable = createSymbolTable(); - } - const exprType = getReducedType(checkExpressionCached(attributeDecl.expression, checkMode)); - if (isTypeAny(exprType)) { - hasSpreadAnyType = true; - } - if (isValidSpreadType(exprType)) { - spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false); - if (allAttributesTable) { - checkSpreadPropOverrides(exprType, allAttributesTable, attributeDecl); - } - } - else { - error(attributeDecl.expression, Diagnostics.Spread_types_may_only_be_created_from_object_types); - typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType; - } - } - } + function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type { + checkNodeDeferred(node); - if (!hasSpreadAnyType) { - if (attributesTable.size > 0) { - spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); - } - } + return getJsxElementTypeAt(node) || anyType; + } - // Handle children attribute - const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined; - // We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement - if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) { - const childrenTypes: Type[] = checkJsxChildren(parent, checkMode); + function checkJsxFragment(node: JsxFragment): Type { + checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment); - if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") { - // Error if there is a attribute named "children" explicitly specified and children element. - // This is because children element will overwrite the value from attributes. - // Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread. - if (explicitlySpecifyChildrenAttribute) { - error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName)); - } + // by default, jsx:'react' will use jsxFactory = React.createElement and jsxFragmentFactory = React.Fragment + // if jsxFactory compiler option is provided, ensure jsxFragmentFactory compiler option or @jsxFrag pragma is provided too + const nodeSourceFile = getSourceFileOfNode(node); + if (getJSXTransformEnabled(compilerOptions) && (compilerOptions.jsxFactory || nodeSourceFile.pragmas.has("jsx")) + && !compilerOptions.jsxFragmentFactory && !nodeSourceFile.pragmas.has("jsxfrag")) { + error(node, compilerOptions.jsxFactory + ? Diagnostics.The_jsxFragmentFactory_compiler_option_must_be_provided_to_use_JSX_fragments_with_the_jsxFactory_compiler_option + : Diagnostics.An_jsxFrag_pragma_is_required_when_using_an_jsx_pragma_with_JSX_fragments); + } - const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes, /*contextFlags*/ undefined); - const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName); - // If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process - const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName); - childrenPropSymbol.type = childrenTypes.length === 1 ? childrenTypes[0] : - childrenContextualType && someType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) : - createArrayType(getUnionType(childrenTypes)); - // Fake up a property declaration for the children - childrenPropSymbol.valueDeclaration = factory.createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined); - setParent(childrenPropSymbol.valueDeclaration, attributes); - childrenPropSymbol.valueDeclaration.symbol = childrenPropSymbol; - const childPropMap = createSymbolTable(); - childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol); - spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, emptyArray), - attributes.symbol, objectFlags, /*readonly*/ false); + checkJsxChildren(node); + return getJsxElementTypeAt(node) || anyType; + } - } - } + function isHyphenatedJsxName(name: string | __String) { + return stringContains(name as string, "-"); + } - if (hasSpreadAnyType) { - return anyType; - } - if (typeToIntersect && spread !== emptyJsxObjectType) { - return getIntersectionType([typeToIntersect, spread]); - } - return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread); + /** + * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name + */ + function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): boolean { + return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText); + } - /** - * Create anonymous type from given attributes symbol table. - * @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable - * @param attributesTable a symbol table of attributes property - */ - function createJsxAttributesType() { - objectFlags |= freshObjectLiteralFlag; - const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, emptyArray); - result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; - return result; - } - } + function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) { + return node.initializer + ? checkExpressionForMutableLocation(node.initializer, checkMode) + : trueType; // is sugar for + } - function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) { - const childrenTypes: Type[] = []; - for (const child of node.children) { - // In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that - // because then type of children property will have constituent of string type. - if (child.kind === SyntaxKind.JsxText) { - if (!child.containsOnlyTriviaWhiteSpaces) { - childrenTypes.push(stringType); + /** + * Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element. + * + * @param openingLikeElement a JSX opening-like element + * @param filter a function to remove attributes that will not participate in checking whether attributes are assignable + * @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property. + * @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral, + * which also calls getSpreadType. + */ + function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode | undefined) { + const attributes = openingLikeElement.attributes; + const attributesType = getContextualType(attributes, ContextFlags.None); + const allAttributesTable = strictNullChecks ? createSymbolTable() : undefined; + let attributesTable = createSymbolTable(); + let spread: Type = emptyJsxObjectType; + let hasSpreadAnyType = false; + let typeToIntersect: Type | undefined; + let explicitlySpecifyChildrenAttribute = false; + let objectFlags: ObjectFlags = ObjectFlags.JsxAttributes; + const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(openingLikeElement)); + + for (const attributeDecl of attributes.properties) { + const member = attributeDecl.symbol; + if (isJsxAttribute(attributeDecl)) { + const exprType = checkJsxAttribute(attributeDecl, checkMode); + objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags; + + const attributeSymbol = createSymbol(SymbolFlags.Property | member.flags, member.escapedName); + attributeSymbol.declarations = member.declarations; + attributeSymbol.parent = member.parent; + if (member.valueDeclaration) { + attributeSymbol.valueDeclaration = member.valueDeclaration; + } + attributeSymbol.type = exprType; + attributeSymbol.target = member; + attributesTable.set(attributeSymbol.escapedName, attributeSymbol); + allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol); + if (attributeDecl.name.escapedText === jsxChildrenPropertyName) { + explicitlySpecifyChildrenAttribute = true; + } + if (attributesType) { + const prop = getPropertyOfType(attributesType, member.escapedName); + if (prop && prop.declarations && isDeprecatedSymbol(prop)) { + addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string); } } - else if (child.kind === SyntaxKind.JsxExpression && !child.expression) { - continue; // empty jsx expressions don't *really* count as present children + } + else { + Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute); + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); + attributesTable = createSymbolTable(); + } + const exprType = getReducedType(checkExpressionCached(attributeDecl.expression, checkMode)); + if (isTypeAny(exprType)) { + hasSpreadAnyType = true; + } + if (isValidSpreadType(exprType)) { + spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false); + if (allAttributesTable) { + checkSpreadPropOverrides(exprType, allAttributesTable, attributeDecl); + } } else { - childrenTypes.push(checkExpressionForMutableLocation(child, checkMode)); + error(attributeDecl.expression, Diagnostics.Spread_types_may_only_be_created_from_object_types); + typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType; } } - return childrenTypes; } - function checkSpreadPropOverrides(type: Type, props: SymbolTable, spread: SpreadAssignment | JsxSpreadAttribute) { - for (const right of getPropertiesOfType(type)) { - if (!(right.flags & SymbolFlags.Optional)) { - const left = props.get(right.escapedName); - if (left) { - const diagnostic = error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName)); - addRelatedInfo(diagnostic, createDiagnosticForNode(spread, Diagnostics.This_spread_always_overwrites_this_property)); - } - } + if (!hasSpreadAnyType) { + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); } } - /** - * Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element. - * (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used) - * @param node a JSXAttributes to be resolved of its type - */ - function checkJsxAttributes(node: JsxAttributes, checkMode: CheckMode | undefined) { - return createJsxAttributesTypeFromAttributesProperty(node.parent, checkMode); + // Handle children attribute + const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined; + // We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement + if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) { + const childrenTypes: Type[] = checkJsxChildren(parent, checkMode); + + if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") { + // Error if there is a attribute named "children" explicitly specified and children element. + // This is because children element will overwrite the value from attributes. + // Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread. + if (explicitlySpecifyChildrenAttribute) { + error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName)); + } + + const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes, /*contextFlags*/ undefined); + const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName); + // If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process + const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName); + childrenPropSymbol.type = childrenTypes.length === 1 ? childrenTypes[0] : + childrenContextualType && someType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) : + createArrayType(getUnionType(childrenTypes)); + // Fake up a property declaration for the children + childrenPropSymbol.valueDeclaration = factory.createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined); + setParent(childrenPropSymbol.valueDeclaration, attributes); + childrenPropSymbol.valueDeclaration.symbol = childrenPropSymbol; + const childPropMap = createSymbolTable(); + childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol); + spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, emptyArray), + attributes.symbol, objectFlags, /*readonly*/ false); + + } } - function getJsxType(name: __String, location: Node | undefined) { - const namespace = getJsxNamespaceAt(location); - const exports = namespace && getExportsOfSymbol(namespace); - const typeSymbol = exports && getSymbol(exports, name, SymbolFlags.Type); - return typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType; + if (hasSpreadAnyType) { + return anyType; } + if (typeToIntersect && spread !== emptyJsxObjectType) { + return getIntersectionType([typeToIntersect, spread]); + } + return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread); /** - * Looks up an intrinsic tag name and returns a symbol that either points to an intrinsic - * property (in which case nodeLinks.jsxFlags will be IntrinsicNamedElement) or an intrinsic - * string index signature (in which case nodeLinks.jsxFlags will be IntrinsicIndexedElement). - * May also return unknownSymbol if both of these lookups fail. + * Create anonymous type from given attributes symbol table. + * @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable + * @param attributesTable a symbol table of attributes property */ - function getIntrinsicTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol { - const links = getNodeLinks(node); - if (!links.resolvedSymbol) { - const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node); - if (!isErrorType(intrinsicElementsType)) { - // Property case - if (!isIdentifier(node.tagName)) return Debug.fail(); - const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText); - if (intrinsicProp) { - links.jsxFlags |= JsxFlags.IntrinsicNamedElement; - return links.resolvedSymbol = intrinsicProp; - } - - // Intrinsic string indexer case - const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, stringType); - if (indexSignatureType) { - links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; - return links.resolvedSymbol = intrinsicElementsType.symbol; - } - - // Wasn't found - error(node, Diagnostics.Property_0_does_not_exist_on_type_1, idText(node.tagName), "JSX." + JsxNames.IntrinsicElements); - return links.resolvedSymbol = unknownSymbol; - } - else { - if (noImplicitAny) { - error(node, Diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, unescapeLeadingUnderscores(JsxNames.IntrinsicElements)); - } - return links.resolvedSymbol = unknownSymbol; - } - } - return links.resolvedSymbol; + function createJsxAttributesType() { + objectFlags |= freshObjectLiteralFlag; + const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, emptyArray); + result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + return result; } + } - function getJsxNamespaceContainerForImplicitImport(location: Node | undefined): Symbol | undefined { - const file = location && getSourceFileOfNode(location); - const links = file && getNodeLinks(file); - if (links && links.jsxImplicitImportContainer === false) { - return undefined; - } - if (links && links.jsxImplicitImportContainer) { - return links.jsxImplicitImportContainer; + function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) { + const childrenTypes: Type[] = []; + for (const child of node.children) { + // In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that + // because then type of children property will have constituent of string type. + if (child.kind === SyntaxKind.JsxText) { + if (!child.containsOnlyTriviaWhiteSpaces) { + childrenTypes.push(stringType); + } } - const runtimeImportSpecifier = getJSXRuntimeImport(getJSXImplicitImportBase(compilerOptions, file), compilerOptions); - if (!runtimeImportSpecifier) { - return undefined; + else if (child.kind === SyntaxKind.JsxExpression && !child.expression) { + continue; // empty jsx expressions don't *really* count as present children } - const isClassic = getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic; - const errorMessage = isClassic - ? Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option - : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; - const mod = resolveExternalModule(location!, runtimeImportSpecifier, errorMessage, location!); - const result = mod && mod !== unknownSymbol ? getMergedSymbol(resolveSymbol(mod)) : undefined; - if (links) { - links.jsxImplicitImportContainer = result || false; + else { + childrenTypes.push(checkExpressionForMutableLocation(child, checkMode)); } - return result; } + return childrenTypes; + } - function getJsxNamespaceAt(location: Node | undefined): Symbol { - const links = location && getNodeLinks(location); - if (links && links.jsxNamespace) { - return links.jsxNamespace; + function checkSpreadPropOverrides(type: Type, props: SymbolTable, spread: SpreadAssignment | JsxSpreadAttribute) { + for (const right of getPropertiesOfType(type)) { + if (!(right.flags & SymbolFlags.Optional)) { + const left = props.get(right.escapedName); + if (left) { + const diagnostic = error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName)); + addRelatedInfo(diagnostic, createDiagnosticForNode(spread, Diagnostics.This_spread_always_overwrites_this_property)); + } } - if (!links || links.jsxNamespace !== false) { - let resolvedNamespace = getJsxNamespaceContainerForImplicitImport(location); + } + } - if (!resolvedNamespace || resolvedNamespace === unknownSymbol) { - const namespaceName = getJsxNamespace(location); - resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false); - } + /** + * Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element. + * (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used) + * @param node a JSXAttributes to be resolved of its type + */ + function checkJsxAttributes(node: JsxAttributes, checkMode: CheckMode | undefined) { + return createJsxAttributesTypeFromAttributesProperty(node.parent, checkMode); + } - if (resolvedNamespace) { - const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace)); - if (candidate && candidate !== unknownSymbol) { - if (links) { - links.jsxNamespace = candidate; - } - return candidate; - } + function getJsxType(name: __String, location: Node | undefined) { + const namespace = getJsxNamespaceAt(location); + const exports = namespace && getExportsOfSymbol(namespace); + const typeSymbol = exports && getSymbol(exports, name, SymbolFlags.Type); + return typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType; + } + + /** + * Looks up an intrinsic tag name and returns a symbol that either points to an intrinsic + * property (in which case nodeLinks.jsxFlags will be IntrinsicNamedElement) or an intrinsic + * string index signature (in which case nodeLinks.jsxFlags will be IntrinsicIndexedElement). + * May also return unknownSymbol if both of these lookups fail. + */ + function getIntrinsicTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol { + const links = getNodeLinks(node); + if (!links.resolvedSymbol) { + const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node); + if (!isErrorType(intrinsicElementsType)) { + // Property case + if (!isIdentifier(node.tagName)) return Debug.fail(); + const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText); + if (intrinsicProp) { + links.jsxFlags |= JsxFlags.IntrinsicNamedElement; + return links.resolvedSymbol = intrinsicProp; } - if (links) { - links.jsxNamespace = false; + + // Intrinsic string indexer case + const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, stringType); + if (indexSignatureType) { + links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; + return links.resolvedSymbol = intrinsicElementsType.symbol; } + + // Wasn't found + error(node, Diagnostics.Property_0_does_not_exist_on_type_1, idText(node.tagName), "JSX." + JsxNames.IntrinsicElements); + return links.resolvedSymbol = unknownSymbol; } - // JSX global fallback - const s = resolveSymbol(getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined)); - if (s === unknownSymbol) { - return undefined!; // TODO: GH#18217 + else { + if (noImplicitAny) { + error(node, Diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, unescapeLeadingUnderscores(JsxNames.IntrinsicElements)); + } + return links.resolvedSymbol = unknownSymbol; } - return s!; // TODO: GH#18217 } + return links.resolvedSymbol; + } - /** - * Look into JSX namespace and then look for container with matching name as nameOfAttribPropContainer. - * Get a single property from that container if existed. Report an error if there are more than one property. - * - * @param nameOfAttribPropContainer a string of value JsxNames.ElementAttributesPropertyNameContainer or JsxNames.ElementChildrenAttributeNameContainer - * if other string is given or the container doesn't exist, return undefined. - */ - function getNameFromJsxElementAttributesContainer(nameOfAttribPropContainer: __String, jsxNamespace: Symbol): __String | undefined { - // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [symbol] - const jsxElementAttribPropInterfaceSym = jsxNamespace && getSymbol(jsxNamespace.exports!, nameOfAttribPropContainer, SymbolFlags.Type); - // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [type] - const jsxElementAttribPropInterfaceType = jsxElementAttribPropInterfaceSym && getDeclaredTypeOfSymbol(jsxElementAttribPropInterfaceSym); - // The properties of JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute - const propertiesOfJsxElementAttribPropInterface = jsxElementAttribPropInterfaceType && getPropertiesOfType(jsxElementAttribPropInterfaceType); - if (propertiesOfJsxElementAttribPropInterface) { - // Element Attributes has zero properties, so the element attributes type will be the class instance type - if (propertiesOfJsxElementAttribPropInterface.length === 0) { - return "" as __String; - } - // Element Attributes has one property, so the element attributes type will be the type of the corresponding - // property of the class instance type - else if (propertiesOfJsxElementAttribPropInterface.length === 1) { - return propertiesOfJsxElementAttribPropInterface[0].escapedName; - } - else if (propertiesOfJsxElementAttribPropInterface.length > 1 && jsxElementAttribPropInterfaceSym.declarations) { - // More than one property on ElementAttributesProperty is an error - error(jsxElementAttribPropInterfaceSym.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, unescapeLeadingUnderscores(nameOfAttribPropContainer)); - } - } + function getJsxNamespaceContainerForImplicitImport(location: Node | undefined): Symbol | undefined { + const file = location && getSourceFileOfNode(location); + const links = file && getNodeLinks(file); + if (links && links.jsxImplicitImportContainer === false) { return undefined; } - - function getJsxLibraryManagedAttributes(jsxNamespace: Symbol) { - // JSX.LibraryManagedAttributes [symbol] - return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.LibraryManagedAttributes, SymbolFlags.Type); + if (links && links.jsxImplicitImportContainer) { + return links.jsxImplicitImportContainer; } - - /// e.g. "props" for React.d.ts, - /// or 'undefined' if ElementAttributesProperty doesn't exist (which means all - /// non-intrinsic elements' attributes type is 'any'), - /// or '' if it has 0 properties (which means every - /// non-intrinsic elements' attributes type is the element instance type) - function getJsxElementPropertiesName(jsxNamespace: Symbol) { - return getNameFromJsxElementAttributesContainer(JsxNames.ElementAttributesPropertyNameContainer, jsxNamespace); + const runtimeImportSpecifier = getJSXRuntimeImport(getJSXImplicitImportBase(compilerOptions, file), compilerOptions); + if (!runtimeImportSpecifier) { + return undefined; + } + const isClassic = getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic; + const errorMessage = isClassic + ? Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option + : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; + const mod = resolveExternalModule(location!, runtimeImportSpecifier, errorMessage, location!); + const result = mod && mod !== unknownSymbol ? getMergedSymbol(resolveSymbol(mod)) : undefined; + if (links) { + links.jsxImplicitImportContainer = result || false; } + return result; + } - function getJsxElementChildrenPropertyName(jsxNamespace: Symbol): __String | undefined { - return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace); + function getJsxNamespaceAt(location: Node | undefined): Symbol { + const links = location && getNodeLinks(location); + if (links && links.jsxNamespace) { + return links.jsxNamespace; } + if (!links || links.jsxNamespace !== false) { + let resolvedNamespace = getJsxNamespaceContainerForImplicitImport(location); - function getUninstantiatedJsxSignaturesOfType(elementType: Type, caller: JsxOpeningLikeElement): readonly Signature[] { - if (elementType.flags & TypeFlags.String) { - return [anySignature]; + if (!resolvedNamespace || resolvedNamespace === unknownSymbol) { + const namespaceName = getJsxNamespace(location); + resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false); } - else if (elementType.flags & TypeFlags.StringLiteral) { - const intrinsicType = getIntrinsicAttributesTypeFromStringLiteralType(elementType as StringLiteralType, caller); - if (!intrinsicType) { - error(caller, Diagnostics.Property_0_does_not_exist_on_type_1, (elementType as StringLiteralType).value, "JSX." + JsxNames.IntrinsicElements); - return emptyArray; - } - else { - const fakeSignature = createSignatureForJSXIntrinsic(caller, intrinsicType); - return [fakeSignature]; + + if (resolvedNamespace) { + const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace)); + if (candidate && candidate !== unknownSymbol) { + if (links) { + links.jsxNamespace = candidate; + } + return candidate; } } - const apparentElemType = getApparentType(elementType); - // Resolve the signatures, preferring constructor - let signatures = getSignaturesOfType(apparentElemType, SignatureKind.Construct); - if (signatures.length === 0) { - // No construct signatures, try call signatures - signatures = getSignaturesOfType(apparentElemType, SignatureKind.Call); - } - if (signatures.length === 0 && apparentElemType.flags & TypeFlags.Union) { - // If each member has some combination of new/call signatures; make a union signature list for those - signatures = getUnionSignatures(map((apparentElemType as UnionType).types, t => getUninstantiatedJsxSignaturesOfType(t, caller))); + if (links) { + links.jsxNamespace = false; } - return signatures; } - - function getIntrinsicAttributesTypeFromStringLiteralType(type: StringLiteralType, location: Node): Type | undefined { - // If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type - // For example: - // var CustomTag: "h1" = "h1"; - // Hello World - const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, location); - if (!isErrorType(intrinsicElementsType)) { - const stringLiteralTypeName = type.value; - const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName)); - if (intrinsicProp) { - return getTypeOfSymbol(intrinsicProp); - } - const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, stringType); - if (indexSignatureType) { - return indexSignatureType; - } - return undefined; - } - // If we need to report an error, we already done so here. So just return any to prevent any more error downstream - return anyType; + // JSX global fallback + const s = resolveSymbol(getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined)); + if (s === unknownSymbol) { + return undefined!; // TODO: GH#18217 } + return s!; // TODO: GH#18217 + } - function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: JsxOpeningLikeElement) { - if (refKind === JsxReferenceKind.Function) { - const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); - if (sfcReturnConstraint) { - checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_return_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); - } - } - else if (refKind === JsxReferenceKind.Component) { - const classConstraint = getJsxElementClassTypeAt(openingLikeElement); - if (classConstraint) { - // Issue an error if this return type isn't assignable to JSX.ElementClass, failing that - checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_instance_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); - } + /** + * Look into JSX namespace and then look for container with matching name as nameOfAttribPropContainer. + * Get a single property from that container if existed. Report an error if there are more than one property. + * + * @param nameOfAttribPropContainer a string of value JsxNames.ElementAttributesPropertyNameContainer or JsxNames.ElementChildrenAttributeNameContainer + * if other string is given or the container doesn't exist, return undefined. + */ + function getNameFromJsxElementAttributesContainer(nameOfAttribPropContainer: __String, jsxNamespace: Symbol): __String | undefined { + // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [symbol] + const jsxElementAttribPropInterfaceSym = jsxNamespace && getSymbol(jsxNamespace.exports!, nameOfAttribPropContainer, SymbolFlags.Type); + // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [type] + const jsxElementAttribPropInterfaceType = jsxElementAttribPropInterfaceSym && getDeclaredTypeOfSymbol(jsxElementAttribPropInterfaceSym); + // The properties of JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute + const propertiesOfJsxElementAttribPropInterface = jsxElementAttribPropInterfaceType && getPropertiesOfType(jsxElementAttribPropInterfaceType); + if (propertiesOfJsxElementAttribPropInterface) { + // Element Attributes has zero properties, so the element attributes type will be the class instance type + if (propertiesOfJsxElementAttribPropInterface.length === 0) { + return "" as __String; + } + // Element Attributes has one property, so the element attributes type will be the type of the corresponding + // property of the class instance type + else if (propertiesOfJsxElementAttribPropInterface.length === 1) { + return propertiesOfJsxElementAttribPropInterface[0].escapedName; + } + else if (propertiesOfJsxElementAttribPropInterface.length > 1 && jsxElementAttribPropInterfaceSym.declarations) { + // More than one property on ElementAttributesProperty is an error + error(jsxElementAttribPropInterfaceSym.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, unescapeLeadingUnderscores(nameOfAttribPropContainer)); + } + } + return undefined; + } + + function getJsxLibraryManagedAttributes(jsxNamespace: Symbol) { + // JSX.LibraryManagedAttributes [symbol] + return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.LibraryManagedAttributes, SymbolFlags.Type); + } + + /// e.g. "props" for React.d.ts, + /// or 'undefined' if ElementAttributesProperty doesn't exist (which means all + /// non-intrinsic elements' attributes type is 'any'), + /// or '' if it has 0 properties (which means every + /// non-intrinsic elements' attributes type is the element instance type) + function getJsxElementPropertiesName(jsxNamespace: Symbol) { + return getNameFromJsxElementAttributesContainer(JsxNames.ElementAttributesPropertyNameContainer, jsxNamespace); + } + + function getJsxElementChildrenPropertyName(jsxNamespace: Symbol): __String | undefined { + return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace); + } + + function getUninstantiatedJsxSignaturesOfType(elementType: Type, caller: JsxOpeningLikeElement): readonly Signature[] { + if (elementType.flags & TypeFlags.String) { + return [anySignature]; + } + else if (elementType.flags & TypeFlags.StringLiteral) { + const intrinsicType = getIntrinsicAttributesTypeFromStringLiteralType(elementType as StringLiteralType, caller); + if (!intrinsicType) { + error(caller, Diagnostics.Property_0_does_not_exist_on_type_1, (elementType as StringLiteralType).value, "JSX." + JsxNames.IntrinsicElements); + return emptyArray; } - else { // Mixed - const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); - const classConstraint = getJsxElementClassTypeAt(openingLikeElement); - if (!sfcReturnConstraint || !classConstraint) { - return; - } - const combined = getUnionType([sfcReturnConstraint, classConstraint]); - checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_element_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); + else { + const fakeSignature = createSignatureForJSXIntrinsic(caller, intrinsicType); + return [fakeSignature]; } + } + const apparentElemType = getApparentType(elementType); + // Resolve the signatures, preferring constructor + let signatures = getSignaturesOfType(apparentElemType, SignatureKind.Construct); + if (signatures.length === 0) { + // No construct signatures, try call signatures + signatures = getSignaturesOfType(apparentElemType, SignatureKind.Call); + } + if (signatures.length === 0 && apparentElemType.flags & TypeFlags.Union) { + // If each member has some combination of new/call signatures; make a union signature list for those + signatures = getUnionSignatures(map((apparentElemType as UnionType).types, t => getUninstantiatedJsxSignaturesOfType(t, caller))); + } + return signatures; + } - function generateInitialErrorChain(): DiagnosticMessageChain { - const componentName = getTextOfNode(openingLikeElement.tagName); - return chainDiagnosticMessages(/* details */ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName); + function getIntrinsicAttributesTypeFromStringLiteralType(type: StringLiteralType, location: Node): Type | undefined { + // If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type + // For example: + // var CustomTag: "h1" = "h1"; + // Hello World + const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, location); + if (!isErrorType(intrinsicElementsType)) { + const stringLiteralTypeName = type.value; + const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName)); + if (intrinsicProp) { + return getTypeOfSymbol(intrinsicProp); + } + const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, stringType); + if (indexSignatureType) { + return indexSignatureType; } + return undefined; } + // If we need to report an error, we already done so here. So just return any to prevent any more error downstream + return anyType; + } - /** - * Get attributes type of the given intrinsic opening-like Jsx element by resolving the tag name. - * The function is intended to be called from a function which has checked that the opening element is an intrinsic element. - * @param node an intrinsic JSX opening-like element - */ - function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type { - Debug.assert(isJsxIntrinsicIdentifier(node.tagName)); - const links = getNodeLinks(node); - if (!links.resolvedJsxElementAttributesType) { - const symbol = getIntrinsicTagSymbol(node); - if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) { - return links.resolvedJsxElementAttributesType = getTypeOfSymbol(symbol) || errorType; - } - else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) { - return links.resolvedJsxElementAttributesType = - getIndexTypeOfType(getJsxType(JsxNames.IntrinsicElements, node), stringType) || errorType; - } - else { - return links.resolvedJsxElementAttributesType = errorType; - } + function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: JsxOpeningLikeElement) { + if (refKind === JsxReferenceKind.Function) { + const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); + if (sfcReturnConstraint) { + checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_return_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); } - return links.resolvedJsxElementAttributesType; } - - function getJsxElementClassTypeAt(location: Node): Type | undefined { - const type = getJsxType(JsxNames.ElementClass, location); - if (isErrorType(type)) return undefined; - return type; + else if (refKind === JsxReferenceKind.Component) { + const classConstraint = getJsxElementClassTypeAt(openingLikeElement); + if (classConstraint) { + // Issue an error if this return type isn't assignable to JSX.ElementClass, failing that + checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_instance_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); + } + } + else { // Mixed + const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); + const classConstraint = getJsxElementClassTypeAt(openingLikeElement); + if (!sfcReturnConstraint || !classConstraint) { + return; + } + const combined = getUnionType([sfcReturnConstraint, classConstraint]); + checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_element_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); } - function getJsxElementTypeAt(location: Node): Type { - return getJsxType(JsxNames.Element, location); + function generateInitialErrorChain(): DiagnosticMessageChain { + const componentName = getTextOfNode(openingLikeElement.tagName); + return chainDiagnosticMessages(/* details */ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName); } + } - function getJsxStatelessElementTypeAt(location: Node): Type | undefined { - const jsxElementType = getJsxElementTypeAt(location); - if (jsxElementType) { - return getUnionType([jsxElementType, nullType]); + /** + * Get attributes type of the given intrinsic opening-like Jsx element by resolving the tag name. + * The function is intended to be called from a function which has checked that the opening element is an intrinsic element. + * @param node an intrinsic JSX opening-like element + */ + function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type { + Debug.assert(isJsxIntrinsicIdentifier(node.tagName)); + const links = getNodeLinks(node); + if (!links.resolvedJsxElementAttributesType) { + const symbol = getIntrinsicTagSymbol(node); + if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) { + return links.resolvedJsxElementAttributesType = getTypeOfSymbol(symbol) || errorType; + } + else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) { + return links.resolvedJsxElementAttributesType = + getIndexTypeOfType(getJsxType(JsxNames.IntrinsicElements, node), stringType) || errorType; + } + else { + return links.resolvedJsxElementAttributesType = errorType; } } + return links.resolvedJsxElementAttributesType; + } - /** - * Returns all the properties of the Jsx.IntrinsicElements interface - */ - function getJsxIntrinsicTagNamesAt(location: Node): Symbol[] { - const intrinsics = getJsxType(JsxNames.IntrinsicElements, location); - return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray; - } + function getJsxElementClassTypeAt(location: Node): Type | undefined { + const type = getJsxType(JsxNames.ElementClass, location); + if (isErrorType(type)) return undefined; + return type; + } - function checkJsxPreconditions(errorNode: Node) { - // Preconditions for using JSX - if ((compilerOptions.jsx || JsxEmit.None) === JsxEmit.None) { - error(errorNode, Diagnostics.Cannot_use_JSX_unless_the_jsx_flag_is_provided); - } + function getJsxElementTypeAt(location: Node): Type { + return getJsxType(JsxNames.Element, location); + } - if (getJsxElementTypeAt(errorNode) === undefined) { - if (noImplicitAny) { - error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist); - } - } + function getJsxStatelessElementTypeAt(location: Node): Type | undefined { + const jsxElementType = getJsxElementTypeAt(location); + if (jsxElementType) { + return getUnionType([jsxElementType, nullType]); } + } + + /** + * Returns all the properties of the Jsx.IntrinsicElements interface + */ + function getJsxIntrinsicTagNamesAt(location: Node): Symbol[] { + const intrinsics = getJsxType(JsxNames.IntrinsicElements, location); + return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray; + } - function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { - const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); + function checkJsxPreconditions(errorNode: Node) { + // Preconditions for using JSX + if ((compilerOptions.jsx || JsxEmit.None) === JsxEmit.None) { + error(errorNode, Diagnostics.Cannot_use_JSX_unless_the_jsx_flag_is_provided); + } - if (isNodeOpeningLikeElement) { - checkGrammarJsxElement(node); + if (getJsxElementTypeAt(errorNode) === undefined) { + if (noImplicitAny) { + error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist); } + } + } - checkJsxPreconditions(node); - - if (!getJsxNamespaceContainerForImplicitImport(node)) { - // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. - // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. - const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; - const jsxFactoryNamespace = getJsxNamespace(node); - const jsxFactoryLocation = isNodeOpeningLikeElement ? node.tagName : node; + function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { + const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); - // allow null as jsxFragmentFactory - let jsxFactorySym: Symbol | undefined; - if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { - jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, jsxFactoryNamespace, /*isUse*/ true); - } + if (isNodeOpeningLikeElement) { + checkGrammarJsxElement(node); + } - if (jsxFactorySym) { - // Mark local symbol as referenced here because it might not have been marked - // if jsx emit was not jsxFactory as there wont be error being emitted - jsxFactorySym.isReferenced = SymbolFlags.All; + checkJsxPreconditions(node); - // If react/jsxFactory symbol is alias, mark it as refereced - if (jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { - markAliasSymbolAsReferenced(jsxFactorySym); - } - } + if (!getJsxNamespaceContainerForImplicitImport(node)) { + // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. + // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. + const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; + const jsxFactoryNamespace = getJsxNamespace(node); + const jsxFactoryLocation = isNodeOpeningLikeElement ? node.tagName : node; - // For JsxFragment, mark jsx pragma as referenced via resolveName - if (isJsxOpeningFragment(node)) { - const file = getSourceFileOfNode(node); - const localJsxNamespace = getLocalJsxNamespace(file); - if (localJsxNamespace) { - resolveName(jsxFactoryLocation, localJsxNamespace, SymbolFlags.Value, jsxFactoryRefErr, localJsxNamespace, /*isUse*/ true); - } - } + // allow null as jsxFragmentFactory + let jsxFactorySym: Symbol | undefined; + if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { + jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, jsxFactoryNamespace, /*isUse*/ true); } - if (isNodeOpeningLikeElement) { - const jsxOpeningLikeNode = node ; - const sig = getResolvedSignature(jsxOpeningLikeNode); - checkDeprecatedSignature(sig, node); - checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode); - } - } + if (jsxFactorySym) { + // Mark local symbol as referenced here because it might not have been marked + // if jsx emit was not jsxFactory as there wont be error being emitted + jsxFactorySym.isReferenced = SymbolFlags.All; - /** - * Check if a property with the given name is known anywhere in the given type. In an object type, a property - * is considered known if - * 1. the object type is empty and the check is for assignability, or - * 2. if the object type has index signatures, or - * 3. if the property is actually declared in the object type - * (this means that 'toString', for example, is not usually a known property). - * 4. In a union or intersection type, - * a property is considered known if it is known in any constituent type. - * @param targetType a type to search a given name in - * @param name a property name to search - * @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType - */ - function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean { - if (targetType.flags & TypeFlags.Object) { - // For backwards compatibility a symbol-named property is satisfied by a string index signature. This - // is incorrect and inconsistent with element access expressions, where it is an error, so eventually - // we should remove this exception. - if (getPropertyOfObjectType(targetType, name) || - getApplicableIndexInfoForName(targetType, name) || - isLateBoundName(name) && getIndexInfoOfType(targetType, stringType) || - isComparingJsxAttributes && isHyphenatedJsxName(name)) { - // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known. - return true; + // If react/jsxFactory symbol is alias, mark it as refereced + if (jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { + markAliasSymbolAsReferenced(jsxFactorySym); } } - else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) { - for (const t of (targetType as UnionOrIntersectionType).types) { - if (isKnownProperty(t, name, isComparingJsxAttributes)) { - return true; - } + + // For JsxFragment, mark jsx pragma as referenced via resolveName + if (isJsxOpeningFragment(node)) { + const file = getSourceFileOfNode(node); + const localJsxNamespace = getLocalJsxNamespace(file); + if (localJsxNamespace) { + resolveName(jsxFactoryLocation, localJsxNamespace, SymbolFlags.Value, jsxFactoryRefErr, localJsxNamespace, /*isUse*/ true); } } - return false; } - function isExcessPropertyCheckTarget(type: Type): boolean { - return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) || - type.flags & TypeFlags.NonPrimitive || - type.flags & TypeFlags.Union && some((type as UnionType).types, isExcessPropertyCheckTarget) || - type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isExcessPropertyCheckTarget)); + if (isNodeOpeningLikeElement) { + const jsxOpeningLikeNode = node ; + const sig = getResolvedSignature(jsxOpeningLikeNode); + checkDeprecatedSignature(sig, node); + checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode); } + } - function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) { - checkGrammarJsxExpression(node); - if (node.expression) { - const type = checkExpression(node.expression, checkMode); - if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) { - error(node, Diagnostics.JSX_spread_child_must_be_an_array_type); - } - return type; + /** + * Check if a property with the given name is known anywhere in the given type. In an object type, a property + * is considered known if + * 1. the object type is empty and the check is for assignability, or + * 2. if the object type has index signatures, or + * 3. if the property is actually declared in the object type + * (this means that 'toString', for example, is not usually a known property). + * 4. In a union or intersection type, + * a property is considered known if it is known in any constituent type. + * @param targetType a type to search a given name in + * @param name a property name to search + * @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType + */ + function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean { + if (targetType.flags & TypeFlags.Object) { + // For backwards compatibility a symbol-named property is satisfied by a string index signature. This + // is incorrect and inconsistent with element access expressions, where it is an error, so eventually + // we should remove this exception. + if (getPropertyOfObjectType(targetType, name) || + getApplicableIndexInfoForName(targetType, name) || + isLateBoundName(name) && getIndexInfoOfType(targetType, stringType) || + isComparingJsxAttributes && isHyphenatedJsxName(name)) { + // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known. + return true; } - else { - return errorType; + } + else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) { + for (const t of (targetType as UnionOrIntersectionType).types) { + if (isKnownProperty(t, name, isComparingJsxAttributes)) { + return true; + } } } + return false; + } - function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { - return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0; - } + function isExcessPropertyCheckTarget(type: Type): boolean { + return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) || + type.flags & TypeFlags.NonPrimitive || + type.flags & TypeFlags.Union && some((type as UnionType).types, isExcessPropertyCheckTarget) || + type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isExcessPropertyCheckTarget)); + } - /** - * Return whether this symbol is a member of a prototype somewhere - * Note that this is not tracked well within the compiler, so the answer may be incorrect. - */ - function isPrototypeProperty(symbol: Symbol) { - if (symbol.flags & SymbolFlags.Method || getCheckFlags(symbol) & CheckFlags.SyntheticMethod) { - return true; - } - if (isInJSFile(symbol.valueDeclaration)) { - const parent = symbol.valueDeclaration!.parent; - return parent && isBinaryExpression(parent) && - getAssignmentDeclarationKind(parent) === AssignmentDeclarationKind.PrototypeProperty; + function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) { + checkGrammarJsxExpression(node); + if (node.expression) { + const type = checkExpression(node.expression, checkMode); + if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) { + error(node, Diagnostics.JSX_spread_child_must_be_an_array_type); } + return type; } + else { + return errorType; + } + } - /** - * Check whether the requested property access is valid. - * Returns true if node is a valid property access, and false otherwise. - * @param node The node to be checked. - * @param isSuper True if the access is from `super.`. - * @param type The type of the object whose property is being accessed. (Not the type of the property.) - * @param prop The symbol for the property being accessed. - */ - function checkPropertyAccessibility( - node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement, - isSuper: boolean, writing: boolean, type: Type, prop: Symbol, reportError = true): boolean { - - const errorNode = !reportError ? undefined : - node.kind === SyntaxKind.QualifiedName ? node.right : - node.kind === SyntaxKind.ImportType ? node : - node.kind === SyntaxKind.BindingElement && node.propertyName ? node.propertyName : node.name; + function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { + return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0; + } - return checkPropertyAccessibilityAtLocation(node, isSuper, writing, type, prop, errorNode); + /** + * Return whether this symbol is a member of a prototype somewhere + * Note that this is not tracked well within the compiler, so the answer may be incorrect. + */ + function isPrototypeProperty(symbol: Symbol) { + if (symbol.flags & SymbolFlags.Method || getCheckFlags(symbol) & CheckFlags.SyntheticMethod) { + return true; } + if (isInJSFile(symbol.valueDeclaration)) { + const parent = symbol.valueDeclaration!.parent; + return parent && isBinaryExpression(parent) && + getAssignmentDeclarationKind(parent) === AssignmentDeclarationKind.PrototypeProperty; + } + } - /** - * Check whether the requested property can be accessed at the requested location. - * Returns true if node is a valid property access, and false otherwise. - * @param location The location node where we want to check if the property is accessible. - * @param isSuper True if the access is from `super.`. - * @param writing True if this is a write property access, false if it is a read property access. - * @param containingType The type of the object whose property is being accessed. (Not the type of the property.) - * @param prop The symbol for the property being accessed. - * @param errorNode The node where we should report an invalid property access error, or undefined if we should not report errors. - */ - function checkPropertyAccessibilityAtLocation(location: Node, - isSuper: boolean, writing: boolean, - containingType: Type, prop: Symbol, errorNode?: Node): boolean { - - const flags = getDeclarationModifierFlagsFromSymbol(prop, writing); + /** + * Check whether the requested property access is valid. + * Returns true if node is a valid property access, and false otherwise. + * @param node The node to be checked. + * @param isSuper True if the access is from `super.`. + * @param type The type of the object whose property is being accessed. (Not the type of the property.) + * @param prop The symbol for the property being accessed. + */ + function checkPropertyAccessibility( + node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement, + isSuper: boolean, writing: boolean, type: Type, prop: Symbol, reportError = true): boolean { + + const errorNode = !reportError ? undefined : + node.kind === SyntaxKind.QualifiedName ? node.right : + node.kind === SyntaxKind.ImportType ? node : + node.kind === SyntaxKind.BindingElement && node.propertyName ? node.propertyName : node.name; + + return checkPropertyAccessibilityAtLocation(node, isSuper, writing, type, prop, errorNode); + } - if (isSuper) { - // TS 1.0 spec (April 2014): 4.8.2 - // - In a constructor, instance member function, instance member accessor, or - // instance member variable initializer where this references a derived class instance, - // a super property access is permitted and must specify a public instance member function of the base class. - // - In a static member function or static member accessor - // where this references the constructor function object of a derived class, - // a super property access is permitted and must specify a public static member function of the base class. - if (languageVersion < ScriptTarget.ES2015) { - if (symbolHasNonMethodDeclaration(prop)) { - if (errorNode) { - error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword); - } - return false; - } - } - if (flags & ModifierFlags.Abstract) { - // A method cannot be accessed in a super property access if the method is abstract. - // This error could mask a private property access error. But, a member - // cannot simultaneously be private and abstract, so this will trigger an - // additional error elsewhere. + /** + * Check whether the requested property can be accessed at the requested location. + * Returns true if node is a valid property access, and false otherwise. + * @param location The location node where we want to check if the property is accessible. + * @param isSuper True if the access is from `super.`. + * @param writing True if this is a write property access, false if it is a read property access. + * @param containingType The type of the object whose property is being accessed. (Not the type of the property.) + * @param prop The symbol for the property being accessed. + * @param errorNode The node where we should report an invalid property access error, or undefined if we should not report errors. + */ + function checkPropertyAccessibilityAtLocation(location: Node, + isSuper: boolean, writing: boolean, + containingType: Type, prop: Symbol, errorNode?: Node): boolean { + + const flags = getDeclarationModifierFlagsFromSymbol(prop, writing); + + if (isSuper) { + // TS 1.0 spec (April 2014): 4.8.2 + // - In a constructor, instance member function, instance member accessor, or + // instance member variable initializer where this references a derived class instance, + // a super property access is permitted and must specify a public instance member function of the base class. + // - In a static member function or static member accessor + // where this references the constructor function object of a derived class, + // a super property access is permitted and must specify a public static member function of the base class. + if (languageVersion < ScriptTarget.ES2015) { + if (symbolHasNonMethodDeclaration(prop)) { if (errorNode) { - error(errorNode, - Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, - symbolToString(prop), - typeToString(getDeclaringClass(prop)!)); + error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword); } return false; } } - - // Referencing abstract properties within their own constructors is not allowed - if ((flags & ModifierFlags.Abstract) && symbolHasNonMethodDeclaration(prop) && - (isThisProperty(location) || isThisInitializedObjectBindingExpression(location) || isObjectBindingPattern(location.parent) && isThisInitializedDeclaration(location.parent.parent))) { - const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!); - if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(location)) { - if (errorNode) { - error(errorNode, - Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, - symbolToString(prop), - getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!)); - } - return false; + if (flags & ModifierFlags.Abstract) { + // A method cannot be accessed in a super property access if the method is abstract. + // This error could mask a private property access error. But, a member + // cannot simultaneously be private and abstract, so this will trigger an + // additional error elsewhere. + if (errorNode) { + error(errorNode, + Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, + symbolToString(prop), + typeToString(getDeclaringClass(prop)!)); } + return false; } + } - // Public properties are otherwise accessible. - if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { - return true; + // Referencing abstract properties within their own constructors is not allowed + if ((flags & ModifierFlags.Abstract) && symbolHasNonMethodDeclaration(prop) && + (isThisProperty(location) || isThisInitializedObjectBindingExpression(location) || isObjectBindingPattern(location.parent) && isThisInitializedDeclaration(location.parent.parent))) { + const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!); + if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(location)) { + if (errorNode) { + error(errorNode, + Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, + symbolToString(prop), + getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!)); + } + return false; } + } - // Property is known to be private or protected at this point + // Public properties are otherwise accessible. + if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { + return true; + } - // Private property is accessible if the property is within the declaring class - if (flags & ModifierFlags.Private) { - const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!)!; - if (!isNodeWithinClass(location, declaringClassDeclaration)) { - if (errorNode) { - error(errorNode, - Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, - symbolToString(prop), - typeToString(getDeclaringClass(prop)!)); - } - return false; + // Property is known to be private or protected at this point + + // Private property is accessible if the property is within the declaring class + if (flags & ModifierFlags.Private) { + const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!)!; + if (!isNodeWithinClass(location, declaringClassDeclaration)) { + if (errorNode) { + error(errorNode, + Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, + symbolToString(prop), + typeToString(getDeclaringClass(prop)!)); } - return true; + return false; } + return true; + } - // Property is known to be protected at this point + // Property is known to be protected at this point - // All protected properties of a supertype are accessible in a super access - if (isSuper) { - return true; - } + // All protected properties of a supertype are accessible in a super access + if (isSuper) { + return true; + } - // Find the first enclosing class that has the declaring classes of the protected constituents - // of the property as base classes - let enclosingClass = forEachEnclosingClass(location, enclosingDeclaration => { - const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!) as InterfaceType; - return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing); - }); - // A protected property is accessible if the property is within the declaring class or classes derived from it - if (!enclosingClass) { - // allow PropertyAccessibility if context is in function with this parameter - // static member access is disallowed - enclosingClass = getEnclosingClassFromThisParameter(location); - enclosingClass = enclosingClass && isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing); - if (flags & ModifierFlags.Static || !enclosingClass) { - if (errorNode) { - error(errorNode, - Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, - symbolToString(prop), - typeToString(getDeclaringClass(prop) || containingType)); - } - return false; - } - } - // No further restrictions for static properties - if (flags & ModifierFlags.Static) { - return true; - } - if (containingType.flags & TypeFlags.TypeParameter) { - // get the original type -- represented as the type constraint of the 'this' type - containingType = (containingType as TypeParameter).isThisType ? getConstraintOfTypeParameter(containingType as TypeParameter)! : getBaseConstraintOfType(containingType as TypeParameter)!; // TODO: GH#18217 Use a different variable that's allowed to be undefined - } - if (!containingType || !hasBaseType(containingType, enclosingClass)) { + // Find the first enclosing class that has the declaring classes of the protected constituents + // of the property as base classes + let enclosingClass = forEachEnclosingClass(location, enclosingDeclaration => { + const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!) as InterfaceType; + return isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing); + }); + // A protected property is accessible if the property is within the declaring class or classes derived from it + if (!enclosingClass) { + // allow PropertyAccessibility if context is in function with this parameter + // static member access is disallowed + enclosingClass = getEnclosingClassFromThisParameter(location); + enclosingClass = enclosingClass && isClassDerivedFromDeclaringClasses(enclosingClass, prop, writing); + if (flags & ModifierFlags.Static || !enclosingClass) { if (errorNode) { error(errorNode, - Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1_This_is_an_instance_of_class_2, - symbolToString(prop), typeToString(enclosingClass), typeToString(containingType)); + Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, + symbolToString(prop), + typeToString(getDeclaringClass(prop) || containingType)); } return false; } + } + // No further restrictions for static properties + if (flags & ModifierFlags.Static) { return true; } - - function getEnclosingClassFromThisParameter(node: Node): InterfaceType | undefined { - const thisParameter = getThisParameterFromNodeContext(node); - let thisType = thisParameter?.type && getTypeFromTypeNode(thisParameter.type); - if (thisType && thisType.flags & TypeFlags.TypeParameter) { - thisType = getConstraintOfTypeParameter(thisType as TypeParameter); - } - if (thisType && getObjectFlags(thisType) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) { - return getTargetType(thisType) as InterfaceType; + if (containingType.flags & TypeFlags.TypeParameter) { + // get the original type -- represented as the type constraint of the 'this' type + containingType = (containingType as TypeParameter).isThisType ? getConstraintOfTypeParameter(containingType as TypeParameter)! : getBaseConstraintOfType(containingType as TypeParameter)!; // TODO: GH#18217 Use a different variable that's allowed to be undefined + } + if (!containingType || !hasBaseType(containingType, enclosingClass)) { + if (errorNode) { + error(errorNode, + Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1_This_is_an_instance_of_class_2, + symbolToString(prop), typeToString(enclosingClass), typeToString(containingType)); } - return undefined; + return false; } + return true; + } - function getThisParameterFromNodeContext(node: Node) { - const thisContainer = getThisContainer(node, /* includeArrowFunctions */ false); - return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined; + function getEnclosingClassFromThisParameter(node: Node): InterfaceType | undefined { + const thisParameter = getThisParameterFromNodeContext(node); + let thisType = thisParameter?.type && getTypeFromTypeNode(thisParameter.type); + if (thisType && thisType.flags & TypeFlags.TypeParameter) { + thisType = getConstraintOfTypeParameter(thisType as TypeParameter); } - - function symbolHasNonMethodDeclaration(symbol: Symbol) { - return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method)); + if (thisType && getObjectFlags(thisType) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) { + return getTargetType(thisType) as InterfaceType; } + return undefined; + } - function checkNonNullExpression(node: Expression | QualifiedName) { - return checkNonNullType(checkExpression(node), node); - } + function getThisParameterFromNodeContext(node: Node) { + const thisContainer = getThisContainer(node, /* includeArrowFunctions */ false); + return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined; + } - function isNullableType(type: Type) { - return !!(getTypeFacts(type) & TypeFacts.IsUndefinedOrNull); - } + function symbolHasNonMethodDeclaration(symbol: Symbol) { + return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method)); + } - function getNonNullableTypeIfNeeded(type: Type) { - return isNullableType(type) ? getNonNullableType(type) : type; - } + function checkNonNullExpression(node: Expression | QualifiedName) { + return checkNonNullType(checkExpression(node), node); + } - function reportObjectPossiblyNullOrUndefinedError(node: Node, facts: TypeFacts) { - const nodeText = isEntityNameExpression(node) ? entityNameToString(node) : undefined; - if (node.kind === SyntaxKind.NullKeyword) { - error(node, Diagnostics.The_value_0_cannot_be_used_here, "null"); + function isNullableType(type: Type) { + return !!(getTypeFacts(type) & TypeFacts.IsUndefinedOrNull); + } + + function getNonNullableTypeIfNeeded(type: Type) { + return isNullableType(type) ? getNonNullableType(type) : type; + } + + function reportObjectPossiblyNullOrUndefinedError(node: Node, facts: TypeFacts) { + const nodeText = isEntityNameExpression(node) ? entityNameToString(node) : undefined; + if (node.kind === SyntaxKind.NullKeyword) { + error(node, Diagnostics.The_value_0_cannot_be_used_here, "null"); + return; + } + if (nodeText !== undefined && nodeText.length < 100) { + if (isIdentifier(node) && nodeText === "undefined") { + error(node, Diagnostics.The_value_0_cannot_be_used_here, "undefined"); return; } - if (nodeText !== undefined && nodeText.length < 100) { - if (isIdentifier(node) && nodeText === "undefined") { - error(node, Diagnostics.The_value_0_cannot_be_used_here, "undefined"); - return; - } - error(node, facts & TypeFacts.IsUndefined ? facts & TypeFacts.IsNull ? - Diagnostics._0_is_possibly_null_or_undefined : - Diagnostics._0_is_possibly_undefined : - Diagnostics._0_is_possibly_null, - nodeText - ); - } - else { - error(node, facts & TypeFacts.IsUndefined ? facts & TypeFacts.IsNull ? - Diagnostics.Object_is_possibly_null_or_undefined : - Diagnostics.Object_is_possibly_undefined : - Diagnostics.Object_is_possibly_null - ); - } + error(node, facts & TypeFacts.IsUndefined ? facts & TypeFacts.IsNull ? + Diagnostics._0_is_possibly_null_or_undefined : + Diagnostics._0_is_possibly_undefined : + Diagnostics._0_is_possibly_null, + nodeText + ); } - - function reportCannotInvokePossiblyNullOrUndefinedError(node: Node, facts: TypeFacts) { + else { error(node, facts & TypeFacts.IsUndefined ? facts & TypeFacts.IsNull ? - Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined : - Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined : - Diagnostics.Cannot_invoke_an_object_which_is_possibly_null + Diagnostics.Object_is_possibly_null_or_undefined : + Diagnostics.Object_is_possibly_undefined : + Diagnostics.Object_is_possibly_null ); } + } - function checkNonNullTypeWithReporter( - type: Type, - node: Node, - reportError: (node: Node, facts: TypeFacts) => void - ): Type { - if (strictNullChecks && type.flags & TypeFlags.Unknown) { - if (isEntityNameExpression(node)) { - const nodeText = entityNameToString(node); - if (nodeText.length < 100) { - error(node, Diagnostics._0_is_of_type_unknown, nodeText); - return errorType; - } + function reportCannotInvokePossiblyNullOrUndefinedError(node: Node, facts: TypeFacts) { + error(node, facts & TypeFacts.IsUndefined ? facts & TypeFacts.IsNull ? + Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined : + Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined : + Diagnostics.Cannot_invoke_an_object_which_is_possibly_null + ); + } + + function checkNonNullTypeWithReporter( + type: Type, + node: Node, + reportError: (node: Node, facts: TypeFacts) => void + ): Type { + if (strictNullChecks && type.flags & TypeFlags.Unknown) { + if (isEntityNameExpression(node)) { + const nodeText = entityNameToString(node); + if (nodeText.length < 100) { + error(node, Diagnostics._0_is_of_type_unknown, nodeText); + return errorType; } - error(node, Diagnostics.Object_is_of_type_unknown); - return errorType; } - const facts = getTypeFacts(type); - if (facts & TypeFacts.IsUndefinedOrNull) { - reportError(node, facts); - const t = getNonNullableType(type); - return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t; - } - return type; + error(node, Diagnostics.Object_is_of_type_unknown); + return errorType; } - - function checkNonNullType(type: Type, node: Node) { - return checkNonNullTypeWithReporter(type, node, reportObjectPossiblyNullOrUndefinedError); + const facts = getTypeFacts(type); + if (facts & TypeFacts.IsUndefinedOrNull) { + reportError(node, facts); + const t = getNonNullableType(type); + return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t; } + return type; + } - function checkNonNullNonVoidType(type: Type, node: Node): Type { - const nonNullType = checkNonNullType(type, node); - if (nonNullType.flags & TypeFlags.Void) { - if (isEntityNameExpression(node)) { - const nodeText = entityNameToString(node); - if (isIdentifier(node) && nodeText === "undefined") { - error(node, Diagnostics.The_value_0_cannot_be_used_here, nodeText); - return nonNullType; - } - if (nodeText.length < 100) { - error(node, Diagnostics._0_is_possibly_undefined, nodeText); - return nonNullType; - } + function checkNonNullType(type: Type, node: Node) { + return checkNonNullTypeWithReporter(type, node, reportObjectPossiblyNullOrUndefinedError); + } + + function checkNonNullNonVoidType(type: Type, node: Node): Type { + const nonNullType = checkNonNullType(type, node); + if (nonNullType.flags & TypeFlags.Void) { + if (isEntityNameExpression(node)) { + const nodeText = entityNameToString(node); + if (isIdentifier(node) && nodeText === "undefined") { + error(node, Diagnostics.The_value_0_cannot_be_used_here, nodeText); + return nonNullType; + } + if (nodeText.length < 100) { + error(node, Diagnostics._0_is_possibly_undefined, nodeText); + return nonNullType; } - error(node, Diagnostics.Object_is_possibly_undefined); } - return nonNullType; + error(node, Diagnostics.Object_is_possibly_undefined); } + return nonNullType; + } - function checkPropertyAccessExpression(node: PropertyAccessExpression, checkMode: CheckMode | undefined) { - return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain, checkMode) : - checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name, checkMode); - } + function checkPropertyAccessExpression(node: PropertyAccessExpression, checkMode: CheckMode | undefined) { + return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain, checkMode) : + checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name, checkMode); + } - function checkPropertyAccessChain(node: PropertyAccessChain, checkMode: CheckMode | undefined) { - const leftType = checkExpression(node.expression); - const nonOptionalType = getOptionalExpressionType(leftType, node.expression); - return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name, checkMode), node, nonOptionalType !== leftType); - } + function checkPropertyAccessChain(node: PropertyAccessChain, checkMode: CheckMode | undefined) { + const leftType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(leftType, node.expression); + return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name, checkMode), node, nonOptionalType !== leftType); + } - function checkQualifiedName(node: QualifiedName, checkMode: CheckMode | undefined) { - const leftType = isPartOfTypeQuery(node) && isThisIdentifier(node.left) ? checkNonNullType(checkThisExpression(node.left), node.left) : checkNonNullExpression(node.left); - return checkPropertyAccessExpressionOrQualifiedName(node, node.left, leftType, node.right, checkMode); + function checkQualifiedName(node: QualifiedName, checkMode: CheckMode | undefined) { + const leftType = isPartOfTypeQuery(node) && isThisIdentifier(node.left) ? checkNonNullType(checkThisExpression(node.left), node.left) : checkNonNullExpression(node.left); + return checkPropertyAccessExpressionOrQualifiedName(node, node.left, leftType, node.right, checkMode); + } + + function isMethodAccessForCall(node: Node) { + while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { + node = node.parent; } + return isCallOrNewExpression(node.parent) && node.parent.expression === node; + } - function isMethodAccessForCall(node: Node) { - while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { - node = node.parent; + // Lookup the private identifier lexically. + function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined { + for (let containingClass = getContainingClass(location); !!containingClass; containingClass = getContainingClass(containingClass)) { + const { symbol } = containingClass; + const name = getSymbolNameForPrivateIdentifier(symbol, propName); + const prop = (symbol.members && symbol.members.get(name)) || (symbol.exports && symbol.exports.get(name)); + if (prop) { + return prop; } - return isCallOrNewExpression(node.parent) && node.parent.expression === node; } + } - // Lookup the private identifier lexically. - function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined { - for (let containingClass = getContainingClass(location); !!containingClass; containingClass = getContainingClass(containingClass)) { - const { symbol } = containingClass; - const name = getSymbolNameForPrivateIdentifier(symbol, propName); - const prop = (symbol.members && symbol.members.get(name)) || (symbol.exports && symbol.exports.get(name)); - if (prop) { - return prop; - } - } + function checkGrammarPrivateIdentifierExpression(privId: PrivateIdentifier): boolean { + if (!getContainingClass(privId)) { + return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); } - function checkGrammarPrivateIdentifierExpression(privId: PrivateIdentifier): boolean { - if (!getContainingClass(privId)) { - return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + if (!isForInStatement(privId.parent)) { + if (!isExpressionNode(privId)) { + return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_only_allowed_in_class_bodies_and_may_only_be_used_as_part_of_a_class_member_declaration_property_access_or_on_the_left_hand_side_of_an_in_expression); } - if (!isForInStatement(privId.parent)) { - if (!isExpressionNode(privId)) { - return grammarErrorOnNode(privId, Diagnostics.Private_identifiers_are_only_allowed_in_class_bodies_and_may_only_be_used_as_part_of_a_class_member_declaration_property_access_or_on_the_left_hand_side_of_an_in_expression); - } - - const isInOperation = isBinaryExpression(privId.parent) && privId.parent.operatorToken.kind === SyntaxKind.InKeyword; - if (!getSymbolForPrivateIdentifierExpression(privId) && !isInOperation) { - return grammarErrorOnNode(privId, Diagnostics.Cannot_find_name_0, idText(privId)); - } + const isInOperation = isBinaryExpression(privId.parent) && privId.parent.operatorToken.kind === SyntaxKind.InKeyword; + if (!getSymbolForPrivateIdentifierExpression(privId) && !isInOperation) { + return grammarErrorOnNode(privId, Diagnostics.Cannot_find_name_0, idText(privId)); } - - return false; } - function checkPrivateIdentifierExpression(privId: PrivateIdentifier): Type { - checkGrammarPrivateIdentifierExpression(privId); - const symbol = getSymbolForPrivateIdentifierExpression(privId); - if (symbol) { - markPropertyAsReferenced(symbol, /* nodeForCheckWriteOnly: */ undefined, /* isThisAccess: */ false); - } - return anyType; - } + return false; + } - function getSymbolForPrivateIdentifierExpression(privId: PrivateIdentifier): Symbol | undefined { - if (!isExpressionNode(privId)) { - return undefined; - } + function checkPrivateIdentifierExpression(privId: PrivateIdentifier): Type { + checkGrammarPrivateIdentifierExpression(privId); + const symbol = getSymbolForPrivateIdentifierExpression(privId); + if (symbol) { + markPropertyAsReferenced(symbol, /* nodeForCheckWriteOnly: */ undefined, /* isThisAccess: */ false); + } + return anyType; + } - const links = getNodeLinks(privId); - if (links.resolvedSymbol === undefined) { - links.resolvedSymbol = lookupSymbolForPrivateIdentifierDeclaration(privId.escapedText, privId); - } - return links.resolvedSymbol; + function getSymbolForPrivateIdentifierExpression(privId: PrivateIdentifier): Symbol | undefined { + if (!isExpressionNode(privId)) { + return undefined; } - function getPrivateIdentifierPropertyOfType(leftType: Type, lexicallyScopedIdentifier: Symbol): Symbol | undefined { - return getPropertyOfType(leftType, lexicallyScopedIdentifier.escapedName); + const links = getNodeLinks(privId); + if (links.resolvedSymbol === undefined) { + links.resolvedSymbol = lookupSymbolForPrivateIdentifierDeclaration(privId.escapedText, privId); } + return links.resolvedSymbol; + } - function checkPrivateIdentifierPropertyAccess(leftType: Type, right: PrivateIdentifier, lexicallyScopedIdentifier: Symbol | undefined): boolean { - // Either the identifier could not be looked up in the lexical scope OR the lexically scoped identifier did not exist on the type. - // Find a private identifier with the same description on the type. - let propertyOnType: Symbol | undefined; - const properties = getPropertiesOfType(leftType); - if (properties) { - forEach(properties, (symbol: Symbol) => { - const decl = symbol.valueDeclaration; - if (decl && isNamedDeclaration(decl) && isPrivateIdentifier(decl.name) && decl.name.escapedText === right.escapedText) { - propertyOnType = symbol; - return true; - } - }); - } - const diagName = diagnosticName(right); - if (propertyOnType) { - const typeValueDecl = Debug.checkDefined(propertyOnType.valueDeclaration); - const typeClass = Debug.checkDefined(getContainingClass(typeValueDecl)); - // We found a private identifier property with the same description. - // Either: - // - There is a lexically scoped private identifier AND it shadows the one we found on the type. - // - It is an attempt to access the private identifier outside of the class. - if (lexicallyScopedIdentifier?.valueDeclaration) { - const lexicalValueDecl = lexicallyScopedIdentifier.valueDeclaration; - const lexicalClass = getContainingClass(lexicalValueDecl); - Debug.assert(!!lexicalClass); - if (findAncestor(lexicalClass, n => typeClass === n)) { - const diagnostic = error( - right, - Diagnostics.The_property_0_cannot_be_accessed_on_type_1_within_this_class_because_it_is_shadowed_by_another_private_identifier_with_the_same_spelling, - diagName, - typeToString(leftType) - ); + function getPrivateIdentifierPropertyOfType(leftType: Type, lexicallyScopedIdentifier: Symbol): Symbol | undefined { + return getPropertyOfType(leftType, lexicallyScopedIdentifier.escapedName); + } - addRelatedInfo( - diagnostic, - createDiagnosticForNode( - lexicalValueDecl, - Diagnostics.The_shadowing_declaration_of_0_is_defined_here, - diagName - ), - createDiagnosticForNode( - typeValueDecl, - Diagnostics.The_declaration_of_0_that_you_probably_intended_to_use_is_defined_here, - diagName - ) - ); - return true; - } + function checkPrivateIdentifierPropertyAccess(leftType: Type, right: PrivateIdentifier, lexicallyScopedIdentifier: Symbol | undefined): boolean { + // Either the identifier could not be looked up in the lexical scope OR the lexically scoped identifier did not exist on the type. + // Find a private identifier with the same description on the type. + let propertyOnType: Symbol | undefined; + const properties = getPropertiesOfType(leftType); + if (properties) { + forEach(properties, (symbol: Symbol) => { + const decl = symbol.valueDeclaration; + if (decl && isNamedDeclaration(decl) && isPrivateIdentifier(decl.name) && decl.name.escapedText === right.escapedText) { + propertyOnType = symbol; + return true; } - error( - right, - Diagnostics.Property_0_is_not_accessible_outside_class_1_because_it_has_a_private_identifier, - diagName, - diagnosticName(typeClass.name || anon) - ); - return true; - } - return false; - } - - function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) { - return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop)) - && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop); + }); } + const diagName = diagnosticName(right); + if (propertyOnType) { + const typeValueDecl = Debug.checkDefined(propertyOnType.valueDeclaration); + const typeClass = Debug.checkDefined(getContainingClass(typeValueDecl)); + // We found a private identifier property with the same description. + // Either: + // - There is a lexically scoped private identifier AND it shadows the one we found on the type. + // - It is an attempt to access the private identifier outside of the class. + if (lexicallyScopedIdentifier?.valueDeclaration) { + const lexicalValueDecl = lexicallyScopedIdentifier.valueDeclaration; + const lexicalClass = getContainingClass(lexicalValueDecl); + Debug.assert(!!lexicalClass); + if (findAncestor(lexicalClass, n => typeClass === n)) { + const diagnostic = error( + right, + Diagnostics.The_property_0_cannot_be_accessed_on_type_1_within_this_class_because_it_is_shadowed_by_another_private_identifier_with_the_same_spelling, + diagName, + typeToString(leftType) + ); - function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, checkMode: CheckMode | undefined) { - const parentSymbol = getNodeLinks(left).resolvedSymbol; - const assignmentKind = getAssignmentTargetKind(node); - const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); - const isAnyLike = isTypeAny(apparentType) || apparentType === silentNeverType; - let prop: Symbol | undefined; - if (isPrivateIdentifier(right)) { - if (languageVersion < ScriptTarget.ESNext) { - if (assignmentKind !== AssignmentKind.None) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldSet); - } - if (assignmentKind !== AssignmentKind.Definite) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldGet); - } + addRelatedInfo( + diagnostic, + createDiagnosticForNode( + lexicalValueDecl, + Diagnostics.The_shadowing_declaration_of_0_is_defined_here, + diagName + ), + createDiagnosticForNode( + typeValueDecl, + Diagnostics.The_declaration_of_0_that_you_probably_intended_to_use_is_defined_here, + diagName + ) + ); + return true; } + } + error( + right, + Diagnostics.Property_0_is_not_accessible_outside_class_1_because_it_has_a_private_identifier, + diagName, + diagnosticName(typeClass.name || anon) + ); + return true; + } + return false; + } - const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); - if (assignmentKind && lexicallyScopedSymbol && lexicallyScopedSymbol.valueDeclaration && isMethodDeclaration(lexicallyScopedSymbol.valueDeclaration)) { - grammarErrorOnNode(right, Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable, idText(right)); - } + function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) { + return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop)) + && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop); + } - if (isAnyLike) { - if (lexicallyScopedSymbol) { - return isErrorType(apparentType) ? errorType : apparentType; - } - if (!getContainingClass(right)) { - grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); - return anyType; - } - } - prop = lexicallyScopedSymbol ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol) : undefined; - // Check for private-identifier-specific shadowing and lexical-scoping errors. - if (!prop && checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) { - return errorType; + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, checkMode: CheckMode | undefined) { + const parentSymbol = getNodeLinks(left).resolvedSymbol; + const assignmentKind = getAssignmentTargetKind(node); + const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); + const isAnyLike = isTypeAny(apparentType) || apparentType === silentNeverType; + let prop: Symbol | undefined; + if (isPrivateIdentifier(right)) { + if (languageVersion < ScriptTarget.ESNext) { + if (assignmentKind !== AssignmentKind.None) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldSet); } - else { - const isSetonlyAccessor = prop && prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); - if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) { - error(node, Diagnostics.Private_accessor_was_defined_without_a_getter); - } + if (assignmentKind !== AssignmentKind.Definite) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldGet); } } - else { - if (isAnyLike) { - if (isIdentifier(left) && parentSymbol) { - markAliasReferenced(parentSymbol, node); - } - return isErrorType(apparentType) ? errorType : apparentType; - } - prop = getPropertyOfType(apparentType, right.escapedText, /*skipObjectFunctionPropertyAugment*/ false, /*includeTypeOnlyMembers*/ node.kind === SyntaxKind.QualifiedName); + + const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); + if (assignmentKind && lexicallyScopedSymbol && lexicallyScopedSymbol.valueDeclaration && isMethodDeclaration(lexicallyScopedSymbol.valueDeclaration)) { + grammarErrorOnNode(right, Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable, idText(right)); } - // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. - // `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined - // here even if `Foo` is not a const enum. - // - // The exceptions are: - // 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and - // 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`. - if (isIdentifier(left) && parentSymbol && ( - compilerOptions.isolatedModules || - !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && node.parent.kind === SyntaxKind.EnumMember)) || - shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(node) - )) { - markAliasReferenced(parentSymbol, node); - } - - let propType: Type; - if (!prop) { - const indexInfo = !isPrivateIdentifier(right) && (assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType)) ? - getApplicableIndexInfoForName(apparentType, right.escapedText) : undefined; - if (!(indexInfo && indexInfo.type)) { - const isUncheckedJS = isUncheckedJSSuggestion(node, leftType.symbol, /*excludeClasses*/ true); - if (!isUncheckedJS && isJSLiteralType(leftType)) { - return anyType; - } - if (leftType.symbol === globalThisSymbol) { - if (globalThisSymbol.exports!.has(right.escapedText) && (globalThisSymbol.exports!.get(right.escapedText)!.flags & SymbolFlags.BlockScoped)) { - error(right, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(right.escapedText), typeToString(leftType)); - } - else if (noImplicitAny) { - error(right, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(leftType)); - } - return anyType; - } - if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { - reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType, isUncheckedJS); - } - return errorType; - } - if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) { - error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); - } - propType = (compilerOptions.noUncheckedIndexedAccess && !isAssignmentTarget(node)) ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; - if (compilerOptions.noPropertyAccessFromIndexSignature && isPropertyAccessExpression(node)) { - error(right, Diagnostics.Property_0_comes_from_an_index_signature_so_it_must_be_accessed_with_0, unescapeLeadingUnderscores(right.escapedText)); + if (isAnyLike) { + if (lexicallyScopedSymbol) { + return isErrorType(apparentType) ? errorType : apparentType; } - if (indexInfo.declaration && getCombinedNodeFlags(indexInfo.declaration) & NodeFlags.Deprecated) { - addDeprecatedSuggestion(right, [indexInfo.declaration], right.escapedText as string); + if (!getContainingClass(right)) { + grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + return anyType; } } + prop = lexicallyScopedSymbol ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol) : undefined; + // Check for private-identifier-specific shadowing and lexical-scoping errors. + if (!prop && checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) { + return errorType; + } else { - if (isDeprecatedSymbol(prop) && isUncalledFunctionReference(node, prop) && prop.declarations) { - addDeprecatedSuggestion(right, prop.declarations, right.escapedText as string); - } - checkPropertyNotUsedBeforeDeclaration(prop, node, right); - markPropertyAsReferenced(prop, node, isSelfTypeAccess(left, parentSymbol)); - getNodeLinks(node).resolvedSymbol = prop; - const writing = isWriteAccess(node); - checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, writing, apparentType, prop); - if (isAssignmentToReadonlyEntity(node as Expression, prop, assignmentKind)) { - error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right)); - return errorType; + const isSetonlyAccessor = prop && prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) { + error(node, Diagnostics.Private_accessor_was_defined_without_a_getter); } - - propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writing ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop); } - - return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode); } - - /** - * Determines whether a did-you-mean error should be a suggestion in an unchecked JS file. - * Only applies to unchecked JS files without checkJS, // @ts-check or // @ts-nocheck - * It does not suggest when the suggestion: - * - Is from a global file that is different from the reference file, or - * - (optionally) Is a class, or is a this.x property access expression - */ - function isUncheckedJSSuggestion(node: Node | undefined, suggestion: Symbol | undefined, excludeClasses: boolean): boolean { - const file = getSourceFileOfNode(node); - if (file) { - if (compilerOptions.checkJs === undefined && file.checkJsDirective === undefined && (file.scriptKind === ScriptKind.JS || file.scriptKind === ScriptKind.JSX)) { - const declarationFile = forEach(suggestion?.declarations, getSourceFileOfNode); - return !(file !== declarationFile && !!declarationFile && isGlobalSourceFile(declarationFile)) - && !(excludeClasses && suggestion && suggestion.flags & SymbolFlags.Class) - && !(!!node && excludeClasses && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword); + else { + if (isAnyLike) { + if (isIdentifier(left) && parentSymbol) { + markAliasReferenced(parentSymbol, node); } + return isErrorType(apparentType) ? errorType : apparentType; } - return false; + prop = getPropertyOfType(apparentType, right.escapedText, /*skipObjectFunctionPropertyAugment*/ false, /*includeTypeOnlyMembers*/ node.kind === SyntaxKind.QualifiedName); } - - function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node, checkMode: CheckMode | undefined) { - // Only compute control flow type if this is a property access expression that isn't an - // assignment target, and the referenced property was declared as a variable, property, - // accessor, or optional method. - const assignmentKind = getAssignmentTargetKind(node); - if (assignmentKind === AssignmentKind.Definite) { - return removeMissingType(propType, !!(prop && prop.flags & SymbolFlags.Optional)); - } - if (prop && - !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) - && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union) - && !isDuplicatedCommonJSExport(prop.declarations)) { - return propType; - } - if (propType === autoType) { - return getFlowTypeOfProperty(node, prop); - } - propType = getNarrowableTypeForReference(propType, node, checkMode); - // If strict null checks and strict property initialization checks are enabled, if we have - // a this.xxx property access, if the property is an instance property without an initializer, - // and if we are in a constructor of the same class as the property declaration, assume that - // the property is uninitialized at the top of the control flow. - let assumeUninitialized = false; - if (strictNullChecks && strictPropertyInitialization && isAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword) { - const declaration = prop && prop.valueDeclaration; - if (declaration && isPropertyWithoutInitializer(declaration)) { - if (!isStatic(declaration)) { - const flowContainer = getControlFlowContainer(node); - if (flowContainer.kind === SyntaxKind.Constructor && flowContainer.parent === declaration.parent && !(declaration.flags & NodeFlags.Ambient)) { - assumeUninitialized = true; - } + // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. + // `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined + // here even if `Foo` is not a const enum. + // + // The exceptions are: + // 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and + // 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`. + if (isIdentifier(left) && parentSymbol && ( + compilerOptions.isolatedModules || + !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && node.parent.kind === SyntaxKind.EnumMember)) || + shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(node) + )) { + markAliasReferenced(parentSymbol, node); + } + + let propType: Type; + if (!prop) { + const indexInfo = !isPrivateIdentifier(right) && (assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType)) ? + getApplicableIndexInfoForName(apparentType, right.escapedText) : undefined; + if (!(indexInfo && indexInfo.type)) { + const isUncheckedJS = isUncheckedJSSuggestion(node, leftType.symbol, /*excludeClasses*/ true); + if (!isUncheckedJS && isJSLiteralType(leftType)) { + return anyType; + } + if (leftType.symbol === globalThisSymbol) { + if (globalThisSymbol.exports!.has(right.escapedText) && (globalThisSymbol.exports!.get(right.escapedText)!.flags & SymbolFlags.BlockScoped)) { + error(right, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(right.escapedText), typeToString(leftType)); + } + else if (noImplicitAny) { + error(right, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(leftType)); } + return anyType; + } + if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { + reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType, isUncheckedJS); } + return errorType; + } + if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) { + error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); } - else if (strictNullChecks && prop && prop.valueDeclaration && - isPropertyAccessExpression(prop.valueDeclaration) && - getAssignmentDeclarationPropertyAccessKind(prop.valueDeclaration) && - getControlFlowContainer(node) === getControlFlowContainer(prop.valueDeclaration)) { - assumeUninitialized = true; + + propType = (compilerOptions.noUncheckedIndexedAccess && !isAssignmentTarget(node)) ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; + if (compilerOptions.noPropertyAccessFromIndexSignature && isPropertyAccessExpression(node)) { + error(right, Diagnostics.Property_0_comes_from_an_index_signature_so_it_must_be_accessed_with_0, unescapeLeadingUnderscores(right.escapedText)); } - const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); - if (assumeUninitialized && !containsUndefinedType(propType) && containsUndefinedType(flowType)) { - error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 - // Return the declared type to reduce follow-on errors - return propType; + if (indexInfo.declaration && getCombinedNodeFlags(indexInfo.declaration) & NodeFlags.Deprecated) { + addDeprecatedSuggestion(right, [indexInfo.declaration], right.escapedText as string); } - return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } - - function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier | PrivateIdentifier): void { - const { valueDeclaration } = prop; - if (!valueDeclaration || getSourceFileOfNode(node).isDeclarationFile) { - return; + else { + if (isDeprecatedSymbol(prop) && isUncalledFunctionReference(node, prop) && prop.declarations) { + addDeprecatedSuggestion(right, prop.declarations, right.escapedText as string); } - - let diagnosticMessage; - const declarationName = idText(right); - if (isInPropertyInitializerOrClassStaticBlock(node) - && !isOptionalPropertyDeclaration(valueDeclaration) - && !(isAccessExpression(node) && isAccessExpression(node.expression)) - && !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right) - && !(isMethodDeclaration(valueDeclaration) && getCombinedModifierFlags(valueDeclaration) & ModifierFlags.Static) - && (compilerOptions.useDefineForClassFields || !isPropertyDeclaredInAncestorClass(prop))) { - diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName); - } - else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration && - node.parent.kind !== SyntaxKind.TypeReference && - !(valueDeclaration.flags & NodeFlags.Ambient) && - !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)) { - diagnosticMessage = error(right, Diagnostics.Class_0_used_before_its_declaration, declarationName); + checkPropertyNotUsedBeforeDeclaration(prop, node, right); + markPropertyAsReferenced(prop, node, isSelfTypeAccess(left, parentSymbol)); + getNodeLinks(node).resolvedSymbol = prop; + const writing = isWriteAccess(node); + checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, writing, apparentType, prop); + if (isAssignmentToReadonlyEntity(node as Expression, prop, assignmentKind)) { + error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right)); + return errorType; } - if (diagnosticMessage) { - addRelatedInfo(diagnosticMessage, - createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName) - ); - } + propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writing ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop); } - function isInPropertyInitializerOrClassStaticBlock(node: Node): boolean { - return !!findAncestor(node, node => { - switch (node.kind) { - case SyntaxKind.PropertyDeclaration: - return true; - case SyntaxKind.PropertyAssignment: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.SpreadAssignment: - case SyntaxKind.ComputedPropertyName: - case SyntaxKind.TemplateSpan: - case SyntaxKind.JsxExpression: - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxAttributes: - case SyntaxKind.JsxSpreadAttribute: - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.HeritageClause: - return false; - case SyntaxKind.ArrowFunction: - case SyntaxKind.ExpressionStatement: - return isBlock(node.parent) && isClassStaticBlockDeclaration(node.parent.parent) ? true : "quit"; - default: - return isExpressionNode(node) ? false : "quit"; - } - }); + return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode); + } + + /** + * Determines whether a did-you-mean error should be a suggestion in an unchecked JS file. + * Only applies to unchecked JS files without checkJS, // @ts-check or // @ts-nocheck + * It does not suggest when the suggestion: + * - Is from a global file that is different from the reference file, or + * - (optionally) Is a class, or is a this.x property access expression + */ + function isUncheckedJSSuggestion(node: Node | undefined, suggestion: Symbol | undefined, excludeClasses: boolean): boolean { + const file = getSourceFileOfNode(node); + if (file) { + if (compilerOptions.checkJs === undefined && file.checkJsDirective === undefined && (file.scriptKind === ScriptKind.JS || file.scriptKind === ScriptKind.JSX)) { + const declarationFile = forEach(suggestion?.declarations, getSourceFileOfNode); + return !(file !== declarationFile && !!declarationFile && isGlobalSourceFile(declarationFile)) + && !(excludeClasses && suggestion && suggestion.flags & SymbolFlags.Class) + && !(!!node && excludeClasses && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword); + } + } + return false; + } + + function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node, checkMode: CheckMode | undefined) { + // Only compute control flow type if this is a property access expression that isn't an + // assignment target, and the referenced property was declared as a variable, property, + // accessor, or optional method. + const assignmentKind = getAssignmentTargetKind(node); + if (assignmentKind === AssignmentKind.Definite) { + return removeMissingType(propType, !!(prop && prop.flags & SymbolFlags.Optional)); + } + if (prop && + !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) + && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union) + && !isDuplicatedCommonJSExport(prop.declarations)) { + return propType; + } + if (propType === autoType) { + return getFlowTypeOfProperty(node, prop); + } + propType = getNarrowableTypeForReference(propType, node, checkMode); + // If strict null checks and strict property initialization checks are enabled, if we have + // a this.xxx property access, if the property is an instance property without an initializer, + // and if we are in a constructor of the same class as the property declaration, assume that + // the property is uninitialized at the top of the control flow. + let assumeUninitialized = false; + if (strictNullChecks && strictPropertyInitialization && isAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword) { + const declaration = prop && prop.valueDeclaration; + if (declaration && isPropertyWithoutInitializer(declaration)) { + if (!isStatic(declaration)) { + const flowContainer = getControlFlowContainer(node); + if (flowContainer.kind === SyntaxKind.Constructor && flowContainer.parent === declaration.parent && !(declaration.flags & NodeFlags.Ambient)) { + assumeUninitialized = true; + } + } + } + } + else if (strictNullChecks && prop && prop.valueDeclaration && + isPropertyAccessExpression(prop.valueDeclaration) && + getAssignmentDeclarationPropertyAccessKind(prop.valueDeclaration) && + getControlFlowContainer(node) === getControlFlowContainer(prop.valueDeclaration)) { + assumeUninitialized = true; + } + const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); + if (assumeUninitialized && !containsUndefinedType(propType) && containsUndefinedType(flowType)) { + error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 + // Return the declared type to reduce follow-on errors + return propType; + } + return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + } + + function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier | PrivateIdentifier): void { + const { valueDeclaration } = prop; + if (!valueDeclaration || getSourceFileOfNode(node).isDeclarationFile) { + return; } - /** - * It's possible that "prop.valueDeclaration" is a local declaration, but the property was also declared in a superclass. - * In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration. - */ - function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean { - if (!(prop.parent!.flags & SymbolFlags.Class)) { - return false; - } - let classType: InterfaceType | undefined = getTypeOfSymbol(prop.parent!) as InterfaceType; - while (true) { - classType = classType.symbol && getSuperClass(classType) as InterfaceType | undefined; - if (!classType) { - return false; - } - const superProperty = getPropertyOfType(classType, prop.escapedName); - if (superProperty && superProperty.valueDeclaration) { - return true; - } - } + let diagnosticMessage; + const declarationName = idText(right); + if (isInPropertyInitializerOrClassStaticBlock(node) + && !isOptionalPropertyDeclaration(valueDeclaration) + && !(isAccessExpression(node) && isAccessExpression(node.expression)) + && !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right) + && !(isMethodDeclaration(valueDeclaration) && getCombinedModifierFlags(valueDeclaration) & ModifierFlags.Static) + && (compilerOptions.useDefineForClassFields || !isPropertyDeclaredInAncestorClass(prop))) { + diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName); + } + else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration && + node.parent.kind !== SyntaxKind.TypeReference && + !(valueDeclaration.flags & NodeFlags.Ambient) && + !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)) { + diagnosticMessage = error(right, Diagnostics.Class_0_used_before_its_declaration, declarationName); } - function getSuperClass(classType: InterfaceType): Type | undefined { - const x = getBaseTypes(classType); - if (x.length === 0) { - return undefined; - } - return getIntersectionType(x); + if (diagnosticMessage) { + addRelatedInfo(diagnosticMessage, + createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName) + ); } + } - function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type, isUncheckedJS: boolean) { - let errorInfo: DiagnosticMessageChain | undefined; - let relatedInfo: Diagnostic | undefined; - if (!isPrivateIdentifier(propNode) && containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) { - for (const subtype of (containingType as UnionType).types) { - if (!getPropertyOfType(subtype, propNode.escapedText) && !getApplicableIndexInfoForName(subtype, propNode.escapedText)) { - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype)); - break; - } - } - } - if (typeHasStaticProperty(propNode.escapedText, containingType)) { - const propName = declarationNameToString(propNode); - const typeName = typeToString(containingType); - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName, typeName, typeName + "." + propName); + function isInPropertyInitializerOrClassStaticBlock(node: Node): boolean { + return !!findAncestor(node, node => { + switch (node.kind) { + case SyntaxKind.PropertyDeclaration: + return true; + case SyntaxKind.PropertyAssignment: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.SpreadAssignment: + case SyntaxKind.ComputedPropertyName: + case SyntaxKind.TemplateSpan: + case SyntaxKind.JsxExpression: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxAttributes: + case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.HeritageClause: + return false; + case SyntaxKind.ArrowFunction: + case SyntaxKind.ExpressionStatement: + return isBlock(node.parent) && isClassStaticBlockDeclaration(node.parent.parent) ? true : "quit"; + default: + return isExpressionNode(node) ? false : "quit"; } - else { - const promisedType = getPromisedTypeOfPromise(containingType); - if (promisedType && getPropertyOfType(promisedType, propNode.escapedText)) { - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); - relatedInfo = createDiagnosticForNode(propNode, Diagnostics.Did_you_forget_to_use_await); - } - else { - const missingProperty = declarationNameToString(propNode); - const container = typeToString(containingType); - const libSuggestion = getSuggestedLibForNonExistentProperty(missingProperty, containingType); - if (libSuggestion !== undefined) { - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_2_or_later, missingProperty, container, libSuggestion); - } - else { - const suggestion = getSuggestedSymbolForNonexistentProperty(propNode, containingType); - if (suggestion !== undefined) { - const suggestedName = symbolName(suggestion); - const message = isUncheckedJS ? Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2 : Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2; - errorInfo = chainDiagnosticMessages(errorInfo, message, missingProperty, container, suggestedName); - relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName); - } - else { - const diagnostic = containerSeemsToBeEmptyDomElement(containingType) - ? Diagnostics.Property_0_does_not_exist_on_type_1_Try_changing_the_lib_compiler_option_to_include_dom - : Diagnostics.Property_0_does_not_exist_on_type_1; - errorInfo = chainDiagnosticMessages(elaborateNeverIntersection(errorInfo, containingType), diagnostic, missingProperty, container); - } - } - } + }); + } + + /** + * It's possible that "prop.valueDeclaration" is a local declaration, but the property was also declared in a superclass. + * In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration. + */ + function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean { + if (!(prop.parent!.flags & SymbolFlags.Class)) { + return false; + } + let classType: InterfaceType | undefined = getTypeOfSymbol(prop.parent!) as InterfaceType; + while (true) { + classType = classType.symbol && getSuperClass(classType) as InterfaceType | undefined; + if (!classType) { + return false; } - const resultDiagnostic = createDiagnosticForNodeFromMessageChain(propNode, errorInfo); - if (relatedInfo) { - addRelatedInfo(resultDiagnostic, relatedInfo); + const superProperty = getPropertyOfType(classType, prop.escapedName); + if (superProperty && superProperty.valueDeclaration) { + return true; } - addErrorOrSuggestion(!isUncheckedJS || errorInfo.code !== Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2.code, resultDiagnostic); - } - - function containerSeemsToBeEmptyDomElement(containingType: Type) { - return (compilerOptions.lib && !compilerOptions.lib.includes("dom")) && - everyContainedType(containingType, type => type.symbol && /^(EventTarget|Node|((HTML[a-zA-Z]*)?Element))$/.test(unescapeLeadingUnderscores(type.symbol.escapedName))) && - isEmptyObjectType(containingType); } + } - function typeHasStaticProperty(propName: __String, containingType: Type): boolean { - const prop = containingType.symbol && getPropertyOfType(getTypeOfSymbol(containingType.symbol), propName); - return prop !== undefined && !!prop.valueDeclaration && isStatic(prop.valueDeclaration); + function getSuperClass(classType: InterfaceType): Type | undefined { + const x = getBaseTypes(classType); + if (x.length === 0) { + return undefined; } + return getIntersectionType(x); + } - function getSuggestedLibForNonExistentName(name: __String | Identifier) { - const missingName = diagnosticName(name); - const allFeatures = getScriptTargetFeatures(); - const libTargets = getOwnKeys(allFeatures); - for (const libTarget of libTargets) { - const containingTypes = getOwnKeys(allFeatures[libTarget]); - if (containingTypes !== undefined && contains(containingTypes, missingName)) { - return libTarget; + function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type, isUncheckedJS: boolean) { + let errorInfo: DiagnosticMessageChain | undefined; + let relatedInfo: Diagnostic | undefined; + if (!isPrivateIdentifier(propNode) && containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) { + for (const subtype of (containingType as UnionType).types) { + if (!getPropertyOfType(subtype, propNode.escapedText) && !getApplicableIndexInfoForName(subtype, propNode.escapedText)) { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype)); + break; } } } - - function getSuggestedLibForNonExistentProperty(missingProperty: string, containingType: Type) { - const container = getApparentType(containingType).symbol; - if (!container) { - return undefined; + if (typeHasStaticProperty(propNode.escapedText, containingType)) { + const propName = declarationNameToString(propNode); + const typeName = typeToString(containingType); + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName, typeName, typeName + "." + propName); + } + else { + const promisedType = getPromisedTypeOfPromise(containingType); + if (promisedType && getPropertyOfType(promisedType, propNode.escapedText)) { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); + relatedInfo = createDiagnosticForNode(propNode, Diagnostics.Did_you_forget_to_use_await); } - const allFeatures = getScriptTargetFeatures(); - const libTargets = getOwnKeys(allFeatures); - for (const libTarget of libTargets) { - const featuresOfLib = allFeatures[libTarget]; - const featuresOfContainingType = featuresOfLib[symbolName(container)]; - if (featuresOfContainingType !== undefined && contains(featuresOfContainingType, missingProperty)) { - return libTarget; + else { + const missingProperty = declarationNameToString(propNode); + const container = typeToString(containingType); + const libSuggestion = getSuggestedLibForNonExistentProperty(missingProperty, containingType); + if (libSuggestion !== undefined) { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_2_or_later, missingProperty, container, libSuggestion); + } + else { + const suggestion = getSuggestedSymbolForNonexistentProperty(propNode, containingType); + if (suggestion !== undefined) { + const suggestedName = symbolName(suggestion); + const message = isUncheckedJS ? Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2 : Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2; + errorInfo = chainDiagnosticMessages(errorInfo, message, missingProperty, container, suggestedName); + relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName); + } + else { + const diagnostic = containerSeemsToBeEmptyDomElement(containingType) + ? Diagnostics.Property_0_does_not_exist_on_type_1_Try_changing_the_lib_compiler_option_to_include_dom + : Diagnostics.Property_0_does_not_exist_on_type_1; + errorInfo = chainDiagnosticMessages(elaborateNeverIntersection(errorInfo, containingType), diagnostic, missingProperty, container); + } } } } - - function getSuggestedSymbolForNonexistentClassMember(name: string, baseType: Type): Symbol | undefined { - return getSpellingSuggestionForName(name, getPropertiesOfType(baseType), SymbolFlags.ClassMember); + const resultDiagnostic = createDiagnosticForNodeFromMessageChain(propNode, errorInfo); + if (relatedInfo) { + addRelatedInfo(resultDiagnostic, relatedInfo); } + addErrorOrSuggestion(!isUncheckedJS || errorInfo.code !== Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2.code, resultDiagnostic); + } - function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined { - let props = getPropertiesOfType(containingType); - if (typeof name !== "string") { - const parent = name.parent; - if (isPropertyAccessExpression(parent)) { - props = filter(props, prop => isValidPropertyAccessForCompletions(parent, containingType, prop)); - } - name = idText(name); + function containerSeemsToBeEmptyDomElement(containingType: Type) { + return (compilerOptions.lib && !compilerOptions.lib.includes("dom")) && + everyContainedType(containingType, type => type.symbol && /^(EventTarget|Node|((HTML[a-zA-Z]*)?Element))$/.test(unescapeLeadingUnderscores(type.symbol.escapedName))) && + isEmptyObjectType(containingType); + } + + function typeHasStaticProperty(propName: __String, containingType: Type): boolean { + const prop = containingType.symbol && getPropertyOfType(getTypeOfSymbol(containingType.symbol), propName); + return prop !== undefined && !!prop.valueDeclaration && isStatic(prop.valueDeclaration); + } + + function getSuggestedLibForNonExistentName(name: __String | Identifier) { + const missingName = diagnosticName(name); + const allFeatures = getScriptTargetFeatures(); + const libTargets = getOwnKeys(allFeatures); + for (const libTarget of libTargets) { + const containingTypes = getOwnKeys(allFeatures[libTarget]); + if (containingTypes !== undefined && contains(containingTypes, missingName)) { + return libTarget; } - return getSpellingSuggestionForName(name, props, SymbolFlags.Value); } + } - function getSuggestedSymbolForNonexistentJSXAttribute(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined { - const strName = isString(name) ? name : idText(name); - const properties = getPropertiesOfType(containingType); - const jsxSpecific = strName === "for" ? find(properties, x => symbolName(x) === "htmlFor") - : strName === "class" ? find(properties, x => symbolName(x) === "className") - : undefined; - return jsxSpecific ?? getSpellingSuggestionForName(strName, properties, SymbolFlags.Value); - } - - function getSuggestionForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): string | undefined { - const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType); - return suggestion && symbolName(suggestion); - } - - function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { - Debug.assert(outerName !== undefined, "outername should always be defined"); - const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, /*getSpellingSuggestions*/ true, (symbols, name, meaning) => { - Debug.assertEqual(outerName, name, "name should equal outerName"); - const symbol = getSymbol(symbols, name, meaning); - // Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function - // So the table *contains* `x` but `x` isn't actually in scope. - // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion. - if (symbol) return symbol; - let candidates: Symbol[]; - if (symbols === globals) { - const primitives = mapDefined( - ["string", "number", "boolean", "object", "bigint", "symbol"], - s => symbols.has((s.charAt(0).toUpperCase() + s.slice(1)) as __String) - ? createSymbol(SymbolFlags.TypeAlias, s as __String) as Symbol - : undefined); - candidates = primitives.concat(arrayFrom(symbols.values())); - } - else { - candidates = arrayFrom(symbols.values()); - } - return getSpellingSuggestionForName(unescapeLeadingUnderscores(name), candidates, meaning); - }); - return result; + function getSuggestedLibForNonExistentProperty(missingProperty: string, containingType: Type) { + const container = getApparentType(containingType).symbol; + if (!container) { + return undefined; } - - function getSuggestionForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): string | undefined { - const symbolResult = getSuggestedSymbolForNonexistentSymbol(location, outerName, meaning); - return symbolResult && symbolName(symbolResult); + const allFeatures = getScriptTargetFeatures(); + const libTargets = getOwnKeys(allFeatures); + for (const libTarget of libTargets) { + const featuresOfLib = allFeatures[libTarget]; + const featuresOfContainingType = featuresOfLib[symbolName(container)]; + if (featuresOfContainingType !== undefined && contains(featuresOfContainingType, missingProperty)) { + return libTarget; + } } + } - function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { - return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); - } + function getSuggestedSymbolForNonexistentClassMember(name: string, baseType: Type): Symbol | undefined { + return getSpellingSuggestionForName(name, getPropertiesOfType(baseType), SymbolFlags.ClassMember); + } - function getSuggestionForNonexistentExport(name: Identifier, targetModule: Symbol): string | undefined { - const suggestion = getSuggestedSymbolForNonexistentModule(name, targetModule); - return suggestion && symbolName(suggestion); + function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined { + let props = getPropertiesOfType(containingType); + if (typeof name !== "string") { + const parent = name.parent; + if (isPropertyAccessExpression(parent)) { + props = filter(props, prop => isValidPropertyAccessForCompletions(parent, containingType, prop)); + } + name = idText(name); } + return getSpellingSuggestionForName(name, props, SymbolFlags.Value); + } - function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression, keyedType: Type): string | undefined { - // check if object type has setter or getter - function hasProp(name: "set" | "get") { - const prop = getPropertyOfObjectType(objectType, name as __String); - if (prop) { - const s = getSingleCallSignature(getTypeOfSymbol(prop)); - return !!s && getMinArgumentCount(s) >= 1 && isTypeAssignableTo(keyedType, getTypeAtPosition(s, 0)); - } - return false; - } + function getSuggestedSymbolForNonexistentJSXAttribute(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined { + const strName = isString(name) ? name : idText(name); + const properties = getPropertiesOfType(containingType); + const jsxSpecific = strName === "for" ? find(properties, x => symbolName(x) === "htmlFor") + : strName === "class" ? find(properties, x => symbolName(x) === "className") + : undefined; + return jsxSpecific ?? getSpellingSuggestionForName(strName, properties, SymbolFlags.Value); + } - const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get"; - if (!hasProp(suggestedMethod)) { - return undefined; - } + function getSuggestionForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): string | undefined { + const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType); + return suggestion && symbolName(suggestion); + } - let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression); - if (suggestion === undefined) { - suggestion = suggestedMethod; + function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { + Debug.assert(outerName !== undefined, "outername should always be defined"); + const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, /*getSpellingSuggestions*/ true, (symbols, name, meaning) => { + Debug.assertEqual(outerName, name, "name should equal outerName"); + const symbol = getSymbol(symbols, name, meaning); + // Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function + // So the table *contains* `x` but `x` isn't actually in scope. + // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion. + if (symbol) return symbol; + let candidates: Symbol[]; + if (symbols === globals) { + const primitives = mapDefined( + ["string", "number", "boolean", "object", "bigint", "symbol"], + s => symbols.has((s.charAt(0).toUpperCase() + s.slice(1)) as __String) + ? createSymbol(SymbolFlags.TypeAlias, s as __String) as Symbol + : undefined); + candidates = primitives.concat(arrayFrom(symbols.values())); } else { - suggestion += "." + suggestedMethod; + candidates = arrayFrom(symbols.values()); } + return getSpellingSuggestionForName(unescapeLeadingUnderscores(name), candidates, meaning); + }); + return result; + } - return suggestion; - } + function getSuggestionForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): string | undefined { + const symbolResult = getSuggestedSymbolForNonexistentSymbol(location, outerName, meaning); + return symbolResult && symbolName(symbolResult); + } + + function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { + return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); + } + + function getSuggestionForNonexistentExport(name: Identifier, targetModule: Symbol): string | undefined { + const suggestion = getSuggestedSymbolForNonexistentModule(name, targetModule); + return suggestion && symbolName(suggestion); + } - function getSuggestedTypeForNonexistentStringLiteralType(source: StringLiteralType, target: UnionType): StringLiteralType | undefined { - const candidates = target.types.filter((type): type is StringLiteralType => !!(type.flags & TypeFlags.StringLiteral)); - return getSpellingSuggestion(source.value, candidates, type => type.value); + function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression, keyedType: Type): string | undefined { + // check if object type has setter or getter + function hasProp(name: "set" | "get") { + const prop = getPropertyOfObjectType(objectType, name as __String); + if (prop) { + const s = getSingleCallSignature(getTypeOfSymbol(prop)); + return !!s && getMinArgumentCount(s) >= 1 && isTypeAssignableTo(keyedType, getTypeAtPosition(s, 0)); + } + return false; } - /** - * Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough. - * Names less than length 3 only check for case-insensitive equality, not levenshtein distance. - * - * If there is a candidate that's the same except for case, return that. - * If there is a candidate that's within one edit of the name, return that. - * Otherwise, return the candidate with the smallest Levenshtein distance, - * except for candidates: - * * With no name - * * Whose meaning doesn't match the `meaning` parameter. - * * Whose length differs from the target name by more than 0.34 of the length of the name. - * * Whose levenshtein distance is more than 0.4 of the length of the name - * (0.4 allows 1 substitution/transposition for every 5 characters, - * and 1 insertion/deletion at 3 characters) - */ - function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined { - return getSpellingSuggestion(name, symbols, getCandidateName); + const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get"; + if (!hasProp(suggestedMethod)) { + return undefined; + } - function getCandidateName(candidate: Symbol) { - const candidateName = symbolName(candidate); - if (startsWith(candidateName, "\"")) { - return undefined; - } + let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression); + if (suggestion === undefined) { + suggestion = suggestedMethod; + } + else { + suggestion += "." + suggestedMethod; + } - if (candidate.flags & meaning) { - return candidateName; - } + return suggestion; + } - if (candidate.flags & SymbolFlags.Alias) { - const alias = tryResolveAlias(candidate); - if (alias && alias.flags & meaning) { - return candidateName; - } - } + function getSuggestedTypeForNonexistentStringLiteralType(source: StringLiteralType, target: UnionType): StringLiteralType | undefined { + const candidates = target.types.filter((type): type is StringLiteralType => !!(type.flags & TypeFlags.StringLiteral)); + return getSpellingSuggestion(source.value, candidates, type => type.value); + } + /** + * Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough. + * Names less than length 3 only check for case-insensitive equality, not levenshtein distance. + * + * If there is a candidate that's the same except for case, return that. + * If there is a candidate that's within one edit of the name, return that. + * Otherwise, return the candidate with the smallest Levenshtein distance, + * except for candidates: + * * With no name + * * Whose meaning doesn't match the `meaning` parameter. + * * Whose length differs from the target name by more than 0.34 of the length of the name. + * * Whose levenshtein distance is more than 0.4 of the length of the name + * (0.4 allows 1 substitution/transposition for every 5 characters, + * and 1 insertion/deletion at 3 characters) + */ + function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined { + return getSpellingSuggestion(name, symbols, getCandidateName); + + function getCandidateName(candidate: Symbol) { + const candidateName = symbolName(candidate); + if (startsWith(candidateName, "\"")) { return undefined; } - } - function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isSelfTypeAccess: boolean) { - const valueDeclaration = prop && (prop.flags & SymbolFlags.ClassMember) && prop.valueDeclaration; - if (!valueDeclaration) { - return; - } - const hasPrivateModifier = hasEffectiveModifier(valueDeclaration, ModifierFlags.Private); - const hasPrivateIdentifier = prop.valueDeclaration && isNamedDeclaration(prop.valueDeclaration) && isPrivateIdentifier(prop.valueDeclaration.name); - if (!hasPrivateModifier && !hasPrivateIdentifier) { - return; - } - if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor)) { - return; + if (candidate.flags & meaning) { + return candidateName; } - if (isSelfTypeAccess) { - // Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters). - const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration); - if (containingMethod && containingMethod.symbol === prop) { - return; + + if (candidate.flags & SymbolFlags.Alias) { + const alias = tryResolveAlias(candidate); + if (alias && alias.flags & meaning) { + return candidateName; } } - (getCheckFlags(prop) & CheckFlags.Instantiated ? getSymbolLinks(prop).target : prop)!.isReferenced = SymbolFlags.All; + return undefined; } + } - function isSelfTypeAccess(name: Expression | QualifiedName, parent: Symbol | undefined) { - return name.kind === SyntaxKind.ThisKeyword - || !!parent && isEntityNameExpression(name) && parent === getResolvedSymbol(getFirstIdentifier(name)); + function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isSelfTypeAccess: boolean) { + const valueDeclaration = prop && (prop.flags & SymbolFlags.ClassMember) && prop.valueDeclaration; + if (!valueDeclaration) { + return; } - - function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean { - switch (node.kind) { - case SyntaxKind.PropertyAccessExpression: - return isValidPropertyAccessWithType(node, node.expression.kind === SyntaxKind.SuperKeyword, propertyName, getWidenedType(checkExpression(node.expression))); - case SyntaxKind.QualifiedName: - return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getWidenedType(checkExpression(node.left))); - case SyntaxKind.ImportType: - return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getTypeFromTypeNode(node)); + const hasPrivateModifier = hasEffectiveModifier(valueDeclaration, ModifierFlags.Private); + const hasPrivateIdentifier = prop.valueDeclaration && isNamedDeclaration(prop.valueDeclaration) && isPrivateIdentifier(prop.valueDeclaration.name); + if (!hasPrivateModifier && !hasPrivateIdentifier) { + return; + } + if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor)) { + return; + } + if (isSelfTypeAccess) { + // Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters). + const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration); + if (containingMethod && containingMethod.symbol === prop) { + return; } } - /** - * Checks if an existing property access is valid for completions purposes. - * @param node a property access-like node where we want to check if we can access a property. - * This node does not need to be an access of the property we are checking. - * e.g. in completions, this node will often be an incomplete property access node, as in `foo.`. - * Besides providing a location (i.e. scope) used to check property accessibility, we use this node for - * computing whether this is a `super` property access. - * @param type the type whose property we are checking. - * @param property the accessed property's symbol. - */ - function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean { - return isPropertyAccessible(node, - node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, - /* isWrite */ false, - type, - property); - // Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context. + (getCheckFlags(prop) & CheckFlags.Instantiated ? getSymbolLinks(prop).target : prop)!.isReferenced = SymbolFlags.All; + } + + function isSelfTypeAccess(name: Expression | QualifiedName, parent: Symbol | undefined) { + return name.kind === SyntaxKind.ThisKeyword + || !!parent && isEntityNameExpression(name) && parent === getResolvedSymbol(getFirstIdentifier(name)); + } + + function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean { + switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + return isValidPropertyAccessWithType(node, node.expression.kind === SyntaxKind.SuperKeyword, propertyName, getWidenedType(checkExpression(node.expression))); + case SyntaxKind.QualifiedName: + return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getWidenedType(checkExpression(node.left))); + case SyntaxKind.ImportType: + return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getTypeFromTypeNode(node)); } + } - function isValidPropertyAccessWithType( - node: PropertyAccessExpression | QualifiedName | ImportTypeNode, - isSuper: boolean, - propertyName: __String, - type: Type): boolean { + /** + * Checks if an existing property access is valid for completions purposes. + * @param node a property access-like node where we want to check if we can access a property. + * This node does not need to be an access of the property we are checking. + * e.g. in completions, this node will often be an incomplete property access node, as in `foo.`. + * Besides providing a location (i.e. scope) used to check property accessibility, we use this node for + * computing whether this is a `super` property access. + * @param type the type whose property we are checking. + * @param property the accessed property's symbol. + */ + function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean { + return isPropertyAccessible(node, + node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, + /* isWrite */ false, + type, + property); + // Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context. + } - // Short-circuiting for improved performance. - if (isTypeAny(type)) { - return true; - } + function isValidPropertyAccessWithType( + node: PropertyAccessExpression | QualifiedName | ImportTypeNode, + isSuper: boolean, + propertyName: __String, + type: Type): boolean { - const prop = getPropertyOfType(type, propertyName); - return !!prop && isPropertyAccessible(node, isSuper, /* isWrite */ false, type, prop); + // Short-circuiting for improved performance. + if (isTypeAny(type)) { + return true; } - /** - * Checks if a property can be accessed in a location. - * The location is given by the `node` parameter. - * The node does not need to be a property access. - * @param node location where to check property accessibility - * @param isSuper whether to consider this a `super` property access, e.g. `super.foo`. - * @param isWrite whether this is a write access, e.g. `++foo.x`. - * @param containingType type where the property comes from. - * @param property property symbol. - */ - function isPropertyAccessible( - node: Node, - isSuper: boolean, - isWrite: boolean, - containingType: Type, - property: Symbol): boolean { - - // Short-circuiting for improved performance. - if (isTypeAny(containingType)) { - return true; - } + const prop = getPropertyOfType(type, propertyName); + return !!prop && isPropertyAccessible(node, isSuper, /* isWrite */ false, type, prop); + } - // A #private property access in an optional chain is an error dealt with by the parser. - // The checker does not check for it, so we need to do our own check here. - if (property.valueDeclaration && isPrivateIdentifierClassElementDeclaration(property.valueDeclaration)) { - const declClass = getContainingClass(property.valueDeclaration); - return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass); - } + /** + * Checks if a property can be accessed in a location. + * The location is given by the `node` parameter. + * The node does not need to be a property access. + * @param node location where to check property accessibility + * @param isSuper whether to consider this a `super` property access, e.g. `super.foo`. + * @param isWrite whether this is a write access, e.g. `++foo.x`. + * @param containingType type where the property comes from. + * @param property property symbol. + */ + function isPropertyAccessible( + node: Node, + isSuper: boolean, + isWrite: boolean, + containingType: Type, + property: Symbol): boolean { + + // Short-circuiting for improved performance. + if (isTypeAny(containingType)) { + return true; + } - return checkPropertyAccessibilityAtLocation(node, isSuper, isWrite, containingType, property); + // A #private property access in an optional chain is an error dealt with by the parser. + // The checker does not check for it, so we need to do our own check here. + if (property.valueDeclaration && isPrivateIdentifierClassElementDeclaration(property.valueDeclaration)) { + const declClass = getContainingClass(property.valueDeclaration); + return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass); } - /** - * Return the symbol of the for-in variable declared or referenced by the given for-in statement. - */ - function getForInVariableSymbol(node: ForInStatement): Symbol | undefined { - const initializer = node.initializer; - if (initializer.kind === SyntaxKind.VariableDeclarationList) { - const variable = (initializer as VariableDeclarationList).declarations[0]; - if (variable && !isBindingPattern(variable.name)) { - return getSymbolOfNode(variable); - } - } - else if (initializer.kind === SyntaxKind.Identifier) { - return getResolvedSymbol(initializer as Identifier); + return checkPropertyAccessibilityAtLocation(node, isSuper, isWrite, containingType, property); + } + + /** + * Return the symbol of the for-in variable declared or referenced by the given for-in statement. + */ + function getForInVariableSymbol(node: ForInStatement): Symbol | undefined { + const initializer = node.initializer; + if (initializer.kind === SyntaxKind.VariableDeclarationList) { + const variable = (initializer as VariableDeclarationList).declarations[0]; + if (variable && !isBindingPattern(variable.name)) { + return getSymbolOfNode(variable); } - return undefined; } - - /** - * Return true if the given type is considered to have numeric property names. - */ - function hasNumericPropertyNames(type: Type) { - return getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, numberType); + else if (initializer.kind === SyntaxKind.Identifier) { + return getResolvedSymbol(initializer as Identifier); } + return undefined; + } - /** - * Return true if given node is an expression consisting of an identifier (possibly parenthesized) - * that references a for-in variable for an object with numeric property names. - */ - function isForInVariableForNumericPropertyNames(expr: Expression) { - const e = skipParentheses(expr); - if (e.kind === SyntaxKind.Identifier) { - const symbol = getResolvedSymbol(e as Identifier); - if (symbol.flags & SymbolFlags.Variable) { - let child: Node = expr; - let node = expr.parent; - while (node) { - if (node.kind === SyntaxKind.ForInStatement && - child === (node as ForInStatement).statement && - getForInVariableSymbol(node as ForInStatement) === symbol && - hasNumericPropertyNames(getTypeOfExpression((node as ForInStatement).expression))) { - return true; - } - child = node; - node = node.parent; + /** + * Return true if the given type is considered to have numeric property names. + */ + function hasNumericPropertyNames(type: Type) { + return getIndexInfosOfType(type).length === 1 && !!getIndexInfoOfType(type, numberType); + } + + /** + * Return true if given node is an expression consisting of an identifier (possibly parenthesized) + * that references a for-in variable for an object with numeric property names. + */ + function isForInVariableForNumericPropertyNames(expr: Expression) { + const e = skipParentheses(expr); + if (e.kind === SyntaxKind.Identifier) { + const symbol = getResolvedSymbol(e as Identifier); + if (symbol.flags & SymbolFlags.Variable) { + let child: Node = expr; + let node = expr.parent; + while (node) { + if (node.kind === SyntaxKind.ForInStatement && + child === (node as ForInStatement).statement && + getForInVariableSymbol(node as ForInStatement) === symbol && + hasNumericPropertyNames(getTypeOfExpression((node as ForInStatement).expression))) { + return true; } + child = node; + node = node.parent; } } - return false; - } - - function checkIndexedAccess(node: ElementAccessExpression, checkMode: CheckMode | undefined): Type { - return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain, checkMode) : - checkElementAccessExpression(node, checkNonNullExpression(node.expression), checkMode); - } - - function checkElementAccessChain(node: ElementAccessChain, checkMode: CheckMode | undefined) { - const exprType = checkExpression(node.expression); - const nonOptionalType = getOptionalExpressionType(exprType, node.expression); - return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression), checkMode), node, nonOptionalType !== exprType); } + return false; + } - function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type, checkMode: CheckMode | undefined): Type { - const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; - const indexExpression = node.argumentExpression; - const indexType = checkExpression(indexExpression); + function checkIndexedAccess(node: ElementAccessExpression, checkMode: CheckMode | undefined): Type { + return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain, checkMode) : + checkElementAccessExpression(node, checkNonNullExpression(node.expression), checkMode); + } - if (isErrorType(objectType) || objectType === silentNeverType) { - return objectType; - } + function checkElementAccessChain(node: ElementAccessChain, checkMode: CheckMode | undefined) { + const exprType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(exprType, node.expression); + return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression), checkMode), node, nonOptionalType !== exprType); + } - if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) { - error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal); - return errorType; - } + function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type, checkMode: CheckMode | undefined): Type { + const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; + const indexExpression = node.argumentExpression; + const indexType = checkExpression(indexExpression); - const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; - const accessFlags = isAssignmentTarget(node) ? - AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : - AccessFlags.ExpressionPosition; - const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node) || errorType; - return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node); + if (isErrorType(objectType) || objectType === silentNeverType) { + return objectType; } - function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { - return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node); + if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) { + error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal); + return errorType; } - function resolveUntypedCall(node: CallLikeExpression): Signature { - if (callLikeExpressionMayHaveTypeArguments(node)) { - // Check type arguments even though we will give an error that untyped calls may not accept type arguments. - // This gets us diagnostics for the type arguments and marks them as referenced. - forEach(node.typeArguments, checkSourceElement); - } - - if (node.kind === SyntaxKind.TaggedTemplateExpression) { - checkExpression(node.template); - } - else if (isJsxOpeningLikeElement(node)) { - checkExpression(node.attributes); - } - else if (node.kind !== SyntaxKind.Decorator) { - forEach((node as CallExpression).arguments, argument => { - checkExpression(argument); - }); - } - return anySignature; - } - - function resolveErrorCall(node: CallLikeExpression): Signature { - resolveUntypedCall(node); - return unknownSignature; - } - - // Re-order candidate signatures into the result array. Assumes the result array to be empty. - // The candidate list orders groups in reverse, but within a group signatures are kept in declaration order - // A nit here is that we reorder only signatures that belong to the same symbol, - // so order how inherited signatures are processed is still preserved. - // interface A { (x: string): void } - // interface B extends A { (x: 'foo'): string } - // const b: B; - // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] - function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void { - let lastParent: Node | undefined; - let lastSymbol: Symbol | undefined; - let cutoffIndex = 0; - let index: number | undefined; - let specializedIndex = -1; - let spliceIndex: number; - Debug.assert(!result.length); - for (const signature of signatures) { - const symbol = signature.declaration && getSymbolOfNode(signature.declaration); - const parent = signature.declaration && signature.declaration.parent; - if (!lastSymbol || symbol === lastSymbol) { - if (lastParent && parent === lastParent) { - index = index! + 1; - } - else { - lastParent = parent; - index = cutoffIndex; - } - } - else { - // current declaration belongs to a different symbol - // set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex - index = cutoffIndex = result.length; - lastParent = parent; - } - lastSymbol = symbol; - - // specialized signatures always need to be placed before non-specialized signatures regardless - // of the cutoff position; see GH#1133 - if (signatureHasLiteralTypes(signature)) { - specializedIndex++; - spliceIndex = specializedIndex; - // The cutoff index always needs to be greater than or equal to the specialized signature index - // in order to prevent non-specialized signatures from being added before a specialized - // signature. - cutoffIndex++; - } - else { - spliceIndex = index; - } + const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; + const accessFlags = isAssignmentTarget(node) ? + AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : + AccessFlags.ExpressionPosition; + const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node) || errorType; + return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node); + } - result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature); - } - } + function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { + return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node); + } - function isSpreadArgument(arg: Expression | undefined): arg is Expression { - return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).isSpread); + function resolveUntypedCall(node: CallLikeExpression): Signature { + if (callLikeExpressionMayHaveTypeArguments(node)) { + // Check type arguments even though we will give an error that untyped calls may not accept type arguments. + // This gets us diagnostics for the type arguments and marks them as referenced. + forEach(node.typeArguments, checkSourceElement); } - function getSpreadArgumentIndex(args: readonly Expression[]): number { - return findIndex(args, isSpreadArgument); + if (node.kind === SyntaxKind.TaggedTemplateExpression) { + checkExpression(node.template); } - - function acceptsVoid(t: Type): boolean { - return !!(t.flags & TypeFlags.Void); + else if (isJsxOpeningLikeElement(node)) { + checkExpression(node.attributes); } - - function acceptsVoidUndefinedUnknownOrAny(t: Type): boolean { - return !!(t.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Unknown | TypeFlags.Any)); + else if (node.kind !== SyntaxKind.Decorator) { + forEach((node as CallExpression).arguments, argument => { + checkExpression(argument); + }); } + return anySignature; + } - function hasCorrectArity(node: CallLikeExpression, args: readonly Expression[], signature: Signature, signatureHelpTrailingComma = false) { - let argCount: number; - let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments - let effectiveParameterCount = getParameterCount(signature); - let effectiveMinimumArguments = getMinArgumentCount(signature); + function resolveErrorCall(node: CallLikeExpression): Signature { + resolveUntypedCall(node); + return unknownSignature; + } - if (node.kind === SyntaxKind.TaggedTemplateExpression) { - argCount = args.length; - if (node.template.kind === SyntaxKind.TemplateExpression) { - // If a tagged template expression lacks a tail literal, the call is incomplete. - // Specifically, a template only can end in a TemplateTail or a Missing literal. - const lastSpan = last(node.template.templateSpans); // we should always have at least one span. - callIsIncomplete = nodeIsMissing(lastSpan.literal) || !!lastSpan.literal.isUnterminated; + // Re-order candidate signatures into the result array. Assumes the result array to be empty. + // The candidate list orders groups in reverse, but within a group signatures are kept in declaration order + // A nit here is that we reorder only signatures that belong to the same symbol, + // so order how inherited signatures are processed is still preserved. + // interface A { (x: string): void } + // interface B extends A { (x: 'foo'): string } + // const b: B; + // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] + function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void { + let lastParent: Node | undefined; + let lastSymbol: Symbol | undefined; + let cutoffIndex = 0; + let index: number | undefined; + let specializedIndex = -1; + let spliceIndex: number; + Debug.assert(!result.length); + for (const signature of signatures) { + const symbol = signature.declaration && getSymbolOfNode(signature.declaration); + const parent = signature.declaration && signature.declaration.parent; + if (!lastSymbol || symbol === lastSymbol) { + if (lastParent && parent === lastParent) { + index = index! + 1; } else { - // If the template didn't end in a backtick, or its beginning occurred right prior to EOF, - // then this might actually turn out to be a TemplateHead in the future; - // so we consider the call to be incomplete. - const templateLiteral = node.template as LiteralExpression; - Debug.assert(templateLiteral.kind === SyntaxKind.NoSubstitutionTemplateLiteral); - callIsIncomplete = !!templateLiteral.isUnterminated; - } - } - else if (node.kind === SyntaxKind.Decorator) { - argCount = getDecoratorArgumentCount(node, signature); - } - else if (isJsxOpeningLikeElement(node)) { - callIsIncomplete = node.attributes.end === node.end; - if (callIsIncomplete) { - return true; + lastParent = parent; + index = cutoffIndex; } - argCount = effectiveMinimumArguments === 0 ? args.length : 1; - effectiveParameterCount = args.length === 0 ? effectiveParameterCount : 1; // class may have argumentless ctor functions - still resolve ctor and compare vs props member type - effectiveMinimumArguments = Math.min(effectiveMinimumArguments, 1); // sfc may specify context argument - handled by framework and not typechecked } - else if (!node.arguments) { - // This only happens when we have something of the form: 'new C' - Debug.assert(node.kind === SyntaxKind.NewExpression); - return getMinArgumentCount(signature) === 0; + else { + // current declaration belongs to a different symbol + // set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex + index = cutoffIndex = result.length; + lastParent = parent; + } + lastSymbol = symbol; + + // specialized signatures always need to be placed before non-specialized signatures regardless + // of the cutoff position; see GH#1133 + if (signatureHasLiteralTypes(signature)) { + specializedIndex++; + spliceIndex = specializedIndex; + // The cutoff index always needs to be greater than or equal to the specialized signature index + // in order to prevent non-specialized signatures from being added before a specialized + // signature. + cutoffIndex++; } else { - argCount = signatureHelpTrailingComma ? args.length + 1 : args.length; + spliceIndex = index; + } - // If we are missing the close parenthesis, the call is incomplete. - callIsIncomplete = node.arguments.end === node.end; + result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature); + } + } - // If a spread argument is present, check that it corresponds to a rest parameter or at least that it's in the valid range. - const spreadArgIndex = getSpreadArgumentIndex(args); - if (spreadArgIndex >= 0) { - return spreadArgIndex >= getMinArgumentCount(signature) && (hasEffectiveRestParameter(signature) || spreadArgIndex < getParameterCount(signature)); - } - } + function isSpreadArgument(arg: Expression | undefined): arg is Expression { + return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).isSpread); + } - // Too many arguments implies incorrect arity. - if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) { - return false; - } + function getSpreadArgumentIndex(args: readonly Expression[]): number { + return findIndex(args, isSpreadArgument); + } - // If the call is incomplete, we should skip the lower bound check. - // JSX signatures can have extra parameters provided by the library which we don't check - if (callIsIncomplete || argCount >= effectiveMinimumArguments) { - return true; + function acceptsVoid(t: Type): boolean { + return !!(t.flags & TypeFlags.Void); + } + + function acceptsVoidUndefinedUnknownOrAny(t: Type): boolean { + return !!(t.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Unknown | TypeFlags.Any)); + } + + function hasCorrectArity(node: CallLikeExpression, args: readonly Expression[], signature: Signature, signatureHelpTrailingComma = false) { + let argCount: number; + let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments + let effectiveParameterCount = getParameterCount(signature); + let effectiveMinimumArguments = getMinArgumentCount(signature); + + if (node.kind === SyntaxKind.TaggedTemplateExpression) { + argCount = args.length; + if (node.template.kind === SyntaxKind.TemplateExpression) { + // If a tagged template expression lacks a tail literal, the call is incomplete. + // Specifically, a template only can end in a TemplateTail or a Missing literal. + const lastSpan = last(node.template.templateSpans); // we should always have at least one span. + callIsIncomplete = nodeIsMissing(lastSpan.literal) || !!lastSpan.literal.isUnterminated; } - for (let i = argCount; i < effectiveMinimumArguments; i++) { - const type = getTypeAtPosition(signature, i); - if (filterType(type, isInJSFile(node) && !strictNullChecks ? acceptsVoidUndefinedUnknownOrAny : acceptsVoid).flags & TypeFlags.Never) { - return false; - } + else { + // If the template didn't end in a backtick, or its beginning occurred right prior to EOF, + // then this might actually turn out to be a TemplateHead in the future; + // so we consider the call to be incomplete. + const templateLiteral = node.template as LiteralExpression; + Debug.assert(templateLiteral.kind === SyntaxKind.NoSubstitutionTemplateLiteral); + callIsIncomplete = !!templateLiteral.isUnterminated; } - return true; } - - function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray | undefined) { - // If the user supplied type arguments, but the number of type arguments does not match - // the declared number of type parameters, the call has an incorrect arity. - const numTypeParameters = length(signature.typeParameters); - const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters); - return !some(typeArguments) || - (typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters); + else if (node.kind === SyntaxKind.Decorator) { + argCount = getDecoratorArgumentCount(node, signature); } - - // If type has a single call signature and no other members, return that signature. Otherwise, return undefined. - function getSingleCallSignature(type: Type): Signature | undefined { - return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false); + else if (isJsxOpeningLikeElement(node)) { + callIsIncomplete = node.attributes.end === node.end; + if (callIsIncomplete) { + return true; + } + argCount = effectiveMinimumArguments === 0 ? args.length : 1; + effectiveParameterCount = args.length === 0 ? effectiveParameterCount : 1; // class may have argumentless ctor functions - still resolve ctor and compare vs props member type + effectiveMinimumArguments = Math.min(effectiveMinimumArguments, 1); // sfc may specify context argument - handled by framework and not typechecked } - - function getSingleCallOrConstructSignature(type: Type): Signature | undefined { - return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) || - getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false); + else if (!node.arguments) { + // This only happens when we have something of the form: 'new C' + Debug.assert(node.kind === SyntaxKind.NewExpression); + return getMinArgumentCount(signature) === 0; } + else { + argCount = signatureHelpTrailingComma ? args.length + 1 : args.length; - function getSingleSignature(type: Type, kind: SignatureKind, allowMembers: boolean): Signature | undefined { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - if (allowMembers || resolved.properties.length === 0 && resolved.indexInfos.length === 0) { - if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) { - return resolved.callSignatures[0]; - } - if (kind === SignatureKind.Construct && resolved.constructSignatures.length === 1 && resolved.callSignatures.length === 0) { - return resolved.constructSignatures[0]; - } - } - } - return undefined; - } + // If we are missing the close parenthesis, the call is incomplete. + callIsIncomplete = node.arguments.end === node.end; - // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) - function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, inferenceContext?: InferenceContext, compareTypes?: TypeComparer): Signature { - const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None, compareTypes); - // We clone the inferenceContext to avoid fixing. For example, when the source signature is (x: T) => T[] and - // the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any') - // for T but leave it possible to later infer '[any]' back to A. - const restType = getEffectiveRestType(contextualSignature); - const mapper = inferenceContext && (restType && restType.flags & TypeFlags.TypeParameter ? inferenceContext.nonFixingMapper : inferenceContext.mapper); - const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature; - applyToParameterTypes(sourceSignature, signature, (source, target) => { - // Type parameters from outer context referenced by source type are fixed by instantiation of the source type - inferTypes(context.inferences, source, target); - }); - if (!inferenceContext) { - applyToReturnTypes(contextualSignature, signature, (source, target) => { - inferTypes(context.inferences, source, target, InferencePriority.ReturnType); - }); + // If a spread argument is present, check that it corresponds to a rest parameter or at least that it's in the valid range. + const spreadArgIndex = getSpreadArgumentIndex(args); + if (spreadArgIndex >= 0) { + return spreadArgIndex >= getMinArgumentCount(signature) && (hasEffectiveRestParameter(signature) || spreadArgIndex < getParameterCount(signature)); } - return getSignatureInstantiation(signature, getInferredTypes(context), isInJSFile(contextualSignature.declaration)); } - function inferJsxTypeArguments(node: JsxOpeningLikeElement, signature: Signature, checkMode: CheckMode, context: InferenceContext): Type[] { - const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); - const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context, checkMode); - inferTypes(context.inferences, checkAttrType, paramType); - return getInferredTypes(context); + // Too many arguments implies incorrect arity. + if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) { + return false; } - function getThisArgumentType(thisArgumentNode: LeftHandSideExpression | undefined) { - if (!thisArgumentNode) { - return voidType; + // If the call is incomplete, we should skip the lower bound check. + // JSX signatures can have extra parameters provided by the library which we don't check + if (callIsIncomplete || argCount >= effectiveMinimumArguments) { + return true; + } + for (let i = argCount; i < effectiveMinimumArguments; i++) { + const type = getTypeAtPosition(signature, i); + if (filterType(type, isInJSFile(node) && !strictNullChecks ? acceptsVoidUndefinedUnknownOrAny : acceptsVoid).flags & TypeFlags.Never) { + return false; } - const thisArgumentType = checkExpression(thisArgumentNode); - return isOptionalChainRoot(thisArgumentNode.parent) ? getNonNullableType(thisArgumentType) : - isOptionalChain(thisArgumentNode.parent) ? removeOptionalTypeMarker(thisArgumentType) : - thisArgumentType; } + return true; + } - function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] { - if (isJsxOpeningLikeElement(node)) { - return inferJsxTypeArguments(node, signature, checkMode, context); - } + function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray | undefined) { + // If the user supplied type arguments, but the number of type arguments does not match + // the declared number of type parameters, the call has an incorrect arity. + const numTypeParameters = length(signature.typeParameters); + const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters); + return !some(typeArguments) || + (typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters); + } - // If a contextual type is available, infer from that type to the return type of the call expression. For - // example, given a 'function wrap(cb: (x: T) => U): (x: T) => U' and a call expression - // 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the - // return type of 'wrap'. - if (node.kind !== SyntaxKind.Decorator) { - const skipBindingPatterns = every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)); - const contextualType = getContextualType(node, skipBindingPatterns ? ContextFlags.SkipBindingPatterns : ContextFlags.None); - if (contextualType) { - const inferenceTargetType = getReturnTypeOfSignature(signature); - if (couldContainTypeVariables(inferenceTargetType)) { - const outerContext = getInferenceContext(node); - const isFromBindingPattern = !skipBindingPatterns && getContextualType(node, ContextFlags.SkipBindingPatterns) !== contextualType; - // A return type inference from a binding pattern can be used in instantiating the contextual - // type of an argument later in inference, but cannot stand on its own as the final return type. - // It is incorporated into `context.returnMapper` which is used in `instantiateContextualType`, - // but doesn't need to go into `context.inferences`. This allows a an array binding pattern to - // produce a tuple for `T` in - // declare function f(cb: () => T): T; - // const [e1, e2, e3] = f(() => [1, "hi", true]); - // but does not produce any inference for `T` in - // declare function f(): T; - // const [e1, e2, e3] = f(); - if (!isFromBindingPattern) { - // 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(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 - // the type parameters are not erased to type any during type inference such that they can - // be inferred as actual types from the contextual type. For example: - // declare function arrayMap(f: (x: T) => U): (a: T[]) => U[]; - // const boxElements: (a: A[]) => { value: A }[] = arrayMap(value => ({ value })); - // Above, the type of the 'value' parameter is inferred to be 'A'. - const contextualSignature = getSingleCallSignature(instantiatedType); - const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ? - getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) : - instantiatedType; - // 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. 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(cloneInferredPartOfContext(returnContext)) : undefined; - } - } - } - - const restType = getNonArrayRestType(signature); - const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; - if (restType && restType.flags & TypeFlags.TypeParameter) { - const info = find(context.inferences, info => info.typeParameter === restType); - if (info) { - info.impliedArity = findIndex(args, isSpreadArgument, argCount) < 0 ? args.length - argCount : undefined; - } - } + // If type has a single call signature and no other members, return that signature. Otherwise, return undefined. + function getSingleCallSignature(type: Type): Signature | undefined { + return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false); + } - const thisType = getThisTypeOfSignature(signature); - if (thisType && couldContainTypeVariables(thisType)) { - const thisArgumentNode = getThisArgumentOfCall(node); - inferTypes(context.inferences, getThisArgumentType(thisArgumentNode), thisType); - } + function getSingleCallOrConstructSignature(type: Type): Signature | undefined { + return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) || + getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false); + } - for (let i = 0; i < argCount; i++) { - const arg = args[i]; - if (arg.kind !== SyntaxKind.OmittedExpression && !(checkMode & CheckMode.IsForStringLiteralArgumentCompletions && hasSkipDirectInferenceFlag(arg))) { - const paramType = getTypeAtPosition(signature, i); - if (couldContainTypeVariables(paramType)) { - const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode); - inferTypes(context.inferences, argType, paramType); - } + function getSingleSignature(type: Type, kind: SignatureKind, allowMembers: boolean): Signature | undefined { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + if (allowMembers || resolved.properties.length === 0 && resolved.indexInfos.length === 0) { + if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) { + return resolved.callSignatures[0]; + } + if (kind === SignatureKind.Construct && resolved.constructSignatures.length === 1 && resolved.callSignatures.length === 0) { + return resolved.constructSignatures[0]; } } + } + return undefined; + } - if (restType && couldContainTypeVariables(restType)) { - const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, context, checkMode); - inferTypes(context.inferences, spreadType, restType); - } + // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) + function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, inferenceContext?: InferenceContext, compareTypes?: TypeComparer): Signature { + const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None, compareTypes); + // We clone the inferenceContext to avoid fixing. For example, when the source signature is (x: T) => T[] and + // the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any') + // for T but leave it possible to later infer '[any]' back to A. + const restType = getEffectiveRestType(contextualSignature); + const mapper = inferenceContext && (restType && restType.flags & TypeFlags.TypeParameter ? inferenceContext.nonFixingMapper : inferenceContext.mapper); + const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature; + applyToParameterTypes(sourceSignature, signature, (source, target) => { + // Type parameters from outer context referenced by source type are fixed by instantiation of the source type + inferTypes(context.inferences, source, target); + }); + if (!inferenceContext) { + applyToReturnTypes(contextualSignature, signature, (source, target) => { + inferTypes(context.inferences, source, target, InferencePriority.ReturnType); + }); + } + return getSignatureInstantiation(signature, getInferredTypes(context), isInJSFile(contextualSignature.declaration)); + } + + function inferJsxTypeArguments(node: JsxOpeningLikeElement, signature: Signature, checkMode: CheckMode, context: InferenceContext): Type[] { + const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); + const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context, checkMode); + inferTypes(context.inferences, checkAttrType, paramType); + return getInferredTypes(context); + } - return getInferredTypes(context); + function getThisArgumentType(thisArgumentNode: LeftHandSideExpression | undefined) { + if (!thisArgumentNode) { + return voidType; } + const thisArgumentType = checkExpression(thisArgumentNode); + return isOptionalChainRoot(thisArgumentNode.parent) ? getNonNullableType(thisArgumentType) : + isOptionalChain(thisArgumentNode.parent) ? removeOptionalTypeMarker(thisArgumentType) : + thisArgumentType; + } - function getMutableArrayOrTupleType(type: Type) { - return type.flags & TypeFlags.Union ? mapType(type, getMutableArrayOrTupleType) : - type.flags & TypeFlags.Any || isMutableArrayOrTuple(getBaseConstraintOfType(type) || type) ? type : - isTupleType(type) ? createTupleType(getTypeArguments(type), type.target.elementFlags, /*readonly*/ false, type.target.labeledElementDeclarations) : - createTupleType([type], [ElementFlags.Variadic]); + function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] { + if (isJsxOpeningLikeElement(node)) { + return inferJsxTypeArguments(node, signature, checkMode, context); } - function getSpreadArgumentType(args: readonly Expression[], index: number, argCount: number, restType: Type, context: InferenceContext | undefined, checkMode: CheckMode) { - if (index >= argCount - 1) { - const arg = args[argCount - 1]; - if (isSpreadArgument(arg)) { - // We are inferring from a spread expression in the last argument position, i.e. both the parameter - // and the argument are ...x forms. - return getMutableArrayOrTupleType(arg.kind === SyntaxKind.SyntheticExpression ? (arg as SyntheticExpression).type : - checkExpressionWithContextualType((arg as SpreadElement).expression, restType, context, checkMode)); - } - } - const types = []; - const flags = []; - const names = []; - for (let i = index; i < argCount; i++) { - const arg = args[i]; - if (isSpreadArgument(arg)) { - const spreadType = arg.kind === SyntaxKind.SyntheticExpression ? (arg as SyntheticExpression).type : checkExpression((arg as SpreadElement).expression); - if (isArrayLikeType(spreadType)) { - types.push(spreadType); - flags.push(ElementFlags.Variadic); - } - else { - types.push(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, arg.kind === SyntaxKind.SpreadElement ? (arg as SpreadElement).expression : arg)); - flags.push(ElementFlags.Rest); - } + // If a contextual type is available, infer from that type to the return type of the call expression. For + // example, given a 'function wrap(cb: (x: T) => U): (x: T) => U' and a call expression + // 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the + // return type of 'wrap'. + if (node.kind !== SyntaxKind.Decorator) { + const skipBindingPatterns = every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)); + const contextualType = getContextualType(node, skipBindingPatterns ? ContextFlags.SkipBindingPatterns : ContextFlags.None); + if (contextualType) { + const inferenceTargetType = getReturnTypeOfSignature(signature); + if (couldContainTypeVariables(inferenceTargetType)) { + const outerContext = getInferenceContext(node); + const isFromBindingPattern = !skipBindingPatterns && getContextualType(node, ContextFlags.SkipBindingPatterns) !== contextualType; + // A return type inference from a binding pattern can be used in instantiating the contextual + // type of an argument later in inference, but cannot stand on its own as the final return type. + // It is incorporated into `context.returnMapper` which is used in `instantiateContextualType`, + // but doesn't need to go into `context.inferences`. This allows a an array binding pattern to + // produce a tuple for `T` in + // declare function f(cb: () => T): T; + // const [e1, e2, e3] = f(() => [1, "hi", true]); + // but does not produce any inference for `T` in + // declare function f(): T; + // const [e1, e2, e3] = f(); + if (!isFromBindingPattern) { + // 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(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 + // the type parameters are not erased to type any during type inference such that they can + // be inferred as actual types from the contextual type. For example: + // declare function arrayMap(f: (x: T) => U): (a: T[]) => U[]; + // const boxElements: (a: A[]) => { value: A }[] = arrayMap(value => ({ value })); + // Above, the type of the 'value' parameter is inferred to be 'A'. + const contextualSignature = getSingleCallSignature(instantiatedType); + const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ? + getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) : + instantiatedType; + // 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. 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(cloneInferredPartOfContext(returnContext)) : undefined; + } + } + } + + const restType = getNonArrayRestType(signature); + const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; + if (restType && restType.flags & TypeFlags.TypeParameter) { + const info = find(context.inferences, info => info.typeParameter === restType); + if (info) { + info.impliedArity = findIndex(args, isSpreadArgument, argCount) < 0 ? args.length - argCount : undefined; + } + } + + const thisType = getThisTypeOfSignature(signature); + if (thisType && couldContainTypeVariables(thisType)) { + const thisArgumentNode = getThisArgumentOfCall(node); + inferTypes(context.inferences, getThisArgumentType(thisArgumentNode), thisType); + } + + for (let i = 0; i < argCount; i++) { + const arg = args[i]; + if (arg.kind !== SyntaxKind.OmittedExpression && !(checkMode & CheckMode.IsForStringLiteralArgumentCompletions && hasSkipDirectInferenceFlag(arg))) { + const paramType = getTypeAtPosition(signature, i); + if (couldContainTypeVariables(paramType)) { + const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode); + inferTypes(context.inferences, argType, paramType); + } + } + } + + if (restType && couldContainTypeVariables(restType)) { + const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, context, checkMode); + inferTypes(context.inferences, spreadType, restType); + } + + return getInferredTypes(context); + } + + function getMutableArrayOrTupleType(type: Type) { + return type.flags & TypeFlags.Union ? mapType(type, getMutableArrayOrTupleType) : + type.flags & TypeFlags.Any || isMutableArrayOrTuple(getBaseConstraintOfType(type) || type) ? type : + isTupleType(type) ? createTupleType(getTypeArguments(type), type.target.elementFlags, /*readonly*/ false, type.target.labeledElementDeclarations) : + createTupleType([type], [ElementFlags.Variadic]); + } + + function getSpreadArgumentType(args: readonly Expression[], index: number, argCount: number, restType: Type, context: InferenceContext | undefined, checkMode: CheckMode) { + if (index >= argCount - 1) { + const arg = args[argCount - 1]; + if (isSpreadArgument(arg)) { + // We are inferring from a spread expression in the last argument position, i.e. both the parameter + // and the argument are ...x forms. + return getMutableArrayOrTupleType(arg.kind === SyntaxKind.SyntheticExpression ? (arg as SyntheticExpression).type : + checkExpressionWithContextualType((arg as SpreadElement).expression, restType, context, checkMode)); + } + } + const types = []; + const flags = []; + const names = []; + for (let i = index; i < argCount; i++) { + const arg = args[i]; + if (isSpreadArgument(arg)) { + const spreadType = arg.kind === SyntaxKind.SyntheticExpression ? (arg as SyntheticExpression).type : checkExpression((arg as SpreadElement).expression); + if (isArrayLikeType(spreadType)) { + types.push(spreadType); + flags.push(ElementFlags.Variadic); } else { - const contextualType = getIndexedAccessType(restType, getNumberLiteralType(i - index), AccessFlags.Contextual); - const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode); - const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping); - types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType)); - flags.push(ElementFlags.Required); + types.push(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, arg.kind === SyntaxKind.SpreadElement ? (arg as SpreadElement).expression : arg)); + flags.push(ElementFlags.Rest); } - if (arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).tupleNameSource) { - names.push((arg as SyntheticExpression).tupleNameSource!); + } + else { + const contextualType = getIndexedAccessType(restType, getNumberLiteralType(i - index), AccessFlags.Contextual); + const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode); + const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping); + types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType)); + flags.push(ElementFlags.Required); + } + if (arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).tupleNameSource) { + names.push((arg as SyntheticExpression).tupleNameSource!); + } + } + return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); + } + + function checkTypeArguments(signature: Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined { + const isJavascript = isInJSFile(signature.declaration); + const typeParameters = signature.typeParameters!; + const typeArgumentTypes = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isJavascript); + let mapper: TypeMapper | undefined; + for (let i = 0; i < typeArgumentNodes.length; i++) { + Debug.assert(typeParameters[i] !== undefined, "Should not call checkTypeArguments with too many type arguments"); + const constraint = getConstraintOfTypeParameter(typeParameters[i]); + if (constraint) { + const errorInfo = reportErrors && headMessage ? (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)) : undefined; + const typeArgumentHeadMessage = headMessage || Diagnostics.Type_0_does_not_satisfy_the_constraint_1; + if (!mapper) { + mapper = createTypeMapper(typeParameters, typeArgumentTypes); + } + const typeArgument = typeArgumentTypes[i]; + if (!checkTypeAssignableTo( + typeArgument, + getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument), + reportErrors ? typeArgumentNodes[i] : undefined, + typeArgumentHeadMessage, + errorInfo)) { + return undefined; } } - return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); } + return typeArgumentTypes; + } - function checkTypeArguments(signature: Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined { - const isJavascript = isInJSFile(signature.declaration); - const typeParameters = signature.typeParameters!; - const typeArgumentTypes = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isJavascript); - let mapper: TypeMapper | undefined; - for (let i = 0; i < typeArgumentNodes.length; i++) { - Debug.assert(typeParameters[i] !== undefined, "Should not call checkTypeArguments with too many type arguments"); - const constraint = getConstraintOfTypeParameter(typeParameters[i]); - if (constraint) { - const errorInfo = reportErrors && headMessage ? (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)) : undefined; - const typeArgumentHeadMessage = headMessage || Diagnostics.Type_0_does_not_satisfy_the_constraint_1; - if (!mapper) { - mapper = createTypeMapper(typeParameters, typeArgumentTypes); - } - const typeArgument = typeArgumentTypes[i]; - if (!checkTypeAssignableTo( - typeArgument, - getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument), - reportErrors ? typeArgumentNodes[i] : undefined, - typeArgumentHeadMessage, - errorInfo)) { - return undefined; - } - } - } - return typeArgumentTypes; + function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind { + if (isJsxIntrinsicIdentifier(node.tagName)) { + return JsxReferenceKind.Mixed; } + const tagType = getApparentType(checkExpression(node.tagName)); + if (length(getSignaturesOfType(tagType, SignatureKind.Construct))) { + return JsxReferenceKind.Component; + } + if (length(getSignaturesOfType(tagType, SignatureKind.Call))) { + return JsxReferenceKind.Function; + } + return JsxReferenceKind.Mixed; + } - function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind { - if (isJsxIntrinsicIdentifier(node.tagName)) { - return JsxReferenceKind.Mixed; + /** + * Check if the given signature can possibly be a signature called by the JSX opening-like element. + * @param node a JSX opening-like element we are trying to figure its call signature + * @param signature a candidate signature we are trying whether it is a call signature + * @param relation a relationship to check parameter and argument type + */ + function checkApplicableSignatureForJsxOpeningLikeElement( + node: JsxOpeningLikeElement, + signature: Signature, + relation: ESMap, + checkMode: CheckMode, + reportErrors: boolean, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } + ) { + // Stateless function components can have maximum of three arguments: "props", "context", and "updater". + // However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props, + // can be specified by users through attributes property. + const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); + const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode); + return checkTagNameDoesNotExpectTooManyArguments() && checkTypeRelatedToAndOptionallyElaborate( + attributesType, + paramType, + relation, + reportErrors ? node.tagName : undefined, + node.attributes, + /*headMessage*/ undefined, + containingMessageChain, + errorOutputContainer); + + function checkTagNameDoesNotExpectTooManyArguments(): boolean { + if (getJsxNamespaceContainerForImplicitImport(node)) { + return true; // factory is implicitly jsx/jsxdev - assume it fits the bill, since we don't strongly look for the jsx/jsxs/jsxDEV factory APIs anywhere else (at least not yet) + } + const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicIdentifier(node.tagName) ? checkExpression(node.tagName) : undefined; + if (!tagType) { + return true; } - const tagType = getApparentType(checkExpression(node.tagName)); - if (length(getSignaturesOfType(tagType, SignatureKind.Construct))) { - return JsxReferenceKind.Component; + const tagCallSignatures = getSignaturesOfType(tagType, SignatureKind.Call); + if (!length(tagCallSignatures)) { + return true; } - if (length(getSignaturesOfType(tagType, SignatureKind.Call))) { - return JsxReferenceKind.Function; + const factory = getJsxFactoryEntity(node); + if (!factory) { + return true; + } + const factorySymbol = resolveEntityName(factory, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, node); + if (!factorySymbol) { + return true; } - return JsxReferenceKind.Mixed; - } - - /** - * Check if the given signature can possibly be a signature called by the JSX opening-like element. - * @param node a JSX opening-like element we are trying to figure its call signature - * @param signature a candidate signature we are trying whether it is a call signature - * @param relation a relationship to check parameter and argument type - */ - function checkApplicableSignatureForJsxOpeningLikeElement( - node: JsxOpeningLikeElement, - signature: Signature, - relation: ESMap, - checkMode: CheckMode, - reportErrors: boolean, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } - ) { - // Stateless function components can have maximum of three arguments: "props", "context", and "updater". - // However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props, - // can be specified by users through attributes property. - const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); - const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode); - return checkTagNameDoesNotExpectTooManyArguments() && checkTypeRelatedToAndOptionallyElaborate( - attributesType, - paramType, - relation, - reportErrors ? node.tagName : undefined, - node.attributes, - /*headMessage*/ undefined, - containingMessageChain, - errorOutputContainer); - - function checkTagNameDoesNotExpectTooManyArguments(): boolean { - if (getJsxNamespaceContainerForImplicitImport(node)) { - return true; // factory is implicitly jsx/jsxdev - assume it fits the bill, since we don't strongly look for the jsx/jsxs/jsxDEV factory APIs anywhere else (at least not yet) - } - const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicIdentifier(node.tagName) ? checkExpression(node.tagName) : undefined; - if (!tagType) { - return true; - } - const tagCallSignatures = getSignaturesOfType(tagType, SignatureKind.Call); - if (!length(tagCallSignatures)) { - return true; - } - const factory = getJsxFactoryEntity(node); - if (!factory) { - return true; - } - const factorySymbol = resolveEntityName(factory, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, node); - if (!factorySymbol) { - return true; - } - const factoryType = getTypeOfSymbol(factorySymbol); - const callSignatures = getSignaturesOfType(factoryType, SignatureKind.Call); - if (!length(callSignatures)) { - return true; - } + const factoryType = getTypeOfSymbol(factorySymbol); + const callSignatures = getSignaturesOfType(factoryType, SignatureKind.Call); + if (!length(callSignatures)) { + return true; + } - let hasFirstParamSignatures = false; - let maxParamCount = 0; - // Check that _some_ first parameter expects a FC-like thing, and that some overload of the SFC expects an acceptable number of arguments - for (const sig of callSignatures) { - const firstparam = getTypeAtPosition(sig, 0); - const signaturesOfParam = getSignaturesOfType(firstparam, SignatureKind.Call); - if (!length(signaturesOfParam)) continue; - for (const paramSig of signaturesOfParam) { - hasFirstParamSignatures = true; - if (hasEffectiveRestParameter(paramSig)) { - return true; // some signature has a rest param, so function components can have an arbitrary number of arguments - } - const paramCount = getParameterCount(paramSig); - if (paramCount > maxParamCount) { - maxParamCount = paramCount; - } + let hasFirstParamSignatures = false; + let maxParamCount = 0; + // Check that _some_ first parameter expects a FC-like thing, and that some overload of the SFC expects an acceptable number of arguments + for (const sig of callSignatures) { + const firstparam = getTypeAtPosition(sig, 0); + const signaturesOfParam = getSignaturesOfType(firstparam, SignatureKind.Call); + if (!length(signaturesOfParam)) continue; + for (const paramSig of signaturesOfParam) { + hasFirstParamSignatures = true; + if (hasEffectiveRestParameter(paramSig)) { + return true; // some signature has a rest param, so function components can have an arbitrary number of arguments } - } - if (!hasFirstParamSignatures) { - // Not a single signature had a first parameter which expected a signature - for back compat, and - // to guard against generic factories which won't have signatures directly, do not error - return true; - } - let absoluteMinArgCount = Infinity; - for (const tagSig of tagCallSignatures) { - const tagRequiredArgCount = getMinArgumentCount(tagSig); - if (tagRequiredArgCount < absoluteMinArgCount) { - absoluteMinArgCount = tagRequiredArgCount; + const paramCount = getParameterCount(paramSig); + if (paramCount > maxParamCount) { + maxParamCount = paramCount; } } - if (absoluteMinArgCount <= maxParamCount) { - return true; // some signature accepts the number of arguments the function component provides + } + if (!hasFirstParamSignatures) { + // Not a single signature had a first parameter which expected a signature - for back compat, and + // to guard against generic factories which won't have signatures directly, do not error + return true; + } + let absoluteMinArgCount = Infinity; + for (const tagSig of tagCallSignatures) { + const tagRequiredArgCount = getMinArgumentCount(tagSig); + if (tagRequiredArgCount < absoluteMinArgCount) { + absoluteMinArgCount = tagRequiredArgCount; } + } + if (absoluteMinArgCount <= maxParamCount) { + return true; // some signature accepts the number of arguments the function component provides + } - if (reportErrors) { - const diag = createDiagnosticForNode(node.tagName, Diagnostics.Tag_0_expects_at_least_1_arguments_but_the_JSX_factory_2_provides_at_most_3, entityNameToString(node.tagName), absoluteMinArgCount, entityNameToString(factory), maxParamCount); - const tagNameDeclaration = getSymbolAtLocation(node.tagName)?.valueDeclaration; - if (tagNameDeclaration) { - addRelatedInfo(diag, createDiagnosticForNode(tagNameDeclaration, Diagnostics._0_is_declared_here, entityNameToString(node.tagName))); - } - if (errorOutputContainer && errorOutputContainer.skipLogging) { - (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); - } - if (!errorOutputContainer.skipLogging) { - diagnostics.add(diag); - } + if (reportErrors) { + const diag = createDiagnosticForNode(node.tagName, Diagnostics.Tag_0_expects_at_least_1_arguments_but_the_JSX_factory_2_provides_at_most_3, entityNameToString(node.tagName), absoluteMinArgCount, entityNameToString(factory), maxParamCount); + const tagNameDeclaration = getSymbolAtLocation(node.tagName)?.valueDeclaration; + if (tagNameDeclaration) { + addRelatedInfo(diag, createDiagnosticForNode(tagNameDeclaration, Diagnostics._0_is_declared_here, entityNameToString(node.tagName))); + } + if (errorOutputContainer && errorOutputContainer.skipLogging) { + (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); + } + if (!errorOutputContainer.skipLogging) { + diagnostics.add(diag); } - return false; } + return false; } + } - function getSignatureApplicabilityError( - node: CallLikeExpression, - args: readonly Expression[], - signature: Signature, - relation: ESMap, - checkMode: CheckMode, - reportErrors: boolean, - containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, - ): readonly Diagnostic[] | undefined { - - const errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } = { errors: undefined, skipLogging: true }; - if (isJsxOpeningLikeElement(node)) { - if (!checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, containingMessageChain, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "jsx should have errors when reporting errors"); - return errorOutputContainer.errors || emptyArray; - } - return undefined; + function getSignatureApplicabilityError( + node: CallLikeExpression, + args: readonly Expression[], + signature: Signature, + relation: ESMap, + checkMode: CheckMode, + reportErrors: boolean, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + ): readonly Diagnostic[] | undefined { + + const errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } = { errors: undefined, skipLogging: true }; + if (isJsxOpeningLikeElement(node)) { + if (!checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, containingMessageChain, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "jsx should have errors when reporting errors"); + return errorOutputContainer.errors || emptyArray; } - const thisType = getThisTypeOfSignature(signature); - if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) { - // If the called expression is not of the form `x.f` or `x["f"]`, then sourceType = voidType - // If the signature's 'this' type is voidType, then the check is skipped -- anything is compatible. - // If the expression is a new expression, then the check is skipped. - const thisArgumentNode = getThisArgumentOfCall(node); - const thisArgumentType = getThisArgumentType(thisArgumentNode); - const errorNode = reportErrors ? (thisArgumentNode || node) : undefined; - const headMessage = Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1; - if (!checkTypeRelatedTo(thisArgumentType, thisType, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "this parameter should have errors when reporting errors"); + return undefined; + } + const thisType = getThisTypeOfSignature(signature); + if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) { + // If the called expression is not of the form `x.f` or `x["f"]`, then sourceType = voidType + // If the signature's 'this' type is voidType, then the check is skipped -- anything is compatible. + // If the expression is a new expression, then the check is skipped. + const thisArgumentNode = getThisArgumentOfCall(node); + const thisArgumentType = getThisArgumentType(thisArgumentNode); + const errorNode = reportErrors ? (thisArgumentNode || node) : undefined; + const headMessage = Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1; + if (!checkTypeRelatedTo(thisArgumentType, thisType, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "this parameter should have errors when reporting errors"); + return errorOutputContainer.errors || emptyArray; + } + } + const headMessage = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1; + const restType = getNonArrayRestType(signature); + const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; + for (let i = 0; i < argCount; i++) { + const arg = args[i]; + if (arg.kind !== SyntaxKind.OmittedExpression) { + const paramType = getTypeAtPosition(signature, i); + const argType = checkExpressionWithContextualType(arg, paramType, /*inferenceContext*/ undefined, checkMode); + // If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive), + // we obtain the regular type of any object literal arguments because we may not have inferred complete + // parameter types yet and therefore excess property checks may yield false positives (see #17041). + const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; + if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? arg : undefined, arg, headMessage, containingMessageChain, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors"); + maybeAddMissingAwaitInfo(arg, checkArgType, paramType); return errorOutputContainer.errors || emptyArray; } } - const headMessage = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1; - const restType = getNonArrayRestType(signature); - const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; - for (let i = 0; i < argCount; i++) { - const arg = args[i]; - if (arg.kind !== SyntaxKind.OmittedExpression) { - const paramType = getTypeAtPosition(signature, i); - const argType = checkExpressionWithContextualType(arg, paramType, /*inferenceContext*/ undefined, checkMode); - // If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive), - // we obtain the regular type of any object literal arguments because we may not have inferred complete - // parameter types yet and therefore excess property checks may yield false positives (see #17041). - const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; - if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? arg : undefined, arg, headMessage, containingMessageChain, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors"); - maybeAddMissingAwaitInfo(arg, checkArgType, paramType); - return errorOutputContainer.errors || emptyArray; - } - } - } - if (restType) { - const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, /*context*/ undefined, checkMode); - const restArgCount = args.length - argCount; - const errorNode = !reportErrors ? undefined : - restArgCount === 0 ? node : - restArgCount === 1 ? args[argCount] : - setTextRangePosEnd(createSyntheticExpression(node, spreadType), args[argCount].pos, args[args.length - 1].end); - if (!checkTypeRelatedTo(spreadType, restType, relation, errorNode, headMessage, /*containingMessageChain*/ undefined, errorOutputContainer)) { - Debug.assert(!reportErrors || !!errorOutputContainer.errors, "rest parameter should have errors when reporting errors"); - maybeAddMissingAwaitInfo(errorNode, spreadType, restType); - return errorOutputContainer.errors || emptyArray; - } + } + if (restType) { + const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, /*context*/ undefined, checkMode); + const restArgCount = args.length - argCount; + const errorNode = !reportErrors ? undefined : + restArgCount === 0 ? node : + restArgCount === 1 ? args[argCount] : + setTextRangePosEnd(createSyntheticExpression(node, spreadType), args[argCount].pos, args[args.length - 1].end); + if (!checkTypeRelatedTo(spreadType, restType, relation, errorNode, headMessage, /*containingMessageChain*/ undefined, errorOutputContainer)) { + Debug.assert(!reportErrors || !!errorOutputContainer.errors, "rest parameter should have errors when reporting errors"); + maybeAddMissingAwaitInfo(errorNode, spreadType, restType); + return errorOutputContainer.errors || emptyArray; } - return undefined; + } + return undefined; - function maybeAddMissingAwaitInfo(errorNode: Node | undefined, source: Type, target: Type) { - if (errorNode && reportErrors && errorOutputContainer.errors && errorOutputContainer.errors.length) { - // Bail if target is Promise-like---something else is wrong - if (getAwaitedTypeOfPromise(target)) { - return; - } - const awaitedTypeOfSource = getAwaitedTypeOfPromise(source); - if (awaitedTypeOfSource && isTypeRelatedTo(awaitedTypeOfSource, target, relation)) { - addRelatedInfo(errorOutputContainer.errors[0], createDiagnosticForNode(errorNode, Diagnostics.Did_you_forget_to_use_await)); - } + function maybeAddMissingAwaitInfo(errorNode: Node | undefined, source: Type, target: Type) { + if (errorNode && reportErrors && errorOutputContainer.errors && errorOutputContainer.errors.length) { + // Bail if target is Promise-like---something else is wrong + if (getAwaitedTypeOfPromise(target)) { + return; + } + const awaitedTypeOfSource = getAwaitedTypeOfPromise(source); + if (awaitedTypeOfSource && isTypeRelatedTo(awaitedTypeOfSource, target, relation)) { + addRelatedInfo(errorOutputContainer.errors[0], createDiagnosticForNode(errorNode, Diagnostics.Did_you_forget_to_use_await)); } } } + } - /** - * Returns the this argument in calls like x.f(...) and x[f](...). Undefined otherwise. - */ - function getThisArgumentOfCall(node: CallLikeExpression): LeftHandSideExpression | undefined { - const expression = node.kind === SyntaxKind.CallExpression ? node.expression : - node.kind === SyntaxKind.TaggedTemplateExpression ? node.tag : undefined; - if (expression) { - const callee = skipOuterExpressions(expression); - if (isAccessExpression(callee)) { - return callee.expression; - } + /** + * Returns the this argument in calls like x.f(...) and x[f](...). Undefined otherwise. + */ + function getThisArgumentOfCall(node: CallLikeExpression): LeftHandSideExpression | undefined { + const expression = node.kind === SyntaxKind.CallExpression ? node.expression : + node.kind === SyntaxKind.TaggedTemplateExpression ? node.tag : undefined; + if (expression) { + const callee = skipOuterExpressions(expression); + if (isAccessExpression(callee)) { + return callee.expression; } } + } - function createSyntheticExpression(parent: Node, type: Type, isSpread?: boolean, tupleNameSource?: ParameterDeclaration | NamedTupleMember) { - const result = parseNodeFactory.createSyntheticExpression(type, isSpread, tupleNameSource); - setTextRange(result, parent); - setParent(result, parent); - return result; - } + function createSyntheticExpression(parent: Node, type: Type, isSpread?: boolean, tupleNameSource?: ParameterDeclaration | NamedTupleMember) { + const result = parseNodeFactory.createSyntheticExpression(type, isSpread, tupleNameSource); + setTextRange(result, parent); + setParent(result, parent); + return result; + } - /** - * Returns the effective arguments for an expression that works like a function invocation. - */ - function getEffectiveCallArguments(node: CallLikeExpression): readonly Expression[] { - if (node.kind === SyntaxKind.TaggedTemplateExpression) { - const template = node.template; - const args: Expression[] = [createSyntheticExpression(template, getGlobalTemplateStringsArrayType())]; - if (template.kind === SyntaxKind.TemplateExpression) { - forEach(template.templateSpans, span => { - args.push(span.expression); + /** + * Returns the effective arguments for an expression that works like a function invocation. + */ + function getEffectiveCallArguments(node: CallLikeExpression): readonly Expression[] { + if (node.kind === SyntaxKind.TaggedTemplateExpression) { + const template = node.template; + const args: Expression[] = [createSyntheticExpression(template, getGlobalTemplateStringsArrayType())]; + if (template.kind === SyntaxKind.TemplateExpression) { + forEach(template.templateSpans, span => { + args.push(span.expression); + }); + } + return args; + } + if (node.kind === SyntaxKind.Decorator) { + return getEffectiveDecoratorArguments(node); + } + if (isJsxOpeningLikeElement(node)) { + return node.attributes.properties.length > 0 || (isJsxOpeningElement(node) && node.parent.children.length > 0) ? [node.attributes] : emptyArray; + } + const args = node.arguments || emptyArray; + const spreadIndex = getSpreadArgumentIndex(args); + if (spreadIndex >= 0) { + // Create synthetic arguments from spreads of tuple types. + const effectiveArgs = args.slice(0, spreadIndex); + for (let i = spreadIndex; i < args.length; i++) { + const arg = args[i]; + // We can call checkExpressionCached because spread expressions never have a contextual type. + const spreadType = arg.kind === SyntaxKind.SpreadElement && (flowLoopCount ? checkExpression((arg as SpreadElement).expression) : checkExpressionCached((arg as SpreadElement).expression)); + if (spreadType && isTupleType(spreadType)) { + forEach(getTypeArguments(spreadType), (t, i) => { + const flags = spreadType.target.elementFlags[i]; + const syntheticArg = createSyntheticExpression(arg, flags & ElementFlags.Rest ? createArrayType(t) : t, + !!(flags & ElementFlags.Variable), spreadType.target.labeledElementDeclarations?.[i]); + effectiveArgs.push(syntheticArg); }); } - return args; - } - if (node.kind === SyntaxKind.Decorator) { - return getEffectiveDecoratorArguments(node); - } - if (isJsxOpeningLikeElement(node)) { - return node.attributes.properties.length > 0 || (isJsxOpeningElement(node) && node.parent.children.length > 0) ? [node.attributes] : emptyArray; - } - const args = node.arguments || emptyArray; - const spreadIndex = getSpreadArgumentIndex(args); - if (spreadIndex >= 0) { - // Create synthetic arguments from spreads of tuple types. - const effectiveArgs = args.slice(0, spreadIndex); - for (let i = spreadIndex; i < args.length; i++) { - const arg = args[i]; - // We can call checkExpressionCached because spread expressions never have a contextual type. - const spreadType = arg.kind === SyntaxKind.SpreadElement && (flowLoopCount ? checkExpression((arg as SpreadElement).expression) : checkExpressionCached((arg as SpreadElement).expression)); - if (spreadType && isTupleType(spreadType)) { - forEach(getTypeArguments(spreadType), (t, i) => { - const flags = spreadType.target.elementFlags[i]; - const syntheticArg = createSyntheticExpression(arg, flags & ElementFlags.Rest ? createArrayType(t) : t, - !!(flags & ElementFlags.Variable), spreadType.target.labeledElementDeclarations?.[i]); - effectiveArgs.push(syntheticArg); - }); - } - else { - effectiveArgs.push(arg); - } + else { + effectiveArgs.push(arg); } - return effectiveArgs; } - return args; + return effectiveArgs; } + return args; + } - /** - * Returns the synthetic argument list for a decorator invocation. - */ - function getEffectiveDecoratorArguments(node: Decorator): readonly Expression[] { - const parent = node.parent; - const expr = node.expression; - switch (parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - // For a class decorator, the `target` is the type of the class (e.g. the - // "static" or "constructor" side of the class). - return [ - createSyntheticExpression(expr, getTypeOfSymbol(getSymbolOfNode(parent))) - ]; - case SyntaxKind.Parameter: - // A parameter declaration decorator will have three arguments (see - // `ParameterDecorator` in core.d.ts). - const func = parent.parent as FunctionLikeDeclaration; - return [ - createSyntheticExpression(expr, parent.parent.kind === SyntaxKind.Constructor ? getTypeOfSymbol(getSymbolOfNode(func)) : errorType), - createSyntheticExpression(expr, anyType), - createSyntheticExpression(expr, numberType) - ]; - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // A method or accessor declaration decorator will have two or three arguments (see - // `PropertyDecorator` and `MethodDecorator` in core.d.ts). If we are emitting decorators - // for ES3, we will only pass two arguments. - const hasPropDesc = languageVersion !== ScriptTarget.ES3 && (!isPropertyDeclaration(parent) || hasAccessorModifier(parent)); - return [ - createSyntheticExpression(expr, getParentTypeOfClassElement(parent as ClassElement)), - createSyntheticExpression(expr, getClassElementPropertyKeyType(parent as ClassElement)), - createSyntheticExpression(expr, hasPropDesc ? createTypedPropertyDescriptorType(getTypeOfNode(parent)) : anyType) - ]; - } - return Debug.fail(); + /** + * Returns the synthetic argument list for a decorator invocation. + */ + function getEffectiveDecoratorArguments(node: Decorator): readonly Expression[] { + const parent = node.parent; + const expr = node.expression; + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + // For a class decorator, the `target` is the type of the class (e.g. the + // "static" or "constructor" side of the class). + return [ + createSyntheticExpression(expr, getTypeOfSymbol(getSymbolOfNode(parent))) + ]; + case SyntaxKind.Parameter: + // A parameter declaration decorator will have three arguments (see + // `ParameterDecorator` in core.d.ts). + const func = parent.parent as FunctionLikeDeclaration; + return [ + createSyntheticExpression(expr, parent.parent.kind === SyntaxKind.Constructor ? getTypeOfSymbol(getSymbolOfNode(func)) : errorType), + createSyntheticExpression(expr, anyType), + createSyntheticExpression(expr, numberType) + ]; + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // A method or accessor declaration decorator will have two or three arguments (see + // `PropertyDecorator` and `MethodDecorator` in core.d.ts). If we are emitting decorators + // for ES3, we will only pass two arguments. + const hasPropDesc = languageVersion !== ScriptTarget.ES3 && (!isPropertyDeclaration(parent) || hasAccessorModifier(parent)); + return [ + createSyntheticExpression(expr, getParentTypeOfClassElement(parent as ClassElement)), + createSyntheticExpression(expr, getClassElementPropertyKeyType(parent as ClassElement)), + createSyntheticExpression(expr, hasPropDesc ? createTypedPropertyDescriptorType(getTypeOfNode(parent)) : anyType) + ]; } + return Debug.fail(); + } - /** - * Returns the argument count for a decorator node that works like a function invocation. - */ - function getDecoratorArgumentCount(node: Decorator, signature: Signature) { - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return 1; - case SyntaxKind.PropertyDeclaration: - return hasAccessorModifier(node.parent) ? 3 : 2; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // For ES3 or decorators with only two parameters we supply only two arguments - return languageVersion === ScriptTarget.ES3 || signature.parameters.length <= 2 ? 2 : 3; - case SyntaxKind.Parameter: - return 3; - default: - return Debug.fail(); - } + /** + * Returns the argument count for a decorator node that works like a function invocation. + */ + function getDecoratorArgumentCount(node: Decorator, signature: Signature) { + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return 1; + case SyntaxKind.PropertyDeclaration: + return hasAccessorModifier(node.parent) ? 3 : 2; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // For ES3 or decorators with only two parameters we supply only two arguments + return languageVersion === ScriptTarget.ES3 || signature.parameters.length <= 2 ? 2 : 3; + case SyntaxKind.Parameter: + return 3; + default: + return Debug.fail(); } - function getDiagnosticSpanForCallNode(node: CallExpression, doNotIncludeArguments?: boolean) { - let start: number; - let length: number; - const sourceFile = getSourceFileOfNode(node); - - 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 getDiagnosticSpanForCallNode(node: CallExpression, doNotIncludeArguments?: boolean) { + let start: number; + let length: number; + const sourceFile = getSourceFileOfNode(node); + + 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); } - 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); - } + else { + return createDiagnosticForNode(node, message, arg0, arg1, arg2, arg3); } + } - function isPromiseResolveArityError(node: CallLikeExpression) { - if (!isCallExpression(node) || !isIdentifier(node.expression)) return false; + function isPromiseResolveArityError(node: CallLikeExpression) { + if (!isCallExpression(node) || !isIdentifier(node.expression)) return false; - const symbol = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, undefined, undefined, false); - const decl = symbol?.valueDeclaration; - if (!decl || !isParameter(decl) || !isFunctionExpressionOrArrowFunction(decl.parent) || !isNewExpression(decl.parent.parent) || !isIdentifier(decl.parent.parent.expression)) { - return false; - } + const symbol = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, undefined, undefined, false); + const decl = symbol?.valueDeclaration; + if (!decl || !isParameter(decl) || !isFunctionExpressionOrArrowFunction(decl.parent) || !isNewExpression(decl.parent.parent) || !isIdentifier(decl.parent.parent.expression)) { + return false; + } - const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); - if (!globalPromiseSymbol) return false; - - const constructorSymbol = getSymbolAtLocation(decl.parent.parent.expression, /*ignoreErrors*/ true); - return constructorSymbol === globalPromiseSymbol; - } - - function getArgumentArityError(node: CallLikeExpression, signatures: readonly Signature[], args: readonly Expression[]) { - const spreadIndex = getSpreadArgumentIndex(args); - if (spreadIndex > -1) { - return createDiagnosticForNode(args[spreadIndex], Diagnostics.A_spread_argument_must_either_have_a_tuple_type_or_be_passed_to_a_rest_parameter); - } - let min = Number.POSITIVE_INFINITY; // smallest parameter count - let max = Number.NEGATIVE_INFINITY; // largest parameter count - let maxBelow = Number.NEGATIVE_INFINITY; // largest parameter count that is smaller than the number of arguments - let minAbove = Number.POSITIVE_INFINITY; // smallest parameter count that is larger than the number of arguments - - let closestSignature: Signature | undefined; - for (const sig of signatures) { - const minParameter = getMinArgumentCount(sig); - const maxParameter = getParameterCount(sig); - // smallest/largest parameter counts - if (minParameter < min) { - min = minParameter; - closestSignature = sig; - } - max = Math.max(max, maxParameter); - // shortest parameter count *longer than the call*/longest parameter count *shorter than the call* - if (minParameter < args.length && minParameter > maxBelow) maxBelow = minParameter; - if (args.length < maxParameter && maxParameter < minAbove) minAbove = maxParameter; - } - const hasRestParameter = some(signatures, hasEffectiveRestParameter); - const parameterRange = hasRestParameter ? min - : min < max ? min + "-" + max - : min; - const isVoidPromiseError = !hasRestParameter && parameterRange === 1 && args.length === 0 && isPromiseResolveArityError(node); - if (isVoidPromiseError && isInJSFile(node)) { - return getDiagnosticForCallNode(node, Diagnostics.Expected_1_argument_but_got_0_new_Promise_needs_a_JSDoc_hint_to_produce_a_resolve_that_can_be_called_without_arguments); - } - const error = hasRestParameter - ? Diagnostics.Expected_at_least_0_arguments_but_got_1 - : isVoidPromiseError - ? Diagnostics.Expected_0_arguments_but_got_1_Did_you_forget_to_include_void_in_your_type_argument_to_Promise - : Diagnostics.Expected_0_arguments_but_got_1; - if (min < args.length && args.length < max) { - // between min and max, but with no matching overload - return getDiagnosticForCallNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, args.length, maxBelow, minAbove); - } - else if (args.length < min) { - // too short: put the error span on the call expression, not any of the args - const diagnostic = getDiagnosticForCallNode(node, error, parameterRange, args.length); - const parameter = closestSignature?.declaration?.parameters[closestSignature.thisParameter ? args.length + 1 : args.length]; - if (parameter) { - const parameterError = createDiagnosticForNode( - parameter, - isBindingPattern(parameter.name) ? Diagnostics.An_argument_matching_this_binding_pattern_was_not_provided - : isRestParameter(parameter) ? Diagnostics.Arguments_for_the_rest_parameter_0_were_not_provided - : Diagnostics.An_argument_for_0_was_not_provided, - !parameter.name ? args.length : !isBindingPattern(parameter.name) ? idText(getFirstIdentifier(parameter.name)) : undefined - ); - return addRelatedInfo(diagnostic, parameterError); - } - return diagnostic; + const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); + if (!globalPromiseSymbol) return false; + + const constructorSymbol = getSymbolAtLocation(decl.parent.parent.expression, /*ignoreErrors*/ true); + return constructorSymbol === globalPromiseSymbol; + } + + function getArgumentArityError(node: CallLikeExpression, signatures: readonly Signature[], args: readonly Expression[]) { + const spreadIndex = getSpreadArgumentIndex(args); + if (spreadIndex > -1) { + return createDiagnosticForNode(args[spreadIndex], Diagnostics.A_spread_argument_must_either_have_a_tuple_type_or_be_passed_to_a_rest_parameter); + } + let min = Number.POSITIVE_INFINITY; // smallest parameter count + let max = Number.NEGATIVE_INFINITY; // largest parameter count + let maxBelow = Number.NEGATIVE_INFINITY; // largest parameter count that is smaller than the number of arguments + let minAbove = Number.POSITIVE_INFINITY; // smallest parameter count that is larger than the number of arguments + + let closestSignature: Signature | undefined; + for (const sig of signatures) { + const minParameter = getMinArgumentCount(sig); + const maxParameter = getParameterCount(sig); + // smallest/largest parameter counts + if (minParameter < min) { + min = minParameter; + closestSignature = sig; + } + max = Math.max(max, maxParameter); + // shortest parameter count *longer than the call*/longest parameter count *shorter than the call* + if (minParameter < args.length && minParameter > maxBelow) maxBelow = minParameter; + if (args.length < maxParameter && maxParameter < minAbove) minAbove = maxParameter; + } + const hasRestParameter = some(signatures, hasEffectiveRestParameter); + const parameterRange = hasRestParameter ? min + : min < max ? min + "-" + max + : min; + const isVoidPromiseError = !hasRestParameter && parameterRange === 1 && args.length === 0 && isPromiseResolveArityError(node); + if (isVoidPromiseError && isInJSFile(node)) { + return getDiagnosticForCallNode(node, Diagnostics.Expected_1_argument_but_got_0_new_Promise_needs_a_JSDoc_hint_to_produce_a_resolve_that_can_be_called_without_arguments); + } + const error = hasRestParameter + ? Diagnostics.Expected_at_least_0_arguments_but_got_1 + : isVoidPromiseError + ? Diagnostics.Expected_0_arguments_but_got_1_Did_you_forget_to_include_void_in_your_type_argument_to_Promise + : Diagnostics.Expected_0_arguments_but_got_1; + if (min < args.length && args.length < max) { + // between min and max, but with no matching overload + return getDiagnosticForCallNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, args.length, maxBelow, minAbove); + } + else if (args.length < min) { + // too short: put the error span on the call expression, not any of the args + const diagnostic = getDiagnosticForCallNode(node, error, parameterRange, args.length); + const parameter = closestSignature?.declaration?.parameters[closestSignature.thisParameter ? args.length + 1 : args.length]; + if (parameter) { + const parameterError = createDiagnosticForNode( + parameter, + isBindingPattern(parameter.name) ? Diagnostics.An_argument_matching_this_binding_pattern_was_not_provided + : isRestParameter(parameter) ? Diagnostics.Arguments_for_the_rest_parameter_0_were_not_provided + : Diagnostics.An_argument_for_0_was_not_provided, + !parameter.name ? args.length : !isBindingPattern(parameter.name) ? idText(getFirstIdentifier(parameter.name)) : undefined + ); + return addRelatedInfo(diagnostic, parameterError); } - else { - // too long; error goes on the excess parameters - const errorSpan = factory.createNodeArray(args.slice(max)); - const pos = first(errorSpan).pos; - let end = last(errorSpan).end; - if (end === pos) { - end++; - } - setTextRangePosEnd(errorSpan, pos, end); - return createDiagnosticForNodeArray(getSourceFileOfNode(node), errorSpan, error, parameterRange, args.length); + return diagnostic; + } + else { + // too long; error goes on the excess parameters + const errorSpan = factory.createNodeArray(args.slice(max)); + const pos = first(errorSpan).pos; + let end = last(errorSpan).end; + if (end === pos) { + end++; } + setTextRangePosEnd(errorSpan, pos, end); + return createDiagnosticForNodeArray(getSourceFileOfNode(node), errorSpan, error, parameterRange, args.length); } + } - function getTypeArgumentArityError(node: Node, signatures: readonly Signature[], typeArguments: NodeArray) { - const argCount = typeArguments.length; - // No overloads exist - if (signatures.length === 1) { - const sig = signatures[0]; - const min = getMinTypeArgumentCount(sig.typeParameters); - const max = length(sig.typeParameters); - return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, min < max ? min + "-" + max : min , argCount); - } - // Overloads exist - let belowArgCount = -Infinity; - let aboveArgCount = Infinity; - for (const sig of signatures) { - const min = getMinTypeArgumentCount(sig.typeParameters); - const max = length(sig.typeParameters); - if (min > argCount) { - aboveArgCount = Math.min(aboveArgCount, min); - } - else if (max < argCount) { - belowArgCount = Math.max(belowArgCount, max); - } + function getTypeArgumentArityError(node: Node, signatures: readonly Signature[], typeArguments: NodeArray) { + const argCount = typeArguments.length; + // No overloads exist + if (signatures.length === 1) { + const sig = signatures[0]; + const min = getMinTypeArgumentCount(sig.typeParameters); + const max = length(sig.typeParameters); + return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, min < max ? min + "-" + max : min , argCount); + } + // Overloads exist + let belowArgCount = -Infinity; + let aboveArgCount = Infinity; + for (const sig of signatures) { + const min = getMinTypeArgumentCount(sig.typeParameters); + const max = length(sig.typeParameters); + if (min > argCount) { + aboveArgCount = Math.min(aboveArgCount, min); } - if (belowArgCount !== -Infinity && aboveArgCount !== Infinity) { - return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.No_overload_expects_0_type_arguments_but_overloads_do_exist_that_expect_either_1_or_2_type_arguments, argCount, belowArgCount, aboveArgCount); + else if (max < argCount) { + belowArgCount = Math.max(belowArgCount, max); } - return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); } + if (belowArgCount !== -Infinity && aboveArgCount !== Infinity) { + return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.No_overload_expects_0_type_arguments_but_overloads_do_exist_that_expect_either_1_or_2_type_arguments, argCount, belowArgCount, aboveArgCount); + } + return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); + } - function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): Signature { - const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; - const isDecorator = node.kind === SyntaxKind.Decorator; - const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); - const reportErrors = !candidatesOutArray; + function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): Signature { + const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; + const isDecorator = node.kind === SyntaxKind.Decorator; + const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); + const reportErrors = !candidatesOutArray; - let typeArguments: NodeArray | undefined; + let typeArguments: NodeArray | undefined; - if (!isDecorator && !isSuperCall(node)) { - typeArguments = (node as CallExpression).typeArguments; + if (!isDecorator && !isSuperCall(node)) { + typeArguments = (node as CallExpression).typeArguments; - // We already perform checking on the type arguments on the class declaration itself. - if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node as CallExpression).expression.kind !== SyntaxKind.SuperKeyword) { - forEach(typeArguments, checkSourceElement); - } + // We already perform checking on the type arguments on the class declaration itself. + if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node as CallExpression).expression.kind !== SyntaxKind.SuperKeyword) { + forEach(typeArguments, checkSourceElement); } + } - const candidates = candidatesOutArray || []; - // reorderCandidates fills up the candidates array directly - reorderCandidates(signatures, candidates, callChainFlags); - if (!candidates.length) { - if (reportErrors) { - diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); - } - return resolveErrorCall(node); + const candidates = candidatesOutArray || []; + // reorderCandidates fills up the candidates array directly + reorderCandidates(signatures, candidates, callChainFlags); + if (!candidates.length) { + if (reportErrors) { + diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); } + return resolveErrorCall(node); + } - const args = getEffectiveCallArguments(node); + const args = getEffectiveCallArguments(node); - // The excludeArgument array contains true for each context sensitive argument (an argument - // is context sensitive it is susceptible to a one-time permanent contextual typing). - // - // The idea is that we will perform type argument inference & assignability checking once - // without using the susceptible parameters that are functions, and once more for those - // parameters, contextually typing each as we go along. - // - // For a tagged template, then the first argument be 'undefined' if necessary because it - // represents a TemplateStringsArray. - // - // For a decorator, no arguments are susceptible to contextual typing due to the fact - // decorators are applied to a declaration by the emitter, and not to an expression. - const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters; - let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal; - argCheckMode |= checkMode & CheckMode.IsForStringLiteralArgumentCompletions; - - // The following variables are captured and modified by calls to chooseOverload. - // If overload resolution or type argument inference fails, we want to report the - // best error possible. The best error is one which says that an argument was not - // assignable to a parameter. This implies that everything else about the overload - // was fine. So if there is any overload that is only incorrect because of an - // argument, we will report an error on that one. - // - // function foo(s: string): void; - // function foo(n: number): void; // Report argument error on this overload - // function foo(): void; - // foo(true); - // - // If none of the overloads even made it that far, there are two possibilities. - // There was a problem with type arguments for some overload, in which case - // report an error on that. Or none of the overloads even had correct arity, - // in which case give an arity error. - // - // function foo(x: T): void; // Report type argument error - // function foo(): void; - // foo(0); - // - let candidatesForArgumentError: Signature[] | undefined; - let candidateForArgumentArityError: Signature | undefined; - let candidateForTypeArgumentError: Signature | undefined; - let result: Signature | undefined; - - // If we are in signature help, a trailing comma indicates that we intend to provide another argument, - // so we will only accept overloads with arity at least 1 higher than the current number of provided arguments. - const signatureHelpTrailingComma = - !!(checkMode & CheckMode.IsForSignatureHelp) && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma; - - // Section 4.12.1: - // if the candidate list contains one or more signatures for which the type of each argument - // expression is a subtype of each corresponding parameter type, the return type of the first - // of those signatures becomes the return type of the function call. - // Otherwise, the return type of the first signature in the candidate list becomes the return - // type of the function call. - // - // Whether the call is an error is determined by assignability of the arguments. The subtype pass - // is just important for choosing the best signature. So in the case where there is only one - // signature, the subtype pass is useless. So skipping it is an optimization. - if (candidates.length > 1) { - result = chooseOverload(candidates, subtypeRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); - } - if (!result) { - result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); - } - if (result) { - return result; - } + // The excludeArgument array contains true for each context sensitive argument (an argument + // is context sensitive it is susceptible to a one-time permanent contextual typing). + // + // The idea is that we will perform type argument inference & assignability checking once + // without using the susceptible parameters that are functions, and once more for those + // parameters, contextually typing each as we go along. + // + // For a tagged template, then the first argument be 'undefined' if necessary because it + // represents a TemplateStringsArray. + // + // For a decorator, no arguments are susceptible to contextual typing due to the fact + // decorators are applied to a declaration by the emitter, and not to an expression. + const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters; + let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal; + argCheckMode |= checkMode & CheckMode.IsForStringLiteralArgumentCompletions; + + // The following variables are captured and modified by calls to chooseOverload. + // If overload resolution or type argument inference fails, we want to report the + // best error possible. The best error is one which says that an argument was not + // assignable to a parameter. This implies that everything else about the overload + // was fine. So if there is any overload that is only incorrect because of an + // argument, we will report an error on that one. + // + // function foo(s: string): void; + // function foo(n: number): void; // Report argument error on this overload + // function foo(): void; + // foo(true); + // + // If none of the overloads even made it that far, there are two possibilities. + // There was a problem with type arguments for some overload, in which case + // report an error on that. Or none of the overloads even had correct arity, + // in which case give an arity error. + // + // function foo(x: T): void; // Report type argument error + // function foo(): void; + // foo(0); + // + let candidatesForArgumentError: Signature[] | undefined; + let candidateForArgumentArityError: Signature | undefined; + let candidateForTypeArgumentError: Signature | undefined; + let result: Signature | undefined; + + // If we are in signature help, a trailing comma indicates that we intend to provide another argument, + // so we will only accept overloads with arity at least 1 higher than the current number of provided arguments. + const signatureHelpTrailingComma = + !!(checkMode & CheckMode.IsForSignatureHelp) && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma; + + // Section 4.12.1: + // if the candidate list contains one or more signatures for which the type of each argument + // expression is a subtype of each corresponding parameter type, the return type of the first + // of those signatures becomes the return type of the function call. + // Otherwise, the return type of the first signature in the candidate list becomes the return + // type of the function call. + // + // Whether the call is an error is determined by assignability of the arguments. The subtype pass + // is just important for choosing the best signature. So in the case where there is only one + // signature, the subtype pass is useless. So skipping it is an optimization. + if (candidates.length > 1) { + result = chooseOverload(candidates, subtypeRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); + } + if (!result) { + result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); + } + if (result) { + return result; + } - result = getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray, checkMode); - // Preemptively cache the result; getResolvedSignature will do this after we return, but - // we need to ensure that the result is present for the error checks below so that if - // this signature is encountered again, we handle the circularity (rather than producing a - // different result which may produce no errors and assert). Callers of getResolvedSignature - // don't hit this issue because they only observe this result after it's had a chance to - // be cached, but the error reporting code below executes before getResolvedSignature sets - // resolvedSignature. - getNodeLinks(node).resolvedSignature = result; - - // No signatures were applicable. Now report errors based on the last applicable signature with - // no arguments excluded from assignability checks. - // If candidate is undefined, it means that no candidates had a suitable arity. In that case, - // skip the checkApplicableSignature check. - if (reportErrors) { - if (candidatesForArgumentError) { - if (candidatesForArgumentError.length === 1 || candidatesForArgumentError.length > 3) { - const last = candidatesForArgumentError[candidatesForArgumentError.length - 1]; - let chain: DiagnosticMessageChain | undefined; - if (candidatesForArgumentError.length > 3) { - chain = chainDiagnosticMessages(chain, Diagnostics.The_last_overload_gave_the_following_error); - chain = chainDiagnosticMessages(chain, Diagnostics.No_overload_matches_this_call); - } - const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain); - if (diags) { - for (const d of diags) { - if (last.declaration && candidatesForArgumentError.length > 3) { - addRelatedInfo(d, createDiagnosticForNode(last.declaration, Diagnostics.The_last_overload_is_declared_here)); - } - addImplementationSuccessElaboration(last, d); - diagnostics.add(d); + result = getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray, checkMode); + // Preemptively cache the result; getResolvedSignature will do this after we return, but + // we need to ensure that the result is present for the error checks below so that if + // this signature is encountered again, we handle the circularity (rather than producing a + // different result which may produce no errors and assert). Callers of getResolvedSignature + // don't hit this issue because they only observe this result after it's had a chance to + // be cached, but the error reporting code below executes before getResolvedSignature sets + // resolvedSignature. + getNodeLinks(node).resolvedSignature = result; + + // No signatures were applicable. Now report errors based on the last applicable signature with + // no arguments excluded from assignability checks. + // If candidate is undefined, it means that no candidates had a suitable arity. In that case, + // skip the checkApplicableSignature check. + if (reportErrors) { + if (candidatesForArgumentError) { + if (candidatesForArgumentError.length === 1 || candidatesForArgumentError.length > 3) { + const last = candidatesForArgumentError[candidatesForArgumentError.length - 1]; + let chain: DiagnosticMessageChain | undefined; + if (candidatesForArgumentError.length > 3) { + chain = chainDiagnosticMessages(chain, Diagnostics.The_last_overload_gave_the_following_error); + chain = chainDiagnosticMessages(chain, Diagnostics.No_overload_matches_this_call); + } + const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain); + if (diags) { + for (const d of diags) { + if (last.declaration && candidatesForArgumentError.length > 3) { + addRelatedInfo(d, createDiagnosticForNode(last.declaration, Diagnostics.The_last_overload_is_declared_here)); } - } - else { - Debug.fail("No error for last overload signature"); + addImplementationSuccessElaboration(last, d); + diagnostics.add(d); } } else { - const allDiagnostics: (readonly DiagnosticRelatedInformation[])[] = []; - let max = 0; - let min = Number.MAX_VALUE; - let minIndex = 0; - let i = 0; - for (const c of candidatesForArgumentError) { - const chain = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Overload_0_of_1_2_gave_the_following_error, i + 1, candidates.length, signatureToString(c)); - const diags = getSignatureApplicabilityError(node, args, c, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, chain); - if (diags) { - if (diags.length <= min) { - min = diags.length; - minIndex = i; - } - max = Math.max(max, diags.length); - allDiagnostics.push(diags); - } - else { - Debug.fail("No error for 3 or fewer overload signatures"); + Debug.fail("No error for last overload signature"); + } + } + else { + const allDiagnostics: (readonly DiagnosticRelatedInformation[])[] = []; + let max = 0; + let min = Number.MAX_VALUE; + let minIndex = 0; + let i = 0; + for (const c of candidatesForArgumentError) { + const chain = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Overload_0_of_1_2_gave_the_following_error, i + 1, candidates.length, signatureToString(c)); + const diags = getSignatureApplicabilityError(node, args, c, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, chain); + if (diags) { + if (diags.length <= min) { + min = diags.length; + minIndex = i; } - i++; - } - - const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics); - Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures"); - const chain = chainDiagnosticMessages( - map(diags, createDiagnosticMessageChainFromDiagnostic), - Diagnostics.No_overload_matches_this_call); - // The below is a spread to guarantee we get a new (mutable) array - our `flatMap` helper tries to do "smart" optimizations where it reuses input - // arrays and the emptyArray singleton where possible, which is decidedly not what we want while we're still constructing this diagnostic - const related = [...flatMap(diags, d => (d as Diagnostic).relatedInformation) as DiagnosticRelatedInformation[]]; - let diag: Diagnostic; - if (every(diags, d => d.start === diags[0].start && d.length === diags[0].length && d.file === diags[0].file)) { - const { file, start, length } = diags[0]; - diag = { file, start, length, code: chain.code, category: chain.category, messageText: chain, relatedInformation: related }; + max = Math.max(max, diags.length); + allDiagnostics.push(diags); } else { - diag = createDiagnosticForNodeFromMessageChain(node, chain, related); + Debug.fail("No error for 3 or fewer overload signatures"); } - addImplementationSuccessElaboration(candidatesForArgumentError[0], diag); - diagnostics.add(diag); + i++; + } + + const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics); + Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures"); + const chain = chainDiagnosticMessages( + map(diags, createDiagnosticMessageChainFromDiagnostic), + Diagnostics.No_overload_matches_this_call); + // The below is a spread to guarantee we get a new (mutable) array - our `flatMap` helper tries to do "smart" optimizations where it reuses input + // arrays and the emptyArray singleton where possible, which is decidedly not what we want while we're still constructing this diagnostic + const related = [...flatMap(diags, d => (d as Diagnostic).relatedInformation) as DiagnosticRelatedInformation[]]; + let diag: Diagnostic; + if (every(diags, d => d.start === diags[0].start && d.length === diags[0].length && d.file === diags[0].file)) { + const { file, start, length } = diags[0]; + diag = { file, start, length, code: chain.code, category: chain.category, messageText: chain, relatedInformation: related }; + } + else { + diag = createDiagnosticForNodeFromMessageChain(node, chain, related); } + addImplementationSuccessElaboration(candidatesForArgumentError[0], diag); + diagnostics.add(diag); } - else if (candidateForArgumentArityError) { - diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args)); + } + else if (candidateForArgumentArityError) { + diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args)); + } + else if (candidateForTypeArgumentError) { + checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, fallbackError); + } + else { + const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); + if (signaturesWithCorrectTypeArgumentArity.length === 0) { + diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!)); } - else if (candidateForTypeArgumentError) { - checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, fallbackError); + else if (!isDecorator) { + diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args)); } - else { - const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); - if (signaturesWithCorrectTypeArgumentArity.length === 0) { - diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!)); - } - else if (!isDecorator) { - diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args)); - } - else if (fallbackError) { - diagnostics.add(getDiagnosticForCallNode(node, fallbackError)); - } + else if (fallbackError) { + diagnostics.add(getDiagnosticForCallNode(node, fallbackError)); } } + } - return result; + return result; - function addImplementationSuccessElaboration(failed: Signature, diagnostic: Diagnostic) { - const oldCandidatesForArgumentError = candidatesForArgumentError; - const oldCandidateForArgumentArityError = candidateForArgumentArityError; - const oldCandidateForTypeArgumentError = candidateForTypeArgumentError; + function addImplementationSuccessElaboration(failed: Signature, diagnostic: Diagnostic) { + const oldCandidatesForArgumentError = candidatesForArgumentError; + const oldCandidateForArgumentArityError = candidateForArgumentArityError; + const oldCandidateForTypeArgumentError = candidateForTypeArgumentError; - const failedSignatureDeclarations = failed.declaration?.symbol?.declarations || emptyArray; - const isOverload = failedSignatureDeclarations.length > 1; - const implDecl = isOverload ? find(failedSignatureDeclarations, d => isFunctionLikeDeclaration(d) && nodeIsPresent(d.body)) : undefined; - if (implDecl) { - const candidate = getSignatureFromDeclaration(implDecl as FunctionLikeDeclaration); - const isSingleNonGenericCandidate = !candidate.typeParameters; - if (chooseOverload([candidate], assignableRelation, isSingleNonGenericCandidate)) { - addRelatedInfo(diagnostic, createDiagnosticForNode(implDecl, Diagnostics.The_call_would_have_succeeded_against_this_implementation_but_implementation_signatures_of_overloads_are_not_externally_visible)); - } + const failedSignatureDeclarations = failed.declaration?.symbol?.declarations || emptyArray; + const isOverload = failedSignatureDeclarations.length > 1; + const implDecl = isOverload ? find(failedSignatureDeclarations, d => isFunctionLikeDeclaration(d) && nodeIsPresent(d.body)) : undefined; + if (implDecl) { + const candidate = getSignatureFromDeclaration(implDecl as FunctionLikeDeclaration); + const isSingleNonGenericCandidate = !candidate.typeParameters; + if (chooseOverload([candidate], assignableRelation, isSingleNonGenericCandidate)) { + addRelatedInfo(diagnostic, createDiagnosticForNode(implDecl, Diagnostics.The_call_would_have_succeeded_against_this_implementation_but_implementation_signatures_of_overloads_are_not_externally_visible)); } - - candidatesForArgumentError = oldCandidatesForArgumentError; - candidateForArgumentArityError = oldCandidateForArgumentArityError; - candidateForTypeArgumentError = oldCandidateForTypeArgumentError; } - function chooseOverload(candidates: Signature[], relation: ESMap, isSingleNonGenericCandidate: boolean, signatureHelpTrailingComma = false) { - candidatesForArgumentError = undefined; - candidateForArgumentArityError = undefined; - candidateForTypeArgumentError = undefined; + candidatesForArgumentError = oldCandidatesForArgumentError; + candidateForArgumentArityError = oldCandidateForArgumentArityError; + candidateForTypeArgumentError = oldCandidateForTypeArgumentError; + } - if (isSingleNonGenericCandidate) { - const candidate = candidates[0]; - if (some(typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { - return undefined; - } - if (getSignatureApplicabilityError(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { - candidatesForArgumentError = [candidate]; - return undefined; - } - return candidate; + function chooseOverload(candidates: Signature[], relation: ESMap, isSingleNonGenericCandidate: boolean, signatureHelpTrailingComma = false) { + candidatesForArgumentError = undefined; + candidateForArgumentArityError = undefined; + candidateForTypeArgumentError = undefined; + + if (isSingleNonGenericCandidate) { + const candidate = candidates[0]; + if (some(typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { + return undefined; } + if (getSignatureApplicabilityError(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { + candidatesForArgumentError = [candidate]; + return undefined; + } + return candidate; + } - for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { - const candidate = candidates[candidateIndex]; - if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { - continue; - } + for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { + const candidate = candidates[candidateIndex]; + if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { + continue; + } - let checkCandidate: Signature; - let inferenceContext: InferenceContext | undefined; + let checkCandidate: Signature; + let inferenceContext: InferenceContext | undefined; - if (candidate.typeParameters) { - let typeArgumentTypes: Type[] | undefined; - if (some(typeArguments)) { - typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false); - if (!typeArgumentTypes) { - candidateForTypeArgumentError = candidate; - continue; - } - } - else { - inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); - typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext); - argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal; + if (candidate.typeParameters) { + let typeArgumentTypes: Type[] | undefined; + if (some(typeArguments)) { + typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false); + if (!typeArgumentTypes) { + candidateForTypeArgumentError = candidate; + continue; } - checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); + } + else { + inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); + typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext); + argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal; + } + checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); + // If the original signature has a generic rest type, instantiation may produce a + // signature with different arity and we need to perform another arity check. + if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { + candidateForArgumentArityError = checkCandidate; + continue; + } + } + else { + checkCandidate = candidate; + } + if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { + // Give preference to error candidates that have no rest parameters (as they are more specific) + (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); + continue; + } + if (argCheckMode) { + // If one or more context sensitive arguments were excluded, we start including + // them now (and keeping do so for any subsequent candidates) and perform a second + // round of type inference and applicability checking for this particular candidate. + argCheckMode = checkMode & CheckMode.IsForStringLiteralArgumentCompletions; + if (inferenceContext) { + const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext); + checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext.inferredTypeParameters); // If the original signature has a generic rest type, instantiation may produce a // signature with different arity and we need to perform another arity check. if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { @@ -31378,14808 +31608,14786 @@ namespace ts { continue; } } - else { - checkCandidate = candidate; - } if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { // Give preference to error candidates that have no rest parameters (as they are more specific) (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); continue; } - if (argCheckMode) { - // If one or more context sensitive arguments were excluded, we start including - // them now (and keeping do so for any subsequent candidates) and perform a second - // round of type inference and applicability checking for this particular candidate. - argCheckMode = checkMode & CheckMode.IsForStringLiteralArgumentCompletions; - if (inferenceContext) { - const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext); - checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext.inferredTypeParameters); - // If the original signature has a generic rest type, instantiation may produce a - // signature with different arity and we need to perform another arity check. - if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { - candidateForArgumentArityError = checkCandidate; - continue; - } - } - if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { - // Give preference to error candidates that have no rest parameters (as they are more specific) - (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); - continue; - } - } - candidates[candidateIndex] = checkCandidate; - return checkCandidate; } - - return undefined; + candidates[candidateIndex] = checkCandidate; + return checkCandidate; } - } - // No signature was applicable. We have already reported the errors for the invalid signature. - function getCandidateForOverloadFailure( - node: CallLikeExpression, - candidates: Signature[], - args: readonly Expression[], - hasCandidatesOutArray: boolean, - checkMode: CheckMode, - ): Signature { - Debug.assert(candidates.length > 0); // Else should not have called this. - checkNodeDeferred(node); - // Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine. - // Don't do this if there is a `candidatesOutArray`, - // because then we want the chosen best candidate to be one of the overloads, not a combination. - return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters) - ? pickLongestCandidateSignature(node, candidates, args, checkMode) - : createUnionOfSignaturesForOverloadFailure(candidates); + return undefined; } + } - function createUnionOfSignaturesForOverloadFailure(candidates: readonly Signature[]): Signature { - const thisParameters = mapDefined(candidates, c => c.thisParameter); - let thisParameter: Symbol | undefined; - if (thisParameters.length) { - thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter)); - } - const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); - const parameters: Symbol[] = []; - for (let i = 0; i < maxNonRestParam; i++) { - const symbols = mapDefined(candidates, s => signatureHasRestParameter(s) ? - i < s.parameters.length - 1 ? s.parameters[i] : last(s.parameters) : - i < s.parameters.length ? s.parameters[i] : undefined); - Debug.assert(symbols.length !== 0); - parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i)))); - } - const restParameterSymbols = mapDefined(candidates, c => signatureHasRestParameter(c) ? last(c.parameters) : undefined); - let flags = SignatureFlags.None; - if (restParameterSymbols.length !== 0) { - const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype)); - parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type)); - flags |= SignatureFlags.HasRestParameter; - } - if (candidates.some(signatureHasLiteralTypes)) { - flags |= SignatureFlags.HasLiteralTypes; - } - return createSignature( - candidates[0].declaration, - /*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`. - thisParameter, - parameters, - /*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)), - /*typePredicate*/ undefined, - minArgumentCount, - flags); - } + // No signature was applicable. We have already reported the errors for the invalid signature. + function getCandidateForOverloadFailure( + node: CallLikeExpression, + candidates: Signature[], + args: readonly Expression[], + hasCandidatesOutArray: boolean, + checkMode: CheckMode, + ): Signature { + Debug.assert(candidates.length > 0); // Else should not have called this. + checkNodeDeferred(node); + // Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine. + // Don't do this if there is a `candidatesOutArray`, + // because then we want the chosen best candidate to be one of the overloads, not a combination. + return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters) + ? pickLongestCandidateSignature(node, candidates, args, checkMode) + : createUnionOfSignaturesForOverloadFailure(candidates); + } - function getNumNonRestParameters(signature: Signature): number { - const numParams = signature.parameters.length; - return signatureHasRestParameter(signature) ? numParams - 1 : numParams; - } + function createUnionOfSignaturesForOverloadFailure(candidates: readonly Signature[]): Signature { + const thisParameters = mapDefined(candidates, c => c.thisParameter); + let thisParameter: Symbol | undefined; + if (thisParameters.length) { + thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter)); + } + const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); + const parameters: Symbol[] = []; + for (let i = 0; i < maxNonRestParam; i++) { + const symbols = mapDefined(candidates, s => signatureHasRestParameter(s) ? + i < s.parameters.length - 1 ? s.parameters[i] : last(s.parameters) : + i < s.parameters.length ? s.parameters[i] : undefined); + Debug.assert(symbols.length !== 0); + parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i)))); + } + const restParameterSymbols = mapDefined(candidates, c => signatureHasRestParameter(c) ? last(c.parameters) : undefined); + let flags = SignatureFlags.None; + if (restParameterSymbols.length !== 0) { + const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype)); + parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type)); + flags |= SignatureFlags.HasRestParameter; + } + if (candidates.some(signatureHasLiteralTypes)) { + flags |= SignatureFlags.HasLiteralTypes; + } + return createSignature( + candidates[0].declaration, + /*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`. + thisParameter, + parameters, + /*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)), + /*typePredicate*/ undefined, + minArgumentCount, + flags); + } - function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol { - return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype)); - } + function getNumNonRestParameters(signature: Signature): number { + const numParams = signature.parameters.length; + return signatureHasRestParameter(signature) ? numParams - 1 : numParams; + } - function createCombinedSymbolForOverloadFailure(sources: readonly Symbol[], type: Type): Symbol { - // This function is currently only used for erroneous overloads, so it's good enough to just use the first source. - return createSymbolWithType(first(sources), type); - } + function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol { + return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype)); + } - function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: readonly Expression[], checkMode: CheckMode): Signature { - // Pick the longest signature. This way we can get a contextual type for cases like: - // declare function f(a: { xa: number; xb: number; }, b: number); - // f({ | - // Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like: - // declare function f(k: keyof T); - // f(" - const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount); - const candidate = candidates[bestIndex]; - const { typeParameters } = candidate; - if (!typeParameters) { - return candidate; - } + function createCombinedSymbolForOverloadFailure(sources: readonly Symbol[], type: Type): Symbol { + // This function is currently only used for erroneous overloads, so it's good enough to just use the first source. + return createSymbolWithType(first(sources), type); + } - const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined; - const instantiated = typeArgumentNodes - ? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node))) - : inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args, checkMode); - candidates[bestIndex] = instantiated; - return instantiated; + function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: readonly Expression[], checkMode: CheckMode): Signature { + // Pick the longest signature. This way we can get a contextual type for cases like: + // declare function f(a: { xa: number; xb: number; }, b: number); + // f({ | + // Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like: + // declare function f(k: keyof T); + // f(" + const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount); + const candidate = candidates[bestIndex]; + const { typeParameters } = candidate; + if (!typeParameters) { + return candidate; + } + + const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined; + const instantiated = typeArgumentNodes + ? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node))) + : inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args, checkMode); + candidates[bestIndex] = instantiated; + return instantiated; + } + + function getTypeArgumentsFromNodes(typeArgumentNodes: readonly TypeNode[], typeParameters: readonly TypeParameter[], isJs: boolean): readonly Type[] { + const typeArguments = typeArgumentNodes.map(getTypeOfNode); + while (typeArguments.length > typeParameters.length) { + typeArguments.pop(); } + while (typeArguments.length < typeParameters.length) { + typeArguments.push(getDefaultFromTypeParameter(typeParameters[typeArguments.length]) || getConstraintOfTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isJs)); + } + return typeArguments; + } + + function inferSignatureInstantiationForOverloadFailure(node: CallLikeExpression, typeParameters: readonly TypeParameter[], candidate: Signature, args: readonly Expression[], checkMode: CheckMode): Signature { + const inferenceContext = createInferenceContext(typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); + const typeArgumentTypes = inferTypeArguments(node, candidate, args, checkMode | CheckMode.SkipContextSensitive | CheckMode.SkipGenericFunctions, inferenceContext); + return createSignatureInstantiation(candidate, typeArgumentTypes); + } - function getTypeArgumentsFromNodes(typeArgumentNodes: readonly TypeNode[], typeParameters: readonly TypeParameter[], isJs: boolean): readonly Type[] { - const typeArguments = typeArgumentNodes.map(getTypeOfNode); - while (typeArguments.length > typeParameters.length) { - typeArguments.pop(); + function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number { + let maxParamsIndex = -1; + let maxParams = -1; + + for (let i = 0; i < candidates.length; i++) { + const candidate = candidates[i]; + const paramCount = getParameterCount(candidate); + if (hasEffectiveRestParameter(candidate) || paramCount >= argsCount) { + return i; } - while (typeArguments.length < typeParameters.length) { - typeArguments.push(getDefaultFromTypeParameter(typeParameters[typeArguments.length]) || getConstraintOfTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isJs)); + if (paramCount > maxParams) { + maxParams = paramCount; + maxParamsIndex = i; } - return typeArguments; - } - - function inferSignatureInstantiationForOverloadFailure(node: CallLikeExpression, typeParameters: readonly TypeParameter[], candidate: Signature, args: readonly Expression[], checkMode: CheckMode): Signature { - const inferenceContext = createInferenceContext(typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); - const typeArgumentTypes = inferTypeArguments(node, candidate, args, checkMode | CheckMode.SkipContextSensitive | CheckMode.SkipGenericFunctions, inferenceContext); - return createSignatureInstantiation(candidate, typeArgumentTypes); } - function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number { - let maxParamsIndex = -1; - let maxParams = -1; + return maxParamsIndex; + } - for (let i = 0; i < candidates.length; i++) { - const candidate = candidates[i]; - const paramCount = getParameterCount(candidate); - if (hasEffectiveRestParameter(candidate) || paramCount >= argsCount) { - return i; + function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + if (node.expression.kind === SyntaxKind.SuperKeyword) { + const superType = checkSuperExpression(node.expression); + if (isTypeAny(superType)) { + for (const arg of node.arguments) { + checkExpression(arg); // Still visit arguments so they get marked for visibility, etc } - if (paramCount > maxParams) { - maxParams = paramCount; - maxParamsIndex = i; + return anySignature; + } + if (!isErrorType(superType)) { + // In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated + // with the type arguments specified in the extends clause. + const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!); + if (baseTypeNode) { + const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode); + return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None); } } + return resolveUntypedCall(node); + } - return maxParamsIndex; + let callChainFlags: SignatureFlags; + let funcType = checkExpression(node.expression); + if (isCallChain(node)) { + const nonOptionalType = getOptionalExpressionType(funcType, node.expression); + callChainFlags = nonOptionalType === funcType ? SignatureFlags.None : + isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain : + SignatureFlags.IsInnerCallChain; + funcType = nonOptionalType; + } + else { + callChainFlags = SignatureFlags.None; } - function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - if (node.expression.kind === SyntaxKind.SuperKeyword) { - const superType = checkSuperExpression(node.expression); - if (isTypeAny(superType)) { - for (const arg of node.arguments) { - checkExpression(arg); // Still visit arguments so they get marked for visibility, etc - } - return anySignature; - } - if (!isErrorType(superType)) { - // In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated - // with the type arguments specified in the extends clause. - const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!); - if (baseTypeNode) { - const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode); - return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None); - } - } - return resolveUntypedCall(node); - } + funcType = checkNonNullTypeWithReporter( + funcType, + node.expression, + reportCannotInvokePossiblyNullOrUndefinedError + ); + + if (funcType === silentNeverType) { + return silentNeverSignature; + } + + const apparentType = getApparentType(funcType); + if (isErrorType(apparentType)) { + // Another error has already been reported + return resolveErrorCall(node); + } - let callChainFlags: SignatureFlags; - let funcType = checkExpression(node.expression); - if (isCallChain(node)) { - const nonOptionalType = getOptionalExpressionType(funcType, node.expression); - callChainFlags = nonOptionalType === funcType ? SignatureFlags.None : - isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain : - SignatureFlags.IsInnerCallChain; - funcType = nonOptionalType; + // Technically, this signatures list may be incomplete. We are taking the apparent type, + // but we are not including call signatures that may have been added to the Object or + // Function interface, since they have none by default. This is a bit of a leap of faith + // that the user will not add any. + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + + // TS 1.0 Spec: 4.12 + // In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual + // types are provided for the argument expressions, and the result is always of type Any. + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { + // The unknownType indicates that an error already occurred (and was reported). No + // need to report another error in this case. + if (!isErrorType(funcType) && node.typeArguments) { + error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); + } + return resolveUntypedCall(node); + } + // If FuncExpr's apparent type(section 3.8.1) is a function type, the call is a typed function call. + // TypeScript employs overload resolution in typed function calls in order to support functions + // with multiple call signatures. + if (!callSignatures.length) { + if (numConstructSignatures) { + error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); } else { - callChainFlags = SignatureFlags.None; + let relatedInformation: DiagnosticRelatedInformation | undefined; + if (node.arguments.length === 1) { + const text = getSourceFileOfNode(node).text; + if (isLineBreak(text.charCodeAt(skipTrivia(text, node.expression.end, /* stopAfterLineBreak */ true) - 1))) { + relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.Are_you_missing_a_semicolon); + } + } + invocationError(node.expression, apparentType, SignatureKind.Call, relatedInformation); } + return resolveErrorCall(node); + } + // When a call to a generic function is an argument to an outer call to a generic function for which + // inference is in process, we have a choice to make. If the inner call relies on inferences made from + // its contextual type to its return type, deferring the inner call processing allows the best possible + // contextual type to accumulate. But if the outer call relies on inferences made from the return type of + // the inner call, the inner call should be processed early. There's no sure way to know which choice is + // right (only a full unification algorithm can determine that), so we resort to the following heuristic: + // If no type arguments are specified in the inner call and at least one call signature is generic and + // returns a function type, we choose to defer processing. This narrowly permits function composition + // operators to flow inferences through return types, but otherwise processes calls right away. We + // use the resolvingSignature singleton to indicate that we deferred processing. This result will be + // propagated out and eventually turned into silentNeverType (a type that is assignable to anything and + // from which we never make inferences). + if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) { + skippedGenericFunction(node, checkMode); + return resolvingSignature; + } + // If the function is explicitly marked with `@class`, then it must be constructed. + if (callSignatures.some(sig => isInJSFile(sig.declaration) && !!getJSDocClassTag(sig.declaration!))) { + error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); + return resolveErrorCall(node); + } - funcType = checkNonNullTypeWithReporter( - funcType, - node.expression, - reportCannotInvokePossiblyNullOrUndefinedError - ); - - if (funcType === silentNeverType) { - return silentNeverSignature; - } + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags); + } - const apparentType = getApparentType(funcType); - if (isErrorType(apparentType)) { - // Another error has already been reported - return resolveErrorCall(node); - } + function isGenericFunctionReturningFunction(signature: Signature) { + return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature))); + } - // Technically, this signatures list may be incomplete. We are taking the apparent type, - // but we are not including call signatures that may have been added to the Object or - // Function interface, since they have none by default. This is a bit of a leap of faith - // that the user will not add any. - const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + /** + * TS 1.0 spec: 4.12 + * If FuncExpr is of type Any, or of an object type that has no call or construct signatures + * but is a subtype of the Function interface, the call is an untyped function call. + */ + function isUntypedFunctionCall(funcType: Type, apparentFuncType: Type, numCallSignatures: number, numConstructSignatures: number): boolean { + // We exclude union types because we may have a union of function types that happen to have no common signatures. + return isTypeAny(funcType) || isTypeAny(apparentFuncType) && !!(funcType.flags & TypeFlags.TypeParameter) || + !numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & TypeFlags.Union) && !(getReducedType(apparentFuncType).flags & TypeFlags.Never) && isTypeAssignableTo(funcType, globalFunctionType); + } - // TS 1.0 Spec: 4.12 - // In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual - // types are provided for the argument expressions, and the result is always of type Any. - if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { - // The unknownType indicates that an error already occurred (and was reported). No - // need to report another error in this case. - if (!isErrorType(funcType) && node.typeArguments) { - error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); - } - return resolveUntypedCall(node); - } - // If FuncExpr's apparent type(section 3.8.1) is a function type, the call is a typed function call. - // TypeScript employs overload resolution in typed function calls in order to support functions - // with multiple call signatures. - if (!callSignatures.length) { - if (numConstructSignatures) { - error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); - } - else { - let relatedInformation: DiagnosticRelatedInformation | undefined; - if (node.arguments.length === 1) { - const text = getSourceFileOfNode(node).text; - if (isLineBreak(text.charCodeAt(skipTrivia(text, node.expression.end, /* stopAfterLineBreak */ true) - 1))) { - relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.Are_you_missing_a_semicolon); - } - } - invocationError(node.expression, apparentType, SignatureKind.Call, relatedInformation); - } - return resolveErrorCall(node); - } - // When a call to a generic function is an argument to an outer call to a generic function for which - // inference is in process, we have a choice to make. If the inner call relies on inferences made from - // its contextual type to its return type, deferring the inner call processing allows the best possible - // contextual type to accumulate. But if the outer call relies on inferences made from the return type of - // the inner call, the inner call should be processed early. There's no sure way to know which choice is - // right (only a full unification algorithm can determine that), so we resort to the following heuristic: - // If no type arguments are specified in the inner call and at least one call signature is generic and - // returns a function type, we choose to defer processing. This narrowly permits function composition - // operators to flow inferences through return types, but otherwise processes calls right away. We - // use the resolvingSignature singleton to indicate that we deferred processing. This result will be - // propagated out and eventually turned into silentNeverType (a type that is assignable to anything and - // from which we never make inferences). - if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) { - skippedGenericFunction(node, checkMode); - return resolvingSignature; - } - // If the function is explicitly marked with `@class`, then it must be constructed. - if (callSignatures.some(sig => isInJSFile(sig.declaration) && !!getJSDocClassTag(sig.declaration!))) { - error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); - return resolveErrorCall(node); + function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + if (node.arguments && languageVersion < ScriptTarget.ES5) { + const spreadIndex = getSpreadArgumentIndex(node.arguments); + if (spreadIndex >= 0) { + error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_5_and_higher); } - - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags); } - function isGenericFunctionReturningFunction(signature: Signature) { - return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature))); + let expressionType = checkNonNullExpression(node.expression); + if (expressionType === silentNeverType) { + return silentNeverSignature; } - /** - * TS 1.0 spec: 4.12 - * If FuncExpr is of type Any, or of an object type that has no call or construct signatures - * but is a subtype of the Function interface, the call is an untyped function call. - */ - function isUntypedFunctionCall(funcType: Type, apparentFuncType: Type, numCallSignatures: number, numConstructSignatures: number): boolean { - // We exclude union types because we may have a union of function types that happen to have no common signatures. - return isTypeAny(funcType) || isTypeAny(apparentFuncType) && !!(funcType.flags & TypeFlags.TypeParameter) || - !numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & TypeFlags.Union) && !(getReducedType(apparentFuncType).flags & TypeFlags.Never) && isTypeAssignableTo(funcType, globalFunctionType); + // If expressionType's apparent type(section 3.8.1) is an object type with one or + // more construct signatures, the expression is processed in the same manner as a + // function call, but using the construct signatures as the initial set of candidate + // signatures for overload resolution. The result type of the function call becomes + // the result type of the operation. + expressionType = getApparentType(expressionType); + if (isErrorType(expressionType)) { + // Another error has already been reported + return resolveErrorCall(node); } - function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - if (node.arguments && languageVersion < ScriptTarget.ES5) { - const spreadIndex = getSpreadArgumentIndex(node.arguments); - if (spreadIndex >= 0) { - error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_5_and_higher); - } + // TS 1.0 spec: 4.11 + // If expressionType is of type Any, Args can be any argument + // list and the result of the operation is of type Any. + if (isTypeAny(expressionType)) { + if (node.typeArguments) { + error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); } + return resolveUntypedCall(node); + } - let expressionType = checkNonNullExpression(node.expression); - if (expressionType === silentNeverType) { - return silentNeverSignature; + // Technically, this signatures list may be incomplete. We are taking the apparent type, + // but we are not including construct signatures that may have been added to the Object or + // Function interface, since they have none by default. This is a bit of a leap of faith + // that the user will not add any. + const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct); + if (constructSignatures.length) { + if (!isConstructorAccessible(node, constructSignatures[0])) { + return resolveErrorCall(node); } - - // If expressionType's apparent type(section 3.8.1) is an object type with one or - // more construct signatures, the expression is processed in the same manner as a - // function call, but using the construct signatures as the initial set of candidate - // signatures for overload resolution. The result type of the function call becomes - // the result type of the operation. - expressionType = getApparentType(expressionType); - if (isErrorType(expressionType)) { - // Another error has already been reported + // If the expression is a class of abstract type, or an abstract construct signature, + // then it cannot be instantiated. + // In the case of a merged class-module or class-interface declaration, + // only the class declaration node will have the Abstract flag set. + if (someSignature(constructSignatures, signature => !!(signature.flags & SignatureFlags.Abstract))) { + error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class); return resolveErrorCall(node); } - - // TS 1.0 spec: 4.11 - // If expressionType is of type Any, Args can be any argument - // list and the result of the operation is of type Any. - if (isTypeAny(expressionType)) { - if (node.typeArguments) { - error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); - } - return resolveUntypedCall(node); + const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol); + if (valueDecl && hasSyntacticModifier(valueDecl, ModifierFlags.Abstract)) { + error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class); + return resolveErrorCall(node); } - // Technically, this signatures list may be incomplete. We are taking the apparent type, - // but we are not including construct signatures that may have been added to the Object or - // Function interface, since they have none by default. This is a bit of a leap of faith - // that the user will not add any. - const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct); - if (constructSignatures.length) { - if (!isConstructorAccessible(node, constructSignatures[0])) { - return resolveErrorCall(node); - } - // If the expression is a class of abstract type, or an abstract construct signature, - // then it cannot be instantiated. - // In the case of a merged class-module or class-interface declaration, - // only the class declaration node will have the Abstract flag set. - if (someSignature(constructSignatures, signature => !!(signature.flags & SignatureFlags.Abstract))) { - error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class); - return resolveErrorCall(node); + return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + + // If expressionType's apparent type is an object type with no construct signatures but + // one or more call signatures, the expression is processed as a function call. A compile-time + // error occurs if the result of the function call is not Void. The type of the result of the + // operation is Any. It is an error to have a Void this type. + const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); + if (callSignatures.length) { + const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + if (!noImplicitAny) { + if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { + error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); } - const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol); - if (valueDecl && hasSyntacticModifier(valueDecl, ModifierFlags.Abstract)) { - error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class); - return resolveErrorCall(node); + if (getThisTypeOfSignature(signature) === voidType) { + error(node, Diagnostics.A_function_that_is_called_with_the_new_keyword_cannot_have_a_this_type_that_is_void); } - - return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None); } + return signature; + } - // If expressionType's apparent type is an object type with no construct signatures but - // one or more call signatures, the expression is processed as a function call. A compile-time - // error occurs if the result of the function call is not Void. The type of the result of the - // operation is Any. It is an error to have a Void this type. - const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); - if (callSignatures.length) { - const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); - if (!noImplicitAny) { - if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { - error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); - } - if (getThisTypeOfSignature(signature) === voidType) { - error(node, Diagnostics.A_function_that_is_called_with_the_new_keyword_cannot_have_a_this_type_that_is_void); - } - } - return signature; - } + invocationError(node.expression, expressionType, SignatureKind.Construct); + return resolveErrorCall(node); + } - invocationError(node.expression, expressionType, SignatureKind.Construct); - return resolveErrorCall(node); + function someSignature(signatures: Signature | readonly Signature[], f: (s: Signature) => boolean): boolean { + if (isArray(signatures)) { + return some(signatures, signature => someSignature(signature, f)); } + return signatures.compositeKind === TypeFlags.Union ? some(signatures.compositeSignatures, f) : f(signatures); + } - function someSignature(signatures: Signature | readonly Signature[], f: (s: Signature) => boolean): boolean { - if (isArray(signatures)) { - return some(signatures, signature => someSignature(signature, f)); - } - return signatures.compositeKind === TypeFlags.Union ? some(signatures.compositeSignatures, f) : f(signatures); + function typeHasProtectedAccessibleBase(target: Symbol, type: InterfaceType): boolean { + const baseTypes = getBaseTypes(type); + if (!length(baseTypes)) { + return false; } - - function typeHasProtectedAccessibleBase(target: Symbol, type: InterfaceType): boolean { - const baseTypes = getBaseTypes(type); - if (!length(baseTypes)) { - return false; - } - const firstBase = baseTypes[0]; - if (firstBase.flags & TypeFlags.Intersection) { - const types = (firstBase as IntersectionType).types; - const mixinFlags = findMixins(types); - let i = 0; - for (const intersectionMember of (firstBase as IntersectionType).types) { - // We want to ignore mixin ctors - if (!mixinFlags[i]) { - if (getObjectFlags(intersectionMember) & (ObjectFlags.Class | ObjectFlags.Interface)) { - if (intersectionMember.symbol === target) { - return true; - } - if (typeHasProtectedAccessibleBase(target, intersectionMember as InterfaceType)) { - return true; - } + const firstBase = baseTypes[0]; + if (firstBase.flags & TypeFlags.Intersection) { + const types = (firstBase as IntersectionType).types; + const mixinFlags = findMixins(types); + let i = 0; + for (const intersectionMember of (firstBase as IntersectionType).types) { + // We want to ignore mixin ctors + if (!mixinFlags[i]) { + if (getObjectFlags(intersectionMember) & (ObjectFlags.Class | ObjectFlags.Interface)) { + if (intersectionMember.symbol === target) { + return true; + } + if (typeHasProtectedAccessibleBase(target, intersectionMember as InterfaceType)) { + return true; } } - i++; } - return false; + i++; } - if (firstBase.symbol === target) { - return true; - } - return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType); + return false; + } + if (firstBase.symbol === target) { + return true; } + return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType); + } - function isConstructorAccessible(node: NewExpression, signature: Signature) { - if (!signature || !signature.declaration) { - return true; - } + function isConstructorAccessible(node: NewExpression, signature: Signature) { + if (!signature || !signature.declaration) { + return true; + } - const declaration = signature.declaration; - const modifiers = getSelectedEffectiveModifierFlags(declaration, ModifierFlags.NonPublicAccessibilityModifier); + const declaration = signature.declaration; + const modifiers = getSelectedEffectiveModifierFlags(declaration, ModifierFlags.NonPublicAccessibilityModifier); - // (1) Public constructors and (2) constructor functions are always accessible. - if (!modifiers || declaration.kind !== SyntaxKind.Constructor) { - return true; - } + // (1) Public constructors and (2) constructor functions are always accessible. + if (!modifiers || declaration.kind !== SyntaxKind.Constructor) { + return true; + } - const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(declaration.parent.symbol)!; - const declaringClass = getDeclaredTypeOfSymbol(declaration.parent.symbol) as InterfaceType; + const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(declaration.parent.symbol)!; + const declaringClass = getDeclaredTypeOfSymbol(declaration.parent.symbol) as InterfaceType; - // A private or protected constructor can only be instantiated within its own class (or a subclass, for protected) - if (!isNodeWithinClass(node, declaringClassDeclaration)) { - const containingClass = getContainingClass(node); - if (containingClass && modifiers & ModifierFlags.Protected) { - const containingType = getTypeOfNode(containingClass); - if (typeHasProtectedAccessibleBase(declaration.parent.symbol, containingType as InterfaceType)) { - return true; - } - } - if (modifiers & ModifierFlags.Private) { - error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); - } - if (modifiers & ModifierFlags.Protected) { - error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); + // A private or protected constructor can only be instantiated within its own class (or a subclass, for protected) + if (!isNodeWithinClass(node, declaringClassDeclaration)) { + const containingClass = getContainingClass(node); + if (containingClass && modifiers & ModifierFlags.Protected) { + const containingType = getTypeOfNode(containingClass); + if (typeHasProtectedAccessibleBase(declaration.parent.symbol, containingType as InterfaceType)) { + return true; } - return false; } - - return true; + if (modifiers & ModifierFlags.Private) { + error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); + } + if (modifiers & ModifierFlags.Protected) { + error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); + } + return false; } - function invocationErrorDetails(errorTarget: Node, apparentType: Type, kind: SignatureKind): { messageChain: DiagnosticMessageChain, relatedMessage: DiagnosticMessage | undefined } { - let errorInfo: DiagnosticMessageChain | undefined; - const isCall = kind === SignatureKind.Call; - const awaitedType = getAwaitedType(apparentType); - const maybeMissingAwait = awaitedType && getSignaturesOfType(awaitedType, kind).length > 0; - 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; - } + return true; + } + + function invocationErrorDetails(errorTarget: Node, apparentType: Type, kind: SignatureKind): { messageChain: DiagnosticMessageChain, relatedMessage: DiagnosticMessage | undefined } { + let errorInfo: DiagnosticMessageChain | undefined; + const isCall = kind === SignatureKind.Call; + const awaitedType = getAwaitedType(apparentType); + const maybeMissingAwait = awaitedType && getSignaturesOfType(awaitedType, kind).length > 0; + 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; } } - 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 { + // 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; + } } } - else { + 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.Type_0_has_no_call_signatures : - Diagnostics.Type_0_has_no_construct_signatures, + 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) + ); + } - let headMessage = isCall ? Diagnostics.This_expression_is_not_callable : Diagnostics.This_expression_is_not_constructable; - - // Diagnose get accessors incorrectly called as functions - if (isCallExpression(errorTarget.parent) && errorTarget.parent.arguments.length === 0) { - const { resolvedSymbol } = getNodeLinks(errorTarget); - if (resolvedSymbol && resolvedSymbol.flags & SymbolFlags.GetAccessor) { - headMessage = Diagnostics.This_expression_is_not_callable_because_it_is_a_get_accessor_Did_you_mean_to_use_it_without; - } - } + let headMessage = isCall ? Diagnostics.This_expression_is_not_callable : Diagnostics.This_expression_is_not_constructable; - return { - messageChain: chainDiagnosticMessages(errorInfo, headMessage), - relatedMessage: maybeMissingAwait ? Diagnostics.Did_you_forget_to_use_await : undefined, - }; - } - function invocationError(errorTarget: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) { - const { messageChain, relatedMessage: relatedInfo } = invocationErrorDetails(errorTarget, apparentType, kind); - const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, messageChain); - if (relatedInfo) { - addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo)); + // Diagnose get accessors incorrectly called as functions + if (isCallExpression(errorTarget.parent) && errorTarget.parent.arguments.length === 0) { + const { resolvedSymbol } = getNodeLinks(errorTarget); + if (resolvedSymbol && resolvedSymbol.flags & SymbolFlags.GetAccessor) { + headMessage = Diagnostics.This_expression_is_not_callable_because_it_is_a_get_accessor_Did_you_mean_to_use_it_without; } - 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); } - function invocationErrorRecovery(apparentType: Type, kind: SignatureKind, diagnostic: Diagnostic) { - if (!apparentType.symbol) { - return; - } - const importNode = getSymbolLinks(apparentType.symbol).originatingImport; - // Create a diagnostic on the originating import if possible onto which we can attach a quickfix - // An import call expression cannot be rewritten into another form to correct the error - the only solution is to use `.default` at the use-site - if (importNode && !isImportCall(importNode)) { - const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target!), kind); - if (!sigs || !sigs.length) return; + return { + messageChain: chainDiagnosticMessages(errorInfo, headMessage), + relatedMessage: maybeMissingAwait ? Diagnostics.Did_you_forget_to_use_await : undefined, + }; + } + function invocationError(errorTarget: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) { + const { messageChain, relatedMessage: relatedInfo } = invocationErrorDetails(errorTarget, apparentType, kind); + const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, messageChain); + if (relatedInfo) { + addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo)); + } + 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); + } - addRelatedInfo(diagnostic, - createDiagnosticForNode(importNode, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead) - ); - } + function invocationErrorRecovery(apparentType: Type, kind: SignatureKind, diagnostic: Diagnostic) { + if (!apparentType.symbol) { + return; } + const importNode = getSymbolLinks(apparentType.symbol).originatingImport; + // Create a diagnostic on the originating import if possible onto which we can attach a quickfix + // An import call expression cannot be rewritten into another form to correct the error - the only solution is to use `.default` at the use-site + if (importNode && !isImportCall(importNode)) { + const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target!), kind); + if (!sigs || !sigs.length) return; - function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - const tagType = checkExpression(node.tag); - const apparentType = getApparentType(tagType); + addRelatedInfo(diagnostic, + createDiagnosticForNode(importNode, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead) + ); + } + } - if (isErrorType(apparentType)) { - // Another error has already been reported - return resolveErrorCall(node); - } + function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + const tagType = checkExpression(node.tag); + const apparentType = getApparentType(tagType); - const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + if (isErrorType(apparentType)) { + // Another error has already been reported + return resolveErrorCall(node); + } - if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) { - return resolveUntypedCall(node); - } + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; - if (!callSignatures.length) { - if (isArrayLiteralExpression(node.parent)) { - const diagnostic = createDiagnosticForNode(node.tag, Diagnostics.It_is_likely_that_you_are_missing_a_comma_to_separate_these_two_template_expressions_They_form_a_tagged_template_expression_which_cannot_be_invoked); - diagnostics.add(diagnostic); - return resolveErrorCall(node); - } + if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); + } - invocationError(node.tag, apparentType, SignatureKind.Call); + if (!callSignatures.length) { + if (isArrayLiteralExpression(node.parent)) { + const diagnostic = createDiagnosticForNode(node.tag, Diagnostics.It_is_likely_that_you_are_missing_a_comma_to_separate_these_two_template_expressions_They_form_a_tagged_template_expression_which_cannot_be_invoked); + diagnostics.add(diagnostic); return resolveErrorCall(node); } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + invocationError(node.tag, apparentType, SignatureKind.Call); + return resolveErrorCall(node); } - /** - * Gets the localized diagnostic head message to use for errors when resolving a decorator as a call expression. - */ - function getDiagnosticHeadMessageForDecoratorResolution(node: Decorator) { - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return Diagnostics.Unable_to_resolve_signature_of_class_decorator_when_called_as_an_expression; - - case SyntaxKind.Parameter: - return Diagnostics.Unable_to_resolve_signature_of_parameter_decorator_when_called_as_an_expression; + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + } - case SyntaxKind.PropertyDeclaration: - return Diagnostics.Unable_to_resolve_signature_of_property_decorator_when_called_as_an_expression; + /** + * Gets the localized diagnostic head message to use for errors when resolving a decorator as a call expression. + */ + function getDiagnosticHeadMessageForDecoratorResolution(node: Decorator) { + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return Diagnostics.Unable_to_resolve_signature_of_class_decorator_when_called_as_an_expression; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return Diagnostics.Unable_to_resolve_signature_of_method_decorator_when_called_as_an_expression; + case SyntaxKind.Parameter: + return Diagnostics.Unable_to_resolve_signature_of_parameter_decorator_when_called_as_an_expression; - default: - return Debug.fail(); - } - } + case SyntaxKind.PropertyDeclaration: + return Diagnostics.Unable_to_resolve_signature_of_property_decorator_when_called_as_an_expression; - /** - * Resolves a decorator as if it were a call expression. - */ - function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - const funcType = checkExpression(node.expression); - const apparentType = getApparentType(funcType); - if (isErrorType(apparentType)) { - return resolveErrorCall(node); - } + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return Diagnostics.Unable_to_resolve_signature_of_method_decorator_when_called_as_an_expression; - const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; - if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { - return resolveUntypedCall(node); - } + default: + return Debug.fail(); + } + } - if (isPotentiallyUncalledDecorator(node, callSignatures)) { - const nodeStr = getTextOfNode(node.expression, /*includeTrivia*/ false); - error(node, Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0, nodeStr); - return resolveErrorCall(node); - } + /** + * Resolves a decorator as if it were a call expression. + */ + function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + const funcType = checkExpression(node.expression); + const apparentType = getApparentType(funcType); + if (isErrorType(apparentType)) { + return resolveErrorCall(node); + } - const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); - if (!callSignatures.length) { - const errorDetails = invocationErrorDetails(node.expression, apparentType, SignatureKind.Call); - const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage); - const diag = createDiagnosticForNodeFromMessageChain(node.expression, messageChain); - if (errorDetails.relatedMessage) { - addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage)); - } - diagnostics.add(diag); - invocationErrorRecovery(apparentType, SignatureKind.Call, diag); - return resolveErrorCall(node); - } + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); + } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); + if (isPotentiallyUncalledDecorator(node, callSignatures)) { + const nodeStr = getTextOfNode(node.expression, /*includeTrivia*/ false); + error(node, Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0, nodeStr); + return resolveErrorCall(node); } - function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature { - const namespace = getJsxNamespaceAt(node); - const exports = namespace && getExportsOfSymbol(namespace); - // We fake up a SFC signature for each intrinsic, however a more specific per-element signature drawn from the JSX declaration - // file would probably be preferable. - const typeSymbol = exports && getSymbol(exports, JsxNames.Element, SymbolFlags.Type); - const returnNode = typeSymbol && nodeBuilder.symbolToEntityName(typeSymbol, SymbolFlags.Type, node); - const declaration = factory.createFunctionTypeNode(/*typeParameters*/ undefined, - [factory.createParameterDeclaration(/*modifiers*/ undefined, /*dotdotdot*/ undefined, "props", /*questionMark*/ undefined, nodeBuilder.typeToTypeNode(result, node))], - returnNode ? factory.createTypeReferenceNode(returnNode, /*typeArguments*/ undefined) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ); - const parameterSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "props" as __String); - parameterSymbol.type = result; - return createSignature( - declaration, - /*typeParameters*/ undefined, - /*thisParameter*/ undefined, - [parameterSymbol], - typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, - /*returnTypePredicate*/ undefined, - 1, - SignatureFlags.None - ); + const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); + if (!callSignatures.length) { + const errorDetails = invocationErrorDetails(node.expression, apparentType, SignatureKind.Call); + const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage); + const diag = createDiagnosticForNodeFromMessageChain(node.expression, messageChain); + if (errorDetails.relatedMessage) { + addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage)); + } + diagnostics.add(diag); + invocationErrorRecovery(apparentType, SignatureKind.Call, diag); + return resolveErrorCall(node); } - function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - if (isJsxIntrinsicIdentifier(node.tagName)) { - const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); - const fakeSignature = createSignatureForJSXIntrinsic(node, result); - checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*mapper*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes); - if (length(node.typeArguments)) { - forEach(node.typeArguments, checkSourceElement); - diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), node.typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(node.typeArguments))); - } - return fakeSignature; - } - const exprTypes = checkExpression(node.tagName); - const apparentType = getApparentType(exprTypes); - if (isErrorType(apparentType)) { - return resolveErrorCall(node); - } + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); + } - const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); - if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { - return resolveUntypedCall(node); - } + function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature { + const namespace = getJsxNamespaceAt(node); + const exports = namespace && getExportsOfSymbol(namespace); + // We fake up a SFC signature for each intrinsic, however a more specific per-element signature drawn from the JSX declaration + // file would probably be preferable. + const typeSymbol = exports && getSymbol(exports, JsxNames.Element, SymbolFlags.Type); + const returnNode = typeSymbol && nodeBuilder.symbolToEntityName(typeSymbol, SymbolFlags.Type, node); + const declaration = factory.createFunctionTypeNode(/*typeParameters*/ undefined, + [factory.createParameterDeclaration(/*modifiers*/ undefined, /*dotdotdot*/ undefined, "props", /*questionMark*/ undefined, nodeBuilder.typeToTypeNode(result, node))], + returnNode ? factory.createTypeReferenceNode(returnNode, /*typeArguments*/ undefined) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ); + const parameterSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "props" as __String); + parameterSymbol.type = result; + return createSignature( + declaration, + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [parameterSymbol], + typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, + /*returnTypePredicate*/ undefined, + 1, + SignatureFlags.None + ); + } - if (signatures.length === 0) { - // We found no signatures at all, which is an error - error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); - return resolveErrorCall(node); + function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + if (isJsxIntrinsicIdentifier(node.tagName)) { + const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); + const fakeSignature = createSignatureForJSXIntrinsic(node, result); + checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*mapper*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes); + if (length(node.typeArguments)) { + forEach(node.typeArguments, checkSourceElement); + diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), node.typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(node.typeArguments))); } - - return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None); + return fakeSignature; } - - /** - * Sometimes, we have a decorator that could accept zero arguments, - * but is receiving too many arguments as part of the decorator invocation. - * In those cases, a user may have meant to *call* the expression before using it as a decorator. - */ - function isPotentiallyUncalledDecorator(decorator: Decorator, signatures: readonly Signature[]) { - return signatures.length && every(signatures, signature => - signature.minArgumentCount === 0 && - !signatureHasRestParameter(signature) && - signature.parameters.length < getDecoratorArgumentCount(decorator, signature)); + const exprTypes = checkExpression(node.tagName); + const apparentType = getApparentType(exprTypes); + if (isErrorType(apparentType)) { + return resolveErrorCall(node); } - function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { - switch (node.kind) { - case SyntaxKind.CallExpression: - return resolveCallExpression(node, candidatesOutArray, checkMode); - case SyntaxKind.NewExpression: - return resolveNewExpression(node, candidatesOutArray, checkMode); - case SyntaxKind.TaggedTemplateExpression: - return resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode); - case SyntaxKind.Decorator: - return resolveDecorator(node, candidatesOutArray, checkMode); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxSelfClosingElement: - return resolveJsxOpeningLikeElement(node, candidatesOutArray, checkMode); - } - throw Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable."); + const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); + if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { + return resolveUntypedCall(node); } - /** - * Resolve a signature of a given call-like expression. - * @param node a call-like expression to try resolve a signature for - * @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service; - * the function will fill it up with appropriate candidate signatures - * @return a signature of the call-like expression or undefined if one can't be found - */ - function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, checkMode?: CheckMode): Signature { - const links = getNodeLinks(node); - // If getResolvedSignature has already been called, we will have cached the resolvedSignature. - // However, it is possible that either candidatesOutArray was not passed in the first time, - // or that a different candidatesOutArray was passed in. Therefore, we need to redo the work - // to correctly fill the candidatesOutArray. - const cached = links.resolvedSignature; - if (cached && cached !== resolvingSignature && !candidatesOutArray) { - return cached; - } - links.resolvedSignature = resolvingSignature; - const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal); - // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call - // resolution should be deferred. - if (result !== resolvingSignature) { - // If signature resolution originated in control flow type analysis (for example to compute the - // assigned type in a flow assignment) we don't cache the result as it may be based on temporary - // types from the control flow analysis. - links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached; - } - return result; + if (signatures.length === 0) { + // We found no signatures at all, which is an error + error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); + return resolveErrorCall(node); } - /** - * Indicates whether a declaration can be treated as a constructor in a JavaScript - * file. - */ - function isJSConstructor(node: Node | undefined): node is FunctionDeclaration | FunctionExpression { - if (!node || !isInJSFile(node)) { - return false; - } - const func = isFunctionDeclaration(node) || isFunctionExpression(node) ? node : - (isVariableDeclaration(node) || isPropertyAssignment(node)) && node.initializer && isFunctionExpression(node.initializer) ? node.initializer : - undefined; - if (func) { - // If the node has a @class or @constructor tag, treat it like a constructor. - if (getJSDocClassTag(node)) return true; + return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None); + } - // If the node is a property of an object literal. - if (isPropertyAssignment(walkUpParenthesizedExpressions(func.parent))) return false; + /** + * Sometimes, we have a decorator that could accept zero arguments, + * but is receiving too many arguments as part of the decorator invocation. + * In those cases, a user may have meant to *call* the expression before using it as a decorator. + */ + function isPotentiallyUncalledDecorator(decorator: Decorator, signatures: readonly Signature[]) { + return signatures.length && every(signatures, signature => + signature.minArgumentCount === 0 && + !signatureHasRestParameter(signature) && + signature.parameters.length < getDecoratorArgumentCount(decorator, signature)); + } - // If the symbol of the node has members, treat it like a constructor. - const symbol = getSymbolOfNode(func); - return !!symbol?.members?.size; - } + function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + switch (node.kind) { + case SyntaxKind.CallExpression: + return resolveCallExpression(node, candidatesOutArray, checkMode); + case SyntaxKind.NewExpression: + return resolveNewExpression(node, candidatesOutArray, checkMode); + case SyntaxKind.TaggedTemplateExpression: + return resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode); + case SyntaxKind.Decorator: + return resolveDecorator(node, candidatesOutArray, checkMode); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + return resolveJsxOpeningLikeElement(node, candidatesOutArray, checkMode); + } + throw Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable."); + } + + /** + * Resolve a signature of a given call-like expression. + * @param node a call-like expression to try resolve a signature for + * @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service; + * the function will fill it up with appropriate candidate signatures + * @return a signature of the call-like expression or undefined if one can't be found + */ + function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, checkMode?: CheckMode): Signature { + const links = getNodeLinks(node); + // If getResolvedSignature has already been called, we will have cached the resolvedSignature. + // However, it is possible that either candidatesOutArray was not passed in the first time, + // or that a different candidatesOutArray was passed in. Therefore, we need to redo the work + // to correctly fill the candidatesOutArray. + const cached = links.resolvedSignature; + if (cached && cached !== resolvingSignature && !candidatesOutArray) { + return cached; + } + links.resolvedSignature = resolvingSignature; + const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal); + // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call + // resolution should be deferred. + if (result !== resolvingSignature) { + // If signature resolution originated in control flow type analysis (for example to compute the + // assigned type in a flow assignment) we don't cache the result as it may be based on temporary + // types from the control flow analysis. + links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached; + } + return result; + } + + /** + * Indicates whether a declaration can be treated as a constructor in a JavaScript + * file. + */ + function isJSConstructor(node: Node | undefined): node is FunctionDeclaration | FunctionExpression { + if (!node || !isInJSFile(node)) { return false; } + const func = isFunctionDeclaration(node) || isFunctionExpression(node) ? node : + (isVariableDeclaration(node) || isPropertyAssignment(node)) && node.initializer && isFunctionExpression(node.initializer) ? node.initializer : + undefined; + if (func) { + // If the node has a @class or @constructor tag, treat it like a constructor. + if (getJSDocClassTag(node)) return true; - function mergeJSSymbols(target: Symbol, source: Symbol | undefined) { - if (source) { - const links = getSymbolLinks(source); - if (!links.inferredClassSymbol || !links.inferredClassSymbol.has(getSymbolId(target))) { - const inferred = isTransientSymbol(target) ? target : cloneSymbol(target) as TransientSymbol; - inferred.exports = inferred.exports || createSymbolTable(); - inferred.members = inferred.members || createSymbolTable(); - inferred.flags |= source.flags & SymbolFlags.Class; - if (source.exports?.size) { - mergeSymbolTable(inferred.exports, source.exports); - } - if (source.members?.size) { - mergeSymbolTable(inferred.members, source.members); - } - (links.inferredClassSymbol || (links.inferredClassSymbol = new Map())).set(getSymbolId(inferred), inferred); - return inferred; + // If the node is a property of an object literal. + if (isPropertyAssignment(walkUpParenthesizedExpressions(func.parent))) return false; + + // If the symbol of the node has members, treat it like a constructor. + const symbol = getSymbolOfNode(func); + return !!symbol?.members?.size; + } + return false; + } + + function mergeJSSymbols(target: Symbol, source: Symbol | undefined) { + if (source) { + const links = getSymbolLinks(source); + if (!links.inferredClassSymbol || !links.inferredClassSymbol.has(getSymbolId(target))) { + const inferred = isTransientSymbol(target) ? target : cloneSymbol(target) as TransientSymbol; + inferred.exports = inferred.exports || createSymbolTable(); + inferred.members = inferred.members || createSymbolTable(); + inferred.flags |= source.flags & SymbolFlags.Class; + if (source.exports?.size) { + mergeSymbolTable(inferred.exports, source.exports); } - return links.inferredClassSymbol.get(getSymbolId(target)); + if (source.members?.size) { + mergeSymbolTable(inferred.members, source.members); + } + (links.inferredClassSymbol || (links.inferredClassSymbol = new Map())).set(getSymbolId(inferred), inferred); + return inferred; } + return links.inferredClassSymbol.get(getSymbolId(target)); } + } - function getAssignedClassSymbol(decl: Declaration): Symbol | undefined { - const assignmentSymbol = decl && getSymbolOfExpando(decl, /*allowDeclaration*/ true); - const prototype = assignmentSymbol?.exports?.get("prototype" as __String); - const init = prototype?.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration); - return init ? getSymbolOfNode(init) : undefined; - } + function getAssignedClassSymbol(decl: Declaration): Symbol | undefined { + const assignmentSymbol = decl && getSymbolOfExpando(decl, /*allowDeclaration*/ true); + const prototype = assignmentSymbol?.exports?.get("prototype" as __String); + const init = prototype?.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration); + return init ? getSymbolOfNode(init) : undefined; + } - function getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined { - if (!node.parent) { + function getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined { + if (!node.parent) { + return undefined; + } + let name: Expression | BindingName | undefined; + let decl: Node | undefined; + if (isVariableDeclaration(node.parent) && node.parent.initializer === node) { + if (!isInJSFile(node) && !(isVarConst(node.parent) && isFunctionLikeDeclaration(node))) { return undefined; } - let name: Expression | BindingName | undefined; - let decl: Node | undefined; - if (isVariableDeclaration(node.parent) && node.parent.initializer === node) { - if (!isInJSFile(node) && !(isVarConst(node.parent) && isFunctionLikeDeclaration(node))) { - return undefined; - } - name = node.parent.name; - decl = node.parent; + name = node.parent.name; + decl = node.parent; + } + else if (isBinaryExpression(node.parent)) { + const parentNode = node.parent; + const parentNodeOperator = node.parent.operatorToken.kind; + if (parentNodeOperator === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.right === node)) { + name = parentNode.left; + decl = name; } - else if (isBinaryExpression(node.parent)) { - const parentNode = node.parent; - const parentNodeOperator = node.parent.operatorToken.kind; - if (parentNodeOperator === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.right === node)) { - name = parentNode.left; + else if (parentNodeOperator === SyntaxKind.BarBarToken || parentNodeOperator === SyntaxKind.QuestionQuestionToken) { + if (isVariableDeclaration(parentNode.parent) && parentNode.parent.initializer === parentNode) { + name = parentNode.parent.name; + decl = parentNode.parent; + } + else if (isBinaryExpression(parentNode.parent) && parentNode.parent.operatorToken.kind === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.parent.right === parentNode)) { + name = parentNode.parent.left; decl = name; } - else if (parentNodeOperator === SyntaxKind.BarBarToken || parentNodeOperator === SyntaxKind.QuestionQuestionToken) { - if (isVariableDeclaration(parentNode.parent) && parentNode.parent.initializer === parentNode) { - name = parentNode.parent.name; - decl = parentNode.parent; - } - else if (isBinaryExpression(parentNode.parent) && parentNode.parent.operatorToken.kind === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.parent.right === parentNode)) { - name = parentNode.parent.left; - decl = name; - } - if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, parentNode.left)) { - return undefined; - } + if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, parentNode.left)) { + return undefined; } } - else if (allowDeclaration && isFunctionDeclaration(node)) { - name = node.name; - decl = node; - } - - if (!decl || !name || (!allowDeclaration && !getExpandoInitializer(node, isPrototypeAccess(name)))) { - return undefined; - } - return getSymbolOfNode(decl); } - - - function getAssignedJSPrototype(node: Node) { - if (!node.parent) { - return false; - } - let parent: Node = node.parent; - while (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { - parent = parent.parent; - } - if (parent && isBinaryExpression(parent) && isPrototypeAccess(parent.left) && parent.operatorToken.kind === SyntaxKind.EqualsToken) { - const right = getInitializerOfBinaryExpression(parent); - return isObjectLiteralExpression(right) && right; - } + else if (allowDeclaration && isFunctionDeclaration(node)) { + name = node.name; + decl = node; } - /** - * Syntactically and semantically checks a call or new expression. - * @param node The call/new expression to be checked. - * @returns On success, the expression's signature's return type. On failure, anyType. - */ - function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { - checkGrammarTypeArguments(node, node.typeArguments); + if (!decl || !name || (!allowDeclaration && !getExpandoInitializer(node, isPrototypeAccess(name)))) { + return undefined; + } + return getSymbolOfNode(decl); + } - const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode); - if (signature === resolvingSignature) { - // CheckMode.SkipGenericFunctions is enabled and this is a call to a generic function that - // returns a function type. We defer checking and return silentNeverType. - return silentNeverType; - } - checkDeprecatedSignature(signature, node); + function getAssignedJSPrototype(node: Node) { + if (!node.parent) { + return false; + } + let parent: Node = node.parent; + while (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { + parent = parent.parent; + } + if (parent && isBinaryExpression(parent) && isPrototypeAccess(parent.left) && parent.operatorToken.kind === SyntaxKind.EqualsToken) { + const right = getInitializerOfBinaryExpression(parent); + return isObjectLiteralExpression(right) && right; + } + } - if (node.expression.kind === SyntaxKind.SuperKeyword) { - return voidType; - } + /** + * Syntactically and semantically checks a call or new expression. + * @param node The call/new expression to be checked. + * @returns On success, the expression's signature's return type. On failure, anyType. + */ + function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { + checkGrammarTypeArguments(node, node.typeArguments); - if (node.kind === SyntaxKind.NewExpression) { - const declaration = signature.declaration; + const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode); + if (signature === resolvingSignature) { + // CheckMode.SkipGenericFunctions is enabled and this is a call to a generic function that + // returns a function type. We defer checking and return silentNeverType. + return silentNeverType; + } - if (declaration && - declaration.kind !== SyntaxKind.Constructor && - declaration.kind !== SyntaxKind.ConstructSignature && - declaration.kind !== SyntaxKind.ConstructorType && - !isJSDocConstructSignature(declaration) && - !isJSConstructor(declaration)) { + checkDeprecatedSignature(signature, node); - // When resolved signature is a call signature (and not a construct signature) the result type is any - if (noImplicitAny) { - error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); - } - return anyType; - } - } + if (node.expression.kind === SyntaxKind.SuperKeyword) { + return voidType; + } - // In JavaScript files, calls to any identifier 'require' are treated as external module imports - if (isInJSFile(node) && isCommonJsRequire(node)) { - return resolveExternalModuleTypeByLiteral(node.arguments![0] as StringLiteral); - } + if (node.kind === SyntaxKind.NewExpression) { + const declaration = signature.declaration; - const returnType = getReturnTypeOfSignature(signature); - // Treat any call to the global 'Symbol' function that is part of a const variable or readonly property - // as a fresh unique symbol literal type. - if (returnType.flags & TypeFlags.ESSymbolLike && isSymbolOrSymbolForCall(node)) { - return getESSymbolLikeTypeForNode(walkUpParenthesizedExpressions(node.parent)); - } - if (node.kind === SyntaxKind.CallExpression && !node.questionDotToken && node.parent.kind === SyntaxKind.ExpressionStatement && - returnType.flags & TypeFlags.Void && getTypePredicateOfSignature(signature)) { - if (!isDottedName(node.expression)) { - error(node.expression, Diagnostics.Assertions_require_the_call_target_to_be_an_identifier_or_qualified_name); - } - else if (!getEffectsSignature(node)) { - const diagnostic = error(node.expression, Diagnostics.Assertions_require_every_name_in_the_call_target_to_be_declared_with_an_explicit_type_annotation); - getTypeOfDottedName(node.expression, diagnostic); - } - } + if (declaration && + declaration.kind !== SyntaxKind.Constructor && + declaration.kind !== SyntaxKind.ConstructSignature && + declaration.kind !== SyntaxKind.ConstructorType && + !isJSDocConstructSignature(declaration) && + !isJSConstructor(declaration)) { - if (isInJSFile(node)) { - const jsSymbol = getSymbolOfExpando(node, /*allowDeclaration*/ false); - if (jsSymbol?.exports?.size) { - const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, emptyArray); - jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; - return getIntersectionType([returnType, jsAssignmentType]); + // When resolved signature is a call signature (and not a construct signature) the result type is any + if (noImplicitAny) { + error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); } + return anyType; } - - return returnType; } - function checkDeprecatedSignature(signature: Signature, node: CallLikeExpression) { - if (signature.declaration && signature.declaration.flags & NodeFlags.Deprecated) { - const suggestionNode = getDeprecatedSuggestionNode(node); - const name = tryGetPropertyAccessOrIdentifierToString(getInvokedExpression(node)); - addDeprecatedSuggestionWithSignature(suggestionNode, signature.declaration, name, signatureToString(signature)); - } + // In JavaScript files, calls to any identifier 'require' are treated as external module imports + if (isInJSFile(node) && isCommonJsRequire(node)) { + return resolveExternalModuleTypeByLiteral(node.arguments![0] as StringLiteral); } - function getDeprecatedSuggestionNode(node: Node): Node { - node = skipParentheses(node); - switch (node.kind) { - case SyntaxKind.CallExpression: - case SyntaxKind.Decorator: - case SyntaxKind.NewExpression: - return getDeprecatedSuggestionNode((node as Decorator | CallExpression | NewExpression).expression); - case SyntaxKind.TaggedTemplateExpression: - return getDeprecatedSuggestionNode((node as TaggedTemplateExpression).tag); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxSelfClosingElement: - return getDeprecatedSuggestionNode((node as JsxOpeningLikeElement).tagName); - case SyntaxKind.ElementAccessExpression: - return (node as ElementAccessExpression).argumentExpression; - case SyntaxKind.PropertyAccessExpression: - return (node as PropertyAccessExpression).name; - case SyntaxKind.TypeReference: - const typeReference = node as TypeReferenceNode; - return isQualifiedName(typeReference.typeName) ? typeReference.typeName.right : typeReference; - default: - return node; - } + const returnType = getReturnTypeOfSignature(signature); + // Treat any call to the global 'Symbol' function that is part of a const variable or readonly property + // as a fresh unique symbol literal type. + if (returnType.flags & TypeFlags.ESSymbolLike && isSymbolOrSymbolForCall(node)) { + return getESSymbolLikeTypeForNode(walkUpParenthesizedExpressions(node.parent)); } - - function isSymbolOrSymbolForCall(node: Node) { - if (!isCallExpression(node)) return false; - let left = node.expression; - if (isPropertyAccessExpression(left) && left.name.escapedText === "for") { - left = left.expression; + if (node.kind === SyntaxKind.CallExpression && !node.questionDotToken && node.parent.kind === SyntaxKind.ExpressionStatement && + returnType.flags & TypeFlags.Void && getTypePredicateOfSignature(signature)) { + if (!isDottedName(node.expression)) { + error(node.expression, Diagnostics.Assertions_require_the_call_target_to_be_an_identifier_or_qualified_name); } - if (!isIdentifier(left) || left.escapedText !== "Symbol") { - return false; + else if (!getEffectsSignature(node)) { + const diagnostic = error(node.expression, Diagnostics.Assertions_require_every_name_in_the_call_target_to_be_declared_with_an_explicit_type_annotation); + getTypeOfDottedName(node.expression, diagnostic); } + } - // make sure `Symbol` is the global symbol - const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); - if (!globalESSymbol) { - return false; + if (isInJSFile(node)) { + const jsSymbol = getSymbolOfExpando(node, /*allowDeclaration*/ false); + if (jsSymbol?.exports?.size) { + const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, emptyArray); + jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; + return getIntersectionType([returnType, jsAssignmentType]); } + } - return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + return returnType; + } + + function checkDeprecatedSignature(signature: Signature, node: CallLikeExpression) { + if (signature.declaration && signature.declaration.flags & NodeFlags.Deprecated) { + const suggestionNode = getDeprecatedSuggestionNode(node); + const name = tryGetPropertyAccessOrIdentifierToString(getInvokedExpression(node)); + addDeprecatedSuggestionWithSignature(suggestionNode, signature.declaration, name, signatureToString(signature)); } + } - function checkImportCallExpression(node: ImportCall): Type { - // Check grammar of dynamic import - checkGrammarImportCallExpression(node); + function getDeprecatedSuggestionNode(node: Node): Node { + node = skipParentheses(node); + switch (node.kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.Decorator: + case SyntaxKind.NewExpression: + return getDeprecatedSuggestionNode((node as Decorator | CallExpression | NewExpression).expression); + case SyntaxKind.TaggedTemplateExpression: + return getDeprecatedSuggestionNode((node as TaggedTemplateExpression).tag); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + return getDeprecatedSuggestionNode((node as JsxOpeningLikeElement).tagName); + case SyntaxKind.ElementAccessExpression: + return (node as ElementAccessExpression).argumentExpression; + case SyntaxKind.PropertyAccessExpression: + return (node as PropertyAccessExpression).name; + case SyntaxKind.TypeReference: + const typeReference = node as TypeReferenceNode; + return isQualifiedName(typeReference.typeName) ? typeReference.typeName.right : typeReference; + default: + return node; + } + } - if (node.arguments.length === 0) { - return createPromiseReturnType(node, anyType); - } + function isSymbolOrSymbolForCall(node: Node) { + if (!isCallExpression(node)) return false; + let left = node.expression; + if (isPropertyAccessExpression(left) && left.name.escapedText === "for") { + left = left.expression; + } + if (!isIdentifier(left) || left.escapedText !== "Symbol") { + return false; + } - const specifier = node.arguments[0]; - const specifierType = checkExpressionCached(specifier); - const optionsType = node.arguments.length > 1 ? checkExpressionCached(node.arguments[1]) : undefined; - // Even though multiple arguments is grammatically incorrect, type-check extra arguments for completion - for (let i = 2; i < node.arguments.length; ++i) { - checkExpressionCached(node.arguments[i]); - } + // make sure `Symbol` is the global symbol + const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); + if (!globalESSymbol) { + return false; + } - if (specifierType.flags & TypeFlags.Undefined || specifierType.flags & TypeFlags.Null || !isTypeAssignableTo(specifierType, stringType)) { - error(specifier, Diagnostics.Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0, typeToString(specifierType)); - } + return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + } - if (optionsType) { - const importCallOptionsType = getGlobalImportCallOptionsType(/*reportErrors*/ true); - if (importCallOptionsType !== emptyObjectType) { - checkTypeAssignableTo(optionsType, getNullableType(importCallOptionsType, TypeFlags.Undefined), node.arguments[1]); - } - } + function checkImportCallExpression(node: ImportCall): Type { + // Check grammar of dynamic import + checkGrammarImportCallExpression(node); - // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal - const moduleSymbol = resolveExternalModuleName(node, specifier); - if (moduleSymbol) { - const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true, /*suppressUsageError*/ false); - if (esModuleSymbol) { - return createPromiseReturnType(node, - getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) || - getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) - ); - } - } + if (node.arguments.length === 0) { return createPromiseReturnType(node, anyType); } - function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol, anonymousSymbol?: Symbol | undefined) { - const memberTable = createSymbolTable(); - const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); - newSymbol.parent = originalSymbol; - newSymbol.nameType = getStringLiteralType("default"); - newSymbol.aliasTarget = resolveSymbol(symbol); - memberTable.set(InternalSymbolName.Default, newSymbol); - return createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, emptyArray); + const specifier = node.arguments[0]; + const specifierType = checkExpressionCached(specifier); + const optionsType = node.arguments.length > 1 ? checkExpressionCached(node.arguments[1]) : undefined; + // Even though multiple arguments is grammatically incorrect, type-check extra arguments for completion + for (let i = 2; i < node.arguments.length; ++i) { + checkExpressionCached(node.arguments[i]); } - function getTypeWithSyntheticDefaultOnly(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression) { - const hasDefaultOnly = isOnlyImportedAsDefault(moduleSpecifier); - if (hasDefaultOnly && type && !isErrorType(type)) { - const synthType = type as SyntheticDefaultModuleType; - if (!synthType.defaultOnlyType) { - const type = createDefaultPropertyWrapperForModule(symbol, originalSymbol); - synthType.defaultOnlyType = type; - } - return synthType.defaultOnlyType; - } - return undefined; + if (specifierType.flags & TypeFlags.Undefined || specifierType.flags & TypeFlags.Null || !isTypeAssignableTo(specifierType, stringType)) { + error(specifier, Diagnostics.Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0, typeToString(specifierType)); } - function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression): Type { - if (allowSyntheticDefaultImports && type && !isErrorType(type)) { - const synthType = type as SyntheticDefaultModuleType; - if (!synthType.syntheticType) { - const file = originalSymbol.declarations?.find(isSourceFile); - const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false, moduleSpecifier); - if (hasSyntheticDefault) { - const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - const defaultContainingObject = createDefaultPropertyWrapperForModule(symbol, originalSymbol, anonymousSymbol); - anonymousSymbol.type = defaultContainingObject; - synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; - } - else { - synthType.syntheticType = type; - } - } - return synthType.syntheticType; + if (optionsType) { + const importCallOptionsType = getGlobalImportCallOptionsType(/*reportErrors*/ true); + if (importCallOptionsType !== emptyObjectType) { + checkTypeAssignableTo(optionsType, getNullableType(importCallOptionsType, TypeFlags.Undefined), node.arguments[1]); } - return type; } - function isCommonJsRequire(node: Node): boolean { - if (!isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { - return false; - } - - // Make sure require is not a local function - if (!isIdentifier(node.expression)) return Debug.fail(); - const resolvedRequire = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true)!; // TODO: GH#18217 - if (resolvedRequire === requireSymbol) { - return true; - } - // project includes symbol named 'require' - make sure that it is ambient and local non-alias - if (resolvedRequire.flags & SymbolFlags.Alias) { - return false; - } - - const targetDeclarationKind = resolvedRequire.flags & SymbolFlags.Function - ? SyntaxKind.FunctionDeclaration - : resolvedRequire.flags & SymbolFlags.Variable - ? SyntaxKind.VariableDeclaration - : SyntaxKind.Unknown; - if (targetDeclarationKind !== SyntaxKind.Unknown) { - const decl = getDeclarationOfKind(resolvedRequire, targetDeclarationKind)!; - // function/variable declaration should be ambient - return !!decl && !!(decl.flags & NodeFlags.Ambient); + // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal + const moduleSymbol = resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true, /*suppressUsageError*/ false); + if (esModuleSymbol) { + return createPromiseReturnType(node, + getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) || + getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) + ); } - return false; } + return createPromiseReturnType(node, anyType); + } - function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { - if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); + function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol, anonymousSymbol?: Symbol | undefined) { + const memberTable = createSymbolTable(); + const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); + newSymbol.parent = originalSymbol; + newSymbol.nameType = getStringLiteralType("default"); + newSymbol.aliasTarget = resolveSymbol(symbol); + memberTable.set(InternalSymbolName.Default, newSymbol); + return createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, emptyArray); + } + + function getTypeWithSyntheticDefaultOnly(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression) { + const hasDefaultOnly = isOnlyImportedAsDefault(moduleSpecifier); + if (hasDefaultOnly && type && !isErrorType(type)) { + const synthType = type as SyntheticDefaultModuleType; + if (!synthType.defaultOnlyType) { + const type = createDefaultPropertyWrapperForModule(symbol, originalSymbol); + synthType.defaultOnlyType = type; } - const signature = getResolvedSignature(node); - checkDeprecatedSignature(signature, node); - return getReturnTypeOfSignature(signature); + return synthType.defaultOnlyType; } + return undefined; + } - function checkAssertion(node: AssertionExpression) { - if (node.kind === SyntaxKind.TypeAssertionExpression) { - const file = getSourceFileOfNode(node); - if (file && fileExtensionIsOneOf(file.fileName, [Extension.Cts, Extension.Mts])) { - grammarErrorOnNode(node, Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Use_an_as_expression_instead); + function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression): Type { + if (allowSyntheticDefaultImports && type && !isErrorType(type)) { + const synthType = type as SyntheticDefaultModuleType; + if (!synthType.syntheticType) { + const file = originalSymbol.declarations?.find(isSourceFile); + const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false, moduleSpecifier); + if (hasSyntheticDefault) { + const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + const defaultContainingObject = createDefaultPropertyWrapperForModule(symbol, originalSymbol, anonymousSymbol); + anonymousSymbol.type = defaultContainingObject; + synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; + } + else { + synthType.syntheticType = type; } } - return checkAssertionWorker(node, node.type, node.expression); + return synthType.syntheticType; } + return type; + } - function isValidConstAssertionArgument(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.TemplateExpression: - return true; - case SyntaxKind.ParenthesizedExpression: - return isValidConstAssertionArgument((node as ParenthesizedExpression).expression); - case SyntaxKind.PrefixUnaryExpression: - const op = (node as PrefixUnaryExpression).operator; - const arg = (node as PrefixUnaryExpression).operand; - return op === SyntaxKind.MinusToken && (arg.kind === SyntaxKind.NumericLiteral || arg.kind === SyntaxKind.BigIntLiteral) || - op === SyntaxKind.PlusToken && arg.kind === SyntaxKind.NumericLiteral; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - const expr = (node as PropertyAccessExpression | ElementAccessExpression).expression; - let symbol = getTypeOfNode(expr).symbol; - if (symbol && symbol.flags & SymbolFlags.Alias) { - symbol = resolveAlias(symbol); - } - return !!(symbol && getAllSymbolFlags(symbol) & SymbolFlags.Enum); - } + function isCommonJsRequire(node: Node): boolean { + if (!isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { return false; } - function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) { - let exprType = checkExpression(expression, checkMode); - if (isConstTypeReference(type)) { - if (!isValidConstAssertionArgument(expression)) { - error(expression, Diagnostics.A_const_assertions_can_only_be_applied_to_references_to_enum_members_or_string_number_boolean_array_or_object_literals); - } - return getRegularTypeOfLiteralType(exprType); - } - checkSourceElement(type); - exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType)); - const targetType = getTypeFromTypeNode(type); - if (!isErrorType(targetType)) { - addLazyDiagnostic(() => { - const widenedType = getWidenedType(exprType); - if (!isTypeComparableTo(targetType, widenedType)) { - checkTypeComparableTo(exprType, targetType, errNode, - Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first); - } - }); - } - return targetType; + // Make sure require is not a local function + if (!isIdentifier(node.expression)) return Debug.fail(); + const resolvedRequire = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true)!; // TODO: GH#18217 + if (resolvedRequire === requireSymbol) { + return true; + } + // project includes symbol named 'require' - make sure that it is ambient and local non-alias + if (resolvedRequire.flags & SymbolFlags.Alias) { + return false; } - function checkNonNullChain(node: NonNullChain) { - const leftType = checkExpression(node.expression); - const nonOptionalType = getOptionalExpressionType(leftType, node.expression); - return propagateOptionalTypeMarker(getNonNullableType(nonOptionalType), node, nonOptionalType !== leftType); + const targetDeclarationKind = resolvedRequire.flags & SymbolFlags.Function + ? SyntaxKind.FunctionDeclaration + : resolvedRequire.flags & SymbolFlags.Variable + ? SyntaxKind.VariableDeclaration + : SyntaxKind.Unknown; + if (targetDeclarationKind !== SyntaxKind.Unknown) { + const decl = getDeclarationOfKind(resolvedRequire, targetDeclarationKind)!; + // function/variable declaration should be ambient + return !!decl && !!(decl.flags & NodeFlags.Ambient); } + return false; + } - function checkNonNullAssertion(node: NonNullExpression) { - return node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) : - getNonNullableType(checkExpression(node.expression)); + function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { + if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); } + const signature = getResolvedSignature(node); + checkDeprecatedSignature(signature, node); + return getReturnTypeOfSignature(signature); + } - function checkExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { - checkGrammarExpressionWithTypeArguments(node); - const exprType = node.kind === SyntaxKind.ExpressionWithTypeArguments ? checkExpression(node.expression) : - isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : - checkExpression(node.exprName); - const typeArguments = node.typeArguments; - if (exprType === silentNeverType || isErrorType(exprType) || !some(typeArguments)) { - return exprType; - } - let hasSomeApplicableSignature = false; - let nonApplicableType: Type | undefined; - const result = getInstantiatedType(exprType); - const errorType = hasSomeApplicableSignature ? nonApplicableType : exprType; - if (errorType) { - diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Type_0_has_no_signatures_for_which_the_type_argument_list_is_applicable, typeToString(errorType))); + function checkAssertion(node: AssertionExpression) { + if (node.kind === SyntaxKind.TypeAssertionExpression) { + const file = getSourceFileOfNode(node); + if (file && fileExtensionIsOneOf(file.fileName, [Extension.Cts, Extension.Mts])) { + grammarErrorOnNode(node, Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Use_an_as_expression_instead); } - return result; - - function getInstantiatedType(type: Type): Type { - let hasSignatures = false; - let hasApplicableSignature = false; - const result = getInstantiatedTypePart(type); - hasSomeApplicableSignature ||= hasApplicableSignature; - if (hasSignatures && !hasApplicableSignature) { - nonApplicableType ??= type; - } - return result; + } + return checkAssertionWorker(node, node.type, node.expression); + } - function getInstantiatedTypePart(type: Type): Type { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - const callSignatures = getInstantiatedSignatures(resolved.callSignatures); - const constructSignatures = getInstantiatedSignatures(resolved.constructSignatures); - hasSignatures ||= resolved.callSignatures.length !== 0 || resolved.constructSignatures.length !== 0; - hasApplicableSignature ||= callSignatures.length !== 0 || constructSignatures.length !== 0; - if (callSignatures !== resolved.callSignatures || constructSignatures !== resolved.constructSignatures) { - const result = createAnonymousType(undefined, resolved.members, callSignatures, constructSignatures, resolved.indexInfos) as ResolvedType & InstantiationExpressionType; - result.objectFlags |= ObjectFlags.InstantiationExpressionType; - result.node = node; - return result; - } - } - else if (type.flags & TypeFlags.InstantiableNonPrimitive) { - const constraint = getBaseConstraintOfType(type); - if (constraint) { - const instantiated = getInstantiatedTypePart(constraint); - if (instantiated !== constraint) { - return instantiated; - } - } - } - else if (type.flags & TypeFlags.Union) { - return mapType(type, getInstantiatedType); - } - else if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(sameMap((type as IntersectionType).types, getInstantiatedTypePart)); - } - return type; - } - } + function isValidConstAssertionArgument(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TemplateExpression: + return true; + case SyntaxKind.ParenthesizedExpression: + return isValidConstAssertionArgument((node as ParenthesizedExpression).expression); + case SyntaxKind.PrefixUnaryExpression: + const op = (node as PrefixUnaryExpression).operator; + const arg = (node as PrefixUnaryExpression).operand; + return op === SyntaxKind.MinusToken && (arg.kind === SyntaxKind.NumericLiteral || arg.kind === SyntaxKind.BigIntLiteral) || + op === SyntaxKind.PlusToken && arg.kind === SyntaxKind.NumericLiteral; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const expr = (node as PropertyAccessExpression | ElementAccessExpression).expression; + let symbol = getTypeOfNode(expr).symbol; + if (symbol && symbol.flags & SymbolFlags.Alias) { + symbol = resolveAlias(symbol); + } + return !!(symbol && getAllSymbolFlags(symbol) & SymbolFlags.Enum); + } + return false; + } - function getInstantiatedSignatures(signatures: readonly Signature[]) { - const applicableSignatures = filter(signatures, sig => !!sig.typeParameters && hasCorrectTypeArgumentArity(sig, typeArguments)); - return sameMap(applicableSignatures, sig => { - const typeArgumentTypes = checkTypeArguments(sig, typeArguments!, /*reportErrors*/ true); - return typeArgumentTypes ? getSignatureInstantiation(sig, typeArgumentTypes, isInJSFile(sig.declaration)) : sig; - }); + function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) { + let exprType = checkExpression(expression, checkMode); + if (isConstTypeReference(type)) { + if (!isValidConstAssertionArgument(expression)) { + error(expression, Diagnostics.A_const_assertions_can_only_be_applied_to_references_to_enum_members_or_string_number_boolean_array_or_object_literals); } + return getRegularTypeOfLiteralType(exprType); } + checkSourceElement(type); + exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType)); + const targetType = getTypeFromTypeNode(type); + if (!isErrorType(targetType)) { + addLazyDiagnostic(() => { + const widenedType = getWidenedType(exprType); + if (!isTypeComparableTo(targetType, widenedType)) { + checkTypeComparableTo(exprType, targetType, errNode, + Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first); + } + }); + } + return targetType; + } - function checkSatisfiesExpression(node: SatisfiesExpression) { - checkSourceElement(node.type); - - const targetType = getTypeFromTypeNode(node.type); - if (isErrorType(targetType)) { - return targetType; - } + function checkNonNullChain(node: NonNullChain) { + const leftType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(leftType, node.expression); + return propagateOptionalTypeMarker(getNonNullableType(nonOptionalType), node, nonOptionalType !== leftType); + } - const exprType = checkExpression(node.expression); - checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, node.type, node.expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1); + function checkNonNullAssertion(node: NonNullExpression) { + return node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) : + getNonNullableType(checkExpression(node.expression)); + } + function checkExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { + checkGrammarExpressionWithTypeArguments(node); + const exprType = node.kind === SyntaxKind.ExpressionWithTypeArguments ? checkExpression(node.expression) : + isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : + checkExpression(node.exprName); + const typeArguments = node.typeArguments; + if (exprType === silentNeverType || isErrorType(exprType) || !some(typeArguments)) { return exprType; } + let hasSomeApplicableSignature = false; + let nonApplicableType: Type | undefined; + const result = getInstantiatedType(exprType); + const errorType = hasSomeApplicableSignature ? nonApplicableType : exprType; + if (errorType) { + diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Type_0_has_no_signatures_for_which_the_type_argument_list_is_applicable, typeToString(errorType))); + } + return result; - function checkMetaProperty(node: MetaProperty): Type { - checkGrammarMetaProperty(node); - - if (node.keywordToken === SyntaxKind.NewKeyword) { - return checkNewTargetMetaProperty(node); + function getInstantiatedType(type: Type): Type { + let hasSignatures = false; + let hasApplicableSignature = false; + const result = getInstantiatedTypePart(type); + hasSomeApplicableSignature ||= hasApplicableSignature; + if (hasSignatures && !hasApplicableSignature) { + nonApplicableType ??= type; } + return result; - if (node.keywordToken === SyntaxKind.ImportKeyword) { - return checkImportMetaProperty(node); + function getInstantiatedTypePart(type: Type): Type { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + const callSignatures = getInstantiatedSignatures(resolved.callSignatures); + const constructSignatures = getInstantiatedSignatures(resolved.constructSignatures); + hasSignatures ||= resolved.callSignatures.length !== 0 || resolved.constructSignatures.length !== 0; + hasApplicableSignature ||= callSignatures.length !== 0 || constructSignatures.length !== 0; + if (callSignatures !== resolved.callSignatures || constructSignatures !== resolved.constructSignatures) { + const result = createAnonymousType(undefined, resolved.members, callSignatures, constructSignatures, resolved.indexInfos) as ResolvedType & InstantiationExpressionType; + result.objectFlags |= ObjectFlags.InstantiationExpressionType; + result.node = node; + return result; + } + } + else if (type.flags & TypeFlags.InstantiableNonPrimitive) { + const constraint = getBaseConstraintOfType(type); + if (constraint) { + const instantiated = getInstantiatedTypePart(constraint); + if (instantiated !== constraint) { + return instantiated; + } + } + } + else if (type.flags & TypeFlags.Union) { + return mapType(type, getInstantiatedType); + } + else if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(sameMap((type as IntersectionType).types, getInstantiatedTypePart)); + } + return type; } - - return Debug.assertNever(node.keywordToken); } - function checkMetaPropertyKeyword(node: MetaProperty): Type { - switch (node.keywordToken) { - case SyntaxKind.ImportKeyword: - return getGlobalImportMetaExpressionType(); - case SyntaxKind.NewKeyword: - const type = checkNewTargetMetaProperty(node); - return isErrorType(type) ? errorType : createNewTargetExpressionType(type); - default: - Debug.assertNever(node.keywordToken); - } + function getInstantiatedSignatures(signatures: readonly Signature[]) { + const applicableSignatures = filter(signatures, sig => !!sig.typeParameters && hasCorrectTypeArgumentArity(sig, typeArguments)); + return sameMap(applicableSignatures, sig => { + const typeArgumentTypes = checkTypeArguments(sig, typeArguments!, /*reportErrors*/ true); + return typeArgumentTypes ? getSignatureInstantiation(sig, typeArgumentTypes, isInJSFile(sig.declaration)) : sig; + }); } + } - function checkNewTargetMetaProperty(node: MetaProperty) { - const container = getNewTargetContainer(node); - if (!container) { - error(node, Diagnostics.Meta_property_0_is_only_allowed_in_the_body_of_a_function_declaration_function_expression_or_constructor, "new.target"); - return errorType; - } - else if (container.kind === SyntaxKind.Constructor) { - const symbol = getSymbolOfNode(container.parent as ClassLikeDeclaration); - return getTypeOfSymbol(symbol); - } - else { - const symbol = getSymbolOfNode(container)!; - return getTypeOfSymbol(symbol); - } + function checkSatisfiesExpression(node: SatisfiesExpression) { + checkSourceElement(node.type); + + const targetType = getTypeFromTypeNode(node.type); + if (isErrorType(targetType)) { + return targetType; + } + + const exprType = checkExpression(node.expression); + checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, node.type, node.expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1); + + return exprType; + } + + function checkMetaProperty(node: MetaProperty): Type { + checkGrammarMetaProperty(node); + + if (node.keywordToken === SyntaxKind.NewKeyword) { + return checkNewTargetMetaProperty(node); } - function checkImportMetaProperty(node: MetaProperty) { - if (moduleKind === ModuleKind.Node16 || moduleKind === ModuleKind.NodeNext) { - if (getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.ESNext) { - error(node, Diagnostics.The_import_meta_meta_property_is_not_allowed_in_files_which_will_build_into_CommonJS_output); - } - } - else if (moduleKind < ModuleKind.ES2020 && moduleKind !== ModuleKind.System) { - error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_es2020_es2022_esnext_system_node16_or_nodenext); - } - const file = getSourceFileOfNode(node); - Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); - return node.name.escapedText === "meta" ? getGlobalImportMetaType() : errorType; + if (node.keywordToken === SyntaxKind.ImportKeyword) { + return checkImportMetaProperty(node); } - function getTypeOfParameter(symbol: Symbol) { - const type = getTypeOfSymbol(symbol); - if (strictNullChecks) { - const declaration = symbol.valueDeclaration; - if (declaration && hasInitializer(declaration)) { - return getOptionalType(type); - } - } - return type; + return Debug.assertNever(node.keywordToken); + } + + function checkMetaPropertyKeyword(node: MetaProperty): Type { + switch (node.keywordToken) { + case SyntaxKind.ImportKeyword: + return getGlobalImportMetaExpressionType(); + case SyntaxKind.NewKeyword: + const type = checkNewTargetMetaProperty(node); + return isErrorType(type) ? errorType : createNewTargetExpressionType(type); + default: + Debug.assertNever(node.keywordToken); } + } - function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember) { - Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names - return d.name.escapedText; + function checkNewTargetMetaProperty(node: MetaProperty) { + const container = getNewTargetContainer(node); + if (!container) { + error(node, Diagnostics.Meta_property_0_is_only_allowed_in_the_body_of_a_function_declaration_function_expression_or_constructor, "new.target"); + return errorType; + } + else if (container.kind === SyntaxKind.Constructor) { + const symbol = getSymbolOfNode(container.parent as ClassLikeDeclaration); + return getTypeOfSymbol(symbol); + } + else { + const symbol = getSymbolOfNode(container)!; + return getTypeOfSymbol(symbol); } + } - function getParameterNameAtPosition(signature: Signature, pos: number, overrideRestType?: Type) { - const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - if (pos < paramCount) { - return signature.parameters[pos].escapedName; + function checkImportMetaProperty(node: MetaProperty) { + if (moduleKind === ModuleKind.Node16 || moduleKind === ModuleKind.NodeNext) { + if (getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.ESNext) { + error(node, Diagnostics.The_import_meta_meta_property_is_not_allowed_in_files_which_will_build_into_CommonJS_output); } - const restParameter = signature.parameters[paramCount] || unknownSymbol; - const restType = overrideRestType || getTypeOfSymbol(restParameter); - if (isTupleType(restType)) { - const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; - const index = pos - paramCount; - return associatedNames && getTupleElementLabel(associatedNames[index]) || restParameter.escapedName + "_" + index as __String; - } - return restParameter.escapedName; } + else if (moduleKind < ModuleKind.ES2020 && moduleKind !== ModuleKind.System) { + error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_es2020_es2022_esnext_system_node16_or_nodenext); + } + const file = getSourceFileOfNode(node); + Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); + return node.name.escapedText === "meta" ? getGlobalImportMetaType() : errorType; + } - function getParameterIdentifierNameAtPosition(signature: Signature, pos: number): [parameterName: __String, isRestParameter: boolean] | undefined { - if (signature.declaration?.kind === SyntaxKind.JSDocFunctionType) { - return undefined; - } - const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - if (pos < paramCount) { - const param = signature.parameters[pos]; - return isParameterDeclarationWithIdentifierName(param) ? [param.escapedName, false] : undefined; + function getTypeOfParameter(symbol: Symbol) { + const type = getTypeOfSymbol(symbol); + if (strictNullChecks) { + const declaration = symbol.valueDeclaration; + if (declaration && hasInitializer(declaration)) { + return getOptionalType(type); } + } + return type; + } - const restParameter = signature.parameters[paramCount] || unknownSymbol; - if (!isParameterDeclarationWithIdentifierName(restParameter)) { - return undefined; - } + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember) { + Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names + return d.name.escapedText; + } - const restType = getTypeOfSymbol(restParameter); - if (isTupleType(restType)) { - const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; - const index = pos - paramCount; - const associatedName = associatedNames?.[index]; - const isRestTupleElement = !!associatedName?.dotDotDotToken; - return associatedName ? [ - getTupleElementLabel(associatedName), - isRestTupleElement - ] : undefined; - } + function getParameterNameAtPosition(signature: Signature, pos: number, overrideRestType?: Type) { + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + return signature.parameters[pos].escapedName; + } + const restParameter = signature.parameters[paramCount] || unknownSymbol; + const restType = overrideRestType || getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; + const index = pos - paramCount; + return associatedNames && getTupleElementLabel(associatedNames[index]) || restParameter.escapedName + "_" + index as __String; + } + return restParameter.escapedName; + } - if (pos === paramCount) { - return [restParameter.escapedName, true]; - } + function getParameterIdentifierNameAtPosition(signature: Signature, pos: number): [parameterName: __String, isRestParameter: boolean] | undefined { + if (signature.declaration?.kind === SyntaxKind.JSDocFunctionType) { return undefined; } + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + const param = signature.parameters[pos]; + return isParameterDeclarationWithIdentifierName(param) ? [param.escapedName, false] : undefined; + } - function isParameterDeclarationWithIdentifierName(symbol: Symbol) { - return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isIdentifier(symbol.valueDeclaration.name); + const restParameter = signature.parameters[paramCount] || unknownSymbol; + if (!isParameterDeclarationWithIdentifierName(restParameter)) { + return undefined; } - function isValidDeclarationForTupleLabel(d: Declaration): d is NamedTupleMember | (ParameterDeclaration & { name: Identifier }) { - return d.kind === SyntaxKind.NamedTupleMember || (isParameter(d) && d.name && isIdentifier(d.name)); + + const restType = getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; + const index = pos - paramCount; + const associatedName = associatedNames?.[index]; + const isRestTupleElement = !!associatedName?.dotDotDotToken; + return associatedName ? [ + getTupleElementLabel(associatedName), + isRestTupleElement + ] : undefined; } - function getNameableDeclarationAtPosition(signature: Signature, pos: number) { - const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - if (pos < paramCount) { - const decl = signature.parameters[pos].valueDeclaration; - return decl && isValidDeclarationForTupleLabel(decl) ? decl : undefined; - } - const restParameter = signature.parameters[paramCount] || unknownSymbol; - const restType = getTypeOfSymbol(restParameter); - if (isTupleType(restType)) { - const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; - const index = pos - paramCount; - return associatedNames && associatedNames[index]; - } - return restParameter.valueDeclaration && isValidDeclarationForTupleLabel(restParameter.valueDeclaration) ? restParameter.valueDeclaration : undefined; + if (pos === paramCount) { + return [restParameter.escapedName, true]; } + return undefined; + } + + function isParameterDeclarationWithIdentifierName(symbol: Symbol) { + return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isIdentifier(symbol.valueDeclaration.name); + } + function isValidDeclarationForTupleLabel(d: Declaration): d is NamedTupleMember | (ParameterDeclaration & { name: Identifier }) { + return d.kind === SyntaxKind.NamedTupleMember || (isParameter(d) && d.name && isIdentifier(d.name)); + } - function getTypeAtPosition(signature: Signature, pos: number): Type { - return tryGetTypeAtPosition(signature, pos) || anyType; + function getNameableDeclarationAtPosition(signature: Signature, pos: number) { + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + const decl = signature.parameters[pos].valueDeclaration; + return decl && isValidDeclarationForTupleLabel(decl) ? decl : undefined; } + const restParameter = signature.parameters[paramCount] || unknownSymbol; + const restType = getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; + const index = pos - paramCount; + return associatedNames && associatedNames[index]; + } + return restParameter.valueDeclaration && isValidDeclarationForTupleLabel(restParameter.valueDeclaration) ? restParameter.valueDeclaration : undefined; + } - function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined { - const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - if (pos < paramCount) { - return getTypeOfParameter(signature.parameters[pos]); - } - if (signatureHasRestParameter(signature)) { - // We want to return the value undefined for an out of bounds parameter position, - // so we need to check bounds here before calling getIndexedAccessType (which - // otherwise would return the type 'undefined'). - const restType = getTypeOfSymbol(signature.parameters[paramCount]); - const index = pos - paramCount; - if (!isTupleType(restType) || restType.target.hasRestElement || index < restType.target.fixedLength) { - return getIndexedAccessType(restType, getNumberLiteralType(index)); - } + function getTypeAtPosition(signature: Signature, pos: number): Type { + return tryGetTypeAtPosition(signature, pos) || anyType; + } + + function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined { + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + return getTypeOfParameter(signature.parameters[pos]); + } + if (signatureHasRestParameter(signature)) { + // We want to return the value undefined for an out of bounds parameter position, + // so we need to check bounds here before calling getIndexedAccessType (which + // otherwise would return the type 'undefined'). + const restType = getTypeOfSymbol(signature.parameters[paramCount]); + const index = pos - paramCount; + if (!isTupleType(restType) || restType.target.hasRestElement || index < restType.target.fixedLength) { + return getIndexedAccessType(restType, getNumberLiteralType(index)); } - return undefined; } + return undefined; + } - function getRestTypeAtPosition(source: Signature, pos: number): Type { - const parameterCount = getParameterCount(source); - const minArgumentCount = getMinArgumentCount(source); - const restType = getEffectiveRestType(source); - if (restType && pos >= parameterCount - 1) { - return pos === parameterCount - 1 ? restType : createArrayType(getIndexedAccessType(restType, numberType)); + function getRestTypeAtPosition(source: Signature, pos: number): Type { + const parameterCount = getParameterCount(source); + const minArgumentCount = getMinArgumentCount(source); + const restType = getEffectiveRestType(source); + if (restType && pos >= parameterCount - 1) { + return pos === parameterCount - 1 ? restType : createArrayType(getIndexedAccessType(restType, numberType)); + } + const types = []; + const flags = []; + const names = []; + for (let i = pos; i < parameterCount; i++) { + if (!restType || i < parameterCount - 1) { + types.push(getTypeAtPosition(source, i)); + flags.push(i < minArgumentCount ? ElementFlags.Required : ElementFlags.Optional); } - const types = []; - const flags = []; - const names = []; - for (let i = pos; i < parameterCount; i++) { - if (!restType || i < parameterCount - 1) { - types.push(getTypeAtPosition(source, i)); - flags.push(i < minArgumentCount ? ElementFlags.Required : ElementFlags.Optional); - } - else { - types.push(restType); - flags.push(ElementFlags.Variadic); - } - const name = getNameableDeclarationAtPosition(source, i); - if (name) { - names.push(name); - } + else { + types.push(restType); + flags.push(ElementFlags.Variadic); + } + const name = getNameableDeclarationAtPosition(source, i); + if (name) { + names.push(name); } - return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); } + return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); + } - // Return the number of parameters in a signature. The rest parameter, if present, counts as one - // parameter. For example, the parameter count of (x: number, y: number, ...z: string[]) is 3 and - // the parameter count of (x: number, ...args: [number, ...string[], boolean])) is also 3. In the - // latter example, the effective rest type is [...string[], boolean]. - function getParameterCount(signature: Signature) { - const length = signature.parameters.length; - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[length - 1]); - if (isTupleType(restType)) { - return length + restType.target.fixedLength - (restType.target.hasRestElement ? 0 : 1); - } + // Return the number of parameters in a signature. The rest parameter, if present, counts as one + // parameter. For example, the parameter count of (x: number, y: number, ...z: string[]) is 3 and + // the parameter count of (x: number, ...args: [number, ...string[], boolean])) is also 3. In the + // latter example, the effective rest type is [...string[], boolean]. + function getParameterCount(signature: Signature) { + const length = signature.parameters.length; + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[length - 1]); + if (isTupleType(restType)) { + return length + restType.target.fixedLength - (restType.target.hasRestElement ? 0 : 1); } - return length; } + return length; + } - function getMinArgumentCount(signature: Signature, flags?: MinArgumentCountFlags) { - const strongArityForUntypedJS = flags! & MinArgumentCountFlags.StrongArityForUntypedJS; - const voidIsNonOptional = flags! & MinArgumentCountFlags.VoidIsNonOptional; - if (voidIsNonOptional || signature.resolvedMinArgumentCount === undefined) { - let minArgumentCount: number | undefined; - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - if (isTupleType(restType)) { - const firstOptionalIndex = findIndex(restType.target.elementFlags, f => !(f & ElementFlags.Required)); - const requiredCount = firstOptionalIndex < 0 ? restType.target.fixedLength : firstOptionalIndex; - if (requiredCount > 0) { - minArgumentCount = signature.parameters.length - 1 + requiredCount; - } - } - } - if (minArgumentCount === undefined) { - if (!strongArityForUntypedJS && signature.flags & SignatureFlags.IsUntypedSignatureInJSFile) { - return 0; + function getMinArgumentCount(signature: Signature, flags?: MinArgumentCountFlags) { + const strongArityForUntypedJS = flags! & MinArgumentCountFlags.StrongArityForUntypedJS; + const voidIsNonOptional = flags! & MinArgumentCountFlags.VoidIsNonOptional; + if (voidIsNonOptional || signature.resolvedMinArgumentCount === undefined) { + let minArgumentCount: number | undefined; + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + if (isTupleType(restType)) { + const firstOptionalIndex = findIndex(restType.target.elementFlags, f => !(f & ElementFlags.Required)); + const requiredCount = firstOptionalIndex < 0 ? restType.target.fixedLength : firstOptionalIndex; + if (requiredCount > 0) { + minArgumentCount = signature.parameters.length - 1 + requiredCount; } - minArgumentCount = signature.minArgumentCount; } - if (voidIsNonOptional) { - return minArgumentCount; + } + if (minArgumentCount === undefined) { + if (!strongArityForUntypedJS && signature.flags & SignatureFlags.IsUntypedSignatureInJSFile) { + return 0; } - for (let i = minArgumentCount - 1; i >= 0; i--) { - const type = getTypeAtPosition(signature, i); - if (filterType(type, acceptsVoid).flags & TypeFlags.Never) { - break; - } - minArgumentCount = i; + minArgumentCount = signature.minArgumentCount; + } + if (voidIsNonOptional) { + return minArgumentCount; + } + for (let i = minArgumentCount - 1; i >= 0; i--) { + const type = getTypeAtPosition(signature, i); + if (filterType(type, acceptsVoid).flags & TypeFlags.Never) { + break; } - signature.resolvedMinArgumentCount = minArgumentCount; + minArgumentCount = i; } - return signature.resolvedMinArgumentCount; + signature.resolvedMinArgumentCount = minArgumentCount; } + return signature.resolvedMinArgumentCount; + } - function hasEffectiveRestParameter(signature: Signature) { - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - return !isTupleType(restType) || restType.target.hasRestElement; - } - return false; + function hasEffectiveRestParameter(signature: Signature) { + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + return !isTupleType(restType) || restType.target.hasRestElement; } + return false; + } - function getEffectiveRestType(signature: Signature) { - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - if (!isTupleType(restType)) { - return restType; - } - if (restType.target.hasRestElement) { - return sliceTupleType(restType, restType.target.fixedLength); - } + function getEffectiveRestType(signature: Signature) { + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + if (!isTupleType(restType)) { + return restType; + } + if (restType.target.hasRestElement) { + return sliceTupleType(restType, restType.target.fixedLength); } - return undefined; } + return undefined; + } - function getNonArrayRestType(signature: Signature) { - const restType = getEffectiveRestType(signature); - return restType && !isArrayType(restType) && !isTypeAny(restType) && (getReducedType(restType).flags & TypeFlags.Never) === 0 ? restType : undefined; - } + function getNonArrayRestType(signature: Signature) { + const restType = getEffectiveRestType(signature); + return restType && !isArrayType(restType) && !isTypeAny(restType) && (getReducedType(restType).flags & TypeFlags.Never) === 0 ? restType : undefined; + } - function getTypeOfFirstParameterOfSignature(signature: Signature) { - return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType); - } + function getTypeOfFirstParameterOfSignature(signature: Signature) { + return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType); + } - function getTypeOfFirstParameterOfSignatureWithFallback(signature: Signature, fallbackType: Type) { - return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType; - } + function getTypeOfFirstParameterOfSignatureWithFallback(signature: Signature, fallbackType: Type) { + return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType; + } - function inferFromAnnotatedParameters(signature: Signature, context: Signature, inferenceContext: InferenceContext) { - const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - for (let i = 0; i < len; i++) { - const declaration = signature.parameters[i].valueDeclaration as ParameterDeclaration; - if (declaration.type) { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - inferTypes(inferenceContext.inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); - } + function inferFromAnnotatedParameters(signature: Signature, context: Signature, inferenceContext: InferenceContext) { + const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + for (let i = 0; i < len; i++) { + const declaration = signature.parameters[i].valueDeclaration as ParameterDeclaration; + if (declaration.type) { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + inferTypes(inferenceContext.inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); } } } + } - function assignContextualParameterTypes(signature: Signature, context: Signature) { - if (context.typeParameters) { - if (!signature.typeParameters) { - signature.typeParameters = context.typeParameters; - } - else { - return; // This signature has already has a contextual inference performed and cached on it! - } + function assignContextualParameterTypes(signature: Signature, context: Signature) { + if (context.typeParameters) { + if (!signature.typeParameters) { + signature.typeParameters = context.typeParameters; } - if (context.thisParameter) { - const parameter = signature.thisParameter; - if (!parameter || parameter.valueDeclaration && !(parameter.valueDeclaration as ParameterDeclaration).type) { - if (!parameter) { - signature.thisParameter = createSymbolWithType(context.thisParameter, /*type*/ undefined); - } - assignParameterType(signature.thisParameter!, getTypeOfSymbol(context.thisParameter)); - } - } - const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - for (let i = 0; i < len; i++) { - const parameter = signature.parameters[i]; - if (!getEffectiveTypeAnnotationNode(parameter.valueDeclaration as ParameterDeclaration)) { - const contextualParameterType = tryGetTypeAtPosition(context, i); - assignParameterType(parameter, contextualParameterType); - } + else { + return; // This signature has already has a contextual inference performed and cached on it! } - if (signatureHasRestParameter(signature)) { - // parameter might be a transient symbol generated by use of `arguments` in the function body. - const parameter = last(signature.parameters); - if (parameter.valueDeclaration - ? !getEffectiveTypeAnnotationNode(parameter.valueDeclaration as ParameterDeclaration) - // a declarationless parameter may still have a `.type` already set by its construction logic - // (which may pull a type from a jsdoc) - only allow fixing on `DeferredType` parameters with a fallback type - : !!(getCheckFlags(parameter) & CheckFlags.DeferredType) - ) { - const contextualParameterType = getRestTypeAtPosition(context, len); - assignParameterType(parameter, contextualParameterType); + } + if (context.thisParameter) { + const parameter = signature.thisParameter; + if (!parameter || parameter.valueDeclaration && !(parameter.valueDeclaration as ParameterDeclaration).type) { + if (!parameter) { + signature.thisParameter = createSymbolWithType(context.thisParameter, /*type*/ undefined); } + assignParameterType(signature.thisParameter!, getTypeOfSymbol(context.thisParameter)); } } - - function assignNonContextualParameterTypes(signature: Signature) { - if (signature.thisParameter) { - assignParameterType(signature.thisParameter); + const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + for (let i = 0; i < len; i++) { + const parameter = signature.parameters[i]; + if (!getEffectiveTypeAnnotationNode(parameter.valueDeclaration as ParameterDeclaration)) { + const contextualParameterType = tryGetTypeAtPosition(context, i); + assignParameterType(parameter, contextualParameterType); } - for (const parameter of signature.parameters) { - assignParameterType(parameter); + } + if (signatureHasRestParameter(signature)) { + // parameter might be a transient symbol generated by use of `arguments` in the function body. + const parameter = last(signature.parameters); + if (parameter.valueDeclaration + ? !getEffectiveTypeAnnotationNode(parameter.valueDeclaration as ParameterDeclaration) + // a declarationless parameter may still have a `.type` already set by its construction logic + // (which may pull a type from a jsdoc) - only allow fixing on `DeferredType` parameters with a fallback type + : !!(getCheckFlags(parameter) & CheckFlags.DeferredType) + ) { + const contextualParameterType = getRestTypeAtPosition(context, len); + assignParameterType(parameter, contextualParameterType); } } + } - function assignParameterType(parameter: Symbol, type?: Type) { - const links = getSymbolLinks(parameter); - if (!links.type) { - const declaration = parameter.valueDeclaration as ParameterDeclaration | undefined; - links.type = type || (declaration ? getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true) : getTypeOfSymbol(parameter)); - if (declaration && declaration.name.kind !== SyntaxKind.Identifier) { - // if inference didn't come up with anything but unknown, fall back to the binding pattern if present. - if (links.type === unknownType) { - links.type = getTypeFromBindingPattern(declaration.name); - } - assignBindingElementTypes(declaration.name, links.type); - } - } - else if (type) { - Debug.assertEqual(links.type, type, "Parameter symbol already has a cached type which differs from newly assigned type"); - } + function assignNonContextualParameterTypes(signature: Signature) { + if (signature.thisParameter) { + assignParameterType(signature.thisParameter); } + for (const parameter of signature.parameters) { + assignParameterType(parameter); + } + } - // When contextual typing assigns a type to a parameter that contains a binding pattern, we also need to push - // the destructured type into the contained binding elements. - function assignBindingElementTypes(pattern: BindingPattern, parentType: Type) { - for (const element of pattern.elements) { - if (!isOmittedExpression(element)) { - const type = getBindingElementTypeFromParentType(element, parentType); - if (element.name.kind === SyntaxKind.Identifier) { - getSymbolLinks(getSymbolOfNode(element)).type = type; - } - else { - assignBindingElementTypes(element.name, type); - } + function assignParameterType(parameter: Symbol, type?: Type) { + const links = getSymbolLinks(parameter); + if (!links.type) { + const declaration = parameter.valueDeclaration as ParameterDeclaration | undefined; + links.type = type || (declaration ? getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true) : getTypeOfSymbol(parameter)); + if (declaration && declaration.name.kind !== SyntaxKind.Identifier) { + // if inference didn't come up with anything but unknown, fall back to the binding pattern if present. + if (links.type === unknownType) { + links.type = getTypeFromBindingPattern(declaration.name); } + assignBindingElementTypes(declaration.name, links.type); } } + else if (type) { + Debug.assertEqual(links.type, type, "Parameter symbol already has a cached type which differs from newly assigned type"); + } + } - function createPromiseType(promisedType: Type): Type { - // creates a `Promise` type where `T` is the promisedType argument - const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); - if (globalPromiseType !== emptyGenericType) { - // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type - // Unwrap an `Awaited` to `T` to improve inference. - promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; - return createTypeReference(globalPromiseType, [promisedType]); + // When contextual typing assigns a type to a parameter that contains a binding pattern, we also need to push + // the destructured type into the contained binding elements. + function assignBindingElementTypes(pattern: BindingPattern, parentType: Type) { + for (const element of pattern.elements) { + if (!isOmittedExpression(element)) { + const type = getBindingElementTypeFromParentType(element, parentType); + if (element.name.kind === SyntaxKind.Identifier) { + getSymbolLinks(getSymbolOfNode(element)).type = type; + } + else { + assignBindingElementTypes(element.name, type); + } } + } + } - return unknownType; + function createPromiseType(promisedType: Type): Type { + // creates a `Promise` type where `T` is the promisedType argument + const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); + if (globalPromiseType !== emptyGenericType) { + // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type + // Unwrap an `Awaited` to `T` to improve inference. + promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; + return createTypeReference(globalPromiseType, [promisedType]); } - function createPromiseLikeType(promisedType: Type): Type { - // creates a `PromiseLike` type where `T` is the promisedType argument - const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ true); - if (globalPromiseLikeType !== emptyGenericType) { - // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type - // Unwrap an `Awaited` to `T` to improve inference. - promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; - return createTypeReference(globalPromiseLikeType, [promisedType]); - } + return unknownType; + } - return unknownType; + function createPromiseLikeType(promisedType: Type): Type { + // creates a `PromiseLike` type where `T` is the promisedType argument + const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ true); + if (globalPromiseLikeType !== emptyGenericType) { + // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type + // Unwrap an `Awaited` to `T` to improve inference. + promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; + return createTypeReference(globalPromiseLikeType, [promisedType]); } - function createPromiseReturnType(func: FunctionLikeDeclaration | ImportCall, promisedType: Type) { - const promiseType = createPromiseType(promisedType); - if (promiseType === unknownType) { - error(func, isImportCall(func) ? - Diagnostics.A_dynamic_import_call_returns_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option : - Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option); - return errorType; - } - else if (!getGlobalPromiseConstructorSymbol(/*reportErrors*/ true)) { - error(func, isImportCall(func) ? - Diagnostics.A_dynamic_import_call_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option : - Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); - } + return unknownType; + } - return promiseType; + function createPromiseReturnType(func: FunctionLikeDeclaration | ImportCall, promisedType: Type) { + const promiseType = createPromiseType(promisedType); + if (promiseType === unknownType) { + error(func, isImportCall(func) ? + Diagnostics.A_dynamic_import_call_returns_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option : + Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option); + return errorType; + } + else if (!getGlobalPromiseConstructorSymbol(/*reportErrors*/ true)) { + error(func, isImportCall(func) ? + Diagnostics.A_dynamic_import_call_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option : + Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); } - function createNewTargetExpressionType(targetType: Type): Type { - // Create a synthetic type `NewTargetExpression { target: TargetType; }` - const symbol = createSymbol(SymbolFlags.None, "NewTargetExpression" as __String); - - const targetPropertySymbol = createSymbol(SymbolFlags.Property, "target" as __String, CheckFlags.Readonly); - targetPropertySymbol.parent = symbol; - targetPropertySymbol.type = targetType; + return promiseType; + } - const members = createSymbolTable([targetPropertySymbol]); - symbol.members = members; - return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); - } + function createNewTargetExpressionType(targetType: Type): Type { + // Create a synthetic type `NewTargetExpression { target: TargetType; }` + const symbol = createSymbol(SymbolFlags.None, "NewTargetExpression" as __String); - function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { - if (!func.body) { - return errorType; - } + const targetPropertySymbol = createSymbol(SymbolFlags.Property, "target" as __String, CheckFlags.Readonly); + targetPropertySymbol.parent = symbol; + targetPropertySymbol.type = targetType; - const functionFlags = getFunctionFlags(func); - const isAsync = (functionFlags & FunctionFlags.Async) !== 0; - const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; + const members = createSymbolTable([targetPropertySymbol]); + symbol.members = members; + return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + } - let returnType: Type | undefined; - let yieldType: Type | undefined; - let nextType: Type | undefined; - let fallbackReturnType: Type = voidType; - if (func.body.kind !== SyntaxKind.Block) { // Async or normal arrow function - returnType = checkExpressionCached(func.body, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); - if (isAsync) { - // From within an async function you can return either a non-promise value or a promise. Any - // Promise/A+ compatible implementation will always assimilate any foreign promise, so the - // return type of the body should be unwrapped to its awaited type, which we will wrap in - // the native Promise type later in this function. - returnType = unwrapAwaitedType(checkAwaitedType(returnType, /*withAlias*/ false, /*errorNode*/ func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)); - } - } - else if (isGenerator) { // Generator or AsyncGenerator function - const returnTypes = checkAndAggregateReturnExpressionTypes(func, checkMode); - if (!returnTypes) { - fallbackReturnType = neverType; - } - else if (returnTypes.length > 0) { - returnType = getUnionType(returnTypes, UnionReduction.Subtype); - } - const { yieldTypes, nextTypes } = checkAndAggregateYieldOperandTypes(func, checkMode); - yieldType = some(yieldTypes) ? getUnionType(yieldTypes, UnionReduction.Subtype) : undefined; - nextType = some(nextTypes) ? getIntersectionType(nextTypes) : undefined; - } - else { // Async or normal function - const types = checkAndAggregateReturnExpressionTypes(func, checkMode); - if (!types) { - // For an async function, the return type will not be never, but rather a Promise for never. - return functionFlags & FunctionFlags.Async - ? createPromiseReturnType(func, neverType) // Async function - : neverType; // Normal function - } - if (types.length === 0) { - // For an async function, the return type will not be void, but rather a Promise for void. - return functionFlags & FunctionFlags.Async - ? createPromiseReturnType(func, voidType) // Async function - : voidType; // Normal function - } - - // Return a union of the return expression types. - returnType = getUnionType(types, UnionReduction.Subtype); - } - - if (returnType || yieldType || nextType) { - if (yieldType) reportErrorsFromWidening(func, yieldType, WideningKind.GeneratorYield); - if (returnType) reportErrorsFromWidening(func, returnType, WideningKind.FunctionReturn); - if (nextType) reportErrorsFromWidening(func, nextType, WideningKind.GeneratorNext); - if (returnType && isUnitType(returnType) || - yieldType && isUnitType(yieldType) || - nextType && isUnitType(nextType)) { - const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); - const contextualType = !contextualSignature ? undefined : - contextualSignature === getSignatureFromDeclaration(func) ? isGenerator ? undefined : returnType : - instantiateContextualType(getReturnTypeOfSignature(contextualSignature), func, /*contextFlags*/ undefined); - if (isGenerator) { - yieldType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(yieldType, contextualType, IterationTypeKind.Yield, isAsync); - returnType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(returnType, contextualType, IterationTypeKind.Return, isAsync); - nextType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(nextType, contextualType, IterationTypeKind.Next, isAsync); - } - else { - returnType = getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(returnType, contextualType, isAsync); - } - } + function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { + if (!func.body) { + return errorType; + } - if (yieldType) yieldType = getWidenedType(yieldType); - if (returnType) returnType = getWidenedType(returnType); - if (nextType) nextType = getWidenedType(nextType); - } + const functionFlags = getFunctionFlags(func); + const isAsync = (functionFlags & FunctionFlags.Async) !== 0; + const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; - if (isGenerator) { - return createGeneratorReturnType( - yieldType || neverType, - returnType || fallbackReturnType, - nextType || getContextualIterationType(IterationTypeKind.Next, func) || unknownType, - isAsync); - } - else { + let returnType: Type | undefined; + let yieldType: Type | undefined; + let nextType: Type | undefined; + let fallbackReturnType: Type = voidType; + if (func.body.kind !== SyntaxKind.Block) { // Async or normal arrow function + returnType = checkExpressionCached(func.body, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); + if (isAsync) { // From within an async function you can return either a non-promise value or a promise. Any // Promise/A+ compatible implementation will always assimilate any foreign promise, so the - // return type of the body is awaited type of the body, wrapped in a native Promise type. - return isAsync - ? createPromiseType(returnType || fallbackReturnType) - : returnType || fallbackReturnType; + // return type of the body should be unwrapped to its awaited type, which we will wrap in + // the native Promise type later in this function. + returnType = unwrapAwaitedType(checkAwaitedType(returnType, /*withAlias*/ false, /*errorNode*/ func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)); } } - - function createGeneratorReturnType(yieldType: Type, returnType: Type, nextType: Type, isAsyncGenerator: boolean) { - const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; - const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); - yieldType = resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || unknownType; - returnType = resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || unknownType; - nextType = resolver.resolveIterationType(nextType, /*errorNode*/ undefined) || unknownType; - if (globalGeneratorType === emptyGenericType) { - // Fall back to the global IterableIterator if returnType is assignable to the expected return iteration - // type of IterableIterator, and the expected next iteration type of IterableIterator is assignable to - // nextType. - const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); - const iterationTypes = globalType !== emptyGenericType ? getIterationTypesOfGlobalIterableType(globalType, resolver) : undefined; - const iterableIteratorReturnType = iterationTypes ? iterationTypes.returnType : anyType; - const iterableIteratorNextType = iterationTypes ? iterationTypes.nextType : undefinedType; - if (isTypeAssignableTo(returnType, iterableIteratorReturnType) && - isTypeAssignableTo(iterableIteratorNextType, nextType)) { - if (globalType !== emptyGenericType) { - return createTypeFromGenericGlobalType(globalType, [yieldType]); - } - - // The global IterableIterator type doesn't exist, so report an error - resolver.getGlobalIterableIteratorType(/*reportErrors*/ true); - return emptyObjectType; - } - - // The global Generator type doesn't exist, so report an error - resolver.getGlobalGeneratorType(/*reportErrors*/ true); - return emptyObjectType; + else if (isGenerator) { // Generator or AsyncGenerator function + const returnTypes = checkAndAggregateReturnExpressionTypes(func, checkMode); + if (!returnTypes) { + fallbackReturnType = neverType; } - - return createTypeFromGenericGlobalType(globalGeneratorType, [yieldType, returnType, nextType]); + else if (returnTypes.length > 0) { + returnType = getUnionType(returnTypes, UnionReduction.Subtype); + } + const { yieldTypes, nextTypes } = checkAndAggregateYieldOperandTypes(func, checkMode); + yieldType = some(yieldTypes) ? getUnionType(yieldTypes, UnionReduction.Subtype) : undefined; + nextType = some(nextTypes) ? getIntersectionType(nextTypes) : undefined; } - - function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined) { - const yieldTypes: Type[] = []; - const nextTypes: Type[] = []; - const isAsync = (getFunctionFlags(func) & FunctionFlags.Async) !== 0; - forEachYieldExpression(func.body as Block, yieldExpression => { - const yieldExpressionType = yieldExpression.expression ? checkExpression(yieldExpression.expression, checkMode) : undefinedWideningType; - pushIfUnique(yieldTypes, getYieldedTypeOfYieldExpression(yieldExpression, yieldExpressionType, anyType, isAsync)); - let nextType: Type | undefined; - if (yieldExpression.asteriskToken) { - const iterationTypes = getIterationTypesOfIterable( - yieldExpressionType, - isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, - yieldExpression.expression); - nextType = iterationTypes && iterationTypes.nextType; + else { // Async or normal function + const types = checkAndAggregateReturnExpressionTypes(func, checkMode); + if (!types) { + // For an async function, the return type will not be never, but rather a Promise for never. + return functionFlags & FunctionFlags.Async + ? createPromiseReturnType(func, neverType) // Async function + : neverType; // Normal function + } + if (types.length === 0) { + // For an async function, the return type will not be void, but rather a Promise for void. + return functionFlags & FunctionFlags.Async + ? createPromiseReturnType(func, voidType) // Async function + : voidType; // Normal function + } + + // Return a union of the return expression types. + returnType = getUnionType(types, UnionReduction.Subtype); + } + + if (returnType || yieldType || nextType) { + if (yieldType) reportErrorsFromWidening(func, yieldType, WideningKind.GeneratorYield); + if (returnType) reportErrorsFromWidening(func, returnType, WideningKind.FunctionReturn); + if (nextType) reportErrorsFromWidening(func, nextType, WideningKind.GeneratorNext); + if (returnType && isUnitType(returnType) || + yieldType && isUnitType(yieldType) || + nextType && isUnitType(nextType)) { + const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); + const contextualType = !contextualSignature ? undefined : + contextualSignature === getSignatureFromDeclaration(func) ? isGenerator ? undefined : returnType : + instantiateContextualType(getReturnTypeOfSignature(contextualSignature), func, /*contextFlags*/ undefined); + if (isGenerator) { + yieldType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(yieldType, contextualType, IterationTypeKind.Yield, isAsync); + returnType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(returnType, contextualType, IterationTypeKind.Return, isAsync); + nextType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(nextType, contextualType, IterationTypeKind.Next, isAsync); } else { - nextType = getContextualType(yieldExpression, /*contextFlags*/ undefined); + returnType = getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(returnType, contextualType, isAsync); } - if (nextType) pushIfUnique(nextTypes, nextType); - }); - return { yieldTypes, nextTypes }; + } + + if (yieldType) yieldType = getWidenedType(yieldType); + if (returnType) returnType = getWidenedType(returnType); + if (nextType) nextType = getWidenedType(nextType); } - function getYieldedTypeOfYieldExpression(node: YieldExpression, expressionType: Type, sentType: Type, isAsync: boolean): Type | undefined { - const errorNode = node.expression || node; - // A `yield*` expression effectively yields everything that its operand yields - const yieldedType = node.asteriskToken ? checkIteratedTypeOrElementType(isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, expressionType, sentType, errorNode) : expressionType; - return !isAsync ? yieldedType : getAwaitedType(yieldedType, errorNode, node.asteriskToken - ? Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member - : Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + if (isGenerator) { + return createGeneratorReturnType( + yieldType || neverType, + returnType || fallbackReturnType, + nextType || getContextualIterationType(IterationTypeKind.Next, func) || unknownType, + isAsync); } + else { + // From within an async function you can return either a non-promise value or a promise. Any + // Promise/A+ compatible implementation will always assimilate any foreign promise, so the + // return type of the body is awaited type of the body, wrapped in a native Promise type. + return isAsync + ? createPromiseType(returnType || fallbackReturnType) + : returnType || fallbackReturnType; + } + } - // Return the combined not-equal type facts for all cases except those between the start and end indices. - function getNotEqualFactsFromTypeofSwitch(start: number, end: number, witnesses: (string | undefined)[]): TypeFacts { - let facts: TypeFacts = TypeFacts.None; - for (let i = 0; i < witnesses.length; i++) { - const witness = i < start || i >= end ? witnesses[i] : undefined; - facts |= witness !== undefined ? typeofNEFacts.get(witness) || TypeFacts.TypeofNEHostObject : 0; + function createGeneratorReturnType(yieldType: Type, returnType: Type, nextType: Type, isAsyncGenerator: boolean) { + const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; + const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); + yieldType = resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || unknownType; + returnType = resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || unknownType; + nextType = resolver.resolveIterationType(nextType, /*errorNode*/ undefined) || unknownType; + if (globalGeneratorType === emptyGenericType) { + // Fall back to the global IterableIterator if returnType is assignable to the expected return iteration + // type of IterableIterator, and the expected next iteration type of IterableIterator is assignable to + // nextType. + const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); + const iterationTypes = globalType !== emptyGenericType ? getIterationTypesOfGlobalIterableType(globalType, resolver) : undefined; + const iterableIteratorReturnType = iterationTypes ? iterationTypes.returnType : anyType; + const iterableIteratorNextType = iterationTypes ? iterationTypes.nextType : undefinedType; + if (isTypeAssignableTo(returnType, iterableIteratorReturnType) && + isTypeAssignableTo(iterableIteratorNextType, nextType)) { + if (globalType !== emptyGenericType) { + return createTypeFromGenericGlobalType(globalType, [yieldType]); + } + + // The global IterableIterator type doesn't exist, so report an error + resolver.getGlobalIterableIteratorType(/*reportErrors*/ true); + return emptyObjectType; } - return facts; + + // The global Generator type doesn't exist, so report an error + resolver.getGlobalGeneratorType(/*reportErrors*/ true); + return emptyObjectType; } - function isExhaustiveSwitchStatement(node: SwitchStatement): boolean { - const links = getNodeLinks(node); - if (links.isExhaustive === undefined) { - links.isExhaustive = 0; // Indicate resolution is in process - const exhaustive = computeExhaustiveSwitchStatement(node); - if (links.isExhaustive === 0) { - links.isExhaustive = exhaustive; - } + return createTypeFromGenericGlobalType(globalGeneratorType, [yieldType, returnType, nextType]); + } + + function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined) { + const yieldTypes: Type[] = []; + const nextTypes: Type[] = []; + const isAsync = (getFunctionFlags(func) & FunctionFlags.Async) !== 0; + forEachYieldExpression(func.body as Block, yieldExpression => { + const yieldExpressionType = yieldExpression.expression ? checkExpression(yieldExpression.expression, checkMode) : undefinedWideningType; + pushIfUnique(yieldTypes, getYieldedTypeOfYieldExpression(yieldExpression, yieldExpressionType, anyType, isAsync)); + let nextType: Type | undefined; + if (yieldExpression.asteriskToken) { + const iterationTypes = getIterationTypesOfIterable( + yieldExpressionType, + isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, + yieldExpression.expression); + nextType = iterationTypes && iterationTypes.nextType; } - else if (links.isExhaustive === 0) { - links.isExhaustive = false; // Resolve circularity to false + else { + nextType = getContextualType(yieldExpression, /*contextFlags*/ undefined); } - return links.isExhaustive; + if (nextType) pushIfUnique(nextTypes, nextType); + }); + return { yieldTypes, nextTypes }; + } + + function getYieldedTypeOfYieldExpression(node: YieldExpression, expressionType: Type, sentType: Type, isAsync: boolean): Type | undefined { + const errorNode = node.expression || node; + // A `yield*` expression effectively yields everything that its operand yields + const yieldedType = node.asteriskToken ? checkIteratedTypeOrElementType(isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, expressionType, sentType, errorNode) : expressionType; + return !isAsync ? yieldedType : getAwaitedType(yieldedType, errorNode, node.asteriskToken + ? Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member + : Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + } + + // Return the combined not-equal type facts for all cases except those between the start and end indices. + function getNotEqualFactsFromTypeofSwitch(start: number, end: number, witnesses: (string | undefined)[]): TypeFacts { + let facts: TypeFacts = TypeFacts.None; + for (let i = 0; i < witnesses.length; i++) { + const witness = i < start || i >= end ? witnesses[i] : undefined; + facts |= witness !== undefined ? typeofNEFacts.get(witness) || TypeFacts.TypeofNEHostObject : 0; } + return facts; + } - function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean { - if (node.expression.kind === SyntaxKind.TypeOfExpression) { - const witnesses = getSwitchClauseTypeOfWitnesses(node); - if (!witnesses) { - return false; - } - const operandConstraint = getBaseConstraintOrType(checkExpressionCached((node.expression as TypeOfExpression).expression)); - // Get the not-equal flags for all handled cases. - const notEqualFacts = getNotEqualFactsFromTypeofSwitch(0, 0, witnesses); - if (operandConstraint.flags & TypeFlags.AnyOrUnknown) { - // We special case the top types to be exhaustive when all cases are handled. - return (TypeFacts.AllTypeofNE & notEqualFacts) === TypeFacts.AllTypeofNE; - } - // A missing not-equal flag indicates that the type wasn't handled by some case. - return !someType(operandConstraint, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts); + function isExhaustiveSwitchStatement(node: SwitchStatement): boolean { + const links = getNodeLinks(node); + if (links.isExhaustive === undefined) { + links.isExhaustive = 0; // Indicate resolution is in process + const exhaustive = computeExhaustiveSwitchStatement(node); + if (links.isExhaustive === 0) { + links.isExhaustive = exhaustive; } - const type = checkExpressionCached(node.expression); - if (!isLiteralType(type)) { + } + else if (links.isExhaustive === 0) { + links.isExhaustive = false; // Resolve circularity to false + } + return links.isExhaustive; + } + + function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean { + if (node.expression.kind === SyntaxKind.TypeOfExpression) { + const witnesses = getSwitchClauseTypeOfWitnesses(node); + if (!witnesses) { return false; } - const switchTypes = getSwitchClauseTypes(node); - if (!switchTypes.length || some(switchTypes, isNeitherUnitTypeNorNever)) { - return false; + const operandConstraint = getBaseConstraintOrType(checkExpressionCached((node.expression as TypeOfExpression).expression)); + // Get the not-equal flags for all handled cases. + const notEqualFacts = getNotEqualFactsFromTypeofSwitch(0, 0, witnesses); + if (operandConstraint.flags & TypeFlags.AnyOrUnknown) { + // We special case the top types to be exhaustive when all cases are handled. + return (TypeFacts.AllTypeofNE & notEqualFacts) === TypeFacts.AllTypeofNE; } - return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes); + // A missing not-equal flag indicates that the type wasn't handled by some case. + return !someType(operandConstraint, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts); } - - function functionHasImplicitReturn(func: FunctionLikeDeclaration) { - return func.endFlowNode && isReachableFlowNode(func.endFlowNode); + const type = checkExpressionCached(node.expression); + if (!isLiteralType(type)) { + return false; + } + const switchTypes = getSwitchClauseTypes(node); + if (!switchTypes.length || some(switchTypes, isNeitherUnitTypeNorNever)) { + return false; } + return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes); + } - /** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */ - function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined): Type[] | undefined { - const functionFlags = getFunctionFlags(func); - const aggregatedTypes: Type[] = []; - let hasReturnWithNoExpression = functionHasImplicitReturn(func); - let hasReturnOfTypeNever = false; - forEachReturnStatement(func.body as Block, returnStatement => { - const expr = returnStatement.expression; - if (expr) { - let type = checkExpressionCached(expr, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); - if (functionFlags & FunctionFlags.Async) { - // From within an async function you can return either a non-promise value or a promise. Any - // Promise/A+ compatible implementation will always assimilate any foreign promise, so the - // return type of the body should be unwrapped to its awaited type, which should be wrapped in - // the native Promise type by the caller. - type = unwrapAwaitedType(checkAwaitedType(type, /*withAlias*/ false, func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)); - } - if (type.flags & TypeFlags.Never) { - hasReturnOfTypeNever = true; - } - pushIfUnique(aggregatedTypes, type); + function functionHasImplicitReturn(func: FunctionLikeDeclaration) { + return func.endFlowNode && isReachableFlowNode(func.endFlowNode); + } + + /** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */ + function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined): Type[] | undefined { + const functionFlags = getFunctionFlags(func); + const aggregatedTypes: Type[] = []; + let hasReturnWithNoExpression = functionHasImplicitReturn(func); + let hasReturnOfTypeNever = false; + forEachReturnStatement(func.body as Block, returnStatement => { + const expr = returnStatement.expression; + if (expr) { + let type = checkExpressionCached(expr, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); + if (functionFlags & FunctionFlags.Async) { + // From within an async function you can return either a non-promise value or a promise. Any + // Promise/A+ compatible implementation will always assimilate any foreign promise, so the + // return type of the body should be unwrapped to its awaited type, which should be wrapped in + // the native Promise type by the caller. + type = unwrapAwaitedType(checkAwaitedType(type, /*withAlias*/ false, func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)); } - else { - hasReturnWithNoExpression = true; + if (type.flags & TypeFlags.Never) { + hasReturnOfTypeNever = true; } - }); - if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) { - return undefined; + pushIfUnique(aggregatedTypes, type); } - if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression && - !(isJSConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) { - // Javascript "callable constructors", containing eg `if (!(this instanceof A)) return new A()` should not add undefined - pushIfUnique(aggregatedTypes, undefinedType); + else { + hasReturnWithNoExpression = true; } - return aggregatedTypes; + }); + if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) { + return undefined; } - function mayReturnNever(func: FunctionLikeDeclaration): boolean { - switch (func.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return true; - case SyntaxKind.MethodDeclaration: - return func.parent.kind === SyntaxKind.ObjectLiteralExpression; - default: - return false; - } + if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression && + !(isJSConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) { + // Javascript "callable constructors", containing eg `if (!(this instanceof A)) return new A()` should not add undefined + pushIfUnique(aggregatedTypes, undefinedType); } + return aggregatedTypes; + } + function mayReturnNever(func: FunctionLikeDeclaration): boolean { + switch (func.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return true; + case SyntaxKind.MethodDeclaration: + return func.parent.kind === SyntaxKind.ObjectLiteralExpression; + default: + return false; + } + } - /** - * TypeScript Specification 1.0 (6.3) - July 2014 - * An explicitly typed function whose return type isn't the Void type, - * the Any type, or a union type containing the Void or Any type as a constituent - * must have at least one return statement somewhere in its body. - * An exception to this rule is if the function implementation consists of a single 'throw' statement. - * - * @param returnType - return type of the function, can be undefined if return type is not explicitly specified - */ - function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration | MethodSignature, returnType: Type | undefined) { - addLazyDiagnostic(checkAllCodePathsInNonVoidFunctionReturnOrThrowDiagnostics); - return; - - function checkAllCodePathsInNonVoidFunctionReturnOrThrowDiagnostics(): void { - const functionFlags = getFunctionFlags(func); - const type = returnType && unwrapReturnType(returnType, functionFlags); + /** + * TypeScript Specification 1.0 (6.3) - July 2014 + * An explicitly typed function whose return type isn't the Void type, + * the Any type, or a union type containing the Void or Any type as a constituent + * must have at least one return statement somewhere in its body. + * An exception to this rule is if the function implementation consists of a single 'throw' statement. + * + * @param returnType - return type of the function, can be undefined if return type is not explicitly specified + */ + function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration | MethodSignature, returnType: Type | undefined) { + addLazyDiagnostic(checkAllCodePathsInNonVoidFunctionReturnOrThrowDiagnostics); + return; + + function checkAllCodePathsInNonVoidFunctionReturnOrThrowDiagnostics(): void { + const functionFlags = getFunctionFlags(func); + const type = returnType && unwrapReturnType(returnType, functionFlags); - // Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions. - if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) { - return; - } + // Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions. + if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) { + return; + } - // If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check. - // also if HasImplicitReturn flag is not set this means that all codepaths in function body end with return or throw - if (func.kind === SyntaxKind.MethodSignature || nodeIsMissing(func.body) || func.body!.kind !== SyntaxKind.Block || !functionHasImplicitReturn(func)) { - return; - } + // If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check. + // also if HasImplicitReturn flag is not set this means that all codepaths in function body end with return or throw + if (func.kind === SyntaxKind.MethodSignature || nodeIsMissing(func.body) || func.body!.kind !== SyntaxKind.Block || !functionHasImplicitReturn(func)) { + return; + } - const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; - const errorNode = getEffectiveReturnTypeNode(func) || func; + const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; + const errorNode = getEffectiveReturnTypeNode(func) || func; - if (type && type.flags & TypeFlags.Never) { - error(errorNode, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); - } - else if (type && !hasExplicitReturn) { - // minimal check: function has syntactic return type annotation and no explicit return statements in the body - // this function does not conform to the specification. - error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); - } - else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { - error(errorNode, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); - } - else if (compilerOptions.noImplicitReturns) { - if (!type) { - // If return type annotation is omitted check if function has any explicit return statements. - // If it does not have any - its inferred return type is void - don't do any checks. - // Otherwise get inferred return type from function body and report error only if it is not void / anytype - if (!hasExplicitReturn) { - return; - } - const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func)); - if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) { - return; - } + if (type && type.flags & TypeFlags.Never) { + error(errorNode, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); + } + else if (type && !hasExplicitReturn) { + // minimal check: function has syntactic return type annotation and no explicit return statements in the body + // this function does not conform to the specification. + error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); + } + else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { + error(errorNode, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); + } + else if (compilerOptions.noImplicitReturns) { + if (!type) { + // If return type annotation is omitted check if function has any explicit return statements. + // If it does not have any - its inferred return type is void - don't do any checks. + // Otherwise get inferred return type from function body and report error only if it is not void / anytype + if (!hasExplicitReturn) { + return; + } + const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func)); + if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) { + return; } - error(errorNode, Diagnostics.Not_all_code_paths_return_a_value); } + error(errorNode, Diagnostics.Not_all_code_paths_return_a_value); } } + } - function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode): Type { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - checkNodeDeferred(node); + function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode): Type { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + checkNodeDeferred(node); - if (isFunctionExpression(node)) { - checkCollisionsForDeclarationName(node, node.name); - } + if (isFunctionExpression(node)) { + checkCollisionsForDeclarationName(node, node.name); + } - // The identityMapper object is used to indicate that function expressions are wildcards - if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) { - // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage - if (!getEffectiveReturnTypeNode(node) && !hasContextSensitiveParameters(node)) { - // Return plain anyFunctionType if there is no possibility we'll make inferences from the return type - const contextualSignature = getContextualSignature(node); - if (contextualSignature && couldContainTypeVariables(getReturnTypeOfSignature(contextualSignature))) { - const links = getNodeLinks(node); - if (links.contextFreeType) { - return links.contextFreeType; - } - const returnType = getReturnTypeFromBody(node, checkMode); - const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, emptyArray); - returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; - return links.contextFreeType = returnOnlyType; + // The identityMapper object is used to indicate that function expressions are wildcards + if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) { + // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage + if (!getEffectiveReturnTypeNode(node) && !hasContextSensitiveParameters(node)) { + // Return plain anyFunctionType if there is no possibility we'll make inferences from the return type + const contextualSignature = getContextualSignature(node); + if (contextualSignature && couldContainTypeVariables(getReturnTypeOfSignature(contextualSignature))) { + const links = getNodeLinks(node); + if (links.contextFreeType) { + return links.contextFreeType; } + const returnType = getReturnTypeFromBody(node, checkMode); + const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, emptyArray); + returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; + return links.contextFreeType = returnOnlyType; } - return anyFunctionType; } + return anyFunctionType; + } - // Grammar checking - const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); - if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { - checkGrammarForGenerator(node); - } + // Grammar checking + const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); + if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { + checkGrammarForGenerator(node); + } - contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); + contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); - return getTypeOfSymbol(getSymbolOfNode(node)); - } + return getTypeOfSymbol(getSymbolOfNode(node)); + } - 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. + 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); + // If a type check is started at a function expression that is an argument of a function call, obtaining the + // contextual type may recursively get back to here during overload resolution of the call. If so, we will have + // already assigned contextual types. if (!(links.flags & NodeCheckFlags.ContextChecked)) { - const contextualSignature = getContextualSignature(node); - // If a type check is started at a function expression that is an argument of a function call, obtaining the - // contextual type may recursively get back to here during overload resolution of the call. If so, we will have - // already assigned contextual types. - if (!(links.flags & NodeCheckFlags.ContextChecked)) { - links.flags |= NodeCheckFlags.ContextChecked; - const signature = firstOrUndefined(getSignaturesOfType(getTypeOfSymbol(getSymbolOfNode(node)), SignatureKind.Call)); - if (!signature) { - return; - } - if (isContextSensitive(node)) { - if (contextualSignature) { - const inferenceContext = getInferenceContext(node); - let instantiatedContextualSignature: Signature | undefined; - if (checkMode && checkMode & CheckMode.Inferential) { - inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); - const restType = getEffectiveRestType(contextualSignature); - if (restType && restType.flags & TypeFlags.TypeParameter) { - instantiatedContextualSignature = instantiateSignature(contextualSignature, inferenceContext!.nonFixingMapper); - } + links.flags |= NodeCheckFlags.ContextChecked; + const signature = firstOrUndefined(getSignaturesOfType(getTypeOfSymbol(getSymbolOfNode(node)), SignatureKind.Call)); + if (!signature) { + return; + } + if (isContextSensitive(node)) { + if (contextualSignature) { + const inferenceContext = getInferenceContext(node); + let instantiatedContextualSignature: Signature | undefined; + if (checkMode && checkMode & CheckMode.Inferential) { + inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); + const restType = getEffectiveRestType(contextualSignature); + if (restType && restType.flags & TypeFlags.TypeParameter) { + instantiatedContextualSignature = instantiateSignature(contextualSignature, inferenceContext!.nonFixingMapper); } - instantiatedContextualSignature ||= inferenceContext ? - instantiateSignature(contextualSignature, inferenceContext.mapper) : contextualSignature; - assignContextualParameterTypes(signature, instantiatedContextualSignature); - } - else { - // Force resolution of all parameter types such that the absence of a contextual type is consistently reflected. - assignNonContextualParameterTypes(signature); } + instantiatedContextualSignature ||= inferenceContext ? + instantiateSignature(contextualSignature, inferenceContext.mapper) : contextualSignature; + assignContextualParameterTypes(signature, instantiatedContextualSignature); } - if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { - const returnType = getReturnTypeFromBody(node, checkMode); - if (!signature.resolvedReturnType) { - signature.resolvedReturnType = returnType; - } + else { + // Force resolution of all parameter types such that the absence of a contextual type is consistently reflected. + assignNonContextualParameterTypes(signature); + } + } + if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { + const returnType = getReturnTypeFromBody(node, checkMode); + if (!signature.resolvedReturnType) { + signature.resolvedReturnType = returnType; } - checkSignatureDeclaration(node); } + checkSignatureDeclaration(node); } } + } - function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - const functionFlags = getFunctionFlags(node); - const returnType = getReturnTypeFromAnnotation(node); - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); + const functionFlags = getFunctionFlags(node); + const returnType = getReturnTypeFromAnnotation(node); + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); - if (node.body) { - if (!getEffectiveReturnTypeNode(node)) { - // There are some checks that are only performed in getReturnTypeFromBody, that may produce errors - // we need. An example is the noImplicitAny errors resulting from widening the return expression - // of a function. Because checking of function expression bodies is deferred, there was never an - // appropriate time to do this during the main walk of the file (see the comment at the top of - // checkFunctionExpressionBodies). So it must be done now. - getReturnTypeOfSignature(getSignatureFromDeclaration(node)); - } + if (node.body) { + if (!getEffectiveReturnTypeNode(node)) { + // There are some checks that are only performed in getReturnTypeFromBody, that may produce errors + // we need. An example is the noImplicitAny errors resulting from widening the return expression + // of a function. Because checking of function expression bodies is deferred, there was never an + // appropriate time to do this during the main walk of the file (see the comment at the top of + // checkFunctionExpressionBodies). So it must be done now. + getReturnTypeOfSignature(getSignatureFromDeclaration(node)); + } - if (node.body.kind === SyntaxKind.Block) { - checkSourceElement(node.body); - } - else { - // From within an async function you can return either a non-promise value or a promise. Any - // Promise/A+ compatible implementation will always assimilate any foreign promise, so we - // should not be checking assignability of a promise to the return type. Instead, we need to - // check assignability of the awaited type of the expression body against the promised type of - // its return type annotation. - const exprType = checkExpression(node.body); - const returnOrPromisedType = returnType && unwrapReturnType(returnType, functionFlags); - if (returnOrPromisedType) { - if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function - const awaitedType = checkAwaitedType(exprType, /*withAlias*/ false, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, node.body, node.body); - } - else { // Normal function - checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, node.body, node.body); - } + if (node.body.kind === SyntaxKind.Block) { + checkSourceElement(node.body); + } + else { + // From within an async function you can return either a non-promise value or a promise. Any + // Promise/A+ compatible implementation will always assimilate any foreign promise, so we + // should not be checking assignability of a promise to the return type. Instead, we need to + // check assignability of the awaited type of the expression body against the promised type of + // its return type annotation. + const exprType = checkExpression(node.body); + const returnOrPromisedType = returnType && unwrapReturnType(returnType, functionFlags); + if (returnOrPromisedType) { + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function + const awaitedType = checkAwaitedType(exprType, /*withAlias*/ false, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, node.body, node.body); + } + else { // Normal function + checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, node.body, node.body); } } } } + } - function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage, isAwaitValid = false): boolean { - if (!isTypeAssignableTo(type, numberOrBigIntType)) { - const awaitedType = isAwaitValid && getAwaitedTypeOfPromise(type); - errorAndMaybeSuggestAwait( - operand, - !!awaitedType && isTypeAssignableTo(awaitedType, numberOrBigIntType), - diagnostic); - return false; - } - return true; + function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage, isAwaitValid = false): boolean { + if (!isTypeAssignableTo(type, numberOrBigIntType)) { + const awaitedType = isAwaitValid && getAwaitedTypeOfPromise(type); + errorAndMaybeSuggestAwait( + operand, + !!awaitedType && isTypeAssignableTo(awaitedType, numberOrBigIntType), + diagnostic); + return false; } + return true; + } - function isReadonlyAssignmentDeclaration(d: Declaration) { - if (!isCallExpression(d)) { - return false; - } - if (!isBindableObjectDefinePropertyCall(d)) { - return false; + function isReadonlyAssignmentDeclaration(d: Declaration) { + if (!isCallExpression(d)) { + return false; + } + if (!isBindableObjectDefinePropertyCall(d)) { + return false; + } + const objectLitType = checkExpressionCached(d.arguments[2]); + const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); + if (valueType) { + const writableProp = getPropertyOfType(objectLitType, "writable" as __String); + const writableType = writableProp && getTypeOfSymbol(writableProp); + if (!writableType || writableType === falseType || writableType === regularFalseType) { + return true; } - const objectLitType = checkExpressionCached(d.arguments[2]); - const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); - if (valueType) { - const writableProp = getPropertyOfType(objectLitType, "writable" as __String); - const writableType = writableProp && getTypeOfSymbol(writableProp); - if (!writableType || writableType === falseType || writableType === regularFalseType) { + // We include this definition whereupon we walk back and check the type at the declaration because + // The usual definition of `Object.defineProperty` will _not_ cause literal types to be preserved in the + // argument types, should the type be contextualized by the call itself. + if (writableProp && writableProp.valueDeclaration && isPropertyAssignment(writableProp.valueDeclaration)) { + const initializer = writableProp.valueDeclaration.initializer; + const rawOriginalType = checkExpression(initializer); + if (rawOriginalType === falseType || rawOriginalType === regularFalseType) { return true; } - // We include this definition whereupon we walk back and check the type at the declaration because - // The usual definition of `Object.defineProperty` will _not_ cause literal types to be preserved in the - // argument types, should the type be contextualized by the call itself. - if (writableProp && writableProp.valueDeclaration && isPropertyAssignment(writableProp.valueDeclaration)) { - const initializer = writableProp.valueDeclaration.initializer; - const rawOriginalType = checkExpression(initializer); - if (rawOriginalType === falseType || rawOriginalType === regularFalseType) { - return true; - } - } - return false; } - const setProp = getPropertyOfType(objectLitType, "set" as __String); - return !setProp; - } - - function isReadonlySymbol(symbol: Symbol): boolean { - // The following symbols are considered read-only: - // Properties with a 'readonly' modifier - // Variables declared with 'const' - // Get accessors without matching set accessors - // Enum members - // Object.defineProperty assignments with writable false or no setter - // Unions and intersections of the above (unions and intersections eagerly set isReadonly on creation) - return !!(getCheckFlags(symbol) & CheckFlags.Readonly || - symbol.flags & SymbolFlags.Property && getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.Readonly || - symbol.flags & SymbolFlags.Variable && getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const || - symbol.flags & SymbolFlags.Accessor && !(symbol.flags & SymbolFlags.SetAccessor) || - symbol.flags & SymbolFlags.EnumMember || - some(symbol.declarations, isReadonlyAssignmentDeclaration) - ); + return false; } + const setProp = getPropertyOfType(objectLitType, "set" as __String); + return !setProp; + } - function isAssignmentToReadonlyEntity(expr: Expression, symbol: Symbol, assignmentKind: AssignmentKind) { - if (assignmentKind === AssignmentKind.None) { - // no assigment means it doesn't matter whether the entity is readonly - return false; - } - if (isReadonlySymbol(symbol)) { - // Allow assignments to readonly properties within constructors of the same class declaration. - if (symbol.flags & SymbolFlags.Property && - isAccessExpression(expr) && - expr.expression.kind === SyntaxKind.ThisKeyword) { - // Look for if this is the constructor for the class that `symbol` is a property of. - const ctor = getContainingFunction(expr); - if (!(ctor && (ctor.kind === SyntaxKind.Constructor || isJSConstructor(ctor)))) { - return true; - } - if (symbol.valueDeclaration) { - const isAssignmentDeclaration = isBinaryExpression(symbol.valueDeclaration); - const isLocalPropertyDeclaration = ctor.parent === symbol.valueDeclaration.parent; - const isLocalParameterProperty = ctor === symbol.valueDeclaration.parent; - const isLocalThisPropertyAssignment = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor.parent; - const isLocalThisPropertyAssignmentConstructorFunction = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor; - const isWriteableSymbol = - isLocalPropertyDeclaration - || isLocalParameterProperty - || isLocalThisPropertyAssignment - || isLocalThisPropertyAssignmentConstructorFunction; - return !isWriteableSymbol; - } + function isReadonlySymbol(symbol: Symbol): boolean { + // The following symbols are considered read-only: + // Properties with a 'readonly' modifier + // Variables declared with 'const' + // Get accessors without matching set accessors + // Enum members + // Object.defineProperty assignments with writable false or no setter + // Unions and intersections of the above (unions and intersections eagerly set isReadonly on creation) + return !!(getCheckFlags(symbol) & CheckFlags.Readonly || + symbol.flags & SymbolFlags.Property && getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.Readonly || + symbol.flags & SymbolFlags.Variable && getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const || + symbol.flags & SymbolFlags.Accessor && !(symbol.flags & SymbolFlags.SetAccessor) || + symbol.flags & SymbolFlags.EnumMember || + some(symbol.declarations, isReadonlyAssignmentDeclaration) + ); + } + + function isAssignmentToReadonlyEntity(expr: Expression, symbol: Symbol, assignmentKind: AssignmentKind) { + if (assignmentKind === AssignmentKind.None) { + // no assigment means it doesn't matter whether the entity is readonly + return false; + } + if (isReadonlySymbol(symbol)) { + // Allow assignments to readonly properties within constructors of the same class declaration. + if (symbol.flags & SymbolFlags.Property && + isAccessExpression(expr) && + expr.expression.kind === SyntaxKind.ThisKeyword) { + // Look for if this is the constructor for the class that `symbol` is a property of. + const ctor = getContainingFunction(expr); + if (!(ctor && (ctor.kind === SyntaxKind.Constructor || isJSConstructor(ctor)))) { + return true; + } + if (symbol.valueDeclaration) { + const isAssignmentDeclaration = isBinaryExpression(symbol.valueDeclaration); + const isLocalPropertyDeclaration = ctor.parent === symbol.valueDeclaration.parent; + const isLocalParameterProperty = ctor === symbol.valueDeclaration.parent; + const isLocalThisPropertyAssignment = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor.parent; + const isLocalThisPropertyAssignmentConstructorFunction = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor; + const isWriteableSymbol = + isLocalPropertyDeclaration + || isLocalParameterProperty + || isLocalThisPropertyAssignment + || isLocalThisPropertyAssignmentConstructorFunction; + return !isWriteableSymbol; } - return true; } - if (isAccessExpression(expr)) { - // references through namespace import should be readonly - const node = skipParentheses(expr.expression); - if (node.kind === SyntaxKind.Identifier) { - const symbol = getNodeLinks(node).resolvedSymbol!; - if (symbol.flags & SymbolFlags.Alias) { - const declaration = getDeclarationOfAliasSymbol(symbol); - return !!declaration && declaration.kind === SyntaxKind.NamespaceImport; - } + return true; + } + if (isAccessExpression(expr)) { + // references through namespace import should be readonly + const node = skipParentheses(expr.expression); + if (node.kind === SyntaxKind.Identifier) { + const symbol = getNodeLinks(node).resolvedSymbol!; + if (symbol.flags & SymbolFlags.Alias) { + const declaration = getDeclarationOfAliasSymbol(symbol); + return !!declaration && declaration.kind === SyntaxKind.NamespaceImport; } } - return false; } + return false; + } - function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage, invalidOptionalChainMessage: DiagnosticMessage): boolean { - // References are combinations of identifiers, parentheses, and property accesses. - const node = skipOuterExpressions(expr, OuterExpressionKinds.Assertions | OuterExpressionKinds.Parentheses); - if (node.kind !== SyntaxKind.Identifier && !isAccessExpression(node)) { - error(expr, invalidReferenceMessage); - return false; - } - if (node.flags & NodeFlags.OptionalChain) { - error(expr, invalidOptionalChainMessage); - return false; - } - return true; + function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage, invalidOptionalChainMessage: DiagnosticMessage): boolean { + // References are combinations of identifiers, parentheses, and property accesses. + const node = skipOuterExpressions(expr, OuterExpressionKinds.Assertions | OuterExpressionKinds.Parentheses); + if (node.kind !== SyntaxKind.Identifier && !isAccessExpression(node)) { + error(expr, invalidReferenceMessage); + return false; } + if (node.flags & NodeFlags.OptionalChain) { + error(expr, invalidOptionalChainMessage); + return false; + } + return true; + } - function checkDeleteExpression(node: DeleteExpression): Type { - checkExpression(node.expression); - const expr = skipParentheses(node.expression); - if (!isAccessExpression(expr)) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_a_property_reference); - return booleanType; - } - if (isPropertyAccessExpression(expr) && isPrivateIdentifier(expr.name)) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_private_identifier); - } - const links = getNodeLinks(expr); - const symbol = getExportSymbolOfValueSymbolIfExported(links.resolvedSymbol); - if (symbol) { - if (isReadonlySymbol(symbol)) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_read_only_property); - } - checkDeleteExpressionMustBeOptional(expr, symbol); - } + function checkDeleteExpression(node: DeleteExpression): Type { + checkExpression(node.expression); + const expr = skipParentheses(node.expression); + if (!isAccessExpression(expr)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_a_property_reference); return booleanType; } - - function checkDeleteExpressionMustBeOptional(expr: AccessExpression, symbol: Symbol) { - const type = getTypeOfSymbol(symbol); - if (strictNullChecks && - !(type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Never)) && - !(exactOptionalPropertyTypes ? symbol.flags & SymbolFlags.Optional : getTypeFacts(type) & TypeFacts.IsUndefined)) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_optional); + if (isPropertyAccessExpression(expr) && isPrivateIdentifier(expr.name)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_private_identifier); + } + const links = getNodeLinks(expr); + const symbol = getExportSymbolOfValueSymbolIfExported(links.resolvedSymbol); + if (symbol) { + if (isReadonlySymbol(symbol)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_read_only_property); } + checkDeleteExpressionMustBeOptional(expr, symbol); } + return booleanType; + } - function checkTypeOfExpression(node: TypeOfExpression): Type { - checkExpression(node.expression); - return typeofType; + function checkDeleteExpressionMustBeOptional(expr: AccessExpression, symbol: Symbol) { + const type = getTypeOfSymbol(symbol); + if (strictNullChecks && + !(type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Never)) && + !(exactOptionalPropertyTypes ? symbol.flags & SymbolFlags.Optional : getTypeFacts(type) & TypeFacts.IsUndefined)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_optional); } + } - function checkVoidExpression(node: VoidExpression): Type { - checkExpression(node.expression); - return undefinedWideningType; - } + function checkTypeOfExpression(node: TypeOfExpression): Type { + checkExpression(node.expression); + return typeofType; + } - function checkAwaitExpressionGrammar(node: AwaitExpression): void { - // Grammar checking - const container = getContainingFunctionOrClassStaticBlock(node); - if (container && isClassStaticBlockDeclaration(container)) { - error(node, Diagnostics.Await_expression_cannot_be_used_inside_a_class_static_block); - } - else if (!(node.flags & NodeFlags.AwaitContext)) { - if (isInTopLevelContext(node)) { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - let span: TextSpan | undefined; - if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { - span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); - const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, - Diagnostics.await_expressions_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module); - diagnostics.add(diagnostic); - } - switch (moduleKind) { - case ModuleKind.Node16: - case ModuleKind.NodeNext: - if (sourceFile.impliedNodeFormat === ModuleKind.CommonJS) { - span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); - diagnostics.add( - createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_current_file_is_a_CommonJS_module_and_cannot_use_await_at_the_top_level) - ); - break; - } - // fallthrough - case ModuleKind.ES2022: - case ModuleKind.ESNext: - case ModuleKind.System: - if (languageVersion >= ScriptTarget.ES2017) { - break; - } - // fallthrough - default: + function checkVoidExpression(node: VoidExpression): Type { + checkExpression(node.expression); + return undefinedWideningType; + } + + function checkAwaitExpressionGrammar(node: AwaitExpression): void { + // Grammar checking + const container = getContainingFunctionOrClassStaticBlock(node); + if (container && isClassStaticBlockDeclaration(container)) { + error(node, Diagnostics.Await_expression_cannot_be_used_inside_a_class_static_block); + } + else if (!(node.flags & NodeFlags.AwaitContext)) { + if (isInTopLevelContext(node)) { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + let span: TextSpan | undefined; + if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { + span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, + Diagnostics.await_expressions_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module); + diagnostics.add(diagnostic); + } + switch (moduleKind) { + case ModuleKind.Node16: + case ModuleKind.NodeNext: + if (sourceFile.impliedNodeFormat === ModuleKind.CommonJS) { span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); diagnostics.add( - createFileDiagnostic(sourceFile, span.start, span.length, - Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher - ) + createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_current_file_is_a_CommonJS_module_and_cannot_use_await_at_the_top_level) ); break; - } + } + // fallthrough + case ModuleKind.ES2022: + case ModuleKind.ESNext: + case ModuleKind.System: + if (languageVersion >= ScriptTarget.ES2017) { + break; + } + // fallthrough + default: + span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add( + createFileDiagnostic(sourceFile, span.start, span.length, + Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher + ) + ); + break; } } - else { - // use of 'await' in non-async function - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); - if (container && container.kind !== SyntaxKind.Constructor && (getFunctionFlags(container) & FunctionFlags.Async) === 0) { - const relatedInfo = createDiagnosticForNode(container, Diagnostics.Did_you_mean_to_mark_this_function_as_async); - addRelatedInfo(diagnostic, relatedInfo); - } - diagnostics.add(diagnostic); + } + else { + // use of 'await' in non-async function + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); + if (container && container.kind !== SyntaxKind.Constructor && (getFunctionFlags(container) & FunctionFlags.Async) === 0) { + const relatedInfo = createDiagnosticForNode(container, Diagnostics.Did_you_mean_to_mark_this_function_as_async); + addRelatedInfo(diagnostic, relatedInfo); } + diagnostics.add(diagnostic); } } + } - if (isInParameterInitializerBeforeContainingFunction(node)) { - error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); - } + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); } + } - function checkAwaitExpression(node: AwaitExpression): Type { - addLazyDiagnostic(() => checkAwaitExpressionGrammar(node)); + function checkAwaitExpression(node: AwaitExpression): Type { + addLazyDiagnostic(() => checkAwaitExpressionGrammar(node)); - const operandType = checkExpression(node.expression); - const awaitedType = checkAwaitedType(operandType, /*withAlias*/ true, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - if (awaitedType === operandType && !isErrorType(awaitedType) && !(operandType.flags & TypeFlags.AnyOrUnknown)) { - addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.await_has_no_effect_on_the_type_of_this_expression)); - } - return awaitedType; + const operandType = checkExpression(node.expression); + const awaitedType = checkAwaitedType(operandType, /*withAlias*/ true, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + if (awaitedType === operandType && !isErrorType(awaitedType) && !(operandType.flags & TypeFlags.AnyOrUnknown)) { + addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.await_has_no_effect_on_the_type_of_this_expression)); } + return awaitedType; + } - function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type { - const operandType = checkExpression(node.operand); - if (operandType === silentNeverType) { - return silentNeverType; - } - switch (node.operand.kind) { - case SyntaxKind.NumericLiteral: - switch (node.operator) { - case SyntaxKind.MinusToken: - return getFreshTypeOfLiteralType(getNumberLiteralType(-(node.operand as NumericLiteral).text)); - case SyntaxKind.PlusToken: - return getFreshTypeOfLiteralType(getNumberLiteralType(+(node.operand as NumericLiteral).text)); - } - break; - case SyntaxKind.BigIntLiteral: - if (node.operator === SyntaxKind.MinusToken) { - return getFreshTypeOfLiteralType(getBigIntLiteralType({ - negative: true, - base10Value: parsePseudoBigInt((node.operand as BigIntLiteral).text) - })); + function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type { + const operandType = checkExpression(node.operand); + if (operandType === silentNeverType) { + return silentNeverType; + } + switch (node.operand.kind) { + case SyntaxKind.NumericLiteral: + switch (node.operator) { + case SyntaxKind.MinusToken: + return getFreshTypeOfLiteralType(getNumberLiteralType(-(node.operand as NumericLiteral).text)); + case SyntaxKind.PlusToken: + return getFreshTypeOfLiteralType(getNumberLiteralType(+(node.operand as NumericLiteral).text)); + } + break; + case SyntaxKind.BigIntLiteral: + if (node.operator === SyntaxKind.MinusToken) { + return getFreshTypeOfLiteralType(getBigIntLiteralType({ + negative: true, + base10Value: parsePseudoBigInt((node.operand as BigIntLiteral).text) + })); + } + } + switch (node.operator) { + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + checkNonNullType(operandType, node.operand); + if (maybeTypeOfKindConsideringBaseConstraint(operandType, TypeFlags.ESSymbolLike)) { + error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator)); + } + if (node.operator === SyntaxKind.PlusToken) { + if (maybeTypeOfKindConsideringBaseConstraint(operandType, TypeFlags.BigIntLike)) { + error(node.operand, Diagnostics.Operator_0_cannot_be_applied_to_type_1, tokenToString(node.operator), typeToString(getBaseTypeOfLiteralType(operandType))); } - } - switch (node.operator) { - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - checkNonNullType(operandType, node.operand); - if (maybeTypeOfKindConsideringBaseConstraint(operandType, TypeFlags.ESSymbolLike)) { - error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator)); - } - if (node.operator === SyntaxKind.PlusToken) { - if (maybeTypeOfKindConsideringBaseConstraint(operandType, TypeFlags.BigIntLike)) { - error(node.operand, Diagnostics.Operator_0_cannot_be_applied_to_type_1, tokenToString(node.operator), typeToString(getBaseTypeOfLiteralType(operandType))); - } - return numberType; - } - return getUnaryResultType(operandType); - case SyntaxKind.ExclamationToken: - checkTruthinessExpression(node.operand); - const facts = getTypeFacts(operandType) & (TypeFacts.Truthy | TypeFacts.Falsy); - return facts === TypeFacts.Truthy ? falseType : - facts === TypeFacts.Falsy ? trueType : - booleanType; - case SyntaxKind.PlusPlusToken: - case SyntaxKind.MinusMinusToken: - const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand), - Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); - if (ok) { - // run check only if former checks succeeded to avoid reporting cascading errors - checkReferenceExpression( - node.operand, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); - } - return getUnaryResultType(operandType); - } - return errorType; + return numberType; + } + return getUnaryResultType(operandType); + case SyntaxKind.ExclamationToken: + checkTruthinessExpression(node.operand); + const facts = getTypeFacts(operandType) & (TypeFacts.Truthy | TypeFacts.Falsy); + return facts === TypeFacts.Truthy ? falseType : + facts === TypeFacts.Falsy ? trueType : + booleanType; + case SyntaxKind.PlusPlusToken: + case SyntaxKind.MinusMinusToken: + const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand), + Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); + if (ok) { + // run check only if former checks succeeded to avoid reporting cascading errors + checkReferenceExpression( + node.operand, + Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, + Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); + } + return getUnaryResultType(operandType); } + return errorType; + } - function checkPostfixUnaryExpression(node: PostfixUnaryExpression): Type { - const operandType = checkExpression(node.operand); - if (operandType === silentNeverType) { - return silentNeverType; - } - const ok = checkArithmeticOperandType( + function checkPostfixUnaryExpression(node: PostfixUnaryExpression): Type { + const operandType = checkExpression(node.operand); + if (operandType === silentNeverType) { + return silentNeverType; + } + const ok = checkArithmeticOperandType( + node.operand, + checkNonNullType(operandType, node.operand), + Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); + if (ok) { + // run check only if former checks succeeded to avoid reporting cascading errors + checkReferenceExpression( node.operand, - checkNonNullType(operandType, node.operand), - Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); - if (ok) { - // run check only if former checks succeeded to avoid reporting cascading errors - checkReferenceExpression( - node.operand, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, - Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); - } - return getUnaryResultType(operandType); + Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, + Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); } + return getUnaryResultType(operandType); + } - function getUnaryResultType(operandType: Type): Type { - if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { - return isTypeAssignableToKind(operandType, TypeFlags.AnyOrUnknown) || maybeTypeOfKind(operandType, TypeFlags.NumberLike) - ? numberOrBigIntType - : bigintType; - } - // If it's not a bigint type, implicit coercion will result in a number - return numberType; + function getUnaryResultType(operandType: Type): Type { + if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { + return isTypeAssignableToKind(operandType, TypeFlags.AnyOrUnknown) || maybeTypeOfKind(operandType, TypeFlags.NumberLike) + ? numberOrBigIntType + : bigintType; } + // If it's not a bigint type, implicit coercion will result in a number + return numberType; + } - function maybeTypeOfKindConsideringBaseConstraint(type: Type, kind: TypeFlags): boolean { - if (maybeTypeOfKind(type, kind)) { - return true; - } - - const baseConstraint = getBaseConstraintOrType(type); - return !!baseConstraint && maybeTypeOfKind(baseConstraint, kind); + function maybeTypeOfKindConsideringBaseConstraint(type: Type, kind: TypeFlags): boolean { + if (maybeTypeOfKind(type, kind)) { + return true; } - // Return true if type might be of the given kind. A union or intersection type might be of a given - // kind if at least one constituent type is of the given kind. - function maybeTypeOfKind(type: Type, kind: TypeFlags): boolean { - if (type.flags & kind) { - return true; - } - if (type.flags & TypeFlags.UnionOrIntersection) { - const types = (type as UnionOrIntersectionType).types; - for (const t of types) { - if (maybeTypeOfKind(t, kind)) { - return true; - } + const baseConstraint = getBaseConstraintOrType(type); + return !!baseConstraint && maybeTypeOfKind(baseConstraint, kind); + } + + // Return true if type might be of the given kind. A union or intersection type might be of a given + // kind if at least one constituent type is of the given kind. + function maybeTypeOfKind(type: Type, kind: TypeFlags): boolean { + if (type.flags & kind) { + return true; + } + if (type.flags & TypeFlags.UnionOrIntersection) { + const types = (type as UnionOrIntersectionType).types; + for (const t of types) { + if (maybeTypeOfKind(t, kind)) { + return true; } } - return false; } + return false; + } - function isTypeAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { - if (source.flags & kind) { - return true; - } - if (strict && source.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null)) { - return false; - } - return !!(kind & TypeFlags.NumberLike) && isTypeAssignableTo(source, numberType) || - !!(kind & TypeFlags.BigIntLike) && isTypeAssignableTo(source, bigintType) || - !!(kind & TypeFlags.StringLike) && isTypeAssignableTo(source, stringType) || - !!(kind & TypeFlags.BooleanLike) && isTypeAssignableTo(source, booleanType) || - !!(kind & TypeFlags.Void) && isTypeAssignableTo(source, voidType) || - !!(kind & TypeFlags.Never) && isTypeAssignableTo(source, neverType) || - !!(kind & TypeFlags.Null) && isTypeAssignableTo(source, nullType) || - !!(kind & TypeFlags.Undefined) && isTypeAssignableTo(source, undefinedType) || - !!(kind & TypeFlags.ESSymbol) && isTypeAssignableTo(source, esSymbolType) || - !!(kind & TypeFlags.NonPrimitive) && isTypeAssignableTo(source, nonPrimitiveType); + function isTypeAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { + if (source.flags & kind) { + return true; } - - function allTypesAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { - return source.flags & TypeFlags.Union ? - every((source as UnionType).types, subType => allTypesAssignableToKind(subType, kind, strict)) : - isTypeAssignableToKind(source, kind, strict); + if (strict && source.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null)) { + return false; } + return !!(kind & TypeFlags.NumberLike) && isTypeAssignableTo(source, numberType) || + !!(kind & TypeFlags.BigIntLike) && isTypeAssignableTo(source, bigintType) || + !!(kind & TypeFlags.StringLike) && isTypeAssignableTo(source, stringType) || + !!(kind & TypeFlags.BooleanLike) && isTypeAssignableTo(source, booleanType) || + !!(kind & TypeFlags.Void) && isTypeAssignableTo(source, voidType) || + !!(kind & TypeFlags.Never) && isTypeAssignableTo(source, neverType) || + !!(kind & TypeFlags.Null) && isTypeAssignableTo(source, nullType) || + !!(kind & TypeFlags.Undefined) && isTypeAssignableTo(source, undefinedType) || + !!(kind & TypeFlags.ESSymbol) && isTypeAssignableTo(source, esSymbolType) || + !!(kind & TypeFlags.NonPrimitive) && isTypeAssignableTo(source, nonPrimitiveType); + } - function isConstEnumObjectType(type: Type): boolean { - return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol); - } + function allTypesAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { + return source.flags & TypeFlags.Union ? + every((source as UnionType).types, subType => allTypesAssignableToKind(subType, kind, strict)) : + isTypeAssignableToKind(source, kind, strict); + } - function isConstEnumSymbol(symbol: Symbol): boolean { - return (symbol.flags & SymbolFlags.ConstEnum) !== 0; - } + function isConstEnumObjectType(type: Type): boolean { + return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol); + } - function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } - // TypeScript 1.0 spec (April 2014): 4.15.4 - // The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type, - // and the right operand to be of type Any, a subtype of the 'Function' interface type, or have a call or construct signature. - // The result is always of the Boolean primitive type. - // NOTE: do not raise error if leftType is unknown as related error was already reported - if (!isTypeAny(leftType) && - allTypesAssignableToKind(leftType, TypeFlags.Primitive)) { - error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); - } - // NOTE: do not raise error if right is unknown as related error was already reported - if (!(isTypeAny(rightType) || typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) { - error(right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_of_type_any_or_of_a_type_assignable_to_the_Function_interface_type); - } - return booleanType; - } + function isConstEnumSymbol(symbol: Symbol): boolean { + return (symbol.flags & SymbolFlags.ConstEnum) !== 0; + } - function hasEmptyObjectIntersection(type: Type): boolean { - return someType(type, t => t === unknownEmptyObjectType || !!(t.flags & TypeFlags.Intersection) && some((t as IntersectionType).types, isEmptyAnonymousObjectType)); + function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; + } + // TypeScript 1.0 spec (April 2014): 4.15.4 + // The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type, + // and the right operand to be of type Any, a subtype of the 'Function' interface type, or have a call or construct signature. + // The result is always of the Boolean primitive type. + // NOTE: do not raise error if leftType is unknown as related error was already reported + if (!isTypeAny(leftType) && + allTypesAssignableToKind(leftType, TypeFlags.Primitive)) { + error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); } + // NOTE: do not raise error if right is unknown as related error was already reported + if (!(isTypeAny(rightType) || typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) { + error(right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_of_type_any_or_of_a_type_assignable_to_the_Function_interface_type); + } + return booleanType; + } - function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } - if (isPrivateIdentifier(left)) { - if (languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(left, ExternalEmitHelpers.ClassPrivateFieldIn); - } - // Unlike in 'checkPrivateIdentifierExpression' we now have access to the RHS type - // which provides us with the opportunity to emit more detailed errors - if (!getNodeLinks(left).resolvedSymbol && getContainingClass(left)) { - const isUncheckedJS = isUncheckedJSSuggestion(left, rightType.symbol, /*excludeClasses*/ true); - reportNonexistentProperty(left, rightType, isUncheckedJS); - } + function hasEmptyObjectIntersection(type: Type): boolean { + return someType(type, t => t === unknownEmptyObjectType || !!(t.flags & TypeFlags.Intersection) && some((t as IntersectionType).types, isEmptyAnonymousObjectType)); + } + + function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; + } + if (isPrivateIdentifier(left)) { + if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(left, ExternalEmitHelpers.ClassPrivateFieldIn); } - else { - // The type of the lef operand must be assignable to string, number, or symbol. - checkTypeAssignableTo(checkNonNullType(leftType, left), stringNumberSymbolType, left); + // Unlike in 'checkPrivateIdentifierExpression' we now have access to the RHS type + // which provides us with the opportunity to emit more detailed errors + if (!getNodeLinks(left).resolvedSymbol && getContainingClass(left)) { + const isUncheckedJS = isUncheckedJSSuggestion(left, rightType.symbol, /*excludeClasses*/ true); + reportNonexistentProperty(left, rightType, isUncheckedJS); } - // The type of the right operand must be assignable to 'object'. - if (checkTypeAssignableTo(checkNonNullType(rightType, right), nonPrimitiveType, right)) { - // The {} type is assignable to the object type, yet {} might represent a primitive type. Here we - // detect and error on {} that results from narrowing the unknown type, as well as intersections - // that include {} (we know that the other types in such intersections are assignable to object - // since we already checked for that). - if (hasEmptyObjectIntersection(rightType)) { - error(right, Diagnostics.Type_0_may_represent_a_primitive_value_which_is_not_permitted_as_the_right_operand_of_the_in_operator, typeToString(rightType)); - } + } + else { + // The type of the lef operand must be assignable to string, number, or symbol. + checkTypeAssignableTo(checkNonNullType(leftType, left), stringNumberSymbolType, left); + } + // The type of the right operand must be assignable to 'object'. + if (checkTypeAssignableTo(checkNonNullType(rightType, right), nonPrimitiveType, right)) { + // The {} type is assignable to the object type, yet {} might represent a primitive type. Here we + // detect and error on {} that results from narrowing the unknown type, as well as intersections + // that include {} (we know that the other types in such intersections are assignable to object + // since we already checked for that). + if (hasEmptyObjectIntersection(rightType)) { + error(right, Diagnostics.Type_0_may_represent_a_primitive_value_which_is_not_permitted_as_the_right_operand_of_the_in_operator, typeToString(rightType)); } - // The result is always of the Boolean primitive type. - return booleanType; } + // The result is always of the Boolean primitive type. + return booleanType; + } - function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type, rightIsThis?: boolean): Type { - const properties = node.properties; - if (strictNullChecks && properties.length === 0) { - return checkNonNullType(sourceType, node); - } - for (let i = 0; i < properties.length; i++) { - checkObjectLiteralDestructuringPropertyAssignment(node, sourceType, i, properties, rightIsThis); - } - return sourceType; + function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type, rightIsThis?: boolean): Type { + const properties = node.properties; + if (strictNullChecks && properties.length === 0) { + return checkNonNullType(sourceType, node); } + 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(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); - if (isTypeUsableAsPropertyName(exprType)) { - const text = getPropertyNameFromType(exprType); - const prop = getPropertyOfType(objectLiteralType, text); - if (prop) { - markPropertyAsReferenced(prop, property, rightIsThis); - checkPropertyAccessibility(property, /*isSuper*/ false, /*writing*/ true, objectLiteralType, prop); - } + /** Note: If property cannot be a SpreadAssignment, then allProperties does not need to be provided */ + 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); + if (isTypeUsableAsPropertyName(exprType)) { + const text = getPropertyNameFromType(exprType); + const prop = getPropertyOfType(objectLiteralType, text); + if (prop) { + markPropertyAsReferenced(prop, property, rightIsThis); + checkPropertyAccessibility(property, /*isSuper*/ false, /*writing*/ true, objectLiteralType, prop); } - const elementType = getIndexedAccessType(objectLiteralType, exprType, AccessFlags.ExpressionPosition, name); - const type = getFlowTypeOfDestructuring(property, elementType); - return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } - else if (property.kind === SyntaxKind.SpreadAssignment) { - if (propertyIndex < properties.length - 1) { - error(property, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + const elementType = getIndexedAccessType(objectLiteralType, exprType, AccessFlags.ExpressionPosition, name); + const type = getFlowTypeOfDestructuring(property, elementType); + return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); + } + else if (property.kind === SyntaxKind.SpreadAssignment) { + if (propertyIndex < properties.length - 1) { + error(property, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + } + else { + if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); } - 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 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); + } + else { + error(property, Diagnostics.Property_assignment_expected); + } + } + + function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type { + const elements = node.elements; + if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); + } + // This elementType will be used if the specific property corresponding to this index is not + // present (aka the tuple element property). This call also checks that the parentType is in + // fact an iterable or array (depending on target language). + const possiblyOutOfBoundsType = checkIteratedTypeOrElementType(IterationUse.Destructuring | IterationUse.PossiblyOutOfBounds, sourceType, undefinedType, node) || errorType; + let inBoundsType: Type | undefined = compilerOptions.noUncheckedIndexedAccess ? undefined: possiblyOutOfBoundsType; + for (let i = 0; i < elements.length; i++) { + let type = possiblyOutOfBoundsType; + if (node.elements[i].kind === SyntaxKind.SpreadElement) { + type = inBoundsType = inBoundsType ?? (checkIteratedTypeOrElementType(IterationUse.Destructuring, sourceType, undefinedType, node) || errorType); } + checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, type, checkMode); } + return sourceType; + } - function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type { - const elements = node.elements; - if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); + function checkArrayLiteralDestructuringElementAssignment(node: ArrayLiteralExpression, sourceType: Type, + elementIndex: number, elementType: Type, checkMode?: CheckMode) { + const elements = node.elements; + const element = elements[elementIndex]; + if (element.kind !== SyntaxKind.OmittedExpression) { + if (element.kind !== SyntaxKind.SpreadElement) { + const indexType = getNumberLiteralType(elementIndex); + if (isArrayLikeType(sourceType)) { + // We create a synthetic expression so that getIndexedAccessType doesn't get confused + // when the element is a SyntaxKind.ElementAccessExpression. + const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0); + const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, accessFlags, createSyntheticExpression(element, indexType)) || errorType; + const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType; + const type = getFlowTypeOfDestructuring(element, assignedType); + return checkDestructuringAssignment(element, type, checkMode); + } + return checkDestructuringAssignment(element, elementType, checkMode); + } + if (elementIndex < elements.length - 1) { + error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } - // This elementType will be used if the specific property corresponding to this index is not - // present (aka the tuple element property). This call also checks that the parentType is in - // fact an iterable or array (depending on target language). - const possiblyOutOfBoundsType = checkIteratedTypeOrElementType(IterationUse.Destructuring | IterationUse.PossiblyOutOfBounds, sourceType, undefinedType, node) || errorType; - let inBoundsType: Type | undefined = compilerOptions.noUncheckedIndexedAccess ? undefined: possiblyOutOfBoundsType; - for (let i = 0; i < elements.length; i++) { - let type = possiblyOutOfBoundsType; - if (node.elements[i].kind === SyntaxKind.SpreadElement) { - type = inBoundsType = inBoundsType ?? (checkIteratedTypeOrElementType(IterationUse.Destructuring, sourceType, undefinedType, node) || errorType); - } - checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, type, checkMode); - } - return sourceType; - } - - function checkArrayLiteralDestructuringElementAssignment(node: ArrayLiteralExpression, sourceType: Type, - elementIndex: number, elementType: Type, checkMode?: CheckMode) { - const elements = node.elements; - const element = elements[elementIndex]; - if (element.kind !== SyntaxKind.OmittedExpression) { - if (element.kind !== SyntaxKind.SpreadElement) { - const indexType = getNumberLiteralType(elementIndex); - if (isArrayLikeType(sourceType)) { - // We create a synthetic expression so that getIndexedAccessType doesn't get confused - // when the element is a SyntaxKind.ElementAccessExpression. - const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0); - const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, accessFlags, createSyntheticExpression(element, indexType)) || errorType; - const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType; - const type = getFlowTypeOfDestructuring(element, assignedType); - return checkDestructuringAssignment(element, type, checkMode); - } - return checkDestructuringAssignment(element, elementType, checkMode); - } - if (elementIndex < elements.length - 1) { - error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + else { + const restExpression = (element as SpreadElement).expression; + if (restExpression.kind === SyntaxKind.BinaryExpression && (restExpression as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + error((restExpression as BinaryExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer); } else { - const restExpression = (element as SpreadElement).expression; - if (restExpression.kind === SyntaxKind.BinaryExpression && (restExpression as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - error((restExpression as BinaryExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer); - } - else { - checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - const type = everyType(sourceType, isTupleType) ? - mapType(sourceType, t => sliceTupleType(t as TupleTypeReference, elementIndex)) : - createArrayType(elementType); - return checkDestructuringAssignment(restExpression, type, checkMode); - } + checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + const type = everyType(sourceType, isTupleType) ? + mapType(sourceType, t => sliceTupleType(t as TupleTypeReference, elementIndex)) : + createArrayType(elementType); + return checkDestructuringAssignment(restExpression, type, checkMode); } } - return undefined; } + return undefined; + } - function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode, rightIsThis?: boolean): Type { - let target: Expression; - if (exprOrAssignment.kind === SyntaxKind.ShorthandPropertyAssignment) { - const prop = exprOrAssignment as ShorthandPropertyAssignment; - if (prop.objectAssignmentInitializer) { - // In strict null checking mode, if a default value of a non-undefined type is specified, remove - // undefined from the final type. - if (strictNullChecks && - !(getTypeFacts(checkExpression(prop.objectAssignmentInitializer)) & TypeFacts.IsUndefined)) { - sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); - } - checkBinaryLikeExpression(prop.name, prop.equalsToken!, prop.objectAssignmentInitializer, checkMode); - } - target = (exprOrAssignment as ShorthandPropertyAssignment).name; - } - else { - target = exprOrAssignment; - } - - if (target.kind === SyntaxKind.BinaryExpression && (target as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - checkBinaryExpression(target as BinaryExpression, checkMode); - target = (target as BinaryExpression).left; - // A default value is specified, so remove undefined from the final type. - if (strictNullChecks) { + function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode, rightIsThis?: boolean): Type { + let target: Expression; + if (exprOrAssignment.kind === SyntaxKind.ShorthandPropertyAssignment) { + const prop = exprOrAssignment as ShorthandPropertyAssignment; + if (prop.objectAssignmentInitializer) { + // In strict null checking mode, if a default value of a non-undefined type is specified, remove + // undefined from the final type. + if (strictNullChecks && + !(getTypeFacts(checkExpression(prop.objectAssignmentInitializer)) & TypeFacts.IsUndefined)) { sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); } + checkBinaryLikeExpression(prop.name, prop.equalsToken!, prop.objectAssignmentInitializer, checkMode); } - if (target.kind === SyntaxKind.ObjectLiteralExpression) { - return checkObjectLiteralAssignment(target as ObjectLiteralExpression, sourceType, rightIsThis); - } - if (target.kind === SyntaxKind.ArrayLiteralExpression) { - return checkArrayLiteralAssignment(target as ArrayLiteralExpression, sourceType, checkMode); - } - return checkReferenceAssignment(target, sourceType, checkMode); + target = (exprOrAssignment as ShorthandPropertyAssignment).name; + } + else { + target = exprOrAssignment; } - function checkReferenceAssignment(target: Expression, sourceType: Type, checkMode?: CheckMode): Type { - const targetType = checkExpression(target, checkMode); - const error = target.parent.kind === SyntaxKind.SpreadAssignment ? - Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access : - Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access; - const optionalError = target.parent.kind === SyntaxKind.SpreadAssignment ? - Diagnostics.The_target_of_an_object_rest_assignment_may_not_be_an_optional_property_access : - Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access; - if (checkReferenceExpression(target, error, optionalError)) { - checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target); - } - if (isPrivateIdentifierPropertyAccessExpression(target)) { - checkExternalEmitHelpers(target.parent, ExternalEmitHelpers.ClassPrivateFieldSet); + if (target.kind === SyntaxKind.BinaryExpression && (target as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + checkBinaryExpression(target as BinaryExpression, checkMode); + target = (target as BinaryExpression).left; + // A default value is specified, so remove undefined from the final type. + if (strictNullChecks) { + sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); } - return sourceType; } + if (target.kind === SyntaxKind.ObjectLiteralExpression) { + return checkObjectLiteralAssignment(target as ObjectLiteralExpression, sourceType, rightIsThis); + } + if (target.kind === SyntaxKind.ArrayLiteralExpression) { + return checkArrayLiteralAssignment(target as ArrayLiteralExpression, sourceType, checkMode); + } + return checkReferenceAssignment(target, sourceType, checkMode); + } - /** - * This is a *shallow* check: An expression is side-effect-free if the - * evaluation of the expression *itself* cannot produce side effects. - * For example, x++ / 3 is side-effect free because the / operator - * does not have side effects. - * The intent is to "smell test" an expression for correctness in positions where - * its value is discarded (e.g. the left side of the comma operator). - */ - function isSideEffectFree(node: Node): boolean { - node = skipParentheses(node); - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.StringLiteral: - case SyntaxKind.RegularExpressionLiteral: - case SyntaxKind.TaggedTemplateExpression: - case SyntaxKind.TemplateExpression: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.UndefinedKeyword: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ClassExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.TypeOfExpression: - case SyntaxKind.NonNullExpression: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxElement: - return true; - - case SyntaxKind.ConditionalExpression: - return isSideEffectFree((node as ConditionalExpression).whenTrue) && - isSideEffectFree((node as ConditionalExpression).whenFalse); + function checkReferenceAssignment(target: Expression, sourceType: Type, checkMode?: CheckMode): Type { + const targetType = checkExpression(target, checkMode); + const error = target.parent.kind === SyntaxKind.SpreadAssignment ? + Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access : + Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access; + const optionalError = target.parent.kind === SyntaxKind.SpreadAssignment ? + Diagnostics.The_target_of_an_object_rest_assignment_may_not_be_an_optional_property_access : + Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access; + if (checkReferenceExpression(target, error, optionalError)) { + checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target); + } + if (isPrivateIdentifierPropertyAccessExpression(target)) { + checkExternalEmitHelpers(target.parent, ExternalEmitHelpers.ClassPrivateFieldSet); + } + return sourceType; + } - case SyntaxKind.BinaryExpression: - if (isAssignmentOperator((node as BinaryExpression).operatorToken.kind)) { - return false; - } - return isSideEffectFree((node as BinaryExpression).left) && - isSideEffectFree((node as BinaryExpression).right); + /** + * This is a *shallow* check: An expression is side-effect-free if the + * evaluation of the expression *itself* cannot produce side effects. + * For example, x++ / 3 is side-effect free because the / operator + * does not have side effects. + * The intent is to "smell test" an expression for correctness in positions where + * its value is discarded (e.g. the left side of the comma operator). + */ + function isSideEffectFree(node: Node): boolean { + node = skipParentheses(node); + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.StringLiteral: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.TemplateExpression: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ClassExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TypeOfExpression: + case SyntaxKind.NonNullExpression: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxElement: + return true; - case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: - // Unary operators ~, !, +, and - have no side effects. - // The rest do. - switch ((node as PrefixUnaryExpression).operator) { - case SyntaxKind.ExclamationToken: - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - return true; - } - return false; + case SyntaxKind.ConditionalExpression: + return isSideEffectFree((node as ConditionalExpression).whenTrue) && + isSideEffectFree((node as ConditionalExpression).whenFalse); - // Some forms listed here for clarity - case SyntaxKind.VoidExpression: // Explicit opt-out - case SyntaxKind.TypeAssertionExpression: // Not SEF, but can produce useful type warnings - case SyntaxKind.AsExpression: // Not SEF, but can produce useful type warnings - default: + case SyntaxKind.BinaryExpression: + if (isAssignmentOperator((node as BinaryExpression).operatorToken.kind)) { return false; - } - } + } + return isSideEffectFree((node as BinaryExpression).left) && + isSideEffectFree((node as BinaryExpression).right); + + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.PostfixUnaryExpression: + // Unary operators ~, !, +, and - have no side effects. + // The rest do. + switch ((node as PrefixUnaryExpression).operator) { + case SyntaxKind.ExclamationToken: + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + return true; + } + return false; - function isTypeEqualityComparableTo(source: Type, target: Type) { - return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); + // Some forms listed here for clarity + case SyntaxKind.VoidExpression: // Explicit opt-out + case SyntaxKind.TypeAssertionExpression: // Not SEF, but can produce useful type warnings + case SyntaxKind.AsExpression: // Not SEF, but can produce useful type warnings + default: + return false; } + } - function createCheckBinaryExpression() { - interface WorkArea { - readonly checkMode: CheckMode | undefined; - skip: boolean; - stackIndex: number; - /** - * Holds the types from the left-side of an expression from [0..stackIndex]. - * Holds the type of the result at stackIndex+1. This allows us to reuse existing stack entries - * and avoid storing an extra property on the object (i.e., `lastResult`). - */ - typeStack: (Type | undefined)[]; - } + function isTypeEqualityComparableTo(source: Type, target: Type) { + return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); + } - const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState); + function createCheckBinaryExpression() { + interface WorkArea { + readonly checkMode: CheckMode | undefined; + skip: boolean; + stackIndex: number; + /** + * Holds the types from the left-side of an expression from [0..stackIndex]. + * Holds the type of the result at stackIndex+1. This allows us to reuse existing stack entries + * and avoid storing an extra property on the object (i.e., `lastResult`). + */ + typeStack: (Type | undefined)[]; + } - return (node: BinaryExpression, checkMode: CheckMode | undefined) => { - const result = trampoline(node, checkMode); - Debug.assertIsDefined(result); - return result; - }; + const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState); - function onEnter(node: BinaryExpression, state: WorkArea | undefined, checkMode: CheckMode | undefined) { - if (state) { - state.stackIndex++; - state.skip = false; - setLeftType(state, /*type*/ undefined); - setLastResult(state, /*type*/ undefined); - } - else { - state = { - checkMode, - skip: false, - stackIndex: 0, - typeStack: [undefined, undefined], - }; - } + return (node: BinaryExpression, checkMode: CheckMode | undefined) => { + const result = trampoline(node, checkMode); + Debug.assertIsDefined(result); + return result; + }; - if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { - state.skip = true; - setLastResult(state, checkExpression(node.right, checkMode)); - return state; - } + function onEnter(node: BinaryExpression, state: WorkArea | undefined, checkMode: CheckMode | undefined) { + if (state) { + state.stackIndex++; + state.skip = false; + setLeftType(state, /*type*/ undefined); + setLastResult(state, /*type*/ undefined); + } + else { + state = { + checkMode, + skip: false, + stackIndex: 0, + typeStack: [undefined, undefined], + }; + } - checkGrammarNullishCoalesceWithLogicalExpression(node); + if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { + state.skip = true; + setLastResult(state, checkExpression(node.right, checkMode)); + return state; + } - const operator = node.operatorToken.kind; - if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) { - state.skip = true; - setLastResult(state, checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword)); - return state; - } + checkGrammarNullishCoalesceWithLogicalExpression(node); + const operator = node.operatorToken.kind; + if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) { + state.skip = true; + setLastResult(state, checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword)); return state; } - function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) { - if (!state.skip) { - return maybeCheckExpression(state, left); - } + return state; + } + + function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) { + if (!state.skip) { + return maybeCheckExpression(state, left); } + } - function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) { - if (!state.skip) { - const leftType = getLastResult(state); - Debug.assertIsDefined(leftType); - setLeftType(state, leftType); - setLastResult(state, /*type*/ undefined); - const operator = operatorToken.kind; - if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { - if (operator === SyntaxKind.AmpersandAmpersandToken) { - let parent = node.parent; - while (parent.kind === SyntaxKind.ParenthesizedExpression - || isBinaryExpression(parent) && (parent.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || parent.operatorToken.kind === SyntaxKind.BarBarToken)) { - parent = parent.parent; - } - checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined); + function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) { + if (!state.skip) { + const leftType = getLastResult(state); + Debug.assertIsDefined(leftType); + setLeftType(state, leftType); + setLastResult(state, /*type*/ undefined); + const operator = operatorToken.kind; + if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { + if (operator === SyntaxKind.AmpersandAmpersandToken) { + let parent = node.parent; + while (parent.kind === SyntaxKind.ParenthesizedExpression + || isBinaryExpression(parent) && (parent.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken || parent.operatorToken.kind === SyntaxKind.BarBarToken)) { + parent = parent.parent; } - checkTruthinessOfType(leftType, node.left); + checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined); } + checkTruthinessOfType(leftType, node.left); } } + } - function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) { - if (!state.skip) { - return maybeCheckExpression(state, right); - } + function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) { + if (!state.skip) { + return maybeCheckExpression(state, right); } + } - function onExit(node: BinaryExpression, state: WorkArea): Type | undefined { - let result: Type | undefined; - if (state.skip) { - result = getLastResult(state); - } - else { - const leftType = getLeftType(state); - Debug.assertIsDefined(leftType); - - const rightType = getLastResult(state); - Debug.assertIsDefined(rightType); + function onExit(node: BinaryExpression, state: WorkArea): Type | undefined { + let result: Type | undefined; + if (state.skip) { + result = getLastResult(state); + } + else { + const leftType = getLeftType(state); + Debug.assertIsDefined(leftType); - result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node); - } + const rightType = getLastResult(state); + Debug.assertIsDefined(rightType); - state.skip = false; - setLeftType(state, /*type*/ undefined); - setLastResult(state, /*type*/ undefined); - state.stackIndex--; - return result; + result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node); } - function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") { - setLastResult(state, result); - return state; - } + state.skip = false; + setLeftType(state, /*type*/ undefined); + setLastResult(state, /*type*/ undefined); + state.stackIndex--; + return result; + } - function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined { - if (isBinaryExpression(node)) { - return node; - } - setLastResult(state, checkExpression(node, state.checkMode)); - } + function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") { + setLastResult(state, result); + return state; + } - function getLeftType(state: WorkArea) { - return state.typeStack[state.stackIndex]; + function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined { + if (isBinaryExpression(node)) { + return node; } + setLastResult(state, checkExpression(node, state.checkMode)); + } - function setLeftType(state: WorkArea, type: Type | undefined) { - state.typeStack[state.stackIndex] = type; - } + function getLeftType(state: WorkArea) { + return state.typeStack[state.stackIndex]; + } - function getLastResult(state: WorkArea) { - return state.typeStack[state.stackIndex + 1]; - } + function setLeftType(state: WorkArea, type: Type | undefined) { + state.typeStack[state.stackIndex] = type; + } - function setLastResult(state: WorkArea, type: Type | undefined) { - // To reduce overhead, reuse the next stack entry to store the - // last result. This avoids the overhead of an additional property - // on `WorkArea` and reuses empty stack entries as we walk back up - // the stack. - state.typeStack[state.stackIndex + 1] = type; - } + function getLastResult(state: WorkArea) { + return state.typeStack[state.stackIndex + 1]; } - function checkGrammarNullishCoalesceWithLogicalExpression(node: BinaryExpression) { - const { left, operatorToken, right } = node; - if (operatorToken.kind === SyntaxKind.QuestionQuestionToken) { - if (isBinaryExpression(left) && (left.operatorToken.kind === SyntaxKind.BarBarToken || left.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { - grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(left.operatorToken.kind), tokenToString(operatorToken.kind)); - } - if (isBinaryExpression(right) && (right.operatorToken.kind === SyntaxKind.BarBarToken || right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { - grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind)); - } - } + function setLastResult(state: WorkArea, type: Type | undefined) { + // To reduce overhead, reuse the next stack entry to store the + // last result. This avoids the overhead of an additional property + // on `WorkArea` and reuses empty stack entries as we walk back up + // the stack. + state.typeStack[state.stackIndex + 1] = type; } + } - // Note that this and `checkBinaryExpression` above should behave mostly the same, except this elides some - // expression-wide checks and does not use a work stack to fold nested binary expressions into the same callstack frame - function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node): Type { - const operator = operatorToken.kind; - if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) { - return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode, right.kind === SyntaxKind.ThisKeyword); - } - let leftType: Type; - if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { - leftType = checkTruthinessExpression(left, checkMode); + function checkGrammarNullishCoalesceWithLogicalExpression(node: BinaryExpression) { + const { left, operatorToken, right } = node; + if (operatorToken.kind === SyntaxKind.QuestionQuestionToken) { + if (isBinaryExpression(left) && (left.operatorToken.kind === SyntaxKind.BarBarToken || left.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { + grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(left.operatorToken.kind), tokenToString(operatorToken.kind)); } - else { - leftType = checkExpression(left, checkMode); + if (isBinaryExpression(right) && (right.operatorToken.kind === SyntaxKind.BarBarToken || right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { + grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind)); } - - const rightType = checkExpression(right, checkMode); - return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode); } + } - function checkBinaryLikeExpressionWorker( - left: Expression, - operatorToken: Node, - right: Expression, - leftType: Type, - rightType: Type, - errorNode?: Node - ): Type { - const operator = operatorToken.kind; - switch (operator) { - case SyntaxKind.AsteriskToken: - case SyntaxKind.AsteriskAsteriskToken: - case SyntaxKind.AsteriskEqualsToken: - case SyntaxKind.AsteriskAsteriskEqualsToken: - case SyntaxKind.SlashToken: - case SyntaxKind.SlashEqualsToken: - case SyntaxKind.PercentToken: - case SyntaxKind.PercentEqualsToken: - case SyntaxKind.MinusToken: - case SyntaxKind.MinusEqualsToken: - case SyntaxKind.LessThanLessThanToken: - case SyntaxKind.LessThanLessThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - case SyntaxKind.BarToken: - case SyntaxKind.BarEqualsToken: - case SyntaxKind.CaretToken: - case SyntaxKind.CaretEqualsToken: - case SyntaxKind.AmpersandToken: - case SyntaxKind.AmpersandEqualsToken: - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } - - leftType = checkNonNullType(leftType, left); - rightType = checkNonNullType(rightType, right); - - let suggestedOperator: SyntaxKind | undefined; - // if a user tries to apply a bitwise operator to 2 boolean operands - // try and return them a helpful suggestion - if ((leftType.flags & TypeFlags.BooleanLike) && - (rightType.flags & TypeFlags.BooleanLike) && - (suggestedOperator = getSuggestedBooleanOperator(operatorToken.kind)) !== undefined) { - error(errorNode || operatorToken, Diagnostics.The_0_operator_is_not_allowed_for_boolean_types_Consider_using_1_instead, tokenToString(operatorToken.kind), tokenToString(suggestedOperator)); - return numberType; - } - else { - // otherwise just check each operand separately and report errors as normal - const leftOk = checkArithmeticOperandType(left, leftType, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); - const rightOk = checkArithmeticOperandType(right, rightType, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); - let resultType: Type; - // If both are any or unknown, allow operation; assume it will resolve to number - if ((isTypeAssignableToKind(leftType, TypeFlags.AnyOrUnknown) && isTypeAssignableToKind(rightType, TypeFlags.AnyOrUnknown)) || - // Or, if neither could be bigint, implicit coercion results in a number result - !(maybeTypeOfKind(leftType, TypeFlags.BigIntLike) || maybeTypeOfKind(rightType, TypeFlags.BigIntLike)) - ) { - resultType = numberType; - } - // At least one is assignable to bigint, so check that both are - else if (bothAreBigIntLike(leftType, rightType)) { - switch (operator) { - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - reportOperatorError(); - break; - case SyntaxKind.AsteriskAsteriskToken: - case SyntaxKind.AsteriskAsteriskEqualsToken: - if (languageVersion < ScriptTarget.ES2016) { - error(errorNode, Diagnostics.Exponentiation_cannot_be_performed_on_bigint_values_unless_the_target_option_is_set_to_es2016_or_later); - } - } - resultType = bigintType; - } - // Exactly one of leftType/rightType is assignable to bigint - else { - reportOperatorError(bothAreBigIntLike); - resultType = errorType; - } - if (leftOk && rightOk) { - checkAssignmentOperator(resultType); - } - return resultType; - } - case SyntaxKind.PlusToken: - case SyntaxKind.PlusEqualsToken: - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } + // Note that this and `checkBinaryExpression` above should behave mostly the same, except this elides some + // expression-wide checks and does not use a work stack to fold nested binary expressions into the same callstack frame + function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node): Type { + const operator = operatorToken.kind; + if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) { + return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode, right.kind === SyntaxKind.ThisKeyword); + } + let leftType: Type; + if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { + leftType = checkTruthinessExpression(left, checkMode); + } + else { + leftType = checkExpression(left, checkMode); + } - if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { - leftType = checkNonNullType(leftType, left); - rightType = checkNonNullType(rightType, right); - } + const rightType = checkExpression(right, checkMode); + return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode); + } - let resultType: Type | undefined; - if (isTypeAssignableToKind(leftType, TypeFlags.NumberLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.NumberLike, /*strict*/ true)) { - // Operands of an enum type are treated as having the primitive type Number. - // If both operands are of the Number primitive type, the result is of the Number primitive type. + function checkBinaryLikeExpressionWorker( + left: Expression, + operatorToken: Node, + right: Expression, + leftType: Type, + rightType: Type, + errorNode?: Node + ): Type { + const operator = operatorToken.kind; + switch (operator) { + case SyntaxKind.AsteriskToken: + case SyntaxKind.AsteriskAsteriskToken: + case SyntaxKind.AsteriskEqualsToken: + case SyntaxKind.AsteriskAsteriskEqualsToken: + case SyntaxKind.SlashToken: + case SyntaxKind.SlashEqualsToken: + case SyntaxKind.PercentToken: + case SyntaxKind.PercentEqualsToken: + case SyntaxKind.MinusToken: + case SyntaxKind.MinusEqualsToken: + case SyntaxKind.LessThanLessThanToken: + case SyntaxKind.LessThanLessThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + case SyntaxKind.BarToken: + case SyntaxKind.BarEqualsToken: + case SyntaxKind.CaretToken: + case SyntaxKind.CaretEqualsToken: + case SyntaxKind.AmpersandToken: + case SyntaxKind.AmpersandEqualsToken: + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; + } + + leftType = checkNonNullType(leftType, left); + rightType = checkNonNullType(rightType, right); + + let suggestedOperator: SyntaxKind | undefined; + // if a user tries to apply a bitwise operator to 2 boolean operands + // try and return them a helpful suggestion + if ((leftType.flags & TypeFlags.BooleanLike) && + (rightType.flags & TypeFlags.BooleanLike) && + (suggestedOperator = getSuggestedBooleanOperator(operatorToken.kind)) !== undefined) { + error(errorNode || operatorToken, Diagnostics.The_0_operator_is_not_allowed_for_boolean_types_Consider_using_1_instead, tokenToString(operatorToken.kind), tokenToString(suggestedOperator)); + return numberType; + } + else { + // otherwise just check each operand separately and report errors as normal + const leftOk = checkArithmeticOperandType(left, leftType, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); + const rightOk = checkArithmeticOperandType(right, rightType, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); + let resultType: Type; + // If both are any or unknown, allow operation; assume it will resolve to number + if ((isTypeAssignableToKind(leftType, TypeFlags.AnyOrUnknown) && isTypeAssignableToKind(rightType, TypeFlags.AnyOrUnknown)) || + // Or, if neither could be bigint, implicit coercion results in a number result + !(maybeTypeOfKind(leftType, TypeFlags.BigIntLike) || maybeTypeOfKind(rightType, TypeFlags.BigIntLike)) + ) { resultType = numberType; } - else if (isTypeAssignableToKind(leftType, TypeFlags.BigIntLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.BigIntLike, /*strict*/ true)) { - // If both operands are of the BigInt primitive type, the result is of the BigInt primitive type. + // At least one is assignable to bigint, so check that both are + else if (bothAreBigIntLike(leftType, rightType)) { + switch (operator) { + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + reportOperatorError(); + break; + case SyntaxKind.AsteriskAsteriskToken: + case SyntaxKind.AsteriskAsteriskEqualsToken: + if (languageVersion < ScriptTarget.ES2016) { + error(errorNode, Diagnostics.Exponentiation_cannot_be_performed_on_bigint_values_unless_the_target_option_is_set_to_es2016_or_later); + } + } resultType = bigintType; } - else if (isTypeAssignableToKind(leftType, TypeFlags.StringLike, /*strict*/ true) || isTypeAssignableToKind(rightType, TypeFlags.StringLike, /*strict*/ true)) { - // If one or both operands are of the String primitive type, the result is of the String primitive type. - resultType = stringType; + // Exactly one of leftType/rightType is assignable to bigint + else { + reportOperatorError(bothAreBigIntLike); + resultType = errorType; } - else if (isTypeAny(leftType) || isTypeAny(rightType)) { - // Otherwise, the result is of type Any. - // NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we. - resultType = isErrorType(leftType) || isErrorType(rightType) ? errorType : anyType; + if (leftOk && rightOk) { + checkAssignmentOperator(resultType); } + return resultType; + } + case SyntaxKind.PlusToken: + case SyntaxKind.PlusEqualsToken: + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; + } - // Symbols are not allowed at all in arithmetic expressions - if (resultType && !checkForDisallowedESSymbolOperand(operator)) { - return resultType; - } + if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { + leftType = checkNonNullType(leftType, left); + rightType = checkNonNullType(rightType, right); + } - if (!resultType) { - // Types that have a reasonably good chance of being a valid operand type. - // If both types have an awaited type of one of these, we'll assume the user - // might be missing an await without doing an exhaustive check that inserting - // await(s) will actually be a completely valid binary expression. - const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown; - reportOperatorError((left, right) => - isTypeAssignableToKind(left, closeEnoughKind) && - isTypeAssignableToKind(right, closeEnoughKind)); - return anyType; - } + let resultType: Type | undefined; + if (isTypeAssignableToKind(leftType, TypeFlags.NumberLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.NumberLike, /*strict*/ true)) { + // Operands of an enum type are treated as having the primitive type Number. + // If both operands are of the Number primitive type, the result is of the Number primitive type. + resultType = numberType; + } + else if (isTypeAssignableToKind(leftType, TypeFlags.BigIntLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.BigIntLike, /*strict*/ true)) { + // If both operands are of the BigInt primitive type, the result is of the BigInt primitive type. + resultType = bigintType; + } + else if (isTypeAssignableToKind(leftType, TypeFlags.StringLike, /*strict*/ true) || isTypeAssignableToKind(rightType, TypeFlags.StringLike, /*strict*/ true)) { + // If one or both operands are of the String primitive type, the result is of the String primitive type. + resultType = stringType; + } + else if (isTypeAny(leftType) || isTypeAny(rightType)) { + // Otherwise, the result is of type Any. + // NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we. + resultType = isErrorType(leftType) || isErrorType(rightType) ? errorType : anyType; + } - if (operator === SyntaxKind.PlusEqualsToken) { - checkAssignmentOperator(resultType); - } + // Symbols are not allowed at all in arithmetic expressions + if (resultType && !checkForDisallowedESSymbolOperand(operator)) { return resultType; - case SyntaxKind.LessThanToken: - case SyntaxKind.GreaterThanToken: - case SyntaxKind.LessThanEqualsToken: - case SyntaxKind.GreaterThanEqualsToken: - if (checkForDisallowedESSymbolOperand(operator)) { - leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left)); - rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right)); - reportOperatorErrorUnless((left, right) => - isTypeComparableTo(left, right) || isTypeComparableTo(right, left) || ( - isTypeAssignableTo(left, numberOrBigIntType) && isTypeAssignableTo(right, numberOrBigIntType))); - } - return booleanType; - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - if (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) { - const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; - error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true"); - } - checkNaNEquality(errorNode, operator, left, right); - reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left)); - return booleanType; + } - case SyntaxKind.InstanceOfKeyword: - return checkInstanceOfExpression(left, right, leftType, rightType); - case SyntaxKind.InKeyword: - return checkInExpression(left, right, leftType, rightType); - case SyntaxKind.AmpersandAmpersandToken: - case SyntaxKind.AmpersandAmpersandEqualsToken: { - const resultType = getTypeFacts(leftType) & TypeFacts.Truthy ? - getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) : - leftType; - if (operator === SyntaxKind.AmpersandAmpersandEqualsToken) { - checkAssignmentOperator(rightType); - } - return resultType; + if (!resultType) { + // Types that have a reasonably good chance of being a valid operand type. + // If both types have an awaited type of one of these, we'll assume the user + // might be missing an await without doing an exhaustive check that inserting + // await(s) will actually be a completely valid binary expression. + const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown; + reportOperatorError((left, right) => + isTypeAssignableToKind(left, closeEnoughKind) && + isTypeAssignableToKind(right, closeEnoughKind)); + return anyType; } - case SyntaxKind.BarBarToken: - case SyntaxKind.BarBarEqualsToken: { - const resultType = getTypeFacts(leftType) & TypeFacts.Falsy ? - getUnionType([getNonNullableType(removeDefinitelyFalsyTypes(leftType)), rightType], UnionReduction.Subtype) : - leftType; - if (operator === SyntaxKind.BarBarEqualsToken) { - checkAssignmentOperator(rightType); - } - return resultType; + + if (operator === SyntaxKind.PlusEqualsToken) { + checkAssignmentOperator(resultType); } - case SyntaxKind.QuestionQuestionToken: - case SyntaxKind.QuestionQuestionEqualsToken: { - const resultType = getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ? - getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) : - leftType; - if (operator === SyntaxKind.QuestionQuestionEqualsToken) { - checkAssignmentOperator(rightType); - } - return resultType; + return resultType; + case SyntaxKind.LessThanToken: + case SyntaxKind.GreaterThanToken: + case SyntaxKind.LessThanEqualsToken: + case SyntaxKind.GreaterThanEqualsToken: + if (checkForDisallowedESSymbolOperand(operator)) { + leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left)); + rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right)); + reportOperatorErrorUnless((left, right) => + isTypeComparableTo(left, right) || isTypeComparableTo(right, left) || ( + isTypeAssignableTo(left, numberOrBigIntType) && isTypeAssignableTo(right, numberOrBigIntType))); } - case SyntaxKind.EqualsToken: - const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None; - checkAssignmentDeclaration(declKind, rightType); - if (isAssignmentDeclaration(declKind)) { - if (!(rightType.flags & TypeFlags.Object) || - declKind !== AssignmentDeclarationKind.ModuleExports && - declKind !== AssignmentDeclarationKind.Prototype && - !isEmptyObjectType(rightType) && - !isFunctionObjectType(rightType as ObjectType) && - !(getObjectFlags(rightType) & ObjectFlags.Class)) { - // don't check assignability of module.exports=, C.prototype=, or expando types because they will necessarily be incomplete - checkAssignmentOperator(rightType); - } - return leftType; - } - else { + return booleanType; + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + if (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) { + const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; + error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true"); + } + checkNaNEquality(errorNode, operator, left, right); + reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left)); + return booleanType; + + case SyntaxKind.InstanceOfKeyword: + return checkInstanceOfExpression(left, right, leftType, rightType); + case SyntaxKind.InKeyword: + return checkInExpression(left, right, leftType, rightType); + case SyntaxKind.AmpersandAmpersandToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: { + const resultType = getTypeFacts(leftType) & TypeFacts.Truthy ? + getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) : + leftType; + if (operator === SyntaxKind.AmpersandAmpersandEqualsToken) { + checkAssignmentOperator(rightType); + } + return resultType; + } + case SyntaxKind.BarBarToken: + case SyntaxKind.BarBarEqualsToken: { + const resultType = getTypeFacts(leftType) & TypeFacts.Falsy ? + getUnionType([getNonNullableType(removeDefinitelyFalsyTypes(leftType)), rightType], UnionReduction.Subtype) : + leftType; + if (operator === SyntaxKind.BarBarEqualsToken) { + checkAssignmentOperator(rightType); + } + return resultType; + } + case SyntaxKind.QuestionQuestionToken: + case SyntaxKind.QuestionQuestionEqualsToken: { + const resultType = getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ? + getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) : + leftType; + if (operator === SyntaxKind.QuestionQuestionEqualsToken) { + checkAssignmentOperator(rightType); + } + return resultType; + } + case SyntaxKind.EqualsToken: + const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None; + checkAssignmentDeclaration(declKind, rightType); + if (isAssignmentDeclaration(declKind)) { + if (!(rightType.flags & TypeFlags.Object) || + declKind !== AssignmentDeclarationKind.ModuleExports && + declKind !== AssignmentDeclarationKind.Prototype && + !isEmptyObjectType(rightType) && + !isFunctionObjectType(rightType as ObjectType) && + !(getObjectFlags(rightType) & ObjectFlags.Class)) { + // don't check assignability of module.exports=, C.prototype=, or expando types because they will necessarily be incomplete checkAssignmentOperator(rightType); - return getRegularTypeOfObjectLiteral(rightType); - } - case SyntaxKind.CommaToken: - if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isEvalNode(right)) { - const sf = getSourceFileOfNode(left); - const sourceText = sf.text; - const start = skipTrivia(sourceText, left.pos); - const isInDiag2657 = sf.parseDiagnostics.some(diag => { - if (diag.code !== Diagnostics.JSX_expressions_must_have_one_parent_element.code) return false; - return textSpanContainsPosition(diag, start); - }); - if (!isInDiag2657) error(left, Diagnostics.Left_side_of_comma_operator_is_unused_and_has_no_side_effects); } - return rightType; + return leftType; + } + else { + checkAssignmentOperator(rightType); + return getRegularTypeOfObjectLiteral(rightType); + } + case SyntaxKind.CommaToken: + if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isEvalNode(right)) { + const sf = getSourceFileOfNode(left); + const sourceText = sf.text; + const start = skipTrivia(sourceText, left.pos); + const isInDiag2657 = sf.parseDiagnostics.some(diag => { + if (diag.code !== Diagnostics.JSX_expressions_must_have_one_parent_element.code) return false; + return textSpanContainsPosition(diag, start); + }); + if (!isInDiag2657) error(left, Diagnostics.Left_side_of_comma_operator_is_unused_and_has_no_side_effects); + } + return rightType; - default: - return Debug.fail(); - } + default: + return Debug.fail(); + } - function bothAreBigIntLike(left: Type, right: Type): boolean { - return isTypeAssignableToKind(left, TypeFlags.BigIntLike) && isTypeAssignableToKind(right, TypeFlags.BigIntLike); - } + function bothAreBigIntLike(left: Type, right: Type): boolean { + return isTypeAssignableToKind(left, TypeFlags.BigIntLike) && isTypeAssignableToKind(right, TypeFlags.BigIntLike); + } - function checkAssignmentDeclaration(kind: AssignmentDeclarationKind, rightType: Type) { - if (kind === AssignmentDeclarationKind.ModuleExports) { - for (const prop of getPropertiesOfObjectType(rightType)) { - const propType = getTypeOfSymbol(prop); - if (propType.symbol && propType.symbol.flags & SymbolFlags.Class) { - const name = prop.escapedName; - const symbol = resolveName(prop.valueDeclaration, name, SymbolFlags.Type, undefined, name, /*isUse*/ false); - if (symbol?.declarations && symbol.declarations.some(isJSDocTypedefTag)) { - addDuplicateDeclarationErrorsForSymbols(symbol, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), prop); - addDuplicateDeclarationErrorsForSymbols(prop, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), symbol); - } + function checkAssignmentDeclaration(kind: AssignmentDeclarationKind, rightType: Type) { + if (kind === AssignmentDeclarationKind.ModuleExports) { + for (const prop of getPropertiesOfObjectType(rightType)) { + const propType = getTypeOfSymbol(prop); + if (propType.symbol && propType.symbol.flags & SymbolFlags.Class) { + const name = prop.escapedName; + const symbol = resolveName(prop.valueDeclaration, name, SymbolFlags.Type, undefined, name, /*isUse*/ false); + if (symbol?.declarations && symbol.declarations.some(isJSDocTypedefTag)) { + addDuplicateDeclarationErrorsForSymbols(symbol, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), prop); + addDuplicateDeclarationErrorsForSymbols(prop, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), symbol); } } } } + } - function isEvalNode(node: Expression) { - return node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "eval"; - } - - // Return true if there was no error, false if there was an error. - function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean { - const offendingSymbolOperand = - maybeTypeOfKindConsideringBaseConstraint(leftType, TypeFlags.ESSymbolLike) ? left : - maybeTypeOfKindConsideringBaseConstraint(rightType, TypeFlags.ESSymbolLike) ? right : - undefined; + function isEvalNode(node: Expression) { + return node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "eval"; + } - if (offendingSymbolOperand) { - error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); - return false; - } + // Return true if there was no error, false if there was an error. + function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean { + const offendingSymbolOperand = + maybeTypeOfKindConsideringBaseConstraint(leftType, TypeFlags.ESSymbolLike) ? left : + maybeTypeOfKindConsideringBaseConstraint(rightType, TypeFlags.ESSymbolLike) ? right : + undefined; - return true; + if (offendingSymbolOperand) { + error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); + return false; } - function getSuggestedBooleanOperator(operator: SyntaxKind): SyntaxKind | undefined { - switch (operator) { - case SyntaxKind.BarToken: - case SyntaxKind.BarEqualsToken: - return SyntaxKind.BarBarToken; - case SyntaxKind.CaretToken: - case SyntaxKind.CaretEqualsToken: - return SyntaxKind.ExclamationEqualsEqualsToken; - case SyntaxKind.AmpersandToken: - case SyntaxKind.AmpersandEqualsToken: - return SyntaxKind.AmpersandAmpersandToken; - default: - return undefined; - } + return true; + } + + function getSuggestedBooleanOperator(operator: SyntaxKind): SyntaxKind | undefined { + switch (operator) { + case SyntaxKind.BarToken: + case SyntaxKind.BarEqualsToken: + return SyntaxKind.BarBarToken; + case SyntaxKind.CaretToken: + case SyntaxKind.CaretEqualsToken: + return SyntaxKind.ExclamationEqualsEqualsToken; + case SyntaxKind.AmpersandToken: + case SyntaxKind.AmpersandEqualsToken: + return SyntaxKind.AmpersandAmpersandToken; + default: + return undefined; } + } - function checkAssignmentOperator(valueType: Type): void { - if (isAssignmentOperator(operator)) { - addLazyDiagnostic(checkAssignmentOperatorWorker); - } + function checkAssignmentOperator(valueType: Type): void { + if (isAssignmentOperator(operator)) { + addLazyDiagnostic(checkAssignmentOperatorWorker); + } - function checkAssignmentOperatorWorker() { - // TypeScript 1.0 spec (April 2014): 4.17 - // An assignment of the form - // VarExpr = ValueExpr - // requires VarExpr to be classified as a reference - // A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1) - // and the type of the non-compound operation to be assignable to the type of VarExpr. + function checkAssignmentOperatorWorker() { + // TypeScript 1.0 spec (April 2014): 4.17 + // An assignment of the form + // VarExpr = ValueExpr + // requires VarExpr to be classified as a reference + // A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1) + // and the type of the non-compound operation to be assignable to the type of VarExpr. - if (checkReferenceExpression(left, - Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access, - Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access) - && (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) { + if (checkReferenceExpression(left, + Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access, + Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access) + && (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) { - let headMessage: DiagnosticMessage | undefined; - if (exactOptionalPropertyTypes && isPropertyAccessExpression(left) && maybeTypeOfKind(valueType, TypeFlags.Undefined)) { - const target = getTypeOfPropertyOfType(getTypeOfExpression(left.expression), left.name.escapedText); - if (isExactOptionalPropertyMismatch(valueType, target)) { - headMessage = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target; - } + let headMessage: DiagnosticMessage | undefined; + if (exactOptionalPropertyTypes && isPropertyAccessExpression(left) && maybeTypeOfKind(valueType, TypeFlags.Undefined)) { + const target = getTypeOfPropertyOfType(getTypeOfExpression(left.expression), left.name.escapedText); + if (isExactOptionalPropertyMismatch(valueType, target)) { + headMessage = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target; } - // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported - checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right, headMessage); } + // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported + checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right, headMessage); } } + } - function isAssignmentDeclaration(kind: AssignmentDeclarationKind) { - switch (kind) { - case AssignmentDeclarationKind.ModuleExports: - return true; - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.Property: - case AssignmentDeclarationKind.Prototype: - case AssignmentDeclarationKind.PrototypeProperty: - case AssignmentDeclarationKind.ThisProperty: - const symbol = getSymbolOfNode(left); - const init = getAssignedExpandoInitializer(right); - return !!init && isObjectLiteralExpression(init) && - !!symbol?.exports?.size; - default: - return false; - } - } - - /** - * Returns true if an error is reported - */ - function reportOperatorErrorUnless(typesAreCompatible: (left: Type, right: Type) => boolean): boolean { - if (!typesAreCompatible(leftType, rightType)) { - reportOperatorError(typesAreCompatible); + function isAssignmentDeclaration(kind: AssignmentDeclarationKind) { + switch (kind) { + case AssignmentDeclarationKind.ModuleExports: return true; - } - return false; + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.Property: + case AssignmentDeclarationKind.Prototype: + case AssignmentDeclarationKind.PrototypeProperty: + case AssignmentDeclarationKind.ThisProperty: + const symbol = getSymbolOfNode(left); + const init = getAssignedExpandoInitializer(right); + return !!init && isObjectLiteralExpression(init) && + !!symbol?.exports?.size; + default: + return false; } + } - function reportOperatorError(isRelated?: (left: Type, right: Type) => boolean) { - let wouldWorkWithAwait = false; - const errNode = errorNode || operatorToken; - if (isRelated) { - const awaitedLeftType = getAwaitedTypeNoAlias(leftType); - const awaitedRightType = getAwaitedTypeNoAlias(rightType); - wouldWorkWithAwait = !(awaitedLeftType === leftType && awaitedRightType === rightType) - && !!(awaitedLeftType && awaitedRightType) - && isRelated(awaitedLeftType, awaitedRightType); - } + /** + * Returns true if an error is reported + */ + function reportOperatorErrorUnless(typesAreCompatible: (left: Type, right: Type) => boolean): boolean { + if (!typesAreCompatible(leftType, rightType)) { + reportOperatorError(typesAreCompatible); + return true; + } + return false; + } - let effectiveLeft = leftType; - let effectiveRight = rightType; - if (!wouldWorkWithAwait && isRelated) { - [effectiveLeft, effectiveRight] = getBaseTypesIfUnrelated(leftType, rightType, isRelated); - } - const [leftStr, rightStr] = getTypeNamesForErrorDisplay(effectiveLeft, effectiveRight); - if (!tryGiveBetterPrimaryError(errNode, wouldWorkWithAwait, leftStr, rightStr)) { - errorAndMaybeSuggestAwait( - errNode, - wouldWorkWithAwait, - Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2, - tokenToString(operatorToken.kind), - leftStr, - rightStr, - ); - } + function reportOperatorError(isRelated?: (left: Type, right: Type) => boolean) { + let wouldWorkWithAwait = false; + const errNode = errorNode || operatorToken; + if (isRelated) { + const awaitedLeftType = getAwaitedTypeNoAlias(leftType); + const awaitedRightType = getAwaitedTypeNoAlias(rightType); + wouldWorkWithAwait = !(awaitedLeftType === leftType && awaitedRightType === rightType) + && !!(awaitedLeftType && awaitedRightType) + && isRelated(awaitedLeftType, awaitedRightType); } - function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) { - switch (operatorToken.kind) { - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - return errorAndMaybeSuggestAwait( - errNode, - maybeMissingAwait, - Diagnostics.This_comparison_appears_to_be_unintentional_because_the_types_0_and_1_have_no_overlap, - leftStr, rightStr); - default: - return undefined; - } + let effectiveLeft = leftType; + let effectiveRight = rightType; + if (!wouldWorkWithAwait && isRelated) { + [effectiveLeft, effectiveRight] = getBaseTypesIfUnrelated(leftType, rightType, isRelated); + } + const [leftStr, rightStr] = getTypeNamesForErrorDisplay(effectiveLeft, effectiveRight); + if (!tryGiveBetterPrimaryError(errNode, wouldWorkWithAwait, leftStr, rightStr)) { + errorAndMaybeSuggestAwait( + errNode, + wouldWorkWithAwait, + Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2, + tokenToString(operatorToken.kind), + leftStr, + rightStr, + ); } + } - function checkNaNEquality(errorNode: Node | undefined, operator: SyntaxKind, left: Expression, right: Expression) { - const isLeftNaN = isGlobalNaN(skipParentheses(left)); - const isRightNaN = isGlobalNaN(skipParentheses(right)); - if (isLeftNaN || isRightNaN) { - const err = error(errorNode, Diagnostics.This_condition_will_always_return_0, - tokenToString(operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.EqualsEqualsToken ? SyntaxKind.FalseKeyword : SyntaxKind.TrueKeyword)); - if (isLeftNaN && isRightNaN) return; - const operatorString = operator === SyntaxKind.ExclamationEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? tokenToString(SyntaxKind.ExclamationToken) : ""; - const location = isLeftNaN ? right : left; - const expression = skipParentheses(location); - addRelatedInfo(err, createDiagnosticForNode(location, Diagnostics.Did_you_mean_0, - `${operatorString}Number.isNaN(${isEntityNameExpression(expression) ? entityNameToString(expression) : "..."})`)); - } + function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) { + switch (operatorToken.kind) { + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + return errorAndMaybeSuggestAwait( + errNode, + maybeMissingAwait, + Diagnostics.This_comparison_appears_to_be_unintentional_because_the_types_0_and_1_have_no_overlap, + leftStr, rightStr); + default: + return undefined; } + } - function isGlobalNaN(expr: Expression): boolean { - if (isIdentifier(expr) && expr.escapedText === "NaN") { - const globalNaNSymbol = getGlobalNaNSymbol(); - return !!globalNaNSymbol && globalNaNSymbol === getResolvedSymbol(expr); - } - return false; + function checkNaNEquality(errorNode: Node | undefined, operator: SyntaxKind, left: Expression, right: Expression) { + const isLeftNaN = isGlobalNaN(skipParentheses(left)); + const isRightNaN = isGlobalNaN(skipParentheses(right)); + if (isLeftNaN || isRightNaN) { + const err = error(errorNode, Diagnostics.This_condition_will_always_return_0, + tokenToString(operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.EqualsEqualsToken ? SyntaxKind.FalseKeyword : SyntaxKind.TrueKeyword)); + if (isLeftNaN && isRightNaN) return; + const operatorString = operator === SyntaxKind.ExclamationEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? tokenToString(SyntaxKind.ExclamationToken) : ""; + const location = isLeftNaN ? right : left; + const expression = skipParentheses(location); + addRelatedInfo(err, createDiagnosticForNode(location, Diagnostics.Did_you_mean_0, + `${operatorString}Number.isNaN(${isEntityNameExpression(expression) ? entityNameToString(expression) : "..."})`)); } } - function getBaseTypesIfUnrelated(leftType: Type, rightType: Type, isRelated: (left: Type, right: Type) => boolean): [Type, Type] { - let effectiveLeft = leftType; - let effectiveRight = rightType; - const leftBase = getBaseTypeOfLiteralType(leftType); - const rightBase = getBaseTypeOfLiteralType(rightType); - if (!isRelated(leftBase, rightBase)) { - effectiveLeft = leftBase; - effectiveRight = rightBase; + function isGlobalNaN(expr: Expression): boolean { + if (isIdentifier(expr) && expr.escapedText === "NaN") { + const globalNaNSymbol = getGlobalNaNSymbol(); + return !!globalNaNSymbol && globalNaNSymbol === getResolvedSymbol(expr); } - return [ effectiveLeft, effectiveRight ]; + return false; } + } - function checkYieldExpression(node: YieldExpression): Type { - addLazyDiagnostic(checkYieldExpressionGrammar); + function getBaseTypesIfUnrelated(leftType: Type, rightType: Type, isRelated: (left: Type, right: Type) => boolean): [Type, Type] { + let effectiveLeft = leftType; + let effectiveRight = rightType; + const leftBase = getBaseTypeOfLiteralType(leftType); + const rightBase = getBaseTypeOfLiteralType(rightType); + if (!isRelated(leftBase, rightBase)) { + effectiveLeft = leftBase; + effectiveRight = rightBase; + } + return [ effectiveLeft, effectiveRight ]; + } - const func = getContainingFunction(node); - if (!func) return anyType; - const functionFlags = getFunctionFlags(func); + function checkYieldExpression(node: YieldExpression): Type { + addLazyDiagnostic(checkYieldExpressionGrammar); - if (!(functionFlags & FunctionFlags.Generator)) { - // If the user's code is syntactically correct, the func should always have a star. After all, we are in a yield context. - return anyType; - } + const func = getContainingFunction(node); + if (!func) return anyType; + const functionFlags = getFunctionFlags(func); - const isAsync = (functionFlags & FunctionFlags.Async) !== 0; - if (node.asteriskToken) { - // Async generator functions prior to ESNext require the __await, __asyncDelegator, - // and __asyncValues helpers - if (isAsync && languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes); - } + if (!(functionFlags & FunctionFlags.Generator)) { + // If the user's code is syntactically correct, the func should always have a star. After all, we are in a yield context. + return anyType; + } - // Generator functions prior to ES2015 require the __values helper - if (!isAsync && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); - } + const isAsync = (functionFlags & FunctionFlags.Async) !== 0; + if (node.asteriskToken) { + // Async generator functions prior to ESNext require the __await, __asyncDelegator, + // and __asyncValues helpers + if (isAsync && languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes); } - // There is no point in doing an assignability check if the function - // has no explicit return type because the return type is directly computed - // from the yield expressions. - const returnType = getReturnTypeFromAnnotation(func); - const iterationTypes = returnType && getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsync); - const signatureYieldType = iterationTypes && iterationTypes.yieldType || anyType; - const signatureNextType = iterationTypes && iterationTypes.nextType || anyType; - const resolvedSignatureNextType = isAsync ? getAwaitedType(signatureNextType) || anyType : signatureNextType; - const yieldExpressionType = node.expression ? checkExpression(node.expression) : undefinedWideningType; - const yieldedType = getYieldedTypeOfYieldExpression(node, yieldExpressionType, resolvedSignatureNextType, isAsync); - if (returnType && yieldedType) { - checkTypeAssignableToAndOptionallyElaborate(yieldedType, signatureYieldType, node.expression || node, node.expression); + // Generator functions prior to ES2015 require the __values helper + if (!isAsync && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); } + } - if (node.asteriskToken) { - const use = isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar; - return getIterationTypeOfIterable(use, IterationTypeKind.Return, yieldExpressionType, node.expression) - || anyType; - } - else if (returnType) { - return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, isAsync) - || anyType; - } - let type = getContextualIterationType(IterationTypeKind.Next, func); - if (!type) { - type = anyType; - addLazyDiagnostic(() => { - if (noImplicitAny && !expressionResultIsUnused(node)) { - const contextualType = getContextualType(node, /*contextFlags*/ undefined); - if (!contextualType || isTypeAny(contextualType)) { - error(node, Diagnostics.yield_expression_implicitly_results_in_an_any_type_because_its_containing_generator_lacks_a_return_type_annotation); - } - } - }); - } - return type; + // There is no point in doing an assignability check if the function + // has no explicit return type because the return type is directly computed + // from the yield expressions. + const returnType = getReturnTypeFromAnnotation(func); + const iterationTypes = returnType && getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsync); + const signatureYieldType = iterationTypes && iterationTypes.yieldType || anyType; + const signatureNextType = iterationTypes && iterationTypes.nextType || anyType; + const resolvedSignatureNextType = isAsync ? getAwaitedType(signatureNextType) || anyType : signatureNextType; + const yieldExpressionType = node.expression ? checkExpression(node.expression) : undefinedWideningType; + const yieldedType = getYieldedTypeOfYieldExpression(node, yieldExpressionType, resolvedSignatureNextType, isAsync); + if (returnType && yieldedType) { + checkTypeAssignableToAndOptionallyElaborate(yieldedType, signatureYieldType, node.expression || node, node.expression); + } - function checkYieldExpressionGrammar() { - if (!(node.flags & NodeFlags.YieldContext)) { - grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); + if (node.asteriskToken) { + const use = isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar; + return getIterationTypeOfIterable(use, IterationTypeKind.Return, yieldExpressionType, node.expression) + || anyType; + } + else if (returnType) { + return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, isAsync) + || anyType; + } + let type = getContextualIterationType(IterationTypeKind.Next, func); + if (!type) { + type = anyType; + addLazyDiagnostic(() => { + if (noImplicitAny && !expressionResultIsUnused(node)) { + const contextualType = getContextualType(node, /*contextFlags*/ undefined); + if (!contextualType || isTypeAny(contextualType)) { + error(node, Diagnostics.yield_expression_implicitly_results_in_an_any_type_because_its_containing_generator_lacks_a_return_type_annotation); + } } + }); + } + return type; - if (isInParameterInitializerBeforeContainingFunction(node)) { - error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); - } + function checkYieldExpressionGrammar() { + if (!(node.flags & NodeFlags.YieldContext)) { + grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); } - } - function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { - const type = checkTruthinessExpression(node.condition); - checkTestingKnownTruthyCallableOrAwaitableType(node.condition, type, node.whenTrue); - const type1 = checkExpression(node.whenTrue, checkMode); - const type2 = checkExpression(node.whenFalse, checkMode); - return getUnionType([type1, type2], UnionReduction.Subtype); + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); + } } + } - function isTemplateLiteralContext(node: Node): boolean { - const parent = node.parent; - return isParenthesizedExpression(parent) && isTemplateLiteralContext(parent) || - isElementAccessExpression(parent) && parent.argumentExpression === node; - } + function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { + const type = checkTruthinessExpression(node.condition); + checkTestingKnownTruthyCallableOrAwaitableType(node.condition, type, node.whenTrue); + const type1 = checkExpression(node.whenTrue, checkMode); + const type2 = checkExpression(node.whenFalse, checkMode); + return getUnionType([type1, type2], UnionReduction.Subtype); + } - function checkTemplateExpression(node: TemplateExpression): Type { - const texts = [node.head.text]; - const types = []; - for (const span of node.templateSpans) { - const type = checkExpression(span.expression); - if (maybeTypeOfKindConsideringBaseConstraint(type, TypeFlags.ESSymbolLike)) { - error(span.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String); - } - texts.push(span.literal.text); - types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType); + function isTemplateLiteralContext(node: Node): boolean { + const parent = node.parent; + return isParenthesizedExpression(parent) && isTemplateLiteralContext(parent) || + isElementAccessExpression(parent) && parent.argumentExpression === node; + } + + function checkTemplateExpression(node: TemplateExpression): Type { + const texts = [node.head.text]; + const types = []; + for (const span of node.templateSpans) { + const type = checkExpression(span.expression); + if (maybeTypeOfKindConsideringBaseConstraint(type, TypeFlags.ESSymbolLike)) { + error(span.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String); } - return isConstContext(node) || isTemplateLiteralContext(node) || someType(getContextualType(node, /*contextFlags*/ undefined) || unknownType, isTemplateLiteralContextualType) ? getTemplateLiteralType(texts, types) : stringType; + texts.push(span.literal.text); + types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType); } + return isConstContext(node) || isTemplateLiteralContext(node) || someType(getContextualType(node, /*contextFlags*/ undefined) || unknownType, isTemplateLiteralContextualType) ? getTemplateLiteralType(texts, types) : stringType; + } - function isTemplateLiteralContextualType(type: Type): boolean { - return !!(type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral) || - type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.StringLike)); - } + function isTemplateLiteralContextualType(type: Type): boolean { + return !!(type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral) || + type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.StringLike)); + } - function getContextNode(node: Expression): Node { - if (node.kind === SyntaxKind.JsxAttributes && !isJsxSelfClosingElement(node.parent)) { - return node.parent.parent; // Needs to be the root JsxElement, so it encompasses the attributes _and_ the children (which are essentially part of the attributes) - } - return node; + function getContextNode(node: Expression): Node { + if (node.kind === SyntaxKind.JsxAttributes && !isJsxSelfClosingElement(node.parent)) { + return node.parent.parent; // Needs to be the root JsxElement, so it encompasses the attributes _and_ the children (which are essentially part of the attributes) } + return node; + } - function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { - const context = getContextNode(node); - const saveContextualType = context.contextualType; - const saveInferenceContext = context.inferenceContext; - try { - context.contextualType = contextualType; - context.inferenceContext = inferenceContext; - const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); - // In CheckMode.Inferential we collect intra-expression inference sites to process before fixing any type - // parameters. This information is no longer needed after the call to checkExpression. - if (inferenceContext && inferenceContext.intraExpressionInferenceSites) { - inferenceContext.intraExpressionInferenceSites = undefined; - } - // We strip literal freshness when an appropriate contextual type is present such that contextually typed - // literals always preserve their literal types (otherwise they might widen during type inference). An alternative - // here would be to not mark contextually typed literals as fresh in the first place. - const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node, /*contextFlags*/ undefined)) ? - getRegularTypeOfLiteralType(type) : type; - return result; - } - finally { - // In the event our operation is canceled or some other exception occurs, reset the contextual type - // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer - // may hold onto the checker that created it. - context.contextualType = saveContextualType; - context.inferenceContext = saveInferenceContext; - } + function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { + const context = getContextNode(node); + const saveContextualType = context.contextualType; + const saveInferenceContext = context.inferenceContext; + try { + context.contextualType = contextualType; + context.inferenceContext = inferenceContext; + const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); + // In CheckMode.Inferential we collect intra-expression inference sites to process before fixing any type + // parameters. This information is no longer needed after the call to checkExpression. + if (inferenceContext && inferenceContext.intraExpressionInferenceSites) { + inferenceContext.intraExpressionInferenceSites = undefined; + } + // We strip literal freshness when an appropriate contextual type is present such that contextually typed + // literals always preserve their literal types (otherwise they might widen during type inference). An alternative + // here would be to not mark contextually typed literals as fresh in the first place. + const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node, /*contextFlags*/ undefined)) ? + getRegularTypeOfLiteralType(type) : type; + return result; + } + finally { + // In the event our operation is canceled or some other exception occurs, reset the contextual type + // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer + // may hold onto the checker that created it. + context.contextualType = saveContextualType; + context.inferenceContext = saveInferenceContext; } + } - function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type { - if (checkMode) { - return checkExpression(node, checkMode); - } - const links = getNodeLinks(node); - if (!links.resolvedType) { - // When computing a type that we're going to cache, we need to ignore any ongoing control flow - // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart - // to the top of the stack ensures all transient types are computed from a known point. - const saveFlowLoopStart = flowLoopStart; - const saveFlowTypeCache = flowTypeCache; - flowLoopStart = flowLoopCount; - flowTypeCache = undefined; - links.resolvedType = checkExpression(node, checkMode); - flowTypeCache = saveFlowTypeCache; - flowLoopStart = saveFlowLoopStart; - } - return links.resolvedType; - } - - function isTypeAssertion(node: Expression) { - node = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); - return node.kind === SyntaxKind.TypeAssertionExpression || - node.kind === SyntaxKind.AsExpression || - isJSDocTypeAssertion(node); - } - - function checkDeclarationInitializer( - declaration: HasExpressionInitializer, - checkMode: CheckMode, - contextualType?: Type | undefined - ) { - const initializer = getEffectiveInitializer(declaration)!; - const type = getQuickTypeOfExpression(initializer) || - (contextualType ? - checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal) - : checkExpressionCached(initializer, checkMode)); - return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern && - isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ? - padTupleType(type, declaration.name) : type; - } - - function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) { - const patternElements = pattern.elements; - const elementTypes = getTypeArguments(type).slice(); - const elementFlags = type.target.elementFlags.slice(); - for (let i = getTypeReferenceArity(type); i < patternElements.length; i++) { - const e = patternElements[i]; - if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && e.dotDotDotToken)) { - elementTypes.push(!isOmittedExpression(e) && hasDefaultValue(e) ? getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false) : anyType); - elementFlags.push(ElementFlags.Optional); - if (!isOmittedExpression(e) && !hasDefaultValue(e)) { - reportImplicitAny(e, anyType); - } - } - } - return createTupleType(elementTypes, elementFlags, type.target.readonly); - } - - function widenTypeInferredFromInitializer(declaration: HasExpressionInitializer, type: Type) { - const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || isDeclarationReadonly(declaration) ? type : getWidenedLiteralType(type); - if (isInJSFile(declaration)) { - if (isEmptyLiteralType(widened)) { - reportImplicitAny(declaration, anyType); - return anyType; - } - else if (isEmptyArrayLiteralType(widened)) { - reportImplicitAny(declaration, anyArrayType); - return anyArrayType; + function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type { + if (checkMode) { + return checkExpression(node, checkMode); + } + const links = getNodeLinks(node); + if (!links.resolvedType) { + // When computing a type that we're going to cache, we need to ignore any ongoing control flow + // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart + // to the top of the stack ensures all transient types are computed from a known point. + const saveFlowLoopStart = flowLoopStart; + const saveFlowTypeCache = flowTypeCache; + flowLoopStart = flowLoopCount; + flowTypeCache = undefined; + links.resolvedType = checkExpression(node, checkMode); + flowTypeCache = saveFlowTypeCache; + flowLoopStart = saveFlowLoopStart; + } + return links.resolvedType; + } + + function isTypeAssertion(node: Expression) { + node = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + return node.kind === SyntaxKind.TypeAssertionExpression || + node.kind === SyntaxKind.AsExpression || + isJSDocTypeAssertion(node); + } + + function checkDeclarationInitializer( + declaration: HasExpressionInitializer, + checkMode: CheckMode, + contextualType?: Type | undefined + ) { + const initializer = getEffectiveInitializer(declaration)!; + const type = getQuickTypeOfExpression(initializer) || + (contextualType ? + checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal) + : checkExpressionCached(initializer, checkMode)); + return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern && + isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ? + padTupleType(type, declaration.name) : type; + } + + function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) { + const patternElements = pattern.elements; + const elementTypes = getTypeArguments(type).slice(); + const elementFlags = type.target.elementFlags.slice(); + for (let i = getTypeReferenceArity(type); i < patternElements.length; i++) { + const e = patternElements[i]; + if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && e.dotDotDotToken)) { + elementTypes.push(!isOmittedExpression(e) && hasDefaultValue(e) ? getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false) : anyType); + elementFlags.push(ElementFlags.Optional); + if (!isOmittedExpression(e) && !hasDefaultValue(e)) { + reportImplicitAny(e, anyType); } } - return widened; } + return createTupleType(elementTypes, elementFlags, type.target.readonly); + } - function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean { - if (contextualType) { - if (contextualType.flags & TypeFlags.UnionOrIntersection) { - const types = (contextualType as UnionType).types; - return some(types, t => isLiteralOfContextualType(candidateType, t)); - } - if (contextualType.flags & TypeFlags.InstantiableNonPrimitive) { - // If the contextual type is a type variable constrained to a primitive type, consider - // this a literal context for literals of that primitive type. For example, given a - // type parameter 'T extends string', infer string literal types for T. - const constraint = getBaseConstraintOfType(contextualType) || unknownType; - return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || - maybeTypeOfKind(constraint, TypeFlags.Number) && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || - maybeTypeOfKind(constraint, TypeFlags.BigInt) && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || - maybeTypeOfKind(constraint, TypeFlags.ESSymbol) && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) || - isLiteralOfContextualType(candidateType, constraint); - } - // If the contextual type is a literal of a particular primitive type, we consider this a - // literal context for all literals of that primitive type. - return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || - contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || - contextualType.flags & TypeFlags.BigIntLiteral && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || - contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) || - contextualType.flags & TypeFlags.UniqueESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol)); + function widenTypeInferredFromInitializer(declaration: HasExpressionInitializer, type: Type) { + const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || isDeclarationReadonly(declaration) ? type : getWidenedLiteralType(type); + if (isInJSFile(declaration)) { + if (isEmptyLiteralType(widened)) { + reportImplicitAny(declaration, anyType); + return anyType; + } + else if (isEmptyArrayLiteralType(widened)) { + reportImplicitAny(declaration, anyArrayType); + return anyArrayType; } - return false; } + return widened; + } - function isConstContext(node: Expression): boolean { - const parent = node.parent; - return isAssertionExpression(parent) && isConstTypeReference(parent.type) || - isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || - (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || - (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); - } + function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean { + if (contextualType) { + if (contextualType.flags & TypeFlags.UnionOrIntersection) { + const types = (contextualType as UnionType).types; + return some(types, t => isLiteralOfContextualType(candidateType, t)); + } + if (contextualType.flags & TypeFlags.InstantiableNonPrimitive) { + // If the contextual type is a type variable constrained to a primitive type, consider + // this a literal context for literals of that primitive type. For example, given a + // type parameter 'T extends string', infer string literal types for T. + const constraint = getBaseConstraintOfType(contextualType) || unknownType; + return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || + maybeTypeOfKind(constraint, TypeFlags.Number) && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || + maybeTypeOfKind(constraint, TypeFlags.BigInt) && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || + maybeTypeOfKind(constraint, TypeFlags.ESSymbol) && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) || + isLiteralOfContextualType(candidateType, constraint); + } + // If the contextual type is a literal of a particular primitive type, we consider this a + // literal context for all literals of that primitive type. + return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || + contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || + contextualType.flags & TypeFlags.BigIntLiteral && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || + contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) || + contextualType.flags & TypeFlags.UniqueESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol)); + } + return false; + } - function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: Type, forceTuple?: boolean): Type { - const type = checkExpression(node, checkMode, forceTuple); - return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) : - isTypeAssertion(node) ? type : - getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node, /*contextFlags*/ undefined) : contextualType, node, /*contextFlags*/ undefined)); - } + function isConstContext(node: Expression): boolean { + const parent = node.parent; + return isAssertionExpression(parent) && isConstTypeReference(parent.type) || + isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || + (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || + (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); + } - function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type { - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); - } + function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: Type, forceTuple?: boolean): Type { + const type = checkExpression(node, checkMode, forceTuple); + return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) : + isTypeAssertion(node) ? type : + getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node, /*contextFlags*/ undefined) : contextualType, node, /*contextFlags*/ undefined)); + } - return checkExpressionForMutableLocation(node.initializer, checkMode); + function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type { + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); } - function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { - // Grammar checking - checkGrammarMethod(node); + return checkExpressionForMutableLocation(node.initializer, checkMode); + } - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); - } + function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { + // Grammar checking + checkGrammarMethod(node); - const uninstantiatedType = checkFunctionExpressionOrObjectLiteralMethod(node, checkMode); - return instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); } - function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) { - if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) { - const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ true); - const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ true); - const signature = callSignature || constructSignature; - if (signature && signature.typeParameters) { - const contextualType = getApparentTypeOfContextualType(node as Expression, ContextFlags.NoConstraints); - if (contextualType) { - const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembers*/ false); - if (contextualSignature && !contextualSignature.typeParameters) { - if (checkMode & CheckMode.SkipGenericFunctions) { - skippedGenericFunction(node, checkMode); - return anyFunctionType; - } - const context = getInferenceContext(node)!; - // We have an expression that is an argument of a generic function for which we are performing - // type argument inference. The expression is of a function type with a single generic call - // signature and a contextual function type with a single non-generic call signature. Now check - // if the outer function returns a function type with a single non-generic call signature and - // if some of the outer function type parameters have no inferences so far. If so, we can - // potentially add inferred type parameters to the outer function return type. - const returnType = context.signature && getReturnTypeOfSignature(context.signature); - const returnSignature = returnType && getSingleCallOrConstructSignature(returnType); - if (returnSignature && !returnSignature.typeParameters && !every(context.inferences, hasInferenceCandidates)) { - // Instantiate the signature with its own type parameters as type arguments, possibly - // renaming the type parameters to ensure they have unique names. - const uniqueTypeParameters = getUniqueTypeParameters(context, signature.typeParameters); - const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, uniqueTypeParameters); - // Infer from the parameters of the instantiated signature to the parameters of the - // contextual signature starting with an empty set of inference candidates. - const inferences = map(context.inferences, info => createInferenceInfo(info.typeParameter)); - applyToParameterTypes(instantiatedSignature, contextualSignature, (source, target) => { - inferTypes(inferences, source, target, /*priority*/ 0, /*contravariant*/ true); + const uninstantiatedType = checkFunctionExpressionOrObjectLiteralMethod(node, checkMode); + return instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); + } + + function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) { + if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) { + const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ true); + const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ true); + const signature = callSignature || constructSignature; + if (signature && signature.typeParameters) { + const contextualType = getApparentTypeOfContextualType(node as Expression, ContextFlags.NoConstraints); + if (contextualType) { + const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembers*/ false); + if (contextualSignature && !contextualSignature.typeParameters) { + if (checkMode & CheckMode.SkipGenericFunctions) { + skippedGenericFunction(node, checkMode); + return anyFunctionType; + } + const context = getInferenceContext(node)!; + // We have an expression that is an argument of a generic function for which we are performing + // type argument inference. The expression is of a function type with a single generic call + // signature and a contextual function type with a single non-generic call signature. Now check + // if the outer function returns a function type with a single non-generic call signature and + // if some of the outer function type parameters have no inferences so far. If so, we can + // potentially add inferred type parameters to the outer function return type. + const returnType = context.signature && getReturnTypeOfSignature(context.signature); + const returnSignature = returnType && getSingleCallOrConstructSignature(returnType); + if (returnSignature && !returnSignature.typeParameters && !every(context.inferences, hasInferenceCandidates)) { + // Instantiate the signature with its own type parameters as type arguments, possibly + // renaming the type parameters to ensure they have unique names. + const uniqueTypeParameters = getUniqueTypeParameters(context, signature.typeParameters); + const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, uniqueTypeParameters); + // Infer from the parameters of the instantiated signature to the parameters of the + // contextual signature starting with an empty set of inference candidates. + const inferences = map(context.inferences, info => createInferenceInfo(info.typeParameter)); + applyToParameterTypes(instantiatedSignature, contextualSignature, (source, target) => { + inferTypes(inferences, source, target, /*priority*/ 0, /*contravariant*/ true); + }); + if (some(inferences, hasInferenceCandidates)) { + // We have inference candidates, indicating that one or more type parameters are referenced + // in the parameter types of the contextual signature. Now also infer from the return type. + applyToReturnTypes(instantiatedSignature, contextualSignature, (source, target) => { + inferTypes(inferences, source, target); }); - if (some(inferences, hasInferenceCandidates)) { - // We have inference candidates, indicating that one or more type parameters are referenced - // in the parameter types of the contextual signature. Now also infer from the return type. - applyToReturnTypes(instantiatedSignature, contextualSignature, (source, target) => { - inferTypes(inferences, source, target); - }); - // If the type parameters for which we produced candidates do not have any inferences yet, - // we adopt the new inference candidates and add the type parameters of the expression type - // to the set of inferred type parameters for the outer function return type. - if (!hasOverlappingInferences(context.inferences, inferences)) { - mergeInferences(context.inferences, inferences); - context.inferredTypeParameters = concatenate(context.inferredTypeParameters, uniqueTypeParameters); - return getOrCreateTypeFromSignature(instantiatedSignature); - } + // If the type parameters for which we produced candidates do not have any inferences yet, + // we adopt the new inference candidates and add the type parameters of the expression type + // to the set of inferred type parameters for the outer function return type. + if (!hasOverlappingInferences(context.inferences, inferences)) { + mergeInferences(context.inferences, inferences); + context.inferredTypeParameters = concatenate(context.inferredTypeParameters, uniqueTypeParameters); + return getOrCreateTypeFromSignature(instantiatedSignature); } } - return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context)); } + return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context)); } } } - return type; } + return type; + } - function skippedGenericFunction(node: Node, checkMode: CheckMode) { - if (checkMode & CheckMode.Inferential) { - // We have skipped a generic function during inferential typing. Obtain the inference context and - // indicate this has occurred such that we know a second pass of inference is be needed. - const context = getInferenceContext(node)!; - context.flags |= InferenceFlags.SkippedGenericFunction; - } + function skippedGenericFunction(node: Node, checkMode: CheckMode) { + if (checkMode & CheckMode.Inferential) { + // We have skipped a generic function during inferential typing. Obtain the inference context and + // indicate this has occurred such that we know a second pass of inference is be needed. + const context = getInferenceContext(node)!; + context.flags |= InferenceFlags.SkippedGenericFunction; } + } - function hasInferenceCandidates(info: InferenceInfo) { - return !!(info.candidates || info.contraCandidates); - } + function hasInferenceCandidates(info: InferenceInfo) { + return !!(info.candidates || info.contraCandidates); + } - function hasInferenceCandidatesOrDefault(info: InferenceInfo) { - return !!(info.candidates || info.contraCandidates || hasTypeParameterDefault(info.typeParameter)); - } + function hasInferenceCandidatesOrDefault(info: InferenceInfo) { + return !!(info.candidates || info.contraCandidates || hasTypeParameterDefault(info.typeParameter)); + } - function hasOverlappingInferences(a: InferenceInfo[], b: InferenceInfo[]) { - for (let i = 0; i < a.length; i++) { - if (hasInferenceCandidates(a[i]) && hasInferenceCandidates(b[i])) { - return true; - } + function hasOverlappingInferences(a: InferenceInfo[], b: InferenceInfo[]) { + for (let i = 0; i < a.length; i++) { + if (hasInferenceCandidates(a[i]) && hasInferenceCandidates(b[i])) { + return true; } - return false; } + return false; + } - function mergeInferences(target: InferenceInfo[], source: InferenceInfo[]) { - for (let i = 0; i < target.length; i++) { - if (!hasInferenceCandidates(target[i]) && hasInferenceCandidates(source[i])) { - target[i] = source[i]; - } + function mergeInferences(target: InferenceInfo[], source: InferenceInfo[]) { + for (let i = 0; i < target.length; i++) { + if (!hasInferenceCandidates(target[i]) && hasInferenceCandidates(source[i])) { + target[i] = source[i]; } } + } - function getUniqueTypeParameters(context: InferenceContext, typeParameters: readonly TypeParameter[]): readonly TypeParameter[] { - const result: TypeParameter[] = []; - let oldTypeParameters: TypeParameter[] | undefined; - let newTypeParameters: TypeParameter[] | undefined; - for (const tp of typeParameters) { - const name = tp.symbol.escapedName; - if (hasTypeParameterByName(context.inferredTypeParameters, name) || hasTypeParameterByName(result, name)) { - const newName = getUniqueTypeParameterName(concatenate(context.inferredTypeParameters, result), name); - const symbol = createSymbol(SymbolFlags.TypeParameter, newName); - const newTypeParameter = createTypeParameter(symbol); - newTypeParameter.target = tp; - oldTypeParameters = append(oldTypeParameters, tp); - newTypeParameters = append(newTypeParameters, newTypeParameter); - result.push(newTypeParameter); - } - else { - result.push(tp); - } + function getUniqueTypeParameters(context: InferenceContext, typeParameters: readonly TypeParameter[]): readonly TypeParameter[] { + const result: TypeParameter[] = []; + let oldTypeParameters: TypeParameter[] | undefined; + let newTypeParameters: TypeParameter[] | undefined; + for (const tp of typeParameters) { + const name = tp.symbol.escapedName; + if (hasTypeParameterByName(context.inferredTypeParameters, name) || hasTypeParameterByName(result, name)) { + const newName = getUniqueTypeParameterName(concatenate(context.inferredTypeParameters, result), name); + const symbol = createSymbol(SymbolFlags.TypeParameter, newName); + const newTypeParameter = createTypeParameter(symbol); + newTypeParameter.target = tp; + oldTypeParameters = append(oldTypeParameters, tp); + newTypeParameters = append(newTypeParameters, newTypeParameter); + result.push(newTypeParameter); } - if (newTypeParameters) { - const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); - for (const tp of newTypeParameters) { - tp.mapper = mapper; - } + else { + result.push(tp); } - return result; - } - - function hasTypeParameterByName(typeParameters: readonly TypeParameter[] | undefined, name: __String) { - return some(typeParameters, tp => tp.symbol.escapedName === name); } - - function getUniqueTypeParameterName(typeParameters: readonly TypeParameter[], baseName: __String) { - let len = (baseName as string).length; - while (len > 1 && (baseName as string).charCodeAt(len - 1) >= CharacterCodes._0 && (baseName as string).charCodeAt(len - 1) <= CharacterCodes._9) len--; - const s = (baseName as string).slice(0, len); - for (let index = 1; true; index++) { - const augmentedName = (s + index as __String); - if (!hasTypeParameterByName(typeParameters, augmentedName)) { - return augmentedName; - } + if (newTypeParameters) { + const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); + for (const tp of newTypeParameters) { + tp.mapper = mapper; } } + return result; + } - function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { - const signature = getSingleCallSignature(funcType); - if (signature && !signature.typeParameters) { - return getReturnTypeOfSignature(signature); + function hasTypeParameterByName(typeParameters: readonly TypeParameter[] | undefined, name: __String) { + return some(typeParameters, tp => tp.symbol.escapedName === name); + } + + function getUniqueTypeParameterName(typeParameters: readonly TypeParameter[], baseName: __String) { + let len = (baseName as string).length; + while (len > 1 && (baseName as string).charCodeAt(len - 1) >= CharacterCodes._0 && (baseName as string).charCodeAt(len - 1) <= CharacterCodes._9) len--; + const s = (baseName as string).slice(0, len); + for (let index = 1; true; index++) { + const augmentedName = (s + index as __String); + if (!hasTypeParameterByName(typeParameters, augmentedName)) { + return augmentedName; } } + } - function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) { - const funcType = checkExpression(expr.expression); - const nonOptionalType = getOptionalExpressionType(funcType, expr.expression); - const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType); - return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType); + function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { + const signature = getSingleCallSignature(funcType); + if (signature && !signature.typeParameters) { + return getReturnTypeOfSignature(signature); } + } - /** - * Returns the type of an expression. Unlike checkExpression, this function is simply concerned - * with computing the type and may not fully check all contained sub-expressions for errors. - */ - function getTypeOfExpression(node: Expression) { - // Don't bother caching types that require no flow analysis and are quick to compute. - const quickType = getQuickTypeOfExpression(node); - if (quickType) { - return quickType; - } - // If a type has been cached for the node, return it. - if (node.flags & NodeFlags.TypeCached && flowTypeCache) { - const cachedType = flowTypeCache[getNodeId(node)]; - if (cachedType) { - return cachedType; - } - } - const startInvocationCount = flowInvocationCount; - const type = checkExpression(node); - // If control flow analysis was required to determine the type, it is worth caching. - if (flowInvocationCount !== startInvocationCount) { - const cache = flowTypeCache || (flowTypeCache = []); - cache[getNodeId(node)] = type; - setNodeFlags(node, node.flags | NodeFlags.TypeCached); - } - return type; - } + function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) { + const funcType = checkExpression(expr.expression); + const nonOptionalType = getOptionalExpressionType(funcType, expr.expression); + const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType); + return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType); + } - function getQuickTypeOfExpression(node: Expression) { - let expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); - if (isJSDocTypeAssertion(expr)) { - const type = getJSDocTypeAssertionType(expr); - if (!isConstTypeReference(type)) { - return getTypeFromTypeNode(type); - } - } - expr = skipParentheses(node); - // Optimize for the common case of a call to a function with a single non-generic call - // signature where we can just fetch the return type without checking the arguments. - if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { - const type = isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : - getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); - if (type) { - return type; - } - } - else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { - return getTypeFromTypeNode((expr as TypeAssertion).type); - } - else if (node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral || - node.kind === SyntaxKind.TrueKeyword || node.kind === SyntaxKind.FalseKeyword) { - return checkExpression(node); - } - return undefined; - } + /** + * Returns the type of an expression. Unlike checkExpression, this function is simply concerned + * with computing the type and may not fully check all contained sub-expressions for errors. + */ + function getTypeOfExpression(node: Expression) { + // Don't bother caching types that require no flow analysis and are quick to compute. + const quickType = getQuickTypeOfExpression(node); + if (quickType) { + return quickType; + } + // If a type has been cached for the node, return it. + if (node.flags & NodeFlags.TypeCached && flowTypeCache) { + const cachedType = flowTypeCache[getNodeId(node)]; + if (cachedType) { + return cachedType; + } + } + const startInvocationCount = flowInvocationCount; + const type = checkExpression(node); + // If control flow analysis was required to determine the type, it is worth caching. + if (flowInvocationCount !== startInvocationCount) { + const cache = flowTypeCache || (flowTypeCache = []); + cache[getNodeId(node)] = type; + setNodeFlags(node, node.flags | NodeFlags.TypeCached); + } + return type; + } - /** - * Returns the type of an expression. Unlike checkExpression, this function is simply concerned - * with computing the type and may not fully check all contained sub-expressions for errors. - * It is intended for uses where you know there is no contextual type, - * and requesting the contextual type might cause a circularity or other bad behaviour. - * It sets the contextual type of the node to any before calling getTypeOfExpression. - */ - function getContextFreeTypeOfExpression(node: Expression) { - const links = getNodeLinks(node); - if (links.contextFreeType) { - return links.contextFreeType; + function getQuickTypeOfExpression(node: Expression) { + let expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + if (isJSDocTypeAssertion(expr)) { + const type = getJSDocTypeAssertionType(expr); + if (!isConstTypeReference(type)) { + return getTypeFromTypeNode(type); } - const saveContextualType = node.contextualType; - node.contextualType = anyType; - try { - const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); + } + expr = skipParentheses(node); + // Optimize for the common case of a call to a function with a single non-generic call + // signature where we can just fetch the return type without checking the arguments. + if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { + const type = isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : + getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); + if (type) { return type; } - finally { - // In the event our operation is canceled or some other exception occurs, reset the contextual type - // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer - // may hold onto the checker that created it. - node.contextualType = saveContextualType; - } } + else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { + return getTypeFromTypeNode((expr as TypeAssertion).type); + } + else if (node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral || + node.kind === SyntaxKind.TrueKeyword || node.kind === SyntaxKind.FalseKeyword) { + return checkExpression(node); + } + return undefined; + } - function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): Type { - tracing?.push(tracing.Phase.Check, "checkExpression", { kind: node.kind, pos: node.pos, end: node.end, path: (node as TracingNode).tracingPath }); - const saveCurrentNode = currentNode; - currentNode = node; - instantiationCount = 0; - const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple); - const type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); - if (isConstEnumObjectType(type)) { - checkConstEnumAccess(node, type); - } - currentNode = saveCurrentNode; - tracing?.pop(); + /** + * Returns the type of an expression. Unlike checkExpression, this function is simply concerned + * with computing the type and may not fully check all contained sub-expressions for errors. + * It is intended for uses where you know there is no contextual type, + * and requesting the contextual type might cause a circularity or other bad behaviour. + * It sets the contextual type of the node to any before calling getTypeOfExpression. + */ + function getContextFreeTypeOfExpression(node: Expression) { + const links = getNodeLinks(node); + if (links.contextFreeType) { + return links.contextFreeType; + } + const saveContextualType = node.contextualType; + node.contextualType = anyType; + try { + const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); return type; } + finally { + // In the event our operation is canceled or some other exception occurs, reset the contextual type + // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer + // may hold onto the checker that created it. + node.contextualType = saveContextualType; + } + } - function checkConstEnumAccess(node: Expression | QualifiedName, type: Type) { - // enum object type for const enums are only permitted in: - // - 'left' in property access - // - 'object' in indexed access - // - target in rhs of import statement - const ok = - (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent as PropertyAccessExpression).expression === node) || - (node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent as ElementAccessExpression).expression === node) || - ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node as Identifier) || - (node.parent.kind === SyntaxKind.TypeQuery && (node.parent as TypeQueryNode).exprName === node)) || - (node.parent.kind === SyntaxKind.ExportSpecifier); // We allow reexporting const enums + function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): Type { + tracing?.push(tracing.Phase.Check, "checkExpression", { kind: node.kind, pos: node.pos, end: node.end, path: (node as TracingNode).tracingPath }); + const saveCurrentNode = currentNode; + currentNode = node; + instantiationCount = 0; + const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple); + const type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); + if (isConstEnumObjectType(type)) { + checkConstEnumAccess(node, type); + } + currentNode = saveCurrentNode; + tracing?.pop(); + return type; + } - if (!ok) { - error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query); - } + function checkConstEnumAccess(node: Expression | QualifiedName, type: Type) { + // enum object type for const enums are only permitted in: + // - 'left' in property access + // - 'object' in indexed access + // - target in rhs of import statement + const ok = + (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent as PropertyAccessExpression).expression === node) || + (node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent as ElementAccessExpression).expression === node) || + ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node as Identifier) || + (node.parent.kind === SyntaxKind.TypeQuery && (node.parent as TypeQueryNode).exprName === node)) || + (node.parent.kind === SyntaxKind.ExportSpecifier); // We allow reexporting const enums - if (compilerOptions.isolatedModules) { - Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum)); - const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration; - if (constEnumDeclaration.flags & NodeFlags.Ambient) { - error(node, Diagnostics.Cannot_access_ambient_const_enums_when_the_isolatedModules_flag_is_provided); - } - } + if (!ok) { + error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query); } - function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { - if (hasJSDocNodes(node) && isJSDocTypeAssertion(node)) { - const type = getJSDocTypeAssertionType(node); - return checkAssertionWorker(type, type, node.expression, checkMode); + if (compilerOptions.isolatedModules) { + Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum)); + const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration; + if (constEnumDeclaration.flags & NodeFlags.Ambient) { + error(node, Diagnostics.Cannot_access_ambient_const_enums_when_the_isolatedModules_flag_is_provided); } - return checkExpression(node.expression, checkMode); } + } + + function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { + if (hasJSDocNodes(node) && isJSDocTypeAssertion(node)) { + const type = getJSDocTypeAssertionType(node); + return checkAssertionWorker(type, type, node.expression, checkMode); + } + return checkExpression(node.expression, checkMode); + } - function checkExpressionWorker(node: Expression | QualifiedName, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { - const kind = node.kind; - if (cancellationToken) { - // Only bother checking on a few construct kinds. We don't want to be excessively - // hitting the cancellation token on every node we check. - switch (kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - cancellationToken.throwIfCancellationRequested(); - } - } + function checkExpressionWorker(node: Expression | QualifiedName, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { + const kind = node.kind; + if (cancellationToken) { + // Only bother checking on a few construct kinds. We don't want to be excessively + // hitting the cancellation token on every node we check. switch (kind) { - case SyntaxKind.Identifier: - return checkIdentifier(node as Identifier, checkMode); - case SyntaxKind.PrivateIdentifier: - return checkPrivateIdentifierExpression(node as PrivateIdentifier); - case SyntaxKind.ThisKeyword: - return checkThisExpression(node); - case SyntaxKind.SuperKeyword: - return checkSuperExpression(node); - case SyntaxKind.NullKeyword: - return nullWideningType; - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.StringLiteral: - return getFreshTypeOfLiteralType(getStringLiteralType((node as StringLiteralLike).text)); - case SyntaxKind.NumericLiteral: - checkGrammarNumericLiteral(node as NumericLiteral); - return getFreshTypeOfLiteralType(getNumberLiteralType(+(node as NumericLiteral).text)); - case SyntaxKind.BigIntLiteral: - checkGrammarBigIntLiteral(node as BigIntLiteral); - return getFreshTypeOfLiteralType(getBigIntLiteralType({ - negative: false, - base10Value: parsePseudoBigInt((node as BigIntLiteral).text) - })); - case SyntaxKind.TrueKeyword: - return trueType; - case SyntaxKind.FalseKeyword: - return falseType; - case SyntaxKind.TemplateExpression: - return checkTemplateExpression(node as TemplateExpression); - case SyntaxKind.RegularExpressionLiteral: - return globalRegExpType; - case SyntaxKind.ArrayLiteralExpression: - return checkArrayLiteral(node as ArrayLiteralExpression, checkMode, forceTuple); - case SyntaxKind.ObjectLiteralExpression: - return checkObjectLiteral(node as ObjectLiteralExpression, checkMode); - case SyntaxKind.PropertyAccessExpression: - return checkPropertyAccessExpression(node as PropertyAccessExpression, checkMode); - case SyntaxKind.QualifiedName: - return checkQualifiedName(node as QualifiedName, checkMode); - case SyntaxKind.ElementAccessExpression: - return checkIndexedAccess(node as ElementAccessExpression, checkMode); - case SyntaxKind.CallExpression: - if ((node as CallExpression).expression.kind === SyntaxKind.ImportKeyword) { - return checkImportCallExpression(node as ImportCall); - } - // falls through - case SyntaxKind.NewExpression: - return checkCallExpression(node as CallExpression, checkMode); - case SyntaxKind.TaggedTemplateExpression: - return checkTaggedTemplateExpression(node as TaggedTemplateExpression); - case SyntaxKind.ParenthesizedExpression: - return checkParenthesizedExpression(node as ParenthesizedExpression, checkMode); case SyntaxKind.ClassExpression: - return checkClassExpression(node as ClassExpression); case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: - return checkFunctionExpressionOrObjectLiteralMethod(node as FunctionExpression | ArrowFunction, checkMode); - case SyntaxKind.TypeOfExpression: - return checkTypeOfExpression(node as TypeOfExpression); - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - return checkAssertion(node as AssertionExpression); - case SyntaxKind.NonNullExpression: - return checkNonNullAssertion(node as NonNullExpression); - case SyntaxKind.ExpressionWithTypeArguments: - return checkExpressionWithTypeArguments(node as ExpressionWithTypeArguments); - case SyntaxKind.SatisfiesExpression: - return checkSatisfiesExpression(node as SatisfiesExpression); - case SyntaxKind.MetaProperty: - return checkMetaProperty(node as MetaProperty); - case SyntaxKind.DeleteExpression: - return checkDeleteExpression(node as DeleteExpression); - case SyntaxKind.VoidExpression: - return checkVoidExpression(node as VoidExpression); - case SyntaxKind.AwaitExpression: - return checkAwaitExpression(node as AwaitExpression); - case SyntaxKind.PrefixUnaryExpression: - return checkPrefixUnaryExpression(node as PrefixUnaryExpression); - case SyntaxKind.PostfixUnaryExpression: - return checkPostfixUnaryExpression(node as PostfixUnaryExpression); - case SyntaxKind.BinaryExpression: - return checkBinaryExpression(node as BinaryExpression, checkMode); - case SyntaxKind.ConditionalExpression: - return checkConditionalExpression(node as ConditionalExpression, checkMode); - case SyntaxKind.SpreadElement: - return checkSpreadExpression(node as SpreadElement, checkMode); - case SyntaxKind.OmittedExpression: - return undefinedWideningType; - case SyntaxKind.YieldExpression: - return checkYieldExpression(node as YieldExpression); - case SyntaxKind.SyntheticExpression: - return checkSyntheticExpression(node as SyntheticExpression); - case SyntaxKind.JsxExpression: - return checkJsxExpression(node as JsxExpression, checkMode); - case SyntaxKind.JsxElement: - return checkJsxElement(node as JsxElement, checkMode); - case SyntaxKind.JsxSelfClosingElement: - return checkJsxSelfClosingElement(node as JsxSelfClosingElement, checkMode); - case SyntaxKind.JsxFragment: - return checkJsxFragment(node as JsxFragment); - case SyntaxKind.JsxAttributes: - return checkJsxAttributes(node as JsxAttributes, checkMode); - case SyntaxKind.JsxOpeningElement: - Debug.fail("Shouldn't ever directly check a JsxOpeningElement"); + cancellationToken.throwIfCancellationRequested(); } - return errorType; } + switch (kind) { + case SyntaxKind.Identifier: + return checkIdentifier(node as Identifier, checkMode); + case SyntaxKind.PrivateIdentifier: + return checkPrivateIdentifierExpression(node as PrivateIdentifier); + case SyntaxKind.ThisKeyword: + return checkThisExpression(node); + case SyntaxKind.SuperKeyword: + return checkSuperExpression(node); + case SyntaxKind.NullKeyword: + return nullWideningType; + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.StringLiteral: + return getFreshTypeOfLiteralType(getStringLiteralType((node as StringLiteralLike).text)); + case SyntaxKind.NumericLiteral: + checkGrammarNumericLiteral(node as NumericLiteral); + return getFreshTypeOfLiteralType(getNumberLiteralType(+(node as NumericLiteral).text)); + case SyntaxKind.BigIntLiteral: + checkGrammarBigIntLiteral(node as BigIntLiteral); + return getFreshTypeOfLiteralType(getBigIntLiteralType({ + negative: false, + base10Value: parsePseudoBigInt((node as BigIntLiteral).text) + })); + case SyntaxKind.TrueKeyword: + return trueType; + case SyntaxKind.FalseKeyword: + return falseType; + case SyntaxKind.TemplateExpression: + return checkTemplateExpression(node as TemplateExpression); + case SyntaxKind.RegularExpressionLiteral: + return globalRegExpType; + case SyntaxKind.ArrayLiteralExpression: + return checkArrayLiteral(node as ArrayLiteralExpression, checkMode, forceTuple); + case SyntaxKind.ObjectLiteralExpression: + return checkObjectLiteral(node as ObjectLiteralExpression, checkMode); + case SyntaxKind.PropertyAccessExpression: + return checkPropertyAccessExpression(node as PropertyAccessExpression, checkMode); + case SyntaxKind.QualifiedName: + return checkQualifiedName(node as QualifiedName, checkMode); + case SyntaxKind.ElementAccessExpression: + return checkIndexedAccess(node as ElementAccessExpression, checkMode); + case SyntaxKind.CallExpression: + if ((node as CallExpression).expression.kind === SyntaxKind.ImportKeyword) { + return checkImportCallExpression(node as ImportCall); + } + // falls through + case SyntaxKind.NewExpression: + return checkCallExpression(node as CallExpression, checkMode); + case SyntaxKind.TaggedTemplateExpression: + return checkTaggedTemplateExpression(node as TaggedTemplateExpression); + case SyntaxKind.ParenthesizedExpression: + return checkParenthesizedExpression(node as ParenthesizedExpression, checkMode); + case SyntaxKind.ClassExpression: + return checkClassExpression(node as ClassExpression); + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return checkFunctionExpressionOrObjectLiteralMethod(node as FunctionExpression | ArrowFunction, checkMode); + case SyntaxKind.TypeOfExpression: + return checkTypeOfExpression(node as TypeOfExpression); + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return checkAssertion(node as AssertionExpression); + case SyntaxKind.NonNullExpression: + return checkNonNullAssertion(node as NonNullExpression); + case SyntaxKind.ExpressionWithTypeArguments: + return checkExpressionWithTypeArguments(node as ExpressionWithTypeArguments); + case SyntaxKind.SatisfiesExpression: + return checkSatisfiesExpression(node as SatisfiesExpression); + case SyntaxKind.MetaProperty: + return checkMetaProperty(node as MetaProperty); + case SyntaxKind.DeleteExpression: + return checkDeleteExpression(node as DeleteExpression); + case SyntaxKind.VoidExpression: + return checkVoidExpression(node as VoidExpression); + case SyntaxKind.AwaitExpression: + return checkAwaitExpression(node as AwaitExpression); + case SyntaxKind.PrefixUnaryExpression: + return checkPrefixUnaryExpression(node as PrefixUnaryExpression); + case SyntaxKind.PostfixUnaryExpression: + return checkPostfixUnaryExpression(node as PostfixUnaryExpression); + case SyntaxKind.BinaryExpression: + return checkBinaryExpression(node as BinaryExpression, checkMode); + case SyntaxKind.ConditionalExpression: + return checkConditionalExpression(node as ConditionalExpression, checkMode); + case SyntaxKind.SpreadElement: + return checkSpreadExpression(node as SpreadElement, checkMode); + case SyntaxKind.OmittedExpression: + return undefinedWideningType; + case SyntaxKind.YieldExpression: + return checkYieldExpression(node as YieldExpression); + case SyntaxKind.SyntheticExpression: + return checkSyntheticExpression(node as SyntheticExpression); + case SyntaxKind.JsxExpression: + return checkJsxExpression(node as JsxExpression, checkMode); + case SyntaxKind.JsxElement: + return checkJsxElement(node as JsxElement, checkMode); + case SyntaxKind.JsxSelfClosingElement: + return checkJsxSelfClosingElement(node as JsxSelfClosingElement, checkMode); + case SyntaxKind.JsxFragment: + return checkJsxFragment(node as JsxFragment); + case SyntaxKind.JsxAttributes: + return checkJsxAttributes(node as JsxAttributes, checkMode); + case SyntaxKind.JsxOpeningElement: + Debug.fail("Shouldn't ever directly check a JsxOpeningElement"); + } + return errorType; + } - // DECLARATION AND STATEMENT TYPE CHECKING + // DECLARATION AND STATEMENT TYPE CHECKING - function checkTypeParameter(node: TypeParameterDeclaration) { - // Grammar Checking - checkGrammarModifiers(node); - if (node.expression) { - grammarErrorOnFirstToken(node.expression, Diagnostics.Type_expected); - } + function checkTypeParameter(node: TypeParameterDeclaration) { + // Grammar Checking + checkGrammarModifiers(node); + if (node.expression) { + grammarErrorOnFirstToken(node.expression, Diagnostics.Type_expected); + } - checkSourceElement(node.constraint); - checkSourceElement(node.default); - const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)); - // Resolve base constraint to reveal circularity errors - getBaseConstraintOfType(typeParameter); - if (!hasNonCircularTypeParameterDefault(typeParameter)) { - error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter)); - } - const constraintType = getConstraintOfTypeParameter(typeParameter); - const defaultType = getDefaultFromTypeParameter(typeParameter); - if (constraintType && defaultType) { - checkTypeAssignableTo(defaultType, getTypeWithThisArgument(instantiateType(constraintType, makeUnaryTypeMapper(typeParameter, defaultType)), defaultType), node.default, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); - } - checkNodeDeferred(node); - addLazyDiagnostic(() => checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0)); + checkSourceElement(node.constraint); + checkSourceElement(node.default); + const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)); + // Resolve base constraint to reveal circularity errors + getBaseConstraintOfType(typeParameter); + if (!hasNonCircularTypeParameterDefault(typeParameter)) { + error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter)); } + const constraintType = getConstraintOfTypeParameter(typeParameter); + const defaultType = getDefaultFromTypeParameter(typeParameter); + if (constraintType && defaultType) { + checkTypeAssignableTo(defaultType, getTypeWithThisArgument(instantiateType(constraintType, makeUnaryTypeMapper(typeParameter, defaultType)), defaultType), node.default, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); + } + checkNodeDeferred(node); + addLazyDiagnostic(() => checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0)); + } - function checkTypeParameterDeferred(node: TypeParameterDeclaration) { - if (isInterfaceDeclaration(node.parent) || isClassLike(node.parent) || isTypeAliasDeclaration(node.parent)) { - const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)); - const modifiers = getVarianceModifiers(typeParameter); - if (modifiers) { - const symbol = getSymbolOfNode(node.parent); - if (isTypeAliasDeclaration(node.parent) && !(getObjectFlags(getDeclaredTypeOfSymbol(symbol)) & (ObjectFlags.Anonymous | ObjectFlags.Mapped))) { - error(node, Diagnostics.Variance_annotations_are_only_supported_in_type_aliases_for_object_function_constructor_and_mapped_types); - } - else if (modifiers === ModifierFlags.In || modifiers === ModifierFlags.Out) { - tracing?.push(tracing.Phase.CheckTypes, "checkTypeParameterDeferred", { parent: getTypeId(getDeclaredTypeOfSymbol(symbol)), id: getTypeId(typeParameter) }); - const source = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSubTypeForCheck : markerSuperTypeForCheck); - const target = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSuperTypeForCheck : markerSubTypeForCheck); - const saveVarianceTypeParameter = typeParameter; - varianceTypeParameter = typeParameter; - checkTypeAssignableTo(source, target, node, Diagnostics.Type_0_is_not_assignable_to_type_1_as_implied_by_variance_annotation); - varianceTypeParameter = saveVarianceTypeParameter; - tracing?.pop(); - } + function checkTypeParameterDeferred(node: TypeParameterDeclaration) { + if (isInterfaceDeclaration(node.parent) || isClassLike(node.parent) || isTypeAliasDeclaration(node.parent)) { + const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)); + const modifiers = getVarianceModifiers(typeParameter); + if (modifiers) { + const symbol = getSymbolOfNode(node.parent); + if (isTypeAliasDeclaration(node.parent) && !(getObjectFlags(getDeclaredTypeOfSymbol(symbol)) & (ObjectFlags.Anonymous | ObjectFlags.Mapped))) { + error(node, Diagnostics.Variance_annotations_are_only_supported_in_type_aliases_for_object_function_constructor_and_mapped_types); + } + else if (modifiers === ModifierFlags.In || modifiers === ModifierFlags.Out) { + tracing?.push(tracing.Phase.CheckTypes, "checkTypeParameterDeferred", { parent: getTypeId(getDeclaredTypeOfSymbol(symbol)), id: getTypeId(typeParameter) }); + const source = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSubTypeForCheck : markerSuperTypeForCheck); + const target = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSuperTypeForCheck : markerSubTypeForCheck); + const saveVarianceTypeParameter = typeParameter; + varianceTypeParameter = typeParameter; + checkTypeAssignableTo(source, target, node, Diagnostics.Type_0_is_not_assignable_to_type_1_as_implied_by_variance_annotation); + varianceTypeParameter = saveVarianceTypeParameter; + tracing?.pop(); } } } + } - function checkParameter(node: ParameterDeclaration) { - // Grammar checking - // It is a SyntaxError if the Identifier "eval" or the Identifier "arguments" occurs as the - // Identifier in a PropertySetParameterList of a PropertyAssignment that is contained in strict code - // or if its FunctionBody is strict code(11.1.5). - checkGrammarDecoratorsAndModifiers(node); + function checkParameter(node: ParameterDeclaration) { + // Grammar checking + // It is a SyntaxError if the Identifier "eval" or the Identifier "arguments" occurs as the + // Identifier in a PropertySetParameterList of a PropertyAssignment that is contained in strict code + // or if its FunctionBody is strict code(11.1.5). + checkGrammarDecoratorsAndModifiers(node); - checkVariableLikeDeclaration(node); - const func = getContainingFunction(node)!; - if (hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { - if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) { - error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); - } - if (func.kind === SyntaxKind.Constructor && isIdentifier(node.name) && node.name.escapedText === "constructor") { - error(node.name, Diagnostics.constructor_cannot_be_used_as_a_parameter_property_name); - } + checkVariableLikeDeclaration(node); + const func = getContainingFunction(node)!; + if (hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { + if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) { + error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); } - if ((node.questionToken || isJSDocOptionalParameter(node)) && isBindingPattern(node.name) && (func as FunctionLikeDeclaration).body) { - error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature); + if (func.kind === SyntaxKind.Constructor && isIdentifier(node.name) && node.name.escapedText === "constructor") { + error(node.name, Diagnostics.constructor_cannot_be_used_as_a_parameter_property_name); } - if (node.name && isIdentifier(node.name) && (node.name.escapedText === "this" || node.name.escapedText === "new")) { - if (func.parameters.indexOf(node) !== 0) { - error(node, Diagnostics.A_0_parameter_must_be_the_first_parameter, node.name.escapedText as string); - } - if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) { - error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter); - } - if (func.kind === SyntaxKind.ArrowFunction) { - error(node, Diagnostics.An_arrow_function_cannot_have_a_this_parameter); - } - if (func.kind === SyntaxKind.GetAccessor || func.kind === SyntaxKind.SetAccessor) { - error(node, Diagnostics.get_and_set_accessors_cannot_declare_this_parameters); - } + } + if ((node.questionToken || isJSDocOptionalParameter(node)) && isBindingPattern(node.name) && (func as FunctionLikeDeclaration).body) { + error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature); + } + if (node.name && isIdentifier(node.name) && (node.name.escapedText === "this" || node.name.escapedText === "new")) { + if (func.parameters.indexOf(node) !== 0) { + error(node, Diagnostics.A_0_parameter_must_be_the_first_parameter, node.name.escapedText as string); } - - // Only check rest parameter type if it's not a binding pattern. Since binding patterns are - // not allowed in a rest parameter, we already have an error from checkGrammarParameterList. - if (node.dotDotDotToken && !isBindingPattern(node.name) && !isTypeAssignableTo(getReducedType(getTypeOfSymbol(node.symbol)), anyReadonlyArrayType)) { - error(node, Diagnostics.A_rest_parameter_must_be_of_an_array_type); + if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) { + error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter); + } + if (func.kind === SyntaxKind.ArrowFunction) { + error(node, Diagnostics.An_arrow_function_cannot_have_a_this_parameter); + } + if (func.kind === SyntaxKind.GetAccessor || func.kind === SyntaxKind.SetAccessor) { + error(node, Diagnostics.get_and_set_accessors_cannot_declare_this_parameters); } } - function checkTypePredicate(node: TypePredicateNode): void { - const parent = getTypePredicateParent(node); - if (!parent) { - // The parent must not be valid. - error(node, Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); - return; - } + // Only check rest parameter type if it's not a binding pattern. Since binding patterns are + // not allowed in a rest parameter, we already have an error from checkGrammarParameterList. + if (node.dotDotDotToken && !isBindingPattern(node.name) && !isTypeAssignableTo(getReducedType(getTypeOfSymbol(node.symbol)), anyReadonlyArrayType)) { + error(node, Diagnostics.A_rest_parameter_must_be_of_an_array_type); + } + } - const signature = getSignatureFromDeclaration(parent); - const typePredicate = getTypePredicateOfSignature(signature); - if (!typePredicate) { - return; - } + function checkTypePredicate(node: TypePredicateNode): void { + const parent = getTypePredicateParent(node); + if (!parent) { + // The parent must not be valid. + error(node, Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); + return; + } - checkSourceElement(node.type); + const signature = getSignatureFromDeclaration(parent); + const typePredicate = getTypePredicateOfSignature(signature); + if (!typePredicate) { + return; + } - const { parameterName } = node; - if (typePredicate.kind === TypePredicateKind.This || typePredicate.kind === TypePredicateKind.AssertsThis) { - getTypeFromThisTypeNode(parameterName as ThisTypeNode); - } - else { - if (typePredicate.parameterIndex >= 0) { - if (signatureHasRestParameter(signature) && typePredicate.parameterIndex === signature.parameters.length - 1) { - error(parameterName, Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter); - } - else { - if (typePredicate.type) { - const leadingError = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type); - checkTypeAssignableTo(typePredicate.type, - getTypeOfSymbol(signature.parameters[typePredicate.parameterIndex]), - node.type, - /*headMessage*/ undefined, - leadingError); - } - } - } - else if (parameterName) { - let hasReportedError = false; - for (const { name } of parent.parameters) { - if (isBindingPattern(name) && - checkIfTypePredicateVariableIsDeclaredInBindingPattern(name, parameterName, typePredicate.parameterName)) { - hasReportedError = true; - break; - } - } - if (!hasReportedError) { - error(node.parameterName, Diagnostics.Cannot_find_parameter_0, typePredicate.parameterName); + checkSourceElement(node.type); + + const { parameterName } = node; + if (typePredicate.kind === TypePredicateKind.This || typePredicate.kind === TypePredicateKind.AssertsThis) { + getTypeFromThisTypeNode(parameterName as ThisTypeNode); + } + else { + if (typePredicate.parameterIndex >= 0) { + if (signatureHasRestParameter(signature) && typePredicate.parameterIndex === signature.parameters.length - 1) { + error(parameterName, Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter); + } + else { + if (typePredicate.type) { + const leadingError = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type); + checkTypeAssignableTo(typePredicate.type, + getTypeOfSymbol(signature.parameters[typePredicate.parameterIndex]), + node.type, + /*headMessage*/ undefined, + leadingError); + } + } + } + else if (parameterName) { + let hasReportedError = false; + for (const { name } of parent.parameters) { + if (isBindingPattern(name) && + checkIfTypePredicateVariableIsDeclaredInBindingPattern(name, parameterName, typePredicate.parameterName)) { + hasReportedError = true; + break; } } + if (!hasReportedError) { + error(node.parameterName, Diagnostics.Cannot_find_parameter_0, typePredicate.parameterName); + } } } + } - function getTypePredicateParent(node: Node): SignatureDeclaration | undefined { - switch (node.parent.kind) { - case SyntaxKind.ArrowFunction: - case SyntaxKind.CallSignature: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionType: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - const parent = node.parent as SignatureDeclaration; - if (node === parent.type) { - return parent; - } - } + function getTypePredicateParent(node: Node): SignatureDeclaration | undefined { + switch (node.parent.kind) { + case SyntaxKind.ArrowFunction: + case SyntaxKind.CallSignature: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionType: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + const parent = node.parent as SignatureDeclaration; + if (node === parent.type) { + return parent; + } } + } - function checkIfTypePredicateVariableIsDeclaredInBindingPattern( - pattern: BindingPattern, - predicateVariableNode: Node, - predicateVariableName: string) { - for (const element of pattern.elements) { - if (isOmittedExpression(element)) { - continue; - } + function checkIfTypePredicateVariableIsDeclaredInBindingPattern( + pattern: BindingPattern, + predicateVariableNode: Node, + predicateVariableName: string) { + for (const element of pattern.elements) { + if (isOmittedExpression(element)) { + continue; + } - const name = element.name; - if (name.kind === SyntaxKind.Identifier && name.escapedText === predicateVariableName) { - error(predicateVariableNode, - Diagnostics.A_type_predicate_cannot_reference_element_0_in_a_binding_pattern, - predicateVariableName); + const name = element.name; + if (name.kind === SyntaxKind.Identifier && name.escapedText === predicateVariableName) { + error(predicateVariableNode, + Diagnostics.A_type_predicate_cannot_reference_element_0_in_a_binding_pattern, + predicateVariableName); + return true; + } + else if (name.kind === SyntaxKind.ArrayBindingPattern || name.kind === SyntaxKind.ObjectBindingPattern) { + if (checkIfTypePredicateVariableIsDeclaredInBindingPattern( + name, + predicateVariableNode, + predicateVariableName)) { return true; } - else if (name.kind === SyntaxKind.ArrayBindingPattern || name.kind === SyntaxKind.ObjectBindingPattern) { - if (checkIfTypePredicateVariableIsDeclaredInBindingPattern( - name, - predicateVariableNode, - predicateVariableName)) { - return true; - } - } } } + } - function checkSignatureDeclaration(node: SignatureDeclaration) { - // Grammar checking - if (node.kind === SyntaxKind.IndexSignature) { - checkGrammarIndexSignature(node); - } - // TODO (yuisu): Remove this check in else-if when SyntaxKind.Construct is moved and ambient context is handled - else if (node.kind === SyntaxKind.FunctionType || node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.ConstructorType || - node.kind === SyntaxKind.CallSignature || node.kind === SyntaxKind.Constructor || - node.kind === SyntaxKind.ConstructSignature) { - checkGrammarFunctionLikeDeclaration(node as FunctionLikeDeclaration); - } + function checkSignatureDeclaration(node: SignatureDeclaration) { + // Grammar checking + if (node.kind === SyntaxKind.IndexSignature) { + checkGrammarIndexSignature(node); + } + // TODO (yuisu): Remove this check in else-if when SyntaxKind.Construct is moved and ambient context is handled + else if (node.kind === SyntaxKind.FunctionType || node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.ConstructorType || + node.kind === SyntaxKind.CallSignature || node.kind === SyntaxKind.Constructor || + node.kind === SyntaxKind.ConstructSignature) { + checkGrammarFunctionLikeDeclaration(node as FunctionLikeDeclaration); + } - const functionFlags = getFunctionFlags(node as FunctionLikeDeclaration); - if (!(functionFlags & FunctionFlags.Invalid)) { - // Async generators prior to ESNext require the __await and __asyncGenerator helpers - if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator && languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncGeneratorIncludes); - } + const functionFlags = getFunctionFlags(node as FunctionLikeDeclaration); + if (!(functionFlags & FunctionFlags.Invalid)) { + // Async generators prior to ESNext require the __await and __asyncGenerator helpers + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator && languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncGeneratorIncludes); + } - // Async functions prior to ES2017 require the __awaiter helper - if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < ScriptTarget.ES2017) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Awaiter); - } + // Async functions prior to ES2017 require the __awaiter helper + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < ScriptTarget.ES2017) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Awaiter); + } - // Generator functions, Async functions, and Async Generator functions prior to - // ES2015 require the __generator helper - if ((functionFlags & FunctionFlags.AsyncGenerator) !== FunctionFlags.Normal && languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator); - } + // Generator functions, Async functions, and Async Generator functions prior to + // ES2015 require the __generator helper + if ((functionFlags & FunctionFlags.AsyncGenerator) !== FunctionFlags.Normal && languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator); } + } - checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); - checkUnmatchedJSDocParameters(node); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkUnmatchedJSDocParameters(node); - forEach(node.parameters, checkParameter); + forEach(node.parameters, checkParameter); - // TODO(rbuckton): Should we start checking JSDoc types? - if (node.type) { - checkSourceElement(node.type); - } + // TODO(rbuckton): Should we start checking JSDoc types? + if (node.type) { + checkSourceElement(node.type); + } - addLazyDiagnostic(checkSignatureDeclarationDiagnostics); + addLazyDiagnostic(checkSignatureDeclarationDiagnostics); - function checkSignatureDeclarationDiagnostics() { - checkCollisionWithArgumentsInGeneratedCode(node); - const returnTypeNode = getEffectiveReturnTypeNode(node); - if (noImplicitAny && !returnTypeNode) { - switch (node.kind) { - case SyntaxKind.ConstructSignature: - error(node, Diagnostics.Construct_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); - break; - case SyntaxKind.CallSignature: - error(node, Diagnostics.Call_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); - break; - } + function checkSignatureDeclarationDiagnostics() { + checkCollisionWithArgumentsInGeneratedCode(node); + const returnTypeNode = getEffectiveReturnTypeNode(node); + if (noImplicitAny && !returnTypeNode) { + switch (node.kind) { + case SyntaxKind.ConstructSignature: + error(node, Diagnostics.Construct_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); + break; + case SyntaxKind.CallSignature: + error(node, Diagnostics.Call_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); + break; } + } - if (returnTypeNode) { - const functionFlags = getFunctionFlags(node as FunctionDeclaration); - if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) { - const returnType = getTypeFromTypeNode(returnTypeNode); - if (returnType === voidType) { - error(returnTypeNode, Diagnostics.A_generator_cannot_have_a_void_type_annotation); - } - else { - // Naively, one could check that Generator is assignable to the return type annotation. - // However, that would not catch the error in the following case. - // - // interface BadGenerator extends Iterable, Iterator { } - // function* g(): BadGenerator { } // Iterable and Iterator have different types! - // - const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType; - const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType; - const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType; - const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async)); - checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeNode); - } + if (returnTypeNode) { + const functionFlags = getFunctionFlags(node as FunctionDeclaration); + if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) { + const returnType = getTypeFromTypeNode(returnTypeNode); + if (returnType === voidType) { + error(returnTypeNode, Diagnostics.A_generator_cannot_have_a_void_type_annotation); } - else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { - checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode); + else { + // Naively, one could check that Generator is assignable to the return type annotation. + // However, that would not catch the error in the following case. + // + // interface BadGenerator extends Iterable, Iterator { } + // function* g(): BadGenerator { } // Iterable and Iterator have different types! + // + const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType; + const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType; + const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType; + const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async)); + checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeNode); } } - if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { - registerForUnusedIdentifiersCheck(node); + else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { + checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode); } } + if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { + registerForUnusedIdentifiersCheck(node); + } } + } - function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { - const instanceNames = new Map<__String, DeclarationMeaning>(); - const staticNames = new Map<__String, DeclarationMeaning>(); - // instance and static private identifiers share the same scope - const privateIdentifiers = new Map<__String, DeclarationMeaning>(); - for (const member of node.members) { - if (member.kind === SyntaxKind.Constructor) { - for (const param of (member as ConstructorDeclaration).parameters) { - if (isParameterPropertyDeclaration(param, member) && !isBindingPattern(param.name)) { - addName(instanceNames, param.name, param.name.escapedText, DeclarationMeaning.GetOrSetAccessor); - } + function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { + const instanceNames = new Map<__String, DeclarationMeaning>(); + const staticNames = new Map<__String, DeclarationMeaning>(); + // instance and static private identifiers share the same scope + const privateIdentifiers = new Map<__String, DeclarationMeaning>(); + for (const member of node.members) { + if (member.kind === SyntaxKind.Constructor) { + for (const param of (member as ConstructorDeclaration).parameters) { + if (isParameterPropertyDeclaration(param, member) && !isBindingPattern(param.name)) { + addName(instanceNames, param.name, param.name.escapedText, DeclarationMeaning.GetOrSetAccessor); } } - else { - const isStaticMember = isStatic(member); - const name = member.name; - if (!name) { - continue; - } - const isPrivate = isPrivateIdentifier(name); - const privateStaticFlags = isPrivate && isStaticMember ? DeclarationMeaning.PrivateStatic : 0; - const names = - isPrivate ? privateIdentifiers : - isStaticMember ? staticNames : - instanceNames; + } + else { + const isStaticMember = isStatic(member); + const name = member.name; + if (!name) { + continue; + } + const isPrivate = isPrivateIdentifier(name); + const privateStaticFlags = isPrivate && isStaticMember ? DeclarationMeaning.PrivateStatic : 0; + const names = + isPrivate ? privateIdentifiers : + isStaticMember ? staticNames : + instanceNames; - const memberName = name && getPropertyNameForPropertyNameNode(name); - if (memberName) { - switch (member.kind) { - case SyntaxKind.GetAccessor: - addName(names, name, memberName, DeclarationMeaning.GetAccessor | privateStaticFlags); - break; + const memberName = name && getPropertyNameForPropertyNameNode(name); + if (memberName) { + switch (member.kind) { + case SyntaxKind.GetAccessor: + addName(names, name, memberName, DeclarationMeaning.GetAccessor | privateStaticFlags); + break; - case SyntaxKind.SetAccessor: - addName(names, name, memberName, DeclarationMeaning.SetAccessor | privateStaticFlags); - break; + case SyntaxKind.SetAccessor: + addName(names, name, memberName, DeclarationMeaning.SetAccessor | privateStaticFlags); + break; - case SyntaxKind.PropertyDeclaration: - addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor | privateStaticFlags); - break; + case SyntaxKind.PropertyDeclaration: + addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor | privateStaticFlags); + break; - case SyntaxKind.MethodDeclaration: - addName(names, name, memberName, DeclarationMeaning.Method | privateStaticFlags); - break; - } + case SyntaxKind.MethodDeclaration: + addName(names, name, memberName, DeclarationMeaning.Method | privateStaticFlags); + break; } } } + } - function addName(names: UnderscoreEscapedMap, location: Node, name: __String, meaning: DeclarationMeaning) { - const prev = names.get(name); - if (prev) { - // For private identifiers, do not allow mixing of static and instance members with the same name - if ((prev & DeclarationMeaning.PrivateStatic) !== (meaning & DeclarationMeaning.PrivateStatic)) { - error(location, Diagnostics.Duplicate_identifier_0_Static_and_instance_elements_cannot_share_the_same_private_name, getTextOfNode(location)); - } - else { - const prevIsMethod = !!(prev & DeclarationMeaning.Method); - const isMethod = !!(meaning & DeclarationMeaning.Method); - if (prevIsMethod || isMethod) { - if (prevIsMethod !== isMethod) { - error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); - } - // If this is a method/method duplication is might be an overload, so this will be handled when overloads are considered - } - else if (prev & meaning & ~DeclarationMeaning.PrivateStatic) { + function addName(names: UnderscoreEscapedMap, location: Node, name: __String, meaning: DeclarationMeaning) { + const prev = names.get(name); + if (prev) { + // For private identifiers, do not allow mixing of static and instance members with the same name + if ((prev & DeclarationMeaning.PrivateStatic) !== (meaning & DeclarationMeaning.PrivateStatic)) { + error(location, Diagnostics.Duplicate_identifier_0_Static_and_instance_elements_cannot_share_the_same_private_name, getTextOfNode(location)); + } + else { + const prevIsMethod = !!(prev & DeclarationMeaning.Method); + const isMethod = !!(meaning & DeclarationMeaning.Method); + if (prevIsMethod || isMethod) { + if (prevIsMethod !== isMethod) { error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); } - else { - names.set(name, prev | meaning); - } + // If this is a method/method duplication is might be an overload, so this will be handled when overloads are considered + } + else if (prev & meaning & ~DeclarationMeaning.PrivateStatic) { + error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); + } + else { + names.set(name, prev | meaning); } } - else { - names.set(name, meaning); - } + } + else { + names.set(name, meaning); } } + } - /** - * Static members being set on a constructor function may conflict with built-in properties - * of Function. Esp. in ECMAScript 5 there are non-configurable and non-writable - * built-in properties. This check issues a transpile error when a class has a static - * member with the same name as a non-writable built-in property. - * - * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.3 - * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.5 - * @see http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-function-constructor - * @see http://www.ecma-international.org/ecma-262/6.0/#sec-function-instances - */ - function checkClassForStaticPropertyNameConflicts(node: ClassLikeDeclaration) { - for (const member of node.members) { - const memberNameNode = member.name; - const isStaticMember = isStatic(member); - if (isStaticMember && memberNameNode) { - const memberName = getPropertyNameForPropertyNameNode(memberNameNode); - switch (memberName) { - case "name": - case "length": - case "caller": - case "arguments": - case "prototype": - const message = Diagnostics.Static_property_0_conflicts_with_built_in_property_Function_0_of_constructor_function_1; - const className = getNameOfSymbolAsWritten(getSymbolOfNode(node)); - error(memberNameNode, message, memberName, className); - break; - } + /** + * Static members being set on a constructor function may conflict with built-in properties + * of Function. Esp. in ECMAScript 5 there are non-configurable and non-writable + * built-in properties. This check issues a transpile error when a class has a static + * member with the same name as a non-writable built-in property. + * + * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.3 + * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.5 + * @see http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-function-constructor + * @see http://www.ecma-international.org/ecma-262/6.0/#sec-function-instances + */ + function checkClassForStaticPropertyNameConflicts(node: ClassLikeDeclaration) { + for (const member of node.members) { + const memberNameNode = member.name; + const isStaticMember = isStatic(member); + if (isStaticMember && memberNameNode) { + const memberName = getPropertyNameForPropertyNameNode(memberNameNode); + switch (memberName) { + case "name": + case "length": + case "caller": + case "arguments": + case "prototype": + const message = Diagnostics.Static_property_0_conflicts_with_built_in_property_Function_0_of_constructor_function_1; + const className = getNameOfSymbolAsWritten(getSymbolOfNode(node)); + error(memberNameNode, message, memberName, className); + break; } } } + } - function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { - const names = new Map(); - for (const member of node.members) { - if (member.kind === SyntaxKind.PropertySignature) { - let memberName: string; - const name = member.name!; - switch (name.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - memberName = name.text; - break; - case SyntaxKind.Identifier: - memberName = idText(name); - break; - default: - continue; - } + function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { + const names = new Map(); + for (const member of node.members) { + if (member.kind === SyntaxKind.PropertySignature) { + let memberName: string; + const name = member.name!; + switch (name.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + memberName = name.text; + break; + case SyntaxKind.Identifier: + memberName = idText(name); + break; + default: + continue; + } - if (names.get(memberName)) { - error(getNameOfDeclaration(member.symbol.valueDeclaration), Diagnostics.Duplicate_identifier_0, memberName); - error(member.name, Diagnostics.Duplicate_identifier_0, memberName); - } - else { - names.set(memberName, true); - } + if (names.get(memberName)) { + error(getNameOfDeclaration(member.symbol.valueDeclaration), Diagnostics.Duplicate_identifier_0, memberName); + error(member.name, Diagnostics.Duplicate_identifier_0, memberName); + } + else { + names.set(memberName, true); } } } + } - function checkTypeForDuplicateIndexSignatures(node: Node) { - if (node.kind === SyntaxKind.InterfaceDeclaration) { - const nodeSymbol = getSymbolOfNode(node as InterfaceDeclaration); - // in case of merging interface declaration it is possible that we'll enter this check procedure several times for every declaration - // to prevent this run check only for the first declaration of a given kind - if (nodeSymbol.declarations && nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) { - return; - } + function checkTypeForDuplicateIndexSignatures(node: Node) { + if (node.kind === SyntaxKind.InterfaceDeclaration) { + const nodeSymbol = getSymbolOfNode(node as InterfaceDeclaration); + // in case of merging interface declaration it is possible that we'll enter this check procedure several times for every declaration + // to prevent this run check only for the first declaration of a given kind + if (nodeSymbol.declarations && nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) { + return; } + } - // TypeScript 1.0 spec (April 2014) - // 3.7.4: An object type can contain at most one string index signature and one numeric index signature. - // 8.5: A class declaration can have at most one string index member declaration and one numeric index member declaration - const indexSymbol = getIndexSymbol(getSymbolOfNode(node)!); - if (indexSymbol?.declarations) { - const indexSignatureMap = new Map(); - for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) { - if (declaration.parameters.length === 1 && declaration.parameters[0].type) { - forEachType(getTypeFromTypeNode(declaration.parameters[0].type), type => { - const entry = indexSignatureMap.get(getTypeId(type)); - if (entry) { - entry.declarations.push(declaration); - } - else { - indexSignatureMap.set(getTypeId(type), { type, declarations: [declaration] }); - } - }); - } - } - indexSignatureMap.forEach(entry => { - if (entry.declarations.length > 1) { - for (const declaration of entry.declarations) { - error(declaration, Diagnostics.Duplicate_index_signature_for_type_0, typeToString(entry.type)); + // TypeScript 1.0 spec (April 2014) + // 3.7.4: An object type can contain at most one string index signature and one numeric index signature. + // 8.5: A class declaration can have at most one string index member declaration and one numeric index member declaration + const indexSymbol = getIndexSymbol(getSymbolOfNode(node)!); + if (indexSymbol?.declarations) { + const indexSignatureMap = new Map(); + for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) { + if (declaration.parameters.length === 1 && declaration.parameters[0].type) { + forEachType(getTypeFromTypeNode(declaration.parameters[0].type), type => { + const entry = indexSignatureMap.get(getTypeId(type)); + if (entry) { + entry.declarations.push(declaration); } - } - }); + else { + indexSignatureMap.set(getTypeId(type), { type, declarations: [declaration] }); + } + }); + } } + indexSignatureMap.forEach(entry => { + if (entry.declarations.length > 1) { + for (const declaration of entry.declarations) { + error(declaration, Diagnostics.Duplicate_index_signature_for_type_0, typeToString(entry.type)); + } + } + }); } + } - function checkPropertyDeclaration(node: PropertyDeclaration | PropertySignature) { - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); - checkVariableLikeDeclaration(node); + function checkPropertyDeclaration(node: PropertyDeclaration | PropertySignature) { + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); + checkVariableLikeDeclaration(node); - setNodeLinksForPrivateIdentifierScope(node); + setNodeLinksForPrivateIdentifierScope(node); - // property signatures already report "initializer not allowed in ambient context" elsewhere - if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.PropertyDeclaration && node.initializer) { - error(node, Diagnostics.Property_0_cannot_have_an_initializer_because_it_is_marked_abstract, declarationNameToString(node.name)); - } + // property signatures already report "initializer not allowed in ambient context" elsewhere + if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.PropertyDeclaration && node.initializer) { + error(node, Diagnostics.Property_0_cannot_have_an_initializer_because_it_is_marked_abstract, declarationNameToString(node.name)); } + } - function checkPropertySignature(node: PropertySignature) { - if (isPrivateIdentifier(node.name)) { - error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); - } - return checkPropertyDeclaration(node); + function checkPropertySignature(node: PropertySignature) { + if (isPrivateIdentifier(node.name)) { + error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); } + return checkPropertyDeclaration(node); + } - function checkMethodDeclaration(node: MethodDeclaration | MethodSignature) { - // Grammar checking - if (!checkGrammarMethod(node)) checkGrammarComputedPropertyName(node.name); - - if (isMethodDeclaration(node) && node.asteriskToken && isIdentifier(node.name) && idText(node.name) === "constructor") { - error(node.name, Diagnostics.Class_constructor_may_not_be_a_generator); - } + function checkMethodDeclaration(node: MethodDeclaration | MethodSignature) { + // Grammar checking + if (!checkGrammarMethod(node)) checkGrammarComputedPropertyName(node.name); - // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration - checkFunctionOrMethodDeclaration(node); + if (isMethodDeclaration(node) && node.asteriskToken && isIdentifier(node.name) && idText(node.name) === "constructor") { + error(node.name, Diagnostics.Class_constructor_may_not_be_a_generator); + } - // method signatures already report "implementation not allowed in ambient context" elsewhere - if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.MethodDeclaration && node.body) { - error(node, Diagnostics.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract, declarationNameToString(node.name)); - } + // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration + checkFunctionOrMethodDeclaration(node); - // Private named methods are only allowed in class declarations - if (isPrivateIdentifier(node.name) && !getContainingClass(node)) { - error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); - } + // method signatures already report "implementation not allowed in ambient context" elsewhere + if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.MethodDeclaration && node.body) { + error(node, Diagnostics.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract, declarationNameToString(node.name)); + } - setNodeLinksForPrivateIdentifierScope(node); + // Private named methods are only allowed in class declarations + if (isPrivateIdentifier(node.name) && !getContainingClass(node)) { + error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); } - function setNodeLinksForPrivateIdentifierScope(node: PropertyDeclaration | PropertySignature | MethodDeclaration | MethodSignature | AccessorDeclaration) { - if (isPrivateIdentifier(node.name) && languageVersion < ScriptTarget.ESNext) { - for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) { - getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers; - } + setNodeLinksForPrivateIdentifierScope(node); + } - // If this is a private element in a class expression inside the body of a loop, - // then we must use a block-scoped binding to store the additional variables required - // to transform private elements. - if (isClassExpression(node.parent)) { - const enclosingIterationStatement = getEnclosingIterationStatement(node.parent); - if (enclosingIterationStatement) { - getNodeLinks(node.name).flags |= NodeCheckFlags.BlockScopedBindingInLoop; - getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; - } - } + function setNodeLinksForPrivateIdentifierScope(node: PropertyDeclaration | PropertySignature | MethodDeclaration | MethodSignature | AccessorDeclaration) { + if (isPrivateIdentifier(node.name) && languageVersion < ScriptTarget.ESNext) { + for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) { + getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers; } - } - function checkClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { - checkGrammarDecoratorsAndModifiers(node); - - forEachChild(node, checkSourceElement); + // If this is a private element in a class expression inside the body of a loop, + // then we must use a block-scoped binding to store the additional variables required + // to transform private elements. + if (isClassExpression(node.parent)) { + const enclosingIterationStatement = getEnclosingIterationStatement(node.parent); + if (enclosingIterationStatement) { + getNodeLinks(node.name).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + } + } } + } - function checkConstructorDeclaration(node: ConstructorDeclaration) { - // Grammar check on signature of constructor and modifier of the constructor is done in checkSignatureDeclaration function. - checkSignatureDeclaration(node); - // Grammar check for checking only related to constructorDeclaration - if (!checkGrammarConstructorTypeParameters(node)) checkGrammarConstructorTypeAnnotation(node); + function checkClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + checkGrammarDecoratorsAndModifiers(node); - checkSourceElement(node.body); + forEachChild(node, checkSourceElement); + } - const symbol = getSymbolOfNode(node); - const firstDeclaration = getDeclarationOfKind(symbol, node.kind); + function checkConstructorDeclaration(node: ConstructorDeclaration) { + // Grammar check on signature of constructor and modifier of the constructor is done in checkSignatureDeclaration function. + checkSignatureDeclaration(node); + // Grammar check for checking only related to constructorDeclaration + if (!checkGrammarConstructorTypeParameters(node)) checkGrammarConstructorTypeAnnotation(node); - // Only type check the symbol once - if (node === firstDeclaration) { - checkFunctionOrConstructorSymbol(symbol); - } + checkSourceElement(node.body); - // exit early in the case of signature - super checks are not relevant to them - if (nodeIsMissing(node.body)) { - return; - } + const symbol = getSymbolOfNode(node); + const firstDeclaration = getDeclarationOfKind(symbol, node.kind); - addLazyDiagnostic(checkConstructorDeclarationDiagnostics); + // Only type check the symbol once + if (node === firstDeclaration) { + checkFunctionOrConstructorSymbol(symbol); + } + // exit early in the case of signature - super checks are not relevant to them + if (nodeIsMissing(node.body)) { return; + } - function isInstancePropertyWithInitializerOrPrivateIdentifierProperty(n: Node): boolean { - if (isPrivateIdentifierClassElementDeclaration(n)) { - return true; - } - return n.kind === SyntaxKind.PropertyDeclaration && - !isStatic(n) && - !!(n as PropertyDeclaration).initializer; - } + addLazyDiagnostic(checkConstructorDeclarationDiagnostics); - function checkConstructorDeclarationDiagnostics() { - // TS 1.0 spec (April 2014): 8.3.2 - // Constructors of classes with no extends clause may not contain super calls, whereas - // constructors of derived classes must contain at least one super call somewhere in their function body. - const containingClassDecl = node.parent as ClassDeclaration; - if (getClassExtendsHeritageElement(containingClassDecl)) { - captureLexicalThis(node.parent, containingClassDecl); - const classExtendsNull = classDeclarationExtendsNull(containingClassDecl); - const superCall = findFirstSuperCall(node.body!); - if (superCall) { - if (classExtendsNull) { - error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); - } + return; - // A super call must be root-level in a constructor if both of the following are true: - // - The containing class is a derived class. - // - The constructor declares parameter properties - // or the containing class declares instance member variables with initializers. + function isInstancePropertyWithInitializerOrPrivateIdentifierProperty(n: Node): boolean { + if (isPrivateIdentifierClassElementDeclaration(n)) { + return true; + } + return n.kind === SyntaxKind.PropertyDeclaration && + !isStatic(n) && + !!(n as PropertyDeclaration).initializer; + } + + function checkConstructorDeclarationDiagnostics() { + // TS 1.0 spec (April 2014): 8.3.2 + // Constructors of classes with no extends clause may not contain super calls, whereas + // constructors of derived classes must contain at least one super call somewhere in their function body. + const containingClassDecl = node.parent as ClassDeclaration; + if (getClassExtendsHeritageElement(containingClassDecl)) { + captureLexicalThis(node.parent, containingClassDecl); + const classExtendsNull = classDeclarationExtendsNull(containingClassDecl); + const superCall = findFirstSuperCall(node.body!); + if (superCall) { + if (classExtendsNull) { + error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); + } + + // A super call must be root-level in a constructor if both of the following are true: + // - The containing class is a derived class. + // - The constructor declares parameter properties + // or the containing class declares instance member variables with initializers. + + const superCallShouldBeRootLevel = + (getEmitScriptTarget(compilerOptions) !== ScriptTarget.ESNext || !useDefineForClassFields) && + (some((node.parent as ClassDeclaration).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) || + some(node.parameters, p => hasSyntacticModifier(p, ModifierFlags.ParameterPropertyModifier))); + + if (superCallShouldBeRootLevel) { + // Until we have better flow analysis, it is an error to place the super call within any kind of block or conditional + // See GH #8277 + if (!superCallIsRootLevelInConstructor(superCall, node.body!)) { + error(superCall, Diagnostics.A_super_call_must_be_a_root_level_statement_within_a_constructor_of_a_derived_class_that_contains_initialized_properties_parameter_properties_or_private_identifiers); + } + // Skip past any prologue directives to check statements for referring to 'super' or 'this' before a super call + else { + let superCallStatement: ExpressionStatement | undefined; - const superCallShouldBeRootLevel = - (getEmitScriptTarget(compilerOptions) !== ScriptTarget.ESNext || !useDefineForClassFields) && - (some((node.parent as ClassDeclaration).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) || - some(node.parameters, p => hasSyntacticModifier(p, ModifierFlags.ParameterPropertyModifier))); + for (const statement of node.body!.statements) { + if (isExpressionStatement(statement) && isSuperCall(skipOuterExpressions(statement.expression))) { + superCallStatement = statement; + break; + } + if (nodeImmediatelyReferencesSuperOrThis(statement)) { + break; + } + } - if (superCallShouldBeRootLevel) { // Until we have better flow analysis, it is an error to place the super call within any kind of block or conditional // See GH #8277 - if (!superCallIsRootLevelInConstructor(superCall, node.body!)) { - error(superCall, Diagnostics.A_super_call_must_be_a_root_level_statement_within_a_constructor_of_a_derived_class_that_contains_initialized_properties_parameter_properties_or_private_identifiers); - } - // Skip past any prologue directives to check statements for referring to 'super' or 'this' before a super call - else { - let superCallStatement: ExpressionStatement | undefined; - - for (const statement of node.body!.statements) { - if (isExpressionStatement(statement) && isSuperCall(skipOuterExpressions(statement.expression))) { - superCallStatement = statement; - break; - } - if (nodeImmediatelyReferencesSuperOrThis(statement)) { - break; - } - } - - // Until we have better flow analysis, it is an error to place the super call within any kind of block or conditional - // See GH #8277 - if (superCallStatement === undefined) { - error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_to_refer_to_super_or_this_when_a_derived_class_contains_initialized_properties_parameter_properties_or_private_identifiers); - } + if (superCallStatement === undefined) { + error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_to_refer_to_super_or_this_when_a_derived_class_contains_initialized_properties_parameter_properties_or_private_identifiers); } } } - else if (!classExtendsNull) { - error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); - } + } + else if (!classExtendsNull) { + error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); } } } + } + + function superCallIsRootLevelInConstructor(superCall: Node, body: Block) { + const superCallParent = walkUpParenthesizedExpressions(superCall.parent); + return isExpressionStatement(superCallParent) && superCallParent.parent === body; + } - function superCallIsRootLevelInConstructor(superCall: Node, body: Block) { - const superCallParent = walkUpParenthesizedExpressions(superCall.parent); - return isExpressionStatement(superCallParent) && superCallParent.parent === body; + function nodeImmediatelyReferencesSuperOrThis(node: Node): boolean { + if (node.kind === SyntaxKind.SuperKeyword || node.kind === SyntaxKind.ThisKeyword) { + return true; } - function nodeImmediatelyReferencesSuperOrThis(node: Node): boolean { - if (node.kind === SyntaxKind.SuperKeyword || node.kind === SyntaxKind.ThisKeyword) { - return true; - } + if (isThisContainerOrFunctionBlock(node)) { + return false; + } - if (isThisContainerOrFunctionBlock(node)) { - return false; - } + return !!forEachChild(node, nodeImmediatelyReferencesSuperOrThis); + } - return !!forEachChild(node, nodeImmediatelyReferencesSuperOrThis); + function checkAccessorDeclaration(node: AccessorDeclaration) { + if (isIdentifier(node.name) && idText(node.name) === "constructor") { + error(node.name, Diagnostics.Class_constructor_may_not_be_an_accessor); } + addLazyDiagnostic(checkAccessorDeclarationDiagnostics); + checkSourceElement(node.body); + setNodeLinksForPrivateIdentifierScope(node); - function checkAccessorDeclaration(node: AccessorDeclaration) { - if (isIdentifier(node.name) && idText(node.name) === "constructor") { - error(node.name, Diagnostics.Class_constructor_may_not_be_an_accessor); - } - addLazyDiagnostic(checkAccessorDeclarationDiagnostics); - checkSourceElement(node.body); - setNodeLinksForPrivateIdentifierScope(node); + function checkAccessorDeclarationDiagnostics() { + // Grammar checking accessors + if (!checkGrammarFunctionLikeDeclaration(node) && !checkGrammarAccessor(node)) checkGrammarComputedPropertyName(node.name); - function checkAccessorDeclarationDiagnostics() { - // Grammar checking accessors - if (!checkGrammarFunctionLikeDeclaration(node) && !checkGrammarAccessor(node)) checkGrammarComputedPropertyName(node.name); - - checkDecorators(node); - checkSignatureDeclaration(node); - if (node.kind === SyntaxKind.GetAccessor) { - if (!(node.flags & NodeFlags.Ambient) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) { - if (!(node.flags & NodeFlags.HasExplicitReturn)) { - error(node.name, Diagnostics.A_get_accessor_must_return_a_value); - } + checkDecorators(node); + checkSignatureDeclaration(node); + if (node.kind === SyntaxKind.GetAccessor) { + if (!(node.flags & NodeFlags.Ambient) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) { + if (!(node.flags & NodeFlags.HasExplicitReturn)) { + error(node.name, Diagnostics.A_get_accessor_must_return_a_value); } } - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); - } + } + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + } - if (hasBindableName(node)) { - // TypeScript 1.0 spec (April 2014): 8.4.3 - // Accessors for the same member name must specify the same accessibility. - const symbol = getSymbolOfNode(node); - const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); - const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); - if (getter && setter && !(getNodeCheckFlags(getter) & NodeCheckFlags.TypeChecked)) { - getNodeLinks(getter).flags |= NodeCheckFlags.TypeChecked; - const getterFlags = getEffectiveModifierFlags(getter); - const setterFlags = getEffectiveModifierFlags(setter); - if ((getterFlags & ModifierFlags.Abstract) !== (setterFlags & ModifierFlags.Abstract)) { - error(getter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); - error(setter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); - } - if (((getterFlags & ModifierFlags.Protected) && !(setterFlags & (ModifierFlags.Protected | ModifierFlags.Private))) || - ((getterFlags & ModifierFlags.Private) && !(setterFlags & ModifierFlags.Private))) { - error(getter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); - error(setter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); - } + if (hasBindableName(node)) { + // TypeScript 1.0 spec (April 2014): 8.4.3 + // Accessors for the same member name must specify the same accessibility. + const symbol = getSymbolOfNode(node); + const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); + const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); + if (getter && setter && !(getNodeCheckFlags(getter) & NodeCheckFlags.TypeChecked)) { + getNodeLinks(getter).flags |= NodeCheckFlags.TypeChecked; + const getterFlags = getEffectiveModifierFlags(getter); + const setterFlags = getEffectiveModifierFlags(setter); + if ((getterFlags & ModifierFlags.Abstract) !== (setterFlags & ModifierFlags.Abstract)) { + error(getter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); + error(setter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); + } + if (((getterFlags & ModifierFlags.Protected) && !(setterFlags & (ModifierFlags.Protected | ModifierFlags.Private))) || + ((getterFlags & ModifierFlags.Private) && !(setterFlags & ModifierFlags.Private))) { + error(getter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); + error(setter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); + } - const getterType = getAnnotatedAccessorType(getter); - const setterType = getAnnotatedAccessorType(setter); - if (getterType && setterType) { - checkTypeAssignableTo(getterType, setterType, getter, Diagnostics.The_return_type_of_a_get_accessor_must_be_assignable_to_its_set_accessor_type); - } + const getterType = getAnnotatedAccessorType(getter); + const setterType = getAnnotatedAccessorType(setter); + if (getterType && setterType) { + checkTypeAssignableTo(getterType, setterType, getter, Diagnostics.The_return_type_of_a_get_accessor_must_be_assignable_to_its_set_accessor_type); } } - const returnType = getTypeOfAccessors(getSymbolOfNode(node)); - if (node.kind === SyntaxKind.GetAccessor) { - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); - } + } + const returnType = getTypeOfAccessors(getSymbolOfNode(node)); + if (node.kind === SyntaxKind.GetAccessor) { + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); } } + } - function checkMissingDeclaration(node: Node) { - checkDecorators(node); - } + function checkMissingDeclaration(node: Node) { + checkDecorators(node); + } - function getEffectiveTypeArgumentAtIndex(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[], index: number): Type { - if (node.typeArguments && index < node.typeArguments.length) { - return getTypeFromTypeNode(node.typeArguments[index]); - } - return getEffectiveTypeArguments(node, typeParameters)[index]; + function getEffectiveTypeArgumentAtIndex(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[], index: number): Type { + if (node.typeArguments && index < node.typeArguments.length) { + return getTypeFromTypeNode(node.typeArguments[index]); } + return getEffectiveTypeArguments(node, typeParameters)[index]; + } - function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { - return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, - getMinTypeArgumentCount(typeParameters), isInJSFile(node)); - } + function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { + return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, + getMinTypeArgumentCount(typeParameters), isInJSFile(node)); + } - function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean { - let typeArguments: Type[] | undefined; - let mapper: TypeMapper | undefined; - let result = true; - for (let i = 0; i < typeParameters.length; i++) { - const constraint = getConstraintOfTypeParameter(typeParameters[i]); - if (constraint) { - if (!typeArguments) { - typeArguments = getEffectiveTypeArguments(node, typeParameters); - mapper = createTypeMapper(typeParameters, typeArguments); - } - result = result && checkTypeAssignableTo( - typeArguments[i], - instantiateType(constraint, mapper), - node.typeArguments![i], - Diagnostics.Type_0_does_not_satisfy_the_constraint_1); - } - } - return result; - } + function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean { + let typeArguments: Type[] | undefined; + let mapper: TypeMapper | undefined; + let result = true; + for (let i = 0; i < typeParameters.length; i++) { + const constraint = getConstraintOfTypeParameter(typeParameters[i]); + if (constraint) { + if (!typeArguments) { + typeArguments = getEffectiveTypeArguments(node, typeParameters); + mapper = createTypeMapper(typeParameters, typeArguments); + } + result = result && checkTypeAssignableTo( + typeArguments[i], + instantiateType(constraint, mapper), + node.typeArguments![i], + Diagnostics.Type_0_does_not_satisfy_the_constraint_1); + } + } + return result; + } - function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) { - const type = getTypeFromTypeReference(node); - if (!isErrorType(type)) { - const symbol = getNodeLinks(node).resolvedSymbol; - if (symbol) { - return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || - (getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined); - } + function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) { + const type = getTypeFromTypeReference(node); + if (!isErrorType(type)) { + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol) { + return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || + (getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined); } - return undefined; } + return undefined; + } - function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { - checkGrammarTypeArguments(node, node.typeArguments); - if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJSFile(node) && !isInJSDoc(node)) { - grammarErrorAtPos(node, node.typeName.jsdocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); - } - forEach(node.typeArguments, checkSourceElement); - const type = getTypeFromTypeReference(node); - if (!isErrorType(type)) { - if (node.typeArguments) { - addLazyDiagnostic(() => { - const typeParameters = getTypeParametersForTypeReference(node); - if (typeParameters) { - checkTypeArgumentConstraints(node, typeParameters); - } - }); - } - const symbol = getNodeLinks(node).resolvedSymbol; - if (symbol) { - if (some(symbol.declarations, d => isTypeDeclaration(d) && !!(d.flags & NodeFlags.Deprecated))) { - addDeprecatedSuggestion( - getDeprecatedSuggestionNode(node), - symbol.declarations!, - symbol.escapedName as string - ); + function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { + checkGrammarTypeArguments(node, node.typeArguments); + if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJSFile(node) && !isInJSDoc(node)) { + grammarErrorAtPos(node, node.typeName.jsdocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } + forEach(node.typeArguments, checkSourceElement); + const type = getTypeFromTypeReference(node); + if (!isErrorType(type)) { + if (node.typeArguments) { + addLazyDiagnostic(() => { + const typeParameters = getTypeParametersForTypeReference(node); + if (typeParameters) { + checkTypeArgumentConstraints(node, typeParameters); } + }); + } + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol) { + if (some(symbol.declarations, d => isTypeDeclaration(d) && !!(d.flags & NodeFlags.Deprecated))) { + addDeprecatedSuggestion( + getDeprecatedSuggestionNode(node), + symbol.declarations!, + symbol.escapedName as string + ); } } } + } - function getTypeArgumentConstraint(node: TypeNode): Type | undefined { - const typeReferenceNode = tryCast(node.parent, isTypeReferenceType); - if (!typeReferenceNode) return undefined; - const typeParameters = getTypeParametersForTypeReference(typeReferenceNode); - if (!typeParameters) return undefined; - const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]); - return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters))); - } + function getTypeArgumentConstraint(node: TypeNode): Type | undefined { + const typeReferenceNode = tryCast(node.parent, isTypeReferenceType); + if (!typeReferenceNode) return undefined; + const typeParameters = getTypeParametersForTypeReference(typeReferenceNode); + if (!typeParameters) return undefined; + const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]); + return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters))); + } - function checkTypeQuery(node: TypeQueryNode) { - getTypeFromTypeQueryNode(node); - } + function checkTypeQuery(node: TypeQueryNode) { + getTypeFromTypeQueryNode(node); + } - function checkTypeLiteral(node: TypeLiteralNode) { - forEach(node.members, checkSourceElement); - addLazyDiagnostic(checkTypeLiteralDiagnostics); + function checkTypeLiteral(node: TypeLiteralNode) { + forEach(node.members, checkSourceElement); + addLazyDiagnostic(checkTypeLiteralDiagnostics); - function checkTypeLiteralDiagnostics() { - const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); - checkIndexConstraints(type, type.symbol); - checkTypeForDuplicateIndexSignatures(node); - checkObjectTypeForDuplicateDeclarations(node); - } + function checkTypeLiteralDiagnostics() { + const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); + checkIndexConstraints(type, type.symbol); + checkTypeForDuplicateIndexSignatures(node); + checkObjectTypeForDuplicateDeclarations(node); } + } - function checkArrayType(node: ArrayTypeNode) { - checkSourceElement(node.elementType); - } + function checkArrayType(node: ArrayTypeNode) { + checkSourceElement(node.elementType); + } - function checkTupleType(node: TupleTypeNode) { - const elementTypes = node.elements; - let seenOptionalElement = false; - let seenRestElement = false; - const hasNamedElement = some(elementTypes, isNamedTupleMember); - for (const e of elementTypes) { - if (e.kind !== SyntaxKind.NamedTupleMember && hasNamedElement) { - grammarErrorOnNode(e, Diagnostics.Tuple_members_must_all_have_names_or_all_not_have_names); + function checkTupleType(node: TupleTypeNode) { + const elementTypes = node.elements; + let seenOptionalElement = false; + let seenRestElement = false; + const hasNamedElement = some(elementTypes, isNamedTupleMember); + for (const e of elementTypes) { + if (e.kind !== SyntaxKind.NamedTupleMember && hasNamedElement) { + grammarErrorOnNode(e, Diagnostics.Tuple_members_must_all_have_names_or_all_not_have_names); + break; + } + const flags = getTupleElementFlags(e); + if (flags & ElementFlags.Variadic) { + const type = getTypeFromTypeNode((e as RestTypeNode | NamedTupleMember).type); + if (!isArrayLikeType(type)) { + error(e, Diagnostics.A_rest_element_type_must_be_an_array_type); break; } - const flags = getTupleElementFlags(e); - if (flags & ElementFlags.Variadic) { - const type = getTypeFromTypeNode((e as RestTypeNode | NamedTupleMember).type); - if (!isArrayLikeType(type)) { - error(e, Diagnostics.A_rest_element_type_must_be_an_array_type); - break; - } - if (isArrayType(type) || isTupleType(type) && type.target.combinedFlags & ElementFlags.Rest) { - seenRestElement = true; - } - } - else if (flags & ElementFlags.Rest) { - if (seenRestElement) { - grammarErrorOnNode(e, Diagnostics.A_rest_element_cannot_follow_another_rest_element); - break; - } + if (isArrayType(type) || isTupleType(type) && type.target.combinedFlags & ElementFlags.Rest) { seenRestElement = true; } - else if (flags & ElementFlags.Optional) { - if (seenRestElement) { - grammarErrorOnNode(e, Diagnostics.An_optional_element_cannot_follow_a_rest_element); - break; - } - seenOptionalElement = true; - } - else if (seenOptionalElement) { - grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); + } + else if (flags & ElementFlags.Rest) { + if (seenRestElement) { + grammarErrorOnNode(e, Diagnostics.A_rest_element_cannot_follow_another_rest_element); break; } + seenRestElement = true; } - forEach(node.elements, checkSourceElement); - getTypeFromTypeNode(node); - } - - function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) { - forEach(node.types, checkSourceElement); - getTypeFromTypeNode(node); - } - - function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) { - if (!(type.flags & TypeFlags.IndexedAccess)) { - return type; - } - // Check if the index type is assignable to 'keyof T' for the object type. - const objectType = (type as IndexedAccessType).objectType; - const indexType = (type as IndexedAccessType).indexType; - if (isTypeAssignableTo(indexType, getIndexType(objectType, /*stringsOnly*/ false))) { - if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && - getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.IncludeReadonly) { - error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); + else if (flags & ElementFlags.Optional) { + if (seenRestElement) { + grammarErrorOnNode(e, Diagnostics.An_optional_element_cannot_follow_a_rest_element); + break; } - return type; - } - // 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. - const apparentObjectType = getApparentType(objectType); - if (getIndexInfoOfType(apparentObjectType, numberType) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { - return type; + seenOptionalElement = true; } - 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; - } - } + else if (seenOptionalElement) { + grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); + break; } - error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); - return errorType; - } - - function checkIndexedAccessType(node: IndexedAccessTypeNode) { - checkSourceElement(node.objectType); - checkSourceElement(node.indexType); - checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); } + forEach(node.elements, checkSourceElement); + getTypeFromTypeNode(node); + } - function checkMappedType(node: MappedTypeNode) { - checkGrammarMappedType(node); - checkSourceElement(node.typeParameter); - checkSourceElement(node.nameType); - checkSourceElement(node.type); - - if (!node.type) { - reportImplicitAny(node, anyType); - } + function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) { + forEach(node.types, checkSourceElement); + getTypeFromTypeNode(node); + } - const type = getTypeFromMappedTypeNode(node) as MappedType; - const nameType = getNameTypeFromMappedType(type); - if (nameType) { - checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType); - } - else { - const constraintType = getConstraintTypeFromMappedType(type); - checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); - } + function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) { + if (!(type.flags & TypeFlags.IndexedAccess)) { + return type; } - - function checkGrammarMappedType(node: MappedTypeNode) { - if (node.members?.length) { - return grammarErrorOnNode(node.members[0], Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); + // Check if the index type is assignable to 'keyof T' for the object type. + const objectType = (type as IndexedAccessType).objectType; + const indexType = (type as IndexedAccessType).indexType; + if (isTypeAssignableTo(indexType, getIndexType(objectType, /*stringsOnly*/ false))) { + if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && + getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.IncludeReadonly) { + error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); } + return type; } - - function checkThisType(node: ThisTypeNode) { - getTypeFromThisTypeNode(node); - } - - function checkTypeOperator(node: TypeOperatorNode) { - checkGrammarTypeOperatorNode(node); - checkSourceElement(node.type); - } - - function checkConditionalType(node: ConditionalTypeNode) { - forEachChild(node, checkSourceElement); + // 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. + const apparentObjectType = getApparentType(objectType); + if (getIndexInfoOfType(apparentObjectType, numberType) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { + return type; } - - function checkInferType(node: InferTypeNode) { - if (!findAncestor(node, n => n.parent && n.parent.kind === SyntaxKind.ConditionalType && (n.parent as ConditionalTypeNode).extendsType === n)) { - grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type); - } - checkSourceElement(node.typeParameter); - const symbol = getSymbolOfNode(node.typeParameter); - if (symbol.declarations && symbol.declarations.length > 1) { - const links = getSymbolLinks(symbol); - if (!links.typeParametersChecked) { - links.typeParametersChecked = true; - const typeParameter = getDeclaredTypeOfTypeParameter(symbol); - const declarations: TypeParameterDeclaration[] = getDeclarationsOfKind(symbol, SyntaxKind.TypeParameter); - if (!areTypeParametersIdentical(declarations, [typeParameter], decl => [decl])) { - // Report an error on every conflicting declaration. - const name = symbolToString(symbol); - for (const declaration of declarations) { - error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_constraints, name); - } - } + 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; } } - registerForUnusedIdentifiersCheck(node); - } - - function checkTemplateLiteralType(node: TemplateLiteralTypeNode) { - for (const span of node.templateSpans) { - checkSourceElement(span.type); - const type = getTypeFromTypeNode(span.type); - checkTypeAssignableTo(type, templateConstraintType, span.type); - } - getTypeFromTypeNode(node); } + error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); + return errorType; + } - function checkImportType(node: ImportTypeNode) { - checkSourceElement(node.argument); + function checkIndexedAccessType(node: IndexedAccessTypeNode) { + checkSourceElement(node.objectType); + checkSourceElement(node.indexType); + checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); + } - if (node.assertions) { - const override = getResolutionModeOverrideForClause(node.assertions.assertClause, grammarErrorOnNode); - if (override) { - if (!isNightly()) { - grammarErrorOnNode(node.assertions.assertClause, Diagnostics.resolution_mode_assertions_are_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next); - } - if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { - grammarErrorOnNode(node.assertions.assertClause, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext); - } - } - } + function checkMappedType(node: MappedTypeNode) { + checkGrammarMappedType(node); + checkSourceElement(node.typeParameter); + checkSourceElement(node.nameType); + checkSourceElement(node.type); - getTypeFromTypeNode(node); + if (!node.type) { + reportImplicitAny(node, anyType); } - function checkNamedTupleMember(node: NamedTupleMember) { - if (node.dotDotDotToken && node.questionToken) { - grammarErrorOnNode(node, Diagnostics.A_tuple_member_cannot_be_both_optional_and_rest); - } - if (node.type.kind === SyntaxKind.OptionalType) { - grammarErrorOnNode(node.type, Diagnostics.A_labeled_tuple_element_is_declared_as_optional_with_a_question_mark_after_the_name_and_before_the_colon_rather_than_after_the_type); - } - if (node.type.kind === SyntaxKind.RestType) { - grammarErrorOnNode(node.type, Diagnostics.A_labeled_tuple_element_is_declared_as_rest_with_a_before_the_name_rather_than_before_the_type); - } - checkSourceElement(node.type); - getTypeFromTypeNode(node); + const type = getTypeFromMappedTypeNode(node) as MappedType; + const nameType = getNameTypeFromMappedType(type); + if (nameType) { + checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType); } - - function isPrivateWithinAmbient(node: Node): boolean { - return (hasEffectiveModifier(node, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(node)) && !!(node.flags & NodeFlags.Ambient); + else { + const constraintType = getConstraintTypeFromMappedType(type); + checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); } + } - function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { - let flags = getCombinedModifierFlags(n); - - // children of classes (even ambient classes) should not be marked as ambient or export - // because those flags have no useful semantics there. - if (n.parent.kind !== SyntaxKind.InterfaceDeclaration && - n.parent.kind !== SyntaxKind.ClassDeclaration && - n.parent.kind !== SyntaxKind.ClassExpression && - n.flags & NodeFlags.Ambient) { - if (!(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { - // It is nested in an ambient context, which means it is automatically exported - flags |= ModifierFlags.Export; - } - flags |= ModifierFlags.Ambient; - } - - return flags & flagsToCheck; + function checkGrammarMappedType(node: MappedTypeNode) { + if (node.members?.length) { + return grammarErrorOnNode(node.members[0], Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); } + } - function checkFunctionOrConstructorSymbol(symbol: Symbol): void { - addLazyDiagnostic(() => checkFunctionOrConstructorSymbolWorker(symbol)); - } + function checkThisType(node: ThisTypeNode) { + getTypeFromThisTypeNode(node); + } - function checkFunctionOrConstructorSymbolWorker(symbol: Symbol): void { - function getCanonicalOverload(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined): Declaration { - // Consider the canonical set of flags to be the flags of the bodyDeclaration or the first declaration - // Error on all deviations from this canonical set of flags - // The caveat is that if some overloads are defined in lib.d.ts, we don't want to - // report the errors on those. To achieve this, we will say that the implementation is - // the canonical signature only if it is in the same container as the first overload - const implementationSharesContainerWithFirstOverload = implementation !== undefined && implementation.parent === overloads[0].parent; - return implementationSharesContainerWithFirstOverload ? implementation : overloads[0]; - } + function checkTypeOperator(node: TypeOperatorNode) { + checkGrammarTypeOperatorNode(node); + checkSourceElement(node.type); + } - function checkFlagAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, flagsToCheck: ModifierFlags, someOverloadFlags: ModifierFlags, allOverloadFlags: ModifierFlags): void { - // Error if some overloads have a flag that is not shared by all overloads. To find the - // deviations, we XOR someOverloadFlags with allOverloadFlags - const someButNotAllOverloadFlags = someOverloadFlags ^ allOverloadFlags; - if (someButNotAllOverloadFlags !== 0) { - const canonicalFlags = getEffectiveDeclarationFlags(getCanonicalOverload(overloads, implementation), flagsToCheck); - forEach(overloads, o => { - const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags; - if (deviation & ModifierFlags.Export) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_exported_or_non_exported); - } - else if (deviation & ModifierFlags.Ambient) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient); - } - else if (deviation & (ModifierFlags.Private | ModifierFlags.Protected)) { - error(getNameOfDeclaration(o) || o, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected); - } - else if (deviation & ModifierFlags.Abstract) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_abstract_or_non_abstract); - } - }); - } - } + function checkConditionalType(node: ConditionalTypeNode) { + forEachChild(node, checkSourceElement); + } - function checkQuestionTokenAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, someHaveQuestionToken: boolean, allHaveQuestionToken: boolean): void { - if (someHaveQuestionToken !== allHaveQuestionToken) { - const canonicalHasQuestionToken = hasQuestionToken(getCanonicalOverload(overloads, implementation)); - forEach(overloads, o => { - const deviation = hasQuestionToken(o) !== canonicalHasQuestionToken; - if (deviation) { - error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_optional_or_required); - } - }); + function checkInferType(node: InferTypeNode) { + if (!findAncestor(node, n => n.parent && n.parent.kind === SyntaxKind.ConditionalType && (n.parent as ConditionalTypeNode).extendsType === n)) { + grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type); + } + checkSourceElement(node.typeParameter); + const symbol = getSymbolOfNode(node.typeParameter); + if (symbol.declarations && symbol.declarations.length > 1) { + const links = getSymbolLinks(symbol); + if (!links.typeParametersChecked) { + links.typeParametersChecked = true; + const typeParameter = getDeclaredTypeOfTypeParameter(symbol); + const declarations: TypeParameterDeclaration[] = getDeclarationsOfKind(symbol, SyntaxKind.TypeParameter); + if (!areTypeParametersIdentical(declarations, [typeParameter], decl => [decl])) { + // Report an error on every conflicting declaration. + const name = symbolToString(symbol); + for (const declaration of declarations) { + error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_constraints, name); + } } } + } + registerForUnusedIdentifiersCheck(node); + } - const flagsToCheck: ModifierFlags = ModifierFlags.Export | ModifierFlags.Ambient | ModifierFlags.Private | ModifierFlags.Protected | ModifierFlags.Abstract; - let someNodeFlags: ModifierFlags = ModifierFlags.None; - let allNodeFlags = flagsToCheck; - let someHaveQuestionToken = false; - let allHaveQuestionToken = true; - let hasOverloads = false; - let bodyDeclaration: FunctionLikeDeclaration | undefined; - let lastSeenNonAmbientDeclaration: FunctionLikeDeclaration | undefined; - let previousDeclaration: SignatureDeclaration | undefined; - - const declarations = symbol.declarations; - const isConstructor = (symbol.flags & SymbolFlags.Constructor) !== 0; + function checkTemplateLiteralType(node: TemplateLiteralTypeNode) { + for (const span of node.templateSpans) { + checkSourceElement(span.type); + const type = getTypeFromTypeNode(span.type); + checkTypeAssignableTo(type, templateConstraintType, span.type); + } + getTypeFromTypeNode(node); + } - function reportImplementationExpectedError(node: SignatureDeclaration): void { - if (node.name && nodeIsMissing(node.name)) { - return; - } + function checkImportType(node: ImportTypeNode) { + checkSourceElement(node.argument); - let seen = false; - const subsequentNode = forEachChild(node.parent, c => { - if (seen) { - return c; - } - else { - seen = c === node; - } - }); - // We may be here because of some extra nodes between overloads that could not be parsed into a valid node. - // In this case the subsequent node is not really consecutive (.pos !== node.end), and we must ignore it here. - if (subsequentNode && subsequentNode.pos === node.end) { - if (subsequentNode.kind === node.kind) { - const errorNode: Node = (subsequentNode as FunctionLikeDeclaration).name || subsequentNode; - const subsequentName = (subsequentNode as FunctionLikeDeclaration).name; - if (node.name && subsequentName && ( - // both are private identifiers - isPrivateIdentifier(node.name) && isPrivateIdentifier(subsequentName) && node.name.escapedText === subsequentName.escapedText || - // Both are computed property names - // TODO: GH#17345: These are methods, so handle computed name case. (`Always allowing computed property names is *not* the correct behavior!) - isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) || - // Both are literal property names that are the same. - isPropertyNameLiteral(node.name) && isPropertyNameLiteral(subsequentName) && - getEscapedTextOfIdentifierOrLiteral(node.name) === getEscapedTextOfIdentifierOrLiteral(subsequentName) - )) { - const reportError = - (node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature) && - isStatic(node) !== isStatic(subsequentNode); - // we can get here in two cases - // 1. mixed static and instance class members - // 2. something with the same name was defined before the set of overloads that prevents them from merging - // here we'll report error only for the first case since for second we should already report error in binder - if (reportError) { - const diagnostic = isStatic(node) ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; - error(errorNode, diagnostic); - } - return; - } - if (nodeIsPresent((subsequentNode as FunctionLikeDeclaration).body)) { - error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name)); - return; - } - } - } - const errorNode: Node = node.name || node; - if (isConstructor) { - error(errorNode, Diagnostics.Constructor_implementation_is_missing); + if (node.assertions) { + const override = getResolutionModeOverrideForClause(node.assertions.assertClause, grammarErrorOnNode); + if (override) { + if (!isNightly()) { + grammarErrorOnNode(node.assertions.assertClause, Diagnostics.resolution_mode_assertions_are_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next); } - else { - // Report different errors regarding non-consecutive blocks of declarations depending on whether - // the node in question is abstract. - if (hasSyntacticModifier(node, ModifierFlags.Abstract)) { - error(errorNode, Diagnostics.All_declarations_of_an_abstract_method_must_be_consecutive); - } - else { - error(errorNode, Diagnostics.Function_implementation_is_missing_or_not_immediately_following_the_declaration); - } + if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { + grammarErrorOnNode(node.assertions.assertClause, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext); } } + } - let duplicateFunctionDeclaration = false; - let multipleConstructorImplementation = false; - let hasNonAmbientClass = false; - const functionDeclarations = [] as Declaration[]; - if (declarations) { - for (const current of declarations) { - const node = current as SignatureDeclaration | ClassDeclaration | ClassExpression; - const inAmbientContext = node.flags & NodeFlags.Ambient; - const inAmbientContextOrInterface = node.parent && (node.parent.kind === SyntaxKind.InterfaceDeclaration || node.parent.kind === SyntaxKind.TypeLiteral) || inAmbientContext; - if (inAmbientContextOrInterface) { - // check if declarations are consecutive only if they are non-ambient - // 1. ambient declarations can be interleaved - // i.e. this is legal - // declare function foo(); - // declare function bar(); - // declare function foo(); - // 2. mixing ambient and non-ambient declarations is a separate error that will be reported - do not want to report an extra one - previousDeclaration = undefined; - } - - if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) { - hasNonAmbientClass = true; - } - - if (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature || node.kind === SyntaxKind.Constructor) { - functionDeclarations.push(node); - const currentNodeFlags = getEffectiveDeclarationFlags(node, flagsToCheck); - someNodeFlags |= currentNodeFlags; - allNodeFlags &= currentNodeFlags; - someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node); - allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node); - const bodyIsPresent = nodeIsPresent((node as FunctionLikeDeclaration).body); - - if (bodyIsPresent && bodyDeclaration) { - if (isConstructor) { - multipleConstructorImplementation = true; - } - else { - duplicateFunctionDeclaration = true; - } - } - else if (previousDeclaration?.parent === node.parent && previousDeclaration.end !== node.pos) { - reportImplementationExpectedError(previousDeclaration); - } - - if (bodyIsPresent) { - if (!bodyDeclaration) { - bodyDeclaration = node as FunctionLikeDeclaration; - } - } - else { - hasOverloads = true; - } + getTypeFromTypeNode(node); + } - previousDeclaration = node; + function checkNamedTupleMember(node: NamedTupleMember) { + if (node.dotDotDotToken && node.questionToken) { + grammarErrorOnNode(node, Diagnostics.A_tuple_member_cannot_be_both_optional_and_rest); + } + if (node.type.kind === SyntaxKind.OptionalType) { + grammarErrorOnNode(node.type, Diagnostics.A_labeled_tuple_element_is_declared_as_optional_with_a_question_mark_after_the_name_and_before_the_colon_rather_than_after_the_type); + } + if (node.type.kind === SyntaxKind.RestType) { + grammarErrorOnNode(node.type, Diagnostics.A_labeled_tuple_element_is_declared_as_rest_with_a_before_the_name_rather_than_before_the_type); + } + checkSourceElement(node.type); + getTypeFromTypeNode(node); + } - if (!inAmbientContextOrInterface) { - lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; - } - } - } - } + function isPrivateWithinAmbient(node: Node): boolean { + return (hasEffectiveModifier(node, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(node)) && !!(node.flags & NodeFlags.Ambient); + } - if (multipleConstructorImplementation) { - forEach(functionDeclarations, declaration => { - error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); - }); - } + function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { + let flags = getCombinedModifierFlags(n); - if (duplicateFunctionDeclaration) { - forEach(functionDeclarations, declaration => { - error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_function_implementation); - }); + // children of classes (even ambient classes) should not be marked as ambient or export + // because those flags have no useful semantics there. + if (n.parent.kind !== SyntaxKind.InterfaceDeclaration && + n.parent.kind !== SyntaxKind.ClassDeclaration && + n.parent.kind !== SyntaxKind.ClassExpression && + n.flags & NodeFlags.Ambient) { + if (!(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { + // It is nested in an ambient context, which means it is automatically exported + flags |= ModifierFlags.Export; } + flags |= ModifierFlags.Ambient; + } - if (hasNonAmbientClass && !isConstructor && symbol.flags & SymbolFlags.Function && declarations) { - const relatedDiagnostics = filter(declarations, d => d.kind === SyntaxKind.ClassDeclaration) - .map(d => createDiagnosticForNode(d, Diagnostics.Consider_adding_a_declare_modifier_to_this_class)); + return flags & flagsToCheck; + } - forEach(declarations, declaration => { - const diagnostic = declaration.kind === SyntaxKind.ClassDeclaration - ? Diagnostics.Class_declaration_cannot_implement_overload_list_for_0 - : declaration.kind === SyntaxKind.FunctionDeclaration - ? Diagnostics.Function_with_bodies_can_only_merge_with_classes_that_are_ambient - : undefined; - if (diagnostic) { - addRelatedInfo( - error(getNameOfDeclaration(declaration) || declaration, diagnostic, symbolName(symbol)), - ...relatedDiagnostics - ); + function checkFunctionOrConstructorSymbol(symbol: Symbol): void { + addLazyDiagnostic(() => checkFunctionOrConstructorSymbolWorker(symbol)); + } + + function checkFunctionOrConstructorSymbolWorker(symbol: Symbol): void { + function getCanonicalOverload(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined): Declaration { + // Consider the canonical set of flags to be the flags of the bodyDeclaration or the first declaration + // Error on all deviations from this canonical set of flags + // The caveat is that if some overloads are defined in lib.d.ts, we don't want to + // report the errors on those. To achieve this, we will say that the implementation is + // the canonical signature only if it is in the same container as the first overload + const implementationSharesContainerWithFirstOverload = implementation !== undefined && implementation.parent === overloads[0].parent; + return implementationSharesContainerWithFirstOverload ? implementation : overloads[0]; + } + + function checkFlagAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, flagsToCheck: ModifierFlags, someOverloadFlags: ModifierFlags, allOverloadFlags: ModifierFlags): void { + // Error if some overloads have a flag that is not shared by all overloads. To find the + // deviations, we XOR someOverloadFlags with allOverloadFlags + const someButNotAllOverloadFlags = someOverloadFlags ^ allOverloadFlags; + if (someButNotAllOverloadFlags !== 0) { + const canonicalFlags = getEffectiveDeclarationFlags(getCanonicalOverload(overloads, implementation), flagsToCheck); + forEach(overloads, o => { + const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags; + if (deviation & ModifierFlags.Export) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_exported_or_non_exported); + } + else if (deviation & ModifierFlags.Ambient) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient); + } + else if (deviation & (ModifierFlags.Private | ModifierFlags.Protected)) { + error(getNameOfDeclaration(o) || o, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected); + } + else if (deviation & ModifierFlags.Abstract) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_abstract_or_non_abstract); } }); } + } - // Abstract methods can't have an implementation -- in particular, they don't need one. - if (lastSeenNonAmbientDeclaration && !lastSeenNonAmbientDeclaration.body && - !hasSyntacticModifier(lastSeenNonAmbientDeclaration, ModifierFlags.Abstract) && !lastSeenNonAmbientDeclaration.questionToken) { - reportImplementationExpectedError(lastSeenNonAmbientDeclaration); - } - - if (hasOverloads) { - if (declarations) { - checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags); - checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken); - } - - if (bodyDeclaration) { - const signatures = getSignaturesOfSymbol(symbol); - const bodySignature = getSignatureFromDeclaration(bodyDeclaration); - for (const signature of signatures) { - if (!isImplementationCompatibleWithOverload(bodySignature, signature)) { - addRelatedInfo( - error(signature.declaration, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), - createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here) - ); - break; - } + function checkQuestionTokenAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, someHaveQuestionToken: boolean, allHaveQuestionToken: boolean): void { + if (someHaveQuestionToken !== allHaveQuestionToken) { + const canonicalHasQuestionToken = hasQuestionToken(getCanonicalOverload(overloads, implementation)); + forEach(overloads, o => { + const deviation = hasQuestionToken(o) !== canonicalHasQuestionToken; + if (deviation) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_optional_or_required); } - } + }); } } - function checkExportsOnMergedDeclarations(node: Declaration): void { - addLazyDiagnostic(() => checkExportsOnMergedDeclarationsWorker(node)); - } + const flagsToCheck: ModifierFlags = ModifierFlags.Export | ModifierFlags.Ambient | ModifierFlags.Private | ModifierFlags.Protected | ModifierFlags.Abstract; + let someNodeFlags: ModifierFlags = ModifierFlags.None; + let allNodeFlags = flagsToCheck; + let someHaveQuestionToken = false; + let allHaveQuestionToken = true; + let hasOverloads = false; + let bodyDeclaration: FunctionLikeDeclaration | undefined; + let lastSeenNonAmbientDeclaration: FunctionLikeDeclaration | undefined; + let previousDeclaration: SignatureDeclaration | undefined; - function checkExportsOnMergedDeclarationsWorker(node: Declaration): void { - // if localSymbol is defined on node then node itself is exported - check is required - let symbol = node.localSymbol; - if (!symbol) { - // local symbol is undefined => this declaration is non-exported. - // however symbol might contain other declarations that are exported - symbol = getSymbolOfNode(node)!; - if (!symbol.exportSymbol) { - // this is a pure local symbol (all declarations are non-exported) - no need to check anything - return; - } - } + const declarations = symbol.declarations; + const isConstructor = (symbol.flags & SymbolFlags.Constructor) !== 0; - // run the check only for the first declaration in the list - if (getDeclarationOfKind(symbol, node.kind) !== node) { + function reportImplementationExpectedError(node: SignatureDeclaration): void { + if (node.name && nodeIsMissing(node.name)) { return; } - let exportedDeclarationSpaces = DeclarationSpaces.None; - let nonExportedDeclarationSpaces = DeclarationSpaces.None; - let defaultExportedDeclarationSpaces = DeclarationSpaces.None; - for (const d of symbol.declarations!) { - const declarationSpaces = getDeclarationSpaces(d); - const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, ModifierFlags.Export | ModifierFlags.Default); - - if (effectiveDeclarationFlags & ModifierFlags.Export) { - if (effectiveDeclarationFlags & ModifierFlags.Default) { - defaultExportedDeclarationSpaces |= declarationSpaces; + let seen = false; + const subsequentNode = forEachChild(node.parent, c => { + if (seen) { + return c; + } + else { + seen = c === node; + } + }); + // We may be here because of some extra nodes between overloads that could not be parsed into a valid node. + // In this case the subsequent node is not really consecutive (.pos !== node.end), and we must ignore it here. + if (subsequentNode && subsequentNode.pos === node.end) { + if (subsequentNode.kind === node.kind) { + const errorNode: Node = (subsequentNode as FunctionLikeDeclaration).name || subsequentNode; + const subsequentName = (subsequentNode as FunctionLikeDeclaration).name; + if (node.name && subsequentName && ( + // both are private identifiers + isPrivateIdentifier(node.name) && isPrivateIdentifier(subsequentName) && node.name.escapedText === subsequentName.escapedText || + // Both are computed property names + // TODO: GH#17345: These are methods, so handle computed name case. (`Always allowing computed property names is *not* the correct behavior!) + isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) || + // Both are literal property names that are the same. + isPropertyNameLiteral(node.name) && isPropertyNameLiteral(subsequentName) && + getEscapedTextOfIdentifierOrLiteral(node.name) === getEscapedTextOfIdentifierOrLiteral(subsequentName) + )) { + const reportError = + (node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature) && + isStatic(node) !== isStatic(subsequentNode); + // we can get here in two cases + // 1. mixed static and instance class members + // 2. something with the same name was defined before the set of overloads that prevents them from merging + // here we'll report error only for the first case since for second we should already report error in binder + if (reportError) { + const diagnostic = isStatic(node) ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; + error(errorNode, diagnostic); + } + return; } - else { - exportedDeclarationSpaces |= declarationSpaces; + if (nodeIsPresent((subsequentNode as FunctionLikeDeclaration).body)) { + error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name)); + return; } } + } + const errorNode: Node = node.name || node; + if (isConstructor) { + error(errorNode, Diagnostics.Constructor_implementation_is_missing); + } + else { + // Report different errors regarding non-consecutive blocks of declarations depending on whether + // the node in question is abstract. + if (hasSyntacticModifier(node, ModifierFlags.Abstract)) { + error(errorNode, Diagnostics.All_declarations_of_an_abstract_method_must_be_consecutive); + } else { - nonExportedDeclarationSpaces |= declarationSpaces; + error(errorNode, Diagnostics.Function_implementation_is_missing_or_not_immediately_following_the_declaration); } } + } - // Spaces for anything not declared a 'default export'. - const nonDefaultExportedDeclarationSpaces = exportedDeclarationSpaces | nonExportedDeclarationSpaces; + let duplicateFunctionDeclaration = false; + let multipleConstructorImplementation = false; + let hasNonAmbientClass = false; + const functionDeclarations = [] as Declaration[]; + if (declarations) { + for (const current of declarations) { + const node = current as SignatureDeclaration | ClassDeclaration | ClassExpression; + const inAmbientContext = node.flags & NodeFlags.Ambient; + const inAmbientContextOrInterface = node.parent && (node.parent.kind === SyntaxKind.InterfaceDeclaration || node.parent.kind === SyntaxKind.TypeLiteral) || inAmbientContext; + if (inAmbientContextOrInterface) { + // check if declarations are consecutive only if they are non-ambient + // 1. ambient declarations can be interleaved + // i.e. this is legal + // declare function foo(); + // declare function bar(); + // declare function foo(); + // 2. mixing ambient and non-ambient declarations is a separate error that will be reported - do not want to report an extra one + previousDeclaration = undefined; + } + + if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) { + hasNonAmbientClass = true; + } + + if (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature || node.kind === SyntaxKind.Constructor) { + functionDeclarations.push(node); + const currentNodeFlags = getEffectiveDeclarationFlags(node, flagsToCheck); + someNodeFlags |= currentNodeFlags; + allNodeFlags &= currentNodeFlags; + someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node); + allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node); + const bodyIsPresent = nodeIsPresent((node as FunctionLikeDeclaration).body); + + if (bodyIsPresent && bodyDeclaration) { + if (isConstructor) { + multipleConstructorImplementation = true; + } + else { + duplicateFunctionDeclaration = true; + } + } + else if (previousDeclaration?.parent === node.parent && previousDeclaration.end !== node.pos) { + reportImplementationExpectedError(previousDeclaration); + } - const commonDeclarationSpacesForExportsAndLocals = exportedDeclarationSpaces & nonExportedDeclarationSpaces; - const commonDeclarationSpacesForDefaultAndNonDefault = defaultExportedDeclarationSpaces & nonDefaultExportedDeclarationSpaces; + if (bodyIsPresent) { + if (!bodyDeclaration) { + bodyDeclaration = node as FunctionLikeDeclaration; + } + } + else { + hasOverloads = true; + } - if (commonDeclarationSpacesForExportsAndLocals || commonDeclarationSpacesForDefaultAndNonDefault) { - // declaration spaces for exported and non-exported declarations intersect - for (const d of symbol.declarations!) { - const declarationSpaces = getDeclarationSpaces(d); + previousDeclaration = node; - const name = getNameOfDeclaration(d); - // Only error on the declarations that contributed to the intersecting spaces. - if (declarationSpaces & commonDeclarationSpacesForDefaultAndNonDefault) { - error(name, Diagnostics.Merged_declaration_0_cannot_include_a_default_export_declaration_Consider_adding_a_separate_export_default_0_declaration_instead, declarationNameToString(name)); - } - else if (declarationSpaces & commonDeclarationSpacesForExportsAndLocals) { - error(name, Diagnostics.Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local, declarationNameToString(name)); + if (!inAmbientContextOrInterface) { + lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; } } } + } - function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { - let d = decl as Node; - switch (d.kind) { - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: + if (multipleConstructorImplementation) { + forEach(functionDeclarations, declaration => { + error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); + }); + } - // A jsdoc typedef and callback are, by definition, type aliases. - // falls through - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - return DeclarationSpaces.ExportType; - case SyntaxKind.ModuleDeclaration: - return isAmbientModule(d as ModuleDeclaration) || getModuleInstanceState(d as ModuleDeclaration) !== ModuleInstanceState.NonInstantiated - ? DeclarationSpaces.ExportNamespace | DeclarationSpaces.ExportValue - : DeclarationSpaces.ExportNamespace; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.EnumMember: - return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue; - case SyntaxKind.SourceFile: - return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue | DeclarationSpaces.ExportNamespace; - case SyntaxKind.ExportAssignment: - case SyntaxKind.BinaryExpression: - const node = d as ExportAssignment | BinaryExpression; - const expression = isExportAssignment(node) ? node.expression : node.right; - // Export assigned entity name expressions act as aliases and should fall through, otherwise they export values - if (!isEntityNameExpression(expression)) { - return DeclarationSpaces.ExportValue; - } - d = expression; + if (duplicateFunctionDeclaration) { + forEach(functionDeclarations, declaration => { + error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_function_implementation); + }); + } - // The below options all declare an Alias, which is allowed to merge with other values within the importing module. - // falls through - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportClause: - let result = DeclarationSpaces.None; - const target = resolveAlias(getSymbolOfNode(d)!); - forEach(target.declarations, d => { - result |= getDeclarationSpaces(d); - }); - return result; - case SyntaxKind.VariableDeclaration: - case SyntaxKind.BindingElement: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591 - case SyntaxKind.Identifier: // https://github.com/microsoft/TypeScript/issues/36098 - // Identifiers are used as declarations of assignment declarations whose parents may be - // SyntaxKind.CallExpression - `Object.defineProperty(thing, "aField", {value: 42});` - // SyntaxKind.ElementAccessExpression - `thing["aField"] = 42;` or `thing["aField"];` (with a doc comment on it) - // or SyntaxKind.PropertyAccessExpression - `thing.aField = 42;` - // all of which are pretty much always values, or at least imply a value meaning. - // It may be apprpriate to treat these as aliases in the future. - return DeclarationSpaces.ExportValue; - default: - return Debug.failBadSyntaxKind(d); + if (hasNonAmbientClass && !isConstructor && symbol.flags & SymbolFlags.Function && declarations) { + const relatedDiagnostics = filter(declarations, d => d.kind === SyntaxKind.ClassDeclaration) + .map(d => createDiagnosticForNode(d, Diagnostics.Consider_adding_a_declare_modifier_to_this_class)); + + forEach(declarations, declaration => { + const diagnostic = declaration.kind === SyntaxKind.ClassDeclaration + ? Diagnostics.Class_declaration_cannot_implement_overload_list_for_0 + : declaration.kind === SyntaxKind.FunctionDeclaration + ? Diagnostics.Function_with_bodies_can_only_merge_with_classes_that_are_ambient + : undefined; + if (diagnostic) { + addRelatedInfo( + error(getNameOfDeclaration(declaration) || declaration, diagnostic, symbolName(symbol)), + ...relatedDiagnostics + ); } - } + }); } - function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { - const promisedType = getPromisedTypeOfPromise(type, errorNode); - return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0); + // Abstract methods can't have an implementation -- in particular, they don't need one. + if (lastSeenNonAmbientDeclaration && !lastSeenNonAmbientDeclaration.body && + !hasSyntacticModifier(lastSeenNonAmbientDeclaration, ModifierFlags.Abstract) && !lastSeenNonAmbientDeclaration.questionToken) { + reportImplementationExpectedError(lastSeenNonAmbientDeclaration); } - /** - * Gets the "promised type" of a promise. - * @param type The type of the promise. - * @remarks The "promised type" of a type is the type of the "value" parameter of the "onfulfilled" callback. - */ - function getPromisedTypeOfPromise(type: Type, errorNode?: Node, thisTypeForErrorOut?: { value?: Type }): Type | undefined { - // - // { // type - // then( // thenFunction - // onfulfilled: ( // onfulfilledParameterType - // value: T // valueParameterType - // ) => any - // ): any; - // } - // - - if (isTypeAny(type)) { - return undefined; + if (hasOverloads) { + if (declarations) { + checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags); + checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken); } - const typeAsPromise = type as PromiseOrAwaitableType; - if (typeAsPromise.promisedTypeOfPromise) { - return typeAsPromise.promisedTypeOfPromise; + if (bodyDeclaration) { + const signatures = getSignaturesOfSymbol(symbol); + const bodySignature = getSignatureFromDeclaration(bodyDeclaration); + for (const signature of signatures) { + if (!isImplementationCompatibleWithOverload(bodySignature, signature)) { + addRelatedInfo( + error(signature.declaration, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), + createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here) + ); + break; + } + } } + } + } - if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))) { - return typeAsPromise.promisedTypeOfPromise = getTypeArguments(type as GenericType)[0]; - } + function checkExportsOnMergedDeclarations(node: Declaration): void { + addLazyDiagnostic(() => checkExportsOnMergedDeclarationsWorker(node)); + } - // primitives with a `{ then() }` won't be unwrapped/adopted. - if (allTypesAssignableToKind(getBaseConstraintOrType(type), TypeFlags.Primitive | TypeFlags.Never)) { - return undefined; + function checkExportsOnMergedDeclarationsWorker(node: Declaration): void { + // if localSymbol is defined on node then node itself is exported - check is required + let symbol = node.localSymbol; + if (!symbol) { + // local symbol is undefined => this declaration is non-exported. + // however symbol might contain other declarations that are exported + symbol = getSymbolOfNode(node)!; + if (!symbol.exportSymbol) { + // this is a pure local symbol (all declarations are non-exported) - no need to check anything + return; } + } - const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217 - if (isTypeAny(thenFunction)) { - return undefined; - } + // run the check only for the first declaration in the list + if (getDeclarationOfKind(symbol, node.kind) !== node) { + return; + } - const thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray; - if (thenSignatures.length === 0) { - if (errorNode) { - error(errorNode, Diagnostics.A_promise_must_have_a_then_method); - } - return undefined; - } + let exportedDeclarationSpaces = DeclarationSpaces.None; + let nonExportedDeclarationSpaces = DeclarationSpaces.None; + let defaultExportedDeclarationSpaces = DeclarationSpaces.None; + for (const d of symbol.declarations!) { + const declarationSpaces = getDeclarationSpaces(d); + const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, ModifierFlags.Export | ModifierFlags.Default); - let thisTypeForError: Type | undefined; - let candidates: Signature[] | undefined; - for (const thenSignature of thenSignatures) { - const thisType = getThisTypeOfSignature(thenSignature); - if (thisType && thisType !== voidType && !isTypeRelatedTo(type, thisType, subtypeRelation)) { - thisTypeForError = thisType; + if (effectiveDeclarationFlags & ModifierFlags.Export) { + if (effectiveDeclarationFlags & ModifierFlags.Default) { + defaultExportedDeclarationSpaces |= declarationSpaces; } else { - candidates = append(candidates, thenSignature); + exportedDeclarationSpaces |= declarationSpaces; } } + else { + nonExportedDeclarationSpaces |= declarationSpaces; + } + } + + // Spaces for anything not declared a 'default export'. + const nonDefaultExportedDeclarationSpaces = exportedDeclarationSpaces | nonExportedDeclarationSpaces; + + const commonDeclarationSpacesForExportsAndLocals = exportedDeclarationSpaces & nonExportedDeclarationSpaces; + const commonDeclarationSpacesForDefaultAndNonDefault = defaultExportedDeclarationSpaces & nonDefaultExportedDeclarationSpaces; + + if (commonDeclarationSpacesForExportsAndLocals || commonDeclarationSpacesForDefaultAndNonDefault) { + // declaration spaces for exported and non-exported declarations intersect + for (const d of symbol.declarations!) { + const declarationSpaces = getDeclarationSpaces(d); - if (!candidates) { - Debug.assertIsDefined(thisTypeForError); - if (thisTypeForErrorOut) { - thisTypeForErrorOut.value = thisTypeForError; + const name = getNameOfDeclaration(d); + // Only error on the declarations that contributed to the intersecting spaces. + if (declarationSpaces & commonDeclarationSpacesForDefaultAndNonDefault) { + error(name, Diagnostics.Merged_declaration_0_cannot_include_a_default_export_declaration_Consider_adding_a_separate_export_default_0_declaration_instead, declarationNameToString(name)); } - if (errorNode) { - error(errorNode, Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1, typeToString(type), typeToString(thisTypeForError)); + else if (declarationSpaces & commonDeclarationSpacesForExportsAndLocals) { + error(name, Diagnostics.Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local, declarationNameToString(name)); } - return undefined; } + } - const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(candidates, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull); - if (isTypeAny(onfulfilledParameterType)) { - return undefined; - } + function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { + let d = decl as Node; + switch (d.kind) { + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: - const onfulfilledParameterSignatures = getSignaturesOfType(onfulfilledParameterType, SignatureKind.Call); - if (onfulfilledParameterSignatures.length === 0) { - if (errorNode) { - error(errorNode, Diagnostics.The_first_parameter_of_the_then_method_of_a_promise_must_be_a_callback); - } - return undefined; + // A jsdoc typedef and callback are, by definition, type aliases. + // falls through + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + return DeclarationSpaces.ExportType; + case SyntaxKind.ModuleDeclaration: + return isAmbientModule(d as ModuleDeclaration) || getModuleInstanceState(d as ModuleDeclaration) !== ModuleInstanceState.NonInstantiated + ? DeclarationSpaces.ExportNamespace | DeclarationSpaces.ExportValue + : DeclarationSpaces.ExportNamespace; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.EnumMember: + return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue; + case SyntaxKind.SourceFile: + return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue | DeclarationSpaces.ExportNamespace; + case SyntaxKind.ExportAssignment: + case SyntaxKind.BinaryExpression: + const node = d as ExportAssignment | BinaryExpression; + const expression = isExportAssignment(node) ? node.expression : node.right; + // Export assigned entity name expressions act as aliases and should fall through, otherwise they export values + if (!isEntityNameExpression(expression)) { + return DeclarationSpaces.ExportValue; + } + d = expression; + + // The below options all declare an Alias, which is allowed to merge with other values within the importing module. + // falls through + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportClause: + let result = DeclarationSpaces.None; + const target = resolveAlias(getSymbolOfNode(d)!); + forEach(target.declarations, d => { + result |= getDeclarationSpaces(d); + }); + return result; + case SyntaxKind.VariableDeclaration: + case SyntaxKind.BindingElement: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591 + case SyntaxKind.Identifier: // https://github.com/microsoft/TypeScript/issues/36098 + // Identifiers are used as declarations of assignment declarations whose parents may be + // SyntaxKind.CallExpression - `Object.defineProperty(thing, "aField", {value: 42});` + // SyntaxKind.ElementAccessExpression - `thing["aField"] = 42;` or `thing["aField"];` (with a doc comment on it) + // or SyntaxKind.PropertyAccessExpression - `thing.aField = 42;` + // all of which are pretty much always values, or at least imply a value meaning. + // It may be apprpriate to treat these as aliases in the future. + return DeclarationSpaces.ExportValue; + default: + return Debug.failBadSyntaxKind(d); } + } + } + + function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { + const promisedType = getPromisedTypeOfPromise(type, errorNode); + return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0); + } - return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype); + /** + * Gets the "promised type" of a promise. + * @param type The type of the promise. + * @remarks The "promised type" of a type is the type of the "value" parameter of the "onfulfilled" callback. + */ + function getPromisedTypeOfPromise(type: Type, errorNode?: Node, thisTypeForErrorOut?: { value?: Type }): Type | undefined { + // + // { // type + // then( // thenFunction + // onfulfilled: ( // onfulfilledParameterType + // value: T // valueParameterType + // ) => any + // ): any; + // } + // + + if (isTypeAny(type)) { + return undefined; } - /** - * Gets the "awaited type" of a type. - * @param type The type to await. - * @param withAlias When `true`, wraps the "awaited type" in `Awaited` if needed. - * @remarks The "awaited type" of an expression is its "promised type" if the expression is a - * Promise-like type; otherwise, it is the type of the expression. This is used to reflect - * The runtime behavior of the `await` keyword. - */ - function checkAwaitedType(type: Type, withAlias: boolean, errorNode: Node, diagnosticMessage: DiagnosticMessage, arg0?: string | number): Type { - const awaitedType = withAlias ? - getAwaitedType(type, errorNode, diagnosticMessage, arg0) : - getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, arg0); - return awaitedType || errorType; + const typeAsPromise = type as PromiseOrAwaitableType; + if (typeAsPromise.promisedTypeOfPromise) { + return typeAsPromise.promisedTypeOfPromise; } - /** - * Determines whether a type is an object with a callable `then` member. - */ - function isThenableType(type: Type): boolean { - if (allTypesAssignableToKind(getBaseConstraintOrType(type), TypeFlags.Primitive | TypeFlags.Never)) { - // primitive types cannot be considered "thenable" since they are not objects. - return false; - } + if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))) { + return typeAsPromise.promisedTypeOfPromise = getTypeArguments(type as GenericType)[0]; + } - const thenFunction = getTypeOfPropertyOfType(type, "then" as __String); - return !!thenFunction && getSignaturesOfType(getTypeWithFacts(thenFunction, TypeFacts.NEUndefinedOrNull), SignatureKind.Call).length > 0; + // primitives with a `{ then() }` won't be unwrapped/adopted. + if (allTypesAssignableToKind(getBaseConstraintOrType(type), TypeFlags.Primitive | TypeFlags.Never)) { + return undefined; } - interface AwaitedTypeInstantiation extends Type { - _awaitedTypeBrand: never; - aliasSymbol: Symbol; - aliasTypeArguments: readonly Type[]; + const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217 + if (isTypeAny(thenFunction)) { + return undefined; } - function isAwaitedTypeInstantiation(type: Type): type is AwaitedTypeInstantiation { - if (type.flags & TypeFlags.Conditional) { - const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ false); - return !!awaitedSymbol && type.aliasSymbol === awaitedSymbol && type.aliasTypeArguments?.length === 1; + const thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray; + if (thenSignatures.length === 0) { + if (errorNode) { + error(errorNode, Diagnostics.A_promise_must_have_a_then_method); } - return false; + return undefined; } - /** - * For a generic `Awaited`, gets `T`. - */ - function unwrapAwaitedType(type: Type) { - return type.flags & TypeFlags.Union ? mapType(type, unwrapAwaitedType) : - isAwaitedTypeInstantiation(type) ? type.aliasTypeArguments[0] : - type; + let thisTypeForError: Type | undefined; + let candidates: Signature[] | undefined; + for (const thenSignature of thenSignatures) { + const thisType = getThisTypeOfSignature(thenSignature); + if (thisType && thisType !== voidType && !isTypeRelatedTo(type, thisType, subtypeRelation)) { + thisTypeForError = thisType; + } + else { + candidates = append(candidates, thenSignature); + } } - function isAwaitedTypeNeeded(type: Type) { - // If this is already an `Awaited`, we shouldn't wrap it. This helps to avoid `Awaited>` in higher-order. - if (isTypeAny(type) || isAwaitedTypeInstantiation(type)) { - return false; + if (!candidates) { + Debug.assertIsDefined(thisTypeForError); + if (thisTypeForErrorOut) { + thisTypeForErrorOut.value = thisTypeForError; } + if (errorNode) { + error(errorNode, Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1, typeToString(type), typeToString(thisTypeForError)); + } + return undefined; + } - // We only need `Awaited` if `T` contains possibly non-primitive types. - if (isGenericObjectType(type)) { - const baseConstraint = getBaseConstraintOfType(type); - // We only need `Awaited` if `T` is a type variable that has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`, - // or is promise-like. - if (baseConstraint ? - baseConstraint.flags & TypeFlags.AnyOrUnknown || isEmptyObjectType(baseConstraint) || someType(baseConstraint, isThenableType) : - maybeTypeOfKind(type, TypeFlags.TypeVariable)) { - return true; - } + const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(candidates, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull); + if (isTypeAny(onfulfilledParameterType)) { + return undefined; + } + + const onfulfilledParameterSignatures = getSignaturesOfType(onfulfilledParameterType, SignatureKind.Call); + if (onfulfilledParameterSignatures.length === 0) { + if (errorNode) { + error(errorNode, Diagnostics.The_first_parameter_of_the_then_method_of_a_promise_must_be_a_callback); } + return undefined; + } + return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype); + } + + /** + * Gets the "awaited type" of a type. + * @param type The type to await. + * @param withAlias When `true`, wraps the "awaited type" in `Awaited` if needed. + * @remarks The "awaited type" of an expression is its "promised type" if the expression is a + * Promise-like type; otherwise, it is the type of the expression. This is used to reflect + * The runtime behavior of the `await` keyword. + */ + function checkAwaitedType(type: Type, withAlias: boolean, errorNode: Node, diagnosticMessage: DiagnosticMessage, arg0?: string | number): Type { + const awaitedType = withAlias ? + getAwaitedType(type, errorNode, diagnosticMessage, arg0) : + getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, arg0); + return awaitedType || errorType; + } + + /** + * Determines whether a type is an object with a callable `then` member. + */ + function isThenableType(type: Type): boolean { + if (allTypesAssignableToKind(getBaseConstraintOrType(type), TypeFlags.Primitive | TypeFlags.Never)) { + // primitive types cannot be considered "thenable" since they are not objects. + return false; + } + + const thenFunction = getTypeOfPropertyOfType(type, "then" as __String); + return !!thenFunction && getSignaturesOfType(getTypeWithFacts(thenFunction, TypeFacts.NEUndefinedOrNull), SignatureKind.Call).length > 0; + } + + interface AwaitedTypeInstantiation extends Type { + _awaitedTypeBrand: never; + aliasSymbol: Symbol; + aliasTypeArguments: readonly Type[]; + } + + function isAwaitedTypeInstantiation(type: Type): type is AwaitedTypeInstantiation { + if (type.flags & TypeFlags.Conditional) { + const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ false); + return !!awaitedSymbol && type.aliasSymbol === awaitedSymbol && type.aliasTypeArguments?.length === 1; + } + return false; + } + + /** + * For a generic `Awaited`, gets `T`. + */ + function unwrapAwaitedType(type: Type) { + return type.flags & TypeFlags.Union ? mapType(type, unwrapAwaitedType) : + isAwaitedTypeInstantiation(type) ? type.aliasTypeArguments[0] : + type; + } + + function isAwaitedTypeNeeded(type: Type) { + // If this is already an `Awaited`, we shouldn't wrap it. This helps to avoid `Awaited>` in higher-order. + if (isTypeAny(type) || isAwaitedTypeInstantiation(type)) { return false; } - function tryCreateAwaitedType(type: Type): Type | undefined { - // Nothing to do if `Awaited` doesn't exist - const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ true); - if (awaitedSymbol) { - // Unwrap unions that may contain `Awaited`, otherwise its possible to manufacture an `Awaited | U>` where - // an `Awaited` would suffice. - return getTypeAliasInstantiation(awaitedSymbol, [unwrapAwaitedType(type)]); + // We only need `Awaited` if `T` contains possibly non-primitive types. + if (isGenericObjectType(type)) { + const baseConstraint = getBaseConstraintOfType(type); + // We only need `Awaited` if `T` is a type variable that has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`, + // or is promise-like. + if (baseConstraint ? + baseConstraint.flags & TypeFlags.AnyOrUnknown || isEmptyObjectType(baseConstraint) || someType(baseConstraint, isThenableType) : + maybeTypeOfKind(type, TypeFlags.TypeVariable)) { + return true; } + } - return undefined; + return false; + } + + function tryCreateAwaitedType(type: Type): Type | undefined { + // Nothing to do if `Awaited` doesn't exist + const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ true); + if (awaitedSymbol) { + // Unwrap unions that may contain `Awaited`, otherwise its possible to manufacture an `Awaited | U>` where + // an `Awaited` would suffice. + return getTypeAliasInstantiation(awaitedSymbol, [unwrapAwaitedType(type)]); } - function createAwaitedTypeIfNeeded(type: Type): Type { - // We wrap type `T` in `Awaited` based on the following conditions: - // - `T` is not already an `Awaited`, and - // - `T` is generic, and - // - One of the following applies: - // - `T` has no base constraint, or - // - The base constraint of `T` is `any`, `unknown`, `object`, or `{}`, or - // - The base constraint of `T` is an object type with a callable `then` method. + return undefined; + } + + function createAwaitedTypeIfNeeded(type: Type): Type { + // We wrap type `T` in `Awaited` based on the following conditions: + // - `T` is not already an `Awaited`, and + // - `T` is generic, and + // - One of the following applies: + // - `T` has no base constraint, or + // - The base constraint of `T` is `any`, `unknown`, `object`, or `{}`, or + // - The base constraint of `T` is an object type with a callable `then` method. - if (isAwaitedTypeNeeded(type)) { - const awaitedType = tryCreateAwaitedType(type); - if (awaitedType) { - return awaitedType; - } + if (isAwaitedTypeNeeded(type)) { + const awaitedType = tryCreateAwaitedType(type); + if (awaitedType) { + return awaitedType; } + } + + Debug.assert(getPromisedTypeOfPromise(type) === undefined, "type provided should not be a non-generic 'promise'-like."); + return type; + } + + /** + * Gets the "awaited type" of a type. + * + * The "awaited type" of an expression is its "promised type" if the expression is a + * Promise-like type; otherwise, it is the type of the expression. If the "promised + * type" is itself a Promise-like, the "promised type" is recursively unwrapped until a + * non-promise type is found. + * + * This is used to reflect the runtime behavior of the `await` keyword. + */ + function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { + const awaitedType = getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, arg0); + return awaitedType && createAwaitedTypeIfNeeded(awaitedType); + } - Debug.assert(getPromisedTypeOfPromise(type) === undefined, "type provided should not be a non-generic 'promise'-like."); + /** + * Gets the "awaited type" of a type without introducing an `Awaited` wrapper. + * + * @see {@link getAwaitedType} + */ + function getAwaitedTypeNoAlias(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { + if (isTypeAny(type)) { return type; } - /** - * Gets the "awaited type" of a type. - * - * The "awaited type" of an expression is its "promised type" if the expression is a - * Promise-like type; otherwise, it is the type of the expression. If the "promised - * type" is itself a Promise-like, the "promised type" is recursively unwrapped until a - * non-promise type is found. - * - * This is used to reflect the runtime behavior of the `await` keyword. - */ - function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { - const awaitedType = getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, arg0); - return awaitedType && createAwaitedTypeIfNeeded(awaitedType); + // If this is already an `Awaited`, just return it. This avoids `Awaited>` in higher-order + if (isAwaitedTypeInstantiation(type)) { + return type; } - /** - * Gets the "awaited type" of a type without introducing an `Awaited` wrapper. - * - * @see {@link getAwaitedType} - */ - function getAwaitedTypeNoAlias(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { - if (isTypeAny(type)) { - return type; - } + // If we've already cached an awaited type, return a possible `Awaited` for it. + const typeAsAwaitable = type as PromiseOrAwaitableType; + if (typeAsAwaitable.awaitedTypeOfType) { + return typeAsAwaitable.awaitedTypeOfType; + } - // If this is already an `Awaited`, just return it. This avoids `Awaited>` in higher-order - if (isAwaitedTypeInstantiation(type)) { - return type; + // For a union, get a union of the awaited types of each constituent. + if (type.flags & TypeFlags.Union) { + if (awaitedTypeStack.lastIndexOf(type.id) >= 0) { + if (errorNode) { + error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); + } + return undefined; } - // If we've already cached an awaited type, return a possible `Awaited` for it. - const typeAsAwaitable = type as PromiseOrAwaitableType; - if (typeAsAwaitable.awaitedTypeOfType) { - return typeAsAwaitable.awaitedTypeOfType; - } + const mapper = errorNode ? (constituentType: Type) => getAwaitedTypeNoAlias(constituentType, errorNode, diagnosticMessage, arg0) : getAwaitedTypeNoAlias; - // For a union, get a union of the awaited types of each constituent. - if (type.flags & TypeFlags.Union) { - if (awaitedTypeStack.lastIndexOf(type.id) >= 0) { - if (errorNode) { - error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); - } - return undefined; - } + awaitedTypeStack.push(type.id); + const mapped = mapType(type, mapper); + awaitedTypeStack.pop(); - const mapper = errorNode ? (constituentType: Type) => getAwaitedTypeNoAlias(constituentType, errorNode, diagnosticMessage, arg0) : getAwaitedTypeNoAlias; + return typeAsAwaitable.awaitedTypeOfType = mapped; + } + + // If `type` is generic and should be wrapped in `Awaited`, return it. + if (isAwaitedTypeNeeded(type)) { + return typeAsAwaitable.awaitedTypeOfType = type; + } + + const thisTypeForErrorOut: { value: Type | undefined } = { value: undefined }; + const promisedType = getPromisedTypeOfPromise(type, /*errorNode*/ undefined, thisTypeForErrorOut); + if (promisedType) { + if (type.id === promisedType.id || awaitedTypeStack.lastIndexOf(promisedType.id) >= 0) { + // Verify that we don't have a bad actor in the form of a promise whose + // promised type is the same as the promise type, or a mutually recursive + // promise. If so, we return undefined as we cannot guess the shape. If this + // were the actual case in the JavaScript, this Promise would never resolve. + // + // An example of a bad actor with a singly-recursive promise type might + // be: + // + // interface BadPromise { + // then( + // onfulfilled: (value: BadPromise) => any, + // onrejected: (error: any) => any): BadPromise; + // } + // + // The above interface will pass the PromiseLike check, and return a + // promised type of `BadPromise`. Since this is a self reference, we + // don't want to keep recursing ad infinitum. + // + // An example of a bad actor in the form of a mutually-recursive + // promise type might be: + // + // interface BadPromiseA { + // then( + // onfulfilled: (value: BadPromiseB) => any, + // onrejected: (error: any) => any): BadPromiseB; + // } + // + // interface BadPromiseB { + // then( + // onfulfilled: (value: BadPromiseA) => any, + // onrejected: (error: any) => any): BadPromiseA; + // } + // + if (errorNode) { + error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); + } + return undefined; + } - awaitedTypeStack.push(type.id); - const mapped = mapType(type, mapper); - awaitedTypeStack.pop(); + // Keep track of the type we're about to unwrap to avoid bad recursive promise types. + // See the comments above for more information. + awaitedTypeStack.push(type.id); + const awaitedType = getAwaitedTypeNoAlias(promisedType, errorNode, diagnosticMessage, arg0); + awaitedTypeStack.pop(); - return typeAsAwaitable.awaitedTypeOfType = mapped; + if (!awaitedType) { + return undefined; } - // If `type` is generic and should be wrapped in `Awaited`, return it. - if (isAwaitedTypeNeeded(type)) { - return typeAsAwaitable.awaitedTypeOfType = type; - } + return typeAsAwaitable.awaitedTypeOfType = awaitedType; + } - const thisTypeForErrorOut: { value: Type | undefined } = { value: undefined }; - const promisedType = getPromisedTypeOfPromise(type, /*errorNode*/ undefined, thisTypeForErrorOut); - if (promisedType) { - if (type.id === promisedType.id || awaitedTypeStack.lastIndexOf(promisedType.id) >= 0) { - // Verify that we don't have a bad actor in the form of a promise whose - // promised type is the same as the promise type, or a mutually recursive - // promise. If so, we return undefined as we cannot guess the shape. If this - // were the actual case in the JavaScript, this Promise would never resolve. - // - // An example of a bad actor with a singly-recursive promise type might - // be: - // - // interface BadPromise { - // then( - // onfulfilled: (value: BadPromise) => any, - // onrejected: (error: any) => any): BadPromise; - // } - // - // The above interface will pass the PromiseLike check, and return a - // promised type of `BadPromise`. Since this is a self reference, we - // don't want to keep recursing ad infinitum. - // - // An example of a bad actor in the form of a mutually-recursive - // promise type might be: - // - // interface BadPromiseA { - // then( - // onfulfilled: (value: BadPromiseB) => any, - // onrejected: (error: any) => any): BadPromiseB; - // } - // - // interface BadPromiseB { - // then( - // onfulfilled: (value: BadPromiseA) => any, - // onrejected: (error: any) => any): BadPromiseA; - // } - // - if (errorNode) { - error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); - } - return undefined; + // The type was not a promise, so it could not be unwrapped any further. + // As long as the type does not have a callable "then" property, it is + // safe to return the type; otherwise, an error is reported and we return + // undefined. + // + // An example of a non-promise "thenable" might be: + // + // await { then(): void {} } + // + // The "thenable" does not match the minimal definition for a promise. When + // a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise + // will never settle. We treat this as an error to help flag an early indicator + // of a runtime problem. If the user wants to return this value from an async + // function, they would need to wrap it in some other value. If they want it to + // be treated as a promise, they can cast to . + if (isThenableType(type)) { + if (errorNode) { + Debug.assertIsDefined(diagnosticMessage); + let chain: DiagnosticMessageChain | undefined; + if (thisTypeForErrorOut.value) { + chain = chainDiagnosticMessages(chain, Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1, typeToString(type), typeToString(thisTypeForErrorOut.value)); } + chain = chainDiagnosticMessages(chain, diagnosticMessage, arg0); + diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode, chain)); + } + return undefined; + } - // Keep track of the type we're about to unwrap to avoid bad recursive promise types. - // See the comments above for more information. - awaitedTypeStack.push(type.id); - const awaitedType = getAwaitedTypeNoAlias(promisedType, errorNode, diagnosticMessage, arg0); - awaitedTypeStack.pop(); + return typeAsAwaitable.awaitedTypeOfType = type; + } - if (!awaitedType) { - return undefined; - } + /** + * Checks the return type of an async function to ensure it is a compatible + * Promise implementation. + * + * This checks that an async function has a valid Promise-compatible return type. + * An async function has a valid Promise-compatible return type if the resolved value + * of the return type has a construct signature that takes in an `initializer` function + * that in turn supplies a `resolve` function as one of its arguments and results in an + * object with a callable `then` signature. + * + * @param node The signature to check + */ + function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode) { + // As part of our emit for an async function, we will need to emit the entity name of + // the return type annotation as an expression. To meet the necessary runtime semantics + // for __awaiter, we must also check that the type of the declaration (e.g. the static + // side or "constructor" of the promise type) is compatible `PromiseConstructorLike`. + // + // An example might be (from lib.es6.d.ts): + // + // interface Promise { ... } + // interface PromiseConstructor { + // new (...): Promise; + // } + // declare var Promise: PromiseConstructor; + // + // When an async function declares a return type annotation of `Promise`, we + // need to get the type of the `Promise` variable declaration above, which would + // be `PromiseConstructor`. + // + // The same case applies to a class: + // + // declare class Promise { + // constructor(...); + // then(...): Promise; + // } + // + const returnType = getTypeFromTypeNode(returnTypeNode); - return typeAsAwaitable.awaitedTypeOfType = awaitedType; + if (languageVersion >= ScriptTarget.ES2015) { + if (isErrorType(returnType)) { + return; } - - // The type was not a promise, so it could not be unwrapped any further. - // As long as the type does not have a callable "then" property, it is - // safe to return the type; otherwise, an error is reported and we return - // undefined. - // - // An example of a non-promise "thenable" might be: - // - // await { then(): void {} } - // - // The "thenable" does not match the minimal definition for a promise. When - // a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise - // will never settle. We treat this as an error to help flag an early indicator - // of a runtime problem. If the user wants to return this value from an async - // function, they would need to wrap it in some other value. If they want it to - // be treated as a promise, they can cast to . - if (isThenableType(type)) { - if (errorNode) { - Debug.assertIsDefined(diagnosticMessage); - let chain: DiagnosticMessageChain | undefined; - if (thisTypeForErrorOut.value) { - chain = chainDiagnosticMessages(chain, Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1, typeToString(type), typeToString(thisTypeForErrorOut.value)); - } - chain = chainDiagnosticMessages(chain, diagnosticMessage, arg0); - diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode, chain)); - } - return undefined; + const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); + if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) { + // The promise type was not a valid type reference to the global promise type, so we + // report an error and return the unknown type. + error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, typeToString(getAwaitedTypeNoAlias(returnType) || voidType)); + return; } - - return typeAsAwaitable.awaitedTypeOfType = type; } + else { + // Always mark the type node as referenced if it points to a value + markTypeNodeAsReferenced(returnTypeNode); - /** - * Checks the return type of an async function to ensure it is a compatible - * Promise implementation. - * - * This checks that an async function has a valid Promise-compatible return type. - * An async function has a valid Promise-compatible return type if the resolved value - * of the return type has a construct signature that takes in an `initializer` function - * that in turn supplies a `resolve` function as one of its arguments and results in an - * object with a callable `then` signature. - * - * @param node The signature to check - */ - function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode) { - // As part of our emit for an async function, we will need to emit the entity name of - // the return type annotation as an expression. To meet the necessary runtime semantics - // for __awaiter, we must also check that the type of the declaration (e.g. the static - // side or "constructor" of the promise type) is compatible `PromiseConstructorLike`. - // - // An example might be (from lib.es6.d.ts): - // - // interface Promise { ... } - // interface PromiseConstructor { - // new (...): Promise; - // } - // declare var Promise: PromiseConstructor; - // - // When an async function declares a return type annotation of `Promise`, we - // need to get the type of the `Promise` variable declaration above, which would - // be `PromiseConstructor`. - // - // The same case applies to a class: - // - // declare class Promise { - // constructor(...); - // then(...): Promise; - // } - // - const returnType = getTypeFromTypeNode(returnTypeNode); - - if (languageVersion >= ScriptTarget.ES2015) { - if (isErrorType(returnType)) { - return; - } - const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); - if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) { - // The promise type was not a valid type reference to the global promise type, so we - // report an error and return the unknown type. - error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, typeToString(getAwaitedTypeNoAlias(returnType) || voidType)); - return; - } + if (isErrorType(returnType)) { + return; } - else { - // Always mark the type node as referenced if it points to a value - markTypeNodeAsReferenced(returnTypeNode); - - if (isErrorType(returnType)) { - return; - } - const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode); - if (promiseConstructorName === undefined) { - error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(returnType)); - return; - } + const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode); + if (promiseConstructorName === undefined) { + error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(returnType)); + return; + } - const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true); - const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType; - if (isErrorType(promiseConstructorType)) { - if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.escapedText === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) { - error(returnTypeNode, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); - } - else { - error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); - } - return; + const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true); + const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType; + if (isErrorType(promiseConstructorType)) { + if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.escapedText === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) { + error(returnTypeNode, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); } - - const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(/*reportErrors*/ true); - if (globalPromiseConstructorLikeType === emptyObjectType) { - // If we couldn't resolve the global PromiseConstructorLike type we cannot verify - // compatibility with __awaiter. + else { error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); - return; } + return; + } - if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeNode, - Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) { - return; - } + const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(/*reportErrors*/ true); + if (globalPromiseConstructorLikeType === emptyObjectType) { + // If we couldn't resolve the global PromiseConstructorLike type we cannot verify + // compatibility with __awaiter. + error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); + return; + } - // Verify there is no local declaration that could collide with the promise constructor. - const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName); - const collidingSymbol = getSymbol(node.locals!, rootName.escapedText, SymbolFlags.Value); - if (collidingSymbol) { - error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, - idText(rootName), - entityNameToString(promiseConstructorName)); - return; - } + if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeNode, + Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) { + return; } - checkAwaitedType(returnType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - } - /** Check a decorator */ - function checkDecorator(node: Decorator): void { - const signature = getResolvedSignature(node); - checkDeprecatedSignature(signature, node); - const returnType = getReturnTypeOfSignature(signature); - if (returnType.flags & TypeFlags.Any) { + // Verify there is no local declaration that could collide with the promise constructor. + const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName); + const collidingSymbol = getSymbol(node.locals!, rootName.escapedText, SymbolFlags.Value); + if (collidingSymbol) { + error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, + idText(rootName), + entityNameToString(promiseConstructorName)); return; } + } + checkAwaitedType(returnType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + } - let headMessage: DiagnosticMessage; - let expectedReturnType: Type; - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; - const classSymbol = getSymbolOfNode(node.parent); - const classConstructorType = getTypeOfSymbol(classSymbol); - expectedReturnType = getUnionType([classConstructorType, voidType]); - break; + /** Check a decorator */ + function checkDecorator(node: Decorator): void { + const signature = getResolvedSignature(node); + checkDeprecatedSignature(signature, node); + const returnType = getReturnTypeOfSignature(signature); + if (returnType.flags & TypeFlags.Any) { + return; + } - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.Parameter: - headMessage = Diagnostics.Decorator_function_return_type_is_0_but_is_expected_to_be_void_or_any; - expectedReturnType = voidType; - break; + let headMessage: DiagnosticMessage; + let expectedReturnType: Type; + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; + const classSymbol = getSymbolOfNode(node.parent); + const classConstructorType = getTypeOfSymbol(classSymbol); + expectedReturnType = getUnionType([classConstructorType, voidType]); + break; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; - const methodType = getTypeOfNode(node.parent); - const descriptorType = createTypedPropertyDescriptorType(methodType); - expectedReturnType = getUnionType([descriptorType, voidType]); - break; + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.Parameter: + headMessage = Diagnostics.Decorator_function_return_type_is_0_but_is_expected_to_be_void_or_any; + expectedReturnType = voidType; + break; - default: - return Debug.fail(); - } + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; + const methodType = getTypeOfNode(node.parent); + const descriptorType = createTypedPropertyDescriptorType(methodType); + expectedReturnType = getUnionType([descriptorType, voidType]); + break; - checkTypeAssignableTo( - returnType, - expectedReturnType, - node, - headMessage); + default: + return Debug.fail(); } - /** - * If a TypeNode can be resolved to a value symbol imported from an external module, it is - * marked as referenced to prevent import elision. - */ - function markTypeNodeAsReferenced(node: TypeNode) { - markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node), /*forDecoratorMetadata*/ false); - } + checkTypeAssignableTo( + returnType, + expectedReturnType, + node, + headMessage); + } - function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined, forDecoratorMetadata: boolean) { - if (!typeName) return; + /** + * If a TypeNode can be resolved to a value symbol imported from an external module, it is + * marked as referenced to prevent import elision. + */ + function markTypeNodeAsReferenced(node: TypeNode) { + markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node), /*forDecoratorMetadata*/ false); + } - const rootName = getFirstIdentifier(typeName); - const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; - const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isReference*/ true); - if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { - if (symbolIsValue(rootSymbol) - && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) - && !getTypeOnlyAliasDeclaration(rootSymbol)) { - markAliasSymbolAsReferenced(rootSymbol); - } - else if (forDecoratorMetadata - && compilerOptions.isolatedModules - && getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015 - && !symbolIsValue(rootSymbol) - && !some(rootSymbol.declarations, isTypeOnlyImportOrExportDeclaration)) { - const diag = error(typeName, Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled); - const aliasDeclaration = find(rootSymbol.declarations || emptyArray, isAliasSymbolDeclaration); - if (aliasDeclaration) { - addRelatedInfo(diag, createDiagnosticForNode(aliasDeclaration, Diagnostics._0_was_imported_here, idText(rootName))); - } + function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined, forDecoratorMetadata: boolean) { + if (!typeName) return; + + const rootName = getFirstIdentifier(typeName); + const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; + const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isReference*/ true); + if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { + if (symbolIsValue(rootSymbol) + && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) + && !getTypeOnlyAliasDeclaration(rootSymbol)) { + markAliasSymbolAsReferenced(rootSymbol); + } + else if (forDecoratorMetadata + && compilerOptions.isolatedModules + && getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015 + && !symbolIsValue(rootSymbol) + && !some(rootSymbol.declarations, isTypeOnlyImportOrExportDeclaration)) { + const diag = error(typeName, Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled); + const aliasDeclaration = find(rootSymbol.declarations || emptyArray, isAliasSymbolDeclaration); + if (aliasDeclaration) { + addRelatedInfo(diag, createDiagnosticForNode(aliasDeclaration, Diagnostics._0_was_imported_here, idText(rootName))); } } } + } - /** - * This function marks the type used for metadata decorator as referenced if it is import - * from external module. - * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in - * union and intersection type - * @param node - */ - function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { - const entityName = getEntityNameForDecoratorMetadata(node); - if (entityName && isEntityName(entityName)) { - markEntityNameOrEntityExpressionAsReference(entityName, /*forDecoratorMetadata*/ true); - } + /** + * This function marks the type used for metadata decorator as referenced if it is import + * from external module. + * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in + * union and intersection type + * @param node + */ + function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { + const entityName = getEntityNameForDecoratorMetadata(node); + if (entityName && isEntityName(entityName)) { + markEntityNameOrEntityExpressionAsReference(entityName, /*forDecoratorMetadata*/ true); } + } - function getEntityNameForDecoratorMetadata(node: TypeNode | undefined): EntityName | undefined { - if (node) { - switch (node.kind) { - case SyntaxKind.IntersectionType: - case SyntaxKind.UnionType: - return getEntityNameForDecoratorMetadataFromTypeList((node as UnionOrIntersectionTypeNode).types); + function getEntityNameForDecoratorMetadata(node: TypeNode | undefined): EntityName | undefined { + if (node) { + switch (node.kind) { + case SyntaxKind.IntersectionType: + case SyntaxKind.UnionType: + return getEntityNameForDecoratorMetadataFromTypeList((node as UnionOrIntersectionTypeNode).types); - case SyntaxKind.ConditionalType: - return getEntityNameForDecoratorMetadataFromTypeList([(node as ConditionalTypeNode).trueType, (node as ConditionalTypeNode).falseType]); + case SyntaxKind.ConditionalType: + return getEntityNameForDecoratorMetadataFromTypeList([(node as ConditionalTypeNode).trueType, (node as ConditionalTypeNode).falseType]); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.NamedTupleMember: - return getEntityNameForDecoratorMetadata((node as ParenthesizedTypeNode).type); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.NamedTupleMember: + return getEntityNameForDecoratorMetadata((node as ParenthesizedTypeNode).type); - case SyntaxKind.TypeReference: - return (node as TypeReferenceNode).typeName; - } + case SyntaxKind.TypeReference: + return (node as TypeReferenceNode).typeName; } } + } - function getEntityNameForDecoratorMetadataFromTypeList(types: readonly TypeNode[]): EntityName | undefined { - let commonEntityName: EntityName | undefined; - for (let typeNode of types) { - while (typeNode.kind === SyntaxKind.ParenthesizedType || typeNode.kind === SyntaxKind.NamedTupleMember) { - typeNode = (typeNode as ParenthesizedTypeNode | NamedTupleMember).type; // Skip parens if need be - } - if (typeNode.kind === SyntaxKind.NeverKeyword) { - continue; // Always elide `never` from the union/intersection if possible - } - if (!strictNullChecks && (typeNode.kind === SyntaxKind.LiteralType && (typeNode as LiteralTypeNode).literal.kind === SyntaxKind.NullKeyword || typeNode.kind === SyntaxKind.UndefinedKeyword)) { - continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks - } - const individualEntityName = getEntityNameForDecoratorMetadata(typeNode); - if (!individualEntityName) { - // Individual is something like string number - // So it would be serialized to either that type or object - // Safe to return here - return undefined; - } + function getEntityNameForDecoratorMetadataFromTypeList(types: readonly TypeNode[]): EntityName | undefined { + let commonEntityName: EntityName | undefined; + for (let typeNode of types) { + while (typeNode.kind === SyntaxKind.ParenthesizedType || typeNode.kind === SyntaxKind.NamedTupleMember) { + typeNode = (typeNode as ParenthesizedTypeNode | NamedTupleMember).type; // Skip parens if need be + } + if (typeNode.kind === SyntaxKind.NeverKeyword) { + continue; // Always elide `never` from the union/intersection if possible + } + if (!strictNullChecks && (typeNode.kind === SyntaxKind.LiteralType && (typeNode as LiteralTypeNode).literal.kind === SyntaxKind.NullKeyword || typeNode.kind === SyntaxKind.UndefinedKeyword)) { + continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks + } + const individualEntityName = getEntityNameForDecoratorMetadata(typeNode); + if (!individualEntityName) { + // Individual is something like string number + // So it would be serialized to either that type or object + // Safe to return here + return undefined; + } - if (commonEntityName) { - // Note this is in sync with the transformation that happens for type node. - // Keep this in sync with serializeUnionOrIntersectionType - // Verify if they refer to same entity and is identifier - // return undefined if they dont match because we would emit object - if (!isIdentifier(commonEntityName) || - !isIdentifier(individualEntityName) || - commonEntityName.escapedText !== individualEntityName.escapedText) { - return undefined; - } - } - else { - commonEntityName = individualEntityName; + if (commonEntityName) { + // Note this is in sync with the transformation that happens for type node. + // Keep this in sync with serializeUnionOrIntersectionType + // Verify if they refer to same entity and is identifier + // return undefined if they dont match because we would emit object + if (!isIdentifier(commonEntityName) || + !isIdentifier(individualEntityName) || + commonEntityName.escapedText !== individualEntityName.escapedText) { + return undefined; } } - return commonEntityName; - } - - function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { - const typeNode = getEffectiveTypeAnnotationNode(node); - return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; + else { + commonEntityName = individualEntityName; + } } + return commonEntityName; + } - /** Check the decorators of a node */ - function checkDecorators(node: Node): void { - // skip this check for nodes that cannot have decorators. These should have already had an error reported by - // checkGrammarDecorators. - if (!canHaveDecorators(node) || !hasDecorators(node) || !node.modifiers || !nodeCanBeDecorated(node, node.parent, node.parent.parent)) { - return; - } + function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { + const typeNode = getEffectiveTypeAnnotationNode(node); + return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; + } - if (!compilerOptions.experimentalDecorators) { - error(node, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning); - } + /** Check the decorators of a node */ + function checkDecorators(node: Node): void { + // skip this check for nodes that cannot have decorators. These should have already had an error reported by + // checkGrammarDecorators. + if (!canHaveDecorators(node) || !hasDecorators(node) || !node.modifiers || !nodeCanBeDecorated(node, node.parent, node.parent.parent)) { + return; + } - const firstDecorator = find(node.modifiers, isDecorator); - if (!firstDecorator) { - return; - } + if (!compilerOptions.experimentalDecorators) { + error(node, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning); + } - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); - if (node.kind === SyntaxKind.Parameter) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); - } + const firstDecorator = find(node.modifiers, isDecorator); + if (!firstDecorator) { + return; + } - if (compilerOptions.emitDecoratorMetadata) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); + if (node.kind === SyntaxKind.Parameter) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); + } - // we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator. - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - const constructor = getFirstConstructorWithBody(node); - if (constructor) { - for (const parameter of constructor.parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); - } - } - break; + if (compilerOptions.emitDecoratorMetadata) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; - const otherAccessor = getDeclarationOfKind(getSymbolOfNode(node), otherKind); - markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); - break; - case SyntaxKind.MethodDeclaration: - for (const parameter of node.parameters) { + // we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator. + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + const constructor = getFirstConstructorWithBody(node); + if (constructor) { + for (const parameter of constructor.parameters) { markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } + } + break; - markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); - break; + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfNode(node), otherKind); + markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); + break; + case SyntaxKind.MethodDeclaration: + for (const parameter of node.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } - case SyntaxKind.PropertyDeclaration: - markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); - break; + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); + break; - case SyntaxKind.Parameter: - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); - const containingSignature = node.parent; - for (const parameter of containingSignature.parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); - } - break; - } - } + case SyntaxKind.PropertyDeclaration: + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); + break; - for (const modifier of node.modifiers) { - if (isDecorator(modifier)) { - checkDecorator(modifier); - } + case SyntaxKind.Parameter: + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); + const containingSignature = node.parent; + for (const parameter of containingSignature.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } + break; } } - function checkFunctionDeclaration(node: FunctionDeclaration): void { - addLazyDiagnostic(checkFunctionDeclarationDiagnostics); - - function checkFunctionDeclarationDiagnostics() { - checkFunctionOrMethodDeclaration(node); - checkGrammarForGenerator(node); - checkCollisionsForDeclarationName(node, node.name); + for (const modifier of node.modifiers) { + if (isDecorator(modifier)) { + checkDecorator(modifier); } } + } - function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) { - if (!node.typeExpression) { - // If the node had `@property` tags, `typeExpression` would have been set to the first property tag. - error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); - } + function checkFunctionDeclaration(node: FunctionDeclaration): void { + addLazyDiagnostic(checkFunctionDeclarationDiagnostics); - if (node.name) { - checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); - } - checkSourceElement(node.typeExpression); - checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + function checkFunctionDeclarationDiagnostics() { + checkFunctionOrMethodDeclaration(node); + checkGrammarForGenerator(node); + checkCollisionsForDeclarationName(node, node.name); } + } - function checkJSDocTemplateTag(node: JSDocTemplateTag): void { - checkSourceElement(node.constraint); - for (const tp of node.typeParameters) { - checkSourceElement(tp); - } + function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) { + if (!node.typeExpression) { + // If the node had `@property` tags, `typeExpression` would have been set to the first property tag. + error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); } - function checkJSDocTypeTag(node: JSDocTypeTag) { - checkSourceElement(node.typeExpression); + if (node.name) { + checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); } + checkSourceElement(node.typeExpression); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + } - function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) { - if (node.name) { - resolveJSDocMemberName(node.name, /*ignoreErrors*/ true); - } + function checkJSDocTemplateTag(node: JSDocTemplateTag): void { + checkSourceElement(node.constraint); + for (const tp of node.typeParameters) { + checkSourceElement(tp); } + } - function checkJSDocParameterTag(node: JSDocParameterTag) { - checkSourceElement(node.typeExpression); - } - function checkJSDocPropertyTag(node: JSDocPropertyTag) { - checkSourceElement(node.typeExpression); + function checkJSDocTypeTag(node: JSDocTypeTag) { + checkSourceElement(node.typeExpression); + } + + function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) { + if (node.name) { + resolveJSDocMemberName(node.name, /*ignoreErrors*/ true); } + } - function checkJSDocFunctionType(node: JSDocFunctionType): void { - addLazyDiagnostic(checkJSDocFunctionTypeImplicitAny); - checkSignatureDeclaration(node); + function checkJSDocParameterTag(node: JSDocParameterTag) { + checkSourceElement(node.typeExpression); + } + function checkJSDocPropertyTag(node: JSDocPropertyTag) { + checkSourceElement(node.typeExpression); + } - function checkJSDocFunctionTypeImplicitAny() { - if (!node.type && !isJSDocConstructSignature(node)) { - reportImplicitAny(node, anyType); - } + function checkJSDocFunctionType(node: JSDocFunctionType): void { + addLazyDiagnostic(checkJSDocFunctionTypeImplicitAny); + checkSignatureDeclaration(node); + + function checkJSDocFunctionTypeImplicitAny() { + if (!node.type && !isJSDocConstructSignature(node)) { + reportImplicitAny(node, anyType); } } + } - function checkJSDocImplementsTag(node: JSDocImplementsTag): void { - const classLike = getEffectiveJSDocHost(node); - if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) { - error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); - } + function checkJSDocImplementsTag(node: JSDocImplementsTag): void { + const classLike = getEffectiveJSDocHost(node); + if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) { + error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); } + } - function checkJSDocAugmentsTag(node: JSDocAugmentsTag): void { - const classLike = getEffectiveJSDocHost(node); - if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) { - error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); - return; - } + function checkJSDocAugmentsTag(node: JSDocAugmentsTag): void { + const classLike = getEffectiveJSDocHost(node); + if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) { + error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); + return; + } - const augmentsTags = getJSDocTags(classLike).filter(isJSDocAugmentsTag); - Debug.assert(augmentsTags.length > 0); - if (augmentsTags.length > 1) { - error(augmentsTags[1], Diagnostics.Class_declarations_cannot_have_more_than_one_augments_or_extends_tag); - } + const augmentsTags = getJSDocTags(classLike).filter(isJSDocAugmentsTag); + Debug.assert(augmentsTags.length > 0); + if (augmentsTags.length > 1) { + error(augmentsTags[1], Diagnostics.Class_declarations_cannot_have_more_than_one_augments_or_extends_tag); + } - const name = getIdentifierFromEntityNameExpression(node.class.expression); - const extend = getClassExtendsHeritageElement(classLike); - if (extend) { - const className = getIdentifierFromEntityNameExpression(extend.expression); - if (className && name.escapedText !== className.escapedText) { - error(name, Diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, idText(node.tagName), idText(name), idText(className)); - } + const name = getIdentifierFromEntityNameExpression(node.class.expression); + const extend = getClassExtendsHeritageElement(classLike); + if (extend) { + const className = getIdentifierFromEntityNameExpression(extend.expression); + if (className && name.escapedText !== className.escapedText) { + error(name, Diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, idText(node.tagName), idText(name), idText(className)); } } + } - function checkJSDocAccessibilityModifiers(node: JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag): void { - const host = getJSDocHost(node); - if (host && isPrivateIdentifierClassElementDeclaration(host)) { - error(node, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); - } + function checkJSDocAccessibilityModifiers(node: JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag): void { + const host = getJSDocHost(node); + if (host && isPrivateIdentifierClassElementDeclaration(host)) { + error(node, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); } + } - function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier | PrivateIdentifier; - function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined; - function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined { - switch (node.kind) { - case SyntaxKind.Identifier: - return node as Identifier; - case SyntaxKind.PropertyAccessExpression: - return (node as PropertyAccessExpression).name; - default: - return undefined; - } + function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier | PrivateIdentifier; + function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined; + function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined { + switch (node.kind) { + case SyntaxKind.Identifier: + return node as Identifier; + case SyntaxKind.PropertyAccessExpression: + return (node as PropertyAccessExpression).name; + default: + return undefined; } + } - function checkFunctionOrMethodDeclaration(node: FunctionDeclaration | MethodDeclaration | MethodSignature): void { - checkDecorators(node); - checkSignatureDeclaration(node); - const functionFlags = getFunctionFlags(node); + function checkFunctionOrMethodDeclaration(node: FunctionDeclaration | MethodDeclaration | MethodSignature): void { + checkDecorators(node); + checkSignatureDeclaration(node); + const functionFlags = getFunctionFlags(node); - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name && node.name.kind === SyntaxKind.ComputedPropertyName) { - // This check will account for methods in class/interface declarations, - // as well as accessors in classes/object literals - checkComputedPropertyName(node.name); - } + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name && node.name.kind === SyntaxKind.ComputedPropertyName) { + // This check will account for methods in class/interface declarations, + // as well as accessors in classes/object literals + checkComputedPropertyName(node.name); + } - if (hasBindableName(node)) { - // first we want to check the local symbol that contain this declaration - // - if node.localSymbol !== undefined - this is current declaration is exported and localSymbol points to the local symbol - // - if node.localSymbol === undefined - this node is non-exported so we can just pick the result of getSymbolOfNode - const symbol = getSymbolOfNode(node); - const localSymbol = node.localSymbol || symbol; + if (hasBindableName(node)) { + // first we want to check the local symbol that contain this declaration + // - if node.localSymbol !== undefined - this is current declaration is exported and localSymbol points to the local symbol + // - if node.localSymbol === undefined - this node is non-exported so we can just pick the result of getSymbolOfNode + const symbol = getSymbolOfNode(node); + const localSymbol = node.localSymbol || symbol; - // Since the javascript won't do semantic analysis like typescript, - // if the javascript file comes before the typescript file and both contain same name functions, - // checkFunctionOrConstructorSymbol wouldn't be called if we didnt ignore javascript function. - const firstDeclaration = localSymbol.declarations?.find( - // Get first non javascript function declaration - declaration => declaration.kind === node.kind && !(declaration.flags & NodeFlags.JavaScriptFile)); + // Since the javascript won't do semantic analysis like typescript, + // if the javascript file comes before the typescript file and both contain same name functions, + // checkFunctionOrConstructorSymbol wouldn't be called if we didnt ignore javascript function. + const firstDeclaration = localSymbol.declarations?.find( + // Get first non javascript function declaration + declaration => declaration.kind === node.kind && !(declaration.flags & NodeFlags.JavaScriptFile)); - // Only type check the symbol once - if (node === firstDeclaration) { - checkFunctionOrConstructorSymbol(localSymbol); - } + // Only type check the symbol once + if (node === firstDeclaration) { + checkFunctionOrConstructorSymbol(localSymbol); + } - if (symbol.parent) { - // run check on export symbol to check that modifiers agree across all exported declarations - checkFunctionOrConstructorSymbol(symbol); - } + if (symbol.parent) { + // run check on export symbol to check that modifiers agree across all exported declarations + checkFunctionOrConstructorSymbol(symbol); } + } - const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body; - checkSourceElement(body); - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); + const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body; + checkSourceElement(body); + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); - addLazyDiagnostic(checkFunctionOrMethodDeclarationDiagnostics); + addLazyDiagnostic(checkFunctionOrMethodDeclarationDiagnostics); - // A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature - if (isInJSFile(node)) { - const typeTag = getJSDocTypeTag(node); - if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) { - error(typeTag.typeExpression.type, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); - } + // A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature + if (isInJSFile(node)) { + const typeTag = getJSDocTypeTag(node); + if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) { + error(typeTag.typeExpression.type, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); } + } - function checkFunctionOrMethodDeclarationDiagnostics() { - if (!getEffectiveReturnTypeNode(node)) { - // Report an implicit any error if there is no body, no explicit return type, and node is not a private method - // in an ambient context - if (nodeIsMissing(body) && !isPrivateWithinAmbient(node)) { - reportImplicitAny(node, anyType); - } + function checkFunctionOrMethodDeclarationDiagnostics() { + if (!getEffectiveReturnTypeNode(node)) { + // Report an implicit any error if there is no body, no explicit return type, and node is not a private method + // in an ambient context + if (nodeIsMissing(body) && !isPrivateWithinAmbient(node)) { + reportImplicitAny(node, anyType); + } - if (functionFlags & FunctionFlags.Generator && nodeIsPresent(body)) { - // A generator with a body and no type annotation can still cause errors. It can error if the - // yielded values have no common supertype, or it can give an implicit any error if it has no - // yielded values. The only way to trigger these errors is to try checking its return type. - getReturnTypeOfSignature(getSignatureFromDeclaration(node)); - } + if (functionFlags & FunctionFlags.Generator && nodeIsPresent(body)) { + // A generator with a body and no type annotation can still cause errors. It can error if the + // yielded values have no common supertype, or it can give an implicit any error if it has no + // yielded values. The only way to trigger these errors is to try checking its return type. + getReturnTypeOfSignature(getSignatureFromDeclaration(node)); } } } + } - function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void { - addLazyDiagnostic(registerForUnusedIdentifiersCheckDiagnostics); + function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void { + addLazyDiagnostic(registerForUnusedIdentifiersCheckDiagnostics); - function registerForUnusedIdentifiersCheckDiagnostics() { - // May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`. - const sourceFile = getSourceFileOfNode(node); - let potentiallyUnusedIdentifiers = allPotentiallyUnusedIdentifiers.get(sourceFile.path); - if (!potentiallyUnusedIdentifiers) { - potentiallyUnusedIdentifiers = []; - allPotentiallyUnusedIdentifiers.set(sourceFile.path, potentiallyUnusedIdentifiers); - } - // TODO: GH#22580 - // Debug.assert(addToSeen(seenPotentiallyUnusedIdentifiers, getNodeId(node)), "Adding potentially-unused identifier twice"); - potentiallyUnusedIdentifiers.push(node); + function registerForUnusedIdentifiersCheckDiagnostics() { + // May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`. + const sourceFile = getSourceFileOfNode(node); + let potentiallyUnusedIdentifiers = allPotentiallyUnusedIdentifiers.get(sourceFile.path); + if (!potentiallyUnusedIdentifiers) { + potentiallyUnusedIdentifiers = []; + allPotentiallyUnusedIdentifiers.set(sourceFile.path, potentiallyUnusedIdentifiers); } + // TODO: GH#22580 + // Debug.assert(addToSeen(seenPotentiallyUnusedIdentifiers, getNodeId(node)), "Adding potentially-unused identifier twice"); + potentiallyUnusedIdentifiers.push(node); } + } - type PotentiallyUnusedIdentifier = - | SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration - | Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement - | Exclude | TypeAliasDeclaration - | InferTypeNode; + type PotentiallyUnusedIdentifier = + | SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration + | Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement + | Exclude | TypeAliasDeclaration + | InferTypeNode; - function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: readonly PotentiallyUnusedIdentifier[], addDiagnostic: AddUnusedDiagnostic) { - for (const node of potentiallyUnusedIdentifiers) { - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - checkUnusedClassMembers(node, addDiagnostic); - checkUnusedTypeParameters(node, addDiagnostic); - break; - case SyntaxKind.SourceFile: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.Block: - case SyntaxKind.CaseBlock: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: + function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: readonly PotentiallyUnusedIdentifier[], addDiagnostic: AddUnusedDiagnostic) { + for (const node of potentiallyUnusedIdentifiers) { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + checkUnusedClassMembers(node, addDiagnostic); + checkUnusedTypeParameters(node, addDiagnostic); + break; + case SyntaxKind.SourceFile: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.Block: + case SyntaxKind.CaseBlock: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + checkUnusedLocalsAndParameters(node, addDiagnostic); + break; + case SyntaxKind.Constructor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + if (node.body) { // Don't report unused parameters in overloads checkUnusedLocalsAndParameters(node, addDiagnostic); - break; - case SyntaxKind.Constructor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - if (node.body) { // Don't report unused parameters in overloads - checkUnusedLocalsAndParameters(node, addDiagnostic); - } - checkUnusedTypeParameters(node, addDiagnostic); - break; - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - checkUnusedTypeParameters(node, addDiagnostic); - break; - case SyntaxKind.InferType: - checkUnusedInferTypeParameter(node, addDiagnostic); - break; - default: - Debug.assertNever(node, "Node should not have been registered for unused identifiers check"); - } + } + checkUnusedTypeParameters(node, addDiagnostic); + break; + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + checkUnusedTypeParameters(node, addDiagnostic); + break; + case SyntaxKind.InferType: + checkUnusedInferTypeParameter(node, addDiagnostic); + break; + default: + Debug.assertNever(node, "Node should not have been registered for unused identifiers check"); } } + } - function errorUnusedLocal(declaration: Declaration, name: string, addDiagnostic: AddUnusedDiagnostic) { - const node = getNameOfDeclaration(declaration) || declaration; - const message = isTypeDeclaration(declaration) ? Diagnostics._0_is_declared_but_never_used : Diagnostics._0_is_declared_but_its_value_is_never_read; - addDiagnostic(declaration, UnusedKind.Local, createDiagnosticForNode(node, message, name)); - } + function errorUnusedLocal(declaration: Declaration, name: string, addDiagnostic: AddUnusedDiagnostic) { + const node = getNameOfDeclaration(declaration) || declaration; + const message = isTypeDeclaration(declaration) ? Diagnostics._0_is_declared_but_never_used : Diagnostics._0_is_declared_but_its_value_is_never_read; + addDiagnostic(declaration, UnusedKind.Local, createDiagnosticForNode(node, message, name)); + } - function isIdentifierThatStartsWithUnderscore(node: Node) { - return isIdentifier(node) && idText(node).charCodeAt(0) === CharacterCodes._; - } + function isIdentifierThatStartsWithUnderscore(node: Node) { + return isIdentifier(node) && idText(node).charCodeAt(0) === CharacterCodes._; + } - function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression, addDiagnostic: AddUnusedDiagnostic): void { - for (const member of node.members) { - switch (member.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - if (member.kind === SyntaxKind.SetAccessor && member.symbol.flags & SymbolFlags.GetAccessor) { - // Already would have reported an error on the getter. - break; - } - const symbol = getSymbolOfNode(member); - if (!symbol.isReferenced - && (hasEffectiveModifier(member, ModifierFlags.Private) || isNamedDeclaration(member) && isPrivateIdentifier(member.name)) - && !(member.flags & NodeFlags.Ambient)) { - addDiagnostic(member, UnusedKind.Local, createDiagnosticForNode(member.name!, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolToString(symbol))); - } + function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression, addDiagnostic: AddUnusedDiagnostic): void { + for (const member of node.members) { + switch (member.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + if (member.kind === SyntaxKind.SetAccessor && member.symbol.flags & SymbolFlags.GetAccessor) { + // Already would have reported an error on the getter. break; - case SyntaxKind.Constructor: - for (const parameter of (member as ConstructorDeclaration).parameters) { - if (!parameter.symbol.isReferenced && hasSyntacticModifier(parameter, ModifierFlags.Private)) { - addDiagnostic(parameter, UnusedKind.Local, createDiagnosticForNode(parameter.name, Diagnostics.Property_0_is_declared_but_its_value_is_never_read, symbolName(parameter.symbol))); - } + } + const symbol = getSymbolOfNode(member); + if (!symbol.isReferenced + && (hasEffectiveModifier(member, ModifierFlags.Private) || isNamedDeclaration(member) && isPrivateIdentifier(member.name)) + && !(member.flags & NodeFlags.Ambient)) { + addDiagnostic(member, UnusedKind.Local, createDiagnosticForNode(member.name!, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolToString(symbol))); + } + break; + case SyntaxKind.Constructor: + for (const parameter of (member as ConstructorDeclaration).parameters) { + if (!parameter.symbol.isReferenced && hasSyntacticModifier(parameter, ModifierFlags.Private)) { + addDiagnostic(parameter, UnusedKind.Local, createDiagnosticForNode(parameter.name, Diagnostics.Property_0_is_declared_but_its_value_is_never_read, symbolName(parameter.symbol))); } - break; - case SyntaxKind.IndexSignature: - case SyntaxKind.SemicolonClassElement: - case SyntaxKind.ClassStaticBlockDeclaration: - // Can't be private - break; - default: - Debug.fail("Unexpected class member"); - } + } + break; + case SyntaxKind.IndexSignature: + case SyntaxKind.SemicolonClassElement: + case SyntaxKind.ClassStaticBlockDeclaration: + // Can't be private + break; + default: + Debug.fail("Unexpected class member"); } } + } - function checkUnusedInferTypeParameter(node: InferTypeNode, addDiagnostic: AddUnusedDiagnostic): void { - const { typeParameter } = node; - if (isTypeParameterUnused(typeParameter)) { - addDiagnostic(node, UnusedKind.Parameter, createDiagnosticForNode(node, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(typeParameter.name))); - } + function checkUnusedInferTypeParameter(node: InferTypeNode, addDiagnostic: AddUnusedDiagnostic): void { + const { typeParameter } = node; + if (isTypeParameterUnused(typeParameter)) { + addDiagnostic(node, UnusedKind.Parameter, createDiagnosticForNode(node, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(typeParameter.name))); } + } - function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration, addDiagnostic: AddUnusedDiagnostic): void { - // Only report errors on the last declaration for the type parameter container; - // this ensures that all uses have been accounted for. - const declarations = getSymbolOfNode(node).declarations; - if (!declarations || last(declarations) !== node) return; - - const typeParameters = getEffectiveTypeParameterDeclarations(node); - const seenParentsWithEveryUnused = new Set(); - - for (const typeParameter of typeParameters) { - if (!isTypeParameterUnused(typeParameter)) continue; - - const name = idText(typeParameter.name); - const { parent } = typeParameter; - if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) { - if (tryAddToSet(seenParentsWithEveryUnused, parent)) { - const sourceFile = getSourceFileOfNode(parent); - const range = isJSDocTemplateTag(parent) - // Whole @template tag - ? rangeOfNode(parent) - // Include the `<>` in the error message - : rangeOfTypeParameters(sourceFile, parent.typeParameters!); - const only = parent.typeParameters!.length === 1; - //TODO: following line is possible reason for bug #41974, unusedTypeParameters_TemplateTag - const message = only ? Diagnostics._0_is_declared_but_its_value_is_never_read : Diagnostics.All_type_parameters_are_unused; - const arg0 = only ? name : undefined; - addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, message, arg0)); - } - } - else { + function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration, addDiagnostic: AddUnusedDiagnostic): void { + // Only report errors on the last declaration for the type parameter container; + // this ensures that all uses have been accounted for. + const declarations = getSymbolOfNode(node).declarations; + if (!declarations || last(declarations) !== node) return; + + const typeParameters = getEffectiveTypeParameterDeclarations(node); + const seenParentsWithEveryUnused = new Set(); + + for (const typeParameter of typeParameters) { + if (!isTypeParameterUnused(typeParameter)) continue; + + const name = idText(typeParameter.name); + const { parent } = typeParameter; + if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) { + if (tryAddToSet(seenParentsWithEveryUnused, parent)) { + const sourceFile = getSourceFileOfNode(parent); + const range = isJSDocTemplateTag(parent) + // Whole @template tag + ? rangeOfNode(parent) + // Include the `<>` in the error message + : rangeOfTypeParameters(sourceFile, parent.typeParameters!); + const only = parent.typeParameters!.length === 1; //TODO: following line is possible reason for bug #41974, unusedTypeParameters_TemplateTag - addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name)); + const message = only ? Diagnostics._0_is_declared_but_its_value_is_never_read : Diagnostics.All_type_parameters_are_unused; + const arg0 = only ? name : undefined; + addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, message, arg0)); } } - } - function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean { - return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name); - } - - function addToGroup(map: ESMap, key: K, value: V, getKey: (key: K) => number | string): void { - const keyString = String(getKey(key)); - const group = map.get(keyString); - if (group) { - group[1].push(value); - } else { - map.set(keyString, [key, [value]]); + //TODO: following line is possible reason for bug #41974, unusedTypeParameters_TemplateTag + addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name)); } } + } + function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean { + return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name); + } - function tryGetRootParameterDeclaration(node: Node): ParameterDeclaration | undefined { - return tryCast(getRootDeclaration(node), isParameter); + function addToGroup(map: ESMap, key: K, value: V, getKey: (key: K) => number | string): void { + const keyString = String(getKey(key)); + const group = map.get(keyString); + if (group) { + group[1].push(value); + } + else { + map.set(keyString, [key, [value]]); } + } - function isValidUnusedLocalDeclaration(declaration: Declaration): boolean { - if (isBindingElement(declaration)) { - if (isObjectBindingPattern(declaration.parent)) { - /** - * ignore starts with underscore names _ - * const { a: _a } = { a: 1 } - */ - return !!(declaration.propertyName && isIdentifierThatStartsWithUnderscore(declaration.name)); - } - return isIdentifierThatStartsWithUnderscore(declaration.name); + function tryGetRootParameterDeclaration(node: Node): ParameterDeclaration | undefined { + return tryCast(getRootDeclaration(node), isParameter); + } + + function isValidUnusedLocalDeclaration(declaration: Declaration): boolean { + if (isBindingElement(declaration)) { + if (isObjectBindingPattern(declaration.parent)) { + /** + * ignore starts with underscore names _ + * const { a: _a } = { a: 1 } + */ + return !!(declaration.propertyName && isIdentifierThatStartsWithUnderscore(declaration.name)); } - return isAmbientModule(declaration) || - (isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!); + return isIdentifierThatStartsWithUnderscore(declaration.name); } + return isAmbientModule(declaration) || + (isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!); + } - function checkUnusedLocalsAndParameters(nodeWithLocals: Node, addDiagnostic: AddUnusedDiagnostic): void { - // Ideally we could use the ImportClause directly as a key, but must wait until we have full ES6 maps. So must store key along with value. - const unusedImports = new Map(); - const unusedDestructures = new Map(); - const unusedVariables = new Map(); - nodeWithLocals.locals!.forEach(local => { - // If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`. - // If it's a type parameter merged with a parameter, check if the parameter-side is used. - if (local.flags & SymbolFlags.TypeParameter ? !(local.flags & SymbolFlags.Variable && !(local.isReferenced! & SymbolFlags.Variable)) : local.isReferenced || local.exportSymbol) { - return; - } + function checkUnusedLocalsAndParameters(nodeWithLocals: Node, addDiagnostic: AddUnusedDiagnostic): void { + // Ideally we could use the ImportClause directly as a key, but must wait until we have full ES6 maps. So must store key along with value. + const unusedImports = new Map(); + const unusedDestructures = new Map(); + const unusedVariables = new Map(); + nodeWithLocals.locals!.forEach(local => { + // If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`. + // If it's a type parameter merged with a parameter, check if the parameter-side is used. + if (local.flags & SymbolFlags.TypeParameter ? !(local.flags & SymbolFlags.Variable && !(local.isReferenced! & SymbolFlags.Variable)) : local.isReferenced || local.exportSymbol) { + return; + } - if (local.declarations) { - for (const declaration of local.declarations) { - if (isValidUnusedLocalDeclaration(declaration)) { - continue; - } + if (local.declarations) { + for (const declaration of local.declarations) { + if (isValidUnusedLocalDeclaration(declaration)) { + continue; + } - if (isImportedDeclaration(declaration)) { - addToGroup(unusedImports, importClauseFromImported(declaration), declaration, getNodeId); + if (isImportedDeclaration(declaration)) { + addToGroup(unusedImports, importClauseFromImported(declaration), declaration, getNodeId); + } + else if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) { + // In `{ a, ...b }, `a` is considered used since it removes a property from `b`. `b` may still be unused though. + const lastElement = last(declaration.parent.elements); + if (declaration === lastElement || !last(declaration.parent.elements).dotDotDotToken) { + addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); } - else if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) { - // In `{ a, ...b }, `a` is considered used since it removes a property from `b`. `b` may still be unused though. - const lastElement = last(declaration.parent.elements); - if (declaration === lastElement || !last(declaration.parent.elements).dotDotDotToken) { - addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); + } + else if (isVariableDeclaration(declaration)) { + addToGroup(unusedVariables, declaration.parent, declaration, getNodeId); + } + else { + const parameter = local.valueDeclaration && tryGetRootParameterDeclaration(local.valueDeclaration); + const name = local.valueDeclaration && getNameOfDeclaration(local.valueDeclaration); + if (parameter && name) { + if (!isParameterPropertyDeclaration(parameter, parameter.parent) && !parameterIsThisKeyword(parameter) && !isIdentifierThatStartsWithUnderscore(name)) { + if (isBindingElement(declaration) && isArrayBindingPattern(declaration.parent)) { + addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); + } + else { + addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local))); + } } } - else if (isVariableDeclaration(declaration)) { - addToGroup(unusedVariables, declaration.parent, declaration, getNodeId); - } else { - const parameter = local.valueDeclaration && tryGetRootParameterDeclaration(local.valueDeclaration); - const name = local.valueDeclaration && getNameOfDeclaration(local.valueDeclaration); - if (parameter && name) { - if (!isParameterPropertyDeclaration(parameter, parameter.parent) && !parameterIsThisKeyword(parameter) && !isIdentifierThatStartsWithUnderscore(name)) { - if (isBindingElement(declaration) && isArrayBindingPattern(declaration.parent)) { - addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); - } - else { - addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local))); - } - } - } - else { - errorUnusedLocal(declaration, symbolName(local), addDiagnostic); - } + errorUnusedLocal(declaration, symbolName(local), addDiagnostic); } } } - }); - unusedImports.forEach(([importClause, unuseds]) => { - const importDecl = importClause.parent; - const nDeclarations = (importClause.name ? 1 : 0) + - (importClause.namedBindings ? - (importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? 1 : importClause.namedBindings.elements.length) - : 0); - if (nDeclarations === unuseds.length) { - addDiagnostic(importDecl, UnusedKind.Local, unuseds.length === 1 - ? createDiagnosticForNode(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(first(unuseds).name!)) - : createDiagnosticForNode(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused)); - } - else { - for (const unused of unuseds) errorUnusedLocal(unused, idText(unused.name!), addDiagnostic); - } - }); - unusedDestructures.forEach(([bindingPattern, bindingElements]) => { - const kind = tryGetRootParameterDeclaration(bindingPattern.parent) ? UnusedKind.Parameter : UnusedKind.Local; - if (bindingPattern.elements.length === bindingElements.length) { - if (bindingElements.length === 1 && bindingPattern.parent.kind === SyntaxKind.VariableDeclaration && bindingPattern.parent.parent.kind === SyntaxKind.VariableDeclarationList) { - addToGroup(unusedVariables, bindingPattern.parent.parent, bindingPattern.parent, getNodeId); - } - else { - addDiagnostic(bindingPattern, kind, bindingElements.length === 1 - ? createDiagnosticForNode(bindingPattern, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(bindingElements).name)) - : createDiagnosticForNode(bindingPattern, Diagnostics.All_destructured_elements_are_unused)); - } + } + }); + unusedImports.forEach(([importClause, unuseds]) => { + const importDecl = importClause.parent; + const nDeclarations = (importClause.name ? 1 : 0) + + (importClause.namedBindings ? + (importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? 1 : importClause.namedBindings.elements.length) + : 0); + if (nDeclarations === unuseds.length) { + addDiagnostic(importDecl, UnusedKind.Local, unuseds.length === 1 + ? createDiagnosticForNode(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(first(unuseds).name!)) + : createDiagnosticForNode(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused)); + } + else { + for (const unused of unuseds) errorUnusedLocal(unused, idText(unused.name!), addDiagnostic); + } + }); + unusedDestructures.forEach(([bindingPattern, bindingElements]) => { + const kind = tryGetRootParameterDeclaration(bindingPattern.parent) ? UnusedKind.Parameter : UnusedKind.Local; + if (bindingPattern.elements.length === bindingElements.length) { + if (bindingElements.length === 1 && bindingPattern.parent.kind === SyntaxKind.VariableDeclaration && bindingPattern.parent.parent.kind === SyntaxKind.VariableDeclarationList) { + addToGroup(unusedVariables, bindingPattern.parent.parent, bindingPattern.parent, getNodeId); } else { - for (const e of bindingElements) { - addDiagnostic(e, kind, createDiagnosticForNode(e, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(e.name))); - } + addDiagnostic(bindingPattern, kind, bindingElements.length === 1 + ? createDiagnosticForNode(bindingPattern, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(bindingElements).name)) + : createDiagnosticForNode(bindingPattern, Diagnostics.All_destructured_elements_are_unused)); } - }); - unusedVariables.forEach(([declarationList, declarations]) => { - if (declarationList.declarations.length === declarations.length) { - addDiagnostic(declarationList, UnusedKind.Local, declarations.length === 1 - ? createDiagnosticForNode(first(declarations).name, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(declarations).name)) - : createDiagnosticForNode(declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList, Diagnostics.All_variables_are_unused)); + } + else { + for (const e of bindingElements) { + addDiagnostic(e, kind, createDiagnosticForNode(e, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(e.name))); } - else { - for (const decl of declarations) { - addDiagnostic(decl, UnusedKind.Local, createDiagnosticForNode(decl, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(decl.name))); - } + } + }); + unusedVariables.forEach(([declarationList, declarations]) => { + if (declarationList.declarations.length === declarations.length) { + addDiagnostic(declarationList, UnusedKind.Local, declarations.length === 1 + ? createDiagnosticForNode(first(declarations).name, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(declarations).name)) + : createDiagnosticForNode(declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList, Diagnostics.All_variables_are_unused)); + } + else { + for (const decl of declarations) { + addDiagnostic(decl, UnusedKind.Local, createDiagnosticForNode(decl, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(decl.name))); } - }); - } + } + }); + } - function checkPotentialUncheckedRenamedBindingElementsInTypes() { - for (const node of potentialUnusedRenamedBindingElementsInTypes) { - if (!getSymbolOfNode(node)?.isReferenced) { - const wrappingDeclaration = walkUpBindingElementsAndPatterns(node); - Debug.assert(isParameterDeclaration(wrappingDeclaration), "Only parameter declaration should be checked here"); - const diagnostic = createDiagnosticForNode(node.name, Diagnostics._0_is_an_unused_renaming_of_1_Did_you_intend_to_use_it_as_a_type_annotation, declarationNameToString(node.name), declarationNameToString(node.propertyName)); - if (!wrappingDeclaration.type) { - // entire parameter does not have type annotation, suggest adding an annotation - addRelatedInfo( - diagnostic, - createFileDiagnostic(getSourceFileOfNode(wrappingDeclaration), wrappingDeclaration.end, 1, Diagnostics.We_can_only_write_a_type_for_0_by_adding_a_type_for_the_entire_parameter_here, declarationNameToString(node.propertyName)) - ); - } - diagnostics.add(diagnostic); + function checkPotentialUncheckedRenamedBindingElementsInTypes() { + for (const node of potentialUnusedRenamedBindingElementsInTypes) { + if (!getSymbolOfNode(node)?.isReferenced) { + const wrappingDeclaration = walkUpBindingElementsAndPatterns(node); + Debug.assert(isParameterDeclaration(wrappingDeclaration), "Only parameter declaration should be checked here"); + const diagnostic = createDiagnosticForNode(node.name, Diagnostics._0_is_an_unused_renaming_of_1_Did_you_intend_to_use_it_as_a_type_annotation, declarationNameToString(node.name), declarationNameToString(node.propertyName)); + if (!wrappingDeclaration.type) { + // entire parameter does not have type annotation, suggest adding an annotation + addRelatedInfo( + diagnostic, + createFileDiagnostic(getSourceFileOfNode(wrappingDeclaration), wrappingDeclaration.end, 1, Diagnostics.We_can_only_write_a_type_for_0_by_adding_a_type_for_the_entire_parameter_here, declarationNameToString(node.propertyName)) + ); } + diagnostics.add(diagnostic); } } + } - function bindingNameText(name: BindingName): string { - switch (name.kind) { - case SyntaxKind.Identifier: - return idText(name); - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ObjectBindingPattern: - return bindingNameText(cast(first(name.elements), isBindingElement).name); - default: - return Debug.assertNever(name); - } + function bindingNameText(name: BindingName): string { + switch (name.kind) { + case SyntaxKind.Identifier: + return idText(name); + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ObjectBindingPattern: + return bindingNameText(cast(first(name.elements), isBindingElement).name); + default: + return Debug.assertNever(name); } + } + + type ImportedDeclaration = ImportClause | ImportSpecifier | NamespaceImport; + function isImportedDeclaration(node: Node): node is ImportedDeclaration { + return node.kind === SyntaxKind.ImportClause || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.NamespaceImport; + } + function importClauseFromImported(decl: ImportedDeclaration): ImportClause { + return decl.kind === SyntaxKind.ImportClause ? decl : decl.kind === SyntaxKind.NamespaceImport ? decl.parent : decl.parent.parent; + } - type ImportedDeclaration = ImportClause | ImportSpecifier | NamespaceImport; - function isImportedDeclaration(node: Node): node is ImportedDeclaration { - return node.kind === SyntaxKind.ImportClause || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.NamespaceImport; + function checkBlock(node: Block) { + // Grammar checking for SyntaxKind.Block + if (node.kind === SyntaxKind.Block) { + checkGrammarStatementInAmbientContext(node); } - function importClauseFromImported(decl: ImportedDeclaration): ImportClause { - return decl.kind === SyntaxKind.ImportClause ? decl : decl.kind === SyntaxKind.NamespaceImport ? decl.parent : decl.parent.parent; + if (isFunctionOrModuleBlock(node)) { + const saveFlowAnalysisDisabled = flowAnalysisDisabled; + forEach(node.statements, checkSourceElement); + flowAnalysisDisabled = saveFlowAnalysisDisabled; } - - function checkBlock(node: Block) { - // Grammar checking for SyntaxKind.Block - if (node.kind === SyntaxKind.Block) { - checkGrammarStatementInAmbientContext(node); - } - if (isFunctionOrModuleBlock(node)) { - const saveFlowAnalysisDisabled = flowAnalysisDisabled; - forEach(node.statements, checkSourceElement); - flowAnalysisDisabled = saveFlowAnalysisDisabled; - } - else { - forEach(node.statements, checkSourceElement); - } - if (node.locals) { - registerForUnusedIdentifiersCheck(node); - } + else { + forEach(node.statements, checkSourceElement); } + if (node.locals) { + registerForUnusedIdentifiersCheck(node); + } + } - function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) { - // no rest parameters \ declaration context \ overload - no codegen impact - if (languageVersion >= ScriptTarget.ES2015 || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((node as FunctionLikeDeclaration).body)) { - return; - } - - forEach(node.parameters, p => { - if (p.name && !isBindingPattern(p.name) && p.name.escapedText === argumentsSymbol.escapedName) { - errorSkippedOn("noEmit", p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters); - } - }); + function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) { + // no rest parameters \ declaration context \ overload - no codegen impact + if (languageVersion >= ScriptTarget.ES2015 || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((node as FunctionLikeDeclaration).body)) { + return; } - /** - * Checks whether an {@link Identifier}, in the context of another {@link Node}, would collide with a runtime value - * of {@link name} in an outer scope. This is used to check for collisions for downlevel transformations that - * require names like `Object`, `Promise`, `Reflect`, `require`, `exports`, etc. - */ - function needCollisionCheckForIdentifier(node: Node, identifier: Identifier | undefined, name: string): boolean { - if (identifier?.escapedText !== name) { - return false; + forEach(node.parameters, p => { + if (p.name && !isBindingPattern(p.name) && p.name.escapedText === argumentsSymbol.escapedName) { + errorSkippedOn("noEmit", p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters); } + }); + } - if (node.kind === SyntaxKind.PropertyDeclaration || - node.kind === SyntaxKind.PropertySignature || - node.kind === SyntaxKind.MethodDeclaration || - node.kind === SyntaxKind.MethodSignature || - node.kind === SyntaxKind.GetAccessor || - node.kind === SyntaxKind.SetAccessor || - node.kind === SyntaxKind.PropertyAssignment) { - // it is ok to have member named '_super', '_this', `Promise`, etc. - member access is always qualified - return false; - } + /** + * Checks whether an {@link Identifier}, in the context of another {@link Node}, would collide with a runtime value + * of {@link name} in an outer scope. This is used to check for collisions for downlevel transformations that + * require names like `Object`, `Promise`, `Reflect`, `require`, `exports`, etc. + */ + function needCollisionCheckForIdentifier(node: Node, identifier: Identifier | undefined, name: string): boolean { + if (identifier?.escapedText !== name) { + return false; + } - if (node.flags & NodeFlags.Ambient) { - // ambient context - no codegen impact - return false; - } + if (node.kind === SyntaxKind.PropertyDeclaration || + node.kind === SyntaxKind.PropertySignature || + node.kind === SyntaxKind.MethodDeclaration || + node.kind === SyntaxKind.MethodSignature || + node.kind === SyntaxKind.GetAccessor || + node.kind === SyntaxKind.SetAccessor || + node.kind === SyntaxKind.PropertyAssignment) { + // it is ok to have member named '_super', '_this', `Promise`, etc. - member access is always qualified + return false; + } - if (isImportClause(node) || isImportEqualsDeclaration(node) || isImportSpecifier(node)) { - // type-only imports do not require collision checks against runtime values. - if (isTypeOnlyImportOrExportDeclaration(node)) { - return false; - } - } + if (node.flags & NodeFlags.Ambient) { + // ambient context - no codegen impact + return false; + } - const root = getRootDeclaration(node); - if (isParameter(root) && nodeIsMissing((root.parent as FunctionLikeDeclaration).body)) { - // just an overload - no codegen impact + if (isImportClause(node) || isImportEqualsDeclaration(node) || isImportSpecifier(node)) { + // type-only imports do not require collision checks against runtime values. + if (isTypeOnlyImportOrExportDeclaration(node)) { return false; } - - return true; } - // this function will run after checking the source file so 'CaptureThis' is correct for all nodes - function checkIfThisIsCapturedInEnclosingScope(node: Node): void { - findAncestor(node, current => { - if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureThis) { - const isDeclaration = node.kind !== SyntaxKind.Identifier; - if (isDeclaration) { - error(getNameOfDeclaration(node as Declaration), Diagnostics.Duplicate_identifier_this_Compiler_uses_variable_declaration_this_to_capture_this_reference); - } - else { - error(node, Diagnostics.Expression_resolves_to_variable_declaration_this_that_compiler_uses_to_capture_this_reference); - } - return true; - } - return false; - }); + const root = getRootDeclaration(node); + if (isParameter(root) && nodeIsMissing((root.parent as FunctionLikeDeclaration).body)) { + // just an overload - no codegen impact + return false; } - function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { - findAncestor(node, current => { - if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { - const isDeclaration = node.kind !== SyntaxKind.Identifier; - if (isDeclaration) { - error(getNameOfDeclaration(node as Declaration), Diagnostics.Duplicate_identifier_newTarget_Compiler_uses_variable_declaration_newTarget_to_capture_new_target_meta_property_reference); - } - else { - error(node, Diagnostics.Expression_resolves_to_variable_declaration_newTarget_that_compiler_uses_to_capture_new_target_meta_property_reference); - } - return true; - } - return false; - }); - } + return true; + } - function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier | undefined) { - // No need to check for require or exports for ES6 modules and later - if (moduleKind >= ModuleKind.ES2015 && !(moduleKind >= ModuleKind.Node16 && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { - return; + // this function will run after checking the source file so 'CaptureThis' is correct for all nodes + function checkIfThisIsCapturedInEnclosingScope(node: Node): void { + findAncestor(node, current => { + if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureThis) { + const isDeclaration = node.kind !== SyntaxKind.Identifier; + if (isDeclaration) { + error(getNameOfDeclaration(node as Declaration), Diagnostics.Duplicate_identifier_this_Compiler_uses_variable_declaration_this_to_capture_this_reference); + } + else { + error(node, Diagnostics.Expression_resolves_to_variable_declaration_this_that_compiler_uses_to_capture_this_reference); + } + return true; } + return false; + }); + } - if (!name || !needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) { - return; + function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { + findAncestor(node, current => { + if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { + const isDeclaration = node.kind !== SyntaxKind.Identifier; + if (isDeclaration) { + error(getNameOfDeclaration(node as Declaration), Diagnostics.Duplicate_identifier_newTarget_Compiler_uses_variable_declaration_newTarget_to_capture_new_target_meta_property_reference); + } + else { + error(node, Diagnostics.Expression_resolves_to_variable_declaration_newTarget_that_compiler_uses_to_capture_new_target_meta_property_reference); + } + return true; } + return false; + }); + } - // Uninstantiated modules shouldnt do this check - if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { - return; - } + function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier | undefined) { + // No need to check for require or exports for ES6 modules and later + if (moduleKind >= ModuleKind.ES2015 && !(moduleKind >= ModuleKind.Node16 && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { + return; + } - // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent - const parent = getDeclarationContainer(node); - if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent as SourceFile)) { - // If the declaration happens to be in external module, report error that require and exports are reserved keywords - errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module, - declarationNameToString(name), declarationNameToString(name)); - } + if (!name || !needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) { + return; } - function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier | undefined): void { - if (!name || languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) { - return; - } + // Uninstantiated modules shouldnt do this check + if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { + return; + } - // Uninstantiated modules shouldnt do this check - if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { - return; - } + // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent + const parent = getDeclarationContainer(node); + if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent as SourceFile)) { + // If the declaration happens to be in external module, report error that require and exports are reserved keywords + errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module, + declarationNameToString(name), declarationNameToString(name)); + } + } - // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent - const parent = getDeclarationContainer(node); - if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent as SourceFile) && parent.flags & NodeFlags.HasAsyncFunctions) { - // If the declaration happens to be in external module, report error that Promise is a reserved identifier. - errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions, - declarationNameToString(name), declarationNameToString(name)); - } + function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier | undefined): void { + if (!name || languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) { + return; } - function recordPotentialCollisionWithWeakMapSetInGeneratedCode(node: Node, name: Identifier): void { - if (languageVersion <= ScriptTarget.ES2021 - && (needCollisionCheckForIdentifier(node, name, "WeakMap") || needCollisionCheckForIdentifier(node, name, "WeakSet"))) { - potentialWeakMapSetCollisions.push(node); - } + // Uninstantiated modules shouldnt do this check + if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { + return; } - function checkWeakMapSetCollision(node: Node) { - const enclosingBlockScope = getEnclosingBlockScopeContainer(node); - if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) { - Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name) && typeof node.name.escapedText === "string", "The target of a WeakMap/WeakSet collision check should be an identifier"); - errorSkippedOn("noEmit", node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, node.name.escapedText); - } + // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent + const parent = getDeclarationContainer(node); + if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent as SourceFile) && parent.flags & NodeFlags.HasAsyncFunctions) { + // If the declaration happens to be in external module, report error that Promise is a reserved identifier. + errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions, + declarationNameToString(name), declarationNameToString(name)); } + } - function recordPotentialCollisionWithReflectInGeneratedCode(node: Node, name: Identifier | undefined): void { - if (name && languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 - && needCollisionCheckForIdentifier(node, name, "Reflect")) { - potentialReflectCollisions.push(node); - } + function recordPotentialCollisionWithWeakMapSetInGeneratedCode(node: Node, name: Identifier): void { + if (languageVersion <= ScriptTarget.ES2021 + && (needCollisionCheckForIdentifier(node, name, "WeakMap") || needCollisionCheckForIdentifier(node, name, "WeakSet"))) { + potentialWeakMapSetCollisions.push(node); } + } - function checkReflectCollision(node: Node) { - let hasCollision = false; - if (isClassExpression(node)) { - // ClassExpression names don't contribute to their containers, but do matter for any of their block-scoped members. - for (const member of node.members) { - if (getNodeCheckFlags(member) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { - hasCollision = true; - break; - } - } - } - else if (isFunctionExpression(node)) { - // FunctionExpression names don't contribute to their containers, but do matter for their contents - if (getNodeCheckFlags(node) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { + function checkWeakMapSetCollision(node: Node) { + const enclosingBlockScope = getEnclosingBlockScopeContainer(node); + if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) { + Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name) && typeof node.name.escapedText === "string", "The target of a WeakMap/WeakSet collision check should be an identifier"); + errorSkippedOn("noEmit", node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, node.name.escapedText); + } + } + + function recordPotentialCollisionWithReflectInGeneratedCode(node: Node, name: Identifier | undefined): void { + if (name && languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 + && needCollisionCheckForIdentifier(node, name, "Reflect")) { + potentialReflectCollisions.push(node); + } + } + + function checkReflectCollision(node: Node) { + let hasCollision = false; + if (isClassExpression(node)) { + // ClassExpression names don't contribute to their containers, but do matter for any of their block-scoped members. + for (const member of node.members) { + if (getNodeCheckFlags(member) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { hasCollision = true; + break; } } - else { - const container = getEnclosingBlockScopeContainer(node); - if (container && getNodeCheckFlags(container) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { - hasCollision = true; - } + } + else if (isFunctionExpression(node)) { + // FunctionExpression names don't contribute to their containers, but do matter for their contents + if (getNodeCheckFlags(node) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { + hasCollision = true; } - if (hasCollision) { - Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name), "The target of a Reflect collision check should be an identifier"); - errorSkippedOn("noEmit", node, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_when_emitting_super_references_in_static_initializers, - declarationNameToString(node.name), - "Reflect"); + } + else { + const container = getEnclosingBlockScopeContainer(node); + if (container && getNodeCheckFlags(container) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { + hasCollision = true; } } + if (hasCollision) { + Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name), "The target of a Reflect collision check should be an identifier"); + errorSkippedOn("noEmit", node, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_when_emitting_super_references_in_static_initializers, + declarationNameToString(node.name), + "Reflect"); + } + } - function checkCollisionsForDeclarationName(node: Node, name: Identifier | undefined) { - if (!name) return; - checkCollisionWithRequireExportsInGeneratedCode(node, name); - checkCollisionWithGlobalPromiseInGeneratedCode(node, name); - recordPotentialCollisionWithWeakMapSetInGeneratedCode(node, name); - recordPotentialCollisionWithReflectInGeneratedCode(node, name); - if (isClassLike(node)) { - checkTypeNameIsReserved(name, Diagnostics.Class_name_cannot_be_0); - if (!(node.flags & NodeFlags.Ambient)) { - checkClassNameCollisionWithObject(name); - } - } - else if (isEnumDeclaration(node)) { - checkTypeNameIsReserved(name, Diagnostics.Enum_name_cannot_be_0); + function checkCollisionsForDeclarationName(node: Node, name: Identifier | undefined) { + if (!name) return; + checkCollisionWithRequireExportsInGeneratedCode(node, name); + checkCollisionWithGlobalPromiseInGeneratedCode(node, name); + recordPotentialCollisionWithWeakMapSetInGeneratedCode(node, name); + recordPotentialCollisionWithReflectInGeneratedCode(node, name); + if (isClassLike(node)) { + checkTypeNameIsReserved(name, Diagnostics.Class_name_cannot_be_0); + if (!(node.flags & NodeFlags.Ambient)) { + checkClassNameCollisionWithObject(name); } } + else if (isEnumDeclaration(node)) { + checkTypeNameIsReserved(name, Diagnostics.Enum_name_cannot_be_0); + } + } - function checkVarDeclaredNamesNotShadowed(node: VariableDeclaration | BindingElement) { - // - ScriptBody : StatementList - // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList - // also occurs in the VarDeclaredNames of StatementList. - - // - Block : { StatementList } - // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList - // also occurs in the VarDeclaredNames of StatementList. - - // Variable declarations are hoisted to the top of their function scope. They can shadow - // block scoped declarations, which bind tighter. this will not be flagged as duplicate definition - // by the binder as the declaration scope is different. - // A non-initialized declaration is a no-op as the block declaration will resolve before the var - // declaration. the problem is if the declaration has an initializer. this will act as a write to the - // block declared value. this is fine for let, but not const. - // Only consider declarations with initializers, uninitialized const declarations will not - // step on a let/const variable. - // Do not consider const and const declarations, as duplicate block-scoped declarations - // are handled by the binder. - // We are only looking for const declarations that step on let\const declarations from a - // different scope. e.g.: - // { - // const x = 0; // localDeclarationSymbol obtained after name resolution will correspond to this declaration - // const x = 0; // symbol for this declaration will be 'symbol' - // } - - // skip block-scoped variables and parameters - if ((getCombinedNodeFlags(node) & NodeFlags.BlockScoped) !== 0 || isParameterDeclaration(node)) { - return; - } + function checkVarDeclaredNamesNotShadowed(node: VariableDeclaration | BindingElement) { + // - ScriptBody : StatementList + // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList + // also occurs in the VarDeclaredNames of StatementList. + + // - Block : { StatementList } + // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList + // also occurs in the VarDeclaredNames of StatementList. + + // Variable declarations are hoisted to the top of their function scope. They can shadow + // block scoped declarations, which bind tighter. this will not be flagged as duplicate definition + // by the binder as the declaration scope is different. + // A non-initialized declaration is a no-op as the block declaration will resolve before the var + // declaration. the problem is if the declaration has an initializer. this will act as a write to the + // block declared value. this is fine for let, but not const. + // Only consider declarations with initializers, uninitialized const declarations will not + // step on a let/const variable. + // Do not consider const and const declarations, as duplicate block-scoped declarations + // are handled by the binder. + // We are only looking for const declarations that step on let\const declarations from a + // different scope. e.g.: + // { + // const x = 0; // localDeclarationSymbol obtained after name resolution will correspond to this declaration + // const x = 0; // symbol for this declaration will be 'symbol' + // } + + // skip block-scoped variables and parameters + if ((getCombinedNodeFlags(node) & NodeFlags.BlockScoped) !== 0 || isParameterDeclaration(node)) { + return; + } - // skip variable declarations that don't have initializers - // NOTE: in ES6 spec initializer is required in variable declarations where name is binding pattern - // so we'll always treat binding elements as initialized - if (node.kind === SyntaxKind.VariableDeclaration && !node.initializer) { - return; - } + // skip variable declarations that don't have initializers + // NOTE: in ES6 spec initializer is required in variable declarations where name is binding pattern + // so we'll always treat binding elements as initialized + if (node.kind === SyntaxKind.VariableDeclaration && !node.initializer) { + return; + } - const symbol = getSymbolOfNode(node); - if (symbol.flags & SymbolFlags.FunctionScopedVariable) { - if (!isIdentifier(node.name)) return Debug.fail(); - const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); - if (localDeclarationSymbol && - localDeclarationSymbol !== symbol && - localDeclarationSymbol.flags & SymbolFlags.BlockScopedVariable) { - if (getDeclarationNodeFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.BlockScoped) { - const varDeclList = getAncestor(localDeclarationSymbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!; - const container = - varDeclList.parent.kind === SyntaxKind.VariableStatement && varDeclList.parent.parent - ? varDeclList.parent.parent - : undefined; + const symbol = getSymbolOfNode(node); + if (symbol.flags & SymbolFlags.FunctionScopedVariable) { + if (!isIdentifier(node.name)) return Debug.fail(); + const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + if (localDeclarationSymbol && + localDeclarationSymbol !== symbol && + localDeclarationSymbol.flags & SymbolFlags.BlockScopedVariable) { + if (getDeclarationNodeFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.BlockScoped) { + const varDeclList = getAncestor(localDeclarationSymbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!; + const container = + varDeclList.parent.kind === SyntaxKind.VariableStatement && varDeclList.parent.parent + ? varDeclList.parent.parent + : undefined; - // names of block-scoped and function scoped variables can collide only - // if block scoped variable is defined in the function\module\source file scope (because of variable hoisting) - const namesShareScope = - container && - (container.kind === SyntaxKind.Block && isFunctionLike(container.parent) || - container.kind === SyntaxKind.ModuleBlock || - container.kind === SyntaxKind.ModuleDeclaration || - container.kind === SyntaxKind.SourceFile); + // names of block-scoped and function scoped variables can collide only + // if block scoped variable is defined in the function\module\source file scope (because of variable hoisting) + const namesShareScope = + container && + (container.kind === SyntaxKind.Block && isFunctionLike(container.parent) || + container.kind === SyntaxKind.ModuleBlock || + container.kind === SyntaxKind.ModuleDeclaration || + container.kind === SyntaxKind.SourceFile); - // here we know that function scoped variable is shadowed by block scoped one - // if they are defined in the same scope - binder has already reported redeclaration error - // otherwise if variable has an initializer - show error that initialization will fail - // since LHS will be block scoped name instead of function scoped - if (!namesShareScope) { - const name = symbolToString(localDeclarationSymbol); - error(node, Diagnostics.Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1, name, name); - } + // here we know that function scoped variable is shadowed by block scoped one + // if they are defined in the same scope - binder has already reported redeclaration error + // otherwise if variable has an initializer - show error that initialization will fail + // since LHS will be block scoped name instead of function scoped + if (!namesShareScope) { + const name = symbolToString(localDeclarationSymbol); + error(node, Diagnostics.Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1, name, name); } } } } + } + + function convertAutoToAny(type: Type) { + return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type; + } - function convertAutoToAny(type: Type) { - return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type; + // Check variable, parameter, or property declaration + function checkVariableLikeDeclaration(node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement) { + checkDecorators(node); + if (!isBindingElement(node)) { + checkSourceElement(node.type); } - // Check variable, parameter, or property declaration - function checkVariableLikeDeclaration(node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement) { - checkDecorators(node); - if (!isBindingElement(node)) { - checkSourceElement(node.type); - } + // JSDoc `function(string, string): string` syntax results in parameters with no name + if (!node.name) { + return; + } - // JSDoc `function(string, string): string` syntax results in parameters with no name - if (!node.name) { + // For a computed property, just check the initializer and exit + // Do not use hasDynamicName here, because that returns false for well known symbols. + // We want to perform checkComputedPropertyName for all computed properties, including + // well known symbols. + if (node.name.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + if (hasOnlyExpressionInitializer(node) && node.initializer) { + checkExpressionCached(node.initializer); + } + } + + if (isBindingElement(node)) { + if ( + node.propertyName && + isIdentifier(node.name) && + isParameterDeclaration(node) && + nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { + // type F = ({a: string}) => void; + // ^^^^^^ + // variable renaming in function type notation is confusing, + // so we forbid it even if noUnusedLocals is not enabled + potentialUnusedRenamedBindingElementsInTypes.push(node); return; } - // For a computed property, just check the initializer and exit - // Do not use hasDynamicName here, because that returns false for well known symbols. - // We want to perform checkComputedPropertyName for all computed properties, including - // well known symbols. - if (node.name.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.name); - if (hasOnlyExpressionInitializer(node) && node.initializer) { - checkExpressionCached(node.initializer); - } + if (isObjectBindingPattern(node.parent) && node.dotDotDotToken && languageVersion < ScriptTarget.ES2018) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest); } - - if (isBindingElement(node)) { - if ( - node.propertyName && - isIdentifier(node.name) && - isParameterDeclaration(node) && - nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { - // type F = ({a: string}) => void; - // ^^^^^^ - // variable renaming in function type notation is confusing, - // so we forbid it even if noUnusedLocals is not enabled - potentialUnusedRenamedBindingElementsInTypes.push(node); - return; - } - - if (isObjectBindingPattern(node.parent) && node.dotDotDotToken && languageVersion < ScriptTarget.ES2018) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest); - } - // check computed properties inside property names of binding elements - if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.propertyName); - } - - // check private/protected variable access - const parent = node.parent.parent; - const parentCheckMode = node.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal; - const parentType = getTypeForBindingElementParent(parent, parentCheckMode); - const name = node.propertyName || node.name; - if (parentType && !isBindingPattern(name)) { - const exprType = getLiteralTypeFromPropertyName(name); - if (isTypeUsableAsPropertyName(exprType)) { - const nameText = getPropertyNameFromType(exprType); - const property = getPropertyOfType(parentType, nameText); - if (property) { - markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isSelfTypeAccess*/ false); // A destructuring is never a write-only reference. - checkPropertyAccessibility(node, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, /*writing*/ false, parentType, property); - } - } - } + // check computed properties inside property names of binding elements + if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.propertyName); } - // For a binding pattern, check contained binding elements - if (isBindingPattern(node.name)) { - if (node.name.kind === SyntaxKind.ArrayBindingPattern && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); - } - - forEach(node.name.elements, checkSourceElement); - } - // For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body - if (isParameter(node) && node.initializer && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { - error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); - return; - } - // For a binding pattern, validate the initializer and exit - if (isBindingPattern(node.name)) { - const needCheckInitializer = hasOnlyExpressionInitializer(node) && node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement; - const needCheckWidenedType = !some(node.name.elements, not(isOmittedExpression)); - if (needCheckInitializer || needCheckWidenedType) { - // Don't validate for-in initializer as it is already an error - const widenedType = getWidenedTypeForVariableLikeDeclaration(node); - if (needCheckInitializer) { - const initializerType = checkExpressionCached(node.initializer); - if (strictNullChecks && needCheckWidenedType) { - checkNonNullNonVoidType(initializerType, node); - } - else { - checkTypeAssignableToAndOptionallyElaborate(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, node.initializer); - } - } - // check the binding pattern with empty elements - if (needCheckWidenedType) { - if (isArrayBindingPattern(node.name)) { - checkIteratedTypeOrElementType(IterationUse.Destructuring, widenedType, undefinedType, node); - } - else if (strictNullChecks) { - checkNonNullNonVoidType(widenedType, node); - } + // check private/protected variable access + const parent = node.parent.parent; + const parentCheckMode = node.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal; + const parentType = getTypeForBindingElementParent(parent, parentCheckMode); + const name = node.propertyName || node.name; + if (parentType && !isBindingPattern(name)) { + const exprType = getLiteralTypeFromPropertyName(name); + if (isTypeUsableAsPropertyName(exprType)) { + const nameText = getPropertyNameFromType(exprType); + const property = getPropertyOfType(parentType, nameText); + if (property) { + markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isSelfTypeAccess*/ false); // A destructuring is never a write-only reference. + checkPropertyAccessibility(node, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, /*writing*/ false, parentType, property); } } - return; } - // For a commonjs `const x = require`, validate the alias and exit - const symbol = getSymbolOfNode(node); - if (symbol.flags & SymbolFlags.Alias && isVariableDeclarationInitializedToBareOrAccessedRequire(node.kind === SyntaxKind.BindingElement ? node.parent.parent : node)) { - checkAliasSymbol(node as BindingElement | VariableDeclaration); - return; + } + + // For a binding pattern, check contained binding elements + if (isBindingPattern(node.name)) { + if (node.name.kind === SyntaxKind.ArrayBindingPattern && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); } - const type = convertAutoToAny(getTypeOfSymbol(symbol)); - if (node === symbol.valueDeclaration) { - // Node is the primary declaration of the symbol, just validate the initializer + forEach(node.name.elements, checkSourceElement); + } + // For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body + if (isParameter(node) && node.initializer && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { + error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); + return; + } + // For a binding pattern, validate the initializer and exit + if (isBindingPattern(node.name)) { + const needCheckInitializer = hasOnlyExpressionInitializer(node) && node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement; + const needCheckWidenedType = !some(node.name.elements, not(isOmittedExpression)); + if (needCheckInitializer || needCheckWidenedType) { // Don't validate for-in initializer as it is already an error - const initializer = hasOnlyExpressionInitializer(node) && getEffectiveInitializer(node); - if (initializer) { - const isJSObjectLiteralInitializer = isInJSFile(node) && - isObjectLiteralExpression(initializer) && - (initializer.properties.length === 0 || isPrototypeAccess(node.name)) && - !!symbol.exports?.size; - if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { - checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined); + const widenedType = getWidenedTypeForVariableLikeDeclaration(node); + if (needCheckInitializer) { + const initializerType = checkExpressionCached(node.initializer); + if (strictNullChecks && needCheckWidenedType) { + checkNonNullNonVoidType(initializerType, node); + } + else { + checkTypeAssignableToAndOptionallyElaborate(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, node.initializer); } } - if (symbol.declarations && symbol.declarations.length > 1) { - if (some(symbol.declarations, d => d !== node && isVariableLike(d) && !areDeclarationFlagsIdentical(d, node))) { - error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); + // check the binding pattern with empty elements + if (needCheckWidenedType) { + if (isArrayBindingPattern(node.name)) { + checkIteratedTypeOrElementType(IterationUse.Destructuring, widenedType, undefinedType, node); + } + else if (strictNullChecks) { + checkNonNullNonVoidType(widenedType, node); } } } - else { - // Node is a secondary declaration, check that type is identical to primary declaration and check that - // initializer is consistent with type associated with the node - const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node)); + return; + } + // For a commonjs `const x = require`, validate the alias and exit + const symbol = getSymbolOfNode(node); + if (symbol.flags & SymbolFlags.Alias && isVariableDeclarationInitializedToBareOrAccessedRequire(node.kind === SyntaxKind.BindingElement ? node.parent.parent : node)) { + checkAliasSymbol(node as BindingElement | VariableDeclaration); + return; + } - if (!isErrorType(type) && !isErrorType(declarationType) && - !isTypeIdenticalTo(type, declarationType) && - !(symbol.flags & SymbolFlags.Assignment)) { - errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType); - } - if (hasOnlyExpressionInitializer(node) && node.initializer) { - checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined); - } - if (symbol.valueDeclaration && !areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) { - error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); + const type = convertAutoToAny(getTypeOfSymbol(symbol)); + if (node === symbol.valueDeclaration) { + // Node is the primary declaration of the symbol, just validate the initializer + // Don't validate for-in initializer as it is already an error + const initializer = hasOnlyExpressionInitializer(node) && getEffectiveInitializer(node); + if (initializer) { + const isJSObjectLiteralInitializer = isInJSFile(node) && + isObjectLiteralExpression(initializer) && + (initializer.properties.length === 0 || isPrototypeAccess(node.name)) && + !!symbol.exports?.size; + if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { + checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined); } } - if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature) { - // We know we don't have a binding pattern or computed name here - checkExportsOnMergedDeclarations(node); - if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { - checkVarDeclaredNamesNotShadowed(node); + if (symbol.declarations && symbol.declarations.length > 1) { + if (some(symbol.declarations, d => d !== node && isVariableLike(d) && !areDeclarationFlagsIdentical(d, node))) { + error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); } - checkCollisionsForDeclarationName(node, node.name); } } + else { + // Node is a secondary declaration, check that type is identical to primary declaration and check that + // initializer is consistent with type associated with the node + const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node)); - 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; - const declName = declarationNameToString(nextDeclarationName); - const err = error( - nextDeclarationName, - message, - declName, - typeToString(firstType), - typeToString(nextType) - ); - if (firstDeclaration) { - addRelatedInfo(err, - createDiagnosticForNode(firstDeclaration, Diagnostics._0_was_also_declared_here, declName) - ); + if (!isErrorType(type) && !isErrorType(declarationType) && + !isTypeIdenticalTo(type, declarationType) && + !(symbol.flags & SymbolFlags.Assignment)) { + errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType); } - } - - function areDeclarationFlagsIdentical(left: Declaration, right: Declaration) { - if ((left.kind === SyntaxKind.Parameter && right.kind === SyntaxKind.VariableDeclaration) || - (left.kind === SyntaxKind.VariableDeclaration && right.kind === SyntaxKind.Parameter)) { - // Differences in optionality between parameters and variables are allowed. - return true; + if (hasOnlyExpressionInitializer(node) && node.initializer) { + checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined); } - - if (hasQuestionToken(left) !== hasQuestionToken(right)) { - return false; + if (symbol.valueDeclaration && !areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) { + error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); } - - const interestingFlags = ModifierFlags.Private | - ModifierFlags.Protected | - ModifierFlags.Async | - ModifierFlags.Abstract | - ModifierFlags.Readonly | - ModifierFlags.Static; - - return getSelectedEffectiveModifierFlags(left, interestingFlags) === getSelectedEffectiveModifierFlags(right, interestingFlags); } + if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature) { + // We know we don't have a binding pattern or computed name here + checkExportsOnMergedDeclarations(node); + if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { + checkVarDeclaredNamesNotShadowed(node); + } + checkCollisionsForDeclarationName(node, node.name); + } + } - function checkVariableDeclaration(node: VariableDeclaration) { - tracing?.push(tracing.Phase.Check, "checkVariableDeclaration", { kind: node.kind, pos: node.pos, end: node.end, path: (node as TracingNode).tracingPath }); - checkGrammarVariableDeclaration(node); - checkVariableLikeDeclaration(node); - tracing?.pop(); + 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; + const declName = declarationNameToString(nextDeclarationName); + const err = error( + nextDeclarationName, + message, + declName, + typeToString(firstType), + typeToString(nextType) + ); + if (firstDeclaration) { + addRelatedInfo(err, + createDiagnosticForNode(firstDeclaration, Diagnostics._0_was_also_declared_here, declName) + ); } + } - function checkBindingElement(node: BindingElement) { - checkGrammarBindingElement(node); - return checkVariableLikeDeclaration(node); + function areDeclarationFlagsIdentical(left: Declaration, right: Declaration) { + if ((left.kind === SyntaxKind.Parameter && right.kind === SyntaxKind.VariableDeclaration) || + (left.kind === SyntaxKind.VariableDeclaration && right.kind === SyntaxKind.Parameter)) { + // Differences in optionality between parameters and variables are allowed. + return true; } - function checkVariableStatement(node: VariableStatement) { - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) checkGrammarForDisallowedLetOrConstStatement(node); - forEach(node.declarationList.declarations, checkSourceElement); + if (hasQuestionToken(left) !== hasQuestionToken(right)) { + return false; } - function checkExpressionStatement(node: ExpressionStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + const interestingFlags = ModifierFlags.Private | + ModifierFlags.Protected | + ModifierFlags.Async | + ModifierFlags.Abstract | + ModifierFlags.Readonly | + ModifierFlags.Static; - checkExpression(node.expression); - } + return getSelectedEffectiveModifierFlags(left, interestingFlags) === getSelectedEffectiveModifierFlags(right, interestingFlags); + } - function checkIfStatement(node: IfStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); - const type = checkTruthinessExpression(node.expression); - checkTestingKnownTruthyCallableOrAwaitableType(node.expression, type, node.thenStatement); - checkSourceElement(node.thenStatement); + function checkVariableDeclaration(node: VariableDeclaration) { + tracing?.push(tracing.Phase.Check, "checkVariableDeclaration", { kind: node.kind, pos: node.pos, end: node.end, path: (node as TracingNode).tracingPath }); + checkGrammarVariableDeclaration(node); + checkVariableLikeDeclaration(node); + tracing?.pop(); + } - if (node.thenStatement.kind === SyntaxKind.EmptyStatement) { - error(node.thenStatement, Diagnostics.The_body_of_an_if_statement_cannot_be_the_empty_statement); - } + function checkBindingElement(node: BindingElement) { + checkGrammarBindingElement(node); + return checkVariableLikeDeclaration(node); + } + + function checkVariableStatement(node: VariableStatement) { + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) checkGrammarForDisallowedLetOrConstStatement(node); + forEach(node.declarationList.declarations, checkSourceElement); + } - checkSourceElement(node.elseStatement); + function checkExpressionStatement(node: ExpressionStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + checkExpression(node.expression); + } + + function checkIfStatement(node: IfStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + const type = checkTruthinessExpression(node.expression); + checkTestingKnownTruthyCallableOrAwaitableType(node.expression, type, node.thenStatement); + checkSourceElement(node.thenStatement); + + if (node.thenStatement.kind === SyntaxKind.EmptyStatement) { + error(node.thenStatement, Diagnostics.The_body_of_an_if_statement_cannot_be_the_empty_statement); } - function checkTestingKnownTruthyCallableOrAwaitableType(condExpr: Expression, condType: Type, body?: Statement | Expression) { - if (!strictNullChecks) return; + checkSourceElement(node.elseStatement); + } + + function checkTestingKnownTruthyCallableOrAwaitableType(condExpr: Expression, condType: Type, body?: Statement | Expression) { + if (!strictNullChecks) return; + helper(condExpr, body); + while (isBinaryExpression(condExpr) && condExpr.operatorToken.kind === SyntaxKind.BarBarToken) { + condExpr = condExpr.left; helper(condExpr, body); - while (isBinaryExpression(condExpr) && condExpr.operatorToken.kind === SyntaxKind.BarBarToken) { - condExpr = condExpr.left; - helper(condExpr, body); - } - - function helper(condExpr: Expression, body: Expression | Statement | undefined) { - const location = isBinaryExpression(condExpr) && - (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) - ? condExpr.right - : condExpr; - if (isModuleExportsAccessExpression(location)) return; - const type = location === condExpr ? condType : checkTruthinessExpression(location); - const isPropertyExpressionCast = isPropertyAccessExpression(location) && isTypeAssertion(location.expression); - if (!(getTypeFacts(type) & TypeFacts.Truthy) || isPropertyExpressionCast) return; - - // While it technically should be invalid for any known-truthy value - // to be tested, we de-scope to functions and Promises unreferenced in - // the block as a heuristic to identify the most common bugs. There - // are too many false positives for values sourced from type - // definitions without strictNullChecks otherwise. - const callSignatures = getSignaturesOfType(type, SignatureKind.Call); - const isPromise = !!getAwaitedTypeOfPromise(type); - if (callSignatures.length === 0 && !isPromise) { - return; - } + } - const testedNode = isIdentifier(location) ? location - : isPropertyAccessExpression(location) ? location.name - : isBinaryExpression(location) && isIdentifier(location.right) ? location.right - : undefined; - const testedSymbol = testedNode && getSymbolAtLocation(testedNode); - if (!testedSymbol && !isPromise) { - return; - } + function helper(condExpr: Expression, body: Expression | Statement | undefined) { + const location = isBinaryExpression(condExpr) && + (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) + ? condExpr.right + : condExpr; + if (isModuleExportsAccessExpression(location)) return; + const type = location === condExpr ? condType : checkTruthinessExpression(location); + const isPropertyExpressionCast = isPropertyAccessExpression(location) && isTypeAssertion(location.expression); + if (!(getTypeFacts(type) & TypeFacts.Truthy) || isPropertyExpressionCast) return; + + // While it technically should be invalid for any known-truthy value + // to be tested, we de-scope to functions and Promises unreferenced in + // the block as a heuristic to identify the most common bugs. There + // are too many false positives for values sourced from type + // definitions without strictNullChecks otherwise. + const callSignatures = getSignaturesOfType(type, SignatureKind.Call); + const isPromise = !!getAwaitedTypeOfPromise(type); + if (callSignatures.length === 0 && !isPromise) { + return; + } - const isUsed = testedSymbol && isBinaryExpression(condExpr.parent) && isSymbolUsedInBinaryExpressionChain(condExpr.parent, testedSymbol) - || testedSymbol && body && isSymbolUsedInConditionBody(condExpr, body, testedNode, testedSymbol); - if (!isUsed) { - if (isPromise) { - errorAndMaybeSuggestAwait( - location, - /*maybeMissingAwait*/ true, - Diagnostics.This_condition_will_always_return_true_since_this_0_is_always_defined, - getTypeNameForErrorDisplay(type)); - } - else { - error(location, Diagnostics.This_condition_will_always_return_true_since_this_function_is_always_defined_Did_you_mean_to_call_it_instead); - } + const testedNode = isIdentifier(location) ? location + : isPropertyAccessExpression(location) ? location.name + : isBinaryExpression(location) && isIdentifier(location.right) ? location.right + : undefined; + const testedSymbol = testedNode && getSymbolAtLocation(testedNode); + if (!testedSymbol && !isPromise) { + return; + } + + const isUsed = testedSymbol && isBinaryExpression(condExpr.parent) && isSymbolUsedInBinaryExpressionChain(condExpr.parent, testedSymbol) + || testedSymbol && body && isSymbolUsedInConditionBody(condExpr, body, testedNode, testedSymbol); + if (!isUsed) { + if (isPromise) { + errorAndMaybeSuggestAwait( + location, + /*maybeMissingAwait*/ true, + Diagnostics.This_condition_will_always_return_true_since_this_0_is_always_defined, + getTypeNameForErrorDisplay(type)); + } + else { + error(location, Diagnostics.This_condition_will_always_return_true_since_this_function_is_always_defined_Did_you_mean_to_call_it_instead); } } } + } - function isSymbolUsedInConditionBody(expr: Expression, body: Statement | Expression, testedNode: Node, testedSymbol: Symbol): boolean { - return !!forEachChild(body, function check(childNode): boolean | undefined { - if (isIdentifier(childNode)) { - const childSymbol = getSymbolAtLocation(childNode); - if (childSymbol && childSymbol === testedSymbol) { - // If the test was a simple identifier, the above check is sufficient - if (isIdentifier(expr) || isIdentifier(testedNode) && isBinaryExpression(testedNode.parent)) { - return true; + function isSymbolUsedInConditionBody(expr: Expression, body: Statement | Expression, testedNode: Node, testedSymbol: Symbol): boolean { + return !!forEachChild(body, function check(childNode): boolean | undefined { + if (isIdentifier(childNode)) { + const childSymbol = getSymbolAtLocation(childNode); + if (childSymbol && childSymbol === testedSymbol) { + // If the test was a simple identifier, the above check is sufficient + if (isIdentifier(expr) || isIdentifier(testedNode) && isBinaryExpression(testedNode.parent)) { + return true; + } + // Otherwise we need to ensure the symbol is called on the same target + let testedExpression = testedNode.parent; + let childExpression = childNode.parent; + while (testedExpression && childExpression) { + if (isIdentifier(testedExpression) && isIdentifier(childExpression) || + testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword) { + return getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression); } - // Otherwise we need to ensure the symbol is called on the same target - let testedExpression = testedNode.parent; - let childExpression = childNode.parent; - while (testedExpression && childExpression) { - if (isIdentifier(testedExpression) && isIdentifier(childExpression) || - testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword) { - return getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression); - } - else if (isPropertyAccessExpression(testedExpression) && isPropertyAccessExpression(childExpression)) { - if (getSymbolAtLocation(testedExpression.name) !== getSymbolAtLocation(childExpression.name)) { - return false; - } - childExpression = childExpression.expression; - testedExpression = testedExpression.expression; - } - else if (isCallExpression(testedExpression) && isCallExpression(childExpression)) { - childExpression = childExpression.expression; - testedExpression = testedExpression.expression; - } - else { + else if (isPropertyAccessExpression(testedExpression) && isPropertyAccessExpression(childExpression)) { + if (getSymbolAtLocation(testedExpression.name) !== getSymbolAtLocation(childExpression.name)) { return false; } + childExpression = childExpression.expression; + testedExpression = testedExpression.expression; + } + else if (isCallExpression(testedExpression) && isCallExpression(childExpression)) { + childExpression = childExpression.expression; + testedExpression = testedExpression.expression; + } + else { + return false; } } } - return forEachChild(childNode, check); - }); - } + } + return forEachChild(childNode, check); + }); + } - function isSymbolUsedInBinaryExpressionChain(node: Node, testedSymbol: Symbol): boolean { - while (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { - const isUsed = forEachChild(node.right, function visit(child): boolean | undefined { - if (isIdentifier(child)) { - const symbol = getSymbolAtLocation(child); - if (symbol && symbol === testedSymbol) { - return true; - } + function isSymbolUsedInBinaryExpressionChain(node: Node, testedSymbol: Symbol): boolean { + while (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + const isUsed = forEachChild(node.right, function visit(child): boolean | undefined { + if (isIdentifier(child)) { + const symbol = getSymbolAtLocation(child); + if (symbol && symbol === testedSymbol) { + return true; } - return forEachChild(child, visit); - }); - if (isUsed) { - return true; } - node = node.parent; + return forEachChild(child, visit); + }); + if (isUsed) { + return true; } - return false; + node = node.parent; } + return false; + } - function checkDoStatement(node: DoStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + function checkDoStatement(node: DoStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); - checkSourceElement(node.statement); - checkTruthinessExpression(node.expression); - } + checkSourceElement(node.statement); + checkTruthinessExpression(node.expression); + } - function checkWhileStatement(node: WhileStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + function checkWhileStatement(node: WhileStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); - checkTruthinessExpression(node.expression); - checkSourceElement(node.statement); - } + checkTruthinessExpression(node.expression); + checkSourceElement(node.statement); + } - function checkTruthinessOfType(type: Type, node: Node) { - if (type.flags & TypeFlags.Void) { - error(node, Diagnostics.An_expression_of_type_void_cannot_be_tested_for_truthiness); - } - return type; + function checkTruthinessOfType(type: Type, node: Node) { + if (type.flags & TypeFlags.Void) { + error(node, Diagnostics.An_expression_of_type_void_cannot_be_tested_for_truthiness); } + return type; + } - function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) { - return checkTruthinessOfType(checkExpression(node, checkMode), node); - } + function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) { + return checkTruthinessOfType(checkExpression(node, checkMode), node); + } - function checkForStatement(node: ForStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { - checkGrammarVariableDeclarationList(node.initializer as VariableDeclarationList); - } + function checkForStatement(node: ForStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { + checkGrammarVariableDeclarationList(node.initializer as VariableDeclarationList); } + } - if (node.initializer) { - if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { - forEach((node.initializer as VariableDeclarationList).declarations, checkVariableDeclaration); - } - else { - checkExpression(node.initializer); - } + if (node.initializer) { + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + forEach((node.initializer as VariableDeclarationList).declarations, checkVariableDeclaration); } - - if (node.condition) checkTruthinessExpression(node.condition); - if (node.incrementor) checkExpression(node.incrementor); - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); + else { + checkExpression(node.initializer); } } - function checkForOfStatement(node: ForOfStatement): void { - checkGrammarForInOrForOfStatement(node); + if (node.condition) checkTruthinessExpression(node.condition); + if (node.incrementor) checkExpression(node.incrementor); + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); + } + } - const container = getContainingFunctionOrClassStaticBlock(node); - if (node.awaitModifier) { - if (container && isClassStaticBlockDeclaration(container)) { - grammarErrorOnNode(node.awaitModifier, Diagnostics.For_await_loops_cannot_be_used_inside_a_class_static_block); - } - else { - const functionFlags = getFunctionFlags(container); - if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Async)) === FunctionFlags.Async && languageVersion < ScriptTarget.ESNext) { - // for..await..of in an async function or async generator function prior to ESNext requires the __asyncValues helper - checkExternalEmitHelpers(node, ExternalEmitHelpers.ForAwaitOfIncludes); - } - } + function checkForOfStatement(node: ForOfStatement): void { + checkGrammarForInOrForOfStatement(node); + + const container = getContainingFunctionOrClassStaticBlock(node); + if (node.awaitModifier) { + if (container && isClassStaticBlockDeclaration(container)) { + grammarErrorOnNode(node.awaitModifier, Diagnostics.For_await_loops_cannot_be_used_inside_a_class_static_block); } - else if (compilerOptions.downlevelIteration && languageVersion < ScriptTarget.ES2015) { - // for..of prior to ES2015 requires the __values helper when downlevelIteration is enabled - checkExternalEmitHelpers(node, ExternalEmitHelpers.ForOfIncludes); + else { + const functionFlags = getFunctionFlags(container); + if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Async)) === FunctionFlags.Async && languageVersion < ScriptTarget.ESNext) { + // for..await..of in an async function or async generator function prior to ESNext requires the __asyncValues helper + checkExternalEmitHelpers(node, ExternalEmitHelpers.ForAwaitOfIncludes); + } } + } + else if (compilerOptions.downlevelIteration && languageVersion < ScriptTarget.ES2015) { + // for..of prior to ES2015 requires the __values helper when downlevelIteration is enabled + checkExternalEmitHelpers(node, ExternalEmitHelpers.ForOfIncludes); + } - // Check the LHS and RHS - // If the LHS is a declaration, just check it as a variable declaration, which will in turn check the RHS - // via checkRightHandSideOfForOf. - // If the LHS is an expression, check the LHS, as a destructuring assignment or as a reference. - // Then check that the RHS is assignable to it. - if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { - checkForInOrForOfVariableDeclaration(node); + // Check the LHS and RHS + // If the LHS is a declaration, just check it as a variable declaration, which will in turn check the RHS + // via checkRightHandSideOfForOf. + // If the LHS is an expression, check the LHS, as a destructuring assignment or as a reference. + // Then check that the RHS is assignable to it. + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + checkForInOrForOfVariableDeclaration(node); + } + else { + const varExpr = node.initializer; + const iteratedType = checkRightHandSideOfForOf(node); + + // There may be a destructuring assignment on the left side + if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { + // iteratedType may be undefined. In this case, we still want to check the structure of + // varExpr, in particular making sure it's a valid LeftHandSideExpression. But we'd like + // to short circuit the type relation checking as much as possible, so we pass the unknownType. + checkDestructuringAssignment(varExpr, iteratedType || errorType); } else { - const varExpr = node.initializer; - const iteratedType = checkRightHandSideOfForOf(node); - - // There may be a destructuring assignment on the left side - if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { - // iteratedType may be undefined. In this case, we still want to check the structure of - // varExpr, in particular making sure it's a valid LeftHandSideExpression. But we'd like - // to short circuit the type relation checking as much as possible, so we pass the unknownType. - checkDestructuringAssignment(varExpr, iteratedType || errorType); - } - else { - const leftType = checkExpression(varExpr); - checkReferenceExpression( - varExpr, - Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access, - Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_an_optional_property_access); + const leftType = checkExpression(varExpr); + checkReferenceExpression( + varExpr, + Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access, + Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_an_optional_property_access); - // iteratedType will be undefined if the rightType was missing properties/signatures - // required to get its iteratedType (like [Symbol.iterator] or next). This may be - // because we accessed properties from anyType, or it may have led to an error inside - // getElementTypeOfIterable. - if (iteratedType) { - checkTypeAssignableToAndOptionallyElaborate(iteratedType, leftType, varExpr, node.expression); - } + // iteratedType will be undefined if the rightType was missing properties/signatures + // required to get its iteratedType (like [Symbol.iterator] or next). This may be + // because we accessed properties from anyType, or it may have led to an error inside + // getElementTypeOfIterable. + if (iteratedType) { + checkTypeAssignableToAndOptionallyElaborate(iteratedType, leftType, varExpr, node.expression); } } + } - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); - } + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); } + } - function checkForInStatement(node: ForInStatement) { - // Grammar checking - checkGrammarForInOrForOfStatement(node); + function checkForInStatement(node: ForInStatement) { + // Grammar checking + checkGrammarForInOrForOfStatement(node); - const rightType = getNonNullableTypeIfNeeded(checkExpression(node.expression)); - // TypeScript 1.0 spec (April 2014): 5.4 + const rightType = getNonNullableTypeIfNeeded(checkExpression(node.expression)); + // TypeScript 1.0 spec (April 2014): 5.4 + // In a 'for-in' statement of the form + // for (let VarDecl in Expr) Statement + // VarDecl must be a variable declaration without a type annotation that declares a variable of type Any, + // and Expr must be an expression of type Any, an object type, or a type parameter type. + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + const variable = (node.initializer as VariableDeclarationList).declarations[0]; + if (variable && isBindingPattern(variable.name)) { + error(variable.name, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); + } + checkForInOrForOfVariableDeclaration(node); + } + else { // In a 'for-in' statement of the form - // for (let VarDecl in Expr) Statement - // VarDecl must be a variable declaration without a type annotation that declares a variable of type Any, + // for (Var in Expr) Statement + // Var must be an expression classified as a reference of type Any or the String primitive type, // and Expr must be an expression of type Any, an object type, or a type parameter type. - if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { - const variable = (node.initializer as VariableDeclarationList).declarations[0]; - if (variable && isBindingPattern(variable.name)) { - error(variable.name, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); - } - checkForInOrForOfVariableDeclaration(node); + const varExpr = node.initializer; + const leftType = checkExpression(varExpr); + if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { + error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); } - else { - // In a 'for-in' statement of the form - // for (Var in Expr) Statement - // Var must be an expression classified as a reference of type Any or the String primitive type, - // and Expr must be an expression of type Any, an object type, or a type parameter type. - const varExpr = node.initializer; - const leftType = checkExpression(varExpr); - if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { - error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); - } - else if (!isTypeAssignableTo(getIndexTypeOrString(rightType), leftType)) { - error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_of_type_string_or_any); - } - else { - // run check only former check succeeded to avoid cascading errors - checkReferenceExpression( - varExpr, - Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access, - Diagnostics.The_left_hand_side_of_a_for_in_statement_may_not_be_an_optional_property_access); - } + else if (!isTypeAssignableTo(getIndexTypeOrString(rightType), leftType)) { + error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_of_type_string_or_any); } - - // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved - // in this case error about missing name is already reported - do not report extra one - if (rightType === neverType || !isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { - error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter_but_here_has_type_0, typeToString(rightType)); + else { + // run check only former check succeeded to avoid cascading errors + checkReferenceExpression( + varExpr, + Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access, + Diagnostics.The_left_hand_side_of_a_for_in_statement_may_not_be_an_optional_property_access); } + } - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); - } + // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved + // in this case error about missing name is already reported - do not report extra one + if (rightType === neverType || !isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { + error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter_but_here_has_type_0, typeToString(rightType)); } - function checkForInOrForOfVariableDeclaration(iterationStatement: ForInOrOfStatement): void { - const variableDeclarationList = iterationStatement.initializer as VariableDeclarationList; - // checkGrammarForInOrForOfStatement will check that there is exactly one declaration. - if (variableDeclarationList.declarations.length >= 1) { - const decl = variableDeclarationList.declarations[0]; - checkVariableDeclaration(decl); - } + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); } + } - function checkRightHandSideOfForOf(statement: ForOfStatement): Type { - const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; - return checkIteratedTypeOrElementType(use, checkNonNullExpression(statement.expression), undefinedType, statement.expression); + function checkForInOrForOfVariableDeclaration(iterationStatement: ForInOrOfStatement): void { + const variableDeclarationList = iterationStatement.initializer as VariableDeclarationList; + // checkGrammarForInOrForOfStatement will check that there is exactly one declaration. + if (variableDeclarationList.declarations.length >= 1) { + const decl = variableDeclarationList.declarations[0]; + checkVariableDeclaration(decl); } + } - function checkIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined): Type { - if (isTypeAny(inputType)) { - return inputType; - } - return getIteratedTypeOrElementType(use, inputType, sentType, errorNode, /*checkAssignability*/ true) || anyType; + function checkRightHandSideOfForOf(statement: ForOfStatement): Type { + const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; + return checkIteratedTypeOrElementType(use, checkNonNullExpression(statement.expression), undefinedType, statement.expression); + } + + function checkIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined): Type { + if (isTypeAny(inputType)) { + return inputType; } + return getIteratedTypeOrElementType(use, inputType, sentType, errorNode, /*checkAssignability*/ true) || anyType; + } - /** - * When consuming an iterable type in a for..of, spread, or iterator destructuring assignment - * we want to get the iterated type of an iterable for ES2015 or later, or the iterated type - * of a iterable (if defined globally) or element type of an array like for ES2015 or earlier. - */ - function getIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined, checkAssignability: boolean): Type | undefined { - const allowAsyncIterables = (use & IterationUse.AllowsAsyncIterablesFlag) !== 0; - if (inputType === neverType) { - reportTypeNotIterableError(errorNode!, inputType, allowAsyncIterables); // TODO: GH#18217 - return undefined; - } + /** + * When consuming an iterable type in a for..of, spread, or iterator destructuring assignment + * we want to get the iterated type of an iterable for ES2015 or later, or the iterated type + * of a iterable (if defined globally) or element type of an array like for ES2015 or earlier. + */ + function getIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined, checkAssignability: boolean): Type | undefined { + const allowAsyncIterables = (use & IterationUse.AllowsAsyncIterablesFlag) !== 0; + if (inputType === neverType) { + reportTypeNotIterableError(errorNode!, inputType, allowAsyncIterables); // TODO: GH#18217 + return undefined; + } - const uplevelIteration = languageVersion >= ScriptTarget.ES2015; - const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration; - const possibleOutOfBounds = compilerOptions.noUncheckedIndexedAccess && !!(use & IterationUse.PossiblyOutOfBounds); + const uplevelIteration = languageVersion >= ScriptTarget.ES2015; + const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration; + const possibleOutOfBounds = compilerOptions.noUncheckedIndexedAccess && !!(use & IterationUse.PossiblyOutOfBounds); - // Get the iterated type of an `Iterable` or `IterableIterator` only in ES2015 - // or higher, when inside of an async generator or for-await-if, or when - // downlevelIteration is requested. - if (uplevelIteration || downlevelIteration || allowAsyncIterables) { - // We only report errors for an invalid iterable type in ES2015 or higher. - const iterationTypes = getIterationTypesOfIterable(inputType, use, uplevelIteration ? errorNode : undefined); - if (checkAssignability) { - if (iterationTypes) { - const diagnostic = - use & IterationUse.ForOfFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_for_of_will_always_send_0 : - use & IterationUse.SpreadFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_spread_will_always_send_0 : - use & IterationUse.DestructuringFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_destructuring_will_always_send_0 : - use & IterationUse.YieldStarFlag ? Diagnostics.Cannot_delegate_iteration_to_value_because_the_next_method_of_its_iterator_expects_type_1_but_the_containing_generator_will_always_send_0 : - undefined; - if (diagnostic) { - checkTypeAssignableTo(sentType, iterationTypes.nextType, errorNode, diagnostic); - } + // Get the iterated type of an `Iterable` or `IterableIterator` only in ES2015 + // or higher, when inside of an async generator or for-await-if, or when + // downlevelIteration is requested. + if (uplevelIteration || downlevelIteration || allowAsyncIterables) { + // We only report errors for an invalid iterable type in ES2015 or higher. + const iterationTypes = getIterationTypesOfIterable(inputType, use, uplevelIteration ? errorNode : undefined); + if (checkAssignability) { + if (iterationTypes) { + const diagnostic = + use & IterationUse.ForOfFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_for_of_will_always_send_0 : + use & IterationUse.SpreadFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_spread_will_always_send_0 : + use & IterationUse.DestructuringFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_destructuring_will_always_send_0 : + use & IterationUse.YieldStarFlag ? Diagnostics.Cannot_delegate_iteration_to_value_because_the_next_method_of_its_iterator_expects_type_1_but_the_containing_generator_will_always_send_0 : + undefined; + if (diagnostic) { + checkTypeAssignableTo(sentType, iterationTypes.nextType, errorNode, diagnostic); } } - if (iterationTypes || uplevelIteration) { - return possibleOutOfBounds ? includeUndefinedInIndexSignature(iterationTypes && iterationTypes.yieldType) : (iterationTypes && iterationTypes.yieldType); - } } + if (iterationTypes || uplevelIteration) { + return possibleOutOfBounds ? includeUndefinedInIndexSignature(iterationTypes && iterationTypes.yieldType) : (iterationTypes && iterationTypes.yieldType); + } + } - let arrayType = inputType; - let reportedError = false; - let hasStringConstituent = false; + let arrayType = inputType; + let reportedError = false; + let hasStringConstituent = false; - // If strings are permitted, remove any string-like constituents from the array type. - // This allows us to find other non-string element types from an array unioned with - // a string. - if (use & IterationUse.AllowsStringInputFlag) { - if (arrayType.flags & TypeFlags.Union) { - // After we remove all types that are StringLike, we will know if there was a string constituent - // based on whether the result of filter is a new array. - const arrayTypes = (inputType as UnionType).types; - const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike)); - if (filteredTypes !== arrayTypes) { - arrayType = getUnionType(filteredTypes, UnionReduction.Subtype); - } + // If strings are permitted, remove any string-like constituents from the array type. + // This allows us to find other non-string element types from an array unioned with + // a string. + if (use & IterationUse.AllowsStringInputFlag) { + if (arrayType.flags & TypeFlags.Union) { + // After we remove all types that are StringLike, we will know if there was a string constituent + // based on whether the result of filter is a new array. + const arrayTypes = (inputType as UnionType).types; + const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike)); + if (filteredTypes !== arrayTypes) { + arrayType = getUnionType(filteredTypes, UnionReduction.Subtype); } - else if (arrayType.flags & TypeFlags.StringLike) { - arrayType = neverType; - } - - hasStringConstituent = arrayType !== inputType; - if (hasStringConstituent) { - if (languageVersion < ScriptTarget.ES5) { - if (errorNode) { - error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher); - reportedError = true; - } - } + } + else if (arrayType.flags & TypeFlags.StringLike) { + arrayType = neverType; + } - // Now that we've removed all the StringLike types, if no constituents remain, then the entire - // arrayOrStringType was a string. - if (arrayType.flags & TypeFlags.Never) { - return possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType; + hasStringConstituent = arrayType !== inputType; + if (hasStringConstituent) { + if (languageVersion < ScriptTarget.ES5) { + if (errorNode) { + error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher); + reportedError = true; } } - } - if (!isArrayLikeType(arrayType)) { - if (errorNode && !reportedError) { - // Which error we report depends on whether we allow strings or if there was a - // string constituent. For example, if the input type is number | string, we - // want to say that number is not an array type. But if the input was just - // number and string input is allowed, we want to say that number is not an - // array type or a string type. - const allowsStrings = !!(use & IterationUse.AllowsStringInputFlag) && !hasStringConstituent; - const [defaultDiagnostic, maybeMissingAwait] = getIterationDiagnosticDetails(allowsStrings, downlevelIteration); - errorAndMaybeSuggestAwait( - errorNode, - maybeMissingAwait && !!getAwaitedTypeOfPromise(arrayType), - defaultDiagnostic, - typeToString(arrayType)); + // Now that we've removed all the StringLike types, if no constituents remain, then the entire + // arrayOrStringType was a string. + if (arrayType.flags & TypeFlags.Never) { + return possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType; } - return hasStringConstituent ? possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType : undefined; } + } - const arrayElementType = getIndexTypeOfType(arrayType, numberType); - if (hasStringConstituent && arrayElementType) { - // This is just an optimization for the case where arrayOrStringType is string | string[] - if (arrayElementType.flags & TypeFlags.StringLike && !compilerOptions.noUncheckedIndexedAccess) { - return stringType; - } - - return getUnionType(possibleOutOfBounds ? [arrayElementType, stringType, undefinedType] : [arrayElementType, stringType], UnionReduction.Subtype); + if (!isArrayLikeType(arrayType)) { + if (errorNode && !reportedError) { + // Which error we report depends on whether we allow strings or if there was a + // string constituent. For example, if the input type is number | string, we + // want to say that number is not an array type. But if the input was just + // number and string input is allowed, we want to say that number is not an + // array type or a string type. + const allowsStrings = !!(use & IterationUse.AllowsStringInputFlag) && !hasStringConstituent; + const [defaultDiagnostic, maybeMissingAwait] = getIterationDiagnosticDetails(allowsStrings, downlevelIteration); + errorAndMaybeSuggestAwait( + errorNode, + maybeMissingAwait && !!getAwaitedTypeOfPromise(arrayType), + defaultDiagnostic, + typeToString(arrayType)); } + return hasStringConstituent ? possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType : undefined; + } - return (use & IterationUse.PossiblyOutOfBounds) ? includeUndefinedInIndexSignature(arrayElementType) : arrayElementType; + const arrayElementType = getIndexTypeOfType(arrayType, numberType); + if (hasStringConstituent && arrayElementType) { + // This is just an optimization for the case where arrayOrStringType is string | string[] + if (arrayElementType.flags & TypeFlags.StringLike && !compilerOptions.noUncheckedIndexedAccess) { + return stringType; + } - function getIterationDiagnosticDetails(allowsStrings: boolean, downlevelIteration: boolean | undefined): [error: DiagnosticMessage, maybeMissingAwait: boolean] { - if (downlevelIteration) { - return allowsStrings - ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true] - : [Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true]; - } + return getUnionType(possibleOutOfBounds ? [arrayElementType, stringType, undefinedType] : [arrayElementType, stringType], UnionReduction.Subtype); + } - const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined); + return (use & IterationUse.PossiblyOutOfBounds) ? includeUndefinedInIndexSignature(arrayElementType) : arrayElementType; - if (yieldType) { - return [Diagnostics.Type_0_can_only_be_iterated_through_when_using_the_downlevelIteration_flag_or_with_a_target_of_es2015_or_higher, false]; - } + function getIterationDiagnosticDetails(allowsStrings: boolean, downlevelIteration: boolean | undefined): [error: DiagnosticMessage, maybeMissingAwait: boolean] { + if (downlevelIteration) { + return allowsStrings + ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true] + : [Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true]; + } - if (isES2015OrLaterIterable(inputType.symbol?.escapedName)) { - return [Diagnostics.Type_0_can_only_be_iterated_through_when_using_the_downlevelIteration_flag_or_with_a_target_of_es2015_or_higher, true]; - } + const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined); - return allowsStrings - ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type, true] - : [Diagnostics.Type_0_is_not_an_array_type, true]; - } - } - - function isES2015OrLaterIterable(n: __String) { - switch (n) { - case "Float32Array": - case "Float64Array": - case "Int16Array": - case "Int32Array": - case "Int8Array": - case "NodeList": - case "Uint16Array": - case "Uint32Array": - case "Uint8Array": - case "Uint8ClampedArray": - return true; + if (yieldType) { + return [Diagnostics.Type_0_can_only_be_iterated_through_when_using_the_downlevelIteration_flag_or_with_a_target_of_es2015_or_higher, false]; } - return false; - } - /** - * Gets the requested "iteration type" from an `Iterable`-like or `AsyncIterable`-like type. - */ - function getIterationTypeOfIterable(use: IterationUse, typeKind: IterationTypeKind, inputType: Type, errorNode: Node | undefined): Type | undefined { - if (isTypeAny(inputType)) { - return undefined; + if (isES2015OrLaterIterable(inputType.symbol?.escapedName)) { + return [Diagnostics.Type_0_can_only_be_iterated_through_when_using_the_downlevelIteration_flag_or_with_a_target_of_es2015_or_higher, true]; } - const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode); - return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)]; + return allowsStrings + ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type, true] + : [Diagnostics.Type_0_is_not_an_array_type, true]; } + } - function createIterationTypes(yieldType: Type = neverType, returnType: Type = neverType, nextType: Type = unknownType): IterationTypes { - // `yieldType` and `returnType` are defaulted to `neverType` they each will be combined - // via `getUnionType` when merging iteration types. `nextType` is defined as `unknownType` - // as it is combined via `getIntersectionType` when merging iteration types. - - // Use the cache only for intrinsic types to keep it small as they are likely to be - // more frequently created (i.e. `Iterator`). Iteration types - // are also cached on the type they are requested for, so we shouldn't need to maintain - // the cache for less-frequently used types. - if (yieldType.flags & TypeFlags.Intrinsic && - returnType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined) && - nextType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined)) { - const id = getTypeListId([yieldType, returnType, nextType]); - let iterationTypes = iterationTypesCache.get(id); - if (!iterationTypes) { - iterationTypes = { yieldType, returnType, nextType }; - iterationTypesCache.set(id, iterationTypes); - } - return iterationTypes; - } - return { yieldType, returnType, nextType }; + function isES2015OrLaterIterable(n: __String) { + switch (n) { + case "Float32Array": + case "Float64Array": + case "Int16Array": + case "Int32Array": + case "Int8Array": + case "NodeList": + case "Uint16Array": + case "Uint32Array": + case "Uint8Array": + case "Uint8ClampedArray": + return true; } + return false; + } - /** - * Combines multiple `IterationTypes` records. - * - * If `array` is empty or all elements are missing or are references to `noIterationTypes`, - * then `noIterationTypes` is returned. Otherwise, an `IterationTypes` record is returned - * for the combined iteration types. - */ - function combineIterationTypes(array: (IterationTypes | undefined)[]) { - let yieldTypes: Type[] | undefined; - let returnTypes: Type[] | undefined; - let nextTypes: Type[] | undefined; - for (const iterationTypes of array) { - if (iterationTypes === undefined || iterationTypes === noIterationTypes) { - continue; - } - if (iterationTypes === anyIterationTypes) { - return anyIterationTypes; - } - yieldTypes = append(yieldTypes, iterationTypes.yieldType); - returnTypes = append(returnTypes, iterationTypes.returnType); - nextTypes = append(nextTypes, iterationTypes.nextType); - } - if (yieldTypes || returnTypes || nextTypes) { - return createIterationTypes( - yieldTypes && getUnionType(yieldTypes), - returnTypes && getUnionType(returnTypes), - nextTypes && getIntersectionType(nextTypes)); - } - return noIterationTypes; + /** + * Gets the requested "iteration type" from an `Iterable`-like or `AsyncIterable`-like type. + */ + function getIterationTypeOfIterable(use: IterationUse, typeKind: IterationTypeKind, inputType: Type, errorNode: Node | undefined): Type | undefined { + if (isTypeAny(inputType)) { + return undefined; } - function getCachedIterationTypes(type: Type, cacheKey: MatchingKeys) { - return (type as IterableOrIteratorType)[cacheKey]; - } + const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode); + return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)]; + } - function setCachedIterationTypes(type: Type, cacheKey: MatchingKeys, cachedTypes: IterationTypes) { - return (type as IterableOrIteratorType)[cacheKey] = cachedTypes; - } + function createIterationTypes(yieldType: Type = neverType, returnType: Type = neverType, nextType: Type = unknownType): IterationTypes { + // `yieldType` and `returnType` are defaulted to `neverType` they each will be combined + // via `getUnionType` when merging iteration types. `nextType` is defined as `unknownType` + // as it is combined via `getIntersectionType` when merging iteration types. + + // Use the cache only for intrinsic types to keep it small as they are likely to be + // more frequently created (i.e. `Iterator`). Iteration types + // are also cached on the type they are requested for, so we shouldn't need to maintain + // the cache for less-frequently used types. + if (yieldType.flags & TypeFlags.Intrinsic && + returnType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined) && + nextType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined)) { + const id = getTypeListId([yieldType, returnType, nextType]); + let iterationTypes = iterationTypesCache.get(id); + if (!iterationTypes) { + iterationTypes = { yieldType, returnType, nextType }; + iterationTypesCache.set(id, iterationTypes); + } + return iterationTypes; + } + return { yieldType, returnType, nextType }; + } - /** - * Gets the *yield*, *return*, and *next* types from an `Iterable`-like or `AsyncIterable`-like type. - * - * At every level that involves analyzing return types of signatures, we union the return types of all the signatures. - * - * Another thing to note is that at any step of this process, we could run into a dead end, - * meaning either the property is missing, or we run into the anyType. If either of these things - * happens, we return `undefined` to signal that we could not find the iteration type. If a property - * is missing, and the previous step did not result in `any`, then we also give an error if the - * caller requested it. Then the caller can decide what to do in the case where there is no iterated - * type. - * - * For a **for-of** statement, `yield*` (in a normal generator), spread, array - * destructuring, or normal generator we will only ever look for a `[Symbol.iterator]()` - * method. - * - * For an async generator we will only ever look at the `[Symbol.asyncIterator]()` method. - * - * For a **for-await-of** statement or a `yield*` in an async generator we will look for - * the `[Symbol.asyncIterator]()` method first, and then the `[Symbol.iterator]()` method. - */ - function getIterationTypesOfIterable(type: Type, use: IterationUse, errorNode: Node | undefined) { - if (isTypeAny(type)) { + /** + * Combines multiple `IterationTypes` records. + * + * If `array` is empty or all elements are missing or are references to `noIterationTypes`, + * then `noIterationTypes` is returned. Otherwise, an `IterationTypes` record is returned + * for the combined iteration types. + */ + function combineIterationTypes(array: (IterationTypes | undefined)[]) { + let yieldTypes: Type[] | undefined; + let returnTypes: Type[] | undefined; + let nextTypes: Type[] | undefined; + for (const iterationTypes of array) { + if (iterationTypes === undefined || iterationTypes === noIterationTypes) { + continue; + } + if (iterationTypes === anyIterationTypes) { return anyIterationTypes; } + yieldTypes = append(yieldTypes, iterationTypes.yieldType); + returnTypes = append(returnTypes, iterationTypes.returnType); + nextTypes = append(nextTypes, iterationTypes.nextType); + } + if (yieldTypes || returnTypes || nextTypes) { + return createIterationTypes( + yieldTypes && getUnionType(yieldTypes), + returnTypes && getUnionType(returnTypes), + nextTypes && getIntersectionType(nextTypes)); + } + return noIterationTypes; + } - if (!(type.flags & TypeFlags.Union)) { - const errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined = errorNode ? { errors: undefined } : undefined; - const iterationTypes = getIterationTypesOfIterableWorker(type, use, errorNode, errorOutputContainer); - if (iterationTypes === noIterationTypes) { - if (errorNode) { - const rootDiag = reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); - if (errorOutputContainer?.errors) { - addRelatedInfo(rootDiag, ...errorOutputContainer.errors); - } + function getCachedIterationTypes(type: Type, cacheKey: MatchingKeys) { + return (type as IterableOrIteratorType)[cacheKey]; + } + + function setCachedIterationTypes(type: Type, cacheKey: MatchingKeys, cachedTypes: IterationTypes) { + return (type as IterableOrIteratorType)[cacheKey] = cachedTypes; + } + + /** + * Gets the *yield*, *return*, and *next* types from an `Iterable`-like or `AsyncIterable`-like type. + * + * At every level that involves analyzing return types of signatures, we union the return types of all the signatures. + * + * Another thing to note is that at any step of this process, we could run into a dead end, + * meaning either the property is missing, or we run into the anyType. If either of these things + * happens, we return `undefined` to signal that we could not find the iteration type. If a property + * is missing, and the previous step did not result in `any`, then we also give an error if the + * caller requested it. Then the caller can decide what to do in the case where there is no iterated + * type. + * + * For a **for-of** statement, `yield*` (in a normal generator), spread, array + * destructuring, or normal generator we will only ever look for a `[Symbol.iterator]()` + * method. + * + * For an async generator we will only ever look at the `[Symbol.asyncIterator]()` method. + * + * For a **for-await-of** statement or a `yield*` in an async generator we will look for + * the `[Symbol.asyncIterator]()` method first, and then the `[Symbol.iterator]()` method. + */ + function getIterationTypesOfIterable(type: Type, use: IterationUse, errorNode: Node | undefined) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + + if (!(type.flags & TypeFlags.Union)) { + const errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined = errorNode ? { errors: undefined } : undefined; + const iterationTypes = getIterationTypesOfIterableWorker(type, use, errorNode, errorOutputContainer); + if (iterationTypes === noIterationTypes) { + if (errorNode) { + const rootDiag = reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); + if (errorOutputContainer?.errors) { + addRelatedInfo(rootDiag, ...errorOutputContainer.errors); } - return undefined; } - else if (errorOutputContainer?.errors?.length) { - for (const diag of errorOutputContainer.errors) { - diagnostics.add(diag); - } + return undefined; + } + else if (errorOutputContainer?.errors?.length) { + for (const diag of errorOutputContainer.errors) { + diagnostics.add(diag); } - return iterationTypes; } + return iterationTypes; + } - const cacheKey = use & IterationUse.AllowsAsyncIterablesFlag ? "iterationTypesOfAsyncIterable" : "iterationTypesOfIterable"; - const cachedTypes = getCachedIterationTypes(type, cacheKey); - if (cachedTypes) return cachedTypes === noIterationTypes ? undefined : cachedTypes; + const cacheKey = use & IterationUse.AllowsAsyncIterablesFlag ? "iterationTypesOfAsyncIterable" : "iterationTypesOfIterable"; + const cachedTypes = getCachedIterationTypes(type, cacheKey); + if (cachedTypes) return cachedTypes === noIterationTypes ? undefined : cachedTypes; - let allIterationTypes: IterationTypes[] | undefined; - for (const constituent of (type as UnionType).types) { - const errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined = errorNode ? { errors: undefined } : undefined; - const iterationTypes = getIterationTypesOfIterableWorker(constituent, use, errorNode, errorOutputContainer); - if (iterationTypes === noIterationTypes) { - if (errorNode) { - const rootDiag = reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); - if (errorOutputContainer?.errors) { - addRelatedInfo(rootDiag, ...errorOutputContainer.errors); - } + let allIterationTypes: IterationTypes[] | undefined; + for (const constituent of (type as UnionType).types) { + const errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined = errorNode ? { errors: undefined } : undefined; + const iterationTypes = getIterationTypesOfIterableWorker(constituent, use, errorNode, errorOutputContainer); + if (iterationTypes === noIterationTypes) { + if (errorNode) { + const rootDiag = reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); + if (errorOutputContainer?.errors) { + addRelatedInfo(rootDiag, ...errorOutputContainer.errors); } - setCachedIterationTypes(type, cacheKey, noIterationTypes); - return undefined; } - else if (errorOutputContainer?.errors?.length) { - for (const diag of errorOutputContainer.errors) { - diagnostics.add(diag); - } + setCachedIterationTypes(type, cacheKey, noIterationTypes); + return undefined; + } + else if (errorOutputContainer?.errors?.length) { + for (const diag of errorOutputContainer.errors) { + diagnostics.add(diag); } - - allIterationTypes = append(allIterationTypes, iterationTypes); } - const iterationTypes = allIterationTypes ? combineIterationTypes(allIterationTypes) : noIterationTypes; - setCachedIterationTypes(type, cacheKey, iterationTypes); - return iterationTypes === noIterationTypes ? undefined : iterationTypes; - } - - function getAsyncFromSyncIterationTypes(iterationTypes: IterationTypes, errorNode: Node | undefined) { - if (iterationTypes === noIterationTypes) return noIterationTypes; - if (iterationTypes === anyIterationTypes) return anyIterationTypes; - const { yieldType, returnType, nextType } = iterationTypes; - // if we're requesting diagnostics, report errors for a missing `Awaited`. - if (errorNode) { - getGlobalAwaitedSymbol(/*reportErrors*/ true); - } - return createIterationTypes( - getAwaitedType(yieldType, errorNode) || anyType, - getAwaitedType(returnType, errorNode) || anyType, - nextType); + allIterationTypes = append(allIterationTypes, iterationTypes); } - /** - * Gets the *yield*, *return*, and *next* types from a non-union type. - * - * If we are unable to find the *yield*, *return*, and *next* types, `noIterationTypes` is - * returned to indicate to the caller that it should report an error. Otherwise, an - * `IterationTypes` record is returned. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableWorker(type: Type, use: IterationUse, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined) { - if (isTypeAny(type)) { - return anyIterationTypes; - } + const iterationTypes = allIterationTypes ? combineIterationTypes(allIterationTypes) : noIterationTypes; + setCachedIterationTypes(type, cacheKey, iterationTypes); + return iterationTypes === noIterationTypes ? undefined : iterationTypes; + } - // If we are reporting errors and encounter a cached `noIterationTypes`, we should ignore the cached value and continue as if nothing was cached. - // In addition, we should not cache any new results for this call. - let noCache = false; + function getAsyncFromSyncIterationTypes(iterationTypes: IterationTypes, errorNode: Node | undefined) { + if (iterationTypes === noIterationTypes) return noIterationTypes; + if (iterationTypes === anyIterationTypes) return anyIterationTypes; + const { yieldType, returnType, nextType } = iterationTypes; + // if we're requesting diagnostics, report errors for a missing `Awaited`. + if (errorNode) { + getGlobalAwaitedSymbol(/*reportErrors*/ true); + } + return createIterationTypes( + getAwaitedType(yieldType, errorNode) || anyType, + getAwaitedType(returnType, errorNode) || anyType, + nextType); + } - if (use & IterationUse.AllowsAsyncIterablesFlag) { - const iterationTypes = - getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || - getIterationTypesOfIterableFast(type, asyncIterationTypesResolver); - if (iterationTypes) { - if (iterationTypes === noIterationTypes && errorNode) { - // ignore the cached value - noCache = true; - } - else { - return use & IterationUse.ForOfFlag ? - getAsyncFromSyncIterationTypes(iterationTypes, errorNode) : - iterationTypes; - } + /** + * Gets the *yield*, *return*, and *next* types from a non-union type. + * + * If we are unable to find the *yield*, *return*, and *next* types, `noIterationTypes` is + * returned to indicate to the caller that it should report an error. Otherwise, an + * `IterationTypes` record is returned. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableWorker(type: Type, use: IterationUse, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + + // If we are reporting errors and encounter a cached `noIterationTypes`, we should ignore the cached value and continue as if nothing was cached. + // In addition, we should not cache any new results for this call. + let noCache = false; + + if (use & IterationUse.AllowsAsyncIterablesFlag) { + const iterationTypes = + getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || + getIterationTypesOfIterableFast(type, asyncIterationTypesResolver); + if (iterationTypes) { + if (iterationTypes === noIterationTypes && errorNode) { + // ignore the cached value + noCache = true; } - } - - if (use & IterationUse.AllowsSyncIterablesFlag) { - let iterationTypes = - getIterationTypesOfIterableCached(type, syncIterationTypesResolver) || - getIterationTypesOfIterableFast(type, syncIterationTypesResolver); - if (iterationTypes) { - if (iterationTypes === noIterationTypes && errorNode) { - // ignore the cached value - noCache = true; - } - else { - if (use & IterationUse.AllowsAsyncIterablesFlag) { - // for a sync iterable in an async context, only use the cached types if they are valid. - if (iterationTypes !== noIterationTypes) { - iterationTypes = getAsyncFromSyncIterationTypes(iterationTypes, errorNode); - return noCache ? iterationTypes : setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes); - } - } - else { - return iterationTypes; - } - } + else { + return use & IterationUse.ForOfFlag ? + getAsyncFromSyncIterationTypes(iterationTypes, errorNode) : + iterationTypes; } } + } - if (use & IterationUse.AllowsAsyncIterablesFlag) { - const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode, errorOutputContainer, noCache); - if (iterationTypes !== noIterationTypes) { - return iterationTypes; + if (use & IterationUse.AllowsSyncIterablesFlag) { + let iterationTypes = + getIterationTypesOfIterableCached(type, syncIterationTypesResolver) || + getIterationTypesOfIterableFast(type, syncIterationTypesResolver); + if (iterationTypes) { + if (iterationTypes === noIterationTypes && errorNode) { + // ignore the cached value + noCache = true; } - } - - if (use & IterationUse.AllowsSyncIterablesFlag) { - let iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode, errorOutputContainer, noCache); - if (iterationTypes !== noIterationTypes) { + else { if (use & IterationUse.AllowsAsyncIterablesFlag) { - iterationTypes = getAsyncFromSyncIterationTypes(iterationTypes, errorNode); - return noCache ? iterationTypes : setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes); + // for a sync iterable in an async context, only use the cached types if they are valid. + if (iterationTypes !== noIterationTypes) { + iterationTypes = getAsyncFromSyncIterationTypes(iterationTypes, errorNode); + return noCache ? iterationTypes : setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes); + } } else { return iterationTypes; } } } - - return noIterationTypes; } - /** - * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or - * `AsyncIterable`-like type from the cache. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableCached(type: Type, resolver: IterationTypesResolver) { - return getCachedIterationTypes(type, resolver.iterableCacheKey); + if (use & IterationUse.AllowsAsyncIterablesFlag) { + const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode, errorOutputContainer, noCache); + if (iterationTypes !== noIterationTypes) { + return iterationTypes; + } } - function getIterationTypesOfGlobalIterableType(globalType: Type, resolver: IterationTypesResolver) { - const globalIterationTypes = - getIterationTypesOfIterableCached(globalType, resolver) || - getIterationTypesOfIterableSlow(globalType, resolver, /*errorNode*/ undefined, /*errorOutputContainer*/ undefined, /*noCache*/ false); - return globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; + if (use & IterationUse.AllowsSyncIterablesFlag) { + let iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode, errorOutputContainer, noCache); + if (iterationTypes !== noIterationTypes) { + if (use & IterationUse.AllowsAsyncIterablesFlag) { + iterationTypes = getAsyncFromSyncIterationTypes(iterationTypes, errorNode); + return noCache ? iterationTypes : setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes); + } + else { + return iterationTypes; + } + } } - /** - * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like - * type from from common heuristics. - * - * If we previously analyzed this type and found no iteration types, `noIterationTypes` is - * returned. If we found iteration types, an `IterationTypes` record is returned. - * Otherwise, we return `undefined` to indicate to the caller it should perform a more - * exhaustive analysis. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableFast(type: Type, resolver: IterationTypesResolver) { - // As an optimization, if the type is an instantiation of one of the following global types, then - // just grab its related type argument: - // - `Iterable` or `AsyncIterable` - // - `IterableIterator` or `AsyncIterableIterator` - let globalType: Type; - if (isReferenceToType(type, globalType = resolver.getGlobalIterableType(/*reportErrors*/ false)) || - isReferenceToType(type, globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false))) { - const [yieldType] = getTypeArguments(type as GenericType); - // The "return" and "next" types of `Iterable` and `IterableIterator` are defined by the - // iteration types of their `[Symbol.iterator]()` method. The same is true for their async cousins. - // While we define these as `any` and `undefined` in our libs by default, a custom lib *could* use - // different definitions. - const { returnType, nextType } = getIterationTypesOfGlobalIterableType(globalType, resolver); - return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); - } - - // As an optimization, if the type is an instantiation of the following global type, then - // just grab its related type arguments: - // - `Generator` or `AsyncGenerator` - if (isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { - const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); - return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); - } - } - - function getPropertyNameForKnownSymbolName(symbolName: string): __String { - const ctorType = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); - const uniqueType = ctorType && getTypeOfPropertyOfType(getTypeOfSymbol(ctorType), escapeLeadingUnderscores(symbolName)); - return uniqueType && isTypeUsableAsPropertyName(uniqueType) ? getPropertyNameFromType(uniqueType) : `__@${symbolName}` as __String; - } + return noIterationTypes; + } - /** - * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like - * type from its members. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, `noIterationTypes` is returned. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterable` instead. - */ - function getIterationTypesOfIterableSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { - const method = getPropertyOfType(type, getPropertyNameForKnownSymbolName(resolver.iteratorSymbolName)); - const methodType = method && !(method.flags & SymbolFlags.Optional) ? getTypeOfSymbol(method) : undefined; - if (isTypeAny(methodType)) { - return noCache ? anyIterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, anyIterationTypes); - } - - const signatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : undefined; - if (!some(signatures)) { - return noCache ? noIterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, noIterationTypes); - } - - const iteratorType = getIntersectionType(map(signatures, getReturnTypeOfSignature)); - const iterationTypes = getIterationTypesOfIteratorWorker(iteratorType, resolver, errorNode, errorOutputContainer, noCache) ?? noIterationTypes; - return noCache ? iterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, iterationTypes); - } - - function reportTypeNotIterableError(errorNode: Node, type: Type, allowAsyncIterables: boolean): Diagnostic { - const message = allowAsyncIterables - ? Diagnostics.Type_0_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator - : Diagnostics.Type_0_must_have_a_Symbol_iterator_method_that_returns_an_iterator; - const suggestAwait = - // for (const x of Promise<...>) or [...Promise<...>] - !!getAwaitedTypeOfPromise(type) - // for (const x of AsyncIterable<...>) - || ( - !allowAsyncIterables && - isForOfStatement(errorNode.parent) && - errorNode.parent.expression === errorNode && - getGlobalAsyncIterableType(/** reportErrors */ false) !== emptyGenericType && - isTypeAssignableTo(type, getGlobalAsyncIterableType(/** reportErrors */ false) - )); - return errorAndMaybeSuggestAwait(errorNode, suggestAwait, message, typeToString(type)); - } + /** + * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or + * `AsyncIterable`-like type from the cache. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableCached(type: Type, resolver: IterationTypesResolver) { + return getCachedIterationTypes(type, resolver.iterableCacheKey); + } - /** - * Gets the *yield*, *return*, and *next* types from an `Iterator`-like or `AsyncIterator`-like type. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, `undefined` is returned. - */ - function getIterationTypesOfIterator(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined) { - return getIterationTypesOfIteratorWorker(type, resolver, errorNode, errorOutputContainer, /*noCache*/ false); + function getIterationTypesOfGlobalIterableType(globalType: Type, resolver: IterationTypesResolver) { + const globalIterationTypes = + getIterationTypesOfIterableCached(globalType, resolver) || + getIterationTypesOfIterableSlow(globalType, resolver, /*errorNode*/ undefined, /*errorOutputContainer*/ undefined, /*noCache*/ false); + return globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; + } + + /** + * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like + * type from from common heuristics. + * + * If we previously analyzed this type and found no iteration types, `noIterationTypes` is + * returned. If we found iteration types, an `IterationTypes` record is returned. + * Otherwise, we return `undefined` to indicate to the caller it should perform a more + * exhaustive analysis. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableFast(type: Type, resolver: IterationTypesResolver) { + // As an optimization, if the type is an instantiation of one of the following global types, then + // just grab its related type argument: + // - `Iterable` or `AsyncIterable` + // - `IterableIterator` or `AsyncIterableIterator` + let globalType: Type; + if (isReferenceToType(type, globalType = resolver.getGlobalIterableType(/*reportErrors*/ false)) || + isReferenceToType(type, globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false))) { + const [yieldType] = getTypeArguments(type as GenericType); + // The "return" and "next" types of `Iterable` and `IterableIterator` are defined by the + // iteration types of their `[Symbol.iterator]()` method. The same is true for their async cousins. + // While we define these as `any` and `undefined` in our libs by default, a custom lib *could* use + // different definitions. + const { returnType, nextType } = getIterationTypesOfGlobalIterableType(globalType, resolver); + return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); + } + + // As an optimization, if the type is an instantiation of the following global type, then + // just grab its related type arguments: + // - `Generator` or `AsyncGenerator` + if (isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { + const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); + return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); } + } - /** - * Gets the *yield*, *return*, and *next* types from an `Iterator`-like or `AsyncIterator`-like type. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, `undefined` is returned. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterator` instead. - */ - function getIterationTypesOfIteratorWorker(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { - if (isTypeAny(type)) { - return anyIterationTypes; - } + function getPropertyNameForKnownSymbolName(symbolName: string): __String { + const ctorType = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); + const uniqueType = ctorType && getTypeOfPropertyOfType(getTypeOfSymbol(ctorType), escapeLeadingUnderscores(symbolName)); + return uniqueType && isTypeUsableAsPropertyName(uniqueType) ? getPropertyNameFromType(uniqueType) : `__@${symbolName}` as __String; + } - let iterationTypes = - getIterationTypesOfIteratorCached(type, resolver) || - getIterationTypesOfIteratorFast(type, resolver); + /** + * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like + * type from its members. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, `noIterationTypes` is returned. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterable` instead. + */ + function getIterationTypesOfIterableSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { + const method = getPropertyOfType(type, getPropertyNameForKnownSymbolName(resolver.iteratorSymbolName)); + const methodType = method && !(method.flags & SymbolFlags.Optional) ? getTypeOfSymbol(method) : undefined; + if (isTypeAny(methodType)) { + return noCache ? anyIterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, anyIterationTypes); + } + + const signatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : undefined; + if (!some(signatures)) { + return noCache ? noIterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, noIterationTypes); + } + + const iteratorType = getIntersectionType(map(signatures, getReturnTypeOfSignature)); + const iterationTypes = getIterationTypesOfIteratorWorker(iteratorType, resolver, errorNode, errorOutputContainer, noCache) ?? noIterationTypes; + return noCache ? iterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, iterationTypes); + } - if (iterationTypes === noIterationTypes && errorNode) { - iterationTypes = undefined; - noCache = true; - } + function reportTypeNotIterableError(errorNode: Node, type: Type, allowAsyncIterables: boolean): Diagnostic { + const message = allowAsyncIterables + ? Diagnostics.Type_0_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator + : Diagnostics.Type_0_must_have_a_Symbol_iterator_method_that_returns_an_iterator; + const suggestAwait = + // for (const x of Promise<...>) or [...Promise<...>] + !!getAwaitedTypeOfPromise(type) + // for (const x of AsyncIterable<...>) + || ( + !allowAsyncIterables && + isForOfStatement(errorNode.parent) && + errorNode.parent.expression === errorNode && + getGlobalAsyncIterableType(/** reportErrors */ false) !== emptyGenericType && + isTypeAssignableTo(type, getGlobalAsyncIterableType(/** reportErrors */ false) + )); + return errorAndMaybeSuggestAwait(errorNode, suggestAwait, message, typeToString(type)); + } - iterationTypes ??= getIterationTypesOfIteratorSlow(type, resolver, errorNode, errorOutputContainer, noCache); - return iterationTypes === noIterationTypes ? undefined : iterationTypes; - } + /** + * Gets the *yield*, *return*, and *next* types from an `Iterator`-like or `AsyncIterator`-like type. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, `undefined` is returned. + */ + function getIterationTypesOfIterator(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined) { + return getIterationTypesOfIteratorWorker(type, resolver, errorNode, errorOutputContainer, /*noCache*/ false); + } - /** - * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the - * cache. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterator` instead. - */ - function getIterationTypesOfIteratorCached(type: Type, resolver: IterationTypesResolver) { - return getCachedIterationTypes(type, resolver.iteratorCacheKey); + /** + * Gets the *yield*, *return*, and *next* types from an `Iterator`-like or `AsyncIterator`-like type. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, `undefined` is returned. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterator` instead. + */ + function getIterationTypesOfIteratorWorker(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { + if (isTypeAny(type)) { + return anyIterationTypes; } - /** - * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the - * cache or from common heuristics. - * - * If we previously analyzed this type and found no iteration types, `noIterationTypes` is - * returned. If we found iteration types, an `IterationTypes` record is returned. - * Otherwise, we return `undefined` to indicate to the caller it should perform a more - * exhaustive analysis. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterator` instead. - */ - function getIterationTypesOfIteratorFast(type: Type, resolver: IterationTypesResolver) { - // As an optimization, if the type is an instantiation of one of the following global types, - // then just grab its related type argument: - // - `IterableIterator` or `AsyncIterableIterator` - // - `Iterator` or `AsyncIterator` - // - `Generator` or `AsyncGenerator` - const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); - if (isReferenceToType(type, globalType)) { - const [yieldType] = getTypeArguments(type as GenericType); - // The "return" and "next" types of `IterableIterator` and `AsyncIterableIterator` are defined by the - // iteration types of their `next`, `return`, and `throw` methods. While we define these as `any` - // and `undefined` in our libs by default, a custom lib *could* use different definitions. - const globalIterationTypes = - getIterationTypesOfIteratorCached(globalType, resolver) || - getIterationTypesOfIteratorSlow(globalType, resolver, /*errorNode*/ undefined, /*errorOutputContainer*/ undefined, /*noCache*/ false); - const { returnType, nextType } = globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; - return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType)); - } - if (isReferenceToType(type, resolver.getGlobalIteratorType(/*reportErrors*/ false)) || - isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { - const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); - return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType)); - } - } + let iterationTypes = + getIterationTypesOfIteratorCached(type, resolver) || + getIterationTypesOfIteratorFast(type, resolver); - function isIteratorResult(type: Type, kind: IterationTypeKind.Yield | IterationTypeKind.Return) { - // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface: - // > [done] is the result status of an iterator `next` method call. If the end of the iterator was reached `done` is `true`. - // > If the end was not reached `done` is `false` and a value is available. - // > If a `done` property (either own or inherited) does not exist, it is consider to have the value `false`. - const doneType = getTypeOfPropertyOfType(type, "done" as __String) || falseType; - return isTypeAssignableTo(kind === IterationTypeKind.Yield ? falseType : trueType, doneType); + if (iterationTypes === noIterationTypes && errorNode) { + iterationTypes = undefined; + noCache = true; } - function isYieldIteratorResult(type: Type) { - return isIteratorResult(type, IterationTypeKind.Yield); - } + iterationTypes ??= getIterationTypesOfIteratorSlow(type, resolver, errorNode, errorOutputContainer, noCache); + return iterationTypes === noIterationTypes ? undefined : iterationTypes; + } + + /** + * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the + * cache. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterator` instead. + */ + function getIterationTypesOfIteratorCached(type: Type, resolver: IterationTypesResolver) { + return getCachedIterationTypes(type, resolver.iteratorCacheKey); + } - function isReturnIteratorResult(type: Type) { - return isIteratorResult(type, IterationTypeKind.Return); + /** + * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the + * cache or from common heuristics. + * + * If we previously analyzed this type and found no iteration types, `noIterationTypes` is + * returned. If we found iteration types, an `IterationTypes` record is returned. + * Otherwise, we return `undefined` to indicate to the caller it should perform a more + * exhaustive analysis. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterator` instead. + */ + function getIterationTypesOfIteratorFast(type: Type, resolver: IterationTypesResolver) { + // As an optimization, if the type is an instantiation of one of the following global types, + // then just grab its related type argument: + // - `IterableIterator` or `AsyncIterableIterator` + // - `Iterator` or `AsyncIterator` + // - `Generator` or `AsyncGenerator` + const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); + if (isReferenceToType(type, globalType)) { + const [yieldType] = getTypeArguments(type as GenericType); + // The "return" and "next" types of `IterableIterator` and `AsyncIterableIterator` are defined by the + // iteration types of their `next`, `return`, and `throw` methods. While we define these as `any` + // and `undefined` in our libs by default, a custom lib *could* use different definitions. + const globalIterationTypes = + getIterationTypesOfIteratorCached(globalType, resolver) || + getIterationTypesOfIteratorSlow(globalType, resolver, /*errorNode*/ undefined, /*errorOutputContainer*/ undefined, /*noCache*/ false); + const { returnType, nextType } = globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; + return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType)); } + if (isReferenceToType(type, resolver.getGlobalIteratorType(/*reportErrors*/ false)) || + isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { + const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); + return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType)); + } + } - /** - * Gets the *yield* and *return* types of an `IteratorResult`-like type. - * - * If we are unable to determine a *yield* or a *return* type, `noIterationTypes` is - * returned to indicate to the caller that it should handle the error. Otherwise, an - * `IterationTypes` record is returned. - */ - function getIterationTypesOfIteratorResult(type: Type) { - if (isTypeAny(type)) { - return anyIterationTypes; - } + function isIteratorResult(type: Type, kind: IterationTypeKind.Yield | IterationTypeKind.Return) { + // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface: + // > [done] is the result status of an iterator `next` method call. If the end of the iterator was reached `done` is `true`. + // > If the end was not reached `done` is `false` and a value is available. + // > If a `done` property (either own or inherited) does not exist, it is consider to have the value `false`. + const doneType = getTypeOfPropertyOfType(type, "done" as __String) || falseType; + return isTypeAssignableTo(kind === IterationTypeKind.Yield ? falseType : trueType, doneType); + } - const cachedTypes = getCachedIterationTypes(type, "iterationTypesOfIteratorResult"); - if (cachedTypes) { - return cachedTypes; - } + function isYieldIteratorResult(type: Type) { + return isIteratorResult(type, IterationTypeKind.Yield); + } - // As an optimization, if the type is an instantiation of one of the global `IteratorYieldResult` - // or `IteratorReturnResult` types, then just grab its type argument. - if (isReferenceToType(type, getGlobalIteratorYieldResultType(/*reportErrors*/ false))) { - const yieldType = getTypeArguments(type as GenericType)[0]; - return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, /*returnType*/ undefined, /*nextType*/ undefined)); - } - if (isReferenceToType(type, getGlobalIteratorReturnResultType(/*reportErrors*/ false))) { - const returnType = getTypeArguments(type as GenericType)[0]; - return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(/*yieldType*/ undefined, returnType, /*nextType*/ undefined)); - } + function isReturnIteratorResult(type: Type) { + return isIteratorResult(type, IterationTypeKind.Return); + } - // Choose any constituents that can produce the requested iteration type. - const yieldIteratorResult = filterType(type, isYieldIteratorResult); - const yieldType = yieldIteratorResult !== neverType ? getTypeOfPropertyOfType(yieldIteratorResult, "value" as __String) : undefined; + /** + * Gets the *yield* and *return* types of an `IteratorResult`-like type. + * + * If we are unable to determine a *yield* or a *return* type, `noIterationTypes` is + * returned to indicate to the caller that it should handle the error. Otherwise, an + * `IterationTypes` record is returned. + */ + function getIterationTypesOfIteratorResult(type: Type) { + if (isTypeAny(type)) { + return anyIterationTypes; + } - const returnIteratorResult = filterType(type, isReturnIteratorResult); - const returnType = returnIteratorResult !== neverType ? getTypeOfPropertyOfType(returnIteratorResult, "value" as __String) : undefined; + const cachedTypes = getCachedIterationTypes(type, "iterationTypesOfIteratorResult"); + if (cachedTypes) { + return cachedTypes; + } - if (!yieldType && !returnType) { - return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", noIterationTypes); - } + // As an optimization, if the type is an instantiation of one of the global `IteratorYieldResult` + // or `IteratorReturnResult` types, then just grab its type argument. + if (isReferenceToType(type, getGlobalIteratorYieldResultType(/*reportErrors*/ false))) { + const yieldType = getTypeArguments(type as GenericType)[0]; + return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, /*returnType*/ undefined, /*nextType*/ undefined)); + } + if (isReferenceToType(type, getGlobalIteratorReturnResultType(/*reportErrors*/ false))) { + const returnType = getTypeArguments(type as GenericType)[0]; + return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(/*yieldType*/ undefined, returnType, /*nextType*/ undefined)); + } + + // Choose any constituents that can produce the requested iteration type. + const yieldIteratorResult = filterType(type, isYieldIteratorResult); + const yieldType = yieldIteratorResult !== neverType ? getTypeOfPropertyOfType(yieldIteratorResult, "value" as __String) : undefined; + + const returnIteratorResult = filterType(type, isReturnIteratorResult); + const returnType = returnIteratorResult !== neverType ? getTypeOfPropertyOfType(returnIteratorResult, "value" as __String) : undefined; - // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface - // > ... If the iterator does not have a return value, `value` is `undefined`. In that case, the - // > `value` property may be absent from the conforming object if it does not inherit an explicit - // > `value` property. - return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, returnType || voidType, /*nextType*/ undefined)); + if (!yieldType && !returnType) { + return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", noIterationTypes); } - /** - * Gets the *yield*, *return*, and *next* types of a the `next()`, `return()`, or - * `throw()` method of an `Iterator`-like or `AsyncIterator`-like type. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, we return `undefined`. - */ - function getIterationTypesOfMethod(type: Type, resolver: IterationTypesResolver, methodName: "next" | "return" | "throw", errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined): IterationTypes | undefined { - const method = getPropertyOfType(type, methodName as __String); + // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface + // > ... If the iterator does not have a return value, `value` is `undefined`. In that case, the + // > `value` property may be absent from the conforming object if it does not inherit an explicit + // > `value` property. + return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, returnType || voidType, /*nextType*/ undefined)); + } - // Ignore 'return' or 'throw' if they are missing. - if (!method && methodName !== "next") { - return undefined; - } + /** + * Gets the *yield*, *return*, and *next* types of a the `next()`, `return()`, or + * `throw()` method of an `Iterator`-like or `AsyncIterator`-like type. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, we return `undefined`. + */ + function getIterationTypesOfMethod(type: Type, resolver: IterationTypesResolver, methodName: "next" | "return" | "throw", errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined): IterationTypes | undefined { + const method = getPropertyOfType(type, methodName as __String); + + // Ignore 'return' or 'throw' if they are missing. + if (!method && methodName !== "next") { + return undefined; + } - const methodType = method && !(methodName === "next" && (method.flags & SymbolFlags.Optional)) - ? methodName === "next" ? getTypeOfSymbol(method) : getTypeWithFacts(getTypeOfSymbol(method), TypeFacts.NEUndefinedOrNull) - : undefined; + const methodType = method && !(methodName === "next" && (method.flags & SymbolFlags.Optional)) + ? methodName === "next" ? getTypeOfSymbol(method) : getTypeWithFacts(getTypeOfSymbol(method), TypeFacts.NEUndefinedOrNull) + : undefined; - if (isTypeAny(methodType)) { - // `return()` and `throw()` don't provide a *next* type. - return methodName === "next" ? anyIterationTypes : anyIterationTypesExceptNext; - } + if (isTypeAny(methodType)) { + // `return()` and `throw()` don't provide a *next* type. + return methodName === "next" ? anyIterationTypes : anyIterationTypesExceptNext; + } - // Both async and non-async iterators *must* have a `next` method. - const methodSignatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : emptyArray; - if (methodSignatures.length === 0) { - if (errorNode) { - const diagnostic = methodName === "next" - ? resolver.mustHaveANextMethodDiagnostic - : resolver.mustBeAMethodDiagnostic; - if (errorOutputContainer) { - errorOutputContainer.errors ??= []; - errorOutputContainer.errors.push(createDiagnosticForNode(errorNode, diagnostic, methodName)); - } - else { - error(errorNode, diagnostic, methodName); - } + // Both async and non-async iterators *must* have a `next` method. + const methodSignatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : emptyArray; + if (methodSignatures.length === 0) { + if (errorNode) { + const diagnostic = methodName === "next" + ? resolver.mustHaveANextMethodDiagnostic + : resolver.mustBeAMethodDiagnostic; + if (errorOutputContainer) { + errorOutputContainer.errors ??= []; + errorOutputContainer.errors.push(createDiagnosticForNode(errorNode, diagnostic, methodName)); } - return methodName === "next" ? noIterationTypes : undefined; - } - - // If the method signature comes exclusively from the global iterator or generator type, - // create iteration types from its type arguments like `getIterationTypesOfIteratorFast` - // does (so as to remove `undefined` from the next and return types). We arrive here when - // a contextual type for a generator was not a direct reference to one of those global types, - // but looking up `methodType` referred to one of them (and nothing else). E.g., in - // `interface SpecialIterator extends Iterator {}`, `SpecialIterator` is not a - // reference to `Iterator`, but its `next` member derives exclusively from `Iterator`. - if (methodType?.symbol && methodSignatures.length === 1) { - const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); - const globalIteratorType = resolver.getGlobalIteratorType(/*reportErrors*/ false); - const isGeneratorMethod = globalGeneratorType.symbol?.members?.get(methodName as __String) === methodType.symbol; - const isIteratorMethod = !isGeneratorMethod && globalIteratorType.symbol?.members?.get(methodName as __String) === methodType.symbol; - if (isGeneratorMethod || isIteratorMethod) { - const globalType = isGeneratorMethod ? globalGeneratorType : globalIteratorType; - const { mapper } = methodType as AnonymousType; - return createIterationTypes( - getMappedType(globalType.typeParameters![0], mapper!), - getMappedType(globalType.typeParameters![1], mapper!), - methodName === "next" ? getMappedType(globalType.typeParameters![2], mapper!) : undefined); + else { + error(errorNode, diagnostic, methodName); } } + return methodName === "next" ? noIterationTypes : undefined; + } - // Extract the first parameter and return type of each signature. - let methodParameterTypes: Type[] | undefined; - let methodReturnTypes: Type[] | undefined; - for (const signature of methodSignatures) { - if (methodName !== "throw" && some(signature.parameters)) { - methodParameterTypes = append(methodParameterTypes, getTypeAtPosition(signature, 0)); - } - methodReturnTypes = append(methodReturnTypes, getReturnTypeOfSignature(signature)); + // If the method signature comes exclusively from the global iterator or generator type, + // create iteration types from its type arguments like `getIterationTypesOfIteratorFast` + // does (so as to remove `undefined` from the next and return types). We arrive here when + // a contextual type for a generator was not a direct reference to one of those global types, + // but looking up `methodType` referred to one of them (and nothing else). E.g., in + // `interface SpecialIterator extends Iterator {}`, `SpecialIterator` is not a + // reference to `Iterator`, but its `next` member derives exclusively from `Iterator`. + if (methodType?.symbol && methodSignatures.length === 1) { + const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); + const globalIteratorType = resolver.getGlobalIteratorType(/*reportErrors*/ false); + const isGeneratorMethod = globalGeneratorType.symbol?.members?.get(methodName as __String) === methodType.symbol; + const isIteratorMethod = !isGeneratorMethod && globalIteratorType.symbol?.members?.get(methodName as __String) === methodType.symbol; + if (isGeneratorMethod || isIteratorMethod) { + const globalType = isGeneratorMethod ? globalGeneratorType : globalIteratorType; + const { mapper } = methodType as AnonymousType; + return createIterationTypes( + getMappedType(globalType.typeParameters![0], mapper!), + getMappedType(globalType.typeParameters![1], mapper!), + methodName === "next" ? getMappedType(globalType.typeParameters![2], mapper!) : undefined); } + } - // Resolve the *next* or *return* type from the first parameter of a `next()` or - // `return()` method, respectively. - let returnTypes: Type[] | undefined; - let nextType: Type | undefined; - if (methodName !== "throw") { - const methodParameterType = methodParameterTypes ? getUnionType(methodParameterTypes) : unknownType; - if (methodName === "next") { - // The value of `next(value)` is *not* awaited by async generators - nextType = methodParameterType; - } - else if (methodName === "return") { - // The value of `return(value)` *is* awaited by async generators - const resolvedMethodParameterType = resolver.resolveIterationType(methodParameterType, errorNode) || anyType; - returnTypes = append(returnTypes, resolvedMethodParameterType); - } + // Extract the first parameter and return type of each signature. + let methodParameterTypes: Type[] | undefined; + let methodReturnTypes: Type[] | undefined; + for (const signature of methodSignatures) { + if (methodName !== "throw" && some(signature.parameters)) { + methodParameterTypes = append(methodParameterTypes, getTypeAtPosition(signature, 0)); } + methodReturnTypes = append(methodReturnTypes, getReturnTypeOfSignature(signature)); + } - // Resolve the *yield* and *return* types from the return type of the method (i.e. `IteratorResult`) - let yieldType: Type; - const methodReturnType = methodReturnTypes ? getIntersectionType(methodReturnTypes) : neverType; - const resolvedMethodReturnType = resolver.resolveIterationType(methodReturnType, errorNode) || anyType; - const iterationTypes = getIterationTypesOfIteratorResult(resolvedMethodReturnType); - if (iterationTypes === noIterationTypes) { - if (errorNode) { - if (errorOutputContainer) { - errorOutputContainer.errors ??= []; - errorOutputContainer.errors.push(createDiagnosticForNode(errorNode, resolver.mustHaveAValueDiagnostic, methodName)); - } - else { - error(errorNode, resolver.mustHaveAValueDiagnostic, methodName); - } - } - yieldType = anyType; - returnTypes = append(returnTypes, anyType); + // Resolve the *next* or *return* type from the first parameter of a `next()` or + // `return()` method, respectively. + let returnTypes: Type[] | undefined; + let nextType: Type | undefined; + if (methodName !== "throw") { + const methodParameterType = methodParameterTypes ? getUnionType(methodParameterTypes) : unknownType; + if (methodName === "next") { + // The value of `next(value)` is *not* awaited by async generators + nextType = methodParameterType; } - else { - yieldType = iterationTypes.yieldType; - returnTypes = append(returnTypes, iterationTypes.returnType); + else if (methodName === "return") { + // The value of `return(value)` *is* awaited by async generators + const resolvedMethodParameterType = resolver.resolveIterationType(methodParameterType, errorNode) || anyType; + returnTypes = append(returnTypes, resolvedMethodParameterType); } - - return createIterationTypes(yieldType, getUnionType(returnTypes), nextType); } - /** - * Gets the *yield*, *return*, and *next* types of an `Iterator`-like or `AsyncIterator`-like - * type from its members. - * - * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` - * record is returned. Otherwise, `noIterationTypes` is returned. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterator` instead. - */ - function getIterationTypesOfIteratorSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { - const iterationTypes = combineIterationTypes([ - getIterationTypesOfMethod(type, resolver, "next", errorNode, errorOutputContainer), - getIterationTypesOfMethod(type, resolver, "return", errorNode, errorOutputContainer), - getIterationTypesOfMethod(type, resolver, "throw", errorNode, errorOutputContainer), - ]); - return noCache ? iterationTypes : setCachedIterationTypes(type, resolver.iteratorCacheKey, iterationTypes); + // Resolve the *yield* and *return* types from the return type of the method (i.e. `IteratorResult`) + let yieldType: Type; + const methodReturnType = methodReturnTypes ? getIntersectionType(methodReturnTypes) : neverType; + const resolvedMethodReturnType = resolver.resolveIterationType(methodReturnType, errorNode) || anyType; + const iterationTypes = getIterationTypesOfIteratorResult(resolvedMethodReturnType); + if (iterationTypes === noIterationTypes) { + if (errorNode) { + if (errorOutputContainer) { + errorOutputContainer.errors ??= []; + errorOutputContainer.errors.push(createDiagnosticForNode(errorNode, resolver.mustHaveAValueDiagnostic, methodName)); + } + else { + error(errorNode, resolver.mustHaveAValueDiagnostic, methodName); + } + } + yieldType = anyType; + returnTypes = append(returnTypes, anyType); + } + else { + yieldType = iterationTypes.yieldType; + returnTypes = append(returnTypes, iterationTypes.returnType); } - /** - * Gets the requested "iteration type" from a type that is either `Iterable`-like, `Iterator`-like, - * `IterableIterator`-like, or `Generator`-like (for a non-async generator); or `AsyncIterable`-like, - * `AsyncIterator`-like, `AsyncIterableIterator`-like, or `AsyncGenerator`-like (for an async generator). - */ - function getIterationTypeOfGeneratorFunctionReturnType(kind: IterationTypeKind, returnType: Type, isAsyncGenerator: boolean): Type | undefined { - if (isTypeAny(returnType)) { - return undefined; - } + return createIterationTypes(yieldType, getUnionType(returnTypes), nextType); + } + + /** + * Gets the *yield*, *return*, and *next* types of an `Iterator`-like or `AsyncIterator`-like + * type from its members. + * + * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` + * record is returned. Otherwise, `noIterationTypes` is returned. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterator` instead. + */ + function getIterationTypesOfIteratorSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { + const iterationTypes = combineIterationTypes([ + getIterationTypesOfMethod(type, resolver, "next", errorNode, errorOutputContainer), + getIterationTypesOfMethod(type, resolver, "return", errorNode, errorOutputContainer), + getIterationTypesOfMethod(type, resolver, "throw", errorNode, errorOutputContainer), + ]); + return noCache ? iterationTypes : setCachedIterationTypes(type, resolver.iteratorCacheKey, iterationTypes); + } - const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsyncGenerator); - return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(kind)]; + /** + * Gets the requested "iteration type" from a type that is either `Iterable`-like, `Iterator`-like, + * `IterableIterator`-like, or `Generator`-like (for a non-async generator); or `AsyncIterable`-like, + * `AsyncIterator`-like, `AsyncIterableIterator`-like, or `AsyncGenerator`-like (for an async generator). + */ + function getIterationTypeOfGeneratorFunctionReturnType(kind: IterationTypeKind, returnType: Type, isAsyncGenerator: boolean): Type | undefined { + if (isTypeAny(returnType)) { + return undefined; } - function getIterationTypesOfGeneratorFunctionReturnType(type: Type, isAsyncGenerator: boolean) { - if (isTypeAny(type)) { - return anyIterationTypes; - } + const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsyncGenerator); + return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(kind)]; + } - const use = isAsyncGenerator ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType; - const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; - return getIterationTypesOfIterable(type, use, /*errorNode*/ undefined) || - getIterationTypesOfIterator(type, resolver, /*errorNode*/ undefined, /*errorOutputContainer*/ undefined); + function getIterationTypesOfGeneratorFunctionReturnType(type: Type, isAsyncGenerator: boolean) { + if (isTypeAny(type)) { + return anyIterationTypes; } - function checkBreakOrContinueStatement(node: BreakOrContinueStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) checkGrammarBreakOrContinueStatement(node); + const use = isAsyncGenerator ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType; + const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; + return getIterationTypesOfIterable(type, use, /*errorNode*/ undefined) || + getIterationTypesOfIterator(type, resolver, /*errorNode*/ undefined, /*errorOutputContainer*/ undefined); + } - // TODO: Check that target label is valid - } + function checkBreakOrContinueStatement(node: BreakOrContinueStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) checkGrammarBreakOrContinueStatement(node); - function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) { - const isGenerator = !!(functionFlags & FunctionFlags.Generator); - const isAsync = !!(functionFlags & FunctionFlags.Async); - if (isGenerator) { - const returnIterationType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync); - if (!returnIterationType) { - return errorType; - } - return isAsync ? getAwaitedTypeNoAlias(unwrapAwaitedType(returnIterationType)) : returnIterationType; + // TODO: Check that target label is valid + } + + function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) { + const isGenerator = !!(functionFlags & FunctionFlags.Generator); + const isAsync = !!(functionFlags & FunctionFlags.Async); + if (isGenerator) { + const returnIterationType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync); + if (!returnIterationType) { + return errorType; } - return isAsync ? getAwaitedTypeNoAlias(returnType) || errorType : returnType; + return isAsync ? getAwaitedTypeNoAlias(unwrapAwaitedType(returnIterationType)) : returnIterationType; } + return isAsync ? getAwaitedTypeNoAlias(returnType) || errorType : returnType; + } - function isUnwrappedReturnTypeVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean { - const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(func)); - return !!unwrappedReturnType && maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.AnyOrUnknown); - } + function isUnwrappedReturnTypeVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean { + const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(func)); + return !!unwrappedReturnType && maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.AnyOrUnknown); + } - function checkReturnStatement(node: ReturnStatement) { - // Grammar checking - if (checkGrammarStatementInAmbientContext(node)) { - return; - } + function checkReturnStatement(node: ReturnStatement) { + // Grammar checking + if (checkGrammarStatementInAmbientContext(node)) { + return; + } - const container = getContainingFunctionOrClassStaticBlock(node); - if(container && isClassStaticBlockDeclaration(container)) { - grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_cannot_be_used_inside_a_class_static_block); - return; - } + const container = getContainingFunctionOrClassStaticBlock(node); + if(container && isClassStaticBlockDeclaration(container)) { + grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_cannot_be_used_inside_a_class_static_block); + return; + } - if (!container) { - grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body); - return; - } + if (!container) { + grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body); + return; + } - const signature = getSignatureFromDeclaration(container); - const returnType = getReturnTypeOfSignature(signature); - const functionFlags = getFunctionFlags(container); - if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { - const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; - if (container.kind === SyntaxKind.SetAccessor) { - if (node.expression) { - error(node, Diagnostics.Setters_cannot_return_a_value); - } + const signature = getSignatureFromDeclaration(container); + const returnType = getReturnTypeOfSignature(signature); + const functionFlags = getFunctionFlags(container); + if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { + const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; + if (container.kind === SyntaxKind.SetAccessor) { + if (node.expression) { + error(node, Diagnostics.Setters_cannot_return_a_value); } - else if (container.kind === SyntaxKind.Constructor) { - if (node.expression && !checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression)) { - error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); - } - } - else if (getReturnTypeFromAnnotation(container)) { - const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; - const unwrappedExprType = functionFlags & FunctionFlags.Async - ? checkAwaitedType(exprType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) - : exprType; - if (unwrappedReturnType) { - // If the function has a return type, but promisedType is - // undefined, an error will be reported in checkAsyncFunctionReturnType - // so we don't need to report one here. - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); - } + } + else if (container.kind === SyntaxKind.Constructor) { + if (node.expression && !checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression)) { + error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); } } - else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(container, returnType)) { - // The function has a return type, but the return statement doesn't have an expression. - error(node, Diagnostics.Not_all_code_paths_return_a_value); + else if (getReturnTypeFromAnnotation(container)) { + const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; + const unwrappedExprType = functionFlags & FunctionFlags.Async + ? checkAwaitedType(exprType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) + : exprType; + if (unwrappedReturnType) { + // If the function has a return type, but promisedType is + // undefined, an error will be reported in checkAsyncFunctionReturnType + // so we don't need to report one here. + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); + } } } + else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(container, returnType)) { + // The function has a return type, but the return statement doesn't have an expression. + error(node, Diagnostics.Not_all_code_paths_return_a_value); + } + } - function checkWithStatement(node: WithStatement) { - // Grammar checking for withStatement - if (!checkGrammarStatementInAmbientContext(node)) { - if (node.flags & NodeFlags.AwaitContext) { - grammarErrorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_an_async_function_block); - } + function checkWithStatement(node: WithStatement) { + // Grammar checking for withStatement + if (!checkGrammarStatementInAmbientContext(node)) { + if (node.flags & NodeFlags.AwaitContext) { + grammarErrorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_an_async_function_block); } + } - checkExpression(node.expression); + checkExpression(node.expression); - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const start = getSpanOfTokenAtPosition(sourceFile, node.pos).start; - const end = node.statement.pos; - grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.The_with_statement_is_not_supported_All_symbols_in_a_with_block_will_have_type_any); - } + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const start = getSpanOfTokenAtPosition(sourceFile, node.pos).start; + const end = node.statement.pos; + grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.The_with_statement_is_not_supported_All_symbols_in_a_with_block_will_have_type_any); } + } - function checkSwitchStatement(node: SwitchStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); - - let firstDefaultClause: CaseOrDefaultClause; - let hasDuplicateDefaultClause = false; + function checkSwitchStatement(node: SwitchStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); - const expressionType = checkExpression(node.expression); - const expressionIsLiteral = isLiteralType(expressionType); - forEach(node.caseBlock.clauses, clause => { - // Grammar check for duplicate default clauses, skip if we already report duplicate default clause - if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) { - if (firstDefaultClause === undefined) { - firstDefaultClause = clause; - } - else { - grammarErrorOnNode(clause, Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement); - hasDuplicateDefaultClause = true; - } - } + let firstDefaultClause: CaseOrDefaultClause; + let hasDuplicateDefaultClause = false; - if (clause.kind === SyntaxKind.CaseClause) { - addLazyDiagnostic(createLazyCaseClauseDiagnostics(clause)); + const expressionType = checkExpression(node.expression); + const expressionIsLiteral = isLiteralType(expressionType); + forEach(node.caseBlock.clauses, clause => { + // Grammar check for duplicate default clauses, skip if we already report duplicate default clause + if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) { + if (firstDefaultClause === undefined) { + firstDefaultClause = clause; } - forEach(clause.statements, checkSourceElement); - if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { - error(clause, Diagnostics.Fallthrough_case_in_switch); + else { + grammarErrorOnNode(clause, Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement); + hasDuplicateDefaultClause = true; } + } - function createLazyCaseClauseDiagnostics(clause: CaseClause) { - return () => { - // TypeScript 1.0 spec (April 2014): 5.9 - // In a 'switch' statement, each 'case' expression must be of a type that is comparable - // to or from the type of the 'switch' expression. - let caseType = checkExpression(clause.expression); - const caseIsLiteral = isLiteralType(caseType); - let comparedExpressionType = expressionType; - if (!caseIsLiteral || !expressionIsLiteral) { - caseType = caseIsLiteral ? getBaseTypeOfLiteralType(caseType) : caseType; - comparedExpressionType = getBaseTypeOfLiteralType(expressionType); - } - if (!isTypeEqualityComparableTo(comparedExpressionType, caseType)) { - // expressionType is not comparable to caseType, try the reversed check and report errors if it fails - checkTypeComparableTo(caseType, comparedExpressionType, clause.expression, /*headMessage*/ undefined); - } - }; - } - }); - if (node.caseBlock.locals) { - registerForUnusedIdentifiersCheck(node.caseBlock); + if (clause.kind === SyntaxKind.CaseClause) { + addLazyDiagnostic(createLazyCaseClauseDiagnostics(clause)); + } + forEach(clause.statements, checkSourceElement); + if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { + error(clause, Diagnostics.Fallthrough_case_in_switch); } - } - function checkLabeledStatement(node: LabeledStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - findAncestor(node.parent, current => { - if (isFunctionLike(current)) { - return "quit"; - } - if (current.kind === SyntaxKind.LabeledStatement && (current as LabeledStatement).label.escapedText === node.label.escapedText) { - grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNode(node.label)); - return true; + function createLazyCaseClauseDiagnostics(clause: CaseClause) { + return () => { + // TypeScript 1.0 spec (April 2014): 5.9 + // In a 'switch' statement, each 'case' expression must be of a type that is comparable + // to or from the type of the 'switch' expression. + let caseType = checkExpression(clause.expression); + const caseIsLiteral = isLiteralType(caseType); + let comparedExpressionType = expressionType; + if (!caseIsLiteral || !expressionIsLiteral) { + caseType = caseIsLiteral ? getBaseTypeOfLiteralType(caseType) : caseType; + comparedExpressionType = getBaseTypeOfLiteralType(expressionType); + } + if (!isTypeEqualityComparableTo(comparedExpressionType, caseType)) { + // expressionType is not comparable to caseType, try the reversed check and report errors if it fails + checkTypeComparableTo(caseType, comparedExpressionType, clause.expression, /*headMessage*/ undefined); } - return false; - }); + }; } - - // ensure that label is unique - checkSourceElement(node.statement); + }); + if (node.caseBlock.locals) { + registerForUnusedIdentifiersCheck(node.caseBlock); } + } - function checkThrowStatement(node: ThrowStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - if (isIdentifier(node.expression) && !node.expression.escapedText) { - grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here); + function checkLabeledStatement(node: LabeledStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + findAncestor(node.parent, current => { + if (isFunctionLike(current)) { + return "quit"; } - } + if (current.kind === SyntaxKind.LabeledStatement && (current as LabeledStatement).label.escapedText === node.label.escapedText) { + grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNode(node.label)); + return true; + } + return false; + }); + } + + // ensure that label is unique + checkSourceElement(node.statement); + } - if (node.expression) { - checkExpression(node.expression); + function checkThrowStatement(node: ThrowStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + if (isIdentifier(node.expression) && !node.expression.escapedText) { + grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here); } } - function checkTryStatement(node: TryStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + if (node.expression) { + checkExpression(node.expression); + } + } - checkBlock(node.tryBlock); - const catchClause = node.catchClause; - if (catchClause) { - // Grammar checking - if (catchClause.variableDeclaration) { - const declaration = catchClause.variableDeclaration; - const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration)); - if (typeNode) { - const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false, CheckMode.Normal); - if (type && !(type.flags & TypeFlags.AnyOrUnknown)) { - grammarErrorOnFirstToken(typeNode, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified); - } - } - else if (declaration.initializer) { - grammarErrorOnFirstToken(declaration.initializer, Diagnostics.Catch_clause_variable_cannot_have_an_initializer); + function checkTryStatement(node: TryStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + checkBlock(node.tryBlock); + const catchClause = node.catchClause; + if (catchClause) { + // Grammar checking + if (catchClause.variableDeclaration) { + const declaration = catchClause.variableDeclaration; + const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration)); + if (typeNode) { + const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false, CheckMode.Normal); + if (type && !(type.flags & TypeFlags.AnyOrUnknown)) { + grammarErrorOnFirstToken(typeNode, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified); } - else { - const blockLocals = catchClause.block.locals; - if (blockLocals) { - forEachKey(catchClause.locals!, caughtName => { - const blockLocal = blockLocals.get(caughtName); - if (blockLocal?.valueDeclaration && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) { - grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName); - } - }); - } + } + else if (declaration.initializer) { + grammarErrorOnFirstToken(declaration.initializer, Diagnostics.Catch_clause_variable_cannot_have_an_initializer); + } + else { + const blockLocals = catchClause.block.locals; + if (blockLocals) { + forEachKey(catchClause.locals!, caughtName => { + const blockLocal = blockLocals.get(caughtName); + if (blockLocal?.valueDeclaration && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) { + grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName); + } + }); } } - - checkBlock(catchClause.block); } - if (node.finallyBlock) { - checkBlock(node.finallyBlock); - } + checkBlock(catchClause.block); } - function checkIndexConstraints(type: Type, symbol: Symbol, isStaticIndex?: boolean) { - const indexInfos = getIndexInfosOfType(type); - if (indexInfos.length === 0) { - return; - } - for (const prop of getPropertiesOfObjectType(type)) { - if (!(isStaticIndex && prop.flags & SymbolFlags.Prototype)) { - checkIndexConstraintForProperty(type, prop, getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique, /*includeNonPublic*/ true), getNonMissingTypeOfSymbol(prop)); - } + if (node.finallyBlock) { + checkBlock(node.finallyBlock); + } + } + + function checkIndexConstraints(type: Type, symbol: Symbol, isStaticIndex?: boolean) { + const indexInfos = getIndexInfosOfType(type); + if (indexInfos.length === 0) { + return; + } + for (const prop of getPropertiesOfObjectType(type)) { + if (!(isStaticIndex && prop.flags & SymbolFlags.Prototype)) { + checkIndexConstraintForProperty(type, prop, getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique, /*includeNonPublic*/ true), getNonMissingTypeOfSymbol(prop)); } - const typeDeclaration = symbol.valueDeclaration; - if (typeDeclaration && isClassLike(typeDeclaration)) { - for (const member of typeDeclaration.members) { - // Only process instance properties with computed names here. Static properties cannot be in conflict with indexers, - // and properties with literal names were already checked. - if (!isStatic(member) && !hasBindableName(member)) { - const symbol = getSymbolOfNode(member); - checkIndexConstraintForProperty(type, symbol, getTypeOfExpression((member as DynamicNamedDeclaration).name.expression), getNonMissingTypeOfSymbol(symbol)); - } + } + const typeDeclaration = symbol.valueDeclaration; + if (typeDeclaration && isClassLike(typeDeclaration)) { + for (const member of typeDeclaration.members) { + // Only process instance properties with computed names here. Static properties cannot be in conflict with indexers, + // and properties with literal names were already checked. + if (!isStatic(member) && !hasBindableName(member)) { + const symbol = getSymbolOfNode(member); + checkIndexConstraintForProperty(type, symbol, getTypeOfExpression((member as DynamicNamedDeclaration).name.expression), getNonMissingTypeOfSymbol(symbol)); } } - if (indexInfos.length > 1) { - for (const info of indexInfos) { - checkIndexConstraintForIndexSignature(type, info); - } + } + if (indexInfos.length > 1) { + for (const info of indexInfos) { + checkIndexConstraintForIndexSignature(type, info); } } + } - function checkIndexConstraintForProperty(type: Type, prop: Symbol, propNameType: Type, propType: Type) { - const declaration = prop.valueDeclaration; - const name = getNameOfDeclaration(declaration); - if (name && isPrivateIdentifier(name)) { - return; - } - const indexInfos = getApplicableIndexInfos(type, propNameType); - const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; - const propDeclaration = declaration && declaration.kind === SyntaxKind.BinaryExpression || - name && name.kind === SyntaxKind.ComputedPropertyName ? declaration : undefined; - const localPropDeclaration = getParentOfSymbol(prop) === type.symbol ? declaration : undefined; - for (const info of indexInfos) { - const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfNode(info.declaration)) === type.symbol ? info.declaration : undefined; - // We check only when (a) the property is declared in the containing type, or (b) the applicable index signature is declared - // in the containing type, or (c) the containing type is an interface and no base interface contains both the property and - // the index signature (i.e. property and index signature are declared in separate inherited interfaces). - const errorNode = localPropDeclaration || localIndexDeclaration || - (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getPropertyOfObjectType(base, prop.escapedName) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); - if (errorNode && !isTypeAssignableTo(propType, info.type)) { - const diagnostic = createError(errorNode, Diagnostics.Property_0_of_type_1_is_not_assignable_to_2_index_type_3, - symbolToString(prop), typeToString(propType), typeToString(info.keyType), typeToString(info.type)); - if (propDeclaration && errorNode !== propDeclaration) { - addRelatedInfo(diagnostic, createDiagnosticForNode(propDeclaration, Diagnostics._0_is_declared_here, symbolToString(prop))); - } - diagnostics.add(diagnostic); + function checkIndexConstraintForProperty(type: Type, prop: Symbol, propNameType: Type, propType: Type) { + const declaration = prop.valueDeclaration; + const name = getNameOfDeclaration(declaration); + if (name && isPrivateIdentifier(name)) { + return; + } + const indexInfos = getApplicableIndexInfos(type, propNameType); + const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; + const propDeclaration = declaration && declaration.kind === SyntaxKind.BinaryExpression || + name && name.kind === SyntaxKind.ComputedPropertyName ? declaration : undefined; + const localPropDeclaration = getParentOfSymbol(prop) === type.symbol ? declaration : undefined; + for (const info of indexInfos) { + const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfNode(info.declaration)) === type.symbol ? info.declaration : undefined; + // We check only when (a) the property is declared in the containing type, or (b) the applicable index signature is declared + // in the containing type, or (c) the containing type is an interface and no base interface contains both the property and + // the index signature (i.e. property and index signature are declared in separate inherited interfaces). + const errorNode = localPropDeclaration || localIndexDeclaration || + (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getPropertyOfObjectType(base, prop.escapedName) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); + if (errorNode && !isTypeAssignableTo(propType, info.type)) { + const diagnostic = createError(errorNode, Diagnostics.Property_0_of_type_1_is_not_assignable_to_2_index_type_3, + symbolToString(prop), typeToString(propType), typeToString(info.keyType), typeToString(info.type)); + if (propDeclaration && errorNode !== propDeclaration) { + addRelatedInfo(diagnostic, createDiagnosticForNode(propDeclaration, Diagnostics._0_is_declared_here, symbolToString(prop))); } + diagnostics.add(diagnostic); } } + } - function checkIndexConstraintForIndexSignature(type: Type, checkInfo: IndexInfo) { - const declaration = checkInfo.declaration; - const indexInfos = getApplicableIndexInfos(type, checkInfo.keyType); - const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; - const localCheckDeclaration = declaration && getParentOfSymbol(getSymbolOfNode(declaration)) === type.symbol ? declaration : undefined; - for (const info of indexInfos) { - if (info === checkInfo) continue; - const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfNode(info.declaration)) === type.symbol ? info.declaration : undefined; - // We check only when (a) the check index signature is declared in the containing type, or (b) the applicable index - // signature is declared in the containing type, or (c) the containing type is an interface and no base interface contains - // both index signatures (i.e. the index signatures are declared in separate inherited interfaces). - const errorNode = localCheckDeclaration || localIndexDeclaration || - (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getIndexInfoOfType(base, checkInfo.keyType) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); - if (errorNode && !isTypeAssignableTo(checkInfo.type, info.type)) { - error(errorNode, Diagnostics._0_index_type_1_is_not_assignable_to_2_index_type_3, - typeToString(checkInfo.keyType), typeToString(checkInfo.type), typeToString(info.keyType), typeToString(info.type)); - } - } - } - - function checkTypeNameIsReserved(name: Identifier, message: DiagnosticMessage): void { - // TS 1.0 spec (April 2014): 3.6.1 - // The predefined type keywords are reserved and cannot be used as names of user defined types. - switch (name.escapedText) { - case "any": - case "unknown": - case "never": - case "number": - case "bigint": - case "boolean": - case "string": - case "symbol": - case "void": - case "object": - error(name, message, name.escapedText as string); + function checkIndexConstraintForIndexSignature(type: Type, checkInfo: IndexInfo) { + const declaration = checkInfo.declaration; + const indexInfos = getApplicableIndexInfos(type, checkInfo.keyType); + const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; + const localCheckDeclaration = declaration && getParentOfSymbol(getSymbolOfNode(declaration)) === type.symbol ? declaration : undefined; + for (const info of indexInfos) { + if (info === checkInfo) continue; + const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfNode(info.declaration)) === type.symbol ? info.declaration : undefined; + // We check only when (a) the check index signature is declared in the containing type, or (b) the applicable index + // signature is declared in the containing type, or (c) the containing type is an interface and no base interface contains + // both index signatures (i.e. the index signatures are declared in separate inherited interfaces). + const errorNode = localCheckDeclaration || localIndexDeclaration || + (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getIndexInfoOfType(base, checkInfo.keyType) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); + if (errorNode && !isTypeAssignableTo(checkInfo.type, info.type)) { + error(errorNode, Diagnostics._0_index_type_1_is_not_assignable_to_2_index_type_3, + typeToString(checkInfo.keyType), typeToString(checkInfo.type), typeToString(info.keyType), typeToString(info.type)); } } + } - /** - * The name cannot be used as 'Object' of user defined types with special target. - */ - function checkClassNameCollisionWithObject(name: Identifier): void { - if (languageVersion >= ScriptTarget.ES5 && name.escapedText === "Object" - && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(name).impliedNodeFormat === ModuleKind.CommonJS)) { - error(name, Diagnostics.Class_name_cannot_be_Object_when_targeting_ES5_with_module_0, ModuleKind[moduleKind]); // https://github.com/Microsoft/TypeScript/issues/17494 - } + function checkTypeNameIsReserved(name: Identifier, message: DiagnosticMessage): void { + // TS 1.0 spec (April 2014): 3.6.1 + // The predefined type keywords are reserved and cannot be used as names of user defined types. + switch (name.escapedText) { + case "any": + case "unknown": + case "never": + case "number": + case "bigint": + case "boolean": + case "string": + case "symbol": + case "void": + case "object": + error(name, message, name.escapedText as string); } + } - function checkUnmatchedJSDocParameters(node: SignatureDeclaration) { - const jsdocParameters = filter(getJSDocTags(node), isJSDocParameterTag); - if (!length(jsdocParameters)) return; + /** + * The name cannot be used as 'Object' of user defined types with special target. + */ + function checkClassNameCollisionWithObject(name: Identifier): void { + if (languageVersion >= ScriptTarget.ES5 && name.escapedText === "Object" + && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(name).impliedNodeFormat === ModuleKind.CommonJS)) { + error(name, Diagnostics.Class_name_cannot_be_Object_when_targeting_ES5_with_module_0, ModuleKind[moduleKind]); // https://github.com/Microsoft/TypeScript/issues/17494 + } + } - const isJs = isInJSFile(node); - const parameters = new Set<__String>(); - const excludedParameters = new Set(); - forEach(node.parameters, ({ name }, index) => { - if (isIdentifier(name)) { - parameters.add(name.escapedText); - } - if (isBindingPattern(name)) { - excludedParameters.add(index); - } - }); + function checkUnmatchedJSDocParameters(node: SignatureDeclaration) { + const jsdocParameters = filter(getJSDocTags(node), isJSDocParameterTag); + if (!length(jsdocParameters)) return; - const containsArguments = containsArgumentsReference(node); - if (containsArguments) { - const lastJSDocParam = lastOrUndefined(jsdocParameters); - if (isJs && lastJSDocParam && isIdentifier(lastJSDocParam.name) && lastJSDocParam.typeExpression && - lastJSDocParam.typeExpression.type && !parameters.has(lastJSDocParam.name.escapedText) && !isArrayType(getTypeFromTypeNode(lastJSDocParam.typeExpression.type))) { - error(lastJSDocParam.name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, idText(lastJSDocParam.name)); - } + const isJs = isInJSFile(node); + const parameters = new Set<__String>(); + const excludedParameters = new Set(); + forEach(node.parameters, ({ name }, index) => { + if (isIdentifier(name)) { + parameters.add(name.escapedText); } - else { - forEach(jsdocParameters, ({ name, isNameFirst }, index) => { - if (excludedParameters.has(index) || isIdentifier(name) && parameters.has(name.escapedText)) { - return; - } - if (isQualifiedName(name)) { - if (isJs) { - error(name, Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, entityNameToString(name), entityNameToString(name.left)); - } + if (isBindingPattern(name)) { + excludedParameters.add(index); + } + }); + + const containsArguments = containsArgumentsReference(node); + if (containsArguments) { + const lastJSDocParam = lastOrUndefined(jsdocParameters); + if (isJs && lastJSDocParam && isIdentifier(lastJSDocParam.name) && lastJSDocParam.typeExpression && + lastJSDocParam.typeExpression.type && !parameters.has(lastJSDocParam.name.escapedText) && !isArrayType(getTypeFromTypeNode(lastJSDocParam.typeExpression.type))) { + error(lastJSDocParam.name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, idText(lastJSDocParam.name)); + } + } + else { + forEach(jsdocParameters, ({ name, isNameFirst }, index) => { + if (excludedParameters.has(index) || isIdentifier(name) && parameters.has(name.escapedText)) { + return; + } + if (isQualifiedName(name)) { + if (isJs) { + error(name, Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, entityNameToString(name), entityNameToString(name.left)); } - else { - if (!isNameFirst) { - errorOrSuggestion(isJs, name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, idText(name)); - } + } + else { + if (!isNameFirst) { + errorOrSuggestion(isJs, name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, idText(name)); } - }); - } + } + }); } + } - /** - * Check each type parameter and check that type parameters have no duplicate type parameter declarations - */ - function checkTypeParameters(typeParameterDeclarations: readonly TypeParameterDeclaration[] | undefined) { - let seenDefault = false; - if (typeParameterDeclarations) { - for (let i = 0; i < typeParameterDeclarations.length; i++) { - const node = typeParameterDeclarations[i]; - checkTypeParameter(node); + /** + * Check each type parameter and check that type parameters have no duplicate type parameter declarations + */ + function checkTypeParameters(typeParameterDeclarations: readonly TypeParameterDeclaration[] | undefined) { + let seenDefault = false; + if (typeParameterDeclarations) { + for (let i = 0; i < typeParameterDeclarations.length; i++) { + const node = typeParameterDeclarations[i]; + checkTypeParameter(node); - addLazyDiagnostic(createCheckTypeParameterDiagnostic(node, i)); - } + addLazyDiagnostic(createCheckTypeParameterDiagnostic(node, i)); } + } - function createCheckTypeParameterDiagnostic(node: TypeParameterDeclaration, i: number) { - return () => { - if (node.default) { - seenDefault = true; - checkTypeParametersNotReferenced(node.default, typeParameterDeclarations!, i); - } - else if (seenDefault) { - error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters); - } - for (let j = 0; j < i; j++) { - if (typeParameterDeclarations![j].symbol === node.symbol) { - error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name)); - } + function createCheckTypeParameterDiagnostic(node: TypeParameterDeclaration, i: number) { + return () => { + if (node.default) { + seenDefault = true; + checkTypeParametersNotReferenced(node.default, typeParameterDeclarations!, i); + } + else if (seenDefault) { + error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters); + } + for (let j = 0; j < i; j++) { + if (typeParameterDeclarations![j].symbol === node.symbol) { + error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name)); } - }; - } + } + }; } + } - /** Check that type parameter defaults only reference previously declared type parameters */ - function checkTypeParametersNotReferenced(root: TypeNode, typeParameters: readonly TypeParameterDeclaration[], index: number) { - visit(root); - function visit(node: Node) { - if (node.kind === SyntaxKind.TypeReference) { - const type = getTypeFromTypeReference(node as TypeReferenceNode); - if (type.flags & TypeFlags.TypeParameter) { - for (let i = index; i < typeParameters.length; i++) { - if (type.symbol === getSymbolOfNode(typeParameters[i])) { - error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); - } + /** Check that type parameter defaults only reference previously declared type parameters */ + function checkTypeParametersNotReferenced(root: TypeNode, typeParameters: readonly TypeParameterDeclaration[], index: number) { + visit(root); + function visit(node: Node) { + if (node.kind === SyntaxKind.TypeReference) { + const type = getTypeFromTypeReference(node as TypeReferenceNode); + if (type.flags & TypeFlags.TypeParameter) { + for (let i = index; i < typeParameters.length; i++) { + if (type.symbol === getSymbolOfNode(typeParameters[i])) { + error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); } } } - forEachChild(node, visit); } + forEachChild(node, visit); + } + } + + /** Check that type parameter lists are identical across multiple declarations */ + function checkTypeParameterListsIdentical(symbol: Symbol) { + if (symbol.declarations && symbol.declarations.length === 1) { + return; } - /** Check that type parameter lists are identical across multiple declarations */ - function checkTypeParameterListsIdentical(symbol: Symbol) { - if (symbol.declarations && symbol.declarations.length === 1) { + const links = getSymbolLinks(symbol); + if (!links.typeParametersChecked) { + links.typeParametersChecked = true; + const declarations = getClassOrInterfaceDeclarationsOfSymbol(symbol); + if (!declarations || declarations.length <= 1) { return; } - const links = getSymbolLinks(symbol); - if (!links.typeParametersChecked) { - links.typeParametersChecked = true; - const declarations = getClassOrInterfaceDeclarationsOfSymbol(symbol); - if (!declarations || declarations.length <= 1) { - return; - } - - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; - if (!areTypeParametersIdentical(declarations, type.localTypeParameters!, getEffectiveTypeParameterDeclarations)) { - // Report an error on every conflicting declaration. - const name = symbolToString(symbol); - for (const declaration of declarations) { - error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_type_parameters, name); - } + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + if (!areTypeParametersIdentical(declarations, type.localTypeParameters!, getEffectiveTypeParameterDeclarations)) { + // Report an error on every conflicting declaration. + const name = symbolToString(symbol); + for (const declaration of declarations) { + error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_type_parameters, name); } } } + } - function areTypeParametersIdentical(declarations: readonly T[], targetParameters: TypeParameter[], getTypeParameterDeclarations: (node: T) => readonly TypeParameterDeclaration[]) { - const maxTypeArgumentCount = length(targetParameters); - const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters); - - for (const declaration of declarations) { - // If this declaration has too few or too many type parameters, we report an error - const sourceParameters = getTypeParameterDeclarations(declaration); - const numTypeParameters = sourceParameters.length; - if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) { - return false; - } + function areTypeParametersIdentical(declarations: readonly T[], targetParameters: TypeParameter[], getTypeParameterDeclarations: (node: T) => readonly TypeParameterDeclaration[]) { + const maxTypeArgumentCount = length(targetParameters); + const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters); - for (let i = 0; i < numTypeParameters; i++) { - const source = sourceParameters[i]; - const target = targetParameters[i]; + for (const declaration of declarations) { + // If this declaration has too few or too many type parameters, we report an error + const sourceParameters = getTypeParameterDeclarations(declaration); + const numTypeParameters = sourceParameters.length; + if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) { + return false; + } - // If the type parameter node does not have the same as the resolved type - // parameter at this position, we report an error. - if (source.name.escapedText !== target.symbol.escapedName) { - return false; - } + for (let i = 0; i < numTypeParameters; i++) { + const source = sourceParameters[i]; + const target = targetParameters[i]; - // If the type parameter node does not have an identical constraint as the resolved - // type parameter at this position, we report an error. - const constraint = getEffectiveConstraintOfTypeParameter(source); - const sourceConstraint = constraint && getTypeFromTypeNode(constraint); - const targetConstraint = getConstraintOfTypeParameter(target); - // relax check if later interface augmentation has no constraint, it's more broad and is OK to merge with - // a more constrained interface (this could be generalized to a full hierarchy check, but that's maybe overkill) - if (sourceConstraint && targetConstraint && !isTypeIdenticalTo(sourceConstraint, targetConstraint)) { - return false; - } + // If the type parameter node does not have the same as the resolved type + // parameter at this position, we report an error. + if (source.name.escapedText !== target.symbol.escapedName) { + return false; + } - // If the type parameter node has a default and it is not identical to the default - // for the type parameter at this position, we report an error. - const sourceDefault = source.default && getTypeFromTypeNode(source.default); - const targetDefault = getDefaultFromTypeParameter(target); - if (sourceDefault && targetDefault && !isTypeIdenticalTo(sourceDefault, targetDefault)) { - return false; - } + // If the type parameter node does not have an identical constraint as the resolved + // type parameter at this position, we report an error. + const constraint = getEffectiveConstraintOfTypeParameter(source); + const sourceConstraint = constraint && getTypeFromTypeNode(constraint); + const targetConstraint = getConstraintOfTypeParameter(target); + // relax check if later interface augmentation has no constraint, it's more broad and is OK to merge with + // a more constrained interface (this could be generalized to a full hierarchy check, but that's maybe overkill) + if (sourceConstraint && targetConstraint && !isTypeIdenticalTo(sourceConstraint, targetConstraint)) { + return false; + } + + // If the type parameter node has a default and it is not identical to the default + // for the type parameter at this position, we report an error. + const sourceDefault = source.default && getTypeFromTypeNode(source.default); + const targetDefault = getDefaultFromTypeParameter(target); + if (sourceDefault && targetDefault && !isTypeIdenticalTo(sourceDefault, targetDefault)) { + return false; } } - - return true; } - function checkClassExpression(node: ClassExpression): Type { - checkClassLikeDeclaration(node); - checkNodeDeferred(node); - return getTypeOfSymbol(getSymbolOfNode(node)); - } + return true; + } - function checkClassExpressionDeferred(node: ClassExpression) { - forEach(node.members, checkSourceElement); - registerForUnusedIdentifiersCheck(node); - } + function checkClassExpression(node: ClassExpression): Type { + checkClassLikeDeclaration(node); + checkNodeDeferred(node); + return getTypeOfSymbol(getSymbolOfNode(node)); + } - function checkClassDeclaration(node: ClassDeclaration) { - const firstDecorator = find(node.modifiers, isDecorator); - if (firstDecorator && some(node.members, p => hasStaticModifier(p) && isPrivateIdentifierClassElementDeclaration(p))) { - grammarErrorOnNode(firstDecorator, Diagnostics.Class_decorators_can_t_be_used_with_static_private_identifier_Consider_removing_the_experimental_decorator); - } - if (!node.name && !hasSyntacticModifier(node, ModifierFlags.Default)) { - grammarErrorOnFirstToken(node, Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name); - } - checkClassLikeDeclaration(node); - forEach(node.members, checkSourceElement); + function checkClassExpressionDeferred(node: ClassExpression) { + forEach(node.members, checkSourceElement); + registerForUnusedIdentifiersCheck(node); + } - registerForUnusedIdentifiersCheck(node); + function checkClassDeclaration(node: ClassDeclaration) { + const firstDecorator = find(node.modifiers, isDecorator); + if (firstDecorator && some(node.members, p => hasStaticModifier(p) && isPrivateIdentifierClassElementDeclaration(p))) { + grammarErrorOnNode(firstDecorator, Diagnostics.Class_decorators_can_t_be_used_with_static_private_identifier_Consider_removing_the_experimental_decorator); + } + if (!node.name && !hasSyntacticModifier(node, ModifierFlags.Default)) { + grammarErrorOnFirstToken(node, Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name); } + checkClassLikeDeclaration(node); + forEach(node.members, checkSourceElement); - function checkClassLikeDeclaration(node: ClassLikeDeclaration) { - checkGrammarClassLikeDeclaration(node); - checkDecorators(node); - checkCollisionsForDeclarationName(node, node.name); - checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfNode(node); - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; - const typeWithThis = getTypeWithThisArgument(type); - const staticType = getTypeOfSymbol(symbol) as ObjectType; - checkTypeParameterListsIdentical(symbol); - checkFunctionOrConstructorSymbol(symbol); - checkClassForDuplicateDeclarations(node); + registerForUnusedIdentifiersCheck(node); + } - // Only check for reserved static identifiers on non-ambient context. - const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); - if (!nodeInAmbientContext) { - checkClassForStaticPropertyNameConflicts(node); + function checkClassLikeDeclaration(node: ClassLikeDeclaration) { + checkGrammarClassLikeDeclaration(node); + checkDecorators(node); + checkCollisionsForDeclarationName(node, node.name); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfNode(node); + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + const typeWithThis = getTypeWithThisArgument(type); + const staticType = getTypeOfSymbol(symbol) as ObjectType; + checkTypeParameterListsIdentical(symbol); + checkFunctionOrConstructorSymbol(symbol); + checkClassForDuplicateDeclarations(node); + + // Only check for reserved static identifiers on non-ambient context. + const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); + if (!nodeInAmbientContext) { + checkClassForStaticPropertyNameConflicts(node); + } + + const baseTypeNode = getEffectiveBaseTypeNode(node); + if (baseTypeNode) { + forEach(baseTypeNode.typeArguments, checkSourceElement); + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(baseTypeNode.parent, ExternalEmitHelpers.Extends); + } + // check both @extends and extends if both are specified. + const extendsNode = getClassExtendsHeritageElement(node); + if (extendsNode && extendsNode !== baseTypeNode) { + checkExpression(extendsNode.expression); } - const baseTypeNode = getEffectiveBaseTypeNode(node); - if (baseTypeNode) { - forEach(baseTypeNode.typeArguments, checkSourceElement); - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(baseTypeNode.parent, ExternalEmitHelpers.Extends); - } - // check both @extends and extends if both are specified. - const extendsNode = getClassExtendsHeritageElement(node); - if (extendsNode && extendsNode !== baseTypeNode) { - checkExpression(extendsNode.expression); - } - - const baseTypes = getBaseTypes(type); - if (baseTypes.length) { - addLazyDiagnostic(() => { - const baseType = baseTypes[0]; - const baseConstructorType = getBaseConstructorTypeOfClass(type); - const staticBaseType = getApparentType(baseConstructorType); - checkBaseTypeAccessibility(staticBaseType, baseTypeNode); - checkSourceElement(baseTypeNode.expression); - if (some(baseTypeNode.typeArguments)) { - forEach(baseTypeNode.typeArguments, checkSourceElement); - for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) { - if (!checkTypeArgumentConstraints(baseTypeNode, constructor.typeParameters!)) { - break; - } + const baseTypes = getBaseTypes(type); + if (baseTypes.length) { + addLazyDiagnostic(() => { + const baseType = baseTypes[0]; + const baseConstructorType = getBaseConstructorTypeOfClass(type); + const staticBaseType = getApparentType(baseConstructorType); + checkBaseTypeAccessibility(staticBaseType, baseTypeNode); + checkSourceElement(baseTypeNode.expression); + if (some(baseTypeNode.typeArguments)) { + forEach(baseTypeNode.typeArguments, checkSourceElement); + for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) { + if (!checkTypeArgumentConstraints(baseTypeNode, constructor.typeParameters!)) { + break; } } - const baseWithThis = getTypeWithThisArgument(baseType, type.thisType); - if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { - issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1); + } + const baseWithThis = getTypeWithThisArgument(baseType, type.thisType); + if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { + issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1); + } + else { + // Report static side error only when instance type is assignable + checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node, + Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1); + } + if (baseConstructorType.flags & TypeFlags.TypeVariable) { + if (!isMixinConstructorType(staticType)) { + error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any); } else { - // Report static side error only when instance type is assignable - checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node, - Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1); - } - if (baseConstructorType.flags & TypeFlags.TypeVariable) { - if (!isMixinConstructorType(staticType)) { - error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any); - } - else { - const constructSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); - if (constructSignatures.some(signature => signature.flags & SignatureFlags.Abstract) && !hasSyntacticModifier(node, ModifierFlags.Abstract)) { - error(node.name || node, Diagnostics.A_mixin_class_that_extends_from_a_type_variable_containing_an_abstract_construct_signature_must_also_be_declared_abstract); - } + const constructSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); + if (constructSignatures.some(signature => signature.flags & SignatureFlags.Abstract) && !hasSyntacticModifier(node, ModifierFlags.Abstract)) { + error(node.name || node, Diagnostics.A_mixin_class_that_extends_from_a_type_variable_containing_an_abstract_construct_signature_must_also_be_declared_abstract); } } + } - if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) { - // When the static base type is a "class-like" constructor function (but not actually a class), we verify - // that all instantiated base constructor signatures return the same type. - const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode); - if (forEach(constructors, sig => !isJSConstructor(sig.declaration) && !isTypeIdenticalTo(getReturnTypeOfSignature(sig), baseType))) { - error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type); - } + if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) { + // When the static base type is a "class-like" constructor function (but not actually a class), we verify + // that all instantiated base constructor signatures return the same type. + const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode); + if (forEach(constructors, sig => !isJSConstructor(sig.declaration) && !isTypeIdenticalTo(getReturnTypeOfSignature(sig), baseType))) { + error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type); } - checkKindsOfPropertyMemberOverrides(type, baseType); - }); - } + } + checkKindsOfPropertyMemberOverrides(type, baseType); + }); } + } - checkMembersForOverrideModifier(node, type, typeWithThis, staticType); + checkMembersForOverrideModifier(node, type, typeWithThis, staticType); - const implementedTypeNodes = getEffectiveImplementsTypeNodes(node); - if (implementedTypeNodes) { - for (const typeRefNode of implementedTypeNodes) { - if (!isEntityNameExpression(typeRefNode.expression) || isOptionalChain(typeRefNode.expression)) { - error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments); - } - checkTypeReferenceNode(typeRefNode); - addLazyDiagnostic(createImplementsDiagnostics(typeRefNode)); + const implementedTypeNodes = getEffectiveImplementsTypeNodes(node); + if (implementedTypeNodes) { + for (const typeRefNode of implementedTypeNodes) { + if (!isEntityNameExpression(typeRefNode.expression) || isOptionalChain(typeRefNode.expression)) { + error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments); } + checkTypeReferenceNode(typeRefNode); + addLazyDiagnostic(createImplementsDiagnostics(typeRefNode)); } + } - addLazyDiagnostic(() => { - checkIndexConstraints(type, symbol); - checkIndexConstraints(staticType, symbol, /*isStaticIndex*/ true); - checkTypeForDuplicateIndexSignatures(node); - checkPropertyInitialization(node); - }); + addLazyDiagnostic(() => { + checkIndexConstraints(type, symbol); + checkIndexConstraints(staticType, symbol, /*isStaticIndex*/ true); + checkTypeForDuplicateIndexSignatures(node); + checkPropertyInitialization(node); + }); - function createImplementsDiagnostics(typeRefNode: ExpressionWithTypeArguments) { - return () => { - const t = getReducedType(getTypeFromTypeNode(typeRefNode)); - if (!isErrorType(t)) { - if (isValidBaseType(t)) { - const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ? - Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass : - Diagnostics.Class_0_incorrectly_implements_interface_1; - const baseWithThis = getTypeWithThisArgument(t, type.thisType); - if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { - issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag); - } - } - else { - error(typeRefNode, Diagnostics.A_class_can_only_implement_an_object_type_or_intersection_of_object_types_with_statically_known_members); + function createImplementsDiagnostics(typeRefNode: ExpressionWithTypeArguments) { + return () => { + const t = getReducedType(getTypeFromTypeNode(typeRefNode)); + if (!isErrorType(t)) { + if (isValidBaseType(t)) { + const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ? + Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass : + Diagnostics.Class_0_incorrectly_implements_interface_1; + const baseWithThis = getTypeWithThisArgument(t, type.thisType); + if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { + issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag); } } - }; - } + else { + error(typeRefNode, Diagnostics.A_class_can_only_implement_an_object_type_or_intersection_of_object_types_with_statically_known_members); + } + } + }; } + } - function checkMembersForOverrideModifier(node: ClassLikeDeclaration, type: InterfaceType, typeWithThis: Type, staticType: ObjectType) { - const baseTypeNode = getEffectiveBaseTypeNode(node); - const baseTypes = baseTypeNode && getBaseTypes(type); - const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; - const baseStaticType = getBaseConstructorTypeOfClass(type); - - for (const member of node.members) { - if (hasAmbientModifier(member)) { - continue; - } + function checkMembersForOverrideModifier(node: ClassLikeDeclaration, type: InterfaceType, typeWithThis: Type, staticType: ObjectType) { + const baseTypeNode = getEffectiveBaseTypeNode(node); + const baseTypes = baseTypeNode && getBaseTypes(type); + const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; + const baseStaticType = getBaseConstructorTypeOfClass(type); - if (isConstructorDeclaration(member)) { - forEach(member.parameters, param => { - if (isParameterPropertyDeclaration(param, member)) { - checkExistingMemberForOverrideModifier( - node, - staticType, - baseStaticType, - baseWithThis, - type, - typeWithThis, - param, - /* memberIsParameterProperty */ true - ); - } - }); - } - checkExistingMemberForOverrideModifier( - node, - staticType, - baseStaticType, - baseWithThis, - type, - typeWithThis, - member, - /* memberIsParameterProperty */ false, - ); + for (const member of node.members) { + if (hasAmbientModifier(member)) { + continue; } - } - /** - * @param member Existing member node to be checked. - * Note: `member` cannot be a synthetic node. - */ - function checkExistingMemberForOverrideModifier( - node: ClassLikeDeclaration, - staticType: ObjectType, - baseStaticType: Type, - baseWithThis: Type | undefined, - type: InterfaceType, - typeWithThis: Type, - member: ClassElement | ParameterPropertyDeclaration, - memberIsParameterProperty: boolean, - reportErrors = true, - ): MemberOverrideStatus { - const declaredProp = member.name - && getSymbolAtLocation(member.name) - || getSymbolAtLocation(member); - if (!declaredProp) { - return MemberOverrideStatus.Ok; - } - - return checkMemberForOverrideModifier( + if (isConstructorDeclaration(member)) { + forEach(member.parameters, param => { + if (isParameterPropertyDeclaration(param, member)) { + checkExistingMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + param, + /* memberIsParameterProperty */ true + ); + } + }); + } + checkExistingMemberForOverrideModifier( node, staticType, baseStaticType, baseWithThis, type, typeWithThis, - hasOverrideModifier(member), - hasAbstractModifier(member), - isStatic(member), - memberIsParameterProperty, - symbolName(declaredProp), - reportErrors ? member : undefined, + member, + /* memberIsParameterProperty */ false, ); } + } - /** - * Checks a class member declaration for either a missing or an invalid `override` modifier. - * Note: this function can be used for speculative checking, - * i.e. checking a member that does not yet exist in the program. - * An example of that would be to call this function in a completions scenario, - * when offering a method declaration as completion. - * @param errorNode The node where we should report an error, or undefined if we should not report errors. - */ - function checkMemberForOverrideModifier( - node: ClassLikeDeclaration, - staticType: ObjectType, - baseStaticType: Type, - baseWithThis: Type | undefined, - type: InterfaceType, - typeWithThis: Type, - memberHasOverrideModifier: boolean, - memberHasAbstractModifier: boolean, - memberIsStatic: boolean, - memberIsParameterProperty: boolean, - memberName: string, - errorNode?: Node, - ): MemberOverrideStatus { - const isJs = isInJSFile(node); - const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); - if (baseWithThis && (memberHasOverrideModifier || compilerOptions.noImplicitOverride)) { - const memberEscapedName = escapeLeadingUnderscores(memberName); - const thisType = memberIsStatic ? staticType : typeWithThis; - const baseType = memberIsStatic ? baseStaticType : baseWithThis; - const prop = getPropertyOfType(thisType, memberEscapedName); - const baseProp = getPropertyOfType(baseType, memberEscapedName); - - const baseClassName = typeToString(baseWithThis); - if (prop && !baseProp && memberHasOverrideModifier) { - if (errorNode) { - const suggestion = getSuggestedSymbolForNonexistentClassMember(memberName, baseType); // Again, using symbol name: note that's different from `symbol.escapedName` - suggestion ? - error( - errorNode, - isJs ? - Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1 : - Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1, - baseClassName, - symbolToString(suggestion)) : - error( - errorNode, - isJs ? - Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0 : - Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0, - baseClassName); - } - return MemberOverrideStatus.HasInvalidOverride; - } - else if (prop && baseProp?.declarations && compilerOptions.noImplicitOverride && !nodeInAmbientContext) { - const baseHasAbstract = some(baseProp.declarations, hasAbstractModifier); - if (memberHasOverrideModifier) { - return MemberOverrideStatus.Ok; - } + /** + * @param member Existing member node to be checked. + * Note: `member` cannot be a synthetic node. + */ + function checkExistingMemberForOverrideModifier( + node: ClassLikeDeclaration, + staticType: ObjectType, + baseStaticType: Type, + baseWithThis: Type | undefined, + type: InterfaceType, + typeWithThis: Type, + member: ClassElement | ParameterPropertyDeclaration, + memberIsParameterProperty: boolean, + reportErrors = true, + ): MemberOverrideStatus { + const declaredProp = member.name + && getSymbolAtLocation(member.name) + || getSymbolAtLocation(member); + if (!declaredProp) { + return MemberOverrideStatus.Ok; + } - if (!baseHasAbstract) { - if (errorNode) { - const diag = memberIsParameterProperty ? - isJs ? - Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : - Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0 : - isJs ? - Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : - Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0; - error(errorNode, diag, baseClassName); - } - return MemberOverrideStatus.NeedsOverride; - } - else if (memberHasAbstractModifier && baseHasAbstract) { - if (errorNode) { - error(errorNode, Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0, baseClassName); - } - return MemberOverrideStatus.NeedsOverride; - } - } - } - else if (memberHasOverrideModifier) { + return checkMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + hasOverrideModifier(member), + hasAbstractModifier(member), + isStatic(member), + memberIsParameterProperty, + symbolName(declaredProp), + reportErrors ? member : undefined, + ); + } + + /** + * Checks a class member declaration for either a missing or an invalid `override` modifier. + * Note: this function can be used for speculative checking, + * i.e. checking a member that does not yet exist in the program. + * An example of that would be to call this function in a completions scenario, + * when offering a method declaration as completion. + * @param errorNode The node where we should report an error, or undefined if we should not report errors. + */ + function checkMemberForOverrideModifier( + node: ClassLikeDeclaration, + staticType: ObjectType, + baseStaticType: Type, + baseWithThis: Type | undefined, + type: InterfaceType, + typeWithThis: Type, + memberHasOverrideModifier: boolean, + memberHasAbstractModifier: boolean, + memberIsStatic: boolean, + memberIsParameterProperty: boolean, + memberName: string, + errorNode?: Node, + ): MemberOverrideStatus { + const isJs = isInJSFile(node); + const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); + if (baseWithThis && (memberHasOverrideModifier || compilerOptions.noImplicitOverride)) { + const memberEscapedName = escapeLeadingUnderscores(memberName); + const thisType = memberIsStatic ? staticType : typeWithThis; + const baseType = memberIsStatic ? baseStaticType : baseWithThis; + const prop = getPropertyOfType(thisType, memberEscapedName); + const baseProp = getPropertyOfType(baseType, memberEscapedName); + + const baseClassName = typeToString(baseWithThis); + if (prop && !baseProp && memberHasOverrideModifier) { if (errorNode) { - const className = typeToString(type); - error( - errorNode, - isJs ? - Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class : - Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class, - className); + const suggestion = getSuggestedSymbolForNonexistentClassMember(memberName, baseType); // Again, using symbol name: note that's different from `symbol.escapedName` + suggestion ? + error( + errorNode, + isJs ? + Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1 : + Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1, + baseClassName, + symbolToString(suggestion)) : + error( + errorNode, + isJs ? + Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0 : + Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0, + baseClassName); } return MemberOverrideStatus.HasInvalidOverride; } - - return MemberOverrideStatus.Ok; - } - - function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) { - // iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible - let issuedMemberError = false; - for (const member of node.members) { - if (isStatic(member)) { - continue; + else if (prop && baseProp?.declarations && compilerOptions.noImplicitOverride && !nodeInAmbientContext) { + const baseHasAbstract = some(baseProp.declarations, hasAbstractModifier); + if (memberHasOverrideModifier) { + return MemberOverrideStatus.Ok; } - const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member); - if (declaredProp) { - const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName); - const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName); - if (prop && baseProp) { - const rootChain = () => chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2, - symbolToString(declaredProp), - typeToString(typeWithThis), - typeToString(baseWithThis) - ); - if (!checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(baseProp), member.name || member, /*message*/ undefined, rootChain)) { - issuedMemberError = true; - } + + if (!baseHasAbstract) { + if (errorNode) { + const diag = memberIsParameterProperty ? + isJs ? + Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : + Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0 : + isJs ? + Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : + Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0; + error(errorNode, diag, baseClassName); + } + return MemberOverrideStatus.NeedsOverride; + } + else if (memberHasAbstractModifier && baseHasAbstract) { + if (errorNode) { + error(errorNode, Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0, baseClassName); } + return MemberOverrideStatus.NeedsOverride; } } - if (!issuedMemberError) { - // check again with diagnostics to generate a less-specific error - checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag); + } + else if (memberHasOverrideModifier) { + if (errorNode) { + const className = typeToString(type); + error( + errorNode, + isJs ? + Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class : + Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class, + className); } + return MemberOverrideStatus.HasInvalidOverride; } - function checkBaseTypeAccessibility(type: Type, node: ExpressionWithTypeArguments) { - const signatures = getSignaturesOfType(type, SignatureKind.Construct); - if (signatures.length) { - const declaration = signatures[0].declaration; - if (declaration && hasEffectiveModifier(declaration, ModifierFlags.Private)) { - const typeClassDeclaration = getClassLikeDeclarationOfSymbol(type.symbol)!; - if (!isNodeWithinClass(node, typeClassDeclaration)) { - error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, getFullyQualifiedName(type.symbol)); + return MemberOverrideStatus.Ok; + } + + function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) { + // iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible + let issuedMemberError = false; + for (const member of node.members) { + if (isStatic(member)) { + continue; + } + const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member); + if (declaredProp) { + const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName); + const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName); + if (prop && baseProp) { + const rootChain = () => chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2, + symbolToString(declaredProp), + typeToString(typeWithThis), + typeToString(baseWithThis) + ); + if (!checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(baseProp), member.name || member, /*message*/ undefined, rootChain)) { + issuedMemberError = true; } } } } + if (!issuedMemberError) { + // check again with diagnostics to generate a less-specific error + checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag); + } + } - /** - * Checks a member declaration node to see if has a missing or invalid `override` modifier. - * @param node Class-like node where the member is declared. - * @param member Member declaration node. - * Note: `member` can be a synthetic node without a parent. - */ - function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus { - if (!member.name) { - return MemberOverrideStatus.Ok; + function checkBaseTypeAccessibility(type: Type, node: ExpressionWithTypeArguments) { + const signatures = getSignaturesOfType(type, SignatureKind.Construct); + if (signatures.length) { + const declaration = signatures[0].declaration; + if (declaration && hasEffectiveModifier(declaration, ModifierFlags.Private)) { + const typeClassDeclaration = getClassLikeDeclarationOfSymbol(type.symbol)!; + if (!isNodeWithinClass(node, typeClassDeclaration)) { + error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, getFullyQualifiedName(type.symbol)); + } } - - const symbol = getSymbolOfNode(node); - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; - const typeWithThis = getTypeWithThisArgument(type); - const staticType = getTypeOfSymbol(symbol) as ObjectType; - - const baseTypeNode = getEffectiveBaseTypeNode(node); - const baseTypes = baseTypeNode && getBaseTypes(type); - const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; - const baseStaticType = getBaseConstructorTypeOfClass(type); - - const memberHasOverrideModifier = member.parent - ? hasOverrideModifier(member) - : hasSyntacticModifier(member, ModifierFlags.Override); - - const memberName = unescapeLeadingUnderscores(getTextOfPropertyName(member.name)); - - return checkMemberForOverrideModifier( - node, - staticType, - baseStaticType, - baseWithThis, - type, - typeWithThis, - memberHasOverrideModifier, - hasAbstractModifier(member), - isStatic(member), - /* memberIsParameterProperty */ false, - memberName, - ); } + } - function getTargetSymbol(s: Symbol) { - // if symbol is instantiated its flags are not copied from the 'target' - // so we'll need to get back original 'target' symbol to work with correct set of flags - return getCheckFlags(s) & CheckFlags.Instantiated ? (s as TransientSymbol).target! : s; + /** + * Checks a member declaration node to see if has a missing or invalid `override` modifier. + * @param node Class-like node where the member is declared. + * @param member Member declaration node. + * Note: `member` can be a synthetic node without a parent. + */ + function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus { + if (!member.name) { + return MemberOverrideStatus.Ok; } - function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) { - return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration => - d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration); - } + const symbol = getSymbolOfNode(node); + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + const typeWithThis = getTypeWithThisArgument(type); + const staticType = getTypeOfSymbol(symbol) as ObjectType; + + const baseTypeNode = getEffectiveBaseTypeNode(node); + const baseTypes = baseTypeNode && getBaseTypes(type); + const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; + const baseStaticType = getBaseConstructorTypeOfClass(type); + + const memberHasOverrideModifier = member.parent + ? hasOverrideModifier(member) + : hasSyntacticModifier(member, ModifierFlags.Override); + + const memberName = unescapeLeadingUnderscores(getTextOfPropertyName(member.name)); + + return checkMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + memberHasOverrideModifier, + hasAbstractModifier(member), + isStatic(member), + /* memberIsParameterProperty */ false, + memberName, + ); + } - function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: BaseType): void { - // TypeScript 1.0 spec (April 2014): 8.2.3 - // A derived class inherits all members from its base class it doesn't override. - // Inheritance means that a derived class implicitly contains all non - overridden members of the base class. - // Both public and private property members are inherited, but only public property members can be overridden. - // A property member in a derived class is said to override a property member in a base class - // when the derived class property member has the same name and kind(instance or static) - // as the base class property member. - // The type of an overriding property member must be assignable(section 3.8.4) - // to the type of the overridden property member, or otherwise a compile - time error occurs. - // Base class instance member functions can be overridden by derived class instance member functions, - // but not by other kinds of members. - // Base class instance member variables and accessors can be overridden by - // derived class instance member variables and accessors, but not by other kinds of members. + function getTargetSymbol(s: Symbol) { + // if symbol is instantiated its flags are not copied from the 'target' + // so we'll need to get back original 'target' symbol to work with correct set of flags + return getCheckFlags(s) & CheckFlags.Instantiated ? (s as TransientSymbol).target! : s; + } - // NOTE: assignability is checked in checkClassDeclaration - const baseProperties = getPropertiesOfType(baseType); - basePropertyCheck: for (const baseProperty of baseProperties) { - const base = getTargetSymbol(baseProperty); + function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) { + return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration => + d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration); + } - if (base.flags & SymbolFlags.Prototype) { - continue; + function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: BaseType): void { + // TypeScript 1.0 spec (April 2014): 8.2.3 + // A derived class inherits all members from its base class it doesn't override. + // Inheritance means that a derived class implicitly contains all non - overridden members of the base class. + // Both public and private property members are inherited, but only public property members can be overridden. + // A property member in a derived class is said to override a property member in a base class + // when the derived class property member has the same name and kind(instance or static) + // as the base class property member. + // The type of an overriding property member must be assignable(section 3.8.4) + // to the type of the overridden property member, or otherwise a compile - time error occurs. + // Base class instance member functions can be overridden by derived class instance member functions, + // but not by other kinds of members. + // Base class instance member variables and accessors can be overridden by + // derived class instance member variables and accessors, but not by other kinds of members. + + // NOTE: assignability is checked in checkClassDeclaration + const baseProperties = getPropertiesOfType(baseType); + basePropertyCheck: for (const baseProperty of baseProperties) { + const base = getTargetSymbol(baseProperty); + + if (base.flags & SymbolFlags.Prototype) { + continue; + } + const baseSymbol = getPropertyOfObjectType(type, base.escapedName); + if (!baseSymbol) { + continue; + } + const derived = getTargetSymbol(baseSymbol); + const baseDeclarationFlags = getDeclarationModifierFlagsFromSymbol(base); + + Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration."); + + // In order to resolve whether the inherited method was overridden in the base class or not, + // we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated* + // type declaration, derived and base resolve to the same symbol even in the case of generic classes. + if (derived === base) { + // derived class inherits base without override/redeclaration + const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!; + + // It is an error to inherit an abstract member without implementing it or being declared abstract. + // If there is no declaration for the derived class (as in the case of class expressions), + // then the class cannot be declared abstract. + if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasSyntacticModifier(derivedClassDecl, ModifierFlags.Abstract))) { + // Searches other base types for a declaration that would satisfy the inherited abstract member. + // (The class may have more than one base type via declaration merging with an interface with the + // same name.) + for (const otherBaseType of getBaseTypes(type)) { + if (otherBaseType === baseType) continue; + const baseSymbol = getPropertyOfObjectType(otherBaseType, base.escapedName); + const derivedElsewhere = baseSymbol && getTargetSymbol(baseSymbol); + if (derivedElsewhere && derivedElsewhere !== base) { + continue basePropertyCheck; + } + } + + if (derivedClassDecl.kind === SyntaxKind.ClassExpression) { + error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, + symbolToString(baseProperty), typeToString(baseType)); + } + else { + error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, + typeToString(type), symbolToString(baseProperty), typeToString(baseType)); + } } - const baseSymbol = getPropertyOfObjectType(type, base.escapedName); - if (!baseSymbol) { + } + else { + // derived overrides base. + const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived); + if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) { + // either base or derived property is private - not override, skip it continue; } - const derived = getTargetSymbol(baseSymbol); - const baseDeclarationFlags = getDeclarationModifierFlagsFromSymbol(base); - - Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration."); - - // In order to resolve whether the inherited method was overridden in the base class or not, - // we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated* - // type declaration, derived and base resolve to the same symbol even in the case of generic classes. - if (derived === base) { - // derived class inherits base without override/redeclaration - const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!; - - // It is an error to inherit an abstract member without implementing it or being declared abstract. - // If there is no declaration for the derived class (as in the case of class expressions), - // then the class cannot be declared abstract. - if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasSyntacticModifier(derivedClassDecl, ModifierFlags.Abstract))) { - // Searches other base types for a declaration that would satisfy the inherited abstract member. - // (The class may have more than one base type via declaration merging with an interface with the - // same name.) - for (const otherBaseType of getBaseTypes(type)) { - if (otherBaseType === baseType) continue; - const baseSymbol = getPropertyOfObjectType(otherBaseType, base.escapedName); - const derivedElsewhere = baseSymbol && getTargetSymbol(baseSymbol); - if (derivedElsewhere && derivedElsewhere !== base) { - continue basePropertyCheck; - } - } - if (derivedClassDecl.kind === SyntaxKind.ClassExpression) { - error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, - symbolToString(baseProperty), typeToString(baseType)); - } - else { - error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, - typeToString(type), symbolToString(baseProperty), typeToString(baseType)); - } - } - } - else { - // derived overrides base. - const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived); - if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) { - // either base or derived property is private - not override, skip it + let errorMessage: DiagnosticMessage; + const basePropertyFlags = base.flags & SymbolFlags.PropertyOrAccessor; + const derivedPropertyFlags = derived.flags & SymbolFlags.PropertyOrAccessor; + if (basePropertyFlags && derivedPropertyFlags) { + // property/accessor is overridden with property/accessor + if ((getCheckFlags(base) & CheckFlags.Synthetic + ? base.declarations?.some(d => isPropertyAbstractOrInterface(d, baseDeclarationFlags)) + : base.declarations?.every(d => isPropertyAbstractOrInterface(d, baseDeclarationFlags))) + || getCheckFlags(base) & CheckFlags.Mapped + || derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration)) { + // when the base property is abstract or from an interface, base/derived flags don't need to match + // for intersection properties, this must be true of *any* of the declarations, for others it must be true of *all* + // same when the derived property is from an assignment continue; } - let errorMessage: DiagnosticMessage; - const basePropertyFlags = base.flags & SymbolFlags.PropertyOrAccessor; - const derivedPropertyFlags = derived.flags & SymbolFlags.PropertyOrAccessor; - if (basePropertyFlags && derivedPropertyFlags) { - // property/accessor is overridden with property/accessor - if ((getCheckFlags(base) & CheckFlags.Synthetic - ? base.declarations?.some(d => isPropertyAbstractOrInterface(d, baseDeclarationFlags)) - : base.declarations?.every(d => isPropertyAbstractOrInterface(d, baseDeclarationFlags))) - || getCheckFlags(base) & CheckFlags.Mapped - || derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration)) { - // when the base property is abstract or from an interface, base/derived flags don't need to match - // for intersection properties, this must be true of *any* of the declarations, for others it must be true of *all* - // same when the derived property is from an assignment - continue; - } - - const overriddenInstanceProperty = basePropertyFlags !== SymbolFlags.Property && derivedPropertyFlags === SymbolFlags.Property; - const overriddenInstanceAccessor = basePropertyFlags === SymbolFlags.Property && derivedPropertyFlags !== SymbolFlags.Property; - if (overriddenInstanceProperty || overriddenInstanceAccessor) { - const errorMessage = overriddenInstanceProperty ? - Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property : - Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor; - error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType), typeToString(type)); - } - else if (useDefineForClassFields) { - const uninitialized = derived.declarations?.find(d => d.kind === SyntaxKind.PropertyDeclaration && !(d as PropertyDeclaration).initializer); - if (uninitialized - && !(derived.flags & SymbolFlags.Transient) - && !(baseDeclarationFlags & ModifierFlags.Abstract) - && !(derivedDeclarationFlags & ModifierFlags.Abstract) - && !derived.declarations?.some(d => !!(d.flags & NodeFlags.Ambient))) { - const constructor = findConstructorDeclaration(getClassLikeDeclarationOfSymbol(type.symbol)!); - const propName = (uninitialized as PropertyDeclaration).name; - if ((uninitialized as PropertyDeclaration).exclamationToken - || !constructor - || !isIdentifier(propName) - || !strictNullChecks - || !isPropertyInitializedInConstructor(propName, type, constructor)) { - const errorMessage = Diagnostics.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_add_a_declare_modifier_or_remove_the_redundant_declaration; - error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType)); - } + const overriddenInstanceProperty = basePropertyFlags !== SymbolFlags.Property && derivedPropertyFlags === SymbolFlags.Property; + const overriddenInstanceAccessor = basePropertyFlags === SymbolFlags.Property && derivedPropertyFlags !== SymbolFlags.Property; + if (overriddenInstanceProperty || overriddenInstanceAccessor) { + const errorMessage = overriddenInstanceProperty ? + Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property : + Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor; + error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType), typeToString(type)); + } + else if (useDefineForClassFields) { + const uninitialized = derived.declarations?.find(d => d.kind === SyntaxKind.PropertyDeclaration && !(d as PropertyDeclaration).initializer); + if (uninitialized + && !(derived.flags & SymbolFlags.Transient) + && !(baseDeclarationFlags & ModifierFlags.Abstract) + && !(derivedDeclarationFlags & ModifierFlags.Abstract) + && !derived.declarations?.some(d => !!(d.flags & NodeFlags.Ambient))) { + const constructor = findConstructorDeclaration(getClassLikeDeclarationOfSymbol(type.symbol)!); + const propName = (uninitialized as PropertyDeclaration).name; + if ((uninitialized as PropertyDeclaration).exclamationToken + || !constructor + || !isIdentifier(propName) + || !strictNullChecks + || !isPropertyInitializedInConstructor(propName, type, constructor)) { + const errorMessage = Diagnostics.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_add_a_declare_modifier_or_remove_the_redundant_declaration; + error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType)); } } + } - // correct case + // correct case + continue; + } + else if (isPrototypeProperty(base)) { + if (isPrototypeProperty(derived) || derived.flags & SymbolFlags.Property) { + // method is overridden with method or property -- correct case continue; } - else if (isPrototypeProperty(base)) { - if (isPrototypeProperty(derived) || derived.flags & SymbolFlags.Property) { - // method is overridden with method or property -- correct case - continue; - } - else { - Debug.assert(!!(derived.flags & SymbolFlags.Accessor)); - errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor; - } - } - else if (base.flags & SymbolFlags.Accessor) { - errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function; - } else { - errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function; + Debug.assert(!!(derived.flags & SymbolFlags.Accessor)); + errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor; } - - error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type)); } + else if (base.flags & SymbolFlags.Accessor) { + errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function; + } + else { + errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function; + } + + error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type)); } } + } - function isPropertyAbstractOrInterface(declaration: Declaration, baseDeclarationFlags: ModifierFlags) { - return baseDeclarationFlags & ModifierFlags.Abstract && (!isPropertyDeclaration(declaration) || !declaration.initializer) - || isInterfaceDeclaration(declaration.parent); - } + function isPropertyAbstractOrInterface(declaration: Declaration, baseDeclarationFlags: ModifierFlags) { + return baseDeclarationFlags & ModifierFlags.Abstract && (!isPropertyDeclaration(declaration) || !declaration.initializer) + || isInterfaceDeclaration(declaration.parent); + } - function getNonInheritedProperties(type: InterfaceType, baseTypes: BaseType[], properties: Symbol[]) { - if (!length(baseTypes)) { - return properties; - } - const seen = new Map<__String, Symbol>(); - forEach(properties, p => { - seen.set(p.escapedName, p); - }); + function getNonInheritedProperties(type: InterfaceType, baseTypes: BaseType[], properties: Symbol[]) { + if (!length(baseTypes)) { + return properties; + } + const seen = new Map<__String, Symbol>(); + forEach(properties, p => { + seen.set(p.escapedName, p); + }); - for (const base of baseTypes) { - const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); - for (const prop of properties) { - const existing = seen.get(prop.escapedName); - if (existing && prop.parent === existing.parent) { - seen.delete(prop.escapedName); - } + for (const base of baseTypes) { + const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); + for (const prop of properties) { + const existing = seen.get(prop.escapedName); + if (existing && prop.parent === existing.parent) { + seen.delete(prop.escapedName); } } - - return arrayFrom(seen.values()); } - function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean { - const baseTypes = getBaseTypes(type); - if (baseTypes.length < 2) { - return true; - } + return arrayFrom(seen.values()); + } - interface InheritanceInfoMap { prop: Symbol; containingType: Type; } - const seen = new Map<__String, InheritanceInfoMap>(); - forEach(resolveDeclaredMembers(type).declaredProperties, p => { - seen.set(p.escapedName, { prop: p, containingType: type }); - }); - let ok = true; + function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean { + const baseTypes = getBaseTypes(type); + if (baseTypes.length < 2) { + return true; + } - for (const base of baseTypes) { - const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); - for (const prop of properties) { - const existing = seen.get(prop.escapedName); - if (!existing) { - seen.set(prop.escapedName, { prop, containingType: base }); - } - else { - const isInheritedProperty = existing.containingType !== type; - if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) { - ok = false; + interface InheritanceInfoMap { prop: Symbol; containingType: Type; } + const seen = new Map<__String, InheritanceInfoMap>(); + forEach(resolveDeclaredMembers(type).declaredProperties, p => { + seen.set(p.escapedName, { prop: p, containingType: type }); + }); + let ok = true; - const typeName1 = typeToString(existing.containingType); - const typeName2 = typeToString(base); + for (const base of baseTypes) { + const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); + for (const prop of properties) { + const existing = seen.get(prop.escapedName); + if (!existing) { + seen.set(prop.escapedName, { prop, containingType: base }); + } + else { + const isInheritedProperty = existing.containingType !== type; + if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) { + ok = false; - let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Named_property_0_of_types_1_and_2_are_not_identical, symbolToString(prop), typeName1, typeName2); - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2); - diagnostics.add(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo)); - } + const typeName1 = typeToString(existing.containingType); + const typeName2 = typeToString(base); + + let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Named_property_0_of_types_1_and_2_are_not_identical, symbolToString(prop), typeName1, typeName2); + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2); + diagnostics.add(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo)); } } } - - return ok; } - function checkPropertyInitialization(node: ClassLikeDeclaration) { - if (!strictNullChecks || !strictPropertyInitialization || node.flags & NodeFlags.Ambient) { - return; + return ok; + } + + function checkPropertyInitialization(node: ClassLikeDeclaration) { + if (!strictNullChecks || !strictPropertyInitialization || node.flags & NodeFlags.Ambient) { + return; + } + const constructor = findConstructorDeclaration(node); + for (const member of node.members) { + if (getEffectiveModifierFlags(member) & ModifierFlags.Ambient) { + continue; } - const constructor = findConstructorDeclaration(node); - for (const member of node.members) { - if (getEffectiveModifierFlags(member) & ModifierFlags.Ambient) { - continue; - } - if (!isStatic(member) && isPropertyWithoutInitializer(member)) { - const propName = (member as PropertyDeclaration).name; - if (isIdentifier(propName) || isPrivateIdentifier(propName) || isComputedPropertyName(propName)) { - const type = getTypeOfSymbol(getSymbolOfNode(member)); - if (!(type.flags & TypeFlags.AnyOrUnknown || containsUndefinedType(type))) { - if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) { - error(member.name, Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor, declarationNameToString(propName)); - } + if (!isStatic(member) && isPropertyWithoutInitializer(member)) { + const propName = (member as PropertyDeclaration).name; + if (isIdentifier(propName) || isPrivateIdentifier(propName) || isComputedPropertyName(propName)) { + const type = getTypeOfSymbol(getSymbolOfNode(member)); + if (!(type.flags & TypeFlags.AnyOrUnknown || containsUndefinedType(type))) { + if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) { + error(member.name, Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor, declarationNameToString(propName)); } } } } } + } - function isPropertyWithoutInitializer(node: Node) { - return node.kind === SyntaxKind.PropertyDeclaration && - !hasAbstractModifier(node) && - !(node as PropertyDeclaration).exclamationToken && - !(node as PropertyDeclaration).initializer; - } + function isPropertyWithoutInitializer(node: Node) { + return node.kind === SyntaxKind.PropertyDeclaration && + !hasAbstractModifier(node) && + !(node as PropertyDeclaration).exclamationToken && + !(node as PropertyDeclaration).initializer; + } - function isPropertyInitializedInStaticBlocks(propName: Identifier | PrivateIdentifier, propType: Type, staticBlocks: readonly ClassStaticBlockDeclaration[], startPos: number, endPos: number) { - for (const staticBlock of staticBlocks) { - // static block must be within the provided range as they are evaluated in document order (unlike constructors) - if (staticBlock.pos >= startPos && staticBlock.pos <= endPos) { - const reference = factory.createPropertyAccessExpression(factory.createThis(), propName); - setParent(reference.expression, reference); - setParent(reference, staticBlock); - reference.flowNode = staticBlock.returnFlowNode; - const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); - if (!containsUndefinedType(flowType)) { - return true; - } + function isPropertyInitializedInStaticBlocks(propName: Identifier | PrivateIdentifier, propType: Type, staticBlocks: readonly ClassStaticBlockDeclaration[], startPos: number, endPos: number) { + for (const staticBlock of staticBlocks) { + // static block must be within the provided range as they are evaluated in document order (unlike constructors) + if (staticBlock.pos >= startPos && staticBlock.pos <= endPos) { + const reference = factory.createPropertyAccessExpression(factory.createThis(), propName); + setParent(reference.expression, reference); + setParent(reference, staticBlock); + reference.flowNode = staticBlock.returnFlowNode; + const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); + if (!containsUndefinedType(flowType)) { + return true; } } - return false; - } - - function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier | ComputedPropertyName, propType: Type, constructor: ConstructorDeclaration) { - const reference = isComputedPropertyName(propName) - ? factory.createElementAccessExpression(factory.createThis(), propName.expression) - : factory.createPropertyAccessExpression(factory.createThis(), propName); - setParent(reference.expression, reference); - setParent(reference, constructor); - reference.flowNode = constructor.returnFlowNode; - const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); - return !containsUndefinedType(flowType); } + return false; + } + function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier | ComputedPropertyName, propType: Type, constructor: ConstructorDeclaration) { + const reference = isComputedPropertyName(propName) + ? factory.createElementAccessExpression(factory.createThis(), propName.expression) + : factory.createPropertyAccessExpression(factory.createThis(), propName); + setParent(reference.expression, reference); + setParent(reference, constructor); + reference.flowNode = constructor.returnFlowNode; + const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); + return !containsUndefinedType(flowType); + } - function checkInterfaceDeclaration(node: InterfaceDeclaration) { - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node)) checkGrammarInterfaceDeclaration(node); - - checkTypeParameters(node.typeParameters); - addLazyDiagnostic(() => { - checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); - - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfNode(node); - checkTypeParameterListsIdentical(symbol); - - // Only check this symbol once - const firstInterfaceDecl = getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); - if (node === firstInterfaceDecl) { - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; - const typeWithThis = getTypeWithThisArgument(type); - // run subsequent checks only if first set succeeded - if (checkInheritedPropertiesAreIdentical(type, node.name)) { - for (const baseType of getBaseTypes(type)) { - checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name, Diagnostics.Interface_0_incorrectly_extends_interface_1); - } - checkIndexConstraints(type, symbol); - } - } - checkObjectTypeForDuplicateDeclarations(node); - }); - forEach(getInterfaceBaseTypeNodes(node), heritageElement => { - if (!isEntityNameExpression(heritageElement.expression) || isOptionalChain(heritageElement.expression)) { - error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments); - } - checkTypeReferenceNode(heritageElement); - }); - forEach(node.members, checkSourceElement); + function checkInterfaceDeclaration(node: InterfaceDeclaration) { + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node)) checkGrammarInterfaceDeclaration(node); - addLazyDiagnostic(() => { - checkTypeForDuplicateIndexSignatures(node); - registerForUnusedIdentifiersCheck(node); - }); - } + checkTypeParameters(node.typeParameters); + addLazyDiagnostic(() => { + checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); - function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { - // Grammar checking - checkGrammarDecoratorsAndModifiers(node); - checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); checkExportsOnMergedDeclarations(node); - checkTypeParameters(node.typeParameters); - if (node.type.kind === SyntaxKind.IntrinsicKeyword) { - if (!intrinsicTypeKinds.has(node.name.escapedText as string) || length(node.typeParameters) !== 1) { - error(node.type, Diagnostics.The_intrinsic_keyword_can_only_be_used_to_declare_compiler_provided_intrinsic_types); + const symbol = getSymbolOfNode(node); + checkTypeParameterListsIdentical(symbol); + + // Only check this symbol once + const firstInterfaceDecl = getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); + if (node === firstInterfaceDecl) { + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + const typeWithThis = getTypeWithThisArgument(type); + // run subsequent checks only if first set succeeded + if (checkInheritedPropertiesAreIdentical(type, node.name)) { + for (const baseType of getBaseTypes(type)) { + checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name, Diagnostics.Interface_0_incorrectly_extends_interface_1); + } + checkIndexConstraints(type, symbol); } } - else { - checkSourceElement(node.type); - registerForUnusedIdentifiersCheck(node); + checkObjectTypeForDuplicateDeclarations(node); + }); + forEach(getInterfaceBaseTypeNodes(node), heritageElement => { + if (!isEntityNameExpression(heritageElement.expression) || isOptionalChain(heritageElement.expression)) { + error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments); } - } + checkTypeReferenceNode(heritageElement); + }); - function computeEnumMemberValues(node: EnumDeclaration) { - const nodeLinks = getNodeLinks(node); - if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) { - nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; - let autoValue: number | undefined = 0; - for (const member of node.members) { - const value = computeMemberValue(member, autoValue); - getNodeLinks(member).enumMemberValue = value; - autoValue = typeof value === "number" ? value + 1 : undefined; - } + forEach(node.members, checkSourceElement); + + addLazyDiagnostic(() => { + checkTypeForDuplicateIndexSignatures(node); + registerForUnusedIdentifiersCheck(node); + }); + } + + function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { + // Grammar checking + checkGrammarDecoratorsAndModifiers(node); + checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); + checkExportsOnMergedDeclarations(node); + checkTypeParameters(node.typeParameters); + if (node.type.kind === SyntaxKind.IntrinsicKeyword) { + if (!intrinsicTypeKinds.has(node.name.escapedText as string) || length(node.typeParameters) !== 1) { + error(node.type, Diagnostics.The_intrinsic_keyword_can_only_be_used_to_declare_compiler_provided_intrinsic_types); } } + else { + checkSourceElement(node.type); + registerForUnusedIdentifiersCheck(node); + } + } - function computeMemberValue(member: EnumMember, autoValue: number | undefined) { - if (isComputedNonLiteralName(member.name)) { - error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums); - } - else { - const text = getTextOfPropertyName(member.name); - if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) { - error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name); - } - } - if (member.initializer) { - return computeConstantValue(member); - } - // 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)) { - return undefined; + function computeEnumMemberValues(node: EnumDeclaration) { + const nodeLinks = getNodeLinks(node); + if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) { + nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; + let autoValue: number | undefined = 0; + for (const member of node.members) { + const value = computeMemberValue(member, autoValue); + getNodeLinks(member).enumMemberValue = value; + autoValue = typeof value === "number" ? value + 1 : undefined; } - // If the member declaration specifies no value, the member is considered a constant enum member. - // If the member is the first member in the enum declaration, it is assigned the value zero. - // Otherwise, it is assigned the value of the immediately preceding member plus one, and an error - // occurs if the immediately preceding member is not a constant enum member. - if (autoValue !== undefined) { - return autoValue; + } + } + + function computeMemberValue(member: EnumMember, autoValue: number | undefined) { + if (isComputedNonLiteralName(member.name)) { + error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums); + } + else { + const text = getTextOfPropertyName(member.name); + if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) { + error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name); } - error(member.name, Diagnostics.Enum_member_must_have_initializer); + } + if (member.initializer) { + return computeConstantValue(member); + } + // 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)) { return undefined; } + // If the member declaration specifies no value, the member is considered a constant enum member. + // If the member is the first member in the enum declaration, it is assigned the value zero. + // Otherwise, it is assigned the value of the immediately preceding member plus one, and an error + // occurs if the immediately preceding member is not a constant enum member. + if (autoValue !== undefined) { + return autoValue; + } + error(member.name, Diagnostics.Enum_member_must_have_initializer); + return undefined; + } - function computeConstantValue(member: EnumMember): string | number | undefined { - const isConstEnum = isEnumConst(member.parent); - const initializer = member.initializer!; - const value = evaluate(initializer, member); - if (value !== undefined) { - if (isConstEnum && typeof value === "number" && !isFinite(value)) { - error(initializer, isNaN(value) ? - Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN : - Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value); - } + function computeConstantValue(member: EnumMember): string | number | undefined { + const isConstEnum = isEnumConst(member.parent); + const initializer = member.initializer!; + const value = evaluate(initializer, member); + if (value !== undefined) { + if (isConstEnum && typeof value === "number" && !isFinite(value)) { + error(initializer, isNaN(value) ? + Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN : + Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value); } - else if (isConstEnum) { - error(initializer, Diagnostics.const_enum_member_initializers_must_be_constant_expressions); - } - else if (member.parent.flags & NodeFlags.Ambient) { - error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression); - } - else { - checkTypeAssignableTo(checkExpression(initializer), numberType, initializer, Diagnostics.Type_0_is_not_assignable_to_type_1_as_required_for_computed_enum_member_values); - } - return value; } + else if (isConstEnum) { + error(initializer, Diagnostics.const_enum_member_initializers_must_be_constant_expressions); + } + else if (member.parent.flags & NodeFlags.Ambient) { + error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression); + } + else { + checkTypeAssignableTo(checkExpression(initializer), numberType, initializer, Diagnostics.Type_0_is_not_assignable_to_type_1_as_required_for_computed_enum_member_values); + } + return value; + } - function evaluate(expr: Expression, location: Declaration): string | number | undefined { - switch (expr.kind) { - case SyntaxKind.PrefixUnaryExpression: - const value = evaluate((expr as PrefixUnaryExpression).operand, location); - if (typeof value === "number") { - switch ((expr as PrefixUnaryExpression).operator) { - case SyntaxKind.PlusToken: return value; - case SyntaxKind.MinusToken: return -value; - case SyntaxKind.TildeToken: return ~value; - } - } - break; - case SyntaxKind.BinaryExpression: - const left = evaluate((expr as BinaryExpression).left, location); - const right = evaluate((expr as BinaryExpression).right, location); - if (typeof left === "number" && typeof right === "number") { - switch ((expr as BinaryExpression).operatorToken.kind) { - case SyntaxKind.BarToken: return left | right; - case SyntaxKind.AmpersandToken: return left & right; - case SyntaxKind.GreaterThanGreaterThanToken: return left >> right; - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; - case SyntaxKind.LessThanLessThanToken: return left << right; - case SyntaxKind.CaretToken: return left ^ right; - case SyntaxKind.AsteriskToken: return left * right; - case SyntaxKind.SlashToken: return left / right; - case SyntaxKind.PlusToken: return left + right; - case SyntaxKind.MinusToken: return left - right; - case SyntaxKind.PercentToken: return left % right; - case SyntaxKind.AsteriskAsteriskToken: return left ** right; - } - } - else if ((typeof left === "string" || typeof left === "number") && - (typeof right === "string" || typeof right === "number") && - (expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) { - return "" + left + right; - } - break; - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return (expr as StringLiteralLike).text; - case SyntaxKind.TemplateExpression: - return evaluateTemplateExpression(expr as TemplateExpression, location); - case SyntaxKind.NumericLiteral: - checkGrammarNumericLiteral(expr as NumericLiteral); - return +(expr as NumericLiteral).text; - case SyntaxKind.ParenthesizedExpression: - return evaluate((expr as ParenthesizedExpression).expression, location); - case SyntaxKind.Identifier: - if (isInfinityOrNaNString((expr as Identifier).escapedText)) { - return +((expr as Identifier).escapedText); + function evaluate(expr: Expression, location: Declaration): string | number | undefined { + switch (expr.kind) { + case SyntaxKind.PrefixUnaryExpression: + const value = evaluate((expr as PrefixUnaryExpression).operand, location); + if (typeof value === "number") { + switch ((expr as PrefixUnaryExpression).operator) { + case SyntaxKind.PlusToken: return value; + case SyntaxKind.MinusToken: return -value; + case SyntaxKind.TildeToken: return ~value; } - // falls through - case SyntaxKind.PropertyAccessExpression: - if (isEntityNameExpression(expr)) { - const symbol = resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true); - if (symbol) { - if (symbol.flags & SymbolFlags.EnumMember) { - return evaluateEnumMember(expr, symbol, location); - } - if (isConstVariable(symbol)) { - const declaration = symbol.valueDeclaration as VariableDeclaration | undefined; - if (declaration && !declaration.type && declaration.initializer && declaration !== location && isBlockScopedNameDeclaredBeforeUse(declaration, location)) { - return evaluate(declaration.initializer, declaration); - } + } + break; + case SyntaxKind.BinaryExpression: + const left = evaluate((expr as BinaryExpression).left, location); + const right = evaluate((expr as BinaryExpression).right, location); + if (typeof left === "number" && typeof right === "number") { + switch ((expr as BinaryExpression).operatorToken.kind) { + case SyntaxKind.BarToken: return left | right; + case SyntaxKind.AmpersandToken: return left & right; + case SyntaxKind.GreaterThanGreaterThanToken: return left >> right; + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; + case SyntaxKind.LessThanLessThanToken: return left << right; + case SyntaxKind.CaretToken: return left ^ right; + case SyntaxKind.AsteriskToken: return left * right; + case SyntaxKind.SlashToken: return left / right; + case SyntaxKind.PlusToken: return left + right; + case SyntaxKind.MinusToken: return left - right; + case SyntaxKind.PercentToken: return left % right; + case SyntaxKind.AsteriskAsteriskToken: return left ** right; + } + } + else if ((typeof left === "string" || typeof left === "number") && + (typeof right === "string" || typeof right === "number") && + (expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) { + return "" + left + right; + } + break; + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return (expr as StringLiteralLike).text; + case SyntaxKind.TemplateExpression: + return evaluateTemplateExpression(expr as TemplateExpression, location); + case SyntaxKind.NumericLiteral: + checkGrammarNumericLiteral(expr as NumericLiteral); + return +(expr as NumericLiteral).text; + case SyntaxKind.ParenthesizedExpression: + return evaluate((expr as ParenthesizedExpression).expression, location); + case SyntaxKind.Identifier: + if (isInfinityOrNaNString((expr as Identifier).escapedText)) { + return +((expr as Identifier).escapedText); + } + // falls through + case SyntaxKind.PropertyAccessExpression: + if (isEntityNameExpression(expr)) { + const symbol = resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true); + if (symbol) { + if (symbol.flags & SymbolFlags.EnumMember) { + return evaluateEnumMember(expr, symbol, location); + } + if (isConstVariable(symbol)) { + const declaration = symbol.valueDeclaration as VariableDeclaration | undefined; + if (declaration && !declaration.type && declaration.initializer && declaration !== location && isBlockScopedNameDeclaredBeforeUse(declaration, location)) { + return evaluate(declaration.initializer, declaration); } } } - break; - case SyntaxKind.ElementAccessExpression: - const root = (expr as ElementAccessExpression).expression; - if (isEntityNameExpression(root) && isStringLiteralLike((expr as ElementAccessExpression).argumentExpression)) { - const rootSymbol = resolveEntityName(root, SymbolFlags.Value, /*ignoreErrors*/ true); - if (rootSymbol && rootSymbol.flags & SymbolFlags.Enum) { - const name = escapeLeadingUnderscores(((expr as ElementAccessExpression).argumentExpression as StringLiteralLike).text); - const member = rootSymbol.exports!.get(name); - if (member) { - return evaluateEnumMember(expr, member, location); - } + } + break; + case SyntaxKind.ElementAccessExpression: + const root = (expr as ElementAccessExpression).expression; + if (isEntityNameExpression(root) && isStringLiteralLike((expr as ElementAccessExpression).argumentExpression)) { + const rootSymbol = resolveEntityName(root, SymbolFlags.Value, /*ignoreErrors*/ true); + if (rootSymbol && rootSymbol.flags & SymbolFlags.Enum) { + const name = escapeLeadingUnderscores(((expr as ElementAccessExpression).argumentExpression as StringLiteralLike).text); + const member = rootSymbol.exports!.get(name); + if (member) { + return evaluateEnumMember(expr, member, location); } } - break; - } + } + break; + } + return undefined; + } + + function evaluateEnumMember(expr: Expression, symbol: Symbol, location: Declaration) { + const declaration = symbol.valueDeclaration; + if (!declaration || declaration === location) { + error(expr, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(symbol)); return undefined; } + if (!isBlockScopedNameDeclaredBeforeUse(declaration, location)) { + error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); + return 0; + } + return getEnumMemberValue(declaration as EnumMember); + } - function evaluateEnumMember(expr: Expression, symbol: Symbol, location: Declaration) { - const declaration = symbol.valueDeclaration; - if (!declaration || declaration === location) { - error(expr, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(symbol)); + function evaluateTemplateExpression(expr: TemplateExpression, location: Declaration) { + let result = expr.head.text; + for (const span of expr.templateSpans) { + const value = evaluate(span.expression, location); + if (value === undefined) { return undefined; } - if (!isBlockScopedNameDeclaredBeforeUse(declaration, location)) { - error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); - return 0; - } - return getEnumMemberValue(declaration as EnumMember); + result += value; + result += span.literal.text; } + return result; + } - function evaluateTemplateExpression(expr: TemplateExpression, location: Declaration) { - let result = expr.head.text; - for (const span of expr.templateSpans) { - const value = evaluate(span.expression, location); - if (value === undefined) { - return undefined; - } - result += value; - result += span.literal.text; - } - return result; - } + function checkEnumDeclaration(node: EnumDeclaration) { + addLazyDiagnostic(() => checkEnumDeclarationWorker(node)); + } - function checkEnumDeclaration(node: EnumDeclaration) { - addLazyDiagnostic(() => checkEnumDeclarationWorker(node)); - } + function checkEnumDeclarationWorker(node: EnumDeclaration) { + // Grammar checking + checkGrammarDecoratorsAndModifiers(node); - function checkEnumDeclarationWorker(node: EnumDeclaration) { - // Grammar checking - checkGrammarDecoratorsAndModifiers(node); + checkCollisionsForDeclarationName(node, node.name); + checkExportsOnMergedDeclarations(node); + node.members.forEach(checkEnumMember); - checkCollisionsForDeclarationName(node, node.name); - checkExportsOnMergedDeclarations(node); - node.members.forEach(checkEnumMember); + computeEnumMemberValues(node); - computeEnumMemberValues(node); + // Spec 2014 - Section 9.3: + // It isn't possible for one enum declaration to continue the automatic numbering sequence of another, + // and when an enum type has multiple declarations, only one declaration is permitted to omit a value + // for the first member. + // + // Only perform this check once per symbol + const enumSymbol = getSymbolOfNode(node); + const firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); + if (node === firstDeclaration) { + if (enumSymbol.declarations && enumSymbol.declarations.length > 1) { + const enumIsConst = isEnumConst(node); + // check that const is placed\omitted on all enum declarations + forEach(enumSymbol.declarations, decl => { + if (isEnumDeclaration(decl) && isEnumConst(decl) !== enumIsConst) { + error(getNameOfDeclaration(decl), Diagnostics.Enum_declarations_must_all_be_const_or_non_const); + } + }); + } - // Spec 2014 - Section 9.3: - // It isn't possible for one enum declaration to continue the automatic numbering sequence of another, - // and when an enum type has multiple declarations, only one declaration is permitted to omit a value - // for the first member. - // - // Only perform this check once per symbol - const enumSymbol = getSymbolOfNode(node); - const firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); - if (node === firstDeclaration) { - if (enumSymbol.declarations && enumSymbol.declarations.length > 1) { - const enumIsConst = isEnumConst(node); - // check that const is placed\omitted on all enum declarations - forEach(enumSymbol.declarations, decl => { - if (isEnumDeclaration(decl) && isEnumConst(decl) !== enumIsConst) { - error(getNameOfDeclaration(decl), Diagnostics.Enum_declarations_must_all_be_const_or_non_const); - } - }); + let seenEnumMissingInitialInitializer = false; + forEach(enumSymbol.declarations, declaration => { + // return true if we hit a violation of the rule, false otherwise + if (declaration.kind !== SyntaxKind.EnumDeclaration) { + return false; } - let seenEnumMissingInitialInitializer = false; - forEach(enumSymbol.declarations, declaration => { - // return true if we hit a violation of the rule, false otherwise - if (declaration.kind !== SyntaxKind.EnumDeclaration) { - return false; - } + const enumDeclaration = declaration as EnumDeclaration; + if (!enumDeclaration.members.length) { + return false; + } - const enumDeclaration = declaration as EnumDeclaration; - if (!enumDeclaration.members.length) { - return false; + const firstEnumMember = enumDeclaration.members[0]; + if (!firstEnumMember.initializer) { + if (seenEnumMissingInitialInitializer) { + error(firstEnumMember.name, Diagnostics.In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element); } - - const firstEnumMember = enumDeclaration.members[0]; - if (!firstEnumMember.initializer) { - if (seenEnumMissingInitialInitializer) { - error(firstEnumMember.name, Diagnostics.In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element); - } - else { - seenEnumMissingInitialInitializer = true; - } + else { + seenEnumMissingInitialInitializer = true; } - }); - } + } + }); } + } - function checkEnumMember(node: EnumMember) { - if (isPrivateIdentifier(node.name)) { - error(node, Diagnostics.An_enum_member_cannot_be_named_with_a_private_identifier); - } - if (node.initializer) { - checkExpression(node.initializer); - } + function checkEnumMember(node: EnumMember) { + if (isPrivateIdentifier(node.name)) { + error(node, Diagnostics.An_enum_member_cannot_be_named_with_a_private_identifier); } + if (node.initializer) { + checkExpression(node.initializer); + } + } - function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration | undefined { - const declarations = symbol.declarations; - if (declarations) { - for (const declaration of declarations) { - if ((declaration.kind === SyntaxKind.ClassDeclaration || - (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration as FunctionLikeDeclaration).body))) && - !(declaration.flags & NodeFlags.Ambient)) { - return declaration; - } + function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration | undefined { + const declarations = symbol.declarations; + if (declarations) { + for (const declaration of declarations) { + if ((declaration.kind === SyntaxKind.ClassDeclaration || + (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration as FunctionLikeDeclaration).body))) && + !(declaration.flags & NodeFlags.Ambient)) { + return declaration; } } - return undefined; } + return undefined; + } - function inSameLexicalScope(node1: Node, node2: Node) { - const container1 = getEnclosingBlockScopeContainer(node1); - const container2 = getEnclosingBlockScopeContainer(node2); - if (isGlobalSourceFile(container1)) { - return isGlobalSourceFile(container2); - } - else if (isGlobalSourceFile(container2)) { - return false; - } - else { - return container1 === container2; - } + function inSameLexicalScope(node1: Node, node2: Node) { + const container1 = getEnclosingBlockScopeContainer(node1); + const container2 = getEnclosingBlockScopeContainer(node2); + if (isGlobalSourceFile(container1)) { + return isGlobalSourceFile(container2); + } + else if (isGlobalSourceFile(container2)) { + return false; + } + else { + return container1 === container2; } + } - function checkModuleDeclaration(node: ModuleDeclaration) { - if (node.body) { - checkSourceElement(node.body); - if (!isGlobalScopeAugmentation(node)) { - registerForUnusedIdentifiersCheck(node); - } + function checkModuleDeclaration(node: ModuleDeclaration) { + if (node.body) { + checkSourceElement(node.body); + if (!isGlobalScopeAugmentation(node)) { + registerForUnusedIdentifiersCheck(node); } + } - addLazyDiagnostic(checkModuleDeclarationDiagnostics); + addLazyDiagnostic(checkModuleDeclarationDiagnostics); - function checkModuleDeclarationDiagnostics() { - // Grammar checking - const isGlobalAugmentation = isGlobalScopeAugmentation(node); - const inAmbientContext = node.flags & NodeFlags.Ambient; - if (isGlobalAugmentation && !inAmbientContext) { - error(node.name, Diagnostics.Augmentations_for_the_global_scope_should_have_declare_modifier_unless_they_appear_in_already_ambient_context); - } + function checkModuleDeclarationDiagnostics() { + // Grammar checking + const isGlobalAugmentation = isGlobalScopeAugmentation(node); + const inAmbientContext = node.flags & NodeFlags.Ambient; + if (isGlobalAugmentation && !inAmbientContext) { + error(node.name, Diagnostics.Augmentations_for_the_global_scope_should_have_declare_modifier_unless_they_appear_in_already_ambient_context); + } + + const isAmbientExternalModule: boolean = isAmbientModule(node); + const contextErrorMessage = isAmbientExternalModule + ? Diagnostics.An_ambient_module_declaration_is_only_allowed_at_the_top_level_in_a_file + : Diagnostics.A_namespace_declaration_is_only_allowed_at_the_top_level_of_a_namespace_or_module; + if (checkGrammarModuleElementContext(node, contextErrorMessage)) { + // If we hit a module declaration in an illegal context, just bail out to avoid cascading errors. + return; + } - const isAmbientExternalModule: boolean = isAmbientModule(node); - const contextErrorMessage = isAmbientExternalModule - ? Diagnostics.An_ambient_module_declaration_is_only_allowed_at_the_top_level_in_a_file - : Diagnostics.A_namespace_declaration_is_only_allowed_at_the_top_level_of_a_namespace_or_module; - if (checkGrammarModuleElementContext(node, contextErrorMessage)) { - // If we hit a module declaration in an illegal context, just bail out to avoid cascading errors. - return; + if (!checkGrammarDecoratorsAndModifiers(node)) { + if (!inAmbientContext && node.name.kind === SyntaxKind.StringLiteral) { + grammarErrorOnNode(node.name, Diagnostics.Only_ambient_modules_can_use_quoted_names); } + } + + if (isIdentifier(node.name)) { + checkCollisionsForDeclarationName(node, node.name); + } + + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfNode(node); - if (!checkGrammarDecoratorsAndModifiers(node)) { - if (!inAmbientContext && node.name.kind === SyntaxKind.StringLiteral) { - grammarErrorOnNode(node.name, Diagnostics.Only_ambient_modules_can_use_quoted_names); + // The following checks only apply on a non-ambient instantiated module declaration. + if (symbol.flags & SymbolFlags.ValueModule + && !inAmbientContext + && symbol.declarations + && symbol.declarations.length > 1 + && isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions))) { + const firstNonAmbientClassOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol); + if (firstNonAmbientClassOrFunc) { + if (getSourceFileOfNode(node) !== getSourceFileOfNode(firstNonAmbientClassOrFunc)) { + error(node.name, Diagnostics.A_namespace_declaration_cannot_be_in_a_different_file_from_a_class_or_function_with_which_it_is_merged); + } + else if (node.pos < firstNonAmbientClassOrFunc.pos) { + error(node.name, Diagnostics.A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged); } } - if (isIdentifier(node.name)) { - checkCollisionsForDeclarationName(node, node.name); + // if the module merges with a class declaration in the same lexical scope, + // we need to track this to ensure the correct emit. + const mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration); + if (mergedClass && + inSameLexicalScope(node, mergedClass)) { + getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass; } + } - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfNode(node); - - // The following checks only apply on a non-ambient instantiated module declaration. - if (symbol.flags & SymbolFlags.ValueModule - && !inAmbientContext - && symbol.declarations - && symbol.declarations.length > 1 - && isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions))) { - const firstNonAmbientClassOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol); - if (firstNonAmbientClassOrFunc) { - if (getSourceFileOfNode(node) !== getSourceFileOfNode(firstNonAmbientClassOrFunc)) { - error(node.name, Diagnostics.A_namespace_declaration_cannot_be_in_a_different_file_from_a_class_or_function_with_which_it_is_merged); - } - else if (node.pos < firstNonAmbientClassOrFunc.pos) { - error(node.name, Diagnostics.A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged); + if (isAmbientExternalModule) { + if (isExternalModuleAugmentation(node)) { + // body of the augmentation should be checked for consistency only if augmentation was applied to its target (either global scope or module) + // otherwise we'll be swamped in cascading errors. + // We can detect if augmentation was applied using following rules: + // - augmentation for a global scope is always applied + // - augmentation for some external module is applied if symbol for augmentation is merged (it was combined with target module). + const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Transient); + if (checkBody && node.body) { + for (const statement of node.body.statements) { + checkModuleAugmentationElement(statement, isGlobalAugmentation); } } - - // if the module merges with a class declaration in the same lexical scope, - // we need to track this to ensure the correct emit. - const mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration); - if (mergedClass && - inSameLexicalScope(node, mergedClass)) { - getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass; - } } - - if (isAmbientExternalModule) { - if (isExternalModuleAugmentation(node)) { - // body of the augmentation should be checked for consistency only if augmentation was applied to its target (either global scope or module) - // otherwise we'll be swamped in cascading errors. - // We can detect if augmentation was applied using following rules: - // - augmentation for a global scope is always applied - // - augmentation for some external module is applied if symbol for augmentation is merged (it was combined with target module). - const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Transient); - if (checkBody && node.body) { - for (const statement of node.body.statements) { - checkModuleAugmentationElement(statement, isGlobalAugmentation); - } - } + else if (isGlobalSourceFile(node.parent)) { + if (isGlobalAugmentation) { + error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); } - else if (isGlobalSourceFile(node.parent)) { - if (isGlobalAugmentation) { - error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); - } - else if (isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(node.name))) { - error(node.name, Diagnostics.Ambient_module_declaration_cannot_specify_relative_module_name); - } + else if (isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(node.name))) { + error(node.name, Diagnostics.Ambient_module_declaration_cannot_specify_relative_module_name); + } + } + else { + if (isGlobalAugmentation) { + error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); } else { - if (isGlobalAugmentation) { - error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); - } - else { - // Node is not an augmentation and is not located on the script level. - // This means that this is declaration of ambient module that is located in other module or namespace which is prohibited. - error(node.name, Diagnostics.Ambient_modules_cannot_be_nested_in_other_modules_or_namespaces); - } + // Node is not an augmentation and is not located on the script level. + // This means that this is declaration of ambient module that is located in other module or namespace which is prohibited. + error(node.name, Diagnostics.Ambient_modules_cannot_be_nested_in_other_modules_or_namespaces); } } } } + } - function checkModuleAugmentationElement(node: Node, isGlobalAugmentation: boolean): void { - switch (node.kind) { - case SyntaxKind.VariableStatement: - // error each individual name in variable statement instead of marking the entire variable statement - for (const decl of (node as VariableStatement).declarationList.declarations) { - checkModuleAugmentationElement(decl, isGlobalAugmentation); - } - break; - case SyntaxKind.ExportAssignment: - case SyntaxKind.ExportDeclaration: - grammarErrorOnFirstToken(node, Diagnostics.Exports_and_export_assignments_are_not_permitted_in_module_augmentations); - break; - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportDeclaration: - grammarErrorOnFirstToken(node, Diagnostics.Imports_are_not_permitted_in_module_augmentations_Consider_moving_them_to_the_enclosing_external_module); - break; - case SyntaxKind.BindingElement: - case SyntaxKind.VariableDeclaration: - const name = (node as VariableDeclaration | BindingElement).name; - if (isBindingPattern(name)) { - for (const el of name.elements) { - // mark individual names in binding pattern - checkModuleAugmentationElement(el, isGlobalAugmentation); - } - break; - } - // falls through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.TypeAliasDeclaration: - if (isGlobalAugmentation) { - return; + function checkModuleAugmentationElement(node: Node, isGlobalAugmentation: boolean): void { + switch (node.kind) { + case SyntaxKind.VariableStatement: + // error each individual name in variable statement instead of marking the entire variable statement + for (const decl of (node as VariableStatement).declarationList.declarations) { + checkModuleAugmentationElement(decl, isGlobalAugmentation); + } + break; + case SyntaxKind.ExportAssignment: + case SyntaxKind.ExportDeclaration: + grammarErrorOnFirstToken(node, Diagnostics.Exports_and_export_assignments_are_not_permitted_in_module_augmentations); + break; + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportDeclaration: + grammarErrorOnFirstToken(node, Diagnostics.Imports_are_not_permitted_in_module_augmentations_Consider_moving_them_to_the_enclosing_external_module); + break; + case SyntaxKind.BindingElement: + case SyntaxKind.VariableDeclaration: + const name = (node as VariableDeclaration | BindingElement).name; + if (isBindingPattern(name)) { + for (const el of name.elements) { + // mark individual names in binding pattern + checkModuleAugmentationElement(el, isGlobalAugmentation); } break; - } + } + // falls through + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.TypeAliasDeclaration: + if (isGlobalAugmentation) { + return; + } + break; } + } - function getFirstNonModuleExportsIdentifier(node: EntityNameOrEntityNameExpression): Identifier { - switch (node.kind) { - case SyntaxKind.Identifier: - return node; - case SyntaxKind.QualifiedName: - do { - node = node.left; - } while (node.kind !== SyntaxKind.Identifier); - return node; - case SyntaxKind.PropertyAccessExpression: - do { - if (isModuleExportsAccessExpression(node.expression) && !isPrivateIdentifier(node.name)) { - return node.name; - } - node = node.expression; - } while (node.kind !== SyntaxKind.Identifier); - return node; - } + function getFirstNonModuleExportsIdentifier(node: EntityNameOrEntityNameExpression): Identifier { + switch (node.kind) { + case SyntaxKind.Identifier: + return node; + case SyntaxKind.QualifiedName: + do { + node = node.left; + } while (node.kind !== SyntaxKind.Identifier); + return node; + case SyntaxKind.PropertyAccessExpression: + do { + if (isModuleExportsAccessExpression(node.expression) && !isPrivateIdentifier(node.name)) { + return node.name; + } + node = node.expression; + } while (node.kind !== SyntaxKind.Identifier); + return node; } + } - function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean { - const moduleName = getExternalModuleName(node); - if (!moduleName || nodeIsMissing(moduleName)) { - // Should be a parse error. - return false; - } - if (!isStringLiteral(moduleName)) { - error(moduleName, Diagnostics.String_literal_expected); - return false; - } - const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); - if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule) { - error(moduleName, node.kind === SyntaxKind.ExportDeclaration ? - Diagnostics.Export_declarations_are_not_permitted_in_a_namespace : - Diagnostics.Import_declarations_in_a_namespace_cannot_reference_a_module); + function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean { + const moduleName = getExternalModuleName(node); + if (!moduleName || nodeIsMissing(moduleName)) { + // Should be a parse error. + return false; + } + if (!isStringLiteral(moduleName)) { + error(moduleName, Diagnostics.String_literal_expected); + return false; + } + const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); + if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule) { + error(moduleName, node.kind === SyntaxKind.ExportDeclaration ? + Diagnostics.Export_declarations_are_not_permitted_in_a_namespace : + Diagnostics.Import_declarations_in_a_namespace_cannot_reference_a_module); + return false; + } + if (inAmbientExternalModule && isExternalModuleNameRelative(moduleName.text)) { + // we have already reported errors on top level imports/exports in external module augmentations in checkModuleDeclaration + // no need to do this again. + if (!isTopLevelInExternalModuleAugmentation(node)) { + // TypeScript 1.0 spec (April 2013): 12.1.6 + // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference + // other external modules only through top - level external module names. + // Relative external module names are not permitted. + error(node, Diagnostics.Import_or_export_declaration_in_an_ambient_module_declaration_cannot_reference_module_through_relative_module_name); return false; } - if (inAmbientExternalModule && isExternalModuleNameRelative(moduleName.text)) { - // we have already reported errors on top level imports/exports in external module augmentations in checkModuleDeclaration - // no need to do this again. - if (!isTopLevelInExternalModuleAugmentation(node)) { - // TypeScript 1.0 spec (April 2013): 12.1.6 - // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference - // other external modules only through top - level external module names. - // Relative external module names are not permitted. - error(node, Diagnostics.Import_or_export_declaration_in_an_ambient_module_declaration_cannot_reference_module_through_relative_module_name); - return false; - } - } - if (!isImportEqualsDeclaration(node) && node.assertClause) { - let hasError = false; - for (const clause of node.assertClause.elements) { - if (!isStringLiteral(clause.value)) { - hasError = true; - error(clause.value, Diagnostics.Import_assertion_values_must_be_string_literal_expressions); - } + } + if (!isImportEqualsDeclaration(node) && node.assertClause) { + let hasError = false; + for (const clause of node.assertClause.elements) { + if (!isStringLiteral(clause.value)) { + hasError = true; + error(clause.value, Diagnostics.Import_assertion_values_must_be_string_literal_expressions); } - return !hasError; } - return true; + return !hasError; } + return true; + } - function checkAliasSymbol(node: ImportEqualsDeclaration | VariableDeclaration | ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier | NamespaceExport | BindingElement) { - let symbol = getSymbolOfNode(node); - const target = resolveAlias(symbol); - - if (target !== unknownSymbol) { - // For external modules, `symbol` represents the local symbol for an alias. - // This local symbol will merge any other local declarations (excluding other aliases) - // and symbol.flags will contains combined representation for all merged declaration. - // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, - // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* - // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). - symbol = getMergedSymbol(symbol.exportSymbol || symbol); - - // A type-only import/export will already have a grammar error in a JS file, so no need to issue more errors within - if (isInJSFile(node) && !(target.flags & SymbolFlags.Value) && !isTypeOnlyImportOrExportDeclaration(node)) { - const errorNode = - isImportOrExportSpecifier(node) ? node.propertyName || node.name : - isNamedDeclaration(node) ? node.name : - node; - - Debug.assert(node.kind !== SyntaxKind.NamespaceExport); - if (node.kind === SyntaxKind.ExportSpecifier) { - const diag = error(errorNode, Diagnostics.Types_cannot_appear_in_export_declarations_in_JavaScript_files); - const alreadyExportedSymbol = getSourceFileOfNode(node).symbol?.exports?.get((node.propertyName || node.name).escapedText); - if (alreadyExportedSymbol === target) { - const exportingDeclaration = alreadyExportedSymbol.declarations?.find(isJSDocNode); - if (exportingDeclaration) { - addRelatedInfo(diag, createDiagnosticForNode( - exportingDeclaration, - Diagnostics._0_is_automatically_exported_here, - unescapeLeadingUnderscores(alreadyExportedSymbol.escapedName))); - } + function checkAliasSymbol(node: ImportEqualsDeclaration | VariableDeclaration | ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier | NamespaceExport | BindingElement) { + let symbol = getSymbolOfNode(node); + const target = resolveAlias(symbol); + + if (target !== unknownSymbol) { + // For external modules, `symbol` represents the local symbol for an alias. + // This local symbol will merge any other local declarations (excluding other aliases) + // and symbol.flags will contains combined representation for all merged declaration. + // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, + // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* + // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). + symbol = getMergedSymbol(symbol.exportSymbol || symbol); + + // A type-only import/export will already have a grammar error in a JS file, so no need to issue more errors within + if (isInJSFile(node) && !(target.flags & SymbolFlags.Value) && !isTypeOnlyImportOrExportDeclaration(node)) { + const errorNode = + isImportOrExportSpecifier(node) ? node.propertyName || node.name : + isNamedDeclaration(node) ? node.name : + node; + + Debug.assert(node.kind !== SyntaxKind.NamespaceExport); + if (node.kind === SyntaxKind.ExportSpecifier) { + const diag = error(errorNode, Diagnostics.Types_cannot_appear_in_export_declarations_in_JavaScript_files); + const alreadyExportedSymbol = getSourceFileOfNode(node).symbol?.exports?.get((node.propertyName || node.name).escapedText); + if (alreadyExportedSymbol === target) { + const exportingDeclaration = alreadyExportedSymbol.declarations?.find(isJSDocNode); + if (exportingDeclaration) { + addRelatedInfo(diag, createDiagnosticForNode( + exportingDeclaration, + Diagnostics._0_is_automatically_exported_here, + unescapeLeadingUnderscores(alreadyExportedSymbol.escapedName))); } } - else { - Debug.assert(node.kind !== SyntaxKind.VariableDeclaration); - const importDeclaration = findAncestor(node, or(isImportDeclaration, isImportEqualsDeclaration)); - const moduleSpecifier = (importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration)?.text) ?? "..."; - const importedIdentifier = unescapeLeadingUnderscores(isIdentifier(errorNode) ? errorNode.escapedText : symbol.escapedName); - error( - errorNode, - Diagnostics._0_is_a_type_and_cannot_be_imported_in_JavaScript_files_Use_1_in_a_JSDoc_type_annotation, - importedIdentifier, - `import("${moduleSpecifier}").${importedIdentifier}`); - } - return; } + else { + Debug.assert(node.kind !== SyntaxKind.VariableDeclaration); + const importDeclaration = findAncestor(node, or(isImportDeclaration, isImportEqualsDeclaration)); + const moduleSpecifier = (importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration)?.text) ?? "..."; + const importedIdentifier = unescapeLeadingUnderscores(isIdentifier(errorNode) ? errorNode.escapedText : symbol.escapedName); + error( + errorNode, + Diagnostics._0_is_a_type_and_cannot_be_imported_in_JavaScript_files_Use_1_in_a_JSDoc_type_annotation, + importedIdentifier, + `import("${moduleSpecifier}").${importedIdentifier}`); + } + return; + } - const targetFlags = getAllSymbolFlags(target); - const excludedMeanings = - (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | - (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | - (symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0); - if (targetFlags & excludedMeanings) { - const message = node.kind === SyntaxKind.ExportSpecifier ? - Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 : - Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0; - error(node, message, symbolToString(symbol)); - } - - if (compilerOptions.isolatedModules - && !isTypeOnlyImportOrExportDeclaration(node) - && !(node.flags & NodeFlags.Ambient)) { - const typeOnlyAlias = getTypeOnlyAliasDeclaration(symbol); - const isType = !(targetFlags & SymbolFlags.Value); - if (isType || typeOnlyAlias) { - switch (node.kind) { - case SyntaxKind.ImportClause: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportEqualsDeclaration: { - if (compilerOptions.preserveValueImports) { - Debug.assertIsDefined(node.name, "An ImportClause with a symbol should have a name"); - const message = isType - ? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled - : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled; - const name = idText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name); - addTypeOnlyDeclarationRelatedInfo( - error(node, message, name), - isType ? undefined : typeOnlyAlias, - name - ); - } - if (isType && node.kind === SyntaxKind.ImportEqualsDeclaration && hasEffectiveModifier(node, ModifierFlags.Export)) { - error(node, Diagnostics.Cannot_use_export_import_on_a_type_or_type_only_namespace_when_the_isolatedModules_flag_is_provided); - } - break; + const targetFlags = getAllSymbolFlags(target); + const excludedMeanings = + (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | + (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | + (symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0); + if (targetFlags & excludedMeanings) { + const message = node.kind === SyntaxKind.ExportSpecifier ? + Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 : + Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0; + error(node, message, symbolToString(symbol)); + } + + if (compilerOptions.isolatedModules + && !isTypeOnlyImportOrExportDeclaration(node) + && !(node.flags & NodeFlags.Ambient)) { + const typeOnlyAlias = getTypeOnlyAliasDeclaration(symbol); + const isType = !(targetFlags & SymbolFlags.Value); + if (isType || typeOnlyAlias) { + switch (node.kind) { + case SyntaxKind.ImportClause: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportEqualsDeclaration: { + if (compilerOptions.preserveValueImports) { + Debug.assertIsDefined(node.name, "An ImportClause with a symbol should have a name"); + const message = isType + ? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled + : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled; + const name = idText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name); + addTypeOnlyDeclarationRelatedInfo( + error(node, message, name), + isType ? undefined : typeOnlyAlias, + name + ); } - case SyntaxKind.ExportSpecifier: { - // Don't allow re-exporting an export that will be elided when `--isolatedModules` is set. - // The exception is that `import type { A } from './a'; export { A }` is allowed - // because single-file analysis can determine that the export should be dropped. - if (getSourceFileOfNode(typeOnlyAlias) !== getSourceFileOfNode(node)) { - const message = isType - ? Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type - : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_isolatedModules_is_enabled; - const name = idText(node.propertyName || node.name); - addTypeOnlyDeclarationRelatedInfo( - error(node, message, name), - isType ? undefined : typeOnlyAlias, - name - ); - return; - } + if (isType && node.kind === SyntaxKind.ImportEqualsDeclaration && hasEffectiveModifier(node, ModifierFlags.Export)) { + error(node, Diagnostics.Cannot_use_export_import_on_a_type_or_type_only_namespace_when_the_isolatedModules_flag_is_provided); + } + break; + } + case SyntaxKind.ExportSpecifier: { + // Don't allow re-exporting an export that will be elided when `--isolatedModules` is set. + // The exception is that `import type { A } from './a'; export { A }` is allowed + // because single-file analysis can determine that the export should be dropped. + if (getSourceFileOfNode(typeOnlyAlias) !== getSourceFileOfNode(node)) { + const message = isType + ? Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type + : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_isolatedModules_is_enabled; + const name = idText(node.propertyName || node.name); + addTypeOnlyDeclarationRelatedInfo( + error(node, message, name), + isType ? undefined : typeOnlyAlias, + name + ); + return; } } } } + } - if (isImportSpecifier(node)) { - const targetSymbol = checkDeprecatedAliasedSymbol(symbol, node); - if (isDeprecatedAliasedSymbol(targetSymbol) && targetSymbol.declarations) { - addDeprecatedSuggestion(node, targetSymbol.declarations, targetSymbol.escapedName as string); - } + if (isImportSpecifier(node)) { + const targetSymbol = checkDeprecatedAliasedSymbol(symbol, node); + if (isDeprecatedAliasedSymbol(targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, targetSymbol.escapedName as string); } } } + } - function isDeprecatedAliasedSymbol(symbol: Symbol) { - return !!symbol.declarations && every(symbol.declarations, d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); - } + function isDeprecatedAliasedSymbol(symbol: Symbol) { + return !!symbol.declarations && every(symbol.declarations, d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); + } - function checkDeprecatedAliasedSymbol(symbol: Symbol, location: Node) { - if (!(symbol.flags & SymbolFlags.Alias)) return symbol; + function checkDeprecatedAliasedSymbol(symbol: Symbol, location: Node) { + if (!(symbol.flags & SymbolFlags.Alias)) return symbol; - const targetSymbol = resolveAlias(symbol); - if (targetSymbol === unknownSymbol) return targetSymbol; - - while (symbol.flags & SymbolFlags.Alias) { - const target = getImmediateAliasedSymbol(symbol); - if (target) { - if (target === targetSymbol) break; - if (target.declarations && length(target.declarations)) { - if (isDeprecatedAliasedSymbol(target)) { - addDeprecatedSuggestion(location, target.declarations, target.escapedName as string); - break; - } - else { - if (symbol === targetSymbol) break; - symbol = target; - } + const targetSymbol = resolveAlias(symbol); + if (targetSymbol === unknownSymbol) return targetSymbol; + + while (symbol.flags & SymbolFlags.Alias) { + const target = getImmediateAliasedSymbol(symbol); + if (target) { + if (target === targetSymbol) break; + if (target.declarations && length(target.declarations)) { + if (isDeprecatedAliasedSymbol(target)) { + addDeprecatedSuggestion(location, target.declarations, target.escapedName as string); + break; + } + else { + if (symbol === targetSymbol) break; + symbol = target; } - } - else { - break; } } - return targetSymbol; - } - - function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) { - checkCollisionsForDeclarationName(node, node.name); - checkAliasSymbol(node); - if (node.kind === SyntaxKind.ImportSpecifier && - idText(node.propertyName || node.name) === "default" && - getESModuleInterop(compilerOptions) && - moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + else { + break; } } + return targetSymbol; + } - function checkAssertClause(declaration: ImportDeclaration | ExportDeclaration) { - if (declaration.assertClause) { - const validForTypeAssertions = isExclusivelyTypeOnlyImportOrExport(declaration); - const override = getResolutionModeOverrideForClause(declaration.assertClause, validForTypeAssertions ? grammarErrorOnNode : undefined); - if (validForTypeAssertions && override) { - if (!isNightly()) { - grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_assertions_are_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next); - } + function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) { + checkCollisionsForDeclarationName(node, node.name); + checkAliasSymbol(node); + if (node.kind === SyntaxKind.ImportSpecifier && + idText(node.propertyName || node.name) === "default" && + getESModuleInterop(compilerOptions) && + moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + } + } - if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { - return grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext); - } - return; // Other grammar checks do not apply to type-only imports with resolution mode assertions + function checkAssertClause(declaration: ImportDeclaration | ExportDeclaration) { + if (declaration.assertClause) { + const validForTypeAssertions = isExclusivelyTypeOnlyImportOrExport(declaration); + const override = getResolutionModeOverrideForClause(declaration.assertClause, validForTypeAssertions ? grammarErrorOnNode : undefined); + if (validForTypeAssertions && override) { + if (!isNightly()) { + grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_assertions_are_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next); } - const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier); - if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext) { - return grammarErrorOnNode(declaration.assertClause, - moduleKind === ModuleKind.NodeNext - ? Diagnostics.Import_assertions_are_not_allowed_on_statements_that_transpile_to_commonjs_require_calls - : Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext_or_nodenext); + if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { + return grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext); } + return; // Other grammar checks do not apply to type-only imports with resolution mode assertions + } - if (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly) { - return grammarErrorOnNode(declaration.assertClause, Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports); - } + const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier); + if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext) { + return grammarErrorOnNode(declaration.assertClause, + moduleKind === ModuleKind.NodeNext + ? Diagnostics.Import_assertions_are_not_allowed_on_statements_that_transpile_to_commonjs_require_calls + : Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext_or_nodenext); + } - if (override) { - return grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_can_only_be_set_for_type_only_imports); - } + if (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly) { + return grammarErrorOnNode(declaration.assertClause, Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports); } - } - function checkImportDeclaration(node: ImportDeclaration) { - if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { - // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. - return; + if (override) { + return grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_can_only_be_set_for_type_only_imports); } - if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { - grammarErrorOnFirstToken(node, Diagnostics.An_import_declaration_cannot_have_modifiers); - } - if (checkExternalImportOrExportDeclaration(node)) { - const importClause = node.importClause; - if (importClause && !checkGrammarImportClause(importClause)) { - if (importClause.name) { - checkImportBinding(importClause); - } - if (importClause.namedBindings) { - if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - checkImportBinding(importClause.namedBindings); - if (moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && getESModuleInterop(compilerOptions)) { - // import * as ns from "foo"; - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); - } + } + } + + function checkImportDeclaration(node: ImportDeclaration) { + if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { + // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. + return; + } + if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_import_declaration_cannot_have_modifiers); + } + if (checkExternalImportOrExportDeclaration(node)) { + const importClause = node.importClause; + if (importClause && !checkGrammarImportClause(importClause)) { + if (importClause.name) { + checkImportBinding(importClause); + } + if (importClause.namedBindings) { + if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + checkImportBinding(importClause.namedBindings); + if (moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && getESModuleInterop(compilerOptions)) { + // import * as ns from "foo"; + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); } - else { - const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier); - if (moduleExisted) { - forEach(importClause.namedBindings.elements, checkImportBinding); - } + } + else { + const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier); + if (moduleExisted) { + forEach(importClause.namedBindings.elements, checkImportBinding); } } } } - checkAssertClause(node); } + checkAssertClause(node); + } - function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) { - if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { - // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. - return; - } + function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) { + if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { + // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. + return; + } - checkGrammarDecoratorsAndModifiers(node); - if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { - checkImportBinding(node); - if (hasSyntacticModifier(node, ModifierFlags.Export)) { - markExportAsReferenced(node); - } - if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { - const target = resolveAlias(getSymbolOfNode(node)); - if (target !== unknownSymbol) { - const targetFlags = getAllSymbolFlags(target); - if (targetFlags & SymbolFlags.Value) { - // Target is a value symbol, check that it is not hidden by a local declaration with the same name - const moduleName = getFirstIdentifier(node.moduleReference); - if (!(resolveEntityName(moduleName, SymbolFlags.Value | SymbolFlags.Namespace)!.flags & SymbolFlags.Namespace)) { - error(moduleName, Diagnostics.Module_0_is_hidden_by_a_local_declaration_with_the_same_name, declarationNameToString(moduleName)); - } - } - if (targetFlags & SymbolFlags.Type) { - checkTypeNameIsReserved(node.name, Diagnostics.Import_name_cannot_be_0); + checkGrammarDecoratorsAndModifiers(node); + if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { + checkImportBinding(node); + if (hasSyntacticModifier(node, ModifierFlags.Export)) { + markExportAsReferenced(node); + } + if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { + const target = resolveAlias(getSymbolOfNode(node)); + if (target !== unknownSymbol) { + const targetFlags = getAllSymbolFlags(target); + if (targetFlags & SymbolFlags.Value) { + // Target is a value symbol, check that it is not hidden by a local declaration with the same name + const moduleName = getFirstIdentifier(node.moduleReference); + if (!(resolveEntityName(moduleName, SymbolFlags.Value | SymbolFlags.Namespace)!.flags & SymbolFlags.Namespace)) { + error(moduleName, Diagnostics.Module_0_is_hidden_by_a_local_declaration_with_the_same_name, declarationNameToString(moduleName)); } } - if (node.isTypeOnly) { - grammarErrorOnNode(node, Diagnostics.An_import_alias_cannot_use_import_type); + if (targetFlags & SymbolFlags.Type) { + checkTypeNameIsReserved(node.name, Diagnostics.Import_name_cannot_be_0); } } - else { - if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat === undefined && !node.isTypeOnly && !(node.flags & NodeFlags.Ambient)) { - // Import equals declaration is deprecated in es6 or above - grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead); - } + if (node.isTypeOnly) { + grammarErrorOnNode(node, Diagnostics.An_import_alias_cannot_use_import_type); + } + } + else { + if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat === undefined && !node.isTypeOnly && !(node.flags & NodeFlags.Ambient)) { + // Import equals declaration is deprecated in es6 or above + grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead); } } } + } - function checkExportDeclaration(node: ExportDeclaration) { - if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { - // If we hit an export in an illegal context, just bail out to avoid cascading errors. - return; - } + function checkExportDeclaration(node: ExportDeclaration) { + if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { + // If we hit an export in an illegal context, just bail out to avoid cascading errors. + return; + } - if (!checkGrammarDecoratorsAndModifiers(node) && hasSyntacticModifiers(node)) { - grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); - } + if (!checkGrammarDecoratorsAndModifiers(node) && hasSyntacticModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); + } - if (node.moduleSpecifier && node.exportClause && isNamedExports(node.exportClause) && length(node.exportClause.elements) && languageVersion === ScriptTarget.ES3) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.CreateBinding); - } + if (node.moduleSpecifier && node.exportClause && isNamedExports(node.exportClause) && length(node.exportClause.elements) && languageVersion === ScriptTarget.ES3) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.CreateBinding); + } - checkGrammarExportDeclaration(node); - if (!node.moduleSpecifier || checkExternalImportOrExportDeclaration(node)) { - if (node.exportClause && !isNamespaceExport(node.exportClause)) { - // export { x, y } - // export { x, y } from "foo" - forEach(node.exportClause.elements, checkExportSpecifier); - const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); - const inAmbientNamespaceDeclaration = !inAmbientExternalModule && node.parent.kind === SyntaxKind.ModuleBlock && - !node.moduleSpecifier && node.flags & NodeFlags.Ambient; - if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule && !inAmbientNamespaceDeclaration) { - error(node, Diagnostics.Export_declarations_are_not_permitted_in_a_namespace); - } + checkGrammarExportDeclaration(node); + if (!node.moduleSpecifier || checkExternalImportOrExportDeclaration(node)) { + if (node.exportClause && !isNamespaceExport(node.exportClause)) { + // export { x, y } + // export { x, y } from "foo" + forEach(node.exportClause.elements, checkExportSpecifier); + const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); + const inAmbientNamespaceDeclaration = !inAmbientExternalModule && node.parent.kind === SyntaxKind.ModuleBlock && + !node.moduleSpecifier && node.flags & NodeFlags.Ambient; + if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule && !inAmbientNamespaceDeclaration) { + error(node, Diagnostics.Export_declarations_are_not_permitted_in_a_namespace); } - else { - // export * from "foo" - // export * as ns from "foo"; - const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!); - if (moduleSymbol && hasExportAssignmentSymbol(moduleSymbol)) { - error(node.moduleSpecifier, Diagnostics.Module_0_uses_export_and_cannot_be_used_with_export_Asterisk, symbolToString(moduleSymbol)); - } - else if (node.exportClause) { - checkAliasSymbol(node.exportClause); - } - if (moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { - if (node.exportClause) { - // export * as ns from "foo"; - // For ES2015 modules, we emit it as a pair of `import * as a_1 ...; export { a_1 as ns }` and don't need the helper. - // We only use the helper here when in esModuleInterop - if (getESModuleInterop(compilerOptions)) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); - } - } - else { - // export * from "foo" - checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar); + } + else { + // export * from "foo" + // export * as ns from "foo"; + const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!); + if (moduleSymbol && hasExportAssignmentSymbol(moduleSymbol)) { + error(node.moduleSpecifier, Diagnostics.Module_0_uses_export_and_cannot_be_used_with_export_Asterisk, symbolToString(moduleSymbol)); + } + else if (node.exportClause) { + checkAliasSymbol(node.exportClause); + } + if (moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { + if (node.exportClause) { + // export * as ns from "foo"; + // For ES2015 modules, we emit it as a pair of `import * as a_1 ...; export { a_1 as ns }` and don't need the helper. + // We only use the helper here when in esModuleInterop + if (getESModuleInterop(compilerOptions)) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); } } + else { + // export * from "foo" + checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar); + } } } - checkAssertClause(node); } + checkAssertClause(node); + } - function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { - if (node.isTypeOnly) { - if (node.exportClause?.kind === SyntaxKind.NamedExports) { - return checkGrammarNamedImportsOrExports(node.exportClause); - } - else { - return grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type); - } + function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { + if (node.isTypeOnly) { + if (node.exportClause?.kind === SyntaxKind.NamedExports) { + return checkGrammarNamedImportsOrExports(node.exportClause); } - return false; - } - - function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean { - const isInAppropriateContext = node.parent.kind === SyntaxKind.SourceFile || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.ModuleDeclaration; - if (!isInAppropriateContext) { - grammarErrorOnFirstToken(node, errorMessage); + else { + return grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type); } - return !isInAppropriateContext; } + return false; + } - function importClauseContainsReferencedImport(importClause: ImportClause) { - return forEachImportClauseDeclaration(importClause, declaration => { - return !!getSymbolOfNode(declaration).isReferenced; - }); + function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean { + const isInAppropriateContext = node.parent.kind === SyntaxKind.SourceFile || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.ModuleDeclaration; + if (!isInAppropriateContext) { + grammarErrorOnFirstToken(node, errorMessage); } + return !isInAppropriateContext; + } - function importClauseContainsConstEnumUsedAsValue(importClause: ImportClause) { - return forEachImportClauseDeclaration(importClause, declaration => { - return !!getSymbolLinks(getSymbolOfNode(declaration)).constEnumReferenced; - }); - } + function importClauseContainsReferencedImport(importClause: ImportClause) { + return forEachImportClauseDeclaration(importClause, declaration => { + return !!getSymbolOfNode(declaration).isReferenced; + }); + } - function canConvertImportDeclarationToTypeOnly(statement: Statement) { - return isImportDeclaration(statement) && - statement.importClause && - !statement.importClause.isTypeOnly && - importClauseContainsReferencedImport(statement.importClause) && - !isReferencedAliasDeclaration(statement.importClause, /*checkChildren*/ true) && - !importClauseContainsConstEnumUsedAsValue(statement.importClause); - } + function importClauseContainsConstEnumUsedAsValue(importClause: ImportClause) { + return forEachImportClauseDeclaration(importClause, declaration => { + return !!getSymbolLinks(getSymbolOfNode(declaration)).constEnumReferenced; + }); + } - function canConvertImportEqualsDeclarationToTypeOnly(statement: Statement) { - return isImportEqualsDeclaration(statement) && - isExternalModuleReference(statement.moduleReference) && - !statement.isTypeOnly && - getSymbolOfNode(statement).isReferenced && - !isReferencedAliasDeclaration(statement, /*checkChildren*/ false) && - !getSymbolLinks(getSymbolOfNode(statement)).constEnumReferenced; - } + function canConvertImportDeclarationToTypeOnly(statement: Statement) { + return isImportDeclaration(statement) && + statement.importClause && + !statement.importClause.isTypeOnly && + importClauseContainsReferencedImport(statement.importClause) && + !isReferencedAliasDeclaration(statement.importClause, /*checkChildren*/ true) && + !importClauseContainsConstEnumUsedAsValue(statement.importClause); + } - function checkImportsForTypeOnlyConversion(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (canConvertImportDeclarationToTypeOnly(statement) || canConvertImportEqualsDeclarationToTypeOnly(statement)) { - error( - statement, - Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_importsNotUsedAsValues_is_set_to_error); - } + function canConvertImportEqualsDeclarationToTypeOnly(statement: Statement) { + return isImportEqualsDeclaration(statement) && + isExternalModuleReference(statement.moduleReference) && + !statement.isTypeOnly && + getSymbolOfNode(statement).isReferenced && + !isReferencedAliasDeclaration(statement, /*checkChildren*/ false) && + !getSymbolLinks(getSymbolOfNode(statement)).constEnumReferenced; + } + + function checkImportsForTypeOnlyConversion(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (canConvertImportDeclarationToTypeOnly(statement) || canConvertImportEqualsDeclarationToTypeOnly(statement)) { + error( + statement, + Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_importsNotUsedAsValues_is_set_to_error); } } + } - function checkExportSpecifier(node: ExportSpecifier) { - checkAliasSymbol(node); - if (getEmitDeclarations(compilerOptions)) { - collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); - } - if (!node.parent.parent.moduleSpecifier) { - const exportedName = node.propertyName || node.name; - // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) - const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, - /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); - if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { - error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); - } - else { - if (!node.isTypeOnly && !node.parent.parent.isTypeOnly) { - markExportAsReferenced(node); - } - const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); - if (!target || getAllSymbolFlags(target) & SymbolFlags.Value) { - checkExpressionCached(node.propertyName || node.name); - } - } + function checkExportSpecifier(node: ExportSpecifier) { + checkAliasSymbol(node); + if (getEmitDeclarations(compilerOptions)) { + collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); + } + if (!node.parent.parent.moduleSpecifier) { + const exportedName = node.propertyName || node.name; + // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) + const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, + /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { + error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); } else { - if (getESModuleInterop(compilerOptions) && - moduleKind !== ModuleKind.System && - (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && - idText(node.propertyName || node.name) === "default") { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + if (!node.isTypeOnly && !node.parent.parent.isTypeOnly) { + markExportAsReferenced(node); + } + const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); + if (!target || getAllSymbolFlags(target) & SymbolFlags.Value) { + checkExpressionCached(node.propertyName || node.name); } } } - - function checkExportAssignment(node: ExportAssignment) { - const illegalContextMessage = node.isExportEquals - ? Diagnostics.An_export_assignment_must_be_at_the_top_level_of_a_file_or_module_declaration - : Diagnostics.A_default_export_must_be_at_the_top_level_of_a_file_or_module_declaration; - if (checkGrammarModuleElementContext(node, illegalContextMessage)) { - // If we hit an export assignment in an illegal context, just bail out to avoid cascading errors. - return; + else { + if (getESModuleInterop(compilerOptions) && + moduleKind !== ModuleKind.System && + (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && + idText(node.propertyName || node.name) === "default") { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); } + } + } - const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent as ModuleDeclaration; - if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { - if (node.isExportEquals) { - error(node, Diagnostics.An_export_assignment_cannot_be_used_in_a_namespace); - } - else { - error(node, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); - } + function checkExportAssignment(node: ExportAssignment) { + const illegalContextMessage = node.isExportEquals + ? Diagnostics.An_export_assignment_must_be_at_the_top_level_of_a_file_or_module_declaration + : Diagnostics.A_default_export_must_be_at_the_top_level_of_a_file_or_module_declaration; + if (checkGrammarModuleElementContext(node, illegalContextMessage)) { + // If we hit an export assignment in an illegal context, just bail out to avoid cascading errors. + return; + } - return; + const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent as ModuleDeclaration; + if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { + if (node.isExportEquals) { + error(node, Diagnostics.An_export_assignment_cannot_be_used_in_a_namespace); } - // Grammar checking - if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { - grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); + else { + error(node, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); } - const typeAnnotationNode = getEffectiveTypeAnnotationNode(node); - if (typeAnnotationNode) { - checkTypeAssignableTo(checkExpressionCached(node.expression), getTypeFromTypeNode(typeAnnotationNode), node.expression); - } + return; + } + // Grammar checking + if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); + } - if (node.expression.kind === SyntaxKind.Identifier) { - const id = node.expression as Identifier; - const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node); - if (sym) { - markAliasReferenced(sym, id); - // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) - const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym; - if (getAllSymbolFlags(target) & SymbolFlags.Value) { - // However if it is a value, we need to check it's being used correctly - checkExpressionCached(node.expression); - } - } - else { - checkExpressionCached(node.expression); // doesn't resolve, check as expression to mark as error - } + const typeAnnotationNode = getEffectiveTypeAnnotationNode(node); + if (typeAnnotationNode) { + checkTypeAssignableTo(checkExpressionCached(node.expression), getTypeFromTypeNode(typeAnnotationNode), node.expression); + } - if (getEmitDeclarations(compilerOptions)) { - collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true); + if (node.expression.kind === SyntaxKind.Identifier) { + const id = node.expression as Identifier; + const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node); + if (sym) { + markAliasReferenced(sym, id); + // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) + const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym; + if (getAllSymbolFlags(target) & SymbolFlags.Value) { + // However if it is a value, we need to check it's being used correctly + checkExpressionCached(node.expression); } } else { - checkExpressionCached(node.expression); + checkExpressionCached(node.expression); // doesn't resolve, check as expression to mark as error } - checkExternalModuleExports(container); - - if ((node.flags & NodeFlags.Ambient) && !isEntityNameExpression(node.expression)) { - grammarErrorOnNode(node.expression, Diagnostics.The_expression_of_an_export_assignment_must_be_an_identifier_or_qualified_name_in_an_ambient_context); + if (getEmitDeclarations(compilerOptions)) { + collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true); } + } + else { + checkExpressionCached(node.expression); + } - if (node.isExportEquals && !(node.flags & NodeFlags.Ambient)) { - if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.CommonJS) { - // export assignment is not supported in es6 modules - grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead); - } - else if (moduleKind === ModuleKind.System) { - // system modules does not support export assignment - grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system); - } - } + checkExternalModuleExports(container); + + if ((node.flags & NodeFlags.Ambient) && !isEntityNameExpression(node.expression)) { + grammarErrorOnNode(node.expression, Diagnostics.The_expression_of_an_export_assignment_must_be_an_identifier_or_qualified_name_in_an_ambient_context); } - function hasExportedMembers(moduleSymbol: Symbol) { - return forEachEntry(moduleSymbol.exports!, (_, id) => id !== "export="); + if (node.isExportEquals && !(node.flags & NodeFlags.Ambient)) { + if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.CommonJS) { + // export assignment is not supported in es6 modules + grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead); + } + else if (moduleKind === ModuleKind.System) { + // system modules does not support export assignment + grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system); + } } + } - function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { - const moduleSymbol = getSymbolOfNode(node); - const links = getSymbolLinks(moduleSymbol); - if (!links.exportsChecked) { - const exportEqualsSymbol = moduleSymbol.exports!.get("export=" as __String); - if (exportEqualsSymbol && hasExportedMembers(moduleSymbol)) { - const declaration = getDeclarationOfAliasSymbol(exportEqualsSymbol) || exportEqualsSymbol.valueDeclaration; - if (declaration && !isTopLevelInExternalModuleAugmentation(declaration) && !isInJSFile(declaration)) { - error(declaration, Diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements); - } + function hasExportedMembers(moduleSymbol: Symbol) { + return forEachEntry(moduleSymbol.exports!, (_, id) => id !== "export="); + } + + function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { + const moduleSymbol = getSymbolOfNode(node); + const links = getSymbolLinks(moduleSymbol); + if (!links.exportsChecked) { + const exportEqualsSymbol = moduleSymbol.exports!.get("export=" as __String); + if (exportEqualsSymbol && hasExportedMembers(moduleSymbol)) { + const declaration = getDeclarationOfAliasSymbol(exportEqualsSymbol) || exportEqualsSymbol.valueDeclaration; + if (declaration && !isTopLevelInExternalModuleAugmentation(declaration) && !isInJSFile(declaration)) { + error(declaration, Diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements); } - // Checks for export * conflicts - const exports = getExportsOfModule(moduleSymbol); - if (exports) { - exports.forEach(({ declarations, flags }, id) => { - if (id === "__export") { - return; - } - // ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. - // (TS Exceptions: namespaces, function overloads, enums, and interfaces) - if (flags & (SymbolFlags.Namespace | SymbolFlags.Enum)) { - return; - } - const exportedDeclarationsCount = countWhere(declarations, and(isNotOverloadAndNotAccessor, not(isInterfaceDeclaration))); - if (flags & SymbolFlags.TypeAlias && exportedDeclarationsCount <= 2) { - // it is legal to merge type alias with other values - // so count should be either 1 (just type alias) or 2 (type alias + merged value) - return; - } - if (exportedDeclarationsCount > 1) { - if (!isDuplicatedCommonJSExport(declarations)) { - for (const declaration of declarations!) { - if (isNotOverload(declaration)) { - diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Cannot_redeclare_exported_variable_0, unescapeLeadingUnderscores(id))); - } + } + // Checks for export * conflicts + const exports = getExportsOfModule(moduleSymbol); + if (exports) { + exports.forEach(({ declarations, flags }, id) => { + if (id === "__export") { + return; + } + // ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. + // (TS Exceptions: namespaces, function overloads, enums, and interfaces) + if (flags & (SymbolFlags.Namespace | SymbolFlags.Enum)) { + return; + } + const exportedDeclarationsCount = countWhere(declarations, and(isNotOverloadAndNotAccessor, not(isInterfaceDeclaration))); + if (flags & SymbolFlags.TypeAlias && exportedDeclarationsCount <= 2) { + // it is legal to merge type alias with other values + // so count should be either 1 (just type alias) or 2 (type alias + merged value) + return; + } + if (exportedDeclarationsCount > 1) { + if (!isDuplicatedCommonJSExport(declarations)) { + for (const declaration of declarations!) { + if (isNotOverload(declaration)) { + diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Cannot_redeclare_exported_variable_0, unescapeLeadingUnderscores(id))); } } } - }); - } - links.exportsChecked = true; + } + }); } + links.exportsChecked = true; } + } - function isDuplicatedCommonJSExport(declarations: Declaration[] | undefined) { - return declarations - && declarations.length > 1 - && declarations.every(d => isInJSFile(d) && isAccessExpression(d) && (isExportsIdentifier(d.expression) || isModuleExportsAccessExpression(d.expression))); - } + function isDuplicatedCommonJSExport(declarations: Declaration[] | undefined) { + return declarations + && declarations.length > 1 + && declarations.every(d => isInJSFile(d) && isAccessExpression(d) && (isExportsIdentifier(d.expression) || isModuleExportsAccessExpression(d.expression))); + } - function checkSourceElement(node: Node | undefined): void { - if (node) { - const saveCurrentNode = currentNode; - currentNode = node; - instantiationCount = 0; - checkSourceElementWorker(node); - currentNode = saveCurrentNode; - } + function checkSourceElement(node: Node | undefined): void { + if (node) { + const saveCurrentNode = currentNode; + currentNode = node; + instantiationCount = 0; + checkSourceElementWorker(node); + currentNode = saveCurrentNode; } + } - function checkSourceElementWorker(node: Node): void { - forEach((node as JSDocContainer).jsDoc, ({ comment, tags }) => { - checkJSDocCommentWorker(comment); - forEach(tags, tag => { - checkJSDocCommentWorker(tag.comment); - if (isInJSFile(node)) { - checkSourceElement(tag); - } - }); - }); - - const kind = node.kind; - if (cancellationToken) { - // Only bother checking on a few construct kinds. We don't want to be excessively - // hitting the cancellation token on every node we check. - switch (kind) { - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.FunctionDeclaration: - cancellationToken.throwIfCancellationRequested(); + function checkSourceElementWorker(node: Node): void { + forEach((node as JSDocContainer).jsDoc, ({ comment, tags }) => { + checkJSDocCommentWorker(comment); + forEach(tags, tag => { + checkJSDocCommentWorker(tag.comment); + if (isInJSFile(node)) { + checkSourceElement(tag); } - } - if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && node.flowNode && !isReachableFlowNode(node.flowNode)) { - errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); - } + }); + }); + const kind = node.kind; + if (cancellationToken) { + // Only bother checking on a few construct kinds. We don't want to be excessively + // hitting the cancellation token on every node we check. switch (kind) { - case SyntaxKind.TypeParameter: - return checkTypeParameter(node as TypeParameterDeclaration); - case SyntaxKind.Parameter: - return checkParameter(node as ParameterDeclaration); - case SyntaxKind.PropertyDeclaration: - return checkPropertyDeclaration(node as PropertyDeclaration); - case SyntaxKind.PropertySignature: - return checkPropertySignature(node as PropertySignature); - case SyntaxKind.ConstructorType: - case SyntaxKind.FunctionType: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - return checkSignatureDeclaration(node as SignatureDeclaration); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return checkMethodDeclaration(node as MethodDeclaration | MethodSignature); - case SyntaxKind.ClassStaticBlockDeclaration: - return checkClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); - case SyntaxKind.Constructor: - return checkConstructorDeclaration(node as ConstructorDeclaration); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return checkAccessorDeclaration(node as AccessorDeclaration); - case SyntaxKind.TypeReference: - return checkTypeReferenceNode(node as TypeReferenceNode); - case SyntaxKind.TypePredicate: - return checkTypePredicate(node as TypePredicateNode); - case SyntaxKind.TypeQuery: - return checkTypeQuery(node as TypeQueryNode); - case SyntaxKind.TypeLiteral: - return checkTypeLiteral(node as TypeLiteralNode); - case SyntaxKind.ArrayType: - return checkArrayType(node as ArrayTypeNode); - case SyntaxKind.TupleType: - return checkTupleType(node as TupleTypeNode); - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - return checkUnionOrIntersectionType(node as UnionOrIntersectionTypeNode); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.OptionalType: - case SyntaxKind.RestType: - return checkSourceElement((node as ParenthesizedTypeNode | OptionalTypeNode | RestTypeNode).type); - case SyntaxKind.ThisType: - return checkThisType(node as ThisTypeNode); - case SyntaxKind.TypeOperator: - return checkTypeOperator(node as TypeOperatorNode); - case SyntaxKind.ConditionalType: - return checkConditionalType(node as ConditionalTypeNode); - case SyntaxKind.InferType: - return checkInferType(node as InferTypeNode); - case SyntaxKind.TemplateLiteralType: - return checkTemplateLiteralType(node as TemplateLiteralTypeNode); - case SyntaxKind.ImportType: - return checkImportType(node as ImportTypeNode); - case SyntaxKind.NamedTupleMember: - return checkNamedTupleMember(node as NamedTupleMember); - case SyntaxKind.JSDocAugmentsTag: - return checkJSDocAugmentsTag(node as JSDocAugmentsTag); - case SyntaxKind.JSDocImplementsTag: - return checkJSDocImplementsTag(node as JSDocImplementsTag); - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - return checkJSDocTypeAliasTag(node as JSDocTypedefTag); - case SyntaxKind.JSDocTemplateTag: - return checkJSDocTemplateTag(node as JSDocTemplateTag); - case SyntaxKind.JSDocTypeTag: - return checkJSDocTypeTag(node as JSDocTypeTag); - case SyntaxKind.JSDocLink: - case SyntaxKind.JSDocLinkCode: - case SyntaxKind.JSDocLinkPlain: - return checkJSDocLinkLikeTag(node as JSDocLink | JSDocLinkCode | JSDocLinkPlain); - case SyntaxKind.JSDocParameterTag: - return checkJSDocParameterTag(node as JSDocParameterTag); - case SyntaxKind.JSDocPropertyTag: - return checkJSDocPropertyTag(node as JSDocPropertyTag); - case SyntaxKind.JSDocFunctionType: - checkJSDocFunctionType(node as JSDocFunctionType); - // falls through - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocNullableType: - case SyntaxKind.JSDocAllType: - case SyntaxKind.JSDocUnknownType: - case SyntaxKind.JSDocTypeLiteral: - checkJSDocTypeIsInJsFile(node); - forEachChild(node, checkSourceElement); - return; - case SyntaxKind.JSDocVariadicType: - checkJSDocVariadicType(node as JSDocVariadicType); - return; - case SyntaxKind.JSDocTypeExpression: - return checkSourceElement((node as JSDocTypeExpression).type); - case SyntaxKind.JSDocPublicTag: - case SyntaxKind.JSDocProtectedTag: - case SyntaxKind.JSDocPrivateTag: - return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag); - case SyntaxKind.IndexedAccessType: - return checkIndexedAccessType(node as IndexedAccessTypeNode); - case SyntaxKind.MappedType: - return checkMappedType(node as MappedTypeNode); - case SyntaxKind.FunctionDeclaration: - return checkFunctionDeclaration(node as FunctionDeclaration); - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - return checkBlock(node as Block); - case SyntaxKind.VariableStatement: - return checkVariableStatement(node as VariableStatement); - case SyntaxKind.ExpressionStatement: - return checkExpressionStatement(node as ExpressionStatement); - case SyntaxKind.IfStatement: - return checkIfStatement(node as IfStatement); - case SyntaxKind.DoStatement: - return checkDoStatement(node as DoStatement); - case SyntaxKind.WhileStatement: - return checkWhileStatement(node as WhileStatement); - case SyntaxKind.ForStatement: - return checkForStatement(node as ForStatement); - case SyntaxKind.ForInStatement: - return checkForInStatement(node as ForInStatement); - case SyntaxKind.ForOfStatement: - return checkForOfStatement(node as ForOfStatement); - case SyntaxKind.ContinueStatement: - case SyntaxKind.BreakStatement: - return checkBreakOrContinueStatement(node as BreakOrContinueStatement); - case SyntaxKind.ReturnStatement: - return checkReturnStatement(node as ReturnStatement); - case SyntaxKind.WithStatement: - return checkWithStatement(node as WithStatement); - case SyntaxKind.SwitchStatement: - return checkSwitchStatement(node as SwitchStatement); - case SyntaxKind.LabeledStatement: - return checkLabeledStatement(node as LabeledStatement); - case SyntaxKind.ThrowStatement: - return checkThrowStatement(node as ThrowStatement); - case SyntaxKind.TryStatement: - return checkTryStatement(node as TryStatement); - case SyntaxKind.VariableDeclaration: - return checkVariableDeclaration(node as VariableDeclaration); - case SyntaxKind.BindingElement: - return checkBindingElement(node as BindingElement); + case SyntaxKind.ModuleDeclaration: case SyntaxKind.ClassDeclaration: - return checkClassDeclaration(node as ClassDeclaration); case SyntaxKind.InterfaceDeclaration: - return checkInterfaceDeclaration(node as InterfaceDeclaration); - case SyntaxKind.TypeAliasDeclaration: - return checkTypeAliasDeclaration(node as TypeAliasDeclaration); - case SyntaxKind.EnumDeclaration: - return checkEnumDeclaration(node as EnumDeclaration); - case SyntaxKind.ModuleDeclaration: - return checkModuleDeclaration(node as ModuleDeclaration); - case SyntaxKind.ImportDeclaration: - return checkImportDeclaration(node as ImportDeclaration); - case SyntaxKind.ImportEqualsDeclaration: - return checkImportEqualsDeclaration(node as ImportEqualsDeclaration); - case SyntaxKind.ExportDeclaration: - return checkExportDeclaration(node as ExportDeclaration); - case SyntaxKind.ExportAssignment: - return checkExportAssignment(node as ExportAssignment); - case SyntaxKind.EmptyStatement: - case SyntaxKind.DebuggerStatement: - checkGrammarStatementInAmbientContext(node); - return; - case SyntaxKind.MissingDeclaration: - return checkMissingDeclaration(node); - } - } - - function checkJSDocCommentWorker(node: string | readonly JSDocComment[] | undefined) { - if (isArray(node)) { - forEach(node, tag => { - if (isJSDocLinkLike(tag)) { - checkSourceElement(tag); - } - }); + case SyntaxKind.FunctionDeclaration: + cancellationToken.throwIfCancellationRequested(); } } - - function checkJSDocTypeIsInJsFile(node: Node): void { - if (!isInJSFile(node)) { - grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); - } + if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && node.flowNode && !isReachableFlowNode(node.flowNode)) { + errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); + } + + switch (kind) { + case SyntaxKind.TypeParameter: + return checkTypeParameter(node as TypeParameterDeclaration); + case SyntaxKind.Parameter: + return checkParameter(node as ParameterDeclaration); + case SyntaxKind.PropertyDeclaration: + return checkPropertyDeclaration(node as PropertyDeclaration); + case SyntaxKind.PropertySignature: + return checkPropertySignature(node as PropertySignature); + case SyntaxKind.ConstructorType: + case SyntaxKind.FunctionType: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + return checkSignatureDeclaration(node as SignatureDeclaration); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return checkMethodDeclaration(node as MethodDeclaration | MethodSignature); + case SyntaxKind.ClassStaticBlockDeclaration: + return checkClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); + case SyntaxKind.Constructor: + return checkConstructorDeclaration(node as ConstructorDeclaration); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return checkAccessorDeclaration(node as AccessorDeclaration); + case SyntaxKind.TypeReference: + return checkTypeReferenceNode(node as TypeReferenceNode); + case SyntaxKind.TypePredicate: + return checkTypePredicate(node as TypePredicateNode); + case SyntaxKind.TypeQuery: + return checkTypeQuery(node as TypeQueryNode); + case SyntaxKind.TypeLiteral: + return checkTypeLiteral(node as TypeLiteralNode); + case SyntaxKind.ArrayType: + return checkArrayType(node as ArrayTypeNode); + case SyntaxKind.TupleType: + return checkTupleType(node as TupleTypeNode); + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + return checkUnionOrIntersectionType(node as UnionOrIntersectionTypeNode); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.OptionalType: + case SyntaxKind.RestType: + return checkSourceElement((node as ParenthesizedTypeNode | OptionalTypeNode | RestTypeNode).type); + case SyntaxKind.ThisType: + return checkThisType(node as ThisTypeNode); + case SyntaxKind.TypeOperator: + return checkTypeOperator(node as TypeOperatorNode); + case SyntaxKind.ConditionalType: + return checkConditionalType(node as ConditionalTypeNode); + case SyntaxKind.InferType: + return checkInferType(node as InferTypeNode); + case SyntaxKind.TemplateLiteralType: + return checkTemplateLiteralType(node as TemplateLiteralTypeNode); + case SyntaxKind.ImportType: + return checkImportType(node as ImportTypeNode); + case SyntaxKind.NamedTupleMember: + return checkNamedTupleMember(node as NamedTupleMember); + case SyntaxKind.JSDocAugmentsTag: + return checkJSDocAugmentsTag(node as JSDocAugmentsTag); + case SyntaxKind.JSDocImplementsTag: + return checkJSDocImplementsTag(node as JSDocImplementsTag); + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + return checkJSDocTypeAliasTag(node as JSDocTypedefTag); + case SyntaxKind.JSDocTemplateTag: + return checkJSDocTemplateTag(node as JSDocTemplateTag); + case SyntaxKind.JSDocTypeTag: + return checkJSDocTypeTag(node as JSDocTypeTag); + case SyntaxKind.JSDocLink: + case SyntaxKind.JSDocLinkCode: + case SyntaxKind.JSDocLinkPlain: + return checkJSDocLinkLikeTag(node as JSDocLink | JSDocLinkCode | JSDocLinkPlain); + case SyntaxKind.JSDocParameterTag: + return checkJSDocParameterTag(node as JSDocParameterTag); + case SyntaxKind.JSDocPropertyTag: + return checkJSDocPropertyTag(node as JSDocPropertyTag); + case SyntaxKind.JSDocFunctionType: + checkJSDocFunctionType(node as JSDocFunctionType); + // falls through + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocNullableType: + case SyntaxKind.JSDocAllType: + case SyntaxKind.JSDocUnknownType: + case SyntaxKind.JSDocTypeLiteral: + checkJSDocTypeIsInJsFile(node); + forEachChild(node, checkSourceElement); + return; + case SyntaxKind.JSDocVariadicType: + checkJSDocVariadicType(node as JSDocVariadicType); + return; + case SyntaxKind.JSDocTypeExpression: + return checkSourceElement((node as JSDocTypeExpression).type); + case SyntaxKind.JSDocPublicTag: + case SyntaxKind.JSDocProtectedTag: + case SyntaxKind.JSDocPrivateTag: + return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag); + case SyntaxKind.IndexedAccessType: + return checkIndexedAccessType(node as IndexedAccessTypeNode); + case SyntaxKind.MappedType: + return checkMappedType(node as MappedTypeNode); + case SyntaxKind.FunctionDeclaration: + return checkFunctionDeclaration(node as FunctionDeclaration); + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + return checkBlock(node as Block); + case SyntaxKind.VariableStatement: + return checkVariableStatement(node as VariableStatement); + case SyntaxKind.ExpressionStatement: + return checkExpressionStatement(node as ExpressionStatement); + case SyntaxKind.IfStatement: + return checkIfStatement(node as IfStatement); + case SyntaxKind.DoStatement: + return checkDoStatement(node as DoStatement); + case SyntaxKind.WhileStatement: + return checkWhileStatement(node as WhileStatement); + case SyntaxKind.ForStatement: + return checkForStatement(node as ForStatement); + case SyntaxKind.ForInStatement: + return checkForInStatement(node as ForInStatement); + case SyntaxKind.ForOfStatement: + return checkForOfStatement(node as ForOfStatement); + case SyntaxKind.ContinueStatement: + case SyntaxKind.BreakStatement: + return checkBreakOrContinueStatement(node as BreakOrContinueStatement); + case SyntaxKind.ReturnStatement: + return checkReturnStatement(node as ReturnStatement); + case SyntaxKind.WithStatement: + return checkWithStatement(node as WithStatement); + case SyntaxKind.SwitchStatement: + return checkSwitchStatement(node as SwitchStatement); + case SyntaxKind.LabeledStatement: + return checkLabeledStatement(node as LabeledStatement); + case SyntaxKind.ThrowStatement: + return checkThrowStatement(node as ThrowStatement); + case SyntaxKind.TryStatement: + return checkTryStatement(node as TryStatement); + case SyntaxKind.VariableDeclaration: + return checkVariableDeclaration(node as VariableDeclaration); + case SyntaxKind.BindingElement: + return checkBindingElement(node as BindingElement); + case SyntaxKind.ClassDeclaration: + return checkClassDeclaration(node as ClassDeclaration); + case SyntaxKind.InterfaceDeclaration: + return checkInterfaceDeclaration(node as InterfaceDeclaration); + case SyntaxKind.TypeAliasDeclaration: + return checkTypeAliasDeclaration(node as TypeAliasDeclaration); + case SyntaxKind.EnumDeclaration: + return checkEnumDeclaration(node as EnumDeclaration); + case SyntaxKind.ModuleDeclaration: + return checkModuleDeclaration(node as ModuleDeclaration); + case SyntaxKind.ImportDeclaration: + return checkImportDeclaration(node as ImportDeclaration); + case SyntaxKind.ImportEqualsDeclaration: + return checkImportEqualsDeclaration(node as ImportEqualsDeclaration); + case SyntaxKind.ExportDeclaration: + return checkExportDeclaration(node as ExportDeclaration); + case SyntaxKind.ExportAssignment: + return checkExportAssignment(node as ExportAssignment); + case SyntaxKind.EmptyStatement: + case SyntaxKind.DebuggerStatement: + checkGrammarStatementInAmbientContext(node); + return; + case SyntaxKind.MissingDeclaration: + return checkMissingDeclaration(node); } + } - function checkJSDocVariadicType(node: JSDocVariadicType): void { - checkJSDocTypeIsInJsFile(node); - checkSourceElement(node.type); - - // Only legal location is in the *last* parameter tag or last parameter of a JSDoc function. - const { parent } = node; - if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { - if (last(parent.parent.parameters) !== parent) { - error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); + function checkJSDocCommentWorker(node: string | readonly JSDocComment[] | undefined) { + if (isArray(node)) { + forEach(node, tag => { + if (isJSDocLinkLike(tag)) { + checkSourceElement(tag); } - return; - } - - if (!isJSDocTypeExpression(parent)) { - error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); - } + }); + } + } - const paramTag = node.parent.parent; - if (!isJSDocParameterTag(paramTag)) { - error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); - return; - } + function checkJSDocTypeIsInJsFile(node: Node): void { + if (!isInJSFile(node)) { + grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } + } - const param = getParameterSymbolFromJSDoc(paramTag); - if (!param) { - // We will error in `checkJSDocParameterTag`. - return; - } + function checkJSDocVariadicType(node: JSDocVariadicType): void { + checkJSDocTypeIsInJsFile(node); + checkSourceElement(node.type); - const host = getHostSignatureFromJSDoc(paramTag); - if (!host || last(host.parameters).symbol !== param) { + // Only legal location is in the *last* parameter tag or last parameter of a JSDoc function. + const { parent } = node; + if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { + if (last(parent.parent.parameters) !== parent) { error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); } + return; } - function getTypeFromJSDocVariadicType(node: JSDocVariadicType): Type { - const type = getTypeFromTypeNode(node.type); - const { parent } = node; - const paramTag = node.parent.parent; - if (isJSDocTypeExpression(node.parent) && isJSDocParameterTag(paramTag)) { - // Else we will add a diagnostic, see `checkJSDocVariadicType`. - const host = getHostSignatureFromJSDoc(paramTag); - const isCallbackTag = isJSDocCallbackTag(paramTag.parent.parent); - if (host || isCallbackTag) { - /* - Only return an array type if the corresponding parameter is marked as a rest parameter, or if there are no parameters. - So in the following situation we will not create an array type: - /** @param {...number} a * / - function f(a) {} - Because `a` will just be of type `number | undefined`. A synthetic `...args` will also be added, which *will* get an array type. - */ - const lastParamDeclaration = isCallbackTag - ? lastOrUndefined((paramTag.parent.parent as unknown as JSDocCallbackTag).typeExpression.parameters) - : lastOrUndefined(host!.parameters); - const symbol = getParameterSymbolFromJSDoc(paramTag); - if (!lastParamDeclaration || - symbol && lastParamDeclaration.symbol === symbol && isRestParameter(lastParamDeclaration)) { - return createArrayType(type); - } - } - } - if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { - return createArrayType(type); - } - return addOptionality(type); - } - - // Function and class expression bodies are checked after all statements in the enclosing body. This is - // to ensure constructs like the following are permitted: - // const foo = function () { - // const s = foo(); - // return "hello"; - // } - // Here, performing a full type check of the body of the function expression whilst in the process of - // determining the type of foo would cause foo to be given type any because of the recursive reference. - // Delaying the type check of the body ensures foo has been assigned a type. - function checkNodeDeferred(node: Node) { - const enclosingFile = getSourceFileOfNode(node); - const links = getNodeLinks(enclosingFile); - if (!(links.flags & NodeCheckFlags.TypeChecked)) { - links.deferredNodes ||= new Set(); - links.deferredNodes.add(node); - } + if (!isJSDocTypeExpression(parent)) { + error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); } - function checkDeferredNodes(context: SourceFile) { - const links = getNodeLinks(context); - if (links.deferredNodes) { - links.deferredNodes.forEach(checkDeferredNode); - } + const paramTag = node.parent.parent; + if (!isJSDocParameterTag(paramTag)) { + error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); + return; } - function checkDeferredNode(node: Node) { - tracing?.push(tracing.Phase.Check, "checkDeferredNode", { kind: node.kind, pos: node.pos, end: node.end, path: (node as TracingNode).tracingPath }); - const saveCurrentNode = currentNode; - currentNode = node; - instantiationCount = 0; - switch (node.kind) { - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.TaggedTemplateExpression: - case SyntaxKind.Decorator: - case SyntaxKind.JsxOpeningElement: - // These node kinds are deferred checked when overload resolution fails - // To save on work, we ensure the arguments are checked just once, in - // a deferred way - resolveUntypedCall(node as CallLikeExpression); - break; - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - checkFunctionExpressionOrObjectLiteralMethodDeferred(node as FunctionExpression); - break; - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - checkAccessorDeclaration(node as AccessorDeclaration); - break; - case SyntaxKind.ClassExpression: - checkClassExpressionDeferred(node as ClassExpression); - break; - case SyntaxKind.TypeParameter: - checkTypeParameterDeferred(node as TypeParameterDeclaration); - break; - case SyntaxKind.JsxSelfClosingElement: - checkJsxSelfClosingElementDeferred(node as JsxSelfClosingElement); - break; - case SyntaxKind.JsxElement: - checkJsxElementDeferred(node as JsxElement); - break; - } - currentNode = saveCurrentNode; - tracing?.pop(); + const param = getParameterSymbolFromJSDoc(paramTag); + if (!param) { + // We will error in `checkJSDocParameterTag`. + return; } - function checkSourceFile(node: SourceFile) { - tracing?.push(tracing.Phase.Check, "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true); - performance.mark("beforeCheck"); - checkSourceFileWorker(node); - performance.mark("afterCheck"); - performance.measure("Check", "beforeCheck", "afterCheck"); - tracing?.pop(); + const host = getHostSignatureFromJSDoc(paramTag); + if (!host || last(host.parameters).symbol !== param) { + error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); } + } - function unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean { - if (isAmbient) { - return false; - } - switch (kind) { - case UnusedKind.Local: - return !!compilerOptions.noUnusedLocals; - case UnusedKind.Parameter: - return !!compilerOptions.noUnusedParameters; - default: - return Debug.assertNever(kind); - } + function getTypeFromJSDocVariadicType(node: JSDocVariadicType): Type { + const type = getTypeFromTypeNode(node.type); + const { parent } = node; + const paramTag = node.parent.parent; + if (isJSDocTypeExpression(node.parent) && isJSDocParameterTag(paramTag)) { + // Else we will add a diagnostic, see `checkJSDocVariadicType`. + const host = getHostSignatureFromJSDoc(paramTag); + const isCallbackTag = isJSDocCallbackTag(paramTag.parent.parent); + if (host || isCallbackTag) { + /* + Only return an array type if the corresponding parameter is marked as a rest parameter, or if there are no parameters. + So in the following situation we will not create an array type: + /** @param {...number} a * / + function f(a) {} + Because `a` will just be of type `number | undefined`. A synthetic `...args` will also be added, which *will* get an array type. + */ + const lastParamDeclaration = isCallbackTag + ? lastOrUndefined((paramTag.parent.parent as unknown as JSDocCallbackTag).typeExpression.parameters) + : lastOrUndefined(host!.parameters); + const symbol = getParameterSymbolFromJSDoc(paramTag); + if (!lastParamDeclaration || + symbol && lastParamDeclaration.symbol === symbol && isRestParameter(lastParamDeclaration)) { + return createArrayType(type); + } + } + } + if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { + return createArrayType(type); + } + return addOptionality(type); + } + + // Function and class expression bodies are checked after all statements in the enclosing body. This is + // to ensure constructs like the following are permitted: + // const foo = function () { + // const s = foo(); + // return "hello"; + // } + // Here, performing a full type check of the body of the function expression whilst in the process of + // determining the type of foo would cause foo to be given type any because of the recursive reference. + // Delaying the type check of the body ensures foo has been assigned a type. + function checkNodeDeferred(node: Node) { + const enclosingFile = getSourceFileOfNode(node); + const links = getNodeLinks(enclosingFile); + if (!(links.flags & NodeCheckFlags.TypeChecked)) { + links.deferredNodes ||= new Set(); + links.deferredNodes.add(node); } + } - function getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] { - return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray; + function checkDeferredNodes(context: SourceFile) { + const links = getNodeLinks(context); + if (links.deferredNodes) { + links.deferredNodes.forEach(checkDeferredNode); } + } - // Fully type check a source file and collect the relevant diagnostics. - function checkSourceFileWorker(node: SourceFile) { - const links = getNodeLinks(node); - if (!(links.flags & NodeCheckFlags.TypeChecked)) { - if (skipTypeChecking(node, compilerOptions, host)) { - return; - } + function checkDeferredNode(node: Node) { + tracing?.push(tracing.Phase.Check, "checkDeferredNode", { kind: node.kind, pos: node.pos, end: node.end, path: (node as TracingNode).tracingPath }); + const saveCurrentNode = currentNode; + currentNode = node; + instantiationCount = 0; + switch (node.kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.Decorator: + case SyntaxKind.JsxOpeningElement: + // These node kinds are deferred checked when overload resolution fails + // To save on work, we ensure the arguments are checked just once, in + // a deferred way + resolveUntypedCall(node as CallLikeExpression); + break; + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + checkFunctionExpressionOrObjectLiteralMethodDeferred(node as FunctionExpression); + break; + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + checkAccessorDeclaration(node as AccessorDeclaration); + break; + case SyntaxKind.ClassExpression: + checkClassExpressionDeferred(node as ClassExpression); + break; + case SyntaxKind.TypeParameter: + checkTypeParameterDeferred(node as TypeParameterDeclaration); + break; + case SyntaxKind.JsxSelfClosingElement: + checkJsxSelfClosingElementDeferred(node as JsxSelfClosingElement); + break; + case SyntaxKind.JsxElement: + checkJsxElementDeferred(node as JsxElement); + break; + } + currentNode = saveCurrentNode; + tracing?.pop(); + } - // Grammar checking - checkGrammarSourceFile(node); + function checkSourceFile(node: SourceFile) { + tracing?.push(tracing.Phase.Check, "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true); + performance.mark("beforeCheck"); + checkSourceFileWorker(node); + performance.mark("afterCheck"); + performance.measure("Check", "beforeCheck", "afterCheck"); + tracing?.pop(); + } - clear(potentialThisCollisions); - clear(potentialNewTargetCollisions); - clear(potentialWeakMapSetCollisions); - clear(potentialReflectCollisions); - clear(potentialUnusedRenamedBindingElementsInTypes); + function unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean { + if (isAmbient) { + return false; + } + switch (kind) { + case UnusedKind.Local: + return !!compilerOptions.noUnusedLocals; + case UnusedKind.Parameter: + return !!compilerOptions.noUnusedParameters; + default: + return Debug.assertNever(kind); + } + } - forEach(node.statements, checkSourceElement); - checkSourceElement(node.endOfFileToken); + function getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] { + return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray; + } - checkDeferredNodes(node); + // Fully type check a source file and collect the relevant diagnostics. + function checkSourceFileWorker(node: SourceFile) { + const links = getNodeLinks(node); + if (!(links.flags & NodeCheckFlags.TypeChecked)) { + if (skipTypeChecking(node, compilerOptions, host)) { + return; + } - if (isExternalOrCommonJsModule(node)) { - registerForUnusedIdentifiersCheck(node); - } + // Grammar checking + checkGrammarSourceFile(node); - addLazyDiagnostic(() => { - // This relies on the results of other lazy diagnostics, so must be computed after them - if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) { - checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(node), (containingNode, kind, diag) => { - if (!containsParseError(containingNode) && unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { - diagnostics.add(diag); - } - }); - } - if (!node.isDeclarationFile) { - checkPotentialUncheckedRenamedBindingElementsInTypes(); - } - }); + clear(potentialThisCollisions); + clear(potentialNewTargetCollisions); + clear(potentialWeakMapSetCollisions); + clear(potentialReflectCollisions); + clear(potentialUnusedRenamedBindingElementsInTypes); - if (compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && - !node.isDeclarationFile && - isExternalModule(node) - ) { - checkImportsForTypeOnlyConversion(node); - } + forEach(node.statements, checkSourceElement); + checkSourceElement(node.endOfFileToken); - if (isExternalOrCommonJsModule(node)) { - checkExternalModuleExports(node); - } + checkDeferredNodes(node); - if (potentialThisCollisions.length) { - forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); - clear(potentialThisCollisions); - } + if (isExternalOrCommonJsModule(node)) { + registerForUnusedIdentifiersCheck(node); + } - if (potentialNewTargetCollisions.length) { - forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); - clear(potentialNewTargetCollisions); + addLazyDiagnostic(() => { + // This relies on the results of other lazy diagnostics, so must be computed after them + if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) { + checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(node), (containingNode, kind, diag) => { + if (!containsParseError(containingNode) && unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { + diagnostics.add(diag); + } + }); } - - if (potentialWeakMapSetCollisions.length) { - forEach(potentialWeakMapSetCollisions, checkWeakMapSetCollision); - clear(potentialWeakMapSetCollisions); + if (!node.isDeclarationFile) { + checkPotentialUncheckedRenamedBindingElementsInTypes(); } + }); - if (potentialReflectCollisions.length) { - forEach(potentialReflectCollisions, checkReflectCollision); - clear(potentialReflectCollisions); - } + if (compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && + !node.isDeclarationFile && + isExternalModule(node) + ) { + checkImportsForTypeOnlyConversion(node); + } - links.flags |= NodeCheckFlags.TypeChecked; + if (isExternalOrCommonJsModule(node)) { + checkExternalModuleExports(node); } - } - function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] { - try { - // Record the cancellation token so it can be checked later on during checkSourceElement. - // Do this in a finally block so we can ensure that it gets reset back to nothing after - // this call is done. - cancellationToken = ct; - return getDiagnosticsWorker(sourceFile); + if (potentialThisCollisions.length) { + forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); + clear(potentialThisCollisions); } - finally { - cancellationToken = undefined; + + if (potentialNewTargetCollisions.length) { + forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); + clear(potentialNewTargetCollisions); + } + + if (potentialWeakMapSetCollisions.length) { + forEach(potentialWeakMapSetCollisions, checkWeakMapSetCollision); + clear(potentialWeakMapSetCollisions); } - } - function ensurePendingDiagnosticWorkComplete() { - // Invoke any existing lazy diagnostics to add them, clear the backlog of diagnostics - for (const cb of deferredDiagnosticsCallbacks) { - cb(); + if (potentialReflectCollisions.length) { + forEach(potentialReflectCollisions, checkReflectCollision); + clear(potentialReflectCollisions); } - deferredDiagnosticsCallbacks = []; + + links.flags |= NodeCheckFlags.TypeChecked; } + } - function checkSourceFileWithEagerDiagnostics(sourceFile: SourceFile) { - ensurePendingDiagnosticWorkComplete(); - // then setup diagnostics for immediate invocation (as we are about to collect them, and - // this avoids the overhead of longer-lived callbacks we don't need to allocate) - // This also serves to make the shift to possibly lazy diagnostics transparent to serial command-line scenarios - // (as in those cases, all the diagnostics will still be computed as the appropriate place in the tree, - // thus much more likely retaining the same union ordering as before we had lazy diagnostics) - const oldAddLazyDiagnostics = addLazyDiagnostic; - addLazyDiagnostic = cb => cb(); - checkSourceFile(sourceFile); - addLazyDiagnostic = oldAddLazyDiagnostics; + function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] { + try { + // Record the cancellation token so it can be checked later on during checkSourceElement. + // Do this in a finally block so we can ensure that it gets reset back to nothing after + // this call is done. + cancellationToken = ct; + return getDiagnosticsWorker(sourceFile); } + finally { + cancellationToken = undefined; + } + } - function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] { - if (sourceFile) { - ensurePendingDiagnosticWorkComplete(); - // Some global diagnostics are deferred until they are needed and - // may not be reported in the first call to getGlobalDiagnostics. - // We should catch these changes and report them. - const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); - const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length; + function ensurePendingDiagnosticWorkComplete() { + // Invoke any existing lazy diagnostics to add them, clear the backlog of diagnostics + for (const cb of deferredDiagnosticsCallbacks) { + cb(); + } + deferredDiagnosticsCallbacks = []; + } - checkSourceFileWithEagerDiagnostics(sourceFile); + function checkSourceFileWithEagerDiagnostics(sourceFile: SourceFile) { + ensurePendingDiagnosticWorkComplete(); + // then setup diagnostics for immediate invocation (as we are about to collect them, and + // this avoids the overhead of longer-lived callbacks we don't need to allocate) + // This also serves to make the shift to possibly lazy diagnostics transparent to serial command-line scenarios + // (as in those cases, all the diagnostics will still be computed as the appropriate place in the tree, + // thus much more likely retaining the same union ordering as before we had lazy diagnostics) + const oldAddLazyDiagnostics = addLazyDiagnostic; + addLazyDiagnostic = cb => cb(); + checkSourceFile(sourceFile); + addLazyDiagnostic = oldAddLazyDiagnostics; + } - const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName); - const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); - if (currentGlobalDiagnostics !== previousGlobalDiagnostics) { - // If the arrays are not the same reference, new diagnostics were added. - const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics); - return concatenate(deferredGlobalDiagnostics, semanticDiagnostics); - } - else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) { - // If the arrays are the same reference, but the length has changed, a single - // new diagnostic was added as DiagnosticCollection attempts to reuse the - // same array. - return concatenate(currentGlobalDiagnostics, semanticDiagnostics); - } + function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] { + if (sourceFile) { + ensurePendingDiagnosticWorkComplete(); + // Some global diagnostics are deferred until they are needed and + // may not be reported in the first call to getGlobalDiagnostics. + // We should catch these changes and report them. + const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); + const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length; - return semanticDiagnostics; + checkSourceFileWithEagerDiagnostics(sourceFile); + + const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName); + const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); + if (currentGlobalDiagnostics !== previousGlobalDiagnostics) { + // If the arrays are not the same reference, new diagnostics were added. + const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics); + return concatenate(deferredGlobalDiagnostics, semanticDiagnostics); + } + else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) { + // If the arrays are the same reference, but the length has changed, a single + // new diagnostic was added as DiagnosticCollection attempts to reuse the + // same array. + return concatenate(currentGlobalDiagnostics, semanticDiagnostics); } - // Global diagnostics are always added when a file is not provided to - // getDiagnostics - forEach(host.getSourceFiles(), checkSourceFileWithEagerDiagnostics); - return diagnostics.getDiagnostics(); + return semanticDiagnostics; } - function getGlobalDiagnostics(): Diagnostic[] { - ensurePendingDiagnosticWorkComplete(); - return diagnostics.getGlobalDiagnostics(); - } + // Global diagnostics are always added when a file is not provided to + // getDiagnostics + forEach(host.getSourceFiles(), checkSourceFileWithEagerDiagnostics); + return diagnostics.getDiagnostics(); + } - // Language service support + function getGlobalDiagnostics(): Diagnostic[] { + ensurePendingDiagnosticWorkComplete(); + return diagnostics.getGlobalDiagnostics(); + } - function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { - if (location.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return []; - } + // Language service support - const symbols = createSymbolTable(); - let isStaticSymbol = false; + function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { + if (location.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return []; + } - populateSymbols(); + const symbols = createSymbolTable(); + let isStaticSymbol = false; - symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword - return symbolsToArray(symbols); + populateSymbols(); - function populateSymbols() { - while (location) { - if (location.locals && !isGlobalSourceFile(location)) { - copySymbols(location.locals, meaning); - } + symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword + return symbolsToArray(symbols); - switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalModule(location as SourceFile)) break; - // falls through - case SyntaxKind.ModuleDeclaration: - copyLocallyVisibleExportSymbols(getSymbolOfNode(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); - break; - case SyntaxKind.EnumDeclaration: - copySymbols(getSymbolOfNode(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); - break; - case SyntaxKind.ClassExpression: - const className = (location as ClassExpression).name; - if (className) { - copySymbol(location.symbol, meaning); - } + function populateSymbols() { + while (location) { + if (location.locals && !isGlobalSourceFile(location)) { + copySymbols(location.locals, meaning); + } - // this fall-through is necessary because we would like to handle - // type parameter inside class expression similar to how we handle it in classDeclaration and interface Declaration. + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalModule(location as SourceFile)) break; // falls through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - // If we didn't come from static member of class or interface, - // add the type parameters into the symbol table - // (type parameters of classDeclaration/classExpression and interface are in member property of the symbol. - // Note: that the memberFlags come from previous iteration. - if (!isStaticSymbol) { - copySymbols(getMembersOfSymbol(getSymbolOfNode(location as ClassDeclaration | InterfaceDeclaration)), meaning & SymbolFlags.Type); - } - break; - case SyntaxKind.FunctionExpression: - const funcName = (location as FunctionExpression).name; - if (funcName) { - copySymbol(location.symbol, meaning); - } - break; - } + case SyntaxKind.ModuleDeclaration: + copyLocallyVisibleExportSymbols(getSymbolOfNode(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); + break; + case SyntaxKind.EnumDeclaration: + copySymbols(getSymbolOfNode(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); + break; + case SyntaxKind.ClassExpression: + const className = (location as ClassExpression).name; + if (className) { + copySymbol(location.symbol, meaning); + } - if (introducesArgumentsExoticObject(location)) { - copySymbol(argumentsSymbol, meaning); - } + // this fall-through is necessary because we would like to handle + // type parameter inside class expression similar to how we handle it in classDeclaration and interface Declaration. + // falls through + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + // If we didn't come from static member of class or interface, + // add the type parameters into the symbol table + // (type parameters of classDeclaration/classExpression and interface are in member property of the symbol. + // Note: that the memberFlags come from previous iteration. + if (!isStaticSymbol) { + copySymbols(getMembersOfSymbol(getSymbolOfNode(location as ClassDeclaration | InterfaceDeclaration)), meaning & SymbolFlags.Type); + } + break; + case SyntaxKind.FunctionExpression: + const funcName = (location as FunctionExpression).name; + if (funcName) { + copySymbol(location.symbol, meaning); + } + break; + } - isStaticSymbol = isStatic(location); - location = location.parent; + if (introducesArgumentsExoticObject(location)) { + copySymbol(argumentsSymbol, meaning); } - copySymbols(globals, meaning); + isStaticSymbol = isStatic(location); + location = location.parent; } - /** - * Copy the given symbol into symbol tables if the symbol has the given meaning - * and it doesn't already existed in the symbol table - * @param key a key for storing in symbol table; if undefined, use symbol.name - * @param symbol the symbol to be added into symbol table - * @param meaning meaning of symbol to filter by before adding to symbol table - */ - function copySymbol(symbol: Symbol, meaning: SymbolFlags): void { - if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) { - const id = symbol.escapedName; - // We will copy all symbol regardless of its reserved name because - // symbolsToArray will check whether the key is a reserved name and - // it will not copy symbol with reserved name to the array - if (!symbols.has(id)) { - symbols.set(id, symbol); - } + copySymbols(globals, meaning); + } + + /** + * Copy the given symbol into symbol tables if the symbol has the given meaning + * and it doesn't already existed in the symbol table + * @param key a key for storing in symbol table; if undefined, use symbol.name + * @param symbol the symbol to be added into symbol table + * @param meaning meaning of symbol to filter by before adding to symbol table + */ + function copySymbol(symbol: Symbol, meaning: SymbolFlags): void { + if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) { + const id = symbol.escapedName; + // We will copy all symbol regardless of its reserved name because + // symbolsToArray will check whether the key is a reserved name and + // it will not copy symbol with reserved name to the array + if (!symbols.has(id)) { + symbols.set(id, symbol); } } + } - function copySymbols(source: SymbolTable, meaning: SymbolFlags): void { - if (meaning) { - source.forEach(symbol => { - copySymbol(symbol, meaning); - }); - } + function copySymbols(source: SymbolTable, meaning: SymbolFlags): void { + if (meaning) { + source.forEach(symbol => { + copySymbol(symbol, meaning); + }); } + } - function copyLocallyVisibleExportSymbols(source: SymbolTable, meaning: SymbolFlags): void { - if (meaning) { - source.forEach(symbol => { - // Similar condition as in `resolveNameHelper` - if (!getDeclarationOfKind(symbol, SyntaxKind.ExportSpecifier) && !getDeclarationOfKind(symbol, SyntaxKind.NamespaceExport)) { - copySymbol(symbol, meaning); - } - }); - } + function copyLocallyVisibleExportSymbols(source: SymbolTable, meaning: SymbolFlags): void { + if (meaning) { + source.forEach(symbol => { + // Similar condition as in `resolveNameHelper` + if (!getDeclarationOfKind(symbol, SyntaxKind.ExportSpecifier) && !getDeclarationOfKind(symbol, SyntaxKind.NamespaceExport)) { + copySymbol(symbol, meaning); + } + }); } } + } - function isTypeDeclarationName(name: Node): boolean { - return name.kind === SyntaxKind.Identifier && - isTypeDeclaration(name.parent) && - getNameOfDeclaration(name.parent) === name; + function isTypeDeclarationName(name: Node): boolean { + return name.kind === SyntaxKind.Identifier && + isTypeDeclaration(name.parent) && + getNameOfDeclaration(name.parent) === name; + } + + // True if the given identifier is part of a type reference + function isTypeReferenceIdentifier(node: EntityName): boolean { + while (node.parent.kind === SyntaxKind.QualifiedName) { + node = node.parent as QualifiedName; } - // True if the given identifier is part of a type reference - function isTypeReferenceIdentifier(node: EntityName): boolean { - while (node.parent.kind === SyntaxKind.QualifiedName) { - node = node.parent as QualifiedName; - } + return node.parent.kind === SyntaxKind.TypeReference; + } - return node.parent.kind === SyntaxKind.TypeReference; + function isHeritageClauseElementIdentifier(node: Node): boolean { + while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { + node = node.parent; } - function isHeritageClauseElementIdentifier(node: Node): boolean { - while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { - node = node.parent; - } + return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments; + } - return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments; + function forEachEnclosingClass(node: Node, callback: (node: Node) => T | undefined): T | undefined { + let result: T | undefined; + + while (true) { + node = getContainingClass(node)!; + if (!node) break; + if (result = callback(node)) break; } - function forEachEnclosingClass(node: Node, callback: (node: Node) => T | undefined): T | undefined { - let result: T | undefined; + return result; + } - while (true) { - node = getContainingClass(node)!; - if (!node) break; - if (result = callback(node)) break; + function isNodeUsedDuringClassInitialization(node: Node) { + return !!findAncestor(node, element => { + if (isConstructorDeclaration(element) && nodeIsPresent(element.body) || isPropertyDeclaration(element)) { + return true; + } + else if (isClassLike(element) || isFunctionLikeDeclaration(element)) { + return "quit"; } - return result; - } + return false; + }); + } - function isNodeUsedDuringClassInitialization(node: Node) { - return !!findAncestor(node, element => { - if (isConstructorDeclaration(element) && nodeIsPresent(element.body) || isPropertyDeclaration(element)) { - return true; - } - else if (isClassLike(element) || isFunctionLikeDeclaration(element)) { - return "quit"; - } + function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { + return !!forEachEnclosingClass(node, n => n === classDeclaration); + } - return false; - }); + function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined { + while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { + nodeOnRightSide = nodeOnRightSide.parent as QualifiedName; } - function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { - return !!forEachEnclosingClass(node, n => n === classDeclaration); + if (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) { + return (nodeOnRightSide.parent as ImportEqualsDeclaration).moduleReference === nodeOnRightSide ? nodeOnRightSide.parent as ImportEqualsDeclaration : undefined; } - function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined { - while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { - nodeOnRightSide = nodeOnRightSide.parent as QualifiedName; - } + if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { + return (nodeOnRightSide.parent as ExportAssignment).expression === nodeOnRightSide as Node ? nodeOnRightSide.parent as ExportAssignment : undefined; + } - if (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) { - return (nodeOnRightSide.parent as ImportEqualsDeclaration).moduleReference === nodeOnRightSide ? nodeOnRightSide.parent as ImportEqualsDeclaration : undefined; - } + return undefined; + } - if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { - return (nodeOnRightSide.parent as ExportAssignment).expression === nodeOnRightSide as Node ? nodeOnRightSide.parent as ExportAssignment : undefined; - } + function isInRightSideOfImportOrExportAssignment(node: EntityName) { + return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined; + } - return undefined; + function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) { + const specialPropertyAssignmentKind = getAssignmentDeclarationKind(entityName.parent.parent as BinaryExpression); + switch (specialPropertyAssignmentKind) { + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.PrototypeProperty: + return getSymbolOfNode(entityName.parent); + case AssignmentDeclarationKind.ThisProperty: + case AssignmentDeclarationKind.ModuleExports: + case AssignmentDeclarationKind.Property: + return getSymbolOfNode(entityName.parent.parent); } + } - function isInRightSideOfImportOrExportAssignment(node: EntityName) { - return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined; + function isImportTypeQualifierPart(node: EntityName): ImportTypeNode | undefined { + let parent = node.parent; + while (isQualifiedName(parent)) { + node = parent; + parent = parent.parent; } - - function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) { - const specialPropertyAssignmentKind = getAssignmentDeclarationKind(entityName.parent.parent as BinaryExpression); - switch (specialPropertyAssignmentKind) { - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.PrototypeProperty: - return getSymbolOfNode(entityName.parent); - case AssignmentDeclarationKind.ThisProperty: - case AssignmentDeclarationKind.ModuleExports: - case AssignmentDeclarationKind.Property: - return getSymbolOfNode(entityName.parent.parent); - } + if (parent && parent.kind === SyntaxKind.ImportType && (parent as ImportTypeNode).qualifier === node) { + return parent as ImportTypeNode; + } + return undefined; + } + + function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined { + if (isDeclarationName(name)) { + return getSymbolOfNode(name.parent); } - function isImportTypeQualifierPart(node: EntityName): ImportTypeNode | undefined { - let parent = node.parent; - while (isQualifiedName(parent)) { - node = parent; - parent = parent.parent; - } - if (parent && parent.kind === SyntaxKind.ImportType && (parent as ImportTypeNode).qualifier === node) { - return parent as ImportTypeNode; + if (isInJSFile(name) && + name.parent.kind === SyntaxKind.PropertyAccessExpression && + name.parent === (name.parent.parent as BinaryExpression).left) { + // Check if this is a special property assignment + if (!isPrivateIdentifier(name) && !isJSDocMemberName(name)) { + const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name); + if (specialPropertyAssignmentSymbol) { + return specialPropertyAssignmentSymbol; + } } - return undefined; } - function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined { - if (isDeclarationName(name)) { - return getSymbolOfNode(name.parent); + if (name.parent.kind === SyntaxKind.ExportAssignment && isEntityNameExpression(name)) { + // Even an entity name expression that doesn't resolve as an entityname may still typecheck as a property access expression + const success = resolveEntityName(name, + /*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*ignoreErrors*/ true); + if (success && success !== unknownSymbol) { + return success; } + } + else if (isEntityName(name) && isInRightSideOfImportOrExportAssignment(name)) { + // Since we already checked for ExportAssignment, this really could only be an Import + const importEqualsDeclaration = getAncestor(name, SyntaxKind.ImportEqualsDeclaration); + Debug.assert(importEqualsDeclaration !== undefined); + return getSymbolOfPartOfRightHandSideOfImportEquals(name, /*dontResolveAlias*/ true); + } - if (isInJSFile(name) && - name.parent.kind === SyntaxKind.PropertyAccessExpression && - name.parent === (name.parent.parent as BinaryExpression).left) { - // Check if this is a special property assignment - if (!isPrivateIdentifier(name) && !isJSDocMemberName(name)) { - const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name); - if (specialPropertyAssignmentSymbol) { - return specialPropertyAssignmentSymbol; - } - } + if (isEntityName(name)) { + const possibleImportNode = isImportTypeQualifierPart(name); + if (possibleImportNode) { + getTypeFromTypeNode(possibleImportNode); + const sym = getNodeLinks(name).resolvedSymbol; + return sym === unknownSymbol ? undefined : sym; } + } + + while (isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName(name)) { + name = name.parent as QualifiedName | PropertyAccessEntityNameExpression | JSDocMemberName; + } + + if (isHeritageClauseElementIdentifier(name)) { + let meaning = SymbolFlags.None; + // In an interface or class, we're definitely interested in a type. + if (name.parent.kind === SyntaxKind.ExpressionWithTypeArguments) { + meaning = SymbolFlags.Type; - if (name.parent.kind === SyntaxKind.ExportAssignment && isEntityNameExpression(name)) { - // Even an entity name expression that doesn't resolve as an entityname may still typecheck as a property access expression - const success = resolveEntityName(name, - /*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*ignoreErrors*/ true); - if (success && success !== unknownSymbol) { - return success; + // In a class 'extends' clause we are also looking for a value. + if (isExpressionWithTypeArgumentsInClassExtendsClause(name.parent)) { + meaning |= SymbolFlags.Value; } } - else if (isEntityName(name) && isInRightSideOfImportOrExportAssignment(name)) { - // Since we already checked for ExportAssignment, this really could only be an Import - const importEqualsDeclaration = getAncestor(name, SyntaxKind.ImportEqualsDeclaration); - Debug.assert(importEqualsDeclaration !== undefined); - return getSymbolOfPartOfRightHandSideOfImportEquals(name, /*dontResolveAlias*/ true); + else { + meaning = SymbolFlags.Namespace; } - if (isEntityName(name)) { - const possibleImportNode = isImportTypeQualifierPart(name); - if (possibleImportNode) { - getTypeFromTypeNode(possibleImportNode); - const sym = getNodeLinks(name).resolvedSymbol; - return sym === unknownSymbol ? undefined : sym; - } + meaning |= SymbolFlags.Alias; + const entityNameSymbol = isEntityNameExpression(name) ? resolveEntityName(name, meaning) : undefined; + if (entityNameSymbol) { + return entityNameSymbol; } + } - while (isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName(name)) { - name = name.parent as QualifiedName | PropertyAccessEntityNameExpression | JSDocMemberName; - } + if (name.parent.kind === SyntaxKind.JSDocParameterTag) { + return getParameterSymbolFromJSDoc(name.parent as JSDocParameterTag); + } - if (isHeritageClauseElementIdentifier(name)) { - let meaning = SymbolFlags.None; - // In an interface or class, we're definitely interested in a type. - if (name.parent.kind === SyntaxKind.ExpressionWithTypeArguments) { - meaning = SymbolFlags.Type; + if (name.parent.kind === SyntaxKind.TypeParameter && name.parent.parent.kind === SyntaxKind.JSDocTemplateTag) { + Debug.assert(!isInJSFile(name)); // Otherwise `isDeclarationName` would have been true. + const typeParameter = getTypeParameterFromJsDoc(name.parent as TypeParameterDeclaration & { parent: JSDocTemplateTag }); + return typeParameter && typeParameter.symbol; + } - // In a class 'extends' clause we are also looking for a value. - if (isExpressionWithTypeArgumentsInClassExtendsClause(name.parent)) { - meaning |= SymbolFlags.Value; - } + if (isExpressionNode(name)) { + if (nodeIsMissing(name)) { + // Missing entity name. + return undefined; + } + + const isJSDoc = findAncestor(name, or(isJSDocLinkLike, isJSDocNameReference, isJSDocMemberName)); + const meaning = isJSDoc ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value; + if (name.kind === SyntaxKind.Identifier) { + if (isJSXTagName(name) && isJsxIntrinsicIdentifier(name)) { + const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement); + return symbol === unknownSymbol ? undefined : symbol; } - else { - meaning = SymbolFlags.Namespace; + const result = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /* dontResolveAlias */ true, getHostSignatureFromJSDoc(name)); + if (!result && isJSDoc) { + const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration)); + if (container) { + return resolveJSDocMemberName(name, /*ignoreErrors*/ false, getSymbolOfNode(container)); + } } - - meaning |= SymbolFlags.Alias; - const entityNameSymbol = isEntityNameExpression(name) ? resolveEntityName(name, meaning) : undefined; - if (entityNameSymbol) { - return entityNameSymbol; + if (result && isJSDoc) { + const container = getJSDocHost(name); + if (container && isEnumMember(container) && container === result.valueDeclaration) { + return resolveEntityName(name, meaning, /*ignoreErrors*/ true, /* dontResolveAlias */ true, getSourceFileOfNode(container)) || result; + } } + return result; } - - if (name.parent.kind === SyntaxKind.JSDocParameterTag) { - return getParameterSymbolFromJSDoc(name.parent as JSDocParameterTag); - } - - if (name.parent.kind === SyntaxKind.TypeParameter && name.parent.parent.kind === SyntaxKind.JSDocTemplateTag) { - Debug.assert(!isInJSFile(name)); // Otherwise `isDeclarationName` would have been true. - const typeParameter = getTypeParameterFromJsDoc(name.parent as TypeParameterDeclaration & { parent: JSDocTemplateTag }); - return typeParameter && typeParameter.symbol; + else if (isPrivateIdentifier(name)) { + return getSymbolForPrivateIdentifierExpression(name); } - - if (isExpressionNode(name)) { - if (nodeIsMissing(name)) { - // Missing entity name. - return undefined; + else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) { + const links = getNodeLinks(name); + if (links.resolvedSymbol) { + return links.resolvedSymbol; } - const isJSDoc = findAncestor(name, or(isJSDocLinkLike, isJSDocNameReference, isJSDocMemberName)); - const meaning = isJSDoc ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value; - if (name.kind === SyntaxKind.Identifier) { - if (isJSXTagName(name) && isJsxIntrinsicIdentifier(name)) { - const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement); - return symbol === unknownSymbol ? undefined : symbol; - } - const result = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /* dontResolveAlias */ true, getHostSignatureFromJSDoc(name)); - if (!result && isJSDoc) { - const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration)); - if (container) { - return resolveJSDocMemberName(name, /*ignoreErrors*/ false, getSymbolOfNode(container)); - } - } - if (result && isJSDoc) { - const container = getJSDocHost(name); - if (container && isEnumMember(container) && container === result.valueDeclaration) { - return resolveEntityName(name, meaning, /*ignoreErrors*/ true, /* dontResolveAlias */ true, getSourceFileOfNode(container)) || result; - } - } - return result; - } - else if (isPrivateIdentifier(name)) { - return getSymbolForPrivateIdentifierExpression(name); - } - else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) { - const links = getNodeLinks(name); - if (links.resolvedSymbol) { - return links.resolvedSymbol; - } - - if (name.kind === SyntaxKind.PropertyAccessExpression) { - checkPropertyAccessExpression(name, CheckMode.Normal); - if (!links.resolvedSymbol) { - const expressionType = checkExpressionCached(name.expression); - const infos = getApplicableIndexInfos(expressionType, getLiteralTypeFromPropertyName(name.name)); - if (infos.length && (expressionType as ObjectType).members) { - const resolved = resolveStructuredTypeMembers(expressionType as ObjectType); - const symbol = resolved.members.get(InternalSymbolName.Index); - if (infos === getIndexInfosOfType(expressionType)) { - links.resolvedSymbol = symbol; + if (name.kind === SyntaxKind.PropertyAccessExpression) { + checkPropertyAccessExpression(name, CheckMode.Normal); + if (!links.resolvedSymbol) { + const expressionType = checkExpressionCached(name.expression); + const infos = getApplicableIndexInfos(expressionType, getLiteralTypeFromPropertyName(name.name)); + if (infos.length && (expressionType as ObjectType).members) { + const resolved = resolveStructuredTypeMembers(expressionType as ObjectType); + const symbol = resolved.members.get(InternalSymbolName.Index); + if (infos === getIndexInfosOfType(expressionType)) { + links.resolvedSymbol = symbol; + } + else if (symbol) { + const symbolLinks = getSymbolLinks(symbol); + const declarationList = mapDefined(infos, i => i.declaration); + const nodeListId = map(declarationList, getNodeId).join(","); + if (!symbolLinks.filteredIndexSymbolCache) { + symbolLinks.filteredIndexSymbolCache = new Map(); } - else if (symbol) { - const symbolLinks = getSymbolLinks(symbol); - const declarationList = mapDefined(infos, i => i.declaration); - const nodeListId = map(declarationList, getNodeId).join(","); - if (!symbolLinks.filteredIndexSymbolCache) { - symbolLinks.filteredIndexSymbolCache = new Map(); - } - if (symbolLinks.filteredIndexSymbolCache.has(nodeListId)) { - links.resolvedSymbol = symbolLinks.filteredIndexSymbolCache.get(nodeListId)!; - } - else { - const copy = createSymbol(SymbolFlags.Signature, InternalSymbolName.Index); - copy.declarations = mapDefined(infos, i => i.declaration); - copy.parent = expressionType.aliasSymbol ? expressionType.aliasSymbol : expressionType.symbol ? expressionType.symbol : getSymbolAtLocation(copy.declarations[0].parent); - symbolLinks.filteredIndexSymbolCache.set(nodeListId, copy); - links.resolvedSymbol = symbolLinks.filteredIndexSymbolCache.get(nodeListId)!; - } + if (symbolLinks.filteredIndexSymbolCache.has(nodeListId)) { + links.resolvedSymbol = symbolLinks.filteredIndexSymbolCache.get(nodeListId)!; + } + else { + const copy = createSymbol(SymbolFlags.Signature, InternalSymbolName.Index); + copy.declarations = mapDefined(infos, i => i.declaration); + copy.parent = expressionType.aliasSymbol ? expressionType.aliasSymbol : expressionType.symbol ? expressionType.symbol : getSymbolAtLocation(copy.declarations[0].parent); + symbolLinks.filteredIndexSymbolCache.set(nodeListId, copy); + links.resolvedSymbol = symbolLinks.filteredIndexSymbolCache.get(nodeListId)!; } } } } - else { - checkQualifiedName(name, CheckMode.Normal); - } - if (!links.resolvedSymbol && isJSDoc && isQualifiedName(name)) { - return resolveJSDocMemberName(name); - } - return links.resolvedSymbol; } - else if (isJSDocMemberName(name)) { + else { + checkQualifiedName(name, CheckMode.Normal); + } + if (!links.resolvedSymbol && isJSDoc && isQualifiedName(name)) { return resolveJSDocMemberName(name); } + return links.resolvedSymbol; } - else if (isTypeReferenceIdentifier(name as EntityName)) { - const meaning = name.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; - const symbol = resolveEntityName(name as EntityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); - return symbol && symbol !== unknownSymbol ? symbol : getUnresolvedSymbolForEntityName(name as EntityName); + else if (isJSDocMemberName(name)) { + return resolveJSDocMemberName(name); } - if (name.parent.kind === SyntaxKind.TypePredicate) { - return resolveEntityName(name as Identifier, /*meaning*/ SymbolFlags.FunctionScopedVariable); - } - - return undefined; + } + else if (isTypeReferenceIdentifier(name as EntityName)) { + const meaning = name.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; + const symbol = resolveEntityName(name as EntityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); + return symbol && symbol !== unknownSymbol ? symbol : getUnresolvedSymbolForEntityName(name as EntityName); + } + if (name.parent.kind === SyntaxKind.TypePredicate) { + return resolveEntityName(name as Identifier, /*meaning*/ SymbolFlags.FunctionScopedVariable); } - /** - * Recursively resolve entity names and jsdoc instance references: - * 1. K#m as K.prototype.m for a class (or other value) K - * 2. K.m as K.prototype.m - * 3. I.m as I.m for a type I, or any other I.m that fails to resolve in (1) or (2) - * - * For unqualified names, a container K may be provided as a second argument. - */ - function resolveJSDocMemberName(name: EntityName | JSDocMemberName, ignoreErrors?: boolean, container?: Symbol): Symbol | undefined { - if (isEntityName(name)) { - // resolve static values first - const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value; - let symbol = resolveEntityName(name, meaning, ignoreErrors, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); - if (!symbol && isIdentifier(name) && container) { - symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(container), name.escapedText, meaning)); - } - if (symbol) { - return symbol; - } + return undefined; + } + + /** + * Recursively resolve entity names and jsdoc instance references: + * 1. K#m as K.prototype.m for a class (or other value) K + * 2. K.m as K.prototype.m + * 3. I.m as I.m for a type I, or any other I.m that fails to resolve in (1) or (2) + * + * For unqualified names, a container K may be provided as a second argument. + */ + function resolveJSDocMemberName(name: EntityName | JSDocMemberName, ignoreErrors?: boolean, container?: Symbol): Symbol | undefined { + if (isEntityName(name)) { + // resolve static values first + const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value; + let symbol = resolveEntityName(name, meaning, ignoreErrors, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); + if (!symbol && isIdentifier(name) && container) { + symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(container), name.escapedText, meaning)); } - const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left, ignoreErrors, container); - const right = isIdentifier(name) ? name.escapedText : name.right.escapedText; - if (left) { - const proto = left.flags & SymbolFlags.Value && getPropertyOfType(getTypeOfSymbol(left), "prototype" as __String); - const t = proto ? getTypeOfSymbol(proto) : getDeclaredTypeOfSymbol(left); - return getPropertyOfType(t, right); + if (symbol) { + return symbol; } } + const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left, ignoreErrors, container); + const right = isIdentifier(name) ? name.escapedText : name.right.escapedText; + if (left) { + const proto = left.flags & SymbolFlags.Value && getPropertyOfType(getTypeOfSymbol(left), "prototype" as __String); + const t = proto ? getTypeOfSymbol(proto) : getDeclaredTypeOfSymbol(left); + return getPropertyOfType(t, right); + } + } - function getSymbolAtLocation(node: Node, ignoreErrors?: boolean): Symbol | undefined { - if (node.kind === SyntaxKind.SourceFile) { - return isExternalModule(node as SourceFile) ? getMergedSymbol(node.symbol) : undefined; - } - const { parent } = node; - const grandParent = parent.parent; + function getSymbolAtLocation(node: Node, ignoreErrors?: boolean): Symbol | undefined { + if (node.kind === SyntaxKind.SourceFile) { + return isExternalModule(node as SourceFile) ? getMergedSymbol(node.symbol) : undefined; + } + const { parent } = node; + const grandParent = parent.parent; - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; - } + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; + } - if (isDeclarationNameOrImportPropertyName(node)) { - // This is a declaration, call getSymbolOfNode - const parentSymbol = getSymbolOfNode(parent)!; - return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node - ? getImmediateAliasedSymbol(parentSymbol) - : parentSymbol; - } - else if (isLiteralComputedPropertyDeclarationName(node)) { - return getSymbolOfNode(parent.parent); + if (isDeclarationNameOrImportPropertyName(node)) { + // This is a declaration, call getSymbolOfNode + const parentSymbol = getSymbolOfNode(parent)!; + return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node + ? getImmediateAliasedSymbol(parentSymbol) + : parentSymbol; + } + else if (isLiteralComputedPropertyDeclarationName(node)) { + return getSymbolOfNode(parent.parent); + } + + if (node.kind === SyntaxKind.Identifier) { + if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { + return getSymbolOfNameOrPropertyAccessExpression(node as Identifier); } + else if (parent.kind === SyntaxKind.BindingElement && + grandParent.kind === SyntaxKind.ObjectBindingPattern && + node === (parent as BindingElement).propertyName) { + const typeOfPattern = getTypeOfNode(grandParent); + const propertyDeclaration = getPropertyOfType(typeOfPattern, (node as Identifier).escapedText); - if (node.kind === SyntaxKind.Identifier) { - if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { - return getSymbolOfNameOrPropertyAccessExpression(node as Identifier); + if (propertyDeclaration) { + return propertyDeclaration; } - else if (parent.kind === SyntaxKind.BindingElement && - grandParent.kind === SyntaxKind.ObjectBindingPattern && - node === (parent as BindingElement).propertyName) { - const typeOfPattern = getTypeOfNode(grandParent); - const propertyDeclaration = getPropertyOfType(typeOfPattern, (node as Identifier).escapedText); - - if (propertyDeclaration) { - return propertyDeclaration; - } + } + else if (isMetaProperty(parent) && parent.name === node) { + if (parent.keywordToken === SyntaxKind.NewKeyword && idText(node as Identifier) === "target") { + // `target` in `new.target` + return checkNewTargetMetaProperty(parent).symbol; } - else if (isMetaProperty(parent) && parent.name === node) { - if (parent.keywordToken === SyntaxKind.NewKeyword && idText(node as Identifier) === "target") { - // `target` in `new.target` - return checkNewTargetMetaProperty(parent).symbol; - } - // The `meta` in `import.meta` could be given `getTypeOfNode(parent).symbol` (the `ImportMeta` interface symbol), but - // we have a fake expression type made for other reasons already, whose transient `meta` - // member should more exactly be the kind of (declarationless) symbol we want. - // (See #44364 and #45031 for relevant implementation PRs) - if (parent.keywordToken === SyntaxKind.ImportKeyword && idText(node as Identifier) === "meta") { - return getGlobalImportMetaExpressionType().members!.get("meta" as __String); - } - // no other meta properties are valid syntax, thus no others should have symbols - return undefined; + // The `meta` in `import.meta` could be given `getTypeOfNode(parent).symbol` (the `ImportMeta` interface symbol), but + // we have a fake expression type made for other reasons already, whose transient `meta` + // member should more exactly be the kind of (declarationless) symbol we want. + // (See #44364 and #45031 for relevant implementation PRs) + if (parent.keywordToken === SyntaxKind.ImportKeyword && idText(node as Identifier) === "meta") { + return getGlobalImportMetaExpressionType().members!.get("meta" as __String); } + // no other meta properties are valid syntax, thus no others should have symbols + return undefined; } + } - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PrivateIdentifier: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.QualifiedName: - if (!isThisInTypeQuery(node)) { - return getSymbolOfNameOrPropertyAccessExpression(node as EntityName | PrivateIdentifier | PropertyAccessExpression); - } - // falls through + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.QualifiedName: + if (!isThisInTypeQuery(node)) { + return getSymbolOfNameOrPropertyAccessExpression(node as EntityName | PrivateIdentifier | PropertyAccessExpression); + } + // falls through - case SyntaxKind.ThisKeyword: - const container = getThisContainer(node, /*includeArrowFunctions*/ false); - if (isFunctionLike(container)) { - const sig = getSignatureFromDeclaration(container); - if (sig.thisParameter) { - return sig.thisParameter; - } - } - if (isInExpressionContext(node)) { - return checkExpression(node as Expression).symbol; + case SyntaxKind.ThisKeyword: + const container = getThisContainer(node, /*includeArrowFunctions*/ false); + if (isFunctionLike(container)) { + const sig = getSignatureFromDeclaration(container); + if (sig.thisParameter) { + return sig.thisParameter; } - // falls through - - case SyntaxKind.ThisType: - return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode).symbol; - - case SyntaxKind.SuperKeyword: + } + if (isInExpressionContext(node)) { return checkExpression(node as Expression).symbol; + } + // falls through - case SyntaxKind.ConstructorKeyword: - // constructor keyword for an overload, should take us to the definition if it exist - const constructorDeclaration = node.parent; - if (constructorDeclaration && constructorDeclaration.kind === SyntaxKind.Constructor) { - return (constructorDeclaration.parent as ClassDeclaration).symbol; - } - return undefined; - - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - // 1). import x = require("./mo/*gotToDefinitionHere*/d") - // 2). External module name in an import declaration - // 3). Dynamic import call or require in javascript - // 4). type A = import("./f/*gotToDefinitionHere*/oo") - if ((isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) || - ((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (node.parent as ImportDeclaration).moduleSpecifier === node) || - ((isInJSFile(node) && isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false)) || isImportCall(node.parent)) || - (isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent) - ) { - return resolveExternalModuleName(node, node as LiteralExpression, ignoreErrors); - } - if (isCallExpression(parent) && isBindableObjectDefinePropertyCall(parent) && parent.arguments[1] === node) { - return getSymbolOfNode(parent); - } - // falls through - - case SyntaxKind.NumericLiteral: - // index access - const objectType = isElementAccessExpression(parent) - ? parent.argumentExpression === node ? getTypeOfExpression(parent.expression) : undefined - : isLiteralTypeNode(parent) && isIndexedAccessTypeNode(grandParent) - ? getTypeFromTypeNode(grandParent.objectType) - : undefined; - return objectType && getPropertyOfType(objectType, escapeLeadingUnderscores((node as StringLiteral | NumericLiteral).text)); + case SyntaxKind.ThisType: + return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode).symbol; - case SyntaxKind.DefaultKeyword: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.EqualsGreaterThanToken: - case SyntaxKind.ClassKeyword: - return getSymbolOfNode(node.parent); - case SyntaxKind.ImportType: - return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal, ignoreErrors) : undefined; + case SyntaxKind.SuperKeyword: + return checkExpression(node as Expression).symbol; - case SyntaxKind.ExportKeyword: - return isExportAssignment(node.parent) ? Debug.checkDefined(node.parent.symbol) : undefined; + case SyntaxKind.ConstructorKeyword: + // constructor keyword for an overload, should take us to the definition if it exist + const constructorDeclaration = node.parent; + if (constructorDeclaration && constructorDeclaration.kind === SyntaxKind.Constructor) { + return (constructorDeclaration.parent as ClassDeclaration).symbol; + } + return undefined; - case SyntaxKind.ImportKeyword: - case SyntaxKind.NewKeyword: - return isMetaProperty(node.parent) ? checkMetaPropertyKeyword(node.parent).symbol : undefined; - case SyntaxKind.MetaProperty: - return checkExpression(node as Expression).symbol; + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + // 1). import x = require("./mo/*gotToDefinitionHere*/d") + // 2). External module name in an import declaration + // 3). Dynamic import call or require in javascript + // 4). type A = import("./f/*gotToDefinitionHere*/oo") + if ((isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) || + ((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (node.parent as ImportDeclaration).moduleSpecifier === node) || + ((isInJSFile(node) && isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false)) || isImportCall(node.parent)) || + (isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent) + ) { + return resolveExternalModuleName(node, node as LiteralExpression, ignoreErrors); + } + if (isCallExpression(parent) && isBindableObjectDefinePropertyCall(parent) && parent.arguments[1] === node) { + return getSymbolOfNode(parent); + } + // falls through + + case SyntaxKind.NumericLiteral: + // index access + const objectType = isElementAccessExpression(parent) + ? parent.argumentExpression === node ? getTypeOfExpression(parent.expression) : undefined + : isLiteralTypeNode(parent) && isIndexedAccessTypeNode(grandParent) + ? getTypeFromTypeNode(grandParent.objectType) + : undefined; + return objectType && getPropertyOfType(objectType, escapeLeadingUnderscores((node as StringLiteral | NumericLiteral).text)); + + case SyntaxKind.DefaultKeyword: + case SyntaxKind.FunctionKeyword: + case SyntaxKind.EqualsGreaterThanToken: + case SyntaxKind.ClassKeyword: + return getSymbolOfNode(node.parent); + case SyntaxKind.ImportType: + return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal, ignoreErrors) : undefined; + + case SyntaxKind.ExportKeyword: + return isExportAssignment(node.parent) ? Debug.checkDefined(node.parent.symbol) : undefined; + + case SyntaxKind.ImportKeyword: + case SyntaxKind.NewKeyword: + return isMetaProperty(node.parent) ? checkMetaPropertyKeyword(node.parent).symbol : undefined; + case SyntaxKind.MetaProperty: + return checkExpression(node as Expression).symbol; - default: - return undefined; - } + default: + return undefined; } + } - function getIndexInfosAtLocation(node: Node): readonly IndexInfo[] | undefined { - if (isIdentifier(node) && isPropertyAccessExpression(node.parent) && node.parent.name === node) { - const keyType = getLiteralTypeFromPropertyName(node); - const objectType = getTypeOfExpression(node.parent.expression); - const objectTypes = objectType.flags & TypeFlags.Union ? (objectType as UnionType).types : [objectType]; - return flatMap(objectTypes, t => filter(getIndexInfosOfType(t), info => isApplicableIndexType(keyType, info.keyType))); - } - return undefined; + function getIndexInfosAtLocation(node: Node): readonly IndexInfo[] | undefined { + if (isIdentifier(node) && isPropertyAccessExpression(node.parent) && node.parent.name === node) { + const keyType = getLiteralTypeFromPropertyName(node); + const objectType = getTypeOfExpression(node.parent.expression); + const objectTypes = objectType.flags & TypeFlags.Union ? (objectType as UnionType).types : [objectType]; + return flatMap(objectTypes, t => filter(getIndexInfosOfType(t), info => isApplicableIndexType(keyType, info.keyType))); } + return undefined; + } - function getShorthandAssignmentValueSymbol(location: Node | undefined): Symbol | undefined { - if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) { - return resolveEntityName((location as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Alias); - } - return undefined; + function getShorthandAssignmentValueSymbol(location: Node | undefined): Symbol | undefined { + if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) { + return resolveEntityName((location as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Alias); } + return undefined; + } - /** Returns the target of an export specifier without following aliases */ - function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier | Identifier): Symbol | undefined { - if (isExportSpecifier(node)) { - return node.parent.parent.moduleSpecifier ? - getExternalModuleMember(node.parent.parent, node) : - resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); - } - else { - return resolveEntityName(node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); - } + /** Returns the target of an export specifier without following aliases */ + function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier | Identifier): Symbol | undefined { + if (isExportSpecifier(node)) { + return node.parent.parent.moduleSpecifier ? + getExternalModuleMember(node.parent.parent, node) : + resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); } + else { + return resolveEntityName(node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); + } + } - function getTypeOfNode(node: Node): Type { - if (isSourceFile(node) && !isExternalModule(node)) { - return errorType; - } - - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return errorType; - } + function getTypeOfNode(node: Node): Type { + if (isSourceFile(node) && !isExternalModule(node)) { + return errorType; + } - const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); - const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(classDecl.class)); - if (isPartOfTypeNode(node)) { - const typeFromTypeNode = getTypeFromTypeNode(node as TypeNode); - return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode; - } + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return errorType; + } - if (isExpressionNode(node)) { - return getRegularTypeOfExpression(node as Expression); - } + const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); + const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(classDecl.class)); + if (isPartOfTypeNode(node)) { + const typeFromTypeNode = getTypeFromTypeNode(node as TypeNode); + return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode; + } - if (classType && !classDecl.isImplements) { - // A SyntaxKind.ExpressionWithTypeArguments is considered a type node, except when it occurs in the - // extends clause of a class. We handle that case here. - const baseType = firstOrUndefined(getBaseTypes(classType)); - return baseType ? getTypeWithThisArgument(baseType, classType.thisType) : errorType; - } + if (isExpressionNode(node)) { + return getRegularTypeOfExpression(node as Expression); + } - if (isTypeDeclaration(node)) { - // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration - const symbol = getSymbolOfNode(node); - return getDeclaredTypeOfSymbol(symbol); - } + if (classType && !classDecl.isImplements) { + // A SyntaxKind.ExpressionWithTypeArguments is considered a type node, except when it occurs in the + // extends clause of a class. We handle that case here. + const baseType = firstOrUndefined(getBaseTypes(classType)); + return baseType ? getTypeWithThisArgument(baseType, classType.thisType) : errorType; + } - if (isTypeDeclarationName(node)) { - const symbol = getSymbolAtLocation(node); - return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; - } + if (isTypeDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfNode(node); + return getDeclaredTypeOfSymbol(symbol); + } - if (isDeclaration(node)) { - // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration - const symbol = getSymbolOfNode(node); - return symbol ? getTypeOfSymbol(symbol) : errorType; - } + if (isTypeDeclarationName(node)) { + const symbol = getSymbolAtLocation(node); + return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; + } - if (isDeclarationNameOrImportPropertyName(node)) { - const symbol = getSymbolAtLocation(node); - if (symbol) { - return getTypeOfSymbol(symbol); - } - return errorType; - } + if (isDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfNode(node); + return symbol ? getTypeOfSymbol(symbol) : errorType; + } - if (isBindingPattern(node)) { - return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true, CheckMode.Normal) || errorType; + if (isDeclarationNameOrImportPropertyName(node)) { + const symbol = getSymbolAtLocation(node); + if (symbol) { + return getTypeOfSymbol(symbol); } + return errorType; + } - if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { - const symbol = getSymbolAtLocation(node); - if (symbol) { - const declaredType = getDeclaredTypeOfSymbol(symbol); - return !isErrorType(declaredType) ? declaredType : getTypeOfSymbol(symbol); - } - } + if (isBindingPattern(node)) { + return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true, CheckMode.Normal) || errorType; + } - if (isMetaProperty(node.parent) && node.parent.keywordToken === node.kind) { - return checkMetaPropertyKeyword(node.parent); + if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { + const symbol = getSymbolAtLocation(node); + if (symbol) { + const declaredType = getDeclaredTypeOfSymbol(symbol); + return !isErrorType(declaredType) ? declaredType : getTypeOfSymbol(symbol); } + } - return errorType; + if (isMetaProperty(node.parent) && node.parent.keywordToken === node.kind) { + return checkMetaPropertyKeyword(node.parent); } - // Gets the type of object literal or array literal of destructuring assignment. - // { a } from + return errorType; + } + + // Gets the type of object literal or array literal of destructuring assignment. + // { a } from + // for ( { a } of elems) { + // } + // [ a ] from + // [a] = [ some array ...] + 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) { // } - // [ a ] from - // [a] = [ some array ...] - 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) { - // } - if (expr.parent.kind === SyntaxKind.ForOfStatement) { - const iteratedType = checkRightHandSideOfForOf(expr.parent as ForOfStatement); - return checkDestructuringAssignment(expr, iteratedType || errorType); - } - // If this is from "for" initializer - // for ({a } = elems[0];.....) { } - if (expr.parent.kind === SyntaxKind.BinaryExpression) { - const iteratedType = getTypeOfExpression((expr.parent as BinaryExpression).right); - return checkDestructuringAssignment(expr, iteratedType || errorType); - } - // 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 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 - const node = cast(expr.parent, isArrayLiteralExpression); - // [{ property1: p1, property2 }] = elems; - const typeOfArrayLiteral = getTypeOfAssignmentPattern(node) || errorType; - const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, typeOfArrayLiteral, undefinedType, expr.parent) || errorType; - return checkArrayLiteralDestructuringElementAssignment(node, typeOfArrayLiteral, node.elements.indexOf(expr), elementType); - } - - // Gets the property symbol corresponding to the property in destructuring assignment - // 'property1' from - // for ( { property1: a } of elems) { - // } - // 'property1' at location 'a' from: - // [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 = getTypeOfAssignmentPattern(cast(location.parent.parent, isAssignmentPattern)); - return typeOfObjectLiteral && getPropertyOfType(typeOfObjectLiteral, location.escapedText); - } + if (expr.parent.kind === SyntaxKind.ForOfStatement) { + const iteratedType = checkRightHandSideOfForOf(expr.parent as ForOfStatement); + return checkDestructuringAssignment(expr, iteratedType || errorType); + } + // If this is from "for" initializer + // for ({a } = elems[0];.....) { } + if (expr.parent.kind === SyntaxKind.BinaryExpression) { + const iteratedType = getTypeOfExpression((expr.parent as BinaryExpression).right); + return checkDestructuringAssignment(expr, iteratedType || errorType); + } + // 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 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 + const node = cast(expr.parent, isArrayLiteralExpression); + // [{ property1: p1, property2 }] = elems; + const typeOfArrayLiteral = getTypeOfAssignmentPattern(node) || errorType; + const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, typeOfArrayLiteral, undefinedType, expr.parent) || errorType; + return checkArrayLiteralDestructuringElementAssignment(node, typeOfArrayLiteral, node.elements.indexOf(expr), elementType); + } - function getRegularTypeOfExpression(expr: Expression): Type { - if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) { - expr = expr.parent as Expression; - } - return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); - } + // Gets the property symbol corresponding to the property in destructuring assignment + // 'property1' from + // for ( { property1: a } of elems) { + // } + // 'property1' at location 'a' from: + // [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 = getTypeOfAssignmentPattern(cast(location.parent.parent, isAssignmentPattern)); + return typeOfObjectLiteral && getPropertyOfType(typeOfObjectLiteral, location.escapedText); + } - /** - * Gets either the static or instance type of a class element, based on - * whether the element is declared as "static". - */ - function getParentTypeOfClassElement(node: ClassElement) { - const classSymbol = getSymbolOfNode(node.parent)!; - return isStatic(node) - ? getTypeOfSymbol(classSymbol) - : getDeclaredTypeOfSymbol(classSymbol); + function getRegularTypeOfExpression(expr: Expression): Type { + if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) { + expr = expr.parent as Expression; } + return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); + } - function getClassElementPropertyKeyType(element: ClassElement) { - const name = element.name!; - switch (name.kind) { - case SyntaxKind.Identifier: - return getStringLiteralType(idText(name)); - case SyntaxKind.NumericLiteral: - case SyntaxKind.StringLiteral: - return getStringLiteralType(name.text); - case SyntaxKind.ComputedPropertyName: - const nameType = checkComputedPropertyName(name); - return isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike) ? nameType : stringType; - default: - return Debug.fail("Unsupported property name."); - } - } + /** + * Gets either the static or instance type of a class element, based on + * whether the element is declared as "static". + */ + function getParentTypeOfClassElement(node: ClassElement) { + const classSymbol = getSymbolOfNode(node.parent)!; + return isStatic(node) + ? getTypeOfSymbol(classSymbol) + : getDeclaredTypeOfSymbol(classSymbol); + } - // Return the list of properties of the given type, augmented with properties from Function - // if the type has call or construct signatures - function getAugmentedPropertiesOfType(type: Type): Symbol[] { - type = getApparentType(type); - const propsByName = createSymbolTable(getPropertiesOfType(type)); - const functionType = getSignaturesOfType(type, SignatureKind.Call).length ? globalCallableFunctionType : - getSignaturesOfType(type, SignatureKind.Construct).length ? globalNewableFunctionType : - undefined; - if (functionType) { - forEach(getPropertiesOfType(functionType), p => { - if (!propsByName.has(p.escapedName)) { - propsByName.set(p.escapedName, p); - } - }); - } - return getNamedMembers(propsByName); + function getClassElementPropertyKeyType(element: ClassElement) { + const name = element.name!; + switch (name.kind) { + case SyntaxKind.Identifier: + return getStringLiteralType(idText(name)); + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + return getStringLiteralType(name.text); + case SyntaxKind.ComputedPropertyName: + const nameType = checkComputedPropertyName(name); + return isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike) ? nameType : stringType; + default: + return Debug.fail("Unsupported property name."); } + } - function typeHasCallOrConstructSignatures(type: Type): boolean { - return ts.typeHasCallOrConstructSignatures(type, checker); + // Return the list of properties of the given type, augmented with properties from Function + // if the type has call or construct signatures + function getAugmentedPropertiesOfType(type: Type): Symbol[] { + type = getApparentType(type); + const propsByName = createSymbolTable(getPropertiesOfType(type)); + const functionType = getSignaturesOfType(type, SignatureKind.Call).length ? globalCallableFunctionType : + getSignaturesOfType(type, SignatureKind.Construct).length ? globalNewableFunctionType : + undefined; + if (functionType) { + forEach(getPropertiesOfType(functionType), p => { + if (!propsByName.has(p.escapedName)) { + propsByName.set(p.escapedName, p); + } + }); } + return getNamedMembers(propsByName); + } - function getRootSymbols(symbol: Symbol): readonly Symbol[] { - const roots = getImmediateRootSymbols(symbol); - return roots ? flatMap(roots, getRootSymbols) : [symbol]; + function typeHasCallOrConstructSignatures(type: Type): boolean { + return ts.typeHasCallOrConstructSignatures(type, checker); + } + + function getRootSymbols(symbol: Symbol): readonly Symbol[] { + const roots = getImmediateRootSymbols(symbol); + return roots ? flatMap(roots, getRootSymbols) : [symbol]; + } + function getImmediateRootSymbols(symbol: Symbol): readonly Symbol[] | undefined { + if (getCheckFlags(symbol) & CheckFlags.Synthetic) { + return mapDefined(getSymbolLinks(symbol).containingType!.types, type => getPropertyOfType(type, symbol.escapedName)); } - function getImmediateRootSymbols(symbol: Symbol): readonly Symbol[] | undefined { - if (getCheckFlags(symbol) & CheckFlags.Synthetic) { - return mapDefined(getSymbolLinks(symbol).containingType!.types, type => getPropertyOfType(type, symbol.escapedName)); - } - else if (symbol.flags & SymbolFlags.Transient) { - const { leftSpread, rightSpread, syntheticOrigin } = symbol as TransientSymbol; - return leftSpread ? [leftSpread, rightSpread!] - : syntheticOrigin ? [syntheticOrigin] - : singleElementArray(tryGetTarget(symbol)); - } - return undefined; + else if (symbol.flags & SymbolFlags.Transient) { + const { leftSpread, rightSpread, syntheticOrigin } = symbol as TransientSymbol; + return leftSpread ? [leftSpread, rightSpread!] + : syntheticOrigin ? [syntheticOrigin] + : singleElementArray(tryGetTarget(symbol)); } - function tryGetTarget(symbol: Symbol): Symbol | undefined { - let target: Symbol | undefined; - let next: Symbol | undefined = symbol; - while (next = getSymbolLinks(next).target) { - target = next; - } - return target; + return undefined; + } + function tryGetTarget(symbol: Symbol): Symbol | undefined { + let target: Symbol | undefined; + let next: Symbol | undefined = symbol; + while (next = getSymbolLinks(next).target) { + target = next; } + return target; + } - // Emitter support + // Emitter support + + function isArgumentsLocalBinding(nodeIn: Identifier): boolean { + // Note: does not handle isShorthandPropertyAssignment (and probably a few more) + if (isGeneratedIdentifier(nodeIn)) return false; + const node = getParseTreeNode(nodeIn, isIdentifier); + if (!node) return false; + const parent = node.parent; + if (!parent) return false; + const isPropertyName = ((isPropertyAccessExpression(parent) + || isPropertyAssignment(parent)) + && parent.name === node); + return !isPropertyName && getReferencedValueSymbol(node) === argumentsSymbol; + } - function isArgumentsLocalBinding(nodeIn: Identifier): boolean { - // Note: does not handle isShorthandPropertyAssignment (and probably a few more) - if (isGeneratedIdentifier(nodeIn)) return false; - const node = getParseTreeNode(nodeIn, isIdentifier); - if (!node) return false; - const parent = node.parent; - if (!parent) return false; - const isPropertyName = ((isPropertyAccessExpression(parent) - || isPropertyAssignment(parent)) - && parent.name === node); - return !isPropertyName && getReferencedValueSymbol(node) === argumentsSymbol; + function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean { + let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression); + if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) { + // If the module is not found or is shorthand, assume that it may export a value. + return true; } - function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean { - let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression); - if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) { - // If the module is not found or is shorthand, assume that it may export a value. - return true; - } - - const hasExportAssignment = hasExportAssignmentSymbol(moduleSymbol); - // if module has export assignment then 'resolveExternalModuleSymbol' will return resolved symbol for export assignment - // otherwise it will return moduleSymbol itself - moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); + const hasExportAssignment = hasExportAssignmentSymbol(moduleSymbol); + // if module has export assignment then 'resolveExternalModuleSymbol' will return resolved symbol for export assignment + // otherwise it will return moduleSymbol itself + moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); - const symbolLinks = getSymbolLinks(moduleSymbol); - if (symbolLinks.exportsSomeValue === undefined) { - // for export assignments - check if resolved symbol for RHS is itself a value - // otherwise - check if at least one export is value - symbolLinks.exportsSomeValue = hasExportAssignment - ? !!(moduleSymbol.flags & SymbolFlags.Value) - : forEachEntry(getExportsOfModule(moduleSymbol), isValue); - } + const symbolLinks = getSymbolLinks(moduleSymbol); + if (symbolLinks.exportsSomeValue === undefined) { + // for export assignments - check if resolved symbol for RHS is itself a value + // otherwise - check if at least one export is value + symbolLinks.exportsSomeValue = hasExportAssignment + ? !!(moduleSymbol.flags & SymbolFlags.Value) + : forEachEntry(getExportsOfModule(moduleSymbol), isValue); + } - return symbolLinks.exportsSomeValue!; + return symbolLinks.exportsSomeValue!; - function isValue(s: Symbol): boolean { - s = resolveSymbol(s); - return s && !!(getAllSymbolFlags(s) & SymbolFlags.Value); - } + function isValue(s: Symbol): boolean { + s = resolveSymbol(s); + return s && !!(getAllSymbolFlags(s) & SymbolFlags.Value); } + } - function isNameOfModuleOrEnumDeclaration(node: Identifier) { - return isModuleOrEnumDeclaration(node.parent) && node === node.parent.name; - } + function isNameOfModuleOrEnumDeclaration(node: Identifier) { + return isModuleOrEnumDeclaration(node.parent) && node === node.parent.name; + } - // When resolved as an expression identifier, if the given node references an exported entity, return the declaration - // node of the exported entity's container. Otherwise, return undefined. - function getReferencedExportContainer(nodeIn: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration | undefined { - const node = getParseTreeNode(nodeIn, isIdentifier); - if (node) { - // When resolving the export container for the name of a module or enum - // declaration, we need to start resolution at the declaration's container. - // Otherwise, we could incorrectly resolve the export container as the - // declaration if it contains an exported member with the same name. - let symbol = getReferencedValueSymbol(node, /*startInDeclarationContainer*/ isNameOfModuleOrEnumDeclaration(node)); - if (symbol) { - if (symbol.flags & SymbolFlags.ExportValue) { - // If we reference an exported entity within the same module declaration, then whether - // we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the - // kinds that we do NOT prefix. - const exportSymbol = getMergedSymbol(symbol.exportSymbol!); - if (!prefixLocals && exportSymbol.flags & SymbolFlags.ExportHasLocal && !(exportSymbol.flags & SymbolFlags.Variable)) { - return undefined; - } - symbol = exportSymbol; - } - const parentSymbol = getParentOfSymbol(symbol); - if (parentSymbol) { - if (parentSymbol.flags & SymbolFlags.ValueModule && parentSymbol.valueDeclaration?.kind === SyntaxKind.SourceFile) { - const symbolFile = parentSymbol.valueDeclaration as SourceFile; - const referenceFile = getSourceFileOfNode(node); - // If `node` accesses an export and that export isn't in the same file, then symbol is a namespace export, so return undefined. - const symbolIsUmdExport = symbolFile !== referenceFile; - return symbolIsUmdExport ? undefined : symbolFile; - } - return findAncestor(node.parent, (n): n is ModuleDeclaration | EnumDeclaration => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol); + // When resolved as an expression identifier, if the given node references an exported entity, return the declaration + // node of the exported entity's container. Otherwise, return undefined. + function getReferencedExportContainer(nodeIn: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration | undefined { + const node = getParseTreeNode(nodeIn, isIdentifier); + if (node) { + // When resolving the export container for the name of a module or enum + // declaration, we need to start resolution at the declaration's container. + // Otherwise, we could incorrectly resolve the export container as the + // declaration if it contains an exported member with the same name. + let symbol = getReferencedValueSymbol(node, /*startInDeclarationContainer*/ isNameOfModuleOrEnumDeclaration(node)); + if (symbol) { + if (symbol.flags & SymbolFlags.ExportValue) { + // If we reference an exported entity within the same module declaration, then whether + // we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the + // kinds that we do NOT prefix. + const exportSymbol = getMergedSymbol(symbol.exportSymbol!); + if (!prefixLocals && exportSymbol.flags & SymbolFlags.ExportHasLocal && !(exportSymbol.flags & SymbolFlags.Variable)) { + return undefined; } + symbol = exportSymbol; } - } - } - - // When resolved as an expression identifier, if the given node references an import, return the declaration of - // that import. Otherwise, return undefined. - function getReferencedImportDeclaration(nodeIn: Identifier): Declaration | undefined { - if (nodeIn.generatedImportReference) { - return nodeIn.generatedImportReference; - } - const node = getParseTreeNode(nodeIn, isIdentifier); - if (node) { - const symbol = getReferencedValueOrAliasSymbol(node); - - // We should only get the declaration of an alias if there isn't a local value - // declaration for the symbol - if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value)) { - return getDeclarationOfAliasSymbol(symbol); + const parentSymbol = getParentOfSymbol(symbol); + if (parentSymbol) { + if (parentSymbol.flags & SymbolFlags.ValueModule && parentSymbol.valueDeclaration?.kind === SyntaxKind.SourceFile) { + const symbolFile = parentSymbol.valueDeclaration as SourceFile; + const referenceFile = getSourceFileOfNode(node); + // If `node` accesses an export and that export isn't in the same file, then symbol is a namespace export, so return undefined. + const symbolIsUmdExport = symbolFile !== referenceFile; + return symbolIsUmdExport ? undefined : symbolFile; + } + return findAncestor(node.parent, (n): n is ModuleDeclaration | EnumDeclaration => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol); } } - - return undefined; } + } - function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) { - return symbol.valueDeclaration - && isBindingElement(symbol.valueDeclaration) - && walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause; + // When resolved as an expression identifier, if the given node references an import, return the declaration of + // that import. Otherwise, return undefined. + function getReferencedImportDeclaration(nodeIn: Identifier): Declaration | undefined { + if (nodeIn.generatedImportReference) { + return nodeIn.generatedImportReference; } + const node = getParseTreeNode(nodeIn, isIdentifier); + if (node) { + const symbol = getReferencedValueOrAliasSymbol(node); - function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean { - if (symbol.flags & SymbolFlags.BlockScoped && symbol.valueDeclaration && !isSourceFile(symbol.valueDeclaration)) { - const links = getSymbolLinks(symbol); - if (links.isDeclarationWithCollidingName === undefined) { - const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); - 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 - links.isDeclarationWithCollidingName = true; - } - else if (nodeLinks.flags & NodeCheckFlags.CapturedBlockScopedBinding) { - // binding is captured in the function - // should be renamed if: - // - binding is not top level - top level bindings never collide with anything - // AND - // - binding is not declared in loop, should be renamed to avoid name reuse across siblings - // let a, b - // { let x = 1; a = () => x; } - // { let x = 100; b = () => x; } - // console.log(a()); // should print '1' - // console.log(b()); // should print '100' - // OR - // - binding is declared inside loop but not in inside initializer of iteration statement or directly inside loop body - // * variables from initializer are passed to rewritten loop body as parameters so they are not captured directly - // * variables that are declared immediately in loop body will become top level variable after loop is rewritten and thus - // they will not collide with anything - const isDeclaredInLoop = nodeLinks.flags & NodeCheckFlags.BlockScopedBindingInLoop; - const inLoopInitializer = isIterationStatement(container, /*lookInLabeledStatements*/ false); - const inLoopBodyBlock = container.kind === SyntaxKind.Block && isIterationStatement(container.parent, /*lookInLabeledStatements*/ false); - - links.isDeclarationWithCollidingName = !isBlockScopedContainerTopLevel(container) && (!isDeclaredInLoop || (!inLoopInitializer && !inLoopBodyBlock)); - } - else { - links.isDeclarationWithCollidingName = false; - } - } - } - return links.isDeclarationWithCollidingName!; + // We should only get the declaration of an alias if there isn't a local value + // declaration for the symbol + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value)) { + return getDeclarationOfAliasSymbol(symbol); } - return false; } - // When resolved as an expression identifier, if the given node references a nested block scoped entity with - // a name that either hides an existing name or might hide it when compiled downlevel, - // return the declaration of that entity. Otherwise, return undefined. - function getReferencedDeclarationWithCollidingName(nodeIn: Identifier): Declaration | undefined { - if (!isGeneratedIdentifier(nodeIn)) { - const node = getParseTreeNode(nodeIn, isIdentifier); - if (node) { - const symbol = getReferencedValueSymbol(node); - if (symbol && isSymbolOfDeclarationWithCollidingName(symbol)) { - return symbol.valueDeclaration; + return undefined; + } + + function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) { + return symbol.valueDeclaration + && isBindingElement(symbol.valueDeclaration) + && walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause; + } + + function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean { + if (symbol.flags & SymbolFlags.BlockScoped && symbol.valueDeclaration && !isSourceFile(symbol.valueDeclaration)) { + const links = getSymbolLinks(symbol); + if (links.isDeclarationWithCollidingName === undefined) { + const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); + 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 + links.isDeclarationWithCollidingName = true; + } + else if (nodeLinks.flags & NodeCheckFlags.CapturedBlockScopedBinding) { + // binding is captured in the function + // should be renamed if: + // - binding is not top level - top level bindings never collide with anything + // AND + // - binding is not declared in loop, should be renamed to avoid name reuse across siblings + // let a, b + // { let x = 1; a = () => x; } + // { let x = 100; b = () => x; } + // console.log(a()); // should print '1' + // console.log(b()); // should print '100' + // OR + // - binding is declared inside loop but not in inside initializer of iteration statement or directly inside loop body + // * variables from initializer are passed to rewritten loop body as parameters so they are not captured directly + // * variables that are declared immediately in loop body will become top level variable after loop is rewritten and thus + // they will not collide with anything + const isDeclaredInLoop = nodeLinks.flags & NodeCheckFlags.BlockScopedBindingInLoop; + const inLoopInitializer = isIterationStatement(container, /*lookInLabeledStatements*/ false); + const inLoopBodyBlock = container.kind === SyntaxKind.Block && isIterationStatement(container.parent, /*lookInLabeledStatements*/ false); + + links.isDeclarationWithCollidingName = !isBlockScopedContainerTopLevel(container) && (!isDeclaredInLoop || (!inLoopInitializer && !inLoopBodyBlock)); + } + else { + links.isDeclarationWithCollidingName = false; } } } - - return undefined; + return links.isDeclarationWithCollidingName!; } + return false; + } - // Return true if the given node is a declaration of a nested block scoped entity with a name that either hides an - // existing name or might hide a name when compiled downlevel - function isDeclarationWithCollidingName(nodeIn: Declaration): boolean { - const node = getParseTreeNode(nodeIn, isDeclaration); + // When resolved as an expression identifier, if the given node references a nested block scoped entity with + // a name that either hides an existing name or might hide it when compiled downlevel, + // return the declaration of that entity. Otherwise, return undefined. + function getReferencedDeclarationWithCollidingName(nodeIn: Identifier): Declaration | undefined { + if (!isGeneratedIdentifier(nodeIn)) { + const node = getParseTreeNode(nodeIn, isIdentifier); if (node) { - const symbol = getSymbolOfNode(node); - if (symbol) { - return isSymbolOfDeclarationWithCollidingName(symbol); + const symbol = getReferencedValueSymbol(node); + if (symbol && isSymbolOfDeclarationWithCollidingName(symbol)) { + return symbol.valueDeclaration; } } - - return false; - } - - function isValueAliasDeclaration(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - return isAliasResolvedToValue(getSymbolOfNode(node)); - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - const symbol = getSymbolOfNode(node); - return !!symbol && isAliasResolvedToValue(symbol) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value); - case SyntaxKind.ExportDeclaration: - const exportClause = (node as ExportDeclaration).exportClause; - return !!exportClause && ( - isNamespaceExport(exportClause) || - some(exportClause.elements, isValueAliasDeclaration) - ); - case SyntaxKind.ExportAssignment: - return (node as ExportAssignment).expression && (node as ExportAssignment).expression.kind === SyntaxKind.Identifier ? - isAliasResolvedToValue(getSymbolOfNode(node)) : - true; - } - return false; } - function isTopLevelValueImportEqualsWithEntityName(nodeIn: ImportEqualsDeclaration): boolean { - const node = getParseTreeNode(nodeIn, isImportEqualsDeclaration); - if (node === undefined || node.parent.kind !== SyntaxKind.SourceFile || !isInternalModuleImportEqualsDeclaration(node)) { - // parent is not source file or it is not reference to internal module - return false; - } - - const isValue = isAliasResolvedToValue(getSymbolOfNode(node)); - return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); - } + return undefined; + } - function isAliasResolvedToValue(symbol: Symbol | undefined): boolean { - if (!symbol) { - return false; - } - const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); - if (target === unknownSymbol) { - return true; + // Return true if the given node is a declaration of a nested block scoped entity with a name that either hides an + // existing name or might hide a name when compiled downlevel + function isDeclarationWithCollidingName(nodeIn: Declaration): boolean { + const node = getParseTreeNode(nodeIn, isDeclaration); + if (node) { + const symbol = getSymbolOfNode(node); + if (symbol) { + return isSymbolOfDeclarationWithCollidingName(symbol); } - // const enums and modules that contain only const enums are not considered values from the emit perspective - // unless 'preserveConstEnums' option is set to true - return !!((getAllSymbolFlags(target) ?? -1) & SymbolFlags.Value) && - (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target)); } - function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean { - return isConstEnumSymbol(s) || !!s.constEnumOnlyModule; - } + return false; + } - function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { - if (isAliasSymbolDeclaration(node)) { + function isValueAliasDeclaration(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return isAliasResolvedToValue(getSymbolOfNode(node)); + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: const symbol = getSymbolOfNode(node); - const links = symbol && getSymbolLinks(symbol); - if (links?.referenced) { - return true; - } - const target = getSymbolLinks(symbol!).aliasTarget; // TODO: GH#18217 - if (target && getEffectiveModifierFlags(node) & ModifierFlags.Export && - getAllSymbolFlags(target) & SymbolFlags.Value && - (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target))) { - // An `export import ... =` of a value symbol is always considered referenced - return true; - } - } + return !!symbol && isAliasResolvedToValue(symbol) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value); + case SyntaxKind.ExportDeclaration: + const exportClause = (node as ExportDeclaration).exportClause; + return !!exportClause && ( + isNamespaceExport(exportClause) || + some(exportClause.elements, isValueAliasDeclaration) + ); + case SyntaxKind.ExportAssignment: + return (node as ExportAssignment).expression && (node as ExportAssignment).expression.kind === SyntaxKind.Identifier ? + isAliasResolvedToValue(getSymbolOfNode(node)) : + true; + } + return false; + } - if (checkChildren) { - return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren)); - } + function isTopLevelValueImportEqualsWithEntityName(nodeIn: ImportEqualsDeclaration): boolean { + const node = getParseTreeNode(nodeIn, isImportEqualsDeclaration); + if (node === undefined || node.parent.kind !== SyntaxKind.SourceFile || !isInternalModuleImportEqualsDeclaration(node)) { + // parent is not source file or it is not reference to internal module return false; } - function isImplementationOfOverload(node: SignatureDeclaration) { - if (nodeIsPresent((node as FunctionLikeDeclaration).body)) { - if (isGetAccessor(node) || isSetAccessor(node)) return false; // Get or set accessors can never be overload implementations, but can have up to 2 signatures - const symbol = getSymbolOfNode(node); - const signaturesOfSymbol = getSignaturesOfSymbol(symbol); - // If this function body corresponds to function with multiple signature, it is implementation of overload - // e.g.: function foo(a: string): string; - // function foo(a: number): number; - // function foo(a: any) { // This is implementation of the overloads - // return a; - // } - return signaturesOfSymbol.length > 1 || - // If there is single signature for the symbol, it is overload if that signature isn't coming from the node - // e.g.: function foo(a: string): string; - // function foo(a: any) { // This is implementation of the overloads - // return a; - // } - (signaturesOfSymbol.length === 1 && signaturesOfSymbol[0].declaration !== node); - } + const isValue = isAliasResolvedToValue(getSymbolOfNode(node)); + return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); + } + + function isAliasResolvedToValue(symbol: Symbol | undefined): boolean { + if (!symbol) { return false; } - - function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { - return !!strictNullChecks && - !isOptionalParameter(parameter) && - !isJSDocParameterTag(parameter) && - !!parameter.initializer && - !hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); + if (target === unknownSymbol) { + return true; } + // const enums and modules that contain only const enums are not considered values from the emit perspective + // unless 'preserveConstEnums' option is set to true + return !!((getAllSymbolFlags(target) ?? -1) & SymbolFlags.Value) && + (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target)); + } - function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration) { - return strictNullChecks && - isOptionalParameter(parameter) && - !parameter.initializer && - hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); - } + function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean { + return isConstEnumSymbol(s) || !!s.constEnumOnlyModule; + } - function isExpandoFunctionDeclaration(node: Declaration): boolean { - const declaration = getParseTreeNode(node, isFunctionDeclaration); - if (!declaration) { - return false; + function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { + if (isAliasSymbolDeclaration(node)) { + const symbol = getSymbolOfNode(node); + const links = symbol && getSymbolLinks(symbol); + if (links?.referenced) { + return true; } - const symbol = getSymbolOfNode(declaration); - if (!symbol || !(symbol.flags & SymbolFlags.Function)) { - return false; + const target = getSymbolLinks(symbol!).aliasTarget; // TODO: GH#18217 + if (target && getEffectiveModifierFlags(node) & ModifierFlags.Export && + getAllSymbolFlags(target) & SymbolFlags.Value && + (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target))) { + // An `export import ... =` of a value symbol is always considered referenced + return true; } - return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && p.valueDeclaration && isPropertyAccessExpression(p.valueDeclaration)); } - function getPropertiesOfContainerFunction(node: Declaration): Symbol[] { - const declaration = getParseTreeNode(node, isFunctionDeclaration); - if (!declaration) { - return emptyArray; - } - const symbol = getSymbolOfNode(declaration); - return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray; + if (checkChildren) { + return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren)); } + return false; + } - function getNodeCheckFlags(node: Node): NodeCheckFlags { - const nodeId = node.id || 0; - if (nodeId < 0 || nodeId >= nodeLinks.length) return 0; - return nodeLinks[nodeId]?.flags || 0; + function isImplementationOfOverload(node: SignatureDeclaration) { + if (nodeIsPresent((node as FunctionLikeDeclaration).body)) { + if (isGetAccessor(node) || isSetAccessor(node)) return false; // Get or set accessors can never be overload implementations, but can have up to 2 signatures + const symbol = getSymbolOfNode(node); + const signaturesOfSymbol = getSignaturesOfSymbol(symbol); + // If this function body corresponds to function with multiple signature, it is implementation of overload + // e.g.: function foo(a: string): string; + // function foo(a: number): number; + // function foo(a: any) { // This is implementation of the overloads + // return a; + // } + return signaturesOfSymbol.length > 1 || + // If there is single signature for the symbol, it is overload if that signature isn't coming from the node + // e.g.: function foo(a: string): string; + // function foo(a: any) { // This is implementation of the overloads + // return a; + // } + (signaturesOfSymbol.length === 1 && signaturesOfSymbol[0].declaration !== node); } + return false; + } - function getEnumMemberValue(node: EnumMember): string | number | undefined { - computeEnumMemberValues(node.parent); - return getNodeLinks(node).enumMemberValue; - } + function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { + return !!strictNullChecks && + !isOptionalParameter(parameter) && + !isJSDocParameterTag(parameter) && + !!parameter.initializer && + !hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + } - function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression { - switch (node.kind) { - case SyntaxKind.EnumMember: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return true; - } + function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration) { + return strictNullChecks && + isOptionalParameter(parameter) && + !parameter.initializer && + hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + } + + function isExpandoFunctionDeclaration(node: Declaration): boolean { + const declaration = getParseTreeNode(node, isFunctionDeclaration); + if (!declaration) { return false; } + const symbol = getSymbolOfNode(declaration); + if (!symbol || !(symbol.flags & SymbolFlags.Function)) { + return false; + } + return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && p.valueDeclaration && isPropertyAccessExpression(p.valueDeclaration)); + } - function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { - if (node.kind === SyntaxKind.EnumMember) { - return getEnumMemberValue(node); - } + function getPropertiesOfContainerFunction(node: Declaration): Symbol[] { + const declaration = getParseTreeNode(node, isFunctionDeclaration); + if (!declaration) { + return emptyArray; + } + const symbol = getSymbolOfNode(declaration); + return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray; + } - const symbol = getNodeLinks(node).resolvedSymbol; - if (symbol && (symbol.flags & SymbolFlags.EnumMember)) { - // inline property\index accesses only for const enums - const member = symbol.valueDeclaration as EnumMember; - if (isEnumConst(member.parent)) { - return getEnumMemberValue(member); - } - } + function getNodeCheckFlags(node: Node): NodeCheckFlags { + const nodeId = node.id || 0; + if (nodeId < 0 || nodeId >= nodeLinks.length) return 0; + return nodeLinks[nodeId]?.flags || 0; + } - return undefined; - } + function getEnumMemberValue(node: EnumMember): string | number | undefined { + computeEnumMemberValues(node.parent); + return getNodeLinks(node).enumMemberValue; + } - function isFunctionType(type: Type): boolean { - return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0; + function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression { + switch (node.kind) { + case SyntaxKind.EnumMember: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return true; } + return false; + } - function getTypeReferenceSerializationKind(typeNameIn: EntityName, location?: Node): TypeReferenceSerializationKind { - // ensure both `typeName` and `location` are parse tree nodes. - const typeName = getParseTreeNode(typeNameIn, isEntityName); - if (!typeName) return TypeReferenceSerializationKind.Unknown; + function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { + if (node.kind === SyntaxKind.EnumMember) { + return getEnumMemberValue(node); + } - if (location) { - location = getParseTreeNode(location); - if (!location) return TypeReferenceSerializationKind.Unknown; + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol && (symbol.flags & SymbolFlags.EnumMember)) { + // inline property\index accesses only for const enums + const member = symbol.valueDeclaration as EnumMember; + if (isEnumConst(member.parent)) { + return getEnumMemberValue(member); } + } - // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. - let isTypeOnly = false; - if (isQualifiedName(typeName)) { - const rootValueSymbol = resolveEntityName(getFirstIdentifier(typeName), SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); - isTypeOnly = !!rootValueSymbol?.declarations?.every(isTypeOnlyImportOrExportDeclaration); - } - const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); - const resolvedSymbol = valueSymbol && valueSymbol.flags & SymbolFlags.Alias ? resolveAlias(valueSymbol) : valueSymbol; - isTypeOnly ||= !!valueSymbol?.declarations?.every(isTypeOnlyImportOrExportDeclaration); + return undefined; + } - // Resolve the symbol as a type so that we can provide a more useful hint for the type serializer. - const typeSymbol = resolveEntityName(typeName, SymbolFlags.Type, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location); - if (resolvedSymbol && resolvedSymbol === typeSymbol) { - const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); - if (globalPromiseSymbol && resolvedSymbol === globalPromiseSymbol) { - return TypeReferenceSerializationKind.Promise; - } + function isFunctionType(type: Type): boolean { + return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0; + } - const constructorType = getTypeOfSymbol(resolvedSymbol); - if (constructorType && isConstructorType(constructorType)) { - return isTypeOnly ? TypeReferenceSerializationKind.TypeWithCallSignature : TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; - } - } + function getTypeReferenceSerializationKind(typeNameIn: EntityName, location?: Node): TypeReferenceSerializationKind { + // ensure both `typeName` and `location` are parse tree nodes. + const typeName = getParseTreeNode(typeNameIn, isEntityName); + if (!typeName) return TypeReferenceSerializationKind.Unknown; - // We might not be able to resolve type symbol so use unknown type in that case (eg error case) - if (!typeSymbol) { - return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; - } - const type = getDeclaredTypeOfSymbol(typeSymbol); - if (isErrorType(type)) { - return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; - } - else if (type.flags & TypeFlags.AnyOrUnknown) { - return TypeReferenceSerializationKind.ObjectType; - } - else if (isTypeAssignableToKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) { - return TypeReferenceSerializationKind.VoidNullableOrNeverType; - } - else if (isTypeAssignableToKind(type, TypeFlags.BooleanLike)) { - return TypeReferenceSerializationKind.BooleanType; - } - else if (isTypeAssignableToKind(type, TypeFlags.NumberLike)) { - return TypeReferenceSerializationKind.NumberLikeType; - } - else if (isTypeAssignableToKind(type, TypeFlags.BigIntLike)) { - return TypeReferenceSerializationKind.BigIntLikeType; - } - else if (isTypeAssignableToKind(type, TypeFlags.StringLike)) { - return TypeReferenceSerializationKind.StringLikeType; - } - else if (isTupleType(type)) { - return TypeReferenceSerializationKind.ArrayLikeType; - } - else if (isTypeAssignableToKind(type, TypeFlags.ESSymbolLike)) { - return TypeReferenceSerializationKind.ESSymbolType; - } - else if (isFunctionType(type)) { - return TypeReferenceSerializationKind.TypeWithCallSignature; - } - else if (isArrayType(type)) { - return TypeReferenceSerializationKind.ArrayLikeType; - } - else { - return TypeReferenceSerializationKind.ObjectType; - } + if (location) { + location = getParseTreeNode(location); + if (!location) return TypeReferenceSerializationKind.Unknown; } - function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) { - const declaration = getParseTreeNode(declarationIn, isVariableLikeOrAccessor); - if (!declaration) { - return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; - } - // Get type of the symbol if this is the valid symbol otherwise get type at location - const symbol = getSymbolOfNode(declaration); - let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) - ? getWidenedLiteralType(getTypeOfSymbol(symbol)) - : errorType; - if (type.flags & TypeFlags.UniqueESSymbol && - type.symbol === symbol) { - flags |= NodeBuilderFlags.AllowUniqueESSymbolType; - } - if (addUndefined) { - type = getOptionalType(type); - } - return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. + let isTypeOnly = false; + if (isQualifiedName(typeName)) { + const rootValueSymbol = resolveEntityName(getFirstIdentifier(typeName), SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); + isTypeOnly = !!rootValueSymbol?.declarations?.every(isTypeOnlyImportOrExportDeclaration); } + const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); + const resolvedSymbol = valueSymbol && valueSymbol.flags & SymbolFlags.Alias ? resolveAlias(valueSymbol) : valueSymbol; + isTypeOnly ||= !!valueSymbol?.declarations?.every(isTypeOnlyImportOrExportDeclaration); - function createReturnTypeOfSignatureDeclaration(signatureDeclarationIn: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { - const signatureDeclaration = getParseTreeNode(signatureDeclarationIn, isFunctionLike); - if (!signatureDeclaration) { - return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + // Resolve the symbol as a type so that we can provide a more useful hint for the type serializer. + const typeSymbol = resolveEntityName(typeName, SymbolFlags.Type, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location); + if (resolvedSymbol && resolvedSymbol === typeSymbol) { + const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); + if (globalPromiseSymbol && resolvedSymbol === globalPromiseSymbol) { + return TypeReferenceSerializationKind.Promise; } - const signature = getSignatureFromDeclaration(signatureDeclaration); - return nodeBuilder.typeToTypeNode(getReturnTypeOfSignature(signature), enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); - } - function createTypeOfExpression(exprIn: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { - const expr = getParseTreeNode(exprIn, isExpression); - if (!expr) { - return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + const constructorType = getTypeOfSymbol(resolvedSymbol); + if (constructorType && isConstructorType(constructorType)) { + return isTypeOnly ? TypeReferenceSerializationKind.TypeWithCallSignature : TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; } - const type = getWidenedType(getRegularTypeOfExpression(expr)); - return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); } - function hasGlobalName(name: string): boolean { - return globals.has(escapeLeadingUnderscores(name)); + // We might not be able to resolve type symbol so use unknown type in that case (eg error case) + if (!typeSymbol) { + return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; + } + const type = getDeclaredTypeOfSymbol(typeSymbol); + if (isErrorType(type)) { + return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; + } + else if (type.flags & TypeFlags.AnyOrUnknown) { + return TypeReferenceSerializationKind.ObjectType; + } + else if (isTypeAssignableToKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) { + return TypeReferenceSerializationKind.VoidNullableOrNeverType; + } + else if (isTypeAssignableToKind(type, TypeFlags.BooleanLike)) { + return TypeReferenceSerializationKind.BooleanType; + } + else if (isTypeAssignableToKind(type, TypeFlags.NumberLike)) { + return TypeReferenceSerializationKind.NumberLikeType; } + else if (isTypeAssignableToKind(type, TypeFlags.BigIntLike)) { + return TypeReferenceSerializationKind.BigIntLikeType; + } + else if (isTypeAssignableToKind(type, TypeFlags.StringLike)) { + return TypeReferenceSerializationKind.StringLikeType; + } + else if (isTupleType(type)) { + return TypeReferenceSerializationKind.ArrayLikeType; + } + else if (isTypeAssignableToKind(type, TypeFlags.ESSymbolLike)) { + return TypeReferenceSerializationKind.ESSymbolType; + } + else if (isFunctionType(type)) { + return TypeReferenceSerializationKind.TypeWithCallSignature; + } + else if (isArrayType(type)) { + return TypeReferenceSerializationKind.ArrayLikeType; + } + else { + return TypeReferenceSerializationKind.ObjectType; + } + } - function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol | undefined { - const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; - if (resolvedSymbol) { - return resolvedSymbol; - } + function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) { + const declaration = getParseTreeNode(declarationIn, isVariableLikeOrAccessor); + if (!declaration) { + return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + } + // Get type of the symbol if this is the valid symbol otherwise get type at location + const symbol = getSymbolOfNode(declaration); + let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) + ? getWidenedLiteralType(getTypeOfSymbol(symbol)) + : errorType; + if (type.flags & TypeFlags.UniqueESSymbol && + type.symbol === symbol) { + flags |= NodeBuilderFlags.AllowUniqueESSymbolType; + } + if (addUndefined) { + type = getOptionalType(type); + } + return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } - let location: Node = reference; - if (startInDeclarationContainer) { - // When resolving the name of a declaration as a value, we need to start resolution - // at a point outside of the declaration. - const parent = reference.parent; - if (isDeclaration(parent) && reference === parent.name) { - location = getDeclarationContainer(parent); - } - } + function createReturnTypeOfSignatureDeclaration(signatureDeclarationIn: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { + const signatureDeclaration = getParseTreeNode(signatureDeclarationIn, isFunctionLike); + if (!signatureDeclaration) { + return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + } + const signature = getSignatureFromDeclaration(signatureDeclaration); + return nodeBuilder.typeToTypeNode(getReturnTypeOfSignature(signature), enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } - return resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + function createTypeOfExpression(exprIn: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { + const expr = getParseTreeNode(exprIn, isExpression); + if (!expr) { + return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; } + const type = getWidenedType(getRegularTypeOfExpression(expr)); + return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } - /** - * Get either a value-meaning symbol or an alias symbol. - * Unlike `getReferencedValueSymbol`, if the cached resolved symbol is the unknown symbol, - * we call `resolveName` to find a symbol. - * This is because when caching the resolved symbol, we only consider value symbols, but here - * we want to also get an alias symbol if one exists. - */ - function getReferencedValueOrAliasSymbol(reference: Identifier): Symbol | undefined { - const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; - if (resolvedSymbol && resolvedSymbol !== unknownSymbol) { - return resolvedSymbol; - } - - return resolveName( - reference, - reference.escapedText, - SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, - /*nodeNotFoundMessage*/ undefined, - /*nameArg*/ undefined, - /*isUse*/ true, - /*excludeGlobals*/ undefined, - /*getSpellingSuggestions*/ undefined); - } - - function getReferencedValueDeclaration(referenceIn: Identifier): Declaration | undefined { - if (!isGeneratedIdentifier(referenceIn)) { - const reference = getParseTreeNode(referenceIn, isIdentifier); - if (reference) { - const symbol = getReferencedValueSymbol(reference); - if (symbol) { - return getExportSymbolOfValueSymbolIfExported(symbol).valueDeclaration; - } - } - } + function hasGlobalName(name: string): boolean { + return globals.has(escapeLeadingUnderscores(name)); + } - return undefined; + function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol | undefined { + const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; + if (resolvedSymbol) { + return resolvedSymbol; } - function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { - if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) { - return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node))); + let location: Node = reference; + if (startInDeclarationContainer) { + // When resolving the name of a declaration as a value, we need to start resolution + // at a point outside of the declaration. + const parent = reference.parent; + if (isDeclaration(parent) && reference === parent.name) { + location = getDeclarationContainer(parent); } - return false; - } - - function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { - const enumResult = type.flags & TypeFlags.EnumLiteral ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing, /*flags*/ undefined, tracker) - : type === trueType ? factory.createTrue() : type === falseType && factory.createFalse(); - if (enumResult) return enumResult; - const literalValue = (type as LiteralType).value; - return typeof literalValue === "object" ? factory.createBigIntLiteral(literalValue) : - typeof literalValue === "number" ? factory.createNumericLiteral(literalValue) : - factory.createStringLiteral(literalValue); } - function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { - const type = getTypeOfSymbol(getSymbolOfNode(node)); - return literalTypeToNode(type as FreshableType, node, tracker); - } + return resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + } - function getJsxFactoryEntity(location: Node): EntityName | undefined { - return location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity; - } + /** + * Get either a value-meaning symbol or an alias symbol. + * Unlike `getReferencedValueSymbol`, if the cached resolved symbol is the unknown symbol, + * we call `resolveName` to find a symbol. + * This is because when caching the resolved symbol, we only consider value symbols, but here + * we want to also get an alias symbol if one exists. + */ + function getReferencedValueOrAliasSymbol(reference: Identifier): Symbol | undefined { + const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; + if (resolvedSymbol && resolvedSymbol !== unknownSymbol) { + return resolvedSymbol; + } + + return resolveName( + reference, + reference.escapedText, + SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, + /*nodeNotFoundMessage*/ undefined, + /*nameArg*/ undefined, + /*isUse*/ true, + /*excludeGlobals*/ undefined, + /*getSpellingSuggestions*/ undefined); + } - function getJsxFragmentFactoryEntity(location: Node): EntityName | undefined { - if (location) { - const file = getSourceFileOfNode(location); - if (file) { - if (file.localJsxFragmentFactory) { - return file.localJsxFragmentFactory; - } - const jsxFragPragmas = file.pragmas.get("jsxfrag"); - const jsxFragPragma = isArray(jsxFragPragmas) ? jsxFragPragmas[0] : jsxFragPragmas; - if (jsxFragPragma) { - file.localJsxFragmentFactory = parseIsolatedEntityName(jsxFragPragma.arguments.factory, languageVersion); - return file.localJsxFragmentFactory; - } + function getReferencedValueDeclaration(referenceIn: Identifier): Declaration | undefined { + if (!isGeneratedIdentifier(referenceIn)) { + const reference = getParseTreeNode(referenceIn, isIdentifier); + if (reference) { + const symbol = getReferencedValueSymbol(reference); + if (symbol) { + return getExportSymbolOfValueSymbolIfExported(symbol).valueDeclaration; } } + } - if (compilerOptions.jsxFragmentFactory) { - return parseIsolatedEntityName(compilerOptions.jsxFragmentFactory, languageVersion); - } + return undefined; + } + + function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { + if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) { + return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node))); } + return false; + } - function createResolver(): EmitResolver { - // this variable and functions that use it are deliberately moved here from the outer scope - // to avoid scope pollution - const resolvedTypeReferenceDirectives = host.getResolvedTypeReferenceDirectives(); - let fileToDirective: ESMap; - if (resolvedTypeReferenceDirectives) { - // populate reverse mapping: file path -> type reference directive that was resolved to this file - fileToDirective = new Map(); - resolvedTypeReferenceDirectives.forEach((resolvedDirective, key, mode) => { - if (!resolvedDirective || !resolvedDirective.resolvedFileName) { - return; - } - const file = host.getSourceFile(resolvedDirective.resolvedFileName); - if (file) { - // Add the transitive closure of path references loaded by this file (as long as they are not) - // part of an existing type reference. - addReferencedFilesToTypeDirective(file, key, mode); - } - }); - } + function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { + const enumResult = type.flags & TypeFlags.EnumLiteral ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing, /*flags*/ undefined, tracker) + : type === trueType ? factory.createTrue() : type === falseType && factory.createFalse(); + if (enumResult) return enumResult; + const literalValue = (type as LiteralType).value; + return typeof literalValue === "object" ? factory.createBigIntLiteral(literalValue) : + typeof literalValue === "number" ? factory.createNumericLiteral(literalValue) : + factory.createStringLiteral(literalValue); + } - return { - getReferencedExportContainer, - getReferencedImportDeclaration, - getReferencedDeclarationWithCollidingName, - isDeclarationWithCollidingName, - isValueAliasDeclaration: nodeIn => { - const node = getParseTreeNode(nodeIn); - // Synthesized nodes are always treated like values. - return node ? isValueAliasDeclaration(node) : true; - }, - hasGlobalName, - isReferencedAliasDeclaration: (nodeIn, checkChildren?) => { - const node = getParseTreeNode(nodeIn); - // Synthesized nodes are always treated as referenced. - return node ? isReferencedAliasDeclaration(node, checkChildren) : true; - }, - getNodeCheckFlags: nodeIn => { - const node = getParseTreeNode(nodeIn); - return node ? getNodeCheckFlags(node) : 0; - }, - isTopLevelValueImportEqualsWithEntityName, - isDeclarationVisible, - isImplementationOfOverload, - isRequiredInitializedParameter, - isOptionalUninitializedParameterProperty, - isExpandoFunctionDeclaration, - getPropertiesOfContainerFunction, - createTypeOfDeclaration, - createReturnTypeOfSignatureDeclaration, - createTypeOfExpression, - createLiteralConstValue, - isSymbolAccessible, - isEntityNameVisible, - getConstantValue: nodeIn => { - const node = getParseTreeNode(nodeIn, canHaveConstantValue); - return node ? getConstantValue(node) : undefined; - }, - collectLinkedAliases, - getReferencedValueDeclaration, - getTypeReferenceSerializationKind, - isOptionalParameter, - moduleExportsSomeValue, - isArgumentsLocalBinding, - getExternalModuleFileFromDeclaration: nodeIn => { - const node = getParseTreeNode(nodeIn, hasPossibleExternalModuleReference); - return node && getExternalModuleFileFromDeclaration(node); - }, - getTypeReferenceDirectivesForEntityName, - getTypeReferenceDirectivesForSymbol, - isLiteralConstDeclaration, - isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => { - const node = getParseTreeNode(nodeIn, isDeclaration); - const symbol = node && getSymbolOfNode(node); - return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late); - }, - getJsxFactoryEntity, - getJsxFragmentFactoryEntity, - getAllAccessorDeclarations(accessor: AccessorDeclaration): AllAccessorDeclarations { - accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217 - const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor; - const otherAccessor = getDeclarationOfKind(getSymbolOfNode(accessor), otherKind); - const firstAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? otherAccessor : accessor; - const secondAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? accessor : otherAccessor; - const setAccessor = accessor.kind === SyntaxKind.SetAccessor ? accessor : otherAccessor as SetAccessorDeclaration; - const getAccessor = accessor.kind === SyntaxKind.GetAccessor ? accessor : otherAccessor as GetAccessorDeclaration; - return { - firstAccessor, - secondAccessor, - setAccessor, - getAccessor - }; - }, - getSymbolOfExternalModuleSpecifier: moduleName => resolveExternalModuleNameWorker(moduleName, moduleName, /*moduleNotFoundError*/ undefined), - isBindingCapturedByNode: (node, decl) => { - const parseNode = getParseTreeNode(node); - const parseDecl = getParseTreeNode(decl); - return !!parseNode && !!parseDecl && (isVariableDeclaration(parseDecl) || isBindingElement(parseDecl)) && isBindingCapturedByNode(parseNode, parseDecl); - }, - getDeclarationStatementsForSourceFile: (node, flags, tracker, bundled) => { - const n = getParseTreeNode(node) as SourceFile; - Debug.assert(n && n.kind === SyntaxKind.SourceFile, "Non-sourcefile node passed into getDeclarationsForSourceFile"); - const sym = getSymbolOfNode(node); - if (!sym) { - return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker, bundled); - } - return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled); - }, - isImportRequiredByAugmentation, - }; + function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { + const type = getTypeOfSymbol(getSymbolOfNode(node)); + return literalTypeToNode(type as FreshableType, node, tracker); + } - function isImportRequiredByAugmentation(node: ImportDeclaration) { - const file = getSourceFileOfNode(node); - if (!file.symbol) return false; - const importTarget = getExternalModuleFileFromDeclaration(node); - if (!importTarget) return false; - if (importTarget === file) return false; - const exports = getExportsOfModule(file.symbol); - for (const s of arrayFrom(exports.values())) { - if (s.mergeId) { - const merged = getMergedSymbol(s); - if (merged.declarations) { - for (const d of merged.declarations) { - const declFile = getSourceFileOfNode(d); - if (declFile === importTarget) { - return true; - } - } - } - } + function getJsxFactoryEntity(location: Node): EntityName | undefined { + return location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity; + } + + function getJsxFragmentFactoryEntity(location: Node): EntityName | undefined { + if (location) { + const file = getSourceFileOfNode(location); + if (file) { + if (file.localJsxFragmentFactory) { + return file.localJsxFragmentFactory; + } + const jsxFragPragmas = file.pragmas.get("jsxfrag"); + const jsxFragPragma = isArray(jsxFragPragmas) ? jsxFragPragmas[0] : jsxFragPragmas; + if (jsxFragPragma) { + file.localJsxFragmentFactory = parseIsolatedEntityName(jsxFragPragma.arguments.factory, languageVersion); + return file.localJsxFragmentFactory; } - return false; } + } - function isInHeritageClause(node: PropertyAccessEntityNameExpression) { - return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.parent && node.parent.parent.kind === SyntaxKind.HeritageClause; - } + if (compilerOptions.jsxFragmentFactory) { + return parseIsolatedEntityName(compilerOptions.jsxFragmentFactory, languageVersion); + } + } - // defined here to avoid outer scope pollution - function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined { - // program does not have any files with type reference directives - bail out - if (!fileToDirective) { - return undefined; - } - // computed property name should use node as value - // property access can only be used as values, or types when within an expression with type arguments inside a heritage clause - // qualified names can only be used as types\namespaces - // identifiers are treated as values only if they appear in type queries - let meaning; - if (node.parent.kind === SyntaxKind.ComputedPropertyName) { - meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + function createResolver(): EmitResolver { + // this variable and functions that use it are deliberately moved here from the outer scope + // to avoid scope pollution + const resolvedTypeReferenceDirectives = host.getResolvedTypeReferenceDirectives(); + let fileToDirective: ESMap; + if (resolvedTypeReferenceDirectives) { + // populate reverse mapping: file path -> type reference directive that was resolved to this file + fileToDirective = new Map(); + resolvedTypeReferenceDirectives.forEach((resolvedDirective, key, mode) => { + if (!resolvedDirective || !resolvedDirective.resolvedFileName) { + return; } - else { - meaning = SymbolFlags.Type | SymbolFlags.Namespace; - if ((node.kind === SyntaxKind.Identifier && isInTypeQuery(node)) || (node.kind === SyntaxKind.PropertyAccessExpression && !isInHeritageClause(node))) { - meaning = SymbolFlags.Value | SymbolFlags.ExportValue; - } + const file = host.getSourceFile(resolvedDirective.resolvedFileName); + if (file) { + // Add the transitive closure of path references loaded by this file (as long as they are not) + // part of an existing type reference. + addReferencedFilesToTypeDirective(file, key, mode); } + }); + } - const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); - return symbol && symbol !== unknownSymbol ? getTypeReferenceDirectivesForSymbol(symbol, meaning) : undefined; - } + return { + getReferencedExportContainer, + getReferencedImportDeclaration, + getReferencedDeclarationWithCollidingName, + isDeclarationWithCollidingName, + isValueAliasDeclaration: nodeIn => { + const node = getParseTreeNode(nodeIn); + // Synthesized nodes are always treated like values. + return node ? isValueAliasDeclaration(node) : true; + }, + hasGlobalName, + isReferencedAliasDeclaration: (nodeIn, checkChildren?) => { + const node = getParseTreeNode(nodeIn); + // Synthesized nodes are always treated as referenced. + return node ? isReferencedAliasDeclaration(node, checkChildren) : true; + }, + getNodeCheckFlags: nodeIn => { + const node = getParseTreeNode(nodeIn); + return node ? getNodeCheckFlags(node) : 0; + }, + isTopLevelValueImportEqualsWithEntityName, + isDeclarationVisible, + isImplementationOfOverload, + isRequiredInitializedParameter, + isOptionalUninitializedParameterProperty, + isExpandoFunctionDeclaration, + getPropertiesOfContainerFunction, + createTypeOfDeclaration, + createReturnTypeOfSignatureDeclaration, + createTypeOfExpression, + createLiteralConstValue, + isSymbolAccessible, + isEntityNameVisible, + getConstantValue: nodeIn => { + const node = getParseTreeNode(nodeIn, canHaveConstantValue); + return node ? getConstantValue(node) : undefined; + }, + collectLinkedAliases, + getReferencedValueDeclaration, + getTypeReferenceSerializationKind, + isOptionalParameter, + moduleExportsSomeValue, + isArgumentsLocalBinding, + getExternalModuleFileFromDeclaration: nodeIn => { + const node = getParseTreeNode(nodeIn, hasPossibleExternalModuleReference); + return node && getExternalModuleFileFromDeclaration(node); + }, + getTypeReferenceDirectivesForEntityName, + getTypeReferenceDirectivesForSymbol, + isLiteralConstDeclaration, + isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => { + const node = getParseTreeNode(nodeIn, isDeclaration); + const symbol = node && getSymbolOfNode(node); + return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late); + }, + getJsxFactoryEntity, + getJsxFragmentFactoryEntity, + getAllAccessorDeclarations(accessor: AccessorDeclaration): AllAccessorDeclarations { + accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217 + const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfNode(accessor), otherKind); + const firstAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? otherAccessor : accessor; + const secondAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? accessor : otherAccessor; + const setAccessor = accessor.kind === SyntaxKind.SetAccessor ? accessor : otherAccessor as SetAccessorDeclaration; + const getAccessor = accessor.kind === SyntaxKind.GetAccessor ? accessor : otherAccessor as GetAccessorDeclaration; + return { + firstAccessor, + secondAccessor, + setAccessor, + getAccessor + }; + }, + getSymbolOfExternalModuleSpecifier: moduleName => resolveExternalModuleNameWorker(moduleName, moduleName, /*moduleNotFoundError*/ undefined), + isBindingCapturedByNode: (node, decl) => { + const parseNode = getParseTreeNode(node); + const parseDecl = getParseTreeNode(decl); + return !!parseNode && !!parseDecl && (isVariableDeclaration(parseDecl) || isBindingElement(parseDecl)) && isBindingCapturedByNode(parseNode, parseDecl); + }, + getDeclarationStatementsForSourceFile: (node, flags, tracker, bundled) => { + const n = getParseTreeNode(node) as SourceFile; + Debug.assert(n && n.kind === SyntaxKind.SourceFile, "Non-sourcefile node passed into getDeclarationsForSourceFile"); + const sym = getSymbolOfNode(node); + if (!sym) { + return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker, bundled); + } + return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled); + }, + isImportRequiredByAugmentation, + }; - // defined here to avoid outer scope pollution - function getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined { - // program does not have any files with type reference directives - bail out - if (!fileToDirective || !isSymbolFromTypeDeclarationFile(symbol)) { - return undefined; - } - // check what declarations in the symbol can contribute to the target meaning - let typeReferenceDirectives: [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined; - for (const decl of symbol.declarations!) { - // check meaning of the local symbol to see if declaration needs to be analyzed further - if (decl.symbol && decl.symbol.flags & meaning!) { - const file = getSourceFileOfNode(decl); - const typeReferenceDirective = fileToDirective.get(file.path); - if (typeReferenceDirective) { - (typeReferenceDirectives || (typeReferenceDirectives = [])).push(typeReferenceDirective); - } - else { - // found at least one entry that does not originate from type reference directive - return undefined; + function isImportRequiredByAugmentation(node: ImportDeclaration) { + const file = getSourceFileOfNode(node); + if (!file.symbol) return false; + const importTarget = getExternalModuleFileFromDeclaration(node); + if (!importTarget) return false; + if (importTarget === file) return false; + const exports = getExportsOfModule(file.symbol); + for (const s of arrayFrom(exports.values())) { + if (s.mergeId) { + const merged = getMergedSymbol(s); + if (merged.declarations) { + for (const d of merged.declarations) { + const declFile = getSourceFileOfNode(d); + if (declFile === importTarget) { + return true; + } } } } - return typeReferenceDirectives; } + return false; + } - function isSymbolFromTypeDeclarationFile(symbol: Symbol): boolean { - // bail out if symbol does not have associated declarations (i.e. this is transient symbol created for property in binding pattern) - if (!symbol.declarations) { - return false; + function isInHeritageClause(node: PropertyAccessEntityNameExpression) { + return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.parent && node.parent.parent.kind === SyntaxKind.HeritageClause; + } + + // defined here to avoid outer scope pollution + function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined { + // program does not have any files with type reference directives - bail out + if (!fileToDirective) { + return undefined; + } + // computed property name should use node as value + // property access can only be used as values, or types when within an expression with type arguments inside a heritage clause + // qualified names can only be used as types\namespaces + // identifiers are treated as values only if they appear in type queries + let meaning; + if (node.parent.kind === SyntaxKind.ComputedPropertyName) { + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + } + else { + meaning = SymbolFlags.Type | SymbolFlags.Namespace; + if ((node.kind === SyntaxKind.Identifier && isInTypeQuery(node)) || (node.kind === SyntaxKind.PropertyAccessExpression && !isInHeritageClause(node))) { + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; } + } - // walk the parent chain for symbols to make sure that top level parent symbol is in the global scope - // external modules cannot define or contribute to type declaration files - let current = symbol; - while (true) { - const parent = getParentOfSymbol(current); - if (parent) { - current = parent; + const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); + return symbol && symbol !== unknownSymbol ? getTypeReferenceDirectivesForSymbol(symbol, meaning) : undefined; + } + + // defined here to avoid outer scope pollution + function getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined { + // program does not have any files with type reference directives - bail out + if (!fileToDirective || !isSymbolFromTypeDeclarationFile(symbol)) { + return undefined; + } + // check what declarations in the symbol can contribute to the target meaning + let typeReferenceDirectives: [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined; + for (const decl of symbol.declarations!) { + // check meaning of the local symbol to see if declaration needs to be analyzed further + if (decl.symbol && decl.symbol.flags & meaning!) { + const file = getSourceFileOfNode(decl); + const typeReferenceDirective = fileToDirective.get(file.path); + if (typeReferenceDirective) { + (typeReferenceDirectives || (typeReferenceDirectives = [])).push(typeReferenceDirective); } else { - break; + // found at least one entry that does not originate from type reference directive + return undefined; } } + } + return typeReferenceDirectives; + } - if (current.valueDeclaration && current.valueDeclaration.kind === SyntaxKind.SourceFile && current.flags & SymbolFlags.ValueModule) { - return false; - } + function isSymbolFromTypeDeclarationFile(symbol: Symbol): boolean { + // bail out if symbol does not have associated declarations (i.e. this is transient symbol created for property in binding pattern) + if (!symbol.declarations) { + return false; + } - // check that at least one declaration of top level symbol originates from type declaration file - for (const decl of symbol.declarations) { - const file = getSourceFileOfNode(decl); - if (fileToDirective.has(file.path)) { - return true; - } + // walk the parent chain for symbols to make sure that top level parent symbol is in the global scope + // external modules cannot define or contribute to type declaration files + let current = symbol; + while (true) { + const parent = getParentOfSymbol(current); + if (parent) { + current = parent; } + else { + break; + } + } + + if (current.valueDeclaration && current.valueDeclaration.kind === SyntaxKind.SourceFile && current.flags & SymbolFlags.ValueModule) { return false; } - function addReferencedFilesToTypeDirective(file: SourceFile, key: string, mode: SourceFile["impliedNodeFormat"] | undefined) { - if (fileToDirective.has(file.path)) return; - fileToDirective.set(file.path, [key, mode]); - for (const { fileName, resolutionMode } of file.referencedFiles) { - const resolvedFile = resolveTripleslashReference(fileName, file.fileName); - const referencedFile = host.getSourceFile(resolvedFile); - if (referencedFile) { - addReferencedFilesToTypeDirective(referencedFile, key, resolutionMode || file.impliedNodeFormat); - } + // check that at least one declaration of top level symbol originates from type declaration file + for (const decl of symbol.declarations) { + const file = getSourceFileOfNode(decl); + if (fileToDirective.has(file.path)) { + return true; } } + return false; } - function getExternalModuleFileFromDeclaration(declaration: AnyImportOrReExport | ModuleDeclaration | ImportTypeNode | ImportCall): SourceFile | undefined { - const specifier = declaration.kind === SyntaxKind.ModuleDeclaration ? tryCast(declaration.name, isStringLiteral) : getExternalModuleName(declaration); - const moduleSymbol = resolveExternalModuleNameWorker(specifier!, specifier!, /*moduleNotFoundError*/ undefined); // TODO: GH#18217 - if (!moduleSymbol) { - return undefined; + function addReferencedFilesToTypeDirective(file: SourceFile, key: string, mode: SourceFile["impliedNodeFormat"] | undefined) { + if (fileToDirective.has(file.path)) return; + fileToDirective.set(file.path, [key, mode]); + for (const { fileName, resolutionMode } of file.referencedFiles) { + const resolvedFile = resolveTripleslashReference(fileName, file.fileName); + const referencedFile = host.getSourceFile(resolvedFile); + if (referencedFile) { + addReferencedFilesToTypeDirective(referencedFile, key, resolutionMode || file.impliedNodeFormat); + } } - return getDeclarationOfKind(moduleSymbol, SyntaxKind.SourceFile); } + } - function initializeTypeChecker() { - // Bind all source files and propagate errors - for (const file of host.getSourceFiles()) { - bindSourceFile(file, compilerOptions); - } + function getExternalModuleFileFromDeclaration(declaration: AnyImportOrReExport | ModuleDeclaration | ImportTypeNode | ImportCall): SourceFile | undefined { + const specifier = declaration.kind === SyntaxKind.ModuleDeclaration ? tryCast(declaration.name, isStringLiteral) : getExternalModuleName(declaration); + const moduleSymbol = resolveExternalModuleNameWorker(specifier!, specifier!, /*moduleNotFoundError*/ undefined); // TODO: GH#18217 + if (!moduleSymbol) { + return undefined; + } + return getDeclarationOfKind(moduleSymbol, SyntaxKind.SourceFile); + } + + function initializeTypeChecker() { + // Bind all source files and propagate errors + for (const file of host.getSourceFiles()) { + bindSourceFile(file, compilerOptions); + } - amalgamatedDuplicates = new Map(); + amalgamatedDuplicates = new Map(); - // Initialize global symbol table - let augmentations: (readonly (StringLiteral | Identifier)[])[] | undefined; - for (const file of host.getSourceFiles()) { - if (file.redirectInfo) { - continue; - } - if (!isExternalOrCommonJsModule(file)) { - // It is an error for a non-external-module (i.e. script) to declare its own `globalThis`. - // We can't use `builtinGlobals` for this due to synthetic expando-namespace generation in JS files. - const fileGlobalThisSymbol = file.locals!.get("globalThis" as __String); - if (fileGlobalThisSymbol?.declarations) { - for (const declaration of fileGlobalThisSymbol.declarations) { - diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis")); - } + // Initialize global symbol table + let augmentations: (readonly (StringLiteral | Identifier)[])[] | undefined; + for (const file of host.getSourceFiles()) { + if (file.redirectInfo) { + continue; + } + if (!isExternalOrCommonJsModule(file)) { + // It is an error for a non-external-module (i.e. script) to declare its own `globalThis`. + // We can't use `builtinGlobals` for this due to synthetic expando-namespace generation in JS files. + const fileGlobalThisSymbol = file.locals!.get("globalThis" as __String); + if (fileGlobalThisSymbol?.declarations) { + for (const declaration of fileGlobalThisSymbol.declarations) { + diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis")); } - mergeSymbolTable(globals, file.locals!); - } - if (file.jsGlobalAugmentations) { - mergeSymbolTable(globals, file.jsGlobalAugmentations); - } - if (file.patternAmbientModules && file.patternAmbientModules.length) { - patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules); - } - if (file.moduleAugmentations.length) { - (augmentations || (augmentations = [])).push(file.moduleAugmentations); - } - if (file.symbol && file.symbol.globalExports) { - // Merge in UMD exports with first-in-wins semantics (see #9771) - const source = file.symbol.globalExports; - source.forEach((sourceSymbol, id) => { - if (!globals.has(id)) { - globals.set(id, sourceSymbol); - } - }); } + mergeSymbolTable(globals, file.locals!); } - - // We do global augmentations separately from module augmentations (and before creating global types) because they - // 1. Affect global types. We won't have the correct global types until global augmentations are merged. Also, - // 2. Module augmentation instantiation requires creating the type of a module, which, in turn, can require - // checking for an export or property on the module (if export=) which, in turn, can fall back to the - // apparent type of the module - either globalObjectType or globalFunctionType - which wouldn't exist if we - // did module augmentations prior to finalizing the global types. - if (augmentations) { - // merge _global_ module augmentations. - // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed - for (const list of augmentations) { - for (const augmentation of list) { - if (!isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; - mergeModuleAugmentation(augmentation); + if (file.jsGlobalAugmentations) { + mergeSymbolTable(globals, file.jsGlobalAugmentations); + } + if (file.patternAmbientModules && file.patternAmbientModules.length) { + patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules); + } + if (file.moduleAugmentations.length) { + (augmentations || (augmentations = [])).push(file.moduleAugmentations); + } + if (file.symbol && file.symbol.globalExports) { + // Merge in UMD exports with first-in-wins semantics (see #9771) + const source = file.symbol.globalExports; + source.forEach((sourceSymbol, id) => { + if (!globals.has(id)) { + globals.set(id, sourceSymbol); } + }); + } + } + + // We do global augmentations separately from module augmentations (and before creating global types) because they + // 1. Affect global types. We won't have the correct global types until global augmentations are merged. Also, + // 2. Module augmentation instantiation requires creating the type of a module, which, in turn, can require + // checking for an export or property on the module (if export=) which, in turn, can fall back to the + // apparent type of the module - either globalObjectType or globalFunctionType - which wouldn't exist if we + // did module augmentations prior to finalizing the global types. + if (augmentations) { + // merge _global_ module augmentations. + // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed + for (const list of augmentations) { + for (const augmentation of list) { + if (!isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; + mergeModuleAugmentation(augmentation); } } + } - // Setup global builtins - addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0); + // Setup global builtins + addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0); - getSymbolLinks(undefinedSymbol).type = undefinedWideningType; - getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments" as __String, /*arity*/ 0, /*reportErrors*/ true); - getSymbolLinks(unknownSymbol).type = errorType; - getSymbolLinks(globalThisSymbol).type = createObjectType(ObjectFlags.Anonymous, globalThisSymbol); + getSymbolLinks(undefinedSymbol).type = undefinedWideningType; + getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments" as __String, /*arity*/ 0, /*reportErrors*/ true); + getSymbolLinks(unknownSymbol).type = errorType; + getSymbolLinks(globalThisSymbol).type = createObjectType(ObjectFlags.Anonymous, globalThisSymbol); - // Initialize special types - globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true); - globalObjectType = getGlobalType("Object" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalFunctionType = getGlobalType("Function" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalCallableFunctionType = strictBindCallApply && getGlobalType("CallableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; - globalNewableFunctionType = strictBindCallApply && getGlobalType("NewableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; - globalStringType = getGlobalType("String" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalNumberType = getGlobalType("Number" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalBooleanType = getGlobalType("Boolean" as __String, /*arity*/ 0, /*reportErrors*/ true); - globalRegExpType = getGlobalType("RegExp" as __String, /*arity*/ 0, /*reportErrors*/ true); - anyArrayType = createArrayType(anyType); + // Initialize special types + globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true); + globalObjectType = getGlobalType("Object" as __String, /*arity*/ 0, /*reportErrors*/ true); + globalFunctionType = getGlobalType("Function" as __String, /*arity*/ 0, /*reportErrors*/ true); + globalCallableFunctionType = strictBindCallApply && getGlobalType("CallableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; + globalNewableFunctionType = strictBindCallApply && getGlobalType("NewableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; + globalStringType = getGlobalType("String" as __String, /*arity*/ 0, /*reportErrors*/ true); + globalNumberType = getGlobalType("Number" as __String, /*arity*/ 0, /*reportErrors*/ true); + globalBooleanType = getGlobalType("Boolean" as __String, /*arity*/ 0, /*reportErrors*/ true); + globalRegExpType = getGlobalType("RegExp" as __String, /*arity*/ 0, /*reportErrors*/ true); + anyArrayType = createArrayType(anyType); - autoArrayType = createArrayType(autoType); - if (autoArrayType === emptyObjectType) { - // autoArrayType is used as a marker, so even if global Array type is not defined, it needs to be a unique type - autoArrayType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - } + autoArrayType = createArrayType(autoType); + if (autoArrayType === emptyObjectType) { + // autoArrayType is used as a marker, so even if global Array type is not defined, it needs to be a unique type + autoArrayType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + } - globalReadonlyArrayType = getGlobalTypeOrUndefined("ReadonlyArray" as __String, /*arity*/ 1) as GenericType || globalArrayType; - anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType; - globalThisType = getGlobalTypeOrUndefined("ThisType" as __String, /*arity*/ 1) as GenericType; + globalReadonlyArrayType = getGlobalTypeOrUndefined("ReadonlyArray" as __String, /*arity*/ 1) as GenericType || globalArrayType; + anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType; + globalThisType = getGlobalTypeOrUndefined("ThisType" as __String, /*arity*/ 1) as GenericType; - if (augmentations) { - // merge _nonglobal_ module augmentations. - // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed - for (const list of augmentations) { - for (const augmentation of list) { - if (isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; - mergeModuleAugmentation(augmentation); - } + if (augmentations) { + // merge _nonglobal_ module augmentations. + // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed + for (const list of augmentations) { + for (const augmentation of list) { + if (isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; + mergeModuleAugmentation(augmentation); } } + } - amalgamatedDuplicates.forEach(({ firstFile, secondFile, conflictingSymbols }) => { - // If not many things conflict, issue individual errors - if (conflictingSymbols.size < 8) { - conflictingSymbols.forEach(({ isBlockScoped, firstFileLocations, secondFileLocations }, symbolName) => { - const message = isBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 : Diagnostics.Duplicate_identifier_0; - for (const node of firstFileLocations) { - addDuplicateDeclarationError(node, message, symbolName, secondFileLocations); - } - for (const node of secondFileLocations) { - addDuplicateDeclarationError(node, message, symbolName, firstFileLocations); - } - }); - } - else { - // Otherwise issue top-level error since the files appear very identical in terms of what they contain - const list = arrayFrom(conflictingSymbols.keys()).join(", "); - diagnostics.add(addRelatedInfo( - createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), - createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file) - )); - diagnostics.add(addRelatedInfo( - createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), - createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file) - )); - } - }); - amalgamatedDuplicates = undefined; - } - - function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) { - if ((requestedExternalEmitHelpers & helpers) !== helpers && compilerOptions.importHelpers) { - const sourceFile = getSourceFileOfNode(location); - if (isEffectiveExternalModule(sourceFile, compilerOptions) && !(location.flags & NodeFlags.Ambient)) { - const helpersModule = resolveHelpersModule(sourceFile, location); - if (helpersModule !== unknownSymbol) { - const uncheckedHelpers = helpers & ~requestedExternalEmitHelpers; - for (let helper = ExternalEmitHelpers.FirstEmitHelper; helper <= ExternalEmitHelpers.LastEmitHelper; helper <<= 1) { - if (uncheckedHelpers & helper) { - const name = getHelperName(helper); - const symbol = getSymbol(helpersModule.exports!, escapeLeadingUnderscores(name), SymbolFlags.Value); - if (!symbol) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_which_does_not_exist_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name); - } - else if (helper & ExternalEmitHelpers.ClassPrivateFieldGet) { - if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 3)) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 4); - } + amalgamatedDuplicates.forEach(({ firstFile, secondFile, conflictingSymbols }) => { + // If not many things conflict, issue individual errors + if (conflictingSymbols.size < 8) { + conflictingSymbols.forEach(({ isBlockScoped, firstFileLocations, secondFileLocations }, symbolName) => { + const message = isBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 : Diagnostics.Duplicate_identifier_0; + for (const node of firstFileLocations) { + addDuplicateDeclarationError(node, message, symbolName, secondFileLocations); + } + for (const node of secondFileLocations) { + addDuplicateDeclarationError(node, message, symbolName, firstFileLocations); + } + }); + } + else { + // Otherwise issue top-level error since the files appear very identical in terms of what they contain + const list = arrayFrom(conflictingSymbols.keys()).join(", "); + diagnostics.add(addRelatedInfo( + createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), + createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file) + )); + diagnostics.add(addRelatedInfo( + createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), + createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file) + )); + } + }); + amalgamatedDuplicates = undefined; + } + + function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) { + if ((requestedExternalEmitHelpers & helpers) !== helpers && compilerOptions.importHelpers) { + const sourceFile = getSourceFileOfNode(location); + if (isEffectiveExternalModule(sourceFile, compilerOptions) && !(location.flags & NodeFlags.Ambient)) { + const helpersModule = resolveHelpersModule(sourceFile, location); + if (helpersModule !== unknownSymbol) { + const uncheckedHelpers = helpers & ~requestedExternalEmitHelpers; + for (let helper = ExternalEmitHelpers.FirstEmitHelper; helper <= ExternalEmitHelpers.LastEmitHelper; helper <<= 1) { + if (uncheckedHelpers & helper) { + const name = getHelperName(helper); + const symbol = getSymbol(helpersModule.exports!, escapeLeadingUnderscores(name), SymbolFlags.Value); + if (!symbol) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_which_does_not_exist_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name); + } + else if (helper & ExternalEmitHelpers.ClassPrivateFieldGet) { + if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 3)) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 4); } - else if (helper & ExternalEmitHelpers.ClassPrivateFieldSet) { - if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 4)) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 5); - } + } + else if (helper & ExternalEmitHelpers.ClassPrivateFieldSet) { + if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 4)) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 5); } - else if (helper & ExternalEmitHelpers.SpreadArray) { - if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 2)) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 3); - } + } + else if (helper & ExternalEmitHelpers.SpreadArray) { + if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 2)) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 3); } } } } - requestedExternalEmitHelpers |= helpers; } + requestedExternalEmitHelpers |= helpers; } } + } - function getHelperName(helper: ExternalEmitHelpers) { - switch (helper) { - case ExternalEmitHelpers.Extends: return "__extends"; - case ExternalEmitHelpers.Assign: return "__assign"; - case ExternalEmitHelpers.Rest: return "__rest"; - case ExternalEmitHelpers.Decorate: return "__decorate"; - case ExternalEmitHelpers.Metadata: return "__metadata"; - case ExternalEmitHelpers.Param: return "__param"; - case ExternalEmitHelpers.Awaiter: return "__awaiter"; - case ExternalEmitHelpers.Generator: return "__generator"; - case ExternalEmitHelpers.Values: return "__values"; - case ExternalEmitHelpers.Read: return "__read"; - case ExternalEmitHelpers.SpreadArray: return "__spreadArray"; - case ExternalEmitHelpers.Await: return "__await"; - case ExternalEmitHelpers.AsyncGenerator: return "__asyncGenerator"; - case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator"; - case ExternalEmitHelpers.AsyncValues: return "__asyncValues"; - case ExternalEmitHelpers.ExportStar: return "__exportStar"; - case ExternalEmitHelpers.ImportStar: return "__importStar"; - case ExternalEmitHelpers.ImportDefault: return "__importDefault"; - case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; - case ExternalEmitHelpers.ClassPrivateFieldGet: return "__classPrivateFieldGet"; - case ExternalEmitHelpers.ClassPrivateFieldSet: return "__classPrivateFieldSet"; - case ExternalEmitHelpers.ClassPrivateFieldIn: return "__classPrivateFieldIn"; - case ExternalEmitHelpers.CreateBinding: return "__createBinding"; - default: return Debug.fail("Unrecognized helper"); - } + function getHelperName(helper: ExternalEmitHelpers) { + switch (helper) { + case ExternalEmitHelpers.Extends: return "__extends"; + case ExternalEmitHelpers.Assign: return "__assign"; + case ExternalEmitHelpers.Rest: return "__rest"; + case ExternalEmitHelpers.Decorate: return "__decorate"; + case ExternalEmitHelpers.Metadata: return "__metadata"; + case ExternalEmitHelpers.Param: return "__param"; + case ExternalEmitHelpers.Awaiter: return "__awaiter"; + case ExternalEmitHelpers.Generator: return "__generator"; + case ExternalEmitHelpers.Values: return "__values"; + case ExternalEmitHelpers.Read: return "__read"; + case ExternalEmitHelpers.SpreadArray: return "__spreadArray"; + case ExternalEmitHelpers.Await: return "__await"; + case ExternalEmitHelpers.AsyncGenerator: return "__asyncGenerator"; + case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator"; + case ExternalEmitHelpers.AsyncValues: return "__asyncValues"; + case ExternalEmitHelpers.ExportStar: return "__exportStar"; + case ExternalEmitHelpers.ImportStar: return "__importStar"; + case ExternalEmitHelpers.ImportDefault: return "__importDefault"; + case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; + case ExternalEmitHelpers.ClassPrivateFieldGet: return "__classPrivateFieldGet"; + case ExternalEmitHelpers.ClassPrivateFieldSet: return "__classPrivateFieldSet"; + case ExternalEmitHelpers.ClassPrivateFieldIn: return "__classPrivateFieldIn"; + case ExternalEmitHelpers.CreateBinding: return "__createBinding"; + default: return Debug.fail("Unrecognized helper"); } + } - function resolveHelpersModule(node: SourceFile, errorNode: Node) { - if (!externalHelpersModule) { - externalHelpersModule = resolveExternalModule(node, externalHelpersModuleNameText, Diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode) || unknownSymbol; - } - return externalHelpersModule; + function resolveHelpersModule(node: SourceFile, errorNode: Node) { + if (!externalHelpersModule) { + externalHelpersModule = resolveExternalModule(node, externalHelpersModuleNameText, Diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode) || unknownSymbol; } + return externalHelpersModule; + } - // GRAMMAR CHECKING - function checkGrammarDecoratorsAndModifiers(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators): boolean { - return checkGrammarDecorators(node) || checkGrammarModifiers(node); - } + // GRAMMAR CHECKING + function checkGrammarDecoratorsAndModifiers(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators): boolean { + return checkGrammarDecorators(node) || checkGrammarModifiers(node); + } - function checkGrammarDecorators(node: Node): boolean { - if (canHaveIllegalDecorators(node) && some(node.illegalDecorators)) { + function checkGrammarDecorators(node: Node): boolean { + if (canHaveIllegalDecorators(node) && some(node.illegalDecorators)) { + return grammarErrorOnFirstToken(node, Diagnostics.Decorators_are_not_valid_here); + } + if (!canHaveDecorators(node) || !hasDecorators(node)) { + return false; + } + if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { + if (node.kind === SyntaxKind.MethodDeclaration && !nodeIsPresent(node.body)) { + return grammarErrorOnFirstToken(node, Diagnostics.A_decorator_can_only_decorate_a_method_implementation_not_an_overload); + } + else { return grammarErrorOnFirstToken(node, Diagnostics.Decorators_are_not_valid_here); } - if (!canHaveDecorators(node) || !hasDecorators(node)) { - return false; + } + else if (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) { + const accessors = getAllAccessorDeclarations((node.parent as ClassDeclaration).members, node as AccessorDeclaration); + if (hasDecorators(accessors.firstAccessor) && node === accessors.secondAccessor) { + return grammarErrorOnFirstToken(node, Diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name); } - if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { - if (node.kind === SyntaxKind.MethodDeclaration && !nodeIsPresent(node.body)) { - return grammarErrorOnFirstToken(node, Diagnostics.A_decorator_can_only_decorate_a_method_implementation_not_an_overload); + } + return false; + } + + function checkGrammarModifiers(node: HasModifiers | HasIllegalModifiers): boolean { + const quickResult = reportObviousModifierErrors(node); + if (quickResult !== undefined) { + return quickResult; + } + + let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastOverride: Node | undefined; + let flags = ModifierFlags.None; + for (const modifier of node.modifiers!) { + if (isDecorator(modifier)) continue; + if (modifier.kind !== SyntaxKind.ReadonlyKeyword) { + if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_member, tokenToString(modifier.kind)); } - else { - return grammarErrorOnFirstToken(node, Diagnostics.Decorators_are_not_valid_here); + if (node.kind === SyntaxKind.IndexSignature && (modifier.kind !== SyntaxKind.StaticKeyword || !isClassLike(node.parent))) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind)); } } - else if (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) { - const accessors = getAllAccessorDeclarations((node.parent as ClassDeclaration).members, node as AccessorDeclaration); - if (hasDecorators(accessors.firstAccessor) && node === accessors.secondAccessor) { - return grammarErrorOnFirstToken(node, Diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name); + if (modifier.kind !== SyntaxKind.InKeyword && modifier.kind !== SyntaxKind.OutKeyword) { + if (node.kind === SyntaxKind.TypeParameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_parameter, tokenToString(modifier.kind)); } } - return false; - } - - function checkGrammarModifiers(node: HasModifiers | HasIllegalModifiers): boolean { - const quickResult = reportObviousModifierErrors(node); - if (quickResult !== undefined) { - return quickResult; - } - - let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastOverride: Node | undefined; - let flags = ModifierFlags.None; - for (const modifier of node.modifiers!) { - if (isDecorator(modifier)) continue; - if (modifier.kind !== SyntaxKind.ReadonlyKeyword) { - if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_member, tokenToString(modifier.kind)); + switch (modifier.kind) { + case SyntaxKind.ConstKeyword: + if (node.kind !== SyntaxKind.EnumDeclaration) { + return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword)); } - if (node.kind === SyntaxKind.IndexSignature && (modifier.kind !== SyntaxKind.StaticKeyword || !isClassLike(node.parent))) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind)); + break; + case SyntaxKind.OverrideKeyword: + // If node.kind === SyntaxKind.Parameter, checkParameter reports an error if it's not a parameter property. + if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "override"); } - } - if (modifier.kind !== SyntaxKind.InKeyword && modifier.kind !== SyntaxKind.OutKeyword) { - if (node.kind === SyntaxKind.TypeParameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_parameter, tokenToString(modifier.kind)); + else if (flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "override", "declare"); } - } - switch (modifier.kind) { - case SyntaxKind.ConstKeyword: - if (node.kind !== SyntaxKind.EnumDeclaration) { - return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword)); - } - break; - case SyntaxKind.OverrideKeyword: - // If node.kind === SyntaxKind.Parameter, checkParameter reports an error if it's not a parameter property. - if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "override"); - } - else if (flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "override", "declare"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "readonly"); - } - else if (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "accessor"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "async"); - } - flags |= ModifierFlags.Override; - lastOverride = modifier; - break; - - case SyntaxKind.PublicKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.PrivateKeyword: - const text = visibilityToString(modifierToFlag(modifier.kind)); - - if (flags & ModifierFlags.AccessibilityModifier) { - return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen); - } - else if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "override"); - } - else if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static"); - } - else if (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "accessor"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "readonly"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "async"); - } - else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, text); - } - else if (flags & ModifierFlags.Abstract) { - if (modifier.kind === SyntaxKind.PrivateKeyword) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, text, "abstract"); - } - else { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "abstract"); - } - } - else if (isPrivateIdentifierClassElementDeclaration(node)) { - return grammarErrorOnNode(modifier, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); - } - flags |= modifierToFlag(modifier.kind); - break; + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "readonly"); + } + else if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "accessor"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "async"); + } + flags |= ModifierFlags.Override; + lastOverride = modifier; + break; - case SyntaxKind.StaticKeyword: - if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "static"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async"); - } - else if (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "accessor"); - } - else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, "static"); - } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "static"); - } - else if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); - } - else if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "override"); - } - flags |= ModifierFlags.Static; - lastStatic = modifier; - break; + case SyntaxKind.PublicKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PrivateKeyword: + const text = visibilityToString(modifierToFlag(modifier.kind)); - case SyntaxKind.AccessorKeyword: - if (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "accessor"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "accessor", "readonly"); - } - else if (flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "accessor", "declare"); + if (flags & ModifierFlags.AccessibilityModifier) { + return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen); + } + else if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "override"); + } + else if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static"); + } + else if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "accessor"); + } + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "readonly"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "async"); + } + else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, text); + } + else if (flags & ModifierFlags.Abstract) { + if (modifier.kind === SyntaxKind.PrivateKeyword) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, text, "abstract"); } - else if (node.kind !== SyntaxKind.PropertyDeclaration) { - return grammarErrorOnNode(modifier, Diagnostics.accessor_modifier_can_only_appear_on_a_property_declaration); + else { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "abstract"); } + } + else if (isPrivateIdentifierClassElementDeclaration(node)) { + return grammarErrorOnNode(modifier, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); + } + flags |= modifierToFlag(modifier.kind); + break; - flags |= ModifierFlags.Accessor; - break; + case SyntaxKind.StaticKeyword: + if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "static"); + } + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async"); + } + else if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "accessor"); + } + else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, "static"); + } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "static"); + } + else if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); + } + else if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "override"); + } + flags |= ModifierFlags.Static; + lastStatic = modifier; + break; - case SyntaxKind.ReadonlyKeyword: - if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly"); - } - else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.Parameter) { - // If node.kind === SyntaxKind.Parameter, checkParameter reports an error if it's not a parameter property. - return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature); - } - flags |= ModifierFlags.Readonly; - break; + case SyntaxKind.AccessorKeyword: + if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "accessor"); + } + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "accessor", "readonly"); + } + else if (flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "accessor", "declare"); + } + else if (node.kind !== SyntaxKind.PropertyDeclaration) { + return grammarErrorOnNode(modifier, Diagnostics.accessor_modifier_can_only_appear_on_a_property_declaration); + } - case SyntaxKind.ExportKeyword: - if (flags & ModifierFlags.Export) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export"); - } - else if (flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "declare"); - } - else if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "abstract"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "async"); - } - else if (isClassLike(node.parent)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "export"); - } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "export"); - } - flags |= ModifierFlags.Export; - break; - case SyntaxKind.DefaultKeyword: - const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent; - if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { - return grammarErrorOnNode(modifier, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); - } - else if (!(flags & ModifierFlags.Export)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "default"); - } + flags |= ModifierFlags.Accessor; + break; - flags |= ModifierFlags.Default; - break; - case SyntaxKind.DeclareKeyword: - if (flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "declare"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); - } - else if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "override"); - } - else if (isClassLike(node.parent) && !isPropertyDeclaration(node)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "declare"); - } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "declare"); - } - else if ((node.parent.flags & NodeFlags.Ambient) && node.parent.kind === SyntaxKind.ModuleBlock) { - return grammarErrorOnNode(modifier, Diagnostics.A_declare_modifier_cannot_be_used_in_an_already_ambient_context); - } - else if (isPrivateIdentifierClassElementDeclaration(node)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "declare"); - } - flags |= ModifierFlags.Ambient; - lastDeclare = modifier; - break; + case SyntaxKind.ReadonlyKeyword: + if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly"); + } + else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.Parameter) { + // If node.kind === SyntaxKind.Parameter, checkParameter reports an error if it's not a parameter property. + return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature); + } + flags |= ModifierFlags.Readonly; + break; - case SyntaxKind.AbstractKeyword: - if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract"); - } - if (node.kind !== SyntaxKind.ClassDeclaration && - node.kind !== SyntaxKind.ConstructorType) { - if (node.kind !== SyntaxKind.MethodDeclaration && - node.kind !== SyntaxKind.PropertyDeclaration && - node.kind !== SyntaxKind.GetAccessor && - node.kind !== SyntaxKind.SetAccessor) { - return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration); - } - if (!(node.parent.kind === SyntaxKind.ClassDeclaration && hasSyntacticModifier(node.parent, ModifierFlags.Abstract))) { - return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class); - } - if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); - } - if (flags & ModifierFlags.Private) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "private", "abstract"); - } - if (flags & ModifierFlags.Async && lastAsync) { - return grammarErrorOnNode(lastAsync, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "async", "abstract"); - } - if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "abstract", "override"); - } - if (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "abstract", "accessor"); - } - } - if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.PrivateIdentifier) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "abstract"); - } + case SyntaxKind.ExportKeyword: + if (flags & ModifierFlags.Export) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export"); + } + else if (flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "declare"); + } + else if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "abstract"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "async"); + } + else if (isClassLike(node.parent)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "export"); + } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "export"); + } + flags |= ModifierFlags.Export; + break; + case SyntaxKind.DefaultKeyword: + const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent; + if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { + return grammarErrorOnNode(modifier, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); + } + else if (!(flags & ModifierFlags.Export)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "default"); + } - flags |= ModifierFlags.Abstract; - break; + flags |= ModifierFlags.Default; + break; + case SyntaxKind.DeclareKeyword: + if (flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "declare"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); + } + else if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "override"); + } + else if (isClassLike(node.parent) && !isPropertyDeclaration(node)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "declare"); + } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "declare"); + } + else if ((node.parent.flags & NodeFlags.Ambient) && node.parent.kind === SyntaxKind.ModuleBlock) { + return grammarErrorOnNode(modifier, Diagnostics.A_declare_modifier_cannot_be_used_in_an_already_ambient_context); + } + else if (isPrivateIdentifierClassElementDeclaration(node)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "declare"); + } + flags |= ModifierFlags.Ambient; + lastDeclare = modifier; + break; - case SyntaxKind.AsyncKeyword: - if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "async"); + case SyntaxKind.AbstractKeyword: + if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract"); + } + if (node.kind !== SyntaxKind.ClassDeclaration && + node.kind !== SyntaxKind.ConstructorType) { + if (node.kind !== SyntaxKind.MethodDeclaration && + node.kind !== SyntaxKind.PropertyDeclaration && + node.kind !== SyntaxKind.GetAccessor && + node.kind !== SyntaxKind.SetAccessor) { + return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration); } - else if (flags & ModifierFlags.Ambient || node.parent.flags & NodeFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); + if (!(node.parent.kind === SyntaxKind.ClassDeclaration && hasSyntacticModifier(node.parent, ModifierFlags.Abstract))) { + return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class); } - else if (node.kind === SyntaxKind.Parameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "async"); + if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); } - if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "async", "abstract"); + if (flags & ModifierFlags.Private) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "private", "abstract"); } - flags |= ModifierFlags.Async; - lastAsync = modifier; - break; - - case SyntaxKind.InKeyword: - case SyntaxKind.OutKeyword: - const inOutFlag = modifier.kind === SyntaxKind.InKeyword ? ModifierFlags.In : ModifierFlags.Out; - const inOutText = modifier.kind === SyntaxKind.InKeyword ? "in" : "out"; - if (node.kind !== SyntaxKind.TypeParameter || !(isInterfaceDeclaration(node.parent) || isClassLike(node.parent) || isTypeAliasDeclaration(node.parent))) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_can_only_appear_on_a_type_parameter_of_a_class_interface_or_type_alias, inOutText); + if (flags & ModifierFlags.Async && lastAsync) { + return grammarErrorOnNode(lastAsync, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "async", "abstract"); } - if (flags & inOutFlag) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, inOutText); + if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "abstract", "override"); } - if (inOutFlag & ModifierFlags.In && flags & ModifierFlags.Out) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "in", "out"); + if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "abstract", "accessor"); } - flags |= inOutFlag; - break; - } - } + } + if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.PrivateIdentifier) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "abstract"); + } - if (node.kind === SyntaxKind.Constructor) { - if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "static"); - } - if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(lastOverride!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "override"); // TODO: GH#18217 - } - if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); - } - return false; - } - else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(lastDeclare!, Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare"); - } - else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && isBindingPattern(node.name)) { - return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_declared_using_a_binding_pattern); - } - else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && node.dotDotDotToken) { - return grammarErrorOnNode(node, Diagnostics.A_parameter_property_cannot_be_declared_using_a_rest_parameter); - } - if (flags & ModifierFlags.Async) { - return checkGrammarAsyncModifier(node, lastAsync!); - } - return false; - } + flags |= ModifierFlags.Abstract; + break; - /** - * true | false: Early return this value from checkGrammarModifiers. - * undefined: Need to do full checking on the modifiers. - */ - function reportObviousModifierErrors(node: HasModifiers | HasIllegalModifiers): boolean | undefined { - return !node.modifiers - ? false - : shouldReportBadModifier(node) - ? grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here) - : undefined; - } + case SyntaxKind.AsyncKeyword: + if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "async"); + } + else if (flags & ModifierFlags.Ambient || node.parent.flags & NodeFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); + } + else if (node.kind === SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "async"); + } + if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "async", "abstract"); + } + flags |= ModifierFlags.Async; + lastAsync = modifier; + break; - function shouldReportBadModifier(node: HasModifiers | HasIllegalModifiers): boolean { - switch (node.kind) { - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.Constructor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExportAssignment: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.Parameter: - case SyntaxKind.TypeParameter: - return false; - case SyntaxKind.ClassStaticBlockDeclaration: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.NamespaceExportDeclaration: - case SyntaxKind.FunctionType: - case SyntaxKind.MissingDeclaration: - return true; - default: - if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - return false; + case SyntaxKind.InKeyword: + case SyntaxKind.OutKeyword: + const inOutFlag = modifier.kind === SyntaxKind.InKeyword ? ModifierFlags.In : ModifierFlags.Out; + const inOutText = modifier.kind === SyntaxKind.InKeyword ? "in" : "out"; + if (node.kind !== SyntaxKind.TypeParameter || !(isInterfaceDeclaration(node.parent) || isClassLike(node.parent) || isTypeAliasDeclaration(node.parent))) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_can_only_appear_on_a_type_parameter_of_a_class_interface_or_type_alias, inOutText); } - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - return nodeHasAnyModifiersExcept(node, SyntaxKind.AsyncKeyword); - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ConstructorType: - return nodeHasAnyModifiersExcept(node, SyntaxKind.AbstractKeyword); - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.VariableStatement: - case SyntaxKind.TypeAliasDeclaration: - return true; - case SyntaxKind.EnumDeclaration: - return nodeHasAnyModifiersExcept(node, SyntaxKind.ConstKeyword); - default: - Debug.assertNever(node); + if (flags & inOutFlag) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, inOutText); } + if (inOutFlag & ModifierFlags.In && flags & ModifierFlags.Out) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "in", "out"); + } + flags |= inOutFlag; + break; } } - function nodeHasAnyModifiersExcept(node: HasModifiers, allowedModifier: SyntaxKind): boolean { - for (const modifier of node.modifiers!) { - if (isDecorator(modifier)) continue; - return modifier.kind !== allowedModifier; + if (node.kind === SyntaxKind.Constructor) { + if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "static"); + } + if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(lastOverride!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "override"); // TODO: GH#18217 + } + if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); } return false; } + else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(lastDeclare!, Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare"); + } + else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && isBindingPattern(node.name)) { + return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_declared_using_a_binding_pattern); + } + else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && node.dotDotDotToken) { + return grammarErrorOnNode(node, Diagnostics.A_parameter_property_cannot_be_declared_using_a_rest_parameter); + } + if (flags & ModifierFlags.Async) { + return checkGrammarAsyncModifier(node, lastAsync!); + } + return false; + } - function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean { - switch (node.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: + /** + * true | false: Early return this value from checkGrammarModifiers. + * undefined: Need to do full checking on the modifiers. + */ + function reportObviousModifierErrors(node: HasModifiers | HasIllegalModifiers): boolean | undefined { + return !node.modifiers + ? false + : shouldReportBadModifier(node) + ? grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here) + : undefined; + } + + function shouldReportBadModifier(node: HasModifiers | HasIllegalModifiers): boolean { + switch (node.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.Constructor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExportAssignment: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.Parameter: + case SyntaxKind.TypeParameter: + return false; + case SyntaxKind.ClassStaticBlockDeclaration: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.NamespaceExportDeclaration: + case SyntaxKind.FunctionType: + case SyntaxKind.MissingDeclaration: + return true; + default: + if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { return false; - } + } + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return nodeHasAnyModifiersExcept(node, SyntaxKind.AsyncKeyword); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ConstructorType: + return nodeHasAnyModifiersExcept(node, SyntaxKind.AbstractKeyword); + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.VariableStatement: + case SyntaxKind.TypeAliasDeclaration: + return true; + case SyntaxKind.EnumDeclaration: + return nodeHasAnyModifiersExcept(node, SyntaxKind.ConstKeyword); + default: + Debug.assertNever(node); + } + } + } - return grammarErrorOnNode(asyncModifier, Diagnostics._0_modifier_cannot_be_used_here, "async"); + function nodeHasAnyModifiersExcept(node: HasModifiers, allowedModifier: SyntaxKind): boolean { + for (const modifier of node.modifiers!) { + if (isDecorator(modifier)) continue; + return modifier.kind !== allowedModifier; } + return false; + } - function checkGrammarForDisallowedTrailingComma(list: NodeArray | undefined, diag = Diagnostics.Trailing_comma_not_allowed): boolean { - if (list && list.hasTrailingComma) { - return grammarErrorAtPos(list[0], list.end - ",".length, ",".length, diag); - } - return false; + function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean { + switch (node.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return false; } - function checkGrammarTypeParameterList(typeParameters: NodeArray | undefined, file: SourceFile): boolean { - if (typeParameters && typeParameters.length === 0) { - const start = typeParameters.pos - "<".length; - const end = skipTrivia(file.text, typeParameters.end) + ">".length; - return grammarErrorAtPos(file, start, end - start, Diagnostics.Type_parameter_list_cannot_be_empty); - } - return false; + return grammarErrorOnNode(asyncModifier, Diagnostics._0_modifier_cannot_be_used_here, "async"); + } + + function checkGrammarForDisallowedTrailingComma(list: NodeArray | undefined, diag = Diagnostics.Trailing_comma_not_allowed): boolean { + if (list && list.hasTrailingComma) { + return grammarErrorAtPos(list[0], list.end - ",".length, ",".length, diag); } + return false; + } - function checkGrammarParameterList(parameters: NodeArray) { - let seenOptionalParameter = false; - const parameterCount = parameters.length; + function checkGrammarTypeParameterList(typeParameters: NodeArray | undefined, file: SourceFile): boolean { + if (typeParameters && typeParameters.length === 0) { + const start = typeParameters.pos - "<".length; + const end = skipTrivia(file.text, typeParameters.end) + ">".length; + return grammarErrorAtPos(file, start, end - start, Diagnostics.Type_parameter_list_cannot_be_empty); + } + return false; + } - for (let i = 0; i < parameterCount; i++) { - const parameter = parameters[i]; - if (parameter.dotDotDotToken) { - if (i !== (parameterCount - 1)) { - return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); - } - if (!(parameter.flags & NodeFlags.Ambient)) { // Allow `...foo,` in ambient declarations; see GH#23070 - checkGrammarForDisallowedTrailingComma(parameters, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - } + function checkGrammarParameterList(parameters: NodeArray) { + let seenOptionalParameter = false; + const parameterCount = parameters.length; - if (parameter.questionToken) { - return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional); - } + for (let i = 0; i < parameterCount; i++) { + const parameter = parameters[i]; + if (parameter.dotDotDotToken) { + if (i !== (parameterCount - 1)) { + return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); + } + if (!(parameter.flags & NodeFlags.Ambient)) { // Allow `...foo,` in ambient declarations; see GH#23070 + checkGrammarForDisallowedTrailingComma(parameters, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + } - if (parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer); - } + if (parameter.questionToken) { + return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional); } - else if (isOptionalParameter(parameter)) { - seenOptionalParameter = true; - if (parameter.questionToken && parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.Parameter_cannot_have_question_mark_and_initializer); - } + + if (parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer); } - else if (seenOptionalParameter && !parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.A_required_parameter_cannot_follow_an_optional_parameter); + } + else if (isOptionalParameter(parameter)) { + seenOptionalParameter = true; + if (parameter.questionToken && parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.Parameter_cannot_have_question_mark_and_initializer); } } + else if (seenOptionalParameter && !parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.A_required_parameter_cannot_follow_an_optional_parameter); + } } + } - function getNonSimpleParameters(parameters: readonly ParameterDeclaration[]): readonly ParameterDeclaration[] { - return filter(parameters, parameter => !!parameter.initializer || isBindingPattern(parameter.name) || isRestParameter(parameter)); - } + function getNonSimpleParameters(parameters: readonly ParameterDeclaration[]): readonly ParameterDeclaration[] { + return filter(parameters, parameter => !!parameter.initializer || isBindingPattern(parameter.name) || isRestParameter(parameter)); + } - function checkGrammarForUseStrictSimpleParameterList(node: FunctionLikeDeclaration): boolean { - if (languageVersion >= ScriptTarget.ES2016) { - const useStrictDirective = node.body && isBlock(node.body) && findUseStrictPrologue(node.body.statements); - if (useStrictDirective) { - const nonSimpleParameters = getNonSimpleParameters(node.parameters); - if (length(nonSimpleParameters)) { - forEach(nonSimpleParameters, parameter => { - addRelatedInfo( - error(parameter, Diagnostics.This_parameter_is_not_allowed_with_use_strict_directive), - createDiagnosticForNode(useStrictDirective, Diagnostics.use_strict_directive_used_here) - ); - }); + function checkGrammarForUseStrictSimpleParameterList(node: FunctionLikeDeclaration): boolean { + if (languageVersion >= ScriptTarget.ES2016) { + const useStrictDirective = node.body && isBlock(node.body) && findUseStrictPrologue(node.body.statements); + if (useStrictDirective) { + const nonSimpleParameters = getNonSimpleParameters(node.parameters); + if (length(nonSimpleParameters)) { + forEach(nonSimpleParameters, parameter => { + addRelatedInfo( + error(parameter, Diagnostics.This_parameter_is_not_allowed_with_use_strict_directive), + createDiagnosticForNode(useStrictDirective, Diagnostics.use_strict_directive_used_here) + ); + }); - const diagnostics = nonSimpleParameters.map((parameter, index) => ( - index === 0 ? createDiagnosticForNode(parameter, Diagnostics.Non_simple_parameter_declared_here) : createDiagnosticForNode(parameter, Diagnostics.and_here) - )) as [DiagnosticWithLocation, ...DiagnosticWithLocation[]]; - addRelatedInfo(error(useStrictDirective, Diagnostics.use_strict_directive_cannot_be_used_with_non_simple_parameter_list), ...diagnostics); - return true; - } + const diagnostics = nonSimpleParameters.map((parameter, index) => ( + index === 0 ? createDiagnosticForNode(parameter, Diagnostics.Non_simple_parameter_declared_here) : createDiagnosticForNode(parameter, Diagnostics.and_here) + )) as [DiagnosticWithLocation, ...DiagnosticWithLocation[]]; + addRelatedInfo(error(useStrictDirective, Diagnostics.use_strict_directive_cannot_be_used_with_non_simple_parameter_list), ...diagnostics); + return true; } } - return false; } + return false; + } - function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean { - // Prevent cascading error by short-circuit - const file = getSourceFileOfNode(node); - return checkGrammarDecoratorsAndModifiers(node) || - checkGrammarTypeParameterList(node.typeParameters, file) || - checkGrammarParameterList(node.parameters) || - checkGrammarArrowFunction(node, file) || - (isFunctionLikeDeclaration(node) && checkGrammarForUseStrictSimpleParameterList(node)); - } + function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean { + // Prevent cascading error by short-circuit + const file = getSourceFileOfNode(node); + return checkGrammarDecoratorsAndModifiers(node) || + checkGrammarTypeParameterList(node.typeParameters, file) || + checkGrammarParameterList(node.parameters) || + checkGrammarArrowFunction(node, file) || + (isFunctionLikeDeclaration(node) && checkGrammarForUseStrictSimpleParameterList(node)); + } - function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean { - const file = getSourceFileOfNode(node); - return checkGrammarClassDeclarationHeritageClauses(node) || - checkGrammarTypeParameterList(node.typeParameters, file); - } + function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean { + const file = getSourceFileOfNode(node); + return checkGrammarClassDeclarationHeritageClauses(node) || + checkGrammarTypeParameterList(node.typeParameters, file); + } - function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean { - if (!isArrowFunction(node)) { - return false; - } + function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean { + if (!isArrowFunction(node)) { + return false; + } - if (node.typeParameters && !(length(node.typeParameters) > 1 || node.typeParameters.hasTrailingComma || node.typeParameters[0].constraint)) { - if (file && fileExtensionIsOneOf(file.fileName, [Extension.Mts, Extension.Cts])) { - grammarErrorOnNode(node.typeParameters[0], Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Add_a_trailing_comma_or_explicit_constraint); - } + if (node.typeParameters && !(length(node.typeParameters) > 1 || node.typeParameters.hasTrailingComma || node.typeParameters[0].constraint)) { + if (file && fileExtensionIsOneOf(file.fileName, [Extension.Mts, Extension.Cts])) { + grammarErrorOnNode(node.typeParameters[0], Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Add_a_trailing_comma_or_explicit_constraint); } - - const { equalsGreaterThanToken } = node; - const startLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.pos).line; - const endLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.end).line; - return startLine !== endLine && grammarErrorOnNode(equalsGreaterThanToken, Diagnostics.Line_terminator_not_permitted_before_arrow); } - function checkGrammarIndexSignatureParameters(node: SignatureDeclaration): boolean { - const parameter = node.parameters[0]; - if (node.parameters.length !== 1) { - if (parameter) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_must_have_exactly_one_parameter); - } - else { - return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_exactly_one_parameter); - } - } - checkGrammarForDisallowedTrailingComma(node.parameters, Diagnostics.An_index_signature_cannot_have_a_trailing_comma); - if (parameter.dotDotDotToken) { - return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.An_index_signature_cannot_have_a_rest_parameter); - } - if (hasEffectiveModifiers(parameter)) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_accessibility_modifier); - } - if (parameter.questionToken) { - return grammarErrorOnNode(parameter.questionToken, Diagnostics.An_index_signature_parameter_cannot_have_a_question_mark); - } - if (parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_initializer); - } - if (!parameter.type) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_must_have_a_type_annotation); - } - const type = getTypeFromTypeNode(parameter.type); - if (someType(type, t => !!(t.flags & TypeFlags.StringOrNumberLiteralOrUnique)) || isGenericType(type)) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_cannot_be_a_literal_type_or_generic_type_Consider_using_a_mapped_object_type_instead); - } - if (!everyType(type, isValidIndexKeyType)) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_must_be_string_number_symbol_or_a_template_literal_type); + const { equalsGreaterThanToken } = node; + const startLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.pos).line; + const endLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.end).line; + return startLine !== endLine && grammarErrorOnNode(equalsGreaterThanToken, Diagnostics.Line_terminator_not_permitted_before_arrow); + } + + function checkGrammarIndexSignatureParameters(node: SignatureDeclaration): boolean { + const parameter = node.parameters[0]; + if (node.parameters.length !== 1) { + if (parameter) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_must_have_exactly_one_parameter); } - if (!node.type) { - return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation); + else { + return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_exactly_one_parameter); } - return false; } - - function checkGrammarIndexSignature(node: IndexSignatureDeclaration) { - // Prevent cascading error by short-circuit - return checkGrammarDecoratorsAndModifiers(node) || checkGrammarIndexSignatureParameters(node); + checkGrammarForDisallowedTrailingComma(node.parameters, Diagnostics.An_index_signature_cannot_have_a_trailing_comma); + if (parameter.dotDotDotToken) { + return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.An_index_signature_cannot_have_a_rest_parameter); } - - function checkGrammarForAtLeastOneTypeArgument(node: Node, typeArguments: NodeArray | undefined): boolean { - if (typeArguments && typeArguments.length === 0) { - const sourceFile = getSourceFileOfNode(node); - const start = typeArguments.pos - "<".length; - const end = skipTrivia(sourceFile.text, typeArguments.end) + ">".length; - return grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.Type_argument_list_cannot_be_empty); - } - return false; + if (hasEffectiveModifiers(parameter)) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_accessibility_modifier); } - - function checkGrammarTypeArguments(node: Node, typeArguments: NodeArray | undefined): boolean { - return checkGrammarForDisallowedTrailingComma(typeArguments) || - checkGrammarForAtLeastOneTypeArgument(node, typeArguments); + if (parameter.questionToken) { + return grammarErrorOnNode(parameter.questionToken, Diagnostics.An_index_signature_parameter_cannot_have_a_question_mark); + } + if (parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_initializer); } + if (!parameter.type) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_must_have_a_type_annotation); + } + const type = getTypeFromTypeNode(parameter.type); + if (someType(type, t => !!(t.flags & TypeFlags.StringOrNumberLiteralOrUnique)) || isGenericType(type)) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_cannot_be_a_literal_type_or_generic_type_Consider_using_a_mapped_object_type_instead); + } + if (!everyType(type, isValidIndexKeyType)) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_must_be_string_number_symbol_or_a_template_literal_type); + } + if (!node.type) { + return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation); + } + return false; + } - function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean { - if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) { - return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain); - } - return false; + function checkGrammarIndexSignature(node: IndexSignatureDeclaration) { + // Prevent cascading error by short-circuit + return checkGrammarDecoratorsAndModifiers(node) || checkGrammarIndexSignatureParameters(node); + } + + function checkGrammarForAtLeastOneTypeArgument(node: Node, typeArguments: NodeArray | undefined): boolean { + if (typeArguments && typeArguments.length === 0) { + const sourceFile = getSourceFileOfNode(node); + const start = typeArguments.pos - "<".length; + const end = skipTrivia(sourceFile.text, typeArguments.end) + ">".length; + return grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.Type_argument_list_cannot_be_empty); } + return false; + } - function checkGrammarHeritageClause(node: HeritageClause): boolean { - const types = node.types; - if (checkGrammarForDisallowedTrailingComma(types)) { - return true; - } - if (types && types.length === 0) { - const listType = tokenToString(node.token); - return grammarErrorAtPos(node, types.pos, 0, Diagnostics._0_list_cannot_be_empty, listType); - } - return some(types, checkGrammarExpressionWithTypeArguments); + function checkGrammarTypeArguments(node: Node, typeArguments: NodeArray | undefined): boolean { + return checkGrammarForDisallowedTrailingComma(typeArguments) || + checkGrammarForAtLeastOneTypeArgument(node, typeArguments); + } + + function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean { + if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) { + return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain); } + return false; + } - function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { - if (isExpressionWithTypeArguments(node) && isImportKeyword(node.expression) && node.typeArguments) { - return grammarErrorOnNode(node, Diagnostics.This_use_of_import_is_invalid_import_calls_can_be_written_but_they_must_have_parentheses_and_cannot_have_type_arguments); - } - return checkGrammarTypeArguments(node, node.typeArguments); + function checkGrammarHeritageClause(node: HeritageClause): boolean { + const types = node.types; + if (checkGrammarForDisallowedTrailingComma(types)) { + return true; + } + if (types && types.length === 0) { + const listType = tokenToString(node.token); + return grammarErrorAtPos(node, types.pos, 0, Diagnostics._0_list_cannot_be_empty, listType); } + return some(types, checkGrammarExpressionWithTypeArguments); + } - function checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { - let seenExtendsClause = false; - let seenImplementsClause = false; + function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { + if (isExpressionWithTypeArguments(node) && isImportKeyword(node.expression) && node.typeArguments) { + return grammarErrorOnNode(node, Diagnostics.This_use_of_import_is_invalid_import_calls_can_be_written_but_they_must_have_parentheses_and_cannot_have_type_arguments); + } + return checkGrammarTypeArguments(node, node.typeArguments); + } - if (!checkGrammarDecoratorsAndModifiers(node) && node.heritageClauses) { - for (const heritageClause of node.heritageClauses) { - if (heritageClause.token === SyntaxKind.ExtendsKeyword) { - if (seenExtendsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); - } + function checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { + let seenExtendsClause = false; + let seenImplementsClause = false; - if (seenImplementsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause); - } + if (!checkGrammarDecoratorsAndModifiers(node) && node.heritageClauses) { + for (const heritageClause of node.heritageClauses) { + if (heritageClause.token === SyntaxKind.ExtendsKeyword) { + if (seenExtendsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); + } - if (heritageClause.types.length > 1) { - return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); - } + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause); + } - seenExtendsClause = true; + if (heritageClause.types.length > 1) { + return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); } - else { - Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); - if (seenImplementsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen); - } - seenImplementsClause = true; + seenExtendsClause = true; + } + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen); } - // Grammar checking heritageClause inside class declaration - checkGrammarHeritageClause(heritageClause); + seenImplementsClause = true; } + + // Grammar checking heritageClause inside class declaration + checkGrammarHeritageClause(heritageClause); } } + } - function checkGrammarInterfaceDeclaration(node: InterfaceDeclaration) { - let seenExtendsClause = false; - - if (node.heritageClauses) { - for (const heritageClause of node.heritageClauses) { - if (heritageClause.token === SyntaxKind.ExtendsKeyword) { - if (seenExtendsClause) { - return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); - } + function checkGrammarInterfaceDeclaration(node: InterfaceDeclaration) { + let seenExtendsClause = false; - seenExtendsClause = true; - } - else { - Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); - return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); + if (node.heritageClauses) { + for (const heritageClause of node.heritageClauses) { + if (heritageClause.token === SyntaxKind.ExtendsKeyword) { + if (seenExtendsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); } - // Grammar checking heritageClause inside class declaration - checkGrammarHeritageClause(heritageClause); + seenExtendsClause = true; + } + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); } - } - return false; - } - function checkGrammarComputedPropertyName(node: Node): boolean { - // If node is not a computedPropertyName, just skip the grammar checking - if (node.kind !== SyntaxKind.ComputedPropertyName) { - return false; + // Grammar checking heritageClause inside class declaration + checkGrammarHeritageClause(heritageClause); } + } + return false; + } - const computedPropertyName = node as ComputedPropertyName; - if (computedPropertyName.expression.kind === SyntaxKind.BinaryExpression && (computedPropertyName.expression as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken) { - return grammarErrorOnNode(computedPropertyName.expression, Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name); - } + function checkGrammarComputedPropertyName(node: Node): boolean { + // If node is not a computedPropertyName, just skip the grammar checking + if (node.kind !== SyntaxKind.ComputedPropertyName) { return false; } - function checkGrammarForGenerator(node: FunctionLikeDeclaration) { - if (node.asteriskToken) { - Debug.assert( - node.kind === SyntaxKind.FunctionDeclaration || - node.kind === SyntaxKind.FunctionExpression || - node.kind === SyntaxKind.MethodDeclaration); - if (node.flags & NodeFlags.Ambient) { - return grammarErrorOnNode(node.asteriskToken, Diagnostics.Generators_are_not_allowed_in_an_ambient_context); - } - if (!node.body) { - return grammarErrorOnNode(node.asteriskToken, Diagnostics.An_overload_signature_cannot_be_declared_as_a_generator); - } - } + const computedPropertyName = node as ComputedPropertyName; + if (computedPropertyName.expression.kind === SyntaxKind.BinaryExpression && (computedPropertyName.expression as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken) { + return grammarErrorOnNode(computedPropertyName.expression, Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name); } + return false; + } - function checkGrammarForInvalidQuestionMark(questionToken: QuestionToken | undefined, message: DiagnosticMessage): boolean { - return !!questionToken && grammarErrorOnNode(questionToken, message); + function checkGrammarForGenerator(node: FunctionLikeDeclaration) { + if (node.asteriskToken) { + Debug.assert( + node.kind === SyntaxKind.FunctionDeclaration || + node.kind === SyntaxKind.FunctionExpression || + node.kind === SyntaxKind.MethodDeclaration); + if (node.flags & NodeFlags.Ambient) { + return grammarErrorOnNode(node.asteriskToken, Diagnostics.Generators_are_not_allowed_in_an_ambient_context); + } + if (!node.body) { + return grammarErrorOnNode(node.asteriskToken, Diagnostics.An_overload_signature_cannot_be_declared_as_a_generator); + } } + } - function checkGrammarForInvalidExclamationToken(exclamationToken: ExclamationToken | undefined, message: DiagnosticMessage): boolean { - return !!exclamationToken && grammarErrorOnNode(exclamationToken, message); - } + function checkGrammarForInvalidQuestionMark(questionToken: QuestionToken | undefined, message: DiagnosticMessage): boolean { + return !!questionToken && grammarErrorOnNode(questionToken, message); + } + + function checkGrammarForInvalidExclamationToken(exclamationToken: ExclamationToken | undefined, message: DiagnosticMessage): boolean { + return !!exclamationToken && grammarErrorOnNode(exclamationToken, message); + } - function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { - const seen = new Map<__String, DeclarationMeaning>(); + function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { + const seen = new Map<__String, DeclarationMeaning>(); - for (const prop of node.properties) { - if (prop.kind === SyntaxKind.SpreadAssignment) { - if (inDestructuring) { - // a rest property cannot be destructured any further - const expression = skipParentheses(prop.expression); - if (isArrayLiteralExpression(expression) || isObjectLiteralExpression(expression)) { - return grammarErrorOnNode(prop.expression, Diagnostics.A_rest_element_cannot_contain_a_binding_pattern); - } + for (const prop of node.properties) { + if (prop.kind === SyntaxKind.SpreadAssignment) { + if (inDestructuring) { + // a rest property cannot be destructured any further + const expression = skipParentheses(prop.expression); + if (isArrayLiteralExpression(expression) || isObjectLiteralExpression(expression)) { + return grammarErrorOnNode(prop.expression, Diagnostics.A_rest_element_cannot_contain_a_binding_pattern); } - continue; - } - const name = prop.name; - if (name.kind === SyntaxKind.ComputedPropertyName) { - // If the name is not a ComputedPropertyName, the grammar checking will skip it - checkGrammarComputedPropertyName(name); } + continue; + } + const name = prop.name; + if (name.kind === SyntaxKind.ComputedPropertyName) { + // If the name is not a ComputedPropertyName, the grammar checking will skip it + checkGrammarComputedPropertyName(name); + } - if (prop.kind === SyntaxKind.ShorthandPropertyAssignment && !inDestructuring && prop.objectAssignmentInitializer) { - // having objectAssignmentInitializer is only valid in ObjectAssignmentPattern - // outside of destructuring it is a syntax error - grammarErrorOnNode(prop.equalsToken!, Diagnostics.Did_you_mean_to_use_a_Colon_An_can_only_follow_a_property_name_when_the_containing_object_literal_is_part_of_a_destructuring_pattern); - } + if (prop.kind === SyntaxKind.ShorthandPropertyAssignment && !inDestructuring && prop.objectAssignmentInitializer) { + // having objectAssignmentInitializer is only valid in ObjectAssignmentPattern + // outside of destructuring it is a syntax error + grammarErrorOnNode(prop.equalsToken!, Diagnostics.Did_you_mean_to_use_a_Colon_An_can_only_follow_a_property_name_when_the_containing_object_literal_is_part_of_a_destructuring_pattern); + } - if (name.kind === SyntaxKind.PrivateIdentifier) { - grammarErrorOnNode(name, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); - } + if (name.kind === SyntaxKind.PrivateIdentifier) { + grammarErrorOnNode(name, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + } - // Modifiers are never allowed on properties except for 'async' on a method declaration - if (canHaveModifiers(prop) && prop.modifiers) { - for (const mod of prop.modifiers) { - if (isModifier(mod) && (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration)) { - grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); - } + // Modifiers are never allowed on properties except for 'async' on a method declaration + if (canHaveModifiers(prop) && prop.modifiers) { + for (const mod of prop.modifiers) { + if (isModifier(mod) && (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration)) { + grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); } } - else if (canHaveIllegalModifiers(prop) && prop.modifiers) { - for (const mod of prop.modifiers) { - grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); + } + else if (canHaveIllegalModifiers(prop) && prop.modifiers) { + for (const mod of prop.modifiers) { + grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); + } + } + + // ECMA-262 11.1.5 Object Initializer + // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true + // a.This production is contained in strict code and IsDataDescriptor(previous) is true and + // IsDataDescriptor(propId.descriptor) is true. + // b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true. + // c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true. + // d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true + // and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields + let currentKind: DeclarationMeaning; + switch (prop.kind) { + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.PropertyAssignment: + // Grammar checking for computedPropertyName and shorthandPropertyAssignment + checkGrammarForInvalidExclamationToken(prop.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); + checkGrammarForInvalidQuestionMark(prop.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional); + if (name.kind === SyntaxKind.NumericLiteral) { + checkGrammarNumericLiteral(name); } - } + currentKind = DeclarationMeaning.PropertyAssignment; + break; + case SyntaxKind.MethodDeclaration: + currentKind = DeclarationMeaning.Method; + break; + case SyntaxKind.GetAccessor: + currentKind = DeclarationMeaning.GetAccessor; + break; + case SyntaxKind.SetAccessor: + currentKind = DeclarationMeaning.SetAccessor; + break; + default: + throw Debug.assertNever(prop, "Unexpected syntax kind:" + (prop as Node).kind); + } - // ECMA-262 11.1.5 Object Initializer - // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true - // a.This production is contained in strict code and IsDataDescriptor(previous) is true and - // IsDataDescriptor(propId.descriptor) is true. - // b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true. - // c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true. - // d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true - // and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields - let currentKind: DeclarationMeaning; - switch (prop.kind) { - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.PropertyAssignment: - // Grammar checking for computedPropertyName and shorthandPropertyAssignment - checkGrammarForInvalidExclamationToken(prop.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); - checkGrammarForInvalidQuestionMark(prop.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional); - if (name.kind === SyntaxKind.NumericLiteral) { - checkGrammarNumericLiteral(name); - } - currentKind = DeclarationMeaning.PropertyAssignment; - break; - case SyntaxKind.MethodDeclaration: - currentKind = DeclarationMeaning.Method; - break; - case SyntaxKind.GetAccessor: - currentKind = DeclarationMeaning.GetAccessor; - break; - case SyntaxKind.SetAccessor: - currentKind = DeclarationMeaning.SetAccessor; - break; - default: - throw Debug.assertNever(prop, "Unexpected syntax kind:" + (prop as Node).kind); + if (!inDestructuring) { + const effectiveName = getPropertyNameForPropertyNameNode(name); + if (effectiveName === undefined) { + continue; } - if (!inDestructuring) { - const effectiveName = getPropertyNameForPropertyNameNode(name); - if (effectiveName === undefined) { - continue; + const existingKind = seen.get(effectiveName); + if (!existingKind) { + seen.set(effectiveName, currentKind); + } + else { + if ((currentKind & DeclarationMeaning.Method) && (existingKind & DeclarationMeaning.Method)) { + grammarErrorOnNode(name, Diagnostics.Duplicate_identifier_0, getTextOfNode(name)); } - - const existingKind = seen.get(effectiveName); - if (!existingKind) { - seen.set(effectiveName, currentKind); + else if ((currentKind & DeclarationMeaning.PropertyAssignment) && (existingKind & DeclarationMeaning.PropertyAssignment)) { + grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_properties_with_the_same_name, getTextOfNode(name)); } - else { - if ((currentKind & DeclarationMeaning.Method) && (existingKind & DeclarationMeaning.Method)) { - grammarErrorOnNode(name, Diagnostics.Duplicate_identifier_0, getTextOfNode(name)); - } - else if ((currentKind & DeclarationMeaning.PropertyAssignment) && (existingKind & DeclarationMeaning.PropertyAssignment)) { - grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_properties_with_the_same_name, getTextOfNode(name)); - } - else if ((currentKind & DeclarationMeaning.GetOrSetAccessor) && (existingKind & DeclarationMeaning.GetOrSetAccessor)) { - if (existingKind !== DeclarationMeaning.GetOrSetAccessor && currentKind !== existingKind) { - seen.set(effectiveName, currentKind | existingKind); - } - else { - return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_get_Slashset_accessors_with_the_same_name); - } + else if ((currentKind & DeclarationMeaning.GetOrSetAccessor) && (existingKind & DeclarationMeaning.GetOrSetAccessor)) { + if (existingKind !== DeclarationMeaning.GetOrSetAccessor && currentKind !== existingKind) { + seen.set(effectiveName, currentKind | existingKind); } else { - return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_property_and_accessor_with_the_same_name); + return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_get_Slashset_accessors_with_the_same_name); } } + else { + return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_property_and_accessor_with_the_same_name); + } } } } + } - function checkGrammarJsxElement(node: JsxOpeningLikeElement) { - checkGrammarJsxName(node.tagName); - checkGrammarTypeArguments(node, node.typeArguments); - const seen = new Map<__String, boolean>(); + function checkGrammarJsxElement(node: JsxOpeningLikeElement) { + checkGrammarJsxName(node.tagName); + checkGrammarTypeArguments(node, node.typeArguments); + const seen = new Map<__String, boolean>(); - for (const attr of node.attributes.properties) { - if (attr.kind === SyntaxKind.JsxSpreadAttribute) { - continue; - } + for (const attr of node.attributes.properties) { + if (attr.kind === SyntaxKind.JsxSpreadAttribute) { + continue; + } - const { name, initializer } = attr; - if (!seen.get(name.escapedText)) { - seen.set(name.escapedText, true); - } - else { - return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name); - } + const { name, initializer } = attr; + if (!seen.get(name.escapedText)) { + seen.set(name.escapedText, true); + } + else { + return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name); + } - if (initializer && initializer.kind === SyntaxKind.JsxExpression && !initializer.expression) { - return grammarErrorOnNode(initializer, Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression); - } + if (initializer && initializer.kind === SyntaxKind.JsxExpression && !initializer.expression) { + return grammarErrorOnNode(initializer, Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression); } } + } - function checkGrammarJsxName(node: JsxTagNameExpression) { - if (isPropertyAccessExpression(node)) { - let propName: JsxTagNameExpression = node; - do { - const check = checkGrammarJsxNestedIdentifier(propName.name); - if (check) { - return check; - } - propName = propName.expression; - } while (isPropertyAccessExpression(propName)); - const check = checkGrammarJsxNestedIdentifier(propName); + function checkGrammarJsxName(node: JsxTagNameExpression) { + if (isPropertyAccessExpression(node)) { + let propName: JsxTagNameExpression = node; + do { + const check = checkGrammarJsxNestedIdentifier(propName.name); if (check) { return check; } + propName = propName.expression; + } while (isPropertyAccessExpression(propName)); + const check = checkGrammarJsxNestedIdentifier(propName); + if (check) { + return check; } + } - function checkGrammarJsxNestedIdentifier(name: MemberName | ThisExpression) { - if (isIdentifier(name) && idText(name).indexOf(":") !== -1) { - return grammarErrorOnNode(name, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names); - } + function checkGrammarJsxNestedIdentifier(name: MemberName | ThisExpression) { + if (isIdentifier(name) && idText(name).indexOf(":") !== -1) { + return grammarErrorOnNode(name, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names); } } + } - 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 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; - } + function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean { + if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { + return true; + } - if (forInOrOfStatement.kind === SyntaxKind.ForOfStatement && forInOrOfStatement.awaitModifier) { - if (!(forInOrOfStatement.flags & NodeFlags.AwaitContext)) { - const sourceFile = getSourceFileOfNode(forInOrOfStatement); - if (isInTopLevelContext(forInOrOfStatement)) { - if (!hasParseDiagnostics(sourceFile)) { - if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { - diagnostics.add(createDiagnosticForNode(forInOrOfStatement.awaitModifier, - Diagnostics.for_await_loops_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module)); - } - switch (moduleKind) { - case ModuleKind.Node16: - case ModuleKind.NodeNext: - if (sourceFile.impliedNodeFormat === ModuleKind.CommonJS) { - diagnostics.add( - createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.The_current_file_is_a_CommonJS_module_and_cannot_use_await_at_the_top_level) - ); - break; - } - // fallthrough - case ModuleKind.ES2022: - case ModuleKind.ESNext: - case ModuleKind.System: - if (languageVersion >= ScriptTarget.ES2017) { - break; - } - // fallthrough - default: + if (forInOrOfStatement.kind === SyntaxKind.ForOfStatement && forInOrOfStatement.awaitModifier) { + if (!(forInOrOfStatement.flags & NodeFlags.AwaitContext)) { + const sourceFile = getSourceFileOfNode(forInOrOfStatement); + if (isInTopLevelContext(forInOrOfStatement)) { + if (!hasParseDiagnostics(sourceFile)) { + if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { + diagnostics.add(createDiagnosticForNode(forInOrOfStatement.awaitModifier, + Diagnostics.for_await_loops_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module)); + } + switch (moduleKind) { + case ModuleKind.Node16: + case ModuleKind.NodeNext: + if (sourceFile.impliedNodeFormat === ModuleKind.CommonJS) { diagnostics.add( - createDiagnosticForNode(forInOrOfStatement.awaitModifier, - Diagnostics.Top_level_for_await_loops_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher - ) + createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.The_current_file_is_a_CommonJS_module_and_cannot_use_await_at_the_top_level) ); break; - } + } + // fallthrough + case ModuleKind.ES2022: + case ModuleKind.ESNext: + case ModuleKind.System: + if (languageVersion >= ScriptTarget.ES2017) { + break; + } + // fallthrough + default: + diagnostics.add( + createDiagnosticForNode(forInOrOfStatement.awaitModifier, + Diagnostics.Top_level_for_await_loops_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher + ) + ); + break; } } - else { - // use of 'for-await-of' in non-async function - if (!hasParseDiagnostics(sourceFile)) { - const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.for_await_loops_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); - const func = getContainingFunction(forInOrOfStatement); - if (func && func.kind !== SyntaxKind.Constructor) { - Debug.assert((getFunctionFlags(func) & FunctionFlags.Async) === 0, "Enclosing function should never be an async function."); - const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async); - addRelatedInfo(diagnostic, relatedInfo); - } - diagnostics.add(diagnostic); - return true; + } + else { + // use of 'for-await-of' in non-async function + if (!hasParseDiagnostics(sourceFile)) { + const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.for_await_loops_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); + const func = getContainingFunction(forInOrOfStatement); + if (func && func.kind !== SyntaxKind.Constructor) { + Debug.assert((getFunctionFlags(func) & FunctionFlags.Async) === 0, "Enclosing function should never be an async function."); + const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async); + addRelatedInfo(diagnostic, relatedInfo); } + diagnostics.add(diagnostic); + return true; } - return false; } - } - - if (isForOfStatement(forInOrOfStatement) && !(forInOrOfStatement.flags & NodeFlags.AwaitContext) && - isIdentifier(forInOrOfStatement.initializer) && forInOrOfStatement.initializer.escapedText === "async") { - grammarErrorOnNode(forInOrOfStatement.initializer, Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_async); return false; } + } - if (forInOrOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { - const variableList = forInOrOfStatement.initializer as VariableDeclarationList; - if (!checkGrammarVariableDeclarationList(variableList)) { - const declarations = variableList.declarations; - - // declarations.length can be zero if there is an error in variable declaration in for-of or for-in - // See http://www.ecma-international.org/ecma-262/6.0/#sec-for-in-and-for-of-statements for details - // For example: - // var let = 10; - // for (let of [1,2,3]) {} // this is invalid ES6 syntax - // for (let in [1,2,3]) {} // this is invalid ES6 syntax - // We will then want to skip on grammar checking on variableList declaration - if (!declarations.length) { - return false; - } + if (isForOfStatement(forInOrOfStatement) && !(forInOrOfStatement.flags & NodeFlags.AwaitContext) && + isIdentifier(forInOrOfStatement.initializer) && forInOrOfStatement.initializer.escapedText === "async") { + grammarErrorOnNode(forInOrOfStatement.initializer, Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_async); + return false; + } - if (declarations.length > 1) { - const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement - ? Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement - : Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement; - return grammarErrorOnFirstToken(variableList.declarations[1], diagnostic); - } - const firstDeclaration = declarations[0]; + if (forInOrOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { + const variableList = forInOrOfStatement.initializer as VariableDeclarationList; + if (!checkGrammarVariableDeclarationList(variableList)) { + const declarations = variableList.declarations; - if (firstDeclaration.initializer) { - const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement - ? Diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer - : Diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer; - return grammarErrorOnNode(firstDeclaration.name, diagnostic); - } - if (firstDeclaration.type) { - const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement - ? Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_use_a_type_annotation - : Diagnostics.The_left_hand_side_of_a_for_of_statement_cannot_use_a_type_annotation; - return grammarErrorOnNode(firstDeclaration, diagnostic); - } + // declarations.length can be zero if there is an error in variable declaration in for-of or for-in + // See http://www.ecma-international.org/ecma-262/6.0/#sec-for-in-and-for-of-statements for details + // For example: + // var let = 10; + // for (let of [1,2,3]) {} // this is invalid ES6 syntax + // for (let in [1,2,3]) {} // this is invalid ES6 syntax + // We will then want to skip on grammar checking on variableList declaration + if (!declarations.length) { + return false; } - } - - return false; - } - function checkGrammarAccessor(accessor: AccessorDeclaration): boolean { - if (!(accessor.flags & NodeFlags.Ambient) && (accessor.parent.kind !== SyntaxKind.TypeLiteral) && (accessor.parent.kind !== SyntaxKind.InterfaceDeclaration)) { - if (languageVersion < ScriptTarget.ES5) { - return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher); + if (declarations.length > 1) { + const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement + ? Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement + : Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement; + return grammarErrorOnFirstToken(variableList.declarations[1], diagnostic); } - if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(accessor.name)) { - return grammarErrorOnNode(accessor.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); + const firstDeclaration = declarations[0]; + + if (firstDeclaration.initializer) { + const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement + ? Diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer + : Diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer; + return grammarErrorOnNode(firstDeclaration.name, diagnostic); } - if (accessor.body === undefined && !hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { - return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); + if (firstDeclaration.type) { + const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement + ? Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_use_a_type_annotation + : Diagnostics.The_left_hand_side_of_a_for_of_statement_cannot_use_a_type_annotation; + return grammarErrorOnNode(firstDeclaration, diagnostic); } } - if (accessor.body) { - if (hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { - return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation); - } - if (accessor.parent.kind === SyntaxKind.TypeLiteral || accessor.parent.kind === SyntaxKind.InterfaceDeclaration) { - return grammarErrorOnNode(accessor.body, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); - } + } + + return false; + } + + function checkGrammarAccessor(accessor: AccessorDeclaration): boolean { + if (!(accessor.flags & NodeFlags.Ambient) && (accessor.parent.kind !== SyntaxKind.TypeLiteral) && (accessor.parent.kind !== SyntaxKind.InterfaceDeclaration)) { + if (languageVersion < ScriptTarget.ES5) { + return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher); } - if (accessor.typeParameters) { - return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters); + if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(accessor.name)) { + return grammarErrorOnNode(accessor.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); } - if (!doesAccessorHaveCorrectParameterCount(accessor)) { - return grammarErrorOnNode(accessor.name, - accessor.kind === SyntaxKind.GetAccessor ? - Diagnostics.A_get_accessor_cannot_have_parameters : - Diagnostics.A_set_accessor_must_have_exactly_one_parameter); + if (accessor.body === undefined && !hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { + return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); } - if (accessor.kind === SyntaxKind.SetAccessor) { - if (accessor.type) { - return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation); - } - const parameter = Debug.checkDefined(getSetAccessorValueParameter(accessor), "Return value does not match parameter count assertion."); - if (parameter.dotDotDotToken) { - return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter); - } - if (parameter.questionToken) { - return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter); - } - if (parameter.initializer) { - return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer); - } + } + if (accessor.body) { + if (hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { + return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation); + } + if (accessor.parent.kind === SyntaxKind.TypeLiteral || accessor.parent.kind === SyntaxKind.InterfaceDeclaration) { + return grammarErrorOnNode(accessor.body, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); } - return false; } - - /** Does the accessor have the right number of parameters? - * A get accessor has no parameters or a single `this` parameter. - * A set accessor has one parameter or a `this` parameter and one more parameter. - */ - function doesAccessorHaveCorrectParameterCount(accessor: AccessorDeclaration) { - return getAccessorThisParameter(accessor) || accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 0 : 1); + if (accessor.typeParameters) { + return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters); } - - function getAccessorThisParameter(accessor: AccessorDeclaration): ParameterDeclaration | undefined { - if (accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 1 : 2)) { - return getThisParameter(accessor); + if (!doesAccessorHaveCorrectParameterCount(accessor)) { + return grammarErrorOnNode(accessor.name, + accessor.kind === SyntaxKind.GetAccessor ? + Diagnostics.A_get_accessor_cannot_have_parameters : + Diagnostics.A_set_accessor_must_have_exactly_one_parameter); + } + if (accessor.kind === SyntaxKind.SetAccessor) { + if (accessor.type) { + return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation); + } + const parameter = Debug.checkDefined(getSetAccessorValueParameter(accessor), "Return value does not match parameter count assertion."); + if (parameter.dotDotDotToken) { + return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter); + } + if (parameter.questionToken) { + return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter); + } + if (parameter.initializer) { + return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer); } } + return false; + } + + /** Does the accessor have the right number of parameters? + * A get accessor has no parameters or a single `this` parameter. + * A set accessor has one parameter or a `this` parameter and one more parameter. + */ + function doesAccessorHaveCorrectParameterCount(accessor: AccessorDeclaration) { + return getAccessorThisParameter(accessor) || accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 0 : 1); + } + + function getAccessorThisParameter(accessor: AccessorDeclaration): ParameterDeclaration | undefined { + if (accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 1 : 2)) { + return getThisParameter(accessor); + } + } - function checkGrammarTypeOperatorNode(node: TypeOperatorNode) { - if (node.operator === SyntaxKind.UniqueKeyword) { - if (node.type.kind !== SyntaxKind.SymbolKeyword) { - return grammarErrorOnNode(node.type, Diagnostics._0_expected, tokenToString(SyntaxKind.SymbolKeyword)); + function checkGrammarTypeOperatorNode(node: TypeOperatorNode) { + if (node.operator === SyntaxKind.UniqueKeyword) { + if (node.type.kind !== SyntaxKind.SymbolKeyword) { + return grammarErrorOnNode(node.type, Diagnostics._0_expected, tokenToString(SyntaxKind.SymbolKeyword)); + } + let parent = walkUpParenthesizedTypes(node.parent); + if (isInJSFile(parent) && isJSDocTypeExpression(parent)) { + const host = getJSDocHost(parent); + if (host) { + parent = getSingleVariableOfVariableStatement(host) || host; } - let parent = walkUpParenthesizedTypes(node.parent); - if (isInJSFile(parent) && isJSDocTypeExpression(parent)) { - const host = getJSDocHost(parent); - if (host) { - parent = getSingleVariableOfVariableStatement(host) || host; + } + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + const decl = parent as VariableDeclaration; + if (decl.name.kind !== SyntaxKind.Identifier) { + return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_may_not_be_used_on_a_variable_declaration_with_a_binding_name); } - } - switch (parent.kind) { - case SyntaxKind.VariableDeclaration: - const decl = parent as VariableDeclaration; - if (decl.name.kind !== SyntaxKind.Identifier) { - return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_may_not_be_used_on_a_variable_declaration_with_a_binding_name); - } - if (!isVariableDeclarationInVariableStatement(decl)) { - return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_only_allowed_on_variables_in_a_variable_statement); - } - if (!(decl.parent.flags & NodeFlags.Const)) { - return grammarErrorOnNode((parent as VariableDeclaration).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const); - } - break; + if (!isVariableDeclarationInVariableStatement(decl)) { + return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_only_allowed_on_variables_in_a_variable_statement); + } + if (!(decl.parent.flags & NodeFlags.Const)) { + return grammarErrorOnNode((parent as VariableDeclaration).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const); + } + break; - case SyntaxKind.PropertyDeclaration: - if (!isStatic(parent) || - !hasEffectiveReadonlyModifier(parent)) { - return grammarErrorOnNode((parent as PropertyDeclaration).name, Diagnostics.A_property_of_a_class_whose_type_is_a_unique_symbol_type_must_be_both_static_and_readonly); - } - break; + case SyntaxKind.PropertyDeclaration: + if (!isStatic(parent) || + !hasEffectiveReadonlyModifier(parent)) { + return grammarErrorOnNode((parent as PropertyDeclaration).name, Diagnostics.A_property_of_a_class_whose_type_is_a_unique_symbol_type_must_be_both_static_and_readonly); + } + break; - case SyntaxKind.PropertySignature: - if (!hasSyntacticModifier(parent, ModifierFlags.Readonly)) { - return grammarErrorOnNode((parent as PropertySignature).name, Diagnostics.A_property_of_an_interface_or_type_literal_whose_type_is_a_unique_symbol_type_must_be_readonly); - } - break; + case SyntaxKind.PropertySignature: + if (!hasSyntacticModifier(parent, ModifierFlags.Readonly)) { + return grammarErrorOnNode((parent as PropertySignature).name, Diagnostics.A_property_of_an_interface_or_type_literal_whose_type_is_a_unique_symbol_type_must_be_readonly); + } + break; - default: - return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here); - } + default: + return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here); } - else if (node.operator === SyntaxKind.ReadonlyKeyword) { - if (node.type.kind !== SyntaxKind.ArrayType && node.type.kind !== SyntaxKind.TupleType) { - return grammarErrorOnFirstToken(node, Diagnostics.readonly_type_modifier_is_only_permitted_on_array_and_tuple_literal_types, tokenToString(SyntaxKind.SymbolKeyword)); - } + } + else if (node.operator === SyntaxKind.ReadonlyKeyword) { + if (node.type.kind !== SyntaxKind.ArrayType && node.type.kind !== SyntaxKind.TupleType) { + return grammarErrorOnFirstToken(node, Diagnostics.readonly_type_modifier_is_only_permitted_on_array_and_tuple_literal_types, tokenToString(SyntaxKind.SymbolKeyword)); } } + } - function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { - if (isNonBindableDynamicName(node)) { - return grammarErrorOnNode(node, message); - } + function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { + if (isNonBindableDynamicName(node)) { + return grammarErrorOnNode(node, message); } + } - function checkGrammarMethod(node: MethodDeclaration | MethodSignature) { - if (checkGrammarFunctionLikeDeclaration(node)) { - return true; - } + function checkGrammarMethod(node: MethodDeclaration | MethodSignature) { + if (checkGrammarFunctionLikeDeclaration(node)) { + return true; + } - if (node.kind === SyntaxKind.MethodDeclaration) { - if (node.parent.kind === SyntaxKind.ObjectLiteralExpression) { - // We only disallow modifier on a method declaration if it is a property of object-literal-expression - if (node.modifiers && !(node.modifiers.length === 1 && first(node.modifiers).kind === SyntaxKind.AsyncKeyword)) { - return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here); - } - else if (checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional)) { - return true; - } - else if (checkGrammarForInvalidExclamationToken(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context)) { - return true; - } - else if (node.body === undefined) { - return grammarErrorAtPos(node, node.end - 1, ";".length, Diagnostics._0_expected, "{"); - } + if (node.kind === SyntaxKind.MethodDeclaration) { + if (node.parent.kind === SyntaxKind.ObjectLiteralExpression) { + // We only disallow modifier on a method declaration if it is a property of object-literal-expression + if (node.modifiers && !(node.modifiers.length === 1 && first(node.modifiers).kind === SyntaxKind.AsyncKeyword)) { + return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here); } - if (checkGrammarForGenerator(node)) { + else if (checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional)) { return true; } - } - - if (isClassLike(node.parent)) { - if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { - return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); - } - // Technically, computed properties in ambient contexts is disallowed - // for property declarations and accessors too, not just methods. - // However, property declarations disallow computed names in general, - // and accessors are not allowed in ambient contexts in general, - // so this error only really matters for methods. - if (node.flags & NodeFlags.Ambient) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_ambient_context_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + else if (checkGrammarForInvalidExclamationToken(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context)) { + return true; } - else if (node.kind === SyntaxKind.MethodDeclaration && !node.body) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_method_overload_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + else if (node.body === undefined) { + return grammarErrorAtPos(node, node.end - 1, ";".length, Diagnostics._0_expected, "{"); } } - else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); - } - else if (node.parent.kind === SyntaxKind.TypeLiteral) { - return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + if (checkGrammarForGenerator(node)) { + return true; } } - function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean { - let current: Node = node; - while (current) { - if (isFunctionLikeOrClassStaticBlockDeclaration(current)) { - return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); - } + if (isClassLike(node.parent)) { + if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { + return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); + } + // Technically, computed properties in ambient contexts is disallowed + // for property declarations and accessors too, not just methods. + // However, property declarations disallow computed names in general, + // and accessors are not allowed in ambient contexts in general, + // so this error only really matters for methods. + if (node.flags & NodeFlags.Ambient) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_ambient_context_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + else if (node.kind === SyntaxKind.MethodDeclaration && !node.body) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_method_overload_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + } + else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + else if (node.parent.kind === SyntaxKind.TypeLiteral) { + return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); + } + } - switch (current.kind) { - case SyntaxKind.LabeledStatement: - if (node.label && (current as LabeledStatement).label.escapedText === node.label.escapedText) { - // found matching label - verify that label usage is correct - // continue can only target labels that are on iteration statements - const isMisplacedContinueLabel = node.kind === SyntaxKind.ContinueStatement - && !isIterationStatement((current as LabeledStatement).statement, /*lookInLabeledStatement*/ true); + function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean { + let current: Node = node; + while (current) { + if (isFunctionLikeOrClassStaticBlockDeclaration(current)) { + return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); + } - if (isMisplacedContinueLabel) { - return grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement); - } + switch (current.kind) { + case SyntaxKind.LabeledStatement: + if (node.label && (current as LabeledStatement).label.escapedText === node.label.escapedText) { + // found matching label - verify that label usage is correct + // continue can only target labels that are on iteration statements + const isMisplacedContinueLabel = node.kind === SyntaxKind.ContinueStatement + && !isIterationStatement((current as LabeledStatement).statement, /*lookInLabeledStatement*/ true); - return false; - } - break; - case SyntaxKind.SwitchStatement: - if (node.kind === SyntaxKind.BreakStatement && !node.label) { - // unlabeled break within switch statement - ok - return false; - } - break; - default: - if (isIterationStatement(current, /*lookInLabeledStatement*/ false) && !node.label) { - // unlabeled break or continue within iteration statement - ok - return false; + if (isMisplacedContinueLabel) { + return grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement); } - break; - } - current = current.parent; + return false; + } + break; + case SyntaxKind.SwitchStatement: + if (node.kind === SyntaxKind.BreakStatement && !node.label) { + // unlabeled break within switch statement - ok + return false; + } + break; + default: + if (isIterationStatement(current, /*lookInLabeledStatement*/ false) && !node.label) { + // unlabeled break or continue within iteration statement - ok + return false; + } + break; } - if (node.label) { - const message = node.kind === SyntaxKind.BreakStatement - ? Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement - : Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement; - - return grammarErrorOnNode(node, message); - } - else { - const message = node.kind === SyntaxKind.BreakStatement - ? Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement - : Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement; - return grammarErrorOnNode(node, message); - } + current = current.parent; } - function checkGrammarBindingElement(node: BindingElement) { - if (node.dotDotDotToken) { - const elements = node.parent.elements; - if (node !== last(elements)) { - return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); - } - checkGrammarForDisallowedTrailingComma(elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + if (node.label) { + const message = node.kind === SyntaxKind.BreakStatement + ? Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement + : Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement; - if (node.propertyName) { - return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_have_a_property_name); - } + return grammarErrorOnNode(node, message); + } + else { + const message = node.kind === SyntaxKind.BreakStatement + ? Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement + : Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement; + return grammarErrorOnNode(node, message); + } + } + + function checkGrammarBindingElement(node: BindingElement) { + if (node.dotDotDotToken) { + const elements = node.parent.elements; + if (node !== last(elements)) { + return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } + checkGrammarForDisallowedTrailingComma(elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - if (node.dotDotDotToken && node.initializer) { - // Error on equals token which immediately precedes the initializer - return grammarErrorAtPos(node, node.initializer.pos - 1, 1, Diagnostics.A_rest_element_cannot_have_an_initializer); + if (node.propertyName) { + return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_have_a_property_name); } } - function isStringOrNumberLiteralExpression(expr: Expression) { - return isStringOrNumericLiteralLike(expr) || - expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && - (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.NumericLiteral; + if (node.dotDotDotToken && node.initializer) { + // Error on equals token which immediately precedes the initializer + return grammarErrorAtPos(node, node.initializer.pos - 1, 1, Diagnostics.A_rest_element_cannot_have_an_initializer); } + } - function isBigIntLiteralExpression(expr: Expression) { - return expr.kind === SyntaxKind.BigIntLiteral || - expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && - (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.BigIntLiteral; - } + function isStringOrNumberLiteralExpression(expr: Expression) { + return isStringOrNumericLiteralLike(expr) || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && + (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.NumericLiteral; + } - function isSimpleLiteralEnumReference(expr: Expression) { - if ((isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) && - isEntityNameExpression(expr.expression)) { - return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLiteral); - } + function isBigIntLiteralExpression(expr: Expression) { + return expr.kind === SyntaxKind.BigIntLiteral || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && + (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.BigIntLiteral; + } + + function isSimpleLiteralEnumReference(expr: Expression) { + if ((isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) && + isEntityNameExpression(expr.expression)) { + return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLiteral); } + } - function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) { - const initializer = node.initializer; - if (initializer) { - const isInvalidInitializer = !( - isStringOrNumberLiteralExpression(initializer) || - isSimpleLiteralEnumReference(initializer) || - initializer.kind === SyntaxKind.TrueKeyword || initializer.kind === SyntaxKind.FalseKeyword || - isBigIntLiteralExpression(initializer) - ); - const isConstOrReadonly = isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node); - if (isConstOrReadonly && !node.type) { - if (isInvalidInitializer) { - return grammarErrorOnNode(initializer, Diagnostics.A_const_initializer_in_an_ambient_context_must_be_a_string_or_numeric_literal_or_literal_enum_reference); - } - } - else { - return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); + function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) { + const initializer = node.initializer; + if (initializer) { + const isInvalidInitializer = !( + isStringOrNumberLiteralExpression(initializer) || + isSimpleLiteralEnumReference(initializer) || + initializer.kind === SyntaxKind.TrueKeyword || initializer.kind === SyntaxKind.FalseKeyword || + isBigIntLiteralExpression(initializer) + ); + const isConstOrReadonly = isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node); + if (isConstOrReadonly && !node.type) { + if (isInvalidInitializer) { + return grammarErrorOnNode(initializer, Diagnostics.A_const_initializer_in_an_ambient_context_must_be_a_string_or_numeric_literal_or_literal_enum_reference); } } + else { + return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); + } } + } - function checkGrammarVariableDeclaration(node: VariableDeclaration) { - if (node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) { - if (node.flags & NodeFlags.Ambient) { - checkAmbientInitializer(node); + function checkGrammarVariableDeclaration(node: VariableDeclaration) { + if (node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) { + if (node.flags & NodeFlags.Ambient) { + checkAmbientInitializer(node); + } + else if (!node.initializer) { + if (isBindingPattern(node.name) && !isBindingPattern(node.parent)) { + return grammarErrorOnNode(node, Diagnostics.A_destructuring_declaration_must_have_an_initializer); } - else if (!node.initializer) { - if (isBindingPattern(node.name) && !isBindingPattern(node.parent)) { - return grammarErrorOnNode(node, Diagnostics.A_destructuring_declaration_must_have_an_initializer); - } - if (isVarConst(node)) { - return grammarErrorOnNode(node, Diagnostics.const_declarations_must_be_initialized); - } + if (isVarConst(node)) { + return grammarErrorOnNode(node, Diagnostics.const_declarations_must_be_initialized); } } + } - if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) { - const message = node.initializer - ? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions - : !node.type - ? Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations - : Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context; - return grammarErrorOnNode(node.exclamationToken, message); - } + if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) { + const message = node.initializer + ? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions + : !node.type + ? Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations + : Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context; + return grammarErrorOnNode(node.exclamationToken, message); + } - if ((moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && moduleKind !== ModuleKind.System && - !(node.parent.parent.flags & NodeFlags.Ambient) && hasSyntacticModifier(node.parent.parent, ModifierFlags.Export)) { - checkESModuleMarker(node.name); - } + if ((moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && moduleKind !== ModuleKind.System && + !(node.parent.parent.flags & NodeFlags.Ambient) && hasSyntacticModifier(node.parent.parent, ModifierFlags.Export)) { + checkESModuleMarker(node.name); + } - const checkLetConstNames = (isLet(node) || isVarConst(node)); + const checkLetConstNames = (isLet(node) || isVarConst(node)); - // 1. LexicalDeclaration : LetOrConst BindingList ; - // It is a Syntax Error if the BoundNames of BindingList contains "let". - // 2. ForDeclaration: ForDeclaration : LetOrConst ForBinding - // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". + // 1. LexicalDeclaration : LetOrConst BindingList ; + // It is a Syntax Error if the BoundNames of BindingList contains "let". + // 2. ForDeclaration: ForDeclaration : LetOrConst ForBinding + // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". - // It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within strict code - // and its Identifier is eval or arguments - return checkLetConstNames && checkGrammarNameInLetOrConstDeclarations(node.name); - } + // It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within strict code + // and its Identifier is eval or arguments + return checkLetConstNames && checkGrammarNameInLetOrConstDeclarations(node.name); + } - function checkESModuleMarker(name: Identifier | BindingPattern): boolean { - if (name.kind === SyntaxKind.Identifier) { - if (idText(name) === "__esModule") { - return grammarErrorOnNodeSkippedOn("noEmit", name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules); - } + function checkESModuleMarker(name: Identifier | BindingPattern): boolean { + if (name.kind === SyntaxKind.Identifier) { + if (idText(name) === "__esModule") { + return grammarErrorOnNodeSkippedOn("noEmit", name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules); } - else { - const elements = name.elements; - for (const element of elements) { - if (!isOmittedExpression(element)) { - return checkESModuleMarker(element.name); - } + } + else { + const elements = name.elements; + for (const element of elements) { + if (!isOmittedExpression(element)) { + return checkESModuleMarker(element.name); } } - return false; } + return false; + } - function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { - if (name.kind === SyntaxKind.Identifier) { - if (name.originalKeywordKind === SyntaxKind.LetKeyword) { - return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations); - } + function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { + if (name.kind === SyntaxKind.Identifier) { + if (name.originalKeywordKind === SyntaxKind.LetKeyword) { + return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations); } - else { - const elements = name.elements; - for (const element of elements) { - if (!isOmittedExpression(element)) { - checkGrammarNameInLetOrConstDeclarations(element.name); - } + } + else { + const elements = name.elements; + for (const element of elements) { + if (!isOmittedExpression(element)) { + checkGrammarNameInLetOrConstDeclarations(element.name); } } - return false; } + return false; + } - function checkGrammarVariableDeclarationList(declarationList: VariableDeclarationList): boolean { - const declarations = declarationList.declarations; - if (checkGrammarForDisallowedTrailingComma(declarationList.declarations)) { - return true; - } + function checkGrammarVariableDeclarationList(declarationList: VariableDeclarationList): boolean { + const declarations = declarationList.declarations; + if (checkGrammarForDisallowedTrailingComma(declarationList.declarations)) { + return true; + } - if (!declarationList.declarations.length) { - return grammarErrorAtPos(declarationList, declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty); - } - return false; + if (!declarationList.declarations.length) { + return grammarErrorAtPos(declarationList, declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty); + } + return false; + } + + function allowLetAndConstDeclarations(parent: Node): boolean { + switch (parent.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + return false; + case SyntaxKind.LabeledStatement: + return allowLetAndConstDeclarations(parent.parent); } - function allowLetAndConstDeclarations(parent: Node): boolean { - switch (parent.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - return false; - case SyntaxKind.LabeledStatement: - return allowLetAndConstDeclarations(parent.parent); - } + return true; + } - return true; + function checkGrammarForDisallowedLetOrConstStatement(node: VariableStatement) { + if (!allowLetAndConstDeclarations(node.parent)) { + if (isLet(node.declarationList)) { + return grammarErrorOnNode(node, Diagnostics.let_declarations_can_only_be_declared_inside_a_block); + } + else if (isVarConst(node.declarationList)) { + return grammarErrorOnNode(node, Diagnostics.const_declarations_can_only_be_declared_inside_a_block); + } } + } - function checkGrammarForDisallowedLetOrConstStatement(node: VariableStatement) { - if (!allowLetAndConstDeclarations(node.parent)) { - if (isLet(node.declarationList)) { - return grammarErrorOnNode(node, Diagnostics.let_declarations_can_only_be_declared_inside_a_block); + function checkGrammarMetaProperty(node: MetaProperty) { + const escapedText = node.name.escapedText; + switch (node.keywordToken) { + case SyntaxKind.NewKeyword: + if (escapedText !== "target") { + return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "target"); } - else if (isVarConst(node.declarationList)) { - return grammarErrorOnNode(node, Diagnostics.const_declarations_can_only_be_declared_inside_a_block); + break; + case SyntaxKind.ImportKeyword: + if (escapedText !== "meta") { + return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "meta"); } - } + break; } + } - function checkGrammarMetaProperty(node: MetaProperty) { - const escapedText = node.name.escapedText; - switch (node.keywordToken) { - case SyntaxKind.NewKeyword: - if (escapedText !== "target") { - return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "target"); - } - break; - case SyntaxKind.ImportKeyword: - if (escapedText !== "meta") { - return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "meta"); - } - break; - } - } + function hasParseDiagnostics(sourceFile: SourceFile): boolean { + return sourceFile.parseDiagnostics.length > 0; + } - function hasParseDiagnostics(sourceFile: SourceFile): boolean { - return sourceFile.parseDiagnostics.length > 0; + function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2)); + return true; } + return false; + } - function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2)); - return true; - } - return false; + function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(nodeForSourceFile); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2)); + return true; } + return false; + } - function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(nodeForSourceFile); - if (!hasParseDiagnostics(sourceFile)) { - diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2)); - return true; - } - return false; + function grammarErrorOnNodeSkippedOn(key: keyof CompilerOptions, node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + errorSkippedOn(key, node, message, arg0, arg1, arg2); + return true; } + return false; + } - function grammarErrorOnNodeSkippedOn(key: keyof CompilerOptions, node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - errorSkippedOn(key, node, message, arg0, arg1, arg2); - return true; - } - return false; + function grammarErrorOnNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createDiagnosticForNode(node, message, arg0, arg1, arg2)); + return true; } + return false; + } - function grammarErrorOnNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - diagnostics.add(createDiagnosticForNode(node, message, arg0, arg1, arg2)); - return true; - } - return false; + function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { + const jsdocTypeParameters = isInJSFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined; + const range = node.typeParameters || jsdocTypeParameters && firstOrUndefined(jsdocTypeParameters); + if (range) { + const pos = range.pos === range.end ? range.pos : skipTrivia(getSourceFileOfNode(node).text, range.pos); + return grammarErrorAtPos(node, pos, range.end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration); } + } - function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { - const jsdocTypeParameters = isInJSFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined; - const range = node.typeParameters || jsdocTypeParameters && firstOrUndefined(jsdocTypeParameters); - if (range) { - const pos = range.pos === range.end ? range.pos : skipTrivia(getSourceFileOfNode(node).text, range.pos); - return grammarErrorAtPos(node, pos, range.end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration); - } + function checkGrammarConstructorTypeAnnotation(node: ConstructorDeclaration) { + const type = node.type || getEffectiveReturnTypeNode(node); + if (type) { + return grammarErrorOnNode(type, Diagnostics.Type_annotation_cannot_appear_on_a_constructor_declaration); } + } - function checkGrammarConstructorTypeAnnotation(node: ConstructorDeclaration) { - const type = node.type || getEffectiveReturnTypeNode(node); - if (type) { - return grammarErrorOnNode(type, Diagnostics.Type_annotation_cannot_appear_on_a_constructor_declaration); - } + function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) { + if (isComputedPropertyName(node.name) && isBinaryExpression(node.name.expression) && node.name.expression.operatorToken.kind === SyntaxKind.InKeyword) { + return grammarErrorOnNode( + (node.parent as ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode).members[0], + Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); } - - function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) { - if (isComputedPropertyName(node.name) && isBinaryExpression(node.name.expression) && node.name.expression.operatorToken.kind === SyntaxKind.InKeyword) { - return grammarErrorOnNode( - (node.parent as ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode).members[0], - Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); + if (isClassLike(node.parent)) { + if (isStringLiteral(node.name) && node.name.text === "constructor") { + return grammarErrorOnNode(node.name, Diagnostics.Classes_may_not_have_a_field_named_constructor); } - if (isClassLike(node.parent)) { - if (isStringLiteral(node.name) && node.name.text === "constructor") { - return grammarErrorOnNode(node.name, Diagnostics.Classes_may_not_have_a_field_named_constructor); - } - if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_have_a_simple_literal_type_or_a_unique_symbol_type)) { - return true; - } - if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { - return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); - } - if (languageVersion < ScriptTarget.ES2015 && isAutoAccessorPropertyDeclaration(node)) { - return grammarErrorOnNode(node.name, Diagnostics.Properties_with_the_accessor_modifier_are_only_available_when_targeting_ECMAScript_2015_and_higher); - } - if (isAutoAccessorPropertyDeclaration(node) && checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_accessor_property_cannot_be_declared_optional)) { - return true; - } + if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_have_a_simple_literal_type_or_a_unique_symbol_type)) { + return true; } - else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { - if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { - return true; - } - - // Interfaces cannot contain property declarations - Debug.assertNode(node, isPropertySignature); - if (node.initializer) { - return grammarErrorOnNode(node.initializer, Diagnostics.An_interface_property_cannot_have_an_initializer); - } + if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { + return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); } - else if (isTypeLiteralNode(node.parent)) { - if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { - return true; - } - // Type literals cannot contain property declarations - Debug.assertNode(node, isPropertySignature); - if (node.initializer) { - return grammarErrorOnNode(node.initializer, Diagnostics.A_type_literal_property_cannot_have_an_initializer); - } + if (languageVersion < ScriptTarget.ES2015 && isAutoAccessorPropertyDeclaration(node)) { + return grammarErrorOnNode(node.name, Diagnostics.Properties_with_the_accessor_modifier_are_only_available_when_targeting_ECMAScript_2015_and_higher); } - - if (node.flags & NodeFlags.Ambient) { - checkAmbientInitializer(node); + if (isAutoAccessorPropertyDeclaration(node) && checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_accessor_property_cannot_be_declared_optional)) { + return true; + } + } + else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { + if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { + return true; } - if (isPropertyDeclaration(node) && node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer || - node.flags & NodeFlags.Ambient || isStatic(node) || hasAbstractModifier(node))) { - const message = node.initializer - ? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions - : !node.type - ? Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations - : Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context; - return grammarErrorOnNode(node.exclamationToken, message); + // Interfaces cannot contain property declarations + Debug.assertNode(node, isPropertySignature); + if (node.initializer) { + return grammarErrorOnNode(node.initializer, Diagnostics.An_interface_property_cannot_have_an_initializer); } } - - function checkGrammarTopLevelElementForRequiredDeclareModifier(node: Node): boolean { - // A declare modifier is required for any top level .d.ts declaration except export=, export default, export as namespace - // interfaces and imports categories: - // - // DeclarationElement: - // ExportAssignment - // export_opt InterfaceDeclaration - // export_opt TypeAliasDeclaration - // export_opt ImportDeclaration - // export_opt ExternalImportDeclaration - // export_opt AmbientDeclaration - // - // TODO: The spec needs to be amended to reflect this grammar. - if (node.kind === SyntaxKind.InterfaceDeclaration || - node.kind === SyntaxKind.TypeAliasDeclaration || - node.kind === SyntaxKind.ImportDeclaration || - node.kind === SyntaxKind.ImportEqualsDeclaration || - node.kind === SyntaxKind.ExportDeclaration || - node.kind === SyntaxKind.ExportAssignment || - node.kind === SyntaxKind.NamespaceExportDeclaration || - hasSyntacticModifier(node, ModifierFlags.Ambient | ModifierFlags.Export | ModifierFlags.Default)) { - return false; + else if (isTypeLiteralNode(node.parent)) { + if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { + return true; } + // Type literals cannot contain property declarations + Debug.assertNode(node, isPropertySignature); + if (node.initializer) { + return grammarErrorOnNode(node.initializer, Diagnostics.A_type_literal_property_cannot_have_an_initializer); + } + } - return grammarErrorOnFirstToken(node, Diagnostics.Top_level_declarations_in_d_ts_files_must_start_with_either_a_declare_or_export_modifier); + if (node.flags & NodeFlags.Ambient) { + checkAmbientInitializer(node); } - function checkGrammarTopLevelElementsForRequiredDeclareModifier(file: SourceFile): boolean { - for (const decl of file.statements) { - if (isDeclaration(decl) || decl.kind === SyntaxKind.VariableStatement) { - if (checkGrammarTopLevelElementForRequiredDeclareModifier(decl)) { - return true; - } - } - } - return false; + if (isPropertyDeclaration(node) && node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer || + node.flags & NodeFlags.Ambient || isStatic(node) || hasAbstractModifier(node))) { + const message = node.initializer + ? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions + : !node.type + ? Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations + : Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context; + return grammarErrorOnNode(node.exclamationToken, message); } + } - function checkGrammarSourceFile(node: SourceFile): boolean { - return !!(node.flags & NodeFlags.Ambient) && checkGrammarTopLevelElementsForRequiredDeclareModifier(node); + function checkGrammarTopLevelElementForRequiredDeclareModifier(node: Node): boolean { + // A declare modifier is required for any top level .d.ts declaration except export=, export default, export as namespace + // interfaces and imports categories: + // + // DeclarationElement: + // ExportAssignment + // export_opt InterfaceDeclaration + // export_opt TypeAliasDeclaration + // export_opt ImportDeclaration + // export_opt ExternalImportDeclaration + // export_opt AmbientDeclaration + // + // TODO: The spec needs to be amended to reflect this grammar. + if (node.kind === SyntaxKind.InterfaceDeclaration || + node.kind === SyntaxKind.TypeAliasDeclaration || + node.kind === SyntaxKind.ImportDeclaration || + node.kind === SyntaxKind.ImportEqualsDeclaration || + node.kind === SyntaxKind.ExportDeclaration || + node.kind === SyntaxKind.ExportAssignment || + node.kind === SyntaxKind.NamespaceExportDeclaration || + hasSyntacticModifier(node, ModifierFlags.Ambient | ModifierFlags.Export | ModifierFlags.Default)) { + return false; } - function checkGrammarStatementInAmbientContext(node: Node): boolean { - if (node.flags & NodeFlags.Ambient) { - // Find containing block which is either Block, ModuleBlock, SourceFile - const links = getNodeLinks(node); - if (!links.hasReportedStatementInAmbientContext && (isFunctionLike(node.parent) || isAccessor(node.parent))) { - return getNodeLinks(node).hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); - } + return grammarErrorOnFirstToken(node, Diagnostics.Top_level_declarations_in_d_ts_files_must_start_with_either_a_declare_or_export_modifier); + } - // We are either parented by another statement, or some sort of block. - // If we're in a block, we only want to really report an error once - // to prevent noisiness. So use a bit on the block to indicate if - // this has already been reported, and don't report if it has. - // - if (node.parent.kind === SyntaxKind.Block || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - const links = getNodeLinks(node.parent); - // Check if the containing block ever report this error - if (!links.hasReportedStatementInAmbientContext) { - return links.hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.Statements_are_not_allowed_in_ambient_contexts); - } - } - else { - // We must be parented by a statement. If so, there's no need - // to report the error as our parent will have already done it. - // Debug.assert(isStatement(node.parent)); + function checkGrammarTopLevelElementsForRequiredDeclareModifier(file: SourceFile): boolean { + for (const decl of file.statements) { + if (isDeclaration(decl) || decl.kind === SyntaxKind.VariableStatement) { + if (checkGrammarTopLevelElementForRequiredDeclareModifier(decl)) { + return true; } } - return false; } + return false; + } - function checkGrammarNumericLiteral(node: NumericLiteral): boolean { - // Grammar checking - if (node.numericLiteralFlags & TokenFlags.Octal) { - let diagnosticMessage: DiagnosticMessage | undefined; - if (languageVersion >= ScriptTarget.ES5) { - diagnosticMessage = Diagnostics.Octal_literals_are_not_available_when_targeting_ECMAScript_5_and_higher_Use_the_syntax_0; - } - else if (isChildOfNodeWithKind(node, SyntaxKind.LiteralType)) { - diagnosticMessage = Diagnostics.Octal_literal_types_must_use_ES2015_syntax_Use_the_syntax_0; - } - else if (isChildOfNodeWithKind(node, SyntaxKind.EnumMember)) { - diagnosticMessage = Diagnostics.Octal_literals_are_not_allowed_in_enums_members_initializer_Use_the_syntax_0; - } - if (diagnosticMessage) { - const withMinus = isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.MinusToken; - const literal = (withMinus ? "-" : "") + "0o" + node.text; - return grammarErrorOnNode(withMinus ? node.parent : node, diagnosticMessage, literal); - } + function checkGrammarSourceFile(node: SourceFile): boolean { + return !!(node.flags & NodeFlags.Ambient) && checkGrammarTopLevelElementsForRequiredDeclareModifier(node); + } + + function checkGrammarStatementInAmbientContext(node: Node): boolean { + if (node.flags & NodeFlags.Ambient) { + // Find containing block which is either Block, ModuleBlock, SourceFile + const links = getNodeLinks(node); + if (!links.hasReportedStatementInAmbientContext && (isFunctionLike(node.parent) || isAccessor(node.parent))) { + return getNodeLinks(node).hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); } - // Realism (size) checking - checkNumericLiteralValueSize(node); + // We are either parented by another statement, or some sort of block. + // If we're in a block, we only want to really report an error once + // to prevent noisiness. So use a bit on the block to indicate if + // this has already been reported, and don't report if it has. + // + if (node.parent.kind === SyntaxKind.Block || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + const links = getNodeLinks(node.parent); + // Check if the containing block ever report this error + if (!links.hasReportedStatementInAmbientContext) { + return links.hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.Statements_are_not_allowed_in_ambient_contexts); + } + } + else { + // We must be parented by a statement. If so, there's no need + // to report the error as our parent will have already done it. + // Debug.assert(isStatement(node.parent)); + } + } + return false; + } - return false; + function checkGrammarNumericLiteral(node: NumericLiteral): boolean { + // Grammar checking + if (node.numericLiteralFlags & TokenFlags.Octal) { + let diagnosticMessage: DiagnosticMessage | undefined; + if (languageVersion >= ScriptTarget.ES5) { + diagnosticMessage = Diagnostics.Octal_literals_are_not_available_when_targeting_ECMAScript_5_and_higher_Use_the_syntax_0; + } + else if (isChildOfNodeWithKind(node, SyntaxKind.LiteralType)) { + diagnosticMessage = Diagnostics.Octal_literal_types_must_use_ES2015_syntax_Use_the_syntax_0; + } + else if (isChildOfNodeWithKind(node, SyntaxKind.EnumMember)) { + diagnosticMessage = Diagnostics.Octal_literals_are_not_allowed_in_enums_members_initializer_Use_the_syntax_0; + } + if (diagnosticMessage) { + const withMinus = isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.MinusToken; + const literal = (withMinus ? "-" : "") + "0o" + node.text; + return grammarErrorOnNode(withMinus ? node.parent : node, diagnosticMessage, literal); + } } - function checkNumericLiteralValueSize(node: NumericLiteral) { - // We should test against `getTextOfNode(node)` rather than `node.text`, because `node.text` for large numeric literals can contain "." - // e.g. `node.text` for numeric literal `1100000000000000000000` is `1.1e21`. - const isFractional = getTextOfNode(node).indexOf(".") !== -1; - const isScientific = node.numericLiteralFlags & TokenFlags.Scientific; + // Realism (size) checking + checkNumericLiteralValueSize(node); - // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint - // Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway - if (isFractional || isScientific) { - return; - } + return false; + } - // Here `node` is guaranteed to be a numeric literal representing an integer. - // We need to judge whether the integer `node` represents is <= 2 ** 53 - 1, which can be accomplished by comparing to `value` defined below because: - // 1) when `node` represents an integer <= 2 ** 53 - 1, `node.text` is its exact string representation and thus `value` precisely represents the integer. - // 2) otherwise, although `node.text` may be imprecise string representation, its mathematical value and consequently `value` cannot be less than 2 ** 53, - // thus the result of the predicate won't be affected. - const value = +node.text; - if (value <= 2 ** 53 - 1) { - return; - } + function checkNumericLiteralValueSize(node: NumericLiteral) { + // We should test against `getTextOfNode(node)` rather than `node.text`, because `node.text` for large numeric literals can contain "." + // e.g. `node.text` for numeric literal `1100000000000000000000` is `1.1e21`. + const isFractional = getTextOfNode(node).indexOf(".") !== -1; + const isScientific = node.numericLiteralFlags & TokenFlags.Scientific; - addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers)); + // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint + // Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway + if (isFractional || isScientific) { + return; } - function checkGrammarBigIntLiteral(node: BigIntLiteral): boolean { - const literalType = isLiteralTypeNode(node.parent) || - isPrefixUnaryExpression(node.parent) && isLiteralTypeNode(node.parent.parent); - if (!literalType) { - if (languageVersion < ScriptTarget.ES2020) { - if (grammarErrorOnNode(node, Diagnostics.BigInt_literals_are_not_available_when_targeting_lower_than_ES2020)) { - return true; - } - } - } - return false; + // Here `node` is guaranteed to be a numeric literal representing an integer. + // We need to judge whether the integer `node` represents is <= 2 ** 53 - 1, which can be accomplished by comparing to `value` defined below because: + // 1) when `node` represents an integer <= 2 ** 53 - 1, `node.text` is its exact string representation and thus `value` precisely represents the integer. + // 2) otherwise, although `node.text` may be imprecise string representation, its mathematical value and consequently `value` cannot be less than 2 ** 53, + // thus the result of the predicate won't be affected. + const value = +node.text; + if (value <= 2 ** 53 - 1) { + return; } - function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, arg0, arg1, arg2)); - return true; - } - return false; - } + addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers)); + } - function getAmbientModules(): Symbol[] { - if (!ambientModulesCache) { - ambientModulesCache = []; - globals.forEach((global, sym) => { - // No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module. - if (ambientModuleSymbolRegex.test(sym as string)) { - ambientModulesCache!.push(global); - } - }); + function checkGrammarBigIntLiteral(node: BigIntLiteral): boolean { + const literalType = isLiteralTypeNode(node.parent) || + isPrefixUnaryExpression(node.parent) && isLiteralTypeNode(node.parent.parent); + if (!literalType) { + if (languageVersion < ScriptTarget.ES2020) { + if (grammarErrorOnNode(node, Diagnostics.BigInt_literals_are_not_available_when_targeting_lower_than_ES2020)) { + return true; + } } - return ambientModulesCache; } + return false; + } - function checkGrammarImportClause(node: ImportClause): boolean { - if (node.isTypeOnly && node.name && node.namedBindings) { - return grammarErrorOnNode(node, Diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both); - } - if (node.isTypeOnly && node.namedBindings?.kind === SyntaxKind.NamedImports) { - return checkGrammarNamedImportsOrExports(node.namedBindings); - } - return false; + function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, arg0, arg1, arg2)); + return true; } + return false; + } - function checkGrammarNamedImportsOrExports(namedBindings: NamedImportsOrExports): boolean { - return !!forEach(namedBindings.elements, specifier => { - if (specifier.isTypeOnly) { - return grammarErrorOnFirstToken( - specifier, - specifier.kind === SyntaxKind.ImportSpecifier - ? Diagnostics.The_type_modifier_cannot_be_used_on_a_named_import_when_import_type_is_used_on_its_import_statement - : Diagnostics.The_type_modifier_cannot_be_used_on_a_named_export_when_export_type_is_used_on_its_export_statement); + function getAmbientModules(): Symbol[] { + if (!ambientModulesCache) { + ambientModulesCache = []; + globals.forEach((global, sym) => { + // No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module. + if (ambientModuleSymbolRegex.test(sym as string)) { + ambientModulesCache!.push(global); } }); } + return ambientModulesCache; + } - function checkGrammarImportCallExpression(node: ImportCall): boolean { - if (moduleKind === ModuleKind.ES2015) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_es2022_esnext_commonjs_amd_system_umd_node16_or_nodenext); - } + function checkGrammarImportClause(node: ImportClause): boolean { + if (node.isTypeOnly && node.name && node.namedBindings) { + return grammarErrorOnNode(node, Diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both); + } + if (node.isTypeOnly && node.namedBindings?.kind === SyntaxKind.NamedImports) { + return checkGrammarNamedImportsOrExports(node.namedBindings); + } + return false; + } - if (node.typeArguments) { - return grammarErrorOnNode(node, Diagnostics.This_use_of_import_is_invalid_import_calls_can_be_written_but_they_must_have_parentheses_and_cannot_have_type_arguments); + function checkGrammarNamedImportsOrExports(namedBindings: NamedImportsOrExports): boolean { + return !!forEach(namedBindings.elements, specifier => { + if (specifier.isTypeOnly) { + return grammarErrorOnFirstToken( + specifier, + specifier.kind === SyntaxKind.ImportSpecifier + ? Diagnostics.The_type_modifier_cannot_be_used_on_a_named_import_when_import_type_is_used_on_its_import_statement + : Diagnostics.The_type_modifier_cannot_be_used_on_a_named_export_when_export_type_is_used_on_its_export_statement); } + }); + } - const nodeArguments = node.arguments; - if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.NodeNext && moduleKind !== ModuleKind.Node16) { - // We are allowed trailing comma after proposal-import-assertions. - checkGrammarForDisallowedTrailingComma(nodeArguments); + function checkGrammarImportCallExpression(node: ImportCall): boolean { + if (moduleKind === ModuleKind.ES2015) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_es2022_esnext_commonjs_amd_system_umd_node16_or_nodenext); + } - if (nodeArguments.length > 1) { - const assertionArgument = nodeArguments[1]; - return grammarErrorOnNode(assertionArgument, Diagnostics.Dynamic_imports_only_support_a_second_argument_when_the_module_option_is_set_to_esnext_node16_or_nodenext); - } - } + if (node.typeArguments) { + return grammarErrorOnNode(node, Diagnostics.This_use_of_import_is_invalid_import_calls_can_be_written_but_they_must_have_parentheses_and_cannot_have_type_arguments); + } - if (nodeArguments.length === 0 || nodeArguments.length > 2) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_assertion_as_arguments); - } + const nodeArguments = node.arguments; + if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.NodeNext && moduleKind !== ModuleKind.Node16) { + // We are allowed trailing comma after proposal-import-assertions. + checkGrammarForDisallowedTrailingComma(nodeArguments); - // see: parseArgumentOrArrayLiteralElement...we use this function which parse arguments of callExpression to parse specifier for dynamic import. - // parseArgumentOrArrayLiteralElement allows spread element to be in an argument list which is not allowed as specifier in dynamic import. - const spreadElement = find(nodeArguments, isSpreadElement); - if (spreadElement) { - return grammarErrorOnNode(spreadElement, Diagnostics.Argument_of_dynamic_import_cannot_be_spread_element); + if (nodeArguments.length > 1) { + const assertionArgument = nodeArguments[1]; + return grammarErrorOnNode(assertionArgument, Diagnostics.Dynamic_imports_only_support_a_second_argument_when_the_module_option_is_set_to_esnext_node16_or_nodenext); } - return false; } - function findMatchingTypeReferenceOrTypeAliasReference(source: Type, unionTarget: UnionOrIntersectionType) { - const sourceObjectFlags = getObjectFlags(source); - if (sourceObjectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous) && unionTarget.flags & TypeFlags.Union) { - return find(unionTarget.types, target => { - if (target.flags & TypeFlags.Object) { - const overlapObjFlags = sourceObjectFlags & getObjectFlags(target); - if (overlapObjFlags & ObjectFlags.Reference) { - return (source as TypeReference).target === (target as TypeReference).target; - } - if (overlapObjFlags & ObjectFlags.Anonymous) { - return !!(source as AnonymousType).aliasSymbol && (source as AnonymousType).aliasSymbol === (target as AnonymousType).aliasSymbol; - } + if (nodeArguments.length === 0 || nodeArguments.length > 2) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_assertion_as_arguments); + } + + // see: parseArgumentOrArrayLiteralElement...we use this function which parse arguments of callExpression to parse specifier for dynamic import. + // parseArgumentOrArrayLiteralElement allows spread element to be in an argument list which is not allowed as specifier in dynamic import. + const spreadElement = find(nodeArguments, isSpreadElement); + if (spreadElement) { + return grammarErrorOnNode(spreadElement, Diagnostics.Argument_of_dynamic_import_cannot_be_spread_element); + } + return false; + } + + function findMatchingTypeReferenceOrTypeAliasReference(source: Type, unionTarget: UnionOrIntersectionType) { + const sourceObjectFlags = getObjectFlags(source); + if (sourceObjectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous) && unionTarget.flags & TypeFlags.Union) { + return find(unionTarget.types, target => { + if (target.flags & TypeFlags.Object) { + const overlapObjFlags = sourceObjectFlags & getObjectFlags(target); + if (overlapObjFlags & ObjectFlags.Reference) { + return (source as TypeReference).target === (target as TypeReference).target; } - return false; - }); - } + if (overlapObjFlags & ObjectFlags.Anonymous) { + return !!(source as AnonymousType).aliasSymbol && (source as AnonymousType).aliasSymbol === (target as AnonymousType).aliasSymbol; + } + } + return false; + }); } + } - function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) { - if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && someType(unionTarget, isArrayLikeType)) { - return find(unionTarget.types, t => !isArrayLikeType(t)); - } + function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) { + if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && someType(unionTarget, isArrayLikeType)) { + return find(unionTarget.types, t => !isArrayLikeType(t)); } + } - function findBestTypeForInvokable(source: Type, unionTarget: UnionOrIntersectionType) { - let signatureKind = SignatureKind.Call; - const hasSignatures = getSignaturesOfType(source, signatureKind).length > 0 || - (signatureKind = SignatureKind.Construct, getSignaturesOfType(source, signatureKind).length > 0); - if (hasSignatures) { - return find(unionTarget.types, t => getSignaturesOfType(t, signatureKind).length > 0); - } + function findBestTypeForInvokable(source: Type, unionTarget: UnionOrIntersectionType) { + let signatureKind = SignatureKind.Call; + const hasSignatures = getSignaturesOfType(source, signatureKind).length > 0 || + (signatureKind = SignatureKind.Construct, getSignaturesOfType(source, signatureKind).length > 0); + if (hasSignatures) { + return find(unionTarget.types, t => getSignaturesOfType(t, signatureKind).length > 0); } + } - function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) { - let bestMatch: Type | undefined; - if (!(source.flags & (TypeFlags.Primitive | TypeFlags.InstantiablePrimitive))) { - let matchingCount = 0; - for (const target of unionTarget.types) { - if (!(target.flags & (TypeFlags.Primitive | TypeFlags.InstantiablePrimitive))) { - const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]); - if (overlap.flags & TypeFlags.Index) { - // perfect overlap of keys - return target; - } - else if (isUnitType(overlap) || overlap.flags & TypeFlags.Union) { - // We only want to account for literal types otherwise. - // If we have a union of index types, it seems likely that we - // needed to elaborate between two generic mapped types anyway. - const len = overlap.flags & TypeFlags.Union ? countWhere((overlap as UnionType).types, isUnitType) : 1; - if (len >= matchingCount) { - bestMatch = target; - matchingCount = len; - } + function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) { + let bestMatch: Type | undefined; + if (!(source.flags & (TypeFlags.Primitive | TypeFlags.InstantiablePrimitive))) { + let matchingCount = 0; + for (const target of unionTarget.types) { + if (!(target.flags & (TypeFlags.Primitive | TypeFlags.InstantiablePrimitive))) { + const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]); + if (overlap.flags & TypeFlags.Index) { + // perfect overlap of keys + return target; + } + else if (isUnitType(overlap) || overlap.flags & TypeFlags.Union) { + // We only want to account for literal types otherwise. + // If we have a union of index types, it seems likely that we + // needed to elaborate between two generic mapped types anyway. + const len = overlap.flags & TypeFlags.Union ? countWhere((overlap as UnionType).types, isUnitType) : 1; + if (len >= matchingCount) { + bestMatch = target; + matchingCount = len; } } } } - return bestMatch; } + 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; - } + 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; } + return type; + } - // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly - function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary, skipPartial?: boolean) { - if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { - const match = getMatchingUnionConstituentForType(target as UnionType, source); - if (match) { - return match; - } - const sourceProperties = getPropertiesOfType(source); - if (sourceProperties) { - const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); - if (sourcePropertiesFiltered) { - return discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo, /*defaultValue*/ undefined, skipPartial); - } + // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly + function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary, skipPartial?: boolean) { + if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { + const match = getMatchingUnionConstituentForType(target as UnionType, source); + if (match) { + return match; + } + const sourceProperties = getPropertiesOfType(source); + if (sourceProperties) { + const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); + if (sourcePropertiesFiltered) { + return discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo, /*defaultValue*/ undefined, skipPartial); } } - return undefined; } + return undefined; } +} - function isNotAccessor(declaration: Declaration): boolean { - // Accessors check for their own matching duplicates, and in contexts where they are valid, there are already duplicate identifier checks - return !isAccessor(declaration); - } +function isNotAccessor(declaration: Declaration): boolean { + // Accessors check for their own matching duplicates, and in contexts where they are valid, there are already duplicate identifier checks + return !isAccessor(declaration); +} - function isNotOverload(declaration: Declaration): boolean { - return (declaration.kind !== SyntaxKind.FunctionDeclaration && declaration.kind !== SyntaxKind.MethodDeclaration) || - !!(declaration as FunctionDeclaration).body; - } +function isNotOverload(declaration: Declaration): boolean { + return (declaration.kind !== SyntaxKind.FunctionDeclaration && declaration.kind !== SyntaxKind.MethodDeclaration) || + !!(declaration as FunctionDeclaration).body; +} - /** Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`. */ - function isDeclarationNameOrImportPropertyName(name: Node): boolean { - switch (name.parent.kind) { - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - return isIdentifier(name); - default: - return isDeclarationName(name); - } +/** Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`. */ +function isDeclarationNameOrImportPropertyName(name: Node): boolean { + switch (name.parent.kind) { + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + return isIdentifier(name); + default: + return isDeclarationName(name); } +} - namespace JsxNames { - export const JSX = "JSX" as __String; - export const IntrinsicElements = "IntrinsicElements" as __String; - export const ElementClass = "ElementClass" as __String; - export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String; // TODO: Deprecate and remove support - export const ElementChildrenAttributeNameContainer = "ElementChildrenAttribute" as __String; - export const Element = "Element" as __String; - export const IntrinsicAttributes = "IntrinsicAttributes" as __String; - export const IntrinsicClassAttributes = "IntrinsicClassAttributes" as __String; - export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String; - } +namespace JsxNames { + export const JSX = "JSX" as __String; + export const IntrinsicElements = "IntrinsicElements" as __String; + export const ElementClass = "ElementClass" as __String; + export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String; // TODO: Deprecate and remove support + export const ElementChildrenAttributeNameContainer = "ElementChildrenAttribute" as __String; + export const Element = "Element" as __String; + export const IntrinsicAttributes = "IntrinsicAttributes" as __String; + export const IntrinsicClassAttributes = "IntrinsicClassAttributes" as __String; + export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String; +} - function getIterationTypesKeyFromIterationTypeKind(typeKind: IterationTypeKind) { - switch (typeKind) { - case IterationTypeKind.Yield: return "yieldType"; - case IterationTypeKind.Return: return "returnType"; - case IterationTypeKind.Next: return "nextType"; - } +function getIterationTypesKeyFromIterationTypeKind(typeKind: IterationTypeKind) { + switch (typeKind) { + case IterationTypeKind.Yield: return "yieldType"; + case IterationTypeKind.Return: return "returnType"; + case IterationTypeKind.Next: return "nextType"; } +} - export function signatureHasRestParameter(s: Signature) { - return !!(s.flags & SignatureFlags.HasRestParameter); - } +/** @internal */ +export function signatureHasRestParameter(s: Signature) { + return !!(s.flags & SignatureFlags.HasRestParameter); +} - export function signatureHasLiteralTypes(s: Signature) { - return !!(s.flags & SignatureFlags.HasLiteralTypes); - } +/** @internal */ +export function signatureHasLiteralTypes(s: Signature) { + return !!(s.flags & SignatureFlags.HasLiteralTypes); } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 5afb2ee8cfb69..aa0e7c7b91dbe 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1,3767 +1,3795 @@ -namespace ts { - /* @internal */ - export const compileOnSaveCommandLineOption: CommandLineOption = { - name: "compileOnSave", +import { + AlternateModeDiagnostics, append, arrayFrom, ArrayLiteralExpression, arrayToMap, assign, BuildOptions, + changeExtension, CharacterCodes, combinePaths, CommandLineOption, CommandLineOptionOfCustomType, + CommandLineOptionOfListType, CompilerOptions, CompilerOptionsValue, ConfigFileSpecs, containsPath, + convertToRelativePath, createCompilerDiagnostic, createDiagnosticForNodeInSourceFile, createGetCanonicalFileName, + Debug, Diagnostic, DiagnosticMessage, Diagnostics, DidYouMeanOptionsDiagnostics, directorySeparator, emptyArray, + endsWith, ensureTrailingDirectorySeparator, ESMap, every, Expression, extend, Extension, FileExtensionInfo, + fileExtensionIs, fileExtensionIsOneOf, filter, filterMutate, find, findIndex, firstDefined, flatten, forEach, + forEachEntry, getBaseFileName, getDirectoryPath, getEntries, getFileMatcherPatterns, getLocaleSpecificMessage, + getNormalizedAbsolutePath, getRegexFromPattern, getRegularExpressionForWildcard, getRegularExpressionsForWildcards, + getRelativePathFromFile, getSpellingSuggestion, getSupportedExtensions, + getSupportedExtensionsWithJsonIfResolveJsonModule, getTextOfPropertyName, getTsConfigPropArray, + getTsConfigPropArrayElementValue, hasExtension, hasProperty, ImportsNotUsedAsValues, isArray, + isArrayLiteralExpression, isComputedNonLiteralName, isImplicitGlob, isObjectLiteralExpression, isRootedDiskPath, + isString, isStringDoubleQuoted, isStringLiteral, JsonSourceFile, JsxEmit, length, map, Map, mapDefined, mapIterator, + MapLike, ModuleDetectionKind, ModuleKind, ModuleResolutionKind, NewLineKind, Node, NodeArray, + nodeModuleNameResolver, normalizePath, normalizeSlashes, NumericLiteral, ObjectLiteralExpression, ParseConfigHost, + ParsedCommandLine, parseJsonText, Path, PollingWatchKind, PrefixUnaryExpression, ProjectReference, PropertyName, + Push, removeTrailingDirectorySeparator, returnTrue, ScriptTarget, startsWith, StringLiteral, SyntaxKind, sys, + toFileNameLowerCase, toPath, tracing, trimString, TsConfigOnlyOption, TsConfigSourceFile, TypeAcquisition, + unescapeLeadingUnderscores, WatchDirectoryFlags, WatchDirectoryKind, WatchFileKind, WatchOptions, +} from "./_namespaces/ts"; + +/** @internal */ +export const compileOnSaveCommandLineOption: CommandLineOption = { + name: "compileOnSave", + type: "boolean", + defaultValueDescription: false, +}; + +const jsxOptionMap = new Map(getEntries({ + "preserve": JsxEmit.Preserve, + "react-native": JsxEmit.ReactNative, + "react": JsxEmit.React, + "react-jsx": JsxEmit.ReactJSX, + "react-jsxdev": JsxEmit.ReactJSXDev, +})); + +/** @internal */ +export const inverseJsxOptionMap = new Map(arrayFrom(mapIterator(jsxOptionMap.entries(), ([key, value]: [string, JsxEmit]) => ["" + value, key] as const))); + +// NOTE: The order here is important to default lib ordering as entries will have the same +// order in the generated program (see `getDefaultLibPriority` in program.ts). This +// order also affects overload resolution when a type declared in one lib is +// augmented in another lib. +const libEntries: [string, string][] = [ + // JavaScript only + ["es5", "lib.es5.d.ts"], + ["es6", "lib.es2015.d.ts"], + ["es2015", "lib.es2015.d.ts"], + ["es7", "lib.es2016.d.ts"], + ["es2016", "lib.es2016.d.ts"], + ["es2017", "lib.es2017.d.ts"], + ["es2018", "lib.es2018.d.ts"], + ["es2019", "lib.es2019.d.ts"], + ["es2020", "lib.es2020.d.ts"], + ["es2021", "lib.es2021.d.ts"], + ["es2022", "lib.es2022.d.ts"], + ["esnext", "lib.esnext.d.ts"], + // Host only + ["dom", "lib.dom.d.ts"], + ["dom.iterable", "lib.dom.iterable.d.ts"], + ["webworker", "lib.webworker.d.ts"], + ["webworker.importscripts", "lib.webworker.importscripts.d.ts"], + ["webworker.iterable", "lib.webworker.iterable.d.ts"], + ["scripthost", "lib.scripthost.d.ts"], + // ES2015 Or ESNext By-feature options + ["es2015.core", "lib.es2015.core.d.ts"], + ["es2015.collection", "lib.es2015.collection.d.ts"], + ["es2015.generator", "lib.es2015.generator.d.ts"], + ["es2015.iterable", "lib.es2015.iterable.d.ts"], + ["es2015.promise", "lib.es2015.promise.d.ts"], + ["es2015.proxy", "lib.es2015.proxy.d.ts"], + ["es2015.reflect", "lib.es2015.reflect.d.ts"], + ["es2015.symbol", "lib.es2015.symbol.d.ts"], + ["es2015.symbol.wellknown", "lib.es2015.symbol.wellknown.d.ts"], + ["es2016.array.include", "lib.es2016.array.include.d.ts"], + ["es2017.object", "lib.es2017.object.d.ts"], + ["es2017.sharedmemory", "lib.es2017.sharedmemory.d.ts"], + ["es2017.string", "lib.es2017.string.d.ts"], + ["es2017.intl", "lib.es2017.intl.d.ts"], + ["es2017.typedarrays", "lib.es2017.typedarrays.d.ts"], + ["es2018.asyncgenerator", "lib.es2018.asyncgenerator.d.ts"], + ["es2018.asynciterable", "lib.es2018.asynciterable.d.ts"], + ["es2018.intl", "lib.es2018.intl.d.ts"], + ["es2018.promise", "lib.es2018.promise.d.ts"], + ["es2018.regexp", "lib.es2018.regexp.d.ts"], + ["es2019.array", "lib.es2019.array.d.ts"], + ["es2019.object", "lib.es2019.object.d.ts"], + ["es2019.string", "lib.es2019.string.d.ts"], + ["es2019.symbol", "lib.es2019.symbol.d.ts"], + ["es2019.intl", "lib.es2019.intl.d.ts"], + ["es2020.bigint", "lib.es2020.bigint.d.ts"], + ["es2020.date", "lib.es2020.date.d.ts"], + ["es2020.promise", "lib.es2020.promise.d.ts"], + ["es2020.sharedmemory", "lib.es2020.sharedmemory.d.ts"], + ["es2020.string", "lib.es2020.string.d.ts"], + ["es2020.symbol.wellknown", "lib.es2020.symbol.wellknown.d.ts"], + ["es2020.intl", "lib.es2020.intl.d.ts"], + ["es2020.number", "lib.es2020.number.d.ts"], + ["es2021.promise", "lib.es2021.promise.d.ts"], + ["es2021.string", "lib.es2021.string.d.ts"], + ["es2021.weakref", "lib.es2021.weakref.d.ts"], + ["es2021.intl", "lib.es2021.intl.d.ts"], + ["es2022.array", "lib.es2022.array.d.ts"], + ["es2022.error", "lib.es2022.error.d.ts"], + ["es2022.intl", "lib.es2022.intl.d.ts"], + ["es2022.object", "lib.es2022.object.d.ts"], + ["es2022.sharedmemory", "lib.es2022.sharedmemory.d.ts"], + ["es2022.string", "lib.es2022.string.d.ts"], + ["esnext.array", "lib.es2022.array.d.ts"], + ["esnext.symbol", "lib.es2019.symbol.d.ts"], + ["esnext.asynciterable", "lib.es2018.asynciterable.d.ts"], + ["esnext.intl", "lib.esnext.intl.d.ts"], + ["esnext.bigint", "lib.es2020.bigint.d.ts"], + ["esnext.string", "lib.es2022.string.d.ts"], + ["esnext.promise", "lib.es2021.promise.d.ts"], + ["esnext.weakref", "lib.es2021.weakref.d.ts"] +]; + +/** + * An array of supported "lib" reference file names used to determine the order for inclusion + * when referenced, as well as for spelling suggestions. This ensures the correct ordering for + * overload resolution when a type declared in one lib is extended by another. + * + * @internal + */ +export const libs = libEntries.map(entry => entry[0]); + +/** + * A map of lib names to lib files. This map is used both for parsing the "lib" command line + * option as well as for resolving lib reference directives. + * + * @internal + */ +export const libMap = new Map(libEntries); + +// Watch related options +/** @internal */ +export const optionsForWatch: CommandLineOption[] = [ + { + name: "watchFile", + type: new Map(getEntries({ + fixedpollinginterval: WatchFileKind.FixedPollingInterval, + prioritypollinginterval: WatchFileKind.PriorityPollingInterval, + dynamicprioritypolling: WatchFileKind.DynamicPriorityPolling, + fixedchunksizepolling: WatchFileKind.FixedChunkSizePolling, + usefsevents: WatchFileKind.UseFsEvents, + usefseventsonparentdirectory: WatchFileKind.UseFsEventsOnParentDirectory, + })), + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Specify_how_the_TypeScript_watch_mode_works, + defaultValueDescription: WatchFileKind.UseFsEvents, + }, + { + name: "watchDirectory", + type: new Map(getEntries({ + usefsevents: WatchDirectoryKind.UseFsEvents, + fixedpollinginterval: WatchDirectoryKind.FixedPollingInterval, + dynamicprioritypolling: WatchDirectoryKind.DynamicPriorityPolling, + fixedchunksizepolling: WatchDirectoryKind.FixedChunkSizePolling, + })), + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Specify_how_directories_are_watched_on_systems_that_lack_recursive_file_watching_functionality, + defaultValueDescription: WatchDirectoryKind.UseFsEvents, + }, + { + name: "fallbackPolling", + type: new Map(getEntries({ + fixedinterval: PollingWatchKind.FixedInterval, + priorityinterval: PollingWatchKind.PriorityInterval, + dynamicpriority: PollingWatchKind.DynamicPriority, + fixedchunksize: PollingWatchKind.FixedChunkSize, + })), + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Specify_what_approach_the_watcher_should_use_if_the_system_runs_out_of_native_file_watchers, + defaultValueDescription: PollingWatchKind.PriorityInterval, + }, + { + name: "synchronousWatchDirectory", type: "boolean", + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively, defaultValueDescription: false, - }; - - const jsxOptionMap = new Map(getEntries({ - "preserve": JsxEmit.Preserve, - "react-native": JsxEmit.ReactNative, - "react": JsxEmit.React, - "react-jsx": JsxEmit.ReactJSX, - "react-jsxdev": JsxEmit.ReactJSXDev, - })); - - /* @internal */ - export const inverseJsxOptionMap = new Map(arrayFrom(mapIterator(jsxOptionMap.entries(), ([key, value]: [string, JsxEmit]) => ["" + value, key] as const))); - - // NOTE: The order here is important to default lib ordering as entries will have the same - // order in the generated program (see `getDefaultLibPriority` in program.ts). This - // order also affects overload resolution when a type declared in one lib is - // augmented in another lib. - const libEntries: [string, string][] = [ - // JavaScript only - ["es5", "lib.es5.d.ts"], - ["es6", "lib.es2015.d.ts"], - ["es2015", "lib.es2015.d.ts"], - ["es7", "lib.es2016.d.ts"], - ["es2016", "lib.es2016.d.ts"], - ["es2017", "lib.es2017.d.ts"], - ["es2018", "lib.es2018.d.ts"], - ["es2019", "lib.es2019.d.ts"], - ["es2020", "lib.es2020.d.ts"], - ["es2021", "lib.es2021.d.ts"], - ["es2022", "lib.es2022.d.ts"], - ["esnext", "lib.esnext.d.ts"], - // Host only - ["dom", "lib.dom.d.ts"], - ["dom.iterable", "lib.dom.iterable.d.ts"], - ["webworker", "lib.webworker.d.ts"], - ["webworker.importscripts", "lib.webworker.importscripts.d.ts"], - ["webworker.iterable", "lib.webworker.iterable.d.ts"], - ["scripthost", "lib.scripthost.d.ts"], - // ES2015 Or ESNext By-feature options - ["es2015.core", "lib.es2015.core.d.ts"], - ["es2015.collection", "lib.es2015.collection.d.ts"], - ["es2015.generator", "lib.es2015.generator.d.ts"], - ["es2015.iterable", "lib.es2015.iterable.d.ts"], - ["es2015.promise", "lib.es2015.promise.d.ts"], - ["es2015.proxy", "lib.es2015.proxy.d.ts"], - ["es2015.reflect", "lib.es2015.reflect.d.ts"], - ["es2015.symbol", "lib.es2015.symbol.d.ts"], - ["es2015.symbol.wellknown", "lib.es2015.symbol.wellknown.d.ts"], - ["es2016.array.include", "lib.es2016.array.include.d.ts"], - ["es2017.object", "lib.es2017.object.d.ts"], - ["es2017.sharedmemory", "lib.es2017.sharedmemory.d.ts"], - ["es2017.string", "lib.es2017.string.d.ts"], - ["es2017.intl", "lib.es2017.intl.d.ts"], - ["es2017.typedarrays", "lib.es2017.typedarrays.d.ts"], - ["es2018.asyncgenerator", "lib.es2018.asyncgenerator.d.ts"], - ["es2018.asynciterable", "lib.es2018.asynciterable.d.ts"], - ["es2018.intl", "lib.es2018.intl.d.ts"], - ["es2018.promise", "lib.es2018.promise.d.ts"], - ["es2018.regexp", "lib.es2018.regexp.d.ts"], - ["es2019.array", "lib.es2019.array.d.ts"], - ["es2019.object", "lib.es2019.object.d.ts"], - ["es2019.string", "lib.es2019.string.d.ts"], - ["es2019.symbol", "lib.es2019.symbol.d.ts"], - ["es2019.intl", "lib.es2019.intl.d.ts"], - ["es2020.bigint", "lib.es2020.bigint.d.ts"], - ["es2020.date", "lib.es2020.date.d.ts"], - ["es2020.promise", "lib.es2020.promise.d.ts"], - ["es2020.sharedmemory", "lib.es2020.sharedmemory.d.ts"], - ["es2020.string", "lib.es2020.string.d.ts"], - ["es2020.symbol.wellknown", "lib.es2020.symbol.wellknown.d.ts"], - ["es2020.intl", "lib.es2020.intl.d.ts"], - ["es2020.number", "lib.es2020.number.d.ts"], - ["es2021.promise", "lib.es2021.promise.d.ts"], - ["es2021.string", "lib.es2021.string.d.ts"], - ["es2021.weakref", "lib.es2021.weakref.d.ts"], - ["es2021.intl", "lib.es2021.intl.d.ts"], - ["es2022.array", "lib.es2022.array.d.ts"], - ["es2022.error", "lib.es2022.error.d.ts"], - ["es2022.intl", "lib.es2022.intl.d.ts"], - ["es2022.object", "lib.es2022.object.d.ts"], - ["es2022.sharedmemory", "lib.es2022.sharedmemory.d.ts"], - ["es2022.string", "lib.es2022.string.d.ts"], - ["esnext.array", "lib.es2022.array.d.ts"], - ["esnext.symbol", "lib.es2019.symbol.d.ts"], - ["esnext.asynciterable", "lib.es2018.asynciterable.d.ts"], - ["esnext.intl", "lib.esnext.intl.d.ts"], - ["esnext.bigint", "lib.es2020.bigint.d.ts"], - ["esnext.string", "lib.es2022.string.d.ts"], - ["esnext.promise", "lib.es2021.promise.d.ts"], - ["esnext.weakref", "lib.es2021.weakref.d.ts"] - ]; - - /** - * An array of supported "lib" reference file names used to determine the order for inclusion - * when referenced, as well as for spelling suggestions. This ensures the correct ordering for - * overload resolution when a type declared in one lib is extended by another. - */ - /* @internal */ - export const libs = libEntries.map(entry => entry[0]); - - /** - * A map of lib names to lib files. This map is used both for parsing the "lib" command line - * option as well as for resolving lib reference directives. - */ - /* @internal */ - export const libMap = new Map(libEntries); - - // Watch related options - /* @internal */ - export const optionsForWatch: CommandLineOption[] = [ - { - name: "watchFile", - type: new Map(getEntries({ - fixedpollinginterval: WatchFileKind.FixedPollingInterval, - prioritypollinginterval: WatchFileKind.PriorityPollingInterval, - dynamicprioritypolling: WatchFileKind.DynamicPriorityPolling, - fixedchunksizepolling: WatchFileKind.FixedChunkSizePolling, - usefsevents: WatchFileKind.UseFsEvents, - usefseventsonparentdirectory: WatchFileKind.UseFsEventsOnParentDirectory, - })), - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Specify_how_the_TypeScript_watch_mode_works, - defaultValueDescription: WatchFileKind.UseFsEvents, - }, - { - name: "watchDirectory", - type: new Map(getEntries({ - usefsevents: WatchDirectoryKind.UseFsEvents, - fixedpollinginterval: WatchDirectoryKind.FixedPollingInterval, - dynamicprioritypolling: WatchDirectoryKind.DynamicPriorityPolling, - fixedchunksizepolling: WatchDirectoryKind.FixedChunkSizePolling, - })), - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Specify_how_directories_are_watched_on_systems_that_lack_recursive_file_watching_functionality, - defaultValueDescription: WatchDirectoryKind.UseFsEvents, - }, - { - name: "fallbackPolling", - type: new Map(getEntries({ - fixedinterval: PollingWatchKind.FixedInterval, - priorityinterval: PollingWatchKind.PriorityInterval, - dynamicpriority: PollingWatchKind.DynamicPriority, - fixedchunksize: PollingWatchKind.FixedChunkSize, - })), - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Specify_what_approach_the_watcher_should_use_if_the_system_runs_out_of_native_file_watchers, - defaultValueDescription: PollingWatchKind.PriorityInterval, - }, - { - name: "synchronousWatchDirectory", - type: "boolean", - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively, - defaultValueDescription: false, - }, - { - name: "excludeDirectories", - type: "list", - element: { - name: "excludeDirectory", - type: "string", - isFilePath: true, - extraValidation: specToDiagnostic - }, - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Remove_a_list_of_directories_from_the_watch_process, - }, - { - name: "excludeFiles", - type: "list", - element: { - name: "excludeFile", - type: "string", - isFilePath: true, - extraValidation: specToDiagnostic - }, - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Remove_a_list_of_files_from_the_watch_mode_s_processing, - }, - ]; - - /* @internal */ - export const commonOptionsWithBuild: CommandLineOption[] = [ - { - name: "help", - shortName: "h", - type: "boolean", - showInSimplifiedHelpView: true, - isCommandLineOnly: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Print_this_message, - defaultValueDescription: false, - }, - { - name: "help", - shortName: "?", - type: "boolean", - isCommandLineOnly: true, - category: Diagnostics.Command_line_Options, - defaultValueDescription: false, - }, - { - name: "watch", - shortName: "w", - type: "boolean", - showInSimplifiedHelpView: true, - isCommandLineOnly: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Watch_input_files, - defaultValueDescription: false, - }, - { - name: "preserveWatchOutput", - type: "boolean", - showInSimplifiedHelpView: false, - category: Diagnostics.Output_Formatting, - description: Diagnostics.Disable_wiping_the_console_in_watch_mode, - defaultValueDescription: false, - }, - { - name: "listFiles", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Print_all_of_the_files_read_during_the_compilation, - defaultValueDescription: false, - }, - { - name: "explainFiles", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Print_files_read_during_the_compilation_including_why_it_was_included, - defaultValueDescription: false, - }, - { - name: "listEmittedFiles", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Print_the_names_of_emitted_files_after_a_compilation, - defaultValueDescription: false, - }, - { - name: "pretty", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Output_Formatting, - description: Diagnostics.Enable_color_and_formatting_in_TypeScript_s_output_to_make_compiler_errors_easier_to_read, - defaultValueDescription: true, - }, - { - name: "traceResolution", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Log_paths_used_during_the_moduleResolution_process, - defaultValueDescription: false, - }, - { - name: "diagnostics", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Output_compiler_performance_information_after_building, - defaultValueDescription: false, - }, - { - name: "extendedDiagnostics", - type: "boolean", - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Output_more_detailed_compiler_performance_information_after_building, - defaultValueDescription: false, - }, - { - name: "generateCpuProfile", + }, + { + name: "excludeDirectories", + type: "list", + element: { + name: "excludeDirectory", type: "string", isFilePath: true, - paramType: Diagnostics.FILE_OR_DIRECTORY, - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Emit_a_v8_CPU_profile_of_the_compiler_run_for_debugging, - defaultValueDescription: "profile.cpuprofile" - }, - { - name: "generateTrace", + extraValidation: specToDiagnostic + }, + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Remove_a_list_of_directories_from_the_watch_process, + }, + { + name: "excludeFiles", + type: "list", + element: { + name: "excludeFile", type: "string", isFilePath: true, - isCommandLineOnly: true, - paramType: Diagnostics.DIRECTORY, - category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Generates_an_event_trace_and_a_list_of_types - }, - { - name: "incremental", - shortName: "i", - type: "boolean", - category: Diagnostics.Projects, - description: Diagnostics.Save_tsbuildinfo_files_to_allow_for_incremental_compilation_of_projects, - transpileOptionValue: undefined, - defaultValueDescription: Diagnostics.false_unless_composite_is_set - }, - { - name: "declaration", - shortName: "d", - type: "boolean", - // Not setting affectsEmit because we calculate this flag might not affect full emit - affectsBuildInfo: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - transpileOptionValue: undefined, - description: Diagnostics.Generate_d_ts_files_from_TypeScript_and_JavaScript_files_in_your_project, - defaultValueDescription: Diagnostics.false_unless_composite_is_set, - }, - { - name: "declarationMap", - type: "boolean", - // Not setting affectsEmit because we calculate this flag might not affect full emit - affectsBuildInfo: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - transpileOptionValue: undefined, - defaultValueDescription: false, - description: Diagnostics.Create_sourcemaps_for_d_ts_files - }, - { - name: "emitDeclarationOnly", - type: "boolean", - // Not setting affectsEmit because we calculate this flag might not affect full emit - affectsBuildInfo: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - description: Diagnostics.Only_output_d_ts_files_and_not_JavaScript_files, - transpileOptionValue: undefined, - defaultValueDescription: false, + extraValidation: specToDiagnostic + }, + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Remove_a_list_of_files_from_the_watch_mode_s_processing, + }, +]; + +/** @internal */ +export const commonOptionsWithBuild: CommandLineOption[] = [ + { + name: "help", + shortName: "h", + type: "boolean", + showInSimplifiedHelpView: true, + isCommandLineOnly: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Print_this_message, + defaultValueDescription: false, + }, + { + name: "help", + shortName: "?", + type: "boolean", + isCommandLineOnly: true, + category: Diagnostics.Command_line_Options, + defaultValueDescription: false, + }, + { + name: "watch", + shortName: "w", + type: "boolean", + showInSimplifiedHelpView: true, + isCommandLineOnly: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Watch_input_files, + defaultValueDescription: false, + }, + { + name: "preserveWatchOutput", + type: "boolean", + showInSimplifiedHelpView: false, + category: Diagnostics.Output_Formatting, + description: Diagnostics.Disable_wiping_the_console_in_watch_mode, + defaultValueDescription: false, + }, + { + name: "listFiles", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Print_all_of_the_files_read_during_the_compilation, + defaultValueDescription: false, + }, + { + name: "explainFiles", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Print_files_read_during_the_compilation_including_why_it_was_included, + defaultValueDescription: false, + }, + { + name: "listEmittedFiles", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Print_the_names_of_emitted_files_after_a_compilation, + defaultValueDescription: false, + }, + { + name: "pretty", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Output_Formatting, + description: Diagnostics.Enable_color_and_formatting_in_TypeScript_s_output_to_make_compiler_errors_easier_to_read, + defaultValueDescription: true, + }, + { + name: "traceResolution", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Log_paths_used_during_the_moduleResolution_process, + defaultValueDescription: false, + }, + { + name: "diagnostics", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Output_compiler_performance_information_after_building, + defaultValueDescription: false, + }, + { + name: "extendedDiagnostics", + type: "boolean", + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Output_more_detailed_compiler_performance_information_after_building, + defaultValueDescription: false, + }, + { + name: "generateCpuProfile", + type: "string", + isFilePath: true, + paramType: Diagnostics.FILE_OR_DIRECTORY, + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Emit_a_v8_CPU_profile_of_the_compiler_run_for_debugging, + defaultValueDescription: "profile.cpuprofile" + }, + { + name: "generateTrace", + type: "string", + isFilePath: true, + isCommandLineOnly: true, + paramType: Diagnostics.DIRECTORY, + category: Diagnostics.Compiler_Diagnostics, + description: Diagnostics.Generates_an_event_trace_and_a_list_of_types + }, + { + name: "incremental", + shortName: "i", + type: "boolean", + category: Diagnostics.Projects, + description: Diagnostics.Save_tsbuildinfo_files_to_allow_for_incremental_compilation_of_projects, + transpileOptionValue: undefined, + defaultValueDescription: Diagnostics.false_unless_composite_is_set + }, + { + name: "declaration", + shortName: "d", + type: "boolean", + // Not setting affectsEmit because we calculate this flag might not affect full emit + affectsBuildInfo: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + transpileOptionValue: undefined, + description: Diagnostics.Generate_d_ts_files_from_TypeScript_and_JavaScript_files_in_your_project, + defaultValueDescription: Diagnostics.false_unless_composite_is_set, + }, + { + name: "declarationMap", + type: "boolean", + // Not setting affectsEmit because we calculate this flag might not affect full emit + affectsBuildInfo: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + transpileOptionValue: undefined, + defaultValueDescription: false, + description: Diagnostics.Create_sourcemaps_for_d_ts_files + }, + { + name: "emitDeclarationOnly", + type: "boolean", + // Not setting affectsEmit because we calculate this flag might not affect full emit + affectsBuildInfo: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + description: Diagnostics.Only_output_d_ts_files_and_not_JavaScript_files, + transpileOptionValue: undefined, + defaultValueDescription: false, + }, + { + name: "sourceMap", + type: "boolean", + // Not setting affectsEmit because we calculate this flag might not affect full emit + affectsBuildInfo: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + defaultValueDescription: false, + description: Diagnostics.Create_source_map_files_for_emitted_JavaScript_files, + }, + { + name: "inlineSourceMap", + type: "boolean", + // Not setting affectsEmit because we calculate this flag might not affect full emit + affectsBuildInfo: true, + category: Diagnostics.Emit, + description: Diagnostics.Include_sourcemap_files_inside_the_emitted_JavaScript, + defaultValueDescription: false, + }, + { + name: "assumeChangesOnlyAffectDirectDependencies", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Watch_and_Build_Modes, + description: Diagnostics.Have_recompiles_in_projects_that_use_incremental_and_watch_mode_assume_that_changes_within_a_file_will_only_affect_files_directly_depending_on_it, + defaultValueDescription: false, + }, + { + name: "locale", + type: "string", + category: Diagnostics.Command_line_Options, + isCommandLineOnly: true, + description: Diagnostics.Set_the_language_of_the_messaging_from_TypeScript_This_does_not_affect_emit, + defaultValueDescription: Diagnostics.Platform_specific + }, +]; + +/** @internal */ +export const targetOptionDeclaration: CommandLineOptionOfCustomType = { + name: "target", + shortName: "t", + type: new Map(getEntries({ + es3: ScriptTarget.ES3, + es5: ScriptTarget.ES5, + es6: ScriptTarget.ES2015, + es2015: ScriptTarget.ES2015, + es2016: ScriptTarget.ES2016, + es2017: ScriptTarget.ES2017, + es2018: ScriptTarget.ES2018, + es2019: ScriptTarget.ES2019, + es2020: ScriptTarget.ES2020, + es2021: ScriptTarget.ES2021, + es2022: ScriptTarget.ES2022, + esnext: ScriptTarget.ESNext, + })), + affectsSourceFile: true, + affectsModuleResolution: true, + affectsEmit: true, + affectsBuildInfo: true, + paramType: Diagnostics.VERSION, + showInSimplifiedHelpView: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Set_the_JavaScript_language_version_for_emitted_JavaScript_and_include_compatible_library_declarations, + defaultValueDescription: ScriptTarget.ES3, +}; + +/** @internal */ +export const moduleOptionDeclaration: CommandLineOptionOfCustomType = { + name: "module", + shortName: "m", + type: new Map(getEntries({ + none: ModuleKind.None, + commonjs: ModuleKind.CommonJS, + amd: ModuleKind.AMD, + system: ModuleKind.System, + umd: ModuleKind.UMD, + es6: ModuleKind.ES2015, + es2015: ModuleKind.ES2015, + es2020: ModuleKind.ES2020, + es2022: ModuleKind.ES2022, + esnext: ModuleKind.ESNext, + node16: ModuleKind.Node16, + nodenext: ModuleKind.NodeNext, + })), + affectsModuleResolution: true, + affectsEmit: true, + affectsBuildInfo: true, + paramType: Diagnostics.KIND, + showInSimplifiedHelpView: true, + category: Diagnostics.Modules, + description: Diagnostics.Specify_what_module_code_is_generated, + defaultValueDescription: undefined, +}; + +const commandOptionsWithoutBuild: CommandLineOption[] = [ + // CommandLine only options + { + name: "all", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Show_all_compiler_options, + defaultValueDescription: false, + }, + { + name: "version", + shortName: "v", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Print_the_compiler_s_version, + defaultValueDescription: false, + }, + { + name: "init", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Initializes_a_TypeScript_project_and_creates_a_tsconfig_json_file, + defaultValueDescription: false, + }, + { + name: "project", + shortName: "p", + type: "string", + isFilePath: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + paramType: Diagnostics.FILE_OR_DIRECTORY, + description: Diagnostics.Compile_the_project_given_the_path_to_its_configuration_file_or_to_a_folder_with_a_tsconfig_json, + }, + { + name: "build", + type: "boolean", + shortName: "b", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date, + defaultValueDescription: false, + }, + { + name: "showConfig", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + isCommandLineOnly: true, + description: Diagnostics.Print_the_final_configuration_instead_of_building, + defaultValueDescription: false, + }, + { + name: "listFilesOnly", + type: "boolean", + category: Diagnostics.Command_line_Options, + isCommandLineOnly: true, + description: Diagnostics.Print_names_of_files_that_are_part_of_the_compilation_and_then_stop_processing, + defaultValueDescription: false, + }, + + // Basic + targetOptionDeclaration, + moduleOptionDeclaration, + { + name: "lib", + type: "list", + element: { + name: "lib", + type: libMap, + defaultValueDescription: undefined, }, - { - name: "sourceMap", - type: "boolean", - // Not setting affectsEmit because we calculate this flag might not affect full emit - affectsBuildInfo: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - defaultValueDescription: false, - description: Diagnostics.Create_source_map_files_for_emitted_JavaScript_files, + affectsProgramStructure: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_a_set_of_bundled_library_declaration_files_that_describe_the_target_runtime_environment, + transpileOptionValue: undefined + }, + { + name: "allowJs", + type: "boolean", + affectsModuleResolution: true, + showInSimplifiedHelpView: true, + category: Diagnostics.JavaScript_Support, + description: Diagnostics.Allow_JavaScript_files_to_be_a_part_of_your_program_Use_the_checkJS_option_to_get_errors_from_these_files, + defaultValueDescription: false, + }, + { + name: "checkJs", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.JavaScript_Support, + description: Diagnostics.Enable_error_reporting_in_type_checked_JavaScript_files, + defaultValueDescription: false, + }, + { + name: "jsx", + type: jsxOptionMap, + affectsSourceFile: true, + affectsEmit: true, + affectsBuildInfo: true, + affectsModuleResolution: true, + paramType: Diagnostics.KIND, + showInSimplifiedHelpView: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_what_JSX_code_is_generated, + defaultValueDescription: undefined, + }, + { + name: "outFile", + type: "string", + affectsEmit: true, + affectsBuildInfo: true, + affectsDeclarationPath: true, + isFilePath: true, + paramType: Diagnostics.FILE, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + description: Diagnostics.Specify_a_file_that_bundles_all_outputs_into_one_JavaScript_file_If_declaration_is_true_also_designates_a_file_that_bundles_all_d_ts_output, + transpileOptionValue: undefined, + }, + { + name: "outDir", + type: "string", + affectsEmit: true, + affectsBuildInfo: true, + affectsDeclarationPath: true, + isFilePath: true, + paramType: Diagnostics.DIRECTORY, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + description: Diagnostics.Specify_an_output_folder_for_all_emitted_files, + }, + { + name: "rootDir", + type: "string", + affectsEmit: true, + affectsBuildInfo: true, + affectsDeclarationPath: true, + isFilePath: true, + paramType: Diagnostics.LOCATION, + category: Diagnostics.Modules, + description: Diagnostics.Specify_the_root_folder_within_your_source_files, + defaultValueDescription: Diagnostics.Computed_from_the_list_of_input_files + }, + { + name: "composite", + type: "boolean", + // Not setting affectsEmit because we calculate this flag might not affect full emit + affectsBuildInfo: true, + isTSConfigOnly: true, + category: Diagnostics.Projects, + transpileOptionValue: undefined, + defaultValueDescription: false, + description: Diagnostics.Enable_constraints_that_allow_a_TypeScript_project_to_be_used_with_project_references, + }, + { + name: "tsBuildInfoFile", + type: "string", + affectsEmit: true, + affectsBuildInfo: true, + isFilePath: true, + paramType: Diagnostics.FILE, + category: Diagnostics.Projects, + transpileOptionValue: undefined, + defaultValueDescription: ".tsbuildinfo", + description: Diagnostics.Specify_the_path_to_tsbuildinfo_incremental_compilation_file, + }, + { + name: "removeComments", + type: "boolean", + affectsEmit: true, + affectsBuildInfo: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + defaultValueDescription: false, + description: Diagnostics.Disable_emitting_comments, + }, + { + name: "noEmit", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Emit, + description: Diagnostics.Disable_emitting_files_from_a_compilation, + transpileOptionValue: undefined, + defaultValueDescription: false, + }, + { + name: "importHelpers", + type: "boolean", + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Emit, + description: Diagnostics.Allow_importing_helper_functions_from_tslib_once_per_project_instead_of_including_them_per_file, + defaultValueDescription: false, + }, + { + name: "importsNotUsedAsValues", + type: new Map(getEntries({ + remove: ImportsNotUsedAsValues.Remove, + preserve: ImportsNotUsedAsValues.Preserve, + error: ImportsNotUsedAsValues.Error, + })), + affectsEmit: true, + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Emit, + description: Diagnostics.Specify_emit_Slashchecking_behavior_for_imports_that_are_only_used_for_types, + defaultValueDescription: ImportsNotUsedAsValues.Remove, + }, + { + name: "downlevelIteration", + type: "boolean", + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Emit, + description: Diagnostics.Emit_more_compliant_but_verbose_and_less_performant_JavaScript_for_iteration, + defaultValueDescription: false, + }, + { + name: "isolatedModules", + type: "boolean", + category: Diagnostics.Interop_Constraints, + description: Diagnostics.Ensure_that_each_file_can_be_safely_transpiled_without_relying_on_other_imports, + transpileOptionValue: true, + defaultValueDescription: false, + }, + + // Strict Type Checks + { + name: "strict", + type: "boolean", + // Though this affects semantic diagnostics, affectsSemanticDiagnostics is not set here + // The value of each strictFlag depends on own strictFlag value or this and never accessed directly. + // But we need to store `strict` in builf info, even though it won't be examined directly, so that the + // flags it controls (e.g. `strictNullChecks`) will be retrieved correctly + affectsBuildInfo: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_all_strict_type_checking_options, + defaultValueDescription: false, + }, + { + name: "noImplicitAny", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_error_reporting_for_expressions_and_declarations_with_an_implied_any_type, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "strictNullChecks", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.When_type_checking_take_into_account_null_and_undefined, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "strictFunctionTypes", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.When_assigning_functions_check_to_ensure_parameters_and_the_return_values_are_subtype_compatible, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "strictBindCallApply", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Check_that_the_arguments_for_bind_call_and_apply_methods_match_the_original_function, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "strictPropertyInitialization", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Check_for_class_properties_that_are_declared_but_not_set_in_the_constructor, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "noImplicitThis", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_error_reporting_when_this_is_given_the_type_any, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + { + name: "useUnknownInCatchVariables", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Default_catch_clause_variables_as_unknown_instead_of_any, + defaultValueDescription: false, + }, + { + name: "alwaysStrict", + type: "boolean", + affectsSourceFile: true, + affectsEmit: true, + affectsBuildInfo: true, + strictFlag: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Ensure_use_strict_is_always_emitted, + defaultValueDescription: Diagnostics.false_unless_strict_is_set + }, + + // Additional Checks + { + name: "noUnusedLocals", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_error_reporting_when_local_variables_aren_t_read, + defaultValueDescription: false, + }, + { + name: "noUnusedParameters", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Raise_an_error_when_a_function_parameter_isn_t_read, + defaultValueDescription: false, + }, + { + name: "exactOptionalPropertyTypes", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Interpret_optional_property_types_as_written_rather_than_adding_undefined, + defaultValueDescription: false, + }, + { + name: "noImplicitReturns", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_error_reporting_for_codepaths_that_do_not_explicitly_return_in_a_function, + defaultValueDescription: false, + }, + { + name: "noFallthroughCasesInSwitch", + type: "boolean", + affectsBindDiagnostics: true, + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enable_error_reporting_for_fallthrough_cases_in_switch_statements, + defaultValueDescription: false, + }, + { + name: "noUncheckedIndexedAccess", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Add_undefined_to_a_type_when_accessed_using_an_index, + defaultValueDescription: false, + }, + { + name: "noImplicitOverride", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Ensure_overriding_members_in_derived_classes_are_marked_with_an_override_modifier, + defaultValueDescription: false, + }, + { + name: "noPropertyAccessFromIndexSignature", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + showInSimplifiedHelpView: false, + category: Diagnostics.Type_Checking, + description: Diagnostics.Enforces_using_indexed_accessors_for_keys_declared_using_an_indexed_type, + defaultValueDescription: false, + }, + + // Module Resolution + { + name: "moduleResolution", + type: new Map(getEntries({ + node: ModuleResolutionKind.NodeJs, + classic: ModuleResolutionKind.Classic, + node16: ModuleResolutionKind.Node16, + nodenext: ModuleResolutionKind.NodeNext, + })), + affectsModuleResolution: true, + paramType: Diagnostics.STRATEGY, + category: Diagnostics.Modules, + description: Diagnostics.Specify_how_TypeScript_looks_up_a_file_from_a_given_module_specifier, + defaultValueDescription: Diagnostics.module_AMD_or_UMD_or_System_or_ES6_then_Classic_Otherwise_Node + }, + { + name: "baseUrl", + type: "string", + affectsModuleResolution: true, + isFilePath: true, + category: Diagnostics.Modules, + description: Diagnostics.Specify_the_base_directory_to_resolve_non_relative_module_names + }, + { + // this option can only be specified in tsconfig.json + // use type = object to copy the value as-is + name: "paths", + type: "object", + affectsModuleResolution: true, + isTSConfigOnly: true, + category: Diagnostics.Modules, + description: Diagnostics.Specify_a_set_of_entries_that_re_map_imports_to_additional_lookup_locations, + transpileOptionValue: undefined + }, + { + // this option can only be specified in tsconfig.json + // use type = object to copy the value as-is + name: "rootDirs", + type: "list", + isTSConfigOnly: true, + element: { + name: "rootDirs", + type: "string", + isFilePath: true }, - { - name: "inlineSourceMap", - type: "boolean", - // Not setting affectsEmit because we calculate this flag might not affect full emit - affectsBuildInfo: true, - category: Diagnostics.Emit, - description: Diagnostics.Include_sourcemap_files_inside_the_emitted_JavaScript, - defaultValueDescription: false, + affectsModuleResolution: true, + category: Diagnostics.Modules, + description: Diagnostics.Allow_multiple_folders_to_be_treated_as_one_when_resolving_modules, + transpileOptionValue: undefined, + defaultValueDescription: Diagnostics.Computed_from_the_list_of_input_files + }, + { + name: "typeRoots", + type: "list", + element: { + name: "typeRoots", + type: "string", + isFilePath: true }, - { - name: "assumeChangesOnlyAffectDirectDependencies", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Have_recompiles_in_projects_that_use_incremental_and_watch_mode_assume_that_changes_within_a_file_will_only_affect_files_directly_depending_on_it, - defaultValueDescription: false, + affectsModuleResolution: true, + category: Diagnostics.Modules, + description: Diagnostics.Specify_multiple_folders_that_act_like_Slashnode_modules_Slash_types + }, + { + name: "types", + type: "list", + element: { + name: "types", + type: "string" }, - { - name: "locale", + affectsProgramStructure: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Modules, + description: Diagnostics.Specify_type_package_names_to_be_included_without_being_referenced_in_a_source_file, + transpileOptionValue: undefined + }, + { + name: "allowSyntheticDefaultImports", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Interop_Constraints, + description: Diagnostics.Allow_import_x_from_y_when_a_module_doesn_t_have_a_default_export, + defaultValueDescription: Diagnostics.module_system_or_esModuleInterop + }, + { + name: "esModuleInterop", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsEmit: true, + affectsBuildInfo: true, + showInSimplifiedHelpView: true, + category: Diagnostics.Interop_Constraints, + description: Diagnostics.Emit_additional_JavaScript_to_ease_support_for_importing_CommonJS_modules_This_enables_allowSyntheticDefaultImports_for_type_compatibility, + defaultValueDescription: false, + }, + { + name: "preserveSymlinks", + type: "boolean", + category: Diagnostics.Interop_Constraints, + description: Diagnostics.Disable_resolving_symlinks_to_their_realpath_This_correlates_to_the_same_flag_in_node, + defaultValueDescription: false, + }, + { + name: "allowUmdGlobalAccess", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Modules, + description: Diagnostics.Allow_accessing_UMD_globals_from_modules, + defaultValueDescription: false, + }, + { + name: "moduleSuffixes", + type: "list", + element: { + name: "suffix", type: "string", - category: Diagnostics.Command_line_Options, - isCommandLineOnly: true, - description: Diagnostics.Set_the_language_of_the_messaging_from_TypeScript_This_does_not_affect_emit, - defaultValueDescription: Diagnostics.Platform_specific }, - ]; + listPreserveFalsyValues: true, + affectsModuleResolution: true, + category: Diagnostics.Modules, + description: Diagnostics.List_of_file_name_suffixes_to_search_when_resolving_a_module, + }, + + // Source Maps + { + name: "sourceRoot", + type: "string", + affectsEmit: true, + affectsBuildInfo: true, + paramType: Diagnostics.LOCATION, + category: Diagnostics.Emit, + description: Diagnostics.Specify_the_root_path_for_debuggers_to_find_the_reference_source_code, + }, + { + name: "mapRoot", + type: "string", + affectsEmit: true, + affectsBuildInfo: true, + paramType: Diagnostics.LOCATION, + category: Diagnostics.Emit, + description: Diagnostics.Specify_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations, + }, + { + name: "inlineSources", + type: "boolean", + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Emit, + description: Diagnostics.Include_source_code_in_the_sourcemaps_inside_the_emitted_JavaScript, + defaultValueDescription: false, + }, + + // Experimental + { + name: "experimentalDecorators", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Enable_experimental_support_for_TC39_stage_2_draft_decorators, + defaultValueDescription: false, + }, + { + name: "emitDecoratorMetadata", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Emit_design_type_metadata_for_decorated_declarations_in_source_files, + defaultValueDescription: false, + }, + + // Advanced + { + name: "jsxFactory", + type: "string", + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_the_JSX_factory_function_used_when_targeting_React_JSX_emit_e_g_React_createElement_or_h, + defaultValueDescription: "`React.createElement`" + }, + { + name: "jsxFragmentFactory", + type: "string", + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_the_JSX_Fragment_reference_used_for_fragments_when_targeting_React_JSX_emit_e_g_React_Fragment_or_Fragment, + defaultValueDescription: "React.Fragment", + }, + { + name: "jsxImportSource", + type: "string", + affectsSemanticDiagnostics: true, + affectsEmit: true, + affectsBuildInfo: true, + affectsModuleResolution: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_module_specifier_used_to_import_the_JSX_factory_functions_when_using_jsx_Colon_react_jsx_Asterisk, + defaultValueDescription: "react" + }, + { + name: "resolveJsonModule", + type: "boolean", + affectsModuleResolution: true, + category: Diagnostics.Modules, + description: Diagnostics.Enable_importing_json_files, + defaultValueDescription: false, + }, - /* @internal */ - export const targetOptionDeclaration: CommandLineOptionOfCustomType = { - name: "target", - shortName: "t", + { + name: "out", + type: "string", + affectsEmit: true, + affectsBuildInfo: true, + affectsDeclarationPath: true, + isFilePath: false, // This is intentionally broken to support compatability with existing tsconfig files + // for correct behaviour, please use outFile + category: Diagnostics.Backwards_Compatibility, + paramType: Diagnostics.FILE, + transpileOptionValue: undefined, + description: Diagnostics.Deprecated_setting_Use_outFile_instead, + }, + { + name: "reactNamespace", + type: "string", + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Language_and_Environment, + description: Diagnostics.Specify_the_object_invoked_for_createElement_This_only_applies_when_targeting_react_JSX_emit, + defaultValueDescription: "`React`", + }, + { + name: "skipDefaultLibCheck", + type: "boolean", + // We need to store these to determine whether `lib` files need to be rechecked + affectsBuildInfo: true, + category: Diagnostics.Completeness, + description: Diagnostics.Skip_type_checking_d_ts_files_that_are_included_with_TypeScript, + defaultValueDescription: false, + }, + { + name: "charset", + type: "string", + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.No_longer_supported_In_early_versions_manually_set_the_text_encoding_for_reading_files, + defaultValueDescription: "utf8" + }, + { + name: "emitBOM", + type: "boolean", + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Emit, + description: Diagnostics.Emit_a_UTF_8_Byte_Order_Mark_BOM_in_the_beginning_of_output_files, + defaultValueDescription: false, + }, + { + name: "newLine", type: new Map(getEntries({ - es3: ScriptTarget.ES3, - es5: ScriptTarget.ES5, - es6: ScriptTarget.ES2015, - es2015: ScriptTarget.ES2015, - es2016: ScriptTarget.ES2016, - es2017: ScriptTarget.ES2017, - es2018: ScriptTarget.ES2018, - es2019: ScriptTarget.ES2019, - es2020: ScriptTarget.ES2020, - es2021: ScriptTarget.ES2021, - es2022: ScriptTarget.ES2022, - esnext: ScriptTarget.ESNext, + crlf: NewLineKind.CarriageReturnLineFeed, + lf: NewLineKind.LineFeed })), - affectsSourceFile: true, + affectsEmit: true, + affectsBuildInfo: true, + paramType: Diagnostics.NEWLINE, + category: Diagnostics.Emit, + description: Diagnostics.Set_the_newline_character_for_emitting_files, + defaultValueDescription: Diagnostics.Platform_specific + }, + { + name: "noErrorTruncation", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Output_Formatting, + description: Diagnostics.Disable_truncating_types_in_error_messages, + defaultValueDescription: false, + }, + { + name: "noLib", + type: "boolean", + category: Diagnostics.Language_and_Environment, + affectsProgramStructure: true, + description: Diagnostics.Disable_including_any_library_files_including_the_default_lib_d_ts, + // We are not returning a sourceFile for lib file when asked by the program, + // so pass --noLib to avoid reporting a file not found error. + transpileOptionValue: true, + defaultValueDescription: false, + }, + { + name: "noResolve", + type: "boolean", affectsModuleResolution: true, + category: Diagnostics.Modules, + description: Diagnostics.Disallow_import_s_require_s_or_reference_s_from_expanding_the_number_of_files_TypeScript_should_add_to_a_project, + // We are not doing a full typecheck, we are not resolving the whole context, + // so pass --noResolve to avoid reporting missing file errors. + transpileOptionValue: true, + defaultValueDescription: false, + }, + { + name: "stripInternal", + type: "boolean", + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Emit, + description: Diagnostics.Disable_emitting_declarations_that_have_internal_in_their_JSDoc_comments, + defaultValueDescription: false, + }, + { + name: "disableSizeLimit", + type: "boolean", + affectsProgramStructure: true, + category: Diagnostics.Editor_Support, + description: Diagnostics.Remove_the_20mb_cap_on_total_source_code_size_for_JavaScript_files_in_the_TypeScript_language_server, + defaultValueDescription: false, + }, + { + name: "disableSourceOfProjectReferenceRedirect", + type: "boolean", + isTSConfigOnly: true, + category: Diagnostics.Projects, + description: Diagnostics.Disable_preferring_source_files_instead_of_declaration_files_when_referencing_composite_projects, + defaultValueDescription: false, + }, + { + name: "disableSolutionSearching", + type: "boolean", + isTSConfigOnly: true, + category: Diagnostics.Projects, + description: Diagnostics.Opt_a_project_out_of_multi_project_reference_checking_when_editing, + defaultValueDescription: false, + }, + { + name: "disableReferencedProjectLoad", + type: "boolean", + isTSConfigOnly: true, + category: Diagnostics.Projects, + description: Diagnostics.Reduce_the_number_of_projects_loaded_automatically_by_TypeScript, + defaultValueDescription: false, + }, + { + name: "noImplicitUseStrict", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.Disable_adding_use_strict_directives_in_emitted_JavaScript_files, + defaultValueDescription: false, + }, + { + name: "noEmitHelpers", + type: "boolean", + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Emit, + description: Diagnostics.Disable_generating_custom_helper_functions_like_extends_in_compiled_output, + defaultValueDescription: false, + }, + { + name: "noEmitOnError", + type: "boolean", + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Emit, + transpileOptionValue: undefined, + description: Diagnostics.Disable_emitting_files_if_any_type_checking_errors_are_reported, + defaultValueDescription: false, + }, + { + name: "preserveConstEnums", + type: "boolean", + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Emit, + description: Diagnostics.Disable_erasing_const_enum_declarations_in_generated_code, + defaultValueDescription: false, + }, + { + name: "declarationDir", + type: "string", + affectsEmit: true, + affectsBuildInfo: true, + affectsDeclarationPath: true, + isFilePath: true, + paramType: Diagnostics.DIRECTORY, + category: Diagnostics.Emit, + transpileOptionValue: undefined, + description: Diagnostics.Specify_the_output_directory_for_generated_declaration_files, + }, + { + name: "skipLibCheck", + type: "boolean", + // We need to store these to determine whether `lib` files need to be rechecked + affectsBuildInfo: true, + category: Diagnostics.Completeness, + description: Diagnostics.Skip_type_checking_all_d_ts_files, + defaultValueDescription: false, + }, + { + name: "allowUnusedLabels", + type: "boolean", + affectsBindDiagnostics: true, + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Disable_error_reporting_for_unused_labels, + defaultValueDescription: undefined, + }, + { + name: "allowUnreachableCode", + type: "boolean", + affectsBindDiagnostics: true, + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Type_Checking, + description: Diagnostics.Disable_error_reporting_for_unreachable_code, + defaultValueDescription: undefined, + }, + { + name: "suppressExcessPropertyErrors", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.Disable_reporting_of_excess_property_errors_during_the_creation_of_object_literals, + defaultValueDescription: false, + }, + { + name: "suppressImplicitAnyIndexErrors", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.Suppress_noImplicitAny_errors_when_indexing_objects_that_lack_index_signatures, + defaultValueDescription: false, + }, + { + name: "forceConsistentCasingInFileNames", + type: "boolean", + affectsModuleResolution: true, + category: Diagnostics.Interop_Constraints, + description: Diagnostics.Ensure_that_casing_is_correct_in_imports, + defaultValueDescription: false, + }, + { + name: "maxNodeModuleJsDepth", + type: "number", + affectsModuleResolution: true, + category: Diagnostics.JavaScript_Support, + description: Diagnostics.Specify_the_maximum_folder_depth_used_for_checking_JavaScript_files_from_node_modules_Only_applicable_with_allowJs, + defaultValueDescription: 0, + }, + { + name: "noStrictGenericChecks", + type: "boolean", + affectsSemanticDiagnostics: true, + affectsBuildInfo: true, + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.Disable_strict_checking_of_generic_signatures_in_function_types, + defaultValueDescription: false, + }, + { + name: "useDefineForClassFields", + type: "boolean", + affectsSemanticDiagnostics: true, affectsEmit: true, affectsBuildInfo: true, - paramType: Diagnostics.VERSION, - showInSimplifiedHelpView: true, category: Diagnostics.Language_and_Environment, - description: Diagnostics.Set_the_JavaScript_language_version_for_emitted_JavaScript_and_include_compatible_library_declarations, - defaultValueDescription: ScriptTarget.ES3, - }; + description: Diagnostics.Emit_ECMAScript_standard_compliant_class_fields, + defaultValueDescription: Diagnostics.true_for_ES2022_and_above_including_ESNext + }, + { + name: "preserveValueImports", + type: "boolean", + affectsEmit: true, + affectsBuildInfo: true, + category: Diagnostics.Emit, + description: Diagnostics.Preserve_unused_imported_values_in_the_JavaScript_output_that_would_otherwise_be_removed, + defaultValueDescription: false, + }, - /*@internal*/ - export const moduleOptionDeclaration: CommandLineOptionOfCustomType = { - name: "module", - shortName: "m", + { + name: "keyofStringsOnly", + type: "boolean", + category: Diagnostics.Backwards_Compatibility, + description: Diagnostics.Make_keyof_only_return_strings_instead_of_string_numbers_or_symbols_Legacy_option, + defaultValueDescription: false, + }, + { + // A list of plugins to load in the language service + name: "plugins", + type: "list", + isTSConfigOnly: true, + element: { + name: "plugin", + type: "object" + }, + description: Diagnostics.Specify_a_list_of_language_service_plugins_to_include, + category: Diagnostics.Editor_Support, + + }, + { + name: "moduleDetection", type: new Map(getEntries({ - none: ModuleKind.None, - commonjs: ModuleKind.CommonJS, - amd: ModuleKind.AMD, - system: ModuleKind.System, - umd: ModuleKind.UMD, - es6: ModuleKind.ES2015, - es2015: ModuleKind.ES2015, - es2020: ModuleKind.ES2020, - es2022: ModuleKind.ES2022, - esnext: ModuleKind.ESNext, - node16: ModuleKind.Node16, - nodenext: ModuleKind.NodeNext, + auto: ModuleDetectionKind.Auto, + legacy: ModuleDetectionKind.Legacy, + force: ModuleDetectionKind.Force, })), affectsModuleResolution: true, - affectsEmit: true, - affectsBuildInfo: true, - paramType: Diagnostics.KIND, - showInSimplifiedHelpView: true, - category: Diagnostics.Modules, - description: Diagnostics.Specify_what_module_code_is_generated, - defaultValueDescription: undefined, - }; - - const commandOptionsWithoutBuild: CommandLineOption[] = [ - // CommandLine only options - { - name: "all", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Show_all_compiler_options, - defaultValueDescription: false, - }, - { - name: "version", - shortName: "v", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Print_the_compiler_s_version, - defaultValueDescription: false, - }, - { - name: "init", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Initializes_a_TypeScript_project_and_creates_a_tsconfig_json_file, - defaultValueDescription: false, - }, - { - name: "project", - shortName: "p", - type: "string", - isFilePath: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - paramType: Diagnostics.FILE_OR_DIRECTORY, - description: Diagnostics.Compile_the_project_given_the_path_to_its_configuration_file_or_to_a_folder_with_a_tsconfig_json, - }, - { - name: "build", - type: "boolean", - shortName: "b", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - description: Diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date, - defaultValueDescription: false, - }, - { - name: "showConfig", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Command_line_Options, - isCommandLineOnly: true, - description: Diagnostics.Print_the_final_configuration_instead_of_building, - defaultValueDescription: false, - }, - { - name: "listFilesOnly", - type: "boolean", - category: Diagnostics.Command_line_Options, - isCommandLineOnly: true, - description: Diagnostics.Print_names_of_files_that_are_part_of_the_compilation_and_then_stop_processing, - defaultValueDescription: false, - }, - - // Basic - targetOptionDeclaration, - moduleOptionDeclaration, - { - name: "lib", - type: "list", - element: { - name: "lib", - type: libMap, - defaultValueDescription: undefined, - }, - affectsProgramStructure: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_a_set_of_bundled_library_declaration_files_that_describe_the_target_runtime_environment, - transpileOptionValue: undefined - }, - { - name: "allowJs", - type: "boolean", - affectsModuleResolution: true, - showInSimplifiedHelpView: true, - category: Diagnostics.JavaScript_Support, - description: Diagnostics.Allow_JavaScript_files_to_be_a_part_of_your_program_Use_the_checkJS_option_to_get_errors_from_these_files, - defaultValueDescription: false, - }, - { - name: "checkJs", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.JavaScript_Support, - description: Diagnostics.Enable_error_reporting_in_type_checked_JavaScript_files, - defaultValueDescription: false, - }, - { - name: "jsx", - type: jsxOptionMap, - affectsSourceFile: true, - affectsEmit: true, - affectsBuildInfo: true, - affectsModuleResolution: true, - paramType: Diagnostics.KIND, - showInSimplifiedHelpView: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_what_JSX_code_is_generated, - defaultValueDescription: undefined, - }, - { - name: "outFile", - type: "string", - affectsEmit: true, - affectsBuildInfo: true, - affectsDeclarationPath: true, - isFilePath: true, - paramType: Diagnostics.FILE, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - description: Diagnostics.Specify_a_file_that_bundles_all_outputs_into_one_JavaScript_file_If_declaration_is_true_also_designates_a_file_that_bundles_all_d_ts_output, - transpileOptionValue: undefined, - }, - { - name: "outDir", - type: "string", - affectsEmit: true, - affectsBuildInfo: true, - affectsDeclarationPath: true, - isFilePath: true, - paramType: Diagnostics.DIRECTORY, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - description: Diagnostics.Specify_an_output_folder_for_all_emitted_files, - }, - { - name: "rootDir", - type: "string", - affectsEmit: true, - affectsBuildInfo: true, - affectsDeclarationPath: true, - isFilePath: true, - paramType: Diagnostics.LOCATION, - category: Diagnostics.Modules, - description: Diagnostics.Specify_the_root_folder_within_your_source_files, - defaultValueDescription: Diagnostics.Computed_from_the_list_of_input_files - }, - { - name: "composite", - type: "boolean", - // Not setting affectsEmit because we calculate this flag might not affect full emit - affectsBuildInfo: true, - isTSConfigOnly: true, - category: Diagnostics.Projects, - transpileOptionValue: undefined, - defaultValueDescription: false, - description: Diagnostics.Enable_constraints_that_allow_a_TypeScript_project_to_be_used_with_project_references, - }, - { - name: "tsBuildInfoFile", - type: "string", - affectsEmit: true, - affectsBuildInfo: true, - isFilePath: true, - paramType: Diagnostics.FILE, - category: Diagnostics.Projects, - transpileOptionValue: undefined, - defaultValueDescription: ".tsbuildinfo", - description: Diagnostics.Specify_the_path_to_tsbuildinfo_incremental_compilation_file, - }, - { - name: "removeComments", - type: "boolean", - affectsEmit: true, - affectsBuildInfo: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - defaultValueDescription: false, - description: Diagnostics.Disable_emitting_comments, - }, - { - name: "noEmit", - type: "boolean", - showInSimplifiedHelpView: true, - category: Diagnostics.Emit, - description: Diagnostics.Disable_emitting_files_from_a_compilation, - transpileOptionValue: undefined, - defaultValueDescription: false, - }, - { - name: "importHelpers", - type: "boolean", - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Emit, - description: Diagnostics.Allow_importing_helper_functions_from_tslib_once_per_project_instead_of_including_them_per_file, - defaultValueDescription: false, - }, - { - name: "importsNotUsedAsValues", - type: new Map(getEntries({ - remove: ImportsNotUsedAsValues.Remove, - preserve: ImportsNotUsedAsValues.Preserve, - error: ImportsNotUsedAsValues.Error, - })), - affectsEmit: true, - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Emit, - description: Diagnostics.Specify_emit_Slashchecking_behavior_for_imports_that_are_only_used_for_types, - defaultValueDescription: ImportsNotUsedAsValues.Remove, - }, - { - name: "downlevelIteration", - type: "boolean", - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Emit, - description: Diagnostics.Emit_more_compliant_but_verbose_and_less_performant_JavaScript_for_iteration, - defaultValueDescription: false, - }, - { - name: "isolatedModules", - type: "boolean", - category: Diagnostics.Interop_Constraints, - description: Diagnostics.Ensure_that_each_file_can_be_safely_transpiled_without_relying_on_other_imports, - transpileOptionValue: true, - defaultValueDescription: false, - }, - - // Strict Type Checks - { - name: "strict", - type: "boolean", - // Though this affects semantic diagnostics, affectsSemanticDiagnostics is not set here - // The value of each strictFlag depends on own strictFlag value or this and never accessed directly. - // But we need to store `strict` in builf info, even though it won't be examined directly, so that the - // flags it controls (e.g. `strictNullChecks`) will be retrieved correctly - affectsBuildInfo: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_all_strict_type_checking_options, - defaultValueDescription: false, - }, - { - name: "noImplicitAny", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_error_reporting_for_expressions_and_declarations_with_an_implied_any_type, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "strictNullChecks", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.When_type_checking_take_into_account_null_and_undefined, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "strictFunctionTypes", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.When_assigning_functions_check_to_ensure_parameters_and_the_return_values_are_subtype_compatible, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "strictBindCallApply", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Check_that_the_arguments_for_bind_call_and_apply_methods_match_the_original_function, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "strictPropertyInitialization", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Check_for_class_properties_that_are_declared_but_not_set_in_the_constructor, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "noImplicitThis", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_error_reporting_when_this_is_given_the_type_any, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - { - name: "useUnknownInCatchVariables", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Default_catch_clause_variables_as_unknown_instead_of_any, - defaultValueDescription: false, - }, - { - name: "alwaysStrict", - type: "boolean", - affectsSourceFile: true, - affectsEmit: true, - affectsBuildInfo: true, - strictFlag: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Ensure_use_strict_is_always_emitted, - defaultValueDescription: Diagnostics.false_unless_strict_is_set - }, - - // Additional Checks - { - name: "noUnusedLocals", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_error_reporting_when_local_variables_aren_t_read, - defaultValueDescription: false, - }, - { - name: "noUnusedParameters", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Raise_an_error_when_a_function_parameter_isn_t_read, - defaultValueDescription: false, - }, - { - name: "exactOptionalPropertyTypes", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Interpret_optional_property_types_as_written_rather_than_adding_undefined, - defaultValueDescription: false, - }, - { - name: "noImplicitReturns", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_error_reporting_for_codepaths_that_do_not_explicitly_return_in_a_function, - defaultValueDescription: false, - }, - { - name: "noFallthroughCasesInSwitch", - type: "boolean", - affectsBindDiagnostics: true, - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_error_reporting_for_fallthrough_cases_in_switch_statements, - defaultValueDescription: false, - }, - { - name: "noUncheckedIndexedAccess", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Add_undefined_to_a_type_when_accessed_using_an_index, - defaultValueDescription: false, - }, - { - name: "noImplicitOverride", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Ensure_overriding_members_in_derived_classes_are_marked_with_an_override_modifier, - defaultValueDescription: false, - }, - { - name: "noPropertyAccessFromIndexSignature", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - showInSimplifiedHelpView: false, - category: Diagnostics.Type_Checking, - description: Diagnostics.Enforces_using_indexed_accessors_for_keys_declared_using_an_indexed_type, - defaultValueDescription: false, - }, - - // Module Resolution - { - name: "moduleResolution", - type: new Map(getEntries({ - node: ModuleResolutionKind.NodeJs, - classic: ModuleResolutionKind.Classic, - node16: ModuleResolutionKind.Node16, - nodenext: ModuleResolutionKind.NodeNext, - })), - affectsModuleResolution: true, - paramType: Diagnostics.STRATEGY, - category: Diagnostics.Modules, - description: Diagnostics.Specify_how_TypeScript_looks_up_a_file_from_a_given_module_specifier, - defaultValueDescription: Diagnostics.module_AMD_or_UMD_or_System_or_ES6_then_Classic_Otherwise_Node - }, - { - name: "baseUrl", - type: "string", - affectsModuleResolution: true, - isFilePath: true, - category: Diagnostics.Modules, - description: Diagnostics.Specify_the_base_directory_to_resolve_non_relative_module_names - }, - { - // this option can only be specified in tsconfig.json - // use type = object to copy the value as-is - name: "paths", - type: "object", - affectsModuleResolution: true, - isTSConfigOnly: true, - category: Diagnostics.Modules, - description: Diagnostics.Specify_a_set_of_entries_that_re_map_imports_to_additional_lookup_locations, - transpileOptionValue: undefined - }, - { - // this option can only be specified in tsconfig.json - // use type = object to copy the value as-is - name: "rootDirs", - type: "list", - isTSConfigOnly: true, - element: { - name: "rootDirs", - type: "string", - isFilePath: true - }, - affectsModuleResolution: true, - category: Diagnostics.Modules, - description: Diagnostics.Allow_multiple_folders_to_be_treated_as_one_when_resolving_modules, - transpileOptionValue: undefined, - defaultValueDescription: Diagnostics.Computed_from_the_list_of_input_files - }, - { - name: "typeRoots", - type: "list", - element: { - name: "typeRoots", - type: "string", - isFilePath: true - }, - affectsModuleResolution: true, - category: Diagnostics.Modules, - description: Diagnostics.Specify_multiple_folders_that_act_like_Slashnode_modules_Slash_types - }, - { - name: "types", - type: "list", - element: { - name: "types", - type: "string" - }, - affectsProgramStructure: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Modules, - description: Diagnostics.Specify_type_package_names_to_be_included_without_being_referenced_in_a_source_file, - transpileOptionValue: undefined - }, - { - name: "allowSyntheticDefaultImports", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Interop_Constraints, - description: Diagnostics.Allow_import_x_from_y_when_a_module_doesn_t_have_a_default_export, - defaultValueDescription: Diagnostics.module_system_or_esModuleInterop - }, - { - name: "esModuleInterop", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsEmit: true, - affectsBuildInfo: true, - showInSimplifiedHelpView: true, - category: Diagnostics.Interop_Constraints, - description: Diagnostics.Emit_additional_JavaScript_to_ease_support_for_importing_CommonJS_modules_This_enables_allowSyntheticDefaultImports_for_type_compatibility, - defaultValueDescription: false, - }, - { - name: "preserveSymlinks", - type: "boolean", - category: Diagnostics.Interop_Constraints, - description: Diagnostics.Disable_resolving_symlinks_to_their_realpath_This_correlates_to_the_same_flag_in_node, - defaultValueDescription: false, - }, - { - name: "allowUmdGlobalAccess", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Modules, - description: Diagnostics.Allow_accessing_UMD_globals_from_modules, - defaultValueDescription: false, - }, - { - name: "moduleSuffixes", - type: "list", - element: { - name: "suffix", - type: "string", - }, - listPreserveFalsyValues: true, - affectsModuleResolution: true, - category: Diagnostics.Modules, - description: Diagnostics.List_of_file_name_suffixes_to_search_when_resolving_a_module, - }, - - // Source Maps - { - name: "sourceRoot", - type: "string", - affectsEmit: true, - affectsBuildInfo: true, - paramType: Diagnostics.LOCATION, - category: Diagnostics.Emit, - description: Diagnostics.Specify_the_root_path_for_debuggers_to_find_the_reference_source_code, - }, - { - name: "mapRoot", - type: "string", - affectsEmit: true, - affectsBuildInfo: true, - paramType: Diagnostics.LOCATION, - category: Diagnostics.Emit, - description: Diagnostics.Specify_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations, - }, - { - name: "inlineSources", - type: "boolean", - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Emit, - description: Diagnostics.Include_source_code_in_the_sourcemaps_inside_the_emitted_JavaScript, - defaultValueDescription: false, - }, - - // Experimental - { - name: "experimentalDecorators", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Enable_experimental_support_for_TC39_stage_2_draft_decorators, - defaultValueDescription: false, - }, - { - name: "emitDecoratorMetadata", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Emit_design_type_metadata_for_decorated_declarations_in_source_files, - defaultValueDescription: false, - }, - - // Advanced - { - name: "jsxFactory", - type: "string", - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_the_JSX_factory_function_used_when_targeting_React_JSX_emit_e_g_React_createElement_or_h, - defaultValueDescription: "`React.createElement`" - }, - { - name: "jsxFragmentFactory", - type: "string", - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_the_JSX_Fragment_reference_used_for_fragments_when_targeting_React_JSX_emit_e_g_React_Fragment_or_Fragment, - defaultValueDescription: "React.Fragment", - }, - { - name: "jsxImportSource", - type: "string", - affectsSemanticDiagnostics: true, - affectsEmit: true, - affectsBuildInfo: true, - affectsModuleResolution: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_module_specifier_used_to_import_the_JSX_factory_functions_when_using_jsx_Colon_react_jsx_Asterisk, - defaultValueDescription: "react" - }, - { - name: "resolveJsonModule", - type: "boolean", - affectsModuleResolution: true, - category: Diagnostics.Modules, - description: Diagnostics.Enable_importing_json_files, - defaultValueDescription: false, - }, - - { - name: "out", - type: "string", - affectsEmit: true, - affectsBuildInfo: true, - affectsDeclarationPath: true, - isFilePath: false, // This is intentionally broken to support compatability with existing tsconfig files - // for correct behaviour, please use outFile - category: Diagnostics.Backwards_Compatibility, - paramType: Diagnostics.FILE, - transpileOptionValue: undefined, - description: Diagnostics.Deprecated_setting_Use_outFile_instead, - }, - { - name: "reactNamespace", - type: "string", - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Specify_the_object_invoked_for_createElement_This_only_applies_when_targeting_react_JSX_emit, - defaultValueDescription: "`React`", - }, - { - name: "skipDefaultLibCheck", - type: "boolean", - // We need to store these to determine whether `lib` files need to be rechecked - affectsBuildInfo: true, - category: Diagnostics.Completeness, - description: Diagnostics.Skip_type_checking_d_ts_files_that_are_included_with_TypeScript, - defaultValueDescription: false, - }, - { - name: "charset", - type: "string", - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.No_longer_supported_In_early_versions_manually_set_the_text_encoding_for_reading_files, - defaultValueDescription: "utf8" - }, - { - name: "emitBOM", - type: "boolean", - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Emit, - description: Diagnostics.Emit_a_UTF_8_Byte_Order_Mark_BOM_in_the_beginning_of_output_files, - defaultValueDescription: false, - }, - { - name: "newLine", - type: new Map(getEntries({ - crlf: NewLineKind.CarriageReturnLineFeed, - lf: NewLineKind.LineFeed - })), - affectsEmit: true, - affectsBuildInfo: true, - paramType: Diagnostics.NEWLINE, - category: Diagnostics.Emit, - description: Diagnostics.Set_the_newline_character_for_emitting_files, - defaultValueDescription: Diagnostics.Platform_specific - }, - { - name: "noErrorTruncation", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Output_Formatting, - description: Diagnostics.Disable_truncating_types_in_error_messages, - defaultValueDescription: false, - }, - { - name: "noLib", - type: "boolean", - category: Diagnostics.Language_and_Environment, - affectsProgramStructure: true, - description: Diagnostics.Disable_including_any_library_files_including_the_default_lib_d_ts, - // We are not returning a sourceFile for lib file when asked by the program, - // so pass --noLib to avoid reporting a file not found error. - transpileOptionValue: true, - defaultValueDescription: false, - }, - { - name: "noResolve", - type: "boolean", - affectsModuleResolution: true, - category: Diagnostics.Modules, - description: Diagnostics.Disallow_import_s_require_s_or_reference_s_from_expanding_the_number_of_files_TypeScript_should_add_to_a_project, - // We are not doing a full typecheck, we are not resolving the whole context, - // so pass --noResolve to avoid reporting missing file errors. - transpileOptionValue: true, - defaultValueDescription: false, - }, - { - name: "stripInternal", - type: "boolean", - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Emit, - description: Diagnostics.Disable_emitting_declarations_that_have_internal_in_their_JSDoc_comments, - defaultValueDescription: false, - }, - { - name: "disableSizeLimit", - type: "boolean", - affectsProgramStructure: true, - category: Diagnostics.Editor_Support, - description: Diagnostics.Remove_the_20mb_cap_on_total_source_code_size_for_JavaScript_files_in_the_TypeScript_language_server, - defaultValueDescription: false, - }, - { - name: "disableSourceOfProjectReferenceRedirect", - type: "boolean", - isTSConfigOnly: true, - category: Diagnostics.Projects, - description: Diagnostics.Disable_preferring_source_files_instead_of_declaration_files_when_referencing_composite_projects, - defaultValueDescription: false, - }, - { - name: "disableSolutionSearching", - type: "boolean", - isTSConfigOnly: true, - category: Diagnostics.Projects, - description: Diagnostics.Opt_a_project_out_of_multi_project_reference_checking_when_editing, - defaultValueDescription: false, - }, - { - name: "disableReferencedProjectLoad", - type: "boolean", - isTSConfigOnly: true, - category: Diagnostics.Projects, - description: Diagnostics.Reduce_the_number_of_projects_loaded_automatically_by_TypeScript, - defaultValueDescription: false, - }, - { - name: "noImplicitUseStrict", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Disable_adding_use_strict_directives_in_emitted_JavaScript_files, - defaultValueDescription: false, - }, - { - name: "noEmitHelpers", - type: "boolean", - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Emit, - description: Diagnostics.Disable_generating_custom_helper_functions_like_extends_in_compiled_output, - defaultValueDescription: false, - }, - { - name: "noEmitOnError", - type: "boolean", - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Emit, - transpileOptionValue: undefined, - description: Diagnostics.Disable_emitting_files_if_any_type_checking_errors_are_reported, - defaultValueDescription: false, - }, - { - name: "preserveConstEnums", - type: "boolean", - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Emit, - description: Diagnostics.Disable_erasing_const_enum_declarations_in_generated_code, - defaultValueDescription: false, - }, - { - name: "declarationDir", - type: "string", - affectsEmit: true, - affectsBuildInfo: true, - affectsDeclarationPath: true, - isFilePath: true, - paramType: Diagnostics.DIRECTORY, - category: Diagnostics.Emit, - transpileOptionValue: undefined, - description: Diagnostics.Specify_the_output_directory_for_generated_declaration_files, - }, - { - name: "skipLibCheck", - type: "boolean", - // We need to store these to determine whether `lib` files need to be rechecked - affectsBuildInfo: true, - category: Diagnostics.Completeness, - description: Diagnostics.Skip_type_checking_all_d_ts_files, - defaultValueDescription: false, - }, - { - name: "allowUnusedLabels", - type: "boolean", - affectsBindDiagnostics: true, - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Disable_error_reporting_for_unused_labels, - defaultValueDescription: undefined, - }, - { - name: "allowUnreachableCode", - type: "boolean", - affectsBindDiagnostics: true, - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Type_Checking, - description: Diagnostics.Disable_error_reporting_for_unreachable_code, - defaultValueDescription: undefined, - }, - { - name: "suppressExcessPropertyErrors", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Disable_reporting_of_excess_property_errors_during_the_creation_of_object_literals, - defaultValueDescription: false, - }, - { - name: "suppressImplicitAnyIndexErrors", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Suppress_noImplicitAny_errors_when_indexing_objects_that_lack_index_signatures, - defaultValueDescription: false, - }, - { - name: "forceConsistentCasingInFileNames", - type: "boolean", - affectsModuleResolution: true, - category: Diagnostics.Interop_Constraints, - description: Diagnostics.Ensure_that_casing_is_correct_in_imports, - defaultValueDescription: false, - }, - { - name: "maxNodeModuleJsDepth", - type: "number", - affectsModuleResolution: true, - category: Diagnostics.JavaScript_Support, - description: Diagnostics.Specify_the_maximum_folder_depth_used_for_checking_JavaScript_files_from_node_modules_Only_applicable_with_allowJs, - defaultValueDescription: 0, - }, - { - name: "noStrictGenericChecks", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsBuildInfo: true, - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Disable_strict_checking_of_generic_signatures_in_function_types, - defaultValueDescription: false, - }, - { - name: "useDefineForClassFields", - type: "boolean", - affectsSemanticDiagnostics: true, - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Language_and_Environment, - description: Diagnostics.Emit_ECMAScript_standard_compliant_class_fields, - defaultValueDescription: Diagnostics.true_for_ES2022_and_above_including_ESNext - }, - { - name: "preserveValueImports", - type: "boolean", - affectsEmit: true, - affectsBuildInfo: true, - category: Diagnostics.Emit, - description: Diagnostics.Preserve_unused_imported_values_in_the_JavaScript_output_that_would_otherwise_be_removed, - defaultValueDescription: false, - }, + description: Diagnostics.Control_what_method_is_used_to_detect_module_format_JS_files, + category: Diagnostics.Language_and_Environment, + defaultValueDescription: Diagnostics.auto_Colon_Treat_files_with_imports_exports_import_meta_jsx_with_jsx_Colon_react_jsx_or_esm_format_with_module_Colon_node16_as_modules, + } +]; + +/** @internal */ +export const optionDeclarations: CommandLineOption[] = [ + ...commonOptionsWithBuild, + ...commandOptionsWithoutBuild, +]; + +/** @internal */ +export const semanticDiagnosticsOptionDeclarations: readonly CommandLineOption[] = + optionDeclarations.filter(option => !!option.affectsSemanticDiagnostics); + +/** @internal */ +export const affectsEmitOptionDeclarations: readonly CommandLineOption[] = + optionDeclarations.filter(option => !!option.affectsEmit); + +/** @internal */ +export const affectsDeclarationPathOptionDeclarations: readonly CommandLineOption[] = + optionDeclarations.filter(option => !!option.affectsDeclarationPath); + +/** @internal */ +export const moduleResolutionOptionDeclarations: readonly CommandLineOption[] = + optionDeclarations.filter(option => !!option.affectsModuleResolution); + +/** @internal */ +export const sourceFileAffectingCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => + !!option.affectsSourceFile || !!option.affectsModuleResolution || !!option.affectsBindDiagnostics); + +/** @internal */ +export const optionsAffectingProgramStructure: readonly CommandLineOption[] = + optionDeclarations.filter(option => !!option.affectsProgramStructure); + +/** @internal */ +export const transpileOptionValueCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => + hasProperty(option, "transpileOptionValue")); + +// Build related options +/** @internal */ +export const optionsForBuild: CommandLineOption[] = [ + { + name: "verbose", + shortName: "v", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Enable_verbose_logging, + type: "boolean", + defaultValueDescription: false, + }, + { + name: "dry", + shortName: "d", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean, + type: "boolean", + defaultValueDescription: false, + }, + { + name: "force", + shortName: "f", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date, + type: "boolean", + defaultValueDescription: false, + }, + { + name: "clean", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Delete_the_outputs_of_all_projects, + type: "boolean", + defaultValueDescription: false, + } +]; - { - name: "keyofStringsOnly", - type: "boolean", - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Make_keyof_only_return_strings_instead_of_string_numbers_or_symbols_Legacy_option, - defaultValueDescription: false, - }, - { - // A list of plugins to load in the language service - name: "plugins", - type: "list", - isTSConfigOnly: true, - element: { - name: "plugin", - type: "object" - }, - description: Diagnostics.Specify_a_list_of_language_service_plugins_to_include, - category: Diagnostics.Editor_Support, +/** @internal */ +export const buildOpts: CommandLineOption[] = [ + ...commonOptionsWithBuild, + ...optionsForBuild +]; - }, - { - name: "moduleDetection", - type: new Map(getEntries({ - auto: ModuleDetectionKind.Auto, - legacy: ModuleDetectionKind.Legacy, - force: ModuleDetectionKind.Force, - })), - affectsModuleResolution: true, - description: Diagnostics.Control_what_method_is_used_to_detect_module_format_JS_files, - category: Diagnostics.Language_and_Environment, - defaultValueDescription: Diagnostics.auto_Colon_Treat_files_with_imports_exports_import_meta_jsx_with_jsx_Colon_react_jsx_or_esm_format_with_module_Colon_node16_as_modules, - } - ]; - - /* @internal */ - export const optionDeclarations: CommandLineOption[] = [ - ...commonOptionsWithBuild, - ...commandOptionsWithoutBuild, - ]; - - /* @internal */ - export const semanticDiagnosticsOptionDeclarations: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsSemanticDiagnostics); - - /* @internal */ - export const affectsEmitOptionDeclarations: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsEmit); - - /* @internal */ - export const affectsDeclarationPathOptionDeclarations: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsDeclarationPath); - - /* @internal */ - export const moduleResolutionOptionDeclarations: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsModuleResolution); - - /* @internal */ - export const sourceFileAffectingCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => - !!option.affectsSourceFile || !!option.affectsModuleResolution || !!option.affectsBindDiagnostics); - - /* @internal */ - export const optionsAffectingProgramStructure: readonly CommandLineOption[] = - optionDeclarations.filter(option => !!option.affectsProgramStructure); - - /* @internal */ - export const transpileOptionValueCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => - hasProperty(option, "transpileOptionValue")); - - // Build related options - /* @internal */ - export const optionsForBuild: CommandLineOption[] = [ - { - name: "verbose", - shortName: "v", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Enable_verbose_logging, - type: "boolean", - defaultValueDescription: false, - }, - { - name: "dry", - shortName: "d", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean, - type: "boolean", - defaultValueDescription: false, - }, - { - name: "force", - shortName: "f", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date, - type: "boolean", - defaultValueDescription: false, - }, - { - name: "clean", - category: Diagnostics.Command_line_Options, - description: Diagnostics.Delete_the_outputs_of_all_projects, - type: "boolean", - defaultValueDescription: false, - } - ]; - - /* @internal */ - export const buildOpts: CommandLineOption[] = [ - ...commonOptionsWithBuild, - ...optionsForBuild - ]; - - /* @internal */ - export const typeAcquisitionDeclarations: CommandLineOption[] = [ - { - /* @deprecated typingOptions.enableAutoDiscovery - * Use typeAcquisition.enable instead. - */ - name: "enableAutoDiscovery", - type: "boolean", - defaultValueDescription: false, - }, - { - name: "enable", - type: "boolean", - defaultValueDescription: false, - }, - { +/** @internal */ +export const typeAcquisitionDeclarations: CommandLineOption[] = [ + { + /* @deprecated typingOptions.enableAutoDiscovery + * Use typeAcquisition.enable instead. + */ + name: "enableAutoDiscovery", + type: "boolean", + defaultValueDescription: false, + }, + { + name: "enable", + type: "boolean", + defaultValueDescription: false, + }, + { + name: "include", + type: "list", + element: { name: "include", - type: "list", - element: { - name: "include", - type: "string" - } - }, - { + type: "string" + } + }, + { + name: "exclude", + type: "list", + element: { name: "exclude", - type: "list", - element: { - name: "exclude", - type: "string" - } - }, - { - name: "disableFilenameBasedTypeAcquisition", - type: "boolean", - defaultValueDescription: false, - }, - ]; - - /* @internal */ - export interface OptionsNameMap { - optionsNameMap: ESMap; - shortOptionNames: ESMap; - } - - /*@internal*/ - export function createOptionNameMap(optionDeclarations: readonly CommandLineOption[]): OptionsNameMap { - const optionsNameMap = new Map(); - const shortOptionNames = new Map(); - forEach(optionDeclarations, option => { - optionsNameMap.set(option.name.toLowerCase(), option); - if (option.shortName) { - shortOptionNames.set(option.shortName, option.name); - } - }); + type: "string" + } + }, + { + name: "disableFilenameBasedTypeAcquisition", + type: "boolean", + defaultValueDescription: false, + }, +]; - return { optionsNameMap, shortOptionNames }; - } +/** @internal */ +export interface OptionsNameMap { + optionsNameMap: ESMap; + shortOptionNames: ESMap; +} - let optionsNameMapCache: OptionsNameMap; +/** @internal */ +export function createOptionNameMap(optionDeclarations: readonly CommandLineOption[]): OptionsNameMap { + const optionsNameMap = new Map(); + const shortOptionNames = new Map(); + forEach(optionDeclarations, option => { + optionsNameMap.set(option.name.toLowerCase(), option); + if (option.shortName) { + shortOptionNames.set(option.shortName, option.name); + } + }); - /* @internal */ - export function getOptionsNameMap(): OptionsNameMap { - return optionsNameMapCache ||= createOptionNameMap(optionDeclarations); - } + return { optionsNameMap, shortOptionNames }; +} - const compilerOptionsAlternateMode: AlternateModeDiagnostics = { - diagnostic: Diagnostics.Compiler_option_0_may_only_be_used_with_build, - getOptionsNameMap: getBuildOptionsNameMap - }; +let optionsNameMapCache: OptionsNameMap; - /* @internal */ - export const defaultInitCompilerOptions: CompilerOptions = { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES2016, - strict: true, - esModuleInterop: true, - forceConsistentCasingInFileNames: true, - skipLibCheck: true - }; +/** @internal */ +export function getOptionsNameMap(): OptionsNameMap { + return optionsNameMapCache ||= createOptionNameMap(optionDeclarations); +} - /* @internal */ - export function convertEnableAutoDiscoveryToEnable(typeAcquisition: TypeAcquisition): TypeAcquisition { - // Convert deprecated typingOptions.enableAutoDiscovery to typeAcquisition.enable - if (typeAcquisition && typeAcquisition.enableAutoDiscovery !== undefined && typeAcquisition.enable === undefined) { - return { - enable: typeAcquisition.enableAutoDiscovery, - include: typeAcquisition.include || [], - exclude: typeAcquisition.exclude || [] - }; - } - return typeAcquisition; +const compilerOptionsAlternateMode: AlternateModeDiagnostics = { + diagnostic: Diagnostics.Compiler_option_0_may_only_be_used_with_build, + getOptionsNameMap: getBuildOptionsNameMap +}; + +/** @internal */ +export const defaultInitCompilerOptions: CompilerOptions = { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES2016, + strict: true, + esModuleInterop: true, + forceConsistentCasingInFileNames: true, + skipLibCheck: true +}; + +/** @internal */ +export function convertEnableAutoDiscoveryToEnable(typeAcquisition: TypeAcquisition): TypeAcquisition { + // Convert deprecated typingOptions.enableAutoDiscovery to typeAcquisition.enable + if (typeAcquisition && typeAcquisition.enableAutoDiscovery !== undefined && typeAcquisition.enable === undefined) { + return { + enable: typeAcquisition.enableAutoDiscovery, + include: typeAcquisition.include || [], + exclude: typeAcquisition.exclude || [] + }; } + return typeAcquisition; +} - /* @internal */ - export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { - return createDiagnosticForInvalidCustomType(opt, createCompilerDiagnostic); - } +/** @internal */ +export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { + return createDiagnosticForInvalidCustomType(opt, createCompilerDiagnostic); +} - function createDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType, createDiagnostic: (message: DiagnosticMessage, arg0: string, arg1: string) => Diagnostic): Diagnostic { - const namesOfType = arrayFrom(opt.type.keys()).map(key => `'${key}'`).join(", "); - return createDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); - } +function createDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType, createDiagnostic: (message: DiagnosticMessage, arg0: string, arg1: string) => Diagnostic): Diagnostic { + const namesOfType = arrayFrom(opt.type.keys()).map(key => `'${key}'`).join(", "); + return createDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); +} - /* @internal */ - export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { - return convertJsonOptionOfCustomType(opt, trimString(value || ""), errors); - } +/** @internal */ +export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { + return convertJsonOptionOfCustomType(opt, trimString(value || ""), errors); +} - /* @internal */ - export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "", errors: Push): (string | number)[] | undefined { - value = trimString(value); - if (startsWith(value, "-")) { - return undefined; - } - if (value === "") { - return []; - } - const values = value.split(","); - switch (opt.element.type) { - case "number": - return mapDefined(values, v => validateJsonOptionValue(opt.element, parseInt(v), errors)); - case "string": - return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors)); - default: - return mapDefined(values, v => parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors)); - } +/** @internal */ +export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "", errors: Push): (string | number)[] | undefined { + value = trimString(value); + if (startsWith(value, "-")) { + return undefined; } - - /*@internal*/ - export interface OptionsBase { - [option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined; + if (value === "") { + return []; } - - /*@internal*/ - export interface ParseCommandLineWorkerDiagnostics extends DidYouMeanOptionsDiagnostics { - getOptionsNameMap: () => OptionsNameMap; - optionTypeMismatchDiagnostic: DiagnosticMessage; + const values = value.split(","); + switch (opt.element.type) { + case "number": + return mapDefined(values, v => validateJsonOptionValue(opt.element, parseInt(v), errors)); + case "string": + return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors)); + default: + return mapDefined(values, v => parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors)); } +} - function getOptionName(option: CommandLineOption) { - return option.name; - } +/** @internal */ +export interface OptionsBase { + [option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined; +} - function createUnknownOptionError( - unknownOption: string, - diagnostics: DidYouMeanOptionsDiagnostics, - createDiagnostics: (message: DiagnosticMessage, arg0: string, arg1?: string) => Diagnostic, - unknownOptionErrorText?: string - ) { - if (diagnostics.alternateMode?.getOptionsNameMap().optionsNameMap.has(unknownOption.toLowerCase())) { - return createDiagnostics(diagnostics.alternateMode.diagnostic, unknownOption); - } +/** @internal */ +export interface ParseCommandLineWorkerDiagnostics extends DidYouMeanOptionsDiagnostics { + getOptionsNameMap: () => OptionsNameMap; + optionTypeMismatchDiagnostic: DiagnosticMessage; +} + +function getOptionName(option: CommandLineOption) { + return option.name; +} - const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName); - return possibleOption ? - createDiagnostics(diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : - createDiagnostics(diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption); +function createUnknownOptionError( + unknownOption: string, + diagnostics: DidYouMeanOptionsDiagnostics, + createDiagnostics: (message: DiagnosticMessage, arg0: string, arg1?: string) => Diagnostic, + unknownOptionErrorText?: string +) { + if (diagnostics.alternateMode?.getOptionsNameMap().optionsNameMap.has(unknownOption.toLowerCase())) { + return createDiagnostics(diagnostics.alternateMode.diagnostic, unknownOption); } - /*@internal*/ - export function parseCommandLineWorker( - diagnostics: ParseCommandLineWorkerDiagnostics, - commandLine: readonly string[], - readFile?: (path: string) => string | undefined) { - const options = {} as OptionsBase; - let watchOptions: WatchOptions | undefined; - const fileNames: string[] = []; - const errors: Diagnostic[] = []; + const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName); + return possibleOption ? + createDiagnostics(diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : + createDiagnostics(diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption); +} - parseStrings(commandLine); - return { - options, - watchOptions, - fileNames, - errors - }; +/** @internal */ +export function parseCommandLineWorker( + diagnostics: ParseCommandLineWorkerDiagnostics, + commandLine: readonly string[], + readFile?: (path: string) => string | undefined) { + const options = {} as OptionsBase; + let watchOptions: WatchOptions | undefined; + const fileNames: string[] = []; + const errors: Diagnostic[] = []; + + parseStrings(commandLine); + return { + options, + watchOptions, + fileNames, + errors + }; - function parseStrings(args: readonly string[]) { - let i = 0; - while (i < args.length) { - const s = args[i]; - i++; - if (s.charCodeAt(0) === CharacterCodes.at) { - parseResponseFile(s.slice(1)); - } - else if (s.charCodeAt(0) === CharacterCodes.minus) { - const inputOptionName = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1); - const opt = getOptionDeclarationFromName(diagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); - if (opt) { - i = parseOptionValue(args, i, diagnostics, opt, options, errors); - } - else { - const watchOpt = getOptionDeclarationFromName(watchOptionsDidYouMeanDiagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); - if (watchOpt) { - i = parseOptionValue(args, i, watchOptionsDidYouMeanDiagnostics, watchOpt, watchOptions || (watchOptions = {}), errors); - } - else { - errors.push(createUnknownOptionError(inputOptionName, diagnostics, createCompilerDiagnostic, s)); - } - } + function parseStrings(args: readonly string[]) { + let i = 0; + while (i < args.length) { + const s = args[i]; + i++; + if (s.charCodeAt(0) === CharacterCodes.at) { + parseResponseFile(s.slice(1)); + } + else if (s.charCodeAt(0) === CharacterCodes.minus) { + const inputOptionName = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1); + const opt = getOptionDeclarationFromName(diagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); + if (opt) { + i = parseOptionValue(args, i, diagnostics, opt, options, errors); } else { - fileNames.push(s); - } - } - } - - function parseResponseFile(fileName: string) { - const text = tryReadFile(fileName, readFile || (fileName => sys.readFile(fileName))); - if (!isString(text)) { - errors.push(text); - return; - } - - const args: string[] = []; - let pos = 0; - while (true) { - while (pos < text.length && text.charCodeAt(pos) <= CharacterCodes.space) pos++; - if (pos >= text.length) break; - const start = pos; - if (text.charCodeAt(start) === CharacterCodes.doubleQuote) { - pos++; - while (pos < text.length && text.charCodeAt(pos) !== CharacterCodes.doubleQuote) pos++; - if (pos < text.length) { - args.push(text.substring(start + 1, pos)); - pos++; + const watchOpt = getOptionDeclarationFromName(watchOptionsDidYouMeanDiagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true); + if (watchOpt) { + i = parseOptionValue(args, i, watchOptionsDidYouMeanDiagnostics, watchOpt, watchOptions || (watchOptions = {}), errors); } else { - errors.push(createCompilerDiagnostic(Diagnostics.Unterminated_quoted_string_in_response_file_0, fileName)); + errors.push(createUnknownOptionError(inputOptionName, diagnostics, createCompilerDiagnostic, s)); } } - else { - while (text.charCodeAt(pos) > CharacterCodes.space) pos++; - args.push(text.substring(start, pos)); - } } - parseStrings(args); + else { + fileNames.push(s); + } } } - function parseOptionValue( - args: readonly string[], - i: number, - diagnostics: ParseCommandLineWorkerDiagnostics, - opt: CommandLineOption, - options: OptionsBase, - errors: Diagnostic[] - ) { - if (opt.isTSConfigOnly) { - const optValue = args[i]; - if (optValue === "null") { - options[opt.name] = undefined; - i++; - } - else if (opt.type === "boolean") { - if (optValue === "false") { - options[opt.name] = validateJsonOptionValue(opt, /*value*/ false, errors); - i++; + function parseResponseFile(fileName: string) { + const text = tryReadFile(fileName, readFile || (fileName => sys.readFile(fileName))); + if (!isString(text)) { + errors.push(text); + return; + } + + const args: string[] = []; + let pos = 0; + while (true) { + while (pos < text.length && text.charCodeAt(pos) <= CharacterCodes.space) pos++; + if (pos >= text.length) break; + const start = pos; + if (text.charCodeAt(start) === CharacterCodes.doubleQuote) { + pos++; + while (pos < text.length && text.charCodeAt(pos) !== CharacterCodes.doubleQuote) pos++; + if (pos < text.length) { + args.push(text.substring(start + 1, pos)); + pos++; } else { - if (optValue === "true") i++; - errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_false_or_null_on_command_line, opt.name)); + errors.push(createCompilerDiagnostic(Diagnostics.Unterminated_quoted_string_in_response_file_0, fileName)); } } else { - errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line, opt.name)); - if (optValue && !startsWith(optValue, "-")) i++; + while (text.charCodeAt(pos) > CharacterCodes.space) pos++; + args.push(text.substring(start, pos)); } } - else { - // Check to see if no argument was provided (e.g. "--locale" is the last command-line argument). - if (!args[i] && opt.type !== "boolean") { - errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt))); - } + parseStrings(args); + } +} - if (args[i] !== "null") { - switch (opt.type) { - case "number": - options[opt.name] = validateJsonOptionValue(opt, parseInt(args[i]), errors); - i++; - break; - case "boolean": - // boolean flag has optional value true, false, others - const optValue = args[i]; - options[opt.name] = validateJsonOptionValue(opt, optValue !== "false", errors); - // consume next argument as boolean flag value - if (optValue === "false" || optValue === "true") { - i++; - } - break; - case "string": - options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors); - i++; - break; - case "list": - const result = parseListTypeOption(opt, args[i], errors); - options[opt.name] = result || []; - if (result) { - i++; - } - break; - // If not a primitive, the possible types are specified in what is effectively a map of options. - default: - options[opt.name] = parseCustomTypeOption(opt as CommandLineOptionOfCustomType, args[i], errors); - i++; - break; - } +function parseOptionValue( + args: readonly string[], + i: number, + diagnostics: ParseCommandLineWorkerDiagnostics, + opt: CommandLineOption, + options: OptionsBase, + errors: Diagnostic[] +) { + if (opt.isTSConfigOnly) { + const optValue = args[i]; + if (optValue === "null") { + options[opt.name] = undefined; + i++; + } + else if (opt.type === "boolean") { + if (optValue === "false") { + options[opt.name] = validateJsonOptionValue(opt, /*value*/ false, errors); + i++; } else { - options[opt.name] = undefined; - i++; + if (optValue === "true") i++; + errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_false_or_null_on_command_line, opt.name)); } } - return i; - } - - /*@internal*/ - export const compilerOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { - alternateMode: compilerOptionsAlternateMode, - getOptionsNameMap, - optionDeclarations, - unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, - optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument - }; - export function parseCommandLine(commandLine: readonly string[], readFile?: (path: string) => string | undefined): ParsedCommandLine { - return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile); - } - - /** @internal */ - export function getOptionFromName(optionName: string, allowShort?: boolean): CommandLineOption | undefined { - return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort); + else { + errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line, opt.name)); + if (optValue && !startsWith(optValue, "-")) i++; + } } + else { + // Check to see if no argument was provided (e.g. "--locale" is the last command-line argument). + if (!args[i] && opt.type !== "boolean") { + errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt))); + } - function getOptionDeclarationFromName(getOptionNameMap: () => OptionsNameMap, optionName: string, allowShort = false): CommandLineOption | undefined { - optionName = optionName.toLowerCase(); - const { optionsNameMap, shortOptionNames } = getOptionNameMap(); - // Try to translate short option names to their full equivalents. - if (allowShort) { - const short = shortOptionNames.get(optionName); - if (short !== undefined) { - optionName = short; + if (args[i] !== "null") { + switch (opt.type) { + case "number": + options[opt.name] = validateJsonOptionValue(opt, parseInt(args[i]), errors); + i++; + break; + case "boolean": + // boolean flag has optional value true, false, others + const optValue = args[i]; + options[opt.name] = validateJsonOptionValue(opt, optValue !== "false", errors); + // consume next argument as boolean flag value + if (optValue === "false" || optValue === "true") { + i++; + } + break; + case "string": + options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors); + i++; + break; + case "list": + const result = parseListTypeOption(opt, args[i], errors); + options[opt.name] = result || []; + if (result) { + i++; + } + break; + // If not a primitive, the possible types are specified in what is effectively a map of options. + default: + options[opt.name] = parseCustomTypeOption(opt as CommandLineOptionOfCustomType, args[i], errors); + i++; + break; } } - return optionsNameMap.get(optionName); + else { + options[opt.name] = undefined; + i++; + } } + return i; +} - /*@internal*/ - export interface ParsedBuildCommand { - buildOptions: BuildOptions; - watchOptions: WatchOptions | undefined; - projects: string[]; - errors: Diagnostic[]; - } +/** @internal */ +export const compilerOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { + alternateMode: compilerOptionsAlternateMode, + getOptionsNameMap, + optionDeclarations, + unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, + optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument +}; +export function parseCommandLine(commandLine: readonly string[], readFile?: (path: string) => string | undefined): ParsedCommandLine { + return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile); +} + +/** @internal */ +export function getOptionFromName(optionName: string, allowShort?: boolean): CommandLineOption | undefined { + return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort); +} - let buildOptionsNameMapCache: OptionsNameMap; - function getBuildOptionsNameMap(): OptionsNameMap { - return buildOptionsNameMapCache || (buildOptionsNameMapCache = createOptionNameMap(buildOpts)); +function getOptionDeclarationFromName(getOptionNameMap: () => OptionsNameMap, optionName: string, allowShort = false): CommandLineOption | undefined { + optionName = optionName.toLowerCase(); + const { optionsNameMap, shortOptionNames } = getOptionNameMap(); + // Try to translate short option names to their full equivalents. + if (allowShort) { + const short = shortOptionNames.get(optionName); + if (short !== undefined) { + optionName = short; + } } + return optionsNameMap.get(optionName); +} - const buildOptionsAlternateMode: AlternateModeDiagnostics = { - diagnostic: Diagnostics.Compiler_option_0_may_not_be_used_with_build, - getOptionsNameMap - }; +/** @internal */ +export interface ParsedBuildCommand { + buildOptions: BuildOptions; + watchOptions: WatchOptions | undefined; + projects: string[]; + errors: Diagnostic[]; +} - const buildOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { - alternateMode: buildOptionsAlternateMode, - getOptionsNameMap: getBuildOptionsNameMap, - optionDeclarations: buildOpts, - unknownOptionDiagnostic: Diagnostics.Unknown_build_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_build_option_0_Did_you_mean_1, - optionTypeMismatchDiagnostic: Diagnostics.Build_option_0_requires_a_value_of_type_1 - }; +let buildOptionsNameMapCache: OptionsNameMap; +function getBuildOptionsNameMap(): OptionsNameMap { + return buildOptionsNameMapCache || (buildOptionsNameMapCache = createOptionNameMap(buildOpts)); +} - /*@internal*/ - export function parseBuildCommand(args: readonly string[]): ParsedBuildCommand { - const { options, watchOptions, fileNames: projects, errors } = parseCommandLineWorker( - buildOptionsDidYouMeanDiagnostics, - args - ); - const buildOptions = options as BuildOptions; +const buildOptionsAlternateMode: AlternateModeDiagnostics = { + diagnostic: Diagnostics.Compiler_option_0_may_not_be_used_with_build, + getOptionsNameMap +}; - if (projects.length === 0) { - // tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ." - projects.push("."); - } +const buildOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { + alternateMode: buildOptionsAlternateMode, + getOptionsNameMap: getBuildOptionsNameMap, + optionDeclarations: buildOpts, + unknownOptionDiagnostic: Diagnostics.Unknown_build_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_build_option_0_Did_you_mean_1, + optionTypeMismatchDiagnostic: Diagnostics.Build_option_0_requires_a_value_of_type_1 +}; - // Nonsensical combinations - if (buildOptions.clean && buildOptions.force) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force")); - } - if (buildOptions.clean && buildOptions.verbose) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose")); - } - if (buildOptions.clean && buildOptions.watch) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch")); - } - if (buildOptions.watch && buildOptions.dry) { - errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry")); - } +/** @internal */ +export function parseBuildCommand(args: readonly string[]): ParsedBuildCommand { + const { options, watchOptions, fileNames: projects, errors } = parseCommandLineWorker( + buildOptionsDidYouMeanDiagnostics, + args + ); + const buildOptions = options as BuildOptions; - return { buildOptions, watchOptions, projects, errors }; + if (projects.length === 0) { + // tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ." + projects.push("."); } - /* @internal */ - export function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { - const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); - return diagnostic.messageText as string; + // Nonsensical combinations + if (buildOptions.clean && buildOptions.force) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force")); } - - export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - /** - * Reports config file diagnostics - */ - export interface ConfigFileDiagnosticsReporter { - /** - * Reports unrecoverable error when parsing config file - */ - onUnRecoverableConfigFileDiagnostic: DiagnosticReporter; + if (buildOptions.clean && buildOptions.verbose) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose")); } - - /** - * Interface extending ParseConfigHost to support ParseConfigFile that reads config file and reports errors - */ - export interface ParseConfigFileHost extends ParseConfigHost, ConfigFileDiagnosticsReporter { - getCurrentDirectory(): string; + if (buildOptions.clean && buildOptions.watch) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch")); + } + if (buildOptions.watch && buildOptions.dry) { + errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry")); } - /** - * Reads the config file, reports errors if any and exits if the config file cannot be found - */ - export function getParsedCommandLineOfConfigFile( - configFileName: string, - optionsToExtend: CompilerOptions | undefined, - host: ParseConfigFileHost, - extendedConfigCache?: Map, - watchOptionsToExtend?: WatchOptions, - extraFileExtensions?: readonly FileExtensionInfo[], - ): ParsedCommandLine | undefined { - const configFileText = tryReadFile(configFileName, fileName => host.readFile(fileName)); - if (!isString(configFileText)) { - host.onUnRecoverableConfigFileDiagnostic(configFileText); - return undefined; - } + return { buildOptions, watchOptions, projects, errors }; +} - const result = parseJsonText(configFileName, configFileText); - const cwd = host.getCurrentDirectory(); - result.path = toPath(configFileName, cwd, createGetCanonicalFileName(host.useCaseSensitiveFileNames)); - result.resolvedPath = result.path; - result.originalFileName = result.fileName; - return parseJsonSourceFileConfigFileContent( - result, - host, - getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), - optionsToExtend, - getNormalizedAbsolutePath(configFileName, cwd), - /*resolutionStack*/ undefined, - extraFileExtensions, - extendedConfigCache, - watchOptionsToExtend - ); - } +/** @internal */ +export function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { + const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); + return diagnostic.messageText as string; +} +export type DiagnosticReporter = (diagnostic: Diagnostic) => void; +/** + * Reports config file diagnostics + */ +export interface ConfigFileDiagnosticsReporter { /** - * Read tsconfig.json file - * @param fileName The path to the config file + * Reports unrecoverable error when parsing config file */ - export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: Diagnostic } { - const textOrDiagnostic = tryReadFile(fileName, readFile); - return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic }; - } + onUnRecoverableConfigFileDiagnostic: DiagnosticReporter; +} - /** - * Parse the text of the tsconfig.json file - * @param fileName The path to the config file - * @param jsonText The text of the config file - */ - export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } { - const jsonSourceFile = parseJsonText(fileName, jsonText); - return { - config: convertConfigFileToObject(jsonSourceFile, jsonSourceFile.parseDiagnostics, /*reportOptionsErrors*/ false, /*optionsIterator*/ undefined), - error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined - }; - } +/** + * Interface extending ParseConfigHost to support ParseConfigFile that reads config file and reports errors + */ +export interface ParseConfigFileHost extends ParseConfigHost, ConfigFileDiagnosticsReporter { + getCurrentDirectory(): string; +} - /** - * Read tsconfig.json file - * @param fileName The path to the config file - */ - export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): TsConfigSourceFile { - const textOrDiagnostic = tryReadFile(fileName, readFile); - return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] } as TsConfigSourceFile; +/** + * Reads the config file, reports errors if any and exits if the config file cannot be found + */ +export function getParsedCommandLineOfConfigFile( + configFileName: string, + optionsToExtend: CompilerOptions | undefined, + host: ParseConfigFileHost, + extendedConfigCache?: Map, + watchOptionsToExtend?: WatchOptions, + extraFileExtensions?: readonly FileExtensionInfo[], +): ParsedCommandLine | undefined { + const configFileText = tryReadFile(configFileName, fileName => host.readFile(fileName)); + if (!isString(configFileText)) { + host.onUnRecoverableConfigFileDiagnostic(configFileText); + return undefined; } - /*@internal*/ - export function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic { - let text: string | undefined; - try { - text = readFile(fileName); - } - catch (e) { - return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message); - } - return text === undefined ? createCompilerDiagnostic(Diagnostics.Cannot_read_file_0, fileName) : text; - } + const result = parseJsonText(configFileName, configFileText); + const cwd = host.getCurrentDirectory(); + result.path = toPath(configFileName, cwd, createGetCanonicalFileName(host.useCaseSensitiveFileNames)); + result.resolvedPath = result.path; + result.originalFileName = result.fileName; + return parseJsonSourceFileConfigFileContent( + result, + host, + getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), + optionsToExtend, + getNormalizedAbsolutePath(configFileName, cwd), + /*resolutionStack*/ undefined, + extraFileExtensions, + extendedConfigCache, + watchOptionsToExtend + ); +} - function commandLineOptionsToMap(options: readonly CommandLineOption[]) { - return arrayToMap(options, getOptionName); - } +/** + * Read tsconfig.json file + * @param fileName The path to the config file + */ +export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: Diagnostic } { + const textOrDiagnostic = tryReadFile(fileName, readFile); + return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic }; +} - const typeAcquisitionDidYouMeanDiagnostics: DidYouMeanOptionsDiagnostics = { - optionDeclarations: typeAcquisitionDeclarations, - unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1, +/** + * Parse the text of the tsconfig.json file + * @param fileName The path to the config file + * @param jsonText The text of the config file + */ +export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } { + const jsonSourceFile = parseJsonText(fileName, jsonText); + return { + config: convertConfigFileToObject(jsonSourceFile, jsonSourceFile.parseDiagnostics, /*reportOptionsErrors*/ false, /*optionsIterator*/ undefined), + error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined }; +} + +/** + * Read tsconfig.json file + * @param fileName The path to the config file + */ +export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): TsConfigSourceFile { + const textOrDiagnostic = tryReadFile(fileName, readFile); + return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] } as TsConfigSourceFile; +} - let watchOptionsNameMapCache: OptionsNameMap; - function getWatchOptionsNameMap(): OptionsNameMap { - return watchOptionsNameMapCache || (watchOptionsNameMapCache = createOptionNameMap(optionsForWatch)); +/** @internal */ +export function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic { + let text: string | undefined; + try { + text = readFile(fileName); } - const watchOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { - getOptionsNameMap: getWatchOptionsNameMap, - optionDeclarations: optionsForWatch, - unknownOptionDiagnostic: Diagnostics.Unknown_watch_option_0, - unknownDidYouMeanDiagnostic: Diagnostics.Unknown_watch_option_0_Did_you_mean_1, - optionTypeMismatchDiagnostic: Diagnostics.Watch_option_0_requires_a_value_of_type_1 - }; + catch (e) { + return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message); + } + return text === undefined ? createCompilerDiagnostic(Diagnostics.Cannot_read_file_0, fileName) : text; +} - let commandLineCompilerOptionsMapCache: ESMap; - function getCommandLineCompilerOptionsMap() { - return commandLineCompilerOptionsMapCache || (commandLineCompilerOptionsMapCache = commandLineOptionsToMap(optionDeclarations)); - } - let commandLineWatchOptionsMapCache: ESMap; - function getCommandLineWatchOptionsMap() { - return commandLineWatchOptionsMapCache || (commandLineWatchOptionsMapCache = commandLineOptionsToMap(optionsForWatch)); - } - let commandLineTypeAcquisitionMapCache: ESMap; - function getCommandLineTypeAcquisitionMap() { - return commandLineTypeAcquisitionMapCache || (commandLineTypeAcquisitionMapCache = commandLineOptionsToMap(typeAcquisitionDeclarations)); - } - - let _tsconfigRootOptions: TsConfigOnlyOption; - function getTsconfigRootOptionsMap() { - if (_tsconfigRootOptions === undefined) { - _tsconfigRootOptions = { - name: undefined!, // should never be needed since this is root - type: "object", - elementOptions: commandLineOptionsToMap([ - { - name: "compilerOptions", - type: "object", - elementOptions: getCommandLineCompilerOptionsMap(), - extraKeyDiagnostics: compilerOptionsDidYouMeanDiagnostics, - }, - { - name: "watchOptions", - type: "object", - elementOptions: getCommandLineWatchOptionsMap(), - extraKeyDiagnostics: watchOptionsDidYouMeanDiagnostics, - }, - { - name: "typingOptions", - type: "object", - elementOptions: getCommandLineTypeAcquisitionMap(), - extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics, - }, - { - name: "typeAcquisition", - type: "object", - elementOptions: getCommandLineTypeAcquisitionMap(), - extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics - }, - { - name: "extends", - type: "string", - category: Diagnostics.File_Management, - }, - { +function commandLineOptionsToMap(options: readonly CommandLineOption[]) { + return arrayToMap(options, getOptionName); +} + +const typeAcquisitionDidYouMeanDiagnostics: DidYouMeanOptionsDiagnostics = { + optionDeclarations: typeAcquisitionDeclarations, + unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1, +}; + +let watchOptionsNameMapCache: OptionsNameMap; +function getWatchOptionsNameMap(): OptionsNameMap { + return watchOptionsNameMapCache || (watchOptionsNameMapCache = createOptionNameMap(optionsForWatch)); +} +const watchOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { + getOptionsNameMap: getWatchOptionsNameMap, + optionDeclarations: optionsForWatch, + unknownOptionDiagnostic: Diagnostics.Unknown_watch_option_0, + unknownDidYouMeanDiagnostic: Diagnostics.Unknown_watch_option_0_Did_you_mean_1, + optionTypeMismatchDiagnostic: Diagnostics.Watch_option_0_requires_a_value_of_type_1 +}; + +let commandLineCompilerOptionsMapCache: ESMap; +function getCommandLineCompilerOptionsMap() { + return commandLineCompilerOptionsMapCache || (commandLineCompilerOptionsMapCache = commandLineOptionsToMap(optionDeclarations)); +} +let commandLineWatchOptionsMapCache: ESMap; +function getCommandLineWatchOptionsMap() { + return commandLineWatchOptionsMapCache || (commandLineWatchOptionsMapCache = commandLineOptionsToMap(optionsForWatch)); +} +let commandLineTypeAcquisitionMapCache: ESMap; +function getCommandLineTypeAcquisitionMap() { + return commandLineTypeAcquisitionMapCache || (commandLineTypeAcquisitionMapCache = commandLineOptionsToMap(typeAcquisitionDeclarations)); +} + +let _tsconfigRootOptions: TsConfigOnlyOption; +function getTsconfigRootOptionsMap() { + if (_tsconfigRootOptions === undefined) { + _tsconfigRootOptions = { + name: undefined!, // should never be needed since this is root + type: "object", + elementOptions: commandLineOptionsToMap([ + { + name: "compilerOptions", + type: "object", + elementOptions: getCommandLineCompilerOptionsMap(), + extraKeyDiagnostics: compilerOptionsDidYouMeanDiagnostics, + }, + { + name: "watchOptions", + type: "object", + elementOptions: getCommandLineWatchOptionsMap(), + extraKeyDiagnostics: watchOptionsDidYouMeanDiagnostics, + }, + { + name: "typingOptions", + type: "object", + elementOptions: getCommandLineTypeAcquisitionMap(), + extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics, + }, + { + name: "typeAcquisition", + type: "object", + elementOptions: getCommandLineTypeAcquisitionMap(), + extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics + }, + { + name: "extends", + type: "string", + category: Diagnostics.File_Management, + }, + { + name: "references", + type: "list", + element: { name: "references", - type: "list", - element: { - name: "references", - type: "object" - }, - category: Diagnostics.Projects, + type: "object" }, - { + category: Diagnostics.Projects, + }, + { + name: "files", + type: "list", + element: { name: "files", - type: "list", - element: { - name: "files", - type: "string" - }, - category: Diagnostics.File_Management, + type: "string" }, - { + category: Diagnostics.File_Management, + }, + { + name: "include", + type: "list", + element: { name: "include", - type: "list", - element: { - name: "include", - type: "string" - }, - category: Diagnostics.File_Management, - defaultValueDescription: Diagnostics.if_files_is_specified_otherwise_Asterisk_Asterisk_Slash_Asterisk + type: "string" }, - { + category: Diagnostics.File_Management, + defaultValueDescription: Diagnostics.if_files_is_specified_otherwise_Asterisk_Asterisk_Slash_Asterisk + }, + { + name: "exclude", + type: "list", + element: { name: "exclude", - type: "list", - element: { - name: "exclude", - type: "string" - }, - category: Diagnostics.File_Management, - defaultValueDescription: Diagnostics.node_modules_bower_components_jspm_packages_plus_the_value_of_outDir_if_one_is_specified + type: "string" }, - compileOnSaveCommandLineOption - ]) - }; - } - return _tsconfigRootOptions; - } - - /*@internal*/ - interface JsonConversionNotifier { - /** - * Notifies parent option object is being set with the optionKey and a valid optionValue - * Currently it notifies only if there is element with type object (parentOption) and - * has element's option declarations map associated with it - * @param parentOption parent option name in which the option and value are being set - * @param option option declaration which is being set with the value - * @param value value of the option - */ - onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue): void; - /** - * Notify when valid root key value option is being set - * @param key option key - * @param keyNode node corresponding to node in the source file - * @param value computed value of the key - * @param ValueNode node corresponding to value in the source file - */ - onSetValidOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; - /** - * Notify when unknown root key value option is being set - * @param key option key - * @param keyNode node corresponding to node in the source file - * @param value computed value of the key - * @param ValueNode node corresponding to value in the source file - */ - onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; - } - - function convertConfigFileToObject(sourceFile: JsonSourceFile, errors: Push, reportOptionsErrors: boolean, optionsIterator: JsonConversionNotifier | undefined): any { - const rootExpression: Expression | undefined = sourceFile.statements[0]?.expression; - const knownRootOptions = reportOptionsErrors ? getTsconfigRootOptionsMap() : undefined; - if (rootExpression && rootExpression.kind !== SyntaxKind.ObjectLiteralExpression) { - errors.push(createDiagnosticForNodeInSourceFile( - sourceFile, - rootExpression, - Diagnostics.The_root_value_of_a_0_file_must_be_an_object, - getBaseFileName(sourceFile.fileName) === "jsconfig.json" ? "jsconfig.json" : "tsconfig.json" - )); - // Last-ditch error recovery. Somewhat useful because the JSON parser will recover from some parse errors by - // synthesizing a top-level array literal expression. There's a reasonable chance the first element of that - // array is a well-formed configuration object, made into an array element by stray characters. - if (isArrayLiteralExpression(rootExpression)) { - const firstObject = find(rootExpression.elements, isObjectLiteralExpression); - if (firstObject) { - return convertToObjectWorker(sourceFile, firstObject, errors, /*returnValue*/ true, knownRootOptions, optionsIterator); - } - } - return {}; - } - return convertToObjectWorker(sourceFile, rootExpression, errors, /*returnValue*/ true, knownRootOptions, optionsIterator); + category: Diagnostics.File_Management, + defaultValueDescription: Diagnostics.node_modules_bower_components_jspm_packages_plus_the_value_of_outDir_if_one_is_specified + }, + compileOnSaveCommandLineOption + ]) + }; } + return _tsconfigRootOptions; +} +/** @internal */ +export interface JsonConversionNotifier { /** - * Convert the json syntax tree into the json value + * Notifies parent option object is being set with the optionKey and a valid optionValue + * Currently it notifies only if there is element with type object (parentOption) and + * has element's option declarations map associated with it + * @param parentOption parent option name in which the option and value are being set + * @param option option declaration which is being set with the value + * @param value value of the option */ - export function convertToObject(sourceFile: JsonSourceFile, errors: Push): any { - return convertToObjectWorker(sourceFile, sourceFile.statements[0]?.expression, errors, /*returnValue*/ true, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); - } - + onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue): void; /** - * Convert the json syntax tree into the json value and report errors - * This returns the json value (apart from checking errors) only if returnValue provided is true. - * Otherwise it just checks the errors and returns undefined + * Notify when valid root key value option is being set + * @param key option key + * @param keyNode node corresponding to node in the source file + * @param value computed value of the key + * @param ValueNode node corresponding to value in the source file */ - /*@internal*/ - export function convertToObjectWorker( - sourceFile: JsonSourceFile, - rootExpression: Expression | undefined, - errors: Push, - returnValue: boolean, - knownRootOptions: CommandLineOption | undefined, - jsonConversionNotifier: JsonConversionNotifier | undefined): any { - if (!rootExpression) { - return returnValue ? {} : undefined; - } - - return convertPropertyValueToJson(rootExpression, knownRootOptions); - - function isRootOptionMap(knownOptions: ESMap | undefined) { - return knownRootOptions && (knownRootOptions as TsConfigOnlyOption).elementOptions === knownOptions; - } - - function convertObjectLiteralExpressionToJson( - node: ObjectLiteralExpression, - knownOptions: ESMap | undefined, - extraKeyDiagnostics: DidYouMeanOptionsDiagnostics | undefined, - parentOption: string | undefined - ): any { - const result: any = returnValue ? {} : undefined; - for (const element of node.properties) { - if (element.kind !== SyntaxKind.PropertyAssignment) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element, Diagnostics.Property_assignment_expected)); - continue; - } + onSetValidOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; + /** + * Notify when unknown root key value option is being set + * @param key option key + * @param keyNode node corresponding to node in the source file + * @param value computed value of the key + * @param ValueNode node corresponding to value in the source file + */ + onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; +} + +function convertConfigFileToObject(sourceFile: JsonSourceFile, errors: Push, reportOptionsErrors: boolean, optionsIterator: JsonConversionNotifier | undefined): any { + const rootExpression: Expression | undefined = sourceFile.statements[0]?.expression; + const knownRootOptions = reportOptionsErrors ? getTsconfigRootOptionsMap() : undefined; + if (rootExpression && rootExpression.kind !== SyntaxKind.ObjectLiteralExpression) { + errors.push(createDiagnosticForNodeInSourceFile( + sourceFile, + rootExpression, + Diagnostics.The_root_value_of_a_0_file_must_be_an_object, + getBaseFileName(sourceFile.fileName) === "jsconfig.json" ? "jsconfig.json" : "tsconfig.json" + )); + // Last-ditch error recovery. Somewhat useful because the JSON parser will recover from some parse errors by + // synthesizing a top-level array literal expression. There's a reasonable chance the first element of that + // array is a well-formed configuration object, made into an array element by stray characters. + if (isArrayLiteralExpression(rootExpression)) { + const firstObject = find(rootExpression.elements, isObjectLiteralExpression); + if (firstObject) { + return convertToObjectWorker(sourceFile, firstObject, errors, /*returnValue*/ true, knownRootOptions, optionsIterator); + } + } + return {}; + } + return convertToObjectWorker(sourceFile, rootExpression, errors, /*returnValue*/ true, knownRootOptions, optionsIterator); +} + +/** + * Convert the json syntax tree into the json value + */ +export function convertToObject(sourceFile: JsonSourceFile, errors: Push): any { + return convertToObjectWorker(sourceFile, sourceFile.statements[0]?.expression, errors, /*returnValue*/ true, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); +} - if (element.questionToken) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.questionToken, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); +/** + * Convert the json syntax tree into the json value and report errors + * This returns the json value (apart from checking errors) only if returnValue provided is true. + * Otherwise it just checks the errors and returns undefined + * + * @internal + */ +export function convertToObjectWorker( + sourceFile: JsonSourceFile, + rootExpression: Expression | undefined, + errors: Push, + returnValue: boolean, + knownRootOptions: CommandLineOption | undefined, + jsonConversionNotifier: JsonConversionNotifier | undefined): any { + if (!rootExpression) { + return returnValue ? {} : undefined; + } + + return convertPropertyValueToJson(rootExpression, knownRootOptions); + + function isRootOptionMap(knownOptions: ESMap | undefined) { + return knownRootOptions && (knownRootOptions as TsConfigOnlyOption).elementOptions === knownOptions; + } + + function convertObjectLiteralExpressionToJson( + node: ObjectLiteralExpression, + knownOptions: ESMap | undefined, + extraKeyDiagnostics: DidYouMeanOptionsDiagnostics | undefined, + parentOption: string | undefined + ): any { + const result: any = returnValue ? {} : undefined; + for (const element of node.properties) { + if (element.kind !== SyntaxKind.PropertyAssignment) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element, Diagnostics.Property_assignment_expected)); + continue; + } + + if (element.questionToken) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.questionToken, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); + } + if (!isDoubleQuotedString(element.name)) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, Diagnostics.String_literal_with_double_quotes_expected)); + } + + const textOfKey = isComputedNonLiteralName(element.name) ? undefined : getTextOfPropertyName(element.name); + const keyText = textOfKey && unescapeLeadingUnderscores(textOfKey); + const option = keyText && knownOptions ? knownOptions.get(keyText) : undefined; + if (keyText && extraKeyDiagnostics && !option) { + if (knownOptions) { + errors.push(createUnknownOptionError( + keyText, + extraKeyDiagnostics, + (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, element.name, message, arg0, arg1) + )); } - if (!isDoubleQuotedString(element.name)) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, Diagnostics.String_literal_with_double_quotes_expected)); + else { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownOptionDiagnostic, keyText)); } - - const textOfKey = isComputedNonLiteralName(element.name) ? undefined : getTextOfPropertyName(element.name); - const keyText = textOfKey && unescapeLeadingUnderscores(textOfKey); - const option = keyText && knownOptions ? knownOptions.get(keyText) : undefined; - if (keyText && extraKeyDiagnostics && !option) { - if (knownOptions) { - errors.push(createUnknownOptionError( - keyText, - extraKeyDiagnostics, - (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, element.name, message, arg0, arg1) - )); - } - else { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownOptionDiagnostic, keyText)); - } + } + const value = convertPropertyValueToJson(element.initializer, option); + if (typeof keyText !== "undefined") { + if (returnValue) { + result[keyText] = value; } - const value = convertPropertyValueToJson(element.initializer, option); - if (typeof keyText !== "undefined") { - if (returnValue) { - result[keyText] = value; + // Notify key value set, if user asked for it + if (jsonConversionNotifier && + // Current callbacks are only on known parent option or if we are setting values in the root + (parentOption || isRootOptionMap(knownOptions))) { + const isValidOptionValue = isCompilerOptionsValue(option, value); + if (parentOption) { + if (isValidOptionValue) { + // Notify option set in the parent if its a valid option value + jsonConversionNotifier.onSetValidOptionKeyValueInParent(parentOption, option!, value); + } } - // Notify key value set, if user asked for it - if (jsonConversionNotifier && - // Current callbacks are only on known parent option or if we are setting values in the root - (parentOption || isRootOptionMap(knownOptions))) { - const isValidOptionValue = isCompilerOptionsValue(option, value); - if (parentOption) { - if (isValidOptionValue) { - // Notify option set in the parent if its a valid option value - jsonConversionNotifier.onSetValidOptionKeyValueInParent(parentOption, option!, value); - } + else if (isRootOptionMap(knownOptions)) { + if (isValidOptionValue) { + // Notify about the valid root key value being set + jsonConversionNotifier.onSetValidOptionKeyValueInRoot(keyText, element.name, value, element.initializer); } - else if (isRootOptionMap(knownOptions)) { - if (isValidOptionValue) { - // Notify about the valid root key value being set - jsonConversionNotifier.onSetValidOptionKeyValueInRoot(keyText, element.name, value, element.initializer); - } - else if (!option) { - // Notify about the unknown root key value being set - jsonConversionNotifier.onSetUnknownOptionKeyValueInRoot(keyText, element.name, value, element.initializer); - } + else if (!option) { + // Notify about the unknown root key value being set + jsonConversionNotifier.onSetUnknownOptionKeyValueInRoot(keyText, element.name, value, element.initializer); } } } } - return result; } + return result; + } - function convertArrayLiteralExpressionToJson( - elements: NodeArray, - elementOption: CommandLineOption | undefined - ) { - if (!returnValue) { - elements.forEach(element => convertPropertyValueToJson(element, elementOption)); - return undefined; - } - - // Filter out invalid values - return filter(elements.map(element => convertPropertyValueToJson(element, elementOption)), v => v !== undefined); + function convertArrayLiteralExpressionToJson( + elements: NodeArray, + elementOption: CommandLineOption | undefined + ) { + if (!returnValue) { + elements.forEach(element => convertPropertyValueToJson(element, elementOption)); + return undefined; } - function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption | undefined): any { - let invalidReported: boolean | undefined; - switch (valueExpression.kind) { - case SyntaxKind.TrueKeyword: - reportInvalidOptionValue(option && option.type !== "boolean"); - return validateValue(/*value*/ true); - - case SyntaxKind.FalseKeyword: - reportInvalidOptionValue(option && option.type !== "boolean"); - return validateValue(/*value*/ false); + // Filter out invalid values + return filter(elements.map(element => convertPropertyValueToJson(element, elementOption)), v => v !== undefined); + } - case SyntaxKind.NullKeyword: - reportInvalidOptionValue(option && option.name === "extends"); // "extends" is the only option we don't allow null/undefined for - return validateValue(/*value*/ null); // eslint-disable-line no-null/no-null + function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption | undefined): any { + let invalidReported: boolean | undefined; + switch (valueExpression.kind) { + case SyntaxKind.TrueKeyword: + reportInvalidOptionValue(option && option.type !== "boolean"); + return validateValue(/*value*/ true); - case SyntaxKind.StringLiteral: - if (!isDoubleQuotedString(valueExpression)) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected)); - } - reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string")); - const text = (valueExpression as StringLiteral).text; - if (option && !isString(option.type)) { - const customOption = option as CommandLineOptionOfCustomType; - // Validate custom option type - if (!customOption.type.has(text.toLowerCase())) { - errors.push( - createDiagnosticForInvalidCustomType( - customOption, - (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, message, arg0, arg1) - ) - ); - invalidReported = true; - } - } - return validateValue(text); + case SyntaxKind.FalseKeyword: + reportInvalidOptionValue(option && option.type !== "boolean"); + return validateValue(/*value*/ false); - case SyntaxKind.NumericLiteral: - reportInvalidOptionValue(option && option.type !== "number"); - return validateValue(Number((valueExpression as NumericLiteral).text)); + case SyntaxKind.NullKeyword: + reportInvalidOptionValue(option && option.name === "extends"); // "extends" is the only option we don't allow null/undefined for + return validateValue(/*value*/ null); // eslint-disable-line no-null/no-null - case SyntaxKind.PrefixUnaryExpression: - if ((valueExpression as PrefixUnaryExpression).operator !== SyntaxKind.MinusToken || (valueExpression as PrefixUnaryExpression).operand.kind !== SyntaxKind.NumericLiteral) { - break; // not valid JSON syntax - } - reportInvalidOptionValue(option && option.type !== "number"); - return validateValue(-Number(((valueExpression as PrefixUnaryExpression).operand as NumericLiteral).text)); - - case SyntaxKind.ObjectLiteralExpression: - reportInvalidOptionValue(option && option.type !== "object"); - const objectLiteralExpression = valueExpression as ObjectLiteralExpression; - - // Currently having element option declaration in the tsconfig with type "object" - // determines if it needs onSetValidOptionKeyValueInParent callback or not - // At moment there are only "compilerOptions", "typeAcquisition" and "typingOptions" - // that satifies it and need it to modify options set in them (for normalizing file paths) - // vs what we set in the json - // If need arises, we can modify this interface and callbacks as needed - if (option) { - const { elementOptions, extraKeyDiagnostics, name: optionName } = option as TsConfigOnlyOption; - return validateValue(convertObjectLiteralExpressionToJson(objectLiteralExpression, - elementOptions, extraKeyDiagnostics, optionName)); - } - else { - return validateValue(convertObjectLiteralExpressionToJson( - objectLiteralExpression, /* knownOptions*/ undefined, - /*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined)); + case SyntaxKind.StringLiteral: + if (!isDoubleQuotedString(valueExpression)) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected)); + } + reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string")); + const text = (valueExpression as StringLiteral).text; + if (option && !isString(option.type)) { + const customOption = option as CommandLineOptionOfCustomType; + // Validate custom option type + if (!customOption.type.has(text.toLowerCase())) { + errors.push( + createDiagnosticForInvalidCustomType( + customOption, + (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, message, arg0, arg1) + ) + ); + invalidReported = true; } + } + return validateValue(text); - case SyntaxKind.ArrayLiteralExpression: - reportInvalidOptionValue(option && option.type !== "list"); - return validateValue(convertArrayLiteralExpressionToJson( - (valueExpression as ArrayLiteralExpression).elements, - option && (option as CommandLineOptionOfListType).element)); - } + case SyntaxKind.NumericLiteral: + reportInvalidOptionValue(option && option.type !== "number"); + return validateValue(Number((valueExpression as NumericLiteral).text)); - // Not in expected format - if (option) { - reportInvalidOptionValue(/*isError*/ true); - } - else { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Property_value_can_only_be_string_literal_numeric_literal_true_false_null_object_literal_or_array_literal)); - } + case SyntaxKind.PrefixUnaryExpression: + if ((valueExpression as PrefixUnaryExpression).operator !== SyntaxKind.MinusToken || (valueExpression as PrefixUnaryExpression).operand.kind !== SyntaxKind.NumericLiteral) { + break; // not valid JSON syntax + } + reportInvalidOptionValue(option && option.type !== "number"); + return validateValue(-Number(((valueExpression as PrefixUnaryExpression).operand as NumericLiteral).text)); + + case SyntaxKind.ObjectLiteralExpression: + reportInvalidOptionValue(option && option.type !== "object"); + const objectLiteralExpression = valueExpression as ObjectLiteralExpression; + + // Currently having element option declaration in the tsconfig with type "object" + // determines if it needs onSetValidOptionKeyValueInParent callback or not + // At moment there are only "compilerOptions", "typeAcquisition" and "typingOptions" + // that satifies it and need it to modify options set in them (for normalizing file paths) + // vs what we set in the json + // If need arises, we can modify this interface and callbacks as needed + if (option) { + const { elementOptions, extraKeyDiagnostics, name: optionName } = option as TsConfigOnlyOption; + return validateValue(convertObjectLiteralExpressionToJson(objectLiteralExpression, + elementOptions, extraKeyDiagnostics, optionName)); + } + else { + return validateValue(convertObjectLiteralExpressionToJson( + objectLiteralExpression, /* knownOptions*/ undefined, + /*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined)); + } - return undefined; + case SyntaxKind.ArrayLiteralExpression: + reportInvalidOptionValue(option && option.type !== "list"); + return validateValue(convertArrayLiteralExpressionToJson( + (valueExpression as ArrayLiteralExpression).elements, + option && (option as CommandLineOptionOfListType).element)); + } - function validateValue(value: CompilerOptionsValue) { - if (!invalidReported) { - const diagnostic = option?.extraValidation?.(value); - if (diagnostic) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, ...diagnostic)); - return undefined; - } - } - return value; - } + // Not in expected format + if (option) { + reportInvalidOptionValue(/*isError*/ true); + } + else { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Property_value_can_only_be_string_literal_numeric_literal_true_false_null_object_literal_or_array_literal)); + } + + return undefined; - function reportInvalidOptionValue(isError: boolean | undefined) { - if (isError) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option!.name, getCompilerOptionValueTypeString(option!))); - invalidReported = true; + function validateValue(value: CompilerOptionsValue) { + if (!invalidReported) { + const diagnostic = option?.extraValidation?.(value); + if (diagnostic) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, ...diagnostic)); + return undefined; } } + return value; } - function isDoubleQuotedString(node: Node): boolean { - return isStringLiteral(node) && isStringDoubleQuoted(node, sourceFile); + function reportInvalidOptionValue(isError: boolean | undefined) { + if (isError) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option!.name, getCompilerOptionValueTypeString(option!))); + invalidReported = true; + } } } - function getCompilerOptionValueTypeString(option: CommandLineOption) { - return option.type === "list" ? - "Array" : - isString(option.type) ? option.type : "string"; + function isDoubleQuotedString(node: Node): boolean { + return isStringLiteral(node) && isStringDoubleQuoted(node, sourceFile); } +} - function isCompilerOptionsValue(option: CommandLineOption | undefined, value: any): value is CompilerOptionsValue { - if (option) { - if (isNullOrUndefined(value)) return true; // All options are undefinable/nullable - if (option.type === "list") { - return isArray(value); - } - const expectedType = isString(option.type) ? option.type : "string"; - return typeof value === expectedType; +function getCompilerOptionValueTypeString(option: CommandLineOption) { + return option.type === "list" ? + "Array" : + isString(option.type) ? option.type : "string"; +} + +function isCompilerOptionsValue(option: CommandLineOption | undefined, value: any): value is CompilerOptionsValue { + if (option) { + if (isNullOrUndefined(value)) return true; // All options are undefinable/nullable + if (option.type === "list") { + return isArray(value); } - return false; + const expectedType = isString(option.type) ? option.type : "string"; + return typeof value === expectedType; } + return false; +} - /** @internal */ - export interface TSConfig { - compilerOptions: CompilerOptions; - compileOnSave: boolean | undefined; - exclude?: readonly string[]; - files: readonly string[] | undefined; - include?: readonly string[]; - references: readonly ProjectReference[] | undefined; - } +/** @internal */ +export interface TSConfig { + compilerOptions: CompilerOptions; + compileOnSave: boolean | undefined; + exclude?: readonly string[]; + files: readonly string[] | undefined; + include?: readonly string[]; + references: readonly ProjectReference[] | undefined; +} - /** @internal */ - export interface ConvertToTSConfigHost { - getCurrentDirectory(): string; - useCaseSensitiveFileNames: boolean; - } +/** @internal */ +export interface ConvertToTSConfigHost { + getCurrentDirectory(): string; + useCaseSensitiveFileNames: boolean; +} - /** - * Generate an uncommented, complete tsconfig for use with "--showConfig" - * @param configParseResult options to be generated into tsconfig.json - * @param configFileName name of the parsed config file - output paths will be generated relative to this - * @param host provides current directory and case sensitivity services - */ - /** @internal */ - export function convertToTSConfig(configParseResult: ParsedCommandLine, configFileName: string, host: ConvertToTSConfigHost): TSConfig { - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - const files = map( - filter( - configParseResult.fileNames, - !configParseResult.options.configFile?.configFileSpecs?.validatedIncludeSpecs ? returnTrue : matchesSpecs( - configFileName, - configParseResult.options.configFile.configFileSpecs.validatedIncludeSpecs, - configParseResult.options.configFile.configFileSpecs.validatedExcludeSpecs, - host, - ) - ), - f => getRelativePathFromFile(getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), getNormalizedAbsolutePath(f, host.getCurrentDirectory()), getCanonicalFileName) - ); - const optionMap = serializeCompilerOptions(configParseResult.options, { configFilePath: getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames }); - const watchOptionMap = configParseResult.watchOptions && serializeWatchOptions(configParseResult.watchOptions); - const config = { - compilerOptions: { - ...optionMapToObject(optionMap), - showConfig: undefined, - configFile: undefined, - configFilePath: undefined, - help: undefined, - init: undefined, - listFiles: undefined, - listEmittedFiles: undefined, - project: undefined, - build: undefined, - version: undefined, - }, - watchOptions: watchOptionMap && optionMapToObject(watchOptionMap), - references: map(configParseResult.projectReferences, r => ({ ...r, path: r.originalPath ? r.originalPath : "", originalPath: undefined })), - files: length(files) ? files : undefined, - ...(configParseResult.options.configFile?.configFileSpecs ? { - include: filterSameAsDefaultInclude(configParseResult.options.configFile.configFileSpecs.validatedIncludeSpecs), - exclude: configParseResult.options.configFile.configFileSpecs.validatedExcludeSpecs - } : {}), - compileOnSave: !!configParseResult.compileOnSave ? true : undefined - }; - return config; - } +/** + * Generate an uncommented, complete tsconfig for use with "--showConfig" + * @param configParseResult options to be generated into tsconfig.json + * @param configFileName name of the parsed config file - output paths will be generated relative to this + * @param host provides current directory and case sensitivity services + * + * @internal + */ +export function convertToTSConfig(configParseResult: ParsedCommandLine, configFileName: string, host: ConvertToTSConfigHost): TSConfig { + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + const files = map( + filter( + configParseResult.fileNames, + !configParseResult.options.configFile?.configFileSpecs?.validatedIncludeSpecs ? returnTrue : matchesSpecs( + configFileName, + configParseResult.options.configFile.configFileSpecs.validatedIncludeSpecs, + configParseResult.options.configFile.configFileSpecs.validatedExcludeSpecs, + host, + ) + ), + f => getRelativePathFromFile(getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), getNormalizedAbsolutePath(f, host.getCurrentDirectory()), getCanonicalFileName) + ); + const optionMap = serializeCompilerOptions(configParseResult.options, { configFilePath: getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames }); + const watchOptionMap = configParseResult.watchOptions && serializeWatchOptions(configParseResult.watchOptions); + const config = { + compilerOptions: { + ...optionMapToObject(optionMap), + showConfig: undefined, + configFile: undefined, + configFilePath: undefined, + help: undefined, + init: undefined, + listFiles: undefined, + listEmittedFiles: undefined, + project: undefined, + build: undefined, + version: undefined, + }, + watchOptions: watchOptionMap && optionMapToObject(watchOptionMap), + references: map(configParseResult.projectReferences, r => ({ ...r, path: r.originalPath ? r.originalPath : "", originalPath: undefined })), + files: length(files) ? files : undefined, + ...(configParseResult.options.configFile?.configFileSpecs ? { + include: filterSameAsDefaultInclude(configParseResult.options.configFile.configFileSpecs.validatedIncludeSpecs), + exclude: configParseResult.options.configFile.configFileSpecs.validatedExcludeSpecs + } : {}), + compileOnSave: !!configParseResult.compileOnSave ? true : undefined + }; + return config; +} - /*@internal*/ - export function optionMapToObject(optionMap: ESMap): object { - return { - ...arrayFrom(optionMap.entries()).reduce((prev, cur) => ({ ...prev, [cur[0]]: cur[1] }), {}), - }; - } +/** @internal */ +export function optionMapToObject(optionMap: ESMap): object { + return { + ...arrayFrom(optionMap.entries()).reduce((prev, cur) => ({ ...prev, [cur[0]]: cur[1] }), {}), + }; +} - function filterSameAsDefaultInclude(specs: readonly string[] | undefined) { - if (!length(specs)) return undefined; - if (length(specs) !== 1) return specs; - if (specs![0] === defaultIncludeSpec) return undefined; - return specs; - } +function filterSameAsDefaultInclude(specs: readonly string[] | undefined) { + if (!length(specs)) return undefined; + if (length(specs) !== 1) return specs; + if (specs![0] === defaultIncludeSpec) return undefined; + return specs; +} - function matchesSpecs(path: string, includeSpecs: readonly string[] | undefined, excludeSpecs: readonly string[] | undefined, host: ConvertToTSConfigHost): (path: string) => boolean { - if (!includeSpecs) return returnTrue; - const patterns = getFileMatcherPatterns(path, excludeSpecs, includeSpecs, host.useCaseSensitiveFileNames, host.getCurrentDirectory()); - const excludeRe = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, host.useCaseSensitiveFileNames); - const includeRe = patterns.includeFilePattern && getRegexFromPattern(patterns.includeFilePattern, host.useCaseSensitiveFileNames); - if (includeRe) { - if (excludeRe) { - return path => !(includeRe.test(path) && !excludeRe.test(path)); - } - return path => !includeRe.test(path); - } +function matchesSpecs(path: string, includeSpecs: readonly string[] | undefined, excludeSpecs: readonly string[] | undefined, host: ConvertToTSConfigHost): (path: string) => boolean { + if (!includeSpecs) return returnTrue; + const patterns = getFileMatcherPatterns(path, excludeSpecs, includeSpecs, host.useCaseSensitiveFileNames, host.getCurrentDirectory()); + const excludeRe = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, host.useCaseSensitiveFileNames); + const includeRe = patterns.includeFilePattern && getRegexFromPattern(patterns.includeFilePattern, host.useCaseSensitiveFileNames); + if (includeRe) { if (excludeRe) { - return path => excludeRe.test(path); + return path => !(includeRe.test(path) && !excludeRe.test(path)); } - return returnTrue; + return path => !includeRe.test(path); } - - function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): ESMap | undefined { - if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean" || optionDefinition.type === "object") { - // this is of a type CommandLineOptionOfPrimitiveType - return undefined; - } - else if (optionDefinition.type === "list") { - return getCustomTypeMapOfCommandLineOption(optionDefinition.element); - } - else { - return optionDefinition.type; - } + if (excludeRe) { + return path => excludeRe.test(path); } + return returnTrue; +} - /* @internal */ - export function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: ESMap): string | undefined { - // There is a typeMap associated with this command-line option so use it to map value back to its name - return forEachEntry(customTypeMap, (mapValue, key) => { - if (mapValue === value) { - return key; - } - }); +function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): ESMap | undefined { + if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean" || optionDefinition.type === "object") { + // this is of a type CommandLineOptionOfPrimitiveType + return undefined; } - - /* @internal */ - export function serializeCompilerOptions( - options: CompilerOptions, - pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean } - ): ESMap { - return serializeOptionBaseObject(options, getOptionsNameMap(), pathOptions); + else if (optionDefinition.type === "list") { + return getCustomTypeMapOfCommandLineOption(optionDefinition.element); } - - function serializeWatchOptions(options: WatchOptions) { - return serializeOptionBaseObject(options, getWatchOptionsNameMap()); + else { + return optionDefinition.type; } +} + +/** @internal */ +export function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: ESMap): string | undefined { + // There is a typeMap associated with this command-line option so use it to map value back to its name + return forEachEntry(customTypeMap, (mapValue, key) => { + if (mapValue === value) { + return key; + } + }); +} + +/** @internal */ +export function serializeCompilerOptions( + options: CompilerOptions, + pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean } +): ESMap { + return serializeOptionBaseObject(options, getOptionsNameMap(), pathOptions); +} - function serializeOptionBaseObject( - options: OptionsBase, - { optionsNameMap }: OptionsNameMap, - pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean } - ): ESMap { - const result = new Map(); - const getCanonicalFileName = pathOptions && createGetCanonicalFileName(pathOptions.useCaseSensitiveFileNames); +function serializeWatchOptions(options: WatchOptions) { + return serializeOptionBaseObject(options, getWatchOptionsNameMap()); +} - for (const name in options) { - if (hasProperty(options, name)) { - // tsconfig only options cannot be specified via command line, - // so we can assume that only types that can appear here string | number | boolean - if (optionsNameMap.has(name) && (optionsNameMap.get(name)!.category === Diagnostics.Command_line_Options || optionsNameMap.get(name)!.category === Diagnostics.Output_Formatting)) { - continue; +function serializeOptionBaseObject( + options: OptionsBase, + { optionsNameMap }: OptionsNameMap, + pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean } +): ESMap { + const result = new Map(); + const getCanonicalFileName = pathOptions && createGetCanonicalFileName(pathOptions.useCaseSensitiveFileNames); + + for (const name in options) { + if (hasProperty(options, name)) { + // tsconfig only options cannot be specified via command line, + // so we can assume that only types that can appear here string | number | boolean + if (optionsNameMap.has(name) && (optionsNameMap.get(name)!.category === Diagnostics.Command_line_Options || optionsNameMap.get(name)!.category === Diagnostics.Output_Formatting)) { + continue; + } + const value = options[name] as CompilerOptionsValue; + const optionDefinition = optionsNameMap.get(name.toLowerCase()); + if (optionDefinition) { + const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition); + if (!customTypeMap) { + // There is no map associated with this compiler option then use the value as-is + // This is the case if the value is expect to be string, number, boolean or list of string + if (pathOptions && optionDefinition.isFilePath) { + result.set(name, getRelativePathFromFile(pathOptions.configFilePath, getNormalizedAbsolutePath(value as string, getDirectoryPath(pathOptions.configFilePath)), getCanonicalFileName!)); + } + else { + result.set(name, value); + } } - const value = options[name] as CompilerOptionsValue; - const optionDefinition = optionsNameMap.get(name.toLowerCase()); - if (optionDefinition) { - const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition); - if (!customTypeMap) { - // There is no map associated with this compiler option then use the value as-is - // This is the case if the value is expect to be string, number, boolean or list of string - if (pathOptions && optionDefinition.isFilePath) { - result.set(name, getRelativePathFromFile(pathOptions.configFilePath, getNormalizedAbsolutePath(value as string, getDirectoryPath(pathOptions.configFilePath)), getCanonicalFileName!)); - } - else { - result.set(name, value); - } + else { + if (optionDefinition.type === "list") { + result.set(name, (value as readonly (string | number)[]).map(element => getNameOfCompilerOptionValue(element, customTypeMap)!)); // TODO: GH#18217 } else { - if (optionDefinition.type === "list") { - result.set(name, (value as readonly (string | number)[]).map(element => getNameOfCompilerOptionValue(element, customTypeMap)!)); // TODO: GH#18217 - } - else { - // There is a typeMap associated with this command-line option so use it to map value back to its name - result.set(name, getNameOfCompilerOptionValue(value, customTypeMap)); - } + // There is a typeMap associated with this command-line option so use it to map value back to its name + result.set(name, getNameOfCompilerOptionValue(value, customTypeMap)); } } } } - return result; } + return result; +} - /** - * Generate a list of the compiler options whose value is not the default. - * @param options compilerOptions to be evaluated. - /** @internal */ - export function getCompilerOptionsDiffValue(options: CompilerOptions, newLine: string): string { - const compilerOptionsMap = getSerializedCompilerOption(options); - return getOverwrittenDefaultOptions(); - - function makePadding(paddingLength: number): string { - return Array(paddingLength + 1).join(" "); - } - - function getOverwrittenDefaultOptions() { - const result: string[] = []; - const tab = makePadding(2); - commandOptionsWithoutBuild.forEach(cmd => { - if (!compilerOptionsMap.has(cmd.name)) { - return; - } +/** + * Generate a list of the compiler options whose value is not the default. + * @param options compilerOptions to be evaluated. +/** @internal */ +export function getCompilerOptionsDiffValue(options: CompilerOptions, newLine: string): string { + const compilerOptionsMap = getSerializedCompilerOption(options); + return getOverwrittenDefaultOptions(); - const newValue = compilerOptionsMap.get(cmd.name); - const defaultValue = getDefaultValueForOption(cmd); - if (newValue !== defaultValue) { - result.push(`${tab}${cmd.name}: ${newValue}`); - } - else if (hasProperty(defaultInitCompilerOptions, cmd.name)) { - result.push(`${tab}${cmd.name}: ${defaultValue}`); - } - }); - return result.join(newLine) + newLine; - } + function makePadding(paddingLength: number): string { + return Array(paddingLength + 1).join(" "); } - /** - * Get the compiler options to be written into the tsconfig.json. - * @param options commandlineOptions to be included in the compileOptions. - */ - function getSerializedCompilerOption(options: CompilerOptions): ESMap { - const compilerOptions = extend(options, defaultInitCompilerOptions); - return serializeCompilerOptions(compilerOptions); - } - /** - * Generate tsconfig configuration when running command line "--init" - * @param options commandlineOptions to be generated into tsconfig.json - * @param fileNames array of filenames to be generated into tsconfig.json - */ - /* @internal */ - export function generateTSConfig(options: CompilerOptions, fileNames: readonly string[], newLine: string): string { - const compilerOptionsMap = getSerializedCompilerOption(options); - return writeConfigurations(); - - function makePadding(paddingLength: number): string { - return Array(paddingLength + 1).join(" "); - } - - function isAllowedOptionForOutput({ category, name, isCommandLineOnly }: CommandLineOption): boolean { - // Skip options which do not have a category or have categories which are more niche - const categoriesToSkip = [Diagnostics.Command_line_Options, Diagnostics.Editor_Support, Diagnostics.Compiler_Diagnostics, Diagnostics.Backwards_Compatibility, Diagnostics.Watch_and_Build_Modes, Diagnostics.Output_Formatting]; - return !isCommandLineOnly && category !== undefined && (!categoriesToSkip.includes(category) || compilerOptionsMap.has(name)); - } - - function writeConfigurations() { - // Filter applicable options to place in the file - const categorizedOptions = new Map(); - // Set allowed categories in order - categorizedOptions.set(Diagnostics.Projects, []); - categorizedOptions.set(Diagnostics.Language_and_Environment, []); - categorizedOptions.set(Diagnostics.Modules, []); - categorizedOptions.set(Diagnostics.JavaScript_Support, []); - categorizedOptions.set(Diagnostics.Emit, []); - categorizedOptions.set(Diagnostics.Interop_Constraints, []); - categorizedOptions.set(Diagnostics.Type_Checking, []); - categorizedOptions.set(Diagnostics.Completeness, []); - for (const option of optionDeclarations) { - if (isAllowedOptionForOutput(option)) { - let listForCategory = categorizedOptions.get(option.category!); - if (!listForCategory) categorizedOptions.set(option.category!, listForCategory = []); - listForCategory.push(option); - } + function getOverwrittenDefaultOptions() { + const result: string[] = []; + const tab = makePadding(2); + commandOptionsWithoutBuild.forEach(cmd => { + if (!compilerOptionsMap.has(cmd.name)) { + return; } - // Serialize all options and their descriptions - let marginLength = 0; - let seenKnownKeys = 0; - const entries: { value: string, description?: string }[] = []; - categorizedOptions.forEach((options, category) => { - if (entries.length !== 0) { - entries.push({ value: "" }); - } - entries.push({ value: `/* ${getLocaleSpecificMessage(category)} */` }); - for (const option of options) { - let optionName; - if (compilerOptionsMap.has(option.name)) { - optionName = `"${option.name}": ${JSON.stringify(compilerOptionsMap.get(option.name))}${(seenKnownKeys += 1) === compilerOptionsMap.size ? "" : ","}`; - } - else { - optionName = `// "${option.name}": ${JSON.stringify(getDefaultValueForOption(option))},`; - } - entries.push({ - value: optionName, - description: `/* ${option.description && getLocaleSpecificMessage(option.description) || option.name} */` - }); - marginLength = Math.max(optionName.length, marginLength); - } - }); - - // Write the output - const tab = makePadding(2); - const result: string[] = []; - result.push(`{`); - result.push(`${tab}"compilerOptions": {`); - result.push(`${tab}${tab}/* ${getLocaleSpecificMessage(Diagnostics.Visit_https_Colon_Slash_Slashaka_ms_Slashtsconfig_to_read_more_about_this_file)} */`); - result.push(""); - // Print out each row, aligning all the descriptions on the same column. - for (const entry of entries) { - const { value, description = "" } = entry; - result.push(value && `${tab}${tab}${value}${description && (makePadding(marginLength - value.length + 2) + description)}`); - } - if (fileNames.length) { - result.push(`${tab}},`); - result.push(`${tab}"files": [`); - for (let i = 0; i < fileNames.length; i++) { - result.push(`${tab}${tab}${JSON.stringify(fileNames[i])}${i === fileNames.length - 1 ? "" : ","}`); - } - result.push(`${tab}]`); - } - else { - result.push(`${tab}}`); + const newValue = compilerOptionsMap.get(cmd.name); + const defaultValue = getDefaultValueForOption(cmd); + if (newValue !== defaultValue) { + result.push(`${tab}${cmd.name}: ${newValue}`); } - result.push(`}`); - - return result.join(newLine) + newLine; - } - } - - /* @internal */ - export function convertToOptionsWithAbsolutePaths(options: CompilerOptions, toAbsolutePath: (path: string) => string) { - const result: CompilerOptions = {}; - const optionsNameMap = getOptionsNameMap().optionsNameMap; - - for (const name in options) { - if (hasProperty(options, name)) { - result[name] = convertToOptionValueWithAbsolutePaths( - optionsNameMap.get(name.toLowerCase()), - options[name] as CompilerOptionsValue, - toAbsolutePath - ); + else if (hasProperty(defaultInitCompilerOptions, cmd.name)) { + result.push(`${tab}${cmd.name}: ${defaultValue}`); } - } - if (result.configFilePath) { - result.configFilePath = toAbsolutePath(result.configFilePath); - } - return result; + }); + return result.join(newLine) + newLine; } +} - function convertToOptionValueWithAbsolutePaths(option: CommandLineOption | undefined, value: CompilerOptionsValue, toAbsolutePath: (path: string) => string) { - if (option && !isNullOrUndefined(value)) { - if (option.type === "list") { - const values = value as readonly (string | number)[]; - if (option.element.isFilePath && values.length) { - return values.map(toAbsolutePath); +/** + * Get the compiler options to be written into the tsconfig.json. + * @param options commandlineOptions to be included in the compileOptions. + */ +function getSerializedCompilerOption(options: CompilerOptions): ESMap { + const compilerOptions = extend(options, defaultInitCompilerOptions); + return serializeCompilerOptions(compilerOptions); +} +/** + * Generate tsconfig configuration when running command line "--init" + * @param options commandlineOptions to be generated into tsconfig.json + * @param fileNames array of filenames to be generated into tsconfig.json + * + * @internal + */ +export function generateTSConfig(options: CompilerOptions, fileNames: readonly string[], newLine: string): string { + const compilerOptionsMap = getSerializedCompilerOption(options); + return writeConfigurations(); + + function makePadding(paddingLength: number): string { + return Array(paddingLength + 1).join(" "); + } + + function isAllowedOptionForOutput({ category, name, isCommandLineOnly }: CommandLineOption): boolean { + // Skip options which do not have a category or have categories which are more niche + const categoriesToSkip = [Diagnostics.Command_line_Options, Diagnostics.Editor_Support, Diagnostics.Compiler_Diagnostics, Diagnostics.Backwards_Compatibility, Diagnostics.Watch_and_Build_Modes, Diagnostics.Output_Formatting]; + return !isCommandLineOnly && category !== undefined && (!categoriesToSkip.includes(category) || compilerOptionsMap.has(name)); + } + + function writeConfigurations() { + // Filter applicable options to place in the file + const categorizedOptions = new Map(); + // Set allowed categories in order + categorizedOptions.set(Diagnostics.Projects, []); + categorizedOptions.set(Diagnostics.Language_and_Environment, []); + categorizedOptions.set(Diagnostics.Modules, []); + categorizedOptions.set(Diagnostics.JavaScript_Support, []); + categorizedOptions.set(Diagnostics.Emit, []); + categorizedOptions.set(Diagnostics.Interop_Constraints, []); + categorizedOptions.set(Diagnostics.Type_Checking, []); + categorizedOptions.set(Diagnostics.Completeness, []); + for (const option of optionDeclarations) { + if (isAllowedOptionForOutput(option)) { + let listForCategory = categorizedOptions.get(option.category!); + if (!listForCategory) categorizedOptions.set(option.category!, listForCategory = []); + listForCategory.push(option); + } + } + + // Serialize all options and their descriptions + let marginLength = 0; + let seenKnownKeys = 0; + const entries: { value: string, description?: string }[] = []; + categorizedOptions.forEach((options, category) => { + if (entries.length !== 0) { + entries.push({ value: "" }); + } + entries.push({ value: `/* ${getLocaleSpecificMessage(category)} */` }); + for (const option of options) { + let optionName; + if (compilerOptionsMap.has(option.name)) { + optionName = `"${option.name}": ${JSON.stringify(compilerOptionsMap.get(option.name))}${(seenKnownKeys += 1) === compilerOptionsMap.size ? "" : ","}`; } + else { + optionName = `// "${option.name}": ${JSON.stringify(getDefaultValueForOption(option))},`; + } + entries.push({ + value: optionName, + description: `/* ${option.description && getLocaleSpecificMessage(option.description) || option.name} */` + }); + marginLength = Math.max(optionName.length, marginLength); } - else if (option.isFilePath) { - return toAbsolutePath(value as string); - } + }); + + // Write the output + const tab = makePadding(2); + const result: string[] = []; + result.push(`{`); + result.push(`${tab}"compilerOptions": {`); + result.push(`${tab}${tab}/* ${getLocaleSpecificMessage(Diagnostics.Visit_https_Colon_Slash_Slashaka_ms_Slashtsconfig_to_read_more_about_this_file)} */`); + result.push(""); + // Print out each row, aligning all the descriptions on the same column. + for (const entry of entries) { + const { value, description = "" } = entry; + result.push(value && `${tab}${tab}${value}${description && (makePadding(marginLength - value.length + 2) + description)}`); + } + if (fileNames.length) { + result.push(`${tab}},`); + result.push(`${tab}"files": [`); + for (let i = 0; i < fileNames.length; i++) { + result.push(`${tab}${tab}${JSON.stringify(fileNames[i])}${i === fileNames.length - 1 ? "" : ","}`); + } + result.push(`${tab}]`); } - return value; - } + else { + result.push(`${tab}}`); + } + result.push(`}`); - /** - * Parse the contents of a config file (tsconfig.json). - * @param json The contents of the config file to parse - * @param host Instance of ParseConfigHost used to enumerate files in folder. - * @param basePath A root directory to resolve relative path entries in the config - * file to. e.g. outDir - */ - export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { - return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); + return result.join(newLine) + newLine; } +} - /** - * Parse the contents of a config file (tsconfig.json). - * @param jsonNode The contents of the config file to parse - * @param host Instance of ParseConfigHost used to enumerate files in folder. - * @param basePath A root directory to resolve relative path entries in the config - * file to. e.g. outDir - */ - export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { - tracing?.push(tracing.Phase.Parse, "parseJsonSourceFileConfigFileContent", { path: sourceFile.fileName }); - const result = parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); - tracing?.pop(); - return result; - } +/** @internal */ +export function convertToOptionsWithAbsolutePaths(options: CompilerOptions, toAbsolutePath: (path: string) => string) { + const result: CompilerOptions = {}; + const optionsNameMap = getOptionsNameMap().optionsNameMap; - /*@internal*/ - export function setConfigFileInOptions(options: CompilerOptions, configFile: TsConfigSourceFile | undefined) { - if (configFile) { - Object.defineProperty(options, "configFile", { enumerable: false, writable: false, value: configFile }); + for (const name in options) { + if (hasProperty(options, name)) { + result[name] = convertToOptionValueWithAbsolutePaths( + optionsNameMap.get(name.toLowerCase()), + options[name] as CompilerOptionsValue, + toAbsolutePath + ); } } - - function isNullOrUndefined(x: any): x is null | undefined { - return x === undefined || x === null; // eslint-disable-line no-null/no-null + if (result.configFilePath) { + result.configFilePath = toAbsolutePath(result.configFilePath); } + return result; +} - function directoryOfCombinedPath(fileName: string, basePath: string) { - // Use the `getNormalizedAbsolutePath` function to avoid canonicalizing the path, as it must remain noncanonical - // until consistent casing errors are reported - return getDirectoryPath(getNormalizedAbsolutePath(fileName, basePath)); +function convertToOptionValueWithAbsolutePaths(option: CommandLineOption | undefined, value: CompilerOptionsValue, toAbsolutePath: (path: string) => string) { + if (option && !isNullOrUndefined(value)) { + if (option.type === "list") { + const values = value as readonly (string | number)[]; + if (option.element.isFilePath && values.length) { + return values.map(toAbsolutePath); + } + } + else if (option.isFilePath) { + return toAbsolutePath(value as string); + } } + return value; +} - /*@internal*/ - export const defaultIncludeSpec = "**/*"; +/** + * Parse the contents of a config file (tsconfig.json). + * @param json The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + */ +export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { + return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); +} - /** - * Parse the contents of a config file from json or json source file (tsconfig.json). - * @param json The contents of the config file to parse - * @param sourceFile sourceFile corresponding to the Json - * @param host Instance of ParseConfigHost used to enumerate files in folder. - * @param basePath A root directory to resolve relative path entries in the config - * file to. e.g. outDir - * @param resolutionStack Only present for backwards-compatibility. Should be empty. - */ - function parseJsonConfigFileContentWorker( - json: any, - sourceFile: TsConfigSourceFile | undefined, - host: ParseConfigHost, - basePath: string, - existingOptions: CompilerOptions = {}, - existingWatchOptions: WatchOptions | undefined, - configFileName?: string, - resolutionStack: Path[] = [], - extraFileExtensions: readonly FileExtensionInfo[] = [], - extendedConfigCache?: ESMap - ): ParsedCommandLine { - Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined)); - const errors: Diagnostic[] = []; - - const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors, extendedConfigCache); - const { raw } = parsedConfig; - const options = extend(existingOptions, parsedConfig.options || {}); - const watchOptions = existingWatchOptions && parsedConfig.watchOptions ? - extend(existingWatchOptions, parsedConfig.watchOptions) : - parsedConfig.watchOptions || existingWatchOptions; - - options.configFilePath = configFileName && normalizeSlashes(configFileName); - const configFileSpecs = getConfigFileSpecs(); - if (sourceFile) sourceFile.configFileSpecs = configFileSpecs; - setConfigFileInOptions(options, sourceFile); - - const basePathForFileNames = normalizePath(configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath); - return { - options, - watchOptions, - fileNames: getFileNames(basePathForFileNames), - projectReferences: getProjectReferences(basePathForFileNames), - typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(), - raw, - errors, - // Wildcard directories (provided as part of a wildcard path) are stored in a - // file map that marks whether it was a regular wildcard match (with a `*` or `?` token), - // or a recursive directory. This information is used by filesystem watchers to monitor for - // new entries in these paths. - wildcardDirectories: getWildcardDirectories(configFileSpecs, basePathForFileNames, host.useCaseSensitiveFileNames), - compileOnSave: !!raw.compileOnSave, - }; +/** + * Parse the contents of a config file (tsconfig.json). + * @param jsonNode The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + */ +export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map, existingWatchOptions?: WatchOptions): ParsedCommandLine { + tracing?.push(tracing.Phase.Parse, "parseJsonSourceFileConfigFileContent", { path: sourceFile.fileName }); + const result = parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache); + tracing?.pop(); + return result; +} - function getConfigFileSpecs(): ConfigFileSpecs { - const referencesOfRaw = getPropFromRaw("references", element => typeof element === "object", "object"); - const filesSpecs = toPropValue(getSpecsFromRaw("files")); - if (filesSpecs) { - const hasZeroOrNoReferences = referencesOfRaw === "no-prop" || isArray(referencesOfRaw) && referencesOfRaw.length === 0; - const hasExtends = hasProperty(raw, "extends"); - if (filesSpecs.length === 0 && hasZeroOrNoReferences && !hasExtends) { - if (sourceFile) { - const fileName = configFileName || "tsconfig.json"; - const diagnosticMessage = Diagnostics.The_files_list_in_config_file_0_is_empty; - const nodeValue = firstDefined(getTsConfigPropArray(sourceFile, "files"), property => property.initializer); - const error = nodeValue - ? createDiagnosticForNodeInSourceFile(sourceFile, nodeValue, diagnosticMessage, fileName) - : createCompilerDiagnostic(diagnosticMessage, fileName); - errors.push(error); - } - else { - createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"); - } - } - } +/** @internal */ +export function setConfigFileInOptions(options: CompilerOptions, configFile: TsConfigSourceFile | undefined) { + if (configFile) { + Object.defineProperty(options, "configFile", { enumerable: false, writable: false, value: configFile }); + } +} - let includeSpecs = toPropValue(getSpecsFromRaw("include")); +function isNullOrUndefined(x: any): x is null | undefined { + return x === undefined || x === null; // eslint-disable-line no-null/no-null +} - const excludeOfRaw = getSpecsFromRaw("exclude"); - let isDefaultIncludeSpec = false; - let excludeSpecs = toPropValue(excludeOfRaw); - if (excludeOfRaw === "no-prop" && raw.compilerOptions) { - const outDir = raw.compilerOptions.outDir; - const declarationDir = raw.compilerOptions.declarationDir; +function directoryOfCombinedPath(fileName: string, basePath: string) { + // Use the `getNormalizedAbsolutePath` function to avoid canonicalizing the path, as it must remain noncanonical + // until consistent casing errors are reported + return getDirectoryPath(getNormalizedAbsolutePath(fileName, basePath)); +} - if (outDir || declarationDir) { - excludeSpecs = [outDir, declarationDir].filter(d => !!d); - } - } +/** @internal */ +export const defaultIncludeSpec = "**/*"; + +/** + * Parse the contents of a config file from json or json source file (tsconfig.json). + * @param json The contents of the config file to parse + * @param sourceFile sourceFile corresponding to the Json + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + * @param resolutionStack Only present for backwards-compatibility. Should be empty. + */ +function parseJsonConfigFileContentWorker( + json: any, + sourceFile: TsConfigSourceFile | undefined, + host: ParseConfigHost, + basePath: string, + existingOptions: CompilerOptions = {}, + existingWatchOptions: WatchOptions | undefined, + configFileName?: string, + resolutionStack: Path[] = [], + extraFileExtensions: readonly FileExtensionInfo[] = [], + extendedConfigCache?: ESMap +): ParsedCommandLine { + Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined)); + const errors: Diagnostic[] = []; + + const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors, extendedConfigCache); + const { raw } = parsedConfig; + const options = extend(existingOptions, parsedConfig.options || {}); + const watchOptions = existingWatchOptions && parsedConfig.watchOptions ? + extend(existingWatchOptions, parsedConfig.watchOptions) : + parsedConfig.watchOptions || existingWatchOptions; + + options.configFilePath = configFileName && normalizeSlashes(configFileName); + const configFileSpecs = getConfigFileSpecs(); + if (sourceFile) sourceFile.configFileSpecs = configFileSpecs; + setConfigFileInOptions(options, sourceFile); + + const basePathForFileNames = normalizePath(configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath); + return { + options, + watchOptions, + fileNames: getFileNames(basePathForFileNames), + projectReferences: getProjectReferences(basePathForFileNames), + typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(), + raw, + errors, + // Wildcard directories (provided as part of a wildcard path) are stored in a + // file map that marks whether it was a regular wildcard match (with a `*` or `?` token), + // or a recursive directory. This information is used by filesystem watchers to monitor for + // new entries in these paths. + wildcardDirectories: getWildcardDirectories(configFileSpecs, basePathForFileNames, host.useCaseSensitiveFileNames), + compileOnSave: !!raw.compileOnSave, + }; - if (filesSpecs === undefined && includeSpecs === undefined) { - includeSpecs = [defaultIncludeSpec]; - isDefaultIncludeSpec = true; + function getConfigFileSpecs(): ConfigFileSpecs { + const referencesOfRaw = getPropFromRaw("references", element => typeof element === "object", "object"); + const filesSpecs = toPropValue(getSpecsFromRaw("files")); + if (filesSpecs) { + const hasZeroOrNoReferences = referencesOfRaw === "no-prop" || isArray(referencesOfRaw) && referencesOfRaw.length === 0; + const hasExtends = hasProperty(raw, "extends"); + if (filesSpecs.length === 0 && hasZeroOrNoReferences && !hasExtends) { + if (sourceFile) { + const fileName = configFileName || "tsconfig.json"; + const diagnosticMessage = Diagnostics.The_files_list_in_config_file_0_is_empty; + const nodeValue = firstDefined(getTsConfigPropArray(sourceFile, "files"), property => property.initializer); + const error = nodeValue + ? createDiagnosticForNodeInSourceFile(sourceFile, nodeValue, diagnosticMessage, fileName) + : createCompilerDiagnostic(diagnosticMessage, fileName); + errors.push(error); + } + else { + createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"); + } } - let validatedIncludeSpecs: readonly string[] | undefined, validatedExcludeSpecs: readonly string[] | undefined; + } - // The exclude spec list is converted into a regular expression, which allows us to quickly - // test whether a file or directory should be excluded before recursively traversing the - // file system. + let includeSpecs = toPropValue(getSpecsFromRaw("include")); - if (includeSpecs) { - validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*disallowTrailingRecursion*/ true, sourceFile, "include"); - } + const excludeOfRaw = getSpecsFromRaw("exclude"); + let isDefaultIncludeSpec = false; + let excludeSpecs = toPropValue(excludeOfRaw); + if (excludeOfRaw === "no-prop" && raw.compilerOptions) { + const outDir = raw.compilerOptions.outDir; + const declarationDir = raw.compilerOptions.declarationDir; - if (excludeSpecs) { - validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*disallowTrailingRecursion*/ false, sourceFile, "exclude"); + if (outDir || declarationDir) { + excludeSpecs = [outDir, declarationDir].filter(d => !!d); } - - return { - filesSpecs, - includeSpecs, - excludeSpecs, - validatedFilesSpec: filter(filesSpecs, isString), - validatedIncludeSpecs, - validatedExcludeSpecs, - pathPatterns: undefined, // Initialized on first use - isDefaultIncludeSpec, - }; } - function getFileNames(basePath: string): string[] { - const fileNames = getFileNamesFromConfigSpecs(configFileSpecs, basePath, options, host, extraFileExtensions); - if (shouldReportNoInputFiles(fileNames, canJsonReportNoInputFiles(raw), resolutionStack)) { - errors.push(getErrorForNoInputFiles(configFileSpecs, configFileName)); - } - return fileNames; + if (filesSpecs === undefined && includeSpecs === undefined) { + includeSpecs = [defaultIncludeSpec]; + isDefaultIncludeSpec = true; } + let validatedIncludeSpecs: readonly string[] | undefined, validatedExcludeSpecs: readonly string[] | undefined; - function getProjectReferences(basePath: string): readonly ProjectReference[] | undefined { - let projectReferences: ProjectReference[] | undefined; - const referencesOfRaw = getPropFromRaw("references", element => typeof element === "object", "object"); - if (isArray(referencesOfRaw)) { - for (const ref of referencesOfRaw) { - if (typeof ref.path !== "string") { - createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "reference.path", "string"); - } - else { - (projectReferences || (projectReferences = [])).push({ - path: getNormalizedAbsolutePath(ref.path, basePath), - originalPath: ref.path, - prepend: ref.prepend, - circular: ref.circular - }); - } - } - } - return projectReferences; + // The exclude spec list is converted into a regular expression, which allows us to quickly + // test whether a file or directory should be excluded before recursively traversing the + // file system. + + if (includeSpecs) { + validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*disallowTrailingRecursion*/ true, sourceFile, "include"); } - type PropOfRaw = readonly T[] | "not-array" | "no-prop"; - function toPropValue(specResult: PropOfRaw) { - return isArray(specResult) ? specResult : undefined; + if (excludeSpecs) { + validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*disallowTrailingRecursion*/ false, sourceFile, "exclude"); } - function getSpecsFromRaw(prop: "files" | "include" | "exclude"): PropOfRaw { - return getPropFromRaw(prop, isString, "string"); + return { + filesSpecs, + includeSpecs, + excludeSpecs, + validatedFilesSpec: filter(filesSpecs, isString), + validatedIncludeSpecs, + validatedExcludeSpecs, + pathPatterns: undefined, // Initialized on first use + isDefaultIncludeSpec, + }; + } + + function getFileNames(basePath: string): string[] { + const fileNames = getFileNamesFromConfigSpecs(configFileSpecs, basePath, options, host, extraFileExtensions); + if (shouldReportNoInputFiles(fileNames, canJsonReportNoInputFiles(raw), resolutionStack)) { + errors.push(getErrorForNoInputFiles(configFileSpecs, configFileName)); } + return fileNames; + } - function getPropFromRaw(prop: "files" | "include" | "exclude" | "references", validateElement: (value: unknown) => boolean, elementTypeName: string): PropOfRaw { - if (hasProperty(raw, prop) && !isNullOrUndefined(raw[prop])) { - if (isArray(raw[prop])) { - const result = raw[prop] as T[]; - if (!sourceFile && !every(result, validateElement)) { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, elementTypeName)); - } - return result; + function getProjectReferences(basePath: string): readonly ProjectReference[] | undefined { + let projectReferences: ProjectReference[] | undefined; + const referencesOfRaw = getPropFromRaw("references", element => typeof element === "object", "object"); + if (isArray(referencesOfRaw)) { + for (const ref of referencesOfRaw) { + if (typeof ref.path !== "string") { + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "reference.path", "string"); } else { - createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, "Array"); - return "not-array"; + (projectReferences || (projectReferences = [])).push({ + path: getNormalizedAbsolutePath(ref.path, basePath), + originalPath: ref.path, + prepend: ref.prepend, + circular: ref.circular + }); } } - return "no-prop"; - } - - function createCompilerDiagnosticOnlyIfJson(message: DiagnosticMessage, arg0?: string, arg1?: string) { - if (!sourceFile) { - errors.push(createCompilerDiagnostic(message, arg0, arg1)); - } } + return projectReferences; } - function isErrorNoInputFiles(error: Diagnostic) { - return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; - } - - function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) { - return createCompilerDiagnostic( - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - configFileName || "tsconfig.json", - JSON.stringify(includeSpecs || []), - JSON.stringify(excludeSpecs || [])); + type PropOfRaw = readonly T[] | "not-array" | "no-prop"; + function toPropValue(specResult: PropOfRaw) { + return isArray(specResult) ? specResult : undefined; } - function shouldReportNoInputFiles(fileNames: string[], canJsonReportNoInutFiles: boolean, resolutionStack?: Path[]) { - return fileNames.length === 0 && canJsonReportNoInutFiles && (!resolutionStack || resolutionStack.length === 0); + function getSpecsFromRaw(prop: "files" | "include" | "exclude"): PropOfRaw { + return getPropFromRaw(prop, isString, "string"); } - /*@internal*/ - export function canJsonReportNoInputFiles(raw: any) { - return !hasProperty(raw, "files") && !hasProperty(raw, "references"); + function getPropFromRaw(prop: "files" | "include" | "exclude" | "references", validateElement: (value: unknown) => boolean, elementTypeName: string): PropOfRaw { + if (hasProperty(raw, prop) && !isNullOrUndefined(raw[prop])) { + if (isArray(raw[prop])) { + const result = raw[prop] as T[]; + if (!sourceFile && !every(result, validateElement)) { + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, elementTypeName)); + } + return result; + } + else { + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, "Array"); + return "not-array"; + } + } + return "no-prop"; } - /*@internal*/ - export function updateErrorForNoInputFiles(fileNames: string[], configFileName: string, configFileSpecs: ConfigFileSpecs, configParseDiagnostics: Diagnostic[], canJsonReportNoInutFiles: boolean) { - const existingErrors = configParseDiagnostics.length; - if (shouldReportNoInputFiles(fileNames, canJsonReportNoInutFiles)) { - configParseDiagnostics.push(getErrorForNoInputFiles(configFileSpecs, configFileName)); + function createCompilerDiagnosticOnlyIfJson(message: DiagnosticMessage, arg0?: string, arg1?: string) { + if (!sourceFile) { + errors.push(createCompilerDiagnostic(message, arg0, arg1)); } - else { - filterMutate(configParseDiagnostics, error => !isErrorNoInputFiles(error)); - } - return existingErrors !== configParseDiagnostics.length; } +} - export interface ParsedTsconfig { - raw: any; - options?: CompilerOptions; - watchOptions?: WatchOptions; - typeAcquisition?: TypeAcquisition; - /** - * Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet - */ - extendedConfigPath?: string; - } +function isErrorNoInputFiles(error: Diagnostic) { + return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; +} + +function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) { + return createCompilerDiagnostic( + Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + configFileName || "tsconfig.json", + JSON.stringify(includeSpecs || []), + JSON.stringify(excludeSpecs || [])); +} - function isSuccessfulParsedTsconfig(value: ParsedTsconfig) { - return !!value.options; +function shouldReportNoInputFiles(fileNames: string[], canJsonReportNoInutFiles: boolean, resolutionStack?: Path[]) { + return fileNames.length === 0 && canJsonReportNoInutFiles && (!resolutionStack || resolutionStack.length === 0); +} + +/** @internal */ +export function canJsonReportNoInputFiles(raw: any) { + return !hasProperty(raw, "files") && !hasProperty(raw, "references"); +} + +/** @internal */ +export function updateErrorForNoInputFiles(fileNames: string[], configFileName: string, configFileSpecs: ConfigFileSpecs, configParseDiagnostics: Diagnostic[], canJsonReportNoInutFiles: boolean) { + const existingErrors = configParseDiagnostics.length; + if (shouldReportNoInputFiles(fileNames, canJsonReportNoInutFiles)) { + configParseDiagnostics.push(getErrorForNoInputFiles(configFileSpecs, configFileName)); } + else { + filterMutate(configParseDiagnostics, error => !isErrorNoInputFiles(error)); + } + return existingErrors !== configParseDiagnostics.length; +} +export interface ParsedTsconfig { + raw: any; + options?: CompilerOptions; + watchOptions?: WatchOptions; + typeAcquisition?: TypeAcquisition; /** - * This *just* extracts options/include/exclude/files out of a config file. - * It does *not* resolve the included files. + * Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet */ - function parseConfig( - json: any, - sourceFile: TsConfigSourceFile | undefined, - host: ParseConfigHost, - basePath: string, - configFileName: string | undefined, - resolutionStack: string[], - errors: Push, - extendedConfigCache?: ESMap - ): ParsedTsconfig { - basePath = normalizeSlashes(basePath); - const resolvedPath = getNormalizedAbsolutePath(configFileName || "", basePath); - - if (resolutionStack.indexOf(resolvedPath) >= 0) { - errors.push(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))); - return { raw: json || convertToObject(sourceFile!, errors) }; - } - - const ownConfig = json ? - parseOwnConfigOfJson(json, host, basePath, configFileName, errors) : - parseOwnConfigOfJsonSourceFile(sourceFile!, host, basePath, configFileName, errors); - - if (ownConfig.options?.paths) { - // If we end up needing to resolve relative paths from 'paths' relative to - // the config file location, we'll need to know where that config file was. - // Since 'paths' can be inherited from an extended config in another directory, - // we wouldn't know which directory to use unless we store it here. - ownConfig.options.pathsBasePath = basePath; - } - if (ownConfig.extendedConfigPath) { - // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. - resolutionStack = resolutionStack.concat([resolvedPath]); - const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, resolutionStack, errors, extendedConfigCache); - if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) { - const baseRaw = extendedConfig.raw; - const raw = ownConfig.raw; - let relativeDifference: string | undefined ; - const setPropertyInRawIfNotUndefined = (propertyName: string) => { - if (!raw[propertyName] && baseRaw[propertyName]) { - raw[propertyName] = map(baseRaw[propertyName], (path: string) => isRootedDiskPath(path) ? path : combinePaths( - relativeDifference ||= convertToRelativePath(getDirectoryPath(ownConfig.extendedConfigPath!), basePath, createGetCanonicalFileName(host.useCaseSensitiveFileNames)), - path - )); - } - }; - setPropertyInRawIfNotUndefined("include"); - setPropertyInRawIfNotUndefined("exclude"); - setPropertyInRawIfNotUndefined("files"); - if (raw.compileOnSave === undefined) { - raw.compileOnSave = baseRaw.compileOnSave; + extendedConfigPath?: string; +} + +function isSuccessfulParsedTsconfig(value: ParsedTsconfig) { + return !!value.options; +} + +/** + * This *just* extracts options/include/exclude/files out of a config file. + * It does *not* resolve the included files. + */ +function parseConfig( + json: any, + sourceFile: TsConfigSourceFile | undefined, + host: ParseConfigHost, + basePath: string, + configFileName: string | undefined, + resolutionStack: string[], + errors: Push, + extendedConfigCache?: ESMap +): ParsedTsconfig { + basePath = normalizeSlashes(basePath); + const resolvedPath = getNormalizedAbsolutePath(configFileName || "", basePath); + + if (resolutionStack.indexOf(resolvedPath) >= 0) { + errors.push(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))); + return { raw: json || convertToObject(sourceFile!, errors) }; + } + + const ownConfig = json ? + parseOwnConfigOfJson(json, host, basePath, configFileName, errors) : + parseOwnConfigOfJsonSourceFile(sourceFile!, host, basePath, configFileName, errors); + + if (ownConfig.options?.paths) { + // If we end up needing to resolve relative paths from 'paths' relative to + // the config file location, we'll need to know where that config file was. + // Since 'paths' can be inherited from an extended config in another directory, + // we wouldn't know which directory to use unless we store it here. + ownConfig.options.pathsBasePath = basePath; + } + if (ownConfig.extendedConfigPath) { + // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. + resolutionStack = resolutionStack.concat([resolvedPath]); + const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, resolutionStack, errors, extendedConfigCache); + if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) { + const baseRaw = extendedConfig.raw; + const raw = ownConfig.raw; + let relativeDifference: string | undefined ; + const setPropertyInRawIfNotUndefined = (propertyName: string) => { + if (!raw[propertyName] && baseRaw[propertyName]) { + raw[propertyName] = map(baseRaw[propertyName], (path: string) => isRootedDiskPath(path) ? path : combinePaths( + relativeDifference ||= convertToRelativePath(getDirectoryPath(ownConfig.extendedConfigPath!), basePath, createGetCanonicalFileName(host.useCaseSensitiveFileNames)), + path + )); } - ownConfig.options = assign({}, extendedConfig.options, ownConfig.options); - ownConfig.watchOptions = ownConfig.watchOptions && extendedConfig.watchOptions ? - assign({}, extendedConfig.watchOptions, ownConfig.watchOptions) : - ownConfig.watchOptions || extendedConfig.watchOptions; - // TODO extend type typeAcquisition + }; + setPropertyInRawIfNotUndefined("include"); + setPropertyInRawIfNotUndefined("exclude"); + setPropertyInRawIfNotUndefined("files"); + if (raw.compileOnSave === undefined) { + raw.compileOnSave = baseRaw.compileOnSave; } + ownConfig.options = assign({}, extendedConfig.options, ownConfig.options); + ownConfig.watchOptions = ownConfig.watchOptions && extendedConfig.watchOptions ? + assign({}, extendedConfig.watchOptions, ownConfig.watchOptions) : + ownConfig.watchOptions || extendedConfig.watchOptions; + // TODO extend type typeAcquisition } - - return ownConfig; } - function parseOwnConfigOfJson( - json: any, - host: ParseConfigHost, - basePath: string, - configFileName: string | undefined, - errors: Push - ): ParsedTsconfig { - if (hasProperty(json, "excludes")) { - errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); - } - - const options = convertCompilerOptionsFromJsonWorker(json.compilerOptions, basePath, errors, configFileName); - // typingOptions has been deprecated and is only supported for backward compatibility purposes. - // It should be removed in future releases - use typeAcquisition instead. - const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json.typeAcquisition || json.typingOptions, basePath, errors, configFileName); - const watchOptions = convertWatchOptionsFromJsonWorker(json.watchOptions, basePath, errors); - json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); - let extendedConfigPath: string | undefined; + return ownConfig; +} - if (json.extends) { - if (!isString(json.extends)) { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string")); - } - else { - const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; - extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic); - } +function parseOwnConfigOfJson( + json: any, + host: ParseConfigHost, + basePath: string, + configFileName: string | undefined, + errors: Push +): ParsedTsconfig { + if (hasProperty(json, "excludes")) { + errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); + } + + const options = convertCompilerOptionsFromJsonWorker(json.compilerOptions, basePath, errors, configFileName); + // typingOptions has been deprecated and is only supported for backward compatibility purposes. + // It should be removed in future releases - use typeAcquisition instead. + const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json.typeAcquisition || json.typingOptions, basePath, errors, configFileName); + const watchOptions = convertWatchOptionsFromJsonWorker(json.watchOptions, basePath, errors); + json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); + let extendedConfigPath: string | undefined; + + if (json.extends) { + if (!isString(json.extends)) { + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string")); } - return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; - } - - function parseOwnConfigOfJsonSourceFile( - sourceFile: TsConfigSourceFile, - host: ParseConfigHost, - basePath: string, - configFileName: string | undefined, - errors: Push - ): ParsedTsconfig { - const options = getDefaultCompilerOptions(configFileName); - let typeAcquisition: TypeAcquisition | undefined, typingOptionstypeAcquisition: TypeAcquisition | undefined; - let watchOptions: WatchOptions | undefined; - let extendedConfigPath: string | undefined; - let rootCompilerOptions: PropertyName[] | undefined; - - const optionsIterator: JsonConversionNotifier = { - onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) { - let currentOption; - switch (parentOption) { - case "compilerOptions": - currentOption = options; - break; - case "watchOptions": - currentOption = (watchOptions || (watchOptions = {})); - break; - case "typeAcquisition": - currentOption = (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName))); - break; - case "typingOptions": - currentOption = (typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName))); - break; - default: - Debug.fail("Unknown option"); - } + else { + const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; + extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic); + } + } + return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; +} - currentOption[option.name] = normalizeOptionValue(option, basePath, value); - }, - onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) { - switch (key) { - case "extends": - const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; - extendedConfigPath = getExtendsConfigPath( - value as string, - host, - newBase, - errors, - (message, arg0) => - createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) - ); - return; - } - }, - onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) { - if (key === "excludes") { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, keyNode, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); - } - if (find(commandOptionsWithoutBuild, (opt) => opt.name === key)) { - rootCompilerOptions = append(rootCompilerOptions, keyNode); - } +function parseOwnConfigOfJsonSourceFile( + sourceFile: TsConfigSourceFile, + host: ParseConfigHost, + basePath: string, + configFileName: string | undefined, + errors: Push +): ParsedTsconfig { + const options = getDefaultCompilerOptions(configFileName); + let typeAcquisition: TypeAcquisition | undefined, typingOptionstypeAcquisition: TypeAcquisition | undefined; + let watchOptions: WatchOptions | undefined; + let extendedConfigPath: string | undefined; + let rootCompilerOptions: PropertyName[] | undefined; + + const optionsIterator: JsonConversionNotifier = { + onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) { + let currentOption; + switch (parentOption) { + case "compilerOptions": + currentOption = options; + break; + case "watchOptions": + currentOption = (watchOptions || (watchOptions = {})); + break; + case "typeAcquisition": + currentOption = (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName))); + break; + case "typingOptions": + currentOption = (typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName))); + break; + default: + Debug.fail("Unknown option"); + } + + currentOption[option.name] = normalizeOptionValue(option, basePath, value); + }, + onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) { + switch (key) { + case "extends": + const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; + extendedConfigPath = getExtendsConfigPath( + value as string, + host, + newBase, + errors, + (message, arg0) => + createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) + ); + return; } - }; - const json = convertConfigFileToObject(sourceFile, errors, /*reportOptionsErrors*/ true, optionsIterator); - - if (!typeAcquisition) { - if (typingOptionstypeAcquisition) { - typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ? - { - enable: typingOptionstypeAcquisition.enableAutoDiscovery, - include: typingOptionstypeAcquisition.include, - exclude: typingOptionstypeAcquisition.exclude - } : - typingOptionstypeAcquisition; + }, + onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) { + if (key === "excludes") { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, keyNode, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); } - else { - typeAcquisition = getDefaultTypeAcquisition(configFileName); + if (find(commandOptionsWithoutBuild, (opt) => opt.name === key)) { + rootCompilerOptions = append(rootCompilerOptions, keyNode); } } - - if (rootCompilerOptions && json && json.compilerOptions === undefined) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, rootCompilerOptions[0], Diagnostics._0_should_be_set_inside_the_compilerOptions_object_of_the_config_json_file, getTextOfPropertyName(rootCompilerOptions[0]) as string)); - } - - return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; - } - - function getExtendsConfigPath( - extendedConfig: string, - host: ParseConfigHost, - basePath: string, - errors: Push, - createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) { - extendedConfig = normalizeSlashes(extendedConfig); - if (isRootedDiskPath(extendedConfig) || startsWith(extendedConfig, "./") || startsWith(extendedConfig, "../")) { - let extendedConfigPath = getNormalizedAbsolutePath(extendedConfig, basePath); - if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) { - extendedConfigPath = `${extendedConfigPath}.json`; - if (!host.fileExists(extendedConfigPath)) { - errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); - return undefined; - } - } - return extendedConfigPath; + }; + const json = convertConfigFileToObject(sourceFile, errors, /*reportOptionsErrors*/ true, optionsIterator); + + if (!typeAcquisition) { + if (typingOptionstypeAcquisition) { + typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ? + { + enable: typingOptionstypeAcquisition.enableAutoDiscovery, + include: typingOptionstypeAcquisition.include, + exclude: typingOptionstypeAcquisition.exclude + } : + typingOptionstypeAcquisition; } - // If the path isn't a rooted or relative path, resolve like a module - const resolved = nodeModuleNameResolver(extendedConfig, combinePaths(basePath, "tsconfig.json"), { moduleResolution: ModuleResolutionKind.NodeJs }, host, /*cache*/ undefined, /*projectRefs*/ undefined, /*lookupConfig*/ true); - if (resolved.resolvedModule) { - return resolved.resolvedModule.resolvedFileName; + else { + typeAcquisition = getDefaultTypeAcquisition(configFileName); } - errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); - return undefined; } - export interface ExtendedConfigCacheEntry { - extendedResult: TsConfigSourceFile; - extendedConfig: ParsedTsconfig | undefined; + if (rootCompilerOptions && json && json.compilerOptions === undefined) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, rootCompilerOptions[0], Diagnostics._0_should_be_set_inside_the_compilerOptions_object_of_the_config_json_file, getTextOfPropertyName(rootCompilerOptions[0]) as string)); } - function getExtendedConfig( - sourceFile: TsConfigSourceFile | undefined, - extendedConfigPath: string, - host: ParseConfigHost, - resolutionStack: string[], - errors: Push, - extendedConfigCache?: ESMap - ): ParsedTsconfig | undefined { - const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toFileNameLowerCase(extendedConfigPath); - let value: ExtendedConfigCacheEntry | undefined; - let extendedResult: TsConfigSourceFile; - let extendedConfig: ParsedTsconfig | undefined; - if (extendedConfigCache && (value = extendedConfigCache.get(path))) { - ({ extendedResult, extendedConfig } = value); - } - else { - extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path)); - if (!extendedResult.parseDiagnostics.length) { - extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, getDirectoryPath(extendedConfigPath), - getBaseFileName(extendedConfigPath), resolutionStack, errors, extendedConfigCache); - } - if (extendedConfigCache) { - extendedConfigCache.set(path, { extendedResult, extendedConfig }); - } - } - if (sourceFile) { - sourceFile.extendedSourceFiles = [extendedResult.fileName]; - if (extendedResult.extendedSourceFiles) { - sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); + return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath }; +} + +function getExtendsConfigPath( + extendedConfig: string, + host: ParseConfigHost, + basePath: string, + errors: Push, + createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) { + extendedConfig = normalizeSlashes(extendedConfig); + if (isRootedDiskPath(extendedConfig) || startsWith(extendedConfig, "./") || startsWith(extendedConfig, "../")) { + let extendedConfigPath = getNormalizedAbsolutePath(extendedConfig, basePath); + if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) { + extendedConfigPath = `${extendedConfigPath}.json`; + if (!host.fileExists(extendedConfigPath)) { + errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); + return undefined; } } - if (extendedResult.parseDiagnostics.length) { - errors.push(...extendedResult.parseDiagnostics); - return undefined; - } - return extendedConfig!; + return extendedConfigPath; } - - function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Push): boolean { - if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) { - return false; - } - const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption.compileOnSave, basePath, errors); - return typeof result === "boolean" && result; + // If the path isn't a rooted or relative path, resolve like a module + const resolved = nodeModuleNameResolver(extendedConfig, combinePaths(basePath, "tsconfig.json"), { moduleResolution: ModuleResolutionKind.NodeJs }, host, /*cache*/ undefined, /*projectRefs*/ undefined, /*lookupConfig*/ true); + if (resolved.resolvedModule) { + return resolved.resolvedModule.resolvedFileName; } + errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); + return undefined; +} - export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: CompilerOptions, errors: Diagnostic[] } { - const errors: Diagnostic[] = []; - const options = convertCompilerOptionsFromJsonWorker(jsonOptions, basePath, errors, configFileName); - return { options, errors }; - } +export interface ExtendedConfigCacheEntry { + extendedResult: TsConfigSourceFile; + extendedConfig: ParsedTsconfig | undefined; +} - export function convertTypeAcquisitionFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: TypeAcquisition, errors: Diagnostic[] } { - const errors: Diagnostic[] = []; - const options = convertTypeAcquisitionFromJsonWorker(jsonOptions, basePath, errors, configFileName); - return { options, errors }; +function getExtendedConfig( + sourceFile: TsConfigSourceFile | undefined, + extendedConfigPath: string, + host: ParseConfigHost, + resolutionStack: string[], + errors: Push, + extendedConfigCache?: ESMap +): ParsedTsconfig | undefined { + const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toFileNameLowerCase(extendedConfigPath); + let value: ExtendedConfigCacheEntry | undefined; + let extendedResult: TsConfigSourceFile; + let extendedConfig: ParsedTsconfig | undefined; + if (extendedConfigCache && (value = extendedConfigCache.get(path))) { + ({ extendedResult, extendedConfig } = value); + } + else { + extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path)); + if (!extendedResult.parseDiagnostics.length) { + extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, getDirectoryPath(extendedConfigPath), + getBaseFileName(extendedConfigPath), resolutionStack, errors, extendedConfigCache); + } + if (extendedConfigCache) { + extendedConfigCache.set(path, { extendedResult, extendedConfig }); + } + } + if (sourceFile) { + sourceFile.extendedSourceFiles = [extendedResult.fileName]; + if (extendedResult.extendedSourceFiles) { + sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); + } + } + if (extendedResult.parseDiagnostics.length) { + errors.push(...extendedResult.parseDiagnostics); + return undefined; } + return extendedConfig!; +} - function getDefaultCompilerOptions(configFileName?: string) { - const options: CompilerOptions = configFileName && getBaseFileName(configFileName) === "jsconfig.json" - ? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, skipLibCheck: true, noEmit: true } - : {}; - return options; +function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Push): boolean { + if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) { + return false; } + const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption.compileOnSave, basePath, errors); + return typeof result === "boolean" && result; +} + +export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: CompilerOptions, errors: Diagnostic[] } { + const errors: Diagnostic[] = []; + const options = convertCompilerOptionsFromJsonWorker(jsonOptions, basePath, errors, configFileName); + return { options, errors }; +} - function convertCompilerOptionsFromJsonWorker(jsonOptions: any, - basePath: string, errors: Push, configFileName?: string): CompilerOptions { +export function convertTypeAcquisitionFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: TypeAcquisition, errors: Diagnostic[] } { + const errors: Diagnostic[] = []; + const options = convertTypeAcquisitionFromJsonWorker(jsonOptions, basePath, errors, configFileName); + return { options, errors }; +} - const options = getDefaultCompilerOptions(configFileName); - convertOptionsFromJson(getCommandLineCompilerOptionsMap(), jsonOptions, basePath, options, compilerOptionsDidYouMeanDiagnostics, errors); - if (configFileName) { - options.configFilePath = normalizeSlashes(configFileName); - } - return options; - } +function getDefaultCompilerOptions(configFileName?: string) { + const options: CompilerOptions = configFileName && getBaseFileName(configFileName) === "jsconfig.json" + ? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, skipLibCheck: true, noEmit: true } + : {}; + return options; +} + +function convertCompilerOptionsFromJsonWorker(jsonOptions: any, + basePath: string, errors: Push, configFileName?: string): CompilerOptions { - function getDefaultTypeAcquisition(configFileName?: string): TypeAcquisition { - return { enable: !!configFileName && getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] }; + const options = getDefaultCompilerOptions(configFileName); + convertOptionsFromJson(getCommandLineCompilerOptionsMap(), jsonOptions, basePath, options, compilerOptionsDidYouMeanDiagnostics, errors); + if (configFileName) { + options.configFilePath = normalizeSlashes(configFileName); } + return options; +} - function convertTypeAcquisitionFromJsonWorker(jsonOptions: any, - basePath: string, errors: Push, configFileName?: string): TypeAcquisition { +function getDefaultTypeAcquisition(configFileName?: string): TypeAcquisition { + return { enable: !!configFileName && getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] }; +} - const options = getDefaultTypeAcquisition(configFileName); - const typeAcquisition = convertEnableAutoDiscoveryToEnable(jsonOptions); +function convertTypeAcquisitionFromJsonWorker(jsonOptions: any, + basePath: string, errors: Push, configFileName?: string): TypeAcquisition { - convertOptionsFromJson(getCommandLineTypeAcquisitionMap(), typeAcquisition, basePath, options, typeAcquisitionDidYouMeanDiagnostics, errors); - return options; - } + const options = getDefaultTypeAcquisition(configFileName); + const typeAcquisition = convertEnableAutoDiscoveryToEnable(jsonOptions); - function convertWatchOptionsFromJsonWorker(jsonOptions: any, basePath: string, errors: Push): WatchOptions | undefined { - return convertOptionsFromJson(getCommandLineWatchOptionsMap(), jsonOptions, basePath, /*defaultOptions*/ undefined, watchOptionsDidYouMeanDiagnostics, errors); - } + convertOptionsFromJson(getCommandLineTypeAcquisitionMap(), typeAcquisition, basePath, options, typeAcquisitionDidYouMeanDiagnostics, errors); + return options; +} - function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, - defaultOptions: undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): WatchOptions | undefined; - function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, - defaultOptions: CompilerOptions | TypeAcquisition, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): CompilerOptions | TypeAcquisition; - function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, - defaultOptions: CompilerOptions | TypeAcquisition | WatchOptions | undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push) { +function convertWatchOptionsFromJsonWorker(jsonOptions: any, basePath: string, errors: Push): WatchOptions | undefined { + return convertOptionsFromJson(getCommandLineWatchOptionsMap(), jsonOptions, basePath, /*defaultOptions*/ undefined, watchOptionsDidYouMeanDiagnostics, errors); +} - if (!jsonOptions) { - return; - } +function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, + defaultOptions: undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): WatchOptions | undefined; +function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, + defaultOptions: CompilerOptions | TypeAcquisition, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push): CompilerOptions | TypeAcquisition; +function convertOptionsFromJson(optionsNameMap: ESMap, jsonOptions: any, basePath: string, + defaultOptions: CompilerOptions | TypeAcquisition | WatchOptions | undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push) { - for (const id in jsonOptions) { - const opt = optionsNameMap.get(id); - if (opt) { - (defaultOptions || (defaultOptions = {}))[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); - } - else { - errors.push(createUnknownOptionError(id, diagnostics, createCompilerDiagnostic)); - } - } - return defaultOptions; + if (!jsonOptions) { + return; } - /*@internal*/ - export function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push): CompilerOptionsValue { - if (isCompilerOptionsValue(opt, value)) { - const optType = opt.type; - if (optType === "list" && isArray(value)) { - return convertJsonOptionOfListType(opt , value, basePath, errors); - } - else if (!isString(optType)) { - return convertJsonOptionOfCustomType(opt as CommandLineOptionOfCustomType, value as string, errors); - } - const validatedValue = validateJsonOptionValue(opt, value, errors); - return isNullOrUndefined(validatedValue) ? validatedValue : normalizeNonListOptionValue(opt, basePath, validatedValue); + for (const id in jsonOptions) { + const opt = optionsNameMap.get(id); + if (opt) { + (defaultOptions || (defaultOptions = {}))[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); } else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, getCompilerOptionValueTypeString(opt))); + errors.push(createUnknownOptionError(id, diagnostics, createCompilerDiagnostic)); } } + return defaultOptions; +} - function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { - if (isNullOrUndefined(value)) return undefined; - if (option.type === "list") { - const listOption = option; - if (listOption.element.isFilePath || !isString(listOption.element.type)) { - return filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => listOption.listPreserveFalsyValues ? true : !!v) as CompilerOptionsValue; - } - return value; +/** @internal */ +export function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push): CompilerOptionsValue { + if (isCompilerOptionsValue(opt, value)) { + const optType = opt.type; + if (optType === "list" && isArray(value)) { + return convertJsonOptionOfListType(opt , value, basePath, errors); } - else if (!isString(option.type)) { - return option.type.get(isString(value) ? value.toLowerCase() : value); + else if (!isString(optType)) { + return convertJsonOptionOfCustomType(opt as CommandLineOptionOfCustomType, value as string, errors); } - return normalizeNonListOptionValue(option, basePath, value); + const validatedValue = validateJsonOptionValue(opt, value, errors); + return isNullOrUndefined(validatedValue) ? validatedValue : normalizeNonListOptionValue(opt, basePath, validatedValue); } + else { + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, getCompilerOptionValueTypeString(opt))); + } +} - function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { - if (option.isFilePath) { - value = getNormalizedAbsolutePath(value, basePath); - if (value === "") { - value = "."; - } +function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { + if (isNullOrUndefined(value)) return undefined; + if (option.type === "list") { + const listOption = option; + if (listOption.element.isFilePath || !isString(listOption.element.type)) { + return filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => listOption.listPreserveFalsyValues ? true : !!v) as CompilerOptionsValue; } return value; } - - function validateJsonOptionValue(opt: CommandLineOption, value: T, errors: Push): T | undefined { - if (isNullOrUndefined(value)) return undefined; - const d = opt.extraValidation?.(value); - if (!d) return value; - errors.push(createCompilerDiagnostic(...d)); - return undefined; + else if (!isString(option.type)) { + return option.type.get(isString(value) ? value.toLowerCase() : value); } + return normalizeNonListOptionValue(option, basePath, value); +} - function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { - if (isNullOrUndefined(value)) return undefined; - const key = value.toLowerCase(); - const val = opt.type.get(key); - if (val !== undefined) { - return validateJsonOptionValue(opt, val, errors); - } - else { - errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); +function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { + if (option.isFilePath) { + value = getNormalizedAbsolutePath(value, basePath); + if (value === "") { + value = "."; } } + return value; +} - function convertJsonOptionOfListType(option: CommandLineOptionOfListType, values: readonly any[], basePath: string, errors: Push): any[] { - return filter(map(values, v => convertJsonOption(option.element, v, basePath, errors)), v => option.listPreserveFalsyValues ? true : !!v); - } - - /** - * Tests for a path that ends in a recursive directory wildcard. - * Matches **, \**, **\, and \**\, but not a**b. - * - * NOTE: used \ in place of / above to avoid issues with multiline comments. - * - * Breakdown: - * (^|\/) # matches either the beginning of the string or a directory separator. - * \*\* # matches the recursive directory wildcard "**". - * \/?$ # matches an optional trailing directory separator at the end of the string. - */ - const invalidTrailingRecursionPattern = /(^|\/)\*\*\/?$/; +function validateJsonOptionValue(opt: CommandLineOption, value: T, errors: Push): T | undefined { + if (isNullOrUndefined(value)) return undefined; + const d = opt.extraValidation?.(value); + if (!d) return value; + errors.push(createCompilerDiagnostic(...d)); + return undefined; +} - /** - * Matches the portion of a wildcard path that does not contain wildcards. - * Matches \a of \a\*, or \a\b\c of \a\b\c\?\d. - * - * NOTE: used \ in place of / above to avoid issues with multiline comments. - * - * Breakdown: - * ^ # matches the beginning of the string - * [^*?]* # matches any number of non-wildcard characters - * (?=\/[^/]*[*?]) # lookahead that matches a directory separator followed by - * # a path component that contains at least one wildcard character (* or ?). - */ - const wildcardDirectoryPattern = /^[^*?]*(?=\/[^/]*[*?])/; +function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Push) { + if (isNullOrUndefined(value)) return undefined; + const key = value.toLowerCase(); + const val = opt.type.get(key); + if (val !== undefined) { + return validateJsonOptionValue(opt, val, errors); + } + else { + errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); + } +} - /** - * Gets the file names from the provided config file specs that contain, files, include, exclude and - * other properties needed to resolve the file names - * @param configFileSpecs The config file specs extracted with file names to include, wildcards to include/exclude and other details - * @param basePath The base path for any relative file specifications. - * @param options Compiler options. - * @param host The host used to resolve files and directories. - * @param extraFileExtensions optionaly file extra file extension information from host - */ - /* @internal */ - export function getFileNamesFromConfigSpecs( - configFileSpecs: ConfigFileSpecs, - basePath: string, - options: CompilerOptions, - host: ParseConfigHost, - extraFileExtensions: readonly FileExtensionInfo[] = emptyArray - ): string[] { - basePath = normalizePath(basePath); - - const keyMapper = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - - // Literal file names (provided via the "files" array in tsconfig.json) are stored in a - // file map with a possibly case insensitive key. We use this map later when when including - // wildcard paths. - const literalFileMap = new Map(); - - // Wildcard paths (provided via the "includes" array in tsconfig.json) are stored in a - // file map with a possibly case insensitive key. We use this map to store paths matched - // via wildcard, and to handle extension priority. - const wildcardFileMap = new Map(); - - // Wildcard paths of json files (provided via the "includes" array in tsconfig.json) are stored in a - // file map with a possibly case insensitive key. We use this map to store paths matched - // via wildcard of *.json kind - const wildCardJsonFileMap = new Map(); - const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs } = configFileSpecs; - - // Rather than re-query this for each file and filespec, we query the supported extensions - // once and store it on the expansion context. - const supportedExtensions = getSupportedExtensions(options, extraFileExtensions); - const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); - - // Literal files are always included verbatim. An "include" or "exclude" specification cannot - // remove a literal file. - if (validatedFilesSpec) { - for (const fileName of validatedFilesSpec) { - const file = getNormalizedAbsolutePath(fileName, basePath); - literalFileMap.set(keyMapper(file), file); - } - } +function convertJsonOptionOfListType(option: CommandLineOptionOfListType, values: readonly any[], basePath: string, errors: Push): any[] { + return filter(map(values, v => convertJsonOption(option.element, v, basePath, errors)), v => option.listPreserveFalsyValues ? true : !!v); +} - let jsonOnlyIncludeRegexes: readonly RegExp[] | undefined; - if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) { - for (const file of host.readDirectory(basePath, flatten(supportedExtensionsWithJsonIfResolveJsonModule), validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) { - if (fileExtensionIs(file, Extension.Json)) { - // Valid only if *.json specified - if (!jsonOnlyIncludeRegexes) { - const includes = validatedIncludeSpecs.filter(s => endsWith(s, Extension.Json)); - const includeFilePatterns = map(getRegularExpressionsForWildcards(includes, basePath, "files"), pattern => `^${pattern}$`); - jsonOnlyIncludeRegexes = includeFilePatterns ? includeFilePatterns.map(pattern => getRegexFromPattern(pattern, host.useCaseSensitiveFileNames)) : emptyArray; - } - const includeIndex = findIndex(jsonOnlyIncludeRegexes, re => re.test(file)); - if (includeIndex !== -1) { - const key = keyMapper(file); - if (!literalFileMap.has(key) && !wildCardJsonFileMap.has(key)) { - wildCardJsonFileMap.set(key, file); - } - } - continue; +/** + * Tests for a path that ends in a recursive directory wildcard. + * Matches **, \**, **\, and \**\, but not a**b. + * + * NOTE: used \ in place of / above to avoid issues with multiline comments. + * + * Breakdown: + * (^|\/) # matches either the beginning of the string or a directory separator. + * \*\* # matches the recursive directory wildcard "**". + * \/?$ # matches an optional trailing directory separator at the end of the string. + */ +const invalidTrailingRecursionPattern = /(^|\/)\*\*\/?$/; + +/** + * Matches the portion of a wildcard path that does not contain wildcards. + * Matches \a of \a\*, or \a\b\c of \a\b\c\?\d. + * + * NOTE: used \ in place of / above to avoid issues with multiline comments. + * + * Breakdown: + * ^ # matches the beginning of the string + * [^*?]* # matches any number of non-wildcard characters + * (?=\/[^/]*[*?]) # lookahead that matches a directory separator followed by + * # a path component that contains at least one wildcard character (* or ?). + */ +const wildcardDirectoryPattern = /^[^*?]*(?=\/[^/]*[*?])/; + +/** + * Gets the file names from the provided config file specs that contain, files, include, exclude and + * other properties needed to resolve the file names + * @param configFileSpecs The config file specs extracted with file names to include, wildcards to include/exclude and other details + * @param basePath The base path for any relative file specifications. + * @param options Compiler options. + * @param host The host used to resolve files and directories. + * @param extraFileExtensions optionaly file extra file extension information from host + * + * @internal + */ +export function getFileNamesFromConfigSpecs( + configFileSpecs: ConfigFileSpecs, + basePath: string, + options: CompilerOptions, + host: ParseConfigHost, + extraFileExtensions: readonly FileExtensionInfo[] = emptyArray +): string[] { + basePath = normalizePath(basePath); + + const keyMapper = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + + // Literal file names (provided via the "files" array in tsconfig.json) are stored in a + // file map with a possibly case insensitive key. We use this map later when when including + // wildcard paths. + const literalFileMap = new Map(); + + // Wildcard paths (provided via the "includes" array in tsconfig.json) are stored in a + // file map with a possibly case insensitive key. We use this map to store paths matched + // via wildcard, and to handle extension priority. + const wildcardFileMap = new Map(); + + // Wildcard paths of json files (provided via the "includes" array in tsconfig.json) are stored in a + // file map with a possibly case insensitive key. We use this map to store paths matched + // via wildcard of *.json kind + const wildCardJsonFileMap = new Map(); + const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs } = configFileSpecs; + + // Rather than re-query this for each file and filespec, we query the supported extensions + // once and store it on the expansion context. + const supportedExtensions = getSupportedExtensions(options, extraFileExtensions); + const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); + + // Literal files are always included verbatim. An "include" or "exclude" specification cannot + // remove a literal file. + if (validatedFilesSpec) { + for (const fileName of validatedFilesSpec) { + const file = getNormalizedAbsolutePath(fileName, basePath); + literalFileMap.set(keyMapper(file), file); + } + } + + let jsonOnlyIncludeRegexes: readonly RegExp[] | undefined; + if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) { + for (const file of host.readDirectory(basePath, flatten(supportedExtensionsWithJsonIfResolveJsonModule), validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) { + if (fileExtensionIs(file, Extension.Json)) { + // Valid only if *.json specified + if (!jsonOnlyIncludeRegexes) { + const includes = validatedIncludeSpecs.filter(s => endsWith(s, Extension.Json)); + const includeFilePatterns = map(getRegularExpressionsForWildcards(includes, basePath, "files"), pattern => `^${pattern}$`); + jsonOnlyIncludeRegexes = includeFilePatterns ? includeFilePatterns.map(pattern => getRegexFromPattern(pattern, host.useCaseSensitiveFileNames)) : emptyArray; } - // If we have already included a literal or wildcard path with a - // higher priority extension, we should skip this file. - // - // This handles cases where we may encounter both .ts and - // .d.ts (or .js if "allowJs" is enabled) in the same - // directory when they are compilation outputs. - if (hasFileWithHigherPriorityExtension(file, literalFileMap, wildcardFileMap, supportedExtensions, keyMapper)) { - continue; + const includeIndex = findIndex(jsonOnlyIncludeRegexes, re => re.test(file)); + if (includeIndex !== -1) { + const key = keyMapper(file); + if (!literalFileMap.has(key) && !wildCardJsonFileMap.has(key)) { + wildCardJsonFileMap.set(key, file); + } } + continue; + } + // If we have already included a literal or wildcard path with a + // higher priority extension, we should skip this file. + // + // This handles cases where we may encounter both .ts and + // .d.ts (or .js if "allowJs" is enabled) in the same + // directory when they are compilation outputs. + if (hasFileWithHigherPriorityExtension(file, literalFileMap, wildcardFileMap, supportedExtensions, keyMapper)) { + continue; + } - // We may have included a wildcard path with a lower priority - // extension due to the user-defined order of entries in the - // "include" array. If there is a lower priority extension in the - // same directory, we should remove it. - removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper); + // We may have included a wildcard path with a lower priority + // extension due to the user-defined order of entries in the + // "include" array. If there is a lower priority extension in the + // same directory, we should remove it. + removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper); - const key = keyMapper(file); - if (!literalFileMap.has(key) && !wildcardFileMap.has(key)) { - wildcardFileMap.set(key, file); - } + const key = keyMapper(file); + if (!literalFileMap.has(key) && !wildcardFileMap.has(key)) { + wildcardFileMap.set(key, file); } } - - const literalFiles = arrayFrom(literalFileMap.values()); - const wildcardFiles = arrayFrom(wildcardFileMap.values()); - - return literalFiles.concat(wildcardFiles, arrayFrom(wildCardJsonFileMap.values())); } - /* @internal */ - export function isExcludedFile( - pathToCheck: string, - spec: ConfigFileSpecs, - basePath: string, - useCaseSensitiveFileNames: boolean, - currentDirectory: string - ): boolean { - const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs } = spec; - if (!length(validatedIncludeSpecs) || !length(validatedExcludeSpecs)) return false; + const literalFiles = arrayFrom(literalFileMap.values()); + const wildcardFiles = arrayFrom(wildcardFileMap.values()); - basePath = normalizePath(basePath); + return literalFiles.concat(wildcardFiles, arrayFrom(wildCardJsonFileMap.values())); +} - const keyMapper = createGetCanonicalFileName(useCaseSensitiveFileNames); - if (validatedFilesSpec) { - for (const fileName of validatedFilesSpec) { - if (keyMapper(getNormalizedAbsolutePath(fileName, basePath)) === pathToCheck) return false; - } - } +/** @internal */ +export function isExcludedFile( + pathToCheck: string, + spec: ConfigFileSpecs, + basePath: string, + useCaseSensitiveFileNames: boolean, + currentDirectory: string +): boolean { + const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs } = spec; + if (!length(validatedIncludeSpecs) || !length(validatedExcludeSpecs)) return false; - return matchesExcludeWorker(pathToCheck, validatedExcludeSpecs, useCaseSensitiveFileNames, currentDirectory, basePath); - } + basePath = normalizePath(basePath); - function invalidDotDotAfterRecursiveWildcard(s: string) { - // We used to use the regex /(^|\/)\*\*\/(.*\/)?\.\.($|\/)/ to check for this case, but - // in v8, that has polynomial performance because the recursive wildcard match - **/ - - // can be matched in many arbitrary positions when multiple are present, resulting - // in bad backtracking (and we don't care which is matched - just that some /.. segment - // comes after some **/ segment). - const wildcardIndex = startsWith(s, "**/") ? 0 : s.indexOf("/**/"); - if (wildcardIndex === -1) { - return false; + const keyMapper = createGetCanonicalFileName(useCaseSensitiveFileNames); + if (validatedFilesSpec) { + for (const fileName of validatedFilesSpec) { + if (keyMapper(getNormalizedAbsolutePath(fileName, basePath)) === pathToCheck) return false; } - const lastDotIndex = endsWith(s, "/..") ? s.length : s.lastIndexOf("/../"); - return lastDotIndex > wildcardIndex; } - /* @internal */ - export function matchesExclude( - pathToCheck: string, - excludeSpecs: readonly string[] | undefined, - useCaseSensitiveFileNames: boolean, - currentDirectory: string - ) { - return matchesExcludeWorker( - pathToCheck, - filter(excludeSpecs, spec => !invalidDotDotAfterRecursiveWildcard(spec)), - useCaseSensitiveFileNames, - currentDirectory - ); - } - - function matchesExcludeWorker( - pathToCheck: string, - excludeSpecs: readonly string[] | undefined, - useCaseSensitiveFileNames: boolean, - currentDirectory: string, - basePath?: string - ) { - const excludePattern = getRegularExpressionForWildcard(excludeSpecs, combinePaths(normalizePath(currentDirectory), basePath), "exclude"); - const excludeRegex = excludePattern && getRegexFromPattern(excludePattern, useCaseSensitiveFileNames); - if (!excludeRegex) return false; - if (excludeRegex.test(pathToCheck)) return true; - return !hasExtension(pathToCheck) && excludeRegex.test(ensureTrailingDirectorySeparator(pathToCheck)); - } - - function validateSpecs(specs: readonly string[], errors: Push, disallowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] { - return specs.filter(spec => { - if (!isString(spec)) return false; - const diag = specToDiagnostic(spec, disallowTrailingRecursion); - if (diag !== undefined) { - errors.push(createDiagnostic(...diag)); - } - return diag === undefined; - }); + return matchesExcludeWorker(pathToCheck, validatedExcludeSpecs, useCaseSensitiveFileNames, currentDirectory, basePath); +} - function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic { - const element = getTsConfigPropArrayElementValue(jsonSourceFile, specKey, spec); - return element ? - createDiagnosticForNodeInSourceFile(jsonSourceFile!, element, message, spec) : - createCompilerDiagnostic(message, spec); - } +function invalidDotDotAfterRecursiveWildcard(s: string) { + // We used to use the regex /(^|\/)\*\*\/(.*\/)?\.\.($|\/)/ to check for this case, but + // in v8, that has polynomial performance because the recursive wildcard match - **/ - + // can be matched in many arbitrary positions when multiple are present, resulting + // in bad backtracking (and we don't care which is matched - just that some /.. segment + // comes after some **/ segment). + const wildcardIndex = startsWith(s, "**/") ? 0 : s.indexOf("/**/"); + if (wildcardIndex === -1) { + return false; } + const lastDotIndex = endsWith(s, "/..") ? s.length : s.lastIndexOf("/../"); + return lastDotIndex > wildcardIndex; +} - function specToDiagnostic(spec: string, disallowTrailingRecursion?: boolean): [DiagnosticMessage, string] | undefined { - if (disallowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) { - return [Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec]; - } - else if (invalidDotDotAfterRecursiveWildcard(spec)) { - return [Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec]; +/** @internal */ +export function matchesExclude( + pathToCheck: string, + excludeSpecs: readonly string[] | undefined, + useCaseSensitiveFileNames: boolean, + currentDirectory: string +) { + return matchesExcludeWorker( + pathToCheck, + filter(excludeSpecs, spec => !invalidDotDotAfterRecursiveWildcard(spec)), + useCaseSensitiveFileNames, + currentDirectory + ); +} + +function matchesExcludeWorker( + pathToCheck: string, + excludeSpecs: readonly string[] | undefined, + useCaseSensitiveFileNames: boolean, + currentDirectory: string, + basePath?: string +) { + const excludePattern = getRegularExpressionForWildcard(excludeSpecs, combinePaths(normalizePath(currentDirectory), basePath), "exclude"); + const excludeRegex = excludePattern && getRegexFromPattern(excludePattern, useCaseSensitiveFileNames); + if (!excludeRegex) return false; + if (excludeRegex.test(pathToCheck)) return true; + return !hasExtension(pathToCheck) && excludeRegex.test(ensureTrailingDirectorySeparator(pathToCheck)); +} + +function validateSpecs(specs: readonly string[], errors: Push, disallowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] { + return specs.filter(spec => { + if (!isString(spec)) return false; + const diag = specToDiagnostic(spec, disallowTrailingRecursion); + if (diag !== undefined) { + errors.push(createDiagnostic(...diag)); } + return diag === undefined; + }); + + function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic { + const element = getTsConfigPropArrayElementValue(jsonSourceFile, specKey, spec); + return element ? + createDiagnosticForNodeInSourceFile(jsonSourceFile!, element, message, spec) : + createCompilerDiagnostic(message, spec); } +} - /** - * Gets directories in a set of include patterns that should be watched for changes. - */ - function getWildcardDirectories({ validatedIncludeSpecs: include, validatedExcludeSpecs: exclude }: ConfigFileSpecs, path: string, useCaseSensitiveFileNames: boolean): MapLike { - // We watch a directory recursively if it contains a wildcard anywhere in a directory segment - // of the pattern: - // - // /a/b/**/d - Watch /a/b recursively to catch changes to any d in any subfolder recursively - // /a/b/*/d - Watch /a/b recursively to catch any d in any immediate subfolder, even if a new subfolder is added - // /a/b - Watch /a/b recursively to catch changes to anything in any recursive subfoler - // - // We watch a directory without recursion if it contains a wildcard in the file segment of - // the pattern: - // - // /a/b/* - Watch /a/b directly to catch any new file - // /a/b/a?z - Watch /a/b directly to catch any new file matching a?z - const rawExcludeRegex = getRegularExpressionForWildcard(exclude, path, "exclude"); - const excludeRegex = rawExcludeRegex && new RegExp(rawExcludeRegex, useCaseSensitiveFileNames ? "" : "i"); - const wildcardDirectories: MapLike = {}; - if (include !== undefined) { - const recursiveKeys: string[] = []; - for (const file of include) { - const spec = normalizePath(combinePaths(path, file)); - if (excludeRegex && excludeRegex.test(spec)) { - continue; - } +function specToDiagnostic(spec: string, disallowTrailingRecursion?: boolean): [DiagnosticMessage, string] | undefined { + if (disallowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) { + return [Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec]; + } + else if (invalidDotDotAfterRecursiveWildcard(spec)) { + return [Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec]; + } +} - const match = getWildcardDirectoryFromSpec(spec, useCaseSensitiveFileNames); - if (match) { - const { key, flags } = match; - const existingFlags = wildcardDirectories[key]; - if (existingFlags === undefined || existingFlags < flags) { - wildcardDirectories[key] = flags; - if (flags === WatchDirectoryFlags.Recursive) { - recursiveKeys.push(key); - } +/** + * Gets directories in a set of include patterns that should be watched for changes. + */ +function getWildcardDirectories({ validatedIncludeSpecs: include, validatedExcludeSpecs: exclude }: ConfigFileSpecs, path: string, useCaseSensitiveFileNames: boolean): MapLike { + // We watch a directory recursively if it contains a wildcard anywhere in a directory segment + // of the pattern: + // + // /a/b/**/d - Watch /a/b recursively to catch changes to any d in any subfolder recursively + // /a/b/*/d - Watch /a/b recursively to catch any d in any immediate subfolder, even if a new subfolder is added + // /a/b - Watch /a/b recursively to catch changes to anything in any recursive subfoler + // + // We watch a directory without recursion if it contains a wildcard in the file segment of + // the pattern: + // + // /a/b/* - Watch /a/b directly to catch any new file + // /a/b/a?z - Watch /a/b directly to catch any new file matching a?z + const rawExcludeRegex = getRegularExpressionForWildcard(exclude, path, "exclude"); + const excludeRegex = rawExcludeRegex && new RegExp(rawExcludeRegex, useCaseSensitiveFileNames ? "" : "i"); + const wildcardDirectories: MapLike = {}; + if (include !== undefined) { + const recursiveKeys: string[] = []; + for (const file of include) { + const spec = normalizePath(combinePaths(path, file)); + if (excludeRegex && excludeRegex.test(spec)) { + continue; + } + + const match = getWildcardDirectoryFromSpec(spec, useCaseSensitiveFileNames); + if (match) { + const { key, flags } = match; + const existingFlags = wildcardDirectories[key]; + if (existingFlags === undefined || existingFlags < flags) { + wildcardDirectories[key] = flags; + if (flags === WatchDirectoryFlags.Recursive) { + recursiveKeys.push(key); } } } + } - // Remove any subpaths under an existing recursively watched directory. - for (const key in wildcardDirectories) { - if (hasProperty(wildcardDirectories, key)) { - for (const recursiveKey of recursiveKeys) { - if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { - delete wildcardDirectories[key]; - } + // Remove any subpaths under an existing recursively watched directory. + for (const key in wildcardDirectories) { + if (hasProperty(wildcardDirectories, key)) { + for (const recursiveKey of recursiveKeys) { + if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { + delete wildcardDirectories[key]; } } } } + } - return wildcardDirectories; - } - - function getWildcardDirectoryFromSpec(spec: string, useCaseSensitiveFileNames: boolean): { key: string, flags: WatchDirectoryFlags } | undefined { - const match = wildcardDirectoryPattern.exec(spec); - if (match) { - // We check this with a few `indexOf` calls because 3 `indexOf`/`lastIndexOf` calls is - // less algorithmically complex (roughly O(3n) worst-case) than the regex we used to use, - // \/[^/]*?[*?][^/]*\/ which was polynominal in v8, since arbitrary sequences of wildcard - // characters could match any of the central patterns, resulting in bad backtracking. - const questionWildcardIndex = spec.indexOf("?"); - const starWildcardIndex = spec.indexOf("*"); - const lastDirectorySeperatorIndex = spec.lastIndexOf(directorySeparator); - return { - key: useCaseSensitiveFileNames ? match[0] : toFileNameLowerCase(match[0]), - flags: (questionWildcardIndex !== -1 && questionWildcardIndex < lastDirectorySeperatorIndex) - || (starWildcardIndex !== -1 && starWildcardIndex < lastDirectorySeperatorIndex) - ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None - }; - } - if (isImplicitGlob(spec.substring(spec.lastIndexOf(directorySeparator) + 1))) { - return { - key: removeTrailingDirectorySeparator(useCaseSensitiveFileNames ? spec : toFileNameLowerCase(spec)), - flags: WatchDirectoryFlags.Recursive - }; - } - return undefined; + return wildcardDirectories; +} + +function getWildcardDirectoryFromSpec(spec: string, useCaseSensitiveFileNames: boolean): { key: string, flags: WatchDirectoryFlags } | undefined { + const match = wildcardDirectoryPattern.exec(spec); + if (match) { + // We check this with a few `indexOf` calls because 3 `indexOf`/`lastIndexOf` calls is + // less algorithmically complex (roughly O(3n) worst-case) than the regex we used to use, + // \/[^/]*?[*?][^/]*\/ which was polynominal in v8, since arbitrary sequences of wildcard + // characters could match any of the central patterns, resulting in bad backtracking. + const questionWildcardIndex = spec.indexOf("?"); + const starWildcardIndex = spec.indexOf("*"); + const lastDirectorySeperatorIndex = spec.lastIndexOf(directorySeparator); + return { + key: useCaseSensitiveFileNames ? match[0] : toFileNameLowerCase(match[0]), + flags: (questionWildcardIndex !== -1 && questionWildcardIndex < lastDirectorySeperatorIndex) + || (starWildcardIndex !== -1 && starWildcardIndex < lastDirectorySeperatorIndex) + ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None + }; + } + if (isImplicitGlob(spec.substring(spec.lastIndexOf(directorySeparator) + 1))) { + return { + key: removeTrailingDirectorySeparator(useCaseSensitiveFileNames ? spec : toFileNameLowerCase(spec)), + flags: WatchDirectoryFlags.Recursive + }; } + return undefined; +} - /** - * Determines whether a literal or wildcard file has already been included that has a higher - * extension priority. - * - * @param file The path to the file. - */ - function hasFileWithHigherPriorityExtension(file: string, literalFiles: ESMap, wildcardFiles: ESMap, extensions: readonly string[][], keyMapper: (value: string) => string) { - const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined); - if (!extensionGroup) { +/** + * Determines whether a literal or wildcard file has already been included that has a higher + * extension priority. + * + * @param file The path to the file. + */ +function hasFileWithHigherPriorityExtension(file: string, literalFiles: ESMap, wildcardFiles: ESMap, extensions: readonly string[][], keyMapper: (value: string) => string) { + const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined); + if (!extensionGroup) { + return false; + } + for (const ext of extensionGroup) { + if (fileExtensionIs(file, ext)) { return false; } - for (const ext of extensionGroup) { - if (fileExtensionIs(file, ext)) { - return false; - } - const higherPriorityPath = keyMapper(changeExtension(file, ext)); - if (literalFiles.has(higherPriorityPath) || wildcardFiles.has(higherPriorityPath)) { - if (ext === Extension.Dts && (fileExtensionIs(file, Extension.Js) || fileExtensionIs(file, Extension.Jsx))) { - // LEGACY BEHAVIOR: An off-by-one bug somewhere in the extension priority system for wildcard module loading allowed declaration - // files to be loaded alongside their js(x) counterparts. We regard this as generally undesirable, but retain the behavior to - // prevent breakage. - continue; - } - return true; + const higherPriorityPath = keyMapper(changeExtension(file, ext)); + if (literalFiles.has(higherPriorityPath) || wildcardFiles.has(higherPriorityPath)) { + if (ext === Extension.Dts && (fileExtensionIs(file, Extension.Js) || fileExtensionIs(file, Extension.Jsx))) { + // LEGACY BEHAVIOR: An off-by-one bug somewhere in the extension priority system for wildcard module loading allowed declaration + // files to be loaded alongside their js(x) counterparts. We regard this as generally undesirable, but retain the behavior to + // prevent breakage. + continue; } + return true; } - - return false; } - /** - * Removes files included via wildcard expansion with a lower extension priority that have - * already been included. - * - * @param file The path to the file. - */ - function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: ESMap, extensions: readonly string[][], keyMapper: (value: string) => string) { - const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined); - if (!extensionGroup) { + return false; +} + +/** + * Removes files included via wildcard expansion with a lower extension priority that have + * already been included. + * + * @param file The path to the file. + */ +function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: ESMap, extensions: readonly string[][], keyMapper: (value: string) => string) { + const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined); + if (!extensionGroup) { + return; + } + for (let i = extensionGroup.length - 1; i >= 0; i--) { + const ext = extensionGroup[i]; + if (fileExtensionIs(file, ext)) { return; } - for (let i = extensionGroup.length - 1; i >= 0; i--) { - const ext = extensionGroup[i]; - if (fileExtensionIs(file, ext)) { - return; + const lowerPriorityPath = keyMapper(changeExtension(file, ext)); + wildcardFiles.delete(lowerPriorityPath); + } +} + +/** + * Produces a cleaned version of compiler options with personally identifying info (aka, paths) removed. + * Also converts enum values back to strings. + * + * @internal + */ +export function convertCompilerOptionsForTelemetry(opts: CompilerOptions): CompilerOptions { + const out: CompilerOptions = {}; + for (const key in opts) { + if (hasProperty(opts, key)) { + const type = getOptionFromName(key); + if (type !== undefined) { // Ignore unknown options + out[key] = getOptionValueWithEmptyStrings(opts[key], type); } - const lowerPriorityPath = keyMapper(changeExtension(file, ext)); - wildcardFiles.delete(lowerPriorityPath); } } + return out; +} - /** - * Produces a cleaned version of compiler options with personally identifying info (aka, paths) removed. - * Also converts enum values back to strings. - */ - /* @internal */ - export function convertCompilerOptionsForTelemetry(opts: CompilerOptions): CompilerOptions { - const out: CompilerOptions = {}; - for (const key in opts) { - if (hasProperty(opts, key)) { - const type = getOptionFromName(key); - if (type !== undefined) { // Ignore unknown options - out[key] = getOptionValueWithEmptyStrings(opts[key], type); +function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption): {} { + switch (option.type) { + case "object": // "paths". Can't get any useful information from the value since we blank out strings, so just return "". + return ""; + case "string": // Could be any arbitrary string -- use empty string instead. + return ""; + case "number": // Allow numbers, but be sure to check it's actually a number. + return typeof value === "number" ? value : ""; + case "boolean": + return typeof value === "boolean" ? value : ""; + case "list": + const elementType = option.element; + return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : ""; + default: + return forEachEntry(option.type, (optionEnumValue, optionStringValue) => { + if (optionEnumValue === value) { + return optionStringValue; } - } - } - return out; - } - - function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption): {} { - switch (option.type) { - case "object": // "paths". Can't get any useful information from the value since we blank out strings, so just return "". - return ""; - case "string": // Could be any arbitrary string -- use empty string instead. - return ""; - case "number": // Allow numbers, but be sure to check it's actually a number. - return typeof value === "number" ? value : ""; - case "boolean": - return typeof value === "boolean" ? value : ""; - case "list": - const elementType = option.element; - return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : ""; - default: - return forEachEntry(option.type, (optionEnumValue, optionStringValue) => { - if (optionEnumValue === value) { - return optionStringValue; - } - })!; // TODO: GH#18217 - } + })!; // TODO: GH#18217 } +} - function getDefaultValueForOption(option: CommandLineOption) { - switch (option.type) { - case "number": - return 1; - case "boolean": - return true; - case "string": - const defaultValue = option.defaultValueDescription; - return option.isFilePath ? `./${defaultValue && typeof defaultValue === "string" ? defaultValue : ""}` : ""; - case "list": - return []; - case "object": - return {}; - default: - const iterResult = option.type.keys().next(); - if (!iterResult.done) return iterResult.value; - return Debug.fail("Expected 'option.type' to have entries."); - } +function getDefaultValueForOption(option: CommandLineOption) { + switch (option.type) { + case "number": + return 1; + case "boolean": + return true; + case "string": + const defaultValue = option.defaultValueDescription; + return option.isFilePath ? `./${defaultValue && typeof defaultValue === "string" ? defaultValue : ""}` : ""; + case "list": + return []; + case "object": + return {}; + default: + const iterResult = option.type.keys().next(); + if (!iterResult.done) return iterResult.value; + return Debug.fail("Expected 'option.type' to have entries."); } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 870892d864031..93e5aaef932de 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1,368 +1,489 @@ -/* @internal */ -namespace ts { - export function getIterator | ReadonlyESMap | undefined>(iterable: I): Iterator< - I extends ReadonlyESMap ? [K, V] : - I extends ReadonlySet ? T : - I extends readonly (infer T)[] ? T : - I extends undefined ? undefined : - never>; - export function getIterator(iterable: ReadonlyESMap): Iterator<[K, V]>; - export function getIterator(iterable: ReadonlyESMap | undefined): Iterator<[K, V]> | undefined; - export function getIterator(iterable: readonly T[] | ReadonlySet): Iterator; - export function getIterator(iterable: readonly T[] | ReadonlySet | undefined): Iterator | undefined; - export function getIterator(iterable: readonly any[] | ReadonlySet | ReadonlyESMap | undefined): Iterator | undefined { - if (iterable) { - if (isArray(iterable)) return arrayIterator(iterable); - if (iterable instanceof Map) return iterable.entries(); - if (iterable instanceof Set) return iterable.values(); - throw new Error("Iteration not supported."); - } - } - - export const emptyArray: never[] = [] as never[]; - export const emptyMap: ReadonlyESMap = new Map(); - export const emptySet: ReadonlySet = new Set(); - - export function length(array: readonly any[] | undefined): number { - return array ? array.length : 0; - } - - /** - * Iterates through 'array' by index and performs the callback on each element of array until the callback - * returns a truthy value, then returns that value. - * If no such value is found, the callback is applied to each element of array and undefined is returned. - */ - export function forEach(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { - if (array) { - for (let i = 0; i < array.length; i++) { - const result = callback(array[i], i); - if (result) { - return result; - } - } - } - return undefined; +import { + __String, CharacterCodes, Comparer, Comparison, Debug, EqualityComparer, ESMap, isWhiteSpaceLike, Iterator, Map, + MapLike, Push, Queue, ReadonlyESMap, ReadonlySet, Set, SortedArray, SortedReadonlyArray, TextSpan, + UnderscoreEscapedMap, +} from "./_namespaces/ts"; + +/** @internal */ +export function getIterator | ReadonlyESMap | undefined>(iterable: I): Iterator< + I extends ReadonlyESMap ? [K, V] : + I extends ReadonlySet ? T : + I extends readonly (infer T)[] ? T : + I extends undefined ? undefined : + never>; +/** @internal */ +export function getIterator(iterable: ReadonlyESMap): Iterator<[K, V]>; +/** @internal */ +export function getIterator(iterable: ReadonlyESMap | undefined): Iterator<[K, V]> | undefined; +/** @internal */ +export function getIterator(iterable: readonly T[] | ReadonlySet): Iterator; +/** @internal */ +export function getIterator(iterable: readonly T[] | ReadonlySet | undefined): Iterator | undefined; +/** @internal */ +export function getIterator(iterable: readonly any[] | ReadonlySet | ReadonlyESMap | undefined): Iterator | undefined { + if (iterable) { + if (isArray(iterable)) return arrayIterator(iterable); + if (iterable instanceof Map) return iterable.entries(); + if (iterable instanceof Set) return iterable.values(); + throw new Error("Iteration not supported."); } +} - /** - * Like `forEach`, but iterates in reverse order. - */ - export function forEachRight(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { - if (array) { - for (let i = array.length - 1; i >= 0; i--) { - const result = callback(array[i], i); - if (result) { - return result; - } - } - } - return undefined; - } +/** @internal */ +export const emptyArray: never[] = [] as never[]; +/** @internal */ +export const emptyMap: ReadonlyESMap = new Map(); +/** @internal */ +export const emptySet: ReadonlySet = new Set(); - /** Like `forEach`, but suitable for use with numbers and strings (which may be falsy). */ - export function firstDefined(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { - if (array === undefined) { - return undefined; - } +/** @internal */ +export function length(array: readonly any[] | undefined): number { + return array ? array.length : 0; +} +/** + * Iterates through 'array' by index and performs the callback on each element of array until the callback + * returns a truthy value, then returns that value. + * If no such value is found, the callback is applied to each element of array and undefined is returned. + * + * @internal + */ +export function forEach(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { + if (array) { for (let i = 0; i < array.length; i++) { const result = callback(array[i], i); - if (result !== undefined) { + if (result) { return result; } } - return undefined; } + return undefined; +} - export function firstDefinedIterator(iter: Iterator, callback: (element: T) => U | undefined): U | undefined { - while (true) { - const iterResult = iter.next(); - if (iterResult.done) { - return undefined; - } - const result = callback(iterResult.value); - if (result !== undefined) { +/** + * Like `forEach`, but iterates in reverse order. + * + * @internal + */ +export function forEachRight(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { + if (array) { + for (let i = array.length - 1; i >= 0; i--) { + const result = callback(array[i], i); + if (result) { return result; } } } + return undefined; +} - export function reduceLeftIterator(iterator: Iterator | undefined, f: (memo: U, value: T, i: number) => U, initial: U): U { - let result = initial; - if (iterator) { - for (let step = iterator.next(), pos = 0; !step.done; step = iterator.next(), pos++) { - result = f(result, step.value, pos); - } - } - return result; +/** + * Like `forEach`, but suitable for use with numbers and strings (which may be falsy). + * + * @internal + */ +export function firstDefined(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { + if (array === undefined) { + return undefined; } - export function zipWith(arrayA: readonly T[], arrayB: readonly U[], callback: (a: T, b: U, index: number) => V): V[] { - const result: V[] = []; - Debug.assertEqual(arrayA.length, arrayB.length); - for (let i = 0; i < arrayA.length; i++) { - result.push(callback(arrayA[i], arrayB[i], i)); + for (let i = 0; i < array.length; i++) { + const result = callback(array[i], i); + if (result !== undefined) { + return result; } - return result; } + return undefined; +} - export function zipToIterator(arrayA: readonly T[], arrayB: readonly U[]): Iterator<[T, U]> { - Debug.assertEqual(arrayA.length, arrayB.length); - let i = 0; - return { - next() { - if (i === arrayA.length) { - return { value: undefined as never, done: true }; - } - i++; - return { value: [arrayA[i - 1], arrayB[i - 1]] as [T, U], done: false }; - } - }; +/** @internal */ +export function firstDefinedIterator(iter: Iterator, callback: (element: T) => U | undefined): U | undefined { + while (true) { + const iterResult = iter.next(); + if (iterResult.done) { + return undefined; + } + const result = callback(iterResult.value); + if (result !== undefined) { + return result; + } } +} - export function zipToMap(keys: readonly K[], values: readonly V[]): ESMap { - Debug.assert(keys.length === values.length); - const map = new Map(); - for (let i = 0; i < keys.length; ++i) { - map.set(keys[i], values[i]); +/** @internal */ +export function reduceLeftIterator(iterator: Iterator | undefined, f: (memo: U, value: T, i: number) => U, initial: U): U { + let result = initial; + if (iterator) { + for (let step = iterator.next(), pos = 0; !step.done; step = iterator.next(), pos++) { + result = f(result, step.value, pos); } - return map; } + return result; +} - /** - * Creates a new array with `element` interspersed in between each element of `input` - * if there is more than 1 value in `input`. Otherwise, returns the existing array. - */ - export function intersperse(input: T[], element: T): T[] { - if (input.length <= 1) { - return input; - } - const result: T[] = []; - for (let i = 0, n = input.length; i < n; i++) { - if (i) result.push(element); - result.push(input[i]); - } - return result; +/** @internal */ +export function zipWith(arrayA: readonly T[], arrayB: readonly U[], callback: (a: T, b: U, index: number) => V): V[] { + const result: V[] = []; + Debug.assertEqual(arrayA.length, arrayB.length); + for (let i = 0; i < arrayA.length; i++) { + result.push(callback(arrayA[i], arrayB[i], i)); } + return result; +} - /** - * Iterates through `array` by index and performs the callback on each element of array until the callback - * returns a falsey value, then returns false. - * If no such value is found, the callback is applied to each element of array and `true` is returned. - */ - export function every(array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean { - if (array) { - for (let i = 0; i < array.length; i++) { - if (!callback(array[i], i)) { - return false; - } +/** @internal */ +export function zipToIterator(arrayA: readonly T[], arrayB: readonly U[]): Iterator<[T, U]> { + Debug.assertEqual(arrayA.length, arrayB.length); + let i = 0; + return { + next() { + if (i === arrayA.length) { + return { value: undefined as never, done: true }; } + i++; + return { value: [arrayA[i - 1], arrayB[i - 1]] as [T, U], done: false }; } + }; +} - return true; +/** @internal */ +export function zipToMap(keys: readonly K[], values: readonly V[]): ESMap { + Debug.assert(keys.length === values.length); + const map = new Map(); + for (let i = 0; i < keys.length; ++i) { + map.set(keys[i], values[i]); } + return map; +} + +/** + * Creates a new array with `element` interspersed in between each element of `input` + * if there is more than 1 value in `input`. Otherwise, returns the existing array. + * + * @internal + */ +export function intersperse(input: T[], element: T): T[] { + if (input.length <= 1) { + return input; + } + const result: T[] = []; + for (let i = 0, n = input.length; i < n; i++) { + if (i) result.push(element); + result.push(input[i]); + } + return result; +} - /** Works like Array.prototype.find, returning `undefined` if no element satisfying the predicate is found. */ - export function find(array: readonly T[] | undefined, predicate: (element: T, index: number) => element is U, startIndex?: number): U | undefined; - export function find(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): T | undefined; - export function find(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): T | undefined { - if (array === undefined) return undefined; - for (let i = startIndex ?? 0; i < array.length; i++) { - const value = array[i]; - if (predicate(value, i)) { - return value; +/** + * Iterates through `array` by index and performs the callback on each element of array until the callback + * returns a falsey value, then returns false. + * If no such value is found, the callback is applied to each element of array and `true` is returned. + * + * @internal + */ +export function every(array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean { + if (array) { + for (let i = 0; i < array.length; i++) { + if (!callback(array[i], i)) { + return false; } } - return undefined; } - export function findLast(array: readonly T[] | undefined, predicate: (element: T, index: number) => element is U, startIndex?: number): U | undefined; - export function findLast(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): T | undefined; - export function findLast(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): T | undefined { - if (array === undefined) return undefined; - for (let i = startIndex ?? array.length - 1; i >= 0; i--) { - const value = array[i]; - if (predicate(value, i)) { - return value; - } + return true; +} + +/** + * Works like Array.prototype.find, returning `undefined` if no element satisfying the predicate is found. + * + * @internal + */ +export function find(array: readonly T[] | undefined, predicate: (element: T, index: number) => element is U, startIndex?: number): U | undefined; +/** @internal */ +export function find(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): T | undefined; +/** @internal */ +export function find(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): T | undefined { + if (array === undefined) return undefined; + for (let i = startIndex ?? 0; i < array.length; i++) { + const value = array[i]; + if (predicate(value, i)) { + return value; } - return undefined; } + return undefined; +} - /** Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. */ - export function findIndex(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): number { - if (array === undefined) return -1; - for (let i = startIndex ?? 0; i < array.length; i++) { - if (predicate(array[i], i)) { - return i; - } +/** @internal */ +export function findLast(array: readonly T[] | undefined, predicate: (element: T, index: number) => element is U, startIndex?: number): U | undefined; +/** @internal */ +export function findLast(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): T | undefined; +/** @internal */ +export function findLast(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): T | undefined { + if (array === undefined) return undefined; + for (let i = startIndex ?? array.length - 1; i >= 0; i--) { + const value = array[i]; + if (predicate(value, i)) { + return value; } - return -1; } + return undefined; +} - export function findLastIndex(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): number { - if (array === undefined) return -1; - for (let i = startIndex ?? array.length - 1; i >= 0; i--) { - if (predicate(array[i], i)) { - return i; - } +/** + * Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. + * + * @internal + */ +export function findIndex(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): number { + if (array === undefined) return -1; + for (let i = startIndex ?? 0; i < array.length; i++) { + if (predicate(array[i], i)) { + return i; } - return -1; } + return -1; +} - /** - * Returns the first truthy result of `callback`, or else fails. - * This is like `forEach`, but never returns undefined. - */ - export function findMap(array: readonly T[], callback: (element: T, index: number) => U | undefined): U { - for (let i = 0; i < array.length; i++) { - const result = callback(array[i], i); - if (result) { - return result; - } +/** @internal */ +export function findLastIndex(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): number { + if (array === undefined) return -1; + for (let i = startIndex ?? array.length - 1; i >= 0; i--) { + if (predicate(array[i], i)) { + return i; } - return Debug.fail(); } + return -1; +} - export function contains(array: readonly T[] | undefined, value: T, equalityComparer: EqualityComparer = equateValues): boolean { - if (array) { - for (const v of array) { - if (equalityComparer(v, value)) { - return true; - } +/** + * Returns the first truthy result of `callback`, or else fails. + * This is like `forEach`, but never returns undefined. + * + * @internal + */ +export function findMap(array: readonly T[], callback: (element: T, index: number) => U | undefined): U { + for (let i = 0; i < array.length; i++) { + const result = callback(array[i], i); + if (result) { + return result; + } + } + return Debug.fail(); +} + +/** @internal */ +export function contains(array: readonly T[] | undefined, value: T, equalityComparer: EqualityComparer = equateValues): boolean { + if (array) { + for (const v of array) { + if (equalityComparer(v, value)) { + return true; } } - return false; } + return false; +} + +/** @internal */ +export function arraysEqual(a: readonly T[], b: readonly T[], equalityComparer: EqualityComparer = equateValues): boolean { + return a.length === b.length && a.every((x, i) => equalityComparer(x, b[i])); +} - export function arraysEqual(a: readonly T[], b: readonly T[], equalityComparer: EqualityComparer = equateValues): boolean { - return a.length === b.length && a.every((x, i) => equalityComparer(x, b[i])); +/** @internal */ +export function indexOfAnyCharCode(text: string, charCodes: readonly number[], start?: number): number { + for (let i = start || 0; i < text.length; i++) { + if (contains(charCodes, text.charCodeAt(i))) { + return i; + } } + return -1; +} - export function indexOfAnyCharCode(text: string, charCodes: readonly number[], start?: number): number { - for (let i = start || 0; i < text.length; i++) { - if (contains(charCodes, text.charCodeAt(i))) { - return i; +/** @internal */ +export function countWhere(array: readonly T[] | undefined, predicate: (x: T, i: number) => boolean): number { + let count = 0; + if (array) { + for (let i = 0; i < array.length; i++) { + const v = array[i]; + if (predicate(v, i)) { + count++; } } - return -1; } + return count; +} - export function countWhere(array: readonly T[] | undefined, predicate: (x: T, i: number) => boolean): number { - let count = 0; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = array[i]; - if (predicate(v, i)) { - count++; +/** + * Filters an array by a predicate function. Returns the same array instance if the predicate is + * true for all elements, otherwise returns a new array instance containing the filtered subset. + * + * @internal + */ +export function filter(array: T[], f: (x: T) => x is U): U[]; +/** @internal */ +export function filter(array: T[], f: (x: T) => boolean): T[]; +/** @internal */ +export function filter(array: readonly T[], f: (x: T) => x is U): readonly U[]; +/** @internal */ +export function filter(array: readonly T[], f: (x: T) => boolean): readonly T[]; +/** @internal */ +export function filter(array: T[] | undefined, f: (x: T) => x is U): U[] | undefined; +/** @internal */ +export function filter(array: T[] | undefined, f: (x: T) => boolean): T[] | undefined; +/** @internal */ +export function filter(array: readonly T[] | undefined, f: (x: T) => x is U): readonly U[] | undefined; +/** @internal */ +export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined; +/** @internal */ +export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined { + if (array) { + const len = array.length; + let i = 0; + while (i < len && f(array[i])) i++; + if (i < len) { + const result = array.slice(0, i); + i++; + while (i < len) { + const item = array[i]; + if (f(item)) { + result.push(item); } + i++; } + return result; } - return count; } + return array; +} - /** - * Filters an array by a predicate function. Returns the same array instance if the predicate is - * true for all elements, otherwise returns a new array instance containing the filtered subset. - */ - export function filter(array: T[], f: (x: T) => x is U): U[]; - export function filter(array: T[], f: (x: T) => boolean): T[]; - export function filter(array: readonly T[], f: (x: T) => x is U): readonly U[]; - export function filter(array: readonly T[], f: (x: T) => boolean): readonly T[]; - export function filter(array: T[] | undefined, f: (x: T) => x is U): U[] | undefined; - export function filter(array: T[] | undefined, f: (x: T) => boolean): T[] | undefined; - export function filter(array: readonly T[] | undefined, f: (x: T) => x is U): readonly U[] | undefined; - export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined; - export function filter(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined { - if (array) { - const len = array.length; - let i = 0; - while (i < len && f(array[i])) i++; - if (i < len) { - const result = array.slice(0, i); - i++; - while (i < len) { - const item = array[i]; - if (f(item)) { - result.push(item); - } - i++; - } - return result; - } +/** @internal */ +export function filterMutate(array: T[], f: (x: T, i: number, array: T[]) => boolean): void { + let outIndex = 0; + for (let i = 0; i < array.length; i++) { + if (f(array[i], i, array)) { + array[outIndex] = array[i]; + outIndex++; } - return array; } + array.length = outIndex; +} - export function filterMutate(array: T[], f: (x: T, i: number, array: T[]) => boolean): void { - let outIndex = 0; +/** @internal */ +export function clear(array: unknown[]): void { + array.length = 0; +} + +/** @internal */ +export function map(array: readonly T[], f: (x: T, i: number) => U): U[]; +/** @internal */ +export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined; +/** @internal */ +export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined { + let result: U[] | undefined; + if (array) { + result = []; for (let i = 0; i < array.length; i++) { - if (f(array[i], i, array)) { - array[outIndex] = array[i]; - outIndex++; - } + result.push(f(array[i], i)); } - array.length = outIndex; } + return result; +} - export function clear(array: unknown[]): void { - array.length = 0; - } - export function map(array: readonly T[], f: (x: T, i: number) => U): U[]; - export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined; - export function map(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined { - let result: U[] | undefined; - if (array) { - result = []; - for (let i = 0; i < array.length; i++) { - result.push(f(array[i], i)); +/** @internal */ +export function mapIterator(iter: Iterator, mapFn: (x: T) => U): Iterator { + return { + next() { + const iterRes = iter.next(); + return iterRes.done ? iterRes as { done: true, value: never } : { value: mapFn(iterRes.value), done: false }; + } + }; +} + +/** + * Maps from T to T and avoids allocation if all elements map to themselves + * + * @internal */ +export function sameMap(array: T[], f: (x: T, i: number) => T): T[]; +/** @internal */ +export function sameMap(array: readonly T[], f: (x: T, i: number) => T): readonly T[]; +/** @internal */ +export function sameMap(array: T[] | undefined, f: (x: T, i: number) => T): T[] | undefined; +/** @internal */ +export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined; +/** @internal */ +export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined { + if (array) { + for (let i = 0; i < array.length; i++) { + const item = array[i]; + const mapped = f(item, i); + if (item !== mapped) { + const result = array.slice(0, i); + result.push(mapped); + for (i++; i < array.length; i++) { + result.push(f(array[i], i)); + } + return result; } } - return result; } + return array; +} - - export function mapIterator(iter: Iterator, mapFn: (x: T) => U): Iterator { - return { - next() { - const iterRes = iter.next(); - return iterRes.done ? iterRes as { done: true, value: never } : { value: mapFn(iterRes.value), done: false }; +/** + * Flattens an array containing a mix of array or non-array elements. + * + * @param array The array to flatten. + * + * @internal + */ +export function flatten(array: T[][] | readonly (T | readonly T[] | undefined)[]): T[] { + const result = []; + for (const v of array) { + if (v) { + if (isArray(v)) { + addRange(result, v); } - }; + else { + result.push(v); + } + } } + return result; +} - // Maps from T to T and avoids allocation if all elements map to themselves - export function sameMap(array: T[], f: (x: T, i: number) => T): T[]; - export function sameMap(array: readonly T[], f: (x: T, i: number) => T): readonly T[]; - export function sameMap(array: T[] | undefined, f: (x: T, i: number) => T): T[] | undefined; - export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined; - export function sameMap(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined { - if (array) { - for (let i = 0; i < array.length; i++) { - const item = array[i]; - const mapped = f(item, i); - if (item !== mapped) { - const result = array.slice(0, i); - result.push(mapped); - for (i++; i < array.length; i++) { - result.push(f(array[i], i)); - } - return result; + +/** + * Maps an array. If the mapped value is an array, it is spread into the result. + * + * @param array The array to map. + * @param mapfn The callback used to map the result into one or more values. + * + * @internal + */ +export function flatMap(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): readonly U[] { + let result: U[] | undefined; + if (array) { + for (let i = 0; i < array.length; i++) { + const v = mapfn(array[i], i); + if (v) { + if (isArray(v)) { + result = addRange(result, v); + } + else { + result = append(result, v); } } } - return array; } + return result || emptyArray; +} - /** - * Flattens an array containing a mix of array or non-array elements. - * - * @param array The array to flatten. - */ - export function flatten(array: T[][] | readonly (T | readonly T[] | undefined)[]): T[] { - const result = []; - for (const v of array) { +/** @internal */ +export function flatMapToMutable(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): U[] { + const result: U[] = []; + if (array) { + for (let i = 0; i < array.length; i++) { + const v = mapfn(array[i], i); if (v) { if (isArray(v)) { addRange(result, v); @@ -372,2164 +493,2440 @@ namespace ts { } } } - return result; } + return result; +} - /** - * Maps an array. If the mapped value is an array, it is spread into the result. - * - * @param array The array to map. - * @param mapfn The callback used to map the result into one or more values. - */ - export function flatMap(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): readonly U[] { - let result: U[] | undefined; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = mapfn(array[i], i); - if (v) { - if (isArray(v)) { - result = addRange(result, v); - } - else { - result = append(result, v); - } +/** @internal */ +export function flatMapIterator(iter: Iterator, mapfn: (x: T) => readonly U[] | Iterator | undefined): Iterator { + const first = iter.next(); + if (first.done) { + return emptyIterator; + } + let currentIter = getIterator(first.value); + return { + next() { + while (true) { + const currentRes = currentIter.next(); + if (!currentRes.done) { + return currentRes; } - } - } - return result || emptyArray; - } - - export function flatMapToMutable(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): U[] { - const result: U[] = []; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = mapfn(array[i], i); - if (v) { - if (isArray(v)) { - addRange(result, v); - } - else { - result.push(v); - } + const iterRes = iter.next(); + if (iterRes.done) { + return iterRes as { done: true, value: never }; } + currentIter = getIterator(iterRes.value); } - } - return result; + }, + }; + + function getIterator(x: T): Iterator { + const res = mapfn(x); + return res === undefined ? emptyIterator : isArray(res) ? arrayIterator(res) : res; } +} - export function flatMapIterator(iter: Iterator, mapfn: (x: T) => readonly U[] | Iterator | undefined): Iterator { - const first = iter.next(); - if (first.done) { - return emptyIterator; - } - let currentIter = getIterator(first.value); - return { - next() { - while (true) { - const currentRes = currentIter.next(); - if (!currentRes.done) { - return currentRes; - } - const iterRes = iter.next(); - if (iterRes.done) { - return iterRes as { done: true, value: never }; - } - currentIter = getIterator(iterRes.value); - } - }, - }; - function getIterator(x: T): Iterator { - const res = mapfn(x); - return res === undefined ? emptyIterator : isArray(res) ? arrayIterator(res) : res; +/** + * Maps an array. If the mapped value is an array, it is spread into the result. + * Avoids allocation if all elements map to themselves. + * + * @param array The array to map. + * @param mapfn The callback used to map the result into one or more values. + * + * @internal + */ +export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | readonly T[]): T[]; +/** @internal */ +export function sameFlatMap(array: readonly T[], mapfn: (x: T, i: number) => T | readonly T[]): readonly T[]; +/** @internal */ +export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | T[]): T[] { + let result: T[] | undefined; + if (array) { + for (let i = 0; i < array.length; i++) { + const item = array[i]; + const mapped = mapfn(item, i); + if (result || item !== mapped || isArray(mapped)) { + if (!result) { + result = array.slice(0, i); + } + if (isArray(mapped)) { + addRange(result, mapped); + } + else { + result.push(mapped); + } + } } } + return result || array; +} - /** - * Maps an array. If the mapped value is an array, it is spread into the result. - * Avoids allocation if all elements map to themselves. - * - * @param array The array to map. - * @param mapfn The callback used to map the result into one or more values. - */ - export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | readonly T[]): T[]; - export function sameFlatMap(array: readonly T[], mapfn: (x: T, i: number) => T | readonly T[]): readonly T[]; - export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | T[]): T[] { - let result: T[] | undefined; - if (array) { - for (let i = 0; i < array.length; i++) { - const item = array[i]; - const mapped = mapfn(item, i); - if (result || item !== mapped || isArray(mapped)) { - if (!result) { - result = array.slice(0, i); - } - if (isArray(mapped)) { - addRange(result, mapped); - } - else { - result.push(mapped); - } - } - } +/** @internal */ +export function mapAllOrFail(array: readonly T[], mapFn: (x: T, i: number) => U | undefined): U[] | undefined { + const result: U[] = []; + for (let i = 0; i < array.length; i++) { + const mapped = mapFn(array[i], i); + if (mapped === undefined) { + return undefined; } - return result || array; + result.push(mapped); } + return result; +} - export function mapAllOrFail(array: readonly T[], mapFn: (x: T, i: number) => U | undefined): U[] | undefined { - const result: U[] = []; +/** @internal */ +export function mapDefined(array: readonly T[] | undefined, mapFn: (x: T, i: number) => U | undefined): U[] { + const result: U[] = []; + if (array) { for (let i = 0; i < array.length; i++) { const mapped = mapFn(array[i], i); - if (mapped === undefined) { - return undefined; + if (mapped !== undefined) { + result.push(mapped); } - result.push(mapped); } - return result; } + return result; +} - export function mapDefined(array: readonly T[] | undefined, mapFn: (x: T, i: number) => U | undefined): U[] { - const result: U[] = []; - if (array) { - for (let i = 0; i < array.length; i++) { - const mapped = mapFn(array[i], i); - if (mapped !== undefined) { - result.push(mapped); +/** @internal */ +export function mapDefinedIterator(iter: Iterator, mapFn: (x: T) => U | undefined): Iterator { + return { + next() { + while (true) { + const res = iter.next(); + if (res.done) { + return res as { done: true, value: never }; + } + const value = mapFn(res.value); + if (value !== undefined) { + return { value, done: false }; } } } - return result; - } + }; +} - export function mapDefinedIterator(iter: Iterator, mapFn: (x: T) => U | undefined): Iterator { - return { - next() { - while (true) { - const res = iter.next(); - if (res.done) { - return res as { done: true, value: never }; - } - const value = mapFn(res.value); - if (value !== undefined) { - return { value, done: false }; - } - } - } - }; +/** @internal */ +export function mapDefinedEntries(map: ReadonlyESMap, f: (key: K1, value: V1) => readonly [K2, V2] | undefined): ESMap; +/** @internal */ +export function mapDefinedEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2 | undefined, V2 | undefined] | undefined): ESMap | undefined; +/** @internal */ +export function mapDefinedEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2 | undefined, V2 | undefined] | undefined): ESMap | undefined { + if (!map) { + return undefined; } - export function mapDefinedEntries(map: ReadonlyESMap, f: (key: K1, value: V1) => readonly [K2, V2] | undefined): ESMap; - export function mapDefinedEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2 | undefined, V2 | undefined] | undefined): ESMap | undefined; - export function mapDefinedEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2 | undefined, V2 | undefined] | undefined): ESMap | undefined { - if (!map) { - return undefined; + const result = new Map(); + map.forEach((value, key) => { + const entry = f(key, value); + if (entry !== undefined) { + const [newKey, newValue] = entry; + if (newKey !== undefined && newValue !== undefined) { + result.set(newKey, newValue); + } } + }); - const result = new Map(); - map.forEach((value, key) => { - const entry = f(key, value); - if (entry !== undefined) { - const [newKey, newValue] = entry; - if (newKey !== undefined && newValue !== undefined) { - result.set(newKey, newValue); - } + return result; +} + +/** @internal */ +export function mapDefinedValues(set: ReadonlySet, f: (value: V1) => V2 | undefined): Set; +/** @internal */ +export function mapDefinedValues(set: ReadonlySet | undefined, f: (value: V1) => V2 | undefined): Set | undefined; +/** @internal */ +export function mapDefinedValues(set: ReadonlySet | undefined, f: (value: V1) => V2 | undefined): Set | undefined { + if (set) { + const result = new Set(); + set.forEach(value => { + const newValue = f(value); + if (newValue !== undefined) { + result.add(newValue); } }); - return result; } +} - export function mapDefinedValues(set: ReadonlySet, f: (value: V1) => V2 | undefined): Set; - export function mapDefinedValues(set: ReadonlySet | undefined, f: (value: V1) => V2 | undefined): Set | undefined; - export function mapDefinedValues(set: ReadonlySet | undefined, f: (value: V1) => V2 | undefined): Set | undefined { - if (set) { - const result = new Set(); - set.forEach(value => { - const newValue = f(value); - if (newValue !== undefined) { - result.add(newValue); - } - }); - return result; - } +/** @internal */ +export function getOrUpdate(map: ESMap, key: K, callback: () => V) { + if (map.has(key)) { + return map.get(key)!; } + const value = callback(); + map.set(key, value); + return value; +} - export function getOrUpdate(map: ESMap, key: K, callback: () => V) { - if (map.has(key)) { - return map.get(key)!; - } - const value = callback(); - map.set(key, value); - return value; +/** @internal */ +export function tryAddToSet(set: Set, value: T) { + if (!set.has(value)) { + set.add(value); + return true; } + return false; +} - export function tryAddToSet(set: Set, value: T) { - if (!set.has(value)) { - set.add(value); - return true; +/** @internal */ +export const emptyIterator: Iterator = { next: () => ({ value: undefined as never, done: true }) }; + +/** @internal */ +export function singleIterator(value: T): Iterator { + let done = false; + return { + next() { + const wasDone = done; + done = true; + return wasDone ? { value: undefined as never, done: true } : { value, done: false }; } - return false; - } + }; +} - export const emptyIterator: Iterator = { next: () => ({ value: undefined as never, done: true }) }; +/** + * Maps contiguous spans of values with the same key. + * + * @param array The array to map. + * @param keyfn A callback used to select the key for an element. + * @param mapfn A callback used to map a contiguous chunk of values to a single value. + * + * @internal + */ +export function spanMap(array: readonly T[], keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[]; +/** @internal */ +export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined; +/** @internal */ +export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined { + let result: U[] | undefined; + if (array) { + result = []; + const len = array.length; + let previousKey: K | undefined; + let key: K | undefined; + let start = 0; + let pos = 0; + while (start < len) { + while (pos < len) { + const value = array[pos]; + key = keyfn(value, pos); + if (pos === 0) { + previousKey = key; + } + else if (key !== previousKey) { + break; + } - export function singleIterator(value: T): Iterator { - let done = false; - return { - next() { - const wasDone = done; - done = true; - return wasDone ? { value: undefined as never, done: true } : { value, done: false }; + pos++; } - }; - } - /** - * Maps contiguous spans of values with the same key. - * - * @param array The array to map. - * @param keyfn A callback used to select the key for an element. - * @param mapfn A callback used to map a contiguous chunk of values to a single value. - */ - export function spanMap(array: readonly T[], keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[]; - export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined; - export function spanMap(array: readonly T[] | undefined, keyfn: (x: T, i: number) => K, mapfn: (chunk: T[], key: K, start: number, end: number) => U): U[] | undefined { - let result: U[] | undefined; - if (array) { - result = []; - const len = array.length; - let previousKey: K | undefined; - let key: K | undefined; - let start = 0; - let pos = 0; - while (start < len) { - while (pos < len) { - const value = array[pos]; - key = keyfn(value, pos); - if (pos === 0) { - previousKey = key; - } - else if (key !== previousKey) { - break; - } - - pos++; - } - - if (start < pos) { - const v = mapfn(array.slice(start, pos), previousKey!, start, pos); - if (v) { - result.push(v); - } - - start = pos; + if (start < pos) { + const v = mapfn(array.slice(start, pos), previousKey!, start, pos); + if (v) { + result.push(v); } - previousKey = key; - pos++; + start = pos; } - } - return result; + previousKey = key; + pos++; + } } - export function mapEntries(map: ReadonlyESMap, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap; - export function mapEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap | undefined; - export function mapEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap | undefined { - if (!map) { - return undefined; - } + return result; +} - const result = new Map(); - map.forEach((value, key) => { - const [newKey, newValue] = f(key, value); - result.set(newKey, newValue); - }); - return result; +/** @internal */ +export function mapEntries(map: ReadonlyESMap, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap; +/** @internal */ +export function mapEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap | undefined; +/** @internal */ +export function mapEntries(map: ReadonlyESMap | undefined, f: (key: K1, value: V1) => readonly [K2, V2]): ESMap | undefined { + if (!map) { + return undefined; } - export function some(array: readonly T[] | undefined): array is readonly T[]; - export function some(array: readonly T[] | undefined, predicate: (value: T) => boolean): boolean; - export function some(array: readonly T[] | undefined, predicate?: (value: T) => boolean): boolean { - if (array) { - if (predicate) { - for (const v of array) { - if (predicate(v)) { - return true; - } + const result = new Map(); + map.forEach((value, key) => { + const [newKey, newValue] = f(key, value); + result.set(newKey, newValue); + }); + return result; +} + +/** @internal */ +export function some(array: readonly T[] | undefined): array is readonly T[]; +/** @internal */ +export function some(array: readonly T[] | undefined, predicate: (value: T) => boolean): boolean; +/** @internal */ +export function some(array: readonly T[] | undefined, predicate?: (value: T) => boolean): boolean { + if (array) { + if (predicate) { + for (const v of array) { + if (predicate(v)) { + return true; } } - else { - return array.length > 0; - } } - return false; + else { + return array.length > 0; + } } + return false; +} - /** Calls the callback with (start, afterEnd) index pairs for each range where 'pred' is true. */ - export function getRangesWhere(arr: readonly T[], pred: (t: T) => boolean, cb: (start: number, afterEnd: number) => void): void { - let start: number | undefined; - for (let i = 0; i < arr.length; i++) { - if (pred(arr[i])) { - start = start === undefined ? i : start; - } - else { - if (start !== undefined) { - cb(start, i); - start = undefined; - } +/** + * Calls the callback with (start, afterEnd) index pairs for each range where 'pred' is true. + * + * @internal + */ +export function getRangesWhere(arr: readonly T[], pred: (t: T) => boolean, cb: (start: number, afterEnd: number) => void): void { + let start: number | undefined; + for (let i = 0; i < arr.length; i++) { + if (pred(arr[i])) { + start = start === undefined ? i : start; + } + else { + if (start !== undefined) { + cb(start, i); + start = undefined; } } - if (start !== undefined) cb(start, arr.length); } + if (start !== undefined) cb(start, arr.length); +} - export function concatenate(array1: T[], array2: T[]): T[]; - export function concatenate(array1: readonly T[], array2: readonly T[]): readonly T[]; - export function concatenate(array1: T[] | undefined, array2: T[] | undefined): T[]; - export function concatenate(array1: readonly T[] | undefined, array2: readonly T[] | undefined): readonly T[]; - export function concatenate(array1: T[], array2: T[]): T[] { - if (!some(array2)) return array1; - if (!some(array1)) return array2; - return [...array1, ...array2]; - } +/** @internal */ +export function concatenate(array1: T[], array2: T[]): T[]; +/** @internal */ +export function concatenate(array1: readonly T[], array2: readonly T[]): readonly T[]; +/** @internal */ +export function concatenate(array1: T[] | undefined, array2: T[] | undefined): T[]; +/** @internal */ +export function concatenate(array1: readonly T[] | undefined, array2: readonly T[] | undefined): readonly T[]; +/** @internal */ +export function concatenate(array1: T[], array2: T[]): T[] { + if (!some(array2)) return array1; + if (!some(array1)) return array2; + return [...array1, ...array2]; +} - function selectIndex(_: unknown, i: number) { - return i; - } +function selectIndex(_: unknown, i: number) { + return i; +} - export function indicesOf(array: readonly unknown[]): number[] { - return array.map(selectIndex); - } +/** @internal */ +export function indicesOf(array: readonly unknown[]): number[] { + return array.map(selectIndex); +} - function deduplicateRelational(array: readonly T[], equalityComparer: EqualityComparer, comparer: Comparer) { - // Perform a stable sort of the array. This ensures the first entry in a list of - // duplicates remains the first entry in the result. - const indices = indicesOf(array); - stableSortIndices(array, indices, comparer); +function deduplicateRelational(array: readonly T[], equalityComparer: EqualityComparer, comparer: Comparer) { + // Perform a stable sort of the array. This ensures the first entry in a list of + // duplicates remains the first entry in the result. + const indices = indicesOf(array); + stableSortIndices(array, indices, comparer); - let last = array[indices[0]]; - const deduplicated: number[] = [indices[0]]; - for (let i = 1; i < indices.length; i++) { - const index = indices[i]; - const item = array[index]; - if (!equalityComparer(last, item)) { - deduplicated.push(index); - last = item; - } + let last = array[indices[0]]; + const deduplicated: number[] = [indices[0]]; + for (let i = 1; i < indices.length; i++) { + const index = indices[i]; + const item = array[index]; + if (!equalityComparer(last, item)) { + deduplicated.push(index); + last = item; } - - // restore original order - deduplicated.sort(); - return deduplicated.map(i => array[i]); } - function deduplicateEquality(array: readonly T[], equalityComparer: EqualityComparer) { - const result: T[] = []; - for (const item of array) { - pushIfUnique(result, item, equalityComparer); - } - return result; - } + // restore original order + deduplicated.sort(); + return deduplicated.map(i => array[i]); +} - /** - * Deduplicates an unsorted array. - * @param equalityComparer An `EqualityComparer` used to determine if two values are duplicates. - * @param comparer An optional `Comparer` used to sort entries before comparison, though the - * result will remain in the original order in `array`. - */ - export function deduplicate(array: readonly T[], equalityComparer: EqualityComparer, comparer?: Comparer): T[] { - return array.length === 0 ? [] : - array.length === 1 ? array.slice() : - comparer ? deduplicateRelational(array, equalityComparer, comparer) : - deduplicateEquality(array, equalityComparer); +function deduplicateEquality(array: readonly T[], equalityComparer: EqualityComparer) { + const result: T[] = []; + for (const item of array) { + pushIfUnique(result, item, equalityComparer); } + return result; +} - /** - * Deduplicates an array that has already been sorted. - */ - function deduplicateSorted(array: SortedReadonlyArray, comparer: EqualityComparer | Comparer): SortedReadonlyArray { - if (array.length === 0) return emptyArray as any as SortedReadonlyArray; - - let last = array[0]; - const deduplicated: T[] = [last]; - for (let i = 1; i < array.length; i++) { - const next = array[i]; - switch (comparer(next, last)) { - // equality comparison - case true: - - // relational comparison - // falls through - case Comparison.EqualTo: - continue; - - case Comparison.LessThan: - // If `array` is sorted, `next` should **never** be less than `last`. - return Debug.fail("Array is unsorted."); - } +/** + * Deduplicates an unsorted array. + * @param equalityComparer An `EqualityComparer` used to determine if two values are duplicates. + * @param comparer An optional `Comparer` used to sort entries before comparison, though the + * result will remain in the original order in `array`. + * + * @internal + */ +export function deduplicate(array: readonly T[], equalityComparer: EqualityComparer, comparer?: Comparer): T[] { + return array.length === 0 ? [] : + array.length === 1 ? array.slice() : + comparer ? deduplicateRelational(array, equalityComparer, comparer) : + deduplicateEquality(array, equalityComparer); +} - deduplicated.push(last = next); - } +/** + * Deduplicates an array that has already been sorted. + */ +function deduplicateSorted(array: SortedReadonlyArray, comparer: EqualityComparer | Comparer): SortedReadonlyArray { + if (array.length === 0) return emptyArray as any as SortedReadonlyArray; - return deduplicated as any as SortedReadonlyArray; - } + let last = array[0]; + const deduplicated: T[] = [last]; + for (let i = 1; i < array.length; i++) { + const next = array[i]; + switch (comparer(next, last)) { + // equality comparison + case true: - export function createSortedArray(): SortedArray { - return [] as any as SortedArray; // TODO: GH#19873 - } + // relational comparison + // falls through + case Comparison.EqualTo: + continue; - export function insertSorted(array: SortedArray, insert: T, compare: Comparer, allowDuplicates?: boolean): boolean { - if (array.length === 0) { - array.push(insert); - return true; + case Comparison.LessThan: + // If `array` is sorted, `next` should **never** be less than `last`. + return Debug.fail("Array is unsorted."); } - const insertIndex = binarySearch(array, insert, identity, compare); - if (insertIndex < 0) { - array.splice(~insertIndex, 0, insert); - return true; - } + deduplicated.push(last = next); + } - if (allowDuplicates) { - array.splice(insertIndex, 0, insert); - return true; - } + return deduplicated as any as SortedReadonlyArray; +} - return false; +/** @internal */ +export function createSortedArray(): SortedArray { + return [] as any as SortedArray; // TODO: GH#19873 +} + +/** @internal */ +export function insertSorted(array: SortedArray, insert: T, compare: Comparer, allowDuplicates?: boolean): boolean { + if (array.length === 0) { + array.push(insert); + return true; } - export function sortAndDeduplicate(array: readonly string[]): SortedReadonlyArray; - export function sortAndDeduplicate(array: readonly T[], comparer: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray; - export function sortAndDeduplicate(array: readonly T[], comparer?: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray { - return deduplicateSorted(sort(array, comparer), equalityComparer || comparer || compareStringsCaseSensitive as any as Comparer); + const insertIndex = binarySearch(array, insert, identity, compare); + if (insertIndex < 0) { + array.splice(~insertIndex, 0, insert); + return true; } - export function arrayIsSorted(array: readonly T[], comparer: Comparer) { - if (array.length < 2) return true; - let prevElement = array[0]; - for (const element of array.slice(1)) { - if (comparer(prevElement, element) === Comparison.GreaterThan) { - return false; - } - prevElement = element; - } + if (allowDuplicates) { + array.splice(insertIndex, 0, insert); return true; } - export function arrayIsEqualTo(array1: readonly T[] | undefined, array2: readonly T[] | undefined, equalityComparer: (a: T, b: T, index: number) => boolean = equateValues): boolean { - if (!array1 || !array2) { - return array1 === array2; - } + return false; +} + +/** @internal */ +export function sortAndDeduplicate(array: readonly string[]): SortedReadonlyArray; +/** @internal */ +export function sortAndDeduplicate(array: readonly T[], comparer: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray; +/** @internal */ +export function sortAndDeduplicate(array: readonly T[], comparer?: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray { + return deduplicateSorted(sort(array, comparer), equalityComparer || comparer || compareStringsCaseSensitive as any as Comparer); +} - if (array1.length !== array2.length) { +/** @internal */ +export function arrayIsSorted(array: readonly T[], comparer: Comparer) { + if (array.length < 2) return true; + let prevElement = array[0]; + for (const element of array.slice(1)) { + if (comparer(prevElement, element) === Comparison.GreaterThan) { return false; } + prevElement = element; + } + return true; +} - for (let i = 0; i < array1.length; i++) { - if (!equalityComparer(array1[i], array2[i], i)) { - return false; - } - } +/** @internal */ +export function arrayIsEqualTo(array1: readonly T[] | undefined, array2: readonly T[] | undefined, equalityComparer: (a: T, b: T, index: number) => boolean = equateValues): boolean { + if (!array1 || !array2) { + return array1 === array2; + } - return true; + if (array1.length !== array2.length) { + return false; } - /** - * Compacts an array, removing any falsey elements. - */ - export function compact(array: (T | undefined | null | false | 0 | "")[]): T[]; - export function compact(array: readonly (T | undefined | null | false | 0 | "")[]): readonly T[]; - // ESLint thinks these can be combined with the above - they cannot; they'd produce higher-priority inferences and prevent the falsey types from being stripped - export function compact(array: T[]): T[]; // eslint-disable-line @typescript-eslint/unified-signatures - export function compact(array: readonly T[]): readonly T[]; // eslint-disable-line @typescript-eslint/unified-signatures - export function compact(array: T[]): T[] { - let result: T[] | undefined; - if (array) { - for (let i = 0; i < array.length; i++) { - const v = array[i]; - if (result || !v) { - if (!result) { - result = array.slice(0, i); - } - if (v) { - result.push(v); - } - } - } + for (let i = 0; i < array1.length; i++) { + if (!equalityComparer(array1[i], array2[i], i)) { + return false; } - return result || array; } - /** - * Gets the relative complement of `arrayA` with respect to `arrayB`, returning the elements that - * are not present in `arrayA` but are present in `arrayB`. Assumes both arrays are sorted - * based on the provided comparer. - */ - export function relativeComplement(arrayA: T[] | undefined, arrayB: T[] | undefined, comparer: Comparer): T[] | undefined { - if (!arrayB || !arrayA || arrayB.length === 0 || arrayA.length === 0) return arrayB; - const result: T[] = []; - loopB: for (let offsetA = 0, offsetB = 0; offsetB < arrayB.length; offsetB++) { - if (offsetB > 0) { - // Ensure `arrayB` is properly sorted. - Debug.assertGreaterThanOrEqual(comparer(arrayB[offsetB], arrayB[offsetB - 1]), Comparison.EqualTo); - } + return true; +} - loopA: for (const startA = offsetA; offsetA < arrayA.length; offsetA++) { - if (offsetA > startA) { - // Ensure `arrayA` is properly sorted. We only need to perform this check if - // `offsetA` has changed since we entered the loop. - Debug.assertGreaterThanOrEqual(comparer(arrayA[offsetA], arrayA[offsetA - 1]), Comparison.EqualTo); +/** + * Compacts an array, removing any falsey elements. + * + * @internal + */ +export function compact(array: (T | undefined | null | false | 0 | "")[]): T[]; +/** @internal */ +export function compact(array: readonly (T | undefined | null | false | 0 | "")[]): readonly T[]; +// ESLint thinks these can be combined with the above - they cannot; they'd produce higher-priority inferences and prevent the falsey types from being stripped +/** @internal */ +export function compact(array: T[]): T[]; // eslint-disable-line @typescript-eslint/unified-signatures +/** @internal */ +export function compact(array: readonly T[]): readonly T[]; // eslint-disable-line @typescript-eslint/unified-signatures +/** @internal */ +export function compact(array: T[]): T[] { + let result: T[] | undefined; + if (array) { + for (let i = 0; i < array.length; i++) { + const v = array[i]; + if (result || !v) { + if (!result) { + result = array.slice(0, i); } - - switch (comparer(arrayB[offsetB], arrayA[offsetA])) { - case Comparison.LessThan: - // If B is less than A, B does not exist in arrayA. Add B to the result and - // move to the next element in arrayB without changing the current position - // in arrayA. - result.push(arrayB[offsetB]); - continue loopB; - case Comparison.EqualTo: - // If B is equal to A, B exists in arrayA. Move to the next element in - // arrayB without adding B to the result or changing the current position - // in arrayA. - continue loopB; - case Comparison.GreaterThan: - // If B is greater than A, we need to keep looking for B in arrayA. Move to - // the next element in arrayA and recheck. - continue loopA; + if (v) { + result.push(v); } } } - return result; } + return result || array; +} - export function sum, K extends string>(array: readonly T[], prop: K): number { - let result = 0; - for (const v of array) { - result += v[prop]; +/** + * Gets the relative complement of `arrayA` with respect to `arrayB`, returning the elements that + * are not present in `arrayA` but are present in `arrayB`. Assumes both arrays are sorted + * based on the provided comparer. + * + * @internal + */ +export function relativeComplement(arrayA: T[] | undefined, arrayB: T[] | undefined, comparer: Comparer): T[] | undefined { + if (!arrayB || !arrayA || arrayB.length === 0 || arrayA.length === 0) return arrayB; + const result: T[] = []; + loopB: for (let offsetA = 0, offsetB = 0; offsetB < arrayB.length; offsetB++) { + if (offsetB > 0) { + // Ensure `arrayB` is properly sorted. + Debug.assertGreaterThanOrEqual(comparer(arrayB[offsetB], arrayB[offsetB - 1]), Comparison.EqualTo); + } + + loopA: for (const startA = offsetA; offsetA < arrayA.length; offsetA++) { + if (offsetA > startA) { + // Ensure `arrayA` is properly sorted. We only need to perform this check if + // `offsetA` has changed since we entered the loop. + Debug.assertGreaterThanOrEqual(comparer(arrayA[offsetA], arrayA[offsetA - 1]), Comparison.EqualTo); + } + + switch (comparer(arrayB[offsetB], arrayA[offsetA])) { + case Comparison.LessThan: + // If B is less than A, B does not exist in arrayA. Add B to the result and + // move to the next element in arrayB without changing the current position + // in arrayA. + result.push(arrayB[offsetB]); + continue loopB; + case Comparison.EqualTo: + // If B is equal to A, B exists in arrayA. Move to the next element in + // arrayB without adding B to the result or changing the current position + // in arrayA. + continue loopB; + case Comparison.GreaterThan: + // If B is greater than A, we need to keep looking for B in arrayA. Move to + // the next element in arrayA and recheck. + continue loopA; + } } - return result; } + return result; +} - /** - * Appends a value to an array, returning the array. - * - * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array - * is created if `value` was appended. - * @param value The value to append to the array. If `value` is `undefined`, nothing is - * appended. - */ - export function append[number] | undefined>(to: TArray, value: TValue): [undefined, undefined] extends [TArray, TValue] ? TArray : NonNullable[number][]; - export function append(to: T[], value: T | undefined): T[]; - export function append(to: T[] | undefined, value: T): T[]; - export function append(to: T[] | undefined, value: T | undefined): T[] | undefined; - export function append(to: Push, value: T | undefined): void; - export function append(to: T[], value: T | undefined): T[] | undefined { - if (value === undefined) return to; - if (to === undefined) return [value]; - to.push(value); - return to; +/** @internal */ +export function sum, K extends string>(array: readonly T[], prop: K): number { + let result = 0; + for (const v of array) { + result += v[prop]; } + return result; +} - /** - * Combines two arrays, values, or undefineds into the smallest container that can accommodate the resulting set: - * - * ``` - * undefined -> undefined -> undefined - * T -> undefined -> T - * T -> T -> T[] - * T[] -> undefined -> T[] (no-op) - * T[] -> T -> T[] (append) - * T[] -> T[] -> T[] (concatenate) - * ``` - */ - export function combine(xs: T | readonly T[] | undefined, ys: T | readonly T[] | undefined): T | readonly T[] | undefined; - export function combine(xs: T | T[] | undefined, ys: T | T[] | undefined): T | T[] | undefined; - export function combine(xs: T | T[] | undefined, ys: T | T[] | undefined) { - if (xs === undefined) return ys; - if (ys === undefined) return xs; - if (isArray(xs)) return isArray(ys) ? concatenate(xs, ys) : append(xs, ys); - if (isArray(ys)) return append(ys, xs); - return [xs, ys]; - } +/** + * Appends a value to an array, returning the array. + * + * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array + * is created if `value` was appended. + * @param value The value to append to the array. If `value` is `undefined`, nothing is + * appended. + * + * @internal + */ +export function append[number] | undefined>(to: TArray, value: TValue): [undefined, undefined] extends [TArray, TValue] ? TArray : NonNullable[number][]; +/** @internal */ +export function append(to: T[], value: T | undefined): T[]; +/** @internal */ +export function append(to: T[] | undefined, value: T): T[]; +/** @internal */ +export function append(to: T[] | undefined, value: T | undefined): T[] | undefined; +/** @internal */ +export function append(to: Push, value: T | undefined): void; +/** @internal */ +export function append(to: T[], value: T | undefined): T[] | undefined { + if (value === undefined) return to; + if (to === undefined) return [value]; + to.push(value); + return to; +} - /** - * Gets the actual offset into an array for a relative offset. Negative offsets indicate a - * position offset from the end of the array. - */ - function toOffset(array: readonly any[], offset: number) { - return offset < 0 ? array.length + offset : offset; - } +/** + * Combines two arrays, values, or undefineds into the smallest container that can accommodate the resulting set: + * + * ``` + * undefined -> undefined -> undefined + * T -> undefined -> T + * T -> T -> T[] + * T[] -> undefined -> T[] (no-op) + * T[] -> T -> T[] (append) + * T[] -> T[] -> T[] (concatenate) + * ``` + * + * @internal + */ +export function combine(xs: T | readonly T[] | undefined, ys: T | readonly T[] | undefined): T | readonly T[] | undefined; +/** @internal */ +export function combine(xs: T | T[] | undefined, ys: T | T[] | undefined): T | T[] | undefined; +/** @internal */ +export function combine(xs: T | T[] | undefined, ys: T | T[] | undefined) { + if (xs === undefined) return ys; + if (ys === undefined) return xs; + if (isArray(xs)) return isArray(ys) ? concatenate(xs, ys) : append(xs, ys); + if (isArray(ys)) return append(ys, xs); + return [xs, ys]; +} - /** - * Appends a range of value to an array, returning the array. - * - * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array - * is created if `value` was appended. - * @param from The values to append to the array. If `from` is `undefined`, nothing is - * appended. If an element of `from` is `undefined`, that element is not appended. - * @param start The offset in `from` at which to start copying values. - * @param end The offset in `from` at which to stop copying values (non-inclusive). - */ - export function addRange(to: T[], from: readonly T[] | undefined, start?: number, end?: number): T[]; - export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined; - export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined { - if (from === undefined || from.length === 0) return to; - if (to === undefined) return from.slice(start, end); - start = start === undefined ? 0 : toOffset(from, start); - end = end === undefined ? from.length : toOffset(from, end); - for (let i = start; i < end && i < from.length; i++) { - if (from[i] !== undefined) { - to.push(from[i]); - } - } - return to; +/** + * Gets the actual offset into an array for a relative offset. Negative offsets indicate a + * position offset from the end of the array. + */ +function toOffset(array: readonly any[], offset: number) { + return offset < 0 ? array.length + offset : offset; +} + +/** + * Appends a range of value to an array, returning the array. + * + * @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array + * is created if `value` was appended. + * @param from The values to append to the array. If `from` is `undefined`, nothing is + * appended. If an element of `from` is `undefined`, that element is not appended. + * @param start The offset in `from` at which to start copying values. + * @param end The offset in `from` at which to stop copying values (non-inclusive). + * + * @internal + */ +export function addRange(to: T[], from: readonly T[] | undefined, start?: number, end?: number): T[]; +/** @internal */ +export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined; +/** @internal */ +export function addRange(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined { + if (from === undefined || from.length === 0) return to; + if (to === undefined) return from.slice(start, end); + start = start === undefined ? 0 : toOffset(from, start); + end = end === undefined ? from.length : toOffset(from, end); + for (let i = start; i < end && i < from.length; i++) { + if (from[i] !== undefined) { + to.push(from[i]); + } + } + return to; +} + +/** + * @return Whether the value was added. + * + * @internal + */ +export function pushIfUnique(array: T[], toAdd: T, equalityComparer?: EqualityComparer): boolean { + if (contains(array, toAdd, equalityComparer)) { + return false; } + else { + array.push(toAdd); + return true; + } +} - /** - * @return Whether the value was added. - */ - export function pushIfUnique(array: T[], toAdd: T, equalityComparer?: EqualityComparer): boolean { - if (contains(array, toAdd, equalityComparer)) { - return false; - } - else { - array.push(toAdd); - return true; - } +/** + * Unlike `pushIfUnique`, this can take `undefined` as an input, and returns a new array. + * + * @internal + */ +export function appendIfUnique(array: T[] | undefined, toAdd: T, equalityComparer?: EqualityComparer): T[] { + if (array) { + pushIfUnique(array, toAdd, equalityComparer); + return array; } + else { + return [toAdd]; + } +} - /** - * Unlike `pushIfUnique`, this can take `undefined` as an input, and returns a new array. - */ - export function appendIfUnique(array: T[] | undefined, toAdd: T, equalityComparer?: EqualityComparer): T[] { - if (array) { - pushIfUnique(array, toAdd, equalityComparer); - return array; +function stableSortIndices(array: readonly T[], indices: number[], comparer: Comparer) { + // sort indices by value then position + indices.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)); +} + +/** + * Returns a new sorted array. + * + * @internal + */ +export function sort(array: readonly T[], comparer?: Comparer): SortedReadonlyArray { + return (array.length === 0 ? array : array.slice().sort(comparer)) as SortedReadonlyArray; +} + +/** @internal */ +export function arrayIterator(array: readonly T[]): Iterator { + let i = 0; + return { next: () => { + if (i === array.length) { + return { value: undefined as never, done: true }; } else { - return [toAdd]; + i++; + return { value: array[i - 1], done: false }; } - } - - function stableSortIndices(array: readonly T[], indices: number[], comparer: Comparer) { - // sort indices by value then position - indices.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)); - } - - /** - * Returns a new sorted array. - */ - export function sort(array: readonly T[], comparer?: Comparer): SortedReadonlyArray { - return (array.length === 0 ? array : array.slice().sort(comparer)) as SortedReadonlyArray; - } + }}; +} - export function arrayIterator(array: readonly T[]): Iterator { - let i = 0; - return { next: () => { - if (i === array.length) { +/** @internal */ +export function arrayReverseIterator(array: readonly T[]): Iterator { + let i = array.length; + return { + next: () => { + if (i === 0) { return { value: undefined as never, done: true }; } else { - i++; - return { value: array[i - 1], done: false }; + i--; + return { value: array[i], done: false }; } - }}; - } - - export function arrayReverseIterator(array: readonly T[]): Iterator { - let i = array.length; - return { - next: () => { - if (i === 0) { - return { value: undefined as never, done: true }; - } - else { - i--; - return { value: array[i], done: false }; - } - } - }; - } + } + }; +} - /** - * Stable sort of an array. Elements equal to each other maintain their relative position in the array. - */ - export function stableSort(array: readonly T[], comparer: Comparer): SortedReadonlyArray { - const indices = indicesOf(array); - stableSortIndices(array, indices, comparer); - return indices.map(i => array[i]) as SortedArray as SortedReadonlyArray; - } +/** + * Stable sort of an array. Elements equal to each other maintain their relative position in the array. + * + * @internal + */ +export function stableSort(array: readonly T[], comparer: Comparer): SortedReadonlyArray { + const indices = indicesOf(array); + stableSortIndices(array, indices, comparer); + return indices.map(i => array[i]) as SortedArray as SortedReadonlyArray; +} - export function rangeEquals(array1: readonly T[], array2: readonly T[], pos: number, end: number) { - while (pos < end) { - if (array1[pos] !== array2[pos]) { - return false; - } - pos++; +/** @internal */ +export function rangeEquals(array1: readonly T[], array2: readonly T[], pos: number, end: number) { + while (pos < end) { + if (array1[pos] !== array2[pos]) { + return false; } - return true; + pos++; } + return true; +} - /** - * Returns the element at a specific offset in an array if non-empty, `undefined` otherwise. - * A negative offset indicates the element should be retrieved from the end of the array. - */ - export function elementAt(array: readonly T[] | undefined, offset: number): T | undefined { - if (array) { - offset = toOffset(array, offset); - if (offset < array.length) { - return array[offset]; - } +/** + * Returns the element at a specific offset in an array if non-empty, `undefined` otherwise. + * A negative offset indicates the element should be retrieved from the end of the array. + * + * @internal + */ +export function elementAt(array: readonly T[] | undefined, offset: number): T | undefined { + if (array) { + offset = toOffset(array, offset); + if (offset < array.length) { + return array[offset]; } - return undefined; } + return undefined; +} - /** - * Returns the first element of an array if non-empty, `undefined` otherwise. - */ - export function firstOrUndefined(array: readonly T[] | undefined): T | undefined { - return array === undefined || array.length === 0 ? undefined : array[0]; - } +/** + * Returns the first element of an array if non-empty, `undefined` otherwise. + * + * @internal + */ +export function firstOrUndefined(array: readonly T[] | undefined): T | undefined { + return array === undefined || array.length === 0 ? undefined : array[0]; +} - export function first(array: readonly T[]): T { - Debug.assert(array.length !== 0); - return array[0]; - } +/** @internal */ +export function first(array: readonly T[]): T { + Debug.assert(array.length !== 0); + return array[0]; +} - /** - * Returns the last element of an array if non-empty, `undefined` otherwise. - */ - export function lastOrUndefined(array: readonly T[] | undefined): T | undefined { - return array === undefined || array.length === 0 ? undefined : array[array.length - 1]; - } +/** + * Returns the last element of an array if non-empty, `undefined` otherwise. + * + * @internal + */ +export function lastOrUndefined(array: readonly T[] | undefined): T | undefined { + return array === undefined || array.length === 0 ? undefined : array[array.length - 1]; +} - export function last(array: readonly T[]): T { - Debug.assert(array.length !== 0); - return array[array.length - 1]; - } +/** @internal */ +export function last(array: readonly T[]): T { + Debug.assert(array.length !== 0); + return array[array.length - 1]; +} - /** - * Returns the only element of an array if it contains only one element, `undefined` otherwise. - */ - export function singleOrUndefined(array: readonly T[] | undefined): T | undefined { - return array && array.length === 1 - ? array[0] - : undefined; - } +/** + * Returns the only element of an array if it contains only one element, `undefined` otherwise. + * + * @internal + */ +export function singleOrUndefined(array: readonly T[] | undefined): T | undefined { + return array && array.length === 1 + ? array[0] + : undefined; +} - /** - * Returns the only element of an array if it contains only one element; throws otherwise. - */ - export function single(array: readonly T[]): T { - return Debug.checkDefined(singleOrUndefined(array)); - } +/** + * Returns the only element of an array if it contains only one element; throws otherwise. + * + * @internal + */ +export function single(array: readonly T[]): T { + return Debug.checkDefined(singleOrUndefined(array)); +} - /** - * Returns the only element of an array if it contains only one element; otherwise, returns the - * array. - */ - export function singleOrMany(array: T[]): T | T[]; - export function singleOrMany(array: readonly T[]): T | readonly T[]; - export function singleOrMany(array: T[] | undefined): T | T[] | undefined; - export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined; - export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined { - return array && array.length === 1 - ? array[0] - : array; - } - - export function replaceElement(array: readonly T[], index: number, value: T): T[] { - const result = array.slice(0); - result[index] = value; - return result; - } +/** + * Returns the only element of an array if it contains only one element; otherwise, returns the + * array. + * + * @internal + */ +export function singleOrMany(array: T[]): T | T[]; +/** @internal */ +export function singleOrMany(array: readonly T[]): T | readonly T[]; +/** @internal */ +export function singleOrMany(array: T[] | undefined): T | T[] | undefined; +/** @internal */ +export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined; +/** @internal */ +export function singleOrMany(array: readonly T[] | undefined): T | readonly T[] | undefined { + return array && array.length === 1 + ? array[0] + : array; +} - /** - * Performs a binary search, finding the index at which `value` occurs in `array`. - * If no such index is found, returns the 2's-complement of first index at which - * `array[index]` exceeds `value`. - * @param array A sorted array whose first element must be no larger than number - * @param value The value to be searched for in the array. - * @param keySelector A callback used to select the search key from `value` and each element of - * `array`. - * @param keyComparer A callback used to compare two keys in a sorted array. - * @param offset An offset into `array` at which to start the search. - */ - export function binarySearch(array: readonly T[], value: T, keySelector: (v: T) => U, keyComparer: Comparer, offset?: number): number { - return binarySearchKey(array, keySelector(value), keySelector, keyComparer, offset); - } +/** @internal */ +export function replaceElement(array: readonly T[], index: number, value: T): T[] { + const result = array.slice(0); + result[index] = value; + return result; +} - /** - * Performs a binary search, finding the index at which an object with `key` occurs in `array`. - * If no such index is found, returns the 2's-complement of first index at which - * `array[index]` exceeds `key`. - * @param array A sorted array whose first element must be no larger than number - * @param key The key to be searched for in the array. - * @param keySelector A callback used to select the search key from each element of `array`. - * @param keyComparer A callback used to compare two keys in a sorted array. - * @param offset An offset into `array` at which to start the search. - */ - export function binarySearchKey(array: readonly T[], key: U, keySelector: (v: T, i: number) => U, keyComparer: Comparer, offset?: number): number { - if (!some(array)) { - return -1; - } +/** + * Performs a binary search, finding the index at which `value` occurs in `array`. + * If no such index is found, returns the 2's-complement of first index at which + * `array[index]` exceeds `value`. + * @param array A sorted array whose first element must be no larger than number + * @param value The value to be searched for in the array. + * @param keySelector A callback used to select the search key from `value` and each element of + * `array`. + * @param keyComparer A callback used to compare two keys in a sorted array. + * @param offset An offset into `array` at which to start the search. + * + * @internal + */ +export function binarySearch(array: readonly T[], value: T, keySelector: (v: T) => U, keyComparer: Comparer, offset?: number): number { + return binarySearchKey(array, keySelector(value), keySelector, keyComparer, offset); +} - let low = offset || 0; - let high = array.length - 1; - while (low <= high) { - const middle = low + ((high - low) >> 1); - const midKey = keySelector(array[middle], middle); - switch (keyComparer(midKey, key)) { - case Comparison.LessThan: - low = middle + 1; - break; - case Comparison.EqualTo: - return middle; - case Comparison.GreaterThan: - high = middle - 1; - break; - } - } +/** + * Performs a binary search, finding the index at which an object with `key` occurs in `array`. + * If no such index is found, returns the 2's-complement of first index at which + * `array[index]` exceeds `key`. + * @param array A sorted array whose first element must be no larger than number + * @param key The key to be searched for in the array. + * @param keySelector A callback used to select the search key from each element of `array`. + * @param keyComparer A callback used to compare two keys in a sorted array. + * @param offset An offset into `array` at which to start the search. + * + * @internal + */ +export function binarySearchKey(array: readonly T[], key: U, keySelector: (v: T, i: number) => U, keyComparer: Comparer, offset?: number): number { + if (!some(array)) { + return -1; + } - return ~low; + let low = offset || 0; + let high = array.length - 1; + while (low <= high) { + const middle = low + ((high - low) >> 1); + const midKey = keySelector(array[middle], middle); + switch (keyComparer(midKey, key)) { + case Comparison.LessThan: + low = middle + 1; + break; + case Comparison.EqualTo: + return middle; + case Comparison.GreaterThan: + high = middle - 1; + break; + } } - export function reduceLeft(array: readonly T[] | undefined, f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; - export function reduceLeft(array: readonly T[], f: (memo: T, value: T, i: number) => T): T | undefined; - export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T | undefined { - if (array && array.length > 0) { - const size = array.length; - if (size > 0) { - let pos = start === undefined || start < 0 ? 0 : start; - const end = count === undefined || pos + count > size - 1 ? size - 1 : pos + count; - let result: T; - if (arguments.length <= 2) { - result = array[pos]; - pos++; - } - else { - result = initial!; - } - while (pos <= end) { - result = f(result, array[pos], pos); - pos++; - } - return result; + return ~low; +} + +/** @internal */ +export function reduceLeft(array: readonly T[] | undefined, f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U; +/** @internal */ +export function reduceLeft(array: readonly T[], f: (memo: T, value: T, i: number) => T): T | undefined; +/** @internal */ +export function reduceLeft(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T | undefined { + if (array && array.length > 0) { + const size = array.length; + if (size > 0) { + let pos = start === undefined || start < 0 ? 0 : start; + const end = count === undefined || pos + count > size - 1 ? size - 1 : pos + count; + let result: T; + if (arguments.length <= 2) { + result = array[pos]; + pos++; + } + else { + result = initial!; + } + while (pos <= end) { + result = f(result, array[pos], pos); + pos++; } + return result; } - return initial; } + return initial; +} - const hasOwnProperty = Object.prototype.hasOwnProperty; +const hasOwnProperty = Object.prototype.hasOwnProperty; + +/** + * Indicates whether a map-like contains an own property with the specified key. + * + * @param map A map-like. + * @param key A property key. + * + * @internal + */ +export function hasProperty(map: MapLike, key: string): boolean { + return hasOwnProperty.call(map, key); +} - /** - * Indicates whether a map-like contains an own property with the specified key. - * - * @param map A map-like. - * @param key A property key. - */ - export function hasProperty(map: MapLike, key: string): boolean { - return hasOwnProperty.call(map, key); - } +/** + * Gets the value of an owned property in a map-like. + * + * @param map A map-like. + * @param key A property key. + * + * @internal + */ +export function getProperty(map: MapLike, key: string): T | undefined { + return hasOwnProperty.call(map, key) ? map[key] : undefined; +} - /** - * Gets the value of an owned property in a map-like. - * - * @param map A map-like. - * @param key A property key. - */ - export function getProperty(map: MapLike, key: string): T | undefined { - return hasOwnProperty.call(map, key) ? map[key] : undefined; +/** + * Gets the owned, enumerable property keys of a map-like. + * + * @internal + */ +export function getOwnKeys(map: MapLike): string[] { + const keys: string[] = []; + for (const key in map) { + if (hasOwnProperty.call(map, key)) { + keys.push(key); + } } - /** - * Gets the owned, enumerable property keys of a map-like. - */ - export function getOwnKeys(map: MapLike): string[] { - const keys: string[] = []; - for (const key in map) { - if (hasOwnProperty.call(map, key)) { - keys.push(key); - } + return keys; +} + +/** @internal */ +export function getAllKeys(obj: object): string[] { + const result: string[] = []; + do { + const names = Object.getOwnPropertyNames(obj); + for (const name of names) { + pushIfUnique(result, name); } + } while (obj = Object.getPrototypeOf(obj)); + return result; +} - return keys; +/** @internal */ +export function getOwnValues(collection: MapLike | T[]): T[] { + const values: T[] = []; + for (const key in collection) { + if (hasOwnProperty.call(collection, key)) { + values.push((collection as MapLike)[key]); + } } - export function getAllKeys(obj: object): string[] { - const result: string[] = []; - do { - const names = Object.getOwnPropertyNames(obj); - for (const name of names) { - pushIfUnique(result, name); - } - } while (obj = Object.getPrototypeOf(obj)); - return result; + return values; +} + +const _entries = Object.entries || ((obj: MapLike) => { + const keys = getOwnKeys(obj); + const result: [string, T][] = Array(keys.length); + for (let i = 0; i < keys.length; i++) { + result[i] = [keys[i], obj[keys[i]]]; } + return result; +}); - export function getOwnValues(collection: MapLike | T[]): T[] { - const values: T[] = []; - for (const key in collection) { - if (hasOwnProperty.call(collection, key)) { - values.push((collection as MapLike)[key]); - } - } +/** @internal */ +export function getEntries(obj: MapLike): [string, T][] { + return obj ? _entries(obj) : []; +} - return values; +/** @internal */ +export function arrayOf(count: number, f: (index: number) => T): T[] { + const result = new Array(count); + for (let i = 0; i < count; i++) { + result[i] = f(i); } + return result; +} - const _entries = Object.entries || ((obj: MapLike) => { - const keys = getOwnKeys(obj); - const result: [string, T][] = Array(keys.length); - for (let i = 0; i < keys.length; i++) { - result[i] = [keys[i], obj[keys[i]]]; - } - return result; - }); +/** + * Shims `Array.from`. + * + * @internal + */ +export function arrayFrom(iterator: Iterator | IterableIterator, map: (t: T) => U): U[]; +/** @internal */ +export function arrayFrom(iterator: Iterator | IterableIterator): T[]; +/** @internal */ +export function arrayFrom(iterator: Iterator | IterableIterator, map?: (t: T) => U): (T | U)[] { + const result: (T | U)[] = []; + for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { + result.push(map ? map(iterResult.value) : iterResult.value); + } + return result; +} - export function getEntries(obj: MapLike): [string, T][] { - return obj ? _entries(obj) : []; - } - export function arrayOf(count: number, f: (index: number) => T): T[] { - const result = new Array(count); - for (let i = 0; i < count; i++) { - result[i] = f(i); +/** @internal */ +export function assign(t: T, ...args: (T | undefined)[]) { + for (const arg of args) { + if (arg === undefined) continue; + for (const p in arg) { + if (hasProperty(arg, p)) { + t[p] = arg[p]; + } } - return result; } + return t; +} - /** Shims `Array.from`. */ - export function arrayFrom(iterator: Iterator | IterableIterator, map: (t: T) => U): U[]; - export function arrayFrom(iterator: Iterator | IterableIterator): T[]; - export function arrayFrom(iterator: Iterator | IterableIterator, map?: (t: T) => U): (T | U)[] { - const result: (T | U)[] = []; - for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) { - result.push(map ? map(iterResult.value) : iterResult.value); +/** + * Performs a shallow equality comparison of the contents of two map-likes. + * + * @param left A map-like whose properties should be compared. + * @param right A map-like whose properties should be compared. + * + * @internal + */ +export function equalOwnProperties(left: MapLike | undefined, right: MapLike | undefined, equalityComparer: EqualityComparer = equateValues) { + if (left === right) return true; + if (!left || !right) return false; + for (const key in left) { + if (hasOwnProperty.call(left, key)) { + if (!hasOwnProperty.call(right, key)) return false; + if (!equalityComparer(left[key], right[key])) return false; } - return result; } - - export function assign(t: T, ...args: (T | undefined)[]) { - for (const arg of args) { - if (arg === undefined) continue; - for (const p in arg) { - if (hasProperty(arg, p)) { - t[p] = arg[p]; - } - } + for (const key in right) { + if (hasOwnProperty.call(right, key)) { + if (!hasOwnProperty.call(left, key)) return false; } - return t; } - /** - * Performs a shallow equality comparison of the contents of two map-likes. - * - * @param left A map-like whose properties should be compared. - * @param right A map-like whose properties should be compared. - */ - export function equalOwnProperties(left: MapLike | undefined, right: MapLike | undefined, equalityComparer: EqualityComparer = equateValues) { - if (left === right) return true; - if (!left || !right) return false; - for (const key in left) { - if (hasOwnProperty.call(left, key)) { - if (!hasOwnProperty.call(right, key)) return false; - if (!equalityComparer(left[key], right[key])) return false; - } - } + return true; +} - for (const key in right) { - if (hasOwnProperty.call(right, key)) { - if (!hasOwnProperty.call(left, key)) return false; - } - } +/** + * Creates a map from the elements of an array. + * + * @param array the array of input elements. + * @param makeKey a function that produces a key for a given element. + * + * This function makes no effort to avoid collisions; if any two elements produce + * the same key with the given 'makeKey' function, then the element with the higher + * index in the array will be the one associated with the produced key. + * + * @internal + */ +export function arrayToMap(array: readonly V[], makeKey: (value: V) => K | undefined): ESMap; +/** @internal */ +export function arrayToMap(array: readonly V1[], makeKey: (value: V1) => K | undefined, makeValue: (value: V1) => V2): ESMap; +/** @internal */ +export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined): ESMap; +/** @internal */ +export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined, makeValue: (value: T) => U): ESMap; +/** @internal */ +export function arrayToMap(array: readonly V1[], makeKey: (value: V1) => K | undefined, makeValue: (value: V1) => V1 | V2 = identity): ESMap { + const result = new Map(); + for (const value of array) { + const key = makeKey(value); + if (key !== undefined) result.set(key, makeValue(value)); + } + return result; +} - return true; - } +/** @internal */ +export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number): T[]; +/** @internal */ +export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => U): U[]; +/** @internal */ +export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => T | U = identity): (T | U)[] { + const result: (T | U)[] = []; + for (const value of array) { + result[makeKey(value)] = makeValue(value); + } + return result; +} - /** - * Creates a map from the elements of an array. - * - * @param array the array of input elements. - * @param makeKey a function that produces a key for a given element. - * - * This function makes no effort to avoid collisions; if any two elements produce - * the same key with the given 'makeKey' function, then the element with the higher - * index in the array will be the one associated with the produced key. - */ - export function arrayToMap(array: readonly V[], makeKey: (value: V) => K | undefined): ESMap; - export function arrayToMap(array: readonly V1[], makeKey: (value: V1) => K | undefined, makeValue: (value: V1) => V2): ESMap; - export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined): ESMap; - export function arrayToMap(array: readonly T[], makeKey: (value: T) => string | undefined, makeValue: (value: T) => U): ESMap; - export function arrayToMap(array: readonly V1[], makeKey: (value: V1) => K | undefined, makeValue: (value: V1) => V1 | V2 = identity): ESMap { - const result = new Map(); - for (const value of array) { - const key = makeKey(value); - if (key !== undefined) result.set(key, makeValue(value)); +/** @internal */ +export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K): MultiMap; +/** @internal */ +export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K, makeValue: (value: V) => U): MultiMap; +/** @internal */ +export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K, makeValue: (value: V) => V | U = identity): MultiMap { + const result = createMultiMap(); + for (const value of values) { + result.add(makeKey(value), makeValue(value)); + } + return result; +} + +/** @internal */ +export function group(values: readonly T[], getGroupId: (value: T) => K): readonly (readonly T[])[]; +/** @internal */ +export function group(values: readonly T[], getGroupId: (value: T) => K, resultSelector: (values: readonly T[]) => R): R[]; +/** @internal */ +export function group(values: readonly T[], getGroupId: (value: T) => string): readonly (readonly T[])[]; +/** @internal */ +export function group(values: readonly T[], getGroupId: (value: T) => string, resultSelector: (values: readonly T[]) => R): R[]; +/** @internal */ +export function group(values: readonly T[], getGroupId: (value: T) => K, resultSelector: (values: readonly T[]) => readonly T[] = identity): readonly (readonly T[])[] { + return arrayFrom(arrayToMultiMap(values, getGroupId).values(), resultSelector); +} + +/** @internal */ +export function clone(object: T): T { + const result: any = {}; + for (const id in object) { + if (hasOwnProperty.call(object, id)) { + result[id] = (object as any)[id]; } - return result; } + return result; +} - export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number): T[]; - export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => U): U[]; - export function arrayToNumericMap(array: readonly T[], makeKey: (value: T) => number, makeValue: (value: T) => T | U = identity): (T | U)[] { - const result: (T | U)[] = []; - for (const value of array) { - result[makeKey(value)] = makeValue(value); +/** + * Creates a new object by adding the own properties of `second`, then the own properties of `first`. + * + * NOTE: This means that if a property exists in both `first` and `second`, the property in `first` will be chosen. + * + * @internal + */ +export function extend(first: T1, second: T2): T1 & T2 { + const result: T1 & T2 = {} as any; + for (const id in second) { + if (hasOwnProperty.call(second, id)) { + (result as any)[id] = (second as any)[id]; } - return result; } - export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K): MultiMap; - export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K, makeValue: (value: V) => U): MultiMap; - export function arrayToMultiMap(values: readonly V[], makeKey: (value: V) => K, makeValue: (value: V) => V | U = identity): MultiMap { - const result = createMultiMap(); - for (const value of values) { - result.add(makeKey(value), makeValue(value)); + for (const id in first) { + if (hasOwnProperty.call(first, id)) { + (result as any)[id] = (first as any)[id]; } - return result; } - export function group(values: readonly T[], getGroupId: (value: T) => K): readonly (readonly T[])[]; - export function group(values: readonly T[], getGroupId: (value: T) => K, resultSelector: (values: readonly T[]) => R): R[]; - export function group(values: readonly T[], getGroupId: (value: T) => string): readonly (readonly T[])[]; - export function group(values: readonly T[], getGroupId: (value: T) => string, resultSelector: (values: readonly T[]) => R): R[]; - export function group(values: readonly T[], getGroupId: (value: T) => K, resultSelector: (values: readonly T[]) => readonly T[] = identity): readonly (readonly T[])[] { - return arrayFrom(arrayToMultiMap(values, getGroupId).values(), resultSelector); - } + return result; +} - export function clone(object: T): T { - const result: any = {}; - for (const id in object) { - if (hasOwnProperty.call(object, id)) { - result[id] = (object as any)[id]; - } +/** @internal */ +export function copyProperties(first: T1, second: T2) { + for (const id in second) { + if (hasOwnProperty.call(second, id)) { + (first as any)[id] = second[id]; } - return result; } +} +/** @internal */ +export function maybeBind(obj: T, fn: ((this: T, ...args: A) => R) | undefined): ((...args: A) => R) | undefined { + return fn ? fn.bind(obj) : undefined; +} + +/** @internal */ +export interface MultiMap extends ESMap { /** - * Creates a new object by adding the own properties of `second`, then the own properties of `first`. - * - * NOTE: This means that if a property exists in both `first` and `second`, the property in `first` will be chosen. + * Adds the value to an array of values associated with the key, and returns the array. + * Creates the array if it does not already exist. */ - export function extend(first: T1, second: T2): T1 & T2 { - const result: T1 & T2 = {} as any; - for (const id in second) { - if (hasOwnProperty.call(second, id)) { - (result as any)[id] = (second as any)[id]; - } - } - - for (const id in first) { - if (hasOwnProperty.call(first, id)) { - (result as any)[id] = (first as any)[id]; - } - } + add(key: K, value: V): V[]; + /** + * Removes a value from an array of values associated with the key. + * Does not preserve the order of those values. + * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. + */ + remove(key: K, value: V): void; +} - return result; +/** @internal */ +export function createMultiMap(): MultiMap; +/** @internal */ +export function createMultiMap(): MultiMap; +/** @internal */ +export function createMultiMap(): MultiMap { + const map = new Map() as MultiMap; + map.add = multiMapAdd; + map.remove = multiMapRemove; + return map; +} +function multiMapAdd(this: MultiMap, key: K, value: V) { + let values = this.get(key); + if (values) { + values.push(value); } - - export function copyProperties(first: T1, second: T2) { - for (const id in second) { - if (hasOwnProperty.call(second, id)) { - (first as any)[id] = second[id]; - } + else { + this.set(key, values = [value]); + } + return values; +} +function multiMapRemove(this: MultiMap, key: K, value: V) { + const values = this.get(key); + if (values) { + unorderedRemoveItem(values, value); + if (!values.length) { + this.delete(key); } } +} - export function maybeBind(obj: T, fn: ((this: T, ...args: A) => R) | undefined): ((...args: A) => R) | undefined { - return fn ? fn.bind(obj) : undefined; - } +/** @internal */ +export interface UnderscoreEscapedMultiMap extends UnderscoreEscapedMap { + /** + * Adds the value to an array of values associated with the key, and returns the array. + * Creates the array if it does not already exist. + */ + add(key: __String, value: T): T[]; + /** + * Removes a value from an array of values associated with the key. + * Does not preserve the order of those values. + * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. + */ + remove(key: __String, value: T): void; +} - export interface MultiMap extends ESMap { - /** - * Adds the value to an array of values associated with the key, and returns the array. - * Creates the array if it does not already exist. - */ - add(key: K, value: V): V[]; - /** - * Removes a value from an array of values associated with the key. - * Does not preserve the order of those values. - * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. - */ - remove(key: K, value: V): void; - } +/** @internal */ +export function createUnderscoreEscapedMultiMap(): UnderscoreEscapedMultiMap { + return createMultiMap() as UnderscoreEscapedMultiMap; +} - export function createMultiMap(): MultiMap; - export function createMultiMap(): MultiMap; - export function createMultiMap(): MultiMap { - const map = new Map() as MultiMap; - map.add = multiMapAdd; - map.remove = multiMapRemove; - return map; - } - function multiMapAdd(this: MultiMap, key: K, value: V) { - let values = this.get(key); - if (values) { - values.push(value); - } - else { - this.set(key, values = [value]); - } - return values; - } - function multiMapRemove(this: MultiMap, key: K, value: V) { - const values = this.get(key); - if (values) { - unorderedRemoveItem(values, value); - if (!values.length) { - this.delete(key); - } - } - } +/** @internal */ +export function createQueue(items?: readonly T[]): Queue { + const elements: (T | undefined)[] = items?.slice() || []; + let headIndex = 0; - export interface UnderscoreEscapedMultiMap extends UnderscoreEscapedMap { - /** - * Adds the value to an array of values associated with the key, and returns the array. - * Creates the array if it does not already exist. - */ - add(key: __String, value: T): T[]; - /** - * Removes a value from an array of values associated with the key. - * Does not preserve the order of those values. - * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. - */ - remove(key: __String, value: T): void; + function isEmpty() { + return headIndex === elements.length; } - export function createUnderscoreEscapedMultiMap(): UnderscoreEscapedMultiMap { - return createMultiMap() as UnderscoreEscapedMultiMap; + function enqueue(...items: T[]) { + elements.push(...items); } - export function createQueue(items?: readonly T[]): Queue { - const elements: (T | undefined)[] = items?.slice() || []; - let headIndex = 0; - - function isEmpty() { - return headIndex === elements.length; - } - - function enqueue(...items: T[]) { - elements.push(...items); + function dequeue(): T { + if (isEmpty()) { + throw new Error("Queue is empty"); } - function dequeue(): T { - if (isEmpty()) { - throw new Error("Queue is empty"); - } - - const result = elements[headIndex] as T; - elements[headIndex] = undefined; // Don't keep referencing dequeued item - headIndex++; - - // If more than half of the queue is empty, copy the remaining elements to the - // front and shrink the array (unless we'd be saving fewer than 100 slots) - if (headIndex > 100 && headIndex > (elements.length >> 1)) { - const newLength = elements.length - headIndex; - elements.copyWithin(/*target*/ 0, /*start*/ headIndex); + const result = elements[headIndex] as T; + elements[headIndex] = undefined; // Don't keep referencing dequeued item + headIndex++; - elements.length = newLength; - headIndex = 0; - } + // If more than half of the queue is empty, copy the remaining elements to the + // front and shrink the array (unless we'd be saving fewer than 100 slots) + if (headIndex > 100 && headIndex > (elements.length >> 1)) { + const newLength = elements.length - headIndex; + elements.copyWithin(/*target*/ 0, /*start*/ headIndex); - return result; + elements.length = newLength; + headIndex = 0; } - return { - enqueue, - dequeue, - isEmpty, - }; + return result; } - /** - * Creates a Set with custom equality and hash code functionality. This is useful when you - * want to use something looser than object identity - e.g. "has the same span". - * - * If `equals(a, b)`, it must be the case that `getHashCode(a) === getHashCode(b)`. - * The converse is not required. - * - * To facilitate a perf optimization (lazy allocation of bucket arrays), `TElement` is - * assumed not to be an array type. - */ - export function createSet(getHashCode: (element: TElement) => THash, equals: EqualityComparer): Set { - const multiMap = new Map(); - let size = 0; + return { + enqueue, + dequeue, + isEmpty, + }; +} - function getElementIterator(): Iterator { - const valueIt = multiMap.values(); - let arrayIt: Iterator | undefined; - return { - next: () => { - while (true) { - if (arrayIt) { - const n = arrayIt.next(); - if (!n.done) { - return { value: n.value }; - } - arrayIt = undefined; +/** + * Creates a Set with custom equality and hash code functionality. This is useful when you + * want to use something looser than object identity - e.g. "has the same span". + * + * If `equals(a, b)`, it must be the case that `getHashCode(a) === getHashCode(b)`. + * The converse is not required. + * + * To facilitate a perf optimization (lazy allocation of bucket arrays), `TElement` is + * assumed not to be an array type. + * + * @internal + */ +export function createSet(getHashCode: (element: TElement) => THash, equals: EqualityComparer): Set { + const multiMap = new Map(); + let size = 0; + + function getElementIterator(): Iterator { + const valueIt = multiMap.values(); + let arrayIt: Iterator | undefined; + return { + next: () => { + while (true) { + if (arrayIt) { + const n = arrayIt.next(); + if (!n.done) { + return { value: n.value }; } - else { - const n = valueIt.next(); - if (n.done) { - return { value: undefined, done: true }; - } - if (!isArray(n.value)) { - return { value: n.value }; - } - arrayIt = arrayIterator(n.value); + arrayIt = undefined; + } + else { + const n = valueIt.next(); + if (n.done) { + return { value: undefined, done: true }; + } + if (!isArray(n.value)) { + return { value: n.value }; } + arrayIt = arrayIterator(n.value); } } - }; - } + } + }; + } - const set: Set = { - has(element: TElement): boolean { - const hash = getHashCode(element); - if (!multiMap.has(hash)) return false; - const candidates = multiMap.get(hash)!; - if (!isArray(candidates)) return equals(candidates, element); + const set: Set = { + has(element: TElement): boolean { + const hash = getHashCode(element); + if (!multiMap.has(hash)) return false; + const candidates = multiMap.get(hash)!; + if (!isArray(candidates)) return equals(candidates, element); - for (const candidate of candidates) { - if (equals(candidate, element)) { - return true; - } + for (const candidate of candidates) { + if (equals(candidate, element)) { + return true; } - return false; - }, - add(element: TElement): Set { - const hash = getHashCode(element); - if (multiMap.has(hash)) { - const values = multiMap.get(hash)!; - if (isArray(values)) { - if (!contains(values, element, equals)) { - values.push(element); - size++; - } - } - else { - const value = values; - if (!equals(value, element)) { - multiMap.set(hash, [ value, element ]); - size++; - } + } + return false; + }, + add(element: TElement): Set { + const hash = getHashCode(element); + if (multiMap.has(hash)) { + const values = multiMap.get(hash)!; + if (isArray(values)) { + if (!contains(values, element, equals)) { + values.push(element); + size++; } } else { - multiMap.set(hash, element); - size++; - } - - return this; - }, - delete(element: TElement): boolean { - const hash = getHashCode(element); - if (!multiMap.has(hash)) return false; - const candidates = multiMap.get(hash)!; - if (isArray(candidates)) { - for (let i = 0; i < candidates.length; i++) { - if (equals(candidates[i], element)) { - if (candidates.length === 1) { - multiMap.delete(hash); - } - else if (candidates.length === 2) { - multiMap.set(hash, candidates[1 - i]); - } - else { - unorderedRemoveItemAt(candidates, i); - } - size--; - return true; - } + const value = values; + if (!equals(value, element)) { + multiMap.set(hash, [ value, element ]); + size++; } } - else { - const candidate = candidates; - if (equals(candidate, element)) { - multiMap.delete(hash); + } + else { + multiMap.set(hash, element); + size++; + } + + return this; + }, + delete(element: TElement): boolean { + const hash = getHashCode(element); + if (!multiMap.has(hash)) return false; + const candidates = multiMap.get(hash)!; + if (isArray(candidates)) { + for (let i = 0; i < candidates.length; i++) { + if (equals(candidates[i], element)) { + if (candidates.length === 1) { + multiMap.delete(hash); + } + else if (candidates.length === 2) { + multiMap.set(hash, candidates[1 - i]); + } + else { + unorderedRemoveItemAt(candidates, i); + } size--; return true; } } + } + else { + const candidate = candidates; + if (equals(candidate, element)) { + multiMap.delete(hash); + size--; + return true; + } + } - return false; - }, - clear(): void { - multiMap.clear(); - size = 0; - }, - get size() { - return size; - }, - forEach(action: (value: TElement, key: TElement) => void): void { - for (const elements of arrayFrom(multiMap.values())) { - if (isArray(elements)) { - for (const element of elements) { - action(element, element); - } - } - else { - const element = elements; + return false; + }, + clear(): void { + multiMap.clear(); + size = 0; + }, + get size() { + return size; + }, + forEach(action: (value: TElement, key: TElement) => void): void { + for (const elements of arrayFrom(multiMap.values())) { + if (isArray(elements)) { + for (const element of elements) { action(element, element); } } - }, - keys(): Iterator { - return getElementIterator(); - }, - values(): Iterator { - return getElementIterator(); - }, - entries(): Iterator<[TElement, TElement]> { - const it = getElementIterator(); - return { - next: () => { - const n = it.next(); - return n.done ? n : { value: [ n.value, n.value ] }; - } - }; - }, - }; + else { + const element = elements; + action(element, element); + } + } + }, + keys(): Iterator { + return getElementIterator(); + }, + values(): Iterator { + return getElementIterator(); + }, + entries(): Iterator<[TElement, TElement]> { + const it = getElementIterator(); + return { + next: () => { + const n = it.next(); + return n.done ? n : { value: [ n.value, n.value ] }; + } + }; + }, + }; - return set; - } + return set; +} - /** - * Tests whether a value is an array. - */ - export function isArray(value: any): value is readonly unknown[] { - return Array.isArray ? Array.isArray(value) : value instanceof Array; - } +/** + * Tests whether a value is an array. + * + * @internal + */ +export function isArray(value: any): value is readonly unknown[] { + return Array.isArray ? Array.isArray(value) : value instanceof Array; +} - export function toArray(value: T | T[]): T[]; - export function toArray(value: T | readonly T[]): readonly T[]; - export function toArray(value: T | T[]): T[] { - return isArray(value) ? value : [value]; - } +/** @internal */ +export function toArray(value: T | T[]): T[]; +/** @internal */ +export function toArray(value: T | readonly T[]): readonly T[]; +/** @internal */ +export function toArray(value: T | T[]): T[] { + return isArray(value) ? value : [value]; +} - /** - * Tests whether a value is string - */ - export function isString(text: unknown): text is string { - return typeof text === "string"; - } - export function isNumber(x: unknown): x is number { - return typeof x === "number"; - } +/** + * Tests whether a value is string + * + * @internal + */ +export function isString(text: unknown): text is string { + return typeof text === "string"; +} +/** @internal */ +export function isNumber(x: unknown): x is number { + return typeof x === "number"; +} - export function tryCast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined; - export function tryCast(value: T, test: (value: T) => boolean): T | undefined; - export function tryCast(value: T, test: (value: T) => boolean): T | undefined { - return value !== undefined && test(value) ? value : undefined; - } +/** @internal */ +export function tryCast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined; +/** @internal */ +export function tryCast(value: T, test: (value: T) => boolean): T | undefined; +/** @internal */ +export function tryCast(value: T, test: (value: T) => boolean): T | undefined { + return value !== undefined && test(value) ? value : undefined; +} - export function cast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut { - if (value !== undefined && test(value)) return value; +/** @internal */ +export function cast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut { + if (value !== undefined && test(value)) return value; - return Debug.fail(`Invalid cast. The supplied value ${value} did not pass the test '${Debug.getFunctionName(test)}'.`); - } + return Debug.fail(`Invalid cast. The supplied value ${value} did not pass the test '${Debug.getFunctionName(test)}'.`); +} - /** Does nothing. */ - export function noop(_?: unknown): void { } +/** + * Does nothing. + * + * @internal + */ +export function noop(_?: unknown): void { } + +/** @internal */ +export const noopPush: Push = { + push: noop, + length: 0 +}; + +/** + * Do nothing and return false + * + * @internal + */ +export function returnFalse(): false { + return false; +} + +/** + * Do nothing and return true + * + * @internal + */ +export function returnTrue(): true { + return true; +} + +/** + * Do nothing and return undefined + * + * @internal + */ +export function returnUndefined(): undefined { + return undefined; +} + +/** + * Returns its argument. + * + * @internal + */ +export function identity(x: T) { + return x; +} + +/** + * Returns lower case string + * + * @internal + */ +export function toLowerCase(x: string) { + return x.toLowerCase(); +} + +// We convert the file names to lower case as key for file name on case insensitive file system +// While doing so we need to handle special characters (eg \u0130) to ensure that we dont convert +// it to lower case, fileName with its lowercase form can exist along side it. +// Handle special characters and make those case sensitive instead +// +// |-#--|-Unicode--|-Char code-|-Desc-------------------------------------------------------------------| +// | 1. | i | 105 | Ascii i | +// | 2. | I | 73 | Ascii I | +// |-------- Special characters ------------------------------------------------------------------------| +// | 3. | \u0130 | 304 | Upper case I with dot above | +// | 4. | i,\u0307 | 105,775 | i, followed by 775: Lower case of (3rd item) | +// | 5. | I,\u0307 | 73,775 | I, followed by 775: Upper case of (4th item), lower case is (4th item) | +// | 6. | \u0131 | 305 | Lower case i without dot, upper case is I (2nd item) | +// | 7. | \u00DF | 223 | Lower case sharp s | +// +// Because item 3 is special where in its lowercase character has its own +// upper case form we cant convert its case. +// Rest special characters are either already in lower case format or +// they have corresponding upper case character so they dont need special handling +// +// But to avoid having to do string building for most common cases, also ignore +// a-z, 0-9, \u0131, \u00DF, \, /, ., : and space +const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g; +/** + * Case insensitive file systems have descripencies in how they handle some characters (eg. turkish Upper case I with dot on top - \u0130) + * This function is used in places where we want to make file name as a key on these systems + * It is possible on mac to be able to refer to file name with I with dot on top as a fileName with its lower case form + * But on windows we cannot. Windows can have fileName with I with dot on top next to its lower case and they can not each be referred with the lowercase forms + * Technically we would want this function to be platform sepcific as well but + * our api has till now only taken caseSensitive as the only input and just for some characters we dont want to update API and ensure all customers use those api + * We could use upper case and we would still need to deal with the descripencies but + * we want to continue using lower case since in most cases filenames are lowercasewe and wont need any case changes and avoid having to store another string for the key + * So for this function purpose, we go ahead and assume character I with dot on top it as case sensitive since its very unlikely to use lower case form of that special character + * + * @internal + */ +export function toFileNameLowerCase(x: string) { + return fileNameLowerCaseRegExp.test(x) ? + x.replace(fileNameLowerCaseRegExp, toLowerCase) : + x; +} + +/** + * Throws an error because a function is not implemented. + * + * @internal + */ +export function notImplemented(): never { + throw new Error("Not implemented"); +} + +/** @internal */ +export function memoize(callback: () => T): () => T { + let value: T; + return () => { + if (callback) { + value = callback(); + callback = undefined!; + } + return value; + }; +} - export const noopPush: Push = { - push: noop, - length: 0 +/** + * A version of `memoize` that supports a single primitive argument + * + * @internal + */ +export function memoizeOne(callback: (arg: A) => T): (arg: A) => T { + const map = new Map(); + return (arg: A) => { + const key = `${typeof arg}:${arg}`; + let value = map.get(key); + if (value === undefined && !map.has(key)) { + value = callback(arg); + map.set(key, value); + } + return value!; }; +} - /** Do nothing and return false */ - export function returnFalse(): false { - return false; - } +/** + * High-order function, composes functions. Note that functions are composed inside-out; + * for example, `compose(a, b)` is the equivalent of `x => b(a(x))`. + * + * @param args The functions to compose. + * + * @internal + */ +export function compose(...args: ((t: T) => T)[]): (t: T) => T; +/** @internal */ +export function compose(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T { + if (!!e) { + const args: ((t: T) => T)[] = []; + for (let i = 0; i < arguments.length; i++) { + args[i] = arguments[i]; + } - /** Do nothing and return true */ - export function returnTrue(): true { - return true; + return t => reduceLeft(args, (u, f) => f(u), t); } - - /** Do nothing and return undefined */ - export function returnUndefined(): undefined { - return undefined; + else if (d) { + return t => d(c(b(a(t)))); } - - /** Returns its argument. */ - export function identity(x: T) { - return x; + else if (c) { + return t => c(b(a(t))); } - - /** Returns lower case string */ - export function toLowerCase(x: string) { - return x.toLowerCase(); + else if (b) { + return t => b(a(t)); } - - // We convert the file names to lower case as key for file name on case insensitive file system - // While doing so we need to handle special characters (eg \u0130) to ensure that we dont convert - // it to lower case, fileName with its lowercase form can exist along side it. - // Handle special characters and make those case sensitive instead - // - // |-#--|-Unicode--|-Char code-|-Desc-------------------------------------------------------------------| - // | 1. | i | 105 | Ascii i | - // | 2. | I | 73 | Ascii I | - // |-------- Special characters ------------------------------------------------------------------------| - // | 3. | \u0130 | 304 | Upper case I with dot above | - // | 4. | i,\u0307 | 105,775 | i, followed by 775: Lower case of (3rd item) | - // | 5. | I,\u0307 | 73,775 | I, followed by 775: Upper case of (4th item), lower case is (4th item) | - // | 6. | \u0131 | 305 | Lower case i without dot, upper case is I (2nd item) | - // | 7. | \u00DF | 223 | Lower case sharp s | - // - // Because item 3 is special where in its lowercase character has its own - // upper case form we cant convert its case. - // Rest special characters are either already in lower case format or - // they have corresponding upper case character so they dont need special handling - // - // But to avoid having to do string building for most common cases, also ignore - // a-z, 0-9, \u0131, \u00DF, \, /, ., : and space - const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g; - /** - * Case insensitive file systems have descripencies in how they handle some characters (eg. turkish Upper case I with dot on top - \u0130) - * This function is used in places where we want to make file name as a key on these systems - * It is possible on mac to be able to refer to file name with I with dot on top as a fileName with its lower case form - * But on windows we cannot. Windows can have fileName with I with dot on top next to its lower case and they can not each be referred with the lowercase forms - * Technically we would want this function to be platform sepcific as well but - * our api has till now only taken caseSensitive as the only input and just for some characters we dont want to update API and ensure all customers use those api - * We could use upper case and we would still need to deal with the descripencies but - * we want to continue using lower case since in most cases filenames are lowercasewe and wont need any case changes and avoid having to store another string for the key - * So for this function purpose, we go ahead and assume character I with dot on top it as case sensitive since its very unlikely to use lower case form of that special character - */ - export function toFileNameLowerCase(x: string) { - return fileNameLowerCaseRegExp.test(x) ? - x.replace(fileNameLowerCaseRegExp, toLowerCase) : - x; + else if (a) { + return t => a(t); } - - /** Throws an error because a function is not implemented. */ - export function notImplemented(): never { - throw new Error("Not implemented"); + else { + return t => t; } +} - export function memoize(callback: () => T): () => T { - let value: T; - return () => { - if (callback) { - value = callback(); - callback = undefined!; - } - return value; - }; - } +/** @internal */ +export const enum AssertionLevel { + None = 0, + Normal = 1, + Aggressive = 2, + VeryAggressive = 3, +} - /** A version of `memoize` that supports a single primitive argument */ - export function memoizeOne(callback: (arg: A) => T): (arg: A) => T { - const map = new Map(); - return (arg: A) => { - const key = `${typeof arg}:${arg}`; - let value = map.get(key); - if (value === undefined && !map.has(key)) { - value = callback(arg); - map.set(key, value); - } - return value!; - }; - } +/** + * Safer version of `Function` which should not be called. + * Every function should be assignable to this, but this should not be assignable to every function. + * + * @internal + */ +export type AnyFunction = (...args: never[]) => void; +/** @internal */ +export type AnyConstructor = new (...args: unknown[]) => unknown; + +/** @internal */ +export function equateValues(a: T, b: T) { + return a === b; +} - /** - * High-order function, composes functions. Note that functions are composed inside-out; - * for example, `compose(a, b)` is the equivalent of `x => b(a(x))`. - * - * @param args The functions to compose. - */ - export function compose(...args: ((t: T) => T)[]): (t: T) => T; - export function compose(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T { - if (!!e) { - const args: ((t: T) => T)[] = []; - for (let i = 0; i < arguments.length; i++) { - args[i] = arguments[i]; - } +/** + * Compare the equality of two strings using a case-sensitive ordinal comparison. + * + * Case-sensitive comparisons compare both strings one code-point at a time using the integer + * value of each code-point after applying `toUpperCase` to each string. We always map both + * strings to their upper-case form as some unicode characters do not properly round-trip to + * lowercase (such as `ẞ` (German sharp capital s)). + * + * @internal + */ +export function equateStringsCaseInsensitive(a: string, b: string) { + return a === b + || a !== undefined + && b !== undefined + && a.toUpperCase() === b.toUpperCase(); +} - return t => reduceLeft(args, (u, f) => f(u), t); - } - else if (d) { - return t => d(c(b(a(t)))); - } - else if (c) { - return t => c(b(a(t))); - } - else if (b) { - return t => b(a(t)); - } - else if (a) { - return t => a(t); - } - else { - return t => t; - } - } +/** + * Compare the equality of two strings using a case-sensitive ordinal comparison. + * + * Case-sensitive comparisons compare both strings one code-point at a time using the + * integer value of each code-point. + * + * @internal + */ +export function equateStringsCaseSensitive(a: string, b: string) { + return equateValues(a, b); +} - export const enum AssertionLevel { - None = 0, - Normal = 1, - Aggressive = 2, - VeryAggressive = 3, - } +function compareComparableValues(a: string | undefined, b: string | undefined): Comparison; +function compareComparableValues(a: number | undefined, b: number | undefined): Comparison; +function compareComparableValues(a: string | number | undefined, b: string | number | undefined) { + return a === b ? Comparison.EqualTo : + a === undefined ? Comparison.LessThan : + b === undefined ? Comparison.GreaterThan : + a < b ? Comparison.LessThan : + Comparison.GreaterThan; +} - /** - * Safer version of `Function` which should not be called. - * Every function should be assignable to this, but this should not be assignable to every function. - */ - export type AnyFunction = (...args: never[]) => void; - export type AnyConstructor = new (...args: unknown[]) => unknown; +/** + * Compare two numeric values for their order relative to each other. + * To compare strings, use any of the `compareStrings` functions. + * + * @internal + */ +export function compareValues(a: number | undefined, b: number | undefined): Comparison { + return compareComparableValues(a, b); +} - export function equateValues(a: T, b: T) { - return a === b; - } +/** + * Compare two TextSpans, first by `start`, then by `length`. + * + * @internal + */ +export function compareTextSpans(a: Partial | undefined, b: Partial | undefined): Comparison { + return compareValues(a?.start, b?.start) || compareValues(a?.length, b?.length); +} - /** - * Compare the equality of two strings using a case-sensitive ordinal comparison. - * - * Case-sensitive comparisons compare both strings one code-point at a time using the integer - * value of each code-point after applying `toUpperCase` to each string. We always map both - * strings to their upper-case form as some unicode characters do not properly round-trip to - * lowercase (such as `ẞ` (German sharp capital s)). - */ - export function equateStringsCaseInsensitive(a: string, b: string) { - return a === b - || a !== undefined - && b !== undefined - && a.toUpperCase() === b.toUpperCase(); - } +/** @internal */ +export function min(items: readonly [T, ...T[]], compare: Comparer): T; +/** @internal */ +export function min(items: readonly T[], compare: Comparer): T | undefined; +/** @internal */ +export function min(items: readonly T[], compare: Comparer): T | undefined { + return reduceLeft(items, (x, y) => compare(x, y) === Comparison.LessThan ? x : y); +} - /** - * Compare the equality of two strings using a case-sensitive ordinal comparison. - * - * Case-sensitive comparisons compare both strings one code-point at a time using the - * integer value of each code-point. - */ - export function equateStringsCaseSensitive(a: string, b: string) { - return equateValues(a, b); - } +/** + * Compare two strings using a case-insensitive ordinal comparison. + * + * Ordinal comparisons are based on the difference between the unicode code points of both + * strings. Characters with multiple unicode representations are considered unequal. Ordinal + * comparisons provide predictable ordering, but place "a" after "B". + * + * Case-insensitive comparisons compare both strings one code-point at a time using the integer + * value of each code-point after applying `toUpperCase` to each string. We always map both + * strings to their upper-case form as some unicode characters do not properly round-trip to + * lowercase (such as `ẞ` (German sharp capital s)). + * + * @internal + */ +export function compareStringsCaseInsensitive(a: string, b: string) { + if (a === b) return Comparison.EqualTo; + if (a === undefined) return Comparison.LessThan; + if (b === undefined) return Comparison.GreaterThan; + a = a.toUpperCase(); + b = b.toUpperCase(); + return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; +} - function compareComparableValues(a: string | undefined, b: string | undefined): Comparison; - function compareComparableValues(a: number | undefined, b: number | undefined): Comparison; - function compareComparableValues(a: string | number | undefined, b: string | number | undefined) { - return a === b ? Comparison.EqualTo : - a === undefined ? Comparison.LessThan : - b === undefined ? Comparison.GreaterThan : - a < b ? Comparison.LessThan : - Comparison.GreaterThan; - } +/** + * Compare two strings using a case-sensitive ordinal comparison. + * + * Ordinal comparisons are based on the difference between the unicode code points of both + * strings. Characters with multiple unicode representations are considered unequal. Ordinal + * comparisons provide predictable ordering, but place "a" after "B". + * + * Case-sensitive comparisons compare both strings one code-point at a time using the integer + * value of each code-point. + * + * @internal + */ +export function compareStringsCaseSensitive(a: string | undefined, b: string | undefined): Comparison { + return compareComparableValues(a, b); +} - /** - * Compare two numeric values for their order relative to each other. - * To compare strings, use any of the `compareStrings` functions. - */ - export function compareValues(a: number | undefined, b: number | undefined): Comparison { - return compareComparableValues(a, b); - } +/** @internal */ +export function getStringComparer(ignoreCase?: boolean) { + return ignoreCase ? compareStringsCaseInsensitive : compareStringsCaseSensitive; +} - /** - * Compare two TextSpans, first by `start`, then by `length`. - */ - export function compareTextSpans(a: Partial | undefined, b: Partial | undefined): Comparison { - return compareValues(a?.start, b?.start) || compareValues(a?.length, b?.length); - } +/** + * Creates a string comparer for use with string collation in the UI. + */ +const createUIStringComparer = (() => { + let defaultComparer: Comparer | undefined; + let enUSComparer: Comparer | undefined; - export function min(items: readonly [T, ...T[]], compare: Comparer): T; - export function min(items: readonly T[], compare: Comparer): T | undefined; - export function min(items: readonly T[], compare: Comparer): T | undefined { - return reduceLeft(items, (x, y) => compare(x, y) === Comparison.LessThan ? x : y); - } + const stringComparerFactory = getStringComparerFactory(); + return createStringComparer; - /** - * Compare two strings using a case-insensitive ordinal comparison. - * - * Ordinal comparisons are based on the difference between the unicode code points of both - * strings. Characters with multiple unicode representations are considered unequal. Ordinal - * comparisons provide predictable ordering, but place "a" after "B". - * - * Case-insensitive comparisons compare both strings one code-point at a time using the integer - * value of each code-point after applying `toUpperCase` to each string. We always map both - * strings to their upper-case form as some unicode characters do not properly round-trip to - * lowercase (such as `ẞ` (German sharp capital s)). - */ - export function compareStringsCaseInsensitive(a: string, b: string) { + function compareWithCallback(a: string | undefined, b: string | undefined, comparer: (a: string, b: string) => number) { if (a === b) return Comparison.EqualTo; if (a === undefined) return Comparison.LessThan; if (b === undefined) return Comparison.GreaterThan; - a = a.toUpperCase(); - b = b.toUpperCase(); - return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; - } - - /** - * Compare two strings using a case-sensitive ordinal comparison. - * - * Ordinal comparisons are based on the difference between the unicode code points of both - * strings. Characters with multiple unicode representations are considered unequal. Ordinal - * comparisons provide predictable ordering, but place "a" after "B". - * - * Case-sensitive comparisons compare both strings one code-point at a time using the integer - * value of each code-point. - */ - export function compareStringsCaseSensitive(a: string | undefined, b: string | undefined): Comparison { - return compareComparableValues(a, b); + const value = comparer(a, b); + return value < 0 ? Comparison.LessThan : value > 0 ? Comparison.GreaterThan : Comparison.EqualTo; } - export function getStringComparer(ignoreCase?: boolean) { - return ignoreCase ? compareStringsCaseInsensitive : compareStringsCaseSensitive; + function createIntlCollatorStringComparer(locale: string | undefined): Comparer { + // Intl.Collator.prototype.compare is bound to the collator. See NOTE in + // http://www.ecma-international.org/ecma-402/2.0/#sec-Intl.Collator.prototype.compare + const comparer = new Intl.Collator(locale, { usage: "sort", sensitivity: "variant" }).compare; + return (a, b) => compareWithCallback(a, b, comparer); } - /** - * Creates a string comparer for use with string collation in the UI. - */ - const createUIStringComparer = (() => { - let defaultComparer: Comparer | undefined; - let enUSComparer: Comparer | undefined; + function createLocaleCompareStringComparer(locale: string | undefined): Comparer { + // if the locale is not the default locale (`undefined`), use the fallback comparer. + if (locale !== undefined) return createFallbackStringComparer(); - const stringComparerFactory = getStringComparerFactory(); - return createStringComparer; - - function compareWithCallback(a: string | undefined, b: string | undefined, comparer: (a: string, b: string) => number) { - if (a === b) return Comparison.EqualTo; - if (a === undefined) return Comparison.LessThan; - if (b === undefined) return Comparison.GreaterThan; - const value = comparer(a, b); - return value < 0 ? Comparison.LessThan : value > 0 ? Comparison.GreaterThan : Comparison.EqualTo; - } + return (a, b) => compareWithCallback(a, b, compareStrings); - function createIntlCollatorStringComparer(locale: string | undefined): Comparer { - // Intl.Collator.prototype.compare is bound to the collator. See NOTE in - // http://www.ecma-international.org/ecma-402/2.0/#sec-Intl.Collator.prototype.compare - const comparer = new Intl.Collator(locale, { usage: "sort", sensitivity: "variant" }).compare; - return (a, b) => compareWithCallback(a, b, comparer); + function compareStrings(a: string, b: string) { + return a.localeCompare(b); } + } - function createLocaleCompareStringComparer(locale: string | undefined): Comparer { - // if the locale is not the default locale (`undefined`), use the fallback comparer. - if (locale !== undefined) return createFallbackStringComparer(); - - return (a, b) => compareWithCallback(a, b, compareStrings); + function createFallbackStringComparer(): Comparer { + // An ordinal comparison puts "A" after "b", but for the UI we want "A" before "b". + // We first sort case insensitively. So "Aaa" will come before "baa". + // Then we sort case sensitively, so "aaa" will come before "Aaa". + // + // For case insensitive comparisons we always map both strings to their + // upper-case form as some unicode characters do not properly round-trip to + // lowercase (such as `ẞ` (German sharp capital s)). + return (a, b) => compareWithCallback(a, b, compareDictionaryOrder); - function compareStrings(a: string, b: string) { - return a.localeCompare(b); - } + function compareDictionaryOrder(a: string, b: string) { + return compareStrings(a.toUpperCase(), b.toUpperCase()) || compareStrings(a, b); } - function createFallbackStringComparer(): Comparer { - // An ordinal comparison puts "A" after "b", but for the UI we want "A" before "b". - // We first sort case insensitively. So "Aaa" will come before "baa". - // Then we sort case sensitively, so "aaa" will come before "Aaa". - // - // For case insensitive comparisons we always map both strings to their - // upper-case form as some unicode characters do not properly round-trip to - // lowercase (such as `ẞ` (German sharp capital s)). - return (a, b) => compareWithCallback(a, b, compareDictionaryOrder); - - function compareDictionaryOrder(a: string, b: string) { - return compareStrings(a.toUpperCase(), b.toUpperCase()) || compareStrings(a, b); - } - - function compareStrings(a: string, b: string) { - return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; - } + function compareStrings(a: string, b: string) { + return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo; } + } - function getStringComparerFactory() { - // If the host supports Intl, we use it for comparisons using the default locale. - if (typeof Intl === "object" && typeof Intl.Collator === "function") { - return createIntlCollatorStringComparer; - } - - // If the host does not support Intl, we fall back to localeCompare. - // localeCompare in Node v0.10 is just an ordinal comparison, so don't use it. - if (typeof String.prototype.localeCompare === "function" && - typeof String.prototype.toLocaleUpperCase === "function" && - "a".localeCompare("B") < 0) { - return createLocaleCompareStringComparer; - } - - // Otherwise, fall back to ordinal comparison: - return createFallbackStringComparer; + function getStringComparerFactory() { + // If the host supports Intl, we use it for comparisons using the default locale. + if (typeof Intl === "object" && typeof Intl.Collator === "function") { + return createIntlCollatorStringComparer; } - function createStringComparer(locale: string | undefined) { - // Hold onto common string comparers. This avoids constantly reallocating comparers during - // tests. - if (locale === undefined) { - return defaultComparer || (defaultComparer = stringComparerFactory(locale)); - } - else if (locale === "en-US") { - return enUSComparer || (enUSComparer = stringComparerFactory(locale)); - } - else { - return stringComparerFactory(locale); - } + // If the host does not support Intl, we fall back to localeCompare. + // localeCompare in Node v0.10 is just an ordinal comparison, so don't use it. + if (typeof String.prototype.localeCompare === "function" && + typeof String.prototype.toLocaleUpperCase === "function" && + "a".localeCompare("B") < 0) { + return createLocaleCompareStringComparer; } - })(); - let uiComparerCaseSensitive: Comparer | undefined; - let uiLocale: string | undefined; - - export function getUILocale() { - return uiLocale; + // Otherwise, fall back to ordinal comparison: + return createFallbackStringComparer; } - export function setUILocale(value: string | undefined) { - if (uiLocale !== value) { - uiLocale = value; - uiComparerCaseSensitive = undefined; + function createStringComparer(locale: string | undefined) { + // Hold onto common string comparers. This avoids constantly reallocating comparers during + // tests. + if (locale === undefined) { + return defaultComparer || (defaultComparer = stringComparerFactory(locale)); + } + else if (locale === "en-US") { + return enUSComparer || (enUSComparer = stringComparerFactory(locale)); + } + else { + return stringComparerFactory(locale); } } +})(); - /** - * Compare two strings in a using the case-sensitive sort behavior of the UI locale. - * - * Ordering is not predictable between different host locales, but is best for displaying - * ordered data for UI presentation. Characters with multiple unicode representations may - * be considered equal. - * - * Case-sensitive comparisons compare strings that differ in base characters, or - * accents/diacritic marks, or case as unequal. - */ - export function compareStringsCaseSensitiveUI(a: string, b: string) { - const comparer = uiComparerCaseSensitive || (uiComparerCaseSensitive = createUIStringComparer(uiLocale)); - return comparer(a, b); - } +let uiComparerCaseSensitive: Comparer | undefined; +let uiLocale: string | undefined; - export function compareProperties(a: T | undefined, b: T | undefined, key: K, comparer: Comparer): Comparison { - return a === b ? Comparison.EqualTo : - a === undefined ? Comparison.LessThan : - b === undefined ? Comparison.GreaterThan : - comparer(a[key], b[key]); - } +/** @internal */ +export function getUILocale() { + return uiLocale; +} - /** True is greater than false. */ - export function compareBooleans(a: boolean, b: boolean): Comparison { - return compareValues(a ? 1 : 0, b ? 1 : 0); +/** @internal */ +export function setUILocale(value: string | undefined) { + if (uiLocale !== value) { + uiLocale = value; + uiComparerCaseSensitive = undefined; } +} - /** - * Given a name and a list of names that are *not* equal to the name, return a spelling suggestion if there is one that is close enough. - * Names less than length 3 only check for case-insensitive equality. - * - * find the candidate with the smallest Levenshtein distance, - * except for candidates: - * * With no name - * * Whose length differs from the target name by more than 0.34 of the length of the name. - * * Whose levenshtein distance is more than 0.4 of the length of the name - * (0.4 allows 1 substitution/transposition for every 5 characters, - * and 1 insertion/deletion at 3 characters) - */ - export function getSpellingSuggestion(name: string, candidates: T[], getName: (candidate: T) => string | undefined): T | undefined { - const maximumLengthDifference = Math.max(2, Math.floor(name.length * 0.34)); - let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result is worse than this, don't bother. - let bestCandidate: T | undefined; - for (const candidate of candidates) { - const candidateName = getName(candidate); - if (candidateName !== undefined && Math.abs(candidateName.length - name.length) <= maximumLengthDifference) { - if (candidateName === name) { - continue; - } - // Only consider candidates less than 3 characters long when they differ by case. - // Otherwise, don't bother, since a user would usually notice differences of a 2-character name. - if (candidateName.length < 3 && candidateName.toLowerCase() !== name.toLowerCase()) { - continue; - } - - const distance = levenshteinWithMax(name, candidateName, bestDistance - 0.1); - if (distance === undefined) { - continue; - } +/** + * Compare two strings in a using the case-sensitive sort behavior of the UI locale. + * + * Ordering is not predictable between different host locales, but is best for displaying + * ordered data for UI presentation. Characters with multiple unicode representations may + * be considered equal. + * + * Case-sensitive comparisons compare strings that differ in base characters, or + * accents/diacritic marks, or case as unequal. + * + * @internal + */ +export function compareStringsCaseSensitiveUI(a: string, b: string) { + const comparer = uiComparerCaseSensitive || (uiComparerCaseSensitive = createUIStringComparer(uiLocale)); + return comparer(a, b); +} - Debug.assert(distance < bestDistance); // Else `levenshteinWithMax` should return undefined - bestDistance = distance; - bestCandidate = candidate; - } - } - return bestCandidate; - } +/** @internal */ +export function compareProperties(a: T | undefined, b: T | undefined, key: K, comparer: Comparer): Comparison { + return a === b ? Comparison.EqualTo : + a === undefined ? Comparison.LessThan : + b === undefined ? Comparison.GreaterThan : + comparer(a[key], b[key]); +} - function levenshteinWithMax(s1: string, s2: string, max: number): number | undefined { - let previous = new Array(s2.length + 1); - let current = new Array(s2.length + 1); - /** Represents any value > max. We don't care about the particular value. */ - const big = max + 0.01; - for (let i = 0; i <= s2.length; i++) { - previous[i] = i; - } +/** + * True is greater than false. + * + * @internal + */ +export function compareBooleans(a: boolean, b: boolean): Comparison { + return compareValues(a ? 1 : 0, b ? 1 : 0); +} - for (let i = 1; i <= s1.length; i++) { - const c1 = s1.charCodeAt(i - 1); - const minJ = Math.ceil(i > max ? i - max : 1); - const maxJ = Math.floor(s2.length > max + i ? max + i : s2.length); - current[0] = i; - /** Smallest value of the matrix in the ith column. */ - let colMin = i; - for (let j = 1; j < minJ; j++) { - current[j] = big; - } - for (let j = minJ; j <= maxJ; j++) { - // case difference should be significantly cheaper than other differences - const substitutionDistance = s1[i - 1].toLowerCase() === s2[j-1].toLowerCase() - ? (previous[j - 1] + 0.1) - : (previous[j - 1] + 2); - const dist = c1 === s2.charCodeAt(j - 1) - ? previous[j - 1] - : Math.min(/*delete*/ previous[j] + 1, /*insert*/ current[j - 1] + 1, /*substitute*/ substitutionDistance); - current[j] = dist; - colMin = Math.min(colMin, dist); - } - for (let j = maxJ + 1; j <= s2.length; j++) { - current[j] = big; - } - if (colMin > max) { - // Give up -- everything in this column is > max and it can't get better in future columns. - return undefined; - } +/** + * Given a name and a list of names that are *not* equal to the name, return a spelling suggestion if there is one that is close enough. + * Names less than length 3 only check for case-insensitive equality. + * + * find the candidate with the smallest Levenshtein distance, + * except for candidates: + * * With no name + * * Whose length differs from the target name by more than 0.34 of the length of the name. + * * Whose levenshtein distance is more than 0.4 of the length of the name + * (0.4 allows 1 substitution/transposition for every 5 characters, + * and 1 insertion/deletion at 3 characters) + * + * @internal + */ +export function getSpellingSuggestion(name: string, candidates: T[], getName: (candidate: T) => string | undefined): T | undefined { + const maximumLengthDifference = Math.max(2, Math.floor(name.length * 0.34)); + let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result is worse than this, don't bother. + let bestCandidate: T | undefined; + for (const candidate of candidates) { + const candidateName = getName(candidate); + if (candidateName !== undefined && Math.abs(candidateName.length - name.length) <= maximumLengthDifference) { + if (candidateName === name) { + continue; + } + // Only consider candidates less than 3 characters long when they differ by case. + // Otherwise, don't bother, since a user would usually notice differences of a 2-character name. + if (candidateName.length < 3 && candidateName.toLowerCase() !== name.toLowerCase()) { + continue; + } + + const distance = levenshteinWithMax(name, candidateName, bestDistance - 0.1); + if (distance === undefined) { + continue; + } + + Debug.assert(distance < bestDistance); // Else `levenshteinWithMax` should return undefined + bestDistance = distance; + bestCandidate = candidate; + } + } + return bestCandidate; +} - const temp = previous; - previous = current; - current = temp; +function levenshteinWithMax(s1: string, s2: string, max: number): number | undefined { + let previous = new Array(s2.length + 1); + let current = new Array(s2.length + 1); + /** Represents any value > max. We don't care about the particular value. */ + const big = max + 0.01; + + for (let i = 0; i <= s2.length; i++) { + previous[i] = i; + } + + for (let i = 1; i <= s1.length; i++) { + const c1 = s1.charCodeAt(i - 1); + const minJ = Math.ceil(i > max ? i - max : 1); + const maxJ = Math.floor(s2.length > max + i ? max + i : s2.length); + current[0] = i; + /** Smallest value of the matrix in the ith column. */ + let colMin = i; + for (let j = 1; j < minJ; j++) { + current[j] = big; + } + for (let j = minJ; j <= maxJ; j++) { + // case difference should be significantly cheaper than other differences + const substitutionDistance = s1[i - 1].toLowerCase() === s2[j-1].toLowerCase() + ? (previous[j - 1] + 0.1) + : (previous[j - 1] + 2); + const dist = c1 === s2.charCodeAt(j - 1) + ? previous[j - 1] + : Math.min(/*delete*/ previous[j] + 1, /*insert*/ current[j - 1] + 1, /*substitute*/ substitutionDistance); + current[j] = dist; + colMin = Math.min(colMin, dist); + } + for (let j = maxJ + 1; j <= s2.length; j++) { + current[j] = big; + } + if (colMin > max) { + // Give up -- everything in this column is > max and it can't get better in future columns. + return undefined; } - const res = previous[s2.length]; - return res > max ? undefined : res; + const temp = previous; + previous = current; + current = temp; } - export function endsWith(str: string, suffix: string): boolean { - const expectedPos = str.length - suffix.length; - return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos; - } + const res = previous[s2.length]; + return res > max ? undefined : res; +} - export function removeSuffix(str: string, suffix: string): string { - return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : str; - } +/** @internal */ +export function endsWith(str: string, suffix: string): boolean { + const expectedPos = str.length - suffix.length; + return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos; +} - export function tryRemoveSuffix(str: string, suffix: string): string | undefined { - return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : undefined; - } +/** @internal */ +export function removeSuffix(str: string, suffix: string): string { + return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : str; +} - export function stringContains(str: string, substring: string): boolean { - return str.indexOf(substring) !== -1; - } +/** @internal */ +export function tryRemoveSuffix(str: string, suffix: string): string | undefined { + return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : undefined; +} - /** - * Takes a string like "jquery-min.4.2.3" and returns "jquery" - */ - export function removeMinAndVersionNumbers(fileName: string) { - // We used to use the regex /[.-]((min)|(\d+(\.\d+)*))$/ and would just .replace it twice. - // Unfortunately, that regex has O(n^2) performance because v8 doesn't match from the end of the string. - // Instead, we now essentially scan the filename (backwards) ourselves. - - let end: number = fileName.length; - - for (let pos = end - 1; pos > 0; pos--) { - let ch: number = fileName.charCodeAt(pos); - if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { - // Match a \d+ segment - do { - --pos; - ch = fileName.charCodeAt(pos); - } while (pos > 0 && ch >= CharacterCodes._0 && ch <= CharacterCodes._9); - } - else if (pos > 4 && (ch === CharacterCodes.n || ch === CharacterCodes.N)) { - // Looking for "min" or "min" - // Already matched the 'n' - --pos; - ch = fileName.charCodeAt(pos); - if (ch !== CharacterCodes.i && ch !== CharacterCodes.I) { - break; - } - --pos; - ch = fileName.charCodeAt(pos); - if (ch !== CharacterCodes.m && ch !== CharacterCodes.M) { - break; - } +/** @internal */ +export function stringContains(str: string, substring: string): boolean { + return str.indexOf(substring) !== -1; +} + +/** + * Takes a string like "jquery-min.4.2.3" and returns "jquery" + * + * @internal + */ +export function removeMinAndVersionNumbers(fileName: string) { + // We used to use the regex /[.-]((min)|(\d+(\.\d+)*))$/ and would just .replace it twice. + // Unfortunately, that regex has O(n^2) performance because v8 doesn't match from the end of the string. + // Instead, we now essentially scan the filename (backwards) ourselves. + + let end: number = fileName.length; + + for (let pos = end - 1; pos > 0; pos--) { + let ch: number = fileName.charCodeAt(pos); + if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { + // Match a \d+ segment + do { --pos; ch = fileName.charCodeAt(pos); - } - else { - // This character is not part of either suffix pattern + } while (pos > 0 && ch >= CharacterCodes._0 && ch <= CharacterCodes._9); + } + else if (pos > 4 && (ch === CharacterCodes.n || ch === CharacterCodes.N)) { + // Looking for "min" or "min" + // Already matched the 'n' + --pos; + ch = fileName.charCodeAt(pos); + if (ch !== CharacterCodes.i && ch !== CharacterCodes.I) { break; } - - if (ch !== CharacterCodes.minus && ch !== CharacterCodes.dot) { + --pos; + ch = fileName.charCodeAt(pos); + if (ch !== CharacterCodes.m && ch !== CharacterCodes.M) { break; } + --pos; + ch = fileName.charCodeAt(pos); + } + else { + // This character is not part of either suffix pattern + break; + } - end = pos; + if (ch !== CharacterCodes.minus && ch !== CharacterCodes.dot) { + break; } - // end might be fileName.length, in which case this should internally no-op - return end === fileName.length ? fileName : fileName.slice(0, end); + end = pos; } - /** Remove an item from an array, moving everything to its right one space left. */ - export function orderedRemoveItem(array: T[], item: T): boolean { - for (let i = 0; i < array.length; i++) { - if (array[i] === item) { - orderedRemoveItemAt(array, i); - return true; - } - } - return false; - } + // end might be fileName.length, in which case this should internally no-op + return end === fileName.length ? fileName : fileName.slice(0, end); +} - /** Remove an item by index from an array, moving everything to its right one space left. */ - export function orderedRemoveItemAt(array: T[], index: number): void { - // This seems to be faster than either `array.splice(i, 1)` or `array.copyWithin(i, i+ 1)`. - for (let i = index; i < array.length - 1; i++) { - array[i] = array[i + 1]; +/** + * Remove an item from an array, moving everything to its right one space left. + * + * @internal + */ +export function orderedRemoveItem(array: T[], item: T): boolean { + for (let i = 0; i < array.length; i++) { + if (array[i] === item) { + orderedRemoveItemAt(array, i); + return true; } - array.pop(); } + return false; +} - export function unorderedRemoveItemAt(array: T[], index: number): void { - // Fill in the "hole" left at `index`. - array[index] = array[array.length - 1]; - array.pop(); - } +/** + * Remove an item by index from an array, moving everything to its right one space left. + * + * @internal + */ +export function orderedRemoveItemAt(array: T[], index: number): void { + // This seems to be faster than either `array.splice(i, 1)` or `array.copyWithin(i, i+ 1)`. + for (let i = index; i < array.length - 1; i++) { + array[i] = array[i + 1]; + } + array.pop(); +} - /** Remove the *first* occurrence of `item` from the array. */ - export function unorderedRemoveItem(array: T[], item: T) { - return unorderedRemoveFirstItemWhere(array, element => element === item); - } +/** @internal */ +export function unorderedRemoveItemAt(array: T[], index: number): void { + // Fill in the "hole" left at `index`. + array[index] = array[array.length - 1]; + array.pop(); +} - /** Remove the *first* element satisfying `predicate`. */ - function unorderedRemoveFirstItemWhere(array: T[], predicate: (element: T) => boolean) { - for (let i = 0; i < array.length; i++) { - if (predicate(array[i])) { - unorderedRemoveItemAt(array, i); - return true; - } +/** + * Remove the *first* occurrence of `item` from the array. + * + * @internal + */ +export function unorderedRemoveItem(array: T[], item: T) { + return unorderedRemoveFirstItemWhere(array, element => element === item); +} + +/** Remove the *first* element satisfying `predicate`. */ +function unorderedRemoveFirstItemWhere(array: T[], predicate: (element: T) => boolean) { + for (let i = 0; i < array.length; i++) { + if (predicate(array[i])) { + unorderedRemoveItemAt(array, i); + return true; } - return false; } + return false; +} - export type GetCanonicalFileName = (fileName: string) => string; - export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName { - return useCaseSensitiveFileNames ? identity : toFileNameLowerCase; - } +/** @internal */ +export type GetCanonicalFileName = (fileName: string) => string; +/** @internal */ +export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName { + return useCaseSensitiveFileNames ? identity : toFileNameLowerCase; +} - /** Represents a "prefix*suffix" pattern. */ - export interface Pattern { - prefix: string; - suffix: string; - } +/** + * Represents a "prefix*suffix" pattern. + * + * @internal + */ +export interface Pattern { + prefix: string; + suffix: string; +} - export function patternText({ prefix, suffix }: Pattern): string { - return `${prefix}*${suffix}`; - } +/** @internal */ +export function patternText({ prefix, suffix }: Pattern): string { + return `${prefix}*${suffix}`; +} - /** - * Given that candidate matches pattern, returns the text matching the '*'. - * E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar" - */ - export function matchedText(pattern: Pattern, candidate: string): string { - Debug.assert(isPatternMatch(pattern, candidate)); - return candidate.substring(pattern.prefix.length, candidate.length - pattern.suffix.length); - } - - /** Return the object corresponding to the best pattern to match `candidate`. */ - export function findBestPatternMatch(values: readonly T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined { - let matchedValue: T | undefined; - // use length of prefix as betterness criteria - let longestMatchPrefixLength = -1; - - for (const v of values) { - const pattern = getPattern(v); - if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) { - longestMatchPrefixLength = pattern.prefix.length; - matchedValue = v; - } - } +/** + * Given that candidate matches pattern, returns the text matching the '*'. + * E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar" + * + * @internal + */ +export function matchedText(pattern: Pattern, candidate: string): string { + Debug.assert(isPatternMatch(pattern, candidate)); + return candidate.substring(pattern.prefix.length, candidate.length - pattern.suffix.length); +} - return matchedValue; - } +/** + * Return the object corresponding to the best pattern to match `candidate`. + * + * @internal + */ +export function findBestPatternMatch(values: readonly T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined { + let matchedValue: T | undefined; + // use length of prefix as betterness criteria + let longestMatchPrefixLength = -1; - export function startsWith(str: string, prefix: string): boolean { - return str.lastIndexOf(prefix, 0) === 0; + for (const v of values) { + const pattern = getPattern(v); + if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) { + longestMatchPrefixLength = pattern.prefix.length; + matchedValue = v; + } } - export function removePrefix(str: string, prefix: string): string { - return startsWith(str, prefix) ? str.substr(prefix.length) : str; - } + return matchedValue; +} - export function tryRemovePrefix(str: string, prefix: string, getCanonicalFileName: GetCanonicalFileName = identity): string | undefined { - return startsWith(getCanonicalFileName(str), getCanonicalFileName(prefix)) ? str.substring(prefix.length) : undefined; - } +/** @internal */ +export function startsWith(str: string, prefix: string): boolean { + return str.lastIndexOf(prefix, 0) === 0; +} - export function isPatternMatch({ prefix, suffix }: Pattern, candidate: string) { - return candidate.length >= prefix.length + suffix.length && - startsWith(candidate, prefix) && - endsWith(candidate, suffix); - } +/** @internal */ +export function removePrefix(str: string, prefix: string): string { + return startsWith(str, prefix) ? str.substr(prefix.length) : str; +} - export function and(f: (arg: T) => boolean, g: (arg: T) => boolean) { - return (arg: T) => f(arg) && g(arg); - } +/** @internal */ +export function tryRemovePrefix(str: string, prefix: string, getCanonicalFileName: GetCanonicalFileName = identity): string | undefined { + return startsWith(getCanonicalFileName(str), getCanonicalFileName(prefix)) ? str.substring(prefix.length) : undefined; +} - export function or(f1: (p1: P) => p1 is R1, f2: (p2: P) => p2 is R2): (p: P) => p is R1 | R2; - export function or(f1: (p1: P) => p1 is R1, f2: (p2: P) => p2 is R2, f3: (p3: P) => p3 is R3): (p: P) => p is R1 | R2 | R3; - export function or(...fs: ((...args: T) => U)[]): (...args: T) => U; - export function or(...fs: ((...args: T) => U)[]): (...args: T) => U { - return (...args) => { - let lastResult: U; - for (const f of fs) { - lastResult = f(...args); - if (lastResult) { - return lastResult; - } - } - return lastResult!; - }; - } +/** @internal */ +export function isPatternMatch({ prefix, suffix }: Pattern, candidate: string) { + return candidate.length >= prefix.length + suffix.length && + startsWith(candidate, prefix) && + endsWith(candidate, suffix); +} - export function not(fn: (...args: T) => boolean): (...args: T) => boolean { - return (...args) => !fn(...args); - } +/** @internal */ +export function and(f: (arg: T) => boolean, g: (arg: T) => boolean) { + return (arg: T) => f(arg) && g(arg); +} - export function assertType(_: T): void { } +/** @internal */ +export function or(f1: (p1: P) => p1 is R1, f2: (p2: P) => p2 is R2): (p: P) => p is R1 | R2; +/** @internal */ +export function or(f1: (p1: P) => p1 is R1, f2: (p2: P) => p2 is R2, f3: (p3: P) => p3 is R3): (p: P) => p is R1 | R2 | R3; +/** @internal */ +export function or(...fs: ((...args: T) => U)[]): (...args: T) => U; +/** @internal */ +export function or(...fs: ((...args: T) => U)[]): (...args: T) => U { + return (...args) => { + let lastResult: U; + for (const f of fs) { + lastResult = f(...args); + if (lastResult) { + return lastResult; + } + } + return lastResult!; + }; +} - export function singleElementArray(t: T | undefined): T[] | undefined { - return t === undefined ? undefined : [t]; - } +/** @internal */ +export function not(fn: (...args: T) => boolean): (...args: T) => boolean { + return (...args) => !fn(...args); +} - export function enumerateInsertsAndDeletes(newItems: readonly T[], oldItems: readonly U[], comparer: (a: T, b: U) => Comparison, inserted: (newItem: T) => void, deleted: (oldItem: U) => void, unchanged?: (oldItem: U, newItem: T) => void) { - unchanged = unchanged || noop; - let newIndex = 0; - let oldIndex = 0; - const newLen = newItems.length; - const oldLen = oldItems.length; - let hasChanges = false; - while (newIndex < newLen && oldIndex < oldLen) { - const newItem = newItems[newIndex]; - const oldItem = oldItems[oldIndex]; - const compareResult = comparer(newItem, oldItem); - if (compareResult === Comparison.LessThan) { - inserted(newItem); - newIndex++; - hasChanges = true; - } - else if (compareResult === Comparison.GreaterThan) { - deleted(oldItem); - oldIndex++; - hasChanges = true; - } - else { - unchanged(oldItem, newItem); - newIndex++; - oldIndex++; - } - } - while (newIndex < newLen) { - inserted(newItems[newIndex++]); +/** @internal */ +export function assertType(_: T): void { } + +/** @internal */ +export function singleElementArray(t: T | undefined): T[] | undefined { + return t === undefined ? undefined : [t]; +} + +/** @internal */ +export function enumerateInsertsAndDeletes(newItems: readonly T[], oldItems: readonly U[], comparer: (a: T, b: U) => Comparison, inserted: (newItem: T) => void, deleted: (oldItem: U) => void, unchanged?: (oldItem: U, newItem: T) => void) { + unchanged = unchanged || noop; + let newIndex = 0; + let oldIndex = 0; + const newLen = newItems.length; + const oldLen = oldItems.length; + let hasChanges = false; + while (newIndex < newLen && oldIndex < oldLen) { + const newItem = newItems[newIndex]; + const oldItem = oldItems[oldIndex]; + const compareResult = comparer(newItem, oldItem); + if (compareResult === Comparison.LessThan) { + inserted(newItem); + newIndex++; hasChanges = true; } - while (oldIndex < oldLen) { - deleted(oldItems[oldIndex++]); + else if (compareResult === Comparison.GreaterThan) { + deleted(oldItem); + oldIndex++; hasChanges = true; } - return hasChanges; - } - - export function fill(length: number, cb: (index: number) => T): T[] { - const result = Array(length); - for (let i = 0; i < length; i++) { - result[i] = cb(i); + else { + unchanged(oldItem, newItem); + newIndex++; + oldIndex++; } - return result; } - - export function cartesianProduct(arrays: readonly T[][]) { - const result: T[][] = []; - cartesianProductWorker(arrays, result, /*outer*/ undefined, 0); - return result; + while (newIndex < newLen) { + inserted(newItems[newIndex++]); + hasChanges = true; } - - function cartesianProductWorker(arrays: readonly (readonly T[])[], result: (readonly T[])[], outer: readonly T[] | undefined, index: number) { - for (const element of arrays[index]) { - let inner: T[]; - if (outer) { - inner = outer.slice(); - inner.push(element); - } - else { - inner = [element]; - } - if (index === arrays.length - 1) { - result.push(inner); - } - else { - cartesianProductWorker(arrays, result, inner, index + 1); - } - } + while (oldIndex < oldLen) { + deleted(oldItems[oldIndex++]); + hasChanges = true; } + return hasChanges; +} - - /** - * Returns string left-padded with spaces or zeros until it reaches the given length. - * - * @param s String to pad. - * @param length Final padded length. If less than or equal to 's.length', returns 's' unchanged. - * @param padString Character to use as padding (default " "). - */ - export function padLeft(s: string, length: number, padString: " " | "0" = " ") { - return length <= s.length ? s : padString.repeat(length - s.length) + s; +/** @internal */ +export function fill(length: number, cb: (index: number) => T): T[] { + const result = Array(length); + for (let i = 0; i < length; i++) { + result[i] = cb(i); } + return result; +} - /** - * Returns string right-padded with spaces until it reaches the given length. - * - * @param s String to pad. - * @param length Final padded length. If less than or equal to 's.length', returns 's' unchanged. - * @param padString Character to use as padding (default " "). - */ - export function padRight(s: string, length: number, padString: " " = " ") { - return length <= s.length ? s : s + padString.repeat(length - s.length); - } +/** @internal */ +export function cartesianProduct(arrays: readonly T[][]) { + const result: T[][] = []; + cartesianProductWorker(arrays, result, /*outer*/ undefined, 0); + return result; +} - export function takeWhile(array: readonly T[], predicate: (element: T) => element is U): U[]; - export function takeWhile(array: readonly T[], predicate: (element: T) => boolean): T[] { - const len = array.length; - let index = 0; - while (index < len && predicate(array[index])) { - index++; +function cartesianProductWorker(arrays: readonly (readonly T[])[], result: (readonly T[])[], outer: readonly T[] | undefined, index: number) { + for (const element of arrays[index]) { + let inner: T[]; + if (outer) { + inner = outer.slice(); + inner.push(element); + } + else { + inner = [element]; + } + if (index === arrays.length - 1) { + result.push(inner); + } + else { + cartesianProductWorker(arrays, result, inner, index + 1); } - return array.slice(0, index); } +} - /** - * Removes the leading and trailing white space and line terminator characters from a string. - */ - export const trimString = !!String.prototype.trim ? ((s: string) => s.trim()) : (s: string) => trimStringEnd(trimStringStart(s)); - /** - * Returns a copy with trailing whitespace removed. - */ - export const trimStringEnd = !!String.prototype.trimEnd ? ((s: string) => s.trimEnd()) : trimEndImpl; +/** + * Returns string left-padded with spaces or zeros until it reaches the given length. + * + * @param s String to pad. + * @param length Final padded length. If less than or equal to 's.length', returns 's' unchanged. + * @param padString Character to use as padding (default " "). + * + * @internal + */ +export function padLeft(s: string, length: number, padString: " " | "0" = " ") { + return length <= s.length ? s : padString.repeat(length - s.length) + s; +} - /** - * Returns a copy with leading whitespace removed. - */ - export const trimStringStart = !!String.prototype.trimStart ? ((s: string) => s.trimStart()) : (s: string) => s.replace(/^\s+/g, ""); +/** + * Returns string right-padded with spaces until it reaches the given length. + * + * @param s String to pad. + * @param length Final padded length. If less than or equal to 's.length', returns 's' unchanged. + * @param padString Character to use as padding (default " "). + * + * @internal + */ +export function padRight(s: string, length: number, padString: " " = " ") { + return length <= s.length ? s : s + padString.repeat(length - s.length); +} - /** - * https://jsbench.me/gjkoxld4au/1 - * The simple regex for this, /\s+$/g is O(n^2) in v8. - * The native .trimEnd method is by far best, but since that's technically ES2019, - * we provide a (still much faster than the simple regex) fallback. - */ - function trimEndImpl(s: string) { - let end = s.length - 1; - while (end >= 0) { - if (!isWhiteSpaceLike(s.charCodeAt(end))) break; - end--; - } - return s.slice(0, end + 1); +/** @internal */ +export function takeWhile(array: readonly T[], predicate: (element: T) => element is U): U[]; +/** @internal */ +export function takeWhile(array: readonly T[], predicate: (element: T) => boolean): T[] { + const len = array.length; + let index = 0; + while (index < len && predicate(array[index])) { + index++; } + return array.slice(0, index); +} + +/** + * Removes the leading and trailing white space and line terminator characters from a string. + * + * @internal + */ +export const trimString = !!String.prototype.trim ? ((s: string) => s.trim()) : (s: string) => trimStringEnd(trimStringStart(s)); + +/** + * Returns a copy with trailing whitespace removed. + * + * @internal + */ +export const trimStringEnd = !!String.prototype.trimEnd ? ((s: string) => s.trimEnd()) : trimEndImpl; + +/** + * Returns a copy with leading whitespace removed. + * + * @internal + */ +export const trimStringStart = !!String.prototype.trimStart ? ((s: string) => s.trimStart()) : (s: string) => s.replace(/^\s+/g, ""); + +/** + * https://jsbench.me/gjkoxld4au/1 + * The simple regex for this, /\s+$/g is O(n^2) in v8. + * The native .trimEnd method is by far best, but since that's technically ES2019, + * we provide a (still much faster than the simple regex) fallback. + */ +function trimEndImpl(s: string) { + let end = s.length - 1; + while (end >= 0) { + if (!isWhiteSpaceLike(s.charCodeAt(end))) break; + end--; + } + return s.slice(0, end + 1); +} + +declare const process: any; + +/** @internal */ +export function isNodeLikeSystem(): boolean { + // This is defined here rather than in sys.ts to prevent a cycle from its + // use in performanceCore.ts. + // + // We don't use the presence of `require` to check if we are in Node; + // when bundled using esbuild, this function will be rewritten to `__require` + // and definitely exist. + return typeof process !== "undefined" + && process.nextTick + && !process.browser + && typeof module === "object"; } diff --git a/src/compiler/corePublic.ts b/src/compiler/corePublic.ts index e5128977fd098..aa0933e9e6e60 100644 --- a/src/compiler/corePublic.ts +++ b/src/compiler/corePublic.ts @@ -1,157 +1,155 @@ -namespace ts { - // WARNING: The script `configurePrerelease.ts` uses a regexp to parse out these values. - // If changing the text in this section, be sure to test `configurePrerelease` too. - export const versionMajorMinor = "5.0"; - // The following is baselined as a literal template type without intervention - /** The version of the TypeScript compiler release */ - // eslint-disable-next-line @typescript-eslint/no-inferrable-types - export const version: string = `${versionMajorMinor}.0-dev`; +// WARNING: The script `configurePrerelease.ts` uses a regexp to parse out these values. +// If changing the text in this section, be sure to test `configurePrerelease` too. +export const versionMajorMinor = "5.0"; +// The following is baselined as a literal template type without intervention +/** The version of the TypeScript compiler release */ +// eslint-disable-next-line @typescript-eslint/no-inferrable-types +export const version: string = `${versionMajorMinor}.0-dev`; + +/** + * Type of objects whose values are all of the same type. + * The `in` and `for-in` operators can *not* be safely used, + * since `Object.prototype` may be modified by outside code. + */ +export interface MapLike { + [index: string]: T; +} - /** - * Type of objects whose values are all of the same type. - * The `in` and `for-in` operators can *not* be safely used, - * since `Object.prototype` may be modified by outside code. - */ - export interface MapLike { - [index: string]: T; - } +export interface SortedReadonlyArray extends ReadonlyArray { + " __sortedArrayBrand": any; +} - export interface SortedReadonlyArray extends ReadonlyArray { - " __sortedArrayBrand": any; - } +export interface SortedArray extends Array { + " __sortedArrayBrand": any; +} - export interface SortedArray extends Array { - " __sortedArrayBrand": any; - } +/** Common read methods for ES6 Map/Set. */ +export interface ReadonlyCollection { + readonly size: number; + has(key: K): boolean; + keys(): Iterator; +} - /** Common read methods for ES6 Map/Set. */ - export interface ReadonlyCollection { - readonly size: number; - has(key: K): boolean; - keys(): Iterator; - } +/** Common write methods for ES6 Map/Set. */ +export interface Collection extends ReadonlyCollection { + delete(key: K): boolean; + clear(): void; +} - /** Common write methods for ES6 Map/Set. */ - export interface Collection extends ReadonlyCollection { - delete(key: K): boolean; - clear(): void; - } +/** ES6 Map interface, only read methods included. */ +export interface ReadonlyESMap extends ReadonlyCollection { + get(key: K): V | undefined; + values(): Iterator; + entries(): Iterator<[K, V]>; + forEach(action: (value: V, key: K) => void): void; +} - /** ES6 Map interface, only read methods included. */ - export interface ReadonlyESMap extends ReadonlyCollection { - get(key: K): V | undefined; - values(): Iterator; - entries(): Iterator<[K, V]>; - forEach(action: (value: V, key: K) => void): void; - } +/** + * ES6 Map interface, only read methods included. + */ +export interface ReadonlyMap extends ReadonlyESMap { +} - /** - * ES6 Map interface, only read methods included. - */ - export interface ReadonlyMap extends ReadonlyESMap { - } +/** ES6 Map interface. */ +export interface ESMap extends ReadonlyESMap, Collection { + set(key: K, value: V): this; +} - /** ES6 Map interface. */ - export interface ESMap extends ReadonlyESMap, Collection { - set(key: K, value: V): this; - } +/** + * ES6 Map interface. + */ +export interface Map extends ESMap { +} - /** - * ES6 Map interface. - */ - export interface Map extends ESMap { - } +/** @internal */ +export interface MapConstructor { + // eslint-disable-next-line @typescript-eslint/prefer-function-type + new (iterable?: readonly (readonly [K, V])[] | ReadonlyESMap): ESMap; +} - /* @internal */ - export interface MapConstructor { - // eslint-disable-next-line @typescript-eslint/prefer-function-type - new (iterable?: readonly (readonly [K, V])[] | ReadonlyESMap): ESMap; - } +/** ES6 Set interface, only read methods included. */ +export interface ReadonlySet extends ReadonlyCollection { + has(value: T): boolean; + values(): Iterator; + entries(): Iterator<[T, T]>; + forEach(action: (value: T, key: T) => void): void; +} - /** ES6 Set interface, only read methods included. */ - export interface ReadonlySet extends ReadonlyCollection { - has(value: T): boolean; - values(): Iterator; - entries(): Iterator<[T, T]>; - forEach(action: (value: T, key: T) => void): void; - } +/** ES6 Set interface. */ +export interface Set extends ReadonlySet, Collection { + add(value: T): this; + delete(value: T): boolean; +} - /** ES6 Set interface. */ - export interface Set extends ReadonlySet, Collection { - add(value: T): this; - delete(value: T): boolean; - } +/** @internal */ +export interface SetConstructor { + // eslint-disable-next-line @typescript-eslint/prefer-function-type + new (iterable?: readonly T[] | ReadonlySet): Set; +} - /* @internal */ - export interface SetConstructor { - // eslint-disable-next-line @typescript-eslint/prefer-function-type - new (iterable?: readonly T[] | ReadonlySet): Set; - } +/** ES6 Iterator type. */ +export interface Iterator { + next(): { value: T, done?: false } | { value: void, done: true }; +} - /** ES6 Iterator type. */ - export interface Iterator { - next(): { value: T, done?: false } | { value: void, done: true }; - } +/** Array that is only intended to be pushed to, never read. */ +export interface Push { + push(...values: T[]): void; + /** @internal */ readonly length: number; +} - /** Array that is only intended to be pushed to, never read. */ - export interface Push { - push(...values: T[]): void; - /* @internal*/ readonly length: number; - } +/** @internal */ +export type EqualityComparer = (a: T, b: T) => boolean; - /* @internal */ - export type EqualityComparer = (a: T, b: T) => boolean; +/** @internal */ +export type Comparer = (a: T, b: T) => Comparison; - /* @internal */ - export type Comparer = (a: T, b: T) => Comparison; +/** @internal */ +export const enum Comparison { + LessThan = -1, + EqualTo = 0, + GreaterThan = 1 +} - /* @internal */ - export const enum Comparison { - LessThan = -1, - EqualTo = 0, - GreaterThan = 1 - } +/** @internal */ +namespace NativeCollections { + declare const self: any; - /* @internal */ - namespace NativeCollections { - declare const self: any; - - const globals = typeof globalThis !== "undefined" ? globalThis : - typeof global !== "undefined" ? global : - typeof self !== "undefined" ? self : - undefined; - - /** - * Returns the native Map implementation if it is available and compatible (i.e. supports iteration). - */ - export function tryGetNativeMap(): MapConstructor { - // Internet Explorer's Map doesn't support iteration, so don't use it. - const gMap = globals?.Map; - // eslint-disable-next-line local/no-in-operator - const constructor = typeof gMap !== "undefined" && "entries" in gMap.prototype && new gMap([[0, 0]]).size === 1 ? gMap : undefined; - if (!constructor) { - throw new Error("No compatible Map implementation found."); - } - return constructor; - } + const globals = typeof globalThis !== "undefined" ? globalThis : + typeof global !== "undefined" ? global : + typeof self !== "undefined" ? self : + undefined; - /** - * Returns the native Set implementation if it is available and compatible (i.e. supports iteration). - */ - export function tryGetNativeSet(): SetConstructor { - // Internet Explorer's Set doesn't support iteration, so don't use it. - const gSet = globals?.Set; - // eslint-disable-next-line local/no-in-operator - const constructor = typeof gSet !== "undefined" && "entries" in gSet.prototype && new gSet([0]).size === 1 ? gSet : undefined; - if (!constructor) { - throw new Error("No compatible Set implementation found."); - } - return constructor; + /** + * Returns the native Map implementation if it is available and compatible (i.e. supports iteration). + */ + export function tryGetNativeMap(): MapConstructor { + // Internet Explorer's Map doesn't support iteration, so don't use it. + const gMap = globals?.Map; + // eslint-disable-next-line local/no-in-operator + const constructor = typeof gMap !== "undefined" && "entries" in gMap.prototype && new gMap([[0, 0]]).size === 1 ? gMap : undefined; + if (!constructor) { + throw new Error("No compatible Map implementation found."); } + return constructor; } - /* @internal */ - export const Map = NativeCollections.tryGetNativeMap(); - /* @internal */ - export const Set = NativeCollections.tryGetNativeSet(); + /** + * Returns the native Set implementation if it is available and compatible (i.e. supports iteration). + */ + export function tryGetNativeSet(): SetConstructor { + // Internet Explorer's Set doesn't support iteration, so don't use it. + const gSet = globals?.Set; + // eslint-disable-next-line local/no-in-operator + const constructor = typeof gSet !== "undefined" && "entries" in gSet.prototype && new gSet([0]).size === 1 ? gSet : undefined; + if (!constructor) { + throw new Error("No compatible Set implementation found."); + } + return constructor; + } } + +/** @internal */ +export const Map = NativeCollections.tryGetNativeMap(); +/** @internal */ +export const Set = NativeCollections.tryGetNativeSet(); diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index a4f900f45e141..adb2d539c8a4f 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -1,817 +1,1180 @@ -/* @internal */ -namespace ts { - export enum LogLevel { - Off, - Error, - Warning, - Info, - Verbose - } +import * as ts from "./_namespaces/ts"; +import { + AnyFunction, AssertionLevel, BigIntLiteralType, CheckMode, compareValues, EmitFlags, every, FlowFlags, FlowLabel, FlowNode, + FlowNodeBase, FlowSwitchClause, formatStringFromArgs, getEffectiveModifierFlagsNoCache, getEmitFlags, getOwnKeys, + getParseTreeNode, getSourceFileOfNode, getSourceTextOfNodeFromSourceFile, hasProperty, idText, IntrinsicType, + isArrayTypeNode, isBigIntLiteral, isCallSignatureDeclaration, isConditionalTypeNode, isConstructorDeclaration, + isConstructorTypeNode, isConstructSignatureDeclaration, isDefaultClause, isFunctionTypeNode, isGeneratedIdentifier, + isGetAccessorDeclaration, isIdentifier, isImportTypeNode, isIndexedAccessTypeNode, isIndexSignatureDeclaration, + isInferTypeNode, isIntersectionTypeNode, isLiteralTypeNode, isMappedTypeNode, isNamedTupleMember, isNumericLiteral, + isOptionalTypeNode, isParameter, isParenthesizedTypeNode, isParseTreeNode, isPrivateIdentifier, isRestTypeNode, + isSetAccessorDeclaration, isStringLiteral, isThisTypeNode, isTupleTypeNode, isTypeLiteralNode, isTypeOperatorNode, + isTypeParameterDeclaration, isTypePredicateNode, isTypeQueryNode, isTypeReferenceNode, isUnionTypeNode, LiteralType, + map, Map, MatchingKeys, ModifierFlags, Node, NodeArray, NodeFlags, nodeIsSynthesized, noop, objectAllocator, + ObjectFlags, ObjectType, RelationComparisonResult, Set, Signature, SignatureCheckMode, + SignatureFlags, SnippetKind, SortedReadonlyArray, stableSort, Symbol, SymbolFlags, symbolName, SyntaxKind, + TransformFlags, Type, TypeFacts, TypeFlags, TypeMapKind, TypeMapper, unescapeLeadingUnderscores, VarianceFlags, + version, Version, zipWith, +} from "./_namespaces/ts"; + +/** @internal */ +export enum LogLevel { + Off, + Error, + Warning, + Info, + Verbose +} - export interface LoggingHost { - log(level: LogLevel, s: string): void; - } +/** @internal */ +export interface LoggingHost { + log(level: LogLevel, s: string): void; +} - export interface DeprecationOptions { - message?: string; - error?: boolean; - since?: Version | string; - warnAfter?: Version | string; - errorAfter?: Version | string; - typeScriptVersion?: Version | string; - name?: string; - } +/** @internal */ +export interface DeprecationOptions { + message?: string; + error?: boolean; + since?: Version | string; + warnAfter?: Version | string; + errorAfter?: Version | string; + typeScriptVersion?: Version | string; + name?: string; +} - export namespace Debug { - let typeScriptVersion: Version | undefined; +/** @internal */ +export namespace Debug { + let typeScriptVersion: Version | undefined; + + /* eslint-disable prefer-const */ + let currentAssertionLevel = AssertionLevel.None; + export let currentLogLevel = LogLevel.Warning; + export let isDebugging = false; + export let loggingHost: LoggingHost | undefined; + export let enableDeprecationWarnings = true; + /* eslint-enable prefer-const */ + + type AssertionKeys = MatchingKeys; + export function getTypeScriptVersion() { + return typeScriptVersion ?? (typeScriptVersion = new Version(version)); + } - /* eslint-disable prefer-const */ - let currentAssertionLevel = AssertionLevel.None; - export let currentLogLevel = LogLevel.Warning; - export let isDebugging = false; - export let loggingHost: LoggingHost | undefined; - export let enableDeprecationWarnings = true; - /* eslint-enable prefer-const */ + export function shouldLog(level: LogLevel): boolean { + return currentLogLevel <= level; + } - type AssertionKeys = MatchingKeys; - export function getTypeScriptVersion() { - return typeScriptVersion ?? (typeScriptVersion = new Version(version)); + function logMessage(level: LogLevel, s: string): void { + if (loggingHost && shouldLog(level)) { + loggingHost.log(level, s); } + } + + export function log(s: string): void { + logMessage(LogLevel.Info, s); + } - export function shouldLog(level: LogLevel): boolean { - return currentLogLevel <= level; + export namespace log { + export function error(s: string): void { + logMessage(LogLevel.Error, s); } - function logMessage(level: LogLevel, s: string): void { - if (loggingHost && shouldLog(level)) { - loggingHost.log(level, s); - } + export function warn(s: string): void { + logMessage(LogLevel.Warning, s); } export function log(s: string): void { logMessage(LogLevel.Info, s); } - export namespace log { - export function error(s: string): void { - logMessage(LogLevel.Error, s); - } + export function trace(s: string): void { + logMessage(LogLevel.Verbose, s); + } + } - export function warn(s: string): void { - logMessage(LogLevel.Warning, s); - } + const assertionCache: Partial> = {}; - export function log(s: string): void { - logMessage(LogLevel.Info, s); - } + export function getAssertionLevel() { + return currentAssertionLevel; + } - export function trace(s: string): void { - logMessage(LogLevel.Verbose, s); + export function setAssertionLevel(level: AssertionLevel) { + const prevAssertionLevel = currentAssertionLevel; + currentAssertionLevel = level; + + if (level > prevAssertionLevel) { + // restore assertion functions for the current assertion level (see `shouldAssertFunction`). + for (const key of getOwnKeys(assertionCache) as AssertionKeys[]) { + const cachedFunc = assertionCache[key]; + if (cachedFunc !== undefined && Debug[key] !== cachedFunc.assertion && level >= cachedFunc.level) { + (Debug as any)[key] = cachedFunc; + assertionCache[key] = undefined; + } } } + } - const assertionCache: Partial> = {}; - - export function getAssertionLevel() { - return currentAssertionLevel; - } + export function shouldAssert(level: AssertionLevel): boolean { + return currentAssertionLevel >= level; + } - export function setAssertionLevel(level: AssertionLevel) { - const prevAssertionLevel = currentAssertionLevel; - currentAssertionLevel = level; + /** + * Tests whether an assertion function should be executed. If it shouldn't, it is cached and replaced with `ts.noop`. + * Replaced assertion functions are restored when `Debug.setAssertionLevel` is set to a high enough level. + * @param level The minimum assertion level required. + * @param name The name of the current assertion function. + */ + function shouldAssertFunction(level: AssertionLevel, name: K): boolean { + if (!shouldAssert(level)) { + assertionCache[name] = { level, assertion: Debug[name] }; + (Debug as any)[name] = noop; + return false; + } + return true; + } - if (level > prevAssertionLevel) { - // restore assertion functions for the current assertion level (see `shouldAssertFunction`). - for (const key of getOwnKeys(assertionCache) as AssertionKeys[]) { - const cachedFunc = assertionCache[key]; - if (cachedFunc !== undefined && Debug[key] !== cachedFunc.assertion && level >= cachedFunc.level) { - (Debug as any)[key] = cachedFunc; - assertionCache[key] = undefined; - } - } - } + export function fail(message?: string, stackCrawlMark?: AnyFunction): never { + debugger; + const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); + if ((Error as any).captureStackTrace) { + (Error as any).captureStackTrace(e, stackCrawlMark || fail); } + throw e; + } - export function shouldAssert(level: AssertionLevel): boolean { - return currentAssertionLevel >= level; - } + export function failBadSyntaxKind(node: Node, message?: string, stackCrawlMark?: AnyFunction): never { + return fail( + `${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`, + stackCrawlMark || failBadSyntaxKind); + } - /** - * Tests whether an assertion function should be executed. If it shouldn't, it is cached and replaced with `ts.noop`. - * Replaced assertion functions are restored when `Debug.setAssertionLevel` is set to a high enough level. - * @param level The minimum assertion level required. - * @param name The name of the current assertion function. - */ - function shouldAssertFunction(level: AssertionLevel, name: K): boolean { - if (!shouldAssert(level)) { - assertionCache[name] = { level, assertion: Debug[name] }; - (Debug as any)[name] = noop; - return false; + export function assert(expression: unknown, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): asserts expression { + if (!expression) { + message = message ? `False expression: ${message}` : "False expression."; + if (verboseDebugInfo) { + message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); } - return true; + fail(message, stackCrawlMark || assert); } + } - export function fail(message?: string, stackCrawlMark?: AnyFunction): never { - debugger; - const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); - if ((Error as any).captureStackTrace) { - (Error as any).captureStackTrace(e, stackCrawlMark || fail); - } - throw e; + export function assertEqual(a: T, b: T, msg?: string, msg2?: string, stackCrawlMark?: AnyFunction): void { + if (a !== b) { + const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; + fail(`Expected ${a} === ${b}. ${message}`, stackCrawlMark || assertEqual); } + } - export function failBadSyntaxKind(node: Node, message?: string, stackCrawlMark?: AnyFunction): never { - return fail( - `${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`, - stackCrawlMark || failBadSyntaxKind); + export function assertLessThan(a: number, b: number, msg?: string, stackCrawlMark?: AnyFunction): void { + if (a >= b) { + fail(`Expected ${a} < ${b}. ${msg || ""}`, stackCrawlMark || assertLessThan); } + } - export function assert(expression: unknown, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): asserts expression { - if (!expression) { - message = message ? `False expression: ${message}` : "False expression."; - if (verboseDebugInfo) { - message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); - } - fail(message, stackCrawlMark || assert); - } + export function assertLessThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { + if (a > b) { + fail(`Expected ${a} <= ${b}`, stackCrawlMark || assertLessThanOrEqual); } + } - export function assertEqual(a: T, b: T, msg?: string, msg2?: string, stackCrawlMark?: AnyFunction): void { - if (a !== b) { - const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; - fail(`Expected ${a} === ${b}. ${message}`, stackCrawlMark || assertEqual); - } + export function assertGreaterThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { + if (a < b) { + fail(`Expected ${a} >= ${b}`, stackCrawlMark || assertGreaterThanOrEqual); } + } - export function assertLessThan(a: number, b: number, msg?: string, stackCrawlMark?: AnyFunction): void { - if (a >= b) { - fail(`Expected ${a} < ${b}. ${msg || ""}`, stackCrawlMark || assertLessThan); - } + export function assertIsDefined(value: T, message?: string, stackCrawlMark?: AnyFunction): asserts value is NonNullable { + // eslint-disable-next-line no-null/no-null + if (value === undefined || value === null) { + fail(message, stackCrawlMark || assertIsDefined); } + } - export function assertLessThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { - if (a > b) { - fail(`Expected ${a} <= ${b}`, stackCrawlMark || assertLessThanOrEqual); - } - } + export function checkDefined(value: T | null | undefined, message?: string, stackCrawlMark?: AnyFunction): T { + assertIsDefined(value, message, stackCrawlMark || checkDefined); + return value; + } - export function assertGreaterThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { - if (a < b) { - fail(`Expected ${a} >= ${b}`, stackCrawlMark || assertGreaterThanOrEqual); - } + export function assertEachIsDefined(value: NodeArray, message?: string, stackCrawlMark?: AnyFunction): asserts value is NodeArray; + export function assertEachIsDefined(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction): asserts value is readonly NonNullable[]; + export function assertEachIsDefined(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction) { + for (const v of value) { + assertIsDefined(v, message, stackCrawlMark || assertEachIsDefined); } + } - export function assertIsDefined(value: T, message?: string, stackCrawlMark?: AnyFunction): asserts value is NonNullable { - // eslint-disable-next-line no-null/no-null - if (value === undefined || value === null) { - fail(message, stackCrawlMark || assertIsDefined); - } - } + export function checkEachDefined(value: A, message?: string, stackCrawlMark?: AnyFunction): A { + assertEachIsDefined(value, message, stackCrawlMark || checkEachDefined); + return value; + } - export function checkDefined(value: T | null | undefined, message?: string, stackCrawlMark?: AnyFunction): T { - assertIsDefined(value, message, stackCrawlMark || checkDefined); - return value; - } + export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { + const detail = typeof member === "object" && hasProperty(member, "kind") && hasProperty(member, "pos") ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member); + return fail(`${message} ${detail}`, stackCrawlMark || assertNever); + } - export function assertEachIsDefined(value: NodeArray, message?: string, stackCrawlMark?: AnyFunction): asserts value is NodeArray; - export function assertEachIsDefined(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction): asserts value is readonly NonNullable[]; - export function assertEachIsDefined(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction) { - for (const v of value) { - assertIsDefined(v, message, stackCrawlMark || assertEachIsDefined); - } + export function assertEachNode(nodes: NodeArray, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is NodeArray; + export function assertEachNode(nodes: readonly T[], test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is readonly U[]; + export function assertEachNode(nodes: NodeArray | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is NodeArray | undefined; + export function assertEachNode(nodes: readonly T[] | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is readonly U[] | undefined; + export function assertEachNode(nodes: readonly Node[], test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction): void; + export function assertEachNode(nodes: readonly Node[] | undefined, test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertEachNode")) { + assert( + test === undefined || every(nodes, test), + message || "Unexpected node.", + () => `Node array did not pass test '${getFunctionName(test)}'.`, + stackCrawlMark || assertEachNode); } + } - export function checkEachDefined(value: A, message?: string, stackCrawlMark?: AnyFunction): A { - assertEachIsDefined(value, message, stackCrawlMark || checkEachDefined); - return value; + export function assertNode(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U; + export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; + export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertNode")) { + assert( + node !== undefined && (test === undefined || test(node)), + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, + stackCrawlMark || assertNode); } + } - export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { - const detail = typeof member === "object" && hasProperty(member, "kind") && hasProperty(member, "pos") ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member); - return fail(`${message} ${detail}`, stackCrawlMark || assertNever); + export function assertNotNode(node: T | undefined, test: (node: Node) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is Exclude; + export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; + export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertNotNode")) { + assert( + node === undefined || test === undefined || !test(node), + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, + stackCrawlMark || assertNotNode); } + } - export function assertEachNode(nodes: NodeArray, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is NodeArray; - export function assertEachNode(nodes: readonly T[], test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is readonly U[]; - export function assertEachNode(nodes: NodeArray | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is NodeArray | undefined; - export function assertEachNode(nodes: readonly T[] | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is readonly U[] | undefined; - export function assertEachNode(nodes: readonly Node[], test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction): void; - export function assertEachNode(nodes: readonly Node[] | undefined, test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertEachNode")) { - assert( - test === undefined || every(nodes, test), - message || "Unexpected node.", - () => `Node array did not pass test '${getFunctionName(test)}'.`, - stackCrawlMark || assertEachNode); - } + export function assertOptionalNode(node: T, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U; + export function assertOptionalNode(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U | undefined; + export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; + export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalNode")) { + assert( + test === undefined || node === undefined || test(node), + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, + stackCrawlMark || assertOptionalNode); } + } - export function assertNode(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U; - export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; - export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertNode")) { - assert( - node !== undefined && (test === undefined || test(node)), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, - stackCrawlMark || assertNode); - } + export function assertOptionalToken(node: T, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract; + export function assertOptionalToken(node: T | undefined, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract | undefined; + export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction): void; + export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalToken")) { + assert( + kind === undefined || node === undefined || node.kind === kind, + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node?.kind)} was not a '${formatSyntaxKind(kind)}' token.`, + stackCrawlMark || assertOptionalToken); } + } - export function assertNotNode(node: T | undefined, test: (node: Node) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is Exclude; - export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; - export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertNotNode")) { - assert( - node === undefined || test === undefined || !test(node), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, - stackCrawlMark || assertNotNode); - } + export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction): asserts node is undefined; + export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction) { + if (shouldAssertFunction(AssertionLevel.Normal, "assertMissingNode")) { + assert( + node === undefined, + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node!.kind)} was unexpected'.`, + stackCrawlMark || assertMissingNode); } + } - export function assertOptionalNode(node: T, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U; - export function assertOptionalNode(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U | undefined; - export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; - export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalNode")) { - assert( - test === undefined || node === undefined || test(node), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, - stackCrawlMark || assertOptionalNode); - } - } + /** + * Asserts a value has the specified type in typespace only (does not perform a runtime assertion). + * This is useful in cases where we switch on `node.kind` and can be reasonably sure the type is accurate, and + * as a result can reduce the number of unnecessary casts. + */ + export function type(value: unknown): asserts value is T; + export function type(_value: unknown) { } - export function assertOptionalToken(node: T, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract; - export function assertOptionalToken(node: T | undefined, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract | undefined; - export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction): void; - export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalToken")) { - assert( - kind === undefined || node === undefined || node.kind === kind, - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node?.kind)} was not a '${formatSyntaxKind(kind)}' token.`, - stackCrawlMark || assertOptionalToken); - } + export function getFunctionName(func: AnyFunction) { + if (typeof func !== "function") { + return ""; } - - export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction): asserts node is undefined; - export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction) { - if (shouldAssertFunction(AssertionLevel.Normal, "assertMissingNode")) { - assert( - node === undefined, - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node!.kind)} was unexpected'.`, - stackCrawlMark || assertMissingNode); - } + else if (hasProperty(func, "name")) { + return (func as any).name; } + else { + const text = Function.prototype.toString.call(func); + const match = /^function\s+([\w\$]+)\s*\(/.exec(text); + return match ? match[1] : ""; + } + } - /** - * Asserts a value has the specified type in typespace only (does not perform a runtime assertion). - * This is useful in cases where we switch on `node.kind` and can be reasonably sure the type is accurate, and - * as a result can reduce the number of unnecessary casts. - */ - export function type(value: unknown): asserts value is T; - export function type(_value: unknown) { } + export function formatSymbol(symbol: Symbol): string { + return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`; + } - export function getFunctionName(func: AnyFunction) { - if (typeof func !== "function") { - return ""; + /** + * 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) { + const result: string[] = []; + let remainingFlags = value; + for (const [enumValue, enumName] of members) { + if (enumValue > value) { + break; + } + if (enumValue !== 0 && enumValue & value) { + result.push(enumName); + remainingFlags &= ~enumValue; + } } - else if (hasProperty(func, "name")) { - return (func as any).name; + if (remainingFlags === 0) { + return result.join("|"); } - else { - const text = Function.prototype.toString.call(func); - const match = /^function\s+([\w\$]+)\s*\(/.exec(text); - return match ? match[1] : ""; + } + else { + for (const [enumValue, enumName] of members) { + if (enumValue === value) { + return enumName; + } } } + return value.toString(); + } - export function formatSymbol(symbol: Symbol): string { - return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`; + const enumMemberCache = new Map>(); + + function getEnumMembers(enumObject: any) { + // Assuming enum objects do not change at runtime, we can cache the enum members list + // to reuse later. This saves us from reconstructing this each and every time we call + // a formatting function (which can be expensive for large enums like SyntaxKind). + const existing = enumMemberCache.get(enumObject); + if (existing) { + return existing; } - /** - * 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) { - const result: string[] = []; - let remainingFlags = value; - for (const [enumValue, enumName] of members) { - if (enumValue > value) { - break; - } - if (enumValue !== 0 && enumValue & value) { - result.push(enumName); - remainingFlags &= ~enumValue; - } - } - if (remainingFlags === 0) { - return result.join("|"); - } + const result: [number, string][] = []; + for (const name in enumObject) { + const value = enumObject[name]; + if (typeof value === "number") { + result.push([value, name]); } - else { - for (const [enumValue, enumName] of members) { - if (enumValue === value) { - return enumName; - } - } - } - return value.toString(); } - const enumMemberCache = new Map>(); + const sorted = stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); + enumMemberCache.set(enumObject, sorted); + return sorted; + } - function getEnumMembers(enumObject: any) { - // Assuming enum objects do not change at runtime, we can cache the enum members list - // to reuse later. This saves us from reconstructing this each and every time we call - // a formatting function (which can be expensive for large enums like SyntaxKind). - const existing = enumMemberCache.get(enumObject); - if (existing) { - return existing; - } + export function formatSyntaxKind(kind: SyntaxKind | undefined): string { + return formatEnum(kind, (ts as any).SyntaxKind, /*isFlags*/ false); + } - const result: [number, string][] = []; - for (const name in enumObject) { - const value = enumObject[name]; - if (typeof value === "number") { - result.push([value, name]); - } - } + export function formatSnippetKind(kind: SnippetKind | undefined): string { + return formatEnum(kind, (ts as any).SnippetKind, /*isFlags*/ false); + } - const sorted = stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); - enumMemberCache.set(enumObject, sorted); - return sorted; - } + export function formatNodeFlags(flags: NodeFlags | undefined): string { + return formatEnum(flags, (ts as any).NodeFlags, /*isFlags*/ true); + } - export function formatSyntaxKind(kind: SyntaxKind | undefined): string { - return formatEnum(kind, (ts as any).SyntaxKind, /*isFlags*/ false); - } + export function formatModifierFlags(flags: ModifierFlags | undefined): string { + return formatEnum(flags, (ts as any).ModifierFlags, /*isFlags*/ true); + } - export function formatSnippetKind(kind: SnippetKind | undefined): string { - return formatEnum(kind, (ts as any).SnippetKind, /*isFlags*/ false); - } + export function formatTransformFlags(flags: TransformFlags | undefined): string { + return formatEnum(flags, (ts as any).TransformFlags, /*isFlags*/ true); + } - export function formatNodeFlags(flags: NodeFlags | undefined): string { - return formatEnum(flags, (ts as any).NodeFlags, /*isFlags*/ true); - } + export function formatEmitFlags(flags: EmitFlags | undefined): string { + return formatEnum(flags, (ts as any).EmitFlags, /*isFlags*/ true); + } - export function formatModifierFlags(flags: ModifierFlags | undefined): string { - return formatEnum(flags, (ts as any).ModifierFlags, /*isFlags*/ true); - } + export function formatSymbolFlags(flags: SymbolFlags | undefined): string { + return formatEnum(flags, (ts as any).SymbolFlags, /*isFlags*/ true); + } - export function formatTransformFlags(flags: TransformFlags | undefined): string { - return formatEnum(flags, (ts as any).TransformFlags, /*isFlags*/ true); - } + export function formatTypeFlags(flags: TypeFlags | undefined): string { + return formatEnum(flags, (ts as any).TypeFlags, /*isFlags*/ true); + } - export function formatEmitFlags(flags: EmitFlags | undefined): string { - return formatEnum(flags, (ts as any).EmitFlags, /*isFlags*/ true); - } + export function formatSignatureFlags(flags: SignatureFlags | undefined): string { + return formatEnum(flags, (ts as any).SignatureFlags, /*isFlags*/ true); + } - export function formatSymbolFlags(flags: SymbolFlags | undefined): string { - return formatEnum(flags, (ts as any).SymbolFlags, /*isFlags*/ true); - } + export function formatObjectFlags(flags: ObjectFlags | undefined): string { + return formatEnum(flags, (ts as any).ObjectFlags, /*isFlags*/ true); + } - export function formatTypeFlags(flags: TypeFlags | undefined): string { - return formatEnum(flags, (ts as any).TypeFlags, /*isFlags*/ true); - } + export function formatFlowFlags(flags: FlowFlags | undefined): string { + return formatEnum(flags, (ts as any).FlowFlags, /*isFlags*/ true); + } - export function formatSignatureFlags(flags: SignatureFlags | undefined): string { - return formatEnum(flags, (ts as any).SignatureFlags, /*isFlags*/ true); - } + export function formatRelationComparisonResult(result: RelationComparisonResult | undefined): string { + return formatEnum(result, (ts as any).RelationComparisonResult, /*isFlags*/ true); + } - export function formatObjectFlags(flags: ObjectFlags | undefined): string { - return formatEnum(flags, (ts as any).ObjectFlags, /*isFlags*/ true); - } + export function formatCheckMode(mode: CheckMode | undefined): string { + return formatEnum(mode, (ts as any).CheckMode, /*isFlags*/ true); + } - export function formatFlowFlags(flags: FlowFlags | undefined): string { - return formatEnum(flags, (ts as any).FlowFlags, /*isFlags*/ true); - } + export function formatSignatureCheckMode(mode: SignatureCheckMode | undefined): string { + return formatEnum(mode, (ts as any).SignatureCheckMode, /*isFlags*/ true); + } - export function formatRelationComparisonResult(result: RelationComparisonResult | undefined): string { - return formatEnum(result, (ts as any).RelationComparisonResult, /*isFlags*/ true); - } + export function formatTypeFacts(facts: TypeFacts | undefined): string { + return formatEnum(facts, (ts as any).TypeFacts, /*isFlags*/ true); + } - export function formatCheckMode(mode: CheckMode | undefined): string { - return formatEnum(mode, (ts as any).CheckMode, /*isFlags*/ true); - } + let isDebugInfoEnabled = false; + + let flowNodeProto: FlowNodeBase | undefined; - export function formatSignatureCheckMode(mode: SignatureCheckMode | undefined): string { - return formatEnum(mode, (ts as any).SignatureCheckMode, /*isFlags*/ true); + function attachFlowNodeDebugInfoWorker(flowNode: FlowNodeBase) { + if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line local/no-in-operator + Object.defineProperties(flowNode, { + // for use with vscode-js-debug's new customDescriptionGenerator in launch.json + __tsDebuggerDisplay: { + value(this: FlowNodeBase) { + const flowHeader = + this.flags & FlowFlags.Start ? "FlowStart" : + this.flags & FlowFlags.BranchLabel ? "FlowBranchLabel" : + this.flags & FlowFlags.LoopLabel ? "FlowLoopLabel" : + this.flags & FlowFlags.Assignment ? "FlowAssignment" : + this.flags & FlowFlags.TrueCondition ? "FlowTrueCondition" : + this.flags & FlowFlags.FalseCondition ? "FlowFalseCondition" : + this.flags & FlowFlags.SwitchClause ? "FlowSwitchClause" : + this.flags & FlowFlags.ArrayMutation ? "FlowArrayMutation" : + this.flags & FlowFlags.Call ? "FlowCall" : + this.flags & FlowFlags.ReduceLabel ? "FlowReduceLabel" : + this.flags & FlowFlags.Unreachable ? "FlowUnreachable" : + "UnknownFlow"; + const remainingFlags = this.flags & ~(FlowFlags.Referenced - 1); + return `${flowHeader}${remainingFlags ? ` (${formatFlowFlags(remainingFlags)})`: ""}`; + } + }, + __debugFlowFlags: { get(this: FlowNodeBase) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, + __debugToString: { value(this: FlowNodeBase) { return formatControlFlowGraph(this); } } + }); } + } - export function formatTypeFacts(facts: TypeFacts | undefined): string { - return formatEnum(facts, (ts as any).TypeFacts, /*isFlags*/ true); + export function attachFlowNodeDebugInfo(flowNode: FlowNodeBase) { + if (isDebugInfoEnabled) { + if (typeof Object.setPrototypeOf === "function") { + // if we're in es2015, attach the method to a shared prototype for `FlowNode` + // so the method doesn't show up in the watch window. + if (!flowNodeProto) { + flowNodeProto = Object.create(Object.prototype) as FlowNodeBase; + attachFlowNodeDebugInfoWorker(flowNodeProto); + } + Object.setPrototypeOf(flowNode, flowNodeProto); + } + else { + // not running in an es2015 environment, attach the method directly. + attachFlowNodeDebugInfoWorker(flowNode); + } } + } - let isDebugInfoEnabled = false; + let nodeArrayProto: NodeArray | undefined; - interface ExtendedDebugModule { - init(_ts: typeof ts): void; - formatControlFlowGraph(flowNode: FlowNode): string; + function attachNodeArrayDebugInfoWorker(array: NodeArray) { + if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line local/no-in-operator + Object.defineProperties(array, { + __tsDebuggerDisplay: { + value(this: NodeArray, defaultValue: string) { + // An `Array` with extra properties is rendered as `[A, B, prop1: 1, prop2: 2]`. Most of + // these aren't immediately useful so we trim off the `prop1: ..., prop2: ...` part from the + // formatted string. + // This regex can trigger slow backtracking because of overlapping potential captures. + // We don't care, this is debug code that's only enabled with a debugger attached - + // we're just taking note of it for anyone checking regex performance in the future. + defaultValue = String(defaultValue).replace(/(?:,[\s\w\d_]+:[^,]+)+\]$/, "]"); + return `NodeArray ${defaultValue}`; + } + } + }); } + } - let extendedDebugModule: ExtendedDebugModule | undefined; - - function extendedDebug() { - enableDebugInfo(); - if (!extendedDebugModule) { - throw new Error("Debugging helpers could not be loaded."); + export function attachNodeArrayDebugInfo(array: NodeArray) { + if (isDebugInfoEnabled) { + if (typeof Object.setPrototypeOf === "function") { + // if we're in es2015, attach the method to a shared prototype for `NodeArray` + // so the method doesn't show up in the watch window. + if (!nodeArrayProto) { + nodeArrayProto = Object.create(Array.prototype) as NodeArray; + attachNodeArrayDebugInfoWorker(nodeArrayProto); + } + Object.setPrototypeOf(array, nodeArrayProto); + } + else { + // not running in an es2015 environment, attach the method directly. + attachNodeArrayDebugInfoWorker(array); } - return extendedDebugModule; } + } + + /** + * Injects debug information into frequently used types. + */ + export function enableDebugInfo() { + if (isDebugInfoEnabled) return; + + // avoid recomputing + let weakTypeTextMap: WeakMap | undefined; + let weakNodeTextMap: WeakMap | undefined; - export function printControlFlowGraph(flowNode: FlowNode) { - return console.log(formatControlFlowGraph(flowNode)); + function getWeakTypeTextMap() { + if (weakTypeTextMap === undefined) { + if (typeof WeakMap === "function") weakTypeTextMap = new WeakMap(); + } + return weakTypeTextMap; } - export function formatControlFlowGraph(flowNode: FlowNode) { - return extendedDebug().formatControlFlowGraph(flowNode); + function getWeakNodeTextMap() { + if (weakNodeTextMap === undefined) { + if (typeof WeakMap === "function") weakNodeTextMap = new WeakMap(); + } + return weakNodeTextMap; } - let flowNodeProto: FlowNodeBase | undefined; - function attachFlowNodeDebugInfoWorker(flowNode: FlowNodeBase) { - if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line local/no-in-operator - Object.defineProperties(flowNode, { + // Add additional properties in debug mode to assist with debugging. + Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { + // for use with vscode-js-debug's new customDescriptionGenerator in launch.json + __tsDebuggerDisplay: { + value(this: Symbol) { + const symbolHeader = + this.flags & SymbolFlags.Transient ? "TransientSymbol" : + "Symbol"; + const remainingSymbolFlags = this.flags & ~SymbolFlags.Transient; + return `${symbolHeader} '${symbolName(this)}'${remainingSymbolFlags ? ` (${formatSymbolFlags(remainingSymbolFlags)})` : ""}`; + } + }, + __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } + }); + + Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { + // for use with vscode-js-debug's new customDescriptionGenerator in launch.json + __tsDebuggerDisplay: { + value(this: Type) { + const typeHeader = + this.flags & TypeFlags.Nullable ? "NullableType" : + this.flags & TypeFlags.StringOrNumberLiteral ? `LiteralType ${JSON.stringify((this as LiteralType).value)}` : + this.flags & TypeFlags.BigIntLiteral ? `LiteralType ${(this as BigIntLiteralType).value.negative ? "-" : ""}${(this as BigIntLiteralType).value.base10Value}n` : + this.flags & TypeFlags.UniqueESSymbol ? "UniqueESSymbolType" : + this.flags & TypeFlags.Enum ? "EnumType" : + this.flags & TypeFlags.Intrinsic ? `IntrinsicType ${(this as IntrinsicType).intrinsicName}` : + this.flags & TypeFlags.Union ? "UnionType" : + this.flags & TypeFlags.Intersection ? "IntersectionType" : + this.flags & TypeFlags.Index ? "IndexType" : + this.flags & TypeFlags.IndexedAccess ? "IndexedAccessType" : + this.flags & TypeFlags.Conditional ? "ConditionalType" : + this.flags & TypeFlags.Substitution ? "SubstitutionType" : + this.flags & TypeFlags.TypeParameter ? "TypeParameter" : + this.flags & TypeFlags.Object ? + (this as ObjectType).objectFlags & ObjectFlags.ClassOrInterface ? "InterfaceType" : + (this as ObjectType).objectFlags & ObjectFlags.Reference ? "TypeReference" : + (this as ObjectType).objectFlags & ObjectFlags.Tuple ? "TupleType" : + (this as ObjectType).objectFlags & ObjectFlags.Anonymous ? "AnonymousType" : + (this as ObjectType).objectFlags & ObjectFlags.Mapped ? "MappedType" : + (this as ObjectType).objectFlags & ObjectFlags.ReverseMapped ? "ReverseMappedType" : + (this as ObjectType).objectFlags & ObjectFlags.EvolvingArray ? "EvolvingArrayType" : + "ObjectType" : + "Type"; + const remainingObjectFlags = this.flags & TypeFlags.Object ? (this as ObjectType).objectFlags & ~ObjectFlags.ObjectTypeKindMask : 0; + return `${typeHeader}${this.symbol ? ` '${symbolName(this.symbol)}'` : ""}${remainingObjectFlags ? ` (${formatObjectFlags(remainingObjectFlags)})` : ""}`; + } + }, + __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, + __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this as ObjectType).objectFlags) : ""; } }, + __debugTypeToString: { + value(this: Type) { + // avoid recomputing + const map = getWeakTypeTextMap(); + let text = map?.get(this); + if (text === undefined) { + text = this.checker.typeToString(this); + map?.set(this, text); + } + return text; + } + }, + }); + + Object.defineProperties(objectAllocator.getSignatureConstructor().prototype, { + __debugFlags: { get(this: Signature) { return formatSignatureFlags(this.flags); } }, + __debugSignatureToString: { value(this: Signature) { return this.checker?.signatureToString(this); } } + }); + + const nodeConstructors = [ + objectAllocator.getNodeConstructor(), + objectAllocator.getIdentifierConstructor(), + objectAllocator.getTokenConstructor(), + objectAllocator.getSourceFileConstructor() + ]; + + for (const ctor of nodeConstructors) { + if (!hasProperty(ctor.prototype, "__debugKind")) { + Object.defineProperties(ctor.prototype, { // for use with vscode-js-debug's new customDescriptionGenerator in launch.json __tsDebuggerDisplay: { - value(this: FlowNodeBase) { - const flowHeader = - this.flags & FlowFlags.Start ? "FlowStart" : - this.flags & FlowFlags.BranchLabel ? "FlowBranchLabel" : - this.flags & FlowFlags.LoopLabel ? "FlowLoopLabel" : - this.flags & FlowFlags.Assignment ? "FlowAssignment" : - this.flags & FlowFlags.TrueCondition ? "FlowTrueCondition" : - this.flags & FlowFlags.FalseCondition ? "FlowFalseCondition" : - this.flags & FlowFlags.SwitchClause ? "FlowSwitchClause" : - this.flags & FlowFlags.ArrayMutation ? "FlowArrayMutation" : - this.flags & FlowFlags.Call ? "FlowCall" : - this.flags & FlowFlags.ReduceLabel ? "FlowReduceLabel" : - this.flags & FlowFlags.Unreachable ? "FlowUnreachable" : - "UnknownFlow"; - const remainingFlags = this.flags & ~(FlowFlags.Referenced - 1); - return `${flowHeader}${remainingFlags ? ` (${formatFlowFlags(remainingFlags)})`: ""}`; + value(this: Node) { + const nodeHeader = + isGeneratedIdentifier(this) ? "GeneratedIdentifier" : + isIdentifier(this) ? `Identifier '${idText(this)}'` : + isPrivateIdentifier(this) ? `PrivateIdentifier '${idText(this)}'` : + isStringLiteral(this) ? `StringLiteral ${JSON.stringify(this.text.length < 10 ? this.text : this.text.slice(10) + "...")}` : + isNumericLiteral(this) ? `NumericLiteral ${this.text}` : + isBigIntLiteral(this) ? `BigIntLiteral ${this.text}n` : + isTypeParameterDeclaration(this) ? "TypeParameterDeclaration" : + isParameter(this) ? "ParameterDeclaration" : + isConstructorDeclaration(this) ? "ConstructorDeclaration" : + isGetAccessorDeclaration(this) ? "GetAccessorDeclaration" : + isSetAccessorDeclaration(this) ? "SetAccessorDeclaration" : + isCallSignatureDeclaration(this) ? "CallSignatureDeclaration" : + isConstructSignatureDeclaration(this) ? "ConstructSignatureDeclaration" : + isIndexSignatureDeclaration(this) ? "IndexSignatureDeclaration" : + isTypePredicateNode(this) ? "TypePredicateNode" : + isTypeReferenceNode(this) ? "TypeReferenceNode" : + isFunctionTypeNode(this) ? "FunctionTypeNode" : + isConstructorTypeNode(this) ? "ConstructorTypeNode" : + isTypeQueryNode(this) ? "TypeQueryNode" : + isTypeLiteralNode(this) ? "TypeLiteralNode" : + isArrayTypeNode(this) ? "ArrayTypeNode" : + isTupleTypeNode(this) ? "TupleTypeNode" : + isOptionalTypeNode(this) ? "OptionalTypeNode" : + isRestTypeNode(this) ? "RestTypeNode" : + isUnionTypeNode(this) ? "UnionTypeNode" : + isIntersectionTypeNode(this) ? "IntersectionTypeNode" : + isConditionalTypeNode(this) ? "ConditionalTypeNode" : + isInferTypeNode(this) ? "InferTypeNode" : + isParenthesizedTypeNode(this) ? "ParenthesizedTypeNode" : + isThisTypeNode(this) ? "ThisTypeNode" : + isTypeOperatorNode(this) ? "TypeOperatorNode" : + isIndexedAccessTypeNode(this) ? "IndexedAccessTypeNode" : + isMappedTypeNode(this) ? "MappedTypeNode" : + isLiteralTypeNode(this) ? "LiteralTypeNode" : + isNamedTupleMember(this) ? "NamedTupleMember" : + isImportTypeNode(this) ? "ImportTypeNode" : + formatSyntaxKind(this.kind); + return `${nodeHeader}${this.flags ? ` (${formatNodeFlags(this.flags)})` : ""}`; } }, - __debugFlowFlags: { get(this: FlowNodeBase) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, - __debugToString: { value(this: FlowNodeBase) { return formatControlFlowGraph(this); } } + __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, + __debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } }, + __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getEffectiveModifierFlagsNoCache(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 ""; + // avoid recomputing + const map = getWeakNodeTextMap(); + let text = map?.get(this); + if (text === undefined) { + const parseNode = getParseTreeNode(this); + const sourceFile = parseNode && getSourceFileOfNode(parseNode); + text = sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : ""; + map?.set(this, text); + } + return text; + } + } }); } } - export function attachFlowNodeDebugInfo(flowNode: FlowNodeBase) { - if (isDebugInfoEnabled) { - if (typeof Object.setPrototypeOf === "function") { - // if we're in es2015, attach the method to a shared prototype for `FlowNode` - // so the method doesn't show up in the watch window. - if (!flowNodeProto) { - flowNodeProto = Object.create(Object.prototype) as FlowNodeBase; - attachFlowNodeDebugInfoWorker(flowNodeProto); - } - Object.setPrototypeOf(flowNode, flowNodeProto); - } - else { - // not running in an es2015 environment, attach the method directly. - attachFlowNodeDebugInfoWorker(flowNode); - } + isDebugInfoEnabled = true; + } + + function formatDeprecationMessage(name: string, error: boolean | undefined, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { + let deprecationMessage = error ? "DeprecationError: " : "DeprecationWarning: "; + deprecationMessage += `'${name}' `; + deprecationMessage += since ? `has been deprecated since v${since}` : "is deprecated"; + deprecationMessage += error ? " and can no longer be used." : errorAfter ? ` and will no longer be usable after v${errorAfter}.` : "."; + deprecationMessage += message ? ` ${formatStringFromArgs(message, [name], 0)}` : ""; + return deprecationMessage; + } + + function createErrorDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { + const deprecationMessage = formatDeprecationMessage(name, /*error*/ true, errorAfter, since, message); + return () => { + throw new TypeError(deprecationMessage); + }; + } + + function createWarningDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { + let hasWrittenDeprecation = false; + return () => { + if (enableDeprecationWarnings && !hasWrittenDeprecation) { + log.warn(formatDeprecationMessage(name, /*error*/ false, errorAfter, since, message)); + hasWrittenDeprecation = true; + } + }; + } + + export function createDeprecation(name: string, options: DeprecationOptions & { error: true }): () => never; + export function createDeprecation(name: string, options?: DeprecationOptions): () => void; + export function createDeprecation(name: string, options: DeprecationOptions = {}) { + const version = typeof options.typeScriptVersion === "string" ? new Version(options.typeScriptVersion) : options.typeScriptVersion ?? getTypeScriptVersion(); + const errorAfter = typeof options.errorAfter === "string" ? new Version(options.errorAfter) : options.errorAfter; + const warnAfter = typeof options.warnAfter === "string" ? new Version(options.warnAfter) : options.warnAfter; + const since = typeof options.since === "string" ? new Version(options.since) : options.since ?? warnAfter; + const error = options.error || errorAfter && version.compareTo(errorAfter) <= 0; + const warn = !warnAfter || version.compareTo(warnAfter) >= 0; + return error ? createErrorDeprecation(name, errorAfter, since, options.message) : + warn ? createWarningDeprecation(name, errorAfter, since, options.message) : + noop; + } + + function wrapFunction any>(deprecation: () => void, func: F): F { + return function (this: unknown) { + deprecation(); + return func.apply(this, arguments); + } as F; + } + + export function deprecate any>(func: F, options?: DeprecationOptions): F { + const deprecation = createDeprecation(options?.name ?? getFunctionName(func), options); + return wrapFunction(deprecation, func); + } + + export function formatVariance(varianceFlags: VarianceFlags) { + const variance = varianceFlags & VarianceFlags.VarianceMask; + let result = + variance === VarianceFlags.Invariant ? "in out" : + variance === VarianceFlags.Bivariant ? "[bivariant]" : + variance === VarianceFlags.Contravariant ? "in" : + variance === VarianceFlags.Covariant ? "out" : + variance === VarianceFlags.Independent ? "[independent]" : ""; + if (varianceFlags & VarianceFlags.Unmeasurable) { + result += " (unmeasurable)"; + } + else if (varianceFlags & VarianceFlags.Unreliable) { + result += " (unreliable)"; + } + return result; + } + + export type DebugType = Type & { __debugTypeToString(): string }; // eslint-disable-line @typescript-eslint/naming-convention + export class DebugTypeMapper { + declare kind: TypeMapKind; + __debugToString(): string { // eslint-disable-line @typescript-eslint/naming-convention + type(this); + switch (this.kind) { + case TypeMapKind.Function: return this.debugInfo?.() || "(function mapper)"; + case TypeMapKind.Simple: return `${(this.source as DebugType).__debugTypeToString()} -> ${(this.target as DebugType).__debugTypeToString()}`; + case TypeMapKind.Array: return zipWith( + this.sources as readonly DebugType[], + this.targets as readonly DebugType[] || map(this.sources, () => "any"), + (s, t) => `${s.__debugTypeToString()} -> ${typeof t === "string" ? t : t.__debugTypeToString()}`).join(", "); + case TypeMapKind.Deferred: return zipWith( + this.sources, + this.targets, + (s, t) => `${(s as DebugType).__debugTypeToString()} -> ${(t() as DebugType).__debugTypeToString()}`).join(", "); + case TypeMapKind.Merged: + case TypeMapKind.Composite: return `m1: ${(this.mapper1 as unknown as DebugTypeMapper).__debugToString().split("\n").join("\n ")} +m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n").join("\n ")}`; + default: return assertNever(this); } } + } - let nodeArrayProto: NodeArray | undefined; + export function attachDebugPrototypeIfDebug(mapper: TypeMapper): TypeMapper { + if (isDebugging) { + return Object.setPrototypeOf(mapper, DebugTypeMapper.prototype); + } + return mapper; + } - function attachNodeArrayDebugInfoWorker(array: NodeArray) { - if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line local/no-in-operator - Object.defineProperties(array, { - __tsDebuggerDisplay: { - value(this: NodeArray, defaultValue: string) { - // An `Array` with extra properties is rendered as `[A, B, prop1: 1, prop2: 2]`. Most of - // these aren't immediately useful so we trim off the `prop1: ..., prop2: ...` part from the - // formatted string. - // This regex can trigger slow backtracking because of overlapping potential captures. - // We don't care, this is debug code that's only enabled with a debugger attached - - // we're just taking note of it for anyone checking regex performance in the future. - defaultValue = String(defaultValue).replace(/(?:,[\s\w\d_]+:[^,]+)+\]$/, "]"); - return `NodeArray ${defaultValue}`; - } - } - }); + export function printControlFlowGraph(flowNode: FlowNode) { + return console.log(formatControlFlowGraph(flowNode)); + } + + export function formatControlFlowGraph(flowNode: FlowNode) { + let nextDebugFlowId = -1; + + function getDebugFlowNodeId(f: FlowNode) { + if (!f.id) { + f.id = nextDebugFlowId; + nextDebugFlowId--; + } + return f.id; + } + + const enum BoxCharacter { + lr = "─", + ud = "│", + dr = "╭", + dl = "╮", + ul = "╯", + ur = "╰", + udr = "├", + udl = "┤", + dlr = "┬", + ulr = "┴", + udlr = "╫", + } + + const enum Connection { + None = 0, + Up = 1 << 0, + Down = 1 << 1, + Left = 1 << 2, + Right = 1 << 3, + + UpDown = Up | Down, + LeftRight = Left | Right, + UpLeft = Up | Left, + UpRight = Up | Right, + DownLeft = Down | Left, + DownRight = Down | Right, + UpDownLeft = UpDown | Left, + UpDownRight = UpDown | Right, + UpLeftRight = Up | LeftRight, + DownLeftRight = Down | LeftRight, + UpDownLeftRight = UpDown | LeftRight, + + NoChildren = 1 << 4, + } + + interface FlowGraphNode { + id: number; + flowNode: FlowNode; + edges: FlowGraphEdge[]; + text: string; + lane: number; + endLane: number; + level: number; + circular: boolean | "circularity"; + } + + interface FlowGraphEdge { + source: FlowGraphNode; + target: FlowGraphNode; + } + + const hasAntecedentFlags = + FlowFlags.Assignment | + FlowFlags.Condition | + FlowFlags.SwitchClause | + FlowFlags.ArrayMutation | + FlowFlags.Call | + FlowFlags.ReduceLabel; + + const hasNodeFlags = + FlowFlags.Start | + FlowFlags.Assignment | + FlowFlags.Call | + FlowFlags.Condition | + FlowFlags.ArrayMutation; + + const links: Record = Object.create(/*o*/ null); // eslint-disable-line no-null/no-null + const nodes: FlowGraphNode[] = []; + const edges: FlowGraphEdge[] = []; + const root = buildGraphNode(flowNode, new Set()); + for (const node of nodes) { + node.text = renderFlowNode(node.flowNode, node.circular); + computeLevel(node); + } + + const height = computeHeight(root); + const columnWidths = computeColumnWidths(height); + computeLanes(root, 0); + return renderGraph(); + + function isFlowSwitchClause(f: FlowNode): f is FlowSwitchClause { + return !!(f.flags & FlowFlags.SwitchClause); + } + + function hasAntecedents(f: FlowNode): f is FlowLabel & { antecedents: FlowNode[] } { + return !!(f.flags & FlowFlags.Label) && !!(f as FlowLabel).antecedents; + } + + function hasAntecedent(f: FlowNode): f is Extract { + return !!(f.flags & hasAntecedentFlags); + } + + function hasNode(f: FlowNode): f is Extract { + return !!(f.flags & hasNodeFlags); + } + + function getChildren(node: FlowGraphNode) { + const children: FlowGraphNode[] = []; + for (const edge of node.edges) { + if (edge.source === node) { + children.push(edge.target); + } } + return children; } - export function attachNodeArrayDebugInfo(array: NodeArray) { - if (isDebugInfoEnabled) { - if (typeof Object.setPrototypeOf === "function") { - // if we're in es2015, attach the method to a shared prototype for `NodeArray` - // so the method doesn't show up in the watch window. - if (!nodeArrayProto) { - nodeArrayProto = Object.create(Array.prototype) as NodeArray; - attachNodeArrayDebugInfoWorker(nodeArrayProto); + function getParents(node: FlowGraphNode) { + const parents: FlowGraphNode[] = []; + for (const edge of node.edges) { + if (edge.target === node) { + parents.push(edge.source); + } + } + return parents; + } + + function buildGraphNode(flowNode: FlowNode, seen: Set): FlowGraphNode { + const id = getDebugFlowNodeId(flowNode); + let graphNode = links[id]; + if (graphNode && seen.has(flowNode)) { + graphNode.circular = true; + graphNode = { + id: -1, + flowNode, + edges: [], + text: "", + lane: -1, + endLane: -1, + level: -1, + circular: "circularity" + }; + nodes.push(graphNode); + return graphNode; + } + seen.add(flowNode); + if (!graphNode) { + links[id] = graphNode = { id, flowNode, edges: [], text: "", lane: -1, endLane: -1, level: -1, circular: false }; + nodes.push(graphNode); + if (hasAntecedents(flowNode)) { + for (const antecedent of flowNode.antecedents) { + buildGraphEdge(graphNode, antecedent, seen); } - Object.setPrototypeOf(array, nodeArrayProto); } - else { - // not running in an es2015 environment, attach the method directly. - attachNodeArrayDebugInfoWorker(array); + else if (hasAntecedent(flowNode)) { + buildGraphEdge(graphNode, flowNode.antecedent, seen); } } + seen.delete(flowNode); + return graphNode; } - /** - * Injects debug information into frequently used types. - */ - export function enableDebugInfo() { - if (isDebugInfoEnabled) return; + function buildGraphEdge(source: FlowGraphNode, antecedent: FlowNode, seen: Set) { + const target = buildGraphNode(antecedent, seen); + const edge: FlowGraphEdge = { source, target }; + edges.push(edge); + source.edges.push(edge); + target.edges.push(edge); + } - // avoid recomputing - let weakTypeTextMap: WeakMap | undefined; - let weakNodeTextMap: WeakMap | undefined; + function computeLevel(node: FlowGraphNode): number { + if (node.level !== -1) { + return node.level; + } + let level = 0; + for (const parent of getParents(node)) { + level = Math.max(level, computeLevel(parent) + 1); + } + return node.level = level; + } - function getWeakTypeTextMap() { - if (weakTypeTextMap === undefined) { - if (typeof WeakMap === "function") weakTypeTextMap = new WeakMap(); - } - return weakTypeTextMap; + function computeHeight(node: FlowGraphNode): number { + let height = 0; + for (const child of getChildren(node)) { + height = Math.max(height, computeHeight(child)); } + return height + 1; + } - function getWeakNodeTextMap() { - if (weakNodeTextMap === undefined) { - if (typeof WeakMap === "function") weakNodeTextMap = new WeakMap(); + function computeColumnWidths(height: number) { + const columns: number[] = fill(Array(height), 0); + for (const node of nodes) { + columns[node.level] = Math.max(columns[node.level], node.text.length); + } + return columns; + } + + function computeLanes(node: FlowGraphNode, lane: number) { + if (node.lane === -1) { + node.lane = lane; + node.endLane = lane; + const children = getChildren(node); + for (let i = 0; i < children.length; i++) { + if (i > 0) lane++; + const child = children[i]; + computeLanes(child, lane); + if (child.endLane > node.endLane) { + lane = child.endLane; + } } - return weakNodeTextMap; + node.endLane = lane; } + } + function getHeader(flags: FlowFlags) { + if (flags & FlowFlags.Start) return "Start"; + if (flags & FlowFlags.BranchLabel) return "Branch"; + if (flags & FlowFlags.LoopLabel) return "Loop"; + if (flags & FlowFlags.Assignment) return "Assignment"; + if (flags & FlowFlags.TrueCondition) return "True"; + if (flags & FlowFlags.FalseCondition) return "False"; + if (flags & FlowFlags.SwitchClause) return "SwitchClause"; + if (flags & FlowFlags.ArrayMutation) return "ArrayMutation"; + if (flags & FlowFlags.Call) return "Call"; + if (flags & FlowFlags.ReduceLabel) return "ReduceLabel"; + if (flags & FlowFlags.Unreachable) return "Unreachable"; + throw new Error(); + } - // Add additional properties in debug mode to assist with debugging. - Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { - // for use with vscode-js-debug's new customDescriptionGenerator in launch.json - __tsDebuggerDisplay: { - value(this: Symbol) { - const symbolHeader = - this.flags & SymbolFlags.Transient ? "TransientSymbol" : - "Symbol"; - const remainingSymbolFlags = this.flags & ~SymbolFlags.Transient; - return `${symbolHeader} '${symbolName(this)}'${remainingSymbolFlags ? ` (${formatSymbolFlags(remainingSymbolFlags)})` : ""}`; - } - }, - __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } - }); + function getNodeText(node: Node) { + const sourceFile = getSourceFileOfNode(node); + return getSourceTextOfNodeFromSourceFile(sourceFile, node, /*includeTrivia*/ false); + } - Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { - // for use with vscode-js-debug's new customDescriptionGenerator in launch.json - __tsDebuggerDisplay: { - value(this: Type) { - const typeHeader = - this.flags & TypeFlags.Nullable ? "NullableType" : - this.flags & TypeFlags.StringOrNumberLiteral ? `LiteralType ${JSON.stringify((this as LiteralType).value)}` : - this.flags & TypeFlags.BigIntLiteral ? `LiteralType ${(this as BigIntLiteralType).value.negative ? "-" : ""}${(this as BigIntLiteralType).value.base10Value}n` : - this.flags & TypeFlags.UniqueESSymbol ? "UniqueESSymbolType" : - this.flags & TypeFlags.Enum ? "EnumType" : - this.flags & TypeFlags.Intrinsic ? `IntrinsicType ${(this as IntrinsicType).intrinsicName}` : - this.flags & TypeFlags.Union ? "UnionType" : - this.flags & TypeFlags.Intersection ? "IntersectionType" : - this.flags & TypeFlags.Index ? "IndexType" : - this.flags & TypeFlags.IndexedAccess ? "IndexedAccessType" : - this.flags & TypeFlags.Conditional ? "ConditionalType" : - this.flags & TypeFlags.Substitution ? "SubstitutionType" : - this.flags & TypeFlags.TypeParameter ? "TypeParameter" : - this.flags & TypeFlags.Object ? - (this as ObjectType).objectFlags & ObjectFlags.ClassOrInterface ? "InterfaceType" : - (this as ObjectType).objectFlags & ObjectFlags.Reference ? "TypeReference" : - (this as ObjectType).objectFlags & ObjectFlags.Tuple ? "TupleType" : - (this as ObjectType).objectFlags & ObjectFlags.Anonymous ? "AnonymousType" : - (this as ObjectType).objectFlags & ObjectFlags.Mapped ? "MappedType" : - (this as ObjectType).objectFlags & ObjectFlags.ReverseMapped ? "ReverseMappedType" : - (this as ObjectType).objectFlags & ObjectFlags.EvolvingArray ? "EvolvingArrayType" : - "ObjectType" : - "Type"; - const remainingObjectFlags = this.flags & TypeFlags.Object ? (this as ObjectType).objectFlags & ~ObjectFlags.ObjectTypeKindMask : 0; - return `${typeHeader}${this.symbol ? ` '${symbolName(this.symbol)}'` : ""}${remainingObjectFlags ? ` (${formatObjectFlags(remainingObjectFlags)})` : ""}`; + function renderFlowNode(flowNode: FlowNode, circular: boolean | "circularity") { + let text = getHeader(flowNode.flags); + if (circular) { + text = `${text}#${getDebugFlowNodeId(flowNode)}`; + } + if (hasNode(flowNode)) { + if (flowNode.node) { + text += ` (${getNodeText(flowNode.node)})`; + } + } + else if (isFlowSwitchClause(flowNode)) { + const clauses: string[] = []; + for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) { + const clause = flowNode.switchStatement.caseBlock.clauses[i]; + if (isDefaultClause(clause)) { + clauses.push("default"); } - }, - __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, - __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this as ObjectType).objectFlags) : ""; } }, - __debugTypeToString: { - value(this: Type) { - // avoid recomputing - const map = getWeakTypeTextMap(); - let text = map?.get(this); - if (text === undefined) { - text = this.checker.typeToString(this); - map?.set(this, text); - } - return text; + else { + clauses.push(getNodeText(clause.expression)); } - }, - }); - - Object.defineProperties(objectAllocator.getSignatureConstructor().prototype, { - __debugFlags: { get(this: Signature) { return formatSignatureFlags(this.flags); } }, - __debugSignatureToString: { value(this: Signature) { return this.checker?.signatureToString(this); } } - }); - - const nodeConstructors = [ - objectAllocator.getNodeConstructor(), - objectAllocator.getIdentifierConstructor(), - objectAllocator.getTokenConstructor(), - objectAllocator.getSourceFileConstructor() - ]; - - for (const ctor of nodeConstructors) { - if (!hasProperty(ctor.prototype, "__debugKind")) { - Object.defineProperties(ctor.prototype, { - // for use with vscode-js-debug's new customDescriptionGenerator in launch.json - __tsDebuggerDisplay: { - value(this: Node) { - const nodeHeader = - isGeneratedIdentifier(this) ? "GeneratedIdentifier" : - isIdentifier(this) ? `Identifier '${idText(this)}'` : - isPrivateIdentifier(this) ? `PrivateIdentifier '${idText(this)}'` : - isStringLiteral(this) ? `StringLiteral ${JSON.stringify(this.text.length < 10 ? this.text : this.text.slice(10) + "...")}` : - isNumericLiteral(this) ? `NumericLiteral ${this.text}` : - isBigIntLiteral(this) ? `BigIntLiteral ${this.text}n` : - isTypeParameterDeclaration(this) ? "TypeParameterDeclaration" : - isParameter(this) ? "ParameterDeclaration" : - isConstructorDeclaration(this) ? "ConstructorDeclaration" : - isGetAccessorDeclaration(this) ? "GetAccessorDeclaration" : - isSetAccessorDeclaration(this) ? "SetAccessorDeclaration" : - isCallSignatureDeclaration(this) ? "CallSignatureDeclaration" : - isConstructSignatureDeclaration(this) ? "ConstructSignatureDeclaration" : - isIndexSignatureDeclaration(this) ? "IndexSignatureDeclaration" : - isTypePredicateNode(this) ? "TypePredicateNode" : - isTypeReferenceNode(this) ? "TypeReferenceNode" : - isFunctionTypeNode(this) ? "FunctionTypeNode" : - isConstructorTypeNode(this) ? "ConstructorTypeNode" : - isTypeQueryNode(this) ? "TypeQueryNode" : - isTypeLiteralNode(this) ? "TypeLiteralNode" : - isArrayTypeNode(this) ? "ArrayTypeNode" : - isTupleTypeNode(this) ? "TupleTypeNode" : - isOptionalTypeNode(this) ? "OptionalTypeNode" : - isRestTypeNode(this) ? "RestTypeNode" : - isUnionTypeNode(this) ? "UnionTypeNode" : - isIntersectionTypeNode(this) ? "IntersectionTypeNode" : - isConditionalTypeNode(this) ? "ConditionalTypeNode" : - isInferTypeNode(this) ? "InferTypeNode" : - isParenthesizedTypeNode(this) ? "ParenthesizedTypeNode" : - isThisTypeNode(this) ? "ThisTypeNode" : - isTypeOperatorNode(this) ? "TypeOperatorNode" : - isIndexedAccessTypeNode(this) ? "IndexedAccessTypeNode" : - isMappedTypeNode(this) ? "MappedTypeNode" : - isLiteralTypeNode(this) ? "LiteralTypeNode" : - isNamedTupleMember(this) ? "NamedTupleMember" : - isImportTypeNode(this) ? "ImportTypeNode" : - formatSyntaxKind(this.kind); - return `${nodeHeader}${this.flags ? ` (${formatNodeFlags(this.flags)})` : ""}`; - } - }, - __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, - __debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } }, - __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getEffectiveModifierFlagsNoCache(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 ""; - // avoid recomputing - const map = getWeakNodeTextMap(); - let text = map?.get(this); - if (text === undefined) { - const parseNode = getParseTreeNode(this); - const sourceFile = parseNode && getSourceFileOfNode(parseNode); - text = sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : ""; - map?.set(this, text); - } - return text; - } - } - }); + } + text += ` (${clauses.join(", ")})`; + } + return circular === "circularity" ? `Circular(${text})` : text; + } + + function renderGraph() { + const columnCount = columnWidths.length; + const laneCount = nodes.reduce((x, n) => Math.max(x, n.lane), 0) + 1; + const lanes: string[] = fill(Array(laneCount), ""); + const grid: (FlowGraphNode | undefined)[][] = columnWidths.map(() => Array(laneCount)); + const connectors: Connection[][] = columnWidths.map(() => fill(Array(laneCount), 0)); + + // build connectors + for (const node of nodes) { + grid[node.level][node.lane] = node; + const children = getChildren(node); + for (let i = 0; i < children.length; i++) { + const child = children[i]; + let connector: Connection = Connection.Right; + if (child.lane === node.lane) connector |= Connection.Left; + if (i > 0) connector |= Connection.Up; + if (i < children.length - 1) connector |= Connection.Down; + connectors[node.level][child.lane] |= connector; + } + if (children.length === 0) { + connectors[node.level][node.lane] |= Connection.NoChildren; + } + const parents = getParents(node); + for (let i = 0; i < parents.length; i++) { + const parent = parents[i]; + let connector: Connection = Connection.Left; + if (i > 0) connector |= Connection.Up; + if (i < parents.length - 1) connector |= Connection.Down; + connectors[node.level - 1][parent.lane] |= connector; } } - // attempt to load extended debugging information - try { - if (sys && sys.require) { - const basePath = getDirectoryPath(resolvePath(sys.getExecutingFilePath())); - const result = sys.require(basePath, "./compiler-debug") as RequireResult; - if (!result.error) { - result.module.init(ts); - extendedDebugModule = result.module; + // fill in missing connectors + for (let column = 0; column < columnCount; column++) { + for (let lane = 0; lane < laneCount; lane++) { + const left = column > 0 ? connectors[column - 1][lane] : 0; + const above = lane > 0 ? connectors[column][lane - 1] : 0; + let connector = connectors[column][lane]; + if (!connector) { + if (left & Connection.Right) connector |= Connection.LeftRight; + if (above & Connection.Down) connector |= Connection.UpDown; + connectors[column][lane] = connector; } } } - catch { - // do nothing + + for (let column = 0; column < columnCount; column++) { + for (let lane = 0; lane < lanes.length; lane++) { + const connector = connectors[column][lane]; + const fill = connector & Connection.Left ? BoxCharacter.lr : " "; + const node = grid[column][lane]; + if (!node) { + if (column < columnCount - 1) { + writeLane(lane, repeat(fill, columnWidths[column] + 1)); + } + } + else { + writeLane(lane, node.text); + if (column < columnCount - 1) { + writeLane(lane, " "); + writeLane(lane, repeat(fill, columnWidths[column] - node.text.length)); + } + } + writeLane(lane, getBoxCharacter(connector)); + writeLane(lane, connector & Connection.Right && column < columnCount - 1 && !grid[column + 1][lane] ? BoxCharacter.lr : " "); + } } - isDebugInfoEnabled = true; - } + return `\n${lanes.join("\n")}\n`; - function formatDeprecationMessage(name: string, error: boolean | undefined, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { - let deprecationMessage = error ? "DeprecationError: " : "DeprecationWarning: "; - deprecationMessage += `'${name}' `; - deprecationMessage += since ? `has been deprecated since v${since}` : "is deprecated"; - deprecationMessage += error ? " and can no longer be used." : errorAfter ? ` and will no longer be usable after v${errorAfter}.` : "."; - deprecationMessage += message ? ` ${formatStringFromArgs(message, [name], 0)}` : ""; - return deprecationMessage; + function writeLane(lane: number, text: string) { + lanes[lane] += text; + } } - function createErrorDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { - const deprecationMessage = formatDeprecationMessage(name, /*error*/ true, errorAfter, since, message); - return () => { - throw new TypeError(deprecationMessage); - }; + function getBoxCharacter(connector: Connection) { + switch (connector) { + case Connection.UpDown: return BoxCharacter.ud; + case Connection.LeftRight: return BoxCharacter.lr; + case Connection.UpLeft: return BoxCharacter.ul; + case Connection.UpRight: return BoxCharacter.ur; + case Connection.DownLeft: return BoxCharacter.dl; + case Connection.DownRight: return BoxCharacter.dr; + case Connection.UpDownLeft: return BoxCharacter.udl; + case Connection.UpDownRight: return BoxCharacter.udr; + case Connection.UpLeftRight: return BoxCharacter.ulr; + case Connection.DownLeftRight: return BoxCharacter.dlr; + case Connection.UpDownLeftRight: return BoxCharacter.udlr; + } + return " "; } - function createWarningDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { - let hasWrittenDeprecation = false; - return () => { - if (enableDeprecationWarnings && !hasWrittenDeprecation) { - log.warn(formatDeprecationMessage(name, /*error*/ false, errorAfter, since, message)); - hasWrittenDeprecation = true; - } - }; - } - - export function createDeprecation(name: string, options: DeprecationOptions & { error: true }): () => never; - export function createDeprecation(name: string, options?: DeprecationOptions): () => void; - export function createDeprecation(name: string, options: DeprecationOptions = {}) { - const version = typeof options.typeScriptVersion === "string" ? new Version(options.typeScriptVersion) : options.typeScriptVersion ?? getTypeScriptVersion(); - const errorAfter = typeof options.errorAfter === "string" ? new Version(options.errorAfter) : options.errorAfter; - const warnAfter = typeof options.warnAfter === "string" ? new Version(options.warnAfter) : options.warnAfter; - const since = typeof options.since === "string" ? new Version(options.since) : options.since ?? warnAfter; - const error = options.error || errorAfter && version.compareTo(errorAfter) <= 0; - const warn = !warnAfter || version.compareTo(warnAfter) >= 0; - return error ? createErrorDeprecation(name, errorAfter, since, options.message) : - warn ? createWarningDeprecation(name, errorAfter, since, options.message) : - noop; - } - - function wrapFunction any>(deprecation: () => void, func: F): F { - return function (this: unknown) { - deprecation(); - return func.apply(this, arguments); - } as F; - } - - export function deprecate any>(func: F, options?: DeprecationOptions): F { - const deprecation = createDeprecation(options?.name ?? getFunctionName(func), options); - return wrapFunction(deprecation, func); - } - - export function formatVariance(varianceFlags: VarianceFlags) { - const variance = varianceFlags & VarianceFlags.VarianceMask; - let result = - variance === VarianceFlags.Invariant ? "in out" : - variance === VarianceFlags.Bivariant ? "[bivariant]" : - variance === VarianceFlags.Contravariant ? "in" : - variance === VarianceFlags.Covariant ? "out" : - variance === VarianceFlags.Independent ? "[independent]" : ""; - if (varianceFlags & VarianceFlags.Unmeasurable) { - result += " (unmeasurable)"; - } - else if (varianceFlags & VarianceFlags.Unreliable) { - result += " (unreliable)"; - } - return result; - } - - export type DebugType = Type & { __debugTypeToString(): string }; // eslint-disable-line @typescript-eslint/naming-convention - export class DebugTypeMapper { - declare kind: TypeMapKind; - __debugToString(): string { // eslint-disable-line @typescript-eslint/naming-convention - type(this); - switch (this.kind) { - case TypeMapKind.Function: return this.debugInfo?.() || "(function mapper)"; - case TypeMapKind.Simple: return `${(this.source as DebugType).__debugTypeToString()} -> ${(this.target as DebugType).__debugTypeToString()}`; - case TypeMapKind.Array: return zipWith( - this.sources as readonly DebugType[], - this.targets as readonly DebugType[] || map(this.sources, () => "any"), - (s, t) => `${s.__debugTypeToString()} -> ${typeof t === "string" ? t : t.__debugTypeToString()}`).join(", "); - case TypeMapKind.Deferred: return zipWith( - this.sources, - this.targets, - (s, t) => `${(s as DebugType).__debugTypeToString()} -> ${(t() as DebugType).__debugTypeToString()}`).join(", "); - case TypeMapKind.Merged: - case TypeMapKind.Composite: return `m1: ${(this.mapper1 as unknown as DebugTypeMapper).__debugToString().split("\n").join("\n ")} -m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n").join("\n ")}`; - default: return assertNever(this); + function fill(array: T[], value: T) { + if (array.fill) { + array.fill(value); + } + else { + for (let i = 0; i < array.length; i++) { + array[i] = value; } } + return array; } - export function attachDebugPrototypeIfDebug(mapper: TypeMapper): TypeMapper { - if (isDebugging) { - return Object.setPrototypeOf(mapper, DebugTypeMapper.prototype); + function repeat(ch: string, length: number) { + if (ch.repeat) { + return length > 0 ? ch.repeat(length) : ""; + } + let s = ""; + while (s.length < length) { + s += ch; } - return mapper; + return s; } } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index d9117d14c719a..4c878210dd016 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1,5262 +1,5366 @@ -namespace ts { - const brackets = createBracketsMap(); +import * as ts from "./_namespaces/ts"; +import { + AccessorDeclaration, ArrayBindingPattern, ArrayLiteralExpression, arrayToMap, ArrayTypeNode, ArrowFunction, + AsExpression, AssertClause, AssertEntry, AwaitExpression, base64encode, BigIntLiteral, BinaryExpression, + BinaryOperatorToken, BindingElement, BindingPattern, Block, BlockLike, BreakStatement, BuildInfo, Bundle, + BundleBuildInfo, BundleFileInfo, BundleFileSectionKind, BundleFileTextLike, BundleFileTextLikeKind, CallExpression, + CallSignatureDeclaration, CaseBlock, CaseClause, CaseOrDefaultClause, cast, CatchClause, changeExtension, + CharacterCodes, ClassDeclaration, ClassExpression, ClassStaticBlockDeclaration, clone, combinePaths, + CommaListExpression, CommentRange, compareEmitHelpers, comparePaths, Comparison, CompilerHost, CompilerOptions, + computeCommonSourceDirectoryOfFilenames, ComputedPropertyName, computeLineStarts, computeSignature, + ConditionalExpression, ConditionalTypeNode, ConstructorDeclaration, ConstructorTypeNode, + ConstructSignatureDeclaration, contains, ContinueStatement, createBinaryExpressionTrampoline, + createDiagnosticCollection, createGetCanonicalFileName, createInputFilesWithFileTexts, createMultiMap, + createPrependNodes, createSourceMapGenerator, createTextWriter, CustomTransformers, Debug, DebuggerStatement, + DeclarationName, Decorator, DefaultClause, DeleteExpression, directorySeparator, DoStatement, DotToken, + ElementAccessExpression, emitDetachedComments, EmitFileNames, EmitFlags, EmitHint, EmitHost, + emitNewLineBeforeLeadingCommentOfPosition, EmitOnly, EmitResolver, EmitResult, EmitTextWriter, EmitTransformers, + emptyArray, ensurePathIsNonModuleName, ensureTrailingDirectorySeparator, EntityName, EnumDeclaration, EnumMember, + escapeJsxAttributeString, escapeLeadingUnderscores, escapeNonAsciiString, escapeString, ESMap, every, + ExportAssignment, ExportDeclaration, ExportSpecifier, Expression, ExpressionStatement, ExpressionWithTypeArguments, + Extension, ExternalModuleReference, factory, fileExtensionIs, fileExtensionIsOneOf, FileReference, filter, + findIndex, firstOrUndefined, forEach, forEachChild, forEachLeadingCommentRange, forEachTrailingCommentRange, + ForInOrOfStatement, ForInStatement, formatGeneratedName, formatGeneratedNamePart, ForOfStatement, ForStatement, + FunctionDeclaration, FunctionExpression, FunctionLikeDeclaration, FunctionTypeNode, GeneratedIdentifier, + GeneratedIdentifierFlags, GeneratedNamePart, GeneratedPrivateIdentifier, getAreDeclarationMapsEnabled, + getBaseFileName, GetCanonicalFileName, getCommentRange, getConstantValue, getContainingNodeArray, + getDeclarationEmitExtensionForPath, getDeclarationEmitOutputFilePath, getDirectoryPath, getEmitDeclarations, + getEmitFlags, getEmitHelpers, getEmitModuleKind, getExternalHelpersModuleName, getExternalModuleName, + getLeadingCommentRanges, getLineAndCharacterOfPosition, getLinesBetweenPositionAndNextNonWhitespaceCharacter, + getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter, getLinesBetweenRangeEndAndRangeStart, getLineStarts, + getLiteralText, GetLiteralTextFlags, getNewLineCharacter, getNodeForGeneratedName, getNodeId, + getNormalizedAbsolutePath, getOriginalNode, getOwnEmitOutputFilePath, getParseTreeNode, + getRelativePathFromDirectory, getRelativePathToDirectoryOrUrl, getRootLength, getShebang, getSnippetElement, + getSourceFileOfNode, getSourceFilePathInNewDir, getSourceFilesToEmit, getSourceMapRange, + getSourceTextOfNodeFromSourceFile, getStartsOnNewLine, getSyntheticLeadingComments, getSyntheticTrailingComments, + getTextOfJSDocComment, getTrailingCommentRanges, getTrailingSemicolonDeferringWriter, getTransformers, getTypeNode, + guessIndentation, hasRecordedExternalHelpers, HeritageClause, Identifier, idText, IfStatement, ImportClause, + ImportDeclaration, ImportEqualsDeclaration, ImportOrExportSpecifier, ImportSpecifier, ImportTypeNode, + IndexedAccessTypeNode, IndexSignatureDeclaration, InferTypeNode, InterfaceDeclaration, IntersectionTypeNode, + isAccessExpression, isArray, isArrowFunction, isBinaryExpression, isBindingPattern, isBlock, isBundle, + isBundleFileTextLike, isDeclaration, isDeclarationFileName, isDecorator, isEmptyStatement, isExportAssignment, + isExportSpecifier, isExpression, isFunctionLike, isGeneratedIdentifier, isGeneratedPrivateIdentifier, isIdentifier, + isIncrementalCompilation, isInJsonFile, isInternalDeclaration, isJSDocLikeText, isJsonSourceFile, + isJsxClosingElement, isJsxOpeningElement, isKeyword, isLet, isLiteralExpression, isMemberName, isModifier, + isModuleDeclaration, isNodeDescendantOf, isNumericLiteral, isParenthesizedExpression, isPartiallyEmittedExpression, + isPinnedComment, isPrivateIdentifier, isPrologueDirective, isRecognizedTripleSlashComment, isSourceFile, + isSourceFileNotJson, isStringLiteral, isTemplateLiteralKind, isTokenKind, isTypeParameterDeclaration, + isUnparsedNode, isUnparsedPrepend, isUnparsedSource, isVarConst, isVariableStatement, JSDoc, JSDocAugmentsTag, + JSDocCallbackTag, JSDocComment, JSDocEnumTag, JSDocFunctionType, JSDocImplementsTag, JSDocNameReference, + JSDocNonNullableType, JSDocNullableType, JSDocOptionalType, JSDocPropertyLikeTag, JSDocReturnTag, JSDocSeeTag, + JSDocSignature, JSDocTag, JSDocTemplateTag, JSDocThisTag, JSDocTypedefTag, JSDocTypeExpression, JSDocTypeLiteral, + JSDocTypeTag, JSDocVariadicType, JsxAttribute, JsxAttributes, JsxClosingElement, JsxClosingFragment, JsxElement, + JsxEmit, JsxExpression, JsxFragment, JsxOpeningElement, JsxOpeningFragment, JsxSelfClosingElement, + JsxSpreadAttribute, JsxTagNameExpression, JsxText, LabeledStatement, last, lastOrUndefined, LateBoundDeclaration, + length, ListFormat, LiteralExpression, LiteralLikeNode, LiteralTypeNode, makeIdentifierFromModuleName, Map, + MappedTypeNode, maybeBind, memoize, MetaProperty, MethodDeclaration, MethodSignature, Modifier, ModifierLike, + ModuleBlock, ModuleDeclaration, ModuleKind, ModuleReference, NamedDeclaration, NamedExports, NamedImports, + NamedImportsOrExports, NamedTupleMember, NamespaceExport, NamespaceExportDeclaration, NamespaceImport, + NewExpression, Node, NodeArray, NodeFlags, nodeIsSynthesized, noEmitNotification, noEmitSubstitution, + NonNullExpression, normalizePath, normalizeSlashes, notImplemented, NumericLiteral, ObjectBindingPattern, + ObjectLiteralExpression, OptionalTypeNode, outFile, OutputFile, ParameterDeclaration, ParenthesizedExpression, + ParenthesizedTypeNode, ParsedCommandLine, PartiallyEmittedExpression, Placeholder, positionIsSynthesized, + positionsAreOnSameLine, PostfixUnaryExpression, PrefixUnaryExpression, Printer, PrinterOptions, PrintHandlers, + PrivateIdentifier, ProgramBuildInfo, ProgramBundleEmitBuildInfo, ProjectReference, PropertyAccessExpression, + PropertyAssignment, PropertyDeclaration, PropertySignature, QualifiedName, rangeEndIsOnSameLineAsRangeStart, + rangeEndPositionsAreOnSameLine, rangeIsOnSingleLine, rangeStartPositionsAreOnSameLine, readJsonOrUndefined, + removeFileExtension, resolvePath, RestTypeNode, returnFalse, ReturnStatement, returnUndefined, SatisfiesExpression, + ScriptTarget, Set, setEachParent, setOriginalNode, setParent, setTextRange, setTextRangePosEnd, + setTextRangePosWidth, ShorthandPropertyAssignment, SignatureDeclaration, singleOrUndefined, + skipPartiallyEmittedExpressions, skipTrivia, SnippetElement, SnippetKind, some, SourceFile, + SourceFilePrologueDirective, SourceFilePrologueInfo, SourceMapEmitResult, SourceMapGenerator, SourceMapSource, + SpreadAssignment, SpreadElement, stableSort, Statement, stringContains, StringLiteral, supportedJSExtensionsFlat, + SwitchStatement, Symbol, SymbolFlags, SyntaxKind, SynthesizedComment, sys, TabStop, TaggedTemplateExpression, + TemplateExpression, TemplateLiteralTypeNode, TemplateLiteralTypeSpan, TemplateSpan, TextRange, ThrowStatement, + tokenToString, tracing, TransformationResult, transformNodes, tryParseRawSourceMap, TryStatement, TupleTypeNode, + TypeAliasDeclaration, TypeAssertion, TypeLiteralNode, TypeNode, TypeOfExpression, TypeOperatorNode, + TypeParameterDeclaration, TypePredicateNode, TypeQueryNode, TypeReferenceNode, UnionTypeNode, UnparsedNode, + UnparsedPrepend, UnparsedPrologue, UnparsedSource, UnparsedSyntheticReference, UnparsedTextLike, + VariableDeclaration, VariableDeclarationList, VariableStatement, VoidExpression, WhileStatement, WithStatement, + writeCommentRange, writeFile, WriteFileCallbackData, YieldExpression, +} from "./_namespaces/ts"; +import * as performance from "./_namespaces/ts.performance"; + +const brackets = createBracketsMap(); + +/** @internal */ +export function isBuildInfoFile(file: string) { + return fileExtensionIs(file, Extension.TsBuildInfo); +} - /*@internal*/ - export function isBuildInfoFile(file: string) { - return fileExtensionIs(file, Extension.TsBuildInfo); +/** + * Iterates over the source files that are expected to have an emit output. + * + * @param host An EmitHost. + * @param action The action to execute. + * @param sourceFilesOrTargetSourceFile + * If an array, the full list of source files to emit. + * Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit. + * + * @internal + */ +export function forEachEmittedFile( + host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) => T, + sourceFilesOrTargetSourceFile?: readonly SourceFile[] | SourceFile, + forceDtsEmit = false, + onlyBuildInfo?: boolean, + includeBuildInfo?: boolean) { + const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile, forceDtsEmit); + const options = host.getCompilerOptions(); + if (outFile(options)) { + const prepends = host.getPrependNodes(); + if (sourceFiles.length || prepends.length) { + const bundle = factory.createBundle(sourceFiles, prepends); + const result = action(getOutputPathsFor(bundle, host, forceDtsEmit), bundle); + if (result) { + return result; + } + } } - - /*@internal*/ - /** - * Iterates over the source files that are expected to have an emit output. - * - * @param host An EmitHost. - * @param action The action to execute. - * @param sourceFilesOrTargetSourceFile - * If an array, the full list of source files to emit. - * Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit. - */ - export function forEachEmittedFile( - host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) => T, - sourceFilesOrTargetSourceFile?: readonly SourceFile[] | SourceFile, - forceDtsEmit = false, - onlyBuildInfo?: boolean, - includeBuildInfo?: boolean) { - const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile, forceDtsEmit); - const options = host.getCompilerOptions(); - if (outFile(options)) { - const prepends = host.getPrependNodes(); - if (sourceFiles.length || prepends.length) { - const bundle = factory.createBundle(sourceFiles, prepends); - const result = action(getOutputPathsFor(bundle, host, forceDtsEmit), bundle); + else { + if (!onlyBuildInfo) { + for (const sourceFile of sourceFiles) { + const result = action(getOutputPathsFor(sourceFile, host, forceDtsEmit), sourceFile); if (result) { return result; } } } - else { - if (!onlyBuildInfo) { - for (const sourceFile of sourceFiles) { - const result = action(getOutputPathsFor(sourceFile, host, forceDtsEmit), sourceFile); - if (result) { - return result; - } - } - } - if (includeBuildInfo) { - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); - if (buildInfoPath) return action({ buildInfoPath }, /*sourceFileOrBundle*/ undefined); - } + if (includeBuildInfo) { + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); + if (buildInfoPath) return action({ buildInfoPath }, /*sourceFileOrBundle*/ undefined); } } +} - export function getTsBuildInfoEmitOutputFilePath(options: CompilerOptions) { - const configFile = options.configFilePath; - if (!isIncrementalCompilation(options)) return undefined; - if (options.tsBuildInfoFile) return options.tsBuildInfoFile; - const outPath = outFile(options); - let buildInfoExtensionLess: string; - if (outPath) { - buildInfoExtensionLess = removeFileExtension(outPath); - } - else { - if (!configFile) return undefined; - const configFileExtensionLess = removeFileExtension(configFile); - buildInfoExtensionLess = options.outDir ? - options.rootDir ? - resolvePath(options.outDir, getRelativePathFromDirectory(options.rootDir, configFileExtensionLess, /*ignoreCase*/ true)) : - combinePaths(options.outDir, getBaseFileName(configFileExtensionLess)) : - configFileExtensionLess; - } - return buildInfoExtensionLess + Extension.TsBuildInfo; - } - - /*@internal*/ - export function getOutputPathsForBundle(options: CompilerOptions, forceDtsPaths: boolean): EmitFileNames { - const outPath = outFile(options)!; - const jsFilePath = options.emitDeclarationOnly ? undefined : outPath; - const sourceMapFilePath = jsFilePath && getSourceMapFilePath(jsFilePath, options); - const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(outPath) + Extension.Dts : undefined; - const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); - return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }; +export function getTsBuildInfoEmitOutputFilePath(options: CompilerOptions) { + const configFile = options.configFilePath; + if (!isIncrementalCompilation(options)) return undefined; + if (options.tsBuildInfoFile) return options.tsBuildInfoFile; + const outPath = outFile(options); + let buildInfoExtensionLess: string; + if (outPath) { + buildInfoExtensionLess = removeFileExtension(outPath); } - - /*@internal*/ - export function getOutputPathsFor(sourceFile: SourceFile | Bundle, host: EmitHost, forceDtsPaths: boolean): EmitFileNames { - const options = host.getCompilerOptions(); - if (sourceFile.kind === SyntaxKind.Bundle) { - return getOutputPathsForBundle(options, forceDtsPaths); - } - else { - const ownOutputFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile.fileName, options)); - const isJsonFile = isJsonSourceFile(sourceFile); - // If json file emits to the same location skip writing it, if emitDeclarationOnly skip writing it - const isJsonEmittedToSameLocation = isJsonFile && - comparePaths(sourceFile.fileName, ownOutputFilePath, host.getCurrentDirectory(), !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; - const jsFilePath = options.emitDeclarationOnly || isJsonEmittedToSameLocation ? undefined : ownOutputFilePath; - const sourceMapFilePath = !jsFilePath || isJsonSourceFile(sourceFile) ? undefined : getSourceMapFilePath(jsFilePath, options); - const declarationFilePath = (forceDtsPaths || (getEmitDeclarations(options) && !isJsonFile)) ? getDeclarationEmitOutputFilePath(sourceFile.fileName, host) : undefined; - const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; - return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath: undefined }; - } - } - - function getSourceMapFilePath(jsFilePath: string, options: CompilerOptions) { - return (options.sourceMap && !options.inlineSourceMap) ? jsFilePath + ".map" : undefined; - } - - /* @internal */ - export function getOutputExtension(fileName: string, options: CompilerOptions): Extension { - return fileExtensionIs(fileName, Extension.Json) ? Extension.Json : - options.jsx === JsxEmit.Preserve && fileExtensionIsOneOf(fileName, [Extension.Jsx, Extension.Tsx]) ? Extension.Jsx : - fileExtensionIsOneOf(fileName, [Extension.Mts, Extension.Mjs]) ? Extension.Mjs : - fileExtensionIsOneOf(fileName, [Extension.Cts, Extension.Cjs]) ? Extension.Cjs : - Extension.Js; - } - - function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined, getCommonSourceDirectory?: () => string) { - return outputDir ? - resolvePath( - outputDir, - getRelativePathFromDirectory(getCommonSourceDirectory ? getCommonSourceDirectory() : getCommonSourceDirectoryOfConfig(configFile, ignoreCase), inputFileName, ignoreCase) - ) : - inputFileName; - } - - /* @internal */ - export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { - return changeExtension( - getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir, getCommonSourceDirectory), - getDeclarationEmitExtensionForPath(inputFileName) - ); + else { + if (!configFile) return undefined; + const configFileExtensionLess = removeFileExtension(configFile); + buildInfoExtensionLess = options.outDir ? + options.rootDir ? + resolvePath(options.outDir, getRelativePathFromDirectory(options.rootDir, configFileExtensionLess, /*ignoreCase*/ true)) : + combinePaths(options.outDir, getBaseFileName(configFileExtensionLess)) : + configFileExtensionLess; } + return buildInfoExtensionLess + Extension.TsBuildInfo; +} - function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { - if (configFile.options.emitDeclarationOnly) return undefined; - const isJsonFile = fileExtensionIs(inputFileName, Extension.Json); - const outputFileName = changeExtension( - getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir, getCommonSourceDirectory), - getOutputExtension(inputFileName, configFile.options) - ); - return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.checkDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ? - outputFileName : - undefined; - } +/** @internal */ +export function getOutputPathsForBundle(options: CompilerOptions, forceDtsPaths: boolean): EmitFileNames { + const outPath = outFile(options)!; + const jsFilePath = options.emitDeclarationOnly ? undefined : outPath; + const sourceMapFilePath = jsFilePath && getSourceMapFilePath(jsFilePath, options); + const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(outPath) + Extension.Dts : undefined; + const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); + return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }; +} - function createAddOutput() { - let outputs: string[] | undefined; - return { addOutput, getOutputs }; - function addOutput(path: string | undefined) { - if (path) { - (outputs || (outputs = [])).push(path); - } - } - function getOutputs(): readonly string[] { - return outputs || emptyArray; - } +/** @internal */ +export function getOutputPathsFor(sourceFile: SourceFile | Bundle, host: EmitHost, forceDtsPaths: boolean): EmitFileNames { + const options = host.getCompilerOptions(); + if (sourceFile.kind === SyntaxKind.Bundle) { + return getOutputPathsForBundle(options, forceDtsPaths); } - - function getSingleOutputFileNames(configFile: ParsedCommandLine, addOutput: ReturnType["addOutput"]) { - const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); - addOutput(jsFilePath); - addOutput(sourceMapFilePath); - addOutput(declarationFilePath); - addOutput(declarationMapPath); - addOutput(buildInfoPath); + else { + const ownOutputFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile.fileName, options)); + const isJsonFile = isJsonSourceFile(sourceFile); + // If json file emits to the same location skip writing it, if emitDeclarationOnly skip writing it + const isJsonEmittedToSameLocation = isJsonFile && + comparePaths(sourceFile.fileName, ownOutputFilePath, host.getCurrentDirectory(), !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + const jsFilePath = options.emitDeclarationOnly || isJsonEmittedToSameLocation ? undefined : ownOutputFilePath; + const sourceMapFilePath = !jsFilePath || isJsonSourceFile(sourceFile) ? undefined : getSourceMapFilePath(jsFilePath, options); + const declarationFilePath = (forceDtsPaths || (getEmitDeclarations(options) && !isJsonFile)) ? getDeclarationEmitOutputFilePath(sourceFile.fileName, host) : undefined; + const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined; + return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath: undefined }; } +} - function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType["addOutput"], getCommonSourceDirectory?: () => string) { - if (isDeclarationFileName(inputFileName)) return; - const js = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); - addOutput(js); - if (fileExtensionIs(inputFileName, Extension.Json)) return; - if (js && configFile.options.sourceMap) { - addOutput(`${js}.map`); - } - if (getEmitDeclarations(configFile.options)) { - const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); - addOutput(dts); - if (configFile.options.declarationMap) { - addOutput(`${dts}.map`); - } +function getSourceMapFilePath(jsFilePath: string, options: CompilerOptions) { + return (options.sourceMap && !options.inlineSourceMap) ? jsFilePath + ".map" : undefined; +} + +/** @internal */ +export function getOutputExtension(fileName: string, options: CompilerOptions): Extension { + return fileExtensionIs(fileName, Extension.Json) ? Extension.Json : + options.jsx === JsxEmit.Preserve && fileExtensionIsOneOf(fileName, [Extension.Jsx, Extension.Tsx]) ? Extension.Jsx : + fileExtensionIsOneOf(fileName, [Extension.Mts, Extension.Mjs]) ? Extension.Mjs : + fileExtensionIsOneOf(fileName, [Extension.Cts, Extension.Cjs]) ? Extension.Cjs : + Extension.Js; +} + +function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined, getCommonSourceDirectory?: () => string) { + return outputDir ? + resolvePath( + outputDir, + getRelativePathFromDirectory(getCommonSourceDirectory ? getCommonSourceDirectory() : getCommonSourceDirectoryOfConfig(configFile, ignoreCase), inputFileName, ignoreCase) + ) : + inputFileName; +} + +/** @internal */ +export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { + return changeExtension( + getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir, getCommonSourceDirectory), + getDeclarationEmitExtensionForPath(inputFileName) + ); +} + +function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { + if (configFile.options.emitDeclarationOnly) return undefined; + const isJsonFile = fileExtensionIs(inputFileName, Extension.Json); + const outputFileName = changeExtension( + getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir, getCommonSourceDirectory), + getOutputExtension(inputFileName, configFile.options) + ); + return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.checkDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ? + outputFileName : + undefined; +} + +function createAddOutput() { + let outputs: string[] | undefined; + return { addOutput, getOutputs }; + function addOutput(path: string | undefined) { + if (path) { + (outputs || (outputs = [])).push(path); } } + function getOutputs(): readonly string[] { + return outputs || emptyArray; + } +} - /*@internal*/ - export function getCommonSourceDirectory( - options: CompilerOptions, - emittedFiles: () => readonly string[], - currentDirectory: string, - getCanonicalFileName: GetCanonicalFileName, - checkSourceFilesBelongToPath?: (commonSourceDirectory: string) => void - ): string { - let commonSourceDirectory; - if (options.rootDir) { - // If a rootDir is specified use it as the commonSourceDirectory - commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory); - checkSourceFilesBelongToPath?.(options.rootDir); - } - else if (options.composite && options.configFilePath) { - // Project compilations never infer their root from the input source paths - commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath)); - checkSourceFilesBelongToPath?.(commonSourceDirectory); - } - else { - commonSourceDirectory = computeCommonSourceDirectoryOfFilenames(emittedFiles(), currentDirectory, getCanonicalFileName); - } +function getSingleOutputFileNames(configFile: ParsedCommandLine, addOutput: ReturnType["addOutput"]) { + const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); + addOutput(jsFilePath); + addOutput(sourceMapFilePath); + addOutput(declarationFilePath); + addOutput(declarationMapPath); + addOutput(buildInfoPath); +} - if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) { - // Make sure directory path ends with directory separator so this string can directly - // used to replace with "" to get the relative path of the source file and the relative path doesn't - // start with / making it rooted path - commonSourceDirectory += directorySeparator; +function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType["addOutput"], getCommonSourceDirectory?: () => string) { + if (isDeclarationFileName(inputFileName)) return; + const js = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); + addOutput(js); + if (fileExtensionIs(inputFileName, Extension.Json)) return; + if (js && configFile.options.sourceMap) { + addOutput(`${js}.map`); + } + if (getEmitDeclarations(configFile.options)) { + const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); + addOutput(dts); + if (configFile.options.declarationMap) { + addOutput(`${dts}.map`); } - return commonSourceDirectory; } +} - /*@internal*/ - export function getCommonSourceDirectoryOfConfig({ options, fileNames }: ParsedCommandLine, ignoreCase: boolean): string { - return getCommonSourceDirectory( - options, - () => filter(fileNames, file => !(options.noEmitForJsFiles && fileExtensionIsOneOf(file, supportedJSExtensionsFlat)) && !isDeclarationFileName(file)), - getDirectoryPath(normalizeSlashes(Debug.checkDefined(options.configFilePath))), - createGetCanonicalFileName(!ignoreCase) - ); +/** @internal */ +export function getCommonSourceDirectory( + options: CompilerOptions, + emittedFiles: () => readonly string[], + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName, + checkSourceFilesBelongToPath?: (commonSourceDirectory: string) => void +): string { + let commonSourceDirectory; + if (options.rootDir) { + // If a rootDir is specified use it as the commonSourceDirectory + commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory); + checkSourceFilesBelongToPath?.(options.rootDir); } - - /*@internal*/ - export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): readonly string[] { - const { addOutput, getOutputs } = createAddOutput(); - if (outFile(configFile.options)) { - getSingleOutputFileNames(configFile, addOutput); - } - else { - const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(configFile, ignoreCase)); - for (const inputFileName of configFile.fileNames) { - getOwnOutputFileNames(configFile, inputFileName, ignoreCase, addOutput, getCommonSourceDirectory); - } - addOutput(getTsBuildInfoEmitOutputFilePath(configFile.options)); - } - return getOutputs(); + else if (options.composite && options.configFilePath) { + // Project compilations never infer their root from the input source paths + commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath)); + checkSourceFilesBelongToPath?.(commonSourceDirectory); + } + else { + commonSourceDirectory = computeCommonSourceDirectoryOfFilenames(emittedFiles(), currentDirectory, getCanonicalFileName); } - export function getOutputFileNames(commandLine: ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[] { - inputFileName = normalizePath(inputFileName); - Debug.assert(contains(commandLine.fileNames, inputFileName), `Expected fileName to be present in command line`); - const { addOutput, getOutputs } = createAddOutput(); - if (outFile(commandLine.options)) { - getSingleOutputFileNames(commandLine, addOutput); - } - else { - getOwnOutputFileNames(commandLine, inputFileName, ignoreCase, addOutput); - } - return getOutputs(); + if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) { + // Make sure directory path ends with directory separator so this string can directly + // used to replace with "" to get the relative path of the source file and the relative path doesn't + // start with / making it rooted path + commonSourceDirectory += directorySeparator; } + return commonSourceDirectory; +} - /*@internal*/ - export function getFirstProjectOutput(configFile: ParsedCommandLine, ignoreCase: boolean): string { - if (outFile(configFile.options)) { - const { jsFilePath, declarationFilePath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); - return Debug.checkDefined(jsFilePath || declarationFilePath, `project ${configFile.options.configFilePath} expected to have at least one output`); - } +/** @internal */ +export function getCommonSourceDirectoryOfConfig({ options, fileNames }: ParsedCommandLine, ignoreCase: boolean): string { + return getCommonSourceDirectory( + options, + () => filter(fileNames, file => !(options.noEmitForJsFiles && fileExtensionIsOneOf(file, supportedJSExtensionsFlat)) && !isDeclarationFileName(file)), + getDirectoryPath(normalizeSlashes(Debug.checkDefined(options.configFilePath))), + createGetCanonicalFileName(!ignoreCase) + ); +} +/** @internal */ +export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): readonly string[] { + const { addOutput, getOutputs } = createAddOutput(); + if (outFile(configFile.options)) { + getSingleOutputFileNames(configFile, addOutput); + } + else { const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(configFile, ignoreCase)); for (const inputFileName of configFile.fileNames) { - if (isDeclarationFileName(inputFileName)) continue; - const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); - if (jsFilePath) return jsFilePath; - if (fileExtensionIs(inputFileName, Extension.Json)) continue; - if (getEmitDeclarations(configFile.options)) { - return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); - } + getOwnOutputFileNames(configFile, inputFileName, ignoreCase, addOutput, getCommonSourceDirectory); } - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(configFile.options); - if (buildInfoPath) return buildInfoPath; - return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`); - } - - /*@internal*/ - // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature - export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, { scriptTransformers, declarationTransformers }: EmitTransformers, emitOnly?: boolean | EmitOnly, onlyBuildInfo?: boolean, forceDtsEmit?: boolean): EmitResult { - const compilerOptions = host.getCompilerOptions(); - const sourceMapDataList: SourceMapEmitResult[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined; - const emittedFilesList: string[] | undefined = compilerOptions.listEmittedFiles ? [] : undefined; - const emitterDiagnostics = createDiagnosticCollection(); - const newLine = getNewLineCharacter(compilerOptions, () => host.getNewLine()); - const writer = createTextWriter(newLine); - const { enter, exit } = performance.createTimer("printTime", "beforePrint", "afterPrint"); - let bundleBuildInfo: BundleBuildInfo | undefined; - let emitSkipped = false; - - // Emit each output file - enter(); - forEachEmittedFile( - host, - emitSourceFileOrBundle, - getSourceFilesToEmit(host, targetSourceFile, forceDtsEmit), - forceDtsEmit, - onlyBuildInfo, - !targetSourceFile - ); - exit(); - + addOutput(getTsBuildInfoEmitOutputFilePath(configFile.options)); + } + return getOutputs(); +} - return { - emitSkipped, - diagnostics: emitterDiagnostics.getDiagnostics(), - emittedFiles: emittedFilesList, - sourceMaps: sourceMapDataList, - }; +export function getOutputFileNames(commandLine: ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[] { + inputFileName = normalizePath(inputFileName); + Debug.assert(contains(commandLine.fileNames, inputFileName), `Expected fileName to be present in command line`); + const { addOutput, getOutputs } = createAddOutput(); + if (outFile(commandLine.options)) { + getSingleOutputFileNames(commandLine, addOutput); + } + else { + getOwnOutputFileNames(commandLine, inputFileName, ignoreCase, addOutput); + } + return getOutputs(); +} - 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: relativeToBuildInfo(host.getCommonSourceDirectory()), - sourceFiles: sourceFileOrBundle.sourceFiles.map(file => relativeToBuildInfo(getNormalizedAbsolutePath(file.fileName, host.getCurrentDirectory()))) - }; - } - tracing?.push(tracing.Phase.Emit, "emitJsFileOrBundle", { jsFilePath }); - emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath, relativeToBuildInfo); - tracing?.pop(); - - tracing?.push(tracing.Phase.Emit, "emitDeclarationFileOrBundle", { declarationFilePath }); - emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath, relativeToBuildInfo); - tracing?.pop(); - - tracing?.push(tracing.Phase.Emit, "emitBuildInfo", { buildInfoPath }); - emitBuildInfo(bundleBuildInfo, buildInfoPath); - tracing?.pop(); - - if (!emitSkipped && emittedFilesList) { - if (!emitOnly) { - if (jsFilePath) { - emittedFilesList.push(jsFilePath); - } - if (sourceMapFilePath) { - emittedFilesList.push(sourceMapFilePath); - } - if (buildInfoPath) { - emittedFilesList.push(buildInfoPath); - } - } - if (emitOnly !== EmitOnly.Js) { - if (declarationFilePath) { - emittedFilesList.push(declarationFilePath); - } - if (declarationMapPath) { - emittedFilesList.push(declarationMapPath); - } - } - } +/** @internal */ +export function getFirstProjectOutput(configFile: ParsedCommandLine, ignoreCase: boolean): string { + if (outFile(configFile.options)) { + const { jsFilePath, declarationFilePath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false); + return Debug.checkDefined(jsFilePath || declarationFilePath, `project ${configFile.options.configFilePath} expected to have at least one output`); + } - function relativeToBuildInfo(path: string) { - return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory!, path, host.getCanonicalFileName)); - } + const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(configFile, ignoreCase)); + for (const inputFileName of configFile.fileNames) { + if (isDeclarationFileName(inputFileName)) continue; + const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); + if (jsFilePath) return jsFilePath; + if (fileExtensionIs(inputFileName, Extension.Json)) continue; + if (getEmitDeclarations(configFile.options)) { + return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory); } + } + const buildInfoPath = getTsBuildInfoEmitOutputFilePath(configFile.options); + if (buildInfoPath) return buildInfoPath; + return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`); +} - function emitBuildInfo(bundle: BundleBuildInfo | undefined, buildInfoPath: string | undefined) { - // Write build information if applicable - if (!buildInfoPath || targetSourceFile || emitSkipped) return; - if (host.isEmitBlocked(buildInfoPath)) { - emitSkipped = true; - return; - } - const buildInfo = host.getBuildInfo(bundle) || createBuildInfo(/*program*/ undefined, bundle); - // Pass buildinfo as additional data to avoid having to reparse - writeFile(host, emitterDiagnostics, buildInfoPath, getBuildInfoText(buildInfo), /*writeByteOrderMark*/ false, /*sourceFiles*/ undefined, { buildInfo }); - } - - function emitJsFileOrBundle( - sourceFileOrBundle: SourceFile | Bundle | undefined, - jsFilePath: string | undefined, - sourceMapFilePath: string | undefined, - relativeToBuildInfo: (path: string) => string) { - if (!sourceFileOrBundle || emitOnly || !jsFilePath) { - return; - } - - // Make sure not to write js file and source map file if any of them cannot be written - if (host.isEmitBlocked(jsFilePath) || compilerOptions.noEmit) { - emitSkipped = true; - return; - } - // Transform the source files - const transform = transformNodes(resolver, host, factory, compilerOptions, [sourceFileOrBundle], scriptTransformers, /*allowDtsFiles*/ false); - - const printerOptions: PrinterOptions = { - removeComments: compilerOptions.removeComments, - newLine: compilerOptions.newLine, - noEmitHelpers: compilerOptions.noEmitHelpers, - module: compilerOptions.module, - target: compilerOptions.target, - sourceMap: compilerOptions.sourceMap, - inlineSourceMap: compilerOptions.inlineSourceMap, - inlineSources: compilerOptions.inlineSources, - extendedDiagnostics: compilerOptions.extendedDiagnostics, - writeBundleFileInfo: !!bundleBuildInfo, - relativeToBuildInfo - }; - - // Create a printer to print the nodes - const printer = createPrinter(printerOptions, { - // resolver hooks - hasGlobalName: resolver.hasGlobalName, - - // transform hooks - onEmitNode: transform.emitNodeWithNotification, - isEmitNotificationEnabled: transform.isEmitNotificationEnabled, - substituteNode: transform.substituteNode, - }); - - Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform"); - printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform, printer, compilerOptions); - - // Clean up emit nodes on parse tree - transform.dispose(); - if (bundleBuildInfo) bundleBuildInfo.js = printer.bundleFileInfo; - } - - function emitDeclarationFileOrBundle( - sourceFileOrBundle: SourceFile | Bundle | undefined, - declarationFilePath: string | undefined, - declarationMapPath: string | undefined, - relativeToBuildInfo: (path: string) => string) { - if (!sourceFileOrBundle || emitOnly === EmitOnly.Js) return; - if (!declarationFilePath) { - if (emitOnly || compilerOptions.emitDeclarationOnly) emitSkipped = true; - return; - } - const sourceFiles = isSourceFile(sourceFileOrBundle) ? [sourceFileOrBundle] : sourceFileOrBundle.sourceFiles; - const filesForEmit = forceDtsEmit ? sourceFiles : filter(sourceFiles, isSourceFileNotJson); - // Setup and perform the transformation to retrieve declarations from the input files - const inputListOrBundle = outFile(compilerOptions) ? [factory.createBundle(filesForEmit, !isSourceFile(sourceFileOrBundle) ? sourceFileOrBundle.prepends : undefined)] : filesForEmit; - if (emitOnly && !getEmitDeclarations(compilerOptions)) { - // Checker wont collect the linked aliases since thats only done when declaration is enabled. - // Do that here when emitting only dts files - filesForEmit.forEach(collectLinkedAliases); - } - const declarationTransform = transformNodes(resolver, host, factory, compilerOptions, inputListOrBundle, declarationTransformers, /*allowDtsFiles*/ false); - if (length(declarationTransform.diagnostics)) { - for (const diagnostic of declarationTransform.diagnostics!) { - emitterDiagnostics.add(diagnostic); - } - } +/** @internal */ +// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature +export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, { scriptTransformers, declarationTransformers }: EmitTransformers, emitOnly?: boolean | EmitOnly, onlyBuildInfo?: boolean, forceDtsEmit?: boolean): EmitResult { + const compilerOptions = host.getCompilerOptions(); + const sourceMapDataList: SourceMapEmitResult[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined; + const emittedFilesList: string[] | undefined = compilerOptions.listEmittedFiles ? [] : undefined; + const emitterDiagnostics = createDiagnosticCollection(); + const newLine = getNewLineCharacter(compilerOptions, () => host.getNewLine()); + const writer = createTextWriter(newLine); + const { enter, exit } = performance.createTimer("printTime", "beforePrint", "afterPrint"); + let bundleBuildInfo: BundleBuildInfo | undefined; + let emitSkipped = false; + + // Emit each output file + enter(); + forEachEmittedFile( + host, + emitSourceFileOrBundle, + getSourceFilesToEmit(host, targetSourceFile, forceDtsEmit), + forceDtsEmit, + onlyBuildInfo, + !targetSourceFile + ); + exit(); + + + return { + emitSkipped, + diagnostics: emitterDiagnostics.getDiagnostics(), + emittedFiles: emittedFilesList, + sourceMaps: sourceMapDataList, + }; - const printerOptions: PrinterOptions = { - removeComments: compilerOptions.removeComments, - newLine: compilerOptions.newLine, - noEmitHelpers: true, - module: compilerOptions.module, - target: compilerOptions.target, - sourceMap: !forceDtsEmit && compilerOptions.declarationMap, - inlineSourceMap: compilerOptions.inlineSourceMap, - extendedDiagnostics: compilerOptions.extendedDiagnostics, - onlyPrintJsDocStyle: true, - writeBundleFileInfo: !!bundleBuildInfo, - recordInternalSection: !!bundleBuildInfo, - relativeToBuildInfo + 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: relativeToBuildInfo(host.getCommonSourceDirectory()), + sourceFiles: sourceFileOrBundle.sourceFiles.map(file => relativeToBuildInfo(getNormalizedAbsolutePath(file.fileName, host.getCurrentDirectory()))) }; - - const declarationPrinter = createPrinter(printerOptions, { - // resolver hooks - hasGlobalName: resolver.hasGlobalName, - - // transform hooks - onEmitNode: declarationTransform.emitNodeWithNotification, - isEmitNotificationEnabled: declarationTransform.isEmitNotificationEnabled, - substituteNode: declarationTransform.substituteNode, - }); - const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit; - emitSkipped = emitSkipped || declBlocked; - if (!declBlocked || forceDtsEmit) { - Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform"); - printSourceFileOrBundle( - declarationFilePath, - declarationMapPath, - declarationTransform, - declarationPrinter, - { - sourceMap: printerOptions.sourceMap, - sourceRoot: compilerOptions.sourceRoot, - mapRoot: compilerOptions.mapRoot, - extendedDiagnostics: compilerOptions.extendedDiagnostics, - // Explicitly do not passthru either `inline` option - } - ); - } - declarationTransform.dispose(); - if (bundleBuildInfo) bundleBuildInfo.dts = declarationPrinter.bundleFileInfo; } + tracing?.push(tracing.Phase.Emit, "emitJsFileOrBundle", { jsFilePath }); + emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath, relativeToBuildInfo); + tracing?.pop(); - function collectLinkedAliases(node: Node) { - if (isExportAssignment(node)) { - if (node.expression.kind === SyntaxKind.Identifier) { - resolver.collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true); - } - return; - } - else if (isExportSpecifier(node)) { - resolver.collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); - return; - } - forEachChild(node, collectLinkedAliases); - } - - function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, transform: TransformationResult, printer: Printer, mapOptions: SourceMapOptions) { - const sourceFileOrBundle = transform.transformed[0]; - const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined; - const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined; - const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile!]; - - let sourceMapGenerator: SourceMapGenerator | undefined; - if (shouldEmitSourceMaps(mapOptions, sourceFileOrBundle)) { - sourceMapGenerator = createSourceMapGenerator( - host, - getBaseFileName(normalizeSlashes(jsFilePath)), - getSourceRoot(mapOptions), - getSourceMapDirectory(mapOptions, jsFilePath, sourceFile), - mapOptions); - } - - if (bundle) { - printer.writeBundle(bundle, writer, sourceMapGenerator); - } - else { - printer.writeFile(sourceFile!, writer, sourceMapGenerator); - } + tracing?.push(tracing.Phase.Emit, "emitDeclarationFileOrBundle", { declarationFilePath }); + emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath, relativeToBuildInfo); + tracing?.pop(); - let sourceMapUrlPos; - if (sourceMapGenerator) { - if (sourceMapDataList) { - sourceMapDataList.push({ - inputSourceFileNames: sourceMapGenerator.getSources(), - sourceMap: sourceMapGenerator.toJSON() - }); - } + tracing?.push(tracing.Phase.Emit, "emitBuildInfo", { buildInfoPath }); + emitBuildInfo(bundleBuildInfo, buildInfoPath); + tracing?.pop(); - const sourceMappingURL = getSourceMappingURL( - mapOptions, - sourceMapGenerator, - jsFilePath, - sourceMapFilePath, - sourceFile); - - if (sourceMappingURL) { - if (!writer.isAtStartOfLine()) writer.rawWrite(newLine); - sourceMapUrlPos = writer.getTextPos(); - writer.writeComment(`//# ${"sourceMappingURL"}=${sourceMappingURL}`); // Tools can sometimes see this line as a source mapping url comment + if (!emitSkipped && emittedFilesList) { + if (!emitOnly) { + if (jsFilePath) { + emittedFilesList.push(jsFilePath); } - - // Write the source map if (sourceMapFilePath) { - const sourceMap = sourceMapGenerator.toString(); - writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap, /*writeByteOrderMark*/ false, sourceFiles); - if (printer.bundleFileInfo) printer.bundleFileInfo.mapHash = computeSignature(sourceMap, maybeBind(host, host.createHash)); + emittedFilesList.push(sourceMapFilePath); + } + if (buildInfoPath) { + emittedFilesList.push(buildInfoPath); } } - else { - writer.writeLine(); + if (emitOnly !== EmitOnly.Js) { + if (declarationFilePath) { + emittedFilesList.push(declarationFilePath); + } + if (declarationMapPath) { + emittedFilesList.push(declarationMapPath); + } } - - // Write the output file - const text = writer.getText(); - writeFile(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, { sourceMapUrlPos, diagnostics: transform.diagnostics }); - // We store the hash of the text written in the buildinfo to ensure that text of the referenced d.ts file is same as whats in the buildinfo - // This is needed because incremental can be toggled between two runs and we might use stale file text to do text manipulation in prepend mode - if (printer.bundleFileInfo) printer.bundleFileInfo.hash = computeSignature(text, maybeBind(host, host.createHash)); - - // Reset state - writer.clear(); - } - - interface SourceMapOptions { - sourceMap?: boolean; - inlineSourceMap?: boolean; - inlineSources?: boolean; - sourceRoot?: string; - mapRoot?: string; - extendedDiagnostics?: boolean; } - function shouldEmitSourceMaps(mapOptions: SourceMapOptions, sourceFileOrBundle: SourceFile | Bundle) { - return (mapOptions.sourceMap || mapOptions.inlineSourceMap) - && (sourceFileOrBundle.kind !== SyntaxKind.SourceFile || !fileExtensionIs(sourceFileOrBundle.fileName, Extension.Json)); - } - - function getSourceRoot(mapOptions: SourceMapOptions) { - // Normalize source root and make sure it has trailing "/" so that it can be used to combine paths with the - // relative paths of the sources list in the sourcemap - const sourceRoot = normalizeSlashes(mapOptions.sourceRoot || ""); - return sourceRoot ? ensureTrailingDirectorySeparator(sourceRoot) : sourceRoot; + function relativeToBuildInfo(path: string) { + return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory!, path, host.getCanonicalFileName)); } + } - function getSourceMapDirectory(mapOptions: SourceMapOptions, filePath: string, sourceFile: SourceFile | undefined) { - if (mapOptions.sourceRoot) return host.getCommonSourceDirectory(); - if (mapOptions.mapRoot) { - let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); - if (sourceFile) { - // For modules or multiple emit files the mapRoot will have directory structure like the sources - // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map - sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); - } - if (getRootLength(sourceMapDir) === 0) { - // The relative paths are relative to the common directory - sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); - } - return sourceMapDir; - } - return getDirectoryPath(normalizePath(filePath)); + function emitBuildInfo(bundle: BundleBuildInfo | undefined, buildInfoPath: string | undefined) { + // Write build information if applicable + if (!buildInfoPath || targetSourceFile || emitSkipped) return; + if (host.isEmitBlocked(buildInfoPath)) { + emitSkipped = true; + return; } + const buildInfo = host.getBuildInfo(bundle) || createBuildInfo(/*program*/ undefined, bundle); + // Pass buildinfo as additional data to avoid having to reparse + writeFile(host, emitterDiagnostics, buildInfoPath, getBuildInfoText(buildInfo), /*writeByteOrderMark*/ false, /*sourceFiles*/ undefined, { buildInfo }); + } - function getSourceMappingURL(mapOptions: SourceMapOptions, sourceMapGenerator: SourceMapGenerator, filePath: string, sourceMapFilePath: string | undefined, sourceFile: SourceFile | undefined) { - if (mapOptions.inlineSourceMap) { - // Encode the sourceMap into the sourceMap url - const sourceMapText = sourceMapGenerator.toString(); - const base64SourceMapText = base64encode(sys, sourceMapText); - return `data:application/json;base64,${base64SourceMapText}`; - } + function emitJsFileOrBundle( + sourceFileOrBundle: SourceFile | Bundle | undefined, + jsFilePath: string | undefined, + sourceMapFilePath: string | undefined, + relativeToBuildInfo: (path: string) => string) { + if (!sourceFileOrBundle || emitOnly || !jsFilePath) { + return; + } + + // Make sure not to write js file and source map file if any of them cannot be written + if (host.isEmitBlocked(jsFilePath) || compilerOptions.noEmit) { + emitSkipped = true; + return; + } + // Transform the source files + const transform = transformNodes(resolver, host, factory, compilerOptions, [sourceFileOrBundle], scriptTransformers, /*allowDtsFiles*/ false); + + const printerOptions: PrinterOptions = { + removeComments: compilerOptions.removeComments, + newLine: compilerOptions.newLine, + noEmitHelpers: compilerOptions.noEmitHelpers, + module: compilerOptions.module, + target: compilerOptions.target, + sourceMap: compilerOptions.sourceMap, + inlineSourceMap: compilerOptions.inlineSourceMap, + inlineSources: compilerOptions.inlineSources, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + writeBundleFileInfo: !!bundleBuildInfo, + relativeToBuildInfo + }; - const sourceMapFile = getBaseFileName(normalizeSlashes(Debug.checkDefined(sourceMapFilePath))); - if (mapOptions.mapRoot) { - let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); - if (sourceFile) { - // For modules or multiple emit files the mapRoot will have directory structure like the sources - // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map - sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); - } - if (getRootLength(sourceMapDir) === 0) { - // The relative paths are relative to the common directory - sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); - return encodeURI( - getRelativePathToDirectoryOrUrl( - getDirectoryPath(normalizePath(filePath)), // get the relative sourceMapDir path based on jsFilePath - combinePaths(sourceMapDir, sourceMapFile), // this is where user expects to see sourceMap - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ true)); - } - else { - return encodeURI(combinePaths(sourceMapDir, sourceMapFile)); - } - } - return encodeURI(sourceMapFile); - } - } - - /*@internal*/ - export function createBuildInfo(program: ProgramBuildInfo | undefined, bundle: BundleBuildInfo | undefined): BuildInfo { - const version = ts.version; // Extracted into a const so the form is stable between namespace and module - return { bundle, program, version }; - } - - /*@internal*/ - export function getBuildInfoText(buildInfo: BuildInfo) { - return JSON.stringify(buildInfo); - } - - /*@internal*/ - export function getBuildInfo(buildInfoFile: string, buildInfoText: string) { - return readJsonOrUndefined(buildInfoFile, buildInfoText) as BuildInfo | undefined; - } - - /*@internal*/ - export const notImplementedResolver: EmitResolver = { - hasGlobalName: notImplemented, - getReferencedExportContainer: notImplemented, - getReferencedImportDeclaration: notImplemented, - getReferencedDeclarationWithCollidingName: notImplemented, - isDeclarationWithCollidingName: notImplemented, - isValueAliasDeclaration: notImplemented, - isReferencedAliasDeclaration: notImplemented, - isTopLevelValueImportEqualsWithEntityName: notImplemented, - getNodeCheckFlags: notImplemented, - isDeclarationVisible: notImplemented, - isLateBound: (_node): _node is LateBoundDeclaration => false, - collectLinkedAliases: notImplemented, - isImplementationOfOverload: notImplemented, - isRequiredInitializedParameter: notImplemented, - isOptionalUninitializedParameterProperty: notImplemented, - isExpandoFunctionDeclaration: notImplemented, - getPropertiesOfContainerFunction: notImplemented, - createTypeOfDeclaration: notImplemented, - createReturnTypeOfSignatureDeclaration: notImplemented, - createTypeOfExpression: notImplemented, - createLiteralConstValue: notImplemented, - isSymbolAccessible: notImplemented, - isEntityNameVisible: notImplemented, - // Returns the constant value this property access resolves to: notImplemented, or 'undefined' for a non-constant - getConstantValue: notImplemented, - getReferencedValueDeclaration: notImplemented, - getTypeReferenceSerializationKind: notImplemented, - isOptionalParameter: notImplemented, - moduleExportsSomeValue: notImplemented, - isArgumentsLocalBinding: notImplemented, - getExternalModuleFileFromDeclaration: notImplemented, - getTypeReferenceDirectivesForEntityName: notImplemented, - getTypeReferenceDirectivesForSymbol: notImplemented, - isLiteralConstDeclaration: notImplemented, - getJsxFactoryEntity: notImplemented, - getJsxFragmentFactoryEntity: notImplemented, - getAllAccessorDeclarations: notImplemented, - getSymbolOfExternalModuleSpecifier: notImplemented, - isBindingCapturedByNode: notImplemented, - getDeclarationStatementsForSourceFile: notImplemented, - isImportRequiredByAugmentation: notImplemented, - }; + // Create a printer to print the nodes + const printer = createPrinter(printerOptions, { + // resolver hooks + hasGlobalName: resolver.hasGlobalName, - /*@internal*/ - /** File that isnt present resulting in error or output files */ - export type EmitUsingBuildInfoResult = string | readonly OutputFile[]; - - function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo, buildInfoDirectory: string, host: CompilerHost): readonly SourceFile[] { - const jsBundle = Debug.checkDefined(bundle.js); - const prologueMap = jsBundle.sources?.prologues && arrayToMap(jsBundle.sources.prologues, prologueInfo => prologueInfo.file); - return bundle.sourceFiles.map((fileName, index) => { - const prologueInfo = prologueMap?.get(index); - const statements = prologueInfo?.directives.map(directive => { - const literal = setTextRange(factory.createStringLiteral(directive.expression.text), directive.expression); - const statement = setTextRange(factory.createExpressionStatement(literal), directive); - setParent(literal, statement); - return statement; - }); - const eofToken = factory.createToken(SyntaxKind.EndOfFileToken); - const sourceFile = factory.createSourceFile(statements ?? [], eofToken, NodeFlags.None); - sourceFile.fileName = getRelativePathFromDirectory( - host.getCurrentDirectory(), - getNormalizedAbsolutePath(fileName, buildInfoDirectory), - !host.useCaseSensitiveFileNames() - ); - sourceFile.text = prologueInfo?.text ?? ""; - setTextRangePosWidth(sourceFile, 0, prologueInfo?.text.length ?? 0); - setEachParent(sourceFile.statements, sourceFile); - setTextRangePosWidth(eofToken, sourceFile.end, 0); - setParent(eofToken, sourceFile); - return sourceFile; + // transform hooks + onEmitNode: transform.emitNodeWithNotification, + isEmitNotificationEnabled: transform.isEmitNotificationEnabled, + substituteNode: transform.substituteNode, }); - } - /*@internal*/ - export function emitUsingBuildInfo( - config: ParsedCommandLine, - host: CompilerHost, - getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined, - customTransformers?: CustomTransformers - ): EmitUsingBuildInfoResult { - tracing?.push(tracing.Phase.Emit, "emitUsingBuildInfo", {}, /*separateBeginAndEnd*/ true); - performance.mark("beforeEmit"); - const result = emitUsingBuildInfoWorker(config, host, getCommandLine, customTransformers); - performance.mark("afterEmit"); - performance.measure("Emit", "beforeEmit", "afterEmit"); - tracing?.pop(); - return result; + Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform"); + printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform, printer, compilerOptions); + + // Clean up emit nodes on parse tree + transform.dispose(); + if (bundleBuildInfo) bundleBuildInfo.js = printer.bundleFileInfo; } - function emitUsingBuildInfoWorker( - config: ParsedCommandLine, - host: CompilerHost, - getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined, - customTransformers?: CustomTransformers - ): EmitUsingBuildInfoResult { - const createHash = maybeBind(host, host.createHash); - const { buildInfoPath, jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(config.options, /*forceDtsPaths*/ false); - // If host directly provides buildinfo we can get it directly. This allows host to cache the buildinfo - const buildInfo = host.getBuildInfo!(buildInfoPath!, config.options.configFilePath); - if (!buildInfo) return buildInfoPath!; - if (!buildInfo.bundle || !buildInfo.bundle.js || (declarationFilePath && !buildInfo.bundle.dts)) return buildInfoPath!; - - const jsFileText = host.readFile(Debug.checkDefined(jsFilePath)); - if (!jsFileText) return jsFilePath!; - // If the jsFileText is not same has what it was created with, tsbuildinfo is stale so dont use it - if (computeSignature(jsFileText, createHash) !== buildInfo.bundle.js.hash) return jsFilePath!; - const sourceMapText = sourceMapFilePath && host.readFile(sourceMapFilePath); - // error if no source map or for now if inline sourcemap - if ((sourceMapFilePath && !sourceMapText) || config.options.inlineSourceMap) return sourceMapFilePath || "inline sourcemap decoding"; - if (sourceMapFilePath && computeSignature(sourceMapText!, createHash) !== buildInfo.bundle.js.mapHash) return sourceMapFilePath; - - // read declaration text - const declarationText = declarationFilePath && host.readFile(declarationFilePath); - if (declarationFilePath && !declarationText) return declarationFilePath; - if (declarationFilePath && computeSignature(declarationText!, createHash) !== buildInfo.bundle.dts!.hash) return declarationFilePath; - const declarationMapText = declarationMapPath && host.readFile(declarationMapPath); - // error if no source map or for now if inline sourcemap - if ((declarationMapPath && !declarationMapText) || config.options.inlineSourceMap) return declarationMapPath || "inline sourcemap decoding"; - if (declarationMapPath && computeSignature(declarationMapText!, createHash) !== buildInfo.bundle.dts!.mapHash) return declarationMapPath; - - const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath!, host.getCurrentDirectory())); - const ownPrependInput = createInputFilesWithFileTexts( - jsFilePath, - jsFileText, - sourceMapFilePath, - sourceMapText, - declarationFilePath, - declarationText!, - declarationMapPath, - declarationMapText, - buildInfoPath, - buildInfo, - /*onlyOwnText*/ true - ); - const outputFiles: OutputFile[] = []; - const prependNodes = createPrependNodes(config.projectReferences, getCommandLine, f => host.readFile(f), host); - const sourceFilesForJsEmit = createSourceFilesFromBundleBuildInfo(buildInfo.bundle, buildInfoDirectory, host); - let changedDtsText: string | undefined; - let changedDtsData: WriteFileCallbackData | undefined; - const emitHost: EmitHost = { - getPrependNodes: memoize(() => [...prependNodes, ownPrependInput]), - getCanonicalFileName: host.getCanonicalFileName, - getCommonSourceDirectory: () => getNormalizedAbsolutePath(buildInfo.bundle!.commonSourceDirectory, buildInfoDirectory), - getCompilerOptions: () => config.options, - getCurrentDirectory: () => host.getCurrentDirectory(), - getNewLine: () => host.getNewLine(), - getSourceFile: returnUndefined, - getSourceFileByPath: returnUndefined, - getSourceFiles: () => sourceFilesForJsEmit, - getLibFileFromReference: notImplemented, - isSourceFileFromExternalLibrary: returnFalse, - getResolvedProjectReferenceToRedirect: returnUndefined, - getProjectReferenceRedirect: returnUndefined, - isSourceOfProjectReferenceRedirect: returnFalse, - writeFile: (name, text, writeByteOrderMark, _onError, _sourceFiles, data) => { - switch (name) { - case jsFilePath: - if (jsFileText === text) return; - break; - case sourceMapFilePath: - if (sourceMapText === text) return; - break; - case buildInfoPath: - break; - case declarationFilePath: - if (declarationText === text) return; - changedDtsText = text; - changedDtsData = data; - break; - case declarationMapPath: - if (declarationMapText === text) return; - break; - default: - Debug.fail(`Unexpected path: ${name}`); - } - outputFiles.push({ name, text, writeByteOrderMark, data }); - }, - isEmitBlocked: returnFalse, - readFile: f => host.readFile(f), - fileExists: f => host.fileExists(f), - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), - getBuildInfo: bundle => { - const program = buildInfo.program; - if (program && changedDtsText !== undefined && config.options.composite) { - // Update the output signature - (program as ProgramBundleEmitBuildInfo).outSignature = computeSignature(changedDtsText, createHash, changedDtsData); - } - // Update sourceFileInfo - const { js, dts, sourceFiles } = buildInfo.bundle!; - bundle!.js!.sources = js!.sources; - if (dts) { - bundle!.dts!.sources = dts.sources; - } - bundle!.sourceFiles = sourceFiles; - return createBuildInfo(program, bundle); - }, - getSourceFileFromReference: returnUndefined, - redirectTargetsMap: createMultiMap(), - getFileIncludeReasons: notImplemented, - createHash, + function emitDeclarationFileOrBundle( + sourceFileOrBundle: SourceFile | Bundle | undefined, + declarationFilePath: string | undefined, + declarationMapPath: string | undefined, + relativeToBuildInfo: (path: string) => string) { + if (!sourceFileOrBundle || emitOnly === EmitOnly.Js) return; + if (!declarationFilePath) { + if (emitOnly || compilerOptions.emitDeclarationOnly) emitSkipped = true; + return; + } + const sourceFiles = isSourceFile(sourceFileOrBundle) ? [sourceFileOrBundle] : sourceFileOrBundle.sourceFiles; + const filesForEmit = forceDtsEmit ? sourceFiles : filter(sourceFiles, isSourceFileNotJson); + // Setup and perform the transformation to retrieve declarations from the input files + const inputListOrBundle = outFile(compilerOptions) ? [factory.createBundle(filesForEmit, !isSourceFile(sourceFileOrBundle) ? sourceFileOrBundle.prepends : undefined)] : filesForEmit; + if (emitOnly && !getEmitDeclarations(compilerOptions)) { + // Checker wont collect the linked aliases since thats only done when declaration is enabled. + // Do that here when emitting only dts files + filesForEmit.forEach(collectLinkedAliases); + } + const declarationTransform = transformNodes(resolver, host, factory, compilerOptions, inputListOrBundle, declarationTransformers, /*allowDtsFiles*/ false); + if (length(declarationTransform.diagnostics)) { + for (const diagnostic of declarationTransform.diagnostics!) { + emitterDiagnostics.add(diagnostic); + } + } + + const printerOptions: PrinterOptions = { + removeComments: compilerOptions.removeComments, + newLine: compilerOptions.newLine, + noEmitHelpers: true, + module: compilerOptions.module, + target: compilerOptions.target, + sourceMap: !forceDtsEmit && compilerOptions.declarationMap, + inlineSourceMap: compilerOptions.inlineSourceMap, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + onlyPrintJsDocStyle: true, + writeBundleFileInfo: !!bundleBuildInfo, + recordInternalSection: !!bundleBuildInfo, + relativeToBuildInfo }; - emitFiles( - notImplementedResolver, - emitHost, - /*targetSourceFile*/ undefined, - getTransformers(config.options, customTransformers) - ); - return outputFiles; - } - - const enum PipelinePhase { - Notification, - Substitution, - Comments, - SourceMaps, - Emit, - } - - export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer { - const { - hasGlobalName, - onEmitNode = noEmitNotification, - isEmitNotificationEnabled, - substituteNode = noEmitSubstitution, - onBeforeEmitNode, - onAfterEmitNode, - onBeforeEmitNodeArray, - onAfterEmitNodeArray, - onBeforeEmitToken, - onAfterEmitToken - } = handlers; - - const extendedDiagnostics = !!printerOptions.extendedDiagnostics; - const newLine = getNewLineCharacter(printerOptions); - const moduleKind = getEmitModuleKind(printerOptions); - const bundledHelpers = new Map(); - let currentSourceFile: SourceFile | undefined; - let nodeIdToGeneratedName: string[]; // Map of generated names for specific nodes. - let autoGeneratedIdToGeneratedName: string[]; // Map of generated names for temp and loop variables. - let generatedNames: Set; // Set of names generated by the NameGenerator. - let formattedNameTempFlagsStack: (ESMap | undefined)[]; - let formattedNameTempFlags: ESMap | undefined; - let privateNameTempFlagsStack: TempFlags[]; // Stack of enclosing name generation scopes. - let privateNameTempFlags: TempFlags; // TempFlags for the current name generation scope. - let tempFlagsStack: TempFlags[]; // Stack of enclosing name generation scopes. - let tempFlags: TempFlags; // TempFlags for the current name generation scope. - let reservedNamesStack: Set[]; // Stack of TempFlags reserved in enclosing name generation scopes. - let reservedNames: Set; // TempFlags to reserve in nested name generation scopes. - let preserveSourceNewlines = printerOptions.preserveSourceNewlines; // Can be overridden inside nodes with the `IgnoreSourceNewlines` emit flag. - let nextListElementPos: number | undefined; // See comment in `getLeadingLineTerminatorCount`. - - let writer: EmitTextWriter; - let ownWriter: EmitTextWriter; // Reusable `EmitTextWriter` for basic printing. - let write = writeBase; - let isOwnFileEmit: boolean; - const bundleFileInfo = printerOptions.writeBundleFileInfo ? { sections: [] } as BundleFileInfo : undefined; - const relativeToBuildInfo = bundleFileInfo ? Debug.checkDefined(printerOptions.relativeToBuildInfo) : undefined; - const recordInternalSection = printerOptions.recordInternalSection; - let sourceFileTextPos = 0; - let sourceFileTextKind: BundleFileTextLikeKind = BundleFileSectionKind.Text; - - // Source Maps - let sourceMapsDisabled = true; - let sourceMapGenerator: SourceMapGenerator | undefined; - let sourceMapSource: SourceMapSource; - let sourceMapSourceIndex = -1; - let mostRecentlyAddedSourceMapSource: SourceMapSource; - let mostRecentlyAddedSourceMapSourceIndex = -1; - - // Comments - let containerPos = -1; - let containerEnd = -1; - let declarationListContainerEnd = -1; - let currentLineMap: readonly number[] | undefined; - let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number }[] | undefined; - let hasWrittenComment = false; - let commentsDisabled = !!printerOptions.removeComments; - let lastSubstitution: Node | undefined; - let currentParenthesizerRule: ((node: Node) => Node) | undefined; - const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment"); - const parenthesizer = factory.parenthesizer; - const typeArgumentParenthesizerRuleSelector: OrdinalParentheizerRuleSelector = { - select: index => index === 0 ? parenthesizer.parenthesizeLeadingTypeArgument : undefined - }; - const emitBinaryExpression = createEmitBinaryExpression(); + const declarationPrinter = createPrinter(printerOptions, { + // resolver hooks + hasGlobalName: resolver.hasGlobalName, - reset(); - return { - // public API - printNode, - printList, - printFile, - printBundle, - - // internal API - writeNode, - writeList, - writeFile, - writeBundle, - bundleFileInfo - }; + // transform hooks + onEmitNode: declarationTransform.emitNodeWithNotification, + isEmitNotificationEnabled: declarationTransform.isEmitNotificationEnabled, + substituteNode: declarationTransform.substituteNode, + }); + const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit; + emitSkipped = emitSkipped || declBlocked; + if (!declBlocked || forceDtsEmit) { + Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform"); + printSourceFileOrBundle( + declarationFilePath, + declarationMapPath, + declarationTransform, + declarationPrinter, + { + sourceMap: printerOptions.sourceMap, + sourceRoot: compilerOptions.sourceRoot, + mapRoot: compilerOptions.mapRoot, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + // Explicitly do not passthru either `inline` option + } + ); + } + declarationTransform.dispose(); + if (bundleBuildInfo) bundleBuildInfo.dts = declarationPrinter.bundleFileInfo; + } - function printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string { - switch (hint) { - case EmitHint.SourceFile: - Debug.assert(isSourceFile(node), "Expected a SourceFile node."); - break; - case EmitHint.IdentifierName: - Debug.assert(isIdentifier(node), "Expected an Identifier node."); - break; - case EmitHint.Expression: - Debug.assert(isExpression(node), "Expected an Expression node."); - break; - } - switch (node.kind) { - case SyntaxKind.SourceFile: return printFile(node as SourceFile); - case SyntaxKind.Bundle: return printBundle(node as Bundle); - case SyntaxKind.UnparsedSource: return printUnparsedSource(node as UnparsedSource); + function collectLinkedAliases(node: Node) { + if (isExportAssignment(node)) { + if (node.expression.kind === SyntaxKind.Identifier) { + resolver.collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true); } - writeNode(hint, node, sourceFile, beginPrint()); - return endPrint(); + return; } - - function printList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile) { - writeList(format, nodes, sourceFile, beginPrint()); - return endPrint(); + else if (isExportSpecifier(node)) { + resolver.collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); + return; } + forEachChild(node, collectLinkedAliases); + } - function printBundle(bundle: Bundle): string { - writeBundle(bundle, beginPrint(), /*sourceMapEmitter*/ undefined); - return endPrint(); - } + function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, transform: TransformationResult, printer: Printer, mapOptions: SourceMapOptions) { + const sourceFileOrBundle = transform.transformed[0]; + const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined; + const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined; + const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile!]; - function printFile(sourceFile: SourceFile): string { - writeFile(sourceFile, beginPrint(), /*sourceMapEmitter*/ undefined); - return endPrint(); + let sourceMapGenerator: SourceMapGenerator | undefined; + if (shouldEmitSourceMaps(mapOptions, sourceFileOrBundle)) { + sourceMapGenerator = createSourceMapGenerator( + host, + getBaseFileName(normalizeSlashes(jsFilePath)), + getSourceRoot(mapOptions), + getSourceMapDirectory(mapOptions, jsFilePath, sourceFile), + mapOptions); } - function printUnparsedSource(unparsed: UnparsedSource): string { - writeUnparsedSource(unparsed, beginPrint()); - return endPrint(); + if (bundle) { + printer.writeBundle(bundle, writer, sourceMapGenerator); } - - /** - * If `sourceFile` is `undefined`, `node` must be a synthesized `TypeNode`. - */ - function writeNode(hint: EmitHint, node: TypeNode, sourceFile: undefined, output: EmitTextWriter): void; - function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile, output: EmitTextWriter): void; - function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined, output: EmitTextWriter) { - const previousWriter = writer; - setWriter(output, /*_sourceMapGenerator*/ undefined); - print(hint, node, sourceFile); - reset(); - writer = previousWriter; + else { + printer.writeFile(sourceFile!, writer, sourceMapGenerator); } - function writeList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile | undefined, output: EmitTextWriter) { - const previousWriter = writer; - setWriter(output, /*_sourceMapGenerator*/ undefined); - if (sourceFile) { - setSourceFile(sourceFile); + let sourceMapUrlPos; + if (sourceMapGenerator) { + if (sourceMapDataList) { + sourceMapDataList.push({ + inputSourceFileNames: sourceMapGenerator.getSources(), + sourceMap: sourceMapGenerator.toJSON() + }); } - emitList(/*parentNode*/ undefined, nodes, format); - reset(); - writer = previousWriter; - } - - function getTextPosWithWriteLine() { - return writer.getTextPosWithWriteLine ? writer.getTextPosWithWriteLine() : writer.getTextPos(); - } - function updateOrPushBundleFileTextLike(pos: number, end: number, kind: BundleFileTextLikeKind) { - const last = lastOrUndefined(bundleFileInfo!.sections); - if (last && last.kind === kind) { - last.end = end; - } - else { - bundleFileInfo!.sections.push({ pos, end, kind }); - } - } + const sourceMappingURL = getSourceMappingURL( + mapOptions, + sourceMapGenerator, + jsFilePath, + sourceMapFilePath, + sourceFile); - function recordBundleFileInternalSectionStart(node: Node) { - if (recordInternalSection && - bundleFileInfo && - currentSourceFile && - (isDeclaration(node) || isVariableStatement(node)) && - isInternalDeclaration(node, currentSourceFile) && - sourceFileTextKind !== BundleFileSectionKind.Internal) { - const prevSourceFileTextKind = sourceFileTextKind; - recordBundleFileTextLikeSection(writer.getTextPos()); - sourceFileTextPos = getTextPosWithWriteLine(); - sourceFileTextKind = BundleFileSectionKind.Internal; - return prevSourceFileTextKind; + if (sourceMappingURL) { + if (!writer.isAtStartOfLine()) writer.rawWrite(newLine); + sourceMapUrlPos = writer.getTextPos(); + writer.writeComment(`//# ${"sourceMappingURL"}=${sourceMappingURL}`); // Tools can sometimes see this line as a source mapping url comment } - return undefined; - } - function recordBundleFileInternalSectionEnd(prevSourceFileTextKind: ReturnType) { - if (prevSourceFileTextKind) { - recordBundleFileTextLikeSection(writer.getTextPos()); - sourceFileTextPos = getTextPosWithWriteLine(); - sourceFileTextKind = prevSourceFileTextKind; + // Write the source map + if (sourceMapFilePath) { + const sourceMap = sourceMapGenerator.toString(); + writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap, /*writeByteOrderMark*/ false, sourceFiles); + if (printer.bundleFileInfo) printer.bundleFileInfo.mapHash = computeSignature(sourceMap, maybeBind(host, host.createHash)); } } - - function recordBundleFileTextLikeSection(end: number) { - if (sourceFileTextPos < end) { - updateOrPushBundleFileTextLike(sourceFileTextPos, end, sourceFileTextKind); - return true; - } - return false; + else { + writer.writeLine(); } - function writeBundle(bundle: Bundle, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { - isOwnFileEmit = false; - const previousWriter = writer; - setWriter(output, sourceMapGenerator); - emitShebangIfNeeded(bundle); - emitPrologueDirectivesIfNeeded(bundle); - emitHelpers(bundle); - emitSyntheticTripleSlashReferencesIfNeeded(bundle); - - for (const prepend of bundle.prepends) { - writeLine(); - const pos = writer.getTextPos(); - const savedSections = bundleFileInfo && bundleFileInfo.sections; - if (savedSections) bundleFileInfo.sections = []; - print(EmitHint.Unspecified, prepend, /*sourceFile*/ undefined); - if (bundleFileInfo) { - const newSections = bundleFileInfo.sections; - bundleFileInfo.sections = savedSections!; - 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: relativeToBuildInfo!((prepend as UnparsedSource).fileName), - texts: newSections as BundleFileTextLike[] - }); - } - } - } - - sourceFileTextPos = getTextPosWithWriteLine(); - for (const sourceFile of bundle.sourceFiles) { - print(EmitHint.SourceFile, sourceFile, sourceFile); - } - if (bundleFileInfo && bundle.sourceFiles.length) { - const end = writer.getTextPos(); - if (recordBundleFileTextLikeSection(end)) { - // Store prologues - const prologues = getPrologueDirectivesFromBundledSourceFiles(bundle); - if (prologues) { - if (!bundleFileInfo.sources) bundleFileInfo.sources = {}; - bundleFileInfo.sources.prologues = prologues; - } - - // Store helpes - const helpers = getHelpersFromBundledSourceFiles(bundle); - if (helpers) { - if (!bundleFileInfo.sources) bundleFileInfo.sources = {}; - bundleFileInfo.sources.helpers = helpers; - } - } - } - - reset(); - writer = previousWriter; - } + // Write the output file + const text = writer.getText(); + writeFile(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, { sourceMapUrlPos, diagnostics: transform.diagnostics }); + // We store the hash of the text written in the buildinfo to ensure that text of the referenced d.ts file is same as whats in the buildinfo + // This is needed because incremental can be toggled between two runs and we might use stale file text to do text manipulation in prepend mode + if (printer.bundleFileInfo) printer.bundleFileInfo.hash = computeSignature(text, maybeBind(host, host.createHash)); - function writeUnparsedSource(unparsed: UnparsedSource, output: EmitTextWriter) { - const previousWriter = writer; - setWriter(output, /*_sourceMapGenerator*/ undefined); - print(EmitHint.Unspecified, unparsed, /*sourceFile*/ undefined); - reset(); - writer = previousWriter; - } + // Reset state + writer.clear(); + } - function writeFile(sourceFile: SourceFile, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { - isOwnFileEmit = true; - const previousWriter = writer; - setWriter(output, sourceMapGenerator); - emitShebangIfNeeded(sourceFile); - emitPrologueDirectivesIfNeeded(sourceFile); - print(EmitHint.SourceFile, sourceFile, sourceFile); - reset(); - writer = previousWriter; - } + interface SourceMapOptions { + sourceMap?: boolean; + inlineSourceMap?: boolean; + inlineSources?: boolean; + sourceRoot?: string; + mapRoot?: string; + extendedDiagnostics?: boolean; + } - function beginPrint() { - return ownWriter || (ownWriter = createTextWriter(newLine)); - } + function shouldEmitSourceMaps(mapOptions: SourceMapOptions, sourceFileOrBundle: SourceFile | Bundle) { + return (mapOptions.sourceMap || mapOptions.inlineSourceMap) + && (sourceFileOrBundle.kind !== SyntaxKind.SourceFile || !fileExtensionIs(sourceFileOrBundle.fileName, Extension.Json)); + } - function endPrint() { - const text = ownWriter.getText(); - ownWriter.clear(); - return text; - } + function getSourceRoot(mapOptions: SourceMapOptions) { + // Normalize source root and make sure it has trailing "/" so that it can be used to combine paths with the + // relative paths of the sources list in the sourcemap + const sourceRoot = normalizeSlashes(mapOptions.sourceRoot || ""); + return sourceRoot ? ensureTrailingDirectorySeparator(sourceRoot) : sourceRoot; + } - function print(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined) { + function getSourceMapDirectory(mapOptions: SourceMapOptions, filePath: string, sourceFile: SourceFile | undefined) { + if (mapOptions.sourceRoot) return host.getCommonSourceDirectory(); + if (mapOptions.mapRoot) { + let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); if (sourceFile) { - setSourceFile(sourceFile); + // For modules or multiple emit files the mapRoot will have directory structure like the sources + // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map + sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); } + if (getRootLength(sourceMapDir) === 0) { + // The relative paths are relative to the common directory + sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); + } + return sourceMapDir; + } + return getDirectoryPath(normalizePath(filePath)); + } - pipelineEmit(hint, node, /*parenthesizerRule*/ undefined); + function getSourceMappingURL(mapOptions: SourceMapOptions, sourceMapGenerator: SourceMapGenerator, filePath: string, sourceMapFilePath: string | undefined, sourceFile: SourceFile | undefined) { + if (mapOptions.inlineSourceMap) { + // Encode the sourceMap into the sourceMap url + const sourceMapText = sourceMapGenerator.toString(); + const base64SourceMapText = base64encode(sys, sourceMapText); + return `data:application/json;base64,${base64SourceMapText}`; } - function setSourceFile(sourceFile: SourceFile | undefined) { - currentSourceFile = sourceFile; - currentLineMap = undefined; - detachedCommentsInfo = undefined; + const sourceMapFile = getBaseFileName(normalizeSlashes(Debug.checkDefined(sourceMapFilePath))); + if (mapOptions.mapRoot) { + let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); if (sourceFile) { - setSourceMapSource(sourceFile); + // For modules or multiple emit files the mapRoot will have directory structure like the sources + // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map + sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); + } + if (getRootLength(sourceMapDir) === 0) { + // The relative paths are relative to the common directory + sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); + return encodeURI( + getRelativePathToDirectoryOrUrl( + getDirectoryPath(normalizePath(filePath)), // get the relative sourceMapDir path based on jsFilePath + combinePaths(sourceMapDir, sourceMapFile), // this is where user expects to see sourceMap + host.getCurrentDirectory(), + host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ true)); } - } - - function setWriter(_writer: EmitTextWriter | undefined, _sourceMapGenerator: SourceMapGenerator | undefined) { - if (_writer && printerOptions.omitTrailingSemicolon) { - _writer = getTrailingSemicolonDeferringWriter(_writer); + else { + return encodeURI(combinePaths(sourceMapDir, sourceMapFile)); } - - writer = _writer!; // TODO: GH#18217 - sourceMapGenerator = _sourceMapGenerator; - sourceMapsDisabled = !writer || !sourceMapGenerator; - } - - function reset() { - nodeIdToGeneratedName = []; - autoGeneratedIdToGeneratedName = []; - generatedNames = new Set(); - formattedNameTempFlagsStack = []; - formattedNameTempFlags = new Map(); - privateNameTempFlagsStack = []; - privateNameTempFlags = TempFlags.Auto; - tempFlagsStack = []; - tempFlags = TempFlags.Auto; - reservedNamesStack = []; - currentSourceFile = undefined; - currentLineMap = undefined; - detachedCommentsInfo = undefined; - setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined); } + return encodeURI(sourceMapFile); + } +} - function getCurrentLineMap() { - return currentLineMap || (currentLineMap = getLineStarts(Debug.checkDefined(currentSourceFile))); - } +/** @internal */ +export function createBuildInfo(program: ProgramBuildInfo | undefined, bundle: BundleBuildInfo | undefined): BuildInfo { + const version = ts.version; // Extracted into a const so the form is stable between namespace and module + return { bundle, program, version }; +} - function emit(node: Node, parenthesizerRule?: (node: Node) => Node): void; - function emit(node: Node | undefined, parenthesizerRule?: (node: Node) => Node): void; - function emit(node: Node | undefined, parenthesizerRule?: (node: Node) => Node) { - if (node === undefined) return; - const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node); - pipelineEmit(EmitHint.Unspecified, node, parenthesizerRule); - recordBundleFileInternalSectionEnd(prevSourceFileTextKind); - } +/** @internal */ +export function getBuildInfoText(buildInfo: BuildInfo) { + return JSON.stringify(buildInfo); +} - function emitIdentifierName(node: Identifier): void; - function emitIdentifierName(node: Identifier | undefined): void; - function emitIdentifierName(node: Identifier | undefined) { - if (node === undefined) return; - pipelineEmit(EmitHint.IdentifierName, node, /*parenthesizerRule*/ undefined); - } +/** @internal */ +export function getBuildInfo(buildInfoFile: string, buildInfoText: string) { + return readJsonOrUndefined(buildInfoFile, buildInfoText) as BuildInfo | undefined; +} - function emitExpression(node: Expression, parenthesizerRule?: (node: Expression) => Expression): void; - function emitExpression(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression): void; - function emitExpression(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression) { - if (node === undefined) return; - pipelineEmit(EmitHint.Expression, node, parenthesizerRule); - } +/** @internal */ +export const notImplementedResolver: EmitResolver = { + hasGlobalName: notImplemented, + getReferencedExportContainer: notImplemented, + getReferencedImportDeclaration: notImplemented, + getReferencedDeclarationWithCollidingName: notImplemented, + isDeclarationWithCollidingName: notImplemented, + isValueAliasDeclaration: notImplemented, + isReferencedAliasDeclaration: notImplemented, + isTopLevelValueImportEqualsWithEntityName: notImplemented, + getNodeCheckFlags: notImplemented, + isDeclarationVisible: notImplemented, + isLateBound: (_node): _node is LateBoundDeclaration => false, + collectLinkedAliases: notImplemented, + isImplementationOfOverload: notImplemented, + isRequiredInitializedParameter: notImplemented, + isOptionalUninitializedParameterProperty: notImplemented, + isExpandoFunctionDeclaration: notImplemented, + getPropertiesOfContainerFunction: notImplemented, + createTypeOfDeclaration: notImplemented, + createReturnTypeOfSignatureDeclaration: notImplemented, + createTypeOfExpression: notImplemented, + createLiteralConstValue: notImplemented, + isSymbolAccessible: notImplemented, + isEntityNameVisible: notImplemented, + // Returns the constant value this property access resolves to: notImplemented, or 'undefined' for a non-constant + getConstantValue: notImplemented, + getReferencedValueDeclaration: notImplemented, + getTypeReferenceSerializationKind: notImplemented, + isOptionalParameter: notImplemented, + moduleExportsSomeValue: notImplemented, + isArgumentsLocalBinding: notImplemented, + getExternalModuleFileFromDeclaration: notImplemented, + getTypeReferenceDirectivesForEntityName: notImplemented, + getTypeReferenceDirectivesForSymbol: notImplemented, + isLiteralConstDeclaration: notImplemented, + getJsxFactoryEntity: notImplemented, + getJsxFragmentFactoryEntity: notImplemented, + getAllAccessorDeclarations: notImplemented, + getSymbolOfExternalModuleSpecifier: notImplemented, + isBindingCapturedByNode: notImplemented, + getDeclarationStatementsForSourceFile: notImplemented, + isImportRequiredByAugmentation: notImplemented, +}; + +/** + * File that isnt present resulting in error or output files + * + * @internal + */ +export type EmitUsingBuildInfoResult = string | readonly OutputFile[]; + +function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo, buildInfoDirectory: string, host: CompilerHost): readonly SourceFile[] { + const jsBundle = Debug.checkDefined(bundle.js); + const prologueMap = jsBundle.sources?.prologues && arrayToMap(jsBundle.sources.prologues, prologueInfo => prologueInfo.file); + return bundle.sourceFiles.map((fileName, index) => { + const prologueInfo = prologueMap?.get(index); + const statements = prologueInfo?.directives.map(directive => { + const literal = setTextRange(factory.createStringLiteral(directive.expression.text), directive.expression); + const statement = setTextRange(factory.createExpressionStatement(literal), directive); + setParent(literal, statement); + return statement; + }); + const eofToken = factory.createToken(SyntaxKind.EndOfFileToken); + const sourceFile = factory.createSourceFile(statements ?? [], eofToken, NodeFlags.None); + sourceFile.fileName = getRelativePathFromDirectory( + host.getCurrentDirectory(), + getNormalizedAbsolutePath(fileName, buildInfoDirectory), + !host.useCaseSensitiveFileNames() + ); + sourceFile.text = prologueInfo?.text ?? ""; + setTextRangePosWidth(sourceFile, 0, prologueInfo?.text.length ?? 0); + setEachParent(sourceFile.statements, sourceFile); + setTextRangePosWidth(eofToken, sourceFile.end, 0); + setParent(eofToken, sourceFile); + return sourceFile; + }); +} - function emitJsxAttributeValue(node: StringLiteral | JsxExpression): void { - pipelineEmit(isStringLiteral(node) ? EmitHint.JsxAttributeValue : EmitHint.Unspecified, node); - } +/** @internal */ +export function emitUsingBuildInfo( + config: ParsedCommandLine, + host: CompilerHost, + getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined, + customTransformers?: CustomTransformers +): EmitUsingBuildInfoResult { + tracing?.push(tracing.Phase.Emit, "emitUsingBuildInfo", {}, /*separateBeginAndEnd*/ true); + ts.performance.mark("beforeEmit"); + const result = emitUsingBuildInfoWorker(config, host, getCommandLine, customTransformers); + ts.performance.mark("afterEmit"); + ts.performance.measure("Emit", "beforeEmit", "afterEmit"); + tracing?.pop(); + return result; +} - function beforeEmitNode(node: Node) { - if (preserveSourceNewlines && (getEmitFlags(node) & EmitFlags.IgnoreSourceNewlines)) { - preserveSourceNewlines = false; - } - } +function emitUsingBuildInfoWorker( + config: ParsedCommandLine, + host: CompilerHost, + getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined, + customTransformers?: CustomTransformers +): EmitUsingBuildInfoResult { + const createHash = maybeBind(host, host.createHash); + const { buildInfoPath, jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(config.options, /*forceDtsPaths*/ false); + // If host directly provides buildinfo we can get it directly. This allows host to cache the buildinfo + const buildInfo = host.getBuildInfo!(buildInfoPath!, config.options.configFilePath); + if (!buildInfo) return buildInfoPath!; + if (!buildInfo.bundle || !buildInfo.bundle.js || (declarationFilePath && !buildInfo.bundle.dts)) return buildInfoPath!; + + const jsFileText = host.readFile(Debug.checkDefined(jsFilePath)); + if (!jsFileText) return jsFilePath!; + // If the jsFileText is not same has what it was created with, tsbuildinfo is stale so dont use it + if (computeSignature(jsFileText, createHash) !== buildInfo.bundle.js.hash) return jsFilePath!; + const sourceMapText = sourceMapFilePath && host.readFile(sourceMapFilePath); + // error if no source map or for now if inline sourcemap + if ((sourceMapFilePath && !sourceMapText) || config.options.inlineSourceMap) return sourceMapFilePath || "inline sourcemap decoding"; + if (sourceMapFilePath && computeSignature(sourceMapText!, createHash) !== buildInfo.bundle.js.mapHash) return sourceMapFilePath; + + // read declaration text + const declarationText = declarationFilePath && host.readFile(declarationFilePath); + if (declarationFilePath && !declarationText) return declarationFilePath; + if (declarationFilePath && computeSignature(declarationText!, createHash) !== buildInfo.bundle.dts!.hash) return declarationFilePath; + const declarationMapText = declarationMapPath && host.readFile(declarationMapPath); + // error if no source map or for now if inline sourcemap + if ((declarationMapPath && !declarationMapText) || config.options.inlineSourceMap) return declarationMapPath || "inline sourcemap decoding"; + if (declarationMapPath && computeSignature(declarationMapText!, createHash) !== buildInfo.bundle.dts!.mapHash) return declarationMapPath; + + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath!, host.getCurrentDirectory())); + const ownPrependInput = createInputFilesWithFileTexts( + jsFilePath, + jsFileText, + sourceMapFilePath, + sourceMapText, + declarationFilePath, + declarationText!, + declarationMapPath, + declarationMapText, + buildInfoPath, + buildInfo, + /*onlyOwnText*/ true + ); + const outputFiles: OutputFile[] = []; + const prependNodes = createPrependNodes(config.projectReferences, getCommandLine, f => host.readFile(f), host); + const sourceFilesForJsEmit = createSourceFilesFromBundleBuildInfo(buildInfo.bundle, buildInfoDirectory, host); + let changedDtsText: string | undefined; + let changedDtsData: WriteFileCallbackData | undefined; + const emitHost: EmitHost = { + getPrependNodes: memoize(() => [...prependNodes, ownPrependInput]), + getCanonicalFileName: host.getCanonicalFileName, + getCommonSourceDirectory: () => getNormalizedAbsolutePath(buildInfo.bundle!.commonSourceDirectory, buildInfoDirectory), + getCompilerOptions: () => config.options, + getCurrentDirectory: () => host.getCurrentDirectory(), + getNewLine: () => host.getNewLine(), + getSourceFile: returnUndefined, + getSourceFileByPath: returnUndefined, + getSourceFiles: () => sourceFilesForJsEmit, + getLibFileFromReference: notImplemented, + isSourceFileFromExternalLibrary: returnFalse, + getResolvedProjectReferenceToRedirect: returnUndefined, + getProjectReferenceRedirect: returnUndefined, + isSourceOfProjectReferenceRedirect: returnFalse, + writeFile: (name, text, writeByteOrderMark, _onError, _sourceFiles, data) => { + switch (name) { + case jsFilePath: + if (jsFileText === text) return; + break; + case sourceMapFilePath: + if (sourceMapText === text) return; + break; + case buildInfoPath: + break; + case declarationFilePath: + if (declarationText === text) return; + changedDtsText = text; + changedDtsData = data; + break; + case declarationMapPath: + if (declarationMapText === text) return; + break; + default: + Debug.fail(`Unexpected path: ${name}`); + } + outputFiles.push({ name, text, writeByteOrderMark, data }); + }, + isEmitBlocked: returnFalse, + readFile: f => host.readFile(f), + fileExists: f => host.fileExists(f), + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), + getBuildInfo: bundle => { + const program = buildInfo.program; + if (program && changedDtsText !== undefined && config.options.composite) { + // Update the output signature + (program as ProgramBundleEmitBuildInfo).outSignature = computeSignature(changedDtsText, createHash, changedDtsData); + } + // Update sourceFileInfo + const { js, dts, sourceFiles } = buildInfo.bundle!; + bundle!.js!.sources = js!.sources; + if (dts) { + bundle!.dts!.sources = dts.sources; + } + bundle!.sourceFiles = sourceFiles; + return createBuildInfo(program, bundle); + }, + getSourceFileFromReference: returnUndefined, + redirectTargetsMap: createMultiMap(), + getFileIncludeReasons: notImplemented, + createHash, + }; + emitFiles( + notImplementedResolver, + emitHost, + /*targetSourceFile*/ undefined, + getTransformers(config.options, customTransformers) + ); + return outputFiles; +} - function afterEmitNode(savedPreserveSourceNewlines: boolean | undefined) { - preserveSourceNewlines = savedPreserveSourceNewlines; - } +const enum PipelinePhase { + Notification, + Substitution, + Comments, + SourceMaps, + Emit, +} - function pipelineEmit(emitHint: EmitHint, node: Node, parenthesizerRule?: (node: Node) => Node) { - currentParenthesizerRule = parenthesizerRule; - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, emitHint, node); - pipelinePhase(emitHint, node); - currentParenthesizerRule = undefined; - } +export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer { + const { + hasGlobalName, + onEmitNode = noEmitNotification, + isEmitNotificationEnabled, + substituteNode = noEmitSubstitution, + onBeforeEmitNode, + onAfterEmitNode, + onBeforeEmitNodeArray, + onAfterEmitNodeArray, + onBeforeEmitToken, + onAfterEmitToken + } = handlers; + + const extendedDiagnostics = !!printerOptions.extendedDiagnostics; + const newLine = getNewLineCharacter(printerOptions); + const moduleKind = getEmitModuleKind(printerOptions); + const bundledHelpers = new Map(); + + let currentSourceFile: SourceFile | undefined; + let nodeIdToGeneratedName: string[]; // Map of generated names for specific nodes. + let autoGeneratedIdToGeneratedName: string[]; // Map of generated names for temp and loop variables. + let generatedNames: Set; // Set of names generated by the NameGenerator. + let formattedNameTempFlagsStack: (ESMap | undefined)[]; + let formattedNameTempFlags: ESMap | undefined; + let privateNameTempFlagsStack: TempFlags[]; // Stack of enclosing name generation scopes. + let privateNameTempFlags: TempFlags; // TempFlags for the current name generation scope. + let tempFlagsStack: TempFlags[]; // Stack of enclosing name generation scopes. + let tempFlags: TempFlags; // TempFlags for the current name generation scope. + let reservedNamesStack: Set[]; // Stack of TempFlags reserved in enclosing name generation scopes. + let reservedNames: Set; // TempFlags to reserve in nested name generation scopes. + let preserveSourceNewlines = printerOptions.preserveSourceNewlines; // Can be overridden inside nodes with the `IgnoreSourceNewlines` emit flag. + let nextListElementPos: number | undefined; // See comment in `getLeadingLineTerminatorCount`. + + let writer: EmitTextWriter; + let ownWriter: EmitTextWriter; // Reusable `EmitTextWriter` for basic printing. + let write = writeBase; + let isOwnFileEmit: boolean; + const bundleFileInfo = printerOptions.writeBundleFileInfo ? { sections: [] } as BundleFileInfo : undefined; + const relativeToBuildInfo = bundleFileInfo ? Debug.checkDefined(printerOptions.relativeToBuildInfo) : undefined; + const recordInternalSection = printerOptions.recordInternalSection; + let sourceFileTextPos = 0; + let sourceFileTextKind: BundleFileTextLikeKind = BundleFileSectionKind.Text; + + // Source Maps + let sourceMapsDisabled = true; + let sourceMapGenerator: SourceMapGenerator | undefined; + let sourceMapSource: SourceMapSource; + let sourceMapSourceIndex = -1; + let mostRecentlyAddedSourceMapSource: SourceMapSource; + let mostRecentlyAddedSourceMapSourceIndex = -1; + + // Comments + let containerPos = -1; + let containerEnd = -1; + let declarationListContainerEnd = -1; + let currentLineMap: readonly number[] | undefined; + let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number }[] | undefined; + let hasWrittenComment = false; + let commentsDisabled = !!printerOptions.removeComments; + let lastSubstitution: Node | undefined; + let currentParenthesizerRule: ((node: Node) => Node) | undefined; + const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment"); + const parenthesizer = factory.parenthesizer; + const typeArgumentParenthesizerRuleSelector: OrdinalParentheizerRuleSelector = { + select: index => index === 0 ? parenthesizer.parenthesizeLeadingTypeArgument : undefined + }; + const emitBinaryExpression = createEmitBinaryExpression(); + + reset(); + return { + // public API + printNode, + printList, + printFile, + printBundle, + + // internal API + writeNode, + writeList, + writeFile, + writeBundle, + bundleFileInfo + }; - function shouldEmitComments(node: Node) { - return !commentsDisabled && !isSourceFile(node); - } + function printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string { + switch (hint) { + case EmitHint.SourceFile: + Debug.assert(isSourceFile(node), "Expected a SourceFile node."); + break; + case EmitHint.IdentifierName: + Debug.assert(isIdentifier(node), "Expected an Identifier node."); + break; + case EmitHint.Expression: + Debug.assert(isExpression(node), "Expected an Expression node."); + break; + } + switch (node.kind) { + case SyntaxKind.SourceFile: return printFile(node as SourceFile); + case SyntaxKind.Bundle: return printBundle(node as Bundle); + case SyntaxKind.UnparsedSource: return printUnparsedSource(node as UnparsedSource); + } + writeNode(hint, node, sourceFile, beginPrint()); + return endPrint(); + } + + function printList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile) { + writeList(format, nodes, sourceFile, beginPrint()); + return endPrint(); + } + + function printBundle(bundle: Bundle): string { + writeBundle(bundle, beginPrint(), /*sourceMapEmitter*/ undefined); + return endPrint(); + } - function shouldEmitSourceMaps(node: Node) { - return !sourceMapsDisabled && - !isSourceFile(node) && - !isInJsonFile(node) && - !isUnparsedSource(node) && - !isUnparsedPrepend(node); + function printFile(sourceFile: SourceFile): string { + writeFile(sourceFile, beginPrint(), /*sourceMapEmitter*/ undefined); + return endPrint(); + } + + function printUnparsedSource(unparsed: UnparsedSource): string { + writeUnparsedSource(unparsed, beginPrint()); + return endPrint(); + } + + /** + * If `sourceFile` is `undefined`, `node` must be a synthesized `TypeNode`. + */ + function writeNode(hint: EmitHint, node: TypeNode, sourceFile: undefined, output: EmitTextWriter): void; + function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile, output: EmitTextWriter): void; + function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined, output: EmitTextWriter) { + const previousWriter = writer; + setWriter(output, /*_sourceMapGenerator*/ undefined); + print(hint, node, sourceFile); + reset(); + writer = previousWriter; + } + + function writeList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile | undefined, output: EmitTextWriter) { + const previousWriter = writer; + setWriter(output, /*_sourceMapGenerator*/ undefined); + if (sourceFile) { + setSourceFile(sourceFile); } + emitList(/*parentNode*/ undefined, nodes, format); + reset(); + writer = previousWriter; + } - function getPipelinePhase(phase: PipelinePhase, emitHint: EmitHint, node: Node) { - switch (phase) { - case PipelinePhase.Notification: - if (onEmitNode !== noEmitNotification && (!isEmitNotificationEnabled || isEmitNotificationEnabled(node))) { - return pipelineEmitWithNotification; - } - // falls through - case PipelinePhase.Substitution: - if (substituteNode !== noEmitSubstitution && (lastSubstitution = substituteNode(emitHint, node) || node) !== node) { - if (currentParenthesizerRule) { - lastSubstitution = currentParenthesizerRule(lastSubstitution); - } - return pipelineEmitWithSubstitution; - } - // falls through - case PipelinePhase.Comments: - if (shouldEmitComments(node)) { - return pipelineEmitWithComments; - } - // falls through - case PipelinePhase.SourceMaps: - if (shouldEmitSourceMaps(node)) { - return pipelineEmitWithSourceMaps; - } - // falls through - case PipelinePhase.Emit: - return pipelineEmitWithHint; - default: - return Debug.assertNever(phase); - } + function getTextPosWithWriteLine() { + return writer.getTextPosWithWriteLine ? writer.getTextPosWithWriteLine() : writer.getTextPos(); + } + + function updateOrPushBundleFileTextLike(pos: number, end: number, kind: BundleFileTextLikeKind) { + const last = lastOrUndefined(bundleFileInfo!.sections); + if (last && last.kind === kind) { + last.end = end; + } + else { + bundleFileInfo!.sections.push({ pos, end, kind }); } + } - function getNextPipelinePhase(currentPhase: PipelinePhase, emitHint: EmitHint, node: Node) { - return getPipelinePhase(currentPhase + 1, emitHint, node); + function recordBundleFileInternalSectionStart(node: Node) { + if (recordInternalSection && + bundleFileInfo && + currentSourceFile && + (isDeclaration(node) || isVariableStatement(node)) && + isInternalDeclaration(node, currentSourceFile) && + sourceFileTextKind !== BundleFileSectionKind.Internal) { + const prevSourceFileTextKind = sourceFileTextKind; + recordBundleFileTextLikeSection(writer.getTextPos()); + sourceFileTextPos = getTextPosWithWriteLine(); + sourceFileTextKind = BundleFileSectionKind.Internal; + return prevSourceFileTextKind; } + return undefined; + } - function pipelineEmitWithNotification(hint: EmitHint, node: Node) { - const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, hint, node); - onEmitNode(hint, node, pipelinePhase); + function recordBundleFileInternalSectionEnd(prevSourceFileTextKind: ReturnType) { + if (prevSourceFileTextKind) { + recordBundleFileTextLikeSection(writer.getTextPos()); + sourceFileTextPos = getTextPosWithWriteLine(); + sourceFileTextKind = prevSourceFileTextKind; } + } - function pipelineEmitWithHint(hint: EmitHint, node: Node): void { - onBeforeEmitNode?.(node); - if (preserveSourceNewlines) { - const savedPreserveSourceNewlines = preserveSourceNewlines; - beforeEmitNode(node); - pipelineEmitWithHintWorker(hint, node); - afterEmitNode(savedPreserveSourceNewlines); - } - else { - pipelineEmitWithHintWorker(hint, node); - } - onAfterEmitNode?.(node); - // clear the parenthesizer rule as we ascend - currentParenthesizerRule = undefined; + function recordBundleFileTextLikeSection(end: number) { + if (sourceFileTextPos < end) { + updateOrPushBundleFileTextLike(sourceFileTextPos, end, sourceFileTextKind); + return true; } + return false; + } - function pipelineEmitWithHintWorker(hint: EmitHint, node: Node, allowSnippets = true): void { - if (allowSnippets) { - const snippet = getSnippetElement(node); - if (snippet) { - return emitSnippetNode(hint, node, snippet); + function writeBundle(bundle: Bundle, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { + isOwnFileEmit = false; + const previousWriter = writer; + setWriter(output, sourceMapGenerator); + emitShebangIfNeeded(bundle); + emitPrologueDirectivesIfNeeded(bundle); + emitHelpers(bundle); + emitSyntheticTripleSlashReferencesIfNeeded(bundle); + + for (const prepend of bundle.prepends) { + writeLine(); + const pos = writer.getTextPos(); + const savedSections = bundleFileInfo && bundleFileInfo.sections; + if (savedSections) bundleFileInfo.sections = []; + print(EmitHint.Unspecified, prepend, /*sourceFile*/ undefined); + if (bundleFileInfo) { + const newSections = bundleFileInfo.sections; + bundleFileInfo.sections = savedSections!; + 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: relativeToBuildInfo!((prepend as UnparsedSource).fileName), + texts: newSections as BundleFileTextLike[] + }); } } - if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile)); - if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier)); - if (hint === EmitHint.JsxAttributeValue) return emitLiteral(cast(node, isStringLiteral), /*jsxAttributeEscape*/ true); - if (hint === EmitHint.MappedTypeParameter) return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration)); - if (hint === EmitHint.EmbeddedStatement) { - Debug.assertNode(node, isEmptyStatement); - return emitEmptyStatement(/*isEmbeddedStatement*/ true); - } - if (hint === EmitHint.Unspecified) { - switch (node.kind) { - // Pseudo-literals - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - return emitLiteral(node as LiteralExpression, /*jsxAttributeEscape*/ false); - - // Identifiers - case SyntaxKind.Identifier: - return emitIdentifier(node as Identifier); - - // PrivateIdentifiers - case SyntaxKind.PrivateIdentifier: - return emitPrivateIdentifier(node as PrivateIdentifier); - - // Parse tree nodes - // Names - case SyntaxKind.QualifiedName: - return emitQualifiedName(node as QualifiedName); - case SyntaxKind.ComputedPropertyName: - return emitComputedPropertyName(node as ComputedPropertyName); - - // Signature elements - case SyntaxKind.TypeParameter: - return emitTypeParameter(node as TypeParameterDeclaration); - case SyntaxKind.Parameter: - return emitParameter(node as ParameterDeclaration); - case SyntaxKind.Decorator: - return emitDecorator(node as Decorator); - - // Type members - case SyntaxKind.PropertySignature: - return emitPropertySignature(node as PropertySignature); - case SyntaxKind.PropertyDeclaration: - return emitPropertyDeclaration(node as PropertyDeclaration); - case SyntaxKind.MethodSignature: - return emitMethodSignature(node as MethodSignature); - case SyntaxKind.MethodDeclaration: - return emitMethodDeclaration(node as MethodDeclaration); - case SyntaxKind.ClassStaticBlockDeclaration: - return emitClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); - case SyntaxKind.Constructor: - return emitConstructor(node as ConstructorDeclaration); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return emitAccessorDeclaration(node as AccessorDeclaration); - case SyntaxKind.CallSignature: - return emitCallSignature(node as CallSignatureDeclaration); - case SyntaxKind.ConstructSignature: - return emitConstructSignature(node as ConstructSignatureDeclaration); - case SyntaxKind.IndexSignature: - return emitIndexSignature(node as IndexSignatureDeclaration); - - // Types - case SyntaxKind.TypePredicate: - return emitTypePredicate(node as TypePredicateNode); - case SyntaxKind.TypeReference: - return emitTypeReference(node as TypeReferenceNode); - case SyntaxKind.FunctionType: - return emitFunctionType(node as FunctionTypeNode); - case SyntaxKind.ConstructorType: - return emitConstructorType(node as ConstructorTypeNode); - case SyntaxKind.TypeQuery: - return emitTypeQuery(node as TypeQueryNode); - case SyntaxKind.TypeLiteral: - return emitTypeLiteral(node as TypeLiteralNode); - case SyntaxKind.ArrayType: - return emitArrayType(node as ArrayTypeNode); - case SyntaxKind.TupleType: - return emitTupleType(node as TupleTypeNode); - case SyntaxKind.OptionalType: - return emitOptionalType(node as OptionalTypeNode); - // SyntaxKind.RestType is handled below - case SyntaxKind.UnionType: - return emitUnionType(node as UnionTypeNode); - case SyntaxKind.IntersectionType: - return emitIntersectionType(node as IntersectionTypeNode); - case SyntaxKind.ConditionalType: - return emitConditionalType(node as ConditionalTypeNode); - case SyntaxKind.InferType: - return emitInferType(node as InferTypeNode); - case SyntaxKind.ParenthesizedType: - return emitParenthesizedType(node as ParenthesizedTypeNode); - case SyntaxKind.ExpressionWithTypeArguments: - return emitExpressionWithTypeArguments(node as ExpressionWithTypeArguments); - case SyntaxKind.ThisType: - return emitThisType(); - case SyntaxKind.TypeOperator: - return emitTypeOperator(node as TypeOperatorNode); - case SyntaxKind.IndexedAccessType: - return emitIndexedAccessType(node as IndexedAccessTypeNode); - case SyntaxKind.MappedType: - return emitMappedType(node as MappedTypeNode); - case SyntaxKind.LiteralType: - return emitLiteralType(node as LiteralTypeNode); - case SyntaxKind.NamedTupleMember: - return emitNamedTupleMember(node as NamedTupleMember); - case SyntaxKind.TemplateLiteralType: - return emitTemplateType(node as TemplateLiteralTypeNode); - case SyntaxKind.TemplateLiteralTypeSpan: - return emitTemplateTypeSpan(node as TemplateLiteralTypeSpan); - case SyntaxKind.ImportType: - return emitImportTypeNode(node as ImportTypeNode); - - // Binding patterns - case SyntaxKind.ObjectBindingPattern: - return emitObjectBindingPattern(node as ObjectBindingPattern); - case SyntaxKind.ArrayBindingPattern: - return emitArrayBindingPattern(node as ArrayBindingPattern); - case SyntaxKind.BindingElement: - return emitBindingElement(node as BindingElement); - - // Misc - case SyntaxKind.TemplateSpan: - return emitTemplateSpan(node as TemplateSpan); - case SyntaxKind.SemicolonClassElement: - return emitSemicolonClassElement(); - - // Statements - case SyntaxKind.Block: - return emitBlock(node as Block); - case SyntaxKind.VariableStatement: - return emitVariableStatement(node as VariableStatement); - case SyntaxKind.EmptyStatement: - return emitEmptyStatement(/*isEmbeddedStatement*/ false); - case SyntaxKind.ExpressionStatement: - return emitExpressionStatement(node as ExpressionStatement); - case SyntaxKind.IfStatement: - return emitIfStatement(node as IfStatement); - case SyntaxKind.DoStatement: - return emitDoStatement(node as DoStatement); - case SyntaxKind.WhileStatement: - return emitWhileStatement(node as WhileStatement); - case SyntaxKind.ForStatement: - return emitForStatement(node as ForStatement); - case SyntaxKind.ForInStatement: - return emitForInStatement(node as ForInStatement); - case SyntaxKind.ForOfStatement: - return emitForOfStatement(node as ForOfStatement); - case SyntaxKind.ContinueStatement: - return emitContinueStatement(node as ContinueStatement); - case SyntaxKind.BreakStatement: - return emitBreakStatement(node as BreakStatement); - case SyntaxKind.ReturnStatement: - return emitReturnStatement(node as ReturnStatement); - case SyntaxKind.WithStatement: - return emitWithStatement(node as WithStatement); - case SyntaxKind.SwitchStatement: - return emitSwitchStatement(node as SwitchStatement); - case SyntaxKind.LabeledStatement: - return emitLabeledStatement(node as LabeledStatement); - case SyntaxKind.ThrowStatement: - return emitThrowStatement(node as ThrowStatement); - case SyntaxKind.TryStatement: - return emitTryStatement(node as TryStatement); - case SyntaxKind.DebuggerStatement: - return emitDebuggerStatement(node as DebuggerStatement); - - // Declarations - case SyntaxKind.VariableDeclaration: - return emitVariableDeclaration(node as VariableDeclaration); - case SyntaxKind.VariableDeclarationList: - return emitVariableDeclarationList(node as VariableDeclarationList); - case SyntaxKind.FunctionDeclaration: - return emitFunctionDeclaration(node as FunctionDeclaration); - case SyntaxKind.ClassDeclaration: - return emitClassDeclaration(node as ClassDeclaration); - case SyntaxKind.InterfaceDeclaration: - return emitInterfaceDeclaration(node as InterfaceDeclaration); - case SyntaxKind.TypeAliasDeclaration: - return emitTypeAliasDeclaration(node as TypeAliasDeclaration); - case SyntaxKind.EnumDeclaration: - return emitEnumDeclaration(node as EnumDeclaration); - case SyntaxKind.ModuleDeclaration: - return emitModuleDeclaration(node as ModuleDeclaration); - case SyntaxKind.ModuleBlock: - return emitModuleBlock(node as ModuleBlock); - case SyntaxKind.CaseBlock: - return emitCaseBlock(node as CaseBlock); - case SyntaxKind.NamespaceExportDeclaration: - return emitNamespaceExportDeclaration(node as NamespaceExportDeclaration); - case SyntaxKind.ImportEqualsDeclaration: - return emitImportEqualsDeclaration(node as ImportEqualsDeclaration); - case SyntaxKind.ImportDeclaration: - return emitImportDeclaration(node as ImportDeclaration); - case SyntaxKind.ImportClause: - return emitImportClause(node as ImportClause); - case SyntaxKind.NamespaceImport: - return emitNamespaceImport(node as NamespaceImport); - case SyntaxKind.NamespaceExport: - return emitNamespaceExport(node as NamespaceExport); - case SyntaxKind.NamedImports: - return emitNamedImports(node as NamedImports); - case SyntaxKind.ImportSpecifier: - return emitImportSpecifier(node as ImportSpecifier); - case SyntaxKind.ExportAssignment: - return emitExportAssignment(node as ExportAssignment); - case SyntaxKind.ExportDeclaration: - return emitExportDeclaration(node as ExportDeclaration); - case SyntaxKind.NamedExports: - return emitNamedExports(node as NamedExports); - case SyntaxKind.ExportSpecifier: - return emitExportSpecifier(node as ExportSpecifier); - case SyntaxKind.AssertClause: - return emitAssertClause(node as AssertClause); - case SyntaxKind.AssertEntry: - return emitAssertEntry(node as AssertEntry); - case SyntaxKind.MissingDeclaration: - return; - - // Module references - case SyntaxKind.ExternalModuleReference: - return emitExternalModuleReference(node as ExternalModuleReference); - - // JSX (non-expression) - case SyntaxKind.JsxText: - return emitJsxText(node as JsxText); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxOpeningFragment: - return emitJsxOpeningElementOrFragment(node as JsxOpeningElement); - case SyntaxKind.JsxClosingElement: - case SyntaxKind.JsxClosingFragment: - return emitJsxClosingElementOrFragment(node as JsxClosingElement); - case SyntaxKind.JsxAttribute: - return emitJsxAttribute(node as JsxAttribute); - case SyntaxKind.JsxAttributes: - return emitJsxAttributes(node as JsxAttributes); - case SyntaxKind.JsxSpreadAttribute: - return emitJsxSpreadAttribute(node as JsxSpreadAttribute); - case SyntaxKind.JsxExpression: - return emitJsxExpression(node as JsxExpression); - - // Clauses - case SyntaxKind.CaseClause: - return emitCaseClause(node as CaseClause); - case SyntaxKind.DefaultClause: - return emitDefaultClause(node as DefaultClause); - case SyntaxKind.HeritageClause: - return emitHeritageClause(node as HeritageClause); - case SyntaxKind.CatchClause: - return emitCatchClause(node as CatchClause); - - // Property assignments - case SyntaxKind.PropertyAssignment: - return emitPropertyAssignment(node as PropertyAssignment); - case SyntaxKind.ShorthandPropertyAssignment: - return emitShorthandPropertyAssignment(node as ShorthandPropertyAssignment); - case SyntaxKind.SpreadAssignment: - return emitSpreadAssignment(node as SpreadAssignment); - - // Enum - case SyntaxKind.EnumMember: - return emitEnumMember(node as EnumMember); - - // Unparsed - case SyntaxKind.UnparsedPrologue: - return writeUnparsedNode(node as UnparsedNode); - case SyntaxKind.UnparsedSource: - case SyntaxKind.UnparsedPrepend: - return emitUnparsedSourceOrPrepend(node as UnparsedSource); - case SyntaxKind.UnparsedText: - case SyntaxKind.UnparsedInternalText: - return emitUnparsedTextLike(node as UnparsedTextLike); - case SyntaxKind.UnparsedSyntheticReference: - return emitUnparsedSyntheticReference(node as UnparsedSyntheticReference); - - // Top-level nodes - case SyntaxKind.SourceFile: - return emitSourceFile(node as SourceFile); - case SyntaxKind.Bundle: - return Debug.fail("Bundles should be printed using printBundle"); - // SyntaxKind.UnparsedSource (handled above) - case SyntaxKind.InputFiles: - return Debug.fail("InputFiles should not be printed"); - - // JSDoc nodes (only used in codefixes currently) - case SyntaxKind.JSDocTypeExpression: - return emitJSDocTypeExpression(node as JSDocTypeExpression); - case SyntaxKind.JSDocNameReference: - return emitJSDocNameReference(node as JSDocNameReference); - case SyntaxKind.JSDocAllType: - return writePunctuation("*"); - case SyntaxKind.JSDocUnknownType: - return writePunctuation("?"); - case SyntaxKind.JSDocNullableType: - return emitJSDocNullableType(node as JSDocNullableType); - case SyntaxKind.JSDocNonNullableType: - return emitJSDocNonNullableType(node as JSDocNonNullableType); - case SyntaxKind.JSDocOptionalType: - return emitJSDocOptionalType(node as JSDocOptionalType); - case SyntaxKind.JSDocFunctionType: - return emitJSDocFunctionType(node as JSDocFunctionType); - case SyntaxKind.RestType: - case SyntaxKind.JSDocVariadicType: - return emitRestOrJSDocVariadicType(node as RestTypeNode | JSDocVariadicType); - case SyntaxKind.JSDocNamepathType: - return; - case SyntaxKind.JSDoc: - return emitJSDoc(node as JSDoc); - case SyntaxKind.JSDocTypeLiteral: - return emitJSDocTypeLiteral(node as JSDocTypeLiteral); - case SyntaxKind.JSDocSignature: - return emitJSDocSignature(node as JSDocSignature); - case SyntaxKind.JSDocTag: - case SyntaxKind.JSDocClassTag: - case SyntaxKind.JSDocOverrideTag: - return emitJSDocSimpleTag(node as JSDocTag); - case SyntaxKind.JSDocAugmentsTag: - case SyntaxKind.JSDocImplementsTag: - return emitJSDocHeritageTag(node as JSDocImplementsTag | JSDocAugmentsTag); - case SyntaxKind.JSDocAuthorTag: - case SyntaxKind.JSDocDeprecatedTag: - return; - // SyntaxKind.JSDocClassTag (see JSDocTag, above) - case SyntaxKind.JSDocPublicTag: - case SyntaxKind.JSDocPrivateTag: - case SyntaxKind.JSDocProtectedTag: - case SyntaxKind.JSDocReadonlyTag: - return; - case SyntaxKind.JSDocCallbackTag: - return emitJSDocCallbackTag(node as JSDocCallbackTag); - // SyntaxKind.JSDocEnumTag (see below) - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - return emitJSDocPropertyLikeTag(node as JSDocPropertyLikeTag); - case SyntaxKind.JSDocEnumTag: - case SyntaxKind.JSDocReturnTag: - case SyntaxKind.JSDocThisTag: - case SyntaxKind.JSDocTypeTag: - return emitJSDocSimpleTypedTag(node as JSDocTypeTag); - case SyntaxKind.JSDocTemplateTag: - return emitJSDocTemplateTag(node as JSDocTemplateTag); - case SyntaxKind.JSDocTypedefTag: - return emitJSDocTypedefTag(node as JSDocTypedefTag); - case SyntaxKind.JSDocSeeTag: - return emitJSDocSeeTag(node as JSDocSeeTag); - // SyntaxKind.JSDocPropertyTag (see JSDocParameterTag, above) - - // Transformation nodes - case SyntaxKind.NotEmittedStatement: - case SyntaxKind.EndOfDeclarationMarker: - case SyntaxKind.MergeDeclarationMarker: - return; - } - if (isExpression(node)) { - hint = EmitHint.Expression; - if (substituteNode !== noEmitSubstitution) { - const substitute = substituteNode(hint, node) || node; - if (substitute !== node) { - node = substitute; - if (currentParenthesizerRule) { - node = currentParenthesizerRule(node); - } - } - } + } + + sourceFileTextPos = getTextPosWithWriteLine(); + for (const sourceFile of bundle.sourceFiles) { + print(EmitHint.SourceFile, sourceFile, sourceFile); + } + if (bundleFileInfo && bundle.sourceFiles.length) { + const end = writer.getTextPos(); + if (recordBundleFileTextLikeSection(end)) { + // Store prologues + const prologues = getPrologueDirectivesFromBundledSourceFiles(bundle); + if (prologues) { + if (!bundleFileInfo.sources) bundleFileInfo.sources = {}; + bundleFileInfo.sources.prologues = prologues; } - } - if (hint === EmitHint.Expression) { - switch (node.kind) { - // Literals - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - return emitNumericOrBigIntLiteral(node as NumericLiteral | BigIntLiteral); - - case SyntaxKind.StringLiteral: - case SyntaxKind.RegularExpressionLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return emitLiteral(node as LiteralExpression, /*jsxAttributeEscape*/ false); - - // Identifiers - case SyntaxKind.Identifier: - return emitIdentifier(node as Identifier); - case SyntaxKind.PrivateIdentifier: - return emitPrivateIdentifier(node as PrivateIdentifier); - - // Expressions - case SyntaxKind.ArrayLiteralExpression: - return emitArrayLiteralExpression(node as ArrayLiteralExpression); - case SyntaxKind.ObjectLiteralExpression: - return emitObjectLiteralExpression(node as ObjectLiteralExpression); - case SyntaxKind.PropertyAccessExpression: - return emitPropertyAccessExpression(node as PropertyAccessExpression); - case SyntaxKind.ElementAccessExpression: - return emitElementAccessExpression(node as ElementAccessExpression); - case SyntaxKind.CallExpression: - return emitCallExpression(node as CallExpression); - case SyntaxKind.NewExpression: - return emitNewExpression(node as NewExpression); - case SyntaxKind.TaggedTemplateExpression: - return emitTaggedTemplateExpression(node as TaggedTemplateExpression); - case SyntaxKind.TypeAssertionExpression: - return emitTypeAssertionExpression(node as TypeAssertion); - case SyntaxKind.ParenthesizedExpression: - return emitParenthesizedExpression(node as ParenthesizedExpression); - case SyntaxKind.FunctionExpression: - return emitFunctionExpression(node as FunctionExpression); - case SyntaxKind.ArrowFunction: - return emitArrowFunction(node as ArrowFunction); - case SyntaxKind.DeleteExpression: - return emitDeleteExpression(node as DeleteExpression); - case SyntaxKind.TypeOfExpression: - return emitTypeOfExpression(node as TypeOfExpression); - case SyntaxKind.VoidExpression: - return emitVoidExpression(node as VoidExpression); - case SyntaxKind.AwaitExpression: - return emitAwaitExpression(node as AwaitExpression); - case SyntaxKind.PrefixUnaryExpression: - return emitPrefixUnaryExpression(node as PrefixUnaryExpression); - case SyntaxKind.PostfixUnaryExpression: - return emitPostfixUnaryExpression(node as PostfixUnaryExpression); - case SyntaxKind.BinaryExpression: - return emitBinaryExpression(node as BinaryExpression); - case SyntaxKind.ConditionalExpression: - return emitConditionalExpression(node as ConditionalExpression); - case SyntaxKind.TemplateExpression: - return emitTemplateExpression(node as TemplateExpression); - case SyntaxKind.YieldExpression: - return emitYieldExpression(node as YieldExpression); - case SyntaxKind.SpreadElement: - return emitSpreadElement(node as SpreadElement); - case SyntaxKind.ClassExpression: - return emitClassExpression(node as ClassExpression); - case SyntaxKind.OmittedExpression: - return; - case SyntaxKind.AsExpression: - return emitAsExpression(node as AsExpression); - case SyntaxKind.NonNullExpression: - return emitNonNullExpression(node as NonNullExpression); - case SyntaxKind.ExpressionWithTypeArguments: - return emitExpressionWithTypeArguments(node as ExpressionWithTypeArguments); - case SyntaxKind.SatisfiesExpression: - return emitSatisfiesExpression(node as SatisfiesExpression); - case SyntaxKind.MetaProperty: - return emitMetaProperty(node as MetaProperty); - case SyntaxKind.SyntheticExpression: - return Debug.fail("SyntheticExpression should never be printed."); - - // JSX - case SyntaxKind.JsxElement: - return emitJsxElement(node as JsxElement); - case SyntaxKind.JsxSelfClosingElement: - return emitJsxSelfClosingElement(node as JsxSelfClosingElement); - case SyntaxKind.JsxFragment: - return emitJsxFragment(node as JsxFragment); - - // Synthesized list - case SyntaxKind.SyntaxList: - return Debug.fail("SyntaxList should not be printed"); - - // Transformation nodes - case SyntaxKind.NotEmittedStatement: - return; - case SyntaxKind.PartiallyEmittedExpression: - return emitPartiallyEmittedExpression(node as PartiallyEmittedExpression); - case SyntaxKind.CommaListExpression: - return emitCommaList(node as CommaListExpression); - case SyntaxKind.MergeDeclarationMarker: - case SyntaxKind.EndOfDeclarationMarker: - return; - case SyntaxKind.SyntheticReferenceExpression: - return Debug.fail("SyntheticReferenceExpression should not be printed"); + + // Store helpes + const helpers = getHelpersFromBundledSourceFiles(bundle); + if (helpers) { + if (!bundleFileInfo.sources) bundleFileInfo.sources = {}; + bundleFileInfo.sources.helpers = helpers; } } - if (isKeyword(node.kind)) return writeTokenNode(node, writeKeyword); - if (isTokenKind(node.kind)) return writeTokenNode(node, writePunctuation); - Debug.fail(`Unhandled SyntaxKind: ${Debug.formatSyntaxKind(node.kind)}.`); } - function emitMappedTypeParameter(node: TypeParameterDeclaration): void { - emit(node.name); - writeSpace(); - writeKeyword("in"); - writeSpace(); - emit(node.constraint); - } + reset(); + writer = previousWriter; + } - function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) { - const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, hint, node); - Debug.assertIsDefined(lastSubstitution); - node = lastSubstitution; - lastSubstitution = undefined; - pipelinePhase(hint, node); - } + function writeUnparsedSource(unparsed: UnparsedSource, output: EmitTextWriter) { + const previousWriter = writer; + setWriter(output, /*_sourceMapGenerator*/ undefined); + print(EmitHint.Unspecified, unparsed, /*sourceFile*/ undefined); + reset(); + writer = previousWriter; + } - function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined { - let result: string[] | undefined; - if (moduleKind === ModuleKind.None || printerOptions.noEmitHelpers) { - return undefined; - } - const bundledHelpers = new Map(); - for (const sourceFile of bundle.sourceFiles) { - const shouldSkip = getExternalHelpersModuleName(sourceFile) !== undefined; - const helpers = getSortedEmitHelpers(sourceFile); - if (!helpers) continue; - for (const helper of helpers) { - if (!helper.scoped && !shouldSkip && !bundledHelpers.get(helper.name)) { - bundledHelpers.set(helper.name, true); - (result || (result = [])).push(helper.name); - } - } - } + function writeFile(sourceFile: SourceFile, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { + isOwnFileEmit = true; + const previousWriter = writer; + setWriter(output, sourceMapGenerator); + emitShebangIfNeeded(sourceFile); + emitPrologueDirectivesIfNeeded(sourceFile); + print(EmitHint.SourceFile, sourceFile, sourceFile); + reset(); + writer = previousWriter; + } + + function beginPrint() { + return ownWriter || (ownWriter = createTextWriter(newLine)); + } - return result; + function endPrint() { + const text = ownWriter.getText(); + ownWriter.clear(); + return text; + } + + function print(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined) { + if (sourceFile) { + setSourceFile(sourceFile); } - function emitHelpers(node: Node) { - let helpersEmitted = false; - const bundle = node.kind === SyntaxKind.Bundle ? node as Bundle : undefined; - if (bundle && moduleKind === ModuleKind.None) { - return; - } - const numPrepends = bundle ? bundle.prepends.length : 0; - const numNodes = bundle ? bundle.sourceFiles.length + numPrepends : 1; - for (let i = 0; i < numNodes; i++) { - const currentNode = bundle ? i < numPrepends ? bundle.prepends[i] : bundle.sourceFiles[i - numPrepends] : node; - const sourceFile = isSourceFile(currentNode) ? currentNode : isUnparsedSource(currentNode) ? undefined : currentSourceFile; - const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && hasRecordedExternalHelpers(sourceFile)); - const shouldBundle = (isSourceFile(currentNode) || isUnparsedSource(currentNode)) && !isOwnFileEmit; - const helpers = isUnparsedSource(currentNode) ? currentNode.helpers : getSortedEmitHelpers(currentNode); - if (helpers) { - for (const helper of helpers) { - if (!helper.scoped) { - // Skip the helper if it can be skipped and the noEmitHelpers compiler - // option is set, or if it can be imported and the importHelpers compiler - // option is set. - if (shouldSkip) continue; - - // Skip the helper if it can be bundled but hasn't already been emitted and we - // are emitting a bundled module. - if (shouldBundle) { - if (bundledHelpers.get(helper.name)) { - continue; - } - - bundledHelpers.set(helper.name, true); - } - } - else if (bundle) { - // Skip the helper if it is scoped and we are emitting bundled helpers - continue; - } - const pos = getTextPosWithWriteLine(); - if (typeof helper.text === "string") { - writeLines(helper.text); - } - else { - writeLines(helper.text(makeFileLevelOptimisticUniqueName)); - } - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.EmitHelpers, data: helper.name }); - helpersEmitted = true; - } - } - } + pipelineEmit(hint, node, /*parenthesizerRule*/ undefined); + } - return helpersEmitted; + function setSourceFile(sourceFile: SourceFile | undefined) { + currentSourceFile = sourceFile; + currentLineMap = undefined; + detachedCommentsInfo = undefined; + if (sourceFile) { + setSourceMapSource(sourceFile); } + } - function getSortedEmitHelpers(node: Node) { - const helpers = getEmitHelpers(node); - return helpers && stableSort(helpers, compareEmitHelpers); + function setWriter(_writer: EmitTextWriter | undefined, _sourceMapGenerator: SourceMapGenerator | undefined) { + if (_writer && printerOptions.omitTrailingSemicolon) { + _writer = getTrailingSemicolonDeferringWriter(_writer); } - // - // Literals/Pseudo-literals - // + writer = _writer!; // TODO: GH#18217 + sourceMapGenerator = _sourceMapGenerator; + sourceMapsDisabled = !writer || !sourceMapGenerator; + } - // SyntaxKind.NumericLiteral - // SyntaxKind.BigIntLiteral - function emitNumericOrBigIntLiteral(node: NumericLiteral | BigIntLiteral) { - emitLiteral(node, /*jsxAttributeEscape*/ false); - } - - // SyntaxKind.StringLiteral - // SyntaxKind.RegularExpressionLiteral - // SyntaxKind.NoSubstitutionTemplateLiteral - // SyntaxKind.TemplateHead - // SyntaxKind.TemplateMiddle - // SyntaxKind.TemplateTail - function emitLiteral(node: LiteralLikeNode, jsxAttributeEscape: boolean) { - const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape, jsxAttributeEscape); - if ((printerOptions.sourceMap || printerOptions.inlineSourceMap) - && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { - writeLiteral(text); - } - else { - // Quick info expects all literals to be called with writeStringLiteral, as there's no specific type for numberLiterals - writeStringLiteral(text); - } - } + function reset() { + nodeIdToGeneratedName = []; + autoGeneratedIdToGeneratedName = []; + generatedNames = new Set(); + formattedNameTempFlagsStack = []; + formattedNameTempFlags = new Map(); + privateNameTempFlagsStack = []; + privateNameTempFlags = TempFlags.Auto; + tempFlagsStack = []; + tempFlags = TempFlags.Auto; + reservedNamesStack = []; + currentSourceFile = undefined; + currentLineMap = undefined; + detachedCommentsInfo = undefined; + setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined); + } - // SyntaxKind.UnparsedSource - // SyntaxKind.UnparsedPrepend - function emitUnparsedSourceOrPrepend(unparsed: UnparsedSource | UnparsedPrepend) { - for (const text of unparsed.texts) { - writeLine(); - emit(text); - } + function getCurrentLineMap() { + return currentLineMap || (currentLineMap = getLineStarts(Debug.checkDefined(currentSourceFile))); + } + + function emit(node: Node, parenthesizerRule?: (node: Node) => Node): void; + function emit(node: Node | undefined, parenthesizerRule?: (node: Node) => Node): void; + function emit(node: Node | undefined, parenthesizerRule?: (node: Node) => Node) { + if (node === undefined) return; + const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node); + pipelineEmit(EmitHint.Unspecified, node, parenthesizerRule); + recordBundleFileInternalSectionEnd(prevSourceFileTextKind); + } + + function emitIdentifierName(node: Identifier): void; + function emitIdentifierName(node: Identifier | undefined): void; + function emitIdentifierName(node: Identifier | undefined) { + if (node === undefined) return; + pipelineEmit(EmitHint.IdentifierName, node, /*parenthesizerRule*/ undefined); + } + + function emitExpression(node: Expression, parenthesizerRule?: (node: Expression) => Expression): void; + function emitExpression(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression): void; + function emitExpression(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression) { + if (node === undefined) return; + pipelineEmit(EmitHint.Expression, node, parenthesizerRule); + } + + function emitJsxAttributeValue(node: StringLiteral | JsxExpression): void { + pipelineEmit(isStringLiteral(node) ? EmitHint.JsxAttributeValue : EmitHint.Unspecified, node); + } + + function beforeEmitNode(node: Node) { + if (preserveSourceNewlines && (getEmitFlags(node) & EmitFlags.IgnoreSourceNewlines)) { + preserveSourceNewlines = false; } + } + + function afterEmitNode(savedPreserveSourceNewlines: boolean | undefined) { + preserveSourceNewlines = savedPreserveSourceNewlines; + } + + function pipelineEmit(emitHint: EmitHint, node: Node, parenthesizerRule?: (node: Node) => Node) { + currentParenthesizerRule = parenthesizerRule; + const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, emitHint, node); + pipelinePhase(emitHint, node); + currentParenthesizerRule = undefined; + } + + function shouldEmitComments(node: Node) { + return !commentsDisabled && !isSourceFile(node); + } + + function shouldEmitSourceMaps(node: Node) { + return !sourceMapsDisabled && + !isSourceFile(node) && + !isInJsonFile(node) && + !isUnparsedSource(node) && + !isUnparsedPrepend(node); + } - // SyntaxKind.UnparsedPrologue - // SyntaxKind.UnparsedText - // SyntaxKind.UnparsedInternal - // SyntaxKind.UnparsedSyntheticReference - function writeUnparsedNode(unparsed: UnparsedNode) { - writer.rawWrite(unparsed.parent.text.substring(unparsed.pos, unparsed.end)); + function getPipelinePhase(phase: PipelinePhase, emitHint: EmitHint, node: Node) { + switch (phase) { + case PipelinePhase.Notification: + if (onEmitNode !== noEmitNotification && (!isEmitNotificationEnabled || isEmitNotificationEnabled(node))) { + return pipelineEmitWithNotification; + } + // falls through + case PipelinePhase.Substitution: + if (substituteNode !== noEmitSubstitution && (lastSubstitution = substituteNode(emitHint, node) || node) !== node) { + if (currentParenthesizerRule) { + lastSubstitution = currentParenthesizerRule(lastSubstitution); + } + return pipelineEmitWithSubstitution; + } + // falls through + case PipelinePhase.Comments: + if (shouldEmitComments(node)) { + return pipelineEmitWithComments; + } + // falls through + case PipelinePhase.SourceMaps: + if (shouldEmitSourceMaps(node)) { + return pipelineEmitWithSourceMaps; + } + // falls through + case PipelinePhase.Emit: + return pipelineEmitWithHint; + default: + return Debug.assertNever(phase); } + } - // SyntaxKind.UnparsedText - // SyntaxKind.UnparsedInternal - function emitUnparsedTextLike(unparsed: UnparsedTextLike) { - const pos = getTextPosWithWriteLine(); - writeUnparsedNode(unparsed); - if (bundleFileInfo) { - updateOrPushBundleFileTextLike( - pos, - writer.getTextPos(), - unparsed.kind === SyntaxKind.UnparsedText ? - BundleFileSectionKind.Text : - BundleFileSectionKind.Internal - ); - } + function getNextPipelinePhase(currentPhase: PipelinePhase, emitHint: EmitHint, node: Node) { + return getPipelinePhase(currentPhase + 1, emitHint, node); + } + + function pipelineEmitWithNotification(hint: EmitHint, node: Node) { + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, hint, node); + onEmitNode(hint, node, pipelinePhase); + } + + function pipelineEmitWithHint(hint: EmitHint, node: Node): void { + onBeforeEmitNode?.(node); + if (preserveSourceNewlines) { + const savedPreserveSourceNewlines = preserveSourceNewlines; + beforeEmitNode(node); + pipelineEmitWithHintWorker(hint, node); + afterEmitNode(savedPreserveSourceNewlines); } + else { + pipelineEmitWithHintWorker(hint, node); + } + onAfterEmitNode?.(node); + // clear the parenthesizer rule as we ascend + currentParenthesizerRule = undefined; + } - // SyntaxKind.UnparsedSyntheticReference - function emitUnparsedSyntheticReference(unparsed: UnparsedSyntheticReference) { - const pos = getTextPosWithWriteLine(); - writeUnparsedNode(unparsed); - if (bundleFileInfo) { - const section = clone(unparsed.section); - section.pos = pos; - section.end = writer.getTextPos(); - bundleFileInfo.sections.push(section); + function pipelineEmitWithHintWorker(hint: EmitHint, node: Node, allowSnippets = true): void { + if (allowSnippets) { + const snippet = getSnippetElement(node); + if (snippet) { + return emitSnippetNode(hint, node, snippet); } } + if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile)); + if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier)); + if (hint === EmitHint.JsxAttributeValue) return emitLiteral(cast(node, isStringLiteral), /*jsxAttributeEscape*/ true); + if (hint === EmitHint.MappedTypeParameter) return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration)); + if (hint === EmitHint.EmbeddedStatement) { + Debug.assertNode(node, isEmptyStatement); + return emitEmptyStatement(/*isEmbeddedStatement*/ true); + } + if (hint === EmitHint.Unspecified) { + switch (node.kind) { + // Pseudo-literals + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + return emitLiteral(node as LiteralExpression, /*jsxAttributeEscape*/ false); - // - // Snippet Elements - // + // Identifiers + case SyntaxKind.Identifier: + return emitIdentifier(node as Identifier); - function emitSnippetNode(hint: EmitHint, node: Node, snippet: SnippetElement) { - switch (snippet.kind) { - case SnippetKind.Placeholder: - emitPlaceholder(hint, node, snippet); - break; - case SnippetKind.TabStop: - emitTabStop(hint, node, snippet); - break; - } - } + // PrivateIdentifiers + case SyntaxKind.PrivateIdentifier: + return emitPrivateIdentifier(node as PrivateIdentifier); - function emitPlaceholder(hint: EmitHint, node: Node, snippet: Placeholder) { - nonEscapingWrite(`\$\{${snippet.order}:`); // `${2:` - pipelineEmitWithHintWorker(hint, node, /*allowSnippets*/ false); // `...` - nonEscapingWrite(`\}`); // `}` - // `${2:...}` - } + // Parse tree nodes + // Names + case SyntaxKind.QualifiedName: + return emitQualifiedName(node as QualifiedName); + case SyntaxKind.ComputedPropertyName: + return emitComputedPropertyName(node as ComputedPropertyName); - function emitTabStop(hint: EmitHint, node: Node, snippet: TabStop) { - // A tab stop should only be attached to an empty node, i.e. a node that doesn't emit any text. - Debug.assert(node.kind === SyntaxKind.EmptyStatement, - `A tab stop cannot be attached to a node of kind ${Debug.formatSyntaxKind(node.kind)}.`); - Debug.assert(hint !== EmitHint.EmbeddedStatement, - `A tab stop cannot be attached to an embedded statement.`); - nonEscapingWrite(`\$${snippet.order}`); - } + // Signature elements + case SyntaxKind.TypeParameter: + return emitTypeParameter(node as TypeParameterDeclaration); + case SyntaxKind.Parameter: + return emitParameter(node as ParameterDeclaration); + case SyntaxKind.Decorator: + return emitDecorator(node as Decorator); - // - // Identifiers - // + // Type members + case SyntaxKind.PropertySignature: + return emitPropertySignature(node as PropertySignature); + case SyntaxKind.PropertyDeclaration: + return emitPropertyDeclaration(node as PropertyDeclaration); + case SyntaxKind.MethodSignature: + return emitMethodSignature(node as MethodSignature); + case SyntaxKind.MethodDeclaration: + return emitMethodDeclaration(node as MethodDeclaration); + case SyntaxKind.ClassStaticBlockDeclaration: + return emitClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); + case SyntaxKind.Constructor: + return emitConstructor(node as ConstructorDeclaration); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return emitAccessorDeclaration(node as AccessorDeclaration); + case SyntaxKind.CallSignature: + return emitCallSignature(node as CallSignatureDeclaration); + case SyntaxKind.ConstructSignature: + return emitConstructSignature(node as ConstructSignatureDeclaration); + case SyntaxKind.IndexSignature: + return emitIndexSignature(node as IndexSignatureDeclaration); + + // Types + case SyntaxKind.TypePredicate: + return emitTypePredicate(node as TypePredicateNode); + case SyntaxKind.TypeReference: + return emitTypeReference(node as TypeReferenceNode); + case SyntaxKind.FunctionType: + return emitFunctionType(node as FunctionTypeNode); + case SyntaxKind.ConstructorType: + return emitConstructorType(node as ConstructorTypeNode); + case SyntaxKind.TypeQuery: + return emitTypeQuery(node as TypeQueryNode); + case SyntaxKind.TypeLiteral: + return emitTypeLiteral(node as TypeLiteralNode); + case SyntaxKind.ArrayType: + return emitArrayType(node as ArrayTypeNode); + case SyntaxKind.TupleType: + return emitTupleType(node as TupleTypeNode); + case SyntaxKind.OptionalType: + return emitOptionalType(node as OptionalTypeNode); + // SyntaxKind.RestType is handled below + case SyntaxKind.UnionType: + return emitUnionType(node as UnionTypeNode); + case SyntaxKind.IntersectionType: + return emitIntersectionType(node as IntersectionTypeNode); + case SyntaxKind.ConditionalType: + return emitConditionalType(node as ConditionalTypeNode); + case SyntaxKind.InferType: + return emitInferType(node as InferTypeNode); + case SyntaxKind.ParenthesizedType: + return emitParenthesizedType(node as ParenthesizedTypeNode); + case SyntaxKind.ExpressionWithTypeArguments: + return emitExpressionWithTypeArguments(node as ExpressionWithTypeArguments); + case SyntaxKind.ThisType: + return emitThisType(); + case SyntaxKind.TypeOperator: + return emitTypeOperator(node as TypeOperatorNode); + case SyntaxKind.IndexedAccessType: + return emitIndexedAccessType(node as IndexedAccessTypeNode); + case SyntaxKind.MappedType: + return emitMappedType(node as MappedTypeNode); + case SyntaxKind.LiteralType: + return emitLiteralType(node as LiteralTypeNode); + case SyntaxKind.NamedTupleMember: + return emitNamedTupleMember(node as NamedTupleMember); + case SyntaxKind.TemplateLiteralType: + return emitTemplateType(node as TemplateLiteralTypeNode); + case SyntaxKind.TemplateLiteralTypeSpan: + return emitTemplateTypeSpan(node as TemplateLiteralTypeSpan); + case SyntaxKind.ImportType: + return emitImportTypeNode(node as ImportTypeNode); + + // Binding patterns + case SyntaxKind.ObjectBindingPattern: + return emitObjectBindingPattern(node as ObjectBindingPattern); + case SyntaxKind.ArrayBindingPattern: + return emitArrayBindingPattern(node as ArrayBindingPattern); + case SyntaxKind.BindingElement: + return emitBindingElement(node as BindingElement); - function emitIdentifier(node: Identifier) { - const writeText = node.symbol ? writeSymbol : write; - writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); - emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments - } + // Misc + case SyntaxKind.TemplateSpan: + return emitTemplateSpan(node as TemplateSpan); + case SyntaxKind.SemicolonClassElement: + return emitSemicolonClassElement(); - // - // Names - // + // Statements + case SyntaxKind.Block: + return emitBlock(node as Block); + case SyntaxKind.VariableStatement: + return emitVariableStatement(node as VariableStatement); + case SyntaxKind.EmptyStatement: + return emitEmptyStatement(/*isEmbeddedStatement*/ false); + case SyntaxKind.ExpressionStatement: + return emitExpressionStatement(node as ExpressionStatement); + case SyntaxKind.IfStatement: + return emitIfStatement(node as IfStatement); + case SyntaxKind.DoStatement: + return emitDoStatement(node as DoStatement); + case SyntaxKind.WhileStatement: + return emitWhileStatement(node as WhileStatement); + case SyntaxKind.ForStatement: + return emitForStatement(node as ForStatement); + case SyntaxKind.ForInStatement: + return emitForInStatement(node as ForInStatement); + case SyntaxKind.ForOfStatement: + return emitForOfStatement(node as ForOfStatement); + case SyntaxKind.ContinueStatement: + return emitContinueStatement(node as ContinueStatement); + case SyntaxKind.BreakStatement: + return emitBreakStatement(node as BreakStatement); + case SyntaxKind.ReturnStatement: + return emitReturnStatement(node as ReturnStatement); + case SyntaxKind.WithStatement: + return emitWithStatement(node as WithStatement); + case SyntaxKind.SwitchStatement: + return emitSwitchStatement(node as SwitchStatement); + case SyntaxKind.LabeledStatement: + return emitLabeledStatement(node as LabeledStatement); + case SyntaxKind.ThrowStatement: + return emitThrowStatement(node as ThrowStatement); + case SyntaxKind.TryStatement: + return emitTryStatement(node as TryStatement); + case SyntaxKind.DebuggerStatement: + return emitDebuggerStatement(node as DebuggerStatement); - function emitPrivateIdentifier(node: PrivateIdentifier) { - const writeText = node.symbol ? writeSymbol : write; - writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); + // Declarations + case SyntaxKind.VariableDeclaration: + return emitVariableDeclaration(node as VariableDeclaration); + case SyntaxKind.VariableDeclarationList: + return emitVariableDeclarationList(node as VariableDeclarationList); + case SyntaxKind.FunctionDeclaration: + return emitFunctionDeclaration(node as FunctionDeclaration); + case SyntaxKind.ClassDeclaration: + return emitClassDeclaration(node as ClassDeclaration); + case SyntaxKind.InterfaceDeclaration: + return emitInterfaceDeclaration(node as InterfaceDeclaration); + case SyntaxKind.TypeAliasDeclaration: + return emitTypeAliasDeclaration(node as TypeAliasDeclaration); + case SyntaxKind.EnumDeclaration: + return emitEnumDeclaration(node as EnumDeclaration); + case SyntaxKind.ModuleDeclaration: + return emitModuleDeclaration(node as ModuleDeclaration); + case SyntaxKind.ModuleBlock: + return emitModuleBlock(node as ModuleBlock); + case SyntaxKind.CaseBlock: + return emitCaseBlock(node as CaseBlock); + case SyntaxKind.NamespaceExportDeclaration: + return emitNamespaceExportDeclaration(node as NamespaceExportDeclaration); + case SyntaxKind.ImportEqualsDeclaration: + return emitImportEqualsDeclaration(node as ImportEqualsDeclaration); + case SyntaxKind.ImportDeclaration: + return emitImportDeclaration(node as ImportDeclaration); + case SyntaxKind.ImportClause: + return emitImportClause(node as ImportClause); + case SyntaxKind.NamespaceImport: + return emitNamespaceImport(node as NamespaceImport); + case SyntaxKind.NamespaceExport: + return emitNamespaceExport(node as NamespaceExport); + case SyntaxKind.NamedImports: + return emitNamedImports(node as NamedImports); + case SyntaxKind.ImportSpecifier: + return emitImportSpecifier(node as ImportSpecifier); + case SyntaxKind.ExportAssignment: + return emitExportAssignment(node as ExportAssignment); + case SyntaxKind.ExportDeclaration: + return emitExportDeclaration(node as ExportDeclaration); + case SyntaxKind.NamedExports: + return emitNamedExports(node as NamedExports); + case SyntaxKind.ExportSpecifier: + return emitExportSpecifier(node as ExportSpecifier); + case SyntaxKind.AssertClause: + return emitAssertClause(node as AssertClause); + case SyntaxKind.AssertEntry: + return emitAssertEntry(node as AssertEntry); + case SyntaxKind.MissingDeclaration: + return; + + // Module references + case SyntaxKind.ExternalModuleReference: + return emitExternalModuleReference(node as ExternalModuleReference); + + // JSX (non-expression) + case SyntaxKind.JsxText: + return emitJsxText(node as JsxText); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxOpeningFragment: + return emitJsxOpeningElementOrFragment(node as JsxOpeningElement); + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxClosingFragment: + return emitJsxClosingElementOrFragment(node as JsxClosingElement); + case SyntaxKind.JsxAttribute: + return emitJsxAttribute(node as JsxAttribute); + case SyntaxKind.JsxAttributes: + return emitJsxAttributes(node as JsxAttributes); + case SyntaxKind.JsxSpreadAttribute: + return emitJsxSpreadAttribute(node as JsxSpreadAttribute); + case SyntaxKind.JsxExpression: + return emitJsxExpression(node as JsxExpression); + + // Clauses + case SyntaxKind.CaseClause: + return emitCaseClause(node as CaseClause); + case SyntaxKind.DefaultClause: + return emitDefaultClause(node as DefaultClause); + case SyntaxKind.HeritageClause: + return emitHeritageClause(node as HeritageClause); + case SyntaxKind.CatchClause: + return emitCatchClause(node as CatchClause); + + // Property assignments + case SyntaxKind.PropertyAssignment: + return emitPropertyAssignment(node as PropertyAssignment); + case SyntaxKind.ShorthandPropertyAssignment: + return emitShorthandPropertyAssignment(node as ShorthandPropertyAssignment); + case SyntaxKind.SpreadAssignment: + return emitSpreadAssignment(node as SpreadAssignment); + + // Enum + case SyntaxKind.EnumMember: + return emitEnumMember(node as EnumMember); + + // Unparsed + case SyntaxKind.UnparsedPrologue: + return writeUnparsedNode(node as UnparsedNode); + case SyntaxKind.UnparsedSource: + case SyntaxKind.UnparsedPrepend: + return emitUnparsedSourceOrPrepend(node as UnparsedSource); + case SyntaxKind.UnparsedText: + case SyntaxKind.UnparsedInternalText: + return emitUnparsedTextLike(node as UnparsedTextLike); + case SyntaxKind.UnparsedSyntheticReference: + return emitUnparsedSyntheticReference(node as UnparsedSyntheticReference); + + // Top-level nodes + case SyntaxKind.SourceFile: + return emitSourceFile(node as SourceFile); + case SyntaxKind.Bundle: + return Debug.fail("Bundles should be printed using printBundle"); + // SyntaxKind.UnparsedSource (handled above) + case SyntaxKind.InputFiles: + return Debug.fail("InputFiles should not be printed"); + + // JSDoc nodes (only used in codefixes currently) + case SyntaxKind.JSDocTypeExpression: + return emitJSDocTypeExpression(node as JSDocTypeExpression); + case SyntaxKind.JSDocNameReference: + return emitJSDocNameReference(node as JSDocNameReference); + case SyntaxKind.JSDocAllType: + return writePunctuation("*"); + case SyntaxKind.JSDocUnknownType: + return writePunctuation("?"); + case SyntaxKind.JSDocNullableType: + return emitJSDocNullableType(node as JSDocNullableType); + case SyntaxKind.JSDocNonNullableType: + return emitJSDocNonNullableType(node as JSDocNonNullableType); + case SyntaxKind.JSDocOptionalType: + return emitJSDocOptionalType(node as JSDocOptionalType); + case SyntaxKind.JSDocFunctionType: + return emitJSDocFunctionType(node as JSDocFunctionType); + case SyntaxKind.RestType: + case SyntaxKind.JSDocVariadicType: + return emitRestOrJSDocVariadicType(node as RestTypeNode | JSDocVariadicType); + case SyntaxKind.JSDocNamepathType: + return; + case SyntaxKind.JSDoc: + return emitJSDoc(node as JSDoc); + case SyntaxKind.JSDocTypeLiteral: + return emitJSDocTypeLiteral(node as JSDocTypeLiteral); + case SyntaxKind.JSDocSignature: + return emitJSDocSignature(node as JSDocSignature); + case SyntaxKind.JSDocTag: + case SyntaxKind.JSDocClassTag: + case SyntaxKind.JSDocOverrideTag: + return emitJSDocSimpleTag(node as JSDocTag); + case SyntaxKind.JSDocAugmentsTag: + case SyntaxKind.JSDocImplementsTag: + return emitJSDocHeritageTag(node as JSDocImplementsTag | JSDocAugmentsTag); + case SyntaxKind.JSDocAuthorTag: + case SyntaxKind.JSDocDeprecatedTag: + return; + // SyntaxKind.JSDocClassTag (see JSDocTag, above) + case SyntaxKind.JSDocPublicTag: + case SyntaxKind.JSDocPrivateTag: + case SyntaxKind.JSDocProtectedTag: + case SyntaxKind.JSDocReadonlyTag: + return; + case SyntaxKind.JSDocCallbackTag: + return emitJSDocCallbackTag(node as JSDocCallbackTag); + // SyntaxKind.JSDocEnumTag (see below) + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + return emitJSDocPropertyLikeTag(node as JSDocPropertyLikeTag); + case SyntaxKind.JSDocEnumTag: + case SyntaxKind.JSDocReturnTag: + case SyntaxKind.JSDocThisTag: + case SyntaxKind.JSDocTypeTag: + return emitJSDocSimpleTypedTag(node as JSDocTypeTag); + case SyntaxKind.JSDocTemplateTag: + return emitJSDocTemplateTag(node as JSDocTemplateTag); + case SyntaxKind.JSDocTypedefTag: + return emitJSDocTypedefTag(node as JSDocTypedefTag); + case SyntaxKind.JSDocSeeTag: + return emitJSDocSeeTag(node as JSDocSeeTag); + // SyntaxKind.JSDocPropertyTag (see JSDocParameterTag, above) + + // Transformation nodes + case SyntaxKind.NotEmittedStatement: + case SyntaxKind.EndOfDeclarationMarker: + case SyntaxKind.MergeDeclarationMarker: + return; + } + if (isExpression(node)) { + hint = EmitHint.Expression; + if (substituteNode !== noEmitSubstitution) { + const substitute = substituteNode(hint, node) || node; + if (substitute !== node) { + node = substitute; + if (currentParenthesizerRule) { + node = currentParenthesizerRule(node); + } + } + } + } } + if (hint === EmitHint.Expression) { + switch (node.kind) { + // Literals + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + return emitNumericOrBigIntLiteral(node as NumericLiteral | BigIntLiteral); + case SyntaxKind.StringLiteral: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return emitLiteral(node as LiteralExpression, /*jsxAttributeEscape*/ false); - function emitQualifiedName(node: QualifiedName) { - emitEntityName(node.left); - writePunctuation("."); - emit(node.right); - } + // Identifiers + case SyntaxKind.Identifier: + return emitIdentifier(node as Identifier); + case SyntaxKind.PrivateIdentifier: + return emitPrivateIdentifier(node as PrivateIdentifier); + + // Expressions + case SyntaxKind.ArrayLiteralExpression: + return emitArrayLiteralExpression(node as ArrayLiteralExpression); + case SyntaxKind.ObjectLiteralExpression: + return emitObjectLiteralExpression(node as ObjectLiteralExpression); + case SyntaxKind.PropertyAccessExpression: + return emitPropertyAccessExpression(node as PropertyAccessExpression); + case SyntaxKind.ElementAccessExpression: + return emitElementAccessExpression(node as ElementAccessExpression); + case SyntaxKind.CallExpression: + return emitCallExpression(node as CallExpression); + case SyntaxKind.NewExpression: + return emitNewExpression(node as NewExpression); + case SyntaxKind.TaggedTemplateExpression: + return emitTaggedTemplateExpression(node as TaggedTemplateExpression); + case SyntaxKind.TypeAssertionExpression: + return emitTypeAssertionExpression(node as TypeAssertion); + case SyntaxKind.ParenthesizedExpression: + return emitParenthesizedExpression(node as ParenthesizedExpression); + case SyntaxKind.FunctionExpression: + return emitFunctionExpression(node as FunctionExpression); + case SyntaxKind.ArrowFunction: + return emitArrowFunction(node as ArrowFunction); + case SyntaxKind.DeleteExpression: + return emitDeleteExpression(node as DeleteExpression); + case SyntaxKind.TypeOfExpression: + return emitTypeOfExpression(node as TypeOfExpression); + case SyntaxKind.VoidExpression: + return emitVoidExpression(node as VoidExpression); + case SyntaxKind.AwaitExpression: + return emitAwaitExpression(node as AwaitExpression); + case SyntaxKind.PrefixUnaryExpression: + return emitPrefixUnaryExpression(node as PrefixUnaryExpression); + case SyntaxKind.PostfixUnaryExpression: + return emitPostfixUnaryExpression(node as PostfixUnaryExpression); + case SyntaxKind.BinaryExpression: + return emitBinaryExpression(node as BinaryExpression); + case SyntaxKind.ConditionalExpression: + return emitConditionalExpression(node as ConditionalExpression); + case SyntaxKind.TemplateExpression: + return emitTemplateExpression(node as TemplateExpression); + case SyntaxKind.YieldExpression: + return emitYieldExpression(node as YieldExpression); + case SyntaxKind.SpreadElement: + return emitSpreadElement(node as SpreadElement); + case SyntaxKind.ClassExpression: + return emitClassExpression(node as ClassExpression); + case SyntaxKind.OmittedExpression: + return; + case SyntaxKind.AsExpression: + return emitAsExpression(node as AsExpression); + case SyntaxKind.NonNullExpression: + return emitNonNullExpression(node as NonNullExpression); + case SyntaxKind.ExpressionWithTypeArguments: + return emitExpressionWithTypeArguments(node as ExpressionWithTypeArguments); + case SyntaxKind.SatisfiesExpression: + return emitSatisfiesExpression(node as SatisfiesExpression); + case SyntaxKind.MetaProperty: + return emitMetaProperty(node as MetaProperty); + case SyntaxKind.SyntheticExpression: + return Debug.fail("SyntheticExpression should never be printed."); + + // JSX + case SyntaxKind.JsxElement: + return emitJsxElement(node as JsxElement); + case SyntaxKind.JsxSelfClosingElement: + return emitJsxSelfClosingElement(node as JsxSelfClosingElement); + case SyntaxKind.JsxFragment: + return emitJsxFragment(node as JsxFragment); + + // Synthesized list + case SyntaxKind.SyntaxList: + return Debug.fail("SyntaxList should not be printed"); + + // Transformation nodes + case SyntaxKind.NotEmittedStatement: + return; + case SyntaxKind.PartiallyEmittedExpression: + return emitPartiallyEmittedExpression(node as PartiallyEmittedExpression); + case SyntaxKind.CommaListExpression: + return emitCommaList(node as CommaListExpression); + case SyntaxKind.MergeDeclarationMarker: + case SyntaxKind.EndOfDeclarationMarker: + return; + case SyntaxKind.SyntheticReferenceExpression: + return Debug.fail("SyntheticReferenceExpression should not be printed"); + } + } + if (isKeyword(node.kind)) return writeTokenNode(node, writeKeyword); + if (isTokenKind(node.kind)) return writeTokenNode(node, writePunctuation); + Debug.fail(`Unhandled SyntaxKind: ${Debug.formatSyntaxKind(node.kind)}.`); + } - function emitEntityName(node: EntityName) { - if (node.kind === SyntaxKind.Identifier) { - emitExpression(node); - } - else { - emit(node); + function emitMappedTypeParameter(node: TypeParameterDeclaration): void { + emit(node.name); + writeSpace(); + writeKeyword("in"); + writeSpace(); + emit(node.constraint); + } + + function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) { + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, hint, node); + Debug.assertIsDefined(lastSubstitution); + node = lastSubstitution; + lastSubstitution = undefined; + pipelinePhase(hint, node); + } + + function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined { + let result: string[] | undefined; + if (moduleKind === ModuleKind.None || printerOptions.noEmitHelpers) { + return undefined; + } + const bundledHelpers = new Map(); + for (const sourceFile of bundle.sourceFiles) { + const shouldSkip = getExternalHelpersModuleName(sourceFile) !== undefined; + const helpers = getSortedEmitHelpers(sourceFile); + if (!helpers) continue; + for (const helper of helpers) { + if (!helper.scoped && !shouldSkip && !bundledHelpers.get(helper.name)) { + bundledHelpers.set(helper.name, true); + (result || (result = [])).push(helper.name); + } } } - function emitComputedPropertyName(node: ComputedPropertyName) { - writePunctuation("["); - emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfComputedPropertyName); - writePunctuation("]"); - } + return result; + } - // - // Signature elements - // + function emitHelpers(node: Node) { + let helpersEmitted = false; + const bundle = node.kind === SyntaxKind.Bundle ? node as Bundle : undefined; + if (bundle && moduleKind === ModuleKind.None) { + return; + } + const numPrepends = bundle ? bundle.prepends.length : 0; + const numNodes = bundle ? bundle.sourceFiles.length + numPrepends : 1; + for (let i = 0; i < numNodes; i++) { + const currentNode = bundle ? i < numPrepends ? bundle.prepends[i] : bundle.sourceFiles[i - numPrepends] : node; + const sourceFile = isSourceFile(currentNode) ? currentNode : isUnparsedSource(currentNode) ? undefined : currentSourceFile; + const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && hasRecordedExternalHelpers(sourceFile)); + const shouldBundle = (isSourceFile(currentNode) || isUnparsedSource(currentNode)) && !isOwnFileEmit; + const helpers = isUnparsedSource(currentNode) ? currentNode.helpers : getSortedEmitHelpers(currentNode); + if (helpers) { + for (const helper of helpers) { + if (!helper.scoped) { + // Skip the helper if it can be skipped and the noEmitHelpers compiler + // option is set, or if it can be imported and the importHelpers compiler + // option is set. + if (shouldSkip) continue; + + // Skip the helper if it can be bundled but hasn't already been emitted and we + // are emitting a bundled module. + if (shouldBundle) { + if (bundledHelpers.get(helper.name)) { + continue; + } - function emitTypeParameter(node: TypeParameterDeclaration) { - emitModifiers(node, node.modifiers); - emit(node.name); - if (node.constraint) { - writeSpace(); - writeKeyword("extends"); - writeSpace(); - emit(node.constraint); - } - if (node.default) { - writeSpace(); - writeOperator("="); - writeSpace(); - emit(node.default); + bundledHelpers.set(helper.name, true); + } + } + else if (bundle) { + // Skip the helper if it is scoped and we are emitting bundled helpers + continue; + } + const pos = getTextPosWithWriteLine(); + if (typeof helper.text === "string") { + writeLines(helper.text); + } + else { + writeLines(helper.text(makeFileLevelOptimisticUniqueName)); + } + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.EmitHelpers, data: helper.name }); + helpersEmitted = true; + } } } - function emitParameter(node: ParameterDeclaration) { - emitDecoratorsAndModifiers(node, node.modifiers); - emit(node.dotDotDotToken); - emitNodeWithWriter(node.name, writeParameter); - emit(node.questionToken); - if (node.parent && node.parent.kind === SyntaxKind.JSDocFunctionType && !node.name) { - emit(node.type); - } - else { - emitTypeAnnotation(node.type); - } - // The comment position has to fallback to any present node within the parameterdeclaration because as it turns out, the parser can make parameter declarations with _just_ an initializer. - emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name ? node.name.end : node.modifiers ? node.modifiers.end : node.pos, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + return helpersEmitted; + } + + function getSortedEmitHelpers(node: Node) { + const helpers = getEmitHelpers(node); + return helpers && stableSort(helpers, compareEmitHelpers); + } + + // + // Literals/Pseudo-literals + // + + // SyntaxKind.NumericLiteral + // SyntaxKind.BigIntLiteral + function emitNumericOrBigIntLiteral(node: NumericLiteral | BigIntLiteral) { + emitLiteral(node, /*jsxAttributeEscape*/ false); + } + + // SyntaxKind.StringLiteral + // SyntaxKind.RegularExpressionLiteral + // SyntaxKind.NoSubstitutionTemplateLiteral + // SyntaxKind.TemplateHead + // SyntaxKind.TemplateMiddle + // SyntaxKind.TemplateTail + function emitLiteral(node: LiteralLikeNode, jsxAttributeEscape: boolean) { + const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape, jsxAttributeEscape); + if ((printerOptions.sourceMap || printerOptions.inlineSourceMap) + && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { + writeLiteral(text); + } + else { + // Quick info expects all literals to be called with writeStringLiteral, as there's no specific type for numberLiterals + writeStringLiteral(text); } + } - function emitDecorator(decorator: Decorator) { - writePunctuation("@"); - emitExpression(decorator.expression, parenthesizer.parenthesizeLeftSideOfAccess); + // SyntaxKind.UnparsedSource + // SyntaxKind.UnparsedPrepend + function emitUnparsedSourceOrPrepend(unparsed: UnparsedSource | UnparsedPrepend) { + for (const text of unparsed.texts) { + writeLine(); + emit(text); } + } - // - // Type members - // + // SyntaxKind.UnparsedPrologue + // SyntaxKind.UnparsedText + // SyntaxKind.UnparsedInternal + // SyntaxKind.UnparsedSyntheticReference + function writeUnparsedNode(unparsed: UnparsedNode) { + writer.rawWrite(unparsed.parent.text.substring(unparsed.pos, unparsed.end)); + } - function emitPropertySignature(node: PropertySignature) { - emitModifiers(node, node.modifiers); - emitNodeWithWriter(node.name, writeProperty); - emit(node.questionToken); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); + // SyntaxKind.UnparsedText + // SyntaxKind.UnparsedInternal + function emitUnparsedTextLike(unparsed: UnparsedTextLike) { + const pos = getTextPosWithWriteLine(); + writeUnparsedNode(unparsed); + if (bundleFileInfo) { + updateOrPushBundleFileTextLike( + pos, + writer.getTextPos(), + unparsed.kind === SyntaxKind.UnparsedText ? + BundleFileSectionKind.Text : + BundleFileSectionKind.Internal + ); } + } - function emitPropertyDeclaration(node: PropertyDeclaration) { - emitDecoratorsAndModifiers(node, node.modifiers); - emit(node.name); - emit(node.questionToken); - emit(node.exclamationToken); - emitTypeAnnotation(node.type); - emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name.end, node); - writeTrailingSemicolon(); + // SyntaxKind.UnparsedSyntheticReference + function emitUnparsedSyntheticReference(unparsed: UnparsedSyntheticReference) { + const pos = getTextPosWithWriteLine(); + writeUnparsedNode(unparsed); + if (bundleFileInfo) { + const section = clone(unparsed.section); + section.pos = pos; + section.end = writer.getTextPos(); + bundleFileInfo.sections.push(section); } + } - function emitMethodSignature(node: MethodSignature) { - pushNameGenerationScope(node); - emitModifiers(node, node.modifiers); - emit(node.name); - emit(node.questionToken); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); - } + // + // Snippet Elements + // - function emitMethodDeclaration(node: MethodDeclaration) { - emitDecoratorsAndModifiers(node, node.modifiers); - emit(node.asteriskToken); - emit(node.name); - emit(node.questionToken); - emitSignatureAndBody(node, emitSignatureHead); + function emitSnippetNode(hint: EmitHint, node: Node, snippet: SnippetElement) { + switch (snippet.kind) { + case SnippetKind.Placeholder: + emitPlaceholder(hint, node, snippet); + break; + case SnippetKind.TabStop: + emitTabStop(hint, node, snippet); + break; } + } - function emitClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { - writeKeyword("static"); - emitBlockFunctionBody(node.body); - } + function emitPlaceholder(hint: EmitHint, node: Node, snippet: Placeholder) { + nonEscapingWrite(`\$\{${snippet.order}:`); // `${2:` + pipelineEmitWithHintWorker(hint, node, /*allowSnippets*/ false); // `...` + nonEscapingWrite(`\}`); // `}` + // `${2:...}` + } - function emitConstructor(node: ConstructorDeclaration) { - emitModifiers(node, node.modifiers); - writeKeyword("constructor"); - emitSignatureAndBody(node, emitSignatureHead); - } + function emitTabStop(hint: EmitHint, node: Node, snippet: TabStop) { + // A tab stop should only be attached to an empty node, i.e. a node that doesn't emit any text. + Debug.assert(node.kind === SyntaxKind.EmptyStatement, + `A tab stop cannot be attached to a node of kind ${Debug.formatSyntaxKind(node.kind)}.`); + Debug.assert(hint !== EmitHint.EmbeddedStatement, + `A tab stop cannot be attached to an embedded statement.`); + nonEscapingWrite(`\$${snippet.order}`); + } - function emitAccessorDeclaration(node: AccessorDeclaration) { - emitDecoratorsAndModifiers(node, node.modifiers); - writeKeyword(node.kind === SyntaxKind.GetAccessor ? "get" : "set"); - writeSpace(); - emit(node.name); - emitSignatureAndBody(node, emitSignatureHead); - } + // + // Identifiers + // - function emitCallSignature(node: CallSignatureDeclaration) { - pushNameGenerationScope(node); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); - } + function emitIdentifier(node: Identifier) { + const writeText = node.symbol ? writeSymbol : write; + writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); + emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments + } - function emitConstructSignature(node: ConstructSignatureDeclaration) { - pushNameGenerationScope(node); - writeKeyword("new"); - writeSpace(); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - popNameGenerationScope(node); - } + // + // Names + // - function emitIndexSignature(node: IndexSignatureDeclaration) { - emitModifiers(node, node.modifiers); - emitParametersForIndexSignature(node, node.parameters); - emitTypeAnnotation(node.type); - writeTrailingSemicolon(); - } + function emitPrivateIdentifier(node: PrivateIdentifier) { + const writeText = node.symbol ? writeSymbol : write; + writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); + } - function emitTemplateTypeSpan(node: TemplateLiteralTypeSpan) { - emit(node.type); - emit(node.literal); - } - function emitSemicolonClassElement() { - writeTrailingSemicolon(); + function emitQualifiedName(node: QualifiedName) { + emitEntityName(node.left); + writePunctuation("."); + emit(node.right); + } + + function emitEntityName(node: EntityName) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); + } + else { + emit(node); } + } - // - // Types - // + function emitComputedPropertyName(node: ComputedPropertyName) { + writePunctuation("["); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfComputedPropertyName); + writePunctuation("]"); + } - function emitTypePredicate(node: TypePredicateNode) { - if (node.assertsModifier) { - emit(node.assertsModifier); - writeSpace(); - } - emit(node.parameterName); - if (node.type) { - writeSpace(); - writeKeyword("is"); - writeSpace(); - emit(node.type); - } - } + // + // Signature elements + // - function emitTypeReference(node: TypeReferenceNode) { - emit(node.typeName); - emitTypeArguments(node, node.typeArguments); + function emitTypeParameter(node: TypeParameterDeclaration) { + emitModifiers(node, node.modifiers); + emit(node.name); + if (node.constraint) { + writeSpace(); + writeKeyword("extends"); + writeSpace(); + emit(node.constraint); } - - function emitFunctionType(node: FunctionTypeNode) { - pushNameGenerationScope(node); - emitTypeParameters(node, node.typeParameters); - emitParametersForArrow(node, node.parameters); + if (node.default) { writeSpace(); - writePunctuation("=>"); + writeOperator("="); writeSpace(); - emit(node.type); - popNameGenerationScope(node); + emit(node.default); } + } - function emitJSDocFunctionType(node: JSDocFunctionType) { - writeKeyword("function"); - emitParameters(node, node.parameters); - writePunctuation(":"); + function emitParameter(node: ParameterDeclaration) { + emitDecoratorsAndModifiers(node, node.modifiers); + emit(node.dotDotDotToken); + emitNodeWithWriter(node.name, writeParameter); + emit(node.questionToken); + if (node.parent && node.parent.kind === SyntaxKind.JSDocFunctionType && !node.name) { emit(node.type); } + else { + emitTypeAnnotation(node.type); + } + // The comment position has to fallback to any present node within the parameterdeclaration because as it turns out, the parser can make parameter declarations with _just_ an initializer. + emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name ? node.name.end : node.modifiers ? node.modifiers.end : node.pos, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + } + function emitDecorator(decorator: Decorator) { + writePunctuation("@"); + emitExpression(decorator.expression, parenthesizer.parenthesizeLeftSideOfAccess); + } - function emitJSDocNullableType(node: JSDocNullableType) { - writePunctuation("?"); - emit(node.type); - } + // + // Type members + // - function emitJSDocNonNullableType(node: JSDocNonNullableType) { - writePunctuation("!"); - emit(node.type); - } + function emitPropertySignature(node: PropertySignature) { + emitModifiers(node, node.modifiers); + emitNodeWithWriter(node.name, writeProperty); + emit(node.questionToken); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + } - function emitJSDocOptionalType(node: JSDocOptionalType) { - emit(node.type); - writePunctuation("="); - } + function emitPropertyDeclaration(node: PropertyDeclaration) { + emitDecoratorsAndModifiers(node, node.modifiers); + emit(node.name); + emit(node.questionToken); + emit(node.exclamationToken); + emitTypeAnnotation(node.type); + emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name.end, node); + writeTrailingSemicolon(); + } + + function emitMethodSignature(node: MethodSignature) { + pushNameGenerationScope(node); + emitModifiers(node, node.modifiers); + emit(node.name); + emit(node.questionToken); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); + } + + function emitMethodDeclaration(node: MethodDeclaration) { + emitDecoratorsAndModifiers(node, node.modifiers); + emit(node.asteriskToken); + emit(node.name); + emit(node.questionToken); + emitSignatureAndBody(node, emitSignatureHead); + } + + function emitClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + writeKeyword("static"); + emitBlockFunctionBody(node.body); + } + + function emitConstructor(node: ConstructorDeclaration) { + emitModifiers(node, node.modifiers); + writeKeyword("constructor"); + emitSignatureAndBody(node, emitSignatureHead); + } + + function emitAccessorDeclaration(node: AccessorDeclaration) { + emitDecoratorsAndModifiers(node, node.modifiers); + writeKeyword(node.kind === SyntaxKind.GetAccessor ? "get" : "set"); + writeSpace(); + emit(node.name); + emitSignatureAndBody(node, emitSignatureHead); + } + + function emitCallSignature(node: CallSignatureDeclaration) { + pushNameGenerationScope(node); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); + } + + function emitConstructSignature(node: ConstructSignatureDeclaration) { + pushNameGenerationScope(node); + writeKeyword("new"); + writeSpace(); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + popNameGenerationScope(node); + } + + function emitIndexSignature(node: IndexSignatureDeclaration) { + emitModifiers(node, node.modifiers); + emitParametersForIndexSignature(node, node.parameters); + emitTypeAnnotation(node.type); + writeTrailingSemicolon(); + } + + function emitTemplateTypeSpan(node: TemplateLiteralTypeSpan) { + emit(node.type); + emit(node.literal); + } + + function emitSemicolonClassElement() { + writeTrailingSemicolon(); + } + + // + // Types + // - function emitConstructorType(node: ConstructorTypeNode) { - pushNameGenerationScope(node); - emitModifiers(node, node.modifiers); - writeKeyword("new"); + function emitTypePredicate(node: TypePredicateNode) { + if (node.assertsModifier) { + emit(node.assertsModifier); writeSpace(); - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); + } + emit(node.parameterName); + if (node.type) { writeSpace(); - writePunctuation("=>"); + writeKeyword("is"); writeSpace(); emit(node.type); - popNameGenerationScope(node); } + } - function emitTypeQuery(node: TypeQueryNode) { - writeKeyword("typeof"); - writeSpace(); - emit(node.exprName); - emitTypeArguments(node, node.typeArguments); - } + function emitTypeReference(node: TypeReferenceNode) { + emit(node.typeName); + emitTypeArguments(node, node.typeArguments); + } - function emitTypeLiteral(node: TypeLiteralNode) { - writePunctuation("{"); - const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTypeLiteralMembers : ListFormat.MultiLineTypeLiteralMembers; - emitList(node, node.members, flags | ListFormat.NoSpaceIfEmpty); - writePunctuation("}"); - } + function emitFunctionType(node: FunctionTypeNode) { + pushNameGenerationScope(node); + emitTypeParameters(node, node.typeParameters); + emitParametersForArrow(node, node.parameters); + writeSpace(); + writePunctuation("=>"); + writeSpace(); + emit(node.type); + popNameGenerationScope(node); + } - function emitArrayType(node: ArrayTypeNode) { - emit(node.elementType, parenthesizer.parenthesizeNonArrayTypeOfPostfixType); - writePunctuation("["); - writePunctuation("]"); - } + function emitJSDocFunctionType(node: JSDocFunctionType) { + writeKeyword("function"); + emitParameters(node, node.parameters); + writePunctuation(":"); + emit(node.type); + } - function emitRestOrJSDocVariadicType(node: RestTypeNode | JSDocVariadicType) { - writePunctuation("..."); - emit(node.type); - } - function emitTupleType(node: TupleTypeNode) { - emitTokenWithComment(SyntaxKind.OpenBracketToken, node.pos, writePunctuation, node); - const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTupleTypeElements : ListFormat.MultiLineTupleTypeElements; - emitList(node, node.elements, flags | ListFormat.NoSpaceIfEmpty, parenthesizer.parenthesizeElementTypeOfTupleType); - emitTokenWithComment(SyntaxKind.CloseBracketToken, node.elements.end, writePunctuation, node); - } + function emitJSDocNullableType(node: JSDocNullableType) { + writePunctuation("?"); + emit(node.type); + } - function emitNamedTupleMember(node: NamedTupleMember) { - emit(node.dotDotDotToken); - emit(node.name); - emit(node.questionToken); - emitTokenWithComment(SyntaxKind.ColonToken, node.name.end, writePunctuation, node); - writeSpace(); - emit(node.type); - } + function emitJSDocNonNullableType(node: JSDocNonNullableType) { + writePunctuation("!"); + emit(node.type); + } - function emitOptionalType(node: OptionalTypeNode) { - emit(node.type, parenthesizer.parenthesizeTypeOfOptionalType); - writePunctuation("?"); - } + function emitJSDocOptionalType(node: JSDocOptionalType) { + emit(node.type); + writePunctuation("="); + } - function emitUnionType(node: UnionTypeNode) { - emitList(node, node.types, ListFormat.UnionTypeConstituents, parenthesizer.parenthesizeConstituentTypeOfUnionType); - } + function emitConstructorType(node: ConstructorTypeNode) { + pushNameGenerationScope(node); + emitModifiers(node, node.modifiers); + writeKeyword("new"); + writeSpace(); + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + writeSpace(); + writePunctuation("=>"); + writeSpace(); + emit(node.type); + popNameGenerationScope(node); + } - function emitIntersectionType(node: IntersectionTypeNode) { - emitList(node, node.types, ListFormat.IntersectionTypeConstituents, parenthesizer.parenthesizeConstituentTypeOfIntersectionType); - } + function emitTypeQuery(node: TypeQueryNode) { + writeKeyword("typeof"); + writeSpace(); + emit(node.exprName); + emitTypeArguments(node, node.typeArguments); + } - function emitConditionalType(node: ConditionalTypeNode) { - emit(node.checkType, parenthesizer.parenthesizeCheckTypeOfConditionalType); - writeSpace(); - writeKeyword("extends"); - writeSpace(); - emit(node.extendsType, parenthesizer.parenthesizeExtendsTypeOfConditionalType); - writeSpace(); - writePunctuation("?"); - writeSpace(); - emit(node.trueType); - writeSpace(); - writePunctuation(":"); - writeSpace(); - emit(node.falseType); - } + function emitTypeLiteral(node: TypeLiteralNode) { + writePunctuation("{"); + const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTypeLiteralMembers : ListFormat.MultiLineTypeLiteralMembers; + emitList(node, node.members, flags | ListFormat.NoSpaceIfEmpty); + writePunctuation("}"); + } - function emitInferType(node: InferTypeNode) { - writeKeyword("infer"); - writeSpace(); - emit(node.typeParameter); - } + function emitArrayType(node: ArrayTypeNode) { + emit(node.elementType, parenthesizer.parenthesizeNonArrayTypeOfPostfixType); + writePunctuation("["); + writePunctuation("]"); + } - function emitParenthesizedType(node: ParenthesizedTypeNode) { - writePunctuation("("); - emit(node.type); - writePunctuation(")"); - } + function emitRestOrJSDocVariadicType(node: RestTypeNode | JSDocVariadicType) { + writePunctuation("..."); + emit(node.type); + } - function emitThisType() { - writeKeyword("this"); - } + function emitTupleType(node: TupleTypeNode) { + emitTokenWithComment(SyntaxKind.OpenBracketToken, node.pos, writePunctuation, node); + const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTupleTypeElements : ListFormat.MultiLineTupleTypeElements; + emitList(node, node.elements, flags | ListFormat.NoSpaceIfEmpty, parenthesizer.parenthesizeElementTypeOfTupleType); + emitTokenWithComment(SyntaxKind.CloseBracketToken, node.elements.end, writePunctuation, node); + } - function emitTypeOperator(node: TypeOperatorNode) { - writeTokenText(node.operator, writeKeyword); - writeSpace(); + function emitNamedTupleMember(node: NamedTupleMember) { + emit(node.dotDotDotToken); + emit(node.name); + emit(node.questionToken); + emitTokenWithComment(SyntaxKind.ColonToken, node.name.end, writePunctuation, node); + writeSpace(); + emit(node.type); + } - const parenthesizerRule = node.operator === SyntaxKind.ReadonlyKeyword ? - parenthesizer.parenthesizeOperandOfReadonlyTypeOperator : - parenthesizer.parenthesizeOperandOfTypeOperator; - emit(node.type, parenthesizerRule); - } + function emitOptionalType(node: OptionalTypeNode) { + emit(node.type, parenthesizer.parenthesizeTypeOfOptionalType); + writePunctuation("?"); + } - function emitIndexedAccessType(node: IndexedAccessTypeNode) { - emit(node.objectType, parenthesizer.parenthesizeNonArrayTypeOfPostfixType); - writePunctuation("["); - emit(node.indexType); - writePunctuation("]"); - } + function emitUnionType(node: UnionTypeNode) { + emitList(node, node.types, ListFormat.UnionTypeConstituents, parenthesizer.parenthesizeConstituentTypeOfUnionType); + } - function emitMappedType(node: MappedTypeNode) { - const emitFlags = getEmitFlags(node); - writePunctuation("{"); - if (emitFlags & EmitFlags.SingleLine) { - writeSpace(); - } - else { - writeLine(); - increaseIndent(); - } - if (node.readonlyToken) { - emit(node.readonlyToken); - if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { - writeKeyword("readonly"); - } - writeSpace(); - } - writePunctuation("["); + function emitIntersectionType(node: IntersectionTypeNode) { + emitList(node, node.types, ListFormat.IntersectionTypeConstituents, parenthesizer.parenthesizeConstituentTypeOfIntersectionType); + } - pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter); - if (node.nameType) { - writeSpace(); - writeKeyword("as"); - writeSpace(); - emit(node.nameType); - } + function emitConditionalType(node: ConditionalTypeNode) { + emit(node.checkType, parenthesizer.parenthesizeCheckTypeOfConditionalType); + writeSpace(); + writeKeyword("extends"); + writeSpace(); + emit(node.extendsType, parenthesizer.parenthesizeExtendsTypeOfConditionalType); + writeSpace(); + writePunctuation("?"); + writeSpace(); + emit(node.trueType); + writeSpace(); + writePunctuation(":"); + writeSpace(); + emit(node.falseType); + } - writePunctuation("]"); - if (node.questionToken) { - emit(node.questionToken); - if (node.questionToken.kind !== SyntaxKind.QuestionToken) { - writePunctuation("?"); - } - } - writePunctuation(":"); - writeSpace(); - emit(node.type); - writeTrailingSemicolon(); - if (emitFlags & EmitFlags.SingleLine) { - writeSpace(); - } - else { - writeLine(); - decreaseIndent(); - } - emitList(node, node.members, ListFormat.PreserveLines); - writePunctuation("}"); - } + function emitInferType(node: InferTypeNode) { + writeKeyword("infer"); + writeSpace(); + emit(node.typeParameter); + } - function emitLiteralType(node: LiteralTypeNode) { - emitExpression(node.literal); - } + function emitParenthesizedType(node: ParenthesizedTypeNode) { + writePunctuation("("); + emit(node.type); + writePunctuation(")"); + } - function emitTemplateType(node: TemplateLiteralTypeNode) { - emit(node.head); - emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); - } + function emitThisType() { + writeKeyword("this"); + } - function emitImportTypeNode(node: ImportTypeNode) { - if (node.isTypeOf) { - writeKeyword("typeof"); - writeSpace(); - } - writeKeyword("import"); - writePunctuation("("); - emit(node.argument); - if (node.assertions) { - writePunctuation(","); - writeSpace(); - writePunctuation("{"); - writeSpace(); - writeKeyword("assert"); - writePunctuation(":"); - writeSpace(); - const elements = node.assertions.assertClause.elements; - emitList(node.assertions.assertClause, elements, ListFormat.ImportClauseEntries); - writeSpace(); - writePunctuation("}"); - } - writePunctuation(")"); - if (node.qualifier) { - writePunctuation("."); - emit(node.qualifier); - } - emitTypeArguments(node, node.typeArguments); - } + function emitTypeOperator(node: TypeOperatorNode) { + writeTokenText(node.operator, writeKeyword); + writeSpace(); - // - // Binding patterns - // + const parenthesizerRule = node.operator === SyntaxKind.ReadonlyKeyword ? + parenthesizer.parenthesizeOperandOfReadonlyTypeOperator : + parenthesizer.parenthesizeOperandOfTypeOperator; + emit(node.type, parenthesizerRule); + } - function emitObjectBindingPattern(node: ObjectBindingPattern) { - writePunctuation("{"); - emitList(node, node.elements, ListFormat.ObjectBindingPatternElements); - writePunctuation("}"); + function emitIndexedAccessType(node: IndexedAccessTypeNode) { + emit(node.objectType, parenthesizer.parenthesizeNonArrayTypeOfPostfixType); + writePunctuation("["); + emit(node.indexType); + writePunctuation("]"); + } + + function emitMappedType(node: MappedTypeNode) { + const emitFlags = getEmitFlags(node); + writePunctuation("{"); + if (emitFlags & EmitFlags.SingleLine) { + writeSpace(); + } + else { + writeLine(); + increaseIndent(); } + if (node.readonlyToken) { + emit(node.readonlyToken); + if (node.readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { + writeKeyword("readonly"); + } + writeSpace(); + } + writePunctuation("["); - function emitArrayBindingPattern(node: ArrayBindingPattern) { - writePunctuation("["); - emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); - writePunctuation("]"); + pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter); + if (node.nameType) { + writeSpace(); + writeKeyword("as"); + writeSpace(); + emit(node.nameType); } - function emitBindingElement(node: BindingElement) { - emit(node.dotDotDotToken); - if (node.propertyName) { - emit(node.propertyName); - writePunctuation(":"); - writeSpace(); + writePunctuation("]"); + if (node.questionToken) { + emit(node.questionToken); + if (node.questionToken.kind !== SyntaxKind.QuestionToken) { + writePunctuation("?"); } - emit(node.name); - emitInitializer(node.initializer, node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); } + writePunctuation(":"); + writeSpace(); + emit(node.type); + writeTrailingSemicolon(); + if (emitFlags & EmitFlags.SingleLine) { + writeSpace(); + } + else { + writeLine(); + decreaseIndent(); + } + emitList(node, node.members, ListFormat.PreserveLines); + writePunctuation("}"); + } - // - // Expressions - // + function emitLiteralType(node: LiteralTypeNode) { + emitExpression(node.literal); + } - function emitArrayLiteralExpression(node: ArrayLiteralExpression) { - const elements = node.elements; - const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; - emitExpressionList(node, elements, ListFormat.ArrayLiteralExpressionElements | preferNewLine, parenthesizer.parenthesizeExpressionForDisallowedComma); + function emitTemplateType(node: TemplateLiteralTypeNode) { + emit(node.head); + emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); + } + + function emitImportTypeNode(node: ImportTypeNode) { + if (node.isTypeOf) { + writeKeyword("typeof"); + writeSpace(); + } + writeKeyword("import"); + writePunctuation("("); + emit(node.argument); + if (node.assertions) { + writePunctuation(","); + writeSpace(); + writePunctuation("{"); + writeSpace(); + writeKeyword("assert"); + writePunctuation(":"); + writeSpace(); + const elements = node.assertions.assertClause.elements; + emitList(node.assertions.assertClause, elements, ListFormat.ImportClauseEntries); + writeSpace(); + writePunctuation("}"); } + writePunctuation(")"); + if (node.qualifier) { + writePunctuation("."); + emit(node.qualifier); + } + emitTypeArguments(node, node.typeArguments); + } - function emitObjectLiteralExpression(node: ObjectLiteralExpression) { - forEach(node.properties, generateMemberNames); + // + // Binding patterns + // - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } + function emitObjectBindingPattern(node: ObjectBindingPattern) { + writePunctuation("{"); + emitList(node, node.elements, ListFormat.ObjectBindingPatternElements); + writePunctuation("}"); + } - const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; - const allowTrailingComma = currentSourceFile && currentSourceFile.languageVersion >= ScriptTarget.ES5 && !isJsonSourceFile(currentSourceFile) ? ListFormat.AllowTrailingComma : ListFormat.None; - emitList(node, node.properties, ListFormat.ObjectLiteralExpressionProperties | allowTrailingComma | preferNewLine); + function emitArrayBindingPattern(node: ArrayBindingPattern) { + writePunctuation("["); + emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); + writePunctuation("]"); + } - if (indentedFlag) { - decreaseIndent(); - } + function emitBindingElement(node: BindingElement) { + emit(node.dotDotDotToken); + if (node.propertyName) { + emit(node.propertyName); + writePunctuation(":"); + writeSpace(); } + emit(node.name); + emitInitializer(node.initializer, node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - function emitPropertyAccessExpression(node: PropertyAccessExpression) { - emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); - const token = node.questionDotToken || setTextRangePosEnd(factory.createToken(SyntaxKind.DotToken) as DotToken, node.expression.end, node.name.pos); - const linesBeforeDot = getLinesBetweenNodes(node, node.expression, token); - const linesAfterDot = getLinesBetweenNodes(node, token, node.name); - - writeLinesAndIndent(linesBeforeDot, /*writeSpaceIfNotIndenting*/ false); + // + // Expressions + // - const shouldEmitDotDot = - token.kind !== SyntaxKind.QuestionDotToken && - mayNeedDotDotForPropertyAccess(node.expression) && - !writer.hasTrailingComment() && - !writer.hasTrailingWhitespace(); + function emitArrayLiteralExpression(node: ArrayLiteralExpression) { + const elements = node.elements; + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; + emitExpressionList(node, elements, ListFormat.ArrayLiteralExpressionElements | preferNewLine, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - if (shouldEmitDotDot) { - writePunctuation("."); - } + function emitObjectLiteralExpression(node: ObjectLiteralExpression) { + forEach(node.properties, generateMemberNames); - if (node.questionDotToken) { - emit(token); - } - else { - emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node); - } - writeLinesAndIndent(linesAfterDot, /*writeSpaceIfNotIndenting*/ false); - emit(node.name); - decreaseIndentIf(linesBeforeDot, linesAfterDot); - } - - // 1..toString is a valid property access, emit a dot after the literal - // Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal - function mayNeedDotDotForPropertyAccess(expression: Expression) { - expression = skipPartiallyEmittedExpressions(expression); - if (isNumericLiteral(expression)) { - // check if numeric literal is a decimal literal that was originally written with a dot - const text = getLiteralTextOfNode(expression as LiteralExpression, /*neverAsciiEscape*/ true, /*jsxAttributeEscape*/ false); - // If he number will be printed verbatim and it doesn't already contain a dot, add one - // if the expression doesn't have any comments that will be emitted. - return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!); - } - else if (isAccessExpression(expression)) { - // check if constant enum value is integer - const constantValue = getConstantValue(expression); - // isFinite handles cases when constantValue is undefined - return typeof constantValue === "number" && isFinite(constantValue) - && Math.floor(constantValue) === constantValue; - } + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); } - function emitElementAccessExpression(node: ElementAccessExpression) { - emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); - emit(node.questionDotToken); - emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node); - emitExpression(node.argumentExpression); - emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node); - } + const preferNewLine = node.multiLine ? ListFormat.PreferNewLine : ListFormat.None; + const allowTrailingComma = currentSourceFile && currentSourceFile.languageVersion >= ScriptTarget.ES5 && !isJsonSourceFile(currentSourceFile) ? ListFormat.AllowTrailingComma : ListFormat.None; + emitList(node, node.properties, ListFormat.ObjectLiteralExpressionProperties | allowTrailingComma | preferNewLine); - function emitCallExpression(node: CallExpression) { - const indirectCall = getEmitFlags(node) & EmitFlags.IndirectCall; - if (indirectCall) { - writePunctuation("("); - writeLiteral("0"); - writePunctuation(","); - writeSpace(); - } - emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); - if (indirectCall) { - writePunctuation(")"); - } - emit(node.questionDotToken); - emitTypeArguments(node, node.typeArguments); - emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments, parenthesizer.parenthesizeExpressionForDisallowedComma); + if (indentedFlag) { + decreaseIndent(); } + } - function emitNewExpression(node: NewExpression) { - emitTokenWithComment(SyntaxKind.NewKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfNew); - emitTypeArguments(node, node.typeArguments); - emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments, parenthesizer.parenthesizeExpressionForDisallowedComma); - } + function emitPropertyAccessExpression(node: PropertyAccessExpression) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + const token = node.questionDotToken || setTextRangePosEnd(factory.createToken(SyntaxKind.DotToken) as DotToken, node.expression.end, node.name.pos); + const linesBeforeDot = getLinesBetweenNodes(node, node.expression, token); + const linesAfterDot = getLinesBetweenNodes(node, token, node.name); - function emitTaggedTemplateExpression(node: TaggedTemplateExpression) { - const indirectCall = getEmitFlags(node) & EmitFlags.IndirectCall; - if (indirectCall) { - writePunctuation("("); - writeLiteral("0"); - writePunctuation(","); - writeSpace(); - } - emitExpression(node.tag, parenthesizer.parenthesizeLeftSideOfAccess); - if (indirectCall) { - writePunctuation(")"); - } - emitTypeArguments(node, node.typeArguments); - writeSpace(); - emitExpression(node.template); - } + writeLinesAndIndent(linesBeforeDot, /*writeSpaceIfNotIndenting*/ false); - function emitTypeAssertionExpression(node: TypeAssertion) { - writePunctuation("<"); - emit(node.type); - writePunctuation(">"); - emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); - } + const shouldEmitDotDot = + token.kind !== SyntaxKind.QuestionDotToken && + mayNeedDotDotForPropertyAccess(node.expression) && + !writer.hasTrailingComment() && + !writer.hasTrailingWhitespace(); - function emitParenthesizedExpression(node: ParenthesizedExpression) { - const openParenPos = emitTokenWithComment(SyntaxKind.OpenParenToken, node.pos, writePunctuation, node); - const indented = writeLineSeparatorsAndIndentBefore(node.expression, node); - emitExpression(node.expression, /*parenthesizerRules*/ undefined); - writeLineSeparatorsAfter(node.expression, node); - decreaseIndentIf(indented); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression ? node.expression.end : openParenPos, writePunctuation, node); + if (shouldEmitDotDot) { + writePunctuation("."); } - function emitFunctionExpression(node: FunctionExpression) { - generateNameIfNeeded(node.name); - emitFunctionDeclarationOrExpression(node); + if (node.questionDotToken) { + emit(token); } - - function emitArrowFunction(node: ArrowFunction) { - emitModifiers(node, node.modifiers); - emitSignatureAndBody(node, emitArrowFunctionHead); + else { + emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node); } + writeLinesAndIndent(linesAfterDot, /*writeSpaceIfNotIndenting*/ false); + emit(node.name); + decreaseIndentIf(linesBeforeDot, linesAfterDot); + } - function emitArrowFunctionHead(node: ArrowFunction) { - emitTypeParameters(node, node.typeParameters); - emitParametersForArrow(node, node.parameters); - emitTypeAnnotation(node.type); - writeSpace(); - emit(node.equalsGreaterThanToken); + // 1..toString is a valid property access, emit a dot after the literal + // Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal + function mayNeedDotDotForPropertyAccess(expression: Expression) { + expression = skipPartiallyEmittedExpressions(expression); + if (isNumericLiteral(expression)) { + // check if numeric literal is a decimal literal that was originally written with a dot + const text = getLiteralTextOfNode(expression as LiteralExpression, /*neverAsciiEscape*/ true, /*jsxAttributeEscape*/ false); + // If he number will be printed verbatim and it doesn't already contain a dot, add one + // if the expression doesn't have any comments that will be emitted. + return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!); + } + else if (isAccessExpression(expression)) { + // check if constant enum value is integer + const constantValue = getConstantValue(expression); + // isFinite handles cases when constantValue is undefined + return typeof constantValue === "number" && isFinite(constantValue) + && Math.floor(constantValue) === constantValue; } + } - function emitDeleteExpression(node: DeleteExpression) { - emitTokenWithComment(SyntaxKind.DeleteKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); - } + function emitElementAccessExpression(node: ElementAccessExpression) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + emit(node.questionDotToken); + emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node); + emitExpression(node.argumentExpression); + emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node); + } - function emitTypeOfExpression(node: TypeOfExpression) { - emitTokenWithComment(SyntaxKind.TypeOfKeyword, node.pos, writeKeyword, node); + function emitCallExpression(node: CallExpression) { + const indirectCall = getEmitFlags(node) & EmitFlags.IndirectCall; + if (indirectCall) { + writePunctuation("("); + writeLiteral("0"); + writePunctuation(","); writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); } - - function emitVoidExpression(node: VoidExpression) { - emitTokenWithComment(SyntaxKind.VoidKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + if (indirectCall) { + writePunctuation(")"); } + emit(node.questionDotToken); + emitTypeArguments(node, node.typeArguments); + emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments, parenthesizer.parenthesizeExpressionForDisallowedComma); + } + + function emitNewExpression(node: NewExpression) { + emitTokenWithComment(SyntaxKind.NewKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfNew); + emitTypeArguments(node, node.typeArguments); + emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - function emitAwaitExpression(node: AwaitExpression) { - emitTokenWithComment(SyntaxKind.AwaitKeyword, node.pos, writeKeyword, node); + function emitTaggedTemplateExpression(node: TaggedTemplateExpression) { + const indirectCall = getEmitFlags(node) & EmitFlags.IndirectCall; + if (indirectCall) { + writePunctuation("("); + writeLiteral("0"); + writePunctuation(","); writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); } - - function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { - writeTokenText(node.operator, writeOperator); - if (shouldEmitWhitespaceBeforeOperand(node)) { - writeSpace(); - } - emitExpression(node.operand, parenthesizer.parenthesizeOperandOfPrefixUnary); + emitExpression(node.tag, parenthesizer.parenthesizeLeftSideOfAccess); + if (indirectCall) { + writePunctuation(")"); } + emitTypeArguments(node, node.typeArguments); + writeSpace(); + emitExpression(node.template); + } - function shouldEmitWhitespaceBeforeOperand(node: PrefixUnaryExpression) { - // In some cases, we need to emit a space between the operator and the operand. One obvious case - // is when the operator is an identifier, like delete or typeof. We also need to do this for plus - // and minus expressions in certain cases. Specifically, consider the following two cases (parens - // are just for clarity of exposition, and not part of the source code): - // - // (+(+1)) - // (+(++1)) - // - // We need to emit a space in both cases. In the first case, the absence of a space will make - // the resulting expression a prefix increment operation. And in the second, it will make the resulting - // expression a prefix increment whose operand is a plus expression - (++(+x)) - // The same is true of minus of course. - const operand = node.operand; - return operand.kind === SyntaxKind.PrefixUnaryExpression - && ((node.operator === SyntaxKind.PlusToken && ((operand as PrefixUnaryExpression).operator === SyntaxKind.PlusToken || (operand as PrefixUnaryExpression).operator === SyntaxKind.PlusPlusToken)) - || (node.operator === SyntaxKind.MinusToken && ((operand as PrefixUnaryExpression).operator === SyntaxKind.MinusToken || (operand as PrefixUnaryExpression).operator === SyntaxKind.MinusMinusToken))); - } - - function emitPostfixUnaryExpression(node: PostfixUnaryExpression) { - emitExpression(node.operand, parenthesizer.parenthesizeOperandOfPostfixUnary); - writeTokenText(node.operator, writeOperator); - } - - function createEmitBinaryExpression() { - interface WorkArea { - stackIndex: number; - preserveSourceNewlinesStack: (boolean | undefined)[]; - containerPosStack: number[]; - containerEndStack: number[]; - declarationListContainerEndStack: number[]; - shouldEmitCommentsStack: boolean[]; - shouldEmitSourceMapsStack: boolean[]; - } + function emitTypeAssertionExpression(node: TypeAssertion) { + writePunctuation("<"); + emit(node.type); + writePunctuation(">"); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } - return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined); - - function onEnter(node: BinaryExpression, state: WorkArea | undefined) { - if (state) { - state.stackIndex++; - state.preserveSourceNewlinesStack[state.stackIndex] = preserveSourceNewlines; - state.containerPosStack[state.stackIndex] = containerPos; - state.containerEndStack[state.stackIndex] = containerEnd; - state.declarationListContainerEndStack[state.stackIndex] = declarationListContainerEnd; - const emitComments = state.shouldEmitCommentsStack[state.stackIndex] = shouldEmitComments(node); - const emitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex] = shouldEmitSourceMaps(node); - onBeforeEmitNode?.(node); - if (emitComments) emitCommentsBeforeNode(node); - if (emitSourceMaps) emitSourceMapsBeforeNode(node); - beforeEmitNode(node); - } - else { - state = { - stackIndex: 0, - preserveSourceNewlinesStack: [undefined], - containerPosStack: [-1], - containerEndStack: [-1], - declarationListContainerEndStack: [-1], - shouldEmitCommentsStack: [false], - shouldEmitSourceMapsStack: [false], - }; - } - return state; - } + function emitParenthesizedExpression(node: ParenthesizedExpression) { + const openParenPos = emitTokenWithComment(SyntaxKind.OpenParenToken, node.pos, writePunctuation, node); + const indented = writeLineSeparatorsAndIndentBefore(node.expression, node); + emitExpression(node.expression, /*parenthesizerRules*/ undefined); + writeLineSeparatorsAfter(node.expression, node); + decreaseIndentIf(indented); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression ? node.expression.end : openParenPos, writePunctuation, node); + } - function onLeft(next: Expression, _workArea: WorkArea, parent: BinaryExpression) { - return maybeEmitExpression(next, parent, "left"); - } + function emitFunctionExpression(node: FunctionExpression) { + generateNameIfNeeded(node.name); + emitFunctionDeclarationOrExpression(node); + } - function onOperator(operatorToken: BinaryOperatorToken, _state: WorkArea, node: BinaryExpression) { - const isCommaOperator = operatorToken.kind !== SyntaxKind.CommaToken; - const linesBeforeOperator = getLinesBetweenNodes(node, node.left, operatorToken); - const linesAfterOperator = getLinesBetweenNodes(node, operatorToken, node.right); - writeLinesAndIndent(linesBeforeOperator, isCommaOperator); - emitLeadingCommentsOfPosition(operatorToken.pos); - writeTokenNode(operatorToken, operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator); - emitTrailingCommentsOfPosition(operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts - writeLinesAndIndent(linesAfterOperator, /*writeSpaceIfNotIndenting*/ true); - } + function emitArrowFunction(node: ArrowFunction) { + emitModifiers(node, node.modifiers); + emitSignatureAndBody(node, emitArrowFunctionHead); + } - function onRight(next: Expression, _workArea: WorkArea, parent: BinaryExpression) { - return maybeEmitExpression(next, parent, "right"); - } + function emitArrowFunctionHead(node: ArrowFunction) { + emitTypeParameters(node, node.typeParameters); + emitParametersForArrow(node, node.parameters); + emitTypeAnnotation(node.type); + writeSpace(); + emit(node.equalsGreaterThanToken); + } - function onExit(node: BinaryExpression, state: WorkArea) { - const linesBeforeOperator = getLinesBetweenNodes(node, node.left, node.operatorToken); - const linesAfterOperator = getLinesBetweenNodes(node, node.operatorToken, node.right); - decreaseIndentIf(linesBeforeOperator, linesAfterOperator); - if (state.stackIndex > 0) { - const savedPreserveSourceNewlines = state.preserveSourceNewlinesStack[state.stackIndex]; - const savedContainerPos = state.containerPosStack[state.stackIndex]; - const savedContainerEnd = state.containerEndStack[state.stackIndex]; - const savedDeclarationListContainerEnd = state.declarationListContainerEndStack[state.stackIndex]; - const shouldEmitComments = state.shouldEmitCommentsStack[state.stackIndex]; - const shouldEmitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex]; - afterEmitNode(savedPreserveSourceNewlines); - if (shouldEmitSourceMaps) emitSourceMapsAfterNode(node); - if (shouldEmitComments) emitCommentsAfterNode(node, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); - onAfterEmitNode?.(node); - state.stackIndex--; - } - } + function emitDeleteExpression(node: DeleteExpression) { + emitTokenWithComment(SyntaxKind.DeleteKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } - function maybeEmitExpression(next: Expression, parent: BinaryExpression, side: "left" | "right") { - const parenthesizerRule = side === "left" ? - parenthesizer.getParenthesizeLeftSideOfBinaryForOperator(parent.operatorToken.kind) : - parenthesizer.getParenthesizeRightSideOfBinaryForOperator(parent.operatorToken.kind); - - let pipelinePhase = getPipelinePhase(PipelinePhase.Notification, EmitHint.Expression, next); - if (pipelinePhase === pipelineEmitWithSubstitution) { - Debug.assertIsDefined(lastSubstitution); - next = parenthesizerRule(cast(lastSubstitution, isExpression)); - pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, EmitHint.Expression, next); - lastSubstitution = undefined; - } + function emitTypeOfExpression(node: TypeOfExpression) { + emitTokenWithComment(SyntaxKind.TypeOfKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } - if (pipelinePhase === pipelineEmitWithComments || - pipelinePhase === pipelineEmitWithSourceMaps || - pipelinePhase === pipelineEmitWithHint) { - if (isBinaryExpression(next)) { - return next; - } - } + function emitVoidExpression(node: VoidExpression) { + emitTokenWithComment(SyntaxKind.VoidKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } - currentParenthesizerRule = parenthesizerRule; - pipelinePhase(EmitHint.Expression, next); - } + function emitAwaitExpression(node: AwaitExpression) { + emitTokenWithComment(SyntaxKind.AwaitKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeOperandOfPrefixUnary); + } + + function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { + writeTokenText(node.operator, writeOperator); + if (shouldEmitWhitespaceBeforeOperand(node)) { + writeSpace(); } + emitExpression(node.operand, parenthesizer.parenthesizeOperandOfPrefixUnary); + } - function emitConditionalExpression(node: ConditionalExpression) { - const linesBeforeQuestion = getLinesBetweenNodes(node, node.condition, node.questionToken); - const linesAfterQuestion = getLinesBetweenNodes(node, node.questionToken, node.whenTrue); - const linesBeforeColon = getLinesBetweenNodes(node, node.whenTrue, node.colonToken); - const linesAfterColon = getLinesBetweenNodes(node, node.colonToken, node.whenFalse); + function shouldEmitWhitespaceBeforeOperand(node: PrefixUnaryExpression) { + // In some cases, we need to emit a space between the operator and the operand. One obvious case + // is when the operator is an identifier, like delete or typeof. We also need to do this for plus + // and minus expressions in certain cases. Specifically, consider the following two cases (parens + // are just for clarity of exposition, and not part of the source code): + // + // (+(+1)) + // (+(++1)) + // + // We need to emit a space in both cases. In the first case, the absence of a space will make + // the resulting expression a prefix increment operation. And in the second, it will make the resulting + // expression a prefix increment whose operand is a plus expression - (++(+x)) + // The same is true of minus of course. + const operand = node.operand; + return operand.kind === SyntaxKind.PrefixUnaryExpression + && ((node.operator === SyntaxKind.PlusToken && ((operand as PrefixUnaryExpression).operator === SyntaxKind.PlusToken || (operand as PrefixUnaryExpression).operator === SyntaxKind.PlusPlusToken)) + || (node.operator === SyntaxKind.MinusToken && ((operand as PrefixUnaryExpression).operator === SyntaxKind.MinusToken || (operand as PrefixUnaryExpression).operator === SyntaxKind.MinusMinusToken))); + } - emitExpression(node.condition, parenthesizer.parenthesizeConditionOfConditionalExpression); - writeLinesAndIndent(linesBeforeQuestion, /*writeSpaceIfNotIndenting*/ true); - emit(node.questionToken); - writeLinesAndIndent(linesAfterQuestion, /*writeSpaceIfNotIndenting*/ true); - emitExpression(node.whenTrue, parenthesizer.parenthesizeBranchOfConditionalExpression); - decreaseIndentIf(linesBeforeQuestion, linesAfterQuestion); + function emitPostfixUnaryExpression(node: PostfixUnaryExpression) { + emitExpression(node.operand, parenthesizer.parenthesizeOperandOfPostfixUnary); + writeTokenText(node.operator, writeOperator); + } - writeLinesAndIndent(linesBeforeColon, /*writeSpaceIfNotIndenting*/ true); - emit(node.colonToken); - writeLinesAndIndent(linesAfterColon, /*writeSpaceIfNotIndenting*/ true); - emitExpression(node.whenFalse, parenthesizer.parenthesizeBranchOfConditionalExpression); - decreaseIndentIf(linesBeforeColon, linesAfterColon); + function createEmitBinaryExpression() { + interface WorkArea { + stackIndex: number; + preserveSourceNewlinesStack: (boolean | undefined)[]; + containerPosStack: number[]; + containerEndStack: number[]; + declarationListContainerEndStack: number[]; + shouldEmitCommentsStack: boolean[]; + shouldEmitSourceMapsStack: boolean[]; + } + + return createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, /*foldState*/ undefined); + + function onEnter(node: BinaryExpression, state: WorkArea | undefined) { + if (state) { + state.stackIndex++; + state.preserveSourceNewlinesStack[state.stackIndex] = preserveSourceNewlines; + state.containerPosStack[state.stackIndex] = containerPos; + state.containerEndStack[state.stackIndex] = containerEnd; + state.declarationListContainerEndStack[state.stackIndex] = declarationListContainerEnd; + const emitComments = state.shouldEmitCommentsStack[state.stackIndex] = shouldEmitComments(node); + const emitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex] = shouldEmitSourceMaps(node); + onBeforeEmitNode?.(node); + if (emitComments) emitCommentsBeforeNode(node); + if (emitSourceMaps) emitSourceMapsBeforeNode(node); + beforeEmitNode(node); + } + else { + state = { + stackIndex: 0, + preserveSourceNewlinesStack: [undefined], + containerPosStack: [-1], + containerEndStack: [-1], + declarationListContainerEndStack: [-1], + shouldEmitCommentsStack: [false], + shouldEmitSourceMapsStack: [false], + }; + } + return state; } - function emitTemplateExpression(node: TemplateExpression) { - emit(node.head); - emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); + function onLeft(next: Expression, _workArea: WorkArea, parent: BinaryExpression) { + return maybeEmitExpression(next, parent, "left"); } - function emitYieldExpression(node: YieldExpression) { - emitTokenWithComment(SyntaxKind.YieldKeyword, node.pos, writeKeyword, node); - emit(node.asteriskToken); - emitExpressionWithLeadingSpace(node.expression && parenthesizeExpressionForNoAsi(node.expression), parenthesizeExpressionForNoAsiAndDisallowedComma); + function onOperator(operatorToken: BinaryOperatorToken, _state: WorkArea, node: BinaryExpression) { + const isCommaOperator = operatorToken.kind !== SyntaxKind.CommaToken; + const linesBeforeOperator = getLinesBetweenNodes(node, node.left, operatorToken); + const linesAfterOperator = getLinesBetweenNodes(node, operatorToken, node.right); + writeLinesAndIndent(linesBeforeOperator, isCommaOperator); + emitLeadingCommentsOfPosition(operatorToken.pos); + writeTokenNode(operatorToken, operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator); + emitTrailingCommentsOfPosition(operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts + writeLinesAndIndent(linesAfterOperator, /*writeSpaceIfNotIndenting*/ true); } - function emitSpreadElement(node: SpreadElement) { - emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); - emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); + function onRight(next: Expression, _workArea: WorkArea, parent: BinaryExpression) { + return maybeEmitExpression(next, parent, "right"); } - function emitClassExpression(node: ClassExpression) { - generateNameIfNeeded(node.name); - emitClassDeclarationOrExpression(node); + function onExit(node: BinaryExpression, state: WorkArea) { + const linesBeforeOperator = getLinesBetweenNodes(node, node.left, node.operatorToken); + const linesAfterOperator = getLinesBetweenNodes(node, node.operatorToken, node.right); + decreaseIndentIf(linesBeforeOperator, linesAfterOperator); + if (state.stackIndex > 0) { + const savedPreserveSourceNewlines = state.preserveSourceNewlinesStack[state.stackIndex]; + const savedContainerPos = state.containerPosStack[state.stackIndex]; + const savedContainerEnd = state.containerEndStack[state.stackIndex]; + const savedDeclarationListContainerEnd = state.declarationListContainerEndStack[state.stackIndex]; + const shouldEmitComments = state.shouldEmitCommentsStack[state.stackIndex]; + const shouldEmitSourceMaps = state.shouldEmitSourceMapsStack[state.stackIndex]; + afterEmitNode(savedPreserveSourceNewlines); + if (shouldEmitSourceMaps) emitSourceMapsAfterNode(node); + if (shouldEmitComments) emitCommentsAfterNode(node, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); + onAfterEmitNode?.(node); + state.stackIndex--; + } } - function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { - emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); - emitTypeArguments(node, node.typeArguments); - } + function maybeEmitExpression(next: Expression, parent: BinaryExpression, side: "left" | "right") { + const parenthesizerRule = side === "left" ? + parenthesizer.getParenthesizeLeftSideOfBinaryForOperator(parent.operatorToken.kind) : + parenthesizer.getParenthesizeRightSideOfBinaryForOperator(parent.operatorToken.kind); - function emitAsExpression(node: AsExpression) { - emitExpression(node.expression, /*parenthesizerRules*/ undefined); - if (node.type) { - writeSpace(); - writeKeyword("as"); - writeSpace(); - emit(node.type); + let pipelinePhase = getPipelinePhase(PipelinePhase.Notification, EmitHint.Expression, next); + if (pipelinePhase === pipelineEmitWithSubstitution) { + Debug.assertIsDefined(lastSubstitution); + next = parenthesizerRule(cast(lastSubstitution, isExpression)); + pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, EmitHint.Expression, next); + lastSubstitution = undefined; } - } - function emitNonNullExpression(node: NonNullExpression) { - emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); - writeOperator("!"); - } - - function emitSatisfiesExpression(node: SatisfiesExpression) { - emitExpression(node.expression, /*parenthesizerRules*/ undefined); - if (node.type) { - writeSpace(); - writeKeyword("satisfies"); - writeSpace(); - emit(node.type); + if (pipelinePhase === pipelineEmitWithComments || + pipelinePhase === pipelineEmitWithSourceMaps || + pipelinePhase === pipelineEmitWithHint) { + if (isBinaryExpression(next)) { + return next; + } } - } - function emitMetaProperty(node: MetaProperty) { - writeToken(node.keywordToken, node.pos, writePunctuation); - writePunctuation("."); - emit(node.name); + currentParenthesizerRule = parenthesizerRule; + pipelinePhase(EmitHint.Expression, next); } + } - // - // Misc - // + function emitConditionalExpression(node: ConditionalExpression) { + const linesBeforeQuestion = getLinesBetweenNodes(node, node.condition, node.questionToken); + const linesAfterQuestion = getLinesBetweenNodes(node, node.questionToken, node.whenTrue); + const linesBeforeColon = getLinesBetweenNodes(node, node.whenTrue, node.colonToken); + const linesAfterColon = getLinesBetweenNodes(node, node.colonToken, node.whenFalse); + + emitExpression(node.condition, parenthesizer.parenthesizeConditionOfConditionalExpression); + writeLinesAndIndent(linesBeforeQuestion, /*writeSpaceIfNotIndenting*/ true); + emit(node.questionToken); + writeLinesAndIndent(linesAfterQuestion, /*writeSpaceIfNotIndenting*/ true); + emitExpression(node.whenTrue, parenthesizer.parenthesizeBranchOfConditionalExpression); + decreaseIndentIf(linesBeforeQuestion, linesAfterQuestion); + + writeLinesAndIndent(linesBeforeColon, /*writeSpaceIfNotIndenting*/ true); + emit(node.colonToken); + writeLinesAndIndent(linesAfterColon, /*writeSpaceIfNotIndenting*/ true); + emitExpression(node.whenFalse, parenthesizer.parenthesizeBranchOfConditionalExpression); + decreaseIndentIf(linesBeforeColon, linesAfterColon); + } - function emitTemplateSpan(node: TemplateSpan) { - emitExpression(node.expression); - emit(node.literal); + function emitTemplateExpression(node: TemplateExpression) { + emit(node.head); + emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans); + } + + function emitYieldExpression(node: YieldExpression) { + emitTokenWithComment(SyntaxKind.YieldKeyword, node.pos, writeKeyword, node); + emit(node.asteriskToken); + emitExpressionWithLeadingSpace(node.expression && parenthesizeExpressionForNoAsi(node.expression), parenthesizeExpressionForNoAsiAndDisallowedComma); + } + + function emitSpreadElement(node: SpreadElement) { + emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); + } + + function emitClassExpression(node: ClassExpression) { + generateNameIfNeeded(node.name); + emitClassDeclarationOrExpression(node); + } + + function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + emitTypeArguments(node, node.typeArguments); + } + + function emitAsExpression(node: AsExpression) { + emitExpression(node.expression, /*parenthesizerRules*/ undefined); + if (node.type) { + writeSpace(); + writeKeyword("as"); + writeSpace(); + emit(node.type); } + } - // - // Statements - // + function emitNonNullExpression(node: NonNullExpression) { + emitExpression(node.expression, parenthesizer.parenthesizeLeftSideOfAccess); + writeOperator("!"); + } - function emitBlock(node: Block) { - emitBlockStatements(node, /*forceSingleLine*/ !node.multiLine && isEmptyBlock(node)); + function emitSatisfiesExpression(node: SatisfiesExpression) { + emitExpression(node.expression, /*parenthesizerRules*/ undefined); + if (node.type) { + writeSpace(); + writeKeyword("satisfies"); + writeSpace(); + emit(node.type); } + } + + function emitMetaProperty(node: MetaProperty) { + writeToken(node.keywordToken, node.pos, writePunctuation); + writePunctuation("."); + emit(node.name); + } + + // + // Misc + // + + function emitTemplateSpan(node: TemplateSpan) { + emitExpression(node.expression); + emit(node.literal); + } + + // + // Statements + // - function emitBlockStatements(node: BlockLike, forceSingleLine: boolean) { - emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, /*contextNode*/ node); - const format = forceSingleLine || getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineBlockStatements : ListFormat.MultiLineBlockStatements; - emitList(node, node.statements, format); - emitTokenWithComment(SyntaxKind.CloseBraceToken, node.statements.end, writePunctuation, /*contextNode*/ node, /*indentLeading*/ !!(format & ListFormat.MultiLine)); + function emitBlock(node: Block) { + emitBlockStatements(node, /*forceSingleLine*/ !node.multiLine && isEmptyBlock(node)); + } + + function emitBlockStatements(node: BlockLike, forceSingleLine: boolean) { + emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, /*contextNode*/ node); + const format = forceSingleLine || getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineBlockStatements : ListFormat.MultiLineBlockStatements; + emitList(node, node.statements, format); + emitTokenWithComment(SyntaxKind.CloseBraceToken, node.statements.end, writePunctuation, /*contextNode*/ node, /*indentLeading*/ !!(format & ListFormat.MultiLine)); + } + + function emitVariableStatement(node: VariableStatement) { + emitModifiers(node, node.modifiers); + emit(node.declarationList); + writeTrailingSemicolon(); + } + + function emitEmptyStatement(isEmbeddedStatement: boolean) { + // While most trailing semicolons are possibly insignificant, an embedded "empty" + // statement is significant and cannot be elided by a trailing-semicolon-omitting writer. + if (isEmbeddedStatement) { + writePunctuation(";"); + } + else { + writeTrailingSemicolon(); } + } - function emitVariableStatement(node: VariableStatement) { - emitModifiers(node, node.modifiers); - emit(node.declarationList); + function emitExpressionStatement(node: ExpressionStatement) { + emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfExpressionStatement); + // Emit semicolon in non json files + // or if json file that created synthesized expression(eg.define expression statement when --out and amd code generation) + if (!currentSourceFile || !isJsonSourceFile(currentSourceFile) || nodeIsSynthesized(node.expression)) { writeTrailingSemicolon(); } + } - function emitEmptyStatement(isEmbeddedStatement: boolean) { - // While most trailing semicolons are possibly insignificant, an embedded "empty" - // statement is significant and cannot be elided by a trailing-semicolon-omitting writer. - if (isEmbeddedStatement) { - writePunctuation(";"); + function emitIfStatement(node: IfStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.IfKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.thenStatement); + if (node.elseStatement) { + writeLineOrSpace(node, node.thenStatement, node.elseStatement); + emitTokenWithComment(SyntaxKind.ElseKeyword, node.thenStatement.end, writeKeyword, node); + if (node.elseStatement.kind === SyntaxKind.IfStatement) { + writeSpace(); + emit(node.elseStatement); } else { - writeTrailingSemicolon(); + emitEmbeddedStatement(node, node.elseStatement); } } + } - function emitExpressionStatement(node: ExpressionStatement) { - emitExpression(node.expression, parenthesizer.parenthesizeExpressionOfExpressionStatement); - // Emit semicolon in non json files - // or if json file that created synthesized expression(eg.define expression statement when --out and amd code generation) - if (!currentSourceFile || !isJsonSourceFile(currentSourceFile) || nodeIsSynthesized(node.expression)) { - writeTrailingSemicolon(); - } - } + function emitWhileClause(node: WhileStatement | DoStatement, startPos: number) { + const openParenPos = emitTokenWithComment(SyntaxKind.WhileKeyword, startPos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + } - function emitIfStatement(node: IfStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.IfKeyword, node.pos, writeKeyword, node); + function emitDoStatement(node: DoStatement) { + emitTokenWithComment(SyntaxKind.DoKeyword, node.pos, writeKeyword, node); + emitEmbeddedStatement(node, node.statement); + if (isBlock(node.statement) && !preserveSourceNewlines) { writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.thenStatement); - if (node.elseStatement) { - writeLineOrSpace(node, node.thenStatement, node.elseStatement); - emitTokenWithComment(SyntaxKind.ElseKeyword, node.thenStatement.end, writeKeyword, node); - if (node.elseStatement.kind === SyntaxKind.IfStatement) { - writeSpace(); - emit(node.elseStatement); - } - else { - emitEmbeddedStatement(node, node.elseStatement); - } - } } - - function emitWhileClause(node: WhileStatement | DoStatement, startPos: number) { - const openParenPos = emitTokenWithComment(SyntaxKind.WhileKeyword, startPos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + else { + writeLineOrSpace(node, node.statement, node.expression); } - function emitDoStatement(node: DoStatement) { - emitTokenWithComment(SyntaxKind.DoKeyword, node.pos, writeKeyword, node); - emitEmbeddedStatement(node, node.statement); - if (isBlock(node.statement) && !preserveSourceNewlines) { - writeSpace(); + emitWhileClause(node, node.statement.end); + writeTrailingSemicolon(); + } + + function emitWhileStatement(node: WhileStatement) { + emitWhileClause(node, node.pos); + emitEmbeddedStatement(node, node.statement); + } + + function emitForStatement(node: ForStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + let pos = emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, /*contextNode*/ node); + emitForBinding(node.initializer); + pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.initializer ? node.initializer.end : pos, writePunctuation, node); + emitExpressionWithLeadingSpace(node.condition); + pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.condition ? node.condition.end : pos, writePunctuation, node); + emitExpressionWithLeadingSpace(node.incrementor); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.incrementor ? node.incrementor.end : pos, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } + + function emitForInStatement(node: ForInStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitForBinding(node.initializer); + writeSpace(); + emitTokenWithComment(SyntaxKind.InKeyword, node.initializer.end, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } + + function emitForOfStatement(node: ForOfStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitWithTrailingSpace(node.awaitModifier); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitForBinding(node.initializer); + writeSpace(); + emitTokenWithComment(SyntaxKind.OfKeyword, node.initializer.end, writeKeyword, node); + writeSpace(); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } + + function emitForBinding(node: VariableDeclarationList | Expression | undefined) { + if (node !== undefined) { + if (node.kind === SyntaxKind.VariableDeclarationList) { + emit(node); } else { - writeLineOrSpace(node, node.statement, node.expression); + emitExpression(node); } - - emitWhileClause(node, node.statement.end); - writeTrailingSemicolon(); } + } - function emitWhileStatement(node: WhileStatement) { - emitWhileClause(node, node.pos); - emitEmbeddedStatement(node, node.statement); - } + function emitContinueStatement(node: ContinueStatement) { + emitTokenWithComment(SyntaxKind.ContinueKeyword, node.pos, writeKeyword, node); + emitWithLeadingSpace(node.label); + writeTrailingSemicolon(); + } - function emitForStatement(node: ForStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - let pos = emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, /*contextNode*/ node); - emitForBinding(node.initializer); - pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.initializer ? node.initializer.end : pos, writePunctuation, node); - emitExpressionWithLeadingSpace(node.condition); - pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.condition ? node.condition.end : pos, writePunctuation, node); - emitExpressionWithLeadingSpace(node.incrementor); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.incrementor ? node.incrementor.end : pos, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } - - function emitForInStatement(node: ForInStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitForBinding(node.initializer); - writeSpace(); - emitTokenWithComment(SyntaxKind.InKeyword, node.initializer.end, writeKeyword, node); - writeSpace(); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } + function emitBreakStatement(node: BreakStatement) { + emitTokenWithComment(SyntaxKind.BreakKeyword, node.pos, writeKeyword, node); + emitWithLeadingSpace(node.label); + writeTrailingSemicolon(); + } - function emitForOfStatement(node: ForOfStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.ForKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitWithTrailingSpace(node.awaitModifier); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitForBinding(node.initializer); - writeSpace(); - emitTokenWithComment(SyntaxKind.OfKeyword, node.initializer.end, writeKeyword, node); - writeSpace(); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); + function emitTokenWithComment(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode: Node, indentLeading?: boolean) { + const node = getParseTreeNode(contextNode); + const isSimilarNode = node && node.kind === contextNode.kind; + const startPos = pos; + if (isSimilarNode && currentSourceFile) { + pos = skipTrivia(currentSourceFile.text, pos); } - - function emitForBinding(node: VariableDeclarationList | Expression | undefined) { - if (node !== undefined) { - if (node.kind === SyntaxKind.VariableDeclarationList) { - emit(node); - } - else { - emitExpression(node); - } + if (isSimilarNode && contextNode.pos !== startPos) { + const needsIndent = indentLeading && currentSourceFile && !positionsAreOnSameLine(startPos, pos, currentSourceFile); + if (needsIndent) { + increaseIndent(); + } + emitLeadingCommentsOfPosition(startPos); + if (needsIndent) { + decreaseIndent(); } } - - function emitContinueStatement(node: ContinueStatement) { - emitTokenWithComment(SyntaxKind.ContinueKeyword, node.pos, writeKeyword, node); - emitWithLeadingSpace(node.label); - writeTrailingSemicolon(); + pos = writeTokenText(token, writer, pos); + if (isSimilarNode && contextNode.end !== pos) { + const isJsxExprContext = contextNode.kind === SyntaxKind.JsxExpression; + emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ !isJsxExprContext, /*forceNoNewline*/ isJsxExprContext); } + return pos; + } - function emitBreakStatement(node: BreakStatement) { - emitTokenWithComment(SyntaxKind.BreakKeyword, node.pos, writeKeyword, node); - emitWithLeadingSpace(node.label); - writeTrailingSemicolon(); - } + function commentWillEmitNewLine(node: CommentRange) { + return node.kind === SyntaxKind.SingleLineCommentTrivia || !!node.hasTrailingNewLine; + } - function emitTokenWithComment(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode: Node, indentLeading?: boolean) { - const node = getParseTreeNode(contextNode); - const isSimilarNode = node && node.kind === contextNode.kind; - const startPos = pos; - if (isSimilarNode && currentSourceFile) { - pos = skipTrivia(currentSourceFile.text, pos); - } - if (isSimilarNode && contextNode.pos !== startPos) { - const needsIndent = indentLeading && currentSourceFile && !positionsAreOnSameLine(startPos, pos, currentSourceFile); - if (needsIndent) { - increaseIndent(); - } - emitLeadingCommentsOfPosition(startPos); - if (needsIndent) { - decreaseIndent(); - } + function willEmitLeadingNewLine(node: Expression): boolean { + if (!currentSourceFile) return false; + if (some(getLeadingCommentRanges(currentSourceFile.text, node.pos), commentWillEmitNewLine)) return true; + if (some(getSyntheticLeadingComments(node), commentWillEmitNewLine)) return true; + if (isPartiallyEmittedExpression(node)) { + if (node.pos !== node.expression.pos) { + if (some(getTrailingCommentRanges(currentSourceFile.text, node.expression.pos), commentWillEmitNewLine)) return true; } - pos = writeTokenText(token, writer, pos); - if (isSimilarNode && contextNode.end !== pos) { - const isJsxExprContext = contextNode.kind === SyntaxKind.JsxExpression; - emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ !isJsxExprContext, /*forceNoNewline*/ isJsxExprContext); - } - return pos; + return willEmitLeadingNewLine(node.expression); } + return false; + } - function commentWillEmitNewLine(node: CommentRange) { - return node.kind === SyntaxKind.SingleLineCommentTrivia || !!node.hasTrailingNewLine; - } + /** + * Wraps an expression in parens if we would emit a leading comment that would introduce a line separator + * between the node and its parent. + */ + function parenthesizeExpressionForNoAsi(node: Expression) { + if (!commentsDisabled && isPartiallyEmittedExpression(node) && willEmitLeadingNewLine(node)) { + const parseNode = getParseTreeNode(node); + if (parseNode && isParenthesizedExpression(parseNode)) { + // If the original node was a parenthesized expression, restore it to preserve comment and source map emit + const parens = factory.createParenthesizedExpression(node.expression); + setOriginalNode(parens, node); + setTextRange(parens, parseNode); + return parens; + } + return factory.createParenthesizedExpression(node); + } + return node; + } - function willEmitLeadingNewLine(node: Expression): boolean { - if (!currentSourceFile) return false; - if (some(getLeadingCommentRanges(currentSourceFile.text, node.pos), commentWillEmitNewLine)) return true; - if (some(getSyntheticLeadingComments(node), commentWillEmitNewLine)) return true; - if (isPartiallyEmittedExpression(node)) { - if (node.pos !== node.expression.pos) { - if (some(getTrailingCommentRanges(currentSourceFile.text, node.expression.pos), commentWillEmitNewLine)) return true; - } - return willEmitLeadingNewLine(node.expression); - } - return false; + function parenthesizeExpressionForNoAsiAndDisallowedComma(node: Expression) { + return parenthesizeExpressionForNoAsi(parenthesizer.parenthesizeExpressionForDisallowedComma(node)); + } + + function emitReturnStatement(node: ReturnStatement) { + emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, writeKeyword, /*contextNode*/ node); + emitExpressionWithLeadingSpace(node.expression && parenthesizeExpressionForNoAsi(node.expression), parenthesizeExpressionForNoAsi); + writeTrailingSemicolon(); + } + + function emitWithStatement(node: WithStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.WithKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + emitEmbeddedStatement(node, node.statement); + } + + function emitSwitchStatement(node: SwitchStatement) { + const openParenPos = emitTokenWithComment(SyntaxKind.SwitchKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); + writeSpace(); + emit(node.caseBlock); + } + + function emitLabeledStatement(node: LabeledStatement) { + emit(node.label); + emitTokenWithComment(SyntaxKind.ColonToken, node.label.end, writePunctuation, node); + writeSpace(); + emit(node.statement); + } + + function emitThrowStatement(node: ThrowStatement) { + emitTokenWithComment(SyntaxKind.ThrowKeyword, node.pos, writeKeyword, node); + emitExpressionWithLeadingSpace(parenthesizeExpressionForNoAsi(node.expression), parenthesizeExpressionForNoAsi); + writeTrailingSemicolon(); + } + + function emitTryStatement(node: TryStatement) { + emitTokenWithComment(SyntaxKind.TryKeyword, node.pos, writeKeyword, node); + writeSpace(); + emit(node.tryBlock); + if (node.catchClause) { + writeLineOrSpace(node, node.tryBlock, node.catchClause); + emit(node.catchClause); + } + if (node.finallyBlock) { + writeLineOrSpace(node, node.catchClause || node.tryBlock, node.finallyBlock); + emitTokenWithComment(SyntaxKind.FinallyKeyword, (node.catchClause || node.tryBlock).end, writeKeyword, node); + writeSpace(); + emit(node.finallyBlock); } + } + + function emitDebuggerStatement(node: DebuggerStatement) { + writeToken(SyntaxKind.DebuggerKeyword, node.pos, writeKeyword); + writeTrailingSemicolon(); + } + + // + // Declarations + // - /** - * Wraps an expression in parens if we would emit a leading comment that would introduce a line separator - * between the node and its parent. - */ - function parenthesizeExpressionForNoAsi(node: Expression) { - if (!commentsDisabled && isPartiallyEmittedExpression(node) && willEmitLeadingNewLine(node)) { - const parseNode = getParseTreeNode(node); - if (parseNode && isParenthesizedExpression(parseNode)) { - // If the original node was a parenthesized expression, restore it to preserve comment and source map emit - const parens = factory.createParenthesizedExpression(node.expression); - setOriginalNode(parens, node); - setTextRange(parens, parseNode); - return parens; - } - return factory.createParenthesizedExpression(node); - } - return node; - } + function emitVariableDeclaration(node: VariableDeclaration) { + emit(node.name); + emit(node.exclamationToken); + emitTypeAnnotation(node.type); + emitInitializer(node.initializer, node.type?.end ?? node.name.emitNode?.typeNode?.end ?? node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - function parenthesizeExpressionForNoAsiAndDisallowedComma(node: Expression) { - return parenthesizeExpressionForNoAsi(parenthesizer.parenthesizeExpressionForDisallowedComma(node)); - } + function emitVariableDeclarationList(node: VariableDeclarationList) { + writeKeyword(isLet(node) ? "let" : isVarConst(node) ? "const" : "var"); + writeSpace(); + emitList(node, node.declarations, ListFormat.VariableDeclarationList); + } - function emitReturnStatement(node: ReturnStatement) { - emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, writeKeyword, /*contextNode*/ node); - emitExpressionWithLeadingSpace(node.expression && parenthesizeExpressionForNoAsi(node.expression), parenthesizeExpressionForNoAsi); - writeTrailingSemicolon(); - } + function emitFunctionDeclaration(node: FunctionDeclaration) { + emitFunctionDeclarationOrExpression(node); + } - function emitWithStatement(node: WithStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.WithKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - emitEmbeddedStatement(node, node.statement); - } + function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) { + emitModifiers(node, node.modifiers); + writeKeyword("function"); + emit(node.asteriskToken); + writeSpace(); + emitIdentifierName(node.name); + emitSignatureAndBody(node, emitSignatureHead); + } - function emitSwitchStatement(node: SwitchStatement) { - const openParenPos = emitTokenWithComment(SyntaxKind.SwitchKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); - writeSpace(); - emit(node.caseBlock); - } + function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) { + const body = node.body; + if (body) { + if (isBlock(body)) { + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { + increaseIndent(); + } - function emitLabeledStatement(node: LabeledStatement) { - emit(node.label); - emitTokenWithComment(SyntaxKind.ColonToken, node.label.end, writePunctuation, node); - writeSpace(); - emit(node.statement); - } + pushNameGenerationScope(node); + forEach(node.parameters, generateNames); + generateNames(node.body); - function emitThrowStatement(node: ThrowStatement) { - emitTokenWithComment(SyntaxKind.ThrowKeyword, node.pos, writeKeyword, node); - emitExpressionWithLeadingSpace(parenthesizeExpressionForNoAsi(node.expression), parenthesizeExpressionForNoAsi); - writeTrailingSemicolon(); - } + emitSignatureHead(node); + emitBlockFunctionBody(body); + popNameGenerationScope(node); - function emitTryStatement(node: TryStatement) { - emitTokenWithComment(SyntaxKind.TryKeyword, node.pos, writeKeyword, node); - writeSpace(); - emit(node.tryBlock); - if (node.catchClause) { - writeLineOrSpace(node, node.tryBlock, node.catchClause); - emit(node.catchClause); + if (indentedFlag) { + decreaseIndent(); + } } - if (node.finallyBlock) { - writeLineOrSpace(node, node.catchClause || node.tryBlock, node.finallyBlock); - emitTokenWithComment(SyntaxKind.FinallyKeyword, (node.catchClause || node.tryBlock).end, writeKeyword, node); + else { + emitSignatureHead(node); writeSpace(); - emit(node.finallyBlock); + emitExpression(body, parenthesizer.parenthesizeConciseBodyOfArrowFunction); } } - - function emitDebuggerStatement(node: DebuggerStatement) { - writeToken(SyntaxKind.DebuggerKeyword, node.pos, writeKeyword); + else { + emitSignatureHead(node); writeTrailingSemicolon(); } - // - // Declarations - // + } - function emitVariableDeclaration(node: VariableDeclaration) { - emit(node.name); - emit(node.exclamationToken); - emitTypeAnnotation(node.type); - emitInitializer(node.initializer, node.type?.end ?? node.name.emitNode?.typeNode?.end ?? node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); - } + function emitSignatureHead(node: FunctionDeclaration | FunctionExpression | MethodDeclaration | AccessorDeclaration | ConstructorDeclaration) { + emitTypeParameters(node, node.typeParameters); + emitParameters(node, node.parameters); + emitTypeAnnotation(node.type); + } - function emitVariableDeclarationList(node: VariableDeclarationList) { - writeKeyword(isLet(node) ? "let" : isVarConst(node) ? "const" : "var"); - writeSpace(); - emitList(node, node.declarations, ListFormat.VariableDeclarationList); - } + function shouldEmitBlockFunctionBodyOnSingleLine(body: Block) { + // We must emit a function body as a single-line body in the following case: + // * The body has NodeEmitFlags.SingleLine specified. - function emitFunctionDeclaration(node: FunctionDeclaration) { - emitFunctionDeclarationOrExpression(node); - } + // We must emit a function body as a multi-line body in the following cases: + // * The body is explicitly marked as multi-line. + // * A non-synthesized body's start and end position are on different lines. + // * Any statement in the body starts on a new line. - function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) { - emitModifiers(node, node.modifiers); - writeKeyword("function"); - emit(node.asteriskToken); - writeSpace(); - emitIdentifierName(node.name); - emitSignatureAndBody(node, emitSignatureHead); + if (getEmitFlags(body) & EmitFlags.SingleLine) { + return true; } - function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) { - const body = node.body; - if (body) { - if (isBlock(body)) { - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } + if (body.multiLine) { + return false; + } - pushNameGenerationScope(node); - forEach(node.parameters, generateNames); - generateNames(node.body); + if (!nodeIsSynthesized(body) && currentSourceFile && !rangeIsOnSingleLine(body, currentSourceFile)) { + return false; + } - emitSignatureHead(node); - emitBlockFunctionBody(body); - popNameGenerationScope(node); + if (getLeadingLineTerminatorCount(body, firstOrUndefined(body.statements), ListFormat.PreserveLines) + || getClosingLineTerminatorCount(body, lastOrUndefined(body.statements), ListFormat.PreserveLines, body.statements)) { + return false; + } - if (indentedFlag) { - decreaseIndent(); - } - } - else { - emitSignatureHead(node); - writeSpace(); - emitExpression(body, parenthesizer.parenthesizeConciseBodyOfArrowFunction); - } - } - else { - emitSignatureHead(node); - writeTrailingSemicolon(); + let previousStatement: Statement | undefined; + for (const statement of body.statements) { + if (getSeparatingLineTerminatorCount(previousStatement, statement, ListFormat.PreserveLines) > 0) { + return false; } + previousStatement = statement; } - function emitSignatureHead(node: FunctionDeclaration | FunctionExpression | MethodDeclaration | AccessorDeclaration | ConstructorDeclaration) { - emitTypeParameters(node, node.typeParameters); - emitParameters(node, node.parameters); - emitTypeAnnotation(node.type); - } + return true; + } - function shouldEmitBlockFunctionBodyOnSingleLine(body: Block) { - // We must emit a function body as a single-line body in the following case: - // * The body has NodeEmitFlags.SingleLine specified. + function emitBlockFunctionBody(body: Block) { + onBeforeEmitNode?.(body); + writeSpace(); + writePunctuation("{"); + increaseIndent(); - // We must emit a function body as a multi-line body in the following cases: - // * The body is explicitly marked as multi-line. - // * A non-synthesized body's start and end position are on different lines. - // * Any statement in the body starts on a new line. + const emitBlockFunctionBody = shouldEmitBlockFunctionBodyOnSingleLine(body) + ? emitBlockFunctionBodyOnSingleLine + : emitBlockFunctionBodyWorker; - if (getEmitFlags(body) & EmitFlags.SingleLine) { - return true; - } + emitBodyWithDetachedComments(body, body.statements, emitBlockFunctionBody); - if (body.multiLine) { - return false; - } + decreaseIndent(); + writeToken(SyntaxKind.CloseBraceToken, body.statements.end, writePunctuation, body); + onAfterEmitNode?.(body); + } - if (!nodeIsSynthesized(body) && currentSourceFile && !rangeIsOnSingleLine(body, currentSourceFile)) { - return false; - } + function emitBlockFunctionBodyOnSingleLine(body: Block) { + emitBlockFunctionBodyWorker(body, /*emitBlockFunctionBodyOnSingleLine*/ true); + } - if (getLeadingLineTerminatorCount(body, firstOrUndefined(body.statements), ListFormat.PreserveLines) - || getClosingLineTerminatorCount(body, lastOrUndefined(body.statements), ListFormat.PreserveLines, body.statements)) { - return false; - } + function emitBlockFunctionBodyWorker(body: Block, emitBlockFunctionBodyOnSingleLine?: boolean) { + // Emit all the prologue directives (like "use strict"). + const statementOffset = emitPrologueDirectives(body.statements); + const pos = writer.getTextPos(); + emitHelpers(body); + if (statementOffset === 0 && pos === writer.getTextPos() && emitBlockFunctionBodyOnSingleLine) { + decreaseIndent(); + emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); + increaseIndent(); + } + else { + emitList(body, body.statements, ListFormat.MultiLineFunctionBodyStatements, /*parenthesizerRule*/ undefined, statementOffset); + } + } - let previousStatement: Statement | undefined; - for (const statement of body.statements) { - if (getSeparatingLineTerminatorCount(previousStatement, statement, ListFormat.PreserveLines) > 0) { - return false; - } + function emitClassDeclaration(node: ClassDeclaration) { + emitClassDeclarationOrExpression(node); + } - previousStatement = statement; - } + function emitClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression) { + forEach(node.members, generateMemberNames); - return true; + emitDecoratorsAndModifiers(node, node.modifiers); + writeKeyword("class"); + if (node.name) { + writeSpace(); + emitIdentifierName(node.name); } - function emitBlockFunctionBody(body: Block) { - onBeforeEmitNode?.(body); - writeSpace(); - writePunctuation("{"); + const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; + if (indentedFlag) { increaseIndent(); + } - const emitBlockFunctionBody = shouldEmitBlockFunctionBodyOnSingleLine(body) - ? emitBlockFunctionBodyOnSingleLine - : emitBlockFunctionBodyWorker; + emitTypeParameters(node, node.typeParameters); + emitList(node, node.heritageClauses, ListFormat.ClassHeritageClauses); - emitBodyWithDetachedComments(body, body.statements, emitBlockFunctionBody); + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.ClassMembers); + writePunctuation("}"); + if (indentedFlag) { decreaseIndent(); - writeToken(SyntaxKind.CloseBraceToken, body.statements.end, writePunctuation, body); - onAfterEmitNode?.(body); } + } - function emitBlockFunctionBodyOnSingleLine(body: Block) { - emitBlockFunctionBodyWorker(body, /*emitBlockFunctionBodyOnSingleLine*/ true); - } + function emitInterfaceDeclaration(node: InterfaceDeclaration) { + emitModifiers(node, node.modifiers); + writeKeyword("interface"); + writeSpace(); + emit(node.name); + emitTypeParameters(node, node.typeParameters); + emitList(node, node.heritageClauses, ListFormat.HeritageClauses); + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.InterfaceMembers); + writePunctuation("}"); + } - function emitBlockFunctionBodyWorker(body: Block, emitBlockFunctionBodyOnSingleLine?: boolean) { - // Emit all the prologue directives (like "use strict"). - const statementOffset = emitPrologueDirectives(body.statements); - const pos = writer.getTextPos(); - emitHelpers(body); - if (statementOffset === 0 && pos === writer.getTextPos() && emitBlockFunctionBodyOnSingleLine) { - decreaseIndent(); - emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); - increaseIndent(); - } - else { - emitList(body, body.statements, ListFormat.MultiLineFunctionBodyStatements, /*parenthesizerRule*/ undefined, statementOffset); - } - } + function emitTypeAliasDeclaration(node: TypeAliasDeclaration) { + emitModifiers(node, node.modifiers); + writeKeyword("type"); + writeSpace(); + emit(node.name); + emitTypeParameters(node, node.typeParameters); + writeSpace(); + writePunctuation("="); + writeSpace(); + emit(node.type); + writeTrailingSemicolon(); + } + + function emitEnumDeclaration(node: EnumDeclaration) { + emitModifiers(node, node.modifiers); + writeKeyword("enum"); + writeSpace(); + emit(node.name); + + writeSpace(); + writePunctuation("{"); + emitList(node, node.members, ListFormat.EnumMembers); + writePunctuation("}"); + } - function emitClassDeclaration(node: ClassDeclaration) { - emitClassDeclarationOrExpression(node); + function emitModuleDeclaration(node: ModuleDeclaration) { + emitModifiers(node, node.modifiers); + if (~node.flags & NodeFlags.GlobalAugmentation) { + writeKeyword(node.flags & NodeFlags.Namespace ? "namespace" : "module"); + writeSpace(); } + emit(node.name); - function emitClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression) { - forEach(node.members, generateMemberNames); + let body = node.body; + if (!body) return writeTrailingSemicolon(); + while (body && isModuleDeclaration(body)) { + writePunctuation("."); + emit(body.name); + body = body.body; + } - emitDecoratorsAndModifiers(node, node.modifiers); - writeKeyword("class"); - if (node.name) { - writeSpace(); - emitIdentifierName(node.name); - } + writeSpace(); + emit(body); + } - const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; - if (indentedFlag) { - increaseIndent(); - } + function emitModuleBlock(node: ModuleBlock) { + pushNameGenerationScope(node); + forEach(node.statements, generateNames); + emitBlockStatements(node, /*forceSingleLine*/ isEmptyBlock(node)); + popNameGenerationScope(node); + } - emitTypeParameters(node, node.typeParameters); - emitList(node, node.heritageClauses, ListFormat.ClassHeritageClauses); + function emitCaseBlock(node: CaseBlock) { + emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); + emitList(node, node.clauses, ListFormat.CaseBlockClauses); + emitTokenWithComment(SyntaxKind.CloseBraceToken, node.clauses.end, writePunctuation, node, /*indentLeading*/ true); + } + function emitImportEqualsDeclaration(node: ImportEqualsDeclaration) { + emitModifiers(node, node.modifiers); + emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); + writeSpace(); + if (node.isTypeOnly) { + emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.ClassMembers); - writePunctuation("}"); - - if (indentedFlag) { - decreaseIndent(); - } } + emit(node.name); + writeSpace(); + emitTokenWithComment(SyntaxKind.EqualsToken, node.name.end, writePunctuation, node); + writeSpace(); + emitModuleReference(node.moduleReference); + writeTrailingSemicolon(); + } - function emitInterfaceDeclaration(node: InterfaceDeclaration) { - emitModifiers(node, node.modifiers); - writeKeyword("interface"); - writeSpace(); - emit(node.name); - emitTypeParameters(node, node.typeParameters); - emitList(node, node.heritageClauses, ListFormat.HeritageClauses); - writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.InterfaceMembers); - writePunctuation("}"); + function emitModuleReference(node: ModuleReference) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); + } + else { + emit(node); } + } - function emitTypeAliasDeclaration(node: TypeAliasDeclaration) { - emitModifiers(node, node.modifiers); - writeKeyword("type"); + function emitImportDeclaration(node: ImportDeclaration) { + emitModifiers(node, node.modifiers); + emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); + writeSpace(); + if (node.importClause) { + emit(node.importClause); writeSpace(); - emit(node.name); - emitTypeParameters(node, node.typeParameters); - writeSpace(); - writePunctuation("="); + emitTokenWithComment(SyntaxKind.FromKeyword, node.importClause.end, writeKeyword, node); writeSpace(); - emit(node.type); - writeTrailingSemicolon(); } + emitExpression(node.moduleSpecifier); + if (node.assertClause) { + emitWithLeadingSpace(node.assertClause); + } + writeTrailingSemicolon(); + } - function emitEnumDeclaration(node: EnumDeclaration) { - emitModifiers(node, node.modifiers); - writeKeyword("enum"); + function emitImportClause(node: ImportClause) { + if (node.isTypeOnly) { + emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); writeSpace(); - emit(node.name); - + } + emit(node.name); + if (node.name && node.namedBindings) { + emitTokenWithComment(SyntaxKind.CommaToken, node.name.end, writePunctuation, node); writeSpace(); - writePunctuation("{"); - emitList(node, node.members, ListFormat.EnumMembers); - writePunctuation("}"); } + emit(node.namedBindings); + } - function emitModuleDeclaration(node: ModuleDeclaration) { - emitModifiers(node, node.modifiers); - if (~node.flags & NodeFlags.GlobalAugmentation) { - writeKeyword(node.flags & NodeFlags.Namespace ? "namespace" : "module"); - writeSpace(); - } - emit(node.name); - - let body = node.body; - if (!body) return writeTrailingSemicolon(); - while (body && isModuleDeclaration(body)) { - writePunctuation("."); - emit(body.name); - body = body.body; - } + function emitNamespaceImport(node: NamespaceImport) { + const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); + writeSpace(); + emit(node.name); + } - writeSpace(); - emit(body); - } + function emitNamedImports(node: NamedImports) { + emitNamedImportsOrExports(node); + } - function emitModuleBlock(node: ModuleBlock) { - pushNameGenerationScope(node); - forEach(node.statements, generateNames); - emitBlockStatements(node, /*forceSingleLine*/ isEmptyBlock(node)); - popNameGenerationScope(node); - } + function emitImportSpecifier(node: ImportSpecifier) { + emitImportOrExportSpecifier(node); + } - function emitCaseBlock(node: CaseBlock) { - emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); - emitList(node, node.clauses, ListFormat.CaseBlockClauses); - emitTokenWithComment(SyntaxKind.CloseBraceToken, node.clauses.end, writePunctuation, node, /*indentLeading*/ true); + function emitExportAssignment(node: ExportAssignment) { + const nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.isExportEquals) { + emitTokenWithComment(SyntaxKind.EqualsToken, nextPos, writeOperator, node); + } + else { + emitTokenWithComment(SyntaxKind.DefaultKeyword, nextPos, writeKeyword, node); } + writeSpace(); + emitExpression(node.expression, node.isExportEquals ? + parenthesizer.getParenthesizeRightSideOfBinaryForOperator(SyntaxKind.EqualsToken) : + parenthesizer.parenthesizeExpressionOfExportDefault); + writeTrailingSemicolon(); + } - function emitImportEqualsDeclaration(node: ImportEqualsDeclaration) { - emitModifiers(node, node.modifiers); - emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); - writeSpace(); - if (node.isTypeOnly) { - emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); - writeSpace(); - } - emit(node.name); + function emitExportDeclaration(node: ExportDeclaration) { + emitModifiers(node, node.modifiers); + let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.isTypeOnly) { + nextPos = emitTokenWithComment(SyntaxKind.TypeKeyword, nextPos, writeKeyword, node); writeSpace(); - emitTokenWithComment(SyntaxKind.EqualsToken, node.name.end, writePunctuation, node); - writeSpace(); - emitModuleReference(node.moduleReference); - writeTrailingSemicolon(); } - - function emitModuleReference(node: ModuleReference) { - if (node.kind === SyntaxKind.Identifier) { - emitExpression(node); - } - else { - emit(node); - } + if (node.exportClause) { + emit(node.exportClause); } - - function emitImportDeclaration(node: ImportDeclaration) { - emitModifiers(node, node.modifiers); - emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node); + else { + nextPos = emitTokenWithComment(SyntaxKind.AsteriskToken, nextPos, writePunctuation, node); + } + if (node.moduleSpecifier) { + writeSpace(); + const fromPos = node.exportClause ? node.exportClause.end : nextPos; + emitTokenWithComment(SyntaxKind.FromKeyword, fromPos, writeKeyword, node); writeSpace(); - if (node.importClause) { - emit(node.importClause); - writeSpace(); - emitTokenWithComment(SyntaxKind.FromKeyword, node.importClause.end, writeKeyword, node); - writeSpace(); - } emitExpression(node.moduleSpecifier); - if (node.assertClause) { - emitWithLeadingSpace(node.assertClause); - } - writeTrailingSemicolon(); } - - function emitImportClause(node: ImportClause) { - if (node.isTypeOnly) { - emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node); - writeSpace(); - } - emit(node.name); - if (node.name && node.namedBindings) { - emitTokenWithComment(SyntaxKind.CommaToken, node.name.end, writePunctuation, node); - writeSpace(); - } - emit(node.namedBindings); + if (node.assertClause) { + emitWithLeadingSpace(node.assertClause); } + writeTrailingSemicolon(); + } - function emitNamespaceImport(node: NamespaceImport) { - const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); - writeSpace(); - emit(node.name); - } + function emitAssertClause(node: AssertClause) { + emitTokenWithComment(SyntaxKind.AssertKeyword, node.pos, writeKeyword, node); + writeSpace(); + const elements = node.elements; + emitList(node, elements, ListFormat.ImportClauseEntries); + } - function emitNamedImports(node: NamedImports) { - emitNamedImportsOrExports(node); - } + function emitAssertEntry(node: AssertEntry) { + emit(node.name); + writePunctuation(":"); + writeSpace(); - function emitImportSpecifier(node: ImportSpecifier) { - emitImportOrExportSpecifier(node); + const value = node.value; + /** @see {emitPropertyAssignment} */ + if ((getEmitFlags(value) & EmitFlags.NoLeadingComments) === 0) { + const commentRange = getCommentRange(value); + emitTrailingCommentsOfPosition(commentRange.pos); } + emit(value); + } + + function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) { + let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + writeSpace(); + nextPos = emitTokenWithComment(SyntaxKind.AsKeyword, nextPos, writeKeyword, node); + writeSpace(); + nextPos = emitTokenWithComment(SyntaxKind.NamespaceKeyword, nextPos, writeKeyword, node); + writeSpace(); + emit(node.name); + writeTrailingSemicolon(); + } + + function emitNamespaceExport(node: NamespaceExport) { + const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); + writeSpace(); + emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); + writeSpace(); + emit(node.name); + } + + function emitNamedExports(node: NamedExports) { + emitNamedImportsOrExports(node); + } + + function emitExportSpecifier(node: ExportSpecifier) { + emitImportOrExportSpecifier(node); + } + + function emitNamedImportsOrExports(node: NamedImportsOrExports) { + writePunctuation("{"); + emitList(node, node.elements, ListFormat.NamedImportsOrExportsElements); + writePunctuation("}"); + } - function emitExportAssignment(node: ExportAssignment) { - const nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); + function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { + if (node.isTypeOnly) { + writeKeyword("type"); writeSpace(); - if (node.isExportEquals) { - emitTokenWithComment(SyntaxKind.EqualsToken, nextPos, writeOperator, node); - } - else { - emitTokenWithComment(SyntaxKind.DefaultKeyword, nextPos, writeKeyword, node); - } + } + if (node.propertyName) { + emit(node.propertyName); + writeSpace(); + emitTokenWithComment(SyntaxKind.AsKeyword, node.propertyName.end, writeKeyword, node); writeSpace(); - emitExpression(node.expression, node.isExportEquals ? - parenthesizer.getParenthesizeRightSideOfBinaryForOperator(SyntaxKind.EqualsToken) : - parenthesizer.parenthesizeExpressionOfExportDefault); - writeTrailingSemicolon(); } - function emitExportDeclaration(node: ExportDeclaration) { - emitModifiers(node, node.modifiers); - let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); - writeSpace(); - if (node.isTypeOnly) { - nextPos = emitTokenWithComment(SyntaxKind.TypeKeyword, nextPos, writeKeyword, node); - writeSpace(); - } - if (node.exportClause) { - emit(node.exportClause); - } - else { - nextPos = emitTokenWithComment(SyntaxKind.AsteriskToken, nextPos, writePunctuation, node); - } - if (node.moduleSpecifier) { - writeSpace(); - const fromPos = node.exportClause ? node.exportClause.end : nextPos; - emitTokenWithComment(SyntaxKind.FromKeyword, fromPos, writeKeyword, node); + emit(node.name); + } + + // + // Module references + // + + function emitExternalModuleReference(node: ExternalModuleReference) { + writeKeyword("require"); + writePunctuation("("); + emitExpression(node.expression); + writePunctuation(")"); + } + + // + // JSX + // + + function emitJsxElement(node: JsxElement) { + emit(node.openingElement); + emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); + emit(node.closingElement); + } + + function emitJsxSelfClosingElement(node: JsxSelfClosingElement) { + writePunctuation("<"); + emitJsxTagName(node.tagName); + emitTypeArguments(node, node.typeArguments); + writeSpace(); + emit(node.attributes); + writePunctuation("/>"); + } + + function emitJsxFragment(node: JsxFragment) { + emit(node.openingFragment); + emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); + emit(node.closingFragment); + } + + function emitJsxOpeningElementOrFragment(node: JsxOpeningElement | JsxOpeningFragment) { + writePunctuation("<"); + + if (isJsxOpeningElement(node)) { + const indented = writeLineSeparatorsAndIndentBefore(node.tagName, node); + emitJsxTagName(node.tagName); + emitTypeArguments(node, node.typeArguments); + if (node.attributes.properties && node.attributes.properties.length > 0) { writeSpace(); - emitExpression(node.moduleSpecifier); } - if (node.assertClause) { - emitWithLeadingSpace(node.assertClause); - } - writeTrailingSemicolon(); + emit(node.attributes); + writeLineSeparatorsAfter(node.attributes, node); + decreaseIndentIf(indented); } - function emitAssertClause(node: AssertClause) { - emitTokenWithComment(SyntaxKind.AssertKeyword, node.pos, writeKeyword, node); - writeSpace(); - const elements = node.elements; - emitList(node, elements, ListFormat.ImportClauseEntries); - } + writePunctuation(">"); + } - function emitAssertEntry(node: AssertEntry) { - emit(node.name); - writePunctuation(":"); - writeSpace(); + function emitJsxText(node: JsxText) { + writer.writeLiteral(node.text); + } - const value = node.value; - /** @see {emitPropertyAssignment} */ - if ((getEmitFlags(value) & EmitFlags.NoLeadingComments) === 0) { - const commentRange = getCommentRange(value); - emitTrailingCommentsOfPosition(commentRange.pos); - } - emit(value); + function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) { + writePunctuation(""); + } - function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) { - let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); - writeSpace(); - nextPos = emitTokenWithComment(SyntaxKind.AsKeyword, nextPos, writeKeyword, node); - writeSpace(); - nextPos = emitTokenWithComment(SyntaxKind.NamespaceKeyword, nextPos, writeKeyword, node); - writeSpace(); - emit(node.name); - writeTrailingSemicolon(); - } + function emitJsxAttributes(node: JsxAttributes) { + emitList(node, node.properties, ListFormat.JsxElementAttributes); + } - function emitNamespaceExport(node: NamespaceExport) { - const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node); - writeSpace(); - emitTokenWithComment(SyntaxKind.AsKeyword, asPos, writeKeyword, node); - writeSpace(); - emit(node.name); - } + function emitJsxAttribute(node: JsxAttribute) { + emit(node.name); + emitNodeWithPrefix("=", writePunctuation, node.initializer, emitJsxAttributeValue); + } - function emitNamedExports(node: NamedExports) { - emitNamedImportsOrExports(node); - } + function emitJsxSpreadAttribute(node: JsxSpreadAttribute) { + writePunctuation("{..."); + emitExpression(node.expression); + writePunctuation("}"); + } - function emitExportSpecifier(node: ExportSpecifier) { - emitImportOrExportSpecifier(node); - } + function hasTrailingCommentsAtPosition(pos: number) { + let result = false; + forEachTrailingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); + return result; + } + + function hasLeadingCommentsAtPosition(pos: number) { + let result = false; + forEachLeadingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); + return result; + } - function emitNamedImportsOrExports(node: NamedImportsOrExports) { - writePunctuation("{"); - emitList(node, node.elements, ListFormat.NamedImportsOrExportsElements); - writePunctuation("}"); - } + function hasCommentsAtPosition(pos: number) { + return hasTrailingCommentsAtPosition(pos) || hasLeadingCommentsAtPosition(pos); + } - function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { - if (node.isTypeOnly) { - writeKeyword("type"); - writeSpace(); + function emitJsxExpression(node: JsxExpression) { + if (node.expression || (!commentsDisabled && !nodeIsSynthesized(node) && hasCommentsAtPosition(node.pos))) { // preserve empty expressions if they contain comments! + const isMultiline = currentSourceFile && !nodeIsSynthesized(node) && getLineAndCharacterOfPosition(currentSourceFile, node.pos).line !== getLineAndCharacterOfPosition(currentSourceFile, node.end).line; + if (isMultiline) { + writer.increaseIndent(); } - if (node.propertyName) { - emit(node.propertyName); - writeSpace(); - emitTokenWithComment(SyntaxKind.AsKeyword, node.propertyName.end, writeKeyword, node); - writeSpace(); + const end = emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); + emit(node.dotDotDotToken); + emitExpression(node.expression); + emitTokenWithComment(SyntaxKind.CloseBraceToken, node.expression?.end || end, writePunctuation, node); + if (isMultiline) { + writer.decreaseIndent(); } - - emit(node.name); } + } - // - // Module references - // - - function emitExternalModuleReference(node: ExternalModuleReference) { - writeKeyword("require"); - writePunctuation("("); - emitExpression(node.expression); - writePunctuation(")"); + function emitJsxTagName(node: JsxTagNameExpression) { + if (node.kind === SyntaxKind.Identifier) { + emitExpression(node); } - - // - // JSX - // - - function emitJsxElement(node: JsxElement) { - emit(node.openingElement); - emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); - emit(node.closingElement); + else { + emit(node); } + } - function emitJsxSelfClosingElement(node: JsxSelfClosingElement) { - writePunctuation("<"); - emitJsxTagName(node.tagName); - emitTypeArguments(node, node.typeArguments); - writeSpace(); - emit(node.attributes); - writePunctuation("/>"); - } + // + // Clauses + // - function emitJsxFragment(node: JsxFragment) { - emit(node.openingFragment); - emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren); - emit(node.closingFragment); - } + function emitCaseClause(node: CaseClause) { + emitTokenWithComment(SyntaxKind.CaseKeyword, node.pos, writeKeyword, node); + writeSpace(); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); - function emitJsxOpeningElementOrFragment(node: JsxOpeningElement | JsxOpeningFragment) { - writePunctuation("<"); + emitCaseOrDefaultClauseRest(node, node.statements, node.expression.end); + } - if (isJsxOpeningElement(node)) { - const indented = writeLineSeparatorsAndIndentBefore(node.tagName, node); - emitJsxTagName(node.tagName); - emitTypeArguments(node, node.typeArguments); - if (node.attributes.properties && node.attributes.properties.length > 0) { - writeSpace(); - } - emit(node.attributes); - writeLineSeparatorsAfter(node.attributes, node); - decreaseIndentIf(indented); - } + function emitDefaultClause(node: DefaultClause) { + const pos = emitTokenWithComment(SyntaxKind.DefaultKeyword, node.pos, writeKeyword, node); + emitCaseOrDefaultClauseRest(node, node.statements, pos); + } - writePunctuation(">"); - } + function emitCaseOrDefaultClauseRest(parentNode: Node, statements: NodeArray, colonPos: number) { + const emitAsSingleStatement = + statements.length === 1 && + ( + // treat synthesized nodes as located on the same line for emit purposes + !currentSourceFile || + nodeIsSynthesized(parentNode) || + nodeIsSynthesized(statements[0]) || + rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile) + ); - function emitJsxText(node: JsxText) { - writer.writeLiteral(node.text); + let format = ListFormat.CaseOrDefaultClauseStatements; + if (emitAsSingleStatement) { + writeToken(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); + writeSpace(); + format &= ~(ListFormat.MultiLine | ListFormat.Indented); } - - function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) { - writePunctuation(""); + else { + emitTokenWithComment(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); } + emitList(parentNode, statements, format); + } - function emitJsxAttributes(node: JsxAttributes) { - emitList(node, node.properties, ListFormat.JsxElementAttributes); - } + function emitHeritageClause(node: HeritageClause) { + writeSpace(); + writeTokenText(node.token, writeKeyword); + writeSpace(); + emitList(node, node.types, ListFormat.HeritageClauseTypes); + } - function emitJsxAttribute(node: JsxAttribute) { - emit(node.name); - emitNodeWithPrefix("=", writePunctuation, node.initializer, emitJsxAttributeValue); + function emitCatchClause(node: CatchClause) { + const openParenPos = emitTokenWithComment(SyntaxKind.CatchKeyword, node.pos, writeKeyword, node); + writeSpace(); + if (node.variableDeclaration) { + emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); + emit(node.variableDeclaration); + emitTokenWithComment(SyntaxKind.CloseParenToken, node.variableDeclaration.end, writePunctuation, node); + writeSpace(); } + emit(node.block); + } - function emitJsxSpreadAttribute(node: JsxSpreadAttribute) { - writePunctuation("{..."); - emitExpression(node.expression); - writePunctuation("}"); - } + // + // Property assignments + // + + function emitPropertyAssignment(node: PropertyAssignment) { + emit(node.name); + writePunctuation(":"); + writeSpace(); + // This is to ensure that we emit comment in the following case: + // For example: + // obj = { + // id: /*comment1*/ ()=>void + // } + // "comment1" is not considered to be leading comment for node.initializer + // but rather a trailing comment on the previous node. + const initializer = node.initializer; + if ((getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) { + const commentRange = getCommentRange(initializer); + emitTrailingCommentsOfPosition(commentRange.pos); + } + emitExpression(initializer, parenthesizer.parenthesizeExpressionForDisallowedComma); + } - function hasTrailingCommentsAtPosition(pos: number) { - let result = false; - forEachTrailingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); - return result; + function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { + emit(node.name); + if (node.objectAssignmentInitializer) { + writeSpace(); + writePunctuation("="); + writeSpace(); + emitExpression(node.objectAssignmentInitializer, parenthesizer.parenthesizeExpressionForDisallowedComma); } + } - function hasLeadingCommentsAtPosition(pos: number) { - let result = false; - forEachLeadingCommentRange(currentSourceFile?.text || "", pos + 1, () => result = true); - return result; + function emitSpreadAssignment(node: SpreadAssignment) { + if (node.expression) { + emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); + emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); } + } - function hasCommentsAtPosition(pos: number) { - return hasTrailingCommentsAtPosition(pos) || hasLeadingCommentsAtPosition(pos); - } + // + // Enum + // - function emitJsxExpression(node: JsxExpression) { - if (node.expression || (!commentsDisabled && !nodeIsSynthesized(node) && hasCommentsAtPosition(node.pos))) { // preserve empty expressions if they contain comments! - const isMultiline = currentSourceFile && !nodeIsSynthesized(node) && getLineAndCharacterOfPosition(currentSourceFile, node.pos).line !== getLineAndCharacterOfPosition(currentSourceFile, node.end).line; - if (isMultiline) { - writer.increaseIndent(); - } - const end = emitTokenWithComment(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, node); - emit(node.dotDotDotToken); - emitExpression(node.expression); - emitTokenWithComment(SyntaxKind.CloseBraceToken, node.expression?.end || end, writePunctuation, node); - if (isMultiline) { - writer.decreaseIndent(); + function emitEnumMember(node: EnumMember) { + emit(node.name); + emitInitializer(node.initializer, node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + } + + // + // JSDoc + // + function emitJSDoc(node: JSDoc) { + write("/**"); + if (node.comment) { + const text = getTextOfJSDocComment(node.comment); + if (text) { + const lines = text.split(/\r\n?|\n/g); + for (const line of lines) { + writeLine(); + writeSpace(); + writePunctuation("*"); + writeSpace(); + write(line); } } } - - function emitJsxTagName(node: JsxTagNameExpression) { - if (node.kind === SyntaxKind.Identifier) { - emitExpression(node); + if (node.tags) { + if (node.tags.length === 1 && node.tags[0].kind === SyntaxKind.JSDocTypeTag && !node.comment) { + writeSpace(); + emit(node.tags[0]); } else { - emit(node); + emitList(node, node.tags, ListFormat.JSDocComment); } } + writeSpace(); + write("*/"); + } - // - // Clauses - // + function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) { + emitJSDocTagName(tag.tagName); + emitJSDocTypeExpression(tag.typeExpression); + emitJSDocComment(tag.comment); + } - function emitCaseClause(node: CaseClause) { - emitTokenWithComment(SyntaxKind.CaseKeyword, node.pos, writeKeyword, node); - writeSpace(); - emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); + function emitJSDocSeeTag(tag: JSDocSeeTag) { + emitJSDocTagName(tag.tagName); + emit(tag.name); + emitJSDocComment(tag.comment); + } - emitCaseOrDefaultClauseRest(node, node.statements, node.expression.end); - } + function emitJSDocNameReference(node: JSDocNameReference) { + writeSpace(); + writePunctuation("{"); + emit(node.name); + writePunctuation("}"); + } - function emitDefaultClause(node: DefaultClause) { - const pos = emitTokenWithComment(SyntaxKind.DefaultKeyword, node.pos, writeKeyword, node); - emitCaseOrDefaultClauseRest(node, node.statements, pos); - } + function emitJSDocHeritageTag(tag: JSDocImplementsTag | JSDocAugmentsTag) { + emitJSDocTagName(tag.tagName); + writeSpace(); + writePunctuation("{"); + emit(tag.class); + writePunctuation("}"); + emitJSDocComment(tag.comment); + } - function emitCaseOrDefaultClauseRest(parentNode: Node, statements: NodeArray, colonPos: number) { - const emitAsSingleStatement = - statements.length === 1 && - ( - // treat synthesized nodes as located on the same line for emit purposes - !currentSourceFile || - nodeIsSynthesized(parentNode) || - nodeIsSynthesized(statements[0]) || - rangeStartPositionsAreOnSameLine(parentNode, statements[0], currentSourceFile) - ); + function emitJSDocTemplateTag(tag: JSDocTemplateTag) { + emitJSDocTagName(tag.tagName); + emitJSDocTypeExpression(tag.constraint); + writeSpace(); + emitList(tag, tag.typeParameters, ListFormat.CommaListElements); + emitJSDocComment(tag.comment); + } - let format = ListFormat.CaseOrDefaultClauseStatements; - if (emitAsSingleStatement) { - writeToken(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); - writeSpace(); - format &= ~(ListFormat.MultiLine | ListFormat.Indented); + function emitJSDocTypedefTag(tag: JSDocTypedefTag) { + emitJSDocTagName(tag.tagName); + if (tag.typeExpression) { + if (tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { + emitJSDocTypeExpression(tag.typeExpression); } else { - emitTokenWithComment(SyntaxKind.ColonToken, colonPos, writePunctuation, parentNode); + writeSpace(); + writePunctuation("{"); + write("Object"); + if (tag.typeExpression.isArrayType) { + writePunctuation("["); + writePunctuation("]"); + } + writePunctuation("}"); } - emitList(parentNode, statements, format); } - - function emitHeritageClause(node: HeritageClause) { - writeSpace(); - writeTokenText(node.token, writeKeyword); + if (tag.fullName) { writeSpace(); - emitList(node, node.types, ListFormat.HeritageClauseTypes); + emit(tag.fullName); } - - function emitCatchClause(node: CatchClause) { - const openParenPos = emitTokenWithComment(SyntaxKind.CatchKeyword, node.pos, writeKeyword, node); - writeSpace(); - if (node.variableDeclaration) { - emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); - emit(node.variableDeclaration); - emitTokenWithComment(SyntaxKind.CloseParenToken, node.variableDeclaration.end, writePunctuation, node); - writeSpace(); - } - emit(node.block); + emitJSDocComment(tag.comment); + if (tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeLiteral) { + emitJSDocTypeLiteral(tag.typeExpression); } + } - // - // Property assignments - // - - function emitPropertyAssignment(node: PropertyAssignment) { - emit(node.name); - writePunctuation(":"); + function emitJSDocCallbackTag(tag: JSDocCallbackTag) { + emitJSDocTagName(tag.tagName); + if (tag.name) { writeSpace(); - // This is to ensure that we emit comment in the following case: - // For example: - // obj = { - // id: /*comment1*/ ()=>void - // } - // "comment1" is not considered to be leading comment for node.initializer - // but rather a trailing comment on the previous node. - const initializer = node.initializer; - if ((getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) { - const commentRange = getCommentRange(initializer); - emitTrailingCommentsOfPosition(commentRange.pos); - } - emitExpression(initializer, parenthesizer.parenthesizeExpressionForDisallowedComma); - } - - function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { - emit(node.name); - if (node.objectAssignmentInitializer) { - writeSpace(); - writePunctuation("="); - writeSpace(); - emitExpression(node.objectAssignmentInitializer, parenthesizer.parenthesizeExpressionForDisallowedComma); - } + emit(tag.name); } + emitJSDocComment(tag.comment); + emitJSDocSignature(tag.typeExpression); + } - function emitSpreadAssignment(node: SpreadAssignment) { - if (node.expression) { - emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); - emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); - } - } + function emitJSDocSimpleTag(tag: JSDocTag) { + emitJSDocTagName(tag.tagName); + emitJSDocComment(tag.comment); + } - // - // Enum - // + function emitJSDocTypeLiteral(lit: JSDocTypeLiteral) { + emitList(lit, factory.createNodeArray(lit.jsDocPropertyTags), ListFormat.JSDocComment); + } - function emitEnumMember(node: EnumMember) { - emit(node.name); - emitInitializer(node.initializer, node.name.end, node, parenthesizer.parenthesizeExpressionForDisallowedComma); + function emitJSDocSignature(sig: JSDocSignature) { + if (sig.typeParameters) { + emitList(sig, factory.createNodeArray(sig.typeParameters), ListFormat.JSDocComment); } - - // - // JSDoc - // - function emitJSDoc(node: JSDoc) { - write("/**"); - if (node.comment) { - const text = getTextOfJSDocComment(node.comment); - if (text) { - const lines = text.split(/\r\n?|\n/g); - for (const line of lines) { - writeLine(); - writeSpace(); - writePunctuation("*"); - writeSpace(); - write(line); - } - } - } - if (node.tags) { - if (node.tags.length === 1 && node.tags[0].kind === SyntaxKind.JSDocTypeTag && !node.comment) { - writeSpace(); - emit(node.tags[0]); - } - else { - emitList(node, node.tags, ListFormat.JSDocComment); - } - } + if (sig.parameters) { + emitList(sig, factory.createNodeArray(sig.parameters), ListFormat.JSDocComment); + } + if (sig.type) { + writeLine(); + writeSpace(); + writePunctuation("*"); writeSpace(); - write("*/"); + emit(sig.type); } + } - function emitJSDocSimpleTypedTag(tag: JSDocTypeTag | JSDocThisTag | JSDocEnumTag | JSDocReturnTag) { - emitJSDocTagName(tag.tagName); - emitJSDocTypeExpression(tag.typeExpression); - emitJSDocComment(tag.comment); + function emitJSDocPropertyLikeTag(param: JSDocPropertyLikeTag) { + emitJSDocTagName(param.tagName); + emitJSDocTypeExpression(param.typeExpression); + writeSpace(); + if (param.isBracketed) { + writePunctuation("["); } - - function emitJSDocSeeTag(tag: JSDocSeeTag) { - emitJSDocTagName(tag.tagName); - emit(tag.name); - emitJSDocComment(tag.comment); + emit(param.name); + if (param.isBracketed) { + writePunctuation("]"); } + emitJSDocComment(param.comment); + } - function emitJSDocNameReference(node: JSDocNameReference) { + function emitJSDocTagName(tagName: Identifier) { + writePunctuation("@"); + emit(tagName); + } + + function emitJSDocComment(comment: string | NodeArray | undefined) { + const text = getTextOfJSDocComment(comment); + if (text) { writeSpace(); - writePunctuation("{"); - emit(node.name); - writePunctuation("}"); + write(text); } + } - function emitJSDocHeritageTag(tag: JSDocImplementsTag | JSDocAugmentsTag) { - emitJSDocTagName(tag.tagName); + function emitJSDocTypeExpression(typeExpression: JSDocTypeExpression | undefined) { + if (typeExpression) { writeSpace(); writePunctuation("{"); - emit(tag.class); + emit(typeExpression.type); writePunctuation("}"); - emitJSDocComment(tag.comment); } + } - function emitJSDocTemplateTag(tag: JSDocTemplateTag) { - emitJSDocTagName(tag.tagName); - emitJSDocTypeExpression(tag.constraint); - writeSpace(); - emitList(tag, tag.typeParameters, ListFormat.CommaListElements); - emitJSDocComment(tag.comment); - } + // + // Top-level nodes + // + + function emitSourceFile(node: SourceFile) { + writeLine(); + const statements = node.statements; + // Emit detached comment if there are no prologue directives or if the first node is synthesized. + // The synthesized node will have no leading comment so some comments may be missed. + const shouldEmitDetachedComment = statements.length === 0 || + !isPrologueDirective(statements[0]) || + nodeIsSynthesized(statements[0]); + if (shouldEmitDetachedComment) { + emitBodyWithDetachedComments(node, statements, emitSourceFileWorker); + return; + } + emitSourceFileWorker(node); + } - function emitJSDocTypedefTag(tag: JSDocTypedefTag) { - emitJSDocTagName(tag.tagName); - if (tag.typeExpression) { - if (tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { - emitJSDocTypeExpression(tag.typeExpression); - } - else { - writeSpace(); - writePunctuation("{"); - write("Object"); - if (tag.typeExpression.isArrayType) { - writePunctuation("["); - writePunctuation("]"); - } - writePunctuation("}"); + function emitSyntheticTripleSlashReferencesIfNeeded(node: Bundle) { + emitTripleSlashDirectives(!!node.hasNoDefaultLib, node.syntheticFileReferences || [], node.syntheticTypeReferences || [], node.syntheticLibReferences || []); + for (const prepend of node.prepends) { + if (isUnparsedSource(prepend) && prepend.syntheticReferences) { + for (const ref of prepend.syntheticReferences) { + emit(ref); + writeLine(); } } - if (tag.fullName) { - writeSpace(); - emit(tag.fullName); - } - emitJSDocComment(tag.comment); - if (tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeLiteral) { - emitJSDocTypeLiteral(tag.typeExpression); - } } + } - function emitJSDocCallbackTag(tag: JSDocCallbackTag) { - emitJSDocTagName(tag.tagName); - if (tag.name) { - writeSpace(); - emit(tag.name); - } - emitJSDocComment(tag.comment); - emitJSDocSignature(tag.typeExpression); - } + function emitTripleSlashDirectivesIfNeeded(node: SourceFile) { + if (node.isDeclarationFile) emitTripleSlashDirectives(node.hasNoDefaultLib, node.referencedFiles, node.typeReferenceDirectives, node.libReferenceDirectives); + } - function emitJSDocSimpleTag(tag: JSDocTag) { - emitJSDocTagName(tag.tagName); - emitJSDocComment(tag.comment); + function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: readonly FileReference[], types: readonly FileReference[], libs: readonly FileReference[]) { + if (hasNoDefaultLib) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.NoDefaultLib }); + writeLine(); } - - function emitJSDocTypeLiteral(lit: JSDocTypeLiteral) { - emitList(lit, factory.createNodeArray(lit.jsDocPropertyTags), ListFormat.JSDocComment); + if (currentSourceFile && currentSourceFile.moduleName) { + writeComment(`/// `); + writeLine(); } - - function emitJSDocSignature(sig: JSDocSignature) { - if (sig.typeParameters) { - emitList(sig, factory.createNodeArray(sig.typeParameters), ListFormat.JSDocComment); - } - if (sig.parameters) { - emitList(sig, factory.createNodeArray(sig.parameters), ListFormat.JSDocComment); - } - if (sig.type) { + if (currentSourceFile && currentSourceFile.amdDependencies) { + for (const dep of currentSourceFile.amdDependencies) { + if (dep.name) { + writeComment(`/// `); + } + else { + writeComment(`/// `); + } writeLine(); - writeSpace(); - writePunctuation("*"); - writeSpace(); - emit(sig.type); } } - - function emitJSDocPropertyLikeTag(param: JSDocPropertyLikeTag) { - emitJSDocTagName(param.tagName); - emitJSDocTypeExpression(param.typeExpression); - writeSpace(); - if (param.isBracketed) { - writePunctuation("["); - } - emit(param.name); - if (param.isBracketed) { - writePunctuation("]"); - } - emitJSDocComment(param.comment); + for (const directive of files) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Reference, data: directive.fileName }); + writeLine(); } - - function emitJSDocTagName(tagName: Identifier) { - writePunctuation("@"); - emit(tagName); + for (const directive of types) { + const pos = writer.getTextPos(); + const resolutionMode = directive.resolutionMode && directive.resolutionMode !== currentSourceFile?.impliedNodeFormat + ? `resolution-mode="${directive.resolutionMode === ModuleKind.ESNext ? "import" : "require"}"` + : ""; + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: !directive.resolutionMode ? BundleFileSectionKind.Type : directive.resolutionMode === ModuleKind.ESNext ? BundleFileSectionKind.TypeResolutionModeImport : BundleFileSectionKind.TypeResolutionModeRequire, data: directive.fileName }); + writeLine(); } - - function emitJSDocComment(comment: string | NodeArray | undefined) { - const text = getTextOfJSDocComment(comment); - if (text) { - writeSpace(); - write(text); - } + for (const directive of libs) { + const pos = writer.getTextPos(); + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Lib, data: directive.fileName }); + writeLine(); } + } - function emitJSDocTypeExpression(typeExpression: JSDocTypeExpression | undefined) { - if (typeExpression) { - writeSpace(); - writePunctuation("{"); - emit(typeExpression.type); - writePunctuation("}"); - } - } + function emitSourceFileWorker(node: SourceFile) { + const statements = node.statements; + pushNameGenerationScope(node); + forEach(node.statements, generateNames); + emitHelpers(node); + const index = findIndex(statements, statement => !isPrologueDirective(statement)); + emitTripleSlashDirectivesIfNeeded(node); + emitList(node, statements, ListFormat.MultiLine, /*parenthesizerRule*/ undefined, index === -1 ? statements.length : index); + popNameGenerationScope(node); + } - // - // Top-level nodes - // + // Transformation nodes - function emitSourceFile(node: SourceFile) { - writeLine(); - const statements = node.statements; - // Emit detached comment if there are no prologue directives or if the first node is synthesized. - // The synthesized node will have no leading comment so some comments may be missed. - const shouldEmitDetachedComment = statements.length === 0 || - !isPrologueDirective(statements[0]) || - nodeIsSynthesized(statements[0]); - if (shouldEmitDetachedComment) { - emitBodyWithDetachedComments(node, statements, emitSourceFileWorker); - return; - } - emitSourceFileWorker(node); + function emitPartiallyEmittedExpression(node: PartiallyEmittedExpression) { + const emitFlags = getEmitFlags(node); + if (!(emitFlags & EmitFlags.NoLeadingComments) && node.pos !== node.expression.pos) { + emitTrailingCommentsOfPosition(node.expression.pos); } - - function emitSyntheticTripleSlashReferencesIfNeeded(node: Bundle) { - emitTripleSlashDirectives(!!node.hasNoDefaultLib, node.syntheticFileReferences || [], node.syntheticTypeReferences || [], node.syntheticLibReferences || []); - for (const prepend of node.prepends) { - if (isUnparsedSource(prepend) && prepend.syntheticReferences) { - for (const ref of prepend.syntheticReferences) { - emit(ref); - writeLine(); - } - } - } + emitExpression(node.expression); + if (!(emitFlags & EmitFlags.NoTrailingComments) && node.end !== node.expression.end) { + emitLeadingCommentsOfPosition(node.expression.end); } + } - function emitTripleSlashDirectivesIfNeeded(node: SourceFile) { - if (node.isDeclarationFile) emitTripleSlashDirectives(node.hasNoDefaultLib, node.referencedFiles, node.typeReferenceDirectives, node.libReferenceDirectives); - } + function emitCommaList(node: CommaListExpression) { + emitExpressionList(node, node.elements, ListFormat.CommaListElements, /*parenthesizerRule*/ undefined); + } - function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: readonly FileReference[], types: readonly FileReference[], libs: readonly FileReference[]) { - if (hasNoDefaultLib) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.NoDefaultLib }); - writeLine(); - } - if (currentSourceFile && currentSourceFile.moduleName) { - writeComment(`/// `); - writeLine(); - } - if (currentSourceFile && currentSourceFile.amdDependencies) { - for (const dep of currentSourceFile.amdDependencies) { - if (dep.name) { - writeComment(`/// `); - } - else { - writeComment(`/// `); + /** + * Emits any prologue directives at the start of a Statement list, returning the + * number of prologue directives written to the output. + */ + function emitPrologueDirectives(statements: readonly Node[], sourceFile?: SourceFile, seenPrologueDirectives?: Set, recordBundleFileSection?: true): number { + let needsToSetSourceFile = !!sourceFile; + for (let i = 0; i < statements.length; i++) { + const statement = statements[i]; + if (isPrologueDirective(statement)) { + const shouldEmitPrologueDirective = seenPrologueDirectives ? !seenPrologueDirectives.has(statement.expression.text) : true; + if (shouldEmitPrologueDirective) { + if (needsToSetSourceFile) { + needsToSetSourceFile = false; + setSourceFile(sourceFile); } writeLine(); + const pos = writer.getTextPos(); + emit(statement); + if (recordBundleFileSection && bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: statement.expression.text }); + if (seenPrologueDirectives) { + seenPrologueDirectives.add(statement.expression.text); + } } } - for (const directive of files) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Reference, data: directive.fileName }); - writeLine(); - } - for (const directive of types) { - const pos = writer.getTextPos(); - const resolutionMode = directive.resolutionMode && directive.resolutionMode !== currentSourceFile?.impliedNodeFormat - ? `resolution-mode="${directive.resolutionMode === ModuleKind.ESNext ? "import" : "require"}"` - : ""; - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: !directive.resolutionMode ? BundleFileSectionKind.Type : directive.resolutionMode === ModuleKind.ESNext ? BundleFileSectionKind.TypeResolutionModeImport : BundleFileSectionKind.TypeResolutionModeRequire, data: directive.fileName }); - writeLine(); - } - for (const directive of libs) { - const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Lib, data: directive.fileName }); - writeLine(); + else { + // return index of the first non prologue directive + return i; } } - function emitSourceFileWorker(node: SourceFile) { - const statements = node.statements; - pushNameGenerationScope(node); - forEach(node.statements, generateNames); - emitHelpers(node); - const index = findIndex(statements, statement => !isPrologueDirective(statement)); - emitTripleSlashDirectivesIfNeeded(node); - emitList(node, statements, ListFormat.MultiLine, /*parenthesizerRule*/ undefined, index === -1 ? statements.length : index); - popNameGenerationScope(node); - } - - // Transformation nodes - - function emitPartiallyEmittedExpression(node: PartiallyEmittedExpression) { - const emitFlags = getEmitFlags(node); - if (!(emitFlags & EmitFlags.NoLeadingComments) && node.pos !== node.expression.pos) { - emitTrailingCommentsOfPosition(node.expression.pos); - } - emitExpression(node.expression); - if (!(emitFlags & EmitFlags.NoTrailingComments) && node.end !== node.expression.end) { - emitLeadingCommentsOfPosition(node.expression.end); - } - } + return statements.length; + } - function emitCommaList(node: CommaListExpression) { - emitExpressionList(node, node.elements, ListFormat.CommaListElements, /*parenthesizerRule*/ undefined); - } - - /** - * Emits any prologue directives at the start of a Statement list, returning the - * number of prologue directives written to the output. - */ - function emitPrologueDirectives(statements: readonly Node[], sourceFile?: SourceFile, seenPrologueDirectives?: Set, recordBundleFileSection?: true): number { - let needsToSetSourceFile = !!sourceFile; - for (let i = 0; i < statements.length; i++) { - const statement = statements[i]; - if (isPrologueDirective(statement)) { - const shouldEmitPrologueDirective = seenPrologueDirectives ? !seenPrologueDirectives.has(statement.expression.text) : true; - if (shouldEmitPrologueDirective) { - if (needsToSetSourceFile) { - needsToSetSourceFile = false; - setSourceFile(sourceFile); - } - writeLine(); - const pos = writer.getTextPos(); - emit(statement); - if (recordBundleFileSection && bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: statement.expression.text }); - if (seenPrologueDirectives) { - seenPrologueDirectives.add(statement.expression.text); - } - } - } - else { - // return index of the first non prologue directive - return i; + function emitUnparsedPrologues(prologues: readonly UnparsedPrologue[], seenPrologueDirectives: Set) { + for (const prologue of prologues) { + if (!seenPrologueDirectives.has(prologue.data)) { + writeLine(); + const pos = writer.getTextPos(); + emit(prologue); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: prologue.data }); + if (seenPrologueDirectives) { + seenPrologueDirectives.add(prologue.data); } } - - return statements.length; } + } - function emitUnparsedPrologues(prologues: readonly UnparsedPrologue[], seenPrologueDirectives: Set) { - for (const prologue of prologues) { - if (!seenPrologueDirectives.has(prologue.data)) { - writeLine(); - const pos = writer.getTextPos(); - emit(prologue); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: prologue.data }); - if (seenPrologueDirectives) { - seenPrologueDirectives.add(prologue.data); - } - } + function emitPrologueDirectivesIfNeeded(sourceFileOrBundle: Bundle | SourceFile) { + if (isSourceFile(sourceFileOrBundle)) { + emitPrologueDirectives(sourceFileOrBundle.statements, sourceFileOrBundle); + } + else { + const seenPrologueDirectives = new Set(); + for (const prepend of sourceFileOrBundle.prepends) { + emitUnparsedPrologues((prepend as UnparsedSource).prologues, seenPrologueDirectives); } + for (const sourceFile of sourceFileOrBundle.sourceFiles) { + emitPrologueDirectives(sourceFile.statements, sourceFile, seenPrologueDirectives, /*recordBundleFileSection*/ true); + } + setSourceFile(undefined); } + } - function emitPrologueDirectivesIfNeeded(sourceFileOrBundle: Bundle | SourceFile) { - if (isSourceFile(sourceFileOrBundle)) { - emitPrologueDirectives(sourceFileOrBundle.statements, sourceFileOrBundle); - } - else { - const seenPrologueDirectives = new Set(); - for (const prepend of sourceFileOrBundle.prepends) { - emitUnparsedPrologues((prepend as UnparsedSource).prologues, seenPrologueDirectives); - } - for (const sourceFile of sourceFileOrBundle.sourceFiles) { - emitPrologueDirectives(sourceFile.statements, sourceFile, seenPrologueDirectives, /*recordBundleFileSection*/ true); - } - setSourceFile(undefined); + function getPrologueDirectivesFromBundledSourceFiles(bundle: Bundle): SourceFilePrologueInfo[] | undefined { + const seenPrologueDirectives = new Set(); + let prologues: SourceFilePrologueInfo[] | undefined; + for (let index = 0; index < bundle.sourceFiles.length; index++) { + const sourceFile = bundle.sourceFiles[index]; + let directives: SourceFilePrologueDirective[] | undefined; + let end = 0; + for (const statement of sourceFile.statements) { + if (!isPrologueDirective(statement)) break; + if (seenPrologueDirectives.has(statement.expression.text)) continue; + seenPrologueDirectives.add(statement.expression.text); + (directives || (directives = [])).push({ + pos: statement.pos, + end: statement.end, + expression: { + pos: statement.expression.pos, + end: statement.expression.end, + text: statement.expression.text + } + }); + end = end < statement.end ? statement.end : end; } + if (directives) (prologues || (prologues = [])).push({ file: index, text: sourceFile.text.substring(0, end), directives }); } + return prologues; + } - function getPrologueDirectivesFromBundledSourceFiles(bundle: Bundle): SourceFilePrologueInfo[] | undefined { - const seenPrologueDirectives = new Set(); - let prologues: SourceFilePrologueInfo[] | undefined; - for (let index = 0; index < bundle.sourceFiles.length; index++) { - const sourceFile = bundle.sourceFiles[index]; - let directives: SourceFilePrologueDirective[] | undefined; - let end = 0; - for (const statement of sourceFile.statements) { - if (!isPrologueDirective(statement)) break; - if (seenPrologueDirectives.has(statement.expression.text)) continue; - seenPrologueDirectives.add(statement.expression.text); - (directives || (directives = [])).push({ - pos: statement.pos, - end: statement.end, - expression: { - pos: statement.expression.pos, - end: statement.expression.end, - text: statement.expression.text - } - }); - end = end < statement.end ? statement.end : end; - } - if (directives) (prologues || (prologues = [])).push({ file: index, text: sourceFile.text.substring(0, end), directives }); + function emitShebangIfNeeded(sourceFileOrBundle: Bundle | SourceFile | UnparsedSource) { + if (isSourceFile(sourceFileOrBundle) || isUnparsedSource(sourceFileOrBundle)) { + const shebang = getShebang(sourceFileOrBundle.text); + if (shebang) { + writeComment(shebang); + writeLine(); + return true; } - return prologues; } - - function emitShebangIfNeeded(sourceFileOrBundle: Bundle | SourceFile | UnparsedSource) { - if (isSourceFile(sourceFileOrBundle) || isUnparsedSource(sourceFileOrBundle)) { - const shebang = getShebang(sourceFileOrBundle.text); - if (shebang) { - writeComment(shebang); - writeLine(); + else { + for (const prepend of sourceFileOrBundle.prepends) { + Debug.assertNode(prepend, isUnparsedSource); + if (emitShebangIfNeeded(prepend)) { return true; } } - else { - for (const prepend of sourceFileOrBundle.prepends) { - Debug.assertNode(prepend, isUnparsedSource); - if (emitShebangIfNeeded(prepend)) { - return true; - } - } - for (const sourceFile of sourceFileOrBundle.sourceFiles) { - // Emit only the first encountered shebang - if (emitShebangIfNeeded(sourceFile)) { - return true; - } + for (const sourceFile of sourceFileOrBundle.sourceFiles) { + // Emit only the first encountered shebang + if (emitShebangIfNeeded(sourceFile)) { + return true; } } } + } - // - // Helpers - // + // + // Helpers + // - function emitNodeWithWriter(node: Node | undefined, writer: typeof write) { - if (!node) return; - const savedWrite = write; - write = writer; - emit(node); - write = savedWrite; - } + function emitNodeWithWriter(node: Node | undefined, writer: typeof write) { + if (!node) return; + const savedWrite = write; + write = writer; + emit(node); + write = savedWrite; + } - function emitDecoratorsAndModifiers(node: Node, modifiers: NodeArray | undefined) { - if (modifiers?.length) { - if (every(modifiers, isModifier)) { - // if all modifier-likes are `Modifier`, simply emit the array as modifiers. - return emitModifiers(node, modifiers as NodeArray); - } + function emitDecoratorsAndModifiers(node: Node, modifiers: NodeArray | undefined) { + if (modifiers?.length) { + if (every(modifiers, isModifier)) { + // if all modifier-likes are `Modifier`, simply emit the array as modifiers. + return emitModifiers(node, modifiers as NodeArray); + } - if (every(modifiers, isDecorator)) { - // if all modifier-likes are `Decorator`, simply emit the array as decorators. - return emitDecorators(node, modifiers as NodeArray); - } + if (every(modifiers, isDecorator)) { + // if all modifier-likes are `Decorator`, simply emit the array as decorators. + return emitDecorators(node, modifiers as NodeArray); + } - onBeforeEmitNodeArray?.(modifiers); - - // partition modifiers into contiguous chunks of `Modifier` or `Decorator` - let lastMode: "modifiers" | "decorators" | undefined; - let mode: "modifiers" | "decorators" | undefined; - let start = 0; - let pos = 0; - while (start < modifiers.length) { - while (pos < modifiers.length) { - const modifier = modifiers[pos]; - mode = isDecorator(modifier) ? "decorators" : "modifiers"; - if (lastMode === undefined) { - lastMode = mode; - } - else if (mode !== lastMode) { - break; - } + onBeforeEmitNodeArray?.(modifiers); - pos++; + // partition modifiers into contiguous chunks of `Modifier` or `Decorator` + let lastMode: "modifiers" | "decorators" | undefined; + let mode: "modifiers" | "decorators" | undefined; + let start = 0; + let pos = 0; + while (start < modifiers.length) { + while (pos < modifiers.length) { + const modifier = modifiers[pos]; + mode = isDecorator(modifier) ? "decorators" : "modifiers"; + if (lastMode === undefined) { + lastMode = mode; + } + else if (mode !== lastMode) { + break; } - const textRange: TextRange = { pos: -1, end: -1 }; - if (start === 0) textRange.pos = modifiers.pos; - if (pos === modifiers.length - 1) textRange.end = modifiers.end; - emitNodeListItems( - emit, - node, - modifiers, - lastMode === "modifiers" ? ListFormat.Modifiers : ListFormat.Decorators, - /*parenthesizerRule*/ undefined, - start, - pos - start, - /*hasTrailingComma*/ false, - textRange); - start = pos; - lastMode = mode; pos++; } - onAfterEmitNodeArray?.(modifiers); - } + const textRange: TextRange = { pos: -1, end: -1 }; + if (start === 0) textRange.pos = modifiers.pos; + if (pos === modifiers.length - 1) textRange.end = modifiers.end; + emitNodeListItems( + emit, + node, + modifiers, + lastMode === "modifiers" ? ListFormat.Modifiers : ListFormat.Decorators, + /*parenthesizerRule*/ undefined, + start, + pos - start, + /*hasTrailingComma*/ false, + textRange); + start = pos; + lastMode = mode; + pos++; + } + + onAfterEmitNodeArray?.(modifiers); } + } - function emitModifiers(node: Node, modifiers: NodeArray | undefined): void { - emitList(node, modifiers, ListFormat.Modifiers); - } + function emitModifiers(node: Node, modifiers: NodeArray | undefined): void { + emitList(node, modifiers, ListFormat.Modifiers); + } - function emitTypeAnnotation(node: TypeNode | undefined) { - if (node) { - writePunctuation(":"); - writeSpace(); - emit(node); - } + function emitTypeAnnotation(node: TypeNode | undefined) { + if (node) { + writePunctuation(":"); + writeSpace(); + emit(node); } + } - function emitInitializer(node: Expression | undefined, equalCommentStartPos: number, container: Node, parenthesizerRule?: (node: Expression) => Expression) { - if (node) { - writeSpace(); - emitTokenWithComment(SyntaxKind.EqualsToken, equalCommentStartPos, writeOperator, container); - writeSpace(); - emitExpression(node, parenthesizerRule); - } + function emitInitializer(node: Expression | undefined, equalCommentStartPos: number, container: Node, parenthesizerRule?: (node: Expression) => Expression) { + if (node) { + writeSpace(); + emitTokenWithComment(SyntaxKind.EqualsToken, equalCommentStartPos, writeOperator, container); + writeSpace(); + emitExpression(node, parenthesizerRule); } + } - function emitNodeWithPrefix(prefix: string, prefixWriter: (s: string) => void, node: T | undefined, emit: (node: T) => void) { - if (node) { - prefixWriter(prefix); - emit(node); - } + function emitNodeWithPrefix(prefix: string, prefixWriter: (s: string) => void, node: T | undefined, emit: (node: T) => void) { + if (node) { + prefixWriter(prefix); + emit(node); } + } - function emitWithLeadingSpace(node: Node | undefined) { - if (node) { - writeSpace(); - emit(node); - } + function emitWithLeadingSpace(node: Node | undefined) { + if (node) { + writeSpace(); + emit(node); } + } - function emitExpressionWithLeadingSpace(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression) { - if (node) { - writeSpace(); - emitExpression(node, parenthesizerRule); - } + function emitExpressionWithLeadingSpace(node: Expression | undefined, parenthesizerRule?: (node: Expression) => Expression) { + if (node) { + writeSpace(); + emitExpression(node, parenthesizerRule); } + } - function emitWithTrailingSpace(node: Node | undefined) { - if (node) { - emit(node); - writeSpace(); - } + function emitWithTrailingSpace(node: Node | undefined) { + if (node) { + emit(node); + writeSpace(); } + } - function emitEmbeddedStatement(parent: Node, node: Statement) { - if (isBlock(node) || getEmitFlags(parent) & EmitFlags.SingleLine) { - writeSpace(); - emit(node); + function emitEmbeddedStatement(parent: Node, node: Statement) { + if (isBlock(node) || getEmitFlags(parent) & EmitFlags.SingleLine) { + writeSpace(); + emit(node); + } + else { + writeLine(); + increaseIndent(); + if (isEmptyStatement(node)) { + pipelineEmit(EmitHint.EmbeddedStatement, node); } else { - writeLine(); - increaseIndent(); - if (isEmptyStatement(node)) { - pipelineEmit(EmitHint.EmbeddedStatement, node); - } - else { - emit(node); - } - decreaseIndent(); + emit(node); } + decreaseIndent(); } + } - function emitDecorators(parentNode: Node, decorators: NodeArray | undefined): void { - emitList(parentNode, decorators, ListFormat.Decorators); - } - - function emitTypeArguments(parentNode: Node, typeArguments: NodeArray | undefined) { - emitList(parentNode, typeArguments, ListFormat.TypeArguments, typeArgumentParenthesizerRuleSelector); - } + function emitDecorators(parentNode: Node, decorators: NodeArray | undefined): void { + emitList(parentNode, decorators, ListFormat.Decorators); + } - function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray | undefined) { - if (isFunctionLike(parentNode) && parentNode.typeArguments) { // Quick info uses type arguments in place of type parameters on instantiated signatures - return emitTypeArguments(parentNode, parentNode.typeArguments); - } - emitList(parentNode, typeParameters, ListFormat.TypeParameters); - } + function emitTypeArguments(parentNode: Node, typeArguments: NodeArray | undefined) { + emitList(parentNode, typeArguments, ListFormat.TypeArguments, typeArgumentParenthesizerRuleSelector); + } - function emitParameters(parentNode: Node, parameters: NodeArray) { - emitList(parentNode, parameters, ListFormat.Parameters); + function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray | undefined) { + if (isFunctionLike(parentNode) && parentNode.typeArguments) { // Quick info uses type arguments in place of type parameters on instantiated signatures + return emitTypeArguments(parentNode, parentNode.typeArguments); } + emitList(parentNode, typeParameters, ListFormat.TypeParameters); + } - function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { - const parameter = singleOrUndefined(parameters); - return parameter - && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter - && isArrowFunction(parentNode) // only arrow functions may have simple arrow head - && !parentNode.type // arrow function may not have return type annotation - && !some(parentNode.modifiers) // parent may not have decorators or modifiers - && !some(parentNode.typeParameters) // parent may not have type parameters - && !some(parameter.modifiers) // parameter may not have decorators or modifiers - && !parameter.dotDotDotToken // parameter may not be rest - && !parameter.questionToken // parameter may not be optional - && !parameter.type // parameter may not have a type annotation - && !parameter.initializer // parameter may not have an initializer - && isIdentifier(parameter.name); // parameter name must be identifier - } + function emitParameters(parentNode: Node, parameters: NodeArray) { + emitList(parentNode, parameters, ListFormat.Parameters); + } - function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { - if (canEmitSimpleArrowHead(parentNode, parameters)) { - emitList(parentNode, parameters, ListFormat.Parameters & ~ListFormat.Parenthesis); - } - else { - emitParameters(parentNode, parameters); - } - } + function canEmitSimpleArrowHead(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { + const parameter = singleOrUndefined(parameters); + return parameter + && parameter.pos === parentNode.pos // may not have parsed tokens between parent and parameter + && isArrowFunction(parentNode) // only arrow functions may have simple arrow head + && !parentNode.type // arrow function may not have return type annotation + && !some(parentNode.modifiers) // parent may not have decorators or modifiers + && !some(parentNode.typeParameters) // parent may not have type parameters + && !some(parameter.modifiers) // parameter may not have decorators or modifiers + && !parameter.dotDotDotToken // parameter may not be rest + && !parameter.questionToken // parameter may not be optional + && !parameter.type // parameter may not have a type annotation + && !parameter.initializer // parameter may not have an initializer + && isIdentifier(parameter.name); // parameter name must be identifier + } - function emitParametersForIndexSignature(parentNode: Node, parameters: NodeArray) { - emitList(parentNode, parameters, ListFormat.IndexSignatureParameters); + function emitParametersForArrow(parentNode: FunctionTypeNode | ArrowFunction, parameters: NodeArray) { + if (canEmitSimpleArrowHead(parentNode, parameters)) { + emitList(parentNode, parameters, ListFormat.Parameters & ~ListFormat.Parenthesis); } - - function writeDelimiter(format: ListFormat) { - switch (format & ListFormat.DelimitersMask) { - case ListFormat.None: - break; - case ListFormat.CommaDelimited: - writePunctuation(","); - break; - case ListFormat.BarDelimited: - writeSpace(); - writePunctuation("|"); - break; - case ListFormat.AsteriskDelimited: - writeSpace(); - writePunctuation("*"); - writeSpace(); - break; - case ListFormat.AmpersandDelimited: - writeSpace(); - writePunctuation("&"); - break; - } + else { + emitParameters(parentNode, parameters); } + } - function emitList(parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule?: ParenthesizerRuleOrSelector, start?: number, count?: number) { - emitNodeList(emit, parentNode, children, format, parenthesizerRule, start, count); - } + function emitParametersForIndexSignature(parentNode: Node, parameters: NodeArray) { + emitList(parentNode, parameters, ListFormat.IndexSignatureParameters); + } - function emitExpressionList(parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule?: ParenthesizerRuleOrSelector, start?: number, count?: number) { - emitNodeList(emitExpression, parentNode, children, format, parenthesizerRule, start, count); + function writeDelimiter(format: ListFormat) { + switch (format & ListFormat.DelimitersMask) { + case ListFormat.None: + break; + case ListFormat.CommaDelimited: + writePunctuation(","); + break; + case ListFormat.BarDelimited: + writeSpace(); + writePunctuation("|"); + break; + case ListFormat.AsteriskDelimited: + writeSpace(); + writePunctuation("*"); + writeSpace(); + break; + case ListFormat.AmpersandDelimited: + writeSpace(); + writePunctuation("&"); + break; } + } - function emitNodeList(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule: ParenthesizerRuleOrSelector | undefined, start = 0, count = children ? children.length - start : 0) { - const isUndefined = children === undefined; - if (isUndefined && format & ListFormat.OptionalIfUndefined) { - return; - } + function emitList(parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule?: ParenthesizerRuleOrSelector, start?: number, count?: number) { + emitNodeList(emit, parentNode, children, format, parenthesizerRule, start, count); + } - const isEmpty = children === undefined || start >= children.length || count === 0; - if (isEmpty && format & ListFormat.OptionalIfEmpty) { - onBeforeEmitNodeArray?.(children); - onAfterEmitNodeArray?.(children); - return; - } + function emitExpressionList(parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule?: ParenthesizerRuleOrSelector, start?: number, count?: number) { + emitNodeList(emitExpression, parentNode, children, format, parenthesizerRule, start, count); + } - if (format & ListFormat.BracketsMask) { - writePunctuation(getOpeningBracket(format)); - if (isEmpty && children) { - emitTrailingCommentsOfPosition(children.pos, /*prefixSpace*/ true); // Emit comments within empty bracketed lists - } - } + function emitNodeList(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parentNode: Node | undefined, children: NodeArray | undefined, format: ListFormat, parenthesizerRule: ParenthesizerRuleOrSelector | undefined, start = 0, count = children ? children.length - start : 0) { + const isUndefined = children === undefined; + if (isUndefined && format & ListFormat.OptionalIfUndefined) { + return; + } + const isEmpty = children === undefined || start >= children.length || count === 0; + if (isEmpty && format & ListFormat.OptionalIfEmpty) { onBeforeEmitNodeArray?.(children); - - if (isEmpty) { - // Write a line terminator if the parent node was multi-line - if (format & ListFormat.MultiLine && !(preserveSourceNewlines && (!parentNode || currentSourceFile && rangeIsOnSingleLine(parentNode, currentSourceFile)))) { - writeLine(); - } - else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) { - writeSpace(); - } - } - else { - emitNodeListItems(emit, parentNode, children, format, parenthesizerRule, start, count, children.hasTrailingComma, children); - } - onAfterEmitNodeArray?.(children); + return; + } - if (format & ListFormat.BracketsMask) { - if (isEmpty && children) { - emitLeadingCommentsOfPosition(children.end); // Emit leading comments within empty lists - } - writePunctuation(getClosingBracket(format)); + if (format & ListFormat.BracketsMask) { + writePunctuation(getOpeningBracket(format)); + if (isEmpty && children) { + emitTrailingCommentsOfPosition(children.pos, /*prefixSpace*/ true); // Emit comments within empty bracketed lists } } - /** - * Emits a list without brackets or raising events. - * - * NOTE: You probably don't want to call this directly and should be using `emitList` or `emitExpressionList` instead. - */ - function emitNodeListItems(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parentNode: Node | undefined, children: readonly Node[], format: ListFormat, parenthesizerRule: ParenthesizerRuleOrSelector | undefined, start: number, count: number, hasTrailingComma: boolean, childrenTextRange: TextRange | undefined) { - // Write the opening line terminator or leading whitespace. - const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0; - let shouldEmitInterveningComments = mayEmitInterveningComments; - - const leadingLineTerminatorCount = getLeadingLineTerminatorCount(parentNode, children[start], format); - if (leadingLineTerminatorCount) { - writeLine(leadingLineTerminatorCount); - shouldEmitInterveningComments = false; + onBeforeEmitNodeArray?.(children); + + if (isEmpty) { + // Write a line terminator if the parent node was multi-line + if (format & ListFormat.MultiLine && !(preserveSourceNewlines && (!parentNode || currentSourceFile && rangeIsOnSingleLine(parentNode, currentSourceFile)))) { + writeLine(); } - else if (format & ListFormat.SpaceBetweenBraces) { + else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) { writeSpace(); } + } + else { + emitNodeListItems(emit, parentNode, children, format, parenthesizerRule, start, count, children.hasTrailingComma, children); + } - // Increase the indent, if requested. - if (format & ListFormat.Indented) { - increaseIndent(); - } + onAfterEmitNodeArray?.(children); - const emitListItem = getEmitListItem(emit, parenthesizerRule); + if (format & ListFormat.BracketsMask) { + if (isEmpty && children) { + emitLeadingCommentsOfPosition(children.end); // Emit leading comments within empty lists + } + writePunctuation(getClosingBracket(format)); + } + } - // Emit each child. - let previousSibling: Node | undefined; - let previousSourceFileTextKind: ReturnType; - let shouldDecreaseIndentAfterEmit = false; - for (let i = 0; i < count; i++) { - const child = children[start + i]; + /** + * Emits a list without brackets or raising events. + * + * NOTE: You probably don't want to call this directly and should be using `emitList` or `emitExpressionList` instead. + */ + function emitNodeListItems(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parentNode: Node | undefined, children: readonly Node[], format: ListFormat, parenthesizerRule: ParenthesizerRuleOrSelector | undefined, start: number, count: number, hasTrailingComma: boolean, childrenTextRange: TextRange | undefined) { + // Write the opening line terminator or leading whitespace. + const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0; + let shouldEmitInterveningComments = mayEmitInterveningComments; - // Write the delimiter if this is not the first node. - if (format & ListFormat.AsteriskDelimited) { - // always write JSDoc in the format "\n *" - writeLine(); - writeDelimiter(format); - } - else if (previousSibling) { - // i.e - // function commentedParameters( - // /* Parameter a */ - // a - // /* End of parameter a */ -> this comment isn't considered to be trailing comment of parameter "a" due to newline - // , - if (format & ListFormat.DelimitersMask && previousSibling.end !== (parentNode ? parentNode.end : -1)) { - emitLeadingCommentsOfPosition(previousSibling.end); - } - writeDelimiter(format); - recordBundleFileInternalSectionEnd(previousSourceFileTextKind); - - // Write either a line terminator or whitespace to separate the elements. - const separatingLineTerminatorCount = getSeparatingLineTerminatorCount(previousSibling, child, format); - if (separatingLineTerminatorCount > 0) { - // If a synthesized node in a single-line list starts on a new - // line, we should increase the indent. - if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) { - increaseIndent(); - shouldDecreaseIndentAfterEmit = true; - } + const leadingLineTerminatorCount = getLeadingLineTerminatorCount(parentNode, children[start], format); + if (leadingLineTerminatorCount) { + writeLine(leadingLineTerminatorCount); + shouldEmitInterveningComments = false; + } + else if (format & ListFormat.SpaceBetweenBraces) { + writeSpace(); + } - writeLine(separatingLineTerminatorCount); - shouldEmitInterveningComments = false; - } - else if (previousSibling && format & ListFormat.SpaceBetweenSiblings) { - writeSpace(); - } - } + // Increase the indent, if requested. + if (format & ListFormat.Indented) { + increaseIndent(); + } - // Emit this child. - previousSourceFileTextKind = recordBundleFileInternalSectionStart(child); - if (shouldEmitInterveningComments) { - const commentRange = getCommentRange(child); - emitTrailingCommentsOfPosition(commentRange.pos); - } - else { - shouldEmitInterveningComments = mayEmitInterveningComments; - } + const emitListItem = getEmitListItem(emit, parenthesizerRule); - nextListElementPos = child.pos; - emitListItem(child, emit, parenthesizerRule, i); + // Emit each child. + let previousSibling: Node | undefined; + let previousSourceFileTextKind: ReturnType; + let shouldDecreaseIndentAfterEmit = false; + for (let i = 0; i < count; i++) { + const child = children[start + i]; - if (shouldDecreaseIndentAfterEmit) { - decreaseIndent(); - shouldDecreaseIndentAfterEmit = false; + // Write the delimiter if this is not the first node. + if (format & ListFormat.AsteriskDelimited) { + // always write JSDoc in the format "\n *" + writeLine(); + writeDelimiter(format); + } + else if (previousSibling) { + // i.e + // function commentedParameters( + // /* Parameter a */ + // a + // /* End of parameter a */ -> this comment isn't considered to be trailing comment of parameter "a" due to newline + // , + if (format & ListFormat.DelimitersMask && previousSibling.end !== (parentNode ? parentNode.end : -1)) { + emitLeadingCommentsOfPosition(previousSibling.end); } + writeDelimiter(format); + recordBundleFileInternalSectionEnd(previousSourceFileTextKind); + + // Write either a line terminator or whitespace to separate the elements. + const separatingLineTerminatorCount = getSeparatingLineTerminatorCount(previousSibling, child, format); + if (separatingLineTerminatorCount > 0) { + // If a synthesized node in a single-line list starts on a new + // line, we should increase the indent. + if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) { + increaseIndent(); + shouldDecreaseIndentAfterEmit = true; + } - previousSibling = child; - } - - // Write a trailing comma, if requested. - const emitFlags = previousSibling ? getEmitFlags(previousSibling) : 0; - const skipTrailingComments = commentsDisabled || !!(emitFlags & EmitFlags.NoTrailingComments); - const emitTrailingComma = hasTrailingComma && (format & ListFormat.AllowTrailingComma) && (format & ListFormat.CommaDelimited); - if (emitTrailingComma) { - if (previousSibling && !skipTrailingComments) { - emitTokenWithComment(SyntaxKind.CommaToken, previousSibling.end, writePunctuation, previousSibling); + writeLine(separatingLineTerminatorCount); + shouldEmitInterveningComments = false; } - else { - writePunctuation(","); + else if (previousSibling && format & ListFormat.SpaceBetweenSiblings) { + writeSpace(); } } - // Emit any trailing comment of the last element in the list - // i.e - // var array = [... - // 2 - // /* end of element 2 */ - // ]; - if (previousSibling && (parentNode ? parentNode.end : -1) !== previousSibling.end && (format & ListFormat.DelimitersMask) && !skipTrailingComments) { - emitLeadingCommentsOfPosition(emitTrailingComma && childrenTextRange?.end ? childrenTextRange.end : previousSibling.end); + // Emit this child. + previousSourceFileTextKind = recordBundleFileInternalSectionStart(child); + if (shouldEmitInterveningComments) { + const commentRange = getCommentRange(child); + emitTrailingCommentsOfPosition(commentRange.pos); + } + else { + shouldEmitInterveningComments = mayEmitInterveningComments; } - // Decrease the indent, if requested. - if (format & ListFormat.Indented) { + nextListElementPos = child.pos; + emitListItem(child, emit, parenthesizerRule, i); + + if (shouldDecreaseIndentAfterEmit) { decreaseIndent(); + shouldDecreaseIndentAfterEmit = false; } - recordBundleFileInternalSectionEnd(previousSourceFileTextKind); + previousSibling = child; + } - // Write the closing line terminator or closing whitespace. - const closingLineTerminatorCount = getClosingLineTerminatorCount(parentNode, children[start + count - 1], format, childrenTextRange); - if (closingLineTerminatorCount) { - writeLine(closingLineTerminatorCount); + // Write a trailing comma, if requested. + const emitFlags = previousSibling ? getEmitFlags(previousSibling) : 0; + const skipTrailingComments = commentsDisabled || !!(emitFlags & EmitFlags.NoTrailingComments); + const emitTrailingComma = hasTrailingComma && (format & ListFormat.AllowTrailingComma) && (format & ListFormat.CommaDelimited); + if (emitTrailingComma) { + if (previousSibling && !skipTrailingComments) { + emitTokenWithComment(SyntaxKind.CommaToken, previousSibling.end, writePunctuation, previousSibling); } - else if (format & (ListFormat.SpaceAfterList | ListFormat.SpaceBetweenBraces)) { - writeSpace(); + else { + writePunctuation(","); } } - // Writers - - function writeLiteral(s: string) { - writer.writeLiteral(s); + // Emit any trailing comment of the last element in the list + // i.e + // var array = [... + // 2 + // /* end of element 2 */ + // ]; + if (previousSibling && (parentNode ? parentNode.end : -1) !== previousSibling.end && (format & ListFormat.DelimitersMask) && !skipTrailingComments) { + emitLeadingCommentsOfPosition(emitTrailingComma && childrenTextRange?.end ? childrenTextRange.end : previousSibling.end); } - function writeStringLiteral(s: string) { - writer.writeStringLiteral(s); + // Decrease the indent, if requested. + if (format & ListFormat.Indented) { + decreaseIndent(); } - function writeBase(s: string) { - writer.write(s); - } + recordBundleFileInternalSectionEnd(previousSourceFileTextKind); - function writeSymbol(s: string, sym: Symbol) { - writer.writeSymbol(s, sym); + // Write the closing line terminator or closing whitespace. + const closingLineTerminatorCount = getClosingLineTerminatorCount(parentNode, children[start + count - 1], format, childrenTextRange); + if (closingLineTerminatorCount) { + writeLine(closingLineTerminatorCount); } - - function writePunctuation(s: string) { - writer.writePunctuation(s); + else if (format & (ListFormat.SpaceAfterList | ListFormat.SpaceBetweenBraces)) { + writeSpace(); } + } - function writeTrailingSemicolon() { - writer.writeTrailingSemicolon(";"); - } + // Writers - function writeKeyword(s: string) { - writer.writeKeyword(s); - } + function writeLiteral(s: string) { + writer.writeLiteral(s); + } - function writeOperator(s: string) { - writer.writeOperator(s); - } + function writeStringLiteral(s: string) { + writer.writeStringLiteral(s); + } - function writeParameter(s: string) { - writer.writeParameter(s); - } + function writeBase(s: string) { + writer.write(s); + } - function writeComment(s: string) { - writer.writeComment(s); - } + function writeSymbol(s: string, sym: Symbol) { + writer.writeSymbol(s, sym); + } - function writeSpace() { - writer.writeSpace(" "); - } + function writePunctuation(s: string) { + writer.writePunctuation(s); + } - function writeProperty(s: string) { - writer.writeProperty(s); - } + function writeTrailingSemicolon() { + writer.writeTrailingSemicolon(";"); + } - function nonEscapingWrite(s: string) { - // This should be defined in a snippet-escaping text writer. - if (writer.nonEscapingWrite) { - writer.nonEscapingWrite(s); - } - else { - writer.write(s); - } - } + function writeKeyword(s: string) { + writer.writeKeyword(s); + } - function writeLine(count = 1) { - for (let i = 0; i < count; i++) { - writer.writeLine(i > 0); - } - } + function writeOperator(s: string) { + writer.writeOperator(s); + } - function increaseIndent() { - writer.increaseIndent(); - } + function writeParameter(s: string) { + writer.writeParameter(s); + } - function decreaseIndent() { - writer.decreaseIndent(); - } + function writeComment(s: string) { + writer.writeComment(s); + } + + function writeSpace() { + writer.writeSpace(" "); + } - function writeToken(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode?: Node) { - return !sourceMapsDisabled - ? emitTokenWithSourceMap(contextNode, token, writer, pos, writeTokenText) - : writeTokenText(token, writer, pos); + function writeProperty(s: string) { + writer.writeProperty(s); + } + + function nonEscapingWrite(s: string) { + // This should be defined in a snippet-escaping text writer. + if (writer.nonEscapingWrite) { + writer.nonEscapingWrite(s); + } + else { + writer.write(s); } + } - function writeTokenNode(node: Node, writer: (s: string) => void) { - if (onBeforeEmitToken) { - onBeforeEmitToken(node); - } - writer(tokenToString(node.kind)!); - if (onAfterEmitToken) { - onAfterEmitToken(node); - } + function writeLine(count = 1) { + for (let i = 0; i < count; i++) { + writer.writeLine(i > 0); } + } + + function increaseIndent() { + writer.increaseIndent(); + } + + function decreaseIndent() { + writer.decreaseIndent(); + } + + function writeToken(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode?: Node) { + return !sourceMapsDisabled + ? emitTokenWithSourceMap(contextNode, token, writer, pos, writeTokenText) + : writeTokenText(token, writer, pos); + } - function writeTokenText(token: SyntaxKind, writer: (s: string) => void): void; - function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos: number): number; - function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos?: number): number { - const tokenString = tokenToString(token)!; - writer(tokenString); - return pos! < 0 ? pos! : pos! + tokenString.length; + function writeTokenNode(node: Node, writer: (s: string) => void) { + if (onBeforeEmitToken) { + onBeforeEmitToken(node); + } + writer(tokenToString(node.kind)!); + if (onAfterEmitToken) { + onAfterEmitToken(node); } + } - function writeLineOrSpace(parentNode: Node, prevChildNode: Node, nextChildNode: Node) { - if (getEmitFlags(parentNode) & EmitFlags.SingleLine) { - writeSpace(); - } - else if (preserveSourceNewlines) { - const lines = getLinesBetweenNodes(parentNode, prevChildNode, nextChildNode); - if (lines) { - writeLine(lines); - } - else { - writeSpace(); - } + function writeTokenText(token: SyntaxKind, writer: (s: string) => void): void; + function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos: number): number; + function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos?: number): number { + const tokenString = tokenToString(token)!; + writer(tokenString); + return pos! < 0 ? pos! : pos! + tokenString.length; + } + + function writeLineOrSpace(parentNode: Node, prevChildNode: Node, nextChildNode: Node) { + if (getEmitFlags(parentNode) & EmitFlags.SingleLine) { + writeSpace(); + } + else if (preserveSourceNewlines) { + const lines = getLinesBetweenNodes(parentNode, prevChildNode, nextChildNode); + if (lines) { + writeLine(lines); } else { - writeLine(); + writeSpace(); } } - - function writeLines(text: string): void { - const lines = text.split(/\r\n?|\n/g); - const indentation = guessIndentation(lines); - for (const lineText of lines) { - const line = indentation ? lineText.slice(indentation) : lineText; - if (line.length) { - writeLine(); - write(line); - } - } + else { + writeLine(); } + } - function writeLinesAndIndent(lineCount: number, writeSpaceIfNotIndenting: boolean) { - if (lineCount) { - increaseIndent(); - writeLine(lineCount); - } - else if (writeSpaceIfNotIndenting) { - writeSpace(); + function writeLines(text: string): void { + const lines = text.split(/\r\n?|\n/g); + const indentation = guessIndentation(lines); + for (const lineText of lines) { + const line = indentation ? lineText.slice(indentation) : lineText; + if (line.length) { + writeLine(); + write(line); } } + } - // Helper function to decrease the indent if we previously indented. Allows multiple - // previous indent values to be considered at a time. This also allows caller to just - // call this once, passing in all their appropriate indent values, instead of needing - // to call this helper function multiple times. - function decreaseIndentIf(value1: boolean | number | undefined, value2?: boolean | number) { - if (value1) { - decreaseIndent(); - } - if (value2) { - decreaseIndent(); - } + function writeLinesAndIndent(lineCount: number, writeSpaceIfNotIndenting: boolean) { + if (lineCount) { + increaseIndent(); + writeLine(lineCount); + } + else if (writeSpaceIfNotIndenting) { + writeSpace(); } + } - function getLeadingLineTerminatorCount(parentNode: Node | undefined, firstChild: Node | undefined, format: ListFormat): number { - if (format & ListFormat.PreserveLines || preserveSourceNewlines) { - if (format & ListFormat.PreferNewLine) { - return 1; - } + // Helper function to decrease the indent if we previously indented. Allows multiple + // previous indent values to be considered at a time. This also allows caller to just + // call this once, passing in all their appropriate indent values, instead of needing + // to call this helper function multiple times. + function decreaseIndentIf(value1: boolean | number | undefined, value2?: boolean | number) { + if (value1) { + decreaseIndent(); + } + if (value2) { + decreaseIndent(); + } + } - if (firstChild === undefined) { - return !parentNode || currentSourceFile && rangeIsOnSingleLine(parentNode, currentSourceFile) ? 0 : 1; - } - if (firstChild.pos === nextListElementPos) { - // If this child starts at the beginning of a list item in a parent list, its leading - // line terminators have already been written as the separating line terminators of the - // parent list. Example: - // - // class Foo { - // constructor() {} - // public foo() {} - // } - // - // The outer list is the list of class members, with one line terminator between the - // constructor and the method. The constructor is written, the separating line terminator - // is written, and then we start emitting the method. Its modifiers ([public]) constitute an inner - // list, so we look for its leading line terminators. If we didn't know that we had already - // written a newline as part of the parent list, it would appear that we need to write a - // leading newline to start the modifiers. - return 0; - } - if (firstChild.kind === SyntaxKind.JsxText) { - // JsxText will be written with its leading whitespace, so don't add more manually. - return 0; - } - if (currentSourceFile && parentNode && - !positionIsSynthesized(parentNode.pos) && - !nodeIsSynthesized(firstChild) && - (!firstChild.parent || getOriginalNode(firstChild.parent) === getOriginalNode(parentNode)) - ) { - if (preserveSourceNewlines) { - return getEffectiveLines( - includeComments => getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter( - firstChild.pos, - parentNode.pos, - currentSourceFile!, - includeComments)); - } - return rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile) ? 0 : 1; - } - if (synthesizedNodeStartsOnNewLine(firstChild, format)) { - return 1; + function getLeadingLineTerminatorCount(parentNode: Node | undefined, firstChild: Node | undefined, format: ListFormat): number { + if (format & ListFormat.PreserveLines || preserveSourceNewlines) { + if (format & ListFormat.PreferNewLine) { + return 1; + } + + if (firstChild === undefined) { + return !parentNode || currentSourceFile && rangeIsOnSingleLine(parentNode, currentSourceFile) ? 0 : 1; + } + if (firstChild.pos === nextListElementPos) { + // If this child starts at the beginning of a list item in a parent list, its leading + // line terminators have already been written as the separating line terminators of the + // parent list. Example: + // + // class Foo { + // constructor() {} + // public foo() {} + // } + // + // The outer list is the list of class members, with one line terminator between the + // constructor and the method. The constructor is written, the separating line terminator + // is written, and then we start emitting the method. Its modifiers ([public]) constitute an inner + // list, so we look for its leading line terminators. If we didn't know that we had already + // written a newline as part of the parent list, it would appear that we need to write a + // leading newline to start the modifiers. + return 0; + } + if (firstChild.kind === SyntaxKind.JsxText) { + // JsxText will be written with its leading whitespace, so don't add more manually. + return 0; + } + if (currentSourceFile && parentNode && + !positionIsSynthesized(parentNode.pos) && + !nodeIsSynthesized(firstChild) && + (!firstChild.parent || getOriginalNode(firstChild.parent) === getOriginalNode(parentNode)) + ) { + if (preserveSourceNewlines) { + return getEffectiveLines( + includeComments => getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter( + firstChild.pos, + parentNode.pos, + currentSourceFile!, + includeComments)); } + return rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile) ? 0 : 1; + } + if (synthesizedNodeStartsOnNewLine(firstChild, format)) { + return 1; } - return format & ListFormat.MultiLine ? 1 : 0; } + return format & ListFormat.MultiLine ? 1 : 0; + } - function getSeparatingLineTerminatorCount(previousNode: Node | undefined, nextNode: Node, format: ListFormat): number { - if (format & ListFormat.PreserveLines || preserveSourceNewlines) { - if (previousNode === undefined || nextNode === undefined) { - return 0; - } - if (nextNode.kind === SyntaxKind.JsxText) { - // JsxText will be written with its leading whitespace, so don't add more manually. - return 0; - } - else if (currentSourceFile && !nodeIsSynthesized(previousNode) && !nodeIsSynthesized(nextNode)) { - if (preserveSourceNewlines && siblingNodePositionsAreComparable(previousNode, nextNode)) { - return getEffectiveLines( - includeComments => getLinesBetweenRangeEndAndRangeStart( - previousNode, - nextNode, - currentSourceFile!, - includeComments)); - } - // If `preserveSourceNewlines` is `false` we do not intend to preserve the effective lines between the - // previous and next node. Instead we naively check whether nodes are on separate lines within the - // same node parent. If so, we intend to preserve a single line terminator. This is less precise and - // expensive than checking with `preserveSourceNewlines` as above, but the goal is not to preserve the - // effective source lines between two sibling nodes. - else if (!preserveSourceNewlines && originalNodesHaveSameParent(previousNode, nextNode)) { - return rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile) ? 0 : 1; - } - // If the two nodes are not comparable, add a line terminator based on the format that can indicate - // whether new lines are preferred or not. - return format & ListFormat.PreferNewLine ? 1 : 0; + function getSeparatingLineTerminatorCount(previousNode: Node | undefined, nextNode: Node, format: ListFormat): number { + if (format & ListFormat.PreserveLines || preserveSourceNewlines) { + if (previousNode === undefined || nextNode === undefined) { + return 0; + } + if (nextNode.kind === SyntaxKind.JsxText) { + // JsxText will be written with its leading whitespace, so don't add more manually. + return 0; + } + else if (currentSourceFile && !nodeIsSynthesized(previousNode) && !nodeIsSynthesized(nextNode)) { + if (preserveSourceNewlines && siblingNodePositionsAreComparable(previousNode, nextNode)) { + return getEffectiveLines( + includeComments => getLinesBetweenRangeEndAndRangeStart( + previousNode, + nextNode, + currentSourceFile!, + includeComments)); } - else if (synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format)) { - return 1; + // If `preserveSourceNewlines` is `false` we do not intend to preserve the effective lines between the + // previous and next node. Instead we naively check whether nodes are on separate lines within the + // same node parent. If so, we intend to preserve a single line terminator. This is less precise and + // expensive than checking with `preserveSourceNewlines` as above, but the goal is not to preserve the + // effective source lines between two sibling nodes. + else if (!preserveSourceNewlines && originalNodesHaveSameParent(previousNode, nextNode)) { + return rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile) ? 0 : 1; } + // If the two nodes are not comparable, add a line terminator based on the format that can indicate + // whether new lines are preferred or not. + return format & ListFormat.PreferNewLine ? 1 : 0; } - else if (getStartsOnNewLine(nextNode)) { + else if (synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format)) { return 1; } - return format & ListFormat.MultiLine ? 1 : 0; } + else if (getStartsOnNewLine(nextNode)) { + return 1; + } + return format & ListFormat.MultiLine ? 1 : 0; + } - function getClosingLineTerminatorCount(parentNode: Node | undefined, lastChild: Node | undefined, format: ListFormat, childrenTextRange: TextRange | undefined): number { - if (format & ListFormat.PreserveLines || preserveSourceNewlines) { - if (format & ListFormat.PreferNewLine) { - return 1; - } + function getClosingLineTerminatorCount(parentNode: Node | undefined, lastChild: Node | undefined, format: ListFormat, childrenTextRange: TextRange | undefined): number { + if (format & ListFormat.PreserveLines || preserveSourceNewlines) { + if (format & ListFormat.PreferNewLine) { + return 1; + } - if (lastChild === undefined) { - return !parentNode || currentSourceFile && rangeIsOnSingleLine(parentNode, currentSourceFile) ? 0 : 1; - } - if (currentSourceFile && parentNode && !positionIsSynthesized(parentNode.pos) && !nodeIsSynthesized(lastChild) && (!lastChild.parent || lastChild.parent === parentNode)) { - if (preserveSourceNewlines) { - const end = childrenTextRange && !positionIsSynthesized(childrenTextRange.end) ? childrenTextRange.end : lastChild.end; - return getEffectiveLines( - includeComments => getLinesBetweenPositionAndNextNonWhitespaceCharacter( - end, - parentNode.end, - currentSourceFile!, - includeComments)); - } - return rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile) ? 0 : 1; - } - if (synthesizedNodeStartsOnNewLine(lastChild, format)) { - return 1; + if (lastChild === undefined) { + return !parentNode || currentSourceFile && rangeIsOnSingleLine(parentNode, currentSourceFile) ? 0 : 1; + } + if (currentSourceFile && parentNode && !positionIsSynthesized(parentNode.pos) && !nodeIsSynthesized(lastChild) && (!lastChild.parent || lastChild.parent === parentNode)) { + if (preserveSourceNewlines) { + const end = childrenTextRange && !positionIsSynthesized(childrenTextRange.end) ? childrenTextRange.end : lastChild.end; + return getEffectiveLines( + includeComments => getLinesBetweenPositionAndNextNonWhitespaceCharacter( + end, + parentNode.end, + currentSourceFile!, + includeComments)); } + return rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile) ? 0 : 1; } - if (format & ListFormat.MultiLine && !(format & ListFormat.NoTrailingNewLine)) { + if (synthesizedNodeStartsOnNewLine(lastChild, format)) { return 1; } - return 0; } + if (format & ListFormat.MultiLine && !(format & ListFormat.NoTrailingNewLine)) { + return 1; + } + return 0; + } - function getEffectiveLines(getLineDifference: (includeComments: boolean) => number) { - // If 'preserveSourceNewlines' is disabled, we should never call this function - // because it could be more expensive than alternative approximations. - Debug.assert(!!preserveSourceNewlines); - // We start by measuring the line difference from a position to its adjacent comments, - // so that this is counted as a one-line difference, not two: + function getEffectiveLines(getLineDifference: (includeComments: boolean) => number) { + // If 'preserveSourceNewlines' is disabled, we should never call this function + // because it could be more expensive than alternative approximations. + Debug.assert(!!preserveSourceNewlines); + // We start by measuring the line difference from a position to its adjacent comments, + // so that this is counted as a one-line difference, not two: + // + // node1; + // // NODE2 COMMENT + // node2; + const lines = getLineDifference(/*includeComments*/ true); + if (lines === 0) { + // However, if the line difference considering comments was 0, we might have this: // - // node1; - // // NODE2 COMMENT + // node1; // NODE2 COMMENT // node2; - const lines = getLineDifference(/*includeComments*/ true); - if (lines === 0) { - // However, if the line difference considering comments was 0, we might have this: - // - // node1; // NODE2 COMMENT - // node2; - // - // in which case we should be ignoring node2's comment, so this too is counted as - // a one-line difference, not zero. - return getLineDifference(/*includeComments*/ false); - } - return lines; + // + // in which case we should be ignoring node2's comment, so this too is counted as + // a one-line difference, not zero. + return getLineDifference(/*includeComments*/ false); } + return lines; + } - function writeLineSeparatorsAndIndentBefore(node: Node, parent: Node): boolean { - const leadingNewlines = preserveSourceNewlines && getLeadingLineTerminatorCount(parent, node, ListFormat.None); - if (leadingNewlines) { - writeLinesAndIndent(leadingNewlines, /*writeSpaceIfNotIndenting*/ false); - } - return !!leadingNewlines; + function writeLineSeparatorsAndIndentBefore(node: Node, parent: Node): boolean { + const leadingNewlines = preserveSourceNewlines && getLeadingLineTerminatorCount(parent, node, ListFormat.None); + if (leadingNewlines) { + writeLinesAndIndent(leadingNewlines, /*writeSpaceIfNotIndenting*/ false); } + return !!leadingNewlines; + } - function writeLineSeparatorsAfter(node: Node, parent: Node) { - const trailingNewlines = preserveSourceNewlines && getClosingLineTerminatorCount(parent, node, ListFormat.None, /*childrenTextRange*/ undefined); - if (trailingNewlines) { - writeLine(trailingNewlines); - } + function writeLineSeparatorsAfter(node: Node, parent: Node) { + const trailingNewlines = preserveSourceNewlines && getClosingLineTerminatorCount(parent, node, ListFormat.None, /*childrenTextRange*/ undefined); + if (trailingNewlines) { + writeLine(trailingNewlines); } + } - function synthesizedNodeStartsOnNewLine(node: Node, format: ListFormat) { - if (nodeIsSynthesized(node)) { - const startsOnNewLine = getStartsOnNewLine(node); - if (startsOnNewLine === undefined) { - return (format & ListFormat.PreferNewLine) !== 0; - } - - return startsOnNewLine; + function synthesizedNodeStartsOnNewLine(node: Node, format: ListFormat) { + if (nodeIsSynthesized(node)) { + const startsOnNewLine = getStartsOnNewLine(node); + if (startsOnNewLine === undefined) { + return (format & ListFormat.PreferNewLine) !== 0; } - return (format & ListFormat.PreferNewLine) !== 0; + return startsOnNewLine; } - function getLinesBetweenNodes(parent: Node, node1: Node, node2: Node): number { - if (getEmitFlags(parent) & EmitFlags.NoIndentation) { - return 0; - } - - parent = skipSynthesizedParentheses(parent); - node1 = skipSynthesizedParentheses(node1); - node2 = skipSynthesizedParentheses(node2); - - // Always use a newline for synthesized code if the synthesizer desires it. - if (getStartsOnNewLine(node2)) { - return 1; - } - - if (currentSourceFile && !nodeIsSynthesized(parent) && !nodeIsSynthesized(node1) && !nodeIsSynthesized(node2)) { - if (preserveSourceNewlines) { - return getEffectiveLines( - includeComments => getLinesBetweenRangeEndAndRangeStart( - node1, - node2, - currentSourceFile!, - includeComments)); - } - return rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile) ? 0 : 1; - } + return (format & ListFormat.PreferNewLine) !== 0; + } + function getLinesBetweenNodes(parent: Node, node1: Node, node2: Node): number { + if (getEmitFlags(parent) & EmitFlags.NoIndentation) { return 0; } - function isEmptyBlock(block: BlockLike) { - return block.statements.length === 0 - && (!currentSourceFile || rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile)); - } - - function skipSynthesizedParentheses(node: Node) { - while (node.kind === SyntaxKind.ParenthesizedExpression && nodeIsSynthesized(node)) { - node = (node as ParenthesizedExpression).expression; - } + parent = skipSynthesizedParentheses(parent); + node1 = skipSynthesizedParentheses(node1); + node2 = skipSynthesizedParentheses(node2); - return node; + // Always use a newline for synthesized code if the synthesizer desires it. + if (getStartsOnNewLine(node2)) { + return 1; } - function getTextOfNode(node: Identifier | PrivateIdentifier | LiteralExpression, includeTrivia?: boolean): string { - if (isGeneratedIdentifier(node) || isGeneratedPrivateIdentifier(node)) { - return generateName(node); - } - if (isStringLiteral(node) && node.textSourceNode) { - return getTextOfNode(node.textSourceNode, includeTrivia); - } - const sourceFile = currentSourceFile; // const needed for control flow - const canUseSourceFile = !!sourceFile && !!node.parent && !nodeIsSynthesized(node); - if (isMemberName(node)) { - if (!canUseSourceFile || getSourceFileOfNode(node) !== getOriginalNode(sourceFile)) { - return idText(node); - } - } - else { - Debug.assertNode(node, isLiteralExpression); // not strictly necessary - if (!canUseSourceFile) { - return node.text; - } + if (currentSourceFile && !nodeIsSynthesized(parent) && !nodeIsSynthesized(node1) && !nodeIsSynthesized(node2)) { + if (preserveSourceNewlines) { + return getEffectiveLines( + includeComments => getLinesBetweenRangeEndAndRangeStart( + node1, + node2, + currentSourceFile!, + includeComments)); } - return getSourceTextOfNodeFromSourceFile(sourceFile, node, includeTrivia); + return rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile) ? 0 : 1; } - function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined, jsxAttributeEscape: boolean): string { - if (node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).textSourceNode) { - const textSourceNode = (node as StringLiteral).textSourceNode!; - if (isIdentifier(textSourceNode) || isPrivateIdentifier(textSourceNode) || isNumericLiteral(textSourceNode)) { - const text = isNumericLiteral(textSourceNode) ? textSourceNode.text : getTextOfNode(textSourceNode); - return jsxAttributeEscape ? `"${escapeJsxAttributeString(text)}"` : - neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? `"${escapeString(text)}"` : - `"${escapeNonAsciiString(text)}"`; - } - else { - return getLiteralTextOfNode(textSourceNode, neverAsciiEscape, jsxAttributeEscape); - } - } + return 0; + } - const flags = (neverAsciiEscape ? GetLiteralTextFlags.NeverAsciiEscape : 0) - | (jsxAttributeEscape ? GetLiteralTextFlags.JsxAttributeEscape : 0) - | (printerOptions.terminateUnterminatedLiterals ? GetLiteralTextFlags.TerminateUnterminatedLiterals : 0) - | (printerOptions.target && printerOptions.target === ScriptTarget.ESNext ? GetLiteralTextFlags.AllowNumericSeparator : 0); + function isEmptyBlock(block: BlockLike) { + return block.statements.length === 0 + && (!currentSourceFile || rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile)); + } - return getLiteralText(node, currentSourceFile, flags); + function skipSynthesizedParentheses(node: Node) { + while (node.kind === SyntaxKind.ParenthesizedExpression && nodeIsSynthesized(node)) { + node = (node as ParenthesizedExpression).expression; } - /** - * Push a new name generation scope. - */ - function pushNameGenerationScope(node: Node | undefined) { - if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - return; - } - tempFlagsStack.push(tempFlags); - tempFlags = TempFlags.Auto; - privateNameTempFlagsStack.push(privateNameTempFlags); - privateNameTempFlags = TempFlags.Auto; - formattedNameTempFlagsStack.push(formattedNameTempFlags); - formattedNameTempFlags = undefined; - reservedNamesStack.push(reservedNames); - } - - /** - * Pop the current name generation scope. - */ - function popNameGenerationScope(node: Node | undefined) { - if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - return; + return node; + } + + function getTextOfNode(node: Identifier | PrivateIdentifier | LiteralExpression, includeTrivia?: boolean): string { + if (isGeneratedIdentifier(node) || isGeneratedPrivateIdentifier(node)) { + return generateName(node); + } + if (isStringLiteral(node) && node.textSourceNode) { + return getTextOfNode(node.textSourceNode, includeTrivia); + } + const sourceFile = currentSourceFile; // const needed for control flow + const canUseSourceFile = !!sourceFile && !!node.parent && !nodeIsSynthesized(node); + if (isMemberName(node)) { + if (!canUseSourceFile || getSourceFileOfNode(node) !== getOriginalNode(sourceFile)) { + return idText(node); } - tempFlags = tempFlagsStack.pop()!; - privateNameTempFlags = privateNameTempFlagsStack.pop()!; - formattedNameTempFlags = formattedNameTempFlagsStack.pop(); - reservedNames = reservedNamesStack.pop()!; } - - function reserveNameInNestedScopes(name: string) { - if (!reservedNames || reservedNames === lastOrUndefined(reservedNamesStack)) { - reservedNames = new Set(); + else { + Debug.assertNode(node, isLiteralExpression); // not strictly necessary + if (!canUseSourceFile) { + return node.text; } - reservedNames.add(name); } + return getSourceTextOfNodeFromSourceFile(sourceFile, node, includeTrivia); + } - function generateNames(node: Node | undefined) { - if (!node) return; - switch (node.kind) { - case SyntaxKind.Block: - forEach((node as Block).statements, generateNames); - break; - case SyntaxKind.LabeledStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - generateNames((node as LabeledStatement | WithStatement | DoStatement | WhileStatement).statement); - break; - case SyntaxKind.IfStatement: - generateNames((node as IfStatement).thenStatement); - generateNames((node as IfStatement).elseStatement); - break; - case SyntaxKind.ForStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForInStatement: - generateNames((node as ForStatement | ForInOrOfStatement).initializer); - generateNames((node as ForStatement | ForInOrOfStatement).statement); - break; - case SyntaxKind.SwitchStatement: - generateNames((node as SwitchStatement).caseBlock); - break; - case SyntaxKind.CaseBlock: - forEach((node as CaseBlock).clauses, generateNames); - break; - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - forEach((node as CaseOrDefaultClause).statements, generateNames); - break; - case SyntaxKind.TryStatement: - generateNames((node as TryStatement).tryBlock); - generateNames((node as TryStatement).catchClause); - generateNames((node as TryStatement).finallyBlock); - break; - case SyntaxKind.CatchClause: - generateNames((node as CatchClause).variableDeclaration); - generateNames((node as CatchClause).block); - break; - case SyntaxKind.VariableStatement: - generateNames((node as VariableStatement).declarationList); - break; - case SyntaxKind.VariableDeclarationList: - forEach((node as VariableDeclarationList).declarations, generateNames); - break; - case SyntaxKind.VariableDeclaration: - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - case SyntaxKind.ClassDeclaration: - generateNameIfNeeded((node as NamedDeclaration).name); - break; - case SyntaxKind.FunctionDeclaration: - generateNameIfNeeded((node as FunctionDeclaration).name); - if (getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { - forEach((node as FunctionDeclaration).parameters, generateNames); - generateNames((node as FunctionDeclaration).body); - } - break; - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - forEach((node as BindingPattern).elements, generateNames); - break; - case SyntaxKind.ImportDeclaration: - generateNames((node as ImportDeclaration).importClause); - break; - case SyntaxKind.ImportClause: - generateNameIfNeeded((node as ImportClause).name); - generateNames((node as ImportClause).namedBindings); - break; - case SyntaxKind.NamespaceImport: - generateNameIfNeeded((node as NamespaceImport).name); - break; - case SyntaxKind.NamespaceExport: - generateNameIfNeeded((node as NamespaceExport).name); - break; - case SyntaxKind.NamedImports: - forEach((node as NamedImports).elements, generateNames); - break; - case SyntaxKind.ImportSpecifier: - generateNameIfNeeded((node as ImportSpecifier).propertyName || (node as ImportSpecifier).name); - break; + function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined, jsxAttributeEscape: boolean): string { + if (node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).textSourceNode) { + const textSourceNode = (node as StringLiteral).textSourceNode!; + if (isIdentifier(textSourceNode) || isPrivateIdentifier(textSourceNode) || isNumericLiteral(textSourceNode)) { + const text = isNumericLiteral(textSourceNode) ? textSourceNode.text : getTextOfNode(textSourceNode); + return jsxAttributeEscape ? `"${escapeJsxAttributeString(text)}"` : + neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? `"${escapeString(text)}"` : + `"${escapeNonAsciiString(text)}"`; + } + else { + return getLiteralTextOfNode(textSourceNode, neverAsciiEscape, jsxAttributeEscape); } } - function generateMemberNames(node: Node | undefined) { - if (!node) return; - switch (node.kind) { - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - generateNameIfNeeded((node as NamedDeclaration).name); - break; - } + const flags = (neverAsciiEscape ? GetLiteralTextFlags.NeverAsciiEscape : 0) + | (jsxAttributeEscape ? GetLiteralTextFlags.JsxAttributeEscape : 0) + | (printerOptions.terminateUnterminatedLiterals ? GetLiteralTextFlags.TerminateUnterminatedLiterals : 0) + | (printerOptions.target && printerOptions.target === ScriptTarget.ESNext ? GetLiteralTextFlags.AllowNumericSeparator : 0); + + return getLiteralText(node, currentSourceFile, flags); + } + + /** + * Push a new name generation scope. + */ + function pushNameGenerationScope(node: Node | undefined) { + if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + return; + } + tempFlagsStack.push(tempFlags); + tempFlags = TempFlags.Auto; + privateNameTempFlagsStack.push(privateNameTempFlags); + privateNameTempFlags = TempFlags.Auto; + formattedNameTempFlagsStack.push(formattedNameTempFlags); + formattedNameTempFlags = undefined; + reservedNamesStack.push(reservedNames); + } + + /** + * Pop the current name generation scope. + */ + function popNameGenerationScope(node: Node | undefined) { + if (node && getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + return; + } + tempFlags = tempFlagsStack.pop()!; + privateNameTempFlags = privateNameTempFlagsStack.pop()!; + formattedNameTempFlags = formattedNameTempFlagsStack.pop(); + reservedNames = reservedNamesStack.pop()!; + } + + function reserveNameInNestedScopes(name: string) { + if (!reservedNames || reservedNames === lastOrUndefined(reservedNamesStack)) { + reservedNames = new Set(); } + reservedNames.add(name); + } - function generateNameIfNeeded(name: DeclarationName | undefined) { - if (name) { - if (isGeneratedIdentifier(name) || isGeneratedPrivateIdentifier(name)) { - generateName(name); + function generateNames(node: Node | undefined) { + if (!node) return; + switch (node.kind) { + case SyntaxKind.Block: + forEach((node as Block).statements, generateNames); + break; + case SyntaxKind.LabeledStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + generateNames((node as LabeledStatement | WithStatement | DoStatement | WhileStatement).statement); + break; + case SyntaxKind.IfStatement: + generateNames((node as IfStatement).thenStatement); + generateNames((node as IfStatement).elseStatement); + break; + case SyntaxKind.ForStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForInStatement: + generateNames((node as ForStatement | ForInOrOfStatement).initializer); + generateNames((node as ForStatement | ForInOrOfStatement).statement); + break; + case SyntaxKind.SwitchStatement: + generateNames((node as SwitchStatement).caseBlock); + break; + case SyntaxKind.CaseBlock: + forEach((node as CaseBlock).clauses, generateNames); + break; + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + forEach((node as CaseOrDefaultClause).statements, generateNames); + break; + case SyntaxKind.TryStatement: + generateNames((node as TryStatement).tryBlock); + generateNames((node as TryStatement).catchClause); + generateNames((node as TryStatement).finallyBlock); + break; + case SyntaxKind.CatchClause: + generateNames((node as CatchClause).variableDeclaration); + generateNames((node as CatchClause).block); + break; + case SyntaxKind.VariableStatement: + generateNames((node as VariableStatement).declarationList); + break; + case SyntaxKind.VariableDeclarationList: + forEach((node as VariableDeclarationList).declarations, generateNames); + break; + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + case SyntaxKind.ClassDeclaration: + generateNameIfNeeded((node as NamedDeclaration).name); + break; + case SyntaxKind.FunctionDeclaration: + generateNameIfNeeded((node as FunctionDeclaration).name); + if (getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { + forEach((node as FunctionDeclaration).parameters, generateNames); + generateNames((node as FunctionDeclaration).body); } - else if (isBindingPattern(name)) { - generateNames(name); - } - } + break; + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + forEach((node as BindingPattern).elements, generateNames); + break; + case SyntaxKind.ImportDeclaration: + generateNames((node as ImportDeclaration).importClause); + break; + case SyntaxKind.ImportClause: + generateNameIfNeeded((node as ImportClause).name); + generateNames((node as ImportClause).namedBindings); + break; + case SyntaxKind.NamespaceImport: + generateNameIfNeeded((node as NamespaceImport).name); + break; + case SyntaxKind.NamespaceExport: + generateNameIfNeeded((node as NamespaceExport).name); + break; + case SyntaxKind.NamedImports: + forEach((node as NamedImports).elements, generateNames); + break; + case SyntaxKind.ImportSpecifier: + generateNameIfNeeded((node as ImportSpecifier).propertyName || (node as ImportSpecifier).name); + break; + } + } + + function generateMemberNames(node: Node | undefined) { + if (!node) return; + switch (node.kind) { + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + generateNameIfNeeded((node as NamedDeclaration).name); + break; } + } - /** - * Generate the text for a generated identifier. - */ - function generateName(name: GeneratedIdentifier | GeneratedPrivateIdentifier) { - if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { - // Node names generate unique names based on their original node - // and are cached based on that node's id. - return generateNameCached(getNodeForGeneratedName(name), isPrivateIdentifier(name), name.autoGenerateFlags, name.autoGeneratePrefix, name.autoGenerateSuffix); + function generateNameIfNeeded(name: DeclarationName | undefined) { + if (name) { + if (isGeneratedIdentifier(name) || isGeneratedPrivateIdentifier(name)) { + generateName(name); } - else { - // Auto, Loop, and Unique names are cached based on their unique - // autoGenerateId. - const autoGenerateId = name.autoGenerateId!; - return autoGeneratedIdToGeneratedName[autoGenerateId] || (autoGeneratedIdToGeneratedName[autoGenerateId] = makeName(name)); + else if (isBindingPattern(name)) { + generateNames(name); } } + } - function generateNameCached(node: Node, privateName: boolean, flags?: GeneratedIdentifierFlags, prefix?: string | GeneratedNamePart, suffix?: string) { - const nodeId = getNodeId(node); - return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node, privateName, flags ?? GeneratedIdentifierFlags.None, formatGeneratedNamePart(prefix, generateName), formatGeneratedNamePart(suffix))); + /** + * Generate the text for a generated identifier. + */ + function generateName(name: GeneratedIdentifier | GeneratedPrivateIdentifier) { + if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { + // Node names generate unique names based on their original node + // and are cached based on that node's id. + return generateNameCached(getNodeForGeneratedName(name), isPrivateIdentifier(name), name.autoGenerateFlags, name.autoGeneratePrefix, name.autoGenerateSuffix); } - - /** - * Returns a value indicating whether a name is unique globally, within the current file, - * or within the NameGenerator. - */ - function isUniqueName(name: string): boolean { - return isFileLevelUniqueName(name) - && !generatedNames.has(name) - && !(reservedNames && reservedNames.has(name)); + else { + // Auto, Loop, and Unique names are cached based on their unique + // autoGenerateId. + const autoGenerateId = name.autoGenerateId!; + return autoGeneratedIdToGeneratedName[autoGenerateId] || (autoGeneratedIdToGeneratedName[autoGenerateId] = makeName(name)); } + } - /** - * Returns a value indicating whether a name is unique globally or within the current file. - */ - function isFileLevelUniqueName(name: string) { - return currentSourceFile ? ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName) : true; - } + function generateNameCached(node: Node, privateName: boolean, flags?: GeneratedIdentifierFlags, prefix?: string | GeneratedNamePart, suffix?: string) { + const nodeId = getNodeId(node); + return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = generateNameForNode(node, privateName, flags ?? GeneratedIdentifierFlags.None, formatGeneratedNamePart(prefix, generateName), formatGeneratedNamePart(suffix))); + } - /** - * Returns a value indicating whether a name is unique within a container. - */ - function isUniqueLocalName(name: string, container: Node): boolean { - for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer!) { - if (node.locals) { - const local = node.locals.get(escapeLeadingUnderscores(name)); - // We conservatively include alias symbols to cover cases where they're emitted as locals - if (local && local.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { - return false; - } + /** + * Returns a value indicating whether a name is unique globally, within the current file, + * or within the NameGenerator. + */ + function isUniqueName(name: string): boolean { + return isFileLevelUniqueName(name) + && !generatedNames.has(name) + && !(reservedNames && reservedNames.has(name)); + } + + /** + * Returns a value indicating whether a name is unique globally or within the current file. + */ + function isFileLevelUniqueName(name: string) { + return currentSourceFile ? ts.isFileLevelUniqueName(currentSourceFile, name, hasGlobalName) : true; + } + + /** + * Returns a value indicating whether a name is unique within a container. + */ + function isUniqueLocalName(name: string, container: Node): boolean { + for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer!) { + if (node.locals) { + const local = node.locals.get(escapeLeadingUnderscores(name)); + // We conservatively include alias symbols to cover cases where they're emitted as locals + if (local && local.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { + return false; } } - return true; } + return true; + } - function getTempFlags(formattedNameKey: string) { - switch (formattedNameKey) { - case "": - return tempFlags; - case "#": - return privateNameTempFlags; - default: - return formattedNameTempFlags?.get(formattedNameKey) ?? TempFlags.Auto; - } + function getTempFlags(formattedNameKey: string) { + switch (formattedNameKey) { + case "": + return tempFlags; + case "#": + return privateNameTempFlags; + default: + return formattedNameTempFlags?.get(formattedNameKey) ?? TempFlags.Auto; } + } - function setTempFlags(formattedNameKey: string, flags: TempFlags) { - switch (formattedNameKey) { - case "": - tempFlags = flags; - break; - case "#": - privateNameTempFlags = flags; - break; - default: - formattedNameTempFlags ??= new Map(); - formattedNameTempFlags.set(formattedNameKey, flags); - break; - } + function setTempFlags(formattedNameKey: string, flags: TempFlags) { + switch (formattedNameKey) { + case "": + tempFlags = flags; + break; + case "#": + privateNameTempFlags = flags; + break; + default: + formattedNameTempFlags ??= new Map(); + formattedNameTempFlags.set(formattedNameKey, flags); + break; } + } - /** - * Return the next available name in the pattern _a ... _z, _0, _1, ... - * TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. - * Note that names generated by makeTempVariableName and makeUniqueName will never conflict. - */ - function makeTempVariableName(flags: TempFlags, reservedInNestedScopes: boolean, privateName: boolean, prefix: string, suffix: string): string { - if (prefix.length > 0 && prefix.charCodeAt(0) === CharacterCodes.hash) { - prefix = prefix.slice(1); + /** + * Return the next available name in the pattern _a ... _z, _0, _1, ... + * TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. + * Note that names generated by makeTempVariableName and makeUniqueName will never conflict. + */ + function makeTempVariableName(flags: TempFlags, reservedInNestedScopes: boolean, privateName: boolean, prefix: string, suffix: string): string { + if (prefix.length > 0 && prefix.charCodeAt(0) === CharacterCodes.hash) { + prefix = prefix.slice(1); + } + + // Generate a key to use to acquire a TempFlags counter based on the fixed portions of the generated name. + const key = formatGeneratedName(privateName, prefix, "", suffix); + let tempFlags = getTempFlags(key); + + if (flags && !(tempFlags & flags)) { + const name = flags === TempFlags._i ? "_i" : "_n"; + const fullName = formatGeneratedName(privateName, prefix, name, suffix); + if (isUniqueName(fullName)) { + tempFlags |= flags; + if (reservedInNestedScopes) { + reserveNameInNestedScopes(fullName); + } + setTempFlags(key, tempFlags); + return fullName; } + } - // Generate a key to use to acquire a TempFlags counter based on the fixed portions of the generated name. - const key = formatGeneratedName(privateName, prefix, "", suffix); - let tempFlags = getTempFlags(key); - - if (flags && !(tempFlags & flags)) { - const name = flags === TempFlags._i ? "_i" : "_n"; + while (true) { + const count = tempFlags & TempFlags.CountMask; + tempFlags++; + // Skip over 'i' and 'n' + if (count !== 8 && count !== 13) { + const name = count < 26 + ? "_" + String.fromCharCode(CharacterCodes.a + count) + : "_" + (count - 26); const fullName = formatGeneratedName(privateName, prefix, name, suffix); if (isUniqueName(fullName)) { - tempFlags |= flags; if (reservedInNestedScopes) { reserveNameInNestedScopes(fullName); } @@ -5264,790 +5368,771 @@ namespace ts { return fullName; } } + } + } - while (true) { - const count = tempFlags & TempFlags.CountMask; - tempFlags++; - // Skip over 'i' and 'n' - if (count !== 8 && count !== 13) { - const name = count < 26 - ? "_" + String.fromCharCode(CharacterCodes.a + count) - : "_" + (count - 26); - const fullName = formatGeneratedName(privateName, prefix, name, suffix); - if (isUniqueName(fullName)) { - if (reservedInNestedScopes) { - reserveNameInNestedScopes(fullName); - } - setTempFlags(key, tempFlags); - return fullName; - } + /** + * Generate a name that is unique within the current file and doesn't conflict with any names + * in global scope. The name is formed by adding an '_n' suffix to the specified base name, + * where n is a positive integer. Note that names generated by makeTempVariableName and + * makeUniqueName are guaranteed to never conflict. + * If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1' + */ + function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic: boolean, scoped: boolean, privateName: boolean, prefix: string, suffix: string): string { + if (baseName.length > 0 && baseName.charCodeAt(0) === CharacterCodes.hash) { + baseName = baseName.slice(1); + } + if (prefix.length > 0 && prefix.charCodeAt(0) === CharacterCodes.hash) { + prefix = prefix.slice(1); + } + if (optimistic) { + const fullName = formatGeneratedName(privateName, prefix, baseName, suffix); + if (checkFn(fullName)) { + if (scoped) { + reserveNameInNestedScopes(fullName); + } + else { + generatedNames.add(fullName); } + return fullName; } } - - /** - * Generate a name that is unique within the current file and doesn't conflict with any names - * in global scope. The name is formed by adding an '_n' suffix to the specified base name, - * where n is a positive integer. Note that names generated by makeTempVariableName and - * makeUniqueName are guaranteed to never conflict. - * If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1' - */ - function makeUniqueName(baseName: string, checkFn: (name: string) => boolean = isUniqueName, optimistic: boolean, scoped: boolean, privateName: boolean, prefix: string, suffix: string): string { - if (baseName.length > 0 && baseName.charCodeAt(0) === CharacterCodes.hash) { - baseName = baseName.slice(1); - } - if (prefix.length > 0 && prefix.charCodeAt(0) === CharacterCodes.hash) { - prefix = prefix.slice(1); - } - if (optimistic) { - const fullName = formatGeneratedName(privateName, prefix, baseName, suffix); - if (checkFn(fullName)) { - if (scoped) { - reserveNameInNestedScopes(fullName); - } - else { - generatedNames.add(fullName); - } - return fullName; + // Find the first unique 'name_n', where n is a positive number + if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { + baseName += "_"; + } + let i = 1; + while (true) { + const fullName = formatGeneratedName(privateName, prefix, baseName + i, suffix); + if (checkFn(fullName)) { + if (scoped) { + reserveNameInNestedScopes(fullName); } - } - // Find the first unique 'name_n', where n is a positive number - if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) { - baseName += "_"; - } - let i = 1; - while (true) { - const fullName = formatGeneratedName(privateName, prefix, baseName + i, suffix); - if (checkFn(fullName)) { - if (scoped) { - reserveNameInNestedScopes(fullName); - } - else { - generatedNames.add(fullName); - } - return fullName; + else { + generatedNames.add(fullName); } - i++; + return fullName; } + i++; } + } - function makeFileLevelOptimisticUniqueName(name: string) { - return makeUniqueName(name, isFileLevelUniqueName, /*optimistic*/ true, /*scoped*/ false, /*privateName*/ false, /*prefix*/ "", /*suffix*/ ""); - } + function makeFileLevelOptimisticUniqueName(name: string) { + return makeUniqueName(name, isFileLevelUniqueName, /*optimistic*/ true, /*scoped*/ false, /*privateName*/ false, /*prefix*/ "", /*suffix*/ ""); + } - /** - * Generates a unique name for a ModuleDeclaration or EnumDeclaration. - */ - function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { - const name = getTextOfNode(node.name); - // Use module/enum name itself if it is unique, otherwise make a unique variation - return isUniqueLocalName(name, node) ? name : makeUniqueName(name, isUniqueName, /*optimistic*/ false, /*scoped*/ false, /*privateName*/ false, /*prefix*/ "", /*suffix*/ ""); - } + /** + * Generates a unique name for a ModuleDeclaration or EnumDeclaration. + */ + function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) { + const name = getTextOfNode(node.name); + // Use module/enum name itself if it is unique, otherwise make a unique variation + return isUniqueLocalName(name, node) ? name : makeUniqueName(name, isUniqueName, /*optimistic*/ false, /*scoped*/ false, /*privateName*/ false, /*prefix*/ "", /*suffix*/ ""); + } - /** - * Generates a unique name for an ImportDeclaration or ExportDeclaration. - */ - function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { - const expr = getExternalModuleName(node)!; // TODO: GH#18217 - const baseName = isStringLiteral(expr) ? - makeIdentifierFromModuleName(expr.text) : "module"; - return makeUniqueName(baseName, isUniqueName, /*optimistic*/ false, /*scoped*/ false, /*privateName*/ false, /*prefix*/ "", /*suffix*/ ""); - } + /** + * Generates a unique name for an ImportDeclaration or ExportDeclaration. + */ + function generateNameForImportOrExportDeclaration(node: ImportDeclaration | ExportDeclaration) { + const expr = getExternalModuleName(node)!; // TODO: GH#18217 + const baseName = isStringLiteral(expr) ? + makeIdentifierFromModuleName(expr.text) : "module"; + return makeUniqueName(baseName, isUniqueName, /*optimistic*/ false, /*scoped*/ false, /*privateName*/ false, /*prefix*/ "", /*suffix*/ ""); + } - /** - * Generates a unique name for a default export. - */ - function generateNameForExportDefault() { - return makeUniqueName("default", isUniqueName, /*optimistic*/ false, /*scoped*/ false, /*privateName*/ false, /*prefix*/ "", /*suffix*/ ""); - } + /** + * Generates a unique name for a default export. + */ + function generateNameForExportDefault() { + return makeUniqueName("default", isUniqueName, /*optimistic*/ false, /*scoped*/ false, /*privateName*/ false, /*prefix*/ "", /*suffix*/ ""); + } - /** - * Generates a unique name for a class expression. - */ - function generateNameForClassExpression() { - return makeUniqueName("class", isUniqueName, /*optimistic*/ false, /*scoped*/ false, /*privateName*/ false, /*prefix*/ "", /*suffix*/ ""); - } + /** + * Generates a unique name for a class expression. + */ + function generateNameForClassExpression() { + return makeUniqueName("class", isUniqueName, /*optimistic*/ false, /*scoped*/ false, /*privateName*/ false, /*prefix*/ "", /*suffix*/ ""); + } - function generateNameForMethodOrAccessor(node: MethodDeclaration | AccessorDeclaration, privateName: boolean, prefix: string, suffix: string) { - if (isIdentifier(node.name)) { - return generateNameCached(node.name, privateName); - } - return makeTempVariableName(TempFlags.Auto, /*reservedInNestedScopes*/ false, privateName, prefix, suffix); + function generateNameForMethodOrAccessor(node: MethodDeclaration | AccessorDeclaration, privateName: boolean, prefix: string, suffix: string) { + if (isIdentifier(node.name)) { + return generateNameCached(node.name, privateName); } + return makeTempVariableName(TempFlags.Auto, /*reservedInNestedScopes*/ false, privateName, prefix, suffix); + } - /** - * Generates a unique name from a node. - */ - function generateNameForNode(node: Node, privateName: boolean, flags: GeneratedIdentifierFlags, prefix: string, suffix: string): string { - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PrivateIdentifier: - return makeUniqueName( - getTextOfNode(node as Identifier), - isUniqueName, - !!(flags & GeneratedIdentifierFlags.Optimistic), - !!(flags & GeneratedIdentifierFlags.ReservedInNestedScopes), - privateName, - prefix, - suffix - ); - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - Debug.assert(!prefix && !suffix && !privateName); - return generateNameForModuleOrEnum(node as ModuleDeclaration | EnumDeclaration); - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - Debug.assert(!prefix && !suffix && !privateName); - return generateNameForImportOrExportDeclaration(node as ImportDeclaration | ExportDeclaration); - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ExportAssignment: - Debug.assert(!prefix && !suffix && !privateName); - return generateNameForExportDefault(); - case SyntaxKind.ClassExpression: - Debug.assert(!prefix && !suffix && !privateName); - return generateNameForClassExpression(); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return generateNameForMethodOrAccessor(node as MethodDeclaration | AccessorDeclaration, privateName, prefix, suffix); - case SyntaxKind.ComputedPropertyName: - return makeTempVariableName(TempFlags.Auto, /*reserveInNestedScopes*/ true, privateName, prefix, suffix); - default: - return makeTempVariableName(TempFlags.Auto, /*reserveInNestedScopes*/ false, privateName, prefix, suffix); - } + /** + * Generates a unique name from a node. + */ + function generateNameForNode(node: Node, privateName: boolean, flags: GeneratedIdentifierFlags, prefix: string, suffix: string): string { + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: + return makeUniqueName( + getTextOfNode(node as Identifier), + isUniqueName, + !!(flags & GeneratedIdentifierFlags.Optimistic), + !!(flags & GeneratedIdentifierFlags.ReservedInNestedScopes), + privateName, + prefix, + suffix + ); + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + Debug.assert(!prefix && !suffix && !privateName); + return generateNameForModuleOrEnum(node as ModuleDeclaration | EnumDeclaration); + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + Debug.assert(!prefix && !suffix && !privateName); + return generateNameForImportOrExportDeclaration(node as ImportDeclaration | ExportDeclaration); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ExportAssignment: + Debug.assert(!prefix && !suffix && !privateName); + return generateNameForExportDefault(); + case SyntaxKind.ClassExpression: + Debug.assert(!prefix && !suffix && !privateName); + return generateNameForClassExpression(); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return generateNameForMethodOrAccessor(node as MethodDeclaration | AccessorDeclaration, privateName, prefix, suffix); + case SyntaxKind.ComputedPropertyName: + return makeTempVariableName(TempFlags.Auto, /*reserveInNestedScopes*/ true, privateName, prefix, suffix); + default: + return makeTempVariableName(TempFlags.Auto, /*reserveInNestedScopes*/ false, privateName, prefix, suffix); } + } - /** - * Generates a unique identifier for a node. - */ - function makeName(name: GeneratedIdentifier | GeneratedPrivateIdentifier) { - const prefix = formatGeneratedNamePart(name.autoGeneratePrefix, generateName); - const suffix = formatGeneratedNamePart (name.autoGenerateSuffix); - switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { - case GeneratedIdentifierFlags.Auto: - return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), isPrivateIdentifier(name), prefix, suffix); - case GeneratedIdentifierFlags.Loop: - Debug.assertNode(name, isIdentifier); - return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), /*privateName*/ false, prefix, suffix); - case GeneratedIdentifierFlags.Unique: - return makeUniqueName( - idText(name), - (name.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName, - !!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic), - !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), - isPrivateIdentifier(name), - prefix, - suffix - ); - } - - return Debug.fail(`Unsupported GeneratedIdentifierKind: ${Debug.formatEnum(name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask, (ts as any).GeneratedIdentifierFlags, /*isFlags*/ true)}.`); + /** + * Generates a unique identifier for a node. + */ + function makeName(name: GeneratedIdentifier | GeneratedPrivateIdentifier) { + const prefix = formatGeneratedNamePart(name.autoGeneratePrefix, generateName); + const suffix = formatGeneratedNamePart (name.autoGenerateSuffix); + switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { + case GeneratedIdentifierFlags.Auto: + return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), isPrivateIdentifier(name), prefix, suffix); + case GeneratedIdentifierFlags.Loop: + Debug.assertNode(name, isIdentifier); + return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), /*privateName*/ false, prefix, suffix); + case GeneratedIdentifierFlags.Unique: + return makeUniqueName( + idText(name), + (name.autoGenerateFlags & GeneratedIdentifierFlags.FileLevel) ? isFileLevelUniqueName : isUniqueName, + !!(name.autoGenerateFlags & GeneratedIdentifierFlags.Optimistic), + !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes), + isPrivateIdentifier(name), + prefix, + suffix + ); } - // Comments + return Debug.fail(`Unsupported GeneratedIdentifierKind: ${Debug.formatEnum(name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask, (ts as any).GeneratedIdentifierFlags, /*isFlags*/ true)}.`); + } + + // Comments - function pipelineEmitWithComments(hint: EmitHint, node: Node) { - const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, hint, node); - const savedContainerPos = containerPos; - const savedContainerEnd = containerEnd; - const savedDeclarationListContainerEnd = declarationListContainerEnd; - emitCommentsBeforeNode(node); - pipelinePhase(hint, node); - emitCommentsAfterNode(node, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); - } + function pipelineEmitWithComments(hint: EmitHint, node: Node) { + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, hint, node); + const savedContainerPos = containerPos; + const savedContainerEnd = containerEnd; + const savedDeclarationListContainerEnd = declarationListContainerEnd; + emitCommentsBeforeNode(node); + pipelinePhase(hint, node); + emitCommentsAfterNode(node, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); + } - function emitCommentsBeforeNode(node: Node) { - const emitFlags = getEmitFlags(node); - const commentRange = getCommentRange(node); + function emitCommentsBeforeNode(node: Node) { + const emitFlags = getEmitFlags(node); + const commentRange = getCommentRange(node); - // Emit leading comments - emitLeadingCommentsOfNode(node, emitFlags, commentRange.pos, commentRange.end); - if (emitFlags & EmitFlags.NoNestedComments) { - commentsDisabled = true; - } + // Emit leading comments + emitLeadingCommentsOfNode(node, emitFlags, commentRange.pos, commentRange.end); + if (emitFlags & EmitFlags.NoNestedComments) { + commentsDisabled = true; } + } - function emitCommentsAfterNode(node: Node, savedContainerPos: number, savedContainerEnd: number, savedDeclarationListContainerEnd: number) { - const emitFlags = getEmitFlags(node); - const commentRange = getCommentRange(node); + function emitCommentsAfterNode(node: Node, savedContainerPos: number, savedContainerEnd: number, savedDeclarationListContainerEnd: number) { + const emitFlags = getEmitFlags(node); + const commentRange = getCommentRange(node); - // Emit trailing comments - if (emitFlags & EmitFlags.NoNestedComments) { - commentsDisabled = false; - } - emitTrailingCommentsOfNode(node, emitFlags, commentRange.pos, commentRange.end, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); - const typeNode = getTypeNode(node); - if (typeNode) { - emitTrailingCommentsOfNode(node, emitFlags, typeNode.pos, typeNode.end, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); - } + // Emit trailing comments + if (emitFlags & EmitFlags.NoNestedComments) { + commentsDisabled = false; + } + emitTrailingCommentsOfNode(node, emitFlags, commentRange.pos, commentRange.end, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); + const typeNode = getTypeNode(node); + if (typeNode) { + emitTrailingCommentsOfNode(node, emitFlags, typeNode.pos, typeNode.end, savedContainerPos, savedContainerEnd, savedDeclarationListContainerEnd); } + } - function emitLeadingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number) { - enterComment(); - hasWrittenComment = false; + function emitLeadingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number) { + enterComment(); + hasWrittenComment = false; - // We have to explicitly check that the node is JsxText because if the compilerOptions.jsx is "preserve" we will not do any transformation. - // It is expensive to walk entire tree just to set one kind of node to have no comments. - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0 || node.kind === SyntaxKind.JsxText; - const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; + // We have to explicitly check that the node is JsxText because if the compilerOptions.jsx is "preserve" we will not do any transformation. + // It is expensive to walk entire tree just to set one kind of node to have no comments. + const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0 || node.kind === SyntaxKind.JsxText; + const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; - // Save current container state on the stack. - if ((pos > 0 || end > 0) && pos !== end) { - // Emit leading comments if the position is not synthesized and the node - // has not opted out from emitting leading comments. - if (!skipLeadingComments) { - emitLeadingComments(pos, /*isEmittedNode*/ node.kind !== SyntaxKind.NotEmittedStatement); - } + // Save current container state on the stack. + if ((pos > 0 || end > 0) && pos !== end) { + // Emit leading comments if the position is not synthesized and the node + // has not opted out from emitting leading comments. + if (!skipLeadingComments) { + emitLeadingComments(pos, /*isEmittedNode*/ node.kind !== SyntaxKind.NotEmittedStatement); + } - if (!skipLeadingComments || (pos >= 0 && (emitFlags & EmitFlags.NoLeadingComments) !== 0)) { - // Advance the container position if comments get emitted or if they've been disabled explicitly using NoLeadingComments. - containerPos = pos; - } + if (!skipLeadingComments || (pos >= 0 && (emitFlags & EmitFlags.NoLeadingComments) !== 0)) { + // Advance the container position if comments get emitted or if they've been disabled explicitly using NoLeadingComments. + containerPos = pos; + } - if (!skipTrailingComments || (end >= 0 && (emitFlags & EmitFlags.NoTrailingComments) !== 0)) { - // As above. - containerEnd = end; + if (!skipTrailingComments || (end >= 0 && (emitFlags & EmitFlags.NoTrailingComments) !== 0)) { + // As above. + containerEnd = end; - // To avoid invalid comment emit in a down-level binding pattern, we - // keep track of the last declaration list container's end - if (node.kind === SyntaxKind.VariableDeclarationList) { - declarationListContainerEnd = end; - } - } - } - forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment); - exitComment(); - } - - function emitTrailingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number, savedContainerPos: number, savedContainerEnd: number, savedDeclarationListContainerEnd: number) { - enterComment(); - const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; - forEach(getSyntheticTrailingComments(node), emitTrailingSynthesizedComment); - if ((pos > 0 || end > 0) && pos !== end) { - // Restore previous container state. - containerPos = savedContainerPos; - containerEnd = savedContainerEnd; - declarationListContainerEnd = savedDeclarationListContainerEnd; - - // Emit trailing comments if the position is not synthesized and the node - // has not opted out from emitting leading comments and is an emitted node. - if (!skipTrailingComments && node.kind !== SyntaxKind.NotEmittedStatement) { - emitTrailingComments(end); + // To avoid invalid comment emit in a down-level binding pattern, we + // keep track of the last declaration list container's end + if (node.kind === SyntaxKind.VariableDeclarationList) { + declarationListContainerEnd = end; } } - exitComment(); } + forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment); + exitComment(); + } - function emitLeadingSynthesizedComment(comment: SynthesizedComment) { - if (comment.hasLeadingNewline || comment.kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); - } - writeSynthesizedComment(comment); - if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); - } - else { - writer.writeSpace(" "); - } - } + function emitTrailingCommentsOfNode(node: Node, emitFlags: EmitFlags, pos: number, end: number, savedContainerPos: number, savedContainerEnd: number, savedDeclarationListContainerEnd: number) { + enterComment(); + const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; + forEach(getSyntheticTrailingComments(node), emitTrailingSynthesizedComment); + if ((pos > 0 || end > 0) && pos !== end) { + // Restore previous container state. + containerPos = savedContainerPos; + containerEnd = savedContainerEnd; + declarationListContainerEnd = savedDeclarationListContainerEnd; - function emitTrailingSynthesizedComment(comment: SynthesizedComment) { - if (!writer.isAtStartOfLine()) { - writer.writeSpace(" "); - } - writeSynthesizedComment(comment); - if (comment.hasTrailingNewLine) { - writer.writeLine(); + // Emit trailing comments if the position is not synthesized and the node + // has not opted out from emitting leading comments and is an emitted node. + if (!skipTrailingComments && node.kind !== SyntaxKind.NotEmittedStatement) { + emitTrailingComments(end); } } + exitComment(); + } - function writeSynthesizedComment(comment: SynthesizedComment) { - const text = formatSynthesizedComment(comment); - const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined; - writeCommentRange(text, lineMap!, writer, 0, text.length, newLine); + function emitLeadingSynthesizedComment(comment: SynthesizedComment) { + if (comment.hasLeadingNewline || comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); } - - function formatSynthesizedComment(comment: SynthesizedComment) { - return comment.kind === SyntaxKind.MultiLineCommentTrivia - ? `/*${comment.text}*/` - : `//${comment.text}`; + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); + } + else { + writer.writeSpace(" "); } + } - function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { - enterComment(); - const { pos, end } = detachedRange; - const emitFlags = getEmitFlags(node); - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; - const skipTrailingComments = commentsDisabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; - if (!skipLeadingComments) { - emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); - } + function emitTrailingSynthesizedComment(comment: SynthesizedComment) { + if (!writer.isAtStartOfLine()) { + writer.writeSpace(" "); + } + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine) { + writer.writeLine(); + } + } - exitComment(); - if (emitFlags & EmitFlags.NoNestedComments && !commentsDisabled) { - commentsDisabled = true; - emitCallback(node); - commentsDisabled = false; - } - else { - emitCallback(node); - } + function writeSynthesizedComment(comment: SynthesizedComment) { + const text = formatSynthesizedComment(comment); + const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined; + writeCommentRange(text, lineMap!, writer, 0, text.length, newLine); + } - enterComment(); - if (!skipTrailingComments) { - emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); - if (hasWrittenComment && !writer.isAtStartOfLine()) { - writer.writeLine(); - } - } - exitComment(); + function formatSynthesizedComment(comment: SynthesizedComment) { + return comment.kind === SyntaxKind.MultiLineCommentTrivia + ? `/*${comment.text}*/` + : `//${comment.text}`; + } + function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { + enterComment(); + const { pos, end } = detachedRange; + const emitFlags = getEmitFlags(node); + const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; + const skipTrailingComments = commentsDisabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; + if (!skipLeadingComments) { + emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); } - function originalNodesHaveSameParent(nodeA: Node, nodeB: Node) { - nodeA = getOriginalNode(nodeA); - // For performance, do not call `getOriginalNode` for `nodeB` if `nodeA` doesn't even - // have a parent node. - return nodeA.parent && nodeA.parent === getOriginalNode(nodeB).parent; + exitComment(); + if (emitFlags & EmitFlags.NoNestedComments && !commentsDisabled) { + commentsDisabled = true; + emitCallback(node); + commentsDisabled = false; + } + else { + emitCallback(node); } - function siblingNodePositionsAreComparable(previousNode: Node, nextNode: Node) { - if (nextNode.pos < previousNode.end) { - return false; + enterComment(); + if (!skipTrailingComments) { + emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); + if (hasWrittenComment && !writer.isAtStartOfLine()) { + writer.writeLine(); } + } + exitComment(); - previousNode = getOriginalNode(previousNode); - nextNode = getOriginalNode(nextNode); - const parent = previousNode.parent; - if (!parent || parent !== nextNode.parent) { - return false; - } + } + + function originalNodesHaveSameParent(nodeA: Node, nodeB: Node) { + nodeA = getOriginalNode(nodeA); + // For performance, do not call `getOriginalNode` for `nodeB` if `nodeA` doesn't even + // have a parent node. + return nodeA.parent && nodeA.parent === getOriginalNode(nodeB).parent; + } - const parentNodeArray = getContainingNodeArray(previousNode); - const prevNodeIndex = parentNodeArray?.indexOf(previousNode); - return prevNodeIndex !== undefined && prevNodeIndex > -1 && parentNodeArray!.indexOf(nextNode) === prevNodeIndex + 1; + function siblingNodePositionsAreComparable(previousNode: Node, nextNode: Node) { + if (nextNode.pos < previousNode.end) { + return false; } - function emitLeadingComments(pos: number, isEmittedNode: boolean) { - hasWrittenComment = false; + previousNode = getOriginalNode(previousNode); + nextNode = getOriginalNode(nextNode); + const parent = previousNode.parent; + if (!parent || parent !== nextNode.parent) { + return false; + } - if (isEmittedNode) { - if (pos === 0 && currentSourceFile?.isDeclarationFile) { - forEachLeadingCommentToEmit(pos, emitNonTripleSlashLeadingComment); - } - else { - forEachLeadingCommentToEmit(pos, emitLeadingComment); - } + const parentNodeArray = getContainingNodeArray(previousNode); + const prevNodeIndex = parentNodeArray?.indexOf(previousNode); + return prevNodeIndex !== undefined && prevNodeIndex > -1 && parentNodeArray!.indexOf(nextNode) === prevNodeIndex + 1; + } + + function emitLeadingComments(pos: number, isEmittedNode: boolean) { + hasWrittenComment = false; + + if (isEmittedNode) { + if (pos === 0 && currentSourceFile?.isDeclarationFile) { + forEachLeadingCommentToEmit(pos, emitNonTripleSlashLeadingComment); } - else if (pos === 0) { - // If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node, - // unless it is a triple slash comment at the top of the file. - // For Example: - // /// - // declare var x; - // /// - // interface F {} - // The first /// will NOT be removed while the second one will be removed even though both node will not be emitted - forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); + else { + forEachLeadingCommentToEmit(pos, emitLeadingComment); } } - - function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (isTripleSlashComment(commentPos, commentEnd)) { - emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); - } + else if (pos === 0) { + // If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node, + // unless it is a triple slash comment at the top of the file. + // For Example: + // /// + // declare var x; + // /// + // interface F {} + // The first /// will NOT be removed while the second one will be removed even though both node will not be emitted + forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); } + } - function emitNonTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (!isTripleSlashComment(commentPos, commentEnd)) { - emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); - } + function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (isTripleSlashComment(commentPos, commentEnd)) { + emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); } + } - function shouldWriteComment(text: string, pos: number) { - if (printerOptions.onlyPrintJsDocStyle) { - return (isJSDocLikeText(text, pos) || isPinnedComment(text, pos)); - } - return true; + function emitNonTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (!isTripleSlashComment(commentPos, commentEnd)) { + emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); } + } - function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (!currentSourceFile || !shouldWriteComment(currentSourceFile.text, commentPos)) return; - if (!hasWrittenComment) { - emitNewLineBeforeLeadingCommentOfPosition(getCurrentLineMap(), writer, rangePos, commentPos); - hasWrittenComment = true; - } - - // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitPos(commentPos); - writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); + function shouldWriteComment(text: string, pos: number) { + if (printerOptions.onlyPrintJsDocStyle) { + return (isJSDocLikeText(text, pos) || isPinnedComment(text, pos)); + } + return true; + } - if (hasTrailingNewLine) { - writer.writeLine(); - } - else if (kind === SyntaxKind.MultiLineCommentTrivia) { - writer.writeSpace(" "); - } + function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (!currentSourceFile || !shouldWriteComment(currentSourceFile.text, commentPos)) return; + if (!hasWrittenComment) { + emitNewLineBeforeLeadingCommentOfPosition(getCurrentLineMap(), writer, rangePos, commentPos); + hasWrittenComment = true; } - function emitLeadingCommentsOfPosition(pos: number) { - if (commentsDisabled || pos === -1) { - return; - } + // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space + emitPos(commentPos); + writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); - emitLeadingComments(pos, /*isEmittedNode*/ true); + if (hasTrailingNewLine) { + writer.writeLine(); + } + else if (kind === SyntaxKind.MultiLineCommentTrivia) { + writer.writeSpace(" "); } + } - function emitTrailingComments(pos: number) { - forEachTrailingCommentToEmit(pos, emitTrailingComment); + function emitLeadingCommentsOfPosition(pos: number) { + if (commentsDisabled || pos === -1) { + return; } - function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { - if (!currentSourceFile || !shouldWriteComment(currentSourceFile.text, commentPos)) return; - // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ - if (!writer.isAtStartOfLine()) { - writer.writeSpace(" "); - } + emitLeadingComments(pos, /*isEmittedNode*/ true); + } - emitPos(commentPos); - writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); + function emitTrailingComments(pos: number) { + forEachTrailingCommentToEmit(pos, emitTrailingComment); + } - if (hasTrailingNewLine) { - writer.writeLine(); - } + function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + if (!currentSourceFile || !shouldWriteComment(currentSourceFile.text, commentPos)) return; + // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ + if (!writer.isAtStartOfLine()) { + writer.writeSpace(" "); } - function emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean, forceNoNewline?: boolean) { - if (commentsDisabled) { - return; - } - enterComment(); - forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : forceNoNewline ? emitTrailingCommentOfPositionNoNewline : emitTrailingCommentOfPosition); - exitComment(); + emitPos(commentPos); + writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + + if (hasTrailingNewLine) { + writer.writeLine(); + } + } + + function emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean, forceNoNewline?: boolean) { + if (commentsDisabled) { + return; } + enterComment(); + forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : forceNoNewline ? emitTrailingCommentOfPositionNoNewline : emitTrailingCommentOfPosition); + exitComment(); + } - function emitTrailingCommentOfPositionNoNewline(commentPos: number, commentEnd: number, kind: SyntaxKind) { - if (!currentSourceFile) return; - // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space + function emitTrailingCommentOfPositionNoNewline(commentPos: number, commentEnd: number, kind: SyntaxKind) { + if (!currentSourceFile) return; + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space - emitPos(commentPos); - writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); + emitPos(commentPos); + writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); - if (kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); // still write a newline for single-line comments, so closing tokens aren't written on the same line - } + if (kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); // still write a newline for single-line comments, so closing tokens aren't written on the same line } + } - function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { - if(!currentSourceFile) return; - // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space + function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + if(!currentSourceFile) return; + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space - emitPos(commentPos); - writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); + emitPos(commentPos); + writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); - if (hasTrailingNewLine) { - writer.writeLine(); + if (hasTrailingNewLine) { + writer.writeLine(); + } + else { + writer.writeSpace(" "); + } + } + + function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { + // Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments + if (currentSourceFile && (containerPos === -1 || pos !== containerPos)) { + if (hasDetachedComments(pos)) { + forEachLeadingCommentWithoutDetachedComments(cb); } else { - writer.writeSpace(" "); + forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); } } + } - function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { - // Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments - if (currentSourceFile && (containerPos === -1 || pos !== containerPos)) { - if (hasDetachedComments(pos)) { - forEachLeadingCommentWithoutDetachedComments(cb); - } - else { - forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); - } - } + function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) { + // Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments + if (currentSourceFile && (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) { + forEachTrailingCommentRange(currentSourceFile.text, end, cb); } + } - function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) { - // Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments - if (currentSourceFile && (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) { - forEachTrailingCommentRange(currentSourceFile.text, end, cb); - } - } + function hasDetachedComments(pos: number) { + return detachedCommentsInfo !== undefined && last(detachedCommentsInfo).nodePos === pos; + } - function hasDetachedComments(pos: number) { - return detachedCommentsInfo !== undefined && last(detachedCommentsInfo).nodePos === pos; + function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { + if (!currentSourceFile) return; + // get the leading comments from detachedPos + const pos = last(detachedCommentsInfo!).detachedCommentEndPos; + if (detachedCommentsInfo!.length - 1) { + detachedCommentsInfo!.pop(); } + else { + detachedCommentsInfo = undefined; + } + + forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); + } - function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { - if (!currentSourceFile) return; - // get the leading comments from detachedPos - const pos = last(detachedCommentsInfo!).detachedCommentEndPos; - if (detachedCommentsInfo!.length - 1) { - detachedCommentsInfo!.pop(); + function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { + const currentDetachedCommentInfo = currentSourceFile && emitDetachedComments(currentSourceFile.text, getCurrentLineMap(), writer, emitComment, range, newLine, commentsDisabled); + if (currentDetachedCommentInfo) { + if (detachedCommentsInfo) { + detachedCommentsInfo.push(currentDetachedCommentInfo); } else { - detachedCommentsInfo = undefined; + detachedCommentsInfo = [currentDetachedCommentInfo]; } - - forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); } + } - function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { - const currentDetachedCommentInfo = currentSourceFile && emitDetachedComments(currentSourceFile.text, getCurrentLineMap(), writer, emitComment, range, newLine, commentsDisabled); - if (currentDetachedCommentInfo) { - if (detachedCommentsInfo) { - detachedCommentsInfo.push(currentDetachedCommentInfo); - } - else { - detachedCommentsInfo = [currentDetachedCommentInfo]; - } - } - } + function emitComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { + if (!currentSourceFile || !shouldWriteComment(currentSourceFile.text, commentPos)) return; + emitPos(commentPos); + writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + } - function emitComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { - if (!currentSourceFile || !shouldWriteComment(currentSourceFile.text, commentPos)) return; - emitPos(commentPos); - writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); - emitPos(commentEnd); - } + /** + * Determine if the given comment is a triple-slash + * + * @return true if the comment is a triple-slash comment else false + */ + function isTripleSlashComment(commentPos: number, commentEnd: number) { + return !!currentSourceFile && isRecognizedTripleSlashComment(currentSourceFile.text, commentPos, commentEnd); + } - /** - * Determine if the given comment is a triple-slash - * - * @return true if the comment is a triple-slash comment else false - */ - function isTripleSlashComment(commentPos: number, commentEnd: number) { - return !!currentSourceFile && isRecognizedTripleSlashComment(currentSourceFile.text, commentPos, commentEnd); + // Source Maps + + function getParsedSourceMap(node: UnparsedSource) { + if (node.parsedSourceMap === undefined && node.sourceMapText !== undefined) { + node.parsedSourceMap = tryParseRawSourceMap(node.sourceMapText) || false; } + return node.parsedSourceMap || undefined; + } - // Source Maps + function pipelineEmitWithSourceMaps(hint: EmitHint, node: Node) { + const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, hint, node); + emitSourceMapsBeforeNode(node); + pipelinePhase(hint, node); + emitSourceMapsAfterNode(node); + } - function getParsedSourceMap(node: UnparsedSource) { - if (node.parsedSourceMap === undefined && node.sourceMapText !== undefined) { - node.parsedSourceMap = tryParseRawSourceMap(node.sourceMapText) || false; - } - return node.parsedSourceMap || undefined; - } - - function pipelineEmitWithSourceMaps(hint: EmitHint, node: Node) { - const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, hint, node); - emitSourceMapsBeforeNode(node); - pipelinePhase(hint, node); - emitSourceMapsAfterNode(node); - } - - function emitSourceMapsBeforeNode(node: Node) { - const emitFlags = getEmitFlags(node); - const sourceMapRange = getSourceMapRange(node); - - // Emit leading sourcemap - if (isUnparsedNode(node)) { - Debug.assertIsDefined(node.parent, "UnparsedNodes must have parent pointers"); - const parsed = getParsedSourceMap(node.parent); - if (parsed && sourceMapGenerator) { - sourceMapGenerator.appendSourceMap( - writer.getLine(), - writer.getColumn(), - parsed, - node.parent.sourceMapPath!, - node.parent.getLineAndCharacterOfPosition(node.pos), - node.parent.getLineAndCharacterOfPosition(node.end) - ); - } - } - else { - const source = sourceMapRange.source || sourceMapSource; - if (node.kind !== SyntaxKind.NotEmittedStatement - && (emitFlags & EmitFlags.NoLeadingSourceMap) === 0 - && sourceMapRange.pos >= 0) { - emitSourcePos(sourceMapRange.source || sourceMapSource, skipSourceTrivia(source, sourceMapRange.pos)); - } - if (emitFlags & EmitFlags.NoNestedSourceMaps) { - sourceMapsDisabled = true; - } + function emitSourceMapsBeforeNode(node: Node) { + const emitFlags = getEmitFlags(node); + const sourceMapRange = getSourceMapRange(node); + + // Emit leading sourcemap + if (isUnparsedNode(node)) { + Debug.assertIsDefined(node.parent, "UnparsedNodes must have parent pointers"); + const parsed = getParsedSourceMap(node.parent); + if (parsed && sourceMapGenerator) { + sourceMapGenerator.appendSourceMap( + writer.getLine(), + writer.getColumn(), + parsed, + node.parent.sourceMapPath!, + node.parent.getLineAndCharacterOfPosition(node.pos), + node.parent.getLineAndCharacterOfPosition(node.end) + ); } } - - function emitSourceMapsAfterNode(node: Node) { - const emitFlags = getEmitFlags(node); - const sourceMapRange = getSourceMapRange(node); - - // Emit trailing sourcemap - if (!isUnparsedNode(node)) { - if (emitFlags & EmitFlags.NoNestedSourceMaps) { - sourceMapsDisabled = false; - } - if (node.kind !== SyntaxKind.NotEmittedStatement - && (emitFlags & EmitFlags.NoTrailingSourceMap) === 0 - && sourceMapRange.end >= 0) { - emitSourcePos(sourceMapRange.source || sourceMapSource, sourceMapRange.end); - } + else { + const source = sourceMapRange.source || sourceMapSource; + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoLeadingSourceMap) === 0 + && sourceMapRange.pos >= 0) { + emitSourcePos(sourceMapRange.source || sourceMapSource, skipSourceTrivia(source, sourceMapRange.pos)); + } + if (emitFlags & EmitFlags.NoNestedSourceMaps) { + sourceMapsDisabled = true; } } + } - /** - * Skips trivia such as comments and white-space that can be optionally overridden by the source-map source - */ - function skipSourceTrivia(source: SourceMapSource, pos: number): number { - return source.skipTrivia ? source.skipTrivia(pos) : skipTrivia(source.text, pos); - } - - /** - * Emits a mapping. - * - * If the position is synthetic (undefined or a negative value), no mapping will be - * created. - * - * @param pos The position. - */ - function emitPos(pos: number) { - if (sourceMapsDisabled || positionIsSynthesized(pos) || isJsonSourceMapSource(sourceMapSource)) { - return; - } + function emitSourceMapsAfterNode(node: Node) { + const emitFlags = getEmitFlags(node); + const sourceMapRange = getSourceMapRange(node); - const { line: sourceLine, character: sourceCharacter } = getLineAndCharacterOfPosition(sourceMapSource, pos); - sourceMapGenerator!.addMapping( - writer.getLine(), - writer.getColumn(), - sourceMapSourceIndex, - sourceLine, - sourceCharacter, - /*nameIndex*/ undefined); - } - - function emitSourcePos(source: SourceMapSource, pos: number) { - if (source !== sourceMapSource) { - const savedSourceMapSource = sourceMapSource; - const savedSourceMapSourceIndex = sourceMapSourceIndex; - setSourceMapSource(source); - emitPos(pos); - resetSourceMapSource(savedSourceMapSource, savedSourceMapSourceIndex); + // Emit trailing sourcemap + if (!isUnparsedNode(node)) { + if (emitFlags & EmitFlags.NoNestedSourceMaps) { + sourceMapsDisabled = false; } - else { - emitPos(pos); + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoTrailingSourceMap) === 0 + && sourceMapRange.end >= 0) { + emitSourcePos(sourceMapRange.source || sourceMapSource, sourceMapRange.end); } } + } - /** - * Emits a token of a node with possible leading and trailing source maps. - * - * @param node The node containing the token. - * @param token The token to emit. - * @param tokenStartPos The start pos of the token. - * @param emitCallback The callback used to emit the token. - */ - function emitTokenWithSourceMap(node: Node | undefined, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) { - if (sourceMapsDisabled || node && isInJsonFile(node)) { - return emitCallback(token, writer, tokenPos); - } + /** + * Skips trivia such as comments and white-space that can be optionally overridden by the source-map source + */ + function skipSourceTrivia(source: SourceMapSource, pos: number): number { + return source.skipTrivia ? source.skipTrivia(pos) : skipTrivia(source.text, pos); + } - const emitNode = node && node.emitNode; - const emitFlags = emitNode && emitNode.flags || EmitFlags.None; - const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; - const source = range && range.source || sourceMapSource; + /** + * Emits a mapping. + * + * If the position is synthetic (undefined or a negative value), no mapping will be + * created. + * + * @param pos The position. + */ + function emitPos(pos: number) { + if (sourceMapsDisabled || positionIsSynthesized(pos) || isJsonSourceMapSource(sourceMapSource)) { + return; + } + + const { line: sourceLine, character: sourceCharacter } = getLineAndCharacterOfPosition(sourceMapSource, pos); + sourceMapGenerator!.addMapping( + writer.getLine(), + writer.getColumn(), + sourceMapSourceIndex, + sourceLine, + sourceCharacter, + /*nameIndex*/ undefined); + } - tokenPos = skipSourceTrivia(source, range ? range.pos : tokenPos); - if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { - emitSourcePos(source, tokenPos); - } + function emitSourcePos(source: SourceMapSource, pos: number) { + if (source !== sourceMapSource) { + const savedSourceMapSource = sourceMapSource; + const savedSourceMapSourceIndex = sourceMapSourceIndex; + setSourceMapSource(source); + emitPos(pos); + resetSourceMapSource(savedSourceMapSource, savedSourceMapSourceIndex); + } + else { + emitPos(pos); + } + } - tokenPos = emitCallback(token, writer, tokenPos); + /** + * Emits a token of a node with possible leading and trailing source maps. + * + * @param node The node containing the token. + * @param token The token to emit. + * @param tokenStartPos The start pos of the token. + * @param emitCallback The callback used to emit the token. + */ + function emitTokenWithSourceMap(node: Node | undefined, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) { + if (sourceMapsDisabled || node && isInJsonFile(node)) { + return emitCallback(token, writer, tokenPos); + } - if (range) tokenPos = range.end; - if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { - emitSourcePos(source, tokenPos); - } + const emitNode = node && node.emitNode; + const emitFlags = emitNode && emitNode.flags || EmitFlags.None; + const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; + const source = range && range.source || sourceMapSource; - return tokenPos; + tokenPos = skipSourceTrivia(source, range ? range.pos : tokenPos); + if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { + emitSourcePos(source, tokenPos); } - function setSourceMapSource(source: SourceMapSource) { - if (sourceMapsDisabled) { - return; - } + tokenPos = emitCallback(token, writer, tokenPos); - sourceMapSource = source; + if (range) tokenPos = range.end; + if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { + emitSourcePos(source, tokenPos); + } - if (source === mostRecentlyAddedSourceMapSource) { - // Fast path for when the new source map is the most recently added, in which case - // we use its captured index without going through the source map generator. - sourceMapSourceIndex = mostRecentlyAddedSourceMapSourceIndex; - return; - } + return tokenPos; + } - if (isJsonSourceMapSource(source)) { - return; - } + function setSourceMapSource(source: SourceMapSource) { + if (sourceMapsDisabled) { + return; + } - sourceMapSourceIndex = sourceMapGenerator!.addSource(source.fileName); - if (printerOptions.inlineSources) { - sourceMapGenerator!.setSourceContent(sourceMapSourceIndex, source.text); - } + sourceMapSource = source; - mostRecentlyAddedSourceMapSource = source; - mostRecentlyAddedSourceMapSourceIndex = sourceMapSourceIndex; + if (source === mostRecentlyAddedSourceMapSource) { + // Fast path for when the new source map is the most recently added, in which case + // we use its captured index without going through the source map generator. + sourceMapSourceIndex = mostRecentlyAddedSourceMapSourceIndex; + return; } - function resetSourceMapSource(source: SourceMapSource, sourceIndex: number) { - sourceMapSource = source; - sourceMapSourceIndex = sourceIndex; + if (isJsonSourceMapSource(source)) { + return; } - function isJsonSourceMapSource(sourceFile: SourceMapSource) { - return fileExtensionIs(sourceFile.fileName, Extension.Json); + sourceMapSourceIndex = sourceMapGenerator!.addSource(source.fileName); + if (printerOptions.inlineSources) { + sourceMapGenerator!.setSourceContent(sourceMapSourceIndex, source.text); } - } - function createBracketsMap() { - const brackets: string[][] = []; - brackets[ListFormat.Braces] = ["{", "}"]; - brackets[ListFormat.Parenthesis] = ["(", ")"]; - brackets[ListFormat.AngleBrackets] = ["<", ">"]; - brackets[ListFormat.SquareBrackets] = ["[", "]"]; - return brackets; + mostRecentlyAddedSourceMapSource = source; + mostRecentlyAddedSourceMapSourceIndex = sourceMapSourceIndex; } - function getOpeningBracket(format: ListFormat) { - return brackets[format & ListFormat.BracketsMask][0]; + function resetSourceMapSource(source: SourceMapSource, sourceIndex: number) { + sourceMapSource = source; + sourceMapSourceIndex = sourceIndex; } - function getClosingBracket(format: ListFormat) { - return brackets[format & ListFormat.BracketsMask][1]; + function isJsonSourceMapSource(sourceFile: SourceMapSource) { + return fileExtensionIs(sourceFile.fileName, Extension.Json); } +} - // Flags enum to track count of temp variables and a few dedicated names - const enum TempFlags { - Auto = 0x00000000, // No preferred name - CountMask = 0x0FFFFFFF, // Temp variable counter - _i = 0x10000000, // Use/preference flag for '_i' - } +function createBracketsMap() { + const brackets: string[][] = []; + brackets[ListFormat.Braces] = ["{", "}"]; + brackets[ListFormat.Parenthesis] = ["(", ")"]; + brackets[ListFormat.AngleBrackets] = ["<", ">"]; + brackets[ListFormat.SquareBrackets] = ["[", "]"]; + return brackets; +} - interface OrdinalParentheizerRuleSelector { - select(index: number): ((node: T) => T) | undefined; - } +function getOpeningBracket(format: ListFormat) { + return brackets[format & ListFormat.BracketsMask][0]; +} - type ParenthesizerRule = (node: T) => T; +function getClosingBracket(format: ListFormat) { + return brackets[format & ListFormat.BracketsMask][1]; +} - type ParenthesizerRuleOrSelector = OrdinalParentheizerRuleSelector | ParenthesizerRule; +// Flags enum to track count of temp variables and a few dedicated names +const enum TempFlags { + Auto = 0x00000000, // No preferred name + CountMask = 0x0FFFFFFF, // Temp variable counter + _i = 0x10000000, // Use/preference flag for '_i' +} - function emitListItemNoParenthesizer(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, _parenthesizerRule: ParenthesizerRuleOrSelector | undefined, _index: number) { - emit(node); - } +interface OrdinalParentheizerRuleSelector { + select(index: number): ((node: T) => T) | undefined; +} - function emitListItemWithParenthesizerRuleSelector(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRuleSelector: OrdinalParentheizerRuleSelector, index: number) { - emit(node, parenthesizerRuleSelector.select(index)); - } +type ParenthesizerRule = (node: T) => T; - function emitListItemWithParenthesizerRule(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRule: ParenthesizerRule | undefined, _index: number) { - emit(node, parenthesizerRule); - } +type ParenthesizerRuleOrSelector = OrdinalParentheizerRuleSelector | ParenthesizerRule; - function getEmitListItem | undefined>(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRule: R): (node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRule: R, index: number) => void { - return emit.length === 1 ? emitListItemNoParenthesizer : - typeof parenthesizerRule === "object" ? emitListItemWithParenthesizerRuleSelector : - emitListItemWithParenthesizerRule; - } +function emitListItemNoParenthesizer(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, _parenthesizerRule: ParenthesizerRuleOrSelector | undefined, _index: number) { + emit(node); +} + +function emitListItemWithParenthesizerRuleSelector(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRuleSelector: OrdinalParentheizerRuleSelector, index: number) { + emit(node, parenthesizerRuleSelector.select(index)); +} + +function emitListItemWithParenthesizerRule(node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRule: ParenthesizerRule | undefined, _index: number) { + emit(node, parenthesizerRule); +} + +function getEmitListItem | undefined>(emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRule: R): (node: Node, emit: (node: Node, parenthesizerRule?: ((node: Node) => Node) | undefined) => void, parenthesizerRule: R, index: number) => void { + return emit.length === 1 ? emitListItemNoParenthesizer : + typeof parenthesizerRule === "object" ? emitListItemWithParenthesizerRuleSelector : + emitListItemWithParenthesizerRule; } diff --git a/src/compiler/factory/baseNodeFactory.ts b/src/compiler/factory/baseNodeFactory.ts index a2eb7a2d0bbe5..39b9490fba770 100644 --- a/src/compiler/factory/baseNodeFactory.ts +++ b/src/compiler/factory/baseNodeFactory.ts @@ -1,54 +1,56 @@ -/* @internal */ -namespace ts { - /** - * A `BaseNodeFactory` is an abstraction over an `ObjectAllocator` that handles caching `Node` constructors - * and allocating `Node` instances based on a set of predefined types. - */ - /* @internal */ - export interface BaseNodeFactory { - createBaseSourceFileNode(kind: SyntaxKind): Node; - createBaseIdentifierNode(kind: SyntaxKind): Node; - createBasePrivateIdentifierNode(kind: SyntaxKind): Node; - createBaseTokenNode(kind: SyntaxKind): Node; - createBaseNode(kind: SyntaxKind): Node; +import { Node, objectAllocator, SyntaxKind } from "../_namespaces/ts"; + +/** + * A `BaseNodeFactory` is an abstraction over an `ObjectAllocator` that handles caching `Node` constructors + * and allocating `Node` instances based on a set of predefined types. + * + * @internal + */ +export interface BaseNodeFactory { + createBaseSourceFileNode(kind: SyntaxKind): Node; + createBaseIdentifierNode(kind: SyntaxKind): Node; + createBasePrivateIdentifierNode(kind: SyntaxKind): Node; + createBaseTokenNode(kind: SyntaxKind): Node; + createBaseNode(kind: SyntaxKind): Node; +} + +/** + * Creates a `BaseNodeFactory` which can be used to create `Node` instances from the constructors provided by the object allocator. + * + * @internal + */ +export function createBaseNodeFactory(): BaseNodeFactory { + let NodeConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + let TokenConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + let IdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + let SourceFileConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + + return { + createBaseSourceFileNode, + createBaseIdentifierNode, + createBasePrivateIdentifierNode, + createBaseTokenNode, + createBaseNode + }; + + function createBaseSourceFileNode(kind: SyntaxKind): Node { + return new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, /*pos*/ -1, /*end*/ -1); + } + + function createBaseIdentifierNode(kind: SyntaxKind): Node { + return new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, /*pos*/ -1, /*end*/ -1); + } + + function createBasePrivateIdentifierNode(kind: SyntaxKind): Node { + return new (PrivateIdentifierConstructor || (PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor()))(kind, /*pos*/ -1, /*end*/ -1); + } + + function createBaseTokenNode(kind: SyntaxKind): Node { + return new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, /*pos*/ -1, /*end*/ -1); } - /** - * Creates a `BaseNodeFactory` which can be used to create `Node` instances from the constructors provided by the object allocator. - */ - export function createBaseNodeFactory(): BaseNodeFactory { - let NodeConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let TokenConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let IdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let SourceFileConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - - return { - createBaseSourceFileNode, - createBaseIdentifierNode, - createBasePrivateIdentifierNode, - createBaseTokenNode, - createBaseNode - }; - - function createBaseSourceFileNode(kind: SyntaxKind): Node { - return new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, /*pos*/ -1, /*end*/ -1); - } - - function createBaseIdentifierNode(kind: SyntaxKind): Node { - return new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, /*pos*/ -1, /*end*/ -1); - } - - function createBasePrivateIdentifierNode(kind: SyntaxKind): Node { - return new (PrivateIdentifierConstructor || (PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor()))(kind, /*pos*/ -1, /*end*/ -1); - } - - function createBaseTokenNode(kind: SyntaxKind): Node { - return new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, /*pos*/ -1, /*end*/ -1); - } - - function createBaseNode(kind: SyntaxKind): Node { - return new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, /*pos*/ -1, /*end*/ -1); - } + function createBaseNode(kind: SyntaxKind): Node { + return new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, /*pos*/ -1, /*end*/ -1); } } diff --git a/src/compiler/factory/emitHelpers.ts b/src/compiler/factory/emitHelpers.ts index 36063fadd55c7..8783c52b767f4 100644 --- a/src/compiler/factory/emitHelpers.ts +++ b/src/compiler/factory/emitHelpers.ts @@ -1,479 +1,494 @@ -/* @internal */ -namespace ts { - export interface EmitHelperFactory { - getUnscopedHelperName(name: string): Identifier; +import { + __String, ArrayLiteralExpression, arrayToMap, BindingOrAssignmentElement, Block, compareValues, Comparison, + createExpressionFromEntityName, Debug, EmitFlags, EmitHelper, EmitHelperUniqueNameCallback, EmitNode, EntityName, + Expression, FunctionExpression, GeneratedIdentifierFlags, getEmitFlags, getEmitScriptTarget, + getPropertyNameOfBindingOrAssignmentElement, Identifier, isCallExpression, isComputedPropertyName, isIdentifier, + memoize, PrivateIdentifierKind, ReadonlyESMap, ScriptTarget, setEmitFlags, setTextRange, SyntaxKind, TextRange, + TransformationContext, UnscopedEmitHelper, +} from "../_namespaces/ts"; + +/** @internal */ +export interface EmitHelperFactory { + getUnscopedHelperName(name: string): Identifier; + // TypeScript Helpers + createDecorateHelper(decoratorExpressions: readonly Expression[], target: Expression, memberName?: Expression, descriptor?: Expression): Expression; + createMetadataHelper(metadataKey: string, metadataValue: Expression): Expression; + createParamHelper(expression: Expression, parameterOffset: number): Expression; + // ES2018 Helpers + createAssignHelper(attributesSegments: readonly Expression[]): Expression; + createAwaitHelper(expression: Expression): Expression; + createAsyncGeneratorHelper(generatorFunc: FunctionExpression, hasLexicalThis: boolean): Expression; + createAsyncDelegatorHelper(expression: Expression): Expression; + createAsyncValuesHelper(expression: Expression): Expression; + // ES2018 Destructuring Helpers + createRestHelper(value: Expression, elements: readonly BindingOrAssignmentElement[], computedTempVariables: readonly Expression[] | undefined, location: TextRange): Expression; + // ES2017 Helpers + createAwaiterHelper(hasLexicalThis: boolean, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression | undefined, body: Block): Expression; + // ES2015 Helpers + createExtendsHelper(name: Identifier): Expression; + createTemplateObjectHelper(cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression): Expression; + createSpreadArrayHelper(to: Expression, from: Expression, packFrom: boolean): Expression; + // ES2015 Destructuring Helpers + createValuesHelper(expression: Expression): Expression; + createReadHelper(iteratorRecord: Expression, count: number | undefined): Expression; + // ES2015 Generator Helpers + createGeneratorHelper(body: FunctionExpression): Expression; + // ES Module Helpers + createCreateBindingHelper(module: Expression, inputName: Expression, outputName: Expression | undefined): Expression; + createImportStarHelper(expression: Expression): Expression; + createImportStarCallbackHelper(): Expression; + createImportDefaultHelper(expression: Expression): Expression; + createExportStarHelper(moduleExpression: Expression, exportsExpression?: Expression): Expression; + // Class Fields Helpers + createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; + createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; + createClassPrivateFieldInHelper(state: Identifier, receiver: Expression): Expression; +} + +/** @internal */ +export function createEmitHelperFactory(context: TransformationContext): EmitHelperFactory { + const factory = context.factory; + const immutableTrue = memoize(() => setEmitFlags(factory.createTrue(), EmitFlags.Immutable)); + const immutableFalse = memoize(() => setEmitFlags(factory.createFalse(), EmitFlags.Immutable)); + + return { + getUnscopedHelperName, // TypeScript Helpers - createDecorateHelper(decoratorExpressions: readonly Expression[], target: Expression, memberName?: Expression, descriptor?: Expression): Expression; - createMetadataHelper(metadataKey: string, metadataValue: Expression): Expression; - createParamHelper(expression: Expression, parameterOffset: number): Expression; + createDecorateHelper, + createMetadataHelper, + createParamHelper, // ES2018 Helpers - createAssignHelper(attributesSegments: readonly Expression[]): Expression; - createAwaitHelper(expression: Expression): Expression; - createAsyncGeneratorHelper(generatorFunc: FunctionExpression, hasLexicalThis: boolean): Expression; - createAsyncDelegatorHelper(expression: Expression): Expression; - createAsyncValuesHelper(expression: Expression): Expression; + createAssignHelper, + createAwaitHelper, + createAsyncGeneratorHelper, + createAsyncDelegatorHelper, + createAsyncValuesHelper, // ES2018 Destructuring Helpers - createRestHelper(value: Expression, elements: readonly BindingOrAssignmentElement[], computedTempVariables: readonly Expression[] | undefined, location: TextRange): Expression; + createRestHelper, // ES2017 Helpers - createAwaiterHelper(hasLexicalThis: boolean, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression | undefined, body: Block): Expression; + createAwaiterHelper, // ES2015 Helpers - createExtendsHelper(name: Identifier): Expression; - createTemplateObjectHelper(cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression): Expression; - createSpreadArrayHelper(to: Expression, from: Expression, packFrom: boolean): Expression; + createExtendsHelper, + createTemplateObjectHelper, + createSpreadArrayHelper, // ES2015 Destructuring Helpers - createValuesHelper(expression: Expression): Expression; - createReadHelper(iteratorRecord: Expression, count: number | undefined): Expression; + createValuesHelper, + createReadHelper, // ES2015 Generator Helpers - createGeneratorHelper(body: FunctionExpression): Expression; + createGeneratorHelper, // ES Module Helpers - createCreateBindingHelper(module: Expression, inputName: Expression, outputName: Expression | undefined): Expression; - createImportStarHelper(expression: Expression): Expression; - createImportStarCallbackHelper(): Expression; - createImportDefaultHelper(expression: Expression): Expression; - createExportStarHelper(moduleExpression: Expression, exportsExpression?: Expression): Expression; + createCreateBindingHelper, + createImportStarHelper, + createImportStarCallbackHelper, + createImportDefaultHelper, + createExportStarHelper, // Class Fields Helpers - createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; - createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; - createClassPrivateFieldInHelper(state: Identifier, receiver: Expression): Expression; - } + createClassPrivateFieldGetHelper, + createClassPrivateFieldSetHelper, + createClassPrivateFieldInHelper + }; - export function createEmitHelperFactory(context: TransformationContext): EmitHelperFactory { - const factory = context.factory; - const immutableTrue = memoize(() => setEmitFlags(factory.createTrue(), EmitFlags.Immutable)); - const immutableFalse = memoize(() => setEmitFlags(factory.createFalse(), EmitFlags.Immutable)); - - return { - getUnscopedHelperName, - // TypeScript Helpers - createDecorateHelper, - createMetadataHelper, - createParamHelper, - // ES2018 Helpers - createAssignHelper, - createAwaitHelper, - createAsyncGeneratorHelper, - createAsyncDelegatorHelper, - createAsyncValuesHelper, - // ES2018 Destructuring Helpers - createRestHelper, - // ES2017 Helpers - createAwaiterHelper, - // ES2015 Helpers - createExtendsHelper, - createTemplateObjectHelper, - createSpreadArrayHelper, - // ES2015 Destructuring Helpers - createValuesHelper, - createReadHelper, - // ES2015 Generator Helpers - createGeneratorHelper, - // ES Module Helpers - createCreateBindingHelper, - createImportStarHelper, - createImportStarCallbackHelper, - createImportDefaultHelper, - createExportStarHelper, - // Class Fields Helpers - createClassPrivateFieldGetHelper, - createClassPrivateFieldSetHelper, - createClassPrivateFieldInHelper - }; - - /** - * Gets an identifier for the name of an *unscoped* emit helper. - */ - function getUnscopedHelperName(name: string) { - return setEmitFlags(factory.createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode); - } + /** + * Gets an identifier for the name of an *unscoped* emit helper. + */ + function getUnscopedHelperName(name: string) { + return setEmitFlags(factory.createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode); + } - // TypeScript Helpers + // TypeScript Helpers - function createDecorateHelper(decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression) { - context.requestEmitHelper(decorateHelper); + function createDecorateHelper(decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression) { + context.requestEmitHelper(decorateHelper); - const argumentsArray: Expression[] = []; - argumentsArray.push(factory.createArrayLiteralExpression(decoratorExpressions, /*multiLine*/ true)); - argumentsArray.push(target); - if (memberName) { - argumentsArray.push(memberName); - if (descriptor) { - argumentsArray.push(descriptor); - } + const argumentsArray: Expression[] = []; + argumentsArray.push(factory.createArrayLiteralExpression(decoratorExpressions, /*multiLine*/ true)); + argumentsArray.push(target); + if (memberName) { + argumentsArray.push(memberName); + if (descriptor) { + argumentsArray.push(descriptor); } - - return factory.createCallExpression( - getUnscopedHelperName("__decorate"), - /*typeArguments*/ undefined, - argumentsArray - ); } - function createMetadataHelper(metadataKey: string, metadataValue: Expression) { - context.requestEmitHelper(metadataHelper); - return factory.createCallExpression( - getUnscopedHelperName("__metadata"), + return factory.createCallExpression( + getUnscopedHelperName("__decorate"), + /*typeArguments*/ undefined, + argumentsArray + ); + } + + function createMetadataHelper(metadataKey: string, metadataValue: Expression) { + context.requestEmitHelper(metadataHelper); + return factory.createCallExpression( + getUnscopedHelperName("__metadata"), + /*typeArguments*/ undefined, + [ + factory.createStringLiteral(metadataKey), + metadataValue + ] + ); + } + + function createParamHelper(expression: Expression, parameterOffset: number, location?: TextRange) { + context.requestEmitHelper(paramHelper); + return setTextRange( + factory.createCallExpression( + getUnscopedHelperName("__param"), /*typeArguments*/ undefined, [ - factory.createStringLiteral(metadataKey), - metadataValue + factory.createNumericLiteral(parameterOffset + ""), + expression ] - ); - } - - function createParamHelper(expression: Expression, parameterOffset: number, location?: TextRange) { - context.requestEmitHelper(paramHelper); - return setTextRange( - factory.createCallExpression( - getUnscopedHelperName("__param"), - /*typeArguments*/ undefined, - [ - factory.createNumericLiteral(parameterOffset + ""), - expression - ] - ), - location - ); - } - - // ES2018 Helpers + ), + location + ); + } - function createAssignHelper(attributesSegments: Expression[]) { - if (getEmitScriptTarget(context.getCompilerOptions()) >= ScriptTarget.ES2015) { - return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("Object"), "assign"), - /*typeArguments*/ undefined, - attributesSegments); - } - context.requestEmitHelper(assignHelper); - return factory.createCallExpression( - getUnscopedHelperName("__assign"), - /*typeArguments*/ undefined, - attributesSegments - ); - } + // ES2018 Helpers - function createAwaitHelper(expression: Expression) { - context.requestEmitHelper(awaitHelper); - return factory.createCallExpression(getUnscopedHelperName("__await"), /*typeArguments*/ undefined, [expression]); + function createAssignHelper(attributesSegments: Expression[]) { + if (getEmitScriptTarget(context.getCompilerOptions()) >= ScriptTarget.ES2015) { + return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("Object"), "assign"), + /*typeArguments*/ undefined, + attributesSegments); } + context.requestEmitHelper(assignHelper); + return factory.createCallExpression( + getUnscopedHelperName("__assign"), + /*typeArguments*/ undefined, + attributesSegments + ); + } - function createAsyncGeneratorHelper(generatorFunc: FunctionExpression, hasLexicalThis: boolean) { - context.requestEmitHelper(awaitHelper); - context.requestEmitHelper(asyncGeneratorHelper); - - // Mark this node as originally an async function - (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; + function createAwaitHelper(expression: Expression) { + context.requestEmitHelper(awaitHelper); + return factory.createCallExpression(getUnscopedHelperName("__await"), /*typeArguments*/ undefined, [expression]); + } - return factory.createCallExpression( - getUnscopedHelperName("__asyncGenerator"), - /*typeArguments*/ undefined, - [ - hasLexicalThis ? factory.createThis() : factory.createVoidZero(), - factory.createIdentifier("arguments"), - generatorFunc - ] - ); - } + function createAsyncGeneratorHelper(generatorFunc: FunctionExpression, hasLexicalThis: boolean) { + context.requestEmitHelper(awaitHelper); + context.requestEmitHelper(asyncGeneratorHelper); + + // Mark this node as originally an async function + (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; + + return factory.createCallExpression( + getUnscopedHelperName("__asyncGenerator"), + /*typeArguments*/ undefined, + [ + hasLexicalThis ? factory.createThis() : factory.createVoidZero(), + factory.createIdentifier("arguments"), + generatorFunc + ] + ); + } - function createAsyncDelegatorHelper(expression: Expression) { - context.requestEmitHelper(awaitHelper); - context.requestEmitHelper(asyncDelegator); - return factory.createCallExpression( - getUnscopedHelperName("__asyncDelegator"), - /*typeArguments*/ undefined, - [expression] - ); - } + function createAsyncDelegatorHelper(expression: Expression) { + context.requestEmitHelper(awaitHelper); + context.requestEmitHelper(asyncDelegator); + return factory.createCallExpression( + getUnscopedHelperName("__asyncDelegator"), + /*typeArguments*/ undefined, + [expression] + ); + } - function createAsyncValuesHelper(expression: Expression) { - context.requestEmitHelper(asyncValues); - return factory.createCallExpression( - getUnscopedHelperName("__asyncValues"), - /*typeArguments*/ undefined, - [expression] - ); - } + function createAsyncValuesHelper(expression: Expression) { + context.requestEmitHelper(asyncValues); + return factory.createCallExpression( + getUnscopedHelperName("__asyncValues"), + /*typeArguments*/ undefined, + [expression] + ); + } - // ES2018 Destructuring Helpers + // ES2018 Destructuring Helpers - /** Given value: o, propName: p, pattern: { a, b, ...p } from the original statement - * `{ a, b, ...p } = o`, create `p = __rest(o, ["a", "b"]);` - */ - function createRestHelper(value: Expression, elements: readonly BindingOrAssignmentElement[], computedTempVariables: readonly Expression[] | undefined, location: TextRange): Expression { - context.requestEmitHelper(restHelper); - const propertyNames: Expression[] = []; - let computedTempVariableOffset = 0; - for (let i = 0; i < elements.length - 1; i++) { - const propertyName = getPropertyNameOfBindingOrAssignmentElement(elements[i]); - if (propertyName) { - if (isComputedPropertyName(propertyName)) { - Debug.assertIsDefined(computedTempVariables, "Encountered computed property name but 'computedTempVariables' argument was not provided."); - const temp = computedTempVariables[computedTempVariableOffset]; - computedTempVariableOffset++; - // typeof _tmp === "symbol" ? _tmp : _tmp + "" - propertyNames.push( - factory.createConditionalExpression( - factory.createTypeCheck(temp, "symbol"), - /*questionToken*/ undefined, - temp, - /*colonToken*/ undefined, - factory.createAdd(temp, factory.createStringLiteral("")) - ) - ); - } - else { - propertyNames.push(factory.createStringLiteralFromNode(propertyName)); - } + /** Given value: o, propName: p, pattern: { a, b, ...p } from the original statement + * `{ a, b, ...p } = o`, create `p = __rest(o, ["a", "b"]);` + */ + function createRestHelper(value: Expression, elements: readonly BindingOrAssignmentElement[], computedTempVariables: readonly Expression[] | undefined, location: TextRange): Expression { + context.requestEmitHelper(restHelper); + const propertyNames: Expression[] = []; + let computedTempVariableOffset = 0; + for (let i = 0; i < elements.length - 1; i++) { + const propertyName = getPropertyNameOfBindingOrAssignmentElement(elements[i]); + if (propertyName) { + if (isComputedPropertyName(propertyName)) { + Debug.assertIsDefined(computedTempVariables, "Encountered computed property name but 'computedTempVariables' argument was not provided."); + const temp = computedTempVariables[computedTempVariableOffset]; + computedTempVariableOffset++; + // typeof _tmp === "symbol" ? _tmp : _tmp + "" + propertyNames.push( + factory.createConditionalExpression( + factory.createTypeCheck(temp, "symbol"), + /*questionToken*/ undefined, + temp, + /*colonToken*/ undefined, + factory.createAdd(temp, factory.createStringLiteral("")) + ) + ); + } + else { + propertyNames.push(factory.createStringLiteralFromNode(propertyName)); } } - return factory.createCallExpression( - getUnscopedHelperName("__rest"), - /*typeArguments*/ undefined, - [ - value, - setTextRange( - factory.createArrayLiteralExpression(propertyNames), - location - )] - ); } + return factory.createCallExpression( + getUnscopedHelperName("__rest"), + /*typeArguments*/ undefined, + [ + value, + setTextRange( + factory.createArrayLiteralExpression(propertyNames), + location + )] + ); + } - // ES2017 Helpers - - function createAwaiterHelper(hasLexicalThis: boolean, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression | undefined, body: Block) { - context.requestEmitHelper(awaiterHelper); + // ES2017 Helpers - const generatorFunc = factory.createFunctionExpression( - /*modifiers*/ undefined, - factory.createToken(SyntaxKind.AsteriskToken), - /*name*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ [], - /*type*/ undefined, - body - ); + function createAwaiterHelper(hasLexicalThis: boolean, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression | undefined, body: Block) { + context.requestEmitHelper(awaiterHelper); + + const generatorFunc = factory.createFunctionExpression( + /*modifiers*/ undefined, + factory.createToken(SyntaxKind.AsteriskToken), + /*name*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ [], + /*type*/ undefined, + body + ); + + // Mark this node as originally an async function + (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; + + return factory.createCallExpression( + getUnscopedHelperName("__awaiter"), + /*typeArguments*/ undefined, + [ + hasLexicalThis ? factory.createThis() : factory.createVoidZero(), + hasLexicalArguments ? factory.createIdentifier("arguments") : factory.createVoidZero(), + promiseConstructor ? createExpressionFromEntityName(factory, promiseConstructor) : factory.createVoidZero(), + generatorFunc + ] + ); + } - // Mark this node as originally an async function - (generatorFunc.emitNode || (generatorFunc.emitNode = {} as EmitNode)).flags |= EmitFlags.AsyncFunctionBody | EmitFlags.ReuseTempVariableScope; + // ES2015 Helpers - return factory.createCallExpression( - getUnscopedHelperName("__awaiter"), - /*typeArguments*/ undefined, - [ - hasLexicalThis ? factory.createThis() : factory.createVoidZero(), - hasLexicalArguments ? factory.createIdentifier("arguments") : factory.createVoidZero(), - promiseConstructor ? createExpressionFromEntityName(factory, promiseConstructor) : factory.createVoidZero(), - generatorFunc - ] - ); - } + function createExtendsHelper(name: Identifier) { + context.requestEmitHelper(extendsHelper); + return factory.createCallExpression( + getUnscopedHelperName("__extends"), + /*typeArguments*/ undefined, + [name, factory.createUniqueName("_super", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel)] + ); + } - // ES2015 Helpers + function createTemplateObjectHelper(cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) { + context.requestEmitHelper(templateObjectHelper); + return factory.createCallExpression( + getUnscopedHelperName("__makeTemplateObject"), + /*typeArguments*/ undefined, + [cooked, raw] + ); + } - function createExtendsHelper(name: Identifier) { - context.requestEmitHelper(extendsHelper); - return factory.createCallExpression( - getUnscopedHelperName("__extends"), - /*typeArguments*/ undefined, - [name, factory.createUniqueName("_super", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel)] - ); - } + function createSpreadArrayHelper(to: Expression, from: Expression, packFrom: boolean) { + context.requestEmitHelper(spreadArrayHelper); + return factory.createCallExpression( + getUnscopedHelperName("__spreadArray"), + /*typeArguments*/ undefined, + [to, from, packFrom ? immutableTrue() : immutableFalse()] + ); + } - function createTemplateObjectHelper(cooked: ArrayLiteralExpression, raw: ArrayLiteralExpression) { - context.requestEmitHelper(templateObjectHelper); - return factory.createCallExpression( - getUnscopedHelperName("__makeTemplateObject"), - /*typeArguments*/ undefined, - [cooked, raw] - ); - } + // ES2015 Destructuring Helpers - function createSpreadArrayHelper(to: Expression, from: Expression, packFrom: boolean) { - context.requestEmitHelper(spreadArrayHelper); - return factory.createCallExpression( - getUnscopedHelperName("__spreadArray"), - /*typeArguments*/ undefined, - [to, from, packFrom ? immutableTrue() : immutableFalse()] - ); - } + function createValuesHelper(expression: Expression) { + context.requestEmitHelper(valuesHelper); + return factory.createCallExpression( + getUnscopedHelperName("__values"), + /*typeArguments*/ undefined, + [expression] + ); + } - // ES2015 Destructuring Helpers + function createReadHelper(iteratorRecord: Expression, count: number | undefined) { + context.requestEmitHelper(readHelper); + return factory.createCallExpression( + getUnscopedHelperName("__read"), + /*typeArguments*/ undefined, + count !== undefined + ? [iteratorRecord, factory.createNumericLiteral(count + "")] + : [iteratorRecord] + ); + } - function createValuesHelper(expression: Expression) { - context.requestEmitHelper(valuesHelper); - return factory.createCallExpression( - getUnscopedHelperName("__values"), - /*typeArguments*/ undefined, - [expression] - ); - } + // ES2015 Generator Helpers - function createReadHelper(iteratorRecord: Expression, count: number | undefined) { - context.requestEmitHelper(readHelper); - return factory.createCallExpression( - getUnscopedHelperName("__read"), - /*typeArguments*/ undefined, - count !== undefined - ? [iteratorRecord, factory.createNumericLiteral(count + "")] - : [iteratorRecord] - ); - } + function createGeneratorHelper(body: FunctionExpression) { + context.requestEmitHelper(generatorHelper); + return factory.createCallExpression( + getUnscopedHelperName("__generator"), + /*typeArguments*/ undefined, + [factory.createThis(), body]); + } - // ES2015 Generator Helpers + // ES Module Helpers - function createGeneratorHelper(body: FunctionExpression) { - context.requestEmitHelper(generatorHelper); - return factory.createCallExpression( - getUnscopedHelperName("__generator"), - /*typeArguments*/ undefined, - [factory.createThis(), body]); - } + function createCreateBindingHelper(module: Expression, inputName: Expression, outputName: Expression | undefined) { + context.requestEmitHelper(createBindingHelper); + return factory.createCallExpression( + getUnscopedHelperName("__createBinding"), + /*typeArguments*/ undefined, + [factory.createIdentifier("exports"), module, inputName, ...(outputName ? [outputName] : [])]); + } - // ES Module Helpers + function createImportStarHelper(expression: Expression) { + context.requestEmitHelper(importStarHelper); + return factory.createCallExpression( + getUnscopedHelperName("__importStar"), + /*typeArguments*/ undefined, + [expression] + ); + } - function createCreateBindingHelper(module: Expression, inputName: Expression, outputName: Expression | undefined) { - context.requestEmitHelper(createBindingHelper); - return factory.createCallExpression( - getUnscopedHelperName("__createBinding"), - /*typeArguments*/ undefined, - [factory.createIdentifier("exports"), module, inputName, ...(outputName ? [outputName] : [])]); - } + function createImportStarCallbackHelper() { + context.requestEmitHelper(importStarHelper); + return getUnscopedHelperName("__importStar"); + } - function createImportStarHelper(expression: Expression) { - context.requestEmitHelper(importStarHelper); - return factory.createCallExpression( - getUnscopedHelperName("__importStar"), - /*typeArguments*/ undefined, - [expression] - ); - } + function createImportDefaultHelper(expression: Expression) { + context.requestEmitHelper(importDefaultHelper); + return factory.createCallExpression( + getUnscopedHelperName("__importDefault"), + /*typeArguments*/ undefined, + [expression] + ); + } - function createImportStarCallbackHelper() { - context.requestEmitHelper(importStarHelper); - return getUnscopedHelperName("__importStar"); - } + function createExportStarHelper(moduleExpression: Expression, exportsExpression: Expression = factory.createIdentifier("exports")) { + context.requestEmitHelper(exportStarHelper); + context.requestEmitHelper(createBindingHelper); + return factory.createCallExpression( + getUnscopedHelperName("__exportStar"), + /*typeArguments*/ undefined, + [moduleExpression, exportsExpression] + ); + } - function createImportDefaultHelper(expression: Expression) { - context.requestEmitHelper(importDefaultHelper); - return factory.createCallExpression( - getUnscopedHelperName("__importDefault"), - /*typeArguments*/ undefined, - [expression] - ); - } + // Class Fields Helpers - function createExportStarHelper(moduleExpression: Expression, exportsExpression: Expression = factory.createIdentifier("exports")) { - context.requestEmitHelper(exportStarHelper); - context.requestEmitHelper(createBindingHelper); - return factory.createCallExpression( - getUnscopedHelperName("__exportStar"), - /*typeArguments*/ undefined, - [moduleExpression, exportsExpression] - ); + function createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined) { + context.requestEmitHelper(classPrivateFieldGetHelper); + let args; + if (!f) { + args = [receiver, state, factory.createStringLiteral(kind)]; } - - // Class Fields Helpers - - function createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined) { - context.requestEmitHelper(classPrivateFieldGetHelper); - let args; - if (!f) { - args = [receiver, state, factory.createStringLiteral(kind)]; - } - else { - args = [receiver, state, factory.createStringLiteral(kind), f]; - } - return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldGet"), /*typeArguments*/ undefined, args); + else { + args = [receiver, state, factory.createStringLiteral(kind), f]; } + return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldGet"), /*typeArguments*/ undefined, args); + } - function createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined) { - context.requestEmitHelper(classPrivateFieldSetHelper); - let args; - if (!f) { - args = [receiver, state, value, factory.createStringLiteral(kind)]; - } - else { - args = [receiver, state, value, factory.createStringLiteral(kind), f]; - } - return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldSet"), /*typeArguments*/ undefined, args); + function createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined) { + context.requestEmitHelper(classPrivateFieldSetHelper); + let args; + if (!f) { + args = [receiver, state, value, factory.createStringLiteral(kind)]; } - - function createClassPrivateFieldInHelper(state: Identifier, receiver: Expression) { - context.requestEmitHelper(classPrivateFieldInHelper); - return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldIn"), /* typeArguments*/ undefined, [state, receiver]); + else { + args = [receiver, state, value, factory.createStringLiteral(kind), f]; } + return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldSet"), /*typeArguments*/ undefined, args); } - /* @internal */ - export function compareEmitHelpers(x: EmitHelper, y: EmitHelper) { - if (x === y) return Comparison.EqualTo; - if (x.priority === y.priority) return Comparison.EqualTo; - if (x.priority === undefined) return Comparison.GreaterThan; - if (y.priority === undefined) return Comparison.LessThan; - return compareValues(x.priority, y.priority); + function createClassPrivateFieldInHelper(state: Identifier, receiver: Expression) { + context.requestEmitHelper(classPrivateFieldInHelper); + return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldIn"), /* typeArguments*/ undefined, [state, receiver]); } +} - /** - * @param input Template string input strings - * @param args Names which need to be made file-level unique - */ - export function helperString(input: TemplateStringsArray, ...args: string[]) { - return (uniqueName: EmitHelperUniqueNameCallback) => { - let result = ""; - for (let i = 0; i < args.length; i++) { - result += input[i]; - result += uniqueName(args[i]); - } - result += input[input.length - 1]; - return result; - }; - } +/** @internal */ +export function compareEmitHelpers(x: EmitHelper, y: EmitHelper) { + if (x === y) return Comparison.EqualTo; + if (x.priority === y.priority) return Comparison.EqualTo; + if (x.priority === undefined) return Comparison.GreaterThan; + if (y.priority === undefined) return Comparison.LessThan; + return compareValues(x.priority, y.priority); +} - // TypeScript Helpers +/** + * @param input Template string input strings + * @param args Names which need to be made file-level unique + * + * @internal + */ +export function helperString(input: TemplateStringsArray, ...args: string[]) { + return (uniqueName: EmitHelperUniqueNameCallback) => { + let result = ""; + for (let i = 0; i < args.length; i++) { + result += input[i]; + result += uniqueName(args[i]); + } + result += input[input.length - 1]; + return result; + }; +} + +// TypeScript Helpers - export const decorateHelper: UnscopedEmitHelper = { - name: "typescript:decorate", - importName: "__decorate", - scoped: false, - priority: 2, - text: ` +/** @internal */ +export const decorateHelper: UnscopedEmitHelper = { + name: "typescript:decorate", + importName: "__decorate", + scoped: false, + priority: 2, + text: ` var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; };` - }; - - export const metadataHelper: UnscopedEmitHelper = { - name: "typescript:metadata", - importName: "__metadata", - scoped: false, - priority: 3, - text: ` +}; + +/** @internal */ +export const metadataHelper: UnscopedEmitHelper = { + name: "typescript:metadata", + importName: "__metadata", + scoped: false, + priority: 3, + text: ` var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); };` - }; - - export const paramHelper: UnscopedEmitHelper = { - name: "typescript:param", - importName: "__param", - scoped: false, - priority: 4, - text: ` +}; + +/** @internal */ +export const paramHelper: UnscopedEmitHelper = { + name: "typescript:param", + importName: "__param", + scoped: false, + priority: 4, + text: ` var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } };` - }; +}; - // ES2018 Helpers +// ES2018 Helpers - export const assignHelper: UnscopedEmitHelper = { - name: "typescript:assign", - importName: "__assign", - scoped: false, - priority: 1, - text: ` +/** @internal */ +export const assignHelper: UnscopedEmitHelper = { + name: "typescript:assign", + importName: "__assign", + scoped: false, + priority: 1, + text: ` var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { @@ -485,22 +500,24 @@ namespace ts { }; return __assign.apply(this, arguments); };` - }; - - export const awaitHelper: UnscopedEmitHelper = { - name: "typescript:await", - importName: "__await", - scoped: false, - text: ` +}; + +/** @internal */ +export const awaitHelper: UnscopedEmitHelper = { + name: "typescript:await", + importName: "__await", + scoped: false, + text: ` var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }` - }; - - export const asyncGeneratorHelper: UnscopedEmitHelper = { - name: "typescript:asyncGenerator", - importName: "__asyncGenerator", - scoped: false, - dependencies: [awaitHelper], - text: ` +}; + +/** @internal */ +export const asyncGeneratorHelper: UnscopedEmitHelper = { + name: "typescript:asyncGenerator", + importName: "__asyncGenerator", + scoped: false, + dependencies: [awaitHelper], + text: ` var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; @@ -512,26 +529,28 @@ namespace ts { function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } };` - }; - - export const asyncDelegator: UnscopedEmitHelper = { - name: "typescript:asyncDelegator", - importName: "__asyncDelegator", - scoped: false, - dependencies: [awaitHelper], - text: ` +}; + +/** @internal */ +export const asyncDelegator: UnscopedEmitHelper = { + name: "typescript:asyncDelegator", + importName: "__asyncDelegator", + scoped: false, + dependencies: [awaitHelper], + text: ` var __asyncDelegator = (this && this.__asyncDelegator) || function (o) { var i, p; return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } };` - }; - - export const asyncValues: UnscopedEmitHelper = { - name: "typescript:asyncValues", - importName: "__asyncValues", - scoped: false, - text: ` +}; + +/** @internal */ +export const asyncValues: UnscopedEmitHelper = { + name: "typescript:asyncValues", + importName: "__asyncValues", + scoped: false, + text: ` var __asyncValues = (this && this.__asyncValues) || function (o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; @@ -539,15 +558,16 @@ namespace ts { function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } };` - }; +}; - // ES2018 Destructuring Helpers +// ES2018 Destructuring Helpers - export const restHelper: UnscopedEmitHelper = { - name: "typescript:rest", - importName: "__rest", - scoped: false, - text: ` +/** @internal */ +export const restHelper: UnscopedEmitHelper = { + name: "typescript:rest", + importName: "__rest", + scoped: false, + text: ` var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) @@ -559,16 +579,17 @@ namespace ts { } return t; };` - }; +}; - // ES2017 Helpers +// ES2017 Helpers - export const awaiterHelper: UnscopedEmitHelper = { - name: "typescript:awaiter", - importName: "__awaiter", - scoped: false, - priority: 5, - text: ` +/** @internal */ +export const awaiterHelper: UnscopedEmitHelper = { + name: "typescript:awaiter", + importName: "__awaiter", + scoped: false, + priority: 5, + text: ` var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -578,16 +599,17 @@ namespace ts { step((generator = generator.apply(thisArg, _arguments || [])).next()); }); };` - }; +}; - // ES2015 Helpers +// ES2015 Helpers - export const extendsHelper: UnscopedEmitHelper = { - name: "typescript:extends", - importName: "__extends", - scoped: false, - priority: 0, - text: ` +/** @internal */ +export const extendsHelper: UnscopedEmitHelper = { + name: "typescript:extends", + importName: "__extends", + scoped: false, + priority: 0, + text: ` var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || @@ -604,25 +626,27 @@ namespace ts { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })();` - }; - - export const templateObjectHelper: UnscopedEmitHelper = { - name: "typescript:makeTemplateObject", - importName: "__makeTemplateObject", - scoped: false, - priority: 0, - text: ` +}; + +/** @internal */ +export const templateObjectHelper: UnscopedEmitHelper = { + name: "typescript:makeTemplateObject", + importName: "__makeTemplateObject", + scoped: false, + priority: 0, + text: ` var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; };` - }; - - export const readHelper: UnscopedEmitHelper = { - name: "typescript:read", - importName: "__read", - scoped: false, - text: ` +}; + +/** @internal */ +export const readHelper: UnscopedEmitHelper = { + name: "typescript:read", + importName: "__read", + scoped: false, + text: ` var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; @@ -639,13 +663,14 @@ namespace ts { } return ar; };` - }; - - export const spreadArrayHelper: UnscopedEmitHelper = { - name: "typescript:spreadArray", - importName: "__spreadArray", - scoped: false, - text: ` +}; + +/** @internal */ +export const spreadArrayHelper: UnscopedEmitHelper = { + name: "typescript:spreadArray", + importName: "__spreadArray", + scoped: false, + text: ` var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { @@ -655,15 +680,16 @@ namespace ts { } return to.concat(ar || Array.prototype.slice.call(from)); };` - }; +}; - // ES2015 Destructuring Helpers +// ES2015 Destructuring Helpers - export const valuesHelper: UnscopedEmitHelper = { - name: "typescript:values", - importName: "__values", - scoped: false, - text: ` +/** @internal */ +export const valuesHelper: UnscopedEmitHelper = { + name: "typescript:values", + importName: "__values", + scoped: false, + text: ` var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); @@ -675,77 +701,78 @@ namespace ts { }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); };` - }; - - // ES2015 Generator Helpers - - // The __generator helper is used by down-level transformations to emulate the runtime - // semantics of an ES2015 generator function. When called, this helper returns an - // object that implements the Iterator protocol, in that it has `next`, `return`, and - // `throw` methods that step through the generator when invoked. - // - // parameters: - // @param thisArg The value to use as the `this` binding for the transformed generator body. - // @param body A function that acts as the transformed generator body. - // - // variables: - // _ Persistent state for the generator that is shared between the helper and the - // generator body. The state object has the following members: - // sent() - A method that returns or throws the current completion value. - // label - The next point at which to resume evaluation of the generator body. - // trys - A stack of protected regions (try/catch/finally blocks). - // ops - A stack of pending instructions when inside of a finally block. - // f A value indicating whether the generator is executing. - // y An iterator to delegate for a yield*. - // t A temporary variable that holds one of the following values (note that these - // cases do not overlap): - // - The completion value when resuming from a `yield` or `yield*`. - // - The error value for a catch block. - // - The current protected region (array of try/catch/finally/end labels). - // - The verb (`next`, `throw`, or `return` method) to delegate to the expression - // of a `yield*`. - // - The result of evaluating the verb delegated to the expression of a `yield*`. - // g A temporary variable that holds onto the generator object until the generator - // is started, allowing it to also act as the `suspendedStart` state. - // - // functions: - // verb(n) Creates a bound callback to the `step` function for opcode `n`. - // step(op) Evaluates opcodes in a generator body until execution is suspended or - // completed. - // - // The __generator helper understands a limited set of instructions: - // 0: next(value?) - Start or resume the generator with the specified value. - // 1: throw(error) - Resume the generator with an exception. If the generator is - // suspended inside of one or more protected regions, evaluates - // any intervening finally blocks between the current label and - // the nearest catch block or function boundary. If uncaught, the - // exception is thrown to the caller. - // 2: return(value?) - Resume the generator as if with a return. If the generator is - // suspended inside of one or more protected regions, evaluates any - // intervening finally blocks. - // 3: break(label) - Jump to the specified label. If the label is outside of the - // current protected region, evaluates any intervening finally - // blocks. - // 4: yield(value?) - Yield execution to the caller with an optional value. When - // resumed, the generator will continue at the next label. - // 5: yield*(value) - Delegates evaluation to the supplied iterator. When - // delegation completes, the generator will continue at the next - // label. - // 6: catch(error) - Handles an exception thrown from within the generator body. If - // the current label is inside of one or more protected regions, - // evaluates any intervening finally blocks between the current - // label and the nearest catch block or function boundary. If - // uncaught, the exception is thrown to the caller. - // 7: endfinally - Ends a finally block, resuming the last instruction prior to - // entering a finally block. - // - // For examples of how these are used, see the comments in ./transformers/generators.ts - export const generatorHelper: UnscopedEmitHelper = { - name: "typescript:generator", - importName: "__generator", - scoped: false, - priority: 6, - text: ` +}; + +// ES2015 Generator Helpers + +// The __generator helper is used by down-level transformations to emulate the runtime +// semantics of an ES2015 generator function. When called, this helper returns an +// object that implements the Iterator protocol, in that it has `next`, `return`, and +// `throw` methods that step through the generator when invoked. +// +// parameters: +// @param thisArg The value to use as the `this` binding for the transformed generator body. +// @param body A function that acts as the transformed generator body. +// +// variables: +// _ Persistent state for the generator that is shared between the helper and the +// generator body. The state object has the following members: +// sent() - A method that returns or throws the current completion value. +// label - The next point at which to resume evaluation of the generator body. +// trys - A stack of protected regions (try/catch/finally blocks). +// ops - A stack of pending instructions when inside of a finally block. +// f A value indicating whether the generator is executing. +// y An iterator to delegate for a yield*. +// t A temporary variable that holds one of the following values (note that these +// cases do not overlap): +// - The completion value when resuming from a `yield` or `yield*`. +// - The error value for a catch block. +// - The current protected region (array of try/catch/finally/end labels). +// - The verb (`next`, `throw`, or `return` method) to delegate to the expression +// of a `yield*`. +// - The result of evaluating the verb delegated to the expression of a `yield*`. +// g A temporary variable that holds onto the generator object until the generator +// is started, allowing it to also act as the `suspendedStart` state. +// +// functions: +// verb(n) Creates a bound callback to the `step` function for opcode `n`. +// step(op) Evaluates opcodes in a generator body until execution is suspended or +// completed. +// +// The __generator helper understands a limited set of instructions: +// 0: next(value?) - Start or resume the generator with the specified value. +// 1: throw(error) - Resume the generator with an exception. If the generator is +// suspended inside of one or more protected regions, evaluates +// any intervening finally blocks between the current label and +// the nearest catch block or function boundary. If uncaught, the +// exception is thrown to the caller. +// 2: return(value?) - Resume the generator as if with a return. If the generator is +// suspended inside of one or more protected regions, evaluates any +// intervening finally blocks. +// 3: break(label) - Jump to the specified label. If the label is outside of the +// current protected region, evaluates any intervening finally +// blocks. +// 4: yield(value?) - Yield execution to the caller with an optional value. When +// resumed, the generator will continue at the next label. +// 5: yield*(value) - Delegates evaluation to the supplied iterator. When +// delegation completes, the generator will continue at the next +// label. +// 6: catch(error) - Handles an exception thrown from within the generator body. If +// the current label is inside of one or more protected regions, +// evaluates any intervening finally blocks between the current +// label and the nearest catch block or function boundary. If +// uncaught, the exception is thrown to the caller. +// 7: endfinally - Ends a finally block, resuming the last instruction prior to +// entering a finally block. +// +// For examples of how these are used, see the comments in ./transformers/generators.ts +/** @internal */ +export const generatorHelper: UnscopedEmitHelper = { + name: "typescript:generator", + importName: "__generator", + scoped: false, + priority: 6, + text: ` var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; @@ -773,16 +800,17 @@ namespace ts { if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } };` - }; +}; - // ES Module Helpers +// ES Module Helpers - export const createBindingHelper: UnscopedEmitHelper = { - name: "typescript:commonjscreatebinding", - importName: "__createBinding", - scoped: false, - priority: 1, - text: ` +/** @internal */ +export const createBindingHelper: UnscopedEmitHelper = { + name: "typescript:commonjscreatebinding", + importName: "__createBinding", + scoped: false, + priority: 1, + text: ` var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); @@ -794,29 +822,31 @@ namespace ts { if (k2 === undefined) k2 = k; o[k2] = m[k]; }));` - }; - - export const setModuleDefaultHelper: UnscopedEmitHelper = { - name: "typescript:commonjscreatevalue", - importName: "__setModuleDefault", - scoped: false, - priority: 1, - text: ` +}; + +/** @internal */ +export const setModuleDefaultHelper: UnscopedEmitHelper = { + name: "typescript:commonjscreatevalue", + importName: "__setModuleDefault", + scoped: false, + priority: 1, + text: ` var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; });` - }; - - // emit helper for `import * as Name from "foo"` - export const importStarHelper: UnscopedEmitHelper = { - name: "typescript:commonjsimportstar", - importName: "__importStar", - scoped: false, - dependencies: [createBindingHelper, setModuleDefaultHelper], - priority: 2, - text: ` +}; + +// emit helper for `import * as Name from "foo"` +/** @internal */ +export const importStarHelper: UnscopedEmitHelper = { + name: "typescript:commonjsimportstar", + importName: "__importStar", + scoped: false, + dependencies: [createBindingHelper, setModuleDefaultHelper], + priority: 2, + text: ` var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; @@ -824,230 +854,241 @@ namespace ts { __setModuleDefault(result, mod); return result; };` - }; - - // emit helper for `import Name from "foo"` - export const importDefaultHelper: UnscopedEmitHelper = { - name: "typescript:commonjsimportdefault", - importName: "__importDefault", - scoped: false, - text: ` +}; + +// emit helper for `import Name from "foo"` +/** @internal */ +export const importDefaultHelper: UnscopedEmitHelper = { + name: "typescript:commonjsimportdefault", + importName: "__importDefault", + scoped: false, + text: ` var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; };` - }; - - export const exportStarHelper: UnscopedEmitHelper = { - name: "typescript:export-star", - importName: "__exportStar", - scoped: false, - dependencies: [createBindingHelper], - priority: 2, - text: ` +}; + +/** @internal */ +export const exportStarHelper: UnscopedEmitHelper = { + name: "typescript:export-star", + importName: "__exportStar", + scoped: false, + dependencies: [createBindingHelper], + priority: 2, + text: ` var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); };` - }; - - /** - * Parameters: - * @param receiver — The object from which the private member will be read. - * @param state — One of the following: - * - A WeakMap used to read a private instance field. - * - A WeakSet used as an instance brand for private instance methods and accessors. - * - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors. - * @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values: - * - undefined — Indicates a private instance field (pre TS 4.3). - * - "f" — Indicates a private field (instance or static). - * - "m" — Indicates a private method (instance or static). - * - "a" — Indicates a private accessor (instance or static). - * @param f — (optional pre TS 4.3) Depends on the arguments for state and kind: - * - If kind is "m", this should be the function corresponding to the static or instance method. - * - If kind is "a", this should be the function corresponding to the getter method, or undefined if the getter was not defined. - * - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated. - * Usage: - * This helper will only ever be used by the compiler in the following ways: - * - * Reading from a private instance field (pre TS 4.3): - * __classPrivateFieldGet(, ) - * - * Reading from a private instance field (TS 4.3+): - * __classPrivateFieldGet(, , "f") - * - * Reading from a private instance get accessor (when defined, TS 4.3+): - * __classPrivateFieldGet(, , "a", ) - * - * Reading from a private instance get accessor (when not defined, TS 4.3+): - * __classPrivateFieldGet(, , "a", void 0) - * NOTE: This always results in a runtime error. - * - * Reading from a private instance method (TS 4.3+): - * __classPrivateFieldGet(, , "m", ) - * - * Reading from a private static field (TS 4.3+): - * __classPrivateFieldGet(, , "f", <{ value: any }>) - * - * Reading from a private static get accessor (when defined, TS 4.3+): - * __classPrivateFieldGet(, , "a", ) - * - * Reading from a private static get accessor (when not defined, TS 4.3+): - * __classPrivateFieldGet(, , "a", void 0) - * NOTE: This always results in a runtime error. - * - * Reading from a private static method (TS 4.3+): - * __classPrivateFieldGet(, , "m", ) - */ - export const classPrivateFieldGetHelper: UnscopedEmitHelper = { - name: "typescript:classPrivateFieldGet", - importName: "__classPrivateFieldGet", - scoped: false, - text: ` +}; + +/** + * Parameters: + * @param receiver — The object from which the private member will be read. + * @param state — One of the following: + * - A WeakMap used to read a private instance field. + * - A WeakSet used as an instance brand for private instance methods and accessors. + * - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors. + * @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values: + * - undefined — Indicates a private instance field (pre TS 4.3). + * - "f" — Indicates a private field (instance or static). + * - "m" — Indicates a private method (instance or static). + * - "a" — Indicates a private accessor (instance or static). + * @param f — (optional pre TS 4.3) Depends on the arguments for state and kind: + * - If kind is "m", this should be the function corresponding to the static or instance method. + * - If kind is "a", this should be the function corresponding to the getter method, or undefined if the getter was not defined. + * - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated. + * Usage: + * This helper will only ever be used by the compiler in the following ways: + * + * Reading from a private instance field (pre TS 4.3): + * __classPrivateFieldGet(, ) + * + * Reading from a private instance field (TS 4.3+): + * __classPrivateFieldGet(, , "f") + * + * Reading from a private instance get accessor (when defined, TS 4.3+): + * __classPrivateFieldGet(, , "a", ) + * + * Reading from a private instance get accessor (when not defined, TS 4.3+): + * __classPrivateFieldGet(, , "a", void 0) + * NOTE: This always results in a runtime error. + * + * Reading from a private instance method (TS 4.3+): + * __classPrivateFieldGet(, , "m", ) + * + * Reading from a private static field (TS 4.3+): + * __classPrivateFieldGet(, , "f", <{ value: any }>) + * + * Reading from a private static get accessor (when defined, TS 4.3+): + * __classPrivateFieldGet(, , "a", ) + * + * Reading from a private static get accessor (when not defined, TS 4.3+): + * __classPrivateFieldGet(, , "a", void 0) + * NOTE: This always results in a runtime error. + * + * Reading from a private static method (TS 4.3+): + * __classPrivateFieldGet(, , "m", ) + * + * @internal + */ +export const classPrivateFieldGetHelper: UnscopedEmitHelper = { + name: "typescript:classPrivateFieldGet", + importName: "__classPrivateFieldGet", + scoped: false, + text: ` var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); };` - }; - - /** - * Parameters: - * @param receiver — The object on which the private member will be set. - * @param state — One of the following: - * - A WeakMap used to store a private instance field. - * - A WeakSet used as an instance brand for private instance methods and accessors. - * - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors. - * @param value — The value to set. - * @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values: - * - undefined — Indicates a private instance field (pre TS 4.3). - * - "f" — Indicates a private field (instance or static). - * - "m" — Indicates a private method (instance or static). - * - "a" — Indicates a private accessor (instance or static). - * @param f — (optional pre TS 4.3) Depends on the arguments for state and kind: - * - If kind is "m", this should be the function corresponding to the static or instance method. - * - If kind is "a", this should be the function corresponding to the setter method, or undefined if the setter was not defined. - * - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated. - * Usage: - * This helper will only ever be used by the compiler in the following ways: - * - * Writing to a private instance field (pre TS 4.3): - * __classPrivateFieldSet(, , ) - * - * Writing to a private instance field (TS 4.3+): - * __classPrivateFieldSet(, , , "f") - * - * Writing to a private instance set accessor (when defined, TS 4.3+): - * __classPrivateFieldSet(, , , "a", ) - * - * Writing to a private instance set accessor (when not defined, TS 4.3+): - * __classPrivateFieldSet(, , , "a", void 0) - * NOTE: This always results in a runtime error. - * - * Writing to a private instance method (TS 4.3+): - * __classPrivateFieldSet(, , , "m", ) - * NOTE: This always results in a runtime error. - * - * Writing to a private static field (TS 4.3+): - * __classPrivateFieldSet(, , , "f", <{ value: any }>) - * - * Writing to a private static set accessor (when defined, TS 4.3+): - * __classPrivateFieldSet(, , , "a", ) - * - * Writing to a private static set accessor (when not defined, TS 4.3+): - * __classPrivateFieldSet(, , , "a", void 0) - * NOTE: This always results in a runtime error. - * - * Writing to a private static method (TS 4.3+): - * __classPrivateFieldSet(, , , "m", ) - * NOTE: This always results in a runtime error. - */ - export const classPrivateFieldSetHelper: UnscopedEmitHelper = { - name: "typescript:classPrivateFieldSet", - importName: "__classPrivateFieldSet", - scoped: false, - text: ` +}; + +/** + * Parameters: + * @param receiver — The object on which the private member will be set. + * @param state — One of the following: + * - A WeakMap used to store a private instance field. + * - A WeakSet used as an instance brand for private instance methods and accessors. + * - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors. + * @param value — The value to set. + * @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values: + * - undefined — Indicates a private instance field (pre TS 4.3). + * - "f" — Indicates a private field (instance or static). + * - "m" — Indicates a private method (instance or static). + * - "a" — Indicates a private accessor (instance or static). + * @param f — (optional pre TS 4.3) Depends on the arguments for state and kind: + * - If kind is "m", this should be the function corresponding to the static or instance method. + * - If kind is "a", this should be the function corresponding to the setter method, or undefined if the setter was not defined. + * - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated. + * Usage: + * This helper will only ever be used by the compiler in the following ways: + * + * Writing to a private instance field (pre TS 4.3): + * __classPrivateFieldSet(, , ) + * + * Writing to a private instance field (TS 4.3+): + * __classPrivateFieldSet(, , , "f") + * + * Writing to a private instance set accessor (when defined, TS 4.3+): + * __classPrivateFieldSet(, , , "a", ) + * + * Writing to a private instance set accessor (when not defined, TS 4.3+): + * __classPrivateFieldSet(, , , "a", void 0) + * NOTE: This always results in a runtime error. + * + * Writing to a private instance method (TS 4.3+): + * __classPrivateFieldSet(, , , "m", ) + * NOTE: This always results in a runtime error. + * + * Writing to a private static field (TS 4.3+): + * __classPrivateFieldSet(, , , "f", <{ value: any }>) + * + * Writing to a private static set accessor (when defined, TS 4.3+): + * __classPrivateFieldSet(, , , "a", ) + * + * Writing to a private static set accessor (when not defined, TS 4.3+): + * __classPrivateFieldSet(, , , "a", void 0) + * NOTE: This always results in a runtime error. + * + * Writing to a private static method (TS 4.3+): + * __classPrivateFieldSet(, , , "m", ) + * NOTE: This always results in a runtime error. + * + * @internal + */ +export const classPrivateFieldSetHelper: UnscopedEmitHelper = { + name: "typescript:classPrivateFieldSet", + importName: "__classPrivateFieldSet", + scoped: false, + text: ` var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; };` - }; - - /** - * Parameters: - * @param state — One of the following: - * - A WeakMap when the member is a private instance field. - * - A WeakSet when the member is a private instance method or accessor. - * - A function value that should be the undecorated class constructor when the member is a private static field, method, or accessor. - * @param receiver — The object being checked if it has the private member. - * - * Usage: - * This helper is used to transform `#field in expression` to - * `__classPrivateFieldIn(, expression)` - */ - export const classPrivateFieldInHelper: UnscopedEmitHelper = { - name: "typescript:classPrivateFieldIn", - importName: "__classPrivateFieldIn", - scoped: false, - text: ` +}; + +/** + * Parameters: + * @param state — One of the following: + * - A WeakMap when the member is a private instance field. + * - A WeakSet when the member is a private instance method or accessor. + * - A function value that should be the undecorated class constructor when the member is a private static field, method, or accessor. + * @param receiver — The object being checked if it has the private member. + * + * Usage: + * This helper is used to transform `#field in expression` to + * `__classPrivateFieldIn(, expression)` + * + * @internal + */ +export const classPrivateFieldInHelper: UnscopedEmitHelper = { + name: "typescript:classPrivateFieldIn", + importName: "__classPrivateFieldIn", + scoped: false, + text: ` var __classPrivateFieldIn = (this && this.__classPrivateFieldIn) || function(state, receiver) { if (receiver === null || (typeof receiver !== "object" && typeof receiver !== "function")) throw new TypeError("Cannot use 'in' operator on non-object"); return typeof state === "function" ? receiver === state : state.has(receiver); };` - }; - - let allUnscopedEmitHelpers: ReadonlyESMap | undefined; - - export function getAllUnscopedEmitHelpers() { - return allUnscopedEmitHelpers || (allUnscopedEmitHelpers = arrayToMap([ - decorateHelper, - metadataHelper, - paramHelper, - assignHelper, - awaitHelper, - asyncGeneratorHelper, - asyncDelegator, - asyncValues, - restHelper, - awaiterHelper, - extendsHelper, - templateObjectHelper, - spreadArrayHelper, - valuesHelper, - readHelper, - generatorHelper, - importStarHelper, - importDefaultHelper, - exportStarHelper, - classPrivateFieldGetHelper, - classPrivateFieldSetHelper, - classPrivateFieldInHelper, - createBindingHelper, - setModuleDefaultHelper - ], helper => helper.name)); - } +}; + +let allUnscopedEmitHelpers: ReadonlyESMap | undefined; + +/** @internal */ +export function getAllUnscopedEmitHelpers() { + return allUnscopedEmitHelpers || (allUnscopedEmitHelpers = arrayToMap([ + decorateHelper, + metadataHelper, + paramHelper, + assignHelper, + awaitHelper, + asyncGeneratorHelper, + asyncDelegator, + asyncValues, + restHelper, + awaiterHelper, + extendsHelper, + templateObjectHelper, + spreadArrayHelper, + valuesHelper, + readHelper, + generatorHelper, + importStarHelper, + importDefaultHelper, + exportStarHelper, + classPrivateFieldGetHelper, + classPrivateFieldSetHelper, + classPrivateFieldInHelper, + createBindingHelper, + setModuleDefaultHelper + ], helper => helper.name)); +} - export const asyncSuperHelper: EmitHelper = { - name: "typescript:async-super", - scoped: true, - text: helperString` +/** @internal */ +export const asyncSuperHelper: EmitHelper = { + name: "typescript:async-super", + scoped: true, + text: helperString` const ${"_superIndex"} = name => super[name];` - }; +}; - export const advancedAsyncSuperHelper: EmitHelper = { - name: "typescript:advanced-async-super", - scoped: true, - text: helperString` +/** @internal */ +export const advancedAsyncSuperHelper: EmitHelper = { + name: "typescript:advanced-async-super", + scoped: true, + text: helperString` const ${"_superIndex"} = (function (geti, seti) { const cache = Object.create(null); return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); })(name => super[name], (name, value) => super[name] = value);` - }; - - export function isCallToHelper(firstSegment: Expression, helperName: __String): boolean { - return isCallExpression(firstSegment) - && isIdentifier(firstSegment.expression) - && (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName) !== 0 - && firstSegment.expression.escapedText === helperName; - } +}; + +/** @internal */ +export function isCallToHelper(firstSegment: Expression, helperName: __String): boolean { + return isCallExpression(firstSegment) + && isIdentifier(firstSegment.expression) + && (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName) !== 0 + && firstSegment.expression.escapedText === helperName; } diff --git a/src/compiler/factory/emitNode.ts b/src/compiler/factory/emitNode.ts index 19ebfd53ba2ba..693094489a67e 100644 --- a/src/compiler/factory/emitNode.ts +++ b/src/compiler/factory/emitNode.ts @@ -1,294 +1,303 @@ -namespace ts { - /** - * Associates a node with the current transformation, initializing - * various transient transformation properties. - * @internal - */ - export function getOrCreateEmitNode(node: Node): EmitNode { - if (!node.emitNode) { - if (isParseTreeNode(node)) { - // To avoid holding onto transformation artifacts, we keep track of any - // parse tree node we are annotating. This allows us to clean them up after - // all transformations have completed. - if (node.kind === SyntaxKind.SourceFile) { - return node.emitNode = { annotatedNodes: [node] } as EmitNode; - } - - const sourceFile = getSourceFileOfNode(getParseTreeNode(getSourceFileOfNode(node))) ?? Debug.fail("Could not determine parsed source file."); - getOrCreateEmitNode(sourceFile).annotatedNodes!.push(node); +import { + AccessExpression, append, appendIfUnique, Debug, EmitFlags, EmitHelper, EmitNode, getParseTreeNode, + getSourceFileOfNode, isParseTreeNode, Node, orderedRemoveItem, SnippetElement, some, SourceFile, SourceMapRange, + SyntaxKind, SynthesizedComment, TextRange, TypeNode, +} from "../_namespaces/ts"; + +/** + * Associates a node with the current transformation, initializing + * various transient transformation properties. + * @internal + */ +export function getOrCreateEmitNode(node: Node): EmitNode { + if (!node.emitNode) { + if (isParseTreeNode(node)) { + // To avoid holding onto transformation artifacts, we keep track of any + // parse tree node we are annotating. This allows us to clean them up after + // all transformations have completed. + if (node.kind === SyntaxKind.SourceFile) { + return node.emitNode = { annotatedNodes: [node] } as EmitNode; } - node.emitNode = {} as EmitNode; + const sourceFile = getSourceFileOfNode(getParseTreeNode(getSourceFileOfNode(node))) ?? Debug.fail("Could not determine parsed source file."); + getOrCreateEmitNode(sourceFile).annotatedNodes!.push(node); } - else { - Debug.assert(!(node.emitNode.flags & EmitFlags.Immutable), "Invalid attempt to mutate an immutable node."); - } - return node.emitNode; + + node.emitNode = {} as EmitNode; + } + else { + Debug.assert(!(node.emitNode.flags & EmitFlags.Immutable), "Invalid attempt to mutate an immutable node."); } + return node.emitNode; +} - /** - * Clears any `EmitNode` entries from parse-tree nodes. - * @param sourceFile A source file. - */ - export function disposeEmitNodes(sourceFile: SourceFile | undefined) { - // During transformation we may need to annotate a parse tree node with transient - // transformation properties. As parse tree nodes live longer than transformation - // nodes, we need to make sure we reclaim any memory allocated for custom ranges - // from these nodes to ensure we do not hold onto entire subtrees just for position - // information. We also need to reset these nodes to a pre-transformation state - // for incremental parsing scenarios so that we do not impact later emit. - const annotatedNodes = getSourceFileOfNode(getParseTreeNode(sourceFile))?.emitNode?.annotatedNodes; - if (annotatedNodes) { - for (const node of annotatedNodes) { - node.emitNode = undefined; - } +/** + * Clears any `EmitNode` entries from parse-tree nodes. + * @param sourceFile A source file. + */ +export function disposeEmitNodes(sourceFile: SourceFile | undefined) { + // During transformation we may need to annotate a parse tree node with transient + // transformation properties. As parse tree nodes live longer than transformation + // nodes, we need to make sure we reclaim any memory allocated for custom ranges + // from these nodes to ensure we do not hold onto entire subtrees just for position + // information. We also need to reset these nodes to a pre-transformation state + // for incremental parsing scenarios so that we do not impact later emit. + const annotatedNodes = getSourceFileOfNode(getParseTreeNode(sourceFile))?.emitNode?.annotatedNodes; + if (annotatedNodes) { + for (const node of annotatedNodes) { + node.emitNode = undefined; } } +} - /** - * 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; - } +/** + * 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; +} - /** - * Sets flags that control emit behavior of a node. - */ - export function setEmitFlags(node: T, emitFlags: EmitFlags) { - getOrCreateEmitNode(node).flags = emitFlags; - return node; - } +/** + * Sets flags that control emit behavior of a node. + */ +export function setEmitFlags(node: T, emitFlags: EmitFlags) { + getOrCreateEmitNode(node).flags = emitFlags; + return node; +} - /** - * Sets flags that control emit behavior of a node. - */ - /* @internal */ - export function addEmitFlags(node: T, emitFlags: EmitFlags) { - const emitNode = getOrCreateEmitNode(node); - emitNode.flags = emitNode.flags | emitFlags; - return node; - } +/** + * Sets flags that control emit behavior of a node. + * + * @internal + */ +export function addEmitFlags(node: T, emitFlags: EmitFlags) { + const emitNode = getOrCreateEmitNode(node); + emitNode.flags = emitNode.flags | emitFlags; + return node; +} - /** - * Gets a custom text range to use when emitting source maps. - */ - export function getSourceMapRange(node: Node): SourceMapRange { - return node.emitNode?.sourceMapRange ?? node; - } +/** + * Gets a custom text range to use when emitting source maps. + */ +export function getSourceMapRange(node: Node): SourceMapRange { + return node.emitNode?.sourceMapRange ?? node; +} - /** - * Sets a custom text range to use when emitting source maps. - */ - export function setSourceMapRange(node: T, range: SourceMapRange | undefined) { - getOrCreateEmitNode(node).sourceMapRange = range; - return node; - } +/** + * Sets a custom text range to use when emitting source maps. + */ +export function setSourceMapRange(node: T, range: SourceMapRange | undefined) { + getOrCreateEmitNode(node).sourceMapRange = range; + return node; +} - /** - * Gets the TextRange to use for source maps for a token of a node. - */ - export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined { - return node.emitNode?.tokenSourceMapRanges?.[token]; - } +/** + * Gets the TextRange to use for source maps for a token of a node. + */ +export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined { + return node.emitNode?.tokenSourceMapRanges?.[token]; +} - /** - * Sets the TextRange to use for source maps for a token of a node. - */ - export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: SourceMapRange | undefined) { - const emitNode = getOrCreateEmitNode(node); - const tokenSourceMapRanges = emitNode.tokenSourceMapRanges ?? (emitNode.tokenSourceMapRanges = []); - tokenSourceMapRanges[token] = range; - return node; - } +/** + * Sets the TextRange to use for source maps for a token of a node. + */ +export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: SourceMapRange | undefined) { + const emitNode = getOrCreateEmitNode(node); + const tokenSourceMapRanges = emitNode.tokenSourceMapRanges ?? (emitNode.tokenSourceMapRanges = []); + tokenSourceMapRanges[token] = range; + return node; +} - /** - * Gets a custom text range to use when emitting comments. - */ - /*@internal*/ - export function getStartsOnNewLine(node: Node) { - return node.emitNode?.startsOnNewLine; - } +/** + * Gets a custom text range to use when emitting comments. + * + * @internal + */ +export function getStartsOnNewLine(node: Node) { + return node.emitNode?.startsOnNewLine; +} - /** - * Sets a custom text range to use when emitting comments. - */ - /*@internal*/ - export function setStartsOnNewLine(node: T, newLine: boolean) { - getOrCreateEmitNode(node).startsOnNewLine = newLine; - return node; - } +/** + * Sets a custom text range to use when emitting comments. + * + * @internal + */ +export function setStartsOnNewLine(node: T, newLine: boolean) { + getOrCreateEmitNode(node).startsOnNewLine = newLine; + return node; +} - /** - * Gets a custom text range to use when emitting comments. - */ - export function getCommentRange(node: Node): TextRange { - return node.emitNode?.commentRange ?? node; - } +/** + * Gets a custom text range to use when emitting comments. + */ +export function getCommentRange(node: Node): TextRange { + return node.emitNode?.commentRange ?? node; +} - /** - * Sets a custom text range to use when emitting comments. - */ - export function setCommentRange(node: T, range: TextRange) { - getOrCreateEmitNode(node).commentRange = range; - return node; - } +/** + * Sets a custom text range to use when emitting comments. + */ +export function setCommentRange(node: T, range: TextRange) { + getOrCreateEmitNode(node).commentRange = range; + return node; +} - export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined { - return node.emitNode?.leadingComments; - } +export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined { + return node.emitNode?.leadingComments; +} - export function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[] | undefined) { - getOrCreateEmitNode(node).leadingComments = comments; - return node; - } +export function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[] | undefined) { + getOrCreateEmitNode(node).leadingComments = comments; + return node; +} - export function addSyntheticLeadingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { - return setSyntheticLeadingComments(node, append(getSyntheticLeadingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); - } +export function addSyntheticLeadingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { + return setSyntheticLeadingComments(node, append(getSyntheticLeadingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); +} - export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined { - return node.emitNode?.trailingComments; - } +export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined { + return node.emitNode?.trailingComments; +} - export function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[] | undefined) { - getOrCreateEmitNode(node).trailingComments = comments; - return node; - } +export function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[] | undefined) { + getOrCreateEmitNode(node).trailingComments = comments; + return node; +} - export function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { - return setSyntheticTrailingComments(node, append(getSyntheticTrailingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); - } +export function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) { + return setSyntheticTrailingComments(node, append(getSyntheticTrailingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text })); +} - export function moveSyntheticComments(node: T, original: Node): T { - setSyntheticLeadingComments(node, getSyntheticLeadingComments(original)); - setSyntheticTrailingComments(node, getSyntheticTrailingComments(original)); - const emit = getOrCreateEmitNode(original); - emit.leadingComments = undefined; - emit.trailingComments = undefined; - return node; - } +export function moveSyntheticComments(node: T, original: Node): T { + setSyntheticLeadingComments(node, getSyntheticLeadingComments(original)); + setSyntheticTrailingComments(node, getSyntheticTrailingComments(original)); + const emit = getOrCreateEmitNode(original); + emit.leadingComments = undefined; + emit.trailingComments = undefined; + return node; +} - /** - * Gets the constant value to emit for an expression representing an enum. - */ - export function getConstantValue(node: AccessExpression): string | number | undefined { - return node.emitNode?.constantValue; - } +/** + * Gets the constant value to emit for an expression representing an enum. + */ +export function getConstantValue(node: AccessExpression): string | number | undefined { + return node.emitNode?.constantValue; +} - /** - * Sets the constant value to emit for an expression. - */ - export function setConstantValue(node: AccessExpression, value: string | number): AccessExpression { - const emitNode = getOrCreateEmitNode(node); - emitNode.constantValue = value; - return node; - } +/** + * Sets the constant value to emit for an expression. + */ +export function setConstantValue(node: AccessExpression, value: string | number): AccessExpression { + const emitNode = getOrCreateEmitNode(node); + emitNode.constantValue = value; + return node; +} - /** - * Adds an EmitHelper to a node. - */ - export function addEmitHelper(node: T, helper: EmitHelper): T { - const emitNode = getOrCreateEmitNode(node); - emitNode.helpers = append(emitNode.helpers, helper); - return node; - } +/** + * Adds an EmitHelper to a node. + */ +export function addEmitHelper(node: T, helper: EmitHelper): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.helpers = append(emitNode.helpers, helper); + return node; +} - /** - * Add EmitHelpers to a node. - */ - export function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T { - if (some(helpers)) { - const emitNode = getOrCreateEmitNode(node); - for (const helper of helpers) { - emitNode.helpers = appendIfUnique(emitNode.helpers, helper); - } +/** + * Add EmitHelpers to a node. + */ +export function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T { + if (some(helpers)) { + const emitNode = getOrCreateEmitNode(node); + for (const helper of helpers) { + emitNode.helpers = appendIfUnique(emitNode.helpers, helper); } - return node; } + return node; +} - /** - * Removes an EmitHelper from a node. - */ - export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { - const helpers = node.emitNode?.helpers; - if (helpers) { - return orderedRemoveItem(helpers, helper); - } - return false; +/** + * Removes an EmitHelper from a node. + */ +export function removeEmitHelper(node: Node, helper: EmitHelper): boolean { + const helpers = node.emitNode?.helpers; + if (helpers) { + return orderedRemoveItem(helpers, helper); } + return false; +} - /** - * Gets the EmitHelpers of a node. - */ - export function getEmitHelpers(node: Node): EmitHelper[] | undefined { - return node.emitNode?.helpers; - } +/** + * Gets the EmitHelpers of a node. + */ +export function getEmitHelpers(node: Node): EmitHelper[] | undefined { + return node.emitNode?.helpers; +} - /** - * Moves matching emit helpers from a source node to a target node. - */ - export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { - const sourceEmitNode = source.emitNode; - const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; - if (!some(sourceEmitHelpers)) return; - - const targetEmitNode = getOrCreateEmitNode(target); - let helpersRemoved = 0; - for (let i = 0; i < sourceEmitHelpers.length; i++) { - const helper = sourceEmitHelpers[i]; - if (predicate(helper)) { - helpersRemoved++; - targetEmitNode.helpers = appendIfUnique(targetEmitNode.helpers, helper); - } - else if (helpersRemoved > 0) { - sourceEmitHelpers[i - helpersRemoved] = helper; - } +/** + * Moves matching emit helpers from a source node to a target node. + */ +export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) { + const sourceEmitNode = source.emitNode; + const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers; + if (!some(sourceEmitHelpers)) return; + + const targetEmitNode = getOrCreateEmitNode(target); + let helpersRemoved = 0; + for (let i = 0; i < sourceEmitHelpers.length; i++) { + const helper = sourceEmitHelpers[i]; + if (predicate(helper)) { + helpersRemoved++; + targetEmitNode.helpers = appendIfUnique(targetEmitNode.helpers, helper); } - - if (helpersRemoved > 0) { - sourceEmitHelpers.length -= helpersRemoved; + else if (helpersRemoved > 0) { + sourceEmitHelpers[i - helpersRemoved] = helper; } } - /** - * Gets the SnippetElement of a node. - */ - /* @internal */ - export function getSnippetElement(node: Node): SnippetElement | undefined { - return node.emitNode?.snippetElement; + if (helpersRemoved > 0) { + sourceEmitHelpers.length -= helpersRemoved; } +} - /** - * Sets the SnippetElement of a node. - */ - /* @internal */ - export function setSnippetElement(node: T, snippet: SnippetElement): T { - const emitNode = getOrCreateEmitNode(node); - emitNode.snippetElement = snippet; - return node; - } +/** + * Gets the SnippetElement of a node. + * + * @internal + */ +export function getSnippetElement(node: Node): SnippetElement | undefined { + return node.emitNode?.snippetElement; +} - /* @internal */ - export function ignoreSourceNewlines(node: T): T { - getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines; - return node; - } +/** + * Sets the SnippetElement of a node. + * + * @internal + */ +export function setSnippetElement(node: T, snippet: SnippetElement): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.snippetElement = snippet; + return node; +} - /* @internal */ - export function setTypeNode(node: T, type: TypeNode): T { - const emitNode = getOrCreateEmitNode(node); - emitNode.typeNode = type; - return node; - } +/** @internal */ +export function ignoreSourceNewlines(node: T): T { + getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines; + return node; +} - /* @internal */ - export function getTypeNode(node: T): TypeNode | undefined { - return node.emitNode?.typeNode; - } +/** @internal */ +export function setTypeNode(node: T, type: TypeNode): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.typeNode = type; + return node; +} + +/** @internal */ +export function getTypeNode(node: T): TypeNode | undefined { + return node.emitNode?.typeNode; } diff --git a/src/compiler/factory/nodeConverters.ts b/src/compiler/factory/nodeConverters.ts index 08aff2a91cd87..74cddf31527b5 100644 --- a/src/compiler/factory/nodeConverters.ts +++ b/src/compiler/factory/nodeConverters.ts @@ -1,137 +1,145 @@ -/* @internal */ -namespace ts { - export function createNodeConverters(factory: NodeFactory): NodeConverters { - return { - convertToFunctionBlock, - convertToFunctionExpression, - convertToArrayAssignmentElement, - convertToObjectAssignmentElement, - convertToAssignmentPattern, - convertToObjectAssignmentPattern, - convertToArrayAssignmentPattern, - convertToAssignmentElementTarget, - }; +import { + ArrayBindingOrAssignmentElement, ArrayBindingOrAssignmentPattern, AssignmentPattern, + BindingOrAssignmentElementTarget, BindingOrAssignmentPattern, Block, cast, ConciseBody, Debug, Expression, + FunctionDeclaration, getStartsOnNewLine, isArrayBindingPattern, isArrayLiteralExpression, isBindingElement, + isBindingPattern, isBlock, isExpression, isIdentifier, isObjectBindingPattern, isObjectLiteralElementLike, + isObjectLiteralExpression, map, NodeConverters, NodeFactory, notImplemented, ObjectBindingOrAssignmentElement, + ObjectBindingOrAssignmentPattern, setOriginalNode, setStartsOnNewLine, setTextRange, SyntaxKind, +} from "../_namespaces/ts"; - function convertToFunctionBlock(node: ConciseBody, multiLine?: boolean): Block { - if (isBlock(node)) return node; - const returnStatement = factory.createReturnStatement(node); - setTextRange(returnStatement, node); - const body = factory.createBlock([returnStatement], multiLine); - setTextRange(body, node); - return body; - } +/** @internal */ +export function createNodeConverters(factory: NodeFactory): NodeConverters { + return { + convertToFunctionBlock, + convertToFunctionExpression, + convertToArrayAssignmentElement, + convertToObjectAssignmentElement, + convertToAssignmentPattern, + convertToObjectAssignmentPattern, + convertToArrayAssignmentPattern, + convertToAssignmentElementTarget, + }; - function convertToFunctionExpression(node: FunctionDeclaration) { - if (!node.body) return Debug.fail(`Cannot convert a FunctionDeclaration without a body`); - const updated = factory.createFunctionExpression( - node.modifiers, - node.asteriskToken, - node.name, - node.typeParameters, - node.parameters, - node.type, - node.body - ); - setOriginalNode(updated, node); - setTextRange(updated, node); - if (getStartsOnNewLine(node)) { - setStartsOnNewLine(updated, /*newLine*/ true); - } - return updated; + function convertToFunctionBlock(node: ConciseBody, multiLine?: boolean): Block { + if (isBlock(node)) return node; + const returnStatement = factory.createReturnStatement(node); + setTextRange(returnStatement, node); + const body = factory.createBlock([returnStatement], multiLine); + setTextRange(body, node); + return body; + } + + function convertToFunctionExpression(node: FunctionDeclaration) { + if (!node.body) return Debug.fail(`Cannot convert a FunctionDeclaration without a body`); + const updated = factory.createFunctionExpression( + node.modifiers, + node.asteriskToken, + node.name, + node.typeParameters, + node.parameters, + node.type, + node.body + ); + setOriginalNode(updated, node); + setTextRange(updated, node); + if (getStartsOnNewLine(node)) { + setStartsOnNewLine(updated, /*newLine*/ true); } + return updated; + } - function convertToArrayAssignmentElement(element: ArrayBindingOrAssignmentElement) { - if (isBindingElement(element)) { - if (element.dotDotDotToken) { - Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(setTextRange(factory.createSpreadElement(element.name), element), element); - } - const expression = convertToAssignmentElementTarget(element.name); - return element.initializer - ? setOriginalNode( - setTextRange( - factory.createAssignment(expression, element.initializer), - element - ), - element - ) - : expression; + function convertToArrayAssignmentElement(element: ArrayBindingOrAssignmentElement) { + if (isBindingElement(element)) { + if (element.dotDotDotToken) { + Debug.assertNode(element.name, isIdentifier); + return setOriginalNode(setTextRange(factory.createSpreadElement(element.name), element), element); } - return cast(element, isExpression); + const expression = convertToAssignmentElementTarget(element.name); + return element.initializer + ? setOriginalNode( + setTextRange( + factory.createAssignment(expression, element.initializer), + element + ), + element + ) + : expression; } + return cast(element, isExpression); + } - function convertToObjectAssignmentElement(element: ObjectBindingOrAssignmentElement) { - if (isBindingElement(element)) { - if (element.dotDotDotToken) { - Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(setTextRange(factory.createSpreadAssignment(element.name), element), element); - } - if (element.propertyName) { - const expression = convertToAssignmentElementTarget(element.name); - return setOriginalNode(setTextRange(factory.createPropertyAssignment(element.propertyName, element.initializer ? factory.createAssignment(expression, element.initializer) : expression), element), element); - } + function convertToObjectAssignmentElement(element: ObjectBindingOrAssignmentElement) { + if (isBindingElement(element)) { + if (element.dotDotDotToken) { Debug.assertNode(element.name, isIdentifier); - return setOriginalNode(setTextRange(factory.createShorthandPropertyAssignment(element.name, element.initializer), element), element); + return setOriginalNode(setTextRange(factory.createSpreadAssignment(element.name), element), element); } - - return cast(element, isObjectLiteralElementLike); + if (element.propertyName) { + const expression = convertToAssignmentElementTarget(element.name); + return setOriginalNode(setTextRange(factory.createPropertyAssignment(element.propertyName, element.initializer ? factory.createAssignment(expression, element.initializer) : expression), element), element); + } + Debug.assertNode(element.name, isIdentifier); + return setOriginalNode(setTextRange(factory.createShorthandPropertyAssignment(element.name, element.initializer), element), element); } - function convertToAssignmentPattern(node: BindingOrAssignmentPattern): AssignmentPattern { - switch (node.kind) { - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ArrayLiteralExpression: - return convertToArrayAssignmentPattern(node); + return cast(element, isObjectLiteralElementLike); + } - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ObjectLiteralExpression: - return convertToObjectAssignmentPattern(node); - } + function convertToAssignmentPattern(node: BindingOrAssignmentPattern): AssignmentPattern { + switch (node.kind) { + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + return convertToArrayAssignmentPattern(node); + + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ObjectLiteralExpression: + return convertToObjectAssignmentPattern(node); } + } - function convertToObjectAssignmentPattern(node: ObjectBindingOrAssignmentPattern) { - if (isObjectBindingPattern(node)) { - return setOriginalNode( - setTextRange( - factory.createObjectLiteralExpression(map(node.elements, convertToObjectAssignmentElement)), - node - ), + function convertToObjectAssignmentPattern(node: ObjectBindingOrAssignmentPattern) { + if (isObjectBindingPattern(node)) { + return setOriginalNode( + setTextRange( + factory.createObjectLiteralExpression(map(node.elements, convertToObjectAssignmentElement)), node - ); - } - return cast(node, isObjectLiteralExpression); + ), + node + ); } + return cast(node, isObjectLiteralExpression); + } - function convertToArrayAssignmentPattern(node: ArrayBindingOrAssignmentPattern) { - if (isArrayBindingPattern(node)) { - return setOriginalNode( - setTextRange( - factory.createArrayLiteralExpression(map(node.elements, convertToArrayAssignmentElement)), - node - ), + function convertToArrayAssignmentPattern(node: ArrayBindingOrAssignmentPattern) { + if (isArrayBindingPattern(node)) { + return setOriginalNode( + setTextRange( + factory.createArrayLiteralExpression(map(node.elements, convertToArrayAssignmentElement)), node - ); - } - return cast(node, isArrayLiteralExpression); + ), + node + ); } + return cast(node, isArrayLiteralExpression); + } - function convertToAssignmentElementTarget(node: BindingOrAssignmentElementTarget): Expression { - if (isBindingPattern(node)) { - return convertToAssignmentPattern(node); - } - - return cast(node, isExpression); + function convertToAssignmentElementTarget(node: BindingOrAssignmentElementTarget): Expression { + if (isBindingPattern(node)) { + return convertToAssignmentPattern(node); } + + return cast(node, isExpression); } +} - export const nullNodeConverters: NodeConverters = { - convertToFunctionBlock: notImplemented, - convertToFunctionExpression: notImplemented, - convertToArrayAssignmentElement: notImplemented, - convertToObjectAssignmentElement: notImplemented, - convertToAssignmentPattern: notImplemented, - convertToObjectAssignmentPattern: notImplemented, - convertToArrayAssignmentPattern: notImplemented, - convertToAssignmentElementTarget: notImplemented, - }; -} \ No newline at end of file +/** @internal */ +export const nullNodeConverters: NodeConverters = { + convertToFunctionBlock: notImplemented, + convertToFunctionExpression: notImplemented, + convertToArrayAssignmentElement: notImplemented, + convertToObjectAssignmentElement: notImplemented, + convertToAssignmentPattern: notImplemented, + convertToObjectAssignmentPattern: notImplemented, + convertToArrayAssignmentPattern: notImplemented, + convertToAssignmentElementTarget: notImplemented, +}; diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 26ad2d7a6512b..f2f111efb22c1 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -1,6930 +1,7021 @@ -namespace ts { - let nextAutoGenerateId = 0; - - /* @internal */ - export const enum NodeFactoryFlags { - None = 0, - // Disables the parenthesizer rules for the factory. - NoParenthesizerRules = 1 << 0, - // Disables the node converters for the factory. - NoNodeConverters = 1 << 1, - // Ensures new `PropertyAccessExpression` nodes are created with the `NoIndentation` emit flag set. - NoIndentationOnFreshPropertyAccess = 1 << 2, - // Do not set an `original` pointer when updating a node. - NoOriginalNode = 1 << 3, - } +import { + addRange, append, appendIfUnique, ArrayBindingElement, ArrayBindingPattern, ArrayLiteralExpression, ArrayTypeNode, + ArrowFunction, AsExpression, AssertClause, AssertEntry, AssertionKey, AssertsKeyword, AssignmentPattern, + AsteriskToken, AwaitExpression, AwaitKeyword, BaseNodeFactory, BigIntLiteral, BinaryExpression, BinaryOperator, + BinaryOperatorToken, BindingElement, BindingName, BindingPattern, Block, BooleanLiteral, BreakStatement, BuildInfo, + Bundle, BundleFileHasNoDefaultLib, BundleFileInfo, BundleFileReference, BundleFileSectionKind, CallBinding, + CallChain, CallExpression, CallSignatureDeclaration, canHaveModifiers, CaseBlock, CaseClause, CaseOrDefaultClause, + cast, CatchClause, ClassDeclaration, ClassElement, ClassExpression, ClassLikeDeclaration, + ClassStaticBlockDeclaration, ColonToken, CommaListExpression, CompilerHost, CompilerOptions, ComputedPropertyName, + ConciseBody, ConditionalExpression, ConditionalTypeNode, ConstructorDeclaration, ConstructorTypeNode, + ConstructSignatureDeclaration, ContinueStatement, createBaseNodeFactory, createNodeConverters, + createParenthesizerRules, createScanner, Debug, DebuggerStatement, Declaration, DeclarationName, Decorator, + DefaultClause, DeleteExpression, DoStatement, DotDotDotToken, ElementAccessChain, ElementAccessExpression, + EmitFlags, EmitNode, emptyArray, EmptyStatement, EndOfDeclarationMarker, EndOfFileToken, EntityName, + EnumDeclaration, EnumMember, EqualsGreaterThanToken, escapeLeadingUnderscores, every, ExclamationToken, + ExportAssignment, ExportDeclaration, ExportSpecifier, Expression, ExpressionStatement, ExpressionWithTypeArguments, + ExternalModuleReference, FalseLiteral, FileReference, findUseStrictPrologue, forEach, ForInitializer, + ForInStatement, formatGeneratedName, ForOfStatement, ForStatement, FunctionDeclaration, FunctionExpression, + FunctionLikeDeclaration, FunctionTypeNode, GeneratedIdentifier, GeneratedIdentifierFlags, GeneratedNamePart, + GetAccessorDeclaration, getAllUnscopedEmitHelpers, getBuildInfo, getCommentRange, + getElementsOfBindingOrAssignmentPattern, getEmitFlags, getJSDocTypeAliasName, getLineAndCharacterOfPosition, + getNameOfDeclaration, getNodeId, getSourceMapRange, getSyntheticLeadingComments, getSyntheticTrailingComments, + getTargetOfBindingOrAssignmentElement, getTextOfIdentifierOrLiteral, hasInvalidEscape, HasModifiers, hasProperty, + hasStaticModifier, hasSyntacticModifier, HeritageClause, Identifier, idText, IfStatement, ImportClause, + ImportDeclaration, ImportEqualsDeclaration, ImportSpecifier, ImportTypeAssertionContainer, ImportTypeNode, + IndexedAccessTypeNode, IndexSignatureDeclaration, InferTypeNode, InputFiles, InterfaceDeclaration, + IntersectionTypeNode, isArray, isArrayLiteralExpression, isArrowFunction, isAssignmentPattern, isBinaryExpression, + isCallChain, isClassDeclaration, isClassExpression, isCommaListExpression, isCommaToken, isComputedPropertyName, + isConstructorDeclaration, isConstructorTypeNode, isCustomPrologue, isElementAccessChain, isElementAccessExpression, + isEnumDeclaration, isExclamationToken, isExportAssignment, isExportDeclaration, isExternalModuleReference, + isFunctionDeclaration, isFunctionExpression, isGeneratedIdentifier, isGetAccessorDeclaration, isHoistedFunction, + isHoistedVariableStatement, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isImportKeyword, + isIndexSignatureDeclaration, isInterfaceDeclaration, isLabeledStatement, isLocalName, + isLogicalOrCoalescingAssignmentOperator, isMemberName, isMethodDeclaration, isMethodSignature, isModuleDeclaration, + isNamedDeclaration, isNodeArray, isNodeKind, isNonNullChain, isNotEmittedStatement, isObjectLiteralExpression, + isOmittedExpression, isOuterExpression, isParameter, isParenthesizedExpression, isParseTreeNode, + isPrivateIdentifier, isPrologueDirective, isPropertyAccessChain, isPropertyAccessExpression, isPropertyDeclaration, + isPropertyName, isPropertySignature, isQuestionToken, isSetAccessorDeclaration, isSourceFile, isStatement, + isStatementOrBlock, isString, isStringLiteral, isSuperKeyword, isSuperProperty, isThisIdentifier, + isTypeAliasDeclaration, isTypeParameterDeclaration, isVariableDeclaration, isVariableStatement, JSDoc, JSDocAllType, + JSDocAugmentsTag, JSDocAuthorTag, JSDocCallbackTag, JSDocClassTag, JSDocComment, JSDocDeprecatedTag, JSDocEnumTag, + JSDocFunctionType, JSDocImplementsTag, JSDocLink, JSDocLinkCode, JSDocLinkPlain, JSDocMemberName, JSDocNamepathType, + JSDocNameReference, JSDocNamespaceDeclaration, JSDocNonNullableType, JSDocNullableType, JSDocOptionalType, + JSDocOverrideTag, JSDocParameterTag, JSDocPrivateTag, JSDocPropertyLikeTag, JSDocPropertyTag, JSDocProtectedTag, + JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, JSDocSeeTag, JSDocSignature, JSDocTag, JSDocTemplateTag, + JSDocText, JSDocThisTag, JSDocType, JSDocTypedefTag, JSDocTypeExpression, JSDocTypeLiteral, JSDocTypeTag, + JSDocUnknownTag, JSDocUnknownType, JSDocVariadicType, JsxAttribute, JsxAttributeLike, JsxAttributes, + JsxAttributeValue, JsxChild, JsxClosingElement, JsxClosingFragment, JsxElement, JsxExpression, JsxFragment, + JsxOpeningElement, JsxOpeningFragment, JsxSelfClosingElement, JsxSpreadAttribute, JsxTagNameExpression, JsxText, + KeywordSyntaxKind, KeywordToken, KeywordTypeNode, KeywordTypeSyntaxKind, LabeledStatement, LanguageVariant, + lastOrUndefined, LeftHandSideExpression, LiteralToken, LiteralTypeNode, map, Map, MappedTypeNode, + memoize, memoizeOne, MergeDeclarationMarker, MetaProperty, MethodDeclaration, MethodSignature, MinusToken, + MissingDeclaration, Modifier, ModifierFlags, ModifierLike, modifiersToFlags, ModifierSyntaxKind, ModifierToken, + ModuleBlock, ModuleBody, ModuleDeclaration, ModuleKind, ModuleName, ModuleReference, Mutable, MutableNodeArray, + NamedDeclaration, NamedExportBindings, NamedExports, NamedImportBindings, NamedImports, NamedTupleMember, + NamespaceExport, NamespaceExportDeclaration, NamespaceImport, NewExpression, Node, NodeArray, NodeFactory, + NodeFlags, nodeIsSynthesized, NonNullChain, NonNullExpression, NoSubstitutionTemplateLiteral, NotEmittedStatement, + NullLiteral, nullNodeConverters, nullParenthesizerRules, NumericLiteral, objectAllocator, ObjectBindingPattern, + ObjectLiteralElementLike, ObjectLiteralExpression, OmittedExpression, OptionalTypeNode, OuterExpression, + OuterExpressionKinds, ParameterDeclaration, ParenthesizedExpression, ParenthesizedTypeNode, parseNodeFactory, + PartiallyEmittedExpression, PlusToken, PostfixUnaryExpression, PostfixUnaryOperator, PrefixUnaryExpression, + PrefixUnaryOperator, PrimaryExpression, PrivateIdentifier, PrologueDirective, PropertyAccessChain, + PropertyAccessExpression, PropertyAssignment, PropertyDeclaration, PropertyDescriptorAttributes, PropertyName, + PropertyNameLiteral, PropertySignature, PseudoBigInt, pseudoBigIntToString, PunctuationSyntaxKind, PunctuationToken, + Push, QualifiedName, QuestionDotToken, QuestionToken, ReadonlyKeyword, reduceLeft, RegularExpressionLiteral, + RestTypeNode, ReturnStatement, returnTrue, sameFlatMap, SatisfiesExpression, Scanner, ScriptTarget, + SemicolonClassElement, SetAccessorDeclaration, setEachParent, setEmitFlags, setParent, setTextRange, + setTextRangePosEnd, setTextRangePosWidth, ShorthandPropertyAssignment, SignatureDeclarationBase, singleOrUndefined, + skipOuterExpressions, skipParentheses, some, SourceFile, SourceMapSource, SpreadAssignment, SpreadElement, + startOnNewLine, startsWith, Statement, StringLiteral, StringLiteralLike, stringToToken, SuperExpression, + SwitchStatement, SyntaxKind, SyntaxList, SyntheticExpression, SyntheticReferenceExpression, + TaggedTemplateExpression, TemplateExpression, TemplateHead, TemplateLiteral, TemplateLiteralLikeNode, + TemplateLiteralToken, TemplateLiteralTypeNode, TemplateLiteralTypeSpan, TemplateMiddle, TemplateSpan, TemplateTail, + TextRange, ThisExpression, ThisTypeNode, ThrowStatement, Token, TokenFlags, TransformFlags, TrueLiteral, + TryStatement, TupleTypeNode, Type, TypeAliasDeclaration, TypeAssertion, TypeElement, TypeLiteralNode, TypeNode, + TypeOfExpression, TypeOfTag, TypeOperatorNode, TypeParameterDeclaration, TypePredicateNode, TypeQueryNode, + TypeReferenceNode, UnionOrIntersectionTypeNode, UnionTypeNode, UnparsedNode, UnparsedPrepend, UnparsedPrologue, + UnparsedSource, UnparsedSourceText, UnparsedSyntheticReference, UnparsedTextLike, UnscopedEmitHelper, + VariableDeclaration, VariableDeclarationList, VariableStatement, visitNode, VisitResult, VoidExpression, + WhileStatement, WithStatement, YieldExpression, +} from "../_namespaces/ts"; + +let nextAutoGenerateId = 0; + +/** @internal */ +export const enum NodeFactoryFlags { + None = 0, + // Disables the parenthesizer rules for the factory. + NoParenthesizerRules = 1 << 0, + // Disables the node converters for the factory. + NoNodeConverters = 1 << 1, + // Ensures new `PropertyAccessExpression` nodes are created with the `NoIndentation` emit flag set. + NoIndentationOnFreshPropertyAccess = 1 << 2, + // Do not set an `original` pointer when updating a node. + NoOriginalNode = 1 << 3, +} - /** - * Creates a `NodeFactory` that can be used to create and update a syntax tree. - * @param flags Flags that control factory behavior. - * @param baseFactory A `BaseNodeFactory` used to create the base `Node` objects. - */ - /* @internal */ - export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNodeFactory): NodeFactory { - const update = flags & NodeFactoryFlags.NoOriginalNode ? updateWithoutOriginal : updateWithOriginal; - - // Lazily load the parenthesizer, node converters, and some factory methods until they are used. - const parenthesizerRules = memoize(() => flags & NodeFactoryFlags.NoParenthesizerRules ? nullParenthesizerRules : createParenthesizerRules(factory)); - const converters = memoize(() => flags & NodeFactoryFlags.NoNodeConverters ? nullNodeConverters : createNodeConverters(factory)); - - // lazy initializaton of common operator factories - const getBinaryCreateFunction = memoizeOne((operator: BinaryOperator) => (left: Expression, right: Expression) => createBinaryExpression(left, operator, right)); - const getPrefixUnaryCreateFunction = memoizeOne((operator: PrefixUnaryOperator) => (operand: Expression) => createPrefixUnaryExpression(operator, operand)); - const getPostfixUnaryCreateFunction = memoizeOne((operator: PostfixUnaryOperator) => (operand: Expression) => createPostfixUnaryExpression(operand, operator)); - const getJSDocPrimaryTypeCreateFunction = memoizeOne((kind: T["kind"]) => () => createJSDocPrimaryTypeWorker(kind)); - const getJSDocUnaryTypeCreateFunction = memoizeOne((kind: T["kind"]) => (type: T["type"]) => createJSDocUnaryTypeWorker(kind, type)); - const getJSDocUnaryTypeUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, type: T["type"]) => updateJSDocUnaryTypeWorker(kind, node, type)); - const getJSDocPrePostfixUnaryTypeCreateFunction = memoizeOne((kind: T["kind"]) => (type: T["type"], postfix?: boolean) => createJSDocPrePostfixUnaryTypeWorker(kind, type, postfix)); - const getJSDocPrePostfixUnaryTypeUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, type: T["type"]) => updateJSDocPrePostfixUnaryTypeWorker(kind, node, type)); - const getJSDocSimpleTagCreateFunction = memoizeOne((kind: T["kind"]) => (tagName: Identifier | undefined, comment?: NodeArray) => createJSDocSimpleTagWorker(kind, tagName, comment)); - const getJSDocSimpleTagUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, tagName: Identifier | undefined, comment?: NodeArray) => updateJSDocSimpleTagWorker(kind, node, tagName, comment)); - const getJSDocTypeLikeTagCreateFunction = memoizeOne((kind: T["kind"]) => (tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: NodeArray) => createJSDocTypeLikeTagWorker(kind, tagName, typeExpression, comment)); - const getJSDocTypeLikeTagUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: NodeArray) => updateJSDocTypeLikeTagWorker(kind, node, tagName, typeExpression, comment)); - - const factory: NodeFactory = { - get parenthesizer() { return parenthesizerRules(); }, - get converters() { return converters(); }, - baseFactory, - flags, - createNodeArray, - createNumericLiteral, - createBigIntLiteral, - createStringLiteral, - createStringLiteralFromNode, - createRegularExpressionLiteral, - createLiteralLikeNode, - createIdentifier, - updateIdentifier, - createTempVariable, - createLoopVariable, - createUniqueName, - getGeneratedNameForNode, - createPrivateIdentifier, - createUniquePrivateName, - getGeneratedPrivateNameForNode, - createToken, - createSuper, - createThis, - createNull, - createTrue, - createFalse, - createModifier, - createModifiersFromModifierFlags, - createQualifiedName, - updateQualifiedName, - createComputedPropertyName, - updateComputedPropertyName, - createTypeParameterDeclaration, - updateTypeParameterDeclaration, - createParameterDeclaration, - updateParameterDeclaration, - createDecorator, - updateDecorator, - createPropertySignature, - updatePropertySignature, - createPropertyDeclaration, - updatePropertyDeclaration, - createMethodSignature, - updateMethodSignature, - createMethodDeclaration, - updateMethodDeclaration, - createConstructorDeclaration, - updateConstructorDeclaration, - createGetAccessorDeclaration, - updateGetAccessorDeclaration, - createSetAccessorDeclaration, - updateSetAccessorDeclaration, - createCallSignature, - updateCallSignature, - createConstructSignature, - updateConstructSignature, - createIndexSignature, - updateIndexSignature, - createClassStaticBlockDeclaration, - updateClassStaticBlockDeclaration, - createTemplateLiteralTypeSpan, - updateTemplateLiteralTypeSpan, - createKeywordTypeNode, - createTypePredicateNode, - updateTypePredicateNode, - createTypeReferenceNode, - updateTypeReferenceNode, - createFunctionTypeNode, - updateFunctionTypeNode, - createConstructorTypeNode, - updateConstructorTypeNode, - createTypeQueryNode, - updateTypeQueryNode, - createTypeLiteralNode, - updateTypeLiteralNode, - createArrayTypeNode, - updateArrayTypeNode, - createTupleTypeNode, - updateTupleTypeNode, - createNamedTupleMember, - updateNamedTupleMember, - createOptionalTypeNode, - updateOptionalTypeNode, - createRestTypeNode, - updateRestTypeNode, - createUnionTypeNode, - updateUnionTypeNode, - createIntersectionTypeNode, - updateIntersectionTypeNode, - createConditionalTypeNode, - updateConditionalTypeNode, - createInferTypeNode, - updateInferTypeNode, - createImportTypeNode, - updateImportTypeNode, - createParenthesizedType, - updateParenthesizedType, - createThisTypeNode, - createTypeOperatorNode, - updateTypeOperatorNode, - createIndexedAccessTypeNode, - updateIndexedAccessTypeNode, - createMappedTypeNode, - updateMappedTypeNode, - createLiteralTypeNode, - updateLiteralTypeNode, - createTemplateLiteralType, - updateTemplateLiteralType, - createObjectBindingPattern, - updateObjectBindingPattern, - createArrayBindingPattern, - updateArrayBindingPattern, - createBindingElement, - updateBindingElement, - createArrayLiteralExpression, - updateArrayLiteralExpression, - createObjectLiteralExpression, - updateObjectLiteralExpression, - createPropertyAccessExpression: flags & NodeFactoryFlags.NoIndentationOnFreshPropertyAccess ? - (expression, name) => setEmitFlags(createPropertyAccessExpression(expression, name), EmitFlags.NoIndentation) : - createPropertyAccessExpression, - updatePropertyAccessExpression, - createPropertyAccessChain: flags & NodeFactoryFlags.NoIndentationOnFreshPropertyAccess ? - (expression, questionDotToken, name) => setEmitFlags(createPropertyAccessChain(expression, questionDotToken, name), EmitFlags.NoIndentation) : - createPropertyAccessChain, - updatePropertyAccessChain, - createElementAccessExpression, - updateElementAccessExpression, - createElementAccessChain, - updateElementAccessChain, - createCallExpression, - updateCallExpression, - createCallChain, - updateCallChain, - createNewExpression, - updateNewExpression, - createTaggedTemplateExpression, - updateTaggedTemplateExpression, - createTypeAssertion, - updateTypeAssertion, - createParenthesizedExpression, - updateParenthesizedExpression, - createFunctionExpression, - updateFunctionExpression, - createArrowFunction, - updateArrowFunction, - createDeleteExpression, - updateDeleteExpression, - createTypeOfExpression, - updateTypeOfExpression, - createVoidExpression, - updateVoidExpression, - createAwaitExpression, - updateAwaitExpression, - createPrefixUnaryExpression, - updatePrefixUnaryExpression, - createPostfixUnaryExpression, - updatePostfixUnaryExpression, - createBinaryExpression, - updateBinaryExpression, - createConditionalExpression, - updateConditionalExpression, - createTemplateExpression, - updateTemplateExpression, - createTemplateHead, - createTemplateMiddle, - createTemplateTail, - createNoSubstitutionTemplateLiteral, - createTemplateLiteralLikeNode, - createYieldExpression, - updateYieldExpression, - createSpreadElement, - updateSpreadElement, - createClassExpression, - updateClassExpression, - createOmittedExpression, - createExpressionWithTypeArguments, - updateExpressionWithTypeArguments, - createAsExpression, - updateAsExpression, - createNonNullExpression, - updateNonNullExpression, - createSatisfiesExpression, - updateSatisfiesExpression, - createNonNullChain, - updateNonNullChain, - createMetaProperty, - updateMetaProperty, - createTemplateSpan, - updateTemplateSpan, - createSemicolonClassElement, - createBlock, - updateBlock, - createVariableStatement, - updateVariableStatement, - createEmptyStatement, - createExpressionStatement, - updateExpressionStatement, - createIfStatement, - updateIfStatement, - createDoStatement, - updateDoStatement, - createWhileStatement, - updateWhileStatement, - createForStatement, - updateForStatement, - createForInStatement, - updateForInStatement, - createForOfStatement, - updateForOfStatement, - createContinueStatement, - updateContinueStatement, - createBreakStatement, - updateBreakStatement, - createReturnStatement, - updateReturnStatement, - createWithStatement, - updateWithStatement, - createSwitchStatement, - updateSwitchStatement, - createLabeledStatement, - updateLabeledStatement, - createThrowStatement, - updateThrowStatement, - createTryStatement, - updateTryStatement, - createDebuggerStatement, - createVariableDeclaration, - updateVariableDeclaration, - createVariableDeclarationList, - updateVariableDeclarationList, - createFunctionDeclaration, - updateFunctionDeclaration, - createClassDeclaration, - updateClassDeclaration, - createInterfaceDeclaration, - updateInterfaceDeclaration, - createTypeAliasDeclaration, - updateTypeAliasDeclaration, - createEnumDeclaration, - updateEnumDeclaration, - createModuleDeclaration, - updateModuleDeclaration, - createModuleBlock, - updateModuleBlock, - createCaseBlock, - updateCaseBlock, - createNamespaceExportDeclaration, - updateNamespaceExportDeclaration, - createImportEqualsDeclaration, - updateImportEqualsDeclaration, - createImportDeclaration, - updateImportDeclaration, - createImportClause, - updateImportClause, - createAssertClause, - updateAssertClause, - createAssertEntry, - updateAssertEntry, - createImportTypeAssertionContainer, - updateImportTypeAssertionContainer, - createNamespaceImport, - updateNamespaceImport, - createNamespaceExport, - updateNamespaceExport, - createNamedImports, - updateNamedImports, - createImportSpecifier, - updateImportSpecifier, - createExportAssignment, - updateExportAssignment, - createExportDeclaration, - updateExportDeclaration, - createNamedExports, - updateNamedExports, - createExportSpecifier, - updateExportSpecifier, - createMissingDeclaration, - createExternalModuleReference, - updateExternalModuleReference, - // lazily load factory members for JSDoc types with similar structure - get createJSDocAllType() { return getJSDocPrimaryTypeCreateFunction(SyntaxKind.JSDocAllType); }, - get createJSDocUnknownType() { return getJSDocPrimaryTypeCreateFunction(SyntaxKind.JSDocUnknownType); }, - get createJSDocNonNullableType() { return getJSDocPrePostfixUnaryTypeCreateFunction(SyntaxKind.JSDocNonNullableType); }, - get updateJSDocNonNullableType() { return getJSDocPrePostfixUnaryTypeUpdateFunction(SyntaxKind.JSDocNonNullableType); }, - get createJSDocNullableType() { return getJSDocPrePostfixUnaryTypeCreateFunction(SyntaxKind.JSDocNullableType); }, - get updateJSDocNullableType() { return getJSDocPrePostfixUnaryTypeUpdateFunction(SyntaxKind.JSDocNullableType); }, - get createJSDocOptionalType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocOptionalType); }, - get updateJSDocOptionalType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocOptionalType); }, - get createJSDocVariadicType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocVariadicType); }, - get updateJSDocVariadicType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocVariadicType); }, - get createJSDocNamepathType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocNamepathType); }, - get updateJSDocNamepathType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocNamepathType); }, - createJSDocFunctionType, - updateJSDocFunctionType, - createJSDocTypeLiteral, - updateJSDocTypeLiteral, - createJSDocTypeExpression, - updateJSDocTypeExpression, - createJSDocSignature, - updateJSDocSignature, - createJSDocTemplateTag, - updateJSDocTemplateTag, - createJSDocTypedefTag, - updateJSDocTypedefTag, - createJSDocParameterTag, - updateJSDocParameterTag, - createJSDocPropertyTag, - updateJSDocPropertyTag, - createJSDocCallbackTag, - updateJSDocCallbackTag, - createJSDocAugmentsTag, - updateJSDocAugmentsTag, - createJSDocImplementsTag, - updateJSDocImplementsTag, - createJSDocSeeTag, - updateJSDocSeeTag, - createJSDocNameReference, - updateJSDocNameReference, - createJSDocMemberName, - updateJSDocMemberName, - createJSDocLink, - updateJSDocLink, - createJSDocLinkCode, - updateJSDocLinkCode, - createJSDocLinkPlain, - updateJSDocLinkPlain, - // lazily load factory members for JSDoc tags with similar structure - get createJSDocTypeTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocTypeTag); }, - get updateJSDocTypeTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocTypeTag); }, - get createJSDocReturnTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocReturnTag); }, - get updateJSDocReturnTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocReturnTag); }, - get createJSDocThisTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocThisTag); }, - get updateJSDocThisTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocThisTag); }, - get createJSDocEnumTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocEnumTag); }, - get updateJSDocEnumTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocEnumTag); }, - get createJSDocAuthorTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocAuthorTag); }, - get updateJSDocAuthorTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocAuthorTag); }, - get createJSDocClassTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocClassTag); }, - get updateJSDocClassTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocClassTag); }, - get createJSDocPublicTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocPublicTag); }, - get updateJSDocPublicTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocPublicTag); }, - get createJSDocPrivateTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocPrivateTag); }, - get updateJSDocPrivateTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocPrivateTag); }, - get createJSDocProtectedTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocProtectedTag); }, - get updateJSDocProtectedTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocProtectedTag); }, - get createJSDocReadonlyTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocReadonlyTag); }, - get updateJSDocReadonlyTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocReadonlyTag); }, - get createJSDocOverrideTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocOverrideTag); }, - get updateJSDocOverrideTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocOverrideTag); }, - get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocDeprecatedTag); }, - get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocDeprecatedTag); }, - createJSDocUnknownTag, - updateJSDocUnknownTag, - createJSDocText, - updateJSDocText, - createJSDocComment, - updateJSDocComment, - createJsxElement, - updateJsxElement, - createJsxSelfClosingElement, - updateJsxSelfClosingElement, - createJsxOpeningElement, - updateJsxOpeningElement, - createJsxClosingElement, - updateJsxClosingElement, - createJsxFragment, - createJsxText, - updateJsxText, - createJsxOpeningFragment, - createJsxJsxClosingFragment, - updateJsxFragment, - createJsxAttribute, - updateJsxAttribute, - createJsxAttributes, - updateJsxAttributes, - createJsxSpreadAttribute, - updateJsxSpreadAttribute, - createJsxExpression, - updateJsxExpression, - createCaseClause, - updateCaseClause, - createDefaultClause, - updateDefaultClause, - createHeritageClause, - updateHeritageClause, - createCatchClause, - updateCatchClause, - createPropertyAssignment, - updatePropertyAssignment, - createShorthandPropertyAssignment, - updateShorthandPropertyAssignment, - createSpreadAssignment, - updateSpreadAssignment, - createEnumMember, - updateEnumMember, - createSourceFile, - updateSourceFile, - createBundle, - updateBundle, - createUnparsedSource, - createUnparsedPrologue, - createUnparsedPrepend, - createUnparsedTextLike, - createUnparsedSyntheticReference, - createInputFiles, - createSyntheticExpression, - createSyntaxList, - createNotEmittedStatement, - createPartiallyEmittedExpression, - updatePartiallyEmittedExpression, - createCommaListExpression, - updateCommaListExpression, - createEndOfDeclarationMarker, - createMergeDeclarationMarker, - createSyntheticReferenceExpression, - updateSyntheticReferenceExpression, - cloneNode, - - // Lazily load factory methods for common operator factories and utilities - get createComma() { return getBinaryCreateFunction(SyntaxKind.CommaToken); }, - get createAssignment() { return getBinaryCreateFunction(SyntaxKind.EqualsToken) as NodeFactory["createAssignment"]; }, - get createLogicalOr() { return getBinaryCreateFunction(SyntaxKind.BarBarToken); }, - get createLogicalAnd() { return getBinaryCreateFunction(SyntaxKind.AmpersandAmpersandToken); }, - get createBitwiseOr() { return getBinaryCreateFunction(SyntaxKind.BarToken); }, - get createBitwiseXor() { return getBinaryCreateFunction(SyntaxKind.CaretToken); }, - get createBitwiseAnd() { return getBinaryCreateFunction(SyntaxKind.AmpersandToken); }, - get createStrictEquality() { return getBinaryCreateFunction(SyntaxKind.EqualsEqualsEqualsToken); }, - get createStrictInequality() { return getBinaryCreateFunction(SyntaxKind.ExclamationEqualsEqualsToken); }, - get createEquality() { return getBinaryCreateFunction(SyntaxKind.EqualsEqualsToken); }, - get createInequality() { return getBinaryCreateFunction(SyntaxKind.ExclamationEqualsToken); }, - get createLessThan() { return getBinaryCreateFunction(SyntaxKind.LessThanToken); }, - get createLessThanEquals() { return getBinaryCreateFunction(SyntaxKind.LessThanEqualsToken); }, - get createGreaterThan() { return getBinaryCreateFunction(SyntaxKind.GreaterThanToken); }, - get createGreaterThanEquals() { return getBinaryCreateFunction(SyntaxKind.GreaterThanEqualsToken); }, - get createLeftShift() { return getBinaryCreateFunction(SyntaxKind.LessThanLessThanToken); }, - get createRightShift() { return getBinaryCreateFunction(SyntaxKind.GreaterThanGreaterThanToken); }, - get createUnsignedRightShift() { return getBinaryCreateFunction(SyntaxKind.GreaterThanGreaterThanGreaterThanToken); }, - get createAdd() { return getBinaryCreateFunction(SyntaxKind.PlusToken); }, - get createSubtract() { return getBinaryCreateFunction(SyntaxKind.MinusToken); }, - get createMultiply() { return getBinaryCreateFunction(SyntaxKind.AsteriskToken); }, - get createDivide() { return getBinaryCreateFunction(SyntaxKind.SlashToken); }, - get createModulo() { return getBinaryCreateFunction(SyntaxKind.PercentToken); }, - get createExponent() { return getBinaryCreateFunction(SyntaxKind.AsteriskAsteriskToken); }, - get createPrefixPlus() { return getPrefixUnaryCreateFunction(SyntaxKind.PlusToken); }, - get createPrefixMinus() { return getPrefixUnaryCreateFunction(SyntaxKind.MinusToken); }, - get createPrefixIncrement() { return getPrefixUnaryCreateFunction(SyntaxKind.PlusPlusToken); }, - get createPrefixDecrement() { return getPrefixUnaryCreateFunction(SyntaxKind.MinusMinusToken); }, - get createBitwiseNot() { return getPrefixUnaryCreateFunction(SyntaxKind.TildeToken); }, - get createLogicalNot() { return getPrefixUnaryCreateFunction(SyntaxKind.ExclamationToken); }, - get createPostfixIncrement() { return getPostfixUnaryCreateFunction(SyntaxKind.PlusPlusToken); }, - get createPostfixDecrement() { return getPostfixUnaryCreateFunction(SyntaxKind.MinusMinusToken); }, - - // Compound nodes - createImmediatelyInvokedFunctionExpression, - createImmediatelyInvokedArrowFunction, - createVoidZero, - createExportDefault, - createExternalModuleExport, - createTypeCheck, - createMethodCall, - createGlobalMethodCall, - createFunctionBindCall, - createFunctionCallCall, - createFunctionApplyCall, - createArraySliceCall, - createArrayConcatCall, - createObjectDefinePropertyCall, - createReflectGetCall, - createReflectSetCall, - createPropertyDescriptor, - createCallBinding, - createAssignmentTargetWrapper, - - // Utilities - inlineExpressions, - getInternalName, - getLocalName, - getExportName, - getDeclarationName, - getNamespaceMemberName, - getExternalModuleOrNamespaceExportName, - restoreOuterExpressions, - restoreEnclosingLabel, - createUseStrictPrologue, - copyPrologue, - copyStandardPrologue, - copyCustomPrologue, - ensureUseStrict, - liftToBlock, - mergeLexicalEnvironment, - updateModifiers, - }; - - return factory; - - // @api - function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray { - if (elements === undefined || elements === emptyArray) { - elements = []; - } - else if (isNodeArray(elements)) { - if (hasTrailingComma === undefined || elements.hasTrailingComma === hasTrailingComma) { - // Ensure the transform flags have been aggregated for this NodeArray - if (elements.transformFlags === undefined) { - aggregateChildrenFlags(elements as MutableNodeArray); - } - Debug.attachNodeArrayDebugInfo(elements); - return elements; - } +const nodeFactoryPatchers: ((factory: NodeFactory) => void)[] = []; + +/** @internal */ +export function addNodeFactoryPatcher(fn: (factory: NodeFactory) => void) { + nodeFactoryPatchers.push(fn); +} + +/** + * Creates a `NodeFactory` that can be used to create and update a syntax tree. + * @param flags Flags that control factory behavior. + * @param baseFactory A `BaseNodeFactory` used to create the base `Node` objects. + * + * @internal + */ +export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNodeFactory): NodeFactory { + const update = flags & NodeFactoryFlags.NoOriginalNode ? updateWithoutOriginal : updateWithOriginal; + + // Lazily load the parenthesizer, node converters, and some factory methods until they are used. + const parenthesizerRules = memoize(() => flags & NodeFactoryFlags.NoParenthesizerRules ? nullParenthesizerRules : createParenthesizerRules(factory)); + const converters = memoize(() => flags & NodeFactoryFlags.NoNodeConverters ? nullNodeConverters : createNodeConverters(factory)); + + // lazy initializaton of common operator factories + const getBinaryCreateFunction = memoizeOne((operator: BinaryOperator) => (left: Expression, right: Expression) => createBinaryExpression(left, operator, right)); + const getPrefixUnaryCreateFunction = memoizeOne((operator: PrefixUnaryOperator) => (operand: Expression) => createPrefixUnaryExpression(operator, operand)); + const getPostfixUnaryCreateFunction = memoizeOne((operator: PostfixUnaryOperator) => (operand: Expression) => createPostfixUnaryExpression(operand, operator)); + const getJSDocPrimaryTypeCreateFunction = memoizeOne((kind: T["kind"]) => () => createJSDocPrimaryTypeWorker(kind)); + const getJSDocUnaryTypeCreateFunction = memoizeOne((kind: T["kind"]) => (type: T["type"]) => createJSDocUnaryTypeWorker(kind, type)); + const getJSDocUnaryTypeUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, type: T["type"]) => updateJSDocUnaryTypeWorker(kind, node, type)); + const getJSDocPrePostfixUnaryTypeCreateFunction = memoizeOne((kind: T["kind"]) => (type: T["type"], postfix?: boolean) => createJSDocPrePostfixUnaryTypeWorker(kind, type, postfix)); + const getJSDocPrePostfixUnaryTypeUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, type: T["type"]) => updateJSDocPrePostfixUnaryTypeWorker(kind, node, type)); + const getJSDocSimpleTagCreateFunction = memoizeOne((kind: T["kind"]) => (tagName: Identifier | undefined, comment?: NodeArray) => createJSDocSimpleTagWorker(kind, tagName, comment)); + const getJSDocSimpleTagUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, tagName: Identifier | undefined, comment?: NodeArray) => updateJSDocSimpleTagWorker(kind, node, tagName, comment)); + const getJSDocTypeLikeTagCreateFunction = memoizeOne((kind: T["kind"]) => (tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: NodeArray) => createJSDocTypeLikeTagWorker(kind, tagName, typeExpression, comment)); + const getJSDocTypeLikeTagUpdateFunction = memoizeOne((kind: T["kind"]) => (node: T, tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: NodeArray) => updateJSDocTypeLikeTagWorker(kind, node, tagName, typeExpression, comment)); + + const factory: NodeFactory = { + get parenthesizer() { return parenthesizerRules(); }, + get converters() { return converters(); }, + baseFactory, + flags, + createNodeArray, + createNumericLiteral, + createBigIntLiteral, + createStringLiteral, + createStringLiteralFromNode, + createRegularExpressionLiteral, + createLiteralLikeNode, + createIdentifier, + updateIdentifier, + createTempVariable, + createLoopVariable, + createUniqueName, + getGeneratedNameForNode, + createPrivateIdentifier, + createUniquePrivateName, + getGeneratedPrivateNameForNode, + createToken, + createSuper, + createThis, + createNull, + createTrue, + createFalse, + createModifier, + createModifiersFromModifierFlags, + createQualifiedName, + updateQualifiedName, + createComputedPropertyName, + updateComputedPropertyName, + createTypeParameterDeclaration, + updateTypeParameterDeclaration, + createParameterDeclaration, + updateParameterDeclaration, + createDecorator, + updateDecorator, + createPropertySignature, + updatePropertySignature, + createPropertyDeclaration, + updatePropertyDeclaration, + createMethodSignature, + updateMethodSignature, + createMethodDeclaration, + updateMethodDeclaration, + createConstructorDeclaration, + updateConstructorDeclaration, + createGetAccessorDeclaration, + updateGetAccessorDeclaration, + createSetAccessorDeclaration, + updateSetAccessorDeclaration, + createCallSignature, + updateCallSignature, + createConstructSignature, + updateConstructSignature, + createIndexSignature, + updateIndexSignature, + createClassStaticBlockDeclaration, + updateClassStaticBlockDeclaration, + createTemplateLiteralTypeSpan, + updateTemplateLiteralTypeSpan, + createKeywordTypeNode, + createTypePredicateNode, + updateTypePredicateNode, + createTypeReferenceNode, + updateTypeReferenceNode, + createFunctionTypeNode, + updateFunctionTypeNode, + createConstructorTypeNode, + updateConstructorTypeNode, + createTypeQueryNode, + updateTypeQueryNode, + createTypeLiteralNode, + updateTypeLiteralNode, + createArrayTypeNode, + updateArrayTypeNode, + createTupleTypeNode, + updateTupleTypeNode, + createNamedTupleMember, + updateNamedTupleMember, + createOptionalTypeNode, + updateOptionalTypeNode, + createRestTypeNode, + updateRestTypeNode, + createUnionTypeNode, + updateUnionTypeNode, + createIntersectionTypeNode, + updateIntersectionTypeNode, + createConditionalTypeNode, + updateConditionalTypeNode, + createInferTypeNode, + updateInferTypeNode, + createImportTypeNode, + updateImportTypeNode, + createParenthesizedType, + updateParenthesizedType, + createThisTypeNode, + createTypeOperatorNode, + updateTypeOperatorNode, + createIndexedAccessTypeNode, + updateIndexedAccessTypeNode, + createMappedTypeNode, + updateMappedTypeNode, + createLiteralTypeNode, + updateLiteralTypeNode, + createTemplateLiteralType, + updateTemplateLiteralType, + createObjectBindingPattern, + updateObjectBindingPattern, + createArrayBindingPattern, + updateArrayBindingPattern, + createBindingElement, + updateBindingElement, + createArrayLiteralExpression, + updateArrayLiteralExpression, + createObjectLiteralExpression, + updateObjectLiteralExpression, + createPropertyAccessExpression: flags & NodeFactoryFlags.NoIndentationOnFreshPropertyAccess ? + (expression, name) => setEmitFlags(createPropertyAccessExpression(expression, name), EmitFlags.NoIndentation) : + createPropertyAccessExpression, + updatePropertyAccessExpression, + createPropertyAccessChain: flags & NodeFactoryFlags.NoIndentationOnFreshPropertyAccess ? + (expression, questionDotToken, name: string) => setEmitFlags(createPropertyAccessChain(expression, questionDotToken, name), EmitFlags.NoIndentation) : + createPropertyAccessChain, + updatePropertyAccessChain, + createElementAccessExpression, + updateElementAccessExpression, + createElementAccessChain, + updateElementAccessChain, + createCallExpression, + updateCallExpression, + createCallChain, + updateCallChain, + createNewExpression, + updateNewExpression, + createTaggedTemplateExpression, + updateTaggedTemplateExpression, + createTypeAssertion, + updateTypeAssertion, + createParenthesizedExpression, + updateParenthesizedExpression, + createFunctionExpression, + updateFunctionExpression, + createArrowFunction, + updateArrowFunction, + createDeleteExpression, + updateDeleteExpression, + createTypeOfExpression, + updateTypeOfExpression, + createVoidExpression, + updateVoidExpression, + createAwaitExpression, + updateAwaitExpression, + createPrefixUnaryExpression, + updatePrefixUnaryExpression, + createPostfixUnaryExpression, + updatePostfixUnaryExpression, + createBinaryExpression, + updateBinaryExpression, + createConditionalExpression, + updateConditionalExpression, + createTemplateExpression, + updateTemplateExpression, + createTemplateHead, + createTemplateMiddle, + createTemplateTail, + createNoSubstitutionTemplateLiteral, + createTemplateLiteralLikeNode, + createYieldExpression, + updateYieldExpression, + createSpreadElement, + updateSpreadElement, + createClassExpression, + updateClassExpression, + createOmittedExpression, + createExpressionWithTypeArguments, + updateExpressionWithTypeArguments, + createAsExpression, + updateAsExpression, + createNonNullExpression, + updateNonNullExpression, + createSatisfiesExpression, + updateSatisfiesExpression, + createNonNullChain, + updateNonNullChain, + createMetaProperty, + updateMetaProperty, + createTemplateSpan, + updateTemplateSpan, + createSemicolonClassElement, + createBlock, + updateBlock, + createVariableStatement, + updateVariableStatement, + createEmptyStatement, + createExpressionStatement, + updateExpressionStatement, + createIfStatement, + updateIfStatement, + createDoStatement, + updateDoStatement, + createWhileStatement, + updateWhileStatement, + createForStatement, + updateForStatement, + createForInStatement, + updateForInStatement, + createForOfStatement, + updateForOfStatement, + createContinueStatement, + updateContinueStatement, + createBreakStatement, + updateBreakStatement, + createReturnStatement, + updateReturnStatement, + createWithStatement, + updateWithStatement, + createSwitchStatement, + updateSwitchStatement, + createLabeledStatement, + updateLabeledStatement, + createThrowStatement, + updateThrowStatement, + createTryStatement, + updateTryStatement, + createDebuggerStatement, + createVariableDeclaration, + updateVariableDeclaration, + createVariableDeclarationList, + updateVariableDeclarationList, + createFunctionDeclaration, + updateFunctionDeclaration, + createClassDeclaration, + updateClassDeclaration, + createInterfaceDeclaration, + updateInterfaceDeclaration, + createTypeAliasDeclaration, + updateTypeAliasDeclaration, + createEnumDeclaration, + updateEnumDeclaration, + createModuleDeclaration, + updateModuleDeclaration, + createModuleBlock, + updateModuleBlock, + createCaseBlock, + updateCaseBlock, + createNamespaceExportDeclaration, + updateNamespaceExportDeclaration, + createImportEqualsDeclaration, + updateImportEqualsDeclaration, + createImportDeclaration, + updateImportDeclaration, + createImportClause, + updateImportClause, + createAssertClause, + updateAssertClause, + createAssertEntry, + updateAssertEntry, + createImportTypeAssertionContainer, + updateImportTypeAssertionContainer, + createNamespaceImport, + updateNamespaceImport, + createNamespaceExport, + updateNamespaceExport, + createNamedImports, + updateNamedImports, + createImportSpecifier, + updateImportSpecifier, + createExportAssignment, + updateExportAssignment, + createExportDeclaration, + updateExportDeclaration, + createNamedExports, + updateNamedExports, + createExportSpecifier, + updateExportSpecifier, + createMissingDeclaration, + createExternalModuleReference, + updateExternalModuleReference, + // lazily load factory members for JSDoc types with similar structure + get createJSDocAllType() { return getJSDocPrimaryTypeCreateFunction(SyntaxKind.JSDocAllType); }, + get createJSDocUnknownType() { return getJSDocPrimaryTypeCreateFunction(SyntaxKind.JSDocUnknownType); }, + get createJSDocNonNullableType() { return getJSDocPrePostfixUnaryTypeCreateFunction(SyntaxKind.JSDocNonNullableType); }, + get updateJSDocNonNullableType() { return getJSDocPrePostfixUnaryTypeUpdateFunction(SyntaxKind.JSDocNonNullableType); }, + get createJSDocNullableType() { return getJSDocPrePostfixUnaryTypeCreateFunction(SyntaxKind.JSDocNullableType); }, + get updateJSDocNullableType() { return getJSDocPrePostfixUnaryTypeUpdateFunction(SyntaxKind.JSDocNullableType); }, + get createJSDocOptionalType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocOptionalType); }, + get updateJSDocOptionalType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocOptionalType); }, + get createJSDocVariadicType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocVariadicType); }, + get updateJSDocVariadicType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocVariadicType); }, + get createJSDocNamepathType() { return getJSDocUnaryTypeCreateFunction(SyntaxKind.JSDocNamepathType); }, + get updateJSDocNamepathType() { return getJSDocUnaryTypeUpdateFunction(SyntaxKind.JSDocNamepathType); }, + createJSDocFunctionType, + updateJSDocFunctionType, + createJSDocTypeLiteral, + updateJSDocTypeLiteral, + createJSDocTypeExpression, + updateJSDocTypeExpression, + createJSDocSignature, + updateJSDocSignature, + createJSDocTemplateTag, + updateJSDocTemplateTag, + createJSDocTypedefTag, + updateJSDocTypedefTag, + createJSDocParameterTag, + updateJSDocParameterTag, + createJSDocPropertyTag, + updateJSDocPropertyTag, + createJSDocCallbackTag, + updateJSDocCallbackTag, + createJSDocAugmentsTag, + updateJSDocAugmentsTag, + createJSDocImplementsTag, + updateJSDocImplementsTag, + createJSDocSeeTag, + updateJSDocSeeTag, + createJSDocNameReference, + updateJSDocNameReference, + createJSDocMemberName, + updateJSDocMemberName, + createJSDocLink, + updateJSDocLink, + createJSDocLinkCode, + updateJSDocLinkCode, + createJSDocLinkPlain, + updateJSDocLinkPlain, + // lazily load factory members for JSDoc tags with similar structure + get createJSDocTypeTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocTypeTag); }, + get updateJSDocTypeTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocTypeTag); }, + get createJSDocReturnTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocReturnTag); }, + get updateJSDocReturnTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocReturnTag); }, + get createJSDocThisTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocThisTag); }, + get updateJSDocThisTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocThisTag); }, + get createJSDocEnumTag() { return getJSDocTypeLikeTagCreateFunction(SyntaxKind.JSDocEnumTag); }, + get updateJSDocEnumTag() { return getJSDocTypeLikeTagUpdateFunction(SyntaxKind.JSDocEnumTag); }, + get createJSDocAuthorTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocAuthorTag); }, + get updateJSDocAuthorTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocAuthorTag); }, + get createJSDocClassTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocClassTag); }, + get updateJSDocClassTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocClassTag); }, + get createJSDocPublicTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocPublicTag); }, + get updateJSDocPublicTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocPublicTag); }, + get createJSDocPrivateTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocPrivateTag); }, + get updateJSDocPrivateTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocPrivateTag); }, + get createJSDocProtectedTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocProtectedTag); }, + get updateJSDocProtectedTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocProtectedTag); }, + get createJSDocReadonlyTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocReadonlyTag); }, + get updateJSDocReadonlyTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocReadonlyTag); }, + get createJSDocOverrideTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocOverrideTag); }, + get updateJSDocOverrideTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocOverrideTag); }, + get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction(SyntaxKind.JSDocDeprecatedTag); }, + get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction(SyntaxKind.JSDocDeprecatedTag); }, + createJSDocUnknownTag, + updateJSDocUnknownTag, + createJSDocText, + updateJSDocText, + createJSDocComment, + updateJSDocComment, + createJsxElement, + updateJsxElement, + createJsxSelfClosingElement, + updateJsxSelfClosingElement, + createJsxOpeningElement, + updateJsxOpeningElement, + createJsxClosingElement, + updateJsxClosingElement, + createJsxFragment, + createJsxText, + updateJsxText, + createJsxOpeningFragment, + createJsxJsxClosingFragment, + updateJsxFragment, + createJsxAttribute, + updateJsxAttribute, + createJsxAttributes, + updateJsxAttributes, + createJsxSpreadAttribute, + updateJsxSpreadAttribute, + createJsxExpression, + updateJsxExpression, + createCaseClause, + updateCaseClause, + createDefaultClause, + updateDefaultClause, + createHeritageClause, + updateHeritageClause, + createCatchClause, + updateCatchClause, + createPropertyAssignment, + updatePropertyAssignment, + createShorthandPropertyAssignment, + updateShorthandPropertyAssignment, + createSpreadAssignment, + updateSpreadAssignment, + createEnumMember, + updateEnumMember, + createSourceFile, + updateSourceFile, + createBundle, + updateBundle, + createUnparsedSource, + createUnparsedPrologue, + createUnparsedPrepend, + createUnparsedTextLike, + createUnparsedSyntheticReference, + createInputFiles, + createSyntheticExpression, + createSyntaxList, + createNotEmittedStatement, + createPartiallyEmittedExpression, + updatePartiallyEmittedExpression, + createCommaListExpression, + updateCommaListExpression, + createEndOfDeclarationMarker, + createMergeDeclarationMarker, + createSyntheticReferenceExpression, + updateSyntheticReferenceExpression, + cloneNode, + + // Lazily load factory methods for common operator factories and utilities + get createComma() { return getBinaryCreateFunction(SyntaxKind.CommaToken); }, + get createAssignment() { return getBinaryCreateFunction(SyntaxKind.EqualsToken) as NodeFactory["createAssignment"]; }, + get createLogicalOr() { return getBinaryCreateFunction(SyntaxKind.BarBarToken); }, + get createLogicalAnd() { return getBinaryCreateFunction(SyntaxKind.AmpersandAmpersandToken); }, + get createBitwiseOr() { return getBinaryCreateFunction(SyntaxKind.BarToken); }, + get createBitwiseXor() { return getBinaryCreateFunction(SyntaxKind.CaretToken); }, + get createBitwiseAnd() { return getBinaryCreateFunction(SyntaxKind.AmpersandToken); }, + get createStrictEquality() { return getBinaryCreateFunction(SyntaxKind.EqualsEqualsEqualsToken); }, + get createStrictInequality() { return getBinaryCreateFunction(SyntaxKind.ExclamationEqualsEqualsToken); }, + get createEquality() { return getBinaryCreateFunction(SyntaxKind.EqualsEqualsToken); }, + get createInequality() { return getBinaryCreateFunction(SyntaxKind.ExclamationEqualsToken); }, + get createLessThan() { return getBinaryCreateFunction(SyntaxKind.LessThanToken); }, + get createLessThanEquals() { return getBinaryCreateFunction(SyntaxKind.LessThanEqualsToken); }, + get createGreaterThan() { return getBinaryCreateFunction(SyntaxKind.GreaterThanToken); }, + get createGreaterThanEquals() { return getBinaryCreateFunction(SyntaxKind.GreaterThanEqualsToken); }, + get createLeftShift() { return getBinaryCreateFunction(SyntaxKind.LessThanLessThanToken); }, + get createRightShift() { return getBinaryCreateFunction(SyntaxKind.GreaterThanGreaterThanToken); }, + get createUnsignedRightShift() { return getBinaryCreateFunction(SyntaxKind.GreaterThanGreaterThanGreaterThanToken); }, + get createAdd() { return getBinaryCreateFunction(SyntaxKind.PlusToken); }, + get createSubtract() { return getBinaryCreateFunction(SyntaxKind.MinusToken); }, + get createMultiply() { return getBinaryCreateFunction(SyntaxKind.AsteriskToken); }, + get createDivide() { return getBinaryCreateFunction(SyntaxKind.SlashToken); }, + get createModulo() { return getBinaryCreateFunction(SyntaxKind.PercentToken); }, + get createExponent() { return getBinaryCreateFunction(SyntaxKind.AsteriskAsteriskToken); }, + get createPrefixPlus() { return getPrefixUnaryCreateFunction(SyntaxKind.PlusToken); }, + get createPrefixMinus() { return getPrefixUnaryCreateFunction(SyntaxKind.MinusToken); }, + get createPrefixIncrement() { return getPrefixUnaryCreateFunction(SyntaxKind.PlusPlusToken); }, + get createPrefixDecrement() { return getPrefixUnaryCreateFunction(SyntaxKind.MinusMinusToken); }, + get createBitwiseNot() { return getPrefixUnaryCreateFunction(SyntaxKind.TildeToken); }, + get createLogicalNot() { return getPrefixUnaryCreateFunction(SyntaxKind.ExclamationToken); }, + get createPostfixIncrement() { return getPostfixUnaryCreateFunction(SyntaxKind.PlusPlusToken); }, + get createPostfixDecrement() { return getPostfixUnaryCreateFunction(SyntaxKind.MinusMinusToken); }, + + // Compound nodes + createImmediatelyInvokedFunctionExpression, + createImmediatelyInvokedArrowFunction, + createVoidZero, + createExportDefault, + createExternalModuleExport, + createTypeCheck, + createMethodCall, + createGlobalMethodCall, + createFunctionBindCall, + createFunctionCallCall, + createFunctionApplyCall, + createArraySliceCall, + createArrayConcatCall, + createObjectDefinePropertyCall, + createReflectGetCall, + createReflectSetCall, + createPropertyDescriptor, + createCallBinding, + createAssignmentTargetWrapper, + + // Utilities + inlineExpressions, + getInternalName, + getLocalName, + getExportName, + getDeclarationName, + getNamespaceMemberName, + getExternalModuleOrNamespaceExportName, + restoreOuterExpressions, + restoreEnclosingLabel, + createUseStrictPrologue, + copyPrologue, + copyStandardPrologue, + copyCustomPrologue, + ensureUseStrict, + liftToBlock, + mergeLexicalEnvironment, + updateModifiers, + }; + + forEach(nodeFactoryPatchers, fn => fn(factory)); - // This *was* a `NodeArray`, but the `hasTrailingComma` option differs. Recreate the - // array with the same elements, text range, and transform flags but with the updated - // value for `hasTrailingComma` - const array = elements.slice() as MutableNodeArray; - array.pos = elements.pos; - array.end = elements.end; - array.hasTrailingComma = hasTrailingComma; - array.transformFlags = elements.transformFlags; - Debug.attachNodeArrayDebugInfo(array); - return array; + return factory; + + // @api + function createNodeArray(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray { + if (elements === undefined || elements === emptyArray) { + elements = []; + } + else if (isNodeArray(elements)) { + if (hasTrailingComma === undefined || elements.hasTrailingComma === hasTrailingComma) { + // Ensure the transform flags have been aggregated for this NodeArray + if (elements.transformFlags === undefined) { + aggregateChildrenFlags(elements as MutableNodeArray); + } + Debug.attachNodeArrayDebugInfo(elements); + return elements; } - // Since the element list of a node array is typically created by starting with an empty array and - // repeatedly calling push(), the list may not have the optimal memory layout. We invoke slice() for - // small arrays (1 to 4 elements) to give the VM a chance to allocate an optimal representation. - const length = elements.length; - const array = (length >= 1 && length <= 4 ? elements.slice() : elements) as MutableNodeArray; - setTextRangePosEnd(array, -1, -1); - array.hasTrailingComma = !!hasTrailingComma; - aggregateChildrenFlags(array); + // This *was* a `NodeArray`, but the `hasTrailingComma` option differs. Recreate the + // array with the same elements, text range, and transform flags but with the updated + // value for `hasTrailingComma` + const array = elements.slice() as MutableNodeArray; + array.pos = elements.pos; + array.end = elements.end; + array.hasTrailingComma = hasTrailingComma; + array.transformFlags = elements.transformFlags; Debug.attachNodeArrayDebugInfo(array); return array; } - function createBaseNode(kind: T["kind"]) { - return baseFactory.createBaseNode(kind) as Mutable; - } + // Since the element list of a node array is typically created by starting with an empty array and + // repeatedly calling push(), the list may not have the optimal memory layout. We invoke slice() for + // small arrays (1 to 4 elements) to give the VM a chance to allocate an optimal representation. + const length = elements.length; + const array = (length >= 1 && length <= 4 ? elements.slice() : elements) as MutableNodeArray; + setTextRangePosEnd(array, -1, -1); + array.hasTrailingComma = !!hasTrailingComma; + aggregateChildrenFlags(array); + Debug.attachNodeArrayDebugInfo(array); + return array; + } - function createBaseDeclaration( - kind: T["kind"], - ) { - const node = createBaseNode(kind); - // NOTE: The following properties are commonly set by the binder and are added here to - // ensure declarations have a stable shape. - node.symbol = undefined!; // initialized by binder - node.localSymbol = undefined; // initialized by binder - node.locals = undefined; // initialized by binder - node.nextContainer = undefined; // initialized by binder - return node; - } + function createBaseNode(kind: T["kind"]) { + return baseFactory.createBaseNode(kind) as Mutable; + } - function createBaseNamedDeclaration( - kind: T["kind"], - modifiers: readonly ModifierLike[] | undefined, - name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined - ) { - const node = createBaseDeclaration(kind); - name = asName(name); - node.name = name; - if (canHaveModifiers(node)) { - (node as Mutable).modifiers = asNodeArray(modifiers); - (node as Mutable).transformFlags |= propagateChildrenFlags(node.modifiers); - // node.decorators = filter(node.modifiers, isDecorator); - } + function createBaseDeclaration( + kind: T["kind"], + ) { + const node = createBaseNode(kind); + // NOTE: The following properties are commonly set by the binder and are added here to + // ensure declarations have a stable shape. + node.symbol = undefined!; // initialized by binder + node.localSymbol = undefined; // initialized by binder + node.locals = undefined; // initialized by binder + node.nextContainer = undefined; // initialized by binder + return node; + } - // The PropertyName of a member is allowed to be `await`. - // We don't need to exclude `await` for type signatures since types - // don't propagate child flags. - if (name) { - switch (node.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertyAssignment: - if (isIdentifier(name)) { - node.transformFlags |= propagateIdentifierNameFlags(name); - break; - } - // fall through - default: - node.transformFlags |= propagateChildFlags(name); + function createBaseNamedDeclaration( + kind: T["kind"], + modifiers: readonly ModifierLike[] | undefined, + name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined + ) { + const node = createBaseDeclaration(kind); + name = asName(name); + node.name = name; + if (canHaveModifiers(node)) { + (node as Mutable).modifiers = asNodeArray(modifiers); + (node as Mutable).transformFlags |= propagateChildrenFlags(node.modifiers); + // node.decorators = filter(node.modifiers, isDecorator); + } + + // The PropertyName of a member is allowed to be `await`. + // We don't need to exclude `await` for type signatures since types + // don't propagate child flags. + if (name) { + switch (node.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertyAssignment: + if (isIdentifier(name)) { + node.transformFlags |= propagateIdentifierNameFlags(name); break; - } + } + // fall through + default: + node.transformFlags |= propagateChildFlags(name); + break; } - return node; - } - - function createBaseGenericNamedDeclaration }>( - kind: T["kind"], - modifiers: readonly ModifierLike[] | undefined, - name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined - ) { - const node = createBaseNamedDeclaration( - kind, - modifiers, - name - ); - node.typeParameters = asNodeArray(typeParameters); - node.transformFlags |= propagateChildrenFlags(node.typeParameters); - if (typeParameters) node.transformFlags |= TransformFlags.ContainsTypeScript; - return node; } + return node; + } - function createBaseSignatureDeclaration( - kind: T["kind"], - modifiers: readonly ModifierLike[] | undefined, - name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[] | undefined, - type: TypeNode | undefined - ) { - const node = createBaseGenericNamedDeclaration( - kind, - modifiers, - name, - typeParameters - ); - node.parameters = createNodeArray(parameters); - node.type = type; - node.transformFlags |= - propagateChildrenFlags(node.parameters) | - propagateChildFlags(node.type); - if (type) node.transformFlags |= TransformFlags.ContainsTypeScript; + function createBaseGenericNamedDeclaration }>( + kind: T["kind"], + modifiers: readonly ModifierLike[] | undefined, + name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined + ) { + const node = createBaseNamedDeclaration( + kind, + modifiers, + name + ); + node.typeParameters = asNodeArray(typeParameters); + node.transformFlags |= propagateChildrenFlags(node.typeParameters); + if (typeParameters) node.transformFlags |= TransformFlags.ContainsTypeScript; + return node; + } - // The following properties are used for quick info - node.typeArguments = undefined; - return node; - } + function createBaseSignatureDeclaration( + kind: T["kind"], + modifiers: readonly ModifierLike[] | undefined, + name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[] | undefined, + type: TypeNode | undefined + ) { + const node = createBaseGenericNamedDeclaration( + kind, + modifiers, + name, + typeParameters + ); + node.parameters = createNodeArray(parameters); + node.type = type; + node.transformFlags |= + propagateChildrenFlags(node.parameters) | + propagateChildFlags(node.type); + if (type) node.transformFlags |= TransformFlags.ContainsTypeScript; + + // The following properties are used for quick info + node.typeArguments = undefined; + return node; + } - function finishUpdateBaseSignatureDeclaration(updated: Mutable, original: T) { - if (updated !== original) { - // copy children used for quick info - updated.typeArguments = original.typeArguments; - } - return update(updated, original); + function finishUpdateBaseSignatureDeclaration(updated: Mutable, original: T) { + if (updated !== original) { + // copy children used for quick info + updated.typeArguments = original.typeArguments; } + return update(updated, original); + } - function createBaseFunctionLikeDeclaration( - kind: T["kind"], - modifiers: readonly ModifierLike[] | undefined, - name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[] | undefined, - type: TypeNode | undefined, - body: T["body"] - ) { - const node = createBaseSignatureDeclaration( - kind, - modifiers, - name, - typeParameters, - parameters, - type - ); - node.body = body; - node.transformFlags |= propagateChildFlags(node.body) & ~TransformFlags.ContainsPossibleTopLevelAwait; - if (!body) node.transformFlags |= TransformFlags.ContainsTypeScript; - return node; - } + function createBaseFunctionLikeDeclaration( + kind: T["kind"], + modifiers: readonly ModifierLike[] | undefined, + name: Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | BindingPattern | string | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[] | undefined, + type: TypeNode | undefined, + body: T["body"] + ) { + const node = createBaseSignatureDeclaration( + kind, + modifiers, + name, + typeParameters, + parameters, + type + ); + node.body = body; + node.transformFlags |= propagateChildFlags(node.body) & ~TransformFlags.ContainsPossibleTopLevelAwait; + if (!body) node.transformFlags |= TransformFlags.ContainsTypeScript; + return node; + } - function createBaseInterfaceOrClassLikeDeclaration( - kind: T["kind"], - modifiers: readonly ModifierLike[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined - ) { - const node = createBaseGenericNamedDeclaration( - kind, - modifiers, - name, - typeParameters - ); - node.heritageClauses = asNodeArray(heritageClauses); - node.transformFlags |= propagateChildrenFlags(node.heritageClauses); - return node; - } + function createBaseInterfaceOrClassLikeDeclaration( + kind: T["kind"], + modifiers: readonly ModifierLike[] | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined + ) { + const node = createBaseGenericNamedDeclaration( + kind, + modifiers, + name, + typeParameters + ); + node.heritageClauses = asNodeArray(heritageClauses); + node.transformFlags |= propagateChildrenFlags(node.heritageClauses); + return node; + } - function createBaseClassLikeDeclaration( - kind: T["kind"], - modifiers: readonly ModifierLike[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[] - ) { - const node = createBaseInterfaceOrClassLikeDeclaration( - kind, - modifiers, - name, - typeParameters, - heritageClauses - ); - node.members = createNodeArray(members); - node.transformFlags |= propagateChildrenFlags(node.members); - return node; - } + function createBaseClassLikeDeclaration( + kind: T["kind"], + modifiers: readonly ModifierLike[] | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[] + ) { + const node = createBaseInterfaceOrClassLikeDeclaration( + kind, + modifiers, + name, + typeParameters, + heritageClauses + ); + node.members = createNodeArray(members); + node.transformFlags |= propagateChildrenFlags(node.members); + return node; + } - function createBaseBindingLikeDeclaration( - kind: T["kind"], - modifiers: readonly ModifierLike[] | undefined, - name: string | T["name"] | undefined, - initializer: Expression | undefined - ) { - const node = createBaseNamedDeclaration( - kind, - modifiers, - name - ); - node.initializer = initializer; - node.transformFlags |= propagateChildFlags(node.initializer); - return node; - } + function createBaseBindingLikeDeclaration( + kind: T["kind"], + modifiers: readonly ModifierLike[] | undefined, + name: string | T["name"] | undefined, + initializer: Expression | undefined + ) { + const node = createBaseNamedDeclaration( + kind, + modifiers, + name + ); + node.initializer = initializer; + node.transformFlags |= propagateChildFlags(node.initializer); + return node; + } - function createBaseVariableLikeDeclaration( - kind: T["kind"], - modifiers: readonly ModifierLike[] | undefined, - name: string | T["name"] | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) { - const node = createBaseBindingLikeDeclaration( - kind, - modifiers, - name, - initializer - ); - node.type = type; - node.transformFlags |= propagateChildFlags(type); - if (type) node.transformFlags |= TransformFlags.ContainsTypeScript; - return node; - } + function createBaseVariableLikeDeclaration( + kind: T["kind"], + modifiers: readonly ModifierLike[] | undefined, + name: string | T["name"] | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) { + const node = createBaseBindingLikeDeclaration( + kind, + modifiers, + name, + initializer + ); + node.type = type; + node.transformFlags |= propagateChildFlags(type); + if (type) node.transformFlags |= TransformFlags.ContainsTypeScript; + return node; + } - // - // Literals - // + // + // Literals + // - function createBaseLiteral( - kind: T["kind"], - text: string - ) { - const node = createBaseToken(kind); - node.text = text; - return node; - } + function createBaseLiteral( + kind: T["kind"], + text: string + ) { + const node = createBaseToken(kind); + node.text = text; + return node; + } - // @api - function createNumericLiteral(value: string | number, numericLiteralFlags: TokenFlags = TokenFlags.None): NumericLiteral { - const node = createBaseLiteral(SyntaxKind.NumericLiteral, typeof value === "number" ? value + "" : value); - node.numericLiteralFlags = numericLiteralFlags; - if (numericLiteralFlags & TokenFlags.BinaryOrOctalSpecifier) node.transformFlags |= TransformFlags.ContainsES2015; - return node; - } + // @api + function createNumericLiteral(value: string | number, numericLiteralFlags: TokenFlags = TokenFlags.None): NumericLiteral { + const node = createBaseLiteral(SyntaxKind.NumericLiteral, typeof value === "number" ? value + "" : value); + node.numericLiteralFlags = numericLiteralFlags; + if (numericLiteralFlags & TokenFlags.BinaryOrOctalSpecifier) node.transformFlags |= TransformFlags.ContainsES2015; + return node; + } - // @api - function createBigIntLiteral(value: string | PseudoBigInt): BigIntLiteral { - const node = createBaseLiteral(SyntaxKind.BigIntLiteral, typeof value === "string" ? value : pseudoBigIntToString(value) + "n"); - node.transformFlags |= TransformFlags.ContainsESNext; - return node; - } + // @api + function createBigIntLiteral(value: string | PseudoBigInt): BigIntLiteral { + const node = createBaseLiteral(SyntaxKind.BigIntLiteral, typeof value === "string" ? value : pseudoBigIntToString(value) + "n"); + node.transformFlags |= TransformFlags.ContainsESNext; + return node; + } - function createBaseStringLiteral(text: string, isSingleQuote?: boolean) { - const node = createBaseLiteral(SyntaxKind.StringLiteral, text); - node.singleQuote = isSingleQuote; - return node; - } + function createBaseStringLiteral(text: string, isSingleQuote?: boolean) { + const node = createBaseLiteral(SyntaxKind.StringLiteral, text); + node.singleQuote = isSingleQuote; + return node; + } - // @api - function createStringLiteral(text: string, isSingleQuote?: boolean, hasExtendedUnicodeEscape?: boolean): StringLiteral { - const node = createBaseStringLiteral(text, isSingleQuote); - node.hasExtendedUnicodeEscape = hasExtendedUnicodeEscape; - if (hasExtendedUnicodeEscape) node.transformFlags |= TransformFlags.ContainsES2015; - return node; - } + // @api + function createStringLiteral(text: string, isSingleQuote?: boolean, hasExtendedUnicodeEscape?: boolean): StringLiteral { + const node = createBaseStringLiteral(text, isSingleQuote); + node.hasExtendedUnicodeEscape = hasExtendedUnicodeEscape; + if (hasExtendedUnicodeEscape) node.transformFlags |= TransformFlags.ContainsES2015; + return node; + } - // @api - function createStringLiteralFromNode(sourceNode: PropertyNameLiteral | PrivateIdentifier): StringLiteral { - const node = createBaseStringLiteral(getTextOfIdentifierOrLiteral(sourceNode), /*isSingleQuote*/ undefined); - node.textSourceNode = sourceNode; - return node; - } + // @api + function createStringLiteralFromNode(sourceNode: PropertyNameLiteral | PrivateIdentifier): StringLiteral { + const node = createBaseStringLiteral(getTextOfIdentifierOrLiteral(sourceNode), /*isSingleQuote*/ undefined); + node.textSourceNode = sourceNode; + return node; + } - // @api - function createRegularExpressionLiteral(text: string): RegularExpressionLiteral { - const node = createBaseLiteral(SyntaxKind.RegularExpressionLiteral, text); - return node; - } + // @api + function createRegularExpressionLiteral(text: string): RegularExpressionLiteral { + const node = createBaseLiteral(SyntaxKind.RegularExpressionLiteral, text); + return node; + } - // @api - function createLiteralLikeNode(kind: LiteralToken["kind"] | SyntaxKind.JsxTextAllWhiteSpaces, text: string): LiteralToken { - switch (kind) { - case SyntaxKind.NumericLiteral: return createNumericLiteral(text, /*numericLiteralFlags*/ 0); - case SyntaxKind.BigIntLiteral: return createBigIntLiteral(text); - case SyntaxKind.StringLiteral: return createStringLiteral(text, /*isSingleQuote*/ undefined); - case SyntaxKind.JsxText: return createJsxText(text, /*containsOnlyTriviaWhiteSpaces*/ false); - case SyntaxKind.JsxTextAllWhiteSpaces: return createJsxText(text, /*containsOnlyTriviaWhiteSpaces*/ true); - case SyntaxKind.RegularExpressionLiteral: return createRegularExpressionLiteral(text); - case SyntaxKind.NoSubstitutionTemplateLiteral: return createTemplateLiteralLikeNode(kind, text, /*rawText*/ undefined, /*templateFlags*/ 0) as NoSubstitutionTemplateLiteral; - } + // @api + function createLiteralLikeNode(kind: LiteralToken["kind"] | SyntaxKind.JsxTextAllWhiteSpaces, text: string): LiteralToken { + switch (kind) { + case SyntaxKind.NumericLiteral: return createNumericLiteral(text, /*numericLiteralFlags*/ 0); + case SyntaxKind.BigIntLiteral: return createBigIntLiteral(text); + case SyntaxKind.StringLiteral: return createStringLiteral(text, /*isSingleQuote*/ undefined); + case SyntaxKind.JsxText: return createJsxText(text, /*containsOnlyTriviaWhiteSpaces*/ false); + case SyntaxKind.JsxTextAllWhiteSpaces: return createJsxText(text, /*containsOnlyTriviaWhiteSpaces*/ true); + case SyntaxKind.RegularExpressionLiteral: return createRegularExpressionLiteral(text); + case SyntaxKind.NoSubstitutionTemplateLiteral: return createTemplateLiteralLikeNode(kind, text, /*rawText*/ undefined, /*templateFlags*/ 0) as NoSubstitutionTemplateLiteral; } + } - // - // Identifiers - // + // + // Identifiers + // - function createBaseIdentifier(text: string, originalKeywordKind: SyntaxKind | undefined) { - if (originalKeywordKind === undefined && text) { - originalKeywordKind = stringToToken(text); - } - if (originalKeywordKind === SyntaxKind.Identifier) { - originalKeywordKind = undefined; - } - const node = baseFactory.createBaseIdentifierNode(SyntaxKind.Identifier) as Mutable; - node.originalKeywordKind = originalKeywordKind; - node.escapedText = escapeLeadingUnderscores(text); - return node; + function createBaseIdentifier(text: string, originalKeywordKind: SyntaxKind | undefined) { + if (originalKeywordKind === undefined && text) { + originalKeywordKind = stringToToken(text); } - - function createBaseGeneratedIdentifier(text: string, autoGenerateFlags: GeneratedIdentifierFlags, prefix: string | GeneratedNamePart | undefined, suffix: string | undefined) { - const node = createBaseIdentifier(text, /*originalKeywordKind*/ undefined) as Mutable; - node.autoGenerateFlags = autoGenerateFlags; - node.autoGenerateId = nextAutoGenerateId; - node.autoGeneratePrefix = prefix; - node.autoGenerateSuffix = suffix; - nextAutoGenerateId++; - return node; + if (originalKeywordKind === SyntaxKind.Identifier) { + originalKeywordKind = undefined; } + const node = baseFactory.createBaseIdentifierNode(SyntaxKind.Identifier) as Mutable; + node.originalKeywordKind = originalKeywordKind; + node.escapedText = escapeLeadingUnderscores(text); + return node; + } - // @api - function createIdentifier(text: string, typeArguments?: readonly (TypeNode | TypeParameterDeclaration)[], originalKeywordKind?: SyntaxKind, hasExtendedUnicodeEscape?: boolean): Identifier { - const node = createBaseIdentifier(text, originalKeywordKind); - if (typeArguments) { - // NOTE: we do not use `setChildren` here because typeArguments in an identifier do not contribute to transformations - node.typeArguments = createNodeArray(typeArguments); - } - if (node.originalKeywordKind === SyntaxKind.AwaitKeyword) { - node.transformFlags |= TransformFlags.ContainsPossibleTopLevelAwait; - } - if (hasExtendedUnicodeEscape) { - node.hasExtendedUnicodeEscape = hasExtendedUnicodeEscape; - node.transformFlags |= TransformFlags.ContainsES2015; - } - return node; - } + function createBaseGeneratedIdentifier(text: string, autoGenerateFlags: GeneratedIdentifierFlags, prefix: string | GeneratedNamePart | undefined, suffix: string | undefined) { + const node = createBaseIdentifier(text, /*originalKeywordKind*/ undefined) as Mutable; + node.autoGenerateFlags = autoGenerateFlags; + node.autoGenerateId = nextAutoGenerateId; + node.autoGeneratePrefix = prefix; + node.autoGenerateSuffix = suffix; + nextAutoGenerateId++; + return node; + } - // @api - function updateIdentifier(node: Identifier, typeArguments?: NodeArray | undefined): Identifier { - return node.typeArguments !== typeArguments - ? update(createIdentifier(idText(node), typeArguments), node) - : node; + // @api + function createIdentifier(text: string, typeArguments?: readonly (TypeNode | TypeParameterDeclaration)[], originalKeywordKind?: SyntaxKind, hasExtendedUnicodeEscape?: boolean): Identifier { + const node = createBaseIdentifier(text, originalKeywordKind); + if (typeArguments) { + // NOTE: we do not use `setChildren` here because typeArguments in an identifier do not contribute to transformations + node.typeArguments = createNodeArray(typeArguments); } - - // @api - function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes?: boolean, prefix?: string | GeneratedNamePart, suffix?: string): GeneratedIdentifier { - let flags = GeneratedIdentifierFlags.Auto; - if (reservedInNestedScopes) flags |= GeneratedIdentifierFlags.ReservedInNestedScopes; - const name = createBaseGeneratedIdentifier("", flags, prefix, suffix); - if (recordTempVariable) { - recordTempVariable(name); - } - return name; + if (node.originalKeywordKind === SyntaxKind.AwaitKeyword) { + node.transformFlags |= TransformFlags.ContainsPossibleTopLevelAwait; } - - /** Create a unique temporary variable for use in a loop. */ - // @api - function createLoopVariable(reservedInNestedScopes?: boolean): Identifier { - let flags = GeneratedIdentifierFlags.Loop; - if (reservedInNestedScopes) flags |= GeneratedIdentifierFlags.ReservedInNestedScopes; - return createBaseGeneratedIdentifier("", flags, /*prefix*/ undefined, /*suffix*/ undefined); + if (hasExtendedUnicodeEscape) { + node.hasExtendedUnicodeEscape = hasExtendedUnicodeEscape; + node.transformFlags |= TransformFlags.ContainsES2015; } + return node; + } - /** Create a unique name based on the supplied text. */ - // @api - function createUniqueName(text: string, flags: GeneratedIdentifierFlags = GeneratedIdentifierFlags.None, prefix?: string | GeneratedNamePart, suffix?: string): Identifier { - Debug.assert(!(flags & GeneratedIdentifierFlags.KindMask), "Argument out of range: flags"); - Debug.assert((flags & (GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel)) !== GeneratedIdentifierFlags.FileLevel, "GeneratedIdentifierFlags.FileLevel cannot be set without also setting GeneratedIdentifierFlags.Optimistic"); - return createBaseGeneratedIdentifier(text, GeneratedIdentifierFlags.Unique | flags, prefix, suffix); - } + // @api + function updateIdentifier(node: Identifier, typeArguments?: NodeArray | undefined): Identifier { + return node.typeArguments !== typeArguments + ? update(createIdentifier(idText(node), typeArguments), node) + : node; + } - /** Create a unique name generated for a node. */ - // @api - function getGeneratedNameForNode(node: Node | undefined, flags: GeneratedIdentifierFlags = 0, prefix?: string | GeneratedNamePart, suffix?: string): Identifier { - Debug.assert(!(flags & GeneratedIdentifierFlags.KindMask), "Argument out of range: flags"); - const text = !node ? "" : - isMemberName(node) ? formatGeneratedName(/*privateName*/ false, prefix, node, suffix, idText) : - `generated@${getNodeId(node)}`; - if (prefix || suffix) flags |= GeneratedIdentifierFlags.Optimistic; - const name = createBaseGeneratedIdentifier(text, GeneratedIdentifierFlags.Node | flags, prefix, suffix); - name.original = node; - return name; + // @api + function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes?: boolean, prefix?: string | GeneratedNamePart, suffix?: string): GeneratedIdentifier { + let flags = GeneratedIdentifierFlags.Auto; + if (reservedInNestedScopes) flags |= GeneratedIdentifierFlags.ReservedInNestedScopes; + const name = createBaseGeneratedIdentifier("", flags, prefix, suffix); + if (recordTempVariable) { + recordTempVariable(name); } + return name; + } - function createBasePrivateIdentifier(text: string) { - const node = baseFactory.createBasePrivateIdentifierNode(SyntaxKind.PrivateIdentifier) as Mutable; - node.escapedText = escapeLeadingUnderscores(text); - node.transformFlags |= TransformFlags.ContainsClassFields; - return node; - } + /** Create a unique temporary variable for use in a loop. */ + // @api + function createLoopVariable(reservedInNestedScopes?: boolean): Identifier { + let flags = GeneratedIdentifierFlags.Loop; + if (reservedInNestedScopes) flags |= GeneratedIdentifierFlags.ReservedInNestedScopes; + return createBaseGeneratedIdentifier("", flags, /*prefix*/ undefined, /*suffix*/ undefined); + } - // @api - function createPrivateIdentifier(text: string): PrivateIdentifier { - if (!startsWith(text, "#")) Debug.fail("First character of private identifier must be #: " + text); - return createBasePrivateIdentifier(text); - } + /** Create a unique name based on the supplied text. */ + // @api + function createUniqueName(text: string, flags: GeneratedIdentifierFlags = GeneratedIdentifierFlags.None, prefix?: string | GeneratedNamePart, suffix?: string): Identifier { + Debug.assert(!(flags & GeneratedIdentifierFlags.KindMask), "Argument out of range: flags"); + Debug.assert((flags & (GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel)) !== GeneratedIdentifierFlags.FileLevel, "GeneratedIdentifierFlags.FileLevel cannot be set without also setting GeneratedIdentifierFlags.Optimistic"); + return createBaseGeneratedIdentifier(text, GeneratedIdentifierFlags.Unique | flags, prefix, suffix); + } - function createBaseGeneratedPrivateIdentifier(text: string, autoGenerateFlags: GeneratedIdentifierFlags, prefix: string | GeneratedNamePart | undefined, suffix: string | undefined) { - const node = createBasePrivateIdentifier(text); - node.autoGenerateFlags = autoGenerateFlags; - node.autoGenerateId = nextAutoGenerateId; - node.autoGeneratePrefix = prefix; - node.autoGenerateSuffix = suffix; - nextAutoGenerateId++; - return node; - } + /** Create a unique name generated for a node. */ + // @api + function getGeneratedNameForNode(node: Node | undefined, flags: GeneratedIdentifierFlags = 0, prefix?: string | GeneratedNamePart, suffix?: string): Identifier { + Debug.assert(!(flags & GeneratedIdentifierFlags.KindMask), "Argument out of range: flags"); + const text = !node ? "" : + isMemberName(node) ? formatGeneratedName(/*privateName*/ false, prefix, node, suffix, idText) : + `generated@${getNodeId(node)}`; + if (prefix || suffix) flags |= GeneratedIdentifierFlags.Optimistic; + const name = createBaseGeneratedIdentifier(text, GeneratedIdentifierFlags.Node | flags, prefix, suffix); + name.original = node; + return name; + } - /** Create a unique name based on the supplied text. */ - // @api - function createUniquePrivateName(text?: string, prefix?: string | GeneratedNamePart, suffix?: string): PrivateIdentifier { - if (text && !startsWith(text, "#")) Debug.fail("First character of private identifier must be #: " + text); - const autoGenerateFlags = GeneratedIdentifierFlags.ReservedInNestedScopes | - (text ? GeneratedIdentifierFlags.Unique : GeneratedIdentifierFlags.Auto); - return createBaseGeneratedPrivateIdentifier(text ?? "", autoGenerateFlags, prefix, suffix); - } + function createBasePrivateIdentifier(text: string) { + const node = baseFactory.createBasePrivateIdentifierNode(SyntaxKind.PrivateIdentifier) as Mutable; + node.escapedText = escapeLeadingUnderscores(text); + node.transformFlags |= TransformFlags.ContainsClassFields; + return node; + } - // @api - function getGeneratedPrivateNameForNode(node: Node, prefix?: string | GeneratedNamePart, suffix?: string): PrivateIdentifier { - const text = isMemberName(node) ? formatGeneratedName(/*privateName*/ true, prefix, node, suffix, idText) : - `#generated@${getNodeId(node)}`; - const flags = prefix || suffix ? GeneratedIdentifierFlags.Optimistic : GeneratedIdentifierFlags.None; - const name = createBaseGeneratedPrivateIdentifier(text, GeneratedIdentifierFlags.Node | flags, prefix, suffix); - name.original = node; - return name; - } + // @api + function createPrivateIdentifier(text: string): PrivateIdentifier { + if (!startsWith(text, "#")) Debug.fail("First character of private identifier must be #: " + text); + return createBasePrivateIdentifier(text); + } - // - // Punctuation - // + function createBaseGeneratedPrivateIdentifier(text: string, autoGenerateFlags: GeneratedIdentifierFlags, prefix: string | GeneratedNamePart | undefined, suffix: string | undefined) { + const node = createBasePrivateIdentifier(text); + node.autoGenerateFlags = autoGenerateFlags; + node.autoGenerateId = nextAutoGenerateId; + node.autoGeneratePrefix = prefix; + node.autoGenerateSuffix = suffix; + nextAutoGenerateId++; + return node; + } - function createBaseToken(kind: T["kind"]) { - return baseFactory.createBaseTokenNode(kind) as Mutable; - } + /** Create a unique name based on the supplied text. */ + // @api + function createUniquePrivateName(text?: string, prefix?: string | GeneratedNamePart, suffix?: string): PrivateIdentifier { + if (text && !startsWith(text, "#")) Debug.fail("First character of private identifier must be #: " + text); + const autoGenerateFlags = GeneratedIdentifierFlags.ReservedInNestedScopes | + (text ? GeneratedIdentifierFlags.Unique : GeneratedIdentifierFlags.Auto); + return createBaseGeneratedPrivateIdentifier(text ?? "", autoGenerateFlags, prefix, suffix); + } - // @api - function createToken(token: SyntaxKind.SuperKeyword): SuperExpression; - function createToken(token: SyntaxKind.ThisKeyword): ThisExpression; - function createToken(token: SyntaxKind.NullKeyword): NullLiteral; - function createToken(token: SyntaxKind.TrueKeyword): TrueLiteral; - function createToken(token: SyntaxKind.FalseKeyword): FalseLiteral; - function createToken(token: TKind): PunctuationToken; - function createToken(token: TKind): KeywordTypeNode; - function createToken(token: TKind): ModifierToken; - function createToken(token: TKind): KeywordToken; - function createToken(token: TKind): Token; - function createToken(token: TKind): Token; - function createToken(token: TKind) { - Debug.assert(token >= SyntaxKind.FirstToken && token <= SyntaxKind.LastToken, "Invalid token"); - Debug.assert(token <= SyntaxKind.FirstTemplateToken || token >= SyntaxKind.LastTemplateToken, "Invalid token. Use 'createTemplateLiteralLikeNode' to create template literals."); - Debug.assert(token <= SyntaxKind.FirstLiteralToken || token >= SyntaxKind.LastLiteralToken, "Invalid token. Use 'createLiteralLikeNode' to create literals."); - Debug.assert(token !== SyntaxKind.Identifier, "Invalid token. Use 'createIdentifier' to create identifiers"); - const node = createBaseToken>(token); - let transformFlags = TransformFlags.None; - switch (token) { - case SyntaxKind.AsyncKeyword: - // 'async' modifier is ES2017 (async functions) or ES2018 (async generators) - transformFlags = - TransformFlags.ContainsES2017 | - TransformFlags.ContainsES2018; - break; + // @api + function getGeneratedPrivateNameForNode(node: Node, prefix?: string | GeneratedNamePart, suffix?: string): PrivateIdentifier { + const text = isMemberName(node) ? formatGeneratedName(/*privateName*/ true, prefix, node, suffix, idText) : + `#generated@${getNodeId(node)}`; + const flags = prefix || suffix ? GeneratedIdentifierFlags.Optimistic : GeneratedIdentifierFlags.None; + const name = createBaseGeneratedPrivateIdentifier(text, GeneratedIdentifierFlags.Node | flags, prefix, suffix); + name.original = node; + return name; + } - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.AbstractKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.AnyKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.InKeyword: - case SyntaxKind.OutKeyword: - case SyntaxKind.OverrideKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.UnknownKeyword: - case SyntaxKind.UndefinedKeyword: // `undefined` is an Identifier in the expression case. - transformFlags = TransformFlags.ContainsTypeScript; - break; - case SyntaxKind.SuperKeyword: - transformFlags = TransformFlags.ContainsES2015 | TransformFlags.ContainsLexicalSuper; - break; - case SyntaxKind.StaticKeyword: - transformFlags = TransformFlags.ContainsES2015; - break; - case SyntaxKind.AccessorKeyword: - transformFlags = TransformFlags.ContainsClassFields; - break; - case SyntaxKind.ThisKeyword: - // 'this' indicates a lexical 'this' - transformFlags = TransformFlags.ContainsLexicalThis; - break; - } - if (transformFlags) { - node.transformFlags |= transformFlags; - } - return node; - } + // + // Punctuation + // - // - // Reserved words - // + function createBaseToken(kind: T["kind"]) { + return baseFactory.createBaseTokenNode(kind) as Mutable; + } - // @api - function createSuper() { - return createToken(SyntaxKind.SuperKeyword); - } + // @api + function createToken(token: SyntaxKind.SuperKeyword): SuperExpression; + function createToken(token: SyntaxKind.ThisKeyword): ThisExpression; + function createToken(token: SyntaxKind.NullKeyword): NullLiteral; + function createToken(token: SyntaxKind.TrueKeyword): TrueLiteral; + function createToken(token: SyntaxKind.FalseKeyword): FalseLiteral; + function createToken(token: TKind): PunctuationToken; + function createToken(token: TKind): KeywordTypeNode; + function createToken(token: TKind): ModifierToken; + function createToken(token: TKind): KeywordToken; + function createToken(token: TKind): Token; + function createToken(token: TKind): Token; + function createToken(token: TKind) { + Debug.assert(token >= SyntaxKind.FirstToken && token <= SyntaxKind.LastToken, "Invalid token"); + Debug.assert(token <= SyntaxKind.FirstTemplateToken || token >= SyntaxKind.LastTemplateToken, "Invalid token. Use 'createTemplateLiteralLikeNode' to create template literals."); + Debug.assert(token <= SyntaxKind.FirstLiteralToken || token >= SyntaxKind.LastLiteralToken, "Invalid token. Use 'createLiteralLikeNode' to create literals."); + Debug.assert(token !== SyntaxKind.Identifier, "Invalid token. Use 'createIdentifier' to create identifiers"); + const node = createBaseToken>(token); + let transformFlags = TransformFlags.None; + switch (token) { + case SyntaxKind.AsyncKeyword: + // 'async' modifier is ES2017 (async functions) or ES2018 (async generators) + transformFlags = + TransformFlags.ContainsES2017 | + TransformFlags.ContainsES2018; + break; - // @api - function createThis() { - return createToken(SyntaxKind.ThisKeyword); + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.AbstractKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.ConstKeyword: + case SyntaxKind.AnyKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.InKeyword: + case SyntaxKind.OutKeyword: + case SyntaxKind.OverrideKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.UnknownKeyword: + case SyntaxKind.UndefinedKeyword: // `undefined` is an Identifier in the expression case. + transformFlags = TransformFlags.ContainsTypeScript; + break; + case SyntaxKind.SuperKeyword: + transformFlags = TransformFlags.ContainsES2015 | TransformFlags.ContainsLexicalSuper; + break; + case SyntaxKind.StaticKeyword: + transformFlags = TransformFlags.ContainsES2015; + break; + case SyntaxKind.AccessorKeyword: + transformFlags = TransformFlags.ContainsClassFields; + break; + case SyntaxKind.ThisKeyword: + // 'this' indicates a lexical 'this' + transformFlags = TransformFlags.ContainsLexicalThis; + break; } - - // @api - function createNull() { - return createToken(SyntaxKind.NullKeyword); + if (transformFlags) { + node.transformFlags |= transformFlags; } + return node; + } - // @api - function createTrue() { - return createToken(SyntaxKind.TrueKeyword); - } + // + // Reserved words + // - // @api - function createFalse() { - return createToken(SyntaxKind.FalseKeyword); - } + // @api + function createSuper() { + return createToken(SyntaxKind.SuperKeyword); + } - // - // Modifiers - // + // @api + function createThis() { + return createToken(SyntaxKind.ThisKeyword); + } - // @api - function createModifier(kind: T) { - return createToken(kind); - } + // @api + function createNull() { + return createToken(SyntaxKind.NullKeyword); + } - // @api - function createModifiersFromModifierFlags(flags: ModifierFlags) { - const result: Modifier[] = []; - if (flags & ModifierFlags.Export) result.push(createModifier(SyntaxKind.ExportKeyword)); - if (flags & ModifierFlags.Ambient) result.push(createModifier(SyntaxKind.DeclareKeyword)); - if (flags & ModifierFlags.Default) result.push(createModifier(SyntaxKind.DefaultKeyword)); - if (flags & ModifierFlags.Const) result.push(createModifier(SyntaxKind.ConstKeyword)); - if (flags & ModifierFlags.Public) result.push(createModifier(SyntaxKind.PublicKeyword)); - if (flags & ModifierFlags.Private) result.push(createModifier(SyntaxKind.PrivateKeyword)); - if (flags & ModifierFlags.Protected) result.push(createModifier(SyntaxKind.ProtectedKeyword)); - if (flags & ModifierFlags.Abstract) result.push(createModifier(SyntaxKind.AbstractKeyword)); - if (flags & ModifierFlags.Static) result.push(createModifier(SyntaxKind.StaticKeyword)); - if (flags & ModifierFlags.Override) result.push(createModifier(SyntaxKind.OverrideKeyword)); - if (flags & ModifierFlags.Readonly) result.push(createModifier(SyntaxKind.ReadonlyKeyword)); - if (flags & ModifierFlags.Accessor) result.push(createModifier(SyntaxKind.AccessorKeyword)); - if (flags & ModifierFlags.Async) result.push(createModifier(SyntaxKind.AsyncKeyword)); - if (flags & ModifierFlags.In) result.push(createModifier(SyntaxKind.InKeyword)); - if (flags & ModifierFlags.Out) result.push(createModifier(SyntaxKind.OutKeyword)); - return result.length ? result : undefined; - } + // @api + function createTrue() { + return createToken(SyntaxKind.TrueKeyword); + } - // - // Names - // + // @api + function createFalse() { + return createToken(SyntaxKind.FalseKeyword); + } - // @api - function createQualifiedName(left: EntityName, right: string | Identifier) { - const node = createBaseNode(SyntaxKind.QualifiedName); - node.left = left; - node.right = asName(right); - node.transformFlags |= - propagateChildFlags(node.left) | - propagateIdentifierNameFlags(node.right); - return node; - } + // + // Modifiers + // - // @api - function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier) { - return node.left !== left - || node.right !== right - ? update(createQualifiedName(left, right), node) - : node; - } + // @api + function createModifier(kind: T) { + return createToken(kind); + } - // @api - function createComputedPropertyName(expression: Expression) { - const node = createBaseNode(SyntaxKind.ComputedPropertyName); - node.expression = parenthesizerRules().parenthesizeExpressionOfComputedPropertyName(expression); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsES2015 | - TransformFlags.ContainsComputedPropertyName; - return node; - } + // @api + function createModifiersFromModifierFlags(flags: ModifierFlags) { + const result: Modifier[] = []; + if (flags & ModifierFlags.Export) result.push(createModifier(SyntaxKind.ExportKeyword)); + if (flags & ModifierFlags.Ambient) result.push(createModifier(SyntaxKind.DeclareKeyword)); + if (flags & ModifierFlags.Default) result.push(createModifier(SyntaxKind.DefaultKeyword)); + if (flags & ModifierFlags.Const) result.push(createModifier(SyntaxKind.ConstKeyword)); + if (flags & ModifierFlags.Public) result.push(createModifier(SyntaxKind.PublicKeyword)); + if (flags & ModifierFlags.Private) result.push(createModifier(SyntaxKind.PrivateKeyword)); + if (flags & ModifierFlags.Protected) result.push(createModifier(SyntaxKind.ProtectedKeyword)); + if (flags & ModifierFlags.Abstract) result.push(createModifier(SyntaxKind.AbstractKeyword)); + if (flags & ModifierFlags.Static) result.push(createModifier(SyntaxKind.StaticKeyword)); + if (flags & ModifierFlags.Override) result.push(createModifier(SyntaxKind.OverrideKeyword)); + if (flags & ModifierFlags.Readonly) result.push(createModifier(SyntaxKind.ReadonlyKeyword)); + if (flags & ModifierFlags.Accessor) result.push(createModifier(SyntaxKind.AccessorKeyword)); + if (flags & ModifierFlags.Async) result.push(createModifier(SyntaxKind.AsyncKeyword)); + if (flags & ModifierFlags.In) result.push(createModifier(SyntaxKind.InKeyword)); + if (flags & ModifierFlags.Out) result.push(createModifier(SyntaxKind.OutKeyword)); + return result.length ? result : undefined; + } - // @api - function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression) { - return node.expression !== expression - ? update(createComputedPropertyName(expression), node) - : node; - } + // + // Names + // + + // @api + function createQualifiedName(left: EntityName, right: string | Identifier) { + const node = createBaseNode(SyntaxKind.QualifiedName); + node.left = left; + node.right = asName(right); + node.transformFlags |= + propagateChildFlags(node.left) | + propagateIdentifierNameFlags(node.right); + return node; + } - // - // Signature elements - // + // @api + function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier) { + return node.left !== left + || node.right !== right + ? update(createQualifiedName(left, right), node) + : node; + } - // @api - function createTypeParameterDeclaration(modifiers: readonly Modifier[] | undefined, name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode): TypeParameterDeclaration { - const node = createBaseNamedDeclaration( - SyntaxKind.TypeParameter, - modifiers, - name - ); - node.constraint = constraint; - node.default = defaultType; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createComputedPropertyName(expression: Expression) { + const node = createBaseNode(SyntaxKind.ComputedPropertyName); + node.expression = parenthesizerRules().parenthesizeExpressionOfComputedPropertyName(expression); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsES2015 | + TransformFlags.ContainsComputedPropertyName; + return node; + } - // @api - function updateTypeParameterDeclaration(node: TypeParameterDeclaration, modifiers: readonly Modifier[] | undefined, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration { - return node.modifiers !== modifiers - || node.name !== name - || node.constraint !== constraint - || node.default !== defaultType - ? update(createTypeParameterDeclaration(modifiers, name, constraint, defaultType), node) - : node; - } + // @api + function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression) { + return node.expression !== expression + ? update(createComputedPropertyName(expression), node) + : node; + } - // @api - function createParameterDeclaration( - modifiers: readonly ModifierLike[] | undefined, - dotDotDotToken: DotDotDotToken | undefined, - name: string | BindingName, - questionToken?: QuestionToken, - type?: TypeNode, - initializer?: Expression - ) { - const node = createBaseVariableLikeDeclaration( - SyntaxKind.Parameter, - modifiers, - name, - type, - initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) - ); - node.dotDotDotToken = dotDotDotToken; - node.questionToken = questionToken; - if (isThisIdentifier(node.name)) { - node.transformFlags = TransformFlags.ContainsTypeScript; - } - else { - node.transformFlags |= - propagateChildFlags(node.dotDotDotToken) | - propagateChildFlags(node.questionToken); - if (questionToken) node.transformFlags |= TransformFlags.ContainsTypeScript; - if (modifiersToFlags(node.modifiers) & ModifierFlags.ParameterPropertyModifier) node.transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; - if (initializer || dotDotDotToken) node.transformFlags |= TransformFlags.ContainsES2015; - } - return node; - } + // + // Signature elements + // + + // @api + function createTypeParameterDeclaration(modifiers: readonly Modifier[] | undefined, name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode): TypeParameterDeclaration { + const node = createBaseNamedDeclaration( + SyntaxKind.TypeParameter, + modifiers, + name + ); + node.constraint = constraint; + node.default = defaultType; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateParameterDeclaration( - node: ParameterDeclaration, - modifiers: readonly ModifierLike[] | undefined, - dotDotDotToken: DotDotDotToken | undefined, - name: string | BindingName, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) { - return node.modifiers !== modifiers - || node.dotDotDotToken !== dotDotDotToken - || node.name !== name - || node.questionToken !== questionToken - || node.type !== type - || node.initializer !== initializer - ? update(createParameterDeclaration(modifiers, dotDotDotToken, name, questionToken, type, initializer), node) - : node; - } + // @api + function updateTypeParameterDeclaration(node: TypeParameterDeclaration, modifiers: readonly Modifier[] | undefined, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration { + return node.modifiers !== modifiers + || node.name !== name + || node.constraint !== constraint + || node.default !== defaultType + ? update(createTypeParameterDeclaration(modifiers, name, constraint, defaultType), node) + : node; + } - // @api - function createDecorator(expression: Expression) { - const node = createBaseNode(SyntaxKind.Decorator); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); + // @api + function createParameterDeclaration( + modifiers: readonly ModifierLike[] | undefined, + dotDotDotToken: DotDotDotToken | undefined, + name: string | BindingName, + questionToken?: QuestionToken, + type?: TypeNode, + initializer?: Expression + ) { + const node = createBaseVariableLikeDeclaration( + SyntaxKind.Parameter, + modifiers, + name, + type, + initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) + ); + node.dotDotDotToken = dotDotDotToken; + node.questionToken = questionToken; + if (isThisIdentifier(node.name)) { + node.transformFlags = TransformFlags.ContainsTypeScript; + } + else { node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsTypeScript | - TransformFlags.ContainsTypeScriptClassSyntax | - TransformFlags.ContainsDecorators; - return node; + propagateChildFlags(node.dotDotDotToken) | + propagateChildFlags(node.questionToken); + if (questionToken) node.transformFlags |= TransformFlags.ContainsTypeScript; + if (modifiersToFlags(node.modifiers) & ModifierFlags.ParameterPropertyModifier) node.transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; + if (initializer || dotDotDotToken) node.transformFlags |= TransformFlags.ContainsES2015; } + return node; + } - // @api - function updateDecorator(node: Decorator, expression: Expression) { - return node.expression !== expression - ? update(createDecorator(expression), node) - : node; - } + // @api + function updateParameterDeclaration( + node: ParameterDeclaration, + modifiers: readonly ModifierLike[] | undefined, + dotDotDotToken: DotDotDotToken | undefined, + name: string | BindingName, + questionToken: QuestionToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) { + return node.modifiers !== modifiers + || node.dotDotDotToken !== dotDotDotToken + || node.name !== name + || node.questionToken !== questionToken + || node.type !== type + || node.initializer !== initializer + ? update(createParameterDeclaration(modifiers, dotDotDotToken, name, questionToken, type, initializer), node) + : node; + } - // - // Type Elements - // + // @api + function createDecorator(expression: Expression) { + const node = createBaseNode(SyntaxKind.Decorator); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsTypeScript | + TransformFlags.ContainsTypeScriptClassSyntax | + TransformFlags.ContainsDecorators; + return node; + } - // @api - function createPropertySignature( - modifiers: readonly ModifierLike[] | undefined, - name: PropertyName | string, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined - ): PropertySignature { - const node = createBaseNamedDeclaration( - SyntaxKind.PropertySignature, - modifiers, - name - ); - node.type = type; - node.questionToken = questionToken; - node.transformFlags = TransformFlags.ContainsTypeScript; + // @api + function updateDecorator(node: Decorator, expression: Expression) { + return node.expression !== expression + ? update(createDecorator(expression), node) + : node; + } - // The following properties are used only to report grammar errors - node.initializer = undefined; - return node; - } + // + // Type Elements + // + + // @api + function createPropertySignature( + modifiers: readonly ModifierLike[] | undefined, + name: PropertyName | string, + questionToken: QuestionToken | undefined, + type: TypeNode | undefined + ): PropertySignature { + const node = createBaseNamedDeclaration( + SyntaxKind.PropertySignature, + modifiers, + name + ); + node.type = type; + node.questionToken = questionToken; + node.transformFlags = TransformFlags.ContainsTypeScript; + + // The following properties are used only to report grammar errors + node.initializer = undefined; + return node; + } - // @api - function updatePropertySignature( - node: PropertySignature, - modifiers: readonly ModifierLike[] | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - type: TypeNode | undefined - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.questionToken !== questionToken - || node.type !== type - ? finishUpdatePropertySignature(createPropertySignature(modifiers, name, questionToken, type), node) - : node; - } + // @api + function updatePropertySignature( + node: PropertySignature, + modifiers: readonly ModifierLike[] | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined, + type: TypeNode | undefined + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.questionToken !== questionToken + || node.type !== type + ? finishUpdatePropertySignature(createPropertySignature(modifiers, name, questionToken, type), node) + : node; + } - function finishUpdatePropertySignature(updated: Mutable, original: PropertySignature) { - if (updated !== original) { - // copy children used only for error reporting - updated.initializer = original.initializer; - } - return update(updated, original); + function finishUpdatePropertySignature(updated: Mutable, original: PropertySignature) { + if (updated !== original) { + // copy children used only for error reporting + updated.initializer = original.initializer; } + return update(updated, original); + } - // @api - function createPropertyDeclaration( - modifiers: readonly ModifierLike[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) { - const node = createBaseVariableLikeDeclaration( - SyntaxKind.PropertyDeclaration, - modifiers, - name, - type, - initializer - ); - node.questionToken = questionOrExclamationToken && isQuestionToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined; - node.exclamationToken = questionOrExclamationToken && isExclamationToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined; - node.transformFlags |= - propagateChildFlags(node.questionToken) | - propagateChildFlags(node.exclamationToken) | - TransformFlags.ContainsClassFields; - if (isComputedPropertyName(node.name) || (hasStaticModifier(node) && node.initializer)) { - node.transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; - } - if (questionOrExclamationToken || modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - return node; + // @api + function createPropertyDeclaration( + modifiers: readonly ModifierLike[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) { + const node = createBaseVariableLikeDeclaration( + SyntaxKind.PropertyDeclaration, + modifiers, + name, + type, + initializer + ); + node.questionToken = questionOrExclamationToken && isQuestionToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined; + node.exclamationToken = questionOrExclamationToken && isExclamationToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined; + node.transformFlags |= + propagateChildFlags(node.questionToken) | + propagateChildFlags(node.exclamationToken) | + TransformFlags.ContainsClassFields; + if (isComputedPropertyName(node.name) || (hasStaticModifier(node) && node.initializer)) { + node.transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; + } + if (questionOrExclamationToken || modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { + node.transformFlags |= TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function updatePropertyDeclaration( - node: PropertyDeclaration, - modifiers: readonly ModifierLike[] | undefined, - name: string | PropertyName, - questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, - type: TypeNode | undefined, - initializer: Expression | undefined - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.questionToken !== (questionOrExclamationToken !== undefined && isQuestionToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined) - || node.exclamationToken !== (questionOrExclamationToken !== undefined && isExclamationToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined) - || node.type !== type - || node.initializer !== initializer - ? update(createPropertyDeclaration(modifiers, name, questionOrExclamationToken, type, initializer), node) - : node; - } + // @api + function updatePropertyDeclaration( + node: PropertyDeclaration, + modifiers: readonly ModifierLike[] | undefined, + name: string | PropertyName, + questionOrExclamationToken: QuestionToken | ExclamationToken | undefined, + type: TypeNode | undefined, + initializer: Expression | undefined + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.questionToken !== (questionOrExclamationToken !== undefined && isQuestionToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined) + || node.exclamationToken !== (questionOrExclamationToken !== undefined && isExclamationToken(questionOrExclamationToken) ? questionOrExclamationToken : undefined) + || node.type !== type + || node.initializer !== initializer + ? update(createPropertyDeclaration(modifiers, name, questionOrExclamationToken, type, initializer), node) + : node; + } - // @api - function createMethodSignature( - modifiers: readonly ModifierLike[] | undefined, - name: string | PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ) { - const node = createBaseSignatureDeclaration( - SyntaxKind.MethodSignature, - modifiers, - name, - typeParameters, - parameters, - type - ); - node.questionToken = questionToken; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createMethodSignature( + modifiers: readonly ModifierLike[] | undefined, + name: string | PropertyName, + questionToken: QuestionToken | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ) { + const node = createBaseSignatureDeclaration( + SyntaxKind.MethodSignature, + modifiers, + name, + typeParameters, + parameters, + type + ); + node.questionToken = questionToken; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateMethodSignature( - node: MethodSignature, - modifiers: readonly ModifierLike[] | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.questionToken !== questionToken - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? finishUpdateBaseSignatureDeclaration(createMethodSignature(modifiers, name, questionToken, typeParameters, parameters, type), node) - : node; - } + // @api + function updateMethodSignature( + node: MethodSignature, + modifiers: readonly ModifierLike[] | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.questionToken !== questionToken + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? finishUpdateBaseSignatureDeclaration(createMethodSignature(modifiers, name, questionToken, typeParameters, parameters, type), node) + : node; + } - // @api - function createMethodDeclaration( - modifiers: readonly ModifierLike[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.MethodDeclaration, - modifiers, - name, - typeParameters, - parameters, - type, - body - ); - node.asteriskToken = asteriskToken; - node.questionToken = questionToken; - node.transformFlags |= - propagateChildFlags(node.asteriskToken) | - propagateChildFlags(node.questionToken) | - TransformFlags.ContainsES2015; - if (questionToken) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { - if (asteriskToken) { - node.transformFlags |= TransformFlags.ContainsES2018; - } - else { - node.transformFlags |= TransformFlags.ContainsES2017; - } + // @api + function createMethodDeclaration( + modifiers: readonly ModifierLike[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: string | PropertyName, + questionToken: QuestionToken | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.MethodDeclaration, + modifiers, + name, + typeParameters, + parameters, + type, + body + ); + node.asteriskToken = asteriskToken; + node.questionToken = questionToken; + node.transformFlags |= + propagateChildFlags(node.asteriskToken) | + propagateChildFlags(node.questionToken) | + TransformFlags.ContainsES2015; + if (questionToken) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { + if (asteriskToken) { + node.transformFlags |= TransformFlags.ContainsES2018; } - else if (asteriskToken) { - node.transformFlags |= TransformFlags.ContainsGenerator; + else { + node.transformFlags |= TransformFlags.ContainsES2017; } - - // The following properties are used only to report grammar errors - node.exclamationToken = undefined; - return node; } - - // @api - function updateMethodDeclaration( - node: MethodDeclaration, - modifiers: readonly ModifierLike[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - return node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.name !== name - || node.questionToken !== questionToken - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? finishUpdateMethodDeclaration(createMethodDeclaration(modifiers, asteriskToken, name, questionToken, typeParameters, parameters, type, body), node) - : node; + else if (asteriskToken) { + node.transformFlags |= TransformFlags.ContainsGenerator; } - function finishUpdateMethodDeclaration(updated: Mutable, original: MethodDeclaration) { - if (updated !== original) { - updated.exclamationToken = original.exclamationToken; - } - return update(updated, original); - } + // The following properties are used only to report grammar errors + node.exclamationToken = undefined; + return node; + } - // @api - function createClassStaticBlockDeclaration( - body: Block - ): ClassStaticBlockDeclaration { - const node = createBaseGenericNamedDeclaration( - SyntaxKind.ClassStaticBlockDeclaration, - /*modifiers*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined - ); - node.body = body; - node.transformFlags = propagateChildFlags(body) | TransformFlags.ContainsClassFields; + // @api + function updateMethodDeclaration( + node: MethodDeclaration, + modifiers: readonly ModifierLike[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + return node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.name !== name + || node.questionToken !== questionToken + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? finishUpdateMethodDeclaration(createMethodDeclaration(modifiers, asteriskToken, name, questionToken, typeParameters, parameters, type, body), node) + : node; + } - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - node.modifiers = undefined; - return node; + function finishUpdateMethodDeclaration(updated: Mutable, original: MethodDeclaration) { + if (updated !== original) { + updated.exclamationToken = original.exclamationToken; } + return update(updated, original); + } - // @api - function updateClassStaticBlockDeclaration( - node: ClassStaticBlockDeclaration, - body: Block - ): ClassStaticBlockDeclaration { - return node.body !== body - ? finishUpdateClassStaticBlockDeclaration(createClassStaticBlockDeclaration(body), node) - : node; - } + // @api + function createClassStaticBlockDeclaration( + body: Block + ): ClassStaticBlockDeclaration { + const node = createBaseGenericNamedDeclaration( + SyntaxKind.ClassStaticBlockDeclaration, + /*modifiers*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined + ); + node.body = body; + node.transformFlags = propagateChildFlags(body) | TransformFlags.ContainsClassFields; + + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + node.modifiers = undefined; + return node; + } - function finishUpdateClassStaticBlockDeclaration(updated: Mutable, original: ClassStaticBlockDeclaration) { - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - updated.modifiers = original.modifiers; - } - return update(updated, original); + // @api + function updateClassStaticBlockDeclaration( + node: ClassStaticBlockDeclaration, + body: Block + ): ClassStaticBlockDeclaration { + return node.body !== body + ? finishUpdateClassStaticBlockDeclaration(createClassStaticBlockDeclaration(body), node) + : node; + } + + function finishUpdateClassStaticBlockDeclaration(updated: Mutable, original: ClassStaticBlockDeclaration) { + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; + updated.modifiers = original.modifiers; } + return update(updated, original); + } - // @api - function createConstructorDeclaration( - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - body: Block | undefined - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.Constructor, - modifiers, - /*name*/ undefined, - /*typeParameters*/ undefined, - parameters, - /*type*/ undefined, - body - ); - node.transformFlags |= TransformFlags.ContainsES2015; + // @api + function createConstructorDeclaration( + modifiers: readonly Modifier[] | undefined, + parameters: readonly ParameterDeclaration[], + body: Block | undefined + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.Constructor, + modifiers, + /*name*/ undefined, + /*typeParameters*/ undefined, + parameters, + /*type*/ undefined, + body + ); + node.transformFlags |= TransformFlags.ContainsES2015; + + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + node.typeParameters = undefined; + node.type = undefined; + return node; + } - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - node.typeParameters = undefined; - node.type = undefined; - return node; - } + // @api + function updateConstructorDeclaration( + node: ConstructorDeclaration, + modifiers: readonly Modifier[] | undefined, + parameters: readonly ParameterDeclaration[], + body: Block | undefined + ) { + return node.modifiers !== modifiers + || node.parameters !== parameters + || node.body !== body + ? finishUpdateConstructorDeclaration(createConstructorDeclaration(modifiers, parameters, body), node) + : node; + } - // @api - function updateConstructorDeclaration( - node: ConstructorDeclaration, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - body: Block | undefined - ) { - return node.modifiers !== modifiers - || node.parameters !== parameters - || node.body !== body - ? finishUpdateConstructorDeclaration(createConstructorDeclaration(modifiers, parameters, body), node) - : node; + function finishUpdateConstructorDeclaration(updated: Mutable, original: ConstructorDeclaration) { + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; + updated.typeParameters = original.typeParameters; + updated.type = original.type; } + return finishUpdateBaseSignatureDeclaration(updated, original); + } - function finishUpdateConstructorDeclaration(updated: Mutable, original: ConstructorDeclaration) { - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - updated.typeParameters = original.typeParameters; - updated.type = original.type; - } - return finishUpdateBaseSignatureDeclaration(updated, original); - } + // @api + function createGetAccessorDeclaration( + modifiers: readonly ModifierLike[] | undefined, + name: string | PropertyName, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.GetAccessor, + modifiers, + name, + /*typeParameters*/ undefined, + parameters, + type, + body + ); + + // The following properties are used only to report grammar errors + node.typeParameters = undefined; + return node; + } - // @api - function createGetAccessorDeclaration( - modifiers: readonly ModifierLike[] | undefined, - name: string | PropertyName, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.GetAccessor, - modifiers, - name, - /*typeParameters*/ undefined, - parameters, - type, - body - ); + // @api + function updateGetAccessorDeclaration( + node: GetAccessorDeclaration, + modifiers: readonly ModifierLike[] | undefined, + name: PropertyName, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? finishUpdateGetAccessorDeclaration(createGetAccessorDeclaration(modifiers, name, parameters, type, body), node) + : node; + } - // The following properties are used only to report grammar errors - node.typeParameters = undefined; - return node; + function finishUpdateGetAccessorDeclaration(updated: Mutable, original: GetAccessorDeclaration) { + if (updated !== original) { + updated.typeParameters = original.typeParameters; } + return finishUpdateBaseSignatureDeclaration(updated, original); + } - // @api - function updateGetAccessorDeclaration( - node: GetAccessorDeclaration, - modifiers: readonly ModifierLike[] | undefined, - name: PropertyName, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? finishUpdateGetAccessorDeclaration(createGetAccessorDeclaration(modifiers, name, parameters, type, body), node) - : node; - } + // @api + function createSetAccessorDeclaration( + modifiers: readonly ModifierLike[] | undefined, + name: string | PropertyName, + parameters: readonly ParameterDeclaration[], + body: Block | undefined + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.SetAccessor, + modifiers, + name, + /*typeParameters*/ undefined, + parameters, + /*type*/ undefined, + body + ); + + // The following properties are used only to report grammar errors + node.typeParameters = undefined; + node.type = undefined; + return node; + } - function finishUpdateGetAccessorDeclaration(updated: Mutable, original: GetAccessorDeclaration) { - if (updated !== original) { - updated.typeParameters = original.typeParameters; - } - return finishUpdateBaseSignatureDeclaration(updated, original); + // @api + function updateSetAccessorDeclaration( + node: SetAccessorDeclaration, + modifiers: readonly ModifierLike[] | undefined, + name: PropertyName, + parameters: readonly ParameterDeclaration[], + body: Block | undefined + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.parameters !== parameters + || node.body !== body + ? finishUpdateSetAccessorDeclaration(createSetAccessorDeclaration(modifiers, name, parameters, body), node) + : node; + } + + function finishUpdateSetAccessorDeclaration(updated: Mutable, original: SetAccessorDeclaration) { + if (updated !== original) { + updated.typeParameters = original.typeParameters; + updated.type = original.type; } + return finishUpdateBaseSignatureDeclaration(updated, original); + } - // @api - function createSetAccessorDeclaration( - modifiers: readonly ModifierLike[] | undefined, - name: string | PropertyName, - parameters: readonly ParameterDeclaration[], - body: Block | undefined - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.SetAccessor, - modifiers, - name, - /*typeParameters*/ undefined, - parameters, - /*type*/ undefined, - body - ); + // @api + function createCallSignature( + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): CallSignatureDeclaration { + const node = createBaseSignatureDeclaration( + SyntaxKind.CallSignature, + /*modifiers*/ undefined, + /*name*/ undefined, + typeParameters, + parameters, + type + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // The following properties are used only to report grammar errors - node.typeParameters = undefined; - node.type = undefined; - return node; - } + // @api + function updateCallSignature( + node: CallSignatureDeclaration, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? finishUpdateBaseSignatureDeclaration(createCallSignature(typeParameters, parameters, type), node) + : node; + } - // @api - function updateSetAccessorDeclaration( - node: SetAccessorDeclaration, - modifiers: readonly ModifierLike[] | undefined, - name: PropertyName, - parameters: readonly ParameterDeclaration[], - body: Block | undefined - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.parameters !== parameters - || node.body !== body - ? finishUpdateSetAccessorDeclaration(createSetAccessorDeclaration(modifiers, name, parameters, body), node) - : node; - } + // @api + function createConstructSignature( + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): ConstructSignatureDeclaration { + const node = createBaseSignatureDeclaration( + SyntaxKind.ConstructSignature, + /*modifiers*/ undefined, + /*name*/ undefined, + typeParameters, + parameters, + type + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - function finishUpdateSetAccessorDeclaration(updated: Mutable, original: SetAccessorDeclaration) { - if (updated !== original) { - updated.typeParameters = original.typeParameters; - updated.type = original.type; - } - return finishUpdateBaseSignatureDeclaration(updated, original); - } + // @api + function updateConstructSignature( + node: ConstructSignatureDeclaration, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? finishUpdateBaseSignatureDeclaration(createConstructSignature(typeParameters, parameters, type), node) + : node; + } - // @api - function createCallSignature( - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): CallSignatureDeclaration { - const node = createBaseSignatureDeclaration( - SyntaxKind.CallSignature, - /*modifiers*/ undefined, - /*name*/ undefined, - typeParameters, - parameters, - type - ); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createIndexSignature( + modifiers: readonly Modifier[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): IndexSignatureDeclaration { + const node = createBaseSignatureDeclaration( + SyntaxKind.IndexSignature, + modifiers, + /*name*/ undefined, + /*typeParameters*/ undefined, + parameters, + type + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateCallSignature( - node: CallSignatureDeclaration, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? finishUpdateBaseSignatureDeclaration(createCallSignature(typeParameters, parameters, type), node) - : node; - } + // @api + function updateIndexSignature( + node: IndexSignatureDeclaration, + modifiers: readonly Modifier[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode + ) { + return node.parameters !== parameters + || node.type !== type + || node.modifiers !== modifiers + ? finishUpdateBaseSignatureDeclaration(createIndexSignature(modifiers, parameters, type), node) + : node; + } - // @api - function createConstructSignature( - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): ConstructSignatureDeclaration { - const node = createBaseSignatureDeclaration( - SyntaxKind.ConstructSignature, - /*modifiers*/ undefined, - /*name*/ undefined, - typeParameters, - parameters, - type - ); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createTemplateLiteralTypeSpan(type: TypeNode, literal: TemplateMiddle | TemplateTail) { + const node = createBaseNode(SyntaxKind.TemplateLiteralTypeSpan); + node.type = type; + node.literal = literal; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateConstructSignature( - node: ConstructSignatureDeclaration, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? finishUpdateBaseSignatureDeclaration(createConstructSignature(typeParameters, parameters, type), node) - : node; - } + // @api + function updateTemplateLiteralTypeSpan(node: TemplateLiteralTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail) { + return node.type !== type + || node.literal !== literal + ? update(createTemplateLiteralTypeSpan(type, literal), node) + : node; + } - // @api - function createIndexSignature( - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): IndexSignatureDeclaration { - const node = createBaseSignatureDeclaration( - SyntaxKind.IndexSignature, - modifiers, - /*name*/ undefined, - /*typeParameters*/ undefined, - parameters, - type - ); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // + // Types + // - // @api - function updateIndexSignature( - node: IndexSignatureDeclaration, - modifiers: readonly Modifier[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode - ) { - return node.parameters !== parameters - || node.type !== type - || node.modifiers !== modifiers - ? finishUpdateBaseSignatureDeclaration(createIndexSignature(modifiers, parameters, type), node) - : node; - } + // @api + function createKeywordTypeNode(kind: TKind) { + return createToken(kind); + } - // @api - function createTemplateLiteralTypeSpan(type: TypeNode, literal: TemplateMiddle | TemplateTail) { - const node = createBaseNode(SyntaxKind.TemplateLiteralTypeSpan); - node.type = type; - node.literal = literal; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createTypePredicateNode(assertsModifier: AssertsKeyword | undefined, parameterName: Identifier | ThisTypeNode | string, type: TypeNode | undefined) { + const node = createBaseNode(SyntaxKind.TypePredicate); + node.assertsModifier = assertsModifier; + node.parameterName = asName(parameterName); + node.type = type; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateTemplateLiteralTypeSpan(node: TemplateLiteralTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail) { - return node.type !== type - || node.literal !== literal - ? update(createTemplateLiteralTypeSpan(type, literal), node) - : node; - } + // @api + function updateTypePredicateNode(node: TypePredicateNode, assertsModifier: AssertsKeyword | undefined, parameterName: Identifier | ThisTypeNode, type: TypeNode | undefined) { + return node.assertsModifier !== assertsModifier + || node.parameterName !== parameterName + || node.type !== type + ? update(createTypePredicateNode(assertsModifier, parameterName, type), node) + : node; + } - // - // Types - // + // @api + function createTypeReferenceNode(typeName: string | EntityName, typeArguments: readonly TypeNode[] | undefined) { + const node = createBaseNode(SyntaxKind.TypeReference); + node.typeName = asName(typeName); + node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(createNodeArray(typeArguments)); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createKeywordTypeNode(kind: TKind) { - return createToken(kind); - } + // @api + function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray | undefined) { + return node.typeName !== typeName + || node.typeArguments !== typeArguments + ? update(createTypeReferenceNode(typeName, typeArguments), node) + : node; + } - // @api - function createTypePredicateNode(assertsModifier: AssertsKeyword | undefined, parameterName: Identifier | ThisTypeNode | string, type: TypeNode | undefined) { - const node = createBaseNode(SyntaxKind.TypePredicate); - node.assertsModifier = assertsModifier; - node.parameterName = asName(parameterName); - node.type = type; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createFunctionTypeNode( + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): FunctionTypeNode { + const node = createBaseSignatureDeclaration( + SyntaxKind.FunctionType, + /*modifiers*/ undefined, + /*name*/ undefined, + typeParameters, + parameters, + type + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + + // The following properties are used only to report grammar errors + node.modifiers = undefined; + return node; + } - // @api - function updateTypePredicateNode(node: TypePredicateNode, assertsModifier: AssertsKeyword | undefined, parameterName: Identifier | ThisTypeNode, type: TypeNode | undefined) { - return node.assertsModifier !== assertsModifier - || node.parameterName !== parameterName - || node.type !== type - ? update(createTypePredicateNode(assertsModifier, parameterName, type), node) - : node; - } + // @api + function updateFunctionTypeNode( + node: FunctionTypeNode, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? finishUpdateFunctionTypeNode(createFunctionTypeNode(typeParameters, parameters, type), node) + : node; + } - // @api - function createTypeReferenceNode(typeName: string | EntityName, typeArguments: readonly TypeNode[] | undefined) { - const node = createBaseNode(SyntaxKind.TypeReference); - node.typeName = asName(typeName); - node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(createNodeArray(typeArguments)); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; + function finishUpdateFunctionTypeNode(updated: Mutable, original: FunctionTypeNode) { + if (updated !== original) { + updated.modifiers = original.modifiers; } + return finishUpdateBaseSignatureDeclaration(updated, original); + } - // @api - function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray | undefined) { - return node.typeName !== typeName - || node.typeArguments !== typeArguments - ? update(createTypeReferenceNode(typeName, typeArguments), node) - : node; - } + // @api + function createConstructorTypeNode(...args: Parameters) { + return args.length === 4 ? createConstructorTypeNode1(...args) : + args.length === 3 ? createConstructorTypeNode2(...args) : + Debug.fail("Incorrect number of arguments specified."); + } - // @api - function createFunctionTypeNode( - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): FunctionTypeNode { - const node = createBaseSignatureDeclaration( - SyntaxKind.FunctionType, - /*modifiers*/ undefined, - /*name*/ undefined, - typeParameters, - parameters, - type - ); - node.transformFlags = TransformFlags.ContainsTypeScript; + function createConstructorTypeNode1( + modifiers: readonly Modifier[] | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): ConstructorTypeNode { + const node = createBaseSignatureDeclaration( + SyntaxKind.ConstructorType, + modifiers, + /*name*/ undefined, + typeParameters, + parameters, + type + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // The following properties are used only to report grammar errors - node.modifiers = undefined; - return node; - } + /** @deprecated */ + function createConstructorTypeNode2( + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined + ): ConstructorTypeNode { + return createConstructorTypeNode1(/*modifiers*/ undefined, typeParameters, parameters, type); + } - // @api - function updateFunctionTypeNode( - node: FunctionTypeNode, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? finishUpdateFunctionTypeNode(createFunctionTypeNode(typeParameters, parameters, type), node) - : node; - } + // @api + function updateConstructorTypeNode(...args: Parameters) { + return args.length === 5 ? updateConstructorTypeNode1(...args) : + args.length === 4 ? updateConstructorTypeNode2(...args) : + Debug.fail("Incorrect number of arguments specified."); + } - function finishUpdateFunctionTypeNode(updated: Mutable, original: FunctionTypeNode) { - if (updated !== original) { - updated.modifiers = original.modifiers; - } - return finishUpdateBaseSignatureDeclaration(updated, original); - } + function updateConstructorTypeNode1( + node: ConstructorTypeNode, + modifiers: readonly Modifier[] | undefined, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return node.modifiers !== modifiers + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? finishUpdateBaseSignatureDeclaration(createConstructorTypeNode(modifiers, typeParameters, parameters, type), node) + : node; + } - // @api - function createConstructorTypeNode(...args: Parameters) { - return args.length === 4 ? createConstructorTypeNode1(...args) : - args.length === 3 ? createConstructorTypeNode2(...args) : - Debug.fail("Incorrect number of arguments specified."); - } + /** @deprecated */ + function updateConstructorTypeNode2( + node: ConstructorTypeNode, + typeParameters: NodeArray | undefined, + parameters: NodeArray, + type: TypeNode | undefined + ) { + return updateConstructorTypeNode1(node, node.modifiers, typeParameters, parameters, type); + } - function createConstructorTypeNode1( - modifiers: readonly Modifier[] | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): ConstructorTypeNode { - const node = createBaseSignatureDeclaration( - SyntaxKind.ConstructorType, - modifiers, - /*name*/ undefined, - typeParameters, - parameters, - type - ); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createTypeQueryNode(exprName: EntityName, typeArguments?: readonly TypeNode[]) { + const node = createBaseNode(SyntaxKind.TypeQuery); + node.exprName = exprName; + node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - /** @deprecated */ - function createConstructorTypeNode2( - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined - ): ConstructorTypeNode { - return createConstructorTypeNode1(/*modifiers*/ undefined, typeParameters, parameters, type); - } + // @api + function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName, typeArguments?: readonly TypeNode[]) { + return node.exprName !== exprName + || node.typeArguments !== typeArguments + ? update(createTypeQueryNode(exprName, typeArguments), node) + : node; + } - // @api - function updateConstructorTypeNode(...args: Parameters) { - return args.length === 5 ? updateConstructorTypeNode1(...args) : - args.length === 4 ? updateConstructorTypeNode2(...args) : - Debug.fail("Incorrect number of arguments specified."); - } + // @api + function createTypeLiteralNode(members: readonly TypeElement[] | undefined) { + const node = createBaseNode(SyntaxKind.TypeLiteral); + node.members = createNodeArray(members); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - function updateConstructorTypeNode1( - node: ConstructorTypeNode, - modifiers: readonly Modifier[] | undefined, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return node.modifiers !== modifiers - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? finishUpdateBaseSignatureDeclaration(createConstructorTypeNode(modifiers, typeParameters, parameters, type), node) - : node; - } + // @api + function updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray) { + return node.members !== members + ? update(createTypeLiteralNode(members), node) + : node; + } - /** @deprecated */ - function updateConstructorTypeNode2( - node: ConstructorTypeNode, - typeParameters: NodeArray | undefined, - parameters: NodeArray, - type: TypeNode | undefined - ) { - return updateConstructorTypeNode1(node, node.modifiers, typeParameters, parameters, type); - } + // @api + function createArrayTypeNode(elementType: TypeNode) { + const node = createBaseNode(SyntaxKind.ArrayType); + node.elementType = parenthesizerRules().parenthesizeNonArrayTypeOfPostfixType(elementType); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createTypeQueryNode(exprName: EntityName, typeArguments?: readonly TypeNode[]) { - const node = createBaseNode(SyntaxKind.TypeQuery); - node.exprName = exprName; - node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode { + return node.elementType !== elementType + ? update(createArrayTypeNode(elementType), node) + : node; + } - // @api - function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName, typeArguments?: readonly TypeNode[]) { - return node.exprName !== exprName - || node.typeArguments !== typeArguments - ? update(createTypeQueryNode(exprName, typeArguments), node) - : node; - } + // @api + function createTupleTypeNode(elements: readonly (TypeNode | NamedTupleMember)[]) { + const node = createBaseNode(SyntaxKind.TupleType); + node.elements = createNodeArray(parenthesizerRules().parenthesizeElementTypesOfTupleType(elements)); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createTypeLiteralNode(members: readonly TypeElement[] | undefined) { - const node = createBaseNode(SyntaxKind.TypeLiteral); - node.members = createNodeArray(members); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateTupleTypeNode(node: TupleTypeNode, elements: readonly (TypeNode | NamedTupleMember)[]) { + return node.elements !== elements + ? update(createTupleTypeNode(elements), node) + : node; + } - // @api - function updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray) { - return node.members !== members - ? update(createTypeLiteralNode(members), node) - : node; - } + // @api + function createNamedTupleMember(dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode) { + const node = createBaseNode(SyntaxKind.NamedTupleMember); + node.dotDotDotToken = dotDotDotToken; + node.name = name; + node.questionToken = questionToken; + node.type = type; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createArrayTypeNode(elementType: TypeNode) { - const node = createBaseNode(SyntaxKind.ArrayType); - node.elementType = parenthesizerRules().parenthesizeNonArrayTypeOfPostfixType(elementType); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateNamedTupleMember(node: NamedTupleMember, dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode) { + return node.dotDotDotToken !== dotDotDotToken + || node.name !== name + || node.questionToken !== questionToken + || node.type !== type + ? update(createNamedTupleMember(dotDotDotToken, name, questionToken, type), node) + : node; + } - // @api - function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode { - return node.elementType !== elementType - ? update(createArrayTypeNode(elementType), node) - : node; - } + // @api + function createOptionalTypeNode(type: TypeNode) { + const node = createBaseNode(SyntaxKind.OptionalType); + node.type = parenthesizerRules().parenthesizeTypeOfOptionalType(type); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createTupleTypeNode(elements: readonly (TypeNode | NamedTupleMember)[]) { - const node = createBaseNode(SyntaxKind.TupleType); - node.elements = createNodeArray(parenthesizerRules().parenthesizeElementTypesOfTupleType(elements)); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateOptionalTypeNode(node: OptionalTypeNode, type: TypeNode): OptionalTypeNode { + return node.type !== type + ? update(createOptionalTypeNode(type), node) + : node; + } - // @api - function updateTupleTypeNode(node: TupleTypeNode, elements: readonly (TypeNode | NamedTupleMember)[]) { - return node.elements !== elements - ? update(createTupleTypeNode(elements), node) - : node; - } + // @api + function createRestTypeNode(type: TypeNode) { + const node = createBaseNode(SyntaxKind.RestType); + node.type = type; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createNamedTupleMember(dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode) { - const node = createBaseNode(SyntaxKind.NamedTupleMember); - node.dotDotDotToken = dotDotDotToken; - node.name = name; - node.questionToken = questionToken; - node.type = type; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateRestTypeNode(node: RestTypeNode, type: TypeNode): RestTypeNode { + return node.type !== type + ? update(createRestTypeNode(type), node) + : node; + } - // @api - function updateNamedTupleMember(node: NamedTupleMember, dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode) { - return node.dotDotDotToken !== dotDotDotToken - || node.name !== name - || node.questionToken !== questionToken - || node.type !== type - ? update(createNamedTupleMember(dotDotDotToken, name, questionToken, type), node) - : node; - } + function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: readonly TypeNode[], parenthesize: (nodes: readonly TypeNode[]) => readonly TypeNode[]) { + const node = createBaseNode(kind); + node.types = factory.createNodeArray(parenthesize(types)); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createOptionalTypeNode(type: TypeNode) { - const node = createBaseNode(SyntaxKind.OptionalType); - node.type = parenthesizerRules().parenthesizeTypeOfOptionalType(type); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + function updateUnionOrIntersectionTypeNode(node: T, types: NodeArray, parenthesize: (nodes: readonly TypeNode[]) => readonly TypeNode[]): T { + return node.types !== types + ? update(createUnionOrIntersectionTypeNode(node.kind, types, parenthesize) as T, node) + : node; + } - // @api - function updateOptionalTypeNode(node: OptionalTypeNode, type: TypeNode): OptionalTypeNode { - return node.type !== type - ? update(createOptionalTypeNode(type), node) - : node; - } + // @api + function createUnionTypeNode(types: readonly TypeNode[]): UnionTypeNode { + return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, types, parenthesizerRules().parenthesizeConstituentTypesOfUnionType) as UnionTypeNode; + } - // @api - function createRestTypeNode(type: TypeNode) { - const node = createBaseNode(SyntaxKind.RestType); - node.type = type; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateUnionTypeNode(node: UnionTypeNode, types: NodeArray) { + return updateUnionOrIntersectionTypeNode(node, types, parenthesizerRules().parenthesizeConstituentTypesOfUnionType); + } - // @api - function updateRestTypeNode(node: RestTypeNode, type: TypeNode): RestTypeNode { - return node.type !== type - ? update(createRestTypeNode(type), node) - : node; - } + // @api + function createIntersectionTypeNode(types: readonly TypeNode[]): IntersectionTypeNode { + return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, types, parenthesizerRules().parenthesizeConstituentTypesOfIntersectionType) as IntersectionTypeNode; + } - function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: readonly TypeNode[], parenthesize: (nodes: readonly TypeNode[]) => readonly TypeNode[]) { - const node = createBaseNode(kind); - node.types = factory.createNodeArray(parenthesize(types)); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateIntersectionTypeNode(node: IntersectionTypeNode, types: NodeArray) { + return updateUnionOrIntersectionTypeNode(node, types, parenthesizerRules().parenthesizeConstituentTypesOfIntersectionType); + } - function updateUnionOrIntersectionTypeNode(node: T, types: NodeArray, parenthesize: (nodes: readonly TypeNode[]) => readonly TypeNode[]): T { - return node.types !== types - ? update(createUnionOrIntersectionTypeNode(node.kind, types, parenthesize) as T, node) - : node; - } + // @api + function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { + const node = createBaseNode(SyntaxKind.ConditionalType); + node.checkType = parenthesizerRules().parenthesizeCheckTypeOfConditionalType(checkType); + node.extendsType = parenthesizerRules().parenthesizeExtendsTypeOfConditionalType(extendsType); + node.trueType = trueType; + node.falseType = falseType; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createUnionTypeNode(types: readonly TypeNode[]): UnionTypeNode { - return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, types, parenthesizerRules().parenthesizeConstituentTypesOfUnionType) as UnionTypeNode; - } + // @api + function updateConditionalTypeNode(node: ConditionalTypeNode, checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { + return node.checkType !== checkType + || node.extendsType !== extendsType + || node.trueType !== trueType + || node.falseType !== falseType + ? update(createConditionalTypeNode(checkType, extendsType, trueType, falseType), node) + : node; + } - // @api - function updateUnionTypeNode(node: UnionTypeNode, types: NodeArray) { - return updateUnionOrIntersectionTypeNode(node, types, parenthesizerRules().parenthesizeConstituentTypesOfUnionType); - } + // @api + function createInferTypeNode(typeParameter: TypeParameterDeclaration) { + const node = createBaseNode(SyntaxKind.InferType); + node.typeParameter = typeParameter; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createIntersectionTypeNode(types: readonly TypeNode[]): IntersectionTypeNode { - return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, types, parenthesizerRules().parenthesizeConstituentTypesOfIntersectionType) as IntersectionTypeNode; - } + // @api + function updateInferTypeNode(node: InferTypeNode, typeParameter: TypeParameterDeclaration) { + return node.typeParameter !== typeParameter + ? update(createInferTypeNode(typeParameter), node) + : node; + } - // @api - function updateIntersectionTypeNode(node: IntersectionTypeNode, types: NodeArray) { - return updateUnionOrIntersectionTypeNode(node, types, parenthesizerRules().parenthesizeConstituentTypesOfIntersectionType); - } + // @api + function createTemplateLiteralType(head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) { + const node = createBaseNode(SyntaxKind.TemplateLiteralType); + node.head = head; + node.templateSpans = createNodeArray(templateSpans); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { - const node = createBaseNode(SyntaxKind.ConditionalType); - node.checkType = parenthesizerRules().parenthesizeCheckTypeOfConditionalType(checkType); - node.extendsType = parenthesizerRules().parenthesizeExtendsTypeOfConditionalType(extendsType); - node.trueType = trueType; - node.falseType = falseType; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateTemplateLiteralType(node: TemplateLiteralTypeNode, head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) { + return node.head !== head + || node.templateSpans !== templateSpans + ? update(createTemplateLiteralType(head, templateSpans), node) + : node; + } - // @api - function updateConditionalTypeNode(node: ConditionalTypeNode, checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) { - return node.checkType !== checkType - || node.extendsType !== extendsType - || node.trueType !== trueType - || node.falseType !== falseType - ? update(createConditionalTypeNode(checkType, extendsType, trueType, falseType), node) - : node; - } + // @api + function createImportTypeNode( + argument: TypeNode, + assertions?: ImportTypeAssertionContainer, + qualifier?: EntityName, + typeArguments?: readonly TypeNode[], + isTypeOf = false + ): ImportTypeNode { + const node = createBaseNode(SyntaxKind.ImportType); + node.argument = argument; + node.assertions = assertions; + node.qualifier = qualifier; + node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); + node.isTypeOf = isTypeOf; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createInferTypeNode(typeParameter: TypeParameterDeclaration) { - const node = createBaseNode(SyntaxKind.InferType); - node.typeParameter = typeParameter; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateImportTypeNode( + node: ImportTypeNode, + argument: TypeNode, + assertions: ImportTypeAssertionContainer | undefined, + qualifier: EntityName | undefined, + typeArguments: readonly TypeNode[] | undefined, + isTypeOf: boolean = node.isTypeOf + ): ImportTypeNode { + return node.argument !== argument + || node.assertions !== assertions + || node.qualifier !== qualifier + || node.typeArguments !== typeArguments + || node.isTypeOf !== isTypeOf + ? update(createImportTypeNode(argument, assertions, qualifier, typeArguments, isTypeOf), node) + : node; + } - // @api - function updateInferTypeNode(node: InferTypeNode, typeParameter: TypeParameterDeclaration) { - return node.typeParameter !== typeParameter - ? update(createInferTypeNode(typeParameter), node) - : node; - } + // @api + function createParenthesizedType(type: TypeNode) { + const node = createBaseNode(SyntaxKind.ParenthesizedType); + node.type = type; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createTemplateLiteralType(head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) { - const node = createBaseNode(SyntaxKind.TemplateLiteralType); - node.head = head; - node.templateSpans = createNodeArray(templateSpans); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode) { + return node.type !== type + ? update(createParenthesizedType(type), node) + : node; + } - // @api - function updateTemplateLiteralType(node: TemplateLiteralTypeNode, head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) { - return node.head !== head - || node.templateSpans !== templateSpans - ? update(createTemplateLiteralType(head, templateSpans), node) - : node; - } + // @api + function createThisTypeNode() { + const node = createBaseNode(SyntaxKind.ThisType); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createImportTypeNode( - argument: TypeNode, - assertions?: ImportTypeAssertionContainer, - qualifier?: EntityName, - typeArguments?: readonly TypeNode[], - isTypeOf = false - ): ImportTypeNode { - const node = createBaseNode(SyntaxKind.ImportType); - node.argument = argument; - node.assertions = assertions; - node.qualifier = qualifier; - node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); - node.isTypeOf = isTypeOf; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode { + const node = createBaseNode(SyntaxKind.TypeOperator); + node.operator = operator; + node.type = operator === SyntaxKind.ReadonlyKeyword ? + parenthesizerRules().parenthesizeOperandOfReadonlyTypeOperator(type) : + parenthesizerRules().parenthesizeOperandOfTypeOperator(type); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateImportTypeNode( - node: ImportTypeNode, - argument: TypeNode, - assertions: ImportTypeAssertionContainer | undefined, - qualifier: EntityName | undefined, - typeArguments: readonly TypeNode[] | undefined, - isTypeOf: boolean = node.isTypeOf - ): ImportTypeNode { - return node.argument !== argument - || node.assertions !== assertions - || node.qualifier !== qualifier - || node.typeArguments !== typeArguments - || node.isTypeOf !== isTypeOf - ? update(createImportTypeNode(argument, assertions, qualifier, typeArguments, isTypeOf), node) - : node; - } + // @api + function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode) { + return node.type !== type + ? update(createTypeOperatorNode(node.operator, type), node) + : node; + } - // @api - function createParenthesizedType(type: TypeNode) { - const node = createBaseNode(SyntaxKind.ParenthesizedType); - node.type = type; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { + const node = createBaseNode(SyntaxKind.IndexedAccessType); + node.objectType = parenthesizerRules().parenthesizeNonArrayTypeOfPostfixType(objectType); + node.indexType = indexType; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode) { - return node.type !== type - ? update(createParenthesizedType(type), node) - : node; - } + // @api + function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) { + return node.objectType !== objectType + || node.indexType !== indexType + ? update(createIndexedAccessTypeNode(objectType, indexType), node) + : node; + } - // @api - function createThisTypeNode() { - const node = createBaseNode(SyntaxKind.ThisType); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: readonly TypeElement[] | undefined): MappedTypeNode { + const node = createBaseNode(SyntaxKind.MappedType); + node.readonlyToken = readonlyToken; + node.typeParameter = typeParameter; + node.nameType = nameType; + node.questionToken = questionToken; + node.type = type; + node.members = members && createNodeArray(members); + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode { - const node = createBaseNode(SyntaxKind.TypeOperator); - node.operator = operator; - node.type = operator === SyntaxKind.ReadonlyKeyword ? - parenthesizerRules().parenthesizeOperandOfReadonlyTypeOperator(type) : - parenthesizerRules().parenthesizeOperandOfTypeOperator(type); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: readonly TypeElement[] | undefined): MappedTypeNode { + return node.readonlyToken !== readonlyToken + || node.typeParameter !== typeParameter + || node.nameType !== nameType + || node.questionToken !== questionToken + || node.type !== type + || node.members !== members + ? update(createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), node) + : node; + } - // @api - function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode) { - return node.type !== type - ? update(createTypeOperatorNode(node.operator, type), node) - : node; - } + // @api + function createLiteralTypeNode(literal: LiteralTypeNode["literal"]) { + const node = createBaseNode(SyntaxKind.LiteralType); + node.literal = literal; + node.transformFlags = TransformFlags.ContainsTypeScript; + return node; + } - // @api - function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { - const node = createBaseNode(SyntaxKind.IndexedAccessType); - node.objectType = parenthesizerRules().parenthesizeNonArrayTypeOfPostfixType(objectType); - node.indexType = indexType; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]) { + return node.literal !== literal + ? update(createLiteralTypeNode(literal), node) + : node; + } - // @api - function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) { - return node.objectType !== objectType - || node.indexType !== indexType - ? update(createIndexedAccessTypeNode(objectType, indexType), node) - : node; + // + // Binding Patterns + // + + // @api + function createObjectBindingPattern(elements: readonly BindingElement[]) { + const node = createBaseNode(SyntaxKind.ObjectBindingPattern); + node.elements = createNodeArray(elements); + node.transformFlags |= + propagateChildrenFlags(node.elements) | + TransformFlags.ContainsES2015 | + TransformFlags.ContainsBindingPattern; + if (node.transformFlags & TransformFlags.ContainsRestOrSpread) { + node.transformFlags |= + TransformFlags.ContainsES2018 | + TransformFlags.ContainsObjectRestOrSpread; } + return node; + } - // @api - function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: readonly TypeElement[] | undefined): MappedTypeNode { - const node = createBaseNode(SyntaxKind.MappedType); - node.readonlyToken = readonlyToken; - node.typeParameter = typeParameter; - node.nameType = nameType; - node.questionToken = questionToken; - node.type = type; - node.members = members && createNodeArray(members); - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateObjectBindingPattern(node: ObjectBindingPattern, elements: readonly BindingElement[]) { + return node.elements !== elements + ? update(createObjectBindingPattern(elements), node) + : node; + } - // @api - function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: readonly TypeElement[] | undefined): MappedTypeNode { - return node.readonlyToken !== readonlyToken - || node.typeParameter !== typeParameter - || node.nameType !== nameType - || node.questionToken !== questionToken - || node.type !== type - || node.members !== members - ? update(createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), node) - : node; - } + // @api + function createArrayBindingPattern(elements: readonly ArrayBindingElement[]) { + const node = createBaseNode(SyntaxKind.ArrayBindingPattern); + node.elements = createNodeArray(elements); + node.transformFlags |= + propagateChildrenFlags(node.elements) | + TransformFlags.ContainsES2015 | + TransformFlags.ContainsBindingPattern; + return node; + } - // @api - function createLiteralTypeNode(literal: LiteralTypeNode["literal"]) { - const node = createBaseNode(SyntaxKind.LiteralType); - node.literal = literal; - node.transformFlags = TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateArrayBindingPattern(node: ArrayBindingPattern, elements: readonly ArrayBindingElement[]) { + return node.elements !== elements + ? update(createArrayBindingPattern(elements), node) + : node; + } - // @api - function updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]) { - return node.literal !== literal - ? update(createLiteralTypeNode(literal), node) - : node; - } + // @api + function createBindingElement(dotDotDotToken: DotDotDotToken | undefined, propertyName: string | PropertyName | undefined, name: string | BindingName, initializer?: Expression) { + const node = createBaseBindingLikeDeclaration( + SyntaxKind.BindingElement, + /*modifiers*/ undefined, + name, + initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) + ); + node.propertyName = asName(propertyName); + node.dotDotDotToken = dotDotDotToken; + node.transformFlags |= + propagateChildFlags(node.dotDotDotToken) | + TransformFlags.ContainsES2015; + if (node.propertyName) { + node.transformFlags |= isIdentifier(node.propertyName) ? + propagateIdentifierNameFlags(node.propertyName) : + propagateChildFlags(node.propertyName); + } + if (dotDotDotToken) node.transformFlags |= TransformFlags.ContainsRestOrSpread; + return node; + } - // - // Binding Patterns - // + // @api + function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken | undefined, propertyName: PropertyName | undefined, name: BindingName, initializer: Expression | undefined) { + return node.propertyName !== propertyName + || node.dotDotDotToken !== dotDotDotToken + || node.name !== name + || node.initializer !== initializer + ? update(createBindingElement(dotDotDotToken, propertyName, name, initializer), node) + : node; + } - // @api - function createObjectBindingPattern(elements: readonly BindingElement[]) { - const node = createBaseNode(SyntaxKind.ObjectBindingPattern); - node.elements = createNodeArray(elements); - node.transformFlags |= - propagateChildrenFlags(node.elements) | - TransformFlags.ContainsES2015 | - TransformFlags.ContainsBindingPattern; - if (node.transformFlags & TransformFlags.ContainsRestOrSpread) { - node.transformFlags |= - TransformFlags.ContainsES2018 | - TransformFlags.ContainsObjectRestOrSpread; - } - return node; - } + // + // Expression + // - // @api - function updateObjectBindingPattern(node: ObjectBindingPattern, elements: readonly BindingElement[]) { - return node.elements !== elements - ? update(createObjectBindingPattern(elements), node) - : node; - } + function createBaseExpression(kind: T["kind"]) { + const node = createBaseNode(kind); + // the following properties are commonly set by the checker/binder + return node; + } - // @api - function createArrayBindingPattern(elements: readonly ArrayBindingElement[]) { - const node = createBaseNode(SyntaxKind.ArrayBindingPattern); - node.elements = createNodeArray(elements); - node.transformFlags |= - propagateChildrenFlags(node.elements) | - TransformFlags.ContainsES2015 | - TransformFlags.ContainsBindingPattern; - return node; - } + // @api + function createArrayLiteralExpression(elements?: readonly Expression[], multiLine?: boolean) { + const node = createBaseExpression(SyntaxKind.ArrayLiteralExpression); + // Ensure we add a trailing comma for something like `[NumericLiteral(1), NumericLiteral(2), OmittedExpresion]` so that + // we end up with `[1, 2, ,]` instead of `[1, 2, ]` otherwise the `OmittedExpression` will just end up being treated like + // a trailing comma. + const lastElement = elements && lastOrUndefined(elements); + const elementsArray = createNodeArray(elements, lastElement && isOmittedExpression(lastElement) ? true : undefined); + node.elements = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(elementsArray); + node.multiLine = multiLine; + node.transformFlags |= propagateChildrenFlags(node.elements); + return node; + } - // @api - function updateArrayBindingPattern(node: ArrayBindingPattern, elements: readonly ArrayBindingElement[]) { - return node.elements !== elements - ? update(createArrayBindingPattern(elements), node) - : node; - } + // @api + function updateArrayLiteralExpression(node: ArrayLiteralExpression, elements: readonly Expression[]) { + return node.elements !== elements + ? update(createArrayLiteralExpression(elements, node.multiLine), node) + : node; + } - // @api - function createBindingElement(dotDotDotToken: DotDotDotToken | undefined, propertyName: string | PropertyName | undefined, name: string | BindingName, initializer?: Expression) { - const node = createBaseBindingLikeDeclaration( - SyntaxKind.BindingElement, - /*modifiers*/ undefined, - name, - initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) - ); - node.propertyName = asName(propertyName); - node.dotDotDotToken = dotDotDotToken; + // @api + function createObjectLiteralExpression(properties?: readonly ObjectLiteralElementLike[], multiLine?: boolean) { + const node = createBaseExpression(SyntaxKind.ObjectLiteralExpression); + node.properties = createNodeArray(properties); + node.multiLine = multiLine; + node.transformFlags |= propagateChildrenFlags(node.properties); + return node; + } + + // @api + function updateObjectLiteralExpression(node: ObjectLiteralExpression, properties: readonly ObjectLiteralElementLike[]) { + return node.properties !== properties + ? update(createObjectLiteralExpression(properties, node.multiLine), node) + : node; + } + + // @api + function createPropertyAccessExpression(expression: Expression, name: string | Identifier | PrivateIdentifier) { + const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); + node.name = asName(name); + node.transformFlags = + propagateChildFlags(node.expression) | + (isIdentifier(node.name) ? + propagateIdentifierNameFlags(node.name) : + propagateChildFlags(node.name) | TransformFlags.ContainsPrivateIdentifierInExpression); + if (isSuperKeyword(expression)) { + // super method calls require a lexical 'this' + // super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators node.transformFlags |= - propagateChildFlags(node.dotDotDotToken) | - TransformFlags.ContainsES2015; - if (node.propertyName) { - node.transformFlags |= isIdentifier(node.propertyName) ? - propagateIdentifierNameFlags(node.propertyName) : - propagateChildFlags(node.propertyName); - } - if (dotDotDotToken) node.transformFlags |= TransformFlags.ContainsRestOrSpread; - return node; + TransformFlags.ContainsES2017 | + TransformFlags.ContainsES2018; } + return node; + } - // @api - function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken | undefined, propertyName: PropertyName | undefined, name: BindingName, initializer: Expression | undefined) { - return node.propertyName !== propertyName - || node.dotDotDotToken !== dotDotDotToken - || node.name !== name - || node.initializer !== initializer - ? update(createBindingElement(dotDotDotToken, propertyName, name, initializer), node) - : node; + // @api + function updatePropertyAccessExpression(node: PropertyAccessExpression, expression: Expression, name: Identifier | PrivateIdentifier) { + if (isPropertyAccessChain(node)) { + return updatePropertyAccessChain(node, expression, node.questionDotToken, cast(name, isIdentifier)); } + return node.expression !== expression + || node.name !== name + ? update(createPropertyAccessExpression(expression, name), node) + : node; + } - // - // Expression - // + // @api + function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier | PrivateIdentifier) { + const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); + node.questionDotToken = questionDotToken; + node.name = asName(name); + node.transformFlags |= + TransformFlags.ContainsES2020 | + propagateChildFlags(node.expression) | + propagateChildFlags(node.questionDotToken) | + (isIdentifier(node.name) ? + propagateIdentifierNameFlags(node.name) : + propagateChildFlags(node.name) | TransformFlags.ContainsPrivateIdentifierInExpression); + return node; + } - function createBaseExpression(kind: T["kind"]) { - const node = createBaseNode(kind); - // the following properties are commonly set by the checker/binder - return node; - } + // @api + function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier | PrivateIdentifier) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessExpression using updatePropertyAccessChain. Use updatePropertyAccess instead."); + // Because we are updating an existing PropertyAccessChain we want to inherit its emitFlags + // instead of using the default from createPropertyAccess + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.name !== name + ? update(createPropertyAccessChain(expression, questionDotToken, name), node) + : node; + } - // @api - function createArrayLiteralExpression(elements?: readonly Expression[], multiLine?: boolean) { - const node = createBaseExpression(SyntaxKind.ArrayLiteralExpression); - // Ensure we add a trailing comma for something like `[NumericLiteral(1), NumericLiteral(2), OmittedExpresion]` so that - // we end up with `[1, 2, ,]` instead of `[1, 2, ]` otherwise the `OmittedExpression` will just end up being treated like - // a trailing comma. - const lastElement = elements && lastOrUndefined(elements); - const elementsArray = createNodeArray(elements, lastElement && isOmittedExpression(lastElement) ? true : undefined); - node.elements = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(elementsArray); - node.multiLine = multiLine; - node.transformFlags |= propagateChildrenFlags(node.elements); - return node; + // @api + function createElementAccessExpression(expression: Expression, index: number | Expression) { + const node = createBaseExpression(SyntaxKind.ElementAccessExpression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); + node.argumentExpression = asExpression(index); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.argumentExpression); + if (isSuperKeyword(expression)) { + // super method calls require a lexical 'this' + // super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators + node.transformFlags |= + TransformFlags.ContainsES2017 | + TransformFlags.ContainsES2018; } + return node; + } - // @api - function updateArrayLiteralExpression(node: ArrayLiteralExpression, elements: readonly Expression[]) { - return node.elements !== elements - ? update(createArrayLiteralExpression(elements, node.multiLine), node) - : node; + // @api + function updateElementAccessExpression(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) { + if (isElementAccessChain(node)) { + return updateElementAccessChain(node, expression, node.questionDotToken, argumentExpression); } + return node.expression !== expression + || node.argumentExpression !== argumentExpression + ? update(createElementAccessExpression(expression, argumentExpression), node) + : node; + } - // @api - function createObjectLiteralExpression(properties?: readonly ObjectLiteralElementLike[], multiLine?: boolean) { - const node = createBaseExpression(SyntaxKind.ObjectLiteralExpression); - node.properties = createNodeArray(properties); - node.multiLine = multiLine; - node.transformFlags |= propagateChildrenFlags(node.properties); - return node; - } + // @api + function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { + const node = createBaseExpression(SyntaxKind.ElementAccessExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); + node.questionDotToken = questionDotToken; + node.argumentExpression = asExpression(index); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.questionDotToken) | + propagateChildFlags(node.argumentExpression) | + TransformFlags.ContainsES2020; + return node; + } - // @api - function updateObjectLiteralExpression(node: ObjectLiteralExpression, properties: readonly ObjectLiteralElementLike[]) { - return node.properties !== properties - ? update(createObjectLiteralExpression(properties, node.multiLine), node) - : node; - } + // @api + function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a ElementAccessExpression using updateElementAccessChain. Use updateElementAccess instead."); + // Because we are updating an existing ElementAccessChain we want to inherit its emitFlags + // instead of using the default from createElementAccess + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.argumentExpression !== argumentExpression + ? update(createElementAccessChain(expression, questionDotToken, argumentExpression), node) + : node; + } - // @api - function createPropertyAccessExpression(expression: Expression, name: string | Identifier | PrivateIdentifier) { - const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); - node.name = asName(name); - node.transformFlags = - propagateChildFlags(node.expression) | - (isIdentifier(node.name) ? - propagateIdentifierNameFlags(node.name) : - propagateChildFlags(node.name) | TransformFlags.ContainsPrivateIdentifierInExpression); - if (isSuperKeyword(expression)) { - // super method calls require a lexical 'this' - // super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators - node.transformFlags |= - TransformFlags.ContainsES2017 | - TransformFlags.ContainsES2018; - } - return node; + // @api + function createCallExpression(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = createBaseExpression(SyntaxKind.CallExpression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); + node.typeArguments = asNodeArray(typeArguments); + node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray)); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildrenFlags(node.typeArguments) | + propagateChildrenFlags(node.arguments); + if (node.typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + if (isImportKeyword(node.expression)) { + node.transformFlags |= TransformFlags.ContainsDynamicImport; + } + else if (isSuperProperty(node.expression)) { + node.transformFlags |= TransformFlags.ContainsLexicalThis; } + return node; + } - // @api - function updatePropertyAccessExpression(node: PropertyAccessExpression, expression: Expression, name: Identifier | PrivateIdentifier) { - if (isPropertyAccessChain(node)) { - return updatePropertyAccessChain(node, expression, node.questionDotToken, cast(name, isIdentifier)); - } - return node.expression !== expression - || node.name !== name - ? update(createPropertyAccessExpression(expression, name), node) - : node; + // @api + function updateCallExpression(node: CallExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { + if (isCallChain(node)) { + return updateCallChain(node, expression, node.questionDotToken, typeArguments, argumentsArray); } + return node.expression !== expression + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? update(createCallExpression(expression, typeArguments, argumentsArray), node) + : node; + } - // @api - function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier | PrivateIdentifier) { - const node = createBaseExpression(SyntaxKind.PropertyAccessExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); - node.questionDotToken = questionDotToken; - node.name = asName(name); - node.transformFlags |= - TransformFlags.ContainsES2020 | - propagateChildFlags(node.expression) | - propagateChildFlags(node.questionDotToken) | - (isIdentifier(node.name) ? - propagateIdentifierNameFlags(node.name) : - propagateChildFlags(node.name) | TransformFlags.ContainsPrivateIdentifierInExpression); - return node; + // @api + function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = createBaseExpression(SyntaxKind.CallExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); + node.questionDotToken = questionDotToken; + node.typeArguments = asNodeArray(typeArguments); + node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray)); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.questionDotToken) | + propagateChildrenFlags(node.typeArguments) | + propagateChildrenFlags(node.arguments) | + TransformFlags.ContainsES2020; + if (node.typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + if (isSuperProperty(node.expression)) { + node.transformFlags |= TransformFlags.ContainsLexicalThis; } + return node; + } - // @api - function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier | PrivateIdentifier) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessExpression using updatePropertyAccessChain. Use updatePropertyAccess instead."); - // Because we are updating an existing PropertyAccessChain we want to inherit its emitFlags - // instead of using the default from createPropertyAccess - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.name !== name - ? update(createPropertyAccessChain(expression, questionDotToken, name), node) - : node; - } + // @api + function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallExpression using updateCallChain. Use updateCall instead."); + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? update(createCallChain(expression, questionDotToken, typeArguments, argumentsArray), node) + : node; + } - // @api - function createElementAccessExpression(expression: Expression, index: number | Expression) { - const node = createBaseExpression(SyntaxKind.ElementAccessExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); - node.argumentExpression = asExpression(index); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.argumentExpression); - if (isSuperKeyword(expression)) { - // super method calls require a lexical 'this' - // super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators - node.transformFlags |= - TransformFlags.ContainsES2017 | - TransformFlags.ContainsES2018; - } - return node; + // @api + function createNewExpression(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + const node = createBaseExpression(SyntaxKind.NewExpression); + node.expression = parenthesizerRules().parenthesizeExpressionOfNew(expression); + node.typeArguments = asNodeArray(typeArguments); + node.arguments = argumentsArray ? parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(argumentsArray) : undefined; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildrenFlags(node.typeArguments) | + propagateChildrenFlags(node.arguments) | + TransformFlags.ContainsES2020; + if (node.typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function updateElementAccessExpression(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) { - if (isElementAccessChain(node)) { - return updateElementAccessChain(node, expression, node.questionDotToken, argumentExpression); - } - return node.expression !== expression - || node.argumentExpression !== argumentExpression - ? update(createElementAccessExpression(expression, argumentExpression), node) - : node; - } + // @api + function updateNewExpression(node: NewExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { + return node.expression !== expression + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? update(createNewExpression(expression, typeArguments, argumentsArray), node) + : node; + } - // @api - function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { - const node = createBaseExpression(SyntaxKind.ElementAccessExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); - node.questionDotToken = questionDotToken; - node.argumentExpression = asExpression(index); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.questionDotToken) | - propagateChildFlags(node.argumentExpression) | - TransformFlags.ContainsES2020; - return node; + // @api + function createTaggedTemplateExpression(tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral) { + const node = createBaseExpression(SyntaxKind.TaggedTemplateExpression); + node.tag = parenthesizerRules().parenthesizeLeftSideOfAccess(tag, /*optionalChain*/ false); + node.typeArguments = asNodeArray(typeArguments); + node.template = template; + node.transformFlags |= + propagateChildFlags(node.tag) | + propagateChildrenFlags(node.typeArguments) | + propagateChildFlags(node.template) | + TransformFlags.ContainsES2015; + if (node.typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + if (hasInvalidEscape(node.template)) { + node.transformFlags |= TransformFlags.ContainsES2018; } + return node; + } - // @api - function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a ElementAccessExpression using updateElementAccessChain. Use updateElementAccess instead."); - // Because we are updating an existing ElementAccessChain we want to inherit its emitFlags - // instead of using the default from createElementAccess - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.argumentExpression !== argumentExpression - ? update(createElementAccessChain(expression, questionDotToken, argumentExpression), node) - : node; - } + // @api + function updateTaggedTemplateExpression(node: TaggedTemplateExpression, tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral) { + return node.tag !== tag + || node.typeArguments !== typeArguments + || node.template !== template + ? update(createTaggedTemplateExpression(tag, typeArguments, template), node) + : node; + } - // @api - function createCallExpression(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createBaseExpression(SyntaxKind.CallExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); - node.typeArguments = asNodeArray(typeArguments); - node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray)); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildrenFlags(node.typeArguments) | - propagateChildrenFlags(node.arguments); - if (node.typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - if (isImportKeyword(node.expression)) { - node.transformFlags |= TransformFlags.ContainsDynamicImport; - } - else if (isSuperProperty(node.expression)) { - node.transformFlags |= TransformFlags.ContainsLexicalThis; - } - return node; - } + // @api + function createTypeAssertion(type: TypeNode, expression: Expression) { + const node = createBaseExpression(SyntaxKind.TypeAssertionExpression); + node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); + node.type = type; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.type) | + TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateCallExpression(node: CallExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { - if (isCallChain(node)) { - return updateCallChain(node, expression, node.questionDotToken, typeArguments, argumentsArray); - } - return node.expression !== expression - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? update(createCallExpression(expression, typeArguments, argumentsArray), node) - : node; - } + // @api + function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression) { + return node.type !== type + || node.expression !== expression + ? update(createTypeAssertion(type, expression), node) + : node; + } - // @api - function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createBaseExpression(SyntaxKind.CallExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); - node.questionDotToken = questionDotToken; - node.typeArguments = asNodeArray(typeArguments); - node.arguments = parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(createNodeArray(argumentsArray)); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.questionDotToken) | - propagateChildrenFlags(node.typeArguments) | - propagateChildrenFlags(node.arguments) | - TransformFlags.ContainsES2020; - if (node.typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - if (isSuperProperty(node.expression)) { - node.transformFlags |= TransformFlags.ContainsLexicalThis; - } - return node; - } + // @api + function createParenthesizedExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.ParenthesizedExpression); + node.expression = expression; + node.transformFlags = propagateChildFlags(node.expression); + return node; + } - // @api - function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallExpression using updateCallChain. Use updateCall instead."); - return node.expression !== expression - || node.questionDotToken !== questionDotToken - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? update(createCallChain(expression, questionDotToken, typeArguments, argumentsArray), node) - : node; - } + // @api + function updateParenthesizedExpression(node: ParenthesizedExpression, expression: Expression) { + return node.expression !== expression + ? update(createParenthesizedExpression(expression), node) + : node; + } - // @api - function createNewExpression(expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - const node = createBaseExpression(SyntaxKind.NewExpression); - node.expression = parenthesizerRules().parenthesizeExpressionOfNew(expression); - node.typeArguments = asNodeArray(typeArguments); - node.arguments = argumentsArray ? parenthesizerRules().parenthesizeExpressionsOfCommaDelimitedList(argumentsArray) : undefined; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildrenFlags(node.typeArguments) | - propagateChildrenFlags(node.arguments) | - TransformFlags.ContainsES2020; - if (node.typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; + // @api + function createFunctionExpression( + modifiers: readonly ModifierLike[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[] | undefined, + type: TypeNode | undefined, + body: Block + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.FunctionExpression, + modifiers, + name, + typeParameters, + parameters, + type, + body + ); + node.asteriskToken = asteriskToken; + node.transformFlags |= propagateChildFlags(node.asteriskToken); + if (node.typeParameters) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { + if (node.asteriskToken) { + node.transformFlags |= TransformFlags.ContainsES2018; + } + else { + node.transformFlags |= TransformFlags.ContainsES2017; } - return node; } - - // @api - function updateNewExpression(node: NewExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) { - return node.expression !== expression - || node.typeArguments !== typeArguments - || node.arguments !== argumentsArray - ? update(createNewExpression(expression, typeArguments, argumentsArray), node) - : node; + else if (node.asteriskToken) { + node.transformFlags |= TransformFlags.ContainsGenerator; } + return node; + } - // @api - function createTaggedTemplateExpression(tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral) { - const node = createBaseExpression(SyntaxKind.TaggedTemplateExpression); - node.tag = parenthesizerRules().parenthesizeLeftSideOfAccess(tag, /*optionalChain*/ false); - node.typeArguments = asNodeArray(typeArguments); - node.template = template; - node.transformFlags |= - propagateChildFlags(node.tag) | - propagateChildrenFlags(node.typeArguments) | - propagateChildFlags(node.template) | - TransformFlags.ContainsES2015; - if (node.typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - if (hasInvalidEscape(node.template)) { - node.transformFlags |= TransformFlags.ContainsES2018; - } - return node; - } + // @api + function updateFunctionExpression( + node: FunctionExpression, + modifiers: readonly ModifierLike[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block + ) { + return node.name !== name + || node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? finishUpdateBaseSignatureDeclaration(createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) + : node; + } - // @api - function updateTaggedTemplateExpression(node: TaggedTemplateExpression, tag: Expression, typeArguments: readonly TypeNode[] | undefined, template: TemplateLiteral) { - return node.tag !== tag - || node.typeArguments !== typeArguments - || node.template !== template - ? update(createTaggedTemplateExpression(tag, typeArguments, template), node) - : node; + // @api + function createArrowFunction( + modifiers: readonly ModifierLike[] | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + equalsGreaterThanToken: EqualsGreaterThanToken | undefined, + body: ConciseBody + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.ArrowFunction, + modifiers, + /*name*/ undefined, + typeParameters, + parameters, + type, + parenthesizerRules().parenthesizeConciseBodyOfArrowFunction(body) + ); + node.equalsGreaterThanToken = equalsGreaterThanToken ?? createToken(SyntaxKind.EqualsGreaterThanToken); + node.transformFlags |= + propagateChildFlags(node.equalsGreaterThanToken) | + TransformFlags.ContainsES2015; + if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { + node.transformFlags |= TransformFlags.ContainsES2017 | TransformFlags.ContainsLexicalThis; } + return node; + } - // @api - function createTypeAssertion(type: TypeNode, expression: Expression) { - const node = createBaseExpression(SyntaxKind.TypeAssertionExpression); - node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); - node.type = type; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.type) | - TransformFlags.ContainsTypeScript; - return node; - } - - // @api - function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression) { - return node.type !== type - || node.expression !== expression - ? update(createTypeAssertion(type, expression), node) - : node; - } - - // @api - function createParenthesizedExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.ParenthesizedExpression); - node.expression = expression; - node.transformFlags = propagateChildFlags(node.expression); - return node; - } - - // @api - function updateParenthesizedExpression(node: ParenthesizedExpression, expression: Expression) { - return node.expression !== expression - ? update(createParenthesizedExpression(expression), node) - : node; - } + // @api + function updateArrowFunction( + node: ArrowFunction, + modifiers: readonly ModifierLike[] | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + equalsGreaterThanToken: EqualsGreaterThanToken, + body: ConciseBody + ): ArrowFunction { + return node.modifiers !== modifiers + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.equalsGreaterThanToken !== equalsGreaterThanToken + || node.body !== body + ? finishUpdateBaseSignatureDeclaration(createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body), node) + : node; + } - // @api - function createFunctionExpression( - modifiers: readonly ModifierLike[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[] | undefined, - type: TypeNode | undefined, - body: Block - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.FunctionExpression, - modifiers, - name, - typeParameters, - parameters, - type, - body - ); - node.asteriskToken = asteriskToken; - node.transformFlags |= propagateChildFlags(node.asteriskToken); - if (node.typeParameters) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { - if (node.asteriskToken) { - node.transformFlags |= TransformFlags.ContainsES2018; - } - else { - node.transformFlags |= TransformFlags.ContainsES2017; - } - } - else if (node.asteriskToken) { - node.transformFlags |= TransformFlags.ContainsGenerator; - } - return node; - } + // @api + function createDeleteExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.DeleteExpression); + node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); + node.transformFlags |= propagateChildFlags(node.expression); + return node; + } - // @api - function updateFunctionExpression( - node: FunctionExpression, - modifiers: readonly ModifierLike[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block - ) { - return node.name !== name - || node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? finishUpdateBaseSignatureDeclaration(createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) - : node; - } + // @api + function updateDeleteExpression(node: DeleteExpression, expression: Expression) { + return node.expression !== expression + ? update(createDeleteExpression(expression), node) + : node; + } - // @api - function createArrowFunction( - modifiers: readonly ModifierLike[] | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - equalsGreaterThanToken: EqualsGreaterThanToken | undefined, - body: ConciseBody - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.ArrowFunction, - modifiers, - /*name*/ undefined, - typeParameters, - parameters, - type, - parenthesizerRules().parenthesizeConciseBodyOfArrowFunction(body) - ); - node.equalsGreaterThanToken = equalsGreaterThanToken ?? createToken(SyntaxKind.EqualsGreaterThanToken); - node.transformFlags |= - propagateChildFlags(node.equalsGreaterThanToken) | - TransformFlags.ContainsES2015; - if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { - node.transformFlags |= TransformFlags.ContainsES2017 | TransformFlags.ContainsLexicalThis; - } - return node; - } + // @api + function createTypeOfExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.TypeOfExpression); + node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); + node.transformFlags |= propagateChildFlags(node.expression); + return node; + } - // @api - function updateArrowFunction( - node: ArrowFunction, - modifiers: readonly ModifierLike[] | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - equalsGreaterThanToken: EqualsGreaterThanToken, - body: ConciseBody - ): ArrowFunction { - return node.modifiers !== modifiers - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.equalsGreaterThanToken !== equalsGreaterThanToken - || node.body !== body - ? finishUpdateBaseSignatureDeclaration(createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body), node) - : node; - } + // @api + function updateTypeOfExpression(node: TypeOfExpression, expression: Expression) { + return node.expression !== expression + ? update(createTypeOfExpression(expression), node) + : node; + } - // @api - function createDeleteExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.DeleteExpression); - node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); - node.transformFlags |= propagateChildFlags(node.expression); - return node; - } + // @api + function createVoidExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.VoidExpression); + node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); + node.transformFlags |= propagateChildFlags(node.expression); + return node; + } - // @api - function updateDeleteExpression(node: DeleteExpression, expression: Expression) { - return node.expression !== expression - ? update(createDeleteExpression(expression), node) - : node; - } + // @api + function updateVoidExpression(node: VoidExpression, expression: Expression) { + return node.expression !== expression + ? update(createVoidExpression(expression), node) + : node; + } - // @api - function createTypeOfExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.TypeOfExpression); - node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); - node.transformFlags |= propagateChildFlags(node.expression); - return node; - } + // @api + function createAwaitExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.AwaitExpression); + node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsES2017 | + TransformFlags.ContainsES2018 | + TransformFlags.ContainsAwait; + return node; + } - // @api - function updateTypeOfExpression(node: TypeOfExpression, expression: Expression) { - return node.expression !== expression - ? update(createTypeOfExpression(expression), node) - : node; - } + // @api + function updateAwaitExpression(node: AwaitExpression, expression: Expression) { + return node.expression !== expression + ? update(createAwaitExpression(expression), node) + : node; + } - // @api - function createVoidExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.VoidExpression); - node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); - node.transformFlags |= propagateChildFlags(node.expression); - return node; + // @api + function createPrefixUnaryExpression(operator: PrefixUnaryOperator, operand: Expression) { + const node = createBaseExpression(SyntaxKind.PrefixUnaryExpression); + node.operator = operator; + node.operand = parenthesizerRules().parenthesizeOperandOfPrefixUnary(operand); + node.transformFlags |= propagateChildFlags(node.operand); + // Only set this flag for non-generated identifiers and non-"local" names. See the + // comment in `visitPreOrPostfixUnaryExpression` in module.ts + if ((operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken) && + isIdentifier(node.operand) && + !isGeneratedIdentifier(node.operand) && + !isLocalName(node.operand)) { + node.transformFlags |= TransformFlags.ContainsUpdateExpressionForIdentifier; } + return node; + } - // @api - function updateVoidExpression(node: VoidExpression, expression: Expression) { - return node.expression !== expression - ? update(createVoidExpression(expression), node) - : node; - } + // @api + function updatePrefixUnaryExpression(node: PrefixUnaryExpression, operand: Expression) { + return node.operand !== operand + ? update(createPrefixUnaryExpression(node.operator, operand), node) + : node; + } - // @api - function createAwaitExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.AwaitExpression); - node.expression = parenthesizerRules().parenthesizeOperandOfPrefixUnary(expression); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsES2017 | - TransformFlags.ContainsES2018 | - TransformFlags.ContainsAwait; - return node; + // @api + function createPostfixUnaryExpression(operand: Expression, operator: PostfixUnaryOperator) { + const node = createBaseExpression(SyntaxKind.PostfixUnaryExpression); + node.operator = operator; + node.operand = parenthesizerRules().parenthesizeOperandOfPostfixUnary(operand); + node.transformFlags |= propagateChildFlags(node.operand); + // Only set this flag for non-generated identifiers and non-"local" names. See the + // comment in `visitPreOrPostfixUnaryExpression` in module.ts + if (isIdentifier(node.operand) && + !isGeneratedIdentifier(node.operand) && + !isLocalName(node.operand)) { + node.transformFlags |= TransformFlags.ContainsUpdateExpressionForIdentifier; } + return node; + } - // @api - function updateAwaitExpression(node: AwaitExpression, expression: Expression) { - return node.expression !== expression - ? update(createAwaitExpression(expression), node) - : node; - } + // @api + function updatePostfixUnaryExpression(node: PostfixUnaryExpression, operand: Expression) { + return node.operand !== operand + ? update(createPostfixUnaryExpression(operand, node.operator), node) + : node; + } - // @api - function createPrefixUnaryExpression(operator: PrefixUnaryOperator, operand: Expression) { - const node = createBaseExpression(SyntaxKind.PrefixUnaryExpression); - node.operator = operator; - node.operand = parenthesizerRules().parenthesizeOperandOfPrefixUnary(operand); - node.transformFlags |= propagateChildFlags(node.operand); - // Only set this flag for non-generated identifiers and non-"local" names. See the - // comment in `visitPreOrPostfixUnaryExpression` in module.ts - if ((operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken) && - isIdentifier(node.operand) && - !isGeneratedIdentifier(node.operand) && - !isLocalName(node.operand)) { - node.transformFlags |= TransformFlags.ContainsUpdateExpressionForIdentifier; + // @api + function createBinaryExpression(left: Expression, operator: BinaryOperator | BinaryOperatorToken, right: Expression) { + const node = createBaseExpression(SyntaxKind.BinaryExpression); + const operatorToken = asToken(operator); + const operatorKind = operatorToken.kind; + node.left = parenthesizerRules().parenthesizeLeftSideOfBinary(operatorKind, left); + node.operatorToken = operatorToken; + node.right = parenthesizerRules().parenthesizeRightSideOfBinary(operatorKind, node.left, right); + node.transformFlags |= + propagateChildFlags(node.left) | + propagateChildFlags(node.operatorToken) | + propagateChildFlags(node.right); + if (operatorKind === SyntaxKind.QuestionQuestionToken) { + node.transformFlags |= TransformFlags.ContainsES2020; + } + else if (operatorKind === SyntaxKind.EqualsToken) { + if (isObjectLiteralExpression(node.left)) { + node.transformFlags |= + TransformFlags.ContainsES2015 | + TransformFlags.ContainsES2018 | + TransformFlags.ContainsDestructuringAssignment | + propagateAssignmentPatternFlags(node.left); } - return node; - } - - // @api - function updatePrefixUnaryExpression(node: PrefixUnaryExpression, operand: Expression) { - return node.operand !== operand - ? update(createPrefixUnaryExpression(node.operator, operand), node) - : node; - } - - // @api - function createPostfixUnaryExpression(operand: Expression, operator: PostfixUnaryOperator) { - const node = createBaseExpression(SyntaxKind.PostfixUnaryExpression); - node.operator = operator; - node.operand = parenthesizerRules().parenthesizeOperandOfPostfixUnary(operand); - node.transformFlags |= propagateChildFlags(node.operand); - // Only set this flag for non-generated identifiers and non-"local" names. See the - // comment in `visitPreOrPostfixUnaryExpression` in module.ts - if (isIdentifier(node.operand) && - !isGeneratedIdentifier(node.operand) && - !isLocalName(node.operand)) { - node.transformFlags |= TransformFlags.ContainsUpdateExpressionForIdentifier; + else if (isArrayLiteralExpression(node.left)) { + node.transformFlags |= + TransformFlags.ContainsES2015 | + TransformFlags.ContainsDestructuringAssignment | + propagateAssignmentPatternFlags(node.left); } - return node; } - - // @api - function updatePostfixUnaryExpression(node: PostfixUnaryExpression, operand: Expression) { - return node.operand !== operand - ? update(createPostfixUnaryExpression(operand, node.operator), node) - : node; + else if (operatorKind === SyntaxKind.AsteriskAsteriskToken || operatorKind === SyntaxKind.AsteriskAsteriskEqualsToken) { + node.transformFlags |= TransformFlags.ContainsES2016; } - - // @api - function createBinaryExpression(left: Expression, operator: BinaryOperator | BinaryOperatorToken, right: Expression) { - const node = createBaseExpression(SyntaxKind.BinaryExpression); - const operatorToken = asToken(operator); - const operatorKind = operatorToken.kind; - node.left = parenthesizerRules().parenthesizeLeftSideOfBinary(operatorKind, left); - node.operatorToken = operatorToken; - node.right = parenthesizerRules().parenthesizeRightSideOfBinary(operatorKind, node.left, right); - node.transformFlags |= - propagateChildFlags(node.left) | - propagateChildFlags(node.operatorToken) | - propagateChildFlags(node.right); - if (operatorKind === SyntaxKind.QuestionQuestionToken) { - node.transformFlags |= TransformFlags.ContainsES2020; - } - else if (operatorKind === SyntaxKind.EqualsToken) { - if (isObjectLiteralExpression(node.left)) { - node.transformFlags |= - TransformFlags.ContainsES2015 | - TransformFlags.ContainsES2018 | - TransformFlags.ContainsDestructuringAssignment | - propagateAssignmentPatternFlags(node.left); - } - else if (isArrayLiteralExpression(node.left)) { - node.transformFlags |= - TransformFlags.ContainsES2015 | - TransformFlags.ContainsDestructuringAssignment | - propagateAssignmentPatternFlags(node.left); - } - } - else if (operatorKind === SyntaxKind.AsteriskAsteriskToken || operatorKind === SyntaxKind.AsteriskAsteriskEqualsToken) { - node.transformFlags |= TransformFlags.ContainsES2016; - } - else if (isLogicalOrCoalescingAssignmentOperator(operatorKind)) { - node.transformFlags |= TransformFlags.ContainsES2021; - } - if (operatorKind === SyntaxKind.InKeyword && isPrivateIdentifier(node.left)) { - node.transformFlags |= TransformFlags.ContainsPrivateIdentifierInExpression; - } - return node; + else if (isLogicalOrCoalescingAssignmentOperator(operatorKind)) { + node.transformFlags |= TransformFlags.ContainsES2021; } + if (operatorKind === SyntaxKind.InKeyword && isPrivateIdentifier(node.left)) { + node.transformFlags |= TransformFlags.ContainsPrivateIdentifierInExpression; + } + return node; + } - function propagateAssignmentPatternFlags(node: AssignmentPattern): TransformFlags { - if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) return TransformFlags.ContainsObjectRestOrSpread; - if (node.transformFlags & TransformFlags.ContainsES2018) { - // check for nested spread assignments, otherwise '{ x: { a, ...b } = foo } = c' - // will not be correctly interpreted by the ES2018 transformer - for (const element of getElementsOfBindingOrAssignmentPattern(node)) { - const target = getTargetOfBindingOrAssignmentElement(element); - if (target && isAssignmentPattern(target)) { - if (target.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { - return TransformFlags.ContainsObjectRestOrSpread; - } - if (target.transformFlags & TransformFlags.ContainsES2018) { - const flags = propagateAssignmentPatternFlags(target); - if (flags) return flags; - } + function propagateAssignmentPatternFlags(node: AssignmentPattern): TransformFlags { + if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) return TransformFlags.ContainsObjectRestOrSpread; + if (node.transformFlags & TransformFlags.ContainsES2018) { + // check for nested spread assignments, otherwise '{ x: { a, ...b } = foo } = c' + // will not be correctly interpreted by the ES2018 transformer + for (const element of getElementsOfBindingOrAssignmentPattern(node)) { + const target = getTargetOfBindingOrAssignmentElement(element); + if (target && isAssignmentPattern(target)) { + if (target.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { + return TransformFlags.ContainsObjectRestOrSpread; + } + if (target.transformFlags & TransformFlags.ContainsES2018) { + const flags = propagateAssignmentPatternFlags(target); + if (flags) return flags; } } } - return TransformFlags.None; } + return TransformFlags.None; + } - // @api - function updateBinaryExpression(node: BinaryExpression, left: Expression, operator: BinaryOperatorToken, right: Expression) { - return node.left !== left - || node.operatorToken !== operator - || node.right !== right - ? update(createBinaryExpression(left, operator, right), node) - : node; - } + // @api + function updateBinaryExpression(node: BinaryExpression, left: Expression, operator: BinaryOperatorToken, right: Expression) { + return node.left !== left + || node.operatorToken !== operator + || node.right !== right + ? update(createBinaryExpression(left, operator, right), node) + : node; + } - // @api - function createConditionalExpression(condition: Expression, questionToken: QuestionToken | undefined, whenTrue: Expression, colonToken: ColonToken | undefined, whenFalse: Expression) { - const node = createBaseExpression(SyntaxKind.ConditionalExpression); - node.condition = parenthesizerRules().parenthesizeConditionOfConditionalExpression(condition); - node.questionToken = questionToken ?? createToken(SyntaxKind.QuestionToken); - node.whenTrue = parenthesizerRules().parenthesizeBranchOfConditionalExpression(whenTrue); - node.colonToken = colonToken ?? createToken(SyntaxKind.ColonToken); - node.whenFalse = parenthesizerRules().parenthesizeBranchOfConditionalExpression(whenFalse); - node.transformFlags |= - propagateChildFlags(node.condition) | - propagateChildFlags(node.questionToken) | - propagateChildFlags(node.whenTrue) | - propagateChildFlags(node.colonToken) | - propagateChildFlags(node.whenFalse); - return node; - } + // @api + function createConditionalExpression(condition: Expression, questionToken: QuestionToken | undefined, whenTrue: Expression, colonToken: ColonToken | undefined, whenFalse: Expression) { + const node = createBaseExpression(SyntaxKind.ConditionalExpression); + node.condition = parenthesizerRules().parenthesizeConditionOfConditionalExpression(condition); + node.questionToken = questionToken ?? createToken(SyntaxKind.QuestionToken); + node.whenTrue = parenthesizerRules().parenthesizeBranchOfConditionalExpression(whenTrue); + node.colonToken = colonToken ?? createToken(SyntaxKind.ColonToken); + node.whenFalse = parenthesizerRules().parenthesizeBranchOfConditionalExpression(whenFalse); + node.transformFlags |= + propagateChildFlags(node.condition) | + propagateChildFlags(node.questionToken) | + propagateChildFlags(node.whenTrue) | + propagateChildFlags(node.colonToken) | + propagateChildFlags(node.whenFalse); + return node; + } - // @api - function updateConditionalExpression( - node: ConditionalExpression, - condition: Expression, - questionToken: Token, - whenTrue: Expression, - colonToken: Token, - whenFalse: Expression - ): ConditionalExpression { - return node.condition !== condition - || node.questionToken !== questionToken - || node.whenTrue !== whenTrue - || node.colonToken !== colonToken - || node.whenFalse !== whenFalse - ? update(createConditionalExpression(condition, questionToken, whenTrue, colonToken, whenFalse), node) - : node; - } + // @api + function updateConditionalExpression( + node: ConditionalExpression, + condition: Expression, + questionToken: Token, + whenTrue: Expression, + colonToken: Token, + whenFalse: Expression + ): ConditionalExpression { + return node.condition !== condition + || node.questionToken !== questionToken + || node.whenTrue !== whenTrue + || node.colonToken !== colonToken + || node.whenFalse !== whenFalse + ? update(createConditionalExpression(condition, questionToken, whenTrue, colonToken, whenFalse), node) + : node; + } - // @api - function createTemplateExpression(head: TemplateHead, templateSpans: readonly TemplateSpan[]) { - const node = createBaseExpression(SyntaxKind.TemplateExpression); - node.head = head; - node.templateSpans = createNodeArray(templateSpans); - node.transformFlags |= - propagateChildFlags(node.head) | - propagateChildrenFlags(node.templateSpans) | - TransformFlags.ContainsES2015; - return node; - } + // @api + function createTemplateExpression(head: TemplateHead, templateSpans: readonly TemplateSpan[]) { + const node = createBaseExpression(SyntaxKind.TemplateExpression); + node.head = head; + node.templateSpans = createNodeArray(templateSpans); + node.transformFlags |= + propagateChildFlags(node.head) | + propagateChildrenFlags(node.templateSpans) | + TransformFlags.ContainsES2015; + return node; + } - // @api - function updateTemplateExpression(node: TemplateExpression, head: TemplateHead, templateSpans: readonly TemplateSpan[]) { - return node.head !== head - || node.templateSpans !== templateSpans - ? update(createTemplateExpression(head, templateSpans), node) - : node; - } + // @api + function updateTemplateExpression(node: TemplateExpression, head: TemplateHead, templateSpans: readonly TemplateSpan[]) { + return node.head !== head + || node.templateSpans !== templateSpans + ? update(createTemplateExpression(head, templateSpans), node) + : node; + } - function createTemplateLiteralLikeNodeChecked(kind: TemplateLiteralToken["kind"], text: string | undefined, rawText: string | undefined, templateFlags = TokenFlags.None) { - Debug.assert(!(templateFlags & ~TokenFlags.TemplateLiteralLikeFlags), "Unsupported template flags."); - // NOTE: without the assignment to `undefined`, we don't narrow the initial type of `cooked`. - // eslint-disable-next-line no-undef-init - let cooked: string | object | undefined = undefined; - if (rawText !== undefined && rawText !== text) { - cooked = getCookedText(kind, rawText); - if (typeof cooked === "object") { - return Debug.fail("Invalid raw text"); - } - } - if (text === undefined) { - if (cooked === undefined) { - return Debug.fail("Arguments 'text' and 'rawText' may not both be undefined."); - } - text = cooked; - } - else if (cooked !== undefined) { - Debug.assert(text === cooked, "Expected argument 'text' to be the normalized (i.e. 'cooked') version of argument 'rawText'."); + function createTemplateLiteralLikeNodeChecked(kind: TemplateLiteralToken["kind"], text: string | undefined, rawText: string | undefined, templateFlags = TokenFlags.None) { + Debug.assert(!(templateFlags & ~TokenFlags.TemplateLiteralLikeFlags), "Unsupported template flags."); + // NOTE: without the assignment to `undefined`, we don't narrow the initial type of `cooked`. + // eslint-disable-next-line no-undef-init + let cooked: string | object | undefined = undefined; + if (rawText !== undefined && rawText !== text) { + cooked = getCookedText(kind, rawText); + if (typeof cooked === "object") { + return Debug.fail("Invalid raw text"); } - return createTemplateLiteralLikeNode(kind, text, rawText, templateFlags); } - - // @api - function createTemplateLiteralLikeNode(kind: TemplateLiteralToken["kind"], text: string, rawText: string | undefined, templateFlags: TokenFlags | undefined) { - const node = createBaseToken(kind); - node.text = text; - node.rawText = rawText; - node.templateFlags = templateFlags! & TokenFlags.TemplateLiteralLikeFlags; - node.transformFlags |= TransformFlags.ContainsES2015; - if (node.templateFlags) { - node.transformFlags |= TransformFlags.ContainsES2018; + if (text === undefined) { + if (cooked === undefined) { + return Debug.fail("Arguments 'text' and 'rawText' may not both be undefined."); } - return node; + text = cooked; } - - // @api - function createTemplateHead(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { - return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateHead, text, rawText, templateFlags) as TemplateHead; + else if (cooked !== undefined) { + Debug.assert(text === cooked, "Expected argument 'text' to be the normalized (i.e. 'cooked') version of argument 'rawText'."); } + return createTemplateLiteralLikeNode(kind, text, rawText, templateFlags); + } - // @api - function createTemplateMiddle(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { - return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateMiddle, text, rawText, templateFlags) as TemplateMiddle; + // @api + function createTemplateLiteralLikeNode(kind: TemplateLiteralToken["kind"], text: string, rawText: string | undefined, templateFlags: TokenFlags | undefined) { + const node = createBaseToken(kind); + node.text = text; + node.rawText = rawText; + node.templateFlags = templateFlags! & TokenFlags.TemplateLiteralLikeFlags; + node.transformFlags |= TransformFlags.ContainsES2015; + if (node.templateFlags) { + node.transformFlags |= TransformFlags.ContainsES2018; } + return node; + } - // @api - function createTemplateTail(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { - return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateTail, text, rawText, templateFlags) as TemplateTail; - } + // @api + function createTemplateHead(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { + return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateHead, text, rawText, templateFlags) as TemplateHead; + } - // @api - function createNoSubstitutionTemplateLiteral(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { - return createTemplateLiteralLikeNodeChecked(SyntaxKind.NoSubstitutionTemplateLiteral, text, rawText, templateFlags) as NoSubstitutionTemplateLiteral; - } + // @api + function createTemplateMiddle(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { + return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateMiddle, text, rawText, templateFlags) as TemplateMiddle; + } - // @api - function createYieldExpression(asteriskToken: AsteriskToken | undefined, expression: Expression | undefined): YieldExpression { - Debug.assert(!asteriskToken || !!expression, "A `YieldExpression` with an asteriskToken must have an expression."); - const node = createBaseExpression(SyntaxKind.YieldExpression); - node.expression = expression && parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.asteriskToken = asteriskToken; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.asteriskToken) | - TransformFlags.ContainsES2015 | - TransformFlags.ContainsES2018 | - TransformFlags.ContainsYield; - return node; - } + // @api + function createTemplateTail(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { + return createTemplateLiteralLikeNodeChecked(SyntaxKind.TemplateTail, text, rawText, templateFlags) as TemplateTail; + } - // @api - function updateYieldExpression(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression) { - return node.expression !== expression - || node.asteriskToken !== asteriskToken - ? update(createYieldExpression(asteriskToken, expression), node) - : node; - } + // @api + function createNoSubstitutionTemplateLiteral(text: string | undefined, rawText?: string, templateFlags?: TokenFlags) { + return createTemplateLiteralLikeNodeChecked(SyntaxKind.NoSubstitutionTemplateLiteral, text, rawText, templateFlags) as NoSubstitutionTemplateLiteral; + } - // @api - function createSpreadElement(expression: Expression) { - const node = createBaseExpression(SyntaxKind.SpreadElement); - node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsES2015 | - TransformFlags.ContainsRestOrSpread; - return node; - } + // @api + function createYieldExpression(asteriskToken: AsteriskToken | undefined, expression: Expression | undefined): YieldExpression { + Debug.assert(!asteriskToken || !!expression, "A `YieldExpression` with an asteriskToken must have an expression."); + const node = createBaseExpression(SyntaxKind.YieldExpression); + node.expression = expression && parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.asteriskToken = asteriskToken; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.asteriskToken) | + TransformFlags.ContainsES2015 | + TransformFlags.ContainsES2018 | + TransformFlags.ContainsYield; + return node; + } - // @api - function updateSpreadElement(node: SpreadElement, expression: Expression) { - return node.expression !== expression - ? update(createSpreadElement(expression), node) - : node; - } + // @api + function updateYieldExpression(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression) { + return node.expression !== expression + || node.asteriskToken !== asteriskToken + ? update(createYieldExpression(asteriskToken, expression), node) + : node; + } - // @api - function createClassExpression( - modifiers: readonly ModifierLike[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[] - ) { - const node = createBaseClassLikeDeclaration( - SyntaxKind.ClassExpression, - modifiers, - name, - typeParameters, - heritageClauses, - members - ); - node.transformFlags |= TransformFlags.ContainsES2015; - return node; - } + // @api + function createSpreadElement(expression: Expression) { + const node = createBaseExpression(SyntaxKind.SpreadElement); + node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsES2015 | + TransformFlags.ContainsRestOrSpread; + return node; + } - // @api - function updateClassExpression( - node: ClassExpression, - modifiers: readonly ModifierLike[] | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[] - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? update(createClassExpression(modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } + // @api + function updateSpreadElement(node: SpreadElement, expression: Expression) { + return node.expression !== expression + ? update(createSpreadElement(expression), node) + : node; + } - // @api - function createOmittedExpression() { - return createBaseExpression(SyntaxKind.OmittedExpression); - } + // @api + function createClassExpression( + modifiers: readonly ModifierLike[] | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[] + ) { + const node = createBaseClassLikeDeclaration( + SyntaxKind.ClassExpression, + modifiers, + name, + typeParameters, + heritageClauses, + members + ); + node.transformFlags |= TransformFlags.ContainsES2015; + return node; + } - // @api - function createExpressionWithTypeArguments(expression: Expression, typeArguments: readonly TypeNode[] | undefined) { - const node = createBaseNode(SyntaxKind.ExpressionWithTypeArguments); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); - node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildrenFlags(node.typeArguments) | - TransformFlags.ContainsES2015; - return node; - } + // @api + function updateClassExpression( + node: ClassExpression, + modifiers: readonly ModifierLike[] | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[] + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? update(createClassExpression(modifiers, name, typeParameters, heritageClauses, members), node) + : node; + } - // @api - function updateExpressionWithTypeArguments(node: ExpressionWithTypeArguments, expression: Expression, typeArguments: readonly TypeNode[] | undefined) { - return node.expression !== expression - || node.typeArguments !== typeArguments - ? update(createExpressionWithTypeArguments(expression, typeArguments), node) - : node; - } + // @api + function createOmittedExpression() { + return createBaseExpression(SyntaxKind.OmittedExpression); + } - // @api - function createAsExpression(expression: Expression, type: TypeNode) { - const node = createBaseExpression(SyntaxKind.AsExpression); - node.expression = expression; - node.type = type; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.type) | - TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createExpressionWithTypeArguments(expression: Expression, typeArguments: readonly TypeNode[] | undefined) { + const node = createBaseNode(SyntaxKind.ExpressionWithTypeArguments); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); + node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildrenFlags(node.typeArguments) | + TransformFlags.ContainsES2015; + return node; + } - // @api - function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode) { - return node.expression !== expression - || node.type !== type - ? update(createAsExpression(expression, type), node) - : node; - } + // @api + function updateExpressionWithTypeArguments(node: ExpressionWithTypeArguments, expression: Expression, typeArguments: readonly TypeNode[] | undefined) { + return node.expression !== expression + || node.typeArguments !== typeArguments + ? update(createExpressionWithTypeArguments(expression, typeArguments), node) + : node; + } - // @api - function createNonNullExpression(expression: Expression) { - const node = createBaseExpression(SyntaxKind.NonNullExpression); - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createAsExpression(expression: Expression, type: TypeNode) { + const node = createBaseExpression(SyntaxKind.AsExpression); + node.expression = expression; + node.type = type; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.type) | + TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateNonNullExpression(node: NonNullExpression, expression: Expression) { - if (isNonNullChain(node)) { - return updateNonNullChain(node, expression); - } - return node.expression !== expression - ? update(createNonNullExpression(expression), node) - : node; - } + // @api + function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode) { + return node.expression !== expression + || node.type !== type + ? update(createAsExpression(expression, type), node) + : node; + } - // @api - function createSatisfiesExpression(expression: Expression, type: TypeNode) { - const node = createBaseExpression(SyntaxKind.SatisfiesExpression); - node.expression = expression; - node.type = type; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.type) | - TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createNonNullExpression(expression: Expression) { + const node = createBaseExpression(SyntaxKind.NonNullExpression); + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateSatisfiesExpression(node: SatisfiesExpression, expression: Expression, type: TypeNode) { - return node.expression !== expression - || node.type !== type - ? update(createSatisfiesExpression(expression, type), node) - : node; + // @api + function updateNonNullExpression(node: NonNullExpression, expression: Expression) { + if (isNonNullChain(node)) { + return updateNonNullChain(node, expression); } + return node.expression !== expression + ? update(createNonNullExpression(expression), node) + : node; + } - // @api - function createNonNullChain(expression: Expression) { - const node = createBaseExpression(SyntaxKind.NonNullExpression); - node.flags |= NodeFlags.OptionalChain; - node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsTypeScript; - return node; - } + // @api + function createSatisfiesExpression(expression: Expression, type: TypeNode) { + const node = createBaseExpression(SyntaxKind.SatisfiesExpression); + node.expression = expression; + node.type = type; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.type) | + TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateNonNullChain(node: NonNullChain, expression: Expression) { - Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a NonNullExpression using updateNonNullChain. Use updateNonNullExpression instead."); - return node.expression !== expression - ? update(createNonNullChain(expression), node) - : node; - } + // @api + function updateSatisfiesExpression(node: SatisfiesExpression, expression: Expression, type: TypeNode) { + return node.expression !== expression + || node.type !== type + ? update(createSatisfiesExpression(expression, type), node) + : node; + } - // @api - function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier) { - const node = createBaseExpression(SyntaxKind.MetaProperty); - node.keywordToken = keywordToken; - node.name = name; - node.transformFlags |= propagateChildFlags(node.name); - switch (keywordToken) { - case SyntaxKind.NewKeyword: - node.transformFlags |= TransformFlags.ContainsES2015; - break; - case SyntaxKind.ImportKeyword: - node.transformFlags |= TransformFlags.ContainsESNext; - break; - default: - return Debug.assertNever(keywordToken); - } - return node; - } + // @api + function createNonNullChain(expression: Expression) { + const node = createBaseExpression(SyntaxKind.NonNullExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ true); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsTypeScript; + return node; + } + + // @api + function updateNonNullChain(node: NonNullChain, expression: Expression) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a NonNullExpression using updateNonNullChain. Use updateNonNullExpression instead."); + return node.expression !== expression + ? update(createNonNullChain(expression), node) + : node; + } - // @api - function updateMetaProperty(node: MetaProperty, name: Identifier) { - return node.name !== name - ? update(createMetaProperty(node.keywordToken, name), node) - : node; + // @api + function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier) { + const node = createBaseExpression(SyntaxKind.MetaProperty); + node.keywordToken = keywordToken; + node.name = name; + node.transformFlags |= propagateChildFlags(node.name); + switch (keywordToken) { + case SyntaxKind.NewKeyword: + node.transformFlags |= TransformFlags.ContainsES2015; + break; + case SyntaxKind.ImportKeyword: + node.transformFlags |= TransformFlags.ContainsESNext; + break; + default: + return Debug.assertNever(keywordToken); } + return node; + } - // - // Misc - // + // @api + function updateMetaProperty(node: MetaProperty, name: Identifier) { + return node.name !== name + ? update(createMetaProperty(node.keywordToken, name), node) + : node; + } - // @api - function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail) { - const node = createBaseNode(SyntaxKind.TemplateSpan); - node.expression = expression; - node.literal = literal; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.literal) | - TransformFlags.ContainsES2015; - return node; - } + // + // Misc + // + + // @api + function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail) { + const node = createBaseNode(SyntaxKind.TemplateSpan); + node.expression = expression; + node.literal = literal; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.literal) | + TransformFlags.ContainsES2015; + return node; + } - // @api - function updateTemplateSpan(node: TemplateSpan, expression: Expression, literal: TemplateMiddle | TemplateTail) { - return node.expression !== expression - || node.literal !== literal - ? update(createTemplateSpan(expression, literal), node) - : node; - } + // @api + function updateTemplateSpan(node: TemplateSpan, expression: Expression, literal: TemplateMiddle | TemplateTail) { + return node.expression !== expression + || node.literal !== literal + ? update(createTemplateSpan(expression, literal), node) + : node; + } - // @api - function createSemicolonClassElement() { - const node = createBaseNode(SyntaxKind.SemicolonClassElement); - node.transformFlags |= TransformFlags.ContainsES2015; - return node; - } + // @api + function createSemicolonClassElement() { + const node = createBaseNode(SyntaxKind.SemicolonClassElement); + node.transformFlags |= TransformFlags.ContainsES2015; + return node; + } - // - // Element - // + // + // Element + // - // @api - function createBlock(statements: readonly Statement[], multiLine?: boolean): Block { - const node = createBaseNode(SyntaxKind.Block); - node.statements = createNodeArray(statements); - node.multiLine = multiLine; - node.transformFlags |= propagateChildrenFlags(node.statements); - return node; - } + // @api + function createBlock(statements: readonly Statement[], multiLine?: boolean): Block { + const node = createBaseNode(SyntaxKind.Block); + node.statements = createNodeArray(statements); + node.multiLine = multiLine; + node.transformFlags |= propagateChildrenFlags(node.statements); + return node; + } + + // @api + function updateBlock(node: Block, statements: readonly Statement[]) { + return node.statements !== statements + ? update(createBlock(statements, node.multiLine), node) + : node; + } - // @api - function updateBlock(node: Block, statements: readonly Statement[]) { - return node.statements !== statements - ? update(createBlock(statements, node.multiLine), node) - : node; + // @api + function createVariableStatement(modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList | readonly VariableDeclaration[]) { + const node = createBaseDeclaration(SyntaxKind.VariableStatement); + node.modifiers = asNodeArray(modifiers); + node.declarationList = isArray(declarationList) ? createVariableDeclarationList(declarationList) : declarationList; + node.transformFlags |= + propagateChildrenFlags(node.modifiers) | + propagateChildFlags(node.declarationList); + if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { + node.transformFlags = TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function createVariableStatement(modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList | readonly VariableDeclaration[]) { - const node = createBaseDeclaration(SyntaxKind.VariableStatement); - node.modifiers = asNodeArray(modifiers); - node.declarationList = isArray(declarationList) ? createVariableDeclarationList(declarationList) : declarationList; - node.transformFlags |= - propagateChildrenFlags(node.modifiers) | - propagateChildFlags(node.declarationList); - if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { - node.transformFlags = TransformFlags.ContainsTypeScript; - } - return node; - } + // @api + function updateVariableStatement(node: VariableStatement, modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList) { + return node.modifiers !== modifiers + || node.declarationList !== declarationList + ? update(createVariableStatement(modifiers, declarationList), node) + : node; + } - // @api - function updateVariableStatement(node: VariableStatement, modifiers: readonly Modifier[] | undefined, declarationList: VariableDeclarationList) { - return node.modifiers !== modifiers - || node.declarationList !== declarationList - ? update(createVariableStatement(modifiers, declarationList), node) - : node; - } + // @api + function createEmptyStatement() { + return createBaseNode(SyntaxKind.EmptyStatement); + } - // @api - function createEmptyStatement() { - return createBaseNode(SyntaxKind.EmptyStatement); - } + // @api + function createExpressionStatement(expression: Expression): ExpressionStatement { + const node = createBaseNode(SyntaxKind.ExpressionStatement); + node.expression = parenthesizerRules().parenthesizeExpressionOfExpressionStatement(expression); + node.transformFlags |= propagateChildFlags(node.expression); + return node; + } - // @api - function createExpressionStatement(expression: Expression): ExpressionStatement { - const node = createBaseNode(SyntaxKind.ExpressionStatement); - node.expression = parenthesizerRules().parenthesizeExpressionOfExpressionStatement(expression); - node.transformFlags |= propagateChildFlags(node.expression); - return node; - } + // @api + function updateExpressionStatement(node: ExpressionStatement, expression: Expression) { + return node.expression !== expression + ? update(createExpressionStatement(expression), node) + : node; + } - // @api - function updateExpressionStatement(node: ExpressionStatement, expression: Expression) { - return node.expression !== expression - ? update(createExpressionStatement(expression), node) - : node; - } + // @api + function createIfStatement(expression: Expression, thenStatement: Statement, elseStatement?: Statement) { + const node = createBaseNode(SyntaxKind.IfStatement); + node.expression = expression; + node.thenStatement = asEmbeddedStatement(thenStatement); + node.elseStatement = asEmbeddedStatement(elseStatement); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.thenStatement) | + propagateChildFlags(node.elseStatement); + return node; + } - // @api - function createIfStatement(expression: Expression, thenStatement: Statement, elseStatement?: Statement) { - const node = createBaseNode(SyntaxKind.IfStatement); - node.expression = expression; - node.thenStatement = asEmbeddedStatement(thenStatement); - node.elseStatement = asEmbeddedStatement(elseStatement); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.thenStatement) | - propagateChildFlags(node.elseStatement); - return node; - } + // @api + function updateIfStatement(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement | undefined) { + return node.expression !== expression + || node.thenStatement !== thenStatement + || node.elseStatement !== elseStatement + ? update(createIfStatement(expression, thenStatement, elseStatement), node) + : node; + } - // @api - function updateIfStatement(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement | undefined) { - return node.expression !== expression - || node.thenStatement !== thenStatement - || node.elseStatement !== elseStatement - ? update(createIfStatement(expression, thenStatement, elseStatement), node) - : node; - } + // @api + function createDoStatement(statement: Statement, expression: Expression) { + const node = createBaseNode(SyntaxKind.DoStatement); + node.statement = asEmbeddedStatement(statement); + node.expression = expression; + node.transformFlags |= + propagateChildFlags(node.statement) | + propagateChildFlags(node.expression); + return node; + } - // @api - function createDoStatement(statement: Statement, expression: Expression) { - const node = createBaseNode(SyntaxKind.DoStatement); - node.statement = asEmbeddedStatement(statement); - node.expression = expression; - node.transformFlags |= - propagateChildFlags(node.statement) | - propagateChildFlags(node.expression); - return node; - } + // @api + function updateDoStatement(node: DoStatement, statement: Statement, expression: Expression) { + return node.statement !== statement + || node.expression !== expression + ? update(createDoStatement(statement, expression), node) + : node; + } - // @api - function updateDoStatement(node: DoStatement, statement: Statement, expression: Expression) { - return node.statement !== statement - || node.expression !== expression - ? update(createDoStatement(statement, expression), node) - : node; - } + // @api + function createWhileStatement(expression: Expression, statement: Statement) { + const node = createBaseNode(SyntaxKind.WhileStatement); + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.statement); + return node; + } - // @api - function createWhileStatement(expression: Expression, statement: Statement) { - const node = createBaseNode(SyntaxKind.WhileStatement); - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.statement); - return node; - } + // @api + function updateWhileStatement(node: WhileStatement, expression: Expression, statement: Statement) { + return node.expression !== expression + || node.statement !== statement + ? update(createWhileStatement(expression, statement), node) + : node; + } - // @api - function updateWhileStatement(node: WhileStatement, expression: Expression, statement: Statement) { - return node.expression !== expression - || node.statement !== statement - ? update(createWhileStatement(expression, statement), node) - : node; - } + // @api + function createForStatement(initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { + const node = createBaseNode(SyntaxKind.ForStatement); + node.initializer = initializer; + node.condition = condition; + node.incrementor = incrementor; + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.initializer) | + propagateChildFlags(node.condition) | + propagateChildFlags(node.incrementor) | + propagateChildFlags(node.statement); + return node; + } - // @api - function createForStatement(initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { - const node = createBaseNode(SyntaxKind.ForStatement); - node.initializer = initializer; - node.condition = condition; - node.incrementor = incrementor; - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.initializer) | - propagateChildFlags(node.condition) | - propagateChildFlags(node.incrementor) | - propagateChildFlags(node.statement); - return node; - } + // @api + function updateForStatement(node: ForStatement, initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { + return node.initializer !== initializer + || node.condition !== condition + || node.incrementor !== incrementor + || node.statement !== statement + ? update(createForStatement(initializer, condition, incrementor, statement), node) + : node; + } - // @api - function updateForStatement(node: ForStatement, initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement) { - return node.initializer !== initializer - || node.condition !== condition - || node.incrementor !== incrementor - || node.statement !== statement - ? update(createForStatement(initializer, condition, incrementor, statement), node) - : node; - } + // @api + function createForInStatement(initializer: ForInitializer, expression: Expression, statement: Statement) { + const node = createBaseNode(SyntaxKind.ForInStatement); + node.initializer = initializer; + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.initializer) | + propagateChildFlags(node.expression) | + propagateChildFlags(node.statement); + return node; + } - // @api - function createForInStatement(initializer: ForInitializer, expression: Expression, statement: Statement) { - const node = createBaseNode(SyntaxKind.ForInStatement); - node.initializer = initializer; - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.initializer) | - propagateChildFlags(node.expression) | - propagateChildFlags(node.statement); - return node; - } + // @api + function updateForInStatement(node: ForInStatement, initializer: ForInitializer, expression: Expression, statement: Statement) { + return node.initializer !== initializer + || node.expression !== expression + || node.statement !== statement + ? update(createForInStatement(initializer, expression, statement), node) + : node; + } - // @api - function updateForInStatement(node: ForInStatement, initializer: ForInitializer, expression: Expression, statement: Statement) { - return node.initializer !== initializer - || node.expression !== expression - || node.statement !== statement - ? update(createForInStatement(initializer, expression, statement), node) - : node; - } + // @api + function createForOfStatement(awaitModifier: AwaitKeyword | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { + const node = createBaseNode(SyntaxKind.ForOfStatement); + node.awaitModifier = awaitModifier; + node.initializer = initializer; + node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.awaitModifier) | + propagateChildFlags(node.initializer) | + propagateChildFlags(node.expression) | + propagateChildFlags(node.statement) | + TransformFlags.ContainsES2015; + if (awaitModifier) node.transformFlags |= TransformFlags.ContainsES2018; + return node; + } - // @api - function createForOfStatement(awaitModifier: AwaitKeyword | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { - const node = createBaseNode(SyntaxKind.ForOfStatement); - node.awaitModifier = awaitModifier; - node.initializer = initializer; - node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.awaitModifier) | - propagateChildFlags(node.initializer) | - propagateChildFlags(node.expression) | - propagateChildFlags(node.statement) | - TransformFlags.ContainsES2015; - if (awaitModifier) node.transformFlags |= TransformFlags.ContainsES2018; - return node; - } + // @api + function updateForOfStatement(node: ForOfStatement, awaitModifier: AwaitKeyword | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { + return node.awaitModifier !== awaitModifier + || node.initializer !== initializer + || node.expression !== expression + || node.statement !== statement + ? update(createForOfStatement(awaitModifier, initializer, expression, statement), node) + : node; + } - // @api - function updateForOfStatement(node: ForOfStatement, awaitModifier: AwaitKeyword | undefined, initializer: ForInitializer, expression: Expression, statement: Statement) { - return node.awaitModifier !== awaitModifier - || node.initializer !== initializer - || node.expression !== expression - || node.statement !== statement - ? update(createForOfStatement(awaitModifier, initializer, expression, statement), node) - : node; - } + // @api + function createContinueStatement(label?: string | Identifier): ContinueStatement { + const node = createBaseNode(SyntaxKind.ContinueStatement); + node.label = asName(label); + node.transformFlags |= + propagateChildFlags(node.label) | + TransformFlags.ContainsHoistedDeclarationOrCompletion; + return node; + } - // @api - function createContinueStatement(label?: string | Identifier): ContinueStatement { - const node = createBaseNode(SyntaxKind.ContinueStatement); - node.label = asName(label); - node.transformFlags |= - propagateChildFlags(node.label) | - TransformFlags.ContainsHoistedDeclarationOrCompletion; - return node; - } + // @api + function updateContinueStatement(node: ContinueStatement, label: Identifier | undefined) { + return node.label !== label + ? update(createContinueStatement(label), node) + : node; + } - // @api - function updateContinueStatement(node: ContinueStatement, label: Identifier | undefined) { - return node.label !== label - ? update(createContinueStatement(label), node) - : node; - } + // @api + function createBreakStatement(label?: string | Identifier): BreakStatement { + const node = createBaseNode(SyntaxKind.BreakStatement); + node.label = asName(label); + node.transformFlags |= + propagateChildFlags(node.label) | + TransformFlags.ContainsHoistedDeclarationOrCompletion; + return node; + } - // @api - function createBreakStatement(label?: string | Identifier): BreakStatement { - const node = createBaseNode(SyntaxKind.BreakStatement); - node.label = asName(label); - node.transformFlags |= - propagateChildFlags(node.label) | - TransformFlags.ContainsHoistedDeclarationOrCompletion; - return node; - } + // @api + function updateBreakStatement(node: BreakStatement, label: Identifier | undefined) { + return node.label !== label + ? update(createBreakStatement(label), node) + : node; + } - // @api - function updateBreakStatement(node: BreakStatement, label: Identifier | undefined) { - return node.label !== label - ? update(createBreakStatement(label), node) - : node; - } + // @api + function createReturnStatement(expression?: Expression): ReturnStatement { + const node = createBaseNode(SyntaxKind.ReturnStatement); + node.expression = expression; + // return in an ES2018 async generator must be awaited + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsES2018 | + TransformFlags.ContainsHoistedDeclarationOrCompletion; + return node; + } - // @api - function createReturnStatement(expression?: Expression): ReturnStatement { - const node = createBaseNode(SyntaxKind.ReturnStatement); - node.expression = expression; - // return in an ES2018 async generator must be awaited - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsES2018 | - TransformFlags.ContainsHoistedDeclarationOrCompletion; - return node; - } + // @api + function updateReturnStatement(node: ReturnStatement, expression: Expression | undefined) { + return node.expression !== expression + ? update(createReturnStatement(expression), node) + : node; + } - // @api - function updateReturnStatement(node: ReturnStatement, expression: Expression | undefined) { - return node.expression !== expression - ? update(createReturnStatement(expression), node) - : node; - } + // @api + function createWithStatement(expression: Expression, statement: Statement) { + const node = createBaseNode(SyntaxKind.WithStatement); + node.expression = expression; + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.statement); + return node; + } - // @api - function createWithStatement(expression: Expression, statement: Statement) { - const node = createBaseNode(SyntaxKind.WithStatement); - node.expression = expression; - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.statement); - return node; - } + // @api + function updateWithStatement(node: WithStatement, expression: Expression, statement: Statement) { + return node.expression !== expression + || node.statement !== statement + ? update(createWithStatement(expression, statement), node) + : node; + } - // @api - function updateWithStatement(node: WithStatement, expression: Expression, statement: Statement) { - return node.expression !== expression - || node.statement !== statement - ? update(createWithStatement(expression, statement), node) - : node; - } + // @api + function createSwitchStatement(expression: Expression, caseBlock: CaseBlock): SwitchStatement { + const node = createBaseNode(SyntaxKind.SwitchStatement); + node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.caseBlock = caseBlock; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.caseBlock); + return node; + } - // @api - function createSwitchStatement(expression: Expression, caseBlock: CaseBlock): SwitchStatement { - const node = createBaseNode(SyntaxKind.SwitchStatement); - node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.caseBlock = caseBlock; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.caseBlock); - return node; - } + // @api + function updateSwitchStatement(node: SwitchStatement, expression: Expression, caseBlock: CaseBlock) { + return node.expression !== expression + || node.caseBlock !== caseBlock + ? update(createSwitchStatement(expression, caseBlock), node) + : node; + } - // @api - function updateSwitchStatement(node: SwitchStatement, expression: Expression, caseBlock: CaseBlock) { - return node.expression !== expression - || node.caseBlock !== caseBlock - ? update(createSwitchStatement(expression, caseBlock), node) - : node; - } + // @api + function createLabeledStatement(label: string | Identifier, statement: Statement) { + const node = createBaseNode(SyntaxKind.LabeledStatement); + node.label = asName(label); + node.statement = asEmbeddedStatement(statement); + node.transformFlags |= + propagateChildFlags(node.label) | + propagateChildFlags(node.statement); + return node; + } - // @api - function createLabeledStatement(label: string | Identifier, statement: Statement) { - const node = createBaseNode(SyntaxKind.LabeledStatement); - node.label = asName(label); - node.statement = asEmbeddedStatement(statement); - node.transformFlags |= - propagateChildFlags(node.label) | - propagateChildFlags(node.statement); - return node; - } + // @api + function updateLabeledStatement(node: LabeledStatement, label: Identifier, statement: Statement) { + return node.label !== label + || node.statement !== statement + ? update(createLabeledStatement(label, statement), node) + : node; + } - // @api - function updateLabeledStatement(node: LabeledStatement, label: Identifier, statement: Statement) { - return node.label !== label - || node.statement !== statement - ? update(createLabeledStatement(label, statement), node) - : node; - } + // @api + function createThrowStatement(expression: Expression) { + const node = createBaseNode(SyntaxKind.ThrowStatement); + node.expression = expression; + node.transformFlags |= propagateChildFlags(node.expression); + return node; + } - // @api - function createThrowStatement(expression: Expression) { - const node = createBaseNode(SyntaxKind.ThrowStatement); - node.expression = expression; - node.transformFlags |= propagateChildFlags(node.expression); - return node; - } + // @api + function updateThrowStatement(node: ThrowStatement, expression: Expression) { + return node.expression !== expression + ? update(createThrowStatement(expression), node) + : node; + } - // @api - function updateThrowStatement(node: ThrowStatement, expression: Expression) { - return node.expression !== expression - ? update(createThrowStatement(expression), node) - : node; - } + // @api + function createTryStatement(tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { + const node = createBaseNode(SyntaxKind.TryStatement); + node.tryBlock = tryBlock; + node.catchClause = catchClause; + node.finallyBlock = finallyBlock; + node.transformFlags |= + propagateChildFlags(node.tryBlock) | + propagateChildFlags(node.catchClause) | + propagateChildFlags(node.finallyBlock); + return node; + } - // @api - function createTryStatement(tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { - const node = createBaseNode(SyntaxKind.TryStatement); - node.tryBlock = tryBlock; - node.catchClause = catchClause; - node.finallyBlock = finallyBlock; - node.transformFlags |= - propagateChildFlags(node.tryBlock) | - propagateChildFlags(node.catchClause) | - propagateChildFlags(node.finallyBlock); - return node; - } + // @api + function updateTryStatement(node: TryStatement, tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { + return node.tryBlock !== tryBlock + || node.catchClause !== catchClause + || node.finallyBlock !== finallyBlock + ? update(createTryStatement(tryBlock, catchClause, finallyBlock), node) + : node; + } - // @api - function updateTryStatement(node: TryStatement, tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined) { - return node.tryBlock !== tryBlock - || node.catchClause !== catchClause - || node.finallyBlock !== finallyBlock - ? update(createTryStatement(tryBlock, catchClause, finallyBlock), node) - : node; - } + // @api + function createDebuggerStatement() { + return createBaseNode(SyntaxKind.DebuggerStatement); + } - // @api - function createDebuggerStatement() { - return createBaseNode(SyntaxKind.DebuggerStatement); + // @api + function createVariableDeclaration(name: string | BindingName, exclamationToken: ExclamationToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { + const node = createBaseVariableLikeDeclaration( + SyntaxKind.VariableDeclaration, + /*modifiers*/ undefined, + name, + type, + initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) + ); + node.exclamationToken = exclamationToken; + node.transformFlags |= propagateChildFlags(node.exclamationToken); + if (exclamationToken) { + node.transformFlags |= TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function createVariableDeclaration(name: string | BindingName, exclamationToken: ExclamationToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { - const node = createBaseVariableLikeDeclaration( - SyntaxKind.VariableDeclaration, - /*modifiers*/ undefined, - name, - type, - initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer) - ); - node.exclamationToken = exclamationToken; - node.transformFlags |= propagateChildFlags(node.exclamationToken); - if (exclamationToken) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - return node; - } + // @api + function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, exclamationToken: ExclamationToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { + return node.name !== name + || node.type !== type + || node.exclamationToken !== exclamationToken + || node.initializer !== initializer + ? update(createVariableDeclaration(name, exclamationToken, type, initializer), node) + : node; + } - // @api - function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, exclamationToken: ExclamationToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { - return node.name !== name - || node.type !== type - || node.exclamationToken !== exclamationToken - || node.initializer !== initializer - ? update(createVariableDeclaration(name, exclamationToken, type, initializer), node) - : node; + // @api + function createVariableDeclarationList(declarations: readonly VariableDeclaration[], flags = NodeFlags.None) { + const node = createBaseNode(SyntaxKind.VariableDeclarationList); + node.flags |= flags & NodeFlags.BlockScoped; + node.declarations = createNodeArray(declarations); + node.transformFlags |= + propagateChildrenFlags(node.declarations) | + TransformFlags.ContainsHoistedDeclarationOrCompletion; + if (flags & NodeFlags.BlockScoped) { + node.transformFlags |= + TransformFlags.ContainsES2015 | + TransformFlags.ContainsBlockScopedBinding; } + return node; + } + + // @api + function updateVariableDeclarationList(node: VariableDeclarationList, declarations: readonly VariableDeclaration[]) { + return node.declarations !== declarations + ? update(createVariableDeclarationList(declarations, node.flags), node) + : node; + } - // @api - function createVariableDeclarationList(declarations: readonly VariableDeclaration[], flags = NodeFlags.None) { - const node = createBaseNode(SyntaxKind.VariableDeclarationList); - node.flags |= flags & NodeFlags.BlockScoped; - node.declarations = createNodeArray(declarations); + // @api + function createFunctionDeclaration( + modifiers: readonly ModifierLike[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + const node = createBaseFunctionLikeDeclaration( + SyntaxKind.FunctionDeclaration, + modifiers, + name, + typeParameters, + parameters, + type, + body + ); + node.asteriskToken = asteriskToken; + if (!node.body || modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { + node.transformFlags = TransformFlags.ContainsTypeScript; + } + else { node.transformFlags |= - propagateChildrenFlags(node.declarations) | + propagateChildFlags(node.asteriskToken) | TransformFlags.ContainsHoistedDeclarationOrCompletion; - if (flags & NodeFlags.BlockScoped) { - node.transformFlags |= - TransformFlags.ContainsES2015 | - TransformFlags.ContainsBlockScopedBinding; + if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { + if (node.asteriskToken) { + node.transformFlags |= TransformFlags.ContainsES2018; + } + else { + node.transformFlags |= TransformFlags.ContainsES2017; + } + } + else if (node.asteriskToken) { + node.transformFlags |= TransformFlags.ContainsGenerator; } - return node; } - // @api - function updateVariableDeclarationList(node: VariableDeclarationList, declarations: readonly VariableDeclaration[]) { - return node.declarations !== declarations - ? update(createVariableDeclarationList(declarations, node.flags), node) - : node; - } + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + return node; + } - // @api - function createFunctionDeclaration( - modifiers: readonly ModifierLike[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - const node = createBaseFunctionLikeDeclaration( - SyntaxKind.FunctionDeclaration, - modifiers, - name, - typeParameters, - parameters, - type, - body - ); - node.asteriskToken = asteriskToken; - if (!node.body || modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { - node.transformFlags = TransformFlags.ContainsTypeScript; - } - else { - node.transformFlags |= - propagateChildFlags(node.asteriskToken) | - TransformFlags.ContainsHoistedDeclarationOrCompletion; - if (modifiersToFlags(node.modifiers) & ModifierFlags.Async) { - if (node.asteriskToken) { - node.transformFlags |= TransformFlags.ContainsES2018; - } - else { - node.transformFlags |= TransformFlags.ContainsES2017; - } - } - else if (node.asteriskToken) { - node.transformFlags |= TransformFlags.ContainsGenerator; - } - } + // @api + function updateFunctionDeclaration( + node: FunctionDeclaration, + modifiers: readonly ModifierLike[] | undefined, + asteriskToken: AsteriskToken | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + parameters: readonly ParameterDeclaration[], + type: TypeNode | undefined, + body: Block | undefined + ) { + return node.modifiers !== modifiers + || node.asteriskToken !== asteriskToken + || node.name !== name + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.body !== body + ? finishUpdateFunctionDeclaration(createFunctionDeclaration(modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) + : node; + } - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - return node; + function finishUpdateFunctionDeclaration(updated: Mutable, original: FunctionDeclaration) { + if (updated !== original) { + // copy children used only for error reporting + updated.illegalDecorators = original.illegalDecorators; } + return finishUpdateBaseSignatureDeclaration(updated, original); + } - // @api - function updateFunctionDeclaration( - node: FunctionDeclaration, - modifiers: readonly ModifierLike[] | undefined, - asteriskToken: AsteriskToken | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - parameters: readonly ParameterDeclaration[], - type: TypeNode | undefined, - body: Block | undefined - ) { - return node.modifiers !== modifiers - || node.asteriskToken !== asteriskToken - || node.name !== name - || node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.body !== body - ? finishUpdateFunctionDeclaration(createFunctionDeclaration(modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) - : node; + // @api + function createClassDeclaration( + modifiers: readonly ModifierLike[] | undefined, + name: string | Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[] + ) { + const node = createBaseClassLikeDeclaration( + SyntaxKind.ClassDeclaration, + modifiers, + name, + typeParameters, + heritageClauses, + members + ); + if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { + node.transformFlags = TransformFlags.ContainsTypeScript; } - - function finishUpdateFunctionDeclaration(updated: Mutable, original: FunctionDeclaration) { - if (updated !== original) { - // copy children used only for error reporting - updated.illegalDecorators = original.illegalDecorators; + else { + node.transformFlags |= TransformFlags.ContainsES2015; + if (node.transformFlags & TransformFlags.ContainsTypeScriptClassSyntax) { + node.transformFlags |= TransformFlags.ContainsTypeScript; } - return finishUpdateBaseSignatureDeclaration(updated, original); } + return node; + } - // @api - function createClassDeclaration( - modifiers: readonly ModifierLike[] | undefined, - name: string | Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[] - ) { - const node = createBaseClassLikeDeclaration( - SyntaxKind.ClassDeclaration, - modifiers, - name, - typeParameters, - heritageClauses, - members - ); - if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { - node.transformFlags = TransformFlags.ContainsTypeScript; - } - else { - node.transformFlags |= TransformFlags.ContainsES2015; - if (node.transformFlags & TransformFlags.ContainsTypeScriptClassSyntax) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - } - return node; - } + // @api + function updateClassDeclaration( + node: ClassDeclaration, + modifiers: readonly ModifierLike[] | undefined, + name: Identifier | undefined, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly ClassElement[] + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? update(createClassDeclaration(modifiers, name, typeParameters, heritageClauses, members), node) + : node; + } - // @api - function updateClassDeclaration( - node: ClassDeclaration, - modifiers: readonly ModifierLike[] | undefined, - name: Identifier | undefined, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly ClassElement[] - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? update(createClassDeclaration(modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } + // @api + function createInterfaceDeclaration( + modifiers: readonly Modifier[] | undefined, + name: string | Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly TypeElement[] + ) { + const node = createBaseInterfaceOrClassLikeDeclaration( + SyntaxKind.InterfaceDeclaration, + modifiers, + name, + typeParameters, + heritageClauses + ); + node.members = createNodeArray(members); + node.transformFlags = TransformFlags.ContainsTypeScript; + + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + return node; + } - // @api - function createInterfaceDeclaration( - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly TypeElement[] - ) { - const node = createBaseInterfaceOrClassLikeDeclaration( - SyntaxKind.InterfaceDeclaration, - modifiers, - name, - typeParameters, - heritageClauses - ); - node.members = createNodeArray(members); - node.transformFlags = TransformFlags.ContainsTypeScript; + // @api + function updateInterfaceDeclaration( + node: InterfaceDeclaration, + modifiers: readonly Modifier[] | undefined, + name: Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + heritageClauses: readonly HeritageClause[] | undefined, + members: readonly TypeElement[] + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.heritageClauses !== heritageClauses + || node.members !== members + ? finishUpdateInterfaceDeclaration(createInterfaceDeclaration(modifiers, name, typeParameters, heritageClauses, members), node) + : node; + } - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - return node; + function finishUpdateInterfaceDeclaration(updated: Mutable, original: InterfaceDeclaration) { + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; } + return update(updated, original); + } - // @api - function updateInterfaceDeclaration( - node: InterfaceDeclaration, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - heritageClauses: readonly HeritageClause[] | undefined, - members: readonly TypeElement[] - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.heritageClauses !== heritageClauses - || node.members !== members - ? finishUpdateInterfaceDeclaration(createInterfaceDeclaration(modifiers, name, typeParameters, heritageClauses, members), node) - : node; - } + // @api + function createTypeAliasDeclaration( + modifiers: readonly Modifier[] | undefined, + name: string | Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + type: TypeNode + ) { + const node = createBaseGenericNamedDeclaration( + SyntaxKind.TypeAliasDeclaration, + modifiers, + name, + typeParameters + ); + node.type = type; + node.transformFlags = TransformFlags.ContainsTypeScript; + + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + return node; + } - function finishUpdateInterfaceDeclaration(updated: Mutable, original: InterfaceDeclaration) { - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - } - return update(updated, original); + // @api + function updateTypeAliasDeclaration( + node: TypeAliasDeclaration, + modifiers: readonly Modifier[] | undefined, + name: Identifier, + typeParameters: readonly TypeParameterDeclaration[] | undefined, + type: TypeNode + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.typeParameters !== typeParameters + || node.type !== type + ? finishUpdateTypeAliasDeclaration(createTypeAliasDeclaration(modifiers, name, typeParameters, type), node) + : node; + } + + function finishUpdateTypeAliasDeclaration(updated: Mutable, original: TypeAliasDeclaration) { + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; } + return update(updated, original); + } - // @api - function createTypeAliasDeclaration( - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - type: TypeNode - ) { - const node = createBaseGenericNamedDeclaration( - SyntaxKind.TypeAliasDeclaration, - modifiers, - name, - typeParameters - ); - node.type = type; - node.transformFlags = TransformFlags.ContainsTypeScript; + // @api + function createEnumDeclaration( + modifiers: readonly Modifier[] | undefined, + name: string | Identifier, + members: readonly EnumMember[] + ) { + const node = createBaseNamedDeclaration( + SyntaxKind.EnumDeclaration, + modifiers, + name + ); + node.members = createNodeArray(members); + node.transformFlags |= + propagateChildrenFlags(node.members) | + TransformFlags.ContainsTypeScript; + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Enum declarations cannot contain `await` + + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + return node; + } - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - return node; - } + // @api + function updateEnumDeclaration( + node: EnumDeclaration, + modifiers: readonly Modifier[] | undefined, + name: Identifier, + members: readonly EnumMember[]) { + return node.modifiers !== modifiers + || node.name !== name + || node.members !== members + ? finishUpdateEnumDeclaration(createEnumDeclaration(modifiers, name, members), node) + : node; + } - // @api - function updateTypeAliasDeclaration( - node: TypeAliasDeclaration, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - typeParameters: readonly TypeParameterDeclaration[] | undefined, - type: TypeNode - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.typeParameters !== typeParameters - || node.type !== type - ? finishUpdateTypeAliasDeclaration(createTypeAliasDeclaration(modifiers, name, typeParameters, type), node) - : node; + function finishUpdateEnumDeclaration(updated: Mutable, original: EnumDeclaration) { + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; } + return update(updated, original); + } - function finishUpdateTypeAliasDeclaration(updated: Mutable, original: TypeAliasDeclaration) { - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - } - return update(updated, original); + // @api + function createModuleDeclaration( + modifiers: readonly Modifier[] | undefined, + name: ModuleName, + body: ModuleBody | undefined, + flags = NodeFlags.None + ) { + const node = createBaseDeclaration(SyntaxKind.ModuleDeclaration); + node.modifiers = asNodeArray(modifiers); + node.flags |= flags & (NodeFlags.Namespace | NodeFlags.NestedNamespace | NodeFlags.GlobalAugmentation); + node.name = name; + node.body = body; + if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { + node.transformFlags = TransformFlags.ContainsTypeScript; } - - // @api - function createEnumDeclaration( - modifiers: readonly Modifier[] | undefined, - name: string | Identifier, - members: readonly EnumMember[] - ) { - const node = createBaseNamedDeclaration( - SyntaxKind.EnumDeclaration, - modifiers, - name - ); - node.members = createNodeArray(members); + else { node.transformFlags |= - propagateChildrenFlags(node.members) | + propagateChildrenFlags(node.modifiers) | + propagateChildFlags(node.name) | + propagateChildFlags(node.body) | TransformFlags.ContainsTypeScript; - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Enum declarations cannot contain `await` - - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - return node; - } - - // @api - function updateEnumDeclaration( - node: EnumDeclaration, - modifiers: readonly Modifier[] | undefined, - name: Identifier, - members: readonly EnumMember[]) { - return node.modifiers !== modifiers - || node.name !== name - || node.members !== members - ? finishUpdateEnumDeclaration(createEnumDeclaration(modifiers, name, members), node) - : node; - } - - function finishUpdateEnumDeclaration(updated: Mutable, original: EnumDeclaration) { - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - } - return update(updated, original); } + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Module declarations cannot contain `await`. - // @api - function createModuleDeclaration( - modifiers: readonly Modifier[] | undefined, - name: ModuleName, - body: ModuleBody | undefined, - flags = NodeFlags.None - ) { - const node = createBaseDeclaration(SyntaxKind.ModuleDeclaration); - node.modifiers = asNodeArray(modifiers); - node.flags |= flags & (NodeFlags.Namespace | NodeFlags.NestedNamespace | NodeFlags.GlobalAugmentation); - node.name = name; - node.body = body; - if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) { - node.transformFlags = TransformFlags.ContainsTypeScript; - } - else { - node.transformFlags |= - propagateChildrenFlags(node.modifiers) | - propagateChildFlags(node.name) | - propagateChildFlags(node.body) | - TransformFlags.ContainsTypeScript; - } - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Module declarations cannot contain `await`. + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + return node; + } - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - return node; - } + // @api + function updateModuleDeclaration( + node: ModuleDeclaration, + modifiers: readonly Modifier[] | undefined, + name: ModuleName, + body: ModuleBody | undefined + ) { + return node.modifiers !== modifiers + || node.name !== name + || node.body !== body + ? finishUpdateModuleDeclaration(createModuleDeclaration(modifiers, name, body, node.flags), node) + : node; + } - // @api - function updateModuleDeclaration( - node: ModuleDeclaration, - modifiers: readonly Modifier[] | undefined, - name: ModuleName, - body: ModuleBody | undefined - ) { - return node.modifiers !== modifiers - || node.name !== name - || node.body !== body - ? finishUpdateModuleDeclaration(createModuleDeclaration(modifiers, name, body, node.flags), node) - : node; + function finishUpdateModuleDeclaration(updated: Mutable, original: ModuleDeclaration) { + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; } + return update(updated, original); + } - function finishUpdateModuleDeclaration(updated: Mutable, original: ModuleDeclaration) { - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - } - return update(updated, original); - } + // @api + function createModuleBlock(statements: readonly Statement[]) { + const node = createBaseNode(SyntaxKind.ModuleBlock); + node.statements = createNodeArray(statements); + node.transformFlags |= propagateChildrenFlags(node.statements); + return node; + } - // @api - function createModuleBlock(statements: readonly Statement[]) { - const node = createBaseNode(SyntaxKind.ModuleBlock); - node.statements = createNodeArray(statements); - node.transformFlags |= propagateChildrenFlags(node.statements); - return node; - } + // @api + function updateModuleBlock(node: ModuleBlock, statements: readonly Statement[]) { + return node.statements !== statements + ? update(createModuleBlock(statements), node) + : node; + } - // @api - function updateModuleBlock(node: ModuleBlock, statements: readonly Statement[]) { - return node.statements !== statements - ? update(createModuleBlock(statements), node) - : node; - } + // @api + function createCaseBlock(clauses: readonly CaseOrDefaultClause[]): CaseBlock { + const node = createBaseNode(SyntaxKind.CaseBlock); + node.clauses = createNodeArray(clauses); + node.transformFlags |= propagateChildrenFlags(node.clauses); + return node; + } - // @api - function createCaseBlock(clauses: readonly CaseOrDefaultClause[]): CaseBlock { - const node = createBaseNode(SyntaxKind.CaseBlock); - node.clauses = createNodeArray(clauses); - node.transformFlags |= propagateChildrenFlags(node.clauses); - return node; - } + // @api + function updateCaseBlock(node: CaseBlock, clauses: readonly CaseOrDefaultClause[]) { + return node.clauses !== clauses + ? update(createCaseBlock(clauses), node) + : node; + } - // @api - function updateCaseBlock(node: CaseBlock, clauses: readonly CaseOrDefaultClause[]) { - return node.clauses !== clauses - ? update(createCaseBlock(clauses), node) - : node; - } + // @api + function createNamespaceExportDeclaration(name: string | Identifier) { + const node = createBaseNamedDeclaration( + SyntaxKind.NamespaceExportDeclaration, + /*modifiers*/ undefined, + name + ); + node.transformFlags = TransformFlags.ContainsTypeScript; + + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + node.modifiers = undefined; + return node; + } - // @api - function createNamespaceExportDeclaration(name: string | Identifier) { - const node = createBaseNamedDeclaration( - SyntaxKind.NamespaceExportDeclaration, - /*modifiers*/ undefined, - name - ); - node.transformFlags = TransformFlags.ContainsTypeScript; + // @api + function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier) { + return node.name !== name + ? finishUpdateNamespaceExportDeclaration(createNamespaceExportDeclaration(name), node) + : node; + } - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - node.modifiers = undefined; - return node; + function finishUpdateNamespaceExportDeclaration(updated: Mutable, original: NamespaceExportDeclaration) { + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; + updated.modifiers = original.modifiers; } + return update(updated, original); + } - // @api - function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier) { - return node.name !== name - ? finishUpdateNamespaceExportDeclaration(createNamespaceExportDeclaration(name), node) - : node; - } + // @api + function createImportEqualsDeclaration( + modifiers: readonly Modifier[] | undefined, + isTypeOnly: boolean, + name: string | Identifier, + moduleReference: ModuleReference + ) { + const node = createBaseNamedDeclaration( + SyntaxKind.ImportEqualsDeclaration, + modifiers, + name + ); + node.isTypeOnly = isTypeOnly; + node.moduleReference = moduleReference; + node.transformFlags |= propagateChildFlags(node.moduleReference); + if (!isExternalModuleReference(node.moduleReference)) node.transformFlags |= TransformFlags.ContainsTypeScript; + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Import= declaration is always parsed in an Await context + + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + return node; + } - function finishUpdateNamespaceExportDeclaration(updated: Mutable, original: NamespaceExportDeclaration) { - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - updated.modifiers = original.modifiers; - } - return update(updated, original); - } + // @api + function updateImportEqualsDeclaration( + node: ImportEqualsDeclaration, + modifiers: readonly Modifier[] | undefined, + isTypeOnly: boolean, + name: Identifier, + moduleReference: ModuleReference + ) { + return node.modifiers !== modifiers + || node.isTypeOnly !== isTypeOnly + || node.name !== name + || node.moduleReference !== moduleReference + ? finishUpdateImportEqualsDeclaration(createImportEqualsDeclaration(modifiers, isTypeOnly, name, moduleReference), node) + : node; + } - // @api - function createImportEqualsDeclaration( - modifiers: readonly Modifier[] | undefined, - isTypeOnly: boolean, - name: string | Identifier, - moduleReference: ModuleReference - ) { - const node = createBaseNamedDeclaration( - SyntaxKind.ImportEqualsDeclaration, - modifiers, - name - ); - node.isTypeOnly = isTypeOnly; - node.moduleReference = moduleReference; - node.transformFlags |= propagateChildFlags(node.moduleReference); - if (!isExternalModuleReference(node.moduleReference)) node.transformFlags |= TransformFlags.ContainsTypeScript; - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // Import= declaration is always parsed in an Await context - - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - return node; + function finishUpdateImportEqualsDeclaration(updated: Mutable, original: ImportEqualsDeclaration) { + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; } + return update(updated, original); + } - // @api - function updateImportEqualsDeclaration( - node: ImportEqualsDeclaration, - modifiers: readonly Modifier[] | undefined, - isTypeOnly: boolean, - name: Identifier, - moduleReference: ModuleReference - ) { - return node.modifiers !== modifiers - || node.isTypeOnly !== isTypeOnly - || node.name !== name - || node.moduleReference !== moduleReference - ? finishUpdateImportEqualsDeclaration(createImportEqualsDeclaration(modifiers, isTypeOnly, name, moduleReference), node) - : node; - } + // @api + function createImportDeclaration( + modifiers: readonly Modifier[] | undefined, + importClause: ImportClause | undefined, + moduleSpecifier: Expression, + assertClause: AssertClause | undefined + ): ImportDeclaration { + const node = createBaseDeclaration(SyntaxKind.ImportDeclaration); + node.modifiers = asNodeArray(modifiers); + node.importClause = importClause; + node.moduleSpecifier = moduleSpecifier; + node.assertClause = assertClause; + node.transformFlags |= + propagateChildFlags(node.importClause) | + propagateChildFlags(node.moduleSpecifier); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + return node; + } - function finishUpdateImportEqualsDeclaration(updated: Mutable, original: ImportEqualsDeclaration) { - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - } - return update(updated, original); + // @api + function updateImportDeclaration( + node: ImportDeclaration, + modifiers: readonly Modifier[] | undefined, + importClause: ImportClause | undefined, + moduleSpecifier: Expression, + assertClause: AssertClause | undefined + ) { + return node.modifiers !== modifiers + || node.importClause !== importClause + || node.moduleSpecifier !== moduleSpecifier + || node.assertClause !== assertClause + ? finishUpdateImportDeclaration(createImportDeclaration(modifiers, importClause, moduleSpecifier, assertClause), node) + : node; + } + + function finishUpdateImportDeclaration(updated: Mutable, original: ImportDeclaration) { + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; } + return update(updated, original); + } - // @api - function createImportDeclaration( - modifiers: readonly Modifier[] | undefined, - importClause: ImportClause | undefined, - moduleSpecifier: Expression, - assertClause: AssertClause | undefined - ): ImportDeclaration { - const node = createBaseDeclaration(SyntaxKind.ImportDeclaration); - node.modifiers = asNodeArray(modifiers); - node.importClause = importClause; - node.moduleSpecifier = moduleSpecifier; - node.assertClause = assertClause; - node.transformFlags |= - propagateChildFlags(node.importClause) | - propagateChildFlags(node.moduleSpecifier); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + // @api + function createImportClause(isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause { + const node = createBaseNode(SyntaxKind.ImportClause); + node.isTypeOnly = isTypeOnly; + node.name = name; + node.namedBindings = namedBindings; + node.transformFlags |= + propagateChildFlags(node.name) | + propagateChildFlags(node.namedBindings); + if (isTypeOnly) { + node.transformFlags |= TransformFlags.ContainsTypeScript; + } + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - return node; - } + // @api + function updateImportClause(node: ImportClause, isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined) { + return node.isTypeOnly !== isTypeOnly + || node.name !== name + || node.namedBindings !== namedBindings + ? update(createImportClause(isTypeOnly, name, namedBindings), node) + : node; + } - // @api - function updateImportDeclaration( - node: ImportDeclaration, - modifiers: readonly Modifier[] | undefined, - importClause: ImportClause | undefined, - moduleSpecifier: Expression, - assertClause: AssertClause | undefined - ) { - return node.modifiers !== modifiers - || node.importClause !== importClause - || node.moduleSpecifier !== moduleSpecifier - || node.assertClause !== assertClause - ? finishUpdateImportDeclaration(createImportDeclaration(modifiers, importClause, moduleSpecifier, assertClause), node) - : node; - } + // @api + function createAssertClause(elements: readonly AssertEntry[], multiLine?: boolean): AssertClause { + const node = createBaseNode(SyntaxKind.AssertClause); + node.elements = createNodeArray(elements); + node.multiLine = multiLine; + node.transformFlags |= TransformFlags.ContainsESNext; + return node; + } - function finishUpdateImportDeclaration(updated: Mutable, original: ImportDeclaration) { - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - } - return update(updated, original); - } + // @api + function updateAssertClause(node: AssertClause, elements: readonly AssertEntry[], multiLine?: boolean): AssertClause { + return node.elements !== elements + || node.multiLine !== multiLine + ? update(createAssertClause(elements, multiLine), node) + : node; + } - // @api - function createImportClause(isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause { - const node = createBaseNode(SyntaxKind.ImportClause); - node.isTypeOnly = isTypeOnly; - node.name = name; - node.namedBindings = namedBindings; - node.transformFlags |= - propagateChildFlags(node.name) | - propagateChildFlags(node.namedBindings); - if (isTypeOnly) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createAssertEntry(name: AssertionKey, value: Expression): AssertEntry { + const node = createBaseNode(SyntaxKind.AssertEntry); + node.name = name; + node.value = value; + node.transformFlags |= TransformFlags.ContainsESNext; + return node; + } - // @api - function updateImportClause(node: ImportClause, isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined) { - return node.isTypeOnly !== isTypeOnly - || node.name !== name - || node.namedBindings !== namedBindings - ? update(createImportClause(isTypeOnly, name, namedBindings), node) - : node; - } + // @api + function updateAssertEntry(node: AssertEntry, name: AssertionKey, value: Expression): AssertEntry { + return node.name !== name + || node.value !== value + ? update(createAssertEntry(name, value), node) + : node; + } - // @api - function createAssertClause(elements: readonly AssertEntry[], multiLine?: boolean): AssertClause { - const node = createBaseNode(SyntaxKind.AssertClause); - node.elements = createNodeArray(elements); - node.multiLine = multiLine; - node.transformFlags |= TransformFlags.ContainsESNext; - return node; - } + // @api + function createImportTypeAssertionContainer(clause: AssertClause, multiLine?: boolean): ImportTypeAssertionContainer { + const node = createBaseNode(SyntaxKind.ImportTypeAssertionContainer); + node.assertClause = clause; + node.multiLine = multiLine; + return node; + } - // @api - function updateAssertClause(node: AssertClause, elements: readonly AssertEntry[], multiLine?: boolean): AssertClause { - return node.elements !== elements - || node.multiLine !== multiLine - ? update(createAssertClause(elements, multiLine), node) - : node; - } + // @api + function updateImportTypeAssertionContainer(node: ImportTypeAssertionContainer, clause: AssertClause, multiLine?: boolean): ImportTypeAssertionContainer { + return node.assertClause !== clause + || node.multiLine !== multiLine + ? update(createImportTypeAssertionContainer(clause, multiLine), node) + : node; + } - // @api - function createAssertEntry(name: AssertionKey, value: Expression): AssertEntry { - const node = createBaseNode(SyntaxKind.AssertEntry); - node.name = name; - node.value = value; - node.transformFlags |= TransformFlags.ContainsESNext; - return node; - } + // @api + function createNamespaceImport(name: Identifier): NamespaceImport { + const node = createBaseNode(SyntaxKind.NamespaceImport); + node.name = name; + node.transformFlags |= propagateChildFlags(node.name); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function updateAssertEntry(node: AssertEntry, name: AssertionKey, value: Expression): AssertEntry { - return node.name !== name - || node.value !== value - ? update(createAssertEntry(name, value), node) - : node; - } + // @api + function updateNamespaceImport(node: NamespaceImport, name: Identifier) { + return node.name !== name + ? update(createNamespaceImport(name), node) + : node; + } - // @api - function createImportTypeAssertionContainer(clause: AssertClause, multiLine?: boolean): ImportTypeAssertionContainer { - const node = createBaseNode(SyntaxKind.ImportTypeAssertionContainer); - node.assertClause = clause; - node.multiLine = multiLine; - return node; - } + // @api + function createNamespaceExport(name: Identifier): NamespaceExport { + const node = createBaseNode(SyntaxKind.NamespaceExport); + node.name = name; + node.transformFlags |= + propagateChildFlags(node.name) | + TransformFlags.ContainsESNext; + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function updateImportTypeAssertionContainer(node: ImportTypeAssertionContainer, clause: AssertClause, multiLine?: boolean): ImportTypeAssertionContainer { - return node.assertClause !== clause - || node.multiLine !== multiLine - ? update(createImportTypeAssertionContainer(clause, multiLine), node) - : node; - } + // @api + function updateNamespaceExport(node: NamespaceExport, name: Identifier) { + return node.name !== name + ? update(createNamespaceExport(name), node) + : node; + } - // @api - function createNamespaceImport(name: Identifier): NamespaceImport { - const node = createBaseNode(SyntaxKind.NamespaceImport); - node.name = name; - node.transformFlags |= propagateChildFlags(node.name); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createNamedImports(elements: readonly ImportSpecifier[]): NamedImports { + const node = createBaseNode(SyntaxKind.NamedImports); + node.elements = createNodeArray(elements); + node.transformFlags |= propagateChildrenFlags(node.elements); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function updateNamespaceImport(node: NamespaceImport, name: Identifier) { - return node.name !== name - ? update(createNamespaceImport(name), node) - : node; - } + // @api + function updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]) { + return node.elements !== elements + ? update(createNamedImports(elements), node) + : node; + } - // @api - function createNamespaceExport(name: Identifier): NamespaceExport { - const node = createBaseNode(SyntaxKind.NamespaceExport); - node.name = name; - node.transformFlags |= - propagateChildFlags(node.name) | - TransformFlags.ContainsESNext; - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { + const node = createBaseNode(SyntaxKind.ImportSpecifier); + node.isTypeOnly = isTypeOnly; + node.propertyName = propertyName; + node.name = name; + node.transformFlags |= + propagateChildFlags(node.propertyName) | + propagateChildFlags(node.name); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function updateNamespaceExport(node: NamespaceExport, name: Identifier) { - return node.name !== name - ? update(createNamespaceExport(name), node) - : node; - } + // @api + function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { + return node.isTypeOnly !== isTypeOnly + || node.propertyName !== propertyName + || node.name !== name + ? update(createImportSpecifier(isTypeOnly, propertyName, name), node) + : node; + } - // @api - function createNamedImports(elements: readonly ImportSpecifier[]): NamedImports { - const node = createBaseNode(SyntaxKind.NamedImports); - node.elements = createNodeArray(elements); - node.transformFlags |= propagateChildrenFlags(node.elements); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function createExportAssignment( + modifiers: readonly Modifier[] | undefined, + isExportEquals: boolean | undefined, + expression: Expression + ) { + const node = createBaseDeclaration(SyntaxKind.ExportAssignment); + node.modifiers = asNodeArray(modifiers); + node.isExportEquals = isExportEquals; + node.expression = isExportEquals + ? parenthesizerRules().parenthesizeRightSideOfBinary(SyntaxKind.EqualsToken, /*leftSide*/ undefined, expression) + : parenthesizerRules().parenthesizeExpressionOfExportDefault(expression); + node.transformFlags |= propagateChildrenFlags(node.modifiers) | propagateChildFlags(node.expression); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + return node; + } - // @api - function updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]) { - return node.elements !== elements - ? update(createNamedImports(elements), node) - : node; - } + // @api + function updateExportAssignment( + node: ExportAssignment, + modifiers: readonly Modifier[] | undefined, + expression: Expression + ) { + return node.modifiers !== modifiers + || node.expression !== expression + ? finishUpdateExportAssignment(createExportAssignment(modifiers, node.isExportEquals, expression), node) + : node; + } - // @api - function createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { - const node = createBaseNode(SyntaxKind.ImportSpecifier); - node.isTypeOnly = isTypeOnly; - node.propertyName = propertyName; - node.name = name; - node.transformFlags |= - propagateChildFlags(node.propertyName) | - propagateChildFlags(node.name); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; + function finishUpdateExportAssignment(updated: Mutable, original: ExportAssignment) { + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; } + return update(updated, original); + } - // @api - function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { - return node.isTypeOnly !== isTypeOnly - || node.propertyName !== propertyName - || node.name !== name - ? update(createImportSpecifier(isTypeOnly, propertyName, name), node) - : node; - } + // @api + function createExportDeclaration( + modifiers: readonly Modifier[] | undefined, + isTypeOnly: boolean, + exportClause: NamedExportBindings | undefined, + moduleSpecifier?: Expression, + assertClause?: AssertClause + ) { + const node = createBaseDeclaration(SyntaxKind.ExportDeclaration); + node.modifiers = asNodeArray(modifiers); + node.isTypeOnly = isTypeOnly; + node.exportClause = exportClause; + node.moduleSpecifier = moduleSpecifier; + node.assertClause = assertClause; + node.transformFlags |= + propagateChildrenFlags(node.modifiers) | + propagateChildFlags(node.exportClause) | + propagateChildFlags(node.moduleSpecifier); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + return node; + } - // @api - function createExportAssignment( - modifiers: readonly Modifier[] | undefined, - isExportEquals: boolean | undefined, - expression: Expression - ) { - const node = createBaseDeclaration(SyntaxKind.ExportAssignment); - node.modifiers = asNodeArray(modifiers); - node.isExportEquals = isExportEquals; - node.expression = isExportEquals - ? parenthesizerRules().parenthesizeRightSideOfBinary(SyntaxKind.EqualsToken, /*leftSide*/ undefined, expression) - : parenthesizerRules().parenthesizeExpressionOfExportDefault(expression); - node.transformFlags |= propagateChildrenFlags(node.modifiers) | propagateChildFlags(node.expression); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - return node; - } + // @api + function updateExportDeclaration( + node: ExportDeclaration, + modifiers: readonly Modifier[] | undefined, + isTypeOnly: boolean, + exportClause: NamedExportBindings | undefined, + moduleSpecifier: Expression | undefined, + assertClause: AssertClause | undefined + ) { + return node.modifiers !== modifiers + || node.isTypeOnly !== isTypeOnly + || node.exportClause !== exportClause + || node.moduleSpecifier !== moduleSpecifier + || node.assertClause !== assertClause + ? finishUpdateExportDeclaration(createExportDeclaration(modifiers, isTypeOnly, exportClause, moduleSpecifier, assertClause), node) + : node; + } - // @api - function updateExportAssignment( - node: ExportAssignment, - modifiers: readonly Modifier[] | undefined, - expression: Expression - ) { - return node.modifiers !== modifiers - || node.expression !== expression - ? finishUpdateExportAssignment(createExportAssignment(modifiers, node.isExportEquals, expression), node) - : node; + function finishUpdateExportDeclaration(updated: Mutable, original: ExportDeclaration) { + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; } + return update(updated, original); + } - function finishUpdateExportAssignment(updated: Mutable, original: ExportAssignment) { - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - } - return update(updated, original); - } + // @api + function createNamedExports(elements: readonly ExportSpecifier[]) { + const node = createBaseNode(SyntaxKind.NamedExports); + node.elements = createNodeArray(elements); + node.transformFlags |= propagateChildrenFlags(node.elements); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function createExportDeclaration( - modifiers: readonly Modifier[] | undefined, - isTypeOnly: boolean, - exportClause: NamedExportBindings | undefined, - moduleSpecifier?: Expression, - assertClause?: AssertClause - ) { - const node = createBaseDeclaration(SyntaxKind.ExportDeclaration); - node.modifiers = asNodeArray(modifiers); - node.isTypeOnly = isTypeOnly; - node.exportClause = exportClause; - node.moduleSpecifier = moduleSpecifier; - node.assertClause = assertClause; - node.transformFlags |= - propagateChildrenFlags(node.modifiers) | - propagateChildFlags(node.exportClause) | - propagateChildFlags(node.moduleSpecifier); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + // @api + function updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]) { + return node.elements !== elements + ? update(createNamedExports(elements), node) + : node; + } - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - return node; - } + // @api + function createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) { + const node = createBaseNode(SyntaxKind.ExportSpecifier); + node.isTypeOnly = isTypeOnly; + node.propertyName = asName(propertyName); + node.name = asName(name); + node.transformFlags |= + propagateChildFlags(node.propertyName) | + propagateChildFlags(node.name); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function updateExportDeclaration( - node: ExportDeclaration, - modifiers: readonly Modifier[] | undefined, - isTypeOnly: boolean, - exportClause: NamedExportBindings | undefined, - moduleSpecifier: Expression | undefined, - assertClause: AssertClause | undefined - ) { - return node.modifiers !== modifiers - || node.isTypeOnly !== isTypeOnly - || node.exportClause !== exportClause - || node.moduleSpecifier !== moduleSpecifier - || node.assertClause !== assertClause - ? finishUpdateExportDeclaration(createExportDeclaration(modifiers, isTypeOnly, exportClause, moduleSpecifier, assertClause), node) - : node; - } + // @api + function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { + return node.isTypeOnly !== isTypeOnly + || node.propertyName !== propertyName + || node.name !== name + ? update(createExportSpecifier(isTypeOnly, propertyName, name), node) + : node; + } - function finishUpdateExportDeclaration(updated: Mutable, original: ExportDeclaration) { - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - } - return update(updated, original); - } + // @api + function createMissingDeclaration(): MissingDeclaration { + const node = createBaseDeclaration(SyntaxKind.MissingDeclaration); + return node; + } - // @api - function createNamedExports(elements: readonly ExportSpecifier[]) { - const node = createBaseNode(SyntaxKind.NamedExports); - node.elements = createNodeArray(elements); - node.transformFlags |= propagateChildrenFlags(node.elements); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // + // Module references + // - // @api - function updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]) { - return node.elements !== elements - ? update(createNamedExports(elements), node) - : node; - } + // @api + function createExternalModuleReference(expression: Expression) { + const node = createBaseNode(SyntaxKind.ExternalModuleReference); + node.expression = expression; + node.transformFlags |= propagateChildFlags(node.expression); + node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context + return node; + } - // @api - function createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) { - const node = createBaseNode(SyntaxKind.ExportSpecifier); - node.isTypeOnly = isTypeOnly; - node.propertyName = asName(propertyName); - node.name = asName(name); - node.transformFlags |= - propagateChildFlags(node.propertyName) | - propagateChildFlags(node.name); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression) { + return node.expression !== expression + ? update(createExternalModuleReference(expression), node) + : node; + } - // @api - function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { - return node.isTypeOnly !== isTypeOnly - || node.propertyName !== propertyName - || node.name !== name - ? update(createExportSpecifier(isTypeOnly, propertyName, name), node) - : node; - } + // + // JSDoc + // - // @api - function createMissingDeclaration(): MissingDeclaration { - const node = createBaseDeclaration(SyntaxKind.MissingDeclaration); - return node; - } + // @api + // createJSDocAllType + // createJSDocUnknownType + function createJSDocPrimaryTypeWorker(kind: T["kind"]) { + return createBaseNode(kind); + } - // - // Module references - // + // @api + // createJSDocNullableType + // createJSDocNonNullableType + function createJSDocPrePostfixUnaryTypeWorker(kind: T["kind"], type: T["type"], postfix = false): T { + const node = createJSDocUnaryTypeWorker( + kind, + postfix ? type && parenthesizerRules().parenthesizeNonArrayTypeOfPostfixType(type) : type + ) as Mutable; + node.postfix = postfix; + return node; + } - // @api - function createExternalModuleReference(expression: Expression) { - const node = createBaseNode(SyntaxKind.ExternalModuleReference); - node.expression = expression; - node.transformFlags |= propagateChildFlags(node.expression); - node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context - return node; - } + // @api + // createJSDocOptionalType + // createJSDocVariadicType + // createJSDocNamepathType + function createJSDocUnaryTypeWorker(kind: T["kind"], type: T["type"]): T { + const node = createBaseNode(kind); + node.type = type; + return node; + } - // @api - function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression) { - return node.expression !== expression - ? update(createExternalModuleReference(expression), node) - : node; - } + // @api + // updateJSDocNonNullableType + // updateJSDocNullableType + function updateJSDocPrePostfixUnaryTypeWorker(kind: T["kind"], node: T, type: T["type"]): T { + return node.type !== type + ? update(createJSDocPrePostfixUnaryTypeWorker(kind, type, node.postfix), node) + : node; + } - // - // JSDoc - // + // @api + // updateJSDocOptionalType + // updateJSDocVariadicType + // updateJSDocNamepathType + function updateJSDocUnaryTypeWorker(kind: T["kind"], node: T, type: T["type"]): T { + return node.type !== type + ? update(createJSDocUnaryTypeWorker(kind, type), node) + : node; + } - // @api - // createJSDocAllType - // createJSDocUnknownType - function createJSDocPrimaryTypeWorker(kind: T["kind"]) { - return createBaseNode(kind); - } + // @api + function createJSDocFunctionType(parameters: readonly ParameterDeclaration[], type: TypeNode | undefined): JSDocFunctionType { + const node = createBaseSignatureDeclaration( + SyntaxKind.JSDocFunctionType, + /*modifiers*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + parameters, + type + ); + return node; + } - // @api - // createJSDocNullableType - // createJSDocNonNullableType - function createJSDocPrePostfixUnaryTypeWorker(kind: T["kind"], type: T["type"], postfix = false): T { - const node = createJSDocUnaryTypeWorker( - kind, - postfix ? type && parenthesizerRules().parenthesizeNonArrayTypeOfPostfixType(type) : type - ) as Mutable; - node.postfix = postfix; - return node; - } + // @api + function updateJSDocFunctionType(node: JSDocFunctionType, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined): JSDocFunctionType { + return node.parameters !== parameters + || node.type !== type + ? update(createJSDocFunctionType(parameters, type), node) + : node; + } - // @api - // createJSDocOptionalType - // createJSDocVariadicType - // createJSDocNamepathType - function createJSDocUnaryTypeWorker(kind: T["kind"], type: T["type"]): T { - const node = createBaseNode(kind); - node.type = type; - return node; - } + // @api + function createJSDocTypeLiteral(propertyTags?: readonly JSDocPropertyLikeTag[], isArrayType = false): JSDocTypeLiteral { + const node = createBaseNode(SyntaxKind.JSDocTypeLiteral); + node.jsDocPropertyTags = asNodeArray(propertyTags); + node.isArrayType = isArrayType; + return node; + } - // @api - // updateJSDocNonNullableType - // updateJSDocNullableType - function updateJSDocPrePostfixUnaryTypeWorker(kind: T["kind"], node: T, type: T["type"]): T { - return node.type !== type - ? update(createJSDocPrePostfixUnaryTypeWorker(kind, type, node.postfix), node) + // @api + function updateJSDocTypeLiteral(node: JSDocTypeLiteral, propertyTags: readonly JSDocPropertyLikeTag[] | undefined, isArrayType: boolean): JSDocTypeLiteral { + return node.jsDocPropertyTags !== propertyTags + || node.isArrayType !== isArrayType + ? update(createJSDocTypeLiteral(propertyTags, isArrayType), node) : node; - } - - // @api - // updateJSDocOptionalType - // updateJSDocVariadicType - // updateJSDocNamepathType - function updateJSDocUnaryTypeWorker(kind: T["kind"], node: T, type: T["type"]): T { - return node.type !== type - ? update(createJSDocUnaryTypeWorker(kind, type), node) - : node; - } + } - // @api - function createJSDocFunctionType(parameters: readonly ParameterDeclaration[], type: TypeNode | undefined): JSDocFunctionType { - const node = createBaseSignatureDeclaration( - SyntaxKind.JSDocFunctionType, - /*modifiers*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - parameters, - type - ); - return node; - } + // @api + function createJSDocTypeExpression(type: TypeNode): JSDocTypeExpression { + const node = createBaseNode(SyntaxKind.JSDocTypeExpression); + node.type = type; + return node; + } - // @api - function updateJSDocFunctionType(node: JSDocFunctionType, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined): JSDocFunctionType { - return node.parameters !== parameters - || node.type !== type - ? update(createJSDocFunctionType(parameters, type), node) - : node; - } + // @api + function updateJSDocTypeExpression(node: JSDocTypeExpression, type: TypeNode): JSDocTypeExpression { + return node.type !== type + ? update(createJSDocTypeExpression(type), node) + : node; + } - // @api - function createJSDocTypeLiteral(propertyTags?: readonly JSDocPropertyLikeTag[], isArrayType = false): JSDocTypeLiteral { - const node = createBaseNode(SyntaxKind.JSDocTypeLiteral); - node.jsDocPropertyTags = asNodeArray(propertyTags); - node.isArrayType = isArrayType; - return node; - } + // @api + function createJSDocSignature(typeParameters: readonly JSDocTemplateTag[] | undefined, parameters: readonly JSDocParameterTag[], type?: JSDocReturnTag): JSDocSignature { + const node = createBaseNode(SyntaxKind.JSDocSignature); + node.typeParameters = asNodeArray(typeParameters); + node.parameters = createNodeArray(parameters); + node.type = type; + return node; + } - // @api - function updateJSDocTypeLiteral(node: JSDocTypeLiteral, propertyTags: readonly JSDocPropertyLikeTag[] | undefined, isArrayType: boolean): JSDocTypeLiteral { - return node.jsDocPropertyTags !== propertyTags - || node.isArrayType !== isArrayType - ? update(createJSDocTypeLiteral(propertyTags, isArrayType), node) - : node; - } + // @api + function updateJSDocSignature(node: JSDocSignature, typeParameters: readonly JSDocTemplateTag[] | undefined, parameters: readonly JSDocParameterTag[], type: JSDocReturnTag | undefined): JSDocSignature { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? update(createJSDocSignature(typeParameters, parameters, type), node) + : node; + } - // @api - function createJSDocTypeExpression(type: TypeNode): JSDocTypeExpression { - const node = createBaseNode(SyntaxKind.JSDocTypeExpression); - node.type = type; - return node; - } + function getDefaultTagName(node: JSDocTag) { + const defaultTagName = getDefaultTagNameForKind(node.kind); + return node.tagName.escapedText === escapeLeadingUnderscores(defaultTagName) + ? node.tagName + : createIdentifier(defaultTagName); + } - // @api - function updateJSDocTypeExpression(node: JSDocTypeExpression, type: TypeNode): JSDocTypeExpression { - return node.type !== type - ? update(createJSDocTypeExpression(type), node) - : node; - } + // @api + function createBaseJSDocTag(kind: T["kind"], tagName: Identifier, comment: string | NodeArray | undefined) { + const node = createBaseNode(kind); + node.tagName = tagName; + node.comment = comment; + return node; + } - // @api - function createJSDocSignature(typeParameters: readonly JSDocTemplateTag[] | undefined, parameters: readonly JSDocParameterTag[], type?: JSDocReturnTag): JSDocSignature { - const node = createBaseNode(SyntaxKind.JSDocSignature); - node.typeParameters = asNodeArray(typeParameters); - node.parameters = createNodeArray(parameters); - node.type = type; - return node; - } + // @api + function createJSDocTemplateTag(tagName: Identifier | undefined, constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment?: string | NodeArray): JSDocTemplateTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocTemplateTag, tagName ?? createIdentifier("template"), comment); + node.constraint = constraint; + node.typeParameters = createNodeArray(typeParameters); + return node; + } - // @api - function updateJSDocSignature(node: JSDocSignature, typeParameters: readonly JSDocTemplateTag[] | undefined, parameters: readonly JSDocParameterTag[], type: JSDocReturnTag | undefined): JSDocSignature { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? update(createJSDocSignature(typeParameters, parameters, type), node) - : node; - } + // @api + function updateJSDocTemplateTag(node: JSDocTemplateTag, tagName: Identifier = getDefaultTagName(node), constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment: string | NodeArray | undefined): JSDocTemplateTag { + return node.tagName !== tagName + || node.constraint !== constraint + || node.typeParameters !== typeParameters + || node.comment !== comment + ? update(createJSDocTemplateTag(tagName, constraint, typeParameters, comment), node) + : node; + } - function getDefaultTagName(node: JSDocTag) { - const defaultTagName = getDefaultTagNameForKind(node.kind); - return node.tagName.escapedText === escapeLeadingUnderscores(defaultTagName) - ? node.tagName - : createIdentifier(defaultTagName); - } + // @api + function createJSDocTypedefTag(tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray): JSDocTypedefTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocTypedefTag, tagName ?? createIdentifier("typedef"), comment); + node.typeExpression = typeExpression; + node.fullName = fullName; + node.name = getJSDocTypeAliasName(fullName); + return node; + } - // @api - function createBaseJSDocTag(kind: T["kind"], tagName: Identifier, comment: string | NodeArray | undefined) { - const node = createBaseNode(kind); - node.tagName = tagName; - node.comment = comment; - return node; - } + // @api + function updateJSDocTypedefTag(node: JSDocTypedefTag, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray | undefined): JSDocTypedefTag { + return node.tagName !== tagName + || node.typeExpression !== typeExpression + || node.fullName !== fullName + || node.comment !== comment + ? update(createJSDocTypedefTag(tagName, typeExpression, fullName, comment), node) + : node; + } - // @api - function createJSDocTemplateTag(tagName: Identifier | undefined, constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment?: string | NodeArray): JSDocTemplateTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocTemplateTag, tagName ?? createIdentifier("template"), comment); - node.constraint = constraint; - node.typeParameters = createNodeArray(typeParameters); - return node; - } + // @api + function createJSDocParameterTag(tagName: Identifier | undefined, name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, isNameFirst?: boolean, comment?: string | NodeArray): JSDocParameterTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocParameterTag, tagName ?? createIdentifier("param"), comment); + node.typeExpression = typeExpression; + node.name = name; + node.isNameFirst = !!isNameFirst; + node.isBracketed = isBracketed; + return node; + } - // @api - function updateJSDocTemplateTag(node: JSDocTemplateTag, tagName: Identifier = getDefaultTagName(node), constraint: JSDocTypeExpression | undefined, typeParameters: readonly TypeParameterDeclaration[], comment: string | NodeArray | undefined): JSDocTemplateTag { - return node.tagName !== tagName - || node.constraint !== constraint - || node.typeParameters !== typeParameters - || node.comment !== comment - ? update(createJSDocTemplateTag(tagName, constraint, typeParameters, comment), node) - : node; - } + // @api + function updateJSDocParameterTag(node: JSDocParameterTag, tagName: Identifier = getDefaultTagName(node), name: EntityName, isBracketed: boolean, typeExpression: JSDocTypeExpression | undefined, isNameFirst: boolean, comment: string | NodeArray | undefined): JSDocParameterTag { + return node.tagName !== tagName + || node.name !== name + || node.isBracketed !== isBracketed + || node.typeExpression !== typeExpression + || node.isNameFirst !== isNameFirst + || node.comment !== comment + ? update(createJSDocParameterTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment), node) + : node; + } - // @api - function createJSDocTypedefTag(tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray): JSDocTypedefTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocTypedefTag, tagName ?? createIdentifier("typedef"), comment); - node.typeExpression = typeExpression; - node.fullName = fullName; - node.name = getJSDocTypeAliasName(fullName); - return node; - } + // @api + function createJSDocPropertyTag(tagName: Identifier | undefined, name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, isNameFirst?: boolean, comment?: string | NodeArray): JSDocPropertyTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocPropertyTag, tagName ?? createIdentifier("prop"), comment); + node.typeExpression = typeExpression; + node.name = name; + node.isNameFirst = !!isNameFirst; + node.isBracketed = isBracketed; + return node; + } - // @api - function updateJSDocTypedefTag(node: JSDocTypedefTag, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray | undefined): JSDocTypedefTag { - return node.tagName !== tagName - || node.typeExpression !== typeExpression - || node.fullName !== fullName - || node.comment !== comment - ? update(createJSDocTypedefTag(tagName, typeExpression, fullName, comment), node) - : node; - } + // @api + function updateJSDocPropertyTag(node: JSDocPropertyTag, tagName: Identifier = getDefaultTagName(node), name: EntityName, isBracketed: boolean, typeExpression: JSDocTypeExpression | undefined, isNameFirst: boolean, comment: string | NodeArray | undefined): JSDocPropertyTag { + return node.tagName !== tagName + || node.name !== name + || node.isBracketed !== isBracketed + || node.typeExpression !== typeExpression + || node.isNameFirst !== isNameFirst + || node.comment !== comment + ? update(createJSDocPropertyTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment), node) + : node; + } - // @api - function createJSDocParameterTag(tagName: Identifier | undefined, name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, isNameFirst?: boolean, comment?: string | NodeArray): JSDocParameterTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocParameterTag, tagName ?? createIdentifier("param"), comment); - node.typeExpression = typeExpression; - node.name = name; - node.isNameFirst = !!isNameFirst; - node.isBracketed = isBracketed; - return node; - } + // @api + function createJSDocCallbackTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray): JSDocCallbackTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocCallbackTag, tagName ?? createIdentifier("callback"), comment); + node.typeExpression = typeExpression; + node.fullName = fullName; + node.name = getJSDocTypeAliasName(fullName); + return node; + } - // @api - function updateJSDocParameterTag(node: JSDocParameterTag, tagName: Identifier = getDefaultTagName(node), name: EntityName, isBracketed: boolean, typeExpression: JSDocTypeExpression | undefined, isNameFirst: boolean, comment: string | NodeArray | undefined): JSDocParameterTag { - return node.tagName !== tagName - || node.name !== name - || node.isBracketed !== isBracketed - || node.typeExpression !== typeExpression - || node.isNameFirst !== isNameFirst - || node.comment !== comment - ? update(createJSDocParameterTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment), node) - : node; - } + // @api + function updateJSDocCallbackTag(node: JSDocCallbackTag, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocSignature, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray | undefined): JSDocCallbackTag { + return node.tagName !== tagName + || node.typeExpression !== typeExpression + || node.fullName !== fullName + || node.comment !== comment + ? update(createJSDocCallbackTag(tagName, typeExpression, fullName, comment), node) + : node; + } - // @api - function createJSDocPropertyTag(tagName: Identifier | undefined, name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, isNameFirst?: boolean, comment?: string | NodeArray): JSDocPropertyTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocPropertyTag, tagName ?? createIdentifier("prop"), comment); - node.typeExpression = typeExpression; - node.name = name; - node.isNameFirst = !!isNameFirst; - node.isBracketed = isBracketed; - return node; - } + // @api + function createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray): JSDocAugmentsTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocAugmentsTag, tagName ?? createIdentifier("augments"), comment); + node.class = className; + return node; + } - // @api - function updateJSDocPropertyTag(node: JSDocPropertyTag, tagName: Identifier = getDefaultTagName(node), name: EntityName, isBracketed: boolean, typeExpression: JSDocTypeExpression | undefined, isNameFirst: boolean, comment: string | NodeArray | undefined): JSDocPropertyTag { - return node.tagName !== tagName - || node.name !== name - || node.isBracketed !== isBracketed - || node.typeExpression !== typeExpression - || node.isNameFirst !== isNameFirst - || node.comment !== comment - ? update(createJSDocPropertyTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment), node) - : node; - } + // @api + function updateJSDocAugmentsTag(node: JSDocAugmentsTag, tagName: Identifier = getDefaultTagName(node), className: JSDocAugmentsTag["class"], comment: string | NodeArray | undefined): JSDocAugmentsTag { + return node.tagName !== tagName + || node.class !== className + || node.comment !== comment + ? update(createJSDocAugmentsTag(tagName, className, comment), node) + : node; + } - // @api - function createJSDocCallbackTag(tagName: Identifier | undefined, typeExpression: JSDocSignature, fullName?: Identifier | JSDocNamespaceDeclaration, comment?: string | NodeArray): JSDocCallbackTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocCallbackTag, tagName ?? createIdentifier("callback"), comment); - node.typeExpression = typeExpression; - node.fullName = fullName; - node.name = getJSDocTypeAliasName(fullName); - return node; - } - - // @api - function updateJSDocCallbackTag(node: JSDocCallbackTag, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocSignature, fullName: Identifier | JSDocNamespaceDeclaration | undefined, comment: string | NodeArray | undefined): JSDocCallbackTag { - return node.tagName !== tagName - || node.typeExpression !== typeExpression - || node.fullName !== fullName - || node.comment !== comment - ? update(createJSDocCallbackTag(tagName, typeExpression, fullName, comment), node) - : node; - } - - // @api - function createJSDocAugmentsTag(tagName: Identifier | undefined, className: JSDocAugmentsTag["class"], comment?: string | NodeArray): JSDocAugmentsTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocAugmentsTag, tagName ?? createIdentifier("augments"), comment); - node.class = className; - return node; - } - - // @api - function updateJSDocAugmentsTag(node: JSDocAugmentsTag, tagName: Identifier = getDefaultTagName(node), className: JSDocAugmentsTag["class"], comment: string | NodeArray | undefined): JSDocAugmentsTag { - return node.tagName !== tagName - || node.class !== className - || node.comment !== comment - ? update(createJSDocAugmentsTag(tagName, className, comment), node) - : node; - } - - // @api - function createJSDocImplementsTag(tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment?: string | NodeArray): JSDocImplementsTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocImplementsTag, tagName ?? createIdentifier("implements"), comment); - node.class = className; - return node; - } - - // @api - function createJSDocSeeTag(tagName: Identifier | undefined, name: JSDocNameReference | undefined, comment?: string | NodeArray): JSDocSeeTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocSeeTag, tagName ?? createIdentifier("see"), comment); - node.name = name; - return node; - } - - // @api - function updateJSDocSeeTag(node: JSDocSeeTag, tagName: Identifier | undefined, name: JSDocNameReference | undefined, comment?: string | NodeArray): JSDocSeeTag { - return node.tagName !== tagName - || node.name !== name - || node.comment !== comment - ? update(createJSDocSeeTag(tagName, name, comment), node) - : node; - } - - // @api - function createJSDocNameReference(name: EntityName | JSDocMemberName): JSDocNameReference { - const node = createBaseNode(SyntaxKind.JSDocNameReference); - node.name = name; - return node; - } - - // @api - function updateJSDocNameReference(node: JSDocNameReference, name: EntityName | JSDocMemberName): JSDocNameReference { - return node.name !== name - ? update(createJSDocNameReference(name), node) - : node; - } - - // @api - function createJSDocMemberName(left: EntityName | JSDocMemberName, right: Identifier) { - const node = createBaseNode(SyntaxKind.JSDocMemberName); - node.left = left; - node.right = right; - node.transformFlags |= - propagateChildFlags(node.left) | - propagateChildFlags(node.right); - return node; - } - - // @api - function updateJSDocMemberName(node: JSDocMemberName, left: EntityName | JSDocMemberName, right: Identifier) { - return node.left !== left - || node.right !== right - ? update(createJSDocMemberName(left, right), node) - : node; - } - - // @api - function createJSDocLink(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink { - const node = createBaseNode(SyntaxKind.JSDocLink); - node.name = name; - node.text = text; - return node; - } - - // @api - function updateJSDocLink(node: JSDocLink, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink { - return node.name !== name - ? update(createJSDocLink(name, text), node) - : node; - } - - // @api - function createJSDocLinkCode(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkCode { - const node = createBaseNode(SyntaxKind.JSDocLinkCode); - node.name = name; - node.text = text; - return node; - } + // @api + function createJSDocImplementsTag(tagName: Identifier | undefined, className: JSDocImplementsTag["class"], comment?: string | NodeArray): JSDocImplementsTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocImplementsTag, tagName ?? createIdentifier("implements"), comment); + node.class = className; + return node; + } - // @api - function updateJSDocLinkCode(node: JSDocLinkCode, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkCode { - return node.name !== name - ? update(createJSDocLinkCode(name, text), node) - : node; - } + // @api + function createJSDocSeeTag(tagName: Identifier | undefined, name: JSDocNameReference | undefined, comment?: string | NodeArray): JSDocSeeTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocSeeTag, tagName ?? createIdentifier("see"), comment); + node.name = name; + return node; + } - // @api - function createJSDocLinkPlain(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkPlain { - const node = createBaseNode(SyntaxKind.JSDocLinkPlain); - node.name = name; - node.text = text; - return node; - } + // @api + function updateJSDocSeeTag(node: JSDocSeeTag, tagName: Identifier | undefined, name: JSDocNameReference | undefined, comment?: string | NodeArray): JSDocSeeTag { + return node.tagName !== tagName + || node.name !== name + || node.comment !== comment + ? update(createJSDocSeeTag(tagName, name, comment), node) + : node; + } - // @api - function updateJSDocLinkPlain(node: JSDocLinkPlain, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkPlain { - return node.name !== name - ? update(createJSDocLinkPlain(name, text), node) - : node; - } + // @api + function createJSDocNameReference(name: EntityName | JSDocMemberName): JSDocNameReference { + const node = createBaseNode(SyntaxKind.JSDocNameReference); + node.name = name; + return node; + } - // @api - function updateJSDocImplementsTag(node: JSDocImplementsTag, tagName: Identifier = getDefaultTagName(node), className: JSDocImplementsTag["class"], comment: string | NodeArray | undefined): JSDocImplementsTag { - return node.tagName !== tagName - || node.class !== className - || node.comment !== comment - ? update(createJSDocImplementsTag(tagName, className, comment), node) - : node; - } + // @api + function updateJSDocNameReference(node: JSDocNameReference, name: EntityName | JSDocMemberName): JSDocNameReference { + return node.name !== name + ? update(createJSDocNameReference(name), node) + : node; + } - // @api - // createJSDocAuthorTag - // createJSDocClassTag - // createJSDocPublicTag - // createJSDocPrivateTag - // createJSDocProtectedTag - // createJSDocReadonlyTag - // createJSDocDeprecatedTag - function createJSDocSimpleTagWorker(kind: T["kind"], tagName: Identifier | undefined, comment?: string | NodeArray) { - const node = createBaseJSDocTag(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment); - return node; - } + // @api + function createJSDocMemberName(left: EntityName | JSDocMemberName, right: Identifier) { + const node = createBaseNode(SyntaxKind.JSDocMemberName); + node.left = left; + node.right = right; + node.transformFlags |= + propagateChildFlags(node.left) | + propagateChildFlags(node.right); + return node; + } - // @api - // updateJSDocAuthorTag - // updateJSDocClassTag - // updateJSDocPublicTag - // updateJSDocPrivateTag - // updateJSDocProtectedTag - // updateJSDocReadonlyTag - // updateJSDocDeprecatedTag - function updateJSDocSimpleTagWorker(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), comment: string | NodeArray | undefined) { - return node.tagName !== tagName - || node.comment !== comment - ? update(createJSDocSimpleTagWorker(kind, tagName, comment), node) : - node; - } + // @api + function updateJSDocMemberName(node: JSDocMemberName, left: EntityName | JSDocMemberName, right: Identifier) { + return node.left !== left + || node.right !== right + ? update(createJSDocMemberName(left, right), node) + : node; + } - // @api - // createJSDocTypeTag - // createJSDocReturnTag - // createJSDocThisTag - // createJSDocEnumTag - function createJSDocTypeLikeTagWorker(kind: T["kind"], tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: string | NodeArray) { - const node = createBaseJSDocTag(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment); - node.typeExpression = typeExpression; - return node; - } + // @api + function createJSDocLink(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink { + const node = createBaseNode(SyntaxKind.JSDocLink); + node.name = name; + node.text = text; + return node; + } - // @api - // updateJSDocTypeTag - // updateJSDocReturnTag - // updateJSDocThisTag - // updateJSDocEnumTag - function updateJSDocTypeLikeTagWorker(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, comment: string | NodeArray | undefined) { - return node.tagName !== tagName - || node.typeExpression !== typeExpression - || node.comment !== comment - ? update(createJSDocTypeLikeTagWorker(kind, tagName, typeExpression, comment), node) - : node; - } + // @api + function updateJSDocLink(node: JSDocLink, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLink { + return node.name !== name + ? update(createJSDocLink(name, text), node) + : node; + } - // @api - function createJSDocUnknownTag(tagName: Identifier, comment?: string | NodeArray): JSDocUnknownTag { - const node = createBaseJSDocTag(SyntaxKind.JSDocTag, tagName, comment); - return node; - } + // @api + function createJSDocLinkCode(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkCode { + const node = createBaseNode(SyntaxKind.JSDocLinkCode); + node.name = name; + node.text = text; + return node; + } - // @api - function updateJSDocUnknownTag(node: JSDocUnknownTag, tagName: Identifier, comment: string | NodeArray | undefined): JSDocUnknownTag { - return node.tagName !== tagName - || node.comment !== comment - ? update(createJSDocUnknownTag(tagName, comment), node) - : node; - } + // @api + function updateJSDocLinkCode(node: JSDocLinkCode, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkCode { + return node.name !== name + ? update(createJSDocLinkCode(name, text), node) + : node; + } - // @api - function createJSDocText(text: string): JSDocText { - const node = createBaseNode(SyntaxKind.JSDocText); - node.text = text; - return node; - } + // @api + function createJSDocLinkPlain(name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkPlain { + const node = createBaseNode(SyntaxKind.JSDocLinkPlain); + node.name = name; + node.text = text; + return node; + } - // @api - function updateJSDocText(node: JSDocText, text: string): JSDocText { - return node.text !== text - ? update(createJSDocText(text), node) - : node; - } + // @api + function updateJSDocLinkPlain(node: JSDocLinkPlain, name: EntityName | JSDocMemberName | undefined, text: string): JSDocLinkPlain { + return node.name !== name + ? update(createJSDocLinkPlain(name, text), node) + : node; + } - // @api - function createJSDocComment(comment?: string | NodeArray | undefined, tags?: readonly JSDocTag[] | undefined) { - const node = createBaseNode(SyntaxKind.JSDoc); - node.comment = comment; - node.tags = asNodeArray(tags); - return node; - } + // @api + function updateJSDocImplementsTag(node: JSDocImplementsTag, tagName: Identifier = getDefaultTagName(node), className: JSDocImplementsTag["class"], comment: string | NodeArray | undefined): JSDocImplementsTag { + return node.tagName !== tagName + || node.class !== className + || node.comment !== comment + ? update(createJSDocImplementsTag(tagName, className, comment), node) + : node; + } - // @api - function updateJSDocComment(node: JSDoc, comment: string | NodeArray | undefined, tags: readonly JSDocTag[] | undefined) { - return node.comment !== comment - || node.tags !== tags - ? update(createJSDocComment(comment, tags), node) - : node; - } + // @api + // createJSDocAuthorTag + // createJSDocClassTag + // createJSDocPublicTag + // createJSDocPrivateTag + // createJSDocProtectedTag + // createJSDocReadonlyTag + // createJSDocDeprecatedTag + function createJSDocSimpleTagWorker(kind: T["kind"], tagName: Identifier | undefined, comment?: string | NodeArray) { + const node = createBaseJSDocTag(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment); + return node; + } - // - // JSX - // + // @api + // updateJSDocAuthorTag + // updateJSDocClassTag + // updateJSDocPublicTag + // updateJSDocPrivateTag + // updateJSDocProtectedTag + // updateJSDocReadonlyTag + // updateJSDocDeprecatedTag + function updateJSDocSimpleTagWorker(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), comment: string | NodeArray | undefined) { + return node.tagName !== tagName + || node.comment !== comment + ? update(createJSDocSimpleTagWorker(kind, tagName, comment), node) : + node; + } - // @api - function createJsxElement(openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { - const node = createBaseNode(SyntaxKind.JsxElement); - node.openingElement = openingElement; - node.children = createNodeArray(children); - node.closingElement = closingElement; - node.transformFlags |= - propagateChildFlags(node.openingElement) | - propagateChildrenFlags(node.children) | - propagateChildFlags(node.closingElement) | - TransformFlags.ContainsJsx; - return node; - } + // @api + // createJSDocTypeTag + // createJSDocReturnTag + // createJSDocThisTag + // createJSDocEnumTag + function createJSDocTypeLikeTagWorker(kind: T["kind"], tagName: Identifier | undefined, typeExpression?: JSDocTypeExpression, comment?: string | NodeArray) { + const node = createBaseJSDocTag(kind, tagName ?? createIdentifier(getDefaultTagNameForKind(kind)), comment); + node.typeExpression = typeExpression; + return node; + } - // @api - function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { - return node.openingElement !== openingElement - || node.children !== children - || node.closingElement !== closingElement - ? update(createJsxElement(openingElement, children, closingElement), node) - : node; - } + // @api + // updateJSDocTypeTag + // updateJSDocReturnTag + // updateJSDocThisTag + // updateJSDocEnumTag + function updateJSDocTypeLikeTagWorker(kind: T["kind"], node: T, tagName: Identifier = getDefaultTagName(node), typeExpression: JSDocTypeExpression | undefined, comment: string | NodeArray | undefined) { + return node.tagName !== tagName + || node.typeExpression !== typeExpression + || node.comment !== comment + ? update(createJSDocTypeLikeTagWorker(kind, tagName, typeExpression, comment), node) + : node; + } - // @api - function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - const node = createBaseNode(SyntaxKind.JsxSelfClosingElement); - node.tagName = tagName; - node.typeArguments = asNodeArray(typeArguments); - node.attributes = attributes; - node.transformFlags |= - propagateChildFlags(node.tagName) | - propagateChildrenFlags(node.typeArguments) | - propagateChildFlags(node.attributes) | - TransformFlags.ContainsJsx; - if (node.typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - return node; - } + // @api + function createJSDocUnknownTag(tagName: Identifier, comment?: string | NodeArray): JSDocUnknownTag { + const node = createBaseJSDocTag(SyntaxKind.JSDocTag, tagName, comment); + return node; + } - // @api - function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - return node.tagName !== tagName - || node.typeArguments !== typeArguments - || node.attributes !== attributes - ? update(createJsxSelfClosingElement(tagName, typeArguments, attributes), node) - : node; - } + // @api + function updateJSDocUnknownTag(node: JSDocUnknownTag, tagName: Identifier, comment: string | NodeArray | undefined): JSDocUnknownTag { + return node.tagName !== tagName + || node.comment !== comment + ? update(createJSDocUnknownTag(tagName, comment), node) + : node; + } - // @api - function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - const node = createBaseNode(SyntaxKind.JsxOpeningElement); - node.tagName = tagName; - node.typeArguments = asNodeArray(typeArguments); - node.attributes = attributes; - node.transformFlags |= - propagateChildFlags(node.tagName) | - propagateChildrenFlags(node.typeArguments) | - propagateChildFlags(node.attributes) | - TransformFlags.ContainsJsx; - if (typeArguments) { - node.transformFlags |= TransformFlags.ContainsTypeScript; - } - return node; - } + // @api + function createJSDocText(text: string): JSDocText { + const node = createBaseNode(SyntaxKind.JSDocText); + node.text = text; + return node; + } - // @api - function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { - return node.tagName !== tagName - || node.typeArguments !== typeArguments - || node.attributes !== attributes - ? update(createJsxOpeningElement(tagName, typeArguments, attributes), node) - : node; - } + // @api + function updateJSDocText(node: JSDocText, text: string): JSDocText { + return node.text !== text + ? update(createJSDocText(text), node) + : node; + } - // @api - function createJsxClosingElement(tagName: JsxTagNameExpression) { - const node = createBaseNode(SyntaxKind.JsxClosingElement); - node.tagName = tagName; - node.transformFlags |= - propagateChildFlags(node.tagName) | - TransformFlags.ContainsJsx; - return node; - } + // @api + function createJSDocComment(comment?: string | NodeArray | undefined, tags?: readonly JSDocTag[] | undefined) { + const node = createBaseNode(SyntaxKind.JSDoc); + node.comment = comment; + node.tags = asNodeArray(tags); + return node; + } - // @api - function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression) { - return node.tagName !== tagName - ? update(createJsxClosingElement(tagName), node) - : node; - } + // @api + function updateJSDocComment(node: JSDoc, comment: string | NodeArray | undefined, tags: readonly JSDocTag[] | undefined) { + return node.comment !== comment + || node.tags !== tags + ? update(createJSDocComment(comment, tags), node) + : node; + } - // @api - function createJsxFragment(openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { - const node = createBaseNode(SyntaxKind.JsxFragment); - node.openingFragment = openingFragment; - node.children = createNodeArray(children); - node.closingFragment = closingFragment; - node.transformFlags |= - propagateChildFlags(node.openingFragment) | - propagateChildrenFlags(node.children) | - propagateChildFlags(node.closingFragment) | - TransformFlags.ContainsJsx; - return node; - } + // + // JSX + // + + // @api + function createJsxElement(openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { + const node = createBaseNode(SyntaxKind.JsxElement); + node.openingElement = openingElement; + node.children = createNodeArray(children); + node.closingElement = closingElement; + node.transformFlags |= + propagateChildFlags(node.openingElement) | + propagateChildrenFlags(node.children) | + propagateChildFlags(node.closingElement) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { - return node.openingFragment !== openingFragment - || node.children !== children - || node.closingFragment !== closingFragment - ? update(createJsxFragment(openingFragment, children, closingFragment), node) - : node; - } + // @api + function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: readonly JsxChild[], closingElement: JsxClosingElement) { + return node.openingElement !== openingElement + || node.children !== children + || node.closingElement !== closingElement + ? update(createJsxElement(openingElement, children, closingElement), node) + : node; + } - // @api - function createJsxText(text: string, containsOnlyTriviaWhiteSpaces?: boolean) { - const node = createBaseNode(SyntaxKind.JsxText); - node.text = text; - node.containsOnlyTriviaWhiteSpaces = !!containsOnlyTriviaWhiteSpaces; - node.transformFlags |= TransformFlags.ContainsJsx; - return node; + // @api + function createJsxSelfClosingElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + const node = createBaseNode(SyntaxKind.JsxSelfClosingElement); + node.tagName = tagName; + node.typeArguments = asNodeArray(typeArguments); + node.attributes = attributes; + node.transformFlags |= + propagateChildFlags(node.tagName) | + propagateChildrenFlags(node.typeArguments) | + propagateChildFlags(node.attributes) | + TransformFlags.ContainsJsx; + if (node.typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function updateJsxText(node: JsxText, text: string, containsOnlyTriviaWhiteSpaces?: boolean) { - return node.text !== text - || node.containsOnlyTriviaWhiteSpaces !== containsOnlyTriviaWhiteSpaces - ? update(createJsxText(text, containsOnlyTriviaWhiteSpaces), node) - : node; - } + // @api + function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + return node.tagName !== tagName + || node.typeArguments !== typeArguments + || node.attributes !== attributes + ? update(createJsxSelfClosingElement(tagName, typeArguments, attributes), node) + : node; + } - // @api - function createJsxOpeningFragment() { - const node = createBaseNode(SyntaxKind.JsxOpeningFragment); - node.transformFlags |= TransformFlags.ContainsJsx; - return node; + // @api + function createJsxOpeningElement(tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + const node = createBaseNode(SyntaxKind.JsxOpeningElement); + node.tagName = tagName; + node.typeArguments = asNodeArray(typeArguments); + node.attributes = attributes; + node.transformFlags |= + propagateChildFlags(node.tagName) | + propagateChildrenFlags(node.typeArguments) | + propagateChildFlags(node.attributes) | + TransformFlags.ContainsJsx; + if (typeArguments) { + node.transformFlags |= TransformFlags.ContainsTypeScript; } + return node; + } - // @api - function createJsxJsxClosingFragment() { - const node = createBaseNode(SyntaxKind.JsxClosingFragment); - node.transformFlags |= TransformFlags.ContainsJsx; - return node; - } + // @api + function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, typeArguments: readonly TypeNode[] | undefined, attributes: JsxAttributes) { + return node.tagName !== tagName + || node.typeArguments !== typeArguments + || node.attributes !== attributes + ? update(createJsxOpeningElement(tagName, typeArguments, attributes), node) + : node; + } - // @api - function createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined) { - const node = createBaseNode(SyntaxKind.JsxAttribute); - node.name = name; - node.initializer = initializer; - node.transformFlags |= - propagateChildFlags(node.name) | - propagateChildFlags(node.initializer) | - TransformFlags.ContainsJsx; - return node; - } + // @api + function createJsxClosingElement(tagName: JsxTagNameExpression) { + const node = createBaseNode(SyntaxKind.JsxClosingElement); + node.tagName = tagName; + node.transformFlags |= + propagateChildFlags(node.tagName) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined) { - return node.name !== name - || node.initializer !== initializer - ? update(createJsxAttribute(name, initializer), node) - : node; - } + // @api + function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression) { + return node.tagName !== tagName + ? update(createJsxClosingElement(tagName), node) + : node; + } - // @api - function createJsxAttributes(properties: readonly JsxAttributeLike[]) { - const node = createBaseNode(SyntaxKind.JsxAttributes); - node.properties = createNodeArray(properties); - node.transformFlags |= - propagateChildrenFlags(node.properties) | - TransformFlags.ContainsJsx; - return node; - } + // @api + function createJsxFragment(openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { + const node = createBaseNode(SyntaxKind.JsxFragment); + node.openingFragment = openingFragment; + node.children = createNodeArray(children); + node.closingFragment = closingFragment; + node.transformFlags |= + propagateChildFlags(node.openingFragment) | + propagateChildrenFlags(node.children) | + propagateChildFlags(node.closingFragment) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]) { - return node.properties !== properties - ? update(createJsxAttributes(properties), node) - : node; - } + // @api + function updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment) { + return node.openingFragment !== openingFragment + || node.children !== children + || node.closingFragment !== closingFragment + ? update(createJsxFragment(openingFragment, children, closingFragment), node) + : node; + } - // @api - function createJsxSpreadAttribute(expression: Expression) { - const node = createBaseNode(SyntaxKind.JsxSpreadAttribute); - node.expression = expression; - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsJsx; - return node; - } + // @api + function createJsxText(text: string, containsOnlyTriviaWhiteSpaces?: boolean) { + const node = createBaseNode(SyntaxKind.JsxText); + node.text = text; + node.containsOnlyTriviaWhiteSpaces = !!containsOnlyTriviaWhiteSpaces; + node.transformFlags |= TransformFlags.ContainsJsx; + return node; + } - // @api - function updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression) { - return node.expression !== expression - ? update(createJsxSpreadAttribute(expression), node) - : node; - } + // @api + function updateJsxText(node: JsxText, text: string, containsOnlyTriviaWhiteSpaces?: boolean) { + return node.text !== text + || node.containsOnlyTriviaWhiteSpaces !== containsOnlyTriviaWhiteSpaces + ? update(createJsxText(text, containsOnlyTriviaWhiteSpaces), node) + : node; + } - // @api - function createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined) { - const node = createBaseNode(SyntaxKind.JsxExpression); - node.dotDotDotToken = dotDotDotToken; - node.expression = expression; - node.transformFlags |= - propagateChildFlags(node.dotDotDotToken) | - propagateChildFlags(node.expression) | - TransformFlags.ContainsJsx; - return node; - } + // @api + function createJsxOpeningFragment() { + const node = createBaseNode(SyntaxKind.JsxOpeningFragment); + node.transformFlags |= TransformFlags.ContainsJsx; + return node; + } - // @api - function updateJsxExpression(node: JsxExpression, expression: Expression | undefined) { - return node.expression !== expression - ? update(createJsxExpression(node.dotDotDotToken, expression), node) - : node; - } + // @api + function createJsxJsxClosingFragment() { + const node = createBaseNode(SyntaxKind.JsxClosingFragment); + node.transformFlags |= TransformFlags.ContainsJsx; + return node; + } - // - // Clauses - // + // @api + function createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined) { + const node = createBaseNode(SyntaxKind.JsxAttribute); + node.name = name; + node.initializer = initializer; + node.transformFlags |= + propagateChildFlags(node.name) | + propagateChildFlags(node.initializer) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function createCaseClause(expression: Expression, statements: readonly Statement[]) { - const node = createBaseNode(SyntaxKind.CaseClause); - node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.statements = createNodeArray(statements); - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildrenFlags(node.statements); - return node; - } + // @api + function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined) { + return node.name !== name + || node.initializer !== initializer + ? update(createJsxAttribute(name, initializer), node) + : node; + } - // @api - function updateCaseClause(node: CaseClause, expression: Expression, statements: readonly Statement[]) { - return node.expression !== expression - || node.statements !== statements - ? update(createCaseClause(expression, statements), node) - : node; - } + // @api + function createJsxAttributes(properties: readonly JsxAttributeLike[]) { + const node = createBaseNode(SyntaxKind.JsxAttributes); + node.properties = createNodeArray(properties); + node.transformFlags |= + propagateChildrenFlags(node.properties) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function createDefaultClause(statements: readonly Statement[]) { - const node = createBaseNode(SyntaxKind.DefaultClause); - node.statements = createNodeArray(statements); - node.transformFlags = propagateChildrenFlags(node.statements); - return node; - } + // @api + function updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]) { + return node.properties !== properties + ? update(createJsxAttributes(properties), node) + : node; + } - // @api - function updateDefaultClause(node: DefaultClause, statements: readonly Statement[]) { - return node.statements !== statements - ? update(createDefaultClause(statements), node) - : node; - } + // @api + function createJsxSpreadAttribute(expression: Expression) { + const node = createBaseNode(SyntaxKind.JsxSpreadAttribute); + node.expression = expression; + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function createHeritageClause(token: HeritageClause["token"], types: readonly ExpressionWithTypeArguments[]) { - const node = createBaseNode(SyntaxKind.HeritageClause); - node.token = token; - node.types = createNodeArray(types); - node.transformFlags |= propagateChildrenFlags(node.types); - switch (token) { - case SyntaxKind.ExtendsKeyword: - node.transformFlags |= TransformFlags.ContainsES2015; - break; - case SyntaxKind.ImplementsKeyword: - node.transformFlags |= TransformFlags.ContainsTypeScript; - break; - default: - return Debug.assertNever(token); - } - return node; - } + // @api + function updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression) { + return node.expression !== expression + ? update(createJsxSpreadAttribute(expression), node) + : node; + } - // @api - function updateHeritageClause(node: HeritageClause, types: readonly ExpressionWithTypeArguments[]) { - return node.types !== types - ? update(createHeritageClause(node.token, types), node) - : node; - } + // @api + function createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined) { + const node = createBaseNode(SyntaxKind.JsxExpression); + node.dotDotDotToken = dotDotDotToken; + node.expression = expression; + node.transformFlags |= + propagateChildFlags(node.dotDotDotToken) | + propagateChildFlags(node.expression) | + TransformFlags.ContainsJsx; + return node; + } - // @api - function createCatchClause(variableDeclaration: string | BindingName | VariableDeclaration | undefined, block: Block) { - const node = createBaseNode(SyntaxKind.CatchClause); - if (typeof variableDeclaration === "string" || variableDeclaration && !isVariableDeclaration(variableDeclaration)) { - variableDeclaration = createVariableDeclaration( - variableDeclaration, - /*exclamationToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - ); - } - node.variableDeclaration = variableDeclaration; - node.block = block; - node.transformFlags |= - propagateChildFlags(node.variableDeclaration) | - propagateChildFlags(node.block); - if (!variableDeclaration) node.transformFlags |= TransformFlags.ContainsES2019; - return node; - } + // @api + function updateJsxExpression(node: JsxExpression, expression: Expression | undefined) { + return node.expression !== expression + ? update(createJsxExpression(node.dotDotDotToken, expression), node) + : node; + } - // @api - function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block) { - return node.variableDeclaration !== variableDeclaration - || node.block !== block - ? update(createCatchClause(variableDeclaration, block), node) - : node; - } + // + // Clauses + // + + // @api + function createCaseClause(expression: Expression, statements: readonly Statement[]) { + const node = createBaseNode(SyntaxKind.CaseClause); + node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.statements = createNodeArray(statements); + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildrenFlags(node.statements); + return node; + } - // - // Property assignments - // + // @api + function updateCaseClause(node: CaseClause, expression: Expression, statements: readonly Statement[]) { + return node.expression !== expression + || node.statements !== statements + ? update(createCaseClause(expression, statements), node) + : node; + } - // @api - function createPropertyAssignment(name: string | PropertyName, initializer: Expression) { - const node = createBaseNamedDeclaration( - SyntaxKind.PropertyAssignment, - /*modifiers*/ undefined, - name - ); - node.initializer = parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer); - node.transformFlags |= - propagateChildFlags(node.name) | - propagateChildFlags(node.initializer); + // @api + function createDefaultClause(statements: readonly Statement[]) { + const node = createBaseNode(SyntaxKind.DefaultClause); + node.statements = createNodeArray(statements); + node.transformFlags = propagateChildrenFlags(node.statements); + return node; + } - // The following properties are used only to report grammar errors - node.illegalDecorators = undefined; - node.modifiers = undefined; - node.questionToken = undefined; - node.exclamationToken = undefined; - return node; - } + // @api + function updateDefaultClause(node: DefaultClause, statements: readonly Statement[]) { + return node.statements !== statements + ? update(createDefaultClause(statements), node) + : node; + } - // @api - function updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression) { - return node.name !== name - || node.initializer !== initializer - ? finishUpdatePropertyAssignment(createPropertyAssignment(name, initializer), node) - : node; + // @api + function createHeritageClause(token: HeritageClause["token"], types: readonly ExpressionWithTypeArguments[]) { + const node = createBaseNode(SyntaxKind.HeritageClause); + node.token = token; + node.types = createNodeArray(types); + node.transformFlags |= propagateChildrenFlags(node.types); + switch (token) { + case SyntaxKind.ExtendsKeyword: + node.transformFlags |= TransformFlags.ContainsES2015; + break; + case SyntaxKind.ImplementsKeyword: + node.transformFlags |= TransformFlags.ContainsTypeScript; + break; + default: + return Debug.assertNever(token); } + return node; + } - function finishUpdatePropertyAssignment(updated: Mutable, original: PropertyAssignment) { - // copy children used only for error reporting - if (updated !== original) { - updated.illegalDecorators = original.illegalDecorators; - updated.modifiers = original.modifiers; - updated.questionToken = original.questionToken; - updated.exclamationToken = original.exclamationToken; - } - return update(updated, original); - } + // @api + function updateHeritageClause(node: HeritageClause, types: readonly ExpressionWithTypeArguments[]) { + return node.types !== types + ? update(createHeritageClause(node.token, types), node) + : node; + } - // @api - function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer?: Expression) { - const node = createBaseNamedDeclaration( - SyntaxKind.ShorthandPropertyAssignment, - /*modifiers*/ undefined, - name + // @api + function createCatchClause(variableDeclaration: string | BindingName | VariableDeclaration | undefined, block: Block) { + const node = createBaseNode(SyntaxKind.CatchClause); + if (typeof variableDeclaration === "string" || variableDeclaration && !isVariableDeclaration(variableDeclaration)) { + variableDeclaration = createVariableDeclaration( + variableDeclaration, + /*exclamationToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined ); - node.objectAssignmentInitializer = objectAssignmentInitializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(objectAssignmentInitializer); - node.transformFlags |= - propagateChildFlags(node.objectAssignmentInitializer) | - TransformFlags.ContainsES2015; - - // The following properties are used only to report grammar errors - node.equalsToken = undefined; - node.illegalDecorators = undefined; - node.modifiers = undefined; - node.questionToken = undefined; - node.exclamationToken = undefined; - return node; } + node.variableDeclaration = variableDeclaration; + node.block = block; + node.transformFlags |= + propagateChildFlags(node.variableDeclaration) | + propagateChildFlags(node.block); + if (!variableDeclaration) node.transformFlags |= TransformFlags.ContainsES2019; + return node; + } - // @api - function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression | undefined) { - return node.name !== name - || node.objectAssignmentInitializer !== objectAssignmentInitializer - ? finishUpdateShorthandPropertyAssignment(createShorthandPropertyAssignment(name, objectAssignmentInitializer), node) - : node; - } + // @api + function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block) { + return node.variableDeclaration !== variableDeclaration + || node.block !== block + ? update(createCatchClause(variableDeclaration, block), node) + : node; + } - function finishUpdateShorthandPropertyAssignment(updated: Mutable, original: ShorthandPropertyAssignment) { - if (updated !== original) { - // copy children used only for error reporting - updated.equalsToken = original.equalsToken; - updated.illegalDecorators = original.illegalDecorators; - updated.modifiers = original.modifiers; - updated.questionToken = original.questionToken; - updated.exclamationToken = original.exclamationToken; - } - return update(updated, original); - } + // + // Property assignments + // + + // @api + function createPropertyAssignment(name: string | PropertyName, initializer: Expression) { + const node = createBaseNamedDeclaration( + SyntaxKind.PropertyAssignment, + /*modifiers*/ undefined, + name + ); + node.initializer = parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer); + node.transformFlags |= + propagateChildFlags(node.name) | + propagateChildFlags(node.initializer); + + // The following properties are used only to report grammar errors + node.illegalDecorators = undefined; + node.modifiers = undefined; + node.questionToken = undefined; + node.exclamationToken = undefined; + return node; + } - // @api - function createSpreadAssignment(expression: Expression) { - const node = createBaseNode(SyntaxKind.SpreadAssignment); - node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsES2018 | - TransformFlags.ContainsObjectRestOrSpread; - return node; - } + // @api + function updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression) { + return node.name !== name + || node.initializer !== initializer + ? finishUpdatePropertyAssignment(createPropertyAssignment(name, initializer), node) + : node; + } - // @api - function updateSpreadAssignment(node: SpreadAssignment, expression: Expression) { - return node.expression !== expression - ? update(createSpreadAssignment(expression), node) - : node; + function finishUpdatePropertyAssignment(updated: Mutable, original: PropertyAssignment) { + // copy children used only for error reporting + if (updated !== original) { + updated.illegalDecorators = original.illegalDecorators; + updated.modifiers = original.modifiers; + updated.questionToken = original.questionToken; + updated.exclamationToken = original.exclamationToken; } + return update(updated, original); + } - // - // Enum - // + // @api + function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer?: Expression) { + const node = createBaseNamedDeclaration( + SyntaxKind.ShorthandPropertyAssignment, + /*modifiers*/ undefined, + name + ); + node.objectAssignmentInitializer = objectAssignmentInitializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(objectAssignmentInitializer); + node.transformFlags |= + propagateChildFlags(node.objectAssignmentInitializer) | + TransformFlags.ContainsES2015; + + // The following properties are used only to report grammar errors + node.equalsToken = undefined; + node.illegalDecorators = undefined; + node.modifiers = undefined; + node.questionToken = undefined; + node.exclamationToken = undefined; + return node; + } - // @api - function createEnumMember(name: string | PropertyName, initializer?: Expression) { - const node = createBaseNode(SyntaxKind.EnumMember); - node.name = asName(name); - node.initializer = initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer); - node.transformFlags |= - propagateChildFlags(node.name) | - propagateChildFlags(node.initializer) | - TransformFlags.ContainsTypeScript; - return node; - } + // @api + function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression | undefined) { + return node.name !== name + || node.objectAssignmentInitializer !== objectAssignmentInitializer + ? finishUpdateShorthandPropertyAssignment(createShorthandPropertyAssignment(name, objectAssignmentInitializer), node) + : node; + } - // @api - function updateEnumMember(node: EnumMember, name: PropertyName, initializer: Expression | undefined) { - return node.name !== name - || node.initializer !== initializer - ? update(createEnumMember(name, initializer), node) - : node; + function finishUpdateShorthandPropertyAssignment(updated: Mutable, original: ShorthandPropertyAssignment) { + if (updated !== original) { + // copy children used only for error reporting + updated.equalsToken = original.equalsToken; + updated.illegalDecorators = original.illegalDecorators; + updated.modifiers = original.modifiers; + updated.questionToken = original.questionToken; + updated.exclamationToken = original.exclamationToken; } + return update(updated, original); + } - // - // Top-level nodes - // + // @api + function createSpreadAssignment(expression: Expression) { + const node = createBaseNode(SyntaxKind.SpreadAssignment); + node.expression = parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsES2018 | + TransformFlags.ContainsObjectRestOrSpread; + return node; + } - // @api - function createSourceFile( - statements: readonly Statement[], - endOfFileToken: EndOfFileToken, - flags: NodeFlags - ) { - const node = baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as Mutable; - node.statements = createNodeArray(statements); - node.endOfFileToken = endOfFileToken; - node.flags |= flags; - node.fileName = ""; - node.text = ""; - node.languageVersion = 0; - node.languageVariant = 0; - node.scriptKind = 0; - node.isDeclarationFile = false; - node.hasNoDefaultLib = false; - node.transformFlags |= - propagateChildrenFlags(node.statements) | - propagateChildFlags(node.endOfFileToken); - return node; - } + // @api + function updateSpreadAssignment(node: SpreadAssignment, expression: Expression) { + return node.expression !== expression + ? update(createSpreadAssignment(expression), node) + : node; + } - function cloneSourceFileWithChanges( - source: SourceFile, - statements: readonly Statement[], - isDeclarationFile: boolean, - referencedFiles: readonly FileReference[], - typeReferences: readonly FileReference[], - hasNoDefaultLib: boolean, - libReferences: readonly FileReference[] - ) { - const node = (source.redirectInfo ? Object.create(source.redirectInfo.redirectTarget) : baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile)) as Mutable; - for (const p in source) { - if (p === "emitNode" || hasProperty(node, p) || !hasProperty(source, p)) continue; - (node as any)[p] = (source as any)[p]; - } - node.flags |= source.flags; - node.statements = createNodeArray(statements); - node.endOfFileToken = source.endOfFileToken; - node.isDeclarationFile = isDeclarationFile; - node.referencedFiles = referencedFiles; - node.typeReferenceDirectives = typeReferences; - node.hasNoDefaultLib = hasNoDefaultLib; - node.libReferenceDirectives = libReferences; - node.transformFlags = - propagateChildrenFlags(node.statements) | - propagateChildFlags(node.endOfFileToken); - node.impliedNodeFormat = source.impliedNodeFormat; - return node; - } + // + // Enum + // + + // @api + function createEnumMember(name: string | PropertyName, initializer?: Expression) { + const node = createBaseNode(SyntaxKind.EnumMember); + node.name = asName(name); + node.initializer = initializer && parenthesizerRules().parenthesizeExpressionForDisallowedComma(initializer); + node.transformFlags |= + propagateChildFlags(node.name) | + propagateChildFlags(node.initializer) | + TransformFlags.ContainsTypeScript; + return node; + } - // @api - function updateSourceFile( - node: SourceFile, - statements: readonly Statement[], - isDeclarationFile = node.isDeclarationFile, - referencedFiles = node.referencedFiles, - typeReferenceDirectives = node.typeReferenceDirectives, - hasNoDefaultLib = node.hasNoDefaultLib, - libReferenceDirectives = node.libReferenceDirectives - ) { - return node.statements !== statements - || node.isDeclarationFile !== isDeclarationFile - || node.referencedFiles !== referencedFiles - || node.typeReferenceDirectives !== typeReferenceDirectives - || node.hasNoDefaultLib !== hasNoDefaultLib - || node.libReferenceDirectives !== libReferenceDirectives - ? update(cloneSourceFileWithChanges(node, statements, isDeclarationFile, referencedFiles, typeReferenceDirectives, hasNoDefaultLib, libReferenceDirectives), node) - : node; - } + // @api + function updateEnumMember(node: EnumMember, name: PropertyName, initializer: Expression | undefined) { + return node.name !== name + || node.initializer !== initializer + ? update(createEnumMember(name, initializer), node) + : node; + } - // @api - function createBundle(sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { - const node = createBaseNode(SyntaxKind.Bundle); - node.prepends = prepends; - node.sourceFiles = sourceFiles; - return node; - } + // + // Top-level nodes + // + + // @api + function createSourceFile( + statements: readonly Statement[], + endOfFileToken: EndOfFileToken, + flags: NodeFlags + ) { + const node = baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as Mutable; + node.statements = createNodeArray(statements); + node.endOfFileToken = endOfFileToken; + node.flags |= flags; + node.fileName = ""; + node.text = ""; + node.languageVersion = 0; + node.languageVariant = 0; + node.scriptKind = 0; + node.isDeclarationFile = false; + node.hasNoDefaultLib = false; + node.transformFlags |= + propagateChildrenFlags(node.statements) | + propagateChildFlags(node.endOfFileToken); + return node; + } - // @api - function updateBundle(node: Bundle, sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { - return node.sourceFiles !== sourceFiles - || node.prepends !== prepends - ? update(createBundle(sourceFiles, prepends), node) - : node; - } + function cloneSourceFileWithChanges( + source: SourceFile, + statements: readonly Statement[], + isDeclarationFile: boolean, + referencedFiles: readonly FileReference[], + typeReferences: readonly FileReference[], + hasNoDefaultLib: boolean, + libReferences: readonly FileReference[] + ) { + const node = (source.redirectInfo ? Object.create(source.redirectInfo.redirectTarget) : baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile)) as Mutable; + for (const p in source) { + if (p === "emitNode" || hasProperty(node, p) || !hasProperty(source, p)) continue; + (node as any)[p] = (source as any)[p]; + } + node.flags |= source.flags; + node.statements = createNodeArray(statements); + node.endOfFileToken = source.endOfFileToken; + node.isDeclarationFile = isDeclarationFile; + node.referencedFiles = referencedFiles; + node.typeReferenceDirectives = typeReferences; + node.hasNoDefaultLib = hasNoDefaultLib; + node.libReferenceDirectives = libReferences; + node.transformFlags = + propagateChildrenFlags(node.statements) | + propagateChildFlags(node.endOfFileToken); + node.impliedNodeFormat = source.impliedNodeFormat; + return node; + } - // @api - function createUnparsedSource(prologues: readonly UnparsedPrologue[], syntheticReferences: readonly UnparsedSyntheticReference[] | undefined, texts: readonly UnparsedSourceText[]) { - const node = createBaseNode(SyntaxKind.UnparsedSource); - node.prologues = prologues; - node.syntheticReferences = syntheticReferences; - node.texts = texts; - node.fileName = ""; - node.text = ""; - node.referencedFiles = emptyArray; - node.libReferenceDirectives = emptyArray; - node.getLineAndCharacterOfPosition = pos => getLineAndCharacterOfPosition(node, pos); - return node; - } + // @api + function updateSourceFile( + node: SourceFile, + statements: readonly Statement[], + isDeclarationFile = node.isDeclarationFile, + referencedFiles = node.referencedFiles, + typeReferenceDirectives = node.typeReferenceDirectives, + hasNoDefaultLib = node.hasNoDefaultLib, + libReferenceDirectives = node.libReferenceDirectives + ) { + return node.statements !== statements + || node.isDeclarationFile !== isDeclarationFile + || node.referencedFiles !== referencedFiles + || node.typeReferenceDirectives !== typeReferenceDirectives + || node.hasNoDefaultLib !== hasNoDefaultLib + || node.libReferenceDirectives !== libReferenceDirectives + ? update(cloneSourceFileWithChanges(node, statements, isDeclarationFile, referencedFiles, typeReferenceDirectives, hasNoDefaultLib, libReferenceDirectives), node) + : node; + } - function createBaseUnparsedNode(kind: T["kind"], data?: string) { - const node = createBaseNode(kind); - node.data = data; - return node; - } + // @api + function createBundle(sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { + const node = createBaseNode(SyntaxKind.Bundle); + node.prepends = prepends; + node.sourceFiles = sourceFiles; + return node; + } - // @api - function createUnparsedPrologue(data?: string): UnparsedPrologue { - return createBaseUnparsedNode(SyntaxKind.UnparsedPrologue, data); - } + // @api + function updateBundle(node: Bundle, sourceFiles: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) { + return node.sourceFiles !== sourceFiles + || node.prepends !== prepends + ? update(createBundle(sourceFiles, prepends), node) + : node; + } - // @api - function createUnparsedPrepend(data: string | undefined, texts: readonly UnparsedTextLike[]): UnparsedPrepend { - const node = createBaseUnparsedNode(SyntaxKind.UnparsedPrepend, data); - node.texts = texts; - return node; - } + // @api + function createUnparsedSource(prologues: readonly UnparsedPrologue[], syntheticReferences: readonly UnparsedSyntheticReference[] | undefined, texts: readonly UnparsedSourceText[]) { + const node = createBaseNode(SyntaxKind.UnparsedSource); + node.prologues = prologues; + node.syntheticReferences = syntheticReferences; + node.texts = texts; + node.fileName = ""; + node.text = ""; + node.referencedFiles = emptyArray; + node.libReferenceDirectives = emptyArray; + node.getLineAndCharacterOfPosition = pos => getLineAndCharacterOfPosition(node, pos); + return node; + } - // @api - function createUnparsedTextLike(data: string | undefined, internal: boolean): UnparsedTextLike { - return createBaseUnparsedNode(internal ? SyntaxKind.UnparsedInternalText : SyntaxKind.UnparsedText, data); - } + function createBaseUnparsedNode(kind: T["kind"], data?: string) { + const node = createBaseNode(kind); + node.data = data; + return node; + } - // @api - function createUnparsedSyntheticReference(section: BundleFileHasNoDefaultLib | BundleFileReference): UnparsedSyntheticReference { - const node = createBaseNode(SyntaxKind.UnparsedSyntheticReference); - node.data = section.data; - node.section = section; - return node; - } + // @api + function createUnparsedPrologue(data?: string): UnparsedPrologue { + return createBaseUnparsedNode(SyntaxKind.UnparsedPrologue, data); + } - // @api - function createInputFiles(): InputFiles { - const node = createBaseNode(SyntaxKind.InputFiles); - node.javascriptText = ""; - node.declarationText = ""; - return node; - } + // @api + function createUnparsedPrepend(data: string | undefined, texts: readonly UnparsedTextLike[]): UnparsedPrepend { + const node = createBaseUnparsedNode(SyntaxKind.UnparsedPrepend, data); + node.texts = texts; + return node; + } - // - // Synthetic Nodes (used by checker) - // + // @api + function createUnparsedTextLike(data: string | undefined, internal: boolean): UnparsedTextLike { + return createBaseUnparsedNode(internal ? SyntaxKind.UnparsedInternalText : SyntaxKind.UnparsedText, data); + } - // @api - function createSyntheticExpression(type: Type, isSpread = false, tupleNameSource?: ParameterDeclaration | NamedTupleMember) { - const node = createBaseNode(SyntaxKind.SyntheticExpression); - node.type = type; - node.isSpread = isSpread; - node.tupleNameSource = tupleNameSource; - return node; - } + // @api + function createUnparsedSyntheticReference(section: BundleFileHasNoDefaultLib | BundleFileReference): UnparsedSyntheticReference { + const node = createBaseNode(SyntaxKind.UnparsedSyntheticReference); + node.data = section.data; + node.section = section; + return node; + } - // @api - function createSyntaxList(children: Node[]) { - const node = createBaseNode(SyntaxKind.SyntaxList); - node._children = children; - return node; - } + // @api + function createInputFiles(): InputFiles { + const node = createBaseNode(SyntaxKind.InputFiles); + node.javascriptText = ""; + node.declarationText = ""; + return node; + } - // - // Transformation nodes - // + // + // Synthetic Nodes (used by checker) + // - /** - * Creates a synthetic statement to act as a placeholder for a not-emitted statement in - * order to preserve comments. - * - * @param original The original statement. - */ - // @api - function createNotEmittedStatement(original: Node) { - const node = createBaseNode(SyntaxKind.NotEmittedStatement); - node.original = original; - setTextRange(node, original); - return node; - } + // @api + function createSyntheticExpression(type: Type, isSpread = false, tupleNameSource?: ParameterDeclaration | NamedTupleMember) { + const node = createBaseNode(SyntaxKind.SyntheticExpression); + node.type = type; + node.isSpread = isSpread; + node.tupleNameSource = tupleNameSource; + return node; + } - /** - * Creates a synthetic expression to act as a placeholder for a not-emitted expression in - * order to preserve comments or sourcemap positions. - * - * @param expression The inner expression to emit. - * @param original The original outer expression. - */ - // @api - function createPartiallyEmittedExpression(expression: Expression, original?: Node) { - const node = createBaseNode(SyntaxKind.PartiallyEmittedExpression); - node.expression = expression; - node.original = original; - node.transformFlags |= - propagateChildFlags(node.expression) | - TransformFlags.ContainsTypeScript; - setTextRange(node, original); - return node; - } + // @api + function createSyntaxList(children: Node[]) { + const node = createBaseNode(SyntaxKind.SyntaxList); + node._children = children; + return node; + } - // @api - function updatePartiallyEmittedExpression(node: PartiallyEmittedExpression, expression: Expression) { - return node.expression !== expression - ? update(createPartiallyEmittedExpression(expression, node.original), node) - : node; - } + // + // Transformation nodes + // - function flattenCommaElements(node: Expression): Expression | readonly Expression[] { - if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { - if (isCommaListExpression(node)) { - return node.elements; - } - if (isBinaryExpression(node) && isCommaToken(node.operatorToken)) { - return [node.left, node.right]; - } - } - return node; - } + /** + * Creates a synthetic statement to act as a placeholder for a not-emitted statement in + * order to preserve comments. + * + * @param original The original statement. + */ + // @api + function createNotEmittedStatement(original: Node) { + const node = createBaseNode(SyntaxKind.NotEmittedStatement); + node.original = original; + setTextRange(node, original); + return node; + } - // @api - function createCommaListExpression(elements: readonly Expression[]) { - const node = createBaseNode(SyntaxKind.CommaListExpression); - node.elements = createNodeArray(sameFlatMap(elements, flattenCommaElements)); - node.transformFlags |= propagateChildrenFlags(node.elements); - return node; - } + /** + * Creates a synthetic expression to act as a placeholder for a not-emitted expression in + * order to preserve comments or sourcemap positions. + * + * @param expression The inner expression to emit. + * @param original The original outer expression. + */ + // @api + function createPartiallyEmittedExpression(expression: Expression, original?: Node) { + const node = createBaseNode(SyntaxKind.PartiallyEmittedExpression); + node.expression = expression; + node.original = original; + node.transformFlags |= + propagateChildFlags(node.expression) | + TransformFlags.ContainsTypeScript; + setTextRange(node, original); + return node; + } - // @api - function updateCommaListExpression(node: CommaListExpression, elements: readonly Expression[]) { - return node.elements !== elements - ? update(createCommaListExpression(elements), node) - : node; - } + // @api + function updatePartiallyEmittedExpression(node: PartiallyEmittedExpression, expression: Expression) { + return node.expression !== expression + ? update(createPartiallyEmittedExpression(expression, node.original), node) + : node; + } - /** - * Creates a synthetic element to act as a placeholder for the end of an emitted declaration in - * order to properly emit exports. - */ - // @api - function createEndOfDeclarationMarker(original: Node) { - const node = createBaseNode(SyntaxKind.EndOfDeclarationMarker); - node.emitNode = {} as EmitNode; - node.original = original; - return node; + function flattenCommaElements(node: Expression): Expression | readonly Expression[] { + if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { + if (isCommaListExpression(node)) { + return node.elements; + } + if (isBinaryExpression(node) && isCommaToken(node.operatorToken)) { + return [node.left, node.right]; + } } + return node; + } - /** - * Creates a synthetic element to act as a placeholder for the beginning of a merged declaration in - * order to properly emit exports. - */ - // @api - function createMergeDeclarationMarker(original: Node) { - const node = createBaseNode(SyntaxKind.MergeDeclarationMarker); - node.emitNode = {} as EmitNode; - node.original = original; - return node; - } + // @api + function createCommaListExpression(elements: readonly Expression[]) { + const node = createBaseNode(SyntaxKind.CommaListExpression); + node.elements = createNodeArray(sameFlatMap(elements, flattenCommaElements)); + node.transformFlags |= propagateChildrenFlags(node.elements); + return node; + } - // @api - function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) { - const node = createBaseNode(SyntaxKind.SyntheticReferenceExpression); - node.expression = expression; - node.thisArg = thisArg; - node.transformFlags |= - propagateChildFlags(node.expression) | - propagateChildFlags(node.thisArg); - return node; - } + // @api + function updateCommaListExpression(node: CommaListExpression, elements: readonly Expression[]) { + return node.elements !== elements + ? update(createCommaListExpression(elements), node) + : node; + } - // @api - function updateSyntheticReferenceExpression(node: SyntheticReferenceExpression, expression: Expression, thisArg: Expression) { - return node.expression !== expression - || node.thisArg !== thisArg - ? update(createSyntheticReferenceExpression(expression, thisArg), node) - : node; - } + /** + * Creates a synthetic element to act as a placeholder for the end of an emitted declaration in + * order to properly emit exports. + */ + // @api + function createEndOfDeclarationMarker(original: Node) { + const node = createBaseNode(SyntaxKind.EndOfDeclarationMarker); + node.emitNode = {} as EmitNode; + node.original = original; + return node; + } - // @api - function cloneNode(node: T): T; - function cloneNode(node: T) { - // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of - // the original node. We also need to exclude specific properties and only include own- - // properties (to skip members already defined on the shared prototype). - if (node === undefined) { - return node; - } + /** + * Creates a synthetic element to act as a placeholder for the beginning of a merged declaration in + * order to properly emit exports. + */ + // @api + function createMergeDeclarationMarker(original: Node) { + const node = createBaseNode(SyntaxKind.MergeDeclarationMarker); + node.emitNode = {} as EmitNode; + node.original = original; + return node; + } - const clone = - isSourceFile(node) ? baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as T : - isIdentifier(node) ? baseFactory.createBaseIdentifierNode(SyntaxKind.Identifier) as T : - isPrivateIdentifier(node) ? baseFactory.createBasePrivateIdentifierNode(SyntaxKind.PrivateIdentifier) as T : - !isNodeKind(node.kind) ? baseFactory.createBaseTokenNode(node.kind) as T : - baseFactory.createBaseNode(node.kind) as T; + // @api + function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) { + const node = createBaseNode(SyntaxKind.SyntheticReferenceExpression); + node.expression = expression; + node.thisArg = thisArg; + node.transformFlags |= + propagateChildFlags(node.expression) | + propagateChildFlags(node.thisArg); + return node; + } - (clone as Mutable).flags |= (node.flags & ~NodeFlags.Synthesized); - (clone as Mutable).transformFlags = node.transformFlags; - setOriginalNode(clone, node); + // @api + function updateSyntheticReferenceExpression(node: SyntheticReferenceExpression, expression: Expression, thisArg: Expression) { + return node.expression !== expression + || node.thisArg !== thisArg + ? update(createSyntheticReferenceExpression(expression, thisArg), node) + : node; + } - for (const key in node) { - if (hasProperty(clone, key) || !hasProperty(node, key)) { - continue; - } + // @api + function cloneNode(node: T): T; + function cloneNode(node: T) { + // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of + // the original node. We also need to exclude specific properties and only include own- + // properties (to skip members already defined on the shared prototype). + if (node === undefined) { + return node; + } - clone[key] = node[key]; - } + const clone = + isSourceFile(node) ? baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as T : + isIdentifier(node) ? baseFactory.createBaseIdentifierNode(SyntaxKind.Identifier) as T : + isPrivateIdentifier(node) ? baseFactory.createBasePrivateIdentifierNode(SyntaxKind.PrivateIdentifier) as T : + !isNodeKind(node.kind) ? baseFactory.createBaseTokenNode(node.kind) as T : + baseFactory.createBaseNode(node.kind) as T; - return clone; - } + (clone as Mutable).flags |= (node.flags & ~NodeFlags.Synthesized); + (clone as Mutable).transformFlags = node.transformFlags; + setOriginalNode(clone, node); - // compound nodes - function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[]): CallExpression; - function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; - function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { - return createCallExpression( - createFunctionExpression( - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ param ? [param] : [], - /*type*/ undefined, - createBlock(statements, /*multiLine*/ true) - ), - /*typeArguments*/ undefined, - /*argumentsArray*/ paramValue ? [paramValue] : [] - ); - } + for (const key in node) { + if (hasProperty(clone, key) || !hasProperty(node, key)) { + continue; + } - function createImmediatelyInvokedArrowFunction(statements: readonly Statement[]): CallExpression; - function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; - function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { - return createCallExpression( - createArrowFunction( - /*modifiers*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ param ? [param] : [], - /*type*/ undefined, - /*equalsGreaterThanToken*/ undefined, - createBlock(statements, /*multiLine*/ true) - ), - /*typeArguments*/ undefined, - /*argumentsArray*/ paramValue ? [paramValue] : [] - ); + clone[key] = node[key]; } - function createVoidZero() { - return createVoidExpression(createNumericLiteral("0")); - } + return clone; + } - function createExportDefault(expression: Expression) { - return createExportAssignment( + // compound nodes + function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[]): CallExpression; + function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; + function createImmediatelyInvokedFunctionExpression(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { + return createCallExpression( + createFunctionExpression( /*modifiers*/ undefined, - /*isExportEquals*/ false, - expression); - } + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ param ? [param] : [], + /*type*/ undefined, + createBlock(statements, /*multiLine*/ true) + ), + /*typeArguments*/ undefined, + /*argumentsArray*/ paramValue ? [paramValue] : [] + ); + } - function createExternalModuleExport(exportName: Identifier) { - return createExportDeclaration( + function createImmediatelyInvokedArrowFunction(statements: readonly Statement[]): CallExpression; + function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; + function createImmediatelyInvokedArrowFunction(statements: readonly Statement[], param?: ParameterDeclaration, paramValue?: Expression) { + return createCallExpression( + createArrowFunction( /*modifiers*/ undefined, - /*isTypeOnly*/ false, - createNamedExports([ - createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, exportName) - ]) - ); - } + /*typeParameters*/ undefined, + /*parameters*/ param ? [param] : [], + /*type*/ undefined, + /*equalsGreaterThanToken*/ undefined, + createBlock(statements, /*multiLine*/ true) + ), + /*typeArguments*/ undefined, + /*argumentsArray*/ paramValue ? [paramValue] : [] + ); + } - // - // Utilities - // + function createVoidZero() { + return createVoidExpression(createNumericLiteral("0")); + } - function createTypeCheck(value: Expression, tag: TypeOfTag) { - return tag === "undefined" - ? factory.createStrictEquality(value, createVoidZero()) - : factory.createStrictEquality(createTypeOfExpression(value), createStringLiteral(tag)); - } + function createExportDefault(expression: Expression) { + return createExportAssignment( + /*modifiers*/ undefined, + /*isExportEquals*/ false, + expression); + } - function createMethodCall(object: Expression, methodName: string | Identifier, argumentsList: readonly Expression[]) { - // Preserve the optionality of `object`. - if (isCallChain(object)) { - return createCallChain( - createPropertyAccessChain(object, /*questionDotToken*/ undefined, methodName), - /*questionDotToken*/ undefined, - /*typeArguments*/ undefined, - argumentsList - ); - } - return createCallExpression( - createPropertyAccessExpression(object, methodName), + function createExternalModuleExport(exportName: Identifier) { + return createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + createNamedExports([ + createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, exportName) + ]) + ); + } + + // + // Utilities + // + + function createTypeCheck(value: Expression, tag: TypeOfTag) { + return tag === "undefined" + ? factory.createStrictEquality(value, createVoidZero()) + : factory.createStrictEquality(createTypeOfExpression(value), createStringLiteral(tag)); + } + + function createMethodCall(object: Expression, methodName: string | Identifier, argumentsList: readonly Expression[]) { + // Preserve the optionality of `object`. + if (isCallChain(object)) { + return createCallChain( + createPropertyAccessChain(object, /*questionDotToken*/ undefined, methodName), + /*questionDotToken*/ undefined, /*typeArguments*/ undefined, argumentsList ); } + return createCallExpression( + createPropertyAccessExpression(object, methodName), + /*typeArguments*/ undefined, + argumentsList + ); + } - function createFunctionBindCall(target: Expression, thisArg: Expression, argumentsList: readonly Expression[]) { - return createMethodCall(target, "bind", [thisArg, ...argumentsList]); - } + function createFunctionBindCall(target: Expression, thisArg: Expression, argumentsList: readonly Expression[]) { + return createMethodCall(target, "bind", [thisArg, ...argumentsList]); + } - function createFunctionCallCall(target: Expression, thisArg: Expression, argumentsList: readonly Expression[]) { - return createMethodCall(target, "call", [thisArg, ...argumentsList]); - } + function createFunctionCallCall(target: Expression, thisArg: Expression, argumentsList: readonly Expression[]) { + return createMethodCall(target, "call", [thisArg, ...argumentsList]); + } - function createFunctionApplyCall(target: Expression, thisArg: Expression, argumentsExpression: Expression) { - return createMethodCall(target, "apply", [thisArg, argumentsExpression]); - } + function createFunctionApplyCall(target: Expression, thisArg: Expression, argumentsExpression: Expression) { + return createMethodCall(target, "apply", [thisArg, argumentsExpression]); + } - function createGlobalMethodCall(globalObjectName: string, methodName: string, argumentsList: readonly Expression[]) { - return createMethodCall(createIdentifier(globalObjectName), methodName, argumentsList); - } + function createGlobalMethodCall(globalObjectName: string, methodName: string, argumentsList: readonly Expression[]) { + return createMethodCall(createIdentifier(globalObjectName), methodName, argumentsList); + } - function createArraySliceCall(array: Expression, start?: number | Expression) { - return createMethodCall(array, "slice", start === undefined ? [] : [asExpression(start)]); - } + function createArraySliceCall(array: Expression, start?: number | Expression) { + return createMethodCall(array, "slice", start === undefined ? [] : [asExpression(start)]); + } - function createArrayConcatCall(array: Expression, argumentsList: readonly Expression[]) { - return createMethodCall(array, "concat", argumentsList); - } + function createArrayConcatCall(array: Expression, argumentsList: readonly Expression[]) { + return createMethodCall(array, "concat", argumentsList); + } - function createObjectDefinePropertyCall(target: Expression, propertyName: string | Expression, attributes: Expression) { - return createGlobalMethodCall("Object", "defineProperty", [target, asExpression(propertyName), attributes]); - } + function createObjectDefinePropertyCall(target: Expression, propertyName: string | Expression, attributes: Expression) { + return createGlobalMethodCall("Object", "defineProperty", [target, asExpression(propertyName), attributes]); + } - function createReflectGetCall(target: Expression, propertyKey: Expression, receiver?: Expression): CallExpression { - return createGlobalMethodCall("Reflect", "get", receiver ? [target, propertyKey, receiver] : [target, propertyKey]); - } + function createReflectGetCall(target: Expression, propertyKey: Expression, receiver?: Expression): CallExpression { + return createGlobalMethodCall("Reflect", "get", receiver ? [target, propertyKey, receiver] : [target, propertyKey]); + } - function createReflectSetCall(target: Expression, propertyKey: Expression, value: Expression, receiver?: Expression): CallExpression { - return createGlobalMethodCall("Reflect", "set", receiver ? [target, propertyKey, value, receiver] : [target, propertyKey, value]); - } + function createReflectSetCall(target: Expression, propertyKey: Expression, value: Expression, receiver?: Expression): CallExpression { + return createGlobalMethodCall("Reflect", "set", receiver ? [target, propertyKey, value, receiver] : [target, propertyKey, value]); + } - function tryAddPropertyAssignment(properties: Push, propertyName: string, expression: Expression | undefined) { - if (expression) { - properties.push(createPropertyAssignment(propertyName, expression)); - return true; - } - return false; + function tryAddPropertyAssignment(properties: Push, propertyName: string, expression: Expression | undefined) { + if (expression) { + properties.push(createPropertyAssignment(propertyName, expression)); + return true; } + return false; + } - function createPropertyDescriptor(attributes: PropertyDescriptorAttributes, singleLine?: boolean) { - const properties: PropertyAssignment[] = []; - tryAddPropertyAssignment(properties, "enumerable", asExpression(attributes.enumerable)); - tryAddPropertyAssignment(properties, "configurable", asExpression(attributes.configurable)); + function createPropertyDescriptor(attributes: PropertyDescriptorAttributes, singleLine?: boolean) { + const properties: PropertyAssignment[] = []; + tryAddPropertyAssignment(properties, "enumerable", asExpression(attributes.enumerable)); + tryAddPropertyAssignment(properties, "configurable", asExpression(attributes.configurable)); - let isData = tryAddPropertyAssignment(properties, "writable", asExpression(attributes.writable)); - isData = tryAddPropertyAssignment(properties, "value", attributes.value) || isData; + let isData = tryAddPropertyAssignment(properties, "writable", asExpression(attributes.writable)); + isData = tryAddPropertyAssignment(properties, "value", attributes.value) || isData; - let isAccessor = tryAddPropertyAssignment(properties, "get", attributes.get); - isAccessor = tryAddPropertyAssignment(properties, "set", attributes.set) || isAccessor; + let isAccessor = tryAddPropertyAssignment(properties, "get", attributes.get); + isAccessor = tryAddPropertyAssignment(properties, "set", attributes.set) || isAccessor; - Debug.assert(!(isData && isAccessor), "A PropertyDescriptor may not be both an accessor descriptor and a data descriptor."); - return createObjectLiteralExpression(properties, !singleLine); - } + Debug.assert(!(isData && isAccessor), "A PropertyDescriptor may not be both an accessor descriptor and a data descriptor."); + return createObjectLiteralExpression(properties, !singleLine); + } - function updateOuterExpression(outerExpression: OuterExpression, expression: Expression) { - switch (outerExpression.kind) { - case SyntaxKind.ParenthesizedExpression: return updateParenthesizedExpression(outerExpression, expression); - case SyntaxKind.TypeAssertionExpression: return updateTypeAssertion(outerExpression, outerExpression.type, expression); - case SyntaxKind.AsExpression: return updateAsExpression(outerExpression, expression, outerExpression.type); - case SyntaxKind.SatisfiesExpression: return updateSatisfiesExpression(outerExpression, expression, outerExpression.type); - case SyntaxKind.NonNullExpression: return updateNonNullExpression(outerExpression, expression); - case SyntaxKind.PartiallyEmittedExpression: return updatePartiallyEmittedExpression(outerExpression, expression); - } + function updateOuterExpression(outerExpression: OuterExpression, expression: Expression) { + switch (outerExpression.kind) { + case SyntaxKind.ParenthesizedExpression: return updateParenthesizedExpression(outerExpression, expression); + case SyntaxKind.TypeAssertionExpression: return updateTypeAssertion(outerExpression, outerExpression.type, expression); + case SyntaxKind.AsExpression: return updateAsExpression(outerExpression, expression, outerExpression.type); + case SyntaxKind.SatisfiesExpression: return updateSatisfiesExpression(outerExpression, expression, outerExpression.type); + case SyntaxKind.NonNullExpression: return updateNonNullExpression(outerExpression, expression); + case SyntaxKind.PartiallyEmittedExpression: return updatePartiallyEmittedExpression(outerExpression, expression); } + } - /** - * Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions. - * - * A parenthesized expression can be ignored when all of the following are true: - * - * - It's `pos` and `end` are not -1 - * - It does not have a custom source map range - * - It does not have a custom comment range - * - It does not have synthetic leading or trailing comments - * - * If an outermost parenthesized expression is ignored, but the containing expression requires a parentheses around - * the expression to maintain precedence, a new parenthesized expression should be created automatically when - * the containing expression is created/updated. - */ - function isIgnorableParen(node: Expression) { - return isParenthesizedExpression(node) - && nodeIsSynthesized(node) - && nodeIsSynthesized(getSourceMapRange(node)) - && nodeIsSynthesized(getCommentRange(node)) - && !some(getSyntheticLeadingComments(node)) - && !some(getSyntheticTrailingComments(node)); - } + /** + * Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions. + * + * A parenthesized expression can be ignored when all of the following are true: + * + * - It's `pos` and `end` are not -1 + * - It does not have a custom source map range + * - It does not have a custom comment range + * - It does not have synthetic leading or trailing comments + * + * If an outermost parenthesized expression is ignored, but the containing expression requires a parentheses around + * the expression to maintain precedence, a new parenthesized expression should be created automatically when + * the containing expression is created/updated. + */ + function isIgnorableParen(node: Expression) { + return isParenthesizedExpression(node) + && nodeIsSynthesized(node) + && nodeIsSynthesized(getSourceMapRange(node)) + && nodeIsSynthesized(getCommentRange(node)) + && !some(getSyntheticLeadingComments(node)) + && !some(getSyntheticTrailingComments(node)); + } - function restoreOuterExpressions(outerExpression: Expression | undefined, innerExpression: Expression, kinds = OuterExpressionKinds.All): Expression { - if (outerExpression && isOuterExpression(outerExpression, kinds) && !isIgnorableParen(outerExpression)) { - return updateOuterExpression( - outerExpression, - restoreOuterExpressions(outerExpression.expression, innerExpression) - ); - } - return innerExpression; + function restoreOuterExpressions(outerExpression: Expression | undefined, innerExpression: Expression, kinds = OuterExpressionKinds.All): Expression { + if (outerExpression && isOuterExpression(outerExpression, kinds) && !isIgnorableParen(outerExpression)) { + return updateOuterExpression( + outerExpression, + restoreOuterExpressions(outerExpression.expression, innerExpression) + ); } + return innerExpression; + } - function restoreEnclosingLabel(node: Statement, outermostLabeledStatement: LabeledStatement | undefined, afterRestoreLabelCallback?: (node: LabeledStatement) => void): Statement { - if (!outermostLabeledStatement) { - return node; - } - const updated = updateLabeledStatement( - outermostLabeledStatement, - outermostLabeledStatement.label, - isLabeledStatement(outermostLabeledStatement.statement) - ? restoreEnclosingLabel(node, outermostLabeledStatement.statement) - : node - ); - if (afterRestoreLabelCallback) { - afterRestoreLabelCallback(outermostLabeledStatement); - } - return updated; + function restoreEnclosingLabel(node: Statement, outermostLabeledStatement: LabeledStatement | undefined, afterRestoreLabelCallback?: (node: LabeledStatement) => void): Statement { + if (!outermostLabeledStatement) { + return node; } + const updated = updateLabeledStatement( + outermostLabeledStatement, + outermostLabeledStatement.label, + isLabeledStatement(outermostLabeledStatement.statement) + ? restoreEnclosingLabel(node, outermostLabeledStatement.statement) + : node + ); + if (afterRestoreLabelCallback) { + afterRestoreLabelCallback(outermostLabeledStatement); + } + return updated; + } - function shouldBeCapturedInTempVariable(node: Expression, cacheIdentifiers: boolean): boolean { - const target = skipParentheses(node); - switch (target.kind) { - case SyntaxKind.Identifier: - return cacheIdentifiers; - case SyntaxKind.ThisKeyword: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.StringLiteral: + function shouldBeCapturedInTempVariable(node: Expression, cacheIdentifiers: boolean): boolean { + const target = skipParentheses(node); + switch (target.kind) { + case SyntaxKind.Identifier: + return cacheIdentifiers; + case SyntaxKind.ThisKeyword: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.StringLiteral: + return false; + case SyntaxKind.ArrayLiteralExpression: + const elements = (target as ArrayLiteralExpression).elements; + if (elements.length === 0) { return false; - case SyntaxKind.ArrayLiteralExpression: - const elements = (target as ArrayLiteralExpression).elements; - if (elements.length === 0) { - return false; - } - return true; - case SyntaxKind.ObjectLiteralExpression: - return (target as ObjectLiteralExpression).properties.length > 0; - default: - return true; - } + } + return true; + case SyntaxKind.ObjectLiteralExpression: + return (target as ObjectLiteralExpression).properties.length > 0; + default: + return true; } + } - function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget, cacheIdentifiers = false): CallBinding { - const callee = skipOuterExpressions(expression, OuterExpressionKinds.All); - let thisArg: Expression; - let target: LeftHandSideExpression; - if (isSuperProperty(callee)) { - thisArg = createThis(); - target = callee; - } - else if (isSuperKeyword(callee)) { - thisArg = createThis(); - target = languageVersion !== undefined && languageVersion < ScriptTarget.ES2015 - ? setTextRange(createIdentifier("_super"), callee) - : callee as PrimaryExpression; - } - else if (getEmitFlags(callee) & EmitFlags.HelperName) { - thisArg = createVoidZero(); - target = parenthesizerRules().parenthesizeLeftSideOfAccess(callee, /*optionalChain*/ false); - } - else if (isPropertyAccessExpression(callee)) { - if (shouldBeCapturedInTempVariable(callee.expression, cacheIdentifiers)) { - // for `a.b()` target is `(_a = a).b` and thisArg is `_a` - thisArg = createTempVariable(recordTempVariable); - target = createPropertyAccessExpression( - setTextRange( - factory.createAssignment( - thisArg, - callee.expression - ), + function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget, cacheIdentifiers = false): CallBinding { + const callee = skipOuterExpressions(expression, OuterExpressionKinds.All); + let thisArg: Expression; + let target: LeftHandSideExpression; + if (isSuperProperty(callee)) { + thisArg = createThis(); + target = callee; + } + else if (isSuperKeyword(callee)) { + thisArg = createThis(); + target = languageVersion !== undefined && languageVersion < ScriptTarget.ES2015 + ? setTextRange(createIdentifier("_super"), callee) + : callee as PrimaryExpression; + } + else if (getEmitFlags(callee) & EmitFlags.HelperName) { + thisArg = createVoidZero(); + target = parenthesizerRules().parenthesizeLeftSideOfAccess(callee, /*optionalChain*/ false); + } + else if (isPropertyAccessExpression(callee)) { + if (shouldBeCapturedInTempVariable(callee.expression, cacheIdentifiers)) { + // for `a.b()` target is `(_a = a).b` and thisArg is `_a` + thisArg = createTempVariable(recordTempVariable); + target = createPropertyAccessExpression( + setTextRange( + factory.createAssignment( + thisArg, callee.expression ), - callee.name - ); - setTextRange(target, callee); - } - else { - thisArg = callee.expression; - target = callee; - } + callee.expression + ), + callee.name + ); + setTextRange(target, callee); + } + else { + thisArg = callee.expression; + target = callee; } - else if (isElementAccessExpression(callee)) { - if (shouldBeCapturedInTempVariable(callee.expression, cacheIdentifiers)) { - // for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a` - thisArg = createTempVariable(recordTempVariable); - target = createElementAccessExpression( - setTextRange( - factory.createAssignment( - thisArg, - callee.expression - ), + } + else if (isElementAccessExpression(callee)) { + if (shouldBeCapturedInTempVariable(callee.expression, cacheIdentifiers)) { + // for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a` + thisArg = createTempVariable(recordTempVariable); + target = createElementAccessExpression( + setTextRange( + factory.createAssignment( + thisArg, callee.expression ), - callee.argumentExpression - ); - setTextRange(target, callee); - } - else { - thisArg = callee.expression; - target = callee; - } + callee.expression + ), + callee.argumentExpression + ); + setTextRange(target, callee); } else { - // for `a()` target is `a` and thisArg is `void 0` - thisArg = createVoidZero(); - target = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); + thisArg = callee.expression; + target = callee; } - - return { target, thisArg }; } + else { + // for `a()` target is `a` and thisArg is `void 0` + thisArg = createVoidZero(); + target = parenthesizerRules().parenthesizeLeftSideOfAccess(expression, /*optionalChain*/ false); + } + + return { target, thisArg }; + } - function createAssignmentTargetWrapper(paramName: Identifier, expression: Expression): LeftHandSideExpression { - return createPropertyAccessExpression( - // Explicit parens required because of v8 regression (https://bugs.chromium.org/p/v8/issues/detail?id=9560) - createParenthesizedExpression( - createObjectLiteralExpression([ - createSetAccessorDeclaration( + function createAssignmentTargetWrapper(paramName: Identifier, expression: Expression): LeftHandSideExpression { + return createPropertyAccessExpression( + // Explicit parens required because of v8 regression (https://bugs.chromium.org/p/v8/issues/detail?id=9560) + createParenthesizedExpression( + createObjectLiteralExpression([ + createSetAccessorDeclaration( + /*modifiers*/ undefined, + "value", + [createParameterDeclaration( /*modifiers*/ undefined, - "value", - [createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - paramName, - /*questionToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - )], - createBlock([ - createExpressionStatement(expression) - ]) - ) - ]) - ), - "value" - ); - } + /*dotDotDotToken*/ undefined, + paramName, + /*questionToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + )], + createBlock([ + createExpressionStatement(expression) + ]) + ) + ]) + ), + "value" + ); + } - function inlineExpressions(expressions: readonly Expression[]) { - // Avoid deeply nested comma expressions as traversing them during emit can result in "Maximum call - // stack size exceeded" errors. - return expressions.length > 10 - ? createCommaListExpression(expressions) - : reduceLeft(expressions, factory.createComma)!; - } + function inlineExpressions(expressions: readonly Expression[]) { + // Avoid deeply nested comma expressions as traversing them during emit can result in "Maximum call + // stack size exceeded" errors. + return expressions.length > 10 + ? createCommaListExpression(expressions) + : reduceLeft(expressions, factory.createComma)!; + } - function getName(node: Declaration | undefined, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags: EmitFlags = 0) { - const nodeName = getNameOfDeclaration(node); - if (nodeName && isIdentifier(nodeName) && !isGeneratedIdentifier(nodeName)) { - // TODO(rbuckton): Does this need to be parented? - const name = setParent(setTextRange(cloneNode(nodeName), nodeName), nodeName.parent); - emitFlags |= getEmitFlags(nodeName); - if (!allowSourceMaps) emitFlags |= EmitFlags.NoSourceMap; - if (!allowComments) emitFlags |= EmitFlags.NoComments; - if (emitFlags) setEmitFlags(name, emitFlags); - return name; - } - return getGeneratedNameForNode(node); + function getName(node: Declaration | undefined, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags: EmitFlags = 0) { + const nodeName = getNameOfDeclaration(node); + if (nodeName && isIdentifier(nodeName) && !isGeneratedIdentifier(nodeName)) { + // TODO(rbuckton): Does this need to be parented? + const name = setParent(setTextRange(cloneNode(nodeName), nodeName), nodeName.parent); + emitFlags |= getEmitFlags(nodeName); + if (!allowSourceMaps) emitFlags |= EmitFlags.NoSourceMap; + if (!allowComments) emitFlags |= EmitFlags.NoComments; + if (emitFlags) setEmitFlags(name, emitFlags); + return name; } + return getGeneratedNameForNode(node); + } - /** - * Gets the internal name of a declaration. This is primarily used for declarations that can be - * referred to by name in the body of an ES5 class function body. An internal name will *never* - * be prefixed with an module or namespace export modifier like "exports." when emitted as an - * expression. An internal name will also *never* be renamed due to a collision with a block - * scoped variable. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getInternalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { - return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName | EmitFlags.InternalName); - } + /** + * Gets the internal name of a declaration. This is primarily used for declarations that can be + * referred to by name in the body of an ES5 class function body. An internal name will *never* + * be prefixed with an module or namespace export modifier like "exports." when emitted as an + * expression. An internal name will also *never* be renamed due to a collision with a block + * scoped variable. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getInternalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { + return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName | EmitFlags.InternalName); + } - /** - * Gets the local name of a declaration. This is primarily used for declarations that can be - * referred to by name in the declaration's immediate scope (classes, enums, namespaces). A - * local name will *never* be prefixed with an module or namespace export modifier like - * "exports." when emitted as an expression. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getLocalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { - return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName); - } + /** + * Gets the local name of a declaration. This is primarily used for declarations that can be + * referred to by name in the declaration's immediate scope (classes, enums, namespaces). A + * local name will *never* be prefixed with an module or namespace export modifier like + * "exports." when emitted as an expression. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getLocalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) { + return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName); + } - /** - * Gets the export name of a declaration. This is primarily used for declarations that can be - * referred to by name in the declaration's immediate scope (classes, enums, namespaces). An - * export name will *always* be prefixed with an module or namespace export modifier like - * `"exports."` when emitted as an expression if the name points to an exported symbol. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getExportName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier { - return getName(node, allowComments, allowSourceMaps, EmitFlags.ExportName); - } + /** + * Gets the export name of a declaration. This is primarily used for declarations that can be + * referred to by name in the declaration's immediate scope (classes, enums, namespaces). An + * export name will *always* be prefixed with an module or namespace export modifier like + * `"exports."` when emitted as an expression if the name points to an exported symbol. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getExportName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier { + return getName(node, allowComments, allowSourceMaps, EmitFlags.ExportName); + } - /** - * Gets the name of a declaration for use in declarations. - * - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getDeclarationName(node: Declaration | undefined, allowComments?: boolean, allowSourceMaps?: boolean) { - return getName(node, allowComments, allowSourceMaps); - } + /** + * Gets the name of a declaration for use in declarations. + * + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getDeclarationName(node: Declaration | undefined, allowComments?: boolean, allowSourceMaps?: boolean) { + return getName(node, allowComments, allowSourceMaps); + } - /** - * Gets a namespace-qualified name for use in expressions. - * - * @param ns The namespace identifier. - * @param name The name. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getNamespaceMemberName(ns: Identifier, name: Identifier, allowComments?: boolean, allowSourceMaps?: boolean): PropertyAccessExpression { - const qualifiedName = createPropertyAccessExpression(ns, nodeIsSynthesized(name) ? name : cloneNode(name)); - setTextRange(qualifiedName, name); - let emitFlags: EmitFlags = 0; - if (!allowSourceMaps) emitFlags |= EmitFlags.NoSourceMap; - if (!allowComments) emitFlags |= EmitFlags.NoComments; - if (emitFlags) setEmitFlags(qualifiedName, emitFlags); - return qualifiedName; - } + /** + * Gets a namespace-qualified name for use in expressions. + * + * @param ns The namespace identifier. + * @param name The name. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getNamespaceMemberName(ns: Identifier, name: Identifier, allowComments?: boolean, allowSourceMaps?: boolean): PropertyAccessExpression { + const qualifiedName = createPropertyAccessExpression(ns, nodeIsSynthesized(name) ? name : cloneNode(name)); + setTextRange(qualifiedName, name); + let emitFlags: EmitFlags = 0; + if (!allowSourceMaps) emitFlags |= EmitFlags.NoSourceMap; + if (!allowComments) emitFlags |= EmitFlags.NoComments; + if (emitFlags) setEmitFlags(qualifiedName, emitFlags); + return qualifiedName; + } - /** - * Gets the exported name of a declaration for use in expressions. - * - * An exported name will *always* be prefixed with an module or namespace export modifier like - * "exports." if the name points to an exported symbol. - * - * @param ns The namespace identifier. - * @param node The declaration. - * @param allowComments A value indicating whether comments may be emitted for the name. - * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. - */ - function getExternalModuleOrNamespaceExportName(ns: Identifier | undefined, node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier | PropertyAccessExpression { - if (ns && hasSyntacticModifier(node, ModifierFlags.Export)) { - return getNamespaceMemberName(ns, getName(node), allowComments, allowSourceMaps); - } - return getExportName(node, allowComments, allowSourceMaps); + /** + * Gets the exported name of a declaration for use in expressions. + * + * An exported name will *always* be prefixed with an module or namespace export modifier like + * "exports." if the name points to an exported symbol. + * + * @param ns The namespace identifier. + * @param node The declaration. + * @param allowComments A value indicating whether comments may be emitted for the name. + * @param allowSourceMaps A value indicating whether source maps may be emitted for the name. + */ + function getExternalModuleOrNamespaceExportName(ns: Identifier | undefined, node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier | PropertyAccessExpression { + if (ns && hasSyntacticModifier(node, ModifierFlags.Export)) { + return getNamespaceMemberName(ns, getName(node), allowComments, allowSourceMaps); } + return getExportName(node, allowComments, allowSourceMaps); + } - /** - * Copies any necessary standard and custom prologue-directives into target array. - * @param source origin statements array - * @param target result statements array - * @param ensureUseStrict boolean determining whether the function need to add prologue-directives - * @param visitor Optional callback used to visit any custom prologue directives. - */ - function copyPrologue(source: readonly Statement[], target: Push, ensureUseStrict?: boolean, visitor?: (node: Node) => VisitResult): number { - const offset = copyStandardPrologue(source, target, 0, ensureUseStrict); - return copyCustomPrologue(source, target, offset, visitor); - } + /** + * Copies any necessary standard and custom prologue-directives into target array. + * @param source origin statements array + * @param target result statements array + * @param ensureUseStrict boolean determining whether the function need to add prologue-directives + * @param visitor Optional callback used to visit any custom prologue directives. + */ + function copyPrologue(source: readonly Statement[], target: Push, ensureUseStrict?: boolean, visitor?: (node: Node) => VisitResult): number { + const offset = copyStandardPrologue(source, target, 0, ensureUseStrict); + return copyCustomPrologue(source, target, offset, visitor); + } - function isUseStrictPrologue(node: ExpressionStatement): boolean { - return isStringLiteral(node.expression) && node.expression.text === "use strict"; - } + function isUseStrictPrologue(node: ExpressionStatement): boolean { + return isStringLiteral(node.expression) && node.expression.text === "use strict"; + } - function createUseStrictPrologue() { - return startOnNewLine(createExpressionStatement(createStringLiteral("use strict"))) as PrologueDirective; - } + function createUseStrictPrologue() { + return startOnNewLine(createExpressionStatement(createStringLiteral("use strict"))) as PrologueDirective; + } - /** - * Copies only the standard (string-expression) prologue-directives into the target statement-array. - * @param source origin statements array - * @param target result statements array - * @param statementOffset The offset at which to begin the copy. - * @param ensureUseStrict boolean determining whether the function need to add prologue-directives - * @returns Count of how many directive statements were copied. - */ - function copyStandardPrologue(source: readonly Statement[], target: Push, statementOffset = 0, ensureUseStrict?: boolean): number { - Debug.assert(target.length === 0, "Prologue directives should be at the first statement in the target statements array"); - let foundUseStrict = false; - const numStatements = source.length; - while (statementOffset < numStatements) { - const statement = source[statementOffset]; - if (isPrologueDirective(statement)) { - if (isUseStrictPrologue(statement)) { - foundUseStrict = true; - } - target.push(statement); - } - else { - break; + /** + * Copies only the standard (string-expression) prologue-directives into the target statement-array. + * @param source origin statements array + * @param target result statements array + * @param statementOffset The offset at which to begin the copy. + * @param ensureUseStrict boolean determining whether the function need to add prologue-directives + * @returns Count of how many directive statements were copied. + */ + function copyStandardPrologue(source: readonly Statement[], target: Push, statementOffset = 0, ensureUseStrict?: boolean): number { + Debug.assert(target.length === 0, "Prologue directives should be at the first statement in the target statements array"); + let foundUseStrict = false; + const numStatements = source.length; + while (statementOffset < numStatements) { + const statement = source[statementOffset]; + if (isPrologueDirective(statement)) { + if (isUseStrictPrologue(statement)) { + foundUseStrict = true; } - statementOffset++; + target.push(statement); } - if (ensureUseStrict && !foundUseStrict) { - target.push(createUseStrictPrologue()); + else { + break; } - return statementOffset; + statementOffset++; } + if (ensureUseStrict && !foundUseStrict) { + target.push(createUseStrictPrologue()); + } + return statementOffset; + } - /** - * Copies only the custom prologue-directives into target statement-array. - * @param source origin statements array - * @param target result statements array - * @param statementOffset The offset at which to begin the copy. - * @param visitor Optional callback used to visit any custom prologue directives. - */ - function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number, visitor?: (node: Node) => VisitResult, filter?: (node: Node) => boolean): number; - function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number | undefined, visitor?: (node: Node) => VisitResult, filter?: (node: Node) => boolean): number | undefined; - function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number | undefined, visitor?: (node: Node) => VisitResult, filter: (node: Node) => boolean = returnTrue): number | undefined { - const numStatements = source.length; - while (statementOffset !== undefined && statementOffset < numStatements) { - const statement = source[statementOffset]; - if (getEmitFlags(statement) & EmitFlags.CustomPrologue && filter(statement)) { - append(target, visitor ? visitNode(statement, visitor, isStatement) : statement); - } - else { - break; - } - statementOffset++; + /** + * Copies only the custom prologue-directives into target statement-array. + * @param source origin statements array + * @param target result statements array + * @param statementOffset The offset at which to begin the copy. + * @param visitor Optional callback used to visit any custom prologue directives. + */ + function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number, visitor?: (node: Node) => VisitResult, filter?: (node: Node) => boolean): number; + function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number | undefined, visitor?: (node: Node) => VisitResult, filter?: (node: Node) => boolean): number | undefined; + function copyCustomPrologue(source: readonly Statement[], target: Push, statementOffset: number | undefined, visitor?: (node: Node) => VisitResult, filter: (node: Node) => boolean = returnTrue): number | undefined { + const numStatements = source.length; + while (statementOffset !== undefined && statementOffset < numStatements) { + const statement = source[statementOffset]; + if (getEmitFlags(statement) & EmitFlags.CustomPrologue && filter(statement)) { + append(target, visitor ? visitNode(statement, visitor, isStatement) : statement); } - return statementOffset; + else { + break; + } + statementOffset++; } + return statementOffset; + } - /** - * Ensures "use strict" directive is added - * - * @param statements An array of statements - */ - function ensureUseStrict(statements: NodeArray): NodeArray { - const foundUseStrict = findUseStrictPrologue(statements); - - if (!foundUseStrict) { - return setTextRange(createNodeArray([createUseStrictPrologue(), ...statements]), statements); - } + /** + * Ensures "use strict" directive is added + * + * @param statements An array of statements + */ + function ensureUseStrict(statements: NodeArray): NodeArray { + const foundUseStrict = findUseStrictPrologue(statements); - return statements; + if (!foundUseStrict) { + return setTextRange(createNodeArray([createUseStrictPrologue(), ...statements]), statements); } - /** - * Lifts a NodeArray containing only Statement nodes to a block. - * - * @param nodes The NodeArray. - */ - function liftToBlock(nodes: readonly Node[]): Statement { - Debug.assert(every(nodes, isStatementOrBlock), "Cannot lift nodes to a Block."); - return singleOrUndefined(nodes) as Statement || createBlock(nodes as readonly Statement[]); + return statements; + } + + /** + * Lifts a NodeArray containing only Statement nodes to a block. + * + * @param nodes The NodeArray. + */ + function liftToBlock(nodes: readonly Node[]): Statement { + Debug.assert(every(nodes, isStatementOrBlock), "Cannot lift nodes to a Block."); + return singleOrUndefined(nodes) as Statement || createBlock(nodes as readonly Statement[]); + } + + function findSpanEnd(array: readonly T[], test: (value: T) => boolean, start: number) { + let i = start; + while (i < array.length && test(array[i])) { + i++; } + return i; + } - function findSpanEnd(array: readonly T[], test: (value: T) => boolean, start: number) { - let i = start; - while (i < array.length && test(array[i])) { - i++; - } - return i; + function mergeLexicalEnvironment(statements: NodeArray, declarations: readonly Statement[] | undefined): NodeArray; + function mergeLexicalEnvironment(statements: Statement[], declarations: readonly Statement[] | undefined): Statement[]; + function mergeLexicalEnvironment(statements: Statement[] | NodeArray, declarations: readonly Statement[] | undefined) { + if (!some(declarations)) { + return statements; } - function mergeLexicalEnvironment(statements: NodeArray, declarations: readonly Statement[] | undefined): NodeArray; - function mergeLexicalEnvironment(statements: Statement[], declarations: readonly Statement[] | undefined): Statement[]; - function mergeLexicalEnvironment(statements: Statement[] | NodeArray, declarations: readonly Statement[] | undefined) { - if (!some(declarations)) { - return statements; - } + // When we merge new lexical statements into an existing statement list, we merge them in the following manner: + // + // Given: + // + // | Left | Right | + // |------------------------------------|-------------------------------------| + // | [standard prologues (left)] | [standard prologues (right)] | + // | [hoisted functions (left)] | [hoisted functions (right)] | + // | [hoisted variables (left)] | [hoisted variables (right)] | + // | [lexical init statements (left)] | [lexical init statements (right)] | + // | [other statements (left)] | | + // + // The resulting statement list will be: + // + // | Result | + // |-------------------------------------| + // | [standard prologues (right)] | + // | [standard prologues (left)] | + // | [hoisted functions (right)] | + // | [hoisted functions (left)] | + // | [hoisted variables (right)] | + // | [hoisted variables (left)] | + // | [lexical init statements (right)] | + // | [lexical init statements (left)] | + // | [other statements (left)] | + // + // NOTE: It is expected that new lexical init statements must be evaluated before existing lexical init statements, + // as the prior transformation may depend on the evaluation of the lexical init statements to be in the correct state. - // When we merge new lexical statements into an existing statement list, we merge them in the following manner: - // - // Given: - // - // | Left | Right | - // |------------------------------------|-------------------------------------| - // | [standard prologues (left)] | [standard prologues (right)] | - // | [hoisted functions (left)] | [hoisted functions (right)] | - // | [hoisted variables (left)] | [hoisted variables (right)] | - // | [lexical init statements (left)] | [lexical init statements (right)] | - // | [other statements (left)] | | - // - // The resulting statement list will be: - // - // | Result | - // |-------------------------------------| - // | [standard prologues (right)] | - // | [standard prologues (left)] | - // | [hoisted functions (right)] | - // | [hoisted functions (left)] | - // | [hoisted variables (right)] | - // | [hoisted variables (left)] | - // | [lexical init statements (right)] | - // | [lexical init statements (left)] | - // | [other statements (left)] | - // - // NOTE: It is expected that new lexical init statements must be evaluated before existing lexical init statements, - // as the prior transformation may depend on the evaluation of the lexical init statements to be in the correct state. - - // find standard prologues on left in the following order: standard directives, hoisted functions, hoisted variables, other custom - const leftStandardPrologueEnd = findSpanEnd(statements, isPrologueDirective, 0); - const leftHoistedFunctionsEnd = findSpanEnd(statements, isHoistedFunction, leftStandardPrologueEnd); - const leftHoistedVariablesEnd = findSpanEnd(statements, isHoistedVariableStatement, leftHoistedFunctionsEnd); - - // find standard prologues on right in the following order: standard directives, hoisted functions, hoisted variables, other custom - const rightStandardPrologueEnd = findSpanEnd(declarations, isPrologueDirective, 0); - const rightHoistedFunctionsEnd = findSpanEnd(declarations, isHoistedFunction, rightStandardPrologueEnd); - const rightHoistedVariablesEnd = findSpanEnd(declarations, isHoistedVariableStatement, rightHoistedFunctionsEnd); - const rightCustomPrologueEnd = findSpanEnd(declarations, isCustomPrologue, rightHoistedVariablesEnd); - Debug.assert(rightCustomPrologueEnd === declarations.length, "Expected declarations to be valid standard or custom prologues"); - - // splice prologues from the right into the left. We do this in reverse order - // so that we don't need to recompute the index on the left when we insert items. - const left = isNodeArray(statements) ? statements.slice() : statements; - - // splice other custom prologues from right into left - if (rightCustomPrologueEnd > rightHoistedVariablesEnd) { - left.splice(leftHoistedVariablesEnd, 0, ...declarations.slice(rightHoistedVariablesEnd, rightCustomPrologueEnd)); - } + // find standard prologues on left in the following order: standard directives, hoisted functions, hoisted variables, other custom + const leftStandardPrologueEnd = findSpanEnd(statements, isPrologueDirective, 0); + const leftHoistedFunctionsEnd = findSpanEnd(statements, isHoistedFunction, leftStandardPrologueEnd); + const leftHoistedVariablesEnd = findSpanEnd(statements, isHoistedVariableStatement, leftHoistedFunctionsEnd); - // splice hoisted variables from right into left - if (rightHoistedVariablesEnd > rightHoistedFunctionsEnd) { - left.splice(leftHoistedFunctionsEnd, 0, ...declarations.slice(rightHoistedFunctionsEnd, rightHoistedVariablesEnd)); - } + // find standard prologues on right in the following order: standard directives, hoisted functions, hoisted variables, other custom + const rightStandardPrologueEnd = findSpanEnd(declarations, isPrologueDirective, 0); + const rightHoistedFunctionsEnd = findSpanEnd(declarations, isHoistedFunction, rightStandardPrologueEnd); + const rightHoistedVariablesEnd = findSpanEnd(declarations, isHoistedVariableStatement, rightHoistedFunctionsEnd); + const rightCustomPrologueEnd = findSpanEnd(declarations, isCustomPrologue, rightHoistedVariablesEnd); + Debug.assert(rightCustomPrologueEnd === declarations.length, "Expected declarations to be valid standard or custom prologues"); - // splice hoisted functions from right into left - if (rightHoistedFunctionsEnd > rightStandardPrologueEnd) { - left.splice(leftStandardPrologueEnd, 0, ...declarations.slice(rightStandardPrologueEnd, rightHoistedFunctionsEnd)); - } + // splice prologues from the right into the left. We do this in reverse order + // so that we don't need to recompute the index on the left when we insert items. + const left = isNodeArray(statements) ? statements.slice() : statements; - // splice standard prologues from right into left (that are not already in left) - if (rightStandardPrologueEnd > 0) { - if (leftStandardPrologueEnd === 0) { - left.splice(0, 0, ...declarations.slice(0, rightStandardPrologueEnd)); - } - else { - const leftPrologues = new Map(); - for (let i = 0; i < leftStandardPrologueEnd; i++) { - const leftPrologue = statements[i] as PrologueDirective; - leftPrologues.set(leftPrologue.expression.text, true); - } - for (let i = rightStandardPrologueEnd - 1; i >= 0; i--) { - const rightPrologue = declarations[i] as PrologueDirective; - if (!leftPrologues.has(rightPrologue.expression.text)) { - left.unshift(rightPrologue); - } - } - } - } + // splice other custom prologues from right into left + if (rightCustomPrologueEnd > rightHoistedVariablesEnd) { + left.splice(leftHoistedVariablesEnd, 0, ...declarations.slice(rightHoistedVariablesEnd, rightCustomPrologueEnd)); + } - if (isNodeArray(statements)) { - return setTextRange(createNodeArray(left, statements.hasTrailingComma), statements); - } + // splice hoisted variables from right into left + if (rightHoistedVariablesEnd > rightHoistedFunctionsEnd) { + left.splice(leftHoistedFunctionsEnd, 0, ...declarations.slice(rightHoistedFunctionsEnd, rightHoistedVariablesEnd)); + } - return statements; + // splice hoisted functions from right into left + if (rightHoistedFunctionsEnd > rightStandardPrologueEnd) { + left.splice(leftStandardPrologueEnd, 0, ...declarations.slice(rightStandardPrologueEnd, rightHoistedFunctionsEnd)); } - function updateModifiers(node: T, modifiers: readonly Modifier[] | ModifierFlags): T; - function updateModifiers(node: HasModifiers, modifiers: readonly Modifier[] | ModifierFlags) { - let modifierArray; - if (typeof modifiers === "number") { - modifierArray = createModifiersFromModifierFlags(modifiers); + // splice standard prologues from right into left (that are not already in left) + if (rightStandardPrologueEnd > 0) { + if (leftStandardPrologueEnd === 0) { + left.splice(0, 0, ...declarations.slice(0, rightStandardPrologueEnd)); } else { - modifierArray = modifiers; + const leftPrologues = new Map(); + for (let i = 0; i < leftStandardPrologueEnd; i++) { + const leftPrologue = statements[i] as PrologueDirective; + leftPrologues.set(leftPrologue.expression.text, true); + } + for (let i = rightStandardPrologueEnd - 1; i >= 0; i--) { + const rightPrologue = declarations[i] as PrologueDirective; + if (!leftPrologues.has(rightPrologue.expression.text)) { + left.unshift(rightPrologue); + } + } } - return isTypeParameterDeclaration(node) ? updateTypeParameterDeclaration(node, modifierArray, node.name, node.constraint, node.default) : - isParameter(node) ? updateParameterDeclaration(node, modifierArray, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer) : - isConstructorTypeNode(node) ? updateConstructorTypeNode1(node, modifierArray, node.typeParameters, node.parameters, node.type) : - isPropertySignature(node) ? updatePropertySignature(node, modifierArray, node.name, node.questionToken, node.type) : - isPropertyDeclaration(node) ? updatePropertyDeclaration(node, modifierArray, node.name, node.questionToken ?? node.exclamationToken, node.type, node.initializer) : - isMethodSignature(node) ? updateMethodSignature(node, modifierArray, node.name, node.questionToken, node.typeParameters, node.parameters, node.type) : - isMethodDeclaration(node) ? updateMethodDeclaration(node, modifierArray, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body) : - isConstructorDeclaration(node) ? updateConstructorDeclaration(node, modifierArray, node.parameters, node.body) : - isGetAccessorDeclaration(node) ? updateGetAccessorDeclaration(node, modifierArray, node.name, node.parameters, node.type, node.body) : - isSetAccessorDeclaration(node) ? updateSetAccessorDeclaration(node, modifierArray, node.name, node.parameters, node.body) : - isIndexSignatureDeclaration(node) ? updateIndexSignature(node, modifierArray, node.parameters, node.type) : - isFunctionExpression(node) ? updateFunctionExpression(node, modifierArray, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : - isArrowFunction(node) ? updateArrowFunction(node, modifierArray, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, node.body) : - isClassExpression(node) ? updateClassExpression(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : - isVariableStatement(node) ? updateVariableStatement(node, modifierArray, node.declarationList) : - isFunctionDeclaration(node) ? updateFunctionDeclaration(node, modifierArray, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : - isClassDeclaration(node) ? updateClassDeclaration(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : - isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : - isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, modifierArray, node.name, node.typeParameters, node.type) : - isEnumDeclaration(node) ? updateEnumDeclaration(node, modifierArray, node.name, node.members) : - isModuleDeclaration(node) ? updateModuleDeclaration(node, modifierArray, node.name, node.body) : - isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, modifierArray, node.isTypeOnly, node.name, node.moduleReference) : - isImportDeclaration(node) ? updateImportDeclaration(node, modifierArray, node.importClause, node.moduleSpecifier, node.assertClause) : - isExportAssignment(node) ? updateExportAssignment(node, modifierArray, node.expression) : - isExportDeclaration(node) ? updateExportDeclaration(node, modifierArray, node.isTypeOnly, node.exportClause, node.moduleSpecifier, node.assertClause) : - Debug.assertNever(node); } - function asNodeArray(array: readonly T[]): NodeArray; - function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined; - function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined { - return array ? createNodeArray(array) : undefined; + if (isNodeArray(statements)) { + return setTextRange(createNodeArray(left, statements.hasTrailingComma), statements); } - function asName(name: string | T): T | Identifier { - return typeof name === "string" ? createIdentifier(name) : - name; - } + return statements; + } - function asExpression(value: string | number | boolean | T): T | StringLiteral | NumericLiteral | BooleanLiteral { - return typeof value === "string" ? createStringLiteral(value) : - typeof value === "number" ? createNumericLiteral(value) : - typeof value === "boolean" ? value ? createTrue() : createFalse() : - value; + function updateModifiers(node: T, modifiers: readonly Modifier[] | ModifierFlags): T; + function updateModifiers(node: HasModifiers, modifiers: readonly Modifier[] | ModifierFlags) { + let modifierArray; + if (typeof modifiers === "number") { + modifierArray = createModifiersFromModifierFlags(modifiers); } + else { + modifierArray = modifiers; + } + return isTypeParameterDeclaration(node) ? updateTypeParameterDeclaration(node, modifierArray, node.name, node.constraint, node.default) : + isParameter(node) ? updateParameterDeclaration(node, modifierArray, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer) : + isConstructorTypeNode(node) ? updateConstructorTypeNode1(node, modifierArray, node.typeParameters, node.parameters, node.type) : + isPropertySignature(node) ? updatePropertySignature(node, modifierArray, node.name, node.questionToken, node.type) : + isPropertyDeclaration(node) ? updatePropertyDeclaration(node, modifierArray, node.name, node.questionToken ?? node.exclamationToken, node.type, node.initializer) : + isMethodSignature(node) ? updateMethodSignature(node, modifierArray, node.name, node.questionToken, node.typeParameters, node.parameters, node.type) : + isMethodDeclaration(node) ? updateMethodDeclaration(node, modifierArray, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body) : + isConstructorDeclaration(node) ? updateConstructorDeclaration(node, modifierArray, node.parameters, node.body) : + isGetAccessorDeclaration(node) ? updateGetAccessorDeclaration(node, modifierArray, node.name, node.parameters, node.type, node.body) : + isSetAccessorDeclaration(node) ? updateSetAccessorDeclaration(node, modifierArray, node.name, node.parameters, node.body) : + isIndexSignatureDeclaration(node) ? updateIndexSignature(node, modifierArray, node.parameters, node.type) : + isFunctionExpression(node) ? updateFunctionExpression(node, modifierArray, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : + isArrowFunction(node) ? updateArrowFunction(node, modifierArray, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, node.body) : + isClassExpression(node) ? updateClassExpression(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : + isVariableStatement(node) ? updateVariableStatement(node, modifierArray, node.declarationList) : + isFunctionDeclaration(node) ? updateFunctionDeclaration(node, modifierArray, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : + isClassDeclaration(node) ? updateClassDeclaration(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : + isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : + isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, modifierArray, node.name, node.typeParameters, node.type) : + isEnumDeclaration(node) ? updateEnumDeclaration(node, modifierArray, node.name, node.members) : + isModuleDeclaration(node) ? updateModuleDeclaration(node, modifierArray, node.name, node.body) : + isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, modifierArray, node.isTypeOnly, node.name, node.moduleReference) : + isImportDeclaration(node) ? updateImportDeclaration(node, modifierArray, node.importClause, node.moduleSpecifier, node.assertClause) : + isExportAssignment(node) ? updateExportAssignment(node, modifierArray, node.expression) : + isExportDeclaration(node) ? updateExportDeclaration(node, modifierArray, node.isTypeOnly, node.exportClause, node.moduleSpecifier, node.assertClause) : + Debug.assertNever(node); + } - function asToken(value: TKind | Token): Token { - return typeof value === "number" ? createToken(value) : value; - } + function asNodeArray(array: readonly T[]): NodeArray; + function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined; + function asNodeArray(array: readonly T[] | undefined): NodeArray | undefined { + return array ? createNodeArray(array) : undefined; + } - 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; - } + function asName(name: string | T): T | Identifier { + return typeof name === "string" ? createIdentifier(name) : + name; } - function updateWithoutOriginal(updated: Mutable, original: T): T { - if (updated !== original) { - setTextRange(updated, original); - } - return updated; + function asExpression(value: string | number | boolean | T): T | StringLiteral | NumericLiteral | BooleanLiteral { + return typeof value === "string" ? createStringLiteral(value) : + typeof value === "number" ? createNumericLiteral(value) : + typeof value === "boolean" ? value ? createTrue() : createFalse() : + value; } - function updateWithOriginal(updated: Mutable, original: T): T { - if (updated !== original) { - setOriginalNode(updated, original); - setTextRange(updated, original); - } - return updated; + function asToken(value: TKind | Token): Token { + return typeof value === "number" ? createToken(value) : value; } - function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string { - switch (kind) { - case SyntaxKind.JSDocTypeTag: return "type"; - case SyntaxKind.JSDocReturnTag: return "returns"; - case SyntaxKind.JSDocThisTag: return "this"; - case SyntaxKind.JSDocEnumTag: return "enum"; - case SyntaxKind.JSDocAuthorTag: return "author"; - case SyntaxKind.JSDocClassTag: return "class"; - case SyntaxKind.JSDocPublicTag: return "public"; - case SyntaxKind.JSDocPrivateTag: return "private"; - case SyntaxKind.JSDocProtectedTag: return "protected"; - case SyntaxKind.JSDocReadonlyTag: return "readonly"; - case SyntaxKind.JSDocOverrideTag: return "override"; - case SyntaxKind.JSDocTemplateTag: return "template"; - case SyntaxKind.JSDocTypedefTag: return "typedef"; - case SyntaxKind.JSDocParameterTag: return "param"; - case SyntaxKind.JSDocPropertyTag: return "prop"; - case SyntaxKind.JSDocCallbackTag: return "callback"; - case SyntaxKind.JSDocAugmentsTag: return "augments"; - case SyntaxKind.JSDocImplementsTag: return "implements"; - default: - return Debug.fail(`Unsupported kind: ${Debug.formatSyntaxKind(kind)}`); - } + 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; } +} - let rawTextScanner: Scanner | undefined; - const invalidValueSentinel: object = { }; +function updateWithoutOriginal(updated: Mutable, original: T): T { + if (updated !== original) { + setTextRange(updated, original); + } + return updated; +} - function getCookedText(kind: TemplateLiteralToken["kind"], rawText: string) { - if (!rawTextScanner) { - rawTextScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); - } - switch (kind) { - case SyntaxKind.NoSubstitutionTemplateLiteral: - rawTextScanner.setText("`" + rawText + "`"); - break; - case SyntaxKind.TemplateHead: - rawTextScanner.setText("`" + rawText + "${"); - break; - case SyntaxKind.TemplateMiddle: - rawTextScanner.setText("}" + rawText + "${"); - break; - case SyntaxKind.TemplateTail: - rawTextScanner.setText("}" + rawText + "`"); - break; - } +function updateWithOriginal(updated: Mutable, original: T): T { + if (updated !== original) { + setOriginalNode(updated, original); + setTextRange(updated, original); + } + return updated; +} - let token = rawTextScanner.scan(); - if (token === SyntaxKind.CloseBraceToken) { - token = rawTextScanner.reScanTemplateToken(/*isTaggedTemplate*/ false); - } +function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string { + switch (kind) { + case SyntaxKind.JSDocTypeTag: return "type"; + case SyntaxKind.JSDocReturnTag: return "returns"; + case SyntaxKind.JSDocThisTag: return "this"; + case SyntaxKind.JSDocEnumTag: return "enum"; + case SyntaxKind.JSDocAuthorTag: return "author"; + case SyntaxKind.JSDocClassTag: return "class"; + case SyntaxKind.JSDocPublicTag: return "public"; + case SyntaxKind.JSDocPrivateTag: return "private"; + case SyntaxKind.JSDocProtectedTag: return "protected"; + case SyntaxKind.JSDocReadonlyTag: return "readonly"; + case SyntaxKind.JSDocOverrideTag: return "override"; + case SyntaxKind.JSDocTemplateTag: return "template"; + case SyntaxKind.JSDocTypedefTag: return "typedef"; + case SyntaxKind.JSDocParameterTag: return "param"; + case SyntaxKind.JSDocPropertyTag: return "prop"; + case SyntaxKind.JSDocCallbackTag: return "callback"; + case SyntaxKind.JSDocAugmentsTag: return "augments"; + case SyntaxKind.JSDocImplementsTag: return "implements"; + default: + return Debug.fail(`Unsupported kind: ${Debug.formatSyntaxKind(kind)}`); + } +} - if (rawTextScanner.isUnterminated()) { - rawTextScanner.setText(undefined); - return invalidValueSentinel; - } +let rawTextScanner: Scanner | undefined; +const invalidValueSentinel: object = { }; - let tokenValue: string | undefined; - switch (token) { - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - tokenValue = rawTextScanner.getTokenValue(); - break; - } +function getCookedText(kind: TemplateLiteralToken["kind"], rawText: string) { + if (!rawTextScanner) { + rawTextScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); + } + switch (kind) { + case SyntaxKind.NoSubstitutionTemplateLiteral: + rawTextScanner.setText("`" + rawText + "`"); + break; + case SyntaxKind.TemplateHead: + rawTextScanner.setText("`" + rawText + "${"); + break; + case SyntaxKind.TemplateMiddle: + rawTextScanner.setText("}" + rawText + "${"); + break; + case SyntaxKind.TemplateTail: + rawTextScanner.setText("}" + rawText + "`"); + break; + } - if (tokenValue === undefined || rawTextScanner.scan() !== SyntaxKind.EndOfFileToken) { - rawTextScanner.setText(undefined); - return invalidValueSentinel; - } + let token = rawTextScanner.scan(); + if (token === SyntaxKind.CloseBraceToken) { + token = rawTextScanner.reScanTemplateToken(/*isTaggedTemplate*/ false); + } + if (rawTextScanner.isUnterminated()) { rawTextScanner.setText(undefined); - return tokenValue; + return invalidValueSentinel; } - function propagateIdentifierNameFlags(node: Identifier) { - // An IdentifierName is allowed to be `await` - return propagateChildFlags(node) & ~TransformFlags.ContainsPossibleTopLevelAwait; + let tokenValue: string | undefined; + switch (token) { + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + tokenValue = rawTextScanner.getTokenValue(); + break; } - function propagatePropertyNameFlagsOfChild(node: PropertyName, transformFlags: TransformFlags) { - return transformFlags | (node.transformFlags & TransformFlags.PropertyNamePropagatingFlags); + if (tokenValue === undefined || rawTextScanner.scan() !== SyntaxKind.EndOfFileToken) { + rawTextScanner.setText(undefined); + return invalidValueSentinel; } - function propagateChildFlags(child: Node | undefined): TransformFlags { - if (!child) return TransformFlags.None; - const childFlags = child.transformFlags & ~getTransformFlagsSubtreeExclusions(child.kind); - return isNamedDeclaration(child) && isPropertyName(child.name) ? propagatePropertyNameFlagsOfChild(child.name, childFlags) : childFlags; - } + rawTextScanner.setText(undefined); + return tokenValue; +} - function propagateChildrenFlags(children: NodeArray | undefined): TransformFlags { - return children ? children.transformFlags : TransformFlags.None; - } +function propagateIdentifierNameFlags(node: Identifier) { + // An IdentifierName is allowed to be `await` + return propagateChildFlags(node) & ~TransformFlags.ContainsPossibleTopLevelAwait; +} - function aggregateChildrenFlags(children: MutableNodeArray) { - let subtreeFlags = TransformFlags.None; - for (const child of children) { - subtreeFlags |= propagateChildFlags(child); - } - children.transformFlags = subtreeFlags; - } +function propagatePropertyNameFlagsOfChild(node: PropertyName, transformFlags: TransformFlags) { + return transformFlags | (node.transformFlags & TransformFlags.PropertyNamePropagatingFlags); +} - /** - * Gets the transform flags to exclude when unioning the transform flags of a subtree. - */ - /* @internal */ - export function getTransformFlagsSubtreeExclusions(kind: SyntaxKind) { - if (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) { - return TransformFlags.TypeExcludes; - } +function propagateChildFlags(child: Node | undefined): TransformFlags { + if (!child) return TransformFlags.None; + const childFlags = child.transformFlags & ~getTransformFlagsSubtreeExclusions(child.kind); + return isNamedDeclaration(child) && isPropertyName(child.name) ? propagatePropertyNameFlagsOfChild(child.name, childFlags) : childFlags; +} - switch (kind) { - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.ArrayLiteralExpression: - return TransformFlags.ArrayLiteralOrCallOrNewExcludes; - case SyntaxKind.ModuleDeclaration: - return TransformFlags.ModuleExcludes; - case SyntaxKind.Parameter: - return TransformFlags.ParameterExcludes; - case SyntaxKind.ArrowFunction: - return TransformFlags.ArrowFunctionExcludes; - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - return TransformFlags.FunctionExcludes; - case SyntaxKind.VariableDeclarationList: - return TransformFlags.VariableDeclarationListExcludes; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return TransformFlags.ClassExcludes; - case SyntaxKind.Constructor: - return TransformFlags.ConstructorExcludes; - case SyntaxKind.PropertyDeclaration: - return TransformFlags.PropertyExcludes; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return TransformFlags.MethodOrAccessorExcludes; - case SyntaxKind.AnyKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.TypeParameter: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - return TransformFlags.TypeExcludes; - case SyntaxKind.ObjectLiteralExpression: - return TransformFlags.ObjectLiteralExcludes; - case SyntaxKind.CatchClause: - return TransformFlags.CatchClauseExcludes; - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - return TransformFlags.BindingPatternExcludes; - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.SatisfiesExpression: - case SyntaxKind.AsExpression: - case SyntaxKind.PartiallyEmittedExpression: - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.SuperKeyword: - return TransformFlags.OuterExpressionExcludes; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return TransformFlags.PropertyAccessExcludes; - default: - return TransformFlags.NodeExcludes; - } +function propagateChildrenFlags(children: NodeArray | undefined): TransformFlags { + return children ? children.transformFlags : TransformFlags.None; +} + +function aggregateChildrenFlags(children: MutableNodeArray) { + let subtreeFlags = TransformFlags.None; + for (const child of children) { + subtreeFlags |= propagateChildFlags(child); } + children.transformFlags = subtreeFlags; +} - const baseFactory = createBaseNodeFactory(); +/** + * Gets the transform flags to exclude when unioning the transform flags of a subtree. + * + * @internal + */ +export function getTransformFlagsSubtreeExclusions(kind: SyntaxKind) { + if (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) { + return TransformFlags.TypeExcludes; + } - function makeSynthetic(node: Node) { - (node as Mutable).flags |= NodeFlags.Synthesized; - return node; + switch (kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.ArrayLiteralExpression: + return TransformFlags.ArrayLiteralOrCallOrNewExcludes; + case SyntaxKind.ModuleDeclaration: + return TransformFlags.ModuleExcludes; + case SyntaxKind.Parameter: + return TransformFlags.ParameterExcludes; + case SyntaxKind.ArrowFunction: + return TransformFlags.ArrowFunctionExcludes; + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + return TransformFlags.FunctionExcludes; + case SyntaxKind.VariableDeclarationList: + return TransformFlags.VariableDeclarationListExcludes; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return TransformFlags.ClassExcludes; + case SyntaxKind.Constructor: + return TransformFlags.ConstructorExcludes; + case SyntaxKind.PropertyDeclaration: + return TransformFlags.PropertyExcludes; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return TransformFlags.MethodOrAccessorExcludes; + case SyntaxKind.AnyKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.TypeParameter: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return TransformFlags.TypeExcludes; + case SyntaxKind.ObjectLiteralExpression: + return TransformFlags.ObjectLiteralExcludes; + case SyntaxKind.CatchClause: + return TransformFlags.CatchClauseExcludes; + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + return TransformFlags.BindingPatternExcludes; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.SatisfiesExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.PartiallyEmittedExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.SuperKeyword: + return TransformFlags.OuterExpressionExcludes; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return TransformFlags.PropertyAccessExcludes; + default: + return TransformFlags.NodeExcludes; } +} - const syntheticFactory: BaseNodeFactory = { - createBaseSourceFileNode: kind => makeSynthetic(baseFactory.createBaseSourceFileNode(kind)), - createBaseIdentifierNode: kind => makeSynthetic(baseFactory.createBaseIdentifierNode(kind)), - createBasePrivateIdentifierNode: kind => makeSynthetic(baseFactory.createBasePrivateIdentifierNode(kind)), - createBaseTokenNode: kind => makeSynthetic(baseFactory.createBaseTokenNode(kind)), - createBaseNode: kind => makeSynthetic(baseFactory.createBaseNode(kind)), - }; +const baseFactory = createBaseNodeFactory(); - export const factory = createNodeFactory(NodeFactoryFlags.NoIndentationOnFreshPropertyAccess, syntheticFactory); - - export function createUnparsedSourceFile(text: string): UnparsedSource; - export function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts", stripInternal?: boolean): UnparsedSource; - export function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource; - export function createUnparsedSourceFile(textOrInputFiles: string | InputFiles, mapPathOrType?: string, mapTextOrStripInternal?: string | boolean): UnparsedSource { - let stripInternal: boolean | undefined; - let bundleFileInfo: BundleFileInfo | undefined; - let fileName: string; - let text: string | undefined; - let length: number | (() => number); - let sourceMapPath: string | undefined; - let sourceMapText: string | undefined; - let getText: (() => string) | undefined; - let getSourceMapText: (() => string | undefined) | undefined; - let oldFileOfCurrentEmit: boolean | undefined; - - if (!isString(textOrInputFiles)) { - Debug.assert(mapPathOrType === "js" || mapPathOrType === "dts"); - fileName = (mapPathOrType === "js" ? textOrInputFiles.javascriptPath : textOrInputFiles.declarationPath) || ""; - sourceMapPath = mapPathOrType === "js" ? textOrInputFiles.javascriptMapPath : textOrInputFiles.declarationMapPath; - getText = () => mapPathOrType === "js" ? textOrInputFiles.javascriptText : textOrInputFiles.declarationText; - getSourceMapText = () => mapPathOrType === "js" ? textOrInputFiles.javascriptMapText : textOrInputFiles.declarationMapText; - length = () => getText!().length; - if (textOrInputFiles.buildInfo && textOrInputFiles.buildInfo.bundle) { - Debug.assert(mapTextOrStripInternal === undefined || typeof mapTextOrStripInternal === "boolean"); - stripInternal = mapTextOrStripInternal; - bundleFileInfo = mapPathOrType === "js" ? textOrInputFiles.buildInfo.bundle.js : textOrInputFiles.buildInfo.bundle.dts; - oldFileOfCurrentEmit = textOrInputFiles.oldFileOfCurrentEmit; - } - } - else { - fileName = ""; - text = textOrInputFiles; - length = textOrInputFiles.length; - sourceMapPath = mapPathOrType; - sourceMapText = mapTextOrStripInternal as string; - } - const node = oldFileOfCurrentEmit ? - parseOldFileOfCurrentEmit(Debug.checkDefined(bundleFileInfo)) : - parseUnparsedSourceFile(bundleFileInfo, stripInternal, length); - node.fileName = fileName; - node.sourceMapPath = sourceMapPath; - node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; - if (getText && getSourceMapText) { - Object.defineProperty(node, "text", { get: getText }); - Object.defineProperty(node, "sourceMapText", { get: getSourceMapText }); - } - else { - Debug.assert(!oldFileOfCurrentEmit); - node.text = text ?? ""; - node.sourceMapText = sourceMapText; - } +function makeSynthetic(node: Node) { + (node as Mutable).flags |= NodeFlags.Synthesized; + return node; +} - return node; +const syntheticFactory: BaseNodeFactory = { + createBaseSourceFileNode: kind => makeSynthetic(baseFactory.createBaseSourceFileNode(kind)), + createBaseIdentifierNode: kind => makeSynthetic(baseFactory.createBaseIdentifierNode(kind)), + createBasePrivateIdentifierNode: kind => makeSynthetic(baseFactory.createBasePrivateIdentifierNode(kind)), + createBaseTokenNode: kind => makeSynthetic(baseFactory.createBaseTokenNode(kind)), + createBaseNode: kind => makeSynthetic(baseFactory.createBaseNode(kind)), +}; + +export const factory = createNodeFactory(NodeFactoryFlags.NoIndentationOnFreshPropertyAccess, syntheticFactory); + +export function createUnparsedSourceFile(text: string): UnparsedSource; +export function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts", stripInternal?: boolean): UnparsedSource; +export function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource; +export function createUnparsedSourceFile(textOrInputFiles: string | InputFiles, mapPathOrType?: string, mapTextOrStripInternal?: string | boolean): UnparsedSource { + let stripInternal: boolean | undefined; + let bundleFileInfo: BundleFileInfo | undefined; + let fileName: string; + let text: string | undefined; + let length: number | (() => number); + let sourceMapPath: string | undefined; + let sourceMapText: string | undefined; + let getText: (() => string) | undefined; + let getSourceMapText: (() => string | undefined) | undefined; + let oldFileOfCurrentEmit: boolean | undefined; + + if (!isString(textOrInputFiles)) { + Debug.assert(mapPathOrType === "js" || mapPathOrType === "dts"); + fileName = (mapPathOrType === "js" ? textOrInputFiles.javascriptPath : textOrInputFiles.declarationPath) || ""; + sourceMapPath = mapPathOrType === "js" ? textOrInputFiles.javascriptMapPath : textOrInputFiles.declarationMapPath; + getText = () => mapPathOrType === "js" ? textOrInputFiles.javascriptText : textOrInputFiles.declarationText; + getSourceMapText = () => mapPathOrType === "js" ? textOrInputFiles.javascriptMapText : textOrInputFiles.declarationMapText; + length = () => getText!().length; + if (textOrInputFiles.buildInfo && textOrInputFiles.buildInfo.bundle) { + Debug.assert(mapTextOrStripInternal === undefined || typeof mapTextOrStripInternal === "boolean"); + stripInternal = mapTextOrStripInternal; + bundleFileInfo = mapPathOrType === "js" ? textOrInputFiles.buildInfo.bundle.js : textOrInputFiles.buildInfo.bundle.dts; + oldFileOfCurrentEmit = textOrInputFiles.oldFileOfCurrentEmit; + } + } + else { + fileName = ""; + text = textOrInputFiles; + length = textOrInputFiles.length; + sourceMapPath = mapPathOrType; + sourceMapText = mapTextOrStripInternal as string; + } + const node = oldFileOfCurrentEmit ? + parseOldFileOfCurrentEmit(Debug.checkDefined(bundleFileInfo)) : + parseUnparsedSourceFile(bundleFileInfo, stripInternal, length); + node.fileName = fileName; + node.sourceMapPath = sourceMapPath; + node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; + if (getText && getSourceMapText) { + Object.defineProperty(node, "text", { get: getText }); + Object.defineProperty(node, "sourceMapText", { get: getSourceMapText }); + } + else { + Debug.assert(!oldFileOfCurrentEmit); + node.text = text ?? ""; + node.sourceMapText = sourceMapText; } - function parseUnparsedSourceFile(bundleFileInfo: BundleFileInfo | undefined, stripInternal: boolean | undefined, length: number | (() => number)) { - let prologues: UnparsedPrologue[] | undefined; - let helpers: UnscopedEmitHelper[] | undefined; - let referencedFiles: FileReference[] | undefined; - let typeReferenceDirectives: FileReference[] | undefined; - let libReferenceDirectives: FileReference[] | undefined; - let prependChildren: UnparsedTextLike[] | undefined; - let texts: UnparsedSourceText[] | undefined; - let hasNoDefaultLib: boolean | undefined; + return node; +} - for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) { - switch (section.kind) { - case BundleFileSectionKind.Prologue: - prologues = append(prologues, setTextRange(factory.createUnparsedPrologue(section.data), section)); - break; - case BundleFileSectionKind.EmitHelpers: - helpers = append(helpers, getAllUnscopedEmitHelpers().get(section.data)!); - break; - case BundleFileSectionKind.NoDefaultLib: - hasNoDefaultLib = true; - break; - case BundleFileSectionKind.Reference: - referencedFiles = append(referencedFiles, { pos: -1, end: -1, fileName: section.data }); - break; - case BundleFileSectionKind.Type: - typeReferenceDirectives = append(typeReferenceDirectives, { pos: -1, end: -1, fileName: section.data }); - break; - case BundleFileSectionKind.TypeResolutionModeImport: - typeReferenceDirectives = append(typeReferenceDirectives, { pos: -1, end: -1, fileName: section.data, resolutionMode: ModuleKind.ESNext }); - break; - case BundleFileSectionKind.TypeResolutionModeRequire: - typeReferenceDirectives = append(typeReferenceDirectives, { pos: -1, end: -1, fileName: section.data, resolutionMode: ModuleKind.CommonJS }); - break; - case BundleFileSectionKind.Lib: - libReferenceDirectives = append(libReferenceDirectives, { pos: -1, end: -1, fileName: section.data }); - break; - case BundleFileSectionKind.Prepend: - let prependTexts: UnparsedTextLike[] | undefined; - for (const text of section.texts) { - if (!stripInternal || text.kind !== BundleFileSectionKind.Internal) { - prependTexts = append(prependTexts, setTextRange(factory.createUnparsedTextLike(text.data, text.kind === BundleFileSectionKind.Internal), text)); - } - } - prependChildren = addRange(prependChildren, prependTexts); - texts = append(texts, factory.createUnparsedPrepend(section.data, prependTexts ?? emptyArray)); - break; - case BundleFileSectionKind.Internal: - if (stripInternal) { - if (!texts) texts = []; - break; +function parseUnparsedSourceFile(bundleFileInfo: BundleFileInfo | undefined, stripInternal: boolean | undefined, length: number | (() => number)) { + let prologues: UnparsedPrologue[] | undefined; + let helpers: UnscopedEmitHelper[] | undefined; + let referencedFiles: FileReference[] | undefined; + let typeReferenceDirectives: FileReference[] | undefined; + let libReferenceDirectives: FileReference[] | undefined; + let prependChildren: UnparsedTextLike[] | undefined; + let texts: UnparsedSourceText[] | undefined; + let hasNoDefaultLib: boolean | undefined; + + for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) { + switch (section.kind) { + case BundleFileSectionKind.Prologue: + prologues = append(prologues, setTextRange(factory.createUnparsedPrologue(section.data), section)); + break; + case BundleFileSectionKind.EmitHelpers: + helpers = append(helpers, getAllUnscopedEmitHelpers().get(section.data)!); + break; + case BundleFileSectionKind.NoDefaultLib: + hasNoDefaultLib = true; + break; + case BundleFileSectionKind.Reference: + referencedFiles = append(referencedFiles, { pos: -1, end: -1, fileName: section.data }); + break; + case BundleFileSectionKind.Type: + typeReferenceDirectives = append(typeReferenceDirectives, { pos: -1, end: -1, fileName: section.data }); + break; + case BundleFileSectionKind.TypeResolutionModeImport: + typeReferenceDirectives = append(typeReferenceDirectives, { pos: -1, end: -1, fileName: section.data, resolutionMode: ModuleKind.ESNext }); + break; + case BundleFileSectionKind.TypeResolutionModeRequire: + typeReferenceDirectives = append(typeReferenceDirectives, { pos: -1, end: -1, fileName: section.data, resolutionMode: ModuleKind.CommonJS }); + break; + case BundleFileSectionKind.Lib: + libReferenceDirectives = append(libReferenceDirectives, { pos: -1, end: -1, fileName: section.data }); + break; + case BundleFileSectionKind.Prepend: + let prependTexts: UnparsedTextLike[] | undefined; + for (const text of section.texts) { + if (!stripInternal || text.kind !== BundleFileSectionKind.Internal) { + prependTexts = append(prependTexts, setTextRange(factory.createUnparsedTextLike(text.data, text.kind === BundleFileSectionKind.Internal), text)); } - // falls through - - case BundleFileSectionKind.Text: - texts = append(texts, setTextRange(factory.createUnparsedTextLike(section.data, section.kind === BundleFileSectionKind.Internal), section)); + } + prependChildren = addRange(prependChildren, prependTexts); + texts = append(texts, factory.createUnparsedPrepend(section.data, prependTexts ?? emptyArray)); + break; + case BundleFileSectionKind.Internal: + if (stripInternal) { + if (!texts) texts = []; break; - default: - Debug.assertNever(section); - } - } + } + // falls through - if (!texts) { - const textNode = factory.createUnparsedTextLike(/*data*/ undefined, /*internal*/ false); - setTextRangePosWidth(textNode, 0, typeof length === "function" ? length() : length); - texts = [textNode]; + case BundleFileSectionKind.Text: + texts = append(texts, setTextRange(factory.createUnparsedTextLike(section.data, section.kind === BundleFileSectionKind.Internal), section)); + break; + default: + Debug.assertNever(section); } + } - const node = parseNodeFactory.createUnparsedSource(prologues ?? emptyArray, /*syntheticReferences*/ undefined, texts); - setEachParent(prologues, node); - setEachParent(texts, node); - setEachParent(prependChildren, node); - node.hasNoDefaultLib = hasNoDefaultLib; - node.helpers = helpers; - node.referencedFiles = referencedFiles || emptyArray; - node.typeReferenceDirectives = typeReferenceDirectives; - node.libReferenceDirectives = libReferenceDirectives || emptyArray; - return node; + if (!texts) { + const textNode = factory.createUnparsedTextLike(/*data*/ undefined, /*internal*/ false); + setTextRangePosWidth(textNode, 0, typeof length === "function" ? length() : length); + texts = [textNode]; } - function parseOldFileOfCurrentEmit(bundleFileInfo: BundleFileInfo) { - let texts: UnparsedTextLike[] | undefined; - let syntheticReferences: UnparsedSyntheticReference[] | undefined; - for (const section of bundleFileInfo.sections) { - switch (section.kind) { - case BundleFileSectionKind.Internal: - case BundleFileSectionKind.Text: - texts = append(texts, setTextRange(factory.createUnparsedTextLike(section.data, section.kind === BundleFileSectionKind.Internal), section)); - break; + const node = parseNodeFactory.createUnparsedSource(prologues ?? emptyArray, /*syntheticReferences*/ undefined, texts); + setEachParent(prologues, node); + setEachParent(texts, node); + setEachParent(prependChildren, node); + node.hasNoDefaultLib = hasNoDefaultLib; + node.helpers = helpers; + node.referencedFiles = referencedFiles || emptyArray; + node.typeReferenceDirectives = typeReferenceDirectives; + node.libReferenceDirectives = libReferenceDirectives || emptyArray; + return node; +} - case BundleFileSectionKind.NoDefaultLib: - case BundleFileSectionKind.Reference: - case BundleFileSectionKind.Type: - case BundleFileSectionKind.TypeResolutionModeImport: - case BundleFileSectionKind.TypeResolutionModeRequire: - case BundleFileSectionKind.Lib: - syntheticReferences = append(syntheticReferences, setTextRange(factory.createUnparsedSyntheticReference(section), section)); - break; +function parseOldFileOfCurrentEmit(bundleFileInfo: BundleFileInfo) { + let texts: UnparsedTextLike[] | undefined; + let syntheticReferences: UnparsedSyntheticReference[] | undefined; + for (const section of bundleFileInfo.sections) { + switch (section.kind) { + case BundleFileSectionKind.Internal: + case BundleFileSectionKind.Text: + texts = append(texts, setTextRange(factory.createUnparsedTextLike(section.data, section.kind === BundleFileSectionKind.Internal), section)); + break; - // Ignore - case BundleFileSectionKind.Prologue: - case BundleFileSectionKind.EmitHelpers: - case BundleFileSectionKind.Prepend: - break; + case BundleFileSectionKind.NoDefaultLib: + case BundleFileSectionKind.Reference: + case BundleFileSectionKind.Type: + case BundleFileSectionKind.TypeResolutionModeImport: + case BundleFileSectionKind.TypeResolutionModeRequire: + case BundleFileSectionKind.Lib: + syntheticReferences = append(syntheticReferences, setTextRange(factory.createUnparsedSyntheticReference(section), section)); + break; - default: - Debug.assertNever(section); - } - } + // Ignore + case BundleFileSectionKind.Prologue: + case BundleFileSectionKind.EmitHelpers: + case BundleFileSectionKind.Prepend: + break; - const node = factory.createUnparsedSource(emptyArray, syntheticReferences, texts ?? emptyArray); - setEachParent(syntheticReferences, node); - setEachParent(texts, node); - node.helpers = map(bundleFileInfo.sources && bundleFileInfo.sources.helpers, name => getAllUnscopedEmitHelpers().get(name)!); - return node; - } - - // TODO(rbuckton): Move part of this to factory - export function createInputFiles( - javascriptText: string, - declarationText: string - ): InputFiles; - export function createInputFiles( - javascriptText: string, - declarationText: string, - javascriptMapPath: string | undefined, - javascriptMapText: string | undefined, - declarationMapPath: string | undefined, - declarationMapText: string | undefined - ): InputFiles; - export function createInputFiles( - readFileText: (path: string) => string | undefined, - javascriptPath: string, - javascriptMapPath: string | undefined, - declarationPath: string, - declarationMapPath: string | undefined, - buildInfoPath: string | undefined - ): InputFiles; - export function createInputFiles( - javascriptTextOrReadFileText: string | ((path: string) => string | undefined), - declarationTextOrJavascriptPath: string, - javascriptMapPath?: string, - javascriptMapTextOrDeclarationPath?: string, - declarationMapPath?: string, - declarationMapTextOrBuildInfoPath?: string, - ): InputFiles { - return !isString(javascriptTextOrReadFileText) ? - createInputFilesWithFilePaths( - javascriptTextOrReadFileText, - declarationTextOrJavascriptPath, - javascriptMapPath, - javascriptMapTextOrDeclarationPath!, - declarationMapPath, - declarationMapTextOrBuildInfoPath, - ) : - createInputFilesWithFileTexts( - /*javascriptPath*/ undefined, - javascriptTextOrReadFileText, - javascriptMapPath, - javascriptMapTextOrDeclarationPath, - /*declarationPath*/ undefined, - declarationTextOrJavascriptPath, - declarationMapPath, - declarationMapTextOrBuildInfoPath, - ); + default: + Debug.assertNever(section); + } } - /*@internal*/ - export function createInputFilesWithFilePaths( - readFileText: (path: string) => string | undefined, - javascriptPath: string, - javascriptMapPath: string | undefined, - declarationPath: string, - declarationMapPath: string | undefined, - buildInfoPath: string | undefined, - host?: CompilerHost, - options?: CompilerOptions, - ): InputFiles { - const node = parseNodeFactory.createInputFiles(); - node.javascriptPath = javascriptPath; - node.javascriptMapPath = javascriptMapPath; - node.declarationPath = declarationPath; - node.declarationMapPath = declarationMapPath; - node.buildInfoPath = buildInfoPath; - const cache = new Map(); - const textGetter = (path: string | undefined) => { - if (path === undefined) return undefined; - let value = cache.get(path); - if (value === undefined) { - value = readFileText(path); - cache.set(path, value !== undefined ? value : false); + + const node = factory.createUnparsedSource(emptyArray, syntheticReferences, texts ?? emptyArray); + setEachParent(syntheticReferences, node); + setEachParent(texts, node); + node.helpers = map(bundleFileInfo.sources && bundleFileInfo.sources.helpers, name => getAllUnscopedEmitHelpers().get(name)!); + return node; +} + +// TODO(rbuckton): Move part of this to factory +export function createInputFiles( + javascriptText: string, + declarationText: string +): InputFiles; +export function createInputFiles( + javascriptText: string, + declarationText: string, + javascriptMapPath: string | undefined, + javascriptMapText: string | undefined, + declarationMapPath: string | undefined, + declarationMapText: string | undefined +): InputFiles; +export function createInputFiles( + readFileText: (path: string) => string | undefined, + javascriptPath: string, + javascriptMapPath: string | undefined, + declarationPath: string, + declarationMapPath: string | undefined, + buildInfoPath: string | undefined +): InputFiles; +export function createInputFiles( + javascriptTextOrReadFileText: string | ((path: string) => string | undefined), + declarationTextOrJavascriptPath: string, + javascriptMapPath?: string, + javascriptMapTextOrDeclarationPath?: string, + declarationMapPath?: string, + declarationMapTextOrBuildInfoPath?: string, +): InputFiles { + return !isString(javascriptTextOrReadFileText) ? + createInputFilesWithFilePaths( + javascriptTextOrReadFileText, + declarationTextOrJavascriptPath, + javascriptMapPath, + javascriptMapTextOrDeclarationPath!, + declarationMapPath, + declarationMapTextOrBuildInfoPath, + ) : + createInputFilesWithFileTexts( + /*javascriptPath*/ undefined, + javascriptTextOrReadFileText, + javascriptMapPath, + javascriptMapTextOrDeclarationPath, + /*declarationPath*/ undefined, + declarationTextOrJavascriptPath, + declarationMapPath, + declarationMapTextOrBuildInfoPath, + ); +} +/** @internal */ +export function createInputFilesWithFilePaths( + readFileText: (path: string) => string | undefined, + javascriptPath: string, + javascriptMapPath: string | undefined, + declarationPath: string, + declarationMapPath: string | undefined, + buildInfoPath: string | undefined, + host?: CompilerHost, + options?: CompilerOptions, +): InputFiles { + const node = parseNodeFactory.createInputFiles(); + node.javascriptPath = javascriptPath; + node.javascriptMapPath = javascriptMapPath; + node.declarationPath = declarationPath; + node.declarationMapPath = declarationMapPath; + node.buildInfoPath = buildInfoPath; + const cache = new Map(); + const textGetter = (path: string | undefined) => { + if (path === undefined) return undefined; + let value = cache.get(path); + if (value === undefined) { + value = readFileText(path); + cache.set(path, value !== undefined ? value : false); + } + return value !== false ? value as string : undefined; + }; + const definedTextGetter = (path: string) => { + const result = textGetter(path); + return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`; + }; + let buildInfo: BuildInfo | false; + const getAndCacheBuildInfo = () => { + if (buildInfo === undefined && buildInfoPath) { + if (host?.getBuildInfo) { + buildInfo = host.getBuildInfo(buildInfoPath, options!.configFilePath) ?? false; } - return value !== false ? value as string : undefined; - }; - const definedTextGetter = (path: string) => { - const result = textGetter(path); - return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`; - }; - let buildInfo: BuildInfo | false; - const getAndCacheBuildInfo = () => { - if (buildInfo === undefined && buildInfoPath) { - if (host?.getBuildInfo) { - buildInfo = host.getBuildInfo(buildInfoPath, options!.configFilePath) ?? false; - } - else { - const result = textGetter(buildInfoPath); - buildInfo = result !== undefined ? getBuildInfo(buildInfoPath, result) ?? false : false; - } + else { + const result = textGetter(buildInfoPath); + buildInfo = result !== undefined ? getBuildInfo(buildInfoPath, result) ?? false : false; } - return buildInfo || undefined; - }; - Object.defineProperties(node, { - javascriptText: { get: () => definedTextGetter(javascriptPath) }, - javascriptMapText: { get: () => textGetter(javascriptMapPath) }, // TODO:: if there is inline sourceMap in jsFile, use that - declarationText: { get: () => definedTextGetter(Debug.checkDefined(declarationPath)) }, - declarationMapText: { get: () => textGetter(declarationMapPath) }, // TODO:: if there is inline sourceMap in dtsFile, use that - buildInfo: { get: getAndCacheBuildInfo }, - }); - return node; - } - /*@internal*/ - export function createInputFilesWithFileTexts( - javascriptPath: string | undefined, - javascriptText: string, - javascriptMapPath: string | undefined, - javascriptMapText: string | undefined, - declarationPath: string | undefined, - declarationText: string, - declarationMapPath: string | undefined, - declarationMapText: string | undefined, - buildInfoPath?: string, - buildInfo?: BuildInfo, - oldFileOfCurrentEmit?: boolean, - ): InputFiles { - const node = parseNodeFactory.createInputFiles(); - node.javascriptPath = javascriptPath; - node.javascriptText = javascriptText; - node.javascriptMapPath = javascriptMapPath; - node.javascriptMapText = javascriptMapText; - node.declarationPath = declarationPath; - node.declarationText = declarationText; - node.declarationMapPath = declarationMapPath; - node.declarationMapText = declarationMapText; - node.buildInfoPath = buildInfoPath; - node.buildInfo = buildInfo; - node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; - return node; - } - - let SourceMapSource: new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; + } + return buildInfo || undefined; + }; + Object.defineProperties(node, { + javascriptText: { get: () => definedTextGetter(javascriptPath) }, + javascriptMapText: { get: () => textGetter(javascriptMapPath) }, // TODO:: if there is inline sourceMap in jsFile, use that + declarationText: { get: () => definedTextGetter(Debug.checkDefined(declarationPath)) }, + declarationMapText: { get: () => textGetter(declarationMapPath) }, // TODO:: if there is inline sourceMap in dtsFile, use that + buildInfo: { get: getAndCacheBuildInfo }, + }); + return node; +} +/** @internal */ +export function createInputFilesWithFileTexts( + javascriptPath: string | undefined, + javascriptText: string, + javascriptMapPath: string | undefined, + javascriptMapText: string | undefined, + declarationPath: string | undefined, + declarationText: string, + declarationMapPath: string | undefined, + declarationMapText: string | undefined, + buildInfoPath?: string, + buildInfo?: BuildInfo, + oldFileOfCurrentEmit?: boolean, +): InputFiles { + const node = parseNodeFactory.createInputFiles(); + node.javascriptPath = javascriptPath; + node.javascriptText = javascriptText; + node.javascriptMapPath = javascriptMapPath; + node.javascriptMapText = javascriptMapText; + node.declarationPath = declarationPath; + node.declarationText = declarationText; + node.declarationMapPath = declarationMapPath; + node.declarationMapText = declarationMapText; + node.buildInfoPath = buildInfoPath; + node.buildInfo = buildInfo; + node.oldFileOfCurrentEmit = oldFileOfCurrentEmit; + return node; +} - /** - * Create an external source map source file reference - */ - export function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): SourceMapSource { - return new (SourceMapSource || (SourceMapSource = objectAllocator.getSourceMapSourceConstructor()))(fileName, text, skipTrivia); - } +let SourceMapSource: new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; - // Utilities +/** + * Create an external source map source file reference + */ +export function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): SourceMapSource { + return new (SourceMapSource || (SourceMapSource = objectAllocator.getSourceMapSourceConstructor()))(fileName, text, skipTrivia); +} - export function setOriginalNode(node: T, original: Node | undefined): T { - node.original = original; - if (original) { - const emitNode = original.emitNode; - if (emitNode) node.emitNode = mergeEmitNode(emitNode, node.emitNode); - } - return node; +// Utilities + +export function setOriginalNode(node: T, original: Node | undefined): T { + node.original = original; + if (original) { + const emitNode = original.emitNode; + if (emitNode) node.emitNode = mergeEmitNode(emitNode, node.emitNode); } + return node; +} - function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode | undefined) { - const { - flags, - leadingComments, - trailingComments, - commentRange, - sourceMapRange, - tokenSourceMapRanges, - constantValue, - helpers, - startsOnNewLine, - snippetElement, - } = sourceEmitNode; - if (!destEmitNode) destEmitNode = {} as EmitNode; - // We are using `.slice()` here in case `destEmitNode.leadingComments` is pushed to later. - if (leadingComments) destEmitNode.leadingComments = addRange(leadingComments.slice(), destEmitNode.leadingComments); - if (trailingComments) destEmitNode.trailingComments = addRange(trailingComments.slice(), destEmitNode.trailingComments); - if (flags) destEmitNode.flags = flags & ~EmitFlags.Immutable; - if (commentRange) destEmitNode.commentRange = commentRange; - if (sourceMapRange) destEmitNode.sourceMapRange = sourceMapRange; - if (tokenSourceMapRanges) destEmitNode.tokenSourceMapRanges = mergeTokenSourceMapRanges(tokenSourceMapRanges, destEmitNode.tokenSourceMapRanges!); - if (constantValue !== undefined) destEmitNode.constantValue = constantValue; - if (helpers) { - for (const helper of helpers) { - destEmitNode.helpers = appendIfUnique(destEmitNode.helpers, helper); - } +function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode | undefined) { + const { + flags, + leadingComments, + trailingComments, + commentRange, + sourceMapRange, + tokenSourceMapRanges, + constantValue, + helpers, + startsOnNewLine, + snippetElement, + } = sourceEmitNode; + if (!destEmitNode) destEmitNode = {} as EmitNode; + // We are using `.slice()` here in case `destEmitNode.leadingComments` is pushed to later. + if (leadingComments) destEmitNode.leadingComments = addRange(leadingComments.slice(), destEmitNode.leadingComments); + if (trailingComments) destEmitNode.trailingComments = addRange(trailingComments.slice(), destEmitNode.trailingComments); + if (flags) destEmitNode.flags = flags & ~EmitFlags.Immutable; + if (commentRange) destEmitNode.commentRange = commentRange; + if (sourceMapRange) destEmitNode.sourceMapRange = sourceMapRange; + if (tokenSourceMapRanges) destEmitNode.tokenSourceMapRanges = mergeTokenSourceMapRanges(tokenSourceMapRanges, destEmitNode.tokenSourceMapRanges!); + if (constantValue !== undefined) destEmitNode.constantValue = constantValue; + if (helpers) { + for (const helper of helpers) { + destEmitNode.helpers = appendIfUnique(destEmitNode.helpers, helper); } - if (startsOnNewLine !== undefined) destEmitNode.startsOnNewLine = startsOnNewLine; - if (snippetElement !== undefined) destEmitNode.snippetElement = snippetElement; - return destEmitNode; } + if (startsOnNewLine !== undefined) destEmitNode.startsOnNewLine = startsOnNewLine; + if (snippetElement !== undefined) destEmitNode.snippetElement = snippetElement; + return destEmitNode; +} - function mergeTokenSourceMapRanges(sourceRanges: (TextRange | undefined)[], destRanges: (TextRange | undefined)[]) { - if (!destRanges) destRanges = []; - for (const key in sourceRanges) { - destRanges[key] = sourceRanges[key]; - } - return destRanges; +function mergeTokenSourceMapRanges(sourceRanges: (TextRange | undefined)[], destRanges: (TextRange | undefined)[]) { + if (!destRanges) destRanges = []; + for (const key in sourceRanges) { + destRanges[key] = sourceRanges[key]; } + return destRanges; } diff --git a/src/compiler/factory/nodeTests.ts b/src/compiler/factory/nodeTests.ts index a2a558e9079b1..f391dbb1edb59 100644 --- a/src/compiler/factory/nodeTests.ts +++ b/src/compiler/factory/nodeTests.ts @@ -1,960 +1,999 @@ -namespace ts { - // Literals +import { + AbstractKeyword, AccessorKeyword, ArrayBindingPattern, ArrayLiteralExpression, ArrayTypeNode, ArrowFunction, + AsExpression, AssertClause, AssertEntry, AssertsKeyword, AsteriskToken, AsyncKeyword, AwaitExpression, AwaitKeyword, + BigIntLiteral, BinaryExpression, BindingElement, Block, BreakStatement, Bundle, CallExpression, + CallSignatureDeclaration, CaseBlock, CaseClause, CatchClause, ClassDeclaration, ClassExpression, + ClassStaticBlockDeclaration, ColonToken, CommaListExpression, ComputedPropertyName, ConditionalExpression, + ConditionalTypeNode, ConstructorDeclaration, ConstructorTypeNode, ConstructSignatureDeclaration, ContinueStatement, + DebuggerStatement, Decorator, DefaultClause, DeleteExpression, DoStatement, DotDotDotToken, ElementAccessExpression, + EmptyStatement, EndOfDeclarationMarker, EnumDeclaration, EnumMember, EqualsGreaterThanToken, ExclamationToken, + ExportAssignment, ExportDeclaration, ExportKeyword, ExportSpecifier, ExpressionStatement, + ExpressionWithTypeArguments, ExternalModuleReference, ForInStatement, ForOfStatement, ForStatement, + FunctionDeclaration, FunctionExpression, FunctionTypeNode, GetAccessorDeclaration, HeritageClause, Identifier, + IfStatement, ImportClause, ImportDeclaration, ImportEqualsDeclaration, ImportExpression, ImportSpecifier, + ImportTypeAssertionContainer, ImportTypeNode, IndexedAccessTypeNode, IndexSignatureDeclaration, InferTypeNode, + InterfaceDeclaration, IntersectionTypeNode, JSDoc, JSDocAllType, JSDocAugmentsTag, JSDocAuthorTag, JSDocCallbackTag, + JSDocClassTag, JSDocDeprecatedTag, JSDocEnumTag, JSDocFunctionType, JSDocImplementsTag, JSDocLink, JSDocLinkCode, + JSDocLinkPlain, JSDocMemberName, JSDocNamepathType, JSDocNameReference, JSDocNonNullableType, JSDocNullableType, + JSDocOptionalType, JSDocOverrideTag, JSDocParameterTag, JSDocPrivateTag, JSDocPropertyTag, JSDocProtectedTag, + JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, JSDocSeeTag, JSDocSignature, JSDocTemplateTag, JSDocThisTag, + JSDocTypedefTag, JSDocTypeExpression, JSDocTypeLiteral, JSDocTypeTag, JSDocUnknownTag, JSDocUnknownType, + JSDocVariadicType, JsxAttribute, JsxAttributes, JsxClosingElement, JsxClosingFragment, JsxElement, JsxExpression, + JsxFragment, JsxOpeningElement, JsxOpeningFragment, JsxSelfClosingElement, JsxSpreadAttribute, JsxText, + LabeledStatement, LiteralTypeNode, MappedTypeNode, MergeDeclarationMarker, MetaProperty, MethodDeclaration, + MethodSignature, MinusToken, MissingDeclaration, ModuleBlock, ModuleDeclaration, NamedExports, NamedImports, + NamedTupleMember, NamespaceExport, NamespaceExportDeclaration, NamespaceImport, NewExpression, Node, + NonNullExpression, NoSubstitutionTemplateLiteral, NotEmittedStatement, NumericLiteral, ObjectBindingPattern, + ObjectLiteralExpression, OmittedExpression, OptionalTypeNode, OverrideKeyword, ParameterDeclaration, + ParenthesizedExpression, ParenthesizedTypeNode, PartiallyEmittedExpression, PlusToken, PostfixUnaryExpression, + PrefixUnaryExpression, PrivateIdentifier, PropertyAccessExpression, PropertyAssignment, PropertyDeclaration, + PropertySignature, QualifiedName, QuestionDotToken, QuestionToken, ReadonlyKeyword, RegularExpressionLiteral, + RestTypeNode, ReturnStatement, SatisfiesExpression, SemicolonClassElement, SetAccessorDeclaration, + ShorthandPropertyAssignment, SourceFile, SpreadAssignment, SpreadElement, StaticKeyword, StringLiteral, + SuperExpression, SwitchStatement, SyntaxKind, SyntaxList, SyntheticExpression, SyntheticReferenceExpression, + TaggedTemplateExpression, TemplateExpression, TemplateHead, TemplateLiteralTypeNode, TemplateLiteralTypeSpan, + TemplateMiddle, TemplateSpan, TemplateTail, ThisTypeNode, ThrowStatement, Token, TryStatement, TupleTypeNode, + TypeAliasDeclaration, TypeAssertion, TypeLiteralNode, TypeOfExpression, TypeOperatorNode, TypeParameterDeclaration, + TypePredicateNode, TypeQueryNode, TypeReferenceNode, UnionTypeNode, UnparsedPrepend, UnparsedSource, + VariableDeclaration, VariableDeclarationList, VariableStatement, VoidExpression, WhileStatement, WithStatement, + YieldExpression, +} from "../_namespaces/ts"; + +// Literals + +export function isNumericLiteral(node: Node): node is NumericLiteral { + return node.kind === SyntaxKind.NumericLiteral; +} - export function isNumericLiteral(node: Node): node is NumericLiteral { - return node.kind === SyntaxKind.NumericLiteral; - } +export function isBigIntLiteral(node: Node): node is BigIntLiteral { + return node.kind === SyntaxKind.BigIntLiteral; +} - export function isBigIntLiteral(node: Node): node is BigIntLiteral { - return node.kind === SyntaxKind.BigIntLiteral; - } +export function isStringLiteral(node: Node): node is StringLiteral { + return node.kind === SyntaxKind.StringLiteral; +} - export function isStringLiteral(node: Node): node is StringLiteral { - return node.kind === SyntaxKind.StringLiteral; - } +export function isJsxText(node: Node): node is JsxText { + return node.kind === SyntaxKind.JsxText; +} - export function isJsxText(node: Node): node is JsxText { - return node.kind === SyntaxKind.JsxText; - } +export function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral { + return node.kind === SyntaxKind.RegularExpressionLiteral; +} - export function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral { - return node.kind === SyntaxKind.RegularExpressionLiteral; - } +export function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral { + return node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; +} - export function isNoSubstitutionTemplateLiteral(node: Node): node is NoSubstitutionTemplateLiteral { - return node.kind === SyntaxKind.NoSubstitutionTemplateLiteral; - } +// Pseudo-literals - // Pseudo-literals - - export function isTemplateHead(node: Node): node is TemplateHead { - return node.kind === SyntaxKind.TemplateHead; - } - - export function isTemplateMiddle(node: Node): node is TemplateMiddle { - return node.kind === SyntaxKind.TemplateMiddle; - } - - export function isTemplateTail(node: Node): node is TemplateTail { - return node.kind === SyntaxKind.TemplateTail; - } - - // Punctuation - - export function isDotDotDotToken(node: Node): node is DotDotDotToken { - return node.kind === SyntaxKind.DotDotDotToken; - } - - /*@internal*/ - export function isCommaToken(node: Node): node is Token { - return node.kind === SyntaxKind.CommaToken; - } - - export function isPlusToken(node: Node): node is PlusToken { - return node.kind === SyntaxKind.PlusToken; - } - - export function isMinusToken(node: Node): node is MinusToken { - return node.kind === SyntaxKind.MinusToken; - } - - export function isAsteriskToken(node: Node): node is AsteriskToken { - return node.kind === SyntaxKind.AsteriskToken; - } - - /*@internal*/ - export function isExclamationToken(node: Node): node is ExclamationToken { - return node.kind === SyntaxKind.ExclamationToken; - } - - /*@internal*/ - export function isQuestionToken(node: Node): node is QuestionToken { - return node.kind === SyntaxKind.QuestionToken; - } - - /*@internal*/ - export function isColonToken(node: Node): node is ColonToken { - return node.kind === SyntaxKind.ColonToken; - } - - /*@internal*/ - export function isQuestionDotToken(node: Node): node is QuestionDotToken { - return node.kind === SyntaxKind.QuestionDotToken; - } - - /*@internal*/ - export function isEqualsGreaterThanToken(node: Node): node is EqualsGreaterThanToken { - return node.kind === SyntaxKind.EqualsGreaterThanToken; - } - - // Identifiers +export function isTemplateHead(node: Node): node is TemplateHead { + return node.kind === SyntaxKind.TemplateHead; +} - export function isIdentifier(node: Node): node is Identifier { - return node.kind === SyntaxKind.Identifier; - } +export function isTemplateMiddle(node: Node): node is TemplateMiddle { + return node.kind === SyntaxKind.TemplateMiddle; +} - export function isPrivateIdentifier(node: Node): node is PrivateIdentifier { - return node.kind === SyntaxKind.PrivateIdentifier; - } +export function isTemplateTail(node: Node): node is TemplateTail { + return node.kind === SyntaxKind.TemplateTail; +} - // Reserved Words - - /* @internal */ - export function isExportModifier(node: Node): node is ExportKeyword { - return node.kind === SyntaxKind.ExportKeyword; - } - - /* @internal */ - export function isAsyncModifier(node: Node): node is AsyncKeyword { - return node.kind === SyntaxKind.AsyncKeyword; - } +// Punctuation - /* @internal */ - export function isAssertsKeyword(node: Node): node is AssertsKeyword { - return node.kind === SyntaxKind.AssertsKeyword; - } - - /* @internal */ - export function isAwaitKeyword(node: Node): node is AwaitKeyword { - return node.kind === SyntaxKind.AwaitKeyword; - } - - /* @internal */ - export function isReadonlyKeyword(node: Node): node is ReadonlyKeyword { - return node.kind === SyntaxKind.ReadonlyKeyword; - } - - /* @internal */ - export function isStaticModifier(node: Node): node is StaticKeyword { - return node.kind === SyntaxKind.StaticKeyword; - } - - /* @internal */ - export function isAbstractModifier(node: Node): node is AbstractKeyword { - return node.kind === SyntaxKind.AbstractKeyword; - } - - /* @internal */ - export function isOverrideModifier(node: Node): node is OverrideKeyword { - return node.kind === SyntaxKind.OverrideKeyword; - } +export function isDotDotDotToken(node: Node): node is DotDotDotToken { + return node.kind === SyntaxKind.DotDotDotToken; +} - /* @internal */ - export function isAccessorModifier(node: Node): node is AccessorKeyword { - return node.kind === SyntaxKind.AccessorKeyword; - } - - /*@internal*/ - export function isSuperKeyword(node: Node): node is SuperExpression { - return node.kind === SyntaxKind.SuperKeyword; - } - - /*@internal*/ - export function isImportKeyword(node: Node): node is ImportExpression { - return node.kind === SyntaxKind.ImportKeyword; - } - - // Names - - export function isQualifiedName(node: Node): node is QualifiedName { - return node.kind === SyntaxKind.QualifiedName; - } - - export function isComputedPropertyName(node: Node): node is ComputedPropertyName { - return node.kind === SyntaxKind.ComputedPropertyName; - } - - // Signature elements - - export function isTypeParameterDeclaration(node: Node): node is TypeParameterDeclaration { - return node.kind === SyntaxKind.TypeParameter; - } - - // TODO(rbuckton): Rename to 'isParameterDeclaration' - export function isParameter(node: Node): node is ParameterDeclaration { - return node.kind === SyntaxKind.Parameter; - } +/** @internal */ +export function isCommaToken(node: Node): node is Token { + return node.kind === SyntaxKind.CommaToken; +} - export function isDecorator(node: Node): node is Decorator { - return node.kind === SyntaxKind.Decorator; - } +export function isPlusToken(node: Node): node is PlusToken { + return node.kind === SyntaxKind.PlusToken; +} - // TypeMember +export function isMinusToken(node: Node): node is MinusToken { + return node.kind === SyntaxKind.MinusToken; +} - export function isPropertySignature(node: Node): node is PropertySignature { - return node.kind === SyntaxKind.PropertySignature; - } +export function isAsteriskToken(node: Node): node is AsteriskToken { + return node.kind === SyntaxKind.AsteriskToken; +} - export function isPropertyDeclaration(node: Node): node is PropertyDeclaration { - return node.kind === SyntaxKind.PropertyDeclaration; - } +/** @internal */ +export function isExclamationToken(node: Node): node is ExclamationToken { + return node.kind === SyntaxKind.ExclamationToken; +} - export function isMethodSignature(node: Node): node is MethodSignature { - return node.kind === SyntaxKind.MethodSignature; - } +/** @internal */ +export function isQuestionToken(node: Node): node is QuestionToken { + return node.kind === SyntaxKind.QuestionToken; +} - export function isMethodDeclaration(node: Node): node is MethodDeclaration { - return node.kind === SyntaxKind.MethodDeclaration; - } +/** @internal */ +export function isColonToken(node: Node): node is ColonToken { + return node.kind === SyntaxKind.ColonToken; +} - export function isClassStaticBlockDeclaration(node: Node): node is ClassStaticBlockDeclaration { - return node.kind === SyntaxKind.ClassStaticBlockDeclaration; - } - - export function isConstructorDeclaration(node: Node): node is ConstructorDeclaration { - return node.kind === SyntaxKind.Constructor; - } - - export function isGetAccessorDeclaration(node: Node): node is GetAccessorDeclaration { - return node.kind === SyntaxKind.GetAccessor; - } +/** @internal */ +export function isQuestionDotToken(node: Node): node is QuestionDotToken { + return node.kind === SyntaxKind.QuestionDotToken; +} - export function isSetAccessorDeclaration(node: Node): node is SetAccessorDeclaration { - return node.kind === SyntaxKind.SetAccessor; - } +/** @internal */ +export function isEqualsGreaterThanToken(node: Node): node is EqualsGreaterThanToken { + return node.kind === SyntaxKind.EqualsGreaterThanToken; +} - export function isCallSignatureDeclaration(node: Node): node is CallSignatureDeclaration { - return node.kind === SyntaxKind.CallSignature; - } +// Identifiers - export function isConstructSignatureDeclaration(node: Node): node is ConstructSignatureDeclaration { - return node.kind === SyntaxKind.ConstructSignature; - } +export function isIdentifier(node: Node): node is Identifier { + return node.kind === SyntaxKind.Identifier; +} - export function isIndexSignatureDeclaration(node: Node): node is IndexSignatureDeclaration { - return node.kind === SyntaxKind.IndexSignature; - } +export function isPrivateIdentifier(node: Node): node is PrivateIdentifier { + return node.kind === SyntaxKind.PrivateIdentifier; +} - // Type +// Reserved Words - export function isTypePredicateNode(node: Node): node is TypePredicateNode { - return node.kind === SyntaxKind.TypePredicate; - } +/** @internal */ +export function isExportModifier(node: Node): node is ExportKeyword { + return node.kind === SyntaxKind.ExportKeyword; +} - export function isTypeReferenceNode(node: Node): node is TypeReferenceNode { - return node.kind === SyntaxKind.TypeReference; - } +/** @internal */ +export function isAsyncModifier(node: Node): node is AsyncKeyword { + return node.kind === SyntaxKind.AsyncKeyword; +} - export function isFunctionTypeNode(node: Node): node is FunctionTypeNode { - return node.kind === SyntaxKind.FunctionType; - } +/** @internal */ +export function isAssertsKeyword(node: Node): node is AssertsKeyword { + return node.kind === SyntaxKind.AssertsKeyword; +} - export function isConstructorTypeNode(node: Node): node is ConstructorTypeNode { - return node.kind === SyntaxKind.ConstructorType; - } +/** @internal */ +export function isAwaitKeyword(node: Node): node is AwaitKeyword { + return node.kind === SyntaxKind.AwaitKeyword; +} - export function isTypeQueryNode(node: Node): node is TypeQueryNode { - return node.kind === SyntaxKind.TypeQuery; - } +/** @internal */ +export function isReadonlyKeyword(node: Node): node is ReadonlyKeyword { + return node.kind === SyntaxKind.ReadonlyKeyword; +} - export function isTypeLiteralNode(node: Node): node is TypeLiteralNode { - return node.kind === SyntaxKind.TypeLiteral; - } +/** @internal */ +export function isStaticModifier(node: Node): node is StaticKeyword { + return node.kind === SyntaxKind.StaticKeyword; +} - export function isArrayTypeNode(node: Node): node is ArrayTypeNode { - return node.kind === SyntaxKind.ArrayType; - } +/** @internal */ +export function isAbstractModifier(node: Node): node is AbstractKeyword { + return node.kind === SyntaxKind.AbstractKeyword; +} - export function isTupleTypeNode(node: Node): node is TupleTypeNode { - return node.kind === SyntaxKind.TupleType; - } +/** @internal */ +export function isOverrideModifier(node: Node): node is OverrideKeyword { + return node.kind === SyntaxKind.OverrideKeyword; +} - export function isNamedTupleMember(node: Node): node is NamedTupleMember { - return node.kind === SyntaxKind.NamedTupleMember; - } +/** @internal */ +export function isAccessorModifier(node: Node): node is AccessorKeyword { + return node.kind === SyntaxKind.AccessorKeyword; +} - export function isOptionalTypeNode(node: Node): node is OptionalTypeNode { - return node.kind === SyntaxKind.OptionalType; - } +/** @internal */ +export function isSuperKeyword(node: Node): node is SuperExpression { + return node.kind === SyntaxKind.SuperKeyword; +} - export function isRestTypeNode(node: Node): node is RestTypeNode { - return node.kind === SyntaxKind.RestType; - } +/** @internal */ +export function isImportKeyword(node: Node): node is ImportExpression { + return node.kind === SyntaxKind.ImportKeyword; +} - export function isUnionTypeNode(node: Node): node is UnionTypeNode { - return node.kind === SyntaxKind.UnionType; - } +// Names - export function isIntersectionTypeNode(node: Node): node is IntersectionTypeNode { - return node.kind === SyntaxKind.IntersectionType; - } +export function isQualifiedName(node: Node): node is QualifiedName { + return node.kind === SyntaxKind.QualifiedName; +} - export function isConditionalTypeNode(node: Node): node is ConditionalTypeNode { - return node.kind === SyntaxKind.ConditionalType; - } +export function isComputedPropertyName(node: Node): node is ComputedPropertyName { + return node.kind === SyntaxKind.ComputedPropertyName; +} - export function isInferTypeNode(node: Node): node is InferTypeNode { - return node.kind === SyntaxKind.InferType; - } +// Signature elements - export function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode { - return node.kind === SyntaxKind.ParenthesizedType; - } +export function isTypeParameterDeclaration(node: Node): node is TypeParameterDeclaration { + return node.kind === SyntaxKind.TypeParameter; +} - export function isThisTypeNode(node: Node): node is ThisTypeNode { - return node.kind === SyntaxKind.ThisType; - } +// TODO(rbuckton): Rename to 'isParameterDeclaration' +export function isParameter(node: Node): node is ParameterDeclaration { + return node.kind === SyntaxKind.Parameter; +} - export function isTypeOperatorNode(node: Node): node is TypeOperatorNode { - return node.kind === SyntaxKind.TypeOperator; - } +export function isDecorator(node: Node): node is Decorator { + return node.kind === SyntaxKind.Decorator; +} - export function isIndexedAccessTypeNode(node: Node): node is IndexedAccessTypeNode { - return node.kind === SyntaxKind.IndexedAccessType; - } +// TypeMember - export function isMappedTypeNode(node: Node): node is MappedTypeNode { - return node.kind === SyntaxKind.MappedType; - } +export function isPropertySignature(node: Node): node is PropertySignature { + return node.kind === SyntaxKind.PropertySignature; +} - export function isLiteralTypeNode(node: Node): node is LiteralTypeNode { - return node.kind === SyntaxKind.LiteralType; - } +export function isPropertyDeclaration(node: Node): node is PropertyDeclaration { + return node.kind === SyntaxKind.PropertyDeclaration; +} - export function isImportTypeNode(node: Node): node is ImportTypeNode { - return node.kind === SyntaxKind.ImportType; - } +export function isMethodSignature(node: Node): node is MethodSignature { + return node.kind === SyntaxKind.MethodSignature; +} - export function isTemplateLiteralTypeSpan(node: Node): node is TemplateLiteralTypeSpan { - return node.kind === SyntaxKind.TemplateLiteralTypeSpan; - } +export function isMethodDeclaration(node: Node): node is MethodDeclaration { + return node.kind === SyntaxKind.MethodDeclaration; +} - export function isTemplateLiteralTypeNode(node: Node): node is TemplateLiteralTypeNode { - return node.kind === SyntaxKind.TemplateLiteralType; - } +export function isClassStaticBlockDeclaration(node: Node): node is ClassStaticBlockDeclaration { + return node.kind === SyntaxKind.ClassStaticBlockDeclaration; +} - // Binding patterns +export function isConstructorDeclaration(node: Node): node is ConstructorDeclaration { + return node.kind === SyntaxKind.Constructor; +} - export function isObjectBindingPattern(node: Node): node is ObjectBindingPattern { - return node.kind === SyntaxKind.ObjectBindingPattern; - } +export function isGetAccessorDeclaration(node: Node): node is GetAccessorDeclaration { + return node.kind === SyntaxKind.GetAccessor; +} - export function isArrayBindingPattern(node: Node): node is ArrayBindingPattern { - return node.kind === SyntaxKind.ArrayBindingPattern; - } +export function isSetAccessorDeclaration(node: Node): node is SetAccessorDeclaration { + return node.kind === SyntaxKind.SetAccessor; +} - export function isBindingElement(node: Node): node is BindingElement { - return node.kind === SyntaxKind.BindingElement; - } +export function isCallSignatureDeclaration(node: Node): node is CallSignatureDeclaration { + return node.kind === SyntaxKind.CallSignature; +} - // Expression +export function isConstructSignatureDeclaration(node: Node): node is ConstructSignatureDeclaration { + return node.kind === SyntaxKind.ConstructSignature; +} - export function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression { - return node.kind === SyntaxKind.ArrayLiteralExpression; - } +export function isIndexSignatureDeclaration(node: Node): node is IndexSignatureDeclaration { + return node.kind === SyntaxKind.IndexSignature; +} - export function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression { - return node.kind === SyntaxKind.ObjectLiteralExpression; - } +// Type - export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression { - return node.kind === SyntaxKind.PropertyAccessExpression; - } +export function isTypePredicateNode(node: Node): node is TypePredicateNode { + return node.kind === SyntaxKind.TypePredicate; +} - export function isElementAccessExpression(node: Node): node is ElementAccessExpression { - return node.kind === SyntaxKind.ElementAccessExpression; - } +export function isTypeReferenceNode(node: Node): node is TypeReferenceNode { + return node.kind === SyntaxKind.TypeReference; +} - export function isCallExpression(node: Node): node is CallExpression { - return node.kind === SyntaxKind.CallExpression; - } +export function isFunctionTypeNode(node: Node): node is FunctionTypeNode { + return node.kind === SyntaxKind.FunctionType; +} - export function isNewExpression(node: Node): node is NewExpression { - return node.kind === SyntaxKind.NewExpression; - } +export function isConstructorTypeNode(node: Node): node is ConstructorTypeNode { + return node.kind === SyntaxKind.ConstructorType; +} - export function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression { - return node.kind === SyntaxKind.TaggedTemplateExpression; - } +export function isTypeQueryNode(node: Node): node is TypeQueryNode { + return node.kind === SyntaxKind.TypeQuery; +} - export function isTypeAssertionExpression(node: Node): node is TypeAssertion { - return node.kind === SyntaxKind.TypeAssertionExpression; - } +export function isTypeLiteralNode(node: Node): node is TypeLiteralNode { + return node.kind === SyntaxKind.TypeLiteral; +} - export function isParenthesizedExpression(node: Node): node is ParenthesizedExpression { - return node.kind === SyntaxKind.ParenthesizedExpression; - } +export function isArrayTypeNode(node: Node): node is ArrayTypeNode { + return node.kind === SyntaxKind.ArrayType; +} - export function isFunctionExpression(node: Node): node is FunctionExpression { - return node.kind === SyntaxKind.FunctionExpression; - } +export function isTupleTypeNode(node: Node): node is TupleTypeNode { + return node.kind === SyntaxKind.TupleType; +} - export function isArrowFunction(node: Node): node is ArrowFunction { - return node.kind === SyntaxKind.ArrowFunction; - } +export function isNamedTupleMember(node: Node): node is NamedTupleMember { + return node.kind === SyntaxKind.NamedTupleMember; +} - export function isDeleteExpression(node: Node): node is DeleteExpression { - return node.kind === SyntaxKind.DeleteExpression; - } +export function isOptionalTypeNode(node: Node): node is OptionalTypeNode { + return node.kind === SyntaxKind.OptionalType; +} - export function isTypeOfExpression(node: Node): node is TypeOfExpression { - return node.kind === SyntaxKind.TypeOfExpression; - } +export function isRestTypeNode(node: Node): node is RestTypeNode { + return node.kind === SyntaxKind.RestType; +} - export function isVoidExpression(node: Node): node is VoidExpression { - return node.kind === SyntaxKind.VoidExpression; - } +export function isUnionTypeNode(node: Node): node is UnionTypeNode { + return node.kind === SyntaxKind.UnionType; +} - export function isAwaitExpression(node: Node): node is AwaitExpression { - return node.kind === SyntaxKind.AwaitExpression; - } +export function isIntersectionTypeNode(node: Node): node is IntersectionTypeNode { + return node.kind === SyntaxKind.IntersectionType; +} - export function isPrefixUnaryExpression(node: Node): node is PrefixUnaryExpression { - return node.kind === SyntaxKind.PrefixUnaryExpression; - } +export function isConditionalTypeNode(node: Node): node is ConditionalTypeNode { + return node.kind === SyntaxKind.ConditionalType; +} - export function isPostfixUnaryExpression(node: Node): node is PostfixUnaryExpression { - return node.kind === SyntaxKind.PostfixUnaryExpression; - } +export function isInferTypeNode(node: Node): node is InferTypeNode { + return node.kind === SyntaxKind.InferType; +} - export function isBinaryExpression(node: Node): node is BinaryExpression { - return node.kind === SyntaxKind.BinaryExpression; - } +export function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode { + return node.kind === SyntaxKind.ParenthesizedType; +} - export function isConditionalExpression(node: Node): node is ConditionalExpression { - return node.kind === SyntaxKind.ConditionalExpression; - } +export function isThisTypeNode(node: Node): node is ThisTypeNode { + return node.kind === SyntaxKind.ThisType; +} - export function isTemplateExpression(node: Node): node is TemplateExpression { - return node.kind === SyntaxKind.TemplateExpression; - } +export function isTypeOperatorNode(node: Node): node is TypeOperatorNode { + return node.kind === SyntaxKind.TypeOperator; +} - export function isYieldExpression(node: Node): node is YieldExpression { - return node.kind === SyntaxKind.YieldExpression; - } +export function isIndexedAccessTypeNode(node: Node): node is IndexedAccessTypeNode { + return node.kind === SyntaxKind.IndexedAccessType; +} - export function isSpreadElement(node: Node): node is SpreadElement { - return node.kind === SyntaxKind.SpreadElement; - } +export function isMappedTypeNode(node: Node): node is MappedTypeNode { + return node.kind === SyntaxKind.MappedType; +} - export function isClassExpression(node: Node): node is ClassExpression { - return node.kind === SyntaxKind.ClassExpression; - } +export function isLiteralTypeNode(node: Node): node is LiteralTypeNode { + return node.kind === SyntaxKind.LiteralType; +} - export function isOmittedExpression(node: Node): node is OmittedExpression { - return node.kind === SyntaxKind.OmittedExpression; - } +export function isImportTypeNode(node: Node): node is ImportTypeNode { + return node.kind === SyntaxKind.ImportType; +} - export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { - return node.kind === SyntaxKind.ExpressionWithTypeArguments; - } +export function isTemplateLiteralTypeSpan(node: Node): node is TemplateLiteralTypeSpan { + return node.kind === SyntaxKind.TemplateLiteralTypeSpan; +} - export function isAsExpression(node: Node): node is AsExpression { - return node.kind === SyntaxKind.AsExpression; - } +export function isTemplateLiteralTypeNode(node: Node): node is TemplateLiteralTypeNode { + return node.kind === SyntaxKind.TemplateLiteralType; +} - export function isSatisfiesExpression(node: Node): node is SatisfiesExpression { - return node.kind === SyntaxKind.SatisfiesExpression; - } +// Binding patterns - export function isNonNullExpression(node: Node): node is NonNullExpression { - return node.kind === SyntaxKind.NonNullExpression; - } +export function isObjectBindingPattern(node: Node): node is ObjectBindingPattern { + return node.kind === SyntaxKind.ObjectBindingPattern; +} - export function isMetaProperty(node: Node): node is MetaProperty { - return node.kind === SyntaxKind.MetaProperty; - } +export function isArrayBindingPattern(node: Node): node is ArrayBindingPattern { + return node.kind === SyntaxKind.ArrayBindingPattern; +} - export function isSyntheticExpression(node: Node): node is SyntheticExpression { - return node.kind === SyntaxKind.SyntheticExpression; - } +export function isBindingElement(node: Node): node is BindingElement { + return node.kind === SyntaxKind.BindingElement; +} - export function isPartiallyEmittedExpression(node: Node): node is PartiallyEmittedExpression { - return node.kind === SyntaxKind.PartiallyEmittedExpression; - } +// Expression - export function isCommaListExpression(node: Node): node is CommaListExpression { - return node.kind === SyntaxKind.CommaListExpression; - } +export function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression { + return node.kind === SyntaxKind.ArrayLiteralExpression; +} - // Misc +export function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression { + return node.kind === SyntaxKind.ObjectLiteralExpression; +} - export function isTemplateSpan(node: Node): node is TemplateSpan { - return node.kind === SyntaxKind.TemplateSpan; - } +export function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression { + return node.kind === SyntaxKind.PropertyAccessExpression; +} - export function isSemicolonClassElement(node: Node): node is SemicolonClassElement { - return node.kind === SyntaxKind.SemicolonClassElement; - } +export function isElementAccessExpression(node: Node): node is ElementAccessExpression { + return node.kind === SyntaxKind.ElementAccessExpression; +} - // Elements +export function isCallExpression(node: Node): node is CallExpression { + return node.kind === SyntaxKind.CallExpression; +} - export function isBlock(node: Node): node is Block { - return node.kind === SyntaxKind.Block; - } +export function isNewExpression(node: Node): node is NewExpression { + return node.kind === SyntaxKind.NewExpression; +} - export function isVariableStatement(node: Node): node is VariableStatement { - return node.kind === SyntaxKind.VariableStatement; - } +export function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression { + return node.kind === SyntaxKind.TaggedTemplateExpression; +} - export function isEmptyStatement(node: Node): node is EmptyStatement { - return node.kind === SyntaxKind.EmptyStatement; - } +export function isTypeAssertionExpression(node: Node): node is TypeAssertion { + return node.kind === SyntaxKind.TypeAssertionExpression; +} - export function isExpressionStatement(node: Node): node is ExpressionStatement { - return node.kind === SyntaxKind.ExpressionStatement; - } +export function isParenthesizedExpression(node: Node): node is ParenthesizedExpression { + return node.kind === SyntaxKind.ParenthesizedExpression; +} - export function isIfStatement(node: Node): node is IfStatement { - return node.kind === SyntaxKind.IfStatement; - } +export function isFunctionExpression(node: Node): node is FunctionExpression { + return node.kind === SyntaxKind.FunctionExpression; +} - export function isDoStatement(node: Node): node is DoStatement { - return node.kind === SyntaxKind.DoStatement; - } +export function isArrowFunction(node: Node): node is ArrowFunction { + return node.kind === SyntaxKind.ArrowFunction; +} - export function isWhileStatement(node: Node): node is WhileStatement { - return node.kind === SyntaxKind.WhileStatement; - } +export function isDeleteExpression(node: Node): node is DeleteExpression { + return node.kind === SyntaxKind.DeleteExpression; +} - export function isForStatement(node: Node): node is ForStatement { - return node.kind === SyntaxKind.ForStatement; - } +export function isTypeOfExpression(node: Node): node is TypeOfExpression { + return node.kind === SyntaxKind.TypeOfExpression; +} - export function isForInStatement(node: Node): node is ForInStatement { - return node.kind === SyntaxKind.ForInStatement; - } +export function isVoidExpression(node: Node): node is VoidExpression { + return node.kind === SyntaxKind.VoidExpression; +} - export function isForOfStatement(node: Node): node is ForOfStatement { - return node.kind === SyntaxKind.ForOfStatement; - } +export function isAwaitExpression(node: Node): node is AwaitExpression { + return node.kind === SyntaxKind.AwaitExpression; +} - export function isContinueStatement(node: Node): node is ContinueStatement { - return node.kind === SyntaxKind.ContinueStatement; - } +export function isPrefixUnaryExpression(node: Node): node is PrefixUnaryExpression { + return node.kind === SyntaxKind.PrefixUnaryExpression; +} - export function isBreakStatement(node: Node): node is BreakStatement { - return node.kind === SyntaxKind.BreakStatement; - } +export function isPostfixUnaryExpression(node: Node): node is PostfixUnaryExpression { + return node.kind === SyntaxKind.PostfixUnaryExpression; +} - export function isReturnStatement(node: Node): node is ReturnStatement { - return node.kind === SyntaxKind.ReturnStatement; - } +export function isBinaryExpression(node: Node): node is BinaryExpression { + return node.kind === SyntaxKind.BinaryExpression; +} - export function isWithStatement(node: Node): node is WithStatement { - return node.kind === SyntaxKind.WithStatement; - } +export function isConditionalExpression(node: Node): node is ConditionalExpression { + return node.kind === SyntaxKind.ConditionalExpression; +} - export function isSwitchStatement(node: Node): node is SwitchStatement { - return node.kind === SyntaxKind.SwitchStatement; - } +export function isTemplateExpression(node: Node): node is TemplateExpression { + return node.kind === SyntaxKind.TemplateExpression; +} - export function isLabeledStatement(node: Node): node is LabeledStatement { - return node.kind === SyntaxKind.LabeledStatement; - } +export function isYieldExpression(node: Node): node is YieldExpression { + return node.kind === SyntaxKind.YieldExpression; +} - export function isThrowStatement(node: Node): node is ThrowStatement { - return node.kind === SyntaxKind.ThrowStatement; - } +export function isSpreadElement(node: Node): node is SpreadElement { + return node.kind === SyntaxKind.SpreadElement; +} - export function isTryStatement(node: Node): node is TryStatement { - return node.kind === SyntaxKind.TryStatement; - } +export function isClassExpression(node: Node): node is ClassExpression { + return node.kind === SyntaxKind.ClassExpression; +} - export function isDebuggerStatement(node: Node): node is DebuggerStatement { - return node.kind === SyntaxKind.DebuggerStatement; - } +export function isOmittedExpression(node: Node): node is OmittedExpression { + return node.kind === SyntaxKind.OmittedExpression; +} - export function isVariableDeclaration(node: Node): node is VariableDeclaration { - return node.kind === SyntaxKind.VariableDeclaration; - } +export function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments { + return node.kind === SyntaxKind.ExpressionWithTypeArguments; +} - export function isVariableDeclarationList(node: Node): node is VariableDeclarationList { - return node.kind === SyntaxKind.VariableDeclarationList; - } +export function isAsExpression(node: Node): node is AsExpression { + return node.kind === SyntaxKind.AsExpression; +} - export function isFunctionDeclaration(node: Node): node is FunctionDeclaration { - return node.kind === SyntaxKind.FunctionDeclaration; - } +export function isSatisfiesExpression(node: Node): node is SatisfiesExpression { + return node.kind === SyntaxKind.SatisfiesExpression; +} - export function isClassDeclaration(node: Node): node is ClassDeclaration { - return node.kind === SyntaxKind.ClassDeclaration; - } +export function isNonNullExpression(node: Node): node is NonNullExpression { + return node.kind === SyntaxKind.NonNullExpression; +} - export function isInterfaceDeclaration(node: Node): node is InterfaceDeclaration { - return node.kind === SyntaxKind.InterfaceDeclaration; - } +export function isMetaProperty(node: Node): node is MetaProperty { + return node.kind === SyntaxKind.MetaProperty; +} - export function isTypeAliasDeclaration(node: Node): node is TypeAliasDeclaration { - return node.kind === SyntaxKind.TypeAliasDeclaration; - } +export function isSyntheticExpression(node: Node): node is SyntheticExpression { + return node.kind === SyntaxKind.SyntheticExpression; +} - export function isEnumDeclaration(node: Node): node is EnumDeclaration { - return node.kind === SyntaxKind.EnumDeclaration; - } +export function isPartiallyEmittedExpression(node: Node): node is PartiallyEmittedExpression { + return node.kind === SyntaxKind.PartiallyEmittedExpression; +} - export function isModuleDeclaration(node: Node): node is ModuleDeclaration { - return node.kind === SyntaxKind.ModuleDeclaration; - } +export function isCommaListExpression(node: Node): node is CommaListExpression { + return node.kind === SyntaxKind.CommaListExpression; +} - export function isModuleBlock(node: Node): node is ModuleBlock { - return node.kind === SyntaxKind.ModuleBlock; - } +// Misc - export function isCaseBlock(node: Node): node is CaseBlock { - return node.kind === SyntaxKind.CaseBlock; - } +export function isTemplateSpan(node: Node): node is TemplateSpan { + return node.kind === SyntaxKind.TemplateSpan; +} - export function isNamespaceExportDeclaration(node: Node): node is NamespaceExportDeclaration { - return node.kind === SyntaxKind.NamespaceExportDeclaration; - } +export function isSemicolonClassElement(node: Node): node is SemicolonClassElement { + return node.kind === SyntaxKind.SemicolonClassElement; +} - export function isImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration { - return node.kind === SyntaxKind.ImportEqualsDeclaration; - } +// Elements - export function isImportDeclaration(node: Node): node is ImportDeclaration { - return node.kind === SyntaxKind.ImportDeclaration; - } +export function isBlock(node: Node): node is Block { + return node.kind === SyntaxKind.Block; +} - export function isImportClause(node: Node): node is ImportClause { - return node.kind === SyntaxKind.ImportClause; - } +export function isVariableStatement(node: Node): node is VariableStatement { + return node.kind === SyntaxKind.VariableStatement; +} - export function isImportTypeAssertionContainer(node: Node): node is ImportTypeAssertionContainer { - return node.kind === SyntaxKind.ImportTypeAssertionContainer; - } +export function isEmptyStatement(node: Node): node is EmptyStatement { + return node.kind === SyntaxKind.EmptyStatement; +} - export function isAssertClause(node: Node): node is AssertClause { - return node.kind === SyntaxKind.AssertClause; - } +export function isExpressionStatement(node: Node): node is ExpressionStatement { + return node.kind === SyntaxKind.ExpressionStatement; +} - export function isAssertEntry(node: Node): node is AssertEntry { - return node.kind === SyntaxKind.AssertEntry; - } +export function isIfStatement(node: Node): node is IfStatement { + return node.kind === SyntaxKind.IfStatement; +} - export function isNamespaceImport(node: Node): node is NamespaceImport { - return node.kind === SyntaxKind.NamespaceImport; - } +export function isDoStatement(node: Node): node is DoStatement { + return node.kind === SyntaxKind.DoStatement; +} - export function isNamespaceExport(node: Node): node is NamespaceExport { - return node.kind === SyntaxKind.NamespaceExport; - } +export function isWhileStatement(node: Node): node is WhileStatement { + return node.kind === SyntaxKind.WhileStatement; +} - export function isNamedImports(node: Node): node is NamedImports { - return node.kind === SyntaxKind.NamedImports; - } +export function isForStatement(node: Node): node is ForStatement { + return node.kind === SyntaxKind.ForStatement; +} - export function isImportSpecifier(node: Node): node is ImportSpecifier { - return node.kind === SyntaxKind.ImportSpecifier; - } +export function isForInStatement(node: Node): node is ForInStatement { + return node.kind === SyntaxKind.ForInStatement; +} - export function isExportAssignment(node: Node): node is ExportAssignment { - return node.kind === SyntaxKind.ExportAssignment; - } +export function isForOfStatement(node: Node): node is ForOfStatement { + return node.kind === SyntaxKind.ForOfStatement; +} - export function isExportDeclaration(node: Node): node is ExportDeclaration { - return node.kind === SyntaxKind.ExportDeclaration; - } +export function isContinueStatement(node: Node): node is ContinueStatement { + return node.kind === SyntaxKind.ContinueStatement; +} - export function isNamedExports(node: Node): node is NamedExports { - return node.kind === SyntaxKind.NamedExports; - } +export function isBreakStatement(node: Node): node is BreakStatement { + return node.kind === SyntaxKind.BreakStatement; +} - export function isExportSpecifier(node: Node): node is ExportSpecifier { - return node.kind === SyntaxKind.ExportSpecifier; - } +export function isReturnStatement(node: Node): node is ReturnStatement { + return node.kind === SyntaxKind.ReturnStatement; +} - export function isMissingDeclaration(node: Node): node is MissingDeclaration { - return node.kind === SyntaxKind.MissingDeclaration; - } +export function isWithStatement(node: Node): node is WithStatement { + return node.kind === SyntaxKind.WithStatement; +} - export function isNotEmittedStatement(node: Node): node is NotEmittedStatement { - return node.kind === SyntaxKind.NotEmittedStatement; - } +export function isSwitchStatement(node: Node): node is SwitchStatement { + return node.kind === SyntaxKind.SwitchStatement; +} - /* @internal */ - export function isSyntheticReference(node: Node): node is SyntheticReferenceExpression { - return node.kind === SyntaxKind.SyntheticReferenceExpression; - } +export function isLabeledStatement(node: Node): node is LabeledStatement { + return node.kind === SyntaxKind.LabeledStatement; +} - /* @internal */ - export function isMergeDeclarationMarker(node: Node): node is MergeDeclarationMarker { - return node.kind === SyntaxKind.MergeDeclarationMarker; - } +export function isThrowStatement(node: Node): node is ThrowStatement { + return node.kind === SyntaxKind.ThrowStatement; +} - /* @internal */ - export function isEndOfDeclarationMarker(node: Node): node is EndOfDeclarationMarker { - return node.kind === SyntaxKind.EndOfDeclarationMarker; - } +export function isTryStatement(node: Node): node is TryStatement { + return node.kind === SyntaxKind.TryStatement; +} - // Module References +export function isDebuggerStatement(node: Node): node is DebuggerStatement { + return node.kind === SyntaxKind.DebuggerStatement; +} - export function isExternalModuleReference(node: Node): node is ExternalModuleReference { - return node.kind === SyntaxKind.ExternalModuleReference; - } +export function isVariableDeclaration(node: Node): node is VariableDeclaration { + return node.kind === SyntaxKind.VariableDeclaration; +} - // JSX +export function isVariableDeclarationList(node: Node): node is VariableDeclarationList { + return node.kind === SyntaxKind.VariableDeclarationList; +} - export function isJsxElement(node: Node): node is JsxElement { - return node.kind === SyntaxKind.JsxElement; - } +export function isFunctionDeclaration(node: Node): node is FunctionDeclaration { + return node.kind === SyntaxKind.FunctionDeclaration; +} - export function isJsxSelfClosingElement(node: Node): node is JsxSelfClosingElement { - return node.kind === SyntaxKind.JsxSelfClosingElement; - } +export function isClassDeclaration(node: Node): node is ClassDeclaration { + return node.kind === SyntaxKind.ClassDeclaration; +} - export function isJsxOpeningElement(node: Node): node is JsxOpeningElement { - return node.kind === SyntaxKind.JsxOpeningElement; - } +export function isInterfaceDeclaration(node: Node): node is InterfaceDeclaration { + return node.kind === SyntaxKind.InterfaceDeclaration; +} - export function isJsxClosingElement(node: Node): node is JsxClosingElement { - return node.kind === SyntaxKind.JsxClosingElement; - } +export function isTypeAliasDeclaration(node: Node): node is TypeAliasDeclaration { + return node.kind === SyntaxKind.TypeAliasDeclaration; +} - export function isJsxFragment(node: Node): node is JsxFragment { - return node.kind === SyntaxKind.JsxFragment; - } +export function isEnumDeclaration(node: Node): node is EnumDeclaration { + return node.kind === SyntaxKind.EnumDeclaration; +} - export function isJsxOpeningFragment(node: Node): node is JsxOpeningFragment { - return node.kind === SyntaxKind.JsxOpeningFragment; - } +export function isModuleDeclaration(node: Node): node is ModuleDeclaration { + return node.kind === SyntaxKind.ModuleDeclaration; +} - export function isJsxClosingFragment(node: Node): node is JsxClosingFragment { - return node.kind === SyntaxKind.JsxClosingFragment; - } +export function isModuleBlock(node: Node): node is ModuleBlock { + return node.kind === SyntaxKind.ModuleBlock; +} - export function isJsxAttribute(node: Node): node is JsxAttribute { - return node.kind === SyntaxKind.JsxAttribute; - } +export function isCaseBlock(node: Node): node is CaseBlock { + return node.kind === SyntaxKind.CaseBlock; +} - export function isJsxAttributes(node: Node): node is JsxAttributes { - return node.kind === SyntaxKind.JsxAttributes; - } +export function isNamespaceExportDeclaration(node: Node): node is NamespaceExportDeclaration { + return node.kind === SyntaxKind.NamespaceExportDeclaration; +} - export function isJsxSpreadAttribute(node: Node): node is JsxSpreadAttribute { - return node.kind === SyntaxKind.JsxSpreadAttribute; - } +export function isImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration { + return node.kind === SyntaxKind.ImportEqualsDeclaration; +} - export function isJsxExpression(node: Node): node is JsxExpression { - return node.kind === SyntaxKind.JsxExpression; - } +export function isImportDeclaration(node: Node): node is ImportDeclaration { + return node.kind === SyntaxKind.ImportDeclaration; +} - // Clauses +export function isImportClause(node: Node): node is ImportClause { + return node.kind === SyntaxKind.ImportClause; +} - export function isCaseClause(node: Node): node is CaseClause { - return node.kind === SyntaxKind.CaseClause; - } +export function isImportTypeAssertionContainer(node: Node): node is ImportTypeAssertionContainer { + return node.kind === SyntaxKind.ImportTypeAssertionContainer; +} - export function isDefaultClause(node: Node): node is DefaultClause { - return node.kind === SyntaxKind.DefaultClause; - } +export function isAssertClause(node: Node): node is AssertClause { + return node.kind === SyntaxKind.AssertClause; +} - export function isHeritageClause(node: Node): node is HeritageClause { - return node.kind === SyntaxKind.HeritageClause; - } +export function isAssertEntry(node: Node): node is AssertEntry { + return node.kind === SyntaxKind.AssertEntry; +} - export function isCatchClause(node: Node): node is CatchClause { - return node.kind === SyntaxKind.CatchClause; - } +export function isNamespaceImport(node: Node): node is NamespaceImport { + return node.kind === SyntaxKind.NamespaceImport; +} - // Property assignments +export function isNamespaceExport(node: Node): node is NamespaceExport { + return node.kind === SyntaxKind.NamespaceExport; +} - export function isPropertyAssignment(node: Node): node is PropertyAssignment { - return node.kind === SyntaxKind.PropertyAssignment; - } +export function isNamedImports(node: Node): node is NamedImports { + return node.kind === SyntaxKind.NamedImports; +} - export function isShorthandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { - return node.kind === SyntaxKind.ShorthandPropertyAssignment; - } +export function isImportSpecifier(node: Node): node is ImportSpecifier { + return node.kind === SyntaxKind.ImportSpecifier; +} - export function isSpreadAssignment(node: Node): node is SpreadAssignment { - return node.kind === SyntaxKind.SpreadAssignment; - } +export function isExportAssignment(node: Node): node is ExportAssignment { + return node.kind === SyntaxKind.ExportAssignment; +} - // Enum +export function isExportDeclaration(node: Node): node is ExportDeclaration { + return node.kind === SyntaxKind.ExportDeclaration; +} - export function isEnumMember(node: Node): node is EnumMember { - return node.kind === SyntaxKind.EnumMember; - } +export function isNamedExports(node: Node): node is NamedExports { + return node.kind === SyntaxKind.NamedExports; +} - // Unparsed +export function isExportSpecifier(node: Node): node is ExportSpecifier { + return node.kind === SyntaxKind.ExportSpecifier; +} - // TODO(rbuckton): isUnparsedPrologue +export function isMissingDeclaration(node: Node): node is MissingDeclaration { + return node.kind === SyntaxKind.MissingDeclaration; +} - export function isUnparsedPrepend(node: Node): node is UnparsedPrepend { - return node.kind === SyntaxKind.UnparsedPrepend; - } +export function isNotEmittedStatement(node: Node): node is NotEmittedStatement { + return node.kind === SyntaxKind.NotEmittedStatement; +} - // TODO(rbuckton): isUnparsedText - // TODO(rbuckton): isUnparsedInternalText - // TODO(rbuckton): isUnparsedSyntheticReference +/** @internal */ +export function isSyntheticReference(node: Node): node is SyntheticReferenceExpression { + return node.kind === SyntaxKind.SyntheticReferenceExpression; +} - // Top-level nodes - export function isSourceFile(node: Node): node is SourceFile { - return node.kind === SyntaxKind.SourceFile; - } +/** @internal */ +export function isMergeDeclarationMarker(node: Node): node is MergeDeclarationMarker { + return node.kind === SyntaxKind.MergeDeclarationMarker; +} - export function isBundle(node: Node): node is Bundle { - return node.kind === SyntaxKind.Bundle; - } +/** @internal */ +export function isEndOfDeclarationMarker(node: Node): node is EndOfDeclarationMarker { + return node.kind === SyntaxKind.EndOfDeclarationMarker; +} - export function isUnparsedSource(node: Node): node is UnparsedSource { - return node.kind === SyntaxKind.UnparsedSource; - } +// Module References - // TODO(rbuckton): isInputFiles +export function isExternalModuleReference(node: Node): node is ExternalModuleReference { + return node.kind === SyntaxKind.ExternalModuleReference; +} - // JSDoc Elements +// JSX - export function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression { - return node.kind === SyntaxKind.JSDocTypeExpression; - } +export function isJsxElement(node: Node): node is JsxElement { + return node.kind === SyntaxKind.JsxElement; +} - export function isJSDocNameReference(node: Node): node is JSDocNameReference { - return node.kind === SyntaxKind.JSDocNameReference; - } +export function isJsxSelfClosingElement(node: Node): node is JsxSelfClosingElement { + return node.kind === SyntaxKind.JsxSelfClosingElement; +} - export function isJSDocMemberName(node: Node): node is JSDocMemberName { - return node.kind === SyntaxKind.JSDocMemberName; - } +export function isJsxOpeningElement(node: Node): node is JsxOpeningElement { + return node.kind === SyntaxKind.JsxOpeningElement; +} - export function isJSDocLink(node: Node): node is JSDocLink { - return node.kind === SyntaxKind.JSDocLink; - } +export function isJsxClosingElement(node: Node): node is JsxClosingElement { + return node.kind === SyntaxKind.JsxClosingElement; +} - export function isJSDocLinkCode(node: Node): node is JSDocLinkCode { - return node.kind === SyntaxKind.JSDocLinkCode; - } +export function isJsxFragment(node: Node): node is JsxFragment { + return node.kind === SyntaxKind.JsxFragment; +} - export function isJSDocLinkPlain(node: Node): node is JSDocLinkPlain { - return node.kind === SyntaxKind.JSDocLinkPlain; - } +export function isJsxOpeningFragment(node: Node): node is JsxOpeningFragment { + return node.kind === SyntaxKind.JsxOpeningFragment; +} - export function isJSDocAllType(node: Node): node is JSDocAllType { - return node.kind === SyntaxKind.JSDocAllType; - } +export function isJsxClosingFragment(node: Node): node is JsxClosingFragment { + return node.kind === SyntaxKind.JsxClosingFragment; +} - export function isJSDocUnknownType(node: Node): node is JSDocUnknownType { - return node.kind === SyntaxKind.JSDocUnknownType; - } +export function isJsxAttribute(node: Node): node is JsxAttribute { + return node.kind === SyntaxKind.JsxAttribute; +} - export function isJSDocNullableType(node: Node): node is JSDocNullableType { - return node.kind === SyntaxKind.JSDocNullableType; - } +export function isJsxAttributes(node: Node): node is JsxAttributes { + return node.kind === SyntaxKind.JsxAttributes; +} - export function isJSDocNonNullableType(node: Node): node is JSDocNonNullableType { - return node.kind === SyntaxKind.JSDocNonNullableType; - } +export function isJsxSpreadAttribute(node: Node): node is JsxSpreadAttribute { + return node.kind === SyntaxKind.JsxSpreadAttribute; +} - export function isJSDocOptionalType(node: Node): node is JSDocOptionalType { - return node.kind === SyntaxKind.JSDocOptionalType; - } +export function isJsxExpression(node: Node): node is JsxExpression { + return node.kind === SyntaxKind.JsxExpression; +} - export function isJSDocFunctionType(node: Node): node is JSDocFunctionType { - return node.kind === SyntaxKind.JSDocFunctionType; - } +// Clauses - export function isJSDocVariadicType(node: Node): node is JSDocVariadicType { - return node.kind === SyntaxKind.JSDocVariadicType; - } +export function isCaseClause(node: Node): node is CaseClause { + return node.kind === SyntaxKind.CaseClause; +} - export function isJSDocNamepathType(node: Node): node is JSDocNamepathType { - return node.kind === SyntaxKind.JSDocNamepathType; - } +export function isDefaultClause(node: Node): node is DefaultClause { + return node.kind === SyntaxKind.DefaultClause; +} - export function isJSDoc(node: Node): node is JSDoc { - return node.kind === SyntaxKind.JSDoc; - } +export function isHeritageClause(node: Node): node is HeritageClause { + return node.kind === SyntaxKind.HeritageClause; +} - export function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral { - return node.kind === SyntaxKind.JSDocTypeLiteral; - } +export function isCatchClause(node: Node): node is CatchClause { + return node.kind === SyntaxKind.CatchClause; +} - export function isJSDocSignature(node: Node): node is JSDocSignature { - return node.kind === SyntaxKind.JSDocSignature; - } +// Property assignments - // JSDoc Tags +export function isPropertyAssignment(node: Node): node is PropertyAssignment { + return node.kind === SyntaxKind.PropertyAssignment; +} - export function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag { - return node.kind === SyntaxKind.JSDocAugmentsTag; - } +export function isShorthandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { + return node.kind === SyntaxKind.ShorthandPropertyAssignment; +} - export function isJSDocAuthorTag(node: Node): node is JSDocAuthorTag { - return node.kind === SyntaxKind.JSDocAuthorTag; - } +export function isSpreadAssignment(node: Node): node is SpreadAssignment { + return node.kind === SyntaxKind.SpreadAssignment; +} - export function isJSDocClassTag(node: Node): node is JSDocClassTag { - return node.kind === SyntaxKind.JSDocClassTag; - } +// Enum - export function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag { - return node.kind === SyntaxKind.JSDocCallbackTag; - } +export function isEnumMember(node: Node): node is EnumMember { + return node.kind === SyntaxKind.EnumMember; +} - export function isJSDocPublicTag(node: Node): node is JSDocPublicTag { - return node.kind === SyntaxKind.JSDocPublicTag; - } +// Unparsed - export function isJSDocPrivateTag(node: Node): node is JSDocPrivateTag { - return node.kind === SyntaxKind.JSDocPrivateTag; - } +// TODO(rbuckton): isUnparsedPrologue - export function isJSDocProtectedTag(node: Node): node is JSDocProtectedTag { - return node.kind === SyntaxKind.JSDocProtectedTag; - } +export function isUnparsedPrepend(node: Node): node is UnparsedPrepend { + return node.kind === SyntaxKind.UnparsedPrepend; +} - export function isJSDocReadonlyTag(node: Node): node is JSDocReadonlyTag { - return node.kind === SyntaxKind.JSDocReadonlyTag; - } +// TODO(rbuckton): isUnparsedText +// TODO(rbuckton): isUnparsedInternalText +// TODO(rbuckton): isUnparsedSyntheticReference - export function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag { - return node.kind === SyntaxKind.JSDocOverrideTag; - } +// Top-level nodes +export function isSourceFile(node: Node): node is SourceFile { + return node.kind === SyntaxKind.SourceFile; +} - export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag { - return node.kind === SyntaxKind.JSDocDeprecatedTag; - } +export function isBundle(node: Node): node is Bundle { + return node.kind === SyntaxKind.Bundle; +} - export function isJSDocSeeTag(node: Node): node is JSDocSeeTag { - return node.kind === SyntaxKind.JSDocSeeTag; - } +export function isUnparsedSource(node: Node): node is UnparsedSource { + return node.kind === SyntaxKind.UnparsedSource; +} - export function isJSDocEnumTag(node: Node): node is JSDocEnumTag { - return node.kind === SyntaxKind.JSDocEnumTag; - } +// TODO(rbuckton): isInputFiles - export function isJSDocParameterTag(node: Node): node is JSDocParameterTag { - return node.kind === SyntaxKind.JSDocParameterTag; - } +// JSDoc Elements - export function isJSDocReturnTag(node: Node): node is JSDocReturnTag { - return node.kind === SyntaxKind.JSDocReturnTag; - } +export function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression { + return node.kind === SyntaxKind.JSDocTypeExpression; +} - export function isJSDocThisTag(node: Node): node is JSDocThisTag { - return node.kind === SyntaxKind.JSDocThisTag; - } +export function isJSDocNameReference(node: Node): node is JSDocNameReference { + return node.kind === SyntaxKind.JSDocNameReference; +} - export function isJSDocTypeTag(node: Node): node is JSDocTypeTag { - return node.kind === SyntaxKind.JSDocTypeTag; - } +export function isJSDocMemberName(node: Node): node is JSDocMemberName { + return node.kind === SyntaxKind.JSDocMemberName; +} - export function isJSDocTemplateTag(node: Node): node is JSDocTemplateTag { - return node.kind === SyntaxKind.JSDocTemplateTag; - } +export function isJSDocLink(node: Node): node is JSDocLink { + return node.kind === SyntaxKind.JSDocLink; +} - export function isJSDocTypedefTag(node: Node): node is JSDocTypedefTag { - return node.kind === SyntaxKind.JSDocTypedefTag; - } +export function isJSDocLinkCode(node: Node): node is JSDocLinkCode { + return node.kind === SyntaxKind.JSDocLinkCode; +} - export function isJSDocUnknownTag(node: Node): node is JSDocUnknownTag { - return node.kind === SyntaxKind.JSDocTag; - } +export function isJSDocLinkPlain(node: Node): node is JSDocLinkPlain { + return node.kind === SyntaxKind.JSDocLinkPlain; +} - export function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag { - return node.kind === SyntaxKind.JSDocPropertyTag; - } +export function isJSDocAllType(node: Node): node is JSDocAllType { + return node.kind === SyntaxKind.JSDocAllType; +} - export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag { - return node.kind === SyntaxKind.JSDocImplementsTag; - } +export function isJSDocUnknownType(node: Node): node is JSDocUnknownType { + return node.kind === SyntaxKind.JSDocUnknownType; +} + +export function isJSDocNullableType(node: Node): node is JSDocNullableType { + return node.kind === SyntaxKind.JSDocNullableType; +} + +export function isJSDocNonNullableType(node: Node): node is JSDocNonNullableType { + return node.kind === SyntaxKind.JSDocNonNullableType; +} + +export function isJSDocOptionalType(node: Node): node is JSDocOptionalType { + return node.kind === SyntaxKind.JSDocOptionalType; +} + +export function isJSDocFunctionType(node: Node): node is JSDocFunctionType { + return node.kind === SyntaxKind.JSDocFunctionType; +} + +export function isJSDocVariadicType(node: Node): node is JSDocVariadicType { + return node.kind === SyntaxKind.JSDocVariadicType; +} + +export function isJSDocNamepathType(node: Node): node is JSDocNamepathType { + return node.kind === SyntaxKind.JSDocNamepathType; +} + +export function isJSDoc(node: Node): node is JSDoc { + return node.kind === SyntaxKind.JSDoc; +} + +export function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral { + return node.kind === SyntaxKind.JSDocTypeLiteral; +} + +export function isJSDocSignature(node: Node): node is JSDocSignature { + return node.kind === SyntaxKind.JSDocSignature; +} + +// JSDoc Tags + +export function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag { + return node.kind === SyntaxKind.JSDocAugmentsTag; +} + +export function isJSDocAuthorTag(node: Node): node is JSDocAuthorTag { + return node.kind === SyntaxKind.JSDocAuthorTag; +} + +export function isJSDocClassTag(node: Node): node is JSDocClassTag { + return node.kind === SyntaxKind.JSDocClassTag; +} + +export function isJSDocCallbackTag(node: Node): node is JSDocCallbackTag { + return node.kind === SyntaxKind.JSDocCallbackTag; +} + +export function isJSDocPublicTag(node: Node): node is JSDocPublicTag { + return node.kind === SyntaxKind.JSDocPublicTag; +} + +export function isJSDocPrivateTag(node: Node): node is JSDocPrivateTag { + return node.kind === SyntaxKind.JSDocPrivateTag; +} + +export function isJSDocProtectedTag(node: Node): node is JSDocProtectedTag { + return node.kind === SyntaxKind.JSDocProtectedTag; +} + +export function isJSDocReadonlyTag(node: Node): node is JSDocReadonlyTag { + return node.kind === SyntaxKind.JSDocReadonlyTag; +} + +export function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag { + return node.kind === SyntaxKind.JSDocOverrideTag; +} + +export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag { + return node.kind === SyntaxKind.JSDocDeprecatedTag; +} + +export function isJSDocSeeTag(node: Node): node is JSDocSeeTag { + return node.kind === SyntaxKind.JSDocSeeTag; +} + +export function isJSDocEnumTag(node: Node): node is JSDocEnumTag { + return node.kind === SyntaxKind.JSDocEnumTag; +} + +export function isJSDocParameterTag(node: Node): node is JSDocParameterTag { + return node.kind === SyntaxKind.JSDocParameterTag; +} + +export function isJSDocReturnTag(node: Node): node is JSDocReturnTag { + return node.kind === SyntaxKind.JSDocReturnTag; +} + +export function isJSDocThisTag(node: Node): node is JSDocThisTag { + return node.kind === SyntaxKind.JSDocThisTag; +} + +export function isJSDocTypeTag(node: Node): node is JSDocTypeTag { + return node.kind === SyntaxKind.JSDocTypeTag; +} + +export function isJSDocTemplateTag(node: Node): node is JSDocTemplateTag { + return node.kind === SyntaxKind.JSDocTemplateTag; +} + +export function isJSDocTypedefTag(node: Node): node is JSDocTypedefTag { + return node.kind === SyntaxKind.JSDocTypedefTag; +} + +export function isJSDocUnknownTag(node: Node): node is JSDocUnknownTag { + return node.kind === SyntaxKind.JSDocTag; +} + +export function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag { + return node.kind === SyntaxKind.JSDocPropertyTag; +} + +export function isJSDocImplementsTag(node: Node): node is JSDocImplementsTag { + return node.kind === SyntaxKind.JSDocImplementsTag; +} - // Synthesized list +// Synthesized list - /* @internal */ - export function isSyntaxList(n: Node): n is SyntaxList { - return n.kind === SyntaxKind.SyntaxList; - } +/** @internal */ +export function isSyntaxList(n: Node): n is SyntaxList { + return n.kind === SyntaxKind.SyntaxList; } diff --git a/src/compiler/factory/parenthesizerRules.ts b/src/compiler/factory/parenthesizerRules.ts index 21422f2bc6fef..78abbbc097563 100644 --- a/src/compiler/factory/parenthesizerRules.ts +++ b/src/compiler/factory/parenthesizerRules.ts @@ -1,637 +1,648 @@ -/* @internal */ -namespace ts { - export function createParenthesizerRules(factory: NodeFactory): ParenthesizerRules { - interface BinaryPlusExpression extends BinaryExpression { - cachedLiteralKind: SyntaxKind; - } +import { + Associativity, BinaryExpression, BinaryOperator, cast, compareValues, Comparison, ConciseBody, ESMap, Expression, + getExpressionAssociativity, getExpressionPrecedence, getLeftmostExpression, getOperatorAssociativity, + getOperatorPrecedence, identity, isBinaryExpression, isBlock, isCallExpression, isCommaSequence, + isConditionalTypeNode, isConstructorTypeNode, isFunctionOrConstructorTypeNode, isFunctionTypeNode, isInferTypeNode, + isIntersectionTypeNode, isJSDocNullableType, isLeftHandSideExpression, isLiteralKind, isNamedTupleMember, + isNodeArray, isOptionalChain, isTypeOperatorNode, isUnaryExpression, isUnionTypeNode, last, LeftHandSideExpression, + Map, NamedTupleMember, NewExpression, NodeArray, NodeFactory, OperatorPrecedence, OuterExpressionKinds, + ParenthesizerRules, sameMap, setTextRange, skipPartiallyEmittedExpressions, some, SyntaxKind, TypeNode, + UnaryExpression, +} from "../_namespaces/ts"; + +/** @internal */ +export function createParenthesizerRules(factory: NodeFactory): ParenthesizerRules { + interface BinaryPlusExpression extends BinaryExpression { + cachedLiteralKind: SyntaxKind; + } - let binaryLeftOperandParenthesizerCache: ESMap Expression> | undefined; - let binaryRightOperandParenthesizerCache: ESMap Expression> | undefined; - - return { - getParenthesizeLeftSideOfBinaryForOperator, - getParenthesizeRightSideOfBinaryForOperator, - parenthesizeLeftSideOfBinary, - parenthesizeRightSideOfBinary, - parenthesizeExpressionOfComputedPropertyName, - parenthesizeConditionOfConditionalExpression, - parenthesizeBranchOfConditionalExpression, - parenthesizeExpressionOfExportDefault, - parenthesizeExpressionOfNew, - parenthesizeLeftSideOfAccess, - parenthesizeOperandOfPostfixUnary, - parenthesizeOperandOfPrefixUnary, - parenthesizeExpressionsOfCommaDelimitedList, - parenthesizeExpressionForDisallowedComma, - parenthesizeExpressionOfExpressionStatement, - parenthesizeConciseBodyOfArrowFunction, - parenthesizeCheckTypeOfConditionalType, - parenthesizeExtendsTypeOfConditionalType, - parenthesizeConstituentTypesOfUnionType, - parenthesizeConstituentTypeOfUnionType, - parenthesizeConstituentTypesOfIntersectionType, - parenthesizeConstituentTypeOfIntersectionType, - parenthesizeOperandOfTypeOperator, - parenthesizeOperandOfReadonlyTypeOperator, - parenthesizeNonArrayTypeOfPostfixType, - parenthesizeElementTypesOfTupleType, - parenthesizeElementTypeOfTupleType, - parenthesizeTypeOfOptionalType, - parenthesizeTypeArguments, - parenthesizeLeadingTypeArgument, - }; - - function getParenthesizeLeftSideOfBinaryForOperator(operatorKind: BinaryOperator) { - binaryLeftOperandParenthesizerCache ||= new Map(); - let parenthesizerRule = binaryLeftOperandParenthesizerCache.get(operatorKind); - if (!parenthesizerRule) { - parenthesizerRule = node => parenthesizeLeftSideOfBinary(operatorKind, node); - binaryLeftOperandParenthesizerCache.set(operatorKind, parenthesizerRule); - } - return parenthesizerRule; + let binaryLeftOperandParenthesizerCache: ESMap Expression> | undefined; + let binaryRightOperandParenthesizerCache: ESMap Expression> | undefined; + + return { + getParenthesizeLeftSideOfBinaryForOperator, + getParenthesizeRightSideOfBinaryForOperator, + parenthesizeLeftSideOfBinary, + parenthesizeRightSideOfBinary, + parenthesizeExpressionOfComputedPropertyName, + parenthesizeConditionOfConditionalExpression, + parenthesizeBranchOfConditionalExpression, + parenthesizeExpressionOfExportDefault, + parenthesizeExpressionOfNew, + parenthesizeLeftSideOfAccess, + parenthesizeOperandOfPostfixUnary, + parenthesizeOperandOfPrefixUnary, + parenthesizeExpressionsOfCommaDelimitedList, + parenthesizeExpressionForDisallowedComma, + parenthesizeExpressionOfExpressionStatement, + parenthesizeConciseBodyOfArrowFunction, + parenthesizeCheckTypeOfConditionalType, + parenthesizeExtendsTypeOfConditionalType, + parenthesizeConstituentTypesOfUnionType, + parenthesizeConstituentTypeOfUnionType, + parenthesizeConstituentTypesOfIntersectionType, + parenthesizeConstituentTypeOfIntersectionType, + parenthesizeOperandOfTypeOperator, + parenthesizeOperandOfReadonlyTypeOperator, + parenthesizeNonArrayTypeOfPostfixType, + parenthesizeElementTypesOfTupleType, + parenthesizeElementTypeOfTupleType, + parenthesizeTypeOfOptionalType, + parenthesizeTypeArguments, + parenthesizeLeadingTypeArgument, + }; + + function getParenthesizeLeftSideOfBinaryForOperator(operatorKind: BinaryOperator) { + binaryLeftOperandParenthesizerCache ||= new Map(); + let parenthesizerRule = binaryLeftOperandParenthesizerCache.get(operatorKind); + if (!parenthesizerRule) { + parenthesizerRule = node => parenthesizeLeftSideOfBinary(operatorKind, node); + binaryLeftOperandParenthesizerCache.set(operatorKind, parenthesizerRule); } + return parenthesizerRule; + } - function getParenthesizeRightSideOfBinaryForOperator(operatorKind: BinaryOperator) { - binaryRightOperandParenthesizerCache ||= new Map(); - let parenthesizerRule = binaryRightOperandParenthesizerCache.get(operatorKind); - if (!parenthesizerRule) { - parenthesizerRule = node => parenthesizeRightSideOfBinary(operatorKind, /*leftSide*/ undefined, node); - binaryRightOperandParenthesizerCache.set(operatorKind, parenthesizerRule); - } - return parenthesizerRule; + function getParenthesizeRightSideOfBinaryForOperator(operatorKind: BinaryOperator) { + binaryRightOperandParenthesizerCache ||= new Map(); + let parenthesizerRule = binaryRightOperandParenthesizerCache.get(operatorKind); + if (!parenthesizerRule) { + parenthesizerRule = node => parenthesizeRightSideOfBinary(operatorKind, /*leftSide*/ undefined, node); + binaryRightOperandParenthesizerCache.set(operatorKind, parenthesizerRule); } + return parenthesizerRule; + } - /** - * Determines whether the operand to a BinaryExpression needs to be parenthesized. - * - * @param binaryOperator The operator for the BinaryExpression. - * @param operand The operand for the BinaryExpression. - * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the - * BinaryExpression. - */ - function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression | undefined) { - // If the operand has lower precedence, then it needs to be parenthesized to preserve the - // intent of the expression. For example, if the operand is `a + b` and the operator is - // `*`, then we need to parenthesize the operand to preserve the intended order of - // operations: `(a + b) * x`. - // - // If the operand has higher precedence, then it does not need to be parenthesized. For - // example, if the operand is `a * b` and the operator is `+`, then we do not need to - // parenthesize to preserve the intended order of operations: `a * b + x`. - // - // If the operand has the same precedence, then we need to check the associativity of - // the operator based on whether this is the left or right operand of the expression. - // - // For example, if `a / d` is on the right of operator `*`, we need to parenthesize - // to preserve the intended order of operations: `x * (a / d)` - // - // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve - // the intended order of operations: `(a ** b) ** c` - const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); - const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); - const emittedOperand = skipPartiallyEmittedExpressions(operand); - if (!isLeftSideOfBinary && operand.kind === SyntaxKind.ArrowFunction && binaryOperatorPrecedence > OperatorPrecedence.Assignment) { - // We need to parenthesize arrow functions on the right side to avoid it being - // parsed as parenthesized expression: `a && (() => {})` - return true; - } - const operandPrecedence = getExpressionPrecedence(emittedOperand); - switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { - case Comparison.LessThan: - // If the operand is the right side of a right-associative binary operation - // and is a yield expression, then we do not need parentheses. - if (!isLeftSideOfBinary - && binaryOperatorAssociativity === Associativity.Right - && operand.kind === SyntaxKind.YieldExpression) { - return false; - } + /** + * Determines whether the operand to a BinaryExpression needs to be parenthesized. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression | undefined) { + // If the operand has lower precedence, then it needs to be parenthesized to preserve the + // intent of the expression. For example, if the operand is `a + b` and the operator is + // `*`, then we need to parenthesize the operand to preserve the intended order of + // operations: `(a + b) * x`. + // + // If the operand has higher precedence, then it does not need to be parenthesized. For + // example, if the operand is `a * b` and the operator is `+`, then we do not need to + // parenthesize to preserve the intended order of operations: `a * b + x`. + // + // If the operand has the same precedence, then we need to check the associativity of + // the operator based on whether this is the left or right operand of the expression. + // + // For example, if `a / d` is on the right of operator `*`, we need to parenthesize + // to preserve the intended order of operations: `x * (a / d)` + // + // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve + // the intended order of operations: `(a ** b) ** c` + const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); + const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); + const emittedOperand = skipPartiallyEmittedExpressions(operand); + if (!isLeftSideOfBinary && operand.kind === SyntaxKind.ArrowFunction && binaryOperatorPrecedence > OperatorPrecedence.Assignment) { + // We need to parenthesize arrow functions on the right side to avoid it being + // parsed as parenthesized expression: `a && (() => {})` + return true; + } + const operandPrecedence = getExpressionPrecedence(emittedOperand); + switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { + case Comparison.LessThan: + // If the operand is the right side of a right-associative binary operation + // and is a yield expression, then we do not need parentheses. + if (!isLeftSideOfBinary + && binaryOperatorAssociativity === Associativity.Right + && operand.kind === SyntaxKind.YieldExpression) { + return false; + } - return true; + return true; - case Comparison.GreaterThan: - return false; + case Comparison.GreaterThan: + return false; + + case Comparison.EqualTo: + if (isLeftSideOfBinary) { + // No need to parenthesize the left operand when the binary operator is + // left associative: + // (a*b)/x -> a*b/x + // (a**b)/x -> a**b/x + // + // Parentheses are needed for the left operand when the binary operator is + // right associative: + // (a/b)**x -> (a/b)**x + // (a**b)**x -> (a**b)**x + return binaryOperatorAssociativity === Associativity.Right; + } + else { + if (isBinaryExpression(emittedOperand) + && emittedOperand.operatorToken.kind === binaryOperator) { + // No need to parenthesize the right operand when the binary operator and + // operand are the same and one of the following: + // x*(a*b) => x*a*b + // x|(a|b) => x|a|b + // x&(a&b) => x&a&b + // x^(a^b) => x^a^b + if (operatorHasAssociativeProperty(binaryOperator)) { + return false; + } - case Comparison.EqualTo: - if (isLeftSideOfBinary) { - // No need to parenthesize the left operand when the binary operator is - // left associative: - // (a*b)/x -> a*b/x - // (a**b)/x -> a**b/x - // - // Parentheses are needed for the left operand when the binary operator is - // right associative: - // (a/b)**x -> (a/b)**x - // (a**b)**x -> (a**b)**x - return binaryOperatorAssociativity === Associativity.Right; - } - else { - if (isBinaryExpression(emittedOperand) - && emittedOperand.operatorToken.kind === binaryOperator) { - // No need to parenthesize the right operand when the binary operator and - // operand are the same and one of the following: - // x*(a*b) => x*a*b - // x|(a|b) => x|a|b - // x&(a&b) => x&a&b - // x^(a^b) => x^a^b - if (operatorHasAssociativeProperty(binaryOperator)) { + // No need to parenthesize the right operand when the binary operator + // is plus (+) if both the left and right operands consist solely of either + // literals of the same kind or binary plus (+) expressions for literals of + // the same kind (recursively). + // "a"+(1+2) => "a"+(1+2) + // "a"+("b"+"c") => "a"+"b"+"c" + if (binaryOperator === SyntaxKind.PlusToken) { + const leftKind = leftOperand ? getLiteralKindOfBinaryPlusOperand(leftOperand) : SyntaxKind.Unknown; + if (isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand(emittedOperand)) { return false; } - - // No need to parenthesize the right operand when the binary operator - // is plus (+) if both the left and right operands consist solely of either - // literals of the same kind or binary plus (+) expressions for literals of - // the same kind (recursively). - // "a"+(1+2) => "a"+(1+2) - // "a"+("b"+"c") => "a"+"b"+"c" - if (binaryOperator === SyntaxKind.PlusToken) { - const leftKind = leftOperand ? getLiteralKindOfBinaryPlusOperand(leftOperand) : SyntaxKind.Unknown; - if (isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand(emittedOperand)) { - return false; - } - } } - - // No need to parenthesize the right operand when the operand is right - // associative: - // x/(a**b) -> x/a**b - // x**(a**b) -> x**a**b - // - // Parentheses are needed for the right operand when the operand is left - // associative: - // x/(a*b) -> x/(a*b) - // x**(a/b) -> x**(a/b) - const operandAssociativity = getExpressionAssociativity(emittedOperand); - return operandAssociativity === Associativity.Left; } - } - } - /** - * Determines whether a binary operator is mathematically associative. - * - * @param binaryOperator The binary operator. - */ - function operatorHasAssociativeProperty(binaryOperator: SyntaxKind) { - // The following operators are associative in JavaScript: - // (a*b)*c -> a*(b*c) -> a*b*c - // (a|b)|c -> a|(b|c) -> a|b|c - // (a&b)&c -> a&(b&c) -> a&b&c - // (a^b)^c -> a^(b^c) -> a^b^c - // (a,b),c -> a,(b,c) -> a,b,c - // - // While addition is associative in mathematics, JavaScript's `+` is not - // guaranteed to be associative as it is overloaded with string concatenation. - return binaryOperator === SyntaxKind.AsteriskToken - || binaryOperator === SyntaxKind.BarToken - || binaryOperator === SyntaxKind.AmpersandToken - || binaryOperator === SyntaxKind.CaretToken - || binaryOperator === SyntaxKind.CommaToken; + // No need to parenthesize the right operand when the operand is right + // associative: + // x/(a**b) -> x/a**b + // x**(a**b) -> x**a**b + // + // Parentheses are needed for the right operand when the operand is left + // associative: + // x/(a*b) -> x/(a*b) + // x**(a/b) -> x**(a/b) + const operandAssociativity = getExpressionAssociativity(emittedOperand); + return operandAssociativity === Associativity.Left; + } } + } - /** - * This function determines whether an expression consists of a homogeneous set of - * literal expressions or binary plus expressions that all share the same literal kind. - * It is used to determine whether the right-hand operand of a binary plus expression can be - * emitted without parentheses. - */ - function getLiteralKindOfBinaryPlusOperand(node: Expression): SyntaxKind { - node = skipPartiallyEmittedExpressions(node); - - if (isLiteralKind(node.kind)) { - return node.kind; - } + /** + * Determines whether a binary operator is mathematically associative. + * + * @param binaryOperator The binary operator. + */ + function operatorHasAssociativeProperty(binaryOperator: SyntaxKind) { + // The following operators are associative in JavaScript: + // (a*b)*c -> a*(b*c) -> a*b*c + // (a|b)|c -> a|(b|c) -> a|b|c + // (a&b)&c -> a&(b&c) -> a&b&c + // (a^b)^c -> a^(b^c) -> a^b^c + // (a,b),c -> a,(b,c) -> a,b,c + // + // While addition is associative in mathematics, JavaScript's `+` is not + // guaranteed to be associative as it is overloaded with string concatenation. + return binaryOperator === SyntaxKind.AsteriskToken + || binaryOperator === SyntaxKind.BarToken + || binaryOperator === SyntaxKind.AmpersandToken + || binaryOperator === SyntaxKind.CaretToken + || binaryOperator === SyntaxKind.CommaToken; + } - if (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) { - if ((node as BinaryPlusExpression).cachedLiteralKind !== undefined) { - return (node as BinaryPlusExpression).cachedLiteralKind; - } + /** + * This function determines whether an expression consists of a homogeneous set of + * literal expressions or binary plus expressions that all share the same literal kind. + * It is used to determine whether the right-hand operand of a binary plus expression can be + * emitted without parentheses. + */ + function getLiteralKindOfBinaryPlusOperand(node: Expression): SyntaxKind { + node = skipPartiallyEmittedExpressions(node); - const leftKind = getLiteralKindOfBinaryPlusOperand((node as BinaryExpression).left); - const literalKind = isLiteralKind(leftKind) - && leftKind === getLiteralKindOfBinaryPlusOperand((node as BinaryExpression).right) - ? leftKind - : SyntaxKind.Unknown; + if (isLiteralKind(node.kind)) { + return node.kind; + } - (node as BinaryPlusExpression).cachedLiteralKind = literalKind; - return literalKind; + if (node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) { + if ((node as BinaryPlusExpression).cachedLiteralKind !== undefined) { + return (node as BinaryPlusExpression).cachedLiteralKind; } - return SyntaxKind.Unknown; + const leftKind = getLiteralKindOfBinaryPlusOperand((node as BinaryExpression).left); + const literalKind = isLiteralKind(leftKind) + && leftKind === getLiteralKindOfBinaryPlusOperand((node as BinaryExpression).right) + ? leftKind + : SyntaxKind.Unknown; + + (node as BinaryPlusExpression).cachedLiteralKind = literalKind; + return literalKind; } - /** - * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended - * order of operations. - * - * @param binaryOperator The operator for the BinaryExpression. - * @param operand The operand for the BinaryExpression. - * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the - * BinaryExpression. - */ - function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand?: Expression) { - const skipped = skipPartiallyEmittedExpressions(operand); - - // If the resulting expression is already parenthesized, we do not need to do any further processing. - if (skipped.kind === SyntaxKind.ParenthesizedExpression) { - return operand; - } + return SyntaxKind.Unknown; + } - return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary, leftOperand) - ? factory.createParenthesizedExpression(operand) - : operand; - } + /** + * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended + * order of operations. + * + * @param binaryOperator The operator for the BinaryExpression. + * @param operand The operand for the BinaryExpression. + * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the + * BinaryExpression. + */ + function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand?: Expression) { + const skipped = skipPartiallyEmittedExpressions(operand); + + // If the resulting expression is already parenthesized, we do not need to do any further processing. + if (skipped.kind === SyntaxKind.ParenthesizedExpression) { + return operand; + } + + return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary, leftOperand) + ? factory.createParenthesizedExpression(operand) + : operand; + } - function parenthesizeLeftSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression): Expression { - return parenthesizeBinaryOperand(binaryOperator, leftSide, /*isLeftSideOfBinary*/ true); - } + function parenthesizeLeftSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression): Expression { + return parenthesizeBinaryOperand(binaryOperator, leftSide, /*isLeftSideOfBinary*/ true); + } - function parenthesizeRightSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression | undefined, rightSide: Expression): Expression { - return parenthesizeBinaryOperand(binaryOperator, rightSide, /*isLeftSideOfBinary*/ false, leftSide); - } + function parenthesizeRightSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression | undefined, rightSide: Expression): Expression { + return parenthesizeBinaryOperand(binaryOperator, rightSide, /*isLeftSideOfBinary*/ false, leftSide); + } - function parenthesizeExpressionOfComputedPropertyName(expression: Expression): Expression { - return isCommaSequence(expression) ? factory.createParenthesizedExpression(expression) : expression; - } + function parenthesizeExpressionOfComputedPropertyName(expression: Expression): Expression { + return isCommaSequence(expression) ? factory.createParenthesizedExpression(expression) : expression; + } - function parenthesizeConditionOfConditionalExpression(condition: Expression): Expression { - const conditionalPrecedence = getOperatorPrecedence(SyntaxKind.ConditionalExpression, SyntaxKind.QuestionToken); - const emittedCondition = skipPartiallyEmittedExpressions(condition); - const conditionPrecedence = getExpressionPrecedence(emittedCondition); - if (compareValues(conditionPrecedence, conditionalPrecedence) !== Comparison.GreaterThan) { - return factory.createParenthesizedExpression(condition); - } - return condition; + function parenthesizeConditionOfConditionalExpression(condition: Expression): Expression { + const conditionalPrecedence = getOperatorPrecedence(SyntaxKind.ConditionalExpression, SyntaxKind.QuestionToken); + const emittedCondition = skipPartiallyEmittedExpressions(condition); + const conditionPrecedence = getExpressionPrecedence(emittedCondition); + if (compareValues(conditionPrecedence, conditionalPrecedence) !== Comparison.GreaterThan) { + return factory.createParenthesizedExpression(condition); } + return condition; + } - function parenthesizeBranchOfConditionalExpression(branch: Expression): Expression { - // per ES grammar both 'whenTrue' and 'whenFalse' parts of conditional expression are assignment expressions - // so in case when comma expression is introduced as a part of previous transformations - // if should be wrapped in parens since comma operator has the lowest precedence - const emittedExpression = skipPartiallyEmittedExpressions(branch); - return isCommaSequence(emittedExpression) - ? factory.createParenthesizedExpression(branch) - : branch; - } + function parenthesizeBranchOfConditionalExpression(branch: Expression): Expression { + // per ES grammar both 'whenTrue' and 'whenFalse' parts of conditional expression are assignment expressions + // so in case when comma expression is introduced as a part of previous transformations + // if should be wrapped in parens since comma operator has the lowest precedence + const emittedExpression = skipPartiallyEmittedExpressions(branch); + return isCommaSequence(emittedExpression) + ? factory.createParenthesizedExpression(branch) + : branch; + } - /** - * [Per the spec](https://tc39.github.io/ecma262/#prod-ExportDeclaration), `export default` accepts _AssigmentExpression_ but - * has a lookahead restriction for `function`, `async function`, and `class`. - * - * Basically, that means we need to parenthesize in the following cases: - * - * - BinaryExpression of CommaToken - * - CommaList (synthetic list of multiple comma expressions) - * - FunctionExpression - * - ClassExpression - */ - function parenthesizeExpressionOfExportDefault(expression: Expression): Expression { - const check = skipPartiallyEmittedExpressions(expression); - let needsParens = isCommaSequence(check); - if (!needsParens) { - switch (getLeftmostExpression(check, /*stopAtCallExpression*/ false).kind) { - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - needsParens = true; - } + /** + * [Per the spec](https://tc39.github.io/ecma262/#prod-ExportDeclaration), `export default` accepts _AssigmentExpression_ but + * has a lookahead restriction for `function`, `async function`, and `class`. + * + * Basically, that means we need to parenthesize in the following cases: + * + * - BinaryExpression of CommaToken + * - CommaList (synthetic list of multiple comma expressions) + * - FunctionExpression + * - ClassExpression + */ + function parenthesizeExpressionOfExportDefault(expression: Expression): Expression { + const check = skipPartiallyEmittedExpressions(expression); + let needsParens = isCommaSequence(check); + if (!needsParens) { + switch (getLeftmostExpression(check, /*stopAtCallExpression*/ false).kind) { + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + needsParens = true; } - return needsParens ? factory.createParenthesizedExpression(expression) : expression; } + return needsParens ? factory.createParenthesizedExpression(expression) : expression; + } - /** - * Wraps an expression in parentheses if it is needed in order to use the expression - * as the expression of a `NewExpression` node. - */ - function parenthesizeExpressionOfNew(expression: Expression): LeftHandSideExpression { - const leftmostExpr = getLeftmostExpression(expression, /*stopAtCallExpressions*/ true); - switch (leftmostExpr.kind) { - case SyntaxKind.CallExpression: - return factory.createParenthesizedExpression(expression); - - case SyntaxKind.NewExpression: - return !(leftmostExpr as NewExpression).arguments - ? factory.createParenthesizedExpression(expression) - : expression as LeftHandSideExpression; // TODO(rbuckton): Verify this assertion holds - } + /** + * Wraps an expression in parentheses if it is needed in order to use the expression + * as the expression of a `NewExpression` node. + */ + function parenthesizeExpressionOfNew(expression: Expression): LeftHandSideExpression { + const leftmostExpr = getLeftmostExpression(expression, /*stopAtCallExpressions*/ true); + switch (leftmostExpr.kind) { + case SyntaxKind.CallExpression: + return factory.createParenthesizedExpression(expression); - return parenthesizeLeftSideOfAccess(expression); + case SyntaxKind.NewExpression: + return !(leftmostExpr as NewExpression).arguments + ? factory.createParenthesizedExpression(expression) + : expression as LeftHandSideExpression; // TODO(rbuckton): Verify this assertion holds } - /** - * Wraps an expression in parentheses if it is needed in order to use the expression for - * property or element access. - */ - function parenthesizeLeftSideOfAccess(expression: Expression, optionalChain?: boolean): LeftHandSideExpression { - // isLeftHandSideExpression is almost the correct criterion for when it is not necessary - // to parenthesize the expression before a dot. The known exception is: - // - // NewExpression: - // new C.x -> not the same as (new C).x - // - const emittedExpression = skipPartiallyEmittedExpressions(expression); - if (isLeftHandSideExpression(emittedExpression) - && (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression as NewExpression).arguments) - && (optionalChain || !isOptionalChain(emittedExpression))) { - // TODO(rbuckton): Verify whether this assertion holds. - return expression as LeftHandSideExpression; - } + return parenthesizeLeftSideOfAccess(expression); + } - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return setTextRange(factory.createParenthesizedExpression(expression), expression); + /** + * Wraps an expression in parentheses if it is needed in order to use the expression for + * property or element access. + */ + function parenthesizeLeftSideOfAccess(expression: Expression, optionalChain?: boolean): LeftHandSideExpression { + // isLeftHandSideExpression is almost the correct criterion for when it is not necessary + // to parenthesize the expression before a dot. The known exception is: + // + // NewExpression: + // new C.x -> not the same as (new C).x + // + const emittedExpression = skipPartiallyEmittedExpressions(expression); + if (isLeftHandSideExpression(emittedExpression) + && (emittedExpression.kind !== SyntaxKind.NewExpression || (emittedExpression as NewExpression).arguments) + && (optionalChain || !isOptionalChain(emittedExpression))) { + // TODO(rbuckton): Verify whether this assertion holds. + return expression as LeftHandSideExpression; } - function parenthesizeOperandOfPostfixUnary(operand: Expression): LeftHandSideExpression { - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return isLeftHandSideExpression(operand) ? operand : setTextRange(factory.createParenthesizedExpression(operand), operand); - } + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return setTextRange(factory.createParenthesizedExpression(expression), expression); + } - function parenthesizeOperandOfPrefixUnary(operand: Expression): UnaryExpression { - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return isUnaryExpression(operand) ? operand : setTextRange(factory.createParenthesizedExpression(operand), operand); - } + function parenthesizeOperandOfPostfixUnary(operand: Expression): LeftHandSideExpression { + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return isLeftHandSideExpression(operand) ? operand : setTextRange(factory.createParenthesizedExpression(operand), operand); + } - function parenthesizeExpressionsOfCommaDelimitedList(elements: NodeArray): NodeArray { - const result = sameMap(elements, parenthesizeExpressionForDisallowedComma); - return setTextRange(factory.createNodeArray(result, elements.hasTrailingComma), elements); - } + function parenthesizeOperandOfPrefixUnary(operand: Expression): UnaryExpression { + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return isUnaryExpression(operand) ? operand : setTextRange(factory.createParenthesizedExpression(operand), operand); + } - function parenthesizeExpressionForDisallowedComma(expression: Expression): Expression { - const emittedExpression = skipPartiallyEmittedExpressions(expression); - const expressionPrecedence = getExpressionPrecedence(emittedExpression); - const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return expressionPrecedence > commaPrecedence ? expression : setTextRange(factory.createParenthesizedExpression(expression), expression); - } + function parenthesizeExpressionsOfCommaDelimitedList(elements: NodeArray): NodeArray { + const result = sameMap(elements, parenthesizeExpressionForDisallowedComma); + return setTextRange(factory.createNodeArray(result, elements.hasTrailingComma), elements); + } - function parenthesizeExpressionOfExpressionStatement(expression: Expression): Expression { - const emittedExpression = skipPartiallyEmittedExpressions(expression); - if (isCallExpression(emittedExpression)) { - const callee = emittedExpression.expression; - const kind = skipPartiallyEmittedExpressions(callee).kind; - if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - const updated = factory.updateCallExpression( - emittedExpression, - setTextRange(factory.createParenthesizedExpression(callee), callee), - emittedExpression.typeArguments, - emittedExpression.arguments - ); - return factory.restoreOuterExpressions(expression, updated, OuterExpressionKinds.PartiallyEmittedExpressions); - } - } + function parenthesizeExpressionForDisallowedComma(expression: Expression): Expression { + const emittedExpression = skipPartiallyEmittedExpressions(expression); + const expressionPrecedence = getExpressionPrecedence(emittedExpression); + const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken); + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return expressionPrecedence > commaPrecedence ? expression : setTextRange(factory.createParenthesizedExpression(expression), expression); + } - const leftmostExpressionKind = getLeftmostExpression(emittedExpression, /*stopAtCallExpressions*/ false).kind; - if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) { + function parenthesizeExpressionOfExpressionStatement(expression: Expression): Expression { + const emittedExpression = skipPartiallyEmittedExpressions(expression); + if (isCallExpression(emittedExpression)) { + const callee = emittedExpression.expression; + const kind = skipPartiallyEmittedExpressions(callee).kind; + if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) { // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return setTextRange(factory.createParenthesizedExpression(expression), expression); + const updated = factory.updateCallExpression( + emittedExpression, + setTextRange(factory.createParenthesizedExpression(callee), callee), + emittedExpression.typeArguments, + emittedExpression.arguments + ); + return factory.restoreOuterExpressions(expression, updated, OuterExpressionKinds.PartiallyEmittedExpressions); } + } - return expression; + const leftmostExpressionKind = getLeftmostExpression(emittedExpression, /*stopAtCallExpressions*/ false).kind; + if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) { + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return setTextRange(factory.createParenthesizedExpression(expression), expression); } - function parenthesizeConciseBodyOfArrowFunction(body: Expression): Expression; - function parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody; - function parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody { - if (!isBlock(body) && (isCommaSequence(body) || getLeftmostExpression(body, /*stopAtCallExpressions*/ false).kind === SyntaxKind.ObjectLiteralExpression)) { - // TODO(rbuckton): Verifiy whether `setTextRange` is needed. - return setTextRange(factory.createParenthesizedExpression(body), body); - } + return expression; + } - return body; + function parenthesizeConciseBodyOfArrowFunction(body: Expression): Expression; + function parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody; + function parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody { + if (!isBlock(body) && (isCommaSequence(body) || getLeftmostExpression(body, /*stopAtCallExpressions*/ false).kind === SyntaxKind.ObjectLiteralExpression)) { + // TODO(rbuckton): Verifiy whether `setTextRange` is needed. + return setTextRange(factory.createParenthesizedExpression(body), body); } - // Type[Extends] : - // FunctionOrConstructorType - // ConditionalType[?Extends] + return body; + } - // ConditionalType[Extends] : - // UnionType[?Extends] - // [~Extends] UnionType[~Extends] `extends` Type[+Extends] `?` Type[~Extends] `:` Type[~Extends] - // - // - The check type (the `UnionType`, above) does not allow function, constructor, or conditional types (they must be parenthesized) - // - The extends type (the first `Type`, above) does not allow conditional types (they must be parenthesized). Function and constructor types are fine. - // - The true and false branch types (the second and third `Type` non-terminals, above) allow any type - function parenthesizeCheckTypeOfConditionalType(checkType: TypeNode): TypeNode { - switch (checkType.kind) { - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.ConditionalType: - return factory.createParenthesizedType(checkType); - } - return checkType; - } + // Type[Extends] : + // FunctionOrConstructorType + // ConditionalType[?Extends] + + // ConditionalType[Extends] : + // UnionType[?Extends] + // [~Extends] UnionType[~Extends] `extends` Type[+Extends] `?` Type[~Extends] `:` Type[~Extends] + // + // - The check type (the `UnionType`, above) does not allow function, constructor, or conditional types (they must be parenthesized) + // - The extends type (the first `Type`, above) does not allow conditional types (they must be parenthesized). Function and constructor types are fine. + // - The true and false branch types (the second and third `Type` non-terminals, above) allow any type + function parenthesizeCheckTypeOfConditionalType(checkType: TypeNode): TypeNode { + switch (checkType.kind) { + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.ConditionalType: + return factory.createParenthesizedType(checkType); + } + return checkType; + } - function parenthesizeExtendsTypeOfConditionalType(extendsType: TypeNode): TypeNode { - switch (extendsType.kind) { - case SyntaxKind.ConditionalType: - return factory.createParenthesizedType(extendsType); - } - return extendsType; + function parenthesizeExtendsTypeOfConditionalType(extendsType: TypeNode): TypeNode { + switch (extendsType.kind) { + case SyntaxKind.ConditionalType: + return factory.createParenthesizedType(extendsType); } + return extendsType; + } - // UnionType[Extends] : - // `|`? IntersectionType[?Extends] - // UnionType[?Extends] `|` IntersectionType[?Extends] - // - // - A union type constituent has the same precedence as the check type of a conditional type - function parenthesizeConstituentTypeOfUnionType(type: TypeNode) { - switch (type.kind) { - case SyntaxKind.UnionType: // Not strictly necessary, but a union containing a union should have been flattened - case SyntaxKind.IntersectionType: // Not strictly necessary, but makes generated output more readable and avoids breaks in DT tests - return factory.createParenthesizedType(type); - } - return parenthesizeCheckTypeOfConditionalType(type); - } + // UnionType[Extends] : + // `|`? IntersectionType[?Extends] + // UnionType[?Extends] `|` IntersectionType[?Extends] + // + // - A union type constituent has the same precedence as the check type of a conditional type + function parenthesizeConstituentTypeOfUnionType(type: TypeNode) { + switch (type.kind) { + case SyntaxKind.UnionType: // Not strictly necessary, but a union containing a union should have been flattened + case SyntaxKind.IntersectionType: // Not strictly necessary, but makes generated output more readable and avoids breaks in DT tests + return factory.createParenthesizedType(type); + } + return parenthesizeCheckTypeOfConditionalType(type); + } - function parenthesizeConstituentTypesOfUnionType(members: readonly TypeNode[]): NodeArray { - return factory.createNodeArray(sameMap(members, parenthesizeConstituentTypeOfUnionType)); - } + function parenthesizeConstituentTypesOfUnionType(members: readonly TypeNode[]): NodeArray { + return factory.createNodeArray(sameMap(members, parenthesizeConstituentTypeOfUnionType)); + } - // IntersectionType[Extends] : - // `&`? TypeOperator[?Extends] - // IntersectionType[?Extends] `&` TypeOperator[?Extends] - // - // - An intersection type constituent does not allow function, constructor, conditional, or union types (they must be parenthesized) - function parenthesizeConstituentTypeOfIntersectionType(type: TypeNode) { - switch (type.kind) { - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: // Not strictly necessary, but an intersection containing an intersection should have been flattened - return factory.createParenthesizedType(type); - } - return parenthesizeConstituentTypeOfUnionType(type); - } + // IntersectionType[Extends] : + // `&`? TypeOperator[?Extends] + // IntersectionType[?Extends] `&` TypeOperator[?Extends] + // + // - An intersection type constituent does not allow function, constructor, conditional, or union types (they must be parenthesized) + function parenthesizeConstituentTypeOfIntersectionType(type: TypeNode) { + switch (type.kind) { + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: // Not strictly necessary, but an intersection containing an intersection should have been flattened + return factory.createParenthesizedType(type); + } + return parenthesizeConstituentTypeOfUnionType(type); + } - function parenthesizeConstituentTypesOfIntersectionType(members: readonly TypeNode[]): NodeArray { - return factory.createNodeArray(sameMap(members, parenthesizeConstituentTypeOfIntersectionType)); - } + function parenthesizeConstituentTypesOfIntersectionType(members: readonly TypeNode[]): NodeArray { + return factory.createNodeArray(sameMap(members, parenthesizeConstituentTypeOfIntersectionType)); + } - // TypeOperator[Extends] : - // PostfixType - // InferType[?Extends] - // `keyof` TypeOperator[?Extends] - // `unique` TypeOperator[?Extends] - // `readonly` TypeOperator[?Extends] - // - function parenthesizeOperandOfTypeOperator(type: TypeNode) { - switch (type.kind) { - case SyntaxKind.IntersectionType: - return factory.createParenthesizedType(type); - } - return parenthesizeConstituentTypeOfIntersectionType(type); - } + // TypeOperator[Extends] : + // PostfixType + // InferType[?Extends] + // `keyof` TypeOperator[?Extends] + // `unique` TypeOperator[?Extends] + // `readonly` TypeOperator[?Extends] + // + function parenthesizeOperandOfTypeOperator(type: TypeNode) { + switch (type.kind) { + case SyntaxKind.IntersectionType: + return factory.createParenthesizedType(type); + } + return parenthesizeConstituentTypeOfIntersectionType(type); + } - function parenthesizeOperandOfReadonlyTypeOperator(type: TypeNode) { - switch (type.kind) { - case SyntaxKind.TypeOperator: - return factory.createParenthesizedType(type); - } - return parenthesizeOperandOfTypeOperator(type); + function parenthesizeOperandOfReadonlyTypeOperator(type: TypeNode) { + switch (type.kind) { + case SyntaxKind.TypeOperator: + return factory.createParenthesizedType(type); } + return parenthesizeOperandOfTypeOperator(type); + } - // PostfixType : - // NonArrayType - // NonArrayType [no LineTerminator here] `!` // JSDoc - // NonArrayType [no LineTerminator here] `?` // JSDoc - // IndexedAccessType - // ArrayType - // - // IndexedAccessType : - // NonArrayType `[` Type[~Extends] `]` - // - // ArrayType : - // NonArrayType `[` `]` - // - function parenthesizeNonArrayTypeOfPostfixType(type: TypeNode) { - switch (type.kind) { - case SyntaxKind.InferType: - case SyntaxKind.TypeOperator: - case SyntaxKind.TypeQuery: // Not strictly necessary, but makes generated output more readable and avoids breaks in DT tests - return factory.createParenthesizedType(type); - } - return parenthesizeOperandOfTypeOperator(type); - } + // PostfixType : + // NonArrayType + // NonArrayType [no LineTerminator here] `!` // JSDoc + // NonArrayType [no LineTerminator here] `?` // JSDoc + // IndexedAccessType + // ArrayType + // + // IndexedAccessType : + // NonArrayType `[` Type[~Extends] `]` + // + // ArrayType : + // NonArrayType `[` `]` + // + function parenthesizeNonArrayTypeOfPostfixType(type: TypeNode) { + switch (type.kind) { + case SyntaxKind.InferType: + case SyntaxKind.TypeOperator: + case SyntaxKind.TypeQuery: // Not strictly necessary, but makes generated output more readable and avoids breaks in DT tests + return factory.createParenthesizedType(type); + } + return parenthesizeOperandOfTypeOperator(type); + } - // TupleType : - // `[` Elision? `]` - // `[` NamedTupleElementTypes `]` - // `[` NamedTupleElementTypes `,` Elision? `]` - // `[` TupleElementTypes `]` - // `[` TupleElementTypes `,` Elision? `]` - // - // NamedTupleElementTypes : - // Elision? NamedTupleMember - // NamedTupleElementTypes `,` Elision? NamedTupleMember - // - // NamedTupleMember : - // Identifier `?`? `:` Type[~Extends] - // `...` Identifier `:` Type[~Extends] - // - // TupleElementTypes : - // Elision? TupleElementType - // TupleElementTypes `,` Elision? TupleElementType - // - // TupleElementType : - // Type[~Extends] // NOTE: Needs cover grammar to disallow JSDoc postfix-optional - // OptionalType - // RestType - // - // OptionalType : - // Type[~Extends] `?` // NOTE: Needs cover grammar to disallow JSDoc postfix-optional - // - // RestType : - // `...` Type[~Extends] - // - function parenthesizeElementTypesOfTupleType(types: readonly (TypeNode | NamedTupleMember)[]): NodeArray { - return factory.createNodeArray(sameMap(types, parenthesizeElementTypeOfTupleType)); - } + // TupleType : + // `[` Elision? `]` + // `[` NamedTupleElementTypes `]` + // `[` NamedTupleElementTypes `,` Elision? `]` + // `[` TupleElementTypes `]` + // `[` TupleElementTypes `,` Elision? `]` + // + // NamedTupleElementTypes : + // Elision? NamedTupleMember + // NamedTupleElementTypes `,` Elision? NamedTupleMember + // + // NamedTupleMember : + // Identifier `?`? `:` Type[~Extends] + // `...` Identifier `:` Type[~Extends] + // + // TupleElementTypes : + // Elision? TupleElementType + // TupleElementTypes `,` Elision? TupleElementType + // + // TupleElementType : + // Type[~Extends] // NOTE: Needs cover grammar to disallow JSDoc postfix-optional + // OptionalType + // RestType + // + // OptionalType : + // Type[~Extends] `?` // NOTE: Needs cover grammar to disallow JSDoc postfix-optional + // + // RestType : + // `...` Type[~Extends] + // + function parenthesizeElementTypesOfTupleType(types: readonly (TypeNode | NamedTupleMember)[]): NodeArray { + return factory.createNodeArray(sameMap(types, parenthesizeElementTypeOfTupleType)); + } - function parenthesizeElementTypeOfTupleType(type: TypeNode | NamedTupleMember): TypeNode { - if (hasJSDocPostfixQuestion(type)) return factory.createParenthesizedType(type); - return type; - } + function parenthesizeElementTypeOfTupleType(type: TypeNode | NamedTupleMember): TypeNode { + if (hasJSDocPostfixQuestion(type)) return factory.createParenthesizedType(type); + return type; + } - function hasJSDocPostfixQuestion(type: TypeNode | NamedTupleMember): boolean { - if (isJSDocNullableType(type)) return type.postfix; - if (isNamedTupleMember(type)) return hasJSDocPostfixQuestion(type.type); - if (isFunctionTypeNode(type) || isConstructorTypeNode(type) || isTypeOperatorNode(type)) return hasJSDocPostfixQuestion(type.type); - if (isConditionalTypeNode(type)) return hasJSDocPostfixQuestion(type.falseType); - if (isUnionTypeNode(type)) return hasJSDocPostfixQuestion(last(type.types)); - if (isIntersectionTypeNode(type)) return hasJSDocPostfixQuestion(last(type.types)); - if (isInferTypeNode(type)) return !!type.typeParameter.constraint && hasJSDocPostfixQuestion(type.typeParameter.constraint); - return false; - } + function hasJSDocPostfixQuestion(type: TypeNode | NamedTupleMember): boolean { + if (isJSDocNullableType(type)) return type.postfix; + if (isNamedTupleMember(type)) return hasJSDocPostfixQuestion(type.type); + if (isFunctionTypeNode(type) || isConstructorTypeNode(type) || isTypeOperatorNode(type)) return hasJSDocPostfixQuestion(type.type); + if (isConditionalTypeNode(type)) return hasJSDocPostfixQuestion(type.falseType); + if (isUnionTypeNode(type)) return hasJSDocPostfixQuestion(last(type.types)); + if (isIntersectionTypeNode(type)) return hasJSDocPostfixQuestion(last(type.types)); + if (isInferTypeNode(type)) return !!type.typeParameter.constraint && hasJSDocPostfixQuestion(type.typeParameter.constraint); + return false; + } - function parenthesizeTypeOfOptionalType(type: TypeNode): TypeNode { - if (hasJSDocPostfixQuestion(type)) return factory.createParenthesizedType(type); - return parenthesizeNonArrayTypeOfPostfixType(type); - } + function parenthesizeTypeOfOptionalType(type: TypeNode): TypeNode { + if (hasJSDocPostfixQuestion(type)) return factory.createParenthesizedType(type); + return parenthesizeNonArrayTypeOfPostfixType(type); + } - // function parenthesizeMemberOfElementType(member: TypeNode): TypeNode { - // switch (member.kind) { - // case SyntaxKind.UnionType: - // case SyntaxKind.IntersectionType: - // case SyntaxKind.FunctionType: - // case SyntaxKind.ConstructorType: - // return factory.createParenthesizedType(member); - // } - // return parenthesizeMemberOfConditionalType(member); - // } - - // function parenthesizeElementTypeOfArrayType(member: TypeNode): TypeNode { - // switch (member.kind) { - // case SyntaxKind.TypeQuery: - // case SyntaxKind.TypeOperator: - // case SyntaxKind.InferType: - // return factory.createParenthesizedType(member); - // } - // return parenthesizeMemberOfElementType(member); - // } - - function parenthesizeLeadingTypeArgument(node: TypeNode) { - return isFunctionOrConstructorTypeNode(node) && node.typeParameters ? factory.createParenthesizedType(node) : node; - } + // function parenthesizeMemberOfElementType(member: TypeNode): TypeNode { + // switch (member.kind) { + // case SyntaxKind.UnionType: + // case SyntaxKind.IntersectionType: + // case SyntaxKind.FunctionType: + // case SyntaxKind.ConstructorType: + // return factory.createParenthesizedType(member); + // } + // return parenthesizeMemberOfConditionalType(member); + // } + + // function parenthesizeElementTypeOfArrayType(member: TypeNode): TypeNode { + // switch (member.kind) { + // case SyntaxKind.TypeQuery: + // case SyntaxKind.TypeOperator: + // case SyntaxKind.InferType: + // return factory.createParenthesizedType(member); + // } + // return parenthesizeMemberOfElementType(member); + // } + + function parenthesizeLeadingTypeArgument(node: TypeNode) { + return isFunctionOrConstructorTypeNode(node) && node.typeParameters ? factory.createParenthesizedType(node) : node; + } - function parenthesizeOrdinalTypeArgument(node: TypeNode, i: number) { - return i === 0 ? parenthesizeLeadingTypeArgument(node) : node; - } + function parenthesizeOrdinalTypeArgument(node: TypeNode, i: number) { + return i === 0 ? parenthesizeLeadingTypeArgument(node) : node; + } - function parenthesizeTypeArguments(typeArguments: NodeArray | undefined): NodeArray | undefined { - if (some(typeArguments)) { - return factory.createNodeArray(sameMap(typeArguments, parenthesizeOrdinalTypeArgument)); - } + function parenthesizeTypeArguments(typeArguments: NodeArray | undefined): NodeArray | undefined { + if (some(typeArguments)) { + return factory.createNodeArray(sameMap(typeArguments, parenthesizeOrdinalTypeArgument)); } } - - export const nullParenthesizerRules: ParenthesizerRules = { - getParenthesizeLeftSideOfBinaryForOperator: _ => identity, - getParenthesizeRightSideOfBinaryForOperator: _ => identity, - parenthesizeLeftSideOfBinary: (_binaryOperator, leftSide) => leftSide, - parenthesizeRightSideOfBinary: (_binaryOperator, _leftSide, rightSide) => rightSide, - parenthesizeExpressionOfComputedPropertyName: identity, - parenthesizeConditionOfConditionalExpression: identity, - parenthesizeBranchOfConditionalExpression: identity, - parenthesizeExpressionOfExportDefault: identity, - parenthesizeExpressionOfNew: expression => cast(expression, isLeftHandSideExpression), - parenthesizeLeftSideOfAccess: expression => cast(expression, isLeftHandSideExpression), - parenthesizeOperandOfPostfixUnary: operand => cast(operand, isLeftHandSideExpression), - parenthesizeOperandOfPrefixUnary: operand => cast(operand, isUnaryExpression), - parenthesizeExpressionsOfCommaDelimitedList: nodes => cast(nodes, isNodeArray), - parenthesizeExpressionForDisallowedComma: identity, - parenthesizeExpressionOfExpressionStatement: identity, - parenthesizeConciseBodyOfArrowFunction: identity, - parenthesizeCheckTypeOfConditionalType: identity, - parenthesizeExtendsTypeOfConditionalType: identity, - parenthesizeConstituentTypesOfUnionType: nodes => cast(nodes, isNodeArray), - parenthesizeConstituentTypeOfUnionType: identity, - parenthesizeConstituentTypesOfIntersectionType: nodes => cast(nodes, isNodeArray), - parenthesizeConstituentTypeOfIntersectionType: identity, - parenthesizeOperandOfTypeOperator: identity, - parenthesizeOperandOfReadonlyTypeOperator: identity, - parenthesizeNonArrayTypeOfPostfixType: identity, - parenthesizeElementTypesOfTupleType: nodes => cast(nodes, isNodeArray), - parenthesizeElementTypeOfTupleType: identity, - parenthesizeTypeOfOptionalType: identity, - parenthesizeTypeArguments: nodes => nodes && cast(nodes, isNodeArray), - parenthesizeLeadingTypeArgument: identity, - }; } + +/** @internal */ +export const nullParenthesizerRules: ParenthesizerRules = { + getParenthesizeLeftSideOfBinaryForOperator: _ => identity, + getParenthesizeRightSideOfBinaryForOperator: _ => identity, + parenthesizeLeftSideOfBinary: (_binaryOperator, leftSide) => leftSide, + parenthesizeRightSideOfBinary: (_binaryOperator, _leftSide, rightSide) => rightSide, + parenthesizeExpressionOfComputedPropertyName: identity, + parenthesizeConditionOfConditionalExpression: identity, + parenthesizeBranchOfConditionalExpression: identity, + parenthesizeExpressionOfExportDefault: identity, + parenthesizeExpressionOfNew: expression => cast(expression, isLeftHandSideExpression), + parenthesizeLeftSideOfAccess: expression => cast(expression, isLeftHandSideExpression), + parenthesizeOperandOfPostfixUnary: operand => cast(operand, isLeftHandSideExpression), + parenthesizeOperandOfPrefixUnary: operand => cast(operand, isUnaryExpression), + parenthesizeExpressionsOfCommaDelimitedList: nodes => cast(nodes, isNodeArray), + parenthesizeExpressionForDisallowedComma: identity, + parenthesizeExpressionOfExpressionStatement: identity, + parenthesizeConciseBodyOfArrowFunction: identity, + parenthesizeCheckTypeOfConditionalType: identity, + parenthesizeExtendsTypeOfConditionalType: identity, + parenthesizeConstituentTypesOfUnionType: nodes => cast(nodes, isNodeArray), + parenthesizeConstituentTypeOfUnionType: identity, + parenthesizeConstituentTypesOfIntersectionType: nodes => cast(nodes, isNodeArray), + parenthesizeConstituentTypeOfIntersectionType: identity, + parenthesizeOperandOfTypeOperator: identity, + parenthesizeOperandOfReadonlyTypeOperator: identity, + parenthesizeNonArrayTypeOfPostfixType: identity, + parenthesizeElementTypesOfTupleType: nodes => cast(nodes, isNodeArray), + parenthesizeElementTypeOfTupleType: identity, + parenthesizeTypeOfOptionalType: identity, + parenthesizeTypeArguments: nodes => nodes && cast(nodes, isNodeArray), + parenthesizeLeadingTypeArgument: identity, +}; diff --git a/src/compiler/factory/utilities.ts b/src/compiler/factory/utilities.ts index 6c7f02c240f2e..b6c97f8f3b857 100644 --- a/src/compiler/factory/utilities.ts +++ b/src/compiler/factory/utilities.ts @@ -1,1384 +1,1503 @@ -/* @internal */ -namespace ts { - - // Compound nodes +import { + AccessorDeclaration, addEmitFlags, AdditiveOperator, AdditiveOperatorOrHigher, AssertionLevel, + AssignmentOperatorOrHigher, BinaryExpression, BinaryOperator, BinaryOperatorToken, BindingOrAssignmentElement, + BindingOrAssignmentElementRestIndicator, BindingOrAssignmentElementTarget, BindingOrAssignmentPattern, + BitwiseOperator, BitwiseOperatorOrHigher, Block, BooleanLiteral, CharacterCodes, CommaListExpression, + compareStringsCaseSensitive, CompilerOptions, Debug, Declaration, EmitFlags, EmitHelperFactory, EmitHost, + EmitResolver, EntityName, EqualityOperator, EqualityOperatorOrHigher, ExclamationToken, ExponentiationOperator, + ExportDeclaration, Expression, ExpressionStatement, externalHelpersModuleNameText, first, firstOrUndefined, + ForInitializer, GeneratedIdentifier, GeneratedIdentifierFlags, GeneratedNamePart, GeneratedPrivateIdentifier, + GetAccessorDeclaration, + getAllAccessorDeclarations, getEmitFlags, getEmitHelpers, getEmitModuleKind, getESModuleInterop, + getExternalModuleName, getExternalModuleNameFromPath, getJSDocType, getJSDocTypeTag, getModifiers, + getNamespaceDeclarationNode, getOrCreateEmitNode, getOriginalNode, getParseTreeNode, + getSourceTextOfNodeFromSourceFile, HasIllegalDecorators, HasIllegalModifiers, HasIllegalType, + HasIllegalTypeParameters, Identifier, idText, ImportCall, ImportDeclaration, ImportEqualsDeclaration, + isAssignmentExpression, isAssignmentOperator, isBlock, isComputedPropertyName, isDeclarationBindingElement, + isDefaultImport, isEffectiveExternalModule, isExclamationToken, isExportNamespaceAsDefaultDeclaration, + isFileLevelUniqueName, isGeneratedIdentifier, isGeneratedPrivateIdentifier, isIdentifier, isInJSFile, + isLiteralExpression, isMemberName, isMinusToken, isObjectLiteralElementLike, isParenthesizedExpression, isPlusToken, + isPostfixUnaryExpression, isPrefixUnaryExpression, isPrivateIdentifier, isPrologueDirective, isPropertyAssignment, + isPropertyName, isQualifiedName, isQuestionToken, isReadonlyKeyword, isShorthandPropertyAssignment, isSourceFile, + isSpreadAssignment, isSpreadElement, isStringLiteral, isThisTypeNode, isTypeNode, isTypeParameterDeclaration, + isVariableDeclarationList, JSDocNamespaceBody, JSDocTypeAssertion, JsxOpeningFragment, JsxOpeningLikeElement, + LeftHandSideExpression, LiteralExpression, LogicalOperator, LogicalOperatorOrHigher, map, MemberExpression, + MethodDeclaration, MinusToken, ModifiersArray, ModuleKind, ModuleName, MultiplicativeOperator, + MultiplicativeOperatorOrHigher, Mutable, NamedImportBindings, Node, NodeArray, NodeFactory, NullLiteral, + NumericLiteral, ObjectLiteralElementLike, ObjectLiteralExpression, or, OuterExpression, OuterExpressionKinds, + outFile, parseNodeFactory, PlusToken, PostfixUnaryExpression, PrefixUnaryExpression, PrivateIdentifier, + PropertyAssignment, PropertyDeclaration, PropertyName, pushIfUnique, QuestionToken, ReadonlyKeyword, + RelationalOperator, RelationalOperatorOrHigher, SetAccessorDeclaration, setOriginalNode, setParent, setStartsOnNewLine, setTextRange, + ShiftOperator, ShiftOperatorOrHigher, ShorthandPropertyAssignment, some, SourceFile, Statement, StringLiteral, + SyntaxKind, TextRange, ThisTypeNode, Token, TypeNode, TypeParameterDeclaration, +} from "../_namespaces/ts"; + +// Compound nodes + +/** @internal */ +export function createEmptyExports(factory: NodeFactory) { + return factory.createExportDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([]), /*moduleSpecifier*/ undefined); +} - export function createEmptyExports(factory: NodeFactory) { - return factory.createExportDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([]), /*moduleSpecifier*/ undefined); +/** @internal */ +export function createMemberAccessForPropertyName(factory: NodeFactory, target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { + if (isComputedPropertyName(memberName)) { + return setTextRange(factory.createElementAccessExpression(target, memberName.expression), location); + } + else { + const expression = setTextRange( + isMemberName(memberName) + ? factory.createPropertyAccessExpression(target, memberName) + : factory.createElementAccessExpression(target, memberName), + memberName + ); + getOrCreateEmitNode(expression).flags |= EmitFlags.NoNestedSourceMaps; + return expression; } +} - export function createMemberAccessForPropertyName(factory: NodeFactory, target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { - if (isComputedPropertyName(memberName)) { - return setTextRange(factory.createElementAccessExpression(target, memberName.expression), location); - } - else { - const expression = setTextRange( - isMemberName(memberName) - ? factory.createPropertyAccessExpression(target, memberName) - : factory.createElementAccessExpression(target, memberName), - memberName - ); - getOrCreateEmitNode(expression).flags |= EmitFlags.NoNestedSourceMaps; - return expression; - } - } +function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) { + // To ensure the emit resolver can properly resolve the namespace, we need to + // treat this identifier as if it were a source tree node by clearing the `Synthesized` + // flag and setting a parent node. + const react = parseNodeFactory.createIdentifier(reactNamespace || "React"); + // Set the parent that is in parse tree + // this makes sure that parent chain is intact for checker to traverse complete scope tree + setParent(react, getParseTreeNode(parent)); + return react; +} - function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) { - // To ensure the emit resolver can properly resolve the namespace, we need to - // treat this identifier as if it were a source tree node by clearing the `Synthesized` - // flag and setting a parent node. - const react = parseNodeFactory.createIdentifier(reactNamespace || "React"); - // Set the parent that is in parse tree - // this makes sure that parent chain is intact for checker to traverse complete scope tree - setParent(react, getParseTreeNode(parent)); - return react; +function createJsxFactoryExpressionFromEntityName(factory: NodeFactory, jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { + if (isQualifiedName(jsxFactory)) { + const left = createJsxFactoryExpressionFromEntityName(factory, jsxFactory.left, parent); + const right = factory.createIdentifier(idText(jsxFactory.right)) as Mutable; + right.escapedText = jsxFactory.right.escapedText; + return factory.createPropertyAccessExpression(left, right); } - - function createJsxFactoryExpressionFromEntityName(factory: NodeFactory, jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { - if (isQualifiedName(jsxFactory)) { - const left = createJsxFactoryExpressionFromEntityName(factory, jsxFactory.left, parent); - const right = factory.createIdentifier(idText(jsxFactory.right)) as Mutable; - right.escapedText = jsxFactory.right.escapedText; - return factory.createPropertyAccessExpression(left, right); - } - else { - return createReactNamespace(idText(jsxFactory), parent); - } + else { + return createReactNamespace(idText(jsxFactory), parent); } +} - export function createJsxFactoryExpression(factory: NodeFactory, jsxFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { - return jsxFactoryEntity ? - createJsxFactoryExpressionFromEntityName(factory, jsxFactoryEntity, parent) : - factory.createPropertyAccessExpression( - createReactNamespace(reactNamespace, parent), - "createElement" - ); - } +/** @internal */ +export function createJsxFactoryExpression(factory: NodeFactory, jsxFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { + return jsxFactoryEntity ? + createJsxFactoryExpressionFromEntityName(factory, jsxFactoryEntity, parent) : + factory.createPropertyAccessExpression( + createReactNamespace(reactNamespace, parent), + "createElement" + ); +} - function createJsxFragmentFactoryExpression(factory: NodeFactory, jsxFragmentFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { - return jsxFragmentFactoryEntity ? - createJsxFactoryExpressionFromEntityName(factory, jsxFragmentFactoryEntity, parent) : - factory.createPropertyAccessExpression( - createReactNamespace(reactNamespace, parent), - "Fragment" - ); +function createJsxFragmentFactoryExpression(factory: NodeFactory, jsxFragmentFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression { + return jsxFragmentFactoryEntity ? + createJsxFactoryExpressionFromEntityName(factory, jsxFragmentFactoryEntity, parent) : + factory.createPropertyAccessExpression( + createReactNamespace(reactNamespace, parent), + "Fragment" + ); +} + +/** @internal */ +export function createExpressionForJsxElement(factory: NodeFactory, callee: Expression, tagName: Expression, props: Expression | undefined, children: readonly Expression[] | undefined, location: TextRange): LeftHandSideExpression { + const argumentsList = [tagName]; + if (props) { + argumentsList.push(props); } - export function createExpressionForJsxElement(factory: NodeFactory, callee: Expression, tagName: Expression, props: Expression | undefined, children: readonly Expression[] | undefined, location: TextRange): LeftHandSideExpression { - const argumentsList = [tagName]; - if (props) { - argumentsList.push(props); + if (children && children.length > 0) { + if (!props) { + argumentsList.push(factory.createNull()); } - if (children && children.length > 0) { - if (!props) { - argumentsList.push(factory.createNull()); - } - - if (children.length > 1) { - for (const child of children) { - startOnNewLine(child); - argumentsList.push(child); - } - } - else { - argumentsList.push(children[0]); + if (children.length > 1) { + for (const child of children) { + startOnNewLine(child); + argumentsList.push(child); } } - - return setTextRange( - factory.createCallExpression( - callee, - /*typeArguments*/ undefined, - argumentsList - ), - location - ); + else { + argumentsList.push(children[0]); + } } - export function createExpressionForJsxFragment(factory: NodeFactory, jsxFactoryEntity: EntityName | undefined, jsxFragmentFactoryEntity: EntityName | undefined, reactNamespace: string, children: readonly Expression[], parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression { - const tagName = createJsxFragmentFactoryExpression(factory, jsxFragmentFactoryEntity, reactNamespace, parentElement); - const argumentsList = [tagName, factory.createNull()]; + return setTextRange( + factory.createCallExpression( + callee, + /*typeArguments*/ undefined, + argumentsList + ), + location + ); +} - if (children && children.length > 0) { - if (children.length > 1) { - for (const child of children) { - startOnNewLine(child); - argumentsList.push(child); - } - } - else { - argumentsList.push(children[0]); +/** @internal */ +export function createExpressionForJsxFragment(factory: NodeFactory, jsxFactoryEntity: EntityName | undefined, jsxFragmentFactoryEntity: EntityName | undefined, reactNamespace: string, children: readonly Expression[], parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression { + const tagName = createJsxFragmentFactoryExpression(factory, jsxFragmentFactoryEntity, reactNamespace, parentElement); + const argumentsList = [tagName, factory.createNull()]; + + if (children && children.length > 0) { + if (children.length > 1) { + for (const child of children) { + startOnNewLine(child); + argumentsList.push(child); } } + else { + argumentsList.push(children[0]); + } + } + + return setTextRange( + factory.createCallExpression( + createJsxFactoryExpression(factory, jsxFactoryEntity, reactNamespace, parentElement), + /*typeArguments*/ undefined, + argumentsList + ), + location + ); +} + +// Utilities +/** @internal */ +export function createForOfBindingStatement(factory: NodeFactory, node: ForInitializer, boundValue: Expression): Statement { + if (isVariableDeclarationList(node)) { + const firstDeclaration = first(node.declarations); + const updatedDeclaration = factory.updateVariableDeclaration( + firstDeclaration, + firstDeclaration.name, + /*exclamationToken*/ undefined, + /*type*/ undefined, + boundValue + ); return setTextRange( - factory.createCallExpression( - createJsxFactoryExpression(factory, jsxFactoryEntity, reactNamespace, parentElement), - /*typeArguments*/ undefined, - argumentsList + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.updateVariableDeclarationList(node, [updatedDeclaration]) ), - location + /*location*/ node ); } - - // Utilities - - export function createForOfBindingStatement(factory: NodeFactory, node: ForInitializer, boundValue: Expression): Statement { - if (isVariableDeclarationList(node)) { - const firstDeclaration = first(node.declarations); - const updatedDeclaration = factory.updateVariableDeclaration( - firstDeclaration, - firstDeclaration.name, - /*exclamationToken*/ undefined, - /*type*/ undefined, - boundValue - ); - return setTextRange( - factory.createVariableStatement( - /*modifiers*/ undefined, - factory.updateVariableDeclarationList(node, [updatedDeclaration]) - ), - /*location*/ node - ); - } - else { - const updatedExpression = setTextRange(factory.createAssignment(node, boundValue), /*location*/ node); - return setTextRange(factory.createExpressionStatement(updatedExpression), /*location*/ node); - } + else { + const updatedExpression = setTextRange(factory.createAssignment(node, boundValue), /*location*/ node); + return setTextRange(factory.createExpressionStatement(updatedExpression), /*location*/ node); } +} - export function insertLeadingStatement(factory: NodeFactory, dest: Statement, source: Statement) { - if (isBlock(dest)) { - return factory.updateBlock(dest, setTextRange(factory.createNodeArray([source, ...dest.statements]), dest.statements)); - } - else { - return factory.createBlock(factory.createNodeArray([dest, source]), /*multiLine*/ true); - } +/** @internal */ +export function insertLeadingStatement(factory: NodeFactory, dest: Statement, source: Statement): Block { + if (isBlock(dest)) { + return factory.updateBlock(dest, setTextRange(factory.createNodeArray([source, ...dest.statements]), dest.statements)); } - - export function createExpressionFromEntityName(factory: NodeFactory, node: EntityName | Expression): Expression { - if (isQualifiedName(node)) { - const left = createExpressionFromEntityName(factory, node.left); - // TODO(rbuckton): Does this need to be parented? - const right = setParent(setTextRange(factory.cloneNode(node.right), node.right), node.right.parent); - return setTextRange(factory.createPropertyAccessExpression(left, right), node); - } - else { - // TODO(rbuckton): Does this need to be parented? - return setParent(setTextRange(factory.cloneNode(node), node), node.parent); - } + else { + return factory.createBlock(factory.createNodeArray([dest, source]), /*multiLine*/ true); } +} - export function createExpressionForPropertyName(factory: NodeFactory, memberName: Exclude): Expression { - if (isIdentifier(memberName)) { - return factory.createStringLiteralFromNode(memberName); - } - else if (isComputedPropertyName(memberName)) { - // TODO(rbuckton): Does this need to be parented? - return setParent(setTextRange(factory.cloneNode(memberName.expression), memberName.expression), memberName.expression.parent); - } - else { - // TODO(rbuckton): Does this need to be parented? - return setParent(setTextRange(factory.cloneNode(memberName), memberName), memberName.parent); - } +/** @internal */ +export function createExpressionFromEntityName(factory: NodeFactory, node: EntityName | Expression): Expression { + if (isQualifiedName(node)) { + const left = createExpressionFromEntityName(factory, node.left); + // TODO(rbuckton): Does this need to be parented? + const right = setParent(setTextRange(factory.cloneNode(node.right), node.right), node.right.parent); + return setTextRange(factory.createPropertyAccessExpression(left, right), node); } - - function createExpressionForAccessorDeclaration(factory: NodeFactory, properties: NodeArray, property: AccessorDeclaration & { readonly name: Exclude; }, receiver: Expression, multiLine: boolean) { - const { firstAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(properties, property); - if (property === firstAccessor) { - return setTextRange( - factory.createObjectDefinePropertyCall( - receiver, - createExpressionForPropertyName(factory, property.name), - factory.createPropertyDescriptor({ - enumerable: factory.createFalse(), - configurable: true, - get: getAccessor && setTextRange( - setOriginalNode( - factory.createFunctionExpression( - getModifiers(getAccessor), - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - getAccessor.parameters, - /*type*/ undefined, - getAccessor.body! // TODO: GH#18217 - ), - getAccessor - ), - getAccessor - ), - set: setAccessor && setTextRange( - setOriginalNode( - factory.createFunctionExpression( - getModifiers(setAccessor), - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - setAccessor.parameters, - /*type*/ undefined, - setAccessor.body! // TODO: GH#18217 - ), - setAccessor - ), - setAccessor - ) - }, !multiLine) - ), - firstAccessor - ); - } - - return undefined; + else { + // TODO(rbuckton): Does this need to be parented? + return setParent(setTextRange(factory.cloneNode(node), node), node.parent); } +} - function createExpressionForPropertyAssignment(factory: NodeFactory, property: PropertyAssignment, receiver: Expression) { - return setOriginalNode( - setTextRange( - factory.createAssignment( - createMemberAccessForPropertyName(factory, receiver, property.name, /*location*/ property.name), - property.initializer - ), - property - ), - property - ); +/** @internal */ +export function createExpressionForPropertyName(factory: NodeFactory, memberName: Exclude): Expression { + if (isIdentifier(memberName)) { + return factory.createStringLiteralFromNode(memberName); } - - function createExpressionForShorthandPropertyAssignment(factory: NodeFactory, property: ShorthandPropertyAssignment, receiver: Expression) { - return setOriginalNode( - setTextRange( - factory.createAssignment( - createMemberAccessForPropertyName(factory, receiver, property.name, /*location*/ property.name), - factory.cloneNode(property.name) - ), - /*location*/ property - ), - /*original*/ property - ); + else if (isComputedPropertyName(memberName)) { + // TODO(rbuckton): Does this need to be parented? + return setParent(setTextRange(factory.cloneNode(memberName.expression), memberName.expression), memberName.expression.parent); } + else { + // TODO(rbuckton): Does this need to be parented? + return setParent(setTextRange(factory.cloneNode(memberName), memberName), memberName.parent); + } +} - function createExpressionForMethodDeclaration(factory: NodeFactory, method: MethodDeclaration, receiver: Expression) { - return setOriginalNode( - setTextRange( - factory.createAssignment( - createMemberAccessForPropertyName(factory, receiver, method.name, /*location*/ method.name), - setOriginalNode( - setTextRange( +function createExpressionForAccessorDeclaration(factory: NodeFactory, properties: NodeArray, property: AccessorDeclaration & { readonly name: Exclude; }, receiver: Expression, multiLine: boolean) { + const { firstAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(properties, property); + if (property === firstAccessor) { + return setTextRange( + factory.createObjectDefinePropertyCall( + receiver, + createExpressionForPropertyName(factory, property.name), + factory.createPropertyDescriptor({ + enumerable: factory.createFalse(), + configurable: true, + get: getAccessor && setTextRange( + setOriginalNode( + factory.createFunctionExpression( + getModifiers(getAccessor), + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + getAccessor.parameters, + /*type*/ undefined, + getAccessor.body! // TODO: GH#18217 + ), + getAccessor + ), + getAccessor + ), + set: setAccessor && setTextRange( + setOriginalNode( factory.createFunctionExpression( - getModifiers(method), - method.asteriskToken, + getModifiers(setAccessor), + /*asteriskToken*/ undefined, /*name*/ undefined, /*typeParameters*/ undefined, - method.parameters, + setAccessor.parameters, /*type*/ undefined, - method.body! // TODO: GH#18217 + setAccessor.body! // TODO: GH#18217 ), - /*location*/ method + setAccessor ), - /*original*/ method + setAccessor ) - ), - /*location*/ method + }, !multiLine) ), - /*original*/ method + firstAccessor ); } - export function createExpressionForObjectLiteralElementLike(factory: NodeFactory, node: ObjectLiteralExpression, property: ObjectLiteralElementLike, receiver: Expression): Expression | undefined { - if (property.name && isPrivateIdentifier(property.name)) { - Debug.failBadSyntaxKind(property.name, "Private identifiers are not allowed in object literals."); - } - switch (property.kind) { - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return createExpressionForAccessorDeclaration(factory, node.properties, property as typeof property & { readonly name: Exclude }, receiver, !!node.multiLine); - case SyntaxKind.PropertyAssignment: - return createExpressionForPropertyAssignment(factory, property, receiver); - case SyntaxKind.ShorthandPropertyAssignment: - return createExpressionForShorthandPropertyAssignment(factory, property, receiver); - case SyntaxKind.MethodDeclaration: - return createExpressionForMethodDeclaration(factory, property, receiver); - } - } + return undefined; +} - /** - * Expand the read and increment/decrement operations a pre- or post-increment or pre- or post-decrement expression. - * - * ```ts - * // input - * ++ - * // output (if result is not discarded) - * var ; - * ( = , = ++, ) - * // output (if result is discarded) - * var ; - * ( = , ++, ) - * - * // input - * ++ - * // output (if result is not discarded) - * var ; - * ( = , = ++) - * // output (if result is discarded) - * var ; - * ( = , ++) - * ``` - * - * It is up to the caller to supply a temporary variable for `` if one is needed. - * The temporary variable `` is injected so that `++` and `--` work uniformly with `number` and `bigint`. - * The result of the expression is always the final result of incrementing or decrementing the expression, so that it can be used for storage. - * - * @param factory {@link NodeFactory} used to create the expanded representation. - * @param node The original prefix or postfix unary node. - * @param expression The expression to use as the value to increment or decrement - * @param resultVariable A temporary variable in which to store the result. Pass `undefined` if the result is discarded, or if the value of `` is the expected result. - */ - export function expandPreOrPostfixIncrementOrDecrementExpression(factory: NodeFactory, node: PrefixUnaryExpression | PostfixUnaryExpression, expression: Expression, recordTempVariable: (node: Identifier) => void, resultVariable: Identifier | undefined) { - const operator = node.operator; - Debug.assert(operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken, "Expected 'node' to be a pre- or post-increment or pre- or post-decrement expression"); +function createExpressionForPropertyAssignment(factory: NodeFactory, property: PropertyAssignment, receiver: Expression) { + return setOriginalNode( + setTextRange( + factory.createAssignment( + createMemberAccessForPropertyName(factory, receiver, property.name, /*location*/ property.name), + property.initializer + ), + property + ), + property + ); +} - const temp = factory.createTempVariable(recordTempVariable); - expression = factory.createAssignment(temp, expression); - setTextRange(expression, node.operand); +function createExpressionForShorthandPropertyAssignment(factory: NodeFactory, property: ShorthandPropertyAssignment, receiver: Expression) { + return setOriginalNode( + setTextRange( + factory.createAssignment( + createMemberAccessForPropertyName(factory, receiver, property.name, /*location*/ property.name), + factory.cloneNode(property.name) + ), + /*location*/ property + ), + /*original*/ property + ); +} - let operation: Expression = isPrefixUnaryExpression(node) ? - factory.createPrefixUnaryExpression(operator, temp) : - factory.createPostfixUnaryExpression(temp, operator); - setTextRange(operation, node); +function createExpressionForMethodDeclaration(factory: NodeFactory, method: MethodDeclaration, receiver: Expression) { + return setOriginalNode( + setTextRange( + factory.createAssignment( + createMemberAccessForPropertyName(factory, receiver, method.name, /*location*/ method.name), + setOriginalNode( + setTextRange( + factory.createFunctionExpression( + getModifiers(method), + method.asteriskToken, + /*name*/ undefined, + /*typeParameters*/ undefined, + method.parameters, + /*type*/ undefined, + method.body! // TODO: GH#18217 + ), + /*location*/ method + ), + /*original*/ method + ) + ), + /*location*/ method + ), + /*original*/ method + ); +} - if (resultVariable) { - operation = factory.createAssignment(resultVariable, operation); - setTextRange(operation, node); - } +/** @internal */ +export function createExpressionForObjectLiteralElementLike(factory: NodeFactory, node: ObjectLiteralExpression, property: ObjectLiteralElementLike, receiver: Expression): Expression | undefined { + if (property.name && isPrivateIdentifier(property.name)) { + Debug.failBadSyntaxKind(property.name, "Private identifiers are not allowed in object literals."); + } + switch (property.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return createExpressionForAccessorDeclaration(factory, node.properties, property as typeof property & { readonly name: Exclude }, receiver, !!node.multiLine); + case SyntaxKind.PropertyAssignment: + return createExpressionForPropertyAssignment(factory, property, receiver); + case SyntaxKind.ShorthandPropertyAssignment: + return createExpressionForShorthandPropertyAssignment(factory, property, receiver); + case SyntaxKind.MethodDeclaration: + return createExpressionForMethodDeclaration(factory, property, receiver); + } +} - expression = factory.createComma(expression, operation); - setTextRange(expression, node); +/** + * Expand the read and increment/decrement operations a pre- or post-increment or pre- or post-decrement expression. + * + * ```ts + * // input + * ++ + * // output (if result is not discarded) + * var ; + * ( = , = ++, ) + * // output (if result is discarded) + * var ; + * ( = , ++, ) + * + * // input + * ++ + * // output (if result is not discarded) + * var ; + * ( = , = ++) + * // output (if result is discarded) + * var ; + * ( = , ++) + * ``` + * + * It is up to the caller to supply a temporary variable for `` if one is needed. + * The temporary variable `` is injected so that `++` and `--` work uniformly with `number` and `bigint`. + * The result of the expression is always the final result of incrementing or decrementing the expression, so that it can be used for storage. + * + * @param factory {@link NodeFactory} used to create the expanded representation. + * @param node The original prefix or postfix unary node. + * @param expression The expression to use as the value to increment or decrement + * @param resultVariable A temporary variable in which to store the result. Pass `undefined` if the result is discarded, or if the value of `` is the expected result. + * + * @internal + */ +export function expandPreOrPostfixIncrementOrDecrementExpression(factory: NodeFactory, node: PrefixUnaryExpression | PostfixUnaryExpression, expression: Expression, recordTempVariable: (node: Identifier) => void, resultVariable: Identifier | undefined) { + const operator = node.operator; + Debug.assert(operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken, "Expected 'node' to be a pre- or post-increment or pre- or post-decrement expression"); + + const temp = factory.createTempVariable(recordTempVariable); + expression = factory.createAssignment(temp, expression); + setTextRange(expression, node.operand); + + let operation: Expression = isPrefixUnaryExpression(node) ? + factory.createPrefixUnaryExpression(operator, temp) : + factory.createPostfixUnaryExpression(temp, operator); + setTextRange(operation, node); + + if (resultVariable) { + operation = factory.createAssignment(resultVariable, operation); + setTextRange(operation, node); + } - if (isPostfixUnaryExpression(node)) { - expression = factory.createComma(expression, temp); - setTextRange(expression, node); - } + expression = factory.createComma(expression, operation); + setTextRange(expression, node); - return expression; + if (isPostfixUnaryExpression(node)) { + expression = factory.createComma(expression, temp); + setTextRange(expression, node); } - /** - * Gets whether an identifier should only be referred to by its internal name. - */ - export function isInternalName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.InternalName) !== 0; - } + return expression; +} - /** - * Gets whether an identifier should only be referred to by its local name. - */ - export function isLocalName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.LocalName) !== 0; - } +/** + * Gets whether an identifier should only be referred to by its internal name. + * + * @internal + */ +export function isInternalName(node: Identifier) { + return (getEmitFlags(node) & EmitFlags.InternalName) !== 0; +} - /** - * Gets whether an identifier should only be referred to by its export representation if the - * name points to an exported symbol. - */ - export function isExportName(node: Identifier) { - return (getEmitFlags(node) & EmitFlags.ExportName) !== 0; - } +/** + * Gets whether an identifier should only be referred to by its local name. + * + * @internal + */ +export function isLocalName(node: Identifier) { + return (getEmitFlags(node) & EmitFlags.LocalName) !== 0; +} - function isUseStrictPrologue(node: ExpressionStatement): boolean { - return isStringLiteral(node.expression) && node.expression.text === "use strict"; - } +/** + * Gets whether an identifier should only be referred to by its export representation if the + * name points to an exported symbol. + * + * @internal + */ +export function isExportName(node: Identifier) { + return (getEmitFlags(node) & EmitFlags.ExportName) !== 0; +} - export function findUseStrictPrologue(statements: readonly Statement[]): Statement | undefined { - for (const statement of statements) { - if (isPrologueDirective(statement)) { - if (isUseStrictPrologue(statement)) { - return statement; - } - } - else { - break; +function isUseStrictPrologue(node: ExpressionStatement): boolean { + return isStringLiteral(node.expression) && node.expression.text === "use strict"; +} + +/** @internal */ +export function findUseStrictPrologue(statements: readonly Statement[]): Statement | undefined { + for (const statement of statements) { + if (isPrologueDirective(statement)) { + if (isUseStrictPrologue(statement)) { + return statement; } } - return undefined; + else { + break; + } } + return undefined; +} - export function startsWithUseStrict(statements: readonly Statement[]) { - const firstStatement = firstOrUndefined(statements); - return firstStatement !== undefined - && isPrologueDirective(firstStatement) - && isUseStrictPrologue(firstStatement); - } +/** @internal */ +export function startsWithUseStrict(statements: readonly Statement[]) { + const firstStatement = firstOrUndefined(statements); + return firstStatement !== undefined + && isPrologueDirective(firstStatement) + && isUseStrictPrologue(firstStatement); +} - export function isCommaSequence(node: Expression): node is BinaryExpression & {operatorToken: Token} | CommaListExpression { - return node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken || - node.kind === SyntaxKind.CommaListExpression; - } +/** @internal */ +export function isCommaSequence(node: Expression): node is BinaryExpression & {operatorToken: Token} | CommaListExpression { + return node.kind === SyntaxKind.BinaryExpression && (node as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken || + node.kind === SyntaxKind.CommaListExpression; +} - export function isJSDocTypeAssertion(node: Node): node is JSDocTypeAssertion { - return isParenthesizedExpression(node) - && isInJSFile(node) - && !!getJSDocTypeTag(node); - } +/** @internal */ +export function isJSDocTypeAssertion(node: Node): node is JSDocTypeAssertion { + return isParenthesizedExpression(node) + && isInJSFile(node) + && !!getJSDocTypeTag(node); +} - export function getJSDocTypeAssertionType(node: JSDocTypeAssertion) { - const type = getJSDocType(node); - Debug.assertIsDefined(type); - return type; - } +/** @internal */ +export function getJSDocTypeAssertionType(node: JSDocTypeAssertion) { + const type = getJSDocType(node); + Debug.assertIsDefined(type); + return type; +} - export function isOuterExpression(node: Node, kinds = OuterExpressionKinds.All): node is OuterExpression { - switch (node.kind) { - case SyntaxKind.ParenthesizedExpression: - if (kinds & OuterExpressionKinds.ExcludeJSDocTypeAssertion && isJSDocTypeAssertion(node)) { - return false; - } - return (kinds & OuterExpressionKinds.Parentheses) !== 0; - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - case SyntaxKind.SatisfiesExpression: - return (kinds & OuterExpressionKinds.TypeAssertions) !== 0; - case SyntaxKind.NonNullExpression: - return (kinds & OuterExpressionKinds.NonNullAssertions) !== 0; - case SyntaxKind.PartiallyEmittedExpression: - return (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) !== 0; - } - return false; - } +/** @internal */ +export function isOuterExpression(node: Node, kinds = OuterExpressionKinds.All): node is OuterExpression { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: + if (kinds & OuterExpressionKinds.ExcludeJSDocTypeAssertion && isJSDocTypeAssertion(node)) { + return false; + } + return (kinds & OuterExpressionKinds.Parentheses) !== 0; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.SatisfiesExpression: + return (kinds & OuterExpressionKinds.TypeAssertions) !== 0; + case SyntaxKind.NonNullExpression: + return (kinds & OuterExpressionKinds.NonNullAssertions) !== 0; + case SyntaxKind.PartiallyEmittedExpression: + return (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) !== 0; + } + return false; +} - export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression; - export function skipOuterExpressions(node: Node, kinds?: OuterExpressionKinds): Node; - export function skipOuterExpressions(node: Node, kinds = OuterExpressionKinds.All) { - while (isOuterExpression(node, kinds)) { - node = node.expression; - } - return node; +/** @internal */ +export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression; +/** @internal */ +export function skipOuterExpressions(node: Node, kinds?: OuterExpressionKinds): Node; +/** @internal */ +export function skipOuterExpressions(node: Node, kinds = OuterExpressionKinds.All) { + while (isOuterExpression(node, kinds)) { + node = node.expression; } + return node; +} - export function skipAssertions(node: Expression): Expression; - export function skipAssertions(node: Node): Node; - export function skipAssertions(node: Node): Node { - return skipOuterExpressions(node, OuterExpressionKinds.Assertions); - } +/** @internal */ +export function skipAssertions(node: Expression): Expression; +/** @internal */ +export function skipAssertions(node: Node): Node; +/** @internal */ +export function skipAssertions(node: Node): Node { + return skipOuterExpressions(node, OuterExpressionKinds.Assertions); +} - export function startOnNewLine(node: T): T { - return setStartsOnNewLine(node, /*newLine*/ true); - } +/** @internal */ +export function startOnNewLine(node: T): T { + return setStartsOnNewLine(node, /*newLine*/ true); +} - export function getExternalHelpersModuleName(node: SourceFile) { - const parseNode = getOriginalNode(node, isSourceFile); - const emitNode = parseNode && parseNode.emitNode; - return emitNode && emitNode.externalHelpersModuleName; - } +/** @internal */ +export function getExternalHelpersModuleName(node: SourceFile) { + const parseNode = getOriginalNode(node, isSourceFile); + const emitNode = parseNode && parseNode.emitNode; + return emitNode && emitNode.externalHelpersModuleName; +} - export function hasRecordedExternalHelpers(sourceFile: SourceFile) { - const parseNode = getOriginalNode(sourceFile, isSourceFile); - const emitNode = parseNode && parseNode.emitNode; - return !!emitNode && (!!emitNode.externalHelpersModuleName || !!emitNode.externalHelpers); - } +/** @internal */ +export function hasRecordedExternalHelpers(sourceFile: SourceFile) { + const parseNode = getOriginalNode(sourceFile, isSourceFile); + const emitNode = parseNode && parseNode.emitNode; + return !!emitNode && (!!emitNode.externalHelpersModuleName || !!emitNode.externalHelpers); +} - export function createExternalHelpersImportDeclarationIfNeeded(nodeFactory: NodeFactory, helperFactory: EmitHelperFactory, sourceFile: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStar?: boolean, hasImportDefault?: boolean) { - if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) { - let namedBindings: NamedImportBindings | undefined; - const moduleKind = getEmitModuleKind(compilerOptions); - if ((moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) || sourceFile.impliedNodeFormat === ModuleKind.ESNext) { - // use named imports - const helpers = getEmitHelpers(sourceFile); - if (helpers) { - const helperNames: string[] = []; - for (const helper of helpers) { - if (!helper.scoped) { - const importName = helper.importName; - if (importName) { - pushIfUnique(helperNames, importName); - } +/** @internal */ +export function createExternalHelpersImportDeclarationIfNeeded(nodeFactory: NodeFactory, helperFactory: EmitHelperFactory, sourceFile: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStar?: boolean, hasImportDefault?: boolean) { + if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) { + let namedBindings: NamedImportBindings | undefined; + const moduleKind = getEmitModuleKind(compilerOptions); + if ((moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) || sourceFile.impliedNodeFormat === ModuleKind.ESNext) { + // use named imports + const helpers = getEmitHelpers(sourceFile); + if (helpers) { + const helperNames: string[] = []; + for (const helper of helpers) { + if (!helper.scoped) { + const importName = helper.importName; + if (importName) { + pushIfUnique(helperNames, importName); } } - if (some(helperNames)) { - helperNames.sort(compareStringsCaseSensitive); - // Alias the imports if the names are used somewhere in the file. - // NOTE: We don't need to care about global import collisions as this is a module. - namedBindings = nodeFactory.createNamedImports( - map(helperNames, name => isFileLevelUniqueName(sourceFile, name) - ? nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, nodeFactory.createIdentifier(name)) - : nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, nodeFactory.createIdentifier(name), helperFactory.getUnscopedHelperName(name)) - ) - ); - const parseNode = getOriginalNode(sourceFile, isSourceFile); - const emitNode = getOrCreateEmitNode(parseNode); - emitNode.externalHelpers = true; - } } - } - else { - // use a namespace import - const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(nodeFactory, sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar || hasImportDefault); - if (externalHelpersModuleName) { - namedBindings = nodeFactory.createNamespaceImport(externalHelpersModuleName); + if (some(helperNames)) { + helperNames.sort(compareStringsCaseSensitive); + // Alias the imports if the names are used somewhere in the file. + // NOTE: We don't need to care about global import collisions as this is a module. + namedBindings = nodeFactory.createNamedImports( + map(helperNames, name => isFileLevelUniqueName(sourceFile, name) + ? nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, nodeFactory.createIdentifier(name)) + : nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, nodeFactory.createIdentifier(name), helperFactory.getUnscopedHelperName(name)) + ) + ); + const parseNode = getOriginalNode(sourceFile, isSourceFile); + const emitNode = getOrCreateEmitNode(parseNode); + emitNode.externalHelpers = true; } } - if (namedBindings) { - const externalHelpersImportDeclaration = nodeFactory.createImportDeclaration( - /*modifiers*/ undefined, - nodeFactory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, namedBindings), - nodeFactory.createStringLiteral(externalHelpersModuleNameText), - /*assertClause*/ undefined - ); - addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper); - return externalHelpersImportDeclaration; + } + else { + // use a namespace import + const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(nodeFactory, sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar || hasImportDefault); + if (externalHelpersModuleName) { + namedBindings = nodeFactory.createNamespaceImport(externalHelpersModuleName); } } + if (namedBindings) { + const externalHelpersImportDeclaration = nodeFactory.createImportDeclaration( + /*modifiers*/ undefined, + nodeFactory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, namedBindings), + nodeFactory.createStringLiteral(externalHelpersModuleNameText), + /*assertClause*/ undefined + ); + addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper); + return externalHelpersImportDeclaration; + } } +} - export function getOrCreateExternalHelpersModuleNameIfNeeded(factory: NodeFactory, node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) { - if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) { - const externalHelpersModuleName = getExternalHelpersModuleName(node); - if (externalHelpersModuleName) { - return externalHelpersModuleName; - } +/** @internal */ +export function getOrCreateExternalHelpersModuleNameIfNeeded(factory: NodeFactory, node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) { + if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) { + const externalHelpersModuleName = getExternalHelpersModuleName(node); + if (externalHelpersModuleName) { + return externalHelpersModuleName; + } - const moduleKind = getEmitModuleKind(compilerOptions); - let create = (hasExportStarsToExportValues || (getESModuleInterop(compilerOptions) && hasImportStarOrImportDefault)) - && moduleKind !== ModuleKind.System - && (moduleKind < ModuleKind.ES2015 || node.impliedNodeFormat === ModuleKind.CommonJS); - if (!create) { - const helpers = getEmitHelpers(node); - if (helpers) { - for (const helper of helpers) { - if (!helper.scoped) { - create = true; - break; - } + const moduleKind = getEmitModuleKind(compilerOptions); + let create = (hasExportStarsToExportValues || (getESModuleInterop(compilerOptions) && hasImportStarOrImportDefault)) + && moduleKind !== ModuleKind.System + && (moduleKind < ModuleKind.ES2015 || node.impliedNodeFormat === ModuleKind.CommonJS); + if (!create) { + const helpers = getEmitHelpers(node); + if (helpers) { + for (const helper of helpers) { + if (!helper.scoped) { + create = true; + break; } } } - - if (create) { - const parseNode = getOriginalNode(node, isSourceFile); - const emitNode = getOrCreateEmitNode(parseNode); - return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = factory.createUniqueName(externalHelpersModuleNameText)); - } } - } - /** - * Get the name of that target module from an import or export declaration - */ - export function getLocalNameForExternalImport(factory: NodeFactory, node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile): Identifier | undefined { - const namespaceDeclaration = getNamespaceDeclarationNode(node); - if (namespaceDeclaration && !isDefaultImport(node) && !isExportNamespaceAsDefaultDeclaration(node)) { - const name = namespaceDeclaration.name; - return isGeneratedIdentifier(name) ? name : factory.createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name)); + if (create) { + const parseNode = getOriginalNode(node, isSourceFile); + const emitNode = getOrCreateEmitNode(parseNode); + return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = factory.createUniqueName(externalHelpersModuleNameText)); } - if (node.kind === SyntaxKind.ImportDeclaration && node.importClause) { - return factory.getGeneratedNameForNode(node); - } - if (node.kind === SyntaxKind.ExportDeclaration && node.moduleSpecifier) { - return factory.getGeneratedNameForNode(node); - } - return undefined; } +} - /** - * Get the name of a target module from an import/export declaration as should be written in the emitted output. - * The emitted output name can be different from the input if: - * 1. The module has a /// - * 2. --out or --outFile is used, making the name relative to the rootDir - * 3- The containing SourceFile has an entry in renamedDependencies for the import as requested by some module loaders (e.g. System). - * Otherwise, a new StringLiteral node representing the module name will be returned. - */ - export function getExternalModuleNameLiteral(factory: NodeFactory, importNode: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration | ImportCall, sourceFile: SourceFile, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) { - const moduleName = getExternalModuleName(importNode); - if (moduleName && isStringLiteral(moduleName)) { - return tryGetModuleNameFromDeclaration(importNode, host, factory, resolver, compilerOptions) - || tryRenameExternalModule(factory, moduleName, sourceFile) - || factory.cloneNode(moduleName); - } - - return undefined; +/** + * Get the name of that target module from an import or export declaration + * + * @internal + */ +export function getLocalNameForExternalImport(factory: NodeFactory, node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile): Identifier | undefined { + const namespaceDeclaration = getNamespaceDeclarationNode(node); + if (namespaceDeclaration && !isDefaultImport(node) && !isExportNamespaceAsDefaultDeclaration(node)) { + const name = namespaceDeclaration.name; + return isGeneratedIdentifier(name) ? name : factory.createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name)); } - - /** - * Some bundlers (SystemJS builder) sometimes want to rename dependencies. - * Here we check if alternative name was provided for a given moduleName and return it if possible. - */ - function tryRenameExternalModule(factory: NodeFactory, moduleName: LiteralExpression, sourceFile: SourceFile) { - const rename = sourceFile.renamedDependencies && sourceFile.renamedDependencies.get(moduleName.text); - return rename ? factory.createStringLiteral(rename) : undefined; + if (node.kind === SyntaxKind.ImportDeclaration && node.importClause) { + return factory.getGeneratedNameForNode(node); } - - /** - * Get the name of a module as should be written in the emitted output. - * The emitted output name can be different from the input if: - * 1. The module has a /// - * 2. --out or --outFile is used, making the name relative to the rootDir - * Otherwise, a new StringLiteral node representing the module name will be returned. - */ - export function tryGetModuleNameFromFile(factory: NodeFactory, file: SourceFile | undefined, host: EmitHost, options: CompilerOptions): StringLiteral | undefined { - if (!file) { - return undefined; - } - if (file.moduleName) { - return factory.createStringLiteral(file.moduleName); - } - if (!file.isDeclarationFile && outFile(options)) { - return factory.createStringLiteral(getExternalModuleNameFromPath(host, file.fileName)); - } - return undefined; + if (node.kind === SyntaxKind.ExportDeclaration && node.moduleSpecifier) { + return factory.getGeneratedNameForNode(node); } + return undefined; +} - function tryGetModuleNameFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ImportCall, host: EmitHost, factory: NodeFactory, resolver: EmitResolver, compilerOptions: CompilerOptions) { - return tryGetModuleNameFromFile(factory, resolver.getExternalModuleFileFromDeclaration(declaration), host, compilerOptions); - } - - /** - * Gets the initializer of an BindingOrAssignmentElement. - */ - export function getInitializerOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Expression | undefined { - if (isDeclarationBindingElement(bindingElement)) { - // `1` in `let { a = 1 } = ...` - // `1` in `let { a: b = 1 } = ...` - // `1` in `let { a: {b} = 1 } = ...` - // `1` in `let { a: [b] = 1 } = ...` - // `1` in `let [a = 1] = ...` - // `1` in `let [{a} = 1] = ...` - // `1` in `let [[a] = 1] = ...` - return bindingElement.initializer; - } - - if (isPropertyAssignment(bindingElement)) { - // `1` in `({ a: b = 1 } = ...)` - // `1` in `({ a: {b} = 1 } = ...)` - // `1` in `({ a: [b] = 1 } = ...)` - const initializer = bindingElement.initializer; - return isAssignmentExpression(initializer, /*excludeCompoundAssignment*/ true) - ? initializer.right - : undefined; - } - - if (isShorthandPropertyAssignment(bindingElement)) { - // `1` in `({ a = 1 } = ...)` - return bindingElement.objectAssignmentInitializer; - } +/** + * Get the name of a target module from an import/export declaration as should be written in the emitted output. + * The emitted output name can be different from the input if: + * 1. The module has a /// + * 2. --out or --outFile is used, making the name relative to the rootDir + * 3- The containing SourceFile has an entry in renamedDependencies for the import as requested by some module loaders (e.g. System). + * Otherwise, a new StringLiteral node representing the module name will be returned. + * + * @internal + */ +export function getExternalModuleNameLiteral(factory: NodeFactory, importNode: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration | ImportCall, sourceFile: SourceFile, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) { + const moduleName = getExternalModuleName(importNode); + if (moduleName && isStringLiteral(moduleName)) { + return tryGetModuleNameFromDeclaration(importNode, host, factory, resolver, compilerOptions) + || tryRenameExternalModule(factory, moduleName, sourceFile) + || factory.cloneNode(moduleName); + } + + return undefined; +} - if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { - // `1` in `[a = 1] = ...` - // `1` in `[{a} = 1] = ...` - // `1` in `[[a] = 1] = ...` - return bindingElement.right; - } +/** + * Some bundlers (SystemJS builder) sometimes want to rename dependencies. + * Here we check if alternative name was provided for a given moduleName and return it if possible. + */ +function tryRenameExternalModule(factory: NodeFactory, moduleName: LiteralExpression, sourceFile: SourceFile) { + const rename = sourceFile.renamedDependencies && sourceFile.renamedDependencies.get(moduleName.text); + return rename ? factory.createStringLiteral(rename) : undefined; +} - if (isSpreadElement(bindingElement)) { - // Recovery consistent with existing emit. - return getInitializerOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); - } +/** + * Get the name of a module as should be written in the emitted output. + * The emitted output name can be different from the input if: + * 1. The module has a /// + * 2. --out or --outFile is used, making the name relative to the rootDir + * Otherwise, a new StringLiteral node representing the module name will be returned. + * + * @internal + */ +export function tryGetModuleNameFromFile(factory: NodeFactory, file: SourceFile | undefined, host: EmitHost, options: CompilerOptions): StringLiteral | undefined { + if (!file) { + return undefined; } - - /** - * Gets the name of an BindingOrAssignmentElement. - */ - export function getTargetOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementTarget | undefined { - if (isDeclarationBindingElement(bindingElement)) { - // `a` in `let { a } = ...` - // `a` in `let { a = 1 } = ...` - // `b` in `let { a: b } = ...` - // `b` in `let { a: b = 1 } = ...` - // `a` in `let { ...a } = ...` - // `{b}` in `let { a: {b} } = ...` - // `{b}` in `let { a: {b} = 1 } = ...` - // `[b]` in `let { a: [b] } = ...` - // `[b]` in `let { a: [b] = 1 } = ...` - // `a` in `let [a] = ...` - // `a` in `let [a = 1] = ...` - // `a` in `let [...a] = ...` - // `{a}` in `let [{a}] = ...` - // `{a}` in `let [{a} = 1] = ...` - // `[a]` in `let [[a]] = ...` - // `[a]` in `let [[a] = 1] = ...` - return bindingElement.name; - } - - if (isObjectLiteralElementLike(bindingElement)) { - switch (bindingElement.kind) { - case SyntaxKind.PropertyAssignment: - // `b` in `({ a: b } = ...)` - // `b` in `({ a: b = 1 } = ...)` - // `{b}` in `({ a: {b} } = ...)` - // `{b}` in `({ a: {b} = 1 } = ...)` - // `[b]` in `({ a: [b] } = ...)` - // `[b]` in `({ a: [b] = 1 } = ...)` - // `b.c` in `({ a: b.c } = ...)` - // `b.c` in `({ a: b.c = 1 } = ...)` - // `b[0]` in `({ a: b[0] } = ...)` - // `b[0]` in `({ a: b[0] = 1 } = ...)` - return getTargetOfBindingOrAssignmentElement(bindingElement.initializer as BindingOrAssignmentElement); - - case SyntaxKind.ShorthandPropertyAssignment: - // `a` in `({ a } = ...)` - // `a` in `({ a = 1 } = ...)` - return bindingElement.name; - - case SyntaxKind.SpreadAssignment: - // `a` in `({ ...a } = ...)` - return getTargetOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); - } - - // no target - return undefined; - } - - if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { - // `a` in `[a = 1] = ...` - // `{a}` in `[{a} = 1] = ...` - // `[a]` in `[[a] = 1] = ...` - // `a.b` in `[a.b = 1] = ...` - // `a[0]` in `[a[0] = 1] = ...` - return getTargetOfBindingOrAssignmentElement(bindingElement.left as BindingOrAssignmentElement); - } - - if (isSpreadElement(bindingElement)) { - // `a` in `[...a] = ...` - return getTargetOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); - } - - // `a` in `[a] = ...` - // `{a}` in `[{a}] = ...` - // `[a]` in `[[a]] = ...` - // `a.b` in `[a.b] = ...` - // `a[0]` in `[a[0]] = ...` - return bindingElement; + if (file.moduleName) { + return factory.createStringLiteral(file.moduleName); } - - /** - * Determines whether an BindingOrAssignmentElement is a rest element. - */ - export function getRestIndicatorOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementRestIndicator | undefined { - switch (bindingElement.kind) { - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - // `...` in `let [...a] = ...` - return bindingElement.dotDotDotToken; - - case SyntaxKind.SpreadElement: - case SyntaxKind.SpreadAssignment: - // `...` in `[...a] = ...` - return bindingElement; - } - - return undefined; + if (!file.isDeclarationFile && outFile(options)) { + return factory.createStringLiteral(getExternalModuleNameFromPath(host, file.fileName)); } + return undefined; +} - /** - * Gets the property name of a BindingOrAssignmentElement - */ - export function getPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Exclude | undefined { - const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement); - Debug.assert(!!propertyName || isSpreadAssignment(bindingElement), "Invalid property name for binding element."); - return propertyName; +function tryGetModuleNameFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ImportCall, host: EmitHost, factory: NodeFactory, resolver: EmitResolver, compilerOptions: CompilerOptions) { + return tryGetModuleNameFromFile(factory, resolver.getExternalModuleFileFromDeclaration(declaration), host, compilerOptions); +} + +/** + * Gets the initializer of an BindingOrAssignmentElement. + * + * @internal + */ +export function getInitializerOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Expression | undefined { + if (isDeclarationBindingElement(bindingElement)) { + // `1` in `let { a = 1 } = ...` + // `1` in `let { a: b = 1 } = ...` + // `1` in `let { a: {b} = 1 } = ...` + // `1` in `let { a: [b] = 1 } = ...` + // `1` in `let [a = 1] = ...` + // `1` in `let [{a} = 1] = ...` + // `1` in `let [[a] = 1] = ...` + return bindingElement.initializer; + } + + if (isPropertyAssignment(bindingElement)) { + // `1` in `({ a: b = 1 } = ...)` + // `1` in `({ a: {b} = 1 } = ...)` + // `1` in `({ a: [b] = 1 } = ...)` + const initializer = bindingElement.initializer; + return isAssignmentExpression(initializer, /*excludeCompoundAssignment*/ true) + ? initializer.right + : undefined; + } + + if (isShorthandPropertyAssignment(bindingElement)) { + // `1` in `({ a = 1 } = ...)` + return bindingElement.objectAssignmentInitializer; + } + + if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { + // `1` in `[a = 1] = ...` + // `1` in `[{a} = 1] = ...` + // `1` in `[[a] = 1] = ...` + return bindingElement.right; + } + + if (isSpreadElement(bindingElement)) { + // Recovery consistent with existing emit. + return getInitializerOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); } +} - export function tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Exclude | undefined { +/** + * Gets the name of an BindingOrAssignmentElement. + * + * @internal + */ +export function getTargetOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementTarget | undefined { + if (isDeclarationBindingElement(bindingElement)) { + // `a` in `let { a } = ...` + // `a` in `let { a = 1 } = ...` + // `b` in `let { a: b } = ...` + // `b` in `let { a: b = 1 } = ...` + // `a` in `let { ...a } = ...` + // `{b}` in `let { a: {b} } = ...` + // `{b}` in `let { a: {b} = 1 } = ...` + // `[b]` in `let { a: [b] } = ...` + // `[b]` in `let { a: [b] = 1 } = ...` + // `a` in `let [a] = ...` + // `a` in `let [a = 1] = ...` + // `a` in `let [...a] = ...` + // `{a}` in `let [{a}] = ...` + // `{a}` in `let [{a} = 1] = ...` + // `[a]` in `let [[a]] = ...` + // `[a]` in `let [[a] = 1] = ...` + return bindingElement.name; + } + + if (isObjectLiteralElementLike(bindingElement)) { switch (bindingElement.kind) { - case SyntaxKind.BindingElement: - // `a` in `let { a: b } = ...` - // `[a]` in `let { [a]: b } = ...` - // `"a"` in `let { "a": b } = ...` - // `1` in `let { 1: b } = ...` - if (bindingElement.propertyName) { - const propertyName = bindingElement.propertyName; - if (isPrivateIdentifier(propertyName)) { - return Debug.failBadSyntaxKind(propertyName); - } - return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) - ? propertyName.expression - : propertyName; - } - - break; - case SyntaxKind.PropertyAssignment: - // `a` in `({ a: b } = ...)` - // `[a]` in `({ [a]: b } = ...)` - // `"a"` in `({ "a": b } = ...)` - // `1` in `({ 1: b } = ...)` - if (bindingElement.name) { - const propertyName = bindingElement.name; - if (isPrivateIdentifier(propertyName)) { - return Debug.failBadSyntaxKind(propertyName); - } - return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) - ? propertyName.expression - : propertyName; - } + // `b` in `({ a: b } = ...)` + // `b` in `({ a: b = 1 } = ...)` + // `{b}` in `({ a: {b} } = ...)` + // `{b}` in `({ a: {b} = 1 } = ...)` + // `[b]` in `({ a: [b] } = ...)` + // `[b]` in `({ a: [b] = 1 } = ...)` + // `b.c` in `({ a: b.c } = ...)` + // `b.c` in `({ a: b.c = 1 } = ...)` + // `b[0]` in `({ a: b[0] } = ...)` + // `b[0]` in `({ a: b[0] = 1 } = ...)` + return getTargetOfBindingOrAssignmentElement(bindingElement.initializer as BindingOrAssignmentElement); - break; + case SyntaxKind.ShorthandPropertyAssignment: + // `a` in `({ a } = ...)` + // `a` in `({ a = 1 } = ...)` + return bindingElement.name; case SyntaxKind.SpreadAssignment: // `a` in `({ ...a } = ...)` - if (bindingElement.name && isPrivateIdentifier(bindingElement.name)) { - return Debug.failBadSyntaxKind(bindingElement.name); - } - return bindingElement.name; + return getTargetOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); } - const target = getTargetOfBindingOrAssignmentElement(bindingElement); - if (target && isPropertyName(target)) { - return target; - } + // no target + return undefined; } - function isStringOrNumericLiteral(node: Node): node is StringLiteral | NumericLiteral { - const kind = node.kind; - return kind === SyntaxKind.StringLiteral - || kind === SyntaxKind.NumericLiteral; + if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) { + // `a` in `[a = 1] = ...` + // `{a}` in `[{a} = 1] = ...` + // `[a]` in `[[a] = 1] = ...` + // `a.b` in `[a.b = 1] = ...` + // `a[0]` in `[a[0] = 1] = ...` + return getTargetOfBindingOrAssignmentElement(bindingElement.left as BindingOrAssignmentElement); } - /** - * Gets the elements of a BindingOrAssignmentPattern - */ - export function getElementsOfBindingOrAssignmentPattern(name: BindingOrAssignmentPattern): readonly BindingOrAssignmentElement[] { - switch (name.kind) { - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ArrayLiteralExpression: - // `a` in `{a}` - // `a` in `[a]` - return name.elements as readonly BindingOrAssignmentElement[]; - - case SyntaxKind.ObjectLiteralExpression: - // `a` in `{a}` - return name.properties as readonly BindingOrAssignmentElement[]; - } + if (isSpreadElement(bindingElement)) { + // `a` in `[...a] = ...` + return getTargetOfBindingOrAssignmentElement(bindingElement.expression as BindingOrAssignmentElement); } - /* @internal */ - export function getJSDocTypeAliasName(fullName: JSDocNamespaceBody | undefined) { - if (fullName) { - let rightNode = fullName; - while (true) { - if (isIdentifier(rightNode) || !rightNode.body) { - return isIdentifier(rightNode) ? rightNode : rightNode.name; + // `a` in `[a] = ...` + // `{a}` in `[{a}] = ...` + // `[a]` in `[[a]] = ...` + // `a.b` in `[a.b] = ...` + // `a[0]` in `[a[0]] = ...` + return bindingElement; +} + +/** + * Determines whether an BindingOrAssignmentElement is a rest element. + * + * @internal + */ +export function getRestIndicatorOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementRestIndicator | undefined { + switch (bindingElement.kind) { + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + // `...` in `let [...a] = ...` + return bindingElement.dotDotDotToken; + + case SyntaxKind.SpreadElement: + case SyntaxKind.SpreadAssignment: + // `...` in `[...a] = ...` + return bindingElement; + } + + return undefined; +} + +/** + * Gets the property name of a BindingOrAssignmentElement + * + * @internal + */ +export function getPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Exclude | undefined { + const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement); + Debug.assert(!!propertyName || isSpreadAssignment(bindingElement), "Invalid property name for binding element."); + return propertyName; +} + +/** @internal */ +export function tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Exclude | undefined { + switch (bindingElement.kind) { + case SyntaxKind.BindingElement: + // `a` in `let { a: b } = ...` + // `[a]` in `let { [a]: b } = ...` + // `"a"` in `let { "a": b } = ...` + // `1` in `let { 1: b } = ...` + if (bindingElement.propertyName) { + const propertyName = bindingElement.propertyName; + if (isPrivateIdentifier(propertyName)) { + return Debug.failBadSyntaxKind(propertyName); } - rightNode = rightNode.body; + return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) + ? propertyName.expression + : propertyName; } - } - } - export function canHaveIllegalType(node: Node): node is HasIllegalType { - const kind = node.kind; - return kind === SyntaxKind.Constructor - || kind === SyntaxKind.SetAccessor; - } + break; + + case SyntaxKind.PropertyAssignment: + // `a` in `({ a: b } = ...)` + // `[a]` in `({ [a]: b } = ...)` + // `"a"` in `({ "a": b } = ...)` + // `1` in `({ 1: b } = ...)` + if (bindingElement.name) { + const propertyName = bindingElement.name; + if (isPrivateIdentifier(propertyName)) { + return Debug.failBadSyntaxKind(propertyName); + } + return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression) + ? propertyName.expression + : propertyName; + } - export function canHaveIllegalTypeParameters(node: Node): node is HasIllegalTypeParameters { - const kind = node.kind; - return kind === SyntaxKind.Constructor - || kind === SyntaxKind.GetAccessor - || kind === SyntaxKind.SetAccessor; - } + break; - export function canHaveIllegalDecorators(node: Node): node is HasIllegalDecorators { - const kind = node.kind; - return kind === SyntaxKind.PropertyAssignment - || kind === SyntaxKind.ShorthandPropertyAssignment - || kind === SyntaxKind.FunctionDeclaration - || kind === SyntaxKind.Constructor - || kind === SyntaxKind.IndexSignature - || kind === SyntaxKind.ClassStaticBlockDeclaration - || kind === SyntaxKind.MissingDeclaration - || kind === SyntaxKind.VariableStatement - || kind === SyntaxKind.InterfaceDeclaration - || kind === SyntaxKind.TypeAliasDeclaration - || kind === SyntaxKind.EnumDeclaration - || kind === SyntaxKind.ModuleDeclaration - || kind === SyntaxKind.ImportEqualsDeclaration - || kind === SyntaxKind.ImportDeclaration - || kind === SyntaxKind.NamespaceExportDeclaration - || kind === SyntaxKind.ExportDeclaration - || kind === SyntaxKind.ExportAssignment; + case SyntaxKind.SpreadAssignment: + // `a` in `({ ...a } = ...)` + if (bindingElement.name && isPrivateIdentifier(bindingElement.name)) { + return Debug.failBadSyntaxKind(bindingElement.name); + } + return bindingElement.name; } - export function canHaveIllegalModifiers(node: Node): node is HasIllegalModifiers { - const kind = node.kind; - return kind === SyntaxKind.ClassStaticBlockDeclaration - || kind === SyntaxKind.PropertyAssignment - || kind === SyntaxKind.ShorthandPropertyAssignment - || kind === SyntaxKind.FunctionType - || kind === SyntaxKind.MissingDeclaration - || kind === SyntaxKind.NamespaceExportDeclaration; + const target = getTargetOfBindingOrAssignmentElement(bindingElement); + if (target && isPropertyName(target)) { + return target; } +} - export const isTypeNodeOrTypeParameterDeclaration = or(isTypeNode, isTypeParameterDeclaration) as (node: Node) => node is TypeNode | TypeParameterDeclaration; - export const isQuestionOrExclamationToken = or(isQuestionToken, isExclamationToken) as (node: Node) => node is QuestionToken | ExclamationToken; - export const isIdentifierOrThisTypeNode = or(isIdentifier, isThisTypeNode) as (node: Node) => node is Identifier | ThisTypeNode; - export const isReadonlyKeywordOrPlusOrMinusToken = or(isReadonlyKeyword, isPlusToken, isMinusToken) as (node: Node) => node is ReadonlyKeyword | PlusToken | MinusToken; - export const isQuestionOrPlusOrMinusToken = or(isQuestionToken, isPlusToken, isMinusToken) as (node: Node) => node is QuestionToken | PlusToken | MinusToken; - export const isModuleName = or(isIdentifier, isStringLiteral) as (node: Node) => node is ModuleName; - - export function isLiteralTypeLikeExpression(node: Node): node is NullLiteral | BooleanLiteral | LiteralExpression | PrefixUnaryExpression { - const kind = node.kind; - return kind === SyntaxKind.NullKeyword - || kind === SyntaxKind.TrueKeyword - || kind === SyntaxKind.FalseKeyword - || isLiteralExpression(node) - || isPrefixUnaryExpression(node); - } +function isStringOrNumericLiteral(node: Node): node is StringLiteral | NumericLiteral { + const kind = node.kind; + return kind === SyntaxKind.StringLiteral + || kind === SyntaxKind.NumericLiteral; +} - function isExponentiationOperator(kind: SyntaxKind): kind is ExponentiationOperator { - return kind === SyntaxKind.AsteriskAsteriskToken; +/** + * Gets the elements of a BindingOrAssignmentPattern + * + * @internal + */ +export function getElementsOfBindingOrAssignmentPattern(name: BindingOrAssignmentPattern): readonly BindingOrAssignmentElement[] { + switch (name.kind) { + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ArrayLiteralExpression: + // `a` in `{a}` + // `a` in `[a]` + return name.elements as readonly BindingOrAssignmentElement[]; + + case SyntaxKind.ObjectLiteralExpression: + // `a` in `{a}` + return name.properties as readonly BindingOrAssignmentElement[]; } +} - function isMultiplicativeOperator(kind: SyntaxKind): kind is MultiplicativeOperator { - return kind === SyntaxKind.AsteriskToken - || kind === SyntaxKind.SlashToken - || kind === SyntaxKind.PercentToken; +/** @internal */ +export function getJSDocTypeAliasName(fullName: JSDocNamespaceBody | undefined) { + if (fullName) { + let rightNode = fullName; + while (true) { + if (isIdentifier(rightNode) || !rightNode.body) { + return isIdentifier(rightNode) ? rightNode : rightNode.name; + } + rightNode = rightNode.body; + } } +} - function isMultiplicativeOperatorOrHigher(kind: SyntaxKind): kind is MultiplicativeOperatorOrHigher { - return isExponentiationOperator(kind) - || isMultiplicativeOperator(kind); - } +/** @internal */ +export function canHaveIllegalType(node: Node): node is HasIllegalType { + const kind = node.kind; + return kind === SyntaxKind.Constructor + || kind === SyntaxKind.SetAccessor; +} - function isAdditiveOperator(kind: SyntaxKind): kind is AdditiveOperator { - return kind === SyntaxKind.PlusToken - || kind === SyntaxKind.MinusToken; - } +/** @internal */ +export function canHaveIllegalTypeParameters(node: Node): node is HasIllegalTypeParameters { + const kind = node.kind; + return kind === SyntaxKind.Constructor + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor; +} - function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher { - return isAdditiveOperator(kind) - || isMultiplicativeOperatorOrHigher(kind); - } +/** @internal */ +export function canHaveIllegalDecorators(node: Node): node is HasIllegalDecorators { + const kind = node.kind; + return kind === SyntaxKind.PropertyAssignment + || kind === SyntaxKind.ShorthandPropertyAssignment + || kind === SyntaxKind.FunctionDeclaration + || kind === SyntaxKind.Constructor + || kind === SyntaxKind.IndexSignature + || kind === SyntaxKind.ClassStaticBlockDeclaration + || kind === SyntaxKind.MissingDeclaration + || kind === SyntaxKind.VariableStatement + || kind === SyntaxKind.InterfaceDeclaration + || kind === SyntaxKind.TypeAliasDeclaration + || kind === SyntaxKind.EnumDeclaration + || kind === SyntaxKind.ModuleDeclaration + || kind === SyntaxKind.ImportEqualsDeclaration + || kind === SyntaxKind.ImportDeclaration + || kind === SyntaxKind.NamespaceExportDeclaration + || kind === SyntaxKind.ExportDeclaration + || kind === SyntaxKind.ExportAssignment; +} - function isShiftOperator(kind: SyntaxKind): kind is ShiftOperator { - return kind === SyntaxKind.LessThanLessThanToken - || kind === SyntaxKind.GreaterThanGreaterThanToken - || kind === SyntaxKind.GreaterThanGreaterThanGreaterThanToken; - } +/** @internal */ +export function canHaveIllegalModifiers(node: Node): node is HasIllegalModifiers { + const kind = node.kind; + return kind === SyntaxKind.ClassStaticBlockDeclaration + || kind === SyntaxKind.PropertyAssignment + || kind === SyntaxKind.ShorthandPropertyAssignment + || kind === SyntaxKind.FunctionType + || kind === SyntaxKind.MissingDeclaration + || kind === SyntaxKind.NamespaceExportDeclaration; +} - function isShiftOperatorOrHigher(kind: SyntaxKind): kind is ShiftOperatorOrHigher { - return isShiftOperator(kind) - || isAdditiveOperatorOrHigher(kind); - } +/** @internal */ +export const isTypeNodeOrTypeParameterDeclaration = or(isTypeNode, isTypeParameterDeclaration) as (node: Node) => node is TypeNode | TypeParameterDeclaration; +/** @internal */ +export const isQuestionOrExclamationToken = or(isQuestionToken, isExclamationToken) as (node: Node) => node is QuestionToken | ExclamationToken; +/** @internal */ +export const isIdentifierOrThisTypeNode = or(isIdentifier, isThisTypeNode) as (node: Node) => node is Identifier | ThisTypeNode; +/** @internal */ +export const isReadonlyKeywordOrPlusOrMinusToken = or(isReadonlyKeyword, isPlusToken, isMinusToken) as (node: Node) => node is ReadonlyKeyword | PlusToken | MinusToken; +/** @internal */ +export const isQuestionOrPlusOrMinusToken = or(isQuestionToken, isPlusToken, isMinusToken) as (node: Node) => node is QuestionToken | PlusToken | MinusToken; +/** @internal */ +export const isModuleName = or(isIdentifier, isStringLiteral) as (node: Node) => node is ModuleName; + +/** @internal */ +export function isLiteralTypeLikeExpression(node: Node): node is NullLiteral | BooleanLiteral | LiteralExpression | PrefixUnaryExpression { + const kind = node.kind; + return kind === SyntaxKind.NullKeyword + || kind === SyntaxKind.TrueKeyword + || kind === SyntaxKind.FalseKeyword + || isLiteralExpression(node) + || isPrefixUnaryExpression(node); +} - function isRelationalOperator(kind: SyntaxKind): kind is RelationalOperator { - return kind === SyntaxKind.LessThanToken - || kind === SyntaxKind.LessThanEqualsToken - || kind === SyntaxKind.GreaterThanToken - || kind === SyntaxKind.GreaterThanEqualsToken - || kind === SyntaxKind.InstanceOfKeyword - || kind === SyntaxKind.InKeyword; - } +function isExponentiationOperator(kind: SyntaxKind): kind is ExponentiationOperator { + return kind === SyntaxKind.AsteriskAsteriskToken; +} - function isRelationalOperatorOrHigher(kind: SyntaxKind): kind is RelationalOperatorOrHigher { - return isRelationalOperator(kind) - || isShiftOperatorOrHigher(kind); - } +function isMultiplicativeOperator(kind: SyntaxKind): kind is MultiplicativeOperator { + return kind === SyntaxKind.AsteriskToken + || kind === SyntaxKind.SlashToken + || kind === SyntaxKind.PercentToken; +} - function isEqualityOperator(kind: SyntaxKind): kind is EqualityOperator { - return kind === SyntaxKind.EqualsEqualsToken - || kind === SyntaxKind.EqualsEqualsEqualsToken - || kind === SyntaxKind.ExclamationEqualsToken - || kind === SyntaxKind.ExclamationEqualsEqualsToken; - } +function isMultiplicativeOperatorOrHigher(kind: SyntaxKind): kind is MultiplicativeOperatorOrHigher { + return isExponentiationOperator(kind) + || isMultiplicativeOperator(kind); +} - function isEqualityOperatorOrHigher(kind: SyntaxKind): kind is EqualityOperatorOrHigher { - return isEqualityOperator(kind) - || isRelationalOperatorOrHigher(kind); - } +function isAdditiveOperator(kind: SyntaxKind): kind is AdditiveOperator { + return kind === SyntaxKind.PlusToken + || kind === SyntaxKind.MinusToken; +} - function isBitwiseOperator(kind: SyntaxKind): kind is BitwiseOperator { - return kind === SyntaxKind.AmpersandToken - || kind === SyntaxKind.BarToken - || kind === SyntaxKind.CaretToken; - } +function isAdditiveOperatorOrHigher(kind: SyntaxKind): kind is AdditiveOperatorOrHigher { + return isAdditiveOperator(kind) + || isMultiplicativeOperatorOrHigher(kind); +} - function isBitwiseOperatorOrHigher(kind: SyntaxKind): kind is BitwiseOperatorOrHigher { - return isBitwiseOperator(kind) - || isEqualityOperatorOrHigher(kind); - } +function isShiftOperator(kind: SyntaxKind): kind is ShiftOperator { + return kind === SyntaxKind.LessThanLessThanToken + || kind === SyntaxKind.GreaterThanGreaterThanToken + || kind === SyntaxKind.GreaterThanGreaterThanGreaterThanToken; +} - // NOTE: The version in utilities includes ExclamationToken, which is not a binary operator. - function isLogicalOperator(kind: SyntaxKind): kind is LogicalOperator { - return kind === SyntaxKind.AmpersandAmpersandToken - || kind === SyntaxKind.BarBarToken; - } +function isShiftOperatorOrHigher(kind: SyntaxKind): kind is ShiftOperatorOrHigher { + return isShiftOperator(kind) + || isAdditiveOperatorOrHigher(kind); +} - function isLogicalOperatorOrHigher(kind: SyntaxKind): kind is LogicalOperatorOrHigher { - return isLogicalOperator(kind) - || isBitwiseOperatorOrHigher(kind); - } +function isRelationalOperator(kind: SyntaxKind): kind is RelationalOperator { + return kind === SyntaxKind.LessThanToken + || kind === SyntaxKind.LessThanEqualsToken + || kind === SyntaxKind.GreaterThanToken + || kind === SyntaxKind.GreaterThanEqualsToken + || kind === SyntaxKind.InstanceOfKeyword + || kind === SyntaxKind.InKeyword; +} - function isAssignmentOperatorOrHigher(kind: SyntaxKind): kind is AssignmentOperatorOrHigher { - return kind === SyntaxKind.QuestionQuestionToken - || isLogicalOperatorOrHigher(kind) - || isAssignmentOperator(kind); - } +function isRelationalOperatorOrHigher(kind: SyntaxKind): kind is RelationalOperatorOrHigher { + return isRelationalOperator(kind) + || isShiftOperatorOrHigher(kind); +} - function isBinaryOperator(kind: SyntaxKind): kind is BinaryOperator { - return isAssignmentOperatorOrHigher(kind) - || kind === SyntaxKind.CommaToken; - } +function isEqualityOperator(kind: SyntaxKind): kind is EqualityOperator { + return kind === SyntaxKind.EqualsEqualsToken + || kind === SyntaxKind.EqualsEqualsEqualsToken + || kind === SyntaxKind.ExclamationEqualsToken + || kind === SyntaxKind.ExclamationEqualsEqualsToken; +} - export function isBinaryOperatorToken(node: Node): node is BinaryOperatorToken { - return isBinaryOperator(node.kind); - } +function isEqualityOperatorOrHigher(kind: SyntaxKind): kind is EqualityOperatorOrHigher { + return isEqualityOperator(kind) + || isRelationalOperatorOrHigher(kind); +} - type BinaryExpressionState = (machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }, outerState: TOuterState) => number; - - namespace BinaryExpressionState { - /** - * Handles walking into a `BinaryExpression`. - * @param machine State machine handler functions - * @param frame The current frame - * @returns The new frame - */ - export function enter(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, outerState: TOuterState): number { - const prevUserState = stackIndex > 0 ? userStateStack[stackIndex - 1] : undefined; - Debug.assertEqual(stateStack[stackIndex], enter); - userStateStack[stackIndex] = machine.onEnter(nodeStack[stackIndex], prevUserState, outerState); - stateStack[stackIndex] = nextState(machine, enter); - return stackIndex; - } +function isBitwiseOperator(kind: SyntaxKind): kind is BitwiseOperator { + return kind === SyntaxKind.AmpersandToken + || kind === SyntaxKind.BarToken + || kind === SyntaxKind.CaretToken; +} - /** - * Handles walking the `left` side of a `BinaryExpression`. - * @param machine State machine handler functions - * @param frame The current frame - * @returns The new frame - */ - export function left(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { - Debug.assertEqual(stateStack[stackIndex], left); - Debug.assertIsDefined(machine.onLeft); - stateStack[stackIndex] = nextState(machine, left); - const nextNode = machine.onLeft(nodeStack[stackIndex].left, userStateStack[stackIndex], nodeStack[stackIndex]); - if (nextNode) { - checkCircularity(stackIndex, nodeStack, nextNode); - return pushStack(stackIndex, stateStack, nodeStack, userStateStack, nextNode); - } - return stackIndex; - } +function isBitwiseOperatorOrHigher(kind: SyntaxKind): kind is BitwiseOperatorOrHigher { + return isBitwiseOperator(kind) + || isEqualityOperatorOrHigher(kind); +} - /** - * Handles walking the `operatorToken` of a `BinaryExpression`. - * @param machine State machine handler functions - * @param frame The current frame - * @returns The new frame - */ - export function operator(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { - Debug.assertEqual(stateStack[stackIndex], operator); - Debug.assertIsDefined(machine.onOperator); - stateStack[stackIndex] = nextState(machine, operator); - machine.onOperator(nodeStack[stackIndex].operatorToken, userStateStack[stackIndex], nodeStack[stackIndex]); - return stackIndex; - } +// NOTE: The version in utilities includes ExclamationToken, which is not a binary operator. +function isLogicalOperator(kind: SyntaxKind): kind is LogicalOperator { + return kind === SyntaxKind.AmpersandAmpersandToken + || kind === SyntaxKind.BarBarToken; +} - /** - * Handles walking the `right` side of a `BinaryExpression`. - * @param machine State machine handler functions - * @param frame The current frame - * @returns The new frame - */ - export function right(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { - Debug.assertEqual(stateStack[stackIndex], right); - Debug.assertIsDefined(machine.onRight); - stateStack[stackIndex] = nextState(machine, right); - const nextNode = machine.onRight(nodeStack[stackIndex].right, userStateStack[stackIndex], nodeStack[stackIndex]); - if (nextNode) { - checkCircularity(stackIndex, nodeStack, nextNode); - return pushStack(stackIndex, stateStack, nodeStack, userStateStack, nextNode); - } - return stackIndex; - } +function isLogicalOperatorOrHigher(kind: SyntaxKind): kind is LogicalOperatorOrHigher { + return isLogicalOperator(kind) + || isBitwiseOperatorOrHigher(kind); +} - /** - * Handles walking out of a `BinaryExpression`. - * @param machine State machine handler functions - * @param frame The current frame - * @returns The new frame - */ - export function exit(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }, _outerState: TOuterState): number { - Debug.assertEqual(stateStack[stackIndex], exit); - stateStack[stackIndex] = nextState(machine, exit); - const result = machine.onExit(nodeStack[stackIndex], userStateStack[stackIndex]); - if (stackIndex > 0) { - stackIndex--; - if (machine.foldState) { - const side = stateStack[stackIndex] === exit ? "right" : "left"; - userStateStack[stackIndex] = machine.foldState(userStateStack[stackIndex], result, side); - } - } - else { - resultHolder.value = result; - } - return stackIndex; - } +function isAssignmentOperatorOrHigher(kind: SyntaxKind): kind is AssignmentOperatorOrHigher { + return kind === SyntaxKind.QuestionQuestionToken + || isLogicalOperatorOrHigher(kind) + || isAssignmentOperator(kind); +} - /** - * Handles a frame that is already done. - * @returns The `done` state. - */ - export function done(_machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], _nodeStack: BinaryExpression[], _userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { - Debug.assertEqual(stateStack[stackIndex], done); - return stackIndex; - } +function isBinaryOperator(kind: SyntaxKind): kind is BinaryOperator { + return isAssignmentOperatorOrHigher(kind) + || kind === SyntaxKind.CommaToken; +} - export function nextState(machine: BinaryExpressionStateMachine, currentState: BinaryExpressionState) { - switch (currentState) { - case enter: - if (machine.onLeft) return left; - // falls through - case left: - if (machine.onOperator) return operator; - // falls through - case operator: - if (machine.onRight) return right; - // falls through - case right: return exit; - case exit: return done; - case done: return done; - default: Debug.fail("Invalid state"); - } - } +/** @internal */ +export function isBinaryOperatorToken(node: Node): node is BinaryOperatorToken { + return isBinaryOperator(node.kind); +} - function pushStack(stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], node: BinaryExpression) { - stackIndex++; - stateStack[stackIndex] = enter; - nodeStack[stackIndex] = node; - userStateStack[stackIndex] = undefined!; - return stackIndex; - } +type BinaryExpressionState = (machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }, outerState: TOuterState) => number; - function checkCircularity(stackIndex: number, nodeStack: BinaryExpression[], node: BinaryExpression) { - if (Debug.shouldAssert(AssertionLevel.Aggressive)) { - while (stackIndex >= 0) { - Debug.assert(nodeStack[stackIndex] !== node, "Circular traversal detected."); - stackIndex--; - } - } - } +namespace BinaryExpressionState { + /** + * Handles walking into a `BinaryExpression`. + * @param machine State machine handler functions + * @param frame The current frame + * @returns The new frame + */ + export function enter(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, outerState: TOuterState): number { + const prevUserState = stackIndex > 0 ? userStateStack[stackIndex - 1] : undefined; + Debug.assertEqual(stateStack[stackIndex], enter); + userStateStack[stackIndex] = machine.onEnter(nodeStack[stackIndex], prevUserState, outerState); + stateStack[stackIndex] = nextState(machine, enter); + return stackIndex; } /** - * Holds state machine handler functions + * Handles walking the `left` side of a `BinaryExpression`. + * @param machine State machine handler functions + * @param frame The current frame + * @returns The new frame */ - class BinaryExpressionStateMachine { - constructor( - readonly onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, - readonly onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - readonly onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, - readonly onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - readonly onExit: (node: BinaryExpression, userState: TState) => TResult, - readonly foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, - ) { + export function left(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { + Debug.assertEqual(stateStack[stackIndex], left); + Debug.assertIsDefined(machine.onLeft); + stateStack[stackIndex] = nextState(machine, left); + const nextNode = machine.onLeft(nodeStack[stackIndex].left, userStateStack[stackIndex], nodeStack[stackIndex]); + if (nextNode) { + checkCircularity(stackIndex, nodeStack, nextNode); + return pushStack(stackIndex, stateStack, nodeStack, userStateStack, nextNode); } + return stackIndex; } /** - * Creates a state machine that walks a `BinaryExpression` using the heap to reduce call-stack depth on a large tree. - * @param onEnter Callback evaluated when entering a `BinaryExpression`. Returns new user-defined state to associate with the node while walking. - * @param onLeft Callback evaluated when walking the left side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the right side. - * @param onRight Callback evaluated when walking the right side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the end of the node. - * @param onExit Callback evaluated when exiting a `BinaryExpression`. The result returned will either be folded into the parent's state, or returned from the walker if at the top frame. - * @param foldState Callback evaluated when the result from a nested `onExit` should be folded into the state of that node's parent. - * @returns A function that walks a `BinaryExpression` node using the above callbacks, returning the result of the call to `onExit` from the outermost `BinaryExpression` node. + * Handles walking the `operatorToken` of a `BinaryExpression`. + * @param machine State machine handler functions + * @param frame The current frame + * @returns The new frame */ - export function createBinaryExpressionTrampoline( - onEnter: (node: BinaryExpression, prev: TState | undefined) => TState, - onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, - onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onExit: (node: BinaryExpression, userState: TState) => TResult, - foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, - ): (node: BinaryExpression) => TResult; - /** - * Creates a state machine that walks a `BinaryExpression` using the heap to reduce call-stack depth on a large tree. - * @param onEnter Callback evaluated when entering a `BinaryExpression`. Returns new user-defined state to associate with the node while walking. - * @param onLeft Callback evaluated when walking the left side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the right side. - * @param onRight Callback evaluated when walking the right side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the end of the node. - * @param onExit Callback evaluated when exiting a `BinaryExpression`. The result returned will either be folded into the parent's state, or returned from the walker if at the top frame. - * @param foldState Callback evaluated when the result from a nested `onExit` should be folded into the state of that node's parent. - * @returns A function that walks a `BinaryExpression` node using the above callbacks, returning the result of the call to `onExit` from the outermost `BinaryExpression` node. - */ - export function createBinaryExpressionTrampoline( - onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, - onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, - onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onExit: (node: BinaryExpression, userState: TState) => TResult, - foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, - ): (node: BinaryExpression, outerState: TOuterState) => TResult; - export function createBinaryExpressionTrampoline( - onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, - onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, - onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, - onExit: (node: BinaryExpression, userState: TState) => TResult, - foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, - ) { - const machine = new BinaryExpressionStateMachine(onEnter, onLeft, onOperator, onRight, onExit, foldState); - return trampoline; - - function trampoline(node: BinaryExpression, outerState?: TOuterState) { - const resultHolder: { value: TResult } = { value: undefined! }; - const stateStack: BinaryExpressionState[] = [BinaryExpressionState.enter]; - const nodeStack: BinaryExpression[] = [node]; - const userStateStack: TState[] = [undefined!]; - let stackIndex = 0; - while (stateStack[stackIndex] !== BinaryExpressionState.done) { - stackIndex = stateStack[stackIndex](machine, stackIndex, stateStack, nodeStack, userStateStack, resultHolder, outerState); - } - Debug.assertEqual(stackIndex, 0); - return resultHolder.value; - } + export function operator(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { + Debug.assertEqual(stateStack[stackIndex], operator); + Debug.assertIsDefined(machine.onOperator); + stateStack[stackIndex] = nextState(machine, operator); + machine.onOperator(nodeStack[stackIndex].operatorToken, userStateStack[stackIndex], nodeStack[stackIndex]); + return stackIndex; } /** - * If `nodes` is not undefined, creates an empty `NodeArray` that preserves the `pos` and `end` of `nodes`. - * @internal + * Handles walking the `right` side of a `BinaryExpression`. + * @param machine State machine handler functions + * @param frame The current frame + * @returns The new frame */ - export function elideNodes(factory: NodeFactory, nodes: NodeArray): NodeArray; - export function elideNodes(factory: NodeFactory, nodes: NodeArray | undefined): NodeArray | undefined; - export function elideNodes(factory: NodeFactory, nodes: NodeArray | undefined): NodeArray | undefined { - if (nodes === undefined) return undefined; - if (nodes.length === 0) return nodes; - return setTextRange(factory.createNodeArray([], nodes.hasTrailingComma), nodes); + export function right(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { + Debug.assertEqual(stateStack[stackIndex], right); + Debug.assertIsDefined(machine.onRight); + stateStack[stackIndex] = nextState(machine, right); + const nextNode = machine.onRight(nodeStack[stackIndex].right, userStateStack[stackIndex], nodeStack[stackIndex]); + if (nextNode) { + checkCircularity(stackIndex, nodeStack, nextNode); + return pushStack(stackIndex, stateStack, nodeStack, userStateStack, nextNode); + } + return stackIndex; } /** - * Gets the node from which a name should be generated. + * Handles walking out of a `BinaryExpression`. + * @param machine State machine handler functions + * @param frame The current frame + * @returns The new frame */ - export function getNodeForGeneratedName(name: GeneratedIdentifier | GeneratedPrivateIdentifier) { - if (name.autoGenerateFlags & GeneratedIdentifierFlags.Node) { - const autoGenerateId = name.autoGenerateId; - let node = name as Node; - let original = node.original; - while (original) { - node = original; - - // if "node" is a different generated name (having a different "autoGenerateId"), use it and stop traversing. - if (isMemberName(node) - && !!(node.autoGenerateFlags! & GeneratedIdentifierFlags.Node) - && node.autoGenerateId !== autoGenerateId) { - break; - } - - original = node.original; + export function exit(machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], resultHolder: { value: TResult }, _outerState: TOuterState): number { + Debug.assertEqual(stateStack[stackIndex], exit); + stateStack[stackIndex] = nextState(machine, exit); + const result = machine.onExit(nodeStack[stackIndex], userStateStack[stackIndex]); + if (stackIndex > 0) { + stackIndex--; + if (machine.foldState) { + const side = stateStack[stackIndex] === exit ? "right" : "left"; + userStateStack[stackIndex] = machine.foldState(userStateStack[stackIndex], result, side); } - // otherwise, return the original node for the source - return node; } - return name; + else { + resultHolder.value = result; + } + return stackIndex; } /** - * Formats a prefix or suffix of a generated name. + * Handles a frame that is already done. + * @returns The `done` state. */ - export function formatGeneratedNamePart(part: string | undefined): string; - /** - * Formats a prefix or suffix of a generated name. If the part is a {@link GeneratedNamePart}, calls {@link generateName} to format the source node. - */ - export function formatGeneratedNamePart(part: string | GeneratedNamePart | undefined, generateName: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string): string; - export function formatGeneratedNamePart(part: string | GeneratedNamePart | undefined, generateName?: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string): string { - return typeof part === "object" ? formatGeneratedName(/*privateName*/ false, part.prefix, part.node, part.suffix, generateName!) : - typeof part === "string" ? part.length > 0 && part.charCodeAt(0) === CharacterCodes.hash ? part.slice(1) : part : - ""; + export function done(_machine: BinaryExpressionStateMachine, stackIndex: number, stateStack: BinaryExpressionState[], _nodeStack: BinaryExpression[], _userStateStack: TState[], _resultHolder: { value: TResult }, _outerState: TOuterState): number { + Debug.assertEqual(stateStack[stackIndex], done); + return stackIndex; + } + + export function nextState(machine: BinaryExpressionStateMachine, currentState: BinaryExpressionState) { + switch (currentState) { + case enter: + if (machine.onLeft) return left; + // falls through + case left: + if (machine.onOperator) return operator; + // falls through + case operator: + if (machine.onRight) return right; + // falls through + case right: return exit; + case exit: return done; + case done: return done; + default: Debug.fail("Invalid state"); + } } - function formatIdentifier(name: string | Identifier | PrivateIdentifier, generateName?: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string) { - return typeof name === "string" ? name : - formatIdentifierWorker(name, Debug.checkDefined(generateName)); + function pushStack(stackIndex: number, stateStack: BinaryExpressionState[], nodeStack: BinaryExpression[], userStateStack: TState[], node: BinaryExpression) { + stackIndex++; + stateStack[stackIndex] = enter; + nodeStack[stackIndex] = node; + userStateStack[stackIndex] = undefined!; + return stackIndex; + } + + function checkCircularity(stackIndex: number, nodeStack: BinaryExpression[], node: BinaryExpression) { + if (Debug.shouldAssert(AssertionLevel.Aggressive)) { + while (stackIndex >= 0) { + Debug.assert(nodeStack[stackIndex] !== node, "Circular traversal detected."); + stackIndex--; + } + } } +} - function formatIdentifierWorker(node: Identifier | PrivateIdentifier, generateName: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string) { - return isGeneratedPrivateIdentifier(node) ? generateName(node).slice(1) : - isGeneratedIdentifier(node) ? generateName(node) : - isPrivateIdentifier(node) ? (node.escapedText as string).slice(1) : - idText(node); +/** + * Holds state machine handler functions + */ +class BinaryExpressionStateMachine { + constructor( + readonly onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, + readonly onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + readonly onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, + readonly onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + readonly onExit: (node: BinaryExpression, userState: TState) => TResult, + readonly foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, + ) { } +} - /** - * Formats a generated name. - * @param privateName When `true`, inserts a `#` character at the start of the result. - * @param prefix The prefix (if any) to include before the base name. - * @param baseName The base name for the generated name. - * @param suffix The suffix (if any) to include after the base name. - */ - export function formatGeneratedName(privateName: boolean, prefix: string | undefined, baseName: string, suffix: string | undefined): string; - /** - * Formats a generated name. - * @param privateName When `true`, inserts a `#` character at the start of the result. - * @param prefix The prefix (if any) to include before the base name. - * @param baseName The base name for the generated name. - * @param suffix The suffix (if any) to include after the base name. - * @param generateName Called to format the source node of {@link prefix} when it is a {@link GeneratedNamePart}. - */ - export function formatGeneratedName(privateName: boolean, prefix: string | GeneratedNamePart | undefined, baseName: string | Identifier | PrivateIdentifier, suffix: string | GeneratedNamePart | undefined, generateName: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string): string; - export function formatGeneratedName(privateName: boolean, prefix: string | GeneratedNamePart | undefined, baseName: string | Identifier | PrivateIdentifier, suffix: string | GeneratedNamePart | undefined, generateName?: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string) { - prefix = formatGeneratedNamePart(prefix, generateName!); - suffix = formatGeneratedNamePart(suffix, generateName!); - baseName = formatIdentifier(baseName, generateName); - return `${privateName ? "#" : ""}${prefix}${baseName}${suffix}`; +/** + * Creates a state machine that walks a `BinaryExpression` using the heap to reduce call-stack depth on a large tree. + * @param onEnter Callback evaluated when entering a `BinaryExpression`. Returns new user-defined state to associate with the node while walking. + * @param onLeft Callback evaluated when walking the left side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the right side. + * @param onRight Callback evaluated when walking the right side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the end of the node. + * @param onExit Callback evaluated when exiting a `BinaryExpression`. The result returned will either be folded into the parent's state, or returned from the walker if at the top frame. + * @param foldState Callback evaluated when the result from a nested `onExit` should be folded into the state of that node's parent. + * @returns A function that walks a `BinaryExpression` node using the above callbacks, returning the result of the call to `onExit` from the outermost `BinaryExpression` node. + * + * @internal + */ + export function createBinaryExpressionTrampoline( + onEnter: (node: BinaryExpression, prev: TState | undefined) => TState, + onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, + onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onExit: (node: BinaryExpression, userState: TState) => TResult, + foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, +): (node: BinaryExpression) => TResult; +/** + * Creates a state machine that walks a `BinaryExpression` using the heap to reduce call-stack depth on a large tree. + * @param onEnter Callback evaluated when entering a `BinaryExpression`. Returns new user-defined state to associate with the node while walking. + * @param onLeft Callback evaluated when walking the left side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the right side. + * @param onRight Callback evaluated when walking the right side of a `BinaryExpression`. Return a `BinaryExpression` to continue walking, or `void` to advance to the end of the node. + * @param onExit Callback evaluated when exiting a `BinaryExpression`. The result returned will either be folded into the parent's state, or returned from the walker if at the top frame. + * @param foldState Callback evaluated when the result from a nested `onExit` should be folded into the state of that node's parent. + * @returns A function that walks a `BinaryExpression` node using the above callbacks, returning the result of the call to `onExit` from the outermost `BinaryExpression` node. + * + * @internal + */ +export function createBinaryExpressionTrampoline( + onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, + onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, + onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onExit: (node: BinaryExpression, userState: TState) => TResult, + foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, +): (node: BinaryExpression, outerState: TOuterState) => TResult; +/** @internal */ +export function createBinaryExpressionTrampoline( + onEnter: (node: BinaryExpression, prev: TState | undefined, outerState: TOuterState) => TState, + onLeft: ((left: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onOperator: ((operatorToken: BinaryOperatorToken, userState: TState, node: BinaryExpression) => void) | undefined, + onRight: ((right: Expression, userState: TState, node: BinaryExpression) => BinaryExpression | void) | undefined, + onExit: (node: BinaryExpression, userState: TState) => TResult, + foldState: ((userState: TState, result: TResult, side: "left" | "right") => TState) | undefined, +) { + const machine = new BinaryExpressionStateMachine(onEnter, onLeft, onOperator, onRight, onExit, foldState); + return trampoline; + + function trampoline(node: BinaryExpression, outerState?: TOuterState) { + const resultHolder: { value: TResult } = { value: undefined! }; + const stateStack: BinaryExpressionState[] = [BinaryExpressionState.enter]; + const nodeStack: BinaryExpression[] = [node]; + const userStateStack: TState[] = [undefined!]; + let stackIndex = 0; + while (stateStack[stackIndex] !== BinaryExpressionState.done) { + stackIndex = stateStack[stackIndex](machine, stackIndex, stateStack, nodeStack, userStateStack, resultHolder, outerState); + } + Debug.assertEqual(stackIndex, 0); + return resultHolder.value; } +} +/** + * If `nodes` is not undefined, creates an empty `NodeArray` that preserves the `pos` and `end` of `nodes`. + * @internal + */ +export function elideNodes(factory: NodeFactory, nodes: NodeArray): NodeArray; +/** @internal */ +export function elideNodes(factory: NodeFactory, nodes: NodeArray | undefined): NodeArray | undefined; +/** @internal */ +export function elideNodes(factory: NodeFactory, nodes: NodeArray | undefined): NodeArray | undefined { + if (nodes === undefined) return undefined; + if (nodes.length === 0) return nodes; + return setTextRange(factory.createNodeArray([], nodes.hasTrailingComma), nodes); +} - /** - * Creates a private backing field for an `accessor` {@link PropertyDeclaration}. - */ - export function createAccessorPropertyBackingField(factory: NodeFactory, node: PropertyDeclaration, modifiers: ModifiersArray | undefined, initializer: Expression | undefined) { - return factory.updatePropertyDeclaration( - node, - modifiers, - factory.getGeneratedPrivateNameForNode(node.name, /*prefix*/ undefined, "_accessor_storage"), - /*questionOrExclamationToken*/ undefined, - /*type*/ undefined, - initializer - ); +/** + * Gets the node from which a name should be generated. + * + * @internal + */ +export function getNodeForGeneratedName(name: GeneratedIdentifier | GeneratedPrivateIdentifier) { + if (name.autoGenerateFlags & GeneratedIdentifierFlags.Node) { + const autoGenerateId = name.autoGenerateId; + let node = name as Node; + let original = node.original; + while (original) { + node = original; + + // if "node" is a different generated name (having a different "autoGenerateId"), use it and stop traversing. + if (isMemberName(node) + && !!(node.autoGenerateFlags! & GeneratedIdentifierFlags.Node) + && node.autoGenerateId !== autoGenerateId) { + break; + } + + original = node.original; + } + // otherwise, return the original node for the source + return node; } + return name; +} - /** - * Creates a {@link GetAccessorDeclaration} that reads from a private backing field. - */ - export function createAccessorPropertyGetRedirector(factory: NodeFactory, node: PropertyDeclaration, modifiers: ModifiersArray | undefined, name: PropertyName) { - return factory.createGetAccessorDeclaration( - modifiers, - name, - [], - /*type*/ undefined, - factory.createBlock([ - factory.createReturnStatement( +/** + * Formats a prefix or suffix of a generated name. + * + * @internal + */ +export function formatGeneratedNamePart(part: string | undefined): string; +/** + * Formats a prefix or suffix of a generated name. If the part is a {@link GeneratedNamePart}, calls {@link generateName} to format the source node. + * + * @internal + */ +export function formatGeneratedNamePart(part: string | GeneratedNamePart | undefined, generateName: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string): string; +/** @internal */ +export function formatGeneratedNamePart(part: string | GeneratedNamePart | undefined, generateName?: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string): string { + return typeof part === "object" ? formatGeneratedName(/*privateName*/ false, part.prefix, part.node, part.suffix, generateName!) : + typeof part === "string" ? part.length > 0 && part.charCodeAt(0) === CharacterCodes.hash ? part.slice(1) : part : + ""; +} + +function formatIdentifier(name: string | Identifier | PrivateIdentifier, generateName?: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string) { + return typeof name === "string" ? name : + formatIdentifierWorker(name, Debug.checkDefined(generateName)); +} + +function formatIdentifierWorker(node: Identifier | PrivateIdentifier, generateName: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string) { + return isGeneratedPrivateIdentifier(node) ? generateName(node).slice(1) : + isGeneratedIdentifier(node) ? generateName(node) : + isPrivateIdentifier(node) ? (node.escapedText as string).slice(1) : + idText(node); +} + +/** + * Formats a generated name. + * @param privateName When `true`, inserts a `#` character at the start of the result. + * @param prefix The prefix (if any) to include before the base name. + * @param baseName The base name for the generated name. + * @param suffix The suffix (if any) to include after the base name. + * + * @internal + */ +export function formatGeneratedName(privateName: boolean, prefix: string | undefined, baseName: string, suffix: string | undefined): string; +/** + * Formats a generated name. + * @param privateName When `true`, inserts a `#` character at the start of the result. + * @param prefix The prefix (if any) to include before the base name. + * @param baseName The base name for the generated name. + * @param suffix The suffix (if any) to include after the base name. + * @param generateName Called to format the source node of {@link prefix} when it is a {@link GeneratedNamePart}. + * + * @internal + */ +export function formatGeneratedName(privateName: boolean, prefix: string | GeneratedNamePart | undefined, baseName: string | Identifier | PrivateIdentifier, suffix: string | GeneratedNamePart | undefined, generateName: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string): string; +/** @internal */ +export function formatGeneratedName(privateName: boolean, prefix: string | GeneratedNamePart | undefined, baseName: string | Identifier | PrivateIdentifier, suffix: string | GeneratedNamePart | undefined, generateName?: (name: GeneratedIdentifier | GeneratedPrivateIdentifier) => string) { + prefix = formatGeneratedNamePart(prefix, generateName!); + suffix = formatGeneratedNamePart(suffix, generateName!); + baseName = formatIdentifier(baseName, generateName); + return `${privateName ? "#" : ""}${prefix}${baseName}${suffix}`; +} + + +/** + * Creates a private backing field for an `accessor` {@link PropertyDeclaration}. + * + * @internal + */ +export function createAccessorPropertyBackingField(factory: NodeFactory, node: PropertyDeclaration, modifiers: ModifiersArray | undefined, initializer: Expression | undefined) { + return factory.updatePropertyDeclaration( + node, + modifiers, + factory.getGeneratedPrivateNameForNode(node.name, /*prefix*/ undefined, "_accessor_storage"), + /*questionOrExclamationToken*/ undefined, + /*type*/ undefined, + initializer + ); +} + +/** + * Creates a {@link GetAccessorDeclaration} that reads from a private backing field. + * + * @internal + */ +export function createAccessorPropertyGetRedirector(factory: NodeFactory, node: PropertyDeclaration, modifiers: ModifiersArray | undefined, name: PropertyName): GetAccessorDeclaration { + return factory.createGetAccessorDeclaration( + modifiers, + name, + [], + /*type*/ undefined, + factory.createBlock([ + factory.createReturnStatement( + factory.createPropertyAccessExpression( + factory.createThis(), + factory.getGeneratedPrivateNameForNode(node.name, /*prefix*/ undefined, "_accessor_storage") + ) + ) + ]) + ); +} + +/** + * Creates a {@link SetAccessorDeclaration} that writes to a private backing field. + * + * @internal + */ +export function createAccessorPropertySetRedirector(factory: NodeFactory, node: PropertyDeclaration, modifiers: ModifiersArray | undefined, name: PropertyName): SetAccessorDeclaration { + return factory.createSetAccessorDeclaration( + modifiers, + name, + [factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotdotDotToken*/ undefined, + "value" + )], + factory.createBlock([ + factory.createExpressionStatement( + factory.createAssignment( factory.createPropertyAccessExpression( factory.createThis(), factory.getGeneratedPrivateNameForNode(node.name, /*prefix*/ undefined, "_accessor_storage") - ) + ), + factory.createIdentifier("value") ) - ]) - ); - } - - /** - * Creates a {@link SetAccessorDeclaration} that writes to a private backing field. - */ - export function createAccessorPropertySetRedirector(factory: NodeFactory, node: PropertyDeclaration, modifiers: ModifiersArray | undefined, name: PropertyName) { - return factory.createSetAccessorDeclaration( - modifiers, - name, - [factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotdotDotToken*/ undefined, - "value" - )], - factory.createBlock([ - factory.createExpressionStatement( - factory.createAssignment( - factory.createPropertyAccessExpression( - factory.createThis(), - factory.getGeneratedPrivateNameForNode(node.name, /*prefix*/ undefined, "_accessor_storage") - ), - factory.createIdentifier("value") - ) - ) - ]) - ); - } + ) + ]) + ); } diff --git a/src/compiler/factory/utilitiesPublic.ts b/src/compiler/factory/utilitiesPublic.ts index c51a2466ff5b2..d679f8f34b649 100644 --- a/src/compiler/factory/utilitiesPublic.ts +++ b/src/compiler/factory/utilitiesPublic.ts @@ -1,45 +1,45 @@ -namespace ts { - export function setTextRange(range: T, location: TextRange | undefined): T { - return location ? setTextRangePosEnd(range, location.pos, location.end) : range; - } +import { HasDecorators, HasModifiers, Node, setTextRangePosEnd, SyntaxKind, TextRange } from "../_namespaces/ts"; - export function canHaveModifiers(node: Node): node is HasModifiers { - const kind = node.kind; - return kind === SyntaxKind.TypeParameter - || kind === SyntaxKind.Parameter - || kind === SyntaxKind.PropertySignature - || kind === SyntaxKind.PropertyDeclaration - || kind === SyntaxKind.MethodSignature - || kind === SyntaxKind.MethodDeclaration - || kind === SyntaxKind.Constructor - || kind === SyntaxKind.GetAccessor - || kind === SyntaxKind.SetAccessor - || kind === SyntaxKind.IndexSignature - || kind === SyntaxKind.ConstructorType - || kind === SyntaxKind.FunctionExpression - || kind === SyntaxKind.ArrowFunction - || kind === SyntaxKind.ClassExpression - || kind === SyntaxKind.VariableStatement - || kind === SyntaxKind.FunctionDeclaration - || kind === SyntaxKind.ClassDeclaration - || kind === SyntaxKind.InterfaceDeclaration - || kind === SyntaxKind.TypeAliasDeclaration - || kind === SyntaxKind.EnumDeclaration - || kind === SyntaxKind.ModuleDeclaration - || kind === SyntaxKind.ImportEqualsDeclaration - || kind === SyntaxKind.ImportDeclaration - || kind === SyntaxKind.ExportAssignment - || kind === SyntaxKind.ExportDeclaration; - } +export function setTextRange(range: T, location: TextRange | undefined): T { + return location ? setTextRangePosEnd(range, location.pos, location.end) : range; +} - export function canHaveDecorators(node: Node): node is HasDecorators { - const kind = node.kind; - return kind === SyntaxKind.Parameter - || kind === SyntaxKind.PropertyDeclaration - || kind === SyntaxKind.MethodDeclaration - || kind === SyntaxKind.GetAccessor - || kind === SyntaxKind.SetAccessor - || kind === SyntaxKind.ClassExpression - || kind === SyntaxKind.ClassDeclaration; - } +export function canHaveModifiers(node: Node): node is HasModifiers { + const kind = node.kind; + return kind === SyntaxKind.TypeParameter + || kind === SyntaxKind.Parameter + || kind === SyntaxKind.PropertySignature + || kind === SyntaxKind.PropertyDeclaration + || kind === SyntaxKind.MethodSignature + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.Constructor + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor + || kind === SyntaxKind.IndexSignature + || kind === SyntaxKind.ConstructorType + || kind === SyntaxKind.FunctionExpression + || kind === SyntaxKind.ArrowFunction + || kind === SyntaxKind.ClassExpression + || kind === SyntaxKind.VariableStatement + || kind === SyntaxKind.FunctionDeclaration + || kind === SyntaxKind.ClassDeclaration + || kind === SyntaxKind.InterfaceDeclaration + || kind === SyntaxKind.TypeAliasDeclaration + || kind === SyntaxKind.EnumDeclaration + || kind === SyntaxKind.ModuleDeclaration + || kind === SyntaxKind.ImportEqualsDeclaration + || kind === SyntaxKind.ImportDeclaration + || kind === SyntaxKind.ExportAssignment + || kind === SyntaxKind.ExportDeclaration; +} + +export function canHaveDecorators(node: Node): node is HasDecorators { + const kind = node.kind; + return kind === SyntaxKind.Parameter + || kind === SyntaxKind.PropertyDeclaration + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor + || kind === SyntaxKind.ClassExpression + || kind === SyntaxKind.ClassDeclaration; } \ No newline at end of file diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 100e399004da7..4f8a5637fd679 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1,2363 +1,2387 @@ -namespace ts { - /* @internal */ - export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void; - export function trace(host: ModuleResolutionHost): void { - host.trace!(formatMessage.apply(undefined, arguments)); - } - - /* @internal */ - export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { - return !!compilerOptions.traceResolution && host.trace !== undefined; - } - - function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined): Resolved | undefined { - let packageId: PackageId | undefined; - if (r && packageInfo) { - const packageJsonContent = packageInfo.contents.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 }; - } +import { + append, appendIfUnique, arrayFrom, changeAnyExtension, CharacterCodes, combinePaths, comparePaths, Comparison, + CompilerOptions, contains, containsPath, createCompilerDiagnostic, Debug, Diagnostic, DiagnosticMessage, + DiagnosticReporter, Diagnostics, directoryProbablyExists, directorySeparator, emptyArray, endsWith, + ensureTrailingDirectorySeparator, ESMap, every, Extension, extensionIsTS, fileExtensionIs, fileExtensionIsOneOf, + FileReference, filter, firstDefined, forEach, forEachAncestorDirectory, formatMessage, getBaseFileName, + GetCanonicalFileName, getCommonSourceDirectory, getDirectoryPath, GetEffectiveTypeRootsHost, getEmitModuleKind, + getEmitModuleResolutionKind, getModeForUsageLocation, getNormalizedAbsolutePath, getOwnKeys, getPathComponents, + getPathFromPathComponents, getPathsBasePath, getPossibleOriginalInputExtensionForExtension, + getRelativePathFromDirectory, getRootLength, hasJSFileExtension, hasProperty, hasTrailingDirectorySeparator, + hasTSFileExtension, hostGetCanonicalFileName, isArray, isExternalModuleNameRelative, isRootedDiskPath, isString, + isStringLiteralLike, lastOrUndefined, length, Map, MapLike, matchedText, MatchingKeys, matchPatternOrExact, + ModuleKind, ModuleResolutionHost, ModuleResolutionKind, noop, noopPush, normalizePath, normalizeSlashes, + optionsHaveModuleResolutionChanges, PackageId, packageIdToString, ParsedCommandLine, Path, pathIsRelative, Pattern, + patternText, perfLogger, Push, readJson, removeExtension, removeFileExtension, removePrefix, + ResolvedModuleWithFailedLookupLocations, ResolvedProjectReference, ResolvedTypeReferenceDirective, + ResolvedTypeReferenceDirectiveWithFailedLookupLocations, some, sort, SourceFile, startsWith, stringContains, + StringLiteralLike, supportedTSExtensionsFlat, toPath, tryExtractTSExtension, tryGetExtensionFromPath, + tryParsePatterns, tryRemoveExtension, version, Version, versionMajorMinor, VersionRange, +} from "./_namespaces/ts"; + +/** @internal */ +export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void; +export function trace(host: ModuleResolutionHost): void { + host.trace!(formatMessage.apply(undefined, arguments)); +} - function noPackageId(r: PathAndExtension | undefined): Resolved | undefined { - return withPackageId(/*packageInfo*/ undefined, r); - } +/** @internal */ +export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { + return !!compilerOptions.traceResolution && host.trace !== undefined; +} - function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined { - if (r) { - Debug.assert(r.packageId === undefined); - return { path: r.path, ext: r.extension }; +function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined): Resolved | undefined { + let packageId: PackageId | undefined; + if (r && packageInfo) { + const packageJsonContent = packageInfo.contents.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 }; +} - /** Result of trying to resolve a module. */ - interface Resolved { - path: string; - extension: Extension; - packageId: PackageId | undefined; - /** - * When the resolved is not created from cache, the value is - * - string if it is symbolic link to the resolved `path` - * - undefined if `path` is not a symbolic link - * When the resolved is created using value from cache of ResolvedModuleWithFailedLookupLocations, the value is: - * - string if it is symbolic link to the resolved `path` - * - true if `path` is not a symbolic link - this indicates that the `originalPath` calculation is already done and needs to be skipped - * Note: This is a file name with preserved original casing, not a normalized `Path`. - */ - originalPath?: string | true; - } +function noPackageId(r: PathAndExtension | undefined): Resolved | undefined { + return withPackageId(/*packageInfo*/ undefined, r); +} - /** Result of trying to resolve a module at a file. Needs to have 'packageId' added later. */ - interface PathAndExtension { - path: string; - // (Use a different name than `extension` to make sure Resolved isn't assignable to PathAndExtension.) - ext: Extension; +function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined { + if (r) { + Debug.assert(r.packageId === undefined); + return { path: r.path, ext: r.extension }; } +} +/** Result of trying to resolve a module. */ +interface Resolved { + path: string; + extension: Extension; + packageId: PackageId | undefined; /** - * Kinds of file that we are currently looking for. - * Typically there is one pass with Extensions.TypeScript, then a second pass with Extensions.JavaScript. + * When the resolved is not created from cache, the value is + * - string if it is symbolic link to the resolved `path` + * - undefined if `path` is not a symbolic link + * When the resolved is created using value from cache of ResolvedModuleWithFailedLookupLocations, the value is: + * - string if it is symbolic link to the resolved `path` + * - true if `path` is not a symbolic link - this indicates that the `originalPath` calculation is already done and needs to be skipped + * Note: This is a file name with preserved original casing, not a normalized `Path`. */ - enum Extensions { - TypeScript, /** '.ts', '.tsx', or '.d.ts' */ - JavaScript, /** '.js' or '.jsx' */ - Json, /** '.json' */ - TSConfig, /** '.json' with `tsconfig` used instead of `index` */ - DtsOnly, /** Only '.d.ts' */ - TsOnly, /** '.[cm]tsx?' but not .d.ts variants */ - } - - interface PathAndPackageId { - readonly fileName: string; - readonly packageId: PackageId | undefined; - } - /** Used with `Extensions.DtsOnly` to extract the path from TypeScript results. */ - function resolvedTypeScriptOnly(resolved: Resolved | undefined): PathAndPackageId | undefined { - if (!resolved) { - return undefined; - } - Debug.assert(extensionIsTS(resolved.extension)); - return { fileName: resolved.path, packageId: resolved.packageId }; - } - - function createResolvedModuleWithFailedLookupLocations( - resolved: Resolved | undefined, - isExternalLibraryImport: boolean | undefined, - failedLookupLocations: string[], - affectingLocations: string[], - diagnostics: Diagnostic[], - resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined - ): ResolvedModuleWithFailedLookupLocations { - if (resultFromCache) { - resultFromCache.failedLookupLocations.push(...failedLookupLocations); - resultFromCache.affectingLocations.push(...affectingLocations); - return resultFromCache; - } - return { - resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath: resolved.originalPath === true ? undefined : resolved.originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId }, - failedLookupLocations, - affectingLocations, - resolutionDiagnostics: diagnostics, - }; + originalPath?: string | true; +} + +/** Result of trying to resolve a module at a file. Needs to have 'packageId' added later. */ +interface PathAndExtension { + path: string; + // (Use a different name than `extension` to make sure Resolved isn't assignable to PathAndExtension.) + ext: Extension; +} + +/** + * Kinds of file that we are currently looking for. + * Typically there is one pass with Extensions.TypeScript, then a second pass with Extensions.JavaScript. + */ +enum Extensions { + TypeScript, /** '.ts', '.tsx', or '.d.ts' */ + JavaScript, /** '.js' or '.jsx' */ + Json, /** '.json' */ + TSConfig, /** '.json' with `tsconfig` used instead of `index` */ + DtsOnly, /** Only '.d.ts' */ + TsOnly, /** '.[cm]tsx?' but not .d.ts variants */ +} + +interface PathAndPackageId { + readonly fileName: string; + readonly packageId: PackageId | undefined; +} +/** Used with `Extensions.DtsOnly` to extract the path from TypeScript results. */ +function resolvedTypeScriptOnly(resolved: Resolved | undefined): PathAndPackageId | undefined { + if (!resolved) { + return undefined; } + Debug.assert(extensionIsTS(resolved.extension)); + return { fileName: resolved.path, packageId: resolved.packageId }; +} - /*@internal*/ - interface ModuleResolutionState { - host: ModuleResolutionHost; - compilerOptions: CompilerOptions; - traceEnabled: boolean; - failedLookupLocations: Push; - affectingLocations: Push; - resultFromCache?: ResolvedModuleWithFailedLookupLocations; - packageJsonInfoCache: PackageJsonInfoCache | undefined; - features: NodeResolutionFeatures; - conditions: readonly string[]; - requestContainingDirectory: string | undefined; - reportDiagnostic: DiagnosticReporter; - } - - /** Just the fields that we use for module resolution. */ - /*@internal*/ - interface PackageJsonPathFields { - typings?: string; - types?: string; - typesVersions?: MapLike>; - main?: string; - tsconfig?: string; - type?: string; - imports?: object; - exports?: object; - name?: string; - } - - interface PackageJson extends PackageJsonPathFields { - name?: string; - version?: string; - } - - function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined; - function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "object", state: ModuleResolutionState): PackageJson[K] | undefined; - function readPackageJsonField(jsonContent: PackageJson, fieldName: K, typeOfTag: "string" | "object", state: ModuleResolutionState): PackageJson[K] | undefined { - if (!hasProperty(jsonContent, fieldName)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, fieldName); - } - return; +function createResolvedModuleWithFailedLookupLocations( + resolved: Resolved | undefined, + isExternalLibraryImport: boolean | undefined, + failedLookupLocations: string[], + affectingLocations: string[], + diagnostics: Diagnostic[], + resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined +): ResolvedModuleWithFailedLookupLocations { + if (resultFromCache) { + resultFromCache.failedLookupLocations.push(...failedLookupLocations); + resultFromCache.affectingLocations.push(...affectingLocations); + return resultFromCache; + } + return { + resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath: resolved.originalPath === true ? undefined : resolved.originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId }, + failedLookupLocations, + affectingLocations, + resolutionDiagnostics: diagnostics, + }; +} + +/** @internal */ +export interface ModuleResolutionState { + host: ModuleResolutionHost; + compilerOptions: CompilerOptions; + traceEnabled: boolean; + failedLookupLocations: Push; + affectingLocations: Push; + resultFromCache?: ResolvedModuleWithFailedLookupLocations; + packageJsonInfoCache: PackageJsonInfoCache | undefined; + features: NodeResolutionFeatures; + conditions: readonly string[]; + requestContainingDirectory: string | undefined; + reportDiagnostic: DiagnosticReporter; +} + +/** Just the fields that we use for module resolution. + * + * @internal + */ +export interface PackageJsonPathFields { + typings?: string; + types?: string; + typesVersions?: MapLike>; + main?: string; + tsconfig?: string; + type?: string; + imports?: object; + exports?: object; + name?: string; +} + +interface PackageJson extends PackageJsonPathFields { + name?: string; + version?: string; +} + +function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined; +function readPackageJsonField>(jsonContent: PackageJson, fieldName: K, typeOfTag: "object", state: ModuleResolutionState): PackageJson[K] | undefined; +function readPackageJsonField(jsonContent: PackageJson, fieldName: K, typeOfTag: "string" | "object", state: ModuleResolutionState): PackageJson[K] | undefined { + if (!hasProperty(jsonContent, fieldName)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_does_not_have_a_0_field, fieldName); } - const value = jsonContent[fieldName]; - if (typeof value !== typeOfTag || value === null) { // eslint-disable-line no-null/no-null - if (state.traceEnabled) { - // eslint-disable-next-line no-null/no-null - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, fieldName, typeOfTag, value === null ? "null" : typeof value); - } - return; + return; + } + const value = jsonContent[fieldName]; + if (typeof value !== typeOfTag || value === null) { // eslint-disable-line no-null/no-null + if (state.traceEnabled) { + // eslint-disable-next-line no-null/no-null + trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, fieldName, typeOfTag, value === null ? "null" : typeof value); } - return value; + return; } + return value; +} - 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) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_had_a_falsy_0_field, fieldName); - } - return; - } - const path = normalizePath(combinePaths(baseDirectory, fileName)); +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) { if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path); + trace(state.host, Diagnostics.package_json_had_a_falsy_0_field, fieldName); } - return path; + return; } - - function readPackageJsonTypesFields(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { - return readPackageJsonPathField(jsonContent, "typings", baseDirectory, state) - || readPackageJsonPathField(jsonContent, "types", baseDirectory, state); + 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); } + return path; +} - function readPackageJsonTSConfigField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { - return readPackageJsonPathField(jsonContent, "tsconfig", baseDirectory, state); - } +function readPackageJsonTypesFields(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { + return readPackageJsonPathField(jsonContent, "typings", baseDirectory, state) + || readPackageJsonPathField(jsonContent, "types", baseDirectory, state); +} - function readPackageJsonMainField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { - return readPackageJsonPathField(jsonContent, "main", baseDirectory, state); - } +function readPackageJsonTSConfigField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { + return readPackageJsonPathField(jsonContent, "tsconfig", baseDirectory, state); +} - function readPackageJsonTypesVersionsField(jsonContent: PackageJson, state: ModuleResolutionState) { - const typesVersions = readPackageJsonField(jsonContent, "typesVersions", "object", state); - if (typesVersions === undefined) return; +function readPackageJsonMainField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) { + return readPackageJsonPathField(jsonContent, "main", baseDirectory, state); +} - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_a_typesVersions_field_with_version_specific_path_mappings); - } +function readPackageJsonTypesVersionsField(jsonContent: PackageJson, state: ModuleResolutionState) { + const typesVersions = readPackageJsonField(jsonContent, "typesVersions", "object", state); + if (typesVersions === undefined) return; - return typesVersions; + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_has_a_typesVersions_field_with_version_specific_path_mappings); } - /*@internal*/ - interface VersionPaths { - version: string; - paths: MapLike; - } + return typesVersions; +} - function readPackageJsonTypesVersionPaths(jsonContent: PackageJson, state: ModuleResolutionState): VersionPaths | undefined { - const typesVersions = readPackageJsonTypesVersionsField(jsonContent, state); - if (typesVersions === undefined) return; +/** @internal */ +export interface VersionPaths { + version: string; + paths: MapLike; +} - if (state.traceEnabled) { - for (const key in typesVersions) { - if (hasProperty(typesVersions, key) && !VersionRange.tryParse(key)) { - trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_is_not_a_valid_semver_range, key); - } - } - } +function readPackageJsonTypesVersionPaths(jsonContent: PackageJson, state: ModuleResolutionState): VersionPaths | undefined { + const typesVersions = readPackageJsonTypesVersionsField(jsonContent, state); + if (typesVersions === undefined) return; - const result = getPackageJsonTypesVersionsPaths(typesVersions); - if (!result) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_does_not_have_a_typesVersions_entry_that_matches_version_0, versionMajorMinor); + if (state.traceEnabled) { + for (const key in typesVersions) { + if (hasProperty(typesVersions, key) && !VersionRange.tryParse(key)) { + trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_is_not_a_valid_semver_range, key); } - return; } + } - const { version: bestVersionKey, paths: bestVersionPaths } = result; - if (typeof bestVersionPaths !== "object") { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersions['${bestVersionKey}']`, "object", typeof bestVersionPaths); - } - return; + const result = getPackageJsonTypesVersionsPaths(typesVersions); + if (!result) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_does_not_have_a_typesVersions_entry_that_matches_version_0, versionMajorMinor); } - - return result; + return; } - let typeScriptVersion: Version | undefined; + const { version: bestVersionKey, paths: bestVersionPaths } = result; + if (typeof bestVersionPaths !== "object") { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2, `typesVersions['${bestVersionKey}']`, "object", typeof bestVersionPaths); + } + return; + } - /* @internal */ - export function getPackageJsonTypesVersionsPaths(typesVersions: MapLike>) { - if (!typeScriptVersion) typeScriptVersion = new Version(version); + return result; +} - for (const key in typesVersions) { - if (!hasProperty(typesVersions, key)) continue; +let typeScriptVersion: Version | undefined; - const keyRange = VersionRange.tryParse(key); - if (keyRange === undefined) { - continue; - } +/** @internal */ +export function getPackageJsonTypesVersionsPaths(typesVersions: MapLike>) { + if (!typeScriptVersion) typeScriptVersion = new Version(version); - // return the first entry whose range matches the current compiler version. - if (keyRange.test(typeScriptVersion)) { - return { version: key, paths: typesVersions[key] }; - } - } - } + for (const key in typesVersions) { + if (!hasProperty(typesVersions, key)) continue; - export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined { - if (options.typeRoots) { - return options.typeRoots; + const keyRange = VersionRange.tryParse(key); + if (keyRange === undefined) { + continue; } - let currentDirectory: string | undefined; - if (options.configFilePath) { - currentDirectory = getDirectoryPath(options.configFilePath); - } - else if (host.getCurrentDirectory) { - currentDirectory = host.getCurrentDirectory(); + // return the first entry whose range matches the current compiler version. + if (keyRange.test(typeScriptVersion)) { + return { version: key, paths: typesVersions[key] }; } + } +} - if (currentDirectory !== undefined) { - return getDefaultTypeRoots(currentDirectory, host); - } +export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined { + if (options.typeRoots) { + return options.typeRoots; } - /** - * Returns the path to every node_modules/@types directory from some ancestor directory. - * Returns undefined if there are none. - */ - function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined { - if (!host.directoryExists) { - return [combinePaths(currentDirectory, nodeModulesAtTypes)]; - // And if it doesn't exist, tough. - } + let currentDirectory: string | undefined; + if (options.configFilePath) { + currentDirectory = getDirectoryPath(options.configFilePath); + } + else if (host.getCurrentDirectory) { + currentDirectory = host.getCurrentDirectory(); + } - let typeRoots: string[] | undefined; - forEachAncestorDirectory(normalizePath(currentDirectory), directory => { - const atTypes = combinePaths(directory, nodeModulesAtTypes); - if (host.directoryExists!(atTypes)) { - (typeRoots || (typeRoots = [])).push(atTypes); - } - return undefined; - }); - return typeRoots; + if (currentDirectory !== undefined) { + return getDefaultTypeRoots(currentDirectory, host); } - const nodeModulesAtTypes = combinePaths("node_modules", "@types"); +} - function arePathsEqual(path1: string, path2: string, host: ModuleResolutionHost): boolean { - const useCaseSensitiveFileNames = typeof host.useCaseSensitiveFileNames === "function" ? host.useCaseSensitiveFileNames() : host.useCaseSensitiveFileNames; - return comparePaths(path1, path2, !useCaseSensitiveFileNames) === Comparison.EqualTo; +/** + * Returns the path to every node_modules/@types directory from some ancestor directory. + * Returns undefined if there are none. + */ +function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined { + if (!host.directoryExists) { + return [combinePaths(currentDirectory, nodeModulesAtTypes)]; + // And if it doesn't exist, tough. } - /** - * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. - * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups - * is assumed to be the same as root directory of the project. - */ - export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, cache?: TypeReferenceDirectiveResolutionCache, resolutionMode?: SourceFile["impliedNodeFormat"]): ResolvedTypeReferenceDirectiveWithFailedLookupLocations { - Debug.assert(typeof typeReferenceDirectiveName === "string", "Non-string value passed to `ts.resolveTypeReferenceDirective`, likely by a wrapping package working with an outdated `resolveTypeReferenceDirectives` signature. This is probably not a problem in TS itself."); - const traceEnabled = isTraceEnabled(options, host); - if (redirectedReference) { - options = redirectedReference.commandLine.options; + let typeRoots: string[] | undefined; + forEachAncestorDirectory(normalizePath(currentDirectory), directory => { + const atTypes = combinePaths(directory, nodeModulesAtTypes); + if (host.directoryExists!(atTypes)) { + (typeRoots || (typeRoots = [])).push(atTypes); } + return undefined; + }); + return typeRoots; +} +const nodeModulesAtTypes = combinePaths("node_modules", "@types"); - const containingDirectory = containingFile ? getDirectoryPath(containingFile) : undefined; - const perFolderCache = containingDirectory ? cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference) : undefined; - let result = perFolderCache && perFolderCache.get(typeReferenceDirectiveName, /*mode*/ resolutionMode); - if (result) { - if (traceEnabled) { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1, typeReferenceDirectiveName, containingFile); - if (redirectedReference) trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); - trace(host, Diagnostics.Resolution_for_type_reference_directive_0_was_found_in_cache_from_location_1, typeReferenceDirectiveName, containingDirectory); - traceResult(result); - } - return result; - } +function arePathsEqual(path1: string, path2: string, host: ModuleResolutionHost): boolean { + const useCaseSensitiveFileNames = typeof host.useCaseSensitiveFileNames === "function" ? host.useCaseSensitiveFileNames() : host.useCaseSensitiveFileNames; + return comparePaths(path1, path2, !useCaseSensitiveFileNames) === Comparison.EqualTo; +} - const typeRoots = getEffectiveTypeRoots(options, host); +/** + * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. + * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups + * is assumed to be the same as root directory of the project. + */ +export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, cache?: TypeReferenceDirectiveResolutionCache, resolutionMode?: SourceFile["impliedNodeFormat"]): ResolvedTypeReferenceDirectiveWithFailedLookupLocations { + Debug.assert(typeof typeReferenceDirectiveName === "string", "Non-string value passed to `ts.resolveTypeReferenceDirective`, likely by a wrapping package working with an outdated `resolveTypeReferenceDirectives` signature. This is probably not a problem in TS itself."); + const traceEnabled = isTraceEnabled(options, host); + if (redirectedReference) { + options = redirectedReference.commandLine.options; + } + + const containingDirectory = containingFile ? getDirectoryPath(containingFile) : undefined; + const perFolderCache = containingDirectory ? cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference) : undefined; + let result = perFolderCache && perFolderCache.get(typeReferenceDirectiveName, /*mode*/ resolutionMode); + if (result) { if (traceEnabled) { - if (containingFile === undefined) { - if (typeRoots === undefined) { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName); - } - else { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots); - } + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1, typeReferenceDirectiveName, containingFile); + if (redirectedReference) trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); + trace(host, Diagnostics.Resolution_for_type_reference_directive_0_was_found_in_cache_from_location_1, typeReferenceDirectiveName, containingDirectory); + traceResult(result); + } + return result; + } + + const typeRoots = getEffectiveTypeRoots(options, host); + if (traceEnabled) { + if (containingFile === undefined) { + if (typeRoots === undefined) { + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName); } else { - if (typeRoots === undefined) { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile); - } - else { - trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots); - } + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots); + } + } + else { + if (typeRoots === undefined) { + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile); } - if (redirectedReference) { - trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); + else { + trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots); } } - - const failedLookupLocations: string[] = []; - const affectingLocations: string[] = []; - let features = getDefaultNodeResolutionFeatures(options); - // Unlike `import` statements, whose mode-calculating APIs are all guaranteed to return `undefined` if we're in an un-mode-ed module resolution - // setting, type references will return their target mode regardless of options because of how the parser works, so we guard against the mode being - // set in a non-modal module resolution setting here. Do note that our behavior is not particularly well defined when these mode-overriding imports - // are present in a non-modal project; while in theory we'd like to either ignore the mode or provide faithful modern resolution, depending on what we feel is best, - // in practice, not every cache has the options available to intelligently make the choice to ignore the mode request, and it's unclear how modern "faithful modern - // resolution" should be (`node16`? `nodenext`?). As such, witnessing a mode-overriding triple-slash reference in a non-modal module resolution - // context should _probably_ be an error - and that should likely be handled by the `Program` (which is what we do). - if (resolutionMode === ModuleKind.ESNext && (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext)) { - features |= NodeResolutionFeatures.EsmMode; - } - const conditions = features & NodeResolutionFeatures.Exports ? features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"] : []; - const diagnostics: Diagnostic[] = []; - const moduleResolutionState: ModuleResolutionState = { - compilerOptions: options, - host, - traceEnabled, - failedLookupLocations, - affectingLocations, - packageJsonInfoCache: cache, - features, - conditions, - requestContainingDirectory: containingDirectory, - reportDiagnostic: diag => void diagnostics.push(diag), + if (redirectedReference) { + trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); + } + } + + const failedLookupLocations: string[] = []; + const affectingLocations: string[] = []; + let features = getDefaultNodeResolutionFeatures(options); + // Unlike `import` statements, whose mode-calculating APIs are all guaranteed to return `undefined` if we're in an un-mode-ed module resolution + // setting, type references will return their target mode regardless of options because of how the parser works, so we guard against the mode being + // set in a non-modal module resolution setting here. Do note that our behavior is not particularly well defined when these mode-overriding imports + // are present in a non-modal project; while in theory we'd like to either ignore the mode or provide faithful modern resolution, depending on what we feel is best, + // in practice, not every cache has the options available to intelligently make the choice to ignore the mode request, and it's unclear how modern "faithful modern + // resolution" should be (`node16`? `nodenext`?). As such, witnessing a mode-overriding triple-slash reference in a non-modal module resolution + // context should _probably_ be an error - and that should likely be handled by the `Program` (which is what we do). + if (resolutionMode === ModuleKind.ESNext && (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext)) { + features |= NodeResolutionFeatures.EsmMode; + } + const conditions = features & NodeResolutionFeatures.Exports ? features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"] : []; + const diagnostics: Diagnostic[] = []; + const moduleResolutionState: ModuleResolutionState = { + compilerOptions: options, + host, + traceEnabled, + failedLookupLocations, + affectingLocations, + packageJsonInfoCache: cache, + features, + conditions, + requestContainingDirectory: containingDirectory, + reportDiagnostic: diag => void diagnostics.push(diag), + }; + let resolved = primaryLookup(); + let primary = true; + if (!resolved) { + resolved = secondaryLookup(); + primary = false; + } + + let resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined; + if (resolved) { + const { fileName, packageId } = resolved; + const resolvedFileName = options.preserveSymlinks ? fileName : realPath(fileName, host, traceEnabled); + const pathsAreEqual = arePathsEqual(fileName, resolvedFileName, host); + resolvedTypeReferenceDirective = { + primary, + // If the fileName and realpath are differing only in casing prefer fileName so that we can issue correct errors for casing under forceConsistentCasingInFileNames + resolvedFileName: pathsAreEqual ? fileName : resolvedFileName, + originalPath: pathsAreEqual ? undefined : fileName, + packageId, + isExternalLibraryImport: pathContainsNodeModules(fileName), }; - let resolved = primaryLookup(); - let primary = true; - if (!resolved) { - resolved = secondaryLookup(); - primary = false; - } + } + result = { resolvedTypeReferenceDirective, failedLookupLocations, affectingLocations, resolutionDiagnostics: diagnostics }; + perFolderCache?.set(typeReferenceDirectiveName, /*mode*/ resolutionMode, result); + if (traceEnabled) traceResult(result); + return result; - let resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined; - if (resolved) { - const { fileName, packageId } = resolved; - const resolvedFileName = options.preserveSymlinks ? fileName : realPath(fileName, host, traceEnabled); - const pathsAreEqual = arePathsEqual(fileName, resolvedFileName, host); - resolvedTypeReferenceDirective = { - primary, - // If the fileName and realpath are differing only in casing prefer fileName so that we can issue correct errors for casing under forceConsistentCasingInFileNames - resolvedFileName: pathsAreEqual ? fileName : resolvedFileName, - originalPath: pathsAreEqual ? undefined : fileName, - packageId, - isExternalLibraryImport: pathContainsNodeModules(fileName), - }; + function traceResult(result: ResolvedTypeReferenceDirectiveWithFailedLookupLocations) { + if (!result.resolvedTypeReferenceDirective?.resolvedFileName) { + trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName); } - result = { resolvedTypeReferenceDirective, failedLookupLocations, affectingLocations, resolutionDiagnostics: diagnostics }; - perFolderCache?.set(typeReferenceDirectiveName, /*mode*/ resolutionMode, result); - if (traceEnabled) traceResult(result); - return result; - - function traceResult(result: ResolvedTypeReferenceDirectiveWithFailedLookupLocations) { - if (!result.resolvedTypeReferenceDirective?.resolvedFileName) { - trace(host, Diagnostics.Type_reference_directive_0_was_not_resolved, typeReferenceDirectiveName); - } - else if (result.resolvedTypeReferenceDirective.packageId) { - trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_with_Package_ID_2_primary_Colon_3, typeReferenceDirectiveName, result.resolvedTypeReferenceDirective.resolvedFileName, packageIdToString(result.resolvedTypeReferenceDirective.packageId), result.resolvedTypeReferenceDirective.primary); - } - else { - trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, result.resolvedTypeReferenceDirective.resolvedFileName, result.resolvedTypeReferenceDirective.primary); - } + else if (result.resolvedTypeReferenceDirective.packageId) { + trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_with_Package_ID_2_primary_Colon_3, typeReferenceDirectiveName, result.resolvedTypeReferenceDirective.resolvedFileName, packageIdToString(result.resolvedTypeReferenceDirective.packageId), result.resolvedTypeReferenceDirective.primary); + } + else { + trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, result.resolvedTypeReferenceDirective.resolvedFileName, result.resolvedTypeReferenceDirective.primary); } + } - function primaryLookup(): PathAndPackageId | undefined { - // Check primary library paths - if (typeRoots && typeRoots.length) { - if (traceEnabled) { - trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", ")); - } - return firstDefined(typeRoots, typeRoot => { - const candidate = combinePaths(typeRoot, typeReferenceDirectiveName); - const candidateDirectory = getDirectoryPath(candidate); - const directoryExists = directoryProbablyExists(candidateDirectory, host); - if (!directoryExists && traceEnabled) { - trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidateDirectory); - } - return resolvedTypeScriptOnly( - loadNodeModuleFromDirectory(Extensions.DtsOnly, candidate, - !directoryExists, moduleResolutionState)); - }); - } - else { - if (traceEnabled) { - trace(host, Diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths); + function primaryLookup(): PathAndPackageId | undefined { + // Check primary library paths + if (typeRoots && typeRoots.length) { + if (traceEnabled) { + trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", ")); + } + return firstDefined(typeRoots, typeRoot => { + const candidate = combinePaths(typeRoot, typeReferenceDirectiveName); + const candidateDirectory = getDirectoryPath(candidate); + const directoryExists = directoryProbablyExists(candidateDirectory, host); + if (!directoryExists && traceEnabled) { + trace(host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidateDirectory); } + return resolvedTypeScriptOnly( + loadNodeModuleFromDirectory(Extensions.DtsOnly, candidate, + !directoryExists, moduleResolutionState)); + }); + } + else { + if (traceEnabled) { + trace(host, Diagnostics.Root_directory_cannot_be_determined_skipping_primary_search_paths); } } + } - function secondaryLookup(): PathAndPackageId | undefined { - const initialLocationForSecondaryLookup = containingFile && getDirectoryPath(containingFile); + function secondaryLookup(): PathAndPackageId | undefined { + const initialLocationForSecondaryLookup = containingFile && getDirectoryPath(containingFile); - if (initialLocationForSecondaryLookup !== undefined) { - // check secondary locations - if (traceEnabled) { - trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup); - } - let result: Resolved | undefined; - if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) { - const searchResult = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined); - result = searchResult && searchResult.value; - } - else { - const { path: candidate } = normalizePathForCJSResolution(initialLocationForSecondaryLookup, typeReferenceDirectiveName); - result = nodeLoadModuleByRelativeName(Extensions.DtsOnly, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true); - } - return resolvedTypeScriptOnly(result); + if (initialLocationForSecondaryLookup !== undefined) { + // check secondary locations + if (traceEnabled) { + trace(host, Diagnostics.Looking_up_in_node_modules_folder_initial_location_0, initialLocationForSecondaryLookup); + } + let result: Resolved | undefined; + if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) { + const searchResult = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined); + result = searchResult && searchResult.value; } else { - if (traceEnabled) { - trace(host, Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_node_modules_folder); - } + const { path: candidate } = normalizePathForCJSResolution(initialLocationForSecondaryLookup, typeReferenceDirectiveName); + result = nodeLoadModuleByRelativeName(Extensions.DtsOnly, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true); + } + return resolvedTypeScriptOnly(result); + } + else { + if (traceEnabled) { + trace(host, Diagnostics.Containing_file_is_not_specified_and_root_directory_cannot_be_determined_skipping_lookup_in_node_modules_folder); } } } +} - function getDefaultNodeResolutionFeatures(options: CompilerOptions) { - return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node16 ? NodeResolutionFeatures.Node16Default : - getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext ? NodeResolutionFeatures.NodeNextDefault : - NodeResolutionFeatures.None; - } +function getDefaultNodeResolutionFeatures(options: CompilerOptions) { + return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node16 ? NodeResolutionFeatures.Node16Default : + getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext ? NodeResolutionFeatures.NodeNextDefault : + NodeResolutionFeatures.None; +} - /** - * @internal - * Does not try `@types/${packageName}` - use a second pass if needed. - */ - export function resolvePackageNameToPackageJson( - packageName: string, - containingDirectory: string, - options: CompilerOptions, - host: ModuleResolutionHost, - cache: ModuleResolutionCache | undefined, - ): PackageJsonInfo | undefined { - const moduleResolutionState = getTemporaryModuleResolutionState(cache?.getPackageJsonInfoCache(), host, options); - - return forEachAncestorDirectory(containingDirectory, ancestorDirectory => { - if (getBaseFileName(ancestorDirectory) !== "node_modules") { - const nodeModulesFolder = combinePaths(ancestorDirectory, "node_modules"); - const candidate = combinePaths(nodeModulesFolder, packageName); - return getPackageJsonInfo(candidate, /*onlyRecordFailures*/ false, moduleResolutionState); - } - }); - } +/** + * @internal + * Does not try `@types/${packageName}` - use a second pass if needed. + */ +export function resolvePackageNameToPackageJson( + packageName: string, + containingDirectory: string, + options: CompilerOptions, + host: ModuleResolutionHost, + cache: ModuleResolutionCache | undefined, +): PackageJsonInfo | undefined { + const moduleResolutionState = getTemporaryModuleResolutionState(cache?.getPackageJsonInfoCache(), host, options); + + return forEachAncestorDirectory(containingDirectory, ancestorDirectory => { + if (getBaseFileName(ancestorDirectory) !== "node_modules") { + const nodeModulesFolder = combinePaths(ancestorDirectory, "node_modules"); + const candidate = combinePaths(nodeModulesFolder, packageName); + return getPackageJsonInfo(candidate, /*onlyRecordFailures*/ false, moduleResolutionState); + } + }); +} - /** - * Given a set of options, returns the set of type directive names - * that should be included for this program automatically. - * This list could either come from the config file, - * or from enumerating the types root + initial secondary types lookup location. - * More type directives might appear in the program later as a result of loading actual source files; - * this list is only the set of defaults that are implicitly included. - */ - export function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[] { - // Use explicit type list from tsconfig.json - if (options.types) { - return options.types; - } - - // Walk the primary type lookup locations - const result: string[] = []; - if (host.directoryExists && host.getDirectories) { - const typeRoots = getEffectiveTypeRoots(options, host); - if (typeRoots) { - for (const root of typeRoots) { - if (host.directoryExists(root)) { - for (const typeDirectivePath of host.getDirectories(root)) { - const normalized = normalizePath(typeDirectivePath); - const packageJsonPath = combinePaths(root, normalized, "package.json"); - // `types-publisher` sometimes creates packages with `"typings": null` for packages that don't provide their own types. - // See `createNotNeededPackageJSON` in the types-publisher` repo. - // eslint-disable-next-line no-null/no-null - const isNotNeededPackage = host.fileExists(packageJsonPath) && (readJson(packageJsonPath, host) as PackageJson).typings === null; - if (!isNotNeededPackage) { - const baseFileName = getBaseFileName(normalized); - - // At this stage, skip results with leading dot. - if (baseFileName.charCodeAt(0) !== CharacterCodes.dot) { - // Return just the type directive names - result.push(baseFileName); - } +/** + * Given a set of options, returns the set of type directive names + * that should be included for this program automatically. + * This list could either come from the config file, + * or from enumerating the types root + initial secondary types lookup location. + * More type directives might appear in the program later as a result of loading actual source files; + * this list is only the set of defaults that are implicitly included. + */ +export function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[] { + // Use explicit type list from tsconfig.json + if (options.types) { + return options.types; + } + + // Walk the primary type lookup locations + const result: string[] = []; + if (host.directoryExists && host.getDirectories) { + const typeRoots = getEffectiveTypeRoots(options, host); + if (typeRoots) { + for (const root of typeRoots) { + if (host.directoryExists(root)) { + for (const typeDirectivePath of host.getDirectories(root)) { + const normalized = normalizePath(typeDirectivePath); + const packageJsonPath = combinePaths(root, normalized, "package.json"); + // `types-publisher` sometimes creates packages with `"typings": null` for packages that don't provide their own types. + // See `createNotNeededPackageJSON` in the types-publisher` repo. + // eslint-disable-next-line no-null/no-null + const isNotNeededPackage = host.fileExists(packageJsonPath) && (readJson(packageJsonPath, host) as PackageJson).typings === null; + if (!isNotNeededPackage) { + const baseFileName = getBaseFileName(normalized); + + // At this stage, skip results with leading dot. + if (baseFileName.charCodeAt(0) !== CharacterCodes.dot) { + // Return just the type directive names + result.push(baseFileName); } } } } } } - return result; } + return result; +} - export interface TypeReferenceDirectiveResolutionCache extends PerDirectoryResolutionCache, PackageJsonInfoCache { - /*@internal*/ clearAllExceptPackageJsonInfoCache(): void; - } +export interface TypeReferenceDirectiveResolutionCache extends PerDirectoryResolutionCache, PackageJsonInfoCache { + /** @internal */ clearAllExceptPackageJsonInfoCache(): void; +} - export interface ModeAwareCache { - get(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): T | undefined; - set(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, value: T): this; - delete(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): this; - has(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): boolean; - forEach(cb: (elem: T, key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => void): void; - size(): number; - } +export interface ModeAwareCache { + get(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): T | undefined; + set(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, value: T): this; + delete(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): this; + has(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): boolean; + forEach(cb: (elem: T, key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => void): void; + size(): number; +} +/** + * Cached resolutions per containing directory. + * This assumes that any module id will have the same resolution for sibling files located in the same folder. + */ +export interface PerDirectoryResolutionCache { + getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): ModeAwareCache; + clear(): void; /** - * Cached resolutions per containing directory. - * This assumes that any module id will have the same resolution for sibling files located in the same folder. + * Updates with the current compilerOptions the cache will operate with. + * This updates the redirects map as well if needed so module resolutions are cached if they can across the projects */ - export interface PerDirectoryResolutionCache { - getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): ModeAwareCache; - clear(): void; - /** - * Updates with the current compilerOptions the cache will operate with. - * This updates the redirects map as well if needed so module resolutions are cached if they can across the projects - */ - update(options: CompilerOptions): void; - } + update(options: CompilerOptions): void; +} - export interface ModuleResolutionCache extends PerDirectoryResolutionCache, NonRelativeModuleNameResolutionCache, PackageJsonInfoCache { - getPackageJsonInfoCache(): PackageJsonInfoCache; - /*@internal*/ clearAllExceptPackageJsonInfoCache(): void; - } +export interface ModuleResolutionCache extends PerDirectoryResolutionCache, NonRelativeModuleNameResolutionCache, PackageJsonInfoCache { + getPackageJsonInfoCache(): PackageJsonInfoCache; + /** @internal */ clearAllExceptPackageJsonInfoCache(): void; +} - /** - * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory - * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. - */ - export interface NonRelativeModuleNameResolutionCache extends PackageJsonInfoCache { - getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; - } - - export interface PackageJsonInfoCache { - /*@internal*/ getPackageJsonInfo(packageJsonPath: string): PackageJsonInfo | boolean | undefined; - /*@internal*/ setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean): void; - /*@internal*/ entries(): [Path, PackageJsonInfo | boolean][]; - /*@internal*/ getInternalMap(): ESMap | undefined; - clear(): void; - } - - export interface PerModuleNameCache { - get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined; - set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void; - } - - /*@internal*/ - export interface CacheWithRedirects { - getOwnMap: () => ESMap; - redirectsMap: ESMap>; - getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): ESMap; - clear(): void; - setOwnOptions(newOptions: CompilerOptions): void; - setOwnMap(newOwnMap: ESMap): void; - } - - /*@internal*/ - export function createCacheWithRedirects(options?: CompilerOptions): CacheWithRedirects { - let ownMap: ESMap = new Map(); - const redirectsMap = new Map>(); - return { - getOwnMap, - redirectsMap, - getOrCreateMapOfCacheRedirects, - clear, - setOwnOptions, - setOwnMap - }; +/** + * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory + * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. + */ +export interface NonRelativeModuleNameResolutionCache extends PackageJsonInfoCache { + getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; +} - function getOwnMap() { - return ownMap; - } +export interface PackageJsonInfoCache { + /** @internal */ getPackageJsonInfo(packageJsonPath: string): PackageJsonInfo | boolean | undefined; + /** @internal */ setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean): void; + /** @internal */ entries(): [Path, PackageJsonInfo | boolean][]; + /** @internal */ getInternalMap(): ESMap | undefined; + clear(): void; +} - function setOwnOptions(newOptions: CompilerOptions) { - options = newOptions; - } +export interface PerModuleNameCache { + get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined; + set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void; +} - function setOwnMap(newOwnMap: ESMap) { - ownMap = newOwnMap; - } +/** @internal */ +export interface CacheWithRedirects { + getOwnMap: () => ESMap; + redirectsMap: ESMap>; + getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): ESMap; + clear(): void; + setOwnOptions(newOptions: CompilerOptions): void; + setOwnMap(newOwnMap: ESMap): void; +} - function getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined) { - if (!redirectedReference) { - return ownMap; - } - const path = redirectedReference.sourceFile.path; - let redirects = redirectsMap.get(path); - if (!redirects) { - // Reuse map if redirected reference map uses same resolution - redirects = !options || optionsHaveModuleResolutionChanges(options, redirectedReference.commandLine.options) ? new Map() : ownMap; - redirectsMap.set(path, redirects); - } - return redirects; - } +/** @internal */ +export function createCacheWithRedirects(options?: CompilerOptions): CacheWithRedirects { + let ownMap: ESMap = new Map(); + const redirectsMap = new Map>(); + return { + getOwnMap, + redirectsMap, + getOrCreateMapOfCacheRedirects, + clear, + setOwnOptions, + setOwnMap + }; - function clear() { - ownMap.clear(); - redirectsMap.clear(); - } + function getOwnMap() { + return ownMap; } - function createPackageJsonInfoCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): PackageJsonInfoCache { - let cache: ESMap | undefined; - return { getPackageJsonInfo, setPackageJsonInfo, clear, entries, getInternalMap }; - function getPackageJsonInfo(packageJsonPath: string) { - return cache?.get(toPath(packageJsonPath, currentDirectory, getCanonicalFileName)); - } - function setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean) { - (cache ||= new Map()).set(toPath(packageJsonPath, currentDirectory, getCanonicalFileName), info); - } - function clear() { - cache = undefined; - } - function entries() { - const iter = cache?.entries(); - return iter ? arrayFrom(iter) : []; - } - function getInternalMap() { - return cache; - } + function setOwnOptions(newOptions: CompilerOptions) { + options = newOptions; } - function getOrCreateCache(cacheWithRedirects: CacheWithRedirects, redirectedReference: ResolvedProjectReference | undefined, key: string, create: () => T): T { - const cache = cacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); - let result = cache.get(key); - if (!result) { - result = create(); - cache.set(key, result); - } - return result; + function setOwnMap(newOwnMap: ESMap) { + ownMap = newOwnMap; } - function updateRedirectsMap( - options: CompilerOptions, - directoryToModuleNameMap: CacheWithRedirects>, - moduleNameToDirectoryMap?: CacheWithRedirects - ) { - if (!options.configFile) return; - if (directoryToModuleNameMap.redirectsMap.size === 0) { - // The own map will be for projectCompilerOptions - Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.redirectsMap.size === 0); - Debug.assert(directoryToModuleNameMap.getOwnMap().size === 0); - Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.getOwnMap().size === 0); - directoryToModuleNameMap.redirectsMap.set(options.configFile.path, directoryToModuleNameMap.getOwnMap()); - moduleNameToDirectoryMap?.redirectsMap.set(options.configFile.path, moduleNameToDirectoryMap.getOwnMap()); + function getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined) { + if (!redirectedReference) { + return ownMap; } - else { - // Set correct own map - Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.redirectsMap.size > 0); - const ref: ResolvedProjectReference = { - sourceFile: options.configFile, - commandLine: { options } as ParsedCommandLine - }; - directoryToModuleNameMap.setOwnMap(directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); - moduleNameToDirectoryMap?.setOwnMap(moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); + const path = redirectedReference.sourceFile.path; + let redirects = redirectsMap.get(path); + if (!redirects) { + // Reuse map if redirected reference map uses same resolution + redirects = !options || optionsHaveModuleResolutionChanges(options, redirectedReference.commandLine.options) ? new Map() : ownMap; + redirectsMap.set(path, redirects); } - directoryToModuleNameMap.setOwnOptions(options); - moduleNameToDirectoryMap?.setOwnOptions(options); + return redirects; } - function createPerDirectoryResolutionCache(currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, directoryToModuleNameMap: CacheWithRedirects>): PerDirectoryResolutionCache { - return { - getOrCreateCacheForDirectory, - clear, - update, - }; + function clear() { + ownMap.clear(); + redirectsMap.clear(); + } +} - function clear() { - directoryToModuleNameMap.clear(); - } - - function update(options: CompilerOptions) { - updateRedirectsMap(options, directoryToModuleNameMap); - } - - function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { - const path = toPath(directoryName, currentDirectory, getCanonicalFileName); - return getOrCreateCache>(directoryToModuleNameMap, redirectedReference, path, () => createModeAwareCache()); - } - } - - /* @internal */ - export function createModeAwareCache(): ModeAwareCache { - const underlying = new Map(); - type ModeAwareCacheKey = string & { __modeAwareCacheKey: any; }; - const memoizedReverseKeys = new Map(); - - const cache: ModeAwareCache = { - get(specifier, mode) { - return underlying.get(getUnderlyingCacheKey(specifier, mode)); - }, - set(specifier, mode, value) { - underlying.set(getUnderlyingCacheKey(specifier, mode), value); - return cache; - }, - delete(specifier, mode) { - underlying.delete(getUnderlyingCacheKey(specifier, mode)); - return cache; - }, - has(specifier, mode) { - return underlying.has(getUnderlyingCacheKey(specifier, mode)); - }, - forEach(cb) { - return underlying.forEach((elem, key) => { - const [specifier, mode] = memoizedReverseKeys.get(key)!; - return cb(elem, specifier, mode); - }); - }, - size() { - return underlying.size; - } - }; +function createPackageJsonInfoCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): PackageJsonInfoCache { + let cache: ESMap | undefined; + return { getPackageJsonInfo, setPackageJsonInfo, clear, entries, getInternalMap }; + function getPackageJsonInfo(packageJsonPath: string) { + return cache?.get(toPath(packageJsonPath, currentDirectory, getCanonicalFileName)); + } + function setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean) { + (cache ||= new Map()).set(toPath(packageJsonPath, currentDirectory, getCanonicalFileName), info); + } + function clear() { + cache = undefined; + } + function entries() { + const iter = cache?.entries(); + return iter ? arrayFrom(iter) : []; + } + function getInternalMap() { return cache; + } +} - function getUnderlyingCacheKey(specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) { - const result = (mode === undefined ? specifier : `${mode}|${specifier}`) as ModeAwareCacheKey; - memoizedReverseKeys.set(result, [specifier, mode]); - return result; - } +function getOrCreateCache(cacheWithRedirects: CacheWithRedirects, redirectedReference: ResolvedProjectReference | undefined, key: string, create: () => T): T { + const cache = cacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); + let result = cache.get(key); + if (!result) { + result = create(); + cache.set(key, result); } + return result; +} - /* @internal */ - export function getResolutionName(entry: FileReference | StringLiteralLike) { - // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. - return isStringLiteralLike(entry) ? entry.text : entry.fileName.toLowerCase(); - } - - /* @internal */ - export function getResolutionMode(entry: FileReference | StringLiteralLike, file: SourceFile) { - return isStringLiteralLike(entry) ? getModeForUsageLocation(file, entry) : entry.resolutionMode || file.impliedNodeFormat; - } - - /* @internal */ - export function zipToModeAwareCache(file: SourceFile, keys: readonly StringLiteralLike[] | readonly FileReference[], values: readonly V[]): ModeAwareCache { - Debug.assert(keys.length === values.length); - const map = createModeAwareCache(); - for (let i = 0; i < keys.length; ++i) { - const entry = keys[i]; - map.set(getResolutionName(entry), getResolutionMode(entry, file), values[i]); - } - return map; - } - - export function createModuleResolutionCache( - currentDirectory: string, - getCanonicalFileName: (s: string) => string, - options?: CompilerOptions - ): ModuleResolutionCache; - /*@internal*/ - export function createModuleResolutionCache( - currentDirectory: string, - getCanonicalFileName: GetCanonicalFileName, - options: undefined, - directoryToModuleNameMap: CacheWithRedirects>, - moduleNameToDirectoryMap: CacheWithRedirects, - ): ModuleResolutionCache; - export function createModuleResolutionCache( - currentDirectory: string, - getCanonicalFileName: GetCanonicalFileName, - options?: CompilerOptions, - directoryToModuleNameMap?: CacheWithRedirects>, - moduleNameToDirectoryMap?: CacheWithRedirects, - ): ModuleResolutionCache { - const perDirectoryResolutionCache = createPerDirectoryResolutionCache(currentDirectory, getCanonicalFileName, directoryToModuleNameMap ||= createCacheWithRedirects(options)); - moduleNameToDirectoryMap ||= createCacheWithRedirects(options); - const packageJsonInfoCache = createPackageJsonInfoCache(currentDirectory, getCanonicalFileName); - - return { - ...packageJsonInfoCache, - ...perDirectoryResolutionCache, - getOrCreateCacheForModuleName, - clear, - update, - getPackageJsonInfoCache: () => packageJsonInfoCache, - clearAllExceptPackageJsonInfoCache, +function updateRedirectsMap( + options: CompilerOptions, + directoryToModuleNameMap: CacheWithRedirects>, + moduleNameToDirectoryMap?: CacheWithRedirects +) { + if (!options.configFile) return; + if (directoryToModuleNameMap.redirectsMap.size === 0) { + // The own map will be for projectCompilerOptions + Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.redirectsMap.size === 0); + Debug.assert(directoryToModuleNameMap.getOwnMap().size === 0); + Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.getOwnMap().size === 0); + directoryToModuleNameMap.redirectsMap.set(options.configFile.path, directoryToModuleNameMap.getOwnMap()); + moduleNameToDirectoryMap?.redirectsMap.set(options.configFile.path, moduleNameToDirectoryMap.getOwnMap()); + } + else { + // Set correct own map + Debug.assert(!moduleNameToDirectoryMap || moduleNameToDirectoryMap.redirectsMap.size > 0); + const ref: ResolvedProjectReference = { + sourceFile: options.configFile, + commandLine: { options } as ParsedCommandLine }; + directoryToModuleNameMap.setOwnMap(directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); + moduleNameToDirectoryMap?.setOwnMap(moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); + } + directoryToModuleNameMap.setOwnOptions(options); + moduleNameToDirectoryMap?.setOwnOptions(options); +} - function clear() { - clearAllExceptPackageJsonInfoCache(); - packageJsonInfoCache.clear(); - } +function createPerDirectoryResolutionCache(currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, directoryToModuleNameMap: CacheWithRedirects>): PerDirectoryResolutionCache { + return { + getOrCreateCacheForDirectory, + clear, + update, + }; - function clearAllExceptPackageJsonInfoCache() { - perDirectoryResolutionCache.clear(); - moduleNameToDirectoryMap!.clear(); - } + function clear() { + directoryToModuleNameMap.clear(); + } - function update(options: CompilerOptions) { - updateRedirectsMap(options, directoryToModuleNameMap!, moduleNameToDirectoryMap); - } + function update(options: CompilerOptions) { + updateRedirectsMap(options, directoryToModuleNameMap); + } + + function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { + const path = toPath(directoryName, currentDirectory, getCanonicalFileName); + return getOrCreateCache>(directoryToModuleNameMap, redirectedReference, path, () => createModeAwareCache()); + } +} - function getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { - Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); - return getOrCreateCache(moduleNameToDirectoryMap!, redirectedReference, mode === undefined ? nonRelativeModuleName : `${mode}|${nonRelativeModuleName}`, createPerModuleNameCache); +/** @internal */ +export function createModeAwareCache(): ModeAwareCache { + const underlying = new Map(); + type ModeAwareCacheKey = string & { __modeAwareCacheKey: any; }; + const memoizedReverseKeys = new Map(); + + const cache: ModeAwareCache = { + get(specifier, mode) { + return underlying.get(getUnderlyingCacheKey(specifier, mode)); + }, + set(specifier, mode, value) { + underlying.set(getUnderlyingCacheKey(specifier, mode), value); + return cache; + }, + delete(specifier, mode) { + underlying.delete(getUnderlyingCacheKey(specifier, mode)); + return cache; + }, + has(specifier, mode) { + return underlying.has(getUnderlyingCacheKey(specifier, mode)); + }, + forEach(cb) { + return underlying.forEach((elem, key) => { + const [specifier, mode] = memoizedReverseKeys.get(key)!; + return cb(elem, specifier, mode); + }); + }, + size() { + return underlying.size; } + }; + return cache; + + function getUnderlyingCacheKey(specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) { + const result = (mode === undefined ? specifier : `${mode}|${specifier}`) as ModeAwareCacheKey; + memoizedReverseKeys.set(result, [specifier, mode]); + return result; + } +} - function createPerModuleNameCache(): PerModuleNameCache { - const directoryPathMap = new Map(); +/** @internal */ +export function getResolutionName(entry: FileReference | StringLiteralLike) { + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. + return isStringLiteralLike(entry) ? entry.text : entry.fileName.toLowerCase(); +} - return { get, set }; +/** @internal */ +export function getResolutionMode(entry: FileReference | StringLiteralLike, file: SourceFile) { + return isStringLiteralLike(entry) ? getModeForUsageLocation(file, entry) : entry.resolutionMode || file.impliedNodeFormat; +} - function get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined { - return directoryPathMap.get(toPath(directory, currentDirectory, getCanonicalFileName)); - } +/** @internal */ +export function zipToModeAwareCache(file: SourceFile, keys: readonly StringLiteralLike[] | readonly FileReference[], values: readonly V[]): ModeAwareCache { + Debug.assert(keys.length === values.length); + const map = createModeAwareCache(); + for (let i = 0; i < keys.length; ++i) { + const entry = keys[i]; + map.set(getResolutionName(entry), getResolutionMode(entry, file), values[i]); + } + return map; +} - /** - * At first this function add entry directory -> module resolution result to the table. - * Then it computes the set of parent folders for 'directory' that should have the same module resolution result - * and for every parent folder in set it adds entry: parent -> module resolution. . - * Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts. - * Set of parent folders that should have the same result will be: - * [ - * /a/b/c/d, /a/b/c, /a/b - * ] - * this means that request for module resolution from file in any of these folder will be immediately found in cache. - */ - function set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void { - const path = toPath(directory, currentDirectory, getCanonicalFileName); - // if entry is already in cache do nothing - if (directoryPathMap.has(path)) { - return; - } - directoryPathMap.set(path, result); - - const resolvedFileName = result.resolvedModule && - (result.resolvedModule.originalPath || result.resolvedModule.resolvedFileName); - // find common prefix between directory and resolved file name - // this common prefix should be the shortest path that has the same resolution - // directory: /a/b/c/d/e - // resolvedFileName: /a/b/foo.d.ts - // commonPrefix: /a/b - // for failed lookups cache the result for every directory up to root - const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName); - let current = path; - while (current !== commonPrefix) { - const parent = getDirectoryPath(current); - if (parent === current || directoryPathMap.has(parent)) { - break; - } - directoryPathMap.set(parent, result); - current = parent; - } - } +export function createModuleResolutionCache( + currentDirectory: string, + getCanonicalFileName: (s: string) => string, + options?: CompilerOptions +): ModuleResolutionCache; +/** @internal */ +export function createModuleResolutionCache( + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName, + options: undefined, + directoryToModuleNameMap: CacheWithRedirects>, + moduleNameToDirectoryMap: CacheWithRedirects, +): ModuleResolutionCache; +export function createModuleResolutionCache( + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName, + options?: CompilerOptions, + directoryToModuleNameMap?: CacheWithRedirects>, + moduleNameToDirectoryMap?: CacheWithRedirects, +): ModuleResolutionCache { + const perDirectoryResolutionCache = createPerDirectoryResolutionCache(currentDirectory, getCanonicalFileName, directoryToModuleNameMap ||= createCacheWithRedirects(options)); + moduleNameToDirectoryMap ||= createCacheWithRedirects(options); + const packageJsonInfoCache = createPackageJsonInfoCache(currentDirectory, getCanonicalFileName); - function getCommonPrefix(directory: Path, resolution: string) { - const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName); + return { + ...packageJsonInfoCache, + ...perDirectoryResolutionCache, + getOrCreateCacheForModuleName, + clear, + update, + getPackageJsonInfoCache: () => packageJsonInfoCache, + clearAllExceptPackageJsonInfoCache, + }; - // find first position where directory and resolution differs - let i = 0; - const limit = Math.min(directory.length, resolutionDirectory.length); - while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) { - i++; - } - if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) { - return directory; - } - const rootLength = getRootLength(directory); - if (i < rootLength) { - return undefined; - } - const sep = directory.lastIndexOf(directorySeparator, i - 1); - if (sep === -1) { - return undefined; - } - return directory.substr(0, Math.max(sep, rootLength)); - } - } + function clear() { + clearAllExceptPackageJsonInfoCache(); + packageJsonInfoCache.clear(); } - export function createTypeReferenceDirectiveResolutionCache( - currentDirectory: string, - getCanonicalFileName: (s: string) => string, - options?: CompilerOptions, - packageJsonInfoCache?: PackageJsonInfoCache, - ): TypeReferenceDirectiveResolutionCache; - /*@internal*/ - export function createTypeReferenceDirectiveResolutionCache( - currentDirectory: string, - getCanonicalFileName: GetCanonicalFileName, - options: undefined, - packageJsonInfoCache: PackageJsonInfoCache | undefined, - directoryToModuleNameMap: CacheWithRedirects>, - ): TypeReferenceDirectiveResolutionCache; - export function createTypeReferenceDirectiveResolutionCache( - currentDirectory: string, - getCanonicalFileName: GetCanonicalFileName, - options?: CompilerOptions, - packageJsonInfoCache?: PackageJsonInfoCache | undefined, - directoryToModuleNameMap?: CacheWithRedirects>, - ): TypeReferenceDirectiveResolutionCache { - const perDirectoryResolutionCache = createPerDirectoryResolutionCache(currentDirectory, getCanonicalFileName, directoryToModuleNameMap ||= createCacheWithRedirects(options)); - packageJsonInfoCache ||= createPackageJsonInfoCache(currentDirectory, getCanonicalFileName); - - return { - ...packageJsonInfoCache, - ...perDirectoryResolutionCache, - clear, - clearAllExceptPackageJsonInfoCache, - }; - - function clear() { - clearAllExceptPackageJsonInfoCache(); - packageJsonInfoCache!.clear(); - } + function clearAllExceptPackageJsonInfoCache() { + perDirectoryResolutionCache.clear(); + moduleNameToDirectoryMap!.clear(); + } - function clearAllExceptPackageJsonInfoCache() { - perDirectoryResolutionCache.clear(); - } + function update(options: CompilerOptions) { + updateRedirectsMap(options, directoryToModuleNameMap!, moduleNameToDirectoryMap); } - export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache, mode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined { - const containingDirectory = getDirectoryPath(containingFile); - const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory); - if (!perFolderCache) return undefined; - return perFolderCache.get(moduleName, mode); + function getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { + Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); + return getOrCreateCache(moduleNameToDirectoryMap!, redirectedReference, mode === undefined ? nonRelativeModuleName : `${mode}|${nonRelativeModuleName}`, createPerModuleNameCache); } - export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); - if (redirectedReference) { - compilerOptions = redirectedReference.commandLine.options; + function createPerModuleNameCache(): PerModuleNameCache { + const directoryPathMap = new Map(); + + return { get, set }; + + function get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined { + return directoryPathMap.get(toPath(directory, currentDirectory, getCanonicalFileName)); } - if (traceEnabled) { - trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); - if (redirectedReference) { - trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); + + /** + * At first this function add entry directory -> module resolution result to the table. + * Then it computes the set of parent folders for 'directory' that should have the same module resolution result + * and for every parent folder in set it adds entry: parent -> module resolution. . + * Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts. + * Set of parent folders that should have the same result will be: + * [ + * /a/b/c/d, /a/b/c, /a/b + * ] + * this means that request for module resolution from file in any of these folder will be immediately found in cache. + */ + function set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void { + const path = toPath(directory, currentDirectory, getCanonicalFileName); + // if entry is already in cache do nothing + if (directoryPathMap.has(path)) { + return; + } + directoryPathMap.set(path, result); + + const resolvedFileName = result.resolvedModule && + (result.resolvedModule.originalPath || result.resolvedModule.resolvedFileName); + // find common prefix between directory and resolved file name + // this common prefix should be the shortest path that has the same resolution + // directory: /a/b/c/d/e + // resolvedFileName: /a/b/foo.d.ts + // commonPrefix: /a/b + // for failed lookups cache the result for every directory up to root + const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName); + let current = path; + while (current !== commonPrefix) { + const parent = getDirectoryPath(current); + if (parent === current || directoryPathMap.has(parent)) { + break; + } + directoryPathMap.set(parent, result); + current = parent; } } - const containingDirectory = getDirectoryPath(containingFile); - const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference); - let result = perFolderCache && perFolderCache.get(moduleName, resolutionMode); - if (result) { - if (traceEnabled) { - trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); + function getCommonPrefix(directory: Path, resolution: string) { + const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName); + + // find first position where directory and resolution differs + let i = 0; + const limit = Math.min(directory.length, resolutionDirectory.length); + while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) { + i++; } - } - else { - let moduleResolution = compilerOptions.moduleResolution; - if (moduleResolution === undefined) { - switch (getEmitModuleKind(compilerOptions)) { - case ModuleKind.CommonJS: - moduleResolution = ModuleResolutionKind.NodeJs; - break; - case ModuleKind.Node16: - moduleResolution = ModuleResolutionKind.Node16; - break; - case ModuleKind.NodeNext: - moduleResolution = ModuleResolutionKind.NodeNext; - break; - default: - moduleResolution = ModuleResolutionKind.Classic; - break; - } - if (traceEnabled) { - trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); - } + if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) { + return directory; } - else { - if (traceEnabled) { - trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]); - } + const rootLength = getRootLength(directory); + if (i < rootLength) { + return undefined; + } + const sep = directory.lastIndexOf(directorySeparator, i - 1); + if (sep === -1) { + return undefined; } + return directory.substr(0, Math.max(sep, rootLength)); + } + } +} - perfLogger.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/); - switch (moduleResolution) { - case ModuleResolutionKind.Node16: - result = node16ModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); - break; - case ModuleResolutionKind.NodeNext: - result = nodeNextModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); +export function createTypeReferenceDirectiveResolutionCache( + currentDirectory: string, + getCanonicalFileName: (s: string) => string, + options?: CompilerOptions, + packageJsonInfoCache?: PackageJsonInfoCache, +): TypeReferenceDirectiveResolutionCache; +/** @internal */ +export function createTypeReferenceDirectiveResolutionCache( + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName, + options: undefined, + packageJsonInfoCache: PackageJsonInfoCache | undefined, + directoryToModuleNameMap: CacheWithRedirects>, +): TypeReferenceDirectiveResolutionCache; +export function createTypeReferenceDirectiveResolutionCache( + currentDirectory: string, + getCanonicalFileName: GetCanonicalFileName, + options?: CompilerOptions, + packageJsonInfoCache?: PackageJsonInfoCache | undefined, + directoryToModuleNameMap?: CacheWithRedirects>, +): TypeReferenceDirectiveResolutionCache { + const perDirectoryResolutionCache = createPerDirectoryResolutionCache(currentDirectory, getCanonicalFileName, directoryToModuleNameMap ||= createCacheWithRedirects(options)); + packageJsonInfoCache ||= createPackageJsonInfoCache(currentDirectory, getCanonicalFileName); + + return { + ...packageJsonInfoCache, + ...perDirectoryResolutionCache, + clear, + clearAllExceptPackageJsonInfoCache, + }; + + function clear() { + clearAllExceptPackageJsonInfoCache(); + packageJsonInfoCache!.clear(); + } + + function clearAllExceptPackageJsonInfoCache() { + perDirectoryResolutionCache.clear(); + } +} + +export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache, mode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined { + const containingDirectory = getDirectoryPath(containingFile); + const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory); + if (!perFolderCache) return undefined; + return perFolderCache.get(moduleName, mode); +} + +export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + if (redirectedReference) { + compilerOptions = redirectedReference.commandLine.options; + } + if (traceEnabled) { + trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile); + if (redirectedReference) { + trace(host, Diagnostics.Using_compiler_options_of_project_reference_redirect_0, redirectedReference.sourceFile.fileName); + } + } + const containingDirectory = getDirectoryPath(containingFile); + const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference); + let result = perFolderCache && perFolderCache.get(moduleName, resolutionMode); + + if (result) { + if (traceEnabled) { + trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); + } + } + else { + let moduleResolution = compilerOptions.moduleResolution; + if (moduleResolution === undefined) { + switch (getEmitModuleKind(compilerOptions)) { + case ModuleKind.CommonJS: + moduleResolution = ModuleResolutionKind.NodeJs; break; - case ModuleResolutionKind.NodeJs: - result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); + case ModuleKind.Node16: + moduleResolution = ModuleResolutionKind.Node16; break; - case ModuleResolutionKind.Classic: - result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); + case ModuleKind.NodeNext: + moduleResolution = ModuleResolutionKind.NodeNext; break; default: - return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`); + moduleResolution = ModuleResolutionKind.Classic; + break; } - if (result && result.resolvedModule) perfLogger.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`); - perfLogger.logStopResolveModule((result && result.resolvedModule) ? "" + result.resolvedModule.resolvedFileName : "null"); - - if (perFolderCache) { - perFolderCache.set(moduleName, resolutionMode, result); - if (!isExternalModuleNameRelative(moduleName)) { - // put result in per-module name cache - cache.getOrCreateCacheForModuleName(moduleName, resolutionMode, redirectedReference).set(containingDirectory, result); - } + if (traceEnabled) { + trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); + } + } + else { + if (traceEnabled) { + trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]); + } + } + + perfLogger.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/); + switch (moduleResolution) { + case ModuleResolutionKind.Node16: + result = node16ModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); + break; + case ModuleResolutionKind.NodeNext: + result = nodeNextModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); + break; + case ModuleResolutionKind.NodeJs: + result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); + break; + case ModuleResolutionKind.Classic: + result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); + break; + default: + return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`); + } + if (result && result.resolvedModule) perfLogger.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`); + perfLogger.logStopResolveModule((result && result.resolvedModule) ? "" + result.resolvedModule.resolvedFileName : "null"); + + if (perFolderCache) { + perFolderCache.set(moduleName, resolutionMode, result); + if (!isExternalModuleNameRelative(moduleName)) { + // put result in per-module name cache + cache.getOrCreateCacheForModuleName(moduleName, resolutionMode, redirectedReference).set(containingDirectory, result); } } + } - if (traceEnabled) { - if (result.resolvedModule) { - 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); - } + if (traceEnabled) { + if (result.resolvedModule) { + 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_not_resolved, moduleName); + trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); } } - - return result; + else { + trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName); + } } - /* - * Every module resolution kind can has its specific understanding how to load module from a specific path on disk - * I.e. for path '/a/b/c': - * - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails - * it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with - * 'typings' entry or file 'index' with some supported extension - * - Classic loader will only try to interpret '/a/b/c' as file. - */ - type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined; - - /** - * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to - * mitigate differences between design time structure of the project and its runtime counterpart so the same import name - * can be resolved successfully by TypeScript compiler and runtime module loader. - * If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will - * fallback to standard resolution routine. - * - * - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative - * names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will - * be '/a/b/c/d' - * - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names - * will be resolved based on the content of the module name. - * Structure of 'paths' compiler options - * 'paths': { - * pattern-1: [...substitutions], - * pattern-2: [...substitutions], - * ... - * pattern-n: [...substitutions] - * } - * Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against - * all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case. - * If pattern contains '*' then to match pattern "*" module name must start with the and end with . - * denotes part of the module name between and . - * If module name can be matches with multiple patterns then pattern with the longest prefix will be picked. - * After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module - * from the candidate location. - * Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every - * substitution in the list and replace '*' with string. If candidate location is not rooted it - * will be converted to absolute using baseUrl. - * For example: - * baseUrl: /a/b/c - * "paths": { - * // match all module names - * "*": [ - * "*", // use matched name as is, - * // will be looked as /a/b/c/ - * - * "folder1/*" // substitution will convert matched name to 'folder1/', - * // since it is not rooted then final candidate location will be /a/b/c/folder1/ - * ], - * // match module names that start with 'components/' - * "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/ to '/root/components/folder1/', - * // it is rooted so it will be final candidate location - * } - * - * 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if - * they were in the same location. For example lets say there are two files - * '/local/src/content/file1.ts' - * '/shared/components/contracts/src/content/protocols/file2.ts' - * After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so - * if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime. - * 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all - * root dirs were merged together. - * I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ]. - * Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file: - * '/local/src/content/protocols/file2' and try to load it - failure. - * Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will - * be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining - * entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location. - */ - function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, - state: ModuleResolutionState): Resolved | undefined { - - const resolved = tryLoadModuleUsingPathsIfEligible(extensions, moduleName, loader, state); - if (resolved) return resolved.value; + return result; +} - if (!isExternalModuleNameRelative(moduleName)) { - return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, state); - } - else { - return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, state); - } +/* + * Every module resolution kind can has its specific understanding how to load module from a specific path on disk + * I.e. for path '/a/b/c': + * - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails + * it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with + * 'typings' entry or file 'index' with some supported extension + * - Classic loader will only try to interpret '/a/b/c' as file. + */ +type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined; + +/** + * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to + * mitigate differences between design time structure of the project and its runtime counterpart so the same import name + * can be resolved successfully by TypeScript compiler and runtime module loader. + * If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will + * fallback to standard resolution routine. + * + * - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative + * names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will + * be '/a/b/c/d' + * - paths - this setting can only be used when baseUrl is specified. allows to tune how non-relative module names + * will be resolved based on the content of the module name. + * Structure of 'paths' compiler options + * 'paths': { + * pattern-1: [...substitutions], + * pattern-2: [...substitutions], + * ... + * pattern-n: [...substitutions] + * } + * Pattern here is a string that can contain zero or one '*' character. During module resolution module name will be matched against + * all patterns in the list. Matching for patterns that don't contain '*' means that module name must be equal to pattern respecting the case. + * If pattern contains '*' then to match pattern "*" module name must start with the and end with . + * denotes part of the module name between and . + * If module name can be matches with multiple patterns then pattern with the longest prefix will be picked. + * After selecting pattern we'll use list of substitutions to get candidate locations of the module and the try to load module + * from the candidate location. + * Substitution is a string that can contain zero or one '*'. To get candidate location from substitution we'll pick every + * substitution in the list and replace '*' with string. If candidate location is not rooted it + * will be converted to absolute using baseUrl. + * For example: + * baseUrl: /a/b/c + * "paths": { + * // match all module names + * "*": [ + * "*", // use matched name as is, + * // will be looked as /a/b/c/ + * + * "folder1/*" // substitution will convert matched name to 'folder1/', + * // since it is not rooted then final candidate location will be /a/b/c/folder1/ + * ], + * // match module names that start with 'components/' + * "components/*": [ "/root/components/*" ] // substitution will convert /components/folder1/ to '/root/components/folder1/', + * // it is rooted so it will be final candidate location + * } + * + * 'rootDirs' allows the project to be spreaded across multiple locations and resolve modules with relative names as if + * they were in the same location. For example lets say there are two files + * '/local/src/content/file1.ts' + * '/shared/components/contracts/src/content/protocols/file2.ts' + * After bundling content of '/shared/components/contracts/src' will be merged with '/local/src' so + * if file1 has the following import 'import {x} from "./protocols/file2"' it will be resolved successfully in runtime. + * 'rootDirs' provides the way to tell compiler that in order to get the whole project it should behave as if content of all + * root dirs were merged together. + * I.e. for the example above 'rootDirs' will have two entries: [ '/local/src', '/shared/components/contracts/src' ]. + * Compiler will first convert './protocols/file2' into absolute path relative to the location of containing file: + * '/local/src/content/protocols/file2' and try to load it - failure. + * Then it will search 'rootDirs' looking for a longest matching prefix of this absolute path and if such prefix is found - absolute path will + * be converted to a path relative to found rootDir entry './content/protocols/file2' (*). As a last step compiler will check all remaining + * entries in 'rootDirs', use them to build absolute path out of (*) and try to resolve module from this location. + */ +function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, + state: ModuleResolutionState): Resolved | undefined { + + const resolved = tryLoadModuleUsingPathsIfEligible(extensions, moduleName, loader, state); + if (resolved) return resolved.value; + + if (!isExternalModuleNameRelative(moduleName)) { + return tryLoadModuleUsingBaseUrl(extensions, moduleName, loader, state); + } + else { + return tryLoadModuleUsingRootDirs(extensions, moduleName, containingDirectory, loader, state); } +} - function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) { - const { baseUrl, paths, configFile } = state.compilerOptions; - if (paths && !pathIsRelative(moduleName)) { - if (state.traceEnabled) { - if (baseUrl) { - trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); - } - trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); +function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) { + const { baseUrl, paths, configFile } = state.compilerOptions; + if (paths && !pathIsRelative(moduleName)) { + if (state.traceEnabled) { + if (baseUrl) { + trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); } - const baseDirectory = getPathsBasePath(state.compilerOptions, state.host)!; // Always defined when 'paths' is defined - const pathPatterns = configFile?.configFileSpecs ? configFile.configFileSpecs.pathPatterns ||= tryParsePatterns(paths) : undefined; - return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, pathPatterns, loader, /*onlyRecordFailures*/ false, state); + trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName); } + const baseDirectory = getPathsBasePath(state.compilerOptions, state.host)!; // Always defined when 'paths' is defined + const pathPatterns = configFile?.configFileSpecs ? configFile.configFileSpecs.pathPatterns ||= tryParsePatterns(paths) : undefined; + return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, pathPatterns, loader, /*onlyRecordFailures*/ false, state); } +} - function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, - state: ModuleResolutionState): Resolved | undefined { +function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, + state: ModuleResolutionState): Resolved | undefined { - if (!state.compilerOptions.rootDirs) { - return undefined; + if (!state.compilerOptions.rootDirs) { + return undefined; + } + + if (state.traceEnabled) { + trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName); + } + + const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); + + let matchedRootDir: string | undefined; + let matchedNormalizedPrefix: string | undefined; + for (const rootDir of state.compilerOptions.rootDirs) { + // rootDirs are expected to be absolute + // in case of tsconfig.json this will happen automatically - compiler will expand relative names + // using location of tsconfig.json as base location + let normalizedRoot = normalizePath(rootDir); + if (!endsWith(normalizedRoot, directorySeparator)) { + normalizedRoot += directorySeparator; } + const isLongestMatchingPrefix = + startsWith(candidate, normalizedRoot) && + (matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length); if (state.traceEnabled) { - trace(state.host, Diagnostics.rootDirs_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName); + trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix); } - const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); - - let matchedRootDir: string | undefined; - let matchedNormalizedPrefix: string | undefined; - for (const rootDir of state.compilerOptions.rootDirs) { - // rootDirs are expected to be absolute - // in case of tsconfig.json this will happen automatically - compiler will expand relative names - // using location of tsconfig.json as base location - let normalizedRoot = normalizePath(rootDir); - if (!endsWith(normalizedRoot, directorySeparator)) { - normalizedRoot += directorySeparator; - } - const isLongestMatchingPrefix = - startsWith(candidate, normalizedRoot) && - (matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length); + if (isLongestMatchingPrefix) { + matchedNormalizedPrefix = normalizedRoot; + matchedRootDir = rootDir; + } + } + if (matchedNormalizedPrefix) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix); + } + const suffix = candidate.substr(matchedNormalizedPrefix.length); - if (state.traceEnabled) { - trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix); - } + // first - try to load from a initial location + if (state.traceEnabled) { + trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate); + } + const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state); + if (resolvedFileName) { + return resolvedFileName; + } - if (isLongestMatchingPrefix) { - matchedNormalizedPrefix = normalizedRoot; - matchedRootDir = rootDir; - } + if (state.traceEnabled) { + trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs); } - if (matchedNormalizedPrefix) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Longest_matching_prefix_for_0_is_1, candidate, matchedNormalizedPrefix); + // then try to resolve using remaining entries in rootDirs + for (const rootDir of state.compilerOptions.rootDirs) { + if (rootDir === matchedRootDir) { + // skip the initially matched entry + continue; } - const suffix = candidate.substr(matchedNormalizedPrefix.length); - - // first - try to load from a initial location + const candidate = combinePaths(normalizePath(rootDir), suffix); if (state.traceEnabled) { - trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, matchedNormalizedPrefix, candidate); + trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate); } - const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state); + const baseDirectory = getDirectoryPath(candidate); + const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(baseDirectory, state.host), state); if (resolvedFileName) { return resolvedFileName; } - - if (state.traceEnabled) { - trace(state.host, Diagnostics.Trying_other_entries_in_rootDirs); - } - // then try to resolve using remaining entries in rootDirs - for (const rootDir of state.compilerOptions.rootDirs) { - if (rootDir === matchedRootDir) { - // skip the initially matched entry - continue; - } - const candidate = combinePaths(normalizePath(rootDir), suffix); - if (state.traceEnabled) { - trace(state.host, Diagnostics.Loading_0_from_the_root_dir_1_candidate_location_2, suffix, rootDir, candidate); - } - const baseDirectory = getDirectoryPath(candidate); - const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(baseDirectory, state.host), state); - if (resolvedFileName) { - return resolvedFileName; - } - } - if (state.traceEnabled) { - trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed); - } - } - return undefined; - } - - function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { - const { baseUrl } = state.compilerOptions; - if (!baseUrl) { - return undefined; - } - if (state.traceEnabled) { - trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); } - const candidate = normalizePath(combinePaths(baseUrl, moduleName)); if (state.traceEnabled) { - trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate); + trace(state.host, Diagnostics.Module_resolution_using_rootDirs_has_failed); } - return loader(extensions, candidate, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); } + return undefined; +} - /** - * Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations. - * No way to do this with `require()`: https://github.com/nodejs/node/issues/5963 - * Throws an error if the module can't be resolved. - */ - /* @internal */ - export function resolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string { - const { resolvedModule, failedLookupLocations } = tryResolveJSModuleWorker(moduleName, initialDir, host); - if (!resolvedModule) { - throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations.join(", ")}`); - } - return resolvedModule.resolvedFileName; - } - - /* @internal */ - enum NodeResolutionFeatures { - None = 0, - // resolving `#local` names in your own package.json - Imports = 1 << 1, - // resolving `your-own-name` from your own package.json - SelfName = 1 << 2, - // respecting the `.exports` member of packages' package.json files and its (conditional) mappings of export names - Exports = 1 << 3, - // allowing `*` in the LHS of an export to be followed by more content, eg `"./whatever/*.js"` - // not supported in node 12 - https://github.com/nodejs/Release/issues/690 - ExportsPatternTrailers = 1 << 4, - AllFeatures = Imports | SelfName | Exports | ExportsPatternTrailers, - - Node16Default = Imports | SelfName | Exports | ExportsPatternTrailers, - - NodeNextDefault = AllFeatures, - - EsmMode = 1 << 5, - } - - function node16ModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, - host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, - resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { - return nodeNextModuleNameResolverWorker( - NodeResolutionFeatures.Node16Default, - moduleName, - containingFile, - compilerOptions, - host, - cache, - redirectedReference, - resolutionMode - ); - } - - function nodeNextModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, - host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, - resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { - return nodeNextModuleNameResolverWorker( - NodeResolutionFeatures.NodeNextDefault, - moduleName, - containingFile, - compilerOptions, - host, - cache, - redirectedReference, - resolutionMode - ); - } - - const jsOnlyExtensions = [Extensions.JavaScript]; - const tsExtensions = [Extensions.TypeScript, Extensions.JavaScript]; - const tsPlusJsonExtensions = [...tsExtensions, Extensions.Json]; - const tsconfigExtensions = [Extensions.TSConfig]; - function nodeNextModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { - const containingDirectory = getDirectoryPath(containingFile); - - // es module file or cjs-like input file, use a variant of the legacy cjs resolver that supports the selected modern features - const esmMode = resolutionMode === ModuleKind.ESNext ? NodeResolutionFeatures.EsmMode : 0; - let extensions = compilerOptions.noDtsResolution ? [Extensions.TsOnly, Extensions.JavaScript] : tsExtensions; - if (compilerOptions.resolveJsonModule) { - extensions = [...extensions, Extensions.Json]; - } - return nodeModuleNameResolverWorker(features | esmMode, moduleName, containingDirectory, compilerOptions, host, cache, extensions, redirectedReference); - } - - function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { - return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined); - } - - export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; - /* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures - export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations { - let extensions; - if (lookupConfig) { - extensions = tsconfigExtensions; - } - else if (compilerOptions.noDtsResolution) { - extensions = [Extensions.TsOnly]; - if (compilerOptions.allowJs) extensions.push(Extensions.JavaScript); - if (compilerOptions.resolveJsonModule) extensions.push(Extensions.Json); - } - else { - extensions = compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions; - } - return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, extensions, redirectedReference); - } - - function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); - - const failedLookupLocations: string[] = []; - const affectingLocations: string[] = []; - // conditions are only used by the node16/nodenext resolver - there's no priority order in the list, - //it's essentially a set (priority is determined by object insertion order in the object we look at). - const conditions = features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"]; - if (compilerOptions.noDtsResolution) { - conditions.pop(); - } - - const diagnostics: Diagnostic[] = []; - const state: ModuleResolutionState = { - compilerOptions, - host, - traceEnabled, - failedLookupLocations, - affectingLocations, - packageJsonInfoCache: cache, - features, - conditions, - requestContainingDirectory: containingDirectory, - reportDiagnostic: diag => void diagnostics.push(diag), - }; +function tryLoadModuleUsingBaseUrl(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { + const { baseUrl } = state.compilerOptions; + if (!baseUrl) { + return undefined; + } + if (state.traceEnabled) { + trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); + } + const candidate = normalizePath(combinePaths(baseUrl, moduleName)); + if (state.traceEnabled) { + trace(state.host, Diagnostics.Resolving_module_name_0_relative_to_base_url_1_2, moduleName, baseUrl, candidate); + } + return loader(extensions, candidate, !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); +} - if (traceEnabled && getEmitModuleResolutionKind(compilerOptions) >= ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(compilerOptions) <= ModuleResolutionKind.NodeNext) { - trace(host, Diagnostics.Resolving_in_0_mode_with_conditions_1, features & NodeResolutionFeatures.EsmMode ? "ESM" : "CJS", conditions.map(c => `'${c}'`).join(", ")); - } +/** + * Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations. + * No way to do this with `require()`: https://github.com/nodejs/node/issues/5963 + * Throws an error if the module can't be resolved. + * + * @internal + */ +export function resolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string { + const { resolvedModule, failedLookupLocations } = tryResolveJSModuleWorker(moduleName, initialDir, host); + if (!resolvedModule) { + throw new Error(`Could not resolve JS module '${moduleName}' starting at '${initialDir}'. Looked in: ${failedLookupLocations.join(", ")}`); + } + return resolvedModule.resolvedFileName; +} - const result = forEach(extensions, ext => tryResolve(ext)); - return createResolvedModuleWithFailedLookupLocations( - result?.value?.resolved, - result?.value?.isExternalLibraryImport, - failedLookupLocations, - affectingLocations, - diagnostics, - state.resultFromCache - ); +/** @internal */ +export enum NodeResolutionFeatures { + None = 0, + // resolving `#local` names in your own package.json + Imports = 1 << 1, + // resolving `your-own-name` from your own package.json + SelfName = 1 << 2, + // respecting the `.exports` member of packages' package.json files and its (conditional) mappings of export names + Exports = 1 << 3, + // allowing `*` in the LHS of an export to be followed by more content, eg `"./whatever/*.js"` + // not supported in node 12 - https://github.com/nodejs/Release/issues/690 + ExportsPatternTrailers = 1 << 4, + AllFeatures = Imports | SelfName | Exports | ExportsPatternTrailers, + + Node16Default = Imports | SelfName | Exports | ExportsPatternTrailers, + + NodeNextDefault = AllFeatures, + + EsmMode = 1 << 5, +} - function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> { - const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true); - const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state); - if (resolved) { - return toSearchResult({ resolved, isExternalLibraryImport: pathContainsNodeModules(resolved.path) }); - } +function node16ModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, + host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, + resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { + return nodeNextModuleNameResolverWorker( + NodeResolutionFeatures.Node16Default, + moduleName, + containingFile, + compilerOptions, + host, + cache, + redirectedReference, + resolutionMode + ); +} - if (!isExternalModuleNameRelative(moduleName)) { - let resolved: SearchResult | undefined; - if (features & NodeResolutionFeatures.Imports && startsWith(moduleName, "#")) { - resolved = loadModuleFromImports(extensions, moduleName, containingDirectory, state, cache, redirectedReference); - } - if (!resolved && features & NodeResolutionFeatures.SelfName) { - resolved = loadModuleFromSelfNameReference(extensions, moduleName, containingDirectory, state, cache, redirectedReference); - } - if (!resolved) { - if (traceEnabled) { - trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]); - } - resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); - } - if (!resolved) return undefined; - - let resolvedValue = resolved.value; - if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) { - const path = realPath(resolvedValue.path, host, traceEnabled); - const pathsAreEqual = arePathsEqual(path, resolvedValue.path, host); - const originalPath = pathsAreEqual ? undefined : resolvedValue.path; - // If the path and realpath are differing only in casing prefer path so that we can issue correct errors for casing under forceConsistentCasingInFileNames - resolvedValue = { ...resolvedValue, path: pathsAreEqual ? resolvedValue.path : path, originalPath }; - } - // For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files. - return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } }; - } - else { - const { path: candidate, parts } = normalizePathForCJSResolution(containingDirectory, moduleName); - const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true); - // Treat explicit "node_modules" import as an external library import. - return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, "node_modules") }); - } - } +function nodeNextModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, + host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, + resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { + return nodeNextModuleNameResolverWorker( + NodeResolutionFeatures.NodeNextDefault, + moduleName, + containingFile, + compilerOptions, + host, + cache, + redirectedReference, + resolutionMode + ); +} - } +const jsOnlyExtensions = [Extensions.JavaScript]; +const tsExtensions = [Extensions.TypeScript, Extensions.JavaScript]; +const tsPlusJsonExtensions = [...tsExtensions, Extensions.Json]; +const tsconfigExtensions = [Extensions.TSConfig]; +function nodeNextModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { + const containingDirectory = getDirectoryPath(containingFile); - // If you import from "." inside a containing directory "/foo", the result of `normalizePath` - // would be "/foo", but this loses the information that `foo` is a directory and we intended - // to look inside of it. The Node CommonJS resolution algorithm doesn't call this out - // (https://nodejs.org/api/modules.html#all-together), but it seems that module paths ending - // in `.` are actually normalized to `./` before proceeding with the resolution algorithm. - function normalizePathForCJSResolution(containingDirectory: string, moduleName: string) { - const combined = combinePaths(containingDirectory, moduleName); - const parts = getPathComponents(combined); - const lastPart = lastOrUndefined(parts); - const path = lastPart === "." || lastPart === ".." ? ensureTrailingDirectorySeparator(normalizePath(combined)) : normalizePath(combined); - return { path, parts }; + // es module file or cjs-like input file, use a variant of the legacy cjs resolver that supports the selected modern features + const esmMode = resolutionMode === ModuleKind.ESNext ? NodeResolutionFeatures.EsmMode : 0; + let extensions = compilerOptions.noDtsResolution ? [Extensions.TsOnly, Extensions.JavaScript] : tsExtensions; + if (compilerOptions.resolveJsonModule) { + extensions = [...extensions, Extensions.Json]; } + return nodeModuleNameResolverWorker(features | esmMode, moduleName, containingDirectory, compilerOptions, host, cache, extensions, redirectedReference); +} - function realPath(path: string, host: ModuleResolutionHost, traceEnabled: boolean): string { - if (!host.realpath) { - return path; - } +function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined); +} - const real = normalizePath(host.realpath(path)); - if (traceEnabled) { - trace(host, Diagnostics.Resolving_real_path_for_0_result_1, path, real); - } - Debug.assert(host.fileExists(real), `${path} linked to nonexistent file ${real}`); - return real; +export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; +/** @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures +export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations { + let extensions; + if (lookupConfig) { + extensions = tsconfigExtensions; + } + else if (compilerOptions.noDtsResolution) { + extensions = [Extensions.TsOnly]; + if (compilerOptions.allowJs) extensions.push(Extensions.JavaScript); + if (compilerOptions.resolveJsonModule) extensions.push(Extensions.Json); } + else { + extensions = compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions; + } + return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, extensions, redirectedReference); +} - function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]); +function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + + const failedLookupLocations: string[] = []; + const affectingLocations: string[] = []; + // conditions are only used by the node16/nodenext resolver - there's no priority order in the list, + //it's essentially a set (priority is determined by object insertion order in the object we look at). + const conditions = features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"]; + if (compilerOptions.noDtsResolution) { + conditions.pop(); + } + + const diagnostics: Diagnostic[] = []; + const state: ModuleResolutionState = { + compilerOptions, + host, + traceEnabled, + failedLookupLocations, + affectingLocations, + packageJsonInfoCache: cache, + features, + conditions, + requestContainingDirectory: containingDirectory, + reportDiagnostic: diag => void diagnostics.push(diag), + }; + + if (traceEnabled && getEmitModuleResolutionKind(compilerOptions) >= ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(compilerOptions) <= ModuleResolutionKind.NodeNext) { + trace(host, Diagnostics.Resolving_in_0_mode_with_conditions_1, features & NodeResolutionFeatures.EsmMode ? "ESM" : "CJS", conditions.map(c => `'${c}'`).join(", ")); + } + + const result = forEach(extensions, ext => tryResolve(ext)); + return createResolvedModuleWithFailedLookupLocations( + result?.value?.resolved, + result?.value?.isExternalLibraryImport, + failedLookupLocations, + affectingLocations, + diagnostics, + state.resultFromCache + ); + + function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> { + const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ true); + const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, state); + if (resolved) { + return toSearchResult({ resolved, isExternalLibraryImport: pathContainsNodeModules(resolved.path) }); } - if (!hasTrailingDirectorySeparator(candidate)) { - if (!onlyRecordFailures) { - const parentOfCandidate = getDirectoryPath(candidate); - if (!directoryProbablyExists(parentOfCandidate, state.host)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, parentOfCandidate); - } - onlyRecordFailures = true; + + if (!isExternalModuleNameRelative(moduleName)) { + let resolved: SearchResult | undefined; + if (features & NodeResolutionFeatures.Imports && startsWith(moduleName, "#")) { + resolved = loadModuleFromImports(extensions, moduleName, containingDirectory, state, cache, redirectedReference); + } + if (!resolved && features & NodeResolutionFeatures.SelfName) { + resolved = loadModuleFromSelfNameReference(extensions, moduleName, containingDirectory, state, cache, redirectedReference); + } + if (!resolved) { + if (traceEnabled) { + trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]); } + resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); } - const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state); - if (resolvedFromFile) { - const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile.path) : undefined; - const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined; - return withPackageId(packageInfo, resolvedFromFile); + if (!resolved) return undefined; + + let resolvedValue = resolved.value; + if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) { + const path = realPath(resolvedValue.path, host, traceEnabled); + const pathsAreEqual = arePathsEqual(path, resolvedValue.path, host); + const originalPath = pathsAreEqual ? undefined : resolvedValue.path; + // If the path and realpath are differing only in casing prefer path so that we can issue correct errors for casing under forceConsistentCasingInFileNames + resolvedValue = { ...resolvedValue, path: pathsAreEqual ? resolvedValue.path : path, originalPath }; } + // For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files. + return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } }; } + else { + const { path: candidate, parts } = normalizePathForCJSResolution(containingDirectory, moduleName); + const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true); + // Treat explicit "node_modules" import as an external library import. + return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, "node_modules") }); + } + } + +} + +// If you import from "." inside a containing directory "/foo", the result of `normalizePath` +// would be "/foo", but this loses the information that `foo` is a directory and we intended +// to look inside of it. The Node CommonJS resolution algorithm doesn't call this out +// (https://nodejs.org/api/modules.html#all-together), but it seems that module paths ending +// in `.` are actually normalized to `./` before proceeding with the resolution algorithm. +function normalizePathForCJSResolution(containingDirectory: string, moduleName: string) { + const combined = combinePaths(containingDirectory, moduleName); + const parts = getPathComponents(combined); + const lastPart = lastOrUndefined(parts); + const path = lastPart === "." || lastPart === ".." ? ensureTrailingDirectorySeparator(normalizePath(combined)) : normalizePath(combined); + return { path, parts }; +} + +function realPath(path: string, host: ModuleResolutionHost, traceEnabled: boolean): string { + if (!host.realpath) { + return path; + } + + const real = normalizePath(host.realpath(path)); + if (traceEnabled) { + trace(host, Diagnostics.Resolving_real_path_for_0_result_1, path, real); + } + Debug.assert(host.fileExists(real), `${path} linked to nonexistent file ${real}`); + return real; +} + +function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson: boolean): Resolved | undefined { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Loading_module_as_file_Slash_folder_candidate_module_location_0_target_file_type_1, candidate, Extensions[extensions]); + } + if (!hasTrailingDirectorySeparator(candidate)) { if (!onlyRecordFailures) { - const candidateExists = directoryProbablyExists(candidate, state.host); - if (!candidateExists) { + const parentOfCandidate = getDirectoryPath(candidate); + if (!directoryProbablyExists(parentOfCandidate, state.host)) { if (state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidate); + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, parentOfCandidate); } onlyRecordFailures = true; } } - // esm mode relative imports shouldn't do any directory lookups (either inside `package.json` - // files or implicit `index.js`es). This is a notable depature from cjs norms, where `./foo/pkg` - // could have been redirected by `./foo/pkg/package.json` to an arbitrary location! - if (!(state.features & NodeResolutionFeatures.EsmMode)) { - return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); + const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state); + if (resolvedFromFile) { + const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile.path) : undefined; + const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined; + return withPackageId(packageInfo, resolvedFromFile); + } + } + if (!onlyRecordFailures) { + const candidateExists = directoryProbablyExists(candidate, state.host); + if (!candidateExists) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, candidate); + } + onlyRecordFailures = true; } + } + // esm mode relative imports shouldn't do any directory lookups (either inside `package.json` + // files or implicit `index.js`es). This is a notable depature from cjs norms, where `./foo/pkg` + // could have been redirected by `./foo/pkg/package.json` to an arbitrary location! + if (!(state.features & NodeResolutionFeatures.EsmMode)) { + return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); + } + return undefined; +} + +/** @internal */ +export const nodeModulesPathPart = "/node_modules/"; +/** @internal */ +export function pathContainsNodeModules(path: string): boolean { + return stringContains(path, nodeModulesPathPart); +} + +/** + * This will be called on the successfully resolved path from `loadModuleFromFile`. + * (Not needed for `loadModuleFromNodeModules` as that looks up the `package.json` as part of resolution.) + * + * packageDirectory is the directory of the package itself. + * 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" + * + * @internal + */ +export function parseNodeModuleFromPath(resolved: string): string | undefined { + const path = normalizePath(resolved); + const idx = path.lastIndexOf(nodeModulesPathPart); + if (idx === -1) { return undefined; } - /*@internal*/ - export const nodeModulesPathPart = "/node_modules/"; - /*@internal*/ - export function pathContainsNodeModules(path: string): boolean { - return stringContains(path, nodeModulesPathPart); + const indexAfterNodeModules = idx + nodeModulesPathPart.length; + let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules); + if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { + indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); } + return path.slice(0, indexAfterPackageName); +} - /** - * This will be called on the successfully resolved path from `loadModuleFromFile`. - * (Not needed for `loadModuleFromNodeModules` as that looks up the `package.json` as part of resolution.) - * - * packageDirectory is the directory of the package itself. - * 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" - */ - /* @internal */ - export function parseNodeModuleFromPath(resolved: string): string | undefined { - const path = normalizePath(resolved); - const idx = path.lastIndexOf(nodeModulesPathPart); - if (idx === -1) { - return undefined; - } +function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number { + const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1); + return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex; +} - const indexAfterNodeModules = idx + nodeModulesPathPart.length; - let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules); - if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { - indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); - } - return path.slice(0, indexAfterPackageName); - } +function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined { + return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state)); +} - function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number { - const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1); - return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex; +/** + * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary + * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. + */ +function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + if (extensions === Extensions.Json || extensions === Extensions.TSConfig) { + const extensionLess = tryRemoveExtension(candidate, Extension.Json); + const extension = extensionLess ? candidate.substring(extensionLess.length) : ""; + return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, extension, onlyRecordFailures, state); } - function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined { - return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state)); + // esm mode resolutions don't include automatic extension lookup (without additional flags, at least) + if (!(state.features & NodeResolutionFeatures.EsmMode)) { + // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" + const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, "", onlyRecordFailures, state); + if (resolvedByAddingExtension) { + return resolvedByAddingExtension; + } } - /** - * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary - * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. - */ - function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { - if (extensions === Extensions.Json || extensions === Extensions.TSConfig) { - const extensionLess = tryRemoveExtension(candidate, Extension.Json); - const extension = extensionLess ? candidate.substring(extensionLess.length) : ""; - return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, extension, onlyRecordFailures, state); - } - - // esm mode resolutions don't include automatic extension lookup (without additional flags, at least) - if (!(state.features & NodeResolutionFeatures.EsmMode)) { - // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" - const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, "", onlyRecordFailures, state); - if (resolvedByAddingExtension) { - return resolvedByAddingExtension; - } + return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); +} + +function loadModuleFromFileNoImplicitExtensions(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + // If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one; + // e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" + if (hasJSFileExtension(candidate) || (fileExtensionIs(candidate, Extension.Json) && state.compilerOptions.resolveJsonModule)) { + const extensionless = removeFileExtension(candidate); + const extension = candidate.substring(extensionless.length); + if (state.traceEnabled) { + trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); } + return tryAddingExtensions(extensionless, extensions, extension, onlyRecordFailures, state); + } +} - return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); +function loadJSOrExactTSFileName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + if ((extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) && fileExtensionIsOneOf(candidate, supportedTSExtensionsFlat)) { + const result = tryFile(candidate, onlyRecordFailures, state); + return result !== undefined ? { path: candidate, ext: tryExtractTSExtension(candidate) as Extension } : undefined; } - function loadModuleFromFileNoImplicitExtensions(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { - // If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one; - // e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" - if (hasJSFileExtension(candidate) || (fileExtensionIs(candidate, Extension.Json) && state.compilerOptions.resolveJsonModule)) { - const extensionless = removeFileExtension(candidate); - const extension = candidate.substring(extensionless.length); - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); + return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); +} + +/** Try to return an existing file that adds one of the `extensions` to `candidate`. */ +function tryAddingExtensions(candidate: string, extensions: Extensions, originalExtension: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + if (!onlyRecordFailures) { + // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing + const directory = getDirectoryPath(candidate); + if (directory) { + onlyRecordFailures = !directoryProbablyExists(directory, state.host); + } + } + + switch (extensions) { + case Extensions.DtsOnly: + switch (originalExtension) { + case Extension.Mjs: + case Extension.Mts: + case Extension.Dmts: + return tryExtension(Extension.Dmts); + case Extension.Cjs: + case Extension.Cts: + case Extension.Dcts: + return tryExtension(Extension.Dcts); + case Extension.Json: + candidate += Extension.Json; + return tryExtension(Extension.Dts); + default: return tryExtension(Extension.Dts); + } + case Extensions.TypeScript: + case Extensions.TsOnly: + const useDts = extensions === Extensions.TypeScript; + switch (originalExtension) { + case Extension.Mjs: + case Extension.Mts: + case Extension.Dmts: + return tryExtension(Extension.Mts) || (useDts ? tryExtension(Extension.Dmts) : undefined); + case Extension.Cjs: + case Extension.Cts: + case Extension.Dcts: + return tryExtension(Extension.Cts) || (useDts ? tryExtension(Extension.Dcts) : undefined); + case Extension.Json: + candidate += Extension.Json; + return useDts ? tryExtension(Extension.Dts) : undefined; + default: + return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || (useDts ? tryExtension(Extension.Dts) : undefined); + } + case Extensions.JavaScript: + switch (originalExtension) { + case Extension.Mjs: + case Extension.Mts: + case Extension.Dmts: + return tryExtension(Extension.Mjs); + case Extension.Cjs: + case Extension.Cts: + case Extension.Dcts: + return tryExtension(Extension.Cjs); + case Extension.Json: + return tryExtension(Extension.Json); + default: + return tryExtension(Extension.Js) || tryExtension(Extension.Jsx); } - return tryAddingExtensions(extensionless, extensions, extension, onlyRecordFailures, state); - } + case Extensions.TSConfig: + case Extensions.Json: + return tryExtension(Extension.Json); } - function loadJSOrExactTSFileName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { - if ((extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) && fileExtensionIsOneOf(candidate, supportedTSExtensionsFlat)) { - const result = tryFile(candidate, onlyRecordFailures, state); - return result !== undefined ? { path: candidate, ext: tryExtractTSExtension(candidate) as Extension } : undefined; - } + function tryExtension(ext: Extension): PathAndExtension | undefined { + const path = tryFile(candidate + ext, onlyRecordFailures, state); + return path === undefined ? undefined : { path, ext }; + } +} - return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); +/** Return the file if it exists. */ +function tryFile(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { + if (!state.compilerOptions.moduleSuffixes?.length) { + return tryFileLookup(fileName, onlyRecordFailures, state); } - /** Try to return an existing file that adds one of the `extensions` to `candidate`. */ - function tryAddingExtensions(candidate: string, extensions: Extensions, originalExtension: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { - if (!onlyRecordFailures) { - // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing - const directory = getDirectoryPath(candidate); - if (directory) { - onlyRecordFailures = !directoryProbablyExists(directory, state.host); + const ext = tryGetExtensionFromPath(fileName) ?? ""; + const fileNameNoExtension = ext ? removeExtension(fileName, ext) : fileName; + return forEach(state.compilerOptions.moduleSuffixes, suffix => tryFileLookup(fileNameNoExtension + suffix + ext, onlyRecordFailures, state)); +} + +function tryFileLookup(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { + if (!onlyRecordFailures) { + if (state.host.fileExists(fileName)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); + } + return fileName; + } + else { + if (state.traceEnabled) { + trace(state.host, Diagnostics.File_0_does_not_exist, fileName); } } + } + state.failedLookupLocations.push(fileName); + return undefined; +} - switch (extensions) { - case Extensions.DtsOnly: - switch (originalExtension) { - case Extension.Mjs: - case Extension.Mts: - case Extension.Dmts: - return tryExtension(Extension.Dmts); - case Extension.Cjs: - case Extension.Cts: - case Extension.Dcts: - return tryExtension(Extension.Dcts); - case Extension.Json: - candidate += Extension.Json; - return tryExtension(Extension.Dts); - default: return tryExtension(Extension.Dts); - } - case Extensions.TypeScript: - case Extensions.TsOnly: - const useDts = extensions === Extensions.TypeScript; - switch (originalExtension) { - case Extension.Mjs: - case Extension.Mts: - case Extension.Dmts: - return tryExtension(Extension.Mts) || (useDts ? tryExtension(Extension.Dmts) : undefined); - case Extension.Cjs: - case Extension.Cts: - case Extension.Dcts: - return tryExtension(Extension.Cts) || (useDts ? tryExtension(Extension.Dcts) : undefined); - case Extension.Json: - candidate += Extension.Json; - return useDts ? tryExtension(Extension.Dts) : undefined; - default: - return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || (useDts ? tryExtension(Extension.Dts) : undefined); - } - case Extensions.JavaScript: - switch (originalExtension) { - case Extension.Mjs: - case Extension.Mts: - case Extension.Dmts: - return tryExtension(Extension.Mjs); - case Extension.Cjs: - case Extension.Cts: - case Extension.Dcts: - return tryExtension(Extension.Cjs); - case Extension.Json: - return tryExtension(Extension.Json); - default: - return tryExtension(Extension.Js) || tryExtension(Extension.Jsx); +function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) { + const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined; + const packageJsonContent = packageInfo && packageInfo.contents.packageJsonContent; + const versionPaths = packageInfo && packageInfo.contents.versionPaths; + return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); +} + +/** @internal */ +export function getEntrypointsFromPackageJsonInfo( + packageJsonInfo: PackageJsonInfo, + options: CompilerOptions, + host: ModuleResolutionHost, + cache: ModuleResolutionCache | undefined, + resolveJs?: boolean, +): string[] | false { + if (!resolveJs && packageJsonInfo.contents.resolvedEntrypoints !== undefined) { + // Cached value excludes resolutions to JS files - those could be + // cached separately, but they're used rarely. + return packageJsonInfo.contents.resolvedEntrypoints; + } + + let entrypoints: string[] | undefined; + const extensions = resolveJs ? Extensions.JavaScript : Extensions.TypeScript; + const features = getDefaultNodeResolutionFeatures(options); + const requireState = getTemporaryModuleResolutionState(cache?.getPackageJsonInfoCache(), host, options); + requireState.conditions = ["node", "require", "types"]; + requireState.requestContainingDirectory = packageJsonInfo.packageDirectory; + const requireResolution = loadNodeModuleFromDirectoryWorker( + extensions, + packageJsonInfo.packageDirectory, + /*onlyRecordFailures*/ false, + requireState, + packageJsonInfo.contents.packageJsonContent, + packageJsonInfo.contents.versionPaths); + entrypoints = append(entrypoints, requireResolution?.path); + + if (features & NodeResolutionFeatures.Exports && packageJsonInfo.contents.packageJsonContent.exports) { + for (const conditions of [["node", "import", "types"], ["node", "require", "types"]]) { + const exportState = { ...requireState, failedLookupLocations: [], conditions }; + const exportResolutions = loadEntrypointsFromExportMap( + packageJsonInfo, + packageJsonInfo.contents.packageJsonContent.exports, + exportState, + extensions); + if (exportResolutions) { + for (const resolution of exportResolutions) { + entrypoints = appendIfUnique(entrypoints, resolution.path); } - case Extensions.TSConfig: - case Extensions.Json: - return tryExtension(Extension.Json); + } } + } + + return packageJsonInfo.contents.resolvedEntrypoints = entrypoints || false; +} - function tryExtension(ext: Extension): PathAndExtension | undefined { - const path = tryFile(candidate + ext, onlyRecordFailures, state); - return path === undefined ? undefined : { path, ext }; +function loadEntrypointsFromExportMap( + scope: PackageJsonInfo, + exports: object, + state: ModuleResolutionState, + extensions: Extensions, +): PathAndExtension[] | undefined { + let entrypoints: PathAndExtension[] | undefined; + if (isArray(exports)) { + for (const target of exports) { + loadEntrypointsFromTargetExports(target); } } - - /** Return the file if it exists. */ - function tryFile(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { - if (!state.compilerOptions.moduleSuffixes?.length) { - return tryFileLookup(fileName, onlyRecordFailures, state); + // eslint-disable-next-line no-null/no-null + else if (typeof exports === "object" && exports !== null && allKeysStartWithDot(exports as MapLike)) { + for (const key in exports) { + loadEntrypointsFromTargetExports((exports as MapLike)[key]); } - - const ext = tryGetExtensionFromPath(fileName) ?? ""; - const fileNameNoExtension = ext ? removeExtension(fileName, ext) : fileName; - return forEach(state.compilerOptions.moduleSuffixes, suffix => tryFileLookup(fileNameNoExtension + suffix + ext, onlyRecordFailures, state)); } + else { + loadEntrypointsFromTargetExports(exports); + } + return entrypoints; - function tryFileLookup(fileName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): string | undefined { - if (!onlyRecordFailures) { - if (state.host.fileExists(fileName)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); - } - return fileName; + function loadEntrypointsFromTargetExports(target: unknown): boolean | undefined { + if (typeof target === "string" && startsWith(target, "./") && target.indexOf("*") === -1) { + const partsAfterFirst = getPathComponents(target).slice(2); + if (partsAfterFirst.indexOf("..") >= 0 || partsAfterFirst.indexOf(".") >= 0 || partsAfterFirst.indexOf("node_modules") >= 0) { + return false; } - else { - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_0_does_not_exist, fileName); - } + const resolvedTarget = combinePaths(scope.packageDirectory, target); + const finalPath = getNormalizedAbsolutePath(resolvedTarget, state.host.getCurrentDirectory?.()); + const result = loadJSOrExactTSFileName(extensions, finalPath, /*recordOnlyFailures*/ false, state); + if (result) { + entrypoints = appendIfUnique(entrypoints, result, (a, b) => a.path === b.path); + return true; } } - state.failedLookupLocations.push(fileName); - return undefined; - } - - function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) { - const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined; - const packageJsonContent = packageInfo && packageInfo.contents.packageJsonContent; - const versionPaths = packageInfo && packageInfo.contents.versionPaths; - return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); - } - - /* @internal */ - export function getEntrypointsFromPackageJsonInfo( - packageJsonInfo: PackageJsonInfo, - options: CompilerOptions, - host: ModuleResolutionHost, - cache: ModuleResolutionCache | undefined, - resolveJs?: boolean, - ): string[] | false { - if (!resolveJs && packageJsonInfo.contents.resolvedEntrypoints !== undefined) { - // Cached value excludes resolutions to JS files - those could be - // cached separately, but they're used rarely. - return packageJsonInfo.contents.resolvedEntrypoints; - } - - let entrypoints: string[] | undefined; - const extensions = resolveJs ? Extensions.JavaScript : Extensions.TypeScript; - const features = getDefaultNodeResolutionFeatures(options); - const requireState = getTemporaryModuleResolutionState(cache?.getPackageJsonInfoCache(), host, options); - requireState.conditions = ["node", "require", "types"]; - requireState.requestContainingDirectory = packageJsonInfo.packageDirectory; - const requireResolution = loadNodeModuleFromDirectoryWorker( - extensions, - packageJsonInfo.packageDirectory, - /*onlyRecordFailures*/ false, - requireState, - packageJsonInfo.contents.packageJsonContent, - packageJsonInfo.contents.versionPaths); - entrypoints = append(entrypoints, requireResolution?.path); - - if (features & NodeResolutionFeatures.Exports && packageJsonInfo.contents.packageJsonContent.exports) { - for (const conditions of [["node", "import", "types"], ["node", "require", "types"]]) { - const exportState = { ...requireState, failedLookupLocations: [], conditions }; - const exportResolutions = loadEntrypointsFromExportMap( - packageJsonInfo, - packageJsonInfo.contents.packageJsonContent.exports, - exportState, - extensions); - if (exportResolutions) { - for (const resolution of exportResolutions) { - entrypoints = appendIfUnique(entrypoints, resolution.path); - } + else if (Array.isArray(target)) { + for (const t of target) { + const success = loadEntrypointsFromTargetExports(t); + if (success) { + return true; } } } - - return packageJsonInfo.contents.resolvedEntrypoints = entrypoints || false; - } - - function loadEntrypointsFromExportMap( - scope: PackageJsonInfo, - exports: object, - state: ModuleResolutionState, - extensions: Extensions, - ): PathAndExtension[] | undefined { - let entrypoints: PathAndExtension[] | undefined; - if (isArray(exports)) { - for (const target of exports) { - loadEntrypointsFromTargetExports(target); - } - } // eslint-disable-next-line no-null/no-null - else if (typeof exports === "object" && exports !== null && allKeysStartWithDot(exports as MapLike)) { - for (const key in exports) { - loadEntrypointsFromTargetExports((exports as MapLike)[key]); - } - } - else { - loadEntrypointsFromTargetExports(exports); - } - return entrypoints; - - function loadEntrypointsFromTargetExports(target: unknown): boolean | undefined { - if (typeof target === "string" && startsWith(target, "./") && target.indexOf("*") === -1) { - const partsAfterFirst = getPathComponents(target).slice(2); - if (partsAfterFirst.indexOf("..") >= 0 || partsAfterFirst.indexOf(".") >= 0 || partsAfterFirst.indexOf("node_modules") >= 0) { - return false; - } - const resolvedTarget = combinePaths(scope.packageDirectory, target); - const finalPath = getNormalizedAbsolutePath(resolvedTarget, state.host.getCurrentDirectory?.()); - const result = loadJSOrExactTSFileName(extensions, finalPath, /*recordOnlyFailures*/ false, state); - if (result) { - entrypoints = appendIfUnique(entrypoints, result, (a, b) => a.path === b.path); + else if (typeof target === "object" && target !== null) { + return forEach(getOwnKeys(target as MapLike), key => { + if (key === "default" || contains(state.conditions, key) || isApplicableVersionedTypesKey(state.conditions, key)) { + loadEntrypointsFromTargetExports((target as MapLike)[key]); return true; } - } - else if (Array.isArray(target)) { - for (const t of target) { - const success = loadEntrypointsFromTargetExports(t); - if (success) { - return true; - } - } - } - // eslint-disable-next-line no-null/no-null - else if (typeof target === "object" && target !== null) { - return forEach(getOwnKeys(target as MapLike), key => { - if (key === "default" || contains(state.conditions, key) || isApplicableVersionedTypesKey(state.conditions, key)) { - loadEntrypointsFromTargetExports((target as MapLike)[key]); - return true; - } - }); - } + }); } } +} - /*@internal*/ - export function getTemporaryModuleResolutionState(packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): ModuleResolutionState { - return { - host, - compilerOptions: options, - traceEnabled: isTraceEnabled(options, host), - failedLookupLocations: noopPush, - affectingLocations: noopPush, - packageJsonInfoCache, - features: NodeResolutionFeatures.None, - conditions: emptyArray, - requestContainingDirectory: undefined, - reportDiagnostic: noop - }; - } +/** @internal */ +export function getTemporaryModuleResolutionState(packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): ModuleResolutionState { + return { + host, + compilerOptions: options, + traceEnabled: isTraceEnabled(options, host), + failedLookupLocations: noopPush, + affectingLocations: noopPush, + packageJsonInfoCache, + features: NodeResolutionFeatures.None, + conditions: emptyArray, + requestContainingDirectory: undefined, + reportDiagnostic: noop + }; +} - /*@internal*/ - export interface PackageJsonInfo { - packageDirectory: string; - contents: PackageJsonInfoContents; - } - /*@internal*/ - export interface PackageJsonInfoContents { - packageJsonContent: PackageJsonPathFields; - versionPaths: VersionPaths | undefined; - /** false: resolved to nothing. undefined: not yet resolved */ - resolvedEntrypoints: string[] | false | undefined; - } +/** @internal */ +export interface PackageJsonInfo { + packageDirectory: string; + contents: PackageJsonInfoContents; +} +/** @internal */ +export interface PackageJsonInfoContents { + packageJsonContent: PackageJsonPathFields; + versionPaths: VersionPaths | undefined; + /** false: resolved to nothing. undefined: not yet resolved */ + resolvedEntrypoints: string[] | false | undefined; +} - /** - * A function for locating the package.json scope for a given path - */ - /*@internal*/ - export function getPackageScopeForPath(fileName: string, state: ModuleResolutionState): PackageJsonInfo | undefined { - const parts = getPathComponents(fileName); - parts.pop(); - while (parts.length > 0) { - const pkg = getPackageJsonInfo(getPathFromPathComponents(parts), /*onlyRecordFailures*/ false, state); - if (pkg) { - return pkg; - } - parts.pop(); +/** + * A function for locating the package.json scope for a given path + * + * @internal + */ + export function getPackageScopeForPath(fileName: string, state: ModuleResolutionState): PackageJsonInfo | undefined { + const parts = getPathComponents(fileName); + parts.pop(); + while (parts.length > 0) { + const pkg = getPackageJsonInfo(getPathFromPathComponents(parts), /*onlyRecordFailures*/ false, state); + if (pkg) { + return pkg; } - return undefined; + parts.pop(); } + return undefined; +} - /*@internal*/ - export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { - const { host, traceEnabled } = state; - const packageJsonPath = combinePaths(packageDirectory, "package.json"); - if (onlyRecordFailures) { - state.failedLookupLocations.push(packageJsonPath); - return undefined; - } +/** @internal */ +export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { + const { host, traceEnabled } = state; + const packageJsonPath = combinePaths(packageDirectory, "package.json"); + if (onlyRecordFailures) { + state.failedLookupLocations.push(packageJsonPath); + return undefined; + } - const existing = state.packageJsonInfoCache?.getPackageJsonInfo(packageJsonPath); - if (existing !== undefined) { - if (typeof existing !== "boolean") { - if (traceEnabled) trace(host, Diagnostics.File_0_exists_according_to_earlier_cached_lookups, packageJsonPath); - state.affectingLocations.push(packageJsonPath); - return existing.packageDirectory === packageDirectory ? - existing : - { packageDirectory, contents: existing.contents }; - } - else { - if (existing && traceEnabled) trace(host, Diagnostics.File_0_does_not_exist_according_to_earlier_cached_lookups, packageJsonPath); - state.failedLookupLocations.push(packageJsonPath); - return undefined; - } - } - const directoryExists = directoryProbablyExists(packageDirectory, host); - if (directoryExists && host.fileExists(packageJsonPath)) { - const packageJsonContent = readJson(packageJsonPath, host) as PackageJson; - if (traceEnabled) { - trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); - } - const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); - const result: PackageJsonInfo = { packageDirectory, contents: { packageJsonContent, versionPaths, resolvedEntrypoints: undefined } }; - state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, result); + const existing = state.packageJsonInfoCache?.getPackageJsonInfo(packageJsonPath); + if (existing !== undefined) { + if (typeof existing !== "boolean") { + if (traceEnabled) trace(host, Diagnostics.File_0_exists_according_to_earlier_cached_lookups, packageJsonPath); state.affectingLocations.push(packageJsonPath); - return result; + return existing.packageDirectory === packageDirectory ? + existing : + { packageDirectory, contents: existing.contents }; } else { - if (directoryExists && traceEnabled) { - trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath); - } - state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, directoryExists); - // record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results + if (existing && traceEnabled) trace(host, Diagnostics.File_0_does_not_exist_according_to_earlier_cached_lookups, packageJsonPath); state.failedLookupLocations.push(packageJsonPath); + return undefined; } } - - function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, jsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined { - let packageFile: string | undefined; - if (jsonContent) { - switch (extensions) { - case Extensions.JavaScript: - case Extensions.Json: - case Extensions.TsOnly: - packageFile = readPackageJsonMainField(jsonContent, candidate, state); - break; - case Extensions.TypeScript: - // When resolving typescript modules, try resolving using main field as well - packageFile = readPackageJsonTypesFields(jsonContent, candidate, state) || readPackageJsonMainField(jsonContent, candidate, state); - break; - case Extensions.DtsOnly: - packageFile = readPackageJsonTypesFields(jsonContent, candidate, state); - break; - case Extensions.TSConfig: - packageFile = readPackageJsonTSConfigField(jsonContent, candidate, state); - break; - default: - return Debug.assertNever(extensions); - } + const directoryExists = directoryProbablyExists(packageDirectory, host); + if (directoryExists && host.fileExists(packageJsonPath)) { + const packageJsonContent = readJson(packageJsonPath, host) as PackageJson; + if (traceEnabled) { + trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); } + const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); + const result: PackageJsonInfo = { packageDirectory, contents: { packageJsonContent, versionPaths, resolvedEntrypoints: undefined } }; + state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, result); + state.affectingLocations.push(packageJsonPath); + return result; + } + else { + if (directoryExists && traceEnabled) { + trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath); + } + state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, directoryExists); + // record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results + state.failedLookupLocations.push(packageJsonPath); + } +} - const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { - const fromFile = tryFile(candidate, onlyRecordFailures, state); - if (fromFile) { - const resolved = resolvedIfExtensionMatches(extensions, fromFile); - if (resolved) { - return noPackageId(resolved); - } - if (state.traceEnabled) { - trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile); - } - } - - // Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types" - const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions; - // Don't do package.json lookup recursively, because Node.js' package lookup doesn't. +function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, jsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined { + let packageFile: string | undefined; + if (jsonContent) { + switch (extensions) { + case Extensions.JavaScript: + case Extensions.Json: + case Extensions.TsOnly: + packageFile = readPackageJsonMainField(jsonContent, candidate, state); + break; + case Extensions.TypeScript: + // When resolving typescript modules, try resolving using main field as well + packageFile = readPackageJsonTypesFields(jsonContent, candidate, state) || readPackageJsonMainField(jsonContent, candidate, state); + break; + case Extensions.DtsOnly: + packageFile = readPackageJsonTypesFields(jsonContent, candidate, state); + break; + case Extensions.TSConfig: + packageFile = readPackageJsonTSConfigField(jsonContent, candidate, state); + break; + default: + return Debug.assertNever(extensions); + } + } - // Disable `EsmMode` for the resolution of the package path for cjs-mode packages (so the `main` field can omit extensions) - // (technically it only emits a deprecation warning in esm packages right now, but that's probably - // enough to mean we don't need to support it) - const features = state.features; - if (jsonContent?.type !== "module") { - state.features &= ~NodeResolutionFeatures.EsmMode; + const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { + const fromFile = tryFile(candidate, onlyRecordFailures, state); + if (fromFile) { + const resolved = resolvedIfExtensionMatches(extensions, fromFile); + if (resolved) { + return noPackageId(resolved); } - const result = nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); - state.features = features; - return result; - }; - - const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined; - const onlyRecordFailuresForIndex = onlyRecordFailures || !directoryProbablyExists(candidate, state.host); - const indexPath = combinePaths(candidate, extensions === Extensions.TSConfig ? "tsconfig" : "index"); - - if (versionPaths && (!packageFile || containsPath(candidate, packageFile))) { - const moduleName = getRelativePathFromDirectory(candidate, packageFile || indexPath, /*ignoreCase*/ false); 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, moduleName); - } - const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, /*pathPatterns*/ undefined, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state); - if (result) { - return removeIgnoredPackageId(result.value); + trace(state.host, Diagnostics.File_0_has_an_unsupported_extension_so_skipping_it, fromFile); } } - // It won't have a `packageId` set, because we disabled `considerPackageJson`. - const packageFileResult = packageFile && removeIgnoredPackageId(loader(extensions, packageFile, onlyRecordFailuresForPackageFile!, state)); - if (packageFileResult) return packageFileResult; + // Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types" + const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions; + // Don't do package.json lookup recursively, because Node.js' package lookup doesn't. - // esm mode resolutions don't do package `index` lookups - if (!(state.features & NodeResolutionFeatures.EsmMode)) { - return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state); + // Disable `EsmMode` for the resolution of the package path for cjs-mode packages (so the `main` field can omit extensions) + // (technically it only emits a deprecation warning in esm packages right now, but that's probably + // enough to mean we don't need to support it) + const features = state.features; + if (jsonContent?.type !== "module") { + state.features &= ~NodeResolutionFeatures.EsmMode; } - } + const result = nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); + state.features = features; + return result; + }; - /** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */ - function resolvedIfExtensionMatches(extensions: Extensions, path: string): PathAndExtension | undefined { - const ext = tryGetExtensionFromPath(path); - return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext } : undefined; - } + const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined; + const onlyRecordFailuresForIndex = onlyRecordFailures || !directoryProbablyExists(candidate, state.host); + const indexPath = combinePaths(candidate, extensions === Extensions.TSConfig ? "tsconfig" : "index"); - /** True if `extension` is one of the supported `extensions`. */ - function extensionIsOk(extensions: Extensions, extension: Extension): boolean { - switch (extensions) { - case Extensions.JavaScript: - return extension === Extension.Js || extension === Extension.Jsx || extension === Extension.Mjs || extension === Extension.Cjs; - case Extensions.TSConfig: - case Extensions.Json: - return extension === Extension.Json; - case Extensions.TypeScript: - return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts || extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts; - case Extensions.TsOnly: - return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts; - case Extensions.DtsOnly: - return extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts; + if (versionPaths && (!packageFile || containsPath(candidate, packageFile))) { + const moduleName = getRelativePathFromDirectory(candidate, packageFile || indexPath, /*ignoreCase*/ false); + 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, moduleName); } - } - - /* @internal */ - export function parsePackageName(moduleName: string): { packageName: string, rest: string } { - let idx = moduleName.indexOf(directorySeparator); - if (moduleName[0] === "@") { - idx = moduleName.indexOf(directorySeparator, idx + 1); + const result = tryLoadModuleUsingPaths(extensions, moduleName, candidate, versionPaths.paths, /*pathPatterns*/ undefined, loader, onlyRecordFailuresForPackageFile || onlyRecordFailuresForIndex, state); + if (result) { + return removeIgnoredPackageId(result.value); } - return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) }; } - /* @internal */ - export function allKeysStartWithDot(obj: MapLike) { - return every(getOwnKeys(obj), k => startsWith(k, ".")); + // It won't have a `packageId` set, because we disabled `considerPackageJson`. + const packageFileResult = packageFile && removeIgnoredPackageId(loader(extensions, packageFile, onlyRecordFailuresForPackageFile!, state)); + if (packageFileResult) return packageFileResult; + + // esm mode resolutions don't do package `index` lookups + if (!(state.features & NodeResolutionFeatures.EsmMode)) { + return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state); } +} + +/** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */ +function resolvedIfExtensionMatches(extensions: Extensions, path: string): PathAndExtension | undefined { + const ext = tryGetExtensionFromPath(path); + return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext } : undefined; +} - function noKeyStartsWithDot(obj: MapLike) { - return !some(getOwnKeys(obj), k => startsWith(k, ".")); +/** True if `extension` is one of the supported `extensions`. */ +function extensionIsOk(extensions: Extensions, extension: Extension): boolean { + switch (extensions) { + case Extensions.JavaScript: + return extension === Extension.Js || extension === Extension.Jsx || extension === Extension.Mjs || extension === Extension.Cjs; + case Extensions.TSConfig: + case Extensions.Json: + return extension === Extension.Json; + case Extensions.TypeScript: + return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts || extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts; + case Extensions.TsOnly: + return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts; + case Extensions.DtsOnly: + return extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts; } +} - function loadModuleFromSelfNameReference(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - const directoryPath = getNormalizedAbsolutePath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.()); - const scope = getPackageScopeForPath(directoryPath, state); - if (!scope || !scope.contents.packageJsonContent.exports) { - return undefined; - } - if (typeof scope.contents.packageJsonContent.name !== "string") { - return undefined; - } - const parts = getPathComponents(moduleName); // unrooted paths should have `""` as their 0th entry - const nameParts = getPathComponents(scope.contents.packageJsonContent.name); - if (!every(nameParts, (p, i) => parts[i] === p)) { - return undefined; - } - const trailingParts = parts.slice(nameParts.length); - return loadModuleFromExports(scope, extensions, !length(trailingParts) ? "." : `.${directorySeparator}${trailingParts.join(directorySeparator)}`, state, cache, redirectedReference); +/** @internal */ +export function parsePackageName(moduleName: string): { packageName: string, rest: string } { + let idx = moduleName.indexOf(directorySeparator); + if (moduleName[0] === "@") { + idx = moduleName.indexOf(directorySeparator, idx + 1); } + return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) }; +} - function loadModuleFromExports(scope: PackageJsonInfo, extensions: Extensions, subpath: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - if (!scope.contents.packageJsonContent.exports) { - return undefined; - } +/** @internal */ +export function allKeysStartWithDot(obj: MapLike) { + return every(getOwnKeys(obj), k => startsWith(k, ".")); +} - if (subpath === ".") { - let mainExport; - if (typeof scope.contents.packageJsonContent.exports === "string" || Array.isArray(scope.contents.packageJsonContent.exports) || (typeof scope.contents.packageJsonContent.exports === "object" && noKeyStartsWithDot(scope.contents.packageJsonContent.exports as MapLike))) { - mainExport = scope.contents.packageJsonContent.exports; - } - else if (hasProperty(scope.contents.packageJsonContent.exports as MapLike, ".")) { - mainExport = (scope.contents.packageJsonContent.exports as MapLike)["."]; - } - if (mainExport) { - const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, subpath, scope, /*isImports*/ false); - return loadModuleFromTargetImportOrExport(mainExport, "", /*pattern*/ false, "."); - } - } - else if (allKeysStartWithDot(scope.contents.packageJsonContent.exports as MapLike)) { - if (typeof scope.contents.packageJsonContent.exports !== "object") { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); - } - return toSearchResult(/*value*/ undefined); - } - const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, subpath, scope.contents.packageJsonContent.exports, scope, /*isImports*/ false); - if (result) { - return result; - } - } +function noKeyStartsWithDot(obj: MapLike) { + return !some(getOwnKeys(obj), k => startsWith(k, ".")); +} - if (state.traceEnabled) { - trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); - } - return toSearchResult(/*value*/ undefined); +function loadModuleFromSelfNameReference(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + const directoryPath = getNormalizedAbsolutePath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.()); + const scope = getPackageScopeForPath(directoryPath, state); + if (!scope || !scope.contents.packageJsonContent.exports) { + return undefined; } + if (typeof scope.contents.packageJsonContent.name !== "string") { + return undefined; + } + const parts = getPathComponents(moduleName); // unrooted paths should have `""` as their 0th entry + const nameParts = getPathComponents(scope.contents.packageJsonContent.name); + if (!every(nameParts, (p, i) => parts[i] === p)) { + return undefined; + } + const trailingParts = parts.slice(nameParts.length); + return loadModuleFromExports(scope, extensions, !length(trailingParts) ? "." : `.${directorySeparator}${trailingParts.join(directorySeparator)}`, state, cache, redirectedReference); +} - function loadModuleFromImports(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - if (moduleName === "#" || startsWith(moduleName, "#/")) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Invalid_import_specifier_0_has_no_possible_resolutions, moduleName); - } - return toSearchResult(/*value*/ undefined); +function loadModuleFromExports(scope: PackageJsonInfo, extensions: Extensions, subpath: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + if (!scope.contents.packageJsonContent.exports) { + return undefined; + } + + if (subpath === ".") { + let mainExport; + if (typeof scope.contents.packageJsonContent.exports === "string" || Array.isArray(scope.contents.packageJsonContent.exports) || (typeof scope.contents.packageJsonContent.exports === "object" && noKeyStartsWithDot(scope.contents.packageJsonContent.exports as MapLike))) { + mainExport = scope.contents.packageJsonContent.exports; } - const directoryPath = getNormalizedAbsolutePath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.()); - const scope = getPackageScopeForPath(directoryPath, state); - if (!scope) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_has_no_containing_package_json_scope_Imports_will_not_resolve, directoryPath); - } - return toSearchResult(/*value*/ undefined); + else if (hasProperty(scope.contents.packageJsonContent.exports as MapLike, ".")) { + mainExport = (scope.contents.packageJsonContent.exports as MapLike)["."]; } - if (!scope.contents.packageJsonContent.imports) { + if (mainExport) { + const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, subpath, scope, /*isImports*/ false); + return loadModuleFromTargetImportOrExport(mainExport, "", /*pattern*/ false, "."); + } + } + else if (allKeysStartWithDot(scope.contents.packageJsonContent.exports as MapLike)) { + if (typeof scope.contents.packageJsonContent.exports !== "object") { if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_no_imports_defined, scope.packageDirectory); + trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); } return toSearchResult(/*value*/ undefined); } - - const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, moduleName, scope.contents.packageJsonContent.imports, scope, /*isImports*/ true); + const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, subpath, scope.contents.packageJsonContent.exports, scope, /*isImports*/ false); if (result) { return result; } + } + if (state.traceEnabled) { + trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); + } + return toSearchResult(/*value*/ undefined); +} + +function loadModuleFromImports(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + if (moduleName === "#" || startsWith(moduleName, "#/")) { if (state.traceEnabled) { - trace(state.host, Diagnostics.Import_specifier_0_does_not_exist_in_package_json_scope_at_path_1, moduleName, scope.packageDirectory); + trace(state.host, Diagnostics.Invalid_import_specifier_0_has_no_possible_resolutions, moduleName); } return toSearchResult(/*value*/ undefined); } - - /** - * @internal - * From https://github.com/nodejs/node/blob/8f39f51cbbd3b2de14b9ee896e26421cc5b20121/lib/internal/modules/esm/resolve.js#L722 - - * "longest" has some nuance as to what "longest" means in the presence of pattern trailers - */ - export function comparePatternKeys(a: string, b: string) { - const aPatternIndex = a.indexOf("*"); - const bPatternIndex = b.indexOf("*"); - const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1; - const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1; - if (baseLenA > baseLenB) return -1; - if (baseLenB > baseLenA) return 1; - if (aPatternIndex === -1) return 1; - if (bPatternIndex === -1) return -1; - if (a.length > b.length) return -1; - if (b.length > a.length) return 1; - return 0; - } - - function loadModuleFromImportsOrExports(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, lookupTable: object, scope: PackageJsonInfo, isImports: boolean): SearchResult | undefined { - const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, moduleName, scope, isImports); - - if (!endsWith(moduleName, directorySeparator) && moduleName.indexOf("*") === -1 && hasProperty(lookupTable, moduleName)) { - const target = (lookupTable as {[idx: string]: unknown})[moduleName]; - return loadModuleFromTargetImportOrExport(target, /*subpath*/ "", /*pattern*/ false, moduleName); - } - const expandingKeys = sort(filter(getOwnKeys(lookupTable as MapLike), k => k.indexOf("*") !== -1 || endsWith(k, "/")), comparePatternKeys); - for (const potentialTarget of expandingKeys) { - if (state.features & NodeResolutionFeatures.ExportsPatternTrailers && matchesPatternWithTrailer(potentialTarget, moduleName)) { - const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; - const starPos = potentialTarget.indexOf("*"); - const subpath = moduleName.substring(potentialTarget.substring(0, starPos).length, moduleName.length - (potentialTarget.length - 1 - starPos)); - return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true, potentialTarget); - } - else if (endsWith(potentialTarget, "*") && startsWith(moduleName, potentialTarget.substring(0, potentialTarget.length - 1))) { - const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; - const subpath = moduleName.substring(potentialTarget.length - 1); - return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true, potentialTarget); - } - else if (startsWith(moduleName, potentialTarget)) { - const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; - const subpath = moduleName.substring(potentialTarget.length); - return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ false, potentialTarget); - } + const directoryPath = getNormalizedAbsolutePath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.()); + const scope = getPackageScopeForPath(directoryPath, state); + if (!scope) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Directory_0_has_no_containing_package_json_scope_Imports_will_not_resolve, directoryPath); } + return toSearchResult(/*value*/ undefined); + } + if (!scope.contents.packageJsonContent.imports) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_no_imports_defined, scope.packageDirectory); + } + return toSearchResult(/*value*/ undefined); + } - function matchesPatternWithTrailer(target: string, name: string) { - if (endsWith(target, "*")) return false; // handled by next case in loop - const starPos = target.indexOf("*"); - if (starPos === -1) return false; // handled by last case in loop - return startsWith(name, target.substring(0, starPos)) && endsWith(name, target.substring(starPos + 1)); + const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, moduleName, scope.contents.packageJsonContent.imports, scope, /*isImports*/ true); + if (result) { + return result; + } + + if (state.traceEnabled) { + trace(state.host, Diagnostics.Import_specifier_0_does_not_exist_in_package_json_scope_at_path_1, moduleName, scope.packageDirectory); + } + return toSearchResult(/*value*/ undefined); +} + +/** + * @internal + * From https://github.com/nodejs/node/blob/8f39f51cbbd3b2de14b9ee896e26421cc5b20121/lib/internal/modules/esm/resolve.js#L722 - + * "longest" has some nuance as to what "longest" means in the presence of pattern trailers + */ +export function comparePatternKeys(a: string, b: string) { + const aPatternIndex = a.indexOf("*"); + const bPatternIndex = b.indexOf("*"); + const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1; + const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1; + if (baseLenA > baseLenB) return -1; + if (baseLenB > baseLenA) return 1; + if (aPatternIndex === -1) return 1; + if (bPatternIndex === -1) return -1; + if (a.length > b.length) return -1; + if (b.length > a.length) return 1; + return 0; +} + +function loadModuleFromImportsOrExports(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, lookupTable: object, scope: PackageJsonInfo, isImports: boolean): SearchResult | undefined { + const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, moduleName, scope, isImports); + + if (!endsWith(moduleName, directorySeparator) && moduleName.indexOf("*") === -1 && hasProperty(lookupTable, moduleName)) { + const target = (lookupTable as {[idx: string]: unknown})[moduleName]; + return loadModuleFromTargetImportOrExport(target, /*subpath*/ "", /*pattern*/ false, moduleName); + } + const expandingKeys = sort(filter(getOwnKeys(lookupTable as MapLike), k => k.indexOf("*") !== -1 || endsWith(k, "/")), comparePatternKeys); + for (const potentialTarget of expandingKeys) { + if (state.features & NodeResolutionFeatures.ExportsPatternTrailers && matchesPatternWithTrailer(potentialTarget, moduleName)) { + const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; + const starPos = potentialTarget.indexOf("*"); + const subpath = moduleName.substring(potentialTarget.substring(0, starPos).length, moduleName.length - (potentialTarget.length - 1 - starPos)); + return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true, potentialTarget); + } + else if (endsWith(potentialTarget, "*") && startsWith(moduleName, potentialTarget.substring(0, potentialTarget.length - 1))) { + const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; + const subpath = moduleName.substring(potentialTarget.length - 1); + return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true, potentialTarget); + } + else if (startsWith(moduleName, potentialTarget)) { + const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; + const subpath = moduleName.substring(potentialTarget.length); + return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ false, potentialTarget); } } - /** - * Gets the self-recursive function specialized to retrieving the targeted import/export element for the given resolution configuration - */ - function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, scope: PackageJsonInfo, isImports: boolean) { - return loadModuleFromTargetImportOrExport; - function loadModuleFromTargetImportOrExport(target: unknown, subpath: string, pattern: boolean, key: string): SearchResult | undefined { - if (typeof target === "string") { - if (!pattern && subpath.length > 0 && !endsWith(target, "/")) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); - } - return toSearchResult(/*value*/ undefined); - } - if (!startsWith(target, "./")) { - if (isImports && !startsWith(target, "../") && !startsWith(target, "/") && !isRootedDiskPath(target)) { - const combinedLookup = pattern ? target.replace(/\*/g, subpath) : target + subpath; - traceIfEnabled(state, Diagnostics.Using_0_subpath_1_with_target_2, "imports", key, combinedLookup); - traceIfEnabled(state, Diagnostics.Resolving_module_0_from_1, combinedLookup, scope.packageDirectory + "/"); - const result = nodeModuleNameResolverWorker(state.features, combinedLookup, scope.packageDirectory + "/", state.compilerOptions, state.host, cache, [extensions], redirectedReference); - return toSearchResult(result.resolvedModule ? { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId, originalPath: result.resolvedModule.originalPath } : undefined); - } - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); - } - return toSearchResult(/*value*/ undefined); + function matchesPatternWithTrailer(target: string, name: string) { + if (endsWith(target, "*")) return false; // handled by next case in loop + const starPos = target.indexOf("*"); + if (starPos === -1) return false; // handled by last case in loop + return startsWith(name, target.substring(0, starPos)) && endsWith(name, target.substring(starPos + 1)); + } +} + +/** + * Gets the self-recursive function specialized to retrieving the targeted import/export element for the given resolution configuration + */ +function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, scope: PackageJsonInfo, isImports: boolean) { + return loadModuleFromTargetImportOrExport; + function loadModuleFromTargetImportOrExport(target: unknown, subpath: string, pattern: boolean, key: string): SearchResult | undefined { + if (typeof target === "string") { + if (!pattern && subpath.length > 0 && !endsWith(target, "/")) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } - const parts = pathIsRelative(target) ? getPathComponents(target).slice(1) : getPathComponents(target); - const partsAfterFirst = parts.slice(1); - if (partsAfterFirst.indexOf("..") >= 0 || partsAfterFirst.indexOf(".") >= 0 || partsAfterFirst.indexOf("node_modules") >= 0) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); - } - return toSearchResult(/*value*/ undefined); + return toSearchResult(/*value*/ undefined); + } + if (!startsWith(target, "./")) { + if (isImports && !startsWith(target, "../") && !startsWith(target, "/") && !isRootedDiskPath(target)) { + const combinedLookup = pattern ? target.replace(/\*/g, subpath) : target + subpath; + traceIfEnabled(state, Diagnostics.Using_0_subpath_1_with_target_2, "imports", key, combinedLookup); + traceIfEnabled(state, Diagnostics.Resolving_module_0_from_1, combinedLookup, scope.packageDirectory + "/"); + const result = nodeModuleNameResolverWorker(state.features, combinedLookup, scope.packageDirectory + "/", state.compilerOptions, state.host, cache, [extensions], redirectedReference); + return toSearchResult(result.resolvedModule ? { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId, originalPath: result.resolvedModule.originalPath } : undefined); } - const resolvedTarget = combinePaths(scope.packageDirectory, target); - // TODO: Assert that `resolvedTarget` is actually within the package directory? That's what the spec says.... but I'm not sure we need - // to be in the business of validating everyone's import and export map correctness. - const subpathParts = getPathComponents(subpath); - if (subpathParts.indexOf("..") >= 0 || subpathParts.indexOf(".") >= 0 || subpathParts.indexOf("node_modules") >= 0) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); - } - return toSearchResult(/*value*/ undefined); + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } - + return toSearchResult(/*value*/ undefined); + } + const parts = pathIsRelative(target) ? getPathComponents(target).slice(1) : getPathComponents(target); + const partsAfterFirst = parts.slice(1); + if (partsAfterFirst.indexOf("..") >= 0 || partsAfterFirst.indexOf(".") >= 0 || partsAfterFirst.indexOf("node_modules") >= 0) { if (state.traceEnabled) { - trace(state.host, - Diagnostics.Using_0_subpath_1_with_target_2, - isImports ? "imports" : "exports", - key, - pattern ? target.replace(/\*/g, subpath) : target + subpath); + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } - const finalPath = toAbsolutePath(pattern ? resolvedTarget.replace(/\*/g, subpath) : resolvedTarget + subpath); - const inputLink = tryLoadInputFileForPath(finalPath, subpath, combinePaths(scope.packageDirectory, "package.json"), isImports); - if (inputLink) return inputLink; - return toSearchResult(withPackageId(scope, loadJSOrExactTSFileName(extensions, finalPath, /*onlyRecordFailures*/ false, state))); + return toSearchResult(/*value*/ undefined); } - else if (typeof target === "object" && target !== null) { // eslint-disable-line no-null/no-null - if (!Array.isArray(target)) { - for (const condition of getOwnKeys(target as MapLike)) { - if (condition === "default" || state.conditions.indexOf(condition) >= 0 || isApplicableVersionedTypesKey(state.conditions, condition)) { - traceIfEnabled(state, Diagnostics.Matched_0_condition_1, isImports ? "imports" : "exports", condition); - const subTarget = (target as MapLike)[condition]; - const result = loadModuleFromTargetImportOrExport(subTarget, subpath, pattern, key); - if (result) { - return result; - } - } - else { - traceIfEnabled(state, Diagnostics.Saw_non_matching_condition_0, condition); - } - } - return undefined; + const resolvedTarget = combinePaths(scope.packageDirectory, target); + // TODO: Assert that `resolvedTarget` is actually within the package directory? That's what the spec says.... but I'm not sure we need + // to be in the business of validating everyone's import and export map correctness. + const subpathParts = getPathComponents(subpath); + if (subpathParts.indexOf("..") >= 0 || subpathParts.indexOf(".") >= 0 || subpathParts.indexOf("node_modules") >= 0) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); } - else { - if (!length(target)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); - } - return toSearchResult(/*value*/ undefined); - } - for (const elem of target) { - const result = loadModuleFromTargetImportOrExport(elem, subpath, pattern, key); + return toSearchResult(/*value*/ undefined); + } + + if (state.traceEnabled) { + trace(state.host, + Diagnostics.Using_0_subpath_1_with_target_2, + isImports ? "imports" : "exports", + key, + pattern ? target.replace(/\*/g, subpath) : target + subpath); + } + const finalPath = toAbsolutePath(pattern ? resolvedTarget.replace(/\*/g, subpath) : resolvedTarget + subpath); + const inputLink = tryLoadInputFileForPath(finalPath, subpath, combinePaths(scope.packageDirectory, "package.json"), isImports); + if (inputLink) return inputLink; + return toSearchResult(withPackageId(scope, loadJSOrExactTSFileName(extensions, finalPath, /*onlyRecordFailures*/ false, state))); + } + else if (typeof target === "object" && target !== null) { // eslint-disable-line no-null/no-null + if (!Array.isArray(target)) { + for (const condition of getOwnKeys(target as MapLike)) { + if (condition === "default" || state.conditions.indexOf(condition) >= 0 || isApplicableVersionedTypesKey(state.conditions, condition)) { + traceIfEnabled(state, Diagnostics.Matched_0_condition_1, isImports ? "imports" : "exports", condition); + const subTarget = (target as MapLike)[condition]; + const result = loadModuleFromTargetImportOrExport(subTarget, subpath, pattern, key); if (result) { return result; } } + else { + traceIfEnabled(state, Diagnostics.Saw_non_matching_condition_0, condition); + } } + return undefined; } - else if (target === null) { // eslint-disable-line no-null/no-null - if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_explicitly_maps_specifier_1_to_null, scope.packageDirectory, moduleName); + else { + if (!length(target)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + } + return toSearchResult(/*value*/ undefined); + } + for (const elem of target) { + const result = loadModuleFromTargetImportOrExport(elem, subpath, pattern, key); + if (result) { + return result; + } } - return toSearchResult(/*value*/ undefined); } + } + else if (target === null) { // eslint-disable-line no-null/no-null if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + trace(state.host, Diagnostics.package_json_scope_0_explicitly_maps_specifier_1_to_null, scope.packageDirectory, moduleName); } return toSearchResult(/*value*/ undefined); + } + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + } + return toSearchResult(/*value*/ undefined); - function toAbsolutePath(path: string): string; - function toAbsolutePath(path: string | undefined): string | undefined; - function toAbsolutePath(path: string | undefined): string | undefined { - if (path === undefined) return path; - return getNormalizedAbsolutePath(path, state.host.getCurrentDirectory?.()); - } + function toAbsolutePath(path: string): string; + function toAbsolutePath(path: string | undefined): string | undefined; + function toAbsolutePath(path: string | undefined): string | undefined { + if (path === undefined) return path; + return getNormalizedAbsolutePath(path, state.host.getCurrentDirectory?.()); + } - function combineDirectoryPath(root: string, dir: string) { - return ensureTrailingDirectorySeparator(combinePaths(root, dir)); - } + function combineDirectoryPath(root: string, dir: string) { + return ensureTrailingDirectorySeparator(combinePaths(root, dir)); + } - function useCaseSensitiveFileNames() { - return !state.host.useCaseSensitiveFileNames ? true : - typeof state.host.useCaseSensitiveFileNames === "boolean" ? state.host.useCaseSensitiveFileNames : - state.host.useCaseSensitiveFileNames(); - } + function useCaseSensitiveFileNames() { + return !state.host.useCaseSensitiveFileNames ? true : + typeof state.host.useCaseSensitiveFileNames === "boolean" ? state.host.useCaseSensitiveFileNames : + state.host.useCaseSensitiveFileNames(); + } - function tryLoadInputFileForPath(finalPath: string, entry: string, packagePath: string, isImports: boolean) { - // Replace any references to outputs for files in the program with the input files to support package self-names used with outDir - // PROBLEM: We don't know how to calculate the output paths yet, because the "common source directory" we use as the base of the file structure - // we reproduce into the output directory is based on the set of input files, which we're still in the process of traversing and resolving! - // _Given that_, we have to guess what the base of the output directory is (obviously the user wrote the export map, so has some idea what it is!). - // We are going to probe _so many_ possible paths. We limit where we'll do this to try to reduce the possibilities of false positive lookups. - if ((extensions === Extensions.TypeScript || extensions === Extensions.JavaScript || extensions === Extensions.Json) - && (state.compilerOptions.declarationDir || state.compilerOptions.outDir) - && finalPath.indexOf("/node_modules/") === -1 - && (state.compilerOptions.configFile ? containsPath(scope.packageDirectory, toAbsolutePath(state.compilerOptions.configFile.fileName), !useCaseSensitiveFileNames()) : true) - ) { - // So that all means we'll only try these guesses for files outside `node_modules` in a directory where the `package.json` and `tsconfig.json` are siblings. - // Even with all that, we still don't know if the root of the output file structure will be (relative to the package file) - // `.`, `./src` or any other deeper directory structure. (If project references are used, it's definitely `.` by fiat, so that should be pretty common.) - - const getCanonicalFileName = hostGetCanonicalFileName({ useCaseSensitiveFileNames }); - const commonSourceDirGuesses: string[] = []; - // A `rootDir` compiler option strongly indicates the root location - // A `composite` project is using project references and has it's common src dir set to `.`, so it shouldn't need to check any other locations - if (state.compilerOptions.rootDir || (state.compilerOptions.composite && state.compilerOptions.configFilePath)) { - const commonDir = toAbsolutePath(getCommonSourceDirectory(state.compilerOptions, () => [], state.host.getCurrentDirectory?.() || "", getCanonicalFileName)); - commonSourceDirGuesses.push(commonDir); - } - else if (state.requestContainingDirectory) { - // However without either of those set we're in the dark. Let's say you have - // - // ./tools/index.ts - // ./src/index.ts - // ./dist/index.js - // ./package.json <-- references ./dist/index.js - // ./tsconfig.json <-- loads ./src/index.ts - // - // How do we know `./src` is the common src dir, and not `./tools`, given only the `./dist` out dir and `./dist/index.js` filename? - // Answer: We... don't. We know we're looking for an `index.ts` input file, but we have _no clue_ which subfolder it's supposed to be loaded from - // without more context. - // But we do have more context! Just a tiny bit more! We're resolving an import _for some other input file_! And that input file, too - // must be inside the common source directory! So we propagate that tidbit of info all the way to here via state.requestContainingDirectory - - const requestingFile = toAbsolutePath(combinePaths(state.requestContainingDirectory, "index.ts")); - // And we can try every folder above the common folder for the request folder and the config/package base directory - // This technically can be wrong - we may load ./src/index.ts when ./src/sub/index.ts was right because we don't - // know if only `./src/sub` files were loaded by the program; but this has the best chance to be right of just about anything - // else we have. And, given that we're about to load `./src/index.ts` because we choose it as likely correct, there will then - // be a file outside of `./src/sub` in the program (the file we resolved to), making us de-facto right. So this fallback lookup - // logic may influence what files are pulled in by self-names, which in turn influences the output path shape, but it's all - // internally consistent so the paths should be stable so long as we prefer the "most general" (meaning: top-most-level directory) possible results first. - const commonDir = toAbsolutePath(getCommonSourceDirectory(state.compilerOptions, () => [requestingFile, toAbsolutePath(packagePath)], state.host.getCurrentDirectory?.() || "", getCanonicalFileName)); - commonSourceDirGuesses.push(commonDir); - - let fragment = ensureTrailingDirectorySeparator(commonDir); - while (fragment && fragment.length > 1) { - const parts = getPathComponents(fragment); - parts.pop(); // remove a directory - const commonDir = getPathFromPathComponents(parts); - commonSourceDirGuesses.unshift(commonDir); - fragment = ensureTrailingDirectorySeparator(commonDir); - } - } - if (commonSourceDirGuesses.length > 1) { - state.reportDiagnostic(createCompilerDiagnostic( - isImports - ? Diagnostics.The_project_root_is_ambiguous_but_is_required_to_resolve_import_map_entry_0_in_file_1_Supply_the_rootDir_compiler_option_to_disambiguate - : Diagnostics.The_project_root_is_ambiguous_but_is_required_to_resolve_export_map_entry_0_in_file_1_Supply_the_rootDir_compiler_option_to_disambiguate, - entry === "" ? "." : entry, // replace empty string with `.` - the reverse of the operation done when entries are built - so main entrypoint errors don't look weird - packagePath - )); + function tryLoadInputFileForPath(finalPath: string, entry: string, packagePath: string, isImports: boolean) { + // Replace any references to outputs for files in the program with the input files to support package self-names used with outDir + // PROBLEM: We don't know how to calculate the output paths yet, because the "common source directory" we use as the base of the file structure + // we reproduce into the output directory is based on the set of input files, which we're still in the process of traversing and resolving! + // _Given that_, we have to guess what the base of the output directory is (obviously the user wrote the export map, so has some idea what it is!). + // We are going to probe _so many_ possible paths. We limit where we'll do this to try to reduce the possibilities of false positive lookups. + if ((extensions === Extensions.TypeScript || extensions === Extensions.JavaScript || extensions === Extensions.Json) + && (state.compilerOptions.declarationDir || state.compilerOptions.outDir) + && finalPath.indexOf("/node_modules/") === -1 + && (state.compilerOptions.configFile ? containsPath(scope.packageDirectory, toAbsolutePath(state.compilerOptions.configFile.fileName), !useCaseSensitiveFileNames()) : true) + ) { + // So that all means we'll only try these guesses for files outside `node_modules` in a directory where the `package.json` and `tsconfig.json` are siblings. + // Even with all that, we still don't know if the root of the output file structure will be (relative to the package file) + // `.`, `./src` or any other deeper directory structure. (If project references are used, it's definitely `.` by fiat, so that should be pretty common.) + + const getCanonicalFileName = hostGetCanonicalFileName({ useCaseSensitiveFileNames }); + const commonSourceDirGuesses: string[] = []; + // A `rootDir` compiler option strongly indicates the root location + // A `composite` project is using project references and has it's common src dir set to `.`, so it shouldn't need to check any other locations + if (state.compilerOptions.rootDir || (state.compilerOptions.composite && state.compilerOptions.configFilePath)) { + const commonDir = toAbsolutePath(getCommonSourceDirectory(state.compilerOptions, () => [], state.host.getCurrentDirectory?.() || "", getCanonicalFileName)); + commonSourceDirGuesses.push(commonDir); + } + else if (state.requestContainingDirectory) { + // However without either of those set we're in the dark. Let's say you have + // + // ./tools/index.ts + // ./src/index.ts + // ./dist/index.js + // ./package.json <-- references ./dist/index.js + // ./tsconfig.json <-- loads ./src/index.ts + // + // How do we know `./src` is the common src dir, and not `./tools`, given only the `./dist` out dir and `./dist/index.js` filename? + // Answer: We... don't. We know we're looking for an `index.ts` input file, but we have _no clue_ which subfolder it's supposed to be loaded from + // without more context. + // But we do have more context! Just a tiny bit more! We're resolving an import _for some other input file_! And that input file, too + // must be inside the common source directory! So we propagate that tidbit of info all the way to here via state.requestContainingDirectory + + const requestingFile = toAbsolutePath(combinePaths(state.requestContainingDirectory, "index.ts")); + // And we can try every folder above the common folder for the request folder and the config/package base directory + // This technically can be wrong - we may load ./src/index.ts when ./src/sub/index.ts was right because we don't + // know if only `./src/sub` files were loaded by the program; but this has the best chance to be right of just about anything + // else we have. And, given that we're about to load `./src/index.ts` because we choose it as likely correct, there will then + // be a file outside of `./src/sub` in the program (the file we resolved to), making us de-facto right. So this fallback lookup + // logic may influence what files are pulled in by self-names, which in turn influences the output path shape, but it's all + // internally consistent so the paths should be stable so long as we prefer the "most general" (meaning: top-most-level directory) possible results first. + const commonDir = toAbsolutePath(getCommonSourceDirectory(state.compilerOptions, () => [requestingFile, toAbsolutePath(packagePath)], state.host.getCurrentDirectory?.() || "", getCanonicalFileName)); + commonSourceDirGuesses.push(commonDir); + + let fragment = ensureTrailingDirectorySeparator(commonDir); + while (fragment && fragment.length > 1) { + const parts = getPathComponents(fragment); + parts.pop(); // remove a directory + const commonDir = getPathFromPathComponents(parts); + commonSourceDirGuesses.unshift(commonDir); + fragment = ensureTrailingDirectorySeparator(commonDir); } - for (const commonSourceDirGuess of commonSourceDirGuesses) { - const candidateDirectories = getOutputDirectoriesForBaseDirectory(commonSourceDirGuess); - for (const candidateDir of candidateDirectories) { - if (containsPath(candidateDir, finalPath, !useCaseSensitiveFileNames())) { - // The matched export is looking up something in either the out declaration or js dir, now map the written path back into the source dir and source extension - const pathFragment = finalPath.slice(candidateDir.length + 1); // +1 to also remove directory seperator - const possibleInputBase = combinePaths(commonSourceDirGuess, pathFragment); - const jsAndDtsExtensions = [Extension.Mjs, Extension.Cjs, Extension.Js, Extension.Json, Extension.Dmts, Extension.Dcts, Extension.Dts]; - for (const ext of jsAndDtsExtensions) { - if (fileExtensionIs(possibleInputBase, ext)) { - const inputExts = getPossibleOriginalInputExtensionForExtension(possibleInputBase); - for (const possibleExt of inputExts) { - const possibleInputWithInputExtension = changeAnyExtension(possibleInputBase, possibleExt, ext, !useCaseSensitiveFileNames()); - if ((extensions === Extensions.TypeScript && hasJSFileExtension(possibleInputWithInputExtension)) || - (extensions === Extensions.JavaScript && hasTSFileExtension(possibleInputWithInputExtension))) { - continue; - } - if (state.host.fileExists(possibleInputWithInputExtension)) { - return toSearchResult(withPackageId(scope, loadJSOrExactTSFileName(extensions, possibleInputWithInputExtension, /*onlyRecordFailures*/ false, state))); - } + } + if (commonSourceDirGuesses.length > 1) { + state.reportDiagnostic(createCompilerDiagnostic( + isImports + ? Diagnostics.The_project_root_is_ambiguous_but_is_required_to_resolve_import_map_entry_0_in_file_1_Supply_the_rootDir_compiler_option_to_disambiguate + : Diagnostics.The_project_root_is_ambiguous_but_is_required_to_resolve_export_map_entry_0_in_file_1_Supply_the_rootDir_compiler_option_to_disambiguate, + entry === "" ? "." : entry, // replace empty string with `.` - the reverse of the operation done when entries are built - so main entrypoint errors don't look weird + packagePath + )); + } + for (const commonSourceDirGuess of commonSourceDirGuesses) { + const candidateDirectories = getOutputDirectoriesForBaseDirectory(commonSourceDirGuess); + for (const candidateDir of candidateDirectories) { + if (containsPath(candidateDir, finalPath, !useCaseSensitiveFileNames())) { + // The matched export is looking up something in either the out declaration or js dir, now map the written path back into the source dir and source extension + const pathFragment = finalPath.slice(candidateDir.length + 1); // +1 to also remove directory seperator + const possibleInputBase = combinePaths(commonSourceDirGuess, pathFragment); + const jsAndDtsExtensions = [Extension.Mjs, Extension.Cjs, Extension.Js, Extension.Json, Extension.Dmts, Extension.Dcts, Extension.Dts]; + for (const ext of jsAndDtsExtensions) { + if (fileExtensionIs(possibleInputBase, ext)) { + const inputExts = getPossibleOriginalInputExtensionForExtension(possibleInputBase); + for (const possibleExt of inputExts) { + const possibleInputWithInputExtension = changeAnyExtension(possibleInputBase, possibleExt, ext, !useCaseSensitiveFileNames()); + if ((extensions === Extensions.TypeScript && hasJSFileExtension(possibleInputWithInputExtension)) || + (extensions === Extensions.JavaScript && hasTSFileExtension(possibleInputWithInputExtension))) { + continue; + } + if (state.host.fileExists(possibleInputWithInputExtension)) { + return toSearchResult(withPackageId(scope, loadJSOrExactTSFileName(extensions, possibleInputWithInputExtension, /*onlyRecordFailures*/ false, state))); } } } @@ -2365,357 +2389,358 @@ namespace ts { } } } - return undefined; + } + return undefined; - function getOutputDirectoriesForBaseDirectory(commonSourceDirGuess: string) { - // Config file ouput paths are processed to be relative to the host's current directory, while - // otherwise the paths are resolved relative to the common source dir the compiler puts together - const currentDir = state.compilerOptions.configFile ? state.host.getCurrentDirectory?.() || "" : commonSourceDirGuess; - const candidateDirectories = []; - if (state.compilerOptions.declarationDir) { - candidateDirectories.push(toAbsolutePath(combineDirectoryPath(currentDir, state.compilerOptions.declarationDir))); - } - if (state.compilerOptions.outDir && state.compilerOptions.outDir !== state.compilerOptions.declarationDir) { - candidateDirectories.push(toAbsolutePath(combineDirectoryPath(currentDir, state.compilerOptions.outDir))); - } - return candidateDirectories; + function getOutputDirectoriesForBaseDirectory(commonSourceDirGuess: string) { + // Config file ouput paths are processed to be relative to the host's current directory, while + // otherwise the paths are resolved relative to the common source dir the compiler puts together + const currentDir = state.compilerOptions.configFile ? state.host.getCurrentDirectory?.() || "" : commonSourceDirGuess; + const candidateDirectories = []; + if (state.compilerOptions.declarationDir) { + candidateDirectories.push(toAbsolutePath(combineDirectoryPath(currentDir, state.compilerOptions.declarationDir))); } + if (state.compilerOptions.outDir && state.compilerOptions.outDir !== state.compilerOptions.declarationDir) { + candidateDirectories.push(toAbsolutePath(combineDirectoryPath(currentDir, state.compilerOptions.outDir))); + } + return candidateDirectories; } } } +} - /* @internal */ - export function isApplicableVersionedTypesKey(conditions: readonly string[], key: string) { - if (conditions.indexOf("types") === -1) return false; // only apply versioned types conditions if the types condition is applied - if (!startsWith(key, "types@")) return false; - const range = VersionRange.tryParse(key.substring("types@".length)); - if (!range) return false; - return range.test(version); - } +/** @internal */ +export function isApplicableVersionedTypesKey(conditions: readonly string[], key: string) { + if (conditions.indexOf("types") === -1) return false; // only apply versioned types conditions if the types condition is applied + if (!startsWith(key, "types@")) return false; + const range = VersionRange.tryParse(key.substring("types@".length)); + if (!range) return false; + return range.test(version); +} - function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference); - } +function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference); +} - function loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult { - // Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly. - return loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined, /*redirectedReference*/ undefined); - } +function loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult { + // Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly. + return loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined, /*redirectedReference*/ undefined); +} - function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, state.features === 0 ? undefined : state.features & NodeResolutionFeatures.EsmMode ? ModuleKind.ESNext : ModuleKind.CommonJS, redirectedReference); - return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => { - if (getBaseFileName(ancestorDirectory) !== "node_modules") { - const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state); - if (resolutionFromCache) { - return resolutionFromCache; - } - return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly, cache, redirectedReference)); +function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, state.features === 0 ? undefined : state.features & NodeResolutionFeatures.EsmMode ? ModuleKind.ESNext : ModuleKind.CommonJS, redirectedReference); + return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => { + if (getBaseFileName(ancestorDirectory) !== "node_modules") { + const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state); + if (resolutionFromCache) { + return resolutionFromCache; } - }); - } - - function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { - const nodeModulesFolder = combinePaths(directory, "node_modules"); - const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); - if (!nodeModulesFolderExists && state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesFolder); + return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly, cache, redirectedReference)); } + }); +} - const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificNodeModulesDirectory(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, state, cache, redirectedReference); - if (packageResult) { - return packageResult; - } - if (extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) { - const nodeModulesAtTypes = combinePaths(nodeModulesFolder, "@types"); - let nodeModulesAtTypesExists = nodeModulesFolderExists; - if (nodeModulesFolderExists && !directoryProbablyExists(nodeModulesAtTypes, state.host)) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesAtTypes); - } - nodeModulesAtTypesExists = false; - } - return loadModuleFromSpecificNodeModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, state, cache, redirectedReference); - } +function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { + const nodeModulesFolder = combinePaths(directory, "node_modules"); + const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); + if (!nodeModulesFolderExists && state.traceEnabled) { + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesFolder); } - function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { - const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); - - // First look for a nested package.json, as in `node_modules/foo/bar/package.json`. - let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); - // But only if we're not respecting export maps (if we are, we might redirect around this location) - if (!(state.features & NodeResolutionFeatures.Exports)) { - if (packageInfo) { - const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); - if (fromFile) { - return noPackageId(fromFile); - } - - const fromDirectory = loadNodeModuleFromDirectoryWorker( - extensions, - candidate, - !nodeModulesDirectoryExists, - state, - packageInfo.contents.packageJsonContent, - packageInfo.contents.versionPaths - ); - return withPackageId(packageInfo, fromDirectory); + const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificNodeModulesDirectory(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, state, cache, redirectedReference); + if (packageResult) { + return packageResult; + } + if (extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) { + const nodeModulesAtTypes = combinePaths(nodeModulesFolder, "@types"); + let nodeModulesAtTypesExists = nodeModulesFolderExists; + if (nodeModulesFolderExists && !directoryProbablyExists(nodeModulesAtTypes, state.host)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesAtTypes); } + nodeModulesAtTypesExists = false; } + return loadModuleFromSpecificNodeModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, state, cache, redirectedReference); + } +} - const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { - let pathAndExtension = - loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || - loadNodeModuleFromDirectoryWorker( - extensions, - candidate, - onlyRecordFailures, - state, - packageInfo && packageInfo.contents.packageJsonContent, - packageInfo && packageInfo.contents.versionPaths - ); - if ( - !pathAndExtension && packageInfo - // eslint-disable-next-line no-null/no-null - && (packageInfo.contents.packageJsonContent.exports === undefined || packageInfo.contents.packageJsonContent.exports === null) - && state.features & NodeResolutionFeatures.EsmMode - ) { - // EsmMode disables index lookup in `loadNodeModuleFromDirectoryWorker` generally, however non-relative package resolutions still assume - // a default `index.js` entrypoint if no `main` or `exports` are present - pathAndExtension = loadModuleFromFile(extensions, combinePaths(candidate, "index.js"), onlyRecordFailures, state); - } - return withPackageId(packageInfo, pathAndExtension); - }; +function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { + const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); - const { packageName, rest } = parsePackageName(moduleName); - const packageDirectory = combinePaths(nodeModulesDirectory, packageName); - if (rest !== "") { - // Previous `packageInfo` may have been from a nested package.json; ensure we have the one from the package root now. - packageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); - } - // package exports are higher priority than file/directory/typesVersions lookups and (and, if there's exports present, blocks them) - if (packageInfo && packageInfo.contents.packageJsonContent.exports && state.features & NodeResolutionFeatures.Exports) { - return loadModuleFromExports(packageInfo, extensions, combinePaths(".", rest), state, cache, redirectedReference)?.value; + // First look for a nested package.json, as in `node_modules/foo/bar/package.json`. + let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); + // But only if we're not respecting export maps (if we are, we might redirect around this location) + if (!(state.features & NodeResolutionFeatures.Exports)) { + if (packageInfo) { + const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); + if (fromFile) { + return noPackageId(fromFile); + } + + const fromDirectory = loadNodeModuleFromDirectoryWorker( + extensions, + candidate, + !nodeModulesDirectoryExists, + state, + packageInfo.contents.packageJsonContent, + packageInfo.contents.versionPaths + ); + return withPackageId(packageInfo, fromDirectory); + } + } + + const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { + let pathAndExtension = + loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || + loadNodeModuleFromDirectoryWorker( + extensions, + candidate, + onlyRecordFailures, + state, + packageInfo && packageInfo.contents.packageJsonContent, + packageInfo && packageInfo.contents.versionPaths + ); + if ( + !pathAndExtension && packageInfo + // eslint-disable-next-line no-null/no-null + && (packageInfo.contents.packageJsonContent.exports === undefined || packageInfo.contents.packageJsonContent.exports === null) + && state.features & NodeResolutionFeatures.EsmMode + ) { + // EsmMode disables index lookup in `loadNodeModuleFromDirectoryWorker` generally, however non-relative package resolutions still assume + // a default `index.js` entrypoint if no `main` or `exports` are present + pathAndExtension = loadModuleFromFile(extensions, combinePaths(candidate, "index.js"), onlyRecordFailures, state); + } + return withPackageId(packageInfo, pathAndExtension); + }; + + const { packageName, rest } = parsePackageName(moduleName); + const packageDirectory = combinePaths(nodeModulesDirectory, packageName); + if (rest !== "") { + // Previous `packageInfo` may have been from a nested package.json; ensure we have the one from the package root now. + packageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); + } + // package exports are higher priority than file/directory/typesVersions lookups and (and, if there's exports present, blocks them) + if (packageInfo && packageInfo.contents.packageJsonContent.exports && state.features & NodeResolutionFeatures.Exports) { + return loadModuleFromExports(packageInfo, extensions, combinePaths(".", rest), state, cache, redirectedReference)?.value; + } + if (rest !== "" && packageInfo && packageInfo.contents.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, packageInfo.contents.versionPaths.version, version, rest); } - if (rest !== "" && packageInfo && packageInfo.contents.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, packageInfo.contents.versionPaths.version, version, rest); - } - const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host); - const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, packageInfo.contents.versionPaths.paths, /*pathPatterns*/ undefined, loader, !packageDirectoryExists, state); - if (fromPaths) { - return fromPaths.value; - } + const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host); + const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, packageInfo.contents.versionPaths.paths, /*pathPatterns*/ undefined, loader, !packageDirectoryExists, state); + if (fromPaths) { + return fromPaths.value; } - - return loader(extensions, candidate, !nodeModulesDirectoryExists, state); } - function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike, pathPatterns: readonly (string | Pattern)[] | undefined, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult { - pathPatterns ||= tryParsePatterns(paths); - const matchedPattern = matchPatternOrExact(pathPatterns, moduleName); - if (matchedPattern) { - const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); - const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern); + return loader(extensions, candidate, !nodeModulesDirectoryExists, state); +} + +function tryLoadModuleUsingPaths(extensions: Extensions, moduleName: string, baseDirectory: string, paths: MapLike, pathPatterns: readonly (string | Pattern)[] | undefined, loader: ResolutionKindSpecificLoader, onlyRecordFailures: boolean, state: ModuleResolutionState): SearchResult { + pathPatterns ||= tryParsePatterns(paths); + const matchedPattern = matchPatternOrExact(pathPatterns, moduleName); + if (matchedPattern) { + const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); + const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern); + if (state.traceEnabled) { + trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); + } + const resolved = forEach(paths[matchedPatternText], subst => { + const path = matchedStar ? subst.replace("*", matchedStar) : subst; + // When baseUrl is not specified, the command line parser resolves relative paths to the config file location. + const candidate = normalizePath(combinePaths(baseDirectory, path)); if (state.traceEnabled) { - trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); - } - const resolved = forEach(paths[matchedPatternText], subst => { - const path = matchedStar ? subst.replace("*", matchedStar) : subst; - // When baseUrl is not specified, the command line parser resolves relative paths to the config file location. - const candidate = normalizePath(combinePaths(baseDirectory, path)); - if (state.traceEnabled) { - trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); + trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path); + } + // A path mapping may have an extension, in contrast to an import, which should omit it. + const extension = tryGetExtensionFromPath(subst); + if (extension !== undefined) { + const path = tryFile(candidate, onlyRecordFailures, state); + if (path !== undefined) { + return noPackageId({ path, ext: extension }); } - // A path mapping may have an extension, in contrast to an import, which should omit it. - const extension = tryGetExtensionFromPath(subst); - if (extension !== undefined) { - const path = tryFile(candidate, onlyRecordFailures, state); - if (path !== undefined) { - return noPackageId({ path, ext: extension }); - } - } - return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); - }); - return { value: resolved }; - } + } + return loader(extensions, candidate, onlyRecordFailures || !directoryProbablyExists(getDirectoryPath(candidate), state.host), state); + }); + return { value: resolved }; } +} - /** Double underscores are used in DefinitelyTyped to delimit scoped packages. */ - const mangledScopedPackageSeparator = "__"; +/** Double underscores are used in DefinitelyTyped to delimit scoped packages. */ +const mangledScopedPackageSeparator = "__"; - /** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */ - function mangleScopedPackageNameWithTrace(packageName: string, state: ModuleResolutionState): string { - const mangled = mangleScopedPackageName(packageName); - if (state.traceEnabled && mangled !== packageName) { - trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled); - } - return mangled; +/** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */ +function mangleScopedPackageNameWithTrace(packageName: string, state: ModuleResolutionState): string { + const mangled = mangleScopedPackageName(packageName); + if (state.traceEnabled && mangled !== packageName) { + trace(state.host, Diagnostics.Scoped_package_detected_looking_in_0, mangled); } + return mangled; +} - /* @internal */ - export function getTypesPackageName(packageName: string): string { - return `@types/${mangleScopedPackageName(packageName)}`; - } +/** @internal */ +export function getTypesPackageName(packageName: string): string { + return `@types/${mangleScopedPackageName(packageName)}`; +} - /* @internal */ - export function mangleScopedPackageName(packageName: string): string { - if (startsWith(packageName, "@")) { - const replaceSlash = packageName.replace(directorySeparator, mangledScopedPackageSeparator); - if (replaceSlash !== packageName) { - return replaceSlash.slice(1); // Take off the "@" - } +/** @internal */ +export function mangleScopedPackageName(packageName: string): string { + if (startsWith(packageName, "@")) { + const replaceSlash = packageName.replace(directorySeparator, mangledScopedPackageSeparator); + if (replaceSlash !== packageName) { + return replaceSlash.slice(1); // Take off the "@" } - return packageName; } + return packageName; +} - /* @internal */ - export function getPackageNameFromTypesPackageName(mangledName: string): string { - const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); - if (withoutAtTypePrefix !== mangledName) { - return unmangleScopedPackageName(withoutAtTypePrefix); - } - return mangledName; +/** @internal */ +export function getPackageNameFromTypesPackageName(mangledName: string): string { + const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); + if (withoutAtTypePrefix !== mangledName) { + return unmangleScopedPackageName(withoutAtTypePrefix); } + return mangledName; +} - /* @internal */ - export function unmangleScopedPackageName(typesPackageName: string): string { - return stringContains(typesPackageName, mangledScopedPackageSeparator) ? - "@" + typesPackageName.replace(mangledScopedPackageSeparator, directorySeparator) : - typesPackageName; - } +/** @internal */ +export function unmangleScopedPackageName(typesPackageName: string): string { + return stringContains(typesPackageName, mangledScopedPackageSeparator) ? + "@" + typesPackageName.replace(mangledScopedPackageSeparator, directorySeparator) : + typesPackageName; +} - function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, state: ModuleResolutionState): SearchResult { - const result = cache && cache.get(containingDirectory); - if (result) { - if (state.traceEnabled) { - trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); - } - state.resultFromCache = result; - return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } }; - } - } - - export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); - const failedLookupLocations: string[] = []; - const affectingLocations: string[] = []; - const containingDirectory = getDirectoryPath(containingFile); - const diagnostics: Diagnostic[] = []; - const state: ModuleResolutionState = { - compilerOptions, - host, - traceEnabled, - failedLookupLocations, - affectingLocations, packageJsonInfoCache: cache, - features: NodeResolutionFeatures.None, - conditions: [], - requestContainingDirectory: containingDirectory, - reportDiagnostic: diag => void diagnostics.push(diag), - }; +function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, state: ModuleResolutionState): SearchResult { + const result = cache && cache.get(containingDirectory); + if (result) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory); + } + state.resultFromCache = result; + return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } }; + } +} - const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript); - // No originalPath because classic resolution doesn't resolve realPath - return createResolvedModuleWithFailedLookupLocations( - resolved && resolved.value, - /*isExternalLibraryImport*/ false, - failedLookupLocations, - affectingLocations, - diagnostics, - state.resultFromCache - ); - - function tryResolve(extensions: Extensions): SearchResult { - const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, state); - if (resolvedUsingSettings) { - return { value: resolvedUsingSettings }; - } +export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + const failedLookupLocations: string[] = []; + const affectingLocations: string[] = []; + const containingDirectory = getDirectoryPath(containingFile); + const diagnostics: Diagnostic[] = []; + const state: ModuleResolutionState = { + compilerOptions, + host, + traceEnabled, + failedLookupLocations, + affectingLocations, packageJsonInfoCache: cache, + features: NodeResolutionFeatures.None, + conditions: [], + requestContainingDirectory: containingDirectory, + reportDiagnostic: diag => void diagnostics.push(diag), + }; + + const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript); + // No originalPath because classic resolution doesn't resolve realPath + return createResolvedModuleWithFailedLookupLocations( + resolved && resolved.value, + /*isExternalLibraryImport*/ false, + failedLookupLocations, + affectingLocations, + diagnostics, + state.resultFromCache + ); + + function tryResolve(extensions: Extensions): SearchResult { + const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, state); + if (resolvedUsingSettings) { + return { value: resolvedUsingSettings }; + } - if (!isExternalModuleNameRelative(moduleName)) { - const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, /*mode*/ undefined, redirectedReference); - // Climb up parent directories looking for a module. - const resolved = forEachAncestorDirectory(containingDirectory, directory => { - const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state); - if (resolutionFromCache) { - return resolutionFromCache; - } - const searchName = normalizePath(combinePaths(directory, moduleName)); - return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state)); - }); - if (resolved) { - return resolved; - } - if (extensions === Extensions.TypeScript) { - // If we didn't find the file normally, look it up in @types. - return loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName, containingDirectory, state); + if (!isExternalModuleNameRelative(moduleName)) { + const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, /*mode*/ undefined, redirectedReference); + // Climb up parent directories looking for a module. + const resolved = forEachAncestorDirectory(containingDirectory, directory => { + const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state); + if (resolutionFromCache) { + return resolutionFromCache; } + const searchName = normalizePath(combinePaths(directory, moduleName)); + return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, /*onlyRecordFailures*/ false, state)); + }); + if (resolved) { + return resolved; } - else { - const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); - return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, /*onlyRecordFailures*/ false, state)); + if (extensions === Extensions.TypeScript) { + // If we didn't find the file normally, look it up in @types. + return loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName, containingDirectory, state); } } + else { + const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); + return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, /*onlyRecordFailures*/ false, state)); + } } +} - /** - * 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, packageJsonInfoCache: PackageJsonInfoCache): ResolvedModuleWithFailedLookupLocations { - const traceEnabled = isTraceEnabled(compilerOptions, host); - if (traceEnabled) { - trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache); - } - const failedLookupLocations: string[] = []; - const affectingLocations: string[] = []; - const diagnostics: Diagnostic[] = []; - const state: ModuleResolutionState = { - compilerOptions, - host, - traceEnabled, - failedLookupLocations, - affectingLocations, - packageJsonInfoCache, - features: NodeResolutionFeatures.None, - conditions: [], - requestContainingDirectory: undefined, - reportDiagnostic: diag => void diagnostics.push(diag), - }; - const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false, /*cache*/ undefined, /*redirectedReference*/ undefined); - return createResolvedModuleWithFailedLookupLocations( - resolved, - /*isExternalLibraryImport*/ true, - failedLookupLocations, - affectingLocations, - diagnostics, - state.resultFromCache - ); - } - - /** - * Represents result of search. Normally when searching among several alternatives we treat value `undefined` as indicator - * that search fails and we should try another option. - * However this does not allow us to represent final result that should be used instead of further searching (i.e. a final result that was found in cache). - * SearchResult is used to deal with this issue, its values represents following outcomes: - * - undefined - not found, continue searching - * - { value: undefined } - not found - stop searching - * - { value: } - found - stop searching - */ - type SearchResult = { value: T | undefined } | undefined; +/** + * 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, packageJsonInfoCache: PackageJsonInfoCache): ResolvedModuleWithFailedLookupLocations { + const traceEnabled = isTraceEnabled(compilerOptions, host); + if (traceEnabled) { + trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache); + } + const failedLookupLocations: string[] = []; + const affectingLocations: string[] = []; + const diagnostics: Diagnostic[] = []; + const state: ModuleResolutionState = { + compilerOptions, + host, + traceEnabled, + failedLookupLocations, + affectingLocations, + packageJsonInfoCache, + features: NodeResolutionFeatures.None, + conditions: [], + requestContainingDirectory: undefined, + reportDiagnostic: diag => void diagnostics.push(diag), + }; + const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false, /*cache*/ undefined, /*redirectedReference*/ undefined); + return createResolvedModuleWithFailedLookupLocations( + resolved, + /*isExternalLibraryImport*/ true, + failedLookupLocations, + affectingLocations, + diagnostics, + state.resultFromCache + ); +} - /** - * Wraps value to SearchResult. - * @returns undefined if value is undefined or { value } otherwise - */ - function toSearchResult(value: T | undefined): SearchResult { - return value !== undefined ? { value } : undefined; - } +/** + * Represents result of search. Normally when searching among several alternatives we treat value `undefined` as indicator + * that search fails and we should try another option. + * However this does not allow us to represent final result that should be used instead of further searching (i.e. a final result that was found in cache). + * SearchResult is used to deal with this issue, its values represents following outcomes: + * - undefined - not found, continue searching + * - { value: undefined } - not found - stop searching + * - { value: } - found - stop searching + */ +type SearchResult = { value: T | undefined } | undefined; + +/** + * Wraps value to SearchResult. + * @returns undefined if value is undefined or { value } otherwise + */ +function toSearchResult(value: T | undefined): SearchResult { + return value !== undefined ? { value } : undefined; +} - function traceIfEnabled(state: ModuleResolutionState, diagnostic: DiagnosticMessage, ...args: string[]) { - if (state.traceEnabled) { - trace(state.host, diagnostic, ...args); - } +function traceIfEnabled(state: ModuleResolutionState, diagnostic: DiagnosticMessage, ...args: string[]) { + if (state.traceEnabled) { + trace(state.host, diagnostic, ...args); } } diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 2327f8924b8e6..b6ddd551cf9c6 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -1,959 +1,990 @@ +import { + __String, allKeysStartWithDot, AmbientModuleDeclaration, append, arrayFrom, CharacterCodes, combinePaths, + compareBooleans, compareNumberOfDirectorySeparators, comparePaths, Comparison, CompilerOptions, containsIgnoredPath, + containsPath, createGetCanonicalFileName, Debug, directorySeparator, emptyArray, endsWith, + ensurePathIsNonModuleName, ensureTrailingDirectorySeparator, every, ExportAssignment, Extension, extensionFromPath, + fileExtensionIsOneOf, FileIncludeKind, firstDefined, flatMap, flatten, forEach, forEachAncestorDirectory, + GetCanonicalFileName, getDirectoryPath, getEmitModuleResolutionKind, getImpliedNodeFormatForFile, + getModeForResolutionAtIndex, getModuleNameStringLiteralAt, getNodeModulePathParts, getNormalizedAbsolutePath, + getOwnKeys, getPackageJsonTypesVersionsPaths, getPackageNameFromTypesPackageName, getPathsBasePath, + getRelativePathFromDirectory, getRelativePathToDirectoryOrUrl, getSourceFileOfModule, getSupportedExtensions, + getTextOfIdentifierOrLiteral, hasJSFileExtension, hasTSFileExtension, hostGetCanonicalFileName, Identifier, + isAmbientModule, isApplicableVersionedTypesKey, isExternalModuleAugmentation, isExternalModuleNameRelative, + isModuleBlock, isModuleDeclaration, isNonGlobalAmbientModule, isRootedDiskPath, isSourceFile, isString, JsxEmit, + map, Map, mapDefined, MapLike, matchPatternOrExact, min, ModuleDeclaration, ModuleKind, ModulePath, + ModuleResolutionHost, ModuleResolutionKind, ModuleSpecifierCache, ModuleSpecifierOptions, + ModuleSpecifierResolutionHost, NodeFlags, NodeModulePathParts, normalizePath, Path, pathContainsNodeModules, + pathIsBareSpecifier, pathIsRelative, PropertyAccessExpression, removeFileExtension, removeSuffix, resolvePath, + ScriptKind, some, SourceFile, startsWith, startsWithDirectory, stringContains, StringLiteral, Symbol, SymbolFlags, + toPath, tryGetExtensionFromPath, tryParsePatterns, TypeChecker, UserPreferences, +} from "./_namespaces/ts"; + // Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers. -/* @internal */ -namespace ts.moduleSpecifiers { - const enum RelativePreference { Relative, NonRelative, Shortest, ExternalNonRelative } - // See UserPreferences#importPathEnding - const enum Ending { Minimal, Index, JsExtension } - - // Processed preferences - interface Preferences { - readonly relativePreference: RelativePreference; - readonly ending: Ending; - } - - function getPreferences(host: ModuleSpecifierResolutionHost, { importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences { - return { - relativePreference: - importModuleSpecifierPreference === "relative" ? RelativePreference.Relative : - importModuleSpecifierPreference === "non-relative" ? RelativePreference.NonRelative : - importModuleSpecifierPreference === "project-relative" ? RelativePreference.ExternalNonRelative : - RelativePreference.Shortest, - ending: getEnding(), - }; - function getEnding(): Ending { - switch (importModuleSpecifierEnding) { - case "minimal": return Ending.Minimal; - case "index": return Ending.Index; - case "js": return Ending.JsExtension; - default: return usesJsExtensionOnImports(importingSourceFile) || isFormatRequiringExtensions(compilerOptions, importingSourceFile.path, host) ? Ending.JsExtension - : getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal; - } + +const enum RelativePreference { Relative, NonRelative, Shortest, ExternalNonRelative } +// See UserPreferences#importPathEnding +const enum Ending { Minimal, Index, JsExtension } + +// Processed preferences +interface Preferences { + readonly relativePreference: RelativePreference; + readonly ending: Ending; +} + +function getPreferences(host: ModuleSpecifierResolutionHost, { importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences { + return { + relativePreference: + importModuleSpecifierPreference === "relative" ? RelativePreference.Relative : + importModuleSpecifierPreference === "non-relative" ? RelativePreference.NonRelative : + importModuleSpecifierPreference === "project-relative" ? RelativePreference.ExternalNonRelative : + RelativePreference.Shortest, + ending: getEnding(), + }; + function getEnding(): Ending { + switch (importModuleSpecifierEnding) { + case "minimal": return Ending.Minimal; + case "index": return Ending.Index; + case "js": return Ending.JsExtension; + default: return usesJsExtensionOnImports(importingSourceFile) || isFormatRequiringExtensions(compilerOptions, importingSourceFile.path, host) ? Ending.JsExtension + : getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal; } } +} + +function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Preferences { + return { + relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative, + ending: hasJSFileExtension(oldImportSpecifier) || isFormatRequiringExtensions(compilerOptions, importingSourceFileName, host) ? + Ending.JsExtension : + getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal, + }; +} - function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Preferences { - return { - relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative, - ending: hasJSFileExtension(oldImportSpecifier) || isFormatRequiringExtensions(compilerOptions, importingSourceFileName, host) ? - Ending.JsExtension : - getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal, - }; +function isFormatRequiringExtensions(compilerOptions: CompilerOptions, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost) { + if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16 + && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { + return false; } + return getImpliedNodeFormatForFile(importingSourceFileName, host.getPackageJsonInfoCache?.(), getModuleResolutionHost(host), compilerOptions) !== ModuleKind.CommonJS; +} - function isFormatRequiringExtensions(compilerOptions: CompilerOptions, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost) { - if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16 - && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { - return false; - } - return getImpliedNodeFormatForFile(importingSourceFileName, host.getPackageJsonInfoCache?.(), getModuleResolutionHost(host), compilerOptions) !== ModuleKind.CommonJS; - } - - function getModuleResolutionHost(host: ModuleSpecifierResolutionHost): ModuleResolutionHost { - return { - fileExists: host.fileExists, - readFile: Debug.checkDefined(host.readFile), - directoryExists: host.directoryExists, - getCurrentDirectory: host.getCurrentDirectory, - realpath: host.realpath, - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames?.(), - }; - } - - // `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`? - // Because when this is called by the file renamer, `importingSourceFile` is the file being renamed, - // while `importingSourceFileName` its *new* name. We need a source file just to get its - // `impliedNodeFormat` and to detect certain preferences from existing import module specifiers. - export function updateModuleSpecifier( - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - importingSourceFileName: Path, - toFileName: string, - host: ModuleSpecifierResolutionHost, - oldImportSpecifier: string, - options: ModuleSpecifierOptions = {}, - ): string | undefined { - const res = getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier, importingSourceFileName, host), {}, options); - if (res === oldImportSpecifier) return undefined; - return res; - } - - // `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`? - // Because when this is called by the declaration emitter, `importingSourceFile` is the implementation - // file, but `importingSourceFileName` and `toFileName` refer to declaration files (the former to the - // one currently being produced; the latter to the one being imported). We need an implementation file - // just to get its `impliedNodeFormat` and to detect certain preferences from existing import module - // specifiers. - export function getModuleSpecifier( - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - importingSourceFileName: Path, - toFileName: string, - host: ModuleSpecifierResolutionHost, - options: ModuleSpecifierOptions = {}, - ): string { - return getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferences(host, {}, compilerOptions, importingSourceFile), {}, options); - } - - export function getNodeModulesPackageName( - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - nodeModulesFileName: string, - host: ModuleSpecifierResolutionHost, - preferences: UserPreferences, - options: ModuleSpecifierOptions = {}, - ): string | undefined { - const info = getInfo(importingSourceFile.path, host); - const modulePaths = getAllModulePaths(importingSourceFile.path, nodeModulesFileName, host, preferences, options); - return firstDefined(modulePaths, - modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, preferences, /*packageNameOnly*/ true, options.overrideImportMode)); - } - - function getModuleSpecifierWorker( - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - importingSourceFileName: Path, - toFileName: string, - host: ModuleSpecifierResolutionHost, - preferences: Preferences, - userPreferences: UserPreferences, - options: ModuleSpecifierOptions = {} - ): string { - const info = getInfo(importingSourceFileName, host); - const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host, userPreferences, options); - return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode)) || - getLocalModuleSpecifier(toFileName, info, compilerOptions, host, options.overrideImportMode || importingSourceFile.impliedNodeFormat, preferences); - } - - export function tryGetModuleSpecifiersFromCache( - moduleSymbol: Symbol, - importingSourceFile: SourceFile, - host: ModuleSpecifierResolutionHost, - userPreferences: UserPreferences, - options: ModuleSpecifierOptions = {}, - ): readonly string[] | undefined { - return tryGetModuleSpecifiersFromCacheWorker( - moduleSymbol, - importingSourceFile, - host, - userPreferences, - options)[0]; - } - - function tryGetModuleSpecifiersFromCacheWorker( - moduleSymbol: Symbol, - importingSourceFile: SourceFile, - host: ModuleSpecifierResolutionHost, - userPreferences: UserPreferences, - options: ModuleSpecifierOptions = {}, - ): readonly [specifiers?: readonly string[], moduleFile?: SourceFile, modulePaths?: readonly ModulePath[], cache?: ModuleSpecifierCache] { - const moduleSourceFile = getSourceFileOfModule(moduleSymbol); - if (!moduleSourceFile) { - return emptyArray as []; - } +function getModuleResolutionHost(host: ModuleSpecifierResolutionHost): ModuleResolutionHost { + return { + fileExists: host.fileExists, + readFile: Debug.checkDefined(host.readFile), + directoryExists: host.directoryExists, + getCurrentDirectory: host.getCurrentDirectory, + realpath: host.realpath, + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames?.(), + }; +} - const cache = host.getModuleSpecifierCache?.(); - const cached = cache?.get(importingSourceFile.path, moduleSourceFile.path, userPreferences, options); - return [cached?.moduleSpecifiers, moduleSourceFile, cached?.modulePaths, cache]; - } - - /** Returns an import for each symlink and for the realpath. */ - export function getModuleSpecifiers( - moduleSymbol: Symbol, - checker: TypeChecker, - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - host: ModuleSpecifierResolutionHost, - userPreferences: UserPreferences, - options: ModuleSpecifierOptions = {}, - ): readonly string[] { - return getModuleSpecifiersWithCacheInfo( - moduleSymbol, - checker, - compilerOptions, - importingSourceFile, - host, - userPreferences, - options - ).moduleSpecifiers; - } - - export function getModuleSpecifiersWithCacheInfo( - moduleSymbol: Symbol, - checker: TypeChecker, - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - host: ModuleSpecifierResolutionHost, - userPreferences: UserPreferences, - options: ModuleSpecifierOptions = {}, - ): { moduleSpecifiers: readonly string[], computedWithoutCache: boolean } { - let computedWithoutCache = false; - const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol, checker); - if (ambient) return { moduleSpecifiers: [ambient], computedWithoutCache }; - - // eslint-disable-next-line prefer-const - let [specifiers, moduleSourceFile, modulePaths, cache] = tryGetModuleSpecifiersFromCacheWorker( - moduleSymbol, - importingSourceFile, - host, - userPreferences, - options - ); - if (specifiers) return { moduleSpecifiers: specifiers, computedWithoutCache }; - if (!moduleSourceFile) return { moduleSpecifiers: emptyArray, computedWithoutCache }; - - computedWithoutCache = true; - modulePaths ||= getAllModulePathsWorker(importingSourceFile.path, moduleSourceFile.originalFileName, host); - const result = computeModuleSpecifiers(modulePaths, compilerOptions, importingSourceFile, host, userPreferences, options); - cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, options, modulePaths, result); - return { moduleSpecifiers: result, computedWithoutCache }; - } - - function computeModuleSpecifiers( - modulePaths: readonly ModulePath[], - compilerOptions: CompilerOptions, - importingSourceFile: SourceFile, - host: ModuleSpecifierResolutionHost, - userPreferences: UserPreferences, - options: ModuleSpecifierOptions = {}, - ): readonly string[] { - const info = getInfo(importingSourceFile.path, host); - const preferences = getPreferences(host, userPreferences, compilerOptions, importingSourceFile); - const existingSpecifier = forEach(modulePaths, modulePath => forEach( - host.getFileIncludeReasons().get(toPath(modulePath.path, host.getCurrentDirectory(), info.getCanonicalFileName)), - reason => { - if (reason.kind !== FileIncludeKind.Import || reason.file !== importingSourceFile.path) return undefined; - // If the candidate import mode doesn't match the mode we're generating for, don't consider it - // TODO: maybe useful to keep around as an alternative option for certain contexts where the mode is overridable - if (importingSourceFile.impliedNodeFormat && importingSourceFile.impliedNodeFormat !== getModeForResolutionAtIndex(importingSourceFile, reason.index)) return undefined; - const specifier = getModuleNameStringLiteralAt(importingSourceFile, reason.index).text; - // If the preference is for non relative and the module specifier is relative, ignore it - return preferences.relativePreference !== RelativePreference.NonRelative || !pathIsRelative(specifier) ? - specifier : - undefined; - } - )); - if (existingSpecifier) { - const moduleSpecifiers = [existingSpecifier]; - return moduleSpecifiers; - } +// `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`? +// Because when this is called by the file renamer, `importingSourceFile` is the file being renamed, +// while `importingSourceFileName` its *new* name. We need a source file just to get its +// `impliedNodeFormat` and to detect certain preferences from existing import module specifiers. +/** @internal */ +export function updateModuleSpecifier( + compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, + importingSourceFileName: Path, + toFileName: string, + host: ModuleSpecifierResolutionHost, + oldImportSpecifier: string, + options: ModuleSpecifierOptions = {}, +): string | undefined { + const res = getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier, importingSourceFileName, host), {}, options); + if (res === oldImportSpecifier) return undefined; + return res; +} - const importedFileIsInNodeModules = some(modulePaths, p => p.isInNodeModules); - - // Module specifier priority: - // 1. "Bare package specifiers" (e.g. "@foo/bar") resulting from a path through node_modules to a package.json's "types" entry - // 2. Specifiers generated using "paths" from tsconfig - // 3. Non-relative specfiers resulting from a path through node_modules (e.g. "@foo/bar/path/to/file") - // 4. Relative paths - let nodeModulesSpecifiers: string[] | undefined; - let pathsSpecifiers: string[] | undefined; - let relativeSpecifiers: string[] | undefined; - for (const modulePath of modulePaths) { - const specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode); - nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier); - if (specifier && modulePath.isRedirect) { - // If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar", - // not "@foo/bar/path/to/file"). No other specifier will be this good, so stop looking. - return nodeModulesSpecifiers!; - } +// `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`? +// Because when this is called by the declaration emitter, `importingSourceFile` is the implementation +// file, but `importingSourceFileName` and `toFileName` refer to declaration files (the former to the +// one currently being produced; the latter to the one being imported). We need an implementation file +// just to get its `impliedNodeFormat` and to detect certain preferences from existing import module +// specifiers. +/** @internal */ +export function getModuleSpecifier( + compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, + importingSourceFileName: Path, + toFileName: string, + host: ModuleSpecifierResolutionHost, + options: ModuleSpecifierOptions = {}, +): string { + return getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferences(host, {}, compilerOptions, importingSourceFile), {}, options); +} - if (!specifier && !modulePath.isRedirect) { - const local = getLocalModuleSpecifier(modulePath.path, info, compilerOptions, host, options.overrideImportMode || importingSourceFile.impliedNodeFormat, preferences); - if (pathIsBareSpecifier(local)) { - pathsSpecifiers = append(pathsSpecifiers, local); - } - else if (!importedFileIsInNodeModules || modulePath.isInNodeModules) { - // Why this extra conditional, not just an `else`? If some path to the file contained - // 'node_modules', but we can't create a non-relative specifier (e.g. "@foo/bar/path/to/file"), - // that means we had to go through a *sibling's* node_modules, not one we can access directly. - // If some path to the file was in node_modules but another was not, this likely indicates that - // we have a monorepo structure with symlinks. In this case, the non-node_modules path is - // probably the realpath, e.g. "../bar/path/to/file", but a relative path to another package - // in a monorepo is probably not portable. So, the module specifier we actually go with will be - // the relative path through node_modules, so that the declaration emitter can produce a - // portability error. (See declarationEmitReexportedSymlinkReference3) - relativeSpecifiers = append(relativeSpecifiers, local); - } - } - } +/** @internal */ +export function getNodeModulesPackageName( + compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, + nodeModulesFileName: string, + host: ModuleSpecifierResolutionHost, + preferences: UserPreferences, + options: ModuleSpecifierOptions = {}, +): string | undefined { + const info = getInfo(importingSourceFile.path, host); + const modulePaths = getAllModulePaths(importingSourceFile.path, nodeModulesFileName, host, preferences, options); + return firstDefined(modulePaths, + modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, preferences, /*packageNameOnly*/ true, options.overrideImportMode)); +} - return pathsSpecifiers?.length ? pathsSpecifiers : - nodeModulesSpecifiers?.length ? nodeModulesSpecifiers : - Debug.checkDefined(relativeSpecifiers); - } +function getModuleSpecifierWorker( + compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, + importingSourceFileName: Path, + toFileName: string, + host: ModuleSpecifierResolutionHost, + preferences: Preferences, + userPreferences: UserPreferences, + options: ModuleSpecifierOptions = {} +): string { + const info = getInfo(importingSourceFileName, host); + const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host, userPreferences, options); + return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode)) || + getLocalModuleSpecifier(toFileName, info, compilerOptions, host, options.overrideImportMode || importingSourceFile.impliedNodeFormat, preferences); +} - interface Info { - readonly getCanonicalFileName: GetCanonicalFileName; - readonly importingSourceFileName: Path - readonly sourceDirectory: Path; - } - // importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path - function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Info { - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true); - const sourceDirectory = getDirectoryPath(importingSourceFileName); - return { getCanonicalFileName, importingSourceFileName, sourceDirectory }; - } +/** @internal */ +export function tryGetModuleSpecifiersFromCache( + moduleSymbol: Symbol, + importingSourceFile: SourceFile, + host: ModuleSpecifierResolutionHost, + userPreferences: UserPreferences, + options: ModuleSpecifierOptions = {}, +): readonly string[] | undefined { + return tryGetModuleSpecifiersFromCacheWorker( + moduleSymbol, + importingSourceFile, + host, + userPreferences, + options)[0]; +} - function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: SourceFile["impliedNodeFormat"], { ending, relativePreference }: Preferences): string { - const { baseUrl, paths, rootDirs } = compilerOptions; - const { sourceDirectory, getCanonicalFileName } = info; - const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, ending, compilerOptions) || - removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), ending, compilerOptions); - if (!baseUrl && !paths || relativePreference === RelativePreference.Relative) { - return relativePath; - } +function tryGetModuleSpecifiersFromCacheWorker( + moduleSymbol: Symbol, + importingSourceFile: SourceFile, + host: ModuleSpecifierResolutionHost, + userPreferences: UserPreferences, + options: ModuleSpecifierOptions = {}, +): readonly [specifiers?: readonly string[], moduleFile?: SourceFile, modulePaths?: readonly ModulePath[], cache?: ModuleSpecifierCache] { + const moduleSourceFile = getSourceFileOfModule(moduleSymbol); + if (!moduleSourceFile) { + return emptyArray as []; + } + + const cache = host.getModuleSpecifierCache?.(); + const cached = cache?.get(importingSourceFile.path, moduleSourceFile.path, userPreferences, options); + return [cached?.moduleSpecifiers, moduleSourceFile, cached?.modulePaths, cache]; +} - const baseDirectory = getNormalizedAbsolutePath(getPathsBasePath(compilerOptions, host) || baseUrl!, host.getCurrentDirectory()); - const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseDirectory, getCanonicalFileName); - if (!relativeToBaseUrl) { - return relativePath; - } +/** + * Returns an import for each symlink and for the realpath. + * + * @internal + */ +export function getModuleSpecifiers( + moduleSymbol: Symbol, + checker: TypeChecker, + compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, + host: ModuleSpecifierResolutionHost, + userPreferences: UserPreferences, + options: ModuleSpecifierOptions = {}, +): readonly string[] { + return getModuleSpecifiersWithCacheInfo( + moduleSymbol, + checker, + compilerOptions, + importingSourceFile, + host, + userPreferences, + options + ).moduleSpecifiers; +} - const fromPaths = paths && tryGetModuleNameFromPaths(relativeToBaseUrl, paths, getAllowedEndings(ending, compilerOptions, importMode), host, compilerOptions); - const nonRelative = fromPaths === undefined && baseUrl !== undefined ? removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions) : fromPaths; - if (!nonRelative) { - return relativePath; - } +/** @internal */ +export function getModuleSpecifiersWithCacheInfo( + moduleSymbol: Symbol, + checker: TypeChecker, + compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, + host: ModuleSpecifierResolutionHost, + userPreferences: UserPreferences, + options: ModuleSpecifierOptions = {}, +): { moduleSpecifiers: readonly string[], computedWithoutCache: boolean } { + let computedWithoutCache = false; + const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol, checker); + if (ambient) return { moduleSpecifiers: [ambient], computedWithoutCache }; + + // eslint-disable-next-line prefer-const + let [specifiers, moduleSourceFile, modulePaths, cache] = tryGetModuleSpecifiersFromCacheWorker( + moduleSymbol, + importingSourceFile, + host, + userPreferences, + options + ); + if (specifiers) return { moduleSpecifiers: specifiers, computedWithoutCache }; + if (!moduleSourceFile) return { moduleSpecifiers: emptyArray, computedWithoutCache }; + + computedWithoutCache = true; + modulePaths ||= getAllModulePathsWorker(importingSourceFile.path, moduleSourceFile.originalFileName, host); + const result = computeModuleSpecifiers(modulePaths, compilerOptions, importingSourceFile, host, userPreferences, options); + cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, options, modulePaths, result); + return { moduleSpecifiers: result, computedWithoutCache }; +} - if (relativePreference === RelativePreference.NonRelative) { - return nonRelative; +function computeModuleSpecifiers( + modulePaths: readonly ModulePath[], + compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, + host: ModuleSpecifierResolutionHost, + userPreferences: UserPreferences, + options: ModuleSpecifierOptions = {}, +): readonly string[] { + const info = getInfo(importingSourceFile.path, host); + const preferences = getPreferences(host, userPreferences, compilerOptions, importingSourceFile); + const existingSpecifier = forEach(modulePaths, modulePath => forEach( + host.getFileIncludeReasons().get(toPath(modulePath.path, host.getCurrentDirectory(), info.getCanonicalFileName)), + reason => { + if (reason.kind !== FileIncludeKind.Import || reason.file !== importingSourceFile.path) return undefined; + // If the candidate import mode doesn't match the mode we're generating for, don't consider it + // TODO: maybe useful to keep around as an alternative option for certain contexts where the mode is overridable + if (importingSourceFile.impliedNodeFormat && importingSourceFile.impliedNodeFormat !== getModeForResolutionAtIndex(importingSourceFile, reason.index)) return undefined; + const specifier = getModuleNameStringLiteralAt(importingSourceFile, reason.index).text; + // If the preference is for non relative and the module specifier is relative, ignore it + return preferences.relativePreference !== RelativePreference.NonRelative || !pathIsRelative(specifier) ? + specifier : + undefined; + } + )); + if (existingSpecifier) { + const moduleSpecifiers = [existingSpecifier]; + return moduleSpecifiers; + } + + const importedFileIsInNodeModules = some(modulePaths, p => p.isInNodeModules); + + // Module specifier priority: + // 1. "Bare package specifiers" (e.g. "@foo/bar") resulting from a path through node_modules to a package.json's "types" entry + // 2. Specifiers generated using "paths" from tsconfig + // 3. Non-relative specfiers resulting from a path through node_modules (e.g. "@foo/bar/path/to/file") + // 4. Relative paths + let nodeModulesSpecifiers: string[] | undefined; + let pathsSpecifiers: string[] | undefined; + let relativeSpecifiers: string[] | undefined; + for (const modulePath of modulePaths) { + const specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode); + nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier); + if (specifier && modulePath.isRedirect) { + // If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar", + // not "@foo/bar/path/to/file"). No other specifier will be this good, so stop looking. + return nodeModulesSpecifiers!; + } + + if (!specifier && !modulePath.isRedirect) { + const local = getLocalModuleSpecifier(modulePath.path, info, compilerOptions, host, options.overrideImportMode || importingSourceFile.impliedNodeFormat, preferences); + if (pathIsBareSpecifier(local)) { + pathsSpecifiers = append(pathsSpecifiers, local); + } + else if (!importedFileIsInNodeModules || modulePath.isInNodeModules) { + // Why this extra conditional, not just an `else`? If some path to the file contained + // 'node_modules', but we can't create a non-relative specifier (e.g. "@foo/bar/path/to/file"), + // that means we had to go through a *sibling's* node_modules, not one we can access directly. + // If some path to the file was in node_modules but another was not, this likely indicates that + // we have a monorepo structure with symlinks. In this case, the non-node_modules path is + // probably the realpath, e.g. "../bar/path/to/file", but a relative path to another package + // in a monorepo is probably not portable. So, the module specifier we actually go with will be + // the relative path through node_modules, so that the declaration emitter can produce a + // portability error. (See declarationEmitReexportedSymlinkReference3) + relativeSpecifiers = append(relativeSpecifiers, local); + } } + } - if (relativePreference === RelativePreference.ExternalNonRelative) { - const projectDirectory = compilerOptions.configFilePath ? - toPath(getDirectoryPath(compilerOptions.configFilePath), host.getCurrentDirectory(), info.getCanonicalFileName) : - info.getCanonicalFileName(host.getCurrentDirectory()); - const modulePath = toPath(moduleFileName, projectDirectory, getCanonicalFileName); - const sourceIsInternal = startsWith(sourceDirectory, projectDirectory); - const targetIsInternal = startsWith(modulePath, projectDirectory); - if (sourceIsInternal && !targetIsInternal || !sourceIsInternal && targetIsInternal) { - // 1. The import path crosses the boundary of the tsconfig.json-containing directory. - // - // src/ - // tsconfig.json - // index.ts ------- - // lib/ | (path crosses tsconfig.json) - // imported.ts <--- - // - return nonRelative; - } + return pathsSpecifiers?.length ? pathsSpecifiers : + nodeModulesSpecifiers?.length ? nodeModulesSpecifiers : + Debug.checkDefined(relativeSpecifiers); +} - const nearestTargetPackageJson = getNearestAncestorDirectoryWithPackageJson(host, getDirectoryPath(modulePath)); - const nearestSourcePackageJson = getNearestAncestorDirectoryWithPackageJson(host, sourceDirectory); - if (nearestSourcePackageJson !== nearestTargetPackageJson) { - // 2. The importing and imported files are part of different packages. - // - // packages/a/ - // package.json - // index.ts -------- - // packages/b/ | (path crosses package.json) - // package.json | - // component.ts <--- - // - return nonRelative; - } +interface Info { + readonly getCanonicalFileName: GetCanonicalFileName; + readonly importingSourceFileName: Path + readonly sourceDirectory: Path; +} +// importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path +function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Info { + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true); + const sourceDirectory = getDirectoryPath(importingSourceFileName); + return { getCanonicalFileName, importingSourceFileName, sourceDirectory }; +} - return relativePath; +function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: SourceFile["impliedNodeFormat"], { ending, relativePreference }: Preferences): string { + const { baseUrl, paths, rootDirs } = compilerOptions; + const { sourceDirectory, getCanonicalFileName } = info; + const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, ending, compilerOptions) || + removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), ending, compilerOptions); + if (!baseUrl && !paths || relativePreference === RelativePreference.Relative) { + return relativePath; + } + + const baseDirectory = getNormalizedAbsolutePath(getPathsBasePath(compilerOptions, host) || baseUrl!, host.getCurrentDirectory()); + const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseDirectory, getCanonicalFileName); + if (!relativeToBaseUrl) { + return relativePath; + } + + const fromPaths = paths && tryGetModuleNameFromPaths(relativeToBaseUrl, paths, getAllowedEndings(ending, compilerOptions, importMode), host, compilerOptions); + const nonRelative = fromPaths === undefined && baseUrl !== undefined ? removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions) : fromPaths; + if (!nonRelative) { + return relativePath; + } + + if (relativePreference === RelativePreference.NonRelative) { + return nonRelative; + } + + if (relativePreference === RelativePreference.ExternalNonRelative) { + const projectDirectory = compilerOptions.configFilePath ? + toPath(getDirectoryPath(compilerOptions.configFilePath), host.getCurrentDirectory(), info.getCanonicalFileName) : + info.getCanonicalFileName(host.getCurrentDirectory()); + const modulePath = toPath(moduleFileName, projectDirectory, getCanonicalFileName); + const sourceIsInternal = startsWith(sourceDirectory, projectDirectory); + const targetIsInternal = startsWith(modulePath, projectDirectory); + if (sourceIsInternal && !targetIsInternal || !sourceIsInternal && targetIsInternal) { + // 1. The import path crosses the boundary of the tsconfig.json-containing directory. + // + // src/ + // tsconfig.json + // index.ts ------- + // lib/ | (path crosses tsconfig.json) + // imported.ts <--- + // + return nonRelative; } - if (relativePreference !== RelativePreference.Shortest) Debug.assertNever(relativePreference); + const nearestTargetPackageJson = getNearestAncestorDirectoryWithPackageJson(host, getDirectoryPath(modulePath)); + const nearestSourcePackageJson = getNearestAncestorDirectoryWithPackageJson(host, sourceDirectory); + if (nearestSourcePackageJson !== nearestTargetPackageJson) { + // 2. The importing and imported files are part of different packages. + // + // packages/a/ + // package.json + // index.ts -------- + // packages/b/ | (path crosses package.json) + // package.json | + // component.ts <--- + // + return nonRelative; + } - // Prefer a relative import over a baseUrl import if it has fewer components. - return isPathRelativeToParent(nonRelative) || countPathComponents(relativePath) < countPathComponents(nonRelative) ? relativePath : nonRelative; + return relativePath; } - export function countPathComponents(path: string): number { - let count = 0; - for (let i = startsWith(path, "./") ? 2 : 0; i < path.length; i++) { - if (path.charCodeAt(i) === CharacterCodes.slash) count++; - } - return count; - } + if (relativePreference !== RelativePreference.Shortest) Debug.assertNever(relativePreference); - function usesJsExtensionOnImports({ imports }: SourceFile): boolean { - return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSFileExtension(text) : undefined) || false; - } + // Prefer a relative import over a baseUrl import if it has fewer components. + return isPathRelativeToParent(nonRelative) || countPathComponents(relativePath) < countPathComponents(nonRelative) ? relativePath : nonRelative; +} - function comparePathsByRedirectAndNumberOfDirectorySeparators(a: ModulePath, b: ModulePath) { - return compareBooleans(b.isRedirect, a.isRedirect) || compareNumberOfDirectorySeparators(a.path, b.path); +/** @internal */ +export function countPathComponents(path: string): number { + let count = 0; + for (let i = startsWith(path, "./") ? 2 : 0; i < path.length; i++) { + if (path.charCodeAt(i) === CharacterCodes.slash) count++; } + return count; +} - function getNearestAncestorDirectoryWithPackageJson(host: ModuleSpecifierResolutionHost, fileName: string) { - if (host.getNearestAncestorDirectoryWithPackageJson) { - return host.getNearestAncestorDirectoryWithPackageJson(fileName); - } - return !!forEachAncestorDirectory(fileName, directory => { - return host.fileExists(combinePaths(directory, "package.json")) ? true : undefined; - }); - } +function usesJsExtensionOnImports({ imports }: SourceFile): boolean { + return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSFileExtension(text) : undefined) || false; +} - export function forEachFileNameOfModule( - importingFileName: string, - importedFileName: string, - host: ModuleSpecifierResolutionHost, - preferSymlinks: boolean, - cb: (fileName: string, isRedirect: boolean) => T | undefined - ): T | undefined { - const getCanonicalFileName = hostGetCanonicalFileName(host); - const cwd = host.getCurrentDirectory(); - const referenceRedirect = host.isSourceOfProjectReferenceRedirect(importedFileName) ? host.getProjectReferenceRedirect(importedFileName) : undefined; - const importedPath = toPath(importedFileName, cwd, getCanonicalFileName); - const redirects = host.redirectTargetsMap.get(importedPath) || emptyArray; - const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects]; - const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd)); - let shouldFilterIgnoredPaths = !every(targets, containsIgnoredPath); - - if (!preferSymlinks) { - // Symlinks inside ignored paths are already filtered out of the symlink cache, - // so we only need to remove them from the realpath filenames. - const result = forEach(targets, p => !(shouldFilterIgnoredPaths && containsIgnoredPath(p)) && cb(p, referenceRedirect === p)); - if (result) return result; - } +function comparePathsByRedirectAndNumberOfDirectorySeparators(a: ModulePath, b: ModulePath) { + return compareBooleans(b.isRedirect, a.isRedirect) || compareNumberOfDirectorySeparators(a.path, b.path); +} - const symlinkedDirectories = host.getSymlinkCache?.().getSymlinkedDirectoriesByRealpath(); - const fullImportedFileName = getNormalizedAbsolutePath(importedFileName, cwd); - const result = symlinkedDirectories && forEachAncestorDirectory(getDirectoryPath(fullImportedFileName), realPathDirectory => { - const symlinkDirectories = symlinkedDirectories.get(ensureTrailingDirectorySeparator(toPath(realPathDirectory, cwd, getCanonicalFileName))); - if (!symlinkDirectories) return undefined; // Continue to ancestor directory +function getNearestAncestorDirectoryWithPackageJson(host: ModuleSpecifierResolutionHost, fileName: string) { + if (host.getNearestAncestorDirectoryWithPackageJson) { + return host.getNearestAncestorDirectoryWithPackageJson(fileName); + } + return !!forEachAncestorDirectory(fileName, directory => { + return host.fileExists(combinePaths(directory, "package.json")) ? true : undefined; + }); +} - // Don't want to a package to globally import from itself (importNameCodeFix_symlink_own_package.ts) - if (startsWithDirectory(importingFileName, realPathDirectory, getCanonicalFileName)) { - return false; // Stop search, each ancestor directory will also hit this condition +/** @internal */ +export function forEachFileNameOfModule( + importingFileName: string, + importedFileName: string, + host: ModuleSpecifierResolutionHost, + preferSymlinks: boolean, + cb: (fileName: string, isRedirect: boolean) => T | undefined +): T | undefined { + const getCanonicalFileName = hostGetCanonicalFileName(host); + const cwd = host.getCurrentDirectory(); + const referenceRedirect = host.isSourceOfProjectReferenceRedirect(importedFileName) ? host.getProjectReferenceRedirect(importedFileName) : undefined; + const importedPath = toPath(importedFileName, cwd, getCanonicalFileName); + const redirects = host.redirectTargetsMap.get(importedPath) || emptyArray; + const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects]; + const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd)); + let shouldFilterIgnoredPaths = !every(targets, containsIgnoredPath); + + if (!preferSymlinks) { + // Symlinks inside ignored paths are already filtered out of the symlink cache, + // so we only need to remove them from the realpath filenames. + const result = forEach(targets, p => !(shouldFilterIgnoredPaths && containsIgnoredPath(p)) && cb(p, referenceRedirect === p)); + if (result) return result; + } + + const symlinkedDirectories = host.getSymlinkCache?.().getSymlinkedDirectoriesByRealpath(); + const fullImportedFileName = getNormalizedAbsolutePath(importedFileName, cwd); + const result = symlinkedDirectories && forEachAncestorDirectory(getDirectoryPath(fullImportedFileName), realPathDirectory => { + const symlinkDirectories = symlinkedDirectories.get(ensureTrailingDirectorySeparator(toPath(realPathDirectory, cwd, getCanonicalFileName))); + if (!symlinkDirectories) return undefined; // Continue to ancestor directory + + // Don't want to a package to globally import from itself (importNameCodeFix_symlink_own_package.ts) + if (startsWithDirectory(importingFileName, realPathDirectory, getCanonicalFileName)) { + return false; // Stop search, each ancestor directory will also hit this condition + } + + return forEach(targets, target => { + if (!startsWithDirectory(target, realPathDirectory, getCanonicalFileName)) { + return; } - return forEach(targets, target => { - if (!startsWithDirectory(target, realPathDirectory, getCanonicalFileName)) { - return; - } - - const relative = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName); - for (const symlinkDirectory of symlinkDirectories) { - const option = resolvePath(symlinkDirectory, relative); - const result = cb(option, target === referenceRedirect); - shouldFilterIgnoredPaths = true; // We found a non-ignored path in symlinks, so we can reject ignored-path realpaths - if (result) return result; - } - }); + const relative = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName); + for (const symlinkDirectory of symlinkDirectories) { + const option = resolvePath(symlinkDirectory, relative); + const result = cb(option, target === referenceRedirect); + shouldFilterIgnoredPaths = true; // We found a non-ignored path in symlinks, so we can reject ignored-path realpaths + if (result) return result; + } }); - return result || (preferSymlinks - ? forEach(targets, p => shouldFilterIgnoredPaths && containsIgnoredPath(p) ? undefined : cb(p, p === referenceRedirect)) - : undefined); - } + }); + return result || (preferSymlinks + ? forEach(targets, p => shouldFilterIgnoredPaths && containsIgnoredPath(p) ? undefined : cb(p, p === referenceRedirect)) + : undefined); +} - /** - * Looks for existing imports that use symlinks to this module. - * Symlinks will be returned first so they are preferred over the real path. - */ - function getAllModulePaths( - importingFilePath: Path, - importedFileName: string, - host: ModuleSpecifierResolutionHost, - preferences: UserPreferences, - options: ModuleSpecifierOptions = {}, +/** + * Looks for existing imports that use symlinks to this module. + * Symlinks will be returned first so they are preferred over the real path. + */ +function getAllModulePaths( + importingFilePath: Path, + importedFileName: string, + host: ModuleSpecifierResolutionHost, + preferences: UserPreferences, + options: ModuleSpecifierOptions = {}, +) { + const importedFilePath = toPath(importedFileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host)); + const cache = host.getModuleSpecifierCache?.(); + if (cache) { + const cached = cache.get(importingFilePath, importedFilePath, preferences, options); + if (cached?.modulePaths) return cached.modulePaths; + } + const modulePaths = getAllModulePathsWorker(importingFilePath, importedFileName, host); + if (cache) { + cache.setModulePaths(importingFilePath, importedFilePath, preferences, options, modulePaths); + } + return modulePaths; +} + +function getAllModulePathsWorker(importingFileName: Path, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly ModulePath[] { + const getCanonicalFileName = hostGetCanonicalFileName(host); + const allFileNames = new Map(); + let importedFileFromNodeModules = false; + forEachFileNameOfModule( + importingFileName, + importedFileName, + host, + /*preferSymlinks*/ true, + (path, isRedirect) => { + const isInNodeModules = pathContainsNodeModules(path); + allFileNames.set(path, { path: getCanonicalFileName(path), isRedirect, isInNodeModules }); + importedFileFromNodeModules = importedFileFromNodeModules || isInNodeModules; + // don't return value, so we collect everything + } + ); + + // Sort by paths closest to importing file Name directory + const sortedPaths: ModulePath[] = []; + for ( + let directory = getDirectoryPath(importingFileName); + allFileNames.size !== 0; ) { - const importedFilePath = toPath(importedFileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host)); - const cache = host.getModuleSpecifierCache?.(); - if (cache) { - const cached = cache.get(importingFilePath, importedFilePath, preferences, options); - if (cached?.modulePaths) return cached.modulePaths; - } - const modulePaths = getAllModulePathsWorker(importingFilePath, importedFileName, host); - if (cache) { - cache.setModulePaths(importingFilePath, importedFilePath, preferences, options, modulePaths); - } - return modulePaths; - } - - function getAllModulePathsWorker(importingFileName: Path, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly ModulePath[] { - const getCanonicalFileName = hostGetCanonicalFileName(host); - const allFileNames = new Map(); - let importedFileFromNodeModules = false; - forEachFileNameOfModule( - importingFileName, - importedFileName, - host, - /*preferSymlinks*/ true, - (path, isRedirect) => { - const isInNodeModules = pathContainsNodeModules(path); - allFileNames.set(path, { path: getCanonicalFileName(path), isRedirect, isInNodeModules }); - importedFileFromNodeModules = importedFileFromNodeModules || isInNodeModules; - // don't return value, so we collect everything + const directoryStart = ensureTrailingDirectorySeparator(directory); + let pathsInDirectory: ModulePath[] | undefined; + allFileNames.forEach(({ path, isRedirect, isInNodeModules }, fileName) => { + if (startsWith(path, directoryStart)) { + (pathsInDirectory ||= []).push({ path: fileName, isRedirect, isInNodeModules }); + allFileNames.delete(fileName); } - ); - - // Sort by paths closest to importing file Name directory - const sortedPaths: ModulePath[] = []; - for ( - let directory = getDirectoryPath(importingFileName); - allFileNames.size !== 0; - ) { - const directoryStart = ensureTrailingDirectorySeparator(directory); - let pathsInDirectory: ModulePath[] | undefined; - allFileNames.forEach(({ path, isRedirect, isInNodeModules }, fileName) => { - if (startsWith(path, directoryStart)) { - (pathsInDirectory ||= []).push({ path: fileName, isRedirect, isInNodeModules }); - allFileNames.delete(fileName); - } - }); - if (pathsInDirectory) { - if (pathsInDirectory.length > 1) { - pathsInDirectory.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); - } - sortedPaths.push(...pathsInDirectory); + }); + if (pathsInDirectory) { + if (pathsInDirectory.length > 1) { + pathsInDirectory.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); } - const newDirectory = getDirectoryPath(directory); - if (newDirectory === directory) break; - directory = newDirectory; - } - if (allFileNames.size) { - const remainingPaths = arrayFrom(allFileNames.values()); - if (remainingPaths.length > 1) remainingPaths.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); - sortedPaths.push(...remainingPaths); + sortedPaths.push(...pathsInDirectory); } - - return sortedPaths; + const newDirectory = getDirectoryPath(directory); + if (newDirectory === directory) break; + directory = newDirectory; + } + if (allFileNames.size) { + const remainingPaths = arrayFrom(allFileNames.values()); + if (remainingPaths.length > 1) remainingPaths.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); + sortedPaths.push(...remainingPaths); } - function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol, checker: TypeChecker): string | undefined { - const decl = moduleSymbol.declarations?.find( - d => isNonGlobalAmbientModule(d) && (!isExternalModuleAugmentation(d) || !isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(d.name))) - ) as (ModuleDeclaration & { name: StringLiteral }) | undefined; - if (decl) { - return decl.name.text; - } + return sortedPaths; +} - // the module could be a namespace, which is export through "export=" from an ambient module. - /** - * declare module "m" { - * namespace ns { - * class c {} - * } - * export = ns; - * } - */ - // `import {c} from "m";` is valid, in which case, `moduleSymbol` is "ns", but the module name should be "m" - const ambientModuleDeclareCandidates = mapDefined(moduleSymbol.declarations, - d => { - if (!isModuleDeclaration(d)) return; - const topNamespace = getTopNamespace(d); - if (!(topNamespace?.parent?.parent - && isModuleBlock(topNamespace.parent) && isAmbientModule(topNamespace.parent.parent) && isSourceFile(topNamespace.parent.parent.parent))) return; - const exportAssignment = ((topNamespace.parent.parent.symbol.exports?.get("export=" as __String)?.valueDeclaration as ExportAssignment)?.expression as PropertyAccessExpression | Identifier); - if (!exportAssignment) return; - const exportSymbol = checker.getSymbolAtLocation(exportAssignment); - if (!exportSymbol) return; - const originalExportSymbol = exportSymbol?.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(exportSymbol) : exportSymbol; - if (originalExportSymbol === d.symbol) return topNamespace.parent.parent; - - function getTopNamespace(namespaceDeclaration: ModuleDeclaration) { - while (namespaceDeclaration.flags & NodeFlags.NestedNamespace) { - namespaceDeclaration = namespaceDeclaration.parent as ModuleDeclaration; - } - return namespaceDeclaration; +function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol, checker: TypeChecker): string | undefined { + const decl = moduleSymbol.declarations?.find( + d => isNonGlobalAmbientModule(d) && (!isExternalModuleAugmentation(d) || !isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(d.name))) + ) as (ModuleDeclaration & { name: StringLiteral }) | undefined; + if (decl) { + return decl.name.text; + } + + // the module could be a namespace, which is export through "export=" from an ambient module. + /** + * declare module "m" { + * namespace ns { + * class c {} + * } + * export = ns; + * } + */ + // `import {c} from "m";` is valid, in which case, `moduleSymbol` is "ns", but the module name should be "m" + const ambientModuleDeclareCandidates = mapDefined(moduleSymbol.declarations, + d => { + if (!isModuleDeclaration(d)) return; + const topNamespace = getTopNamespace(d); + if (!(topNamespace?.parent?.parent + && isModuleBlock(topNamespace.parent) && isAmbientModule(topNamespace.parent.parent) && isSourceFile(topNamespace.parent.parent.parent))) return; + const exportAssignment = ((topNamespace.parent.parent.symbol.exports?.get("export=" as __String)?.valueDeclaration as ExportAssignment)?.expression as PropertyAccessExpression | Identifier); + if (!exportAssignment) return; + const exportSymbol = checker.getSymbolAtLocation(exportAssignment); + if (!exportSymbol) return; + const originalExportSymbol = exportSymbol?.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(exportSymbol) : exportSymbol; + if (originalExportSymbol === d.symbol) return topNamespace.parent.parent; + + function getTopNamespace(namespaceDeclaration: ModuleDeclaration) { + while (namespaceDeclaration.flags & NodeFlags.NestedNamespace) { + namespaceDeclaration = namespaceDeclaration.parent as ModuleDeclaration; } + return namespaceDeclaration; } - ); - const ambientModuleDeclare = ambientModuleDeclareCandidates[0] as (AmbientModuleDeclaration & { name: StringLiteral }) | undefined; - if (ambientModuleDeclare) { - return ambientModuleDeclare.name.text; } + ); + const ambientModuleDeclare = ambientModuleDeclareCandidates[0] as (AmbientModuleDeclaration & { name: StringLiteral }) | undefined; + if (ambientModuleDeclare) { + return ambientModuleDeclare.name.text; } +} - function getAllowedEndings(preferredEnding: Ending, compilerOptions: CompilerOptions, importMode: SourceFile["impliedNodeFormat"]) { - if (getEmitModuleResolutionKind(compilerOptions) >= ModuleResolutionKind.Node16 && importMode === ModuleKind.ESNext) { - return [Ending.JsExtension]; - } - switch (preferredEnding) { - case Ending.JsExtension: return [Ending.JsExtension, Ending.Minimal, Ending.Index]; - case Ending.Index: return [Ending.Index, Ending.Minimal, Ending.JsExtension]; - case Ending.Minimal: return [Ending.Minimal, Ending.Index, Ending.JsExtension]; - default: Debug.assertNever(preferredEnding); - } +function getAllowedEndings(preferredEnding: Ending, compilerOptions: CompilerOptions, importMode: SourceFile["impliedNodeFormat"]) { + if (getEmitModuleResolutionKind(compilerOptions) >= ModuleResolutionKind.Node16 && importMode === ModuleKind.ESNext) { + return [Ending.JsExtension]; + } + switch (preferredEnding) { + case Ending.JsExtension: return [Ending.JsExtension, Ending.Minimal, Ending.Index]; + case Ending.Index: return [Ending.Index, Ending.Minimal, Ending.JsExtension]; + case Ending.Minimal: return [Ending.Minimal, Ending.Index, Ending.JsExtension]; + default: Debug.assertNever(preferredEnding); } +} - function tryGetModuleNameFromPaths(relativeToBaseUrl: string, paths: MapLike, allowedEndings: Ending[], host: ModuleSpecifierResolutionHost, compilerOptions: CompilerOptions): string | undefined { - for (const key in paths) { - for (const patternText of paths[key]) { - const pattern = normalizePath(patternText); - const indexOfStar = pattern.indexOf("*"); - // In module resolution, if `pattern` itself has an extension, a file with that extension is looked up directly, - // meaning a '.ts' or '.d.ts' extension is allowed to resolve. This is distinct from the case where a '*' substitution - // causes a module specifier to have an extension, i.e. the extension comes from the module specifier in a JS/TS file - // and matches the '*'. For example: - // - // Module Specifier | Path Mapping (key: [pattern]) | Interpolation | Resolution Action - // ---------------------->------------------------------->--------------------->--------------------------------------------------------------- - // import "@app/foo" -> "@app/*": ["./src/app/*.ts"] -> "./src/app/foo.ts" -> tryFile("./src/app/foo.ts") || [continue resolution algorithm] - // import "@app/foo.ts" -> "@app/*": ["./src/app/*"] -> "./src/app/foo.ts" -> [continue resolution algorithm] - // - // (https://github.com/microsoft/TypeScript/blob/ad4ded80e1d58f0bf36ac16bea71bc10d9f09895/src/compiler/moduleNameResolver.ts#L2509-L2516) - // - // The interpolation produced by both scenarios is identical, but only in the former, where the extension is encoded in - // the path mapping rather than in the module specifier, will we prioritize a file lookup on the interpolation result. - // (In fact, currently, the latter scenario will necessarily fail since no resolution mode recognizes '.ts' as a valid - // extension for a module specifier.) - // - // Here, this means we need to be careful about whether we generate a match from the target filename (typically with a - // .ts extension) or the possible relative module specifiers representing that file: - // - // Filename | Relative Module Specifier Candidates | Path Mapping | Filename Result | Module Specifier Results - // --------------------<----------------------------------------------<------------------------------<-------------------||---------------------------- - // dist/haha.d.ts <- dist/haha, dist/haha.js <- "@app/*": ["./dist/*.d.ts"] <- @app/haha || (none) - // dist/haha.d.ts <- dist/haha, dist/haha.js <- "@app/*": ["./dist/*"] <- (none) || @app/haha, @app/haha.js - // dist/foo/index.d.ts <- dist/foo, dist/foo/index, dist/foo/index.js <- "@app/*": ["./dist/*.d.ts"] <- @app/foo/index || (none) - // dist/foo/index.d.ts <- dist/foo, dist/foo/index, dist/foo/index.js <- "@app/*": ["./dist/*"] <- (none) || @app/foo, @app/foo/index, @app/foo/index.js - // dist/wow.js.js <- dist/wow.js, dist/wow.js.js <- "@app/*": ["./dist/*.js"] <- @app/wow.js || @app/wow, @app/wow.js - // - // The "Filename Result" can be generated only if `pattern` has an extension. Care must be taken that the list of - // relative module specifiers to run the interpolation (a) is actually valid for the module resolution mode, (b) takes - // into account the existence of other files (e.g. 'dist/wow.js' cannot refer to 'dist/wow.js.js' if 'dist/wow.js' - // exists) and (c) that they are ordered by preference. The last row shows that the filename result and module - // specifier results are not mutually exclusive. Note that the filename result is a higher priority in module - // resolution, but as long criteria (b) above is met, I don't think its result needs to be the highest priority result - // in module specifier generation. I have included it last, as it's difficult to tell exactly where it should be - // sorted among the others for a particular value of `importModuleSpecifierEnding`. - const candidates: { ending: Ending | undefined, value: string }[] = allowedEndings.map(ending => ({ - ending, - value: removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions) - })); - if (tryGetExtensionFromPath(pattern)) { - candidates.push({ ending: undefined, value: relativeToBaseUrl }); - } +function tryGetModuleNameFromPaths(relativeToBaseUrl: string, paths: MapLike, allowedEndings: Ending[], host: ModuleSpecifierResolutionHost, compilerOptions: CompilerOptions): string | undefined { + for (const key in paths) { + for (const patternText of paths[key]) { + const pattern = normalizePath(patternText); + const indexOfStar = pattern.indexOf("*"); + // In module resolution, if `pattern` itself has an extension, a file with that extension is looked up directly, + // meaning a '.ts' or '.d.ts' extension is allowed to resolve. This is distinct from the case where a '*' substitution + // causes a module specifier to have an extension, i.e. the extension comes from the module specifier in a JS/TS file + // and matches the '*'. For example: + // + // Module Specifier | Path Mapping (key: [pattern]) | Interpolation | Resolution Action + // ---------------------->------------------------------->--------------------->--------------------------------------------------------------- + // import "@app/foo" -> "@app/*": ["./src/app/*.ts"] -> "./src/app/foo.ts" -> tryFile("./src/app/foo.ts") || [continue resolution algorithm] + // import "@app/foo.ts" -> "@app/*": ["./src/app/*"] -> "./src/app/foo.ts" -> [continue resolution algorithm] + // + // (https://github.com/microsoft/TypeScript/blob/ad4ded80e1d58f0bf36ac16bea71bc10d9f09895/src/compiler/moduleNameResolver.ts#L2509-L2516) + // + // The interpolation produced by both scenarios is identical, but only in the former, where the extension is encoded in + // the path mapping rather than in the module specifier, will we prioritize a file lookup on the interpolation result. + // (In fact, currently, the latter scenario will necessarily fail since no resolution mode recognizes '.ts' as a valid + // extension for a module specifier.) + // + // Here, this means we need to be careful about whether we generate a match from the target filename (typically with a + // .ts extension) or the possible relative module specifiers representing that file: + // + // Filename | Relative Module Specifier Candidates | Path Mapping | Filename Result | Module Specifier Results + // --------------------<----------------------------------------------<------------------------------<-------------------||---------------------------- + // dist/haha.d.ts <- dist/haha, dist/haha.js <- "@app/*": ["./dist/*.d.ts"] <- @app/haha || (none) + // dist/haha.d.ts <- dist/haha, dist/haha.js <- "@app/*": ["./dist/*"] <- (none) || @app/haha, @app/haha.js + // dist/foo/index.d.ts <- dist/foo, dist/foo/index, dist/foo/index.js <- "@app/*": ["./dist/*.d.ts"] <- @app/foo/index || (none) + // dist/foo/index.d.ts <- dist/foo, dist/foo/index, dist/foo/index.js <- "@app/*": ["./dist/*"] <- (none) || @app/foo, @app/foo/index, @app/foo/index.js + // dist/wow.js.js <- dist/wow.js, dist/wow.js.js <- "@app/*": ["./dist/*.js"] <- @app/wow.js || @app/wow, @app/wow.js + // + // The "Filename Result" can be generated only if `pattern` has an extension. Care must be taken that the list of + // relative module specifiers to run the interpolation (a) is actually valid for the module resolution mode, (b) takes + // into account the existence of other files (e.g. 'dist/wow.js' cannot refer to 'dist/wow.js.js' if 'dist/wow.js' + // exists) and (c) that they are ordered by preference. The last row shows that the filename result and module + // specifier results are not mutually exclusive. Note that the filename result is a higher priority in module + // resolution, but as long criteria (b) above is met, I don't think its result needs to be the highest priority result + // in module specifier generation. I have included it last, as it's difficult to tell exactly where it should be + // sorted among the others for a particular value of `importModuleSpecifierEnding`. + const candidates: { ending: Ending | undefined, value: string }[] = allowedEndings.map(ending => ({ + ending, + value: removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions) + })); + if (tryGetExtensionFromPath(pattern)) { + candidates.push({ ending: undefined, value: relativeToBaseUrl }); + } - if (indexOfStar !== -1) { - const prefix = pattern.substring(0, indexOfStar); - const suffix = pattern.substring(indexOfStar + 1); - for (const { ending, value } of candidates) { - if (value.length >= prefix.length + suffix.length && - startsWith(value, prefix) && - endsWith(value, suffix) && - validateEnding({ ending, value }) - ) { - const matchedStar = value.substring(prefix.length, value.length - suffix.length); - return key.replace("*", matchedStar); - } + if (indexOfStar !== -1) { + const prefix = pattern.substring(0, indexOfStar); + const suffix = pattern.substring(indexOfStar + 1); + for (const { ending, value } of candidates) { + if (value.length >= prefix.length + suffix.length && + startsWith(value, prefix) && + endsWith(value, suffix) && + validateEnding({ ending, value }) + ) { + const matchedStar = value.substring(prefix.length, value.length - suffix.length); + return key.replace("*", matchedStar); } } - else if ( - some(candidates, c => c.ending !== Ending.Minimal && pattern === c.value) || - some(candidates, c => c.ending === Ending.Minimal && pattern === c.value && validateEnding(c)) - ) { - return key; - } } - } - - function validateEnding({ ending, value }: { ending: Ending | undefined, value: string }) { - // Optimization: `removeExtensionAndIndexPostFix` can query the file system (a good bit) if `ending` is `Minimal`, the basename - // is 'index', and a `host` is provided. To avoid that until it's unavoidable, we ran the function with no `host` above. Only - // here, after we've checked that the minimal ending is indeed a match (via the length and prefix/suffix checks / `some` calls), - // do we check that the host-validated result is consistent with the answer we got before. If it's not, it falls back to the - // `Ending.Index` result, which should already be in the list of candidates if `Minimal` was. (Note: the assumption here is - // that every module resolution mode that supports dropping extensions also supports dropping `/index`. Like literally - // everything else in this file, this logic needs to be updated if that's not true in some future module resolution mode.) - return ending !== Ending.Minimal || value === removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions, host); + else if ( + some(candidates, c => c.ending !== Ending.Minimal && pattern === c.value) || + some(candidates, c => c.ending === Ending.Minimal && pattern === c.value && validateEnding(c)) + ) { + return key; + } } } - const enum MatchingMode { - Exact, - Directory, - Pattern + function validateEnding({ ending, value }: { ending: Ending | undefined, value: string }) { + // Optimization: `removeExtensionAndIndexPostFix` can query the file system (a good bit) if `ending` is `Minimal`, the basename + // is 'index', and a `host` is provided. To avoid that until it's unavoidable, we ran the function with no `host` above. Only + // here, after we've checked that the minimal ending is indeed a match (via the length and prefix/suffix checks / `some` calls), + // do we check that the host-validated result is consistent with the answer we got before. If it's not, it falls back to the + // `Ending.Index` result, which should already be in the list of candidates if `Minimal` was. (Note: the assumption here is + // that every module resolution mode that supports dropping extensions also supports dropping `/index`. Like literally + // everything else in this file, this logic needs to be updated if that's not true in some future module resolution mode.) + return ending !== Ending.Minimal || value === removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions, host); } +} - function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode = MatchingMode.Exact): { moduleFileToTry: string } | undefined { - if (typeof exports === "string") { - const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); - const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; - switch (mode) { - case MatchingMode.Exact: - if (comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || (extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo)) { - return { moduleFileToTry: packageName }; - } - break; - case MatchingMode.Directory: - if (containsPath(pathOrPattern, targetFilePath)) { - const fragment = getRelativePathFromDirectory(pathOrPattern, targetFilePath, /*ignoreCase*/ false); - return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; - } - break; - case MatchingMode.Pattern: - const starPos = pathOrPattern.indexOf("*"); - const leadingSlice = pathOrPattern.slice(0, starPos); - const trailingSlice = pathOrPattern.slice(starPos + 1); - if (startsWith(targetFilePath, leadingSlice) && endsWith(targetFilePath, trailingSlice)) { - const starReplacement = targetFilePath.slice(leadingSlice.length, targetFilePath.length - trailingSlice.length); - return { moduleFileToTry: packageName.replace("*", starReplacement) }; - } - if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) { - const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length); - return { moduleFileToTry: packageName.replace("*", starReplacement) }; - } - break; - } - } - else if (Array.isArray(exports)) { - return forEach(exports, e => tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, e, conditions)); +const enum MatchingMode { + Exact, + Directory, + Pattern +} + +function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode = MatchingMode.Exact): { moduleFileToTry: string } | undefined { + if (typeof exports === "string") { + const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); + const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; + switch (mode) { + case MatchingMode.Exact: + if (comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || (extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo)) { + return { moduleFileToTry: packageName }; + } + break; + case MatchingMode.Directory: + if (containsPath(pathOrPattern, targetFilePath)) { + const fragment = getRelativePathFromDirectory(pathOrPattern, targetFilePath, /*ignoreCase*/ false); + return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; + } + break; + case MatchingMode.Pattern: + const starPos = pathOrPattern.indexOf("*"); + const leadingSlice = pathOrPattern.slice(0, starPos); + const trailingSlice = pathOrPattern.slice(starPos + 1); + if (startsWith(targetFilePath, leadingSlice) && endsWith(targetFilePath, trailingSlice)) { + const starReplacement = targetFilePath.slice(leadingSlice.length, targetFilePath.length - trailingSlice.length); + return { moduleFileToTry: packageName.replace("*", starReplacement) }; + } + if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) { + const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length); + return { moduleFileToTry: packageName.replace("*", starReplacement) }; + } + break; + } + } + else if (Array.isArray(exports)) { + return forEach(exports, e => tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, e, conditions)); + } + else if (typeof exports === "object" && exports !== null) { // eslint-disable-line no-null/no-null + if (allKeysStartWithDot(exports as MapLike)) { + // sub-mappings + // 3 cases: + // * directory mappings (legacyish, key ends with / (technically allows index/extension resolution under cjs mode)) + // * pattern mappings (contains a *) + // * exact mappings (no *, does not end with /) + return forEach(getOwnKeys(exports as MapLike), k => { + const subPackageName = getNormalizedAbsolutePath(combinePaths(packageName, k), /*currentDirectory*/ undefined); + const mode = endsWith(k, "/") ? MatchingMode.Directory + : stringContains(k, "*") ? MatchingMode.Pattern + : MatchingMode.Exact; + return tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, subPackageName, (exports as MapLike)[k], conditions, mode); + }); } - else if (typeof exports === "object" && exports !== null) { // eslint-disable-line no-null/no-null - if (allKeysStartWithDot(exports as MapLike)) { - // sub-mappings - // 3 cases: - // * directory mappings (legacyish, key ends with / (technically allows index/extension resolution under cjs mode)) - // * pattern mappings (contains a *) - // * exact mappings (no *, does not end with /) - return forEach(getOwnKeys(exports as MapLike), k => { - const subPackageName = getNormalizedAbsolutePath(combinePaths(packageName, k), /*currentDirectory*/ undefined); - const mode = endsWith(k, "/") ? MatchingMode.Directory - : stringContains(k, "*") ? MatchingMode.Pattern - : MatchingMode.Exact; - return tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, subPackageName, (exports as MapLike)[k], conditions, mode); - }); - } - else { - // conditional mapping - for (const key of getOwnKeys(exports as MapLike)) { - if (key === "default" || conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(conditions, key)) { - const subTarget = (exports as MapLike)[key]; - const result = tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, subTarget, conditions); - if (result) { - return result; - } + else { + // conditional mapping + for (const key of getOwnKeys(exports as MapLike)) { + if (key === "default" || conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(conditions, key)) { + const subTarget = (exports as MapLike)[key]; + const result = tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, subTarget, conditions); + if (result) { + return result; } } } } + } + return undefined; +} + +function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, ending: Ending, compilerOptions: CompilerOptions): string | undefined { + const normalizedTargetPaths = getPathsRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName); + if (normalizedTargetPaths === undefined) { return undefined; } - function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, ending: Ending, compilerOptions: CompilerOptions): string | undefined { - const normalizedTargetPaths = getPathsRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName); - if (normalizedTargetPaths === undefined) { - return undefined; - } + const normalizedSourcePaths = getPathsRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName); + const relativePaths = flatMap(normalizedSourcePaths, sourcePath => { + return map(normalizedTargetPaths, targetPath => ensurePathIsNonModuleName(getRelativePathFromDirectory(sourcePath, targetPath, getCanonicalFileName))); + }); + const shortest = min(relativePaths, compareNumberOfDirectorySeparators); + if (!shortest) { + return undefined; + } - const normalizedSourcePaths = getPathsRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName); - const relativePaths = flatMap(normalizedSourcePaths, sourcePath => { - return map(normalizedTargetPaths, targetPath => ensurePathIsNonModuleName(getRelativePathFromDirectory(sourcePath, targetPath, getCanonicalFileName))); - }); - const shortest = min(relativePaths, compareNumberOfDirectorySeparators); - if (!shortest) { - return undefined; - } + return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs + ? removeExtensionAndIndexPostFix(shortest, ending, compilerOptions) + : removeFileExtension(shortest); +} - return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs - ? removeExtensionAndIndexPostFix(shortest, ending, compilerOptions) - : removeFileExtension(shortest); +function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile , host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ModuleKind.ESNext | ModuleKind.CommonJS): string | undefined { + if (!host.fileExists || !host.readFile) { + return undefined; + } + const parts: NodeModulePathParts = getNodeModulePathParts(path)!; + if (!parts) { + return undefined; } - function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile , host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ModuleKind.ESNext | ModuleKind.CommonJS): string | undefined { - if (!host.fileExists || !host.readFile) { - return undefined; - } - const parts: NodeModulePathParts = getNodeModulePathParts(path)!; - if (!parts) { - return undefined; - } - - // Simplify the full file path to something that can be resolved by Node. - - const preferences = getPreferences(host, userPreferences, options, importingSourceFile); - let moduleSpecifier = path; - let isPackageRootPath = false; - if (!packageNameOnly) { - let packageRootIndex = parts.packageRootIndex; - let moduleFileName: string | undefined; - while (true) { - // If the module could be imported by a directory name, use that directory's name - const { moduleFileToTry, packageRootPath, blockedByExports, verbatimFromExports } = tryDirectoryWithPackageJson(packageRootIndex); - if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Classic) { - if (blockedByExports) { - return undefined; // File is under this package.json, but is not publicly exported - there's no way to name it via `node_modules` resolution - } - if (verbatimFromExports) { - return moduleFileToTry; - } + // Simplify the full file path to something that can be resolved by Node. + + const preferences = getPreferences(host, userPreferences, options, importingSourceFile); + let moduleSpecifier = path; + let isPackageRootPath = false; + if (!packageNameOnly) { + let packageRootIndex = parts.packageRootIndex; + let moduleFileName: string | undefined; + while (true) { + // If the module could be imported by a directory name, use that directory's name + const { moduleFileToTry, packageRootPath, blockedByExports, verbatimFromExports } = tryDirectoryWithPackageJson(packageRootIndex); + if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Classic) { + if (blockedByExports) { + return undefined; // File is under this package.json, but is not publicly exported - there's no way to name it via `node_modules` resolution } - if (packageRootPath) { - moduleSpecifier = packageRootPath; - isPackageRootPath = true; - break; + if (verbatimFromExports) { + return moduleFileToTry; } - if (!moduleFileName) moduleFileName = moduleFileToTry; + } + if (packageRootPath) { + moduleSpecifier = packageRootPath; + isPackageRootPath = true; + break; + } + if (!moduleFileName) moduleFileName = moduleFileToTry; - // try with next level of directory - packageRootIndex = path.indexOf(directorySeparator, packageRootIndex + 1); - if (packageRootIndex === -1) { - moduleSpecifier = removeExtensionAndIndexPostFix(moduleFileName, preferences.ending, options, host); - break; - } + // try with next level of directory + packageRootIndex = path.indexOf(directorySeparator, packageRootIndex + 1); + if (packageRootIndex === -1) { + moduleSpecifier = removeExtensionAndIndexPostFix(moduleFileName, preferences.ending, options, host); + break; } } + } - if (isRedirect && !isPackageRootPath) { - return undefined; - } + if (isRedirect && !isPackageRootPath) { + return undefined; + } - const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation(); - // Get a path that's relative to node_modules or the importing file's path - // if node_modules folder is in this folder or any of its parent folders, no need to keep it. - const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex)); - if (!(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) { - return undefined; - } + const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation(); + // Get a path that's relative to node_modules or the importing file's path + // if node_modules folder is in this folder or any of its parent folders, no need to keep it. + const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex)); + if (!(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) { + return undefined; + } - // If the module was found in @types, get the actual Node package name - const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1); - const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName); - // For classic resolution, only allow importing from node_modules/@types, not other node_modules - return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Classic && packageName === nodeModulesDirectoryName ? undefined : packageName; - - function tryDirectoryWithPackageJson(packageRootIndex: number): { moduleFileToTry: string, packageRootPath?: string, blockedByExports?: true, verbatimFromExports?: true } { - const packageRootPath = path.substring(0, packageRootIndex); - const packageJsonPath = combinePaths(packageRootPath, "package.json"); - let moduleFileToTry = path; - let maybeBlockedByTypesVersions = false; - const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); - if (typeof cachedPackageJson === "object" || cachedPackageJson === undefined && host.fileExists(packageJsonPath)) { - const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || JSON.parse(host.readFile!(packageJsonPath)!); - const importMode = overrideMode || importingSourceFile.impliedNodeFormat; - if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) { - const conditions = ["node", importMode === ModuleKind.ESNext ? "import" : "require", "types"]; - const fromExports = packageJsonContent.exports && typeof packageJsonContent.name === "string" - ? tryGetModuleNameFromExports(options, path, packageRootPath, getPackageNameFromTypesPackageName(packageJsonContent.name), packageJsonContent.exports, conditions) - : undefined; - if (fromExports) { - const withJsExtension = !hasTSFileExtension(fromExports.moduleFileToTry) - ? fromExports - : { moduleFileToTry: removeFileExtension(fromExports.moduleFileToTry) + tryGetJSExtensionForFile(fromExports.moduleFileToTry, options) }; - return { ...withJsExtension, verbatimFromExports: true }; - } - if (packageJsonContent.exports) { - return { moduleFileToTry: path, blockedByExports: true }; - } - } - const versionPaths = packageJsonContent.typesVersions - ? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions) + // If the module was found in @types, get the actual Node package name + const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1); + const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName); + // For classic resolution, only allow importing from node_modules/@types, not other node_modules + return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Classic && packageName === nodeModulesDirectoryName ? undefined : packageName; + + function tryDirectoryWithPackageJson(packageRootIndex: number): { moduleFileToTry: string, packageRootPath?: string, blockedByExports?: true, verbatimFromExports?: true } { + const packageRootPath = path.substring(0, packageRootIndex); + const packageJsonPath = combinePaths(packageRootPath, "package.json"); + let moduleFileToTry = path; + let maybeBlockedByTypesVersions = false; + const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); + if (typeof cachedPackageJson === "object" || cachedPackageJson === undefined && host.fileExists(packageJsonPath)) { + const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || JSON.parse(host.readFile!(packageJsonPath)!); + const importMode = overrideMode || importingSourceFile.impliedNodeFormat; + if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) { + const conditions = ["node", importMode === ModuleKind.ESNext ? "import" : "require", "types"]; + const fromExports = packageJsonContent.exports && typeof packageJsonContent.name === "string" + ? tryGetModuleNameFromExports(options, path, packageRootPath, getPackageNameFromTypesPackageName(packageJsonContent.name), packageJsonContent.exports, conditions) : undefined; - if (versionPaths) { - const subModuleName = path.slice(packageRootPath.length + 1); - const fromPaths = tryGetModuleNameFromPaths( - subModuleName, - versionPaths.paths, - getAllowedEndings(preferences.ending, options, importMode), - host, - options - ); - if (fromPaths === undefined) { - maybeBlockedByTypesVersions = true; - } - else { - moduleFileToTry = combinePaths(packageRootPath, fromPaths); - } + if (fromExports) { + const withJsExtension = !hasTSFileExtension(fromExports.moduleFileToTry) + ? fromExports + : { moduleFileToTry: removeFileExtension(fromExports.moduleFileToTry) + tryGetJSExtensionForFile(fromExports.moduleFileToTry, options) }; + return { ...withJsExtension, verbatimFromExports: true }; } - // If the file is the main module, it can be imported by the package name - const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main || "index.js"; - if (isString(mainFileRelative) && !(maybeBlockedByTypesVersions && matchPatternOrExact(tryParsePatterns(versionPaths!.paths), mainFileRelative))) { - // The 'main' file is also subject to mapping through typesVersions, and we couldn't come up with a path - // explicitly through typesVersions, so if it matches a key in typesVersions now, it's not reachable. - // (The only way this can happen is if some file in a package that's not resolvable from outside the - // package got pulled into the program anyway, e.g. transitively through a file that *is* reachable. It - // happens very easily in fourslash tests though, since every test file listed gets included. See - // importNameCodeFix_typesVersions.ts for an example.) - const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName); - if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(moduleFileToTry))) { - // ^ An arbitrary removal of file extension for this comparison is almost certainly wrong - return { packageRootPath, moduleFileToTry }; - } + if (packageJsonContent.exports) { + return { moduleFileToTry: path, blockedByExports: true }; } } - else { - // No package.json exists; an index.js will still resolve as the package name - const fileName = getCanonicalFileName(moduleFileToTry.substring(parts.packageRootIndex + 1)); - if (fileName === "index.d.ts" || fileName === "index.js" || fileName === "index.ts" || fileName === "index.tsx") { - return { moduleFileToTry, packageRootPath }; + const versionPaths = packageJsonContent.typesVersions + ? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions) + : undefined; + if (versionPaths) { + const subModuleName = path.slice(packageRootPath.length + 1); + const fromPaths = tryGetModuleNameFromPaths( + subModuleName, + versionPaths.paths, + getAllowedEndings(preferences.ending, options, importMode), + host, + options + ); + if (fromPaths === undefined) { + maybeBlockedByTypesVersions = true; + } + else { + moduleFileToTry = combinePaths(packageRootPath, fromPaths); + } + } + // If the file is the main module, it can be imported by the package name + const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main || "index.js"; + if (isString(mainFileRelative) && !(maybeBlockedByTypesVersions && matchPatternOrExact(tryParsePatterns(versionPaths!.paths), mainFileRelative))) { + // The 'main' file is also subject to mapping through typesVersions, and we couldn't come up with a path + // explicitly through typesVersions, so if it matches a key in typesVersions now, it's not reachable. + // (The only way this can happen is if some file in a package that's not resolvable from outside the + // package got pulled into the program anyway, e.g. transitively through a file that *is* reachable. It + // happens very easily in fourslash tests though, since every test file listed gets included. See + // importNameCodeFix_typesVersions.ts for an example.) + const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName); + if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(moduleFileToTry))) { + // ^ An arbitrary removal of file extension for this comparison is almost certainly wrong + return { packageRootPath, moduleFileToTry }; } } - return { moduleFileToTry }; } - } - - function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) { - if (!host.fileExists) return; - // We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory - const extensions = flatten(getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }])); - for (const e of extensions) { - const fullPath = path + e; - if (host.fileExists(fullPath)) { - return fullPath; + else { + // No package.json exists; an index.js will still resolve as the package name + const fileName = getCanonicalFileName(moduleFileToTry.substring(parts.packageRootIndex + 1)); + if (fileName === "index.d.ts" || fileName === "index.js" || fileName === "index.ts" || fileName === "index.tsx") { + return { moduleFileToTry, packageRootPath }; } } + return { moduleFileToTry }; } +} - function getPathsRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string[] | undefined { - return mapDefined(rootDirs, rootDir => { - const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName); - return relativePath !== undefined && isPathRelativeToParent(relativePath) ? undefined : relativePath; - }); +function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) { + if (!host.fileExists) return; + // We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory + const extensions = flatten(getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }])); + for (const e of extensions) { + const fullPath = path + e; + if (host.fileExists(fullPath)) { + return fullPath; + } } +} - function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions, host?: ModuleSpecifierResolutionHost): string { - if (fileExtensionIsOneOf(fileName, [Extension.Json, Extension.Mjs, Extension.Cjs])) return fileName; - const noExtension = removeFileExtension(fileName); - if (fileName === noExtension) return fileName; - if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) return noExtension + getJSExtensionForFile(fileName, options); - switch (ending) { - case Ending.Minimal: - const withoutIndex = removeSuffix(noExtension, "/index"); - if (host && withoutIndex !== noExtension && tryGetAnyFileFromPath(host, withoutIndex)) { - // Can't remove index if there's a file by the same name as the directory. - // Probably more callers should pass `host` so we can determine this? - return noExtension; - } - return withoutIndex; - case Ending.Index: +function getPathsRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string[] | undefined { + return mapDefined(rootDirs, rootDir => { + const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName); + return relativePath !== undefined && isPathRelativeToParent(relativePath) ? undefined : relativePath; + }); +} + +function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions, host?: ModuleSpecifierResolutionHost): string { + if (fileExtensionIsOneOf(fileName, [Extension.Json, Extension.Mjs, Extension.Cjs])) return fileName; + const noExtension = removeFileExtension(fileName); + if (fileName === noExtension) return fileName; + if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) return noExtension + getJSExtensionForFile(fileName, options); + switch (ending) { + case Ending.Minimal: + const withoutIndex = removeSuffix(noExtension, "/index"); + if (host && withoutIndex !== noExtension && tryGetAnyFileFromPath(host, withoutIndex)) { + // Can't remove index if there's a file by the same name as the directory. + // Probably more callers should pass `host` so we can determine this? return noExtension; - case Ending.JsExtension: - return noExtension + getJSExtensionForFile(fileName, options); - default: - return Debug.assertNever(ending); - } + } + return withoutIndex; + case Ending.Index: + return noExtension; + case Ending.JsExtension: + return noExtension + getJSExtensionForFile(fileName, options); + default: + return Debug.assertNever(ending); } +} - function getJSExtensionForFile(fileName: string, options: CompilerOptions): Extension { - return tryGetJSExtensionForFile(fileName, options) ?? Debug.fail(`Extension ${extensionFromPath(fileName)} is unsupported:: FileName:: ${fileName}`); - } - - export function tryGetJSExtensionForFile(fileName: string, options: CompilerOptions): Extension | undefined { - const ext = tryGetExtensionFromPath(fileName); - switch (ext) { - case Extension.Ts: - case Extension.Dts: - return Extension.Js; - case Extension.Tsx: - return options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js; - case Extension.Js: - case Extension.Jsx: - case Extension.Json: - return ext; - case Extension.Dmts: - case Extension.Mts: - case Extension.Mjs: - return Extension.Mjs; - case Extension.Dcts: - case Extension.Cts: - case Extension.Cjs: - return Extension.Cjs; - default: - return undefined; - } - } +function getJSExtensionForFile(fileName: string, options: CompilerOptions): Extension { + return tryGetJSExtensionForFile(fileName, options) ?? Debug.fail(`Extension ${extensionFromPath(fileName)} is unsupported:: FileName:: ${fileName}`); +} - function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined { - const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); - return isRootedDiskPath(relativePath) ? undefined : relativePath; +/** @internal */ +export function tryGetJSExtensionForFile(fileName: string, options: CompilerOptions): Extension | undefined { + const ext = tryGetExtensionFromPath(fileName); + switch (ext) { + case Extension.Ts: + case Extension.Dts: + return Extension.Js; + case Extension.Tsx: + return options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js; + case Extension.Js: + case Extension.Jsx: + case Extension.Json: + return ext; + case Extension.Dmts: + case Extension.Mts: + case Extension.Mjs: + return Extension.Mjs; + case Extension.Dcts: + case Extension.Cts: + case Extension.Cjs: + return Extension.Cjs; + default: + return undefined; } +} - function isPathRelativeToParent(path: string): boolean { - return startsWith(path, ".."); - } +function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined { + const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); + return isRootedDiskPath(relativePath) ? undefined : relativePath; +} + +function isPathRelativeToParent(path: string): boolean { + return startsWith(path, ".."); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 3b242daf5cc76..b161549e0fc6e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1,5780 +1,5735 @@ -namespace ts { - const enum SignatureFlags { - None = 0, - Yield = 1 << 0, - Await = 1 << 1, - Type = 1 << 2, - IgnoreMissingOpenBrace = 1 << 4, - JSDoc = 1 << 5, - } +import * as ts from "./_namespaces/ts"; +import { + AccessorDeclaration, addRange, addRelatedInfo, AmdDependency, append, ArrayBindingElement, ArrayBindingPattern, + ArrayLiteralExpression, ArrayTypeNode, ArrowFunction, AsExpression, AssertClause, AssertEntry, AssertionLevel, + AsteriskToken, attachFileToDiagnostics, AwaitExpression, BaseNodeFactory, BinaryExpression, BinaryOperatorToken, + BindingElement, BindingName, BindingPattern, Block, BooleanLiteral, BreakOrContinueStatement, BreakStatement, + CallExpression, CallSignatureDeclaration, canHaveModifiers, CaseBlock, CaseClause, CaseOrDefaultClause, CatchClause, + CharacterCodes, CheckJsDirective, ClassDeclaration, ClassElement, ClassExpression, ClassLikeDeclaration, + ClassStaticBlockDeclaration, CommaListExpression, CommentDirective, commentPragmas, CommentRange, + ComputedPropertyName, concatenate, ConditionalExpression, ConditionalTypeNode, ConstructorDeclaration, + ConstructorTypeNode, ConstructSignatureDeclaration, containsParseError, ContinueStatement, convertToObjectWorker, + createDetachedDiagnostic, createNodeFactory, createScanner, createTextChangeRange, createTextSpanFromBounds, Debug, + Decorator, DefaultClause, DeleteExpression, Diagnostic, DiagnosticMessage, Diagnostics, + DiagnosticWithDetachedLocation, DoStatement, DotDotDotToken, ElementAccessExpression, emptyArray, emptyMap, + EndOfFileToken, ensureScriptKind, EntityName, EnumDeclaration, EnumMember, ESMap, ExclamationToken, + ExportAssignment, ExportDeclaration, ExportSpecifier, Expression, ExpressionStatement, ExpressionWithTypeArguments, + ExternalModuleReference, fileExtensionIsOneOf, FileReference, findIndex, forEach, ForEachChildNodes, + ForInOrOfStatement, ForInStatement, ForOfStatement, ForStatement, FunctionDeclaration, FunctionExpression, + FunctionOrConstructorTypeNode, FunctionTypeNode, GetAccessorDeclaration, getBinaryOperatorPrecedence, getFullWidth, + getJSDocCommentRanges, getLanguageVariant, getLastChild, getLeadingCommentRanges, getSpellingSuggestion, + getTextOfNodeFromSourceText, HasJSDoc, hasJSDocNodes, HasModifiers, HeritageClause, Identifier, idText, IfStatement, + ImportClause, ImportDeclaration, ImportEqualsDeclaration, ImportOrExportSpecifier, ImportSpecifier, + ImportTypeAssertionContainer, ImportTypeNode, IndexedAccessTypeNode, IndexSignatureDeclaration, InferTypeNode, + InterfaceDeclaration, IntersectionTypeNode, isArray, isAssignmentOperator, isAsyncModifier, isClassMemberModifier, + isExportAssignment, isExportDeclaration, isExportModifier, isExpressionWithTypeArguments, isExternalModuleReference, + isFunctionTypeNode, isIdentifierText, isImportDeclaration, isImportEqualsDeclaration, isJSDocFunctionType, + isJSDocNullableType, isJSDocReturnTag, isJSDocTypeTag, isJsxOpeningElement, isJsxOpeningFragment, isKeyword, + isLeftHandSideExpression, isLiteralKind, isMetaProperty, isModifierKind, isNonNullExpression, isPrivateIdentifier, + isSetAccessorDeclaration, isStringOrNumericLiteralLike, isTaggedTemplateExpression, isTemplateLiteralKind, + isTypeReferenceNode, IterationStatement, JSDoc, JSDocAllType, JSDocAugmentsTag, JSDocAuthorTag, JSDocCallbackTag, + JSDocClassTag, JSDocComment, JSDocContainer, JSDocDeprecatedTag, JSDocEnumTag, JSDocFunctionType, + JSDocImplementsTag, JSDocLink, JSDocLinkCode, JSDocLinkPlain, JSDocMemberName, JSDocNameReference, + JSDocNamespaceDeclaration, JSDocNonNullableType, JSDocNullableType, JSDocOptionalType, JSDocOverrideTag, + JSDocParameterTag, JSDocPrivateTag, JSDocPropertyLikeTag, JSDocPropertyTag, JSDocProtectedTag, JSDocPublicTag, + JSDocReadonlyTag, JSDocReturnTag, JSDocSeeTag, JSDocSignature, JSDocSyntaxKind, JSDocTag, JSDocTemplateTag, + JSDocText, JSDocThisTag, JSDocTypedefTag, JSDocTypeExpression, JSDocTypeLiteral, JSDocTypeTag, JSDocUnknownTag, + JSDocUnknownType, JSDocVariadicType, JsonMinusNumericLiteral, JsonObjectExpressionStatement, JsonSourceFile, + JsxAttribute, JsxAttributes, JsxAttributeValue, JsxChild, JsxClosingElement, JsxClosingFragment, JsxElement, + JsxExpression, JsxFragment, JsxOpeningElement, JsxOpeningFragment, JsxOpeningLikeElement, JsxSelfClosingElement, + JsxSpreadAttribute, JsxTagNameExpression, JsxTagNamePropertyAccess, JsxText, JsxTokenSyntaxKind, LabeledStatement, + LanguageVariant, lastOrUndefined, LeftHandSideExpression, LiteralExpression, LiteralLikeNode, LiteralTypeNode, map, + Map, mapDefined, MappedTypeNode, MemberExpression, MetaProperty, MethodDeclaration, MethodSignature, MinusToken, + MissingDeclaration, Modifier, ModifierFlags, ModifierLike, ModifiersArray, modifiersToFlags, ModuleBlock, + ModuleDeclaration, ModuleKind, Mutable, NamedExportBindings, NamedExports, NamedImports, NamedImportsOrExports, + NamedTupleMember, NamespaceDeclaration, NamespaceExport, NamespaceExportDeclaration, NamespaceImport, NewExpression, + Node, NodeArray, NodeFactoryFlags, NodeFlags, nodeIsMissing, nodeIsPresent, NonNullExpression, noop, normalizePath, + NoSubstitutionTemplateLiteral, NullLiteral, NumericLiteral, objectAllocator, ObjectBindingPattern, + ObjectLiteralElementLike, ObjectLiteralExpression, OperatorPrecedence, OptionalTypeNode, PackageJsonInfo, + ParameterDeclaration, ParenthesizedExpression, ParenthesizedTypeNode, PartiallyEmittedExpression, perfLogger, + PlusToken, PostfixUnaryExpression, PostfixUnaryOperator, PragmaDefinition, PragmaKindFlags, PragmaMap, + PragmaPseudoMap, PragmaPseudoMapEntry, PrefixUnaryExpression, PrefixUnaryOperator, PrimaryExpression, + PrivateIdentifier, PropertyAccessEntityNameExpression, PropertyAccessExpression, PropertyAssignment, + PropertyDeclaration, PropertyName, PropertySignature, QualifiedName, QuestionDotToken, QuestionToken, + ReadonlyKeyword, ReadonlyPragmaMap, ReadonlyTextRange, RestTypeNode, ReturnStatement, SatisfiesExpression, + ScriptKind, ScriptTarget, Set, SetAccessorDeclaration, setParent, setParentRecursive, setTextRange, setTextRangePos, + setTextRangePosEnd, setTextRangePosWidth, ShorthandPropertyAssignment, skipTrivia, some, SourceFile, + SpreadAssignment, SpreadElement, startsWith, Statement, StringLiteral, supportedDeclarationExtensions, + SwitchStatement, SyntaxKind, TaggedTemplateExpression, TemplateExpression, TemplateHead, TemplateLiteralToken, + TemplateLiteralTypeNode, TemplateLiteralTypeSpan, TemplateMiddle, TemplateSpan, TemplateTail, TextChangeRange, + textChangeRangeIsUnchanged, textChangeRangeNewSpan, TextRange, textSpanEnd, textToKeywordObj, ThisExpression, + ThisTypeNode, ThrowStatement, toArray, Token, TokenFlags, tokenIsIdentifierOrKeyword, + tokenIsIdentifierOrKeywordOrGreaterThan, tokenToString, tracing, TransformFlags, trimString, TryStatement, + TupleTypeNode, TypeAliasDeclaration, TypeAssertion, TypeElement, TypeLiteralNode, TypeNode, TypeOfExpression, + TypeOperatorNode, TypeParameterDeclaration, TypePredicateNode, TypeQueryNode, TypeReferenceNode, UnaryExpression, + UnionOrIntersectionTypeNode, UnionTypeNode, UpdateExpression, VariableDeclaration, VariableDeclarationList, + VariableStatement, VoidExpression, WhileStatement, WithStatement, YieldExpression, +} from "./_namespaces/ts"; +import * as performance from "./_namespaces/ts.performance"; + +const enum SignatureFlags { + None = 0, + Yield = 1 << 0, + Await = 1 << 1, + Type = 1 << 2, + IgnoreMissingOpenBrace = 1 << 4, + JSDoc = 1 << 5, +} - const enum SpeculationKind { - TryParse, - Lookahead, - Reparse - } +const enum SpeculationKind { + TryParse, + Lookahead, + Reparse +} - let NodeConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let TokenConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let IdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; - let SourceFileConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; +let NodeConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; +let TokenConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; +let IdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; +let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; +let SourceFileConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; + +/** + * NOTE: You should not use this, it is only exported to support `createNode` in `~/src/deprecatedCompat/deprecations.ts`. + * + * @internal + */ +export const parseBaseNodeFactory: BaseNodeFactory = { + createBaseSourceFileNode: kind => new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, -1, -1), + createBaseIdentifierNode: kind => new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, -1, -1), + createBasePrivateIdentifierNode: kind => new (PrivateIdentifierConstructor || (PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor()))(kind, -1, -1), + createBaseTokenNode: kind => new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, -1, -1), + createBaseNode: kind => new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, -1, -1), +}; + +/** @internal */ +export const parseNodeFactory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules, parseBaseNodeFactory); + +function visitNode(cbNode: (node: Node) => T, node: Node | undefined): T | undefined { + return node && cbNode(node); +} - /** - * NOTE: You should not use this, it is only exported to support `createNode` in `~/src/deprecatedCompat/deprecations.ts`. - */ - /* @internal */ - export const parseBaseNodeFactory: BaseNodeFactory = { - createBaseSourceFileNode: kind => new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, -1, -1), - createBaseIdentifierNode: kind => new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, -1, -1), - createBasePrivateIdentifierNode: kind => new (PrivateIdentifierConstructor || (PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor()))(kind, -1, -1), - createBaseTokenNode: kind => new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, -1, -1), - createBaseNode: kind => new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, -1, -1), - }; +function visitNodes(cbNode: (node: Node) => T, cbNodes: ((node: NodeArray) => T | undefined) | undefined, nodes: NodeArray | undefined): T | undefined { + if (nodes) { + if (cbNodes) { + return cbNodes(nodes); + } + for (const node of nodes) { + const result = cbNode(node); + if (result) { + return result; + } + } + } +} - /* @internal */ - export const parseNodeFactory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules, parseBaseNodeFactory); +/** @internal */ +export function isJSDocLikeText(text: string, start: number) { + return text.charCodeAt(start + 1) === CharacterCodes.asterisk && + text.charCodeAt(start + 2) === CharacterCodes.asterisk && + text.charCodeAt(start + 3) !== CharacterCodes.slash; +} - function visitNode(cbNode: (node: Node) => T, node: Node | undefined): T | undefined { - return node && cbNode(node); - } +/** @internal */ +export function isFileProbablyExternalModule(sourceFile: SourceFile) { + // Try to use the first top-level import/export when available, then + // fall back to looking for an 'import.meta' somewhere in the tree if necessary. + return forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) || + getImportMetaIfNecessary(sourceFile); +} - function visitNodes(cbNode: (node: Node) => T, cbNodes: ((node: NodeArray) => T | undefined) | undefined, nodes: NodeArray | undefined): T | undefined { - if (nodes) { - if (cbNodes) { - return cbNodes(nodes); - } - for (const node of nodes) { - const result = cbNode(node); - if (result) { - return result; - } - } - } - } - - /*@internal*/ - export function isJSDocLikeText(text: string, start: number) { - return text.charCodeAt(start + 1) === CharacterCodes.asterisk && - text.charCodeAt(start + 2) === CharacterCodes.asterisk && - text.charCodeAt(start + 3) !== CharacterCodes.slash; - } - - /*@internal*/ - export function isFileProbablyExternalModule(sourceFile: SourceFile) { - // Try to use the first top-level import/export when available, then - // fall back to looking for an 'import.meta' somewhere in the tree if necessary. - return forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) || - getImportMetaIfNecessary(sourceFile); - } - - function isAnExternalModuleIndicatorNode(node: Node) { - return canHaveModifiers(node) && hasModifierOfKind(node, SyntaxKind.ExportKeyword) - || isImportEqualsDeclaration(node) && isExternalModuleReference(node.moduleReference) - || isImportDeclaration(node) - || isExportAssignment(node) - || isExportDeclaration(node) ? node : undefined; - } - - function getImportMetaIfNecessary(sourceFile: SourceFile) { - return sourceFile.flags & NodeFlags.PossiblyContainsImportMeta ? - walkTreeForImportMeta(sourceFile) : - undefined; - } - - function walkTreeForImportMeta(node: Node): Node | undefined { - return isImportMeta(node) ? node : forEachChild(node, walkTreeForImportMeta); - } - - /** Do not use hasModifier inside the parser; it relies on parent pointers. Use this instead. */ - function hasModifierOfKind(node: HasModifiers, kind: SyntaxKind) { - return some(node.modifiers, m => m.kind === kind); - } - - function isImportMeta(node: Node): boolean { - return isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta"; - } - - type ForEachChildFunction = (node: TNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined) => T | undefined; - type ForEachChildTable = { [TNode in ForEachChildNodes as TNode["kind"]]: ForEachChildFunction }; - const forEachChildTable: ForEachChildTable = { - [SyntaxKind.QualifiedName]: function forEachChildInQualifiedName(node: QualifiedName, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.left) || - visitNode(cbNode, node.right); - }, - [SyntaxKind.TypeParameter]: function forEachChildInTypeParameter(node: TypeParameterDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.constraint) || - visitNode(cbNode, node.default) || - visitNode(cbNode, node.expression); - }, - [SyntaxKind.ShorthandPropertyAssignment]: function forEachChildInShorthandPropertyAssignment(node: ShorthandPropertyAssignment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.questionToken) || - visitNode(cbNode, node.exclamationToken) || - visitNode(cbNode, node.equalsToken) || - visitNode(cbNode, node.objectAssignmentInitializer); - }, - [SyntaxKind.SpreadAssignment]: function forEachChildInSpreadAssignment(node: SpreadAssignment, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.Parameter]: function forEachChildInParameter(node: ParameterDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.dotDotDotToken) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.questionToken) || - visitNode(cbNode, node.type) || - visitNode(cbNode, node.initializer); - }, - [SyntaxKind.PropertyDeclaration]: function forEachChildInPropertyDeclaration(node: PropertyDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.questionToken) || - visitNode(cbNode, node.exclamationToken) || - visitNode(cbNode, node.type) || - visitNode(cbNode, node.initializer); - }, - [SyntaxKind.PropertySignature]: function forEachChildInPropertySignature(node: PropertySignature, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.questionToken) || - visitNode(cbNode, node.type) || - visitNode(cbNode, node.initializer); - }, - [SyntaxKind.PropertyAssignment]: function forEachChildInPropertyAssignment(node: PropertyAssignment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.questionToken) || - visitNode(cbNode, node.exclamationToken) || - visitNode(cbNode, node.initializer); - }, - [SyntaxKind.VariableDeclaration]: function forEachChildInVariableDeclaration(node: VariableDeclaration, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.name) || - visitNode(cbNode, node.exclamationToken) || - visitNode(cbNode, node.type) || - visitNode(cbNode, node.initializer); - }, - [SyntaxKind.BindingElement]: function forEachChildInBindingElement(node: BindingElement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.dotDotDotToken) || - visitNode(cbNode, node.propertyName) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.initializer); - }, - [SyntaxKind.IndexSignature]: function forEachChildInIndexSignature(node: IndexSignatureDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type); - }, - [SyntaxKind.ConstructorType]: function forEachChildInConstructorType(node: ConstructorTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type); - }, - [SyntaxKind.FunctionType]: function forEachChildInFunctionType(node: FunctionTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type); - }, - [SyntaxKind.CallSignature]: forEachChildInCallOrConstructSignature, - [SyntaxKind.ConstructSignature]: forEachChildInCallOrConstructSignature, - [SyntaxKind.MethodDeclaration]: function forEachChildInMethodDeclaration(node: MethodDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.asteriskToken) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.questionToken) || - visitNode(cbNode, node.exclamationToken) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type) || - visitNode(cbNode, node.body); - }, - [SyntaxKind.MethodSignature]: function forEachChildInMethodSignature(node: MethodSignature, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.questionToken) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type); - }, - [SyntaxKind.Constructor]: function forEachChildInConstructor(node: ConstructorDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type) || - visitNode(cbNode, node.body); - }, - [SyntaxKind.GetAccessor]: function forEachChildInGetAccessor(node: GetAccessorDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type) || - visitNode(cbNode, node.body); - }, - [SyntaxKind.SetAccessor]: function forEachChildInSetAccessor(node: SetAccessorDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type) || - visitNode(cbNode, node.body); - }, - [SyntaxKind.FunctionDeclaration]: function forEachChildInFunctionDeclaration(node: FunctionDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.asteriskToken) || - visitNode(cbNode, node.name) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type) || - visitNode(cbNode, node.body); - }, - [SyntaxKind.FunctionExpression]: function forEachChildInFunctionExpression(node: FunctionExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.asteriskToken) || - visitNode(cbNode, node.name) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type) || - visitNode(cbNode, node.body); - }, - [SyntaxKind.ArrowFunction]: function forEachChildInArrowFunction(node: ArrowFunction, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type) || - visitNode(cbNode, node.equalsGreaterThanToken) || - visitNode(cbNode, node.body); - }, - [SyntaxKind.ClassStaticBlockDeclaration]: function forEachChildInClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.body); - }, - [SyntaxKind.TypeReference]: function forEachChildInTypeReference(node: TypeReferenceNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.typeName) || - visitNodes(cbNode, cbNodes, node.typeArguments); - }, - [SyntaxKind.TypePredicate]: function forEachChildInTypePredicate(node: TypePredicateNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.assertsModifier) || - visitNode(cbNode, node.parameterName) || - visitNode(cbNode, node.type); - }, - [SyntaxKind.TypeQuery]: function forEachChildInTypeQuery(node: TypeQueryNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.exprName) || - visitNodes(cbNode, cbNodes, node.typeArguments); - }, - [SyntaxKind.TypeLiteral]: function forEachChildInTypeLiteral(node: TypeLiteralNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.members); - }, - [SyntaxKind.ArrayType]: function forEachChildInArrayType(node: ArrayTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.elementType); - }, - [SyntaxKind.TupleType]: function forEachChildInTupleType(node: TupleTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.elements); - }, - [SyntaxKind.UnionType]: forEachChildInUnionOrIntersectionType, - [SyntaxKind.IntersectionType]: forEachChildInUnionOrIntersectionType, - [SyntaxKind.ConditionalType]: function forEachChildInConditionalType(node: ConditionalTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.checkType) || - visitNode(cbNode, node.extendsType) || - visitNode(cbNode, node.trueType) || - visitNode(cbNode, node.falseType); - }, - [SyntaxKind.InferType]: function forEachChildInInferType(node: InferTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.typeParameter); - }, - [SyntaxKind.ImportType]: function forEachChildInImportType(node: ImportTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.argument) || - visitNode(cbNode, node.assertions) || - visitNode(cbNode, node.qualifier) || - visitNodes(cbNode, cbNodes, node.typeArguments); - }, - [SyntaxKind.ImportTypeAssertionContainer]: function forEachChildInImportTypeAssertionContainer(node: ImportTypeAssertionContainer, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.assertClause); - }, - [SyntaxKind.ParenthesizedType]: forEachChildInParenthesizedTypeOrTypeOperator, - [SyntaxKind.TypeOperator]: forEachChildInParenthesizedTypeOrTypeOperator, - [SyntaxKind.IndexedAccessType]: function forEachChildInIndexedAccessType(node: IndexedAccessTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.objectType) || - visitNode(cbNode, node.indexType); - }, - [SyntaxKind.MappedType]: function forEachChildInMappedType(node: MappedTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.readonlyToken) || - visitNode(cbNode, node.typeParameter) || - visitNode(cbNode, node.nameType) || - visitNode(cbNode, node.questionToken) || - visitNode(cbNode, node.type) || - visitNodes(cbNode, cbNodes, node.members); - }, - [SyntaxKind.LiteralType]: function forEachChildInLiteralType(node: LiteralTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.literal); - }, - [SyntaxKind.NamedTupleMember]: function forEachChildInNamedTupleMember(node: NamedTupleMember, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.dotDotDotToken) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.questionToken) || - visitNode(cbNode, node.type); - }, - [SyntaxKind.ObjectBindingPattern]: forEachChildInObjectOrArrayBindingPattern, - [SyntaxKind.ArrayBindingPattern]: forEachChildInObjectOrArrayBindingPattern, - [SyntaxKind.ArrayLiteralExpression]: function forEachChildInArrayLiteralExpression(node: ArrayLiteralExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.elements); - }, - [SyntaxKind.ObjectLiteralExpression]: function forEachChildInObjectLiteralExpression(node: ObjectLiteralExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.properties); - }, - [SyntaxKind.PropertyAccessExpression]: function forEachChildInPropertyAccessExpression(node: PropertyAccessExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression) || - visitNode(cbNode, node.questionDotToken) || - visitNode(cbNode, node.name); - }, - [SyntaxKind.ElementAccessExpression]: function forEachChildInElementAccessExpression(node: ElementAccessExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression) || - visitNode(cbNode, node.questionDotToken) || - visitNode(cbNode, node.argumentExpression); - }, - [SyntaxKind.CallExpression]: forEachChildInCallOrNewExpression, - [SyntaxKind.NewExpression]: forEachChildInCallOrNewExpression, - [SyntaxKind.TaggedTemplateExpression]: function forEachChildInTaggedTemplateExpression(node: TaggedTemplateExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.tag) || - visitNode(cbNode, node.questionDotToken) || - visitNodes(cbNode, cbNodes, node.typeArguments) || - visitNode(cbNode, node.template); - }, - [SyntaxKind.TypeAssertionExpression]: function forEachChildInTypeAssertionExpression(node: TypeAssertion, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.type) || - visitNode(cbNode, node.expression); - }, - [SyntaxKind.ParenthesizedExpression]: function forEachChildInParenthesizedExpression(node: ParenthesizedExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.DeleteExpression]: function forEachChildInDeleteExpression(node: DeleteExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.TypeOfExpression]: function forEachChildInTypeOfExpression(node: TypeOfExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.VoidExpression]: function forEachChildInVoidExpression(node: VoidExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.PrefixUnaryExpression]: function forEachChildInPrefixUnaryExpression(node: PrefixUnaryExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.operand); - }, - [SyntaxKind.YieldExpression]: function forEachChildInYieldExpression(node: YieldExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.asteriskToken) || - visitNode(cbNode, node.expression); - }, - [SyntaxKind.AwaitExpression]: function forEachChildInAwaitExpression(node: AwaitExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.PostfixUnaryExpression]: function forEachChildInPostfixUnaryExpression(node: PostfixUnaryExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.operand); - }, - [SyntaxKind.BinaryExpression]: function forEachChildInBinaryExpression(node: BinaryExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.left) || - visitNode(cbNode, node.operatorToken) || - visitNode(cbNode, node.right); - }, - [SyntaxKind.AsExpression]: function forEachChildInAsExpression(node: AsExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression) || - visitNode(cbNode, node.type); - }, - [SyntaxKind.NonNullExpression]: function forEachChildInNonNullExpression(node: NonNullExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.SatisfiesExpression]: function forEachChildInSatisfiesExpression(node: SatisfiesExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression) || visitNode(cbNode, node.type); - }, - [SyntaxKind.MetaProperty]: function forEachChildInMetaProperty(node: MetaProperty, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.name); - }, - [SyntaxKind.ConditionalExpression]: function forEachChildInConditionalExpression(node: ConditionalExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.condition) || - visitNode(cbNode, node.questionToken) || - visitNode(cbNode, node.whenTrue) || - visitNode(cbNode, node.colonToken) || - visitNode(cbNode, node.whenFalse); - }, - [SyntaxKind.SpreadElement]: function forEachChildInSpreadElement(node: SpreadElement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.Block]: forEachChildInBlock, - [SyntaxKind.ModuleBlock]: forEachChildInBlock, - [SyntaxKind.SourceFile]: function forEachChildInSourceFile(node: SourceFile, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.statements) || - visitNode(cbNode, node.endOfFileToken); - }, - [SyntaxKind.VariableStatement]: function forEachChildInVariableStatement(node: VariableStatement, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.declarationList); - }, - [SyntaxKind.VariableDeclarationList]: function forEachChildInVariableDeclarationList(node: VariableDeclarationList, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.declarations); - }, - [SyntaxKind.ExpressionStatement]: function forEachChildInExpressionStatement(node: ExpressionStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.IfStatement]: function forEachChildInIfStatement(node: IfStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression) || - visitNode(cbNode, node.thenStatement) || - visitNode(cbNode, node.elseStatement); - }, - [SyntaxKind.DoStatement]: function forEachChildInDoStatement(node: DoStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.statement) || - visitNode(cbNode, node.expression); - }, - [SyntaxKind.WhileStatement]: function forEachChildInWhileStatement(node: WhileStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression) || - visitNode(cbNode, node.statement); - }, - [SyntaxKind.ForStatement]: function forEachChildInForStatement(node: ForStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.initializer) || - visitNode(cbNode, node.condition) || - visitNode(cbNode, node.incrementor) || - visitNode(cbNode, node.statement); - }, - [SyntaxKind.ForInStatement]: function forEachChildInForInStatement(node: ForInStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.initializer) || - visitNode(cbNode, node.expression) || - visitNode(cbNode, node.statement); - }, - [SyntaxKind.ForOfStatement]: function forEachChildInForOfStatement(node: ForOfStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.awaitModifier) || - visitNode(cbNode, node.initializer) || - visitNode(cbNode, node.expression) || - visitNode(cbNode, node.statement); - }, - [SyntaxKind.ContinueStatement]: forEachChildInContinueOrBreakStatement, - [SyntaxKind.BreakStatement]: forEachChildInContinueOrBreakStatement, - [SyntaxKind.ReturnStatement]: function forEachChildInReturnStatement(node: ReturnStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.WithStatement]: function forEachChildInWithStatement(node: WithStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression) || - visitNode(cbNode, node.statement); - }, - [SyntaxKind.SwitchStatement]: function forEachChildInSwitchStatement(node: SwitchStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression) || - visitNode(cbNode, node.caseBlock); - }, - [SyntaxKind.CaseBlock]: function forEachChildInCaseBlock(node: CaseBlock, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.clauses); - }, - [SyntaxKind.CaseClause]: function forEachChildInCaseClause(node: CaseClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression) || - visitNodes(cbNode, cbNodes, node.statements); - }, - [SyntaxKind.DefaultClause]: function forEachChildInDefaultClause(node: DefaultClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.statements); - }, - [SyntaxKind.LabeledStatement]: function forEachChildInLabeledStatement(node: LabeledStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.label) || - visitNode(cbNode, node.statement); - }, - [SyntaxKind.ThrowStatement]: function forEachChildInThrowStatement(node: ThrowStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.TryStatement]: function forEachChildInTryStatement(node: TryStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.tryBlock) || - visitNode(cbNode, node.catchClause) || - visitNode(cbNode, node.finallyBlock); - }, - [SyntaxKind.CatchClause]: function forEachChildInCatchClause(node: CatchClause, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.variableDeclaration) || - visitNode(cbNode, node.block); - }, - [SyntaxKind.Decorator]: function forEachChildInDecorator(node: Decorator, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.ClassDeclaration]: forEachChildInClassDeclarationOrExpression, - [SyntaxKind.ClassExpression]: forEachChildInClassDeclarationOrExpression, - [SyntaxKind.InterfaceDeclaration]: function forEachChildInInterfaceDeclaration(node: InterfaceDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.heritageClauses) || - visitNodes(cbNode, cbNodes, node.members); - }, - [SyntaxKind.TypeAliasDeclaration]: function forEachChildInTypeAliasDeclaration(node: TypeAliasDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNode(cbNode, node.type); - }, - [SyntaxKind.EnumDeclaration]: function forEachChildInEnumDeclaration(node: EnumDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNodes(cbNode, cbNodes, node.members); - }, - [SyntaxKind.EnumMember]: function forEachChildInEnumMember(node: EnumMember, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.name) || - visitNode(cbNode, node.initializer); - }, - [SyntaxKind.ModuleDeclaration]: function forEachChildInModuleDeclaration(node: ModuleDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.body); - }, - [SyntaxKind.ImportEqualsDeclaration]: function forEachChildInImportEqualsDeclaration(node: ImportEqualsDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.name) || - visitNode(cbNode, node.moduleReference); - }, - [SyntaxKind.ImportDeclaration]: function forEachChildInImportDeclaration(node: ImportDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.importClause) || - visitNode(cbNode, node.moduleSpecifier) || - visitNode(cbNode, node.assertClause); - }, - [SyntaxKind.ImportClause]: function forEachChildInImportClause(node: ImportClause, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.name) || - visitNode(cbNode, node.namedBindings); - }, - [SyntaxKind.AssertClause]: function forEachChildInAssertClause(node: AssertClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.elements); - }, - [SyntaxKind.AssertEntry]: function forEachChildInAssertEntry(node: AssertEntry, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.name) || - visitNode(cbNode, node.value); - }, - [SyntaxKind.NamespaceExportDeclaration]: function forEachChildInNamespaceExportDeclaration(node: NamespaceExportDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNode(cbNode, node.name); - }, - [SyntaxKind.NamespaceImport]: function forEachChildInNamespaceImport(node: NamespaceImport, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.name); - }, - [SyntaxKind.NamespaceExport]: function forEachChildInNamespaceExport(node: NamespaceExport, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.name); - }, - [SyntaxKind.NamedImports]: forEachChildInNamedImportsOrExports, - [SyntaxKind.NamedExports]: forEachChildInNamedImportsOrExports, - [SyntaxKind.ExportDeclaration]: function forEachChildInExportDeclaration(node: ExportDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.exportClause) || - visitNode(cbNode, node.moduleSpecifier) || - visitNode(cbNode, node.assertClause); - }, - [SyntaxKind.ImportSpecifier]: forEachChildInImportOrExportSpecifier, - [SyntaxKind.ExportSpecifier]: forEachChildInImportOrExportSpecifier, - [SyntaxKind.ExportAssignment]: function forEachChildInExportAssignment(node: ExportAssignment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers) || - visitNode(cbNode, node.expression); - }, - [SyntaxKind.TemplateExpression]: function forEachChildInTemplateExpression(node: TemplateExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.head) || - visitNodes(cbNode, cbNodes, node.templateSpans); - }, - [SyntaxKind.TemplateSpan]: function forEachChildInTemplateSpan(node: TemplateSpan, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression) || - visitNode(cbNode, node.literal); - }, - [SyntaxKind.TemplateLiteralType]: function forEachChildInTemplateLiteralType(node: TemplateLiteralTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.head) || - visitNodes(cbNode, cbNodes, node.templateSpans); - }, - [SyntaxKind.TemplateLiteralTypeSpan]: function forEachChildInTemplateLiteralTypeSpan(node: TemplateLiteralTypeSpan, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.type) || - visitNode(cbNode, node.literal); - }, - [SyntaxKind.ComputedPropertyName]: function forEachChildInComputedPropertyName(node: ComputedPropertyName, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.HeritageClause]: function forEachChildInHeritageClause(node: HeritageClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.types); - }, - [SyntaxKind.ExpressionWithTypeArguments]: function forEachChildInExpressionWithTypeArguments(node: ExpressionWithTypeArguments, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression) || - visitNodes(cbNode, cbNodes, node.typeArguments); - }, - [SyntaxKind.ExternalModuleReference]: function forEachChildInExternalModuleReference(node: ExternalModuleReference, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.MissingDeclaration]: function forEachChildInMissingDeclaration(node: MissingDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.illegalDecorators) || - visitNodes(cbNode, cbNodes, node.modifiers); - }, - [SyntaxKind.CommaListExpression]: function forEachChildInCommaListExpression(node: CommaListExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.elements); - }, - [SyntaxKind.JsxElement]: function forEachChildInJsxElement(node: JsxElement, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.openingElement) || - visitNodes(cbNode, cbNodes, node.children) || - visitNode(cbNode, node.closingElement); - }, - [SyntaxKind.JsxFragment]: function forEachChildInJsxFragment(node: JsxFragment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.openingFragment) || - visitNodes(cbNode, cbNodes, node.children) || - visitNode(cbNode, node.closingFragment); - }, - [SyntaxKind.JsxSelfClosingElement]: forEachChildInJsxOpeningOrSelfClosingElement, - [SyntaxKind.JsxOpeningElement]: forEachChildInJsxOpeningOrSelfClosingElement, - [SyntaxKind.JsxAttributes]: function forEachChildInJsxAttributes(node: JsxAttributes, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.properties); - }, - [SyntaxKind.JsxAttribute]: function forEachChildInJsxAttribute(node: JsxAttribute, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.name) || - visitNode(cbNode, node.initializer); - }, - [SyntaxKind.JsxSpreadAttribute]: function forEachChildInJsxSpreadAttribute(node: JsxSpreadAttribute, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - }, - [SyntaxKind.JsxExpression]: function forEachChildInJsxExpression(node: JsxExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.dotDotDotToken) || - visitNode(cbNode, node.expression); - }, - [SyntaxKind.JsxClosingElement]: function forEachChildInJsxClosingElement(node: JsxClosingElement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.tagName); - }, - [SyntaxKind.OptionalType]: forEachChildInOptionalRestOrJSDocParameterModifier, - [SyntaxKind.RestType]: forEachChildInOptionalRestOrJSDocParameterModifier, - [SyntaxKind.JSDocTypeExpression]: forEachChildInOptionalRestOrJSDocParameterModifier, - [SyntaxKind.JSDocNonNullableType]: forEachChildInOptionalRestOrJSDocParameterModifier, - [SyntaxKind.JSDocNullableType]: forEachChildInOptionalRestOrJSDocParameterModifier, - [SyntaxKind.JSDocOptionalType]: forEachChildInOptionalRestOrJSDocParameterModifier, - [SyntaxKind.JSDocVariadicType]: forEachChildInOptionalRestOrJSDocParameterModifier, - [SyntaxKind.JSDocFunctionType]: function forEachChildInJSDocFunctionType(node: JSDocFunctionType, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type); - }, - [SyntaxKind.JSDoc]: function forEachChildInJSDoc(node: JSDoc, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)) - || visitNodes(cbNode, cbNodes, node.tags); - }, - [SyntaxKind.JSDocSeeTag]: function forEachChildInJSDocSeeTag(node: JSDocSeeTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.tagName) || - visitNode(cbNode, node.name) || - (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); - }, - [SyntaxKind.JSDocNameReference]: function forEachChildInJSDocNameReference(node: JSDocNameReference, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.name); - }, - [SyntaxKind.JSDocMemberName]: function forEachChildInJSDocMemberName(node: JSDocMemberName, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.left) || - visitNode(cbNode, node.right); - }, - [SyntaxKind.JSDocParameterTag]: forEachChildInJSDocParameterOrPropertyTag, - [SyntaxKind.JSDocPropertyTag]: forEachChildInJSDocParameterOrPropertyTag, - [SyntaxKind.JSDocAuthorTag]: function forEachChildInJSDocAuthorTag(node: JSDocAuthorTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.tagName) || - (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); - }, - [SyntaxKind.JSDocImplementsTag]: function forEachChildInJSDocImplementsTag(node: JSDocImplementsTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.tagName) || - visitNode(cbNode, node.class) || - (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); - }, - [SyntaxKind.JSDocAugmentsTag]: function forEachChildInJSDocAugmentsTag(node: JSDocAugmentsTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.tagName) || - visitNode(cbNode, node.class) || - (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); - }, - [SyntaxKind.JSDocTemplateTag]: function forEachChildInJSDocTemplateTag(node: JSDocTemplateTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.tagName) || - visitNode(cbNode, node.constraint) || - visitNodes(cbNode, cbNodes, node.typeParameters) || - (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); - }, - [SyntaxKind.JSDocTypedefTag]: function forEachChildInJSDocTypedefTag(node: JSDocTypedefTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.tagName) || - (node.typeExpression && - node.typeExpression.kind === SyntaxKind.JSDocTypeExpression - ? visitNode(cbNode, node.typeExpression) || - visitNode(cbNode, node.fullName) || - (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)) - : visitNode(cbNode, node.fullName) || - visitNode(cbNode, node.typeExpression) || - (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment))); - }, - [SyntaxKind.JSDocCallbackTag]: function forEachChildInJSDocCallbackTag(node: JSDocCallbackTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.tagName) || - visitNode(cbNode, node.fullName) || - visitNode(cbNode, node.typeExpression) || - (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); - }, - [SyntaxKind.JSDocReturnTag]: forEachChildInJSDocReturnTag, - [SyntaxKind.JSDocTypeTag]: forEachChildInJSDocReturnTag, - [SyntaxKind.JSDocThisTag]: forEachChildInJSDocReturnTag, - [SyntaxKind.JSDocEnumTag]: forEachChildInJSDocReturnTag, - [SyntaxKind.JSDocSignature]: function forEachChildInJSDocSignature(node: JSDocSignature, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return forEach(node.typeParameters, cbNode) || - forEach(node.parameters, cbNode) || - visitNode(cbNode, node.type); - }, - [SyntaxKind.JSDocLink]: forEachChildInJSDocLinkCodeOrPlain, - [SyntaxKind.JSDocLinkCode]: forEachChildInJSDocLinkCodeOrPlain, - [SyntaxKind.JSDocLinkPlain]: forEachChildInJSDocLinkCodeOrPlain, - [SyntaxKind.JSDocTypeLiteral]: function forEachChildInJSDocTypeLiteral(node: JSDocTypeLiteral, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return forEach(node.jsDocPropertyTags, cbNode); - }, - [SyntaxKind.JSDocTag]: forEachChildInJSDocTag, - [SyntaxKind.JSDocClassTag]: forEachChildInJSDocTag, - [SyntaxKind.JSDocPublicTag]: forEachChildInJSDocTag, - [SyntaxKind.JSDocPrivateTag]: forEachChildInJSDocTag, - [SyntaxKind.JSDocProtectedTag]: forEachChildInJSDocTag, - [SyntaxKind.JSDocReadonlyTag]: forEachChildInJSDocTag, - [SyntaxKind.JSDocDeprecatedTag]: forEachChildInJSDocTag, - [SyntaxKind.JSDocOverrideTag]: forEachChildInJSDocTag, - [SyntaxKind.PartiallyEmittedExpression]: forEachChildInPartiallyEmittedExpression, - }; +function isAnExternalModuleIndicatorNode(node: Node) { + return canHaveModifiers(node) && hasModifierOfKind(node, SyntaxKind.ExportKeyword) + || isImportEqualsDeclaration(node) && isExternalModuleReference(node.moduleReference) + || isImportDeclaration(node) + || isExportAssignment(node) + || isExportDeclaration(node) ? node : undefined; +} - // shared +function getImportMetaIfNecessary(sourceFile: SourceFile) { + return sourceFile.flags & NodeFlags.PossiblyContainsImportMeta ? + walkTreeForImportMeta(sourceFile) : + undefined; +} - function forEachChildInCallOrConstructSignature(node: CallSignatureDeclaration | ConstructSignatureDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.typeParameters) || - visitNodes(cbNode, cbNodes, node.parameters) || - visitNode(cbNode, node.type); - } +function walkTreeForImportMeta(node: Node): Node | undefined { + return isImportMeta(node) ? node : forEachChild(node, walkTreeForImportMeta); +} - function forEachChildInUnionOrIntersectionType(node: UnionTypeNode | IntersectionTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.types); - } +/** Do not use hasModifier inside the parser; it relies on parent pointers. Use this instead. */ +function hasModifierOfKind(node: HasModifiers, kind: SyntaxKind) { + return some(node.modifiers, m => m.kind === kind); +} - function forEachChildInParenthesizedTypeOrTypeOperator(node: ParenthesizedTypeNode | TypeOperatorNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.type); - } +function isImportMeta(node: Node): boolean { + return isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta"; +} - function forEachChildInObjectOrArrayBindingPattern(node: BindingPattern, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { +type ForEachChildFunction = (node: TNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined) => T | undefined; +type ForEachChildTable = { [TNode in ForEachChildNodes as TNode["kind"]]: ForEachChildFunction }; +const forEachChildTable: ForEachChildTable = { + [SyntaxKind.QualifiedName]: function forEachChildInQualifiedName(node: QualifiedName, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.left) || + visitNode(cbNode, node.right); + }, + [SyntaxKind.TypeParameter]: function forEachChildInTypeParameter(node: TypeParameterDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.constraint) || + visitNode(cbNode, node.default) || + visitNode(cbNode, node.expression); + }, + [SyntaxKind.ShorthandPropertyAssignment]: function forEachChildInShorthandPropertyAssignment(node: ShorthandPropertyAssignment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.questionToken) || + visitNode(cbNode, node.exclamationToken) || + visitNode(cbNode, node.equalsToken) || + visitNode(cbNode, node.objectAssignmentInitializer); + }, + [SyntaxKind.SpreadAssignment]: function forEachChildInSpreadAssignment(node: SpreadAssignment, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.Parameter]: function forEachChildInParameter(node: ParameterDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.dotDotDotToken) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.questionToken) || + visitNode(cbNode, node.type) || + visitNode(cbNode, node.initializer); + }, + [SyntaxKind.PropertyDeclaration]: function forEachChildInPropertyDeclaration(node: PropertyDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.questionToken) || + visitNode(cbNode, node.exclamationToken) || + visitNode(cbNode, node.type) || + visitNode(cbNode, node.initializer); + }, + [SyntaxKind.PropertySignature]: function forEachChildInPropertySignature(node: PropertySignature, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.questionToken) || + visitNode(cbNode, node.type) || + visitNode(cbNode, node.initializer); + }, + [SyntaxKind.PropertyAssignment]: function forEachChildInPropertyAssignment(node: PropertyAssignment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.questionToken) || + visitNode(cbNode, node.exclamationToken) || + visitNode(cbNode, node.initializer); + }, + [SyntaxKind.VariableDeclaration]: function forEachChildInVariableDeclaration(node: VariableDeclaration, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.name) || + visitNode(cbNode, node.exclamationToken) || + visitNode(cbNode, node.type) || + visitNode(cbNode, node.initializer); + }, + [SyntaxKind.BindingElement]: function forEachChildInBindingElement(node: BindingElement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.dotDotDotToken) || + visitNode(cbNode, node.propertyName) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.initializer); + }, + [SyntaxKind.IndexSignature]: function forEachChildInIndexSignature(node: IndexSignatureDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type); + }, + [SyntaxKind.ConstructorType]: function forEachChildInConstructorType(node: ConstructorTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type); + }, + [SyntaxKind.FunctionType]: function forEachChildInFunctionType(node: FunctionTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type); + }, + [SyntaxKind.CallSignature]: forEachChildInCallOrConstructSignature, + [SyntaxKind.ConstructSignature]: forEachChildInCallOrConstructSignature, + [SyntaxKind.MethodDeclaration]: function forEachChildInMethodDeclaration(node: MethodDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.asteriskToken) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.questionToken) || + visitNode(cbNode, node.exclamationToken) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type) || + visitNode(cbNode, node.body); + }, + [SyntaxKind.MethodSignature]: function forEachChildInMethodSignature(node: MethodSignature, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.questionToken) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type); + }, + [SyntaxKind.Constructor]: function forEachChildInConstructor(node: ConstructorDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type) || + visitNode(cbNode, node.body); + }, + [SyntaxKind.GetAccessor]: function forEachChildInGetAccessor(node: GetAccessorDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type) || + visitNode(cbNode, node.body); + }, + [SyntaxKind.SetAccessor]: function forEachChildInSetAccessor(node: SetAccessorDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type) || + visitNode(cbNode, node.body); + }, + [SyntaxKind.FunctionDeclaration]: function forEachChildInFunctionDeclaration(node: FunctionDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.asteriskToken) || + visitNode(cbNode, node.name) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type) || + visitNode(cbNode, node.body); + }, + [SyntaxKind.FunctionExpression]: function forEachChildInFunctionExpression(node: FunctionExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.asteriskToken) || + visitNode(cbNode, node.name) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type) || + visitNode(cbNode, node.body); + }, + [SyntaxKind.ArrowFunction]: function forEachChildInArrowFunction(node: ArrowFunction, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type) || + visitNode(cbNode, node.equalsGreaterThanToken) || + visitNode(cbNode, node.body); + }, + [SyntaxKind.ClassStaticBlockDeclaration]: function forEachChildInClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.body); + }, + [SyntaxKind.TypeReference]: function forEachChildInTypeReference(node: TypeReferenceNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.typeName) || + visitNodes(cbNode, cbNodes, node.typeArguments); + }, + [SyntaxKind.TypePredicate]: function forEachChildInTypePredicate(node: TypePredicateNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.assertsModifier) || + visitNode(cbNode, node.parameterName) || + visitNode(cbNode, node.type); + }, + [SyntaxKind.TypeQuery]: function forEachChildInTypeQuery(node: TypeQueryNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.exprName) || + visitNodes(cbNode, cbNodes, node.typeArguments); + }, + [SyntaxKind.TypeLiteral]: function forEachChildInTypeLiteral(node: TypeLiteralNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.members); + }, + [SyntaxKind.ArrayType]: function forEachChildInArrayType(node: ArrayTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.elementType); + }, + [SyntaxKind.TupleType]: function forEachChildInTupleType(node: TupleTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.elements); - } - - function forEachChildInCallOrNewExpression(node: CallExpression | NewExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + }, + [SyntaxKind.UnionType]: forEachChildInUnionOrIntersectionType, + [SyntaxKind.IntersectionType]: forEachChildInUnionOrIntersectionType, + [SyntaxKind.ConditionalType]: function forEachChildInConditionalType(node: ConditionalTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.checkType) || + visitNode(cbNode, node.extendsType) || + visitNode(cbNode, node.trueType) || + visitNode(cbNode, node.falseType); + }, + [SyntaxKind.InferType]: function forEachChildInInferType(node: InferTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.typeParameter); + }, + [SyntaxKind.ImportType]: function forEachChildInImportType(node: ImportTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.argument) || + visitNode(cbNode, node.assertions) || + visitNode(cbNode, node.qualifier) || + visitNodes(cbNode, cbNodes, node.typeArguments); + }, + [SyntaxKind.ImportTypeAssertionContainer]: function forEachChildInImportTypeAssertionContainer(node: ImportTypeAssertionContainer, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.assertClause); + }, + [SyntaxKind.ParenthesizedType]: forEachChildInParenthesizedTypeOrTypeOperator, + [SyntaxKind.TypeOperator]: forEachChildInParenthesizedTypeOrTypeOperator, + [SyntaxKind.IndexedAccessType]: function forEachChildInIndexedAccessType(node: IndexedAccessTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.objectType) || + visitNode(cbNode, node.indexType); + }, + [SyntaxKind.MappedType]: function forEachChildInMappedType(node: MappedTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.readonlyToken) || + visitNode(cbNode, node.typeParameter) || + visitNode(cbNode, node.nameType) || + visitNode(cbNode, node.questionToken) || + visitNode(cbNode, node.type) || + visitNodes(cbNode, cbNodes, node.members); + }, + [SyntaxKind.LiteralType]: function forEachChildInLiteralType(node: LiteralTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.literal); + }, + [SyntaxKind.NamedTupleMember]: function forEachChildInNamedTupleMember(node: NamedTupleMember, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.dotDotDotToken) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.questionToken) || + visitNode(cbNode, node.type); + }, + [SyntaxKind.ObjectBindingPattern]: forEachChildInObjectOrArrayBindingPattern, + [SyntaxKind.ArrayBindingPattern]: forEachChildInObjectOrArrayBindingPattern, + [SyntaxKind.ArrayLiteralExpression]: function forEachChildInArrayLiteralExpression(node: ArrayLiteralExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.elements); + }, + [SyntaxKind.ObjectLiteralExpression]: function forEachChildInObjectLiteralExpression(node: ObjectLiteralExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.properties); + }, + [SyntaxKind.PropertyAccessExpression]: function forEachChildInPropertyAccessExpression(node: PropertyAccessExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || - // TODO: should we separate these branches out? - visitNode(cbNode, (node as CallExpression).questionDotToken) || + visitNode(cbNode, node.questionDotToken) || + visitNode(cbNode, node.name); + }, + [SyntaxKind.ElementAccessExpression]: function forEachChildInElementAccessExpression(node: ElementAccessExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression) || + visitNode(cbNode, node.questionDotToken) || + visitNode(cbNode, node.argumentExpression); + }, + [SyntaxKind.CallExpression]: forEachChildInCallOrNewExpression, + [SyntaxKind.NewExpression]: forEachChildInCallOrNewExpression, + [SyntaxKind.TaggedTemplateExpression]: function forEachChildInTaggedTemplateExpression(node: TaggedTemplateExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.tag) || + visitNode(cbNode, node.questionDotToken) || visitNodes(cbNode, cbNodes, node.typeArguments) || - visitNodes(cbNode, cbNodes, node.arguments); - } - - function forEachChildInBlock(node: Block | ModuleBlock, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + visitNode(cbNode, node.template); + }, + [SyntaxKind.TypeAssertionExpression]: function forEachChildInTypeAssertionExpression(node: TypeAssertion, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.type) || + visitNode(cbNode, node.expression); + }, + [SyntaxKind.ParenthesizedExpression]: function forEachChildInParenthesizedExpression(node: ParenthesizedExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.DeleteExpression]: function forEachChildInDeleteExpression(node: DeleteExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.TypeOfExpression]: function forEachChildInTypeOfExpression(node: TypeOfExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.VoidExpression]: function forEachChildInVoidExpression(node: VoidExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.PrefixUnaryExpression]: function forEachChildInPrefixUnaryExpression(node: PrefixUnaryExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.operand); + }, + [SyntaxKind.YieldExpression]: function forEachChildInYieldExpression(node: YieldExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.asteriskToken) || + visitNode(cbNode, node.expression); + }, + [SyntaxKind.AwaitExpression]: function forEachChildInAwaitExpression(node: AwaitExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.PostfixUnaryExpression]: function forEachChildInPostfixUnaryExpression(node: PostfixUnaryExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.operand); + }, + [SyntaxKind.BinaryExpression]: function forEachChildInBinaryExpression(node: BinaryExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.left) || + visitNode(cbNode, node.operatorToken) || + visitNode(cbNode, node.right); + }, + [SyntaxKind.AsExpression]: function forEachChildInAsExpression(node: AsExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression) || + visitNode(cbNode, node.type); + }, + [SyntaxKind.NonNullExpression]: function forEachChildInNonNullExpression(node: NonNullExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.SatisfiesExpression]: function forEachChildInSatisfiesExpression(node: SatisfiesExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression) || visitNode(cbNode, node.type); + }, + [SyntaxKind.MetaProperty]: function forEachChildInMetaProperty(node: MetaProperty, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.name); + }, + [SyntaxKind.ConditionalExpression]: function forEachChildInConditionalExpression(node: ConditionalExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.condition) || + visitNode(cbNode, node.questionToken) || + visitNode(cbNode, node.whenTrue) || + visitNode(cbNode, node.colonToken) || + visitNode(cbNode, node.whenFalse); + }, + [SyntaxKind.SpreadElement]: function forEachChildInSpreadElement(node: SpreadElement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.Block]: forEachChildInBlock, + [SyntaxKind.ModuleBlock]: forEachChildInBlock, + [SyntaxKind.SourceFile]: function forEachChildInSourceFile(node: SourceFile, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.statements) || + visitNode(cbNode, node.endOfFileToken); + }, + [SyntaxKind.VariableStatement]: function forEachChildInVariableStatement(node: VariableStatement, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.declarationList); + }, + [SyntaxKind.VariableDeclarationList]: function forEachChildInVariableDeclarationList(node: VariableDeclarationList, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.declarations); + }, + [SyntaxKind.ExpressionStatement]: function forEachChildInExpressionStatement(node: ExpressionStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.IfStatement]: function forEachChildInIfStatement(node: IfStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression) || + visitNode(cbNode, node.thenStatement) || + visitNode(cbNode, node.elseStatement); + }, + [SyntaxKind.DoStatement]: function forEachChildInDoStatement(node: DoStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.statement) || + visitNode(cbNode, node.expression); + }, + [SyntaxKind.WhileStatement]: function forEachChildInWhileStatement(node: WhileStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression) || + visitNode(cbNode, node.statement); + }, + [SyntaxKind.ForStatement]: function forEachChildInForStatement(node: ForStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.initializer) || + visitNode(cbNode, node.condition) || + visitNode(cbNode, node.incrementor) || + visitNode(cbNode, node.statement); + }, + [SyntaxKind.ForInStatement]: function forEachChildInForInStatement(node: ForInStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.initializer) || + visitNode(cbNode, node.expression) || + visitNode(cbNode, node.statement); + }, + [SyntaxKind.ForOfStatement]: function forEachChildInForOfStatement(node: ForOfStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.awaitModifier) || + visitNode(cbNode, node.initializer) || + visitNode(cbNode, node.expression) || + visitNode(cbNode, node.statement); + }, + [SyntaxKind.ContinueStatement]: forEachChildInContinueOrBreakStatement, + [SyntaxKind.BreakStatement]: forEachChildInContinueOrBreakStatement, + [SyntaxKind.ReturnStatement]: function forEachChildInReturnStatement(node: ReturnStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.WithStatement]: function forEachChildInWithStatement(node: WithStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression) || + visitNode(cbNode, node.statement); + }, + [SyntaxKind.SwitchStatement]: function forEachChildInSwitchStatement(node: SwitchStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression) || + visitNode(cbNode, node.caseBlock); + }, + [SyntaxKind.CaseBlock]: function forEachChildInCaseBlock(node: CaseBlock, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.clauses); + }, + [SyntaxKind.CaseClause]: function forEachChildInCaseClause(node: CaseClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression) || + visitNodes(cbNode, cbNodes, node.statements); + }, + [SyntaxKind.DefaultClause]: function forEachChildInDefaultClause(node: DefaultClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.statements); - } - - function forEachChildInContinueOrBreakStatement(node: ContinueStatement | BreakStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.label); - } - - function forEachChildInClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNodes(cbNode, cbNodes, node.modifiers) || + }, + [SyntaxKind.LabeledStatement]: function forEachChildInLabeledStatement(node: LabeledStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.label) || + visitNode(cbNode, node.statement); + }, + [SyntaxKind.ThrowStatement]: function forEachChildInThrowStatement(node: ThrowStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.TryStatement]: function forEachChildInTryStatement(node: TryStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.tryBlock) || + visitNode(cbNode, node.catchClause) || + visitNode(cbNode, node.finallyBlock); + }, + [SyntaxKind.CatchClause]: function forEachChildInCatchClause(node: CatchClause, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.variableDeclaration) || + visitNode(cbNode, node.block); + }, + [SyntaxKind.Decorator]: function forEachChildInDecorator(node: Decorator, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.ClassDeclaration]: forEachChildInClassDeclarationOrExpression, + [SyntaxKind.ClassExpression]: forEachChildInClassDeclarationOrExpression, + [SyntaxKind.InterfaceDeclaration]: function forEachChildInInterfaceDeclaration(node: InterfaceDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.heritageClauses) || visitNodes(cbNode, cbNodes, node.members); - } - - function forEachChildInNamedImportsOrExports(node: NamedImports | NamedExports, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + }, + [SyntaxKind.TypeAliasDeclaration]: function forEachChildInTypeAliasDeclaration(node: TypeAliasDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNode(cbNode, node.type); + }, + [SyntaxKind.EnumDeclaration]: function forEachChildInEnumDeclaration(node: EnumDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNodes(cbNode, cbNodes, node.members); + }, + [SyntaxKind.EnumMember]: function forEachChildInEnumMember(node: EnumMember, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.name) || + visitNode(cbNode, node.initializer); + }, + [SyntaxKind.ModuleDeclaration]: function forEachChildInModuleDeclaration(node: ModuleDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.body); + }, + [SyntaxKind.ImportEqualsDeclaration]: function forEachChildInImportEqualsDeclaration(node: ImportEqualsDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNode(cbNode, node.moduleReference); + }, + [SyntaxKind.ImportDeclaration]: function forEachChildInImportDeclaration(node: ImportDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.importClause) || + visitNode(cbNode, node.moduleSpecifier) || + visitNode(cbNode, node.assertClause); + }, + [SyntaxKind.ImportClause]: function forEachChildInImportClause(node: ImportClause, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.name) || + visitNode(cbNode, node.namedBindings); + }, + [SyntaxKind.AssertClause]: function forEachChildInAssertClause(node: AssertClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.elements); - } - - function forEachChildInImportOrExportSpecifier(node: ImportSpecifier | ExportSpecifier, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.propertyName) || + }, + [SyntaxKind.AssertEntry]: function forEachChildInAssertEntry(node: AssertEntry, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.name) || + visitNode(cbNode, node.value); + }, + [SyntaxKind.NamespaceExportDeclaration]: function forEachChildInNamespaceExportDeclaration(node: NamespaceExportDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNode(cbNode, node.name); - } - - function forEachChildInJsxOpeningOrSelfClosingElement(node: JsxOpeningLikeElement, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + }, + [SyntaxKind.NamespaceImport]: function forEachChildInNamespaceImport(node: NamespaceImport, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.name); + }, + [SyntaxKind.NamespaceExport]: function forEachChildInNamespaceExport(node: NamespaceExport, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.name); + }, + [SyntaxKind.NamedImports]: forEachChildInNamedImportsOrExports, + [SyntaxKind.NamedExports]: forEachChildInNamedImportsOrExports, + [SyntaxKind.ExportDeclaration]: function forEachChildInExportDeclaration(node: ExportDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.exportClause) || + visitNode(cbNode, node.moduleSpecifier) || + visitNode(cbNode, node.assertClause); + }, + [SyntaxKind.ImportSpecifier]: forEachChildInImportOrExportSpecifier, + [SyntaxKind.ExportSpecifier]: forEachChildInImportOrExportSpecifier, + [SyntaxKind.ExportAssignment]: function forEachChildInExportAssignment(node: ExportAssignment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.expression); + }, + [SyntaxKind.TemplateExpression]: function forEachChildInTemplateExpression(node: TemplateExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.head) || + visitNodes(cbNode, cbNodes, node.templateSpans); + }, + [SyntaxKind.TemplateSpan]: function forEachChildInTemplateSpan(node: TemplateSpan, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression) || + visitNode(cbNode, node.literal); + }, + [SyntaxKind.TemplateLiteralType]: function forEachChildInTemplateLiteralType(node: TemplateLiteralTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.head) || + visitNodes(cbNode, cbNodes, node.templateSpans); + }, + [SyntaxKind.TemplateLiteralTypeSpan]: function forEachChildInTemplateLiteralTypeSpan(node: TemplateLiteralTypeSpan, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.type) || + visitNode(cbNode, node.literal); + }, + [SyntaxKind.ComputedPropertyName]: function forEachChildInComputedPropertyName(node: ComputedPropertyName, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.HeritageClause]: function forEachChildInHeritageClause(node: HeritageClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.types); + }, + [SyntaxKind.ExpressionWithTypeArguments]: function forEachChildInExpressionWithTypeArguments(node: ExpressionWithTypeArguments, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression) || + visitNodes(cbNode, cbNodes, node.typeArguments); + }, + [SyntaxKind.ExternalModuleReference]: function forEachChildInExternalModuleReference(node: ExternalModuleReference, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.MissingDeclaration]: function forEachChildInMissingDeclaration(node: MissingDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.illegalDecorators) || + visitNodes(cbNode, cbNodes, node.modifiers); + }, + [SyntaxKind.CommaListExpression]: function forEachChildInCommaListExpression(node: CommaListExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.elements); + }, + [SyntaxKind.JsxElement]: function forEachChildInJsxElement(node: JsxElement, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.openingElement) || + visitNodes(cbNode, cbNodes, node.children) || + visitNode(cbNode, node.closingElement); + }, + [SyntaxKind.JsxFragment]: function forEachChildInJsxFragment(node: JsxFragment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.openingFragment) || + visitNodes(cbNode, cbNodes, node.children) || + visitNode(cbNode, node.closingFragment); + }, + [SyntaxKind.JsxSelfClosingElement]: forEachChildInJsxOpeningOrSelfClosingElement, + [SyntaxKind.JsxOpeningElement]: forEachChildInJsxOpeningOrSelfClosingElement, + [SyntaxKind.JsxAttributes]: function forEachChildInJsxAttributes(node: JsxAttributes, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.properties); + }, + [SyntaxKind.JsxAttribute]: function forEachChildInJsxAttribute(node: JsxAttribute, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.name) || + visitNode(cbNode, node.initializer); + }, + [SyntaxKind.JsxSpreadAttribute]: function forEachChildInJsxSpreadAttribute(node: JsxSpreadAttribute, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, + [SyntaxKind.JsxExpression]: function forEachChildInJsxExpression(node: JsxExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.dotDotDotToken) || + visitNode(cbNode, node.expression); + }, + [SyntaxKind.JsxClosingElement]: function forEachChildInJsxClosingElement(node: JsxClosingElement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.tagName); + }, + [SyntaxKind.OptionalType]: forEachChildInOptionalRestOrJSDocParameterModifier, + [SyntaxKind.RestType]: forEachChildInOptionalRestOrJSDocParameterModifier, + [SyntaxKind.JSDocTypeExpression]: forEachChildInOptionalRestOrJSDocParameterModifier, + [SyntaxKind.JSDocNonNullableType]: forEachChildInOptionalRestOrJSDocParameterModifier, + [SyntaxKind.JSDocNullableType]: forEachChildInOptionalRestOrJSDocParameterModifier, + [SyntaxKind.JSDocOptionalType]: forEachChildInOptionalRestOrJSDocParameterModifier, + [SyntaxKind.JSDocVariadicType]: forEachChildInOptionalRestOrJSDocParameterModifier, + [SyntaxKind.JSDocFunctionType]: function forEachChildInJSDocFunctionType(node: JSDocFunctionType, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type); + }, + [SyntaxKind.JSDoc]: function forEachChildInJSDoc(node: JSDoc, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)) + || visitNodes(cbNode, cbNodes, node.tags); + }, + [SyntaxKind.JSDocSeeTag]: function forEachChildInJSDocSeeTag(node: JSDocSeeTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || - visitNodes(cbNode, cbNodes, node.typeArguments) || - visitNode(cbNode, node.attributes); - } - - function forEachChildInOptionalRestOrJSDocParameterModifier(node: OptionalTypeNode | RestTypeNode | JSDocTypeExpression | JSDocNullableType | JSDocNonNullableType | JSDocOptionalType | JSDocVariadicType, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.type); - } - - function forEachChildInJSDocParameterOrPropertyTag(node: JSDocParameterTag | JSDocPropertyTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + visitNode(cbNode, node.name) || + (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); + }, + [SyntaxKind.JSDocNameReference]: function forEachChildInJSDocNameReference(node: JSDocNameReference, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.name); + }, + [SyntaxKind.JSDocMemberName]: function forEachChildInJSDocMemberName(node: JSDocMemberName, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.left) || + visitNode(cbNode, node.right); + }, + [SyntaxKind.JSDocParameterTag]: forEachChildInJSDocParameterOrPropertyTag, + [SyntaxKind.JSDocPropertyTag]: forEachChildInJSDocParameterOrPropertyTag, + [SyntaxKind.JSDocAuthorTag]: function forEachChildInJSDocAuthorTag(node: JSDocAuthorTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || - (node.isNameFirst - ? visitNode(cbNode, node.name) || visitNode(cbNode, node.typeExpression) - : visitNode(cbNode, node.typeExpression) || visitNode(cbNode, node.name)) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); - } - - function forEachChildInJSDocReturnTag(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + }, + [SyntaxKind.JSDocImplementsTag]: function forEachChildInJSDocImplementsTag(node: JSDocImplementsTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.tagName) || + visitNode(cbNode, node.class) || + (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); + }, + [SyntaxKind.JSDocAugmentsTag]: function forEachChildInJSDocAugmentsTag(node: JSDocAugmentsTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || + visitNode(cbNode, node.class) || + (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); + }, + [SyntaxKind.JSDocTemplateTag]: function forEachChildInJSDocTemplateTag(node: JSDocTemplateTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.tagName) || + visitNode(cbNode, node.constraint) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); + }, + [SyntaxKind.JSDocTypedefTag]: function forEachChildInJSDocTypedefTag(node: JSDocTypedefTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.tagName) || + (node.typeExpression && + node.typeExpression.kind === SyntaxKind.JSDocTypeExpression + ? visitNode(cbNode, node.typeExpression) || + visitNode(cbNode, node.fullName) || + (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)) + : visitNode(cbNode, node.fullName) || + visitNode(cbNode, node.typeExpression) || + (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment))); + }, + [SyntaxKind.JSDocCallbackTag]: function forEachChildInJSDocCallbackTag(node: JSDocCallbackTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.tagName) || + visitNode(cbNode, node.fullName) || visitNode(cbNode, node.typeExpression) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); - } + }, + [SyntaxKind.JSDocReturnTag]: forEachChildInJSDocReturnTag, + [SyntaxKind.JSDocTypeTag]: forEachChildInJSDocReturnTag, + [SyntaxKind.JSDocThisTag]: forEachChildInJSDocReturnTag, + [SyntaxKind.JSDocEnumTag]: forEachChildInJSDocReturnTag, + [SyntaxKind.JSDocSignature]: function forEachChildInJSDocSignature(node: JSDocSignature, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return forEach(node.typeParameters, cbNode) || + forEach(node.parameters, cbNode) || + visitNode(cbNode, node.type); + }, + [SyntaxKind.JSDocLink]: forEachChildInJSDocLinkCodeOrPlain, + [SyntaxKind.JSDocLinkCode]: forEachChildInJSDocLinkCodeOrPlain, + [SyntaxKind.JSDocLinkPlain]: forEachChildInJSDocLinkCodeOrPlain, + [SyntaxKind.JSDocTypeLiteral]: function forEachChildInJSDocTypeLiteral(node: JSDocTypeLiteral, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return forEach(node.jsDocPropertyTags, cbNode); + }, + [SyntaxKind.JSDocTag]: forEachChildInJSDocTag, + [SyntaxKind.JSDocClassTag]: forEachChildInJSDocTag, + [SyntaxKind.JSDocPublicTag]: forEachChildInJSDocTag, + [SyntaxKind.JSDocPrivateTag]: forEachChildInJSDocTag, + [SyntaxKind.JSDocProtectedTag]: forEachChildInJSDocTag, + [SyntaxKind.JSDocReadonlyTag]: forEachChildInJSDocTag, + [SyntaxKind.JSDocDeprecatedTag]: forEachChildInJSDocTag, + [SyntaxKind.JSDocOverrideTag]: forEachChildInJSDocTag, + [SyntaxKind.PartiallyEmittedExpression]: forEachChildInPartiallyEmittedExpression, +}; + +// shared + +function forEachChildInCallOrConstructSignature(node: CallSignatureDeclaration | ConstructSignatureDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.parameters) || + visitNode(cbNode, node.type); +} - function forEachChildInJSDocLinkCodeOrPlain(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.name); - } +function forEachChildInUnionOrIntersectionType(node: UnionTypeNode | IntersectionTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.types); +} - function forEachChildInJSDocTag(node: JSDocUnknownTag | JSDocClassTag | JSDocPublicTag | JSDocPrivateTag | JSDocProtectedTag | JSDocReadonlyTag | JSDocDeprecatedTag | JSDocOverrideTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.tagName) - || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); - } +function forEachChildInParenthesizedTypeOrTypeOperator(node: ParenthesizedTypeNode | TypeOperatorNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.type); +} - function forEachChildInPartiallyEmittedExpression(node: PartiallyEmittedExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - return visitNode(cbNode, node.expression); - } +function forEachChildInObjectOrArrayBindingPattern(node: BindingPattern, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.elements); +} - /** - * Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes - * stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise, - * embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns - * a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. - * - * @param node a given node to visit its children - * @param cbNode a callback to be invoked for all child nodes - * @param cbNodes a callback to be invoked for embedded array - * - * @remarks `forEachChild` must visit the children of a node in the order - * that they appear in the source code. The language service depends on this property to locate nodes by position. - */ - export function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { - if (node === undefined || node.kind <= SyntaxKind.LastToken) { - return; - } - const fn = (forEachChildTable as Record>)[node.kind]; - return fn === undefined ? undefined : fn(node, cbNode, cbNodes); - } +function forEachChildInCallOrNewExpression(node: CallExpression | NewExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression) || + // TODO: should we separate these branches out? + visitNode(cbNode, (node as CallExpression).questionDotToken) || + visitNodes(cbNode, cbNodes, node.typeArguments) || + visitNodes(cbNode, cbNodes, node.arguments); +} - /** @internal */ - /** - * Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes - * stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; additionally, - * unlike `forEachChild`, embedded arrays are flattened and the 'cbNode' callback is invoked for each element. - * If a callback returns a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. - * - * @param node a given node to visit its children - * @param cbNode a callback to be invoked for all child nodes - * @param cbNodes a callback to be invoked for embedded array - * - * @remarks Unlike `forEachChild`, `forEachChildRecursively` handles recursively invoking the traversal on each child node found, - * and while doing so, handles traversing the structure without relying on the callstack to encode the tree structure. - */ - export function forEachChildRecursively(rootNode: Node, cbNode: (node: Node, parent: Node) => T | "skip" | undefined, cbNodes?: (nodes: NodeArray, parent: Node) => T | "skip" | undefined): T | undefined { - const queue: (Node | NodeArray)[] = gatherPossibleChildren(rootNode); - const parents: Node[] = []; // tracks parent references for elements in queue - while (parents.length < queue.length) { - parents.push(rootNode); - } - while (queue.length !== 0) { - const current = queue.pop()!; - const parent = parents.pop()!; - if (isArray(current)) { - if (cbNodes) { - const res = cbNodes(current, parent); - if (res) { - if (res === "skip") continue; - return res; - } - } - for (let i = current.length - 1; i >= 0; --i) { - queue.push(current[i]); - parents.push(parent); - } - } - else { - const res = cbNode(current, parent); - if (res) { - if (res === "skip") continue; - return res; - } - if (current.kind >= SyntaxKind.FirstNode) { - // add children in reverse order to the queue, so popping gives the first child - for (const child of gatherPossibleChildren(current)) { - queue.push(child); - parents.push(current); - } - } - } - } - } +function forEachChildInBlock(node: Block | ModuleBlock, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.statements); +} - function gatherPossibleChildren(node: Node) { - const children: (Node | NodeArray)[] = []; - forEachChild(node, addWorkItem, addWorkItem); // By using a stack above and `unshift` here, we emulate a depth-first preorder traversal - return children; +function forEachChildInContinueOrBreakStatement(node: ContinueStatement | BreakStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.label); +} - function addWorkItem(n: Node | NodeArray) { - children.unshift(n); - } - } +function forEachChildInClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.modifiers) || + visitNode(cbNode, node.name) || + visitNodes(cbNode, cbNodes, node.typeParameters) || + visitNodes(cbNode, cbNodes, node.heritageClauses) || + visitNodes(cbNode, cbNodes, node.members); +} - export interface CreateSourceFileOptions { - languageVersion: ScriptTarget; - /** - * Controls the format the file is detected as - this can be derived from only the path - * and files on disk, but needs to be done with a module resolution cache in scope to be performant. - * This is usually `undefined` for compilations that do not have `moduleResolution` values of `node16` or `nodenext`. - */ - impliedNodeFormat?: ModuleKind.ESNext | ModuleKind.CommonJS; - /** - * Controls how module-y-ness is set for the given file. Usually the result of calling - * `getSetExternalModuleIndicator` on a valid `CompilerOptions` object. If not present, the default - * check specified by `isFileProbablyExternalModule` will be used to set the field. - */ - setExternalModuleIndicator?: (file: SourceFile) => void; - /*@internal*/ packageJsonLocations?: readonly string[]; - /*@internal*/ packageJsonScope?: PackageJsonInfo; - } +function forEachChildInNamedImportsOrExports(node: NamedImports | NamedExports, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNodes(cbNode, cbNodes, node.elements); +} - function setExternalModuleIndicator(sourceFile: SourceFile) { - sourceFile.externalModuleIndicator = isFileProbablyExternalModule(sourceFile); - } +function forEachChildInImportOrExportSpecifier(node: ImportSpecifier | ExportSpecifier, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.propertyName) || + visitNode(cbNode, node.name); +} - export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { - tracing?.push(tracing.Phase.Parse, "createSourceFile", { path: fileName }, /*separateBeginAndEnd*/ true); - performance.mark("beforeParse"); - let result: SourceFile; +function forEachChildInJsxOpeningOrSelfClosingElement(node: JsxOpeningLikeElement, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.tagName) || + visitNodes(cbNode, cbNodes, node.typeArguments) || + visitNode(cbNode, node.attributes); +} - perfLogger.logStartParseSourceFile(fileName); - const { - languageVersion, - setExternalModuleIndicator: overrideSetExternalModuleIndicator, - impliedNodeFormat: format - } = typeof languageVersionOrOptions === "object" ? languageVersionOrOptions : ({ languageVersion: languageVersionOrOptions } as CreateSourceFileOptions); - if (languageVersion === ScriptTarget.JSON) { - result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON, noop); - } - else { - const setIndicator = format === undefined ? overrideSetExternalModuleIndicator : (file: SourceFile) => { - file.impliedNodeFormat = format; - return (overrideSetExternalModuleIndicator || setExternalModuleIndicator)(file); - }; - result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind, setIndicator); - } - perfLogger.logStopParseSourceFile(); +function forEachChildInOptionalRestOrJSDocParameterModifier(node: OptionalTypeNode | RestTypeNode | JSDocTypeExpression | JSDocNullableType | JSDocNonNullableType | JSDocOptionalType | JSDocVariadicType, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.type); +} - performance.mark("afterParse"); - performance.measure("Parse", "beforeParse", "afterParse"); - tracing?.pop(); - return result; - } +function forEachChildInJSDocParameterOrPropertyTag(node: JSDocParameterTag | JSDocPropertyTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.tagName) || + (node.isNameFirst + ? visitNode(cbNode, node.name) || visitNode(cbNode, node.typeExpression) + : visitNode(cbNode, node.typeExpression) || visitNode(cbNode, node.name)) || + (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); +} - export function parseIsolatedEntityName(text: string, languageVersion: ScriptTarget): EntityName | undefined { - return Parser.parseIsolatedEntityName(text, languageVersion); - } +function forEachChildInJSDocReturnTag(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.tagName) || + visitNode(cbNode, node.typeExpression) || + (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); +} - /** - * Parse json text into SyntaxTree and return node and parse errors if any - * @param fileName - * @param sourceText - */ - export function parseJsonText(fileName: string, sourceText: string): JsonSourceFile { - return Parser.parseJsonText(fileName, sourceText); - } +function forEachChildInJSDocLinkCodeOrPlain(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.name); +} - // See also `isExternalOrCommonJsModule` in utilities.ts - export function isExternalModule(file: SourceFile): boolean { - return file.externalModuleIndicator !== undefined; - } +function forEachChildInJSDocTag(node: JSDocUnknownTag | JSDocClassTag | JSDocPublicTag | JSDocPrivateTag | JSDocProtectedTag | JSDocReadonlyTag | JSDocDeprecatedTag | JSDocOverrideTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.tagName) + || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); +} - // Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter - // indicates what changed between the 'text' that this SourceFile has and the 'newText'. - // The SourceFile will be created with the compiler attempting to reuse as many nodes from - // this file as possible. - // - // Note: this function mutates nodes from this SourceFile. That means any existing nodes - // from this SourceFile that are being held onto may change as a result (including - // becoming detached from any SourceFile). It is recommended that this SourceFile not - // be used once 'update' is called on it. - export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks = false): SourceFile { - const newSourceFile = IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); - // Because new source file node is created, it may not have the flag PossiblyContainDynamicImport. This is the case if there is no new edit to add dynamic import. - // We will manually port the flag to the new source file. - (newSourceFile as Mutable).flags |= (sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags); - return newSourceFile; - } - - /* @internal */ - export function parseIsolatedJSDocComment(content: string, start?: number, length?: number) { - const result = Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length); - if (result && result.jsDoc) { - // because the jsDocComment was parsed out of the source file, it might - // not be covered by the fixupParentReferences. - Parser.fixupParentReferences(result.jsDoc); - } +function forEachChildInPartiallyEmittedExpression(node: PartiallyEmittedExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); +} - return result; +/** + * Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes + * stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise, + * embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns + * a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. + * + * @param node a given node to visit its children + * @param cbNode a callback to be invoked for all child nodes + * @param cbNodes a callback to be invoked for embedded array + * + * @remarks `forEachChild` must visit the children of a node in the order + * that they appear in the source code. The language service depends on this property to locate nodes by position. + */ +export function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + if (node === undefined || node.kind <= SyntaxKind.LastToken) { + return; } + const fn = (forEachChildTable as Record>)[node.kind]; + return fn === undefined ? undefined : fn(node, cbNode, cbNodes); +} - /* @internal */ - // Exposed only for testing. - export function parseJSDocTypeExpressionForTests(content: string, start?: number, length?: number) { - return Parser.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length); +/** + * Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes + * stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; additionally, + * unlike `forEachChild`, embedded arrays are flattened and the 'cbNode' callback is invoked for each element. + * If a callback returns a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. + * + * @param node a given node to visit its children + * @param cbNode a callback to be invoked for all child nodes + * @param cbNodes a callback to be invoked for embedded array + * + * @remarks Unlike `forEachChild`, `forEachChildRecursively` handles recursively invoking the traversal on each child node found, + * and while doing so, handles traversing the structure without relying on the callstack to encode the tree structure. + * + * @internal + */ +export function forEachChildRecursively(rootNode: Node, cbNode: (node: Node, parent: Node) => T | "skip" | undefined, cbNodes?: (nodes: NodeArray, parent: Node) => T | "skip" | undefined): T | undefined { + const queue: (Node | NodeArray)[] = gatherPossibleChildren(rootNode); + const parents: Node[] = []; // tracks parent references for elements in queue + while (parents.length < queue.length) { + parents.push(rootNode); } - - // Implement the parser as a singleton module. We do this for perf reasons because creating - // parser instances can actually be expensive enough to impact us on projects with many source - // files. - namespace Parser { - // Share a single scanner across all calls to parse a source file. This helps speed things - // up by avoiding the cost of creating/compiling scanners over and over again. - const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); - - const disallowInAndDecoratorContext = NodeFlags.DisallowInContext | NodeFlags.DecoratorContext; - - // capture constructors in 'initializeState' to avoid null checks - let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - let IdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - - function countNode(node: Node) { - nodeCount++; - return node; - } - - // Rather than using `createBaseNodeFactory` here, we establish a `BaseNodeFactory` that closes over the - // constructors above, which are reset each time `initializeState` is called. - const baseNodeFactory: BaseNodeFactory = { - createBaseSourceFileNode: kind => countNode(new SourceFileConstructor(kind, /*pos*/ 0, /*end*/ 0)), - createBaseIdentifierNode: kind => countNode(new IdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)), - createBasePrivateIdentifierNode: kind => countNode(new PrivateIdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)), - createBaseTokenNode: kind => countNode(new TokenConstructor(kind, /*pos*/ 0, /*end*/ 0)), - createBaseNode: kind => countNode(new NodeConstructor(kind, /*pos*/ 0, /*end*/ 0)) - }; - - const factory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules | NodeFactoryFlags.NoNodeConverters | NodeFactoryFlags.NoOriginalNode, baseNodeFactory); - - let fileName: string; - let sourceFlags: NodeFlags; - let sourceText: string; - let languageVersion: ScriptTarget; - let scriptKind: ScriptKind; - let languageVariant: LanguageVariant; - let parseDiagnostics: DiagnosticWithDetachedLocation[]; - let jsDocDiagnostics: DiagnosticWithDetachedLocation[]; - let syntaxCursor: IncrementalParser.SyntaxCursor | undefined; - - let currentToken: SyntaxKind; - let nodeCount: number; - let identifiers: ESMap; - let privateIdentifiers: ESMap; - let identifierCount: number; - - let parsingContext: ParsingContext; - - let notParenthesizedArrow: Set | undefined; - - // Flags that dictate what parsing context we're in. For example: - // Whether or not we are in strict parsing mode. All that changes in strict parsing mode is - // that some tokens that would be considered identifiers may be considered keywords. - // - // When adding more parser context flags, consider which is the more common case that the - // flag will be in. This should be the 'false' state for that flag. The reason for this is - // that we don't store data in our nodes unless the value is in the *non-default* state. So, - // for example, more often than code 'allows-in' (or doesn't 'disallow-in'). We opt for - // 'disallow-in' set to 'false'. Otherwise, if we had 'allowsIn' set to 'true', then almost - // all nodes would need extra state on them to store this info. - // - // Note: 'allowIn' and 'allowYield' track 1:1 with the [in] and [yield] concepts in the ES6 - // grammar specification. - // - // An important thing about these context concepts. By default they are effectively inherited - // while parsing through every grammar production. i.e. if you don't change them, then when - // you parse a sub-production, it will have the same context values as the parent production. - // This is great most of the time. After all, consider all the 'expression' grammar productions - // and how nearly all of them pass along the 'in' and 'yield' context values: - // - // EqualityExpression[In, Yield] : - // RelationalExpression[?In, ?Yield] - // EqualityExpression[?In, ?Yield] == RelationalExpression[?In, ?Yield] - // EqualityExpression[?In, ?Yield] != RelationalExpression[?In, ?Yield] - // EqualityExpression[?In, ?Yield] === RelationalExpression[?In, ?Yield] - // EqualityExpression[?In, ?Yield] !== RelationalExpression[?In, ?Yield] - // - // Where you have to be careful is then understanding what the points are in the grammar - // where the values are *not* passed along. For example: - // - // SingleNameBinding[Yield,GeneratorParameter] - // [+GeneratorParameter]BindingIdentifier[Yield] Initializer[In]opt - // [~GeneratorParameter]BindingIdentifier[?Yield]Initializer[In, ?Yield]opt - // - // Here this is saying that if the GeneratorParameter context flag is set, that we should - // explicitly set the 'yield' context flag to false before calling into the BindingIdentifier - // and we should explicitly unset the 'yield' context flag before calling into the Initializer. - // production. Conversely, if the GeneratorParameter context flag is not set, then we - // should leave the 'yield' context flag alone. - // - // Getting this all correct is tricky and requires careful reading of the grammar to - // understand when these values should be changed versus when they should be inherited. - // - // Note: it should not be necessary to save/restore these flags during speculative/lookahead - // parsing. These context flags are naturally stored and restored through normal recursive - // descent parsing and unwinding. - let contextFlags: NodeFlags; - - // Indicates whether we are currently parsing top-level statements. - let topLevel = true; - - // Whether or not we've had a parse error since creating the last AST node. If we have - // encountered an error, it will be stored on the next AST node we create. Parse errors - // can be broken down into three categories: - // - // 1) An error that occurred during scanning. For example, an unterminated literal, or a - // character that was completely not understood. - // - // 2) A token was expected, but was not present. This type of error is commonly produced - // by the 'parseExpected' function. - // - // 3) A token was present that no parsing function was able to consume. This type of error - // only occurs in the 'abortParsingListOrMoveToNextToken' function when the parser - // decides to skip the token. - // - // In all of these cases, we want to mark the next node as having had an error before it. - // With this mark, we can know in incremental settings if this node can be reused, or if - // we have to reparse it. If we don't keep this information around, we may just reuse the - // node. in that event we would then not produce the same errors as we did before, causing - // significant confusion problems. - // - // Note: it is necessary that this value be saved/restored during speculative/lookahead - // parsing. During lookahead parsing, we will often create a node. That node will have - // this value attached, and then this value will be set back to 'false'. If we decide to - // rewind, we must get back to the same value we had prior to the lookahead. - // - // Note: any errors at the end of the file that do not precede a regular node, should get - // attached to the EOF token. - let parseErrorBeforeNextFinishedNode = false; - - export function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, scriptKind?: ScriptKind, setExternalModuleIndicatorOverride?: (file: SourceFile) => void): SourceFile { - scriptKind = ensureScriptKind(fileName, scriptKind); - if (scriptKind === ScriptKind.JSON) { - const result = parseJsonText(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes); - convertToObjectWorker(result, result.statements[0]?.expression, result.parseDiagnostics, /*returnValue*/ false, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); - result.referencedFiles = emptyArray; - result.typeReferenceDirectives = emptyArray; - result.libReferenceDirectives = emptyArray; - result.amdDependencies = emptyArray; - result.hasNoDefaultLib = false; - result.pragmas = emptyMap as ReadonlyPragmaMap; - return result; + while (queue.length !== 0) { + const current = queue.pop()!; + const parent = parents.pop()!; + if (isArray(current)) { + if (cbNodes) { + const res = cbNodes(current, parent); + if (res) { + if (res === "skip") continue; + return res; + } + } + for (let i = current.length - 1; i >= 0; --i) { + queue.push(current[i]); + parents.push(parent); } - - initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind); - - const result = parseSourceFileWorker(languageVersion, setParentNodes, scriptKind, setExternalModuleIndicatorOverride || setExternalModuleIndicator); - - clearState(); - - return result; } - - export function parseIsolatedEntityName(content: string, languageVersion: ScriptTarget): EntityName | undefined { - // Choice of `isDeclarationFile` should be arbitrary - initializeState("", content, languageVersion, /*syntaxCursor*/ undefined, ScriptKind.JS); - // Prime the scanner. - nextToken(); - const entityName = parseEntityName(/*allowReservedWords*/ true); - const isInvalid = token() === SyntaxKind.EndOfFileToken && !parseDiagnostics.length; - clearState(); - return isInvalid ? entityName : undefined; + else { + const res = cbNode(current, parent); + if (res) { + if (res === "skip") continue; + return res; + } + if (current.kind >= SyntaxKind.FirstNode) { + // add children in reverse order to the queue, so popping gives the first child + for (const child of gatherPossibleChildren(current)) { + queue.push(child); + parents.push(current); + } + } } + } +} - export function parseJsonText(fileName: string, sourceText: string, languageVersion: ScriptTarget = ScriptTarget.ES2015, syntaxCursor?: IncrementalParser.SyntaxCursor, setParentNodes = false): JsonSourceFile { - initializeState(fileName, sourceText, languageVersion, syntaxCursor, ScriptKind.JSON); - sourceFlags = contextFlags; - - // Prime the scanner. - nextToken(); - const pos = getNodePos(); - let statements, endOfFileToken; - if (token() === SyntaxKind.EndOfFileToken) { - statements = createNodeArray([], pos, pos); - endOfFileToken = parseTokenNode(); - } - else { - // Loop and synthesize an ArrayLiteralExpression if there are more than - // one top-level expressions to ensure all input text is consumed. - let expressions: Expression[] | Expression | undefined; - while (token() !== SyntaxKind.EndOfFileToken) { - let expression; - switch (token()) { - case SyntaxKind.OpenBracketToken: - expression = parseArrayLiteralExpression(); - break; - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - expression = parseTokenNode(); - break; - case SyntaxKind.MinusToken: - if (lookAhead(() => nextToken() === SyntaxKind.NumericLiteral && nextToken() !== SyntaxKind.ColonToken)) { - expression = parsePrefixUnaryExpression() as JsonMinusNumericLiteral; - } - else { - expression = parseObjectLiteralExpression(); - } - break; - case SyntaxKind.NumericLiteral: - case SyntaxKind.StringLiteral: - if (lookAhead(() => nextToken() !== SyntaxKind.ColonToken)) { - expression = parseLiteralNode() as StringLiteral | NumericLiteral; - break; - } - // falls through - default: - expression = parseObjectLiteralExpression(); - break; - } +function gatherPossibleChildren(node: Node) { + const children: (Node | NodeArray)[] = []; + forEachChild(node, addWorkItem, addWorkItem); // By using a stack above and `unshift` here, we emulate a depth-first preorder traversal + return children; - // Error recovery: collect multiple top-level expressions - if (expressions && isArray(expressions)) { - expressions.push(expression); - } - else if (expressions) { - expressions = [expressions, expression]; - } - else { - expressions = expression; - if (token() !== SyntaxKind.EndOfFileToken) { - parseErrorAtCurrentToken(Diagnostics.Unexpected_token); - } - } - } + function addWorkItem(n: Node | NodeArray) { + children.unshift(n); + } +} - const expression = isArray(expressions) ? finishNode(factory.createArrayLiteralExpression(expressions), pos) : Debug.checkDefined(expressions); - const statement = factory.createExpressionStatement(expression) as JsonObjectExpressionStatement; - finishNode(statement, pos); - statements = createNodeArray([statement], pos); - endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token); - } +export interface CreateSourceFileOptions { + languageVersion: ScriptTarget; + /** + * Controls the format the file is detected as - this can be derived from only the path + * and files on disk, but needs to be done with a module resolution cache in scope to be performant. + * This is usually `undefined` for compilations that do not have `moduleResolution` values of `node16` or `nodenext`. + */ + impliedNodeFormat?: ModuleKind.ESNext | ModuleKind.CommonJS; + /** + * Controls how module-y-ness is set for the given file. Usually the result of calling + * `getSetExternalModuleIndicator` on a valid `CompilerOptions` object. If not present, the default + * check specified by `isFileProbablyExternalModule` will be used to set the field. + */ + setExternalModuleIndicator?: (file: SourceFile) => void; + /** @internal */ packageJsonLocations?: readonly string[]; + /** @internal */ packageJsonScope?: PackageJsonInfo; +} - // Set source file so that errors will be reported with this file name - const sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON, /*isDeclaration*/ false, statements, endOfFileToken, sourceFlags, noop); +function setExternalModuleIndicator(sourceFile: SourceFile) { + sourceFile.externalModuleIndicator = isFileProbablyExternalModule(sourceFile); +} - if (setParentNodes) { - fixupParentReferences(sourceFile); - } +export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes = false, scriptKind?: ScriptKind): SourceFile { + tracing?.push(tracing.Phase.Parse, "createSourceFile", { path: fileName }, /*separateBeginAndEnd*/ true); + performance.mark("beforeParse"); + let result: SourceFile; + + perfLogger.logStartParseSourceFile(fileName); + const { + languageVersion, + setExternalModuleIndicator: overrideSetExternalModuleIndicator, + impliedNodeFormat: format + } = typeof languageVersionOrOptions === "object" ? languageVersionOrOptions : ({ languageVersion: languageVersionOrOptions } as CreateSourceFileOptions); + if (languageVersion === ScriptTarget.JSON) { + result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON, noop); + } + else { + const setIndicator = format === undefined ? overrideSetExternalModuleIndicator : (file: SourceFile) => { + file.impliedNodeFormat = format; + return (overrideSetExternalModuleIndicator || setExternalModuleIndicator)(file); + }; + result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind, setIndicator); + } + perfLogger.logStopParseSourceFile(); - sourceFile.nodeCount = nodeCount; - sourceFile.identifierCount = identifierCount; - sourceFile.identifiers = identifiers; - sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); - if (jsDocDiagnostics) { - sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); - } + performance.mark("afterParse"); + performance.measure("Parse", "beforeParse", "afterParse"); + tracing?.pop(); + return result; +} - const result = sourceFile as JsonSourceFile; - clearState(); - return result; - } +export function parseIsolatedEntityName(text: string, languageVersion: ScriptTarget): EntityName | undefined { + return Parser.parseIsolatedEntityName(text, languageVersion); +} - function initializeState(_fileName: string, _sourceText: string, _languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, _scriptKind: ScriptKind) { - NodeConstructor = objectAllocator.getNodeConstructor(); - TokenConstructor = objectAllocator.getTokenConstructor(); - IdentifierConstructor = objectAllocator.getIdentifierConstructor(); - PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor(); - SourceFileConstructor = objectAllocator.getSourceFileConstructor(); - - fileName = normalizePath(_fileName); - sourceText = _sourceText; - languageVersion = _languageVersion; - syntaxCursor = _syntaxCursor; - scriptKind = _scriptKind; - languageVariant = getLanguageVariant(_scriptKind); - - parseDiagnostics = []; - parsingContext = 0; - identifiers = new Map(); - privateIdentifiers = new Map(); - identifierCount = 0; - nodeCount = 0; - sourceFlags = 0; - topLevel = true; - - switch (scriptKind) { - case ScriptKind.JS: - case ScriptKind.JSX: - contextFlags = NodeFlags.JavaScriptFile; - break; - case ScriptKind.JSON: - contextFlags = NodeFlags.JavaScriptFile | NodeFlags.JsonFile; - break; - default: - contextFlags = NodeFlags.None; - break; - } - parseErrorBeforeNextFinishedNode = false; +/** + * Parse json text into SyntaxTree and return node and parse errors if any + * @param fileName + * @param sourceText + */ +export function parseJsonText(fileName: string, sourceText: string): JsonSourceFile { + return Parser.parseJsonText(fileName, sourceText); +} - // Initialize and prime the scanner before parsing the source elements. - scanner.setText(sourceText); - scanner.setOnError(scanError); - scanner.setScriptTarget(languageVersion); - scanner.setLanguageVariant(languageVariant); - } +// See also `isExternalOrCommonJsModule` in utilities.ts +export function isExternalModule(file: SourceFile): boolean { + return file.externalModuleIndicator !== undefined; +} - function clearState() { - // Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily. - scanner.clearCommentDirectives(); - scanner.setText(""); - scanner.setOnError(undefined); +// Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter +// indicates what changed between the 'text' that this SourceFile has and the 'newText'. +// The SourceFile will be created with the compiler attempting to reuse as many nodes from +// this file as possible. +// +// Note: this function mutates nodes from this SourceFile. That means any existing nodes +// from this SourceFile that are being held onto may change as a result (including +// becoming detached from any SourceFile). It is recommended that this SourceFile not +// be used once 'update' is called on it. +export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks = false): SourceFile { + const newSourceFile = IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); + // Because new source file node is created, it may not have the flag PossiblyContainDynamicImport. This is the case if there is no new edit to add dynamic import. + // We will manually port the flag to the new source file. + (newSourceFile as Mutable).flags |= (sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags); + return newSourceFile; +} - // Clear any data. We don't want to accidentally hold onto it for too long. - sourceText = undefined!; - languageVersion = undefined!; - syntaxCursor = undefined; - scriptKind = undefined!; - languageVariant = undefined!; - sourceFlags = 0; - parseDiagnostics = undefined!; - jsDocDiagnostics = undefined!; - parsingContext = 0; - identifiers = undefined!; - notParenthesizedArrow = undefined; - topLevel = true; - } +/** @internal */ +export function parseIsolatedJSDocComment(content: string, start?: number, length?: number) { + const result = Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length); + if (result && result.jsDoc) { + // because the jsDocComment was parsed out of the source file, it might + // not be covered by the fixupParentReferences. + Parser.fixupParentReferences(result.jsDoc); + } - function parseSourceFileWorker(languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind, setExternalModuleIndicator: (file: SourceFile) => void): SourceFile { - const isDeclarationFile = isDeclarationFileName(fileName); - if (isDeclarationFile) { - contextFlags |= NodeFlags.Ambient; - } + return result; +} - sourceFlags = contextFlags; +/** @internal */ +// Exposed only for testing. +export function parseJSDocTypeExpressionForTests(content: string, start?: number, length?: number) { + return Parser.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length); +} - // Prime the scanner. - nextToken(); +// Implement the parser as a singleton module. We do this for perf reasons because creating +// parser instances can actually be expensive enough to impact us on projects with many source +// files. +namespace Parser { + // Share a single scanner across all calls to parse a source file. This helps speed things + // up by avoiding the cost of creating/compiling scanners over and over again. + const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); + + const disallowInAndDecoratorContext = NodeFlags.DisallowInContext | NodeFlags.DecoratorContext; + + // capture constructors in 'initializeState' to avoid null checks + let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let IdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; + + function countNode(node: Node) { + nodeCount++; + return node; + } - const statements = parseList(ParsingContext.SourceElements, parseStatement); - Debug.assert(token() === SyntaxKind.EndOfFileToken); - const endOfFileToken = addJSDocComment(parseTokenNode()); + // Rather than using `createBaseNodeFactory` here, we establish a `BaseNodeFactory` that closes over the + // constructors above, which are reset each time `initializeState` is called. + const baseNodeFactory: BaseNodeFactory = { + createBaseSourceFileNode: kind => countNode(new SourceFileConstructor(kind, /*pos*/ 0, /*end*/ 0)), + createBaseIdentifierNode: kind => countNode(new IdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)), + createBasePrivateIdentifierNode: kind => countNode(new PrivateIdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)), + createBaseTokenNode: kind => countNode(new TokenConstructor(kind, /*pos*/ 0, /*end*/ 0)), + createBaseNode: kind => countNode(new NodeConstructor(kind, /*pos*/ 0, /*end*/ 0)) + }; - const sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile, statements, endOfFileToken, sourceFlags, setExternalModuleIndicator); + const factory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules | NodeFactoryFlags.NoNodeConverters | NodeFactoryFlags.NoOriginalNode, baseNodeFactory); - // A member of ReadonlyArray isn't assignable to a member of T[] (and prevents a direct cast) - but this is where we set up those members so they can be readonly in the future - processCommentPragmas(sourceFile as {} as PragmaContext, sourceText); - processPragmasIntoFields(sourceFile as {} as PragmaContext, reportPragmaDiagnostic); + let fileName: string; + let sourceFlags: NodeFlags; + let sourceText: string; + let languageVersion: ScriptTarget; + let scriptKind: ScriptKind; + let languageVariant: LanguageVariant; + let parseDiagnostics: DiagnosticWithDetachedLocation[]; + let jsDocDiagnostics: DiagnosticWithDetachedLocation[]; + let syntaxCursor: IncrementalParser.SyntaxCursor | undefined; - sourceFile.commentDirectives = scanner.getCommentDirectives(); - sourceFile.nodeCount = nodeCount; - sourceFile.identifierCount = identifierCount; - sourceFile.identifiers = identifiers; - sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); - if (jsDocDiagnostics) { - sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); - } + let currentToken: SyntaxKind; + let nodeCount: number; + let identifiers: ESMap; + let privateIdentifiers: ESMap; + let identifierCount: number; - if (setParentNodes) { - fixupParentReferences(sourceFile); - } + let parsingContext: ParsingContext; - return sourceFile; + let notParenthesizedArrow: Set | undefined; - function reportPragmaDiagnostic(pos: number, end: number, diagnostic: DiagnosticMessage) { - parseDiagnostics.push(createDetachedDiagnostic(fileName, pos, end, diagnostic)); - } - } + // Flags that dictate what parsing context we're in. For example: + // Whether or not we are in strict parsing mode. All that changes in strict parsing mode is + // that some tokens that would be considered identifiers may be considered keywords. + // + // When adding more parser context flags, consider which is the more common case that the + // flag will be in. This should be the 'false' state for that flag. The reason for this is + // that we don't store data in our nodes unless the value is in the *non-default* state. So, + // for example, more often than code 'allows-in' (or doesn't 'disallow-in'). We opt for + // 'disallow-in' set to 'false'. Otherwise, if we had 'allowsIn' set to 'true', then almost + // all nodes would need extra state on them to store this info. + // + // Note: 'allowIn' and 'allowYield' track 1:1 with the [in] and [yield] concepts in the ES6 + // grammar specification. + // + // An important thing about these context concepts. By default they are effectively inherited + // while parsing through every grammar production. i.e. if you don't change them, then when + // you parse a sub-production, it will have the same context values as the parent production. + // This is great most of the time. After all, consider all the 'expression' grammar productions + // and how nearly all of them pass along the 'in' and 'yield' context values: + // + // EqualityExpression[In, Yield] : + // RelationalExpression[?In, ?Yield] + // EqualityExpression[?In, ?Yield] == RelationalExpression[?In, ?Yield] + // EqualityExpression[?In, ?Yield] != RelationalExpression[?In, ?Yield] + // EqualityExpression[?In, ?Yield] === RelationalExpression[?In, ?Yield] + // EqualityExpression[?In, ?Yield] !== RelationalExpression[?In, ?Yield] + // + // Where you have to be careful is then understanding what the points are in the grammar + // where the values are *not* passed along. For example: + // + // SingleNameBinding[Yield,GeneratorParameter] + // [+GeneratorParameter]BindingIdentifier[Yield] Initializer[In]opt + // [~GeneratorParameter]BindingIdentifier[?Yield]Initializer[In, ?Yield]opt + // + // Here this is saying that if the GeneratorParameter context flag is set, that we should + // explicitly set the 'yield' context flag to false before calling into the BindingIdentifier + // and we should explicitly unset the 'yield' context flag before calling into the Initializer. + // production. Conversely, if the GeneratorParameter context flag is not set, then we + // should leave the 'yield' context flag alone. + // + // Getting this all correct is tricky and requires careful reading of the grammar to + // understand when these values should be changed versus when they should be inherited. + // + // Note: it should not be necessary to save/restore these flags during speculative/lookahead + // parsing. These context flags are naturally stored and restored through normal recursive + // descent parsing and unwinding. + let contextFlags: NodeFlags; - function withJSDoc(node: T, hasJSDoc: boolean): T { - return hasJSDoc ? addJSDocComment(node) : node; - } + // Indicates whether we are currently parsing top-level statements. + let topLevel = true; - let hasDeprecatedTag = false; - function addJSDocComment(node: T): T { - Debug.assert(!node.jsDoc); // Should only be called once per node - const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos)); - if (jsDoc.length) node.jsDoc = jsDoc; - if (hasDeprecatedTag) { - hasDeprecatedTag = false; - (node as Mutable).flags |= NodeFlags.Deprecated; - } - return node; + // Whether or not we've had a parse error since creating the last AST node. If we have + // encountered an error, it will be stored on the next AST node we create. Parse errors + // can be broken down into three categories: + // + // 1) An error that occurred during scanning. For example, an unterminated literal, or a + // character that was completely not understood. + // + // 2) A token was expected, but was not present. This type of error is commonly produced + // by the 'parseExpected' function. + // + // 3) A token was present that no parsing function was able to consume. This type of error + // only occurs in the 'abortParsingListOrMoveToNextToken' function when the parser + // decides to skip the token. + // + // In all of these cases, we want to mark the next node as having had an error before it. + // With this mark, we can know in incremental settings if this node can be reused, or if + // we have to reparse it. If we don't keep this information around, we may just reuse the + // node. in that event we would then not produce the same errors as we did before, causing + // significant confusion problems. + // + // Note: it is necessary that this value be saved/restored during speculative/lookahead + // parsing. During lookahead parsing, we will often create a node. That node will have + // this value attached, and then this value will be set back to 'false'. If we decide to + // rewind, we must get back to the same value we had prior to the lookahead. + // + // Note: any errors at the end of the file that do not precede a regular node, should get + // attached to the EOF token. + let parseErrorBeforeNextFinishedNode = false; + + export function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, scriptKind?: ScriptKind, setExternalModuleIndicatorOverride?: (file: SourceFile) => void): SourceFile { + scriptKind = ensureScriptKind(fileName, scriptKind); + if (scriptKind === ScriptKind.JSON) { + const result = parseJsonText(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes); + convertToObjectWorker(result, result.statements[0]?.expression, result.parseDiagnostics, /*returnValue*/ false, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); + result.referencedFiles = emptyArray; + result.typeReferenceDirectives = emptyArray; + result.libReferenceDirectives = emptyArray; + result.amdDependencies = emptyArray; + result.hasNoDefaultLib = false; + result.pragmas = emptyMap as ReadonlyPragmaMap; + return result; } - function reparseTopLevelAwait(sourceFile: SourceFile) { - const savedSyntaxCursor = syntaxCursor; - const baseSyntaxCursor = IncrementalParser.createSyntaxCursor(sourceFile); - syntaxCursor = { currentNode }; + initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind); - const statements: Statement[] = []; - const savedParseDiagnostics = parseDiagnostics; + const result = parseSourceFileWorker(languageVersion, setParentNodes, scriptKind, setExternalModuleIndicatorOverride || setExternalModuleIndicator); - parseDiagnostics = []; + clearState(); - let pos = 0; - let start = findNextStatementWithAwait(sourceFile.statements, 0); - while (start !== -1) { - // append all statements between pos and start - const prevStatement = sourceFile.statements[pos]; - const nextStatement = sourceFile.statements[start]; - addRange(statements, sourceFile.statements, pos, start); - pos = findNextStatementWithoutAwait(sourceFile.statements, start); + return result; + } - // append all diagnostics associated with the copied range - const diagnosticStart = findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= prevStatement.pos); - const diagnosticEnd = diagnosticStart >= 0 ? findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= nextStatement.pos, diagnosticStart) : -1; - if (diagnosticStart >= 0) { - addRange(parseDiagnostics, savedParseDiagnostics, diagnosticStart, diagnosticEnd >= 0 ? diagnosticEnd : undefined); - } + export function parseIsolatedEntityName(content: string, languageVersion: ScriptTarget): EntityName | undefined { + // Choice of `isDeclarationFile` should be arbitrary + initializeState("", content, languageVersion, /*syntaxCursor*/ undefined, ScriptKind.JS); + // Prime the scanner. + nextToken(); + const entityName = parseEntityName(/*allowReservedWords*/ true); + const isInvalid = token() === SyntaxKind.EndOfFileToken && !parseDiagnostics.length; + clearState(); + return isInvalid ? entityName : undefined; + } - // reparse all statements between start and pos. We skip existing diagnostics for the same range and allow the parser to generate new ones. - speculationHelper(() => { - const savedContextFlags = contextFlags; - contextFlags |= NodeFlags.AwaitContext; - scanner.setTextPos(nextStatement.pos); - nextToken(); + export function parseJsonText(fileName: string, sourceText: string, languageVersion: ScriptTarget = ScriptTarget.ES2015, syntaxCursor?: IncrementalParser.SyntaxCursor, setParentNodes = false): JsonSourceFile { + initializeState(fileName, sourceText, languageVersion, syntaxCursor, ScriptKind.JSON); + sourceFlags = contextFlags; - while (token() !== SyntaxKind.EndOfFileToken) { - const startPos = scanner.getStartPos(); - const statement = parseListElement(ParsingContext.SourceElements, parseStatement); - statements.push(statement); - if (startPos === scanner.getStartPos()) { - nextToken(); + // Prime the scanner. + nextToken(); + const pos = getNodePos(); + let statements, endOfFileToken; + if (token() === SyntaxKind.EndOfFileToken) { + statements = createNodeArray([], pos, pos); + endOfFileToken = parseTokenNode(); + } + else { + // Loop and synthesize an ArrayLiteralExpression if there are more than + // one top-level expressions to ensure all input text is consumed. + let expressions: Expression[] | Expression | undefined; + while (token() !== SyntaxKind.EndOfFileToken) { + let expression; + switch (token()) { + case SyntaxKind.OpenBracketToken: + expression = parseArrayLiteralExpression(); + break; + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + expression = parseTokenNode(); + break; + case SyntaxKind.MinusToken: + if (lookAhead(() => nextToken() === SyntaxKind.NumericLiteral && nextToken() !== SyntaxKind.ColonToken)) { + expression = parsePrefixUnaryExpression() as JsonMinusNumericLiteral; } - - if (pos >= 0) { - const nonAwaitStatement = sourceFile.statements[pos]; - if (statement.end === nonAwaitStatement.pos) { - // done reparsing this section - break; - } - if (statement.end > nonAwaitStatement.pos) { - // we ate into the next statement, so we must reparse it. - pos = findNextStatementWithoutAwait(sourceFile.statements, pos + 1); - } + else { + expression = parseObjectLiteralExpression(); } - } - - contextFlags = savedContextFlags; - }, SpeculationKind.Reparse); - - // find the next statement containing an `await` - start = pos >= 0 ? findNextStatementWithAwait(sourceFile.statements, pos) : -1; - } - - // append all statements between pos and the end of the list - if (pos >= 0) { - const prevStatement = sourceFile.statements[pos]; - addRange(statements, sourceFile.statements, pos); - - // append all diagnostics associated with the copied range - const diagnosticStart = findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= prevStatement.pos); - if (diagnosticStart >= 0) { - addRange(parseDiagnostics, savedParseDiagnostics, diagnosticStart); + break; + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + if (lookAhead(() => nextToken() !== SyntaxKind.ColonToken)) { + expression = parseLiteralNode() as StringLiteral | NumericLiteral; + break; + } + // falls through + default: + expression = parseObjectLiteralExpression(); + break; } - } - syntaxCursor = savedSyntaxCursor; - return factory.updateSourceFile(sourceFile, setTextRange(factory.createNodeArray(statements), sourceFile.statements)); - - function containsPossibleTopLevelAwait(node: Node) { - return !(node.flags & NodeFlags.AwaitContext) - && !!(node.transformFlags & TransformFlags.ContainsPossibleTopLevelAwait); - } - - function findNextStatementWithAwait(statements: NodeArray, start: number) { - for (let i = start; i < statements.length; i++) { - if (containsPossibleTopLevelAwait(statements[i])) { - return i; - } + // Error recovery: collect multiple top-level expressions + if (expressions && isArray(expressions)) { + expressions.push(expression); } - return -1; - } - - function findNextStatementWithoutAwait(statements: NodeArray, start: number) { - for (let i = start; i < statements.length; i++) { - if (!containsPossibleTopLevelAwait(statements[i])) { - return i; + else if (expressions) { + expressions = [expressions, expression]; + } + else { + expressions = expression; + if (token() !== SyntaxKind.EndOfFileToken) { + parseErrorAtCurrentToken(Diagnostics.Unexpected_token); } } - return -1; } - function currentNode(position: number) { - const node = baseSyntaxCursor.currentNode(position); - if (topLevel && node && containsPossibleTopLevelAwait(node)) { - node.intersectsChange = true; - } - return node; - } + const expression = isArray(expressions) ? finishNode(factory.createArrayLiteralExpression(expressions), pos) : Debug.checkDefined(expressions); + const statement = factory.createExpressionStatement(expression) as JsonObjectExpressionStatement; + finishNode(statement, pos); + statements = createNodeArray([statement], pos); + endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token); + } + // Set source file so that errors will be reported with this file name + const sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON, /*isDeclaration*/ false, statements, endOfFileToken, sourceFlags, noop); + + if (setParentNodes) { + fixupParentReferences(sourceFile); } - export function fixupParentReferences(rootNode: Node) { - // normally parent references are set during binding. However, for clients that only need - // a syntax tree, and no semantic features, then the binding process is an unnecessary - // overhead. This functions allows us to set all the parents, without all the expense of - // binding. - setParentRecursive(rootNode, /*incremental*/ true); + sourceFile.nodeCount = nodeCount; + sourceFile.identifierCount = identifierCount; + sourceFile.identifiers = identifiers; + sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); + if (jsDocDiagnostics) { + sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); } - function createSourceFile( - fileName: string, - languageVersion: ScriptTarget, - scriptKind: ScriptKind, - isDeclarationFile: boolean, - statements: readonly Statement[], - endOfFileToken: EndOfFileToken, - flags: NodeFlags, - setExternalModuleIndicator: (sourceFile: SourceFile) => void): SourceFile { - // code from createNode is inlined here so createNode won't have to deal with special case of creating source files - // this is quite rare comparing to other nodes and createNode should be as fast as possible - let sourceFile = factory.createSourceFile(statements, endOfFileToken, flags); - setTextRangePosWidth(sourceFile, 0, sourceText.length); - setFields(sourceFile); + const result = sourceFile as JsonSourceFile; + clearState(); + return result; + } - // If we parsed this as an external module, it may contain top-level await - if (!isDeclarationFile && isExternalModule(sourceFile) && sourceFile.transformFlags & TransformFlags.ContainsPossibleTopLevelAwait) { - sourceFile = reparseTopLevelAwait(sourceFile); - setFields(sourceFile); - } + function initializeState(_fileName: string, _sourceText: string, _languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, _scriptKind: ScriptKind) { + NodeConstructor = objectAllocator.getNodeConstructor(); + TokenConstructor = objectAllocator.getTokenConstructor(); + IdentifierConstructor = objectAllocator.getIdentifierConstructor(); + PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor(); + SourceFileConstructor = objectAllocator.getSourceFileConstructor(); + + fileName = normalizePath(_fileName); + sourceText = _sourceText; + languageVersion = _languageVersion; + syntaxCursor = _syntaxCursor; + scriptKind = _scriptKind; + languageVariant = getLanguageVariant(_scriptKind); + + parseDiagnostics = []; + parsingContext = 0; + identifiers = new Map(); + privateIdentifiers = new Map(); + identifierCount = 0; + nodeCount = 0; + sourceFlags = 0; + topLevel = true; + + switch (scriptKind) { + case ScriptKind.JS: + case ScriptKind.JSX: + contextFlags = NodeFlags.JavaScriptFile; + break; + case ScriptKind.JSON: + contextFlags = NodeFlags.JavaScriptFile | NodeFlags.JsonFile; + break; + default: + contextFlags = NodeFlags.None; + break; + } + parseErrorBeforeNextFinishedNode = false; - return sourceFile; + // Initialize and prime the scanner before parsing the source elements. + scanner.setText(sourceText); + scanner.setOnError(scanError); + scanner.setScriptTarget(languageVersion); + scanner.setLanguageVariant(languageVariant); + } - function setFields(sourceFile: SourceFile) { - sourceFile.text = sourceText; - sourceFile.bindDiagnostics = []; - sourceFile.bindSuggestionDiagnostics = undefined; - sourceFile.languageVersion = languageVersion; - sourceFile.fileName = fileName; - sourceFile.languageVariant = getLanguageVariant(scriptKind); - sourceFile.isDeclarationFile = isDeclarationFile; - sourceFile.scriptKind = scriptKind; + function clearState() { + // Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily. + scanner.clearCommentDirectives(); + scanner.setText(""); + scanner.setOnError(undefined); + + // Clear any data. We don't want to accidentally hold onto it for too long. + sourceText = undefined!; + languageVersion = undefined!; + syntaxCursor = undefined; + scriptKind = undefined!; + languageVariant = undefined!; + sourceFlags = 0; + parseDiagnostics = undefined!; + jsDocDiagnostics = undefined!; + parsingContext = 0; + identifiers = undefined!; + notParenthesizedArrow = undefined; + topLevel = true; + } - setExternalModuleIndicator(sourceFile); - sourceFile.setExternalModuleIndicator = setExternalModuleIndicator; - } + function parseSourceFileWorker(languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind, setExternalModuleIndicator: (file: SourceFile) => void): SourceFile { + const isDeclarationFile = isDeclarationFileName(fileName); + if (isDeclarationFile) { + contextFlags |= NodeFlags.Ambient; } - function setContextFlag(val: boolean, flag: NodeFlags) { - if (val) { - contextFlags |= flag; - } - else { - contextFlags &= ~flag; - } - } + sourceFlags = contextFlags; + + // Prime the scanner. + nextToken(); + + const statements = parseList(ParsingContext.SourceElements, parseStatement); + Debug.assert(token() === SyntaxKind.EndOfFileToken); + const endOfFileToken = addJSDocComment(parseTokenNode()); + + const sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile, statements, endOfFileToken, sourceFlags, setExternalModuleIndicator); - function setDisallowInContext(val: boolean) { - setContextFlag(val, NodeFlags.DisallowInContext); + // A member of ReadonlyArray isn't assignable to a member of T[] (and prevents a direct cast) - but this is where we set up those members so they can be readonly in the future + processCommentPragmas(sourceFile as {} as PragmaContext, sourceText); + processPragmasIntoFields(sourceFile as {} as PragmaContext, reportPragmaDiagnostic); + + sourceFile.commentDirectives = scanner.getCommentDirectives(); + sourceFile.nodeCount = nodeCount; + sourceFile.identifierCount = identifierCount; + sourceFile.identifiers = identifiers; + sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); + if (jsDocDiagnostics) { + sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); } - function setYieldContext(val: boolean) { - setContextFlag(val, NodeFlags.YieldContext); + if (setParentNodes) { + fixupParentReferences(sourceFile); } - function setDecoratorContext(val: boolean) { - setContextFlag(val, NodeFlags.DecoratorContext); + return sourceFile; + + function reportPragmaDiagnostic(pos: number, end: number, diagnostic: DiagnosticMessage) { + parseDiagnostics.push(createDetachedDiagnostic(fileName, pos, end, diagnostic)); } + } + + function withJSDoc(node: T, hasJSDoc: boolean): T { + return hasJSDoc ? addJSDocComment(node) : node; + } - function setAwaitContext(val: boolean) { - setContextFlag(val, NodeFlags.AwaitContext); + let hasDeprecatedTag = false; + function addJSDocComment(node: T): T { + Debug.assert(!node.jsDoc); // Should only be called once per node + const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos)); + if (jsDoc.length) node.jsDoc = jsDoc; + if (hasDeprecatedTag) { + hasDeprecatedTag = false; + (node as Mutable).flags |= NodeFlags.Deprecated; } + return node; + } - function doOutsideOfContext(context: NodeFlags, func: () => T): T { - // contextFlagsToClear will contain only the context flags that are - // currently set that we need to temporarily clear - // We don't just blindly reset to the previous flags to ensure - // that we do not mutate cached flags for the incremental - // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and - // HasAggregatedChildData). - const contextFlagsToClear = context & contextFlags; - if (contextFlagsToClear) { - // clear the requested context flags - setContextFlag(/*val*/ false, contextFlagsToClear); - const result = func(); - // restore the context flags we just cleared - setContextFlag(/*val*/ true, contextFlagsToClear); - return result; - } + function reparseTopLevelAwait(sourceFile: SourceFile) { + const savedSyntaxCursor = syntaxCursor; + const baseSyntaxCursor = IncrementalParser.createSyntaxCursor(sourceFile); + syntaxCursor = { currentNode }; + + const statements: Statement[] = []; + const savedParseDiagnostics = parseDiagnostics; + + parseDiagnostics = []; + + let pos = 0; + let start = findNextStatementWithAwait(sourceFile.statements, 0); + while (start !== -1) { + // append all statements between pos and start + const prevStatement = sourceFile.statements[pos]; + const nextStatement = sourceFile.statements[start]; + addRange(statements, sourceFile.statements, pos, start); + pos = findNextStatementWithoutAwait(sourceFile.statements, start); + + // append all diagnostics associated with the copied range + const diagnosticStart = findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= prevStatement.pos); + const diagnosticEnd = diagnosticStart >= 0 ? findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= nextStatement.pos, diagnosticStart) : -1; + if (diagnosticStart >= 0) { + addRange(parseDiagnostics, savedParseDiagnostics, diagnosticStart, diagnosticEnd >= 0 ? diagnosticEnd : undefined); + } + + // reparse all statements between start and pos. We skip existing diagnostics for the same range and allow the parser to generate new ones. + speculationHelper(() => { + const savedContextFlags = contextFlags; + contextFlags |= NodeFlags.AwaitContext; + scanner.setTextPos(nextStatement.pos); + nextToken(); - // no need to do anything special as we are not in any of the requested contexts - return func(); - } - - function doInsideOfContext(context: NodeFlags, func: () => T): T { - // contextFlagsToSet will contain only the context flags that - // are not currently set that we need to temporarily enable. - // We don't just blindly reset to the previous flags to ensure - // that we do not mutate cached flags for the incremental - // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and - // HasAggregatedChildData). - const contextFlagsToSet = context & ~contextFlags; - if (contextFlagsToSet) { - // set the requested context flags - setContextFlag(/*val*/ true, contextFlagsToSet); - const result = func(); - // reset the context flags we just set - setContextFlag(/*val*/ false, contextFlagsToSet); - return result; - } + while (token() !== SyntaxKind.EndOfFileToken) { + const startPos = scanner.getStartPos(); + const statement = parseListElement(ParsingContext.SourceElements, parseStatement); + statements.push(statement); + if (startPos === scanner.getStartPos()) { + nextToken(); + } - // no need to do anything special as we are already in all of the requested contexts - return func(); - } + if (pos >= 0) { + const nonAwaitStatement = sourceFile.statements[pos]; + if (statement.end === nonAwaitStatement.pos) { + // done reparsing this section + break; + } + if (statement.end > nonAwaitStatement.pos) { + // we ate into the next statement, so we must reparse it. + pos = findNextStatementWithoutAwait(sourceFile.statements, pos + 1); + } + } + } - function allowInAnd(func: () => T): T { - return doOutsideOfContext(NodeFlags.DisallowInContext, func); - } + contextFlags = savedContextFlags; + }, SpeculationKind.Reparse); - function disallowInAnd(func: () => T): T { - return doInsideOfContext(NodeFlags.DisallowInContext, func); + // find the next statement containing an `await` + start = pos >= 0 ? findNextStatementWithAwait(sourceFile.statements, pos) : -1; } - function allowConditionalTypesAnd(func: () => T): T { - return doOutsideOfContext(NodeFlags.DisallowConditionalTypesContext, func); - } + // append all statements between pos and the end of the list + if (pos >= 0) { + const prevStatement = sourceFile.statements[pos]; + addRange(statements, sourceFile.statements, pos); - function disallowConditionalTypesAnd(func: () => T): T { - return doInsideOfContext(NodeFlags.DisallowConditionalTypesContext, func); + // append all diagnostics associated with the copied range + const diagnosticStart = findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= prevStatement.pos); + if (diagnosticStart >= 0) { + addRange(parseDiagnostics, savedParseDiagnostics, diagnosticStart); + } } - function doInYieldContext(func: () => T): T { - return doInsideOfContext(NodeFlags.YieldContext, func); - } + syntaxCursor = savedSyntaxCursor; + return factory.updateSourceFile(sourceFile, setTextRange(factory.createNodeArray(statements), sourceFile.statements)); - function doInDecoratorContext(func: () => T): T { - return doInsideOfContext(NodeFlags.DecoratorContext, func); + function containsPossibleTopLevelAwait(node: Node) { + return !(node.flags & NodeFlags.AwaitContext) + && !!(node.transformFlags & TransformFlags.ContainsPossibleTopLevelAwait); } - function doInAwaitContext(func: () => T): T { - return doInsideOfContext(NodeFlags.AwaitContext, func); + function findNextStatementWithAwait(statements: NodeArray, start: number) { + for (let i = start; i < statements.length; i++) { + if (containsPossibleTopLevelAwait(statements[i])) { + return i; + } + } + return -1; } - function doOutsideOfAwaitContext(func: () => T): T { - return doOutsideOfContext(NodeFlags.AwaitContext, func); + function findNextStatementWithoutAwait(statements: NodeArray, start: number) { + for (let i = start; i < statements.length; i++) { + if (!containsPossibleTopLevelAwait(statements[i])) { + return i; + } + } + return -1; } - function doInYieldAndAwaitContext(func: () => T): T { - return doInsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext, func); + function currentNode(position: number) { + const node = baseSyntaxCursor.currentNode(position); + if (topLevel && node && containsPossibleTopLevelAwait(node)) { + node.intersectsChange = true; + } + return node; } - function doOutsideOfYieldAndAwaitContext(func: () => T): T { - return doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext, func); - } + } - function inContext(flags: NodeFlags) { - return (contextFlags & flags) !== 0; - } + export function fixupParentReferences(rootNode: Node) { + // normally parent references are set during binding. However, for clients that only need + // a syntax tree, and no semantic features, then the binding process is an unnecessary + // overhead. This functions allows us to set all the parents, without all the expense of + // binding. + setParentRecursive(rootNode, /*incremental*/ true); + } - function inYieldContext() { - return inContext(NodeFlags.YieldContext); + function createSourceFile( + fileName: string, + languageVersion: ScriptTarget, + scriptKind: ScriptKind, + isDeclarationFile: boolean, + statements: readonly Statement[], + endOfFileToken: EndOfFileToken, + flags: NodeFlags, + setExternalModuleIndicator: (sourceFile: SourceFile) => void): SourceFile { + // code from createNode is inlined here so createNode won't have to deal with special case of creating source files + // this is quite rare comparing to other nodes and createNode should be as fast as possible + let sourceFile = factory.createSourceFile(statements, endOfFileToken, flags); + setTextRangePosWidth(sourceFile, 0, sourceText.length); + setFields(sourceFile); + + // If we parsed this as an external module, it may contain top-level await + if (!isDeclarationFile && isExternalModule(sourceFile) && sourceFile.transformFlags & TransformFlags.ContainsPossibleTopLevelAwait) { + sourceFile = reparseTopLevelAwait(sourceFile); + setFields(sourceFile); } - function inDisallowInContext() { - return inContext(NodeFlags.DisallowInContext); - } + return sourceFile; - function inDisallowConditionalTypesContext() { - return inContext(NodeFlags.DisallowConditionalTypesContext); - } + function setFields(sourceFile: SourceFile) { + sourceFile.text = sourceText; + sourceFile.bindDiagnostics = []; + sourceFile.bindSuggestionDiagnostics = undefined; + sourceFile.languageVersion = languageVersion; + sourceFile.fileName = fileName; + sourceFile.languageVariant = getLanguageVariant(scriptKind); + sourceFile.isDeclarationFile = isDeclarationFile; + sourceFile.scriptKind = scriptKind; - function inDecoratorContext() { - return inContext(NodeFlags.DecoratorContext); + setExternalModuleIndicator(sourceFile); + sourceFile.setExternalModuleIndicator = setExternalModuleIndicator; } + } - function inAwaitContext() { - return inContext(NodeFlags.AwaitContext); + function setContextFlag(val: boolean, flag: NodeFlags) { + if (val) { + contextFlags |= flag; } - - function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): DiagnosticWithDetachedLocation | undefined { - return parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), message, arg0); + else { + contextFlags &= ~flag; } + } - function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): DiagnosticWithDetachedLocation | undefined { - // Don't report another error if it would just be at the same position as the last error. - const lastError = lastOrUndefined(parseDiagnostics); - let result: DiagnosticWithDetachedLocation | undefined; - if (!lastError || start !== lastError.start) { - result = createDetachedDiagnostic(fileName, start, length, message, arg0); - parseDiagnostics.push(result); - } + function setDisallowInContext(val: boolean) { + setContextFlag(val, NodeFlags.DisallowInContext); + } + + function setYieldContext(val: boolean) { + setContextFlag(val, NodeFlags.YieldContext); + } - // Mark that we've encountered an error. We'll set an appropriate bit on the next - // node we finish so that it can't be reused incrementally. - parseErrorBeforeNextFinishedNode = true; + function setDecoratorContext(val: boolean) { + setContextFlag(val, NodeFlags.DecoratorContext); + } + + function setAwaitContext(val: boolean) { + setContextFlag(val, NodeFlags.AwaitContext); + } + + function doOutsideOfContext(context: NodeFlags, func: () => T): T { + // contextFlagsToClear will contain only the context flags that are + // currently set that we need to temporarily clear + // We don't just blindly reset to the previous flags to ensure + // that we do not mutate cached flags for the incremental + // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and + // HasAggregatedChildData). + const contextFlagsToClear = context & contextFlags; + if (contextFlagsToClear) { + // clear the requested context flags + setContextFlag(/*val*/ false, contextFlagsToClear); + const result = func(); + // restore the context flags we just cleared + setContextFlag(/*val*/ true, contextFlagsToClear); return result; } - function parseErrorAt(start: number, end: number, message: DiagnosticMessage, arg0?: any): DiagnosticWithDetachedLocation | undefined { - return parseErrorAtPosition(start, end - start, message, arg0); - } + // no need to do anything special as we are not in any of the requested contexts + return func(); + } - function parseErrorAtRange(range: TextRange, message: DiagnosticMessage, arg0?: any): void { - parseErrorAt(range.pos, range.end, message, arg0); + function doInsideOfContext(context: NodeFlags, func: () => T): T { + // contextFlagsToSet will contain only the context flags that + // are not currently set that we need to temporarily enable. + // We don't just blindly reset to the previous flags to ensure + // that we do not mutate cached flags for the incremental + // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and + // HasAggregatedChildData). + const contextFlagsToSet = context & ~contextFlags; + if (contextFlagsToSet) { + // set the requested context flags + setContextFlag(/*val*/ true, contextFlagsToSet); + const result = func(); + // reset the context flags we just set + setContextFlag(/*val*/ false, contextFlagsToSet); + return result; } - function scanError(message: DiagnosticMessage, length: number): void { - parseErrorAtPosition(scanner.getTextPos(), length, message); - } + // no need to do anything special as we are already in all of the requested contexts + return func(); + } - function getNodePos(): number { - return scanner.getStartPos(); - } + function allowInAnd(func: () => T): T { + return doOutsideOfContext(NodeFlags.DisallowInContext, func); + } - function hasPrecedingJSDocComment() { - return scanner.hasPrecedingJSDocComment(); - } + function disallowInAnd(func: () => T): T { + return doInsideOfContext(NodeFlags.DisallowInContext, func); + } - // Use this function to access the current token instead of reading the currentToken - // variable. Since function results aren't narrowed in control flow analysis, this ensures - // that the type checker doesn't make wrong assumptions about the type of the current - // token (e.g. a call to nextToken() changes the current token but the checker doesn't - // reason about this side effect). Mainstream VMs inline simple functions like this, so - // there is no performance penalty. - function token(): SyntaxKind { - return currentToken; - } + function allowConditionalTypesAnd(func: () => T): T { + return doOutsideOfContext(NodeFlags.DisallowConditionalTypesContext, func); + } - function nextTokenWithoutCheck() { - return currentToken = scanner.scan(); - } + function disallowConditionalTypesAnd(func: () => T): T { + return doInsideOfContext(NodeFlags.DisallowConditionalTypesContext, func); + } - function nextTokenAnd(func: () => T): T { - nextToken(); - return func(); - } + function doInYieldContext(func: () => T): T { + return doInsideOfContext(NodeFlags.YieldContext, func); + } - function nextToken(): SyntaxKind { - // if the keyword had an escape - if (isKeyword(currentToken) && (scanner.hasUnicodeEscape() || scanner.hasExtendedUnicodeEscape())) { - // issue a parse error for the escape - parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), Diagnostics.Keywords_cannot_contain_escape_characters); - } - return nextTokenWithoutCheck(); - } + function doInDecoratorContext(func: () => T): T { + return doInsideOfContext(NodeFlags.DecoratorContext, func); + } - function nextTokenJSDoc(): JSDocSyntaxKind { - return currentToken = scanner.scanJsDocToken(); - } + function doInAwaitContext(func: () => T): T { + return doInsideOfContext(NodeFlags.AwaitContext, func); + } - function reScanGreaterToken(): SyntaxKind { - return currentToken = scanner.reScanGreaterToken(); - } + function doOutsideOfAwaitContext(func: () => T): T { + return doOutsideOfContext(NodeFlags.AwaitContext, func); + } - function reScanSlashToken(): SyntaxKind { - return currentToken = scanner.reScanSlashToken(); - } + function doInYieldAndAwaitContext(func: () => T): T { + return doInsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext, func); + } - function reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind { - return currentToken = scanner.reScanTemplateToken(isTaggedTemplate); - } + function doOutsideOfYieldAndAwaitContext(func: () => T): T { + return doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext, func); + } - function reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind { - return currentToken = scanner.reScanTemplateHeadOrNoSubstitutionTemplate(); - } + function inContext(flags: NodeFlags) { + return (contextFlags & flags) !== 0; + } - function reScanLessThanToken(): SyntaxKind { - return currentToken = scanner.reScanLessThanToken(); - } + function inYieldContext() { + return inContext(NodeFlags.YieldContext); + } - function reScanHashToken(): SyntaxKind { - return currentToken = scanner.reScanHashToken(); - } + function inDisallowInContext() { + return inContext(NodeFlags.DisallowInContext); + } - function scanJsxIdentifier(): SyntaxKind { - return currentToken = scanner.scanJsxIdentifier(); - } + function inDisallowConditionalTypesContext() { + return inContext(NodeFlags.DisallowConditionalTypesContext); + } - function scanJsxText(): SyntaxKind { - return currentToken = scanner.scanJsxToken(); - } + function inDecoratorContext() { + return inContext(NodeFlags.DecoratorContext); + } + + function inAwaitContext() { + return inContext(NodeFlags.AwaitContext); + } + + function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): DiagnosticWithDetachedLocation | undefined { + return parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), message, arg0); + } - function scanJsxAttributeValue(): SyntaxKind { - return currentToken = scanner.scanJsxAttributeValue(); + function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): DiagnosticWithDetachedLocation | undefined { + // Don't report another error if it would just be at the same position as the last error. + const lastError = lastOrUndefined(parseDiagnostics); + let result: DiagnosticWithDetachedLocation | undefined; + if (!lastError || start !== lastError.start) { + result = createDetachedDiagnostic(fileName, start, length, message, arg0); + parseDiagnostics.push(result); } - function speculationHelper(callback: () => T, speculationKind: SpeculationKind): T { - // Keep track of the state we'll need to rollback to if lookahead fails (or if the - // caller asked us to always reset our state). - const saveToken = currentToken; - const saveParseDiagnosticsLength = parseDiagnostics.length; - const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; + // Mark that we've encountered an error. We'll set an appropriate bit on the next + // node we finish so that it can't be reused incrementally. + parseErrorBeforeNextFinishedNode = true; + return result; + } - // Note: it is not actually necessary to save/restore the context flags here. That's - // because the saving/restoring of these flags happens naturally through the recursive - // descent nature of our parser. However, we still store this here just so we can - // assert that invariant holds. - const saveContextFlags = contextFlags; + function parseErrorAt(start: number, end: number, message: DiagnosticMessage, arg0?: any): DiagnosticWithDetachedLocation | undefined { + return parseErrorAtPosition(start, end - start, message, arg0); + } - // If we're only looking ahead, then tell the scanner to only lookahead as well. - // Otherwise, if we're actually speculatively parsing, then tell the scanner to do the - // same. - const result = speculationKind !== SpeculationKind.TryParse - ? scanner.lookAhead(callback) - : scanner.tryScan(callback); + function parseErrorAtRange(range: TextRange, message: DiagnosticMessage, arg0?: any): void { + parseErrorAt(range.pos, range.end, message, arg0); + } - Debug.assert(saveContextFlags === contextFlags); + function scanError(message: DiagnosticMessage, length: number): void { + parseErrorAtPosition(scanner.getTextPos(), length, message); + } - // If our callback returned something 'falsy' or we're just looking ahead, - // then unconditionally restore us to where we were. - if (!result || speculationKind !== SpeculationKind.TryParse) { - currentToken = saveToken; - if (speculationKind !== SpeculationKind.Reparse) { - parseDiagnostics.length = saveParseDiagnosticsLength; - } - parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; - } + function getNodePos(): number { + return scanner.getStartPos(); + } - return result; - } + function hasPrecedingJSDocComment() { + return scanner.hasPrecedingJSDocComment(); + } - /** Invokes the provided callback then unconditionally restores the parser to the state it - * was in immediately prior to invoking the callback. The result of invoking the callback - * is returned from this function. - */ - function lookAhead(callback: () => T): T { - return speculationHelper(callback, SpeculationKind.Lookahead); - } + // Use this function to access the current token instead of reading the currentToken + // variable. Since function results aren't narrowed in control flow analysis, this ensures + // that the type checker doesn't make wrong assumptions about the type of the current + // token (e.g. a call to nextToken() changes the current token but the checker doesn't + // reason about this side effect). Mainstream VMs inline simple functions like this, so + // there is no performance penalty. + function token(): SyntaxKind { + return currentToken; + } - /** Invokes the provided callback. If the callback returns something falsy, then it restores - * the parser to the state it was in immediately prior to invoking the callback. If the - * callback returns something truthy, then the parser state is not rolled back. The result - * of invoking the callback is returned from this function. - */ - function tryParse(callback: () => T): T { - return speculationHelper(callback, SpeculationKind.TryParse); - } + function nextTokenWithoutCheck() { + return currentToken = scanner.scan(); + } - function isBindingIdentifier(): boolean { - if (token() === SyntaxKind.Identifier) { - return true; - } + function nextTokenAnd(func: () => T): T { + nextToken(); + return func(); + } - // `let await`/`let yield` in [Yield] or [Await] are allowed here and disallowed in the binder. - return token() > SyntaxKind.LastReservedWord; + function nextToken(): SyntaxKind { + // if the keyword had an escape + if (isKeyword(currentToken) && (scanner.hasUnicodeEscape() || scanner.hasExtendedUnicodeEscape())) { + // issue a parse error for the escape + parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), Diagnostics.Keywords_cannot_contain_escape_characters); } + return nextTokenWithoutCheck(); + } - // Ignore strict mode flag because we will report an error in type checker instead. - function isIdentifier(): boolean { - if (token() === SyntaxKind.Identifier) { - return true; - } + function nextTokenJSDoc(): JSDocSyntaxKind { + return currentToken = scanner.scanJsDocToken(); + } - // If we have a 'yield' keyword, and we're in the [yield] context, then 'yield' is - // considered a keyword and is not an identifier. - if (token() === SyntaxKind.YieldKeyword && inYieldContext()) { - return false; - } + function reScanGreaterToken(): SyntaxKind { + return currentToken = scanner.reScanGreaterToken(); + } - // If we have a 'await' keyword, and we're in the [Await] context, then 'await' is - // considered a keyword and is not an identifier. - if (token() === SyntaxKind.AwaitKeyword && inAwaitContext()) { - return false; - } + function reScanSlashToken(): SyntaxKind { + return currentToken = scanner.reScanSlashToken(); + } - return token() > SyntaxKind.LastReservedWord; - } + function reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind { + return currentToken = scanner.reScanTemplateToken(isTaggedTemplate); + } - function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, shouldAdvance = true): boolean { - if (token() === kind) { - if (shouldAdvance) { - nextToken(); - } - return true; - } + function reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind { + return currentToken = scanner.reScanTemplateHeadOrNoSubstitutionTemplate(); + } - // Report specific message if provided with one. Otherwise, report generic fallback message. - if (diagnosticMessage) { - parseErrorAtCurrentToken(diagnosticMessage); - } - else { - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); - } - return false; - } + function reScanLessThanToken(): SyntaxKind { + return currentToken = scanner.reScanLessThanToken(); + } - const viableKeywordSuggestions = Object.keys(textToKeywordObj).filter(keyword => keyword.length > 2); + function reScanHashToken(): SyntaxKind { + return currentToken = scanner.reScanHashToken(); + } - /** - * Provides a better error message than the generic "';' expected" if possible for - * known common variants of a missing semicolon, such as from a mispelled names. - * - * @param node Node preceding the expected semicolon location. - */ - function parseErrorForMissingSemicolonAfter(node: Expression | PropertyName): void { - // Tagged template literals are sometimes used in places where only simple strings are allowed, i.e.: - // module `M1` { - // ^^^^^^^^^^^ This block is parsed as a template literal like module`M1`. - if (isTaggedTemplateExpression(node)) { - parseErrorAt(skipTrivia(sourceText, node.template.pos), node.template.end, Diagnostics.Module_declaration_names_may_only_use_or_quoted_strings); - return; - } + function scanJsxIdentifier(): SyntaxKind { + return currentToken = scanner.scanJsxIdentifier(); + } - // Otherwise, if this isn't a well-known keyword-like identifier, give the generic fallback message. - const expressionText = ts.isIdentifier(node) ? idText(node) : undefined; - if (!expressionText || !isIdentifierText(expressionText, languageVersion)) { - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); - return; + function scanJsxText(): SyntaxKind { + return currentToken = scanner.scanJsxToken(); + } + + function scanJsxAttributeValue(): SyntaxKind { + return currentToken = scanner.scanJsxAttributeValue(); + } + + function speculationHelper(callback: () => T, speculationKind: SpeculationKind): T { + // Keep track of the state we'll need to rollback to if lookahead fails (or if the + // caller asked us to always reset our state). + const saveToken = currentToken; + const saveParseDiagnosticsLength = parseDiagnostics.length; + const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; + + // Note: it is not actually necessary to save/restore the context flags here. That's + // because the saving/restoring of these flags happens naturally through the recursive + // descent nature of our parser. However, we still store this here just so we can + // assert that invariant holds. + const saveContextFlags = contextFlags; + + // If we're only looking ahead, then tell the scanner to only lookahead as well. + // Otherwise, if we're actually speculatively parsing, then tell the scanner to do the + // same. + const result = speculationKind !== SpeculationKind.TryParse + ? scanner.lookAhead(callback) + : scanner.tryScan(callback); + + Debug.assert(saveContextFlags === contextFlags); + + // If our callback returned something 'falsy' or we're just looking ahead, + // then unconditionally restore us to where we were. + if (!result || speculationKind !== SpeculationKind.TryParse) { + currentToken = saveToken; + if (speculationKind !== SpeculationKind.Reparse) { + parseDiagnostics.length = saveParseDiagnosticsLength; } + parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; + } - const pos = skipTrivia(sourceText, node.pos); + return result; + } - // Some known keywords are likely signs of syntax being used improperly. - switch (expressionText) { - case "const": - case "let": - case "var": - parseErrorAt(pos, node.end, Diagnostics.Variable_declaration_not_allowed_at_this_location); - return; + /** Invokes the provided callback then unconditionally restores the parser to the state it + * was in immediately prior to invoking the callback. The result of invoking the callback + * is returned from this function. + */ + function lookAhead(callback: () => T): T { + return speculationHelper(callback, SpeculationKind.Lookahead); + } - case "declare": - // If a declared node failed to parse, it would have emitted a diagnostic already. - return; + /** Invokes the provided callback. If the callback returns something falsy, then it restores + * the parser to the state it was in immediately prior to invoking the callback. If the + * callback returns something truthy, then the parser state is not rolled back. The result + * of invoking the callback is returned from this function. + */ + function tryParse(callback: () => T): T { + return speculationHelper(callback, SpeculationKind.TryParse); + } - case "interface": - parseErrorForInvalidName(Diagnostics.Interface_name_cannot_be_0, Diagnostics.Interface_must_be_given_a_name, SyntaxKind.OpenBraceToken); - return; + function isBindingIdentifier(): boolean { + if (token() === SyntaxKind.Identifier) { + return true; + } - case "is": - parseErrorAt(pos, scanner.getTextPos(), Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); - return; + // `let await`/`let yield` in [Yield] or [Await] are allowed here and disallowed in the binder. + return token() > SyntaxKind.LastReservedWord; + } + + // Ignore strict mode flag because we will report an error in type checker instead. + function isIdentifier(): boolean { + if (token() === SyntaxKind.Identifier) { + return true; + } - case "module": - case "namespace": - parseErrorForInvalidName(Diagnostics.Namespace_name_cannot_be_0, Diagnostics.Namespace_must_be_given_a_name, SyntaxKind.OpenBraceToken); - return; + // If we have a 'yield' keyword, and we're in the [yield] context, then 'yield' is + // considered a keyword and is not an identifier. + if (token() === SyntaxKind.YieldKeyword && inYieldContext()) { + return false; + } - case "type": - parseErrorForInvalidName(Diagnostics.Type_alias_name_cannot_be_0, Diagnostics.Type_alias_must_be_given_a_name, SyntaxKind.EqualsToken); - return; - } + // If we have a 'await' keyword, and we're in the [Await] context, then 'await' is + // considered a keyword and is not an identifier. + if (token() === SyntaxKind.AwaitKeyword && inAwaitContext()) { + return false; + } - // The user alternatively might have misspelled or forgotten to add a space after a common keyword. - const suggestion = getSpellingSuggestion(expressionText, viableKeywordSuggestions, n => n) ?? getSpaceSuggestion(expressionText); - if (suggestion) { - parseErrorAt(pos, node.end, Diagnostics.Unknown_keyword_or_identifier_Did_you_mean_0, suggestion); - return; - } + return token() > SyntaxKind.LastReservedWord; + } - // Unknown tokens are handled with their own errors in the scanner - if (token() === SyntaxKind.Unknown) { - return; + function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, shouldAdvance = true): boolean { + if (token() === kind) { + if (shouldAdvance) { + nextToken(); } - - // Otherwise, we know this some kind of unknown word, not just a missing expected semicolon. - parseErrorAt(pos, node.end, Diagnostics.Unexpected_keyword_or_identifier); + return true; } - /** - * Reports a diagnostic error for the current token being an invalid name. - * - * @param blankDiagnostic Diagnostic to report for the case of the name being blank (matched tokenIfBlankName). - * @param nameDiagnostic Diagnostic to report for all other cases. - * @param tokenIfBlankName Current token if the name was invalid for being blank (not provided / skipped). - */ - function parseErrorForInvalidName(nameDiagnostic: DiagnosticMessage, blankDiagnostic: DiagnosticMessage, tokenIfBlankName: SyntaxKind) { - if (token() === tokenIfBlankName) { - parseErrorAtCurrentToken(blankDiagnostic); - } - else { - parseErrorAtCurrentToken(nameDiagnostic, scanner.getTokenValue()); - } + // Report specific message if provided with one. Otherwise, report generic fallback message. + if (diagnosticMessage) { + parseErrorAtCurrentToken(diagnosticMessage); } + else { + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); + } + return false; + } - function getSpaceSuggestion(expressionText: string) { - for (const keyword of viableKeywordSuggestions) { - if (expressionText.length > keyword.length + 2 && startsWith(expressionText, keyword)) { - return `${keyword} ${expressionText.slice(keyword.length)}`; - } - } + const viableKeywordSuggestions = Object.keys(textToKeywordObj).filter(keyword => keyword.length > 2); - return undefined; + /** + * Provides a better error message than the generic "';' expected" if possible for + * known common variants of a missing semicolon, such as from a mispelled names. + * + * @param node Node preceding the expected semicolon location. + */ + function parseErrorForMissingSemicolonAfter(node: Expression | PropertyName): void { + // Tagged template literals are sometimes used in places where only simple strings are allowed, i.e.: + // module `M1` { + // ^^^^^^^^^^^ This block is parsed as a template literal like module`M1`. + if (isTaggedTemplateExpression(node)) { + parseErrorAt(skipTrivia(sourceText, node.template.pos), node.template.end, Diagnostics.Module_declaration_names_may_only_use_or_quoted_strings); + return; + } + + // Otherwise, if this isn't a well-known keyword-like identifier, give the generic fallback message. + const expressionText = ts.isIdentifier(node) ? idText(node) : undefined; + if (!expressionText || !isIdentifierText(expressionText, languageVersion)) { + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); + return; } - function parseSemicolonAfterPropertyName(name: PropertyName, type: TypeNode | undefined, initializer: Expression | undefined) { - if (token() === SyntaxKind.AtToken && !scanner.hasPrecedingLineBreak()) { - parseErrorAtCurrentToken(Diagnostics.Decorators_must_precede_the_name_and_all_keywords_of_property_declarations); + const pos = skipTrivia(sourceText, node.pos); + + // Some known keywords are likely signs of syntax being used improperly. + switch (expressionText) { + case "const": + case "let": + case "var": + parseErrorAt(pos, node.end, Diagnostics.Variable_declaration_not_allowed_at_this_location); return; - } - if (token() === SyntaxKind.OpenParenToken) { - parseErrorAtCurrentToken(Diagnostics.Cannot_start_a_function_call_in_a_type_annotation); - nextToken(); + case "declare": + // If a declared node failed to parse, it would have emitted a diagnostic already. return; - } - if (type && !canParseSemicolon()) { - if (initializer) { - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); - } - else { - parseErrorAtCurrentToken(Diagnostics.Expected_for_property_initializer); - } + case "interface": + parseErrorForInvalidName(Diagnostics.Interface_name_cannot_be_0, Diagnostics.Interface_must_be_given_a_name, SyntaxKind.OpenBraceToken); return; - } - if (tryParseSemicolon()) { + case "is": + parseErrorAt(pos, scanner.getTextPos(), Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); return; - } - if (initializer) { - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); + case "module": + case "namespace": + parseErrorForInvalidName(Diagnostics.Namespace_name_cannot_be_0, Diagnostics.Namespace_must_be_given_a_name, SyntaxKind.OpenBraceToken); return; - } - parseErrorForMissingSemicolonAfter(name); + case "type": + parseErrorForInvalidName(Diagnostics.Type_alias_name_cannot_be_0, Diagnostics.Type_alias_must_be_given_a_name, SyntaxKind.EqualsToken); + return; } - function parseExpectedJSDoc(kind: JSDocSyntaxKind) { - if (token() === kind) { - nextTokenJSDoc(); - return true; - } - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); - return false; + // The user alternatively might have misspelled or forgotten to add a space after a common keyword. + const suggestion = getSpellingSuggestion(expressionText, viableKeywordSuggestions, n => n) ?? getSpaceSuggestion(expressionText); + if (suggestion) { + parseErrorAt(pos, node.end, Diagnostics.Unknown_keyword_or_identifier_Did_you_mean_0, suggestion); + return; } - function parseExpectedMatchingBrackets(openKind: SyntaxKind, closeKind: SyntaxKind, openParsed: boolean, openPosition: number) { - if (token() === closeKind) { - nextToken(); - return; - } - const lastError = parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(closeKind)); - if (!openParsed) { - return; - } - if (lastError) { - addRelatedInfo( - lastError, - createDetachedDiagnostic(fileName, openPosition, 1, Diagnostics.The_parser_expected_to_find_a_1_to_match_the_0_token_here, tokenToString(openKind), tokenToString(closeKind)) - ); - } + // Unknown tokens are handled with their own errors in the scanner + if (token() === SyntaxKind.Unknown) { + return; } - function parseOptional(t: SyntaxKind): boolean { - if (token() === t) { - nextToken(); - return true; - } - return false; - } + // Otherwise, we know this some kind of unknown word, not just a missing expected semicolon. + parseErrorAt(pos, node.end, Diagnostics.Unexpected_keyword_or_identifier); + } - function parseOptionalToken(t: TKind): Token; - function parseOptionalToken(t: SyntaxKind): Node | undefined { - if (token() === t) { - return parseTokenNode(); - } - return undefined; + /** + * Reports a diagnostic error for the current token being an invalid name. + * + * @param blankDiagnostic Diagnostic to report for the case of the name being blank (matched tokenIfBlankName). + * @param nameDiagnostic Diagnostic to report for all other cases. + * @param tokenIfBlankName Current token if the name was invalid for being blank (not provided / skipped). + */ + function parseErrorForInvalidName(nameDiagnostic: DiagnosticMessage, blankDiagnostic: DiagnosticMessage, tokenIfBlankName: SyntaxKind) { + if (token() === tokenIfBlankName) { + parseErrorAtCurrentToken(blankDiagnostic); + } + else { + parseErrorAtCurrentToken(nameDiagnostic, scanner.getTokenValue()); } + } - function parseOptionalTokenJSDoc(t: TKind): Token; - function parseOptionalTokenJSDoc(t: JSDocSyntaxKind): Node | undefined { - if (token() === t) { - return parseTokenNodeJSDoc(); + function getSpaceSuggestion(expressionText: string) { + for (const keyword of viableKeywordSuggestions) { + if (expressionText.length > keyword.length + 2 && startsWith(expressionText, keyword)) { + return `${keyword} ${expressionText.slice(keyword.length)}`; } - return undefined; } - function parseExpectedToken(t: TKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Token; - function parseExpectedToken(t: SyntaxKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Node { - return parseOptionalToken(t) || - createMissingNode(t, /*reportAtCurrentPosition*/ false, diagnosticMessage || Diagnostics._0_expected, arg0 || tokenToString(t)); - } + return undefined; + } - function parseExpectedTokenJSDoc(t: TKind): Token; - function parseExpectedTokenJSDoc(t: JSDocSyntaxKind): Node { - return parseOptionalTokenJSDoc(t) || - createMissingNode(t, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(t)); + function parseSemicolonAfterPropertyName(name: PropertyName, type: TypeNode | undefined, initializer: Expression | undefined) { + if (token() === SyntaxKind.AtToken && !scanner.hasPrecedingLineBreak()) { + parseErrorAtCurrentToken(Diagnostics.Decorators_must_precede_the_name_and_all_keywords_of_property_declarations); + return; } - function parseTokenNode(): T { - const pos = getNodePos(); - const kind = token(); + if (token() === SyntaxKind.OpenParenToken) { + parseErrorAtCurrentToken(Diagnostics.Cannot_start_a_function_call_in_a_type_annotation); nextToken(); - return finishNode(factory.createToken(kind), pos) as T; - } - - function parseTokenNodeJSDoc(): T { - const pos = getNodePos(); - const kind = token(); - nextTokenJSDoc(); - return finishNode(factory.createToken(kind), pos) as T; + return; } - function canParseSemicolon() { - // If there's a real semicolon, then we can always parse it out. - if (token() === SyntaxKind.SemicolonToken) { - return true; + if (type && !canParseSemicolon()) { + if (initializer) { + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); + } + else { + parseErrorAtCurrentToken(Diagnostics.Expected_for_property_initializer); } + return; + } - // We can parse out an optional semicolon in ASI cases in the following cases. - return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.EndOfFileToken || scanner.hasPrecedingLineBreak(); + if (tryParseSemicolon()) { + return; } - function tryParseSemicolon() { - if (!canParseSemicolon()) { - return false; - } + if (initializer) { + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); + return; + } - if (token() === SyntaxKind.SemicolonToken) { - // consume the semicolon if it was explicitly provided. - nextToken(); - } + parseErrorForMissingSemicolonAfter(name); + } + function parseExpectedJSDoc(kind: JSDocSyntaxKind) { + if (token() === kind) { + nextTokenJSDoc(); return true; } + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); + return false; + } - function parseSemicolon(): boolean { - return tryParseSemicolon() || parseExpected(SyntaxKind.SemicolonToken); + function parseExpectedMatchingBrackets(openKind: SyntaxKind, closeKind: SyntaxKind, openParsed: boolean, openPosition: number) { + if (token() === closeKind) { + nextToken(); + return; } - - function createNodeArray(elements: T[], pos: number, end?: number, hasTrailingComma?: boolean): NodeArray { - const array = factory.createNodeArray(elements, hasTrailingComma); - setTextRangePosEnd(array, pos, end ?? scanner.getStartPos()); - return array; + const lastError = parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(closeKind)); + if (!openParsed) { + return; } - - function finishNode(node: T, pos: number, end?: number): T { - setTextRangePosEnd(node, pos, end ?? scanner.getStartPos()); - if (contextFlags) { - (node as Mutable).flags |= contextFlags; - } - - // Keep track on the node if we encountered an error while parsing it. If we did, then - // we cannot reuse the node incrementally. Once we've marked this node, clear out the - // flag so that we don't mark any subsequent nodes. - if (parseErrorBeforeNextFinishedNode) { - parseErrorBeforeNextFinishedNode = false; - (node as Mutable).flags |= NodeFlags.ThisNodeHasError; - } - - return node; + if (lastError) { + addRelatedInfo( + lastError, + createDetachedDiagnostic(fileName, openPosition, 1, Diagnostics.The_parser_expected_to_find_a_1_to_match_the_0_token_here, tokenToString(openKind), tokenToString(closeKind)) + ); } + } - function createMissingNode(kind: T["kind"], reportAtCurrentPosition: false, diagnosticMessage?: DiagnosticMessage, arg0?: any): T; - function createMissingNode(kind: T["kind"], reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): T; - function createMissingNode(kind: T["kind"], reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): T { - if (reportAtCurrentPosition) { - parseErrorAtPosition(scanner.getStartPos(), 0, diagnosticMessage, arg0); - } - else if (diagnosticMessage) { - parseErrorAtCurrentToken(diagnosticMessage, arg0); - } + function parseOptional(t: SyntaxKind): boolean { + if (token() === t) { + nextToken(); + return true; + } + return false; + } - const pos = getNodePos(); - const result = - kind === SyntaxKind.Identifier ? factory.createIdentifier("", /*typeArguments*/ undefined, /*originalKeywordKind*/ undefined) : - isTemplateLiteralKind(kind) ? factory.createTemplateLiteralLikeNode(kind, "", "", /*templateFlags*/ undefined) : - kind === SyntaxKind.NumericLiteral ? factory.createNumericLiteral("", /*numericLiteralFlags*/ undefined) : - kind === SyntaxKind.StringLiteral ? factory.createStringLiteral("", /*isSingleQuote*/ undefined) : - kind === SyntaxKind.MissingDeclaration ? factory.createMissingDeclaration() : - factory.createToken(kind); - return finishNode(result, pos) as T; + function parseOptionalToken(t: TKind): Token; + function parseOptionalToken(t: SyntaxKind): Node | undefined { + if (token() === t) { + return parseTokenNode(); } + return undefined; + } - function internIdentifier(text: string): string { - let identifier = identifiers.get(text); - if (identifier === undefined) { - identifiers.set(text, identifier = text); - } - return identifier; + function parseOptionalTokenJSDoc(t: TKind): Token; + function parseOptionalTokenJSDoc(t: JSDocSyntaxKind): Node | undefined { + if (token() === t) { + return parseTokenNodeJSDoc(); } + return undefined; + } - // An identifier that starts with two underscores has an extra underscore character prepended to it to avoid issues - // with magic property names like '__proto__'. The 'identifiers' object is used to share a single string instance for - // each identifier in order to reduce memory consumption. - function createIdentifier(isIdentifier: boolean, diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier { - if (isIdentifier) { - identifierCount++; - const pos = getNodePos(); - // Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker - const originalKeywordKind = token(); - const text = internIdentifier(scanner.getTokenValue()); - const hasExtendedUnicodeEscape = scanner.hasExtendedUnicodeEscape(); - nextTokenWithoutCheck(); - return finishNode(factory.createIdentifier(text, /*typeArguments*/ undefined, originalKeywordKind, hasExtendedUnicodeEscape), pos); - } + function parseExpectedToken(t: TKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Token; + function parseExpectedToken(t: SyntaxKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Node { + return parseOptionalToken(t) || + createMissingNode(t, /*reportAtCurrentPosition*/ false, diagnosticMessage || Diagnostics._0_expected, arg0 || tokenToString(t)); + } - if (token() === SyntaxKind.PrivateIdentifier) { - parseErrorAtCurrentToken(privateIdentifierDiagnosticMessage || Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); - return createIdentifier(/*isIdentifier*/ true); - } + function parseExpectedTokenJSDoc(t: TKind): Token; + function parseExpectedTokenJSDoc(t: JSDocSyntaxKind): Node { + return parseOptionalTokenJSDoc(t) || + createMissingNode(t, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(t)); + } - if (token() === SyntaxKind.Unknown && scanner.tryScan(() => scanner.reScanInvalidIdentifier() === SyntaxKind.Identifier)) { - // Scanner has already recorded an 'Invalid character' error, so no need to add another from the parser. - return createIdentifier(/*isIdentifier*/ true); - } + function parseTokenNode(): T { + const pos = getNodePos(); + const kind = token(); + nextToken(); + return finishNode(factory.createToken(kind), pos) as T; + } - identifierCount++; - // Only for end of file because the error gets reported incorrectly on embedded script tags. - const reportAtCurrentPosition = token() === SyntaxKind.EndOfFileToken; + function parseTokenNodeJSDoc(): T { + const pos = getNodePos(); + const kind = token(); + nextTokenJSDoc(); + return finishNode(factory.createToken(kind), pos) as T; + } - const isReservedWord = scanner.isReservedWord(); - const msgArg = scanner.getTokenText(); + function canParseSemicolon() { + // If there's a real semicolon, then we can always parse it out. + if (token() === SyntaxKind.SemicolonToken) { + return true; + } - const defaultMessage = isReservedWord ? - Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here : - Diagnostics.Identifier_expected; + // We can parse out an optional semicolon in ASI cases in the following cases. + return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.EndOfFileToken || scanner.hasPrecedingLineBreak(); + } - return createMissingNode(SyntaxKind.Identifier, reportAtCurrentPosition, diagnosticMessage || defaultMessage, msgArg); + function tryParseSemicolon() { + if (!canParseSemicolon()) { + return false; } - function parseBindingIdentifier(privateIdentifierDiagnosticMessage?: DiagnosticMessage) { - return createIdentifier(isBindingIdentifier(), /*diagnosticMessage*/ undefined, privateIdentifierDiagnosticMessage); + if (token() === SyntaxKind.SemicolonToken) { + // consume the semicolon if it was explicitly provided. + nextToken(); } - function parseIdentifier(diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier { - return createIdentifier(isIdentifier(), diagnosticMessage, privateIdentifierDiagnosticMessage); - } + return true; + } - function parseIdentifierName(diagnosticMessage?: DiagnosticMessage): Identifier { - return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage); - } + function parseSemicolon(): boolean { + return tryParseSemicolon() || parseExpected(SyntaxKind.SemicolonToken); + } - function isLiteralPropertyName(): boolean { - return tokenIsIdentifierOrKeyword(token()) || - token() === SyntaxKind.StringLiteral || - token() === SyntaxKind.NumericLiteral; - } + function createNodeArray(elements: T[], pos: number, end?: number, hasTrailingComma?: boolean): NodeArray { + const array = factory.createNodeArray(elements, hasTrailingComma); + setTextRangePosEnd(array, pos, end ?? scanner.getStartPos()); + return array; + } - function isAssertionKey(): boolean { - return tokenIsIdentifierOrKeyword(token()) || - token() === SyntaxKind.StringLiteral; + function finishNode(node: T, pos: number, end?: number): T { + setTextRangePosEnd(node, pos, end ?? scanner.getStartPos()); + if (contextFlags) { + (node as Mutable).flags |= contextFlags; } - function parsePropertyNameWorker(allowComputedPropertyNames: boolean): PropertyName { - if (token() === SyntaxKind.StringLiteral || token() === SyntaxKind.NumericLiteral) { - const node = parseLiteralNode() as StringLiteral | NumericLiteral; - node.text = internIdentifier(node.text); - return node; - } - if (allowComputedPropertyNames && token() === SyntaxKind.OpenBracketToken) { - return parseComputedPropertyName(); - } - if (token() === SyntaxKind.PrivateIdentifier) { - return parsePrivateIdentifier(); - } - return parseIdentifierName(); + // Keep track on the node if we encountered an error while parsing it. If we did, then + // we cannot reuse the node incrementally. Once we've marked this node, clear out the + // flag so that we don't mark any subsequent nodes. + if (parseErrorBeforeNextFinishedNode) { + parseErrorBeforeNextFinishedNode = false; + (node as Mutable).flags |= NodeFlags.ThisNodeHasError; } - function parsePropertyName(): PropertyName { - return parsePropertyNameWorker(/*allowComputedPropertyNames*/ true); - } + return node; + } - function parseComputedPropertyName(): ComputedPropertyName { - // PropertyName [Yield]: - // LiteralPropertyName - // ComputedPropertyName[?Yield] - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBracketToken); - // We parse any expression (including a comma expression). But the grammar - // says that only an assignment expression is allowed, so the grammar checker - // will error if it sees a comma expression. - const expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseBracketToken); - return finishNode(factory.createComputedPropertyName(expression), pos); - } + function createMissingNode(kind: T["kind"], reportAtCurrentPosition: false, diagnosticMessage?: DiagnosticMessage, arg0?: any): T; + function createMissingNode(kind: T["kind"], reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): T; + function createMissingNode(kind: T["kind"], reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): T { + if (reportAtCurrentPosition) { + parseErrorAtPosition(scanner.getStartPos(), 0, diagnosticMessage, arg0); + } + else if (diagnosticMessage) { + parseErrorAtCurrentToken(diagnosticMessage, arg0); + } + + const pos = getNodePos(); + const result = + kind === SyntaxKind.Identifier ? factory.createIdentifier("", /*typeArguments*/ undefined, /*originalKeywordKind*/ undefined) : + isTemplateLiteralKind(kind) ? factory.createTemplateLiteralLikeNode(kind, "", "", /*templateFlags*/ undefined) : + kind === SyntaxKind.NumericLiteral ? factory.createNumericLiteral("", /*numericLiteralFlags*/ undefined) : + kind === SyntaxKind.StringLiteral ? factory.createStringLiteral("", /*isSingleQuote*/ undefined) : + kind === SyntaxKind.MissingDeclaration ? factory.createMissingDeclaration() : + factory.createToken(kind); + return finishNode(result, pos) as T; + } - function internPrivateIdentifier(text: string): string { - let privateIdentifier = privateIdentifiers.get(text); - if (privateIdentifier === undefined) { - privateIdentifiers.set(text, privateIdentifier = text); - } - return privateIdentifier; + function internIdentifier(text: string): string { + let identifier = identifiers.get(text); + if (identifier === undefined) { + identifiers.set(text, identifier = text); } + return identifier; + } - function parsePrivateIdentifier(): PrivateIdentifier { + // An identifier that starts with two underscores has an extra underscore character prepended to it to avoid issues + // with magic property names like '__proto__'. The 'identifiers' object is used to share a single string instance for + // each identifier in order to reduce memory consumption. + function createIdentifier(isIdentifier: boolean, diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier { + if (isIdentifier) { + identifierCount++; const pos = getNodePos(); - const node = factory.createPrivateIdentifier(internPrivateIdentifier(scanner.getTokenValue())); - nextToken(); - return finishNode(node, pos); + // Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker + const originalKeywordKind = token(); + const text = internIdentifier(scanner.getTokenValue()); + const hasExtendedUnicodeEscape = scanner.hasExtendedUnicodeEscape(); + nextTokenWithoutCheck(); + return finishNode(factory.createIdentifier(text, /*typeArguments*/ undefined, originalKeywordKind, hasExtendedUnicodeEscape), pos); } - function parseContextualModifier(t: SyntaxKind): boolean { - return token() === t && tryParse(nextTokenCanFollowModifier); + if (token() === SyntaxKind.PrivateIdentifier) { + parseErrorAtCurrentToken(privateIdentifierDiagnosticMessage || Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + return createIdentifier(/*isIdentifier*/ true); } - function nextTokenIsOnSameLineAndCanFollowModifier() { - nextToken(); - if (scanner.hasPrecedingLineBreak()) { - return false; - } - return canFollowModifier(); + if (token() === SyntaxKind.Unknown && scanner.tryScan(() => scanner.reScanInvalidIdentifier() === SyntaxKind.Identifier)) { + // Scanner has already recorded an 'Invalid character' error, so no need to add another from the parser. + return createIdentifier(/*isIdentifier*/ true); } - function nextTokenCanFollowModifier() { - switch (token()) { - case SyntaxKind.ConstKeyword: - // 'const' is only a modifier if followed by 'enum'. - return nextToken() === SyntaxKind.EnumKeyword; - case SyntaxKind.ExportKeyword: - nextToken(); - if (token() === SyntaxKind.DefaultKeyword) { - return lookAhead(nextTokenCanFollowDefaultKeyword); - } - if (token() === SyntaxKind.TypeKeyword) { - return lookAhead(nextTokenCanFollowExportModifier); - } - return canFollowExportModifier(); - case SyntaxKind.DefaultKeyword: - return nextTokenCanFollowDefaultKeyword(); - case SyntaxKind.AccessorKeyword: - case SyntaxKind.StaticKeyword: - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - nextToken(); - return canFollowModifier(); - default: - return nextTokenIsOnSameLineAndCanFollowModifier(); - } - } + identifierCount++; + // Only for end of file because the error gets reported incorrectly on embedded script tags. + const reportAtCurrentPosition = token() === SyntaxKind.EndOfFileToken; - function canFollowExportModifier(): boolean { - return token() !== SyntaxKind.AsteriskToken - && token() !== SyntaxKind.AsKeyword - && token() !== SyntaxKind.OpenBraceToken - && canFollowModifier(); - } + const isReservedWord = scanner.isReservedWord(); + const msgArg = scanner.getTokenText(); - function nextTokenCanFollowExportModifier(): boolean { - nextToken(); - return canFollowExportModifier(); + const defaultMessage = isReservedWord ? + Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here : + Diagnostics.Identifier_expected; + + return createMissingNode(SyntaxKind.Identifier, reportAtCurrentPosition, diagnosticMessage || defaultMessage, msgArg); + } + + function parseBindingIdentifier(privateIdentifierDiagnosticMessage?: DiagnosticMessage) { + return createIdentifier(isBindingIdentifier(), /*diagnosticMessage*/ undefined, privateIdentifierDiagnosticMessage); + } + + function parseIdentifier(diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier { + return createIdentifier(isIdentifier(), diagnosticMessage, privateIdentifierDiagnosticMessage); + } + + function parseIdentifierName(diagnosticMessage?: DiagnosticMessage): Identifier { + return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage); + } + + function isLiteralPropertyName(): boolean { + return tokenIsIdentifierOrKeyword(token()) || + token() === SyntaxKind.StringLiteral || + token() === SyntaxKind.NumericLiteral; + } + + function isAssertionKey(): boolean { + return tokenIsIdentifierOrKeyword(token()) || + token() === SyntaxKind.StringLiteral; + } + + function parsePropertyNameWorker(allowComputedPropertyNames: boolean): PropertyName { + if (token() === SyntaxKind.StringLiteral || token() === SyntaxKind.NumericLiteral) { + const node = parseLiteralNode() as StringLiteral | NumericLiteral; + node.text = internIdentifier(node.text); + return node; + } + if (allowComputedPropertyNames && token() === SyntaxKind.OpenBracketToken) { + return parseComputedPropertyName(); } + if (token() === SyntaxKind.PrivateIdentifier) { + return parsePrivateIdentifier(); + } + return parseIdentifierName(); + } + + function parsePropertyName(): PropertyName { + return parsePropertyNameWorker(/*allowComputedPropertyNames*/ true); + } + + function parseComputedPropertyName(): ComputedPropertyName { + // PropertyName [Yield]: + // LiteralPropertyName + // ComputedPropertyName[?Yield] + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBracketToken); + // We parse any expression (including a comma expression). But the grammar + // says that only an assignment expression is allowed, so the grammar checker + // will error if it sees a comma expression. + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseBracketToken); + return finishNode(factory.createComputedPropertyName(expression), pos); + } - function parseAnyContextualModifier(): boolean { - return isModifierKind(token()) && tryParse(nextTokenCanFollowModifier); + function internPrivateIdentifier(text: string): string { + let privateIdentifier = privateIdentifiers.get(text); + if (privateIdentifier === undefined) { + privateIdentifiers.set(text, privateIdentifier = text); } + return privateIdentifier; + } + + function parsePrivateIdentifier(): PrivateIdentifier { + const pos = getNodePos(); + const node = factory.createPrivateIdentifier(internPrivateIdentifier(scanner.getTokenValue())); + nextToken(); + return finishNode(node, pos); + } + + function parseContextualModifier(t: SyntaxKind): boolean { + return token() === t && tryParse(nextTokenCanFollowModifier); + } - function canFollowModifier(): boolean { - return token() === SyntaxKind.OpenBracketToken - || token() === SyntaxKind.OpenBraceToken - || token() === SyntaxKind.AsteriskToken - || token() === SyntaxKind.DotDotDotToken - || isLiteralPropertyName(); + function nextTokenIsOnSameLineAndCanFollowModifier() { + nextToken(); + if (scanner.hasPrecedingLineBreak()) { + return false; } + return canFollowModifier(); + } - function nextTokenCanFollowDefaultKeyword(): boolean { - nextToken(); - return token() === SyntaxKind.ClassKeyword || token() === SyntaxKind.FunctionKeyword || - token() === SyntaxKind.InterfaceKeyword || - (token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsClassKeywordOnSameLine)) || - (token() === SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsFunctionKeywordOnSameLine)); + function nextTokenCanFollowModifier() { + switch (token()) { + case SyntaxKind.ConstKeyword: + // 'const' is only a modifier if followed by 'enum'. + return nextToken() === SyntaxKind.EnumKeyword; + case SyntaxKind.ExportKeyword: + nextToken(); + if (token() === SyntaxKind.DefaultKeyword) { + return lookAhead(nextTokenCanFollowDefaultKeyword); + } + if (token() === SyntaxKind.TypeKeyword) { + return lookAhead(nextTokenCanFollowExportModifier); + } + return canFollowExportModifier(); + case SyntaxKind.DefaultKeyword: + return nextTokenCanFollowDefaultKeyword(); + case SyntaxKind.AccessorKeyword: + case SyntaxKind.StaticKeyword: + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + nextToken(); + return canFollowModifier(); + default: + return nextTokenIsOnSameLineAndCanFollowModifier(); } + } - // True if positioned at the start of a list element - function isListElement(parsingContext: ParsingContext, inErrorRecovery: boolean): boolean { - const node = currentNode(parsingContext); - if (node) { - return true; - } + function canFollowExportModifier(): boolean { + return token() !== SyntaxKind.AsteriskToken + && token() !== SyntaxKind.AsKeyword + && token() !== SyntaxKind.OpenBraceToken + && canFollowModifier(); + } - switch (parsingContext) { - case ParsingContext.SourceElements: - case ParsingContext.BlockStatements: - case ParsingContext.SwitchClauseStatements: - // If we're in error recovery, then we don't want to treat ';' as an empty statement. - // The problem is that ';' can show up in far too many contexts, and if we see one - // and assume it's a statement, then we may bail out inappropriately from whatever - // we're parsing. For example, if we have a semicolon in the middle of a class, then - // we really don't want to assume the class is over and we're on a statement in the - // outer module. We just want to consume and move on. - return !(token() === SyntaxKind.SemicolonToken && inErrorRecovery) && isStartOfStatement(); - case ParsingContext.SwitchClauses: - return token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; - case ParsingContext.TypeMembers: - return lookAhead(isTypeMemberStart); - case ParsingContext.ClassMembers: - // We allow semicolons as class elements (as specified by ES6) as long as we're - // not in error recovery. If we're in error recovery, we don't want an errant - // semicolon to be treated as a class member (since they're almost always used - // for statements. - return lookAhead(isClassMemberStart) || (token() === SyntaxKind.SemicolonToken && !inErrorRecovery); - case ParsingContext.EnumMembers: - // Include open bracket computed properties. This technically also lets in indexers, - // which would be a candidate for improved error reporting. - return token() === SyntaxKind.OpenBracketToken || isLiteralPropertyName(); - case ParsingContext.ObjectLiteralMembers: - switch (token()) { - case SyntaxKind.OpenBracketToken: - case SyntaxKind.AsteriskToken: - case SyntaxKind.DotDotDotToken: - case SyntaxKind.DotToken: // Not an object literal member, but don't want to close the object (see `tests/cases/fourslash/completionsDotInObjectLiteral.ts`) - return true; - default: - return isLiteralPropertyName(); - } - case ParsingContext.RestProperties: - return isLiteralPropertyName(); - case ParsingContext.ObjectBindingElements: - return token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.DotDotDotToken || isLiteralPropertyName(); - case ParsingContext.AssertEntries: - return isAssertionKey(); - case ParsingContext.HeritageClauseElement: - // If we see `{ ... }` then only consume it as an expression if it is followed by `,` or `{` - // That way we won't consume the body of a class in its heritage clause. - if (token() === SyntaxKind.OpenBraceToken) { - return lookAhead(isValidHeritageClauseObjectLiteral); - } + function nextTokenCanFollowExportModifier(): boolean { + nextToken(); + return canFollowExportModifier(); + } - if (!inErrorRecovery) { - return isStartOfLeftHandSideExpression() && !isHeritageClauseExtendsOrImplementsKeyword(); - } - else { - // If we're in error recovery we tighten up what we're willing to match. - // That way we don't treat something like "this" as a valid heritage clause - // element during recovery. - return isIdentifier() && !isHeritageClauseExtendsOrImplementsKeyword(); - } - case ParsingContext.VariableDeclarations: - return isBindingIdentifierOrPrivateIdentifierOrPattern(); - case ParsingContext.ArrayBindingElements: - return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isBindingIdentifierOrPrivateIdentifierOrPattern(); - case ParsingContext.TypeParameters: - return token() === SyntaxKind.InKeyword || isIdentifier(); - case ParsingContext.ArrayLiteralMembers: - switch (token()) { - case SyntaxKind.CommaToken: - case SyntaxKind.DotToken: // Not an array literal member, but don't want to close the array (see `tests/cases/fourslash/completionsDotInArrayLiteralInObjectLiteral.ts`) - return true; - } - // falls through - case ParsingContext.ArgumentExpressions: - return token() === SyntaxKind.DotDotDotToken || isStartOfExpression(); - case ParsingContext.Parameters: - return isStartOfParameter(/*isJSDocParameter*/ false); - case ParsingContext.JSDocParameters: - return isStartOfParameter(/*isJSDocParameter*/ true); - case ParsingContext.TypeArguments: - case ParsingContext.TupleElementTypes: - return token() === SyntaxKind.CommaToken || isStartOfType(); - case ParsingContext.HeritageClauses: - return isHeritageClause(); - case ParsingContext.ImportOrExportSpecifiers: - return tokenIsIdentifierOrKeyword(token()); - case ParsingContext.JsxAttributes: - return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken; - case ParsingContext.JsxChildren: - return true; - } + function parseAnyContextualModifier(): boolean { + return isModifierKind(token()) && tryParse(nextTokenCanFollowModifier); + } + + function canFollowModifier(): boolean { + return token() === SyntaxKind.OpenBracketToken + || token() === SyntaxKind.OpenBraceToken + || token() === SyntaxKind.AsteriskToken + || token() === SyntaxKind.DotDotDotToken + || isLiteralPropertyName(); + } + + function nextTokenCanFollowDefaultKeyword(): boolean { + nextToken(); + return token() === SyntaxKind.ClassKeyword || token() === SyntaxKind.FunctionKeyword || + token() === SyntaxKind.InterfaceKeyword || + (token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsClassKeywordOnSameLine)) || + (token() === SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsFunctionKeywordOnSameLine)); + } + + // True if positioned at the start of a list element + function isListElement(parsingContext: ParsingContext, inErrorRecovery: boolean): boolean { + const node = currentNode(parsingContext); + if (node) { + return true; + } + + switch (parsingContext) { + case ParsingContext.SourceElements: + case ParsingContext.BlockStatements: + case ParsingContext.SwitchClauseStatements: + // If we're in error recovery, then we don't want to treat ';' as an empty statement. + // The problem is that ';' can show up in far too many contexts, and if we see one + // and assume it's a statement, then we may bail out inappropriately from whatever + // we're parsing. For example, if we have a semicolon in the middle of a class, then + // we really don't want to assume the class is over and we're on a statement in the + // outer module. We just want to consume and move on. + return !(token() === SyntaxKind.SemicolonToken && inErrorRecovery) && isStartOfStatement(); + case ParsingContext.SwitchClauses: + return token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; + case ParsingContext.TypeMembers: + return lookAhead(isTypeMemberStart); + case ParsingContext.ClassMembers: + // We allow semicolons as class elements (as specified by ES6) as long as we're + // not in error recovery. If we're in error recovery, we don't want an errant + // semicolon to be treated as a class member (since they're almost always used + // for statements. + return lookAhead(isClassMemberStart) || (token() === SyntaxKind.SemicolonToken && !inErrorRecovery); + case ParsingContext.EnumMembers: + // Include open bracket computed properties. This technically also lets in indexers, + // which would be a candidate for improved error reporting. + return token() === SyntaxKind.OpenBracketToken || isLiteralPropertyName(); + case ParsingContext.ObjectLiteralMembers: + switch (token()) { + case SyntaxKind.OpenBracketToken: + case SyntaxKind.AsteriskToken: + case SyntaxKind.DotDotDotToken: + case SyntaxKind.DotToken: // Not an object literal member, but don't want to close the object (see `tests/cases/fourslash/completionsDotInObjectLiteral.ts`) + return true; + default: + return isLiteralPropertyName(); + } + case ParsingContext.RestProperties: + return isLiteralPropertyName(); + case ParsingContext.ObjectBindingElements: + return token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.DotDotDotToken || isLiteralPropertyName(); + case ParsingContext.AssertEntries: + return isAssertionKey(); + case ParsingContext.HeritageClauseElement: + // If we see `{ ... }` then only consume it as an expression if it is followed by `,` or `{` + // That way we won't consume the body of a class in its heritage clause. + if (token() === SyntaxKind.OpenBraceToken) { + return lookAhead(isValidHeritageClauseObjectLiteral); + } - return Debug.fail("Non-exhaustive case in 'isListElement'."); + if (!inErrorRecovery) { + return isStartOfLeftHandSideExpression() && !isHeritageClauseExtendsOrImplementsKeyword(); + } + else { + // If we're in error recovery we tighten up what we're willing to match. + // That way we don't treat something like "this" as a valid heritage clause + // element during recovery. + return isIdentifier() && !isHeritageClauseExtendsOrImplementsKeyword(); + } + case ParsingContext.VariableDeclarations: + return isBindingIdentifierOrPrivateIdentifierOrPattern(); + case ParsingContext.ArrayBindingElements: + return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isBindingIdentifierOrPrivateIdentifierOrPattern(); + case ParsingContext.TypeParameters: + return token() === SyntaxKind.InKeyword || isIdentifier(); + case ParsingContext.ArrayLiteralMembers: + switch (token()) { + case SyntaxKind.CommaToken: + case SyntaxKind.DotToken: // Not an array literal member, but don't want to close the array (see `tests/cases/fourslash/completionsDotInArrayLiteralInObjectLiteral.ts`) + return true; + } + // falls through + case ParsingContext.ArgumentExpressions: + return token() === SyntaxKind.DotDotDotToken || isStartOfExpression(); + case ParsingContext.Parameters: + return isStartOfParameter(/*isJSDocParameter*/ false); + case ParsingContext.JSDocParameters: + return isStartOfParameter(/*isJSDocParameter*/ true); + case ParsingContext.TypeArguments: + case ParsingContext.TupleElementTypes: + return token() === SyntaxKind.CommaToken || isStartOfType(); + case ParsingContext.HeritageClauses: + return isHeritageClause(); + case ParsingContext.ImportOrExportSpecifiers: + return tokenIsIdentifierOrKeyword(token()); + case ParsingContext.JsxAttributes: + return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken; + case ParsingContext.JsxChildren: + return true; } - function isValidHeritageClauseObjectLiteral() { - Debug.assert(token() === SyntaxKind.OpenBraceToken); - if (nextToken() === SyntaxKind.CloseBraceToken) { - // if we see "extends {}" then only treat the {} as what we're extending (and not - // the class body) if we have: - // - // extends {} { - // extends {}, - // extends {} extends - // extends {} implements + return Debug.fail("Non-exhaustive case in 'isListElement'."); + } - const next = nextToken(); - return next === SyntaxKind.CommaToken || next === SyntaxKind.OpenBraceToken || next === SyntaxKind.ExtendsKeyword || next === SyntaxKind.ImplementsKeyword; - } + function isValidHeritageClauseObjectLiteral() { + Debug.assert(token() === SyntaxKind.OpenBraceToken); + if (nextToken() === SyntaxKind.CloseBraceToken) { + // if we see "extends {}" then only treat the {} as what we're extending (and not + // the class body) if we have: + // + // extends {} { + // extends {}, + // extends {} extends + // extends {} implements - return true; + const next = nextToken(); + return next === SyntaxKind.CommaToken || next === SyntaxKind.OpenBraceToken || next === SyntaxKind.ExtendsKeyword || next === SyntaxKind.ImplementsKeyword; } - function nextTokenIsIdentifier() { - nextToken(); - return isIdentifier(); - } + return true; + } - function nextTokenIsIdentifierOrKeyword() { - nextToken(); - return tokenIsIdentifierOrKeyword(token()); - } + function nextTokenIsIdentifier() { + nextToken(); + return isIdentifier(); + } - function nextTokenIsIdentifierOrKeywordOrGreaterThan() { - nextToken(); - return tokenIsIdentifierOrKeywordOrGreaterThan(token()); - } + function nextTokenIsIdentifierOrKeyword() { + nextToken(); + return tokenIsIdentifierOrKeyword(token()); + } - function isHeritageClauseExtendsOrImplementsKeyword(): boolean { - if (token() === SyntaxKind.ImplementsKeyword || - token() === SyntaxKind.ExtendsKeyword) { + function nextTokenIsIdentifierOrKeywordOrGreaterThan() { + nextToken(); + return tokenIsIdentifierOrKeywordOrGreaterThan(token()); + } - return lookAhead(nextTokenIsStartOfExpression); - } + function isHeritageClauseExtendsOrImplementsKeyword(): boolean { + if (token() === SyntaxKind.ImplementsKeyword || + token() === SyntaxKind.ExtendsKeyword) { - return false; + return lookAhead(nextTokenIsStartOfExpression); } - function nextTokenIsStartOfExpression() { - nextToken(); - return isStartOfExpression(); - } + return false; + } - function nextTokenIsStartOfType() { - nextToken(); - return isStartOfType(); - } + function nextTokenIsStartOfExpression() { + nextToken(); + return isStartOfExpression(); + } - // True if positioned at a list terminator - function isListTerminator(kind: ParsingContext): boolean { - if (token() === SyntaxKind.EndOfFileToken) { - // Being at the end of the file ends all lists. - return true; - } + function nextTokenIsStartOfType() { + nextToken(); + return isStartOfType(); + } - switch (kind) { - case ParsingContext.BlockStatements: - case ParsingContext.SwitchClauses: - case ParsingContext.TypeMembers: - case ParsingContext.ClassMembers: - case ParsingContext.EnumMembers: - case ParsingContext.ObjectLiteralMembers: - case ParsingContext.ObjectBindingElements: - case ParsingContext.ImportOrExportSpecifiers: - case ParsingContext.AssertEntries: - return token() === SyntaxKind.CloseBraceToken; - case ParsingContext.SwitchClauseStatements: - return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; - case ParsingContext.HeritageClauseElement: - return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; - case ParsingContext.VariableDeclarations: - return isVariableDeclaratorListTerminator(); - case ParsingContext.TypeParameters: - // Tokens other than '>' are here for better error recovery - return token() === SyntaxKind.GreaterThanToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; - case ParsingContext.ArgumentExpressions: - // Tokens other than ')' are here for better error recovery - return token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.SemicolonToken; - case ParsingContext.ArrayLiteralMembers: - case ParsingContext.TupleElementTypes: - case ParsingContext.ArrayBindingElements: - return token() === SyntaxKind.CloseBracketToken; - case ParsingContext.JSDocParameters: - case ParsingContext.Parameters: - case ParsingContext.RestProperties: - // Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery - return token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.CloseBracketToken /*|| token === SyntaxKind.OpenBraceToken*/; - case ParsingContext.TypeArguments: - // All other tokens should cause the type-argument to terminate except comma token - return token() !== SyntaxKind.CommaToken; - case ParsingContext.HeritageClauses: - return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.CloseBraceToken; - case ParsingContext.JsxAttributes: - return token() === SyntaxKind.GreaterThanToken || token() === SyntaxKind.SlashToken; - case ParsingContext.JsxChildren: - return token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsSlash); - default: - return false; - } + // True if positioned at a list terminator + function isListTerminator(kind: ParsingContext): boolean { + if (token() === SyntaxKind.EndOfFileToken) { + // Being at the end of the file ends all lists. + return true; } - function isVariableDeclaratorListTerminator(): boolean { - // If we can consume a semicolon (either explicitly, or with ASI), then consider us done - // with parsing the list of variable declarators. - if (canParseSemicolon()) { - return true; - } + switch (kind) { + case ParsingContext.BlockStatements: + case ParsingContext.SwitchClauses: + case ParsingContext.TypeMembers: + case ParsingContext.ClassMembers: + case ParsingContext.EnumMembers: + case ParsingContext.ObjectLiteralMembers: + case ParsingContext.ObjectBindingElements: + case ParsingContext.ImportOrExportSpecifiers: + case ParsingContext.AssertEntries: + return token() === SyntaxKind.CloseBraceToken; + case ParsingContext.SwitchClauseStatements: + return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; + case ParsingContext.HeritageClauseElement: + return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + case ParsingContext.VariableDeclarations: + return isVariableDeclaratorListTerminator(); + case ParsingContext.TypeParameters: + // Tokens other than '>' are here for better error recovery + return token() === SyntaxKind.GreaterThanToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + case ParsingContext.ArgumentExpressions: + // Tokens other than ')' are here for better error recovery + return token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.SemicolonToken; + case ParsingContext.ArrayLiteralMembers: + case ParsingContext.TupleElementTypes: + case ParsingContext.ArrayBindingElements: + return token() === SyntaxKind.CloseBracketToken; + case ParsingContext.JSDocParameters: + case ParsingContext.Parameters: + case ParsingContext.RestProperties: + // Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery + return token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.CloseBracketToken /*|| token === SyntaxKind.OpenBraceToken*/; + case ParsingContext.TypeArguments: + // All other tokens should cause the type-argument to terminate except comma token + return token() !== SyntaxKind.CommaToken; + case ParsingContext.HeritageClauses: + return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.CloseBraceToken; + case ParsingContext.JsxAttributes: + return token() === SyntaxKind.GreaterThanToken || token() === SyntaxKind.SlashToken; + case ParsingContext.JsxChildren: + return token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsSlash); + default: + return false; + } + } - // in the case where we're parsing the variable declarator of a 'for-in' statement, we - // are done if we see an 'in' keyword in front of us. Same with for-of - if (isInOrOfKeyword(token())) { - return true; - } + function isVariableDeclaratorListTerminator(): boolean { + // If we can consume a semicolon (either explicitly, or with ASI), then consider us done + // with parsing the list of variable declarators. + if (canParseSemicolon()) { + return true; + } - // ERROR RECOVERY TWEAK: - // For better error recovery, if we see an '=>' then we just stop immediately. We've got an - // arrow function here and it's going to be very unlikely that we'll resynchronize and get - // another variable declaration. - if (token() === SyntaxKind.EqualsGreaterThanToken) { - return true; - } + // in the case where we're parsing the variable declarator of a 'for-in' statement, we + // are done if we see an 'in' keyword in front of us. Same with for-of + if (isInOrOfKeyword(token())) { + return true; + } - // Keep trying to parse out variable declarators. - return false; + // ERROR RECOVERY TWEAK: + // For better error recovery, if we see an '=>' then we just stop immediately. We've got an + // arrow function here and it's going to be very unlikely that we'll resynchronize and get + // another variable declaration. + if (token() === SyntaxKind.EqualsGreaterThanToken) { + return true; } - // True if positioned at element or terminator of the current list or any enclosing list - function isInSomeParsingContext(): boolean { - for (let kind = 0; kind < ParsingContext.Count; kind++) { - if (parsingContext & (1 << kind)) { - if (isListElement(kind, /*inErrorRecovery*/ true) || isListTerminator(kind)) { - return true; - } + // Keep trying to parse out variable declarators. + return false; + } + + // True if positioned at element or terminator of the current list or any enclosing list + function isInSomeParsingContext(): boolean { + for (let kind = 0; kind < ParsingContext.Count; kind++) { + if (parsingContext & (1 << kind)) { + if (isListElement(kind, /*inErrorRecovery*/ true) || isListTerminator(kind)) { + return true; } } - - return false; } - // Parses a list of elements - function parseList(kind: ParsingContext, parseElement: () => T): NodeArray { - const saveParsingContext = parsingContext; - parsingContext |= 1 << kind; - const list = []; - const listPos = getNodePos(); + return false; + } - while (!isListTerminator(kind)) { - if (isListElement(kind, /*inErrorRecovery*/ false)) { - list.push(parseListElement(kind, parseElement)); + // Parses a list of elements + function parseList(kind: ParsingContext, parseElement: () => T): NodeArray { + const saveParsingContext = parsingContext; + parsingContext |= 1 << kind; + const list = []; + const listPos = getNodePos(); - continue; - } + while (!isListTerminator(kind)) { + if (isListElement(kind, /*inErrorRecovery*/ false)) { + list.push(parseListElement(kind, parseElement)); - if (abortParsingListOrMoveToNextToken(kind)) { - break; - } + continue; } - parsingContext = saveParsingContext; - return createNodeArray(list, listPos); - } - - function parseListElement(parsingContext: ParsingContext, parseElement: () => T): T { - const node = currentNode(parsingContext); - if (node) { - return consumeNode(node) as T; + if (abortParsingListOrMoveToNextToken(kind)) { + break; } - - return parseElement(); } - function currentNode(parsingContext: ParsingContext, pos?: number): Node | undefined { - // If we don't have a cursor or the parsing context isn't reusable, there's nothing to reuse. - // - // If there is an outstanding parse error that we've encountered, but not attached to - // some node, then we cannot get a node from the old source tree. This is because we - // want to mark the next node we encounter as being unusable. - // - // Note: This may be too conservative. Perhaps we could reuse the node and set the bit - // on it (or its leftmost child) as having the error. For now though, being conservative - // is nice and likely won't ever affect perf. - if (!syntaxCursor || !isReusableParsingContext(parsingContext) || parseErrorBeforeNextFinishedNode) { - return undefined; - } - - const node = syntaxCursor.currentNode(pos ?? scanner.getStartPos()); + parsingContext = saveParsingContext; + return createNodeArray(list, listPos); + } - // Can't reuse a missing node. - // Can't reuse a node that intersected the change range. - // Can't reuse a node that contains a parse error. This is necessary so that we - // produce the same set of errors again. - if (nodeIsMissing(node) || node.intersectsChange || containsParseError(node)) { - return undefined; - } + function parseListElement(parsingContext: ParsingContext, parseElement: () => T): T { + const node = currentNode(parsingContext); + if (node) { + return consumeNode(node) as T; + } - // We can only reuse a node if it was parsed under the same strict mode that we're - // currently in. i.e. if we originally parsed a node in non-strict mode, but then - // the user added 'using strict' at the top of the file, then we can't use that node - // again as the presence of strict mode may cause us to parse the tokens in the file - // differently. - // - // Note: we *can* reuse tokens when the strict mode changes. That's because tokens - // are unaffected by strict mode. It's just the parser will decide what to do with it - // differently depending on what mode it is in. - // - // This also applies to all our other context flags as well. - const nodeContextFlags = node.flags & NodeFlags.ContextFlags; - if (nodeContextFlags !== contextFlags) { - return undefined; - } + return parseElement(); + } - // Ok, we have a node that looks like it could be reused. Now verify that it is valid - // in the current list parsing context that we're currently at. - if (!canReuseNode(node, parsingContext)) { - return undefined; - } + function currentNode(parsingContext: ParsingContext, pos?: number): Node | undefined { + // If we don't have a cursor or the parsing context isn't reusable, there's nothing to reuse. + // + // If there is an outstanding parse error that we've encountered, but not attached to + // some node, then we cannot get a node from the old source tree. This is because we + // want to mark the next node we encounter as being unusable. + // + // Note: This may be too conservative. Perhaps we could reuse the node and set the bit + // on it (or its leftmost child) as having the error. For now though, being conservative + // is nice and likely won't ever affect perf. + if (!syntaxCursor || !isReusableParsingContext(parsingContext) || parseErrorBeforeNextFinishedNode) { + return undefined; + } - if ((node as JSDocContainer).jsDocCache) { - // jsDocCache may include tags from parent nodes, which might have been modified. - (node as JSDocContainer).jsDocCache = undefined; - } + const node = syntaxCursor.currentNode(pos ?? scanner.getStartPos()); - return node; + // Can't reuse a missing node. + // Can't reuse a node that intersected the change range. + // Can't reuse a node that contains a parse error. This is necessary so that we + // produce the same set of errors again. + if (nodeIsMissing(node) || node.intersectsChange || containsParseError(node)) { + return undefined; } - function consumeNode(node: Node) { - // Move the scanner so it is after the node we just consumed. - scanner.setTextPos(node.end); - nextToken(); - return node; + // We can only reuse a node if it was parsed under the same strict mode that we're + // currently in. i.e. if we originally parsed a node in non-strict mode, but then + // the user added 'using strict' at the top of the file, then we can't use that node + // again as the presence of strict mode may cause us to parse the tokens in the file + // differently. + // + // Note: we *can* reuse tokens when the strict mode changes. That's because tokens + // are unaffected by strict mode. It's just the parser will decide what to do with it + // differently depending on what mode it is in. + // + // This also applies to all our other context flags as well. + const nodeContextFlags = node.flags & NodeFlags.ContextFlags; + if (nodeContextFlags !== contextFlags) { + return undefined; } - function isReusableParsingContext(parsingContext: ParsingContext): boolean { - switch (parsingContext) { - case ParsingContext.ClassMembers: - case ParsingContext.SwitchClauses: - case ParsingContext.SourceElements: - case ParsingContext.BlockStatements: - case ParsingContext.SwitchClauseStatements: - case ParsingContext.EnumMembers: - case ParsingContext.TypeMembers: - case ParsingContext.VariableDeclarations: - case ParsingContext.JSDocParameters: - case ParsingContext.Parameters: - return true; - } - return false; + // Ok, we have a node that looks like it could be reused. Now verify that it is valid + // in the current list parsing context that we're currently at. + if (!canReuseNode(node, parsingContext)) { + return undefined; } - function canReuseNode(node: Node, parsingContext: ParsingContext): boolean { - switch (parsingContext) { - case ParsingContext.ClassMembers: - return isReusableClassMember(node); + if ((node as JSDocContainer).jsDocCache) { + // jsDocCache may include tags from parent nodes, which might have been modified. + (node as JSDocContainer).jsDocCache = undefined; + } - case ParsingContext.SwitchClauses: - return isReusableSwitchClause(node); + return node; + } - case ParsingContext.SourceElements: - case ParsingContext.BlockStatements: - case ParsingContext.SwitchClauseStatements: - return isReusableStatement(node); + function consumeNode(node: Node) { + // Move the scanner so it is after the node we just consumed. + scanner.setTextPos(node.end); + nextToken(); + return node; + } - case ParsingContext.EnumMembers: - return isReusableEnumMember(node); + function isReusableParsingContext(parsingContext: ParsingContext): boolean { + switch (parsingContext) { + case ParsingContext.ClassMembers: + case ParsingContext.SwitchClauses: + case ParsingContext.SourceElements: + case ParsingContext.BlockStatements: + case ParsingContext.SwitchClauseStatements: + case ParsingContext.EnumMembers: + case ParsingContext.TypeMembers: + case ParsingContext.VariableDeclarations: + case ParsingContext.JSDocParameters: + case ParsingContext.Parameters: + return true; + } + return false; + } - case ParsingContext.TypeMembers: - return isReusableTypeMember(node); + function canReuseNode(node: Node, parsingContext: ParsingContext): boolean { + switch (parsingContext) { + case ParsingContext.ClassMembers: + return isReusableClassMember(node); - case ParsingContext.VariableDeclarations: - return isReusableVariableDeclaration(node); + case ParsingContext.SwitchClauses: + return isReusableSwitchClause(node); - case ParsingContext.JSDocParameters: - case ParsingContext.Parameters: - return isReusableParameter(node); + case ParsingContext.SourceElements: + case ParsingContext.BlockStatements: + case ParsingContext.SwitchClauseStatements: + return isReusableStatement(node); - // Any other lists we do not care about reusing nodes in. But feel free to add if - // you can do so safely. Danger areas involve nodes that may involve speculative - // parsing. If speculative parsing is involved with the node, then the range the - // parser reached while looking ahead might be in the edited range (see the example - // in canReuseVariableDeclaratorNode for a good case of this). + case ParsingContext.EnumMembers: + return isReusableEnumMember(node); - // case ParsingContext.HeritageClauses: - // This would probably be safe to reuse. There is no speculative parsing with - // heritage clauses. + case ParsingContext.TypeMembers: + return isReusableTypeMember(node); - // case ParsingContext.TypeParameters: - // This would probably be safe to reuse. There is no speculative parsing with - // type parameters. Note that that's because type *parameters* only occur in - // unambiguous *type* contexts. While type *arguments* occur in very ambiguous - // *expression* contexts. + case ParsingContext.VariableDeclarations: + return isReusableVariableDeclaration(node); - // case ParsingContext.TupleElementTypes: - // This would probably be safe to reuse. There is no speculative parsing with - // tuple types. + case ParsingContext.JSDocParameters: + case ParsingContext.Parameters: + return isReusableParameter(node); - // Technically, type argument list types are probably safe to reuse. While - // speculative parsing is involved with them (since type argument lists are only - // produced from speculative parsing a < as a type argument list), we only have - // the types because speculative parsing succeeded. Thus, the lookahead never - // went past the end of the list and rewound. - // case ParsingContext.TypeArguments: + // Any other lists we do not care about reusing nodes in. But feel free to add if + // you can do so safely. Danger areas involve nodes that may involve speculative + // parsing. If speculative parsing is involved with the node, then the range the + // parser reached while looking ahead might be in the edited range (see the example + // in canReuseVariableDeclaratorNode for a good case of this). - // Note: these are almost certainly not safe to ever reuse. Expressions commonly - // need a large amount of lookahead, and we should not reuse them as they may - // have actually intersected the edit. - // case ParsingContext.ArgumentExpressions: + // case ParsingContext.HeritageClauses: + // This would probably be safe to reuse. There is no speculative parsing with + // heritage clauses. - // This is not safe to reuse for the same reason as the 'AssignmentExpression' - // cases. i.e. a property assignment may end with an expression, and thus might - // have lookahead far beyond it's old node. - // case ParsingContext.ObjectLiteralMembers: + // case ParsingContext.TypeParameters: + // This would probably be safe to reuse. There is no speculative parsing with + // type parameters. Note that that's because type *parameters* only occur in + // unambiguous *type* contexts. While type *arguments* occur in very ambiguous + // *expression* contexts. - // This is probably not safe to reuse. There can be speculative parsing with - // type names in a heritage clause. There can be generic names in the type - // name list, and there can be left hand side expressions (which can have type - // arguments.) - // case ParsingContext.HeritageClauseElement: + // case ParsingContext.TupleElementTypes: + // This would probably be safe to reuse. There is no speculative parsing with + // tuple types. - // Perhaps safe to reuse, but it's unlikely we'd see more than a dozen attributes - // on any given element. Same for children. - // case ParsingContext.JsxAttributes: - // case ParsingContext.JsxChildren: + // Technically, type argument list types are probably safe to reuse. While + // speculative parsing is involved with them (since type argument lists are only + // produced from speculative parsing a < as a type argument list), we only have + // the types because speculative parsing succeeded. Thus, the lookahead never + // went past the end of the list and rewound. + // case ParsingContext.TypeArguments: - } + // Note: these are almost certainly not safe to ever reuse. Expressions commonly + // need a large amount of lookahead, and we should not reuse them as they may + // have actually intersected the edit. + // case ParsingContext.ArgumentExpressions: - return false; - } + // This is not safe to reuse for the same reason as the 'AssignmentExpression' + // cases. i.e. a property assignment may end with an expression, and thus might + // have lookahead far beyond it's old node. + // case ParsingContext.ObjectLiteralMembers: - function isReusableClassMember(node: Node) { - if (node) { - switch (node.kind) { - case SyntaxKind.Constructor: - case SyntaxKind.IndexSignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.SemicolonClassElement: - return true; - case SyntaxKind.MethodDeclaration: - // Method declarations are not necessarily reusable. An object-literal - // may have a method calls "constructor(...)" and we must reparse that - // into an actual .ConstructorDeclaration. - const methodDeclaration = node as MethodDeclaration; - const nameIsConstructor = methodDeclaration.name.kind === SyntaxKind.Identifier && - methodDeclaration.name.originalKeywordKind === SyntaxKind.ConstructorKeyword; + // This is probably not safe to reuse. There can be speculative parsing with + // type names in a heritage clause. There can be generic names in the type + // name list, and there can be left hand side expressions (which can have type + // arguments.) + // case ParsingContext.HeritageClauseElement: - return !nameIsConstructor; - } - } + // Perhaps safe to reuse, but it's unlikely we'd see more than a dozen attributes + // on any given element. Same for children. + // case ParsingContext.JsxAttributes: + // case ParsingContext.JsxChildren: - return false; } - function isReusableSwitchClause(node: Node) { - if (node) { - switch (node.kind) { - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - return true; - } - } + return false; + } - return false; - } + function isReusableClassMember(node: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.IndexSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.SemicolonClassElement: + return true; + case SyntaxKind.MethodDeclaration: + // Method declarations are not necessarily reusable. An object-literal + // may have a method calls "constructor(...)" and we must reparse that + // into an actual .ConstructorDeclaration. + const methodDeclaration = node as MethodDeclaration; + const nameIsConstructor = methodDeclaration.name.kind === SyntaxKind.Identifier && + methodDeclaration.name.originalKeywordKind === SyntaxKind.ConstructorKeyword; - function isReusableStatement(node: Node) { - if (node) { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.VariableStatement: - case SyntaxKind.Block: - case SyntaxKind.IfStatement: - case SyntaxKind.ExpressionStatement: - case SyntaxKind.ThrowStatement: - case SyntaxKind.ReturnStatement: - case SyntaxKind.SwitchStatement: - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.EmptyStatement: - case SyntaxKind.TryStatement: - case SyntaxKind.LabeledStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.DebuggerStatement: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExportAssignment: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - return true; - } + return !nameIsConstructor; } - - return false; } - function isReusableEnumMember(node: Node) { - return node.kind === SyntaxKind.EnumMember; - } + return false; + } - function isReusableTypeMember(node: Node) { - if (node) { - switch (node.kind) { - case SyntaxKind.ConstructSignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.PropertySignature: - case SyntaxKind.CallSignature: - return true; - } + function isReusableSwitchClause(node: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + return true; } - - return false; } - function isReusableVariableDeclaration(node: Node) { - if (node.kind !== SyntaxKind.VariableDeclaration) { - return false; - } + return false; + } - // Very subtle incremental parsing bug. Consider the following code: - // - // let v = new List < A, B - // - // This is actually legal code. It's a list of variable declarators "v = new List() - // - // then we have a problem. "v = new List(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray; - function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray> | undefined; - function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray> | undefined { - const saveParsingContext = parsingContext; - parsingContext |= 1 << kind; - const list: NonNullable[] = []; - const listPos = getNodePos(); - - let commaStart = -1; // Meaning the previous token was not a comma - while (true) { - if (isListElement(kind, /*inErrorRecovery*/ false)) { - const startPos = scanner.getStartPos(); - const result = parseListElement(kind, parseElement); - if (!result) { - parsingContext = saveParsingContext; - return undefined; - } - list.push(result); - commaStart = scanner.getTokenPos(); - - if (parseOptional(SyntaxKind.CommaToken)) { - // No need to check for a zero length node since we know we parsed a comma - continue; - } - - commaStart = -1; // Back to the state where the last token was not a comma - if (isListTerminator(kind)) { - break; - } - - // We didn't get a comma, and the list wasn't terminated, explicitly parse - // out a comma so we give a good error message. - parseExpected(SyntaxKind.CommaToken, getExpectedCommaDiagnostic(kind)); - - // If the token was a semicolon, and the caller allows that, then skip it and - // continue. This ensures we get back on track and don't result in tons of - // parse errors. For example, this can happen when people do things like use - // a semicolon to delimit object literal members. Note: we'll have already - // reported an error when we called parseExpected above. - if (considerSemicolonAsDelimiter && token() === SyntaxKind.SemicolonToken && !scanner.hasPrecedingLineBreak()) { - nextToken(); - } - if (startPos === scanner.getStartPos()) { - // What we're parsing isn't actually remotely recognizable as a element and we've consumed no tokens whatsoever - // Consume a token to advance the parser in some way and avoid an infinite loop - // This can happen when we're speculatively parsing parenthesized expressions which we think may be arrow functions, - // or when a modifier keyword which is disallowed as a parameter name (ie, `static` in strict mode) is supplied - nextToken(); - } - continue; - } + return false; + } - if (isListTerminator(kind)) { - break; - } + function isReusableVariableDeclaration(node: Node) { + if (node.kind !== SyntaxKind.VariableDeclaration) { + return false; + } - if (abortParsingListOrMoveToNextToken(kind)) { - break; - } - } + // Very subtle incremental parsing bug. Consider the following code: + // + // let v = new List < A, B + // + // This is actually legal code. It's a list of variable declarators "v = new List() + // + // then we have a problem. "v = new List= 0); + function isReusableParameter(node: Node) { + if (node.kind !== SyntaxKind.Parameter) { + return false; } - function getExpectedCommaDiagnostic(kind: ParsingContext) { - return kind === ParsingContext.EnumMembers ? Diagnostics.An_enum_member_name_must_be_followed_by_a_or : undefined; - } + // See the comment in isReusableVariableDeclaration for why we do this. + const parameter = node as ParameterDeclaration; + return parameter.initializer === undefined; + } - interface MissingList extends NodeArray { - isMissingList: true; + // Returns true if we should abort parsing. + function abortParsingListOrMoveToNextToken(kind: ParsingContext) { + parsingContextErrors(kind); + if (isInSomeParsingContext()) { + return true; } - function createMissingList(): MissingList { - const list = createNodeArray([], getNodePos()) as MissingList; - list.isMissingList = true; - return list; - } + nextToken(); + return false; + } - function isMissingList(arr: NodeArray): boolean { - return !!(arr as MissingList).isMissingList; + function parsingContextErrors(context: ParsingContext) { + switch (context) { + case ParsingContext.SourceElements: + return token() === SyntaxKind.DefaultKeyword + ? parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.ExportKeyword)) + : parseErrorAtCurrentToken(Diagnostics.Declaration_or_statement_expected); + case ParsingContext.BlockStatements: return parseErrorAtCurrentToken(Diagnostics.Declaration_or_statement_expected); + case ParsingContext.SwitchClauses: return parseErrorAtCurrentToken(Diagnostics.case_or_default_expected); + case ParsingContext.SwitchClauseStatements: return parseErrorAtCurrentToken(Diagnostics.Statement_expected); + case ParsingContext.RestProperties: // fallthrough + case ParsingContext.TypeMembers: return parseErrorAtCurrentToken(Diagnostics.Property_or_signature_expected); + case ParsingContext.ClassMembers: return parseErrorAtCurrentToken(Diagnostics.Unexpected_token_A_constructor_method_accessor_or_property_was_expected); + case ParsingContext.EnumMembers: return parseErrorAtCurrentToken(Diagnostics.Enum_member_expected); + case ParsingContext.HeritageClauseElement: return parseErrorAtCurrentToken(Diagnostics.Expression_expected); + case ParsingContext.VariableDeclarations: + return isKeyword(token()) + ? parseErrorAtCurrentToken(Diagnostics._0_is_not_allowed_as_a_variable_declaration_name, tokenToString(token())) + : parseErrorAtCurrentToken(Diagnostics.Variable_declaration_expected); + case ParsingContext.ObjectBindingElements: return parseErrorAtCurrentToken(Diagnostics.Property_destructuring_pattern_expected); + case ParsingContext.ArrayBindingElements: return parseErrorAtCurrentToken(Diagnostics.Array_element_destructuring_pattern_expected); + case ParsingContext.ArgumentExpressions: return parseErrorAtCurrentToken(Diagnostics.Argument_expression_expected); + case ParsingContext.ObjectLiteralMembers: return parseErrorAtCurrentToken(Diagnostics.Property_assignment_expected); + case ParsingContext.ArrayLiteralMembers: return parseErrorAtCurrentToken(Diagnostics.Expression_or_comma_expected); + case ParsingContext.JSDocParameters: return parseErrorAtCurrentToken(Diagnostics.Parameter_declaration_expected); + case ParsingContext.Parameters: + return isKeyword(token()) + ? parseErrorAtCurrentToken(Diagnostics._0_is_not_allowed_as_a_parameter_name, tokenToString(token())) + : parseErrorAtCurrentToken(Diagnostics.Parameter_declaration_expected); + case ParsingContext.TypeParameters: return parseErrorAtCurrentToken(Diagnostics.Type_parameter_declaration_expected); + case ParsingContext.TypeArguments: return parseErrorAtCurrentToken(Diagnostics.Type_argument_expected); + case ParsingContext.TupleElementTypes: return parseErrorAtCurrentToken(Diagnostics.Type_expected); + case ParsingContext.HeritageClauses: return parseErrorAtCurrentToken(Diagnostics.Unexpected_token_expected); + case ParsingContext.ImportOrExportSpecifiers: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected); + case ParsingContext.JsxAttributes: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected); + case ParsingContext.JsxChildren: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected); + case ParsingContext.AssertEntries: return parseErrorAtCurrentToken(Diagnostics.Identifier_or_string_literal_expected); // AssertionKey. + case ParsingContext.Count: return Debug.fail("ParsingContext.Count used as a context"); // Not a real context, only a marker. + default: Debug.assertNever(context); } + } - function parseBracketedList(kind: ParsingContext, parseElement: () => T, open: SyntaxKind, close: SyntaxKind): NodeArray { - if (parseExpected(open)) { - const result = parseDelimitedList(kind, parseElement); - parseExpected(close); - return result; - } + // Parses a comma-delimited list of elements + function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray; + function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray> | undefined; + function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray> | undefined { + const saveParsingContext = parsingContext; + parsingContext |= 1 << kind; + const list: NonNullable[] = []; + const listPos = getNodePos(); + + let commaStart = -1; // Meaning the previous token was not a comma + while (true) { + if (isListElement(kind, /*inErrorRecovery*/ false)) { + const startPos = scanner.getStartPos(); + const result = parseListElement(kind, parseElement); + if (!result) { + parsingContext = saveParsingContext; + return undefined; + } + list.push(result); + commaStart = scanner.getTokenPos(); - return createMissingList(); - } + if (parseOptional(SyntaxKind.CommaToken)) { + // No need to check for a zero length node since we know we parsed a comma + continue; + } - function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { - const pos = getNodePos(); - let entity: EntityName = allowReservedWords ? parseIdentifierName(diagnosticMessage) : parseIdentifier(diagnosticMessage); - let dotPos = getNodePos(); - while (parseOptional(SyntaxKind.DotToken)) { - if (token() === SyntaxKind.LessThanToken) { - // the entity is part of a JSDoc-style generic, so record the trailing dot for later error reporting - entity.jsdocDotPos = dotPos; + commaStart = -1; // Back to the state where the last token was not a comma + if (isListTerminator(kind)) { break; } - dotPos = getNodePos(); - entity = finishNode( - factory.createQualifiedName( - entity, - parseRightSideOfDot(allowReservedWords, /* allowPrivateIdentifiers */ false) as Identifier - ), - pos - ); - } - return entity; - } - - function createQualifiedName(entity: EntityName, name: Identifier): QualifiedName { - return finishNode(factory.createQualifiedName(entity, name), entity.pos); - } - function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateIdentifiers: boolean): Identifier | PrivateIdentifier { - // Technically a keyword is valid here as all identifiers and keywords are identifier names. - // However, often we'll encounter this in error situations when the identifier or keyword - // is actually starting another valid construct. - // - // So, we check for the following specific case: - // - // name. - // identifierOrKeyword identifierNameOrKeyword - // - // Note: the newlines are important here. For example, if that above code - // were rewritten into: - // - // name.identifierOrKeyword - // identifierNameOrKeyword - // - // Then we would consider it valid. That's because ASI would take effect and - // the code would be implicitly: "name.identifierOrKeyword; identifierNameOrKeyword". - // In the first case though, ASI will not take effect because there is not a - // line terminator after the identifier or keyword. - if (scanner.hasPrecedingLineBreak() && tokenIsIdentifierOrKeyword(token())) { - const matchesPattern = lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine); + // We didn't get a comma, and the list wasn't terminated, explicitly parse + // out a comma so we give a good error message. + parseExpected(SyntaxKind.CommaToken, getExpectedCommaDiagnostic(kind)); - if (matchesPattern) { - // Report that we need an identifier. However, report it right after the dot, - // and not on the next token. This is because the next token might actually - // be an identifier and the error would be quite confusing. - return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected); + // If the token was a semicolon, and the caller allows that, then skip it and + // continue. This ensures we get back on track and don't result in tons of + // parse errors. For example, this can happen when people do things like use + // a semicolon to delimit object literal members. Note: we'll have already + // reported an error when we called parseExpected above. + if (considerSemicolonAsDelimiter && token() === SyntaxKind.SemicolonToken && !scanner.hasPrecedingLineBreak()) { + nextToken(); + } + if (startPos === scanner.getStartPos()) { + // What we're parsing isn't actually remotely recognizable as a element and we've consumed no tokens whatsoever + // Consume a token to advance the parser in some way and avoid an infinite loop + // This can happen when we're speculatively parsing parenthesized expressions which we think may be arrow functions, + // or when a modifier keyword which is disallowed as a parameter name (ie, `static` in strict mode) is supplied + nextToken(); } + continue; } - if (token() === SyntaxKind.PrivateIdentifier) { - const node = parsePrivateIdentifier(); - return allowPrivateIdentifiers ? node : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected); + if (isListTerminator(kind)) { + break; } - return allowIdentifierNames ? parseIdentifierName() : parseIdentifier(); - } - - function parseTemplateSpans(isTaggedTemplate: boolean) { - const pos = getNodePos(); - const list = []; - let node: TemplateSpan; - do { - node = parseTemplateSpan(isTaggedTemplate); - list.push(node); + if (abortParsingListOrMoveToNextToken(kind)) { + break; } - while (node.literal.kind === SyntaxKind.TemplateMiddle); - return createNodeArray(list, pos); } - function parseTemplateExpression(isTaggedTemplate: boolean): TemplateExpression { - const pos = getNodePos(); - return finishNode( - factory.createTemplateExpression( - parseTemplateHead(isTaggedTemplate), - parseTemplateSpans(isTaggedTemplate) - ), - pos - ); - } + parsingContext = saveParsingContext; + // Recording the trailing comma is deliberately done after the previous + // loop, and not just if we see a list terminator. This is because the list + // may have ended incorrectly, but it is still important to know if there + // was a trailing comma. + // Check if the last token was a comma. + // Always preserve a trailing comma by marking it on the NodeArray + return createNodeArray(list, listPos, /*end*/ undefined, commaStart >= 0); + } - function parseTemplateType(): TemplateLiteralTypeNode { - const pos = getNodePos(); - return finishNode( - factory.createTemplateLiteralType( - parseTemplateHead(/*isTaggedTemplate*/ false), - parseTemplateTypeSpans() - ), - pos - ); - } + function getExpectedCommaDiagnostic(kind: ParsingContext) { + return kind === ParsingContext.EnumMembers ? Diagnostics.An_enum_member_name_must_be_followed_by_a_or : undefined; + } - function parseTemplateTypeSpans() { - const pos = getNodePos(); - const list = []; - let node: TemplateLiteralTypeSpan; - do { - node = parseTemplateTypeSpan(); - list.push(node); - } - while (node.literal.kind === SyntaxKind.TemplateMiddle); - return createNodeArray(list, pos); - } + interface MissingList extends NodeArray { + isMissingList: true; + } - function parseTemplateTypeSpan(): TemplateLiteralTypeSpan { - const pos = getNodePos(); - return finishNode( - factory.createTemplateLiteralTypeSpan( - parseType(), - parseLiteralOfTemplateSpan(/*isTaggedTemplate*/ false) - ), - pos - ); - } + function createMissingList(): MissingList { + const list = createNodeArray([], getNodePos()) as MissingList; + list.isMissingList = true; + return list; + } + + function isMissingList(arr: NodeArray): boolean { + return !!(arr as MissingList).isMissingList; + } - function parseLiteralOfTemplateSpan(isTaggedTemplate: boolean) { - if (token() === SyntaxKind.CloseBraceToken) { - reScanTemplateToken(isTaggedTemplate); - return parseTemplateMiddleOrTemplateTail(); - } - else { - // TODO(rbuckton): Do we need to call `parseExpectedToken` or can we just call `createMissingNode` directly? - return parseExpectedToken(SyntaxKind.TemplateTail, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken)) as TemplateTail; - } + function parseBracketedList(kind: ParsingContext, parseElement: () => T, open: SyntaxKind, close: SyntaxKind): NodeArray { + if (parseExpected(open)) { + const result = parseDelimitedList(kind, parseElement); + parseExpected(close); + return result; } - function parseTemplateSpan(isTaggedTemplate: boolean): TemplateSpan { - const pos = getNodePos(); - return finishNode( - factory.createTemplateSpan( - allowInAnd(parseExpression), - parseLiteralOfTemplateSpan(isTaggedTemplate) + return createMissingList(); + } + + function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { + const pos = getNodePos(); + let entity: EntityName = allowReservedWords ? parseIdentifierName(diagnosticMessage) : parseIdentifier(diagnosticMessage); + let dotPos = getNodePos(); + while (parseOptional(SyntaxKind.DotToken)) { + if (token() === SyntaxKind.LessThanToken) { + // the entity is part of a JSDoc-style generic, so record the trailing dot for later error reporting + entity.jsdocDotPos = dotPos; + break; + } + dotPos = getNodePos(); + entity = finishNode( + factory.createQualifiedName( + entity, + parseRightSideOfDot(allowReservedWords, /* allowPrivateIdentifiers */ false) as Identifier ), pos ); } + return entity; + } - function parseLiteralNode(): LiteralExpression { - return parseLiteralLikeNode(token()) as LiteralExpression; - } + function createQualifiedName(entity: EntityName, name: Identifier): QualifiedName { + return finishNode(factory.createQualifiedName(entity, name), entity.pos); + } + + function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateIdentifiers: boolean): Identifier | PrivateIdentifier { + // Technically a keyword is valid here as all identifiers and keywords are identifier names. + // However, often we'll encounter this in error situations when the identifier or keyword + // is actually starting another valid construct. + // + // So, we check for the following specific case: + // + // name. + // identifierOrKeyword identifierNameOrKeyword + // + // Note: the newlines are important here. For example, if that above code + // were rewritten into: + // + // name.identifierOrKeyword + // identifierNameOrKeyword + // + // Then we would consider it valid. That's because ASI would take effect and + // the code would be implicitly: "name.identifierOrKeyword; identifierNameOrKeyword". + // In the first case though, ASI will not take effect because there is not a + // line terminator after the identifier or keyword. + if (scanner.hasPrecedingLineBreak() && tokenIsIdentifierOrKeyword(token())) { + const matchesPattern = lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine); - function parseTemplateHead(isTaggedTemplate: boolean): TemplateHead { - if (isTaggedTemplate) { - reScanTemplateHeadOrNoSubstitutionTemplate(); + if (matchesPattern) { + // Report that we need an identifier. However, report it right after the dot, + // and not on the next token. This is because the next token might actually + // be an identifier and the error would be quite confusing. + return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected); } - const fragment = parseLiteralLikeNode(token()); - Debug.assert(fragment.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); - return fragment as TemplateHead; } - function parseTemplateMiddleOrTemplateTail(): TemplateMiddle | TemplateTail { - const fragment = parseLiteralLikeNode(token()); - Debug.assert(fragment.kind === SyntaxKind.TemplateMiddle || fragment.kind === SyntaxKind.TemplateTail, "Template fragment has wrong token kind"); - return fragment as TemplateMiddle | TemplateTail; + if (token() === SyntaxKind.PrivateIdentifier) { + const node = parsePrivateIdentifier(); + return allowPrivateIdentifiers ? node : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected); } - function getTemplateLiteralRawText(kind: TemplateLiteralToken["kind"]) { - const isLast = kind === SyntaxKind.NoSubstitutionTemplateLiteral || kind === SyntaxKind.TemplateTail; - const tokenText = scanner.getTokenText(); - return tokenText.substring(1, tokenText.length - (scanner.isUnterminated() ? 0 : isLast ? 1 : 2)); - } + return allowIdentifierNames ? parseIdentifierName() : parseIdentifier(); + } - function parseLiteralLikeNode(kind: SyntaxKind): LiteralLikeNode { - const pos = getNodePos(); - const node = - isTemplateLiteralKind(kind) ? factory.createTemplateLiteralLikeNode(kind, scanner.getTokenValue(), getTemplateLiteralRawText(kind), scanner.getTokenFlags() & TokenFlags.TemplateLiteralLikeFlags) : - // Octal literals are not allowed in strict mode or ES5 - // Note that theoretically the following condition would hold true literals like 009, - // which is not octal. But because of how the scanner separates the tokens, we would - // never get a token like this. Instead, we would get 00 and 9 as two separate tokens. - // We also do not need to check for negatives because any prefix operator would be part of a - // parent unary expression. - kind === SyntaxKind.NumericLiteral ? factory.createNumericLiteral(scanner.getTokenValue(), scanner.getNumericLiteralFlags()) : - kind === SyntaxKind.StringLiteral ? factory.createStringLiteral(scanner.getTokenValue(), /*isSingleQuote*/ undefined, scanner.hasExtendedUnicodeEscape()) : - isLiteralKind(kind) ? factory.createLiteralLikeNode(kind, scanner.getTokenValue()) : - Debug.fail(); + function parseTemplateSpans(isTaggedTemplate: boolean) { + const pos = getNodePos(); + const list = []; + let node: TemplateSpan; + do { + node = parseTemplateSpan(isTaggedTemplate); + list.push(node); + } + while (node.literal.kind === SyntaxKind.TemplateMiddle); + return createNodeArray(list, pos); + } - if (scanner.hasExtendedUnicodeEscape()) { - node.hasExtendedUnicodeEscape = true; - } + function parseTemplateExpression(isTaggedTemplate: boolean): TemplateExpression { + const pos = getNodePos(); + return finishNode( + factory.createTemplateExpression( + parseTemplateHead(isTaggedTemplate), + parseTemplateSpans(isTaggedTemplate) + ), + pos + ); + } - if (scanner.isUnterminated()) { - node.isUnterminated = true; - } + function parseTemplateType(): TemplateLiteralTypeNode { + const pos = getNodePos(); + return finishNode( + factory.createTemplateLiteralType( + parseTemplateHead(/*isTaggedTemplate*/ false), + parseTemplateTypeSpans() + ), + pos + ); + } - nextToken(); - return finishNode(node, pos); + function parseTemplateTypeSpans() { + const pos = getNodePos(); + const list = []; + let node: TemplateLiteralTypeSpan; + do { + node = parseTemplateTypeSpan(); + list.push(node); } + while (node.literal.kind === SyntaxKind.TemplateMiddle); + return createNodeArray(list, pos); + } - // TYPES + function parseTemplateTypeSpan(): TemplateLiteralTypeSpan { + const pos = getNodePos(); + return finishNode( + factory.createTemplateLiteralTypeSpan( + parseType(), + parseLiteralOfTemplateSpan(/*isTaggedTemplate*/ false) + ), + pos + ); + } - function parseEntityNameOfTypeReference() { - return parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected); + function parseLiteralOfTemplateSpan(isTaggedTemplate: boolean) { + if (token() === SyntaxKind.CloseBraceToken) { + reScanTemplateToken(isTaggedTemplate); + return parseTemplateMiddleOrTemplateTail(); } - - function parseTypeArgumentsOfTypeReference() { - if (!scanner.hasPrecedingLineBreak() && reScanLessThanToken() === SyntaxKind.LessThanToken) { - return parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); - } + else { + // TODO(rbuckton): Do we need to call `parseExpectedToken` or can we just call `createMissingNode` directly? + return parseExpectedToken(SyntaxKind.TemplateTail, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken)) as TemplateTail; } + } - function parseTypeReference(): TypeReferenceNode { - const pos = getNodePos(); - return finishNode( - factory.createTypeReferenceNode( - parseEntityNameOfTypeReference(), - parseTypeArgumentsOfTypeReference() - ), - pos - ); - } + function parseTemplateSpan(isTaggedTemplate: boolean): TemplateSpan { + const pos = getNodePos(); + return finishNode( + factory.createTemplateSpan( + allowInAnd(parseExpression), + parseLiteralOfTemplateSpan(isTaggedTemplate) + ), + pos + ); + } - // If true, we should abort parsing an error function. - function typeHasArrowFunctionBlockingParseError(node: TypeNode): boolean { - switch (node.kind) { - case SyntaxKind.TypeReference: - return nodeIsMissing((node as TypeReferenceNode).typeName); - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: { - const { parameters, type } = node as FunctionOrConstructorTypeNode; - return isMissingList(parameters) || typeHasArrowFunctionBlockingParseError(type); - } - case SyntaxKind.ParenthesizedType: - return typeHasArrowFunctionBlockingParseError((node as ParenthesizedTypeNode).type); - default: - return false; - } + function parseLiteralNode(): LiteralExpression { + return parseLiteralLikeNode(token()) as LiteralExpression; + } + + function parseTemplateHead(isTaggedTemplate: boolean): TemplateHead { + if (isTaggedTemplate) { + reScanTemplateHeadOrNoSubstitutionTemplate(); } + const fragment = parseLiteralLikeNode(token()); + Debug.assert(fragment.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); + return fragment as TemplateHead; + } - function parseThisTypePredicate(lhs: ThisTypeNode): TypePredicateNode { - nextToken(); - return finishNode(factory.createTypePredicateNode(/*assertsModifier*/ undefined, lhs, parseType()), lhs.pos); + function parseTemplateMiddleOrTemplateTail(): TemplateMiddle | TemplateTail { + const fragment = parseLiteralLikeNode(token()); + Debug.assert(fragment.kind === SyntaxKind.TemplateMiddle || fragment.kind === SyntaxKind.TemplateTail, "Template fragment has wrong token kind"); + return fragment as TemplateMiddle | TemplateTail; + } + + function getTemplateLiteralRawText(kind: TemplateLiteralToken["kind"]) { + const isLast = kind === SyntaxKind.NoSubstitutionTemplateLiteral || kind === SyntaxKind.TemplateTail; + const tokenText = scanner.getTokenText(); + return tokenText.substring(1, tokenText.length - (scanner.isUnterminated() ? 0 : isLast ? 1 : 2)); + } + + function parseLiteralLikeNode(kind: SyntaxKind): LiteralLikeNode { + const pos = getNodePos(); + const node = + isTemplateLiteralKind(kind) ? factory.createTemplateLiteralLikeNode(kind, scanner.getTokenValue(), getTemplateLiteralRawText(kind), scanner.getTokenFlags() & TokenFlags.TemplateLiteralLikeFlags) : + // Octal literals are not allowed in strict mode or ES5 + // Note that theoretically the following condition would hold true literals like 009, + // which is not octal. But because of how the scanner separates the tokens, we would + // never get a token like this. Instead, we would get 00 and 9 as two separate tokens. + // We also do not need to check for negatives because any prefix operator would be part of a + // parent unary expression. + kind === SyntaxKind.NumericLiteral ? factory.createNumericLiteral(scanner.getTokenValue(), scanner.getNumericLiteralFlags()) : + kind === SyntaxKind.StringLiteral ? factory.createStringLiteral(scanner.getTokenValue(), /*isSingleQuote*/ undefined, scanner.hasExtendedUnicodeEscape()) : + isLiteralKind(kind) ? factory.createLiteralLikeNode(kind, scanner.getTokenValue()) : + Debug.fail(); + + if (scanner.hasExtendedUnicodeEscape()) { + node.hasExtendedUnicodeEscape = true; } - function parseThisTypeNode(): ThisTypeNode { - const pos = getNodePos(); - nextToken(); - return finishNode(factory.createThisTypeNode(), pos); + if (scanner.isUnterminated()) { + node.isUnterminated = true; } - function parseJSDocAllType(): JSDocAllType | JSDocOptionalType { - const pos = getNodePos(); - nextToken(); - return finishNode(factory.createJSDocAllType(), pos); + nextToken(); + return finishNode(node, pos); + } + + // TYPES + + function parseEntityNameOfTypeReference() { + return parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected); + } + + function parseTypeArgumentsOfTypeReference() { + if (!scanner.hasPrecedingLineBreak() && reScanLessThanToken() === SyntaxKind.LessThanToken) { + return parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } + } - function parseJSDocNonNullableType(): TypeNode { - const pos = getNodePos(); - nextToken(); - return finishNode(factory.createJSDocNonNullableType(parseNonArrayType(), /*postfix*/ false), pos); + function parseTypeReference(): TypeReferenceNode { + const pos = getNodePos(); + return finishNode( + factory.createTypeReferenceNode( + parseEntityNameOfTypeReference(), + parseTypeArgumentsOfTypeReference() + ), + pos + ); + } + + // If true, we should abort parsing an error function. + function typeHasArrowFunctionBlockingParseError(node: TypeNode): boolean { + switch (node.kind) { + case SyntaxKind.TypeReference: + return nodeIsMissing((node as TypeReferenceNode).typeName); + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: { + const { parameters, type } = node as FunctionOrConstructorTypeNode; + return isMissingList(parameters) || typeHasArrowFunctionBlockingParseError(type); + } + case SyntaxKind.ParenthesizedType: + return typeHasArrowFunctionBlockingParseError((node as ParenthesizedTypeNode).type); + default: + return false; } + } - function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType { - const pos = getNodePos(); - // skip the ? - nextToken(); + function parseThisTypePredicate(lhs: ThisTypeNode): TypePredicateNode { + nextToken(); + return finishNode(factory.createTypePredicateNode(/*assertsModifier*/ undefined, lhs, parseType()), lhs.pos); + } - // Need to lookahead to decide if this is a nullable or unknown type. + function parseThisTypeNode(): ThisTypeNode { + const pos = getNodePos(); + nextToken(); + return finishNode(factory.createThisTypeNode(), pos); + } - // Here are cases where we'll pick the unknown type: - // - // Foo(?, - // { a: ? } - // Foo(?) - // Foo - // Foo(?= - // (?| - if (token() === SyntaxKind.CommaToken || - token() === SyntaxKind.CloseBraceToken || - token() === SyntaxKind.CloseParenToken || - token() === SyntaxKind.GreaterThanToken || - token() === SyntaxKind.EqualsToken || - token() === SyntaxKind.BarToken) { - return finishNode(factory.createJSDocUnknownType(), pos); - } - else { - return finishNode(factory.createJSDocNullableType(parseType(), /*postfix*/ false), pos); - } + function parseJSDocAllType(): JSDocAllType | JSDocOptionalType { + const pos = getNodePos(); + nextToken(); + return finishNode(factory.createJSDocAllType(), pos); + } + + function parseJSDocNonNullableType(): TypeNode { + const pos = getNodePos(); + nextToken(); + return finishNode(factory.createJSDocNonNullableType(parseNonArrayType(), /*postfix*/ false), pos); + } + + function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType { + const pos = getNodePos(); + // skip the ? + nextToken(); + + // Need to lookahead to decide if this is a nullable or unknown type. + + // Here are cases where we'll pick the unknown type: + // + // Foo(?, + // { a: ? } + // Foo(?) + // Foo + // Foo(?= + // (?| + if (token() === SyntaxKind.CommaToken || + token() === SyntaxKind.CloseBraceToken || + token() === SyntaxKind.CloseParenToken || + token() === SyntaxKind.GreaterThanToken || + token() === SyntaxKind.EqualsToken || + token() === SyntaxKind.BarToken) { + return finishNode(factory.createJSDocUnknownType(), pos); + } + else { + return finishNode(factory.createJSDocNullableType(parseType(), /*postfix*/ false), pos); } + } - function parseJSDocFunctionType(): JSDocFunctionType | TypeReferenceNode { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - if (lookAhead(nextTokenIsOpenParen)) { - nextToken(); - const parameters = parseParameters(SignatureFlags.Type | SignatureFlags.JSDoc); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - return withJSDoc(finishNode(factory.createJSDocFunctionType(parameters, type), pos), hasJSDoc); - } - return finishNode(factory.createTypeReferenceNode(parseIdentifierName(), /*typeArguments*/ undefined), pos); + function parseJSDocFunctionType(): JSDocFunctionType | TypeReferenceNode { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + if (lookAhead(nextTokenIsOpenParen)) { + nextToken(); + const parameters = parseParameters(SignatureFlags.Type | SignatureFlags.JSDoc); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + return withJSDoc(finishNode(factory.createJSDocFunctionType(parameters, type), pos), hasJSDoc); } + return finishNode(factory.createTypeReferenceNode(parseIdentifierName(), /*typeArguments*/ undefined), pos); + } - function parseJSDocParameter(): ParameterDeclaration { - const pos = getNodePos(); - let name: Identifier | undefined; - if (token() === SyntaxKind.ThisKeyword || token() === SyntaxKind.NewKeyword) { - name = parseIdentifierName(); - parseExpected(SyntaxKind.ColonToken); - } - return finishNode( - factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - // TODO(rbuckton): JSDoc parameters don't have names (except `this`/`new`), should we manufacture an empty identifier? - name!, - /*questionToken*/ undefined, - parseJSDocType(), - /*initializer*/ undefined - ), - pos - ); + function parseJSDocParameter(): ParameterDeclaration { + const pos = getNodePos(); + let name: Identifier | undefined; + if (token() === SyntaxKind.ThisKeyword || token() === SyntaxKind.NewKeyword) { + name = parseIdentifierName(); + parseExpected(SyntaxKind.ColonToken); } + return finishNode( + factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + // TODO(rbuckton): JSDoc parameters don't have names (except `this`/`new`), should we manufacture an empty identifier? + name!, + /*questionToken*/ undefined, + parseJSDocType(), + /*initializer*/ undefined + ), + pos + ); + } - function parseJSDocType(): TypeNode { - scanner.setInJSDocType(true); - const pos = getNodePos(); - if (parseOptional(SyntaxKind.ModuleKeyword)) { - // TODO(rbuckton): We never set the type for a JSDocNamepathType. What should we put here? - const moduleTag = factory.createJSDocNamepathType(/*type*/ undefined!); - terminate: while (true) { - switch (token()) { - case SyntaxKind.CloseBraceToken: - case SyntaxKind.EndOfFileToken: - case SyntaxKind.CommaToken: - case SyntaxKind.WhitespaceTrivia: - break terminate; - default: - nextTokenJSDoc(); - } + function parseJSDocType(): TypeNode { + scanner.setInJSDocType(true); + const pos = getNodePos(); + if (parseOptional(SyntaxKind.ModuleKeyword)) { + // TODO(rbuckton): We never set the type for a JSDocNamepathType. What should we put here? + const moduleTag = factory.createJSDocNamepathType(/*type*/ undefined!); + terminate: while (true) { + switch (token()) { + case SyntaxKind.CloseBraceToken: + case SyntaxKind.EndOfFileToken: + case SyntaxKind.CommaToken: + case SyntaxKind.WhitespaceTrivia: + break terminate; + default: + nextTokenJSDoc(); } - - scanner.setInJSDocType(false); - return finishNode(moduleTag, pos); } - const hasDotDotDot = parseOptional(SyntaxKind.DotDotDotToken); - let type = parseTypeOrTypePredicate(); scanner.setInJSDocType(false); - if (hasDotDotDot) { - type = finishNode(factory.createJSDocVariadicType(type), pos); - } - if (token() === SyntaxKind.EqualsToken) { - nextToken(); - return finishNode(factory.createJSDocOptionalType(type), pos); - } - return type; + return finishNode(moduleTag, pos); } - function parseTypeQuery(): TypeQueryNode { - const pos = getNodePos(); - parseExpected(SyntaxKind.TypeOfKeyword); - const entityName = parseEntityName(/*allowReservedWords*/ true); - // Make sure we perform ASI to prevent parsing the next line's type arguments as part of an instantiation expression. - const typeArguments = !scanner.hasPrecedingLineBreak() ? tryParseTypeArguments() : undefined; - return finishNode(factory.createTypeQueryNode(entityName, typeArguments), pos); + const hasDotDotDot = parseOptional(SyntaxKind.DotDotDotToken); + let type = parseTypeOrTypePredicate(); + scanner.setInJSDocType(false); + if (hasDotDotDot) { + type = finishNode(factory.createJSDocVariadicType(type), pos); } - - function parseTypeParameter(): TypeParameterDeclaration { - const pos = getNodePos(); - const modifiers = parseModifiers(); - const name = parseIdentifier(); - let constraint: TypeNode | undefined; - let expression: Expression | undefined; - if (parseOptional(SyntaxKind.ExtendsKeyword)) { - // It's not uncommon for people to write improper constraints to a generic. If the - // user writes a constraint that is an expression and not an actual type, then parse - // it out as an expression (so we can recover well), but report that a type is needed - // instead. - if (isStartOfType() || !isStartOfExpression()) { - constraint = parseType(); - } - else { - // It was not a type, and it looked like an expression. Parse out an expression - // here so we recover well. Note: it is important that we call parseUnaryExpression - // and not parseExpression here. If the user has: - // - // - // - // We do *not* want to consume the `>` as we're consuming the expression for "". - expression = parseUnaryExpressionOrHigher(); - } - } - - const defaultType = parseOptional(SyntaxKind.EqualsToken) ? parseType() : undefined; - const node = factory.createTypeParameterDeclaration(modifiers, name, constraint, defaultType); - node.expression = expression; - return finishNode(node, pos); + if (token() === SyntaxKind.EqualsToken) { + nextToken(); + return finishNode(factory.createJSDocOptionalType(type), pos); } + return type; + } - function parseTypeParameters(): NodeArray | undefined { - if (token() === SyntaxKind.LessThanToken) { - return parseBracketedList(ParsingContext.TypeParameters, parseTypeParameter, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); - } - } - - function isStartOfParameter(isJSDocParameter: boolean): boolean { - return token() === SyntaxKind.DotDotDotToken || - isBindingIdentifierOrPrivateIdentifierOrPattern() || - isModifierKind(token()) || - token() === SyntaxKind.AtToken || - isStartOfType(/*inStartOfParameter*/ !isJSDocParameter); - } - - function parseNameOfParameter(modifiers: NodeArray | undefined) { - // FormalParameter [Yield,Await]: - // BindingElement[?Yield,?Await] - const name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_cannot_be_used_as_parameters); - if (getFullWidth(name) === 0 && !some(modifiers) && isModifierKind(token())) { - // in cases like - // 'use strict' - // function foo(static) - // isParameter('static') === true, because of isModifier('static') - // however 'static' is not a legal identifier in a strict mode. - // so result of this function will be ParameterDeclaration (flags = 0, name = missing, type = undefined, initializer = undefined) - // and current token will not change => parsing of the enclosing parameter list will last till the end of time (or OOM) - // to avoid this we'll advance cursor to the next token. - nextToken(); + function parseTypeQuery(): TypeQueryNode { + const pos = getNodePos(); + parseExpected(SyntaxKind.TypeOfKeyword); + const entityName = parseEntityName(/*allowReservedWords*/ true); + // Make sure we perform ASI to prevent parsing the next line's type arguments as part of an instantiation expression. + const typeArguments = !scanner.hasPrecedingLineBreak() ? tryParseTypeArguments() : undefined; + return finishNode(factory.createTypeQueryNode(entityName, typeArguments), pos); + } + + function parseTypeParameter(): TypeParameterDeclaration { + const pos = getNodePos(); + const modifiers = parseModifiers(); + const name = parseIdentifier(); + let constraint: TypeNode | undefined; + let expression: Expression | undefined; + if (parseOptional(SyntaxKind.ExtendsKeyword)) { + // It's not uncommon for people to write improper constraints to a generic. If the + // user writes a constraint that is an expression and not an actual type, then parse + // it out as an expression (so we can recover well), but report that a type is needed + // instead. + if (isStartOfType() || !isStartOfExpression()) { + constraint = parseType(); + } + else { + // It was not a type, and it looked like an expression. Parse out an expression + // here so we recover well. Note: it is important that we call parseUnaryExpression + // and not parseExpression here. If the user has: + // + // + // + // We do *not* want to consume the `>` as we're consuming the expression for "". + expression = parseUnaryExpressionOrHigher(); } - return name; } - function isParameterNameStart() { - // Be permissive about await and yield by calling isBindingIdentifier instead of isIdentifier; disallowing - // them during a speculative parse leads to many more follow-on errors than allowing the function to parse then later - // complaining about the use of the keywords. - return isBindingIdentifier() || token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.OpenBraceToken; - } + const defaultType = parseOptional(SyntaxKind.EqualsToken) ? parseType() : undefined; + const node = factory.createTypeParameterDeclaration(modifiers, name, constraint, defaultType); + node.expression = expression; + return finishNode(node, pos); + } - function parseParameter(inOuterAwaitContext: boolean): ParameterDeclaration { - return parseParameterWorker(inOuterAwaitContext); + function parseTypeParameters(): NodeArray | undefined { + if (token() === SyntaxKind.LessThanToken) { + return parseBracketedList(ParsingContext.TypeParameters, parseTypeParameter, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } + } - function parseParameterForSpeculation(inOuterAwaitContext: boolean): ParameterDeclaration | undefined { - return parseParameterWorker(inOuterAwaitContext, /*allowAmbiguity*/ false); - } + function isStartOfParameter(isJSDocParameter: boolean): boolean { + return token() === SyntaxKind.DotDotDotToken || + isBindingIdentifierOrPrivateIdentifierOrPattern() || + isModifierKind(token()) || + token() === SyntaxKind.AtToken || + isStartOfType(/*inStartOfParameter*/ !isJSDocParameter); + } - function parseParameterWorker(inOuterAwaitContext: boolean): ParameterDeclaration; - function parseParameterWorker(inOuterAwaitContext: boolean, allowAmbiguity: false): ParameterDeclaration | undefined; - function parseParameterWorker(inOuterAwaitContext: boolean, allowAmbiguity = true): ParameterDeclaration | undefined { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); + function parseNameOfParameter(modifiers: NodeArray | undefined) { + // FormalParameter [Yield,Await]: + // BindingElement[?Yield,?Await] + const name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_cannot_be_used_as_parameters); + if (getFullWidth(name) === 0 && !some(modifiers) && isModifierKind(token())) { + // in cases like + // 'use strict' + // function foo(static) + // isParameter('static') === true, because of isModifier('static') + // however 'static' is not a legal identifier in a strict mode. + // so result of this function will be ParameterDeclaration (flags = 0, name = missing, type = undefined, initializer = undefined) + // and current token will not change => parsing of the enclosing parameter list will last till the end of time (or OOM) + // to avoid this we'll advance cursor to the next token. + nextToken(); + } + return name; + } - // FormalParameter [Yield,Await]: - // BindingElement[?Yield,?Await] + function isParameterNameStart() { + // Be permissive about await and yield by calling isBindingIdentifier instead of isIdentifier; disallowing + // them during a speculative parse leads to many more follow-on errors than allowing the function to parse then later + // complaining about the use of the keywords. + return isBindingIdentifier() || token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.OpenBraceToken; + } - // Decorators are parsed in the outer [Await] context, the rest of the parameter is parsed in the function's [Await] context. - const decorators = inOuterAwaitContext ? doInAwaitContext(parseDecorators) : doOutsideOfAwaitContext(parseDecorators); + function parseParameter(inOuterAwaitContext: boolean): ParameterDeclaration { + return parseParameterWorker(inOuterAwaitContext); + } - if (token() === SyntaxKind.ThisKeyword) { - const node = factory.createParameterDeclaration( - decorators, - /*dotDotDotToken*/ undefined, - createIdentifier(/*isIdentifier*/ true), - /*questionToken*/ undefined, - parseTypeAnnotation(), - /*initializer*/ undefined - ); + function parseParameterForSpeculation(inOuterAwaitContext: boolean): ParameterDeclaration | undefined { + return parseParameterWorker(inOuterAwaitContext, /*allowAmbiguity*/ false); + } - if (decorators) { - parseErrorAtRange(decorators[0], Diagnostics.Decorators_may_not_be_applied_to_this_parameters); - } + function parseParameterWorker(inOuterAwaitContext: boolean): ParameterDeclaration; + function parseParameterWorker(inOuterAwaitContext: boolean, allowAmbiguity: false): ParameterDeclaration | undefined; + function parseParameterWorker(inOuterAwaitContext: boolean, allowAmbiguity = true): ParameterDeclaration | undefined { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); - return withJSDoc(finishNode(node, pos), hasJSDoc); - } + // FormalParameter [Yield,Await]: + // BindingElement[?Yield,?Await] - const savedTopLevel = topLevel; - topLevel = false; + // Decorators are parsed in the outer [Await] context, the rest of the parameter is parsed in the function's [Await] context. + const decorators = inOuterAwaitContext ? doInAwaitContext(parseDecorators) : doOutsideOfAwaitContext(parseDecorators); - const modifiers = combineDecoratorsAndModifiers(decorators, parseModifiers()); - const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + if (token() === SyntaxKind.ThisKeyword) { + const node = factory.createParameterDeclaration( + decorators, + /*dotDotDotToken*/ undefined, + createIdentifier(/*isIdentifier*/ true), + /*questionToken*/ undefined, + parseTypeAnnotation(), + /*initializer*/ undefined + ); - if (!allowAmbiguity && !isParameterNameStart()) { - return undefined; + if (decorators) { + parseErrorAtRange(decorators[0], Diagnostics.Decorators_may_not_be_applied_to_this_parameters); } - const node = withJSDoc( - finishNode( - factory.createParameterDeclaration( - modifiers, - dotDotDotToken, - parseNameOfParameter(modifiers), - parseOptionalToken(SyntaxKind.QuestionToken), - parseTypeAnnotation(), - parseInitializer() - ), - pos - ), - hasJSDoc - ); - topLevel = savedTopLevel; - return node; + return withJSDoc(finishNode(node, pos), hasJSDoc); } - function parseReturnType(returnToken: SyntaxKind.EqualsGreaterThanToken, isType: boolean): TypeNode; - function parseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): TypeNode | undefined; - function parseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean) { - if (shouldParseReturnType(returnToken, isType)) { - return allowConditionalTypesAnd(parseTypeOrTypePredicate); - } - } + const savedTopLevel = topLevel; + topLevel = false; - function shouldParseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): boolean { - if (returnToken === SyntaxKind.EqualsGreaterThanToken) { - parseExpected(returnToken); - return true; - } - else if (parseOptional(SyntaxKind.ColonToken)) { - return true; - } - else if (isType && token() === SyntaxKind.EqualsGreaterThanToken) { - // This is easy to get backward, especially in type contexts, so parse the type anyway - parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)); - nextToken(); - return true; - } - return false; + const modifiers = combineDecoratorsAndModifiers(decorators, parseModifiers()); + const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + + if (!allowAmbiguity && !isParameterNameStart()) { + return undefined; } - function parseParametersWorker(flags: SignatureFlags, allowAmbiguity: true): NodeArray; - function parseParametersWorker(flags: SignatureFlags, allowAmbiguity: false): NodeArray | undefined; - function parseParametersWorker(flags: SignatureFlags, allowAmbiguity: boolean): NodeArray | undefined { - // FormalParameters [Yield,Await]: (modified) - // [empty] - // FormalParameterList[?Yield,Await] - // - // FormalParameter[Yield,Await]: (modified) - // BindingElement[?Yield,Await] - // - // BindingElement [Yield,Await]: (modified) - // SingleNameBinding[?Yield,?Await] - // BindingPattern[?Yield,?Await]Initializer [In, ?Yield,?Await] opt - // - // SingleNameBinding [Yield,Await]: - // BindingIdentifier[?Yield,?Await]Initializer [In, ?Yield,?Await] opt - const savedYieldContext = inYieldContext(); - const savedAwaitContext = inAwaitContext(); + const node = withJSDoc( + finishNode( + factory.createParameterDeclaration( + modifiers, + dotDotDotToken, + parseNameOfParameter(modifiers), + parseOptionalToken(SyntaxKind.QuestionToken), + parseTypeAnnotation(), + parseInitializer() + ), + pos + ), + hasJSDoc + ); + topLevel = savedTopLevel; + return node; + } - setYieldContext(!!(flags & SignatureFlags.Yield)); - setAwaitContext(!!(flags & SignatureFlags.Await)); + function parseReturnType(returnToken: SyntaxKind.EqualsGreaterThanToken, isType: boolean): TypeNode; + function parseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): TypeNode | undefined; + function parseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean) { + if (shouldParseReturnType(returnToken, isType)) { + return allowConditionalTypesAnd(parseTypeOrTypePredicate); + } + } - const parameters = flags & SignatureFlags.JSDoc ? - parseDelimitedList(ParsingContext.JSDocParameters, parseJSDocParameter) : - parseDelimitedList(ParsingContext.Parameters, () => allowAmbiguity ? parseParameter(savedAwaitContext) : parseParameterForSpeculation(savedAwaitContext)); + function shouldParseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): boolean { + if (returnToken === SyntaxKind.EqualsGreaterThanToken) { + parseExpected(returnToken); + return true; + } + else if (parseOptional(SyntaxKind.ColonToken)) { + return true; + } + else if (isType && token() === SyntaxKind.EqualsGreaterThanToken) { + // This is easy to get backward, especially in type contexts, so parse the type anyway + parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)); + nextToken(); + return true; + } + return false; + } - setYieldContext(savedYieldContext); - setAwaitContext(savedAwaitContext); + function parseParametersWorker(flags: SignatureFlags, allowAmbiguity: true): NodeArray; + function parseParametersWorker(flags: SignatureFlags, allowAmbiguity: false): NodeArray | undefined; + function parseParametersWorker(flags: SignatureFlags, allowAmbiguity: boolean): NodeArray | undefined { + // FormalParameters [Yield,Await]: (modified) + // [empty] + // FormalParameterList[?Yield,Await] + // + // FormalParameter[Yield,Await]: (modified) + // BindingElement[?Yield,Await] + // + // BindingElement [Yield,Await]: (modified) + // SingleNameBinding[?Yield,?Await] + // BindingPattern[?Yield,?Await]Initializer [In, ?Yield,?Await] opt + // + // SingleNameBinding [Yield,Await]: + // BindingIdentifier[?Yield,?Await]Initializer [In, ?Yield,?Await] opt + const savedYieldContext = inYieldContext(); + const savedAwaitContext = inAwaitContext(); - return parameters; - } + setYieldContext(!!(flags & SignatureFlags.Yield)); + setAwaitContext(!!(flags & SignatureFlags.Await)); - function parseParameters(flags: SignatureFlags): NodeArray { - // FormalParameters [Yield,Await]: (modified) - // [empty] - // FormalParameterList[?Yield,Await] - // - // FormalParameter[Yield,Await]: (modified) - // BindingElement[?Yield,Await] - // - // BindingElement [Yield,Await]: (modified) - // SingleNameBinding[?Yield,?Await] - // BindingPattern[?Yield,?Await]Initializer [In, ?Yield,?Await] opt - // - // SingleNameBinding [Yield,Await]: - // BindingIdentifier[?Yield,?Await]Initializer [In, ?Yield,?Await] opt - if (!parseExpected(SyntaxKind.OpenParenToken)) { - return createMissingList(); - } + const parameters = flags & SignatureFlags.JSDoc ? + parseDelimitedList(ParsingContext.JSDocParameters, parseJSDocParameter) : + parseDelimitedList(ParsingContext.Parameters, () => allowAmbiguity ? parseParameter(savedAwaitContext) : parseParameterForSpeculation(savedAwaitContext)); - const parameters = parseParametersWorker(flags, /*allowAmbiguity*/ true); - parseExpected(SyntaxKind.CloseParenToken); - return parameters; - } + setYieldContext(savedYieldContext); + setAwaitContext(savedAwaitContext); - function parseTypeMemberSemicolon() { - // We allow type members to be separated by commas or (possibly ASI) semicolons. - // First check if it was a comma. If so, we're done with the member. - if (parseOptional(SyntaxKind.CommaToken)) { - return; - } + return parameters; + } - // Didn't have a comma. We must have a (possible ASI) semicolon. - parseSemicolon(); + function parseParameters(flags: SignatureFlags): NodeArray { + // FormalParameters [Yield,Await]: (modified) + // [empty] + // FormalParameterList[?Yield,Await] + // + // FormalParameter[Yield,Await]: (modified) + // BindingElement[?Yield,Await] + // + // BindingElement [Yield,Await]: (modified) + // SingleNameBinding[?Yield,?Await] + // BindingPattern[?Yield,?Await]Initializer [In, ?Yield,?Await] opt + // + // SingleNameBinding [Yield,Await]: + // BindingIdentifier[?Yield,?Await]Initializer [In, ?Yield,?Await] opt + if (!parseExpected(SyntaxKind.OpenParenToken)) { + return createMissingList(); } - function parseSignatureMember(kind: SyntaxKind.CallSignature | SyntaxKind.ConstructSignature): CallSignatureDeclaration | ConstructSignatureDeclaration { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - if (kind === SyntaxKind.ConstructSignature) { - parseExpected(SyntaxKind.NewKeyword); - } + const parameters = parseParametersWorker(flags, /*allowAmbiguity*/ true); + parseExpected(SyntaxKind.CloseParenToken); + return parameters; + } - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(SignatureFlags.Type); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ true); - parseTypeMemberSemicolon(); - const node = kind === SyntaxKind.CallSignature - ? factory.createCallSignature(typeParameters, parameters, type) - : factory.createConstructSignature(typeParameters, parameters, type); - return withJSDoc(finishNode(node, pos), hasJSDoc); + function parseTypeMemberSemicolon() { + // We allow type members to be separated by commas or (possibly ASI) semicolons. + // First check if it was a comma. If so, we're done with the member. + if (parseOptional(SyntaxKind.CommaToken)) { + return; } - function isIndexSignature(): boolean { - return token() === SyntaxKind.OpenBracketToken && lookAhead(isUnambiguouslyIndexSignature); - } + // Didn't have a comma. We must have a (possible ASI) semicolon. + parseSemicolon(); + } - function isUnambiguouslyIndexSignature() { - // The only allowed sequence is: - // - // [id: - // - // However, for error recovery, we also check the following cases: - // - // [... - // [id, - // [id?, - // [id?: - // [id?] - // [public id - // [private id - // [protected id - // [] - // - nextToken(); - if (token() === SyntaxKind.DotDotDotToken || token() === SyntaxKind.CloseBracketToken) { - return true; - } + function parseSignatureMember(kind: SyntaxKind.CallSignature | SyntaxKind.ConstructSignature): CallSignatureDeclaration | ConstructSignatureDeclaration { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + if (kind === SyntaxKind.ConstructSignature) { + parseExpected(SyntaxKind.NewKeyword); + } - if (isModifierKind(token())) { - nextToken(); - if (isIdentifier()) { - return true; - } - } - else if (!isIdentifier()) { - return false; - } - else { - // Skip the identifier - nextToken(); - } + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(SignatureFlags.Type); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ true); + parseTypeMemberSemicolon(); + const node = kind === SyntaxKind.CallSignature + ? factory.createCallSignature(typeParameters, parameters, type) + : factory.createConstructSignature(typeParameters, parameters, type); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - // A colon signifies a well formed indexer - // A comma should be a badly formed indexer because comma expressions are not allowed - // in computed properties. - if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken) { - return true; - } + function isIndexSignature(): boolean { + return token() === SyntaxKind.OpenBracketToken && lookAhead(isUnambiguouslyIndexSignature); + } - // Question mark could be an indexer with an optional property, - // or it could be a conditional expression in a computed property. - if (token() !== SyntaxKind.QuestionToken) { - return false; - } + function isUnambiguouslyIndexSignature() { + // The only allowed sequence is: + // + // [id: + // + // However, for error recovery, we also check the following cases: + // + // [... + // [id, + // [id?, + // [id?: + // [id?] + // [public id + // [private id + // [protected id + // [] + // + nextToken(); + if (token() === SyntaxKind.DotDotDotToken || token() === SyntaxKind.CloseBracketToken) { + return true; + } - // If any of the following tokens are after the question mark, it cannot - // be a conditional expression, so treat it as an indexer. + if (isModifierKind(token())) { nextToken(); - return token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || token() === SyntaxKind.CloseBracketToken; + if (isIdentifier()) { + return true; + } } - - function parseIndexSignatureDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): IndexSignatureDeclaration { - const parameters = parseBracketedList(ParsingContext.Parameters, () => parseParameter(/*inOuterAwaitContext*/ false), SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); - const type = parseTypeAnnotation(); - parseTypeMemberSemicolon(); - const node = factory.createIndexSignature(modifiers, parameters, type); - (node as Mutable).illegalDecorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); + else if (!isIdentifier()) { + return false; + } + else { + // Skip the identifier + nextToken(); } - function parsePropertyOrMethodSignature(pos: number, hasJSDoc: boolean, modifiers: NodeArray | undefined): PropertySignature | MethodSignature { - const name = parsePropertyName(); - const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - let node: PropertySignature | MethodSignature; - if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - // Method signatures don't exist in expression contexts. So they have neither - // [Yield] nor [Await] - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(SignatureFlags.Type); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ true); - node = factory.createMethodSignature(modifiers, name, questionToken, typeParameters, parameters, type); - } - else { - const type = parseTypeAnnotation(); - node = factory.createPropertySignature(modifiers, name, questionToken, type); - // Although type literal properties cannot not have initializers, we attempt - // to parse an initializer so we can report in the checker that an interface - // property or type literal property cannot have an initializer. - if (token() === SyntaxKind.EqualsToken) (node as Mutable).initializer = parseInitializer(); - } - parseTypeMemberSemicolon(); - return withJSDoc(finishNode(node, pos), hasJSDoc); + // A colon signifies a well formed indexer + // A comma should be a badly formed indexer because comma expressions are not allowed + // in computed properties. + if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken) { + return true; } - function isTypeMemberStart(): boolean { - // Return true if we have the start of a signature member - if (token() === SyntaxKind.OpenParenToken || - token() === SyntaxKind.LessThanToken || - token() === SyntaxKind.GetKeyword || - token() === SyntaxKind.SetKeyword) { - return true; - } - let idToken = false; - // Eat up all modifiers, but hold on to the last one in case it is actually an identifier - while (isModifierKind(token())) { - idToken = true; - nextToken(); - } - // Index signatures and computed property names are type members - if (token() === SyntaxKind.OpenBracketToken) { - return true; - } - // Try to get the first property-like token following all modifiers - if (isLiteralPropertyName()) { - idToken = true; - nextToken(); - } - // If we were able to get any potential identifier, check that it is - // the start of a member declaration - if (idToken) { - return token() === SyntaxKind.OpenParenToken || - token() === SyntaxKind.LessThanToken || - token() === SyntaxKind.QuestionToken || - token() === SyntaxKind.ColonToken || - token() === SyntaxKind.CommaToken || - canParseSemicolon(); - } + // Question mark could be an indexer with an optional property, + // or it could be a conditional expression in a computed property. + if (token() !== SyntaxKind.QuestionToken) { return false; } - function parseTypeMember(): TypeElement { - if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - return parseSignatureMember(SyntaxKind.CallSignature); - } - if (token() === SyntaxKind.NewKeyword && lookAhead(nextTokenIsOpenParenOrLessThan)) { - return parseSignatureMember(SyntaxKind.ConstructSignature); - } - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const modifiers = parseModifiers(); - if (parseContextualModifier(SyntaxKind.GetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers, SyntaxKind.GetAccessor, SignatureFlags.Type); - } + // If any of the following tokens are after the question mark, it cannot + // be a conditional expression, so treat it as an indexer. + nextToken(); + return token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || token() === SyntaxKind.CloseBracketToken; + } - if (parseContextualModifier(SyntaxKind.SetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers, SyntaxKind.SetAccessor, SignatureFlags.Type); - } + function parseIndexSignatureDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): IndexSignatureDeclaration { + const parameters = parseBracketedList(ParsingContext.Parameters, () => parseParameter(/*inOuterAwaitContext*/ false), SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); + const type = parseTypeAnnotation(); + parseTypeMemberSemicolon(); + const node = factory.createIndexSignature(modifiers, parameters, type); + (node as Mutable).illegalDecorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - if (isIndexSignature()) { - return parseIndexSignatureDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers); - } - return parsePropertyOrMethodSignature(pos, hasJSDoc, modifiers); + function parsePropertyOrMethodSignature(pos: number, hasJSDoc: boolean, modifiers: NodeArray | undefined): PropertySignature | MethodSignature { + const name = parsePropertyName(); + const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + let node: PropertySignature | MethodSignature; + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + // Method signatures don't exist in expression contexts. So they have neither + // [Yield] nor [Await] + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(SignatureFlags.Type); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ true); + node = factory.createMethodSignature(modifiers, name, questionToken, typeParameters, parameters, type); } + else { + const type = parseTypeAnnotation(); + node = factory.createPropertySignature(modifiers, name, questionToken, type); + // Although type literal properties cannot not have initializers, we attempt + // to parse an initializer so we can report in the checker that an interface + // property or type literal property cannot have an initializer. + if (token() === SyntaxKind.EqualsToken) (node as Mutable).initializer = parseInitializer(); + } + parseTypeMemberSemicolon(); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function nextTokenIsOpenParenOrLessThan() { + function isTypeMemberStart(): boolean { + // Return true if we have the start of a signature member + if (token() === SyntaxKind.OpenParenToken || + token() === SyntaxKind.LessThanToken || + token() === SyntaxKind.GetKeyword || + token() === SyntaxKind.SetKeyword) { + return true; + } + let idToken = false; + // Eat up all modifiers, but hold on to the last one in case it is actually an identifier + while (isModifierKind(token())) { + idToken = true; + nextToken(); + } + // Index signatures and computed property names are type members + if (token() === SyntaxKind.OpenBracketToken) { + return true; + } + // Try to get the first property-like token following all modifiers + if (isLiteralPropertyName()) { + idToken = true; nextToken(); - return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken; } + // If we were able to get any potential identifier, check that it is + // the start of a member declaration + if (idToken) { + return token() === SyntaxKind.OpenParenToken || + token() === SyntaxKind.LessThanToken || + token() === SyntaxKind.QuestionToken || + token() === SyntaxKind.ColonToken || + token() === SyntaxKind.CommaToken || + canParseSemicolon(); + } + return false; + } - function nextTokenIsDot() { - return nextToken() === SyntaxKind.DotToken; + function parseTypeMember(): TypeElement { + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + return parseSignatureMember(SyntaxKind.CallSignature); + } + if (token() === SyntaxKind.NewKeyword && lookAhead(nextTokenIsOpenParenOrLessThan)) { + return parseSignatureMember(SyntaxKind.ConstructSignature); + } + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const modifiers = parseModifiers(); + if (parseContextualModifier(SyntaxKind.GetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers, SyntaxKind.GetAccessor, SignatureFlags.Type); } - function nextTokenIsOpenParenOrLessThanOrDot() { - switch (nextToken()) { - case SyntaxKind.OpenParenToken: - case SyntaxKind.LessThanToken: - case SyntaxKind.DotToken: - return true; - } - return false; + if (parseContextualModifier(SyntaxKind.SetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers, SyntaxKind.SetAccessor, SignatureFlags.Type); } - function parseTypeLiteral(): TypeLiteralNode { - const pos = getNodePos(); - return finishNode(factory.createTypeLiteralNode(parseObjectTypeMembers()), pos); + if (isIndexSignature()) { + return parseIndexSignatureDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers); } + return parsePropertyOrMethodSignature(pos, hasJSDoc, modifiers); + } - function parseObjectTypeMembers(): NodeArray { - let members: NodeArray; - if (parseExpected(SyntaxKind.OpenBraceToken)) { - members = parseList(ParsingContext.TypeMembers, parseTypeMember); - parseExpected(SyntaxKind.CloseBraceToken); - } - else { - members = createMissingList(); - } + function nextTokenIsOpenParenOrLessThan() { + nextToken(); + return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken; + } - return members; - } + function nextTokenIsDot() { + return nextToken() === SyntaxKind.DotToken; + } - function isStartOfMappedType() { - nextToken(); - if (token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { - return nextToken() === SyntaxKind.ReadonlyKeyword; - } - if (token() === SyntaxKind.ReadonlyKeyword) { - nextToken(); - } - return token() === SyntaxKind.OpenBracketToken && nextTokenIsIdentifier() && nextToken() === SyntaxKind.InKeyword; + function nextTokenIsOpenParenOrLessThanOrDot() { + switch (nextToken()) { + case SyntaxKind.OpenParenToken: + case SyntaxKind.LessThanToken: + case SyntaxKind.DotToken: + return true; } + return false; + } - function parseMappedTypeParameter() { - const pos = getNodePos(); - const name = parseIdentifierName(); - parseExpected(SyntaxKind.InKeyword); - const type = parseType(); - return finishNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, name, type, /*defaultType*/ undefined), pos); - } + function parseTypeLiteral(): TypeLiteralNode { + const pos = getNodePos(); + return finishNode(factory.createTypeLiteralNode(parseObjectTypeMembers()), pos); + } - function parseMappedType() { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBraceToken); - let readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined; - if (token() === SyntaxKind.ReadonlyKeyword || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { - readonlyToken = parseTokenNode(); - if (readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { - parseExpected(SyntaxKind.ReadonlyKeyword); - } - } - parseExpected(SyntaxKind.OpenBracketToken); - const typeParameter = parseMappedTypeParameter(); - const nameType = parseOptional(SyntaxKind.AsKeyword) ? parseType() : undefined; - parseExpected(SyntaxKind.CloseBracketToken); - let questionToken: QuestionToken | PlusToken | MinusToken | undefined; - if (token() === SyntaxKind.QuestionToken || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { - questionToken = parseTokenNode(); - if (questionToken.kind !== SyntaxKind.QuestionToken) { - parseExpected(SyntaxKind.QuestionToken); - } - } - const type = parseTypeAnnotation(); - parseSemicolon(); - const members = parseList(ParsingContext.TypeMembers, parseTypeMember); + function parseObjectTypeMembers(): NodeArray { + let members: NodeArray; + if (parseExpected(SyntaxKind.OpenBraceToken)) { + members = parseList(ParsingContext.TypeMembers, parseTypeMember); parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), pos); } - - function parseTupleElementType() { - const pos = getNodePos(); - if (parseOptional(SyntaxKind.DotDotDotToken)) { - return finishNode(factory.createRestTypeNode(parseType()), pos); - } - const type = parseType(); - if (isJSDocNullableType(type) && type.pos === type.type.pos) { - const node = factory.createOptionalTypeNode(type.type); - setTextRange(node, type); - (node as Mutable).flags = type.flags; - return node; - } - return type; + else { + members = createMissingList(); } - function isNextTokenColonOrQuestionColon() { - return nextToken() === SyntaxKind.ColonToken || (token() === SyntaxKind.QuestionToken && nextToken() === SyntaxKind.ColonToken); - } + return members; + } - function isTupleElementName() { - if (token() === SyntaxKind.DotDotDotToken) { - return tokenIsIdentifierOrKeyword(nextToken()) && isNextTokenColonOrQuestionColon(); - } - return tokenIsIdentifierOrKeyword(token()) && isNextTokenColonOrQuestionColon(); + function isStartOfMappedType() { + nextToken(); + if (token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { + return nextToken() === SyntaxKind.ReadonlyKeyword; + } + if (token() === SyntaxKind.ReadonlyKeyword) { + nextToken(); } + return token() === SyntaxKind.OpenBracketToken && nextTokenIsIdentifier() && nextToken() === SyntaxKind.InKeyword; + } - function parseTupleElementNameOrTupleElementType() { - if (lookAhead(isTupleElementName)) { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - const name = parseIdentifierName(); - const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - parseExpected(SyntaxKind.ColonToken); - const type = parseTupleElementType(); - const node = factory.createNamedTupleMember(dotDotDotToken, name, questionToken, type); - return withJSDoc(finishNode(node, pos), hasJSDoc); + function parseMappedTypeParameter() { + const pos = getNodePos(); + const name = parseIdentifierName(); + parseExpected(SyntaxKind.InKeyword); + const type = parseType(); + return finishNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, name, type, /*defaultType*/ undefined), pos); + } + + function parseMappedType() { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBraceToken); + let readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined; + if (token() === SyntaxKind.ReadonlyKeyword || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { + readonlyToken = parseTokenNode(); + if (readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { + parseExpected(SyntaxKind.ReadonlyKeyword); + } + } + parseExpected(SyntaxKind.OpenBracketToken); + const typeParameter = parseMappedTypeParameter(); + const nameType = parseOptional(SyntaxKind.AsKeyword) ? parseType() : undefined; + parseExpected(SyntaxKind.CloseBracketToken); + let questionToken: QuestionToken | PlusToken | MinusToken | undefined; + if (token() === SyntaxKind.QuestionToken || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { + questionToken = parseTokenNode(); + if (questionToken.kind !== SyntaxKind.QuestionToken) { + parseExpected(SyntaxKind.QuestionToken); } - return parseTupleElementType(); } + const type = parseTypeAnnotation(); + parseSemicolon(); + const members = parseList(ParsingContext.TypeMembers, parseTypeMember); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), pos); + } - function parseTupleType(): TupleTypeNode { - const pos = getNodePos(); - return finishNode( - factory.createTupleTypeNode( - parseBracketedList(ParsingContext.TupleElementTypes, parseTupleElementNameOrTupleElementType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken) - ), - pos - ); + function parseTupleElementType() { + const pos = getNodePos(); + if (parseOptional(SyntaxKind.DotDotDotToken)) { + return finishNode(factory.createRestTypeNode(parseType()), pos); } - - function parseParenthesizedType(): TypeNode { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenParenToken); - const type = parseType(); - parseExpected(SyntaxKind.CloseParenToken); - return finishNode(factory.createParenthesizedType(type), pos); + const type = parseType(); + if (isJSDocNullableType(type) && type.pos === type.type.pos) { + const node = factory.createOptionalTypeNode(type.type); + setTextRange(node, type); + (node as Mutable).flags = type.flags; + return node; } + return type; + } - function parseModifiersForConstructorType(): NodeArray | undefined { - let modifiers: NodeArray | undefined; - if (token() === SyntaxKind.AbstractKeyword) { - const pos = getNodePos(); - nextToken(); - const modifier = finishNode(factory.createToken(SyntaxKind.AbstractKeyword), pos); - modifiers = createNodeArray([modifier], pos); - } - return modifiers; + function isNextTokenColonOrQuestionColon() { + return nextToken() === SyntaxKind.ColonToken || (token() === SyntaxKind.QuestionToken && nextToken() === SyntaxKind.ColonToken); + } + + function isTupleElementName() { + if (token() === SyntaxKind.DotDotDotToken) { + return tokenIsIdentifierOrKeyword(nextToken()) && isNextTokenColonOrQuestionColon(); } + return tokenIsIdentifierOrKeyword(token()) && isNextTokenColonOrQuestionColon(); + } - function parseFunctionOrConstructorType(): TypeNode { + function parseTupleElementNameOrTupleElementType() { + if (lookAhead(isTupleElementName)) { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); - const modifiers = parseModifiersForConstructorType(); - const isConstructorType = parseOptional(SyntaxKind.NewKeyword); - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(SignatureFlags.Type); - const type = parseReturnType(SyntaxKind.EqualsGreaterThanToken, /*isType*/ false); - const node = isConstructorType - ? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, type) - : factory.createFunctionTypeNode(typeParameters, parameters, type); - if (!isConstructorType) (node as Mutable).modifiers = modifiers; + const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + const name = parseIdentifierName(); + const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + parseExpected(SyntaxKind.ColonToken); + const type = parseTupleElementType(); + const node = factory.createNamedTupleMember(dotDotDotToken, name, questionToken, type); return withJSDoc(finishNode(node, pos), hasJSDoc); } + return parseTupleElementType(); + } - function parseKeywordAndNoDot(): TypeNode | undefined { - const node = parseTokenNode(); - return token() === SyntaxKind.DotToken ? undefined : node; - } + function parseTupleType(): TupleTypeNode { + const pos = getNodePos(); + return finishNode( + factory.createTupleTypeNode( + parseBracketedList(ParsingContext.TupleElementTypes, parseTupleElementNameOrTupleElementType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken) + ), + pos + ); + } + + function parseParenthesizedType(): TypeNode { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenParenToken); + const type = parseType(); + parseExpected(SyntaxKind.CloseParenToken); + return finishNode(factory.createParenthesizedType(type), pos); + } - function parseLiteralTypeNode(negative?: boolean): LiteralTypeNode { + function parseModifiersForConstructorType(): NodeArray | undefined { + let modifiers: NodeArray | undefined; + if (token() === SyntaxKind.AbstractKeyword) { const pos = getNodePos(); - if (negative) { - nextToken(); - } - let expression: BooleanLiteral | NullLiteral | LiteralExpression | PrefixUnaryExpression = - token() === SyntaxKind.TrueKeyword || token() === SyntaxKind.FalseKeyword || token() === SyntaxKind.NullKeyword ? - parseTokenNode() : - parseLiteralLikeNode(token()) as LiteralExpression; - if (negative) { - expression = finishNode(factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, expression), pos); - } - return finishNode(factory.createLiteralTypeNode(expression), pos); + nextToken(); + const modifier = finishNode(factory.createToken(SyntaxKind.AbstractKeyword), pos); + modifiers = createNodeArray([modifier], pos); } + return modifiers; + } + + function parseFunctionOrConstructorType(): TypeNode { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const modifiers = parseModifiersForConstructorType(); + const isConstructorType = parseOptional(SyntaxKind.NewKeyword); + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(SignatureFlags.Type); + const type = parseReturnType(SyntaxKind.EqualsGreaterThanToken, /*isType*/ false); + const node = isConstructorType + ? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, type) + : factory.createFunctionTypeNode(typeParameters, parameters, type); + if (!isConstructorType) (node as Mutable).modifiers = modifiers; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } + + function parseKeywordAndNoDot(): TypeNode | undefined { + const node = parseTokenNode(); + return token() === SyntaxKind.DotToken ? undefined : node; + } - function isStartOfTypeOfImportType() { + function parseLiteralTypeNode(negative?: boolean): LiteralTypeNode { + const pos = getNodePos(); + if (negative) { nextToken(); - return token() === SyntaxKind.ImportKeyword; } + let expression: BooleanLiteral | NullLiteral | LiteralExpression | PrefixUnaryExpression = + token() === SyntaxKind.TrueKeyword || token() === SyntaxKind.FalseKeyword || token() === SyntaxKind.NullKeyword ? + parseTokenNode() : + parseLiteralLikeNode(token()) as LiteralExpression; + if (negative) { + expression = finishNode(factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, expression), pos); + } + return finishNode(factory.createLiteralTypeNode(expression), pos); + } - function parseImportTypeAssertions(): ImportTypeAssertionContainer { - const pos = getNodePos(); - const openBracePosition = scanner.getTokenPos(); - parseExpected(SyntaxKind.OpenBraceToken); - const multiLine = scanner.hasPrecedingLineBreak(); - parseExpected(SyntaxKind.AssertKeyword); - parseExpected(SyntaxKind.ColonToken); - const clause = parseAssertClause(/*skipAssertKeyword*/ true); - if (!parseExpected(SyntaxKind.CloseBraceToken)) { - const lastError = lastOrUndefined(parseDiagnostics); - if (lastError && lastError.code === Diagnostics._0_expected.code) { - addRelatedInfo( - lastError, - createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_1_to_match_the_0_token_here, "{", "}") - ); - } + function isStartOfTypeOfImportType() { + nextToken(); + return token() === SyntaxKind.ImportKeyword; + } + + function parseImportTypeAssertions(): ImportTypeAssertionContainer { + const pos = getNodePos(); + const openBracePosition = scanner.getTokenPos(); + parseExpected(SyntaxKind.OpenBraceToken); + const multiLine = scanner.hasPrecedingLineBreak(); + parseExpected(SyntaxKind.AssertKeyword); + parseExpected(SyntaxKind.ColonToken); + const clause = parseAssertClause(/*skipAssertKeyword*/ true); + if (!parseExpected(SyntaxKind.CloseBraceToken)) { + const lastError = lastOrUndefined(parseDiagnostics); + if (lastError && lastError.code === Diagnostics._0_expected.code) { + addRelatedInfo( + lastError, + createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_1_to_match_the_0_token_here, "{", "}") + ); } - return finishNode(factory.createImportTypeAssertionContainer(clause, multiLine), pos); } + return finishNode(factory.createImportTypeAssertionContainer(clause, multiLine), pos); + } - function parseImportType(): ImportTypeNode { - sourceFlags |= NodeFlags.PossiblyContainsDynamicImport; - const pos = getNodePos(); - const isTypeOf = parseOptional(SyntaxKind.TypeOfKeyword); - parseExpected(SyntaxKind.ImportKeyword); - parseExpected(SyntaxKind.OpenParenToken); - const type = parseType(); - let assertions: ImportTypeAssertionContainer | undefined; - if (parseOptional(SyntaxKind.CommaToken)) { - assertions = parseImportTypeAssertions(); - } - parseExpected(SyntaxKind.CloseParenToken); - const qualifier = parseOptional(SyntaxKind.DotToken) ? parseEntityNameOfTypeReference() : undefined; - const typeArguments = parseTypeArgumentsOfTypeReference(); - return finishNode(factory.createImportTypeNode(type, assertions, qualifier, typeArguments, isTypeOf), pos); + function parseImportType(): ImportTypeNode { + sourceFlags |= NodeFlags.PossiblyContainsDynamicImport; + const pos = getNodePos(); + const isTypeOf = parseOptional(SyntaxKind.TypeOfKeyword); + parseExpected(SyntaxKind.ImportKeyword); + parseExpected(SyntaxKind.OpenParenToken); + const type = parseType(); + let assertions: ImportTypeAssertionContainer | undefined; + if (parseOptional(SyntaxKind.CommaToken)) { + assertions = parseImportTypeAssertions(); + } + parseExpected(SyntaxKind.CloseParenToken); + const qualifier = parseOptional(SyntaxKind.DotToken) ? parseEntityNameOfTypeReference() : undefined; + const typeArguments = parseTypeArgumentsOfTypeReference(); + return finishNode(factory.createImportTypeNode(type, assertions, qualifier, typeArguments, isTypeOf), pos); + } + + function nextTokenIsNumericOrBigIntLiteral() { + nextToken(); + return token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral; + } + + function parseNonArrayType(): TypeNode { + switch (token()) { + case SyntaxKind.AnyKeyword: + case SyntaxKind.UnknownKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.ObjectKeyword: + // If these are followed by a dot, then parse these out as a dotted type reference instead. + return tryParse(parseKeywordAndNoDot) || parseTypeReference(); + case SyntaxKind.AsteriskEqualsToken: + // If there is '*=', treat it as * followed by postfix = + scanner.reScanAsteriskEqualsToken(); + // falls through + case SyntaxKind.AsteriskToken: + return parseJSDocAllType(); + case SyntaxKind.QuestionQuestionToken: + // If there is '??', treat it as prefix-'?' in JSDoc type. + scanner.reScanQuestionToken(); + // falls through + case SyntaxKind.QuestionToken: + return parseJSDocUnknownOrNullableType(); + case SyntaxKind.FunctionKeyword: + return parseJSDocFunctionType(); + case SyntaxKind.ExclamationToken: + return parseJSDocNonNullableType(); + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + return parseLiteralTypeNode(); + case SyntaxKind.MinusToken: + return lookAhead(nextTokenIsNumericOrBigIntLiteral) ? parseLiteralTypeNode(/*negative*/ true) : parseTypeReference(); + case SyntaxKind.VoidKeyword: + return parseTokenNode(); + case SyntaxKind.ThisKeyword: { + const thisKeyword = parseThisTypeNode(); + if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { + return parseThisTypePredicate(thisKeyword); + } + else { + return thisKeyword; + } + } + case SyntaxKind.TypeOfKeyword: + return lookAhead(isStartOfTypeOfImportType) ? parseImportType() : parseTypeQuery(); + case SyntaxKind.OpenBraceToken: + return lookAhead(isStartOfMappedType) ? parseMappedType() : parseTypeLiteral(); + case SyntaxKind.OpenBracketToken: + return parseTupleType(); + case SyntaxKind.OpenParenToken: + return parseParenthesizedType(); + case SyntaxKind.ImportKeyword: + return parseImportType(); + case SyntaxKind.AssertsKeyword: + return lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine) ? parseAssertsTypePredicate() : parseTypeReference(); + case SyntaxKind.TemplateHead: + return parseTemplateType(); + default: + return parseTypeReference(); } + } - function nextTokenIsNumericOrBigIntLiteral() { - nextToken(); - return token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral; + function isStartOfType(inStartOfParameter?: boolean): boolean { + switch (token()) { + case SyntaxKind.AnyKeyword: + case SyntaxKind.UnknownKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.UniqueKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.ThisKeyword: + case SyntaxKind.TypeOfKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.OpenBraceToken: + case SyntaxKind.OpenBracketToken: + case SyntaxKind.LessThanToken: + case SyntaxKind.BarToken: + case SyntaxKind.AmpersandToken: + case SyntaxKind.NewKeyword: + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.AsteriskToken: + case SyntaxKind.QuestionToken: + case SyntaxKind.ExclamationToken: + case SyntaxKind.DotDotDotToken: + case SyntaxKind.InferKeyword: + case SyntaxKind.ImportKeyword: + case SyntaxKind.AssertsKeyword: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + return true; + case SyntaxKind.FunctionKeyword: + return !inStartOfParameter; + case SyntaxKind.MinusToken: + return !inStartOfParameter && lookAhead(nextTokenIsNumericOrBigIntLiteral); + case SyntaxKind.OpenParenToken: + // Only consider '(' the start of a type if followed by ')', '...', an identifier, a modifier, + // or something that starts a type. We don't want to consider things like '(1)' a type. + return !inStartOfParameter && lookAhead(isStartOfParenthesizedOrFunctionType); + default: + return isIdentifier(); } + } - function parseNonArrayType(): TypeNode { + function isStartOfParenthesizedOrFunctionType() { + nextToken(); + return token() === SyntaxKind.CloseParenToken || isStartOfParameter(/*isJSDocParameter*/ false) || isStartOfType(); + } + + function parsePostfixTypeOrHigher(): TypeNode { + const pos = getNodePos(); + let type = parseNonArrayType(); + while (!scanner.hasPrecedingLineBreak()) { switch (token()) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.UnknownKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.UndefinedKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.ObjectKeyword: - // If these are followed by a dot, then parse these out as a dotted type reference instead. - return tryParse(parseKeywordAndNoDot) || parseTypeReference(); - case SyntaxKind.AsteriskEqualsToken: - // If there is '*=', treat it as * followed by postfix = - scanner.reScanAsteriskEqualsToken(); - // falls through - case SyntaxKind.AsteriskToken: - return parseJSDocAllType(); - case SyntaxKind.QuestionQuestionToken: - // If there is '??', treat it as prefix-'?' in JSDoc type. - scanner.reScanQuestionToken(); - // falls through - case SyntaxKind.QuestionToken: - return parseJSDocUnknownOrNullableType(); - case SyntaxKind.FunctionKeyword: - return parseJSDocFunctionType(); case SyntaxKind.ExclamationToken: - return parseJSDocNonNullableType(); - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - return parseLiteralTypeNode(); - case SyntaxKind.MinusToken: - return lookAhead(nextTokenIsNumericOrBigIntLiteral) ? parseLiteralTypeNode(/*negative*/ true) : parseTypeReference(); - case SyntaxKind.VoidKeyword: - return parseTokenNode(); - case SyntaxKind.ThisKeyword: { - const thisKeyword = parseThisTypeNode(); - if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { - return parseThisTypePredicate(thisKeyword); + nextToken(); + type = finishNode(factory.createJSDocNonNullableType(type, /*postfix*/ true), pos); + break; + case SyntaxKind.QuestionToken: + // If next token is start of a type we have a conditional type + if (lookAhead(nextTokenIsStartOfType)) { + return type; + } + nextToken(); + type = finishNode(factory.createJSDocNullableType(type, /*postfix*/ true), pos); + break; + case SyntaxKind.OpenBracketToken: + parseExpected(SyntaxKind.OpenBracketToken); + if (isStartOfType()) { + const indexType = parseType(); + parseExpected(SyntaxKind.CloseBracketToken); + type = finishNode(factory.createIndexedAccessTypeNode(type, indexType), pos); } else { - return thisKeyword; + parseExpected(SyntaxKind.CloseBracketToken); + type = finishNode(factory.createArrayTypeNode(type), pos); } - } - case SyntaxKind.TypeOfKeyword: - return lookAhead(isStartOfTypeOfImportType) ? parseImportType() : parseTypeQuery(); - case SyntaxKind.OpenBraceToken: - return lookAhead(isStartOfMappedType) ? parseMappedType() : parseTypeLiteral(); - case SyntaxKind.OpenBracketToken: - return parseTupleType(); - case SyntaxKind.OpenParenToken: - return parseParenthesizedType(); - case SyntaxKind.ImportKeyword: - return parseImportType(); - case SyntaxKind.AssertsKeyword: - return lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine) ? parseAssertsTypePredicate() : parseTypeReference(); - case SyntaxKind.TemplateHead: - return parseTemplateType(); + break; default: - return parseTypeReference(); + return type; } } + return type; + } - function isStartOfType(inStartOfParameter?: boolean): boolean { - switch (token()) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.UnknownKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.UniqueKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.UndefinedKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.ThisKeyword: - case SyntaxKind.TypeOfKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.OpenBraceToken: - case SyntaxKind.OpenBracketToken: - case SyntaxKind.LessThanToken: - case SyntaxKind.BarToken: - case SyntaxKind.AmpersandToken: - case SyntaxKind.NewKeyword: - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.AsteriskToken: - case SyntaxKind.QuestionToken: - case SyntaxKind.ExclamationToken: - case SyntaxKind.DotDotDotToken: - case SyntaxKind.InferKeyword: - case SyntaxKind.ImportKeyword: - case SyntaxKind.AssertsKeyword: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateHead: - return true; - case SyntaxKind.FunctionKeyword: - return !inStartOfParameter; - case SyntaxKind.MinusToken: - return !inStartOfParameter && lookAhead(nextTokenIsNumericOrBigIntLiteral); - case SyntaxKind.OpenParenToken: - // Only consider '(' the start of a type if followed by ')', '...', an identifier, a modifier, - // or something that starts a type. We don't want to consider things like '(1)' a type. - return !inStartOfParameter && lookAhead(isStartOfParenthesizedOrFunctionType); - default: - return isIdentifier(); + function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) { + const pos = getNodePos(); + parseExpected(operator); + return finishNode(factory.createTypeOperatorNode(operator, parseTypeOperatorOrHigher()), pos); + } + + function tryParseConstraintOfInferType() { + if (parseOptional(SyntaxKind.ExtendsKeyword)) { + const constraint = disallowConditionalTypesAnd(parseType); + if (inDisallowConditionalTypesContext() || token() !== SyntaxKind.QuestionToken) { + return constraint; } } + } - function isStartOfParenthesizedOrFunctionType() { - nextToken(); - return token() === SyntaxKind.CloseParenToken || isStartOfParameter(/*isJSDocParameter*/ false) || isStartOfType(); - } + function parseTypeParameterOfInferType(): TypeParameterDeclaration { + const pos = getNodePos(); + const name = parseIdentifier(); + const constraint = tryParse(tryParseConstraintOfInferType); + const node = factory.createTypeParameterDeclaration(/*modifiers*/ undefined, name, constraint); + return finishNode(node, pos); + } - function parsePostfixTypeOrHigher(): TypeNode { - const pos = getNodePos(); - let type = parseNonArrayType(); - while (!scanner.hasPrecedingLineBreak()) { - switch (token()) { - case SyntaxKind.ExclamationToken: - nextToken(); - type = finishNode(factory.createJSDocNonNullableType(type, /*postfix*/ true), pos); - break; - case SyntaxKind.QuestionToken: - // If next token is start of a type we have a conditional type - if (lookAhead(nextTokenIsStartOfType)) { - return type; - } - nextToken(); - type = finishNode(factory.createJSDocNullableType(type, /*postfix*/ true), pos); - break; - case SyntaxKind.OpenBracketToken: - parseExpected(SyntaxKind.OpenBracketToken); - if (isStartOfType()) { - const indexType = parseType(); - parseExpected(SyntaxKind.CloseBracketToken); - type = finishNode(factory.createIndexedAccessTypeNode(type, indexType), pos); - } - else { - parseExpected(SyntaxKind.CloseBracketToken); - type = finishNode(factory.createArrayTypeNode(type), pos); - } - break; - default: - return type; - } - } - return type; - } + function parseInferType(): InferTypeNode { + const pos = getNodePos(); + parseExpected(SyntaxKind.InferKeyword); + return finishNode(factory.createInferTypeNode(parseTypeParameterOfInferType()), pos); + } - function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) { - const pos = getNodePos(); - parseExpected(operator); - return finishNode(factory.createTypeOperatorNode(operator, parseTypeOperatorOrHigher()), pos); - } + function parseTypeOperatorOrHigher(): TypeNode { + const operator = token(); + switch (operator) { + case SyntaxKind.KeyOfKeyword: + case SyntaxKind.UniqueKeyword: + case SyntaxKind.ReadonlyKeyword: + return parseTypeOperator(operator); + case SyntaxKind.InferKeyword: + return parseInferType(); + } + return allowConditionalTypesAnd(parsePostfixTypeOrHigher); + } - function tryParseConstraintOfInferType() { - if (parseOptional(SyntaxKind.ExtendsKeyword)) { - const constraint = disallowConditionalTypesAnd(parseType); - if (inDisallowConditionalTypesContext() || token() !== SyntaxKind.QuestionToken) { - return constraint; - } + function parseFunctionOrConstructorTypeToError( + isInUnionType: boolean + ): TypeNode | undefined { + // the function type and constructor type shorthand notation + // are not allowed directly in unions and intersections, but we'll + // try to parse them gracefully and issue a helpful message. + if (isStartOfFunctionTypeOrConstructorType()) { + const type = parseFunctionOrConstructorType(); + let diagnostic: DiagnosticMessage; + if (isFunctionTypeNode(type)) { + diagnostic = isInUnionType + ? Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_a_union_type + : Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_an_intersection_type; } - } + else { + diagnostic = isInUnionType + ? Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_a_union_type + : Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_an_intersection_type; - function parseTypeParameterOfInferType(): TypeParameterDeclaration { - const pos = getNodePos(); - const name = parseIdentifier(); - const constraint = tryParse(tryParseConstraintOfInferType); - const node = factory.createTypeParameterDeclaration(/*modifiers*/ undefined, name, constraint); - return finishNode(node, pos); + } + parseErrorAtRange(type, diagnostic); + return type; } + return undefined; + } - function parseInferType(): InferTypeNode { - const pos = getNodePos(); - parseExpected(SyntaxKind.InferKeyword); - return finishNode(factory.createInferTypeNode(parseTypeParameterOfInferType()), pos); - } + function parseUnionOrIntersectionType( + operator: SyntaxKind.BarToken | SyntaxKind.AmpersandToken, + parseConstituentType: () => TypeNode, + createTypeNode: (types: NodeArray) => UnionOrIntersectionTypeNode + ): TypeNode { + const pos = getNodePos(); + const isUnionType = operator === SyntaxKind.BarToken; + const hasLeadingOperator = parseOptional(operator); + let type = hasLeadingOperator && parseFunctionOrConstructorTypeToError(isUnionType) + || parseConstituentType(); + if (token() === operator || hasLeadingOperator) { + const types = [type]; + while (parseOptional(operator)) { + types.push(parseFunctionOrConstructorTypeToError(isUnionType) || parseConstituentType()); + } + type = finishNode(createTypeNode(createNodeArray(types, pos)), pos); + } + return type; + } - function parseTypeOperatorOrHigher(): TypeNode { - const operator = token(); - switch (operator) { - case SyntaxKind.KeyOfKeyword: - case SyntaxKind.UniqueKeyword: - case SyntaxKind.ReadonlyKeyword: - return parseTypeOperator(operator); - case SyntaxKind.InferKeyword: - return parseInferType(); - } - return allowConditionalTypesAnd(parsePostfixTypeOrHigher); - } - - function parseFunctionOrConstructorTypeToError( - isInUnionType: boolean - ): TypeNode | undefined { - // the function type and constructor type shorthand notation - // are not allowed directly in unions and intersections, but we'll - // try to parse them gracefully and issue a helpful message. - if (isStartOfFunctionTypeOrConstructorType()) { - const type = parseFunctionOrConstructorType(); - let diagnostic: DiagnosticMessage; - if (isFunctionTypeNode(type)) { - diagnostic = isInUnionType - ? Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_a_union_type - : Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_an_intersection_type; - } - else { - diagnostic = isInUnionType - ? Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_a_union_type - : Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_an_intersection_type; + function parseIntersectionTypeOrHigher(): TypeNode { + return parseUnionOrIntersectionType(SyntaxKind.AmpersandToken, parseTypeOperatorOrHigher, factory.createIntersectionTypeNode); + } - } - parseErrorAtRange(type, diagnostic); - return type; - } - return undefined; - } + function parseUnionTypeOrHigher(): TypeNode { + return parseUnionOrIntersectionType(SyntaxKind.BarToken, parseIntersectionTypeOrHigher, factory.createUnionTypeNode); + } - function parseUnionOrIntersectionType( - operator: SyntaxKind.BarToken | SyntaxKind.AmpersandToken, - parseConstituentType: () => TypeNode, - createTypeNode: (types: NodeArray) => UnionOrIntersectionTypeNode - ): TypeNode { - const pos = getNodePos(); - const isUnionType = operator === SyntaxKind.BarToken; - const hasLeadingOperator = parseOptional(operator); - let type = hasLeadingOperator && parseFunctionOrConstructorTypeToError(isUnionType) - || parseConstituentType(); - if (token() === operator || hasLeadingOperator) { - const types = [type]; - while (parseOptional(operator)) { - types.push(parseFunctionOrConstructorTypeToError(isUnionType) || parseConstituentType()); - } - type = finishNode(createTypeNode(createNodeArray(types, pos)), pos); - } - return type; - } + function nextTokenIsNewKeyword(): boolean { + nextToken(); + return token() === SyntaxKind.NewKeyword; + } - function parseIntersectionTypeOrHigher(): TypeNode { - return parseUnionOrIntersectionType(SyntaxKind.AmpersandToken, parseTypeOperatorOrHigher, factory.createIntersectionTypeNode); + function isStartOfFunctionTypeOrConstructorType(): boolean { + if (token() === SyntaxKind.LessThanToken) { + return true; } - - function parseUnionTypeOrHigher(): TypeNode { - return parseUnionOrIntersectionType(SyntaxKind.BarToken, parseIntersectionTypeOrHigher, factory.createUnionTypeNode); + if (token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType)) { + return true; } + return token() === SyntaxKind.NewKeyword || + token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsNewKeyword); + } - function nextTokenIsNewKeyword(): boolean { + function skipParameterStart(): boolean { + if (isModifierKind(token())) { + // Skip modifiers + parseModifiers(); + } + if (isIdentifier() || token() === SyntaxKind.ThisKeyword) { nextToken(); - return token() === SyntaxKind.NewKeyword; + return true; } - - function isStartOfFunctionTypeOrConstructorType(): boolean { - if (token() === SyntaxKind.LessThanToken) { - return true; - } - if (token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType)) { - return true; - } - return token() === SyntaxKind.NewKeyword || - token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsNewKeyword); + if (token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.OpenBraceToken) { + // Return true if we can parse an array or object binding pattern with no errors + const previousErrorCount = parseDiagnostics.length; + parseIdentifierOrPattern(); + return previousErrorCount === parseDiagnostics.length; } + return false; + } - function skipParameterStart(): boolean { - if (isModifierKind(token())) { - // Skip modifiers - parseModifiers(); - } - if (isIdentifier() || token() === SyntaxKind.ThisKeyword) { - nextToken(); - return true; - } - if (token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.OpenBraceToken) { - // Return true if we can parse an array or object binding pattern with no errors - const previousErrorCount = parseDiagnostics.length; - parseIdentifierOrPattern(); - return previousErrorCount === parseDiagnostics.length; - } - return false; + function isUnambiguouslyStartOfFunctionType() { + nextToken(); + if (token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.DotDotDotToken) { + // ( ) + // ( ... + return true; } - - function isUnambiguouslyStartOfFunctionType() { - nextToken(); - if (token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.DotDotDotToken) { - // ( ) - // ( ... + if (skipParameterStart()) { + // We successfully skipped modifiers (if any) and an identifier or binding pattern, + // now see if we have something that indicates a parameter declaration + if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || + token() === SyntaxKind.QuestionToken || token() === SyntaxKind.EqualsToken) { + // ( xxx : + // ( xxx , + // ( xxx ? + // ( xxx = return true; } - if (skipParameterStart()) { - // We successfully skipped modifiers (if any) and an identifier or binding pattern, - // now see if we have something that indicates a parameter declaration - if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || - token() === SyntaxKind.QuestionToken || token() === SyntaxKind.EqualsToken) { - // ( xxx : - // ( xxx , - // ( xxx ? - // ( xxx = + if (token() === SyntaxKind.CloseParenToken) { + nextToken(); + if (token() === SyntaxKind.EqualsGreaterThanToken) { + // ( xxx ) => return true; } - if (token() === SyntaxKind.CloseParenToken) { - nextToken(); - if (token() === SyntaxKind.EqualsGreaterThanToken) { - // ( xxx ) => - return true; - } - } } - return false; } + return false; + } - function parseTypeOrTypePredicate(): TypeNode { - const pos = getNodePos(); - const typePredicateVariable = isIdentifier() && tryParse(parseTypePredicatePrefix); - const type = parseType(); - if (typePredicateVariable) { - return finishNode(factory.createTypePredicateNode(/*assertsModifier*/ undefined, typePredicateVariable, type), pos); - } - else { - return type; - } + function parseTypeOrTypePredicate(): TypeNode { + const pos = getNodePos(); + const typePredicateVariable = isIdentifier() && tryParse(parseTypePredicatePrefix); + const type = parseType(); + if (typePredicateVariable) { + return finishNode(factory.createTypePredicateNode(/*assertsModifier*/ undefined, typePredicateVariable, type), pos); + } + else { + return type; } + } - function parseTypePredicatePrefix() { - const id = parseIdentifier(); - if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { - nextToken(); - return id; - } + function parseTypePredicatePrefix() { + const id = parseIdentifier(); + if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { + nextToken(); + return id; } + } - function parseAssertsTypePredicate(): TypeNode { - const pos = getNodePos(); - const assertsModifier = parseExpectedToken(SyntaxKind.AssertsKeyword); - const parameterName = token() === SyntaxKind.ThisKeyword ? parseThisTypeNode() : parseIdentifier(); - const type = parseOptional(SyntaxKind.IsKeyword) ? parseType() : undefined; - return finishNode(factory.createTypePredicateNode(assertsModifier, parameterName, type), pos); + function parseAssertsTypePredicate(): TypeNode { + const pos = getNodePos(); + const assertsModifier = parseExpectedToken(SyntaxKind.AssertsKeyword); + const parameterName = token() === SyntaxKind.ThisKeyword ? parseThisTypeNode() : parseIdentifier(); + const type = parseOptional(SyntaxKind.IsKeyword) ? parseType() : undefined; + return finishNode(factory.createTypePredicateNode(assertsModifier, parameterName, type), pos); + } + + function parseType(): TypeNode { + if (contextFlags & NodeFlags.TypeExcludesFlags) { + return doOutsideOfContext(NodeFlags.TypeExcludesFlags, parseType); } - function parseType(): TypeNode { - if (contextFlags & NodeFlags.TypeExcludesFlags) { - return doOutsideOfContext(NodeFlags.TypeExcludesFlags, parseType); - } + if (isStartOfFunctionTypeOrConstructorType()) { + return parseFunctionOrConstructorType(); + } + const pos = getNodePos(); + const type = parseUnionTypeOrHigher(); + if (!inDisallowConditionalTypesContext() && !scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.ExtendsKeyword)) { + // The type following 'extends' is not permitted to be another conditional type + const extendsType = disallowConditionalTypesAnd(parseType); + parseExpected(SyntaxKind.QuestionToken); + const trueType = allowConditionalTypesAnd(parseType); + parseExpected(SyntaxKind.ColonToken); + const falseType = allowConditionalTypesAnd(parseType); + return finishNode(factory.createConditionalTypeNode(type, extendsType, trueType, falseType), pos); + } + return type; + } - if (isStartOfFunctionTypeOrConstructorType()) { - return parseFunctionOrConstructorType(); - } - const pos = getNodePos(); - const type = parseUnionTypeOrHigher(); - if (!inDisallowConditionalTypesContext() && !scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.ExtendsKeyword)) { - // The type following 'extends' is not permitted to be another conditional type - const extendsType = disallowConditionalTypesAnd(parseType); - parseExpected(SyntaxKind.QuestionToken); - const trueType = allowConditionalTypesAnd(parseType); - parseExpected(SyntaxKind.ColonToken); - const falseType = allowConditionalTypesAnd(parseType); - return finishNode(factory.createConditionalTypeNode(type, extendsType, trueType, falseType), pos); - } - return type; + function parseTypeAnnotation(): TypeNode | undefined { + return parseOptional(SyntaxKind.ColonToken) ? parseType() : undefined; + } + + // EXPRESSIONS + function isStartOfLeftHandSideExpression(): boolean { + switch (token()) { + case SyntaxKind.ThisKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + case SyntaxKind.OpenParenToken: + case SyntaxKind.OpenBracketToken: + case SyntaxKind.OpenBraceToken: + case SyntaxKind.FunctionKeyword: + case SyntaxKind.ClassKeyword: + case SyntaxKind.NewKeyword: + case SyntaxKind.SlashToken: + case SyntaxKind.SlashEqualsToken: + case SyntaxKind.Identifier: + return true; + case SyntaxKind.ImportKeyword: + return lookAhead(nextTokenIsOpenParenOrLessThanOrDot); + default: + return isIdentifier(); } + } - function parseTypeAnnotation(): TypeNode | undefined { - return parseOptional(SyntaxKind.ColonToken) ? parseType() : undefined; + function isStartOfExpression(): boolean { + if (isStartOfLeftHandSideExpression()) { + return true; } - // EXPRESSIONS - function isStartOfLeftHandSideExpression(): boolean { - switch (token()) { - case SyntaxKind.ThisKeyword: - case SyntaxKind.SuperKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateHead: - case SyntaxKind.OpenParenToken: - case SyntaxKind.OpenBracketToken: - case SyntaxKind.OpenBraceToken: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.ClassKeyword: - case SyntaxKind.NewKeyword: - case SyntaxKind.SlashToken: - case SyntaxKind.SlashEqualsToken: - case SyntaxKind.Identifier: + switch (token()) { + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + case SyntaxKind.ExclamationToken: + case SyntaxKind.DeleteKeyword: + case SyntaxKind.TypeOfKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.PlusPlusToken: + case SyntaxKind.MinusMinusToken: + case SyntaxKind.LessThanToken: + case SyntaxKind.AwaitKeyword: + case SyntaxKind.YieldKeyword: + case SyntaxKind.PrivateIdentifier: + // Yield/await always starts an expression. Either it is an identifier (in which case + // it is definitely an expression). Or it's a keyword (either because we're in + // a generator or async function, or in strict mode (or both)) and it started a yield or await expression. + return true; + default: + // Error tolerance. If we see the start of some binary operator, we consider + // that the start of an expression. That way we'll parse out a missing identifier, + // give a good message about an identifier being missing, and then consume the + // rest of the binary expression. + if (isBinaryOperator()) { return true; - case SyntaxKind.ImportKeyword: - return lookAhead(nextTokenIsOpenParenOrLessThanOrDot); - default: - return isIdentifier(); - } + } + + return isIdentifier(); } + } - function isStartOfExpression(): boolean { - if (isStartOfLeftHandSideExpression()) { - return true; - } + function isStartOfExpressionStatement(): boolean { + // As per the grammar, none of '{' or 'function' or 'class' can start an expression statement. + return token() !== SyntaxKind.OpenBraceToken && + token() !== SyntaxKind.FunctionKeyword && + token() !== SyntaxKind.ClassKeyword && + token() !== SyntaxKind.AtToken && + isStartOfExpression(); + } - switch (token()) { - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - case SyntaxKind.ExclamationToken: - case SyntaxKind.DeleteKeyword: - case SyntaxKind.TypeOfKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.PlusPlusToken: - case SyntaxKind.MinusMinusToken: - case SyntaxKind.LessThanToken: - case SyntaxKind.AwaitKeyword: - case SyntaxKind.YieldKeyword: - case SyntaxKind.PrivateIdentifier: - // Yield/await always starts an expression. Either it is an identifier (in which case - // it is definitely an expression). Or it's a keyword (either because we're in - // a generator or async function, or in strict mode (or both)) and it started a yield or await expression. - return true; - default: - // Error tolerance. If we see the start of some binary operator, we consider - // that the start of an expression. That way we'll parse out a missing identifier, - // give a good message about an identifier being missing, and then consume the - // rest of the binary expression. - if (isBinaryOperator()) { - return true; - } + function parseExpression(): Expression { + // Expression[in]: + // AssignmentExpression[in] + // Expression[in] , AssignmentExpression[in] - return isIdentifier(); - } + // clear the decorator context when parsing Expression, as it should be unambiguous when parsing a decorator + const saveDecoratorContext = inDecoratorContext(); + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ false); } - function isStartOfExpressionStatement(): boolean { - // As per the grammar, none of '{' or 'function' or 'class' can start an expression statement. - return token() !== SyntaxKind.OpenBraceToken && - token() !== SyntaxKind.FunctionKeyword && - token() !== SyntaxKind.ClassKeyword && - token() !== SyntaxKind.AtToken && - isStartOfExpression(); + const pos = getNodePos(); + let expr = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); + let operatorToken: BinaryOperatorToken; + while ((operatorToken = parseOptionalToken(SyntaxKind.CommaToken))) { + expr = makeBinaryExpression(expr, operatorToken, parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true), pos); } - function parseExpression(): Expression { - // Expression[in]: - // AssignmentExpression[in] - // Expression[in] , AssignmentExpression[in] + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ true); + } + return expr; + } - // clear the decorator context when parsing Expression, as it should be unambiguous when parsing a decorator - const saveDecoratorContext = inDecoratorContext(); - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ false); - } + function parseInitializer(): Expression | undefined { + return parseOptional(SyntaxKind.EqualsToken) ? parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true) : undefined; + } - const pos = getNodePos(); - let expr = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); - let operatorToken: BinaryOperatorToken; - while ((operatorToken = parseOptionalToken(SyntaxKind.CommaToken))) { - expr = makeBinaryExpression(expr, operatorToken, parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true), pos); - } + function parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction: boolean): Expression { + // AssignmentExpression[in,yield]: + // 1) ConditionalExpression[?in,?yield] + // 2) LeftHandSideExpression = AssignmentExpression[?in,?yield] + // 3) LeftHandSideExpression AssignmentOperator AssignmentExpression[?in,?yield] + // 4) ArrowFunctionExpression[?in,?yield] + // 5) AsyncArrowFunctionExpression[in,yield,await] + // 6) [+Yield] YieldExpression[?In] + // + // Note: for ease of implementation we treat productions '2' and '3' as the same thing. + // (i.e. they're both BinaryExpressions with an assignment operator in it). - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ true); - } - return expr; + // First, do the simple check if we have a YieldExpression (production '6'). + if (isYieldExpression()) { + return parseYieldExpression(); + } + + // Then, check if we have an arrow function (production '4' and '5') that starts with a parenthesized + // parameter list or is an async arrow function. + // AsyncArrowFunctionExpression: + // 1) async[no LineTerminator here]AsyncArrowBindingIdentifier[?Yield][no LineTerminator here]=>AsyncConciseBody[?In] + // 2) CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await][no LineTerminator here]=>AsyncConciseBody[?In] + // Production (1) of AsyncArrowFunctionExpression is parsed in "tryParseAsyncSimpleArrowFunctionExpression". + // And production (2) is parsed in "tryParseParenthesizedArrowFunctionExpression". + // + // If we do successfully parse arrow-function, we must *not* recurse for productions 1, 2 or 3. An ArrowFunction is + // not a LeftHandSideExpression, nor does it start a ConditionalExpression. So we are done + // with AssignmentExpression if we see one. + const arrowExpression = tryParseParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction) || tryParseAsyncSimpleArrowFunctionExpression(allowReturnTypeInArrowFunction); + if (arrowExpression) { + return arrowExpression; } - function parseInitializer(): Expression | undefined { - return parseOptional(SyntaxKind.EqualsToken) ? parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true) : undefined; + // Now try to see if we're in production '1', '2' or '3'. A conditional expression can + // start with a LogicalOrExpression, while the assignment productions can only start with + // LeftHandSideExpressions. + // + // So, first, we try to just parse out a BinaryExpression. If we get something that is a + // LeftHandSide or higher, then we can try to parse out the assignment expression part. + // Otherwise, we try to parse out the conditional expression bit. We want to allow any + // binary expression here, so we pass in the 'lowest' precedence here so that it matches + // and consumes anything. + const pos = getNodePos(); + const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); + + // To avoid a look-ahead, we did not handle the case of an arrow function with a single un-parenthesized + // parameter ('x => ...') above. We handle it here by checking if the parsed expression was a single + // identifier and the current token is an arrow. + if (expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { + return parseSimpleArrowFunctionExpression(pos, expr as Identifier, allowReturnTypeInArrowFunction, /*asyncModifier*/ undefined); + } + + // Now see if we might be in cases '2' or '3'. + // If the expression was a LHS expression, and we have an assignment operator, then + // we're in '2' or '3'. Consume the assignment and return. + // + // Note: we call reScanGreaterToken so that we get an appropriately merged token + // for cases like `> > =` becoming `>>=` + if (isLeftHandSideExpression(expr) && isAssignmentOperator(reScanGreaterToken())) { + return makeBinaryExpression(expr, parseTokenNode(), parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction), pos); } - function parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction: boolean): Expression { - // AssignmentExpression[in,yield]: - // 1) ConditionalExpression[?in,?yield] - // 2) LeftHandSideExpression = AssignmentExpression[?in,?yield] - // 3) LeftHandSideExpression AssignmentOperator AssignmentExpression[?in,?yield] - // 4) ArrowFunctionExpression[?in,?yield] - // 5) AsyncArrowFunctionExpression[in,yield,await] - // 6) [+Yield] YieldExpression[?In] - // - // Note: for ease of implementation we treat productions '2' and '3' as the same thing. - // (i.e. they're both BinaryExpressions with an assignment operator in it). + // It wasn't an assignment or a lambda. This is a conditional expression: + return parseConditionalExpressionRest(expr, pos, allowReturnTypeInArrowFunction); + } - // First, do the simple check if we have a YieldExpression (production '6'). - if (isYieldExpression()) { - return parseYieldExpression(); + function isYieldExpression(): boolean { + if (token() === SyntaxKind.YieldKeyword) { + // If we have a 'yield' keyword, and this is a context where yield expressions are + // allowed, then definitely parse out a yield expression. + if (inYieldContext()) { + return true; } - // Then, check if we have an arrow function (production '4' and '5') that starts with a parenthesized - // parameter list or is an async arrow function. - // AsyncArrowFunctionExpression: - // 1) async[no LineTerminator here]AsyncArrowBindingIdentifier[?Yield][no LineTerminator here]=>AsyncConciseBody[?In] - // 2) CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await][no LineTerminator here]=>AsyncConciseBody[?In] - // Production (1) of AsyncArrowFunctionExpression is parsed in "tryParseAsyncSimpleArrowFunctionExpression". - // And production (2) is parsed in "tryParseParenthesizedArrowFunctionExpression". + // We're in a context where 'yield expr' is not allowed. However, if we can + // definitely tell that the user was trying to parse a 'yield expr' and not + // just a normal expr that start with a 'yield' identifier, then parse out + // a 'yield expr'. We can then report an error later that they are only + // allowed in generator expressions. // - // If we do successfully parse arrow-function, we must *not* recurse for productions 1, 2 or 3. An ArrowFunction is - // not a LeftHandSideExpression, nor does it start a ConditionalExpression. So we are done - // with AssignmentExpression if we see one. - const arrowExpression = tryParseParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction) || tryParseAsyncSimpleArrowFunctionExpression(allowReturnTypeInArrowFunction); - if (arrowExpression) { - return arrowExpression; - } - - // Now try to see if we're in production '1', '2' or '3'. A conditional expression can - // start with a LogicalOrExpression, while the assignment productions can only start with - // LeftHandSideExpressions. + // for example, if we see 'yield(foo)', then we'll have to treat that as an + // invocation expression of something called 'yield'. However, if we have + // 'yield foo' then that is not legal as a normal expression, so we can + // definitely recognize this as a yield expression. // - // So, first, we try to just parse out a BinaryExpression. If we get something that is a - // LeftHandSide or higher, then we can try to parse out the assignment expression part. - // Otherwise, we try to parse out the conditional expression bit. We want to allow any - // binary expression here, so we pass in the 'lowest' precedence here so that it matches - // and consumes anything. - const pos = getNodePos(); - const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); + // for now we just check if the next token is an identifier. More heuristics + // can be added here later as necessary. We just need to make sure that we + // don't accidentally consume something legal. + return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); + } - // To avoid a look-ahead, we did not handle the case of an arrow function with a single un-parenthesized - // parameter ('x => ...') above. We handle it here by checking if the parsed expression was a single - // identifier and the current token is an arrow. - if (expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { - return parseSimpleArrowFunctionExpression(pos, expr as Identifier, allowReturnTypeInArrowFunction, /*asyncModifier*/ undefined); - } + return false; + } - // Now see if we might be in cases '2' or '3'. - // If the expression was a LHS expression, and we have an assignment operator, then - // we're in '2' or '3'. Consume the assignment and return. - // - // Note: we call reScanGreaterToken so that we get an appropriately merged token - // for cases like `> > =` becoming `>>=` - if (isLeftHandSideExpression(expr) && isAssignmentOperator(reScanGreaterToken())) { - return makeBinaryExpression(expr, parseTokenNode(), parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction), pos); - } + function nextTokenIsIdentifierOnSameLine() { + nextToken(); + return !scanner.hasPrecedingLineBreak() && isIdentifier(); + } - // It wasn't an assignment or a lambda. This is a conditional expression: - return parseConditionalExpressionRest(expr, pos, allowReturnTypeInArrowFunction); - } + function parseYieldExpression(): YieldExpression { + const pos = getNodePos(); - function isYieldExpression(): boolean { - if (token() === SyntaxKind.YieldKeyword) { - // If we have a 'yield' keyword, and this is a context where yield expressions are - // allowed, then definitely parse out a yield expression. - if (inYieldContext()) { - return true; - } + // YieldExpression[In] : + // yield + // yield [no LineTerminator here] [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] + // yield [no LineTerminator here] * [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] + nextToken(); - // We're in a context where 'yield expr' is not allowed. However, if we can - // definitely tell that the user was trying to parse a 'yield expr' and not - // just a normal expr that start with a 'yield' identifier, then parse out - // a 'yield expr'. We can then report an error later that they are only - // allowed in generator expressions. - // - // for example, if we see 'yield(foo)', then we'll have to treat that as an - // invocation expression of something called 'yield'. However, if we have - // 'yield foo' then that is not legal as a normal expression, so we can - // definitely recognize this as a yield expression. - // - // for now we just check if the next token is an identifier. More heuristics - // can be added here later as necessary. We just need to make sure that we - // don't accidentally consume something legal. - return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); - } + if (!scanner.hasPrecedingLineBreak() && + (token() === SyntaxKind.AsteriskToken || isStartOfExpression())) { + return finishNode( + factory.createYieldExpression( + parseOptionalToken(SyntaxKind.AsteriskToken), + parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true) + ), + pos + ); + } + else { + // if the next token is not on the same line as yield. or we don't have an '*' or + // the start of an expression, then this is just a simple "yield" expression. + return finishNode(factory.createYieldExpression(/*asteriskToken*/ undefined, /*expression*/ undefined), pos); + } + } - return false; + function parseSimpleArrowFunctionExpression(pos: number, identifier: Identifier, allowReturnTypeInArrowFunction: boolean, asyncModifier?: NodeArray | undefined): ArrowFunction { + Debug.assert(token() === SyntaxKind.EqualsGreaterThanToken, "parseSimpleArrowFunctionExpression should only have been called if we had a =>"); + const parameter = factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + identifier, + /*questionToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + ); + finishNode(parameter, identifier.pos); + + const parameters = createNodeArray([parameter], parameter.pos, parameter.end); + const equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); + const body = parseArrowFunctionExpressionBody(/*isAsync*/ !!asyncModifier, allowReturnTypeInArrowFunction); + const node = factory.createArrowFunction(asyncModifier, /*typeParameters*/ undefined, parameters, /*type*/ undefined, equalsGreaterThanToken, body); + return addJSDocComment(finishNode(node, pos)); + } + + function tryParseParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction: boolean): Expression | undefined { + const triState = isParenthesizedArrowFunctionExpression(); + if (triState === Tristate.False) { + // It's definitely not a parenthesized arrow function expression. + return undefined; } - function nextTokenIsIdentifierOnSameLine() { - nextToken(); - return !scanner.hasPrecedingLineBreak() && isIdentifier(); + // If we definitely have an arrow function, then we can just parse one, not requiring a + // following => or { token. Otherwise, we *might* have an arrow function. Try to parse + // it out, but don't allow any ambiguity, and return 'undefined' if this could be an + // expression instead. + return triState === Tristate.True ? + parseParenthesizedArrowFunctionExpression(/*allowAmbiguity*/ true, /*allowReturnTypeInArrowFunction*/ true) : + tryParse(() => parsePossibleParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction)); + } + + // True -> We definitely expect a parenthesized arrow function here. + // False -> There *cannot* be a parenthesized arrow function here. + // Unknown -> There *might* be a parenthesized arrow function here. + // Speculatively look ahead to be sure, and rollback if not. + function isParenthesizedArrowFunctionExpression(): Tristate { + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken || token() === SyntaxKind.AsyncKeyword) { + return lookAhead(isParenthesizedArrowFunctionExpressionWorker); } - function parseYieldExpression(): YieldExpression { - const pos = getNodePos(); + if (token() === SyntaxKind.EqualsGreaterThanToken) { + // ERROR RECOVERY TWEAK: + // If we see a standalone => try to parse it as an arrow function expression as that's + // likely what the user intended to write. + return Tristate.True; + } + // Definitely not a parenthesized arrow function. + return Tristate.False; + } - // YieldExpression[In] : - // yield - // yield [no LineTerminator here] [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] - // yield [no LineTerminator here] * [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield] + function isParenthesizedArrowFunctionExpressionWorker() { + if (token() === SyntaxKind.AsyncKeyword) { nextToken(); - - if (!scanner.hasPrecedingLineBreak() && - (token() === SyntaxKind.AsteriskToken || isStartOfExpression())) { - return finishNode( - factory.createYieldExpression( - parseOptionalToken(SyntaxKind.AsteriskToken), - parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true) - ), - pos - ); + if (scanner.hasPrecedingLineBreak()) { + return Tristate.False; } - else { - // if the next token is not on the same line as yield. or we don't have an '*' or - // the start of an expression, then this is just a simple "yield" expression. - return finishNode(factory.createYieldExpression(/*asteriskToken*/ undefined, /*expression*/ undefined), pos); + if (token() !== SyntaxKind.OpenParenToken && token() !== SyntaxKind.LessThanToken) { + return Tristate.False; } } - function parseSimpleArrowFunctionExpression(pos: number, identifier: Identifier, allowReturnTypeInArrowFunction: boolean, asyncModifier?: NodeArray | undefined): ArrowFunction { - Debug.assert(token() === SyntaxKind.EqualsGreaterThanToken, "parseSimpleArrowFunctionExpression should only have been called if we had a =>"); - const parameter = factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - identifier, - /*questionToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - ); - finishNode(parameter, identifier.pos); - - const parameters = createNodeArray([parameter], parameter.pos, parameter.end); - const equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); - const body = parseArrowFunctionExpressionBody(/*isAsync*/ !!asyncModifier, allowReturnTypeInArrowFunction); - const node = factory.createArrowFunction(asyncModifier, /*typeParameters*/ undefined, parameters, /*type*/ undefined, equalsGreaterThanToken, body); - return addJSDocComment(finishNode(node, pos)); - } + const first = token(); + const second = nextToken(); - function tryParseParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction: boolean): Expression | undefined { - const triState = isParenthesizedArrowFunctionExpression(); - if (triState === Tristate.False) { - // It's definitely not a parenthesized arrow function expression. - return undefined; + if (first === SyntaxKind.OpenParenToken) { + if (second === SyntaxKind.CloseParenToken) { + // Simple cases: "() =>", "(): ", and "() {". + // This is an arrow function with no parameters. + // The last one is not actually an arrow function, + // but this is probably what the user intended. + const third = nextToken(); + switch (third) { + case SyntaxKind.EqualsGreaterThanToken: + case SyntaxKind.ColonToken: + case SyntaxKind.OpenBraceToken: + return Tristate.True; + default: + return Tristate.False; + } } - // If we definitely have an arrow function, then we can just parse one, not requiring a - // following => or { token. Otherwise, we *might* have an arrow function. Try to parse - // it out, but don't allow any ambiguity, and return 'undefined' if this could be an - // expression instead. - return triState === Tristate.True ? - parseParenthesizedArrowFunctionExpression(/*allowAmbiguity*/ true, /*allowReturnTypeInArrowFunction*/ true) : - tryParse(() => parsePossibleParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction)); - } - - // True -> We definitely expect a parenthesized arrow function here. - // False -> There *cannot* be a parenthesized arrow function here. - // Unknown -> There *might* be a parenthesized arrow function here. - // Speculatively look ahead to be sure, and rollback if not. - function isParenthesizedArrowFunctionExpression(): Tristate { - if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken || token() === SyntaxKind.AsyncKeyword) { - return lookAhead(isParenthesizedArrowFunctionExpressionWorker); + // If encounter "([" or "({", this could be the start of a binding pattern. + // Examples: + // ([ x ]) => { } + // ({ x }) => { } + // ([ x ]) + // ({ x }) + if (second === SyntaxKind.OpenBracketToken || second === SyntaxKind.OpenBraceToken) { + return Tristate.Unknown; } - if (token() === SyntaxKind.EqualsGreaterThanToken) { - // ERROR RECOVERY TWEAK: - // If we see a standalone => try to parse it as an arrow function expression as that's - // likely what the user intended to write. + // Simple case: "(..." + // This is an arrow function with a rest parameter. + if (second === SyntaxKind.DotDotDotToken) { return Tristate.True; } - // Definitely not a parenthesized arrow function. - return Tristate.False; - } - function isParenthesizedArrowFunctionExpressionWorker() { - if (token() === SyntaxKind.AsyncKeyword) { - nextToken(); - if (scanner.hasPrecedingLineBreak()) { - return Tristate.False; - } - if (token() !== SyntaxKind.OpenParenToken && token() !== SyntaxKind.LessThanToken) { + // Check for "(xxx yyy", where xxx is a modifier and yyy is an identifier. This + // isn't actually allowed, but we want to treat it as a lambda so we can provide + // a good error message. + if (isModifierKind(second) && second !== SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsIdentifier)) { + if (nextToken() === SyntaxKind.AsKeyword) { + // https://github.com/microsoft/TypeScript/issues/44466 return Tristate.False; } + return Tristate.True; } - const first = token(); - const second = nextToken(); - - if (first === SyntaxKind.OpenParenToken) { - if (second === SyntaxKind.CloseParenToken) { - // Simple cases: "() =>", "(): ", and "() {". - // This is an arrow function with no parameters. - // The last one is not actually an arrow function, - // but this is probably what the user intended. - const third = nextToken(); - switch (third) { - case SyntaxKind.EqualsGreaterThanToken: - case SyntaxKind.ColonToken: - case SyntaxKind.OpenBraceToken: - return Tristate.True; - default: - return Tristate.False; - } - } - - // If encounter "([" or "({", this could be the start of a binding pattern. - // Examples: - // ([ x ]) => { } - // ({ x }) => { } - // ([ x ]) - // ({ x }) - if (second === SyntaxKind.OpenBracketToken || second === SyntaxKind.OpenBraceToken) { - return Tristate.Unknown; - } + // If we had "(" followed by something that's not an identifier, + // then this definitely doesn't look like a lambda. "this" is not + // valid, but we want to parse it and then give a semantic error. + if (!isIdentifier() && second !== SyntaxKind.ThisKeyword) { + return Tristate.False; + } - // Simple case: "(..." - // This is an arrow function with a rest parameter. - if (second === SyntaxKind.DotDotDotToken) { + switch (nextToken()) { + case SyntaxKind.ColonToken: + // If we have something like "(a:", then we must have a + // type-annotated parameter in an arrow function expression. return Tristate.True; - } - - // Check for "(xxx yyy", where xxx is a modifier and yyy is an identifier. This - // isn't actually allowed, but we want to treat it as a lambda so we can provide - // a good error message. - if (isModifierKind(second) && second !== SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsIdentifier)) { - if (nextToken() === SyntaxKind.AsKeyword) { - // https://github.com/microsoft/TypeScript/issues/44466 - return Tristate.False; + case SyntaxKind.QuestionToken: + nextToken(); + // If we have "(a?:" or "(a?," or "(a?=" or "(a?)" then it is definitely a lambda. + if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || token() === SyntaxKind.EqualsToken || token() === SyntaxKind.CloseParenToken) { + return Tristate.True; } - return Tristate.True; - } - - // If we had "(" followed by something that's not an identifier, - // then this definitely doesn't look like a lambda. "this" is not - // valid, but we want to parse it and then give a semantic error. - if (!isIdentifier() && second !== SyntaxKind.ThisKeyword) { + // Otherwise it is definitely not a lambda. return Tristate.False; - } + case SyntaxKind.CommaToken: + case SyntaxKind.EqualsToken: + case SyntaxKind.CloseParenToken: + // If we have "(a," or "(a=" or "(a)" this *could* be an arrow function + return Tristate.Unknown; + } + // It is definitely not an arrow function + return Tristate.False; + } + else { + Debug.assert(first === SyntaxKind.LessThanToken); - switch (nextToken()) { - case SyntaxKind.ColonToken: - // If we have something like "(a:", then we must have a - // type-annotated parameter in an arrow function expression. - return Tristate.True; - case SyntaxKind.QuestionToken: - nextToken(); - // If we have "(a?:" or "(a?," or "(a?=" or "(a?)" then it is definitely a lambda. - if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || token() === SyntaxKind.EqualsToken || token() === SyntaxKind.CloseParenToken) { - return Tristate.True; - } - // Otherwise it is definitely not a lambda. - return Tristate.False; - case SyntaxKind.CommaToken: - case SyntaxKind.EqualsToken: - case SyntaxKind.CloseParenToken: - // If we have "(a," or "(a=" or "(a)" this *could* be an arrow function - return Tristate.Unknown; - } - // It is definitely not an arrow function + // If we have "<" not followed by an identifier, + // then this definitely is not an arrow function. + if (!isIdentifier()) { return Tristate.False; } - else { - Debug.assert(first === SyntaxKind.LessThanToken); - - // If we have "<" not followed by an identifier, - // then this definitely is not an arrow function. - if (!isIdentifier()) { - return Tristate.False; - } - // JSX overrides - if (languageVariant === LanguageVariant.JSX) { - const isArrowFunctionInJsx = lookAhead(() => { - const third = nextToken(); - if (third === SyntaxKind.ExtendsKeyword) { - const fourth = nextToken(); - switch (fourth) { - case SyntaxKind.EqualsToken: - case SyntaxKind.GreaterThanToken: - return false; - default: - return true; - } - } - else if (third === SyntaxKind.CommaToken || third === SyntaxKind.EqualsToken) { - return true; + // JSX overrides + if (languageVariant === LanguageVariant.JSX) { + const isArrowFunctionInJsx = lookAhead(() => { + const third = nextToken(); + if (third === SyntaxKind.ExtendsKeyword) { + const fourth = nextToken(); + switch (fourth) { + case SyntaxKind.EqualsToken: + case SyntaxKind.GreaterThanToken: + return false; + default: + return true; } - return false; - }); - - if (isArrowFunctionInJsx) { - return Tristate.True; } + else if (third === SyntaxKind.CommaToken || third === SyntaxKind.EqualsToken) { + return true; + } + return false; + }); - return Tristate.False; + if (isArrowFunctionInJsx) { + return Tristate.True; } - // This *could* be a parenthesized arrow function. - return Tristate.Unknown; - } - } - - function parsePossibleParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction: boolean): ArrowFunction | undefined { - const tokenPos = scanner.getTokenPos(); - if (notParenthesizedArrow?.has(tokenPos)) { - return undefined; - } - - const result = parseParenthesizedArrowFunctionExpression(/*allowAmbiguity*/ false, allowReturnTypeInArrowFunction); - if (!result) { - (notParenthesizedArrow || (notParenthesizedArrow = new Set())).add(tokenPos); + return Tristate.False; } - return result; + // This *could* be a parenthesized arrow function. + return Tristate.Unknown; } + } - function tryParseAsyncSimpleArrowFunctionExpression(allowReturnTypeInArrowFunction: boolean): ArrowFunction | undefined { - // We do a check here so that we won't be doing unnecessarily call to "lookAhead" - if (token() === SyntaxKind.AsyncKeyword) { - if (lookAhead(isUnParenthesizedAsyncArrowFunctionWorker) === Tristate.True) { - const pos = getNodePos(); - const asyncModifier = parseModifiersForArrowFunction(); - const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); - return parseSimpleArrowFunctionExpression(pos, expr as Identifier, allowReturnTypeInArrowFunction, asyncModifier); - } - } + function parsePossibleParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction: boolean): ArrowFunction | undefined { + const tokenPos = scanner.getTokenPos(); + if (notParenthesizedArrow?.has(tokenPos)) { return undefined; } - function isUnParenthesizedAsyncArrowFunctionWorker(): Tristate { - // AsyncArrowFunctionExpression: - // 1) async[no LineTerminator here]AsyncArrowBindingIdentifier[?Yield][no LineTerminator here]=>AsyncConciseBody[?In] - // 2) CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await][no LineTerminator here]=>AsyncConciseBody[?In] - if (token() === SyntaxKind.AsyncKeyword) { - nextToken(); - // If the "async" is followed by "=>" token then it is not a beginning of an async arrow-function - // but instead a simple arrow-function which will be parsed inside "parseAssignmentExpressionOrHigher" - if (scanner.hasPrecedingLineBreak() || token() === SyntaxKind.EqualsGreaterThanToken) { - return Tristate.False; - } - // Check for un-parenthesized AsyncArrowFunction + const result = parseParenthesizedArrowFunctionExpression(/*allowAmbiguity*/ false, allowReturnTypeInArrowFunction); + if (!result) { + (notParenthesizedArrow || (notParenthesizedArrow = new Set())).add(tokenPos); + } + + return result; + } + + function tryParseAsyncSimpleArrowFunctionExpression(allowReturnTypeInArrowFunction: boolean): ArrowFunction | undefined { + // We do a check here so that we won't be doing unnecessarily call to "lookAhead" + if (token() === SyntaxKind.AsyncKeyword) { + if (lookAhead(isUnParenthesizedAsyncArrowFunctionWorker) === Tristate.True) { + const pos = getNodePos(); + const asyncModifier = parseModifiersForArrowFunction(); const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); - if (!scanner.hasPrecedingLineBreak() && expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { - return Tristate.True; - } + return parseSimpleArrowFunctionExpression(pos, expr as Identifier, allowReturnTypeInArrowFunction, asyncModifier); } + } + return undefined; + } - return Tristate.False; + function isUnParenthesizedAsyncArrowFunctionWorker(): Tristate { + // AsyncArrowFunctionExpression: + // 1) async[no LineTerminator here]AsyncArrowBindingIdentifier[?Yield][no LineTerminator here]=>AsyncConciseBody[?In] + // 2) CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await][no LineTerminator here]=>AsyncConciseBody[?In] + if (token() === SyntaxKind.AsyncKeyword) { + nextToken(); + // If the "async" is followed by "=>" token then it is not a beginning of an async arrow-function + // but instead a simple arrow-function which will be parsed inside "parseAssignmentExpressionOrHigher" + if (scanner.hasPrecedingLineBreak() || token() === SyntaxKind.EqualsGreaterThanToken) { + return Tristate.False; + } + // Check for un-parenthesized AsyncArrowFunction + const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); + if (!scanner.hasPrecedingLineBreak() && expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { + return Tristate.True; + } } - function parseParenthesizedArrowFunctionExpression(allowAmbiguity: boolean, allowReturnTypeInArrowFunction: boolean): ArrowFunction | undefined { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const modifiers = parseModifiersForArrowFunction(); - const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; - // Arrow functions are never generators. - // - // If we're speculatively parsing a signature for a parenthesized arrow function, then - // we have to have a complete parameter list. Otherwise we might see something like - // a => (b => c) - // And think that "(b =>" was actually a parenthesized arrow function with a missing - // close paren. - const typeParameters = parseTypeParameters(); + return Tristate.False; + } - let parameters: NodeArray; - if (!parseExpected(SyntaxKind.OpenParenToken)) { - if (!allowAmbiguity) { + function parseParenthesizedArrowFunctionExpression(allowAmbiguity: boolean, allowReturnTypeInArrowFunction: boolean): ArrowFunction | undefined { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const modifiers = parseModifiersForArrowFunction(); + const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; + // Arrow functions are never generators. + // + // If we're speculatively parsing a signature for a parenthesized arrow function, then + // we have to have a complete parameter list. Otherwise we might see something like + // a => (b => c) + // And think that "(b =>" was actually a parenthesized arrow function with a missing + // close paren. + const typeParameters = parseTypeParameters(); + + let parameters: NodeArray; + if (!parseExpected(SyntaxKind.OpenParenToken)) { + if (!allowAmbiguity) { + return undefined; + } + parameters = createMissingList(); + } + else { + if (!allowAmbiguity) { + const maybeParameters = parseParametersWorker(isAsync, allowAmbiguity); + if (!maybeParameters) { return undefined; } - parameters = createMissingList(); + parameters = maybeParameters; } else { - if (!allowAmbiguity) { - const maybeParameters = parseParametersWorker(isAsync, allowAmbiguity); - if (!maybeParameters) { - return undefined; - } - parameters = maybeParameters; - } - else { - parameters = parseParametersWorker(isAsync, allowAmbiguity); - } - if (!parseExpected(SyntaxKind.CloseParenToken) && !allowAmbiguity) { - return undefined; - } + parameters = parseParametersWorker(isAsync, allowAmbiguity); } - - const hasReturnColon = token() === SyntaxKind.ColonToken; - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - if (type && !allowAmbiguity && typeHasArrowFunctionBlockingParseError(type)) { + if (!parseExpected(SyntaxKind.CloseParenToken) && !allowAmbiguity) { return undefined; } + } - // Parsing a signature isn't enough. - // Parenthesized arrow signatures often look like other valid expressions. - // For instance: - // - "(x = 10)" is an assignment expression parsed as a signature with a default parameter value. - // - "(x,y)" is a comma expression parsed as a signature with two parameters. - // - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation. - // - "a ? (b): function() {}" will too, since function() is a valid JSDoc function type. - // - "a ? (b): (function() {})" as well, but inside of a parenthesized type with an arbitrary amount of nesting. - // - // So we need just a bit of lookahead to ensure that it can only be a signature. - - let unwrappedType = type; - while (unwrappedType?.kind === SyntaxKind.ParenthesizedType) { - unwrappedType = (unwrappedType as ParenthesizedTypeNode).type; // Skip parens if need be - } - - const hasJSDocFunctionType = unwrappedType && isJSDocFunctionType(unwrappedType); - if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && (hasJSDocFunctionType || token() !== SyntaxKind.OpenBraceToken)) { - // Returning undefined here will cause our caller to rewind to where we started from. - return undefined; - } + const hasReturnColon = token() === SyntaxKind.ColonToken; + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + if (type && !allowAmbiguity && typeHasArrowFunctionBlockingParseError(type)) { + return undefined; + } - // If we have an arrow, then try to parse the body. Even if not, try to parse if we - // have an opening brace, just in case we're in an error state. - const lastToken = token(); - const equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); - const body = (lastToken === SyntaxKind.EqualsGreaterThanToken || lastToken === SyntaxKind.OpenBraceToken) - ? parseArrowFunctionExpressionBody(some(modifiers, isAsyncModifier), allowReturnTypeInArrowFunction) - : parseIdentifier(); + // Parsing a signature isn't enough. + // Parenthesized arrow signatures often look like other valid expressions. + // For instance: + // - "(x = 10)" is an assignment expression parsed as a signature with a default parameter value. + // - "(x,y)" is a comma expression parsed as a signature with two parameters. + // - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation. + // - "a ? (b): function() {}" will too, since function() is a valid JSDoc function type. + // - "a ? (b): (function() {})" as well, but inside of a parenthesized type with an arbitrary amount of nesting. + // + // So we need just a bit of lookahead to ensure that it can only be a signature. - // Given: - // x ? y => ({ y }) : z => ({ z }) - // We try to parse the body of the first arrow function by looking at: - // ({ y }) : z => ({ z }) - // This is a valid arrow function with "z" as the return type. - // - // But, if we're in the true side of a conditional expression, this colon - // terminates the expression, so we cannot allow a return type if we aren't - // certain whether or not the preceding text was parsed as a parameter list. - // - // For example, - // a() ? (b: number, c?: string): void => d() : e - // is determined by isParenthesizedArrowFunctionExpression to unambiguously - // be an arrow expression, so we allow a return type. - if (!allowReturnTypeInArrowFunction && hasReturnColon) { - // However, if the arrow function we were able to parse is followed by another colon - // as in: - // a ? (x): string => x : null - // Then allow the arrow function, and treat the second colon as terminating - // the conditional expression. It's okay to do this because this code would - // be a syntax error in JavaScript (as the second colon shouldn't be there). - if (token() !== SyntaxKind.ColonToken) { - return undefined; - } - } + let unwrappedType = type; + while (unwrappedType?.kind === SyntaxKind.ParenthesizedType) { + unwrappedType = (unwrappedType as ParenthesizedTypeNode).type; // Skip parens if need be + } - const node = factory.createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body); - return withJSDoc(finishNode(node, pos), hasJSDoc); + const hasJSDocFunctionType = unwrappedType && isJSDocFunctionType(unwrappedType); + if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && (hasJSDocFunctionType || token() !== SyntaxKind.OpenBraceToken)) { + // Returning undefined here will cause our caller to rewind to where we started from. + return undefined; } - function parseArrowFunctionExpressionBody(isAsync: boolean, allowReturnTypeInArrowFunction: boolean): Block | Expression { - if (token() === SyntaxKind.OpenBraceToken) { - return parseFunctionBlock(isAsync ? SignatureFlags.Await : SignatureFlags.None); - } + // If we have an arrow, then try to parse the body. Even if not, try to parse if we + // have an opening brace, just in case we're in an error state. + const lastToken = token(); + const equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); + const body = (lastToken === SyntaxKind.EqualsGreaterThanToken || lastToken === SyntaxKind.OpenBraceToken) + ? parseArrowFunctionExpressionBody(some(modifiers, isAsyncModifier), allowReturnTypeInArrowFunction) + : parseIdentifier(); - if (token() !== SyntaxKind.SemicolonToken && - token() !== SyntaxKind.FunctionKeyword && - token() !== SyntaxKind.ClassKeyword && - isStartOfStatement() && - !isStartOfExpressionStatement()) { - // Check if we got a plain statement (i.e. no expression-statements, no function/class expressions/declarations) - // - // Here we try to recover from a potential error situation in the case where the - // user meant to supply a block. For example, if the user wrote: - // - // a => - // let v = 0; - // } - // - // they may be missing an open brace. Check to see if that's the case so we can - // try to recover better. If we don't do this, then the next close curly we see may end - // up preemptively closing the containing construct. - // - // Note: even when 'IgnoreMissingOpenBrace' is passed, parseBody will still error. - return parseFunctionBlock(SignatureFlags.IgnoreMissingOpenBrace | (isAsync ? SignatureFlags.Await : SignatureFlags.None)); + // Given: + // x ? y => ({ y }) : z => ({ z }) + // We try to parse the body of the first arrow function by looking at: + // ({ y }) : z => ({ z }) + // This is a valid arrow function with "z" as the return type. + // + // But, if we're in the true side of a conditional expression, this colon + // terminates the expression, so we cannot allow a return type if we aren't + // certain whether or not the preceding text was parsed as a parameter list. + // + // For example, + // a() ? (b: number, c?: string): void => d() : e + // is determined by isParenthesizedArrowFunctionExpression to unambiguously + // be an arrow expression, so we allow a return type. + if (!allowReturnTypeInArrowFunction && hasReturnColon) { + // However, if the arrow function we were able to parse is followed by another colon + // as in: + // a ? (x): string => x : null + // Then allow the arrow function, and treat the second colon as terminating + // the conditional expression. It's okay to do this because this code would + // be a syntax error in JavaScript (as the second colon shouldn't be there). + if (token() !== SyntaxKind.ColonToken) { + return undefined; } - - const savedTopLevel = topLevel; - topLevel = false; - const node = isAsync - ? doInAwaitContext(() => parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction)) - : doOutsideOfAwaitContext(() => parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction)); - topLevel = savedTopLevel; - return node; } - function parseConditionalExpressionRest(leftOperand: Expression, pos: number, allowReturnTypeInArrowFunction: boolean): Expression { - // Note: we are passed in an expression which was produced from parseBinaryExpressionOrHigher. - const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - if (!questionToken) { - return leftOperand; - } + const node = factory.createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - // Note: we explicitly 'allowIn' in the whenTrue part of the condition expression, and - // we do not that for the 'whenFalse' part. - let colonToken; - return finishNode( - factory.createConditionalExpression( - leftOperand, - questionToken, - doOutsideOfContext(disallowInAndDecoratorContext, () => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ false)), - colonToken = parseExpectedToken(SyntaxKind.ColonToken), - nodeIsPresent(colonToken) - ? parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction) - : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)) - ), - pos - ); + function parseArrowFunctionExpressionBody(isAsync: boolean, allowReturnTypeInArrowFunction: boolean): Block | Expression { + if (token() === SyntaxKind.OpenBraceToken) { + return parseFunctionBlock(isAsync ? SignatureFlags.Await : SignatureFlags.None); } - function parseBinaryExpressionOrHigher(precedence: OperatorPrecedence): Expression { - const pos = getNodePos(); - const leftOperand = parseUnaryExpressionOrHigher(); - return parseBinaryExpressionRest(precedence, leftOperand, pos); + if (token() !== SyntaxKind.SemicolonToken && + token() !== SyntaxKind.FunctionKeyword && + token() !== SyntaxKind.ClassKeyword && + isStartOfStatement() && + !isStartOfExpressionStatement()) { + // Check if we got a plain statement (i.e. no expression-statements, no function/class expressions/declarations) + // + // Here we try to recover from a potential error situation in the case where the + // user meant to supply a block. For example, if the user wrote: + // + // a => + // let v = 0; + // } + // + // they may be missing an open brace. Check to see if that's the case so we can + // try to recover better. If we don't do this, then the next close curly we see may end + // up preemptively closing the containing construct. + // + // Note: even when 'IgnoreMissingOpenBrace' is passed, parseBody will still error. + return parseFunctionBlock(SignatureFlags.IgnoreMissingOpenBrace | (isAsync ? SignatureFlags.Await : SignatureFlags.None)); } - function isInOrOfKeyword(t: SyntaxKind) { - return t === SyntaxKind.InKeyword || t === SyntaxKind.OfKeyword; + const savedTopLevel = topLevel; + topLevel = false; + const node = isAsync + ? doInAwaitContext(() => parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction)) + : doOutsideOfAwaitContext(() => parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction)); + topLevel = savedTopLevel; + return node; + } + + function parseConditionalExpressionRest(leftOperand: Expression, pos: number, allowReturnTypeInArrowFunction: boolean): Expression { + // Note: we are passed in an expression which was produced from parseBinaryExpressionOrHigher. + const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + if (!questionToken) { + return leftOperand; } - function parseBinaryExpressionRest(precedence: OperatorPrecedence, leftOperand: Expression, pos: number): Expression { - while (true) { - // We either have a binary operator here, or we're finished. We call - // reScanGreaterToken so that we merge token sequences like > and = into >= - - reScanGreaterToken(); - const newPrecedence = getBinaryOperatorPrecedence(token()); - - // Check the precedence to see if we should "take" this operator - // - For left associative operator (all operator but **), consume the operator, - // recursively call the function below, and parse binaryExpression as a rightOperand - // of the caller if the new precedence of the operator is greater then or equal to the current precedence. - // For example: - // a - b - c; - // ^token; leftOperand = b. Return b to the caller as a rightOperand - // a * b - c - // ^token; leftOperand = b. Return b to the caller as a rightOperand - // a - b * c; - // ^token; leftOperand = b. Return b * c to the caller as a rightOperand - // - For right associative operator (**), consume the operator, recursively call the function - // and parse binaryExpression as a rightOperand of the caller if the new precedence of - // the operator is strictly grater than the current precedence - // For example: - // a ** b ** c; - // ^^token; leftOperand = b. Return b ** c to the caller as a rightOperand - // a - b ** c; - // ^^token; leftOperand = b. Return b ** c to the caller as a rightOperand - // a ** b - c - // ^token; leftOperand = b. Return b to the caller as a rightOperand - const consumeCurrentOperator = token() === SyntaxKind.AsteriskAsteriskToken ? - newPrecedence >= precedence : - newPrecedence > precedence; - - if (!consumeCurrentOperator) { - break; - } + // Note: we explicitly 'allowIn' in the whenTrue part of the condition expression, and + // we do not that for the 'whenFalse' part. + let colonToken; + return finishNode( + factory.createConditionalExpression( + leftOperand, + questionToken, + doOutsideOfContext(disallowInAndDecoratorContext, () => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ false)), + colonToken = parseExpectedToken(SyntaxKind.ColonToken), + nodeIsPresent(colonToken) + ? parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction) + : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)) + ), + pos + ); + } - if (token() === SyntaxKind.InKeyword && inDisallowInContext()) { - break; - } + function parseBinaryExpressionOrHigher(precedence: OperatorPrecedence): Expression { + const pos = getNodePos(); + const leftOperand = parseUnaryExpressionOrHigher(); + return parseBinaryExpressionRest(precedence, leftOperand, pos); + } - if (token() === SyntaxKind.AsKeyword || token() === SyntaxKind.SatisfiesKeyword) { - // Make sure we *do* perform ASI for constructs like this: - // var x = foo - // as (Bar) - // This should be parsed as an initialized variable, followed - // by a function call to 'as' with the argument 'Bar' - if (scanner.hasPrecedingLineBreak()) { - break; - } - else { - const keywordKind = token(); - nextToken(); - leftOperand = keywordKind === SyntaxKind.SatisfiesKeyword ? makeSatisfiesExpression(leftOperand, parseType()) : - makeAsExpression(leftOperand, parseType()); - } + function isInOrOfKeyword(t: SyntaxKind) { + return t === SyntaxKind.InKeyword || t === SyntaxKind.OfKeyword; + } + + function parseBinaryExpressionRest(precedence: OperatorPrecedence, leftOperand: Expression, pos: number): Expression { + while (true) { + // We either have a binary operator here, or we're finished. We call + // reScanGreaterToken so that we merge token sequences like > and = into >= + + reScanGreaterToken(); + const newPrecedence = getBinaryOperatorPrecedence(token()); + + // Check the precedence to see if we should "take" this operator + // - For left associative operator (all operator but **), consume the operator, + // recursively call the function below, and parse binaryExpression as a rightOperand + // of the caller if the new precedence of the operator is greater then or equal to the current precedence. + // For example: + // a - b - c; + // ^token; leftOperand = b. Return b to the caller as a rightOperand + // a * b - c + // ^token; leftOperand = b. Return b to the caller as a rightOperand + // a - b * c; + // ^token; leftOperand = b. Return b * c to the caller as a rightOperand + // - For right associative operator (**), consume the operator, recursively call the function + // and parse binaryExpression as a rightOperand of the caller if the new precedence of + // the operator is strictly grater than the current precedence + // For example: + // a ** b ** c; + // ^^token; leftOperand = b. Return b ** c to the caller as a rightOperand + // a - b ** c; + // ^^token; leftOperand = b. Return b ** c to the caller as a rightOperand + // a ** b - c + // ^token; leftOperand = b. Return b to the caller as a rightOperand + const consumeCurrentOperator = token() === SyntaxKind.AsteriskAsteriskToken ? + newPrecedence >= precedence : + newPrecedence > precedence; + + if (!consumeCurrentOperator) { + break; + } + + if (token() === SyntaxKind.InKeyword && inDisallowInContext()) { + break; + } + + if (token() === SyntaxKind.AsKeyword || token() === SyntaxKind.SatisfiesKeyword) { + // Make sure we *do* perform ASI for constructs like this: + // var x = foo + // as (Bar) + // This should be parsed as an initialized variable, followed + // by a function call to 'as' with the argument 'Bar' + if (scanner.hasPrecedingLineBreak()) { + break; } else { - leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence), pos); + const keywordKind = token(); + nextToken(); + leftOperand = keywordKind === SyntaxKind.SatisfiesKeyword ? makeSatisfiesExpression(leftOperand, parseType()) : + makeAsExpression(leftOperand, parseType()); } } - - return leftOperand; + else { + leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence), pos); + } } - function isBinaryOperator() { - if (inDisallowInContext() && token() === SyntaxKind.InKeyword) { - return false; - } + return leftOperand; + } - return getBinaryOperatorPrecedence(token()) > 0; + function isBinaryOperator() { + if (inDisallowInContext() && token() === SyntaxKind.InKeyword) { + return false; } - function makeSatisfiesExpression(left: Expression, right: TypeNode): SatisfiesExpression { - return finishNode(factory.createSatisfiesExpression(left, right), left.pos); - } + return getBinaryOperatorPrecedence(token()) > 0; + } - function makeBinaryExpression(left: Expression, operatorToken: BinaryOperatorToken, right: Expression, pos: number): BinaryExpression { - return finishNode(factory.createBinaryExpression(left, operatorToken, right), pos); - } + function makeSatisfiesExpression(left: Expression, right: TypeNode): SatisfiesExpression { + return finishNode(factory.createSatisfiesExpression(left, right), left.pos); + } - function makeAsExpression(left: Expression, right: TypeNode): AsExpression { - return finishNode(factory.createAsExpression(left, right), left.pos); - } + function makeBinaryExpression(left: Expression, operatorToken: BinaryOperatorToken, right: Expression, pos: number): BinaryExpression { + return finishNode(factory.createBinaryExpression(left, operatorToken, right), pos); + } - function parsePrefixUnaryExpression() { - const pos = getNodePos(); - return finishNode(factory.createPrefixUnaryExpression(token() as PrefixUnaryOperator, nextTokenAnd(parseSimpleUnaryExpression)), pos); - } + function makeAsExpression(left: Expression, right: TypeNode): AsExpression { + return finishNode(factory.createAsExpression(left, right), left.pos); + } - function parseDeleteExpression() { - const pos = getNodePos(); - return finishNode(factory.createDeleteExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); - } + function parsePrefixUnaryExpression() { + const pos = getNodePos(); + return finishNode(factory.createPrefixUnaryExpression(token() as PrefixUnaryOperator, nextTokenAnd(parseSimpleUnaryExpression)), pos); + } - function parseTypeOfExpression() { - const pos = getNodePos(); - return finishNode(factory.createTypeOfExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); - } + function parseDeleteExpression() { + const pos = getNodePos(); + return finishNode(factory.createDeleteExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); + } - function parseVoidExpression() { - const pos = getNodePos(); - return finishNode(factory.createVoidExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); - } + function parseTypeOfExpression() { + const pos = getNodePos(); + return finishNode(factory.createTypeOfExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); + } - function isAwaitExpression(): boolean { - if (token() === SyntaxKind.AwaitKeyword) { - if (inAwaitContext()) { - return true; - } + function parseVoidExpression() { + const pos = getNodePos(); + return finishNode(factory.createVoidExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); + } - // here we are using similar heuristics as 'isYieldExpression' - return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); + function isAwaitExpression(): boolean { + if (token() === SyntaxKind.AwaitKeyword) { + if (inAwaitContext()) { + return true; } - return false; + // here we are using similar heuristics as 'isYieldExpression' + return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); } - function parseAwaitExpression() { - const pos = getNodePos(); - return finishNode(factory.createAwaitExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); - } + return false; + } + + function parseAwaitExpression() { + const pos = getNodePos(); + return finishNode(factory.createAwaitExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); + } + /** + * Parse ES7 exponential expression and await expression + * + * ES7 ExponentiationExpression: + * 1) UnaryExpression[?Yield] + * 2) UpdateExpression[?Yield] ** ExponentiationExpression[?Yield] + * + */ + function parseUnaryExpressionOrHigher(): UnaryExpression | BinaryExpression { /** - * Parse ES7 exponential expression and await expression - * - * ES7 ExponentiationExpression: - * 1) UnaryExpression[?Yield] - * 2) UpdateExpression[?Yield] ** ExponentiationExpression[?Yield] - * + * ES7 UpdateExpression: + * 1) LeftHandSideExpression[?Yield] + * 2) LeftHandSideExpression[?Yield][no LineTerminator here]++ + * 3) LeftHandSideExpression[?Yield][no LineTerminator here]-- + * 4) ++UnaryExpression[?Yield] + * 5) --UnaryExpression[?Yield] */ - function parseUnaryExpressionOrHigher(): UnaryExpression | BinaryExpression { - /** - * ES7 UpdateExpression: - * 1) LeftHandSideExpression[?Yield] - * 2) LeftHandSideExpression[?Yield][no LineTerminator here]++ - * 3) LeftHandSideExpression[?Yield][no LineTerminator here]-- - * 4) ++UnaryExpression[?Yield] - * 5) --UnaryExpression[?Yield] - */ - if (isUpdateExpression()) { - const pos = getNodePos(); - const updateExpression = parseUpdateExpression(); - return token() === SyntaxKind.AsteriskAsteriskToken ? - parseBinaryExpressionRest(getBinaryOperatorPrecedence(token()), updateExpression, pos) as BinaryExpression : - updateExpression; - } - - /** - * ES7 UnaryExpression: - * 1) UpdateExpression[?yield] - * 2) delete UpdateExpression[?yield] - * 3) void UpdateExpression[?yield] - * 4) typeof UpdateExpression[?yield] - * 5) + UpdateExpression[?yield] - * 6) - UpdateExpression[?yield] - * 7) ~ UpdateExpression[?yield] - * 8) ! UpdateExpression[?yield] - */ - const unaryOperator = token(); - const simpleUnaryExpression = parseSimpleUnaryExpression(); - if (token() === SyntaxKind.AsteriskAsteriskToken) { - const pos = skipTrivia(sourceText, simpleUnaryExpression.pos); - const { end } = simpleUnaryExpression; - if (simpleUnaryExpression.kind === SyntaxKind.TypeAssertionExpression) { - parseErrorAt(pos, end, Diagnostics.A_type_assertion_expression_is_not_allowed_in_the_left_hand_side_of_an_exponentiation_expression_Consider_enclosing_the_expression_in_parentheses); - } - else { - parseErrorAt(pos, end, Diagnostics.An_unary_expression_with_the_0_operator_is_not_allowed_in_the_left_hand_side_of_an_exponentiation_expression_Consider_enclosing_the_expression_in_parentheses, tokenToString(unaryOperator)); - } - } - return simpleUnaryExpression; + if (isUpdateExpression()) { + const pos = getNodePos(); + const updateExpression = parseUpdateExpression(); + return token() === SyntaxKind.AsteriskAsteriskToken ? + parseBinaryExpressionRest(getBinaryOperatorPrecedence(token()), updateExpression, pos) as BinaryExpression : + updateExpression; } /** - * Parse ES7 simple-unary expression or higher: - * * ES7 UnaryExpression: * 1) UpdateExpression[?yield] - * 2) delete UnaryExpression[?yield] - * 3) void UnaryExpression[?yield] - * 4) typeof UnaryExpression[?yield] - * 5) + UnaryExpression[?yield] - * 6) - UnaryExpression[?yield] - * 7) ~ UnaryExpression[?yield] - * 8) ! UnaryExpression[?yield] - * 9) [+Await] await UnaryExpression[?yield] + * 2) delete UpdateExpression[?yield] + * 3) void UpdateExpression[?yield] + * 4) typeof UpdateExpression[?yield] + * 5) + UpdateExpression[?yield] + * 6) - UpdateExpression[?yield] + * 7) ~ UpdateExpression[?yield] + * 8) ! UpdateExpression[?yield] */ - function parseSimpleUnaryExpression(): UnaryExpression { - switch (token()) { - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - case SyntaxKind.ExclamationToken: - return parsePrefixUnaryExpression(); - case SyntaxKind.DeleteKeyword: - return parseDeleteExpression(); - case SyntaxKind.TypeOfKeyword: - return parseTypeOfExpression(); - case SyntaxKind.VoidKeyword: - return parseVoidExpression(); - case SyntaxKind.LessThanToken: - // This is modified UnaryExpression grammar in TypeScript - // UnaryExpression (modified): - // < type > UnaryExpression - return parseTypeAssertion(); - case SyntaxKind.AwaitKeyword: - if (isAwaitExpression()) { - return parseAwaitExpression(); - } - // falls through - default: - return parseUpdateExpression(); + const unaryOperator = token(); + const simpleUnaryExpression = parseSimpleUnaryExpression(); + if (token() === SyntaxKind.AsteriskAsteriskToken) { + const pos = skipTrivia(sourceText, simpleUnaryExpression.pos); + const { end } = simpleUnaryExpression; + if (simpleUnaryExpression.kind === SyntaxKind.TypeAssertionExpression) { + parseErrorAt(pos, end, Diagnostics.A_type_assertion_expression_is_not_allowed_in_the_left_hand_side_of_an_exponentiation_expression_Consider_enclosing_the_expression_in_parentheses); } - } - - /** - * Check if the current token can possibly be an ES7 increment expression. - * - * ES7 UpdateExpression: - * LeftHandSideExpression[?Yield] - * LeftHandSideExpression[?Yield][no LineTerminator here]++ - * LeftHandSideExpression[?Yield][no LineTerminator here]-- - * ++LeftHandSideExpression[?Yield] - * --LeftHandSideExpression[?Yield] - */ - function isUpdateExpression(): boolean { - // This function is called inside parseUnaryExpression to decide - // whether to call parseSimpleUnaryExpression or call parseUpdateExpression directly - switch (token()) { - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - case SyntaxKind.ExclamationToken: - case SyntaxKind.DeleteKeyword: - case SyntaxKind.TypeOfKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.AwaitKeyword: - return false; - case SyntaxKind.LessThanToken: - // If we are not in JSX context, we are parsing TypeAssertion which is an UnaryExpression - if (languageVariant !== LanguageVariant.JSX) { - return false; - } - // We are in JSX context and the token is part of JSXElement. - // falls through - default: - return true; + else { + parseErrorAt(pos, end, Diagnostics.An_unary_expression_with_the_0_operator_is_not_allowed_in_the_left_hand_side_of_an_exponentiation_expression_Consider_enclosing_the_expression_in_parentheses, tokenToString(unaryOperator)); } } + return simpleUnaryExpression; + } - /** - * Parse ES7 UpdateExpression. UpdateExpression is used instead of ES6's PostFixExpression. - * - * ES7 UpdateExpression[yield]: - * 1) LeftHandSideExpression[?yield] - * 2) LeftHandSideExpression[?yield] [[no LineTerminator here]]++ - * 3) LeftHandSideExpression[?yield] [[no LineTerminator here]]-- - * 4) ++LeftHandSideExpression[?yield] - * 5) --LeftHandSideExpression[?yield] - * In TypeScript (2), (3) are parsed as PostfixUnaryExpression. (4), (5) are parsed as PrefixUnaryExpression - */ - function parseUpdateExpression(): UpdateExpression { - if (token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) { - const pos = getNodePos(); - return finishNode(factory.createPrefixUnaryExpression(token() as PrefixUnaryOperator, nextTokenAnd(parseLeftHandSideExpressionOrHigher)), pos); - } - else if (languageVariant === LanguageVariant.JSX && token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsIdentifierOrKeywordOrGreaterThan)) { - // JSXElement is part of primaryExpression - return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true); - } - - const expression = parseLeftHandSideExpressionOrHigher(); - - Debug.assert(isLeftHandSideExpression(expression)); - if ((token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) && !scanner.hasPrecedingLineBreak()) { - const operator = token() as PostfixUnaryOperator; - nextToken(); - return finishNode(factory.createPostfixUnaryExpression(expression, operator), expression.pos); - } - - return expression; + /** + * Parse ES7 simple-unary expression or higher: + * + * ES7 UnaryExpression: + * 1) UpdateExpression[?yield] + * 2) delete UnaryExpression[?yield] + * 3) void UnaryExpression[?yield] + * 4) typeof UnaryExpression[?yield] + * 5) + UnaryExpression[?yield] + * 6) - UnaryExpression[?yield] + * 7) ~ UnaryExpression[?yield] + * 8) ! UnaryExpression[?yield] + * 9) [+Await] await UnaryExpression[?yield] + */ + function parseSimpleUnaryExpression(): UnaryExpression { + switch (token()) { + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + case SyntaxKind.ExclamationToken: + return parsePrefixUnaryExpression(); + case SyntaxKind.DeleteKeyword: + return parseDeleteExpression(); + case SyntaxKind.TypeOfKeyword: + return parseTypeOfExpression(); + case SyntaxKind.VoidKeyword: + return parseVoidExpression(); + case SyntaxKind.LessThanToken: + // This is modified UnaryExpression grammar in TypeScript + // UnaryExpression (modified): + // < type > UnaryExpression + return parseTypeAssertion(); + case SyntaxKind.AwaitKeyword: + if (isAwaitExpression()) { + return parseAwaitExpression(); + } + // falls through + default: + return parseUpdateExpression(); } + } - function parseLeftHandSideExpressionOrHigher(): LeftHandSideExpression { - // Original Ecma: - // LeftHandSideExpression: See 11.2 - // NewExpression - // CallExpression - // - // Our simplification: - // - // LeftHandSideExpression: See 11.2 - // MemberExpression - // CallExpression - // - // See comment in parseMemberExpressionOrHigher on how we replaced NewExpression with - // MemberExpression to make our lives easier. - // - // to best understand the below code, it's important to see how CallExpression expands - // out into its own productions: - // - // CallExpression: - // MemberExpression Arguments - // CallExpression Arguments - // CallExpression[Expression] - // CallExpression.IdentifierName - // import (AssignmentExpression) - // super Arguments - // super.IdentifierName - // - // Because of the recursion in these calls, we need to bottom out first. There are three - // bottom out states we can run into: 1) We see 'super' which must start either of - // the last two CallExpression productions. 2) We see 'import' which must start import call. - // 3)we have a MemberExpression which either completes the LeftHandSideExpression, - // or starts the beginning of the first four CallExpression productions. - const pos = getNodePos(); - let expression: MemberExpression; - if (token() === SyntaxKind.ImportKeyword) { - if (lookAhead(nextTokenIsOpenParenOrLessThan)) { - // We don't want to eagerly consume all import keyword as import call expression so we look ahead to find "(" - // For example: - // var foo3 = require("subfolder - // import * as foo1 from "module-from-node - // We want this import to be a statement rather than import call expression - sourceFlags |= NodeFlags.PossiblyContainsDynamicImport; - expression = parseTokenNode(); - } - else if (lookAhead(nextTokenIsDot)) { - // This is an 'import.*' metaproperty (i.e. 'import.meta') - nextToken(); // advance past the 'import' - nextToken(); // advance past the dot - expression = finishNode(factory.createMetaProperty(SyntaxKind.ImportKeyword, parseIdentifierName()), pos); - sourceFlags |= NodeFlags.PossiblyContainsImportMeta; - } - else { - expression = parseMemberExpressionOrHigher(); + /** + * Check if the current token can possibly be an ES7 increment expression. + * + * ES7 UpdateExpression: + * LeftHandSideExpression[?Yield] + * LeftHandSideExpression[?Yield][no LineTerminator here]++ + * LeftHandSideExpression[?Yield][no LineTerminator here]-- + * ++LeftHandSideExpression[?Yield] + * --LeftHandSideExpression[?Yield] + */ + function isUpdateExpression(): boolean { + // This function is called inside parseUnaryExpression to decide + // whether to call parseSimpleUnaryExpression or call parseUpdateExpression directly + switch (token()) { + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + case SyntaxKind.TildeToken: + case SyntaxKind.ExclamationToken: + case SyntaxKind.DeleteKeyword: + case SyntaxKind.TypeOfKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.AwaitKeyword: + return false; + case SyntaxKind.LessThanToken: + // If we are not in JSX context, we are parsing TypeAssertion which is an UnaryExpression + if (languageVariant !== LanguageVariant.JSX) { + return false; } - } - else { - expression = token() === SyntaxKind.SuperKeyword ? parseSuperExpression() : parseMemberExpressionOrHigher(); - } - - // Now, we *may* be complete. However, we might have consumed the start of a - // CallExpression or OptionalExpression. As such, we need to consume the rest - // of it here to be complete. - return parseCallExpressionRest(pos, expression); + // We are in JSX context and the token is part of JSXElement. + // falls through + default: + return true; } + } - function parseMemberExpressionOrHigher(): MemberExpression { - // Note: to make our lives simpler, we decompose the NewExpression productions and - // place ObjectCreationExpression and FunctionExpression into PrimaryExpression. - // like so: - // - // PrimaryExpression : See 11.1 - // this - // Identifier - // Literal - // ArrayLiteral - // ObjectLiteral - // (Expression) - // FunctionExpression - // new MemberExpression Arguments? - // - // MemberExpression : See 11.2 - // PrimaryExpression - // MemberExpression[Expression] - // MemberExpression.IdentifierName - // - // CallExpression : See 11.2 - // MemberExpression - // CallExpression Arguments - // CallExpression[Expression] - // CallExpression.IdentifierName - // - // Technically this is ambiguous. i.e. CallExpression defines: - // - // CallExpression: - // CallExpression Arguments - // - // If you see: "new Foo()" - // - // Then that could be treated as a single ObjectCreationExpression, or it could be - // treated as the invocation of "new Foo". We disambiguate that in code (to match - // the original grammar) by making sure that if we see an ObjectCreationExpression - // we always consume arguments if they are there. So we treat "new Foo()" as an - // object creation only, and not at all as an invocation. Another way to think - // about this is that for every "new" that we see, we will consume an argument list if - // it is there as part of the *associated* object creation node. Any additional - // argument lists we see, will become invocation expressions. - // - // Because there are no other places in the grammar now that refer to FunctionExpression - // or ObjectCreationExpression, it is safe to push down into the PrimaryExpression - // production. - // - // Because CallExpression and MemberExpression are left recursive, we need to bottom out - // of the recursion immediately. So we parse out a primary expression to start with. + /** + * Parse ES7 UpdateExpression. UpdateExpression is used instead of ES6's PostFixExpression. + * + * ES7 UpdateExpression[yield]: + * 1) LeftHandSideExpression[?yield] + * 2) LeftHandSideExpression[?yield] [[no LineTerminator here]]++ + * 3) LeftHandSideExpression[?yield] [[no LineTerminator here]]-- + * 4) ++LeftHandSideExpression[?yield] + * 5) --LeftHandSideExpression[?yield] + * In TypeScript (2), (3) are parsed as PostfixUnaryExpression. (4), (5) are parsed as PrefixUnaryExpression + */ + function parseUpdateExpression(): UpdateExpression { + if (token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) { const pos = getNodePos(); - const expression = parsePrimaryExpression(); - return parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true); + return finishNode(factory.createPrefixUnaryExpression(token() as PrefixUnaryOperator, nextTokenAnd(parseLeftHandSideExpressionOrHigher)), pos); + } + else if (languageVariant === LanguageVariant.JSX && token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsIdentifierOrKeywordOrGreaterThan)) { + // JSXElement is part of primaryExpression + return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true); } - function parseSuperExpression(): MemberExpression { - const pos = getNodePos(); - let expression = parseTokenNode(); - if (token() === SyntaxKind.LessThanToken) { - const startPos = getNodePos(); - const typeArguments = tryParse(parseTypeArgumentsInExpression); - if (typeArguments !== undefined) { - parseErrorAt(startPos, getNodePos(), Diagnostics.super_may_not_use_type_arguments); - if (!isTemplateStartOfTaggedTemplate()) { - expression = factory.createExpressionWithTypeArguments(expression, typeArguments); - } - } - } - - if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.DotToken || token() === SyntaxKind.OpenBracketToken) { - return expression; - } + const expression = parseLeftHandSideExpressionOrHigher(); - // If we have seen "super" it must be followed by '(' or '.'. - // If it wasn't then just try to parse out a '.' and report an error. - parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access); - // private names will never work with `super` (`super.#foo`), but that's a semantic error, not syntactic - return finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true)), pos); + Debug.assert(isLeftHandSideExpression(expression)); + if ((token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) && !scanner.hasPrecedingLineBreak()) { + const operator = token() as PostfixUnaryOperator; + nextToken(); + return finishNode(factory.createPostfixUnaryExpression(expression, operator), expression.pos); } - function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean, topInvalidNodePosition?: number, openingTag?: JsxOpeningElement | JsxOpeningFragment): JsxElement | JsxSelfClosingElement | JsxFragment { - const pos = getNodePos(); - const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext); - let result: JsxElement | JsxSelfClosingElement | JsxFragment; - if (opening.kind === SyntaxKind.JsxOpeningElement) { - let children = parseJsxChildren(opening); - let closingElement: JsxClosingElement; - - const lastChild: JsxChild | undefined = children[children.length - 1]; - if (lastChild?.kind === SyntaxKind.JsxElement - && !tagNamesAreEquivalent(lastChild.openingElement.tagName, lastChild.closingElement.tagName) - && tagNamesAreEquivalent(opening.tagName, lastChild.closingElement.tagName)) { - // when an unclosed JsxOpeningElement incorrectly parses its parent's JsxClosingElement, - // restructure (
(......
)) --> (
(......)
) - // (no need to error; the parent will error) - const end = lastChild.children.end; - const newLast = finishNode(factory.createJsxElement( - lastChild.openingElement, - lastChild.children, - finishNode(factory.createJsxClosingElement(finishNode(factory.createIdentifier(""), end, end)), end, end)), - lastChild.openingElement.pos, - end); - - children = createNodeArray([...children.slice(0, children.length - 1), newLast], children.pos, end); - closingElement = lastChild.closingElement; - } - else { - closingElement = parseJsxClosingElement(opening, inExpressionContext); - if (!tagNamesAreEquivalent(opening.tagName, closingElement.tagName)) { - if (openingTag && isJsxOpeningElement(openingTag) && tagNamesAreEquivalent(closingElement.tagName, openingTag.tagName)) { - // opening incorrectly matched with its parent's closing -- put error on opening - parseErrorAtRange(opening.tagName, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, opening.tagName)); - } - else { - // other opening/closing mismatches -- put error on closing - parseErrorAtRange(closingElement.tagName, Diagnostics.Expected_corresponding_JSX_closing_tag_for_0, getTextOfNodeFromSourceText(sourceText, opening.tagName)); - } - } - } - result = finishNode(factory.createJsxElement(opening, children, closingElement), pos); - } - else if (opening.kind === SyntaxKind.JsxOpeningFragment) { - result = finishNode(factory.createJsxFragment(opening, parseJsxChildren(opening), parseJsxClosingFragment(inExpressionContext)), pos); + return expression; + } + + function parseLeftHandSideExpressionOrHigher(): LeftHandSideExpression { + // Original Ecma: + // LeftHandSideExpression: See 11.2 + // NewExpression + // CallExpression + // + // Our simplification: + // + // LeftHandSideExpression: See 11.2 + // MemberExpression + // CallExpression + // + // See comment in parseMemberExpressionOrHigher on how we replaced NewExpression with + // MemberExpression to make our lives easier. + // + // to best understand the below code, it's important to see how CallExpression expands + // out into its own productions: + // + // CallExpression: + // MemberExpression Arguments + // CallExpression Arguments + // CallExpression[Expression] + // CallExpression.IdentifierName + // import (AssignmentExpression) + // super Arguments + // super.IdentifierName + // + // Because of the recursion in these calls, we need to bottom out first. There are three + // bottom out states we can run into: 1) We see 'super' which must start either of + // the last two CallExpression productions. 2) We see 'import' which must start import call. + // 3)we have a MemberExpression which either completes the LeftHandSideExpression, + // or starts the beginning of the first four CallExpression productions. + const pos = getNodePos(); + let expression: MemberExpression; + if (token() === SyntaxKind.ImportKeyword) { + if (lookAhead(nextTokenIsOpenParenOrLessThan)) { + // We don't want to eagerly consume all import keyword as import call expression so we look ahead to find "(" + // For example: + // var foo3 = require("subfolder + // import * as foo1 from "module-from-node + // We want this import to be a statement rather than import call expression + sourceFlags |= NodeFlags.PossiblyContainsDynamicImport; + expression = parseTokenNode(); + } + else if (lookAhead(nextTokenIsDot)) { + // This is an 'import.*' metaproperty (i.e. 'import.meta') + nextToken(); // advance past the 'import' + nextToken(); // advance past the dot + expression = finishNode(factory.createMetaProperty(SyntaxKind.ImportKeyword, parseIdentifierName()), pos); + sourceFlags |= NodeFlags.PossiblyContainsImportMeta; } else { - Debug.assert(opening.kind === SyntaxKind.JsxSelfClosingElement); - // Nothing else to do for self-closing elements - result = opening; - } - - // If the user writes the invalid code '
' in an expression context (i.e. not wrapped in - // an enclosing tag), we'll naively try to parse ^ this as a 'less than' operator and the remainder of the tag - // as garbage, which will cause the formatter to badly mangle the JSX. Perform a speculative parse of a JSX - // element if we see a < token so that we can wrap it in a synthetic binary expression so the formatter - // does less damage and we can report a better error. - // Since JSX elements are invalid < operands anyway, this lookahead parse will only occur in error scenarios - // of one sort or another. - if (inExpressionContext && token() === SyntaxKind.LessThanToken) { - const topBadPos = typeof topInvalidNodePosition === "undefined" ? result.pos : topInvalidNodePosition; - const invalidElement = tryParse(() => parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true, topBadPos)); - if (invalidElement) { - const operatorToken = createMissingNode(SyntaxKind.CommaToken, /*reportAtCurrentPosition*/ false); - setTextRangePosWidth(operatorToken, invalidElement.pos, 0); - parseErrorAt(skipTrivia(sourceText, topBadPos), invalidElement.end, Diagnostics.JSX_expressions_must_have_one_parent_element); - return finishNode(factory.createBinaryExpression(result, operatorToken as Token, invalidElement), pos) as Node as JsxElement; - } + expression = parseMemberExpressionOrHigher(); } - - return result; } - - function parseJsxText(): JsxText { - const pos = getNodePos(); - const node = factory.createJsxText(scanner.getTokenValue(), currentToken === SyntaxKind.JsxTextAllWhiteSpaces); - currentToken = scanner.scanJsxToken(); - return finishNode(node, pos); - } - - function parseJsxChild(openingTag: JsxOpeningElement | JsxOpeningFragment, token: JsxTokenSyntaxKind): JsxChild | undefined { - switch (token) { - case SyntaxKind.EndOfFileToken: - // If we hit EOF, issue the error at the tag that lacks the closing element - // rather than at the end of the file (which is useless) - if (isJsxOpeningFragment(openingTag)) { - parseErrorAtRange(openingTag, Diagnostics.JSX_fragment_has_no_corresponding_closing_tag); - } - else { - // We want the error span to cover only 'Foo.Bar' in < Foo.Bar > - // or to cover only 'Foo' in < Foo > - const tag = openingTag.tagName; - const start = skipTrivia(sourceText, tag.pos); - parseErrorAt(start, tag.end, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, openingTag.tagName)); - } - return undefined; - case SyntaxKind.LessThanSlashToken: - case SyntaxKind.ConflictMarkerTrivia: - return undefined; - case SyntaxKind.JsxText: - case SyntaxKind.JsxTextAllWhiteSpaces: - return parseJsxText(); - case SyntaxKind.OpenBraceToken: - return parseJsxExpression(/*inExpressionContext*/ false); - case SyntaxKind.LessThanToken: - return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ false, /*topInvalidNodePosition*/ undefined, openingTag); - default: - return Debug.assertNever(token); - } + else { + expression = token() === SyntaxKind.SuperKeyword ? parseSuperExpression() : parseMemberExpressionOrHigher(); } - function parseJsxChildren(openingTag: JsxOpeningElement | JsxOpeningFragment): NodeArray { - const list = []; - const listPos = getNodePos(); - const saveParsingContext = parsingContext; - parsingContext |= 1 << ParsingContext.JsxChildren; + // Now, we *may* be complete. However, we might have consumed the start of a + // CallExpression or OptionalExpression. As such, we need to consume the rest + // of it here to be complete. + return parseCallExpressionRest(pos, expression); + } + + function parseMemberExpressionOrHigher(): MemberExpression { + // Note: to make our lives simpler, we decompose the NewExpression productions and + // place ObjectCreationExpression and FunctionExpression into PrimaryExpression. + // like so: + // + // PrimaryExpression : See 11.1 + // this + // Identifier + // Literal + // ArrayLiteral + // ObjectLiteral + // (Expression) + // FunctionExpression + // new MemberExpression Arguments? + // + // MemberExpression : See 11.2 + // PrimaryExpression + // MemberExpression[Expression] + // MemberExpression.IdentifierName + // + // CallExpression : See 11.2 + // MemberExpression + // CallExpression Arguments + // CallExpression[Expression] + // CallExpression.IdentifierName + // + // Technically this is ambiguous. i.e. CallExpression defines: + // + // CallExpression: + // CallExpression Arguments + // + // If you see: "new Foo()" + // + // Then that could be treated as a single ObjectCreationExpression, or it could be + // treated as the invocation of "new Foo". We disambiguate that in code (to match + // the original grammar) by making sure that if we see an ObjectCreationExpression + // we always consume arguments if they are there. So we treat "new Foo()" as an + // object creation only, and not at all as an invocation. Another way to think + // about this is that for every "new" that we see, we will consume an argument list if + // it is there as part of the *associated* object creation node. Any additional + // argument lists we see, will become invocation expressions. + // + // Because there are no other places in the grammar now that refer to FunctionExpression + // or ObjectCreationExpression, it is safe to push down into the PrimaryExpression + // production. + // + // Because CallExpression and MemberExpression are left recursive, we need to bottom out + // of the recursion immediately. So we parse out a primary expression to start with. + const pos = getNodePos(); + const expression = parsePrimaryExpression(); + return parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true); + } - while (true) { - const child = parseJsxChild(openingTag, currentToken = scanner.reScanJsxToken()); - if (!child) break; - list.push(child); - if (isJsxOpeningElement(openingTag) - && child?.kind === SyntaxKind.JsxElement - && !tagNamesAreEquivalent(child.openingElement.tagName, child.closingElement.tagName) - && tagNamesAreEquivalent(openingTag.tagName, child.closingElement.tagName)) { - // stop after parsing a mismatched child like
...(
) in order to reattach the
higher - break; + function parseSuperExpression(): MemberExpression { + const pos = getNodePos(); + let expression = parseTokenNode(); + if (token() === SyntaxKind.LessThanToken) { + const startPos = getNodePos(); + const typeArguments = tryParse(parseTypeArgumentsInExpression); + if (typeArguments !== undefined) { + parseErrorAt(startPos, getNodePos(), Diagnostics.super_may_not_use_type_arguments); + if (!isTemplateStartOfTaggedTemplate()) { + expression = factory.createExpressionWithTypeArguments(expression, typeArguments); } } - - parsingContext = saveParsingContext; - return createNodeArray(list, listPos); } - function parseJsxAttributes(): JsxAttributes { - const pos = getNodePos(); - return finishNode(factory.createJsxAttributes(parseList(ParsingContext.JsxAttributes, parseJsxAttribute)), pos); + if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.DotToken || token() === SyntaxKind.OpenBracketToken) { + return expression; } - function parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement | JsxOpeningFragment { - const pos = getNodePos(); - - parseExpected(SyntaxKind.LessThanToken); - - if (token() === SyntaxKind.GreaterThanToken) { - // See below for explanation of scanJsxText - scanJsxText(); - return finishNode(factory.createJsxOpeningFragment(), pos); - } - const tagName = parseJsxElementName(); - const typeArguments = (contextFlags & NodeFlags.JavaScriptFile) === 0 ? tryParseTypeArguments() : undefined; - const attributes = parseJsxAttributes(); - - let node: JsxOpeningLikeElement; + // If we have seen "super" it must be followed by '(' or '.'. + // If it wasn't then just try to parse out a '.' and report an error. + parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access); + // private names will never work with `super` (`super.#foo`), but that's a semantic error, not syntactic + return finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true)), pos); + } - if (token() === SyntaxKind.GreaterThanToken) { - // Closing tag, so scan the immediately-following text with the JSX scanning instead - // of regular scanning to avoid treating illegal characters (e.g. '#') as immediate - // scanning errors - scanJsxText(); - node = factory.createJsxOpeningElement(tagName, typeArguments, attributes); + function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean, topInvalidNodePosition?: number, openingTag?: JsxOpeningElement | JsxOpeningFragment): JsxElement | JsxSelfClosingElement | JsxFragment { + const pos = getNodePos(); + const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext); + let result: JsxElement | JsxSelfClosingElement | JsxFragment; + if (opening.kind === SyntaxKind.JsxOpeningElement) { + let children = parseJsxChildren(opening); + let closingElement: JsxClosingElement; + + const lastChild: JsxChild | undefined = children[children.length - 1]; + if (lastChild?.kind === SyntaxKind.JsxElement + && !tagNamesAreEquivalent(lastChild.openingElement.tagName, lastChild.closingElement.tagName) + && tagNamesAreEquivalent(opening.tagName, lastChild.closingElement.tagName)) { + // when an unclosed JsxOpeningElement incorrectly parses its parent's JsxClosingElement, + // restructure (
(......
)) --> (
(......)
) + // (no need to error; the parent will error) + const end = lastChild.children.end; + const newLast = finishNode(factory.createJsxElement( + lastChild.openingElement, + lastChild.children, + finishNode(factory.createJsxClosingElement(finishNode(factory.createIdentifier(""), end, end)), end, end)), + lastChild.openingElement.pos, + end); + + children = createNodeArray([...children.slice(0, children.length - 1), newLast], children.pos, end); + closingElement = lastChild.closingElement; } else { - parseExpected(SyntaxKind.SlashToken); - if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { - // manually advance the scanner in order to look for jsx text inside jsx - if (inExpressionContext) { - nextToken(); + closingElement = parseJsxClosingElement(opening, inExpressionContext); + if (!tagNamesAreEquivalent(opening.tagName, closingElement.tagName)) { + if (openingTag && isJsxOpeningElement(openingTag) && tagNamesAreEquivalent(closingElement.tagName, openingTag.tagName)) { + // opening incorrectly matched with its parent's closing -- put error on opening + parseErrorAtRange(opening.tagName, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, opening.tagName)); } else { - scanJsxText(); + // other opening/closing mismatches -- put error on closing + parseErrorAtRange(closingElement.tagName, Diagnostics.Expected_corresponding_JSX_closing_tag_for_0, getTextOfNodeFromSourceText(sourceText, opening.tagName)); } } - node = factory.createJsxSelfClosingElement(tagName, typeArguments, attributes); } - - return finishNode(node, pos); + result = finishNode(factory.createJsxElement(opening, children, closingElement), pos); } - - function parseJsxElementName(): JsxTagNameExpression { - const pos = getNodePos(); - scanJsxIdentifier(); - // JsxElement can have name in the form of - // propertyAccessExpression - // primaryExpression in the form of an identifier and "this" keyword - // We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword - // We only want to consider "this" as a primaryExpression - let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ? - parseTokenNode() : parseIdentifierName(); - while (parseOptional(SyntaxKind.DotToken)) { - expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess; - } - return expression; + else if (opening.kind === SyntaxKind.JsxOpeningFragment) { + result = finishNode(factory.createJsxFragment(opening, parseJsxChildren(opening), parseJsxClosingFragment(inExpressionContext)), pos); + } + else { + Debug.assert(opening.kind === SyntaxKind.JsxSelfClosingElement); + // Nothing else to do for self-closing elements + result = opening; } - function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined { - const pos = getNodePos(); - if (!parseExpected(SyntaxKind.OpenBraceToken)) { - return undefined; - } - - let dotDotDotToken: DotDotDotToken | undefined; - let expression: Expression | undefined; - if (token() !== SyntaxKind.CloseBraceToken) { - dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - // 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. - expression = parseExpression(); - } - if (inExpressionContext) { - parseExpected(SyntaxKind.CloseBraceToken); - } - else { - if (parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false)) { - scanJsxText(); - } + // If the user writes the invalid code '
' in an expression context (i.e. not wrapped in + // an enclosing tag), we'll naively try to parse ^ this as a 'less than' operator and the remainder of the tag + // as garbage, which will cause the formatter to badly mangle the JSX. Perform a speculative parse of a JSX + // element if we see a < token so that we can wrap it in a synthetic binary expression so the formatter + // does less damage and we can report a better error. + // Since JSX elements are invalid < operands anyway, this lookahead parse will only occur in error scenarios + // of one sort or another. + if (inExpressionContext && token() === SyntaxKind.LessThanToken) { + const topBadPos = typeof topInvalidNodePosition === "undefined" ? result.pos : topInvalidNodePosition; + const invalidElement = tryParse(() => parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true, topBadPos)); + if (invalidElement) { + const operatorToken = createMissingNode(SyntaxKind.CommaToken, /*reportAtCurrentPosition*/ false); + setTextRangePosWidth(operatorToken, invalidElement.pos, 0); + parseErrorAt(skipTrivia(sourceText, topBadPos), invalidElement.end, Diagnostics.JSX_expressions_must_have_one_parent_element); + return finishNode(factory.createBinaryExpression(result, operatorToken as Token, invalidElement), pos) as Node as JsxElement; } - - return finishNode(factory.createJsxExpression(dotDotDotToken, expression), pos); } - function parseJsxAttribute(): JsxAttribute | JsxSpreadAttribute { - if (token() === SyntaxKind.OpenBraceToken) { - return parseJsxSpreadAttribute(); - } + return result; + } - scanJsxIdentifier(); - const pos = getNodePos(); - return finishNode(factory.createJsxAttribute(parseIdentifierName(), parseJsxAttributeValue()), pos); - } + function parseJsxText(): JsxText { + const pos = getNodePos(); + const node = factory.createJsxText(scanner.getTokenValue(), currentToken === SyntaxKind.JsxTextAllWhiteSpaces); + currentToken = scanner.scanJsxToken(); + return finishNode(node, pos); + } - function parseJsxAttributeValue(): JsxAttributeValue | undefined { - if (token() === SyntaxKind.EqualsToken) { - if (scanJsxAttributeValue() === SyntaxKind.StringLiteral) { - return parseLiteralNode() as StringLiteral; - } - if (token() === SyntaxKind.OpenBraceToken) { - return parseJsxExpression(/*inExpressionContext*/ true); + function parseJsxChild(openingTag: JsxOpeningElement | JsxOpeningFragment, token: JsxTokenSyntaxKind): JsxChild | undefined { + switch (token) { + case SyntaxKind.EndOfFileToken: + // If we hit EOF, issue the error at the tag that lacks the closing element + // rather than at the end of the file (which is useless) + if (isJsxOpeningFragment(openingTag)) { + parseErrorAtRange(openingTag, Diagnostics.JSX_fragment_has_no_corresponding_closing_tag); } - if (token() === SyntaxKind.LessThanToken) { - return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true); + else { + // We want the error span to cover only 'Foo.Bar' in < Foo.Bar > + // or to cover only 'Foo' in < Foo > + const tag = openingTag.tagName; + const start = skipTrivia(sourceText, tag.pos); + parseErrorAt(start, tag.end, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, openingTag.tagName)); } - parseErrorAtCurrentToken(Diagnostics.or_JSX_element_expected); - } - return undefined; + return undefined; + case SyntaxKind.LessThanSlashToken: + case SyntaxKind.ConflictMarkerTrivia: + return undefined; + case SyntaxKind.JsxText: + case SyntaxKind.JsxTextAllWhiteSpaces: + return parseJsxText(); + case SyntaxKind.OpenBraceToken: + return parseJsxExpression(/*inExpressionContext*/ false); + case SyntaxKind.LessThanToken: + return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ false, /*topInvalidNodePosition*/ undefined, openingTag); + default: + return Debug.assertNever(token); } + } - function parseJsxSpreadAttribute(): JsxSpreadAttribute { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBraceToken); - parseExpected(SyntaxKind.DotDotDotToken); - const expression = parseExpression(); - parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(factory.createJsxSpreadAttribute(expression), pos); + function parseJsxChildren(openingTag: JsxOpeningElement | JsxOpeningFragment): NodeArray { + const list = []; + const listPos = getNodePos(); + const saveParsingContext = parsingContext; + parsingContext |= 1 << ParsingContext.JsxChildren; + + while (true) { + const child = parseJsxChild(openingTag, currentToken = scanner.reScanJsxToken()); + if (!child) break; + list.push(child); + if (isJsxOpeningElement(openingTag) + && child?.kind === SyntaxKind.JsxElement + && !tagNamesAreEquivalent(child.openingElement.tagName, child.closingElement.tagName) + && tagNamesAreEquivalent(openingTag.tagName, child.closingElement.tagName)) { + // stop after parsing a mismatched child like
...(
) in order to reattach the
higher + break; + } } - function parseJsxClosingElement(open: JsxOpeningElement, inExpressionContext: boolean): JsxClosingElement { - const pos = getNodePos(); - parseExpected(SyntaxKind.LessThanSlashToken); - const tagName = parseJsxElementName(); - if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { - // manually advance the scanner in order to look for jsx text inside jsx - if (inExpressionContext || !tagNamesAreEquivalent(open.tagName, tagName)) { - nextToken(); - } - else { - scanJsxText(); - } - } - return finishNode(factory.createJsxClosingElement(tagName), pos); + parsingContext = saveParsingContext; + return createNodeArray(list, listPos); + } + + function parseJsxAttributes(): JsxAttributes { + const pos = getNodePos(); + return finishNode(factory.createJsxAttributes(parseList(ParsingContext.JsxAttributes, parseJsxAttribute)), pos); + } + + function parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement | JsxOpeningFragment { + const pos = getNodePos(); + + parseExpected(SyntaxKind.LessThanToken); + + if (token() === SyntaxKind.GreaterThanToken) { + // See below for explanation of scanJsxText + scanJsxText(); + return finishNode(factory.createJsxOpeningFragment(), pos); } + const tagName = parseJsxElementName(); + const typeArguments = (contextFlags & NodeFlags.JavaScriptFile) === 0 ? tryParseTypeArguments() : undefined; + const attributes = parseJsxAttributes(); - function parseJsxClosingFragment(inExpressionContext: boolean): JsxClosingFragment { - const pos = getNodePos(); - parseExpected(SyntaxKind.LessThanSlashToken); - if (tokenIsIdentifierOrKeyword(token())) { - parseErrorAtRange(parseJsxElementName(), Diagnostics.Expected_corresponding_closing_tag_for_JSX_fragment); - } + let node: JsxOpeningLikeElement; + + if (token() === SyntaxKind.GreaterThanToken) { + // Closing tag, so scan the immediately-following text with the JSX scanning instead + // of regular scanning to avoid treating illegal characters (e.g. '#') as immediate + // scanning errors + scanJsxText(); + node = factory.createJsxOpeningElement(tagName, typeArguments, attributes); + } + else { + parseExpected(SyntaxKind.SlashToken); if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { // manually advance the scanner in order to look for jsx text inside jsx if (inExpressionContext) { @@ -5784,4182 +5739,4296 @@ namespace ts { scanJsxText(); } } - return finishNode(factory.createJsxJsxClosingFragment(), pos); + node = factory.createJsxSelfClosingElement(tagName, typeArguments, attributes); } - function parseTypeAssertion(): TypeAssertion { - const pos = getNodePos(); - parseExpected(SyntaxKind.LessThanToken); - const type = parseType(); - parseExpected(SyntaxKind.GreaterThanToken); - const expression = parseSimpleUnaryExpression(); - return finishNode(factory.createTypeAssertion(type, expression), pos); + return finishNode(node, pos); + } + + function parseJsxElementName(): JsxTagNameExpression { + const pos = getNodePos(); + scanJsxIdentifier(); + // JsxElement can have name in the form of + // propertyAccessExpression + // primaryExpression in the form of an identifier and "this" keyword + // We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword + // We only want to consider "this" as a primaryExpression + let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ? + parseTokenNode() : parseIdentifierName(); + while (parseOptional(SyntaxKind.DotToken)) { + expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess; + } + return expression; + } + + function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined { + const pos = getNodePos(); + if (!parseExpected(SyntaxKind.OpenBraceToken)) { + return undefined; } - function nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate() { - nextToken(); - return tokenIsIdentifierOrKeyword(token()) - || token() === SyntaxKind.OpenBracketToken - || isTemplateStartOfTaggedTemplate(); + let dotDotDotToken: DotDotDotToken | undefined; + let expression: Expression | undefined; + if (token() !== SyntaxKind.CloseBraceToken) { + dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + // 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. + expression = parseExpression(); } + if (inExpressionContext) { + parseExpected(SyntaxKind.CloseBraceToken); + } + else { + if (parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false)) { + scanJsxText(); + } + } + + return finishNode(factory.createJsxExpression(dotDotDotToken, expression), pos); + } - function isStartOfOptionalPropertyOrElementAccessChain() { - return token() === SyntaxKind.QuestionDotToken - && lookAhead(nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate); + function parseJsxAttribute(): JsxAttribute | JsxSpreadAttribute { + if (token() === SyntaxKind.OpenBraceToken) { + return parseJsxSpreadAttribute(); } - function tryReparseOptionalChain(node: Expression) { - if (node.flags & NodeFlags.OptionalChain) { - return true; + scanJsxIdentifier(); + const pos = getNodePos(); + return finishNode(factory.createJsxAttribute(parseIdentifierName(), parseJsxAttributeValue()), pos); + } + + function parseJsxAttributeValue(): JsxAttributeValue | undefined { + if (token() === SyntaxKind.EqualsToken) { + if (scanJsxAttributeValue() === SyntaxKind.StringLiteral) { + return parseLiteralNode() as StringLiteral; } - // check for an optional chain in a non-null expression - if (isNonNullExpression(node)) { - let expr = node.expression; - while (isNonNullExpression(expr) && !(expr.flags & NodeFlags.OptionalChain)) { - expr = expr.expression; - } - if (expr.flags & NodeFlags.OptionalChain) { - // this is part of an optional chain. Walk down from `node` to `expression` and set the flag. - while (isNonNullExpression(node)) { - (node as Mutable).flags |= NodeFlags.OptionalChain; - node = node.expression; - } - return true; - } + if (token() === SyntaxKind.OpenBraceToken) { + return parseJsxExpression(/*inExpressionContext*/ true); } - return false; + if (token() === SyntaxKind.LessThanToken) { + return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true); + } + parseErrorAtCurrentToken(Diagnostics.or_JSX_element_expected); } + return undefined; + } + + function parseJsxSpreadAttribute(): JsxSpreadAttribute { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBraceToken); + parseExpected(SyntaxKind.DotDotDotToken); + const expression = parseExpression(); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(factory.createJsxSpreadAttribute(expression), pos); + } - function parsePropertyAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { - const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true); - const isOptionalChain = questionDotToken || tryReparseOptionalChain(expression); - const propertyAccess = isOptionalChain ? - factory.createPropertyAccessChain(expression, questionDotToken, name) : - factory.createPropertyAccessExpression(expression, name); - if (isOptionalChain && isPrivateIdentifier(propertyAccess.name)) { - parseErrorAtRange(propertyAccess.name, Diagnostics.An_optional_chain_cannot_contain_private_identifiers); + function parseJsxClosingElement(open: JsxOpeningElement, inExpressionContext: boolean): JsxClosingElement { + const pos = getNodePos(); + parseExpected(SyntaxKind.LessThanSlashToken); + const tagName = parseJsxElementName(); + if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { + // manually advance the scanner in order to look for jsx text inside jsx + if (inExpressionContext || !tagNamesAreEquivalent(open.tagName, tagName)) { + nextToken(); } - if (isExpressionWithTypeArguments(expression) && expression.typeArguments) { - const pos = expression.typeArguments.pos - 1; - const end = skipTrivia(sourceText, expression.typeArguments.end) + 1; - parseErrorAt(pos, end, Diagnostics.An_instantiation_expression_cannot_be_followed_by_a_property_access); + else { + scanJsxText(); } - return finishNode(propertyAccess, pos); } + return finishNode(factory.createJsxClosingElement(tagName), pos); + } - function parseElementAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { - let argumentExpression: Expression; - if (token() === SyntaxKind.CloseBracketToken) { - argumentExpression = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.An_element_access_expression_should_take_an_argument); + function parseJsxClosingFragment(inExpressionContext: boolean): JsxClosingFragment { + const pos = getNodePos(); + parseExpected(SyntaxKind.LessThanSlashToken); + if (tokenIsIdentifierOrKeyword(token())) { + parseErrorAtRange(parseJsxElementName(), Diagnostics.Expected_corresponding_closing_tag_for_JSX_fragment); + } + if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { + // manually advance the scanner in order to look for jsx text inside jsx + if (inExpressionContext) { + nextToken(); } else { - const argument = allowInAnd(parseExpression); - if (isStringOrNumericLiteralLike(argument)) { - argument.text = internIdentifier(argument.text); - } - argumentExpression = argument; + scanJsxText(); } - - parseExpected(SyntaxKind.CloseBracketToken); - - const indexedAccess = questionDotToken || tryReparseOptionalChain(expression) ? - factory.createElementAccessChain(expression, questionDotToken, argumentExpression) : - factory.createElementAccessExpression(expression, argumentExpression); - return finishNode(indexedAccess, pos); } + return finishNode(factory.createJsxJsxClosingFragment(), pos); + } - function parseMemberExpressionRest(pos: number, expression: LeftHandSideExpression, allowOptionalChain: boolean): MemberExpression { - while (true) { - let questionDotToken: QuestionDotToken | undefined; - let isPropertyAccess = false; - if (allowOptionalChain && isStartOfOptionalPropertyOrElementAccessChain()) { - questionDotToken = parseExpectedToken(SyntaxKind.QuestionDotToken); - isPropertyAccess = tokenIsIdentifierOrKeyword(token()); - } - else { - isPropertyAccess = parseOptional(SyntaxKind.DotToken); - } - - if (isPropertyAccess) { - expression = parsePropertyAccessExpressionRest(pos, expression, questionDotToken); - continue; - } + function parseTypeAssertion(): TypeAssertion { + const pos = getNodePos(); + parseExpected(SyntaxKind.LessThanToken); + const type = parseType(); + parseExpected(SyntaxKind.GreaterThanToken); + const expression = parseSimpleUnaryExpression(); + return finishNode(factory.createTypeAssertion(type, expression), pos); + } - // when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName - if ((questionDotToken || !inDecoratorContext()) && parseOptional(SyntaxKind.OpenBracketToken)) { - expression = parseElementAccessExpressionRest(pos, expression, questionDotToken); - continue; - } + function nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate() { + nextToken(); + return tokenIsIdentifierOrKeyword(token()) + || token() === SyntaxKind.OpenBracketToken + || isTemplateStartOfTaggedTemplate(); + } - if (isTemplateStartOfTaggedTemplate()) { - // Absorb type arguments into TemplateExpression when preceding expression is ExpressionWithTypeArguments - expression = !questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments ? - parseTaggedTemplateRest(pos, (expression as ExpressionWithTypeArguments).expression, questionDotToken, (expression as ExpressionWithTypeArguments).typeArguments) : - parseTaggedTemplateRest(pos, expression, questionDotToken, /*typeArguments*/ undefined); - continue; - } + function isStartOfOptionalPropertyOrElementAccessChain() { + return token() === SyntaxKind.QuestionDotToken + && lookAhead(nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate); + } - if (!questionDotToken) { - if (token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { - nextToken(); - expression = finishNode(factory.createNonNullExpression(expression), pos); - continue; - } - const typeArguments = tryParse(parseTypeArgumentsInExpression); - if (typeArguments) { - expression = finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos); - continue; - } + function tryReparseOptionalChain(node: Expression) { + if (node.flags & NodeFlags.OptionalChain) { + return true; + } + // check for an optional chain in a non-null expression + if (isNonNullExpression(node)) { + let expr = node.expression; + while (isNonNullExpression(expr) && !(expr.flags & NodeFlags.OptionalChain)) { + expr = expr.expression; + } + if (expr.flags & NodeFlags.OptionalChain) { + // this is part of an optional chain. Walk down from `node` to `expression` and set the flag. + while (isNonNullExpression(node)) { + (node as Mutable).flags |= NodeFlags.OptionalChain; + node = node.expression; } - - return expression as MemberExpression; + return true; } } + return false; + } - function isTemplateStartOfTaggedTemplate() { - return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead; - } + function parsePropertyAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { + const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true); + const isOptionalChain = questionDotToken || tryReparseOptionalChain(expression); + const propertyAccess = isOptionalChain ? + factory.createPropertyAccessChain(expression, questionDotToken, name) : + factory.createPropertyAccessExpression(expression, name); + if (isOptionalChain && isPrivateIdentifier(propertyAccess.name)) { + parseErrorAtRange(propertyAccess.name, Diagnostics.An_optional_chain_cannot_contain_private_identifiers); + } + if (isExpressionWithTypeArguments(expression) && expression.typeArguments) { + const pos = expression.typeArguments.pos - 1; + const end = skipTrivia(sourceText, expression.typeArguments.end) + 1; + parseErrorAt(pos, end, Diagnostics.An_instantiation_expression_cannot_be_followed_by_a_property_access); + } + return finishNode(propertyAccess, pos); + } - function parseTaggedTemplateRest(pos: number, tag: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, typeArguments: NodeArray | undefined) { - const tagExpression = factory.createTaggedTemplateExpression( - tag, - typeArguments, - token() === SyntaxKind.NoSubstitutionTemplateLiteral ? - (reScanTemplateHeadOrNoSubstitutionTemplate(), parseLiteralNode() as NoSubstitutionTemplateLiteral) : - parseTemplateExpression(/*isTaggedTemplate*/ true) - ); - if (questionDotToken || tag.flags & NodeFlags.OptionalChain) { - (tagExpression as Mutable).flags |= NodeFlags.OptionalChain; - } - tagExpression.questionDotToken = questionDotToken; - return finishNode(tagExpression, pos); + function parseElementAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { + let argumentExpression: Expression; + if (token() === SyntaxKind.CloseBracketToken) { + argumentExpression = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.An_element_access_expression_should_take_an_argument); } - - function parseCallExpressionRest(pos: number, expression: LeftHandSideExpression): LeftHandSideExpression { - while (true) { - expression = parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true); - let typeArguments: NodeArray | undefined; - const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); - if (questionDotToken) { - typeArguments = tryParse(parseTypeArgumentsInExpression); - if (isTemplateStartOfTaggedTemplate()) { - expression = parseTaggedTemplateRest(pos, expression, questionDotToken, typeArguments); - continue; - } - } - if (typeArguments || token() === SyntaxKind.OpenParenToken) { - // Absorb type arguments into CallExpression when preceding expression is ExpressionWithTypeArguments - if (!questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments) { - typeArguments = (expression as ExpressionWithTypeArguments).typeArguments; - expression = (expression as ExpressionWithTypeArguments).expression; - } - const argumentList = parseArgumentList(); - const callExpr = questionDotToken || tryReparseOptionalChain(expression) ? - factory.createCallChain(expression, questionDotToken, typeArguments, argumentList) : - factory.createCallExpression(expression, typeArguments, argumentList); - expression = finishNode(callExpr, pos); - continue; - } - if (questionDotToken) { - // We parsed `?.` but then failed to parse anything, so report a missing identifier here. - const name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics.Identifier_expected); - expression = finishNode(factory.createPropertyAccessChain(expression, questionDotToken, name), pos); - } - break; + else { + const argument = allowInAnd(parseExpression); + if (isStringOrNumericLiteralLike(argument)) { + argument.text = internIdentifier(argument.text); } - return expression; + argumentExpression = argument; } - function parseArgumentList() { - parseExpected(SyntaxKind.OpenParenToken); - const result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression); - parseExpected(SyntaxKind.CloseParenToken); - return result; - } + parseExpected(SyntaxKind.CloseBracketToken); - function parseTypeArgumentsInExpression() { - if ((contextFlags & NodeFlags.JavaScriptFile) !== 0) { - // TypeArguments must not be parsed in JavaScript files to avoid ambiguity with binary operators. - return undefined; - } + const indexedAccess = questionDotToken || tryReparseOptionalChain(expression) ? + factory.createElementAccessChain(expression, questionDotToken, argumentExpression) : + factory.createElementAccessExpression(expression, argumentExpression); + return finishNode(indexedAccess, pos); + } - if (reScanLessThanToken() !== SyntaxKind.LessThanToken) { - return undefined; + function parseMemberExpressionRest(pos: number, expression: LeftHandSideExpression, allowOptionalChain: boolean): MemberExpression { + while (true) { + let questionDotToken: QuestionDotToken | undefined; + let isPropertyAccess = false; + if (allowOptionalChain && isStartOfOptionalPropertyOrElementAccessChain()) { + questionDotToken = parseExpectedToken(SyntaxKind.QuestionDotToken); + isPropertyAccess = tokenIsIdentifierOrKeyword(token()); } - nextToken(); - - const typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType); - if (reScanGreaterToken() !== SyntaxKind.GreaterThanToken) { - // If it doesn't have the closing `>` then it's definitely not an type argument list. - return undefined; + else { + isPropertyAccess = parseOptional(SyntaxKind.DotToken); } - nextToken(); - - // We successfully parsed a type argument list. The next token determines whether we want to - // treat it as such. If the type argument list is followed by `(` or a template literal, as in - // `f(42)`, we favor the type argument interpretation even though JavaScript would view - // it as a relational expression. - return typeArguments && canFollowTypeArgumentsInExpression() ? typeArguments : undefined; - } - function canFollowTypeArgumentsInExpression(): boolean { - switch (token()) { - // These tokens can follow a type argument list in a call expression. - case SyntaxKind.OpenParenToken: // foo( - case SyntaxKind.NoSubstitutionTemplateLiteral: // foo `...` - case SyntaxKind.TemplateHead: // foo `...${100}...` - return true; - // A type argument list followed by `<` never makes sense, and a type argument list followed - // by `>` is ambiguous with a (re-scanned) `>>` operator, so we disqualify both. Also, in - // this context, `+` and `-` are unary operators, not binary operators. - case SyntaxKind.LessThanToken: - case SyntaxKind.GreaterThanToken: - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - return false; + if (isPropertyAccess) { + expression = parsePropertyAccessExpressionRest(pos, expression, questionDotToken); + continue; } - // We favor the type argument list interpretation when it is immediately followed by - // a line break, a binary operator, or something that can't start an expression. - return scanner.hasPrecedingLineBreak() || isBinaryOperator() || !isStartOfExpression(); - } - - function parsePrimaryExpression(): PrimaryExpression { - switch (token()) { - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return parseLiteralNode(); - case SyntaxKind.ThisKeyword: - case SyntaxKind.SuperKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - return parseTokenNode(); - case SyntaxKind.OpenParenToken: - return parseParenthesizedExpression(); - case SyntaxKind.OpenBracketToken: - return parseArrayLiteralExpression(); - case SyntaxKind.OpenBraceToken: - return parseObjectLiteralExpression(); - case SyntaxKind.AsyncKeyword: - // Async arrow functions are parsed earlier in parseAssignmentExpressionOrHigher. - // If we encounter `async [no LineTerminator here] function` then this is an async - // function; otherwise, its an identifier. - if (!lookAhead(nextTokenIsFunctionKeywordOnSameLine)) { - break; - } - return parseFunctionExpression(); - case SyntaxKind.ClassKeyword: - return parseClassExpression(); - case SyntaxKind.FunctionKeyword: - return parseFunctionExpression(); - case SyntaxKind.NewKeyword: - return parseNewExpressionOrNewDotTarget(); - case SyntaxKind.SlashToken: - case SyntaxKind.SlashEqualsToken: - if (reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) { - return parseLiteralNode(); - } - break; - case SyntaxKind.TemplateHead: - return parseTemplateExpression(/* isTaggedTemplate */ false); - case SyntaxKind.PrivateIdentifier: - return parsePrivateIdentifier(); + // when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName + if ((questionDotToken || !inDecoratorContext()) && parseOptional(SyntaxKind.OpenBracketToken)) { + expression = parseElementAccessExpressionRest(pos, expression, questionDotToken); + continue; } - return parseIdentifier(Diagnostics.Expression_expected); - } - - function parseParenthesizedExpression(): ParenthesizedExpression { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.OpenParenToken); - const expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - return withJSDoc(finishNode(factory.createParenthesizedExpression(expression), pos), hasJSDoc); - } - - function parseSpreadElement(): Expression { - const pos = getNodePos(); - parseExpected(SyntaxKind.DotDotDotToken); - const expression = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); - return finishNode(factory.createSpreadElement(expression), pos); - } - - function parseArgumentOrArrayLiteralElement(): Expression { - return token() === SyntaxKind.DotDotDotToken ? parseSpreadElement() : - token() === SyntaxKind.CommaToken ? finishNode(factory.createOmittedExpression(), getNodePos()) : - parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); - } + if (isTemplateStartOfTaggedTemplate()) { + // Absorb type arguments into TemplateExpression when preceding expression is ExpressionWithTypeArguments + expression = !questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments ? + parseTaggedTemplateRest(pos, (expression as ExpressionWithTypeArguments).expression, questionDotToken, (expression as ExpressionWithTypeArguments).typeArguments) : + parseTaggedTemplateRest(pos, expression, questionDotToken, /*typeArguments*/ undefined); + continue; + } - function parseArgumentExpression(): Expression { - return doOutsideOfContext(disallowInAndDecoratorContext, parseArgumentOrArrayLiteralElement); - } + if (!questionDotToken) { + if (token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + nextToken(); + expression = finishNode(factory.createNonNullExpression(expression), pos); + continue; + } + const typeArguments = tryParse(parseTypeArgumentsInExpression); + if (typeArguments) { + expression = finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos); + continue; + } + } - function parseArrayLiteralExpression(): ArrayLiteralExpression { - const pos = getNodePos(); - const openBracketPosition = scanner.getTokenPos(); - const openBracketParsed = parseExpected(SyntaxKind.OpenBracketToken); - const multiLine = scanner.hasPrecedingLineBreak(); - const elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArgumentOrArrayLiteralElement); - parseExpectedMatchingBrackets(SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken, openBracketParsed, openBracketPosition); - return finishNode(factory.createArrayLiteralExpression(elements, multiLine), pos); + return expression as MemberExpression; } + } - function parseObjectLiteralElement(): ObjectLiteralElementLike { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - - if (parseOptionalToken(SyntaxKind.DotDotDotToken)) { - const expression = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); - return withJSDoc(finishNode(factory.createSpreadAssignment(expression), pos), hasJSDoc); - } + function isTemplateStartOfTaggedTemplate() { + return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead; + } - const decorators = parseDecorators(); - const modifiers = parseModifiers(); + function parseTaggedTemplateRest(pos: number, tag: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, typeArguments: NodeArray | undefined) { + const tagExpression = factory.createTaggedTemplateExpression( + tag, + typeArguments, + token() === SyntaxKind.NoSubstitutionTemplateLiteral ? + (reScanTemplateHeadOrNoSubstitutionTemplate(), parseLiteralNode() as NoSubstitutionTemplateLiteral) : + parseTemplateExpression(/*isTaggedTemplate*/ true) + ); + if (questionDotToken || tag.flags & NodeFlags.OptionalChain) { + (tagExpression as Mutable).flags |= NodeFlags.OptionalChain; + } + tagExpression.questionDotToken = questionDotToken; + return finishNode(tagExpression, pos); + } - if (parseContextualModifier(SyntaxKind.GetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.GetAccessor, SignatureFlags.None); + function parseCallExpressionRest(pos: number, expression: LeftHandSideExpression): LeftHandSideExpression { + while (true) { + expression = parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true); + let typeArguments: NodeArray | undefined; + const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); + if (questionDotToken) { + typeArguments = tryParse(parseTypeArgumentsInExpression); + if (isTemplateStartOfTaggedTemplate()) { + expression = parseTaggedTemplateRest(pos, expression, questionDotToken, typeArguments); + continue; + } } - if (parseContextualModifier(SyntaxKind.SetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.SetAccessor, SignatureFlags.None); + if (typeArguments || token() === SyntaxKind.OpenParenToken) { + // Absorb type arguments into CallExpression when preceding expression is ExpressionWithTypeArguments + if (!questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments) { + typeArguments = (expression as ExpressionWithTypeArguments).typeArguments; + expression = (expression as ExpressionWithTypeArguments).expression; + } + const argumentList = parseArgumentList(); + const callExpr = questionDotToken || tryReparseOptionalChain(expression) ? + factory.createCallChain(expression, questionDotToken, typeArguments, argumentList) : + factory.createCallExpression(expression, typeArguments, argumentList); + expression = finishNode(callExpr, pos); + continue; } - - const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - const tokenIsIdentifier = isIdentifier(); - const name = parsePropertyName(); - - // Disallowing of optional property assignments and definite assignment assertion happens in the grammar checker. - const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - const exclamationToken = parseOptionalToken(SyntaxKind.ExclamationToken); - - if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - return parseMethodDeclaration(pos, hasJSDoc, decorators, modifiers, asteriskToken, name, questionToken, exclamationToken); - } - - // check if it is short-hand property assignment or normal property assignment - // NOTE: if token is EqualsToken it is interpreted as CoverInitializedName production - // CoverInitializedName[Yield] : - // IdentifierReference[?Yield] Initializer[In, ?Yield] - // this is necessary because ObjectLiteral productions are also used to cover grammar for ObjectAssignmentPattern - let node: Mutable; - const isShorthandPropertyAssignment = tokenIsIdentifier && (token() !== SyntaxKind.ColonToken); - if (isShorthandPropertyAssignment) { - const equalsToken = parseOptionalToken(SyntaxKind.EqualsToken); - const objectAssignmentInitializer = equalsToken ? allowInAnd(() => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true)) : undefined; - node = factory.createShorthandPropertyAssignment(name as Identifier, objectAssignmentInitializer); - // Save equals token for error reporting. - // TODO(rbuckton): Consider manufacturing this when we need to report an error as it is otherwise not useful. - node.equalsToken = equalsToken; + if (questionDotToken) { + // We parsed `?.` but then failed to parse anything, so report a missing identifier here. + const name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics.Identifier_expected); + expression = finishNode(factory.createPropertyAccessChain(expression, questionDotToken, name), pos); } - else { - parseExpected(SyntaxKind.ColonToken); - const initializer = allowInAnd(() => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true)); - node = factory.createPropertyAssignment(name, initializer); - } - // Decorators, Modifiers, questionToken, and exclamationToken are not supported by property assignments and are reported in the grammar checker - node.illegalDecorators = decorators; - node.modifiers = modifiers; - node.questionToken = questionToken; - node.exclamationToken = exclamationToken; - return withJSDoc(finishNode(node, pos), hasJSDoc); - } - - function parseObjectLiteralExpression(): ObjectLiteralExpression { - const pos = getNodePos(); - const openBracePosition = scanner.getTokenPos(); - const openBraceParsed = parseExpected(SyntaxKind.OpenBraceToken); - const multiLine = scanner.hasPrecedingLineBreak(); - const properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement, /*considerSemicolonAsDelimiter*/ true); - parseExpectedMatchingBrackets(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, openBraceParsed, openBracePosition); - return finishNode(factory.createObjectLiteralExpression(properties, multiLine), pos); + break; } + return expression; + } - function parseFunctionExpression(): FunctionExpression { - // GeneratorExpression: - // function* BindingIdentifier [Yield][opt](FormalParameters[Yield]){ GeneratorBody } - // - // FunctionExpression: - // function BindingIdentifier[opt](FormalParameters){ FunctionBody } - const savedDecoratorContext = inDecoratorContext(); - setDecoratorContext(/*val*/ false); - - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const modifiers = parseModifiers(); - parseExpected(SyntaxKind.FunctionKeyword); - const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; - const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; - const name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalBindingIdentifier) : - isGenerator ? doInYieldContext(parseOptionalBindingIdentifier) : - isAsync ? doInAwaitContext(parseOptionalBindingIdentifier) : - parseOptionalBindingIdentifier(); - - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(isGenerator | isAsync); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - const body = parseFunctionBlock(isGenerator | isAsync); - - setDecoratorContext(savedDecoratorContext); + function parseArgumentList() { + parseExpected(SyntaxKind.OpenParenToken); + const result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression); + parseExpected(SyntaxKind.CloseParenToken); + return result; + } - const node = factory.createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body); - return withJSDoc(finishNode(node, pos), hasJSDoc); + function parseTypeArgumentsInExpression() { + if ((contextFlags & NodeFlags.JavaScriptFile) !== 0) { + // TypeArguments must not be parsed in JavaScript files to avoid ambiguity with binary operators. + return undefined; } - function parseOptionalBindingIdentifier(): Identifier | undefined { - return isBindingIdentifier() ? parseBindingIdentifier() : undefined; + if (reScanLessThanToken() !== SyntaxKind.LessThanToken) { + return undefined; } + nextToken(); - function parseNewExpressionOrNewDotTarget(): NewExpression | MetaProperty { - const pos = getNodePos(); - parseExpected(SyntaxKind.NewKeyword); - if (parseOptional(SyntaxKind.DotToken)) { - const name = parseIdentifierName(); - return finishNode(factory.createMetaProperty(SyntaxKind.NewKeyword, name), pos); - } - const expressionPos = getNodePos(); - let expression: LeftHandSideExpression = parseMemberExpressionRest(expressionPos, parsePrimaryExpression(), /*allowOptionalChain*/ false); - let typeArguments: NodeArray | undefined; - // Absorb type arguments into NewExpression when preceding expression is ExpressionWithTypeArguments - if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) { - typeArguments = (expression as ExpressionWithTypeArguments).typeArguments; - expression = (expression as ExpressionWithTypeArguments).expression; - } - if (token() === SyntaxKind.QuestionDotToken) { - parseErrorAtCurrentToken(Diagnostics.Invalid_optional_chain_from_new_expression_Did_you_mean_to_call_0, getTextOfNodeFromSourceText(sourceText, expression)); - } - const argumentList = token() === SyntaxKind.OpenParenToken ? parseArgumentList() : undefined; - return finishNode(factory.createNewExpression(expression, typeArguments, argumentList), pos); + const typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType); + if (reScanGreaterToken() !== SyntaxKind.GreaterThanToken) { + // If it doesn't have the closing `>` then it's definitely not an type argument list. + return undefined; + } + nextToken(); + + // We successfully parsed a type argument list. The next token determines whether we want to + // treat it as such. If the type argument list is followed by `(` or a template literal, as in + // `f(42)`, we favor the type argument interpretation even though JavaScript would view + // it as a relational expression. + return typeArguments && canFollowTypeArgumentsInExpression() ? typeArguments : undefined; + } + + function canFollowTypeArgumentsInExpression(): boolean { + switch (token()) { + // These tokens can follow a type argument list in a call expression. + case SyntaxKind.OpenParenToken: // foo( + case SyntaxKind.NoSubstitutionTemplateLiteral: // foo `...` + case SyntaxKind.TemplateHead: // foo `...${100}...` + return true; + // A type argument list followed by `<` never makes sense, and a type argument list followed + // by `>` is ambiguous with a (re-scanned) `>>` operator, so we disqualify both. Also, in + // this context, `+` and `-` are unary operators, not binary operators. + case SyntaxKind.LessThanToken: + case SyntaxKind.GreaterThanToken: + case SyntaxKind.PlusToken: + case SyntaxKind.MinusToken: + return false; } + // We favor the type argument list interpretation when it is immediately followed by + // a line break, a binary operator, or something that can't start an expression. + return scanner.hasPrecedingLineBreak() || isBinaryOperator() || !isStartOfExpression(); + } - // STATEMENTS - function parseBlock(ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const openBracePosition = scanner.getTokenPos(); - const openBraceParsed = parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage); - if (openBraceParsed || ignoreMissingOpenBrace) { - const multiLine = scanner.hasPrecedingLineBreak(); - const statements = parseList(ParsingContext.BlockStatements, parseStatement); - parseExpectedMatchingBrackets(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, openBraceParsed, openBracePosition); - const result = withJSDoc(finishNode(factory.createBlock(statements, multiLine), pos), hasJSDoc); - if (token() === SyntaxKind.EqualsToken) { - parseErrorAtCurrentToken(Diagnostics.Declaration_or_statement_expected_This_follows_a_block_of_statements_so_if_you_intended_to_write_a_destructuring_assignment_you_might_need_to_wrap_the_the_whole_assignment_in_parentheses); - nextToken(); + function parsePrimaryExpression(): PrimaryExpression { + switch (token()) { + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return parseLiteralNode(); + case SyntaxKind.ThisKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + return parseTokenNode(); + case SyntaxKind.OpenParenToken: + return parseParenthesizedExpression(); + case SyntaxKind.OpenBracketToken: + return parseArrayLiteralExpression(); + case SyntaxKind.OpenBraceToken: + return parseObjectLiteralExpression(); + case SyntaxKind.AsyncKeyword: + // Async arrow functions are parsed earlier in parseAssignmentExpressionOrHigher. + // If we encounter `async [no LineTerminator here] function` then this is an async + // function; otherwise, its an identifier. + if (!lookAhead(nextTokenIsFunctionKeywordOnSameLine)) { + break; } - return result; - } - else { - const statements = createMissingList(); - return withJSDoc(finishNode(factory.createBlock(statements, /*multiLine*/ undefined), pos), hasJSDoc); - } + return parseFunctionExpression(); + case SyntaxKind.ClassKeyword: + return parseClassExpression(); + case SyntaxKind.FunctionKeyword: + return parseFunctionExpression(); + case SyntaxKind.NewKeyword: + return parseNewExpressionOrNewDotTarget(); + case SyntaxKind.SlashToken: + case SyntaxKind.SlashEqualsToken: + if (reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) { + return parseLiteralNode(); + } + break; + case SyntaxKind.TemplateHead: + return parseTemplateExpression(/* isTaggedTemplate */ false); + case SyntaxKind.PrivateIdentifier: + return parsePrivateIdentifier(); } - function parseFunctionBlock(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block { - const savedYieldContext = inYieldContext(); - setYieldContext(!!(flags & SignatureFlags.Yield)); + return parseIdentifier(Diagnostics.Expression_expected); + } - const savedAwaitContext = inAwaitContext(); - setAwaitContext(!!(flags & SignatureFlags.Await)); + function parseParenthesizedExpression(): ParenthesizedExpression { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + return withJSDoc(finishNode(factory.createParenthesizedExpression(expression), pos), hasJSDoc); + } - const savedTopLevel = topLevel; - topLevel = false; + function parseSpreadElement(): Expression { + const pos = getNodePos(); + parseExpected(SyntaxKind.DotDotDotToken); + const expression = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); + return finishNode(factory.createSpreadElement(expression), pos); + } - // We may be in a [Decorator] context when parsing a function expression or - // arrow function. The body of the function is not in [Decorator] context. - const saveDecoratorContext = inDecoratorContext(); - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ false); - } + function parseArgumentOrArrayLiteralElement(): Expression { + return token() === SyntaxKind.DotDotDotToken ? parseSpreadElement() : + token() === SyntaxKind.CommaToken ? finishNode(factory.createOmittedExpression(), getNodePos()) : + parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); + } - const block = parseBlock(!!(flags & SignatureFlags.IgnoreMissingOpenBrace), diagnosticMessage); + function parseArgumentExpression(): Expression { + return doOutsideOfContext(disallowInAndDecoratorContext, parseArgumentOrArrayLiteralElement); + } - if (saveDecoratorContext) { - setDecoratorContext(/*val*/ true); - } + function parseArrayLiteralExpression(): ArrayLiteralExpression { + const pos = getNodePos(); + const openBracketPosition = scanner.getTokenPos(); + const openBracketParsed = parseExpected(SyntaxKind.OpenBracketToken); + const multiLine = scanner.hasPrecedingLineBreak(); + const elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArgumentOrArrayLiteralElement); + parseExpectedMatchingBrackets(SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken, openBracketParsed, openBracketPosition); + return finishNode(factory.createArrayLiteralExpression(elements, multiLine), pos); + } - topLevel = savedTopLevel; - setYieldContext(savedYieldContext); - setAwaitContext(savedAwaitContext); + function parseObjectLiteralElement(): ObjectLiteralElementLike { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); - return block; + if (parseOptionalToken(SyntaxKind.DotDotDotToken)) { + const expression = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); + return withJSDoc(finishNode(factory.createSpreadAssignment(expression), pos), hasJSDoc); } - function parseEmptyStatement(): Statement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.SemicolonToken); - return withJSDoc(finishNode(factory.createEmptyStatement(), pos), hasJSDoc); - } + const decorators = parseDecorators(); + const modifiers = parseModifiers(); - function parseIfStatement(): IfStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.IfKeyword); - const openParenPosition = scanner.getTokenPos(); - const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); - const expression = allowInAnd(parseExpression); - parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); - const thenStatement = parseStatement(); - const elseStatement = parseOptional(SyntaxKind.ElseKeyword) ? parseStatement() : undefined; - return withJSDoc(finishNode(factory.createIfStatement(expression, thenStatement, elseStatement), pos), hasJSDoc); + if (parseContextualModifier(SyntaxKind.GetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.GetAccessor, SignatureFlags.None); + } + if (parseContextualModifier(SyntaxKind.SetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.SetAccessor, SignatureFlags.None); } - function parseDoStatement(): DoStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.DoKeyword); - const statement = parseStatement(); - parseExpected(SyntaxKind.WhileKeyword); - const openParenPosition = scanner.getTokenPos(); - const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); - const expression = allowInAnd(parseExpression); - parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); + const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + const tokenIsIdentifier = isIdentifier(); + const name = parsePropertyName(); - // From: https://mail.mozilla.org/pipermail/es-discuss/2011-August/016188.html - // 157 min --- All allen at wirfs-brock.com CONF --- "do{;}while(false)false" prohibited in - // spec but allowed in consensus reality. Approved -- this is the de-facto standard whereby - // do;while(0)x will have a semicolon inserted before x. - parseOptional(SyntaxKind.SemicolonToken); - return withJSDoc(finishNode(factory.createDoStatement(statement, expression), pos), hasJSDoc); + // Disallowing of optional property assignments and definite assignment assertion happens in the grammar checker. + const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + const exclamationToken = parseOptionalToken(SyntaxKind.ExclamationToken); + + if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + return parseMethodDeclaration(pos, hasJSDoc, decorators, modifiers, asteriskToken, name, questionToken, exclamationToken); } - function parseWhileStatement(): WhileStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.WhileKeyword); - const openParenPosition = scanner.getTokenPos(); - const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); - const expression = allowInAnd(parseExpression); - parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); - const statement = parseStatement(); - return withJSDoc(finishNode(factory.createWhileStatement(expression, statement), pos), hasJSDoc); + // check if it is short-hand property assignment or normal property assignment + // NOTE: if token is EqualsToken it is interpreted as CoverInitializedName production + // CoverInitializedName[Yield] : + // IdentifierReference[?Yield] Initializer[In, ?Yield] + // this is necessary because ObjectLiteral productions are also used to cover grammar for ObjectAssignmentPattern + let node: Mutable; + const isShorthandPropertyAssignment = tokenIsIdentifier && (token() !== SyntaxKind.ColonToken); + if (isShorthandPropertyAssignment) { + const equalsToken = parseOptionalToken(SyntaxKind.EqualsToken); + const objectAssignmentInitializer = equalsToken ? allowInAnd(() => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true)) : undefined; + node = factory.createShorthandPropertyAssignment(name as Identifier, objectAssignmentInitializer); + // Save equals token for error reporting. + // TODO(rbuckton): Consider manufacturing this when we need to report an error as it is otherwise not useful. + node.equalsToken = equalsToken; } + else { + parseExpected(SyntaxKind.ColonToken); + const initializer = allowInAnd(() => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true)); + node = factory.createPropertyAssignment(name, initializer); + } + // Decorators, Modifiers, questionToken, and exclamationToken are not supported by property assignments and are reported in the grammar checker + node.illegalDecorators = decorators; + node.modifiers = modifiers; + node.questionToken = questionToken; + node.exclamationToken = exclamationToken; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseForOrForInOrForOfStatement(): Statement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.ForKeyword); - const awaitToken = parseOptionalToken(SyntaxKind.AwaitKeyword); - parseExpected(SyntaxKind.OpenParenToken); + function parseObjectLiteralExpression(): ObjectLiteralExpression { + const pos = getNodePos(); + const openBracePosition = scanner.getTokenPos(); + const openBraceParsed = parseExpected(SyntaxKind.OpenBraceToken); + const multiLine = scanner.hasPrecedingLineBreak(); + const properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement, /*considerSemicolonAsDelimiter*/ true); + parseExpectedMatchingBrackets(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, openBraceParsed, openBracePosition); + return finishNode(factory.createObjectLiteralExpression(properties, multiLine), pos); + } - let initializer!: VariableDeclarationList | Expression; - if (token() !== SyntaxKind.SemicolonToken) { - if (token() === SyntaxKind.VarKeyword || token() === SyntaxKind.LetKeyword || token() === SyntaxKind.ConstKeyword) { - initializer = parseVariableDeclarationList(/*inForStatementInitializer*/ true); - } - else { - initializer = disallowInAnd(parseExpression); - } - } + function parseFunctionExpression(): FunctionExpression { + // GeneratorExpression: + // function* BindingIdentifier [Yield][opt](FormalParameters[Yield]){ GeneratorBody } + // + // FunctionExpression: + // function BindingIdentifier[opt](FormalParameters){ FunctionBody } + const savedDecoratorContext = inDecoratorContext(); + setDecoratorContext(/*val*/ false); + + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const modifiers = parseModifiers(); + parseExpected(SyntaxKind.FunctionKeyword); + const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; + const name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalBindingIdentifier) : + isGenerator ? doInYieldContext(parseOptionalBindingIdentifier) : + isAsync ? doInAwaitContext(parseOptionalBindingIdentifier) : + parseOptionalBindingIdentifier(); + + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(isGenerator | isAsync); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + const body = parseFunctionBlock(isGenerator | isAsync); + + setDecoratorContext(savedDecoratorContext); + + const node = factory.createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - let node: IterationStatement; - if (awaitToken ? parseExpected(SyntaxKind.OfKeyword) : parseOptional(SyntaxKind.OfKeyword)) { - const expression = allowInAnd(() => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true)); - parseExpected(SyntaxKind.CloseParenToken); - node = factory.createForOfStatement(awaitToken, initializer, expression, parseStatement()); - } - else if (parseOptional(SyntaxKind.InKeyword)) { - const expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - node = factory.createForInStatement(initializer, expression, parseStatement()); - } - else { - parseExpected(SyntaxKind.SemicolonToken); - const condition = token() !== SyntaxKind.SemicolonToken && token() !== SyntaxKind.CloseParenToken - ? allowInAnd(parseExpression) - : undefined; - parseExpected(SyntaxKind.SemicolonToken); - const incrementor = token() !== SyntaxKind.CloseParenToken - ? allowInAnd(parseExpression) - : undefined; - parseExpected(SyntaxKind.CloseParenToken); - node = factory.createForStatement(initializer, condition, incrementor, parseStatement()); - } + function parseOptionalBindingIdentifier(): Identifier | undefined { + return isBindingIdentifier() ? parseBindingIdentifier() : undefined; + } - return withJSDoc(finishNode(node, pos) as ForStatement | ForInOrOfStatement, hasJSDoc); + function parseNewExpressionOrNewDotTarget(): NewExpression | MetaProperty { + const pos = getNodePos(); + parseExpected(SyntaxKind.NewKeyword); + if (parseOptional(SyntaxKind.DotToken)) { + const name = parseIdentifierName(); + return finishNode(factory.createMetaProperty(SyntaxKind.NewKeyword, name), pos); } + const expressionPos = getNodePos(); + let expression: LeftHandSideExpression = parseMemberExpressionRest(expressionPos, parsePrimaryExpression(), /*allowOptionalChain*/ false); + let typeArguments: NodeArray | undefined; + // Absorb type arguments into NewExpression when preceding expression is ExpressionWithTypeArguments + if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) { + typeArguments = (expression as ExpressionWithTypeArguments).typeArguments; + expression = (expression as ExpressionWithTypeArguments).expression; + } + if (token() === SyntaxKind.QuestionDotToken) { + parseErrorAtCurrentToken(Diagnostics.Invalid_optional_chain_from_new_expression_Did_you_mean_to_call_0, getTextOfNodeFromSourceText(sourceText, expression)); + } + const argumentList = token() === SyntaxKind.OpenParenToken ? parseArgumentList() : undefined; + return finishNode(factory.createNewExpression(expression, typeArguments, argumentList), pos); + } - function parseBreakOrContinueStatement(kind: SyntaxKind): BreakOrContinueStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - - parseExpected(kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword); - const label = canParseSemicolon() ? undefined : parseIdentifier(); + // STATEMENTS + function parseBlock(ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const openBracePosition = scanner.getTokenPos(); + const openBraceParsed = parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage); + if (openBraceParsed || ignoreMissingOpenBrace) { + const multiLine = scanner.hasPrecedingLineBreak(); + const statements = parseList(ParsingContext.BlockStatements, parseStatement); + parseExpectedMatchingBrackets(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, openBraceParsed, openBracePosition); + const result = withJSDoc(finishNode(factory.createBlock(statements, multiLine), pos), hasJSDoc); + if (token() === SyntaxKind.EqualsToken) { + parseErrorAtCurrentToken(Diagnostics.Declaration_or_statement_expected_This_follows_a_block_of_statements_so_if_you_intended_to_write_a_destructuring_assignment_you_might_need_to_wrap_the_the_whole_assignment_in_parentheses); + nextToken(); + } - parseSemicolon(); - const node = kind === SyntaxKind.BreakStatement - ? factory.createBreakStatement(label) - : factory.createContinueStatement(label); - return withJSDoc(finishNode(node, pos), hasJSDoc); + return result; } - - function parseReturnStatement(): ReturnStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.ReturnKeyword); - const expression = canParseSemicolon() ? undefined : allowInAnd(parseExpression); - parseSemicolon(); - return withJSDoc(finishNode(factory.createReturnStatement(expression), pos), hasJSDoc); + else { + const statements = createMissingList(); + return withJSDoc(finishNode(factory.createBlock(statements, /*multiLine*/ undefined), pos), hasJSDoc); } + } - function parseWithStatement(): WithStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.WithKeyword); - const openParenPosition = scanner.getTokenPos(); - const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); - const expression = allowInAnd(parseExpression); - parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); - const statement = doInsideOfContext(NodeFlags.InWithStatement, parseStatement); - return withJSDoc(finishNode(factory.createWithStatement(expression, statement), pos), hasJSDoc); - } + function parseFunctionBlock(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block { + const savedYieldContext = inYieldContext(); + setYieldContext(!!(flags & SignatureFlags.Yield)); - function parseCaseClause(): CaseClause { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.CaseKeyword); - const expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.ColonToken); - const statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); - return withJSDoc(finishNode(factory.createCaseClause(expression, statements), pos), hasJSDoc); - } + const savedAwaitContext = inAwaitContext(); + setAwaitContext(!!(flags & SignatureFlags.Await)); - function parseDefaultClause(): DefaultClause { - const pos = getNodePos(); - parseExpected(SyntaxKind.DefaultKeyword); - parseExpected(SyntaxKind.ColonToken); - const statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); - return finishNode(factory.createDefaultClause(statements), pos); - } + const savedTopLevel = topLevel; + topLevel = false; - function parseCaseOrDefaultClause(): CaseOrDefaultClause { - return token() === SyntaxKind.CaseKeyword ? parseCaseClause() : parseDefaultClause(); + // We may be in a [Decorator] context when parsing a function expression or + // arrow function. The body of the function is not in [Decorator] context. + const saveDecoratorContext = inDecoratorContext(); + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ false); } - function parseCaseBlock(): CaseBlock { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBraceToken); - const clauses = parseList(ParsingContext.SwitchClauses, parseCaseOrDefaultClause); - parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(factory.createCaseBlock(clauses), pos); - } + const block = parseBlock(!!(flags & SignatureFlags.IgnoreMissingOpenBrace), diagnosticMessage); - function parseSwitchStatement(): SwitchStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.SwitchKeyword); - parseExpected(SyntaxKind.OpenParenToken); - const expression = allowInAnd(parseExpression); - parseExpected(SyntaxKind.CloseParenToken); - const caseBlock = parseCaseBlock(); - return withJSDoc(finishNode(factory.createSwitchStatement(expression, caseBlock), pos), hasJSDoc); + if (saveDecoratorContext) { + setDecoratorContext(/*val*/ true); } - function parseThrowStatement(): ThrowStatement { - // ThrowStatement[Yield] : - // throw [no LineTerminator here]Expression[In, ?Yield]; + topLevel = savedTopLevel; + setYieldContext(savedYieldContext); + setAwaitContext(savedAwaitContext); - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.ThrowKeyword); - - // Because of automatic semicolon insertion, we need to report error if this - // throw could be terminated with a semicolon. Note: we can't call 'parseExpression' - // directly as that might consume an expression on the following line. - // Instead, we create a "missing" identifier, but don't report an error. The actual error - // will be reported in the grammar walker. - let expression = scanner.hasPrecedingLineBreak() ? undefined : allowInAnd(parseExpression); - if (expression === undefined) { - identifierCount++; - expression = finishNode(factory.createIdentifier(""), getNodePos()); - } - if (!tryParseSemicolon()) { - parseErrorForMissingSemicolonAfter(expression); - } - return withJSDoc(finishNode(factory.createThrowStatement(expression), pos), hasJSDoc); - } + return block; + } - // TODO: Review for error recovery - function parseTryStatement(): TryStatement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); + function parseEmptyStatement(): Statement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.SemicolonToken); + return withJSDoc(finishNode(factory.createEmptyStatement(), pos), hasJSDoc); + } - parseExpected(SyntaxKind.TryKeyword); - const tryBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); - const catchClause = token() === SyntaxKind.CatchKeyword ? parseCatchClause() : undefined; + function parseIfStatement(): IfStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.IfKeyword); + const openParenPosition = scanner.getTokenPos(); + const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); + const thenStatement = parseStatement(); + const elseStatement = parseOptional(SyntaxKind.ElseKeyword) ? parseStatement() : undefined; + return withJSDoc(finishNode(factory.createIfStatement(expression, thenStatement, elseStatement), pos), hasJSDoc); + } - // If we don't have a catch clause, then we must have a finally clause. Try to parse - // one out no matter what. - let finallyBlock: Block | undefined; - if (!catchClause || token() === SyntaxKind.FinallyKeyword) { - parseExpected(SyntaxKind.FinallyKeyword, Diagnostics.catch_or_finally_expected); - finallyBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); - } + function parseDoStatement(): DoStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.DoKeyword); + const statement = parseStatement(); + parseExpected(SyntaxKind.WhileKeyword); + const openParenPosition = scanner.getTokenPos(); + const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); + + // From: https://mail.mozilla.org/pipermail/es-discuss/2011-August/016188.html + // 157 min --- All allen at wirfs-brock.com CONF --- "do{;}while(false)false" prohibited in + // spec but allowed in consensus reality. Approved -- this is the de-facto standard whereby + // do;while(0)x will have a semicolon inserted before x. + parseOptional(SyntaxKind.SemicolonToken); + return withJSDoc(finishNode(factory.createDoStatement(statement, expression), pos), hasJSDoc); + } - return withJSDoc(finishNode(factory.createTryStatement(tryBlock, catchClause, finallyBlock), pos), hasJSDoc); - } + function parseWhileStatement(): WhileStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.WhileKeyword); + const openParenPosition = scanner.getTokenPos(); + const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); + const statement = parseStatement(); + return withJSDoc(finishNode(factory.createWhileStatement(expression, statement), pos), hasJSDoc); + } - function parseCatchClause(): CatchClause { - const pos = getNodePos(); - parseExpected(SyntaxKind.CatchKeyword); + function parseForOrForInOrForOfStatement(): Statement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.ForKeyword); + const awaitToken = parseOptionalToken(SyntaxKind.AwaitKeyword); + parseExpected(SyntaxKind.OpenParenToken); - let variableDeclaration; - if (parseOptional(SyntaxKind.OpenParenToken)) { - variableDeclaration = parseVariableDeclaration(); - parseExpected(SyntaxKind.CloseParenToken); + let initializer!: VariableDeclarationList | Expression; + if (token() !== SyntaxKind.SemicolonToken) { + if (token() === SyntaxKind.VarKeyword || token() === SyntaxKind.LetKeyword || token() === SyntaxKind.ConstKeyword) { + initializer = parseVariableDeclarationList(/*inForStatementInitializer*/ true); } else { - // Keep shape of node to avoid degrading performance. - variableDeclaration = undefined; + initializer = disallowInAnd(parseExpression); } - - const block = parseBlock(/*ignoreMissingOpenBrace*/ false); - return finishNode(factory.createCatchClause(variableDeclaration, block), pos); } - function parseDebuggerStatement(): Statement { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - parseExpected(SyntaxKind.DebuggerKeyword); - parseSemicolon(); - return withJSDoc(finishNode(factory.createDebuggerStatement(), pos), hasJSDoc); + let node: IterationStatement; + if (awaitToken ? parseExpected(SyntaxKind.OfKeyword) : parseOptional(SyntaxKind.OfKeyword)) { + const expression = allowInAnd(() => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true)); + parseExpected(SyntaxKind.CloseParenToken); + node = factory.createForOfStatement(awaitToken, initializer, expression, parseStatement()); } - - function parseExpressionOrLabeledStatement(): ExpressionStatement | LabeledStatement { - // Avoiding having to do the lookahead for a labeled statement by just trying to parse - // out an expression, seeing if it is identifier and then seeing if it is followed by - // a colon. - const pos = getNodePos(); - let hasJSDoc = hasPrecedingJSDocComment(); - let node: ExpressionStatement | LabeledStatement; - const hasParen = token() === SyntaxKind.OpenParenToken; + else if (parseOptional(SyntaxKind.InKeyword)) { const expression = allowInAnd(parseExpression); - if (ts.isIdentifier(expression) && parseOptional(SyntaxKind.ColonToken)) { - node = factory.createLabeledStatement(expression, parseStatement()); - } - else { - if (!tryParseSemicolon()) { - parseErrorForMissingSemicolonAfter(expression); - } - node = factory.createExpressionStatement(expression); - if (hasParen) { - // do not parse the same jsdoc twice - hasJSDoc = false; - } - } - return withJSDoc(finishNode(node, pos), hasJSDoc); + parseExpected(SyntaxKind.CloseParenToken); + node = factory.createForInStatement(initializer, expression, parseStatement()); } - - function nextTokenIsIdentifierOrKeywordOnSameLine() { - nextToken(); - return tokenIsIdentifierOrKeyword(token()) && !scanner.hasPrecedingLineBreak(); + else { + parseExpected(SyntaxKind.SemicolonToken); + const condition = token() !== SyntaxKind.SemicolonToken && token() !== SyntaxKind.CloseParenToken + ? allowInAnd(parseExpression) + : undefined; + parseExpected(SyntaxKind.SemicolonToken); + const incrementor = token() !== SyntaxKind.CloseParenToken + ? allowInAnd(parseExpression) + : undefined; + parseExpected(SyntaxKind.CloseParenToken); + node = factory.createForStatement(initializer, condition, incrementor, parseStatement()); } - function nextTokenIsClassKeywordOnSameLine() { - nextToken(); - return token() === SyntaxKind.ClassKeyword && !scanner.hasPrecedingLineBreak(); - } + return withJSDoc(finishNode(node, pos) as ForStatement | ForInOrOfStatement, hasJSDoc); + } - function nextTokenIsFunctionKeywordOnSameLine() { - nextToken(); - return token() === SyntaxKind.FunctionKeyword && !scanner.hasPrecedingLineBreak(); - } + function parseBreakOrContinueStatement(kind: SyntaxKind): BreakOrContinueStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); - function nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine() { - nextToken(); - return (tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral || token() === SyntaxKind.StringLiteral) && !scanner.hasPrecedingLineBreak(); - } + parseExpected(kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword); + const label = canParseSemicolon() ? undefined : parseIdentifier(); - function isDeclaration(): boolean { - while (true) { - switch (token()) { - case SyntaxKind.VarKeyword: - case SyntaxKind.LetKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.ClassKeyword: - case SyntaxKind.EnumKeyword: - return true; + parseSemicolon(); + const node = kind === SyntaxKind.BreakStatement + ? factory.createBreakStatement(label) + : factory.createContinueStatement(label); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - // 'declare', 'module', 'namespace', 'interface'* and 'type' are all legal JavaScript identifiers; - // however, an identifier cannot be followed by another identifier on the same line. This is what we - // count on to parse out the respective declarations. For instance, we exploit this to say that - // - // namespace n - // - // can be none other than the beginning of a namespace declaration, but need to respect that JavaScript sees - // - // namespace - // n - // - // as the identifier 'namespace' on one line followed by the identifier 'n' on another. - // We need to look one token ahead to see if it permissible to try parsing a declaration. - // - // *Note*: 'interface' is actually a strict mode reserved word. So while - // - // "use strict" - // interface - // I {} - // - // could be legal, it would add complexity for very little gain. - case SyntaxKind.InterfaceKeyword: - case SyntaxKind.TypeKeyword: - return nextTokenIsIdentifierOnSameLine(); - case SyntaxKind.ModuleKeyword: - case SyntaxKind.NamespaceKeyword: - return nextTokenIsIdentifierOrStringLiteralOnSameLine(); - case SyntaxKind.AbstractKeyword: - case SyntaxKind.AccessorKeyword: - case SyntaxKind.AsyncKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.PublicKeyword: - case SyntaxKind.ReadonlyKeyword: - nextToken(); - // ASI takes effect for this modifier. - if (scanner.hasPrecedingLineBreak()) { - return false; - } - continue; + function parseReturnStatement(): ReturnStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.ReturnKeyword); + const expression = canParseSemicolon() ? undefined : allowInAnd(parseExpression); + parseSemicolon(); + return withJSDoc(finishNode(factory.createReturnStatement(expression), pos), hasJSDoc); + } - case SyntaxKind.GlobalKeyword: - nextToken(); - return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.Identifier || token() === SyntaxKind.ExportKeyword; + function parseWithStatement(): WithStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.WithKeyword); + const openParenPosition = scanner.getTokenPos(); + const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); + const statement = doInsideOfContext(NodeFlags.InWithStatement, parseStatement); + return withJSDoc(finishNode(factory.createWithStatement(expression, statement), pos), hasJSDoc); + } - case SyntaxKind.ImportKeyword: - nextToken(); - return token() === SyntaxKind.StringLiteral || token() === SyntaxKind.AsteriskToken || - token() === SyntaxKind.OpenBraceToken || tokenIsIdentifierOrKeyword(token()); - case SyntaxKind.ExportKeyword: - let currentToken = nextToken(); - if (currentToken === SyntaxKind.TypeKeyword) { - currentToken = lookAhead(nextToken); - } - if (currentToken === SyntaxKind.EqualsToken || currentToken === SyntaxKind.AsteriskToken || - currentToken === SyntaxKind.OpenBraceToken || currentToken === SyntaxKind.DefaultKeyword || - currentToken === SyntaxKind.AsKeyword) { - return true; - } - continue; + function parseCaseClause(): CaseClause { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.CaseKeyword); + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.ColonToken); + const statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); + return withJSDoc(finishNode(factory.createCaseClause(expression, statements), pos), hasJSDoc); + } - case SyntaxKind.StaticKeyword: - nextToken(); - continue; - default: - return false; - } - } - } + function parseDefaultClause(): DefaultClause { + const pos = getNodePos(); + parseExpected(SyntaxKind.DefaultKeyword); + parseExpected(SyntaxKind.ColonToken); + const statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); + return finishNode(factory.createDefaultClause(statements), pos); + } - function isStartOfDeclaration(): boolean { - return lookAhead(isDeclaration); - } + function parseCaseOrDefaultClause(): CaseOrDefaultClause { + return token() === SyntaxKind.CaseKeyword ? parseCaseClause() : parseDefaultClause(); + } - function isStartOfStatement(): boolean { - switch (token()) { - case SyntaxKind.AtToken: - case SyntaxKind.SemicolonToken: - case SyntaxKind.OpenBraceToken: - case SyntaxKind.VarKeyword: - case SyntaxKind.LetKeyword: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.ClassKeyword: - case SyntaxKind.EnumKeyword: - case SyntaxKind.IfKeyword: - case SyntaxKind.DoKeyword: - case SyntaxKind.WhileKeyword: - case SyntaxKind.ForKeyword: - case SyntaxKind.ContinueKeyword: - case SyntaxKind.BreakKeyword: - case SyntaxKind.ReturnKeyword: - case SyntaxKind.WithKeyword: - case SyntaxKind.SwitchKeyword: - case SyntaxKind.ThrowKeyword: - case SyntaxKind.TryKeyword: - case SyntaxKind.DebuggerKeyword: - // 'catch' and 'finally' do not actually indicate that the code is part of a statement, - // however, we say they are here so that we may gracefully parse them and error later. - // falls through - case SyntaxKind.CatchKeyword: - case SyntaxKind.FinallyKeyword: - return true; + function parseCaseBlock(): CaseBlock { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBraceToken); + const clauses = parseList(ParsingContext.SwitchClauses, parseCaseOrDefaultClause); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(factory.createCaseBlock(clauses), pos); + } - case SyntaxKind.ImportKeyword: - return isStartOfDeclaration() || lookAhead(nextTokenIsOpenParenOrLessThanOrDot); + function parseSwitchStatement(): SwitchStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.SwitchKeyword); + parseExpected(SyntaxKind.OpenParenToken); + const expression = allowInAnd(parseExpression); + parseExpected(SyntaxKind.CloseParenToken); + const caseBlock = parseCaseBlock(); + return withJSDoc(finishNode(factory.createSwitchStatement(expression, caseBlock), pos), hasJSDoc); + } - case SyntaxKind.ConstKeyword: - case SyntaxKind.ExportKeyword: - return isStartOfDeclaration(); + function parseThrowStatement(): ThrowStatement { + // ThrowStatement[Yield] : + // throw [no LineTerminator here]Expression[In, ?Yield]; + + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.ThrowKeyword); + + // Because of automatic semicolon insertion, we need to report error if this + // throw could be terminated with a semicolon. Note: we can't call 'parseExpression' + // directly as that might consume an expression on the following line. + // Instead, we create a "missing" identifier, but don't report an error. The actual error + // will be reported in the grammar walker. + let expression = scanner.hasPrecedingLineBreak() ? undefined : allowInAnd(parseExpression); + if (expression === undefined) { + identifierCount++; + expression = finishNode(factory.createIdentifier(""), getNodePos()); + } + if (!tryParseSemicolon()) { + parseErrorForMissingSemicolonAfter(expression); + } + return withJSDoc(finishNode(factory.createThrowStatement(expression), pos), hasJSDoc); + } - case SyntaxKind.AsyncKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.InterfaceKeyword: - case SyntaxKind.ModuleKeyword: - case SyntaxKind.NamespaceKeyword: - case SyntaxKind.TypeKeyword: - case SyntaxKind.GlobalKeyword: - // When these don't start a declaration, they're an identifier in an expression statement - return true; + // TODO: Review for error recovery + function parseTryStatement(): TryStatement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); - case SyntaxKind.AccessorKeyword: - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.StaticKeyword: - case SyntaxKind.ReadonlyKeyword: - // When these don't start a declaration, they may be the start of a class member if an identifier - // immediately follows. Otherwise they're an identifier in an expression statement. - return isStartOfDeclaration() || !lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine); + parseExpected(SyntaxKind.TryKeyword); + const tryBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); + const catchClause = token() === SyntaxKind.CatchKeyword ? parseCatchClause() : undefined; - default: - return isStartOfExpression(); - } + // If we don't have a catch clause, then we must have a finally clause. Try to parse + // one out no matter what. + let finallyBlock: Block | undefined; + if (!catchClause || token() === SyntaxKind.FinallyKeyword) { + parseExpected(SyntaxKind.FinallyKeyword, Diagnostics.catch_or_finally_expected); + finallyBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); } - function nextTokenIsBindingIdentifierOrStartOfDestructuring() { - nextToken(); - return isBindingIdentifier() || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.OpenBracketToken; - } + return withJSDoc(finishNode(factory.createTryStatement(tryBlock, catchClause, finallyBlock), pos), hasJSDoc); + } - function isLetDeclaration() { - // In ES6 'let' always starts a lexical declaration if followed by an identifier or { - // or [. - return lookAhead(nextTokenIsBindingIdentifierOrStartOfDestructuring); + function parseCatchClause(): CatchClause { + const pos = getNodePos(); + parseExpected(SyntaxKind.CatchKeyword); + + let variableDeclaration; + if (parseOptional(SyntaxKind.OpenParenToken)) { + variableDeclaration = parseVariableDeclaration(); + parseExpected(SyntaxKind.CloseParenToken); + } + else { + // Keep shape of node to avoid degrading performance. + variableDeclaration = undefined; } - function parseStatement(): Statement { - switch (token()) { - case SyntaxKind.SemicolonToken: - return parseEmptyStatement(); - case SyntaxKind.OpenBraceToken: - return parseBlock(/*ignoreMissingOpenBrace*/ false); - case SyntaxKind.VarKeyword: - return parseVariableStatement(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); - case SyntaxKind.LetKeyword: - if (isLetDeclaration()) { - return parseVariableStatement(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); - } - break; - case SyntaxKind.FunctionKeyword: - return parseFunctionDeclaration(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); - case SyntaxKind.ClassKeyword: - return parseClassDeclaration(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); - case SyntaxKind.IfKeyword: - return parseIfStatement(); - case SyntaxKind.DoKeyword: - return parseDoStatement(); - case SyntaxKind.WhileKeyword: - return parseWhileStatement(); - case SyntaxKind.ForKeyword: - return parseForOrForInOrForOfStatement(); - case SyntaxKind.ContinueKeyword: - return parseBreakOrContinueStatement(SyntaxKind.ContinueStatement); - case SyntaxKind.BreakKeyword: - return parseBreakOrContinueStatement(SyntaxKind.BreakStatement); - case SyntaxKind.ReturnKeyword: - return parseReturnStatement(); - case SyntaxKind.WithKeyword: - return parseWithStatement(); - case SyntaxKind.SwitchKeyword: - return parseSwitchStatement(); - case SyntaxKind.ThrowKeyword: - return parseThrowStatement(); - case SyntaxKind.TryKeyword: - // Include 'catch' and 'finally' for error recovery. - // falls through - case SyntaxKind.CatchKeyword: - case SyntaxKind.FinallyKeyword: - return parseTryStatement(); - case SyntaxKind.DebuggerKeyword: - return parseDebuggerStatement(); - case SyntaxKind.AtToken: - return parseDeclaration(); - case SyntaxKind.AsyncKeyword: - case SyntaxKind.InterfaceKeyword: - case SyntaxKind.TypeKeyword: - case SyntaxKind.ModuleKeyword: - case SyntaxKind.NamespaceKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.EnumKeyword: - case SyntaxKind.ExportKeyword: - case SyntaxKind.ImportKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.PublicKeyword: - case SyntaxKind.AbstractKeyword: - case SyntaxKind.AccessorKeyword: - case SyntaxKind.StaticKeyword: - case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.GlobalKeyword: - if (isStartOfDeclaration()) { - return parseDeclaration(); - } - break; + const block = parseBlock(/*ignoreMissingOpenBrace*/ false); + return finishNode(factory.createCatchClause(variableDeclaration, block), pos); + } + + function parseDebuggerStatement(): Statement { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + parseExpected(SyntaxKind.DebuggerKeyword); + parseSemicolon(); + return withJSDoc(finishNode(factory.createDebuggerStatement(), pos), hasJSDoc); + } + + function parseExpressionOrLabeledStatement(): ExpressionStatement | LabeledStatement { + // Avoiding having to do the lookahead for a labeled statement by just trying to parse + // out an expression, seeing if it is identifier and then seeing if it is followed by + // a colon. + const pos = getNodePos(); + let hasJSDoc = hasPrecedingJSDocComment(); + let node: ExpressionStatement | LabeledStatement; + const hasParen = token() === SyntaxKind.OpenParenToken; + const expression = allowInAnd(parseExpression); + if (ts.isIdentifier(expression) && parseOptional(SyntaxKind.ColonToken)) { + node = factory.createLabeledStatement(expression, parseStatement()); + } + else { + if (!tryParseSemicolon()) { + parseErrorForMissingSemicolonAfter(expression); + } + node = factory.createExpressionStatement(expression); + if (hasParen) { + // do not parse the same jsdoc twice + hasJSDoc = false; } - return parseExpressionOrLabeledStatement(); } + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function isDeclareModifier(modifier: Modifier) { - return modifier.kind === SyntaxKind.DeclareKeyword; - } + function nextTokenIsIdentifierOrKeywordOnSameLine() { + nextToken(); + return tokenIsIdentifierOrKeyword(token()) && !scanner.hasPrecedingLineBreak(); + } - function parseDeclaration(): Statement { - // `parseListElement` attempted to get the reused node at this position, - // but the ambient context flag was not yet set, so the node appeared - // not reusable in that context. - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const decorators = parseDecorators(); - const modifiers = parseModifiers(); - const isAmbient = some(modifiers, isDeclareModifier); - if (isAmbient) { - const node = tryReuseAmbientDeclaration(pos); - if (node) { - return node; - } + function nextTokenIsClassKeywordOnSameLine() { + nextToken(); + return token() === SyntaxKind.ClassKeyword && !scanner.hasPrecedingLineBreak(); + } - for (const m of modifiers!) { - (m as Mutable).flags |= NodeFlags.Ambient; - } - return doInsideOfContext(NodeFlags.Ambient, () => parseDeclarationWorker(pos, hasJSDoc, decorators, modifiers)); - } - else { - return parseDeclarationWorker(pos, hasJSDoc, decorators, modifiers); - } - } + function nextTokenIsFunctionKeywordOnSameLine() { + nextToken(); + return token() === SyntaxKind.FunctionKeyword && !scanner.hasPrecedingLineBreak(); + } - function tryReuseAmbientDeclaration(pos: number): Statement | undefined { - return doInsideOfContext(NodeFlags.Ambient, () => { - const node = currentNode(parsingContext, pos); - if (node) { - return consumeNode(node) as Statement; - } - }); - } + function nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine() { + nextToken(); + return (tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral || token() === SyntaxKind.StringLiteral) && !scanner.hasPrecedingLineBreak(); + } - function parseDeclarationWorker(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): Statement { + function isDeclaration(): boolean { + while (true) { switch (token()) { case SyntaxKind.VarKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.ConstKeyword: - return parseVariableStatement(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.FunctionKeyword: - return parseFunctionDeclaration(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.ClassKeyword: - return parseClassDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.EnumKeyword: + return true; + + // 'declare', 'module', 'namespace', 'interface'* and 'type' are all legal JavaScript identifiers; + // however, an identifier cannot be followed by another identifier on the same line. This is what we + // count on to parse out the respective declarations. For instance, we exploit this to say that + // + // namespace n + // + // can be none other than the beginning of a namespace declaration, but need to respect that JavaScript sees + // + // namespace + // n + // + // as the identifier 'namespace' on one line followed by the identifier 'n' on another. + // We need to look one token ahead to see if it permissible to try parsing a declaration. + // + // *Note*: 'interface' is actually a strict mode reserved word. So while + // + // "use strict" + // interface + // I {} + // + // could be legal, it would add complexity for very little gain. case SyntaxKind.InterfaceKeyword: - return parseInterfaceDeclaration(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.TypeKeyword: - return parseTypeAliasDeclaration(pos, hasJSDoc, decorators, modifiers); - case SyntaxKind.EnumKeyword: - return parseEnumDeclaration(pos, hasJSDoc, decorators, modifiers); - case SyntaxKind.GlobalKeyword: + return nextTokenIsIdentifierOnSameLine(); case SyntaxKind.ModuleKeyword: case SyntaxKind.NamespaceKeyword: - return parseModuleDeclaration(pos, hasJSDoc, decorators, modifiers); + return nextTokenIsIdentifierOrStringLiteralOnSameLine(); + case SyntaxKind.AbstractKeyword: + case SyntaxKind.AccessorKeyword: + case SyntaxKind.AsyncKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PublicKeyword: + case SyntaxKind.ReadonlyKeyword: + nextToken(); + // ASI takes effect for this modifier. + if (scanner.hasPrecedingLineBreak()) { + return false; + } + continue; + + case SyntaxKind.GlobalKeyword: + nextToken(); + return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.Identifier || token() === SyntaxKind.ExportKeyword; + case SyntaxKind.ImportKeyword: - return parseImportDeclarationOrImportEqualsDeclaration(pos, hasJSDoc, decorators, modifiers); - case SyntaxKind.ExportKeyword: nextToken(); - switch (token()) { - case SyntaxKind.DefaultKeyword: - case SyntaxKind.EqualsToken: - return parseExportAssignment(pos, hasJSDoc, decorators, modifiers); - case SyntaxKind.AsKeyword: - return parseNamespaceExportDeclaration(pos, hasJSDoc, decorators, modifiers); - default: - return parseExportDeclaration(pos, hasJSDoc, decorators, modifiers); + return token() === SyntaxKind.StringLiteral || token() === SyntaxKind.AsteriskToken || + token() === SyntaxKind.OpenBraceToken || tokenIsIdentifierOrKeyword(token()); + case SyntaxKind.ExportKeyword: + let currentToken = nextToken(); + if (currentToken === SyntaxKind.TypeKeyword) { + currentToken = lookAhead(nextToken); } - default: - if (decorators || modifiers) { - // We reached this point because we encountered decorators and/or modifiers and assumed a declaration - // would follow. For recovery and error reporting purposes, return an incomplete declaration. - const missing = createMissingNode(SyntaxKind.MissingDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); - setTextRangePos(missing, pos); - missing.illegalDecorators = decorators; - missing.modifiers = modifiers; - return missing; + if (currentToken === SyntaxKind.EqualsToken || currentToken === SyntaxKind.AsteriskToken || + currentToken === SyntaxKind.OpenBraceToken || currentToken === SyntaxKind.DefaultKeyword || + currentToken === SyntaxKind.AsKeyword) { + return true; } - return undefined!; // TODO: GH#18217 + continue; + + case SyntaxKind.StaticKeyword: + nextToken(); + continue; + default: + return false; } } + } - function nextTokenIsIdentifierOrStringLiteralOnSameLine() { - nextToken(); - return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token() === SyntaxKind.StringLiteral); - } + function isStartOfDeclaration(): boolean { + return lookAhead(isDeclaration); + } - function parseFunctionBlockOrSemicolon(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block | undefined { - if (token() !== SyntaxKind.OpenBraceToken) { - if (flags & SignatureFlags.Type) { - parseTypeMemberSemicolon(); - return; - } - if (canParseSemicolon()) { - parseSemicolon(); - return; - } - } - return parseFunctionBlock(flags, diagnosticMessage); - } + function isStartOfStatement(): boolean { + switch (token()) { + case SyntaxKind.AtToken: + case SyntaxKind.SemicolonToken: + case SyntaxKind.OpenBraceToken: + case SyntaxKind.VarKeyword: + case SyntaxKind.LetKeyword: + case SyntaxKind.FunctionKeyword: + case SyntaxKind.ClassKeyword: + case SyntaxKind.EnumKeyword: + case SyntaxKind.IfKeyword: + case SyntaxKind.DoKeyword: + case SyntaxKind.WhileKeyword: + case SyntaxKind.ForKeyword: + case SyntaxKind.ContinueKeyword: + case SyntaxKind.BreakKeyword: + case SyntaxKind.ReturnKeyword: + case SyntaxKind.WithKeyword: + case SyntaxKind.SwitchKeyword: + case SyntaxKind.ThrowKeyword: + case SyntaxKind.TryKeyword: + case SyntaxKind.DebuggerKeyword: + // 'catch' and 'finally' do not actually indicate that the code is part of a statement, + // however, we say they are here so that we may gracefully parse them and error later. + // falls through + case SyntaxKind.CatchKeyword: + case SyntaxKind.FinallyKeyword: + return true; - // DECLARATIONS + case SyntaxKind.ImportKeyword: + return isStartOfDeclaration() || lookAhead(nextTokenIsOpenParenOrLessThanOrDot); + + case SyntaxKind.ConstKeyword: + case SyntaxKind.ExportKeyword: + return isStartOfDeclaration(); + + case SyntaxKind.AsyncKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.InterfaceKeyword: + case SyntaxKind.ModuleKeyword: + case SyntaxKind.NamespaceKeyword: + case SyntaxKind.TypeKeyword: + case SyntaxKind.GlobalKeyword: + // When these don't start a declaration, they're an identifier in an expression statement + return true; - function parseArrayBindingElement(): ArrayBindingElement { - const pos = getNodePos(); - if (token() === SyntaxKind.CommaToken) { - return finishNode(factory.createOmittedExpression(), pos); - } - const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - const name = parseIdentifierOrPattern(); - const initializer = parseInitializer(); - return finishNode(factory.createBindingElement(dotDotDotToken, /*propertyName*/ undefined, name, initializer), pos); - } + case SyntaxKind.AccessorKeyword: + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.StaticKeyword: + case SyntaxKind.ReadonlyKeyword: + // When these don't start a declaration, they may be the start of a class member if an identifier + // immediately follows. Otherwise they're an identifier in an expression statement. + return isStartOfDeclaration() || !lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine); - function parseObjectBindingElement(): BindingElement { - const pos = getNodePos(); - const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - const tokenIsIdentifier = isBindingIdentifier(); - let propertyName: PropertyName | undefined = parsePropertyName(); - let name: BindingName; - if (tokenIsIdentifier && token() !== SyntaxKind.ColonToken) { - name = propertyName as Identifier; - propertyName = undefined; - } - else { - parseExpected(SyntaxKind.ColonToken); - name = parseIdentifierOrPattern(); - } - const initializer = parseInitializer(); - return finishNode(factory.createBindingElement(dotDotDotToken, propertyName, name, initializer), pos); + default: + return isStartOfExpression(); } + } - function parseObjectBindingPattern(): ObjectBindingPattern { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBraceToken); - const elements = parseDelimitedList(ParsingContext.ObjectBindingElements, parseObjectBindingElement); - parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(factory.createObjectBindingPattern(elements), pos); - } + function nextTokenIsBindingIdentifierOrStartOfDestructuring() { + nextToken(); + return isBindingIdentifier() || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.OpenBracketToken; + } - function parseArrayBindingPattern(): ArrayBindingPattern { - const pos = getNodePos(); - parseExpected(SyntaxKind.OpenBracketToken); - const elements = parseDelimitedList(ParsingContext.ArrayBindingElements, parseArrayBindingElement); - parseExpected(SyntaxKind.CloseBracketToken); - return finishNode(factory.createArrayBindingPattern(elements), pos); - } + function isLetDeclaration() { + // In ES6 'let' always starts a lexical declaration if followed by an identifier or { + // or [. + return lookAhead(nextTokenIsBindingIdentifierOrStartOfDestructuring); + } - function isBindingIdentifierOrPrivateIdentifierOrPattern() { - return token() === SyntaxKind.OpenBraceToken - || token() === SyntaxKind.OpenBracketToken - || token() === SyntaxKind.PrivateIdentifier - || isBindingIdentifier(); + function parseStatement(): Statement { + switch (token()) { + case SyntaxKind.SemicolonToken: + return parseEmptyStatement(); + case SyntaxKind.OpenBraceToken: + return parseBlock(/*ignoreMissingOpenBrace*/ false); + case SyntaxKind.VarKeyword: + return parseVariableStatement(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); + case SyntaxKind.LetKeyword: + if (isLetDeclaration()) { + return parseVariableStatement(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); + } + break; + case SyntaxKind.FunctionKeyword: + return parseFunctionDeclaration(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); + case SyntaxKind.ClassKeyword: + return parseClassDeclaration(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); + case SyntaxKind.IfKeyword: + return parseIfStatement(); + case SyntaxKind.DoKeyword: + return parseDoStatement(); + case SyntaxKind.WhileKeyword: + return parseWhileStatement(); + case SyntaxKind.ForKeyword: + return parseForOrForInOrForOfStatement(); + case SyntaxKind.ContinueKeyword: + return parseBreakOrContinueStatement(SyntaxKind.ContinueStatement); + case SyntaxKind.BreakKeyword: + return parseBreakOrContinueStatement(SyntaxKind.BreakStatement); + case SyntaxKind.ReturnKeyword: + return parseReturnStatement(); + case SyntaxKind.WithKeyword: + return parseWithStatement(); + case SyntaxKind.SwitchKeyword: + return parseSwitchStatement(); + case SyntaxKind.ThrowKeyword: + return parseThrowStatement(); + case SyntaxKind.TryKeyword: + // Include 'catch' and 'finally' for error recovery. + // falls through + case SyntaxKind.CatchKeyword: + case SyntaxKind.FinallyKeyword: + return parseTryStatement(); + case SyntaxKind.DebuggerKeyword: + return parseDebuggerStatement(); + case SyntaxKind.AtToken: + return parseDeclaration(); + case SyntaxKind.AsyncKeyword: + case SyntaxKind.InterfaceKeyword: + case SyntaxKind.TypeKeyword: + case SyntaxKind.ModuleKeyword: + case SyntaxKind.NamespaceKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.ConstKeyword: + case SyntaxKind.EnumKeyword: + case SyntaxKind.ExportKeyword: + case SyntaxKind.ImportKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PublicKeyword: + case SyntaxKind.AbstractKeyword: + case SyntaxKind.AccessorKeyword: + case SyntaxKind.StaticKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.GlobalKeyword: + if (isStartOfDeclaration()) { + return parseDeclaration(); + } + break; } + return parseExpressionOrLabeledStatement(); + } + + function isDeclareModifier(modifier: Modifier) { + return modifier.kind === SyntaxKind.DeclareKeyword; + } - function parseIdentifierOrPattern(privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier | BindingPattern { - if (token() === SyntaxKind.OpenBracketToken) { - return parseArrayBindingPattern(); + function parseDeclaration(): Statement { + // `parseListElement` attempted to get the reused node at this position, + // but the ambient context flag was not yet set, so the node appeared + // not reusable in that context. + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const decorators = parseDecorators(); + const modifiers = parseModifiers(); + const isAmbient = some(modifiers, isDeclareModifier); + if (isAmbient) { + const node = tryReuseAmbientDeclaration(pos); + if (node) { + return node; } - if (token() === SyntaxKind.OpenBraceToken) { - return parseObjectBindingPattern(); + + for (const m of modifiers!) { + (m as Mutable).flags |= NodeFlags.Ambient; } - return parseBindingIdentifier(privateIdentifierDiagnosticMessage); + return doInsideOfContext(NodeFlags.Ambient, () => parseDeclarationWorker(pos, hasJSDoc, decorators, modifiers)); } - - function parseVariableDeclarationAllowExclamation() { - return parseVariableDeclaration(/*allowExclamation*/ true); + else { + return parseDeclarationWorker(pos, hasJSDoc, decorators, modifiers); } + } - function parseVariableDeclaration(allowExclamation?: boolean): VariableDeclaration { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_are_not_allowed_in_variable_declarations); - let exclamationToken: ExclamationToken | undefined; - if (allowExclamation && name.kind === SyntaxKind.Identifier && - token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { - exclamationToken = parseTokenNode>(); + function tryReuseAmbientDeclaration(pos: number): Statement | undefined { + return doInsideOfContext(NodeFlags.Ambient, () => { + const node = currentNode(parsingContext, pos); + if (node) { + return consumeNode(node) as Statement; } - const type = parseTypeAnnotation(); - const initializer = isInOrOfKeyword(token()) ? undefined : parseInitializer(); - const node = factory.createVariableDeclaration(name, exclamationToken, type, initializer); - return withJSDoc(finishNode(node, pos), hasJSDoc); + }); + } + + function parseDeclarationWorker(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): Statement { + switch (token()) { + case SyntaxKind.VarKeyword: + case SyntaxKind.LetKeyword: + case SyntaxKind.ConstKeyword: + return parseVariableStatement(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.FunctionKeyword: + return parseFunctionDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.ClassKeyword: + return parseClassDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.InterfaceKeyword: + return parseInterfaceDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.TypeKeyword: + return parseTypeAliasDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.EnumKeyword: + return parseEnumDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.GlobalKeyword: + case SyntaxKind.ModuleKeyword: + case SyntaxKind.NamespaceKeyword: + return parseModuleDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.ImportKeyword: + return parseImportDeclarationOrImportEqualsDeclaration(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.ExportKeyword: + nextToken(); + switch (token()) { + case SyntaxKind.DefaultKeyword: + case SyntaxKind.EqualsToken: + return parseExportAssignment(pos, hasJSDoc, decorators, modifiers); + case SyntaxKind.AsKeyword: + return parseNamespaceExportDeclaration(pos, hasJSDoc, decorators, modifiers); + default: + return parseExportDeclaration(pos, hasJSDoc, decorators, modifiers); + } + default: + if (decorators || modifiers) { + // We reached this point because we encountered decorators and/or modifiers and assumed a declaration + // would follow. For recovery and error reporting purposes, return an incomplete declaration. + const missing = createMissingNode(SyntaxKind.MissingDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); + setTextRangePos(missing, pos); + missing.illegalDecorators = decorators; + missing.modifiers = modifiers; + return missing; + } + return undefined!; // TODO: GH#18217 } + } - function parseVariableDeclarationList(inForStatementInitializer: boolean): VariableDeclarationList { - const pos = getNodePos(); + function nextTokenIsIdentifierOrStringLiteralOnSameLine() { + nextToken(); + return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token() === SyntaxKind.StringLiteral); + } - let flags: NodeFlags = 0; - switch (token()) { - case SyntaxKind.VarKeyword: - break; - case SyntaxKind.LetKeyword: - flags |= NodeFlags.Let; - break; - case SyntaxKind.ConstKeyword: - flags |= NodeFlags.Const; - break; - default: - Debug.fail(); + function parseFunctionBlockOrSemicolon(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block | undefined { + if (token() !== SyntaxKind.OpenBraceToken) { + if (flags & SignatureFlags.Type) { + parseTypeMemberSemicolon(); + return; } - - nextToken(); - - // The user may have written the following: - // - // for (let of X) { } - // - // In this case, we want to parse an empty declaration list, and then parse 'of' - // as a keyword. The reason this is not automatic is that 'of' is a valid identifier. - // So we need to look ahead to determine if 'of' should be treated as a keyword in - // this context. - // The checker will then give an error that there is an empty declaration list. - let declarations: readonly VariableDeclaration[]; - if (token() === SyntaxKind.OfKeyword && lookAhead(canFollowContextualOfKeyword)) { - declarations = createMissingList(); + if (canParseSemicolon()) { + parseSemicolon(); + return; } - else { - const savedDisallowIn = inDisallowInContext(); - setDisallowInContext(inForStatementInitializer); - - declarations = parseDelimitedList(ParsingContext.VariableDeclarations, - inForStatementInitializer ? parseVariableDeclaration : parseVariableDeclarationAllowExclamation); + } + return parseFunctionBlock(flags, diagnosticMessage); + } - setDisallowInContext(savedDisallowIn); - } + // DECLARATIONS - return finishNode(factory.createVariableDeclarationList(declarations, flags), pos); + function parseArrayBindingElement(): ArrayBindingElement { + const pos = getNodePos(); + if (token() === SyntaxKind.CommaToken) { + return finishNode(factory.createOmittedExpression(), pos); } + const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + const name = parseIdentifierOrPattern(); + const initializer = parseInitializer(); + return finishNode(factory.createBindingElement(dotDotDotToken, /*propertyName*/ undefined, name, initializer), pos); + } - function canFollowContextualOfKeyword(): boolean { - return nextTokenIsIdentifier() && nextToken() === SyntaxKind.CloseParenToken; + function parseObjectBindingElement(): BindingElement { + const pos = getNodePos(); + const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); + const tokenIsIdentifier = isBindingIdentifier(); + let propertyName: PropertyName | undefined = parsePropertyName(); + let name: BindingName; + if (tokenIsIdentifier && token() !== SyntaxKind.ColonToken) { + name = propertyName as Identifier; + propertyName = undefined; } - - function parseVariableStatement(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): VariableStatement { - const declarationList = parseVariableDeclarationList(/*inForStatementInitializer*/ false); - parseSemicolon(); - const node = factory.createVariableStatement(modifiers, declarationList); - // Decorators are not allowed on a variable statement, so we keep track of them to report them in the grammar checker. - node.illegalDecorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); + else { + parseExpected(SyntaxKind.ColonToken); + name = parseIdentifierOrPattern(); } + const initializer = parseInitializer(); + return finishNode(factory.createBindingElement(dotDotDotToken, propertyName, name, initializer), pos); + } - function parseFunctionDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): FunctionDeclaration { - const savedAwaitContext = inAwaitContext(); + function parseObjectBindingPattern(): ObjectBindingPattern { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBraceToken); + const elements = parseDelimitedList(ParsingContext.ObjectBindingElements, parseObjectBindingElement); + parseExpected(SyntaxKind.CloseBraceToken); + return finishNode(factory.createObjectBindingPattern(elements), pos); + } - const modifierFlags = modifiersToFlags(modifiers); - parseExpected(SyntaxKind.FunctionKeyword); - const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - // We don't parse the name here in await context, instead we will report a grammar error in the checker. - const name = modifierFlags & ModifierFlags.Default ? parseOptionalBindingIdentifier() : parseBindingIdentifier(); - const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; - const isAsync = modifierFlags & ModifierFlags.Async ? SignatureFlags.Await : SignatureFlags.None; - const typeParameters = parseTypeParameters(); - if (modifierFlags & ModifierFlags.Export) setAwaitContext(/*value*/ true); - const parameters = parseParameters(isGenerator | isAsync); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - const body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, Diagnostics.or_expected); - setAwaitContext(savedAwaitContext); - const node = factory.createFunctionDeclaration(modifiers, asteriskToken, name, typeParameters, parameters, type, body); - (node as Mutable).illegalDecorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); - } + function parseArrayBindingPattern(): ArrayBindingPattern { + const pos = getNodePos(); + parseExpected(SyntaxKind.OpenBracketToken); + const elements = parseDelimitedList(ParsingContext.ArrayBindingElements, parseArrayBindingElement); + parseExpected(SyntaxKind.CloseBracketToken); + return finishNode(factory.createArrayBindingPattern(elements), pos); + } - function parseConstructorName() { - if (token() === SyntaxKind.ConstructorKeyword) { - return parseExpected(SyntaxKind.ConstructorKeyword); - } - if (token() === SyntaxKind.StringLiteral && lookAhead(nextToken) === SyntaxKind.OpenParenToken) { - return tryParse(() => { - const literalNode = parseLiteralNode(); - return literalNode.text === "constructor" ? literalNode : undefined; - }); - } - } + function isBindingIdentifierOrPrivateIdentifierOrPattern() { + return token() === SyntaxKind.OpenBraceToken + || token() === SyntaxKind.OpenBracketToken + || token() === SyntaxKind.PrivateIdentifier + || isBindingIdentifier(); + } - function tryParseConstructorDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ConstructorDeclaration | undefined { - return tryParse(() => { - if (parseConstructorName()) { - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(SignatureFlags.None); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - const body = parseFunctionBlockOrSemicolon(SignatureFlags.None, Diagnostics.or_expected); - const node = factory.createConstructorDeclaration(modifiers, parameters, body); - - // Attach invalid nodes if they exist so that we can report them in the grammar checker. - (node as Mutable).illegalDecorators = decorators; - (node as Mutable).typeParameters = typeParameters; - (node as Mutable).type = type; - return withJSDoc(finishNode(node, pos), hasJSDoc); - } - }); + function parseIdentifierOrPattern(privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier | BindingPattern { + if (token() === SyntaxKind.OpenBracketToken) { + return parseArrayBindingPattern(); } - - function parseMethodDeclaration( - pos: number, - hasJSDoc: boolean, - decorators: NodeArray | undefined, - modifiers: NodeArray | undefined, - asteriskToken: AsteriskToken | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined, - exclamationToken: ExclamationToken | undefined, - diagnosticMessage?: DiagnosticMessage - ): MethodDeclaration { - const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; - const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(isGenerator | isAsync); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - const body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, diagnosticMessage); - const node = factory.createMethodDeclaration( - combineDecoratorsAndModifiers(decorators, modifiers), - asteriskToken, - name, - questionToken, - typeParameters, - parameters, - type, - body - ); - - // An exclamation token on a method is invalid syntax and will be handled by the grammar checker - (node as Mutable).exclamationToken = exclamationToken; - return withJSDoc(finishNode(node, pos), hasJSDoc); + if (token() === SyntaxKind.OpenBraceToken) { + return parseObjectBindingPattern(); } + return parseBindingIdentifier(privateIdentifierDiagnosticMessage); + } - function parsePropertyDeclaration( - pos: number, - hasJSDoc: boolean, - decorators: NodeArray | undefined, - modifiers: NodeArray | undefined, - name: PropertyName, - questionToken: QuestionToken | undefined - ): PropertyDeclaration { - const exclamationToken = !questionToken && !scanner.hasPrecedingLineBreak() ? parseOptionalToken(SyntaxKind.ExclamationToken) : undefined; - const type = parseTypeAnnotation(); - const initializer = doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext | NodeFlags.DisallowInContext, parseInitializer); - parseSemicolonAfterPropertyName(name, type, initializer); - const node = factory.createPropertyDeclaration( - combineDecoratorsAndModifiers(decorators, modifiers), - name, - questionToken || exclamationToken, - type, - initializer); - return withJSDoc(finishNode(node, pos), hasJSDoc); - } + function parseVariableDeclarationAllowExclamation() { + return parseVariableDeclaration(/*allowExclamation*/ true); + } - function parsePropertyOrMethodDeclaration( - pos: number, - hasJSDoc: boolean, - decorators: NodeArray | undefined, - modifiers: NodeArray | undefined - ): PropertyDeclaration | MethodDeclaration { - const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - const name = parsePropertyName(); - // Note: this is not legal as per the grammar. But we allow it in the parser and - // report an error in the grammar checker. - const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); - if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { - return parseMethodDeclaration(pos, hasJSDoc, decorators, modifiers, asteriskToken, name, questionToken, /*exclamationToken*/ undefined, Diagnostics.or_expected); - } - return parsePropertyDeclaration(pos, hasJSDoc, decorators, modifiers, name, questionToken); - } + function parseVariableDeclaration(allowExclamation?: boolean): VariableDeclaration { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_are_not_allowed_in_variable_declarations); + let exclamationToken: ExclamationToken | undefined; + if (allowExclamation && name.kind === SyntaxKind.Identifier && + token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + exclamationToken = parseTokenNode>(); + } + const type = parseTypeAnnotation(); + const initializer = isInOrOfKeyword(token()) ? undefined : parseInitializer(); + const node = factory.createVariableDeclaration(name, exclamationToken, type, initializer); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseAccessorDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, kind: AccessorDeclaration["kind"], flags: SignatureFlags): AccessorDeclaration { - const name = parsePropertyName(); - const typeParameters = parseTypeParameters(); - const parameters = parseParameters(SignatureFlags.None); - const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); - const body = parseFunctionBlockOrSemicolon(flags); - const node = kind === SyntaxKind.GetAccessor - ? factory.createGetAccessorDeclaration(combineDecoratorsAndModifiers(decorators, modifiers), name, parameters, type, body) - : factory.createSetAccessorDeclaration(combineDecoratorsAndModifiers(decorators, modifiers), name, parameters, body); - // Keep track of `typeParameters` (for both) and `type` (for setters) if they were parsed those indicate grammar errors - (node as Mutable).typeParameters = typeParameters; - if (isSetAccessorDeclaration(node)) (node as Mutable).type = type; - return withJSDoc(finishNode(node, pos), hasJSDoc); - } + function parseVariableDeclarationList(inForStatementInitializer: boolean): VariableDeclarationList { + const pos = getNodePos(); - function isClassMemberStart(): boolean { - let idToken: SyntaxKind | undefined; + let flags: NodeFlags = 0; + switch (token()) { + case SyntaxKind.VarKeyword: + break; + case SyntaxKind.LetKeyword: + flags |= NodeFlags.Let; + break; + case SyntaxKind.ConstKeyword: + flags |= NodeFlags.Const; + break; + default: + Debug.fail(); + } - if (token() === SyntaxKind.AtToken) { - return true; - } + nextToken(); - // Eat up all modifiers, but hold on to the last one in case it is actually an identifier. - while (isModifierKind(token())) { - idToken = token(); - // If the idToken is a class modifier (protected, private, public, and static), it is - // certain that we are starting to parse class member. This allows better error recovery - // Example: - // public foo() ... // true - // public @dec blah ... // true; we will then report an error later - // export public ... // true; we will then report an error later - if (isClassMemberModifier(idToken)) { - return true; - } + // The user may have written the following: + // + // for (let of X) { } + // + // In this case, we want to parse an empty declaration list, and then parse 'of' + // as a keyword. The reason this is not automatic is that 'of' is a valid identifier. + // So we need to look ahead to determine if 'of' should be treated as a keyword in + // this context. + // The checker will then give an error that there is an empty declaration list. + let declarations: readonly VariableDeclaration[]; + if (token() === SyntaxKind.OfKeyword && lookAhead(canFollowContextualOfKeyword)) { + declarations = createMissingList(); + } + else { + const savedDisallowIn = inDisallowInContext(); + setDisallowInContext(inForStatementInitializer); - nextToken(); - } + declarations = parseDelimitedList(ParsingContext.VariableDeclarations, + inForStatementInitializer ? parseVariableDeclaration : parseVariableDeclarationAllowExclamation); - if (token() === SyntaxKind.AsteriskToken) { - return true; - } + setDisallowInContext(savedDisallowIn); + } - // Try to get the first property-like token following all modifiers. - // This can either be an identifier or the 'get' or 'set' keywords. - if (isLiteralPropertyName()) { - idToken = token(); - nextToken(); - } + return finishNode(factory.createVariableDeclarationList(declarations, flags), pos); + } - // Index signatures and computed properties are class members; we can parse. - if (token() === SyntaxKind.OpenBracketToken) { - return true; - } + function canFollowContextualOfKeyword(): boolean { + return nextTokenIsIdentifier() && nextToken() === SyntaxKind.CloseParenToken; + } - // If we were able to get any potential identifier... - if (idToken !== undefined) { - // If we have a non-keyword identifier, or if we have an accessor, then it's safe to parse. - if (!isKeyword(idToken) || idToken === SyntaxKind.SetKeyword || idToken === SyntaxKind.GetKeyword) { - return true; - } + function parseVariableStatement(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): VariableStatement { + const declarationList = parseVariableDeclarationList(/*inForStatementInitializer*/ false); + parseSemicolon(); + const node = factory.createVariableStatement(modifiers, declarationList); + // Decorators are not allowed on a variable statement, so we keep track of them to report them in the grammar checker. + node.illegalDecorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - // If it *is* a keyword, but not an accessor, check a little farther along - // to see if it should actually be parsed as a class member. - switch (token()) { - case SyntaxKind.OpenParenToken: // Method declaration - case SyntaxKind.LessThanToken: // Generic Method declaration - case SyntaxKind.ExclamationToken: // Non-null assertion on property name - case SyntaxKind.ColonToken: // Type Annotation for declaration - case SyntaxKind.EqualsToken: // Initializer for declaration - case SyntaxKind.QuestionToken: // Not valid, but permitted so that it gets caught later on. - return true; - default: - // Covers - // - Semicolons (declaration termination) - // - Closing braces (end-of-class, must be declaration) - // - End-of-files (not valid, but permitted so that it gets caught later on) - // - Line-breaks (enabling *automatic semicolon insertion*) - return canParseSemicolon(); - } - } + function parseFunctionDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): FunctionDeclaration { + const savedAwaitContext = inAwaitContext(); + + const modifierFlags = modifiersToFlags(modifiers); + parseExpected(SyntaxKind.FunctionKeyword); + const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + // We don't parse the name here in await context, instead we will report a grammar error in the checker. + const name = modifierFlags & ModifierFlags.Default ? parseOptionalBindingIdentifier() : parseBindingIdentifier(); + const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = modifierFlags & ModifierFlags.Async ? SignatureFlags.Await : SignatureFlags.None; + const typeParameters = parseTypeParameters(); + if (modifierFlags & ModifierFlags.Export) setAwaitContext(/*value*/ true); + const parameters = parseParameters(isGenerator | isAsync); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + const body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, Diagnostics.or_expected); + setAwaitContext(savedAwaitContext); + const node = factory.createFunctionDeclaration(modifiers, asteriskToken, name, typeParameters, parameters, type, body); + (node as Mutable).illegalDecorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - return false; + function parseConstructorName() { + if (token() === SyntaxKind.ConstructorKeyword) { + return parseExpected(SyntaxKind.ConstructorKeyword); } - - function parseClassStaticBlockDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: ModifiersArray | undefined): ClassStaticBlockDeclaration { - parseExpectedToken(SyntaxKind.StaticKeyword); - const body = parseClassStaticBlockBody(); - const node = withJSDoc(finishNode(factory.createClassStaticBlockDeclaration(body), pos), hasJSDoc); - (node as Mutable).illegalDecorators = decorators; - (node as Mutable).modifiers = modifiers; - return node; + if (token() === SyntaxKind.StringLiteral && lookAhead(nextToken) === SyntaxKind.OpenParenToken) { + return tryParse(() => { + const literalNode = parseLiteralNode(); + return literalNode.text === "constructor" ? literalNode : undefined; + }); } + } - function parseClassStaticBlockBody() { - const savedYieldContext = inYieldContext(); - const savedAwaitContext = inAwaitContext(); - - setYieldContext(false); - setAwaitContext(true); + function tryParseConstructorDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ConstructorDeclaration | undefined { + return tryParse(() => { + if (parseConstructorName()) { + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(SignatureFlags.None); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + const body = parseFunctionBlockOrSemicolon(SignatureFlags.None, Diagnostics.or_expected); + const node = factory.createConstructorDeclaration(modifiers, parameters, body); - const body = parseBlock(/*ignoreMissingOpenBrace*/ false); + // Attach invalid nodes if they exist so that we can report them in the grammar checker. + (node as Mutable).illegalDecorators = decorators; + (node as Mutable).typeParameters = typeParameters; + (node as Mutable).type = type; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } + }); + } - setYieldContext(savedYieldContext); - setAwaitContext(savedAwaitContext); + function parseMethodDeclaration( + pos: number, + hasJSDoc: boolean, + decorators: NodeArray | undefined, + modifiers: NodeArray | undefined, + asteriskToken: AsteriskToken | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined, + exclamationToken: ExclamationToken | undefined, + diagnosticMessage?: DiagnosticMessage + ): MethodDeclaration { + const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; + const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(isGenerator | isAsync); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + const body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, diagnosticMessage); + const node = factory.createMethodDeclaration( + combineDecoratorsAndModifiers(decorators, modifiers), + asteriskToken, + name, + questionToken, + typeParameters, + parameters, + type, + body + ); + + // An exclamation token on a method is invalid syntax and will be handled by the grammar checker + (node as Mutable).exclamationToken = exclamationToken; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - return body; - } + function parsePropertyDeclaration( + pos: number, + hasJSDoc: boolean, + decorators: NodeArray | undefined, + modifiers: NodeArray | undefined, + name: PropertyName, + questionToken: QuestionToken | undefined + ): PropertyDeclaration { + const exclamationToken = !questionToken && !scanner.hasPrecedingLineBreak() ? parseOptionalToken(SyntaxKind.ExclamationToken) : undefined; + const type = parseTypeAnnotation(); + const initializer = doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext | NodeFlags.DisallowInContext, parseInitializer); + parseSemicolonAfterPropertyName(name, type, initializer); + const node = factory.createPropertyDeclaration( + combineDecoratorsAndModifiers(decorators, modifiers), + name, + questionToken || exclamationToken, + type, + initializer); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseDecoratorExpression() { - if (inAwaitContext() && token() === SyntaxKind.AwaitKeyword) { - // `@await` is is disallowed in an [Await] context, but can cause parsing to go off the rails - // This simply parses the missing identifier and moves on. - const pos = getNodePos(); - const awaitExpression = parseIdentifier(Diagnostics.Expression_expected); - nextToken(); - const memberExpression = parseMemberExpressionRest(pos, awaitExpression, /*allowOptionalChain*/ true); - return parseCallExpressionRest(pos, memberExpression); - } - return parseLeftHandSideExpressionOrHigher(); - } + function parsePropertyOrMethodDeclaration( + pos: number, + hasJSDoc: boolean, + decorators: NodeArray | undefined, + modifiers: NodeArray | undefined + ): PropertyDeclaration | MethodDeclaration { + const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); + const name = parsePropertyName(); + // Note: this is not legal as per the grammar. But we allow it in the parser and + // report an error in the grammar checker. + const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); + if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { + return parseMethodDeclaration(pos, hasJSDoc, decorators, modifiers, asteriskToken, name, questionToken, /*exclamationToken*/ undefined, Diagnostics.or_expected); + } + return parsePropertyDeclaration(pos, hasJSDoc, decorators, modifiers, name, questionToken); + } - function tryParseDecorator(): Decorator | undefined { - const pos = getNodePos(); - if (!parseOptional(SyntaxKind.AtToken)) { - return undefined; - } - const expression = doInDecoratorContext(parseDecoratorExpression); - return finishNode(factory.createDecorator(expression), pos); - } + function parseAccessorDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, kind: AccessorDeclaration["kind"], flags: SignatureFlags): AccessorDeclaration { + const name = parsePropertyName(); + const typeParameters = parseTypeParameters(); + const parameters = parseParameters(SignatureFlags.None); + const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); + const body = parseFunctionBlockOrSemicolon(flags); + const node = kind === SyntaxKind.GetAccessor + ? factory.createGetAccessorDeclaration(combineDecoratorsAndModifiers(decorators, modifiers), name, parameters, type, body) + : factory.createSetAccessorDeclaration(combineDecoratorsAndModifiers(decorators, modifiers), name, parameters, body); + // Keep track of `typeParameters` (for both) and `type` (for setters) if they were parsed those indicate grammar errors + (node as Mutable).typeParameters = typeParameters; + if (isSetAccessorDeclaration(node)) (node as Mutable).type = type; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseDecorators(): NodeArray | undefined { - const pos = getNodePos(); - let list, decorator; - while (decorator = tryParseDecorator()) { - list = append(list, decorator); - } - return list && createNodeArray(list, pos); - } + function isClassMemberStart(): boolean { + let idToken: SyntaxKind | undefined; - function tryParseModifier(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean, hasSeenStaticModifier?: boolean): Modifier | undefined { - const pos = getNodePos(); - const kind = token(); + if (token() === SyntaxKind.AtToken) { + return true; + } - if (token() === SyntaxKind.ConstKeyword && permitInvalidConstAsModifier) { - // We need to ensure that any subsequent modifiers appear on the same line - // so that when 'const' is a standalone declaration, we don't issue an error. - if (!tryParse(nextTokenIsOnSameLineAndCanFollowModifier)) { - return undefined; - } - } - else if (stopOnStartOfClassStaticBlock && token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { - return undefined; - } - else if (hasSeenStaticModifier && token() === SyntaxKind.StaticKeyword) { - return undefined; - } - else { - if (!parseAnyContextualModifier()) { - return undefined; - } + // Eat up all modifiers, but hold on to the last one in case it is actually an identifier. + while (isModifierKind(token())) { + idToken = token(); + // If the idToken is a class modifier (protected, private, public, and static), it is + // certain that we are starting to parse class member. This allows better error recovery + // Example: + // public foo() ... // true + // public @dec blah ... // true; we will then report an error later + // export public ... // true; we will then report an error later + if (isClassMemberModifier(idToken)) { + return true; } - return finishNode(factory.createToken(kind as Modifier["kind"]), pos); + nextToken(); } - function combineDecoratorsAndModifiers(decorators: NodeArray | undefined, modifiers: NodeArray | undefined): NodeArray | undefined { - if (!decorators) return modifiers; - if (!modifiers) return decorators; - const decoratorsAndModifiers = factory.createNodeArray(concatenate(decorators, modifiers)); - setTextRangePosEnd(decoratorsAndModifiers, decorators.pos, modifiers.end); - return decoratorsAndModifiers; + if (token() === SyntaxKind.AsteriskToken) { + return true; } - /* - * There are situations in which a modifier like 'const' will appear unexpectedly, such as on a class member. - * In those situations, if we are entirely sure that 'const' is not valid on its own (such as when ASI takes effect - * and turns it into a standalone declaration), then it is better to parse it and report an error later. - * - * In such situations, 'permitInvalidConstAsModifier' should be set to true. - */ - function parseModifiers(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean): NodeArray | undefined { - const pos = getNodePos(); - let list, modifier, hasSeenStatic = false; - while (modifier = tryParseModifier(permitInvalidConstAsModifier, stopOnStartOfClassStaticBlock, hasSeenStatic)) { - if (modifier.kind === SyntaxKind.StaticKeyword) hasSeenStatic = true; - list = append(list, modifier); - } - return list && createNodeArray(list, pos); + // Try to get the first property-like token following all modifiers. + // This can either be an identifier or the 'get' or 'set' keywords. + if (isLiteralPropertyName()) { + idToken = token(); + nextToken(); } - function parseModifiersForArrowFunction(): NodeArray | undefined { - let modifiers: NodeArray | undefined; - if (token() === SyntaxKind.AsyncKeyword) { - const pos = getNodePos(); - nextToken(); - const modifier = finishNode(factory.createToken(SyntaxKind.AsyncKeyword), pos); - modifiers = createNodeArray([modifier], pos); - } - return modifiers; + // Index signatures and computed properties are class members; we can parse. + if (token() === SyntaxKind.OpenBracketToken) { + return true; } - function parseClassElement(): ClassElement { - const pos = getNodePos(); - if (token() === SyntaxKind.SemicolonToken) { - nextToken(); - return finishNode(factory.createSemicolonClassElement(), pos); + // If we were able to get any potential identifier... + if (idToken !== undefined) { + // If we have a non-keyword identifier, or if we have an accessor, then it's safe to parse. + if (!isKeyword(idToken) || idToken === SyntaxKind.SetKeyword || idToken === SyntaxKind.GetKeyword) { + return true; } - const hasJSDoc = hasPrecedingJSDocComment(); - const decorators = parseDecorators(); - const modifiers = parseModifiers(/*permitInvalidConstAsModifier*/ true, /*stopOnStartOfClassStaticBlock*/ true); - if (token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { - return parseClassStaticBlockDeclaration(pos, hasJSDoc, decorators, modifiers); + // If it *is* a keyword, but not an accessor, check a little farther along + // to see if it should actually be parsed as a class member. + switch (token()) { + case SyntaxKind.OpenParenToken: // Method declaration + case SyntaxKind.LessThanToken: // Generic Method declaration + case SyntaxKind.ExclamationToken: // Non-null assertion on property name + case SyntaxKind.ColonToken: // Type Annotation for declaration + case SyntaxKind.EqualsToken: // Initializer for declaration + case SyntaxKind.QuestionToken: // Not valid, but permitted so that it gets caught later on. + return true; + default: + // Covers + // - Semicolons (declaration termination) + // - Closing braces (end-of-class, must be declaration) + // - End-of-files (not valid, but permitted so that it gets caught later on) + // - Line-breaks (enabling *automatic semicolon insertion*) + return canParseSemicolon(); } + } - if (parseContextualModifier(SyntaxKind.GetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.GetAccessor, SignatureFlags.None); - } + return false; + } - if (parseContextualModifier(SyntaxKind.SetKeyword)) { - return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.SetAccessor, SignatureFlags.None); - } + function parseClassStaticBlockDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: ModifiersArray | undefined): ClassStaticBlockDeclaration { + parseExpectedToken(SyntaxKind.StaticKeyword); + const body = parseClassStaticBlockBody(); + const node = withJSDoc(finishNode(factory.createClassStaticBlockDeclaration(body), pos), hasJSDoc); + (node as Mutable).illegalDecorators = decorators; + (node as Mutable).modifiers = modifiers; + return node; + } - if (token() === SyntaxKind.ConstructorKeyword || token() === SyntaxKind.StringLiteral) { - const constructorDeclaration = tryParseConstructorDeclaration(pos, hasJSDoc, decorators, modifiers); - if (constructorDeclaration) { - return constructorDeclaration; - } - } + function parseClassStaticBlockBody() { + const savedYieldContext = inYieldContext(); + const savedAwaitContext = inAwaitContext(); - if (isIndexSignature()) { - return parseIndexSignatureDeclaration(pos, hasJSDoc, decorators, modifiers); - } + setYieldContext(false); + setAwaitContext(true); - // It is very important that we check this *after* checking indexers because - // the [ token can start an index signature or a computed property name - if (tokenIsIdentifierOrKeyword(token()) || - token() === SyntaxKind.StringLiteral || - token() === SyntaxKind.NumericLiteral || - token() === SyntaxKind.AsteriskToken || - token() === SyntaxKind.OpenBracketToken) { - const isAmbient = some(modifiers, isDeclareModifier); - if (isAmbient) { - for (const m of modifiers!) { - (m as Mutable).flags |= NodeFlags.Ambient; - } - return doInsideOfContext(NodeFlags.Ambient, () => parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers)); - } - else { - return parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers); - } - } + const body = parseBlock(/*ignoreMissingOpenBrace*/ false); - if (decorators || modifiers) { - // treat this as a property declaration with a missing name. - const name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); - return parsePropertyDeclaration(pos, hasJSDoc, decorators, modifiers, name, /*questionToken*/ undefined); - } + setYieldContext(savedYieldContext); + setAwaitContext(savedAwaitContext); - // 'isClassMemberStart' should have hinted not to attempt parsing. - return Debug.fail("Should not have attempted to parse class member declaration."); - } + return body; + } - function parseClassExpression(): ClassExpression { - return parseClassDeclarationOrExpression(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined, SyntaxKind.ClassExpression) as ClassExpression; + function parseDecoratorExpression() { + if (inAwaitContext() && token() === SyntaxKind.AwaitKeyword) { + // `@await` is is disallowed in an [Await] context, but can cause parsing to go off the rails + // This simply parses the missing identifier and moves on. + const pos = getNodePos(); + const awaitExpression = parseIdentifier(Diagnostics.Expression_expected); + nextToken(); + const memberExpression = parseMemberExpressionRest(pos, awaitExpression, /*allowOptionalChain*/ true); + return parseCallExpressionRest(pos, memberExpression); } + return parseLeftHandSideExpressionOrHigher(); + } - function parseClassDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ClassDeclaration { - return parseClassDeclarationOrExpression(pos, hasJSDoc, decorators, modifiers, SyntaxKind.ClassDeclaration) as ClassDeclaration; + function tryParseDecorator(): Decorator | undefined { + const pos = getNodePos(); + if (!parseOptional(SyntaxKind.AtToken)) { + return undefined; } + const expression = doInDecoratorContext(parseDecoratorExpression); + return finishNode(factory.createDecorator(expression), pos); + } - function parseClassDeclarationOrExpression(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, kind: ClassLikeDeclaration["kind"]): ClassLikeDeclaration { - const savedAwaitContext = inAwaitContext(); - parseExpected(SyntaxKind.ClassKeyword); + function parseDecorators(): NodeArray | undefined { + const pos = getNodePos(); + let list, decorator; + while (decorator = tryParseDecorator()) { + list = append(list, decorator); + } + return list && createNodeArray(list, pos); + } - // We don't parse the name here in await context, instead we will report a grammar error in the checker. - const name = parseNameOfClassDeclarationOrExpression(); - const typeParameters = parseTypeParameters(); - if (some(modifiers, isExportModifier)) setAwaitContext(/*value*/ true); - const heritageClauses = parseHeritageClauses(); + function tryParseModifier(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean, hasSeenStaticModifier?: boolean): Modifier | undefined { + const pos = getNodePos(); + const kind = token(); - let members; - if (parseExpected(SyntaxKind.OpenBraceToken)) { - // ClassTail[Yield,Await] : (Modified) See 14.5 - // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } - members = parseClassMembers(); - parseExpected(SyntaxKind.CloseBraceToken); - } - else { - members = createMissingList(); + if (token() === SyntaxKind.ConstKeyword && permitInvalidConstAsModifier) { + // We need to ensure that any subsequent modifiers appear on the same line + // so that when 'const' is a standalone declaration, we don't issue an error. + if (!tryParse(nextTokenIsOnSameLineAndCanFollowModifier)) { + return undefined; } - setAwaitContext(savedAwaitContext); - const node = kind === SyntaxKind.ClassDeclaration - ? factory.createClassDeclaration(combineDecoratorsAndModifiers(decorators, modifiers), name, typeParameters, heritageClauses, members) - : factory.createClassExpression(combineDecoratorsAndModifiers(decorators, modifiers), name, typeParameters, heritageClauses, members); - return withJSDoc(finishNode(node, pos), hasJSDoc); } - - function parseNameOfClassDeclarationOrExpression(): Identifier | undefined { - // implements is a future reserved word so - // 'class implements' might mean either - // - class expression with omitted name, 'implements' starts heritage clause - // - class with name 'implements' - // 'isImplementsClause' helps to disambiguate between these two cases - return isBindingIdentifier() && !isImplementsClause() - ? createIdentifier(isBindingIdentifier()) - : undefined; + else if (stopOnStartOfClassStaticBlock && token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { + return undefined; } - - function isImplementsClause() { - return token() === SyntaxKind.ImplementsKeyword && lookAhead(nextTokenIsIdentifierOrKeyword); + else if (hasSeenStaticModifier && token() === SyntaxKind.StaticKeyword) { + return undefined; + } + else { + if (!parseAnyContextualModifier()) { + return undefined; + } } - function parseHeritageClauses(): NodeArray | undefined { - // ClassTail[Yield,Await] : (Modified) See 14.5 - // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } + return finishNode(factory.createToken(kind as Modifier["kind"]), pos); + } - if (isHeritageClause()) { - return parseList(ParsingContext.HeritageClauses, parseHeritageClause); - } + function combineDecoratorsAndModifiers(decorators: NodeArray | undefined, modifiers: NodeArray | undefined): NodeArray | undefined { + if (!decorators) return modifiers; + if (!modifiers) return decorators; + const decoratorsAndModifiers = factory.createNodeArray(concatenate(decorators, modifiers)); + setTextRangePosEnd(decoratorsAndModifiers, decorators.pos, modifiers.end); + return decoratorsAndModifiers; + } - return undefined; - } + /* + * There are situations in which a modifier like 'const' will appear unexpectedly, such as on a class member. + * In those situations, if we are entirely sure that 'const' is not valid on its own (such as when ASI takes effect + * and turns it into a standalone declaration), then it is better to parse it and report an error later. + * + * In such situations, 'permitInvalidConstAsModifier' should be set to true. + */ + function parseModifiers(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean): NodeArray | undefined { + const pos = getNodePos(); + let list, modifier, hasSeenStatic = false; + while (modifier = tryParseModifier(permitInvalidConstAsModifier, stopOnStartOfClassStaticBlock, hasSeenStatic)) { + if (modifier.kind === SyntaxKind.StaticKeyword) hasSeenStatic = true; + list = append(list, modifier); + } + return list && createNodeArray(list, pos); + } - function parseHeritageClause(): HeritageClause { + function parseModifiersForArrowFunction(): NodeArray | undefined { + let modifiers: NodeArray | undefined; + if (token() === SyntaxKind.AsyncKeyword) { const pos = getNodePos(); - const tok = token(); - Debug.assert(tok === SyntaxKind.ExtendsKeyword || tok === SyntaxKind.ImplementsKeyword); // isListElement() should ensure this. nextToken(); - const types = parseDelimitedList(ParsingContext.HeritageClauseElement, parseExpressionWithTypeArguments); - return finishNode(factory.createHeritageClause(tok, types), pos); - } - - function parseExpressionWithTypeArguments(): ExpressionWithTypeArguments { - const pos = getNodePos(); - const expression = parseLeftHandSideExpressionOrHigher(); - if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) { - return expression as ExpressionWithTypeArguments; - } - const typeArguments = tryParseTypeArguments(); - return finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos); + const modifier = finishNode(factory.createToken(SyntaxKind.AsyncKeyword), pos); + modifiers = createNodeArray([modifier], pos); } + return modifiers; + } - function tryParseTypeArguments(): NodeArray | undefined { - return token() === SyntaxKind.LessThanToken ? - parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken) : undefined; + function parseClassElement(): ClassElement { + const pos = getNodePos(); + if (token() === SyntaxKind.SemicolonToken) { + nextToken(); + return finishNode(factory.createSemicolonClassElement(), pos); } - function isHeritageClause(): boolean { - return token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + const hasJSDoc = hasPrecedingJSDocComment(); + const decorators = parseDecorators(); + const modifiers = parseModifiers(/*permitInvalidConstAsModifier*/ true, /*stopOnStartOfClassStaticBlock*/ true); + if (token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { + return parseClassStaticBlockDeclaration(pos, hasJSDoc, decorators, modifiers); } - function parseClassMembers(): NodeArray { - return parseList(ParsingContext.ClassMembers, parseClassElement); + if (parseContextualModifier(SyntaxKind.GetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.GetAccessor, SignatureFlags.None); } - function parseInterfaceDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): InterfaceDeclaration { - parseExpected(SyntaxKind.InterfaceKeyword); - const name = parseIdentifier(); - const typeParameters = parseTypeParameters(); - const heritageClauses = parseHeritageClauses(); - const members = parseObjectTypeMembers(); - const node = factory.createInterfaceDeclaration(modifiers, name, typeParameters, heritageClauses, members); - (node as Mutable).illegalDecorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); + if (parseContextualModifier(SyntaxKind.SetKeyword)) { + return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.SetAccessor, SignatureFlags.None); } - function parseTypeAliasDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): TypeAliasDeclaration { - parseExpected(SyntaxKind.TypeKeyword); - const name = parseIdentifier(); - const typeParameters = parseTypeParameters(); - parseExpected(SyntaxKind.EqualsToken); - const type = token() === SyntaxKind.IntrinsicKeyword && tryParse(parseKeywordAndNoDot) || parseType(); - parseSemicolon(); - const node = factory.createTypeAliasDeclaration(modifiers, name, typeParameters, type); - (node as Mutable).illegalDecorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); + if (token() === SyntaxKind.ConstructorKeyword || token() === SyntaxKind.StringLiteral) { + const constructorDeclaration = tryParseConstructorDeclaration(pos, hasJSDoc, decorators, modifiers); + if (constructorDeclaration) { + return constructorDeclaration; + } } - // In an ambient declaration, the grammar only allows integer literals as initializers. - // In a non-ambient declaration, the grammar allows uninitialized members only in a - // ConstantEnumMemberSection, which starts at the beginning of an enum declaration - // or any time an integer literal initializer is encountered. - function parseEnumMember(): EnumMember { - const pos = getNodePos(); - const hasJSDoc = hasPrecedingJSDocComment(); - const name = parsePropertyName(); - const initializer = allowInAnd(parseInitializer); - return withJSDoc(finishNode(factory.createEnumMember(name, initializer), pos), hasJSDoc); + if (isIndexSignature()) { + return parseIndexSignatureDeclaration(pos, hasJSDoc, decorators, modifiers); } - function parseEnumDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): EnumDeclaration { - parseExpected(SyntaxKind.EnumKeyword); - const name = parseIdentifier(); - let members; - if (parseExpected(SyntaxKind.OpenBraceToken)) { - members = doOutsideOfYieldAndAwaitContext(() => parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember)); - parseExpected(SyntaxKind.CloseBraceToken); + // It is very important that we check this *after* checking indexers because + // the [ token can start an index signature or a computed property name + if (tokenIsIdentifierOrKeyword(token()) || + token() === SyntaxKind.StringLiteral || + token() === SyntaxKind.NumericLiteral || + token() === SyntaxKind.AsteriskToken || + token() === SyntaxKind.OpenBracketToken) { + const isAmbient = some(modifiers, isDeclareModifier); + if (isAmbient) { + for (const m of modifiers!) { + (m as Mutable).flags |= NodeFlags.Ambient; + } + return doInsideOfContext(NodeFlags.Ambient, () => parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers)); } else { - members = createMissingList(); + return parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers); } - const node = factory.createEnumDeclaration(modifiers, name, members); - (node as Mutable).illegalDecorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); } - function parseModuleBlock(): ModuleBlock { - const pos = getNodePos(); - let statements; - if (parseExpected(SyntaxKind.OpenBraceToken)) { - statements = parseList(ParsingContext.BlockStatements, parseStatement); - parseExpected(SyntaxKind.CloseBraceToken); - } - else { - statements = createMissingList(); - } - return finishNode(factory.createModuleBlock(statements), pos); + if (decorators || modifiers) { + // treat this as a property declaration with a missing name. + const name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); + return parsePropertyDeclaration(pos, hasJSDoc, decorators, modifiers, name, /*questionToken*/ undefined); } - function parseModuleOrNamespaceDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, flags: NodeFlags): ModuleDeclaration { - // If we are parsing a dotted namespace name, we want to - // propagate the 'Namespace' flag across the names if set. - const namespaceFlag = flags & NodeFlags.Namespace; - const name = parseIdentifier(); - const body = parseOptional(SyntaxKind.DotToken) - ? parseModuleOrNamespaceDeclaration(getNodePos(), /*hasJSDoc*/ false, /*decorators*/ undefined, /*modifiers*/ undefined, NodeFlags.NestedNamespace | namespaceFlag) as NamespaceDeclaration - : parseModuleBlock(); - const node = factory.createModuleDeclaration(modifiers, name, body, flags); - (node as Mutable).illegalDecorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); - } + // 'isClassMemberStart' should have hinted not to attempt parsing. + return Debug.fail("Should not have attempted to parse class member declaration."); + } - function parseAmbientExternalModuleDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ModuleDeclaration { - let flags: NodeFlags = 0; - let name; - if (token() === SyntaxKind.GlobalKeyword) { - // parse 'global' as name of global scope augmentation - name = parseIdentifier(); - flags |= NodeFlags.GlobalAugmentation; - } - else { - name = parseLiteralNode() as StringLiteral; - name.text = internIdentifier(name.text); - } - let body: ModuleBlock | undefined; - if (token() === SyntaxKind.OpenBraceToken) { - body = parseModuleBlock(); - } - else { - parseSemicolon(); - } - const node = factory.createModuleDeclaration(modifiers, name, body, flags); - (node as Mutable).illegalDecorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); - } + function parseClassExpression(): ClassExpression { + return parseClassDeclarationOrExpression(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined, SyntaxKind.ClassExpression) as ClassExpression; + } - function parseModuleDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ModuleDeclaration { - let flags: NodeFlags = 0; - if (token() === SyntaxKind.GlobalKeyword) { - // global augmentation - return parseAmbientExternalModuleDeclaration(pos, hasJSDoc, decorators, modifiers); - } - else if (parseOptional(SyntaxKind.NamespaceKeyword)) { - flags |= NodeFlags.Namespace; - } - else { - parseExpected(SyntaxKind.ModuleKeyword); - if (token() === SyntaxKind.StringLiteral) { - return parseAmbientExternalModuleDeclaration(pos, hasJSDoc, decorators, modifiers); - } - } - return parseModuleOrNamespaceDeclaration(pos, hasJSDoc, decorators, modifiers, flags); - } + function parseClassDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ClassDeclaration { + return parseClassDeclarationOrExpression(pos, hasJSDoc, decorators, modifiers, SyntaxKind.ClassDeclaration) as ClassDeclaration; + } - function isExternalModuleReference() { - return token() === SyntaxKind.RequireKeyword && - lookAhead(nextTokenIsOpenParen); - } + function parseClassDeclarationOrExpression(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, kind: ClassLikeDeclaration["kind"]): ClassLikeDeclaration { + const savedAwaitContext = inAwaitContext(); + parseExpected(SyntaxKind.ClassKeyword); - function nextTokenIsOpenParen() { - return nextToken() === SyntaxKind.OpenParenToken; - } + // We don't parse the name here in await context, instead we will report a grammar error in the checker. + const name = parseNameOfClassDeclarationOrExpression(); + const typeParameters = parseTypeParameters(); + if (some(modifiers, isExportModifier)) setAwaitContext(/*value*/ true); + const heritageClauses = parseHeritageClauses(); - function nextTokenIsOpenBrace() { - return nextToken() === SyntaxKind.OpenBraceToken; + let members; + if (parseExpected(SyntaxKind.OpenBraceToken)) { + // ClassTail[Yield,Await] : (Modified) See 14.5 + // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } + members = parseClassMembers(); + parseExpected(SyntaxKind.CloseBraceToken); + } + else { + members = createMissingList(); } + setAwaitContext(savedAwaitContext); + const node = kind === SyntaxKind.ClassDeclaration + ? factory.createClassDeclaration(combineDecoratorsAndModifiers(decorators, modifiers), name, typeParameters, heritageClauses, members) + : factory.createClassExpression(combineDecoratorsAndModifiers(decorators, modifiers), name, typeParameters, heritageClauses, members); + return withJSDoc(finishNode(node, pos), hasJSDoc); + } + + function parseNameOfClassDeclarationOrExpression(): Identifier | undefined { + // implements is a future reserved word so + // 'class implements' might mean either + // - class expression with omitted name, 'implements' starts heritage clause + // - class with name 'implements' + // 'isImplementsClause' helps to disambiguate between these two cases + return isBindingIdentifier() && !isImplementsClause() + ? createIdentifier(isBindingIdentifier()) + : undefined; + } - function nextTokenIsSlash() { - return nextToken() === SyntaxKind.SlashToken; + function isImplementsClause() { + return token() === SyntaxKind.ImplementsKeyword && lookAhead(nextTokenIsIdentifierOrKeyword); + } + + function parseHeritageClauses(): NodeArray | undefined { + // ClassTail[Yield,Await] : (Modified) See 14.5 + // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } + + if (isHeritageClause()) { + return parseList(ParsingContext.HeritageClauses, parseHeritageClause); } - function parseNamespaceExportDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): NamespaceExportDeclaration { - parseExpected(SyntaxKind.AsKeyword); - parseExpected(SyntaxKind.NamespaceKeyword); - const name = parseIdentifier(); - parseSemicolon(); - const node = factory.createNamespaceExportDeclaration(name); - // NamespaceExportDeclaration nodes cannot have decorators or modifiers, so we attach them here so we can report them in the grammar checker - (node as Mutable).illegalDecorators = decorators; - (node as Mutable).modifiers = modifiers; - return withJSDoc(finishNode(node, pos), hasJSDoc); + return undefined; + } + + function parseHeritageClause(): HeritageClause { + const pos = getNodePos(); + const tok = token(); + Debug.assert(tok === SyntaxKind.ExtendsKeyword || tok === SyntaxKind.ImplementsKeyword); // isListElement() should ensure this. + nextToken(); + const types = parseDelimitedList(ParsingContext.HeritageClauseElement, parseExpressionWithTypeArguments); + return finishNode(factory.createHeritageClause(tok, types), pos); + } + + function parseExpressionWithTypeArguments(): ExpressionWithTypeArguments { + const pos = getNodePos(); + const expression = parseLeftHandSideExpressionOrHigher(); + if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) { + return expression as ExpressionWithTypeArguments; } + const typeArguments = tryParseTypeArguments(); + return finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos); + } - function parseImportDeclarationOrImportEqualsDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ImportEqualsDeclaration | ImportDeclaration { - parseExpected(SyntaxKind.ImportKeyword); + function tryParseTypeArguments(): NodeArray | undefined { + return token() === SyntaxKind.LessThanToken ? + parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken) : undefined; + } - const afterImportPos = scanner.getStartPos(); + function isHeritageClause(): boolean { + return token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; + } - // We don't parse the identifier here in await context, instead we will report a grammar error in the checker. - let identifier: Identifier | undefined; - if (isIdentifier()) { - identifier = parseIdentifier(); - } + function parseClassMembers(): NodeArray { + return parseList(ParsingContext.ClassMembers, parseClassElement); + } - let isTypeOnly = false; - if (token() !== SyntaxKind.FromKeyword && - identifier?.escapedText === "type" && - (isIdentifier() || tokenAfterImportDefinitelyProducesImportDeclaration()) - ) { - isTypeOnly = true; - identifier = isIdentifier() ? parseIdentifier() : undefined; - } + function parseInterfaceDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): InterfaceDeclaration { + parseExpected(SyntaxKind.InterfaceKeyword); + const name = parseIdentifier(); + const typeParameters = parseTypeParameters(); + const heritageClauses = parseHeritageClauses(); + const members = parseObjectTypeMembers(); + const node = factory.createInterfaceDeclaration(modifiers, name, typeParameters, heritageClauses, members); + (node as Mutable).illegalDecorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - if (identifier && !tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration()) { - return parseImportEqualsDeclaration(pos, hasJSDoc, decorators, modifiers, identifier, isTypeOnly); - } + function parseTypeAliasDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): TypeAliasDeclaration { + parseExpected(SyntaxKind.TypeKeyword); + const name = parseIdentifier(); + const typeParameters = parseTypeParameters(); + parseExpected(SyntaxKind.EqualsToken); + const type = token() === SyntaxKind.IntrinsicKeyword && tryParse(parseKeywordAndNoDot) || parseType(); + parseSemicolon(); + const node = factory.createTypeAliasDeclaration(modifiers, name, typeParameters, type); + (node as Mutable).illegalDecorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - // ImportDeclaration: - // import ImportClause from ModuleSpecifier ; - // import ModuleSpecifier; - let importClause: ImportClause | undefined; - if (identifier || // import id - token() === SyntaxKind.AsteriskToken || // import * - token() === SyntaxKind.OpenBraceToken // import { - ) { - importClause = parseImportClause(identifier, afterImportPos, isTypeOnly); - parseExpected(SyntaxKind.FromKeyword); - } - const moduleSpecifier = parseModuleSpecifier(); + // In an ambient declaration, the grammar only allows integer literals as initializers. + // In a non-ambient declaration, the grammar allows uninitialized members only in a + // ConstantEnumMemberSection, which starts at the beginning of an enum declaration + // or any time an integer literal initializer is encountered. + function parseEnumMember(): EnumMember { + const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); + const name = parsePropertyName(); + const initializer = allowInAnd(parseInitializer); + return withJSDoc(finishNode(factory.createEnumMember(name, initializer), pos), hasJSDoc); + } - let assertClause: AssertClause | undefined; - if (token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { - assertClause = parseAssertClause(); - } + function parseEnumDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): EnumDeclaration { + parseExpected(SyntaxKind.EnumKeyword); + const name = parseIdentifier(); + let members; + if (parseExpected(SyntaxKind.OpenBraceToken)) { + members = doOutsideOfYieldAndAwaitContext(() => parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember)); + parseExpected(SyntaxKind.CloseBraceToken); + } + else { + members = createMissingList(); + } + const node = factory.createEnumDeclaration(modifiers, name, members); + (node as Mutable).illegalDecorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - parseSemicolon(); - const node = factory.createImportDeclaration(modifiers, importClause, moduleSpecifier, assertClause); - (node as Mutable).illegalDecorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); + function parseModuleBlock(): ModuleBlock { + const pos = getNodePos(); + let statements; + if (parseExpected(SyntaxKind.OpenBraceToken)) { + statements = parseList(ParsingContext.BlockStatements, parseStatement); + parseExpected(SyntaxKind.CloseBraceToken); + } + else { + statements = createMissingList(); } + return finishNode(factory.createModuleBlock(statements), pos); + } - function parseAssertEntry() { - const pos = getNodePos(); - const name = tokenIsIdentifierOrKeyword(token()) ? parseIdentifierName() : parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral; - parseExpected(SyntaxKind.ColonToken); - const value = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); - return finishNode(factory.createAssertEntry(name, value), pos); + function parseModuleOrNamespaceDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, flags: NodeFlags): ModuleDeclaration { + // If we are parsing a dotted namespace name, we want to + // propagate the 'Namespace' flag across the names if set. + const namespaceFlag = flags & NodeFlags.Namespace; + const name = parseIdentifier(); + const body = parseOptional(SyntaxKind.DotToken) + ? parseModuleOrNamespaceDeclaration(getNodePos(), /*hasJSDoc*/ false, /*decorators*/ undefined, /*modifiers*/ undefined, NodeFlags.NestedNamespace | namespaceFlag) as NamespaceDeclaration + : parseModuleBlock(); + const node = factory.createModuleDeclaration(modifiers, name, body, flags); + (node as Mutable).illegalDecorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } + + function parseAmbientExternalModuleDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ModuleDeclaration { + let flags: NodeFlags = 0; + let name; + if (token() === SyntaxKind.GlobalKeyword) { + // parse 'global' as name of global scope augmentation + name = parseIdentifier(); + flags |= NodeFlags.GlobalAugmentation; + } + else { + name = parseLiteralNode() as StringLiteral; + name.text = internIdentifier(name.text); + } + let body: ModuleBlock | undefined; + if (token() === SyntaxKind.OpenBraceToken) { + body = parseModuleBlock(); } + else { + parseSemicolon(); + } + const node = factory.createModuleDeclaration(modifiers, name, body, flags); + (node as Mutable).illegalDecorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseAssertClause(skipAssertKeyword?: true) { - const pos = getNodePos(); - if (!skipAssertKeyword) { - parseExpected(SyntaxKind.AssertKeyword); - } - const openBracePosition = scanner.getTokenPos(); - if (parseExpected(SyntaxKind.OpenBraceToken)) { - const multiLine = scanner.hasPrecedingLineBreak(); - const elements = parseDelimitedList(ParsingContext.AssertEntries, parseAssertEntry, /*considerSemicolonAsDelimiter*/ true); - if (!parseExpected(SyntaxKind.CloseBraceToken)) { - const lastError = lastOrUndefined(parseDiagnostics); - if (lastError && lastError.code === Diagnostics._0_expected.code) { - addRelatedInfo( - lastError, - createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_1_to_match_the_0_token_here, "{", "}") - ); - } - } - return finishNode(factory.createAssertClause(elements, multiLine), pos); - } - else { - const elements = createNodeArray([], getNodePos(), /*end*/ undefined, /*hasTrailingComma*/ false); - return finishNode(factory.createAssertClause(elements, /*multiLine*/ false), pos); + function parseModuleDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ModuleDeclaration { + let flags: NodeFlags = 0; + if (token() === SyntaxKind.GlobalKeyword) { + // global augmentation + return parseAmbientExternalModuleDeclaration(pos, hasJSDoc, decorators, modifiers); + } + else if (parseOptional(SyntaxKind.NamespaceKeyword)) { + flags |= NodeFlags.Namespace; + } + else { + parseExpected(SyntaxKind.ModuleKeyword); + if (token() === SyntaxKind.StringLiteral) { + return parseAmbientExternalModuleDeclaration(pos, hasJSDoc, decorators, modifiers); } } + return parseModuleOrNamespaceDeclaration(pos, hasJSDoc, decorators, modifiers, flags); + } - function tokenAfterImportDefinitelyProducesImportDeclaration() { - return token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.OpenBraceToken; - } + function isExternalModuleReference() { + return token() === SyntaxKind.RequireKeyword && + lookAhead(nextTokenIsOpenParen); + } - function tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration() { - // In `import id ___`, the current token decides whether to produce - // an ImportDeclaration or ImportEqualsDeclaration. - return token() === SyntaxKind.CommaToken || token() === SyntaxKind.FromKeyword; - } + function nextTokenIsOpenParen() { + return nextToken() === SyntaxKind.OpenParenToken; + } - function parseImportEqualsDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, identifier: Identifier, isTypeOnly: boolean): ImportEqualsDeclaration { - parseExpected(SyntaxKind.EqualsToken); - const moduleReference = parseModuleReference(); - parseSemicolon(); - const node = factory.createImportEqualsDeclaration(modifiers, isTypeOnly, identifier, moduleReference); - (node as Mutable).illegalDecorators = decorators; - const finished = withJSDoc(finishNode(node, pos), hasJSDoc); - return finished; - } + function nextTokenIsOpenBrace() { + return nextToken() === SyntaxKind.OpenBraceToken; + } - function parseImportClause(identifier: Identifier | undefined, pos: number, isTypeOnly: boolean) { - // ImportClause: - // ImportedDefaultBinding - // NameSpaceImport - // NamedImports - // ImportedDefaultBinding, NameSpaceImport - // ImportedDefaultBinding, NamedImports + function nextTokenIsSlash() { + return nextToken() === SyntaxKind.SlashToken; + } - // If there was no default import or if there is comma token after default import - // parse namespace or named imports - let namedBindings: NamespaceImport | NamedImports | undefined; - if (!identifier || - parseOptional(SyntaxKind.CommaToken)) { - namedBindings = token() === SyntaxKind.AsteriskToken ? parseNamespaceImport() : parseNamedImportsOrExports(SyntaxKind.NamedImports); - } + function parseNamespaceExportDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): NamespaceExportDeclaration { + parseExpected(SyntaxKind.AsKeyword); + parseExpected(SyntaxKind.NamespaceKeyword); + const name = parseIdentifier(); + parseSemicolon(); + const node = factory.createNamespaceExportDeclaration(name); + // NamespaceExportDeclaration nodes cannot have decorators or modifiers, so we attach them here so we can report them in the grammar checker + (node as Mutable).illegalDecorators = decorators; + (node as Mutable).modifiers = modifiers; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } + + function parseImportDeclarationOrImportEqualsDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ImportEqualsDeclaration | ImportDeclaration { + parseExpected(SyntaxKind.ImportKeyword); - return finishNode(factory.createImportClause(isTypeOnly, identifier, namedBindings), pos); + const afterImportPos = scanner.getStartPos(); + + // We don't parse the identifier here in await context, instead we will report a grammar error in the checker. + let identifier: Identifier | undefined; + if (isIdentifier()) { + identifier = parseIdentifier(); } - function parseModuleReference() { - return isExternalModuleReference() - ? parseExternalModuleReference() - : parseEntityName(/*allowReservedWords*/ false); + let isTypeOnly = false; + if (token() !== SyntaxKind.FromKeyword && + identifier?.escapedText === "type" && + (isIdentifier() || tokenAfterImportDefinitelyProducesImportDeclaration()) + ) { + isTypeOnly = true; + identifier = isIdentifier() ? parseIdentifier() : undefined; } - function parseExternalModuleReference() { - const pos = getNodePos(); - parseExpected(SyntaxKind.RequireKeyword); - parseExpected(SyntaxKind.OpenParenToken); - const expression = parseModuleSpecifier(); - parseExpected(SyntaxKind.CloseParenToken); - return finishNode(factory.createExternalModuleReference(expression), pos); + if (identifier && !tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration()) { + return parseImportEqualsDeclaration(pos, hasJSDoc, decorators, modifiers, identifier, isTypeOnly); } - function parseModuleSpecifier(): Expression { - if (token() === SyntaxKind.StringLiteral) { - const result = parseLiteralNode(); - result.text = internIdentifier(result.text); - return result; - } - else { - // We allow arbitrary expressions here, even though the grammar only allows string - // literals. We check to ensure that it is only a string literal later in the grammar - // check pass. - return parseExpression(); - } + // ImportDeclaration: + // import ImportClause from ModuleSpecifier ; + // import ModuleSpecifier; + let importClause: ImportClause | undefined; + if (identifier || // import id + token() === SyntaxKind.AsteriskToken || // import * + token() === SyntaxKind.OpenBraceToken // import { + ) { + importClause = parseImportClause(identifier, afterImportPos, isTypeOnly); + parseExpected(SyntaxKind.FromKeyword); } + const moduleSpecifier = parseModuleSpecifier(); - function parseNamespaceImport(): NamespaceImport { - // NameSpaceImport: - // * as ImportedBinding - const pos = getNodePos(); - parseExpected(SyntaxKind.AsteriskToken); - parseExpected(SyntaxKind.AsKeyword); - const name = parseIdentifier(); - return finishNode(factory.createNamespaceImport(name), pos); + let assertClause: AssertClause | undefined; + if (token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { + assertClause = parseAssertClause(); } - function parseNamedImportsOrExports(kind: SyntaxKind.NamedImports): NamedImports; - function parseNamedImportsOrExports(kind: SyntaxKind.NamedExports): NamedExports; - function parseNamedImportsOrExports(kind: SyntaxKind): NamedImportsOrExports { - const pos = getNodePos(); + parseSemicolon(); + const node = factory.createImportDeclaration(modifiers, importClause, moduleSpecifier, assertClause); + (node as Mutable).illegalDecorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - // NamedImports: - // { } - // { ImportsList } - // { ImportsList, } + function parseAssertEntry() { + const pos = getNodePos(); + const name = tokenIsIdentifierOrKeyword(token()) ? parseIdentifierName() : parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral; + parseExpected(SyntaxKind.ColonToken); + const value = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); + return finishNode(factory.createAssertEntry(name, value), pos); + } - // ImportsList: - // ImportSpecifier - // ImportsList, ImportSpecifier - const node = kind === SyntaxKind.NamedImports - ? factory.createNamedImports(parseBracketedList(ParsingContext.ImportOrExportSpecifiers, parseImportSpecifier, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)) - : factory.createNamedExports(parseBracketedList(ParsingContext.ImportOrExportSpecifiers, parseExportSpecifier, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)); - return finishNode(node, pos); + function parseAssertClause(skipAssertKeyword?: true) { + const pos = getNodePos(); + if (!skipAssertKeyword) { + parseExpected(SyntaxKind.AssertKeyword); + } + const openBracePosition = scanner.getTokenPos(); + if (parseExpected(SyntaxKind.OpenBraceToken)) { + const multiLine = scanner.hasPrecedingLineBreak(); + const elements = parseDelimitedList(ParsingContext.AssertEntries, parseAssertEntry, /*considerSemicolonAsDelimiter*/ true); + if (!parseExpected(SyntaxKind.CloseBraceToken)) { + const lastError = lastOrUndefined(parseDiagnostics); + if (lastError && lastError.code === Diagnostics._0_expected.code) { + addRelatedInfo( + lastError, + createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_1_to_match_the_0_token_here, "{", "}") + ); + } + } + return finishNode(factory.createAssertClause(elements, multiLine), pos); + } + else { + const elements = createNodeArray([], getNodePos(), /*end*/ undefined, /*hasTrailingComma*/ false); + return finishNode(factory.createAssertClause(elements, /*multiLine*/ false), pos); } + } - function parseExportSpecifier() { - const hasJSDoc = hasPrecedingJSDocComment(); - return withJSDoc(parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier) as ExportSpecifier, hasJSDoc); + function tokenAfterImportDefinitelyProducesImportDeclaration() { + return token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.OpenBraceToken; + } + + function tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration() { + // In `import id ___`, the current token decides whether to produce + // an ImportDeclaration or ImportEqualsDeclaration. + return token() === SyntaxKind.CommaToken || token() === SyntaxKind.FromKeyword; + } + + function parseImportEqualsDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, identifier: Identifier, isTypeOnly: boolean): ImportEqualsDeclaration { + parseExpected(SyntaxKind.EqualsToken); + const moduleReference = parseModuleReference(); + parseSemicolon(); + const node = factory.createImportEqualsDeclaration(modifiers, isTypeOnly, identifier, moduleReference); + (node as Mutable).illegalDecorators = decorators; + const finished = withJSDoc(finishNode(node, pos), hasJSDoc); + return finished; + } + + function parseImportClause(identifier: Identifier | undefined, pos: number, isTypeOnly: boolean) { + // ImportClause: + // ImportedDefaultBinding + // NameSpaceImport + // NamedImports + // ImportedDefaultBinding, NameSpaceImport + // ImportedDefaultBinding, NamedImports + + // If there was no default import or if there is comma token after default import + // parse namespace or named imports + let namedBindings: NamespaceImport | NamedImports | undefined; + if (!identifier || + parseOptional(SyntaxKind.CommaToken)) { + namedBindings = token() === SyntaxKind.AsteriskToken ? parseNamespaceImport() : parseNamedImportsOrExports(SyntaxKind.NamedImports); } - function parseImportSpecifier() { - return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier) as ImportSpecifier; + return finishNode(factory.createImportClause(isTypeOnly, identifier, namedBindings), pos); + } + + function parseModuleReference() { + return isExternalModuleReference() + ? parseExternalModuleReference() + : parseEntityName(/*allowReservedWords*/ false); + } + + function parseExternalModuleReference() { + const pos = getNodePos(); + parseExpected(SyntaxKind.RequireKeyword); + parseExpected(SyntaxKind.OpenParenToken); + const expression = parseModuleSpecifier(); + parseExpected(SyntaxKind.CloseParenToken); + return finishNode(factory.createExternalModuleReference(expression), pos); + } + + function parseModuleSpecifier(): Expression { + if (token() === SyntaxKind.StringLiteral) { + const result = parseLiteralNode(); + result.text = internIdentifier(result.text); + return result; + } + else { + // We allow arbitrary expressions here, even though the grammar only allows string + // literals. We check to ensure that it is only a string literal later in the grammar + // check pass. + return parseExpression(); } + } + + function parseNamespaceImport(): NamespaceImport { + // NameSpaceImport: + // * as ImportedBinding + const pos = getNodePos(); + parseExpected(SyntaxKind.AsteriskToken); + parseExpected(SyntaxKind.AsKeyword); + const name = parseIdentifier(); + return finishNode(factory.createNamespaceImport(name), pos); + } + + function parseNamedImportsOrExports(kind: SyntaxKind.NamedImports): NamedImports; + function parseNamedImportsOrExports(kind: SyntaxKind.NamedExports): NamedExports; + function parseNamedImportsOrExports(kind: SyntaxKind): NamedImportsOrExports { + const pos = getNodePos(); + + // NamedImports: + // { } + // { ImportsList } + // { ImportsList, } + + // ImportsList: + // ImportSpecifier + // ImportsList, ImportSpecifier + const node = kind === SyntaxKind.NamedImports + ? factory.createNamedImports(parseBracketedList(ParsingContext.ImportOrExportSpecifiers, parseImportSpecifier, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)) + : factory.createNamedExports(parseBracketedList(ParsingContext.ImportOrExportSpecifiers, parseExportSpecifier, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)); + return finishNode(node, pos); + } + + function parseExportSpecifier() { + const hasJSDoc = hasPrecedingJSDocComment(); + return withJSDoc(parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier) as ExportSpecifier, hasJSDoc); + } + + function parseImportSpecifier() { + return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier) as ImportSpecifier; + } - function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier { - const pos = getNodePos(); - // ImportSpecifier: - // BindingIdentifier - // IdentifierName as BindingIdentifier - // ExportSpecifier: - // IdentifierName - // IdentifierName as IdentifierName - let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); - let checkIdentifierStart = scanner.getTokenPos(); - let checkIdentifierEnd = scanner.getTextPos(); - let isTypeOnly = false; - let propertyName: Identifier | undefined; - let canParseAsKeyword = true; - let name = parseIdentifierName(); - if (name.escapedText === "type") { - // If the first token of an import specifier is 'type', there are a lot of possibilities, - // especially if we see 'as' afterwards: - // - // import { type } from "mod"; - isTypeOnly: false, name: type - // import { type as } from "mod"; - isTypeOnly: true, name: as - // import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type - // import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as + function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier { + const pos = getNodePos(); + // ImportSpecifier: + // BindingIdentifier + // IdentifierName as BindingIdentifier + // ExportSpecifier: + // IdentifierName + // IdentifierName as IdentifierName + let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); + let checkIdentifierStart = scanner.getTokenPos(); + let checkIdentifierEnd = scanner.getTextPos(); + let isTypeOnly = false; + let propertyName: Identifier | undefined; + let canParseAsKeyword = true; + let name = parseIdentifierName(); + if (name.escapedText === "type") { + // If the first token of an import specifier is 'type', there are a lot of possibilities, + // especially if we see 'as' afterwards: + // + // import { type } from "mod"; - isTypeOnly: false, name: type + // import { type as } from "mod"; - isTypeOnly: true, name: as + // import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type + // import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as + if (token() === SyntaxKind.AsKeyword) { + // { type as ...? } + const firstAs = parseIdentifierName(); if (token() === SyntaxKind.AsKeyword) { - // { type as ...? } - const firstAs = parseIdentifierName(); - if (token() === SyntaxKind.AsKeyword) { - // { type as as ...? } - const secondAs = parseIdentifierName(); - if (tokenIsIdentifierOrKeyword(token())) { - // { type as as something } - isTypeOnly = true; - propertyName = firstAs; - name = parseNameWithKeywordCheck(); - canParseAsKeyword = false; - } - else { - // { type as as } - propertyName = name; - name = secondAs; - canParseAsKeyword = false; - } - } - else if (tokenIsIdentifierOrKeyword(token())) { - // { type as something } - propertyName = name; - canParseAsKeyword = false; + // { type as as ...? } + const secondAs = parseIdentifierName(); + if (tokenIsIdentifierOrKeyword(token())) { + // { type as as something } + isTypeOnly = true; + propertyName = firstAs; name = parseNameWithKeywordCheck(); + canParseAsKeyword = false; } else { - // { type as } - isTypeOnly = true; - name = firstAs; + // { type as as } + propertyName = name; + name = secondAs; + canParseAsKeyword = false; } } else if (tokenIsIdentifierOrKeyword(token())) { - // { type something ...? } - isTypeOnly = true; + // { type as something } + propertyName = name; + canParseAsKeyword = false; name = parseNameWithKeywordCheck(); } + else { + // { type as } + isTypeOnly = true; + name = firstAs; + } } - - if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) { - propertyName = name; - parseExpected(SyntaxKind.AsKeyword); + else if (tokenIsIdentifierOrKeyword(token())) { + // { type something ...? } + isTypeOnly = true; name = parseNameWithKeywordCheck(); } - if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) { - parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected); - } - const node = kind === SyntaxKind.ImportSpecifier - ? factory.createImportSpecifier(isTypeOnly, propertyName, name) - : factory.createExportSpecifier(isTypeOnly, propertyName, name); - return finishNode(node, pos); + } - function parseNameWithKeywordCheck() { - checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); - checkIdentifierStart = scanner.getTokenPos(); - checkIdentifierEnd = scanner.getTextPos(); - return parseIdentifierName(); - } + if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) { + propertyName = name; + parseExpected(SyntaxKind.AsKeyword); + name = parseNameWithKeywordCheck(); } + if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) { + parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected); + } + const node = kind === SyntaxKind.ImportSpecifier + ? factory.createImportSpecifier(isTypeOnly, propertyName, name) + : factory.createExportSpecifier(isTypeOnly, propertyName, name); + return finishNode(node, pos); - function parseNamespaceExport(pos: number): NamespaceExport { - return finishNode(factory.createNamespaceExport(parseIdentifierName()), pos); + function parseNameWithKeywordCheck() { + checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); + checkIdentifierStart = scanner.getTokenPos(); + checkIdentifierEnd = scanner.getTextPos(); + return parseIdentifierName(); } + } - function parseExportDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ExportDeclaration { - const savedAwaitContext = inAwaitContext(); - setAwaitContext(/*value*/ true); - let exportClause: NamedExportBindings | undefined; - let moduleSpecifier: Expression | undefined; - let assertClause: AssertClause | undefined; - const isTypeOnly = parseOptional(SyntaxKind.TypeKeyword); - const namespaceExportPos = getNodePos(); - if (parseOptional(SyntaxKind.AsteriskToken)) { - if (parseOptional(SyntaxKind.AsKeyword)) { - exportClause = parseNamespaceExport(namespaceExportPos); - } + function parseNamespaceExport(pos: number): NamespaceExport { + return finishNode(factory.createNamespaceExport(parseIdentifierName()), pos); + } + + function parseExportDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ExportDeclaration { + const savedAwaitContext = inAwaitContext(); + setAwaitContext(/*value*/ true); + let exportClause: NamedExportBindings | undefined; + let moduleSpecifier: Expression | undefined; + let assertClause: AssertClause | undefined; + const isTypeOnly = parseOptional(SyntaxKind.TypeKeyword); + const namespaceExportPos = getNodePos(); + if (parseOptional(SyntaxKind.AsteriskToken)) { + if (parseOptional(SyntaxKind.AsKeyword)) { + exportClause = parseNamespaceExport(namespaceExportPos); + } + parseExpected(SyntaxKind.FromKeyword); + moduleSpecifier = parseModuleSpecifier(); + } + else { + exportClause = parseNamedImportsOrExports(SyntaxKind.NamedExports); + // It is not uncommon to accidentally omit the 'from' keyword. Additionally, in editing scenarios, + // the 'from' keyword can be parsed as a named export when the export clause is unterminated (i.e. `export { from "moduleName";`) + // If we don't have a 'from' keyword, see if we have a string literal such that ASI won't take effect. + if (token() === SyntaxKind.FromKeyword || (token() === SyntaxKind.StringLiteral && !scanner.hasPrecedingLineBreak())) { parseExpected(SyntaxKind.FromKeyword); moduleSpecifier = parseModuleSpecifier(); } - else { - exportClause = parseNamedImportsOrExports(SyntaxKind.NamedExports); - // It is not uncommon to accidentally omit the 'from' keyword. Additionally, in editing scenarios, - // the 'from' keyword can be parsed as a named export when the export clause is unterminated (i.e. `export { from "moduleName";`) - // If we don't have a 'from' keyword, see if we have a string literal such that ASI won't take effect. - if (token() === SyntaxKind.FromKeyword || (token() === SyntaxKind.StringLiteral && !scanner.hasPrecedingLineBreak())) { - parseExpected(SyntaxKind.FromKeyword); - moduleSpecifier = parseModuleSpecifier(); - } - } - if (moduleSpecifier && token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { - assertClause = parseAssertClause(); - } - parseSemicolon(); - setAwaitContext(savedAwaitContext); - const node = factory.createExportDeclaration(modifiers, isTypeOnly, exportClause, moduleSpecifier, assertClause); - (node as Mutable).illegalDecorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); } + if (moduleSpecifier && token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { + assertClause = parseAssertClause(); + } + parseSemicolon(); + setAwaitContext(savedAwaitContext); + const node = factory.createExportDeclaration(modifiers, isTypeOnly, exportClause, moduleSpecifier, assertClause); + (node as Mutable).illegalDecorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - function parseExportAssignment(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ExportAssignment { - const savedAwaitContext = inAwaitContext(); - setAwaitContext(/*value*/ true); - let isExportEquals: boolean | undefined; - if (parseOptional(SyntaxKind.EqualsToken)) { - isExportEquals = true; - } - else { - parseExpected(SyntaxKind.DefaultKeyword); - } - const expression = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); - parseSemicolon(); - setAwaitContext(savedAwaitContext); - const node = factory.createExportAssignment(modifiers, isExportEquals, expression); - (node as Mutable).illegalDecorators = decorators; - return withJSDoc(finishNode(node, pos), hasJSDoc); + function parseExportAssignment(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ExportAssignment { + const savedAwaitContext = inAwaitContext(); + setAwaitContext(/*value*/ true); + let isExportEquals: boolean | undefined; + if (parseOptional(SyntaxKind.EqualsToken)) { + isExportEquals = true; + } + else { + parseExpected(SyntaxKind.DefaultKeyword); } + const expression = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); + parseSemicolon(); + setAwaitContext(savedAwaitContext); + const node = factory.createExportAssignment(modifiers, isExportEquals, expression); + (node as Mutable).illegalDecorators = decorators; + return withJSDoc(finishNode(node, pos), hasJSDoc); + } - const enum ParsingContext { - SourceElements, // Elements in source file - BlockStatements, // Statements in block - SwitchClauses, // Clauses in switch statement - SwitchClauseStatements, // Statements in switch clause - TypeMembers, // Members in interface or type literal - ClassMembers, // Members in class declaration - EnumMembers, // Members in enum declaration - HeritageClauseElement, // Elements in a heritage clause - VariableDeclarations, // Variable declarations in variable statement - ObjectBindingElements, // Binding elements in object binding list - ArrayBindingElements, // Binding elements in array binding list - ArgumentExpressions, // Expressions in argument list - ObjectLiteralMembers, // Members in object literal - JsxAttributes, // Attributes in jsx element - JsxChildren, // Things between opening and closing JSX tags - ArrayLiteralMembers, // Members in array literal - Parameters, // Parameters in parameter list - JSDocParameters, // JSDoc parameters in parameter list of JSDoc function type - RestProperties, // Property names in a rest type list - TypeParameters, // Type parameters in type parameter list - TypeArguments, // Type arguments in type argument list - TupleElementTypes, // Element types in tuple element type list - HeritageClauses, // Heritage clauses for a class or interface declaration. - ImportOrExportSpecifiers, // Named import clause's import specifier list, - AssertEntries, // Import entries list. - Count // Number of parsing contexts - } - - const enum Tristate { - False, - True, - Unknown - } - - export namespace JSDocParser { - export function parseJSDocTypeExpressionForTests(content: string, start: number | undefined, length: number | undefined): { jsDocTypeExpression: JSDocTypeExpression, diagnostics: Diagnostic[] } | undefined { - initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); - scanner.setText(content, start, length); - currentToken = scanner.scan(); - const jsDocTypeExpression = parseJSDocTypeExpression(); - - const sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS, /*isDeclarationFile*/ false, [], factory.createToken(SyntaxKind.EndOfFileToken), NodeFlags.None, noop); - const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); - if (jsDocDiagnostics) { - sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); - } - - clearState(); - - return jsDocTypeExpression ? { jsDocTypeExpression, diagnostics } : undefined; - } - - // Parses out a JSDoc type expression. - export function parseJSDocTypeExpression(mayOmitBraces?: boolean): JSDocTypeExpression { - const pos = getNodePos(); - const hasBrace = (mayOmitBraces ? parseOptional : parseExpected)(SyntaxKind.OpenBraceToken); - const type = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); - if (!mayOmitBraces || hasBrace) { - parseExpectedJSDoc(SyntaxKind.CloseBraceToken); - } + const enum ParsingContext { + SourceElements, // Elements in source file + BlockStatements, // Statements in block + SwitchClauses, // Clauses in switch statement + SwitchClauseStatements, // Statements in switch clause + TypeMembers, // Members in interface or type literal + ClassMembers, // Members in class declaration + EnumMembers, // Members in enum declaration + HeritageClauseElement, // Elements in a heritage clause + VariableDeclarations, // Variable declarations in variable statement + ObjectBindingElements, // Binding elements in object binding list + ArrayBindingElements, // Binding elements in array binding list + ArgumentExpressions, // Expressions in argument list + ObjectLiteralMembers, // Members in object literal + JsxAttributes, // Attributes in jsx element + JsxChildren, // Things between opening and closing JSX tags + ArrayLiteralMembers, // Members in array literal + Parameters, // Parameters in parameter list + JSDocParameters, // JSDoc parameters in parameter list of JSDoc function type + RestProperties, // Property names in a rest type list + TypeParameters, // Type parameters in type parameter list + TypeArguments, // Type arguments in type argument list + TupleElementTypes, // Element types in tuple element type list + HeritageClauses, // Heritage clauses for a class or interface declaration. + ImportOrExportSpecifiers, // Named import clause's import specifier list, + AssertEntries, // Import entries list. + Count // Number of parsing contexts + } - const result = factory.createJSDocTypeExpression(type); - fixupParentReferences(result); - return finishNode(result, pos); - } + const enum Tristate { + False, + True, + Unknown + } - export function parseJSDocNameReference(): JSDocNameReference { - const pos = getNodePos(); - const hasBrace = parseOptional(SyntaxKind.OpenBraceToken); - const p2 = getNodePos(); - let entityName: EntityName | JSDocMemberName = parseEntityName(/* allowReservedWords*/ false); - while (token() === SyntaxKind.PrivateIdentifier) { - reScanHashToken(); // rescan #id as # id - nextTokenJSDoc(); // then skip the # - entityName = finishNode(factory.createJSDocMemberName(entityName, parseIdentifier()), p2); - } - if (hasBrace) { - parseExpectedJSDoc(SyntaxKind.CloseBraceToken); - } + export namespace JSDocParser { + export function parseJSDocTypeExpressionForTests(content: string, start: number | undefined, length: number | undefined): { jsDocTypeExpression: JSDocTypeExpression, diagnostics: Diagnostic[] } | undefined { + initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); + scanner.setText(content, start, length); + currentToken = scanner.scan(); + const jsDocTypeExpression = parseJSDocTypeExpression(); - const result = factory.createJSDocNameReference(entityName); - fixupParentReferences(result); - return finishNode(result, pos); + const sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS, /*isDeclarationFile*/ false, [], factory.createToken(SyntaxKind.EndOfFileToken), NodeFlags.None, noop); + const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); + if (jsDocDiagnostics) { + sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); } - export function parseIsolatedJSDocComment(content: string, start: number | undefined, length: number | undefined): { jsDoc: JSDoc, diagnostics: Diagnostic[] } | undefined { - initializeState("", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); - const jsDoc = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); + clearState(); + + return jsDocTypeExpression ? { jsDocTypeExpression, diagnostics } : undefined; + } + + // Parses out a JSDoc type expression. + export function parseJSDocTypeExpression(mayOmitBraces?: boolean): JSDocTypeExpression { + const pos = getNodePos(); + const hasBrace = (mayOmitBraces ? parseOptional : parseExpected)(SyntaxKind.OpenBraceToken); + const type = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); + if (!mayOmitBraces || hasBrace) { + parseExpectedJSDoc(SyntaxKind.CloseBraceToken); + } - const sourceFile = { languageVariant: LanguageVariant.Standard, text: content } as SourceFile; - const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); - clearState(); + const result = factory.createJSDocTypeExpression(type); + fixupParentReferences(result); + return finishNode(result, pos); + } - return jsDoc ? { jsDoc, diagnostics } : undefined; + export function parseJSDocNameReference(): JSDocNameReference { + const pos = getNodePos(); + const hasBrace = parseOptional(SyntaxKind.OpenBraceToken); + const p2 = getNodePos(); + let entityName: EntityName | JSDocMemberName = parseEntityName(/* allowReservedWords*/ false); + while (token() === SyntaxKind.PrivateIdentifier) { + reScanHashToken(); // rescan #id as # id + nextTokenJSDoc(); // then skip the # + entityName = finishNode(factory.createJSDocMemberName(entityName, parseIdentifier()), p2); + } + if (hasBrace) { + parseExpectedJSDoc(SyntaxKind.CloseBraceToken); } - export function parseJSDocComment(parent: HasJSDoc, start: number, length: number): JSDoc | undefined { - const saveToken = currentToken; - const saveParseDiagnosticsLength = parseDiagnostics.length; - const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; + const result = factory.createJSDocNameReference(entityName); + fixupParentReferences(result); + return finishNode(result, pos); + } - const comment = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); - setParent(comment, parent); + export function parseIsolatedJSDocComment(content: string, start: number | undefined, length: number | undefined): { jsDoc: JSDoc, diagnostics: Diagnostic[] } | undefined { + initializeState("", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); + const jsDoc = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); - if (contextFlags & NodeFlags.JavaScriptFile) { - if (!jsDocDiagnostics) { - jsDocDiagnostics = []; - } - jsDocDiagnostics.push(...parseDiagnostics); + const sourceFile = { languageVariant: LanguageVariant.Standard, text: content } as SourceFile; + const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); + clearState(); + + return jsDoc ? { jsDoc, diagnostics } : undefined; + } + + export function parseJSDocComment(parent: HasJSDoc, start: number, length: number): JSDoc | undefined { + const saveToken = currentToken; + const saveParseDiagnosticsLength = parseDiagnostics.length; + const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; + + const comment = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); + setParent(comment, parent); + + if (contextFlags & NodeFlags.JavaScriptFile) { + if (!jsDocDiagnostics) { + jsDocDiagnostics = []; } - currentToken = saveToken; - parseDiagnostics.length = saveParseDiagnosticsLength; - parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; - return comment; + jsDocDiagnostics.push(...parseDiagnostics); } + currentToken = saveToken; + parseDiagnostics.length = saveParseDiagnosticsLength; + parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; + return comment; + } - const enum JSDocState { - BeginningOfLine, - SawAsterisk, - SavingComments, - SavingBackticks, // NOTE: Only used when parsing tag comments - } + const enum JSDocState { + BeginningOfLine, + SawAsterisk, + SavingComments, + SavingBackticks, // NOTE: Only used when parsing tag comments + } - const enum PropertyLikeParse { - Property = 1 << 0, - Parameter = 1 << 1, - CallbackParameter = 1 << 2, - } + const enum PropertyLikeParse { + Property = 1 << 0, + Parameter = 1 << 1, + CallbackParameter = 1 << 2, + } - function parseJSDocCommentWorker(start = 0, length: number | undefined): JSDoc | undefined { - const content = sourceText; - const end = length === undefined ? content.length : start + length; - length = end - start; + function parseJSDocCommentWorker(start = 0, length: number | undefined): JSDoc | undefined { + const content = sourceText; + const end = length === undefined ? content.length : start + length; + length = end - start; - Debug.assert(start >= 0); - Debug.assert(start <= end); - Debug.assert(end <= content.length); + Debug.assert(start >= 0); + Debug.assert(start <= end); + Debug.assert(end <= content.length); - // Check for /** (JSDoc opening part) - if (!isJSDocLikeText(content, start)) { - return undefined; - } + // Check for /** (JSDoc opening part) + if (!isJSDocLikeText(content, start)) { + return undefined; + } - let tags: JSDocTag[]; - let tagsPos: number; - let tagsEnd: number; - let linkEnd: number; - let commentsPos: number | undefined; - let comments: string[] = []; - const parts: JSDocComment[] = []; + let tags: JSDocTag[]; + let tagsPos: number; + let tagsEnd: number; + let linkEnd: number; + let commentsPos: number | undefined; + let comments: string[] = []; + const parts: JSDocComment[] = []; - // + 3 for leading /**, - 5 in total for /** */ - return scanner.scanRange(start + 3, length - 5, () => { - // Initially we can parse out a tag. We also have seen a starting asterisk. - // This is so that /** * @type */ doesn't parse. - let state = JSDocState.SawAsterisk; - let margin: number | undefined; - // + 4 for leading '/** ' - // + 1 because the last index of \n is always one index before the first character in the line and coincidentally, if there is no \n before start, it is -1, which is also one index before the first character - let indent = start - (content.lastIndexOf("\n", start) + 1) + 4; - function pushComment(text: string) { - if (!margin) { - margin = indent; - } - comments.push(text); - indent += text.length; + // + 3 for leading /**, - 5 in total for /** */ + return scanner.scanRange(start + 3, length - 5, () => { + // Initially we can parse out a tag. We also have seen a starting asterisk. + // This is so that /** * @type */ doesn't parse. + let state = JSDocState.SawAsterisk; + let margin: number | undefined; + // + 4 for leading '/** ' + // + 1 because the last index of \n is always one index before the first character in the line and coincidentally, if there is no \n before start, it is -1, which is also one index before the first character + let indent = start - (content.lastIndexOf("\n", start) + 1) + 4; + function pushComment(text: string) { + if (!margin) { + margin = indent; } + comments.push(text); + indent += text.length; + } - nextTokenJSDoc(); - while (parseOptionalJsdoc(SyntaxKind.WhitespaceTrivia)); - if (parseOptionalJsdoc(SyntaxKind.NewLineTrivia)) { - state = JSDocState.BeginningOfLine; - indent = 0; - } - loop: while (true) { - switch (token()) { - case SyntaxKind.AtToken: - if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) { - removeTrailingWhitespace(comments); - if (!commentsPos) commentsPos = getNodePos(); - addTag(parseTag(indent)); - // NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag. - // Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning - // for malformed examples like `/** @param {string} x @returns {number} the length */` - state = JSDocState.BeginningOfLine; - margin = undefined; - } - else { - pushComment(scanner.getTokenText()); - } - break; - case SyntaxKind.NewLineTrivia: - comments.push(scanner.getTokenText()); + nextTokenJSDoc(); + while (parseOptionalJsdoc(SyntaxKind.WhitespaceTrivia)); + if (parseOptionalJsdoc(SyntaxKind.NewLineTrivia)) { + state = JSDocState.BeginningOfLine; + indent = 0; + } + loop: while (true) { + switch (token()) { + case SyntaxKind.AtToken: + if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) { + removeTrailingWhitespace(comments); + if (!commentsPos) commentsPos = getNodePos(); + addTag(parseTag(indent)); + // NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag. + // Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning + // for malformed examples like `/** @param {string} x @returns {number} the length */` state = JSDocState.BeginningOfLine; - indent = 0; - break; - case SyntaxKind.AsteriskToken: - const asterisk = scanner.getTokenText(); - if (state === JSDocState.SawAsterisk || state === JSDocState.SavingComments) { - // If we've already seen an asterisk, then we can no longer parse a tag on this line - state = JSDocState.SavingComments; - pushComment(asterisk); - } - else { - // Ignore the first asterisk on a line - state = JSDocState.SawAsterisk; - indent += asterisk.length; - } - break; - case SyntaxKind.WhitespaceTrivia: - // only collect whitespace if we're already saving comments or have just crossed the comment indent margin - const whitespace = scanner.getTokenText(); - if (state === JSDocState.SavingComments) { - comments.push(whitespace); - } - else if (margin !== undefined && indent + whitespace.length > margin) { - comments.push(whitespace.slice(margin - indent)); - } - indent += whitespace.length; - break; - case SyntaxKind.EndOfFileToken: - break loop; - case SyntaxKind.OpenBraceToken: + margin = undefined; + } + else { + pushComment(scanner.getTokenText()); + } + break; + case SyntaxKind.NewLineTrivia: + comments.push(scanner.getTokenText()); + state = JSDocState.BeginningOfLine; + indent = 0; + break; + case SyntaxKind.AsteriskToken: + const asterisk = scanner.getTokenText(); + if (state === JSDocState.SawAsterisk || state === JSDocState.SavingComments) { + // If we've already seen an asterisk, then we can no longer parse a tag on this line state = JSDocState.SavingComments; - const commentEnd = scanner.getStartPos(); - const linkStart = scanner.getTextPos() - 1; - const link = parseJSDocLink(linkStart); - if (link) { - if (!linkEnd) { - removeLeadingNewlines(comments); - } - parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? start, commentEnd)); - parts.push(link); - comments = []; - linkEnd = scanner.getTextPos(); - break; + pushComment(asterisk); + } + else { + // Ignore the first asterisk on a line + state = JSDocState.SawAsterisk; + indent += asterisk.length; + } + break; + case SyntaxKind.WhitespaceTrivia: + // only collect whitespace if we're already saving comments or have just crossed the comment indent margin + const whitespace = scanner.getTokenText(); + if (state === JSDocState.SavingComments) { + comments.push(whitespace); + } + else if (margin !== undefined && indent + whitespace.length > margin) { + comments.push(whitespace.slice(margin - indent)); + } + indent += whitespace.length; + break; + case SyntaxKind.EndOfFileToken: + break loop; + case SyntaxKind.OpenBraceToken: + state = JSDocState.SavingComments; + const commentEnd = scanner.getStartPos(); + const linkStart = scanner.getTextPos() - 1; + const link = parseJSDocLink(linkStart); + if (link) { + if (!linkEnd) { + removeLeadingNewlines(comments); } - // fallthrough if it's not a {@link sequence - default: - // Anything else is doc comment text. We just save it. Because it - // wasn't a tag, we can no longer parse a tag on this line until we hit the next - // line break. - state = JSDocState.SavingComments; - pushComment(scanner.getTokenText()); + parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? start, commentEnd)); + parts.push(link); + comments = []; + linkEnd = scanner.getTextPos(); break; - } - nextTokenJSDoc(); - } - removeTrailingWhitespace(comments); - if (parts.length && comments.length) { - parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? start, commentsPos)); - } - if (parts.length && tags) Debug.assertIsDefined(commentsPos, "having parsed tags implies that the end of the comment span should be set"); - const tagsArray = tags && createNodeArray(tags, tagsPos, tagsEnd); - return finishNode(factory.createJSDocComment(parts.length ? createNodeArray(parts, start, commentsPos) : comments.length ? comments.join("") : undefined, tagsArray), start, end); - }); - - function removeLeadingNewlines(comments: string[]) { - while (comments.length && (comments[0] === "\n" || comments[0] === "\r")) { - comments.shift(); + } + // fallthrough if it's not a {@link sequence + default: + // Anything else is doc comment text. We just save it. Because it + // wasn't a tag, we can no longer parse a tag on this line until we hit the next + // line break. + state = JSDocState.SavingComments; + pushComment(scanner.getTokenText()); + break; } + nextTokenJSDoc(); + } + removeTrailingWhitespace(comments); + if (parts.length && comments.length) { + parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? start, commentsPos)); } + if (parts.length && tags) Debug.assertIsDefined(commentsPos, "having parsed tags implies that the end of the comment span should be set"); + const tagsArray = tags && createNodeArray(tags, tagsPos, tagsEnd); + return finishNode(factory.createJSDocComment(parts.length ? createNodeArray(parts, start, commentsPos) : comments.length ? comments.join("") : undefined, tagsArray), start, end); + }); - function removeTrailingWhitespace(comments: string[]) { - while (comments.length && comments[comments.length - 1].trim() === "") { - comments.pop(); - } + function removeLeadingNewlines(comments: string[]) { + while (comments.length && (comments[0] === "\n" || comments[0] === "\r")) { + comments.shift(); } + } - function isNextNonwhitespaceTokenEndOfFile(): boolean { - // We must use infinite lookahead, as there could be any number of newlines :( - while (true) { - nextTokenJSDoc(); - if (token() === SyntaxKind.EndOfFileToken) { - return true; - } - if (!(token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia)) { - return false; - } - } + function removeTrailingWhitespace(comments: string[]) { + while (comments.length && comments[comments.length - 1].trim() === "") { + comments.pop(); } + } - function skipWhitespace(): void { - if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { - if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { - return; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range - } + function isNextNonwhitespaceTokenEndOfFile(): boolean { + // We must use infinite lookahead, as there could be any number of newlines :( + while (true) { + nextTokenJSDoc(); + if (token() === SyntaxKind.EndOfFileToken) { + return true; } - while (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { - nextTokenJSDoc(); + if (!(token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia)) { + return false; } } + } - function skipWhitespaceOrAsterisk(): string { - if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { - if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { - return ""; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range - } + function skipWhitespace(): void { + if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { + if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { + return; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range } + } + while (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { + nextTokenJSDoc(); + } + } - let precedingLineBreak = scanner.hasPrecedingLineBreak(); - let seenLineBreak = false; - let indentText = ""; - while ((precedingLineBreak && token() === SyntaxKind.AsteriskToken) || token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { - indentText += scanner.getTokenText(); - if (token() === SyntaxKind.NewLineTrivia) { - precedingLineBreak = true; - seenLineBreak = true; - indentText = ""; - } - else if (token() === SyntaxKind.AsteriskToken) { - precedingLineBreak = false; - } - nextTokenJSDoc(); + function skipWhitespaceOrAsterisk(): string { + if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { + if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { + return ""; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range } - return seenLineBreak ? indentText : ""; } - function parseTag(margin: number) { - Debug.assert(token() === SyntaxKind.AtToken); - const start = scanner.getTokenPos(); + let precedingLineBreak = scanner.hasPrecedingLineBreak(); + let seenLineBreak = false; + let indentText = ""; + while ((precedingLineBreak && token() === SyntaxKind.AsteriskToken) || token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { + indentText += scanner.getTokenText(); + if (token() === SyntaxKind.NewLineTrivia) { + precedingLineBreak = true; + seenLineBreak = true; + indentText = ""; + } + else if (token() === SyntaxKind.AsteriskToken) { + precedingLineBreak = false; + } nextTokenJSDoc(); + } + return seenLineBreak ? indentText : ""; + } - const tagName = parseJSDocIdentifierName(/*message*/ undefined); - const indentText = skipWhitespaceOrAsterisk(); + function parseTag(margin: number) { + Debug.assert(token() === SyntaxKind.AtToken); + const start = scanner.getTokenPos(); + nextTokenJSDoc(); - let tag: JSDocTag | undefined; - switch (tagName.escapedText) { - case "author": - tag = parseAuthorTag(start, tagName, margin, indentText); - break; - case "implements": - tag = parseImplementsTag(start, tagName, margin, indentText); - break; - case "augments": - case "extends": - tag = parseAugmentsTag(start, tagName, margin, indentText); - break; - case "class": - case "constructor": - tag = parseSimpleTag(start, factory.createJSDocClassTag, tagName, margin, indentText); - break; - case "public": - tag = parseSimpleTag(start, factory.createJSDocPublicTag, tagName, margin, indentText); - break; - case "private": - tag = parseSimpleTag(start, factory.createJSDocPrivateTag, tagName, margin, indentText); - break; - case "protected": - tag = parseSimpleTag(start, factory.createJSDocProtectedTag, tagName, margin, indentText); - break; - case "readonly": - tag = parseSimpleTag(start, factory.createJSDocReadonlyTag, tagName, margin, indentText); - break; - case "override": - tag = parseSimpleTag(start, factory.createJSDocOverrideTag, tagName, margin, indentText); - break; - case "deprecated": - hasDeprecatedTag = true; - tag = parseSimpleTag(start, factory.createJSDocDeprecatedTag, tagName, margin, indentText); - break; - case "this": - tag = parseThisTag(start, tagName, margin, indentText); - break; - case "enum": - tag = parseEnumTag(start, tagName, margin, indentText); - break; - case "arg": - case "argument": - case "param": - return parseParameterOrPropertyTag(start, tagName, PropertyLikeParse.Parameter, margin); - case "return": - case "returns": - tag = parseReturnTag(start, tagName, margin, indentText); - break; - case "template": - tag = parseTemplateTag(start, tagName, margin, indentText); - break; - case "type": - tag = parseTypeTag(start, tagName, margin, indentText); - break; - case "typedef": - tag = parseTypedefTag(start, tagName, margin, indentText); - break; - case "callback": - tag = parseCallbackTag(start, tagName, margin, indentText); - break; - case "see": - tag = parseSeeTag(start, tagName, margin, indentText); - break; - default: - tag = parseUnknownTag(start, tagName, margin, indentText); - break; - } - return tag; + const tagName = parseJSDocIdentifierName(/*message*/ undefined); + const indentText = skipWhitespaceOrAsterisk(); + + let tag: JSDocTag | undefined; + switch (tagName.escapedText) { + case "author": + tag = parseAuthorTag(start, tagName, margin, indentText); + break; + case "implements": + tag = parseImplementsTag(start, tagName, margin, indentText); + break; + case "augments": + case "extends": + tag = parseAugmentsTag(start, tagName, margin, indentText); + break; + case "class": + case "constructor": + tag = parseSimpleTag(start, factory.createJSDocClassTag, tagName, margin, indentText); + break; + case "public": + tag = parseSimpleTag(start, factory.createJSDocPublicTag, tagName, margin, indentText); + break; + case "private": + tag = parseSimpleTag(start, factory.createJSDocPrivateTag, tagName, margin, indentText); + break; + case "protected": + tag = parseSimpleTag(start, factory.createJSDocProtectedTag, tagName, margin, indentText); + break; + case "readonly": + tag = parseSimpleTag(start, factory.createJSDocReadonlyTag, tagName, margin, indentText); + break; + case "override": + tag = parseSimpleTag(start, factory.createJSDocOverrideTag, tagName, margin, indentText); + break; + case "deprecated": + hasDeprecatedTag = true; + tag = parseSimpleTag(start, factory.createJSDocDeprecatedTag, tagName, margin, indentText); + break; + case "this": + tag = parseThisTag(start, tagName, margin, indentText); + break; + case "enum": + tag = parseEnumTag(start, tagName, margin, indentText); + break; + case "arg": + case "argument": + case "param": + return parseParameterOrPropertyTag(start, tagName, PropertyLikeParse.Parameter, margin); + case "return": + case "returns": + tag = parseReturnTag(start, tagName, margin, indentText); + break; + case "template": + tag = parseTemplateTag(start, tagName, margin, indentText); + break; + case "type": + tag = parseTypeTag(start, tagName, margin, indentText); + break; + case "typedef": + tag = parseTypedefTag(start, tagName, margin, indentText); + break; + case "callback": + tag = parseCallbackTag(start, tagName, margin, indentText); + break; + case "see": + tag = parseSeeTag(start, tagName, margin, indentText); + break; + default: + tag = parseUnknownTag(start, tagName, margin, indentText); + break; } + return tag; + } - function parseTrailingTagComments(pos: number, end: number, margin: number, indentText: string) { - // some tags, like typedef and callback, have already parsed their comments earlier - if (!indentText) { - margin += end - pos; - } - return parseTagComments(margin, indentText.slice(margin)); - } - - function parseTagComments(indent: number, initialMargin?: string): string | NodeArray | undefined { - const commentsPos = getNodePos(); - let comments: string[] = []; - const parts: JSDocComment[] = []; - let linkEnd; - let state = JSDocState.BeginningOfLine; - let previousWhitespace = true; - let margin: number | undefined; - function pushComment(text: string) { - if (!margin) { - margin = indent; - } - comments.push(text); - indent += text.length; - } - if (initialMargin !== undefined) { - // jump straight to saving comments if there is some initial indentation - if (initialMargin !== "") { - pushComment(initialMargin); - } - state = JSDocState.SawAsterisk; - } - let tok = token() as JSDocSyntaxKind; - loop: while (true) { - switch (tok) { - case SyntaxKind.NewLineTrivia: - state = JSDocState.BeginningOfLine; - // don't use pushComment here because we want to keep the margin unchanged + function parseTrailingTagComments(pos: number, end: number, margin: number, indentText: string) { + // some tags, like typedef and callback, have already parsed their comments earlier + if (!indentText) { + margin += end - pos; + } + return parseTagComments(margin, indentText.slice(margin)); + } + + function parseTagComments(indent: number, initialMargin?: string): string | NodeArray | undefined { + const commentsPos = getNodePos(); + let comments: string[] = []; + const parts: JSDocComment[] = []; + let linkEnd; + let state = JSDocState.BeginningOfLine; + let previousWhitespace = true; + let margin: number | undefined; + function pushComment(text: string) { + if (!margin) { + margin = indent; + } + comments.push(text); + indent += text.length; + } + if (initialMargin !== undefined) { + // jump straight to saving comments if there is some initial indentation + if (initialMargin !== "") { + pushComment(initialMargin); + } + state = JSDocState.SawAsterisk; + } + let tok = token() as JSDocSyntaxKind; + loop: while (true) { + switch (tok) { + case SyntaxKind.NewLineTrivia: + 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 + || state === JSDocState.SavingComments && (!previousWhitespace || lookAhead(isNextJSDocTokenWhitespace))) { + // @ doesn't start a new tag inside ``, and inside a comment, only after whitespace or not before whitespace comments.push(scanner.getTokenText()); - indent = 0; - break; - case SyntaxKind.AtToken: - if (state === JSDocState.SavingBackticks - || state === JSDocState.SavingComments && (!previousWhitespace || lookAhead(isNextJSDocTokenWhitespace))) { - // @ doesn't start a new tag inside ``, and inside a comment, only after whitespace or not before whitespace - 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 || state === JSDocState.SavingBackticks) { - pushComment(scanner.getTokenText()); - } - else { - const whitespace = scanner.getTokenText(); - // if the whitespace crosses the margin, take only the whitespace that passes the margin - if (margin !== undefined && indent + whitespace.length > margin) { - comments.push(whitespace.slice(margin - indent)); - } - indent += whitespace.length; - } - break; - case SyntaxKind.OpenBraceToken: - state = JSDocState.SavingComments; - const commentEnd = scanner.getStartPos(); - const linkStart = scanner.getTextPos() - 1; - const link = parseJSDocLink(linkStart); - if (link) { - parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? commentsPos, commentEnd)); - parts.push(link); - comments = []; - linkEnd = scanner.getTextPos(); - } - else { - pushComment(scanner.getTokenText()); - } break; - case SyntaxKind.BacktickToken: - if (state === JSDocState.SavingBackticks) { - state = JSDocState.SavingComments; - } - else { - state = JSDocState.SavingBackticks; - } + } + scanner.setTextPos(scanner.getTextPos() - 1); + // falls through + case SyntaxKind.EndOfFileToken: + // Done + break loop; + case SyntaxKind.WhitespaceTrivia: + if (state === JSDocState.SavingComments || state === JSDocState.SavingBackticks) { pushComment(scanner.getTokenText()); - break; - case SyntaxKind.AsteriskToken: - if (state === JSDocState.BeginningOfLine) { - // leading asterisks start recording on the *next* (non-whitespace) token - state = JSDocState.SawAsterisk; - indent += 1; - break; - } - // record the * as a comment - // falls through - default: - if (state !== JSDocState.SavingBackticks) { - state = JSDocState.SavingComments; // leading identifiers start recording as well + } + else { + const whitespace = scanner.getTokenText(); + // if the whitespace crosses the margin, take only the whitespace that passes the margin + if (margin !== undefined && indent + whitespace.length > margin) { + comments.push(whitespace.slice(margin - indent)); } + indent += whitespace.length; + } + break; + case SyntaxKind.OpenBraceToken: + state = JSDocState.SavingComments; + const commentEnd = scanner.getStartPos(); + const linkStart = scanner.getTextPos() - 1; + const link = parseJSDocLink(linkStart); + if (link) { + parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? commentsPos, commentEnd)); + parts.push(link); + comments = []; + linkEnd = scanner.getTextPos(); + } + else { 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 + state = JSDocState.SawAsterisk; + indent += 1; break; - } - previousWhitespace = token() === SyntaxKind.WhitespaceTrivia; - tok = nextTokenJSDoc(); + } + // record the * as a comment + // falls through + default: + if (state !== JSDocState.SavingBackticks) { + state = JSDocState.SavingComments; // leading identifiers start recording as well + } + pushComment(scanner.getTokenText()); + break; } + previousWhitespace = token() === SyntaxKind.WhitespaceTrivia; + tok = nextTokenJSDoc(); + } - removeLeadingNewlines(comments); - removeTrailingWhitespace(comments); - if (parts.length) { - if (comments.length) { - parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? commentsPos)); - } - return createNodeArray(parts, commentsPos, scanner.getTextPos()); - } - else if (comments.length) { - return comments.join(""); + removeLeadingNewlines(comments); + removeTrailingWhitespace(comments); + if (parts.length) { + if (comments.length) { + parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? commentsPos)); } + return createNodeArray(parts, commentsPos, scanner.getTextPos()); } - - function isNextJSDocTokenWhitespace() { - const next = nextTokenJSDoc(); - return next === SyntaxKind.WhitespaceTrivia || next === SyntaxKind.NewLineTrivia; + else if (comments.length) { + return comments.join(""); } + } - function parseJSDocLink(start: number) { - const linkType = tryParse(parseJSDocLinkPrefix); - if (!linkType) { - return undefined; - } - nextTokenJSDoc(); // start at token after link, then skip any whitespace - skipWhitespace(); - // parseEntityName logs an error for non-identifier, so create a MissingNode ourselves to avoid the error - const p2 = getNodePos(); - let name: EntityName | JSDocMemberName | undefined = tokenIsIdentifierOrKeyword(token()) - ? parseEntityName(/*allowReservedWords*/ true) - : undefined; - if (name) { - while (token() === SyntaxKind.PrivateIdentifier) { - reScanHashToken(); // rescan #id as # id - nextTokenJSDoc(); // then skip the # - name = finishNode(factory.createJSDocMemberName(name, parseIdentifier()), p2); - } - } - const text = []; - while (token() !== SyntaxKind.CloseBraceToken && token() !== SyntaxKind.NewLineTrivia && token() !== SyntaxKind.EndOfFileToken) { - text.push(scanner.getTokenText()); - nextTokenJSDoc(); - } - const create = linkType === "link" ? factory.createJSDocLink - : linkType === "linkcode" ? factory.createJSDocLinkCode - : factory.createJSDocLinkPlain; - return finishNode(create(name, text.join("")), start, scanner.getTextPos()); - } + function isNextJSDocTokenWhitespace() { + const next = nextTokenJSDoc(); + return next === SyntaxKind.WhitespaceTrivia || next === SyntaxKind.NewLineTrivia; + } - function parseJSDocLinkPrefix() { - skipWhitespaceOrAsterisk(); - if (token() === SyntaxKind.OpenBraceToken - && nextTokenJSDoc() === SyntaxKind.AtToken - && tokenIsIdentifierOrKeyword(nextTokenJSDoc())) { - const kind = scanner.getTokenValue(); - if (isJSDocLinkTag(kind)) return kind; + function parseJSDocLink(start: number) { + const linkType = tryParse(parseJSDocLinkPrefix); + if (!linkType) { + return undefined; + } + nextTokenJSDoc(); // start at token after link, then skip any whitespace + skipWhitespace(); + // parseEntityName logs an error for non-identifier, so create a MissingNode ourselves to avoid the error + const p2 = getNodePos(); + let name: EntityName | JSDocMemberName | undefined = tokenIsIdentifierOrKeyword(token()) + ? parseEntityName(/*allowReservedWords*/ true) + : undefined; + if (name) { + while (token() === SyntaxKind.PrivateIdentifier) { + reScanHashToken(); // rescan #id as # id + nextTokenJSDoc(); // then skip the # + name = finishNode(factory.createJSDocMemberName(name, parseIdentifier()), p2); } } - - function isJSDocLinkTag(kind: string) { - return kind === "link" || kind === "linkcode" || kind === "linkplain"; + const text = []; + while (token() !== SyntaxKind.CloseBraceToken && token() !== SyntaxKind.NewLineTrivia && token() !== SyntaxKind.EndOfFileToken) { + text.push(scanner.getTokenText()); + nextTokenJSDoc(); } + const create = linkType === "link" ? factory.createJSDocLink + : linkType === "linkcode" ? factory.createJSDocLinkCode + : factory.createJSDocLinkPlain; + return finishNode(create(name, text.join("")), start, scanner.getTextPos()); + } - function parseUnknownTag(start: number, tagName: Identifier, indent: number, indentText: string) { - return finishNode(factory.createJSDocUnknownTag(tagName, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + function parseJSDocLinkPrefix() { + skipWhitespaceOrAsterisk(); + if (token() === SyntaxKind.OpenBraceToken + && nextTokenJSDoc() === SyntaxKind.AtToken + && tokenIsIdentifierOrKeyword(nextTokenJSDoc())) { + const kind = scanner.getTokenValue(); + if (isJSDocLinkTag(kind)) return kind; } + } - function addTag(tag: JSDocTag | undefined): void { - if (!tag) { - return; - } - if (!tags) { - tags = [tag]; - tagsPos = tag.pos; - } - else { - tags.push(tag); - } - tagsEnd = tag.end; - } + function isJSDocLinkTag(kind: string) { + return kind === "link" || kind === "linkcode" || kind === "linkplain"; + } - function tryParseTypeExpression(): JSDocTypeExpression | undefined { - skipWhitespaceOrAsterisk(); - return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; + function parseUnknownTag(start: number, tagName: Identifier, indent: number, indentText: string) { + return finishNode(factory.createJSDocUnknownTag(tagName, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + } + + function addTag(tag: JSDocTag | undefined): void { + if (!tag) { + return; } + if (!tags) { + tags = [tag]; + tagsPos = tag.pos; + } + else { + tags.push(tag); + } + tagsEnd = tag.end; + } - function parseBracketNameInPropertyAndParamTag(): { name: EntityName, isBracketed: boolean } { - // Looking for something like '[foo]', 'foo', '[foo.bar]' or 'foo.bar' - const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); - if (isBracketed) { - skipWhitespace(); - } - // a markdown-quoted name: `arg` is not legal jsdoc, but occurs in the wild - const isBackquoted = parseOptionalJsdoc(SyntaxKind.BacktickToken); - const name = parseJSDocEntityName(); - if (isBackquoted) { - parseExpectedTokenJSDoc(SyntaxKind.BacktickToken); - } - if (isBracketed) { - skipWhitespace(); - // May have an optional default, e.g. '[foo = 42]' - if (parseOptionalToken(SyntaxKind.EqualsToken)) { - parseExpression(); - } + function tryParseTypeExpression(): JSDocTypeExpression | undefined { + skipWhitespaceOrAsterisk(); + return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; + } - parseExpected(SyntaxKind.CloseBracketToken); + function parseBracketNameInPropertyAndParamTag(): { name: EntityName, isBracketed: boolean } { + // Looking for something like '[foo]', 'foo', '[foo.bar]' or 'foo.bar' + const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); + if (isBracketed) { + skipWhitespace(); + } + // a markdown-quoted name: `arg` is not legal jsdoc, but occurs in the wild + const isBackquoted = parseOptionalJsdoc(SyntaxKind.BacktickToken); + const name = parseJSDocEntityName(); + if (isBackquoted) { + parseExpectedTokenJSDoc(SyntaxKind.BacktickToken); + } + if (isBracketed) { + skipWhitespace(); + // May have an optional default, e.g. '[foo = 42]' + if (parseOptionalToken(SyntaxKind.EqualsToken)) { + parseExpression(); } - return { name, isBracketed }; + parseExpected(SyntaxKind.CloseBracketToken); } - function isObjectOrObjectArrayTypeReference(node: TypeNode): boolean { - switch (node.kind) { - case SyntaxKind.ObjectKeyword: - return true; - case SyntaxKind.ArrayType: - return isObjectOrObjectArrayTypeReference((node as ArrayTypeNode).elementType); - default: - return isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && node.typeName.escapedText === "Object" && !node.typeArguments; - } + return { name, isBracketed }; + } + + function isObjectOrObjectArrayTypeReference(node: TypeNode): boolean { + switch (node.kind) { + case SyntaxKind.ObjectKeyword: + return true; + case SyntaxKind.ArrayType: + return isObjectOrObjectArrayTypeReference((node as ArrayTypeNode).elementType); + default: + return isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && node.typeName.escapedText === "Object" && !node.typeArguments; } + } - function parseParameterOrPropertyTag(start: number, tagName: Identifier, target: PropertyLikeParse, indent: number): JSDocParameterTag | JSDocPropertyTag { - let typeExpression = tryParseTypeExpression(); - let isNameFirst = !typeExpression; - skipWhitespaceOrAsterisk(); + function parseParameterOrPropertyTag(start: number, tagName: Identifier, target: PropertyLikeParse, indent: number): JSDocParameterTag | JSDocPropertyTag { + let typeExpression = tryParseTypeExpression(); + let isNameFirst = !typeExpression; + skipWhitespaceOrAsterisk(); - const { name, isBracketed } = parseBracketNameInPropertyAndParamTag(); - const indentText = skipWhitespaceOrAsterisk(); + const { name, isBracketed } = parseBracketNameInPropertyAndParamTag(); + const indentText = skipWhitespaceOrAsterisk(); - if (isNameFirst && !lookAhead(parseJSDocLinkPrefix)) { - typeExpression = tryParseTypeExpression(); - } + if (isNameFirst && !lookAhead(parseJSDocLinkPrefix)) { + typeExpression = tryParseTypeExpression(); + } - const comment = parseTrailingTagComments(start, getNodePos(), indent, indentText); + const comment = parseTrailingTagComments(start, getNodePos(), indent, indentText); - const nestedTypeLiteral = target !== PropertyLikeParse.CallbackParameter && parseNestedTypeLiteral(typeExpression, name, target, indent); - if (nestedTypeLiteral) { - typeExpression = nestedTypeLiteral; - isNameFirst = true; - } - const result = target === PropertyLikeParse.Property - ? factory.createJSDocPropertyTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment) - : factory.createJSDocParameterTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment); - return finishNode(result, start); - } - - function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression | undefined, name: EntityName, target: PropertyLikeParse, indent: number) { - if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) { - const pos = getNodePos(); - let child: JSDocPropertyLikeTag | JSDocTypeTag | false; - let children: JSDocPropertyLikeTag[] | undefined; - while (child = tryParse(() => parseChildParameterOrPropertyTag(target, indent, name))) { - if (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) { - children = append(children, child); - } - } - if (children) { - const literal = finishNode(factory.createJSDocTypeLiteral(children, typeExpression.type.kind === SyntaxKind.ArrayType), pos); - return finishNode(factory.createJSDocTypeExpression(literal), pos); - } - } + const nestedTypeLiteral = target !== PropertyLikeParse.CallbackParameter && parseNestedTypeLiteral(typeExpression, name, target, indent); + if (nestedTypeLiteral) { + typeExpression = nestedTypeLiteral; + isNameFirst = true; } + const result = target === PropertyLikeParse.Property + ? factory.createJSDocPropertyTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment) + : factory.createJSDocParameterTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment); + return finishNode(result, start); + } - function parseReturnTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocReturnTag { - if (some(tags, isJSDocReturnTag)) { - parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); + function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression | undefined, name: EntityName, target: PropertyLikeParse, indent: number) { + if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) { + const pos = getNodePos(); + let child: JSDocPropertyLikeTag | JSDocTypeTag | false; + let children: JSDocPropertyLikeTag[] | undefined; + while (child = tryParse(() => parseChildParameterOrPropertyTag(target, indent, name))) { + if (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) { + children = append(children, child); + } } + if (children) { + const literal = finishNode(factory.createJSDocTypeLiteral(children, typeExpression.type.kind === SyntaxKind.ArrayType), pos); + return finishNode(factory.createJSDocTypeExpression(literal), pos); + } + } + } - const typeExpression = tryParseTypeExpression(); - return finishNode(factory.createJSDocReturnTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + function parseReturnTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocReturnTag { + if (some(tags, isJSDocReturnTag)) { + parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); } - function parseTypeTag(start: number, tagName: Identifier, indent?: number, indentText?: string): JSDocTypeTag { - if (some(tags, isJSDocTypeTag)) { - parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); - } + const typeExpression = tryParseTypeExpression(); + return finishNode(factory.createJSDocReturnTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + } - const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); - const comments = indent !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), indent, indentText) : undefined; - return finishNode(factory.createJSDocTypeTag(tagName, typeExpression, comments), start); + function parseTypeTag(start: number, tagName: Identifier, indent?: number, indentText?: string): JSDocTypeTag { + if (some(tags, isJSDocTypeTag)) { + parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); } - function parseSeeTag(start: number, tagName: Identifier, indent?: number, indentText?: string): JSDocSeeTag { - const isMarkdownOrJSDocLink = token() === SyntaxKind.OpenBracketToken - || lookAhead(() => nextTokenJSDoc() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextTokenJSDoc()) && isJSDocLinkTag(scanner.getTokenValue())); - const nameExpression = isMarkdownOrJSDocLink ? undefined : parseJSDocNameReference(); - const comments = indent !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), indent, indentText) : undefined; - return finishNode(factory.createJSDocSeeTag(tagName, nameExpression, comments), start); + const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + const comments = indent !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), indent, indentText) : undefined; + return finishNode(factory.createJSDocTypeTag(tagName, typeExpression, comments), start); + } + + function parseSeeTag(start: number, tagName: Identifier, indent?: number, indentText?: string): JSDocSeeTag { + const isMarkdownOrJSDocLink = token() === SyntaxKind.OpenBracketToken + || lookAhead(() => nextTokenJSDoc() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextTokenJSDoc()) && isJSDocLinkTag(scanner.getTokenValue())); + const nameExpression = isMarkdownOrJSDocLink ? undefined : parseJSDocNameReference(); + const comments = indent !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), indent, indentText) : undefined; + return finishNode(factory.createJSDocSeeTag(tagName, nameExpression, comments), start); + } + + function parseAuthorTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocAuthorTag { + const commentStart = getNodePos(); + const textOnly = parseAuthorNameAndEmail(); + let commentEnd = scanner.getStartPos(); + const comments = parseTrailingTagComments(start, commentEnd, indent, indentText); + if (!comments) { + commentEnd = scanner.getStartPos(); } + const allParts = typeof comments !== "string" + ? createNodeArray(concatenate([finishNode(textOnly, commentStart, commentEnd)], comments) as JSDocComment[], commentStart) // cast away readonly + : textOnly.text + comments; + return finishNode(factory.createJSDocAuthorTag(tagName, allParts), start); + } - function parseAuthorTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocAuthorTag { - const commentStart = getNodePos(); - const textOnly = parseAuthorNameAndEmail(); - let commentEnd = scanner.getStartPos(); - const comments = parseTrailingTagComments(start, commentEnd, indent, indentText); - if (!comments) { - commentEnd = scanner.getStartPos(); + function parseAuthorNameAndEmail(): JSDocText { + const comments: string[] = []; + let inEmail = false; + let token = scanner.getToken(); + while (token !== SyntaxKind.EndOfFileToken && token !== SyntaxKind.NewLineTrivia) { + if (token === SyntaxKind.LessThanToken) { + inEmail = true; } - const allParts = typeof comments !== "string" - ? createNodeArray(concatenate([finishNode(textOnly, commentStart, commentEnd)], comments) as JSDocComment[], commentStart) // cast away readonly - : textOnly.text + comments; - return finishNode(factory.createJSDocAuthorTag(tagName, allParts), start); - } - - function parseAuthorNameAndEmail(): JSDocText { - const comments: string[] = []; - let inEmail = false; - let token = scanner.getToken(); - while (token !== SyntaxKind.EndOfFileToken && token !== SyntaxKind.NewLineTrivia) { - if (token === SyntaxKind.LessThanToken) { - inEmail = true; - } - else if (token === SyntaxKind.AtToken && !inEmail) { - break; - } - else if (token === SyntaxKind.GreaterThanToken && inEmail) { - comments.push(scanner.getTokenText()); - scanner.setTextPos(scanner.getTokenPos() + 1); - break; - } + else if (token === SyntaxKind.AtToken && !inEmail) { + break; + } + else if (token === SyntaxKind.GreaterThanToken && inEmail) { comments.push(scanner.getTokenText()); - token = nextTokenJSDoc(); + scanner.setTextPos(scanner.getTokenPos() + 1); + break; } - - return factory.createJSDocText(comments.join("")); + comments.push(scanner.getTokenText()); + token = nextTokenJSDoc(); } - function parseImplementsTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocImplementsTag { - const className = parseExpressionWithTypeArgumentsForAugments(); - return finishNode(factory.createJSDocImplementsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); - } + return factory.createJSDocText(comments.join("")); + } - function parseAugmentsTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocAugmentsTag { - const className = parseExpressionWithTypeArgumentsForAugments(); - return finishNode(factory.createJSDocAugmentsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); - } + function parseImplementsTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocImplementsTag { + const className = parseExpressionWithTypeArgumentsForAugments(); + return finishNode(factory.createJSDocImplementsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + } - function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression } { - const usedBrace = parseOptional(SyntaxKind.OpenBraceToken); - const pos = getNodePos(); - const expression = parsePropertyAccessEntityNameExpression(); - const typeArguments = tryParseTypeArguments(); - const node = factory.createExpressionWithTypeArguments(expression, typeArguments) as ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression }; - const res = finishNode(node, pos); - if (usedBrace) { - parseExpected(SyntaxKind.CloseBraceToken); - } - return res; - } + function parseAugmentsTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocAugmentsTag { + const className = parseExpressionWithTypeArgumentsForAugments(); + return finishNode(factory.createJSDocAugmentsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + } - function parsePropertyAccessEntityNameExpression() { - const pos = getNodePos(); - let node: Identifier | PropertyAccessEntityNameExpression = parseJSDocIdentifierName(); - while (parseOptional(SyntaxKind.DotToken)) { - const name = parseJSDocIdentifierName(); - node = finishNode(factory.createPropertyAccessExpression(node, name), pos) as PropertyAccessEntityNameExpression; - } - return node; + function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression } { + const usedBrace = parseOptional(SyntaxKind.OpenBraceToken); + const pos = getNodePos(); + const expression = parsePropertyAccessEntityNameExpression(); + const typeArguments = tryParseTypeArguments(); + const node = factory.createExpressionWithTypeArguments(expression, typeArguments) as ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression }; + const res = finishNode(node, pos); + if (usedBrace) { + parseExpected(SyntaxKind.CloseBraceToken); } + return res; + } - function parseSimpleTag(start: number, createTag: (tagName: Identifier | undefined, comment?: string | NodeArray) => JSDocTag, tagName: Identifier, margin: number, indentText: string): JSDocTag { - return finishNode(createTag(tagName, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + function parsePropertyAccessEntityNameExpression() { + const pos = getNodePos(); + let node: Identifier | PropertyAccessEntityNameExpression = parseJSDocIdentifierName(); + while (parseOptional(SyntaxKind.DotToken)) { + const name = parseJSDocIdentifierName(); + node = finishNode(factory.createPropertyAccessExpression(node, name), pos) as PropertyAccessEntityNameExpression; } + return node; + } - function parseThisTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocThisTag { - const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); - skipWhitespace(); - return finishNode(factory.createJSDocThisTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); - } + function parseSimpleTag(start: number, createTag: (tagName: Identifier | undefined, comment?: string | NodeArray) => JSDocTag, tagName: Identifier, margin: number, indentText: string): JSDocTag { + return finishNode(createTag(tagName, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + } - function parseEnumTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocEnumTag { - const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); - skipWhitespace(); - return finishNode(factory.createJSDocEnumTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); - } + function parseThisTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocThisTag { + const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + skipWhitespace(); + return finishNode(factory.createJSDocThisTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + } - function parseTypedefTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocTypedefTag { - let typeExpression: JSDocTypeExpression | JSDocTypeLiteral | undefined = tryParseTypeExpression(); - skipWhitespaceOrAsterisk(); + function parseEnumTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocEnumTag { + const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); + skipWhitespace(); + return finishNode(factory.createJSDocEnumTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); + } - const fullName = parseJSDocTypeNameWithNamespace(); - skipWhitespace(); - let comment = parseTagComments(indent); - - let end: number | undefined; - if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { - let child: JSDocTypeTag | JSDocPropertyTag | false; - let childTypeTag: JSDocTypeTag | undefined; - let jsDocPropertyTags: JSDocPropertyTag[] | undefined; - let hasChildren = false; - while (child = tryParse(() => parseChildPropertyTag(indent))) { - hasChildren = true; - if (child.kind === SyntaxKind.JSDocTypeTag) { - if (childTypeTag) { - const lastError = parseErrorAtCurrentToken(Diagnostics.A_JSDoc_typedef_comment_may_not_contain_multiple_type_tags); - if (lastError) { - addRelatedInfo(lastError, createDetachedDiagnostic(fileName, 0, 0, Diagnostics.The_tag_was_first_specified_here)); - } - break; - } - else { - childTypeTag = child; + function parseTypedefTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocTypedefTag { + let typeExpression: JSDocTypeExpression | JSDocTypeLiteral | undefined = tryParseTypeExpression(); + skipWhitespaceOrAsterisk(); + + const fullName = parseJSDocTypeNameWithNamespace(); + skipWhitespace(); + let comment = parseTagComments(indent); + + let end: number | undefined; + if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { + let child: JSDocTypeTag | JSDocPropertyTag | false; + let childTypeTag: JSDocTypeTag | undefined; + let jsDocPropertyTags: JSDocPropertyTag[] | undefined; + let hasChildren = false; + while (child = tryParse(() => parseChildPropertyTag(indent))) { + hasChildren = true; + if (child.kind === SyntaxKind.JSDocTypeTag) { + if (childTypeTag) { + const lastError = parseErrorAtCurrentToken(Diagnostics.A_JSDoc_typedef_comment_may_not_contain_multiple_type_tags); + if (lastError) { + addRelatedInfo(lastError, createDetachedDiagnostic(fileName, 0, 0, Diagnostics.The_tag_was_first_specified_here)); } + break; } else { - jsDocPropertyTags = append(jsDocPropertyTags, child); + childTypeTag = child; } } - if (hasChildren) { - const isArrayType = typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType; - const jsdocTypeLiteral = factory.createJSDocTypeLiteral(jsDocPropertyTags, isArrayType); - typeExpression = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? - childTypeTag.typeExpression : - finishNode(jsdocTypeLiteral, start); - end = typeExpression.end; + else { + jsDocPropertyTags = append(jsDocPropertyTags, child); } } - - // Only include the characters between the name end and the next token if a comment was actually parsed out - otherwise it's just whitespace - end = end || comment !== undefined ? - getNodePos() : - (fullName ?? typeExpression ?? tagName).end; - - if (!comment) { - comment = parseTrailingTagComments(start, end, indent, indentText); + if (hasChildren) { + const isArrayType = typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType; + const jsdocTypeLiteral = factory.createJSDocTypeLiteral(jsDocPropertyTags, isArrayType); + typeExpression = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? + childTypeTag.typeExpression : + finishNode(jsdocTypeLiteral, start); + end = typeExpression.end; } - - const typedefTag = factory.createJSDocTypedefTag(tagName, typeExpression, fullName, comment); - return finishNode(typedefTag, start, end); } - function parseJSDocTypeNameWithNamespace(nested?: boolean) { - const pos = scanner.getTokenPos(); - if (!tokenIsIdentifierOrKeyword(token())) { - return undefined; - } - const typeNameOrNamespaceName = parseJSDocIdentifierName(); - if (parseOptional(SyntaxKind.DotToken)) { - const body = parseJSDocTypeNameWithNamespace(/*nested*/ true); - const jsDocNamespaceNode = factory.createModuleDeclaration( - /*modifiers*/ undefined, - typeNameOrNamespaceName, - body, - nested ? NodeFlags.NestedNamespace : undefined - ) as JSDocNamespaceDeclaration; - return finishNode(jsDocNamespaceNode, pos); - } + // Only include the characters between the name end and the next token if a comment was actually parsed out - otherwise it's just whitespace + end = end || comment !== undefined ? + getNodePos() : + (fullName ?? typeExpression ?? tagName).end; - if (nested) { - typeNameOrNamespaceName.isInJSDocNamespace = true; - } - return typeNameOrNamespaceName; + if (!comment) { + comment = parseTrailingTagComments(start, end, indent, indentText); } + const typedefTag = factory.createJSDocTypedefTag(tagName, typeExpression, fullName, comment); + return finishNode(typedefTag, start, end); + } - function parseCallbackTagParameters(indent: number) { - const pos = getNodePos(); - let child: JSDocParameterTag | false; - let parameters; - while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag)) { - parameters = append(parameters, child); - } - return createNodeArray(parameters || [], pos); + function parseJSDocTypeNameWithNamespace(nested?: boolean) { + const pos = scanner.getTokenPos(); + if (!tokenIsIdentifierOrKeyword(token())) { + return undefined; + } + const typeNameOrNamespaceName = parseJSDocIdentifierName(); + if (parseOptional(SyntaxKind.DotToken)) { + const body = parseJSDocTypeNameWithNamespace(/*nested*/ true); + const jsDocNamespaceNode = factory.createModuleDeclaration( + /*modifiers*/ undefined, + typeNameOrNamespaceName, + body, + nested ? NodeFlags.NestedNamespace : undefined + ) as JSDocNamespaceDeclaration; + return finishNode(jsDocNamespaceNode, pos); } - function parseCallbackTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocCallbackTag { - const fullName = parseJSDocTypeNameWithNamespace(); - skipWhitespace(); - let comment = parseTagComments(indent); - const parameters = parseCallbackTagParameters(indent); - const returnTag = tryParse(() => { - if (parseOptionalJsdoc(SyntaxKind.AtToken)) { - const tag = parseTag(indent); - if (tag && tag.kind === SyntaxKind.JSDocReturnTag) { - return tag as JSDocReturnTag; - } + if (nested) { + typeNameOrNamespaceName.isInJSDocNamespace = true; + } + return typeNameOrNamespaceName; + } + + + function parseCallbackTagParameters(indent: number) { + const pos = getNodePos(); + let child: JSDocParameterTag | false; + let parameters; + while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag)) { + parameters = append(parameters, child); + } + return createNodeArray(parameters || [], pos); + } + + function parseCallbackTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocCallbackTag { + const fullName = parseJSDocTypeNameWithNamespace(); + skipWhitespace(); + let comment = parseTagComments(indent); + const parameters = parseCallbackTagParameters(indent); + const returnTag = tryParse(() => { + if (parseOptionalJsdoc(SyntaxKind.AtToken)) { + const tag = parseTag(indent); + if (tag && tag.kind === SyntaxKind.JSDocReturnTag) { + return tag as JSDocReturnTag; } - }); - const typeExpression = finishNode(factory.createJSDocSignature(/*typeParameters*/ undefined, parameters, returnTag), start); - if (!comment) { - comment = parseTrailingTagComments(start, getNodePos(), indent, indentText); } - const end = comment !== undefined ? getNodePos() : typeExpression.end; - return finishNode(factory.createJSDocCallbackTag(tagName, typeExpression, fullName, comment), start, end); + }); + const typeExpression = finishNode(factory.createJSDocSignature(/*typeParameters*/ undefined, parameters, returnTag), start); + if (!comment) { + comment = parseTrailingTagComments(start, getNodePos(), indent, indentText); } + const end = comment !== undefined ? getNodePos() : typeExpression.end; + return finishNode(factory.createJSDocCallbackTag(tagName, typeExpression, fullName, comment), start, end); + } - function escapedTextsEqual(a: EntityName, b: EntityName): boolean { - while (!ts.isIdentifier(a) || !ts.isIdentifier(b)) { - if (!ts.isIdentifier(a) && !ts.isIdentifier(b) && a.right.escapedText === b.right.escapedText) { - a = a.left; - b = b.left; - } - else { - return false; - } + function escapedTextsEqual(a: EntityName, b: EntityName): boolean { + while (!ts.isIdentifier(a) || !ts.isIdentifier(b)) { + if (!ts.isIdentifier(a) && !ts.isIdentifier(b) && a.right.escapedText === b.right.escapedText) { + a = a.left; + b = b.left; } - return a.escapedText === b.escapedText; - } - - function parseChildPropertyTag(indent: number) { - return parseChildParameterOrPropertyTag(PropertyLikeParse.Property, indent) as JSDocTypeTag | JSDocPropertyTag | false; - } - - function parseChildParameterOrPropertyTag(target: PropertyLikeParse, indent: number, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { - let canParseTag = true; - let seenAsterisk = false; - while (true) { - switch (nextTokenJSDoc()) { - case SyntaxKind.AtToken: - if (canParseTag) { - const child = tryParseChildTag(target, indent); - if (child && (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) && - target !== PropertyLikeParse.CallbackParameter && - name && (ts.isIdentifier(child.name) || !escapedTextsEqual(name, child.name.left))) { - return false; - } - return child; - } - seenAsterisk = false; - break; - case SyntaxKind.NewLineTrivia: - canParseTag = true; - seenAsterisk = false; - break; - case SyntaxKind.AsteriskToken: - if (seenAsterisk) { - canParseTag = false; - } - seenAsterisk = true; - break; - case SyntaxKind.Identifier: - canParseTag = false; - break; - case SyntaxKind.EndOfFileToken: - return false; - } + else { + return false; } } + return a.escapedText === b.escapedText; + } - function tryParseChildTag(target: PropertyLikeParse, indent: number): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { - Debug.assert(token() === SyntaxKind.AtToken); - const start = scanner.getStartPos(); - nextTokenJSDoc(); + function parseChildPropertyTag(indent: number) { + return parseChildParameterOrPropertyTag(PropertyLikeParse.Property, indent) as JSDocTypeTag | JSDocPropertyTag | false; + } - const tagName = parseJSDocIdentifierName(); - skipWhitespace(); - let t: PropertyLikeParse; - switch (tagName.escapedText) { - case "type": - return target === PropertyLikeParse.Property && parseTypeTag(start, tagName); - case "prop": - case "property": - t = PropertyLikeParse.Property; + function parseChildParameterOrPropertyTag(target: PropertyLikeParse, indent: number, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { + let canParseTag = true; + let seenAsterisk = false; + while (true) { + switch (nextTokenJSDoc()) { + case SyntaxKind.AtToken: + if (canParseTag) { + const child = tryParseChildTag(target, indent); + if (child && (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) && + target !== PropertyLikeParse.CallbackParameter && + name && (ts.isIdentifier(child.name) || !escapedTextsEqual(name, child.name.left))) { + return false; + } + return child; + } + seenAsterisk = false; break; - case "arg": - case "argument": - case "param": - t = PropertyLikeParse.Parameter | PropertyLikeParse.CallbackParameter; + case SyntaxKind.NewLineTrivia: + canParseTag = true; + seenAsterisk = false; break; - default: + case SyntaxKind.AsteriskToken: + if (seenAsterisk) { + canParseTag = false; + } + seenAsterisk = true; + break; + case SyntaxKind.Identifier: + canParseTag = false; + break; + case SyntaxKind.EndOfFileToken: return false; } - if (!(target & t)) { - return false; - } - return parseParameterOrPropertyTag(start, tagName, target, indent); } + } - function parseTemplateTagTypeParameter() { - const typeParameterPos = getNodePos(); - const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); - if (isBracketed) { - skipWhitespace(); - } - const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces); + function tryParseChildTag(target: PropertyLikeParse, indent: number): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { + Debug.assert(token() === SyntaxKind.AtToken); + const start = scanner.getStartPos(); + nextTokenJSDoc(); - let defaultType: TypeNode | undefined; - if (isBracketed) { - skipWhitespace(); - parseExpected(SyntaxKind.EqualsToken); - defaultType = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); - parseExpected(SyntaxKind.CloseBracketToken); - } + const tagName = parseJSDocIdentifierName(); + skipWhitespace(); + let t: PropertyLikeParse; + switch (tagName.escapedText) { + case "type": + return target === PropertyLikeParse.Property && parseTypeTag(start, tagName); + case "prop": + case "property": + t = PropertyLikeParse.Property; + break; + case "arg": + case "argument": + case "param": + t = PropertyLikeParse.Parameter | PropertyLikeParse.CallbackParameter; + break; + default: + return false; + } + if (!(target & t)) { + return false; + } + return parseParameterOrPropertyTag(start, tagName, target, indent); + } - if (nodeIsMissing(name)) { - return undefined; - } - return finishNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, name, /*constraint*/ undefined, defaultType), typeParameterPos); + function parseTemplateTagTypeParameter() { + const typeParameterPos = getNodePos(); + const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); + if (isBracketed) { + skipWhitespace(); } + const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces); - function parseTemplateTagTypeParameters() { - const pos = getNodePos(); - const typeParameters = []; - do { - skipWhitespace(); - const node = parseTemplateTagTypeParameter(); - if (node !== undefined) { - typeParameters.push(node); - } - skipWhitespaceOrAsterisk(); - } while (parseOptionalJsdoc(SyntaxKind.CommaToken)); - return createNodeArray(typeParameters, pos); + let defaultType: TypeNode | undefined; + if (isBracketed) { + skipWhitespace(); + parseExpected(SyntaxKind.EqualsToken); + defaultType = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); + parseExpected(SyntaxKind.CloseBracketToken); } - function parseTemplateTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocTemplateTag { - // The template tag looks like one of the following: - // @template T,U,V - // @template {Constraint} T - // - // According to the [closure docs](https://github.com/google/closure-compiler/wiki/Generic-Types#multiple-bounded-template-types): - // > Multiple bounded generics cannot be declared on the same line. For the sake of clarity, if multiple templates share the same - // > type bound they must be declared on separate lines. - // - // TODO: Determine whether we should enforce this in the checker. - // TODO: Consider moving the `constraint` to the first type parameter as we could then remove `getEffectiveConstraintOfTypeParameter`. - // TODO: Consider only parsing a single type parameter if there is a constraint. - const constraint = token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; - const typeParameters = parseTemplateTagTypeParameters(); - return finishNode(factory.createJSDocTemplateTag(tagName, constraint, typeParameters, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + if (nodeIsMissing(name)) { + return undefined; } + return finishNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, name, /*constraint*/ undefined, defaultType), typeParameterPos); + } - function parseOptionalJsdoc(t: JSDocSyntaxKind): boolean { - if (token() === t) { - nextTokenJSDoc(); - return true; + function parseTemplateTagTypeParameters() { + const pos = getNodePos(); + const typeParameters = []; + do { + skipWhitespace(); + const node = parseTemplateTagTypeParameter(); + if (node !== undefined) { + typeParameters.push(node); } - return false; + skipWhitespaceOrAsterisk(); + } while (parseOptionalJsdoc(SyntaxKind.CommaToken)); + return createNodeArray(typeParameters, pos); + } + + function parseTemplateTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocTemplateTag { + // The template tag looks like one of the following: + // @template T,U,V + // @template {Constraint} T + // + // According to the [closure docs](https://github.com/google/closure-compiler/wiki/Generic-Types#multiple-bounded-template-types): + // > Multiple bounded generics cannot be declared on the same line. For the sake of clarity, if multiple templates share the same + // > type bound they must be declared on separate lines. + // + // TODO: Determine whether we should enforce this in the checker. + // TODO: Consider moving the `constraint` to the first type parameter as we could then remove `getEffectiveConstraintOfTypeParameter`. + // TODO: Consider only parsing a single type parameter if there is a constraint. + const constraint = token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; + const typeParameters = parseTemplateTagTypeParameters(); + return finishNode(factory.createJSDocTemplateTag(tagName, constraint, typeParameters, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); + } + + function parseOptionalJsdoc(t: JSDocSyntaxKind): boolean { + if (token() === t) { + nextTokenJSDoc(); + return true; } + return false; + } - function parseJSDocEntityName(): EntityName { - let entity: EntityName = parseJSDocIdentifierName(); + function parseJSDocEntityName(): EntityName { + let entity: EntityName = parseJSDocIdentifierName(); + if (parseOptional(SyntaxKind.OpenBracketToken)) { + parseExpected(SyntaxKind.CloseBracketToken); + // Note that y[] is accepted as an entity name, but the postfix brackets are not saved for checking. + // Technically usejsdoc.org requires them for specifying a property of a type equivalent to Array<{ x: ...}> + // but it's not worth it to enforce that restriction. + } + while (parseOptional(SyntaxKind.DotToken)) { + const name = parseJSDocIdentifierName(); if (parseOptional(SyntaxKind.OpenBracketToken)) { parseExpected(SyntaxKind.CloseBracketToken); - // Note that y[] is accepted as an entity name, but the postfix brackets are not saved for checking. - // Technically usejsdoc.org requires them for specifying a property of a type equivalent to Array<{ x: ...}> - // but it's not worth it to enforce that restriction. - } - while (parseOptional(SyntaxKind.DotToken)) { - const name = parseJSDocIdentifierName(); - if (parseOptional(SyntaxKind.OpenBracketToken)) { - parseExpected(SyntaxKind.CloseBracketToken); - } - entity = createQualifiedName(entity, name); } - return entity; + entity = createQualifiedName(entity, name); } + return entity; + } - function parseJSDocIdentifierName(message?: DiagnosticMessage): Identifier { - if (!tokenIsIdentifierOrKeyword(token())) { - return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ !message, message || Diagnostics.Identifier_expected); - } - - identifierCount++; - const pos = scanner.getTokenPos(); - const end = scanner.getTextPos(); - const originalKeywordKind = token(); - const text = internIdentifier(scanner.getTokenValue()); - const result = finishNode(factory.createIdentifier(text, /*typeArguments*/ undefined, originalKeywordKind), pos, end); - nextTokenJSDoc(); - return result; + function parseJSDocIdentifierName(message?: DiagnosticMessage): Identifier { + if (!tokenIsIdentifierOrKeyword(token())) { + return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ !message, message || Diagnostics.Identifier_expected); } + + identifierCount++; + const pos = scanner.getTokenPos(); + const end = scanner.getTextPos(); + const originalKeywordKind = token(); + const text = internIdentifier(scanner.getTokenValue()); + const result = finishNode(factory.createIdentifier(text, /*typeArguments*/ undefined, originalKeywordKind), pos, end); + nextTokenJSDoc(); + return result; } } } +} - namespace IncrementalParser { - export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean): SourceFile { - aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); - - checkChangeRange(sourceFile, newText, textChangeRange, aggressiveChecks); - if (textChangeRangeIsUnchanged(textChangeRange)) { - // if the text didn't change, then we can just return our current source file as-is. - return sourceFile; - } +namespace IncrementalParser { + export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean): SourceFile { + aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); - if (sourceFile.statements.length === 0) { - // If we don't have any statements in the current source file, then there's no real - // way to incrementally parse. So just do a full parse instead. - return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator); - } + checkChangeRange(sourceFile, newText, textChangeRange, aggressiveChecks); + if (textChangeRangeIsUnchanged(textChangeRange)) { + // if the text didn't change, then we can just return our current source file as-is. + return sourceFile; + } - // Make sure we're not trying to incrementally update a source file more than once. Once - // we do an update the original source file is considered unusable from that point onwards. - // - // This is because we do incremental parsing in-place. i.e. we take nodes from the old - // tree and give them new positions and parents. From that point on, trusting the old - // tree at all is not possible as far too much of it may violate invariants. - const incrementalSourceFile = sourceFile as Node as IncrementalNode; - Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed); - incrementalSourceFile.hasBeenIncrementallyParsed = true; - Parser.fixupParentReferences(incrementalSourceFile); - const oldText = sourceFile.text; - const syntaxCursor = createSyntaxCursor(sourceFile); - - // Make the actual change larger so that we know to reparse anything whose lookahead - // might have intersected the change. - const changeRange = extendToAffectedRange(sourceFile, textChangeRange); - checkChangeRange(sourceFile, newText, changeRange, aggressiveChecks); - - // Ensure that extending the affected range only moved the start of the change range - // earlier in the file. - Debug.assert(changeRange.span.start <= textChangeRange.span.start); - Debug.assert(textSpanEnd(changeRange.span) === textSpanEnd(textChangeRange.span)); - Debug.assert(textSpanEnd(textChangeRangeNewSpan(changeRange)) === textSpanEnd(textChangeRangeNewSpan(textChangeRange))); - - // The is the amount the nodes after the edit range need to be adjusted. It can be - // positive (if the edit added characters), negative (if the edit deleted characters) - // or zero (if this was a pure overwrite with nothing added/removed). - const delta = textChangeRangeNewSpan(changeRange).length - changeRange.span.length; - - // If we added or removed characters during the edit, then we need to go and adjust all - // the nodes after the edit. Those nodes may move forward (if we inserted chars) or they - // may move backward (if we deleted chars). - // - // Doing this helps us out in two ways. First, it means that any nodes/tokens we want - // to reuse are already at the appropriate position in the new text. That way when we - // reuse them, we don't have to figure out if they need to be adjusted. Second, it makes - // it very easy to determine if we can reuse a node. If the node's position is at where - // we are in the text, then we can reuse it. Otherwise we can't. If the node's position - // is ahead of us, then we'll need to rescan tokens. If the node's position is behind - // us, then we'll need to skip it or crumble it as appropriate - // - // We will also adjust the positions of nodes that intersect the change range as well. - // By doing this, we ensure that all the positions in the old tree are consistent, not - // just the positions of nodes entirely before/after the change range. By being - // consistent, we can then easily map from positions to nodes in the old tree easily. - // - // Also, mark any syntax elements that intersect the changed span. We know, up front, - // that we cannot reuse these elements. - updateTokenPositionsAndMarkElements(incrementalSourceFile, - changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks); - - // Now that we've set up our internal incremental state just proceed and parse the - // source file in the normal fashion. When possible the parser will retrieve and - // reuse nodes from the old tree. - // - // Note: passing in 'true' for setNodeParents is very important. When incrementally - // parsing, we will be reusing nodes from the old tree, and placing it into new - // parents. If we don't set the parents now, we'll end up with an observably - // inconsistent tree. Setting the parents on the new tree should be very fast. We - // will immediately bail out of walking any subtrees when we can see that their parents - // are already correct. - const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator); - result.commentDirectives = getNewCommentDirectives( - sourceFile.commentDirectives, - result.commentDirectives, - changeRange.span.start, - textSpanEnd(changeRange.span), - delta, - oldText, - newText, - aggressiveChecks - ); - result.impliedNodeFormat = sourceFile.impliedNodeFormat; - return result; + if (sourceFile.statements.length === 0) { + // If we don't have any statements in the current source file, then there's no real + // way to incrementally parse. So just do a full parse instead. + return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator); } - function getNewCommentDirectives( - oldDirectives: CommentDirective[] | undefined, - newDirectives: CommentDirective[] | undefined, - changeStart: number, - changeRangeOldEnd: number, - delta: number, - oldText: string, - newText: string, - aggressiveChecks: boolean - ): CommentDirective[] | undefined { - if (!oldDirectives) return newDirectives; - let commentDirectives: CommentDirective[] | undefined; - let addedNewlyScannedDirectives = false; - for (const directive of oldDirectives) { - const { range, type } = directive; - // Range before the change - if (range.end < changeStart) { - commentDirectives = append(commentDirectives, directive); - } - else if (range.pos > changeRangeOldEnd) { - addNewlyScannedDirectives(); - // Node is entirely past the change range. We need to move both its pos and - // end, forward or backward appropriately. - const updatedDirective: CommentDirective = { - range: { pos: range.pos + delta, end: range.end + delta }, - type - }; - commentDirectives = append(commentDirectives, updatedDirective); - if (aggressiveChecks) { - Debug.assert(oldText.substring(range.pos, range.end) === newText.substring(updatedDirective.range.pos, updatedDirective.range.end)); - } - } - // Ignore ranges that fall in change range - } - addNewlyScannedDirectives(); - return commentDirectives; + // Make sure we're not trying to incrementally update a source file more than once. Once + // we do an update the original source file is considered unusable from that point onwards. + // + // This is because we do incremental parsing in-place. i.e. we take nodes from the old + // tree and give them new positions and parents. From that point on, trusting the old + // tree at all is not possible as far too much of it may violate invariants. + const incrementalSourceFile = sourceFile as Node as IncrementalNode; + Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed); + incrementalSourceFile.hasBeenIncrementallyParsed = true; + Parser.fixupParentReferences(incrementalSourceFile); + const oldText = sourceFile.text; + const syntaxCursor = createSyntaxCursor(sourceFile); + + // Make the actual change larger so that we know to reparse anything whose lookahead + // might have intersected the change. + const changeRange = extendToAffectedRange(sourceFile, textChangeRange); + checkChangeRange(sourceFile, newText, changeRange, aggressiveChecks); + + // Ensure that extending the affected range only moved the start of the change range + // earlier in the file. + Debug.assert(changeRange.span.start <= textChangeRange.span.start); + Debug.assert(textSpanEnd(changeRange.span) === textSpanEnd(textChangeRange.span)); + Debug.assert(textSpanEnd(textChangeRangeNewSpan(changeRange)) === textSpanEnd(textChangeRangeNewSpan(textChangeRange))); + + // The is the amount the nodes after the edit range need to be adjusted. It can be + // positive (if the edit added characters), negative (if the edit deleted characters) + // or zero (if this was a pure overwrite with nothing added/removed). + const delta = textChangeRangeNewSpan(changeRange).length - changeRange.span.length; + + // If we added or removed characters during the edit, then we need to go and adjust all + // the nodes after the edit. Those nodes may move forward (if we inserted chars) or they + // may move backward (if we deleted chars). + // + // Doing this helps us out in two ways. First, it means that any nodes/tokens we want + // to reuse are already at the appropriate position in the new text. That way when we + // reuse them, we don't have to figure out if they need to be adjusted. Second, it makes + // it very easy to determine if we can reuse a node. If the node's position is at where + // we are in the text, then we can reuse it. Otherwise we can't. If the node's position + // is ahead of us, then we'll need to rescan tokens. If the node's position is behind + // us, then we'll need to skip it or crumble it as appropriate + // + // We will also adjust the positions of nodes that intersect the change range as well. + // By doing this, we ensure that all the positions in the old tree are consistent, not + // just the positions of nodes entirely before/after the change range. By being + // consistent, we can then easily map from positions to nodes in the old tree easily. + // + // Also, mark any syntax elements that intersect the changed span. We know, up front, + // that we cannot reuse these elements. + updateTokenPositionsAndMarkElements(incrementalSourceFile, + changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks); + + // Now that we've set up our internal incremental state just proceed and parse the + // source file in the normal fashion. When possible the parser will retrieve and + // reuse nodes from the old tree. + // + // Note: passing in 'true' for setNodeParents is very important. When incrementally + // parsing, we will be reusing nodes from the old tree, and placing it into new + // parents. If we don't set the parents now, we'll end up with an observably + // inconsistent tree. Setting the parents on the new tree should be very fast. We + // will immediately bail out of walking any subtrees when we can see that their parents + // are already correct. + const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator); + result.commentDirectives = getNewCommentDirectives( + sourceFile.commentDirectives, + result.commentDirectives, + changeRange.span.start, + textSpanEnd(changeRange.span), + delta, + oldText, + newText, + aggressiveChecks + ); + result.impliedNodeFormat = sourceFile.impliedNodeFormat; + return result; + } - function addNewlyScannedDirectives() { - if (addedNewlyScannedDirectives) return; - addedNewlyScannedDirectives = true; - if (!commentDirectives) { - commentDirectives = newDirectives; - } - else if (newDirectives) { - commentDirectives.push(...newDirectives); + function getNewCommentDirectives( + oldDirectives: CommentDirective[] | undefined, + newDirectives: CommentDirective[] | undefined, + changeStart: number, + changeRangeOldEnd: number, + delta: number, + oldText: string, + newText: string, + aggressiveChecks: boolean + ): CommentDirective[] | undefined { + if (!oldDirectives) return newDirectives; + let commentDirectives: CommentDirective[] | undefined; + let addedNewlyScannedDirectives = false; + for (const directive of oldDirectives) { + const { range, type } = directive; + // Range before the change + if (range.end < changeStart) { + commentDirectives = append(commentDirectives, directive); + } + else if (range.pos > changeRangeOldEnd) { + addNewlyScannedDirectives(); + // Node is entirely past the change range. We need to move both its pos and + // end, forward or backward appropriately. + const updatedDirective: CommentDirective = { + range: { pos: range.pos + delta, end: range.end + delta }, + type + }; + commentDirectives = append(commentDirectives, updatedDirective); + if (aggressiveChecks) { + Debug.assert(oldText.substring(range.pos, range.end) === newText.substring(updatedDirective.range.pos, updatedDirective.range.end)); } } + // Ignore ranges that fall in change range } + addNewlyScannedDirectives(); + return commentDirectives; - function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { - if (isArray) { - visitArray(element as IncrementalNodeArray); + function addNewlyScannedDirectives() { + if (addedNewlyScannedDirectives) return; + addedNewlyScannedDirectives = true; + if (!commentDirectives) { + commentDirectives = newDirectives; } - else { - visitNode(element as IncrementalNode); + else if (newDirectives) { + commentDirectives.push(...newDirectives); } - return; + } + } - function visitNode(node: IncrementalNode) { - let text = ""; - if (aggressiveChecks && shouldCheckNode(node)) { - text = oldText.substring(node.pos, node.end); - } + function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { + if (isArray) { + visitArray(element as IncrementalNodeArray); + } + else { + visitNode(element as IncrementalNode); + } + return; - // Ditch any existing LS children we may have created. This way we can avoid - // moving them forward. - if (node._children) { - node._children = undefined; - } + function visitNode(node: IncrementalNode) { + let text = ""; + if (aggressiveChecks && shouldCheckNode(node)) { + text = oldText.substring(node.pos, node.end); + } - setTextRangePosEnd(node, node.pos + delta, node.end + delta); + // Ditch any existing LS children we may have created. This way we can avoid + // moving them forward. + if (node._children) { + node._children = undefined; + } - if (aggressiveChecks && shouldCheckNode(node)) { - Debug.assert(text === newText.substring(node.pos, node.end)); - } + setTextRangePosEnd(node, node.pos + delta, node.end + delta); - forEachChild(node, visitNode, visitArray); - if (hasJSDocNodes(node)) { - for (const jsDocComment of node.jsDoc!) { - visitNode(jsDocComment as Node as IncrementalNode); - } - } - checkNodePositions(node, aggressiveChecks); + if (aggressiveChecks && shouldCheckNode(node)) { + Debug.assert(text === newText.substring(node.pos, node.end)); } - function visitArray(array: IncrementalNodeArray) { - array._children = undefined; - setTextRangePosEnd(array, array.pos + delta, array.end + delta); - - for (const node of array) { - visitNode(node); + forEachChild(node, visitNode, visitArray); + if (hasJSDocNodes(node)) { + for (const jsDocComment of node.jsDoc!) { + visitNode(jsDocComment as Node as IncrementalNode); } } + checkNodePositions(node, aggressiveChecks); } - function shouldCheckNode(node: Node) { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.Identifier: - return true; + function visitArray(array: IncrementalNodeArray) { + array._children = undefined; + setTextRangePosEnd(array, array.pos + delta, array.end + delta); + + for (const node of array) { + visitNode(node); } + } + } - return false; + function shouldCheckNode(node: Node) { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.Identifier: + return true; } - function adjustIntersectingElement(element: IncrementalElement, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number) { - Debug.assert(element.end >= changeStart, "Adjusting an element that was entirely before the change range"); - Debug.assert(element.pos <= changeRangeOldEnd, "Adjusting an element that was entirely after the change range"); - Debug.assert(element.pos <= element.end); + return false; + } - // We have an element that intersects the change range in some way. It may have its - // start, or its end (or both) in the changed range. We want to adjust any part - // that intersects such that the final tree is in a consistent state. i.e. all - // children have spans within the span of their parent, and all siblings are ordered - // properly. + function adjustIntersectingElement(element: IncrementalElement, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number) { + Debug.assert(element.end >= changeStart, "Adjusting an element that was entirely before the change range"); + Debug.assert(element.pos <= changeRangeOldEnd, "Adjusting an element that was entirely after the change range"); + Debug.assert(element.pos <= element.end); - // We may need to update both the 'pos' and the 'end' of the element. + // We have an element that intersects the change range in some way. It may have its + // start, or its end (or both) in the changed range. We want to adjust any part + // that intersects such that the final tree is in a consistent state. i.e. all + // children have spans within the span of their parent, and all siblings are ordered + // properly. - // If the 'pos' is before the start of the change, then we don't need to touch it. - // If it isn't, then the 'pos' must be inside the change. How we update it will - // depend if delta is positive or negative. If delta is positive then we have - // something like: - // - // -------------------AAA----------------- - // -------------------BBBCCCCCCC----------------- - // - // In this case, we consider any node that started in the change range to still be - // starting at the same position. - // - // however, if the delta is negative, then we instead have something like this: - // - // -------------------XXXYYYYYYY----------------- - // -------------------ZZZ----------------- - // - // In this case, any element that started in the 'X' range will keep its position. - // However any element that started after that will have their pos adjusted to be - // at the end of the new range. i.e. any node that started in the 'Y' range will - // be adjusted to have their start at the end of the 'Z' range. - // - // The element will keep its position if possible. Or Move backward to the new-end - // if it's in the 'Y' range. - const pos = Math.min(element.pos, changeRangeNewEnd); - - // If the 'end' is after the change range, then we always adjust it by the delta - // amount. However, if the end is in the change range, then how we adjust it - // will depend on if delta is positive or negative. If delta is positive then we - // have something like: - // - // -------------------AAA----------------- - // -------------------BBBCCCCCCC----------------- - // - // In this case, we consider any node that ended inside the change range to keep its - // end position. - // - // however, if the delta is negative, then we instead have something like this: - // - // -------------------XXXYYYYYYY----------------- - // -------------------ZZZ----------------- - // - // In this case, any element that ended in the 'X' range will keep its position. - // However any element that ended after that will have their pos adjusted to be - // at the end of the new range. i.e. any node that ended in the 'Y' range will - // be adjusted to have their end at the end of the 'Z' range. - const end = element.end >= changeRangeOldEnd ? - // Element ends after the change range. Always adjust the end pos. - element.end + delta : - // Element ends in the change range. The element will keep its position if - // possible. Or Move backward to the new-end if it's in the 'Y' range. - Math.min(element.end, changeRangeNewEnd); - - Debug.assert(pos <= end); - if (element.parent) { - Debug.assertGreaterThanOrEqual(pos, element.parent.pos); - Debug.assertLessThanOrEqual(end, element.parent.end); - } - - setTextRangePosEnd(element, pos, end); - } - - function checkNodePositions(node: Node, aggressiveChecks: boolean) { - if (aggressiveChecks) { - let pos = node.pos; - const visitNode = (child: Node) => { - Debug.assert(child.pos >= pos); - pos = child.end; - }; - if (hasJSDocNodes(node)) { - for (const jsDocComment of node.jsDoc!) { - visitNode(jsDocComment); - } + // We may need to update both the 'pos' and the 'end' of the element. + + // If the 'pos' is before the start of the change, then we don't need to touch it. + // If it isn't, then the 'pos' must be inside the change. How we update it will + // depend if delta is positive or negative. If delta is positive then we have + // something like: + // + // -------------------AAA----------------- + // -------------------BBBCCCCCCC----------------- + // + // In this case, we consider any node that started in the change range to still be + // starting at the same position. + // + // however, if the delta is negative, then we instead have something like this: + // + // -------------------XXXYYYYYYY----------------- + // -------------------ZZZ----------------- + // + // In this case, any element that started in the 'X' range will keep its position. + // However any element that started after that will have their pos adjusted to be + // at the end of the new range. i.e. any node that started in the 'Y' range will + // be adjusted to have their start at the end of the 'Z' range. + // + // The element will keep its position if possible. Or Move backward to the new-end + // if it's in the 'Y' range. + const pos = Math.min(element.pos, changeRangeNewEnd); + + // If the 'end' is after the change range, then we always adjust it by the delta + // amount. However, if the end is in the change range, then how we adjust it + // will depend on if delta is positive or negative. If delta is positive then we + // have something like: + // + // -------------------AAA----------------- + // -------------------BBBCCCCCCC----------------- + // + // In this case, we consider any node that ended inside the change range to keep its + // end position. + // + // however, if the delta is negative, then we instead have something like this: + // + // -------------------XXXYYYYYYY----------------- + // -------------------ZZZ----------------- + // + // In this case, any element that ended in the 'X' range will keep its position. + // However any element that ended after that will have their pos adjusted to be + // at the end of the new range. i.e. any node that ended in the 'Y' range will + // be adjusted to have their end at the end of the 'Z' range. + const end = element.end >= changeRangeOldEnd ? + // Element ends after the change range. Always adjust the end pos. + element.end + delta : + // Element ends in the change range. The element will keep its position if + // possible. Or Move backward to the new-end if it's in the 'Y' range. + Math.min(element.end, changeRangeNewEnd); + + Debug.assert(pos <= end); + if (element.parent) { + Debug.assertGreaterThanOrEqual(pos, element.parent.pos); + Debug.assertLessThanOrEqual(end, element.parent.end); + } + + setTextRangePosEnd(element, pos, end); + } + + function checkNodePositions(node: Node, aggressiveChecks: boolean) { + if (aggressiveChecks) { + let pos = node.pos; + const visitNode = (child: Node) => { + Debug.assert(child.pos >= pos); + pos = child.end; + }; + if (hasJSDocNodes(node)) { + for (const jsDocComment of node.jsDoc!) { + visitNode(jsDocComment); } - forEachChild(node, visitNode); - Debug.assert(pos <= node.end); } + forEachChild(node, visitNode); + Debug.assert(pos <= node.end); } + } - function updateTokenPositionsAndMarkElements( - sourceFile: IncrementalNode, - changeStart: number, - changeRangeOldEnd: number, - changeRangeNewEnd: number, - delta: number, - oldText: string, - newText: string, - aggressiveChecks: boolean): void { + function updateTokenPositionsAndMarkElements( + sourceFile: IncrementalNode, + changeStart: number, + changeRangeOldEnd: number, + changeRangeNewEnd: number, + delta: number, + oldText: string, + newText: string, + aggressiveChecks: boolean): void { + + visitNode(sourceFile); + return; - visitNode(sourceFile); - return; + function visitNode(child: IncrementalNode) { + Debug.assert(child.pos <= child.end); + if (child.pos > changeRangeOldEnd) { + // Node is entirely past the change range. We need to move both its pos and + // end, forward or backward appropriately. + moveElementEntirelyPastChangeRange(child, /*isArray*/ false, delta, oldText, newText, aggressiveChecks); + return; + } - function visitNode(child: IncrementalNode) { - Debug.assert(child.pos <= child.end); - if (child.pos > changeRangeOldEnd) { - // Node is entirely past the change range. We need to move both its pos and - // end, forward or backward appropriately. - moveElementEntirelyPastChangeRange(child, /*isArray*/ false, delta, oldText, newText, aggressiveChecks); - return; - } + // Check if the element intersects the change range. If it does, then it is not + // reusable. Also, we'll need to recurse to see what constituent portions we may + // be able to use. + const fullEnd = child.end; + if (fullEnd >= changeStart) { + child.intersectsChange = true; + child._children = undefined; - // Check if the element intersects the change range. If it does, then it is not - // reusable. Also, we'll need to recurse to see what constituent portions we may - // be able to use. - const fullEnd = child.end; - if (fullEnd >= changeStart) { - child.intersectsChange = true; - child._children = undefined; - - // Adjust the pos or end (or both) of the intersecting element accordingly. - adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); - forEachChild(child, visitNode, visitArray); - if (hasJSDocNodes(child)) { - for (const jsDocComment of child.jsDoc!) { - visitNode(jsDocComment as Node as IncrementalNode); - } + // Adjust the pos or end (or both) of the intersecting element accordingly. + adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); + forEachChild(child, visitNode, visitArray); + if (hasJSDocNodes(child)) { + for (const jsDocComment of child.jsDoc!) { + visitNode(jsDocComment as Node as IncrementalNode); } - checkNodePositions(child, aggressiveChecks); - return; } - - // Otherwise, the node is entirely before the change range. No need to do anything with it. - Debug.assert(fullEnd < changeStart); + checkNodePositions(child, aggressiveChecks); + return; } - function visitArray(array: IncrementalNodeArray) { - Debug.assert(array.pos <= array.end); - if (array.pos > changeRangeOldEnd) { - // Array is entirely after the change range. We need to move it, and move any of - // its children. - moveElementEntirelyPastChangeRange(array, /*isArray*/ true, delta, oldText, newText, aggressiveChecks); - return; - } + // Otherwise, the node is entirely before the change range. No need to do anything with it. + Debug.assert(fullEnd < changeStart); + } - // Check if the element intersects the change range. If it does, then it is not - // reusable. Also, we'll need to recurse to see what constituent portions we may - // be able to use. - const fullEnd = array.end; - if (fullEnd >= changeStart) { - array.intersectsChange = true; - array._children = undefined; + function visitArray(array: IncrementalNodeArray) { + Debug.assert(array.pos <= array.end); + if (array.pos > changeRangeOldEnd) { + // Array is entirely after the change range. We need to move it, and move any of + // its children. + moveElementEntirelyPastChangeRange(array, /*isArray*/ true, delta, oldText, newText, aggressiveChecks); + return; + } - // Adjust the pos or end (or both) of the intersecting array accordingly. - adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); - for (const node of array) { - visitNode(node); - } - return; - } + // Check if the element intersects the change range. If it does, then it is not + // reusable. Also, we'll need to recurse to see what constituent portions we may + // be able to use. + const fullEnd = array.end; + if (fullEnd >= changeStart) { + array.intersectsChange = true; + array._children = undefined; - // Otherwise, the array is entirely before the change range. No need to do anything with it. - Debug.assert(fullEnd < changeStart); + // Adjust the pos or end (or both) of the intersecting array accordingly. + adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); + for (const node of array) { + visitNode(node); + } + return; } + + // Otherwise, the array is entirely before the change range. No need to do anything with it. + Debug.assert(fullEnd < changeStart); } + } - function extendToAffectedRange(sourceFile: SourceFile, changeRange: TextChangeRange): TextChangeRange { - // Consider the following code: - // void foo() { /; } - // - // If the text changes with an insertion of / just before the semicolon then we end up with: - // void foo() { //; } - // - // If we were to just use the changeRange a is, then we would not rescan the { token - // (as it does not intersect the actual original change range). Because an edit may - // change the token touching it, we actually need to look back *at least* one token so - // that the prior token sees that change. - const maxLookahead = 1; + function extendToAffectedRange(sourceFile: SourceFile, changeRange: TextChangeRange): TextChangeRange { + // Consider the following code: + // void foo() { /; } + // + // If the text changes with an insertion of / just before the semicolon then we end up with: + // void foo() { //; } + // + // If we were to just use the changeRange a is, then we would not rescan the { token + // (as it does not intersect the actual original change range). Because an edit may + // change the token touching it, we actually need to look back *at least* one token so + // that the prior token sees that change. + const maxLookahead = 1; - let start = changeRange.span.start; + let start = changeRange.span.start; - // the first iteration aligns us with the change start. subsequent iteration move us to - // the left by maxLookahead tokens. We only need to do this as long as we're not at the - // start of the tree. - for (let i = 0; start > 0 && i <= maxLookahead; i++) { - const nearestNode = findNearestNodeStartingBeforeOrAtPosition(sourceFile, start); - Debug.assert(nearestNode.pos <= start); - const position = nearestNode.pos; + // the first iteration aligns us with the change start. subsequent iteration move us to + // the left by maxLookahead tokens. We only need to do this as long as we're not at the + // start of the tree. + for (let i = 0; start > 0 && i <= maxLookahead; i++) { + const nearestNode = findNearestNodeStartingBeforeOrAtPosition(sourceFile, start); + Debug.assert(nearestNode.pos <= start); + const position = nearestNode.pos; - start = Math.max(0, position - 1); - } + start = Math.max(0, position - 1); + } - const finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span)); - const finalLength = changeRange.newLength + (changeRange.span.start - start); + const finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span)); + const finalLength = changeRange.newLength + (changeRange.span.start - start); - return createTextChangeRange(finalSpan, finalLength); - } + return createTextChangeRange(finalSpan, finalLength); + } - function findNearestNodeStartingBeforeOrAtPosition(sourceFile: SourceFile, position: number): Node { - let bestResult: Node = sourceFile; - let lastNodeEntirelyBeforePosition: Node | undefined; + function findNearestNodeStartingBeforeOrAtPosition(sourceFile: SourceFile, position: number): Node { + let bestResult: Node = sourceFile; + let lastNodeEntirelyBeforePosition: Node | undefined; - forEachChild(sourceFile, visit); + forEachChild(sourceFile, visit); - if (lastNodeEntirelyBeforePosition) { - const lastChildOfLastEntireNodeBeforePosition = getLastDescendant(lastNodeEntirelyBeforePosition); - if (lastChildOfLastEntireNodeBeforePosition.pos > bestResult.pos) { - bestResult = lastChildOfLastEntireNodeBeforePosition; - } + if (lastNodeEntirelyBeforePosition) { + const lastChildOfLastEntireNodeBeforePosition = getLastDescendant(lastNodeEntirelyBeforePosition); + if (lastChildOfLastEntireNodeBeforePosition.pos > bestResult.pos) { + bestResult = lastChildOfLastEntireNodeBeforePosition; } + } - return bestResult; + return bestResult; - function getLastDescendant(node: Node): Node { - while (true) { - const lastChild = getLastChild(node); - if (lastChild) { - node = lastChild; - } - else { - return node; - } + function getLastDescendant(node: Node): Node { + while (true) { + const lastChild = getLastChild(node); + if (lastChild) { + node = lastChild; + } + else { + return node; } } + } - function visit(child: Node) { - if (nodeIsMissing(child)) { - // Missing nodes are effectively invisible to us. We never even consider them - // When trying to find the nearest node before us. - return; + function visit(child: Node) { + if (nodeIsMissing(child)) { + // Missing nodes are effectively invisible to us. We never even consider them + // When trying to find the nearest node before us. + return; + } + + // If the child intersects this position, then this node is currently the nearest + // node that starts before the position. + if (child.pos <= position) { + if (child.pos >= bestResult.pos) { + // This node starts before the position, and is closer to the position than + // the previous best node we found. It is now the new best node. + bestResult = child; } - // If the child intersects this position, then this node is currently the nearest - // node that starts before the position. - if (child.pos <= position) { - if (child.pos >= bestResult.pos) { - // This node starts before the position, and is closer to the position than - // the previous best node we found. It is now the new best node. - bestResult = child; - } + // Now, the node may overlap the position, or it may end entirely before the + // position. If it overlaps with the position, then either it, or one of its + // children must be the nearest node before the position. So we can just + // recurse into this child to see if we can find something better. + if (position < child.end) { + // The nearest node is either this child, or one of the children inside + // of it. We've already marked this child as the best so far. Recurse + // in case one of the children is better. + forEachChild(child, visit); - // Now, the node may overlap the position, or it may end entirely before the - // position. If it overlaps with the position, then either it, or one of its - // children must be the nearest node before the position. So we can just - // recurse into this child to see if we can find something better. - if (position < child.end) { - // The nearest node is either this child, or one of the children inside - // of it. We've already marked this child as the best so far. Recurse - // in case one of the children is better. - forEachChild(child, visit); - - // Once we look at the children of this node, then there's no need to - // continue any further. - return true; - } - else { - Debug.assert(child.end <= position); - // The child ends entirely before this position. Say you have the following - // (where $ is the position) - // - // ? $ : <...> <...> - // - // We would want to find the nearest preceding node in "complex expr 2". - // To support that, we keep track of this node, and once we're done searching - // for a best node, we recurse down this node to see if we can find a good - // result in it. - // - // This approach allows us to quickly skip over nodes that are entirely - // before the position, while still allowing us to find any nodes in the - // last one that might be what we want. - lastNodeEntirelyBeforePosition = child; - } + // Once we look at the children of this node, then there's no need to + // continue any further. + return true; } else { - Debug.assert(child.pos > position); - // We're now at a node that is entirely past the position we're searching for. - // This node (and all following nodes) could never contribute to the result, - // so just skip them by returning 'true' here. - return true; + Debug.assert(child.end <= position); + // The child ends entirely before this position. Say you have the following + // (where $ is the position) + // + // ? $ : <...> <...> + // + // We would want to find the nearest preceding node in "complex expr 2". + // To support that, we keep track of this node, and once we're done searching + // for a best node, we recurse down this node to see if we can find a good + // result in it. + // + // This approach allows us to quickly skip over nodes that are entirely + // before the position, while still allowing us to find any nodes in the + // last one that might be what we want. + lastNodeEntirelyBeforePosition = child; } } + else { + Debug.assert(child.pos > position); + // We're now at a node that is entirely past the position we're searching for. + // This node (and all following nodes) could never contribute to the result, + // so just skip them by returning 'true' here. + return true; + } } + } - function checkChangeRange(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean) { - const oldText = sourceFile.text; - if (textChangeRange) { - Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length); + function checkChangeRange(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean) { + const oldText = sourceFile.text; + if (textChangeRange) { + Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length); - if (aggressiveChecks || Debug.shouldAssert(AssertionLevel.VeryAggressive)) { - const oldTextPrefix = oldText.substr(0, textChangeRange.span.start); - const newTextPrefix = newText.substr(0, textChangeRange.span.start); - Debug.assert(oldTextPrefix === newTextPrefix); + if (aggressiveChecks || Debug.shouldAssert(AssertionLevel.VeryAggressive)) { + const oldTextPrefix = oldText.substr(0, textChangeRange.span.start); + const newTextPrefix = newText.substr(0, textChangeRange.span.start); + Debug.assert(oldTextPrefix === newTextPrefix); - const oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length); - const newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length); - Debug.assert(oldTextSuffix === newTextSuffix); - } + const oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length); + const newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length); + Debug.assert(oldTextSuffix === newTextSuffix); } } + } - interface IncrementalElement extends ReadonlyTextRange { - readonly parent: Node; - intersectsChange: boolean; - length?: number; - _children: Node[] | undefined; - } - - export interface IncrementalNode extends Node, IncrementalElement { - hasBeenIncrementallyParsed: boolean; - } + interface IncrementalElement extends ReadonlyTextRange { + readonly parent: Node; + intersectsChange: boolean; + length?: number; + _children: Node[] | undefined; + } - interface IncrementalNodeArray extends NodeArray, IncrementalElement { - length: number; - } + export interface IncrementalNode extends Node, IncrementalElement { + hasBeenIncrementallyParsed: boolean; + } - // Allows finding nodes in the source file at a certain position in an efficient manner. - // The implementation takes advantage of the calling pattern it knows the parser will - // make in order to optimize finding nodes as quickly as possible. - export interface SyntaxCursor { - currentNode(position: number): IncrementalNode; - } + interface IncrementalNodeArray extends NodeArray, IncrementalElement { + length: number; + } - export function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor { - let currentArray: NodeArray = sourceFile.statements; - let currentArrayIndex = 0; + // Allows finding nodes in the source file at a certain position in an efficient manner. + // The implementation takes advantage of the calling pattern it knows the parser will + // make in order to optimize finding nodes as quickly as possible. + export interface SyntaxCursor { + currentNode(position: number): IncrementalNode; + } - Debug.assert(currentArrayIndex < currentArray.length); - let current = currentArray[currentArrayIndex]; - let lastQueriedPosition = InvalidPosition.Value; + export function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor { + let currentArray: NodeArray = sourceFile.statements; + let currentArrayIndex = 0; - return { - currentNode(position: number) { - // Only compute the current node if the position is different than the last time - // we were asked. The parser commonly asks for the node at the same position - // twice. Once to know if can read an appropriate list element at a certain point, - // and then to actually read and consume the node. - if (position !== lastQueriedPosition) { - // Much of the time the parser will need the very next node in the array that - // we just returned a node from.So just simply check for that case and move - // forward in the array instead of searching for the node again. - if (current && current.end === position && currentArrayIndex < (currentArray.length - 1)) { - currentArrayIndex++; - current = currentArray[currentArrayIndex]; - } + Debug.assert(currentArrayIndex < currentArray.length); + let current = currentArray[currentArrayIndex]; + let lastQueriedPosition = InvalidPosition.Value; - // If we don't have a node, or the node we have isn't in the right position, - // then try to find a viable node at the position requested. - if (!current || current.pos !== position) { - findHighestListElementThatStartsAtPosition(position); - } + return { + currentNode(position: number) { + // Only compute the current node if the position is different than the last time + // we were asked. The parser commonly asks for the node at the same position + // twice. Once to know if can read an appropriate list element at a certain point, + // and then to actually read and consume the node. + if (position !== lastQueriedPosition) { + // Much of the time the parser will need the very next node in the array that + // we just returned a node from.So just simply check for that case and move + // forward in the array instead of searching for the node again. + if (current && current.end === position && currentArrayIndex < (currentArray.length - 1)) { + currentArrayIndex++; + current = currentArray[currentArrayIndex]; } - // Cache this query so that we don't do any extra work if the parser calls back - // into us. Note: this is very common as the parser will make pairs of calls like - // 'isListElement -> parseListElement'. If we were unable to find a node when - // called with 'isListElement', we don't want to redo the work when parseListElement - // is called immediately after. - lastQueriedPosition = position; - - // Either we don'd have a node, or we have a node at the position being asked for. - Debug.assert(!current || current.pos === position); - return current as IncrementalNode; + // If we don't have a node, or the node we have isn't in the right position, + // then try to find a viable node at the position requested. + if (!current || current.pos !== position) { + findHighestListElementThatStartsAtPosition(position); + } } - }; - // Finds the highest element in the tree we can find that starts at the provided position. - // The element must be a direct child of some node list in the tree. This way after we - // return it, we can easily return its next sibling in the list. - function findHighestListElementThatStartsAtPosition(position: number) { - // Clear out any cached state about the last node we found. - currentArray = undefined!; - currentArrayIndex = InvalidPosition.Value; - current = undefined!; - - // Recurse into the source file to find the highest node at this position. - forEachChild(sourceFile, visitNode, visitArray); - return; + // Cache this query so that we don't do any extra work if the parser calls back + // into us. Note: this is very common as the parser will make pairs of calls like + // 'isListElement -> parseListElement'. If we were unable to find a node when + // called with 'isListElement', we don't want to redo the work when parseListElement + // is called immediately after. + lastQueriedPosition = position; - function visitNode(node: Node) { - if (position >= node.pos && position < node.end) { - // Position was within this node. Keep searching deeper to find the node. - forEachChild(node, visitNode, visitArray); + // Either we don'd have a node, or we have a node at the position being asked for. + Debug.assert(!current || current.pos === position); + return current as IncrementalNode; + } + }; - // don't proceed any further in the search. - return true; - } + // Finds the highest element in the tree we can find that starts at the provided position. + // The element must be a direct child of some node list in the tree. This way after we + // return it, we can easily return its next sibling in the list. + function findHighestListElementThatStartsAtPosition(position: number) { + // Clear out any cached state about the last node we found. + currentArray = undefined!; + currentArrayIndex = InvalidPosition.Value; + current = undefined!; + + // Recurse into the source file to find the highest node at this position. + forEachChild(sourceFile, visitNode, visitArray); + return; - // position wasn't in this node, have to keep searching. - return false; + function visitNode(node: Node) { + if (position >= node.pos && position < node.end) { + // Position was within this node. Keep searching deeper to find the node. + forEachChild(node, visitNode, visitArray); + + // don't proceed any further in the search. + return true; } - function visitArray(array: NodeArray) { - if (position >= array.pos && position < array.end) { - // position was in this array. Search through this array to see if we find a - // viable element. - for (let i = 0; i < array.length; i++) { - const child = array[i]; - if (child) { - if (child.pos === position) { - // Found the right node. We're done. - currentArray = array; - currentArrayIndex = i; - current = child; + // position wasn't in this node, have to keep searching. + return false; + } + + function visitArray(array: NodeArray) { + if (position >= array.pos && position < array.end) { + // position was in this array. Search through this array to see if we find a + // viable element. + for (let i = 0; i < array.length; i++) { + const child = array[i]; + if (child) { + if (child.pos === position) { + // Found the right node. We're done. + currentArray = array; + currentArrayIndex = i; + current = child; + return true; + } + else { + if (child.pos < position && position < child.end) { + // Position in somewhere within this child. Search in it and + // stop searching in this array. + forEachChild(child, visitNode, visitArray); return true; } - else { - if (child.pos < position && position < child.end) { - // Position in somewhere within this child. Search in it and - // stop searching in this array. - forEachChild(child, visitNode, visitArray); - return true; - } - } } } } - - // position wasn't in this array, have to keep searching. - return false; } - } - } - const enum InvalidPosition { - Value = -1 + // position wasn't in this array, have to keep searching. + return false; + } } } - /** @internal */ - export function isDeclarationFileName(fileName: string): boolean { - return fileExtensionIsOneOf(fileName, supportedDeclarationExtensions); + const enum InvalidPosition { + Value = -1 } +} - /*@internal*/ - export interface PragmaContext { - languageVersion: ScriptTarget; - pragmas?: PragmaMap; - checkJsDirective?: CheckJsDirective; - referencedFiles: FileReference[]; - typeReferenceDirectives: FileReference[]; - libReferenceDirectives: FileReference[]; - amdDependencies: AmdDependency[]; - hasNoDefaultLib?: boolean; - moduleName?: string; - } +/** @internal */ +export function isDeclarationFileName(fileName: string): boolean { + return fileExtensionIsOneOf(fileName, supportedDeclarationExtensions); +} - function parseResolutionMode(mode: string | undefined, pos: number, end: number, reportDiagnostic: PragmaDiagnosticReporter): ModuleKind.ESNext | ModuleKind.CommonJS | undefined { - if (!mode) { - return undefined; - } - if (mode === "import") { - return ModuleKind.ESNext; - } - if (mode === "require") { - return ModuleKind.CommonJS; - } - reportDiagnostic(pos, end - pos, Diagnostics.resolution_mode_should_be_either_require_or_import); +/** @internal */ +export interface PragmaContext { + languageVersion: ScriptTarget; + pragmas?: PragmaMap; + checkJsDirective?: CheckJsDirective; + referencedFiles: FileReference[]; + typeReferenceDirectives: FileReference[]; + libReferenceDirectives: FileReference[]; + amdDependencies: AmdDependency[]; + hasNoDefaultLib?: boolean; + moduleName?: string; +} + +function parseResolutionMode(mode: string | undefined, pos: number, end: number, reportDiagnostic: PragmaDiagnosticReporter): ModuleKind.ESNext | ModuleKind.CommonJS | undefined { + if (!mode) { return undefined; } + if (mode === "import") { + return ModuleKind.ESNext; + } + if (mode === "require") { + return ModuleKind.CommonJS; + } + reportDiagnostic(pos, end - pos, Diagnostics.resolution_mode_should_be_either_require_or_import); + return undefined; +} - /*@internal*/ - export function processCommentPragmas(context: PragmaContext, sourceText: string): void { - const pragmas: PragmaPseudoMapEntry[] = []; +/** @internal */ +export function processCommentPragmas(context: PragmaContext, sourceText: string): void { + const pragmas: PragmaPseudoMapEntry[] = []; - for (const range of getLeadingCommentRanges(sourceText, 0) || emptyArray) { - const comment = sourceText.substring(range.pos, range.end); - extractPragmas(pragmas, range, comment); - } + for (const range of getLeadingCommentRanges(sourceText, 0) || emptyArray) { + const comment = sourceText.substring(range.pos, range.end); + extractPragmas(pragmas, range, comment); + } - context.pragmas = new Map() as PragmaMap; - for (const pragma of pragmas) { - if (context.pragmas.has(pragma.name)) { - const currentValue = context.pragmas.get(pragma.name); - if (currentValue instanceof Array) { - currentValue.push(pragma.args); - } - else { - context.pragmas.set(pragma.name, [currentValue, pragma.args]); - } - continue; + context.pragmas = new Map() as PragmaMap; + for (const pragma of pragmas) { + if (context.pragmas.has(pragma.name)) { + const currentValue = context.pragmas.get(pragma.name); + if (currentValue instanceof Array) { + currentValue.push(pragma.args); } - context.pragmas.set(pragma.name, pragma.args); - } - } - - /*@internal*/ - type PragmaDiagnosticReporter = (pos: number, length: number, message: DiagnosticMessage) => void; - - /*@internal*/ - export function processPragmasIntoFields(context: PragmaContext, reportDiagnostic: PragmaDiagnosticReporter): void { - context.checkJsDirective = undefined; - context.referencedFiles = []; - context.typeReferenceDirectives = []; - context.libReferenceDirectives = []; - context.amdDependencies = []; - context.hasNoDefaultLib = false; - context.pragmas!.forEach((entryOrList, key) => { // TODO: GH#18217 - // TODO: The below should be strongly type-guarded and not need casts/explicit annotations, since entryOrList is related to - // key and key is constrained to a union; but it's not (see GH#21483 for at least partial fix) :( - switch (key) { - case "reference": { - const referencedFiles = context.referencedFiles; - const typeReferenceDirectives = context.typeReferenceDirectives; - const libReferenceDirectives = context.libReferenceDirectives; - forEach(toArray(entryOrList) as PragmaPseudoMap["reference"][], arg => { - const { types, lib, path, ["resolution-mode"]: res } = arg.arguments; - if (arg.arguments["no-default-lib"]) { - context.hasNoDefaultLib = true; - } - else if (types) { - const parsed = parseResolutionMode(res, types.pos, types.end, reportDiagnostic); - typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value, ...(parsed ? { resolutionMode: parsed } : {}) }); - } - else if (lib) { - libReferenceDirectives.push({ pos: lib.pos, end: lib.end, fileName: lib.value }); - } - else if (path) { - referencedFiles.push({ pos: path.pos, end: path.end, fileName: path.value }); - } - else { - reportDiagnostic(arg.range.pos, arg.range.end - arg.range.pos, Diagnostics.Invalid_reference_directive_syntax); - } - }); - break; - } - case "amd-dependency": { - context.amdDependencies = map( - toArray(entryOrList) as PragmaPseudoMap["amd-dependency"][], - x => ({ name: x.arguments.name, path: x.arguments.path })); - break; - } - case "amd-module": { - if (entryOrList instanceof Array) { - for (const entry of entryOrList) { - if (context.moduleName) { - // TODO: It's probably fine to issue this diagnostic on all instances of the pragma - reportDiagnostic(entry.range.pos, entry.range.end - entry.range.pos, Diagnostics.An_AMD_module_cannot_have_multiple_name_assignments); - } - context.moduleName = (entry as PragmaPseudoMap["amd-module"]).arguments.name; - } - } - else { - context.moduleName = (entryOrList as PragmaPseudoMap["amd-module"]).arguments.name; - } - break; - } - case "ts-nocheck": - case "ts-check": { - // _last_ of either nocheck or check in a file is the "winner" - forEach(toArray(entryOrList), entry => { - if (!context.checkJsDirective || entry.range.pos > context.checkJsDirective.pos) { - context.checkJsDirective = { - enabled: key === "ts-check", - end: entry.range.end, - pos: entry.range.pos - }; - } - }); - break; - } - case "jsx": - case "jsxfrag": - case "jsximportsource": - case "jsxruntime": - return; // Accessed directly - default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future? + else { + context.pragmas.set(pragma.name, [currentValue, pragma.args]); } - }); - } - - const namedArgRegExCache = new Map(); - function getNamedArgRegEx(name: string): RegExp { - if (namedArgRegExCache.has(name)) { - return namedArgRegExCache.get(name)!; + continue; } - const result = new RegExp(`(\\s${name}\\s*=\\s*)(?:(?:'([^']*)')|(?:"([^"]*)"))`, "im"); - namedArgRegExCache.set(name, result); - return result; + context.pragmas.set(pragma.name, pragma.args); } +} - const tripleSlashXMLCommentStartRegEx = /^\/\/\/\s*<(\S+)\s.*?\/>/im; - const singleLinePragmaRegEx = /^\/\/\/?\s*@(\S+)\s*(.*)\s*$/im; - function extractPragmas(pragmas: PragmaPseudoMapEntry[], range: CommentRange, text: string) { - const tripleSlash = range.kind === SyntaxKind.SingleLineCommentTrivia && tripleSlashXMLCommentStartRegEx.exec(text); - if (tripleSlash) { - const name = tripleSlash[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so the below check to make it safe typechecks - const pragma = commentPragmas[name] as PragmaDefinition; - if (!pragma || !(pragma.kind! & PragmaKindFlags.TripleSlashXML)) { - return; - } - if (pragma.args) { - const argument: {[index: string]: string | {value: string, pos: number, end: number}} = {}; - for (const arg of pragma.args) { - const matcher = getNamedArgRegEx(arg.name); - const matchResult = matcher.exec(text); - if (!matchResult && !arg.optional) { - return; // Missing required argument, don't parse +/** @internal */ +export type PragmaDiagnosticReporter = (pos: number, length: number, message: DiagnosticMessage) => void; + +/** @internal */ +export function processPragmasIntoFields(context: PragmaContext, reportDiagnostic: PragmaDiagnosticReporter): void { + context.checkJsDirective = undefined; + context.referencedFiles = []; + context.typeReferenceDirectives = []; + context.libReferenceDirectives = []; + context.amdDependencies = []; + context.hasNoDefaultLib = false; + context.pragmas!.forEach((entryOrList, key) => { // TODO: GH#18217 + // TODO: The below should be strongly type-guarded and not need casts/explicit annotations, since entryOrList is related to + // key and key is constrained to a union; but it's not (see GH#21483 for at least partial fix) :( + switch (key) { + case "reference": { + const referencedFiles = context.referencedFiles; + const typeReferenceDirectives = context.typeReferenceDirectives; + const libReferenceDirectives = context.libReferenceDirectives; + forEach(toArray(entryOrList) as PragmaPseudoMap["reference"][], arg => { + const { types, lib, path, ["resolution-mode"]: res } = arg.arguments; + if (arg.arguments["no-default-lib"]) { + context.hasNoDefaultLib = true; + } + else if (types) { + const parsed = parseResolutionMode(res, types.pos, types.end, reportDiagnostic); + typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value, ...(parsed ? { resolutionMode: parsed } : {}) }); + } + else if (lib) { + libReferenceDirectives.push({ pos: lib.pos, end: lib.end, fileName: lib.value }); + } + else if (path) { + referencedFiles.push({ pos: path.pos, end: path.end, fileName: path.value }); } - else if (matchResult) { - const value = matchResult[2] || matchResult[3]; - if (arg.captureSpan) { - const startPos = range.pos + matchResult.index + matchResult[1].length + 1; - argument[arg.name] = { - value, - pos: startPos, - end: startPos + value.length - }; - } - else { - argument[arg.name] = value; + else { + reportDiagnostic(arg.range.pos, arg.range.end - arg.range.pos, Diagnostics.Invalid_reference_directive_syntax); + } + }); + break; + } + case "amd-dependency": { + context.amdDependencies = map( + toArray(entryOrList) as PragmaPseudoMap["amd-dependency"][], + x => ({ name: x.arguments.name, path: x.arguments.path })); + break; + } + case "amd-module": { + if (entryOrList instanceof Array) { + for (const entry of entryOrList) { + if (context.moduleName) { + // TODO: It's probably fine to issue this diagnostic on all instances of the pragma + reportDiagnostic(entry.range.pos, entry.range.end - entry.range.pos, Diagnostics.An_AMD_module_cannot_have_multiple_name_assignments); } + context.moduleName = (entry as PragmaPseudoMap["amd-module"]).arguments.name; } } - pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry); + else { + context.moduleName = (entryOrList as PragmaPseudoMap["amd-module"]).arguments.name; + } + break; } - else { - pragmas.push({ name, args: { arguments: {}, range } } as PragmaPseudoMapEntry); + case "ts-nocheck": + case "ts-check": { + // _last_ of either nocheck or check in a file is the "winner" + forEach(toArray(entryOrList), entry => { + if (!context.checkJsDirective || entry.range.pos > context.checkJsDirective.pos) { + context.checkJsDirective = { + enabled: key === "ts-check", + end: entry.range.end, + pos: entry.range.pos + }; + } + }); + break; } - return; - } - - const singleLine = range.kind === SyntaxKind.SingleLineCommentTrivia && singleLinePragmaRegEx.exec(text); - if (singleLine) { - return addPragmaForMatch(pragmas, range, PragmaKindFlags.SingleLine, singleLine); + case "jsx": + case "jsxfrag": + case "jsximportsource": + case "jsxruntime": + return; // Accessed directly + default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future? } + }); +} - if (range.kind === SyntaxKind.MultiLineCommentTrivia) { - const multiLinePragmaRegEx = /@(\S+)(\s+.*)?$/gim; // Defined inline since it uses the "g" flag, which keeps a persistent index (for iterating) - let multiLineMatch: RegExpExecArray | null; - while (multiLineMatch = multiLinePragmaRegEx.exec(text)) { - addPragmaForMatch(pragmas, range, PragmaKindFlags.MultiLine, multiLineMatch); - } - } +const namedArgRegExCache = new Map(); +function getNamedArgRegEx(name: string): RegExp { + if (namedArgRegExCache.has(name)) { + return namedArgRegExCache.get(name)!; } + const result = new RegExp(`(\\s${name}\\s*=\\s*)(?:(?:'([^']*)')|(?:"([^"]*)"))`, "im"); + namedArgRegExCache.set(name, result); + return result; +} - function addPragmaForMatch(pragmas: PragmaPseudoMapEntry[], range: CommentRange, kind: PragmaKindFlags, match: RegExpExecArray) { - if (!match) return; - const name = match[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so they below check to make it safe typechecks +const tripleSlashXMLCommentStartRegEx = /^\/\/\/\s*<(\S+)\s.*?\/>/im; +const singleLinePragmaRegEx = /^\/\/\/?\s*@(\S+)\s*(.*)\s*$/im; +function extractPragmas(pragmas: PragmaPseudoMapEntry[], range: CommentRange, text: string) { + const tripleSlash = range.kind === SyntaxKind.SingleLineCommentTrivia && tripleSlashXMLCommentStartRegEx.exec(text); + if (tripleSlash) { + const name = tripleSlash[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so the below check to make it safe typechecks const pragma = commentPragmas[name] as PragmaDefinition; - if (!pragma || !(pragma.kind! & kind)) { + if (!pragma || !(pragma.kind! & PragmaKindFlags.TripleSlashXML)) { return; } - const args = match[2]; // Split on spaces and match up positionally with definition - const argument = getNamedPragmaArguments(pragma, args); - if (argument === "fail") return; // Missing required argument, fail to parse it - pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry); + if (pragma.args) { + const argument: {[index: string]: string | {value: string, pos: number, end: number}} = {}; + for (const arg of pragma.args) { + const matcher = getNamedArgRegEx(arg.name); + const matchResult = matcher.exec(text); + if (!matchResult && !arg.optional) { + return; // Missing required argument, don't parse + } + else if (matchResult) { + const value = matchResult[2] || matchResult[3]; + if (arg.captureSpan) { + const startPos = range.pos + matchResult.index + matchResult[1].length + 1; + argument[arg.name] = { + value, + pos: startPos, + end: startPos + value.length + }; + } + else { + argument[arg.name] = value; + } + } + } + pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry); + } + else { + pragmas.push({ name, args: { arguments: {}, range } } as PragmaPseudoMapEntry); + } return; } - function getNamedPragmaArguments(pragma: PragmaDefinition, text: string | undefined): {[index: string]: string} | "fail" { - if (!text) return {}; - if (!pragma.args) return {}; - const args = trimString(text).split(/\s+/); - const argMap: {[index: string]: string} = {}; - for (let i = 0; i < pragma.args.length; i++) { - const argument = pragma.args[i]; - if (!args[i] && !argument.optional) { - return "fail"; - } - if (argument.captureSpan) { - return Debug.fail("Capture spans not yet implemented for non-xml pragmas"); - } - argMap[argument.name] = args[i]; - } - return argMap; + const singleLine = range.kind === SyntaxKind.SingleLineCommentTrivia && singleLinePragmaRegEx.exec(text); + if (singleLine) { + return addPragmaForMatch(pragmas, range, PragmaKindFlags.SingleLine, singleLine); } - /** @internal */ - export function tagNamesAreEquivalent(lhs: JsxTagNameExpression, rhs: JsxTagNameExpression): boolean { - if (lhs.kind !== rhs.kind) { - return false; + if (range.kind === SyntaxKind.MultiLineCommentTrivia) { + const multiLinePragmaRegEx = /@(\S+)(\s+.*)?$/gim; // Defined inline since it uses the "g" flag, which keeps a persistent index (for iterating) + let multiLineMatch: RegExpExecArray | null; + while (multiLineMatch = multiLinePragmaRegEx.exec(text)) { + addPragmaForMatch(pragmas, range, PragmaKindFlags.MultiLine, multiLineMatch); } + } +} - if (lhs.kind === SyntaxKind.Identifier) { - return lhs.escapedText === (rhs as Identifier).escapedText; - } +function addPragmaForMatch(pragmas: PragmaPseudoMapEntry[], range: CommentRange, kind: PragmaKindFlags, match: RegExpExecArray) { + if (!match) return; + const name = match[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so they below check to make it safe typechecks + const pragma = commentPragmas[name] as PragmaDefinition; + if (!pragma || !(pragma.kind! & kind)) { + return; + } + const args = match[2]; // Split on spaces and match up positionally with definition + const argument = getNamedPragmaArguments(pragma, args); + if (argument === "fail") return; // Missing required argument, fail to parse it + pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry); + return; +} - if (lhs.kind === SyntaxKind.ThisKeyword) { - return true; +function getNamedPragmaArguments(pragma: PragmaDefinition, text: string | undefined): {[index: string]: string} | "fail" { + if (!text) return {}; + if (!pragma.args) return {}; + const args = trimString(text).split(/\s+/); + const argMap: {[index: string]: string} = {}; + for (let i = 0; i < pragma.args.length; i++) { + const argument = pragma.args[i]; + if (!args[i] && !argument.optional) { + return "fail"; } + if (argument.captureSpan) { + return Debug.fail("Capture spans not yet implemented for non-xml pragmas"); + } + argMap[argument.name] = args[i]; + } + return argMap; +} + +/** @internal */ +export function tagNamesAreEquivalent(lhs: JsxTagNameExpression, rhs: JsxTagNameExpression): boolean { + if (lhs.kind !== rhs.kind) { + return false; + } + + if (lhs.kind === SyntaxKind.Identifier) { + return lhs.escapedText === (rhs as Identifier).escapedText; + } - // If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only - // take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression - // it is safe to case the expression property as such. See parseJsxElementName for how we parse tag name in Jsx element - return (lhs as PropertyAccessExpression).name.escapedText === (rhs as PropertyAccessExpression).name.escapedText && - tagNamesAreEquivalent((lhs as PropertyAccessExpression).expression as JsxTagNameExpression, (rhs as PropertyAccessExpression).expression as JsxTagNameExpression); + if (lhs.kind === SyntaxKind.ThisKeyword) { + return true; } + + // If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only + // take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression + // it is safe to case the expression property as such. See parseJsxElementName for how we parse tag name in Jsx element + return (lhs as PropertyAccessExpression).name.escapedText === (rhs as PropertyAccessExpression).name.escapedText && + tagNamesAreEquivalent((lhs as PropertyAccessExpression).expression as JsxTagNameExpression, (rhs as PropertyAccessExpression).expression as JsxTagNameExpression); } diff --git a/src/compiler/path.ts b/src/compiler/path.ts index 86bafd8f785ce..7f84ae19c6a9b 100644 --- a/src/compiler/path.ts +++ b/src/compiler/path.ts @@ -1,871 +1,975 @@ -/* @internal */ -namespace ts { - /** - * Internally, we represent paths as strings with '/' as the directory separator. - * When we make system calls (eg: LanguageServiceHost.getDirectory()), - * we expect the host to correctly handle paths in our specified format. - */ - export const directorySeparator = "/"; - export const altDirectorySeparator = "\\"; - const urlSchemeSeparator = "://"; - const backslashRegExp = /\\/g; - - //// Path Tests - - /** - * Determines whether a charCode corresponds to `/` or `\`. - */ - export function isAnyDirectorySeparator(charCode: number): boolean { - return charCode === CharacterCodes.slash || charCode === CharacterCodes.backslash; - } - - /** - * Determines whether a path starts with a URL scheme (e.g. starts with `http://`, `ftp://`, `file://`, etc.). - */ - export function isUrl(path: string) { - return getEncodedRootLength(path) < 0; - } - - /** - * Determines whether a path is an absolute disk path (e.g. starts with `/`, or a dos path - * like `c:`, `c:\` or `c:/`). - */ - export function isRootedDiskPath(path: string) { - return getEncodedRootLength(path) > 0; - } - - /** - * Determines whether a path consists only of a path root. - */ - export function isDiskPathRoot(path: string) { - const rootLength = getEncodedRootLength(path); - return rootLength > 0 && rootLength === path.length; - } - - /** - * Determines whether a path starts with an absolute path component (i.e. `/`, `c:/`, `file://`, etc.). - * - * ```ts - * // POSIX - * pathIsAbsolute("/path/to/file.ext") === true - * // DOS - * pathIsAbsolute("c:/path/to/file.ext") === true - * // URL - * pathIsAbsolute("file:///path/to/file.ext") === true - * // Non-absolute - * pathIsAbsolute("path/to/file.ext") === false - * pathIsAbsolute("./path/to/file.ext") === false - * ``` - */ - export function pathIsAbsolute(path: string): boolean { - return getEncodedRootLength(path) !== 0; - } - - /** - * Determines whether a path starts with a relative path component (i.e. `.` or `..`). - */ - export function pathIsRelative(path: string): boolean { - return /^\.\.?($|[\\/])/.test(path); - } +import { + CharacterCodes, compareStringsCaseInsensitive, compareStringsCaseSensitive, compareValues, Comparison, Debug, + endsWith, equateStringsCaseInsensitive, equateStringsCaseSensitive, GetCanonicalFileName, getStringComparer, + identity, lastOrUndefined, Path, some, startsWith, stringContains, +} from "./_namespaces/ts"; + +/** + * Internally, we represent paths as strings with '/' as the directory separator. + * When we make system calls (eg: LanguageServiceHost.getDirectory()), + * we expect the host to correctly handle paths in our specified format. + * + * @internal + */ +export const directorySeparator = "/"; +/** @internal */ +export const altDirectorySeparator = "\\"; +const urlSchemeSeparator = "://"; +const backslashRegExp = /\\/g; + +//// Path Tests + +/** + * Determines whether a charCode corresponds to `/` or `\`. + * + * @internal + */ +export function isAnyDirectorySeparator(charCode: number): boolean { + return charCode === CharacterCodes.slash || charCode === CharacterCodes.backslash; +} - /** - * Determines whether a path is neither relative nor absolute, e.g. "path/to/file". - * Also known misleadingly as "non-relative". - */ - export function pathIsBareSpecifier(path: string): boolean { - return !pathIsAbsolute(path) && !pathIsRelative(path); - } +/** + * Determines whether a path starts with a URL scheme (e.g. starts with `http://`, `ftp://`, `file://`, etc.). + * + * @internal + */ +export function isUrl(path: string) { + return getEncodedRootLength(path) < 0; +} - export function hasExtension(fileName: string): boolean { - return stringContains(getBaseFileName(fileName), "."); - } +/** + * Determines whether a path is an absolute disk path (e.g. starts with `/`, or a dos path + * like `c:`, `c:\` or `c:/`). + * + * @internal + */ +export function isRootedDiskPath(path: string) { + return getEncodedRootLength(path) > 0; +} - export function fileExtensionIs(path: string, extension: string): boolean { - return path.length > extension.length && endsWith(path, extension); - } +/** + * Determines whether a path consists only of a path root. + * + * @internal + */ +export function isDiskPathRoot(path: string) { + const rootLength = getEncodedRootLength(path); + return rootLength > 0 && rootLength === path.length; +} - export function fileExtensionIsOneOf(path: string, extensions: readonly string[]): boolean { - for (const extension of extensions) { - if (fileExtensionIs(path, extension)) { - return true; - } - } +/** + * Determines whether a path starts with an absolute path component (i.e. `/`, `c:/`, `file://`, etc.). + * + * ```ts + * // POSIX + * pathIsAbsolute("/path/to/file.ext") === true + * // DOS + * pathIsAbsolute("c:/path/to/file.ext") === true + * // URL + * pathIsAbsolute("file:///path/to/file.ext") === true + * // Non-absolute + * pathIsAbsolute("path/to/file.ext") === false + * pathIsAbsolute("./path/to/file.ext") === false + * ``` + * + * @internal + */ +export function pathIsAbsolute(path: string): boolean { + return getEncodedRootLength(path) !== 0; +} - return false; - } +/** + * Determines whether a path starts with a relative path component (i.e. `.` or `..`). + * + * @internal + */ +export function pathIsRelative(path: string): boolean { + return /^\.\.?($|[\\/])/.test(path); +} - /** - * Determines whether a path has a trailing separator (`/` or `\\`). - */ - export function hasTrailingDirectorySeparator(path: string) { - return path.length > 0 && isAnyDirectorySeparator(path.charCodeAt(path.length - 1)); - } +/** + * Determines whether a path is neither relative nor absolute, e.g. "path/to/file". + * Also known misleadingly as "non-relative". + * + * @internal + */ +export function pathIsBareSpecifier(path: string): boolean { + return !pathIsAbsolute(path) && !pathIsRelative(path); +} - //// Path Parsing +/** @internal */ +export function hasExtension(fileName: string): boolean { + return stringContains(getBaseFileName(fileName), "."); +} - function isVolumeCharacter(charCode: number) { - return (charCode >= CharacterCodes.a && charCode <= CharacterCodes.z) || - (charCode >= CharacterCodes.A && charCode <= CharacterCodes.Z); - } +/** @internal */ +export function fileExtensionIs(path: string, extension: string): boolean { + return path.length > extension.length && endsWith(path, extension); +} - function getFileUrlVolumeSeparatorEnd(url: string, start: number) { - const ch0 = url.charCodeAt(start); - if (ch0 === CharacterCodes.colon) return start + 1; - if (ch0 === CharacterCodes.percent && url.charCodeAt(start + 1) === CharacterCodes._3) { - const ch2 = url.charCodeAt(start + 2); - if (ch2 === CharacterCodes.a || ch2 === CharacterCodes.A) return start + 3; +/** @internal */ +export function fileExtensionIsOneOf(path: string, extensions: readonly string[]): boolean { + for (const extension of extensions) { + if (fileExtensionIs(path, extension)) { + return true; } - return -1; } - /** - * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). - * If the root is part of a URL, the twos-complement of the root length is returned. - */ - function getEncodedRootLength(path: string): number { - if (!path) return 0; - const ch0 = path.charCodeAt(0); + return false; +} - // POSIX or UNC - if (ch0 === CharacterCodes.slash || ch0 === CharacterCodes.backslash) { - if (path.charCodeAt(1) !== ch0) return 1; // POSIX: "/" (or non-normalized "\") +/** + * Determines whether a path has a trailing separator (`/` or `\\`). + * + * @internal + */ +export function hasTrailingDirectorySeparator(path: string) { + return path.length > 0 && isAnyDirectorySeparator(path.charCodeAt(path.length - 1)); +} - const p1 = path.indexOf(ch0 === CharacterCodes.slash ? directorySeparator : altDirectorySeparator, 2); - if (p1 < 0) return path.length; // UNC: "//server" or "\\server" +//// Path Parsing - return p1 + 1; // UNC: "//server/" or "\\server\" - } +function isVolumeCharacter(charCode: number) { + return (charCode >= CharacterCodes.a && charCode <= CharacterCodes.z) || + (charCode >= CharacterCodes.A && charCode <= CharacterCodes.Z); +} - // DOS - if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodes.colon) { - const ch2 = path.charCodeAt(2); - if (ch2 === CharacterCodes.slash || ch2 === CharacterCodes.backslash) return 3; // DOS: "c:/" or "c:\" - if (path.length === 2) return 2; // DOS: "c:" (but not "c:d") - } +function getFileUrlVolumeSeparatorEnd(url: string, start: number) { + const ch0 = url.charCodeAt(start); + if (ch0 === CharacterCodes.colon) return start + 1; + if (ch0 === CharacterCodes.percent && url.charCodeAt(start + 1) === CharacterCodes._3) { + const ch2 = url.charCodeAt(start + 2); + if (ch2 === CharacterCodes.a || ch2 === CharacterCodes.A) return start + 3; + } + return -1; +} - // URL - const schemeEnd = path.indexOf(urlSchemeSeparator); - if (schemeEnd !== -1) { - const authorityStart = schemeEnd + urlSchemeSeparator.length; - const authorityEnd = path.indexOf(directorySeparator, authorityStart); - if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path" - // For local "file" URLs, include the leading DOS volume (if present). - // Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a - // special case interpreted as "the machine from which the URL is being interpreted". - const scheme = path.slice(0, schemeEnd); - const authority = path.slice(authorityStart, authorityEnd); - if (scheme === "file" && (authority === "" || authority === "localhost") && - isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) { - const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2); - if (volumeSeparatorEnd !== -1) { - if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodes.slash) { - // URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/" - return ~(volumeSeparatorEnd + 1); - } - if (volumeSeparatorEnd === path.length) { - // URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a" - // but not "file:///c:d" or "file:///c%3ad" - return ~volumeSeparatorEnd; - } +/** + * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). + * If the root is part of a URL, the twos-complement of the root length is returned. + */ +function getEncodedRootLength(path: string): number { + if (!path) return 0; + const ch0 = path.charCodeAt(0); + + // POSIX or UNC + if (ch0 === CharacterCodes.slash || ch0 === CharacterCodes.backslash) { + if (path.charCodeAt(1) !== ch0) return 1; // POSIX: "/" (or non-normalized "\") + + const p1 = path.indexOf(ch0 === CharacterCodes.slash ? directorySeparator : altDirectorySeparator, 2); + if (p1 < 0) return path.length; // UNC: "//server" or "\\server" + + return p1 + 1; // UNC: "//server/" or "\\server\" + } + + // DOS + if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodes.colon) { + const ch2 = path.charCodeAt(2); + if (ch2 === CharacterCodes.slash || ch2 === CharacterCodes.backslash) return 3; // DOS: "c:/" or "c:\" + if (path.length === 2) return 2; // DOS: "c:" (but not "c:d") + } + + // URL + const schemeEnd = path.indexOf(urlSchemeSeparator); + if (schemeEnd !== -1) { + const authorityStart = schemeEnd + urlSchemeSeparator.length; + const authorityEnd = path.indexOf(directorySeparator, authorityStart); + if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path" + // For local "file" URLs, include the leading DOS volume (if present). + // Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a + // special case interpreted as "the machine from which the URL is being interpreted". + const scheme = path.slice(0, schemeEnd); + const authority = path.slice(authorityStart, authorityEnd); + if (scheme === "file" && (authority === "" || authority === "localhost") && + isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) { + const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2); + if (volumeSeparatorEnd !== -1) { + if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodes.slash) { + // URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/" + return ~(volumeSeparatorEnd + 1); + } + if (volumeSeparatorEnd === path.length) { + // URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a" + // but not "file:///c:d" or "file:///c%3ad" + return ~volumeSeparatorEnd; } } - return ~(authorityEnd + 1); // URL: "file://server/", "http://server/" } - return ~path.length; // URL: "file://server", "http://server" + return ~(authorityEnd + 1); // URL: "file://server/", "http://server/" } - - // relative - return 0; + return ~path.length; // URL: "file://server", "http://server" } - /** - * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). - * - * For example: - * ```ts - * getRootLength("a") === 0 // "" - * getRootLength("/") === 1 // "/" - * getRootLength("c:") === 2 // "c:" - * getRootLength("c:d") === 0 // "" - * getRootLength("c:/") === 3 // "c:/" - * getRootLength("c:\\") === 3 // "c:\\" - * getRootLength("//server") === 7 // "//server" - * getRootLength("//server/share") === 8 // "//server/" - * getRootLength("\\\\server") === 7 // "\\\\server" - * getRootLength("\\\\server\\share") === 8 // "\\\\server\\" - * getRootLength("file:///path") === 8 // "file:///" - * getRootLength("file:///c:") === 10 // "file:///c:" - * getRootLength("file:///c:d") === 8 // "file:///" - * getRootLength("file:///c:/path") === 11 // "file:///c:/" - * getRootLength("file://server") === 13 // "file://server" - * getRootLength("file://server/path") === 14 // "file://server/" - * getRootLength("http://server") === 13 // "http://server" - * getRootLength("http://server/path") === 14 // "http://server/" - * ``` - */ - export function getRootLength(path: string) { - const rootLength = getEncodedRootLength(path); - return rootLength < 0 ? ~rootLength : rootLength; - } + // relative + return 0; +} - /** - * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` - * except that we support URLs as well. - * - * ```ts - * // POSIX - * getDirectoryPath("/path/to/file.ext") === "/path/to" - * getDirectoryPath("/path/to/") === "/path" - * getDirectoryPath("/") === "/" - * // DOS - * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" - * getDirectoryPath("c:/path/to/") === "c:/path" - * getDirectoryPath("c:/") === "c:/" - * getDirectoryPath("c:") === "c:" - * // URL - * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" - * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" - * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" - * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" - * ``` - */ - export function getDirectoryPath(path: Path): Path; - /** - * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` - * except that we support URLs as well. - * - * ```ts - * // POSIX - * getDirectoryPath("/path/to/file.ext") === "/path/to" - * getDirectoryPath("/path/to/") === "/path" - * getDirectoryPath("/") === "/" - * // DOS - * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" - * getDirectoryPath("c:/path/to/") === "c:/path" - * getDirectoryPath("c:/") === "c:/" - * getDirectoryPath("c:") === "c:" - * // URL - * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" - * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" - * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" - * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" - * getDirectoryPath("file://server/path/to/file.ext") === "file://server/path/to" - * getDirectoryPath("file://server/path/to") === "file://server/path" - * getDirectoryPath("file://server/") === "file://server/" - * getDirectoryPath("file://server") === "file://server" - * getDirectoryPath("file:///path/to/file.ext") === "file:///path/to" - * getDirectoryPath("file:///path/to") === "file:///path" - * getDirectoryPath("file:///") === "file:///" - * getDirectoryPath("file://") === "file://" - * ``` - */ - export function getDirectoryPath(path: string): string; - export function getDirectoryPath(path: string): string { - path = normalizeSlashes(path); - - // If the path provided is itself the root, then return it. - const rootLength = getRootLength(path); - if (rootLength === path.length) return path; - - // return the leading portion of the path up to the last (non-terminal) directory separator - // but not including any trailing directory separator. - path = removeTrailingDirectorySeparator(path); - return path.slice(0, Math.max(rootLength, path.lastIndexOf(directorySeparator))); - } +/** + * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files"). + * + * For example: + * ```ts + * getRootLength("a") === 0 // "" + * getRootLength("/") === 1 // "/" + * getRootLength("c:") === 2 // "c:" + * getRootLength("c:d") === 0 // "" + * getRootLength("c:/") === 3 // "c:/" + * getRootLength("c:\\") === 3 // "c:\\" + * getRootLength("//server") === 7 // "//server" + * getRootLength("//server/share") === 8 // "//server/" + * getRootLength("\\\\server") === 7 // "\\\\server" + * getRootLength("\\\\server\\share") === 8 // "\\\\server\\" + * getRootLength("file:///path") === 8 // "file:///" + * getRootLength("file:///c:") === 10 // "file:///c:" + * getRootLength("file:///c:d") === 8 // "file:///" + * getRootLength("file:///c:/path") === 11 // "file:///c:/" + * getRootLength("file://server") === 13 // "file://server" + * getRootLength("file://server/path") === 14 // "file://server/" + * getRootLength("http://server") === 13 // "http://server" + * getRootLength("http://server/path") === 14 // "http://server/" + * ``` + * + * @internal + */ +export function getRootLength(path: string) { + const rootLength = getEncodedRootLength(path); + return rootLength < 0 ? ~rootLength : rootLength; +} - /** - * Returns the path except for its containing directory name. - * Semantics align with NodeJS's `path.basename` except that we support URL's as well. - * - * ```ts - * // POSIX - * getBaseFileName("/path/to/file.ext") === "file.ext" - * getBaseFileName("/path/to/") === "to" - * getBaseFileName("/") === "" - * // DOS - * getBaseFileName("c:/path/to/file.ext") === "file.ext" - * getBaseFileName("c:/path/to/") === "to" - * getBaseFileName("c:/") === "" - * getBaseFileName("c:") === "" - * // URL - * getBaseFileName("http://typescriptlang.org/path/to/file.ext") === "file.ext" - * getBaseFileName("http://typescriptlang.org/path/to/") === "to" - * getBaseFileName("http://typescriptlang.org/") === "" - * getBaseFileName("http://typescriptlang.org") === "" - * getBaseFileName("file://server/path/to/file.ext") === "file.ext" - * getBaseFileName("file://server/path/to/") === "to" - * getBaseFileName("file://server/") === "" - * getBaseFileName("file://server") === "" - * getBaseFileName("file:///path/to/file.ext") === "file.ext" - * getBaseFileName("file:///path/to/") === "to" - * getBaseFileName("file:///") === "" - * getBaseFileName("file://") === "" - * ``` - */ - export function getBaseFileName(path: string): string; - /** - * Gets the portion of a path following the last (non-terminal) separator (`/`). - * Semantics align with NodeJS's `path.basename` except that we support URL's as well. - * If the base name has any one of the provided extensions, it is removed. - * - * ```ts - * getBaseFileName("/path/to/file.ext", ".ext", true) === "file" - * getBaseFileName("/path/to/file.js", ".ext", true) === "file.js" - * getBaseFileName("/path/to/file.js", [".ext", ".js"], true) === "file" - * getBaseFileName("/path/to/file.ext", ".EXT", false) === "file.ext" - * ``` - */ - export function getBaseFileName(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; - export function getBaseFileName(path: string, extensions?: string | readonly string[], ignoreCase?: boolean) { - path = normalizeSlashes(path); - - // if the path provided is itself the root, then it has not file name. - const rootLength = getRootLength(path); - if (rootLength === path.length) return ""; - - // return the trailing portion of the path starting after the last (non-terminal) directory - // separator but not including any trailing directory separator. - path = removeTrailingDirectorySeparator(path); - const name = path.slice(Math.max(getRootLength(path), path.lastIndexOf(directorySeparator) + 1)); - const extension = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(name, extensions, ignoreCase) : undefined; - return extension ? name.slice(0, name.length - extension.length) : name; - } +/** + * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` + * except that we support URLs as well. + * + * ```ts + * // POSIX + * getDirectoryPath("/path/to/file.ext") === "/path/to" + * getDirectoryPath("/path/to/") === "/path" + * getDirectoryPath("/") === "/" + * // DOS + * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" + * getDirectoryPath("c:/path/to/") === "c:/path" + * getDirectoryPath("c:/") === "c:/" + * getDirectoryPath("c:") === "c:" + * // URL + * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" + * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" + * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" + * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" + * ``` + * + * @internal + */ +export function getDirectoryPath(path: Path): Path; +/** + * Returns the path except for its basename. Semantics align with NodeJS's `path.dirname` + * except that we support URLs as well. + * + * ```ts + * // POSIX + * getDirectoryPath("/path/to/file.ext") === "/path/to" + * getDirectoryPath("/path/to/") === "/path" + * getDirectoryPath("/") === "/" + * // DOS + * getDirectoryPath("c:/path/to/file.ext") === "c:/path/to" + * getDirectoryPath("c:/path/to/") === "c:/path" + * getDirectoryPath("c:/") === "c:/" + * getDirectoryPath("c:") === "c:" + * // URL + * getDirectoryPath("http://typescriptlang.org/path/to/file.ext") === "http://typescriptlang.org/path/to" + * getDirectoryPath("http://typescriptlang.org/path/to") === "http://typescriptlang.org/path" + * getDirectoryPath("http://typescriptlang.org/") === "http://typescriptlang.org/" + * getDirectoryPath("http://typescriptlang.org") === "http://typescriptlang.org" + * getDirectoryPath("file://server/path/to/file.ext") === "file://server/path/to" + * getDirectoryPath("file://server/path/to") === "file://server/path" + * getDirectoryPath("file://server/") === "file://server/" + * getDirectoryPath("file://server") === "file://server" + * getDirectoryPath("file:///path/to/file.ext") === "file:///path/to" + * getDirectoryPath("file:///path/to") === "file:///path" + * getDirectoryPath("file:///") === "file:///" + * getDirectoryPath("file://") === "file://" + * ``` + * + * @internal + */ +export function getDirectoryPath(path: string): string; +/** @internal */ +export function getDirectoryPath(path: string): string { + path = normalizeSlashes(path); + + // If the path provided is itself the root, then return it. + const rootLength = getRootLength(path); + if (rootLength === path.length) return path; + + // return the leading portion of the path up to the last (non-terminal) directory separator + // but not including any trailing directory separator. + path = removeTrailingDirectorySeparator(path); + return path.slice(0, Math.max(rootLength, path.lastIndexOf(directorySeparator))); +} - function tryGetExtensionFromPath(path: string, extension: string, stringEqualityComparer: (a: string, b: string) => boolean) { - if (!startsWith(extension, ".")) extension = "." + extension; - if (path.length >= extension.length && path.charCodeAt(path.length - extension.length) === CharacterCodes.dot) { - const pathExtension = path.slice(path.length - extension.length); - if (stringEqualityComparer(pathExtension, extension)) { - return pathExtension; - } - } - } +/** + * Returns the path except for its containing directory name. + * Semantics align with NodeJS's `path.basename` except that we support URL's as well. + * + * ```ts + * // POSIX + * getBaseFileName("/path/to/file.ext") === "file.ext" + * getBaseFileName("/path/to/") === "to" + * getBaseFileName("/") === "" + * // DOS + * getBaseFileName("c:/path/to/file.ext") === "file.ext" + * getBaseFileName("c:/path/to/") === "to" + * getBaseFileName("c:/") === "" + * getBaseFileName("c:") === "" + * // URL + * getBaseFileName("http://typescriptlang.org/path/to/file.ext") === "file.ext" + * getBaseFileName("http://typescriptlang.org/path/to/") === "to" + * getBaseFileName("http://typescriptlang.org/") === "" + * getBaseFileName("http://typescriptlang.org") === "" + * getBaseFileName("file://server/path/to/file.ext") === "file.ext" + * getBaseFileName("file://server/path/to/") === "to" + * getBaseFileName("file://server/") === "" + * getBaseFileName("file://server") === "" + * getBaseFileName("file:///path/to/file.ext") === "file.ext" + * getBaseFileName("file:///path/to/") === "to" + * getBaseFileName("file:///") === "" + * getBaseFileName("file://") === "" + * ``` + * + * @internal + */ +export function getBaseFileName(path: string): string; +/** + * Gets the portion of a path following the last (non-terminal) separator (`/`). + * Semantics align with NodeJS's `path.basename` except that we support URL's as well. + * If the base name has any one of the provided extensions, it is removed. + * + * ```ts + * getBaseFileName("/path/to/file.ext", ".ext", true) === "file" + * getBaseFileName("/path/to/file.js", ".ext", true) === "file.js" + * getBaseFileName("/path/to/file.js", [".ext", ".js"], true) === "file" + * getBaseFileName("/path/to/file.ext", ".EXT", false) === "file.ext" + * ``` + * + * @internal + */ +export function getBaseFileName(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; +/** @internal */ +export function getBaseFileName(path: string, extensions?: string | readonly string[], ignoreCase?: boolean) { + path = normalizeSlashes(path); + + // if the path provided is itself the root, then it has not file name. + const rootLength = getRootLength(path); + if (rootLength === path.length) return ""; + + // return the trailing portion of the path starting after the last (non-terminal) directory + // separator but not including any trailing directory separator. + path = removeTrailingDirectorySeparator(path); + const name = path.slice(Math.max(getRootLength(path), path.lastIndexOf(directorySeparator) + 1)); + const extension = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(name, extensions, ignoreCase) : undefined; + return extension ? name.slice(0, name.length - extension.length) : name; +} - function getAnyExtensionFromPathWorker(path: string, extensions: string | readonly string[], stringEqualityComparer: (a: string, b: string) => boolean) { - if (typeof extensions === "string") { - return tryGetExtensionFromPath(path, extensions, stringEqualityComparer) || ""; - } - for (const extension of extensions) { - const result = tryGetExtensionFromPath(path, extension, stringEqualityComparer); - if (result) return result; +function tryGetExtensionFromPath(path: string, extension: string, stringEqualityComparer: (a: string, b: string) => boolean) { + if (!startsWith(extension, ".")) extension = "." + extension; + if (path.length >= extension.length && path.charCodeAt(path.length - extension.length) === CharacterCodes.dot) { + const pathExtension = path.slice(path.length - extension.length); + if (stringEqualityComparer(pathExtension, extension)) { + return pathExtension; } - return ""; } +} - /** - * Gets the file extension for a path. - * - * ```ts - * getAnyExtensionFromPath("/path/to/file.ext") === ".ext" - * getAnyExtensionFromPath("/path/to/file.ext/") === ".ext" - * getAnyExtensionFromPath("/path/to/file") === "" - * getAnyExtensionFromPath("/path/to.ext/file") === "" - * ``` - */ - export function getAnyExtensionFromPath(path: string): string; - /** - * Gets the file extension for a path, provided it is one of the provided extensions. - * - * ```ts - * getAnyExtensionFromPath("/path/to/file.ext", ".ext", true) === ".ext" - * getAnyExtensionFromPath("/path/to/file.js", ".ext", true) === "" - * getAnyExtensionFromPath("/path/to/file.js", [".ext", ".js"], true) === ".js" - * getAnyExtensionFromPath("/path/to/file.ext", ".EXT", false) === "" - */ - export function getAnyExtensionFromPath(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; - export function getAnyExtensionFromPath(path: string, extensions?: string | readonly string[], ignoreCase?: boolean): string { - // Retrieves any string from the final "." onwards from a base file name. - // Unlike extensionFromPath, which throws an exception on unrecognized extensions. - if (extensions) { - return getAnyExtensionFromPathWorker(removeTrailingDirectorySeparator(path), extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive); - } - const baseFileName = getBaseFileName(path); - const extensionIndex = baseFileName.lastIndexOf("."); - if (extensionIndex >= 0) { - return baseFileName.substring(extensionIndex); - } - return ""; +function getAnyExtensionFromPathWorker(path: string, extensions: string | readonly string[], stringEqualityComparer: (a: string, b: string) => boolean) { + if (typeof extensions === "string") { + return tryGetExtensionFromPath(path, extensions, stringEqualityComparer) || ""; } - - function pathComponents(path: string, rootLength: number) { - const root = path.substring(0, rootLength); - const rest = path.substring(rootLength).split(directorySeparator); - if (rest.length && !lastOrUndefined(rest)) rest.pop(); - return [root, ...rest]; + for (const extension of extensions) { + const result = tryGetExtensionFromPath(path, extension, stringEqualityComparer); + if (result) return result; } + return ""; +} - /** - * Parse a path into an array containing a root component (at index 0) and zero or more path - * components (at indices > 0). The result is not normalized. - * If the path is relative, the root component is `""`. - * If the path is absolute, the root component includes the first path separator (`/`). - * - * ```ts - * // POSIX - * getPathComponents("/path/to/file.ext") === ["/", "path", "to", "file.ext"] - * getPathComponents("/path/to/") === ["/", "path", "to"] - * getPathComponents("/") === ["/"] - * // DOS - * getPathComponents("c:/path/to/file.ext") === ["c:/", "path", "to", "file.ext"] - * getPathComponents("c:/path/to/") === ["c:/", "path", "to"] - * getPathComponents("c:/") === ["c:/"] - * getPathComponents("c:") === ["c:"] - * // URL - * getPathComponents("http://typescriptlang.org/path/to/file.ext") === ["http://typescriptlang.org/", "path", "to", "file.ext"] - * getPathComponents("http://typescriptlang.org/path/to/") === ["http://typescriptlang.org/", "path", "to"] - * getPathComponents("http://typescriptlang.org/") === ["http://typescriptlang.org/"] - * getPathComponents("http://typescriptlang.org") === ["http://typescriptlang.org"] - * getPathComponents("file://server/path/to/file.ext") === ["file://server/", "path", "to", "file.ext"] - * getPathComponents("file://server/path/to/") === ["file://server/", "path", "to"] - * getPathComponents("file://server/") === ["file://server/"] - * getPathComponents("file://server") === ["file://server"] - * getPathComponents("file:///path/to/file.ext") === ["file:///", "path", "to", "file.ext"] - * getPathComponents("file:///path/to/") === ["file:///", "path", "to"] - * getPathComponents("file:///") === ["file:///"] - * getPathComponents("file://") === ["file://"] - */ - export function getPathComponents(path: string, currentDirectory = "") { - path = combinePaths(currentDirectory, path); - return pathComponents(path, getRootLength(path)); - } +/** + * Gets the file extension for a path. + * + * ```ts + * getAnyExtensionFromPath("/path/to/file.ext") === ".ext" + * getAnyExtensionFromPath("/path/to/file.ext/") === ".ext" + * getAnyExtensionFromPath("/path/to/file") === "" + * getAnyExtensionFromPath("/path/to.ext/file") === "" + * ``` + * + * @internal + */ +export function getAnyExtensionFromPath(path: string): string; +/** + * Gets the file extension for a path, provided it is one of the provided extensions. + * + * ```ts + * getAnyExtensionFromPath("/path/to/file.ext", ".ext", true) === ".ext" + * getAnyExtensionFromPath("/path/to/file.js", ".ext", true) === "" + * getAnyExtensionFromPath("/path/to/file.js", [".ext", ".js"], true) === ".js" + * getAnyExtensionFromPath("/path/to/file.ext", ".EXT", false) === "" + * + * @internal + */ +export function getAnyExtensionFromPath(path: string, extensions: string | readonly string[], ignoreCase: boolean): string; +/** @internal */ +export function getAnyExtensionFromPath(path: string, extensions?: string | readonly string[], ignoreCase?: boolean): string { + // Retrieves any string from the final "." onwards from a base file name. + // Unlike extensionFromPath, which throws an exception on unrecognized extensions. + if (extensions) { + return getAnyExtensionFromPathWorker(removeTrailingDirectorySeparator(path), extensions, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive); + } + const baseFileName = getBaseFileName(path); + const extensionIndex = baseFileName.lastIndexOf("."); + if (extensionIndex >= 0) { + return baseFileName.substring(extensionIndex); + } + return ""; +} - //// Path Formatting - - /** - * Formats a parsed path consisting of a root component (at index 0) and zero or more path - * segments (at indices > 0). - * - * ```ts - * getPathFromPathComponents(["/", "path", "to", "file.ext"]) === "/path/to/file.ext" - * ``` - */ - export function getPathFromPathComponents(pathComponents: readonly string[]) { - if (pathComponents.length === 0) return ""; - - const root = pathComponents[0] && ensureTrailingDirectorySeparator(pathComponents[0]); - return root + pathComponents.slice(1).join(directorySeparator); - } +function pathComponents(path: string, rootLength: number) { + const root = path.substring(0, rootLength); + const rest = path.substring(rootLength).split(directorySeparator); + if (rest.length && !lastOrUndefined(rest)) rest.pop(); + return [root, ...rest]; +} - //// Path Normalization +/** + * Parse a path into an array containing a root component (at index 0) and zero or more path + * components (at indices > 0). The result is not normalized. + * If the path is relative, the root component is `""`. + * If the path is absolute, the root component includes the first path separator (`/`). + * + * ```ts + * // POSIX + * getPathComponents("/path/to/file.ext") === ["/", "path", "to", "file.ext"] + * getPathComponents("/path/to/") === ["/", "path", "to"] + * getPathComponents("/") === ["/"] + * // DOS + * getPathComponents("c:/path/to/file.ext") === ["c:/", "path", "to", "file.ext"] + * getPathComponents("c:/path/to/") === ["c:/", "path", "to"] + * getPathComponents("c:/") === ["c:/"] + * getPathComponents("c:") === ["c:"] + * // URL + * getPathComponents("http://typescriptlang.org/path/to/file.ext") === ["http://typescriptlang.org/", "path", "to", "file.ext"] + * getPathComponents("http://typescriptlang.org/path/to/") === ["http://typescriptlang.org/", "path", "to"] + * getPathComponents("http://typescriptlang.org/") === ["http://typescriptlang.org/"] + * getPathComponents("http://typescriptlang.org") === ["http://typescriptlang.org"] + * getPathComponents("file://server/path/to/file.ext") === ["file://server/", "path", "to", "file.ext"] + * getPathComponents("file://server/path/to/") === ["file://server/", "path", "to"] + * getPathComponents("file://server/") === ["file://server/"] + * getPathComponents("file://server") === ["file://server"] + * getPathComponents("file:///path/to/file.ext") === ["file:///", "path", "to", "file.ext"] + * getPathComponents("file:///path/to/") === ["file:///", "path", "to"] + * getPathComponents("file:///") === ["file:///"] + * getPathComponents("file://") === ["file://"] + * ``` + * + * @internal + */ +export function getPathComponents(path: string, currentDirectory = "") { + path = combinePaths(currentDirectory, path); + return pathComponents(path, getRootLength(path)); +} - /** - * Normalize path separators, converting `\` into `/`. - */ - export function normalizeSlashes(path: string): string { - return path.indexOf("\\") !== -1 - ? path.replace(backslashRegExp, directorySeparator) - : path; - } +//// Path Formatting + +/** + * Formats a parsed path consisting of a root component (at index 0) and zero or more path + * segments (at indices > 0). + * + * ```ts + * getPathFromPathComponents(["/", "path", "to", "file.ext"]) === "/path/to/file.ext" + * ``` + * + * @internal + */ +export function getPathFromPathComponents(pathComponents: readonly string[]) { + if (pathComponents.length === 0) return ""; + + const root = pathComponents[0] && ensureTrailingDirectorySeparator(pathComponents[0]); + return root + pathComponents.slice(1).join(directorySeparator); +} - /** - * Reduce an array of path components to a more simplified path by navigating any - * `"."` or `".."` entries in the path. - */ - export function reducePathComponents(components: readonly string[]) { - if (!some(components)) return []; - const reduced = [components[0]]; - for (let i = 1; i < components.length; i++) { - const component = components[i]; - if (!component) continue; - if (component === ".") continue; - if (component === "..") { - if (reduced.length > 1) { - if (reduced[reduced.length - 1] !== "..") { - reduced.pop(); - continue; - } +//// Path Normalization + +/** + * Normalize path separators, converting `\` into `/`. + * + * @internal + */ +export function normalizeSlashes(path: string): string { + return path.indexOf("\\") !== -1 + ? path.replace(backslashRegExp, directorySeparator) + : path; +} + +/** + * Reduce an array of path components to a more simplified path by navigating any + * `"."` or `".."` entries in the path. + * + * @internal + */ +export function reducePathComponents(components: readonly string[]) { + if (!some(components)) return []; + const reduced = [components[0]]; + for (let i = 1; i < components.length; i++) { + const component = components[i]; + if (!component) continue; + if (component === ".") continue; + if (component === "..") { + if (reduced.length > 1) { + if (reduced[reduced.length - 1] !== "..") { + reduced.pop(); + continue; } - else if (reduced[0]) continue; } - reduced.push(component); + else if (reduced[0]) continue; } - return reduced; + reduced.push(component); } + return reduced; +} - /** - * Combines paths. If a path is absolute, it replaces any previous path. Relative paths are not simplified. - * - * ```ts - * // Non-rooted - * combinePaths("path", "to", "file.ext") === "path/to/file.ext" - * combinePaths("path", "dir", "..", "to", "file.ext") === "path/dir/../to/file.ext" - * // POSIX - * combinePaths("/path", "to", "file.ext") === "/path/to/file.ext" - * combinePaths("/path", "/to", "file.ext") === "/to/file.ext" - * // DOS - * combinePaths("c:/path", "to", "file.ext") === "c:/path/to/file.ext" - * combinePaths("c:/path", "c:/to", "file.ext") === "c:/to/file.ext" - * // URL - * combinePaths("file:///path", "to", "file.ext") === "file:///path/to/file.ext" - * combinePaths("file:///path", "file:///to", "file.ext") === "file:///to/file.ext" - * ``` - */ - export function combinePaths(path: string, ...paths: (string | undefined)[]): string { - if (path) path = normalizeSlashes(path); - for (let relativePath of paths) { - if (!relativePath) continue; - relativePath = normalizeSlashes(relativePath); - if (!path || getRootLength(relativePath) !== 0) { - path = relativePath; - } - else { - path = ensureTrailingDirectorySeparator(path) + relativePath; - } +/** + * Combines paths. If a path is absolute, it replaces any previous path. Relative paths are not simplified. + * + * ```ts + * // Non-rooted + * combinePaths("path", "to", "file.ext") === "path/to/file.ext" + * combinePaths("path", "dir", "..", "to", "file.ext") === "path/dir/../to/file.ext" + * // POSIX + * combinePaths("/path", "to", "file.ext") === "/path/to/file.ext" + * combinePaths("/path", "/to", "file.ext") === "/to/file.ext" + * // DOS + * combinePaths("c:/path", "to", "file.ext") === "c:/path/to/file.ext" + * combinePaths("c:/path", "c:/to", "file.ext") === "c:/to/file.ext" + * // URL + * combinePaths("file:///path", "to", "file.ext") === "file:///path/to/file.ext" + * combinePaths("file:///path", "file:///to", "file.ext") === "file:///to/file.ext" + * ``` + * + * @internal + */ +export function combinePaths(path: string, ...paths: (string | undefined)[]): string { + if (path) path = normalizeSlashes(path); + for (let relativePath of paths) { + if (!relativePath) continue; + relativePath = normalizeSlashes(relativePath); + if (!path || getRootLength(relativePath) !== 0) { + path = relativePath; + } + else { + path = ensureTrailingDirectorySeparator(path) + relativePath; } - return path; } + return path; +} - /** - * Combines and resolves paths. If a path is absolute, it replaces any previous path. Any - * `.` and `..` path components are resolved. Trailing directory separators are preserved. - * - * ```ts - * resolvePath("/path", "to", "file.ext") === "path/to/file.ext" - * resolvePath("/path", "to", "file.ext/") === "path/to/file.ext/" - * resolvePath("/path", "dir", "..", "to", "file.ext") === "path/to/file.ext" - * ``` - */ - export function resolvePath(path: string, ...paths: (string | undefined)[]): string { - return normalizePath(some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path)); - } +/** + * Combines and resolves paths. If a path is absolute, it replaces any previous path. Any + * `.` and `..` path components are resolved. Trailing directory separators are preserved. + * + * ```ts + * resolvePath("/path", "to", "file.ext") === "path/to/file.ext" + * resolvePath("/path", "to", "file.ext/") === "path/to/file.ext/" + * resolvePath("/path", "dir", "..", "to", "file.ext") === "path/to/file.ext" + * ``` + * + * @internal + */ +export function resolvePath(path: string, ...paths: (string | undefined)[]): string { + return normalizePath(some(paths) ? combinePaths(path, ...paths) : normalizeSlashes(path)); +} - /** - * Parse a path into an array containing a root component (at index 0) and zero or more path - * components (at indices > 0). The result is normalized. - * If the path is relative, the root component is `""`. - * If the path is absolute, the root component includes the first path separator (`/`). - * - * ```ts - * getNormalizedPathComponents("to/dir/../file.ext", "/path/") === ["/", "path", "to", "file.ext"] - * ``` - */ - export function getNormalizedPathComponents(path: string, currentDirectory: string | undefined) { - return reducePathComponents(getPathComponents(path, currentDirectory)); - } +/** + * Parse a path into an array containing a root component (at index 0) and zero or more path + * components (at indices > 0). The result is normalized. + * If the path is relative, the root component is `""`. + * If the path is absolute, the root component includes the first path separator (`/`). + * + * ```ts + * getNormalizedPathComponents("to/dir/../file.ext", "/path/") === ["/", "path", "to", "file.ext"] + * ``` + * + * @internal + */ +export function getNormalizedPathComponents(path: string, currentDirectory: string | undefined) { + return reducePathComponents(getPathComponents(path, currentDirectory)); +} - export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined) { - return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); - } +/** @internal */ +export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined) { + return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); +} - export function normalizePath(path: string): string { - path = normalizeSlashes(path); - // Most paths don't require normalization +/** @internal */ +export function normalizePath(path: string): string { + path = normalizeSlashes(path); + // Most paths don't require normalization + if (!relativePathSegmentRegExp.test(path)) { + return path; + } + // Some paths only require cleanup of `/./` or leading `./` + const simplified = path.replace(/\/\.\//g, "/").replace(/^\.\//, ""); + if (simplified !== path) { + path = simplified; if (!relativePathSegmentRegExp.test(path)) { return path; } - // Some paths only require cleanup of `/./` or leading `./` - const simplified = path.replace(/\/\.\//g, "/").replace(/^\.\//, ""); - if (simplified !== path) { - path = simplified; - if (!relativePathSegmentRegExp.test(path)) { - return path; - } - } - // Other paths require full normalization - const normalized = getPathFromPathComponents(reducePathComponents(getPathComponents(path))); - return normalized && hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalized) : normalized; } + // Other paths require full normalization + const normalized = getPathFromPathComponents(reducePathComponents(getPathComponents(path))); + return normalized && hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalized) : normalized; +} - function getPathWithoutRoot(pathComponents: readonly string[]) { - if (pathComponents.length === 0) return ""; - return pathComponents.slice(1).join(directorySeparator); - } +function getPathWithoutRoot(pathComponents: readonly string[]) { + if (pathComponents.length === 0) return ""; + return pathComponents.slice(1).join(directorySeparator); +} - export function getNormalizedAbsolutePathWithoutRoot(fileName: string, currentDirectory: string | undefined) { - return getPathWithoutRoot(getNormalizedPathComponents(fileName, currentDirectory)); - } +/** @internal */ +export function getNormalizedAbsolutePathWithoutRoot(fileName: string, currentDirectory: string | undefined) { + return getPathWithoutRoot(getNormalizedPathComponents(fileName, currentDirectory)); +} - export function toPath(fileName: string, basePath: string | undefined, getCanonicalFileName: (path: string) => string): Path { - const nonCanonicalizedPath = isRootedDiskPath(fileName) - ? normalizePath(fileName) - : getNormalizedAbsolutePath(fileName, basePath); - return getCanonicalFileName(nonCanonicalizedPath) as Path; - } +/** @internal */ +export function toPath(fileName: string, basePath: string | undefined, getCanonicalFileName: (path: string) => string): Path { + const nonCanonicalizedPath = isRootedDiskPath(fileName) + ? normalizePath(fileName) + : getNormalizedAbsolutePath(fileName, basePath); + return getCanonicalFileName(nonCanonicalizedPath) as Path; +} - //// Path Mutation - - /** - * Removes a trailing directory separator from a path, if it does not already have one. - * - * ```ts - * removeTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext" - * removeTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext" - * ``` - */ - export function removeTrailingDirectorySeparator(path: Path): Path; - export function removeTrailingDirectorySeparator(path: string): string; - export function removeTrailingDirectorySeparator(path: string) { - if (hasTrailingDirectorySeparator(path)) { - return path.substr(0, path.length - 1); - } +//// Path Mutation + +/** + * Removes a trailing directory separator from a path, if it does not already have one. + * + * ```ts + * removeTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext" + * removeTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext" + * ``` + * + * @internal + */ +export function removeTrailingDirectorySeparator(path: Path): Path; +/** @internal */ +export function removeTrailingDirectorySeparator(path: string): string; +/** @internal */ +export function removeTrailingDirectorySeparator(path: string) { + if (hasTrailingDirectorySeparator(path)) { + return path.substr(0, path.length - 1); + } + + return path; +} - return path; - } +/** + * Adds a trailing directory separator to a path, if it does not already have one. + * + * ```ts + * ensureTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext/" + * ensureTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext/" + * ``` + * + * @internal + */ +export function ensureTrailingDirectorySeparator(path: Path): Path; +/** @internal */ +export function ensureTrailingDirectorySeparator(path: string): string; +/** @internal */ +export function ensureTrailingDirectorySeparator(path: string) { + if (!hasTrailingDirectorySeparator(path)) { + return path + directorySeparator; + } + + return path; +} - /** - * Adds a trailing directory separator to a path, if it does not already have one. - * - * ```ts - * ensureTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext/" - * ensureTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext/" - * ``` - */ - export function ensureTrailingDirectorySeparator(path: Path): Path; - export function ensureTrailingDirectorySeparator(path: string): string; - export function ensureTrailingDirectorySeparator(path: string) { - if (!hasTrailingDirectorySeparator(path)) { - return path + directorySeparator; - } +/** + * Ensures a path is either absolute (prefixed with `/` or `c:`) or dot-relative (prefixed + * with `./` or `../`) so as not to be confused with an unprefixed module name. + * + * ```ts + * ensurePathIsNonModuleName("/path/to/file.ext") === "/path/to/file.ext" + * ensurePathIsNonModuleName("./path/to/file.ext") === "./path/to/file.ext" + * ensurePathIsNonModuleName("../path/to/file.ext") === "../path/to/file.ext" + * ensurePathIsNonModuleName("path/to/file.ext") === "./path/to/file.ext" + * ``` + * + * @internal + */ +export function ensurePathIsNonModuleName(path: string): string { + return !pathIsAbsolute(path) && !pathIsRelative(path) ? "./" + path : path; +} - return path; - } +/** + * Changes the extension of a path to the provided extension. + * + * ```ts + * changeAnyExtension("/path/to/file.ext", ".js") === "/path/to/file.js" + * ``` + * + * @internal + */ +export function changeAnyExtension(path: string, ext: string): string; +/** + * Changes the extension of a path to the provided extension if it has one of the provided extensions. + * + * ```ts + * changeAnyExtension("/path/to/file.ext", ".js", ".ext") === "/path/to/file.js" + * changeAnyExtension("/path/to/file.ext", ".js", ".ts") === "/path/to/file.ext" + * changeAnyExtension("/path/to/file.ext", ".js", [".ext", ".ts"]) === "/path/to/file.js" + * ``` + * + * @internal + */ +export function changeAnyExtension(path: string, ext: string, extensions: string | readonly string[], ignoreCase: boolean): string; +/** @internal */ +export function changeAnyExtension(path: string, ext: string, extensions?: string | readonly string[], ignoreCase?: boolean) { + const pathext = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path); + return pathext ? path.slice(0, path.length - pathext.length) + (startsWith(ext, ".") ? ext : "." + ext) : path; +} - /** - * Ensures a path is either absolute (prefixed with `/` or `c:`) or dot-relative (prefixed - * with `./` or `../`) so as not to be confused with an unprefixed module name. - * - * ```ts - * ensurePathIsNonModuleName("/path/to/file.ext") === "/path/to/file.ext" - * ensurePathIsNonModuleName("./path/to/file.ext") === "./path/to/file.ext" - * ensurePathIsNonModuleName("../path/to/file.ext") === "../path/to/file.ext" - * ensurePathIsNonModuleName("path/to/file.ext") === "./path/to/file.ext" - * ``` - */ - export function ensurePathIsNonModuleName(path: string): string { - return !pathIsAbsolute(path) && !pathIsRelative(path) ? "./" + path : path; - } +//// Path Comparisons - /** - * Changes the extension of a path to the provided extension. - * - * ```ts - * changeAnyExtension("/path/to/file.ext", ".js") === "/path/to/file.js" - * ``` - */ - export function changeAnyExtension(path: string, ext: string): string; - /** - * Changes the extension of a path to the provided extension if it has one of the provided extensions. - * - * ```ts - * changeAnyExtension("/path/to/file.ext", ".js", ".ext") === "/path/to/file.js" - * changeAnyExtension("/path/to/file.ext", ".js", ".ts") === "/path/to/file.ext" - * changeAnyExtension("/path/to/file.ext", ".js", [".ext", ".ts"]) === "/path/to/file.js" - * ``` - */ - export function changeAnyExtension(path: string, ext: string, extensions: string | readonly string[], ignoreCase: boolean): string; - export function changeAnyExtension(path: string, ext: string, extensions?: string | readonly string[], ignoreCase?: boolean) { - const pathext = extensions !== undefined && ignoreCase !== undefined ? getAnyExtensionFromPath(path, extensions, ignoreCase) : getAnyExtensionFromPath(path); - return pathext ? path.slice(0, path.length - pathext.length) + (startsWith(ext, ".") ? ext : "." + ext) : path; - } +// check path for these segments: '', '.'. '..' +const relativePathSegmentRegExp = /(?:\/\/)|(?:^|\/)\.\.?(?:$|\/)/; - //// Path Comparisons +function comparePathsWorker(a: string, b: string, componentComparer: (a: string, b: string) => Comparison) { + if (a === b) return Comparison.EqualTo; + if (a === undefined) return Comparison.LessThan; + if (b === undefined) return Comparison.GreaterThan; - // check path for these segments: '', '.'. '..' - const relativePathSegmentRegExp = /(?:\/\/)|(?:^|\/)\.\.?(?:$|\/)/; + // NOTE: Performance optimization - shortcut if the root segments differ as there would be no + // need to perform path reduction. + const aRoot = a.substring(0, getRootLength(a)); + const bRoot = b.substring(0, getRootLength(b)); + const result = compareStringsCaseInsensitive(aRoot, bRoot); + if (result !== Comparison.EqualTo) { + return result; + } - function comparePathsWorker(a: string, b: string, componentComparer: (a: string, b: string) => Comparison) { - if (a === b) return Comparison.EqualTo; - if (a === undefined) return Comparison.LessThan; - if (b === undefined) return Comparison.GreaterThan; + // NOTE: Performance optimization - shortcut if there are no relative path segments in + // the non-root portion of the path + const aRest = a.substring(aRoot.length); + const bRest = b.substring(bRoot.length); + if (!relativePathSegmentRegExp.test(aRest) && !relativePathSegmentRegExp.test(bRest)) { + return componentComparer(aRest, bRest); + } - // NOTE: Performance optimization - shortcut if the root segments differ as there would be no - // need to perform path reduction. - const aRoot = a.substring(0, getRootLength(a)); - const bRoot = b.substring(0, getRootLength(b)); - const result = compareStringsCaseInsensitive(aRoot, bRoot); + // The path contains a relative path segment. Normalize the paths and perform a slower component + // by component comparison. + const aComponents = reducePathComponents(getPathComponents(a)); + const bComponents = reducePathComponents(getPathComponents(b)); + const sharedLength = Math.min(aComponents.length, bComponents.length); + for (let i = 1; i < sharedLength; i++) { + const result = componentComparer(aComponents[i], bComponents[i]); if (result !== Comparison.EqualTo) { return result; } - - // NOTE: Performance optimization - shortcut if there are no relative path segments in - // the non-root portion of the path - const aRest = a.substring(aRoot.length); - const bRest = b.substring(bRoot.length); - if (!relativePathSegmentRegExp.test(aRest) && !relativePathSegmentRegExp.test(bRest)) { - return componentComparer(aRest, bRest); - } - - // The path contains a relative path segment. Normalize the paths and perform a slower component - // by component comparison. - const aComponents = reducePathComponents(getPathComponents(a)); - const bComponents = reducePathComponents(getPathComponents(b)); - const sharedLength = Math.min(aComponents.length, bComponents.length); - for (let i = 1; i < sharedLength; i++) { - const result = componentComparer(aComponents[i], bComponents[i]); - if (result !== Comparison.EqualTo) { - return result; - } - } - return compareValues(aComponents.length, bComponents.length); } + return compareValues(aComponents.length, bComponents.length); +} - /** - * Performs a case-sensitive comparison of two paths. Path roots are always compared case-insensitively. - */ - export function comparePathsCaseSensitive(a: string, b: string) { - return comparePathsWorker(a, b, compareStringsCaseSensitive); - } +/** + * Performs a case-sensitive comparison of two paths. Path roots are always compared case-insensitively. + * + * @internal + */ +export function comparePathsCaseSensitive(a: string, b: string) { + return comparePathsWorker(a, b, compareStringsCaseSensitive); +} - /** - * Performs a case-insensitive comparison of two paths. - */ - export function comparePathsCaseInsensitive(a: string, b: string) { - return comparePathsWorker(a, b, compareStringsCaseInsensitive); - } +/** + * Performs a case-insensitive comparison of two paths. + * + * @internal + */ +export function comparePathsCaseInsensitive(a: string, b: string) { + return comparePathsWorker(a, b, compareStringsCaseInsensitive); +} - /** - * Compare two paths using the provided case sensitivity. - */ - export function comparePaths(a: string, b: string, ignoreCase?: boolean): Comparison; - export function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean): Comparison; - export function comparePaths(a: string, b: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { - if (typeof currentDirectory === "string") { - a = combinePaths(currentDirectory, a); - b = combinePaths(currentDirectory, b); - } - else if (typeof currentDirectory === "boolean") { - ignoreCase = currentDirectory; - } - return comparePathsWorker(a, b, getStringComparer(ignoreCase)); +/** + * Compare two paths using the provided case sensitivity. + * + * @internal + */ +export function comparePaths(a: string, b: string, ignoreCase?: boolean): Comparison; +/** @internal */ +export function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean): Comparison; +/** @internal */ +export function comparePaths(a: string, b: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { + if (typeof currentDirectory === "string") { + a = combinePaths(currentDirectory, a); + b = combinePaths(currentDirectory, b); + } + else if (typeof currentDirectory === "boolean") { + ignoreCase = currentDirectory; + } + return comparePathsWorker(a, b, getStringComparer(ignoreCase)); +} + +/** + * Determines whether a `parent` path contains a `child` path using the provide case sensitivity. + * + * @internal + */ +export function containsPath(parent: string, child: string, ignoreCase?: boolean): boolean; +/** @internal */ +export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean): boolean; +/** @internal */ +export function containsPath(parent: string, child: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { + if (typeof currentDirectory === "string") { + parent = combinePaths(currentDirectory, parent); + child = combinePaths(currentDirectory, child); + } + else if (typeof currentDirectory === "boolean") { + ignoreCase = currentDirectory; + } + if (parent === undefined || child === undefined) return false; + if (parent === child) return true; + const parentComponents = reducePathComponents(getPathComponents(parent)); + const childComponents = reducePathComponents(getPathComponents(child)); + if (childComponents.length < parentComponents.length) { + return false; } - /** - * Determines whether a `parent` path contains a `child` path using the provide case sensitivity. - */ - export function containsPath(parent: string, child: string, ignoreCase?: boolean): boolean; - export function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean): boolean; - export function containsPath(parent: string, child: string, currentDirectory?: string | boolean, ignoreCase?: boolean) { - if (typeof currentDirectory === "string") { - parent = combinePaths(currentDirectory, parent); - child = combinePaths(currentDirectory, child); - } - else if (typeof currentDirectory === "boolean") { - ignoreCase = currentDirectory; - } - if (parent === undefined || child === undefined) return false; - if (parent === child) return true; - const parentComponents = reducePathComponents(getPathComponents(parent)); - const childComponents = reducePathComponents(getPathComponents(child)); - if (childComponents.length < parentComponents.length) { + const componentEqualityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive; + for (let i = 0; i < parentComponents.length; i++) { + const equalityComparer = i === 0 ? equateStringsCaseInsensitive : componentEqualityComparer; + if (!equalityComparer(parentComponents[i], childComponents[i])) { return false; } - - const componentEqualityComparer = ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive; - for (let i = 0; i < parentComponents.length; i++) { - const equalityComparer = i === 0 ? equateStringsCaseInsensitive : componentEqualityComparer; - if (!equalityComparer(parentComponents[i], childComponents[i])) { - return false; - } - } - - return true; } - /** - * Determines whether `fileName` starts with the specified `directoryName` using the provided path canonicalization callback. - * Comparison is case-sensitive between the canonical paths. - * - * Use `containsPath` if file names are not already reduced and absolute. - */ - export function startsWithDirectory(fileName: string, directoryName: string, getCanonicalFileName: GetCanonicalFileName): boolean { - const canonicalFileName = getCanonicalFileName(fileName); - const canonicalDirectoryName = getCanonicalFileName(directoryName); - return startsWith(canonicalFileName, canonicalDirectoryName + "/") || startsWith(canonicalFileName, canonicalDirectoryName + "\\"); - } - - //// Relative Paths + return true; +} - export function getPathComponentsRelativeTo(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean, getCanonicalFileName: GetCanonicalFileName) { - const fromComponents = reducePathComponents(getPathComponents(from)); - const toComponents = reducePathComponents(getPathComponents(to)); +/** + * Determines whether `fileName` starts with the specified `directoryName` using the provided path canonicalization callback. + * Comparison is case-sensitive between the canonical paths. + * + * Use `containsPath` if file names are not already reduced and absolute. + * + * @internal + */ +export function startsWithDirectory(fileName: string, directoryName: string, getCanonicalFileName: GetCanonicalFileName): boolean { + const canonicalFileName = getCanonicalFileName(fileName); + const canonicalDirectoryName = getCanonicalFileName(directoryName); + return startsWith(canonicalFileName, canonicalDirectoryName + "/") || startsWith(canonicalFileName, canonicalDirectoryName + "\\"); +} - let start: number; - for (start = 0; start < fromComponents.length && start < toComponents.length; start++) { - const fromComponent = getCanonicalFileName(fromComponents[start]); - const toComponent = getCanonicalFileName(toComponents[start]); - const comparer = start === 0 ? equateStringsCaseInsensitive : stringEqualityComparer; - if (!comparer(fromComponent, toComponent)) break; - } +//// Relative Paths - if (start === 0) { - return toComponents; - } +/** @internal */ +export function getPathComponentsRelativeTo(from: string, to: string, stringEqualityComparer: (a: string, b: string) => boolean, getCanonicalFileName: GetCanonicalFileName) { + const fromComponents = reducePathComponents(getPathComponents(from)); + const toComponents = reducePathComponents(getPathComponents(to)); - const components = toComponents.slice(start); - const relative: string[] = []; - for (; start < fromComponents.length; start++) { - relative.push(".."); - } - return ["", ...relative, ...components]; + let start: number; + for (start = 0; start < fromComponents.length && start < toComponents.length; start++) { + const fromComponent = getCanonicalFileName(fromComponents[start]); + const toComponent = getCanonicalFileName(toComponents[start]); + const comparer = start === 0 ? equateStringsCaseInsensitive : stringEqualityComparer; + if (!comparer(fromComponent, toComponent)) break; } - /** - * Gets a relative path that can be used to traverse between `from` and `to`. - */ - export function getRelativePathFromDirectory(from: string, to: string, ignoreCase: boolean): string; - /** - * Gets a relative path that can be used to traverse between `from` and `to`. - */ - export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileName: GetCanonicalFileName): string; // eslint-disable-line @typescript-eslint/unified-signatures - export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean) { - Debug.assert((getRootLength(fromDirectory) > 0) === (getRootLength(to) > 0), "Paths must either both be absolute or both be relative"); - const getCanonicalFileName = typeof getCanonicalFileNameOrIgnoreCase === "function" ? getCanonicalFileNameOrIgnoreCase : identity; - const ignoreCase = typeof getCanonicalFileNameOrIgnoreCase === "boolean" ? getCanonicalFileNameOrIgnoreCase : false; - const pathComponents = getPathComponentsRelativeTo(fromDirectory, to, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive, getCanonicalFileName); - return getPathFromPathComponents(pathComponents); + if (start === 0) { + return toComponents; } - export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string { - return !isRootedDiskPath(absoluteOrRelativePath) - ? absoluteOrRelativePath - : getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); + const components = toComponents.slice(start); + const relative: string[] = []; + for (; start < fromComponents.length; start++) { + relative.push(".."); } + return ["", ...relative, ...components]; +} - export function getRelativePathFromFile(from: string, to: string, getCanonicalFileName: GetCanonicalFileName) { - return ensurePathIsNonModuleName(getRelativePathFromDirectory(getDirectoryPath(from), to, getCanonicalFileName)); - } +/** + * Gets a relative path that can be used to traverse between `from` and `to`. + * + * @internal + */ +export function getRelativePathFromDirectory(from: string, to: string, ignoreCase: boolean): string; +/** + * Gets a relative path that can be used to traverse between `from` and `to`. + * + * @internal + */ +export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileName: GetCanonicalFileName): string; // eslint-disable-line @typescript-eslint/unified-signatures +/** @internal */ +export function getRelativePathFromDirectory(fromDirectory: string, to: string, getCanonicalFileNameOrIgnoreCase: GetCanonicalFileName | boolean) { + Debug.assert((getRootLength(fromDirectory) > 0) === (getRootLength(to) > 0), "Paths must either both be absolute or both be relative"); + const getCanonicalFileName = typeof getCanonicalFileNameOrIgnoreCase === "function" ? getCanonicalFileNameOrIgnoreCase : identity; + const ignoreCase = typeof getCanonicalFileNameOrIgnoreCase === "boolean" ? getCanonicalFileNameOrIgnoreCase : false; + const pathComponents = getPathComponentsRelativeTo(fromDirectory, to, ignoreCase ? equateStringsCaseInsensitive : equateStringsCaseSensitive, getCanonicalFileName); + return getPathFromPathComponents(pathComponents); +} - export function getRelativePathToDirectoryOrUrl(directoryPathOrUrl: string, relativeOrAbsolutePath: string, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, isAbsolutePathAnUrl: boolean) { - const pathComponents = getPathComponentsRelativeTo( - resolvePath(currentDirectory, directoryPathOrUrl), - resolvePath(currentDirectory, relativeOrAbsolutePath), - equateStringsCaseSensitive, - getCanonicalFileName - ); - - const firstComponent = pathComponents[0]; - if (isAbsolutePathAnUrl && isRootedDiskPath(firstComponent)) { - const prefix = firstComponent.charAt(0) === directorySeparator ? "file://" : "file:///"; - pathComponents[0] = prefix + firstComponent; - } +/** @internal */ +export function convertToRelativePath(absoluteOrRelativePath: string, basePath: string, getCanonicalFileName: (path: string) => string): string { + return !isRootedDiskPath(absoluteOrRelativePath) + ? absoluteOrRelativePath + : getRelativePathToDirectoryOrUrl(basePath, absoluteOrRelativePath, basePath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); +} + +/** @internal */ +export function getRelativePathFromFile(from: string, to: string, getCanonicalFileName: GetCanonicalFileName) { + return ensurePathIsNonModuleName(getRelativePathFromDirectory(getDirectoryPath(from), to, getCanonicalFileName)); +} - return getPathFromPathComponents(pathComponents); +/** @internal */ +export function getRelativePathToDirectoryOrUrl(directoryPathOrUrl: string, relativeOrAbsolutePath: string, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, isAbsolutePathAnUrl: boolean) { + const pathComponents = getPathComponentsRelativeTo( + resolvePath(currentDirectory, directoryPathOrUrl), + resolvePath(currentDirectory, relativeOrAbsolutePath), + equateStringsCaseSensitive, + getCanonicalFileName + ); + + const firstComponent = pathComponents[0]; + if (isAbsolutePathAnUrl && isRootedDiskPath(firstComponent)) { + const prefix = firstComponent.charAt(0) === directorySeparator ? "file://" : "file:///"; + pathComponents[0] = prefix + firstComponent; } - //// Path Traversal - - /** - * Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. - */ - export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined; - export function forEachAncestorDirectory(directory: string, callback: (directory: string) => T | undefined): T | undefined; - export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined { - while (true) { - const result = callback(directory); - if (result !== undefined) { - return result; - } + return getPathFromPathComponents(pathComponents); +} - const parentPath = getDirectoryPath(directory); - if (parentPath === directory) { - return undefined; - } +//// Path Traversal + +/** + * Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. + * + * @internal + */ +export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined; +/** @internal */ +export function forEachAncestorDirectory(directory: string, callback: (directory: string) => T | undefined): T | undefined; +/** @internal */ +export function forEachAncestorDirectory(directory: Path, callback: (directory: Path) => T | undefined): T | undefined { + while (true) { + const result = callback(directory); + if (result !== undefined) { + return result; + } - directory = parentPath; + const parentPath = getDirectoryPath(directory); + if (parentPath === directory) { + return undefined; } - } - export function isNodeModulesDirectory(dirPath: Path) { - return endsWith(dirPath, "/node_modules"); + directory = parentPath; } } + +/** @internal */ +export function isNodeModulesDirectory(dirPath: Path) { + return endsWith(dirPath, "/node_modules"); +} diff --git a/src/compiler/perfLogger.ts b/src/compiler/perfLogger.ts index f05e8a3bd1053..fd8a559c753c3 100644 --- a/src/compiler/perfLogger.ts +++ b/src/compiler/perfLogger.ts @@ -1,43 +1,69 @@ -/* @internal */ -namespace ts { - type PerfLogger = typeof import("@microsoft/typescript-etw"); - const nullLogger: PerfLogger = { - logEvent: noop, - logErrEvent: noop, - logPerfEvent: noop, - logInfoEvent: noop, - logStartCommand: noop, - logStopCommand: noop, - logStartUpdateProgram: noop, - logStopUpdateProgram: noop, - logStartUpdateGraph: noop, - logStopUpdateGraph: noop, - logStartResolveModule: noop, - logStopResolveModule: noop, - logStartParseSourceFile: noop, - logStopParseSourceFile: noop, - logStartReadFile: noop, - logStopReadFile: noop, - logStartBindFile: noop, - logStopBindFile: noop, - logStartScheduledOperation: noop, - logStopScheduledOperation: noop, - }; +import { noop } from "./_namespaces/ts"; - // Load optional module to enable Event Tracing for Windows - // See https://github.com/microsoft/typescript-etw for more information - let etwModule; - try { - const etwModulePath = process.env.TS_ETW_MODULE_PATH ?? "./node_modules/@microsoft/typescript-etw"; +/** @internal */ +export interface PerfLogger { + logEvent(msg: string): void; + logErrEvent(msg: string): void; + logPerfEvent(msg: string): void; + logInfoEvent(msg: string): void; + logStartCommand(command: string, msg: string): void; + logStopCommand(command: string, msg: string): void; + logStartUpdateProgram(msg: string): void; + logStopUpdateProgram(msg: string): void; + logStartUpdateGraph(): void; + logStopUpdateGraph(): void; + logStartResolveModule(name: string): void; + logStopResolveModule(success: string): void; + logStartParseSourceFile(filename: string): void; + logStopParseSourceFile(): void; + logStartReadFile(filename: string): void; + logStopReadFile(): void; + logStartBindFile(filename: string): void; + logStopBindFile(): void; + logStartScheduledOperation(operationId: string): void; + logStopScheduledOperation(): void; +} + +const nullLogger: PerfLogger = { + logEvent: noop, + logErrEvent: noop, + logPerfEvent: noop, + logInfoEvent: noop, + logStartCommand: noop, + logStopCommand: noop, + logStartUpdateProgram: noop, + logStopUpdateProgram: noop, + logStartUpdateGraph: noop, + logStopUpdateGraph: noop, + logStartResolveModule: noop, + logStopResolveModule: noop, + logStartParseSourceFile: noop, + logStopParseSourceFile: noop, + logStartReadFile: noop, + logStopReadFile: noop, + logStartBindFile: noop, + logStopBindFile: noop, + logStartScheduledOperation: noop, + logStopScheduledOperation: noop, +}; - // require() will throw an exception if the module is not found - // It may also return undefined if not installed properly - etwModule = require(etwModulePath); - } - catch (e) { - etwModule = undefined; - } +// Load optional module to enable Event Tracing for Windows +// See https://github.com/microsoft/typescript-etw for more information +let etwModule: typeof import("@microsoft/typescript-etw") | undefined; +try { + const etwModulePath = process.env.TS_ETW_MODULE_PATH ?? "./node_modules/@microsoft/typescript-etw"; - /** Performance logger that will generate ETW events if possible - check for `logEvent` member, as `etwModule` will be `{}` when browserified */ - export const perfLogger: PerfLogger = etwModule && etwModule.logEvent ? etwModule : nullLogger; + // require() will throw an exception if the module is not found + // It may also return undefined if not installed properly + etwModule = require(etwModulePath); } +catch (e) { + etwModule = undefined; +} + +/** + * Performance logger that will generate ETW events if possible - check for `logEvent` member, as `etwModule` will be `{}` when browserified + * + * @internal + */ +export const perfLogger: PerfLogger = etwModule?.logEvent ? etwModule : nullLogger; diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 341becea645ab..774f0880e0668 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -1,168 +1,197 @@ -/*@internal*/ +import { + Debug, Map, noop, Performance, PerformanceHooks, sys, System, timestamp, tryGetNativePerformanceHooks, +} from "./_namespaces/ts"; + /** Performance measurements for the compiler. */ -namespace ts.performance { - let perfHooks: PerformanceHooks | undefined; - // when set, indicates the implementation of `Performance` to use for user timing. - // when unset, indicates user timing is unavailable or disabled. - let performanceImpl: Performance | undefined; - - export interface Timer { - enter(): void; - exit(): void; - } - export function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) { - return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer; - } +let perfHooks: PerformanceHooks | undefined; +// when set, indicates the implementation of `Performance` to use for user timing. +// when unset, indicates user timing is unavailable or disabled. +let performanceImpl: Performance | undefined; - export function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer { - let enterCount = 0; - return { - enter, - exit - }; +/** @internal */ +export interface Timer { + enter(): void; + exit(): void; +} - function enter() { - if (++enterCount === 1) { - mark(startMarkName); - } - } +/** @internal */ +export function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) { + return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer; +} - function exit() { - if (--enterCount === 0) { - mark(endMarkName); - measure(measureName, startMarkName, endMarkName); - } - else if (enterCount < 0) { - Debug.fail("enter/exit count does not match."); - } +/** @internal */ +export function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer { + let enterCount = 0; + return { + enter, + exit + }; + + function enter() { + if (++enterCount === 1) { + mark(startMarkName); } } - export const nullTimer: Timer = { enter: noop, exit: noop }; - - let enabled = false; - let timeorigin = timestamp(); - const marks = new Map(); - const counts = new Map(); - const durations = new Map(); - - /** - * Marks a performance event. - * - * @param markName The name of the mark. - */ - export function mark(markName: string) { - if (enabled) { - const count = counts.get(markName) ?? 0; - counts.set(markName, count + 1); - marks.set(markName, timestamp()); - performanceImpl?.mark(markName); + function exit() { + if (--enterCount === 0) { + mark(endMarkName); + measure(measureName, startMarkName, endMarkName); } - } - - /** - * Adds a performance measurement with the specified name. - * - * @param measureName The name of the performance measurement. - * @param startMarkName The name of the starting mark. If not supplied, the point at which the - * profiler was enabled is used. - * @param endMarkName The name of the ending mark. If not supplied, the current timestamp is - * used. - */ - export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { - if (enabled) { - const end = (endMarkName !== undefined ? marks.get(endMarkName) : undefined) ?? timestamp(); - const start = (startMarkName !== undefined ? marks.get(startMarkName) : undefined) ?? timeorigin; - const previousDuration = durations.get(measureName) || 0; - durations.set(measureName, previousDuration + (end - start)); - performanceImpl?.measure(measureName, startMarkName, endMarkName); + else if (enterCount < 0) { + Debug.fail("enter/exit count does not match."); } } +} - /** - * Gets the number of times a marker was encountered. - * - * @param markName The name of the mark. - */ - export function getCount(markName: string) { - return counts.get(markName) || 0; +/** @internal */ +export const nullTimer: Timer = { enter: noop, exit: noop }; + +let enabled = false; +let timeorigin = timestamp(); +const marks = new Map(); +const counts = new Map(); +const durations = new Map(); + +/** + * Marks a performance event. + * + * @param markName The name of the mark. + * + * @internal + */ +export function mark(markName: string) { + if (enabled) { + const count = counts.get(markName) ?? 0; + counts.set(markName, count + 1); + marks.set(markName, timestamp()); + performanceImpl?.mark(markName); } +} - /** - * Gets the total duration of all measurements with the supplied name. - * - * @param measureName The name of the measure whose durations should be accumulated. - */ - export function getDuration(measureName: string) { - return durations.get(measureName) || 0; +/** + * Adds a performance measurement with the specified name. + * + * @param measureName The name of the performance measurement. + * @param startMarkName The name of the starting mark. If not supplied, the point at which the + * profiler was enabled is used. + * @param endMarkName The name of the ending mark. If not supplied, the current timestamp is + * used. + * + * @internal + */ +export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { + if (enabled) { + const end = (endMarkName !== undefined ? marks.get(endMarkName) : undefined) ?? timestamp(); + const start = (startMarkName !== undefined ? marks.get(startMarkName) : undefined) ?? timeorigin; + const previousDuration = durations.get(measureName) || 0; + durations.set(measureName, previousDuration + (end - start)); + performanceImpl?.measure(measureName, startMarkName, endMarkName); } +} - /** - * Iterate over each measure, performing some action - * - * @param cb The action to perform for each measure - */ - export function forEachMeasure(cb: (measureName: string, duration: number) => void) { - durations.forEach((duration, measureName) => cb(measureName, duration)); - } +/** + * Gets the number of times a marker was encountered. + * + * @param markName The name of the mark. + * + * @internal + */ +export function getCount(markName: string) { + return counts.get(markName) || 0; +} - export function forEachMark(cb: (markName: string) => void) { - marks.forEach((_time, markName) => cb(markName)); - } +/** + * Gets the total duration of all measurements with the supplied name. + * + * @param measureName The name of the measure whose durations should be accumulated. + * + * @internal + */ +export function getDuration(measureName: string) { + return durations.get(measureName) || 0; +} - export function clearMeasures(name?: string) { - if (name !== undefined) durations.delete(name); - else durations.clear(); - performanceImpl?.clearMeasures(name); - } +/** + * Iterate over each measure, performing some action + * + * @param cb The action to perform for each measure + * + * @internal + */ +export function forEachMeasure(cb: (measureName: string, duration: number) => void) { + durations.forEach((duration, measureName) => cb(measureName, duration)); +} - export function clearMarks(name?: string) { - if (name !== undefined) { - counts.delete(name); - marks.delete(name); - } - else { - counts.clear(); - marks.clear(); - } - performanceImpl?.clearMarks(name); - } +/** @internal */ +export function forEachMark(cb: (markName: string) => void) { + marks.forEach((_time, markName) => cb(markName)); +} - /** - * Indicates whether the performance API is enabled. - */ - export function isEnabled() { - return enabled; +/** @internal */ +export function clearMeasures(name?: string) { + if (name !== undefined) durations.delete(name); + else durations.clear(); + performanceImpl?.clearMeasures(name); +} + +/** @internal */ +export function clearMarks(name?: string) { + if (name !== undefined) { + counts.delete(name); + marks.delete(name); } + else { + counts.clear(); + marks.clear(); + } + performanceImpl?.clearMarks(name); +} - /** Enables (and resets) performance measurements for the compiler. */ - export function enable(system: System = sys) { - if (!enabled) { - enabled = true; - perfHooks ||= tryGetNativePerformanceHooks(); - if (perfHooks) { - timeorigin = perfHooks.performance.timeOrigin; - // NodeJS's Web Performance API is currently slower than expected, but we'd still like - // to be able to leverage native trace events when node is run with either `--cpu-prof` - // or `--prof`, if we're running with our own `--generateCpuProfile` flag, or when - // running in debug mode (since its possible to generate a cpu profile while debugging). - if (perfHooks.shouldWriteNativeEvents || system?.cpuProfilingEnabled?.() || system?.debugMode) { - performanceImpl = perfHooks.performance; - } +/** + * Indicates whether the performance API is enabled. + * + * @internal + */ +export function isEnabled() { + return enabled; +} + +/** + * Enables (and resets) performance measurements for the compiler. + * + * @internal + */ +export function enable(system: System = sys) { + if (!enabled) { + enabled = true; + perfHooks ||= tryGetNativePerformanceHooks(); + if (perfHooks) { + timeorigin = perfHooks.performance.timeOrigin; + // NodeJS's Web Performance API is currently slower than expected, but we'd still like + // to be able to leverage native trace events when node is run with either `--cpu-prof` + // or `--prof`, if we're running with our own `--generateCpuProfile` flag, or when + // running in debug mode (since its possible to generate a cpu profile while debugging). + if (perfHooks.shouldWriteNativeEvents || system?.cpuProfilingEnabled?.() || system?.debugMode) { + performanceImpl = perfHooks.performance; } } - return true; } + return true; +} - /** Disables performance measurements for the compiler. */ - export function disable() { - if (enabled) { - marks.clear(); - counts.clear(); - durations.clear(); - performanceImpl = undefined; - enabled = false; - } +/** + * Disables performance measurements for the compiler. + * + * @internal + */ +export function disable() { + if (enabled) { + marks.clear(); + counts.clear(); + durations.clear(); + performanceImpl = undefined; + enabled = false; } } diff --git a/src/compiler/performanceCore.ts b/src/compiler/performanceCore.ts index 07492542a5ca4..3788da62b9072 100644 --- a/src/compiler/performanceCore.ts +++ b/src/compiler/performanceCore.ts @@ -1,135 +1,146 @@ -/*@internal*/ -namespace ts { - // The following definitions provide the minimum compatible support for the Web Performance User Timings API - // between browsers and NodeJS: +import { isNodeLikeSystem, Version, VersionRange } from "./_namespaces/ts"; - export interface PerformanceHooks { - /** Indicates whether we should write native performance events */ - shouldWriteNativeEvents: boolean; - performance: Performance; - PerformanceObserver: PerformanceObserverConstructor; - } +// The following definitions provide the minimum compatible support for the Web Performance User Timings API +// between browsers and NodeJS: - export interface Performance { - mark(name: string): void; - measure(name: string, startMark?: string, endMark?: string): void; - clearMeasures(name?: string): void; - clearMarks(name?: string): void; - now(): number; - timeOrigin: number; - } +/** @internal */ +export interface PerformanceHooks { + /** Indicates whether we should write native performance events */ + shouldWriteNativeEvents: boolean; + performance: Performance; + PerformanceObserver: PerformanceObserverConstructor; +} - export interface PerformanceEntry { - name: string; - entryType: string; - startTime: number; - duration: number; - } +/** @internal */ +export interface Performance { + mark(name: string): void; + measure(name: string, startMark?: string, endMark?: string): void; + clearMeasures(name?: string): void; + clearMarks(name?: string): void; + now(): number; + timeOrigin: number; +} - export interface PerformanceObserverEntryList { - getEntries(): PerformanceEntryList; - getEntriesByName(name: string, type?: string): PerformanceEntryList; - getEntriesByType(type: string): PerformanceEntryList; - } +/** @internal */ +export interface PerformanceEntry { + name: string; + entryType: string; + startTime: number; + duration: number; +} - export interface PerformanceObserver { - disconnect(): void; - observe(options: { entryTypes: readonly ("mark" | "measure")[] }): void; - } +/** @internal */ +export interface PerformanceObserverEntryList { + getEntries(): PerformanceEntryList; + getEntriesByName(name: string, type?: string): PerformanceEntryList; + getEntriesByType(type: string): PerformanceEntryList; +} - export type PerformanceObserverConstructor = new (callback: (list: PerformanceObserverEntryList, observer: PerformanceObserver) => void) => PerformanceObserver; - export type PerformanceEntryList = PerformanceEntry[]; +/** @internal */ +export interface PerformanceObserver { + disconnect(): void; + observe(options: { entryTypes: readonly ("mark" | "measure")[] }): void; +} - // Browser globals for the Web Performance User Timings API - declare const process: any; - declare const performance: Performance | undefined; - declare const PerformanceObserver: PerformanceObserverConstructor | undefined; +/** @internal */ +export type PerformanceObserverConstructor = new (callback: (list: PerformanceObserverEntryList, observer: PerformanceObserver) => void) => PerformanceObserver; +/** @internal */ +export type PerformanceEntryList = PerformanceEntry[]; - // eslint-disable-next-line @typescript-eslint/naming-convention - function hasRequiredAPI(performance: Performance | undefined, PerformanceObserver: PerformanceObserverConstructor | undefined) { - return typeof performance === "object" && - typeof performance.timeOrigin === "number" && - typeof performance.mark === "function" && - typeof performance.measure === "function" && - typeof performance.now === "function" && - typeof performance.clearMarks === "function" && - typeof performance.clearMeasures === "function" && - typeof PerformanceObserver === "function"; - } +// Browser globals for the Web Performance User Timings API +declare const process: any; +declare const performance: Performance | undefined; +declare const PerformanceObserver: PerformanceObserverConstructor | undefined; - function tryGetWebPerformanceHooks(): PerformanceHooks | undefined { - if (typeof performance === "object" && - typeof PerformanceObserver === "function" && - hasRequiredAPI(performance, PerformanceObserver)) { - return { - // For now we always write native performance events when running in the browser. We may - // make this conditional in the future if we find that native web performance hooks - // in the browser also slow down compilation. - shouldWriteNativeEvents: true, - performance, - PerformanceObserver - }; - } +// eslint-disable-next-line @typescript-eslint/naming-convention +function hasRequiredAPI(performance: Performance | undefined, PerformanceObserver: PerformanceObserverConstructor | undefined) { + return typeof performance === "object" && + typeof performance.timeOrigin === "number" && + typeof performance.mark === "function" && + typeof performance.measure === "function" && + typeof performance.now === "function" && + typeof performance.clearMarks === "function" && + typeof performance.clearMeasures === "function" && + typeof PerformanceObserver === "function"; +} + +function tryGetWebPerformanceHooks(): PerformanceHooks | undefined { + if (typeof performance === "object" && + typeof PerformanceObserver === "function" && + hasRequiredAPI(performance, PerformanceObserver)) { + return { + // For now we always write native performance events when running in the browser. We may + // make this conditional in the future if we find that native web performance hooks + // in the browser also slow down compilation. + shouldWriteNativeEvents: true, + performance, + PerformanceObserver + }; } +} - function tryGetNodePerformanceHooks(): PerformanceHooks | undefined { - if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof module === "object" && typeof require === "function") { - try { - let performance: Performance; - const { performance: nodePerformance, PerformanceObserver } = require("perf_hooks") as typeof import("perf_hooks"); - if (hasRequiredAPI(nodePerformance as unknown as Performance, PerformanceObserver)) { - performance = nodePerformance as unknown as Performance; - // There is a bug in Node's performance.measure prior to 12.16.3/13.13.0 that does not - // match the Web Performance API specification. Node's implementation did not allow - // optional `start` and `end` arguments for `performance.measure`. - // See https://github.com/nodejs/node/pull/32651 for more information. - const version = new Version(process.versions.node); - const range = new VersionRange("<12.16.3 || 13 <13.13"); - if (range.test(version)) { - performance = { - get timeOrigin() { return nodePerformance.timeOrigin; }, - now() { return nodePerformance.now(); }, - mark(name) { return nodePerformance.mark(name); }, - measure(name, start = "nodeStart", end?) { - if (end === undefined) { - end = "__performance.measure-fix__"; - nodePerformance.mark(end); - } - nodePerformance.measure(name, start, end); - if (end === "__performance.measure-fix__") { - nodePerformance.clearMarks("__performance.measure-fix__"); - } - }, - clearMarks(name) { return nodePerformance.clearMarks(name); }, - clearMeasures(name) { return (nodePerformance as unknown as Performance).clearMeasures(name); }, - }; - } - return { - // By default, only write native events when generating a cpu profile or using the v8 profiler. - shouldWriteNativeEvents: false, - performance, - PerformanceObserver +function tryGetNodePerformanceHooks(): PerformanceHooks | undefined { + if (isNodeLikeSystem()) { + try { + let performance: Performance; + const { performance: nodePerformance, PerformanceObserver } = require("perf_hooks") as typeof import("perf_hooks"); + if (hasRequiredAPI(nodePerformance as unknown as Performance, PerformanceObserver)) { + performance = nodePerformance as unknown as Performance; + // There is a bug in Node's performance.measure prior to 12.16.3/13.13.0 that does not + // match the Web Performance API specification. Node's implementation did not allow + // optional `start` and `end` arguments for `performance.measure`. + // See https://github.com/nodejs/node/pull/32651 for more information. + const version = new Version(process.versions.node); + const range = new VersionRange("<12.16.3 || 13 <13.13"); + if (range.test(version)) { + performance = { + get timeOrigin() { return nodePerformance.timeOrigin; }, + now() { return nodePerformance.now(); }, + mark(name) { return nodePerformance.mark(name); }, + measure(name, start = "nodeStart", end?) { + if (end === undefined) { + end = "__performance.measure-fix__"; + nodePerformance.mark(end); + } + nodePerformance.measure(name, start, end); + if (end === "__performance.measure-fix__") { + nodePerformance.clearMarks("__performance.measure-fix__"); + } + }, + clearMarks(name) { return nodePerformance.clearMarks(name); }, + clearMeasures(name) { return (nodePerformance as unknown as Performance).clearMeasures(name); }, }; } - } - catch { - // ignore errors + return { + // By default, only write native events when generating a cpu profile or using the v8 profiler. + shouldWriteNativeEvents: false, + performance, + PerformanceObserver + }; } } + catch { + // ignore errors + } } +} - // Unlike with the native Map/Set 'tryGet' functions in corePublic.ts, we eagerly evaluate these - // since we will need them for `timestamp`, below. - const nativePerformanceHooks = tryGetWebPerformanceHooks() || tryGetNodePerformanceHooks(); - const nativePerformance = nativePerformanceHooks?.performance; +// Unlike with the native Map/Set 'tryGet' functions in corePublic.ts, we eagerly evaluate these +// since we will need them for `timestamp`, below. +const nativePerformanceHooks = tryGetWebPerformanceHooks() || tryGetNodePerformanceHooks(); +const nativePerformance = nativePerformanceHooks?.performance; - export function tryGetNativePerformanceHooks() { - return nativePerformanceHooks; - } +/** @internal */ +export function tryGetNativePerformanceHooks() { + return nativePerformanceHooks; +} - /** Gets a timestamp with (at least) ms resolution */ - export const timestamp = - nativePerformance ? () => nativePerformance.now() : - Date.now ? Date.now : - () => +(new Date()); -} \ No newline at end of file +/** + * Gets a timestamp with (at least) ms resolution + * + * @internal + */ +export const timestamp = + nativePerformance ? () => nativePerformance.now() : + Date.now ? Date.now : + () => +(new Date()); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 85d34e9106d57..7a6d8d8b09a3c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1,3030 +1,2998 @@ -namespace ts { - export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string | undefined { - return forEachAncestorDirectory(searchPath, ancestor => { - const fileName = combinePaths(ancestor, configName); - return fileExists(fileName) ? fileName : undefined; - }); - } - - export function resolveTripleslashReference(moduleName: string, containingFile: string): string { - const basePath = getDirectoryPath(containingFile); - const referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); - return normalizePath(referencedFileName); - } - - /* @internal */ - export function computeCommonSourceDirectoryOfFilenames(fileNames: readonly string[], currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): string { - let commonPathComponents: string[] | undefined; - const failed = forEach(fileNames, sourceFile => { - // Each file contributes into common source file path - const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory); - sourcePathComponents.pop(); // The base file name is not part of the common directory path +import * as ts from "./_namespaces/ts"; +import { + __String, addEmitFlags, addRange, append, arrayFrom, arrayIsEqualTo, AsExpression, AssertClause, BuilderProgram, + CancellationToken, canHaveModifiers, chainDiagnosticMessages, changeExtension, changesAffectingProgramStructure, + changesAffectModuleResolution, clone, combinePaths, CommentDirective, CommentDirectivesMap, compareDataObjects, + comparePaths, compareValues, Comparison, CompilerHost, CompilerOptions, computeLineAndCharacterOfPosition, + concatenate, contains, containsIgnoredPath, containsPath, convertToRelativePath, createCommentDirectivesMap, + createCompilerDiagnostic, createCompilerDiagnosticFromMessageChain, createDiagnosticCollection, + createDiagnosticForNodeInSourceFile, createDiagnosticForRange, createFileDiagnostic, + createFileDiagnosticFromMessageChain, createGetCanonicalFileName, createInputFilesWithFilePaths, + createModeAwareCache, createModuleResolutionCache, createMultiMap, CreateProgramOptions, createSourceFile, + CreateSourceFileOptions, createSymlinkCache, createTypeChecker, createTypeReferenceDirectiveResolutionCache, + CustomTransformers, Debug, DeclarationWithTypeParameterChildren, Diagnostic, DiagnosticCategory, + diagnosticCategoryName, DiagnosticMessage, DiagnosticMessageChain, DiagnosticReporter, Diagnostics, + DiagnosticWithLocation, directorySeparator, DirectoryStructureHost, emitFiles, EmitFlags, EmitHost, EmitOnly, + EmitResult, emptyArray, ensureTrailingDirectorySeparator, equateStringsCaseInsensitive, equateStringsCaseSensitive, + ESMap, explainIfFileIsRedirectAndImpliedFormat, ExportAssignment, ExportDeclaration, Extension, extensionFromPath, + externalHelpersModuleNameText, factory, fileExtensionIs, fileExtensionIsOneOf, FileIncludeKind, FileIncludeReason, + fileIncludeReasonToDiagnostics, FilePreprocessingDiagnostics, FilePreprocessingDiagnosticsKind, FileReference, + filter, find, firstDefined, firstDefinedIterator, flatMap, flatten, forEach, forEachAncestorDirectory, forEachChild, + forEachChildRecursively, forEachEmittedFile, forEachEntry, forEachKey, FunctionLikeDeclaration, + getAllowJSCompilerOption, getAutomaticTypeDirectiveNames, getBaseFileName, GetCanonicalFileName, + getCommonSourceDirectoryOfConfig, getDefaultLibFileName, getDirectoryPath, getEmitDeclarations, getEmitModuleKind, + getEmitModuleResolutionKind, getEmitScriptTarget, getErrorSpanForNode, getExternalModuleName, + getJSXImplicitImportBase, getJSXRuntimeImport, getLineAndCharacterOfPosition, getLineStarts, getMatchedFileSpec, + getMatchedIncludeSpec, getNewLineCharacter, getNormalizedAbsolutePath, getNormalizedAbsolutePathWithoutRoot, + getNormalizedPathComponents, getOutputDeclarationFileName, getOutputPathsForBundle, getPackageScopeForPath, + getPathFromPathComponents, getPositionOfLineAndCharacter, getPropertyArrayElementValue, getPropertyAssignment, + getResolutionMode, getResolutionName, getResolvedModule, getRootLength, getSetExternalModuleIndicator, + getSpellingSuggestion, getStrictOptionValue, getSupportedExtensions, + getSupportedExtensionsWithJsonIfResolveJsonModule, getTemporaryModuleResolutionState, getTextOfIdentifierOrLiteral, + getTransformers, getTsBuildInfoEmitOutputFilePath, getTsConfigObjectLiteralExpression, getTsConfigPropArray, + getTsConfigPropArrayElementValue, HasChangedAutomaticTypeDirectiveNames, hasChangesInResolutions, hasExtension, + HasInvalidatedResolutions, hasJSDocNodes, hasJSFileExtension, hasJsonModuleEmitEnabled, hasProperty, + hasSyntacticModifier, hasZeroOrOneAsteriskCharacter, HeritageClause, Identifier, identity, ImportClause, + ImportDeclaration, ImportOrExportSpecifier, InputFiles, inverseJsxOptionMap, isAmbientModule, isAnyImportOrReExport, + isArray, isArrayLiteralExpression, isBuildInfoFile, isCheckJsEnabledForFile, isDeclarationFileName, isDecorator, + isExportDeclaration, isExternalModule, isExternalModuleNameRelative, isIdentifierText, isImportCall, + isImportDeclaration, isImportEqualsDeclaration, isImportSpecifier, isImportTypeNode, isIncrementalCompilation, + isInJSFile, isLiteralImportTypeNode, isModifier, isModuleDeclaration, isObjectLiteralExpression, isPlainJsFile, + isRequireCall, isRootedDiskPath, isSourceFileJS, isString, isStringLiteral, isStringLiteralLike, isTraceEnabled, + JsonSourceFile, JsxEmit, length, libMap, libs, Map, mapDefined, mapDefinedIterator, maybeBind, memoize, + MethodDeclaration, ModifierFlags, ModifierLike, ModuleBlock, ModuleDeclaration, ModuleKind, ModuleResolutionCache, + ModuleResolutionHost, ModuleResolutionInfo, moduleResolutionIsEqualTo, ModuleResolutionKind, Mutable, Node, + NodeArray, NodeFlags, nodeModulesPathPart, NodeWithTypeArguments, noop, normalizePath, notImplementedResolver, + noTransformers, ObjectLiteralExpression, OperationCanceledException, optionsHaveChanges, outFile, PackageId, + packageIdToPackageName, packageIdToString, PackageJsonInfoCache, padLeft, ParameterDeclaration, ParseConfigFileHost, + ParsedCommandLine, parseIsolatedEntityName, parseJsonSourceFileConfigFileContent, Path, pathIsAbsolute, + pathIsRelative, Program, ProgramHost, ProjectReference, ProjectReferenceFile, projectReferenceIsEqualTo, + PropertyDeclaration, ReferencedFile, removeFileExtension, removePrefix, removeSuffix, resolutionExtensionIsTSOrJson, + resolveConfigFileProjectName, ResolvedConfigFileName, ResolvedModuleFull, ResolvedModuleWithFailedLookupLocations, + ResolvedProjectReference, ResolvedTypeReferenceDirective, resolveModuleName, resolveModuleNameFromCache, + resolveTypeReferenceDirective, returnFalse, returnUndefined, SatisfiesExpression, ScriptKind, ScriptTarget, Set, + setParent, setParentRecursive, setResolvedModule, setResolvedTypeReferenceDirective, skipTrivia, skipTypeChecking, + some, sortAndDeduplicateDiagnostics, SortedReadonlyArray, SourceFile, sourceFileAffectingCompilerOptions, + sourceFileMayBeEmitted, SourceOfProjectReferenceRedirect, stableSort, startsWith, Statement, stringContains, + StringLiteral, StringLiteralLike, StructureIsReused, supportedJSExtensionsFlat, SymlinkCache, SyntaxKind, sys, + targetOptionDeclaration, toFileNameLowerCase, tokenToString, trace, tracing, trimStringEnd, TsConfigSourceFile, + TypeChecker, typeDirectiveIsEqualTo, TypeReferenceDirectiveResolutionCache, UnparsedSource, VariableDeclaration, + VariableStatement, walkUpParenthesizedExpressions, WriteFileCallback, WriteFileCallbackData, + writeFileEnsuringDirectories, zipToModeAwareCache, +} from "./_namespaces/ts"; +import * as performance from "./_namespaces/ts.performance"; + +export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string | undefined { + return forEachAncestorDirectory(searchPath, ancestor => { + const fileName = combinePaths(ancestor, configName); + return fileExists(fileName) ? fileName : undefined; + }); +} - if (!commonPathComponents) { - // first file - commonPathComponents = sourcePathComponents; - return; - } +export function resolveTripleslashReference(moduleName: string, containingFile: string): string { + const basePath = getDirectoryPath(containingFile); + const referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); + return normalizePath(referencedFileName); +} - const n = Math.min(commonPathComponents.length, sourcePathComponents.length); - for (let i = 0; i < n; i++) { - if (getCanonicalFileName(commonPathComponents[i]) !== getCanonicalFileName(sourcePathComponents[i])) { - if (i === 0) { - // Failed to find any common path component - return true; - } +/** @internal */ +export function computeCommonSourceDirectoryOfFilenames(fileNames: readonly string[], currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): string { + let commonPathComponents: string[] | undefined; + const failed = forEach(fileNames, sourceFile => { + // Each file contributes into common source file path + const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory); + sourcePathComponents.pop(); // The base file name is not part of the common directory path + + if (!commonPathComponents) { + // first file + commonPathComponents = sourcePathComponents; + return; + } - // New common path found that is 0 -> i-1 - commonPathComponents.length = i; - break; + const n = Math.min(commonPathComponents.length, sourcePathComponents.length); + for (let i = 0; i < n; i++) { + if (getCanonicalFileName(commonPathComponents[i]) !== getCanonicalFileName(sourcePathComponents[i])) { + if (i === 0) { + // Failed to find any common path component + return true; } - } - // If the sourcePathComponents was shorter than the commonPathComponents, truncate to the sourcePathComponents - if (sourcePathComponents.length < commonPathComponents.length) { - commonPathComponents.length = sourcePathComponents.length; + // New common path found that is 0 -> i-1 + commonPathComponents.length = i; + break; } - }); - - // A common path can not be found when paths span multiple drives on windows, for example - if (failed) { - return ""; } - if (!commonPathComponents) { // Can happen when all input files are .d.ts files - return currentDirectory; + // If the sourcePathComponents was shorter than the commonPathComponents, truncate to the sourcePathComponents + if (sourcePathComponents.length < commonPathComponents.length) { + commonPathComponents.length = sourcePathComponents.length; } + }); - return getPathFromPathComponents(commonPathComponents); + // A common path can not be found when paths span multiple drives on windows, for example + if (failed) { + return ""; } - export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { - return createCompilerHostWorker(options, setParentNodes); + if (!commonPathComponents) { // Can happen when all input files are .d.ts files + return currentDirectory; } - /*@internal*/ - export function createGetSourceFile( - readFile: ProgramHost["readFile"], - getCompilerOptions: () => CompilerOptions, - setParentNodes: boolean | undefined - ): CompilerHost["getSourceFile"] { - return (fileName, languageVersionOrOptions, onError) => { - let text: string | undefined; - try { - performance.mark("beforeIORead"); - text = readFile(fileName, getCompilerOptions().charset); - performance.mark("afterIORead"); - performance.measure("I/O Read", "beforeIORead", "afterIORead"); - } - catch (e) { - if (onError) { - onError(e.message); - } - text = ""; - } - return text !== undefined ? createSourceFile(fileName, text, languageVersionOrOptions, setParentNodes) : undefined; - }; - } + return getPathFromPathComponents(commonPathComponents); +} - /*@internal*/ - export function createWriteFileMeasuringIO( - actualWriteFile: (path: string, data: string, writeByteOrderMark: boolean) => void, - createDirectory: (path: string) => void, - directoryExists: (path: string) => boolean - ): CompilerHost["writeFile"] { - return (fileName, data, writeByteOrderMark, onError) => { - try { - performance.mark("beforeIOWrite"); - - // NOTE: If patchWriteFileEnsuringDirectory has been called, - // the system.writeFile will do its own directory creation and - // the ensureDirectoriesExist call will always be redundant. - writeFileEnsuringDirectories( - fileName, - data, - writeByteOrderMark, - actualWriteFile, - createDirectory, - directoryExists - ); +export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { + return createCompilerHostWorker(options, setParentNodes); +} - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - } - catch (e) { - if (onError) { - onError(e.message); - } - } - }; - } +/** @internal */ +export function createGetSourceFile( + readFile: ProgramHost["readFile"], + getCompilerOptions: () => CompilerOptions, + setParentNodes: boolean | undefined +): CompilerHost["getSourceFile"] { + return (fileName, languageVersionOrOptions, onError) => { + let text: string | undefined; + try { + performance.mark("beforeIORead"); + text = readFile(fileName, getCompilerOptions().charset); + performance.mark("afterIORead"); + performance.measure("I/O Read", "beforeIORead", "afterIORead"); + } + catch (e) { + if (onError) { + onError(e.message); + } + text = ""; + } + return text !== undefined ? createSourceFile(fileName, text, languageVersionOrOptions, setParentNodes) : undefined; + }; +} - /*@internal*/ - export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost { - const existingDirectories = new Map(); - const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); - function directoryExists(directoryPath: string): boolean { - if (existingDirectories.has(directoryPath)) { - return true; - } - if ((compilerHost.directoryExists || system.directoryExists)(directoryPath)) { - existingDirectories.set(directoryPath, true); - return true; +/** @internal */ +export function createWriteFileMeasuringIO( + actualWriteFile: (path: string, data: string, writeByteOrderMark: boolean) => void, + createDirectory: (path: string) => void, + directoryExists: (path: string) => boolean +): CompilerHost["writeFile"] { + return (fileName, data, writeByteOrderMark, onError) => { + try { + performance.mark("beforeIOWrite"); + + // NOTE: If patchWriteFileEnsuringDirectory has been called, + // the system.writeFile will do its own directory creation and + // the ensureDirectoriesExist call will always be redundant. + writeFileEnsuringDirectories( + fileName, + data, + writeByteOrderMark, + actualWriteFile, + createDirectory, + directoryExists + ); + + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + if (onError) { + onError(e.message); } - return false; } + }; +} - function getDefaultLibLocation(): string { - return getDirectoryPath(normalizePath(system.getExecutingFilePath())); +/** @internal */ +export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost { + const existingDirectories = new Map(); + const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); + function directoryExists(directoryPath: string): boolean { + if (existingDirectories.has(directoryPath)) { + return true; } - - const newLine = getNewLineCharacter(options, () => system.newLine); - const realpath = system.realpath && ((path: string) => system.realpath!(path)); - const compilerHost: CompilerHost = { - getSourceFile: createGetSourceFile(fileName => compilerHost.readFile(fileName), () => options, setParentNodes), - getDefaultLibLocation, - getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - writeFile: createWriteFileMeasuringIO( - (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark), - path => (compilerHost.createDirectory || system.createDirectory)(path), - path => directoryExists(path), - ), - getCurrentDirectory: memoize(() => system.getCurrentDirectory()), - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, - getCanonicalFileName, - getNewLine: () => newLine, - fileExists: fileName => system.fileExists(fileName), - readFile: fileName => system.readFile(fileName), - trace: (s: string) => system.write(s + newLine), - directoryExists: directoryName => system.directoryExists(directoryName), - getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", - getDirectories: (path: string) => system.getDirectories(path), - realpath, - readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth), - createDirectory: d => system.createDirectory(d), - createHash: maybeBind(system, system.createHash) - }; - return compilerHost; + if ((compilerHost.directoryExists || system.directoryExists)(directoryPath)) { + existingDirectories.set(directoryPath, true); + return true; + } + return false; } - /*@internal*/ - interface CompilerHostLikeForCache { - fileExists(fileName: string): boolean; - readFile(fileName: string, encoding?: string): string | undefined; - directoryExists?(directory: string): boolean; - createDirectory?(directory: string): void; - writeFile?: WriteFileCallback; + function getDefaultLibLocation(): string { + return getDirectoryPath(normalizePath(system.getExecutingFilePath())); } - /*@internal*/ - export function changeCompilerHostLikeToUseCache( - host: CompilerHostLikeForCache, - toPath: (fileName: string) => Path, - getSourceFile?: CompilerHost["getSourceFile"] - ) { - const originalReadFile = host.readFile; - const originalFileExists = host.fileExists; - const originalDirectoryExists = host.directoryExists; - const originalCreateDirectory = host.createDirectory; - const originalWriteFile = host.writeFile; - const readFileCache = new Map(); - const fileExistsCache = new Map(); - const directoryExistsCache = new Map(); - const sourceFileCache = new Map>(); - - const readFileWithCache = (fileName: string): string | undefined => { - const key = toPath(fileName); - const value = readFileCache.get(key); - if (value !== undefined) return value !== false ? value : undefined; - return setReadFileCache(key, fileName); - }; - const setReadFileCache = (key: Path, fileName: string) => { - const newValue = originalReadFile.call(host, fileName); - readFileCache.set(key, newValue !== undefined ? newValue : false); - return newValue; - }; - host.readFile = fileName => { - const key = toPath(fileName); - const value = readFileCache.get(key); - if (value !== undefined) return value !== false ? value : undefined; // could be .d.ts from output - // Cache json or buildInfo - if (!fileExtensionIs(fileName, Extension.Json) && !isBuildInfoFile(fileName)) { - return originalReadFile.call(host, fileName); - } + const newLine = getNewLineCharacter(options, () => system.newLine); + const realpath = system.realpath && ((path: string) => system.realpath!(path)); + const compilerHost: CompilerHost = { + getSourceFile: createGetSourceFile(fileName => compilerHost.readFile(fileName), () => options, setParentNodes), + getDefaultLibLocation, + getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), + writeFile: createWriteFileMeasuringIO( + (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark), + path => (compilerHost.createDirectory || system.createDirectory)(path), + path => directoryExists(path), + ), + getCurrentDirectory: memoize(() => system.getCurrentDirectory()), + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + getCanonicalFileName, + getNewLine: () => newLine, + fileExists: fileName => system.fileExists(fileName), + readFile: fileName => system.readFile(fileName), + trace: (s: string) => system.write(s + newLine), + directoryExists: directoryName => system.directoryExists(directoryName), + getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", + getDirectories: (path: string) => system.getDirectories(path), + realpath, + readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth), + createDirectory: d => system.createDirectory(d), + createHash: maybeBind(system, system.createHash) + }; + return compilerHost; +} - return setReadFileCache(key, fileName); - }; +/** @internal */ +export interface CompilerHostLikeForCache { + fileExists(fileName: string): boolean; + readFile(fileName: string, encoding?: string): string | undefined; + directoryExists?(directory: string): boolean; + createDirectory?(directory: string): void; + writeFile?: WriteFileCallback; +} - const getSourceFileWithCache: CompilerHost["getSourceFile"] | undefined = getSourceFile ? (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => { +/** @internal */ +export function changeCompilerHostLikeToUseCache( + host: CompilerHostLikeForCache, + toPath: (fileName: string) => Path, + getSourceFile?: CompilerHost["getSourceFile"] +) { + const originalReadFile = host.readFile; + const originalFileExists = host.fileExists; + const originalDirectoryExists = host.directoryExists; + const originalCreateDirectory = host.createDirectory; + const originalWriteFile = host.writeFile; + const readFileCache = new Map(); + const fileExistsCache = new Map(); + const directoryExistsCache = new Map(); + const sourceFileCache = new Map>(); + + const readFileWithCache = (fileName: string): string | undefined => { + const key = toPath(fileName); + const value = readFileCache.get(key); + if (value !== undefined) return value !== false ? value : undefined; + return setReadFileCache(key, fileName); + }; + const setReadFileCache = (key: Path, fileName: string) => { + const newValue = originalReadFile.call(host, fileName); + readFileCache.set(key, newValue !== undefined ? newValue : false); + return newValue; + }; + host.readFile = fileName => { + const key = toPath(fileName); + const value = readFileCache.get(key); + if (value !== undefined) return value !== false ? value : undefined; // could be .d.ts from output + // Cache json or buildInfo + if (!fileExtensionIs(fileName, Extension.Json) && !isBuildInfoFile(fileName)) { + return originalReadFile.call(host, fileName); + } + + return setReadFileCache(key, fileName); + }; + + const getSourceFileWithCache: CompilerHost["getSourceFile"] | undefined = getSourceFile ? (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => { + const key = toPath(fileName); + const impliedNodeFormat: SourceFile["impliedNodeFormat"] = typeof languageVersionOrOptions === "object" ? languageVersionOrOptions.impliedNodeFormat : undefined; + const forImpliedNodeFormat = sourceFileCache.get(impliedNodeFormat); + const value = forImpliedNodeFormat?.get(key); + if (value) return value; + + const sourceFile = getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile); + if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) { + sourceFileCache.set(impliedNodeFormat, (forImpliedNodeFormat || new Map()).set(key, sourceFile)); + } + return sourceFile; + } : undefined; + + // fileExists for any kind of extension + host.fileExists = fileName => { + const key = toPath(fileName); + const value = fileExistsCache.get(key); + if (value !== undefined) return value; + const newValue = originalFileExists.call(host, fileName); + fileExistsCache.set(key, !!newValue); + return newValue; + }; + if (originalWriteFile) { + host.writeFile = (fileName, data, ...rest) => { const key = toPath(fileName); - const impliedNodeFormat: SourceFile["impliedNodeFormat"] = typeof languageVersionOrOptions === "object" ? languageVersionOrOptions.impliedNodeFormat : undefined; - const forImpliedNodeFormat = sourceFileCache.get(impliedNodeFormat); - const value = forImpliedNodeFormat?.get(key); - if (value) return value; + fileExistsCache.delete(key); - const sourceFile = getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile); - if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) { - sourceFileCache.set(impliedNodeFormat, (forImpliedNodeFormat || new Map()).set(key, sourceFile)); + const value = readFileCache.get(key); + if (value !== undefined && value !== data) { + readFileCache.delete(key); + sourceFileCache.forEach(map => map.delete(key)); + } + else if (getSourceFileWithCache) { + sourceFileCache.forEach(map => { + const sourceFile = map.get(key); + if (sourceFile && sourceFile.text !== data) { + map.delete(key); + } + }); } - return sourceFile; - } : undefined; + originalWriteFile.call(host, fileName, data, ...rest); + }; + } - // fileExists for any kind of extension - host.fileExists = fileName => { - const key = toPath(fileName); - const value = fileExistsCache.get(key); + // directoryExists + if (originalDirectoryExists) { + host.directoryExists = directory => { + const key = toPath(directory); + const value = directoryExistsCache.get(key); if (value !== undefined) return value; - const newValue = originalFileExists.call(host, fileName); - fileExistsCache.set(key, !!newValue); + const newValue = originalDirectoryExists.call(host, directory); + directoryExistsCache.set(key, !!newValue); return newValue; }; - if (originalWriteFile) { - host.writeFile = (fileName, data, ...rest) => { - const key = toPath(fileName); - fileExistsCache.delete(key); - - const value = readFileCache.get(key); - if (value !== undefined && value !== data) { - readFileCache.delete(key); - sourceFileCache.forEach(map => map.delete(key)); - } - else if (getSourceFileWithCache) { - sourceFileCache.forEach(map => { - const sourceFile = map.get(key); - if (sourceFile && sourceFile.text !== data) { - map.delete(key); - } - }); - } - originalWriteFile.call(host, fileName, data, ...rest); - }; - } - // directoryExists - if (originalDirectoryExists) { - host.directoryExists = directory => { + if (originalCreateDirectory) { + host.createDirectory = directory => { const key = toPath(directory); - const value = directoryExistsCache.get(key); - if (value !== undefined) return value; - const newValue = originalDirectoryExists.call(host, directory); - directoryExistsCache.set(key, !!newValue); - return newValue; + directoryExistsCache.delete(key); + originalCreateDirectory.call(host, directory); }; - - if (originalCreateDirectory) { - host.createDirectory = directory => { - const key = toPath(directory); - directoryExistsCache.delete(key); - originalCreateDirectory.call(host, directory); - }; - } } + } - return { - originalReadFile, - originalFileExists, - originalDirectoryExists, - originalCreateDirectory, - originalWriteFile, - getSourceFileWithCache, - readFileWithCache - }; + return { + originalReadFile, + originalFileExists, + originalDirectoryExists, + originalCreateDirectory, + originalWriteFile, + getSourceFileWithCache, + readFileWithCache + }; +} + +export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; +/** @internal */ export function getPreEmitDiagnostics(program: BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; // eslint-disable-line @typescript-eslint/unified-signatures +export function getPreEmitDiagnostics(program: Program | BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + let diagnostics: Diagnostic[] | undefined; + diagnostics = addRange(diagnostics, program.getConfigFileParsingDiagnostics()); + diagnostics = addRange(diagnostics, program.getOptionsDiagnostics(cancellationToken)); + diagnostics = addRange(diagnostics, program.getSyntacticDiagnostics(sourceFile, cancellationToken)); + diagnostics = addRange(diagnostics, program.getGlobalDiagnostics(cancellationToken)); + diagnostics = addRange(diagnostics, program.getSemanticDiagnostics(sourceFile, cancellationToken)); + + if (getEmitDeclarations(program.getCompilerOptions())) { + diagnostics = addRange(diagnostics, program.getDeclarationDiagnostics(sourceFile, cancellationToken)); } - export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; - /*@internal*/ export function getPreEmitDiagnostics(program: BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; // eslint-disable-line @typescript-eslint/unified-signatures - export function getPreEmitDiagnostics(program: Program | BuilderProgram, sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - let diagnostics: Diagnostic[] | undefined; - diagnostics = addRange(diagnostics, program.getConfigFileParsingDiagnostics()); - diagnostics = addRange(diagnostics, program.getOptionsDiagnostics(cancellationToken)); - diagnostics = addRange(diagnostics, program.getSyntacticDiagnostics(sourceFile, cancellationToken)); - diagnostics = addRange(diagnostics, program.getGlobalDiagnostics(cancellationToken)); - diagnostics = addRange(diagnostics, program.getSemanticDiagnostics(sourceFile, cancellationToken)); + return sortAndDeduplicateDiagnostics(diagnostics || emptyArray); +} - if (getEmitDeclarations(program.getCompilerOptions())) { - diagnostics = addRange(diagnostics, program.getDeclarationDiagnostics(sourceFile, cancellationToken)); - } +export interface FormatDiagnosticsHost { + getCurrentDirectory(): string; + getCanonicalFileName(fileName: string): string; + getNewLine(): string; +} - return sortAndDeduplicateDiagnostics(diagnostics || emptyArray); - } +export function formatDiagnostics(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { + let output = ""; - export interface FormatDiagnosticsHost { - getCurrentDirectory(): string; - getCanonicalFileName(fileName: string): string; - getNewLine(): string; + for (const diagnostic of diagnostics) { + output += formatDiagnostic(diagnostic, host); } + return output; +} - export function formatDiagnostics(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { - let output = ""; +export function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string { + const errorMessage = `${diagnosticCategoryName(diagnostic)} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`; - for (const diagnostic of diagnostics) { - output += formatDiagnostic(diagnostic, host); - } - return output; + if (diagnostic.file) { + const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start!); // TODO: GH#18217 + const fileName = diagnostic.file.fileName; + const relativeFileName = convertToRelativePath(fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)); + return `${relativeFileName}(${line + 1},${character + 1}): ` + errorMessage; } - export function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string { - const errorMessage = `${diagnosticCategoryName(diagnostic)} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`; - - if (diagnostic.file) { - const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start!); // TODO: GH#18217 - const fileName = diagnostic.file.fileName; - const relativeFileName = convertToRelativePath(fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)); - return `${relativeFileName}(${line + 1},${character + 1}): ` + errorMessage; - } + return errorMessage; +} - return errorMessage; +/** @internal */ +export enum ForegroundColorEscapeSequences { + Grey = "\u001b[90m", + Red = "\u001b[91m", + Yellow = "\u001b[93m", + Blue = "\u001b[94m", + Cyan = "\u001b[96m" +} +const gutterStyleSequence = "\u001b[7m"; +const gutterSeparator = " "; +const resetEscapeSequence = "\u001b[0m"; +const ellipsis = "..."; +const halfIndent = " "; +const indent = " "; +function getCategoryFormat(category: DiagnosticCategory): ForegroundColorEscapeSequences { + switch (category) { + case DiagnosticCategory.Error: return ForegroundColorEscapeSequences.Red; + case DiagnosticCategory.Warning: return ForegroundColorEscapeSequences.Yellow; + case DiagnosticCategory.Suggestion: return Debug.fail("Should never get an Info diagnostic on the command line."); + case DiagnosticCategory.Message: return ForegroundColorEscapeSequences.Blue; } +} - /** @internal */ - export enum ForegroundColorEscapeSequences { - Grey = "\u001b[90m", - Red = "\u001b[91m", - Yellow = "\u001b[93m", - Blue = "\u001b[94m", - Cyan = "\u001b[96m" - } - const gutterStyleSequence = "\u001b[7m"; - const gutterSeparator = " "; - const resetEscapeSequence = "\u001b[0m"; - const ellipsis = "..."; - const halfIndent = " "; - const indent = " "; - function getCategoryFormat(category: DiagnosticCategory): ForegroundColorEscapeSequences { - switch (category) { - case DiagnosticCategory.Error: return ForegroundColorEscapeSequences.Red; - case DiagnosticCategory.Warning: return ForegroundColorEscapeSequences.Yellow; - case DiagnosticCategory.Suggestion: return Debug.fail("Should never get an Info diagnostic on the command line."); - case DiagnosticCategory.Message: return ForegroundColorEscapeSequences.Blue; - } - } +/** @internal */ +export function formatColorAndReset(text: string, formatStyle: string) { + return formatStyle + text + resetEscapeSequence; +} - /** @internal */ - export function formatColorAndReset(text: string, formatStyle: string) { - return formatStyle + text + resetEscapeSequence; - } +function formatCodeSpan(file: SourceFile, start: number, length: number, indent: string, squiggleColor: ForegroundColorEscapeSequences, host: FormatDiagnosticsHost) { + const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); + const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length); + const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line; - function formatCodeSpan(file: SourceFile, start: number, length: number, indent: string, squiggleColor: ForegroundColorEscapeSequences, host: FormatDiagnosticsHost) { - const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); - const { line: lastLine, character: lastLineChar } = getLineAndCharacterOfPosition(file, start + length); - const lastLineInFile = getLineAndCharacterOfPosition(file, file.text.length).line; + const hasMoreThanFiveLines = (lastLine - firstLine) >= 4; + let gutterWidth = (lastLine + 1 + "").length; + if (hasMoreThanFiveLines) { + gutterWidth = Math.max(ellipsis.length, gutterWidth); + } - const hasMoreThanFiveLines = (lastLine - firstLine) >= 4; - let gutterWidth = (lastLine + 1 + "").length; - if (hasMoreThanFiveLines) { - gutterWidth = Math.max(ellipsis.length, gutterWidth); + let context = ""; + for (let i = firstLine; i <= lastLine; i++) { + context += host.getNewLine(); + // If the error spans over 5 lines, we'll only show the first 2 and last 2 lines, + // so we'll skip ahead to the second-to-last line. + if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) { + context += indent + formatColorAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine(); + i = lastLine - 1; } - let context = ""; - for (let i = firstLine; i <= lastLine; i++) { - context += host.getNewLine(); - // If the error spans over 5 lines, we'll only show the first 2 and last 2 lines, - // so we'll skip ahead to the second-to-last line. - if (hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1) { - context += indent + formatColorAndReset(padLeft(ellipsis, gutterWidth), gutterStyleSequence) + gutterSeparator + host.getNewLine(); - i = lastLine - 1; - } - - const lineStart = getPositionOfLineAndCharacter(file, i, 0); - const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length; - let lineContent = file.text.slice(lineStart, lineEnd); - lineContent = trimStringEnd(lineContent); // trim from end - lineContent = lineContent.replace(/\t/g, " "); // convert tabs to single spaces + const lineStart = getPositionOfLineAndCharacter(file, i, 0); + const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter(file, i + 1, 0) : file.text.length; + let lineContent = file.text.slice(lineStart, lineEnd); + lineContent = trimStringEnd(lineContent); // trim from end + lineContent = lineContent.replace(/\t/g, " "); // convert tabs to single spaces - // Output the gutter and the actual contents of the line. - context += indent + formatColorAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator; - context += lineContent + host.getNewLine(); + // Output the gutter and the actual contents of the line. + context += indent + formatColorAndReset(padLeft(i + 1 + "", gutterWidth), gutterStyleSequence) + gutterSeparator; + context += lineContent + host.getNewLine(); - // Output the gutter and the error span for the line using tildes. - context += indent + formatColorAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator; - context += squiggleColor; - if (i === firstLine) { - // If we're on the last line, then limit it to the last character of the last line. - // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position. - const lastCharForLine = i === lastLine ? lastLineChar : undefined; + // Output the gutter and the error span for the line using tildes. + context += indent + formatColorAndReset(padLeft("", gutterWidth), gutterStyleSequence) + gutterSeparator; + context += squiggleColor; + if (i === firstLine) { + // If we're on the last line, then limit it to the last character of the last line. + // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position. + const lastCharForLine = i === lastLine ? lastLineChar : undefined; - context += lineContent.slice(0, firstLineChar).replace(/\S/g, " "); - context += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~"); - } - else if (i === lastLine) { - context += lineContent.slice(0, lastLineChar).replace(/./g, "~"); - } - else { - // Squiggle the entire line. - context += lineContent.replace(/./g, "~"); - } - context += resetEscapeSequence; + context += lineContent.slice(0, firstLineChar).replace(/\S/g, " "); + context += lineContent.slice(firstLineChar, lastCharForLine).replace(/./g, "~"); } - return context; + else if (i === lastLine) { + context += lineContent.slice(0, lastLineChar).replace(/./g, "~"); + } + else { + // Squiggle the entire line. + context += lineContent.replace(/./g, "~"); + } + context += resetEscapeSequence; } + return context; +} - /* @internal */ - export function formatLocation(file: SourceFile, start: number, host: FormatDiagnosticsHost, color = formatColorAndReset) { - const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); // TODO: GH#18217 - const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName; - - let output = ""; - output += color(relativeFileName, ForegroundColorEscapeSequences.Cyan); - output += ":"; - output += color(`${firstLine + 1}`, ForegroundColorEscapeSequences.Yellow); - output += ":"; - output += color(`${firstLineChar + 1}`, ForegroundColorEscapeSequences.Yellow); - return output; - } +/** @internal */ +export function formatLocation(file: SourceFile, start: number, host: FormatDiagnosticsHost, color = formatColorAndReset) { + const { line: firstLine, character: firstLineChar } = getLineAndCharacterOfPosition(file, start); // TODO: GH#18217 + const relativeFileName = host ? convertToRelativePath(file.fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)) : file.fileName; + + let output = ""; + output += color(relativeFileName, ForegroundColorEscapeSequences.Cyan); + output += ":"; + output += color(`${firstLine + 1}`, ForegroundColorEscapeSequences.Yellow); + output += ":"; + output += color(`${firstLineChar + 1}`, ForegroundColorEscapeSequences.Yellow); + return output; +} - export function formatDiagnosticsWithColorAndContext(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { - let output = ""; - for (const diagnostic of diagnostics) { - if (diagnostic.file) { - const { file, start } = diagnostic; - output += formatLocation(file, start!, host); // TODO: GH#18217 - output += " - "; - } +export function formatDiagnosticsWithColorAndContext(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost): string { + let output = ""; + for (const diagnostic of diagnostics) { + if (diagnostic.file) { + const { file, start } = diagnostic; + output += formatLocation(file, start!, host); // TODO: GH#18217 + output += " - "; + } - output += formatColorAndReset(diagnosticCategoryName(diagnostic), getCategoryFormat(diagnostic.category)); - output += formatColorAndReset(` TS${diagnostic.code}: `, ForegroundColorEscapeSequences.Grey); - output += flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine()); + output += formatColorAndReset(diagnosticCategoryName(diagnostic), getCategoryFormat(diagnostic.category)); + output += formatColorAndReset(` TS${diagnostic.code}: `, ForegroundColorEscapeSequences.Grey); + output += flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine()); - if (diagnostic.file) { - output += host.getNewLine(); - output += formatCodeSpan(diagnostic.file, diagnostic.start!, diagnostic.length!, "", getCategoryFormat(diagnostic.category), host); // TODO: GH#18217 - } - if (diagnostic.relatedInformation) { - output += host.getNewLine(); - for (const { file, start, length, messageText } of diagnostic.relatedInformation) { - if (file) { - output += host.getNewLine(); - output += halfIndent + formatLocation(file, start!, host); // TODO: GH#18217 - output += formatCodeSpan(file, start!, length!, indent, ForegroundColorEscapeSequences.Cyan, host); // TODO: GH#18217 - } + if (diagnostic.file) { + output += host.getNewLine(); + output += formatCodeSpan(diagnostic.file, diagnostic.start!, diagnostic.length!, "", getCategoryFormat(diagnostic.category), host); // TODO: GH#18217 + } + if (diagnostic.relatedInformation) { + output += host.getNewLine(); + for (const { file, start, length, messageText } of diagnostic.relatedInformation) { + if (file) { output += host.getNewLine(); - output += indent + flattenDiagnosticMessageText(messageText, host.getNewLine()); + output += halfIndent + formatLocation(file, start!, host); // TODO: GH#18217 + output += formatCodeSpan(file, start!, length!, indent, ForegroundColorEscapeSequences.Cyan, host); // TODO: GH#18217 } + output += host.getNewLine(); + output += indent + flattenDiagnosticMessageText(messageText, host.getNewLine()); } - output += host.getNewLine(); } - return output; + output += host.getNewLine(); } + return output; +} - export function flattenDiagnosticMessageText(diag: string | DiagnosticMessageChain | undefined, newLine: string, indent = 0): string { - if (isString(diag)) { - return diag; - } - else if (diag === undefined) { - return ""; - } - let result = ""; - if (indent) { - result += newLine; +export function flattenDiagnosticMessageText(diag: string | DiagnosticMessageChain | undefined, newLine: string, indent = 0): string { + if (isString(diag)) { + return diag; + } + else if (diag === undefined) { + return ""; + } + let result = ""; + if (indent) { + result += newLine; - for (let i = 0; i < indent; i++) { - result += " "; - } + for (let i = 0; i < indent; i++) { + result += " "; } - result += diag.messageText; - indent++; - if (diag.next) { - for (const kid of diag.next) { - result += flattenDiagnosticMessageText(kid, newLine, indent); - } + } + result += diag.messageText; + indent++; + if (diag.next) { + for (const kid of diag.next) { + result += flattenDiagnosticMessageText(kid, newLine, indent); } - return result; } + return result; +} - /* @internal */ - export function loadWithTypeDirectiveCache(names: string[] | readonly FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, containingFileMode: SourceFile["impliedNodeFormat"], loader: (name: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined, resolutionMode: SourceFile["impliedNodeFormat"]) => T): T[] { - if (names.length === 0) { - return []; +/** @internal */ +export function loadWithTypeDirectiveCache(names: string[] | readonly FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, containingFileMode: SourceFile["impliedNodeFormat"], loader: (name: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined, resolutionMode: SourceFile["impliedNodeFormat"]) => T): T[] { + if (names.length === 0) { + return []; + } + const resolutions: T[] = []; + const cache = new Map(); + for (const name of names) { + let result: T; + const mode = getModeForFileReference(name, containingFileMode); + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. + const strName = isString(name) ? name : name.fileName.toLowerCase(); + const cacheKey = mode !== undefined ? `${mode}|${strName}` : strName; + if (cache.has(cacheKey)) { + result = cache.get(cacheKey)!; } - const resolutions: T[] = []; - const cache = new Map(); - for (const name of names) { - let result: T; - const mode = getModeForFileReference(name, containingFileMode); - // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. - const strName = isString(name) ? name : name.fileName.toLowerCase(); - const cacheKey = mode !== undefined ? `${mode}|${strName}` : strName; - if (cache.has(cacheKey)) { - result = cache.get(cacheKey)!; - } - else { - cache.set(cacheKey, result = loader(strName, containingFile, redirectedReference, mode)); - } - resolutions.push(result); + else { + cache.set(cacheKey, result = loader(strName, containingFile, redirectedReference, mode)); } - return resolutions; + resolutions.push(result); } + return resolutions; +} - /** - * Subset of a SourceFile used to calculate index-based resolutions - * This includes some internal fields, so unless you have very good reason, - * (and are willing to use some less stable internals) you should probably just pass a SourceFile. - * - * @internal - */ - export interface SourceFileImportsList { - /* @internal */ imports: SourceFile["imports"]; - /* @internal */ moduleAugmentations: SourceFile["moduleAugmentations"]; - impliedNodeFormat?: SourceFile["impliedNodeFormat"]; - } +/** + * Subset of a SourceFile used to calculate index-based resolutions + * This includes some internal fields, so unless you have very good reason, + * (and are willing to use some less stable internals) you should probably just pass a SourceFile. + * + * @internal + */ +export interface SourceFileImportsList { + /** @internal */ imports: SourceFile["imports"]; + /** @internal */ moduleAugmentations: SourceFile["moduleAugmentations"]; + impliedNodeFormat?: SourceFile["impliedNodeFormat"]; +} - /** - * Calculates the resulting resolution mode for some reference in some file - this is generally the explicitly - * provided resolution mode in the reference, unless one is not present, in which case it is the mode of the containing file. - */ - export function getModeForFileReference(ref: FileReference | string, containingFileMode: SourceFile["impliedNodeFormat"]) { - return (isString(ref) ? containingFileMode : ref.resolutionMode) || containingFileMode; - } +/** + * Calculates the resulting resolution mode for some reference in some file - this is generally the explicitly + * provided resolution mode in the reference, unless one is not present, in which case it is the mode of the containing file. + */ +export function getModeForFileReference(ref: FileReference | string, containingFileMode: SourceFile["impliedNodeFormat"]) { + return (isString(ref) ? containingFileMode : ref.resolutionMode) || containingFileMode; +} - /** - * Calculates the final resolution mode for an import at some index within a file's imports list. This is generally the explicitly - * defined mode of the import if provided, or, if not, the mode of the containing file (with some exceptions: import=require is always commonjs, dynamic import is always esm). - * If you have an actual import node, prefer using getModeForUsageLocation on the reference string node. - * @param file File to fetch the resolution mode within - * @param index Index into the file's complete resolution list to get the resolution of - this is a concatenation of the file's imports and module augmentations - */ - export function getModeForResolutionAtIndex(file: SourceFile, index: number): ModuleKind.CommonJS | ModuleKind.ESNext | undefined; - /** @internal */ - // eslint-disable-next-line @typescript-eslint/unified-signatures - export function getModeForResolutionAtIndex(file: SourceFileImportsList, index: number): ModuleKind.CommonJS | ModuleKind.ESNext | undefined; - export function getModeForResolutionAtIndex(file: SourceFileImportsList, index: number): ModuleKind.CommonJS | ModuleKind.ESNext | undefined { - if (file.impliedNodeFormat === undefined) return undefined; - // we ensure all elements of file.imports and file.moduleAugmentations have the relevant parent pointers set during program setup, - // so it's safe to use them even pre-bind - return getModeForUsageLocation(file, getModuleNameStringLiteralAt(file, index)); - } - - /* @internal */ - export function isExclusivelyTypeOnlyImportOrExport(decl: ImportDeclaration | ExportDeclaration) { - if (isExportDeclaration(decl)) { - return decl.isTypeOnly; - } - if (decl.importClause?.isTypeOnly) { - return true; - } - return false; +/** + * Calculates the final resolution mode for an import at some index within a file's imports list. This is generally the explicitly + * defined mode of the import if provided, or, if not, the mode of the containing file (with some exceptions: import=require is always commonjs, dynamic import is always esm). + * If you have an actual import node, prefer using getModeForUsageLocation on the reference string node. + * @param file File to fetch the resolution mode within + * @param index Index into the file's complete resolution list to get the resolution of - this is a concatenation of the file's imports and module augmentations + */ +export function getModeForResolutionAtIndex(file: SourceFile, index: number): ModuleKind.CommonJS | ModuleKind.ESNext | undefined; +/** @internal */ +// eslint-disable-next-line @typescript-eslint/unified-signatures +export function getModeForResolutionAtIndex(file: SourceFileImportsList, index: number): ModuleKind.CommonJS | ModuleKind.ESNext | undefined; +export function getModeForResolutionAtIndex(file: SourceFileImportsList, index: number): ModuleKind.CommonJS | ModuleKind.ESNext | undefined { + if (file.impliedNodeFormat === undefined) return undefined; + // we ensure all elements of file.imports and file.moduleAugmentations have the relevant parent pointers set during program setup, + // so it's safe to use them even pre-bind + return getModeForUsageLocation(file, getModuleNameStringLiteralAt(file, index)); +} + +/** @internal */ +export function isExclusivelyTypeOnlyImportOrExport(decl: ImportDeclaration | ExportDeclaration) { + if (isExportDeclaration(decl)) { + return decl.isTypeOnly; } + if (decl.importClause?.isTypeOnly) { + return true; + } + return false; +} - /** - * Calculates the final resolution mode for a given module reference node. This is generally the explicitly provided resolution mode, if - * one exists, or the mode of the containing source file. (Excepting import=require, which is always commonjs, and dynamic import, which is always esm). - * Notably, this function always returns `undefined` if the containing file has an `undefined` `impliedNodeFormat` - this field is only set when - * `moduleResolution` is `node16`+. - * @param file The file the import or import-like reference is contained within - * @param usage The module reference string - * @returns The final resolution mode of the import - */ - export function getModeForUsageLocation(file: {impliedNodeFormat?: SourceFile["impliedNodeFormat"]}, usage: StringLiteralLike) { - if (file.impliedNodeFormat === undefined) return undefined; - if ((isImportDeclaration(usage.parent) || isExportDeclaration(usage.parent))) { - const isTypeOnly = isExclusivelyTypeOnlyImportOrExport(usage.parent); - if (isTypeOnly) { - const override = getResolutionModeOverrideForClause(usage.parent.assertClause); - if (override) { - return override; - } - } - } - if (usage.parent.parent && isImportTypeNode(usage.parent.parent)) { - const override = getResolutionModeOverrideForClause(usage.parent.parent.assertions?.assertClause); +/** + * Calculates the final resolution mode for a given module reference node. This is generally the explicitly provided resolution mode, if + * one exists, or the mode of the containing source file. (Excepting import=require, which is always commonjs, and dynamic import, which is always esm). + * Notably, this function always returns `undefined` if the containing file has an `undefined` `impliedNodeFormat` - this field is only set when + * `moduleResolution` is `node16`+. + * @param file The file the import or import-like reference is contained within + * @param usage The module reference string + * @returns The final resolution mode of the import + */ +export function getModeForUsageLocation(file: {impliedNodeFormat?: SourceFile["impliedNodeFormat"]}, usage: StringLiteralLike) { + if (file.impliedNodeFormat === undefined) return undefined; + if ((isImportDeclaration(usage.parent) || isExportDeclaration(usage.parent))) { + const isTypeOnly = isExclusivelyTypeOnlyImportOrExport(usage.parent); + if (isTypeOnly) { + const override = getResolutionModeOverrideForClause(usage.parent.assertClause); if (override) { return override; } } - if (file.impliedNodeFormat !== ModuleKind.ESNext) { - // in cjs files, import call expressions are esm format, otherwise everything is cjs - return isImportCall(walkUpParenthesizedExpressions(usage.parent)) ? ModuleKind.ESNext : ModuleKind.CommonJS; + } + if (usage.parent.parent && isImportTypeNode(usage.parent.parent)) { + const override = getResolutionModeOverrideForClause(usage.parent.parent.assertions?.assertClause); + if (override) { + return override; } - // in esm files, import=require statements are cjs format, otherwise everything is esm - // imports are only parent'd up to their containing declaration/expression, so access farther parents with care - const exprParentParent = walkUpParenthesizedExpressions(usage.parent)?.parent; - return exprParentParent && isImportEqualsDeclaration(exprParentParent) ? ModuleKind.CommonJS : ModuleKind.ESNext; } + if (file.impliedNodeFormat !== ModuleKind.ESNext) { + // in cjs files, import call expressions are esm format, otherwise everything is cjs + return isImportCall(walkUpParenthesizedExpressions(usage.parent)) ? ModuleKind.ESNext : ModuleKind.CommonJS; + } + // in esm files, import=require statements are cjs format, otherwise everything is esm + // imports are only parent'd up to their containing declaration/expression, so access farther parents with care + const exprParentParent = walkUpParenthesizedExpressions(usage.parent)?.parent; + return exprParentParent && isImportEqualsDeclaration(exprParentParent) ? ModuleKind.CommonJS : ModuleKind.ESNext; +} - /* @internal */ - export function getResolutionModeOverrideForClause(clause: AssertClause | undefined, grammarErrorOnNode?: (node: Node, diagnostic: DiagnosticMessage) => void) { - if (!clause) return undefined; - if (length(clause.elements) !== 1) { - grammarErrorOnNode?.(clause, Diagnostics.Type_import_assertions_should_have_exactly_one_key_resolution_mode_with_value_import_or_require); - return undefined; - } - const elem = clause.elements[0]; - if (!isStringLiteralLike(elem.name)) return undefined; - if (elem.name.text !== "resolution-mode") { - grammarErrorOnNode?.(elem.name, Diagnostics.resolution_mode_is_the_only_valid_key_for_type_import_assertions); - return undefined; - } - if (!isStringLiteralLike(elem.value)) return undefined; - if (elem.value.text !== "import" && elem.value.text !== "require") { - grammarErrorOnNode?.(elem.value, Diagnostics.resolution_mode_should_be_either_require_or_import); - return undefined; - } - return elem.value.text === "import" ? ModuleKind.ESNext : ModuleKind.CommonJS; +/** @internal */ +export function getResolutionModeOverrideForClause(clause: AssertClause | undefined, grammarErrorOnNode?: (node: Node, diagnostic: DiagnosticMessage) => void) { + if (!clause) return undefined; + if (length(clause.elements) !== 1) { + grammarErrorOnNode?.(clause, Diagnostics.Type_import_assertions_should_have_exactly_one_key_resolution_mode_with_value_import_or_require); + return undefined; + } + const elem = clause.elements[0]; + if (!isStringLiteralLike(elem.name)) return undefined; + if (elem.name.text !== "resolution-mode") { + grammarErrorOnNode?.(elem.name, Diagnostics.resolution_mode_is_the_only_valid_key_for_type_import_assertions); + return undefined; } + if (!isStringLiteralLike(elem.value)) return undefined; + if (elem.value.text !== "import" && elem.value.text !== "require") { + grammarErrorOnNode?.(elem.value, Diagnostics.resolution_mode_should_be_either_require_or_import); + return undefined; + } + return elem.value.text === "import" ? ModuleKind.ESNext : ModuleKind.CommonJS; +} - /* @internal */ - export function loadWithModeAwareCache(names: readonly StringLiteralLike[] | readonly string[], containingFile: SourceFile, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined, resolutionInfo: ModuleResolutionInfo | undefined, loader: (name: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { - if (names.length === 0) { - return []; +/** @internal */ +export function loadWithModeAwareCache(names: readonly StringLiteralLike[] | readonly string[], containingFile: SourceFile, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined, resolutionInfo: ModuleResolutionInfo | undefined, loader: (name: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { + if (names.length === 0) { + return []; + } + const resolutions: T[] = []; + const cache = new Map(); + let i = 0; + for (const entry of resolutionInfo ? resolutionInfo.names : names) { + let result: T; + const mode = !isString(entry) ? + getModeForUsageLocation(containingFile, entry) : + getModeForResolutionAtIndex(containingFile, i); + i++; + const name = isString(entry) ? entry : entry.text; + const cacheKey = mode !== undefined ? `${mode}|${name}` : name; + if (cache.has(cacheKey)) { + result = cache.get(cacheKey)!; } - const resolutions: T[] = []; - const cache = new Map(); - let i = 0; - for (const entry of resolutionInfo ? resolutionInfo.names : names) { - let result: T; - const mode = !isString(entry) ? - getModeForUsageLocation(containingFile, entry) : - getModeForResolutionAtIndex(containingFile, i); - i++; - const name = isString(entry) ? entry : entry.text; - const cacheKey = mode !== undefined ? `${mode}|${name}` : name; - if (cache.has(cacheKey)) { - result = cache.get(cacheKey)!; - } - else { - cache.set(cacheKey, result = loader(name, mode, containingFileName, redirectedReference)); - } - resolutions.push(result); + else { + cache.set(cacheKey, result = loader(name, mode, containingFileName, redirectedReference)); } - return resolutions; + resolutions.push(result); } + return resolutions; +} - /* @internal */ - export function forEachResolvedProjectReference( - resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, - cb: (resolvedProjectReference: ResolvedProjectReference, parent: ResolvedProjectReference | undefined) => T | undefined - ): T | undefined { - return forEachProjectReference(/*projectReferences*/ undefined, resolvedProjectReferences, (resolvedRef, parent) => resolvedRef && cb(resolvedRef, parent)); - } +/** @internal */ +export function forEachResolvedProjectReference( + resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, + cb: (resolvedProjectReference: ResolvedProjectReference, parent: ResolvedProjectReference | undefined) => T | undefined +): T | undefined { + return forEachProjectReference(/*projectReferences*/ undefined, resolvedProjectReferences, (resolvedRef, parent) => resolvedRef && cb(resolvedRef, parent)); +} + +function forEachProjectReference( + projectReferences: readonly ProjectReference[] | undefined, + resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, + cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, parent: ResolvedProjectReference | undefined, index: number) => T | undefined, + cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined +): T | undefined { + let seenResolvedRefs: Set | undefined; - function forEachProjectReference( + return worker(projectReferences, resolvedProjectReferences, /*parent*/ undefined); + + function worker( projectReferences: readonly ProjectReference[] | undefined, resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, - cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, parent: ResolvedProjectReference | undefined, index: number) => T | undefined, - cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined + parent: ResolvedProjectReference | undefined, ): T | undefined { - let seenResolvedRefs: Set | undefined; - - return worker(projectReferences, resolvedProjectReferences, /*parent*/ undefined); - function worker( - projectReferences: readonly ProjectReference[] | undefined, - resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined, - parent: ResolvedProjectReference | undefined, - ): T | undefined { + // Visit project references first + if (cbRef) { + const result = cbRef(projectReferences, parent); + if (result) return result; + } - // Visit project references first - if (cbRef) { - const result = cbRef(projectReferences, parent); - if (result) return result; + return forEach(resolvedProjectReferences, (resolvedRef, index) => { + if (resolvedRef && seenResolvedRefs?.has(resolvedRef.sourceFile.path)) { + // ignore recursives + return undefined; } - return forEach(resolvedProjectReferences, (resolvedRef, index) => { - if (resolvedRef && seenResolvedRefs?.has(resolvedRef.sourceFile.path)) { - // ignore recursives - return undefined; - } + const result = cbResolvedRef(resolvedRef, parent, index); + if (result || !resolvedRef) return result; - const result = cbResolvedRef(resolvedRef, parent, index); - if (result || !resolvedRef) return result; - - (seenResolvedRefs ||= new Set()).add(resolvedRef.sourceFile.path); - return worker(resolvedRef.commandLine.projectReferences, resolvedRef.references, resolvedRef); - }); - } + (seenResolvedRefs ||= new Set()).add(resolvedRef.sourceFile.path); + return worker(resolvedRef.commandLine.projectReferences, resolvedRef.references, resolvedRef); + }); } +} - /* @internal */ - export const inferredTypesContainingFile = "__inferred type names__.ts"; +/** @internal */ +export const inferredTypesContainingFile = "__inferred type names__.ts"; - interface DiagnosticCache { - perFile?: ESMap; - allDiagnostics?: readonly T[]; - } +interface DiagnosticCache { + perFile?: ESMap; + allDiagnostics?: readonly T[]; +} - /*@internal*/ - export function isReferencedFile(reason: FileIncludeReason | undefined): reason is ReferencedFile { - switch (reason?.kind) { - case FileIncludeKind.Import: - case FileIncludeKind.ReferenceFile: - case FileIncludeKind.TypeReferenceDirective: - case FileIncludeKind.LibReferenceDirective: - return true; - default: - return false; - } +/** @internal */ +export function isReferencedFile(reason: FileIncludeReason | undefined): reason is ReferencedFile { + switch (reason?.kind) { + case FileIncludeKind.Import: + case FileIncludeKind.ReferenceFile: + case FileIncludeKind.TypeReferenceDirective: + case FileIncludeKind.LibReferenceDirective: + return true; + default: + return false; } +} - /*@internal*/ - export interface ReferenceFileLocation { - file: SourceFile; - pos: number; - end: number; - packageId: PackageId | undefined; +/** @internal */ +export interface ReferenceFileLocation { + file: SourceFile; + pos: number; + end: number; + packageId: PackageId | undefined; +} + +/** @internal */ +export interface SyntheticReferenceFileLocation { + file: SourceFile; + packageId: PackageId | undefined; + text: string; +} + +/** @internal */ +export function isReferenceFileLocation(location: ReferenceFileLocation | SyntheticReferenceFileLocation): location is ReferenceFileLocation { + return (location as ReferenceFileLocation).pos !== undefined; +} + +/** @internal */ +export function getReferencedFileLocation(getSourceFileByPath: (path: Path) => SourceFile | undefined, ref: ReferencedFile): ReferenceFileLocation | SyntheticReferenceFileLocation { + const file = Debug.checkDefined(getSourceFileByPath(ref.file)); + const { kind, index } = ref; + let pos: number | undefined, end: number | undefined, packageId: PackageId | undefined, resolutionMode: FileReference["resolutionMode"] | undefined; + switch (kind) { + case FileIncludeKind.Import: + const importLiteral = getModuleNameStringLiteralAt(file, index); + packageId = file.resolvedModules?.get(importLiteral.text, getModeForResolutionAtIndex(file, index))?.packageId; + if (importLiteral.pos === -1) return { file, packageId, text: importLiteral.text }; + pos = skipTrivia(file.text, importLiteral.pos); + end = importLiteral.end; + break; + case FileIncludeKind.ReferenceFile: + ({ pos, end } = file.referencedFiles[index]); + break; + case FileIncludeKind.TypeReferenceDirective: + ({ pos, end, resolutionMode } = file.typeReferenceDirectives[index]); + packageId = file.resolvedTypeReferenceDirectiveNames?.get(toFileNameLowerCase(file.typeReferenceDirectives[index].fileName), resolutionMode || file.impliedNodeFormat)?.packageId; + break; + case FileIncludeKind.LibReferenceDirective: + ({ pos, end } = file.libReferenceDirectives[index]); + break; + default: + return Debug.assertNever(kind); } + return { file, pos, end, packageId }; +} - /*@internal*/ - export interface SyntheticReferenceFileLocation { - file: SourceFile; - packageId: PackageId | undefined; - text: string; +/** + * Determines if program structure is upto date or needs to be recreated + * + * @internal + */ +export function isProgramUptoDate( + program: Program | undefined, + rootFileNames: string[], + newOptions: CompilerOptions, + getSourceVersion: (path: Path, fileName: string) => string | undefined, + fileExists: (fileName: string) => boolean, + hasInvalidatedResolutions: HasInvalidatedResolutions, + hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined, + getParsedCommandLine: (fileName: string) => ParsedCommandLine | undefined, + projectReferences: readonly ProjectReference[] | undefined +): boolean { + // If we haven't created a program yet or have changed automatic type directives, then it is not up-to-date + if (!program || hasChangedAutomaticTypeDirectiveNames?.()) return false; + + // If root file names don't match + if (!arrayIsEqualTo(program.getRootFileNames(), rootFileNames)) return false; + + let seenResolvedRefs: ResolvedProjectReference[] | undefined; + + // If project references don't match + if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) return false; + + // If any file is not up-to-date, then the whole program is not up-to-date + if (program.getSourceFiles().some(sourceFileNotUptoDate)) return false; + + // If any of the missing file paths are now created + if (program.getMissingFilePaths().some(fileExists)) return false; + + const currentOptions = program.getCompilerOptions(); + // If the compilation settings do no match, then the program is not up-to-date + if (!compareDataObjects(currentOptions, newOptions)) return false; + + // If everything matches but the text of config file is changed, + // error locations can change for program options, so update the program + if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text; + + return true; + + function sourceFileNotUptoDate(sourceFile: SourceFile) { + return !sourceFileVersionUptoDate(sourceFile) || + hasInvalidatedResolutions(sourceFile.path); } - /*@internal*/ - export function isReferenceFileLocation(location: ReferenceFileLocation | SyntheticReferenceFileLocation): location is ReferenceFileLocation { - return (location as ReferenceFileLocation).pos !== undefined; + function sourceFileVersionUptoDate(sourceFile: SourceFile) { + return sourceFile.version === getSourceVersion(sourceFile.resolvedPath, sourceFile.fileName); } - /*@internal*/ - export function getReferencedFileLocation(getSourceFileByPath: (path: Path) => SourceFile | undefined, ref: ReferencedFile): ReferenceFileLocation | SyntheticReferenceFileLocation { - const file = Debug.checkDefined(getSourceFileByPath(ref.file)); - const { kind, index } = ref; - let pos: number | undefined, end: number | undefined, packageId: PackageId | undefined, resolutionMode: FileReference["resolutionMode"] | undefined; - switch (kind) { - case FileIncludeKind.Import: - const importLiteral = getModuleNameStringLiteralAt(file, index); - packageId = file.resolvedModules?.get(importLiteral.text, getModeForResolutionAtIndex(file, index))?.packageId; - if (importLiteral.pos === -1) return { file, packageId, text: importLiteral.text }; - pos = skipTrivia(file.text, importLiteral.pos); - end = importLiteral.end; - break; - case FileIncludeKind.ReferenceFile: - ({ pos, end } = file.referencedFiles[index]); - break; - case FileIncludeKind.TypeReferenceDirective: - ({ pos, end, resolutionMode } = file.typeReferenceDirectives[index]); - packageId = file.resolvedTypeReferenceDirectiveNames?.get(toFileNameLowerCase(file.typeReferenceDirectives[index].fileName), resolutionMode || file.impliedNodeFormat)?.packageId; - break; - case FileIncludeKind.LibReferenceDirective: - ({ pos, end } = file.libReferenceDirectives[index]); - break; - default: - return Debug.assertNever(kind); - } - return { file, pos, end, packageId }; + function projectReferenceUptoDate(oldRef: ProjectReference, newRef: ProjectReference, index: number) { + return projectReferenceIsEqualTo(oldRef, newRef) && + resolvedProjectReferenceUptoDate(program!.getResolvedProjectReferences()![index], oldRef); } - /** - * Determines if program structure is upto date or needs to be recreated - */ - /* @internal */ - export function isProgramUptoDate( - program: Program | undefined, - rootFileNames: string[], - newOptions: CompilerOptions, - getSourceVersion: (path: Path, fileName: string) => string | undefined, - fileExists: (fileName: string) => boolean, - hasInvalidatedResolutions: HasInvalidatedResolutions, - hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined, - getParsedCommandLine: (fileName: string) => ParsedCommandLine | undefined, - projectReferences: readonly ProjectReference[] | undefined - ): boolean { - // If we haven't created a program yet or have changed automatic type directives, then it is not up-to-date - if (!program || hasChangedAutomaticTypeDirectiveNames?.()) return false; - - // If root file names don't match - if (!arrayIsEqualTo(program.getRootFileNames(), rootFileNames)) return false; - - let seenResolvedRefs: ResolvedProjectReference[] | undefined; - - // If project references don't match - if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) return false; - - // If any file is not up-to-date, then the whole program is not up-to-date - if (program.getSourceFiles().some(sourceFileNotUptoDate)) return false; - - // If any of the missing file paths are now created - if (program.getMissingFilePaths().some(fileExists)) return false; - - const currentOptions = program.getCompilerOptions(); - // If the compilation settings do no match, then the program is not up-to-date - if (!compareDataObjects(currentOptions, newOptions)) return false; - - // If everything matches but the text of config file is changed, - // error locations can change for program options, so update the program - if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text; + function resolvedProjectReferenceUptoDate(oldResolvedRef: ResolvedProjectReference | undefined, oldRef: ProjectReference): boolean { + if (oldResolvedRef) { + // Assume true + if (contains(seenResolvedRefs, oldResolvedRef)) return true; - return true; + const refPath = resolveProjectReferencePath(oldRef); + const newParsedCommandLine = getParsedCommandLine(refPath); - function sourceFileNotUptoDate(sourceFile: SourceFile) { - return !sourceFileVersionUptoDate(sourceFile) || - hasInvalidatedResolutions(sourceFile.path); - } + // Check if config file exists + if (!newParsedCommandLine) return false; - function sourceFileVersionUptoDate(sourceFile: SourceFile) { - return sourceFile.version === getSourceVersion(sourceFile.resolvedPath, sourceFile.fileName); - } + // If change in source file + if (oldResolvedRef.commandLine.options.configFile !== newParsedCommandLine.options.configFile) return false; - function projectReferenceUptoDate(oldRef: ProjectReference, newRef: ProjectReference, index: number) { - return projectReferenceIsEqualTo(oldRef, newRef) && - resolvedProjectReferenceUptoDate(program!.getResolvedProjectReferences()![index], oldRef); - } + // check file names + if (!arrayIsEqualTo(oldResolvedRef.commandLine.fileNames, newParsedCommandLine.fileNames)) return false; - function resolvedProjectReferenceUptoDate(oldResolvedRef: ResolvedProjectReference | undefined, oldRef: ProjectReference): boolean { - if (oldResolvedRef) { - // Assume true - if (contains(seenResolvedRefs, oldResolvedRef)) return true; + // Add to seen before checking the referenced paths of this config file + (seenResolvedRefs || (seenResolvedRefs = [])).push(oldResolvedRef); - const refPath = resolveProjectReferencePath(oldRef); - const newParsedCommandLine = getParsedCommandLine(refPath); + // If child project references are upto date, this project reference is uptodate + return !forEach(oldResolvedRef.references, (childResolvedRef, index) => + !resolvedProjectReferenceUptoDate(childResolvedRef, oldResolvedRef.commandLine.projectReferences![index])); + } - // Check if config file exists - if (!newParsedCommandLine) return false; + // In old program, not able to resolve project reference path, + // so if config file doesnt exist, it is uptodate. + const refPath = resolveProjectReferencePath(oldRef); + return !getParsedCommandLine(refPath); + } +} - // If change in source file - if (oldResolvedRef.commandLine.options.configFile !== newParsedCommandLine.options.configFile) return false; +export function getConfigFileParsingDiagnostics(configFileParseResult: ParsedCommandLine): readonly Diagnostic[] { + return configFileParseResult.options.configFile ? + [...configFileParseResult.options.configFile.parseDiagnostics, ...configFileParseResult.errors] : + configFileParseResult.errors; +} - // check file names - if (!arrayIsEqualTo(oldResolvedRef.commandLine.fileNames, newParsedCommandLine.fileNames)) return false; +/** + * A function for determining if a given file is esm or cjs format, assuming modern node module resolution rules, as configured by the + * `options` parameter. + * + * @param fileName The normalized absolute path to check the format of (it need not exist on disk) + * @param [packageJsonInfoCache] A cache for package file lookups - it's best to have a cache when this function is called often + * @param host The ModuleResolutionHost which can perform the filesystem lookups for package json data + * @param options The compiler options to perform the analysis under - relevant options are `moduleResolution` and `traceResolution` + * @returns `undefined` if the path has no relevant implied format, `ModuleKind.ESNext` for esm format, and `ModuleKind.CommonJS` for cjs format + */ +export function getImpliedNodeFormatForFile(fileName: Path, packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): ModuleKind.ESNext | ModuleKind.CommonJS | undefined { + const result = getImpliedNodeFormatForFileWorker(fileName, packageJsonInfoCache, host, options); + return typeof result === "object" ? result.impliedNodeFormat : result; +} - // Add to seen before checking the referenced paths of this config file - (seenResolvedRefs || (seenResolvedRefs = [])).push(oldResolvedRef); +/** @internal */ +export function getImpliedNodeFormatForFileWorker( + fileName: string, + packageJsonInfoCache: PackageJsonInfoCache | undefined, + host: ModuleResolutionHost, + options: CompilerOptions, +) { + switch (getEmitModuleResolutionKind(options)) { + case ModuleResolutionKind.Node16: + case ModuleResolutionKind.NodeNext: + return fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Mjs]) ? ModuleKind.ESNext : + fileExtensionIsOneOf(fileName, [Extension.Dcts, Extension.Cts, Extension.Cjs]) ? ModuleKind.CommonJS : + fileExtensionIsOneOf(fileName, [Extension.Dts, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx]) ? lookupFromPackageJson() : + undefined; // other extensions, like `json` or `tsbuildinfo`, are set as `undefined` here but they should never be fed through the transformer pipeline + default: + return undefined; + } + function lookupFromPackageJson(): Partial { + const state = getTemporaryModuleResolutionState(packageJsonInfoCache, host, options); + const packageJsonLocations: string[] = []; + state.failedLookupLocations = packageJsonLocations; + state.affectingLocations = packageJsonLocations; + const packageJsonScope = getPackageScopeForPath(fileName, state); + const impliedNodeFormat = packageJsonScope?.contents.packageJsonContent.type === "module" ? ModuleKind.ESNext : ModuleKind.CommonJS; + return { impliedNodeFormat, packageJsonLocations, packageJsonScope }; + } +} - // If child project references are upto date, this project reference is uptodate - return !forEach(oldResolvedRef.references, (childResolvedRef, index) => - !resolvedProjectReferenceUptoDate(childResolvedRef, oldResolvedRef.commandLine.projectReferences![index])); - } +/** @internal */ +export const plainJSErrors: Set = new Set([ + // binder errors + Diagnostics.Cannot_redeclare_block_scoped_variable_0.code, + Diagnostics.A_module_cannot_have_multiple_default_exports.code, + Diagnostics.Another_export_default_is_here.code, + Diagnostics.The_first_export_default_is_here.code, + Diagnostics.Identifier_expected_0_is_a_reserved_word_at_the_top_level_of_a_module.code, + Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode.code, + Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here.code, + Diagnostics.constructor_is_a_reserved_word.code, + Diagnostics.delete_cannot_be_called_on_an_identifier_in_strict_mode.code, + Diagnostics.Code_contained_in_a_class_is_evaluated_in_JavaScript_s_strict_mode_which_does_not_allow_this_use_of_0_For_more_information_see_https_Colon_Slash_Slashdeveloper_mozilla_org_Slashen_US_Slashdocs_SlashWeb_SlashJavaScript_SlashReference_SlashStrict_mode.code, + Diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode.code, + Diagnostics.Invalid_use_of_0_in_strict_mode.code, + Diagnostics.A_label_is_not_allowed_here.code, + Diagnostics.Octal_literals_are_not_allowed_in_strict_mode.code, + Diagnostics.with_statements_are_not_allowed_in_strict_mode.code, + // grammar errors + Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement.code, + Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement.code, + Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name.code, + Diagnostics.A_class_member_cannot_have_the_0_keyword.code, + Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name.code, + Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement.code, + Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement.code, + Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement.code, + Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement.code, + Diagnostics.A_default_export_must_be_at_the_top_level_of_a_file_or_module_declaration.code, + Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context.code, + Diagnostics.A_destructuring_declaration_must_have_an_initializer.code, + Diagnostics.A_get_accessor_cannot_have_parameters.code, + Diagnostics.A_rest_element_cannot_contain_a_binding_pattern.code, + Diagnostics.A_rest_element_cannot_have_a_property_name.code, + Diagnostics.A_rest_element_cannot_have_an_initializer.code, + Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern.code, + Diagnostics.A_rest_parameter_cannot_have_an_initializer.code, + Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list.code, + Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma.code, + Diagnostics.A_return_statement_cannot_be_used_inside_a_class_static_block.code, + Diagnostics.A_set_accessor_cannot_have_rest_parameter.code, + Diagnostics.A_set_accessor_must_have_exactly_one_parameter.code, + Diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_module.code, + Diagnostics.An_export_declaration_cannot_have_modifiers.code, + Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module.code, + Diagnostics.An_import_declaration_cannot_have_modifiers.code, + Diagnostics.An_object_member_cannot_be_declared_optional.code, + Diagnostics.Argument_of_dynamic_import_cannot_be_spread_element.code, + Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable.code, + Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause.code, + Diagnostics.Catch_clause_variable_cannot_have_an_initializer.code, + Diagnostics.Class_decorators_can_t_be_used_with_static_private_identifier_Consider_removing_the_experimental_decorator.code, + Diagnostics.Classes_can_only_extend_a_single_class.code, + Diagnostics.Classes_may_not_have_a_field_named_constructor.code, + Diagnostics.Did_you_mean_to_use_a_Colon_An_can_only_follow_a_property_name_when_the_containing_object_literal_is_part_of_a_destructuring_pattern.code, + Diagnostics.Duplicate_label_0.code, + Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_assertion_as_arguments.code, + Diagnostics.For_await_loops_cannot_be_used_inside_a_class_static_block.code, + Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression.code, + Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name.code, + Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array.code, + Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names.code, + Diagnostics.Jump_target_cannot_cross_function_boundary.code, + Diagnostics.Line_terminator_not_permitted_before_arrow.code, + Diagnostics.Modifiers_cannot_appear_here.code, + Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement.code, + Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement.code, + Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies.code, + Diagnostics.Private_identifiers_are_only_allowed_in_class_bodies_and_may_only_be_used_as_part_of_a_class_member_declaration_property_access_or_on_the_left_hand_side_of_an_in_expression.code, + Diagnostics.Property_0_is_not_accessible_outside_class_1_because_it_has_a_private_identifier.code, + Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain.code, + Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_async.code, + Diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer.code, + Diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer.code, + Diagnostics.Trailing_comma_not_allowed.code, + Diagnostics.Variable_declaration_list_cannot_be_empty.code, + Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses.code, + Diagnostics._0_expected.code, + Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2.code, + Diagnostics._0_list_cannot_be_empty.code, + Diagnostics._0_modifier_already_seen.code, + Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration.code, + Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element.code, + Diagnostics._0_modifier_cannot_appear_on_a_parameter.code, + Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind.code, + Diagnostics._0_modifier_cannot_be_used_here.code, + Diagnostics._0_modifier_must_precede_1_modifier.code, + Diagnostics.const_declarations_can_only_be_declared_inside_a_block.code, + Diagnostics.const_declarations_must_be_initialized.code, + Diagnostics.extends_clause_already_seen.code, + Diagnostics.let_declarations_can_only_be_declared_inside_a_block.code, + Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations.code, + Diagnostics.Class_constructor_may_not_be_a_generator.code, + Diagnostics.Class_constructor_may_not_be_an_accessor.code, + Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules.code, +]); + +/** + * Determine if source file needs to be re-created even if its text hasn't changed + */ +function shouldProgramCreateNewSourceFiles(program: Program | undefined, newOptions: CompilerOptions): boolean { + if (!program) return false; + // If any compiler options change, we can't reuse old source file even if version match + // The change in options like these could result in change in syntax tree or `sourceFile.bindDiagnostics`. + return optionsHaveChanges(program.getCompilerOptions(), newOptions, sourceFileAffectingCompilerOptions); +} - // In old program, not able to resolve project reference path, - // so if config file doesnt exist, it is uptodate. - const refPath = resolveProjectReferencePath(oldRef); - return !getParsedCommandLine(refPath); - } +function createCreateProgramOptions(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): CreateProgramOptions { + return { + rootNames, + options, + host, + oldProgram, + configFileParsingDiagnostics + }; +} + +/** + * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' + * that represent a compilation unit. + * + * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and + * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. + * + * @param createProgramOptions - The options for creating a program. + * @returns A 'Program' object. + */ +export function createProgram(createProgramOptions: CreateProgramOptions): Program; +/** + * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' + * that represent a compilation unit. + * + * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and + * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. + * + * @param rootNames - A set of root files. + * @param options - The compiler options which should be used. + * @param host - The host interacts with the underlying file system. + * @param oldProgram - Reuses an old program structure. + * @param configFileParsingDiagnostics - error during config file parsing + * @returns A 'Program' object. + */ +export function createProgram(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): Program; +export function createProgram(rootNamesOrOptions: readonly string[] | CreateProgramOptions, _options?: CompilerOptions, _host?: CompilerHost, _oldProgram?: Program, _configFileParsingDiagnostics?: readonly Diagnostic[]): Program { + const createProgramOptions = isArray(rootNamesOrOptions) ? createCreateProgramOptions(rootNamesOrOptions, _options!, _host, _oldProgram, _configFileParsingDiagnostics) : rootNamesOrOptions; // TODO: GH#18217 + const { rootNames, options, configFileParsingDiagnostics, projectReferences } = createProgramOptions; + let { oldProgram } = createProgramOptions; + + let processingDefaultLibFiles: SourceFile[] | undefined; + let processingOtherFiles: SourceFile[] | undefined; + let files: SourceFile[]; + let symlinks: SymlinkCache | undefined; + let commonSourceDirectory: string; + let typeChecker: TypeChecker; + let classifiableNames: Set<__String>; + const ambientModuleNameToUnmodifiedFileName = new Map(); + let fileReasons = createMultiMap(); + const cachedBindAndCheckDiagnosticsForFile: DiagnosticCache = {}; + const cachedDeclarationDiagnosticsForFile: DiagnosticCache = {}; + + let resolvedTypeReferenceDirectives = createModeAwareCache(); + let fileProcessingDiagnostics: FilePreprocessingDiagnostics[] | undefined; + + // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. + // This works as imported modules are discovered recursively in a depth first manner, specifically: + // - For each root file, findSourceFile is called. + // - This calls processImportedModules for each module imported in the source file. + // - This calls resolveModuleNames, and then calls findSourceFile for each resolved module. + // As all these operations happen - and are nested - within the createProgram call, they close over the below variables. + // The current resolution depth is tracked by incrementing/decrementing as the depth first search progresses. + const maxNodeModuleJsDepth = typeof options.maxNodeModuleJsDepth === "number" ? options.maxNodeModuleJsDepth : 0; + let currentNodeModulesDepth = 0; + + // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track + // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. + const modulesWithElidedImports = new Map(); + + // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. + const sourceFilesFoundSearchingNodeModules = new Map(); + + tracing?.push(tracing.Phase.Program, "createProgram", { configFilePath: options.configFilePath, rootDir: options.rootDir }, /*separateBeginAndEnd*/ true); + performance.mark("beforeProgram"); + + const host = createProgramOptions.host || createCompilerHost(options); + const configParsingHost = parseConfigHostFromCompilerHostLike(host); + + let skipDefaultLib = options.noLib; + const getDefaultLibraryFileName = memoize(() => host.getDefaultLibFileName(options)); + const defaultLibraryPath = host.getDefaultLibLocation ? host.getDefaultLibLocation() : getDirectoryPath(getDefaultLibraryFileName()); + const programDiagnostics = createDiagnosticCollection(); + const currentDirectory = host.getCurrentDirectory(); + const supportedExtensions = getSupportedExtensions(options); + const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); + + // Map storing if there is emit blocking diagnostics for given input + const hasEmitBlockingDiagnostics = new Map(); + let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression | false | undefined; + + let moduleResolutionCache: ModuleResolutionCache | undefined; + let typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined; + let actualResolveModuleNamesWorker: ( + moduleNames: readonly StringLiteralLike[], + containingFile: SourceFile, + containingFileName: string, + redirectedReference: ResolvedProjectReference | undefined, + resolutionInfo: ModuleResolutionInfo | undefined, + ) => (ResolvedModuleFull | undefined)[]; + const hasInvalidatedResolutions = host.hasInvalidatedResolutions || returnFalse; + if (host.resolveModuleNames) { + actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo) => + host.resolveModuleNames!( + moduleNames.map(literal => literal.text), + containingFileName, + resolutionInfo?.reusedNames?.map(literal => literal.text), + redirectedReference, + options, + containingFile, + resolutionInfo, + ).map(resolved => { + // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. + if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) { + return resolved as ResolvedModuleFull; + } + const withExtension = clone(resolved) as ResolvedModuleFull; + withExtension.extension = extensionFromPath(resolved.resolvedFileName); + return withExtension; + }); + moduleResolutionCache = host.getModuleResolutionCache?.(); + } + else { + moduleResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options); + const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => + resolveModuleName(moduleName, containingFileName, options, host, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule; + actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo) => + loadWithModeAwareCache(moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo, loader); } - export function getConfigFileParsingDiagnostics(configFileParseResult: ParsedCommandLine): readonly Diagnostic[] { - return configFileParseResult.options.configFile ? - [...configFileParseResult.options.configFile.parseDiagnostics, ...configFileParseResult.errors] : - configFileParseResult.errors; + let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined) => (ResolvedTypeReferenceDirective | undefined)[]; + if (host.resolveTypeReferenceDirectives) { + actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference, containingFileMode) => host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options, containingFileMode); } + else { + typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache()); + const loader = (typesRef: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined, resolutionMode: SourceFile["impliedNodeFormat"] | undefined) => resolveTypeReferenceDirective( + typesRef, + containingFile, + options, + host, + redirectedReference, + typeReferenceDirectiveResolutionCache, + resolutionMode, + ).resolvedTypeReferenceDirective!; // TODO: GH#18217 + actualResolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile, redirectedReference, containingFileMode) => loadWithTypeDirectiveCache(Debug.checkEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, containingFileMode, loader); + } + + // Map from a stringified PackageId to the source file with that id. + // Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile). + // `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around. + const packageIdToSourceFile = new Map(); + // Maps from a SourceFile's `.path` to the name of the package it was imported with. + let sourceFileToPackageName = new Map(); + // Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it. + let redirectTargetsMap = createMultiMap(); + let usesUriStyleNodeCoreModules = false; /** - * A function for determining if a given file is esm or cjs format, assuming modern node module resolution rules, as configured by the - * `options` parameter. - * - * @param fileName The normalized absolute path to check the format of (it need not exist on disk) - * @param [packageJsonInfoCache] A cache for package file lookups - it's best to have a cache when this function is called often - * @param host The ModuleResolutionHost which can perform the filesystem lookups for package json data - * @param options The compiler options to perform the analysis under - relevant options are `moduleResolution` and `traceResolution` - * @returns `undefined` if the path has no relevant implied format, `ModuleKind.ESNext` for esm format, and `ModuleKind.CommonJS` for cjs format + * map with + * - SourceFile if present + * - false if sourceFile missing for source of project reference redirect + * - undefined otherwise */ - export function getImpliedNodeFormatForFile(fileName: Path, packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): ModuleKind.ESNext | ModuleKind.CommonJS | undefined { - const result = getImpliedNodeFormatForFileWorker(fileName, packageJsonInfoCache, host, options); - return typeof result === "object" ? result.impliedNodeFormat : result; - } - - /*@internal*/ - export function getImpliedNodeFormatForFileWorker( - fileName: string, - packageJsonInfoCache: PackageJsonInfoCache | undefined, - host: ModuleResolutionHost, - options: CompilerOptions, - ) { - switch (getEmitModuleResolutionKind(options)) { - case ModuleResolutionKind.Node16: - case ModuleResolutionKind.NodeNext: - return fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Mjs]) ? ModuleKind.ESNext : - fileExtensionIsOneOf(fileName, [Extension.Dcts, Extension.Cts, Extension.Cjs]) ? ModuleKind.CommonJS : - fileExtensionIsOneOf(fileName, [Extension.Dts, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx]) ? lookupFromPackageJson() : - undefined; // other extensions, like `json` or `tsbuildinfo`, are set as `undefined` here but they should never be fed through the transformer pipeline - default: - return undefined; - } - function lookupFromPackageJson(): Partial { - const state = getTemporaryModuleResolutionState(packageJsonInfoCache, host, options); - const packageJsonLocations: string[] = []; - state.failedLookupLocations = packageJsonLocations; - state.affectingLocations = packageJsonLocations; - const packageJsonScope = getPackageScopeForPath(fileName, state); - const impliedNodeFormat = packageJsonScope?.contents.packageJsonContent.type === "module" ? ModuleKind.ESNext : ModuleKind.CommonJS; - return { impliedNodeFormat, packageJsonLocations, packageJsonScope }; - } - } - - /** @internal */ - export const plainJSErrors: Set = new Set([ - // binder errors - Diagnostics.Cannot_redeclare_block_scoped_variable_0.code, - Diagnostics.A_module_cannot_have_multiple_default_exports.code, - Diagnostics.Another_export_default_is_here.code, - Diagnostics.The_first_export_default_is_here.code, - Diagnostics.Identifier_expected_0_is_a_reserved_word_at_the_top_level_of_a_module.code, - Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode.code, - Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here.code, - Diagnostics.constructor_is_a_reserved_word.code, - Diagnostics.delete_cannot_be_called_on_an_identifier_in_strict_mode.code, - Diagnostics.Code_contained_in_a_class_is_evaluated_in_JavaScript_s_strict_mode_which_does_not_allow_this_use_of_0_For_more_information_see_https_Colon_Slash_Slashdeveloper_mozilla_org_Slashen_US_Slashdocs_SlashWeb_SlashJavaScript_SlashReference_SlashStrict_mode.code, - Diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode.code, - Diagnostics.Invalid_use_of_0_in_strict_mode.code, - Diagnostics.A_label_is_not_allowed_here.code, - Diagnostics.Octal_literals_are_not_allowed_in_strict_mode.code, - Diagnostics.with_statements_are_not_allowed_in_strict_mode.code, - // grammar errors - Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement.code, - Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement.code, - Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name.code, - Diagnostics.A_class_member_cannot_have_the_0_keyword.code, - Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name.code, - Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement.code, - Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement.code, - Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement.code, - Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement.code, - Diagnostics.A_default_export_must_be_at_the_top_level_of_a_file_or_module_declaration.code, - Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context.code, - Diagnostics.A_destructuring_declaration_must_have_an_initializer.code, - Diagnostics.A_get_accessor_cannot_have_parameters.code, - Diagnostics.A_rest_element_cannot_contain_a_binding_pattern.code, - Diagnostics.A_rest_element_cannot_have_a_property_name.code, - Diagnostics.A_rest_element_cannot_have_an_initializer.code, - Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern.code, - Diagnostics.A_rest_parameter_cannot_have_an_initializer.code, - Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list.code, - Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma.code, - Diagnostics.A_return_statement_cannot_be_used_inside_a_class_static_block.code, - Diagnostics.A_set_accessor_cannot_have_rest_parameter.code, - Diagnostics.A_set_accessor_must_have_exactly_one_parameter.code, - Diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_module.code, - Diagnostics.An_export_declaration_cannot_have_modifiers.code, - Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module.code, - Diagnostics.An_import_declaration_cannot_have_modifiers.code, - Diagnostics.An_object_member_cannot_be_declared_optional.code, - Diagnostics.Argument_of_dynamic_import_cannot_be_spread_element.code, - Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable.code, - Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause.code, - Diagnostics.Catch_clause_variable_cannot_have_an_initializer.code, - Diagnostics.Class_decorators_can_t_be_used_with_static_private_identifier_Consider_removing_the_experimental_decorator.code, - Diagnostics.Classes_can_only_extend_a_single_class.code, - Diagnostics.Classes_may_not_have_a_field_named_constructor.code, - Diagnostics.Did_you_mean_to_use_a_Colon_An_can_only_follow_a_property_name_when_the_containing_object_literal_is_part_of_a_destructuring_pattern.code, - Diagnostics.Duplicate_label_0.code, - Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_assertion_as_arguments.code, - Diagnostics.For_await_loops_cannot_be_used_inside_a_class_static_block.code, - Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression.code, - Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name.code, - Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array.code, - Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names.code, - Diagnostics.Jump_target_cannot_cross_function_boundary.code, - Diagnostics.Line_terminator_not_permitted_before_arrow.code, - Diagnostics.Modifiers_cannot_appear_here.code, - Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement.code, - Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement.code, - Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies.code, - Diagnostics.Private_identifiers_are_only_allowed_in_class_bodies_and_may_only_be_used_as_part_of_a_class_member_declaration_property_access_or_on_the_left_hand_side_of_an_in_expression.code, - Diagnostics.Property_0_is_not_accessible_outside_class_1_because_it_has_a_private_identifier.code, - Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain.code, - Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_async.code, - Diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer.code, - Diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer.code, - Diagnostics.Trailing_comma_not_allowed.code, - Diagnostics.Variable_declaration_list_cannot_be_empty.code, - Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses.code, - Diagnostics._0_expected.code, - Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2.code, - Diagnostics._0_list_cannot_be_empty.code, - Diagnostics._0_modifier_already_seen.code, - Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration.code, - Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element.code, - Diagnostics._0_modifier_cannot_appear_on_a_parameter.code, - Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind.code, - Diagnostics._0_modifier_cannot_be_used_here.code, - Diagnostics._0_modifier_must_precede_1_modifier.code, - Diagnostics.const_declarations_can_only_be_declared_inside_a_block.code, - Diagnostics.const_declarations_must_be_initialized.code, - Diagnostics.extends_clause_already_seen.code, - Diagnostics.let_declarations_can_only_be_declared_inside_a_block.code, - Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations.code, - Diagnostics.Class_constructor_may_not_be_a_generator.code, - Diagnostics.Class_constructor_may_not_be_an_accessor.code, - Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules.code, - ]); - - /** - * Determine if source file needs to be re-created even if its text hasn't changed - */ - function shouldProgramCreateNewSourceFiles(program: Program | undefined, newOptions: CompilerOptions): boolean { - if (!program) return false; - // If any compiler options change, we can't reuse old source file even if version match - // The change in options like these could result in change in syntax tree or `sourceFile.bindDiagnostics`. - return optionsHaveChanges(program.getCompilerOptions(), newOptions, sourceFileAffectingCompilerOptions); - } - - function createCreateProgramOptions(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): CreateProgramOptions { - return { - rootNames, - options, - host, - oldProgram, - configFileParsingDiagnostics - }; - } - - /** - * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' - * that represent a compilation unit. - * - * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and - * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. - * - * @param createProgramOptions - The options for creating a program. - * @returns A 'Program' object. - */ - export function createProgram(createProgramOptions: CreateProgramOptions): Program; - /** - * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' - * that represent a compilation unit. - * - * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and - * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. - * - * @param rootNames - A set of root files. - * @param options - The compiler options which should be used. - * @param host - The host interacts with the underlying file system. - * @param oldProgram - Reuses an old program structure. - * @param configFileParsingDiagnostics - error during config file parsing - * @returns A 'Program' object. - */ - export function createProgram(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): Program; - export function createProgram(rootNamesOrOptions: readonly string[] | CreateProgramOptions, _options?: CompilerOptions, _host?: CompilerHost, _oldProgram?: Program, _configFileParsingDiagnostics?: readonly Diagnostic[]): Program { - const createProgramOptions = isArray(rootNamesOrOptions) ? createCreateProgramOptions(rootNamesOrOptions, _options!, _host, _oldProgram, _configFileParsingDiagnostics) : rootNamesOrOptions; // TODO: GH#18217 - const { rootNames, options, configFileParsingDiagnostics, projectReferences } = createProgramOptions; - let { oldProgram } = createProgramOptions; - - let processingDefaultLibFiles: SourceFile[] | undefined; - let processingOtherFiles: SourceFile[] | undefined; - let files: SourceFile[]; - let symlinks: SymlinkCache | undefined; - let commonSourceDirectory: string; - let typeChecker: TypeChecker; - let classifiableNames: Set<__String>; - const ambientModuleNameToUnmodifiedFileName = new Map(); - let fileReasons = createMultiMap(); - const cachedBindAndCheckDiagnosticsForFile: DiagnosticCache = {}; - const cachedDeclarationDiagnosticsForFile: DiagnosticCache = {}; - - let resolvedTypeReferenceDirectives = createModeAwareCache(); - let fileProcessingDiagnostics: FilePreprocessingDiagnostics[] | undefined; - - // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. - // This works as imported modules are discovered recursively in a depth first manner, specifically: - // - For each root file, findSourceFile is called. - // - This calls processImportedModules for each module imported in the source file. - // - This calls resolveModuleNames, and then calls findSourceFile for each resolved module. - // As all these operations happen - and are nested - within the createProgram call, they close over the below variables. - // The current resolution depth is tracked by incrementing/decrementing as the depth first search progresses. - const maxNodeModuleJsDepth = typeof options.maxNodeModuleJsDepth === "number" ? options.maxNodeModuleJsDepth : 0; - let currentNodeModulesDepth = 0; - - // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track - // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. - const modulesWithElidedImports = new Map(); - - // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. - const sourceFilesFoundSearchingNodeModules = new Map(); - - tracing?.push(tracing.Phase.Program, "createProgram", { configFilePath: options.configFilePath, rootDir: options.rootDir }, /*separateBeginAndEnd*/ true); - performance.mark("beforeProgram"); - - const host = createProgramOptions.host || createCompilerHost(options); - const configParsingHost = parseConfigHostFromCompilerHostLike(host); - - let skipDefaultLib = options.noLib; - const getDefaultLibraryFileName = memoize(() => host.getDefaultLibFileName(options)); - const defaultLibraryPath = host.getDefaultLibLocation ? host.getDefaultLibLocation() : getDirectoryPath(getDefaultLibraryFileName()); - const programDiagnostics = createDiagnosticCollection(); - const currentDirectory = host.getCurrentDirectory(); - const supportedExtensions = getSupportedExtensions(options); - const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); - - // Map storing if there is emit blocking diagnostics for given input - const hasEmitBlockingDiagnostics = new Map(); - let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression | false | undefined; - - let moduleResolutionCache: ModuleResolutionCache | undefined; - let typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined; - let actualResolveModuleNamesWorker: ( - moduleNames: readonly StringLiteralLike[], - containingFile: SourceFile, - containingFileName: string, - redirectedReference: ResolvedProjectReference | undefined, - resolutionInfo: ModuleResolutionInfo | undefined, - ) => (ResolvedModuleFull | undefined)[]; - const hasInvalidatedResolutions = host.hasInvalidatedResolutions || returnFalse; - if (host.resolveModuleNames) { - actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo) => - host.resolveModuleNames!( - moduleNames.map(literal => literal.text), - containingFileName, - resolutionInfo?.reusedNames?.map(literal => literal.text), - redirectedReference, - options, - containingFile, - resolutionInfo, - ).map(resolved => { - // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. - if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) { - return resolved as ResolvedModuleFull; - } - const withExtension = clone(resolved) as ResolvedModuleFull; - withExtension.extension = extensionFromPath(resolved.resolvedFileName); - return withExtension; - }); - moduleResolutionCache = host.getModuleResolutionCache?.(); - } - else { - moduleResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options); - const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => - resolveModuleName(moduleName, containingFileName, options, host, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule; - actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo) => - loadWithModeAwareCache(moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo, loader); - } - - let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined) => (ResolvedTypeReferenceDirective | undefined)[]; - if (host.resolveTypeReferenceDirectives) { - actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference, containingFileMode) => host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options, containingFileMode); - } - else { - typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache()); - const loader = (typesRef: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined, resolutionMode: SourceFile["impliedNodeFormat"] | undefined) => resolveTypeReferenceDirective( - typesRef, - containingFile, - options, - host, - redirectedReference, - typeReferenceDirectiveResolutionCache, - resolutionMode, - ).resolvedTypeReferenceDirective!; // TODO: GH#18217 - actualResolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile, redirectedReference, containingFileMode) => loadWithTypeDirectiveCache(Debug.checkEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, containingFileMode, loader); - } - - // Map from a stringified PackageId to the source file with that id. - // Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile). - // `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around. - const packageIdToSourceFile = new Map(); - // Maps from a SourceFile's `.path` to the name of the package it was imported with. - let sourceFileToPackageName = new Map(); - // Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it. - let redirectTargetsMap = createMultiMap(); - let usesUriStyleNodeCoreModules = false; - - /** - * map with - * - SourceFile if present - * - false if sourceFile missing for source of project reference redirect - * - undefined otherwise - */ - const filesByName = new Map(); - let missingFilePaths: readonly Path[] | undefined; - // stores 'filename -> file association' ignoring case - // used to track cases when two file names differ only in casing - const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? new Map() : undefined; - - // A parallel array to projectReferences storing the results of reading in the referenced tsconfig files - let resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined; - let projectReferenceRedirects: ESMap | undefined; - let mapFromFileToProjectReferenceRedirects: ESMap | undefined; - let mapFromToProjectReferenceRedirectSource: ESMap | undefined; - - const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect?.() && - !options.disableSourceOfProjectReferenceRedirect; - const { onProgramCreateComplete, fileExists, directoryExists } = updateHostForUseSourceOfProjectReferenceRedirect({ - compilerHost: host, - getSymlinkCache, - useSourceOfProjectReferenceRedirect, - toPath, - getResolvedProjectReferences, - getSourceOfProjectReferenceRedirect, - forEachResolvedProjectReference - }); - const readFile = host.readFile.bind(host) as typeof host.readFile; - - tracing?.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram }); - const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); - tracing?.pop(); - // We set `structuralIsReused` to `undefined` because `tryReuseStructureFromOldProgram` calls `tryReuseStructureFromOldProgram` which checks - // `structuralIsReused`, which would be a TDZ violation if it was not set in advance to `undefined`. - let structureIsReused: StructureIsReused; - tracing?.push(tracing.Phase.Program, "tryReuseStructureFromOldProgram", {}); - structureIsReused = tryReuseStructureFromOldProgram(); // eslint-disable-line prefer-const - tracing?.pop(); - if (structureIsReused !== StructureIsReused.Completely) { - processingDefaultLibFiles = []; - processingOtherFiles = []; - - if (projectReferences) { - if (!resolvedProjectReferences) { - resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); - } - if (rootNames.length) { - resolvedProjectReferences?.forEach((parsedRef, index) => { - if (!parsedRef) return; - const out = outFile(parsedRef.commandLine.options); - if (useSourceOfProjectReferenceRedirect) { - if (out || getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { - for (const fileName of parsedRef.commandLine.fileNames) { - processProjectReferenceFile(fileName, { kind: FileIncludeKind.SourceFromProjectReference, index }); - } + const filesByName = new Map(); + let missingFilePaths: readonly Path[] | undefined; + // stores 'filename -> file association' ignoring case + // used to track cases when two file names differ only in casing + const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? new Map() : undefined; + + // A parallel array to projectReferences storing the results of reading in the referenced tsconfig files + let resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined; + let projectReferenceRedirects: ESMap | undefined; + let mapFromFileToProjectReferenceRedirects: ESMap | undefined; + let mapFromToProjectReferenceRedirectSource: ESMap | undefined; + + const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect?.() && + !options.disableSourceOfProjectReferenceRedirect; + const { onProgramCreateComplete, fileExists, directoryExists } = updateHostForUseSourceOfProjectReferenceRedirect({ + compilerHost: host, + getSymlinkCache, + useSourceOfProjectReferenceRedirect, + toPath, + getResolvedProjectReferences, + getSourceOfProjectReferenceRedirect, + forEachResolvedProjectReference + }); + const readFile = host.readFile.bind(host) as typeof host.readFile; + + tracing?.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram }); + const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); + tracing?.pop(); + // We set `structuralIsReused` to `undefined` because `tryReuseStructureFromOldProgram` calls `tryReuseStructureFromOldProgram` which checks + // `structuralIsReused`, which would be a TDZ violation if it was not set in advance to `undefined`. + let structureIsReused: StructureIsReused; + tracing?.push(tracing.Phase.Program, "tryReuseStructureFromOldProgram", {}); + structureIsReused = tryReuseStructureFromOldProgram(); // eslint-disable-line prefer-const + tracing?.pop(); + if (structureIsReused !== StructureIsReused.Completely) { + processingDefaultLibFiles = []; + processingOtherFiles = []; + + if (projectReferences) { + if (!resolvedProjectReferences) { + resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); + } + if (rootNames.length) { + resolvedProjectReferences?.forEach((parsedRef, index) => { + if (!parsedRef) return; + const out = outFile(parsedRef.commandLine.options); + if (useSourceOfProjectReferenceRedirect) { + if (out || getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { + for (const fileName of parsedRef.commandLine.fileNames) { + processProjectReferenceFile(fileName, { kind: FileIncludeKind.SourceFromProjectReference, index }); } } - else { - if (out) { - processProjectReferenceFile(changeExtension(out, ".d.ts"), { kind: FileIncludeKind.OutputFromProjectReference, index }); - } - else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { - const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(parsedRef.commandLine, !host.useCaseSensitiveFileNames())); - for (const fileName of parsedRef.commandLine.fileNames) { - if (!isDeclarationFileName(fileName) && !fileExtensionIs(fileName, Extension.Json)) { - processProjectReferenceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory), { kind: FileIncludeKind.OutputFromProjectReference, index }); - } + } + else { + if (out) { + processProjectReferenceFile(changeExtension(out, ".d.ts"), { kind: FileIncludeKind.OutputFromProjectReference, index }); + } + else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) { + const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(parsedRef.commandLine, !host.useCaseSensitiveFileNames())); + for (const fileName of parsedRef.commandLine.fileNames) { + if (!isDeclarationFileName(fileName) && !fileExtensionIs(fileName, Extension.Json)) { + processProjectReferenceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory), { kind: FileIncludeKind.OutputFromProjectReference, index }); } } } - }); - } + } + }); } + } - tracing?.push(tracing.Phase.Program, "processRootFiles", { count: rootNames.length }); - forEach(rootNames, (name, index) => processRootFile(name, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.RootFile, index })); - tracing?.pop(); + tracing?.push(tracing.Phase.Program, "processRootFiles", { count: rootNames.length }); + forEach(rootNames, (name, index) => processRootFile(name, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.RootFile, index })); + tracing?.pop(); - // load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders - const typeReferences: string[] = rootNames.length ? getAutomaticTypeDirectiveNames(options, host) : emptyArray; - - if (typeReferences.length) { - tracing?.push(tracing.Phase.Program, "processTypeReferences", { count: typeReferences.length }); - // This containingFilename needs to match with the one used in managed-side - const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : host.getCurrentDirectory(); - const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile); - const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename); - for (let i = 0; i < typeReferences.length; i++) { - // under node16/nodenext module resolution, load `types`/ata include names as cjs resolution results by passing an `undefined` mode - processTypeReferenceDirective(typeReferences[i], /*mode*/ undefined, resolutions[i], { kind: FileIncludeKind.AutomaticTypeDirectiveFile, typeReference: typeReferences[i], packageId: resolutions[i]?.packageId }); - } - tracing?.pop(); - } - - // Do not process the default library if: - // - The '--noLib' flag is used. - // - A 'no-default-lib' reference comment is encountered in - // processing the root files. - if (rootNames.length && !skipDefaultLib) { - // If '--lib' is not specified, include default library file according to '--target' - // otherwise, using options specified in '--lib' instead of '--target' default library file - const defaultLibraryFileName = getDefaultLibraryFileName(); - if (!options.lib && defaultLibraryFileName) { - processRootFile(defaultLibraryFileName, /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile }); - } - else { - forEach(options.lib, (libFileName, index) => { - processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile, index }); - }); - } - } + // load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders + const typeReferences: string[] = rootNames.length ? getAutomaticTypeDirectiveNames(options, host) : emptyArray; - missingFilePaths = arrayFrom(mapDefinedIterator(filesByName.entries(), ([path, file]) => file === undefined ? path as Path : undefined)); - files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles); - processingDefaultLibFiles = undefined; - processingOtherFiles = undefined; + if (typeReferences.length) { + tracing?.push(tracing.Phase.Program, "processTypeReferences", { count: typeReferences.length }); + // This containingFilename needs to match with the one used in managed-side + const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : host.getCurrentDirectory(); + const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile); + const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename); + for (let i = 0; i < typeReferences.length; i++) { + // under node16/nodenext module resolution, load `types`/ata include names as cjs resolution results by passing an `undefined` mode + processTypeReferenceDirective(typeReferences[i], /*mode*/ undefined, resolutions[i], { kind: FileIncludeKind.AutomaticTypeDirectiveFile, typeReference: typeReferences[i], packageId: resolutions[i]?.packageId }); + } + tracing?.pop(); } - Debug.assert(!!missingFilePaths); - - // Release any files we have acquired in the old program but are - // not part of the new program. - if (oldProgram && host.onReleaseOldSourceFile) { - const oldSourceFiles = oldProgram.getSourceFiles(); - for (const oldSourceFile of oldSourceFiles) { - const newFile = getSourceFileByPath(oldSourceFile.resolvedPath); - if (shouldCreateNewSourceFile || !newFile || newFile.impliedNodeFormat !== oldSourceFile.impliedNodeFormat || - // old file wasn't redirect but new file is - (oldSourceFile.resolvedPath === oldSourceFile.path && newFile.resolvedPath !== oldSourceFile.path)) { - host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions(), !!getSourceFileByPath(oldSourceFile.path)); - } + // Do not process the default library if: + // - The '--noLib' flag is used. + // - A 'no-default-lib' reference comment is encountered in + // processing the root files. + if (rootNames.length && !skipDefaultLib) { + // If '--lib' is not specified, include default library file according to '--target' + // otherwise, using options specified in '--lib' instead of '--target' default library file + const defaultLibraryFileName = getDefaultLibraryFileName(); + if (!options.lib && defaultLibraryFileName) { + processRootFile(defaultLibraryFileName, /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile }); } - if (!host.getParsedCommandLine) { - oldProgram.forEachResolvedProjectReference(resolvedProjectReference => { - if (!getResolvedProjectReferenceByPath(resolvedProjectReference.sourceFile.path)) { - host.onReleaseOldSourceFile!(resolvedProjectReference.sourceFile, oldProgram!.getCompilerOptions(), /*hasSourceFileByPath*/ false); - } + else { + forEach(options.lib, (libFileName, index) => { + processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile, index }); }); } } - // Release commandlines that new program does not use - if (oldProgram && host.onReleaseParsedCommandLine) { - forEachProjectReference( - oldProgram.getProjectReferences(), - oldProgram.getResolvedProjectReferences(), - (oldResolvedRef, parent, index) => { - const oldReference = parent?.commandLine.projectReferences![index] || oldProgram!.getProjectReferences()![index]; - const oldRefPath = resolveProjectReferencePath(oldReference); - if (!projectReferenceRedirects?.has(toPath(oldRefPath))) { - host.onReleaseParsedCommandLine!(oldRefPath, oldResolvedRef, oldProgram!.getCompilerOptions()); - } + missingFilePaths = arrayFrom(mapDefinedIterator(filesByName.entries(), ([path, file]) => file === undefined ? path as Path : undefined)); + files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles); + processingDefaultLibFiles = undefined; + processingOtherFiles = undefined; + } + + Debug.assert(!!missingFilePaths); + + // Release any files we have acquired in the old program but are + // not part of the new program. + if (oldProgram && host.onReleaseOldSourceFile) { + const oldSourceFiles = oldProgram.getSourceFiles(); + for (const oldSourceFile of oldSourceFiles) { + const newFile = getSourceFileByPath(oldSourceFile.resolvedPath); + if (shouldCreateNewSourceFile || !newFile || newFile.impliedNodeFormat !== oldSourceFile.impliedNodeFormat || + // old file wasn't redirect but new file is + (oldSourceFile.resolvedPath === oldSourceFile.path && newFile.resolvedPath !== oldSourceFile.path)) { + host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions(), !!getSourceFileByPath(oldSourceFile.path)); + } + } + if (!host.getParsedCommandLine) { + oldProgram.forEachResolvedProjectReference(resolvedProjectReference => { + if (!getResolvedProjectReferenceByPath(resolvedProjectReference.sourceFile.path)) { + host.onReleaseOldSourceFile!(resolvedProjectReference.sourceFile, oldProgram!.getCompilerOptions(), /*hasSourceFileByPath*/ false); } - ); + }); } + } - typeReferenceDirectiveResolutionCache = undefined; - - // unconditionally set oldProgram to undefined to prevent it from being captured in closure - oldProgram = undefined; - - const program: Program = { - getRootFileNames: () => rootNames, - getSourceFile, - getSourceFileByPath, - getSourceFiles: () => files, - getMissingFilePaths: () => missingFilePaths!, // TODO: GH#18217 - getModuleResolutionCache: () => moduleResolutionCache, - getFilesByNameMap: () => filesByName, - getCompilerOptions: () => options, - getSyntacticDiagnostics, - getOptionsDiagnostics, - getGlobalDiagnostics, - getSemanticDiagnostics, - getCachedSemanticDiagnostics, - getSuggestionDiagnostics, - getDeclarationDiagnostics, - getBindAndCheckDiagnostics, - getProgramDiagnostics, - getTypeChecker, - getClassifiableNames, - getCommonSourceDirectory, - emit, - getCurrentDirectory: () => currentDirectory, - getNodeCount: () => getTypeChecker().getNodeCount(), - getIdentifierCount: () => getTypeChecker().getIdentifierCount(), - getSymbolCount: () => getTypeChecker().getSymbolCount(), - getTypeCount: () => getTypeChecker().getTypeCount(), - getInstantiationCount: () => getTypeChecker().getInstantiationCount(), - getRelationCacheSizes: () => getTypeChecker().getRelationCacheSizes(), - getFileProcessingDiagnostics: () => fileProcessingDiagnostics, - getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives, - isSourceFileFromExternalLibrary, - isSourceFileDefaultLibrary, - getSourceFileFromReference, - getLibFileFromReference, - sourceFileToPackageName, - redirectTargetsMap, - usesUriStyleNodeCoreModules, - isEmittedFile, - getConfigFileParsingDiagnostics, - getResolvedModuleWithFailedLookupLocationsFromCache, - getProjectReferences, - getResolvedProjectReferences, - getProjectReferenceRedirect, - getResolvedProjectReferenceToRedirect, - getResolvedProjectReferenceByPath, - forEachResolvedProjectReference, - isSourceOfProjectReferenceRedirect, - emitBuildInfo, - fileExists, - readFile, - directoryExists, - getSymlinkCache, - realpath: host.realpath?.bind(host), - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), - getFileIncludeReasons: () => fileReasons, - structureIsReused, - writeFile, - }; + // Release commandlines that new program does not use + if (oldProgram && host.onReleaseParsedCommandLine) { + forEachProjectReference( + oldProgram.getProjectReferences(), + oldProgram.getResolvedProjectReferences(), + (oldResolvedRef, parent, index) => { + const oldReference = parent?.commandLine.projectReferences![index] || oldProgram!.getProjectReferences()![index]; + const oldRefPath = resolveProjectReferencePath(oldReference); + if (!projectReferenceRedirects?.has(toPath(oldRefPath))) { + host.onReleaseParsedCommandLine!(oldRefPath, oldResolvedRef, oldProgram!.getCompilerOptions()); + } + } + ); + } - onProgramCreateComplete(); + typeReferenceDirectiveResolutionCache = undefined; + + // unconditionally set oldProgram to undefined to prevent it from being captured in closure + oldProgram = undefined; + + const program: Program = { + getRootFileNames: () => rootNames, + getSourceFile, + getSourceFileByPath, + getSourceFiles: () => files, + getMissingFilePaths: () => missingFilePaths!, // TODO: GH#18217 + getModuleResolutionCache: () => moduleResolutionCache, + getFilesByNameMap: () => filesByName, + getCompilerOptions: () => options, + getSyntacticDiagnostics, + getOptionsDiagnostics, + getGlobalDiagnostics, + getSemanticDiagnostics, + getCachedSemanticDiagnostics, + getSuggestionDiagnostics, + getDeclarationDiagnostics, + getBindAndCheckDiagnostics, + getProgramDiagnostics, + getTypeChecker, + getClassifiableNames, + getCommonSourceDirectory, + emit, + getCurrentDirectory: () => currentDirectory, + getNodeCount: () => getTypeChecker().getNodeCount(), + getIdentifierCount: () => getTypeChecker().getIdentifierCount(), + getSymbolCount: () => getTypeChecker().getSymbolCount(), + getTypeCount: () => getTypeChecker().getTypeCount(), + getInstantiationCount: () => getTypeChecker().getInstantiationCount(), + getRelationCacheSizes: () => getTypeChecker().getRelationCacheSizes(), + getFileProcessingDiagnostics: () => fileProcessingDiagnostics, + getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives, + isSourceFileFromExternalLibrary, + isSourceFileDefaultLibrary, + getSourceFileFromReference, + getLibFileFromReference, + sourceFileToPackageName, + redirectTargetsMap, + usesUriStyleNodeCoreModules, + isEmittedFile, + getConfigFileParsingDiagnostics, + getResolvedModuleWithFailedLookupLocationsFromCache, + getProjectReferences, + getResolvedProjectReferences, + getProjectReferenceRedirect, + getResolvedProjectReferenceToRedirect, + getResolvedProjectReferenceByPath, + forEachResolvedProjectReference, + isSourceOfProjectReferenceRedirect, + emitBuildInfo, + fileExists, + readFile, + directoryExists, + getSymlinkCache, + realpath: host.realpath?.bind(host), + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), + getFileIncludeReasons: () => fileReasons, + structureIsReused, + writeFile, + }; + + onProgramCreateComplete(); + + // Add file processingDiagnostics + fileProcessingDiagnostics?.forEach(diagnostic => { + switch (diagnostic.kind) { + case FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic: + return programDiagnostics.add(createDiagnosticExplainingFile(diagnostic.file && getSourceFileByPath(diagnostic.file), diagnostic.fileProcessingReason, diagnostic.diagnostic, diagnostic.args || emptyArray)); + case FilePreprocessingDiagnosticsKind.FilePreprocessingReferencedDiagnostic: + const { file, pos, end } = getReferencedFileLocation(getSourceFileByPath, diagnostic.reason) as ReferenceFileLocation; + return programDiagnostics.add(createFileDiagnostic(file, Debug.checkDefined(pos), Debug.checkDefined(end) - pos, diagnostic.diagnostic, ...diagnostic.args || emptyArray)); + default: + Debug.assertNever(diagnostic); + } + }); - // Add file processingDiagnostics - fileProcessingDiagnostics?.forEach(diagnostic => { - switch (diagnostic.kind) { - case FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic: - return programDiagnostics.add(createDiagnosticExplainingFile(diagnostic.file && getSourceFileByPath(diagnostic.file), diagnostic.fileProcessingReason, diagnostic.diagnostic, diagnostic.args || emptyArray)); - case FilePreprocessingDiagnosticsKind.FilePreprocessingReferencedDiagnostic: - const { file, pos, end } = getReferencedFileLocation(getSourceFileByPath, diagnostic.reason) as ReferenceFileLocation; - return programDiagnostics.add(createFileDiagnostic(file, Debug.checkDefined(pos), Debug.checkDefined(end) - pos, diagnostic.diagnostic, ...diagnostic.args || emptyArray)); - default: - Debug.assertNever(diagnostic); - } - }); + verifyCompilerOptions(); + performance.mark("afterProgram"); + performance.measure("Program", "beforeProgram", "afterProgram"); + tracing?.pop(); - verifyCompilerOptions(); - performance.mark("afterProgram"); - performance.measure("Program", "beforeProgram", "afterProgram"); - tracing?.pop(); + return program; - return program; - - function addResolutionDiagnostics(list: Diagnostic[] | undefined) { - if (!list) return; - for (const elem of list) { - programDiagnostics.add(elem); - } - } - - function pullDiagnosticsFromCache(names: readonly StringLiteralLike[] | readonly FileReference[], containingFile: SourceFile) { - if (!moduleResolutionCache) return; - const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory); - const containingDir = getDirectoryPath(containingFileName); - const redirectedReference = getRedirectReferenceForResolution(containingFile); - for (const n of names) { - // mimics logic done in the resolution cache, should be resilient to upgrading it to use `FileReference`s for non-type-reference modal lookups to make it rely on the index in the list less - const mode = getResolutionMode(n, containingFile); - const name = getResolutionName(n); - // only nonrelative names hit the cache, and, at least as of right now, only nonrelative names can issue diagnostics - // (Since diagnostics are only issued via import or export map lookup) - // This may totally change if/when the issue of output paths not mapping to input files is fixed in a broader context - // When it is, how we extract diagnostics from the module name resolver will have the be refined - the current cache - // APIs wrapping the underlying resolver make it almost impossible to smuggle the diagnostics out in a generalized way - if (isExternalModuleNameRelative(name)) continue; - const diags = moduleResolutionCache.getOrCreateCacheForModuleName(name, mode, redirectedReference).get(containingDir)?.resolutionDiagnostics; - addResolutionDiagnostics(diags); - } - } - - function resolveModuleNamesWorker(moduleNames: readonly StringLiteralLike[], containingFile: SourceFile, resolutionInfo: ModuleResolutionInfo | undefined): readonly (ResolvedModuleFull | undefined)[] { - if (!moduleNames.length) return emptyArray; - const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory); - const redirectedReference = getRedirectReferenceForResolution(containingFile); - tracing?.push(tracing.Phase.Program, "resolveModuleNamesWorker", { containingFileName }); - performance.mark("beforeResolveModule"); - const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo); - performance.mark("afterResolveModule"); - performance.measure("ResolveModule", "beforeResolveModule", "afterResolveModule"); - tracing?.pop(); - pullDiagnosticsFromCache(moduleNames, containingFile); - return result; + function addResolutionDiagnostics(list: Diagnostic[] | undefined) { + if (!list) return; + for (const elem of list) { + programDiagnostics.add(elem); } + } - function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] { - if (!typeDirectiveNames.length) return []; - const containingFileName = !isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile; - const redirectedReference = !isString(containingFile) ? getRedirectReferenceForResolution(containingFile) : undefined; - const containingFileMode = !isString(containingFile) ? containingFile.impliedNodeFormat : undefined; - tracing?.push(tracing.Phase.Program, "resolveTypeReferenceDirectiveNamesWorker", { containingFileName }); - performance.mark("beforeResolveTypeReference"); - const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference, containingFileMode); - performance.mark("afterResolveTypeReference"); - performance.measure("ResolveTypeReference", "beforeResolveTypeReference", "afterResolveTypeReference"); - tracing?.pop(); - return result; + function pullDiagnosticsFromCache(names: readonly StringLiteralLike[] | readonly FileReference[], containingFile: SourceFile) { + if (!moduleResolutionCache) return; + const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory); + const containingDir = getDirectoryPath(containingFileName); + const redirectedReference = getRedirectReferenceForResolution(containingFile); + for (const n of names) { + // mimics logic done in the resolution cache, should be resilient to upgrading it to use `FileReference`s for non-type-reference modal lookups to make it rely on the index in the list less + const mode = getResolutionMode(n, containingFile); + const name = getResolutionName(n); + // only nonrelative names hit the cache, and, at least as of right now, only nonrelative names can issue diagnostics + // (Since diagnostics are only issued via import or export map lookup) + // This may totally change if/when the issue of output paths not mapping to input files is fixed in a broader context + // When it is, how we extract diagnostics from the module name resolver will have the be refined - the current cache + // APIs wrapping the underlying resolver make it almost impossible to smuggle the diagnostics out in a generalized way + if (isExternalModuleNameRelative(name)) continue; + const diags = moduleResolutionCache.getOrCreateCacheForModuleName(name, mode, redirectedReference).get(containingDir)?.resolutionDiagnostics; + addResolutionDiagnostics(diags); } + } - function getRedirectReferenceForResolution(file: SourceFile) { - const redirect = getResolvedProjectReferenceToRedirect(file.originalFileName); - if (redirect || !isDeclarationFileName(file.originalFileName)) return redirect; + function resolveModuleNamesWorker(moduleNames: readonly StringLiteralLike[], containingFile: SourceFile, resolutionInfo: ModuleResolutionInfo | undefined): readonly (ResolvedModuleFull | undefined)[] { + if (!moduleNames.length) return emptyArray; + const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory); + const redirectedReference = getRedirectReferenceForResolution(containingFile); + tracing?.push(tracing.Phase.Program, "resolveModuleNamesWorker", { containingFileName }); + performance.mark("beforeResolveModule"); + const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, redirectedReference, resolutionInfo); + performance.mark("afterResolveModule"); + performance.measure("ResolveModule", "beforeResolveModule", "afterResolveModule"); + tracing?.pop(); + pullDiagnosticsFromCache(moduleNames, containingFile); + return result; + } - // The originalFileName could not be actual source file name if file found was d.ts from referecned project - // So in this case try to look up if this is output from referenced project, if it is use the redirected project in that case - const resultFromDts = getRedirectReferenceForResolutionFromSourceOfProject(file.path); - if (resultFromDts) return resultFromDts; + function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] { + if (!typeDirectiveNames.length) return []; + const containingFileName = !isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile; + const redirectedReference = !isString(containingFile) ? getRedirectReferenceForResolution(containingFile) : undefined; + const containingFileMode = !isString(containingFile) ? containingFile.impliedNodeFormat : undefined; + tracing?.push(tracing.Phase.Program, "resolveTypeReferenceDirectiveNamesWorker", { containingFileName }); + performance.mark("beforeResolveTypeReference"); + const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference, containingFileMode); + performance.mark("afterResolveTypeReference"); + performance.measure("ResolveTypeReference", "beforeResolveTypeReference", "afterResolveTypeReference"); + tracing?.pop(); + return result; + } - // If preserveSymlinks is true, module resolution wont jump the symlink - // but the resolved real path may be the .d.ts from project reference - // Note:: Currently we try the real path only if the - // file is from node_modules to avoid having to run real path on all file paths - if (!host.realpath || !options.preserveSymlinks || !stringContains(file.originalFileName, nodeModulesPathPart)) return undefined; - const realDeclarationPath = toPath(host.realpath(file.originalFileName)); - return realDeclarationPath === file.path ? undefined : getRedirectReferenceForResolutionFromSourceOfProject(realDeclarationPath); - } + function getRedirectReferenceForResolution(file: SourceFile) { + const redirect = getResolvedProjectReferenceToRedirect(file.originalFileName); + if (redirect || !isDeclarationFileName(file.originalFileName)) return redirect; + + // The originalFileName could not be actual source file name if file found was d.ts from referecned project + // So in this case try to look up if this is output from referenced project, if it is use the redirected project in that case + const resultFromDts = getRedirectReferenceForResolutionFromSourceOfProject(file.path); + if (resultFromDts) return resultFromDts; + + // If preserveSymlinks is true, module resolution wont jump the symlink + // but the resolved real path may be the .d.ts from project reference + // Note:: Currently we try the real path only if the + // file is from node_modules to avoid having to run real path on all file paths + if (!host.realpath || !options.preserveSymlinks || !stringContains(file.originalFileName, nodeModulesPathPart)) return undefined; + const realDeclarationPath = toPath(host.realpath(file.originalFileName)); + return realDeclarationPath === file.path ? undefined : getRedirectReferenceForResolutionFromSourceOfProject(realDeclarationPath); + } - function getRedirectReferenceForResolutionFromSourceOfProject(filePath: Path) { - const source = getSourceOfProjectReferenceRedirect(filePath); - if (isString(source)) return getResolvedProjectReferenceToRedirect(source); - if (!source) return undefined; - // Output of .d.ts file so return resolved ref that matches the out file name - return forEachResolvedProjectReference(resolvedRef => { - const out = outFile(resolvedRef.commandLine.options); - if (!out) return undefined; - return toPath(out) === filePath ? resolvedRef : undefined; - }); + function getRedirectReferenceForResolutionFromSourceOfProject(filePath: Path) { + const source = getSourceOfProjectReferenceRedirect(filePath); + if (isString(source)) return getResolvedProjectReferenceToRedirect(source); + if (!source) return undefined; + // Output of .d.ts file so return resolved ref that matches the out file name + return forEachResolvedProjectReference(resolvedRef => { + const out = outFile(resolvedRef.commandLine.options); + if (!out) return undefined; + return toPath(out) === filePath ? resolvedRef : undefined; + }); + } + + function compareDefaultLibFiles(a: SourceFile, b: SourceFile) { + return compareValues(getDefaultLibFilePriority(a), getDefaultLibFilePriority(b)); + } + + function getDefaultLibFilePriority(a: SourceFile) { + if (containsPath(defaultLibraryPath, a.fileName, /*ignoreCase*/ false)) { + const basename = getBaseFileName(a.fileName); + if (basename === "lib.d.ts" || basename === "lib.es6.d.ts") return 0; + const name = removeSuffix(removePrefix(basename, "lib."), ".d.ts"); + const index = libs.indexOf(name); + if (index !== -1) return index + 1; } + return libs.length + 2; + } - function compareDefaultLibFiles(a: SourceFile, b: SourceFile) { - return compareValues(getDefaultLibFilePriority(a), getDefaultLibFilePriority(b)); + function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, mode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined { + return moduleResolutionCache && resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache, mode); + } + + function toPath(fileName: string): Path { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + } + + function getCommonSourceDirectory() { + if (commonSourceDirectory === undefined) { + const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program)); + commonSourceDirectory = ts.getCommonSourceDirectory( + options, + () => mapDefined(emittedFiles, file => file.isDeclarationFile ? undefined : file.fileName), + currentDirectory, + getCanonicalFileName, + commonSourceDirectory => checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory) + ); } + return commonSourceDirectory; + } + + function getClassifiableNames() { + if (!classifiableNames) { + // Initialize a checker so that all our files are bound. + getTypeChecker(); + classifiableNames = new Set(); - function getDefaultLibFilePriority(a: SourceFile) { - if (containsPath(defaultLibraryPath, a.fileName, /*ignoreCase*/ false)) { - const basename = getBaseFileName(a.fileName); - if (basename === "lib.d.ts" || basename === "lib.es6.d.ts") return 0; - const name = removeSuffix(removePrefix(basename, "lib."), ".d.ts"); - const index = libs.indexOf(name); - if (index !== -1) return index + 1; + for (const sourceFile of files) { + sourceFile.classifiableNames?.forEach(value => classifiableNames.add(value)); } - return libs.length + 2; } - function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, mode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined { - return moduleResolutionCache && resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache, mode); - } + return classifiableNames; + } - function toPath(fileName: string): Path { - return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + function resolveModuleNamesReusingOldState(moduleNames: readonly StringLiteralLike[], file: SourceFile): readonly (ResolvedModuleFull | undefined)[] { + if (structureIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) { + // If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules, + // the best we can do is fallback to the default logic. + return resolveModuleNamesWorker(moduleNames, file, /*resolutionInfo*/ undefined); } - function getCommonSourceDirectory() { - if (commonSourceDirectory === undefined) { - const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program)); - commonSourceDirectory = ts.getCommonSourceDirectory( - options, - () => mapDefined(emittedFiles, file => file.isDeclarationFile ? undefined : file.fileName), - currentDirectory, - getCanonicalFileName, - commonSourceDirectory => checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory) - ); + const oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName); + if (oldSourceFile !== file && file.resolvedModules) { + // `file` was created for the new program. + // + // We only set `file.resolvedModules` via work from the current function, + // so it is defined iff we already called the current function on `file`. + // That call happened no later than the creation of the `file` object, + // which per above occurred during the current program creation. + // Since we assume the filesystem does not change during program creation, + // it is safe to reuse resolutions from the earlier call. + const result: (ResolvedModuleFull | undefined)[] = []; + for (const moduleName of moduleNames) { + const resolvedModule = file.resolvedModules.get(moduleName.text, getModeForUsageLocation(file, moduleName)); + result.push(resolvedModule); } - return commonSourceDirectory; + return result; } + // At this point, we know at least one of the following hold: + // - file has local declarations for ambient modules + // - old program state is available + // With this information, we can infer some module resolutions without performing resolution. - function getClassifiableNames() { - if (!classifiableNames) { - // Initialize a checker so that all our files are bound. - getTypeChecker(); - classifiableNames = new Set(); - - for (const sourceFile of files) { - sourceFile.classifiableNames?.forEach(value => classifiableNames.add(value)); + /** An ordered list of module names for which we cannot recover the resolution. */ + let unknownModuleNames: StringLiteralLike[] | undefined; + /** + * The indexing of elements in this list matches that of `moduleNames`. + * + * Before combining results, result[i] is in one of the following states: + * * undefined: needs to be recomputed, + * * predictedToResolveToAmbientModuleMarker: known to be an ambient module. + * Needs to be reset to undefined before returning, + * * ResolvedModuleFull instance: can be reused. + */ + let result: (ResolvedModuleFull | undefined)[] | undefined; + let reusedNames: StringLiteralLike[] | undefined; + /** A transient placeholder used to mark predicted resolution in the result list. */ + const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {} as any; + + for (let i = 0; i < moduleNames.length; i++) { + const moduleName = moduleNames[i]; + // If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions + if (file === oldSourceFile && !hasInvalidatedResolutions(oldSourceFile.path)) { + const mode = getModeForUsageLocation(file, moduleName); + const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName.text, mode); + if (oldResolvedModule) { + if (isTraceEnabled(options, host)) { + trace(host, + oldResolvedModule.packageId ? + Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : + Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2, + moduleName.text, + getNormalizedAbsolutePath(file.originalFileName, currentDirectory), + oldResolvedModule.resolvedFileName, + oldResolvedModule.packageId && packageIdToString(oldResolvedModule.packageId) + ); + } + (result ??= new Array(moduleNames.length))[i] = oldResolvedModule; + (reusedNames ??= []).push(moduleName); + continue; + } + } + // We know moduleName resolves to an ambient module provided that moduleName: + // - is in the list of ambient modules locally declared in the current source file. + // - resolved to an ambient module in the old program whose declaration is in an unmodified file + // (so the same module declaration will land in the new program) + let resolvesToAmbientModuleInNonModifiedFile = false; + if (contains(file.ambientModuleNames, moduleName.text)) { + resolvesToAmbientModuleInNonModifiedFile = true; + if (isTraceEnabled(options, host)) { + trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName.text, getNormalizedAbsolutePath(file.originalFileName, currentDirectory)); } } + else { + resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName); + } - return classifiableNames; + if (resolvesToAmbientModuleInNonModifiedFile) { + (result || (result = new Array(moduleNames.length)))[i] = predictedToResolveToAmbientModuleMarker; + } + else { + // Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result. + (unknownModuleNames ??= []).push(moduleName); + } } - function resolveModuleNamesReusingOldState(moduleNames: readonly StringLiteralLike[], file: SourceFile): readonly (ResolvedModuleFull | undefined)[] { - if (structureIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) { - // If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules, - // the best we can do is fallback to the default logic. - return resolveModuleNamesWorker(moduleNames, file, /*resolutionInfo*/ undefined); - } + const resolutions = unknownModuleNames && unknownModuleNames.length + ? resolveModuleNamesWorker(unknownModuleNames, file, { names: unknownModuleNames, reusedNames }) + : emptyArray; + + // Combine results of resolutions and predicted results + if (!result) { + // There were no unresolved/ambient resolutions. + Debug.assert(resolutions.length === moduleNames.length); + return resolutions; + } - const oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName); - if (oldSourceFile !== file && file.resolvedModules) { - // `file` was created for the new program. - // - // We only set `file.resolvedModules` via work from the current function, - // so it is defined iff we already called the current function on `file`. - // That call happened no later than the creation of the `file` object, - // which per above occurred during the current program creation. - // Since we assume the filesystem does not change during program creation, - // it is safe to reuse resolutions from the earlier call. - const result: (ResolvedModuleFull | undefined)[] = []; - for (const moduleName of moduleNames) { - const resolvedModule = file.resolvedModules.get(moduleName.text, getModeForUsageLocation(file, moduleName)); - result.push(resolvedModule); + let j = 0; + for (let i = 0; i < result.length; i++) { + if (result[i]) { + // `result[i]` is either a `ResolvedModuleFull` or a marker. + // If it is the former, we can leave it as is. + if (result[i] === predictedToResolveToAmbientModuleMarker) { + result[i] = undefined; } - return result; } - // At this point, we know at least one of the following hold: - // - file has local declarations for ambient modules - // - old program state is available - // With this information, we can infer some module resolutions without performing resolution. - - /** An ordered list of module names for which we cannot recover the resolution. */ - let unknownModuleNames: StringLiteralLike[] | undefined; - /** - * The indexing of elements in this list matches that of `moduleNames`. - * - * Before combining results, result[i] is in one of the following states: - * * undefined: needs to be recomputed, - * * predictedToResolveToAmbientModuleMarker: known to be an ambient module. - * Needs to be reset to undefined before returning, - * * ResolvedModuleFull instance: can be reused. - */ - let result: (ResolvedModuleFull | undefined)[] | undefined; - let reusedNames: StringLiteralLike[] | undefined; - /** A transient placeholder used to mark predicted resolution in the result list. */ - const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {} as any; - - for (let i = 0; i < moduleNames.length; i++) { - const moduleName = moduleNames[i]; - // If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions - if (file === oldSourceFile && !hasInvalidatedResolutions(oldSourceFile.path)) { - const mode = getModeForUsageLocation(file, moduleName); - const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName.text, mode); - if (oldResolvedModule) { - if (isTraceEnabled(options, host)) { - trace(host, - oldResolvedModule.packageId ? - Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : - Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2, - moduleName.text, - getNormalizedAbsolutePath(file.originalFileName, currentDirectory), - oldResolvedModule.resolvedFileName, - oldResolvedModule.packageId && packageIdToString(oldResolvedModule.packageId) - ); - } - (result ??= new Array(moduleNames.length))[i] = oldResolvedModule; - (reusedNames ??= []).push(moduleName); - continue; - } - } - // We know moduleName resolves to an ambient module provided that moduleName: - // - is in the list of ambient modules locally declared in the current source file. - // - resolved to an ambient module in the old program whose declaration is in an unmodified file - // (so the same module declaration will land in the new program) - let resolvesToAmbientModuleInNonModifiedFile = false; - if (contains(file.ambientModuleNames, moduleName.text)) { - resolvesToAmbientModuleInNonModifiedFile = true; - if (isTraceEnabled(options, host)) { - trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName.text, getNormalizedAbsolutePath(file.originalFileName, currentDirectory)); - } - } - else { - resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName); - } + else { + result[i] = resolutions[j]; + j++; + } + } + Debug.assert(j === resolutions.length); - if (resolvesToAmbientModuleInNonModifiedFile) { - (result || (result = new Array(moduleNames.length)))[i] = predictedToResolveToAmbientModuleMarker; - } - else { - // Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result. - (unknownModuleNames ??= []).push(moduleName); - } + return result; + + // If we change our policy of rechecking failed lookups on each program create, + // we should adjust the value returned here. + function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: StringLiteralLike): boolean { + const resolutionToFile = getResolvedModule(oldSourceFile, moduleName.text, getModeForUsageLocation(file, moduleName)); + const resolvedFile = resolutionToFile && oldProgram!.getSourceFile(resolutionToFile.resolvedFileName); + if (resolutionToFile && resolvedFile) { + // In the old program, we resolved to an ambient module that was in the same + // place as we expected to find an actual module file. + // We actually need to return 'false' here even though this seems like a 'true' case + // because the normal module resolution algorithm will find this anyway. + return false; } - const resolutions = unknownModuleNames && unknownModuleNames.length - ? resolveModuleNamesWorker(unknownModuleNames, file, { names: unknownModuleNames, reusedNames }) - : emptyArray; + // at least one of declarations should come from non-modified source file + const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName.text); - // Combine results of resolutions and predicted results - if (!result) { - // There were no unresolved/ambient resolutions. - Debug.assert(resolutions.length === moduleNames.length); - return resolutions; + if (!unmodifiedFile) { + return false; } - let j = 0; - for (let i = 0; i < result.length; i++) { - if (result[i]) { - // `result[i]` is either a `ResolvedModuleFull` or a marker. - // If it is the former, we can leave it as is. - if (result[i] === predictedToResolveToAmbientModuleMarker) { - result[i] = undefined; - } + if (isTraceEnabled(options, host)) { + trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName.text, unmodifiedFile); + } + return true; + } + } + + function canReuseProjectReferences(): boolean { + return !forEachProjectReference( + oldProgram!.getProjectReferences(), + oldProgram!.getResolvedProjectReferences(), + (oldResolvedRef, parent, index) => { + const newRef = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; + const newResolvedRef = parseProjectReferenceConfigFile(newRef); + if (oldResolvedRef) { + // Resolved project reference has gone missing or changed + return !newResolvedRef || + newResolvedRef.sourceFile !== oldResolvedRef.sourceFile || + !arrayIsEqualTo(oldResolvedRef.commandLine.fileNames, newResolvedRef.commandLine.fileNames); } else { - result[i] = resolutions[j]; - j++; + // A previously-unresolved reference may be resolved now + return newResolvedRef !== undefined; } + }, + (oldProjectReferences, parent) => { + // If array of references is changed, we cant resue old program + const newReferences = parent ? getResolvedProjectReferenceByPath(parent.sourceFile.path)!.commandLine.projectReferences : projectReferences; + return !arrayIsEqualTo(oldProjectReferences, newReferences, projectReferenceIsEqualTo); } - Debug.assert(j === resolutions.length); - - return result; - - // If we change our policy of rechecking failed lookups on each program create, - // we should adjust the value returned here. - function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: StringLiteralLike): boolean { - const resolutionToFile = getResolvedModule(oldSourceFile, moduleName.text, getModeForUsageLocation(file, moduleName)); - const resolvedFile = resolutionToFile && oldProgram!.getSourceFile(resolutionToFile.resolvedFileName); - if (resolutionToFile && resolvedFile) { - // In the old program, we resolved to an ambient module that was in the same - // place as we expected to find an actual module file. - // We actually need to return 'false' here even though this seems like a 'true' case - // because the normal module resolution algorithm will find this anyway. - return false; - } - - // at least one of declarations should come from non-modified source file - const unmodifiedFile = ambientModuleNameToUnmodifiedFileName.get(moduleName.text); + ); + } - if (!unmodifiedFile) { - return false; - } + function tryReuseStructureFromOldProgram(): StructureIsReused { + if (!oldProgram) { + return StructureIsReused.Not; + } - if (isTraceEnabled(options, host)) { - trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName.text, unmodifiedFile); - } - return true; - } + // check properties that can affect structure of the program or module resolution strategy + // if any of these properties has changed - structure cannot be reused + const oldOptions = oldProgram.getCompilerOptions(); + if (changesAffectModuleResolution(oldOptions, options)) { + return StructureIsReused.Not; } - function canReuseProjectReferences(): boolean { - return !forEachProjectReference( - oldProgram!.getProjectReferences(), - oldProgram!.getResolvedProjectReferences(), - (oldResolvedRef, parent, index) => { - const newRef = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; - const newResolvedRef = parseProjectReferenceConfigFile(newRef); - if (oldResolvedRef) { - // Resolved project reference has gone missing or changed - return !newResolvedRef || - newResolvedRef.sourceFile !== oldResolvedRef.sourceFile || - !arrayIsEqualTo(oldResolvedRef.commandLine.fileNames, newResolvedRef.commandLine.fileNames); - } - else { - // A previously-unresolved reference may be resolved now - return newResolvedRef !== undefined; - } - }, - (oldProjectReferences, parent) => { - // If array of references is changed, we cant resue old program - const newReferences = parent ? getResolvedProjectReferenceByPath(parent.sourceFile.path)!.commandLine.projectReferences : projectReferences; - return !arrayIsEqualTo(oldProjectReferences, newReferences, projectReferenceIsEqualTo); - } - ); + // there is an old program, check if we can reuse its structure + const oldRootNames = oldProgram.getRootFileNames(); + if (!arrayIsEqualTo(oldRootNames, rootNames)) { + return StructureIsReused.Not; } - function tryReuseStructureFromOldProgram(): StructureIsReused { - if (!oldProgram) { - return StructureIsReused.Not; - } + // Check if any referenced project tsconfig files are different + if (!canReuseProjectReferences()) { + return StructureIsReused.Not; + } + if (projectReferences) { + resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); + } - // check properties that can affect structure of the program or module resolution strategy - // if any of these properties has changed - structure cannot be reused - const oldOptions = oldProgram.getCompilerOptions(); - if (changesAffectModuleResolution(oldOptions, options)) { - return StructureIsReused.Not; - } + // check if program source files has changed in the way that can affect structure of the program + const newSourceFiles: SourceFile[] = []; + const modifiedSourceFiles: { oldFile: SourceFile, newFile: SourceFile }[] = []; + structureIsReused = StructureIsReused.Completely; - // there is an old program, check if we can reuse its structure - const oldRootNames = oldProgram.getRootFileNames(); - if (!arrayIsEqualTo(oldRootNames, rootNames)) { - return StructureIsReused.Not; - } + // If the missing file paths are now present, it can change the progam structure, + // and hence cant reuse the structure. + // This is same as how we dont reuse the structure if one of the file from old program is now missing + if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) { + return StructureIsReused.Not; + } - // Check if any referenced project tsconfig files are different - if (!canReuseProjectReferences()) { - return StructureIsReused.Not; - } - if (projectReferences) { - resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); - } + const oldSourceFiles = oldProgram.getSourceFiles(); + const enum SeenPackageName { Exists, Modified } + const seenPackageNames = new Map(); - // check if program source files has changed in the way that can affect structure of the program - const newSourceFiles: SourceFile[] = []; - const modifiedSourceFiles: { oldFile: SourceFile, newFile: SourceFile }[] = []; - structureIsReused = StructureIsReused.Completely; + for (const oldSourceFile of oldSourceFiles) { + const sourceFileOptions = getCreateSourceFileOptions(oldSourceFile.fileName, moduleResolutionCache, host, options); + let newSourceFile = host.getSourceFileByPath + ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, sourceFileOptions, /*onError*/ undefined, shouldCreateNewSourceFile || sourceFileOptions.impliedNodeFormat !== oldSourceFile.impliedNodeFormat) + : host.getSourceFile(oldSourceFile.fileName, sourceFileOptions, /*onError*/ undefined, shouldCreateNewSourceFile || sourceFileOptions.impliedNodeFormat !== oldSourceFile.impliedNodeFormat); // TODO: GH#18217 - // If the missing file paths are now present, it can change the progam structure, - // and hence cant reuse the structure. - // This is same as how we dont reuse the structure if one of the file from old program is now missing - if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) { + if (!newSourceFile) { return StructureIsReused.Not; } + newSourceFile.packageJsonLocations = sourceFileOptions.packageJsonLocations?.length ? sourceFileOptions.packageJsonLocations : undefined; + newSourceFile.packageJsonScope = sourceFileOptions.packageJsonScope; - const oldSourceFiles = oldProgram.getSourceFiles(); - const enum SeenPackageName { Exists, Modified } - const seenPackageNames = new Map(); - - for (const oldSourceFile of oldSourceFiles) { - const sourceFileOptions = getCreateSourceFileOptions(oldSourceFile.fileName, moduleResolutionCache, host, options); - let newSourceFile = host.getSourceFileByPath - ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, sourceFileOptions, /*onError*/ undefined, shouldCreateNewSourceFile || sourceFileOptions.impliedNodeFormat !== oldSourceFile.impliedNodeFormat) - : host.getSourceFile(oldSourceFile.fileName, sourceFileOptions, /*onError*/ undefined, shouldCreateNewSourceFile || sourceFileOptions.impliedNodeFormat !== oldSourceFile.impliedNodeFormat); // TODO: GH#18217 + Debug.assert(!newSourceFile.redirectInfo, "Host should not return a redirect source file from `getSourceFile`"); - if (!newSourceFile) { + let fileChanged: boolean; + if (oldSourceFile.redirectInfo) { + // We got `newSourceFile` by path, so it is actually for the unredirected file. + // This lets us know if the unredirected file has changed. If it has we should break the redirect. + if (newSourceFile !== oldSourceFile.redirectInfo.unredirected) { + // Underlying file has changed. Might not redirect anymore. Must rebuild program. return StructureIsReused.Not; } - newSourceFile.packageJsonLocations = sourceFileOptions.packageJsonLocations?.length ? sourceFileOptions.packageJsonLocations : undefined; - newSourceFile.packageJsonScope = sourceFileOptions.packageJsonScope; - - Debug.assert(!newSourceFile.redirectInfo, "Host should not return a redirect source file from `getSourceFile`"); - - let fileChanged: boolean; - if (oldSourceFile.redirectInfo) { - // We got `newSourceFile` by path, so it is actually for the unredirected file. - // This lets us know if the unredirected file has changed. If it has we should break the redirect. - if (newSourceFile !== oldSourceFile.redirectInfo.unredirected) { - // Underlying file has changed. Might not redirect anymore. Must rebuild program. - return StructureIsReused.Not; - } - fileChanged = false; - newSourceFile = oldSourceFile; // Use the redirect. - } - else if (oldProgram.redirectTargetsMap.has(oldSourceFile.path)) { - // If a redirected-to source file changes, the redirect may be broken. - if (newSourceFile !== oldSourceFile) { - return StructureIsReused.Not; - } - fileChanged = false; + fileChanged = false; + newSourceFile = oldSourceFile; // Use the redirect. + } + else if (oldProgram.redirectTargetsMap.has(oldSourceFile.path)) { + // If a redirected-to source file changes, the redirect may be broken. + if (newSourceFile !== oldSourceFile) { + return StructureIsReused.Not; } - else { - fileChanged = newSourceFile !== oldSourceFile; + fileChanged = false; + } + else { + fileChanged = newSourceFile !== oldSourceFile; + } + + // Since the project references havent changed, its right to set originalFileName and resolvedPath here + newSourceFile.path = oldSourceFile.path; + newSourceFile.originalFileName = oldSourceFile.originalFileName; + newSourceFile.resolvedPath = oldSourceFile.resolvedPath; + newSourceFile.fileName = oldSourceFile.fileName; + + const packageName = oldProgram.sourceFileToPackageName.get(oldSourceFile.path); + if (packageName !== undefined) { + // If there are 2 different source files for the same package name and at least one of them changes, + // they might become redirects. So we must rebuild the program. + const prevKind = seenPackageNames.get(packageName); + const newKind = fileChanged ? SeenPackageName.Modified : SeenPackageName.Exists; + if ((prevKind !== undefined && newKind === SeenPackageName.Modified) || prevKind === SeenPackageName.Modified) { + return StructureIsReused.Not; } + seenPackageNames.set(packageName, newKind); + } - // Since the project references havent changed, its right to set originalFileName and resolvedPath here - newSourceFile.path = oldSourceFile.path; - newSourceFile.originalFileName = oldSourceFile.originalFileName; - newSourceFile.resolvedPath = oldSourceFile.resolvedPath; - newSourceFile.fileName = oldSourceFile.fileName; - - const packageName = oldProgram.sourceFileToPackageName.get(oldSourceFile.path); - if (packageName !== undefined) { - // If there are 2 different source files for the same package name and at least one of them changes, - // they might become redirects. So we must rebuild the program. - const prevKind = seenPackageNames.get(packageName); - const newKind = fileChanged ? SeenPackageName.Modified : SeenPackageName.Exists; - if ((prevKind !== undefined && newKind === SeenPackageName.Modified) || prevKind === SeenPackageName.Modified) { - return StructureIsReused.Not; - } - seenPackageNames.set(packageName, newKind); + if (fileChanged) { + if (oldSourceFile.impliedNodeFormat !== newSourceFile.impliedNodeFormat) { + structureIsReused = StructureIsReused.SafeModules; } - - if (fileChanged) { - if (oldSourceFile.impliedNodeFormat !== newSourceFile.impliedNodeFormat) { + // The `newSourceFile` object was created for the new program. + else if (!arrayIsEqualTo(oldSourceFile.libReferenceDirectives, newSourceFile.libReferenceDirectives, fileReferenceIsEqualTo)) { + // 'lib' references has changed. Matches behavior in changesAffectModuleResolution + structureIsReused = StructureIsReused.SafeModules; + } + else if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) { + // value of no-default-lib has changed + // this will affect if default library is injected into the list of files + structureIsReused = StructureIsReused.SafeModules; + } + // check tripleslash references + else if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) { + // tripleslash references has changed + structureIsReused = StructureIsReused.SafeModules; + } + else { + // check imports and module augmentations + collectExternalModuleReferences(newSourceFile); + if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) { + // imports has changed structureIsReused = StructureIsReused.SafeModules; } - // The `newSourceFile` object was created for the new program. - else if (!arrayIsEqualTo(oldSourceFile.libReferenceDirectives, newSourceFile.libReferenceDirectives, fileReferenceIsEqualTo)) { - // 'lib' references has changed. Matches behavior in changesAffectModuleResolution + else if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) { + // moduleAugmentations has changed structureIsReused = StructureIsReused.SafeModules; } - else if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) { - // value of no-default-lib has changed - // this will affect if default library is injected into the list of files + else if ((oldSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags) !== (newSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags)) { + // dynamicImport has changed structureIsReused = StructureIsReused.SafeModules; } - // check tripleslash references - else if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) { - // tripleslash references has changed + else if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) { + // 'types' references has changed structureIsReused = StructureIsReused.SafeModules; } - else { - // check imports and module augmentations - collectExternalModuleReferences(newSourceFile); - if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) { - // imports has changed - structureIsReused = StructureIsReused.SafeModules; - } - else if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) { - // moduleAugmentations has changed - structureIsReused = StructureIsReused.SafeModules; - } - else if ((oldSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags) !== (newSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags)) { - // dynamicImport has changed - structureIsReused = StructureIsReused.SafeModules; - } - else if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) { - // 'types' references has changed - structureIsReused = StructureIsReused.SafeModules; - } - } - - // tentatively approve the file - modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); - } - else if (hasInvalidatedResolutions(oldSourceFile.path)) { - // 'module/types' references could have changed - structureIsReused = StructureIsReused.SafeModules; - - // add file to the modified list so that we will resolve it later - modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); } - // if file has passed all checks it should be safe to reuse it - newSourceFiles.push(newSourceFile); + // tentatively approve the file + modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); } + else if (hasInvalidatedResolutions(oldSourceFile.path)) { + // 'module/types' references could have changed + structureIsReused = StructureIsReused.SafeModules; - if (structureIsReused !== StructureIsReused.Completely) { - return structureIsReused; + // add file to the modified list so that we will resolve it later + modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); } - const modifiedFiles = modifiedSourceFiles.map(f => f.oldFile); - for (const oldFile of oldSourceFiles) { - if (!contains(modifiedFiles, oldFile)) { - for (const moduleName of oldFile.ambientModuleNames) { - ambientModuleNameToUnmodifiedFileName.set(moduleName, oldFile.fileName); - } + // if file has passed all checks it should be safe to reuse it + newSourceFiles.push(newSourceFile); + } + + if (structureIsReused !== StructureIsReused.Completely) { + return structureIsReused; + } + + const modifiedFiles = modifiedSourceFiles.map(f => f.oldFile); + for (const oldFile of oldSourceFiles) { + if (!contains(modifiedFiles, oldFile)) { + for (const moduleName of oldFile.ambientModuleNames) { + ambientModuleNameToUnmodifiedFileName.set(moduleName, oldFile.fileName); } } - // try to verify results of module resolution - for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) { - const moduleNames = getModuleNames(newSourceFile); - const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFile); - // ensure that module resolution results are still correct - const resolutionsChanged = hasChangesInResolutions(moduleNames, newSourceFile, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo); - if (resolutionsChanged) { - structureIsReused = StructureIsReused.SafeModules; - newSourceFile.resolvedModules = zipToModeAwareCache(newSourceFile, moduleNames, resolutions); - } - else { - newSourceFile.resolvedModules = oldSourceFile.resolvedModules; - } - const typesReferenceDirectives = newSourceFile.typeReferenceDirectives; - const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFile); - // ensure that types resolutions are still correct - const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, newSourceFile, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo); - if (typeReferenceResolutionsChanged) { - structureIsReused = StructureIsReused.SafeModules; - newSourceFile.resolvedTypeReferenceDirectiveNames = zipToModeAwareCache(newSourceFile, typesReferenceDirectives, typeReferenceResolutions); - } - else { - newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames; - } + } + // try to verify results of module resolution + for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) { + const moduleNames = getModuleNames(newSourceFile); + const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFile); + // ensure that module resolution results are still correct + const resolutionsChanged = hasChangesInResolutions(moduleNames, newSourceFile, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo); + if (resolutionsChanged) { + structureIsReused = StructureIsReused.SafeModules; + newSourceFile.resolvedModules = zipToModeAwareCache(newSourceFile, moduleNames, resolutions); } - - if (structureIsReused !== StructureIsReused.Completely) { - return structureIsReused; + else { + newSourceFile.resolvedModules = oldSourceFile.resolvedModules; } - - if (changesAffectingProgramStructure(oldOptions, options) || host.hasChangedAutomaticTypeDirectiveNames?.()) { - return StructureIsReused.SafeModules; + const typesReferenceDirectives = newSourceFile.typeReferenceDirectives; + const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFile); + // ensure that types resolutions are still correct + const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, newSourceFile, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo); + if (typeReferenceResolutionsChanged) { + structureIsReused = StructureIsReused.SafeModules; + newSourceFile.resolvedTypeReferenceDirectiveNames = zipToModeAwareCache(newSourceFile, typesReferenceDirectives, typeReferenceResolutions); } - - missingFilePaths = oldProgram.getMissingFilePaths(); - - // update fileName -> file mapping - Debug.assert(newSourceFiles.length === oldProgram.getSourceFiles().length); - for (const newSourceFile of newSourceFiles) { - filesByName.set(newSourceFile.path, newSourceFile); + else { + newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames; } - const oldFilesByNameMap = oldProgram.getFilesByNameMap(); - oldFilesByNameMap.forEach((oldFile, path) => { - if (!oldFile) { - filesByName.set(path, oldFile); - return; - } - if (oldFile.path === path) { - // Set the file as found during node modules search if it was found that way in old progra, - if (oldProgram!.isSourceFileFromExternalLibrary(oldFile)) { - sourceFilesFoundSearchingNodeModules.set(oldFile.path, true); - } - return; - } - filesByName.set(path, filesByName.get(oldFile.path)); - }); - - files = newSourceFiles; - fileReasons = oldProgram.getFileIncludeReasons(); - fileProcessingDiagnostics = oldProgram.getFileProcessingDiagnostics(); - resolvedTypeReferenceDirectives = oldProgram.getResolvedTypeReferenceDirectives(); - - sourceFileToPackageName = oldProgram.sourceFileToPackageName; - redirectTargetsMap = oldProgram.redirectTargetsMap; - usesUriStyleNodeCoreModules = oldProgram.usesUriStyleNodeCoreModules; + } - return StructureIsReused.Completely; + if (structureIsReused !== StructureIsReused.Completely) { + return structureIsReused; } - function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { - return { - getPrependNodes, - getCanonicalFileName, - getCommonSourceDirectory: program.getCommonSourceDirectory, - getCompilerOptions: program.getCompilerOptions, - getCurrentDirectory: () => currentDirectory, - getNewLine: () => host.getNewLine(), - getSourceFile: program.getSourceFile, - getSourceFileByPath: program.getSourceFileByPath, - getSourceFiles: program.getSourceFiles, - getLibFileFromReference: program.getLibFileFromReference, - isSourceFileFromExternalLibrary, - getResolvedProjectReferenceToRedirect, - getProjectReferenceRedirect, - isSourceOfProjectReferenceRedirect, - getSymlinkCache, - writeFile: writeFileCallback || writeFile, - isEmitBlocked, - readFile: f => host.readFile(f), - fileExists: f => { - // Use local caches - const path = toPath(f); - if (getSourceFileByPath(path)) return true; - if (contains(missingFilePaths, path)) return false; - // Before falling back to the host - return host.fileExists(f); - }, - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), - getBuildInfo: bundle => program.getBuildInfo?.(bundle), - getSourceFileFromReference: (file, ref) => program.getSourceFileFromReference(file, ref), - redirectTargetsMap, - getFileIncludeReasons: program.getFileIncludeReasons, - createHash: maybeBind(host, host.createHash), - }; + if (changesAffectingProgramStructure(oldOptions, options) || host.hasChangedAutomaticTypeDirectiveNames?.()) { + return StructureIsReused.SafeModules; } - function writeFile( - fileName: string, - text: string, - writeByteOrderMark: boolean, - onError?: (message: string) => void, - sourceFiles?: readonly SourceFile[], - data?: WriteFileCallbackData - ) { - host.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); - } - - function emitBuildInfo(writeFileCallback?: WriteFileCallback): EmitResult { - Debug.assert(!outFile(options)); - tracing?.push(tracing.Phase.Emit, "emitBuildInfo", {}, /*separateBeginAndEnd*/ true); - performance.mark("beforeEmit"); - const emitResult = emitFiles( - notImplementedResolver, - getEmitHost(writeFileCallback), - /*targetSourceFile*/ undefined, - /*transformers*/ noTransformers, - /*emitOnlyDtsFiles*/ false, - /*onlyBuildInfo*/ true - ); + missingFilePaths = oldProgram.getMissingFilePaths(); - performance.mark("afterEmit"); - performance.measure("Emit", "beforeEmit", "afterEmit"); - tracing?.pop(); - return emitResult; + // update fileName -> file mapping + Debug.assert(newSourceFiles.length === oldProgram.getSourceFiles().length); + for (const newSourceFile of newSourceFiles) { + filesByName.set(newSourceFile.path, newSourceFile); } + const oldFilesByNameMap = oldProgram.getFilesByNameMap(); + oldFilesByNameMap.forEach((oldFile, path) => { + if (!oldFile) { + filesByName.set(path, oldFile); + return; + } + if (oldFile.path === path) { + // Set the file as found during node modules search if it was found that way in old progra, + if (oldProgram!.isSourceFileFromExternalLibrary(oldFile)) { + sourceFilesFoundSearchingNodeModules.set(oldFile.path, true); + } + return; + } + filesByName.set(path, filesByName.get(oldFile.path)); + }); - function getResolvedProjectReferences() { - return resolvedProjectReferences; - } + files = newSourceFiles; + fileReasons = oldProgram.getFileIncludeReasons(); + fileProcessingDiagnostics = oldProgram.getFileProcessingDiagnostics(); + resolvedTypeReferenceDirectives = oldProgram.getResolvedTypeReferenceDirectives(); - function getProjectReferences() { - return projectReferences; - } + sourceFileToPackageName = oldProgram.sourceFileToPackageName; + redirectTargetsMap = oldProgram.redirectTargetsMap; + usesUriStyleNodeCoreModules = oldProgram.usesUriStyleNodeCoreModules; - function getPrependNodes() { - return createPrependNodes( - projectReferences, - (_ref, index) => resolvedProjectReferences![index]?.commandLine, - fileName => { - const path = toPath(fileName); - const sourceFile = getSourceFileByPath(path); - return sourceFile ? sourceFile.text : filesByName.has(path) ? undefined : host.readFile(path); - }, - host, - ); - } + return StructureIsReused.Completely; + } - function isSourceFileFromExternalLibrary(file: SourceFile): boolean { - return !!sourceFilesFoundSearchingNodeModules.get(file.path); - } + function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { + return { + getPrependNodes, + getCanonicalFileName, + getCommonSourceDirectory: program.getCommonSourceDirectory, + getCompilerOptions: program.getCompilerOptions, + getCurrentDirectory: () => currentDirectory, + getNewLine: () => host.getNewLine(), + getSourceFile: program.getSourceFile, + getSourceFileByPath: program.getSourceFileByPath, + getSourceFiles: program.getSourceFiles, + getLibFileFromReference: program.getLibFileFromReference, + isSourceFileFromExternalLibrary, + getResolvedProjectReferenceToRedirect, + getProjectReferenceRedirect, + isSourceOfProjectReferenceRedirect, + getSymlinkCache, + writeFile: writeFileCallback || writeFile, + isEmitBlocked, + readFile: f => host.readFile(f), + fileExists: f => { + // Use local caches + const path = toPath(f); + if (getSourceFileByPath(path)) return true; + if (contains(missingFilePaths, path)) return false; + // Before falling back to the host + return host.fileExists(f); + }, + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), + getBuildInfo: bundle => program.getBuildInfo?.(bundle), + getSourceFileFromReference: (file, ref) => program.getSourceFileFromReference(file, ref), + redirectTargetsMap, + getFileIncludeReasons: program.getFileIncludeReasons, + createHash: maybeBind(host, host.createHash), + }; + } - function isSourceFileDefaultLibrary(file: SourceFile): boolean { - if (!file.isDeclarationFile) { - return false; - } + function writeFile( + fileName: string, + text: string, + writeByteOrderMark: boolean, + onError?: (message: string) => void, + sourceFiles?: readonly SourceFile[], + data?: WriteFileCallbackData + ) { + host.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); + } - if (file.hasNoDefaultLib) { - return true; - } + function emitBuildInfo(writeFileCallback?: WriteFileCallback): EmitResult { + Debug.assert(!outFile(options)); + tracing?.push(tracing.Phase.Emit, "emitBuildInfo", {}, /*separateBeginAndEnd*/ true); + performance.mark("beforeEmit"); + const emitResult = emitFiles( + notImplementedResolver, + getEmitHost(writeFileCallback), + /*targetSourceFile*/ undefined, + /*transformers*/ noTransformers, + /*emitOnlyDtsFiles*/ false, + /*onlyBuildInfo*/ true + ); + + performance.mark("afterEmit"); + performance.measure("Emit", "beforeEmit", "afterEmit"); + tracing?.pop(); + return emitResult; + } - if (!options.noLib) { - return false; - } + function getResolvedProjectReferences() { + return resolvedProjectReferences; + } - // If '--lib' is not specified, include default library file according to '--target' - // otherwise, using options specified in '--lib' instead of '--target' default library file - const equalityComparer = host.useCaseSensitiveFileNames() ? equateStringsCaseSensitive : equateStringsCaseInsensitive; - if (!options.lib) { - return equalityComparer(file.fileName, getDefaultLibraryFileName()); - } - else { - return some(options.lib, libFileName => equalityComparer(file.fileName, pathForLibFile(libFileName))); - } - } + function getProjectReferences() { + return projectReferences; + } - function getTypeChecker() { - return typeChecker || (typeChecker = createTypeChecker(program)); + function getPrependNodes() { + return createPrependNodes( + projectReferences, + (_ref, index) => resolvedProjectReferences![index]?.commandLine, + fileName => { + const path = toPath(fileName); + const sourceFile = getSourceFileByPath(path); + return sourceFile ? sourceFile.text : filesByName.has(path) ? undefined : host.readFile(path); + }, + host, + ); + } + + function isSourceFileFromExternalLibrary(file: SourceFile): boolean { + return !!sourceFilesFoundSearchingNodeModules.get(file.path); + } + + function isSourceFileDefaultLibrary(file: SourceFile): boolean { + if (!file.isDeclarationFile) { + return false; } - function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnly?: boolean | EmitOnly, transformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { - tracing?.push(tracing.Phase.Emit, "emit", { path: sourceFile?.path }, /*separateBeginAndEnd*/ true); - const result = runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnly, transformers, forceDtsEmit)); - tracing?.pop(); - return result; + if (file.hasNoDefaultLib) { + return true; } - function isEmitBlocked(emitFileName: string): boolean { - return hasEmitBlockingDiagnostics.has(toPath(emitFileName)); + if (!options.noLib) { + return false; } - function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnly?: boolean | EmitOnly, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { - if (!forceDtsEmit) { - const result = handleNoEmitOptions(program, sourceFile, writeFileCallback, cancellationToken); - if (result) return result; - } + // If '--lib' is not specified, include default library file according to '--target' + // otherwise, using options specified in '--lib' instead of '--target' default library file + const equalityComparer = host.useCaseSensitiveFileNames() ? equateStringsCaseSensitive : equateStringsCaseInsensitive; + if (!options.lib) { + return equalityComparer(file.fileName, getDefaultLibraryFileName()); + } + else { + return some(options.lib, libFileName => equalityComparer(file.fileName, pathForLibFile(libFileName))); + } + } - // Create the emit resolver outside of the "emitTime" tracking code below. That way - // any cost associated with it (like type checking) are appropriate associated with - // the type-checking counter. - // - // If the -out option is specified, we should not pass the source file to getEmitResolver. - // This is because in the -out scenario all files need to be emitted, and therefore all - // files need to be type checked. And the way to specify that all files need to be type - // checked is to not pass the file to getEmitResolver. - const emitResolver = getTypeChecker().getEmitResolver(outFile(options) ? undefined : sourceFile, cancellationToken); - - performance.mark("beforeEmit"); - - const emitResult = emitFiles( - emitResolver, - getEmitHost(writeFileCallback), - sourceFile, - getTransformers(options, customTransformers, emitOnly), - emitOnly, - /*onlyBuildInfo*/ false, - forceDtsEmit - ); + function getTypeChecker() { + return typeChecker || (typeChecker = createTypeChecker(program)); + } - performance.mark("afterEmit"); - performance.measure("Emit", "beforeEmit", "afterEmit"); - return emitResult; - } + function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnly?: boolean | EmitOnly, transformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { + tracing?.push(tracing.Phase.Emit, "emit", { path: sourceFile?.path }, /*separateBeginAndEnd*/ true); + const result = runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnly, transformers, forceDtsEmit)); + tracing?.pop(); + return result; + } - function getSourceFile(fileName: string): SourceFile | undefined { - return getSourceFileByPath(toPath(fileName)); - } + function isEmitBlocked(emitFileName: string): boolean { + return hasEmitBlockingDiagnostics.has(toPath(emitFileName)); + } - function getSourceFileByPath(path: Path): SourceFile | undefined { - return filesByName.get(path) || undefined; - } + function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnly?: boolean | EmitOnly, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult { + if (!forceDtsEmit) { + const result = handleNoEmitOptions(program, sourceFile, writeFileCallback, cancellationToken); + if (result) return result; + } + + // Create the emit resolver outside of the "emitTime" tracking code below. That way + // any cost associated with it (like type checking) are appropriate associated with + // the type-checking counter. + // + // If the -out option is specified, we should not pass the source file to getEmitResolver. + // This is because in the -out scenario all files need to be emitted, and therefore all + // files need to be type checked. And the way to specify that all files need to be type + // checked is to not pass the file to getEmitResolver. + const emitResolver = getTypeChecker().getEmitResolver(outFile(options) ? undefined : sourceFile, cancellationToken); + + performance.mark("beforeEmit"); + + const emitResult = emitFiles( + emitResolver, + getEmitHost(writeFileCallback), + sourceFile, + getTransformers(options, customTransformers, emitOnly), + emitOnly, + /*onlyBuildInfo*/ false, + forceDtsEmit + ); + + performance.mark("afterEmit"); + performance.measure("Emit", "beforeEmit", "afterEmit"); + return emitResult; + } - function getDiagnosticsHelper( - sourceFile: SourceFile | undefined, - getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken | undefined) => readonly T[], - cancellationToken: CancellationToken | undefined): readonly T[] { - if (sourceFile) { - return getDiagnostics(sourceFile, cancellationToken); - } - return sortAndDeduplicateDiagnostics(flatMap(program.getSourceFiles(), sourceFile => { - if (cancellationToken) { - cancellationToken.throwIfCancellationRequested(); - } - return getDiagnostics(sourceFile, cancellationToken); - })); - } + function getSourceFile(fileName: string): SourceFile | undefined { + return getSourceFileByPath(toPath(fileName)); + } - function getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { - return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); - } + function getSourceFileByPath(path: Path): SourceFile | undefined { + return filesByName.get(path) || undefined; + } - function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); + function getDiagnosticsHelper( + sourceFile: SourceFile | undefined, + getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken | undefined) => readonly T[], + cancellationToken: CancellationToken | undefined): readonly T[] { + if (sourceFile) { + return getDiagnostics(sourceFile, cancellationToken); } + return sortAndDeduplicateDiagnostics(flatMap(program.getSourceFiles(), sourceFile => { + if (cancellationToken) { + cancellationToken.throwIfCancellationRequested(); + } + return getDiagnostics(sourceFile, cancellationToken); + })); + } - function getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined { - return sourceFile - ? cachedBindAndCheckDiagnosticsForFile.perFile?.get(sourceFile.path) - : cachedBindAndCheckDiagnosticsForFile.allDiagnostics; - } + function getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { + return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken); + } - function getBindAndCheckDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { - return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken); - } + function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken); + } - function getProgramDiagnostics(sourceFile: SourceFile): readonly Diagnostic[] { - if (skipTypeChecking(sourceFile, options, program)) { - return emptyArray; - } + function getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined { + return sourceFile + ? cachedBindAndCheckDiagnosticsForFile.perFile?.get(sourceFile.path) + : cachedBindAndCheckDiagnosticsForFile.allDiagnostics; + } - const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName); - if (!sourceFile.commentDirectives?.length) { - return programDiagnosticsInFile; - } + function getBindAndCheckDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken); + } - return getDiagnosticsWithPrecedingDirectives(sourceFile, sourceFile.commentDirectives, programDiagnosticsInFile).diagnostics; + function getProgramDiagnostics(sourceFile: SourceFile): readonly Diagnostic[] { + if (skipTypeChecking(sourceFile, options, program)) { + return emptyArray; } - function getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { - const options = program.getCompilerOptions(); - // collect diagnostics from the program only once if either no source file was specified or out/outFile is set (bundled emit) - if (!sourceFile || outFile(options)) { - return getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); - } - else { - return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken); - } + const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName); + if (!sourceFile.commentDirectives?.length) { + return programDiagnosticsInFile; } - function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): readonly DiagnosticWithLocation[] { - // For JavaScript files, we report semantic errors for using TypeScript-only - // constructs from within a JavaScript file as syntactic errors. - if (isSourceFileJS(sourceFile)) { - if (!sourceFile.additionalSyntacticDiagnostics) { - sourceFile.additionalSyntacticDiagnostics = getJSSyntacticDiagnosticsForFile(sourceFile); - } - return concatenate(sourceFile.additionalSyntacticDiagnostics, sourceFile.parseDiagnostics); - } - return sourceFile.parseDiagnostics; - } + return getDiagnosticsWithPrecedingDirectives(sourceFile, sourceFile.commentDirectives, programDiagnosticsInFile).diagnostics; + } - function runWithCancellationToken(func: () => T): T { - try { - return func(); - } - catch (e) { - if (e instanceof OperationCanceledException) { - // We were canceled while performing the operation. Because our type checker - // might be a bad state, we need to throw it away. - typeChecker = undefined!; - } + function getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { + const options = program.getCompilerOptions(); + // collect diagnostics from the program only once if either no source file was specified or out/outFile is set (bundled emit) + if (!sourceFile || outFile(options)) { + return getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); + } + else { + return getDiagnosticsHelper(sourceFile, getDeclarationDiagnosticsForFile, cancellationToken); + } + } - throw e; + function getSyntacticDiagnosticsForFile(sourceFile: SourceFile): readonly DiagnosticWithLocation[] { + // For JavaScript files, we report semantic errors for using TypeScript-only + // constructs from within a JavaScript file as syntactic errors. + if (isSourceFileJS(sourceFile)) { + if (!sourceFile.additionalSyntacticDiagnostics) { + sourceFile.additionalSyntacticDiagnostics = getJSSyntacticDiagnosticsForFile(sourceFile); } + return concatenate(sourceFile.additionalSyntacticDiagnostics, sourceFile.parseDiagnostics); } + return sourceFile.parseDiagnostics; + } - function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { - return concatenate( - filterSemanticDiagnostics(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken), options), - getProgramDiagnostics(sourceFile) - ); + function runWithCancellationToken(func: () => T): T { + try { + return func(); } + catch (e) { + if (e instanceof OperationCanceledException) { + // We were canceled while performing the operation. Because our type checker + // might be a bad state, we need to throw it away. + typeChecker = undefined!; + } - function getBindAndCheckDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { - return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedBindAndCheckDiagnosticsForFile, getBindAndCheckDiagnosticsForFileNoCache); + throw e; } + } - function getBindAndCheckDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { - return runWithCancellationToken(() => { - if (skipTypeChecking(sourceFile, options, program)) { - return emptyArray; - } + function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { + return concatenate( + filterSemanticDiagnostics(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken), options), + getProgramDiagnostics(sourceFile) + ); + } - const typeChecker = getTypeChecker(); - - Debug.assert(!!sourceFile.bindDiagnostics); - - const isJs = sourceFile.scriptKind === ScriptKind.JS || sourceFile.scriptKind === ScriptKind.JSX; - const isCheckJs = isJs && isCheckJsEnabledForFile(sourceFile, options); - const isPlainJs = isPlainJsFile(sourceFile, options.checkJs); - const isTsNoCheck = !!sourceFile.checkJsDirective && sourceFile.checkJsDirective.enabled === false; - - // By default, only type-check .ts, .tsx, Deferred, plain JS, checked JS and External - // - plain JS: .js files with no // ts-check and checkJs: undefined - // - check JS: .js files with either // ts-check or checkJs: true - // - external: files that are added by plugins - const includeBindAndCheckDiagnostics = !isTsNoCheck && (sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX - || sourceFile.scriptKind === ScriptKind.External || isPlainJs || isCheckJs || sourceFile.scriptKind === ScriptKind.Deferred); - let bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray; - let checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray; - if (isPlainJs) { - bindDiagnostics = filter(bindDiagnostics, d => plainJSErrors.has(d.code)); - checkDiagnostics = filter(checkDiagnostics, d => plainJSErrors.has(d.code)); - } - // skip ts-expect-error errors in plain JS files, and skip JSDoc errors except in checked JS - return getMergedBindAndCheckDiagnostics(sourceFile, includeBindAndCheckDiagnostics && !isPlainJs, bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined); - }); - } + function getBindAndCheckDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { + return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedBindAndCheckDiagnosticsForFile, getBindAndCheckDiagnosticsForFileNoCache); + } - function getMergedBindAndCheckDiagnostics(sourceFile: SourceFile, includeBindAndCheckDiagnostics: boolean, ...allDiagnostics: (readonly Diagnostic[] | undefined)[]) { - const flatDiagnostics = flatten(allDiagnostics); - if (!includeBindAndCheckDiagnostics || !sourceFile.commentDirectives?.length) { - return flatDiagnostics; + function getBindAndCheckDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] { + return runWithCancellationToken(() => { + if (skipTypeChecking(sourceFile, options, program)) { + return emptyArray; } - const { diagnostics, directives } = getDiagnosticsWithPrecedingDirectives(sourceFile, sourceFile.commentDirectives, flatDiagnostics); + const typeChecker = getTypeChecker(); + + Debug.assert(!!sourceFile.bindDiagnostics); + + const isJs = sourceFile.scriptKind === ScriptKind.JS || sourceFile.scriptKind === ScriptKind.JSX; + const isCheckJs = isJs && isCheckJsEnabledForFile(sourceFile, options); + const isPlainJs = isPlainJsFile(sourceFile, options.checkJs); + const isTsNoCheck = !!sourceFile.checkJsDirective && sourceFile.checkJsDirective.enabled === false; - for (const errorExpectation of directives.getUnusedExpectations()) { - diagnostics.push(createDiagnosticForRange(sourceFile, errorExpectation.range, Diagnostics.Unused_ts_expect_error_directive)); + // By default, only type-check .ts, .tsx, Deferred, plain JS, checked JS and External + // - plain JS: .js files with no // ts-check and checkJs: undefined + // - check JS: .js files with either // ts-check or checkJs: true + // - external: files that are added by plugins + const includeBindAndCheckDiagnostics = !isTsNoCheck && (sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX + || sourceFile.scriptKind === ScriptKind.External || isPlainJs || isCheckJs || sourceFile.scriptKind === ScriptKind.Deferred); + let bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray; + let checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray; + if (isPlainJs) { + bindDiagnostics = filter(bindDiagnostics, d => plainJSErrors.has(d.code)); + checkDiagnostics = filter(checkDiagnostics, d => plainJSErrors.has(d.code)); } + // skip ts-expect-error errors in plain JS files, and skip JSDoc errors except in checked JS + return getMergedBindAndCheckDiagnostics(sourceFile, includeBindAndCheckDiagnostics && !isPlainJs, bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined); + }); + } - return diagnostics; + function getMergedBindAndCheckDiagnostics(sourceFile: SourceFile, includeBindAndCheckDiagnostics: boolean, ...allDiagnostics: (readonly Diagnostic[] | undefined)[]) { + const flatDiagnostics = flatten(allDiagnostics); + if (!includeBindAndCheckDiagnostics || !sourceFile.commentDirectives?.length) { + return flatDiagnostics; } - /** - * Creates a map of comment directives along with the diagnostics immediately preceded by one of them. - * Comments that match to any of those diagnostics are marked as used. - */ - function getDiagnosticsWithPrecedingDirectives(sourceFile: SourceFile, commentDirectives: CommentDirective[], flatDiagnostics: Diagnostic[]) { - // Diagnostics are only reported if there is no comment directive preceding them - // This will modify the directives map by marking "used" ones with a corresponding diagnostic - const directives = createCommentDirectivesMap(sourceFile, commentDirectives); - const diagnostics = flatDiagnostics.filter(diagnostic => markPrecedingCommentDirectiveLine(diagnostic, directives) === -1); + const { diagnostics, directives } = getDiagnosticsWithPrecedingDirectives(sourceFile, sourceFile.commentDirectives, flatDiagnostics); - return { diagnostics, directives }; + for (const errorExpectation of directives.getUnusedExpectations()) { + diagnostics.push(createDiagnosticForRange(sourceFile, errorExpectation.range, Diagnostics.Unused_ts_expect_error_directive)); } - function getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { - return runWithCancellationToken(() => { - return getTypeChecker().getSuggestionDiagnostics(sourceFile, cancellationToken); - }); - } + return diagnostics; + } - /** - * @returns The line index marked as preceding the diagnostic, or -1 if none was. - */ - function markPrecedingCommentDirectiveLine(diagnostic: Diagnostic, directives: CommentDirectivesMap) { - const { file, start } = diagnostic; - if (!file) { - return -1; - } + /** + * Creates a map of comment directives along with the diagnostics immediately preceded by one of them. + * Comments that match to any of those diagnostics are marked as used. + */ + function getDiagnosticsWithPrecedingDirectives(sourceFile: SourceFile, commentDirectives: CommentDirective[], flatDiagnostics: Diagnostic[]) { + // Diagnostics are only reported if there is no comment directive preceding them + // This will modify the directives map by marking "used" ones with a corresponding diagnostic + const directives = createCommentDirectivesMap(sourceFile, commentDirectives); + const diagnostics = flatDiagnostics.filter(diagnostic => markPrecedingCommentDirectiveLine(diagnostic, directives) === -1); - // Start out with the line just before the text - const lineStarts = getLineStarts(file); - let line = computeLineAndCharacterOfPosition(lineStarts, start!).line - 1; // TODO: GH#18217 - while (line >= 0) { - // As soon as that line is known to have a comment directive, use that - if (directives.markUsed(line)) { - return line; - } + return { diagnostics, directives }; + } - // Stop searching if the line is not empty and not a comment - const lineText = file.text.slice(lineStarts[line], lineStarts[line + 1]).trim(); - if (lineText !== "" && !/^(\s*)\/\/(.*)$/.test(lineText)) { - return -1; - } + function getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { + return runWithCancellationToken(() => { + return getTypeChecker().getSuggestionDiagnostics(sourceFile, cancellationToken); + }); + } - line--; + /** + * @returns The line index marked as preceding the diagnostic, or -1 if none was. + */ + function markPrecedingCommentDirectiveLine(diagnostic: Diagnostic, directives: CommentDirectivesMap) { + const { file, start } = diagnostic; + if (!file) { + return -1; + } + + // Start out with the line just before the text + const lineStarts = getLineStarts(file); + let line = computeLineAndCharacterOfPosition(lineStarts, start!).line - 1; // TODO: GH#18217 + while (line >= 0) { + // As soon as that line is known to have a comment directive, use that + if (directives.markUsed(line)) { + return line; } - return -1; + // Stop searching if the line is not empty and not a comment + const lineText = file.text.slice(lineStarts[line], lineStarts[line + 1]).trim(); + if (lineText !== "" && !/^(\s*)\/\/(.*)$/.test(lineText)) { + return -1; + } + + line--; } - function getJSSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] { - return runWithCancellationToken(() => { - const diagnostics: DiagnosticWithLocation[] = []; - walk(sourceFile, sourceFile); - forEachChildRecursively(sourceFile, walk, walkArray); + return -1; + } - return diagnostics; + function getJSSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] { + return runWithCancellationToken(() => { + const diagnostics: DiagnosticWithLocation[] = []; + walk(sourceFile, sourceFile); + forEachChildRecursively(sourceFile, walk, walkArray); - function walk(node: Node, parent: Node) { - // Return directly from the case if the given node doesnt want to visit each child - // Otherwise break to visit each child + return diagnostics; - switch (parent.kind) { - case SyntaxKind.Parameter: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - if ((parent as ParameterDeclaration | PropertyDeclaration | MethodDeclaration).questionToken === node) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); - return "skip"; - } - // falls through - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - case SyntaxKind.VariableDeclaration: - // type annotation - if ((parent as FunctionLikeDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration).type === node) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_annotations_can_only_be_used_in_TypeScript_files)); - return "skip"; - } - } + function walk(node: Node, parent: Node) { + // Return directly from the case if the given node doesnt want to visit each child + // Otherwise break to visit each child - switch (node.kind) { - case SyntaxKind.ImportClause: - if ((node as ImportClause).isTypeOnly) { - diagnostics.push(createDiagnosticForNode(parent, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "import type")); - return "skip"; - } - break; - case SyntaxKind.ExportDeclaration: - if ((node as ExportDeclaration).isTypeOnly) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "export type")); - return "skip"; - } - break; - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - if ((node as ImportOrExportSpecifier).isTypeOnly) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, isImportSpecifier(node) ? "import...type" : "export...type")); - return "skip"; - } - break; - case SyntaxKind.ImportEqualsDeclaration: - diagnostics.push(createDiagnosticForNode(node, Diagnostics.import_can_only_be_used_in_TypeScript_files)); + switch (parent.kind) { + case SyntaxKind.Parameter: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + if ((parent as ParameterDeclaration | PropertyDeclaration | MethodDeclaration).questionToken === node) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")); return "skip"; - case SyntaxKind.ExportAssignment: - if ((node as ExportAssignment).isExportEquals) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.export_can_only_be_used_in_TypeScript_files)); - return "skip"; - } - break; - case SyntaxKind.HeritageClause: - const heritageClause = node as HeritageClause; - if (heritageClause.token === SyntaxKind.ImplementsKeyword) { - diagnostics.push(createDiagnosticForNode(node, Diagnostics.implements_clauses_can_only_be_used_in_TypeScript_files)); - return "skip"; - } - break; - case SyntaxKind.InterfaceDeclaration: - const interfaceKeyword = tokenToString(SyntaxKind.InterfaceKeyword); - Debug.assertIsDefined(interfaceKeyword); - diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, interfaceKeyword)); + } + // falls through + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + case SyntaxKind.VariableDeclaration: + // type annotation + if ((parent as FunctionLikeDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration).type === node) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_annotations_can_only_be_used_in_TypeScript_files)); return "skip"; - case SyntaxKind.ModuleDeclaration: - const moduleKeyword = node.flags & NodeFlags.Namespace ? tokenToString(SyntaxKind.NamespaceKeyword) : tokenToString(SyntaxKind.ModuleKeyword); - Debug.assertIsDefined(moduleKeyword); - diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, moduleKeyword)); + } + } + + switch (node.kind) { + case SyntaxKind.ImportClause: + if ((node as ImportClause).isTypeOnly) { + diagnostics.push(createDiagnosticForNode(parent, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "import type")); return "skip"; - case SyntaxKind.TypeAliasDeclaration: - diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_aliases_can_only_be_used_in_TypeScript_files)); + } + break; + case SyntaxKind.ExportDeclaration: + if ((node as ExportDeclaration).isTypeOnly) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, "export type")); return "skip"; - case SyntaxKind.EnumDeclaration: - const enumKeyword = Debug.checkDefined(tokenToString(SyntaxKind.EnumKeyword)); - diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, enumKeyword)); + } + break; + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + if ((node as ImportOrExportSpecifier).isTypeOnly) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, isImportSpecifier(node) ? "import...type" : "export...type")); return "skip"; - case SyntaxKind.NonNullExpression: - diagnostics.push(createDiagnosticForNode(node, Diagnostics.Non_null_assertions_can_only_be_used_in_TypeScript_files)); + } + break; + case SyntaxKind.ImportEqualsDeclaration: + diagnostics.push(createDiagnosticForNode(node, Diagnostics.import_can_only_be_used_in_TypeScript_files)); + return "skip"; + case SyntaxKind.ExportAssignment: + if ((node as ExportAssignment).isExportEquals) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.export_can_only_be_used_in_TypeScript_files)); return "skip"; - case SyntaxKind.AsExpression: - diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files)); + } + break; + case SyntaxKind.HeritageClause: + const heritageClause = node as HeritageClause; + if (heritageClause.token === SyntaxKind.ImplementsKeyword) { + diagnostics.push(createDiagnosticForNode(node, Diagnostics.implements_clauses_can_only_be_used_in_TypeScript_files)); return "skip"; - case SyntaxKind.SatisfiesExpression: - diagnostics.push(createDiagnosticForNode((node as SatisfiesExpression).type, Diagnostics.Type_satisfaction_expressions_can_only_be_used_in_TypeScript_files)); + } + break; + case SyntaxKind.InterfaceDeclaration: + const interfaceKeyword = tokenToString(SyntaxKind.InterfaceKeyword); + Debug.assertIsDefined(interfaceKeyword); + diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, interfaceKeyword)); + return "skip"; + case SyntaxKind.ModuleDeclaration: + const moduleKeyword = node.flags & NodeFlags.Namespace ? tokenToString(SyntaxKind.NamespaceKeyword) : tokenToString(SyntaxKind.ModuleKeyword); + Debug.assertIsDefined(moduleKeyword); + diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, moduleKeyword)); + return "skip"; + case SyntaxKind.TypeAliasDeclaration: + diagnostics.push(createDiagnosticForNode(node, Diagnostics.Type_aliases_can_only_be_used_in_TypeScript_files)); + return "skip"; + case SyntaxKind.EnumDeclaration: + const enumKeyword = Debug.checkDefined(tokenToString(SyntaxKind.EnumKeyword)); + diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_declarations_can_only_be_used_in_TypeScript_files, enumKeyword)); + return "skip"; + case SyntaxKind.NonNullExpression: + diagnostics.push(createDiagnosticForNode(node, Diagnostics.Non_null_assertions_can_only_be_used_in_TypeScript_files)); + return "skip"; + case SyntaxKind.AsExpression: + diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files)); + return "skip"; + case SyntaxKind.SatisfiesExpression: + diagnostics.push(createDiagnosticForNode((node as SatisfiesExpression).type, Diagnostics.Type_satisfaction_expressions_can_only_be_used_in_TypeScript_files)); + return "skip"; + case SyntaxKind.TypeAssertionExpression: + Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX. + } + } + + function walkArray(nodes: NodeArray, parent: Node) { + if (canHaveModifiers(parent) && parent.modifiers === nodes && some(nodes, isDecorator) && !options.experimentalDecorators) { + diagnostics.push(createDiagnosticForNode(parent, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning)); + } + + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ArrowFunction: + // Check type parameters + if (nodes === (parent as DeclarationWithTypeParameterChildren).typeParameters) { + diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_parameter_declarations_can_only_be_used_in_TypeScript_files)); return "skip"; - case SyntaxKind.TypeAssertionExpression: - Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX. - } - } - - function walkArray(nodes: NodeArray, parent: Node) { - if (canHaveModifiers(parent) && parent.modifiers === nodes && some(nodes, isDecorator) && !options.experimentalDecorators) { - diagnostics.push(createDiagnosticForNode(parent, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning)); - } - - switch (parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ArrowFunction: - // Check type parameters - if (nodes === (parent as DeclarationWithTypeParameterChildren).typeParameters) { - diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_parameter_declarations_can_only_be_used_in_TypeScript_files)); - return "skip"; - } - // falls through + } + // falls through - case SyntaxKind.VariableStatement: - // Check modifiers - if (nodes === (parent as VariableStatement).modifiers) { - checkModifiers((parent as VariableStatement).modifiers!, parent.kind === SyntaxKind.VariableStatement); - return "skip"; - } - break; - case SyntaxKind.PropertyDeclaration: - // Check modifiers of property declaration - if (nodes === (parent as PropertyDeclaration).modifiers) { - for (const modifier of nodes as NodeArray) { - if (isModifier(modifier) - && modifier.kind !== SyntaxKind.StaticKeyword - && modifier.kind !== SyntaxKind.AccessorKeyword) { - diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); - } + case SyntaxKind.VariableStatement: + // Check modifiers + if (nodes === (parent as VariableStatement).modifiers) { + checkModifiers((parent as VariableStatement).modifiers!, parent.kind === SyntaxKind.VariableStatement); + return "skip"; + } + break; + case SyntaxKind.PropertyDeclaration: + // Check modifiers of property declaration + if (nodes === (parent as PropertyDeclaration).modifiers) { + for (const modifier of nodes as NodeArray) { + if (isModifier(modifier) + && modifier.kind !== SyntaxKind.StaticKeyword + && modifier.kind !== SyntaxKind.AccessorKeyword) { + diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); } - return "skip"; } - break; - case SyntaxKind.Parameter: - // Check modifiers of parameter declaration - if (nodes === (parent as ParameterDeclaration).modifiers && some(nodes, isModifier)) { - diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Parameter_modifiers_can_only_be_used_in_TypeScript_files)); - return "skip"; - } - break; - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.TaggedTemplateExpression: - // Check type arguments - if (nodes === (parent as NodeWithTypeArguments).typeArguments) { - diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_arguments_can_only_be_used_in_TypeScript_files)); - return "skip"; + return "skip"; + } + break; + case SyntaxKind.Parameter: + // Check modifiers of parameter declaration + if (nodes === (parent as ParameterDeclaration).modifiers && some(nodes, isModifier)) { + diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Parameter_modifiers_can_only_be_used_in_TypeScript_files)); + return "skip"; + } + break; + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.TaggedTemplateExpression: + // Check type arguments + if (nodes === (parent as NodeWithTypeArguments).typeArguments) { + diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.Type_arguments_can_only_be_used_in_TypeScript_files)); + return "skip"; + } + break; + } + } + + function checkModifiers(modifiers: NodeArray, isConstValid: boolean) { + for (const modifier of modifiers) { + switch (modifier.kind) { + case SyntaxKind.ConstKeyword: + if (isConstValid) { + continue; } + // to report error, + // falls through + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.AbstractKeyword: + case SyntaxKind.OverrideKeyword: + case SyntaxKind.InKeyword: + case SyntaxKind.OutKeyword: + diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); break; - } - } - function checkModifiers(modifiers: NodeArray, isConstValid: boolean) { - for (const modifier of modifiers) { - switch (modifier.kind) { - case SyntaxKind.ConstKeyword: - if (isConstValid) { - continue; - } - // to report error, - // falls through - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.AbstractKeyword: - case SyntaxKind.OverrideKeyword: - case SyntaxKind.InKeyword: - case SyntaxKind.OutKeyword: - diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind))); - break; - - // These are all legal modifiers. - case SyntaxKind.StaticKeyword: - case SyntaxKind.ExportKeyword: - case SyntaxKind.DefaultKeyword: - case SyntaxKind.AccessorKeyword: - } + // These are all legal modifiers. + case SyntaxKind.StaticKeyword: + case SyntaxKind.ExportKeyword: + case SyntaxKind.DefaultKeyword: + case SyntaxKind.AccessorKeyword: } } + } - function createDiagnosticForNodeArray(nodes: NodeArray, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { - const start = nodes.pos; - return createFileDiagnostic(sourceFile, start, nodes.end - start, message, arg0, arg1, arg2); - } + function createDiagnosticForNodeArray(nodes: NodeArray, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { + const start = nodes.pos; + return createFileDiagnostic(sourceFile, start, nodes.end - start, message, arg0, arg1, arg2); + } - // Since these are syntactic diagnostics, parent might not have been set - // this means the sourceFile cannot be infered from the node - function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { - return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2); - } - }); - } + // Since these are syntactic diagnostics, parent might not have been set + // this means the sourceFile cannot be infered from the node + function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation { + return createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1, arg2); + } + }); + } - function getDeclarationDiagnosticsWorker(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { - return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedDeclarationDiagnosticsForFile, getDeclarationDiagnosticsForFileNoCache); - } + function getDeclarationDiagnosticsWorker(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { + return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedDeclarationDiagnosticsForFile, getDeclarationDiagnosticsForFileNoCache); + } - function getDeclarationDiagnosticsForFileNoCache(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { - return runWithCancellationToken(() => { - const resolver = getTypeChecker().getEmitResolver(sourceFile, cancellationToken); - // Don't actually write any files since we're just getting diagnostics. - return ts.getDeclarationDiagnostics(getEmitHost(noop), resolver, sourceFile) || emptyArray; - }); - } + function getDeclarationDiagnosticsForFileNoCache(sourceFile: SourceFile | undefined, cancellationToken: CancellationToken | undefined): readonly DiagnosticWithLocation[] { + return runWithCancellationToken(() => { + const resolver = getTypeChecker().getEmitResolver(sourceFile, cancellationToken); + // Don't actually write any files since we're just getting diagnostics. + return ts.getDeclarationDiagnostics(getEmitHost(noop), resolver, sourceFile) || emptyArray; + }); + } - function getAndCacheDiagnostics( - sourceFile: T, - cancellationToken: CancellationToken | undefined, - cache: DiagnosticCache, - getDiagnostics: (sourceFile: T, cancellationToken: CancellationToken | undefined) => readonly U[], - ): readonly U[] { + function getAndCacheDiagnostics( + sourceFile: T, + cancellationToken: CancellationToken | undefined, + cache: DiagnosticCache, + getDiagnostics: (sourceFile: T, cancellationToken: CancellationToken | undefined) => readonly U[], + ): readonly U[] { - const cachedResult = sourceFile - ? cache.perFile?.get(sourceFile.path) - : cache.allDiagnostics; + const cachedResult = sourceFile + ? cache.perFile?.get(sourceFile.path) + : cache.allDiagnostics; - if (cachedResult) { - return cachedResult; - } - const result = getDiagnostics(sourceFile, cancellationToken); - if (sourceFile) { - (cache.perFile || (cache.perFile = new Map())).set(sourceFile.path, result); - } - else { - cache.allDiagnostics = result; - } - return result; + if (cachedResult) { + return cachedResult; } - - function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { - return sourceFile.isDeclarationFile ? [] : getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); + const result = getDiagnostics(sourceFile, cancellationToken); + if (sourceFile) { + (cache.perFile || (cache.perFile = new Map())).set(sourceFile.path, result); } - - function getOptionsDiagnostics(): SortedReadonlyArray { - return sortAndDeduplicateDiagnostics(concatenate( - programDiagnostics.getGlobalDiagnostics(), - getOptionsDiagnosticsOfConfigFile() - )); + else { + cache.allDiagnostics = result; } + return result; + } - function getOptionsDiagnosticsOfConfigFile() { - if (!options.configFile) return emptyArray; - let diagnostics = programDiagnostics.getDiagnostics(options.configFile.fileName); - forEachResolvedProjectReference(resolvedRef => { - diagnostics = concatenate(diagnostics, programDiagnostics.getDiagnostics(resolvedRef.sourceFile.fileName)); - }); - return diagnostics; - } + function getDeclarationDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { + return sourceFile.isDeclarationFile ? [] : getDeclarationDiagnosticsWorker(sourceFile, cancellationToken); + } - function getGlobalDiagnostics(): SortedReadonlyArray { - return rootNames.length ? sortAndDeduplicateDiagnostics(getTypeChecker().getGlobalDiagnostics().slice()) : emptyArray as any as SortedReadonlyArray; - } + function getOptionsDiagnostics(): SortedReadonlyArray { + return sortAndDeduplicateDiagnostics(concatenate( + programDiagnostics.getGlobalDiagnostics(), + getOptionsDiagnosticsOfConfigFile() + )); + } - function getConfigFileParsingDiagnostics(): readonly Diagnostic[] { - return configFileParsingDiagnostics || emptyArray; - } + function getOptionsDiagnosticsOfConfigFile() { + if (!options.configFile) return emptyArray; + let diagnostics = programDiagnostics.getDiagnostics(options.configFile.fileName); + forEachResolvedProjectReference(resolvedRef => { + diagnostics = concatenate(diagnostics, programDiagnostics.getDiagnostics(resolvedRef.sourceFile.fileName)); + }); + return diagnostics; + } - function processRootFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason) { - processSourceFile(normalizePath(fileName), isDefaultLib, ignoreNoDefaultLib, /*packageId*/ undefined, reason); - } + function getGlobalDiagnostics(): SortedReadonlyArray { + return rootNames.length ? sortAndDeduplicateDiagnostics(getTypeChecker().getGlobalDiagnostics().slice()) : emptyArray as any as SortedReadonlyArray; + } - function fileReferenceIsEqualTo(a: FileReference, b: FileReference): boolean { - return a.fileName === b.fileName; - } + function getConfigFileParsingDiagnostics(): readonly Diagnostic[] { + return configFileParsingDiagnostics || emptyArray; + } - function moduleNameIsEqualTo(a: StringLiteralLike | Identifier, b: StringLiteralLike | Identifier): boolean { - return a.kind === SyntaxKind.Identifier - ? b.kind === SyntaxKind.Identifier && a.escapedText === b.escapedText - : b.kind === SyntaxKind.StringLiteral && a.text === b.text; - } + function processRootFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason) { + processSourceFile(normalizePath(fileName), isDefaultLib, ignoreNoDefaultLib, /*packageId*/ undefined, reason); + } - function createSyntheticImport(text: string, file: SourceFile) { - const externalHelpersModuleReference = factory.createStringLiteral(text); - const importDecl = factory.createImportDeclaration(/*modifiers*/ undefined, /*importClause*/ undefined, externalHelpersModuleReference, /*assertClause*/ undefined); - addEmitFlags(importDecl, EmitFlags.NeverApplyImportHelper); - setParent(externalHelpersModuleReference, importDecl); - setParent(importDecl, file); - // explicitly unset the synthesized flag on these declarations so the checker API will answer questions about them - // (which is required to build the dependency graph for incremental emit) - (externalHelpersModuleReference as Mutable).flags &= ~NodeFlags.Synthesized; - (importDecl as Mutable).flags &= ~NodeFlags.Synthesized; - return externalHelpersModuleReference; - } + function fileReferenceIsEqualTo(a: FileReference, b: FileReference): boolean { + return a.fileName === b.fileName; + } - function collectExternalModuleReferences(file: SourceFile): void { - if (file.imports) { - return; - } + function moduleNameIsEqualTo(a: StringLiteralLike | Identifier, b: StringLiteralLike | Identifier): boolean { + return a.kind === SyntaxKind.Identifier + ? b.kind === SyntaxKind.Identifier && a.escapedText === b.escapedText + : b.kind === SyntaxKind.StringLiteral && a.text === b.text; + } + + function createSyntheticImport(text: string, file: SourceFile) { + const externalHelpersModuleReference = factory.createStringLiteral(text); + const importDecl = factory.createImportDeclaration(/*modifiers*/ undefined, /*importClause*/ undefined, externalHelpersModuleReference, /*assertClause*/ undefined); + addEmitFlags(importDecl, EmitFlags.NeverApplyImportHelper); + setParent(externalHelpersModuleReference, importDecl); + setParent(importDecl, file); + // explicitly unset the synthesized flag on these declarations so the checker API will answer questions about them + // (which is required to build the dependency graph for incremental emit) + (externalHelpersModuleReference as Mutable).flags &= ~NodeFlags.Synthesized; + (importDecl as Mutable).flags &= ~NodeFlags.Synthesized; + return externalHelpersModuleReference; + } - const isJavaScriptFile = isSourceFileJS(file); - const isExternalModuleFile = isExternalModule(file); + function collectExternalModuleReferences(file: SourceFile): void { + if (file.imports) { + return; + } - // file.imports may not be undefined if there exists dynamic import - let imports: StringLiteralLike[] | undefined; - let moduleAugmentations: (StringLiteral | Identifier)[] | undefined; - let ambientModules: string[] | undefined; + const isJavaScriptFile = isSourceFileJS(file); + const isExternalModuleFile = isExternalModule(file); - // If we are importing helpers, we need to add a synthetic reference to resolve the - // helpers library. - if ((options.isolatedModules || isExternalModuleFile) - && !file.isDeclarationFile) { - if (options.importHelpers) { - // synthesize 'import "tslib"' declaration - imports = [createSyntheticImport(externalHelpersModuleNameText, file)]; - } - const jsxImport = getJSXRuntimeImport(getJSXImplicitImportBase(options, file), options); - if (jsxImport) { - // synthesize `import "base/jsx-runtime"` declaration - (imports ||= []).push(createSyntheticImport(jsxImport, file)); - } - } + // file.imports may not be undefined if there exists dynamic import + let imports: StringLiteralLike[] | undefined; + let moduleAugmentations: (StringLiteral | Identifier)[] | undefined; + let ambientModules: string[] | undefined; - for (const node of file.statements) { - collectModuleReferences(node, /*inAmbientModule*/ false); + // If we are importing helpers, we need to add a synthetic reference to resolve the + // helpers library. + if ((options.isolatedModules || isExternalModuleFile) + && !file.isDeclarationFile) { + if (options.importHelpers) { + // synthesize 'import "tslib"' declaration + imports = [createSyntheticImport(externalHelpersModuleNameText, file)]; } - if ((file.flags & NodeFlags.PossiblyContainsDynamicImport) || isJavaScriptFile) { - collectDynamicImportOrRequireCalls(file); + const jsxImport = getJSXRuntimeImport(getJSXImplicitImportBase(options, file), options); + if (jsxImport) { + // synthesize `import "base/jsx-runtime"` declaration + (imports ||= []).push(createSyntheticImport(jsxImport, file)); } + } + + for (const node of file.statements) { + collectModuleReferences(node, /*inAmbientModule*/ false); + } + if ((file.flags & NodeFlags.PossiblyContainsDynamicImport) || isJavaScriptFile) { + collectDynamicImportOrRequireCalls(file); + } - file.imports = imports || emptyArray; - file.moduleAugmentations = moduleAugmentations || emptyArray; - file.ambientModuleNames = ambientModules || emptyArray; + file.imports = imports || emptyArray; + file.moduleAugmentations = moduleAugmentations || emptyArray; + file.ambientModuleNames = ambientModules || emptyArray; - return; + return; - function collectModuleReferences(node: Statement, inAmbientModule: boolean): void { - if (isAnyImportOrReExport(node)) { - const moduleNameExpr = getExternalModuleName(node); - // TypeScript 1.0 spec (April 2014): 12.1.6 - // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules - // only through top - level external module names. Relative external module names are not permitted. - if (moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text && (!inAmbientModule || !isExternalModuleNameRelative(moduleNameExpr.text))) { - setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here - imports = append(imports, moduleNameExpr); - if (!usesUriStyleNodeCoreModules && currentNodeModulesDepth === 0 && !file.isDeclarationFile) { - usesUriStyleNodeCoreModules = startsWith(moduleNameExpr.text, "node:"); - } + function collectModuleReferences(node: Statement, inAmbientModule: boolean): void { + if (isAnyImportOrReExport(node)) { + const moduleNameExpr = getExternalModuleName(node); + // TypeScript 1.0 spec (April 2014): 12.1.6 + // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules + // only through top - level external module names. Relative external module names are not permitted. + if (moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text && (!inAmbientModule || !isExternalModuleNameRelative(moduleNameExpr.text))) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here + imports = append(imports, moduleNameExpr); + if (!usesUriStyleNodeCoreModules && currentNodeModulesDepth === 0 && !file.isDeclarationFile) { + usesUriStyleNodeCoreModules = startsWith(moduleNameExpr.text, "node:"); } } - else if (isModuleDeclaration(node)) { - if (isAmbientModule(node) && (inAmbientModule || hasSyntacticModifier(node, ModifierFlags.Ambient) || file.isDeclarationFile)) { - (node.name as Mutable).parent = node; - const nameText = getTextOfIdentifierOrLiteral(node.name); - // Ambient module declarations can be interpreted as augmentations for some existing external modules. - // This will happen in two cases: - // - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope - // - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name - // immediately nested in top level ambient module declaration . - if (isExternalModuleFile || (inAmbientModule && !isExternalModuleNameRelative(nameText))) { - (moduleAugmentations || (moduleAugmentations = [])).push(node.name); + } + else if (isModuleDeclaration(node)) { + if (isAmbientModule(node) && (inAmbientModule || hasSyntacticModifier(node, ModifierFlags.Ambient) || file.isDeclarationFile)) { + (node.name as Mutable).parent = node; + const nameText = getTextOfIdentifierOrLiteral(node.name); + // Ambient module declarations can be interpreted as augmentations for some existing external modules. + // This will happen in two cases: + // - if current file is external module then module augmentation is a ambient module declaration defined in the top level scope + // - if current file is not external module then module augmentation is an ambient module declaration with non-relative module name + // immediately nested in top level ambient module declaration . + if (isExternalModuleFile || (inAmbientModule && !isExternalModuleNameRelative(nameText))) { + (moduleAugmentations || (moduleAugmentations = [])).push(node.name); + } + else if (!inAmbientModule) { + if (file.isDeclarationFile) { + // for global .d.ts files record name of ambient module + (ambientModules || (ambientModules = [])).push(nameText); } - else if (!inAmbientModule) { - if (file.isDeclarationFile) { - // for global .d.ts files record name of ambient module - (ambientModules || (ambientModules = [])).push(nameText); - } - // An AmbientExternalModuleDeclaration declares an external module. - // This type of declaration is permitted only in the global module. - // The StringLiteral must specify a top - level external module name. - // Relative external module names are not permitted - - // NOTE: body of ambient module is always a module block, if it exists - const body = (node as ModuleDeclaration).body as ModuleBlock; - if (body) { - for (const statement of body.statements) { - collectModuleReferences(statement, /*inAmbientModule*/ true); - } + // An AmbientExternalModuleDeclaration declares an external module. + // This type of declaration is permitted only in the global module. + // The StringLiteral must specify a top - level external module name. + // Relative external module names are not permitted + + // NOTE: body of ambient module is always a module block, if it exists + const body = (node as ModuleDeclaration).body as ModuleBlock; + if (body) { + for (const statement of body.statements) { + collectModuleReferences(statement, /*inAmbientModule*/ true); } } } } } + } - function collectDynamicImportOrRequireCalls(file: SourceFile) { - const r = /import|require/g; - while (r.exec(file.text) !== null) { // eslint-disable-line no-null/no-null - const node = getNodeAtPosition(file, r.lastIndex); - if (isJavaScriptFile && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { - setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here - imports = append(imports, node.arguments[0]); - } - // we have to check the argument list has length of at least 1. We will still have to process these even though we have parsing error. - else if (isImportCall(node) && node.arguments.length >= 1 && isStringLiteralLike(node.arguments[0])) { - setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here - imports = append(imports, node.arguments[0]); - } - else if (isLiteralImportTypeNode(node)) { - setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here - imports = append(imports, node.argument.literal); - } + function collectDynamicImportOrRequireCalls(file: SourceFile) { + const r = /import|require/g; + while (r.exec(file.text) !== null) { // eslint-disable-line no-null/no-null + const node = getNodeAtPosition(file, r.lastIndex); + if (isJavaScriptFile && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here + imports = append(imports, node.arguments[0]); } - } - - /** Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only in JS files */ - function getNodeAtPosition(sourceFile: SourceFile, position: number): Node { - let current: Node = sourceFile; - const getContainingChild = (child: Node) => { - if (child.pos <= position && (position < child.end || (position === child.end && (child.kind === SyntaxKind.EndOfFileToken)))) { - return child; - } - }; - while (true) { - const child = isJavaScriptFile && hasJSDocNodes(current) && forEach(current.jsDoc, getContainingChild) || forEachChild(current, getContainingChild); - if (!child) { - return current; - } - current = child; + // we have to check the argument list has length of at least 1. We will still have to process these even though we have parsing error. + else if (isImportCall(node) && node.arguments.length >= 1 && isStringLiteralLike(node.arguments[0])) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here + imports = append(imports, node.arguments[0]); + } + else if (isLiteralImportTypeNode(node)) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here + imports = append(imports, node.argument.literal); } } } - function getLibFileFromReference(ref: FileReference) { - const libName = toFileNameLowerCase(ref.fileName); - const libFileName = libMap.get(libName); - if (libFileName) { - return getSourceFile(pathForLibFile(libFileName)); + /** Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only in JS files */ + function getNodeAtPosition(sourceFile: SourceFile, position: number): Node { + let current: Node = sourceFile; + const getContainingChild = (child: Node) => { + if (child.pos <= position && (position < child.end || (position === child.end && (child.kind === SyntaxKind.EndOfFileToken)))) { + return child; + } + }; + while (true) { + const child = isJavaScriptFile && hasJSDocNodes(current) && forEach(current.jsDoc, getContainingChild) || forEachChild(current, getContainingChild); + if (!child) { + return current; + } + current = child; } } + } - /** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ - function getSourceFileFromReference(referencingFile: SourceFile | UnparsedSource, ref: FileReference): SourceFile | undefined { - return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), getSourceFile); + function getLibFileFromReference(ref: FileReference) { + const libName = toFileNameLowerCase(ref.fileName); + const libFileName = libMap.get(libName); + if (libFileName) { + return getSourceFile(pathForLibFile(libFileName)); } + } - function getSourceFileFromReferenceWorker( - fileName: string, - getSourceFile: (fileName: string) => SourceFile | undefined, - fail?: (diagnostic: DiagnosticMessage, ...argument: string[]) => void, - reason?: FileIncludeReason): SourceFile | undefined { + /** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ + function getSourceFileFromReference(referencingFile: SourceFile | UnparsedSource, ref: FileReference): SourceFile | undefined { + return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), getSourceFile); + } - if (hasExtension(fileName)) { - const canonicalFileName = host.getCanonicalFileName(fileName); - if (!options.allowNonTsExtensions && !forEach(flatten(supportedExtensionsWithJsonIfResolveJsonModule), extension => fileExtensionIs(canonicalFileName, extension))) { - if (fail) { - if (hasJSFileExtension(canonicalFileName)) { - fail(Diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option, fileName); - } - else { - fail(Diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'" + flatten(supportedExtensions).join("', '") + "'"); - } - } - return undefined; - } + function getSourceFileFromReferenceWorker( + fileName: string, + getSourceFile: (fileName: string) => SourceFile | undefined, + fail?: (diagnostic: DiagnosticMessage, ...argument: string[]) => void, + reason?: FileIncludeReason): SourceFile | undefined { - const sourceFile = getSourceFile(fileName); + if (hasExtension(fileName)) { + const canonicalFileName = host.getCanonicalFileName(fileName); + if (!options.allowNonTsExtensions && !forEach(flatten(supportedExtensionsWithJsonIfResolveJsonModule), extension => fileExtensionIs(canonicalFileName, extension))) { if (fail) { - if (!sourceFile) { - const redirect = getProjectReferenceRedirect(fileName); - if (redirect) { - fail(Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, fileName); - } - else { - fail(Diagnostics.File_0_not_found, fileName); - } + if (hasJSFileExtension(canonicalFileName)) { + fail(Diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option, fileName); } - else if (isReferencedFile(reason) && canonicalFileName === host.getCanonicalFileName(getSourceFileByPath(reason.file)!.fileName)) { - fail(Diagnostics.A_file_cannot_have_a_reference_to_itself); + else { + fail(Diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'" + flatten(supportedExtensions).join("', '") + "'"); } } - return sourceFile; + return undefined; } - else { - const sourceFileNoExtension = options.allowNonTsExtensions && getSourceFile(fileName); - if (sourceFileNoExtension) return sourceFileNoExtension; - if (fail && options.allowNonTsExtensions) { - fail(Diagnostics.File_0_not_found, fileName); - return undefined; + const sourceFile = getSourceFile(fileName); + if (fail) { + if (!sourceFile) { + const redirect = getProjectReferenceRedirect(fileName); + if (redirect) { + fail(Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, fileName); + } + else { + fail(Diagnostics.File_0_not_found, fileName); + } + } + else if (isReferencedFile(reason) && canonicalFileName === host.getCanonicalFileName(getSourceFileByPath(reason.file)!.fileName)) { + fail(Diagnostics.A_file_cannot_have_a_reference_to_itself); } - - // Only try adding extensions from the first supported group (which should be .ts/.tsx/.d.ts) - const sourceFileWithAddedExtension = forEach(supportedExtensions[0], extension => getSourceFile(fileName + extension)); - if (fail && !sourceFileWithAddedExtension) fail(Diagnostics.Could_not_resolve_the_path_0_with_the_extensions_Colon_1, fileName, "'" + flatten(supportedExtensions).join("', '") + "'"); - return sourceFileWithAddedExtension; } + return sourceFile; } + else { + const sourceFileNoExtension = options.allowNonTsExtensions && getSourceFile(fileName); + if (sourceFileNoExtension) return sourceFileNoExtension; - /** This has side effects through `findSourceFile`. */ - function processSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, packageId: PackageId | undefined, reason: FileIncludeReason): void { - getSourceFileFromReferenceWorker( - fileName, - fileName => findSourceFile(fileName, isDefaultLib, ignoreNoDefaultLib, reason, packageId), // TODO: GH#18217 - (diagnostic, ...args) => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, diagnostic, args), - reason - ); - } - - function processProjectReferenceFile(fileName: string, reason: ProjectReferenceFile) { - return processSourceFile(fileName, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined, reason); - } - - function reportFileNamesDifferOnlyInCasingError(fileName: string, existingFile: SourceFile, reason: FileIncludeReason): void { - const hasExistingReasonToReportErrorOn = !isReferencedFile(reason) && some(fileReasons.get(existingFile.path), isReferencedFile); - if (hasExistingReasonToReportErrorOn) { - addFilePreprocessingFileExplainingDiagnostic(existingFile, reason, Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, [existingFile.fileName, fileName]); - } - else { - addFilePreprocessingFileExplainingDiagnostic(existingFile, reason, Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, [fileName, existingFile.fileName]); + if (fail && options.allowNonTsExtensions) { + fail(Diagnostics.File_0_not_found, fileName); + return undefined; } - } - - function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string, sourceFileOptions: CreateSourceFileOptions): SourceFile { - const redirect: SourceFile = Object.create(redirectTarget); - redirect.fileName = fileName; - redirect.path = path; - redirect.resolvedPath = resolvedPath; - redirect.originalFileName = originalFileName; - redirect.redirectInfo = { redirectTarget, unredirected }; - redirect.packageJsonLocations = sourceFileOptions.packageJsonLocations?.length ? sourceFileOptions.packageJsonLocations : undefined; - redirect.packageJsonScope = sourceFileOptions.packageJsonScope; - sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); - Object.defineProperties(redirect, { - id: { - get(this: SourceFile) { return this.redirectInfo!.redirectTarget.id; }, - set(this: SourceFile, value: SourceFile["id"]) { this.redirectInfo!.redirectTarget.id = value; }, - }, - symbol: { - get(this: SourceFile) { return this.redirectInfo!.redirectTarget.symbol; }, - set(this: SourceFile, value: SourceFile["symbol"]) { this.redirectInfo!.redirectTarget.symbol = value; }, - }, - }); - return redirect; - } - // Get source file from normalized fileName - function findSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { - tracing?.push(tracing.Phase.Program, "findSourceFile", { - fileName, - isDefaultLib: isDefaultLib || undefined, - fileIncludeKind: (FileIncludeKind as any)[reason.kind], - }); - const result = findSourceFileWorker(fileName, isDefaultLib, ignoreNoDefaultLib, reason, packageId); - tracing?.pop(); - return result; + // Only try adding extensions from the first supported group (which should be .ts/.tsx/.d.ts) + const sourceFileWithAddedExtension = forEach(supportedExtensions[0], extension => getSourceFile(fileName + extension)); + if (fail && !sourceFileWithAddedExtension) fail(Diagnostics.Could_not_resolve_the_path_0_with_the_extensions_Colon_1, fileName, "'" + flatten(supportedExtensions).join("', '") + "'"); + return sourceFileWithAddedExtension; } + } - function getCreateSourceFileOptions(fileName: string, moduleResolutionCache: ModuleResolutionCache | undefined, host: CompilerHost, options: CompilerOptions): CreateSourceFileOptions { - // It's a _little odd_ that we can't set `impliedNodeFormat` until the program step - but it's the first and only time we have a resolution cache - // and a freshly made source file node on hand at the same time, and we need both to set the field. Persisting the resolution cache all the way - // to the check and emit steps would be bad - so we much prefer detecting and storing the format information on the source file node upfront. - const result = getImpliedNodeFormatForFileWorker(getNormalizedAbsolutePath(fileName, currentDirectory), moduleResolutionCache?.getPackageJsonInfoCache(), host, options); - const languageVersion = getEmitScriptTarget(options); - const setExternalModuleIndicator = getSetExternalModuleIndicator(options); - return typeof result === "object" ? - { ...result, languageVersion, setExternalModuleIndicator } : - { languageVersion, impliedNodeFormat: result, setExternalModuleIndicator }; - } - - function findSourceFileWorker(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { - const path = toPath(fileName); - if (useSourceOfProjectReferenceRedirect) { - let source = getSourceOfProjectReferenceRedirect(path); - // If preserveSymlinks is true, module resolution wont jump the symlink - // but the resolved real path may be the .d.ts from project reference - // Note:: Currently we try the real path only if the - // file is from node_modules to avoid having to run real path on all file paths - if (!source && - host.realpath && - options.preserveSymlinks && - isDeclarationFileName(fileName) && - stringContains(fileName, nodeModulesPathPart)) { - const realPath = toPath(host.realpath(fileName)); - if (realPath !== path) source = getSourceOfProjectReferenceRedirect(realPath); - } - if (source) { - const file = isString(source) ? - findSourceFile(source, isDefaultLib, ignoreNoDefaultLib, reason, packageId) : - undefined; - if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined); - return file; - } - } - const originalFileName = fileName; - if (filesByName.has(path)) { - const file = filesByName.get(path); - addFileIncludeReason(file || undefined, reason); - // try to check if we've already seen this file but with a different casing in path - // NOTE: this only makes sense for case-insensitive file systems, and only on files which are not redirected - if (file && options.forceConsistentCasingInFileNames) { - const checkedName = file.fileName; - const isRedirect = toPath(checkedName) !== toPath(fileName); - if (isRedirect) { - fileName = getProjectReferenceRedirect(fileName) || fileName; - } - // Check if it differs only in drive letters its ok to ignore that error: - const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); - const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); - if (checkedAbsolutePath !== inputAbsolutePath) { - reportFileNamesDifferOnlyInCasingError(fileName, file, reason); - } - } - - // If the file was previously found via a node_modules search, but is now being processed as a root file, - // then everything it sucks in may also be marked incorrectly, and needs to be checked again. - if (file && sourceFilesFoundSearchingNodeModules.get(file.path) && currentNodeModulesDepth === 0) { - sourceFilesFoundSearchingNodeModules.set(file.path, false); - if (!options.noResolve) { - processReferencedFiles(file, isDefaultLib); - processTypeReferenceDirectives(file); - } - if (!options.noLib) { - processLibReferenceDirectives(file); - } - - modulesWithElidedImports.set(file.path, false); - processImportedModules(file); - } - // See if we need to reprocess the imports due to prior skipped imports - else if (file && modulesWithElidedImports.get(file.path)) { - if (currentNodeModulesDepth < maxNodeModuleJsDepth) { - modulesWithElidedImports.set(file.path, false); - processImportedModules(file); - } - } - - return file || undefined; - } + /** This has side effects through `findSourceFile`. */ + function processSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, packageId: PackageId | undefined, reason: FileIncludeReason): void { + getSourceFileFromReferenceWorker( + fileName, + fileName => findSourceFile(fileName, isDefaultLib, ignoreNoDefaultLib, reason, packageId), // TODO: GH#18217 + (diagnostic, ...args) => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, diagnostic, args), + reason + ); + } - let redirectedPath: Path | undefined; - if (isReferencedFile(reason) && !useSourceOfProjectReferenceRedirect) { - const redirectProject = getProjectReferenceRedirectProject(fileName); - if (redirectProject) { - if (outFile(redirectProject.commandLine.options)) { - // Shouldnt create many to 1 mapping file in --out scenario - return undefined; - } - const redirect = getProjectReferenceOutputName(redirectProject, fileName); - fileName = redirect; - // Once we start redirecting to a file, we can potentially come back to it - // via a back-reference from another file in the .d.ts folder. If that happens we'll - // end up trying to add it to the program *again* because we were tracking it via its - // original (un-redirected) name. So we have to map both the original path and the redirected path - // to the source file we're about to find/create - redirectedPath = toPath(redirect); - } - } + function processProjectReferenceFile(fileName: string, reason: ProjectReferenceFile) { + return processSourceFile(fileName, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined, reason); + } - // We haven't looked for this file, do so now and cache result - const sourceFileOptions = getCreateSourceFileOptions(fileName, moduleResolutionCache, host, options); - const file = host.getSourceFile( - fileName, - sourceFileOptions, - hostErrorMessage => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, Diagnostics.Cannot_read_file_0_Colon_1, [fileName, hostErrorMessage]), - shouldCreateNewSourceFile || (oldProgram?.getSourceFileByPath(toPath(fileName))?.impliedNodeFormat !== sourceFileOptions.impliedNodeFormat) - ); + function reportFileNamesDifferOnlyInCasingError(fileName: string, existingFile: SourceFile, reason: FileIncludeReason): void { + const hasExistingReasonToReportErrorOn = !isReferencedFile(reason) && some(fileReasons.get(existingFile.path), isReferencedFile); + if (hasExistingReasonToReportErrorOn) { + addFilePreprocessingFileExplainingDiagnostic(existingFile, reason, Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, [existingFile.fileName, fileName]); + } + else { + addFilePreprocessingFileExplainingDiagnostic(existingFile, reason, Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, [fileName, existingFile.fileName]); + } + } - if (packageId) { - const packageIdKey = packageIdToString(packageId); - const fileFromPackageId = packageIdToSourceFile.get(packageIdKey); - if (fileFromPackageId) { - // Some other SourceFile already exists with this package name and version. - // Instead of creating a duplicate, just redirect to the existing one. - const dupFile = createRedirectSourceFile(fileFromPackageId, file!, fileName, path, toPath(fileName), originalFileName, sourceFileOptions); - redirectTargetsMap.add(fileFromPackageId.path, fileName); - addFileToFilesByName(dupFile, path, redirectedPath); - addFileIncludeReason(dupFile, reason); - sourceFileToPackageName.set(path, packageIdToPackageName(packageId)); - processingOtherFiles!.push(dupFile); - return dupFile; - } - else if (file) { - // This is the first source file to have this packageId. - packageIdToSourceFile.set(packageIdKey, file); - sourceFileToPackageName.set(path, packageIdToPackageName(packageId)); - } - } - addFileToFilesByName(file, path, redirectedPath); - - if (file) { - sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); - file.fileName = fileName; // Ensure that source file has same name as what we were looking for - file.path = path; - file.resolvedPath = toPath(fileName); - file.originalFileName = originalFileName; - file.packageJsonLocations = sourceFileOptions.packageJsonLocations?.length ? sourceFileOptions.packageJsonLocations : undefined; - file.packageJsonScope = sourceFileOptions.packageJsonScope; - addFileIncludeReason(file, reason); - - if (host.useCaseSensitiveFileNames()) { - const pathLowerCase = toFileNameLowerCase(path); - // for case-sensitive file systems check if we've already seen some file with similar filename ignoring case - const existingFile = filesByNameIgnoreCase!.get(pathLowerCase); - if (existingFile) { - reportFileNamesDifferOnlyInCasingError(fileName, existingFile, reason); - } - else { - filesByNameIgnoreCase!.set(pathLowerCase, file); - } - } + function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string, sourceFileOptions: CreateSourceFileOptions): SourceFile { + const redirect: SourceFile = Object.create(redirectTarget); + redirect.fileName = fileName; + redirect.path = path; + redirect.resolvedPath = resolvedPath; + redirect.originalFileName = originalFileName; + redirect.redirectInfo = { redirectTarget, unredirected }; + redirect.packageJsonLocations = sourceFileOptions.packageJsonLocations?.length ? sourceFileOptions.packageJsonLocations : undefined; + redirect.packageJsonScope = sourceFileOptions.packageJsonScope; + sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); + Object.defineProperties(redirect, { + id: { + get(this: SourceFile) { return this.redirectInfo!.redirectTarget.id; }, + set(this: SourceFile, value: SourceFile["id"]) { this.redirectInfo!.redirectTarget.id = value; }, + }, + symbol: { + get(this: SourceFile) { return this.redirectInfo!.redirectTarget.symbol; }, + set(this: SourceFile, value: SourceFile["symbol"]) { this.redirectInfo!.redirectTarget.symbol = value; }, + }, + }); + return redirect; + } + + // Get source file from normalized fileName + function findSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { + tracing?.push(tracing.Phase.Program, "findSourceFile", { + fileName, + isDefaultLib: isDefaultLib || undefined, + fileIncludeKind: (FileIncludeKind as any)[reason.kind], + }); + const result = findSourceFileWorker(fileName, isDefaultLib, ignoreNoDefaultLib, reason, packageId); + tracing?.pop(); + return result; + } - skipDefaultLib = skipDefaultLib || (file.hasNoDefaultLib && !ignoreNoDefaultLib); + function getCreateSourceFileOptions(fileName: string, moduleResolutionCache: ModuleResolutionCache | undefined, host: CompilerHost, options: CompilerOptions): CreateSourceFileOptions { + // It's a _little odd_ that we can't set `impliedNodeFormat` until the program step - but it's the first and only time we have a resolution cache + // and a freshly made source file node on hand at the same time, and we need both to set the field. Persisting the resolution cache all the way + // to the check and emit steps would be bad - so we much prefer detecting and storing the format information on the source file node upfront. + const result = getImpliedNodeFormatForFileWorker(getNormalizedAbsolutePath(fileName, currentDirectory), moduleResolutionCache?.getPackageJsonInfoCache(), host, options); + const languageVersion = getEmitScriptTarget(options); + const setExternalModuleIndicator = getSetExternalModuleIndicator(options); + return typeof result === "object" ? + { ...result, languageVersion, setExternalModuleIndicator } : + { languageVersion, impliedNodeFormat: result, setExternalModuleIndicator }; + } + function findSourceFileWorker(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, reason: FileIncludeReason, packageId: PackageId | undefined): SourceFile | undefined { + const path = toPath(fileName); + if (useSourceOfProjectReferenceRedirect) { + let source = getSourceOfProjectReferenceRedirect(path); + // If preserveSymlinks is true, module resolution wont jump the symlink + // but the resolved real path may be the .d.ts from project reference + // Note:: Currently we try the real path only if the + // file is from node_modules to avoid having to run real path on all file paths + if (!source && + host.realpath && + options.preserveSymlinks && + isDeclarationFileName(fileName) && + stringContains(fileName, nodeModulesPathPart)) { + const realPath = toPath(host.realpath(fileName)); + if (realPath !== path) source = getSourceOfProjectReferenceRedirect(realPath); + } + if (source) { + const file = isString(source) ? + findSourceFile(source, isDefaultLib, ignoreNoDefaultLib, reason, packageId) : + undefined; + if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined); + return file; + } + } + const originalFileName = fileName; + if (filesByName.has(path)) { + const file = filesByName.get(path); + addFileIncludeReason(file || undefined, reason); + // try to check if we've already seen this file but with a different casing in path + // NOTE: this only makes sense for case-insensitive file systems, and only on files which are not redirected + if (file && options.forceConsistentCasingInFileNames) { + const checkedName = file.fileName; + const isRedirect = toPath(checkedName) !== toPath(fileName); + if (isRedirect) { + fileName = getProjectReferenceRedirect(fileName) || fileName; + } + // Check if it differs only in drive letters its ok to ignore that error: + const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); + const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(fileName, currentDirectory); + if (checkedAbsolutePath !== inputAbsolutePath) { + reportFileNamesDifferOnlyInCasingError(fileName, file, reason); + } + } + + // If the file was previously found via a node_modules search, but is now being processed as a root file, + // then everything it sucks in may also be marked incorrectly, and needs to be checked again. + if (file && sourceFilesFoundSearchingNodeModules.get(file.path) && currentNodeModulesDepth === 0) { + sourceFilesFoundSearchingNodeModules.set(file.path, false); if (!options.noResolve) { processReferencedFiles(file, isDefaultLib); processTypeReferenceDirectives(file); @@ -3033,1389 +3001,1485 @@ namespace ts { processLibReferenceDirectives(file); } - - // always process imported modules to record module name resolutions + modulesWithElidedImports.set(file.path, false); processImportedModules(file); - - if (isDefaultLib) { - processingDefaultLibFiles!.push(file); - } - else { - processingOtherFiles!.push(file); + } + // See if we need to reprocess the imports due to prior skipped imports + else if (file && modulesWithElidedImports.get(file.path)) { + if (currentNodeModulesDepth < maxNodeModuleJsDepth) { + modulesWithElidedImports.set(file.path, false); + processImportedModules(file); } } - return file; - } - function addFileIncludeReason(file: SourceFile | undefined, reason: FileIncludeReason) { - if (file) fileReasons.add(file.path, reason); + return file || undefined; } - function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) { - if (redirectedPath) { - filesByName.set(redirectedPath, file); - filesByName.set(path, file || false); - } - else { - filesByName.set(path, file); + let redirectedPath: Path | undefined; + if (isReferencedFile(reason) && !useSourceOfProjectReferenceRedirect) { + const redirectProject = getProjectReferenceRedirectProject(fileName); + if (redirectProject) { + if (outFile(redirectProject.commandLine.options)) { + // Shouldnt create many to 1 mapping file in --out scenario + return undefined; + } + const redirect = getProjectReferenceOutputName(redirectProject, fileName); + fileName = redirect; + // Once we start redirecting to a file, we can potentially come back to it + // via a back-reference from another file in the .d.ts folder. If that happens we'll + // end up trying to add it to the program *again* because we were tracking it via its + // original (un-redirected) name. So we have to map both the original path and the redirected path + // to the source file we're about to find/create + redirectedPath = toPath(redirect); + } + } + + // We haven't looked for this file, do so now and cache result + const sourceFileOptions = getCreateSourceFileOptions(fileName, moduleResolutionCache, host, options); + const file = host.getSourceFile( + fileName, + sourceFileOptions, + hostErrorMessage => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, Diagnostics.Cannot_read_file_0_Colon_1, [fileName, hostErrorMessage]), + shouldCreateNewSourceFile || (oldProgram?.getSourceFileByPath(toPath(fileName))?.impliedNodeFormat !== sourceFileOptions.impliedNodeFormat) + ); + + if (packageId) { + const packageIdKey = packageIdToString(packageId); + const fileFromPackageId = packageIdToSourceFile.get(packageIdKey); + if (fileFromPackageId) { + // Some other SourceFile already exists with this package name and version. + // Instead of creating a duplicate, just redirect to the existing one. + const dupFile = createRedirectSourceFile(fileFromPackageId, file!, fileName, path, toPath(fileName), originalFileName, sourceFileOptions); + redirectTargetsMap.add(fileFromPackageId.path, fileName); + addFileToFilesByName(dupFile, path, redirectedPath); + addFileIncludeReason(dupFile, reason); + sourceFileToPackageName.set(path, packageIdToPackageName(packageId)); + processingOtherFiles!.push(dupFile); + return dupFile; + } + else if (file) { + // This is the first source file to have this packageId. + packageIdToSourceFile.set(packageIdKey, file); + sourceFileToPackageName.set(path, packageIdToPackageName(packageId)); + } + } + addFileToFilesByName(file, path, redirectedPath); + + if (file) { + sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); + file.fileName = fileName; // Ensure that source file has same name as what we were looking for + file.path = path; + file.resolvedPath = toPath(fileName); + file.originalFileName = originalFileName; + file.packageJsonLocations = sourceFileOptions.packageJsonLocations?.length ? sourceFileOptions.packageJsonLocations : undefined; + file.packageJsonScope = sourceFileOptions.packageJsonScope; + addFileIncludeReason(file, reason); + + if (host.useCaseSensitiveFileNames()) { + const pathLowerCase = toFileNameLowerCase(path); + // for case-sensitive file systems check if we've already seen some file with similar filename ignoring case + const existingFile = filesByNameIgnoreCase!.get(pathLowerCase); + if (existingFile) { + reportFileNamesDifferOnlyInCasingError(fileName, existingFile, reason); + } + else { + filesByNameIgnoreCase!.set(pathLowerCase, file); + } } - } - function getProjectReferenceRedirect(fileName: string): string | undefined { - const referencedProject = getProjectReferenceRedirectProject(fileName); - return referencedProject && getProjectReferenceOutputName(referencedProject, fileName); - } + skipDefaultLib = skipDefaultLib || (file.hasNoDefaultLib && !ignoreNoDefaultLib); - function getProjectReferenceRedirectProject(fileName: string) { - // Ignore dts or any json files - if (!resolvedProjectReferences || !resolvedProjectReferences.length || isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json)) { - return undefined; + if (!options.noResolve) { + processReferencedFiles(file, isDefaultLib); + processTypeReferenceDirectives(file); + } + if (!options.noLib) { + processLibReferenceDirectives(file); } - - // If this file is produced by a referenced project, we need to rewrite it to - // look in the output folder of the referenced project rather than the input - return getResolvedProjectReferenceToRedirect(fileName); - } - function getProjectReferenceOutputName(referencedProject: ResolvedProjectReference, fileName: string) { - const out = outFile(referencedProject.commandLine.options); - return out ? - changeExtension(out, Extension.Dts) : - getOutputDeclarationFileName(fileName, referencedProject.commandLine, !host.useCaseSensitiveFileNames()); - } + // always process imported modules to record module name resolutions + processImportedModules(file); - /** - * Get the referenced project if the file is input file from that reference project - */ - function getResolvedProjectReferenceToRedirect(fileName: string) { - if (mapFromFileToProjectReferenceRedirects === undefined) { - mapFromFileToProjectReferenceRedirects = new Map(); - forEachResolvedProjectReference(referencedProject => { - // not input file from the referenced project, ignore - if (toPath(options.configFilePath!) !== referencedProject.sourceFile.path) { - referencedProject.commandLine.fileNames.forEach(f => - mapFromFileToProjectReferenceRedirects!.set(toPath(f), referencedProject.sourceFile.path)); - } - }); + if (isDefaultLib) { + processingDefaultLibFiles!.push(file); + } + else { + processingOtherFiles!.push(file); } - - const referencedProjectPath = mapFromFileToProjectReferenceRedirects.get(toPath(fileName)); - return referencedProjectPath && getResolvedProjectReferenceByPath(referencedProjectPath); } + return file; + } - function forEachResolvedProjectReference( - cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined - ): T | undefined { - return ts.forEachResolvedProjectReference(resolvedProjectReferences, cb); - } + function addFileIncludeReason(file: SourceFile | undefined, reason: FileIncludeReason) { + if (file) fileReasons.add(file.path, reason); + } - function getSourceOfProjectReferenceRedirect(path: Path) { - if (!isDeclarationFileName(path)) return undefined; - if (mapFromToProjectReferenceRedirectSource === undefined) { - mapFromToProjectReferenceRedirectSource = new Map(); - forEachResolvedProjectReference(resolvedRef => { - const out = outFile(resolvedRef.commandLine.options); - if (out) { - // Dont know which source file it means so return true? - const outputDts = changeExtension(out, Extension.Dts); - mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true); - } - else { - const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(resolvedRef.commandLine, !host.useCaseSensitiveFileNames())); - forEach(resolvedRef.commandLine.fileNames, fileName => { - if (!isDeclarationFileName(fileName) && !fileExtensionIs(fileName, Extension.Json)) { - const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory); - mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName); - } - }); - } - }); - } - return mapFromToProjectReferenceRedirectSource.get(path); + function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) { + if (redirectedPath) { + filesByName.set(redirectedPath, file); + filesByName.set(path, file || false); } - - function isSourceOfProjectReferenceRedirect(fileName: string) { - return useSourceOfProjectReferenceRedirect && !!getResolvedProjectReferenceToRedirect(fileName); + else { + filesByName.set(path, file); } + } - function getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined { - if (!projectReferenceRedirects) { - return undefined; - } + function getProjectReferenceRedirect(fileName: string): string | undefined { + const referencedProject = getProjectReferenceRedirectProject(fileName); + return referencedProject && getProjectReferenceOutputName(referencedProject, fileName); + } - return projectReferenceRedirects.get(projectReferencePath) || undefined; + function getProjectReferenceRedirectProject(fileName: string) { + // Ignore dts or any json files + if (!resolvedProjectReferences || !resolvedProjectReferences.length || isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json)) { + return undefined; } - function processReferencedFiles(file: SourceFile, isDefaultLib: boolean) { - forEach(file.referencedFiles, (ref, index) => { - processSourceFile( - resolveTripleslashReference(ref.fileName, file.fileName), - isDefaultLib, - /*ignoreNoDefaultLib*/ false, - /*packageId*/ undefined, - { kind: FileIncludeKind.ReferenceFile, file: file.path, index, } - ); - }); - } + // If this file is produced by a referenced project, we need to rewrite it to + // look in the output folder of the referenced project rather than the input + return getResolvedProjectReferenceToRedirect(fileName); + } - function processTypeReferenceDirectives(file: SourceFile) { - const typeDirectives = file.typeReferenceDirectives; - if (!typeDirectives) { - return; - } - const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file); - for (let index = 0; index < typeDirectives.length; index++) { - const ref = file.typeReferenceDirectives[index]; - const resolvedTypeReferenceDirective = resolutions[index]; - // store resolved type directive on the file - const fileName = toFileNameLowerCase(ref.fileName); - setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective); - const mode = ref.resolutionMode || file.impliedNodeFormat; - if (mode && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) { - programDiagnostics.add(createDiagnosticForRange(file, ref, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext)); - } - processTypeReferenceDirective(fileName, mode, resolvedTypeReferenceDirective, { kind: FileIncludeKind.TypeReferenceDirective, file: file.path, index, }); - } - } + function getProjectReferenceOutputName(referencedProject: ResolvedProjectReference, fileName: string) { + const out = outFile(referencedProject.commandLine.options); + return out ? + changeExtension(out, Extension.Dts) : + getOutputDeclarationFileName(fileName, referencedProject.commandLine, !host.useCaseSensitiveFileNames()); + } - function processTypeReferenceDirective( - typeReferenceDirective: string, - mode: SourceFile["impliedNodeFormat"] | undefined, - resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined, - reason: FileIncludeReason - ): void { - tracing?.push(tracing.Phase.Program, "processTypeReferenceDirective", { directive: typeReferenceDirective, hasResolved: !!resolvedTypeReferenceDirective, refKind: reason.kind, refPath: isReferencedFile(reason) ? reason.file : undefined }); - processTypeReferenceDirectiveWorker(typeReferenceDirective, mode, resolvedTypeReferenceDirective, reason); - tracing?.pop(); + /** + * Get the referenced project if the file is input file from that reference project + */ + function getResolvedProjectReferenceToRedirect(fileName: string) { + if (mapFromFileToProjectReferenceRedirects === undefined) { + mapFromFileToProjectReferenceRedirects = new Map(); + forEachResolvedProjectReference(referencedProject => { + // not input file from the referenced project, ignore + if (toPath(options.configFilePath!) !== referencedProject.sourceFile.path) { + referencedProject.commandLine.fileNames.forEach(f => + mapFromFileToProjectReferenceRedirects!.set(toPath(f), referencedProject.sourceFile.path)); + } + }); } - function processTypeReferenceDirectiveWorker( - typeReferenceDirective: string, - mode: SourceFile["impliedNodeFormat"] | undefined, - resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined, - reason: FileIncludeReason - ): void { + const referencedProjectPath = mapFromFileToProjectReferenceRedirects.get(toPath(fileName)); + return referencedProjectPath && getResolvedProjectReferenceByPath(referencedProjectPath); + } - // If we already found this library as a primary reference - nothing to do - const previousResolution = resolvedTypeReferenceDirectives.get(typeReferenceDirective, mode); - if (previousResolution && previousResolution.primary) { - return; - } - let saveResolution = true; - if (resolvedTypeReferenceDirective) { - if (resolvedTypeReferenceDirective.isExternalLibraryImport) currentNodeModulesDepth++; + function forEachResolvedProjectReference( + cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined + ): T | undefined { + return ts.forEachResolvedProjectReference(resolvedProjectReferences, cb); + } - if (resolvedTypeReferenceDirective.primary) { - // resolved from the primary path - processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, reason); // TODO: GH#18217 + function getSourceOfProjectReferenceRedirect(path: Path) { + if (!isDeclarationFileName(path)) return undefined; + if (mapFromToProjectReferenceRedirectSource === undefined) { + mapFromToProjectReferenceRedirectSource = new Map(); + forEachResolvedProjectReference(resolvedRef => { + const out = outFile(resolvedRef.commandLine.options); + if (out) { + // Dont know which source file it means so return true? + const outputDts = changeExtension(out, Extension.Dts); + mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true); } else { - // If we already resolved to this file, it must have been a secondary reference. Check file contents - // for sameness and possibly issue an error - if (previousResolution) { - // Don't bother reading the file again if it's the same file. - if (resolvedTypeReferenceDirective.resolvedFileName !== previousResolution.resolvedFileName) { - const otherFileText = host.readFile(resolvedTypeReferenceDirective.resolvedFileName!); - const existingFile = getSourceFile(previousResolution.resolvedFileName!)!; - if (otherFileText !== existingFile.text) { - addFilePreprocessingFileExplainingDiagnostic( - existingFile, - reason, - Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, - [typeReferenceDirective, resolvedTypeReferenceDirective.resolvedFileName, previousResolution.resolvedFileName] - ); - } + const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(resolvedRef.commandLine, !host.useCaseSensitiveFileNames())); + forEach(resolvedRef.commandLine.fileNames, fileName => { + if (!isDeclarationFileName(fileName) && !fileExtensionIs(fileName, Extension.Json)) { + const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory); + mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName); } - // don't overwrite previous resolution result - saveResolution = false; - } - else { - // First resolution of this library - processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, reason); - } + }); } + }); + } + return mapFromToProjectReferenceRedirectSource.get(path); + } - if (resolvedTypeReferenceDirective.isExternalLibraryImport) currentNodeModulesDepth--; - } - else { - addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, Diagnostics.Cannot_find_type_definition_file_for_0, [typeReferenceDirective]); - } + function isSourceOfProjectReferenceRedirect(fileName: string) { + return useSourceOfProjectReferenceRedirect && !!getResolvedProjectReferenceToRedirect(fileName); + } - if (saveResolution) { - resolvedTypeReferenceDirectives.set(typeReferenceDirective, mode, resolvedTypeReferenceDirective); - } + function getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined { + if (!projectReferenceRedirects) { + return undefined; } - function pathForLibFile(libFileName: string): string { - // Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and - // lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable - // lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown - const components = libFileName.split("."); - let path = components[1]; - let i = 2; - while (components[i] && components[i] !== "d") { - path += (i === 2 ? "/" : "-") + components[i]; - i++; - } - const resolveFrom = combinePaths(currentDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`); - const localOverrideModuleResult = resolveModuleName("@typescript/lib-" + path, resolveFrom, { moduleResolution: ModuleResolutionKind.NodeJs }, host, moduleResolutionCache); - if (localOverrideModuleResult?.resolvedModule) { - return localOverrideModuleResult.resolvedModule.resolvedFileName; - } - return combinePaths(defaultLibraryPath, libFileName); - } + return projectReferenceRedirects.get(projectReferencePath) || undefined; + } - function processLibReferenceDirectives(file: SourceFile) { - forEach(file.libReferenceDirectives, (libReference, index) => { - const libName = toFileNameLowerCase(libReference.fileName); - const libFileName = libMap.get(libName); - if (libFileName) { - // we ignore any 'no-default-lib' reference set on this file. - processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }); - } - else { - const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts"); - const suggestion = getSpellingSuggestion(unqualifiedLibName, libs, identity); - const diagnostic = suggestion ? Diagnostics.Cannot_find_lib_definition_for_0_Did_you_mean_1 : Diagnostics.Cannot_find_lib_definition_for_0; - (fileProcessingDiagnostics ||= []).push({ - kind: FilePreprocessingDiagnosticsKind.FilePreprocessingReferencedDiagnostic, - reason: { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }, - diagnostic, - args: [libName, suggestion] - }); - } - }); - } + function processReferencedFiles(file: SourceFile, isDefaultLib: boolean) { + forEach(file.referencedFiles, (ref, index) => { + processSourceFile( + resolveTripleslashReference(ref.fileName, file.fileName), + isDefaultLib, + /*ignoreNoDefaultLib*/ false, + /*packageId*/ undefined, + { kind: FileIncludeKind.ReferenceFile, file: file.path, index, } + ); + }); + } - function getCanonicalFileName(fileName: string): string { - return host.getCanonicalFileName(fileName); + function processTypeReferenceDirectives(file: SourceFile) { + const typeDirectives = file.typeReferenceDirectives; + if (!typeDirectives) { + return; } - function processImportedModules(file: SourceFile) { - collectExternalModuleReferences(file); - if (file.imports.length || file.moduleAugmentations.length) { - // Because global augmentation doesn't have string literal name, we can check for global augmentation as such. - const moduleNames = getModuleNames(file); - const resolutions = resolveModuleNamesReusingOldState(moduleNames, file); - Debug.assert(resolutions.length === moduleNames.length); - const optionsForFile = (useSourceOfProjectReferenceRedirect ? getRedirectReferenceForResolution(file)?.commandLine.options : undefined) || options; - for (let index = 0; index < moduleNames.length; index++) { - const resolution = resolutions[index]; - setResolvedModule(file, moduleNames[index].text, resolution, getModeForUsageLocation(file, moduleNames[index])); - - if (!resolution) { - continue; - } + const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file); + for (let index = 0; index < typeDirectives.length; index++) { + const ref = file.typeReferenceDirectives[index]; + const resolvedTypeReferenceDirective = resolutions[index]; + // store resolved type directive on the file + const fileName = toFileNameLowerCase(ref.fileName); + setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective); + const mode = ref.resolutionMode || file.impliedNodeFormat; + if (mode && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) { + programDiagnostics.add(createDiagnosticForRange(file, ref, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext)); + } + processTypeReferenceDirective(fileName, mode, resolvedTypeReferenceDirective, { kind: FileIncludeKind.TypeReferenceDirective, file: file.path, index, }); + } + } - const isFromNodeModulesSearch = resolution.isExternalLibraryImport; - const isJsFile = !resolutionExtensionIsTSOrJson(resolution.extension); - const isJsFileFromNodeModules = isFromNodeModulesSearch && isJsFile; - const resolvedFileName = resolution.resolvedFileName; + function processTypeReferenceDirective( + typeReferenceDirective: string, + mode: SourceFile["impliedNodeFormat"] | undefined, + resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined, + reason: FileIncludeReason + ): void { + tracing?.push(tracing.Phase.Program, "processTypeReferenceDirective", { directive: typeReferenceDirective, hasResolved: !!resolvedTypeReferenceDirective, refKind: reason.kind, refPath: isReferencedFile(reason) ? reason.file : undefined }); + processTypeReferenceDirectiveWorker(typeReferenceDirective, mode, resolvedTypeReferenceDirective, reason); + tracing?.pop(); + } - if (isFromNodeModulesSearch) { - currentNodeModulesDepth++; - } + function processTypeReferenceDirectiveWorker( + typeReferenceDirective: string, + mode: SourceFile["impliedNodeFormat"] | undefined, + resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined, + reason: FileIncludeReason + ): void { - // add file to program only if: - // - resolution was successful - // - noResolve is falsy - // - module name comes from the list of imports - // - it's not a top level JavaScript module that exceeded the search max - const elideImport = isJsFileFromNodeModules && currentNodeModulesDepth > maxNodeModuleJsDepth; - // Don't add the file if it has a bad extension (e.g. 'tsx' if we don't have '--allowJs') - // This may still end up being an untyped module -- the file won't be included but imports will be allowed. - const shouldAddFile = resolvedFileName - && !getResolutionDiagnostic(optionsForFile, resolution) - && !optionsForFile.noResolve - && index < file.imports.length - && !elideImport - && !(isJsFile && !getAllowJSCompilerOption(optionsForFile)) - && (isInJSFile(file.imports[index]) || !(file.imports[index].flags & NodeFlags.JSDoc)); - - if (elideImport) { - modulesWithElidedImports.set(file.path, true); - } - else if (shouldAddFile) { - findSourceFile( - resolvedFileName, - /*isDefaultLib*/ false, - /*ignoreNoDefaultLib*/ false, - { kind: FileIncludeKind.Import, file: file.path, index, }, - resolution.packageId, - ); - } + // If we already found this library as a primary reference - nothing to do + const previousResolution = resolvedTypeReferenceDirectives.get(typeReferenceDirective, mode); + if (previousResolution && previousResolution.primary) { + return; + } + let saveResolution = true; + if (resolvedTypeReferenceDirective) { + if (resolvedTypeReferenceDirective.isExternalLibraryImport) currentNodeModulesDepth++; - if (isFromNodeModulesSearch) { - currentNodeModulesDepth--; - } - } + if (resolvedTypeReferenceDirective.primary) { + // resolved from the primary path + processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, reason); // TODO: GH#18217 } else { - // no imports - drop cached module resolutions - file.resolvedModules = undefined; - } - } - - function checkSourceFilesBelongToPath(sourceFiles: readonly SourceFile[], rootDirectory: string): boolean { - let allFilesBelongToPath = true; - const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory)); - for (const sourceFile of sourceFiles) { - if (!sourceFile.isDeclarationFile) { - const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory)); - if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) { - addProgramDiagnosticExplainingFile( - sourceFile, - Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, - [sourceFile.fileName, rootDirectory] - ); - allFilesBelongToPath = false; + // If we already resolved to this file, it must have been a secondary reference. Check file contents + // for sameness and possibly issue an error + if (previousResolution) { + // Don't bother reading the file again if it's the same file. + if (resolvedTypeReferenceDirective.resolvedFileName !== previousResolution.resolvedFileName) { + const otherFileText = host.readFile(resolvedTypeReferenceDirective.resolvedFileName!); + const existingFile = getSourceFile(previousResolution.resolvedFileName!)!; + if (otherFileText !== existingFile.text) { + addFilePreprocessingFileExplainingDiagnostic( + existingFile, + reason, + Diagnostics.Conflicting_definitions_for_0_found_at_1_and_2_Consider_installing_a_specific_version_of_this_library_to_resolve_the_conflict, + [typeReferenceDirective, resolvedTypeReferenceDirective.resolvedFileName, previousResolution.resolvedFileName] + ); + } } + // don't overwrite previous resolution result + saveResolution = false; + } + else { + // First resolution of this library + processSourceFile(resolvedTypeReferenceDirective.resolvedFileName!, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, reason); } } - return allFilesBelongToPath; + if (resolvedTypeReferenceDirective.isExternalLibraryImport) currentNodeModulesDepth--; + } + else { + addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, Diagnostics.Cannot_find_type_definition_file_for_0, [typeReferenceDirective]); } - function parseProjectReferenceConfigFile(ref: ProjectReference): ResolvedProjectReference | undefined { - if (!projectReferenceRedirects) { - projectReferenceRedirects = new Map(); - } + if (saveResolution) { + resolvedTypeReferenceDirectives.set(typeReferenceDirective, mode, resolvedTypeReferenceDirective); + } + } - // The actual filename (i.e. add "/tsconfig.json" if necessary) - const refPath = resolveProjectReferencePath(ref); - const sourceFilePath = toPath(refPath); - const fromCache = projectReferenceRedirects.get(sourceFilePath); - if (fromCache !== undefined) { - return fromCache || undefined; - } + function pathForLibFile(libFileName: string): string { + // Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and + // lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable + // lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown + const components = libFileName.split("."); + let path = components[1]; + let i = 2; + while (components[i] && components[i] !== "d") { + path += (i === 2 ? "/" : "-") + components[i]; + i++; + } + const resolveFrom = combinePaths(currentDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`); + const localOverrideModuleResult = resolveModuleName("@typescript/lib-" + path, resolveFrom, { moduleResolution: ModuleResolutionKind.NodeJs }, host, moduleResolutionCache); + if (localOverrideModuleResult?.resolvedModule) { + return localOverrideModuleResult.resolvedModule.resolvedFileName; + } + return combinePaths(defaultLibraryPath, libFileName); + } - let commandLine: ParsedCommandLine | undefined; - let sourceFile: JsonSourceFile | undefined; - if (host.getParsedCommandLine) { - commandLine = host.getParsedCommandLine(refPath); - if (!commandLine) { - addFileToFilesByName(/*sourceFile*/ undefined, sourceFilePath, /*redirectedPath*/ undefined); - projectReferenceRedirects.set(sourceFilePath, false); - return undefined; - } - sourceFile = Debug.checkDefined(commandLine.options.configFile); - Debug.assert(!sourceFile.path || sourceFile.path === sourceFilePath); - addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); + function processLibReferenceDirectives(file: SourceFile) { + forEach(file.libReferenceDirectives, (libReference, index) => { + const libName = toFileNameLowerCase(libReference.fileName); + const libFileName = libMap.get(libName); + if (libFileName) { + // we ignore any 'no-default-lib' reference set on this file. + processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }); } else { - // An absolute path pointing to the containing directory of the config file - const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory()); - sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined; - addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); - if (sourceFile === undefined) { - projectReferenceRedirects.set(sourceFilePath, false); - return undefined; + const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts"); + const suggestion = getSpellingSuggestion(unqualifiedLibName, libs, identity); + const diagnostic = suggestion ? Diagnostics.Cannot_find_lib_definition_for_0_Did_you_mean_1 : Diagnostics.Cannot_find_lib_definition_for_0; + (fileProcessingDiagnostics ||= []).push({ + kind: FilePreprocessingDiagnosticsKind.FilePreprocessingReferencedDiagnostic, + reason: { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }, + diagnostic, + args: [libName, suggestion] + }); + } + }); + } + + function getCanonicalFileName(fileName: string): string { + return host.getCanonicalFileName(fileName); + } + + function processImportedModules(file: SourceFile) { + collectExternalModuleReferences(file); + if (file.imports.length || file.moduleAugmentations.length) { + // Because global augmentation doesn't have string literal name, we can check for global augmentation as such. + const moduleNames = getModuleNames(file); + const resolutions = resolveModuleNamesReusingOldState(moduleNames, file); + Debug.assert(resolutions.length === moduleNames.length); + const optionsForFile = (useSourceOfProjectReferenceRedirect ? getRedirectReferenceForResolution(file)?.commandLine.options : undefined) || options; + for (let index = 0; index < moduleNames.length; index++) { + const resolution = resolutions[index]; + setResolvedModule(file, moduleNames[index].text, resolution, getModeForUsageLocation(file, moduleNames[index])); + + if (!resolution) { + continue; + } + + const isFromNodeModulesSearch = resolution.isExternalLibraryImport; + const isJsFile = !resolutionExtensionIsTSOrJson(resolution.extension); + const isJsFileFromNodeModules = isFromNodeModulesSearch && isJsFile; + const resolvedFileName = resolution.resolvedFileName; + + if (isFromNodeModulesSearch) { + currentNodeModulesDepth++; + } + + // add file to program only if: + // - resolution was successful + // - noResolve is falsy + // - module name comes from the list of imports + // - it's not a top level JavaScript module that exceeded the search max + const elideImport = isJsFileFromNodeModules && currentNodeModulesDepth > maxNodeModuleJsDepth; + // Don't add the file if it has a bad extension (e.g. 'tsx' if we don't have '--allowJs') + // This may still end up being an untyped module -- the file won't be included but imports will be allowed. + const shouldAddFile = resolvedFileName + && !getResolutionDiagnostic(optionsForFile, resolution) + && !optionsForFile.noResolve + && index < file.imports.length + && !elideImport + && !(isJsFile && !getAllowJSCompilerOption(optionsForFile)) + && (isInJSFile(file.imports[index]) || !(file.imports[index].flags & NodeFlags.JSDoc)); + + if (elideImport) { + modulesWithElidedImports.set(file.path, true); + } + else if (shouldAddFile) { + findSourceFile( + resolvedFileName, + /*isDefaultLib*/ false, + /*ignoreNoDefaultLib*/ false, + { kind: FileIncludeKind.Import, file: file.path, index, }, + resolution.packageId, + ); + } + + if (isFromNodeModulesSearch) { + currentNodeModulesDepth--; } - commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath); } - sourceFile.fileName = refPath; - sourceFile.path = sourceFilePath; - sourceFile.resolvedPath = sourceFilePath; - sourceFile.originalFileName = refPath; + } + else { + // no imports - drop cached module resolutions + file.resolvedModules = undefined; + } + } - const resolvedRef: ResolvedProjectReference = { commandLine, sourceFile }; - projectReferenceRedirects.set(sourceFilePath, resolvedRef); - if (commandLine.projectReferences) { - resolvedRef.references = commandLine.projectReferences.map(parseProjectReferenceConfigFile); + function checkSourceFilesBelongToPath(sourceFiles: readonly SourceFile[], rootDirectory: string): boolean { + let allFilesBelongToPath = true; + const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory)); + for (const sourceFile of sourceFiles) { + if (!sourceFile.isDeclarationFile) { + const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory)); + if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) { + addProgramDiagnosticExplainingFile( + sourceFile, + Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, + [sourceFile.fileName, rootDirectory] + ); + allFilesBelongToPath = false; + } } - return resolvedRef; } - function verifyCompilerOptions() { - if (options.strictPropertyInitialization && !getStrictOptionValue(options, "strictNullChecks")) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "strictPropertyInitialization", "strictNullChecks"); + return allFilesBelongToPath; + } + + function parseProjectReferenceConfigFile(ref: ProjectReference): ResolvedProjectReference | undefined { + if (!projectReferenceRedirects) { + projectReferenceRedirects = new Map(); + } + + // The actual filename (i.e. add "/tsconfig.json" if necessary) + const refPath = resolveProjectReferencePath(ref); + const sourceFilePath = toPath(refPath); + const fromCache = projectReferenceRedirects.get(sourceFilePath); + if (fromCache !== undefined) { + return fromCache || undefined; + } + + let commandLine: ParsedCommandLine | undefined; + let sourceFile: JsonSourceFile | undefined; + if (host.getParsedCommandLine) { + commandLine = host.getParsedCommandLine(refPath); + if (!commandLine) { + addFileToFilesByName(/*sourceFile*/ undefined, sourceFilePath, /*redirectedPath*/ undefined); + projectReferenceRedirects.set(sourceFilePath, false); + return undefined; } - if (options.exactOptionalPropertyTypes && !getStrictOptionValue(options, "strictNullChecks")) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "exactOptionalPropertyTypes", "strictNullChecks"); + sourceFile = Debug.checkDefined(commandLine.options.configFile); + Debug.assert(!sourceFile.path || sourceFile.path === sourceFilePath); + addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); + } + else { + // An absolute path pointing to the containing directory of the config file + const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory()); + sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined; + addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined); + if (sourceFile === undefined) { + projectReferenceRedirects.set(sourceFilePath, false); + return undefined; } + commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath); + } + sourceFile.fileName = refPath; + sourceFile.path = sourceFilePath; + sourceFile.resolvedPath = sourceFilePath; + sourceFile.originalFileName = refPath; - if (options.isolatedModules) { - if (options.out) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "isolatedModules"); - } + const resolvedRef: ResolvedProjectReference = { commandLine, sourceFile }; + projectReferenceRedirects.set(sourceFilePath, resolvedRef); + if (commandLine.projectReferences) { + resolvedRef.references = commandLine.projectReferences.map(parseProjectReferenceConfigFile); + } + return resolvedRef; + } - if (options.outFile) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "outFile", "isolatedModules"); - } + function verifyCompilerOptions() { + if (options.strictPropertyInitialization && !getStrictOptionValue(options, "strictNullChecks")) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "strictPropertyInitialization", "strictNullChecks"); + } + if (options.exactOptionalPropertyTypes && !getStrictOptionValue(options, "strictNullChecks")) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "exactOptionalPropertyTypes", "strictNullChecks"); + } + + if (options.isolatedModules) { + if (options.out) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "isolatedModules"); } - if (options.inlineSourceMap) { - if (options.sourceMap) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "sourceMap", "inlineSourceMap"); - } - if (options.mapRoot) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "mapRoot", "inlineSourceMap"); - } + if (options.outFile) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "outFile", "isolatedModules"); } + } - if (options.composite) { - if (options.declaration === false) { - createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_declaration_emit, "declaration"); - } - if (options.incremental === false) { - createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_incremental_compilation, "declaration"); - } + if (options.inlineSourceMap) { + if (options.sourceMap) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "sourceMap", "inlineSourceMap"); + } + if (options.mapRoot) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "mapRoot", "inlineSourceMap"); } + } - const outputFile = outFile(options); - if (options.tsBuildInfoFile) { - if (!isIncrementalCompilation(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "tsBuildInfoFile", "incremental", "composite"); - } + if (options.composite) { + if (options.declaration === false) { + createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_declaration_emit, "declaration"); } - else if (options.incremental && !outputFile && !options.configFilePath) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified)); + if (options.incremental === false) { + createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_incremental_compilation, "declaration"); } + } + + const outputFile = outFile(options); + if (options.tsBuildInfoFile) { + if (!isIncrementalCompilation(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "tsBuildInfoFile", "incremental", "composite"); + } + } + else if (options.incremental && !outputFile && !options.configFilePath) { + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified)); + } - verifyProjectReferences(); + verifyProjectReferences(); - // List of collected files is complete; validate exhautiveness if this is a project with a file list - if (options.composite) { - const rootPaths = new Set(rootNames.map(toPath)); - for (const file of files) { - // Ignore file that is not emitted - if (sourceFileMayBeEmitted(file, program) && !rootPaths.has(file.path)) { - addProgramDiagnosticExplainingFile( - file, - 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 || ""] - ); - } + // List of collected files is complete; validate exhautiveness if this is a project with a file list + if (options.composite) { + const rootPaths = new Set(rootNames.map(toPath)); + for (const file of files) { + // Ignore file that is not emitted + if (sourceFileMayBeEmitted(file, program) && !rootPaths.has(file.path)) { + addProgramDiagnosticExplainingFile( + file, + 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 || ""] + ); } } + } - if (options.paths) { - for (const key in options.paths) { - if (!hasProperty(options.paths, key)) { - continue; - } - if (!hasZeroOrOneAsteriskCharacter(key)) { - createDiagnosticForOptionPaths(/*onKey*/ true, key, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key); + if (options.paths) { + for (const key in options.paths) { + if (!hasProperty(options.paths, key)) { + continue; + } + if (!hasZeroOrOneAsteriskCharacter(key)) { + createDiagnosticForOptionPaths(/*onKey*/ true, key, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key); + } + if (isArray(options.paths[key])) { + const len = options.paths[key].length; + if (len === 0) { + createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_shouldn_t_be_an_empty_array, key); } - if (isArray(options.paths[key])) { - const len = options.paths[key].length; - if (len === 0) { - createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_shouldn_t_be_an_empty_array, key); - } - for (let i = 0; i < len; i++) { - const subst = options.paths[key][i]; - const typeOfSubst = typeof subst; - if (typeOfSubst === "string") { - if (!hasZeroOrOneAsteriskCharacter(subst)) { - createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_in_pattern_1_can_have_at_most_one_Asterisk_character, subst, key); - } - if (!options.baseUrl && !pathIsRelative(subst) && !pathIsAbsolute(subst)) { - createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Non_relative_paths_are_not_allowed_when_baseUrl_is_not_set_Did_you_forget_a_leading_Slash); - } + for (let i = 0; i < len; i++) { + const subst = options.paths[key][i]; + const typeOfSubst = typeof subst; + if (typeOfSubst === "string") { + if (!hasZeroOrOneAsteriskCharacter(subst)) { + createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_in_pattern_1_can_have_at_most_one_Asterisk_character, subst, key); } - else { - createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_for_pattern_1_has_incorrect_type_expected_string_got_2, subst, key, typeOfSubst); + if (!options.baseUrl && !pathIsRelative(subst) && !pathIsAbsolute(subst)) { + createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Non_relative_paths_are_not_allowed_when_baseUrl_is_not_set_Did_you_forget_a_leading_Slash); } } + else { + createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_for_pattern_1_has_incorrect_type_expected_string_got_2, subst, key, typeOfSubst); + } } - else { - createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_should_be_an_array, key); - } - } - } - - if (!options.sourceMap && !options.inlineSourceMap) { - if (options.inlineSources) { - createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "inlineSources"); } - if (options.sourceRoot) { - createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "sourceRoot"); + else { + createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_should_be_an_array, key); } } + } - if (options.out && options.outFile) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile"); + if (!options.sourceMap && !options.inlineSourceMap) { + if (options.inlineSources) { + createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "inlineSources"); } - - if (options.mapRoot && !(options.sourceMap || options.declarationMap)) { - // Error to specify --mapRoot without --sourcemap - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "mapRoot", "sourceMap", "declarationMap"); + if (options.sourceRoot) { + createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "sourceRoot"); } + } - if (options.declarationDir) { - if (!getEmitDeclarations(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationDir", "declaration", "composite"); - } - if (outputFile) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declarationDir", options.out ? "out" : "outFile"); - } - } + if (options.out && options.outFile) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile"); + } - if (options.declarationMap && !getEmitDeclarations(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationMap", "declaration", "composite"); - } + if (options.mapRoot && !(options.sourceMap || options.declarationMap)) { + // Error to specify --mapRoot without --sourcemap + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "mapRoot", "sourceMap", "declarationMap"); + } - if (options.lib && options.noLib) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib"); + if (options.declarationDir) { + if (!getEmitDeclarations(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationDir", "declaration", "composite"); } - - if (options.noImplicitUseStrict && getStrictOptionValue(options, "alwaysStrict")) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict"); + if (outputFile) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declarationDir", options.out ? "out" : "outFile"); } + } - const languageVersion = getEmitScriptTarget(options); + if (options.declarationMap && !getEmitDeclarations(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationMap", "declaration", "composite"); + } - const firstNonAmbientExternalModuleSourceFile = find(files, f => isExternalModule(f) && !f.isDeclarationFile); - if (options.isolatedModules) { - if (options.module === ModuleKind.None && languageVersion < ScriptTarget.ES2015) { - createDiagnosticForOptionName(Diagnostics.Option_isolatedModules_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES2015_or_higher, "isolatedModules", "target"); - } + if (options.lib && options.noLib) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib"); + } - if (options.preserveConstEnums === false) { - createDiagnosticForOptionName(Diagnostics.Option_preserveConstEnums_cannot_be_disabled_when_isolatedModules_is_enabled, "preserveConstEnums", "isolatedModules"); - } + if (options.noImplicitUseStrict && getStrictOptionValue(options, "alwaysStrict")) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict"); + } - for (const file of files) { - if (!isExternalModule(file) && !isSourceFileJS(file) && !file.isDeclarationFile && file.scriptKind !== ScriptKind.JSON) { - const span = getErrorSpanForNode(file, file); - programDiagnostics.add(createFileDiagnostic(file, span.start, span.length, - Diagnostics._0_cannot_be_compiled_under_isolatedModules_because_it_is_considered_a_global_script_file_Add_an_import_export_or_an_empty_export_statement_to_make_it_a_module, getBaseFileName(file.fileName))); - } + const languageVersion = getEmitScriptTarget(options); + + const firstNonAmbientExternalModuleSourceFile = find(files, f => isExternalModule(f) && !f.isDeclarationFile); + if (options.isolatedModules) { + if (options.module === ModuleKind.None && languageVersion < ScriptTarget.ES2015) { + createDiagnosticForOptionName(Diagnostics.Option_isolatedModules_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES2015_or_higher, "isolatedModules", "target"); + } + + if (options.preserveConstEnums === false) { + createDiagnosticForOptionName(Diagnostics.Option_preserveConstEnums_cannot_be_disabled_when_isolatedModules_is_enabled, "preserveConstEnums", "isolatedModules"); + } + + for (const file of files) { + if (!isExternalModule(file) && !isSourceFileJS(file) && !file.isDeclarationFile && file.scriptKind !== ScriptKind.JSON) { + const span = getErrorSpanForNode(file, file); + programDiagnostics.add(createFileDiagnostic(file, span.start, span.length, + Diagnostics._0_cannot_be_compiled_under_isolatedModules_because_it_is_considered_a_global_script_file_Add_an_import_export_or_an_empty_export_statement_to_make_it_a_module, getBaseFileName(file.fileName))); } } - else if (firstNonAmbientExternalModuleSourceFile && languageVersion < ScriptTarget.ES2015 && options.module === ModuleKind.None) { - // We cannot use createDiagnosticFromNode because nodes do not have parents yet + } + else if (firstNonAmbientExternalModuleSourceFile && languageVersion < ScriptTarget.ES2015 && options.module === ModuleKind.None) { + // We cannot use createDiagnosticFromNode because nodes do not have parents yet + const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, typeof firstNonAmbientExternalModuleSourceFile.externalModuleIndicator === "boolean" ? firstNonAmbientExternalModuleSourceFile : firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!); + programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none)); + } + + // Cannot specify module gen that isn't amd or system with --out + if (outputFile && !options.emitDeclarationOnly) { + if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) { + createDiagnosticForOptionName(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile", "module"); + } + else if (options.module === undefined && firstNonAmbientExternalModuleSourceFile) { const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, typeof firstNonAmbientExternalModuleSourceFile.externalModuleIndicator === "boolean" ? firstNonAmbientExternalModuleSourceFile : firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!); - programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none)); + programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_modules_using_option_0_unless_the_module_flag_is_amd_or_system, options.out ? "out" : "outFile")); } + } - // Cannot specify module gen that isn't amd or system with --out - if (outputFile && !options.emitDeclarationOnly) { - if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) { - createDiagnosticForOptionName(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile", "module"); - } - else if (options.module === undefined && firstNonAmbientExternalModuleSourceFile) { - const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, typeof firstNonAmbientExternalModuleSourceFile.externalModuleIndicator === "boolean" ? firstNonAmbientExternalModuleSourceFile : firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!); - programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_modules_using_option_0_unless_the_module_flag_is_amd_or_system, options.out ? "out" : "outFile")); - } + if (options.resolveJsonModule) { + if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && + getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node16 && + getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) { + createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_cannot_be_specified_without_node_module_resolution_strategy, "resolveJsonModule"); } - - if (options.resolveJsonModule) { - if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && - getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node16 && - getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) { - createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_cannot_be_specified_without_node_module_resolution_strategy, "resolveJsonModule"); - } - // Any emit other than common js, amd, es2015 or esnext is error - else if (!hasJsonModuleEmitEnabled(options)) { - createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_can_only_be_specified_when_module_code_generation_is_commonjs_amd_es2015_or_esNext, "resolveJsonModule", "module"); - } + // Any emit other than common js, amd, es2015 or esnext is error + else if (!hasJsonModuleEmitEnabled(options)) { + createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_can_only_be_specified_when_module_code_generation_is_commonjs_amd_es2015_or_esNext, "resolveJsonModule", "module"); } + } - // there has to be common source directory if user specified --outdir || --rootDir || --sourceRoot - // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted - if (options.outDir || // there is --outDir specified - options.rootDir || // there is --rootDir specified - options.sourceRoot || // there is --sourceRoot specified - options.mapRoot) { // there is --mapRoot specified - - // Precalculate and cache the common source directory - const dir = getCommonSourceDirectory(); + // there has to be common source directory if user specified --outdir || --rootDir || --sourceRoot + // if user specified --mapRoot, there needs to be common source directory if there would be multiple files being emitted + if (options.outDir || // there is --outDir specified + options.rootDir || // there is --rootDir specified + options.sourceRoot || // there is --sourceRoot specified + options.mapRoot) { // there is --mapRoot specified - // If we failed to find a good common directory, but outDir is specified and at least one of our files is on a windows drive/URL/other resource, add a failure - if (options.outDir && dir === "" && files.some(file => getRootLength(file.fileName) > 1)) { - createDiagnosticForOptionName(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files, "outDir"); - } - } + // Precalculate and cache the common source directory + const dir = getCommonSourceDirectory(); - if (options.useDefineForClassFields && languageVersion === ScriptTarget.ES3) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_target_is_ES3, "useDefineForClassFields"); + // If we failed to find a good common directory, but outDir is specified and at least one of our files is on a windows drive/URL/other resource, add a failure + if (options.outDir && dir === "" && files.some(file => getRootLength(file.fileName) > 1)) { + createDiagnosticForOptionName(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files, "outDir"); } + } - if (options.checkJs && !getAllowJSCompilerOption(options)) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "checkJs", "allowJs")); - } + if (options.useDefineForClassFields && languageVersion === ScriptTarget.ES3) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_target_is_ES3, "useDefineForClassFields"); + } - if (options.emitDeclarationOnly) { - if (!getEmitDeclarations(options)) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "emitDeclarationOnly", "declaration", "composite"); - } + if (options.checkJs && !getAllowJSCompilerOption(options)) { + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "checkJs", "allowJs")); + } - if (options.noEmit) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "emitDeclarationOnly", "noEmit"); - } + if (options.emitDeclarationOnly) { + if (!getEmitDeclarations(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "emitDeclarationOnly", "declaration", "composite"); } - if (options.emitDecoratorMetadata && - !options.experimentalDecorators) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators"); + if (options.noEmit) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "emitDeclarationOnly", "noEmit"); } + } - if (options.jsxFactory) { - if (options.reactNamespace) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory"); - } - if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFactory", inverseJsxOptionMap.get("" + options.jsx)); - } - if (!parseIsolatedEntityName(options.jsxFactory, languageVersion)) { - createOptionValueDiagnostic("jsxFactory", Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory); - } + if (options.emitDecoratorMetadata && + !options.experimentalDecorators) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators"); + } + + if (options.jsxFactory) { + if (options.reactNamespace) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory"); } - else if (options.reactNamespace && !isIdentifierText(options.reactNamespace, languageVersion)) { - createOptionValueDiagnostic("reactNamespace", Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace); + if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFactory", inverseJsxOptionMap.get("" + options.jsx)); } - - if (options.jsxFragmentFactory) { - if (!options.jsxFactory) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "jsxFragmentFactory", "jsxFactory"); - } - if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFragmentFactory", inverseJsxOptionMap.get("" + options.jsx)); - } - if (!parseIsolatedEntityName(options.jsxFragmentFactory, languageVersion)) { - createOptionValueDiagnostic("jsxFragmentFactory", Diagnostics.Invalid_value_for_jsxFragmentFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFragmentFactory); - } + if (!parseIsolatedEntityName(options.jsxFactory, languageVersion)) { + createOptionValueDiagnostic("jsxFactory", Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory); } + } + else if (options.reactNamespace && !isIdentifierText(options.reactNamespace, languageVersion)) { + createOptionValueDiagnostic("reactNamespace", Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace); + } - if (options.reactNamespace) { - if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "reactNamespace", inverseJsxOptionMap.get("" + options.jsx)); - } + if (options.jsxFragmentFactory) { + if (!options.jsxFactory) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "jsxFragmentFactory", "jsxFactory"); } - - if (options.jsxImportSource) { - if (options.jsx === JsxEmit.React) { - createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxImportSource", inverseJsxOptionMap.get("" + options.jsx)); - } + if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxFragmentFactory", inverseJsxOptionMap.get("" + options.jsx)); + } + if (!parseIsolatedEntityName(options.jsxFragmentFactory, languageVersion)) { + createOptionValueDiagnostic("jsxFragmentFactory", Diagnostics.Invalid_value_for_jsxFragmentFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFragmentFactory); } + } - if (options.preserveValueImports && getEmitModuleKind(options) < ModuleKind.ES2015) { - createOptionValueDiagnostic("importsNotUsedAsValues", Diagnostics.Option_preserveValueImports_can_only_be_used_when_module_is_set_to_es2015_or_later); + if (options.reactNamespace) { + if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "reactNamespace", inverseJsxOptionMap.get("" + options.jsx)); } + } - // If the emit is enabled make sure that every output file is unique and not overwriting any of the input files - if (!options.noEmit && !options.suppressOutputPathCheck) { - const emitHost = getEmitHost(); - const emitFilesSeen = new Set(); - forEachEmittedFile(emitHost, (emitFileNames) => { - if (!options.emitDeclarationOnly) { - verifyEmitFilePath(emitFileNames.jsFilePath, emitFilesSeen); - } - verifyEmitFilePath(emitFileNames.declarationFilePath, emitFilesSeen); - }); + if (options.jsxImportSource) { + if (options.jsx === JsxEmit.React) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_jsx_is_1, "jsxImportSource", inverseJsxOptionMap.get("" + options.jsx)); } + } - // Verify that all the emit files are unique and don't overwrite input files - function verifyEmitFilePath(emitFileName: string | undefined, emitFilesSeen: Set) { - if (emitFileName) { - const emitFilePath = toPath(emitFileName); - // Report error if the output overwrites input file - if (filesByName.has(emitFilePath)) { - let chain: DiagnosticMessageChain | undefined; - if (!options.configFilePath) { - // The program is from either an inferred project or an external project - chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Adding_a_tsconfig_json_file_will_help_organize_projects_that_contain_both_TypeScript_and_JavaScript_files_Learn_more_at_https_Colon_Slash_Slashaka_ms_Slashtsconfig); - } - chain = chainDiagnosticMessages(chain, Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file, emitFileName); - blockEmittingOfFile(emitFileName, createCompilerDiagnosticFromMessageChain(chain)); - } + if (options.preserveValueImports && getEmitModuleKind(options) < ModuleKind.ES2015) { + createOptionValueDiagnostic("importsNotUsedAsValues", Diagnostics.Option_preserveValueImports_can_only_be_used_when_module_is_set_to_es2015_or_later); + } - const emitFileKey = !host.useCaseSensitiveFileNames() ? toFileNameLowerCase(emitFilePath) : emitFilePath; - // Report error if multiple files write into same file - if (emitFilesSeen.has(emitFileKey)) { - // Already seen the same emit file - report error - blockEmittingOfFile(emitFileName, createCompilerDiagnostic(Diagnostics.Cannot_write_file_0_because_it_would_be_overwritten_by_multiple_input_files, emitFileName)); - } - else { - emitFilesSeen.add(emitFileKey); - } + // If the emit is enabled make sure that every output file is unique and not overwriting any of the input files + if (!options.noEmit && !options.suppressOutputPathCheck) { + const emitHost = getEmitHost(); + const emitFilesSeen = new Set(); + forEachEmittedFile(emitHost, (emitFileNames) => { + if (!options.emitDeclarationOnly) { + verifyEmitFilePath(emitFileNames.jsFilePath, emitFilesSeen); } - } + verifyEmitFilePath(emitFileNames.declarationFilePath, emitFilesSeen); + }); } - function createDiagnosticExplainingFile(file: SourceFile | undefined, fileProcessingReason: FileIncludeReason | undefined, diagnostic: DiagnosticMessage, args: (string | number | undefined)[] | undefined): Diagnostic { - let fileIncludeReasons: DiagnosticMessageChain[] | undefined; - let relatedInfo: Diagnostic[] | undefined; - let locationReason = isReferencedFile(fileProcessingReason) ? fileProcessingReason : undefined; - if (file) fileReasons.get(file.path)?.forEach(processReason); - if (fileProcessingReason) processReason(fileProcessingReason); - // If we have location and there is only one reason file is in which is the location, dont add details for file include - if (locationReason && fileIncludeReasons?.length === 1) fileIncludeReasons = undefined; - const location = locationReason && getReferencedFileLocation(getSourceFileByPath, locationReason); - const fileIncludeReasonDetails = fileIncludeReasons && chainDiagnosticMessages(fileIncludeReasons, Diagnostics.The_file_is_in_the_program_because_Colon); - const redirectInfo = file && explainIfFileIsRedirectAndImpliedFormat(file); - const chain = chainDiagnosticMessages(redirectInfo ? fileIncludeReasonDetails ? [fileIncludeReasonDetails, ...redirectInfo] : redirectInfo : fileIncludeReasonDetails, diagnostic, ...args || emptyArray); - return location && isReferenceFileLocation(location) ? - createFileDiagnosticFromMessageChain(location.file, location.pos, location.end - location.pos, chain, relatedInfo) : - createCompilerDiagnosticFromMessageChain(chain, relatedInfo); - - function processReason(reason: FileIncludeReason) { - (fileIncludeReasons ||= []).push(fileIncludeReasonToDiagnostics(program, reason)); - if (!locationReason && isReferencedFile(reason)) { - // Report error at first reference file or file currently in processing and dont report in related information - locationReason = reason; + // Verify that all the emit files are unique and don't overwrite input files + function verifyEmitFilePath(emitFileName: string | undefined, emitFilesSeen: Set) { + if (emitFileName) { + const emitFilePath = toPath(emitFileName); + // Report error if the output overwrites input file + if (filesByName.has(emitFilePath)) { + let chain: DiagnosticMessageChain | undefined; + if (!options.configFilePath) { + // The program is from either an inferred project or an external project + chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Adding_a_tsconfig_json_file_will_help_organize_projects_that_contain_both_TypeScript_and_JavaScript_files_Learn_more_at_https_Colon_Slash_Slashaka_ms_Slashtsconfig); + } + chain = chainDiagnosticMessages(chain, Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file, emitFileName); + blockEmittingOfFile(emitFileName, createCompilerDiagnosticFromMessageChain(chain)); + } + + const emitFileKey = !host.useCaseSensitiveFileNames() ? toFileNameLowerCase(emitFilePath) : emitFilePath; + // Report error if multiple files write into same file + if (emitFilesSeen.has(emitFileKey)) { + // Already seen the same emit file - report error + blockEmittingOfFile(emitFileName, createCompilerDiagnostic(Diagnostics.Cannot_write_file_0_because_it_would_be_overwritten_by_multiple_input_files, emitFileName)); } - else if (locationReason !== reason) { - relatedInfo = append(relatedInfo, fileIncludeReasonToRelatedInformation(reason)); + else { + emitFilesSeen.add(emitFileKey); } - // Remove fileProcessingReason if its already included in fileReasons of the program - if (reason === fileProcessingReason) fileProcessingReason = undefined; } } + } - function addFilePreprocessingFileExplainingDiagnostic(file: SourceFile | undefined, fileProcessingReason: FileIncludeReason, diagnostic: DiagnosticMessage, args?: (string | number | undefined)[]) { - (fileProcessingDiagnostics ||= []).push({ - kind: FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic, - file: file && file.path, - fileProcessingReason, - diagnostic, - args - }); + function createDiagnosticExplainingFile(file: SourceFile | undefined, fileProcessingReason: FileIncludeReason | undefined, diagnostic: DiagnosticMessage, args: (string | number | undefined)[] | undefined): Diagnostic { + let fileIncludeReasons: DiagnosticMessageChain[] | undefined; + let relatedInfo: Diagnostic[] | undefined; + let locationReason = isReferencedFile(fileProcessingReason) ? fileProcessingReason : undefined; + if (file) fileReasons.get(file.path)?.forEach(processReason); + if (fileProcessingReason) processReason(fileProcessingReason); + // If we have location and there is only one reason file is in which is the location, dont add details for file include + if (locationReason && fileIncludeReasons?.length === 1) fileIncludeReasons = undefined; + const location = locationReason && getReferencedFileLocation(getSourceFileByPath, locationReason); + const fileIncludeReasonDetails = fileIncludeReasons && chainDiagnosticMessages(fileIncludeReasons, Diagnostics.The_file_is_in_the_program_because_Colon); + const redirectInfo = file && explainIfFileIsRedirectAndImpliedFormat(file); + const chain = chainDiagnosticMessages(redirectInfo ? fileIncludeReasonDetails ? [fileIncludeReasonDetails, ...redirectInfo] : redirectInfo : fileIncludeReasonDetails, diagnostic, ...args || emptyArray); + return location && isReferenceFileLocation(location) ? + createFileDiagnosticFromMessageChain(location.file, location.pos, location.end - location.pos, chain, relatedInfo) : + createCompilerDiagnosticFromMessageChain(chain, relatedInfo); + + function processReason(reason: FileIncludeReason) { + (fileIncludeReasons ||= []).push(fileIncludeReasonToDiagnostics(program, reason)); + if (!locationReason && isReferencedFile(reason)) { + // Report error at first reference file or file currently in processing and dont report in related information + locationReason = reason; + } + else if (locationReason !== reason) { + relatedInfo = append(relatedInfo, fileIncludeReasonToRelatedInformation(reason)); + } + // Remove fileProcessingReason if its already included in fileReasons of the program + if (reason === fileProcessingReason) fileProcessingReason = undefined; } + } - function addProgramDiagnosticExplainingFile(file: SourceFile, diagnostic: DiagnosticMessage, args?: (string | number | undefined)[]) { - programDiagnostics.add(createDiagnosticExplainingFile(file, /*fileProcessingReason*/ undefined, diagnostic, args)); - } + function addFilePreprocessingFileExplainingDiagnostic(file: SourceFile | undefined, fileProcessingReason: FileIncludeReason, diagnostic: DiagnosticMessage, args?: (string | number | undefined)[]) { + (fileProcessingDiagnostics ||= []).push({ + kind: FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic, + file: file && file.path, + fileProcessingReason, + diagnostic, + args + }); + } - function fileIncludeReasonToRelatedInformation(reason: FileIncludeReason): DiagnosticWithLocation | undefined { - if (isReferencedFile(reason)) { - const referenceLocation = getReferencedFileLocation(getSourceFileByPath, reason); - let message: DiagnosticMessage; - switch (reason.kind) { - case FileIncludeKind.Import: - message = Diagnostics.File_is_included_via_import_here; - break; - case FileIncludeKind.ReferenceFile: - message = Diagnostics.File_is_included_via_reference_here; - break; - case FileIncludeKind.TypeReferenceDirective: - message = Diagnostics.File_is_included_via_type_library_reference_here; - break; - case FileIncludeKind.LibReferenceDirective: - message = Diagnostics.File_is_included_via_library_reference_here; - break; - default: - Debug.assertNever(reason); - } - return isReferenceFileLocation(referenceLocation) ? createFileDiagnostic( - referenceLocation.file, - referenceLocation.pos, - referenceLocation.end - referenceLocation.pos, - message, - ) : undefined; - } + function addProgramDiagnosticExplainingFile(file: SourceFile, diagnostic: DiagnosticMessage, args?: (string | number | undefined)[]) { + programDiagnostics.add(createDiagnosticExplainingFile(file, /*fileProcessingReason*/ undefined, diagnostic, args)); + } - if (!options.configFile) return undefined; - let configFileNode: Node | undefined; + function fileIncludeReasonToRelatedInformation(reason: FileIncludeReason): DiagnosticWithLocation | undefined { + if (isReferencedFile(reason)) { + const referenceLocation = getReferencedFileLocation(getSourceFileByPath, reason); let message: DiagnosticMessage; switch (reason.kind) { - case FileIncludeKind.RootFile: - if (!options.configFile.configFileSpecs) return undefined; - const fileName = getNormalizedAbsolutePath(rootNames[reason.index], currentDirectory); - const matchedByFiles = getMatchedFileSpec(program, fileName); - if (matchedByFiles) { - configFileNode = getTsConfigPropArrayElementValue(options.configFile, "files", matchedByFiles); - message = Diagnostics.File_is_matched_by_files_list_specified_here; - break; - } - const matchedByInclude = getMatchedIncludeSpec(program, fileName); - // Could be additional files specified as roots - if (!matchedByInclude || !isString(matchedByInclude)) return undefined; - configFileNode = getTsConfigPropArrayElementValue(options.configFile, "include", matchedByInclude); - message = Diagnostics.File_is_matched_by_include_pattern_specified_here; + case FileIncludeKind.Import: + message = Diagnostics.File_is_included_via_import_here; break; - case FileIncludeKind.SourceFromProjectReference: - case FileIncludeKind.OutputFromProjectReference: - const referencedResolvedRef = Debug.checkDefined(resolvedProjectReferences?.[reason.index]); - const referenceInfo = forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, parent, index) => - resolvedRef === referencedResolvedRef ? { sourceFile: parent?.sourceFile || options.configFile!, index } : undefined - ); - if (!referenceInfo) return undefined; - const { sourceFile, index } = referenceInfo; - const referencesSyntax = firstDefined(getTsConfigPropArray(sourceFile as TsConfigSourceFile, "references"), - property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined); - return referencesSyntax && referencesSyntax.elements.length > index ? - createDiagnosticForNodeInSourceFile( - sourceFile, - referencesSyntax.elements[index], - reason.kind === FileIncludeKind.OutputFromProjectReference ? - Diagnostics.File_is_output_from_referenced_project_specified_here : - Diagnostics.File_is_source_from_referenced_project_specified_here, - ) : - undefined; - case FileIncludeKind.AutomaticTypeDirectiveFile: - if (!options.types) return undefined; - configFileNode = getOptionsSyntaxByArrayElementValue("types", reason.typeReference); - message = Diagnostics.File_is_entry_point_of_type_library_specified_here; + case FileIncludeKind.ReferenceFile: + message = Diagnostics.File_is_included_via_reference_here; break; - case FileIncludeKind.LibFile: - if (reason.index !== undefined) { - configFileNode = getOptionsSyntaxByArrayElementValue("lib", options.lib![reason.index]); - message = Diagnostics.File_is_library_specified_here; - break; - } - const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === getEmitScriptTarget(options) ? key : undefined); - configFileNode = target ? getOptionsSyntaxByValue("target", target) : undefined; - message = Diagnostics.File_is_default_library_for_target_specified_here; + case FileIncludeKind.TypeReferenceDirective: + message = Diagnostics.File_is_included_via_type_library_reference_here; + break; + case FileIncludeKind.LibReferenceDirective: + message = Diagnostics.File_is_included_via_library_reference_here; break; default: Debug.assertNever(reason); } - return configFileNode && createDiagnosticForNodeInSourceFile( - options.configFile, - configFileNode, + return isReferenceFileLocation(referenceLocation) ? createFileDiagnostic( + referenceLocation.file, + referenceLocation.pos, + referenceLocation.end - referenceLocation.pos, message, - ); + ) : undefined; + } + + if (!options.configFile) return undefined; + let configFileNode: Node | undefined; + let message: DiagnosticMessage; + switch (reason.kind) { + case FileIncludeKind.RootFile: + if (!options.configFile.configFileSpecs) return undefined; + const fileName = getNormalizedAbsolutePath(rootNames[reason.index], currentDirectory); + const matchedByFiles = getMatchedFileSpec(program, fileName); + if (matchedByFiles) { + configFileNode = getTsConfigPropArrayElementValue(options.configFile, "files", matchedByFiles); + message = Diagnostics.File_is_matched_by_files_list_specified_here; + break; + } + const matchedByInclude = getMatchedIncludeSpec(program, fileName); + // Could be additional files specified as roots + if (!matchedByInclude || !isString(matchedByInclude)) return undefined; + configFileNode = getTsConfigPropArrayElementValue(options.configFile, "include", matchedByInclude); + message = Diagnostics.File_is_matched_by_include_pattern_specified_here; + break; + case FileIncludeKind.SourceFromProjectReference: + case FileIncludeKind.OutputFromProjectReference: + const referencedResolvedRef = Debug.checkDefined(resolvedProjectReferences?.[reason.index]); + const referenceInfo = forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, parent, index) => + resolvedRef === referencedResolvedRef ? { sourceFile: parent?.sourceFile || options.configFile!, index } : undefined + ); + if (!referenceInfo) return undefined; + const { sourceFile, index } = referenceInfo; + const referencesSyntax = firstDefined(getTsConfigPropArray(sourceFile as TsConfigSourceFile, "references"), + property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined); + return referencesSyntax && referencesSyntax.elements.length > index ? + createDiagnosticForNodeInSourceFile( + sourceFile, + referencesSyntax.elements[index], + reason.kind === FileIncludeKind.OutputFromProjectReference ? + Diagnostics.File_is_output_from_referenced_project_specified_here : + Diagnostics.File_is_source_from_referenced_project_specified_here, + ) : + undefined; + case FileIncludeKind.AutomaticTypeDirectiveFile: + if (!options.types) return undefined; + configFileNode = getOptionsSyntaxByArrayElementValue("types", reason.typeReference); + message = Diagnostics.File_is_entry_point_of_type_library_specified_here; + break; + case FileIncludeKind.LibFile: + if (reason.index !== undefined) { + configFileNode = getOptionsSyntaxByArrayElementValue("lib", options.lib![reason.index]); + message = Diagnostics.File_is_library_specified_here; + break; + } + const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === getEmitScriptTarget(options) ? key : undefined); + configFileNode = target ? getOptionsSyntaxByValue("target", target) : undefined; + message = Diagnostics.File_is_default_library_for_target_specified_here; + break; + default: + Debug.assertNever(reason); } + return configFileNode && createDiagnosticForNodeInSourceFile( + options.configFile, + configFileNode, + message, + ); + } - function verifyProjectReferences() { - const buildInfoPath = !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined; - forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, parent, index) => { - const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; - const parentFile = parent && parent.sourceFile as JsonSourceFile; - if (!resolvedRef) { - createDiagnosticForReference(parentFile, index, Diagnostics.File_0_not_found, ref.path); - return; - } - const options = resolvedRef.commandLine.options; - if (!options.composite || options.noEmit) { - // ok to not have composite if the current program is container only - const inputs = parent ? parent.commandLine.fileNames : rootNames; - if (inputs.length) { - if (!options.composite) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.path); - if (options.noEmit) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_may_not_disable_emit, ref.path); - } + function verifyProjectReferences() { + const buildInfoPath = !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined; + forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, parent, index) => { + const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index]; + const parentFile = parent && parent.sourceFile as JsonSourceFile; + if (!resolvedRef) { + createDiagnosticForReference(parentFile, index, Diagnostics.File_0_not_found, ref.path); + return; + } + const options = resolvedRef.commandLine.options; + if (!options.composite || options.noEmit) { + // ok to not have composite if the current program is container only + const inputs = parent ? parent.commandLine.fileNames : rootNames; + if (inputs.length) { + if (!options.composite) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true, ref.path); + if (options.noEmit) createDiagnosticForReference(parentFile, index, Diagnostics.Referenced_project_0_may_not_disable_emit, ref.path); } - if (ref.prepend) { - const out = outFile(options); - if (out) { - if (!host.fileExists(out)) { - createDiagnosticForReference(parentFile, index, Diagnostics.Output_file_0_from_project_1_does_not_exist, out, ref.path); - } - } - else { - createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_prepend_project_0_because_it_does_not_have_outFile_set, ref.path); + } + if (ref.prepend) { + const out = outFile(options); + if (out) { + if (!host.fileExists(out)) { + createDiagnosticForReference(parentFile, index, Diagnostics.Output_file_0_from_project_1_does_not_exist, out, ref.path); } } - if (!parent && buildInfoPath && buildInfoPath === getTsBuildInfoEmitOutputFilePath(options)) { - createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, buildInfoPath, ref.path); - hasEmitBlockingDiagnostics.set(toPath(buildInfoPath), true); + else { + createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_prepend_project_0_because_it_does_not_have_outFile_set, ref.path); } - }); - } + } + if (!parent && buildInfoPath && buildInfoPath === getTsBuildInfoEmitOutputFilePath(options)) { + createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, buildInfoPath, ref.path); + hasEmitBlockingDiagnostics.set(toPath(buildInfoPath), true); + } + }); + } - function createDiagnosticForOptionPathKeyValue(key: string, valueIndex: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number) { - let needCompilerDiagnostic = true; - const pathsSyntax = getOptionPathsSyntax(); - for (const pathProp of pathsSyntax) { - if (isObjectLiteralExpression(pathProp.initializer)) { - for (const keyProps of getPropertyAssignment(pathProp.initializer, key)) { - const initializer = keyProps.initializer; - if (isArrayLiteralExpression(initializer) && initializer.elements.length > valueIndex) { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, initializer.elements[valueIndex], message, arg0, arg1, arg2)); - needCompilerDiagnostic = false; - } + function createDiagnosticForOptionPathKeyValue(key: string, valueIndex: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number) { + let needCompilerDiagnostic = true; + const pathsSyntax = getOptionPathsSyntax(); + for (const pathProp of pathsSyntax) { + if (isObjectLiteralExpression(pathProp.initializer)) { + for (const keyProps of getPropertyAssignment(pathProp.initializer, key)) { + const initializer = keyProps.initializer; + if (isArrayLiteralExpression(initializer) && initializer.elements.length > valueIndex) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, initializer.elements[valueIndex], message, arg0, arg1, arg2)); + needCompilerDiagnostic = false; } } } + } - if (needCompilerDiagnostic) { - programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); - } + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); } + } - function createDiagnosticForOptionPaths(onKey: boolean, key: string, message: DiagnosticMessage, arg0: string | number) { - let needCompilerDiagnostic = true; - const pathsSyntax = getOptionPathsSyntax(); - for (const pathProp of pathsSyntax) { - if (isObjectLiteralExpression(pathProp.initializer) && - createOptionDiagnosticInObjectLiteralSyntax( - pathProp.initializer, onKey, key, /*key2*/ undefined, - message, arg0)) { - needCompilerDiagnostic = false; - } - } - if (needCompilerDiagnostic) { - programDiagnostics.add(createCompilerDiagnostic(message, arg0)); + function createDiagnosticForOptionPaths(onKey: boolean, key: string, message: DiagnosticMessage, arg0: string | number) { + let needCompilerDiagnostic = true; + const pathsSyntax = getOptionPathsSyntax(); + for (const pathProp of pathsSyntax) { + if (isObjectLiteralExpression(pathProp.initializer) && + createOptionDiagnosticInObjectLiteralSyntax( + pathProp.initializer, onKey, key, /*key2*/ undefined, + message, arg0)) { + needCompilerDiagnostic = false; } } - - function getOptionsSyntaxByName(name: string) { - const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); - return compilerOptionsObjectLiteralSyntax && getPropertyAssignment(compilerOptionsObjectLiteralSyntax, name); + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0)); } + } - function getOptionPathsSyntax() { - return getOptionsSyntaxByName("paths") || emptyArray; - } + function getOptionsSyntaxByName(name: string) { + const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); + return compilerOptionsObjectLiteralSyntax && getPropertyAssignment(compilerOptionsObjectLiteralSyntax, name); + } - function getOptionsSyntaxByValue(name: string, value: string) { - const syntaxByName = getOptionsSyntaxByName(name); - return syntaxByName && firstDefined(syntaxByName, property => isStringLiteral(property.initializer) && property.initializer.text === value ? property.initializer : undefined); - } + function getOptionPathsSyntax() { + return getOptionsSyntaxByName("paths") || emptyArray; + } - function getOptionsSyntaxByArrayElementValue(name: string, value: string) { - const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); - return compilerOptionsObjectLiteralSyntax && getPropertyArrayElementValue(compilerOptionsObjectLiteralSyntax, name, value); - } + function getOptionsSyntaxByValue(name: string, value: string) { + const syntaxByName = getOptionsSyntaxByName(name); + return syntaxByName && firstDefined(syntaxByName, property => isStringLiteral(property.initializer) && property.initializer.text === value ? property.initializer : undefined); + } - function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string, option3?: string) { - createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2, option3); - } + function getOptionsSyntaxByArrayElementValue(name: string, value: string) { + const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); + return compilerOptionsObjectLiteralSyntax && getPropertyArrayElementValue(compilerOptionsObjectLiteralSyntax, name, value); + } - function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0?: string, arg1?: string) { - createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0, arg1); - } + function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string, option3?: string) { + createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2, option3); + } - function createDiagnosticForReference(sourceFile: JsonSourceFile | undefined, index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) { - const referencesSyntax = firstDefined(getTsConfigPropArray(sourceFile || options.configFile, "references"), - property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined); - if (referencesSyntax && referencesSyntax.elements.length > index) { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(sourceFile || options.configFile!, referencesSyntax.elements[index], message, arg0, arg1)); - } - else { - programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1)); - } + function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0?: string, arg1?: string) { + createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0, arg1); + } + + function createDiagnosticForReference(sourceFile: JsonSourceFile | undefined, index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) { + const referencesSyntax = firstDefined(getTsConfigPropArray(sourceFile || options.configFile, "references"), + property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined); + if (referencesSyntax && referencesSyntax.elements.length > index) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile(sourceFile || options.configFile!, referencesSyntax.elements[index], message, arg0, arg1)); + } + else { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1)); } + } - function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number) { - const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); - const needCompilerDiagnostic = !compilerOptionsObjectLiteralSyntax || - !createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1, arg2); + function createDiagnosticForOption(onKey: boolean, option1: string, option2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number) { + const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); + const needCompilerDiagnostic = !compilerOptionsObjectLiteralSyntax || + !createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1, arg2); - if (needCompilerDiagnostic) { - programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); - } + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); } + } - function getCompilerOptionsObjectLiteralSyntax() { - if (_compilerOptionsObjectLiteralSyntax === undefined) { - _compilerOptionsObjectLiteralSyntax = false; - const jsonObjectLiteral = getTsConfigObjectLiteralExpression(options.configFile); - if (jsonObjectLiteral) { - for (const prop of getPropertyAssignment(jsonObjectLiteral, "compilerOptions")) { - if (isObjectLiteralExpression(prop.initializer)) { - _compilerOptionsObjectLiteralSyntax = prop.initializer; - break; - } + function getCompilerOptionsObjectLiteralSyntax() { + if (_compilerOptionsObjectLiteralSyntax === undefined) { + _compilerOptionsObjectLiteralSyntax = false; + const jsonObjectLiteral = getTsConfigObjectLiteralExpression(options.configFile); + if (jsonObjectLiteral) { + for (const prop of getPropertyAssignment(jsonObjectLiteral, "compilerOptions")) { + if (isObjectLiteralExpression(prop.initializer)) { + _compilerOptionsObjectLiteralSyntax = prop.initializer; + break; } } } - return _compilerOptionsObjectLiteralSyntax || undefined; - } - - function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): boolean { - const props = getPropertyAssignment(objectLiteral, key1, key2); - for (const prop of props) { - programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, onKey ? prop.name : prop.initializer, message, arg0, arg1, arg2)); - } - return !!props.length; } + return _compilerOptionsObjectLiteralSyntax || undefined; + } - function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) { - hasEmitBlockingDiagnostics.set(toPath(emitFileName), true); - programDiagnostics.add(diag); + function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): boolean { + const props = getPropertyAssignment(objectLiteral, key1, key2); + for (const prop of props) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, onKey ? prop.name : prop.initializer, message, arg0, arg1, arg2)); } + return !!props.length; + } - function isEmittedFile(file: string): boolean { - if (options.noEmit) { - return false; - } - - // If this is source file, its not emitted file - const filePath = toPath(file); - if (getSourceFileByPath(filePath)) { - return false; - } + function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) { + hasEmitBlockingDiagnostics.set(toPath(emitFileName), true); + programDiagnostics.add(diag); + } - // If options have --outFile or --out just check that - const out = outFile(options); - if (out) { - return isSameFile(filePath, out) || isSameFile(filePath, removeFileExtension(out) + Extension.Dts); - } + function isEmittedFile(file: string): boolean { + if (options.noEmit) { + return false; + } - // If declarationDir is specified, return if its a file in that directory - if (options.declarationDir && containsPath(options.declarationDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames())) { - return true; - } + // If this is source file, its not emitted file + const filePath = toPath(file); + if (getSourceFileByPath(filePath)) { + return false; + } - // If --outDir, check if file is in that directory - if (options.outDir) { - return containsPath(options.outDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames()); - } + // If options have --outFile or --out just check that + const out = outFile(options); + if (out) { + return isSameFile(filePath, out) || isSameFile(filePath, removeFileExtension(out) + Extension.Dts); + } - if (fileExtensionIsOneOf(filePath, supportedJSExtensionsFlat) || isDeclarationFileName(filePath)) { - // Otherwise just check if sourceFile with the name exists - const filePathWithoutExtension = removeFileExtension(filePath); - return !!getSourceFileByPath((filePathWithoutExtension + Extension.Ts) as Path) || - !!getSourceFileByPath((filePathWithoutExtension + Extension.Tsx) as Path); - } - return false; + // If declarationDir is specified, return if its a file in that directory + if (options.declarationDir && containsPath(options.declarationDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames())) { + return true; } - function isSameFile(file1: string, file2: string) { - return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + // If --outDir, check if file is in that directory + if (options.outDir) { + return containsPath(options.outDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames()); } - function getSymlinkCache(): SymlinkCache { - if (host.getSymlinkCache) { - return host.getSymlinkCache(); - } - if (!symlinks) { - symlinks = createSymlinkCache(currentDirectory, getCanonicalFileName); - } - if (files && resolvedTypeReferenceDirectives && !symlinks.hasProcessedResolutions()) { - symlinks.setSymlinksFromResolutions(files, resolvedTypeReferenceDirectives); - } - return symlinks; + if (fileExtensionIsOneOf(filePath, supportedJSExtensionsFlat) || isDeclarationFileName(filePath)) { + // Otherwise just check if sourceFile with the name exists + const filePathWithoutExtension = removeFileExtension(filePath); + return !!getSourceFileByPath((filePathWithoutExtension + Extension.Ts) as Path) || + !!getSourceFileByPath((filePathWithoutExtension + Extension.Tsx) as Path); } + return false; } - interface HostForUseSourceOfProjectReferenceRedirect { - compilerHost: CompilerHost; - getSymlinkCache: () => SymlinkCache; - useSourceOfProjectReferenceRedirect: boolean; - toPath(fileName: string): Path; - getResolvedProjectReferences(): readonly (ResolvedProjectReference | undefined)[] | undefined; - getSourceOfProjectReferenceRedirect(path: Path): SourceOfProjectReferenceRedirect | undefined; - forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined): T | undefined; + function isSameFile(file1: string, file2: string) { + return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; } - function updateHostForUseSourceOfProjectReferenceRedirect(host: HostForUseSourceOfProjectReferenceRedirect) { - let setOfDeclarationDirectories: Set | undefined; - const originalFileExists = host.compilerHost.fileExists; - const originalDirectoryExists = host.compilerHost.directoryExists; - const originalGetDirectories = host.compilerHost.getDirectories; - const originalRealpath = host.compilerHost.realpath; - - if (!host.useSourceOfProjectReferenceRedirect) return { onProgramCreateComplete: noop, fileExists }; - - host.compilerHost.fileExists = fileExists; - - let directoryExists; - if (originalDirectoryExists) { - // This implementation of directoryExists checks if the directory being requested is - // directory of .d.ts file for the referenced Project. - // If it is it returns true irrespective of whether that directory exists on host - directoryExists = host.compilerHost.directoryExists = path => { - if (originalDirectoryExists.call(host.compilerHost, path)) { - handleDirectoryCouldBeSymlink(path); - return true; - } - - if (!host.getResolvedProjectReferences()) return false; - - if (!setOfDeclarationDirectories) { - setOfDeclarationDirectories = new Set(); - host.forEachResolvedProjectReference(ref => { - const out = outFile(ref.commandLine.options); - if (out) { - setOfDeclarationDirectories!.add(getDirectoryPath(host.toPath(out))); - } - else { - // Set declaration's in different locations only, if they are next to source the directory present doesnt change - const declarationDir = ref.commandLine.options.declarationDir || ref.commandLine.options.outDir; - if (declarationDir) { - setOfDeclarationDirectories!.add(host.toPath(declarationDir)); - } - } - }); - } - - return fileOrDirectoryExistsUsingSource(path, /*isFile*/ false); - }; + function getSymlinkCache(): SymlinkCache { + if (host.getSymlinkCache) { + return host.getSymlinkCache(); } - - if (originalGetDirectories) { - // Call getDirectories only if directory actually present on the host - // This is needed to ensure that we arent getting directories that we fake about presence for - host.compilerHost.getDirectories = path => - !host.getResolvedProjectReferences() || (originalDirectoryExists && originalDirectoryExists.call(host.compilerHost, path)) ? - originalGetDirectories.call(host.compilerHost, path) : - []; + if (!symlinks) { + symlinks = createSymlinkCache(currentDirectory, getCanonicalFileName); } - - // This is something we keep for life time of the host - if (originalRealpath) { - host.compilerHost.realpath = s => - host.getSymlinkCache().getSymlinkedFiles()?.get(host.toPath(s)) || - originalRealpath.call(host.compilerHost, s); + if (files && resolvedTypeReferenceDirectives && !symlinks.hasProcessedResolutions()) { + symlinks.setSymlinksFromResolutions(files, resolvedTypeReferenceDirectives); } + return symlinks; + } +} - return { onProgramCreateComplete, fileExists, directoryExists }; +interface HostForUseSourceOfProjectReferenceRedirect { + compilerHost: CompilerHost; + getSymlinkCache: () => SymlinkCache; + useSourceOfProjectReferenceRedirect: boolean; + toPath(fileName: string): Path; + getResolvedProjectReferences(): readonly (ResolvedProjectReference | undefined)[] | undefined; + getSourceOfProjectReferenceRedirect(path: Path): SourceOfProjectReferenceRedirect | undefined; + forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference) => T | undefined): T | undefined; +} - function onProgramCreateComplete() { - host.compilerHost.fileExists = originalFileExists; - host.compilerHost.directoryExists = originalDirectoryExists; - host.compilerHost.getDirectories = originalGetDirectories; - // DO not revert realpath as it could be used later - } +function updateHostForUseSourceOfProjectReferenceRedirect(host: HostForUseSourceOfProjectReferenceRedirect) { + let setOfDeclarationDirectories: Set | undefined; + const originalFileExists = host.compilerHost.fileExists; + const originalDirectoryExists = host.compilerHost.directoryExists; + const originalGetDirectories = host.compilerHost.getDirectories; + const originalRealpath = host.compilerHost.realpath; + + if (!host.useSourceOfProjectReferenceRedirect) return { onProgramCreateComplete: noop, fileExists }; + + host.compilerHost.fileExists = fileExists; + + let directoryExists; + if (originalDirectoryExists) { + // This implementation of directoryExists checks if the directory being requested is + // directory of .d.ts file for the referenced Project. + // If it is it returns true irrespective of whether that directory exists on host + directoryExists = host.compilerHost.directoryExists = path => { + if (originalDirectoryExists.call(host.compilerHost, path)) { + handleDirectoryCouldBeSymlink(path); + return true; + } - // This implementation of fileExists checks if the file being requested is - // .d.ts file for the referenced Project. - // If it is it returns true irrespective of whether that file exists on host - function fileExists(file: string) { - if (originalFileExists.call(host.compilerHost, file)) return true; if (!host.getResolvedProjectReferences()) return false; - if (!isDeclarationFileName(file)) return false; - // Project references go to source file instead of .d.ts file - return fileOrDirectoryExistsUsingSource(file, /*isFile*/ true); - } + if (!setOfDeclarationDirectories) { + setOfDeclarationDirectories = new Set(); + host.forEachResolvedProjectReference(ref => { + const out = outFile(ref.commandLine.options); + if (out) { + setOfDeclarationDirectories!.add(getDirectoryPath(host.toPath(out))); + } + else { + // Set declaration's in different locations only, if they are next to source the directory present doesnt change + const declarationDir = ref.commandLine.options.declarationDir || ref.commandLine.options.outDir; + if (declarationDir) { + setOfDeclarationDirectories!.add(host.toPath(declarationDir)); + } + } + }); + } - function fileExistsIfProjectReferenceDts(file: string) { - const source = host.getSourceOfProjectReferenceRedirect(host.toPath(file)); - return source !== undefined ? - isString(source) ? originalFileExists.call(host.compilerHost, source) as boolean : true : - undefined; - } + return fileOrDirectoryExistsUsingSource(path, /*isFile*/ false); + }; + } - function directoryExistsIfProjectReferenceDeclDir(dir: string) { - const dirPath = host.toPath(dir); - const dirPathWithTrailingDirectorySeparator = `${dirPath}${directorySeparator}`; - return forEachKey( - setOfDeclarationDirectories!, - declDirPath => dirPath === declDirPath || - // Any parent directory of declaration dir - startsWith(declDirPath, dirPathWithTrailingDirectorySeparator) || - // Any directory inside declaration dir - startsWith(dirPath, `${declDirPath}/`) - ); - } + if (originalGetDirectories) { + // Call getDirectories only if directory actually present on the host + // This is needed to ensure that we arent getting directories that we fake about presence for + host.compilerHost.getDirectories = path => + !host.getResolvedProjectReferences() || (originalDirectoryExists && originalDirectoryExists.call(host.compilerHost, path)) ? + originalGetDirectories.call(host.compilerHost, path) : + []; + } - function handleDirectoryCouldBeSymlink(directory: string) { - if (!host.getResolvedProjectReferences() || containsIgnoredPath(directory)) return; + // This is something we keep for life time of the host + if (originalRealpath) { + host.compilerHost.realpath = s => + host.getSymlinkCache().getSymlinkedFiles()?.get(host.toPath(s)) || + originalRealpath.call(host.compilerHost, s); + } - // Because we already watch node_modules, handle symlinks in there - if (!originalRealpath || !stringContains(directory, nodeModulesPathPart)) return; - const symlinkCache = host.getSymlinkCache(); - const directoryPath = ensureTrailingDirectorySeparator(host.toPath(directory)); - if (symlinkCache.getSymlinkedDirectories()?.has(directoryPath)) return; + return { onProgramCreateComplete, fileExists, directoryExists }; - const real = normalizePath(originalRealpath.call(host.compilerHost, directory)); - let realPath: Path; - if (real === directory || - (realPath = ensureTrailingDirectorySeparator(host.toPath(real))) === directoryPath) { - // not symlinked - symlinkCache.setSymlinkedDirectory(directoryPath, false); - return; - } + function onProgramCreateComplete() { + host.compilerHost.fileExists = originalFileExists; + host.compilerHost.directoryExists = originalDirectoryExists; + host.compilerHost.getDirectories = originalGetDirectories; + // DO not revert realpath as it could be used later + } - symlinkCache.setSymlinkedDirectory(directory, { - real: ensureTrailingDirectorySeparator(real), - realPath - }); - } + // This implementation of fileExists checks if the file being requested is + // .d.ts file for the referenced Project. + // If it is it returns true irrespective of whether that file exists on host + function fileExists(file: string) { + if (originalFileExists.call(host.compilerHost, file)) return true; + if (!host.getResolvedProjectReferences()) return false; + if (!isDeclarationFileName(file)) return false; - function fileOrDirectoryExistsUsingSource(fileOrDirectory: string, isFile: boolean): boolean { - const fileOrDirectoryExistsUsingSource = isFile ? - (file: string) => fileExistsIfProjectReferenceDts(file) : - (dir: string) => directoryExistsIfProjectReferenceDeclDir(dir); - // Check current directory or file - const result = fileOrDirectoryExistsUsingSource(fileOrDirectory); - if (result !== undefined) return result; - - const symlinkCache = host.getSymlinkCache(); - const symlinkedDirectories = symlinkCache.getSymlinkedDirectories(); - if (!symlinkedDirectories) return false; - const fileOrDirectoryPath = host.toPath(fileOrDirectory); - if (!stringContains(fileOrDirectoryPath, nodeModulesPathPart)) return false; - if (isFile && symlinkCache.getSymlinkedFiles()?.has(fileOrDirectoryPath)) return true; - - // If it contains node_modules check if its one of the symlinked path we know of - return firstDefinedIterator( - symlinkedDirectories.entries(), - ([directoryPath, symlinkedDirectory]) => { - if (!symlinkedDirectory || !startsWith(fileOrDirectoryPath, directoryPath)) return undefined; - const result = fileOrDirectoryExistsUsingSource(fileOrDirectoryPath.replace(directoryPath, symlinkedDirectory.realPath)); - if (isFile && result) { - // Store the real path for the file' - const absolutePath = getNormalizedAbsolutePath(fileOrDirectory, host.compilerHost.getCurrentDirectory()); - symlinkCache.setSymlinkedFile( - fileOrDirectoryPath, - `${symlinkedDirectory.real}${absolutePath.replace(new RegExp(directoryPath, "i"), "")}` - ); - } - return result; - } - ) || false; - } + // Project references go to source file instead of .d.ts file + return fileOrDirectoryExistsUsingSource(file, /*isFile*/ true); } - /*@internal*/ - export const emitSkippedWithNoDiagnostics: EmitResult = { diagnostics: emptyArray, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true }; - - /*@internal*/ - export function handleNoEmitOptions( - program: Program | T, - sourceFile: SourceFile | undefined, - writeFile: WriteFileCallback | undefined, - cancellationToken: CancellationToken | undefined - ): EmitResult | undefined { - const options = program.getCompilerOptions(); - if (options.noEmit) { - // Cache the semantic diagnostics - program.getSemanticDiagnostics(sourceFile, cancellationToken); - return sourceFile || outFile(options) ? - emitSkippedWithNoDiagnostics : - program.emitBuildInfo(writeFile, cancellationToken); - } + function fileExistsIfProjectReferenceDts(file: string) { + const source = host.getSourceOfProjectReferenceRedirect(host.toPath(file)); + return source !== undefined ? + isString(source) ? originalFileExists.call(host.compilerHost, source) as boolean : true : + undefined; + } - // If the noEmitOnError flag is set, then check if we have any errors so far. If so, - // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we - // get any preEmit diagnostics, not just the ones - if (!options.noEmitOnError) return undefined; - let diagnostics: readonly Diagnostic[] = [ - ...program.getOptionsDiagnostics(cancellationToken), - ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), - ...program.getGlobalDiagnostics(cancellationToken), - ...program.getSemanticDiagnostics(sourceFile, cancellationToken) - ]; + function directoryExistsIfProjectReferenceDeclDir(dir: string) { + const dirPath = host.toPath(dir); + const dirPathWithTrailingDirectorySeparator = `${dirPath}${directorySeparator}`; + return forEachKey( + setOfDeclarationDirectories!, + declDirPath => dirPath === declDirPath || + // Any parent directory of declaration dir + startsWith(declDirPath, dirPathWithTrailingDirectorySeparator) || + // Any directory inside declaration dir + startsWith(dirPath, `${declDirPath}/`) + ); + } - if (diagnostics.length === 0 && getEmitDeclarations(program.getCompilerOptions())) { - diagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken); + function handleDirectoryCouldBeSymlink(directory: string) { + if (!host.getResolvedProjectReferences() || containsIgnoredPath(directory)) return; + + // Because we already watch node_modules, handle symlinks in there + if (!originalRealpath || !stringContains(directory, nodeModulesPathPart)) return; + const symlinkCache = host.getSymlinkCache(); + const directoryPath = ensureTrailingDirectorySeparator(host.toPath(directory)); + if (symlinkCache.getSymlinkedDirectories()?.has(directoryPath)) return; + + const real = normalizePath(originalRealpath.call(host.compilerHost, directory)); + let realPath: Path; + if (real === directory || + (realPath = ensureTrailingDirectorySeparator(host.toPath(real))) === directoryPath) { + // not symlinked + symlinkCache.setSymlinkedDirectory(directoryPath, false); + return; } - if (!diagnostics.length) return undefined; - let emittedFiles: string[] | undefined; - if (!sourceFile && !outFile(options)) { - const emitResult = program.emitBuildInfo(writeFile, cancellationToken); - if (emitResult.diagnostics) diagnostics = [...diagnostics, ...emitResult.diagnostics]; - emittedFiles = emitResult.emittedFiles; - } - return { diagnostics, sourceMaps: undefined, emittedFiles, emitSkipped: true }; + symlinkCache.setSymlinkedDirectory(directory, { + real: ensureTrailingDirectorySeparator(real), + realPath + }); } - /*@internal*/ - export function filterSemanticDiagnostics(diagnostic: readonly Diagnostic[], option: CompilerOptions): readonly Diagnostic[] { - return filter(diagnostic, d => !d.skippedOn || !option[d.skippedOn]); + function fileOrDirectoryExistsUsingSource(fileOrDirectory: string, isFile: boolean): boolean { + const fileOrDirectoryExistsUsingSource = isFile ? + (file: string) => fileExistsIfProjectReferenceDts(file) : + (dir: string) => directoryExistsIfProjectReferenceDeclDir(dir); + // Check current directory or file + const result = fileOrDirectoryExistsUsingSource(fileOrDirectory); + if (result !== undefined) return result; + + const symlinkCache = host.getSymlinkCache(); + const symlinkedDirectories = symlinkCache.getSymlinkedDirectories(); + if (!symlinkedDirectories) return false; + const fileOrDirectoryPath = host.toPath(fileOrDirectory); + if (!stringContains(fileOrDirectoryPath, nodeModulesPathPart)) return false; + if (isFile && symlinkCache.getSymlinkedFiles()?.has(fileOrDirectoryPath)) return true; + + // If it contains node_modules check if its one of the symlinked path we know of + return firstDefinedIterator( + symlinkedDirectories.entries(), + ([directoryPath, symlinkedDirectory]) => { + if (!symlinkedDirectory || !startsWith(fileOrDirectoryPath, directoryPath)) return undefined; + const result = fileOrDirectoryExistsUsingSource(fileOrDirectoryPath.replace(directoryPath, symlinkedDirectory.realPath)); + if (isFile && result) { + // Store the real path for the file' + const absolutePath = getNormalizedAbsolutePath(fileOrDirectory, host.compilerHost.getCurrentDirectory()); + symlinkCache.setSymlinkedFile( + fileOrDirectoryPath, + `${symlinkedDirectory.real}${absolutePath.replace(new RegExp(directoryPath, "i"), "")}` + ); + } + return result; + } + ) || false; } +} - /*@internal*/ - interface CompilerHostLike { - useCaseSensitiveFileNames(): boolean; - getCurrentDirectory(): string; - fileExists(fileName: string): boolean; - readFile(fileName: string): string | undefined; - readDirectory?(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): string[]; - trace?(s: string): void; - onUnRecoverableConfigFileDiagnostic?: DiagnosticReporter; +/** @internal */ +export const emitSkippedWithNoDiagnostics: EmitResult = { diagnostics: emptyArray, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true }; + +/** @internal */ +export function handleNoEmitOptions( + program: Program | T, + sourceFile: SourceFile | undefined, + writeFile: WriteFileCallback | undefined, + cancellationToken: CancellationToken | undefined +): EmitResult | undefined { + const options = program.getCompilerOptions(); + if (options.noEmit) { + // Cache the semantic diagnostics + program.getSemanticDiagnostics(sourceFile, cancellationToken); + return sourceFile || outFile(options) ? + emitSkippedWithNoDiagnostics : + program.emitBuildInfo(writeFile, cancellationToken); } - /* @internal */ - export function parseConfigHostFromCompilerHostLike(host: CompilerHostLike, directoryStructureHost: DirectoryStructureHost = host): ParseConfigFileHost { - return { - fileExists: f => directoryStructureHost.fileExists(f), - readDirectory(root, extensions, excludes, includes, depth) { - Debug.assertIsDefined(directoryStructureHost.readDirectory, "'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'"); - return directoryStructureHost.readDirectory(root, extensions, excludes, includes, depth); - }, - readFile: f => directoryStructureHost.readFile(f), - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), - getCurrentDirectory: () => host.getCurrentDirectory(), - onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || returnUndefined, - trace: host.trace ? (s) => host.trace!(s) : undefined - }; + // If the noEmitOnError flag is set, then check if we have any errors so far. If so, + // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we + // get any preEmit diagnostics, not just the ones + if (!options.noEmitOnError) return undefined; + let diagnostics: readonly Diagnostic[] = [ + ...program.getOptionsDiagnostics(cancellationToken), + ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), + ...program.getGlobalDiagnostics(cancellationToken), + ...program.getSemanticDiagnostics(sourceFile, cancellationToken) + ]; + + if (diagnostics.length === 0 && getEmitDeclarations(program.getCompilerOptions())) { + diagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken); } - // For backward compatibility - /** @deprecated */ export interface ResolveProjectReferencePathHost { - fileExists(fileName: string): boolean; + if (!diagnostics.length) return undefined; + let emittedFiles: string[] | undefined; + if (!sourceFile && !outFile(options)) { + const emitResult = program.emitBuildInfo(writeFile, cancellationToken); + if (emitResult.diagnostics) diagnostics = [...diagnostics, ...emitResult.diagnostics]; + emittedFiles = emitResult.emittedFiles; } + return { diagnostics, sourceMaps: undefined, emittedFiles, emitSkipped: true }; +} - /* @internal */ - export function createPrependNodes( - projectReferences: readonly ProjectReference[] | undefined, - getCommandLine: (ref: ProjectReference, index: number) => ParsedCommandLine | undefined, - readFile: (path: string) => string | undefined, - host: CompilerHost, - ) { - if (!projectReferences) return emptyArray; - let nodes: InputFiles[] | undefined; - for (let i = 0; i < projectReferences.length; i++) { - const ref = projectReferences[i]; - const resolvedRefOpts = getCommandLine(ref, i); - if (ref.prepend && resolvedRefOpts && resolvedRefOpts.options) { - const out = outFile(resolvedRefOpts.options); - // Upstream project didn't have outFile set -- skip (error will have been issued earlier) - if (!out) continue; +/** @internal */ +export function filterSemanticDiagnostics(diagnostic: readonly Diagnostic[], option: CompilerOptions): readonly Diagnostic[] { + return filter(diagnostic, d => !d.skippedOn || !option[d.skippedOn]); +} - const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(resolvedRefOpts.options, /*forceDtsPaths*/ true); - const node = createInputFilesWithFilePaths(readFile, jsFilePath!, sourceMapFilePath, declarationFilePath!, declarationMapPath, buildInfoPath, host, resolvedRefOpts.options); - (nodes || (nodes = [])).push(node); - } +/** @internal */ +export interface CompilerHostLike { + useCaseSensitiveFileNames(): boolean; + getCurrentDirectory(): string; + fileExists(fileName: string): boolean; + readFile(fileName: string): string | undefined; + readDirectory?(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): string[]; + trace?(s: string): void; + onUnRecoverableConfigFileDiagnostic?: DiagnosticReporter; +} + +/** @internal */ +export function parseConfigHostFromCompilerHostLike(host: CompilerHostLike, directoryStructureHost: DirectoryStructureHost = host): ParseConfigFileHost { + return { + fileExists: f => directoryStructureHost.fileExists(f), + readDirectory(root, extensions, excludes, includes, depth) { + Debug.assertIsDefined(directoryStructureHost.readDirectory, "'CompilerHost.readDirectory' must be implemented to correctly process 'projectReferences'"); + return directoryStructureHost.readDirectory(root, extensions, excludes, includes, depth); + }, + readFile: f => directoryStructureHost.readFile(f), + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), + getCurrentDirectory: () => host.getCurrentDirectory(), + onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || returnUndefined, + trace: host.trace ? (s) => host.trace!(s) : undefined + }; +} + +// For backward compatibility +/** @deprecated */ export interface ResolveProjectReferencePathHost { + fileExists(fileName: string): boolean; +} + +/** @internal */ +export function createPrependNodes( + projectReferences: readonly ProjectReference[] | undefined, + getCommandLine: (ref: ProjectReference, index: number) => ParsedCommandLine | undefined, + readFile: (path: string) => string | undefined, + host: CompilerHost, +) { + if (!projectReferences) return emptyArray; + let nodes: InputFiles[] | undefined; + for (let i = 0; i < projectReferences.length; i++) { + const ref = projectReferences[i]; + const resolvedRefOpts = getCommandLine(ref, i); + if (ref.prepend && resolvedRefOpts && resolvedRefOpts.options) { + const out = outFile(resolvedRefOpts.options); + // Upstream project didn't have outFile set -- skip (error will have been issued earlier) + if (!out) continue; + + const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(resolvedRefOpts.options, /*forceDtsPaths*/ true); + const node = createInputFilesWithFilePaths(readFile, jsFilePath!, sourceMapFilePath, declarationFilePath!, declarationMapPath, buildInfoPath, host, resolvedRefOpts.options); + (nodes || (nodes = [])).push(node); } - return nodes || emptyArray; - } - /** - * Returns the target config filename of a project reference. - * Note: The file might not exist. - */ - export function resolveProjectReferencePath(ref: ProjectReference): ResolvedConfigFileName; - /** @deprecated */ export function resolveProjectReferencePath(host: ResolveProjectReferencePathHost, ref: ProjectReference): ResolvedConfigFileName; - export function resolveProjectReferencePath(hostOrRef: ResolveProjectReferencePathHost | ProjectReference, ref?: ProjectReference): ResolvedConfigFileName { - const passedInRef = ref ? ref : hostOrRef as ProjectReference; - return resolveConfigFileProjectName(passedInRef.path); } + return nodes || emptyArray; +} +/** + * Returns the target config filename of a project reference. + * Note: The file might not exist. + */ +export function resolveProjectReferencePath(ref: ProjectReference): ResolvedConfigFileName; +/** @deprecated */ export function resolveProjectReferencePath(host: ResolveProjectReferencePathHost, ref: ProjectReference): ResolvedConfigFileName; +export function resolveProjectReferencePath(hostOrRef: ResolveProjectReferencePathHost | ProjectReference, ref?: ProjectReference): ResolvedConfigFileName { + const passedInRef = ref ? ref : hostOrRef as ProjectReference; + return resolveConfigFileProjectName(passedInRef.path); +} - /* @internal */ - /** - * Returns a DiagnosticMessage if we won't include a resolved module due to its extension. - * The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to. - * This returns a diagnostic even if the module will be an untyped module. - */ - export function getResolutionDiagnostic(options: CompilerOptions, { extension }: ResolvedModuleFull): DiagnosticMessage | undefined { - switch (extension) { - case Extension.Ts: - case Extension.Dts: - // These are always allowed. - return undefined; - case Extension.Tsx: - return needJsx(); - case Extension.Jsx: - return needJsx() || needAllowJs(); - case Extension.Js: - return needAllowJs(); - case Extension.Json: - return needResolveJsonModule(); - } +/** + * Returns a DiagnosticMessage if we won't include a resolved module due to its extension. + * The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to. + * This returns a diagnostic even if the module will be an untyped module. + * + * @internal + */ +export function getResolutionDiagnostic(options: CompilerOptions, { extension }: ResolvedModuleFull): DiagnosticMessage | undefined { + switch (extension) { + case Extension.Ts: + case Extension.Dts: + // These are always allowed. + return undefined; + case Extension.Tsx: + return needJsx(); + case Extension.Jsx: + return needJsx() || needAllowJs(); + case Extension.Js: + return needAllowJs(); + case Extension.Json: + return needResolveJsonModule(); + } - function needJsx() { - return options.jsx ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set; - } - function needAllowJs() { - return getAllowJSCompilerOption(options) || !getStrictOptionValue(options, "noImplicitAny") ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type; - } - function needResolveJsonModule() { - return options.resolveJsonModule ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used; - } + function needJsx() { + return options.jsx ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set; + } + function needAllowJs() { + return getAllowJSCompilerOption(options) || !getStrictOptionValue(options, "noImplicitAny") ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type; + } + function needResolveJsonModule() { + return options.resolveJsonModule ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used; } +} - function getModuleNames({ imports, moduleAugmentations }: SourceFile): StringLiteralLike[] { - const res = imports.map(i => i); - for (const aug of moduleAugmentations) { - if (aug.kind === SyntaxKind.StringLiteral) { - res.push(aug); - } - // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. +function getModuleNames({ imports, moduleAugmentations }: SourceFile): StringLiteralLike[] { + const res = imports.map(i => i); + for (const aug of moduleAugmentations) { + if (aug.kind === SyntaxKind.StringLiteral) { + res.push(aug); } - return res; + // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. } + return res; +} - /* @internal */ - export function getModuleNameStringLiteralAt({ imports, moduleAugmentations }: SourceFileImportsList, index: number): StringLiteralLike { - if (index < imports.length) return imports[index]; - let augIndex = imports.length; - for (const aug of moduleAugmentations) { - if (aug.kind === SyntaxKind.StringLiteral) { - if (index === augIndex) return aug; - augIndex++; - } - // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. +/** @internal */ +export function getModuleNameStringLiteralAt({ imports, moduleAugmentations }: SourceFileImportsList, index: number): StringLiteralLike { + if (index < imports.length) return imports[index]; + let augIndex = imports.length; + for (const aug of moduleAugmentations) { + if (aug.kind === SyntaxKind.StringLiteral) { + if (index === augIndex) return aug; + augIndex++; } - Debug.fail("should never ask for module name at index higher than possible module name"); + // Do nothing if it's an Identifier; we don't need to do module resolution for `declare global`. } + Debug.fail("should never ask for module name at index higher than possible module name"); } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 6953ebc845c86..44ed620cf23bc 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -1,1160 +1,1188 @@ -/*@internal*/ -namespace ts { - /** This is the cache of module/typedirectives resolution that can be retained across program */ - export interface ResolutionCache { - startRecordingFilesWithChangedResolutions(): void; - finishRecordingFilesWithChangedResolutions(): Path[] | undefined; - - resolveModuleNames( - moduleNames: string[], - containingFile: string, - reusedNames: string[] | undefined, - redirectedReference: ResolvedProjectReference | undefined, - containingSourceFile: SourceFile | undefined, - resolutionInfo: ModuleResolutionInfo | undefined - ): (ResolvedModuleFull | undefined)[]; - getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined; - resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[]; - - invalidateResolutionsOfFailedLookupLocations(): boolean; - invalidateResolutionOfFile(filePath: Path): void; - removeResolutionsOfFile(filePath: Path): void; - removeResolutionsFromProjectReferenceRedirects(filePath: Path): void; - setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: ESMap): void; - createHasInvalidatedResolutions(customHasInvalidatedResolutions: HasInvalidatedResolutions): HasInvalidatedResolutions; - hasChangedAutomaticTypeDirectiveNames(): boolean; - isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean; - - - startCachingPerDirectoryResolution(): void; - finishCachingPerDirectoryResolution(newProgram: Program | undefined, oldProgram: Program | undefined): void; - - updateTypeRootsWatch(): void; - closeTypeRootsWatch(): void; - - getModuleResolutionCache(): ModuleResolutionCache; - - clear(): void; - } +import * as ts from "./_namespaces/ts"; +import { + arrayToMap, CachedDirectoryStructureHost, CacheWithRedirects, CharacterCodes, clearMap, closeFileWatcher, + closeFileWatcherOf, CompilerOptions, contains, createCacheWithRedirects, createModeAwareCache, + createModuleResolutionCache, createMultiMap, createTypeReferenceDirectiveResolutionCache, Debug, Diagnostics, + directorySeparator, DirectoryWatcherCallback, emptyArray, emptyIterator, endsWith, ESMap, Extension, extensionIsTS, + fileExtensionIs, fileExtensionIsOneOf, FileReference, FileWatcher, FileWatcherCallback, firstDefinedIterator, + GetCanonicalFileName, getDirectoryPath, getEffectiveTypeRoots, getModeForFileReference, getModeForResolutionAtIndex, + getModeForUsageLocation, getNormalizedAbsolutePath, getResolutionName, getRootLength, HasInvalidatedResolutions, + ignoredPaths, inferredTypesContainingFile, isEmittedFileOfProgram, isExternalModuleNameRelative, + isExternalOrCommonJsModule, isNodeModulesDirectory, isRootedDiskPath, isString, isStringLiteralLike, isTraceEnabled, + length, loadModuleFromGlobalCache, Map, memoize, MinimalResolutionCacheHost, ModeAwareCache, ModuleKind, + ModuleResolutionCache, ModuleResolutionHost, ModuleResolutionInfo, mutateMap, noopFileWatcher, normalizePath, + PackageId, packageIdToString, parseNodeModuleFromPath, Path, pathContainsNodeModules, PerModuleNameCache, Program, + ReadonlyESMap, removeSuffix, removeTrailingDirectorySeparator, resolutionExtensionIsTSOrJson, ResolvedModuleFull, + ResolvedModuleWithFailedLookupLocations, ResolvedProjectReference, ResolvedTypeReferenceDirective, + ResolvedTypeReferenceDirectiveWithFailedLookupLocations, returnTrue, Set, some, SourceFile, startsWith, + stringContains, trace, unorderedRemoveItem, WatchDirectoryFlags, +} from "./_namespaces/ts"; + +/** + * This is the cache of module/typedirectives resolution that can be retained across program + * + * @internal + */ +export interface ResolutionCache { + startRecordingFilesWithChangedResolutions(): void; + finishRecordingFilesWithChangedResolutions(): Path[] | undefined; + + resolveModuleNames( + moduleNames: string[], + containingFile: string, + reusedNames: string[] | undefined, + redirectedReference: ResolvedProjectReference | undefined, + containingSourceFile: SourceFile | undefined, + resolutionInfo: ModuleResolutionInfo | undefined + ): (ResolvedModuleFull | undefined)[]; + getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined; + resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[]; + + invalidateResolutionsOfFailedLookupLocations(): boolean; + invalidateResolutionOfFile(filePath: Path): void; + removeResolutionsOfFile(filePath: Path): void; + removeResolutionsFromProjectReferenceRedirects(filePath: Path): void; + setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: ESMap): void; + createHasInvalidatedResolutions(customHasInvalidatedResolutions: HasInvalidatedResolutions): HasInvalidatedResolutions; + hasChangedAutomaticTypeDirectiveNames(): boolean; + isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean; + + + startCachingPerDirectoryResolution(): void; + finishCachingPerDirectoryResolution(newProgram: Program | undefined, oldProgram: Program | undefined): void; + + updateTypeRootsWatch(): void; + closeTypeRootsWatch(): void; + + getModuleResolutionCache(): ModuleResolutionCache; + + clear(): void; +} - interface ResolutionWithFailedLookupLocations { - readonly failedLookupLocations: string[]; - readonly affectingLocations: string[]; - isInvalidated?: boolean; - refCount?: number; - // Files that have this resolution using - files?: Path[]; - } +/** @internal */ +export interface ResolutionWithFailedLookupLocations { + readonly failedLookupLocations: string[]; + readonly affectingLocations: string[]; + isInvalidated?: boolean; + refCount?: number; + // Files that have this resolution using + files?: Path[]; +} - interface ResolutionWithResolvedFileName { - resolvedFileName: string | undefined; - packagetId?: PackageId; - } +interface ResolutionWithResolvedFileName { + resolvedFileName: string | undefined; + packagetId?: PackageId; +} - interface CachedResolvedModuleWithFailedLookupLocations extends ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations { - } +/** @internal */ +export interface CachedResolvedModuleWithFailedLookupLocations extends ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations { +} - interface CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations { - } +interface CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations { +} - export interface ResolutionCacheHost extends MinimalResolutionCacheHost { - toPath(fileName: string): Path; - getCanonicalFileName: GetCanonicalFileName; - getCompilationSettings(): CompilerOptions; - watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; - watchAffectingFileLocation(file: string, cb: FileWatcherCallback): FileWatcher; - onInvalidatedResolution(): void; - watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; - onChangedAutomaticTypeDirectiveNames(): void; - scheduleInvalidateResolutionsOfFailedLookupLocations(): void; - getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; - projectName?: string; - getGlobalCache?(): string | undefined; - globalCacheResolutionModuleName?(externalModuleName: string): string; - writeLog(s: string): void; - getCurrentProgram(): Program | undefined; - fileIsOpen(filePath: Path): boolean; - onDiscoveredSymlink?(): void; - } +/** @internal */ +export interface ResolutionCacheHost extends MinimalResolutionCacheHost { + toPath(fileName: string): Path; + getCanonicalFileName: GetCanonicalFileName; + getCompilationSettings(): CompilerOptions; + watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; + watchAffectingFileLocation(file: string, cb: FileWatcherCallback): FileWatcher; + onInvalidatedResolution(): void; + watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; + onChangedAutomaticTypeDirectiveNames(): void; + scheduleInvalidateResolutionsOfFailedLookupLocations(): void; + getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; + projectName?: string; + getGlobalCache?(): string | undefined; + globalCacheResolutionModuleName?(externalModuleName: string): string; + writeLog(s: string): void; + getCurrentProgram(): Program | undefined; + fileIsOpen(filePath: Path): boolean; + onDiscoveredSymlink?(): void; +} - interface FileWatcherOfAffectingLocation { - /** watcher for the lookup */ - watcher: FileWatcher; - resolutions: number; - files: number; - paths: Set; - } +interface FileWatcherOfAffectingLocation { + /** watcher for the lookup */ + watcher: FileWatcher; + resolutions: number; + files: number; + paths: Set; +} - interface DirectoryWatchesOfFailedLookup { - /** watcher for the lookup */ - watcher: FileWatcher; - /** ref count keeping this watch alive */ - refCount: number; - /** is the directory watched being non recursive */ - nonRecursive?: boolean; - } +interface DirectoryWatchesOfFailedLookup { + /** watcher for the lookup */ + watcher: FileWatcher; + /** ref count keeping this watch alive */ + refCount: number; + /** is the directory watched being non recursive */ + nonRecursive?: boolean; +} + +interface DirectoryOfFailedLookupWatch { + dir: string; + dirPath: Path; + nonRecursive?: boolean; +} - interface DirectoryOfFailedLookupWatch { - dir: string; - dirPath: Path; - nonRecursive?: boolean; +/** @internal */ +export function removeIgnoredPath(path: Path): Path | undefined { + // Consider whole staging folder as if node_modules changed. + if (endsWith(path, "/node_modules/.staging")) { + return removeSuffix(path, "/.staging") as Path; } - export function removeIgnoredPath(path: Path): Path | undefined { - // Consider whole staging folder as if node_modules changed. - if (endsWith(path, "/node_modules/.staging")) { - return removeSuffix(path, "/.staging") as Path; - } + return some(ignoredPaths, searchPath => stringContains(path, searchPath)) ? + undefined : + path; +} - return some(ignoredPaths, searchPath => stringContains(path, searchPath)) ? - undefined : - path; +/** + * Filter out paths like + * "/", "/user", "/user/username", "/user/username/folderAtRoot", + * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot" + * @param dirPath + * + * @internal + */ +export function canWatchDirectoryOrFile(dirPath: Path) { + const rootLength = getRootLength(dirPath); + if (dirPath.length === rootLength) { + // Ignore "/", "c:/" + return false; } - /** - * 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 canWatchDirectoryOrFile(dirPath: Path) { - const rootLength = getRootLength(dirPath); - if (dirPath.length === rootLength) { - // Ignore "/", "c:/" - return false; - } + let nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); + if (nextDirectorySeparator === -1) { + // ignore "/user", "c:/users" or "c:/folderAtRoot" + return false; + } - let nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); + let pathPartForUserCheck = dirPath.substring(rootLength, nextDirectorySeparator + 1); + const isNonDirectorySeparatorRoot = rootLength > 1 || dirPath.charCodeAt(0) !== CharacterCodes.slash; + if (isNonDirectorySeparatorRoot && + dirPath.search(/[a-zA-Z]:/) !== 0 && // Non dos style paths + pathPartForUserCheck.search(/[a-zA-Z]\$\//) === 0) { // Dos style nextPart + nextDirectorySeparator = dirPath.indexOf(directorySeparator, nextDirectorySeparator + 1); if (nextDirectorySeparator === -1) { - // ignore "/user", "c:/users" or "c:/folderAtRoot" + // ignore "//vda1cs4850/c$/folderAtRoot" return false; } - let pathPartForUserCheck = dirPath.substring(rootLength, nextDirectorySeparator + 1); - const isNonDirectorySeparatorRoot = rootLength > 1 || dirPath.charCodeAt(0) !== CharacterCodes.slash; - if (isNonDirectorySeparatorRoot && - dirPath.search(/[a-zA-Z]:/) !== 0 && // Non dos style paths - pathPartForUserCheck.search(/[a-zA-Z]\$\//) === 0) { // Dos style nextPart - nextDirectorySeparator = dirPath.indexOf(directorySeparator, nextDirectorySeparator + 1); - if (nextDirectorySeparator === -1) { - // ignore "//vda1cs4850/c$/folderAtRoot" - return false; - } - - pathPartForUserCheck = dirPath.substring(rootLength + pathPartForUserCheck.length, nextDirectorySeparator + 1); - } - - if (isNonDirectorySeparatorRoot && - pathPartForUserCheck.search(/users\//i) !== 0) { - // Paths like c:/folderAtRoot/subFolder are allowed - return true; - } + pathPartForUserCheck = dirPath.substring(rootLength + pathPartForUserCheck.length, nextDirectorySeparator + 1); + } - for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) { - searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1; - if (searchIndex === 0) { - // Folder isnt at expected minimum levels - return false; - } - } + if (isNonDirectorySeparatorRoot && + pathPartForUserCheck.search(/users\//i) !== 0) { + // Paths like c:/folderAtRoot/subFolder are allowed return true; } - type GetResolutionWithResolvedFileName = - (resolution: T) => R | undefined; - - export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache { - let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; - let filesWithInvalidatedResolutions: Set | undefined; - let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyESMap | undefined; - const nonRelativeExternalModuleResolutions = createMultiMap(); - - const resolutionsWithFailedLookups: ResolutionWithFailedLookupLocations[] = []; - const resolutionsWithOnlyAffectingLocations: ResolutionWithFailedLookupLocations[] = []; - const resolvedFileToResolution = createMultiMap(); - const impliedFormatPackageJsons = new Map(); - - let hasChangedAutomaticTypeDirectiveNames = false; - let affectingPathChecksForFile: Set | undefined; - let affectingPathChecks: Set | undefined; - let failedLookupChecks: Set | undefined; - let startsWithPathChecks: Set | undefined; - let isInDirectoryChecks: Set | undefined; - - const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217 - const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); - - // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file. - // The key in the map is source file's path. - // The values are Map of resolutions with key being name lookedup. - const resolvedModuleNames = new Map>(); - const perDirectoryResolvedModuleNames: CacheWithRedirects> = createCacheWithRedirects(); - const nonRelativeModuleNameCache: CacheWithRedirects = createCacheWithRedirects(); - const moduleResolutionCache = createModuleResolutionCache( - getCurrentDirectory(), - resolutionHost.getCanonicalFileName, - /*options*/ undefined, - perDirectoryResolvedModuleNames, - nonRelativeModuleNameCache, - ); + for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) { + searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1; + if (searchIndex === 0) { + // Folder isnt at expected minimum levels + return false; + } + } + return true; +} - const resolvedTypeReferenceDirectives = new Map>(); - const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects> = createCacheWithRedirects(); - const typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache( - getCurrentDirectory(), - resolutionHost.getCanonicalFileName, - /*options*/ undefined, - moduleResolutionCache.getPackageJsonInfoCache(), - perDirectoryResolvedTypeReferenceDirectives - ); +type GetResolutionWithResolvedFileName = + (resolution: T) => R | undefined; + +/** @internal */ +export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache { + let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; + let filesWithInvalidatedResolutions: Set | undefined; + let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyESMap | undefined; + const nonRelativeExternalModuleResolutions = createMultiMap(); + + const resolutionsWithFailedLookups: ResolutionWithFailedLookupLocations[] = []; + const resolutionsWithOnlyAffectingLocations: ResolutionWithFailedLookupLocations[] = []; + const resolvedFileToResolution = createMultiMap(); + const impliedFormatPackageJsons = new Map(); + + let hasChangedAutomaticTypeDirectiveNames = false; + let affectingPathChecksForFile: Set | undefined; + let affectingPathChecks: Set | undefined; + let failedLookupChecks: Set | undefined; + let startsWithPathChecks: Set | undefined; + let isInDirectoryChecks: Set | undefined; + + const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217 + const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); + + // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file. + // The key in the map is source file's path. + // The values are Map of resolutions with key being name lookedup. + const resolvedModuleNames = new Map>(); + const perDirectoryResolvedModuleNames: CacheWithRedirects> = createCacheWithRedirects(); + const nonRelativeModuleNameCache: CacheWithRedirects = createCacheWithRedirects(); + const moduleResolutionCache = createModuleResolutionCache( + getCurrentDirectory(), + resolutionHost.getCanonicalFileName, + /*options*/ undefined, + perDirectoryResolvedModuleNames, + nonRelativeModuleNameCache, + ); + + const resolvedTypeReferenceDirectives = new Map>(); + const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects> = createCacheWithRedirects(); + const typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache( + getCurrentDirectory(), + resolutionHost.getCanonicalFileName, + /*options*/ undefined, + moduleResolutionCache.getPackageJsonInfoCache(), + perDirectoryResolvedTypeReferenceDirectives + ); - /** - * These are the extensions that failed lookup files will have by default, - * any other extension of failed lookup will be store that path in custom failed lookup path - * This helps in not having to comb through all resolutions when files are added/removed - * Note that .d.ts file also has .d.ts extension hence will be part of default extensions - */ - const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]; - const customFailedLookupPaths = new Map(); - - const directoryWatchesOfFailedLookups = new Map(); - const fileWatchesOfAffectingLocations = new Map(); - const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory())); - const rootPath = (rootDir && resolutionHost.toPath(rootDir)) as Path; // TODO: GH#18217 - const rootSplitLength = rootPath !== undefined ? rootPath.split(directorySeparator).length : 0; - - // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames - const typeRootsWatches = new Map(); - - return { - getModuleResolutionCache: () => moduleResolutionCache, - startRecordingFilesWithChangedResolutions, - finishRecordingFilesWithChangedResolutions, - // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update - // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) - startCachingPerDirectoryResolution, - finishCachingPerDirectoryResolution, - resolveModuleNames, - getResolvedModuleWithFailedLookupLocationsFromCache, - resolveTypeReferenceDirectives, - removeResolutionsFromProjectReferenceRedirects, - removeResolutionsOfFile, - hasChangedAutomaticTypeDirectiveNames: () => hasChangedAutomaticTypeDirectiveNames, - invalidateResolutionOfFile, - invalidateResolutionsOfFailedLookupLocations, - setFilesWithInvalidatedNonRelativeUnresolvedImports, - createHasInvalidatedResolutions, - isFileWithInvalidatedNonRelativeUnresolvedImports, - updateTypeRootsWatch, - closeTypeRootsWatch, - clear - }; + /** + * These are the extensions that failed lookup files will have by default, + * any other extension of failed lookup will be store that path in custom failed lookup path + * This helps in not having to comb through all resolutions when files are added/removed + * Note that .d.ts file also has .d.ts extension hence will be part of default extensions + */ + const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]; + const customFailedLookupPaths = new Map(); + + const directoryWatchesOfFailedLookups = new Map(); + const fileWatchesOfAffectingLocations = new Map(); + const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory())); + const rootPath = (rootDir && resolutionHost.toPath(rootDir)) as Path; // TODO: GH#18217 + const rootSplitLength = rootPath !== undefined ? rootPath.split(directorySeparator).length : 0; + + // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames + const typeRootsWatches = new Map(); + + return { + getModuleResolutionCache: () => moduleResolutionCache, + startRecordingFilesWithChangedResolutions, + finishRecordingFilesWithChangedResolutions, + // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update + // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) + startCachingPerDirectoryResolution, + finishCachingPerDirectoryResolution, + resolveModuleNames, + getResolvedModuleWithFailedLookupLocationsFromCache, + resolveTypeReferenceDirectives, + removeResolutionsFromProjectReferenceRedirects, + removeResolutionsOfFile, + hasChangedAutomaticTypeDirectiveNames: () => hasChangedAutomaticTypeDirectiveNames, + invalidateResolutionOfFile, + invalidateResolutionsOfFailedLookupLocations, + setFilesWithInvalidatedNonRelativeUnresolvedImports, + createHasInvalidatedResolutions, + isFileWithInvalidatedNonRelativeUnresolvedImports, + updateTypeRootsWatch, + closeTypeRootsWatch, + clear + }; + + function getResolvedModule(resolution: CachedResolvedModuleWithFailedLookupLocations) { + return resolution.resolvedModule; + } - function getResolvedModule(resolution: CachedResolvedModuleWithFailedLookupLocations) { - return resolution.resolvedModule; - } + function getResolvedTypeReferenceDirective(resolution: CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations) { + return resolution.resolvedTypeReferenceDirective; + } - function getResolvedTypeReferenceDirective(resolution: CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations) { - return resolution.resolvedTypeReferenceDirective; + function isInDirectoryPath(dir: Path | undefined, file: Path) { + if (dir === undefined || file.length <= dir.length) { + return false; } + return startsWith(file, dir) && file[dir.length] === directorySeparator; + } - function isInDirectoryPath(dir: Path | undefined, file: Path) { - if (dir === undefined || file.length <= dir.length) { - return false; - } - return startsWith(file, dir) && file[dir.length] === directorySeparator; - } + function clear() { + clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); + clearMap(fileWatchesOfAffectingLocations, closeFileWatcherOf); + customFailedLookupPaths.clear(); + nonRelativeExternalModuleResolutions.clear(); + closeTypeRootsWatch(); + resolvedModuleNames.clear(); + resolvedTypeReferenceDirectives.clear(); + resolvedFileToResolution.clear(); + resolutionsWithFailedLookups.length = 0; + resolutionsWithOnlyAffectingLocations.length = 0; + failedLookupChecks = undefined; + startsWithPathChecks = undefined; + isInDirectoryChecks = undefined; + affectingPathChecks = undefined; + affectingPathChecksForFile = undefined; + moduleResolutionCache.clear(); + typeReferenceDirectiveResolutionCache.clear(); + impliedFormatPackageJsons.clear(); + hasChangedAutomaticTypeDirectiveNames = false; + } - function clear() { - clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); - clearMap(fileWatchesOfAffectingLocations, closeFileWatcherOf); - customFailedLookupPaths.clear(); - nonRelativeExternalModuleResolutions.clear(); - closeTypeRootsWatch(); - resolvedModuleNames.clear(); - resolvedTypeReferenceDirectives.clear(); - resolvedFileToResolution.clear(); - resolutionsWithFailedLookups.length = 0; - resolutionsWithOnlyAffectingLocations.length = 0; - failedLookupChecks = undefined; - startsWithPathChecks = undefined; - isInDirectoryChecks = undefined; - affectingPathChecks = undefined; - affectingPathChecksForFile = undefined; - moduleResolutionCache.clear(); - typeReferenceDirectiveResolutionCache.clear(); - impliedFormatPackageJsons.clear(); - hasChangedAutomaticTypeDirectiveNames = false; - } + function startRecordingFilesWithChangedResolutions() { + filesWithChangedSetOfUnresolvedImports = []; + } - function startRecordingFilesWithChangedResolutions() { - filesWithChangedSetOfUnresolvedImports = []; - } + function finishRecordingFilesWithChangedResolutions() { + const collected = filesWithChangedSetOfUnresolvedImports; + filesWithChangedSetOfUnresolvedImports = undefined; + return collected; + } - function finishRecordingFilesWithChangedResolutions() { - const collected = filesWithChangedSetOfUnresolvedImports; - filesWithChangedSetOfUnresolvedImports = undefined; - return collected; + function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean { + if (!filesWithInvalidatedNonRelativeUnresolvedImports) { + return false; } - function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean { - if (!filesWithInvalidatedNonRelativeUnresolvedImports) { - return false; - } - - // Invalidated if file has unresolved imports - const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path); - return !!value && !!value.length; - } + // Invalidated if file has unresolved imports + const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path); + return !!value && !!value.length; + } - function createHasInvalidatedResolutions(customHasInvalidatedResolutions: HasInvalidatedResolutions): HasInvalidatedResolutions { - // Ensure pending resolutions are applied - invalidateResolutionsOfFailedLookupLocations(); - const collected = filesWithInvalidatedResolutions; - filesWithInvalidatedResolutions = undefined; - return path => customHasInvalidatedResolutions(path) || - !!collected?.has(path) || - isFileWithInvalidatedNonRelativeUnresolvedImports(path); - } + function createHasInvalidatedResolutions(customHasInvalidatedResolutions: HasInvalidatedResolutions): HasInvalidatedResolutions { + // Ensure pending resolutions are applied + invalidateResolutionsOfFailedLookupLocations(); + const collected = filesWithInvalidatedResolutions; + filesWithInvalidatedResolutions = undefined; + return path => customHasInvalidatedResolutions(path) || + !!collected?.has(path) || + isFileWithInvalidatedNonRelativeUnresolvedImports(path); + } - function startCachingPerDirectoryResolution() { - moduleResolutionCache.clearAllExceptPackageJsonInfoCache(); - typeReferenceDirectiveResolutionCache.clearAllExceptPackageJsonInfoCache(); - // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update - // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) - nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); - nonRelativeExternalModuleResolutions.clear(); - } + function startCachingPerDirectoryResolution() { + moduleResolutionCache.clearAllExceptPackageJsonInfoCache(); + typeReferenceDirectiveResolutionCache.clearAllExceptPackageJsonInfoCache(); + // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update + // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) + nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); + nonRelativeExternalModuleResolutions.clear(); + } - function finishCachingPerDirectoryResolution(newProgram: Program | undefined, oldProgram: Program | undefined) { - filesWithInvalidatedNonRelativeUnresolvedImports = undefined; - nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); - nonRelativeExternalModuleResolutions.clear(); - // Update file watches - if (newProgram !== oldProgram) { - newProgram?.getSourceFiles().forEach(newFile => { - const expected = isExternalOrCommonJsModule(newFile) ? newFile.packageJsonLocations?.length ?? 0 : 0; - const existing = impliedFormatPackageJsons.get(newFile.path) ?? emptyArray; - for (let i = existing.length; i < expected; i++) { - createFileWatcherOfAffectingLocation(newFile.packageJsonLocations![i], /*forResolution*/ false); - } - if (existing.length > expected) { - for (let i = expected; i < existing.length; i++) { - fileWatchesOfAffectingLocations.get(existing[i])!.files--; - } - } - if (expected) impliedFormatPackageJsons.set(newFile.path, newFile.packageJsonLocations!); - else impliedFormatPackageJsons.delete(newFile.path); - }); - impliedFormatPackageJsons.forEach((existing, path) => { - if (!newProgram?.getSourceFileByPath(path)) { - existing.forEach(location => fileWatchesOfAffectingLocations.get(location)!.files--); - impliedFormatPackageJsons.delete(path); + function finishCachingPerDirectoryResolution(newProgram: Program | undefined, oldProgram: Program | undefined) { + filesWithInvalidatedNonRelativeUnresolvedImports = undefined; + nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); + nonRelativeExternalModuleResolutions.clear(); + // Update file watches + if (newProgram !== oldProgram) { + newProgram?.getSourceFiles().forEach(newFile => { + const expected = isExternalOrCommonJsModule(newFile) ? newFile.packageJsonLocations?.length ?? 0 : 0; + const existing = impliedFormatPackageJsons.get(newFile.path) ?? emptyArray; + for (let i = existing.length; i < expected; i++) { + createFileWatcherOfAffectingLocation(newFile.packageJsonLocations![i], /*forResolution*/ false); + } + if (existing.length > expected) { + for (let i = expected; i < existing.length; i++) { + fileWatchesOfAffectingLocations.get(existing[i])!.files--; } - }); - } - directoryWatchesOfFailedLookups.forEach((watcher, path) => { - if (watcher.refCount === 0) { - directoryWatchesOfFailedLookups.delete(path); - watcher.watcher.close(); } + if (expected) impliedFormatPackageJsons.set(newFile.path, newFile.packageJsonLocations!); + else impliedFormatPackageJsons.delete(newFile.path); }); - fileWatchesOfAffectingLocations.forEach((watcher, path) => { - if (watcher.files === 0 && watcher.resolutions === 0) { - fileWatchesOfAffectingLocations.delete(path); - watcher.watcher.close(); + impliedFormatPackageJsons.forEach((existing, path) => { + if (!newProgram?.getSourceFileByPath(path)) { + existing.forEach(location => fileWatchesOfAffectingLocations.get(location)!.files--); + impliedFormatPackageJsons.delete(path); } }); - - hasChangedAutomaticTypeDirectiveNames = false; } - - function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: never, mode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): CachedResolvedModuleWithFailedLookupLocations { - const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference, mode); - // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts - if (!resolutionHost.getGlobalCache) { - return primaryResult; + directoryWatchesOfFailedLookups.forEach((watcher, path) => { + if (watcher.refCount === 0) { + directoryWatchesOfFailedLookups.delete(path); + watcher.watcher.close(); } - - // otherwise try to load typings from @types - const globalCache = resolutionHost.getGlobalCache(); - 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, affectingLocations } = loadModuleFromGlobalCache( - Debug.checkDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName), - resolutionHost.projectName, - compilerOptions, - host, - globalCache, - moduleResolutionCache, - ); - if (resolvedModule) { - // Modify existing resolution so its saved in the directory cache as well - (primaryResult.resolvedModule as any) = resolvedModule; - primaryResult.failedLookupLocations.push(...failedLookupLocations); - primaryResult.affectingLocations.push(...affectingLocations); - return primaryResult; - } + }); + fileWatchesOfAffectingLocations.forEach((watcher, path) => { + if (watcher.files === 0 && watcher.resolutions === 0) { + fileWatchesOfAffectingLocations.delete(path); + watcher.watcher.close(); } + }); + + hasChangedAutomaticTypeDirectiveNames = false; + } - // Default return the result from the first pass + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: never, mode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): CachedResolvedModuleWithFailedLookupLocations { + const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference, mode); + // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts + if (!resolutionHost.getGlobalCache) { return primaryResult; } - function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: SourceFile, resolutionMode?: SourceFile["impliedNodeFormat"] | undefined): CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations { - return ts.resolveTypeReferenceDirective(typeReferenceDirectiveName, containingFile, options, host, redirectedReference, typeReferenceDirectiveResolutionCache, resolutionMode); + // otherwise try to load typings from @types + const globalCache = resolutionHost.getGlobalCache(); + 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, affectingLocations } = loadModuleFromGlobalCache( + Debug.checkDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName), + resolutionHost.projectName, + compilerOptions, + host, + globalCache, + moduleResolutionCache, + ); + if (resolvedModule) { + // Modify existing resolution so its saved in the directory cache as well + (primaryResult.resolvedModule as any) = resolvedModule; + primaryResult.failedLookupLocations.push(...failedLookupLocations); + primaryResult.affectingLocations.push(...affectingLocations); + return primaryResult; + } } - interface ResolveNamesWithLocalCacheInput { - names: readonly string[] | readonly FileReference[]; - containingFile: string; - redirectedReference: ResolvedProjectReference | undefined; - cache: ESMap>; - perDirectoryCacheWithRedirects: CacheWithRedirects>; - loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => T; - getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName; - shouldRetryResolution: (t: T) => boolean; - reusedNames?: readonly string[]; - resolutionInfo?: ModuleResolutionInfo; - logChanges?: boolean; - containingSourceFile?: SourceFile; - containingSourceFileMode?: SourceFile["impliedNodeFormat"]; - } - function resolveNamesWithLocalCache({ - names, containingFile, redirectedReference, - cache, perDirectoryCacheWithRedirects, - loader, getResolutionWithResolvedFileName, - shouldRetryResolution, reusedNames, resolutionInfo, logChanges, containingSourceFile, containingSourceFileMode - }: ResolveNamesWithLocalCacheInput): (R | undefined)[] { - const path = resolutionHost.toPath(containingFile); - const resolutionsInFile = cache.get(path) || cache.set(path, createModeAwareCache()).get(path)!; - const dirPath = getDirectoryPath(path); - const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); - let perDirectoryResolution = perDirectoryCache.get(dirPath); - if (!perDirectoryResolution) { - perDirectoryResolution = createModeAwareCache(); - perDirectoryCache.set(dirPath, perDirectoryResolution); - } - const resolvedModules: (R | undefined)[] = []; - const compilerOptions = resolutionHost.getCompilationSettings(); - const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path); - - // All the resolutions in this file are invalidated if this file wasn't resolved using same redirect - const program = resolutionHost.getCurrentProgram(); - const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile); - const unmatchedRedirects = oldRedirect ? - !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path : - !!redirectedReference; - - const seenNamesInFile = createModeAwareCache(); - let i = 0; - for (const entry of containingSourceFile && resolutionInfo ? resolutionInfo.names : names) { - const name = !isString(entry) ? getResolutionName(entry) : entry; - // Imports supply a `containingSourceFile` but no `containingSourceFileMode` - it would be redundant - // they require calculating the mode for a given import from it's position in the resolution table, since a given - // import's syntax may override the file's default mode. - // Type references instead supply a `containingSourceFileMode` and a non-string entry which contains - // a default file mode override if applicable. - const mode = !isString(entry) ? - isStringLiteralLike(entry) ? - getModeForUsageLocation(containingSourceFile!, entry) : - getModeForFileReference(entry, containingSourceFileMode) : - containingSourceFile ? - getModeForResolutionAtIndex(containingSourceFile, i) : - undefined; - i++; - let resolution = resolutionsInFile.get(name, mode); - // Resolution is valid if it is present and not invalidated - if (!seenNamesInFile.has(name, mode) && - unmatchedRedirects || !resolution || resolution.isInvalidated || - // If the name is unresolved import that was invalidated, recalculate - (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) { - const existingResolution = resolution; - const resolutionInDirectory = perDirectoryResolution.get(name, mode); - if (resolutionInDirectory) { - resolution = resolutionInDirectory; - const host = resolutionHost.getCompilerHost?.() || resolutionHost; - if (isTraceEnabled(compilerOptions, host)) { - const resolved = getResolutionWithResolvedFileName(resolution); - trace( - host, - loader === resolveModuleName as unknown ? - resolved?.resolvedFileName ? - resolved.packagetId ? - Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4: - Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3: - Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_not_resolved : - resolved?.resolvedFileName ? - resolved.packagetId ? - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4 : - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3 : - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_not_resolved, - name, - containingFile, - getDirectoryPath(containingFile), - resolved?.resolvedFileName, - resolved?.packagetId && packageIdToString(resolved.packagetId) - ); - } - } - else { - resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference, containingSourceFile, mode); - perDirectoryResolution.set(name, mode, resolution); - if (resolutionHost.onDiscoveredSymlink && resolutionIsSymlink(resolution)) { - resolutionHost.onDiscoveredSymlink(); - } - } - resolutionsInFile.set(name, mode, resolution); - watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution, path, getResolutionWithResolvedFileName); - if (existingResolution) { - stopWatchFailedLookupLocationOfResolution(existingResolution, path, getResolutionWithResolvedFileName); - } + // Default return the result from the first pass + return primaryResult; + } - if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { - filesWithChangedSetOfUnresolvedImports.push(path); - // reset log changes to avoid recording the same file multiple times - logChanges = false; - } - } - else { + function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: SourceFile, resolutionMode?: SourceFile["impliedNodeFormat"] | undefined): CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations { + return ts.resolveTypeReferenceDirective(typeReferenceDirectiveName, containingFile, options, host, redirectedReference, typeReferenceDirectiveResolutionCache, resolutionMode); + } + + interface ResolveNamesWithLocalCacheInput { + names: readonly string[] | readonly FileReference[]; + containingFile: string; + redirectedReference: ResolvedProjectReference | undefined; + cache: ESMap>; + perDirectoryCacheWithRedirects: CacheWithRedirects>; + loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => T; + getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName; + shouldRetryResolution: (t: T) => boolean; + reusedNames?: readonly string[]; + resolutionInfo?: ModuleResolutionInfo; + logChanges?: boolean; + containingSourceFile?: SourceFile; + containingSourceFileMode?: SourceFile["impliedNodeFormat"]; + } + function resolveNamesWithLocalCache({ + names, containingFile, redirectedReference, + cache, perDirectoryCacheWithRedirects, + loader, getResolutionWithResolvedFileName, + shouldRetryResolution, reusedNames, resolutionInfo, logChanges, containingSourceFile, containingSourceFileMode + }: ResolveNamesWithLocalCacheInput): (R | undefined)[] { + const path = resolutionHost.toPath(containingFile); + const resolutionsInFile = cache.get(path) || cache.set(path, createModeAwareCache()).get(path)!; + const dirPath = getDirectoryPath(path); + const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); + let perDirectoryResolution = perDirectoryCache.get(dirPath); + if (!perDirectoryResolution) { + perDirectoryResolution = createModeAwareCache(); + perDirectoryCache.set(dirPath, perDirectoryResolution); + } + const resolvedModules: (R | undefined)[] = []; + const compilerOptions = resolutionHost.getCompilationSettings(); + const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path); + + // All the resolutions in this file are invalidated if this file wasn't resolved using same redirect + const program = resolutionHost.getCurrentProgram(); + const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile); + const unmatchedRedirects = oldRedirect ? + !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path : + !!redirectedReference; + + const seenNamesInFile = createModeAwareCache(); + let i = 0; + for (const entry of containingSourceFile && resolutionInfo ? resolutionInfo.names : names) { + const name = !isString(entry) ? getResolutionName(entry) : entry; + // Imports supply a `containingSourceFile` but no `containingSourceFileMode` - it would be redundant + // they require calculating the mode for a given import from it's position in the resolution table, since a given + // import's syntax may override the file's default mode. + // Type references instead supply a `containingSourceFileMode` and a non-string entry which contains + // a default file mode override if applicable. + const mode = !isString(entry) ? + isStringLiteralLike(entry) ? + getModeForUsageLocation(containingSourceFile!, entry) : + getModeForFileReference(entry, containingSourceFileMode) : + containingSourceFile ? + getModeForResolutionAtIndex(containingSourceFile, i) : + undefined; + i++; + let resolution = resolutionsInFile.get(name, mode); + // Resolution is valid if it is present and not invalidated + if (!seenNamesInFile.has(name, mode) && + unmatchedRedirects || !resolution || resolution.isInvalidated || + // If the name is unresolved import that was invalidated, recalculate + (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) { + const existingResolution = resolution; + const resolutionInDirectory = perDirectoryResolution.get(name, mode); + if (resolutionInDirectory) { + resolution = resolutionInDirectory; const host = resolutionHost.getCompilerHost?.() || resolutionHost; - if (isTraceEnabled(compilerOptions, host) && !seenNamesInFile.has(name, mode)) { + if (isTraceEnabled(compilerOptions, host)) { const resolved = getResolutionWithResolvedFileName(resolution); trace( host, loader === resolveModuleName as unknown ? resolved?.resolvedFileName ? resolved.packagetId ? - Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : - Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 : - Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved : + Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4: + Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3: + Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_not_resolved : resolved?.resolvedFileName ? resolved.packagetId ? - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2 : - Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_not_resolved, + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4 : + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3 : + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_not_resolved, name, containingFile, + getDirectoryPath(containingFile), resolved?.resolvedFileName, resolved?.packagetId && packageIdToString(resolved.packagetId) ); } } - Debug.assert(resolution !== undefined && !resolution.isInvalidated); - seenNamesInFile.set(name, mode, true); - resolvedModules.push(getResolutionWithResolvedFileName(resolution)); - } - - if (containingSourceFile && resolutionInfo) { - resolutionInfo.reusedNames?.forEach(literal => seenNamesInFile.set(literal.text, getModeForUsageLocation(containingSourceFile, literal), true)); - reusedNames = undefined; - } - if (resolutionsInFile.size() !== seenNamesInFile.size()) { - // Stop watching and remove the unused name - resolutionsInFile.forEach((resolution, name, mode) => { - if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) { - stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName); - resolutionsInFile.delete(name, mode); + else { + resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference, containingSourceFile, mode); + perDirectoryResolution.set(name, mode, resolution); + if (resolutionHost.onDiscoveredSymlink && resolutionIsSymlink(resolution)) { + resolutionHost.onDiscoveredSymlink(); } - }); - } - - return resolvedModules; - - function resolutionIsEqualTo(oldResolution: T | undefined, newResolution: T | undefined): boolean { - if (oldResolution === newResolution) { - return true; } - if (!oldResolution || !newResolution) { - return false; + resolutionsInFile.set(name, mode, resolution); + watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution, path, getResolutionWithResolvedFileName); + if (existingResolution) { + stopWatchFailedLookupLocationOfResolution(existingResolution, path, getResolutionWithResolvedFileName); } - const oldResult = getResolutionWithResolvedFileName(oldResolution); - const newResult = getResolutionWithResolvedFileName(newResolution); - if (oldResult === newResult) { - return true; + + if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { + filesWithChangedSetOfUnresolvedImports.push(path); + // reset log changes to avoid recording the same file multiple times + logChanges = false; } - if (!oldResult || !newResult) { - return false; + } + else { + const host = resolutionHost.getCompilerHost?.() || resolutionHost; + if (isTraceEnabled(compilerOptions, host) && !seenNamesInFile.has(name, mode)) { + const resolved = getResolutionWithResolvedFileName(resolution); + trace( + host, + loader === resolveModuleName as unknown ? + resolved?.resolvedFileName ? + resolved.packagetId ? + Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : + Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 : + Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved : + resolved?.resolvedFileName ? + resolved.packagetId ? + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2 : + Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_not_resolved, + name, + containingFile, + resolved?.resolvedFileName, + resolved?.packagetId && packageIdToString(resolved.packagetId) + ); } - return oldResult.resolvedFileName === newResult.resolvedFileName; } + Debug.assert(resolution !== undefined && !resolution.isInvalidated); + seenNamesInFile.set(name, mode, true); + resolvedModules.push(getResolutionWithResolvedFileName(resolution)); } - function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[] { - return resolveNamesWithLocalCache({ - names: typeDirectiveNames, - containingFile, - redirectedReference, - cache: resolvedTypeReferenceDirectives, - perDirectoryCacheWithRedirects: perDirectoryResolvedTypeReferenceDirectives, - loader: resolveTypeReferenceDirective, - getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective, - shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined, - containingSourceFileMode: containingFileMode - }); + if (containingSourceFile && resolutionInfo) { + resolutionInfo.reusedNames?.forEach(literal => seenNamesInFile.set(literal.text, getModeForUsageLocation(containingSourceFile, literal), true)); + reusedNames = undefined; } - - function resolveModuleNames( - moduleNames: string[], - containingFile: string, - reusedNames: string[] | undefined, - redirectedReference?: ResolvedProjectReference, - containingSourceFile?: SourceFile, - resolutionInfo?: ModuleResolutionInfo - ): (ResolvedModuleFull | undefined)[] { - return resolveNamesWithLocalCache({ - names: moduleNames, - containingFile, - redirectedReference, - cache: resolvedModuleNames, - perDirectoryCacheWithRedirects: perDirectoryResolvedModuleNames, - loader: resolveModuleName, - getResolutionWithResolvedFileName: getResolvedModule, - shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension), - reusedNames, - resolutionInfo, - logChanges: logChangesWhenResolvingModule, - containingSourceFile, + if (resolutionsInFile.size() !== seenNamesInFile.size()) { + // Stop watching and remove the unused name + resolutionsInFile.forEach((resolution, name, mode) => { + if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) { + stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName); + resolutionsInFile.delete(name, mode); + } }); } - function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined { - const cache = resolvedModuleNames.get(resolutionHost.toPath(containingFile)); - if (!cache) return undefined; - return cache.get(moduleName, resolutionMode); - } + return resolvedModules; - function isNodeModulesAtTypesDirectory(dirPath: Path) { - return endsWith(dirPath, "/node_modules/@types"); + function resolutionIsEqualTo(oldResolution: T | undefined, newResolution: T | undefined): boolean { + if (oldResolution === newResolution) { + return true; + } + if (!oldResolution || !newResolution) { + return false; + } + const oldResult = getResolutionWithResolvedFileName(oldResolution); + const newResult = getResolutionWithResolvedFileName(newResolution); + if (oldResult === newResult) { + return true; + } + if (!oldResult || !newResult) { + return false; + } + return oldResult.resolvedFileName === newResult.resolvedFileName; } + } - function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch | undefined { - if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { - // Ensure failed look up is normalized path - failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()); - const failedLookupPathSplit = failedLookupLocationPath.split(directorySeparator); - const failedLookupSplit = failedLookupLocation.split(directorySeparator); - Debug.assert(failedLookupSplit.length === failedLookupPathSplit.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`); - if (failedLookupPathSplit.length > rootSplitLength + 1) { - // Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution - return { - dir: failedLookupSplit.slice(0, rootSplitLength + 1).join(directorySeparator), - dirPath: failedLookupPathSplit.slice(0, rootSplitLength + 1).join(directorySeparator) as Path - }; - } - else { - // Always watch root directory non recursively - return { - dir: rootDir!, - dirPath: rootPath, - nonRecursive: false - }; - } - } + function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[] { + return resolveNamesWithLocalCache({ + names: typeDirectiveNames, + containingFile, + redirectedReference, + cache: resolvedTypeReferenceDirectives, + perDirectoryCacheWithRedirects: perDirectoryResolvedTypeReferenceDirectives, + loader: resolveTypeReferenceDirective, + getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective, + shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined, + containingSourceFileMode: containingFileMode + }); + } - return getDirectoryToWatchFromFailedLookupLocationDirectory( - getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())), - getDirectoryPath(failedLookupLocationPath) - ); - } + function resolveModuleNames( + moduleNames: string[], + containingFile: string, + reusedNames: string[] | undefined, + redirectedReference?: ResolvedProjectReference, + containingSourceFile?: SourceFile, + resolutionInfo?: ModuleResolutionInfo + ): (ResolvedModuleFull | undefined)[] { + return resolveNamesWithLocalCache({ + names: moduleNames, + containingFile, + redirectedReference, + cache: resolvedModuleNames, + perDirectoryCacheWithRedirects: perDirectoryResolvedModuleNames, + loader: resolveModuleName, + getResolutionWithResolvedFileName: getResolvedModule, + shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension), + reusedNames, + resolutionInfo, + logChanges: logChangesWhenResolvingModule, + containingSourceFile, + }); + } - function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path): DirectoryOfFailedLookupWatch | undefined { - // If directory path contains node module, get the most parent node_modules directory for watching - while (pathContainsNodeModules(dirPath)) { - dir = getDirectoryPath(dir); - dirPath = getDirectoryPath(dirPath); - } + function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined { + const cache = resolvedModuleNames.get(resolutionHost.toPath(containingFile)); + if (!cache) return undefined; + return cache.get(moduleName, resolutionMode); + } - // If the directory is node_modules use it to watch, always watch it recursively - if (isNodeModulesDirectory(dirPath)) { - return canWatchDirectoryOrFile(getDirectoryPath(dirPath)) ? { dir, dirPath } : undefined; - } + function isNodeModulesAtTypesDirectory(dirPath: Path) { + return endsWith(dirPath, "/node_modules/@types"); + } - let nonRecursive = true; - // Use some ancestor of the root directory - let subDirectoryPath: Path | undefined, subDirectory: string | undefined; - if (rootPath !== undefined) { - while (!isInDirectoryPath(dirPath, rootPath)) { - const parentPath = getDirectoryPath(dirPath); - if (parentPath === dirPath) { - break; - } - nonRecursive = false; - subDirectoryPath = dirPath; - subDirectory = dir; - dirPath = parentPath; - dir = getDirectoryPath(dir); - } + function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch | undefined { + if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { + // Ensure failed look up is normalized path + failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()); + const failedLookupPathSplit = failedLookupLocationPath.split(directorySeparator); + const failedLookupSplit = failedLookupLocation.split(directorySeparator); + Debug.assert(failedLookupSplit.length === failedLookupPathSplit.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`); + if (failedLookupPathSplit.length > rootSplitLength + 1) { + // Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution + return { + dir: failedLookupSplit.slice(0, rootSplitLength + 1).join(directorySeparator), + dirPath: failedLookupPathSplit.slice(0, rootSplitLength + 1).join(directorySeparator) as Path + }; } + else { + // Always watch root directory non recursively + return { + dir: rootDir!, + dirPath: rootPath, + nonRecursive: false + }; + } + } - return canWatchDirectoryOrFile(dirPath) ? { dir: subDirectory || dir, dirPath: subDirectoryPath || dirPath, nonRecursive } : undefined; + return getDirectoryToWatchFromFailedLookupLocationDirectory( + getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())), + getDirectoryPath(failedLookupLocationPath) + ); + } + + function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path): DirectoryOfFailedLookupWatch | undefined { + // If directory path contains node module, get the most parent node_modules directory for watching + while (pathContainsNodeModules(dirPath)) { + dir = getDirectoryPath(dir); + dirPath = getDirectoryPath(dirPath); } - function isPathWithDefaultFailedLookupExtension(path: Path) { - return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); + // If the directory is node_modules use it to watch, always watch it recursively + if (isNodeModulesDirectory(dirPath)) { + return canWatchDirectoryOrFile(getDirectoryPath(dirPath)) ? { dir, dirPath } : undefined; } - function watchFailedLookupLocationsOfExternalModuleResolutions( - name: string, - resolution: T, - filePath: Path, - getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, - ) { - if (resolution.refCount) { - resolution.refCount++; - Debug.assertIsDefined(resolution.files); - } - else { - resolution.refCount = 1; - Debug.assert(length(resolution.files) === 0); // This resolution shouldnt be referenced by any file yet - if (isExternalModuleNameRelative(name)) { - watchFailedLookupLocationOfResolution(resolution); - } - else { - nonRelativeExternalModuleResolutions.add(name, resolution); - } - const resolved = getResolutionWithResolvedFileName(resolution); - if (resolved && resolved.resolvedFileName) { - resolvedFileToResolution.add(resolutionHost.toPath(resolved.resolvedFileName), resolution); + let nonRecursive = true; + // Use some ancestor of the root directory + let subDirectoryPath: Path | undefined, subDirectory: string | undefined; + if (rootPath !== undefined) { + while (!isInDirectoryPath(dirPath, rootPath)) { + const parentPath = getDirectoryPath(dirPath); + if (parentPath === dirPath) { + break; } + nonRecursive = false; + subDirectoryPath = dirPath; + subDirectory = dir; + dirPath = parentPath; + dir = getDirectoryPath(dir); } - (resolution.files || (resolution.files = [])).push(filePath); } - function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { - Debug.assert(!!resolution.refCount); + return canWatchDirectoryOrFile(dirPath) ? { dir: subDirectory || dir, dirPath: subDirectoryPath || dirPath, nonRecursive } : undefined; + } - const { failedLookupLocations, affectingLocations } = resolution; - if (!failedLookupLocations.length && !affectingLocations.length) return; - if (failedLookupLocations.length) resolutionsWithFailedLookups.push(resolution); + function isPathWithDefaultFailedLookupExtension(path: Path) { + return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); + } - let setAtRoot = false; - for (const failedLookupLocation of failedLookupLocations) { - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - if (toWatch) { - const { dir, dirPath, nonRecursive } = toWatch; - // If the failed lookup location path is not one of the supported extensions, - // store it in the custom path - if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { - const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; - customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); - } - if (dirPath === rootPath) { - Debug.assert(!nonRecursive); - setAtRoot = true; - } - else { - setDirectoryWatcher(dir, dirPath, nonRecursive); - } - } + function watchFailedLookupLocationsOfExternalModuleResolutions( + name: string, + resolution: T, + filePath: Path, + getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, + ) { + if (resolution.refCount) { + resolution.refCount++; + Debug.assertIsDefined(resolution.files); + } + else { + resolution.refCount = 1; + Debug.assert(length(resolution.files) === 0); // This resolution shouldnt be referenced by any file yet + if (isExternalModuleNameRelative(name)) { + watchFailedLookupLocationOfResolution(resolution); } - - if (setAtRoot) { - // This is always non recursive - setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217 + else { + nonRelativeExternalModuleResolutions.add(name, resolution); } - watchAffectingLocationsOfResolution(resolution, !failedLookupLocations.length); - } - - function watchAffectingLocationsOfResolution(resolution: ResolutionWithFailedLookupLocations, addToResolutionsWithOnlyAffectingLocations: boolean) { - Debug.assert(!!resolution.refCount); - const { affectingLocations } = resolution; - if (!affectingLocations.length) return; - if (addToResolutionsWithOnlyAffectingLocations) resolutionsWithOnlyAffectingLocations.push(resolution); - // Watch package json - for (const affectingLocation of affectingLocations) { - createFileWatcherOfAffectingLocation(affectingLocation, /*forResolution*/ true); + const resolved = getResolutionWithResolvedFileName(resolution); + if (resolved && resolved.resolvedFileName) { + resolvedFileToResolution.add(resolutionHost.toPath(resolved.resolvedFileName), resolution); } } + (resolution.files || (resolution.files = [])).push(filePath); + } - function createFileWatcherOfAffectingLocation(affectingLocation: string, forResolution: boolean) { - const fileWatcher = fileWatchesOfAffectingLocations.get(affectingLocation); - if (fileWatcher) { - if (forResolution) fileWatcher.resolutions++; - else fileWatcher.files++; - return; - } - let locationToWatch = affectingLocation; - if (resolutionHost.realpath) { - locationToWatch = resolutionHost.realpath(affectingLocation); - if (affectingLocation !== locationToWatch) { - const fileWatcher = fileWatchesOfAffectingLocations.get(locationToWatch); - if (fileWatcher) { - if (forResolution) fileWatcher.resolutions++; - else fileWatcher.files++; - fileWatcher.paths.add(affectingLocation); - fileWatchesOfAffectingLocations.set(affectingLocation, fileWatcher); - return; - } + function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { + Debug.assert(!!resolution.refCount); + + const { failedLookupLocations, affectingLocations } = resolution; + if (!failedLookupLocations.length && !affectingLocations.length) return; + if (failedLookupLocations.length) resolutionsWithFailedLookups.push(resolution); + + let setAtRoot = false; + for (const failedLookupLocation of failedLookupLocations) { + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + if (toWatch) { + const { dir, dirPath, nonRecursive } = toWatch; + // If the failed lookup location path is not one of the supported extensions, + // store it in the custom path + if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { + const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; + customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); + } + if (dirPath === rootPath) { + Debug.assert(!nonRecursive); + setAtRoot = true; + } + else { + setDirectoryWatcher(dir, dirPath, nonRecursive); } - } - const paths = new Set(); - paths.add(locationToWatch); - let actualWatcher = canWatchDirectoryOrFile(resolutionHost.toPath(locationToWatch)) ? - resolutionHost.watchAffectingFileLocation(locationToWatch, (fileName, eventKind) => { - cachedDirectoryStructureHost?.addOrDeleteFile(fileName, resolutionHost.toPath(locationToWatch), eventKind); - const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap(); - paths.forEach(path => { - if (watcher.resolutions) (affectingPathChecks ??= new Set()).add(path); - if (watcher.files) (affectingPathChecksForFile ??= new Set()).add(path); - packageJsonMap?.delete(resolutionHost.toPath(path)); - }); - resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations(); - }) : noopFileWatcher; - const watcher: FileWatcherOfAffectingLocation = { - watcher: actualWatcher !== noopFileWatcher ? { - close: () => { - actualWatcher.close(); - // Ensure when watching symlinked package.json, we can close the actual file watcher only once - actualWatcher = noopFileWatcher; - } - } : actualWatcher, - resolutions: forResolution ? 1 : 0, - files: forResolution ? 0 : 1, - paths, - }; - fileWatchesOfAffectingLocations.set(locationToWatch, watcher); - if (affectingLocation !== locationToWatch) { - fileWatchesOfAffectingLocations.set(affectingLocation, watcher); - paths.add(affectingLocation); } } - function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) { - const program = resolutionHost.getCurrentProgram(); - if (!program || !program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name)) { - resolutions.forEach(watchFailedLookupLocationOfResolution); - } - else { - resolutions.forEach(resolution => watchAffectingLocationsOfResolution(resolution, /*addToResolutionWithOnlyAffectingLocations*/ true)); - } + if (setAtRoot) { + // This is always non recursive + setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217 } + watchAffectingLocationsOfResolution(resolution, !failedLookupLocations.length); + } - function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) { - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); - if (dirWatcher) { - Debug.assert(!!nonRecursive === !!dirWatcher.nonRecursive); - dirWatcher.refCount++; - } - else { - directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath, nonRecursive), refCount: 1, nonRecursive }); - } + function watchAffectingLocationsOfResolution(resolution: ResolutionWithFailedLookupLocations, addToResolutionsWithOnlyAffectingLocations: boolean) { + Debug.assert(!!resolution.refCount); + const { affectingLocations } = resolution; + if (!affectingLocations.length) return; + if (addToResolutionsWithOnlyAffectingLocations) resolutionsWithOnlyAffectingLocations.push(resolution); + // Watch package json + for (const affectingLocation of affectingLocations) { + createFileWatcherOfAffectingLocation(affectingLocation, /*forResolution*/ true); } + } - function stopWatchFailedLookupLocationOfResolution( - resolution: T, - filePath: Path, - getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, - ) { - unorderedRemoveItem(Debug.checkDefined(resolution.files), filePath); - resolution.refCount!--; - if (resolution.refCount) { - return; - } - const resolved = getResolutionWithResolvedFileName(resolution); - if (resolved && resolved.resolvedFileName) { - resolvedFileToResolution.remove(resolutionHost.toPath(resolved.resolvedFileName), resolution); + function createFileWatcherOfAffectingLocation(affectingLocation: string, forResolution: boolean) { + const fileWatcher = fileWatchesOfAffectingLocations.get(affectingLocation); + if (fileWatcher) { + if (forResolution) fileWatcher.resolutions++; + else fileWatcher.files++; + return; + } + let locationToWatch = affectingLocation; + if (resolutionHost.realpath) { + locationToWatch = resolutionHost.realpath(affectingLocation); + if (affectingLocation !== locationToWatch) { + const fileWatcher = fileWatchesOfAffectingLocations.get(locationToWatch); + if (fileWatcher) { + if (forResolution) fileWatcher.resolutions++; + else fileWatcher.files++; + fileWatcher.paths.add(affectingLocation); + fileWatchesOfAffectingLocations.set(affectingLocation, fileWatcher); + return; + } } + } + const paths = new Set(); + paths.add(locationToWatch); + let actualWatcher = canWatchDirectoryOrFile(resolutionHost.toPath(locationToWatch)) ? + resolutionHost.watchAffectingFileLocation(locationToWatch, (fileName, eventKind) => { + cachedDirectoryStructureHost?.addOrDeleteFile(fileName, resolutionHost.toPath(locationToWatch), eventKind); + const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap(); + paths.forEach(path => { + if (watcher.resolutions) (affectingPathChecks ??= new Set()).add(path); + if (watcher.files) (affectingPathChecksForFile ??= new Set()).add(path); + packageJsonMap?.delete(resolutionHost.toPath(path)); + }); + resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations(); + }) : noopFileWatcher; + const watcher: FileWatcherOfAffectingLocation = { + watcher: actualWatcher !== noopFileWatcher ? { + close: () => { + actualWatcher.close(); + // Ensure when watching symlinked package.json, we can close the actual file watcher only once + actualWatcher = noopFileWatcher; + } + } : actualWatcher, + resolutions: forResolution ? 1 : 0, + files: forResolution ? 0 : 1, + paths, + }; + fileWatchesOfAffectingLocations.set(locationToWatch, watcher); + if (affectingLocation !== locationToWatch) { + fileWatchesOfAffectingLocations.set(affectingLocation, watcher); + paths.add(affectingLocation); + } + } - const { failedLookupLocations, affectingLocations } = resolution; - if (unorderedRemoveItem(resolutionsWithFailedLookups, resolution)) { - let removeAtRoot = false; - for (const failedLookupLocation of failedLookupLocations) { - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - if (toWatch) { - const { dirPath } = toWatch; - const refCount = customFailedLookupPaths.get(failedLookupLocationPath); - if (refCount) { - if (refCount === 1) { - customFailedLookupPaths.delete(failedLookupLocationPath); - } - else { - Debug.assert(refCount > 1); - customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); - } - } + function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) { + const program = resolutionHost.getCurrentProgram(); + if (!program || !program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name)) { + resolutions.forEach(watchFailedLookupLocationOfResolution); + } + else { + resolutions.forEach(resolution => watchAffectingLocationsOfResolution(resolution, /*addToResolutionWithOnlyAffectingLocations*/ true)); + } + } - if (dirPath === rootPath) { - removeAtRoot = true; + function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) { + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); + if (dirWatcher) { + Debug.assert(!!nonRecursive === !!dirWatcher.nonRecursive); + dirWatcher.refCount++; + } + else { + directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath, nonRecursive), refCount: 1, nonRecursive }); + } + } + + function stopWatchFailedLookupLocationOfResolution( + resolution: T, + filePath: Path, + getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, + ) { + unorderedRemoveItem(Debug.checkDefined(resolution.files), filePath); + resolution.refCount!--; + if (resolution.refCount) { + return; + } + const resolved = getResolutionWithResolvedFileName(resolution); + if (resolved && resolved.resolvedFileName) { + resolvedFileToResolution.remove(resolutionHost.toPath(resolved.resolvedFileName), resolution); + } + + const { failedLookupLocations, affectingLocations } = resolution; + if (unorderedRemoveItem(resolutionsWithFailedLookups, resolution)) { + let removeAtRoot = false; + for (const failedLookupLocation of failedLookupLocations) { + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + if (toWatch) { + const { dirPath } = toWatch; + const refCount = customFailedLookupPaths.get(failedLookupLocationPath); + if (refCount) { + if (refCount === 1) { + customFailedLookupPaths.delete(failedLookupLocationPath); } else { - removeDirectoryWatcher(dirPath); + Debug.assert(refCount > 1); + customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); } } + + if (dirPath === rootPath) { + removeAtRoot = true; + } + else { + removeDirectoryWatcher(dirPath); + } } - if (removeAtRoot) { - removeDirectoryWatcher(rootPath); - } - } - else if (affectingLocations.length) { - unorderedRemoveItem(resolutionsWithOnlyAffectingLocations, resolution); } - - for (const affectingLocation of affectingLocations) { - const watcher = fileWatchesOfAffectingLocations.get(affectingLocation)!; - watcher.resolutions--; + if (removeAtRoot) { + removeDirectoryWatcher(rootPath); } } - - function removeDirectoryWatcher(dirPath: string) { - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath)!; - // Do not close the watcher yet since it might be needed by other failed lookup locations. - dirWatcher.refCount--; + else if (affectingLocations.length) { + unorderedRemoveItem(resolutionsWithOnlyAffectingLocations, resolution); } - function createDirectoryWatcher(directory: string, dirPath: Path, nonRecursive: boolean | undefined) { - return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { - const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (cachedDirectoryStructureHost) { - // Since the file existence changed, update the sourceFiles cache - cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); - } - - scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath); - }, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive); + for (const affectingLocation of affectingLocations) { + const watcher = fileWatchesOfAffectingLocations.get(affectingLocation)!; + watcher.resolutions--; } + } + + function removeDirectoryWatcher(dirPath: string) { + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath)!; + // Do not close the watcher yet since it might be needed by other failed lookup locations. + dirWatcher.refCount--; + } - function removeResolutionsOfFileFromCache( - cache: ESMap>, - filePath: Path, - getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, - ) { - // Deleted file, stop watching failed lookups for all the resolutions in the file - const resolutions = cache.get(filePath); - if (resolutions) { - resolutions.forEach(resolution => stopWatchFailedLookupLocationOfResolution(resolution, filePath, getResolutionWithResolvedFileName)); - cache.delete(filePath); + function createDirectoryWatcher(directory: string, dirPath: Path, nonRecursive: boolean | undefined) { + return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { + const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); + if (cachedDirectoryStructureHost) { + // Since the file existence changed, update the sourceFiles cache + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } + + scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath); + }, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive); + } + + function removeResolutionsOfFileFromCache( + cache: ESMap>, + filePath: Path, + getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, + ) { + // Deleted file, stop watching failed lookups for all the resolutions in the file + const resolutions = cache.get(filePath); + if (resolutions) { + resolutions.forEach(resolution => stopWatchFailedLookupLocationOfResolution(resolution, filePath, getResolutionWithResolvedFileName)); + cache.delete(filePath); } + } - function removeResolutionsFromProjectReferenceRedirects(filePath: Path) { - if (!fileExtensionIs(filePath, Extension.Json)) return; + function removeResolutionsFromProjectReferenceRedirects(filePath: Path) { + if (!fileExtensionIs(filePath, Extension.Json)) return; - const program = resolutionHost.getCurrentProgram(); - if (!program) return; + const program = resolutionHost.getCurrentProgram(); + if (!program) return; - // If this file is input file for the referenced project, get it - const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath); - if (!resolvedProjectReference) return; + // If this file is input file for the referenced project, get it + const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath); + if (!resolvedProjectReference) return; - // filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution - resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f))); - } + // filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution + resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f))); + } - function removeResolutionsOfFile(filePath: Path) { - removeResolutionsOfFileFromCache(resolvedModuleNames, filePath, getResolvedModule); - removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath, getResolvedTypeReferenceDirective); - } + function removeResolutionsOfFile(filePath: Path) { + removeResolutionsOfFileFromCache(resolvedModuleNames, filePath, getResolvedModule); + removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath, getResolvedTypeReferenceDirective); + } - function invalidateResolutions(resolutions: ResolutionWithFailedLookupLocations[] | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean) { - if (!resolutions) return false; - let invalidated = false; - for (const resolution of resolutions) { - if (resolution.isInvalidated || !canInvalidate(resolution)) continue; - resolution.isInvalidated = invalidated = true; - for (const containingFilePath of Debug.checkDefined(resolution.files)) { - (filesWithInvalidatedResolutions ??= new Set()).add(containingFilePath); - // When its a file with inferred types resolution, invalidate type reference directive resolution - hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || endsWith(containingFilePath, inferredTypesContainingFile); - } + function invalidateResolutions(resolutions: ResolutionWithFailedLookupLocations[] | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean) { + if (!resolutions) return false; + let invalidated = false; + for (const resolution of resolutions) { + if (resolution.isInvalidated || !canInvalidate(resolution)) continue; + resolution.isInvalidated = invalidated = true; + for (const containingFilePath of Debug.checkDefined(resolution.files)) { + (filesWithInvalidatedResolutions ??= new Set()).add(containingFilePath); + // When its a file with inferred types resolution, invalidate type reference directive resolution + hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || endsWith(containingFilePath, inferredTypesContainingFile); } - return invalidated; } + return invalidated; + } - function invalidateResolutionOfFile(filePath: Path) { - removeResolutionsOfFile(filePath); - // Resolution is invalidated if the resulting file name is same as the deleted file path - const prevHasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; - if (invalidateResolutions(resolvedFileToResolution.get(filePath), returnTrue) && - hasChangedAutomaticTypeDirectiveNames && - !prevHasChangedAutomaticTypeDirectiveNames) { - resolutionHost.onChangedAutomaticTypeDirectiveNames(); - } + function invalidateResolutionOfFile(filePath: Path) { + removeResolutionsOfFile(filePath); + // Resolution is invalidated if the resulting file name is same as the deleted file path + const prevHasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; + if (invalidateResolutions(resolvedFileToResolution.get(filePath), returnTrue) && + hasChangedAutomaticTypeDirectiveNames && + !prevHasChangedAutomaticTypeDirectiveNames) { + resolutionHost.onChangedAutomaticTypeDirectiveNames(); } + } - function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ReadonlyESMap) { - Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined); - filesWithInvalidatedNonRelativeUnresolvedImports = filesMap; + function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ReadonlyESMap) { + Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined); + filesWithInvalidatedNonRelativeUnresolvedImports = filesMap; + } + + function scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) { + if (isCreatingWatchedDirectory) { + // Watching directory is created + // Invalidate any resolution has failed lookup in this directory + (isInDirectoryChecks ||= new Set()).add(fileOrDirectoryPath); } + else { + // If something to do with folder/file starting with "." in node_modules folder, skip it + const updatedPath = removeIgnoredPath(fileOrDirectoryPath); + if (!updatedPath) return false; + fileOrDirectoryPath = updatedPath; + + // prevent saving an open file from over-eagerly triggering invalidation + if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { + return false; + } - function scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) { - if (isCreatingWatchedDirectory) { - // Watching directory is created - // Invalidate any resolution has failed lookup in this directory - (isInDirectoryChecks ||= new Set()).add(fileOrDirectoryPath); + // 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); + if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) || + isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) { + // Invalidate any resolution from this directory + (failedLookupChecks ||= new Set()).add(fileOrDirectoryPath); + (startsWithPathChecks ||= new Set()).add(fileOrDirectoryPath); } else { - // If something to do with folder/file starting with "." in node_modules folder, skip it - const updatedPath = removeIgnoredPath(fileOrDirectoryPath); - if (!updatedPath) return false; - fileOrDirectoryPath = updatedPath; - - // prevent saving an open file from over-eagerly triggering invalidation - if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { + if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(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); - if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) || - isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) { - // Invalidate any resolution from this directory - (failedLookupChecks ||= new Set()).add(fileOrDirectoryPath); - (startsWithPathChecks ||= new Set()).add(fileOrDirectoryPath); - } - else { - if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) { - return false; - } - // Ignore emits from the program - if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) { - return false; - } - // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created - (failedLookupChecks ||= new Set()).add(fileOrDirectoryPath); - - // If the invalidated file is from a node_modules package, invalidate everything else - // in the package since we might not get notifications for other files in the package. - // This hardens our logic against unreliable file watchers. - const packagePath = parseNodeModuleFromPath(fileOrDirectoryPath); - if (packagePath) (startsWithPathChecks ||= new Set()).add(packagePath as Path); + // Ignore emits from the program + if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) { + return false; } + // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created + (failedLookupChecks ||= new Set()).add(fileOrDirectoryPath); + + // If the invalidated file is from a node_modules package, invalidate everything else + // in the package since we might not get notifications for other files in the package. + // This hardens our logic against unreliable file watchers. + const packagePath = parseNodeModuleFromPath(fileOrDirectoryPath); + if (packagePath) (startsWithPathChecks ||= new Set()).add(packagePath as Path); } - resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations(); } + resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations(); + } - function invalidateResolutionsOfFailedLookupLocations() { - let invalidated = false; - if (affectingPathChecksForFile) { - resolutionHost.getCurrentProgram()?.getSourceFiles().forEach(f => { - if (some(f.packageJsonLocations, location => affectingPathChecksForFile!.has(location))) { - (filesWithInvalidatedResolutions ??= new Set()).add(f.path); - invalidated = true; - } - }); - affectingPathChecksForFile = undefined; - } - - if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks && !affectingPathChecks) { - return invalidated; - } - - invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution) || invalidated; - const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap(); - if (packageJsonMap && (failedLookupChecks || startsWithPathChecks || isInDirectoryChecks)) { - packageJsonMap.forEach((_value, path) => isInvalidatedFailedLookup(path) ? packageJsonMap.delete(path) : undefined); - } - failedLookupChecks = undefined; - startsWithPathChecks = undefined; - isInDirectoryChecks = undefined; - invalidated = invalidateResolutions(resolutionsWithOnlyAffectingLocations, canInvalidatedFailedLookupResolutionWithAffectingLocation) || invalidated; - affectingPathChecks = undefined; - return invalidated; + function invalidateResolutionsOfFailedLookupLocations() { + let invalidated = false; + if (affectingPathChecksForFile) { + resolutionHost.getCurrentProgram()?.getSourceFiles().forEach(f => { + if (some(f.packageJsonLocations, location => affectingPathChecksForFile!.has(location))) { + (filesWithInvalidatedResolutions ??= new Set()).add(f.path); + invalidated = true; + } + }); + affectingPathChecksForFile = undefined; } - function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) { - if (canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution)) return true; - if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) return false; - return resolution.failedLookupLocations.some(location => isInvalidatedFailedLookup(resolutionHost.toPath(location))); + if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks && !affectingPathChecks) { + return invalidated; } - function isInvalidatedFailedLookup(locationPath: Path) { - return failedLookupChecks?.has(locationPath) || - firstDefinedIterator(startsWithPathChecks?.keys() || emptyIterator, fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath) ? true : undefined) || - firstDefinedIterator(isInDirectoryChecks?.keys() || emptyIterator, fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath) ? true : undefined); + invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution) || invalidated; + const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap(); + if (packageJsonMap && (failedLookupChecks || startsWithPathChecks || isInDirectoryChecks)) { + packageJsonMap.forEach((_value, path) => isInvalidatedFailedLookup(path) ? packageJsonMap.delete(path) : undefined); } + failedLookupChecks = undefined; + startsWithPathChecks = undefined; + isInDirectoryChecks = undefined; + invalidated = invalidateResolutions(resolutionsWithOnlyAffectingLocations, canInvalidatedFailedLookupResolutionWithAffectingLocation) || invalidated; + affectingPathChecks = undefined; + return invalidated; + } - function canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution: ResolutionWithFailedLookupLocations) { - return !!affectingPathChecks && resolution.affectingLocations.some(location => affectingPathChecks!.has(location)); - } + function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) { + if (canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution)) return true; + if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) return false; + return resolution.failedLookupLocations.some(location => isInvalidatedFailedLookup(resolutionHost.toPath(location))); + } - function closeTypeRootsWatch() { - clearMap(typeRootsWatches, closeFileWatcher); - } + function isInvalidatedFailedLookup(locationPath: Path) { + return failedLookupChecks?.has(locationPath) || + firstDefinedIterator(startsWithPathChecks?.keys() || emptyIterator, fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath) ? true : undefined) || + firstDefinedIterator(isInDirectoryChecks?.keys() || emptyIterator, fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath) ? true : undefined); + } - function getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot: string, typeRootPath: Path): Path | undefined { - if (isInDirectoryPath(rootPath, typeRootPath)) { - return rootPath; - } - const toWatch = getDirectoryToWatchFromFailedLookupLocationDirectory(typeRoot, typeRootPath); - return toWatch && directoryWatchesOfFailedLookups.has(toWatch.dirPath) ? toWatch.dirPath : undefined; - } + function canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution: ResolutionWithFailedLookupLocations) { + return !!affectingPathChecks && resolution.affectingLocations.some(location => affectingPathChecks!.has(location)); + } - function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher { - // Create new watch and recursive info - return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { - const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (cachedDirectoryStructureHost) { - // Since the file existence changed, update the sourceFiles cache - cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); - } + function closeTypeRootsWatch() { + clearMap(typeRootsWatches, closeFileWatcher); + } - // For now just recompile - // We could potentially store more data here about whether it was/would be really be used or not - // and with that determine to trigger compilation but for now this is enough - hasChangedAutomaticTypeDirectiveNames = true; - resolutionHost.onChangedAutomaticTypeDirectiveNames(); - - // Since directory watchers invoked are flaky, the failed lookup location events might not be triggered - // So handle to failed lookup locations here as well to ensure we are invalidating resolutions - const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath); - if (dirPath) { - scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath); - } - }, WatchDirectoryFlags.Recursive); + function getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot: string, typeRootPath: Path): Path | undefined { + if (isInDirectoryPath(rootPath, typeRootPath)) { + return rootPath; } + const toWatch = getDirectoryToWatchFromFailedLookupLocationDirectory(typeRoot, typeRootPath); + return toWatch && directoryWatchesOfFailedLookups.has(toWatch.dirPath) ? toWatch.dirPath : undefined; + } - /** - * Watches the types that would get added as part of getAutomaticTypeDirectiveNames - * To be called when compiler options change - */ - function updateTypeRootsWatch() { - const options = resolutionHost.getCompilationSettings(); - if (options.types) { - // No need to do any watch since resolution cache is going to handle the failed lookups - // for the types added by this - closeTypeRootsWatch(); - return; + function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher { + // Create new watch and recursive info + return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { + const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); + if (cachedDirectoryStructureHost) { + // Since the file existence changed, update the sourceFiles cache + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } - // we need to assume the directories exist to ensure that we can get all the type root directories that get included - // But filter directories that are at root level to say directory doesnt exist, so that we arent watching them - const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory }); - if (typeRoots) { - mutateMap( - typeRootsWatches, - arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)), - { - createNewValue: createTypeRootsWatch, - onDeleteValue: closeFileWatcher - } - ); - } - else { - closeTypeRootsWatch(); + // For now just recompile + // We could potentially store more data here about whether it was/would be really be used or not + // and with that determine to trigger compilation but for now this is enough + hasChangedAutomaticTypeDirectiveNames = true; + resolutionHost.onChangedAutomaticTypeDirectiveNames(); + + // Since directory watchers invoked are flaky, the failed lookup location events might not be triggered + // So handle to failed lookup locations here as well to ensure we are invalidating resolutions + const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath); + if (dirPath) { + scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath); } + }, WatchDirectoryFlags.Recursive); + } + + /** + * Watches the types that would get added as part of getAutomaticTypeDirectiveNames + * To be called when compiler options change + */ + function updateTypeRootsWatch() { + const options = resolutionHost.getCompilationSettings(); + if (options.types) { + // No need to do any watch since resolution cache is going to handle the failed lookups + // for the types added by this + closeTypeRootsWatch(); + return; } - /** - * Use this function to return if directory exists to get type roots to watch - * If we return directory exists then only the paths will be added to type roots - * Hence return true for all directories except root directories which are filtered from watching - */ - function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) { - const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory)); - const dirPath = resolutionHost.toPath(dir); - return dirPath === rootPath || canWatchDirectoryOrFile(dirPath); + // we need to assume the directories exist to ensure that we can get all the type root directories that get included + // But filter directories that are at root level to say directory doesnt exist, so that we arent watching them + const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory }); + if (typeRoots) { + mutateMap( + typeRootsWatches, + arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)), + { + createNewValue: createTypeRootsWatch, + onDeleteValue: closeFileWatcher + } + ); + } + else { + closeTypeRootsWatch(); } } - function resolutionIsSymlink(resolution: ResolutionWithFailedLookupLocations) { - return !!( - (resolution as ResolvedModuleWithFailedLookupLocations).resolvedModule?.originalPath || - (resolution as ResolvedTypeReferenceDirectiveWithFailedLookupLocations).resolvedTypeReferenceDirective?.originalPath - ); + /** + * Use this function to return if directory exists to get type roots to watch + * If we return directory exists then only the paths will be added to type roots + * Hence return true for all directories except root directories which are filtered from watching + */ + function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) { + const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory)); + const dirPath = resolutionHost.toPath(dir); + return dirPath === rootPath || canWatchDirectoryOrFile(dirPath); } } + +function resolutionIsSymlink(resolution: ResolutionWithFailedLookupLocations) { + return !!( + (resolution as ResolvedModuleWithFailedLookupLocations).resolvedModule?.originalPath || + (resolution as ResolvedTypeReferenceDirectiveWithFailedLookupLocations).resolvedTypeReferenceDirective?.originalPath + ); +} diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index a6053f71ff633..ee1439635cd78 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1,611 +1,817 @@ -namespace ts { - export type ErrorCallback = (message: DiagnosticMessage, length: number) => void; - - /* @internal */ - export function tokenIsIdentifierOrKeyword(token: SyntaxKind): boolean { - return token >= SyntaxKind.Identifier; - } - - /* @internal */ - export function tokenIsIdentifierOrKeywordOrGreaterThan(token: SyntaxKind): boolean { - return token === SyntaxKind.GreaterThanToken || tokenIsIdentifierOrKeyword(token); - } - - export interface Scanner { - getStartPos(): number; - getToken(): SyntaxKind; - getTextPos(): number; - getTokenPos(): number; - getTokenText(): string; - getTokenValue(): string; - hasUnicodeEscape(): boolean; - hasExtendedUnicodeEscape(): boolean; - hasPrecedingLineBreak(): boolean; - /* @internal */ - hasPrecedingJSDocComment(): boolean; - isIdentifier(): boolean; - isReservedWord(): boolean; - isUnterminated(): boolean; - /* @internal */ - getNumericLiteralFlags(): TokenFlags; - /* @internal */ - getCommentDirectives(): CommentDirective[] | undefined; - /* @internal */ - getTokenFlags(): TokenFlags; - reScanGreaterToken(): SyntaxKind; - reScanSlashToken(): SyntaxKind; - reScanAsteriskEqualsToken(): SyntaxKind; - reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind; - reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind; - scanJsxIdentifier(): SyntaxKind; - scanJsxAttributeValue(): SyntaxKind; - reScanJsxAttributeValue(): SyntaxKind; - reScanJsxToken(allowMultilineJsxText?: boolean): JsxTokenSyntaxKind; - reScanLessThanToken(): SyntaxKind; - reScanHashToken(): SyntaxKind; - reScanQuestionToken(): SyntaxKind; - reScanInvalidIdentifier(): SyntaxKind; - scanJsxToken(): JsxTokenSyntaxKind; - scanJsDocToken(): JSDocSyntaxKind; - scan(): SyntaxKind; - - getText(): string; - /* @internal */ - clearCommentDirectives(): void; - // Sets the text for the scanner to scan. An optional subrange starting point and length - // can be provided to have the scanner only scan a portion of the text. - setText(text: string | undefined, start?: number, length?: number): void; - setOnError(onError: ErrorCallback | undefined): void; - setScriptTarget(scriptTarget: ScriptTarget): void; - setLanguageVariant(variant: LanguageVariant): void; - setTextPos(textPos: number): void; - /* @internal */ - setInJSDocType(inType: boolean): void; - // Invokes the provided callback then unconditionally restores the scanner to the state it - // was in immediately prior to invoking the callback. The result of invoking the callback - // is returned from this function. - lookAhead(callback: () => T): T; - - // Invokes the callback with the scanner set to scan the specified range. When the callback - // returns, the scanner is restored to the state it was in before scanRange was called. - scanRange(start: number, length: number, callback: () => T): T; - - // Invokes the provided callback. If the callback returns something falsy, then it restores - // the scanner to the state it was in immediately prior to invoking the callback. If the - // callback returns something truthy, then the scanner state is not rolled back. The result - // of invoking the callback is returned from this function. - tryScan(callback: () => T): T; - } - - /** @internal */ - export const textToKeywordObj: MapLike = { - abstract: SyntaxKind.AbstractKeyword, - accessor: SyntaxKind.AccessorKeyword, - any: SyntaxKind.AnyKeyword, - as: SyntaxKind.AsKeyword, - asserts: SyntaxKind.AssertsKeyword, - assert: SyntaxKind.AssertKeyword, - bigint: SyntaxKind.BigIntKeyword, - boolean: SyntaxKind.BooleanKeyword, - break: SyntaxKind.BreakKeyword, - case: SyntaxKind.CaseKeyword, - catch: SyntaxKind.CatchKeyword, - class: SyntaxKind.ClassKeyword, - continue: SyntaxKind.ContinueKeyword, - const: SyntaxKind.ConstKeyword, - ["" + "constructor"]: SyntaxKind.ConstructorKeyword, - debugger: SyntaxKind.DebuggerKeyword, - declare: SyntaxKind.DeclareKeyword, - default: SyntaxKind.DefaultKeyword, - delete: SyntaxKind.DeleteKeyword, - do: SyntaxKind.DoKeyword, - else: SyntaxKind.ElseKeyword, - enum: SyntaxKind.EnumKeyword, - export: SyntaxKind.ExportKeyword, - extends: SyntaxKind.ExtendsKeyword, - false: SyntaxKind.FalseKeyword, - finally: SyntaxKind.FinallyKeyword, - for: SyntaxKind.ForKeyword, - from: SyntaxKind.FromKeyword, - function: SyntaxKind.FunctionKeyword, - get: SyntaxKind.GetKeyword, - if: SyntaxKind.IfKeyword, - implements: SyntaxKind.ImplementsKeyword, - import: SyntaxKind.ImportKeyword, - in: SyntaxKind.InKeyword, - infer: SyntaxKind.InferKeyword, - instanceof: SyntaxKind.InstanceOfKeyword, - interface: SyntaxKind.InterfaceKeyword, - intrinsic: SyntaxKind.IntrinsicKeyword, - is: SyntaxKind.IsKeyword, - keyof: SyntaxKind.KeyOfKeyword, - let: SyntaxKind.LetKeyword, - module: SyntaxKind.ModuleKeyword, - namespace: SyntaxKind.NamespaceKeyword, - never: SyntaxKind.NeverKeyword, - new: SyntaxKind.NewKeyword, - null: SyntaxKind.NullKeyword, - number: SyntaxKind.NumberKeyword, - object: SyntaxKind.ObjectKeyword, - package: SyntaxKind.PackageKeyword, - private: SyntaxKind.PrivateKeyword, - protected: SyntaxKind.ProtectedKeyword, - public: SyntaxKind.PublicKeyword, - override: SyntaxKind.OverrideKeyword, - out: SyntaxKind.OutKeyword, - readonly: SyntaxKind.ReadonlyKeyword, - require: SyntaxKind.RequireKeyword, - global: SyntaxKind.GlobalKeyword, - return: SyntaxKind.ReturnKeyword, - satisfies: SyntaxKind.SatisfiesKeyword, - set: SyntaxKind.SetKeyword, - static: SyntaxKind.StaticKeyword, - string: SyntaxKind.StringKeyword, - super: SyntaxKind.SuperKeyword, - switch: SyntaxKind.SwitchKeyword, - symbol: SyntaxKind.SymbolKeyword, - this: SyntaxKind.ThisKeyword, - throw: SyntaxKind.ThrowKeyword, - true: SyntaxKind.TrueKeyword, - try: SyntaxKind.TryKeyword, - type: SyntaxKind.TypeKeyword, - typeof: SyntaxKind.TypeOfKeyword, - undefined: SyntaxKind.UndefinedKeyword, - unique: SyntaxKind.UniqueKeyword, - unknown: SyntaxKind.UnknownKeyword, - var: SyntaxKind.VarKeyword, - void: SyntaxKind.VoidKeyword, - while: SyntaxKind.WhileKeyword, - with: SyntaxKind.WithKeyword, - yield: SyntaxKind.YieldKeyword, - async: SyntaxKind.AsyncKeyword, - await: SyntaxKind.AwaitKeyword, - of: SyntaxKind.OfKeyword, - }; +import { + append, arraysEqual, binarySearch, CharacterCodes, CommentDirective, CommentDirectiveType, CommentKind, + CommentRange, compareValues, Debug, DiagnosticMessage, Diagnostics, ESMap, getEntries, identity, JSDocSyntaxKind, + JsxTokenSyntaxKind, KeywordSyntaxKind, LanguageVariant, LineAndCharacter, Map, MapLike, parsePseudoBigInt, + positionIsSynthesized, ScriptTarget, SourceFileLike, SyntaxKind, TokenFlags, trimStringStart, +} from "./_namespaces/ts"; + +export type ErrorCallback = (message: DiagnosticMessage, length: number) => void; + +/** @internal */ +export function tokenIsIdentifierOrKeyword(token: SyntaxKind): boolean { + return token >= SyntaxKind.Identifier; +} - const textToKeyword = new Map(getEntries(textToKeywordObj)); - - const textToToken = new Map(getEntries({ - ...textToKeywordObj, - "{": SyntaxKind.OpenBraceToken, - "}": SyntaxKind.CloseBraceToken, - "(": SyntaxKind.OpenParenToken, - ")": SyntaxKind.CloseParenToken, - "[": SyntaxKind.OpenBracketToken, - "]": SyntaxKind.CloseBracketToken, - ".": SyntaxKind.DotToken, - "...": SyntaxKind.DotDotDotToken, - ";": SyntaxKind.SemicolonToken, - ",": SyntaxKind.CommaToken, - "<": SyntaxKind.LessThanToken, - ">": SyntaxKind.GreaterThanToken, - "<=": SyntaxKind.LessThanEqualsToken, - ">=": SyntaxKind.GreaterThanEqualsToken, - "==": SyntaxKind.EqualsEqualsToken, - "!=": SyntaxKind.ExclamationEqualsToken, - "===": SyntaxKind.EqualsEqualsEqualsToken, - "!==": SyntaxKind.ExclamationEqualsEqualsToken, - "=>": SyntaxKind.EqualsGreaterThanToken, - "+": SyntaxKind.PlusToken, - "-": SyntaxKind.MinusToken, - "**": SyntaxKind.AsteriskAsteriskToken, - "*": SyntaxKind.AsteriskToken, - "/": SyntaxKind.SlashToken, - "%": SyntaxKind.PercentToken, - "++": SyntaxKind.PlusPlusToken, - "--": SyntaxKind.MinusMinusToken, - "<<": SyntaxKind.LessThanLessThanToken, - ">": SyntaxKind.GreaterThanGreaterThanToken, - ">>>": SyntaxKind.GreaterThanGreaterThanGreaterThanToken, - "&": SyntaxKind.AmpersandToken, - "|": SyntaxKind.BarToken, - "^": SyntaxKind.CaretToken, - "!": SyntaxKind.ExclamationToken, - "~": SyntaxKind.TildeToken, - "&&": SyntaxKind.AmpersandAmpersandToken, - "||": SyntaxKind.BarBarToken, - "?": SyntaxKind.QuestionToken, - "??": SyntaxKind.QuestionQuestionToken, - "?.": SyntaxKind.QuestionDotToken, - ":": SyntaxKind.ColonToken, - "=": SyntaxKind.EqualsToken, - "+=": SyntaxKind.PlusEqualsToken, - "-=": SyntaxKind.MinusEqualsToken, - "*=": SyntaxKind.AsteriskEqualsToken, - "**=": SyntaxKind.AsteriskAsteriskEqualsToken, - "/=": SyntaxKind.SlashEqualsToken, - "%=": SyntaxKind.PercentEqualsToken, - "<<=": SyntaxKind.LessThanLessThanEqualsToken, - ">>=": SyntaxKind.GreaterThanGreaterThanEqualsToken, - ">>>=": SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, - "&=": SyntaxKind.AmpersandEqualsToken, - "|=": SyntaxKind.BarEqualsToken, - "^=": SyntaxKind.CaretEqualsToken, - "||=": SyntaxKind.BarBarEqualsToken, - "&&=": SyntaxKind.AmpersandAmpersandEqualsToken, - "??=": SyntaxKind.QuestionQuestionEqualsToken, - "@": SyntaxKind.AtToken, - "#": SyntaxKind.HashToken, - "`": SyntaxKind.BacktickToken, - })); - - /* - As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers - IdentifierStart :: - Can contain Unicode 3.0.0 categories: - Uppercase letter (Lu), - Lowercase letter (Ll), - Titlecase letter (Lt), - Modifier letter (Lm), - Other letter (Lo), or - Letter number (Nl). - IdentifierPart :: = - Can contain IdentifierStart + Unicode 3.0.0 categories: - Non-spacing mark (Mn), - Combining spacing mark (Mc), - Decimal number (Nd), or - Connector punctuation (Pc). - - Codepoint ranges for ES3 Identifiers are extracted from the Unicode 3.0.0 specification at: - http://www.unicode.org/Public/3.0-Update/UnicodeData-3.0.0.txt - */ - const unicodeES3IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1610, 1649, 1747, 1749, 1749, 1765, 1766, 1786, 1788, 1808, 1808, 1810, 1836, 1920, 1957, 2309, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2784, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3294, 3294, 3296, 3297, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3424, 3425, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805, 3840, 3840, 3904, 3911, 3913, 3946, 3976, 3979, 4096, 4129, 4131, 4135, 4137, 4138, 4176, 4181, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6067, 6176, 6263, 6272, 6312, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8319, 8319, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12346, 12353, 12436, 12445, 12446, 12449, 12538, 12540, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65138, 65140, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; - const unicodeES3IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 768, 846, 864, 866, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1155, 1158, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1441, 1443, 1465, 1467, 1469, 1471, 1471, 1473, 1474, 1476, 1476, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1621, 1632, 1641, 1648, 1747, 1749, 1756, 1759, 1768, 1770, 1773, 1776, 1788, 1808, 1836, 1840, 1866, 1920, 1968, 2305, 2307, 2309, 2361, 2364, 2381, 2384, 2388, 2392, 2403, 2406, 2415, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2492, 2494, 2500, 2503, 2504, 2507, 2509, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2562, 2562, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2649, 2652, 2654, 2654, 2662, 2676, 2689, 2691, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2784, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2876, 2883, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2913, 2918, 2927, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3031, 3031, 3047, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3134, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3168, 3169, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3262, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3297, 3302, 3311, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3390, 3395, 3398, 3400, 3402, 3405, 3415, 3415, 3424, 3425, 3430, 3439, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3805, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3946, 3953, 3972, 3974, 3979, 3984, 3991, 3993, 4028, 4038, 4038, 4096, 4129, 4131, 4135, 4137, 4138, 4140, 4146, 4150, 4153, 4160, 4169, 4176, 4185, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 4969, 4977, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6099, 6112, 6121, 6160, 6169, 6176, 6263, 6272, 6313, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8319, 8319, 8400, 8412, 8417, 8417, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12346, 12353, 12436, 12441, 12442, 12445, 12446, 12449, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65056, 65059, 65075, 65076, 65101, 65103, 65136, 65138, 65140, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65381, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; - - /* - As per ECMAScript Language Specification 5th Edition, Section 7.6: ISyntaxToken Names and Identifiers - IdentifierStart :: - Can contain Unicode 6.2 categories: - Uppercase letter (Lu), - Lowercase letter (Ll), - Titlecase letter (Lt), - Modifier letter (Lm), - Other letter (Lo), or - Letter number (Nl). - IdentifierPart :: - Can contain IdentifierStart + Unicode 6.2 categories: - Non-spacing mark (Mn), - Combining spacing mark (Mc), - Decimal number (Nd), - Connector punctuation (Pc), - , or - . - - Codepoint ranges for ES5 Identifiers are extracted from the Unicode 6.2 specification at: - http://www.unicode.org/Public/6.2.0/ucd/UnicodeData.txt - */ - const unicodeES5IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2208, 2208, 2210, 2220, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516, 6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409, 7413, 7414, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; - const unicodeES5IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1520, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2048, 2093, 2112, 2139, 2208, 2208, 2210, 2220, 2276, 2302, 2304, 2403, 2406, 2415, 2417, 2423, 2425, 2431, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3161, 3168, 3171, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3396, 3398, 3400, 3402, 3406, 3415, 3415, 3424, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6263, 6272, 6314, 6320, 6389, 6400, 6428, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6617, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7376, 7378, 7380, 7414, 7424, 7654, 7676, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8204, 8205, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 11823, 11823, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12442, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42647, 42655, 42737, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43047, 43072, 43123, 43136, 43204, 43216, 43225, 43232, 43255, 43259, 43259, 43264, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43643, 43648, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65062, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; +/** @internal */ +export function tokenIsIdentifierOrKeywordOrGreaterThan(token: SyntaxKind): boolean { + return token === SyntaxKind.GreaterThanToken || tokenIsIdentifierOrKeyword(token); +} - /** - * Generated by scripts/regenerate-unicode-identifier-parts.js on node v12.4.0 with unicode 12.1 - * based on http://www.unicode.org/reports/tr31/ and https://www.ecma-international.org/ecma-262/6.0/#sec-names-and-keywords - * unicodeESNextIdentifierStart corresponds to the ID_Start and Other_ID_Start property, and - * unicodeESNextIdentifierPart corresponds to ID_Continue, Other_ID_Continue, plus ID_Start and Other_ID_Start - */ - const unicodeESNextIdentifierStart = [65, 90, 97, 122, 170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 895, 895, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1488, 1514, 1519, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2144, 2154, 2208, 2228, 2230, 2237, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2432, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2556, 2556, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2809, 2809, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3133, 3160, 3162, 3168, 3169, 3200, 3200, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3412, 3414, 3423, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6264, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6430, 6480, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7401, 7404, 7406, 7411, 7413, 7414, 7418, 7418, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12443, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42653, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43261, 43262, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43488, 43492, 43494, 43503, 43514, 43518, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43646, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66176, 66204, 66208, 66256, 66304, 66335, 66349, 66378, 66384, 66421, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68096, 68112, 68115, 68117, 68119, 68121, 68149, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68324, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68899, 69376, 69404, 69415, 69415, 69424, 69445, 69600, 69622, 69635, 69687, 69763, 69807, 69840, 69864, 69891, 69926, 69956, 69956, 69968, 70002, 70006, 70006, 70019, 70066, 70081, 70084, 70106, 70106, 70108, 70108, 70144, 70161, 70163, 70187, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70366, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70461, 70461, 70480, 70480, 70493, 70497, 70656, 70708, 70727, 70730, 70751, 70751, 70784, 70831, 70852, 70853, 70855, 70855, 71040, 71086, 71128, 71131, 71168, 71215, 71236, 71236, 71296, 71338, 71352, 71352, 71424, 71450, 71680, 71723, 71840, 71903, 71935, 71935, 72096, 72103, 72106, 72144, 72161, 72161, 72163, 72163, 72192, 72192, 72203, 72242, 72250, 72250, 72272, 72272, 72284, 72329, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72750, 72768, 72768, 72818, 72847, 72960, 72966, 72968, 72969, 72971, 73008, 73030, 73030, 73056, 73061, 73063, 73064, 73066, 73097, 73112, 73112, 73440, 73458, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92880, 92909, 92928, 92975, 92992, 92995, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94032, 94032, 94099, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 123136, 123180, 123191, 123197, 123214, 123214, 123584, 123627, 124928, 125124, 125184, 125251, 125259, 125259, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101]; - const unicodeESNextIdentifierPart = [48, 57, 65, 90, 95, 95, 97, 122, 170, 170, 181, 181, 183, 183, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 895, 895, 902, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1519, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2045, 2045, 2048, 2093, 2112, 2139, 2144, 2154, 2208, 2228, 2230, 2237, 2259, 2273, 2275, 2403, 2406, 2415, 2417, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2556, 2556, 2558, 2558, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2809, 2815, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3072, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3162, 3168, 3171, 3174, 3183, 3200, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3328, 3331, 3333, 3340, 3342, 3344, 3346, 3396, 3398, 3400, 3402, 3406, 3412, 3415, 3423, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3558, 3567, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4969, 4977, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6264, 6272, 6314, 6320, 6389, 6400, 6430, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6618, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6832, 6845, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7376, 7378, 7380, 7418, 7424, 7673, 7675, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42737, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43047, 43072, 43123, 43136, 43205, 43216, 43225, 43232, 43255, 43259, 43259, 43261, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43488, 43518, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65071, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66045, 66045, 66176, 66204, 66208, 66256, 66272, 66272, 66304, 66335, 66349, 66378, 66384, 66426, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66720, 66729, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68099, 68101, 68102, 68108, 68115, 68117, 68119, 68121, 68149, 68152, 68154, 68159, 68159, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68326, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68903, 68912, 68921, 69376, 69404, 69415, 69415, 69424, 69456, 69600, 69622, 69632, 69702, 69734, 69743, 69759, 69818, 69840, 69864, 69872, 69881, 69888, 69940, 69942, 69951, 69956, 69958, 69968, 70003, 70006, 70006, 70016, 70084, 70089, 70092, 70096, 70106, 70108, 70108, 70144, 70161, 70163, 70199, 70206, 70206, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70378, 70384, 70393, 70400, 70403, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70459, 70468, 70471, 70472, 70475, 70477, 70480, 70480, 70487, 70487, 70493, 70499, 70502, 70508, 70512, 70516, 70656, 70730, 70736, 70745, 70750, 70751, 70784, 70853, 70855, 70855, 70864, 70873, 71040, 71093, 71096, 71104, 71128, 71133, 71168, 71232, 71236, 71236, 71248, 71257, 71296, 71352, 71360, 71369, 71424, 71450, 71453, 71467, 71472, 71481, 71680, 71738, 71840, 71913, 71935, 71935, 72096, 72103, 72106, 72151, 72154, 72161, 72163, 72164, 72192, 72254, 72263, 72263, 72272, 72345, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72758, 72760, 72768, 72784, 72793, 72818, 72847, 72850, 72871, 72873, 72886, 72960, 72966, 72968, 72969, 72971, 73014, 73018, 73018, 73020, 73021, 73023, 73031, 73040, 73049, 73056, 73061, 73063, 73064, 73066, 73102, 73104, 73105, 73107, 73112, 73120, 73129, 73440, 73462, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92768, 92777, 92880, 92909, 92912, 92916, 92928, 92982, 92992, 92995, 93008, 93017, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94031, 94087, 94095, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 113821, 113822, 119141, 119145, 119149, 119154, 119163, 119170, 119173, 119179, 119210, 119213, 119362, 119364, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 120782, 120831, 121344, 121398, 121403, 121452, 121461, 121461, 121476, 121476, 121499, 121503, 121505, 121519, 122880, 122886, 122888, 122904, 122907, 122913, 122915, 122916, 122918, 122922, 123136, 123180, 123184, 123197, 123200, 123209, 123214, 123214, 123584, 123641, 124928, 125124, 125136, 125142, 125184, 125259, 125264, 125273, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101, 917760, 917999]; +export interface Scanner { + getStartPos(): number; + getToken(): SyntaxKind; + getTextPos(): number; + getTokenPos(): number; + getTokenText(): string; + getTokenValue(): string; + hasUnicodeEscape(): boolean; + hasExtendedUnicodeEscape(): boolean; + hasPrecedingLineBreak(): boolean; + /** @internal */ + hasPrecedingJSDocComment(): boolean; + isIdentifier(): boolean; + isReservedWord(): boolean; + isUnterminated(): boolean; + /** @internal */ + getNumericLiteralFlags(): TokenFlags; + /** @internal */ + getCommentDirectives(): CommentDirective[] | undefined; + /** @internal */ + getTokenFlags(): TokenFlags; + reScanGreaterToken(): SyntaxKind; + reScanSlashToken(): SyntaxKind; + reScanAsteriskEqualsToken(): SyntaxKind; + reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind; + reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind; + scanJsxIdentifier(): SyntaxKind; + scanJsxAttributeValue(): SyntaxKind; + reScanJsxAttributeValue(): SyntaxKind; + reScanJsxToken(allowMultilineJsxText?: boolean): JsxTokenSyntaxKind; + reScanLessThanToken(): SyntaxKind; + reScanHashToken(): SyntaxKind; + reScanQuestionToken(): SyntaxKind; + reScanInvalidIdentifier(): SyntaxKind; + scanJsxToken(): JsxTokenSyntaxKind; + scanJsDocToken(): JSDocSyntaxKind; + scan(): SyntaxKind; + + getText(): string; + /** @internal */ + clearCommentDirectives(): void; + // Sets the text for the scanner to scan. An optional subrange starting point and length + // can be provided to have the scanner only scan a portion of the text. + setText(text: string | undefined, start?: number, length?: number): void; + setOnError(onError: ErrorCallback | undefined): void; + setScriptTarget(scriptTarget: ScriptTarget): void; + setLanguageVariant(variant: LanguageVariant): void; + setTextPos(textPos: number): void; + /** @internal */ + setInJSDocType(inType: boolean): void; + // Invokes the provided callback then unconditionally restores the scanner to the state it + // was in immediately prior to invoking the callback. The result of invoking the callback + // is returned from this function. + lookAhead(callback: () => T): T; + + // Invokes the callback with the scanner set to scan the specified range. When the callback + // returns, the scanner is restored to the state it was in before scanRange was called. + scanRange(start: number, length: number, callback: () => T): T; + + // Invokes the provided callback. If the callback returns something falsy, then it restores + // the scanner to the state it was in immediately prior to invoking the callback. If the + // callback returns something truthy, then the scanner state is not rolled back. The result + // of invoking the callback is returned from this function. + tryScan(callback: () => T): T; +} - /** - * Test for whether a single line comment with leading whitespace trimmed's text contains a directive. - */ - const commentDirectiveRegExSingleLine = /^\/\/\/?\s*@(ts-expect-error|ts-ignore)/; +/** @internal */ +export const textToKeywordObj: MapLike = { + abstract: SyntaxKind.AbstractKeyword, + accessor: SyntaxKind.AccessorKeyword, + any: SyntaxKind.AnyKeyword, + as: SyntaxKind.AsKeyword, + asserts: SyntaxKind.AssertsKeyword, + assert: SyntaxKind.AssertKeyword, + bigint: SyntaxKind.BigIntKeyword, + boolean: SyntaxKind.BooleanKeyword, + break: SyntaxKind.BreakKeyword, + case: SyntaxKind.CaseKeyword, + catch: SyntaxKind.CatchKeyword, + class: SyntaxKind.ClassKeyword, + continue: SyntaxKind.ContinueKeyword, + const: SyntaxKind.ConstKeyword, + ["" + "constructor"]: SyntaxKind.ConstructorKeyword, + debugger: SyntaxKind.DebuggerKeyword, + declare: SyntaxKind.DeclareKeyword, + default: SyntaxKind.DefaultKeyword, + delete: SyntaxKind.DeleteKeyword, + do: SyntaxKind.DoKeyword, + else: SyntaxKind.ElseKeyword, + enum: SyntaxKind.EnumKeyword, + export: SyntaxKind.ExportKeyword, + extends: SyntaxKind.ExtendsKeyword, + false: SyntaxKind.FalseKeyword, + finally: SyntaxKind.FinallyKeyword, + for: SyntaxKind.ForKeyword, + from: SyntaxKind.FromKeyword, + function: SyntaxKind.FunctionKeyword, + get: SyntaxKind.GetKeyword, + if: SyntaxKind.IfKeyword, + implements: SyntaxKind.ImplementsKeyword, + import: SyntaxKind.ImportKeyword, + in: SyntaxKind.InKeyword, + infer: SyntaxKind.InferKeyword, + instanceof: SyntaxKind.InstanceOfKeyword, + interface: SyntaxKind.InterfaceKeyword, + intrinsic: SyntaxKind.IntrinsicKeyword, + is: SyntaxKind.IsKeyword, + keyof: SyntaxKind.KeyOfKeyword, + let: SyntaxKind.LetKeyword, + module: SyntaxKind.ModuleKeyword, + namespace: SyntaxKind.NamespaceKeyword, + never: SyntaxKind.NeverKeyword, + new: SyntaxKind.NewKeyword, + null: SyntaxKind.NullKeyword, + number: SyntaxKind.NumberKeyword, + object: SyntaxKind.ObjectKeyword, + package: SyntaxKind.PackageKeyword, + private: SyntaxKind.PrivateKeyword, + protected: SyntaxKind.ProtectedKeyword, + public: SyntaxKind.PublicKeyword, + override: SyntaxKind.OverrideKeyword, + out: SyntaxKind.OutKeyword, + readonly: SyntaxKind.ReadonlyKeyword, + require: SyntaxKind.RequireKeyword, + global: SyntaxKind.GlobalKeyword, + return: SyntaxKind.ReturnKeyword, + satisfies: SyntaxKind.SatisfiesKeyword, + set: SyntaxKind.SetKeyword, + static: SyntaxKind.StaticKeyword, + string: SyntaxKind.StringKeyword, + super: SyntaxKind.SuperKeyword, + switch: SyntaxKind.SwitchKeyword, + symbol: SyntaxKind.SymbolKeyword, + this: SyntaxKind.ThisKeyword, + throw: SyntaxKind.ThrowKeyword, + true: SyntaxKind.TrueKeyword, + try: SyntaxKind.TryKeyword, + type: SyntaxKind.TypeKeyword, + typeof: SyntaxKind.TypeOfKeyword, + undefined: SyntaxKind.UndefinedKeyword, + unique: SyntaxKind.UniqueKeyword, + unknown: SyntaxKind.UnknownKeyword, + var: SyntaxKind.VarKeyword, + void: SyntaxKind.VoidKeyword, + while: SyntaxKind.WhileKeyword, + with: SyntaxKind.WithKeyword, + yield: SyntaxKind.YieldKeyword, + async: SyntaxKind.AsyncKeyword, + await: SyntaxKind.AwaitKeyword, + of: SyntaxKind.OfKeyword, +}; + +const textToKeyword = new Map(getEntries(textToKeywordObj)); + +const textToToken = new Map(getEntries({ + ...textToKeywordObj, + "{": SyntaxKind.OpenBraceToken, + "}": SyntaxKind.CloseBraceToken, + "(": SyntaxKind.OpenParenToken, + ")": SyntaxKind.CloseParenToken, + "[": SyntaxKind.OpenBracketToken, + "]": SyntaxKind.CloseBracketToken, + ".": SyntaxKind.DotToken, + "...": SyntaxKind.DotDotDotToken, + ";": SyntaxKind.SemicolonToken, + ",": SyntaxKind.CommaToken, + "<": SyntaxKind.LessThanToken, + ">": SyntaxKind.GreaterThanToken, + "<=": SyntaxKind.LessThanEqualsToken, + ">=": SyntaxKind.GreaterThanEqualsToken, + "==": SyntaxKind.EqualsEqualsToken, + "!=": SyntaxKind.ExclamationEqualsToken, + "===": SyntaxKind.EqualsEqualsEqualsToken, + "!==": SyntaxKind.ExclamationEqualsEqualsToken, + "=>": SyntaxKind.EqualsGreaterThanToken, + "+": SyntaxKind.PlusToken, + "-": SyntaxKind.MinusToken, + "**": SyntaxKind.AsteriskAsteriskToken, + "*": SyntaxKind.AsteriskToken, + "/": SyntaxKind.SlashToken, + "%": SyntaxKind.PercentToken, + "++": SyntaxKind.PlusPlusToken, + "--": SyntaxKind.MinusMinusToken, + "<<": SyntaxKind.LessThanLessThanToken, + ">": SyntaxKind.GreaterThanGreaterThanToken, + ">>>": SyntaxKind.GreaterThanGreaterThanGreaterThanToken, + "&": SyntaxKind.AmpersandToken, + "|": SyntaxKind.BarToken, + "^": SyntaxKind.CaretToken, + "!": SyntaxKind.ExclamationToken, + "~": SyntaxKind.TildeToken, + "&&": SyntaxKind.AmpersandAmpersandToken, + "||": SyntaxKind.BarBarToken, + "?": SyntaxKind.QuestionToken, + "??": SyntaxKind.QuestionQuestionToken, + "?.": SyntaxKind.QuestionDotToken, + ":": SyntaxKind.ColonToken, + "=": SyntaxKind.EqualsToken, + "+=": SyntaxKind.PlusEqualsToken, + "-=": SyntaxKind.MinusEqualsToken, + "*=": SyntaxKind.AsteriskEqualsToken, + "**=": SyntaxKind.AsteriskAsteriskEqualsToken, + "/=": SyntaxKind.SlashEqualsToken, + "%=": SyntaxKind.PercentEqualsToken, + "<<=": SyntaxKind.LessThanLessThanEqualsToken, + ">>=": SyntaxKind.GreaterThanGreaterThanEqualsToken, + ">>>=": SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, + "&=": SyntaxKind.AmpersandEqualsToken, + "|=": SyntaxKind.BarEqualsToken, + "^=": SyntaxKind.CaretEqualsToken, + "||=": SyntaxKind.BarBarEqualsToken, + "&&=": SyntaxKind.AmpersandAmpersandEqualsToken, + "??=": SyntaxKind.QuestionQuestionEqualsToken, + "@": SyntaxKind.AtToken, + "#": SyntaxKind.HashToken, + "`": SyntaxKind.BacktickToken, +})); + +/* + As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers + IdentifierStart :: + Can contain Unicode 3.0.0 categories: + Uppercase letter (Lu), + Lowercase letter (Ll), + Titlecase letter (Lt), + Modifier letter (Lm), + Other letter (Lo), or + Letter number (Nl). + IdentifierPart :: = + Can contain IdentifierStart + Unicode 3.0.0 categories: + Non-spacing mark (Mn), + Combining spacing mark (Mc), + Decimal number (Nd), or + Connector punctuation (Pc). + + Codepoint ranges for ES3 Identifiers are extracted from the Unicode 3.0.0 specification at: + http://www.unicode.org/Public/3.0-Update/UnicodeData-3.0.0.txt +*/ +const unicodeES3IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1610, 1649, 1747, 1749, 1749, 1765, 1766, 1786, 1788, 1808, 1808, 1810, 1836, 1920, 1957, 2309, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2784, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3294, 3294, 3296, 3297, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3424, 3425, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805, 3840, 3840, 3904, 3911, 3913, 3946, 3976, 3979, 4096, 4129, 4131, 4135, 4137, 4138, 4176, 4181, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6067, 6176, 6263, 6272, 6312, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8319, 8319, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12346, 12353, 12436, 12445, 12446, 12449, 12538, 12540, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65138, 65140, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; +const unicodeES3IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 543, 546, 563, 592, 685, 688, 696, 699, 705, 720, 721, 736, 740, 750, 750, 768, 846, 864, 866, 890, 890, 902, 902, 904, 906, 908, 908, 910, 929, 931, 974, 976, 983, 986, 1011, 1024, 1153, 1155, 1158, 1164, 1220, 1223, 1224, 1227, 1228, 1232, 1269, 1272, 1273, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1441, 1443, 1465, 1467, 1469, 1471, 1471, 1473, 1474, 1476, 1476, 1488, 1514, 1520, 1522, 1569, 1594, 1600, 1621, 1632, 1641, 1648, 1747, 1749, 1756, 1759, 1768, 1770, 1773, 1776, 1788, 1808, 1836, 1840, 1866, 1920, 1968, 2305, 2307, 2309, 2361, 2364, 2381, 2384, 2388, 2392, 2403, 2406, 2415, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2492, 2494, 2500, 2503, 2504, 2507, 2509, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2562, 2562, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2649, 2652, 2654, 2654, 2662, 2676, 2689, 2691, 2693, 2699, 2701, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2784, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2870, 2873, 2876, 2883, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2913, 2918, 2927, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 2997, 2999, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3031, 3031, 3047, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3134, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3168, 3169, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3262, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3297, 3302, 3311, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3368, 3370, 3385, 3390, 3395, 3398, 3400, 3402, 3405, 3415, 3415, 3424, 3425, 3430, 3439, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3805, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3946, 3953, 3972, 3974, 3979, 3984, 3991, 3993, 4028, 4038, 4038, 4096, 4129, 4131, 4135, 4137, 4138, 4140, 4146, 4150, 4153, 4160, 4169, 4176, 4185, 4256, 4293, 4304, 4342, 4352, 4441, 4447, 4514, 4520, 4601, 4608, 4614, 4616, 4678, 4680, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4742, 4744, 4744, 4746, 4749, 4752, 4782, 4784, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4814, 4816, 4822, 4824, 4846, 4848, 4878, 4880, 4880, 4882, 4885, 4888, 4894, 4896, 4934, 4936, 4954, 4969, 4977, 5024, 5108, 5121, 5740, 5743, 5750, 5761, 5786, 5792, 5866, 6016, 6099, 6112, 6121, 6160, 6169, 6176, 6263, 6272, 6313, 7680, 7835, 7840, 7929, 7936, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8319, 8319, 8400, 8412, 8417, 8417, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8497, 8499, 8505, 8544, 8579, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12346, 12353, 12436, 12441, 12442, 12445, 12446, 12449, 12542, 12549, 12588, 12593, 12686, 12704, 12727, 13312, 19893, 19968, 40869, 40960, 42124, 44032, 55203, 63744, 64045, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65056, 65059, 65075, 65076, 65101, 65103, 65136, 65138, 65140, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65381, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; + +/* + As per ECMAScript Language Specification 5th Edition, Section 7.6: ISyntaxToken Names and Identifiers + IdentifierStart :: + Can contain Unicode 6.2 categories: + Uppercase letter (Lu), + Lowercase letter (Ll), + Titlecase letter (Lt), + Modifier letter (Lm), + Other letter (Lo), or + Letter number (Nl). + IdentifierPart :: + Can contain IdentifierStart + Unicode 6.2 categories: + Non-spacing mark (Mn), + Combining spacing mark (Mc), + Decimal number (Nd), + Connector punctuation (Pc), + , or + . + + Codepoint ranges for ES5 Identifiers are extracted from the Unicode 6.2 specification at: + http://www.unicode.org/Public/6.2.0/ucd/UnicodeData.txt +*/ +const unicodeES5IdentifierStart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2208, 2208, 2210, 2220, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516, 6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409, 7413, 7414, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; +const unicodeES5IdentifierPart = [170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1319, 1329, 1366, 1369, 1369, 1377, 1415, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1520, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2048, 2093, 2112, 2139, 2208, 2208, 2210, 2220, 2276, 2302, 2304, 2403, 2406, 2415, 2417, 2423, 2425, 2431, 2433, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3073, 3075, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, 3125, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3161, 3168, 3171, 3174, 3183, 3202, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3330, 3331, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3396, 3398, 3400, 3402, 3406, 3415, 3415, 3424, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3769, 3771, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4992, 5007, 5024, 5108, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6263, 6272, 6314, 6320, 6389, 6400, 6428, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6617, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7376, 7378, 7380, 7414, 7424, 7654, 7676, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8204, 8205, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 11823, 11823, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12442, 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40908, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42647, 42655, 42737, 42775, 42783, 42786, 42888, 42891, 42894, 42896, 42899, 42912, 42922, 43000, 43047, 43072, 43123, 43136, 43204, 43216, 43225, 43232, 43255, 43259, 43259, 43264, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43643, 43648, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43968, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65062, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, ]; + +/** + * Generated by scripts/regenerate-unicode-identifier-parts.js on node v12.4.0 with unicode 12.1 + * based on http://www.unicode.org/reports/tr31/ and https://www.ecma-international.org/ecma-262/6.0/#sec-names-and-keywords + * unicodeESNextIdentifierStart corresponds to the ID_Start and Other_ID_Start property, and + * unicodeESNextIdentifierPart corresponds to ID_Continue, Other_ID_Continue, plus ID_Start and Other_ID_Start + */ +const unicodeESNextIdentifierStart = [65, 90, 97, 122, 170, 170, 181, 181, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 880, 884, 886, 887, 890, 893, 895, 895, 902, 902, 904, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1488, 1514, 1519, 1522, 1568, 1610, 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2144, 2154, 2208, 2228, 2230, 2237, 2308, 2361, 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2432, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, 2544, 2545, 2556, 2556, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, 2809, 2809, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3133, 3160, 3162, 3168, 3169, 3200, 3200, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, 3346, 3386, 3389, 3389, 3406, 3406, 3412, 3414, 3423, 3425, 3450, 3455, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3760, 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3807, 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6264, 6272, 6312, 6314, 6314, 6320, 6389, 6400, 6430, 6480, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6656, 6678, 6688, 6740, 6823, 6823, 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7098, 7141, 7168, 7203, 7245, 7247, 7258, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7401, 7404, 7406, 7411, 7413, 7414, 7418, 7418, 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11502, 11506, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 12293, 12295, 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, 12443, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, 42560, 42606, 42623, 42653, 42656, 42735, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43009, 43011, 43013, 43015, 43018, 43020, 43042, 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, 43261, 43262, 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, 43471, 43471, 43488, 43492, 43494, 43503, 43514, 43518, 43520, 43560, 43584, 43586, 43588, 43595, 43616, 43638, 43642, 43642, 43646, 43695, 43697, 43697, 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, 43739, 43741, 43744, 43754, 43762, 43764, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44002, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66176, 66204, 66208, 66256, 66304, 66335, 66349, 66378, 66384, 66421, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68096, 68112, 68115, 68117, 68119, 68121, 68149, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68324, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68899, 69376, 69404, 69415, 69415, 69424, 69445, 69600, 69622, 69635, 69687, 69763, 69807, 69840, 69864, 69891, 69926, 69956, 69956, 69968, 70002, 70006, 70006, 70019, 70066, 70081, 70084, 70106, 70106, 70108, 70108, 70144, 70161, 70163, 70187, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70366, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70461, 70461, 70480, 70480, 70493, 70497, 70656, 70708, 70727, 70730, 70751, 70751, 70784, 70831, 70852, 70853, 70855, 70855, 71040, 71086, 71128, 71131, 71168, 71215, 71236, 71236, 71296, 71338, 71352, 71352, 71424, 71450, 71680, 71723, 71840, 71903, 71935, 71935, 72096, 72103, 72106, 72144, 72161, 72161, 72163, 72163, 72192, 72192, 72203, 72242, 72250, 72250, 72272, 72272, 72284, 72329, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72750, 72768, 72768, 72818, 72847, 72960, 72966, 72968, 72969, 72971, 73008, 73030, 73030, 73056, 73061, 73063, 73064, 73066, 73097, 73112, 73112, 73440, 73458, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92880, 92909, 92928, 92975, 92992, 92995, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94032, 94032, 94099, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 123136, 123180, 123191, 123197, 123214, 123214, 123584, 123627, 124928, 125124, 125184, 125251, 125259, 125259, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101]; +const unicodeESNextIdentifierPart = [48, 57, 65, 90, 95, 95, 97, 122, 170, 170, 181, 181, 183, 183, 186, 186, 192, 214, 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, 768, 884, 886, 887, 890, 893, 895, 895, 902, 906, 908, 908, 910, 929, 931, 1013, 1015, 1153, 1155, 1159, 1162, 1327, 1329, 1366, 1369, 1369, 1376, 1416, 1425, 1469, 1471, 1471, 1473, 1474, 1476, 1477, 1479, 1479, 1488, 1514, 1519, 1522, 1552, 1562, 1568, 1641, 1646, 1747, 1749, 1756, 1759, 1768, 1770, 1788, 1791, 1791, 1808, 1866, 1869, 1969, 1984, 2037, 2042, 2042, 2045, 2045, 2048, 2093, 2112, 2139, 2144, 2154, 2208, 2228, 2230, 2237, 2259, 2273, 2275, 2403, 2406, 2415, 2417, 2435, 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, 2486, 2489, 2492, 2500, 2503, 2504, 2507, 2510, 2519, 2519, 2524, 2525, 2527, 2531, 2534, 2545, 2556, 2556, 2558, 2558, 2561, 2563, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, 2610, 2611, 2613, 2614, 2616, 2617, 2620, 2620, 2622, 2626, 2631, 2632, 2635, 2637, 2641, 2641, 2649, 2652, 2654, 2654, 2662, 2677, 2689, 2691, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, 2738, 2739, 2741, 2745, 2748, 2757, 2759, 2761, 2763, 2765, 2768, 2768, 2784, 2787, 2790, 2799, 2809, 2815, 2817, 2819, 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, 2869, 2873, 2876, 2884, 2887, 2888, 2891, 2893, 2902, 2903, 2908, 2909, 2911, 2915, 2918, 2927, 2929, 2929, 2946, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, 3006, 3010, 3014, 3016, 3018, 3021, 3024, 3024, 3031, 3031, 3046, 3055, 3072, 3084, 3086, 3088, 3090, 3112, 3114, 3129, 3133, 3140, 3142, 3144, 3146, 3149, 3157, 3158, 3160, 3162, 3168, 3171, 3174, 3183, 3200, 3203, 3205, 3212, 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3260, 3268, 3270, 3272, 3274, 3277, 3285, 3286, 3294, 3294, 3296, 3299, 3302, 3311, 3313, 3314, 3328, 3331, 3333, 3340, 3342, 3344, 3346, 3396, 3398, 3400, 3402, 3406, 3412, 3415, 3423, 3427, 3430, 3439, 3450, 3455, 3458, 3459, 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, 3530, 3530, 3535, 3540, 3542, 3542, 3544, 3551, 3558, 3567, 3570, 3571, 3585, 3642, 3648, 3662, 3664, 3673, 3713, 3714, 3716, 3716, 3718, 3722, 3724, 3747, 3749, 3749, 3751, 3773, 3776, 3780, 3782, 3782, 3784, 3789, 3792, 3801, 3804, 3807, 3840, 3840, 3864, 3865, 3872, 3881, 3893, 3893, 3895, 3895, 3897, 3897, 3902, 3911, 3913, 3948, 3953, 3972, 3974, 3991, 3993, 4028, 4038, 4038, 4096, 4169, 4176, 4253, 4256, 4293, 4295, 4295, 4301, 4301, 4304, 4346, 4348, 4680, 4682, 4685, 4688, 4694, 4696, 4696, 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, 4882, 4885, 4888, 4954, 4957, 4959, 4969, 4977, 4992, 5007, 5024, 5109, 5112, 5117, 5121, 5740, 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5880, 5888, 5900, 5902, 5908, 5920, 5940, 5952, 5971, 5984, 5996, 5998, 6000, 6002, 6003, 6016, 6099, 6103, 6103, 6108, 6109, 6112, 6121, 6155, 6157, 6160, 6169, 6176, 6264, 6272, 6314, 6320, 6389, 6400, 6430, 6432, 6443, 6448, 6459, 6470, 6509, 6512, 6516, 6528, 6571, 6576, 6601, 6608, 6618, 6656, 6683, 6688, 6750, 6752, 6780, 6783, 6793, 6800, 6809, 6823, 6823, 6832, 6845, 6912, 6987, 6992, 7001, 7019, 7027, 7040, 7155, 7168, 7223, 7232, 7241, 7245, 7293, 7296, 7304, 7312, 7354, 7357, 7359, 7376, 7378, 7380, 7418, 7424, 7673, 7675, 7957, 7960, 7965, 7968, 8005, 8008, 8013, 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, 8255, 8256, 8276, 8276, 8305, 8305, 8319, 8319, 8336, 8348, 8400, 8412, 8417, 8417, 8421, 8432, 8450, 8450, 8455, 8455, 8458, 8467, 8469, 8469, 8472, 8477, 8484, 8484, 8486, 8486, 8488, 8488, 8490, 8505, 8508, 8511, 8517, 8521, 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, 11360, 11492, 11499, 11507, 11520, 11557, 11559, 11559, 11565, 11565, 11568, 11623, 11631, 11631, 11647, 11670, 11680, 11686, 11688, 11694, 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, 11728, 11734, 11736, 11742, 11744, 11775, 12293, 12295, 12321, 12335, 12337, 12341, 12344, 12348, 12353, 12438, 12441, 12447, 12449, 12538, 12540, 12543, 12549, 12591, 12593, 12686, 12704, 12730, 12784, 12799, 13312, 19893, 19968, 40943, 40960, 42124, 42192, 42237, 42240, 42508, 42512, 42539, 42560, 42607, 42612, 42621, 42623, 42737, 42775, 42783, 42786, 42888, 42891, 42943, 42946, 42950, 42999, 43047, 43072, 43123, 43136, 43205, 43216, 43225, 43232, 43255, 43259, 43259, 43261, 43309, 43312, 43347, 43360, 43388, 43392, 43456, 43471, 43481, 43488, 43518, 43520, 43574, 43584, 43597, 43600, 43609, 43616, 43638, 43642, 43714, 43739, 43741, 43744, 43759, 43762, 43766, 43777, 43782, 43785, 43790, 43793, 43798, 43808, 43814, 43816, 43822, 43824, 43866, 43868, 43879, 43888, 44010, 44012, 44013, 44016, 44025, 44032, 55203, 55216, 55238, 55243, 55291, 63744, 64109, 64112, 64217, 64256, 64262, 64275, 64279, 64285, 64296, 64298, 64310, 64312, 64316, 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, 65024, 65039, 65056, 65071, 65075, 65076, 65101, 65103, 65136, 65140, 65142, 65276, 65296, 65305, 65313, 65338, 65343, 65343, 65345, 65370, 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, 65856, 65908, 66045, 66045, 66176, 66204, 66208, 66256, 66272, 66272, 66304, 66335, 66349, 66378, 66384, 66426, 66432, 66461, 66464, 66499, 66504, 66511, 66513, 66517, 66560, 66717, 66720, 66729, 66736, 66771, 66776, 66811, 66816, 66855, 66864, 66915, 67072, 67382, 67392, 67413, 67424, 67431, 67584, 67589, 67592, 67592, 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, 67680, 67702, 67712, 67742, 67808, 67826, 67828, 67829, 67840, 67861, 67872, 67897, 67968, 68023, 68030, 68031, 68096, 68099, 68101, 68102, 68108, 68115, 68117, 68119, 68121, 68149, 68152, 68154, 68159, 68159, 68192, 68220, 68224, 68252, 68288, 68295, 68297, 68326, 68352, 68405, 68416, 68437, 68448, 68466, 68480, 68497, 68608, 68680, 68736, 68786, 68800, 68850, 68864, 68903, 68912, 68921, 69376, 69404, 69415, 69415, 69424, 69456, 69600, 69622, 69632, 69702, 69734, 69743, 69759, 69818, 69840, 69864, 69872, 69881, 69888, 69940, 69942, 69951, 69956, 69958, 69968, 70003, 70006, 70006, 70016, 70084, 70089, 70092, 70096, 70106, 70108, 70108, 70144, 70161, 70163, 70199, 70206, 70206, 70272, 70278, 70280, 70280, 70282, 70285, 70287, 70301, 70303, 70312, 70320, 70378, 70384, 70393, 70400, 70403, 70405, 70412, 70415, 70416, 70419, 70440, 70442, 70448, 70450, 70451, 70453, 70457, 70459, 70468, 70471, 70472, 70475, 70477, 70480, 70480, 70487, 70487, 70493, 70499, 70502, 70508, 70512, 70516, 70656, 70730, 70736, 70745, 70750, 70751, 70784, 70853, 70855, 70855, 70864, 70873, 71040, 71093, 71096, 71104, 71128, 71133, 71168, 71232, 71236, 71236, 71248, 71257, 71296, 71352, 71360, 71369, 71424, 71450, 71453, 71467, 71472, 71481, 71680, 71738, 71840, 71913, 71935, 71935, 72096, 72103, 72106, 72151, 72154, 72161, 72163, 72164, 72192, 72254, 72263, 72263, 72272, 72345, 72349, 72349, 72384, 72440, 72704, 72712, 72714, 72758, 72760, 72768, 72784, 72793, 72818, 72847, 72850, 72871, 72873, 72886, 72960, 72966, 72968, 72969, 72971, 73014, 73018, 73018, 73020, 73021, 73023, 73031, 73040, 73049, 73056, 73061, 73063, 73064, 73066, 73102, 73104, 73105, 73107, 73112, 73120, 73129, 73440, 73462, 73728, 74649, 74752, 74862, 74880, 75075, 77824, 78894, 82944, 83526, 92160, 92728, 92736, 92766, 92768, 92777, 92880, 92909, 92912, 92916, 92928, 92982, 92992, 92995, 93008, 93017, 93027, 93047, 93053, 93071, 93760, 93823, 93952, 94026, 94031, 94087, 94095, 94111, 94176, 94177, 94179, 94179, 94208, 100343, 100352, 101106, 110592, 110878, 110928, 110930, 110948, 110951, 110960, 111355, 113664, 113770, 113776, 113788, 113792, 113800, 113808, 113817, 113821, 113822, 119141, 119145, 119149, 119154, 119163, 119170, 119173, 119179, 119210, 119213, 119362, 119364, 119808, 119892, 119894, 119964, 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, 120782, 120831, 121344, 121398, 121403, 121452, 121461, 121461, 121476, 121476, 121499, 121503, 121505, 121519, 122880, 122886, 122888, 122904, 122907, 122913, 122915, 122916, 122918, 122922, 123136, 123180, 123184, 123197, 123200, 123209, 123214, 123214, 123584, 123641, 124928, 125124, 125136, 125142, 125184, 125259, 125264, 125273, 126464, 126467, 126469, 126495, 126497, 126498, 126500, 126500, 126503, 126503, 126505, 126514, 126516, 126519, 126521, 126521, 126523, 126523, 126530, 126530, 126535, 126535, 126537, 126537, 126539, 126539, 126541, 126543, 126545, 126546, 126548, 126548, 126551, 126551, 126553, 126553, 126555, 126555, 126557, 126557, 126559, 126559, 126561, 126562, 126564, 126564, 126567, 126570, 126572, 126578, 126580, 126583, 126585, 126588, 126590, 126590, 126592, 126601, 126603, 126619, 126625, 126627, 126629, 126633, 126635, 126651, 131072, 173782, 173824, 177972, 177984, 178205, 178208, 183969, 183984, 191456, 194560, 195101, 917760, 917999]; + +/** + * Test for whether a single line comment with leading whitespace trimmed's text contains a directive. + */ +const commentDirectiveRegExSingleLine = /^\/\/\/?\s*@(ts-expect-error|ts-ignore)/; + +/** + * Test for whether a multi-line comment with leading whitespace trimmed's last line contains a directive. + */ +const commentDirectiveRegExMultiLine = /^(?:\/|\*)*\s*@(ts-expect-error|ts-ignore)/; + +function lookupInUnicodeMap(code: number, map: readonly number[]): boolean { + // Bail out quickly if it couldn't possibly be in the map. + if (code < map[0]) { + return false; + } - /** - * Test for whether a multi-line comment with leading whitespace trimmed's last line contains a directive. - */ - const commentDirectiveRegExMultiLine = /^(?:\/|\*)*\s*@(ts-expect-error|ts-ignore)/; + // Perform binary search in one of the Unicode range maps + let lo = 0; + let hi: number = map.length; + let mid: number; - function lookupInUnicodeMap(code: number, map: readonly number[]): boolean { - // Bail out quickly if it couldn't possibly be in the map. - if (code < map[0]) { - return false; + while (lo + 1 < hi) { + mid = lo + (hi - lo) / 2; + // mid has to be even to catch a range's beginning + mid -= mid % 2; + if (map[mid] <= code && code <= map[mid + 1]) { + return true; } - // Perform binary search in one of the Unicode range maps - let lo = 0; - let hi: number = map.length; - let mid: number; - - while (lo + 1 < hi) { - mid = lo + (hi - lo) / 2; - // mid has to be even to catch a range's beginning - mid -= mid % 2; - if (map[mid] <= code && code <= map[mid + 1]) { - return true; - } - - if (code < map[mid]) { - hi = mid; - } - else { - lo = mid + 2; - } + if (code < map[mid]) { + hi = mid; + } + else { + lo = mid + 2; } - - return false; } - /* @internal */ export function isUnicodeIdentifierStart(code: number, languageVersion: ScriptTarget | undefined) { - return languageVersion! >= ScriptTarget.ES2015 ? - lookupInUnicodeMap(code, unicodeESNextIdentifierStart) : - languageVersion === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierStart) : - lookupInUnicodeMap(code, unicodeES3IdentifierStart); - } + return false; +} - function isUnicodeIdentifierPart(code: number, languageVersion: ScriptTarget | undefined) { - return languageVersion! >= ScriptTarget.ES2015 ? - lookupInUnicodeMap(code, unicodeESNextIdentifierPart) : - languageVersion === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierPart) : - lookupInUnicodeMap(code, unicodeES3IdentifierPart); - } +/** @internal */ export function isUnicodeIdentifierStart(code: number, languageVersion: ScriptTarget | undefined) { + return languageVersion! >= ScriptTarget.ES2015 ? + lookupInUnicodeMap(code, unicodeESNextIdentifierStart) : + languageVersion === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierStart) : + lookupInUnicodeMap(code, unicodeES3IdentifierStart); +} - function makeReverseMap(source: ESMap): string[] { - const result: string[] = []; - source.forEach((value, name) => { - result[value] = name; - }); - return result; - } +function isUnicodeIdentifierPart(code: number, languageVersion: ScriptTarget | undefined) { + return languageVersion! >= ScriptTarget.ES2015 ? + lookupInUnicodeMap(code, unicodeESNextIdentifierPart) : + languageVersion === ScriptTarget.ES5 ? lookupInUnicodeMap(code, unicodeES5IdentifierPart) : + lookupInUnicodeMap(code, unicodeES3IdentifierPart); +} - const tokenStrings = makeReverseMap(textToToken); - export function tokenToString(t: SyntaxKind): string | undefined { - return tokenStrings[t]; - } +function makeReverseMap(source: ESMap): string[] { + const result: string[] = []; + source.forEach((value, name) => { + result[value] = name; + }); + return result; +} - /* @internal */ - export function stringToToken(s: string): SyntaxKind | undefined { - return textToToken.get(s); - } +const tokenStrings = makeReverseMap(textToToken); +export function tokenToString(t: SyntaxKind): string | undefined { + return tokenStrings[t]; +} - /* @internal */ - export function computeLineStarts(text: string): number[] { - const result: number[] = []; - let pos = 0; - let lineStart = 0; - while (pos < text.length) { - const ch = text.charCodeAt(pos); - pos++; - switch (ch) { - case CharacterCodes.carriageReturn: - if (text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: +/** @internal */ +export function stringToToken(s: string): SyntaxKind | undefined { + return textToToken.get(s); +} + +/** @internal */ +export function computeLineStarts(text: string): number[] { + const result: number[] = []; + let pos = 0; + let lineStart = 0; + while (pos < text.length) { + const ch = text.charCodeAt(pos); + pos++; + switch (ch) { + case CharacterCodes.carriageReturn: + if (text.charCodeAt(pos) === CharacterCodes.lineFeed) { + pos++; + } + // falls through + case CharacterCodes.lineFeed: + result.push(lineStart); + lineStart = pos; + break; + default: + if (ch > CharacterCodes.maxAsciiCharacter && isLineBreak(ch)) { result.push(lineStart); lineStart = pos; - break; - default: - if (ch > CharacterCodes.maxAsciiCharacter && isLineBreak(ch)) { - result.push(lineStart); - lineStart = pos; - } - break; - } + } + break; } - result.push(lineStart); - return result; - } - - export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number; - /* @internal */ - export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number; // eslint-disable-line @typescript-eslint/unified-signatures - export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number { - return sourceFile.getPositionOfLineAndCharacter ? - sourceFile.getPositionOfLineAndCharacter(line, character, allowEdits) : - computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, allowEdits); } + result.push(lineStart); + return result; +} - /* @internal */ - export function computePositionOfLineAndCharacter(lineStarts: readonly number[], line: number, character: number, debugText?: string, allowEdits?: true): number { - if (line < 0 || line >= lineStarts.length) { - if (allowEdits) { - // Clamp line to nearest allowable value - line = line < 0 ? 0 : line >= lineStarts.length ? lineStarts.length - 1 : line; - } - else { - Debug.fail(`Bad line number. Line: ${line}, lineStarts.length: ${lineStarts.length} , line map is correct? ${debugText !== undefined ? arraysEqual(lineStarts, computeLineStarts(debugText)) : "unknown"}`); - } - } +export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number): number; +/** @internal */ +export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number; // eslint-disable-line @typescript-eslint/unified-signatures +export function getPositionOfLineAndCharacter(sourceFile: SourceFileLike, line: number, character: number, allowEdits?: true): number { + return sourceFile.getPositionOfLineAndCharacter ? + sourceFile.getPositionOfLineAndCharacter(line, character, allowEdits) : + computePositionOfLineAndCharacter(getLineStarts(sourceFile), line, character, sourceFile.text, allowEdits); +} - const res = lineStarts[line] + character; +/** @internal */ +export function computePositionOfLineAndCharacter(lineStarts: readonly number[], line: number, character: number, debugText?: string, allowEdits?: true): number { + if (line < 0 || line >= lineStarts.length) { if (allowEdits) { - // Clamp to nearest allowable values to allow the underlying to be edited without crashing (accuracy is lost, instead) - // TODO: Somehow track edits between file as it was during the creation of sourcemap we have and the current file and - // apply them to the computed position to improve accuracy - return res > lineStarts[line + 1] ? lineStarts[line + 1] : typeof debugText === "string" && res > debugText.length ? debugText.length : res; - } - if (line < lineStarts.length - 1) { - Debug.assert(res < lineStarts[line + 1]); + // Clamp line to nearest allowable value + line = line < 0 ? 0 : line >= lineStarts.length ? lineStarts.length - 1 : line; } - else if (debugText !== undefined) { - Debug.assert(res <= debugText.length); // Allow single character overflow for trailing newline + else { + Debug.fail(`Bad line number. Line: ${line}, lineStarts.length: ${lineStarts.length} , line map is correct? ${debugText !== undefined ? arraysEqual(lineStarts, computeLineStarts(debugText)) : "unknown"}`); } - return res; } - /* @internal */ - export function getLineStarts(sourceFile: SourceFileLike): readonly number[] { - return sourceFile.lineMap || (sourceFile.lineMap = computeLineStarts(sourceFile.text)); + const res = lineStarts[line] + character; + if (allowEdits) { + // Clamp to nearest allowable values to allow the underlying to be edited without crashing (accuracy is lost, instead) + // TODO: Somehow track edits between file as it was during the creation of sourcemap we have and the current file and + // apply them to the computed position to improve accuracy + return res > lineStarts[line + 1] ? lineStarts[line + 1] : typeof debugText === "string" && res > debugText.length ? debugText.length : res; } - - /* @internal */ - export function computeLineAndCharacterOfPosition(lineStarts: readonly number[], position: number): LineAndCharacter { - const lineNumber = computeLineOfPosition(lineStarts, position); - return { - line: lineNumber, - character: position - lineStarts[lineNumber] - }; + if (line < lineStarts.length - 1) { + Debug.assert(res < lineStarts[line + 1]); } - - /** - * @internal - * We assume the first line starts at position 0 and 'position' is non-negative. - */ - export function computeLineOfPosition(lineStarts: readonly number[], position: number, lowerBound?: number) { - let lineNumber = binarySearch(lineStarts, position, identity, compareValues, lowerBound); - if (lineNumber < 0) { - // If the actual position was not found, - // the binary search returns the 2's-complement of the next line start - // e.g. if the line starts at [5, 10, 23, 80] and the position requested was 20 - // then the search will return -2. - // - // We want the index of the previous line start, so we subtract 1. - // Review 2's-complement if this is confusing. - lineNumber = ~lineNumber - 1; - Debug.assert(lineNumber !== -1, "position cannot precede the beginning of the file"); - } - return lineNumber; + else if (debugText !== undefined) { + Debug.assert(res <= debugText.length); // Allow single character overflow for trailing newline } + return res; +} - /** @internal */ - export function getLinesBetweenPositions(sourceFile: SourceFileLike, pos1: number, pos2: number) { - if (pos1 === pos2) return 0; - const lineStarts = getLineStarts(sourceFile); - const lower = Math.min(pos1, pos2); - const isNegative = lower === pos2; - const upper = isNegative ? pos1 : pos2; - const lowerLine = computeLineOfPosition(lineStarts, lower); - const upperLine = computeLineOfPosition(lineStarts, upper, lowerLine); - return isNegative ? lowerLine - upperLine : upperLine - lowerLine; - } +/** @internal */ +export function getLineStarts(sourceFile: SourceFileLike): readonly number[] { + return sourceFile.lineMap || (sourceFile.lineMap = computeLineStarts(sourceFile.text)); +} - export function getLineAndCharacterOfPosition(sourceFile: SourceFileLike, position: number): LineAndCharacter { - return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position); - } +/** @internal */ +export function computeLineAndCharacterOfPosition(lineStarts: readonly number[], position: number): LineAndCharacter { + const lineNumber = computeLineOfPosition(lineStarts, position); + return { + line: lineNumber, + character: position - lineStarts[lineNumber] + }; +} - export function isWhiteSpaceLike(ch: number): boolean { - return isWhiteSpaceSingleLine(ch) || isLineBreak(ch); +/** + * @internal + * We assume the first line starts at position 0 and 'position' is non-negative. + */ +export function computeLineOfPosition(lineStarts: readonly number[], position: number, lowerBound?: number) { + let lineNumber = binarySearch(lineStarts, position, identity, compareValues, lowerBound); + if (lineNumber < 0) { + // If the actual position was not found, + // the binary search returns the 2's-complement of the next line start + // e.g. if the line starts at [5, 10, 23, 80] and the position requested was 20 + // then the search will return -2. + // + // We want the index of the previous line start, so we subtract 1. + // Review 2's-complement if this is confusing. + lineNumber = ~lineNumber - 1; + Debug.assert(lineNumber !== -1, "position cannot precede the beginning of the file"); } + return lineNumber; +} - /** Does not include line breaks. For that, see isWhiteSpaceLike. */ - export function isWhiteSpaceSingleLine(ch: number): boolean { - // Note: nextLine is in the Zs space, and should be considered to be a whitespace. - // It is explicitly not a line-break as it isn't in the exact set specified by EcmaScript. - return ch === CharacterCodes.space || - ch === CharacterCodes.tab || - ch === CharacterCodes.verticalTab || - ch === CharacterCodes.formFeed || - ch === CharacterCodes.nonBreakingSpace || - ch === CharacterCodes.nextLine || - ch === CharacterCodes.ogham || - ch >= CharacterCodes.enQuad && ch <= CharacterCodes.zeroWidthSpace || - ch === CharacterCodes.narrowNoBreakSpace || - ch === CharacterCodes.mathematicalSpace || - ch === CharacterCodes.ideographicSpace || - ch === CharacterCodes.byteOrderMark; - } +/** @internal */ +export function getLinesBetweenPositions(sourceFile: SourceFileLike, pos1: number, pos2: number) { + if (pos1 === pos2) return 0; + const lineStarts = getLineStarts(sourceFile); + const lower = Math.min(pos1, pos2); + const isNegative = lower === pos2; + const upper = isNegative ? pos1 : pos2; + const lowerLine = computeLineOfPosition(lineStarts, lower); + const upperLine = computeLineOfPosition(lineStarts, upper, lowerLine); + return isNegative ? lowerLine - upperLine : upperLine - lowerLine; +} - export function isLineBreak(ch: number): boolean { - // ES5 7.3: - // The ECMAScript line terminator characters are listed in Table 3. - // Table 3: Line Terminator Characters - // Code Unit Value Name Formal Name - // \u000A Line Feed - // \u000D Carriage Return - // \u2028 Line separator - // \u2029 Paragraph separator - // Only the characters in Table 3 are treated as line terminators. Other new line or line - // breaking characters are treated as white space but not as line terminators. +export function getLineAndCharacterOfPosition(sourceFile: SourceFileLike, position: number): LineAndCharacter { + return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position); +} - return ch === CharacterCodes.lineFeed || - ch === CharacterCodes.carriageReturn || - ch === CharacterCodes.lineSeparator || - ch === CharacterCodes.paragraphSeparator; - } +export function isWhiteSpaceLike(ch: number): boolean { + return isWhiteSpaceSingleLine(ch) || isLineBreak(ch); +} - function isDigit(ch: number): boolean { - return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; - } +/** Does not include line breaks. For that, see isWhiteSpaceLike. */ +export function isWhiteSpaceSingleLine(ch: number): boolean { + // Note: nextLine is in the Zs space, and should be considered to be a whitespace. + // It is explicitly not a line-break as it isn't in the exact set specified by EcmaScript. + return ch === CharacterCodes.space || + ch === CharacterCodes.tab || + ch === CharacterCodes.verticalTab || + ch === CharacterCodes.formFeed || + ch === CharacterCodes.nonBreakingSpace || + ch === CharacterCodes.nextLine || + ch === CharacterCodes.ogham || + ch >= CharacterCodes.enQuad && ch <= CharacterCodes.zeroWidthSpace || + ch === CharacterCodes.narrowNoBreakSpace || + ch === CharacterCodes.mathematicalSpace || + ch === CharacterCodes.ideographicSpace || + ch === CharacterCodes.byteOrderMark; +} - function isHexDigit(ch: number): boolean { - return isDigit(ch) || ch >= CharacterCodes.A && ch <= CharacterCodes.F || ch >= CharacterCodes.a && ch <= CharacterCodes.f; - } +export function isLineBreak(ch: number): boolean { + // ES5 7.3: + // The ECMAScript line terminator characters are listed in Table 3. + // Table 3: Line Terminator Characters + // Code Unit Value Name Formal Name + // \u000A Line Feed + // \u000D Carriage Return + // \u2028 Line separator + // \u2029 Paragraph separator + // Only the characters in Table 3 are treated as line terminators. Other new line or line + // breaking characters are treated as white space but not as line terminators. + + return ch === CharacterCodes.lineFeed || + ch === CharacterCodes.carriageReturn || + ch === CharacterCodes.lineSeparator || + ch === CharacterCodes.paragraphSeparator; +} + +function isDigit(ch: number): boolean { + return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; +} + +function isHexDigit(ch: number): boolean { + return isDigit(ch) || ch >= CharacterCodes.A && ch <= CharacterCodes.F || ch >= CharacterCodes.a && ch <= CharacterCodes.f; +} + +function isCodePoint(code: number): boolean { + return code <= 0x10FFFF; +} + +/** @internal */ +export function isOctalDigit(ch: number): boolean { + return ch >= CharacterCodes._0 && ch <= CharacterCodes._7; +} - function isCodePoint(code: number): boolean { - return code <= 0x10FFFF; +export function couldStartTrivia(text: string, pos: number): boolean { + // Keep in sync with skipTrivia + const ch = text.charCodeAt(pos); + switch (ch) { + case CharacterCodes.carriageReturn: + case CharacterCodes.lineFeed: + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + case CharacterCodes.slash: + // starts of normal trivia + // falls through + case CharacterCodes.lessThan: + case CharacterCodes.bar: + case CharacterCodes.equals: + case CharacterCodes.greaterThan: + // Starts of conflict marker trivia + return true; + case CharacterCodes.hash: + // Only if its the beginning can we have #! trivia + return pos === 0; + default: + return ch > CharacterCodes.maxAsciiCharacter; } +} - /* @internal */ - export function isOctalDigit(ch: number): boolean { - return ch >= CharacterCodes._0 && ch <= CharacterCodes._7; +/** @internal */ +export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean, stopAtComments?: boolean, inJSDoc?: boolean): number { + if (positionIsSynthesized(pos)) { + return pos; } - export function couldStartTrivia(text: string, pos: number): boolean { - // Keep in sync with skipTrivia + let canConsumeStar = false; + // Keep in sync with couldStartTrivia + while (true) { const ch = text.charCodeAt(pos); switch (ch) { case CharacterCodes.carriageReturn: + if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { + pos++; + } + // falls through case CharacterCodes.lineFeed: + pos++; + if (stopAfterLineBreak) { + return pos; + } + canConsumeStar = !!inJSDoc; + continue; case CharacterCodes.tab: case CharacterCodes.verticalTab: case CharacterCodes.formFeed: case CharacterCodes.space: + pos++; + continue; case CharacterCodes.slash: - // starts of normal trivia - // falls through + if (stopAtComments) { + break; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + while (pos < text.length) { + if (isLineBreak(text.charCodeAt(pos))) { + break; + } + pos++; + } + canConsumeStar = false; + continue; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { + pos += 2; + while (pos < text.length) { + if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + break; + } + pos++; + } + canConsumeStar = false; + continue; + } + break; + case CharacterCodes.lessThan: case CharacterCodes.bar: case CharacterCodes.equals: case CharacterCodes.greaterThan: - // Starts of conflict marker trivia - return true; + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos); + canConsumeStar = false; + continue; + } + break; + case CharacterCodes.hash: - // Only if its the beginning can we have #! trivia - return pos === 0; + if (pos === 0 && isShebangTrivia(text, pos)) { + pos = scanShebangTrivia(text, pos); + canConsumeStar = false; + continue; + } + break; + + case CharacterCodes.asterisk: + if (canConsumeStar) { + pos++; + canConsumeStar = false; + continue; + } + break; + default: - return ch > CharacterCodes.maxAsciiCharacter; + if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { + pos++; + continue; + } + break; } + return pos; } +} + +// All conflict markers consist of the same character repeated seven times. If it is +// a <<<<<<< or >>>>>>> marker then it is also followed by a space. +const mergeConflictMarkerLength = "<<<<<<<".length; - /* @internal */ - export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean, stopAtComments?: boolean, inJSDoc?: boolean): number { - if (positionIsSynthesized(pos)) { - return pos; +function isConflictMarkerTrivia(text: string, pos: number) { + Debug.assert(pos >= 0); + + // Conflict markers must be at the start of a line. + if (pos === 0 || isLineBreak(text.charCodeAt(pos - 1))) { + const ch = text.charCodeAt(pos); + + if ((pos + mergeConflictMarkerLength) < text.length) { + for (let i = 0; i < mergeConflictMarkerLength; i++) { + if (text.charCodeAt(pos + i) !== ch) { + return false; + } + } + + return ch === CharacterCodes.equals || + text.charCodeAt(pos + mergeConflictMarkerLength) === CharacterCodes.space; } + } - let canConsumeStar = false; - // Keep in sync with couldStartTrivia - while (true) { - const ch = text.charCodeAt(pos); - switch (ch) { - case CharacterCodes.carriageReturn: - if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: - pos++; - if (stopAfterLineBreak) { - return pos; - } - canConsumeStar = !!inJSDoc; - continue; - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: + return false; +} + +function scanConflictMarkerTrivia(text: string, pos: number, error?: (diag: DiagnosticMessage, pos?: number, len?: number) => void) { + if (error) { + error(Diagnostics.Merge_conflict_marker_encountered, pos, mergeConflictMarkerLength); + } + + const ch = text.charCodeAt(pos); + const len = text.length; + + if (ch === CharacterCodes.lessThan || ch === CharacterCodes.greaterThan) { + while (pos < len && !isLineBreak(text.charCodeAt(pos))) { + pos++; + } + } + else { + Debug.assert(ch === CharacterCodes.bar || ch === CharacterCodes.equals); + // Consume everything from the start of a ||||||| or ======= marker to the start + // of the next ======= or >>>>>>> marker. + while (pos < len) { + const currentChar = text.charCodeAt(pos); + if ((currentChar === CharacterCodes.equals || currentChar === CharacterCodes.greaterThan) && currentChar !== ch && isConflictMarkerTrivia(text, pos)) { + break; + } + + pos++; + } + } + + return pos; +} + +const shebangTriviaRegex = /^#!.*/; + +/** @internal */ +export function isShebangTrivia(text: string, pos: number) { + // Shebangs check must only be done at the start of the file + Debug.assert(pos === 0); + return shebangTriviaRegex.test(text); +} + +/** @internal */ +export function scanShebangTrivia(text: string, pos: number) { + const shebang = shebangTriviaRegex.exec(text)![0]; + pos = pos + shebang.length; + return pos; +} + +/** + * Invokes a callback for each comment range following the provided position. + * + * Single-line comment ranges include the leading double-slash characters but not the ending + * line break. Multi-line comment ranges include the leading slash-asterisk and trailing + * asterisk-slash characters. + * + * @param reduce If true, accumulates the result of calling the callback in a fashion similar + * to reduceLeft. If false, iteration stops when the callback returns a truthy value. + * @param text The source text to scan. + * @param pos The position at which to start scanning. + * @param trailing If false, whitespace is skipped until the first line break and comments + * between that location and the next token are returned. If true, comments occurring + * between the given position and the next line break are returned. + * @param cb The callback to execute as each comment range is encountered. + * @param state A state value to pass to each iteration of the callback. + * @param initial An initial value to pass when accumulating results (when "reduce" is true). + * @returns If "reduce" is true, the accumulated value. If "reduce" is false, the first truthy + * return value of the callback. + */ +function iterateCommentRanges(reduce: boolean, text: string, pos: number, trailing: boolean, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U | undefined) => U, state: T, initial?: U): U | undefined { + let pendingPos!: number; + let pendingEnd!: number; + let pendingKind!: CommentKind; + let pendingHasTrailingNewLine!: boolean; + let hasPendingCommentRange = false; + let collecting = trailing; + let accumulator = initial; + if (pos === 0) { + collecting = true; + const shebang = getShebang(text); + if (shebang) { + pos = shebang.length; + } + } + scan: while (pos >= 0 && pos < text.length) { + const ch = text.charCodeAt(pos); + switch (ch) { + case CharacterCodes.carriageReturn: + if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { pos++; - continue; - case CharacterCodes.slash: - if (stopAtComments) { - break; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; + } + // falls through + case CharacterCodes.lineFeed: + pos++; + if (trailing) { + break scan; + } + + collecting = true; + if (hasPendingCommentRange) { + pendingHasTrailingNewLine = true; + } + + continue; + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + pos++; + continue; + case CharacterCodes.slash: + const nextChar = text.charCodeAt(pos + 1); + let hasTrailingNewLine = false; + if (nextChar === CharacterCodes.slash || nextChar === CharacterCodes.asterisk) { + const kind = nextChar === CharacterCodes.slash ? SyntaxKind.SingleLineCommentTrivia : SyntaxKind.MultiLineCommentTrivia; + const startPos = pos; + pos += 2; + if (nextChar === CharacterCodes.slash) { while (pos < text.length) { if (isLineBreak(text.charCodeAt(pos))) { + hasTrailingNewLine = true; break; } pos++; } - canConsumeStar = false; - continue; } - if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { - pos += 2; + else { while (pos < text.length) { if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { pos += 2; @@ -613,2050 +819,1849 @@ namespace ts { } pos++; } - canConsumeStar = false; - continue; } - break; - case CharacterCodes.lessThan: - case CharacterCodes.bar: - case CharacterCodes.equals: - case CharacterCodes.greaterThan: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos); - canConsumeStar = false; - continue; - } - break; + if (collecting) { + if (hasPendingCommentRange) { + accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); + if (!reduce && accumulator) { + // If we are not reducing and we have a truthy result, return it. + return accumulator; + } + } - case CharacterCodes.hash: - if (pos === 0 && isShebangTrivia(text, pos)) { - pos = scanShebangTrivia(text, pos); - canConsumeStar = false; - continue; + pendingPos = startPos; + pendingEnd = pos; + pendingKind = kind; + pendingHasTrailingNewLine = hasTrailingNewLine; + hasPendingCommentRange = true; } - break; - case CharacterCodes.asterisk: - if (canConsumeStar) { - pos++; - canConsumeStar = false; - continue; + continue; + } + break scan; + default: + if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { + if (hasPendingCommentRange && isLineBreak(ch)) { + pendingHasTrailingNewLine = true; } - break; - - default: - if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { - pos++; - continue; - } - break; - } - return pos; + pos++; + continue; + } + break scan; } } - // All conflict markers consist of the same character repeated seven times. If it is - // a <<<<<<< or >>>>>>> marker then it is also followed by a space. - const mergeConflictMarkerLength = "<<<<<<<".length; + if (hasPendingCommentRange) { + accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); + } - function isConflictMarkerTrivia(text: string, pos: number) { - Debug.assert(pos >= 0); + return accumulator; +} - // Conflict markers must be at the start of a line. - if (pos === 0 || isLineBreak(text.charCodeAt(pos - 1))) { - const ch = text.charCodeAt(pos); +export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; +export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; +export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { + return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ false, cb, state); +} - if ((pos + mergeConflictMarkerLength) < text.length) { - for (let i = 0; i < mergeConflictMarkerLength; i++) { - if (text.charCodeAt(pos + i) !== ch) { - return false; - } - } +export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; +export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; +export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { + return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ true, cb, state); +} - return ch === CharacterCodes.equals || - text.charCodeAt(pos + mergeConflictMarkerLength) === CharacterCodes.space; - } - } +export function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { + return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ false, cb, state, initial); +} - return false; - } +export function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { + return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ true, cb, state, initial); +} - function scanConflictMarkerTrivia(text: string, pos: number, error?: (diag: DiagnosticMessage, pos?: number, len?: number) => void) { - if (error) { - error(Diagnostics.Merge_conflict_marker_encountered, pos, mergeConflictMarkerLength); - } +function appendCommentRange(pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, _state: any, comments: CommentRange[]) { + if (!comments) { + comments = []; + } - const ch = text.charCodeAt(pos); - const len = text.length; + comments.push({ kind, pos, end, hasTrailingNewLine }); + return comments; +} - if (ch === CharacterCodes.lessThan || ch === CharacterCodes.greaterThan) { - while (pos < len && !isLineBreak(text.charCodeAt(pos))) { - pos++; - } - } - else { - Debug.assert(ch === CharacterCodes.bar || ch === CharacterCodes.equals); - // Consume everything from the start of a ||||||| or ======= marker to the start - // of the next ======= or >>>>>>> marker. - while (pos < len) { - const currentChar = text.charCodeAt(pos); - if ((currentChar === CharacterCodes.equals || currentChar === CharacterCodes.greaterThan) && currentChar !== ch && isConflictMarkerTrivia(text, pos)) { - break; - } +export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] | undefined { + return reduceEachLeadingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); +} - pos++; - } - } +export function getTrailingCommentRanges(text: string, pos: number): CommentRange[] | undefined { + return reduceEachTrailingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); +} - return pos; +/** Optionally, get the shebang */ +export function getShebang(text: string): string | undefined { + const match = shebangTriviaRegex.exec(text); + if (match) { + return match[0]; } +} - const shebangTriviaRegex = /^#!.*/; +export function isIdentifierStart(ch: number, languageVersion: ScriptTarget | undefined): boolean { + return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || + ch === CharacterCodes.$ || ch === CharacterCodes._ || + ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierStart(ch, languageVersion); +} - /*@internal*/ - export function isShebangTrivia(text: string, pos: number) { - // Shebangs check must only be done at the start of the file - Debug.assert(pos === 0); - return shebangTriviaRegex.test(text); - } +export function isIdentifierPart(ch: number, languageVersion: ScriptTarget | undefined, identifierVariant?: LanguageVariant): boolean { + return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || + ch >= CharacterCodes._0 && ch <= CharacterCodes._9 || ch === CharacterCodes.$ || ch === CharacterCodes._ || + // "-" and ":" are valid in JSX Identifiers + (identifierVariant === LanguageVariant.JSX ? (ch === CharacterCodes.minus || ch === CharacterCodes.colon) : false) || + ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierPart(ch, languageVersion); +} - /*@internal*/ - export function scanShebangTrivia(text: string, pos: number) { - const shebang = shebangTriviaRegex.exec(text)![0]; - pos = pos + shebang.length; - return pos; +/** @internal */ +export function isIdentifierText(name: string, languageVersion: ScriptTarget | undefined, identifierVariant?: LanguageVariant): boolean { + let ch = codePointAt(name, 0); + if (!isIdentifierStart(ch, languageVersion)) { + return false; } - /** - * Invokes a callback for each comment range following the provided position. - * - * Single-line comment ranges include the leading double-slash characters but not the ending - * line break. Multi-line comment ranges include the leading slash-asterisk and trailing - * asterisk-slash characters. - * - * @param reduce If true, accumulates the result of calling the callback in a fashion similar - * to reduceLeft. If false, iteration stops when the callback returns a truthy value. - * @param text The source text to scan. - * @param pos The position at which to start scanning. - * @param trailing If false, whitespace is skipped until the first line break and comments - * between that location and the next token are returned. If true, comments occurring - * between the given position and the next line break are returned. - * @param cb The callback to execute as each comment range is encountered. - * @param state A state value to pass to each iteration of the callback. - * @param initial An initial value to pass when accumulating results (when "reduce" is true). - * @returns If "reduce" is true, the accumulated value. If "reduce" is false, the first truthy - * return value of the callback. - */ - function iterateCommentRanges(reduce: boolean, text: string, pos: number, trailing: boolean, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U | undefined) => U, state: T, initial?: U): U | undefined { - let pendingPos!: number; - let pendingEnd!: number; - let pendingKind!: CommentKind; - let pendingHasTrailingNewLine!: boolean; - let hasPendingCommentRange = false; - let collecting = trailing; - let accumulator = initial; - if (pos === 0) { - collecting = true; - const shebang = getShebang(text); - if (shebang) { - pos = shebang.length; - } + for (let i = charSize(ch); i < name.length; i += charSize(ch)) { + if (!isIdentifierPart(ch = codePointAt(name, i), languageVersion, identifierVariant)) { + return false; } - scan: while (pos >= 0 && pos < text.length) { - const ch = text.charCodeAt(pos); - switch (ch) { - case CharacterCodes.carriageReturn: - if (text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: - pos++; - if (trailing) { - break scan; - } - - collecting = true; - if (hasPendingCommentRange) { - pendingHasTrailingNewLine = true; - } + } - continue; - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: - pos++; - continue; - case CharacterCodes.slash: - const nextChar = text.charCodeAt(pos + 1); - let hasTrailingNewLine = false; - if (nextChar === CharacterCodes.slash || nextChar === CharacterCodes.asterisk) { - const kind = nextChar === CharacterCodes.slash ? SyntaxKind.SingleLineCommentTrivia : SyntaxKind.MultiLineCommentTrivia; - const startPos = pos; - pos += 2; - if (nextChar === CharacterCodes.slash) { - while (pos < text.length) { - if (isLineBreak(text.charCodeAt(pos))) { - hasTrailingNewLine = true; - break; - } - pos++; - } - } - else { - while (pos < text.length) { - if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - break; - } - pos++; - } - } + return true; +} - if (collecting) { - if (hasPendingCommentRange) { - accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); - if (!reduce && accumulator) { - // If we are not reducing and we have a truthy result, return it. - return accumulator; - } - } +// Creates a scanner over a (possibly unspecified) range of a piece of text. +export function createScanner(languageVersion: ScriptTarget, + skipTrivia: boolean, + languageVariant = LanguageVariant.Standard, + textInitial?: string, + onError?: ErrorCallback, + start?: number, + length?: number): Scanner { + + let text = textInitial!; + + // Current position (end position of text of current token) + let pos: number; + + + // end of text + let end: number; + + // Start position of whitespace before current token + let startPos: number; + + // Start position of text of current token + let tokenPos: number; + + let token: SyntaxKind; + let tokenValue!: string; + let tokenFlags: TokenFlags; + + let commentDirectives: CommentDirective[] | undefined; + let inJSDocType = 0; + + setText(text, start, length); + + const scanner: Scanner = { + getStartPos: () => startPos, + getTextPos: () => pos, + getToken: () => token, + getTokenPos: () => tokenPos, + getTokenText: () => text.substring(tokenPos, pos), + getTokenValue: () => tokenValue, + hasUnicodeEscape: () => (tokenFlags & TokenFlags.UnicodeEscape) !== 0, + hasExtendedUnicodeEscape: () => (tokenFlags & TokenFlags.ExtendedUnicodeEscape) !== 0, + hasPrecedingLineBreak: () => (tokenFlags & TokenFlags.PrecedingLineBreak) !== 0, + hasPrecedingJSDocComment: () => (tokenFlags & TokenFlags.PrecedingJSDocComment) !== 0, + isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord, + isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord, + isUnterminated: () => (tokenFlags & TokenFlags.Unterminated) !== 0, + getCommentDirectives: () => commentDirectives, + getNumericLiteralFlags: () => tokenFlags & TokenFlags.NumericLiteralFlags, + getTokenFlags: () => tokenFlags, + reScanGreaterToken, + reScanAsteriskEqualsToken, + reScanSlashToken, + reScanTemplateToken, + reScanTemplateHeadOrNoSubstitutionTemplate, + scanJsxIdentifier, + scanJsxAttributeValue, + reScanJsxAttributeValue, + reScanJsxToken, + reScanLessThanToken, + reScanHashToken, + reScanQuestionToken, + reScanInvalidIdentifier, + scanJsxToken, + scanJsDocToken, + scan, + getText, + clearCommentDirectives, + setText, + setScriptTarget, + setLanguageVariant, + setOnError, + setTextPos, + setInJSDocType, + tryScan, + lookAhead, + scanRange, + }; - pendingPos = startPos; - pendingEnd = pos; - pendingKind = kind; - pendingHasTrailingNewLine = hasTrailingNewLine; - hasPendingCommentRange = true; - } + if (Debug.isDebugging) { + Object.defineProperty(scanner, "__debugShowCurrentPositionInText", { + get: () => { + const text = scanner.getText(); + return text.slice(0, scanner.getStartPos()) + "║" + text.slice(scanner.getStartPos()); + }, + }); + } - continue; - } - break scan; - default: - if (ch > CharacterCodes.maxAsciiCharacter && (isWhiteSpaceLike(ch))) { - if (hasPendingCommentRange && isLineBreak(ch)) { - pendingHasTrailingNewLine = true; - } - pos++; - continue; - } - break scan; - } - } + return scanner; - if (hasPendingCommentRange) { - accumulator = cb(pendingPos, pendingEnd, pendingKind, pendingHasTrailingNewLine, state, accumulator); + function error(message: DiagnosticMessage): void; + function error(message: DiagnosticMessage, errPos: number, length: number): void; + function error(message: DiagnosticMessage, errPos: number = pos, length?: number): void { + if (onError) { + const oldPos = pos; + pos = errPos; + onError(message, length || 0); + pos = oldPos; } - - return accumulator; - } - - export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; - export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; - export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { - return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ false, cb, state); } - export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; - export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; - export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { - return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ true, cb, state); + function scanNumberFragment(): string { + let start = pos; + let allowSeparator = false; + let isPreviousTokenSeparator = false; + let result = ""; + while (true) { + const ch = text.charCodeAt(pos); + if (ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (allowSeparator) { + allowSeparator = false; + isPreviousTokenSeparator = true; + result += text.substring(start, pos); + } + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + } + else { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + } + pos++; + start = pos; + continue; + } + if (isDigit(ch)) { + allowSeparator = true; + isPreviousTokenSeparator = false; + pos++; + continue; + } + break; + } + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); + } + return result + text.substring(start, pos); } - export function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { - return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ false, cb, state, initial); - } + function scanNumber(): { type: SyntaxKind, value: string } { + const start = pos; + const mainFragment = scanNumberFragment(); + let decimalFragment: string | undefined; + let scientificFragment: string | undefined; + if (text.charCodeAt(pos) === CharacterCodes.dot) { + pos++; + decimalFragment = scanNumberFragment(); + } + let end = pos; + if (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e) { + pos++; + tokenFlags |= TokenFlags.Scientific; + if (text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === CharacterCodes.minus) pos++; + const preNumericPart = pos; + const finalFragment = scanNumberFragment(); + if (!finalFragment) { + error(Diagnostics.Digit_expected); + } + else { + scientificFragment = text.substring(end, preNumericPart) + finalFragment; + end = pos; + } + } + let result: string; + if (tokenFlags & TokenFlags.ContainsSeparator) { + result = mainFragment; + if (decimalFragment) { + result += "." + decimalFragment; + } + if (scientificFragment) { + result += scientificFragment; + } + } + else { + result = text.substring(start, end); // No need to use all the fragments; no _ removal needed + } - export function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U) { - return iterateCommentRanges(/*reduce*/ true, text, pos, /*trailing*/ true, cb, state, initial); + if (decimalFragment !== undefined || tokenFlags & TokenFlags.Scientific) { + checkForIdentifierStartAfterNumericLiteral(start, decimalFragment === undefined && !!(tokenFlags & TokenFlags.Scientific)); + return { + type: SyntaxKind.NumericLiteral, + value: "" + +result // if value is not an integer, it can be safely coerced to a number + }; + } + else { + tokenValue = result; + const type = checkBigIntSuffix(); // if value is an integer, check whether it is a bigint + checkForIdentifierStartAfterNumericLiteral(start); + return { type, value: tokenValue }; + } } - function appendCommentRange(pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, _state: any, comments: CommentRange[]) { - if (!comments) { - comments = []; + function checkForIdentifierStartAfterNumericLiteral(numericStart: number, isScientific?: boolean) { + if (!isIdentifierStart(codePointAt(text, pos), languageVersion)) { + return; } - comments.push({ kind, pos, end, hasTrailingNewLine }); - return comments; - } - - export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] | undefined { - return reduceEachLeadingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); - } + const identifierStart = pos; + const { length } = scanIdentifierParts(); - export function getTrailingCommentRanges(text: string, pos: number): CommentRange[] | undefined { - return reduceEachTrailingCommentRange(text, pos, appendCommentRange, /*state*/ undefined, /*initial*/ undefined); + if (length === 1 && text[identifierStart] === "n") { + if (isScientific) { + error(Diagnostics.A_bigint_literal_cannot_use_exponential_notation, numericStart, identifierStart - numericStart + 1); + } + else { + error(Diagnostics.A_bigint_literal_must_be_an_integer, numericStart, identifierStart - numericStart + 1); + } + } + else { + error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length); + pos = identifierStart; + } } - /** Optionally, get the shebang */ - export function getShebang(text: string): string | undefined { - const match = shebangTriviaRegex.exec(text); - if (match) { - return match[0]; + function scanOctalDigits(): number { + const start = pos; + while (isOctalDigit(text.charCodeAt(pos))) { + pos++; } + return +(text.substring(start, pos)); } - export function isIdentifierStart(ch: number, languageVersion: ScriptTarget | undefined): boolean { - return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || - ch === CharacterCodes.$ || ch === CharacterCodes._ || - ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierStart(ch, languageVersion); + /** + * Scans the given number of hexadecimal digits in the text, + * returning -1 if the given number is unavailable. + */ + function scanExactNumberOfHexDigits(count: number, canHaveSeparators: boolean): number { + const valueString = scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ false, canHaveSeparators); + return valueString ? parseInt(valueString, 16) : -1; } - export function isIdentifierPart(ch: number, languageVersion: ScriptTarget | undefined, identifierVariant?: LanguageVariant): boolean { - return ch >= CharacterCodes.A && ch <= CharacterCodes.Z || ch >= CharacterCodes.a && ch <= CharacterCodes.z || - ch >= CharacterCodes._0 && ch <= CharacterCodes._9 || ch === CharacterCodes.$ || ch === CharacterCodes._ || - // "-" and ":" are valid in JSX Identifiers - (identifierVariant === LanguageVariant.JSX ? (ch === CharacterCodes.minus || ch === CharacterCodes.colon) : false) || - ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierPart(ch, languageVersion); + /** + * Scans as many hexadecimal digits as are available in the text, + * returning "" if the given number of digits was unavailable. + */ + function scanMinimumNumberOfHexDigits(count: number, canHaveSeparators: boolean): string { + return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ true, canHaveSeparators); } - /* @internal */ - export function isIdentifierText(name: string, languageVersion: ScriptTarget | undefined, identifierVariant?: LanguageVariant): boolean { - let ch = codePointAt(name, 0); - if (!isIdentifierStart(ch, languageVersion)) { - return false; + function scanHexDigits(minCount: number, scanAsManyAsPossible: boolean, canHaveSeparators: boolean): string { + let valueChars: number[] = []; + let allowSeparator = false; + let isPreviousTokenSeparator = false; + while (valueChars.length < minCount || scanAsManyAsPossible) { + let ch = text.charCodeAt(pos); + if (canHaveSeparators && ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (allowSeparator) { + allowSeparator = false; + isPreviousTokenSeparator = true; + } + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + } + else { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + } + pos++; + continue; + } + allowSeparator = canHaveSeparators; + if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) { + ch += CharacterCodes.a - CharacterCodes.A; // standardize hex literals to lowercase + } + else if (!((ch >= CharacterCodes._0 && ch <= CharacterCodes._9) || + (ch >= CharacterCodes.a && ch <= CharacterCodes.f) + )) { + break; + } + valueChars.push(ch); + pos++; + isPreviousTokenSeparator = false; } + if (valueChars.length < minCount) { + valueChars = []; + } + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); + } + return String.fromCharCode(...valueChars); + } - for (let i = charSize(ch); i < name.length; i += charSize(ch)) { - if (!isIdentifierPart(ch = codePointAt(name, i), languageVersion, identifierVariant)) { - return false; + function scanString(jsxAttributeString = false): string { + const quote = text.charCodeAt(pos); + pos++; + let result = ""; + let start = pos; + while (true) { + if (pos >= end) { + result += text.substring(start, pos); + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_string_literal); + break; + } + const ch = text.charCodeAt(pos); + if (ch === quote) { + result += text.substring(start, pos); + pos++; + break; + } + if (ch === CharacterCodes.backslash && !jsxAttributeString) { + result += text.substring(start, pos); + result += scanEscapeSequence(); + start = pos; + continue; + } + if (isLineBreak(ch) && !jsxAttributeString) { + result += text.substring(start, pos); + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_string_literal); + break; } + pos++; } - - return true; + return result; } - // Creates a scanner over a (possibly unspecified) range of a piece of text. - export function createScanner(languageVersion: ScriptTarget, - skipTrivia: boolean, - languageVariant = LanguageVariant.Standard, - textInitial?: string, - onError?: ErrorCallback, - start?: number, - length?: number): Scanner { - - let text = textInitial!; - - // Current position (end position of text of current token) - let pos: number; - - - // end of text - let end: number; - - // Start position of whitespace before current token - let startPos: number; - - // Start position of text of current token - let tokenPos: number; + /** + * Sets the current 'tokenValue' and returns a NoSubstitutionTemplateLiteral or + * a literal component of a TemplateExpression. + */ + function scanTemplateAndSetTokenValue(isTaggedTemplate: boolean): SyntaxKind { + const startedWithBacktick = text.charCodeAt(pos) === CharacterCodes.backtick; - let token: SyntaxKind; - let tokenValue!: string; - let tokenFlags: TokenFlags; + pos++; + let start = pos; + let contents = ""; + let resultingToken: SyntaxKind; - let commentDirectives: CommentDirective[] | undefined; - let inJSDocType = 0; + while (true) { + if (pos >= end) { + contents += text.substring(start, pos); + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_template_literal); + resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; + break; + } - setText(text, start, length); + const currChar = text.charCodeAt(pos); - const scanner: Scanner = { - getStartPos: () => startPos, - getTextPos: () => pos, - getToken: () => token, - getTokenPos: () => tokenPos, - getTokenText: () => text.substring(tokenPos, pos), - getTokenValue: () => tokenValue, - hasUnicodeEscape: () => (tokenFlags & TokenFlags.UnicodeEscape) !== 0, - hasExtendedUnicodeEscape: () => (tokenFlags & TokenFlags.ExtendedUnicodeEscape) !== 0, - hasPrecedingLineBreak: () => (tokenFlags & TokenFlags.PrecedingLineBreak) !== 0, - hasPrecedingJSDocComment: () => (tokenFlags & TokenFlags.PrecedingJSDocComment) !== 0, - isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord, - isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord, - isUnterminated: () => (tokenFlags & TokenFlags.Unterminated) !== 0, - getCommentDirectives: () => commentDirectives, - getNumericLiteralFlags: () => tokenFlags & TokenFlags.NumericLiteralFlags, - getTokenFlags: () => tokenFlags, - reScanGreaterToken, - reScanAsteriskEqualsToken, - reScanSlashToken, - reScanTemplateToken, - reScanTemplateHeadOrNoSubstitutionTemplate, - scanJsxIdentifier, - scanJsxAttributeValue, - reScanJsxAttributeValue, - reScanJsxToken, - reScanLessThanToken, - reScanHashToken, - reScanQuestionToken, - reScanInvalidIdentifier, - scanJsxToken, - scanJsDocToken, - scan, - getText, - clearCommentDirectives, - setText, - setScriptTarget, - setLanguageVariant, - setOnError, - setTextPos, - setInJSDocType, - tryScan, - lookAhead, - scanRange, - }; - - if (Debug.isDebugging) { - Object.defineProperty(scanner, "__debugShowCurrentPositionInText", { - get: () => { - const text = scanner.getText(); - return text.slice(0, scanner.getStartPos()) + "║" + text.slice(scanner.getStartPos()); - }, - }); - } - - return scanner; - - function error(message: DiagnosticMessage): void; - function error(message: DiagnosticMessage, errPos: number, length: number): void; - function error(message: DiagnosticMessage, errPos: number = pos, length?: number): void { - if (onError) { - const oldPos = pos; - pos = errPos; - onError(message, length || 0); - pos = oldPos; + // '`' + if (currChar === CharacterCodes.backtick) { + contents += text.substring(start, pos); + pos++; + resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; + break; } - } - function scanNumberFragment(): string { - let start = pos; - let allowSeparator = false; - let isPreviousTokenSeparator = false; - let result = ""; - while (true) { - const ch = text.charCodeAt(pos); - if (ch === CharacterCodes._) { - tokenFlags |= TokenFlags.ContainsSeparator; - if (allowSeparator) { - allowSeparator = false; - isPreviousTokenSeparator = true; - result += text.substring(start, pos); - } - else if (isPreviousTokenSeparator) { - error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); - } - else { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); - } - pos++; - start = pos; - continue; - } - if (isDigit(ch)) { - allowSeparator = true; - isPreviousTokenSeparator = false; - pos++; - continue; - } + // '${' + if (currChar === CharacterCodes.$ && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.openBrace) { + contents += text.substring(start, pos); + pos += 2; + resultingToken = startedWithBacktick ? SyntaxKind.TemplateHead : SyntaxKind.TemplateMiddle; break; } - if (text.charCodeAt(pos - 1) === CharacterCodes._) { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); - } - return result + text.substring(start, pos); - } - function scanNumber(): { type: SyntaxKind, value: string } { - const start = pos; - const mainFragment = scanNumberFragment(); - let decimalFragment: string | undefined; - let scientificFragment: string | undefined; - if (text.charCodeAt(pos) === CharacterCodes.dot) { - pos++; - decimalFragment = scanNumberFragment(); + // Escape character + if (currChar === CharacterCodes.backslash) { + contents += text.substring(start, pos); + contents += scanEscapeSequence(isTaggedTemplate); + start = pos; + continue; } - let end = pos; - if (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e) { + + // Speculated ECMAScript 6 Spec 11.8.6.1: + // and LineTerminatorSequences are normalized to for Template Values + if (currChar === CharacterCodes.carriageReturn) { + contents += text.substring(start, pos); pos++; - tokenFlags |= TokenFlags.Scientific; - if (text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === CharacterCodes.minus) pos++; - const preNumericPart = pos; - const finalFragment = scanNumberFragment(); - if (!finalFragment) { - error(Diagnostics.Digit_expected); - } - else { - scientificFragment = text.substring(end, preNumericPart) + finalFragment; - end = pos; - } - } - let result: string; - if (tokenFlags & TokenFlags.ContainsSeparator) { - result = mainFragment; - if (decimalFragment) { - result += "." + decimalFragment; - } - if (scientificFragment) { - result += scientificFragment; + + if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { + pos++; } - } - else { - result = text.substring(start, end); // No need to use all the fragments; no _ removal needed - } - if (decimalFragment !== undefined || tokenFlags & TokenFlags.Scientific) { - checkForIdentifierStartAfterNumericLiteral(start, decimalFragment === undefined && !!(tokenFlags & TokenFlags.Scientific)); - return { - type: SyntaxKind.NumericLiteral, - value: "" + +result // if value is not an integer, it can be safely coerced to a number - }; - } - else { - tokenValue = result; - const type = checkBigIntSuffix(); // if value is an integer, check whether it is a bigint - checkForIdentifierStartAfterNumericLiteral(start); - return { type, value: tokenValue }; + contents += "\n"; + start = pos; + continue; } + + pos++; } - function checkForIdentifierStartAfterNumericLiteral(numericStart: number, isScientific?: boolean) { - if (!isIdentifierStart(codePointAt(text, pos), languageVersion)) { - return; - } + Debug.assert(resultingToken !== undefined); - const identifierStart = pos; - const { length } = scanIdentifierParts(); + tokenValue = contents; + return resultingToken; + } - if (length === 1 && text[identifierStart] === "n") { - if (isScientific) { - error(Diagnostics.A_bigint_literal_cannot_use_exponential_notation, numericStart, identifierStart - numericStart + 1); + function scanEscapeSequence(isTaggedTemplate?: boolean): string { + const start = pos; + pos++; + if (pos >= end) { + error(Diagnostics.Unexpected_end_of_text); + return ""; + } + const ch = text.charCodeAt(pos); + pos++; + switch (ch) { + case CharacterCodes._0: + // '\01' + if (isTaggedTemplate && pos < end && isDigit(text.charCodeAt(pos))) { + pos++; + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); } - else { - error(Diagnostics.A_bigint_literal_must_be_an_integer, numericStart, identifierStart - numericStart + 1); + return "\0"; + case CharacterCodes.b: + return "\b"; + case CharacterCodes.t: + return "\t"; + case CharacterCodes.n: + return "\n"; + case CharacterCodes.v: + return "\v"; + case CharacterCodes.f: + return "\f"; + case CharacterCodes.r: + return "\r"; + case CharacterCodes.singleQuote: + return "\'"; + case CharacterCodes.doubleQuote: + return "\""; + case CharacterCodes.u: + if (isTaggedTemplate) { + // '\u' or '\u0' or '\u00' or '\u000' + for (let escapePos = pos; escapePos < pos + 4; escapePos++) { + if (escapePos < end && !isHexDigit(text.charCodeAt(escapePos)) && text.charCodeAt(escapePos) !== CharacterCodes.openBrace) { + pos = escapePos; + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); + } + } } - } - else { - error(Diagnostics.An_identifier_or_keyword_cannot_immediately_follow_a_numeric_literal, identifierStart, length); - pos = identifierStart; - } - } + // '\u{DDDDDDDD}' + if (pos < end && text.charCodeAt(pos) === CharacterCodes.openBrace) { + pos++; - function scanOctalDigits(): number { - const start = pos; - while (isOctalDigit(text.charCodeAt(pos))) { - pos++; - } - return +(text.substring(start, pos)); - } - - /** - * Scans the given number of hexadecimal digits in the text, - * returning -1 if the given number is unavailable. - */ - function scanExactNumberOfHexDigits(count: number, canHaveSeparators: boolean): number { - const valueString = scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ false, canHaveSeparators); - return valueString ? parseInt(valueString, 16) : -1; - } - - /** - * Scans as many hexadecimal digits as are available in the text, - * returning "" if the given number of digits was unavailable. - */ - function scanMinimumNumberOfHexDigits(count: number, canHaveSeparators: boolean): string { - return scanHexDigits(/*minCount*/ count, /*scanAsManyAsPossible*/ true, canHaveSeparators); - } - - function scanHexDigits(minCount: number, scanAsManyAsPossible: boolean, canHaveSeparators: boolean): string { - let valueChars: number[] = []; - let allowSeparator = false; - let isPreviousTokenSeparator = false; - while (valueChars.length < minCount || scanAsManyAsPossible) { - let ch = text.charCodeAt(pos); - if (canHaveSeparators && ch === CharacterCodes._) { - tokenFlags |= TokenFlags.ContainsSeparator; - if (allowSeparator) { - allowSeparator = false; - isPreviousTokenSeparator = true; - } - else if (isPreviousTokenSeparator) { - error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); + // '\u{' + if (isTaggedTemplate && !isHexDigit(text.charCodeAt(pos))) { + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); } - else { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); + + if (isTaggedTemplate) { + const savePos = pos; + const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); + const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; + + // '\u{Not Code Point' or '\u{CodePoint' + if (!isCodePoint(escapedValue) || text.charCodeAt(pos) !== CharacterCodes.closeBrace) { + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); + } + else { + pos = savePos; + } } - pos++; - continue; - } - allowSeparator = canHaveSeparators; - if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) { - ch += CharacterCodes.a - CharacterCodes.A; // standardize hex literals to lowercase + tokenFlags |= TokenFlags.ExtendedUnicodeEscape; + return scanExtendedUnicodeEscape(); } - else if (!((ch >= CharacterCodes._0 && ch <= CharacterCodes._9) || - (ch >= CharacterCodes.a && ch <= CharacterCodes.f) - )) { - break; - } - valueChars.push(ch); - pos++; - isPreviousTokenSeparator = false; - } - if (valueChars.length < minCount) { - valueChars = []; - } - if (text.charCodeAt(pos - 1) === CharacterCodes._) { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); - } - return String.fromCharCode(...valueChars); - } - - function scanString(jsxAttributeString = false): string { - const quote = text.charCodeAt(pos); - pos++; - let result = ""; - let start = pos; - while (true) { - if (pos >= end) { - result += text.substring(start, pos); - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_string_literal); - break; - } - const ch = text.charCodeAt(pos); - if (ch === quote) { - result += text.substring(start, pos); - pos++; - break; - } - if (ch === CharacterCodes.backslash && !jsxAttributeString) { - result += text.substring(start, pos); - result += scanEscapeSequence(); - start = pos; - continue; - } - if (isLineBreak(ch) && !jsxAttributeString) { - result += text.substring(start, pos); - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_string_literal); - break; - } - pos++; - } - return result; - } - /** - * Sets the current 'tokenValue' and returns a NoSubstitutionTemplateLiteral or - * a literal component of a TemplateExpression. - */ - function scanTemplateAndSetTokenValue(isTaggedTemplate: boolean): SyntaxKind { - const startedWithBacktick = text.charCodeAt(pos) === CharacterCodes.backtick; + tokenFlags |= TokenFlags.UnicodeEscape; + // '\uDDDD' + return scanHexadecimalEscape(/*numDigits*/ 4); - pos++; - let start = pos; - let contents = ""; - let resultingToken: SyntaxKind; - - while (true) { - if (pos >= end) { - contents += text.substring(start, pos); - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_template_literal); - resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; - break; + case CharacterCodes.x: + if (isTaggedTemplate) { + if (!isHexDigit(text.charCodeAt(pos))) { + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); + } + else if (!isHexDigit(text.charCodeAt(pos + 1))) { + pos++; + tokenFlags |= TokenFlags.ContainsInvalidEscape; + return text.substring(start, pos); + } } + // '\xDD' + return scanHexadecimalEscape(/*numDigits*/ 2); - const currChar = text.charCodeAt(pos); - - // '`' - if (currChar === CharacterCodes.backtick) { - contents += text.substring(start, pos); + // when encountering a LineContinuation (i.e. a backslash and a line terminator sequence), + // the line terminator is interpreted to be "the empty code unit sequence". + case CharacterCodes.carriageReturn: + if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { pos++; - resultingToken = startedWithBacktick ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail; - break; - } - - // '${' - if (currChar === CharacterCodes.$ && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.openBrace) { - contents += text.substring(start, pos); - pos += 2; - resultingToken = startedWithBacktick ? SyntaxKind.TemplateHead : SyntaxKind.TemplateMiddle; - break; - } - - // Escape character - if (currChar === CharacterCodes.backslash) { - contents += text.substring(start, pos); - contents += scanEscapeSequence(isTaggedTemplate); - start = pos; - continue; } + // falls through + case CharacterCodes.lineFeed: + case CharacterCodes.lineSeparator: + case CharacterCodes.paragraphSeparator: + return ""; + default: + return String.fromCharCode(ch); + } + } - // Speculated ECMAScript 6 Spec 11.8.6.1: - // and LineTerminatorSequences are normalized to for Template Values - if (currChar === CharacterCodes.carriageReturn) { - contents += text.substring(start, pos); - pos++; - - if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; - } - - contents += "\n"; - start = pos; - continue; - } + function scanHexadecimalEscape(numDigits: number): string { + const escapedValue = scanExactNumberOfHexDigits(numDigits, /*canHaveSeparators*/ false); - pos++; - } + if (escapedValue >= 0) { + return String.fromCharCode(escapedValue); + } + else { + error(Diagnostics.Hexadecimal_digit_expected); + return ""; + } + } - Debug.assert(resultingToken !== undefined); + function scanExtendedUnicodeEscape(): string { + const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); + const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; + let isInvalidExtendedEscape = false; - tokenValue = contents; - return resultingToken; + // Validate the value of the digit + if (escapedValue < 0) { + error(Diagnostics.Hexadecimal_digit_expected); + isInvalidExtendedEscape = true; + } + else if (escapedValue > 0x10FFFF) { + error(Diagnostics.An_extended_Unicode_escape_value_must_be_between_0x0_and_0x10FFFF_inclusive); + isInvalidExtendedEscape = true; } - function scanEscapeSequence(isTaggedTemplate?: boolean): string { - const start = pos; - pos++; - if (pos >= end) { - error(Diagnostics.Unexpected_end_of_text); - return ""; - } - const ch = text.charCodeAt(pos); + if (pos >= end) { + error(Diagnostics.Unexpected_end_of_text); + isInvalidExtendedEscape = true; + } + else if (text.charCodeAt(pos) === CharacterCodes.closeBrace) { + // Only swallow the following character up if it's a '}'. pos++; - switch (ch) { - case CharacterCodes._0: - // '\01' - if (isTaggedTemplate && pos < end && isDigit(text.charCodeAt(pos))) { - pos++; - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - return "\0"; - case CharacterCodes.b: - return "\b"; - case CharacterCodes.t: - return "\t"; - case CharacterCodes.n: - return "\n"; - case CharacterCodes.v: - return "\v"; - case CharacterCodes.f: - return "\f"; - case CharacterCodes.r: - return "\r"; - case CharacterCodes.singleQuote: - return "\'"; - case CharacterCodes.doubleQuote: - return "\""; - case CharacterCodes.u: - if (isTaggedTemplate) { - // '\u' or '\u0' or '\u00' or '\u000' - for (let escapePos = pos; escapePos < pos + 4; escapePos++) { - if (escapePos < end && !isHexDigit(text.charCodeAt(escapePos)) && text.charCodeAt(escapePos) !== CharacterCodes.openBrace) { - pos = escapePos; - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - } - } - // '\u{DDDDDDDD}' - if (pos < end && text.charCodeAt(pos) === CharacterCodes.openBrace) { - pos++; - - // '\u{' - if (isTaggedTemplate && !isHexDigit(text.charCodeAt(pos))) { - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - - if (isTaggedTemplate) { - const savePos = pos; - const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); - const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; - - // '\u{Not Code Point' or '\u{CodePoint' - if (!isCodePoint(escapedValue) || text.charCodeAt(pos) !== CharacterCodes.closeBrace) { - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - else { - pos = savePos; - } - } - tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - return scanExtendedUnicodeEscape(); - } - - tokenFlags |= TokenFlags.UnicodeEscape; - // '\uDDDD' - return scanHexadecimalEscape(/*numDigits*/ 4); - - case CharacterCodes.x: - if (isTaggedTemplate) { - if (!isHexDigit(text.charCodeAt(pos))) { - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - else if (!isHexDigit(text.charCodeAt(pos + 1))) { - pos++; - tokenFlags |= TokenFlags.ContainsInvalidEscape; - return text.substring(start, pos); - } - } - // '\xDD' - return scanHexadecimalEscape(/*numDigits*/ 2); + } + else { + error(Diagnostics.Unterminated_Unicode_escape_sequence); + isInvalidExtendedEscape = true; + } - // when encountering a LineContinuation (i.e. a backslash and a line terminator sequence), - // the line terminator is interpreted to be "the empty code unit sequence". - case CharacterCodes.carriageReturn: - if (pos < end && text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: - case CharacterCodes.lineSeparator: - case CharacterCodes.paragraphSeparator: - return ""; - default: - return String.fromCharCode(ch); - } + if (isInvalidExtendedEscape) { + return ""; } - function scanHexadecimalEscape(numDigits: number): string { - const escapedValue = scanExactNumberOfHexDigits(numDigits, /*canHaveSeparators*/ false); + return utf16EncodeAsString(escapedValue); + } - if (escapedValue >= 0) { - return String.fromCharCode(escapedValue); - } - else { - error(Diagnostics.Hexadecimal_digit_expected); - return ""; - } + // Current character is known to be a backslash. Check for Unicode escape of the form '\uXXXX' + // and return code point value if valid Unicode escape is found. Otherwise return -1. + function peekUnicodeEscape(): number { + if (pos + 5 < end && text.charCodeAt(pos + 1) === CharacterCodes.u) { + const start = pos; + pos += 2; + const value = scanExactNumberOfHexDigits(4, /*canHaveSeparators*/ false); + pos = start; + return value; } + return -1; + } + - function scanExtendedUnicodeEscape(): string { + function peekExtendedUnicodeEscape(): number { + if (codePointAt(text, pos + 1) === CharacterCodes.u && codePointAt(text, pos + 2) === CharacterCodes.openBrace) { + const start = pos; + pos += 3; const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; - let isInvalidExtendedEscape = false; - - // Validate the value of the digit - if (escapedValue < 0) { - error(Diagnostics.Hexadecimal_digit_expected); - isInvalidExtendedEscape = true; - } - else if (escapedValue > 0x10FFFF) { - error(Diagnostics.An_extended_Unicode_escape_value_must_be_between_0x0_and_0x10FFFF_inclusive); - isInvalidExtendedEscape = true; - } + pos = start; + return escapedValue; + } + return -1; + } - if (pos >= end) { - error(Diagnostics.Unexpected_end_of_text); - isInvalidExtendedEscape = true; + function scanIdentifierParts(): string { + let result = ""; + let start = pos; + while (pos < end) { + let ch = codePointAt(text, pos); + if (isIdentifierPart(ch, languageVersion)) { + pos += charSize(ch); } - else if (text.charCodeAt(pos) === CharacterCodes.closeBrace) { - // Only swallow the following character up if it's a '}'. - pos++; + else if (ch === CharacterCodes.backslash) { + ch = peekExtendedUnicodeEscape(); + if (ch >= 0 && isIdentifierPart(ch, languageVersion)) { + pos += 3; + tokenFlags |= TokenFlags.ExtendedUnicodeEscape; + result += scanExtendedUnicodeEscape(); + start = pos; + continue; + } + ch = peekUnicodeEscape(); + if (!(ch >= 0 && isIdentifierPart(ch, languageVersion))) { + break; + } + tokenFlags |= TokenFlags.UnicodeEscape; + result += text.substring(start, pos); + result += utf16EncodeAsString(ch); + // Valid Unicode escape is always six characters + pos += 6; + start = pos; } else { - error(Diagnostics.Unterminated_Unicode_escape_sequence); - isInvalidExtendedEscape = true; - } - - if (isInvalidExtendedEscape) { - return ""; - } - - return utf16EncodeAsString(escapedValue); - } - - // Current character is known to be a backslash. Check for Unicode escape of the form '\uXXXX' - // and return code point value if valid Unicode escape is found. Otherwise return -1. - function peekUnicodeEscape(): number { - if (pos + 5 < end && text.charCodeAt(pos + 1) === CharacterCodes.u) { - const start = pos; - pos += 2; - const value = scanExactNumberOfHexDigits(4, /*canHaveSeparators*/ false); - pos = start; - return value; + break; } - return -1; } + result += text.substring(start, pos); + return result; + } - - function peekExtendedUnicodeEscape(): number { - if (codePointAt(text, pos + 1) === CharacterCodes.u && codePointAt(text, pos + 2) === CharacterCodes.openBrace) { - const start = pos; - pos += 3; - const escapedValueString = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ false); - const escapedValue = escapedValueString ? parseInt(escapedValueString, 16) : -1; - pos = start; - return escapedValue; + function getIdentifierToken(): SyntaxKind.Identifier | KeywordSyntaxKind { + // Reserved words are between 2 and 12 characters long and start with a lowercase letter + const len = tokenValue.length; + if (len >= 2 && len <= 12) { + const ch = tokenValue.charCodeAt(0); + if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { + const keyword = textToKeyword.get(tokenValue); + if (keyword !== undefined) { + return token = keyword; + } } - return -1; } + return token = SyntaxKind.Identifier; + } - function scanIdentifierParts(): string { - let result = ""; - let start = pos; - while (pos < end) { - let ch = codePointAt(text, pos); - if (isIdentifierPart(ch, languageVersion)) { - pos += charSize(ch); + function scanBinaryOrOctalDigits(base: 2 | 8): string { + let value = ""; + // For counting number of digits; Valid binaryIntegerLiteral must have at least one binary digit following B or b. + // Similarly valid octalIntegerLiteral must have at least one octal digit following o or O. + let separatorAllowed = false; + let isPreviousTokenSeparator = false; + while (true) { + const ch = text.charCodeAt(pos); + // Numeric separators are allowed anywhere within a numeric literal, except not at the beginning, or following another separator + if (ch === CharacterCodes._) { + tokenFlags |= TokenFlags.ContainsSeparator; + if (separatorAllowed) { + separatorAllowed = false; + isPreviousTokenSeparator = true; } - else if (ch === CharacterCodes.backslash) { - ch = peekExtendedUnicodeEscape(); - if (ch >= 0 && isIdentifierPart(ch, languageVersion)) { - pos += 3; - tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - result += scanExtendedUnicodeEscape(); - start = pos; - continue; - } - ch = peekUnicodeEscape(); - if (!(ch >= 0 && isIdentifierPart(ch, languageVersion))) { - break; - } - tokenFlags |= TokenFlags.UnicodeEscape; - result += text.substring(start, pos); - result += utf16EncodeAsString(ch); - // Valid Unicode escape is always six characters - pos += 6; - start = pos; + else if (isPreviousTokenSeparator) { + error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); } else { - break; + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); } + pos++; + continue; } - result += text.substring(start, pos); - return result; - } - - function getIdentifierToken(): SyntaxKind.Identifier | KeywordSyntaxKind { - // Reserved words are between 2 and 12 characters long and start with a lowercase letter - const len = tokenValue.length; - if (len >= 2 && len <= 12) { - const ch = tokenValue.charCodeAt(0); - if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { - const keyword = textToKeyword.get(tokenValue); - if (keyword !== undefined) { - return token = keyword; - } - } + separatorAllowed = true; + if (!isDigit(ch) || ch - CharacterCodes._0 >= base) { + break; } - return token = SyntaxKind.Identifier; + value += text[pos]; + pos++; + isPreviousTokenSeparator = false; + } + if (text.charCodeAt(pos - 1) === CharacterCodes._) { + // Literal ends with underscore - not allowed + error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); } + return value; + } - function scanBinaryOrOctalDigits(base: 2 | 8): string { - let value = ""; - // For counting number of digits; Valid binaryIntegerLiteral must have at least one binary digit following B or b. - // Similarly valid octalIntegerLiteral must have at least one octal digit following o or O. - let separatorAllowed = false; - let isPreviousTokenSeparator = false; - while (true) { - const ch = text.charCodeAt(pos); - // Numeric separators are allowed anywhere within a numeric literal, except not at the beginning, or following another separator - if (ch === CharacterCodes._) { - tokenFlags |= TokenFlags.ContainsSeparator; - if (separatorAllowed) { - separatorAllowed = false; - isPreviousTokenSeparator = true; - } - else if (isPreviousTokenSeparator) { - error(Diagnostics.Multiple_consecutive_numeric_separators_are_not_permitted, pos, 1); - } - else { - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos, 1); - } - pos++; - continue; - } - separatorAllowed = true; - if (!isDigit(ch) || ch - CharacterCodes._0 >= base) { - break; - } - value += text[pos]; - pos++; - isPreviousTokenSeparator = false; + function checkBigIntSuffix(): SyntaxKind { + if (text.charCodeAt(pos) === CharacterCodes.n) { + tokenValue += "n"; + // Use base 10 instead of base 2 or base 8 for shorter literals + if (tokenFlags & TokenFlags.BinaryOrOctalSpecifier) { + tokenValue = parsePseudoBigInt(tokenValue) + "n"; } - if (text.charCodeAt(pos - 1) === CharacterCodes._) { - // Literal ends with underscore - not allowed - error(Diagnostics.Numeric_separators_are_not_allowed_here, pos - 1, 1); - } - return value; + pos++; + return SyntaxKind.BigIntLiteral; + } + else { // not a bigint, so can convert to number in simplified form + // Number() may not support 0b or 0o, so use parseInt() instead + const numericValue = tokenFlags & TokenFlags.BinarySpecifier + ? parseInt(tokenValue.slice(2), 2) // skip "0b" + : tokenFlags & TokenFlags.OctalSpecifier + ? parseInt(tokenValue.slice(2), 8) // skip "0o" + : +tokenValue; + tokenValue = "" + numericValue; + return SyntaxKind.NumericLiteral; } + } - function checkBigIntSuffix(): SyntaxKind { - if (text.charCodeAt(pos) === CharacterCodes.n) { - tokenValue += "n"; - // Use base 10 instead of base 2 or base 8 for shorter literals - if (tokenFlags & TokenFlags.BinaryOrOctalSpecifier) { - tokenValue = parsePseudoBigInt(tokenValue) + "n"; - } - pos++; - return SyntaxKind.BigIntLiteral; - } - else { // not a bigint, so can convert to number in simplified form - // Number() may not support 0b or 0o, so use parseInt() instead - const numericValue = tokenFlags & TokenFlags.BinarySpecifier - ? parseInt(tokenValue.slice(2), 2) // skip "0b" - : tokenFlags & TokenFlags.OctalSpecifier - ? parseInt(tokenValue.slice(2), 8) // skip "0o" - : +tokenValue; - tokenValue = "" + numericValue; - return SyntaxKind.NumericLiteral; + function scan(): SyntaxKind { + startPos = pos; + tokenFlags = TokenFlags.None; + let asteriskSeen = false; + while (true) { + tokenPos = pos; + if (pos >= end) { + return token = SyntaxKind.EndOfFileToken; } - } + const ch = codePointAt(text, pos); - function scan(): SyntaxKind { - startPos = pos; - tokenFlags = TokenFlags.None; - let asteriskSeen = false; - while (true) { - tokenPos = pos; - if (pos >= end) { - return token = SyntaxKind.EndOfFileToken; + // Special handling for shebang + if (ch === CharacterCodes.hash && pos === 0 && isShebangTrivia(text, pos)) { + pos = scanShebangTrivia(text, pos); + if (skipTrivia) { + continue; + } + else { + return token = SyntaxKind.ShebangTrivia; } - const ch = codePointAt(text, pos); + } - // Special handling for shebang - if (ch === CharacterCodes.hash && pos === 0 && isShebangTrivia(text, pos)) { - pos = scanShebangTrivia(text, pos); + switch (ch) { + case CharacterCodes.lineFeed: + case CharacterCodes.carriageReturn: + tokenFlags |= TokenFlags.PrecedingLineBreak; if (skipTrivia) { + pos++; continue; } else { - return token = SyntaxKind.ShebangTrivia; + if (ch === CharacterCodes.carriageReturn && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { + // consume both CR and LF + pos += 2; + } + else { + pos++; + } + return token = SyntaxKind.NewLineTrivia; } - } - - switch (ch) { - case CharacterCodes.lineFeed: - case CharacterCodes.carriageReturn: - tokenFlags |= TokenFlags.PrecedingLineBreak; - if (skipTrivia) { + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + case CharacterCodes.nonBreakingSpace: + case CharacterCodes.ogham: + case CharacterCodes.enQuad: + case CharacterCodes.emQuad: + case CharacterCodes.enSpace: + case CharacterCodes.emSpace: + case CharacterCodes.threePerEmSpace: + case CharacterCodes.fourPerEmSpace: + case CharacterCodes.sixPerEmSpace: + case CharacterCodes.figureSpace: + case CharacterCodes.punctuationSpace: + case CharacterCodes.thinSpace: + case CharacterCodes.hairSpace: + case CharacterCodes.zeroWidthSpace: + case CharacterCodes.narrowNoBreakSpace: + case CharacterCodes.mathematicalSpace: + case CharacterCodes.ideographicSpace: + case CharacterCodes.byteOrderMark: + if (skipTrivia) { + pos++; + continue; + } + else { + while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { pos++; - continue; } - else { - if (ch === CharacterCodes.carriageReturn && pos + 1 < end && text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) { - // consume both CR and LF - pos += 2; - } - else { - pos++; + return token = SyntaxKind.WhitespaceTrivia; + } + case CharacterCodes.exclamation: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.ExclamationEqualsEqualsToken; + } + return pos += 2, token = SyntaxKind.ExclamationEqualsToken; + } + pos++; + return token = SyntaxKind.ExclamationToken; + case CharacterCodes.doubleQuote: + case CharacterCodes.singleQuote: + tokenValue = scanString(); + return token = SyntaxKind.StringLiteral; + case CharacterCodes.backtick: + return token = scanTemplateAndSetTokenValue(/* isTaggedTemplate */ false); + case CharacterCodes.percent: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.PercentEqualsToken; + } + pos++; + return token = SyntaxKind.PercentToken; + case CharacterCodes.ampersand: + if (text.charCodeAt(pos + 1) === CharacterCodes.ampersand) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.AmpersandAmpersandEqualsToken; + } + return pos += 2, token = SyntaxKind.AmpersandAmpersandToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.AmpersandEqualsToken; + } + pos++; + return token = SyntaxKind.AmpersandToken; + case CharacterCodes.openParen: + pos++; + return token = SyntaxKind.OpenParenToken; + case CharacterCodes.closeParen: + pos++; + return token = SyntaxKind.CloseParenToken; + case CharacterCodes.asterisk: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.AsteriskEqualsToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.AsteriskAsteriskEqualsToken; + } + return pos += 2, token = SyntaxKind.AsteriskAsteriskToken; + } + pos++; + if (inJSDocType && !asteriskSeen && (tokenFlags & TokenFlags.PrecedingLineBreak)) { + // decoration at the start of a JSDoc comment line + asteriskSeen = true; + continue; + } + return token = SyntaxKind.AsteriskToken; + case CharacterCodes.plus: + if (text.charCodeAt(pos + 1) === CharacterCodes.plus) { + return pos += 2, token = SyntaxKind.PlusPlusToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.PlusEqualsToken; + } + pos++; + return token = SyntaxKind.PlusToken; + case CharacterCodes.comma: + pos++; + return token = SyntaxKind.CommaToken; + case CharacterCodes.minus: + if (text.charCodeAt(pos + 1) === CharacterCodes.minus) { + return pos += 2, token = SyntaxKind.MinusMinusToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.MinusEqualsToken; + } + pos++; + return token = SyntaxKind.MinusToken; + case CharacterCodes.dot: + if (isDigit(text.charCodeAt(pos + 1))) { + tokenValue = scanNumber().value; + return token = SyntaxKind.NumericLiteral; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.dot && text.charCodeAt(pos + 2) === CharacterCodes.dot) { + return pos += 3, token = SyntaxKind.DotDotDotToken; + } + pos++; + return token = SyntaxKind.DotToken; + case CharacterCodes.slash: + // Single-line comment + if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + + while (pos < end) { + if (isLineBreak(text.charCodeAt(pos))) { + break; } - return token = SyntaxKind.NewLineTrivia; + pos++; } - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: - case CharacterCodes.nonBreakingSpace: - case CharacterCodes.ogham: - case CharacterCodes.enQuad: - case CharacterCodes.emQuad: - case CharacterCodes.enSpace: - case CharacterCodes.emSpace: - case CharacterCodes.threePerEmSpace: - case CharacterCodes.fourPerEmSpace: - case CharacterCodes.sixPerEmSpace: - case CharacterCodes.figureSpace: - case CharacterCodes.punctuationSpace: - case CharacterCodes.thinSpace: - case CharacterCodes.hairSpace: - case CharacterCodes.zeroWidthSpace: - case CharacterCodes.narrowNoBreakSpace: - case CharacterCodes.mathematicalSpace: - case CharacterCodes.ideographicSpace: - case CharacterCodes.byteOrderMark: + + commentDirectives = appendIfCommentDirective( + commentDirectives, + text.slice(tokenPos, pos), + commentDirectiveRegExSingleLine, + tokenPos, + ); + if (skipTrivia) { - pos++; continue; } else { - while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { - pos++; - } - return token = SyntaxKind.WhitespaceTrivia; - } - case CharacterCodes.exclamation: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.ExclamationEqualsEqualsToken; - } - return pos += 2, token = SyntaxKind.ExclamationEqualsToken; - } - pos++; - return token = SyntaxKind.ExclamationToken; - case CharacterCodes.doubleQuote: - case CharacterCodes.singleQuote: - tokenValue = scanString(); - return token = SyntaxKind.StringLiteral; - case CharacterCodes.backtick: - return token = scanTemplateAndSetTokenValue(/* isTaggedTemplate */ false); - case CharacterCodes.percent: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.PercentEqualsToken; - } - pos++; - return token = SyntaxKind.PercentToken; - case CharacterCodes.ampersand: - if (text.charCodeAt(pos + 1) === CharacterCodes.ampersand) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.AmpersandAmpersandEqualsToken; - } - return pos += 2, token = SyntaxKind.AmpersandAmpersandToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.AmpersandEqualsToken; - } - pos++; - return token = SyntaxKind.AmpersandToken; - case CharacterCodes.openParen: - pos++; - return token = SyntaxKind.OpenParenToken; - case CharacterCodes.closeParen: - pos++; - return token = SyntaxKind.CloseParenToken; - case CharacterCodes.asterisk: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.AsteriskEqualsToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.AsteriskAsteriskEqualsToken; - } - return pos += 2, token = SyntaxKind.AsteriskAsteriskToken; - } - pos++; - if (inJSDocType && !asteriskSeen && (tokenFlags & TokenFlags.PrecedingLineBreak)) { - // decoration at the start of a JSDoc comment line - asteriskSeen = true; - continue; - } - return token = SyntaxKind.AsteriskToken; - case CharacterCodes.plus: - if (text.charCodeAt(pos + 1) === CharacterCodes.plus) { - return pos += 2, token = SyntaxKind.PlusPlusToken; + return token = SyntaxKind.SingleLineCommentTrivia; } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.PlusEqualsToken; - } - pos++; - return token = SyntaxKind.PlusToken; - case CharacterCodes.comma: - pos++; - return token = SyntaxKind.CommaToken; - case CharacterCodes.minus: - if (text.charCodeAt(pos + 1) === CharacterCodes.minus) { - return pos += 2, token = SyntaxKind.MinusMinusToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.MinusEqualsToken; - } - pos++; - return token = SyntaxKind.MinusToken; - case CharacterCodes.dot: - if (isDigit(text.charCodeAt(pos + 1))) { - tokenValue = scanNumber().value; - return token = SyntaxKind.NumericLiteral; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.dot && text.charCodeAt(pos + 2) === CharacterCodes.dot) { - return pos += 3, token = SyntaxKind.DotDotDotToken; + } + // Multi-line comment + if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { + pos += 2; + if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) !== CharacterCodes.slash) { + tokenFlags |= TokenFlags.PrecedingJSDocComment; } - pos++; - return token = SyntaxKind.DotToken; - case CharacterCodes.slash: - // Single-line comment - if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - while (pos < end) { - if (isLineBreak(text.charCodeAt(pos))) { - break; - } - pos++; + let commentClosed = false; + let lastLineStart = tokenPos; + while (pos < end) { + const ch = text.charCodeAt(pos); + + if (ch === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + commentClosed = true; + break; } - commentDirectives = appendIfCommentDirective( - commentDirectives, - text.slice(tokenPos, pos), - commentDirectiveRegExSingleLine, - tokenPos, - ); + pos++; - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.SingleLineCommentTrivia; + if (isLineBreak(ch)) { + lastLineStart = pos; + tokenFlags |= TokenFlags.PrecedingLineBreak; } } - // Multi-line comment - if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { - pos += 2; - if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) !== CharacterCodes.slash) { - tokenFlags |= TokenFlags.PrecedingJSDocComment; - } - - let commentClosed = false; - let lastLineStart = tokenPos; - while (pos < end) { - const ch = text.charCodeAt(pos); - if (ch === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - commentClosed = true; - break; - } + commentDirectives = appendIfCommentDirective(commentDirectives, text.slice(lastLineStart, pos), commentDirectiveRegExMultiLine, lastLineStart); - pos++; - - if (isLineBreak(ch)) { - lastLineStart = pos; - tokenFlags |= TokenFlags.PrecedingLineBreak; - } - } - - commentDirectives = appendIfCommentDirective(commentDirectives, text.slice(lastLineStart, pos), commentDirectiveRegExMultiLine, lastLineStart); + if (!commentClosed) { + error(Diagnostics.Asterisk_Slash_expected); + } + if (skipTrivia) { + continue; + } + else { if (!commentClosed) { - error(Diagnostics.Asterisk_Slash_expected); - } - - if (skipTrivia) { - continue; - } - else { - if (!commentClosed) { - tokenFlags |= TokenFlags.Unterminated; - } - return token = SyntaxKind.MultiLineCommentTrivia; + tokenFlags |= TokenFlags.Unterminated; } + return token = SyntaxKind.MultiLineCommentTrivia; } + } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.SlashEqualsToken; - } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.SlashEqualsToken; + } - pos++; - return token = SyntaxKind.SlashToken; + pos++; + return token = SyntaxKind.SlashToken; - case CharacterCodes._0: - if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.X || text.charCodeAt(pos + 1) === CharacterCodes.x)) { - pos += 2; - tokenValue = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ true); - if (!tokenValue) { - error(Diagnostics.Hexadecimal_digit_expected); - tokenValue = "0"; - } - tokenValue = "0x" + tokenValue; - tokenFlags |= TokenFlags.HexSpecifier; - return token = checkBigIntSuffix(); + case CharacterCodes._0: + if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.X || text.charCodeAt(pos + 1) === CharacterCodes.x)) { + pos += 2; + tokenValue = scanMinimumNumberOfHexDigits(1, /*canHaveSeparators*/ true); + if (!tokenValue) { + error(Diagnostics.Hexadecimal_digit_expected); + tokenValue = "0"; } - else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.B || text.charCodeAt(pos + 1) === CharacterCodes.b)) { - pos += 2; - tokenValue = scanBinaryOrOctalDigits(/* base */ 2); - if (!tokenValue) { - error(Diagnostics.Binary_digit_expected); - tokenValue = "0"; - } - tokenValue = "0b" + tokenValue; - tokenFlags |= TokenFlags.BinarySpecifier; - return token = checkBigIntSuffix(); + tokenValue = "0x" + tokenValue; + tokenFlags |= TokenFlags.HexSpecifier; + return token = checkBigIntSuffix(); + } + else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.B || text.charCodeAt(pos + 1) === CharacterCodes.b)) { + pos += 2; + tokenValue = scanBinaryOrOctalDigits(/* base */ 2); + if (!tokenValue) { + error(Diagnostics.Binary_digit_expected); + tokenValue = "0"; } - else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.O || text.charCodeAt(pos + 1) === CharacterCodes.o)) { - pos += 2; - tokenValue = scanBinaryOrOctalDigits(/* base */ 8); - if (!tokenValue) { - error(Diagnostics.Octal_digit_expected); - tokenValue = "0"; - } - tokenValue = "0o" + tokenValue; - tokenFlags |= TokenFlags.OctalSpecifier; - return token = checkBigIntSuffix(); + tokenValue = "0b" + tokenValue; + tokenFlags |= TokenFlags.BinarySpecifier; + return token = checkBigIntSuffix(); + } + else if (pos + 2 < end && (text.charCodeAt(pos + 1) === CharacterCodes.O || text.charCodeAt(pos + 1) === CharacterCodes.o)) { + pos += 2; + tokenValue = scanBinaryOrOctalDigits(/* base */ 8); + if (!tokenValue) { + error(Diagnostics.Octal_digit_expected); + tokenValue = "0"; } - // Try to parse as an octal - if (pos + 1 < end && isOctalDigit(text.charCodeAt(pos + 1))) { - tokenValue = "" + scanOctalDigits(); - tokenFlags |= TokenFlags.Octal; - return token = SyntaxKind.NumericLiteral; + tokenValue = "0o" + tokenValue; + tokenFlags |= TokenFlags.OctalSpecifier; + return token = checkBigIntSuffix(); + } + // Try to parse as an octal + if (pos + 1 < end && isOctalDigit(text.charCodeAt(pos + 1))) { + tokenValue = "" + scanOctalDigits(); + tokenFlags |= TokenFlags.Octal; + return token = SyntaxKind.NumericLiteral; + } + // This fall-through is a deviation from the EcmaScript grammar. The grammar says that a leading zero + // can only be followed by an octal digit, a dot, or the end of the number literal. However, we are being + // permissive and allowing decimal digits of the form 08* and 09* (which many browsers also do). + // falls through + case CharacterCodes._1: + case CharacterCodes._2: + case CharacterCodes._3: + case CharacterCodes._4: + case CharacterCodes._5: + case CharacterCodes._6: + case CharacterCodes._7: + case CharacterCodes._8: + case CharacterCodes._9: + ({ type: token, value: tokenValue } = scanNumber()); + return token; + case CharacterCodes.colon: + pos++; + return token = SyntaxKind.ColonToken; + case CharacterCodes.semicolon: + pos++; + return token = SyntaxKind.SemicolonToken; + case CharacterCodes.lessThan: + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + if (skipTrivia) { + continue; } - // This fall-through is a deviation from the EcmaScript grammar. The grammar says that a leading zero - // can only be followed by an octal digit, a dot, or the end of the number literal. However, we are being - // permissive and allowing decimal digits of the form 08* and 09* (which many browsers also do). - // falls through - case CharacterCodes._1: - case CharacterCodes._2: - case CharacterCodes._3: - case CharacterCodes._4: - case CharacterCodes._5: - case CharacterCodes._6: - case CharacterCodes._7: - case CharacterCodes._8: - case CharacterCodes._9: - ({ type: token, value: tokenValue } = scanNumber()); - return token; - case CharacterCodes.colon: - pos++; - return token = SyntaxKind.ColonToken; - case CharacterCodes.semicolon: - pos++; - return token = SyntaxKind.SemicolonToken; - case CharacterCodes.lessThan: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.ConflictMarkerTrivia; - } + else { + return token = SyntaxKind.ConflictMarkerTrivia; } + } - if (text.charCodeAt(pos + 1) === CharacterCodes.lessThan) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.LessThanLessThanEqualsToken; - } - return pos += 2, token = SyntaxKind.LessThanLessThanToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.LessThanEqualsToken; + if (text.charCodeAt(pos + 1) === CharacterCodes.lessThan) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.LessThanLessThanEqualsToken; } - if (languageVariant === LanguageVariant.JSX && - text.charCodeAt(pos + 1) === CharacterCodes.slash && - text.charCodeAt(pos + 2) !== CharacterCodes.asterisk) { - return pos += 2, token = SyntaxKind.LessThanSlashToken; + return pos += 2, token = SyntaxKind.LessThanLessThanToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.LessThanEqualsToken; + } + if (languageVariant === LanguageVariant.JSX && + text.charCodeAt(pos + 1) === CharacterCodes.slash && + text.charCodeAt(pos + 2) !== CharacterCodes.asterisk) { + return pos += 2, token = SyntaxKind.LessThanSlashToken; + } + pos++; + return token = SyntaxKind.LessThanToken; + case CharacterCodes.equals: + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + if (skipTrivia) { + continue; } - pos++; - return token = SyntaxKind.LessThanToken; - case CharacterCodes.equals: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.ConflictMarkerTrivia; - } + else { + return token = SyntaxKind.ConflictMarkerTrivia; } + } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.EqualsEqualsEqualsToken; - } - return pos += 2, token = SyntaxKind.EqualsEqualsToken; + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.EqualsEqualsEqualsToken; } - if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { - return pos += 2, token = SyntaxKind.EqualsGreaterThanToken; + return pos += 2, token = SyntaxKind.EqualsEqualsToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { + return pos += 2, token = SyntaxKind.EqualsGreaterThanToken; + } + pos++; + return token = SyntaxKind.EqualsToken; + case CharacterCodes.greaterThan: + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + if (skipTrivia) { + continue; } - pos++; - return token = SyntaxKind.EqualsToken; - case CharacterCodes.greaterThan: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.ConflictMarkerTrivia; - } + else { + return token = SyntaxKind.ConflictMarkerTrivia; } + } - pos++; - return token = SyntaxKind.GreaterThanToken; - case CharacterCodes.question: - if (text.charCodeAt(pos + 1) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 2))) { - return pos += 2, token = SyntaxKind.QuestionDotToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.question) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.QuestionQuestionEqualsToken; - } - return pos += 2, token = SyntaxKind.QuestionQuestionToken; + pos++; + return token = SyntaxKind.GreaterThanToken; + case CharacterCodes.question: + if (text.charCodeAt(pos + 1) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 2))) { + return pos += 2, token = SyntaxKind.QuestionDotToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.question) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.QuestionQuestionEqualsToken; } - pos++; - return token = SyntaxKind.QuestionToken; - case CharacterCodes.openBracket: - pos++; - return token = SyntaxKind.OpenBracketToken; - case CharacterCodes.closeBracket: - pos++; - return token = SyntaxKind.CloseBracketToken; - case CharacterCodes.caret: - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.CaretEqualsToken; + return pos += 2, token = SyntaxKind.QuestionQuestionToken; + } + pos++; + return token = SyntaxKind.QuestionToken; + case CharacterCodes.openBracket: + pos++; + return token = SyntaxKind.OpenBracketToken; + case CharacterCodes.closeBracket: + pos++; + return token = SyntaxKind.CloseBracketToken; + case CharacterCodes.caret: + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.CaretEqualsToken; + } + pos++; + return token = SyntaxKind.CaretToken; + case CharacterCodes.openBrace: + pos++; + return token = SyntaxKind.OpenBraceToken; + case CharacterCodes.bar: + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + if (skipTrivia) { + continue; } - pos++; - return token = SyntaxKind.CaretToken; - case CharacterCodes.openBrace: - pos++; - return token = SyntaxKind.OpenBraceToken; - case CharacterCodes.bar: - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - if (skipTrivia) { - continue; - } - else { - return token = SyntaxKind.ConflictMarkerTrivia; - } + else { + return token = SyntaxKind.ConflictMarkerTrivia; } + } - if (text.charCodeAt(pos + 1) === CharacterCodes.bar) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.BarBarEqualsToken; - } - return pos += 2, token = SyntaxKind.BarBarToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.BarEqualsToken; + if (text.charCodeAt(pos + 1) === CharacterCodes.bar) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.BarBarEqualsToken; } + return pos += 2, token = SyntaxKind.BarBarToken; + } + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.BarEqualsToken; + } + pos++; + return token = SyntaxKind.BarToken; + case CharacterCodes.closeBrace: + pos++; + return token = SyntaxKind.CloseBraceToken; + case CharacterCodes.tilde: + pos++; + return token = SyntaxKind.TildeToken; + case CharacterCodes.at: + pos++; + return token = SyntaxKind.AtToken; + case CharacterCodes.backslash: + const extendedCookedChar = peekExtendedUnicodeEscape(); + if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { + pos += 3; + tokenFlags |= TokenFlags.ExtendedUnicodeEscape; + tokenValue = scanExtendedUnicodeEscape() + scanIdentifierParts(); + return token = getIdentifierToken(); + } + + const cookedChar = peekUnicodeEscape(); + if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { + pos += 6; + tokenFlags |= TokenFlags.UnicodeEscape; + tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts(); + return token = getIdentifierToken(); + } + + error(Diagnostics.Invalid_character); + pos++; + return token = SyntaxKind.Unknown; + case CharacterCodes.hash: + if (pos !== 0 && text[pos + 1] === "!") { + error(Diagnostics.can_only_be_used_at_the_start_of_a_file); pos++; - return token = SyntaxKind.BarToken; - case CharacterCodes.closeBrace: - pos++; - return token = SyntaxKind.CloseBraceToken; - case CharacterCodes.tilde: - pos++; - return token = SyntaxKind.TildeToken; - case CharacterCodes.at: + return token = SyntaxKind.Unknown; + } + + const charAfterHash = codePointAt(text, pos + 1); + if (charAfterHash === CharacterCodes.backslash) { pos++; - return token = SyntaxKind.AtToken; - case CharacterCodes.backslash: const extendedCookedChar = peekExtendedUnicodeEscape(); if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { pos += 3; tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - tokenValue = scanExtendedUnicodeEscape() + scanIdentifierParts(); - return token = getIdentifierToken(); + tokenValue = "#" + scanExtendedUnicodeEscape() + scanIdentifierParts(); + return token = SyntaxKind.PrivateIdentifier; } const cookedChar = peekUnicodeEscape(); if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { pos += 6; tokenFlags |= TokenFlags.UnicodeEscape; - tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts(); - return token = getIdentifierToken(); + tokenValue = "#" + String.fromCharCode(cookedChar) + scanIdentifierParts(); + return token = SyntaxKind.PrivateIdentifier; } + pos--; + } - error(Diagnostics.Invalid_character); + if (isIdentifierStart(charAfterHash, languageVersion)) { pos++; - return token = SyntaxKind.Unknown; - case CharacterCodes.hash: - if (pos !== 0 && text[pos + 1] === "!") { - error(Diagnostics.can_only_be_used_at_the_start_of_a_file); - pos++; - return token = SyntaxKind.Unknown; - } - - const charAfterHash = codePointAt(text, pos + 1); - if (charAfterHash === CharacterCodes.backslash) { - pos++; - const extendedCookedChar = peekExtendedUnicodeEscape(); - if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { - pos += 3; - tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - tokenValue = "#" + scanExtendedUnicodeEscape() + scanIdentifierParts(); - return token = SyntaxKind.PrivateIdentifier; - } - - const cookedChar = peekUnicodeEscape(); - if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { - pos += 6; - tokenFlags |= TokenFlags.UnicodeEscape; - tokenValue = "#" + String.fromCharCode(cookedChar) + scanIdentifierParts(); - return token = SyntaxKind.PrivateIdentifier; - } - pos--; - } - - if (isIdentifierStart(charAfterHash, languageVersion)) { - pos++; - // We're relying on scanIdentifier's behavior and adjusting the token kind after the fact. - // Notably absent from this block is the fact that calling a function named "scanIdentifier", - // but identifiers don't include '#', and that function doesn't deal with it at all. - // This works because 'scanIdentifier' tries to reuse source characters and builds up substrings; - // however, it starts at the 'tokenPos' which includes the '#', and will "accidentally" prepend the '#' for us. - scanIdentifier(charAfterHash, languageVersion); - } - else { - tokenValue = "#"; - error(Diagnostics.Invalid_character, pos++, charSize(ch)); - } - return token = SyntaxKind.PrivateIdentifier; - default: - const identifierKind = scanIdentifier(ch, languageVersion); - if (identifierKind) { - return token = identifierKind; - } - else if (isWhiteSpaceSingleLine(ch)) { - pos += charSize(ch); - continue; - } - else if (isLineBreak(ch)) { - tokenFlags |= TokenFlags.PrecedingLineBreak; - pos += charSize(ch); - continue; - } - const size = charSize(ch); - error(Diagnostics.Invalid_character, pos, size); - pos += size; - return token = SyntaxKind.Unknown; - } + // We're relying on scanIdentifier's behavior and adjusting the token kind after the fact. + // Notably absent from this block is the fact that calling a function named "scanIdentifier", + // but identifiers don't include '#', and that function doesn't deal with it at all. + // This works because 'scanIdentifier' tries to reuse source characters and builds up substrings; + // however, it starts at the 'tokenPos' which includes the '#', and will "accidentally" prepend the '#' for us. + scanIdentifier(charAfterHash, languageVersion); + } + else { + tokenValue = "#"; + error(Diagnostics.Invalid_character, pos++, charSize(ch)); + } + return token = SyntaxKind.PrivateIdentifier; + default: + const identifierKind = scanIdentifier(ch, languageVersion); + if (identifierKind) { + return token = identifierKind; + } + else if (isWhiteSpaceSingleLine(ch)) { + pos += charSize(ch); + continue; + } + else if (isLineBreak(ch)) { + tokenFlags |= TokenFlags.PrecedingLineBreak; + pos += charSize(ch); + continue; + } + const size = charSize(ch); + error(Diagnostics.Invalid_character, pos, size); + pos += size; + return token = SyntaxKind.Unknown; } } + } - function reScanInvalidIdentifier(): SyntaxKind { - Debug.assert(token === SyntaxKind.Unknown, "'reScanInvalidIdentifier' should only be called when the current token is 'SyntaxKind.Unknown'."); - pos = tokenPos = startPos; - tokenFlags = 0; - const ch = codePointAt(text, pos); - const identifierKind = scanIdentifier(ch, ScriptTarget.ESNext); - if (identifierKind) { - return token = identifierKind; - } - pos += charSize(ch); - return token; // Still `SyntaKind.Unknown` - } + function reScanInvalidIdentifier(): SyntaxKind { + Debug.assert(token === SyntaxKind.Unknown, "'reScanInvalidIdentifier' should only be called when the current token is 'SyntaxKind.Unknown'."); + pos = tokenPos = startPos; + tokenFlags = 0; + const ch = codePointAt(text, pos); + const identifierKind = scanIdentifier(ch, ScriptTarget.ESNext); + if (identifierKind) { + return token = identifierKind; + } + pos += charSize(ch); + return token; // Still `SyntaKind.Unknown` + } - function scanIdentifier(startCharacter: number, languageVersion: ScriptTarget) { - let ch = startCharacter; - if (isIdentifierStart(ch, languageVersion)) { - pos += charSize(ch); - while (pos < end && isIdentifierPart(ch = codePointAt(text, pos), languageVersion)) pos += charSize(ch); - tokenValue = text.substring(tokenPos, pos); - if (ch === CharacterCodes.backslash) { - tokenValue += scanIdentifierParts(); - } - return getIdentifierToken(); + function scanIdentifier(startCharacter: number, languageVersion: ScriptTarget) { + let ch = startCharacter; + if (isIdentifierStart(ch, languageVersion)) { + pos += charSize(ch); + while (pos < end && isIdentifierPart(ch = codePointAt(text, pos), languageVersion)) pos += charSize(ch); + tokenValue = text.substring(tokenPos, pos); + if (ch === CharacterCodes.backslash) { + tokenValue += scanIdentifierParts(); } + return getIdentifierToken(); } + } - function reScanGreaterToken(): SyntaxKind { - if (token === SyntaxKind.GreaterThanToken) { - if (text.charCodeAt(pos) === CharacterCodes.greaterThan) { - if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { - if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { - return pos += 3, token = SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken; - } - return pos += 2, token = SyntaxKind.GreaterThanGreaterThanGreaterThanToken; - } - if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { - return pos += 2, token = SyntaxKind.GreaterThanGreaterThanEqualsToken; + function reScanGreaterToken(): SyntaxKind { + if (token === SyntaxKind.GreaterThanToken) { + if (text.charCodeAt(pos) === CharacterCodes.greaterThan) { + if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { + if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { + return pos += 3, token = SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken; } - pos++; - return token = SyntaxKind.GreaterThanGreaterThanToken; + return pos += 2, token = SyntaxKind.GreaterThanGreaterThanGreaterThanToken; } - if (text.charCodeAt(pos) === CharacterCodes.equals) { - pos++; - return token = SyntaxKind.GreaterThanEqualsToken; + if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { + return pos += 2, token = SyntaxKind.GreaterThanGreaterThanEqualsToken; } + pos++; + return token = SyntaxKind.GreaterThanGreaterThanToken; + } + if (text.charCodeAt(pos) === CharacterCodes.equals) { + pos++; + return token = SyntaxKind.GreaterThanEqualsToken; } - return token; } + return token; + } - function reScanAsteriskEqualsToken(): SyntaxKind { - Debug.assert(token === SyntaxKind.AsteriskEqualsToken, "'reScanAsteriskEqualsToken' should only be called on a '*='"); - pos = tokenPos + 1; - return token = SyntaxKind.EqualsToken; - } - - function reScanSlashToken(): SyntaxKind { - if (token === SyntaxKind.SlashToken || token === SyntaxKind.SlashEqualsToken) { - let p = tokenPos + 1; - let inEscape = false; - let inCharacterClass = false; - while (true) { - // If we reach the end of a file, or hit a newline, then this is an unterminated - // regex. Report error and return what we have so far. - if (p >= end) { - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_regular_expression_literal); - break; - } + function reScanAsteriskEqualsToken(): SyntaxKind { + Debug.assert(token === SyntaxKind.AsteriskEqualsToken, "'reScanAsteriskEqualsToken' should only be called on a '*='"); + pos = tokenPos + 1; + return token = SyntaxKind.EqualsToken; + } - const ch = text.charCodeAt(p); - if (isLineBreak(ch)) { - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_regular_expression_literal); - break; - } + function reScanSlashToken(): SyntaxKind { + if (token === SyntaxKind.SlashToken || token === SyntaxKind.SlashEqualsToken) { + let p = tokenPos + 1; + let inEscape = false; + let inCharacterClass = false; + while (true) { + // If we reach the end of a file, or hit a newline, then this is an unterminated + // regex. Report error and return what we have so far. + if (p >= end) { + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_regular_expression_literal); + break; + } - if (inEscape) { - // Parsing an escape character; - // reset the flag and just advance to the next char. - inEscape = false; - } - else if (ch === CharacterCodes.slash && !inCharacterClass) { - // A slash within a character class is permissible, - // but in general it signals the end of the regexp literal. - p++; - break; - } - else if (ch === CharacterCodes.openBracket) { - inCharacterClass = true; - } - else if (ch === CharacterCodes.backslash) { - inEscape = true; - } - else if (ch === CharacterCodes.closeBracket) { - inCharacterClass = false; - } - p++; + const ch = text.charCodeAt(p); + if (isLineBreak(ch)) { + tokenFlags |= TokenFlags.Unterminated; + error(Diagnostics.Unterminated_regular_expression_literal); + break; } - while (p < end && isIdentifierPart(text.charCodeAt(p), languageVersion)) { + if (inEscape) { + // Parsing an escape character; + // reset the flag and just advance to the next char. + inEscape = false; + } + else if (ch === CharacterCodes.slash && !inCharacterClass) { + // A slash within a character class is permissible, + // but in general it signals the end of the regexp literal. p++; + break; } - pos = p; - tokenValue = text.substring(tokenPos, pos); - token = SyntaxKind.RegularExpressionLiteral; - } - return token; - } - - function appendIfCommentDirective( - commentDirectives: CommentDirective[] | undefined, - text: string, - commentDirectiveRegEx: RegExp, - lineStart: number, - ) { - const type = getDirectiveFromComment(trimStringStart(text), commentDirectiveRegEx); - if (type === undefined) { - return commentDirectives; + else if (ch === CharacterCodes.openBracket) { + inCharacterClass = true; + } + else if (ch === CharacterCodes.backslash) { + inEscape = true; + } + else if (ch === CharacterCodes.closeBracket) { + inCharacterClass = false; + } + p++; } - return append( - commentDirectives, - { - range: { pos: lineStart, end: pos }, - type, - }, - ); - } - - function getDirectiveFromComment(text: string, commentDirectiveRegEx: RegExp) { - const match = commentDirectiveRegEx.exec(text); - if (!match) { - return undefined; + while (p < end && isIdentifierPart(text.charCodeAt(p), languageVersion)) { + p++; } + pos = p; + tokenValue = text.substring(tokenPos, pos); + token = SyntaxKind.RegularExpressionLiteral; + } + return token; + } - switch (match[1]) { - case "ts-expect-error": - return CommentDirectiveType.ExpectError; - - case "ts-ignore": - return CommentDirectiveType.Ignore; - } + function appendIfCommentDirective( + commentDirectives: CommentDirective[] | undefined, + text: string, + commentDirectiveRegEx: RegExp, + lineStart: number, + ) { + const type = getDirectiveFromComment(trimStringStart(text), commentDirectiveRegEx); + if (type === undefined) { + return commentDirectives; + } + + return append( + commentDirectives, + { + range: { pos: lineStart, end: pos }, + type, + }, + ); + } + function getDirectiveFromComment(text: string, commentDirectiveRegEx: RegExp) { + const match = commentDirectiveRegEx.exec(text); + if (!match) { return undefined; } - /** - * Unconditionally back up and scan a template expression portion. - */ - function reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind { - Debug.assert(token === SyntaxKind.CloseBraceToken, "'reScanTemplateToken' should only be called on a '}'"); - pos = tokenPos; - return token = scanTemplateAndSetTokenValue(isTaggedTemplate); + switch (match[1]) { + case "ts-expect-error": + return CommentDirectiveType.ExpectError; + + case "ts-ignore": + return CommentDirectiveType.Ignore; } - function reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind { - pos = tokenPos; - return token = scanTemplateAndSetTokenValue(/* isTaggedTemplate */ true); + return undefined; + } + + /** + * Unconditionally back up and scan a template expression portion. + */ + function reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind { + Debug.assert(token === SyntaxKind.CloseBraceToken, "'reScanTemplateToken' should only be called on a '}'"); + pos = tokenPos; + return token = scanTemplateAndSetTokenValue(isTaggedTemplate); + } + + function reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind { + pos = tokenPos; + return token = scanTemplateAndSetTokenValue(/* isTaggedTemplate */ true); + } + + function reScanJsxToken(allowMultilineJsxText = true): JsxTokenSyntaxKind { + pos = tokenPos = startPos; + return token = scanJsxToken(allowMultilineJsxText); + } + + function reScanLessThanToken(): SyntaxKind { + if (token === SyntaxKind.LessThanLessThanToken) { + pos = tokenPos + 1; + return token = SyntaxKind.LessThanToken; } + return token; + } - function reScanJsxToken(allowMultilineJsxText = true): JsxTokenSyntaxKind { - pos = tokenPos = startPos; - return token = scanJsxToken(allowMultilineJsxText); + function reScanHashToken(): SyntaxKind { + if (token === SyntaxKind.PrivateIdentifier) { + pos = tokenPos + 1; + return token = SyntaxKind.HashToken; } + return token; + } - function reScanLessThanToken(): SyntaxKind { - if (token === SyntaxKind.LessThanLessThanToken) { - pos = tokenPos + 1; - return token = SyntaxKind.LessThanToken; - } - return token; + function reScanQuestionToken(): SyntaxKind { + Debug.assert(token === SyntaxKind.QuestionQuestionToken, "'reScanQuestionToken' should only be called on a '??'"); + pos = tokenPos + 1; + return token = SyntaxKind.QuestionToken; + } + + function scanJsxToken(allowMultilineJsxText = true): JsxTokenSyntaxKind { + startPos = tokenPos = pos; + + if (pos >= end) { + return token = SyntaxKind.EndOfFileToken; } - function reScanHashToken(): SyntaxKind { - if (token === SyntaxKind.PrivateIdentifier) { - pos = tokenPos + 1; - return token = SyntaxKind.HashToken; + let char = text.charCodeAt(pos); + if (char === CharacterCodes.lessThan) { + if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { + pos += 2; + return token = SyntaxKind.LessThanSlashToken; } - return token; + pos++; + return token = SyntaxKind.LessThanToken; } - function reScanQuestionToken(): SyntaxKind { - Debug.assert(token === SyntaxKind.QuestionQuestionToken, "'reScanQuestionToken' should only be called on a '??'"); - pos = tokenPos + 1; - return token = SyntaxKind.QuestionToken; + if (char === CharacterCodes.openBrace) { + pos++; + return token = SyntaxKind.OpenBraceToken; } - function scanJsxToken(allowMultilineJsxText = true): JsxTokenSyntaxKind { - startPos = tokenPos = pos; + // First non-whitespace character on this line. + let firstNonWhitespace = 0; - if (pos >= end) { - return token = SyntaxKind.EndOfFileToken; - } + // These initial values are special because the first line is: + // firstNonWhitespace = 0 to indicate that we want leading whitespace, - let char = text.charCodeAt(pos); + while (pos < end) { + char = text.charCodeAt(pos); + if (char === CharacterCodes.openBrace) { + break; + } if (char === CharacterCodes.lessThan) { - if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { - pos += 2; - return token = SyntaxKind.LessThanSlashToken; + if (isConflictMarkerTrivia(text, pos)) { + pos = scanConflictMarkerTrivia(text, pos, error); + return token = SyntaxKind.ConflictMarkerTrivia; } - pos++; - return token = SyntaxKind.LessThanToken; + break; + } + if (char === CharacterCodes.greaterThan) { + error(Diagnostics.Unexpected_token_Did_you_mean_or_gt, pos, 1); + } + if (char === CharacterCodes.closeBrace) { + error(Diagnostics.Unexpected_token_Did_you_mean_or_rbrace, pos, 1); } - if (char === CharacterCodes.openBrace) { - pos++; - return token = SyntaxKind.OpenBraceToken; + // FirstNonWhitespace is 0, then we only see whitespaces so far. If we see a linebreak, we want to ignore that whitespaces. + // i.e (- : whitespace) + //
---- + //
becomes
+ // + //
----
becomes
----
+ if (isLineBreak(char) && firstNonWhitespace === 0) { + firstNonWhitespace = -1; + } + else if (!allowMultilineJsxText && isLineBreak(char) && firstNonWhitespace > 0) { + // Stop JsxText on each line during formatting. This allows the formatter to + // indent each line correctly. + break; } + else if (!isWhiteSpaceLike(char)) { + firstNonWhitespace = pos; + } + + pos++; + } - // First non-whitespace character on this line. - let firstNonWhitespace = 0; + tokenValue = text.substring(startPos, pos); - // These initial values are special because the first line is: - // firstNonWhitespace = 0 to indicate that we want leading whitespace, + return firstNonWhitespace === -1 ? SyntaxKind.JsxTextAllWhiteSpaces : SyntaxKind.JsxText; + } + // Scans a JSX identifier; these differ from normal identifiers in that + // they allow dashes + function scanJsxIdentifier(): SyntaxKind { + if (tokenIsIdentifierOrKeyword(token)) { + // An identifier or keyword has already been parsed - check for a `-` or a single instance of `:` and then append it and + // everything after it to the token + // Do note that this means that `scanJsxIdentifier` effectively _mutates_ the visible token without advancing to a new token + // Any caller should be expecting this behavior and should only read the pos or token value after calling it. + let namespaceSeparator = false; while (pos < end) { - char = text.charCodeAt(pos); - if (char === CharacterCodes.openBrace) { - break; - } - if (char === CharacterCodes.lessThan) { - if (isConflictMarkerTrivia(text, pos)) { - pos = scanConflictMarkerTrivia(text, pos, error); - return token = SyntaxKind.ConflictMarkerTrivia; - } - break; - } - if (char === CharacterCodes.greaterThan) { - error(Diagnostics.Unexpected_token_Did_you_mean_or_gt, pos, 1); - } - if (char === CharacterCodes.closeBrace) { - error(Diagnostics.Unexpected_token_Did_you_mean_or_rbrace, pos, 1); + const ch = text.charCodeAt(pos); + if (ch === CharacterCodes.minus) { + tokenValue += "-"; + pos++; + continue; } - - // FirstNonWhitespace is 0, then we only see whitespaces so far. If we see a linebreak, we want to ignore that whitespaces. - // i.e (- : whitespace) - //
---- - //
becomes
- // - //
----
becomes
----
- if (isLineBreak(char) && firstNonWhitespace === 0) { - firstNonWhitespace = -1; + else if (ch === CharacterCodes.colon && !namespaceSeparator) { + tokenValue += ":"; + pos++; + namespaceSeparator = true; + token = SyntaxKind.Identifier; // swap from keyword kind to identifier kind + continue; } - else if (!allowMultilineJsxText && isLineBreak(char) && firstNonWhitespace > 0) { - // Stop JsxText on each line during formatting. This allows the formatter to - // indent each line correctly. + const oldPos = pos; + tokenValue += scanIdentifierParts(); // reuse `scanIdentifierParts` so unicode escapes are handled + if (pos === oldPos) { break; } - else if (!isWhiteSpaceLike(char)) { - firstNonWhitespace = pos; - } - - pos++; } - - tokenValue = text.substring(startPos, pos); - - return firstNonWhitespace === -1 ? SyntaxKind.JsxTextAllWhiteSpaces : SyntaxKind.JsxText; - } - - // Scans a JSX identifier; these differ from normal identifiers in that - // they allow dashes - function scanJsxIdentifier(): SyntaxKind { - if (tokenIsIdentifierOrKeyword(token)) { - // An identifier or keyword has already been parsed - check for a `-` or a single instance of `:` and then append it and - // everything after it to the token - // Do note that this means that `scanJsxIdentifier` effectively _mutates_ the visible token without advancing to a new token - // Any caller should be expecting this behavior and should only read the pos or token value after calling it. - let namespaceSeparator = false; - while (pos < end) { - const ch = text.charCodeAt(pos); - if (ch === CharacterCodes.minus) { - tokenValue += "-"; - pos++; - continue; - } - else if (ch === CharacterCodes.colon && !namespaceSeparator) { - tokenValue += ":"; - pos++; - namespaceSeparator = true; - token = SyntaxKind.Identifier; // swap from keyword kind to identifier kind - continue; - } - const oldPos = pos; - tokenValue += scanIdentifierParts(); // reuse `scanIdentifierParts` so unicode escapes are handled - if (pos === oldPos) { - break; - } - } - // Do not include a trailing namespace separator in the token, since this is against the spec. - if (tokenValue.slice(-1) === ":") { - tokenValue = tokenValue.slice(0, -1); - pos--; - } - return getIdentifierToken(); + // Do not include a trailing namespace separator in the token, since this is against the spec. + if (tokenValue.slice(-1) === ":") { + tokenValue = tokenValue.slice(0, -1); + pos--; } - return token; + return getIdentifierToken(); } + return token; + } - function scanJsxAttributeValue(): SyntaxKind { - startPos = pos; - - switch (text.charCodeAt(pos)) { - case CharacterCodes.doubleQuote: - case CharacterCodes.singleQuote: - tokenValue = scanString(/*jsxAttributeString*/ true); - return token = SyntaxKind.StringLiteral; - default: - // If this scans anything other than `{`, it's a parse error. - return scan(); - } - } + function scanJsxAttributeValue(): SyntaxKind { + startPos = pos; - function reScanJsxAttributeValue(): SyntaxKind { - pos = tokenPos = startPos; - return scanJsxAttributeValue(); + switch (text.charCodeAt(pos)) { + case CharacterCodes.doubleQuote: + case CharacterCodes.singleQuote: + tokenValue = scanString(/*jsxAttributeString*/ true); + return token = SyntaxKind.StringLiteral; + default: + // If this scans anything other than `{`, it's a parse error. + return scan(); } + } - function scanJsDocToken(): JSDocSyntaxKind { - startPos = tokenPos = pos; - tokenFlags = TokenFlags.None; - if (pos >= end) { - return token = SyntaxKind.EndOfFileToken; - } + function reScanJsxAttributeValue(): SyntaxKind { + pos = tokenPos = startPos; + return scanJsxAttributeValue(); + } - const ch = codePointAt(text, pos); - pos += charSize(ch); - switch (ch) { - case CharacterCodes.tab: - case CharacterCodes.verticalTab: - case CharacterCodes.formFeed: - case CharacterCodes.space: - while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { - pos++; - } - return token = SyntaxKind.WhitespaceTrivia; - case CharacterCodes.at: - return token = SyntaxKind.AtToken; - case CharacterCodes.carriageReturn: - if (text.charCodeAt(pos) === CharacterCodes.lineFeed) { - pos++; - } - // falls through - case CharacterCodes.lineFeed: - tokenFlags |= TokenFlags.PrecedingLineBreak; - return token = SyntaxKind.NewLineTrivia; - case CharacterCodes.asterisk: - return token = SyntaxKind.AsteriskToken; - case CharacterCodes.openBrace: - return token = SyntaxKind.OpenBraceToken; - case CharacterCodes.closeBrace: - return token = SyntaxKind.CloseBraceToken; - case CharacterCodes.openBracket: - return token = SyntaxKind.OpenBracketToken; - case CharacterCodes.closeBracket: - return token = SyntaxKind.CloseBracketToken; - case CharacterCodes.lessThan: - return token = SyntaxKind.LessThanToken; - case CharacterCodes.greaterThan: - return token = SyntaxKind.GreaterThanToken; - case CharacterCodes.equals: - return token = SyntaxKind.EqualsToken; - case CharacterCodes.comma: - return token = SyntaxKind.CommaToken; - case CharacterCodes.dot: - return token = SyntaxKind.DotToken; - case CharacterCodes.backtick: - return token = SyntaxKind.BacktickToken; - case CharacterCodes.hash: - return token = SyntaxKind.HashToken; - case CharacterCodes.backslash: - pos--; - const extendedCookedChar = peekExtendedUnicodeEscape(); - if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { - pos += 3; - tokenFlags |= TokenFlags.ExtendedUnicodeEscape; - tokenValue = scanExtendedUnicodeEscape() + scanIdentifierParts(); - return token = getIdentifierToken(); - } + function scanJsDocToken(): JSDocSyntaxKind { + startPos = tokenPos = pos; + tokenFlags = TokenFlags.None; + if (pos >= end) { + return token = SyntaxKind.EndOfFileToken; + } - const cookedChar = peekUnicodeEscape(); - if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { - pos += 6; - tokenFlags |= TokenFlags.UnicodeEscape; - tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts(); - return token = getIdentifierToken(); - } + const ch = codePointAt(text, pos); + pos += charSize(ch); + switch (ch) { + case CharacterCodes.tab: + case CharacterCodes.verticalTab: + case CharacterCodes.formFeed: + case CharacterCodes.space: + while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) { pos++; - return token = SyntaxKind.Unknown; - } + } + return token = SyntaxKind.WhitespaceTrivia; + case CharacterCodes.at: + return token = SyntaxKind.AtToken; + case CharacterCodes.carriageReturn: + if (text.charCodeAt(pos) === CharacterCodes.lineFeed) { + pos++; + } + // falls through + case CharacterCodes.lineFeed: + tokenFlags |= TokenFlags.PrecedingLineBreak; + return token = SyntaxKind.NewLineTrivia; + case CharacterCodes.asterisk: + return token = SyntaxKind.AsteriskToken; + case CharacterCodes.openBrace: + return token = SyntaxKind.OpenBraceToken; + case CharacterCodes.closeBrace: + return token = SyntaxKind.CloseBraceToken; + case CharacterCodes.openBracket: + return token = SyntaxKind.OpenBracketToken; + case CharacterCodes.closeBracket: + return token = SyntaxKind.CloseBracketToken; + case CharacterCodes.lessThan: + return token = SyntaxKind.LessThanToken; + case CharacterCodes.greaterThan: + return token = SyntaxKind.GreaterThanToken; + case CharacterCodes.equals: + return token = SyntaxKind.EqualsToken; + case CharacterCodes.comma: + return token = SyntaxKind.CommaToken; + case CharacterCodes.dot: + return token = SyntaxKind.DotToken; + case CharacterCodes.backtick: + return token = SyntaxKind.BacktickToken; + case CharacterCodes.hash: + return token = SyntaxKind.HashToken; + case CharacterCodes.backslash: + pos--; + const extendedCookedChar = peekExtendedUnicodeEscape(); + if (extendedCookedChar >= 0 && isIdentifierStart(extendedCookedChar, languageVersion)) { + pos += 3; + tokenFlags |= TokenFlags.ExtendedUnicodeEscape; + tokenValue = scanExtendedUnicodeEscape() + scanIdentifierParts(); + return token = getIdentifierToken(); + } - if (isIdentifierStart(ch, languageVersion)) { - let char = ch; - while (pos < end && isIdentifierPart(char = codePointAt(text, pos), languageVersion) || text.charCodeAt(pos) === CharacterCodes.minus) pos += charSize(char); - tokenValue = text.substring(tokenPos, pos); - if (char === CharacterCodes.backslash) { - tokenValue += scanIdentifierParts(); + const cookedChar = peekUnicodeEscape(); + if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) { + pos += 6; + tokenFlags |= TokenFlags.UnicodeEscape; + tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts(); + return token = getIdentifierToken(); } - return token = getIdentifierToken(); - } - else { + pos++; return token = SyntaxKind.Unknown; - } } - function speculationHelper(callback: () => T, isLookahead: boolean): T { - const savePos = pos; - const saveStartPos = startPos; - const saveTokenPos = tokenPos; - const saveToken = token; - const saveTokenValue = tokenValue; - const saveTokenFlags = tokenFlags; - const result = callback(); - - // If our callback returned something 'falsy' or we're just looking ahead, - // then unconditionally restore us to where we were. - if (!result || isLookahead) { - pos = savePos; - startPos = saveStartPos; - tokenPos = saveTokenPos; - token = saveToken; - tokenValue = saveTokenValue; - tokenFlags = saveTokenFlags; + if (isIdentifierStart(ch, languageVersion)) { + let char = ch; + while (pos < end && isIdentifierPart(char = codePointAt(text, pos), languageVersion) || text.charCodeAt(pos) === CharacterCodes.minus) pos += charSize(char); + tokenValue = text.substring(tokenPos, pos); + if (char === CharacterCodes.backslash) { + tokenValue += scanIdentifierParts(); } - return result; + return token = getIdentifierToken(); } + else { + return token = SyntaxKind.Unknown; + } + } - function scanRange(start: number, length: number, callback: () => T): T { - const saveEnd = end; - const savePos = pos; - const saveStartPos = startPos; - const saveTokenPos = tokenPos; - const saveToken = token; - const saveTokenValue = tokenValue; - const saveTokenFlags = tokenFlags; - const saveErrorExpectations = commentDirectives; - - setText(text, start, length); - const result = callback(); - - end = saveEnd; + function speculationHelper(callback: () => T, isLookahead: boolean): T { + const savePos = pos; + const saveStartPos = startPos; + const saveTokenPos = tokenPos; + const saveToken = token; + const saveTokenValue = tokenValue; + const saveTokenFlags = tokenFlags; + const result = callback(); + + // If our callback returned something 'falsy' or we're just looking ahead, + // then unconditionally restore us to where we were. + if (!result || isLookahead) { pos = savePos; startPos = saveStartPos; tokenPos = saveTokenPos; token = saveToken; tokenValue = saveTokenValue; tokenFlags = saveTokenFlags; - commentDirectives = saveErrorExpectations; - - return result; } + return result; + } - function lookAhead(callback: () => T): T { - return speculationHelper(callback, /*isLookahead*/ true); - } + function scanRange(start: number, length: number, callback: () => T): T { + const saveEnd = end; + const savePos = pos; + const saveStartPos = startPos; + const saveTokenPos = tokenPos; + const saveToken = token; + const saveTokenValue = tokenValue; + const saveTokenFlags = tokenFlags; + const saveErrorExpectations = commentDirectives; - function tryScan(callback: () => T): T { - return speculationHelper(callback, /*isLookahead*/ false); - } + setText(text, start, length); + const result = callback(); - function getText(): string { - return text; - } + end = saveEnd; + pos = savePos; + startPos = saveStartPos; + tokenPos = saveTokenPos; + token = saveToken; + tokenValue = saveTokenValue; + tokenFlags = saveTokenFlags; + commentDirectives = saveErrorExpectations; - function clearCommentDirectives() { - commentDirectives = undefined; - } + return result; + } - function setText(newText: string | undefined, start: number | undefined, length: number | undefined) { - text = newText || ""; - end = length === undefined ? text.length : start! + length; - setTextPos(start || 0); - } + function lookAhead(callback: () => T): T { + return speculationHelper(callback, /*isLookahead*/ true); + } - function setOnError(errorCallback: ErrorCallback | undefined) { - onError = errorCallback; - } + function tryScan(callback: () => T): T { + return speculationHelper(callback, /*isLookahead*/ false); + } - function setScriptTarget(scriptTarget: ScriptTarget) { - languageVersion = scriptTarget; - } + function getText(): string { + return text; + } - function setLanguageVariant(variant: LanguageVariant) { - languageVariant = variant; - } + function clearCommentDirectives() { + commentDirectives = undefined; + } - function setTextPos(textPos: number) { - Debug.assert(textPos >= 0); - pos = textPos; - startPos = textPos; - tokenPos = textPos; - token = SyntaxKind.Unknown; - tokenValue = undefined!; - tokenFlags = TokenFlags.None; - } + function setText(newText: string | undefined, start: number | undefined, length: number | undefined) { + text = newText || ""; + end = length === undefined ? text.length : start! + length; + setTextPos(start || 0); + } - function setInJSDocType(inType: boolean) { - inJSDocType += inType ? 1 : -1; - } + function setOnError(errorCallback: ErrorCallback | undefined) { + onError = errorCallback; } - /* @internal */ - const codePointAt: (s: string, i: number) => number = (String.prototype as any).codePointAt ? (s, i) => (s as any).codePointAt(i) : function codePointAt(str, i): number { - // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt - const size = str.length; - // Account for out-of-bounds indices: - if (i < 0 || i >= size) { - return undefined!; // String.codePointAt returns `undefined` for OOB indexes - } - // Get the first code unit - const first = str.charCodeAt(i); - // check if it’s the start of a surrogate pair - if (first >= 0xD800 && first <= 0xDBFF && size > i + 1) { // high surrogate and there is a next code unit - const second = str.charCodeAt(i + 1); - if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate - // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; - } - } - return first; - }; + function setScriptTarget(scriptTarget: ScriptTarget) { + languageVersion = scriptTarget; + } - /* @internal */ - function charSize(ch: number) { - if (ch >= 0x10000) { - return 2; - } - return 1; + function setLanguageVariant(variant: LanguageVariant) { + languageVariant = variant; } - // Derived from the 10.1.1 UTF16Encoding of the ES6 Spec. - function utf16EncodeAsStringFallback(codePoint: number) { - Debug.assert(0x0 <= codePoint && codePoint <= 0x10FFFF); + function setTextPos(textPos: number) { + Debug.assert(textPos >= 0); + pos = textPos; + startPos = textPos; + tokenPos = textPos; + token = SyntaxKind.Unknown; + tokenValue = undefined!; + tokenFlags = TokenFlags.None; + } - if (codePoint <= 65535) { - return String.fromCharCode(codePoint); - } + function setInJSDocType(inType: boolean) { + inJSDocType += inType ? 1 : -1; + } +} - const codeUnit1 = Math.floor((codePoint - 65536) / 1024) + 0xD800; - const codeUnit2 = ((codePoint - 65536) % 1024) + 0xDC00; +/** @internal */ +const codePointAt: (s: string, i: number) => number = (String.prototype as any).codePointAt ? (s, i) => (s as any).codePointAt(i) : function codePointAt(str, i): number { + // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt + const size = str.length; + // Account for out-of-bounds indices: + if (i < 0 || i >= size) { + return undefined!; // String.codePointAt returns `undefined` for OOB indexes + } + // Get the first code unit + const first = str.charCodeAt(i); + // check if it’s the start of a surrogate pair + if (first >= 0xD800 && first <= 0xDBFF && size > i + 1) { // high surrogate and there is a next code unit + const second = str.charCodeAt(i + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; +}; - return String.fromCharCode(codeUnit1, codeUnit2); +/** @internal */ +function charSize(ch: number) { + if (ch >= 0x10000) { + return 2; } + return 1; +} - const utf16EncodeAsStringWorker: (codePoint: number) => string = (String as any).fromCodePoint ? codePoint => (String as any).fromCodePoint(codePoint) : utf16EncodeAsStringFallback; +// Derived from the 10.1.1 UTF16Encoding of the ES6 Spec. +function utf16EncodeAsStringFallback(codePoint: number) { + Debug.assert(0x0 <= codePoint && codePoint <= 0x10FFFF); - /* @internal */ - export function utf16EncodeAsString(codePoint: number) { - return utf16EncodeAsStringWorker(codePoint); + if (codePoint <= 65535) { + return String.fromCharCode(codePoint); } + + const codeUnit1 = Math.floor((codePoint - 65536) / 1024) + 0xD800; + const codeUnit2 = ((codePoint - 65536) % 1024) + 0xDC00; + + return String.fromCharCode(codeUnit1, codeUnit2); +} + +const utf16EncodeAsStringWorker: (codePoint: number) => string = (String as any).fromCodePoint ? codePoint => (String as any).fromCodePoint(codePoint) : utf16EncodeAsStringFallback; + +/** @internal */ +export function utf16EncodeAsString(codePoint: number) { + return utf16EncodeAsStringWorker(codePoint); } diff --git a/src/compiler/semver.ts b/src/compiler/semver.ts index 7c4a0ce8177ea..c91595719b743 100644 --- a/src/compiler/semver.ts +++ b/src/compiler/semver.ts @@ -1,416 +1,421 @@ -/* @internal */ -namespace ts { - // https://semver.org/#spec-item-2 - // > A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative - // > integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor - // > version, and Z is the patch version. Each element MUST increase numerically. - // - // NOTE: We differ here in that we allow X and X.Y, with missing parts having the default - // value of `0`. - const versionRegExp = /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; - - // https://semver.org/#spec-item-9 - // > A pre-release version MAY be denoted by appending a hyphen and a series of dot separated - // > identifiers immediately following the patch version. Identifiers MUST comprise only ASCII - // > alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers - // > MUST NOT include leading zeroes. - const prereleaseRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)(?:\.(?:0|[1-9]\d*|[a-z-][a-z0-9-]*))*$/i; - const prereleasePartRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)$/i; - - // https://semver.org/#spec-item-10 - // > Build metadata MAY be denoted by appending a plus sign and a series of dot separated - // > identifiers immediately following the patch or pre-release version. Identifiers MUST - // > comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. - const buildRegExp = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/i; - const buildPartRegExp = /^[a-z0-9-]+$/i; - - // https://semver.org/#spec-item-9 - // > Numeric identifiers MUST NOT include leading zeroes. - const numericIdentifierRegExp = /^(0|[1-9]\d*)$/; - - /** - * Describes a precise semantic version number, https://semver.org - */ - export class Version { - static readonly zero = new Version(0, 0, 0, ["0"]); - - readonly major: number; - readonly minor: number; - readonly patch: number; - readonly prerelease: readonly string[]; - readonly build: readonly string[]; - - constructor(text: string); - constructor(major: number, minor?: number, patch?: number, prerelease?: string | readonly string[], build?: string | readonly string[]); - constructor(major: number | string, minor = 0, patch = 0, prerelease: string | readonly string[] = "", build: string | readonly string[] = "") { - if (typeof major === "string") { - const result = Debug.checkDefined(tryParseComponents(major), "Invalid version"); - ({ major, minor, patch, prerelease, build } = result); - } - - Debug.assert(major >= 0, "Invalid argument: major"); - Debug.assert(minor >= 0, "Invalid argument: minor"); - Debug.assert(patch >= 0, "Invalid argument: patch"); - - const prereleaseArray = prerelease ? isArray(prerelease) ? prerelease : prerelease.split(".") : emptyArray; - const buildArray = build ? isArray(build) ? build : build.split(".") : emptyArray; - - Debug.assert(every(prereleaseArray, s => prereleasePartRegExp.test(s)), "Invalid argument: prerelease"); - Debug.assert(every(buildArray, s => buildPartRegExp.test(s)), "Invalid argument: build"); - - this.major = major; - this.minor = minor; - this.patch = patch; - this.prerelease = prereleaseArray; - this.build = buildArray; - } - - static tryParse(text: string) { - const result = tryParseComponents(text); - if (!result) return undefined; - - const { major, minor, patch, prerelease, build } = result; - return new Version(major, minor, patch, prerelease, build); +import { + compareStringsCaseSensitive, compareValues, Comparison, Debug, emptyArray, every, isArray, map, some, trimString, +} from "./_namespaces/ts"; + +// https://semver.org/#spec-item-2 +// > A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative +// > integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor +// > version, and Z is the patch version. Each element MUST increase numerically. +// +// NOTE: We differ here in that we allow X and X.Y, with missing parts having the default +// value of `0`. +const versionRegExp = /^(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*)(?:\-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; + +// https://semver.org/#spec-item-9 +// > A pre-release version MAY be denoted by appending a hyphen and a series of dot separated +// > identifiers immediately following the patch version. Identifiers MUST comprise only ASCII +// > alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers +// > MUST NOT include leading zeroes. +const prereleaseRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)(?:\.(?:0|[1-9]\d*|[a-z-][a-z0-9-]*))*$/i; +const prereleasePartRegExp = /^(?:0|[1-9]\d*|[a-z-][a-z0-9-]*)$/i; + +// https://semver.org/#spec-item-10 +// > Build metadata MAY be denoted by appending a plus sign and a series of dot separated +// > identifiers immediately following the patch or pre-release version. Identifiers MUST +// > comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. +const buildRegExp = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/i; +const buildPartRegExp = /^[a-z0-9-]+$/i; + +// https://semver.org/#spec-item-9 +// > Numeric identifiers MUST NOT include leading zeroes. +const numericIdentifierRegExp = /^(0|[1-9]\d*)$/; + +/** + * Describes a precise semantic version number, https://semver.org + * + * @internal + */ +export class Version { + static readonly zero = new Version(0, 0, 0, ["0"]); + + readonly major: number; + readonly minor: number; + readonly patch: number; + readonly prerelease: readonly string[]; + readonly build: readonly string[]; + + constructor(text: string); + constructor(major: number, minor?: number, patch?: number, prerelease?: string | readonly string[], build?: string | readonly string[]); + constructor(major: number | string, minor = 0, patch = 0, prerelease: string | readonly string[] = "", build: string | readonly string[] = "") { + if (typeof major === "string") { + const result = Debug.checkDefined(tryParseComponents(major), "Invalid version"); + ({ major, minor, patch, prerelease, build } = result); } - compareTo(other: Version | undefined) { - // https://semver.org/#spec-item-11 - // > Precedence is determined by the first difference when comparing each of these - // > identifiers from left to right as follows: Major, minor, and patch versions are - // > always compared numerically. - // - // https://semver.org/#spec-item-11 - // > Precedence for two pre-release versions with the same major, minor, and patch version - // > MUST be determined by comparing each dot separated identifier from left to right until - // > a difference is found [...] - // - // https://semver.org/#spec-item-11 - // > Build metadata does not figure into precedence - if (this === other) return Comparison.EqualTo; - if (other === undefined) return Comparison.GreaterThan; - return compareValues(this.major, other.major) - || compareValues(this.minor, other.minor) - || compareValues(this.patch, other.patch) - || comparePrereleaseIdentifiers(this.prerelease, other.prerelease); - } + Debug.assert(major >= 0, "Invalid argument: major"); + Debug.assert(minor >= 0, "Invalid argument: minor"); + Debug.assert(patch >= 0, "Invalid argument: patch"); - increment(field: "major" | "minor" | "patch") { - switch (field) { - case "major": return new Version(this.major + 1, 0, 0); - case "minor": return new Version(this.major, this.minor + 1, 0); - case "patch": return new Version(this.major, this.minor, this.patch + 1); - default: return Debug.assertNever(field); - } - } + const prereleaseArray = prerelease ? isArray(prerelease) ? prerelease : prerelease.split(".") : emptyArray; + const buildArray = build ? isArray(build) ? build : build.split(".") : emptyArray; - with(fields: { major?: number, minor?: number, patch?: number, prerelease?: string | readonly string[], build?: string | readonly string[] }) { - const { - major = this.major, - minor = this.minor, - patch = this.patch, - prerelease = this.prerelease, - build = this.build - } = fields; - return new Version(major, minor, patch, prerelease, build); - } + Debug.assert(every(prereleaseArray, s => prereleasePartRegExp.test(s)), "Invalid argument: prerelease"); + Debug.assert(every(buildArray, s => buildPartRegExp.test(s)), "Invalid argument: build"); - toString() { - let result = `${this.major}.${this.minor}.${this.patch}`; - if (some(this.prerelease)) result += `-${this.prerelease.join(".")}`; - if (some(this.build)) result += `+${this.build.join(".")}`; - return result; - } + this.major = major; + this.minor = minor; + this.patch = patch; + this.prerelease = prereleaseArray; + this.build = buildArray; } - function tryParseComponents(text: string) { - const match = versionRegExp.exec(text); - if (!match) return undefined; - - const [, major, minor = "0", patch = "0", prerelease = "", build = ""] = match; - if (prerelease && !prereleaseRegExp.test(prerelease)) return undefined; - if (build && !buildRegExp.test(build)) return undefined; - return { - major: parseInt(major, 10), - minor: parseInt(minor, 10), - patch: parseInt(patch, 10), - prerelease, - build - }; + static tryParse(text: string) { + const result = tryParseComponents(text); + if (!result) return undefined; + + const { major, minor, patch, prerelease, build } = result; + return new Version(major, minor, patch, prerelease, build); } - function comparePrereleaseIdentifiers(left: readonly string[], right: readonly string[]) { + compareTo(other: Version | undefined) { // https://semver.org/#spec-item-11 - // > When major, minor, and patch are equal, a pre-release version has lower precedence - // > than a normal version. - if (left === right) return Comparison.EqualTo; - if (left.length === 0) return right.length === 0 ? Comparison.EqualTo : Comparison.GreaterThan; - if (right.length === 0) return Comparison.LessThan; - + // > Precedence is determined by the first difference when comparing each of these + // > identifiers from left to right as follows: Major, minor, and patch versions are + // > always compared numerically. + // // https://semver.org/#spec-item-11 // > Precedence for two pre-release versions with the same major, minor, and patch version // > MUST be determined by comparing each dot separated identifier from left to right until // > a difference is found [...] - const length = Math.min(left.length, right.length); - for (let i = 0; i < length; i++) { - const leftIdentifier = left[i]; - const rightIdentifier = right[i]; - if (leftIdentifier === rightIdentifier) continue; - - const leftIsNumeric = numericIdentifierRegExp.test(leftIdentifier); - const rightIsNumeric = numericIdentifierRegExp.test(rightIdentifier); - if (leftIsNumeric || rightIsNumeric) { - // https://semver.org/#spec-item-11 - // > Numeric identifiers always have lower precedence than non-numeric identifiers. - if (leftIsNumeric !== rightIsNumeric) return leftIsNumeric ? Comparison.LessThan : Comparison.GreaterThan; - - // https://semver.org/#spec-item-11 - // > identifiers consisting of only digits are compared numerically - const result = compareValues(+leftIdentifier, +rightIdentifier); - if (result) return result; - } - else { - // https://semver.org/#spec-item-11 - // > identifiers with letters or hyphens are compared lexically in ASCII sort order. - const result = compareStringsCaseSensitive(leftIdentifier, rightIdentifier); - if (result) return result; - } + // + // https://semver.org/#spec-item-11 + // > Build metadata does not figure into precedence + if (this === other) return Comparison.EqualTo; + if (other === undefined) return Comparison.GreaterThan; + return compareValues(this.major, other.major) + || compareValues(this.minor, other.minor) + || compareValues(this.patch, other.patch) + || comparePrereleaseIdentifiers(this.prerelease, other.prerelease); + } + + increment(field: "major" | "minor" | "patch") { + switch (field) { + case "major": return new Version(this.major + 1, 0, 0); + case "minor": return new Version(this.major, this.minor + 1, 0); + case "patch": return new Version(this.major, this.minor, this.patch + 1); + default: return Debug.assertNever(field); } + } - // https://semver.org/#spec-item-11 - // > A larger set of pre-release fields has a higher precedence than a smaller set, if all - // > of the preceding identifiers are equal. - return compareValues(left.length, right.length); + with(fields: { major?: number, minor?: number, patch?: number, prerelease?: string | readonly string[], build?: string | readonly string[] }) { + const { + major = this.major, + minor = this.minor, + patch = this.patch, + prerelease = this.prerelease, + build = this.build + } = fields; + return new Version(major, minor, patch, prerelease, build); } - /** - * Describes a semantic version range, per https://github.com/npm/node-semver#ranges - */ - export class VersionRange { - private _alternatives: readonly (readonly Comparator[])[]; + toString() { + let result = `${this.major}.${this.minor}.${this.patch}`; + if (some(this.prerelease)) result += `-${this.prerelease.join(".")}`; + if (some(this.build)) result += `+${this.build.join(".")}`; + return result; + } +} + +function tryParseComponents(text: string) { + const match = versionRegExp.exec(text); + if (!match) return undefined; + + const [, major, minor = "0", patch = "0", prerelease = "", build = ""] = match; + if (prerelease && !prereleaseRegExp.test(prerelease)) return undefined; + if (build && !buildRegExp.test(build)) return undefined; + return { + major: parseInt(major, 10), + minor: parseInt(minor, 10), + patch: parseInt(patch, 10), + prerelease, + build + }; +} + +function comparePrereleaseIdentifiers(left: readonly string[], right: readonly string[]) { + // https://semver.org/#spec-item-11 + // > When major, minor, and patch are equal, a pre-release version has lower precedence + // > than a normal version. + if (left === right) return Comparison.EqualTo; + if (left.length === 0) return right.length === 0 ? Comparison.EqualTo : Comparison.GreaterThan; + if (right.length === 0) return Comparison.LessThan; + + // https://semver.org/#spec-item-11 + // > Precedence for two pre-release versions with the same major, minor, and patch version + // > MUST be determined by comparing each dot separated identifier from left to right until + // > a difference is found [...] + const length = Math.min(left.length, right.length); + for (let i = 0; i < length; i++) { + const leftIdentifier = left[i]; + const rightIdentifier = right[i]; + if (leftIdentifier === rightIdentifier) continue; + + const leftIsNumeric = numericIdentifierRegExp.test(leftIdentifier); + const rightIsNumeric = numericIdentifierRegExp.test(rightIdentifier); + if (leftIsNumeric || rightIsNumeric) { + // https://semver.org/#spec-item-11 + // > Numeric identifiers always have lower precedence than non-numeric identifiers. + if (leftIsNumeric !== rightIsNumeric) return leftIsNumeric ? Comparison.LessThan : Comparison.GreaterThan; - constructor(spec: string) { - this._alternatives = spec ? Debug.checkDefined(parseRange(spec), "Invalid range spec.") : emptyArray; + // https://semver.org/#spec-item-11 + // > identifiers consisting of only digits are compared numerically + const result = compareValues(+leftIdentifier, +rightIdentifier); + if (result) return result; } - - static tryParse(text: string) { - const sets = parseRange(text); - if (sets) { - const range = new VersionRange(""); - range._alternatives = sets; - return range; - } - return undefined; + else { + // https://semver.org/#spec-item-11 + // > identifiers with letters or hyphens are compared lexically in ASCII sort order. + const result = compareStringsCaseSensitive(leftIdentifier, rightIdentifier); + if (result) return result; } + } - /** - * Tests whether a version matches the range. This is equivalent to `satisfies(version, range, { includePrerelease: true })`. - * in `node-semver`. - */ - test(version: Version | string) { - if (typeof version === "string") version = new Version(version); - return testDisjunction(version, this._alternatives); - } + // https://semver.org/#spec-item-11 + // > A larger set of pre-release fields has a higher precedence than a smaller set, if all + // > of the preceding identifiers are equal. + return compareValues(left.length, right.length); +} + +/** + * Describes a semantic version range, per https://github.com/npm/node-semver#ranges + * + * @internal + */ +export class VersionRange { + private _alternatives: readonly (readonly Comparator[])[]; + + constructor(spec: string) { + this._alternatives = spec ? Debug.checkDefined(parseRange(spec), "Invalid range spec.") : emptyArray; + } - toString() { - return formatDisjunction(this._alternatives); + static tryParse(text: string) { + const sets = parseRange(text); + if (sets) { + const range = new VersionRange(""); + range._alternatives = sets; + return range; } + return undefined; } - interface Comparator { - readonly operator: "<" | "<=" | ">" | ">=" | "="; - readonly operand: Version; + /** + * Tests whether a version matches the range. This is equivalent to `satisfies(version, range, { includePrerelease: true })`. + * in `node-semver`. + */ + test(version: Version | string) { + if (typeof version === "string") version = new Version(version); + return testDisjunction(version, this._alternatives); } - // https://github.com/npm/node-semver#range-grammar - // - // range-set ::= range ( logical-or range ) * - // range ::= hyphen | simple ( ' ' simple ) * | '' - // logical-or ::= ( ' ' ) * '||' ( ' ' ) * - const logicalOrRegExp = /\|\|/g; - const whitespaceRegExp = /\s+/g; - - // https://github.com/npm/node-semver#range-grammar - // - // partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? - // xr ::= 'x' | 'X' | '*' | nr - // nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * - // qualifier ::= ( '-' pre )? ( '+' build )? - // pre ::= parts - // build ::= parts - // parts ::= part ( '.' part ) * - // part ::= nr | [-0-9A-Za-z]+ - const partialRegExp = /^([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; - - // https://github.com/npm/node-semver#range-grammar - // - // hyphen ::= partial ' - ' partial - const hyphenRegExp = /^\s*([a-z0-9-+.*]+)\s+-\s+([a-z0-9-+.*]+)\s*$/i; - - // https://github.com/npm/node-semver#range-grammar - // - // simple ::= primitive | partial | tilde | caret - // primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial - // tilde ::= '~' partial - // caret ::= '^' partial - const rangeRegExp = /^(~|\^|<|<=|>|>=|=)?\s*([a-z0-9-+.*]+)$/i; - - function parseRange(text: string) { - const alternatives: Comparator[][] = []; - for (let range of trimString(text).split(logicalOrRegExp)) { - if (!range) continue; - const comparators: Comparator[] = []; - range = trimString(range); - const match = hyphenRegExp.exec(range); - if (match) { - if (!parseHyphen(match[1], match[2], comparators)) return undefined; - } - else { - for (const simple of range.split(whitespaceRegExp)) { - const match = rangeRegExp.exec(trimString(simple)); - if (!match || !parseComparator(match[1], match[2], comparators)) return undefined; - } + toString() { + return formatDisjunction(this._alternatives); + } +} + +interface Comparator { + readonly operator: "<" | "<=" | ">" | ">=" | "="; + readonly operand: Version; +} + +// https://github.com/npm/node-semver#range-grammar +// +// range-set ::= range ( logical-or range ) * +// range ::= hyphen | simple ( ' ' simple ) * | '' +// logical-or ::= ( ' ' ) * '||' ( ' ' ) * +const logicalOrRegExp = /\|\|/g; +const whitespaceRegExp = /\s+/g; + +// https://github.com/npm/node-semver#range-grammar +// +// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? +// xr ::= 'x' | 'X' | '*' | nr +// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * +// qualifier ::= ( '-' pre )? ( '+' build )? +// pre ::= parts +// build ::= parts +// parts ::= part ( '.' part ) * +// part ::= nr | [-0-9A-Za-z]+ +const partialRegExp = /^([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:\.([xX*0]|[1-9]\d*)(?:-([a-z0-9-.]+))?(?:\+([a-z0-9-.]+))?)?)?$/i; + +// https://github.com/npm/node-semver#range-grammar +// +// hyphen ::= partial ' - ' partial +const hyphenRegExp = /^\s*([a-z0-9-+.*]+)\s+-\s+([a-z0-9-+.*]+)\s*$/i; + +// https://github.com/npm/node-semver#range-grammar +// +// simple ::= primitive | partial | tilde | caret +// primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial +// tilde ::= '~' partial +// caret ::= '^' partial +const rangeRegExp = /^(~|\^|<|<=|>|>=|=)?\s*([a-z0-9-+.*]+)$/i; + +function parseRange(text: string) { + const alternatives: Comparator[][] = []; + for (let range of trimString(text).split(logicalOrRegExp)) { + if (!range) continue; + const comparators: Comparator[] = []; + range = trimString(range); + const match = hyphenRegExp.exec(range); + if (match) { + if (!parseHyphen(match[1], match[2], comparators)) return undefined; + } + else { + for (const simple of range.split(whitespaceRegExp)) { + const match = rangeRegExp.exec(trimString(simple)); + if (!match || !parseComparator(match[1], match[2], comparators)) return undefined; } - alternatives.push(comparators); } - return alternatives; + alternatives.push(comparators); } + return alternatives; +} - function parsePartial(text: string) { - const match = partialRegExp.exec(text); - if (!match) return undefined; +function parsePartial(text: string) { + const match = partialRegExp.exec(text); + if (!match) return undefined; - const [, major, minor = "*", patch = "*", prerelease, build] = match; - const version = new Version( - isWildcard(major) ? 0 : parseInt(major, 10), - isWildcard(major) || isWildcard(minor) ? 0 : parseInt(minor, 10), - isWildcard(major) || isWildcard(minor) || isWildcard(patch) ? 0 : parseInt(patch, 10), - prerelease, - build); + const [, major, minor = "*", patch = "*", prerelease, build] = match; + const version = new Version( + isWildcard(major) ? 0 : parseInt(major, 10), + isWildcard(major) || isWildcard(minor) ? 0 : parseInt(minor, 10), + isWildcard(major) || isWildcard(minor) || isWildcard(patch) ? 0 : parseInt(patch, 10), + prerelease, + build); - return { version, major, minor, patch }; - } + return { version, major, minor, patch }; +} - function parseHyphen(left: string, right: string, comparators: Comparator[]) { - const leftResult = parsePartial(left); - if (!leftResult) return false; +function parseHyphen(left: string, right: string, comparators: Comparator[]) { + const leftResult = parsePartial(left); + if (!leftResult) return false; - const rightResult = parsePartial(right); - if (!rightResult) return false; + const rightResult = parsePartial(right); + if (!rightResult) return false; - if (!isWildcard(leftResult.major)) { - comparators.push(createComparator(">=", leftResult.version)); - } - - if (!isWildcard(rightResult.major)) { - comparators.push( - isWildcard(rightResult.minor) ? createComparator("<", rightResult.version.increment("major")) : - isWildcard(rightResult.patch) ? createComparator("<", rightResult.version.increment("minor")) : - createComparator("<=", rightResult.version)); - } - - return true; + if (!isWildcard(leftResult.major)) { + comparators.push(createComparator(">=", leftResult.version)); } - function parseComparator(operator: string, text: string, comparators: Comparator[]) { - const result = parsePartial(text); - if (!result) return false; - - const { version, major, minor, patch } = result; - if (!isWildcard(major)) { - switch (operator) { - case "~": - comparators.push(createComparator(">=", version)); - comparators.push(createComparator("<", version.increment( - isWildcard(minor) ? "major" : - "minor"))); - break; - case "^": - comparators.push(createComparator(">=", version)); - comparators.push(createComparator("<", version.increment( - version.major > 0 || isWildcard(minor) ? "major" : - version.minor > 0 || isWildcard(patch) ? "minor" : - "patch"))); - break; - case "<": - case ">=": - comparators.push( - isWildcard(minor) || isWildcard(patch) ? createComparator(operator, version.with({ prerelease: "0" })) : - createComparator(operator, version)); - break; - case "<=": - case ">": - comparators.push( - isWildcard(minor) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("major").with({ prerelease: "0" })) : - isWildcard(patch) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("minor").with({ prerelease: "0" })) : - createComparator(operator, version)); - break; - case "=": - case undefined: - if (isWildcard(minor) || isWildcard(patch)) { - comparators.push(createComparator(">=", version.with({ prerelease: "0" }))); - comparators.push(createComparator("<", version.increment(isWildcard(minor) ? "major" : "minor").with({ prerelease: "0" }))); - } - else { - comparators.push(createComparator("=", version)); - } - break; - default: - // unrecognized - return false; - } - } - else if (operator === "<" || operator === ">") { - comparators.push(createComparator("<", Version.zero)); - } - - return true; + if (!isWildcard(rightResult.major)) { + comparators.push( + isWildcard(rightResult.minor) ? createComparator("<", rightResult.version.increment("major")) : + isWildcard(rightResult.patch) ? createComparator("<", rightResult.version.increment("minor")) : + createComparator("<=", rightResult.version)); } - function isWildcard(part: string) { - return part === "*" || part === "x" || part === "X"; - } + return true; +} - function createComparator(operator: Comparator["operator"], operand: Version) { - return { operator, operand }; - } +function parseComparator(operator: string, text: string, comparators: Comparator[]) { + const result = parsePartial(text); + if (!result) return false; - function testDisjunction(version: Version, alternatives: readonly (readonly Comparator[])[]) { - // an empty disjunction is treated as "*" (all versions) - if (alternatives.length === 0) return true; - for (const alternative of alternatives) { - if (testAlternative(version, alternative)) return true; + const { version, major, minor, patch } = result; + if (!isWildcard(major)) { + switch (operator) { + case "~": + comparators.push(createComparator(">=", version)); + comparators.push(createComparator("<", version.increment( + isWildcard(minor) ? "major" : + "minor"))); + break; + case "^": + comparators.push(createComparator(">=", version)); + comparators.push(createComparator("<", version.increment( + version.major > 0 || isWildcard(minor) ? "major" : + version.minor > 0 || isWildcard(patch) ? "minor" : + "patch"))); + break; + case "<": + case ">=": + comparators.push( + isWildcard(minor) || isWildcard(patch) ? createComparator(operator, version.with({ prerelease: "0" })) : + createComparator(operator, version)); + break; + case "<=": + case ">": + comparators.push( + isWildcard(minor) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("major").with({ prerelease: "0" })) : + isWildcard(patch) ? createComparator(operator === "<=" ? "<" : ">=", version.increment("minor").with({ prerelease: "0" })) : + createComparator(operator, version)); + break; + case "=": + case undefined: + if (isWildcard(minor) || isWildcard(patch)) { + comparators.push(createComparator(">=", version.with({ prerelease: "0" }))); + comparators.push(createComparator("<", version.increment(isWildcard(minor) ? "major" : "minor").with({ prerelease: "0" }))); + } + else { + comparators.push(createComparator("=", version)); + } + break; + default: + // unrecognized + return false; } - return false; } - - function testAlternative(version: Version, comparators: readonly Comparator[]) { - for (const comparator of comparators) { - if (!testComparator(version, comparator.operator, comparator.operand)) return false; - } - return true; + else if (operator === "<" || operator === ">") { + comparators.push(createComparator("<", Version.zero)); } - function testComparator(version: Version, operator: Comparator["operator"], operand: Version) { - const cmp = version.compareTo(operand); - switch (operator) { - case "<": return cmp < 0; - case "<=": return cmp <= 0; - case ">": return cmp > 0; - case ">=": return cmp >= 0; - case "=": return cmp === 0; - default: return Debug.assertNever(operator); - } - } + return true; +} - function formatDisjunction(alternatives: readonly (readonly Comparator[])[]) { - return map(alternatives, formatAlternative).join(" || ") || "*"; - } +function isWildcard(part: string) { + return part === "*" || part === "x" || part === "X"; +} + +function createComparator(operator: Comparator["operator"], operand: Version) { + return { operator, operand }; +} - function formatAlternative(comparators: readonly Comparator[]) { - return map(comparators, formatComparator).join(" "); +function testDisjunction(version: Version, alternatives: readonly (readonly Comparator[])[]) { + // an empty disjunction is treated as "*" (all versions) + if (alternatives.length === 0) return true; + for (const alternative of alternatives) { + if (testAlternative(version, alternative)) return true; } + return false; +} - function formatComparator(comparator: Comparator) { - return `${comparator.operator}${comparator.operand}`; +function testAlternative(version: Version, comparators: readonly Comparator[]) { + for (const comparator of comparators) { + if (!testComparator(version, comparator.operator, comparator.operand)) return false; } + return true; +} + +function testComparator(version: Version, operator: Comparator["operator"], operand: Version) { + const cmp = version.compareTo(operand); + switch (operator) { + case "<": return cmp < 0; + case "<=": return cmp <= 0; + case ">": return cmp > 0; + case ">=": return cmp >= 0; + case "=": return cmp === 0; + default: return Debug.assertNever(operator); + } +} + +function formatDisjunction(alternatives: readonly (readonly Comparator[])[]) { + return map(alternatives, formatAlternative).join(" || ") || "*"; +} + +function formatAlternative(comparators: readonly Comparator[]) { + return map(comparators, formatComparator).join(" "); +} + +function formatComparator(comparator: Comparator) { + return `${comparator.operator}${comparator.operand}`; } \ No newline at end of file diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 36a0fd925e2af..4471ed5ad4ba3 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -1,761 +1,786 @@ -/* @internal */ -namespace ts { - export interface SourceMapGeneratorOptions { - extendedDiagnostics?: boolean; - } - - export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator { - const { enter, exit } = generatorOptions.extendedDiagnostics - ? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap") - : performance.nullTimer; - - // Current source map file and its index in the sources list - const rawSources: string[] = []; - const sources: string[] = []; - const sourceToSourceIndexMap = new Map(); - let sourcesContent: (string | null)[] | undefined; - - const names: string[] = []; - let nameToNameIndexMap: ESMap | undefined; - const mappingCharCodes: number[] = []; - let mappings = ""; - - // Last recorded and encoded mappings - let lastGeneratedLine = 0; - let lastGeneratedCharacter = 0; - let lastSourceIndex = 0; - let lastSourceLine = 0; - let lastSourceCharacter = 0; - let lastNameIndex = 0; - let hasLast = false; - - let pendingGeneratedLine = 0; - let pendingGeneratedCharacter = 0; - let pendingSourceIndex = 0; - let pendingSourceLine = 0; - let pendingSourceCharacter = 0; - let pendingNameIndex = 0; - let hasPending = false; - let hasPendingSource = false; - let hasPendingName = false; +import { + arrayFrom, binarySearchKey, CharacterCodes, combinePaths, compareValues, Debug, DocumentPosition, + DocumentPositionMapper, DocumentPositionMapperHost, EmitHost, emptyArray, ESMap, every, getDirectoryPath, + getNormalizedAbsolutePath, getPositionOfLineAndCharacter, getRelativePathToDirectoryOrUrl, identity, isArray, + isString, Iterator, LineAndCharacter, Map, RawSourceMap, some, sortAndDeduplicate, SortedReadonlyArray, + SourceMapGenerator, trimStringEnd, +} from "./_namespaces/ts"; +import * as performance from "./_namespaces/ts.performance"; + +/** @internal */ +export interface SourceMapGeneratorOptions { + extendedDiagnostics?: boolean; +} - return { - getSources: () => rawSources, - addSource, - setSourceContent, - addName, - addMapping, - appendSourceMap, - toJSON, - toString: () => JSON.stringify(toJSON()) - }; +/** @internal */ +export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator { + const { enter, exit } = generatorOptions.extendedDiagnostics + ? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap") + : performance.nullTimer; + + // Current source map file and its index in the sources list + const rawSources: string[] = []; + const sources: string[] = []; + const sourceToSourceIndexMap = new Map(); + let sourcesContent: (string | null)[] | undefined; + + const names: string[] = []; + let nameToNameIndexMap: ESMap | undefined; + const mappingCharCodes: number[] = []; + let mappings = ""; + + // Last recorded and encoded mappings + let lastGeneratedLine = 0; + let lastGeneratedCharacter = 0; + let lastSourceIndex = 0; + let lastSourceLine = 0; + let lastSourceCharacter = 0; + let lastNameIndex = 0; + let hasLast = false; + + let pendingGeneratedLine = 0; + let pendingGeneratedCharacter = 0; + let pendingSourceIndex = 0; + let pendingSourceLine = 0; + let pendingSourceCharacter = 0; + let pendingNameIndex = 0; + let hasPending = false; + let hasPendingSource = false; + let hasPendingName = false; + + return { + getSources: () => rawSources, + addSource, + setSourceContent, + addName, + addMapping, + appendSourceMap, + toJSON, + toString: () => JSON.stringify(toJSON()) + }; - function addSource(fileName: string) { - enter(); - const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath, - fileName, - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ true); - - let sourceIndex = sourceToSourceIndexMap.get(source); - if (sourceIndex === undefined) { - sourceIndex = sources.length; - sources.push(source); - rawSources.push(fileName); - sourceToSourceIndexMap.set(source, sourceIndex); - } - exit(); - return sourceIndex; + function addSource(fileName: string) { + enter(); + const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath, + fileName, + host.getCurrentDirectory(), + host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ true); + + let sourceIndex = sourceToSourceIndexMap.get(source); + if (sourceIndex === undefined) { + sourceIndex = sources.length; + sources.push(source); + rawSources.push(fileName); + sourceToSourceIndexMap.set(source, sourceIndex); } + exit(); + return sourceIndex; + } - /* eslint-disable local/boolean-trivia, no-null/no-null */ - function setSourceContent(sourceIndex: number, content: string | null) { - enter(); - if (content !== null) { - if (!sourcesContent) sourcesContent = []; - while (sourcesContent.length < sourceIndex) { - sourcesContent.push(null); - } - sourcesContent[sourceIndex] = content; + /* eslint-disable local/boolean-trivia, no-null/no-null */ + function setSourceContent(sourceIndex: number, content: string | null) { + enter(); + if (content !== null) { + if (!sourcesContent) sourcesContent = []; + while (sourcesContent.length < sourceIndex) { + sourcesContent.push(null); } - exit(); + sourcesContent[sourceIndex] = content; } - /* eslint-enable local/boolean-trivia, no-null/no-null */ - - function addName(name: string) { - enter(); - if (!nameToNameIndexMap) nameToNameIndexMap = new Map(); - let nameIndex = nameToNameIndexMap.get(name); - if (nameIndex === undefined) { - nameIndex = names.length; - names.push(name); - nameToNameIndexMap.set(name, nameIndex); - } - exit(); - return nameIndex; + exit(); + } + /* eslint-enable local/boolean-trivia, no-null/no-null */ + + function addName(name: string) { + enter(); + if (!nameToNameIndexMap) nameToNameIndexMap = new Map(); + let nameIndex = nameToNameIndexMap.get(name); + if (nameIndex === undefined) { + nameIndex = names.length; + names.push(name); + nameToNameIndexMap.set(name, nameIndex); } + exit(); + return nameIndex; + } - function isNewGeneratedPosition(generatedLine: number, generatedCharacter: number) { - return !hasPending - || pendingGeneratedLine !== generatedLine - || pendingGeneratedCharacter !== generatedCharacter; - } + function isNewGeneratedPosition(generatedLine: number, generatedCharacter: number) { + return !hasPending + || pendingGeneratedLine !== generatedLine + || pendingGeneratedCharacter !== generatedCharacter; + } - function isBacktrackingSourcePosition(sourceIndex: number | undefined, sourceLine: number | undefined, sourceCharacter: number | undefined) { - return sourceIndex !== undefined - && sourceLine !== undefined - && sourceCharacter !== undefined - && pendingSourceIndex === sourceIndex - && (pendingSourceLine > sourceLine - || pendingSourceLine === sourceLine && pendingSourceCharacter > sourceCharacter); - } + function isBacktrackingSourcePosition(sourceIndex: number | undefined, sourceLine: number | undefined, sourceCharacter: number | undefined) { + return sourceIndex !== undefined + && sourceLine !== undefined + && sourceCharacter !== undefined + && pendingSourceIndex === sourceIndex + && (pendingSourceLine > sourceLine + || pendingSourceLine === sourceLine && pendingSourceCharacter > sourceCharacter); + } - function addMapping(generatedLine: number, generatedCharacter: number, sourceIndex?: number, sourceLine?: number, sourceCharacter?: number, nameIndex?: number) { - Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); - Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); - Debug.assert(sourceIndex === undefined || sourceIndex >= 0, "sourceIndex cannot be negative"); - Debug.assert(sourceLine === undefined || sourceLine >= 0, "sourceLine cannot be negative"); - Debug.assert(sourceCharacter === undefined || sourceCharacter >= 0, "sourceCharacter cannot be negative"); - enter(); - // If this location wasn't recorded or the location in source is going backwards, record the mapping - if (isNewGeneratedPosition(generatedLine, generatedCharacter) || - isBacktrackingSourcePosition(sourceIndex, sourceLine, sourceCharacter)) { - commitPendingMapping(); - pendingGeneratedLine = generatedLine; - pendingGeneratedCharacter = generatedCharacter; - hasPendingSource = false; - hasPendingName = false; - hasPending = true; - } + function addMapping(generatedLine: number, generatedCharacter: number, sourceIndex?: number, sourceLine?: number, sourceCharacter?: number, nameIndex?: number) { + Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); + Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); + Debug.assert(sourceIndex === undefined || sourceIndex >= 0, "sourceIndex cannot be negative"); + Debug.assert(sourceLine === undefined || sourceLine >= 0, "sourceLine cannot be negative"); + Debug.assert(sourceCharacter === undefined || sourceCharacter >= 0, "sourceCharacter cannot be negative"); + enter(); + // If this location wasn't recorded or the location in source is going backwards, record the mapping + if (isNewGeneratedPosition(generatedLine, generatedCharacter) || + isBacktrackingSourcePosition(sourceIndex, sourceLine, sourceCharacter)) { + commitPendingMapping(); + pendingGeneratedLine = generatedLine; + pendingGeneratedCharacter = generatedCharacter; + hasPendingSource = false; + hasPendingName = false; + hasPending = true; + } - if (sourceIndex !== undefined && sourceLine !== undefined && sourceCharacter !== undefined) { - pendingSourceIndex = sourceIndex; - pendingSourceLine = sourceLine; - pendingSourceCharacter = sourceCharacter; - hasPendingSource = true; - if (nameIndex !== undefined) { - pendingNameIndex = nameIndex; - hasPendingName = true; - } + if (sourceIndex !== undefined && sourceLine !== undefined && sourceCharacter !== undefined) { + pendingSourceIndex = sourceIndex; + pendingSourceLine = sourceLine; + pendingSourceCharacter = sourceCharacter; + hasPendingSource = true; + if (nameIndex !== undefined) { + pendingNameIndex = nameIndex; + hasPendingName = true; } - exit(); } + exit(); + } - function appendSourceMap(generatedLine: number, generatedCharacter: number, map: RawSourceMap, sourceMapPath: string, start?: LineAndCharacter, end?: LineAndCharacter) { - Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); - Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); - enter(); - // First, decode the old component sourcemap - const sourceIndexToNewSourceIndexMap: number[] = []; - let nameIndexToNewNameIndexMap: number[] | undefined; - const mappingIterator = decodeMappings(map.mappings); - for (let iterResult = mappingIterator.next(); !iterResult.done; iterResult = mappingIterator.next()) { - const raw = iterResult.value; - if (end && ( - raw.generatedLine > end.line || - (raw.generatedLine === end.line && raw.generatedCharacter > end.character))) { - break; - } + function appendSourceMap(generatedLine: number, generatedCharacter: number, map: RawSourceMap, sourceMapPath: string, start?: LineAndCharacter, end?: LineAndCharacter) { + Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); + Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); + enter(); + // First, decode the old component sourcemap + const sourceIndexToNewSourceIndexMap: number[] = []; + let nameIndexToNewNameIndexMap: number[] | undefined; + const mappingIterator = decodeMappings(map.mappings); + for (let iterResult = mappingIterator.next(); !iterResult.done; iterResult = mappingIterator.next()) { + const raw = iterResult.value; + if (end && ( + raw.generatedLine > end.line || + (raw.generatedLine === end.line && raw.generatedCharacter > end.character))) { + break; + } - if (start && ( - raw.generatedLine < start.line || - (start.line === raw.generatedLine && raw.generatedCharacter < start.character))) { - continue; - } - // Then reencode all the updated mappings into the overall map - let newSourceIndex: number | undefined; - let newSourceLine: number | undefined; - let newSourceCharacter: number | undefined; - let newNameIndex: number | undefined; - if (raw.sourceIndex !== undefined) { - newSourceIndex = sourceIndexToNewSourceIndexMap[raw.sourceIndex]; - if (newSourceIndex === undefined) { - // Apply offsets to each position and fixup source entries - const rawPath = map.sources[raw.sourceIndex]; - const relativePath = map.sourceRoot ? combinePaths(map.sourceRoot, rawPath) : rawPath; - const combinedPath = combinePaths(getDirectoryPath(sourceMapPath), relativePath); - sourceIndexToNewSourceIndexMap[raw.sourceIndex] = newSourceIndex = addSource(combinedPath); - if (map.sourcesContent && typeof map.sourcesContent[raw.sourceIndex] === "string") { - setSourceContent(newSourceIndex, map.sourcesContent[raw.sourceIndex]); - } + if (start && ( + raw.generatedLine < start.line || + (start.line === raw.generatedLine && raw.generatedCharacter < start.character))) { + continue; + } + // Then reencode all the updated mappings into the overall map + let newSourceIndex: number | undefined; + let newSourceLine: number | undefined; + let newSourceCharacter: number | undefined; + let newNameIndex: number | undefined; + if (raw.sourceIndex !== undefined) { + newSourceIndex = sourceIndexToNewSourceIndexMap[raw.sourceIndex]; + if (newSourceIndex === undefined) { + // Apply offsets to each position and fixup source entries + const rawPath = map.sources[raw.sourceIndex]; + const relativePath = map.sourceRoot ? combinePaths(map.sourceRoot, rawPath) : rawPath; + const combinedPath = combinePaths(getDirectoryPath(sourceMapPath), relativePath); + sourceIndexToNewSourceIndexMap[raw.sourceIndex] = newSourceIndex = addSource(combinedPath); + if (map.sourcesContent && typeof map.sourcesContent[raw.sourceIndex] === "string") { + setSourceContent(newSourceIndex, map.sourcesContent[raw.sourceIndex]); } + } - newSourceLine = raw.sourceLine; - newSourceCharacter = raw.sourceCharacter; - if (map.names && raw.nameIndex !== undefined) { - if (!nameIndexToNewNameIndexMap) nameIndexToNewNameIndexMap = []; - newNameIndex = nameIndexToNewNameIndexMap[raw.nameIndex]; - if (newNameIndex === undefined) { - nameIndexToNewNameIndexMap[raw.nameIndex] = newNameIndex = addName(map.names[raw.nameIndex]); - } + newSourceLine = raw.sourceLine; + newSourceCharacter = raw.sourceCharacter; + if (map.names && raw.nameIndex !== undefined) { + if (!nameIndexToNewNameIndexMap) nameIndexToNewNameIndexMap = []; + newNameIndex = nameIndexToNewNameIndexMap[raw.nameIndex]; + if (newNameIndex === undefined) { + nameIndexToNewNameIndexMap[raw.nameIndex] = newNameIndex = addName(map.names[raw.nameIndex]); } } - - const rawGeneratedLine = raw.generatedLine - (start ? start.line : 0); - const newGeneratedLine = rawGeneratedLine + generatedLine; - const rawGeneratedCharacter = start && start.line === raw.generatedLine ? raw.generatedCharacter - start.character : raw.generatedCharacter; - const newGeneratedCharacter = rawGeneratedLine === 0 ? rawGeneratedCharacter + generatedCharacter : rawGeneratedCharacter; - addMapping(newGeneratedLine, newGeneratedCharacter, newSourceIndex, newSourceLine, newSourceCharacter, newNameIndex); } - exit(); - } - function shouldCommitMapping() { - return !hasLast - || lastGeneratedLine !== pendingGeneratedLine - || lastGeneratedCharacter !== pendingGeneratedCharacter - || lastSourceIndex !== pendingSourceIndex - || lastSourceLine !== pendingSourceLine - || lastSourceCharacter !== pendingSourceCharacter - || lastNameIndex !== pendingNameIndex; + const rawGeneratedLine = raw.generatedLine - (start ? start.line : 0); + const newGeneratedLine = rawGeneratedLine + generatedLine; + const rawGeneratedCharacter = start && start.line === raw.generatedLine ? raw.generatedCharacter - start.character : raw.generatedCharacter; + const newGeneratedCharacter = rawGeneratedLine === 0 ? rawGeneratedCharacter + generatedCharacter : rawGeneratedCharacter; + addMapping(newGeneratedLine, newGeneratedCharacter, newSourceIndex, newSourceLine, newSourceCharacter, newNameIndex); } + exit(); + } - function appendMappingCharCode(charCode: number) { - mappingCharCodes.push(charCode); - // String.fromCharCode accepts its arguments on the stack, so we have to chunk the input, - // otherwise we can get stack overflows for large source maps - if (mappingCharCodes.length >= 1024) { - flushMappingBuffer(); - } + function shouldCommitMapping() { + return !hasLast + || lastGeneratedLine !== pendingGeneratedLine + || lastGeneratedCharacter !== pendingGeneratedCharacter + || lastSourceIndex !== pendingSourceIndex + || lastSourceLine !== pendingSourceLine + || lastSourceCharacter !== pendingSourceCharacter + || lastNameIndex !== pendingNameIndex; + } + + function appendMappingCharCode(charCode: number) { + mappingCharCodes.push(charCode); + // String.fromCharCode accepts its arguments on the stack, so we have to chunk the input, + // otherwise we can get stack overflows for large source maps + if (mappingCharCodes.length >= 1024) { + flushMappingBuffer(); } + } - function commitPendingMapping() { - if (!hasPending || !shouldCommitMapping()) { - return; - } + function commitPendingMapping() { + if (!hasPending || !shouldCommitMapping()) { + return; + } - enter(); + enter(); - // Line/Comma delimiters - if (lastGeneratedLine < pendingGeneratedLine) { - // Emit line delimiters - do { - appendMappingCharCode(CharacterCodes.semicolon); - lastGeneratedLine++; - } - while (lastGeneratedLine < pendingGeneratedLine); - // Only need to set this once - lastGeneratedCharacter = 0; + // Line/Comma delimiters + if (lastGeneratedLine < pendingGeneratedLine) { + // Emit line delimiters + do { + appendMappingCharCode(CharacterCodes.semicolon); + lastGeneratedLine++; } - else { - Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack"); - // Emit comma to separate the entry - if (hasLast) { - appendMappingCharCode(CharacterCodes.comma); - } + while (lastGeneratedLine < pendingGeneratedLine); + // Only need to set this once + lastGeneratedCharacter = 0; + } + else { + Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack"); + // Emit comma to separate the entry + if (hasLast) { + appendMappingCharCode(CharacterCodes.comma); } + } - // 1. Relative generated character - appendBase64VLQ(pendingGeneratedCharacter - lastGeneratedCharacter); - lastGeneratedCharacter = pendingGeneratedCharacter; + // 1. Relative generated character + appendBase64VLQ(pendingGeneratedCharacter - lastGeneratedCharacter); + lastGeneratedCharacter = pendingGeneratedCharacter; - if (hasPendingSource) { - // 2. Relative sourceIndex - appendBase64VLQ(pendingSourceIndex - lastSourceIndex); - lastSourceIndex = pendingSourceIndex; + if (hasPendingSource) { + // 2. Relative sourceIndex + appendBase64VLQ(pendingSourceIndex - lastSourceIndex); + lastSourceIndex = pendingSourceIndex; - // 3. Relative source line - appendBase64VLQ(pendingSourceLine - lastSourceLine); - lastSourceLine = pendingSourceLine; + // 3. Relative source line + appendBase64VLQ(pendingSourceLine - lastSourceLine); + lastSourceLine = pendingSourceLine; - // 4. Relative source character - appendBase64VLQ(pendingSourceCharacter - lastSourceCharacter); - lastSourceCharacter = pendingSourceCharacter; + // 4. Relative source character + appendBase64VLQ(pendingSourceCharacter - lastSourceCharacter); + lastSourceCharacter = pendingSourceCharacter; - if (hasPendingName) { - // 5. Relative nameIndex - appendBase64VLQ(pendingNameIndex - lastNameIndex); - lastNameIndex = pendingNameIndex; - } + if (hasPendingName) { + // 5. Relative nameIndex + appendBase64VLQ(pendingNameIndex - lastNameIndex); + lastNameIndex = pendingNameIndex; } - - hasLast = true; - exit(); } - function flushMappingBuffer(): void { - if (mappingCharCodes.length > 0) { - mappings += String.fromCharCode.apply(undefined, mappingCharCodes); - mappingCharCodes.length = 0; - } - } + hasLast = true; + exit(); + } - function toJSON(): RawSourceMap { - commitPendingMapping(); - flushMappingBuffer(); - return { - version: 3, - file, - sourceRoot, - sources, - names, - mappings, - sourcesContent, - }; + function flushMappingBuffer(): void { + if (mappingCharCodes.length > 0) { + mappings += String.fromCharCode.apply(undefined, mappingCharCodes); + mappingCharCodes.length = 0; } + } - function appendBase64VLQ(inValue: number): void { - // Add a new least significant bit that has the sign of the value. - // if negative number the least significant bit that gets added to the number has value 1 - // else least significant bit value that gets added is 0 - // eg. -1 changes to binary : 01 [1] => 3 - // +1 changes to binary : 01 [0] => 2 - if (inValue < 0) { - inValue = ((-inValue) << 1) + 1; - } - else { - inValue = inValue << 1; - } + function toJSON(): RawSourceMap { + commitPendingMapping(); + flushMappingBuffer(); + return { + version: 3, + file, + sourceRoot, + sources, + names, + mappings, + sourcesContent, + }; + } - // Encode 5 bits at a time starting from least significant bits - do { - let currentDigit = inValue & 31; // 11111 - inValue = inValue >> 5; - if (inValue > 0) { - // There are still more digits to decode, set the msb (6th bit) - currentDigit = currentDigit | 32; - } - appendMappingCharCode(base64FormatEncode(currentDigit)); - } while (inValue > 0); + function appendBase64VLQ(inValue: number): void { + // Add a new least significant bit that has the sign of the value. + // if negative number the least significant bit that gets added to the number has value 1 + // else least significant bit value that gets added is 0 + // eg. -1 changes to binary : 01 [1] => 3 + // +1 changes to binary : 01 [0] => 2 + if (inValue < 0) { + inValue = ((-inValue) << 1) + 1; + } + else { + inValue = inValue << 1; } + + // Encode 5 bits at a time starting from least significant bits + do { + let currentDigit = inValue & 31; // 11111 + inValue = inValue >> 5; + if (inValue > 0) { + // There are still more digits to decode, set the msb (6th bit) + currentDigit = currentDigit | 32; + } + appendMappingCharCode(base64FormatEncode(currentDigit)); + } while (inValue > 0); } +} - // Sometimes tools can see the following line as a source mapping url comment, so we mangle it a bit (the [M]) - export const sourceMapCommentRegExpDontCareLineStart = /\/\/[@#] source[M]appingURL=(.+)\r?\n?$/; - export const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\r?\n?$/; +// Sometimes tools can see the following line as a source mapping url comment, so we mangle it a bit (the [M]) +/** @internal */ +export const sourceMapCommentRegExpDontCareLineStart = /\/\/[@#] source[M]appingURL=(.+)\r?\n?$/; +/** @internal */ +export const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\r?\n?$/; - export const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/; +/** @internal */ +export const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/; - export interface LineInfo { - getLineCount(): number; - getLineText(line: number): string; - } +/** @internal */ +export interface LineInfo { + getLineCount(): number; + getLineText(line: number): string; +} - export function getLineInfo(text: string, lineStarts: readonly number[]): LineInfo { - return { - getLineCount: () => lineStarts.length, - getLineText: line => text.substring(lineStarts[line], lineStarts[line + 1]) - }; - } +/** @internal */ +export function getLineInfo(text: string, lineStarts: readonly number[]): LineInfo { + return { + getLineCount: () => lineStarts.length, + getLineText: line => text.substring(lineStarts[line], lineStarts[line + 1]) + }; +} - /** - * Tries to find the sourceMappingURL comment at the end of a file. - */ - export function tryGetSourceMappingURL(lineInfo: LineInfo) { - for (let index = lineInfo.getLineCount() - 1; index >= 0; index--) { - const line = lineInfo.getLineText(index); - const comment = sourceMapCommentRegExp.exec(line); - if (comment) { - return trimStringEnd(comment[1]); - } - // If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file - else if (!line.match(whitespaceOrMapCommentRegExp)) { - break; - } +/** + * Tries to find the sourceMappingURL comment at the end of a file. + * + * @internal + */ +export function tryGetSourceMappingURL(lineInfo: LineInfo) { + for (let index = lineInfo.getLineCount() - 1; index >= 0; index--) { + const line = lineInfo.getLineText(index); + const comment = sourceMapCommentRegExp.exec(line); + if (comment) { + return trimStringEnd(comment[1]); + } + // If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file + else if (!line.match(whitespaceOrMapCommentRegExp)) { + break; } } +} - /* eslint-disable no-null/no-null */ - function isStringOrNull(x: any) { - return typeof x === "string" || x === null; - } +/* eslint-disable no-null/no-null */ +function isStringOrNull(x: any) { + return typeof x === "string" || x === null; +} - export function isRawSourceMap(x: any): x is RawSourceMap { - return x !== null - && typeof x === "object" - && x.version === 3 - && typeof x.file === "string" - && typeof x.mappings === "string" - && isArray(x.sources) && every(x.sources, isString) - && (x.sourceRoot === undefined || x.sourceRoot === null || typeof x.sourceRoot === "string") - && (x.sourcesContent === undefined || x.sourcesContent === null || isArray(x.sourcesContent) && every(x.sourcesContent, isStringOrNull)) - && (x.names === undefined || x.names === null || isArray(x.names) && every(x.names, isString)); +/** @internal */ +export function isRawSourceMap(x: any): x is RawSourceMap { + return x !== null + && typeof x === "object" + && x.version === 3 + && typeof x.file === "string" + && typeof x.mappings === "string" + && isArray(x.sources) && every(x.sources, isString) + && (x.sourceRoot === undefined || x.sourceRoot === null || typeof x.sourceRoot === "string") + && (x.sourcesContent === undefined || x.sourcesContent === null || isArray(x.sourcesContent) && every(x.sourcesContent, isStringOrNull)) + && (x.names === undefined || x.names === null || isArray(x.names) && every(x.names, isString)); +} +/* eslint-enable no-null/no-null */ + +/** @internal */ +export function tryParseRawSourceMap(text: string) { + try { + const parsed = JSON.parse(text); + if (isRawSourceMap(parsed)) { + return parsed; + } + } + catch { + // empty } - /* eslint-enable no-null/no-null */ - export function tryParseRawSourceMap(text: string) { - try { - const parsed = JSON.parse(text); - if (isRawSourceMap(parsed)) { - return parsed; - } - } - catch { - // empty - } + return undefined; +} - return undefined; - } +/** @internal */ +export interface MappingsDecoder extends Iterator { + readonly pos: number; + readonly error: string | undefined; + readonly state: Required; +} - export interface MappingsDecoder extends Iterator { - readonly pos: number; - readonly error: string | undefined; - readonly state: Required; - } +/** @internal */ +export interface Mapping { + generatedLine: number; + generatedCharacter: number; + sourceIndex?: number; + sourceLine?: number; + sourceCharacter?: number; + nameIndex?: number; +} - export interface Mapping { - generatedLine: number; - generatedCharacter: number; - sourceIndex?: number; - sourceLine?: number; - sourceCharacter?: number; - nameIndex?: number; - } +/** @internal */ +export interface SourceMapping extends Mapping { + sourceIndex: number; + sourceLine: number; + sourceCharacter: number; +} - export interface SourceMapping extends Mapping { - sourceIndex: number; - sourceLine: number; - sourceCharacter: number; - } +/** @internal */ +export function decodeMappings(mappings: string): MappingsDecoder { + let done = false; + let pos = 0; + let generatedLine = 0; + let generatedCharacter = 0; + let sourceIndex = 0; + let sourceLine = 0; + let sourceCharacter = 0; + let nameIndex = 0; + let error: string | undefined; + + return { + get pos() { return pos; }, + get error() { return error; }, + get state() { return captureMapping(/*hasSource*/ true, /*hasName*/ true); }, + next() { + while (!done && pos < mappings.length) { + const ch = mappings.charCodeAt(pos); + if (ch === CharacterCodes.semicolon) { + // new line + generatedLine++; + generatedCharacter = 0; + pos++; + continue; + } - export function decodeMappings(mappings: string): MappingsDecoder { - let done = false; - let pos = 0; - let generatedLine = 0; - let generatedCharacter = 0; - let sourceIndex = 0; - let sourceLine = 0; - let sourceCharacter = 0; - let nameIndex = 0; - let error: string | undefined; + if (ch === CharacterCodes.comma) { + // Next entry is on same line - no action needed + pos++; + continue; + } - return { - get pos() { return pos; }, - get error() { return error; }, - get state() { return captureMapping(/*hasSource*/ true, /*hasName*/ true); }, - next() { - while (!done && pos < mappings.length) { - const ch = mappings.charCodeAt(pos); - if (ch === CharacterCodes.semicolon) { - // new line - generatedLine++; - generatedCharacter = 0; - pos++; - continue; - } + let hasSource = false; + let hasName = false; - if (ch === CharacterCodes.comma) { - // Next entry is on same line - no action needed - pos++; - continue; - } + generatedCharacter += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (generatedCharacter < 0) return setErrorAndStopIterating("Invalid generatedCharacter found"); - let hasSource = false; - let hasName = false; + if (!isSourceMappingSegmentEnd()) { + hasSource = true; - generatedCharacter += base64VLQFormatDecode(); + sourceIndex += base64VLQFormatDecode(); if (hasReportedError()) return stopIterating(); - if (generatedCharacter < 0) return setErrorAndStopIterating("Invalid generatedCharacter found"); - - if (!isSourceMappingSegmentEnd()) { - hasSource = true; + if (sourceIndex < 0) return setErrorAndStopIterating("Invalid sourceIndex found"); + if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceIndex"); - sourceIndex += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (sourceIndex < 0) return setErrorAndStopIterating("Invalid sourceIndex found"); - if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceIndex"); + sourceLine += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (sourceLine < 0) return setErrorAndStopIterating("Invalid sourceLine found"); + if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceLine"); - sourceLine += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (sourceLine < 0) return setErrorAndStopIterating("Invalid sourceLine found"); - if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceLine"); + sourceCharacter += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (sourceCharacter < 0) return setErrorAndStopIterating("Invalid sourceCharacter found"); - sourceCharacter += base64VLQFormatDecode(); + if (!isSourceMappingSegmentEnd()) { + hasName = true; + nameIndex += base64VLQFormatDecode(); if (hasReportedError()) return stopIterating(); - if (sourceCharacter < 0) return setErrorAndStopIterating("Invalid sourceCharacter found"); - - if (!isSourceMappingSegmentEnd()) { - hasName = true; - nameIndex += base64VLQFormatDecode(); - if (hasReportedError()) return stopIterating(); - if (nameIndex < 0) return setErrorAndStopIterating("Invalid nameIndex found"); + if (nameIndex < 0) return setErrorAndStopIterating("Invalid nameIndex found"); - if (!isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Error Format: Entries after nameIndex"); - } + if (!isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Error Format: Entries after nameIndex"); } - - return { value: captureMapping(hasSource, hasName), done }; } - return stopIterating(); + return { value: captureMapping(hasSource, hasName), done }; } - }; - function captureMapping(hasSource: true, hasName: true): Required; - function captureMapping(hasSource: boolean, hasName: boolean): Mapping; - function captureMapping(hasSource: boolean, hasName: boolean): Mapping { - return { - generatedLine, - generatedCharacter, - sourceIndex: hasSource ? sourceIndex : undefined, - sourceLine: hasSource ? sourceLine : undefined, - sourceCharacter: hasSource ? sourceCharacter : undefined, - nameIndex: hasName ? nameIndex : undefined - }; + return stopIterating(); } + }; - function stopIterating(): { value: never, done: true } { - done = true; - return { value: undefined!, done: true }; - } + function captureMapping(hasSource: true, hasName: true): Required; + function captureMapping(hasSource: boolean, hasName: boolean): Mapping; + function captureMapping(hasSource: boolean, hasName: boolean): Mapping { + return { + generatedLine, + generatedCharacter, + sourceIndex: hasSource ? sourceIndex : undefined, + sourceLine: hasSource ? sourceLine : undefined, + sourceCharacter: hasSource ? sourceCharacter : undefined, + nameIndex: hasName ? nameIndex : undefined + }; + } - function setError(message: string) { - if (error === undefined) { - error = message; - } - } + function stopIterating(): { value: never, done: true } { + done = true; + return { value: undefined!, done: true }; + } - function setErrorAndStopIterating(message: string) { - setError(message); - return stopIterating(); + function setError(message: string) { + if (error === undefined) { + error = message; } + } - function hasReportedError() { - return error !== undefined; - } + function setErrorAndStopIterating(message: string) { + setError(message); + return stopIterating(); + } - function isSourceMappingSegmentEnd() { - return (pos === mappings.length || - mappings.charCodeAt(pos) === CharacterCodes.comma || - mappings.charCodeAt(pos) === CharacterCodes.semicolon); - } + function hasReportedError() { + return error !== undefined; + } - function base64VLQFormatDecode(): number { - let moreDigits = true; - let shiftCount = 0; - let value = 0; + function isSourceMappingSegmentEnd() { + return (pos === mappings.length || + mappings.charCodeAt(pos) === CharacterCodes.comma || + mappings.charCodeAt(pos) === CharacterCodes.semicolon); + } - for (; moreDigits; pos++) { - if (pos >= mappings.length) return setError("Error in decoding base64VLQFormatDecode, past the mapping string"), -1; + function base64VLQFormatDecode(): number { + let moreDigits = true; + let shiftCount = 0; + let value = 0; - // 6 digit number - const currentByte = base64FormatDecode(mappings.charCodeAt(pos)); - if (currentByte === -1) return setError("Invalid character in VLQ"), -1; + for (; moreDigits; pos++) { + if (pos >= mappings.length) return setError("Error in decoding base64VLQFormatDecode, past the mapping string"), -1; - // If msb is set, we still have more bits to continue - moreDigits = (currentByte & 32) !== 0; + // 6 digit number + const currentByte = base64FormatDecode(mappings.charCodeAt(pos)); + if (currentByte === -1) return setError("Invalid character in VLQ"), -1; - // least significant 5 bits are the next msbs in the final value. - value = value | ((currentByte & 31) << shiftCount); - shiftCount += 5; - } + // If msb is set, we still have more bits to continue + moreDigits = (currentByte & 32) !== 0; - // Least significant bit if 1 represents negative and rest of the msb is actual absolute value - if ((value & 1) === 0) { - // + number - value = value >> 1; - } - else { - // - number - value = value >> 1; - value = -value; - } + // least significant 5 bits are the next msbs in the final value. + value = value | ((currentByte & 31) << shiftCount); + shiftCount += 5; + } - return value; + // Least significant bit if 1 represents negative and rest of the msb is actual absolute value + if ((value & 1) === 0) { + // + number + value = value >> 1; + } + else { + // - number + value = value >> 1; + value = -value; } - } - export function sameMapping(left: T, right: T) { - return left === right - || left.generatedLine === right.generatedLine - && left.generatedCharacter === right.generatedCharacter - && left.sourceIndex === right.sourceIndex - && left.sourceLine === right.sourceLine - && left.sourceCharacter === right.sourceCharacter - && left.nameIndex === right.nameIndex; + return value; } +} - export function isSourceMapping(mapping: Mapping): mapping is SourceMapping { - return mapping.sourceIndex !== undefined - && mapping.sourceLine !== undefined - && mapping.sourceCharacter !== undefined; - } +/** @internal */ +export function sameMapping(left: T, right: T) { + return left === right + || left.generatedLine === right.generatedLine + && left.generatedCharacter === right.generatedCharacter + && left.sourceIndex === right.sourceIndex + && left.sourceLine === right.sourceLine + && left.sourceCharacter === right.sourceCharacter + && left.nameIndex === right.nameIndex; +} - function base64FormatEncode(value: number) { - return value >= 0 && value < 26 ? CharacterCodes.A + value : - value >= 26 && value < 52 ? CharacterCodes.a + value - 26 : - value >= 52 && value < 62 ? CharacterCodes._0 + value - 52 : - value === 62 ? CharacterCodes.plus : - value === 63 ? CharacterCodes.slash : - Debug.fail(`${value}: not a base64 value`); - } +/** @internal */ +export function isSourceMapping(mapping: Mapping): mapping is SourceMapping { + return mapping.sourceIndex !== undefined + && mapping.sourceLine !== undefined + && mapping.sourceCharacter !== undefined; +} - function base64FormatDecode(ch: number) { - return ch >= CharacterCodes.A && ch <= CharacterCodes.Z ? ch - CharacterCodes.A : - ch >= CharacterCodes.a && ch <= CharacterCodes.z ? ch - CharacterCodes.a + 26 : - ch >= CharacterCodes._0 && ch <= CharacterCodes._9 ? ch - CharacterCodes._0 + 52 : - ch === CharacterCodes.plus ? 62 : - ch === CharacterCodes.slash ? 63 : - -1; - } +function base64FormatEncode(value: number) { + return value >= 0 && value < 26 ? CharacterCodes.A + value : + value >= 26 && value < 52 ? CharacterCodes.a + value - 26 : + value >= 52 && value < 62 ? CharacterCodes._0 + value - 52 : + value === 62 ? CharacterCodes.plus : + value === 63 ? CharacterCodes.slash : + Debug.fail(`${value}: not a base64 value`); +} - interface MappedPosition { - generatedPosition: number; - source: string | undefined; - sourceIndex: number | undefined; - sourcePosition: number | undefined; - nameIndex: number | undefined; - } +function base64FormatDecode(ch: number) { + return ch >= CharacterCodes.A && ch <= CharacterCodes.Z ? ch - CharacterCodes.A : + ch >= CharacterCodes.a && ch <= CharacterCodes.z ? ch - CharacterCodes.a + 26 : + ch >= CharacterCodes._0 && ch <= CharacterCodes._9 ? ch - CharacterCodes._0 + 52 : + ch === CharacterCodes.plus ? 62 : + ch === CharacterCodes.slash ? 63 : + -1; +} - interface SourceMappedPosition extends MappedPosition { - source: string; - sourceIndex: number; - sourcePosition: number; - } +interface MappedPosition { + generatedPosition: number; + source: string | undefined; + sourceIndex: number | undefined; + sourcePosition: number | undefined; + nameIndex: number | undefined; +} - function isSourceMappedPosition(value: MappedPosition): value is SourceMappedPosition { - return value.sourceIndex !== undefined - && value.sourcePosition !== undefined; - } +interface SourceMappedPosition extends MappedPosition { + source: string; + sourceIndex: number; + sourcePosition: number; +} - function sameMappedPosition(left: MappedPosition, right: MappedPosition) { - return left.generatedPosition === right.generatedPosition - && left.sourceIndex === right.sourceIndex - && left.sourcePosition === right.sourcePosition; - } +function isSourceMappedPosition(value: MappedPosition): value is SourceMappedPosition { + return value.sourceIndex !== undefined + && value.sourcePosition !== undefined; +} - function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) { - // Compares sourcePosition without comparing sourceIndex - // since the mappings are grouped by sourceIndex - Debug.assert(left.sourceIndex === right.sourceIndex); - return compareValues(left.sourcePosition, right.sourcePosition); - } +function sameMappedPosition(left: MappedPosition, right: MappedPosition) { + return left.generatedPosition === right.generatedPosition + && left.sourceIndex === right.sourceIndex + && left.sourcePosition === right.sourcePosition; +} - function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) { - return compareValues(left.generatedPosition, right.generatedPosition); - } +function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) { + // Compares sourcePosition without comparing sourceIndex + // since the mappings are grouped by sourceIndex + Debug.assert(left.sourceIndex === right.sourceIndex); + return compareValues(left.sourcePosition, right.sourcePosition); +} - function getSourcePositionOfMapping(value: SourceMappedPosition) { - return value.sourcePosition; - } +function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) { + return compareValues(left.generatedPosition, right.generatedPosition); +} - function getGeneratedPositionOfMapping(value: MappedPosition) { - return value.generatedPosition; - } +function getSourcePositionOfMapping(value: SourceMappedPosition) { + return value.sourcePosition; +} - export function createDocumentPositionMapper(host: DocumentPositionMapperHost, map: RawSourceMap, mapPath: string): DocumentPositionMapper { - const mapDirectory = getDirectoryPath(mapPath); - const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory; - const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory); - const generatedFile = host.getSourceFileLike(generatedAbsoluteFilePath); - const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot)); - const sourceToSourceIndexMap = new Map(sourceFileAbsolutePaths.map((source, i) => [host.getCanonicalFileName(source), i])); - let decodedMappings: readonly MappedPosition[] | undefined; - let generatedMappings: SortedReadonlyArray | undefined; - let sourceMappings: readonly SortedReadonlyArray[] | undefined; +function getGeneratedPositionOfMapping(value: MappedPosition) { + return value.generatedPosition; +} - return { - getSourcePosition, - getGeneratedPosition - }; +/** @internal */ +export function createDocumentPositionMapper(host: DocumentPositionMapperHost, map: RawSourceMap, mapPath: string): DocumentPositionMapper { + const mapDirectory = getDirectoryPath(mapPath); + const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory; + const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory); + const generatedFile = host.getSourceFileLike(generatedAbsoluteFilePath); + const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot)); + const sourceToSourceIndexMap = new Map(sourceFileAbsolutePaths.map((source, i) => [host.getCanonicalFileName(source), i])); + let decodedMappings: readonly MappedPosition[] | undefined; + let generatedMappings: SortedReadonlyArray | undefined; + let sourceMappings: readonly SortedReadonlyArray[] | undefined; + + return { + getSourcePosition, + getGeneratedPosition + }; - function processMapping(mapping: Mapping): MappedPosition { - const generatedPosition = generatedFile !== undefined - ? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter, /*allowEdits*/ true) + function processMapping(mapping: Mapping): MappedPosition { + const generatedPosition = generatedFile !== undefined + ? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter, /*allowEdits*/ true) + : -1; + let source: string | undefined; + let sourcePosition: number | undefined; + if (isSourceMapping(mapping)) { + const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]); + source = map.sources[mapping.sourceIndex]; + sourcePosition = sourceFile !== undefined + ? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter, /*allowEdits*/ true) : -1; - let source: string | undefined; - let sourcePosition: number | undefined; - if (isSourceMapping(mapping)) { - const sourceFile = host.getSourceFileLike(sourceFileAbsolutePaths[mapping.sourceIndex]); - source = map.sources[mapping.sourceIndex]; - sourcePosition = sourceFile !== undefined - ? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter, /*allowEdits*/ true) - : -1; - } - return { - generatedPosition, - source, - sourceIndex: mapping.sourceIndex, - sourcePosition, - nameIndex: mapping.nameIndex - }; } + return { + generatedPosition, + source, + sourceIndex: mapping.sourceIndex, + sourcePosition, + nameIndex: mapping.nameIndex + }; + } - function getDecodedMappings() { - if (decodedMappings === undefined) { - const decoder = decodeMappings(map.mappings); - const mappings = arrayFrom(decoder, processMapping); - if (decoder.error !== undefined) { - if (host.log) { - host.log(`Encountered error while decoding sourcemap: ${decoder.error}`); - } - decodedMappings = emptyArray; - } - else { - decodedMappings = mappings; + function getDecodedMappings() { + if (decodedMappings === undefined) { + const decoder = decodeMappings(map.mappings); + const mappings = arrayFrom(decoder, processMapping); + if (decoder.error !== undefined) { + if (host.log) { + host.log(`Encountered error while decoding sourcemap: ${decoder.error}`); } + decodedMappings = emptyArray; + } + else { + decodedMappings = mappings; } - return decodedMappings; } + return decodedMappings; + } - function getSourceMappings(sourceIndex: number) { - if (sourceMappings === undefined) { - const lists: SourceMappedPosition[][] = []; - for (const mapping of getDecodedMappings()) { - if (!isSourceMappedPosition(mapping)) continue; - let list = lists[mapping.sourceIndex]; - if (!list) lists[mapping.sourceIndex] = list = []; - list.push(mapping); - } - sourceMappings = lists.map(list => sortAndDeduplicate(list, compareSourcePositions, sameMappedPosition)); + function getSourceMappings(sourceIndex: number) { + if (sourceMappings === undefined) { + const lists: SourceMappedPosition[][] = []; + for (const mapping of getDecodedMappings()) { + if (!isSourceMappedPosition(mapping)) continue; + let list = lists[mapping.sourceIndex]; + if (!list) lists[mapping.sourceIndex] = list = []; + list.push(mapping); } - return sourceMappings[sourceIndex]; + sourceMappings = lists.map(list => sortAndDeduplicate(list, compareSourcePositions, sameMappedPosition)); } + return sourceMappings[sourceIndex]; + } - function getGeneratedMappings() { - if (generatedMappings === undefined) { - const list: MappedPosition[] = []; - for (const mapping of getDecodedMappings()) { - list.push(mapping); - } - generatedMappings = sortAndDeduplicate(list, compareGeneratedPositions, sameMappedPosition); + function getGeneratedMappings() { + if (generatedMappings === undefined) { + const list: MappedPosition[] = []; + for (const mapping of getDecodedMappings()) { + list.push(mapping); } - return generatedMappings; + generatedMappings = sortAndDeduplicate(list, compareGeneratedPositions, sameMappedPosition); } + return generatedMappings; + } - function getGeneratedPosition(loc: DocumentPosition): DocumentPosition { - const sourceIndex = sourceToSourceIndexMap.get(host.getCanonicalFileName(loc.fileName)); - if (sourceIndex === undefined) return loc; + function getGeneratedPosition(loc: DocumentPosition): DocumentPosition { + const sourceIndex = sourceToSourceIndexMap.get(host.getCanonicalFileName(loc.fileName)); + if (sourceIndex === undefined) return loc; - const sourceMappings = getSourceMappings(sourceIndex); - if (!some(sourceMappings)) return loc; + const sourceMappings = getSourceMappings(sourceIndex); + if (!some(sourceMappings)) return loc; - let targetIndex = binarySearchKey(sourceMappings, loc.pos, getSourcePositionOfMapping, compareValues); - if (targetIndex < 0) { - // if no exact match, closest is 2's complement of result - targetIndex = ~targetIndex; - } - - const mapping = sourceMappings[targetIndex]; - if (mapping === undefined || mapping.sourceIndex !== sourceIndex) { - return loc; - } + let targetIndex = binarySearchKey(sourceMappings, loc.pos, getSourcePositionOfMapping, compareValues); + if (targetIndex < 0) { + // if no exact match, closest is 2's complement of result + targetIndex = ~targetIndex; + } - return { fileName: generatedAbsoluteFilePath, pos: mapping.generatedPosition }; // Closest pos + const mapping = sourceMappings[targetIndex]; + if (mapping === undefined || mapping.sourceIndex !== sourceIndex) { + return loc; } - function getSourcePosition(loc: DocumentPosition): DocumentPosition { - const generatedMappings = getGeneratedMappings(); - if (!some(generatedMappings)) return loc; + return { fileName: generatedAbsoluteFilePath, pos: mapping.generatedPosition }; // Closest pos + } - let targetIndex = binarySearchKey(generatedMappings, loc.pos, getGeneratedPositionOfMapping, compareValues); - if (targetIndex < 0) { - // if no exact match, closest is 2's complement of result - targetIndex = ~targetIndex; - } + function getSourcePosition(loc: DocumentPosition): DocumentPosition { + const generatedMappings = getGeneratedMappings(); + if (!some(generatedMappings)) return loc; - const mapping = generatedMappings[targetIndex]; - if (mapping === undefined || !isSourceMappedPosition(mapping)) { - return loc; - } + let targetIndex = binarySearchKey(generatedMappings, loc.pos, getGeneratedPositionOfMapping, compareValues); + if (targetIndex < 0) { + // if no exact match, closest is 2's complement of result + targetIndex = ~targetIndex; + } - return { fileName: sourceFileAbsolutePaths[mapping.sourceIndex], pos: mapping.sourcePosition }; // Closest pos + const mapping = generatedMappings[targetIndex]; + if (mapping === undefined || !isSourceMappedPosition(mapping)) { + return loc; } - } - export const identitySourceMapConsumer: DocumentPositionMapper = { - getSourcePosition: identity, - getGeneratedPosition: identity - }; + return { fileName: sourceFileAbsolutePaths[mapping.sourceIndex], pos: mapping.sourcePosition }; // Closest pos + } } + +/** @internal */ +export const identitySourceMapConsumer: DocumentPositionMapper = { + getSourcePosition: identity, + getGeneratedPosition: identity +}; diff --git a/src/compiler/symbolWalker.ts b/src/compiler/symbolWalker.ts index 4130d7fc84f9e..71277f27d7d5c 100644 --- a/src/compiler/symbolWalker.ts +++ b/src/compiler/symbolWalker.ts @@ -1,190 +1,194 @@ +import { + clear, EntityNameOrEntityNameExpression, forEach, getOwnValues, getSymbolId, Identifier, IndexedAccessType, + IndexType, InterfaceType, MappedType, Node, ObjectFlags, ObjectType, ResolvedType, Signature, Symbol, SymbolWalker, + SyntaxKind, Type, TypeFlags, TypeParameter, TypePredicate, TypeQueryNode, TypeReference, UnionOrIntersectionType, +} from "./_namespaces/ts"; + /** @internal */ -namespace ts { - export function createGetSymbolWalker( - getRestTypeOfSignature: (sig: Signature) => Type, - getTypePredicateOfSignature: (sig: Signature) => TypePredicate | undefined, - getReturnTypeOfSignature: (sig: Signature) => Type, - getBaseTypes: (type: Type) => Type[], - resolveStructuredTypeMembers: (type: ObjectType) => ResolvedType, - getTypeOfSymbol: (sym: Symbol) => Type, - getResolvedSymbol: (node: Node) => Symbol, - getConstraintOfTypeParameter: (typeParameter: TypeParameter) => Type | undefined, - getFirstIdentifier: (node: EntityNameOrEntityNameExpression) => Identifier, - getTypeArguments: (type: TypeReference) => readonly Type[]) { - - return getSymbolWalker; - - function getSymbolWalker(accept: (symbol: Symbol) => boolean = () => true): SymbolWalker { - const visitedTypes: Type[] = []; // Sparse array from id to type - const visitedSymbols: Symbol[] = []; // Sparse array from id to symbol - - return { - walkType: type => { - try { - visitType(type); - return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; - } - finally { - clear(visitedTypes); - clear(visitedSymbols); - } - }, - walkSymbol: symbol => { - try { - visitSymbol(symbol); - return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; - } - finally { - clear(visitedTypes); - clear(visitedSymbols); - } - }, - }; - - function visitType(type: Type | undefined): void { - if (!type) { - return; - } +export function createGetSymbolWalker( + getRestTypeOfSignature: (sig: Signature) => Type, + getTypePredicateOfSignature: (sig: Signature) => TypePredicate | undefined, + getReturnTypeOfSignature: (sig: Signature) => Type, + getBaseTypes: (type: Type) => Type[], + resolveStructuredTypeMembers: (type: ObjectType) => ResolvedType, + getTypeOfSymbol: (sym: Symbol) => Type, + getResolvedSymbol: (node: Node) => Symbol, + getConstraintOfTypeParameter: (typeParameter: TypeParameter) => Type | undefined, + getFirstIdentifier: (node: EntityNameOrEntityNameExpression) => Identifier, + getTypeArguments: (type: TypeReference) => readonly Type[]) { + + return getSymbolWalker; + + function getSymbolWalker(accept: (symbol: Symbol) => boolean = () => true): SymbolWalker { + const visitedTypes: Type[] = []; // Sparse array from id to type + const visitedSymbols: Symbol[] = []; // Sparse array from id to symbol + + return { + walkType: type => { + try { + visitType(type); + return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; + } + finally { + clear(visitedTypes); + clear(visitedSymbols); + } + }, + walkSymbol: symbol => { + try { + visitSymbol(symbol); + return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; + } + finally { + clear(visitedTypes); + clear(visitedSymbols); + } + }, + }; + + function visitType(type: Type | undefined): void { + if (!type) { + return; + } - if (visitedTypes[type.id]) { - return; - } - visitedTypes[type.id] = type; - - // Reuse visitSymbol to visit the type's symbol, - // but be sure to bail on recuring into the type if accept declines the symbol. - const shouldBail = visitSymbol(type.symbol); - if (shouldBail) return; - - // Visit the type's related types, if any - if (type.flags & TypeFlags.Object) { - const objectType = type as ObjectType; - const objectFlags = objectType.objectFlags; - if (objectFlags & ObjectFlags.Reference) { - visitTypeReference(type as TypeReference); - } - if (objectFlags & ObjectFlags.Mapped) { - visitMappedType(type as MappedType); - } - if (objectFlags & (ObjectFlags.Class | ObjectFlags.Interface)) { - visitInterfaceType(type as InterfaceType); - } - if (objectFlags & (ObjectFlags.Tuple | ObjectFlags.Anonymous)) { - visitObjectType(objectType); - } - } - if (type.flags & TypeFlags.TypeParameter) { - visitTypeParameter(type as TypeParameter); + if (visitedTypes[type.id]) { + return; + } + visitedTypes[type.id] = type; + + // Reuse visitSymbol to visit the type's symbol, + // but be sure to bail on recuring into the type if accept declines the symbol. + const shouldBail = visitSymbol(type.symbol); + if (shouldBail) return; + + // Visit the type's related types, if any + if (type.flags & TypeFlags.Object) { + const objectType = type as ObjectType; + const objectFlags = objectType.objectFlags; + if (objectFlags & ObjectFlags.Reference) { + visitTypeReference(type as TypeReference); } - if (type.flags & TypeFlags.UnionOrIntersection) { - visitUnionOrIntersectionType(type as UnionOrIntersectionType); + if (objectFlags & ObjectFlags.Mapped) { + visitMappedType(type as MappedType); } - if (type.flags & TypeFlags.Index) { - visitIndexType(type as IndexType); + if (objectFlags & (ObjectFlags.Class | ObjectFlags.Interface)) { + visitInterfaceType(type as InterfaceType); } - if (type.flags & TypeFlags.IndexedAccess) { - visitIndexedAccessType(type as IndexedAccessType); + if (objectFlags & (ObjectFlags.Tuple | ObjectFlags.Anonymous)) { + visitObjectType(objectType); } } - - function visitTypeReference(type: TypeReference): void { - visitType(type.target); - forEach(getTypeArguments(type), visitType); + if (type.flags & TypeFlags.TypeParameter) { + visitTypeParameter(type as TypeParameter); } - - function visitTypeParameter(type: TypeParameter): void { - visitType(getConstraintOfTypeParameter(type)); + if (type.flags & TypeFlags.UnionOrIntersection) { + visitUnionOrIntersectionType(type as UnionOrIntersectionType); } - - function visitUnionOrIntersectionType(type: UnionOrIntersectionType): void { - forEach(type.types, visitType); + if (type.flags & TypeFlags.Index) { + visitIndexType(type as IndexType); } - - function visitIndexType(type: IndexType): void { - visitType(type.type); + if (type.flags & TypeFlags.IndexedAccess) { + visitIndexedAccessType(type as IndexedAccessType); } + } - function visitIndexedAccessType(type: IndexedAccessType): void { - visitType(type.objectType); - visitType(type.indexType); - visitType(type.constraint); - } + function visitTypeReference(type: TypeReference): void { + visitType(type.target); + forEach(getTypeArguments(type), visitType); + } - function visitMappedType(type: MappedType): void { - visitType(type.typeParameter); - visitType(type.constraintType); - visitType(type.templateType); - visitType(type.modifiersType); - } + function visitTypeParameter(type: TypeParameter): void { + visitType(getConstraintOfTypeParameter(type)); + } - function visitSignature(signature: Signature): void { - const typePredicate = getTypePredicateOfSignature(signature); - if (typePredicate) { - visitType(typePredicate.type); - } - forEach(signature.typeParameters, visitType); + function visitUnionOrIntersectionType(type: UnionOrIntersectionType): void { + forEach(type.types, visitType); + } - for (const parameter of signature.parameters) { - visitSymbol(parameter); - } - visitType(getRestTypeOfSignature(signature)); - visitType(getReturnTypeOfSignature(signature)); + function visitIndexType(type: IndexType): void { + visitType(type.type); + } + + function visitIndexedAccessType(type: IndexedAccessType): void { + visitType(type.objectType); + visitType(type.indexType); + visitType(type.constraint); + } + + function visitMappedType(type: MappedType): void { + visitType(type.typeParameter); + visitType(type.constraintType); + visitType(type.templateType); + visitType(type.modifiersType); + } + + function visitSignature(signature: Signature): void { + const typePredicate = getTypePredicateOfSignature(signature); + if (typePredicate) { + visitType(typePredicate.type); } + forEach(signature.typeParameters, visitType); - function visitInterfaceType(interfaceT: InterfaceType): void { - visitObjectType(interfaceT); - forEach(interfaceT.typeParameters, visitType); - forEach(getBaseTypes(interfaceT), visitType); - visitType(interfaceT.thisType); + for (const parameter of signature.parameters) { + visitSymbol(parameter); } + visitType(getRestTypeOfSignature(signature)); + visitType(getReturnTypeOfSignature(signature)); + } - function visitObjectType(type: ObjectType): void { - const resolved = resolveStructuredTypeMembers(type); - for (const info of resolved.indexInfos) { - visitType(info.keyType); - visitType(info.type); - } - for (const signature of resolved.callSignatures) { - visitSignature(signature); - } - for (const signature of resolved.constructSignatures) { - visitSignature(signature); - } - for (const p of resolved.properties) { - visitSymbol(p); - } + function visitInterfaceType(interfaceT: InterfaceType): void { + visitObjectType(interfaceT); + forEach(interfaceT.typeParameters, visitType); + forEach(getBaseTypes(interfaceT), visitType); + visitType(interfaceT.thisType); + } + + function visitObjectType(type: ObjectType): void { + const resolved = resolveStructuredTypeMembers(type); + for (const info of resolved.indexInfos) { + visitType(info.keyType); + visitType(info.type); + } + for (const signature of resolved.callSignatures) { + visitSignature(signature); } + for (const signature of resolved.constructSignatures) { + visitSignature(signature); + } + for (const p of resolved.properties) { + visitSymbol(p); + } + } - function visitSymbol(symbol: Symbol | undefined): boolean { - if (!symbol) { - return false; - } - const symbolId = getSymbolId(symbol); - if (visitedSymbols[symbolId]) { - return false; - } - visitedSymbols[symbolId] = symbol; - if (!accept(symbol)) { - return true; - } - const t = getTypeOfSymbol(symbol); - visitType(t); // Should handle members on classes and such - if (symbol.exports) { - symbol.exports.forEach(visitSymbol); - } - forEach(symbol.declarations, d => { - // Type queries are too far resolved when we just visit the symbol's type - // (their type resolved directly to the member deeply referenced) - // So to get the intervening symbols, we need to check if there's a type - // query node on any of the symbol's declarations and get symbols there - if ((d as any).type && (d as any).type.kind === SyntaxKind.TypeQuery) { - const query = (d as any).type as TypeQueryNode; - const entity = getResolvedSymbol(getFirstIdentifier(query.exprName)); - visitSymbol(entity); - } - }); + function visitSymbol(symbol: Symbol | undefined): boolean { + if (!symbol) { + return false; + } + const symbolId = getSymbolId(symbol); + if (visitedSymbols[symbolId]) { return false; } + visitedSymbols[symbolId] = symbol; + if (!accept(symbol)) { + return true; + } + const t = getTypeOfSymbol(symbol); + visitType(t); // Should handle members on classes and such + if (symbol.exports) { + symbol.exports.forEach(visitSymbol); + } + forEach(symbol.declarations, d => { + // Type queries are too far resolved when we just visit the symbol's type + // (their type resolved directly to the member deeply referenced) + // So to get the intervening symbols, we need to check if there's a type + // query node on any of the symbol's declarations and get symbols there + if ((d as any).type && (d as any).type.kind === SyntaxKind.TypeQuery) { + const query = (d as any).type as TypeQueryNode; + const entity = getResolvedSymbol(getFirstIdentifier(query.exprName)); + visitSymbol(entity); + } + }); + return false; } } -} \ No newline at end of file +} diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index b9e6540291fa7..e3f3174e452cb 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -1,1981 +1,1997 @@ +import { + AssertionLevel, closeFileWatcher, closeFileWatcherOf, combinePaths, Comparison, contains, containsPath, + createGetCanonicalFileName, createMultiMap, Debug, directorySeparator, emptyArray, emptyFileSystemEntries, endsWith, + enumerateInsertsAndDeletes, ESMap, FileSystemEntries, getDirectoryPath, getFallbackOptions, + getNormalizedAbsolutePath, getRelativePathToDirectoryOrUrl, getRootLength, getStringComparer, isArray, isNodeLikeSystem, isString, + Map, mapDefined, matchesExclude, matchFiles, memoize, noop, normalizePath, normalizeSlashes, orderedRemoveItem, + Path, perfLogger, PollingWatchKind, RequireResult, resolveJSModule, some, startsWith, stringContains, timestamp, + unorderedRemoveItem, WatchDirectoryKind, WatchFileKind, WatchOptions, writeFileEnsuringDirectories, +} from "./_namespaces/ts"; + declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any; declare function clearTimeout(handle: any): void; -namespace ts { - /** - * djb2 hashing algorithm - * http://www.cse.yorku.ca/~oz/hash.html - */ - /* @internal */ - export function generateDjb2Hash(data: string): string { - let acc = 5381; - for (let i = 0; i < data.length; i++) { - acc = ((acc << 5) + acc) + data.charCodeAt(i); - } - return acc.toString(); +/** + * djb2 hashing algorithm + * http://www.cse.yorku.ca/~oz/hash.html + * + * @internal + */ +export function generateDjb2Hash(data: string): string { + let acc = 5381; + for (let i = 0; i < data.length; i++) { + acc = ((acc << 5) + acc) + data.charCodeAt(i); } + return acc.toString(); +} - /** - * Set a high stack trace limit to provide more information in case of an error. - * Called for command-line and server use cases. - * Not called if TypeScript is used as a library. - */ - /* @internal */ - export function setStackTraceLimit() { - if ((Error as any).stackTraceLimit < 100) { // Also tests that we won't set the property if it doesn't exist. - (Error as any).stackTraceLimit = 100; - } +/** + * Set a high stack trace limit to provide more information in case of an error. + * Called for command-line and server use cases. + * Not called if TypeScript is used as a library. + * + * @internal + */ +export function setStackTraceLimit() { + if ((Error as any).stackTraceLimit < 100) { // Also tests that we won't set the property if it doesn't exist. + (Error as any).stackTraceLimit = 100; } +} - export enum FileWatcherEventKind { - Created, - Changed, - Deleted - } +export enum FileWatcherEventKind { + Created, + Changed, + Deleted +} - export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, modifiedTime?: Date) => void; - export type DirectoryWatcherCallback = (fileName: string) => void; - interface WatchedFile { - readonly fileName: string; - readonly callback: FileWatcherCallback; - mtime: Date; - } +export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, modifiedTime?: Date) => void; +export type DirectoryWatcherCallback = (fileName: string) => void; +interface WatchedFile { + readonly fileName: string; + readonly callback: FileWatcherCallback; + mtime: Date; +} - /* @internal */ - export enum PollingInterval { - High = 2000, - Medium = 500, - Low = 250 - } +/** @internal */ +export enum PollingInterval { + High = 2000, + Medium = 500, + Low = 250 +} - /* @internal */ - export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) => FileWatcher; - /* @internal */ - export type HostWatchDirectory = (fileName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined) => FileWatcher; +/** @internal */ +export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) => FileWatcher; +/** @internal */ +export type HostWatchDirectory = (fileName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined) => FileWatcher; - /* @internal */ - export const missingFileModifiedTime = new Date(0); // Any subsequent modification will occur after this time +/** @internal */ +export const missingFileModifiedTime = new Date(0); // Any subsequent modification will occur after this time - /* @internal */ - export function getModifiedTime(host: { getModifiedTime: NonNullable; }, fileName: string) { - return host.getModifiedTime(fileName) || missingFileModifiedTime; - } +/** @internal */ +export function getModifiedTime(host: { getModifiedTime: NonNullable; }, fileName: string) { + return host.getModifiedTime(fileName) || missingFileModifiedTime; +} + +interface Levels { + Low: number; + Medium: number; + High: number; +} + +function createPollingIntervalBasedLevels(levels: Levels) { + return { + [PollingInterval.Low]: levels.Low, + [PollingInterval.Medium]: levels.Medium, + [PollingInterval.High]: levels.High + }; +} + +const defaultChunkLevels: Levels = { Low: 32, Medium: 64, High: 256 }; +let pollingChunkSize = createPollingIntervalBasedLevels(defaultChunkLevels); +/** @internal */ +export let unchangedPollThresholds = createPollingIntervalBasedLevels(defaultChunkLevels); - interface Levels { - Low: number; - Medium: number; - High: number; +function setCustomPollingValues(system: System) { + if (!system.getEnvironmentVariable) { + return; } + const pollingIntervalChanged = setCustomLevels("TSC_WATCH_POLLINGINTERVAL", PollingInterval); + pollingChunkSize = getCustomPollingBasedLevels("TSC_WATCH_POLLINGCHUNKSIZE", defaultChunkLevels) || pollingChunkSize; + unchangedPollThresholds = getCustomPollingBasedLevels("TSC_WATCH_UNCHANGEDPOLLTHRESHOLDS", defaultChunkLevels) || unchangedPollThresholds; - function createPollingIntervalBasedLevels(levels: Levels) { - return { - [PollingInterval.Low]: levels.Low, - [PollingInterval.Medium]: levels.Medium, - [PollingInterval.High]: levels.High - }; + function getLevel(envVar: string, level: keyof Levels) { + return system.getEnvironmentVariable(`${envVar}_${level.toUpperCase()}`); } - const defaultChunkLevels: Levels = { Low: 32, Medium: 64, High: 256 }; - let pollingChunkSize = createPollingIntervalBasedLevels(defaultChunkLevels); - /* @internal */ - export let unchangedPollThresholds = createPollingIntervalBasedLevels(defaultChunkLevels); + function getCustomLevels(baseVariable: string) { + let customLevels: Partial | undefined; + setCustomLevel("Low"); + setCustomLevel("Medium"); + setCustomLevel("High"); + return customLevels; - function setCustomPollingValues(system: System) { - if (!system.getEnvironmentVariable) { - return; + function setCustomLevel(level: keyof Levels) { + const customLevel = getLevel(baseVariable, level); + if (customLevel) { + (customLevels || (customLevels = {}))[level] = Number(customLevel); + } } - const pollingIntervalChanged = setCustomLevels("TSC_WATCH_POLLINGINTERVAL", PollingInterval); - pollingChunkSize = getCustomPollingBasedLevels("TSC_WATCH_POLLINGCHUNKSIZE", defaultChunkLevels) || pollingChunkSize; - unchangedPollThresholds = getCustomPollingBasedLevels("TSC_WATCH_UNCHANGEDPOLLTHRESHOLDS", defaultChunkLevels) || unchangedPollThresholds; + } - function getLevel(envVar: string, level: keyof Levels) { - return system.getEnvironmentVariable(`${envVar}_${level.toUpperCase()}`); + function setCustomLevels(baseVariable: string, levels: Levels) { + const customLevels = getCustomLevels(baseVariable); + if (customLevels) { + setLevel("Low"); + setLevel("Medium"); + setLevel("High"); + return true; } + return false; - function getCustomLevels(baseVariable: string) { - let customLevels: Partial | undefined; - setCustomLevel("Low"); - setCustomLevel("Medium"); - setCustomLevel("High"); - return customLevels; - - function setCustomLevel(level: keyof Levels) { - const customLevel = getLevel(baseVariable, level); - if (customLevel) { - (customLevels || (customLevels = {}))[level] = Number(customLevel); - } - } + function setLevel(level: keyof Levels) { + levels[level] = customLevels![level] || levels[level]; } + } - function setCustomLevels(baseVariable: string, levels: Levels) { - const customLevels = getCustomLevels(baseVariable); - if (customLevels) { - setLevel("Low"); - setLevel("Medium"); - setLevel("High"); - return true; - } - return false; + function getCustomPollingBasedLevels(baseVariable: string, defaultLevels: Levels) { + const customLevels = getCustomLevels(baseVariable); + return (pollingIntervalChanged || customLevels) && + createPollingIntervalBasedLevels(customLevels ? { ...defaultLevels, ...customLevels } : defaultLevels); + } +} - function setLevel(level: keyof Levels) { - levels[level] = customLevels![level] || levels[level]; - } +interface WatchedFileWithIsClosed extends WatchedFile { + isClosed?: boolean; +} +function pollWatchedFileQueue( + host: { getModifiedTime: NonNullable; }, + queue: (T | undefined)[], + pollIndex: number, chunkSize: number, + callbackOnWatchFileStat?: (watchedFile: T, pollIndex: number, fileChanged: boolean) => void +) { + let definedValueCopyToIndex = pollIndex; + // Max visit would be all elements of the queue + for (let canVisit = queue.length; chunkSize && canVisit; nextPollIndex(), canVisit--) { + const watchedFile = queue[pollIndex]; + if (!watchedFile) { + continue; + } + else if (watchedFile.isClosed) { + queue[pollIndex] = undefined; + continue; } - function getCustomPollingBasedLevels(baseVariable: string, defaultLevels: Levels) { - const customLevels = getCustomLevels(baseVariable); - return (pollingIntervalChanged || customLevels) && - createPollingIntervalBasedLevels(customLevels ? { ...defaultLevels, ...customLevels } : defaultLevels); + // Only files polled count towards chunkSize + chunkSize--; + const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(host, watchedFile.fileName)); + if (watchedFile.isClosed) { + // Closed watcher as part of callback + queue[pollIndex] = undefined; + continue; } - } - interface WatchedFileWithIsClosed extends WatchedFile { - isClosed?: boolean; - } - function pollWatchedFileQueue( - host: { getModifiedTime: NonNullable; }, - queue: (T | undefined)[], - pollIndex: number, chunkSize: number, - callbackOnWatchFileStat?: (watchedFile: T, pollIndex: number, fileChanged: boolean) => void - ) { - let definedValueCopyToIndex = pollIndex; - // Max visit would be all elements of the queue - for (let canVisit = queue.length; chunkSize && canVisit; nextPollIndex(), canVisit--) { - const watchedFile = queue[pollIndex]; - if (!watchedFile) { - continue; - } - else if (watchedFile.isClosed) { + callbackOnWatchFileStat?.(watchedFile, pollIndex, fileChanged); + // Defragment the queue while we are at it + if (queue[pollIndex]) { + // Copy this file to the non hole location + if (definedValueCopyToIndex < pollIndex) { + queue[definedValueCopyToIndex] = watchedFile; queue[pollIndex] = undefined; - continue; } + definedValueCopyToIndex++; + } + } - // Only files polled count towards chunkSize - chunkSize--; - const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(host, watchedFile.fileName)); - if (watchedFile.isClosed) { - // Closed watcher as part of callback - queue[pollIndex] = undefined; - continue; - } + // Return next poll index + return pollIndex; - callbackOnWatchFileStat?.(watchedFile, pollIndex, fileChanged); - // Defragment the queue while we are at it - if (queue[pollIndex]) { - // Copy this file to the non hole location - if (definedValueCopyToIndex < pollIndex) { - queue[definedValueCopyToIndex] = watchedFile; - queue[pollIndex] = undefined; - } - definedValueCopyToIndex++; + function nextPollIndex() { + pollIndex++; + if (pollIndex === queue.length) { + if (definedValueCopyToIndex < pollIndex) { + // There are holes from definedValueCopyToIndex to end of queue, change queue size + queue.length = definedValueCopyToIndex; } + pollIndex = 0; + definedValueCopyToIndex = 0; } + } +} + +interface WatchedFileWithUnchangedPolls extends WatchedFileWithIsClosed { + unchangedPolls: number; +} +function createDynamicPriorityPollingWatchFile(host: { + getModifiedTime: NonNullable; + setTimeout: NonNullable; +}): HostWatchFile { + interface PollingIntervalQueue extends Array { + pollingInterval: PollingInterval; + pollIndex: number; + pollScheduled: boolean; + } - // Return next poll index - return pollIndex; + const watchedFiles: WatchedFileWithUnchangedPolls[] = []; + const changedFilesInLastPoll: WatchedFileWithUnchangedPolls[] = []; + const lowPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Low); + const mediumPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Medium); + const highPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.High); + return watchFile; + + function watchFile(fileName: string, callback: FileWatcherCallback, defaultPollingInterval: PollingInterval): FileWatcher { + const file: WatchedFileWithUnchangedPolls = { + fileName, + callback, + unchangedPolls: 0, + mtime: getModifiedTime(host, fileName) + }; + watchedFiles.push(file); - function nextPollIndex() { - pollIndex++; - if (pollIndex === queue.length) { - if (definedValueCopyToIndex < pollIndex) { - // There are holes from definedValueCopyToIndex to end of queue, change queue size - queue.length = definedValueCopyToIndex; - } - pollIndex = 0; - definedValueCopyToIndex = 0; + addToPollingIntervalQueue(file, defaultPollingInterval); + return { + close: () => { + file.isClosed = true; + // Remove from watchedFiles + unorderedRemoveItem(watchedFiles, file); + // Do not update polling interval queue since that will happen as part of polling } - } + }; } - interface WatchedFileWithUnchangedPolls extends WatchedFileWithIsClosed { - unchangedPolls: number; + function createPollingIntervalQueue(pollingInterval: PollingInterval): PollingIntervalQueue { + const queue = [] as WatchedFileWithUnchangedPolls[] as PollingIntervalQueue; + queue.pollingInterval = pollingInterval; + queue.pollIndex = 0; + queue.pollScheduled = false; + return queue; } - function createDynamicPriorityPollingWatchFile(host: { - getModifiedTime: NonNullable; - setTimeout: NonNullable; - }): HostWatchFile { - interface PollingIntervalQueue extends Array { - pollingInterval: PollingInterval; - pollIndex: number; - pollScheduled: boolean; - } - - const watchedFiles: WatchedFileWithUnchangedPolls[] = []; - const changedFilesInLastPoll: WatchedFileWithUnchangedPolls[] = []; - const lowPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Low); - const mediumPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Medium); - const highPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.High); - return watchFile; - - function watchFile(fileName: string, callback: FileWatcherCallback, defaultPollingInterval: PollingInterval): FileWatcher { - const file: WatchedFileWithUnchangedPolls = { - fileName, - callback, - unchangedPolls: 0, - mtime: getModifiedTime(host, fileName) - }; - watchedFiles.push(file); - addToPollingIntervalQueue(file, defaultPollingInterval); - return { - close: () => { - file.isClosed = true; - // Remove from watchedFiles - unorderedRemoveItem(watchedFiles, file); - // Do not update polling interval queue since that will happen as part of polling - } - }; + function pollPollingIntervalQueue(queue: PollingIntervalQueue) { + queue.pollIndex = pollQueue(queue, queue.pollingInterval, queue.pollIndex, pollingChunkSize[queue.pollingInterval]); + // Set the next polling index and timeout + if (queue.length) { + scheduleNextPoll(queue.pollingInterval); } - - function createPollingIntervalQueue(pollingInterval: PollingInterval): PollingIntervalQueue { - const queue = [] as WatchedFileWithUnchangedPolls[] as PollingIntervalQueue; - queue.pollingInterval = pollingInterval; - queue.pollIndex = 0; + else { + Debug.assert(queue.pollIndex === 0); queue.pollScheduled = false; - return queue; - } - - function pollPollingIntervalQueue(queue: PollingIntervalQueue) { - queue.pollIndex = pollQueue(queue, queue.pollingInterval, queue.pollIndex, pollingChunkSize[queue.pollingInterval]); - // Set the next polling index and timeout - if (queue.length) { - scheduleNextPoll(queue.pollingInterval); - } - else { - Debug.assert(queue.pollIndex === 0); - queue.pollScheduled = false; - } } + } - function pollLowPollingIntervalQueue(queue: PollingIntervalQueue) { - // Always poll complete list of changedFilesInLastPoll - pollQueue(changedFilesInLastPoll, PollingInterval.Low, /*pollIndex*/ 0, changedFilesInLastPoll.length); + function pollLowPollingIntervalQueue(queue: PollingIntervalQueue) { + // Always poll complete list of changedFilesInLastPoll + pollQueue(changedFilesInLastPoll, PollingInterval.Low, /*pollIndex*/ 0, changedFilesInLastPoll.length); - // Finally do the actual polling of the queue - pollPollingIntervalQueue(queue); - // Schedule poll if there are files in changedFilesInLastPoll but no files in the actual queue - // as pollPollingIntervalQueue wont schedule for next poll - if (!queue.pollScheduled && changedFilesInLastPoll.length) { - scheduleNextPoll(PollingInterval.Low); - } + // Finally do the actual polling of the queue + pollPollingIntervalQueue(queue); + // Schedule poll if there are files in changedFilesInLastPoll but no files in the actual queue + // as pollPollingIntervalQueue wont schedule for next poll + if (!queue.pollScheduled && changedFilesInLastPoll.length) { + scheduleNextPoll(PollingInterval.Low); } + } - function pollQueue(queue: (WatchedFileWithUnchangedPolls | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) { - return pollWatchedFileQueue( - host, - queue, - pollIndex, - chunkSize, - onWatchFileStat - ); + function pollQueue(queue: (WatchedFileWithUnchangedPolls | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) { + return pollWatchedFileQueue( + host, + queue, + pollIndex, + chunkSize, + onWatchFileStat + ); - function onWatchFileStat(watchedFile: WatchedFileWithUnchangedPolls, pollIndex: number, fileChanged: boolean) { - if (fileChanged) { - watchedFile.unchangedPolls = 0; - // Changed files go to changedFilesInLastPoll queue - if (queue !== changedFilesInLastPoll) { - queue[pollIndex] = undefined; - addChangedFileToLowPollingIntervalQueue(watchedFile); - } - } - else if (watchedFile.unchangedPolls !== unchangedPollThresholds[pollingInterval]) { - watchedFile.unchangedPolls++; - } - else if (queue === changedFilesInLastPoll) { - // Restart unchangedPollCount for unchanged file and move to low polling interval queue - watchedFile.unchangedPolls = 1; - queue[pollIndex] = undefined; - addToPollingIntervalQueue(watchedFile, PollingInterval.Low); - } - else if (pollingInterval !== PollingInterval.High) { - watchedFile.unchangedPolls++; + function onWatchFileStat(watchedFile: WatchedFileWithUnchangedPolls, pollIndex: number, fileChanged: boolean) { + if (fileChanged) { + watchedFile.unchangedPolls = 0; + // Changed files go to changedFilesInLastPoll queue + if (queue !== changedFilesInLastPoll) { queue[pollIndex] = undefined; - addToPollingIntervalQueue(watchedFile, pollingInterval === PollingInterval.Low ? PollingInterval.Medium : PollingInterval.High); + addChangedFileToLowPollingIntervalQueue(watchedFile); } } - } - - function pollingIntervalQueue(pollingInterval: PollingInterval) { - switch (pollingInterval) { - case PollingInterval.Low: - return lowPollingIntervalQueue; - case PollingInterval.Medium: - return mediumPollingIntervalQueue; - case PollingInterval.High: - return highPollingIntervalQueue; + else if (watchedFile.unchangedPolls !== unchangedPollThresholds[pollingInterval]) { + watchedFile.unchangedPolls++; } - } - - function addToPollingIntervalQueue(file: WatchedFileWithUnchangedPolls, pollingInterval: PollingInterval) { - pollingIntervalQueue(pollingInterval).push(file); - scheduleNextPollIfNotAlreadyScheduled(pollingInterval); - } - - function addChangedFileToLowPollingIntervalQueue(file: WatchedFileWithUnchangedPolls) { - changedFilesInLastPoll.push(file); - scheduleNextPollIfNotAlreadyScheduled(PollingInterval.Low); - } - - function scheduleNextPollIfNotAlreadyScheduled(pollingInterval: PollingInterval) { - if (!pollingIntervalQueue(pollingInterval).pollScheduled) { - scheduleNextPoll(pollingInterval); + else if (queue === changedFilesInLastPoll) { + // Restart unchangedPollCount for unchanged file and move to low polling interval queue + watchedFile.unchangedPolls = 1; + queue[pollIndex] = undefined; + addToPollingIntervalQueue(watchedFile, PollingInterval.Low); + } + else if (pollingInterval !== PollingInterval.High) { + watchedFile.unchangedPolls++; + queue[pollIndex] = undefined; + addToPollingIntervalQueue(watchedFile, pollingInterval === PollingInterval.Low ? PollingInterval.Medium : PollingInterval.High); } } + } - function scheduleNextPoll(pollingInterval: PollingInterval) { - pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval)); + function pollingIntervalQueue(pollingInterval: PollingInterval) { + switch (pollingInterval) { + case PollingInterval.Low: + return lowPollingIntervalQueue; + case PollingInterval.Medium: + return mediumPollingIntervalQueue; + case PollingInterval.High: + return highPollingIntervalQueue; } } - function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile { - // One file can have multiple watchers - const fileWatcherCallbacks = createMultiMap(); - const dirWatchers = new Map(); - const toCanonicalName = createGetCanonicalFileName(useCaseSensitiveFileNames); - return nonPollingWatchFile; - - function nonPollingWatchFile(fileName: string, callback: FileWatcherCallback, _pollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined): FileWatcher { - const filePath = toCanonicalName(fileName); - fileWatcherCallbacks.add(filePath, callback); - const dirPath = getDirectoryPath(filePath) || "."; - const watcher = dirWatchers.get(dirPath) || - createDirectoryWatcher(getDirectoryPath(fileName) || ".", dirPath, fallbackOptions); - watcher.referenceCount++; - return { - close: () => { - if (watcher.referenceCount === 1) { - watcher.close(); - dirWatchers.delete(dirPath); - } - else { - watcher.referenceCount--; - } - fileWatcherCallbacks.remove(filePath, callback); - } - }; - } + function addToPollingIntervalQueue(file: WatchedFileWithUnchangedPolls, pollingInterval: PollingInterval) { + pollingIntervalQueue(pollingInterval).push(file); + scheduleNextPollIfNotAlreadyScheduled(pollingInterval); + } - function createDirectoryWatcher(dirName: string, dirPath: string, fallbackOptions: WatchOptions | undefined) { - const watcher = fsWatch( - dirName, - FileSystemEntryKind.Directory, - (_eventName: string, relativeFileName, modifiedTime) => { - // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined" - if (!isString(relativeFileName)) return; - const fileName = getNormalizedAbsolutePath(relativeFileName, dirName); - // Some applications save a working file via rename operations - const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName)); - if (callbacks) { - for (const fileCallback of callbacks) { - fileCallback(fileName, FileWatcherEventKind.Changed, modifiedTime); - } - } - }, - /*recursive*/ false, - PollingInterval.Medium, - fallbackOptions - ) as DirectoryWatcher; - watcher.referenceCount = 0; - dirWatchers.set(dirPath, watcher); - return watcher; - } + function addChangedFileToLowPollingIntervalQueue(file: WatchedFileWithUnchangedPolls) { + changedFilesInLastPoll.push(file); + scheduleNextPollIfNotAlreadyScheduled(PollingInterval.Low); } - function createFixedChunkSizePollingWatchFile(host: { - getModifiedTime: NonNullable; - setTimeout: NonNullable; - }): HostWatchFile { - const watchedFiles: (WatchedFileWithIsClosed | undefined)[] = []; - let pollIndex = 0; - let pollScheduled: any; - return watchFile; - - function watchFile(fileName: string, callback: FileWatcherCallback): FileWatcher { - const file: WatchedFileWithIsClosed = { - fileName, - callback, - mtime: getModifiedTime(host, fileName) - }; - watchedFiles.push(file); - scheduleNextPoll(); - return { - close: () => { - file.isClosed = true; - unorderedRemoveItem(watchedFiles, file); - } - }; + function scheduleNextPollIfNotAlreadyScheduled(pollingInterval: PollingInterval) { + if (!pollingIntervalQueue(pollingInterval).pollScheduled) { + scheduleNextPoll(pollingInterval); } + } - function pollQueue() { - pollScheduled = undefined; - pollIndex = pollWatchedFileQueue(host, watchedFiles, pollIndex, pollingChunkSize[PollingInterval.Low]); - scheduleNextPoll(); - } + function scheduleNextPoll(pollingInterval: PollingInterval) { + pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval)); + } +} - function scheduleNextPoll() { - if (!watchedFiles.length || pollScheduled) return; - pollScheduled = host.setTimeout(pollQueue, PollingInterval.High); - } +function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile { + // One file can have multiple watchers + const fileWatcherCallbacks = createMultiMap(); + const dirWatchers = new Map(); + const toCanonicalName = createGetCanonicalFileName(useCaseSensitiveFileNames); + return nonPollingWatchFile; + + function nonPollingWatchFile(fileName: string, callback: FileWatcherCallback, _pollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined): FileWatcher { + const filePath = toCanonicalName(fileName); + fileWatcherCallbacks.add(filePath, callback); + const dirPath = getDirectoryPath(filePath) || "."; + const watcher = dirWatchers.get(dirPath) || + createDirectoryWatcher(getDirectoryPath(fileName) || ".", dirPath, fallbackOptions); + watcher.referenceCount++; + return { + close: () => { + if (watcher.referenceCount === 1) { + watcher.close(); + dirWatchers.delete(dirPath); + } + else { + watcher.referenceCount--; + } + fileWatcherCallbacks.remove(filePath, callback); + } + }; } - interface SingleFileWatcher{ - watcher: FileWatcher; - callbacks: T[]; + function createDirectoryWatcher(dirName: string, dirPath: string, fallbackOptions: WatchOptions | undefined) { + const watcher = fsWatch( + dirName, + FileSystemEntryKind.Directory, + (_eventName: string, relativeFileName, modifiedTime) => { + // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined" + if (!isString(relativeFileName)) return; + const fileName = getNormalizedAbsolutePath(relativeFileName, dirName); + // Some applications save a working file via rename operations + const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName)); + if (callbacks) { + for (const fileCallback of callbacks) { + fileCallback(fileName, FileWatcherEventKind.Changed, modifiedTime); + } + } + }, + /*recursive*/ false, + PollingInterval.Medium, + fallbackOptions + ) as DirectoryWatcher; + watcher.referenceCount = 0; + dirWatchers.set(dirPath, watcher); + return watcher; } - function createSingleWatcherPerName( - cache: Map>, - useCaseSensitiveFileNames: boolean, - name: string, - callback: T, - createWatcher: (callback: T) => FileWatcher, - ): FileWatcher { - const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - const path = toCanonicalFileName(name); - const existing = cache.get(path); - if (existing) { - existing.callbacks.push(callback); - } - else { - cache.set(path, { - watcher: createWatcher(( - // Cant infer types correctly so lets satisfy checker - (param1: any, param2: never, param3: any) => cache.get(path)?.callbacks.slice().forEach(cb => cb(param1, param2, param3)) - ) as T), - callbacks: [callback] - }); - } +} +function createFixedChunkSizePollingWatchFile(host: { + getModifiedTime: NonNullable; + setTimeout: NonNullable; +}): HostWatchFile { + const watchedFiles: (WatchedFileWithIsClosed | undefined)[] = []; + let pollIndex = 0; + let pollScheduled: any; + return watchFile; + + function watchFile(fileName: string, callback: FileWatcherCallback): FileWatcher { + const file: WatchedFileWithIsClosed = { + fileName, + callback, + mtime: getModifiedTime(host, fileName) + }; + watchedFiles.push(file); + scheduleNextPoll(); return { close: () => { - const watcher = cache.get(path); - // Watcher is not expected to be undefined, but if it is normally its because - // exception was thrown somewhere else and watch state is not what it should be - if (!watcher) return; - if (!orderedRemoveItem(watcher.callbacks, callback) || watcher.callbacks.length) return; - cache.delete(path); - closeFileWatcherOf(watcher); + file.isClosed = true; + unorderedRemoveItem(watchedFiles, file); } }; } - /** - * Returns true if file status changed - */ - function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean { - const oldTime = watchedFile.mtime.getTime(); - const newTime = modifiedTime.getTime(); - if (oldTime !== newTime) { - watchedFile.mtime = modifiedTime; - // Pass modified times so tsc --build can use it - watchedFile.callback(watchedFile.fileName, getFileWatcherEventKind(oldTime, newTime), modifiedTime); - return true; - } - - return false; + function pollQueue() { + pollScheduled = undefined; + pollIndex = pollWatchedFileQueue(host, watchedFiles, pollIndex, pollingChunkSize[PollingInterval.Low]); + scheduleNextPoll(); } - /*@internal*/ - export function getFileWatcherEventKind(oldTime: number, newTime: number) { - return oldTime === 0 - ? FileWatcherEventKind.Created - : newTime === 0 - ? FileWatcherEventKind.Deleted - : FileWatcherEventKind.Changed; + function scheduleNextPoll() { + if (!watchedFiles.length || pollScheduled) return; + pollScheduled = host.setTimeout(pollQueue, PollingInterval.High); } +} - /*@internal*/ - export const ignoredPaths = ["/node_modules/.", "/.git", "/.#"]; +interface SingleFileWatcher{ + watcher: FileWatcher; + callbacks: T[]; +} +function createSingleWatcherPerName( + cache: Map>, + useCaseSensitiveFileNames: boolean, + name: string, + callback: T, + createWatcher: (callback: T) => FileWatcher, +): FileWatcher { + const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + const path = toCanonicalFileName(name); + const existing = cache.get(path); + if (existing) { + existing.callbacks.push(callback); + } + else { + cache.set(path, { + watcher: createWatcher(( + // Cant infer types correctly so lets satisfy checker + (param1: any, param2: never, param3: any) => cache.get(path)?.callbacks.slice().forEach(cb => cb(param1, param2, param3)) + ) as T), + callbacks: [callback] + }); + } - let curSysLog: (s: string) => void = noop; // eslint-disable-line prefer-const + return { + close: () => { + const watcher = cache.get(path); + // Watcher is not expected to be undefined, but if it is normally its because + // exception was thrown somewhere else and watch state is not what it should be + if (!watcher) return; + if (!orderedRemoveItem(watcher.callbacks, callback) || watcher.callbacks.length) return; + cache.delete(path); + closeFileWatcherOf(watcher); + } + }; +} - /*@internal*/ - export function sysLog(s: string) { - return curSysLog(s); +/** + * Returns true if file status changed + */ +function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean { + const oldTime = watchedFile.mtime.getTime(); + const newTime = modifiedTime.getTime(); + if (oldTime !== newTime) { + watchedFile.mtime = modifiedTime; + // Pass modified times so tsc --build can use it + watchedFile.callback(watchedFile.fileName, getFileWatcherEventKind(oldTime, newTime), modifiedTime); + return true; } - /*@internal*/ - export function setSysLog(logger: typeof sysLog) { - curSysLog = logger; - } + return false; +} - interface RecursiveDirectoryWatcherHost { - watchDirectory: HostWatchDirectory; - useCaseSensitiveFileNames: boolean; - getCurrentDirectory: System["getCurrentDirectory"]; - getAccessibleSortedChildDirectories(path: string): readonly string[]; - fileSystemEntryExists: FileSystemEntryExists; - realpath(s: string): string; - setTimeout: NonNullable; - clearTimeout: NonNullable; - } +/** @internal */ +export function getFileWatcherEventKind(oldTime: number, newTime: number) { + return oldTime === 0 + ? FileWatcherEventKind.Created + : newTime === 0 + ? FileWatcherEventKind.Deleted + : FileWatcherEventKind.Changed; +} - /** - * Watch the directory recursively using host provided method to watch child directories - * that means if this is recursive watcher, watch the children directories as well - * (eg on OS that dont support recursive watch using fs.watch use fs.watchFile) - */ - function createDirectoryWatcherSupportingRecursive({ - watchDirectory, - useCaseSensitiveFileNames, - getCurrentDirectory, - getAccessibleSortedChildDirectories, - fileSystemEntryExists, - realpath, - setTimeout, - clearTimeout - }: RecursiveDirectoryWatcherHost): HostWatchDirectory { - interface ChildDirectoryWatcher extends FileWatcher { - dirName: string; - } - type ChildWatches = readonly ChildDirectoryWatcher[]; - interface HostDirectoryWatcher { - watcher: FileWatcher; - childWatches: ChildWatches; - refCount: number; - } - - const cache = new Map(); - const callbackCache = createMultiMap(); - const cacheToUpdateChildWatches = new Map(); - let timerToUpdateChildWatches: any; - - const filePathComparer = getStringComparer(!useCaseSensitiveFileNames); - const toCanonicalFilePath = createGetCanonicalFileName(useCaseSensitiveFileNames); - - return (dirName, callback, recursive, options) => recursive ? - createDirectoryWatcher(dirName, options, callback) : - watchDirectory(dirName, callback, recursive, options); +/** @internal */ +export const ignoredPaths = ["/node_modules/.", "/.git", "/.#"]; - /** - * Create the directory watcher for the dirPath. - */ - function createDirectoryWatcher(dirName: string, options: WatchOptions | undefined, callback?: DirectoryWatcherCallback): ChildDirectoryWatcher { - const dirPath = toCanonicalFilePath(dirName) as Path; - let directoryWatcher = cache.get(dirPath); - if (directoryWatcher) { - directoryWatcher.refCount++; - } - else { - directoryWatcher = { - watcher: watchDirectory(dirName, fileName => { - if (isIgnoredPath(fileName, options)) return; +let curSysLog: (s: string) => void = noop; // eslint-disable-line prefer-const - if (options?.synchronousWatchDirectory) { - // Call the actual callback - invokeCallbacks(dirPath, fileName); +/** @internal */ +export function sysLog(s: string) { + return curSysLog(s); +} - // Iterate through existing children and update the watches if needed - updateChildWatches(dirName, dirPath, options); - } - else { - nonSyncUpdateChildWatches(dirName, dirPath, fileName, options); - } - }, /*recursive*/ false, options), - refCount: 1, - childWatches: emptyArray - }; - cache.set(dirPath, directoryWatcher); - updateChildWatches(dirName, dirPath, options); - } +/** @internal */ +export function setSysLog(logger: typeof sysLog) { + curSysLog = logger; +} - const callbackToAdd = callback && { dirName, callback }; - if (callbackToAdd) { - callbackCache.add(dirPath, callbackToAdd); - } +interface RecursiveDirectoryWatcherHost { + watchDirectory: HostWatchDirectory; + useCaseSensitiveFileNames: boolean; + getCurrentDirectory: System["getCurrentDirectory"]; + getAccessibleSortedChildDirectories(path: string): readonly string[]; + fileSystemEntryExists: FileSystemEntryExists; + realpath(s: string): string; + setTimeout: NonNullable; + clearTimeout: NonNullable; +} - return { - dirName, - close: () => { - const directoryWatcher = Debug.checkDefined(cache.get(dirPath)); - if (callbackToAdd) callbackCache.remove(dirPath, callbackToAdd); - directoryWatcher.refCount--; +/** + * Watch the directory recursively using host provided method to watch child directories + * that means if this is recursive watcher, watch the children directories as well + * (eg on OS that dont support recursive watch using fs.watch use fs.watchFile) + */ +function createDirectoryWatcherSupportingRecursive({ + watchDirectory, + useCaseSensitiveFileNames, + getCurrentDirectory, + getAccessibleSortedChildDirectories, + fileSystemEntryExists, + realpath, + setTimeout, + clearTimeout +}: RecursiveDirectoryWatcherHost): HostWatchDirectory { + interface ChildDirectoryWatcher extends FileWatcher { + dirName: string; + } + type ChildWatches = readonly ChildDirectoryWatcher[]; + interface HostDirectoryWatcher { + watcher: FileWatcher; + childWatches: ChildWatches; + refCount: number; + } - if (directoryWatcher.refCount) return; + const cache = new Map(); + const callbackCache = createMultiMap(); + const cacheToUpdateChildWatches = new Map(); + let timerToUpdateChildWatches: any; - cache.delete(dirPath); - closeFileWatcherOf(directoryWatcher); - directoryWatcher.childWatches.forEach(closeFileWatcher); - } - }; + const filePathComparer = getStringComparer(!useCaseSensitiveFileNames); + const toCanonicalFilePath = createGetCanonicalFileName(useCaseSensitiveFileNames); + + return (dirName, callback, recursive, options) => recursive ? + createDirectoryWatcher(dirName, options, callback) : + watchDirectory(dirName, callback, recursive, options); + + /** + * Create the directory watcher for the dirPath. + */ + function createDirectoryWatcher(dirName: string, options: WatchOptions | undefined, callback?: DirectoryWatcherCallback): ChildDirectoryWatcher { + const dirPath = toCanonicalFilePath(dirName) as Path; + let directoryWatcher = cache.get(dirPath); + if (directoryWatcher) { + directoryWatcher.refCount++; } + else { + directoryWatcher = { + watcher: watchDirectory(dirName, fileName => { + if (isIgnoredPath(fileName, options)) return; - type InvokeMap = ESMap; - function invokeCallbacks(dirPath: Path, fileName: string): void; - function invokeCallbacks(dirPath: Path, invokeMap: InvokeMap, fileNames: string[] | undefined): void; - function invokeCallbacks(dirPath: Path, fileNameOrInvokeMap: string | InvokeMap, fileNames?: string[]) { - let fileName: string | undefined; - let invokeMap: InvokeMap | undefined; - if (isString(fileNameOrInvokeMap)) { - fileName = fileNameOrInvokeMap; - } - else { - invokeMap = fileNameOrInvokeMap; - } - // Call the actual callback - callbackCache.forEach((callbacks, rootDirName) => { - if (invokeMap && invokeMap.get(rootDirName) === true) return; - if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) { - if (invokeMap) { - if (fileNames) { - const existing = invokeMap.get(rootDirName); - if (existing) { - (existing as string[]).push(...fileNames); - } - else { - invokeMap.set(rootDirName, fileNames.slice()); - } - } - else { - invokeMap.set(rootDirName, true); - } + if (options?.synchronousWatchDirectory) { + // Call the actual callback + invokeCallbacks(dirPath, fileName); + + // Iterate through existing children and update the watches if needed + updateChildWatches(dirName, dirPath, options); } else { - callbacks.forEach(({ callback }) => callback(fileName!)); + nonSyncUpdateChildWatches(dirName, dirPath, fileName, options); } - } - }); + }, /*recursive*/ false, options), + refCount: 1, + childWatches: emptyArray + }; + cache.set(dirPath, directoryWatcher); + updateChildWatches(dirName, dirPath, options); } - function nonSyncUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { - // Iterate through existing children and update the watches if needed - const parentWatcher = cache.get(dirPath); - if (parentWatcher && fileSystemEntryExists(dirName, FileSystemEntryKind.Directory)) { - // Schedule the update and postpone invoke for callbacks - scheduleUpdateChildWatches(dirName, dirPath, fileName, options); - return; - } - - // Call the actual callbacks and remove child watches - invokeCallbacks(dirPath, fileName); - removeChildWatches(parentWatcher); + const callbackToAdd = callback && { dirName, callback }; + if (callbackToAdd) { + callbackCache.add(dirPath, callbackToAdd); } - function scheduleUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { - const existing = cacheToUpdateChildWatches.get(dirPath); - if (existing) { - existing.fileNames.push(fileName); - } - else { - cacheToUpdateChildWatches.set(dirPath, { dirName, options, fileNames: [fileName] }); - } - if (timerToUpdateChildWatches) { - clearTimeout(timerToUpdateChildWatches); - timerToUpdateChildWatches = undefined; - } - timerToUpdateChildWatches = setTimeout(onTimerToUpdateChildWatches, 1000); - } + return { + dirName, + close: () => { + const directoryWatcher = Debug.checkDefined(cache.get(dirPath)); + if (callbackToAdd) callbackCache.remove(dirPath, callbackToAdd); + directoryWatcher.refCount--; - function onTimerToUpdateChildWatches() { - timerToUpdateChildWatches = undefined; - sysLog(`sysLog:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size}`); - const start = timestamp(); - const invokeMap = new Map(); - - while (!timerToUpdateChildWatches && cacheToUpdateChildWatches.size) { - const result = cacheToUpdateChildWatches.entries().next(); - Debug.assert(!result.done); - const { value: [dirPath, { dirName, options, fileNames }] } = result; - cacheToUpdateChildWatches.delete(dirPath); - // Because the child refresh is fresh, we would need to invalidate whole root directory being watched - // to ensure that all the changes are reflected at this time - const hasChanges = updateChildWatches(dirName, dirPath, options); - invokeCallbacks(dirPath, invokeMap, hasChanges ? undefined : fileNames); + if (directoryWatcher.refCount) return; + + cache.delete(dirPath); + closeFileWatcherOf(directoryWatcher); + directoryWatcher.childWatches.forEach(closeFileWatcher); } + }; + } - sysLog(`sysLog:: invokingWatchers:: Elapsed:: ${timestamp() - start}ms:: ${cacheToUpdateChildWatches.size}`); - callbackCache.forEach((callbacks, rootDirName) => { - const existing = invokeMap.get(rootDirName); - if (existing) { - callbacks.forEach(({ callback, dirName }) => { - if (isArray(existing)) { - existing.forEach(callback); + type InvokeMap = ESMap; + function invokeCallbacks(dirPath: Path, fileName: string): void; + function invokeCallbacks(dirPath: Path, invokeMap: InvokeMap, fileNames: string[] | undefined): void; + function invokeCallbacks(dirPath: Path, fileNameOrInvokeMap: string | InvokeMap, fileNames?: string[]) { + let fileName: string | undefined; + let invokeMap: InvokeMap | undefined; + if (isString(fileNameOrInvokeMap)) { + fileName = fileNameOrInvokeMap; + } + else { + invokeMap = fileNameOrInvokeMap; + } + // Call the actual callback + callbackCache.forEach((callbacks, rootDirName) => { + if (invokeMap && invokeMap.get(rootDirName) === true) return; + if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) { + if (invokeMap) { + if (fileNames) { + const existing = invokeMap.get(rootDirName); + if (existing) { + (existing as string[]).push(...fileNames); } else { - callback(dirName); + invokeMap.set(rootDirName, fileNames.slice()); } - }); + } + else { + invokeMap.set(rootDirName, true); + } + } + else { + callbacks.forEach(({ callback }) => callback(fileName!)); } - }); - - const elapsed = timestamp() - start; - sysLog(`sysLog:: Elapsed:: ${elapsed}ms:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size} ${timerToUpdateChildWatches}`); - } - - function removeChildWatches(parentWatcher: HostDirectoryWatcher | undefined) { - if (!parentWatcher) return; - const existingChildWatches = parentWatcher.childWatches; - parentWatcher.childWatches = emptyArray; - for (const childWatcher of existingChildWatches) { - childWatcher.close(); - removeChildWatches(cache.get(toCanonicalFilePath(childWatcher.dirName))); } + }); + } + + function nonSyncUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { + // Iterate through existing children and update the watches if needed + const parentWatcher = cache.get(dirPath); + if (parentWatcher && fileSystemEntryExists(dirName, FileSystemEntryKind.Directory)) { + // Schedule the update and postpone invoke for callbacks + scheduleUpdateChildWatches(dirName, dirPath, fileName, options); + return; } - function updateChildWatches(parentDir: string, parentDirPath: Path, options: WatchOptions | undefined) { - // Iterate through existing children and update the watches if needed - const parentWatcher = cache.get(parentDirPath); - if (!parentWatcher) return false; - let newChildWatches: ChildDirectoryWatcher[] | undefined; - const hasChanges = enumerateInsertsAndDeletes( - fileSystemEntryExists(parentDir, FileSystemEntryKind.Directory) ? mapDefined(getAccessibleSortedChildDirectories(parentDir), child => { - const childFullName = getNormalizedAbsolutePath(child, parentDir); - // Filter our the symbolic link directories since those arent included in recursive watch - // which is same behaviour when recursive: true is passed to fs.watch - return !isIgnoredPath(childFullName, options) && filePathComparer(childFullName, normalizePath(realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; - }) : emptyArray, - parentWatcher.childWatches, - (child, childWatcher) => filePathComparer(child, childWatcher.dirName), - createAndAddChildDirectoryWatcher, - closeFileWatcher, - addChildDirectoryWatcher - ); - parentWatcher.childWatches = newChildWatches || emptyArray; - return hasChanges; - - /** - * Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list - */ - function createAndAddChildDirectoryWatcher(childName: string) { - const result = createDirectoryWatcher(childName, options); - addChildDirectoryWatcher(result); - } + // Call the actual callbacks and remove child watches + invokeCallbacks(dirPath, fileName); + removeChildWatches(parentWatcher); + } - /** - * Add child directory watcher to the new ChildDirectoryWatcher list - */ - function addChildDirectoryWatcher(childWatcher: ChildDirectoryWatcher) { - (newChildWatches || (newChildWatches = [])).push(childWatcher); - } + function scheduleUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { + const existing = cacheToUpdateChildWatches.get(dirPath); + if (existing) { + existing.fileNames.push(fileName); } - - function isIgnoredPath(path: string, options: WatchOptions | undefined) { - return some(ignoredPaths, searchPath => isInPath(path, searchPath)) || - isIgnoredByWatchOptions(path, options, useCaseSensitiveFileNames, getCurrentDirectory); + else { + cacheToUpdateChildWatches.set(dirPath, { dirName, options, fileNames: [fileName] }); } - - function isInPath(path: string, searchPath: string) { - if (stringContains(path, searchPath)) return true; - if (useCaseSensitiveFileNames) return false; - return stringContains(toCanonicalFilePath(path), searchPath); + if (timerToUpdateChildWatches) { + clearTimeout(timerToUpdateChildWatches); + timerToUpdateChildWatches = undefined; } + timerToUpdateChildWatches = setTimeout(onTimerToUpdateChildWatches, 1000); } - /*@internal*/ - export type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined, modifiedTime?: Date) => void; - /*@internal*/ - export type FsWatch = (fileOrDirectory: string, entryKind: FileSystemEntryKind, callback: FsWatchCallback, recursive: boolean, fallbackPollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined) => FileWatcher; - /*@internal*/ - export interface FsWatchWorkerWatcher extends FileWatcher { - on(eventName: string, listener: () => void): void; - } - /*@internal*/ - export type FsWatchWorker = (fileOrDirectory: string, recursive: boolean, callback: FsWatchCallback) => FsWatchWorkerWatcher; - /*@internal*/ - export const enum FileSystemEntryKind { - File, - Directory, - } + function onTimerToUpdateChildWatches() { + timerToUpdateChildWatches = undefined; + sysLog(`sysLog:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size}`); + const start = timestamp(); + const invokeMap = new Map(); + + while (!timerToUpdateChildWatches && cacheToUpdateChildWatches.size) { + const result = cacheToUpdateChildWatches.entries().next(); + Debug.assert(!result.done); + const { value: [dirPath, { dirName, options, fileNames }] } = result; + cacheToUpdateChildWatches.delete(dirPath); + // Because the child refresh is fresh, we would need to invalidate whole root directory being watched + // to ensure that all the changes are reflected at this time + const hasChanges = updateChildWatches(dirName, dirPath, options); + invokeCallbacks(dirPath, invokeMap, hasChanges ? undefined : fileNames); + } - function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback { - return (_fileName, eventKind, modifiedTime) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", "", modifiedTime); + sysLog(`sysLog:: invokingWatchers:: Elapsed:: ${timestamp() - start}ms:: ${cacheToUpdateChildWatches.size}`); + callbackCache.forEach((callbacks, rootDirName) => { + const existing = invokeMap.get(rootDirName); + if (existing) { + callbacks.forEach(({ callback, dirName }) => { + if (isArray(existing)) { + existing.forEach(callback); + } + else { + callback(dirName); + } + }); + } + }); + + const elapsed = timestamp() - start; + sysLog(`sysLog:: Elapsed:: ${elapsed}ms:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size} ${timerToUpdateChildWatches}`); } - function createFsWatchCallbackForFileWatcherCallback( - fileName: string, - callback: FileWatcherCallback, - getModifiedTime: NonNullable - ): FsWatchCallback { - return (eventName, _relativeFileName, modifiedTime) => { - if (eventName === "rename") { - // Check time stamps rather than file system entry checks - modifiedTime ||= getModifiedTime(fileName) || missingFileModifiedTime; - callback(fileName, modifiedTime !== missingFileModifiedTime ? FileWatcherEventKind.Created : FileWatcherEventKind.Deleted, modifiedTime); - } - else { - // Change - callback(fileName, FileWatcherEventKind.Changed, modifiedTime); - } - }; + function removeChildWatches(parentWatcher: HostDirectoryWatcher | undefined) { + if (!parentWatcher) return; + const existingChildWatches = parentWatcher.childWatches; + parentWatcher.childWatches = emptyArray; + for (const childWatcher of existingChildWatches) { + childWatcher.close(); + removeChildWatches(cache.get(toCanonicalFilePath(childWatcher.dirName))); + } } - function isIgnoredByWatchOptions( - pathToCheck: string, - options: WatchOptions | undefined, - useCaseSensitiveFileNames: boolean, - getCurrentDirectory: System["getCurrentDirectory"], - ) { - return (options?.excludeDirectories || options?.excludeFiles) && ( - matchesExclude(pathToCheck, options?.excludeFiles, useCaseSensitiveFileNames, getCurrentDirectory()) || - matchesExclude(pathToCheck, options?.excludeDirectories, useCaseSensitiveFileNames, getCurrentDirectory()) + function updateChildWatches(parentDir: string, parentDirPath: Path, options: WatchOptions | undefined) { + // Iterate through existing children and update the watches if needed + const parentWatcher = cache.get(parentDirPath); + if (!parentWatcher) return false; + let newChildWatches: ChildDirectoryWatcher[] | undefined; + const hasChanges = enumerateInsertsAndDeletes( + fileSystemEntryExists(parentDir, FileSystemEntryKind.Directory) ? mapDefined(getAccessibleSortedChildDirectories(parentDir), child => { + const childFullName = getNormalizedAbsolutePath(child, parentDir); + // Filter our the symbolic link directories since those arent included in recursive watch + // which is same behaviour when recursive: true is passed to fs.watch + return !isIgnoredPath(childFullName, options) && filePathComparer(childFullName, normalizePath(realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; + }) : emptyArray, + parentWatcher.childWatches, + (child, childWatcher) => filePathComparer(child, childWatcher.dirName), + createAndAddChildDirectoryWatcher, + closeFileWatcher, + addChildDirectoryWatcher ); + parentWatcher.childWatches = newChildWatches || emptyArray; + return hasChanges; + + /** + * Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list + */ + function createAndAddChildDirectoryWatcher(childName: string) { + const result = createDirectoryWatcher(childName, options); + addChildDirectoryWatcher(result); + } + + /** + * Add child directory watcher to the new ChildDirectoryWatcher list + */ + function addChildDirectoryWatcher(childWatcher: ChildDirectoryWatcher) { + (newChildWatches || (newChildWatches = [])).push(childWatcher); + } } - function createFsWatchCallbackForDirectoryWatcherCallback( - directoryName: string, - callback: DirectoryWatcherCallback, - options: WatchOptions | undefined, - useCaseSensitiveFileNames: boolean, - getCurrentDirectory: System["getCurrentDirectory"], - ): FsWatchCallback { - return (eventName, relativeFileName) => { - // In watchDirectory we only care about adding and removing files (when event name is - // "rename"); changes made within files are handled by corresponding fileWatchers (when - // event name is "change") - if (eventName === "rename") { - // When deleting a file, the passed baseFileName is null - const fileName = !relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName)); - if (!relativeFileName || !isIgnoredByWatchOptions(fileName, options, useCaseSensitiveFileNames, getCurrentDirectory)) { - callback(fileName); - } - } - }; + function isIgnoredPath(path: string, options: WatchOptions | undefined) { + return some(ignoredPaths, searchPath => isInPath(path, searchPath)) || + isIgnoredByWatchOptions(path, options, useCaseSensitiveFileNames, getCurrentDirectory); } - /*@internal*/ - export type FileSystemEntryExists = (fileorDirectrory: string, entryKind: FileSystemEntryKind) => boolean; - - /*@internal*/ - export interface CreateSystemWatchFunctions { - // Polling watch file - pollingWatchFileWorker: HostWatchFile; - // For dynamic polling watch file - getModifiedTime: NonNullable; - setTimeout: NonNullable; - clearTimeout: NonNullable; - // For fs events : - fsWatchWorker: FsWatchWorker; - fileSystemEntryExists: FileSystemEntryExists; - useCaseSensitiveFileNames: boolean; - getCurrentDirectory: System["getCurrentDirectory"]; - fsSupportsRecursiveFsWatch: boolean; - getAccessibleSortedChildDirectories(path: string): readonly string[]; - realpath(s: string): string; - // For backward compatibility environment variables - tscWatchFile: string | undefined; - useNonPollingWatchers?: boolean; - tscWatchDirectory: string | undefined; - inodeWatching: boolean; - sysLog: (s: string) => void; + function isInPath(path: string, searchPath: string) { + if (stringContains(path, searchPath)) return true; + if (useCaseSensitiveFileNames) return false; + return stringContains(toCanonicalFilePath(path), searchPath); } +} - /*@internal*/ - export function createSystemWatchFunctions({ - pollingWatchFileWorker, - getModifiedTime, - setTimeout, - clearTimeout, - fsWatchWorker, - fileSystemEntryExists, - useCaseSensitiveFileNames, - getCurrentDirectory, - fsSupportsRecursiveFsWatch, - getAccessibleSortedChildDirectories, - realpath, - tscWatchFile, - useNonPollingWatchers, - tscWatchDirectory, - inodeWatching, - sysLog, - }: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } { - const pollingWatches = new Map>(); - const fsWatches = new Map>(); - const fsWatchesRecursive = new Map>(); - let dynamicPollingWatchFile: HostWatchFile | undefined; - let fixedChunkSizePollingWatchFile: HostWatchFile | undefined; - let nonPollingWatchFile: HostWatchFile | undefined; - let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined; - let hitSystemWatcherLimit = false; - return { - watchFile, - watchDirectory - }; +/** @internal */ +export type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined, modifiedTime?: Date) => void; +/** @internal */ +export type FsWatch = (fileOrDirectory: string, entryKind: FileSystemEntryKind, callback: FsWatchCallback, recursive: boolean, fallbackPollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined) => FileWatcher; +/** @internal */ +export interface FsWatchWorkerWatcher extends FileWatcher { + on(eventName: string, listener: () => void): void; +} +/** @internal */ +export type FsWatchWorker = (fileOrDirectory: string, recursive: boolean, callback: FsWatchCallback) => FsWatchWorkerWatcher; +/** @internal */ +export const enum FileSystemEntryKind { + File, + Directory, +} - function watchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined): FileWatcher { - options = updateOptionsForWatchFile(options, useNonPollingWatchers); - const watchFileKind = Debug.checkDefined(options.watchFile); - switch (watchFileKind) { - case WatchFileKind.FixedPollingInterval: - return pollingWatchFile(fileName, callback, PollingInterval.Low, /*options*/ undefined); - case WatchFileKind.PriorityPollingInterval: - return pollingWatchFile(fileName, callback, pollingInterval, /*options*/ undefined); - case WatchFileKind.DynamicPriorityPolling: - return ensureDynamicPollingWatchFile()(fileName, callback, pollingInterval, /*options*/ undefined); - case WatchFileKind.FixedChunkSizePolling: - return ensureFixedChunkSizePollingWatchFile()(fileName, callback, /* pollingInterval */ undefined!, /*options*/ undefined); - case WatchFileKind.UseFsEvents: - return fsWatch( - fileName, - FileSystemEntryKind.File, - createFsWatchCallbackForFileWatcherCallback(fileName, callback, getModifiedTime), - /*recursive*/ false, - pollingInterval, - getFallbackOptions(options) - ); - case WatchFileKind.UseFsEventsOnParentDirectory: - if (!nonPollingWatchFile) { - nonPollingWatchFile = createUseFsEventsOnParentDirectoryWatchFile(fsWatch, useCaseSensitiveFileNames); - } - return nonPollingWatchFile(fileName, callback, pollingInterval, getFallbackOptions(options)); - default: - Debug.assertNever(watchFileKind); - } +function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback { + return (_fileName, eventKind, modifiedTime) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", "", modifiedTime); +} + +function createFsWatchCallbackForFileWatcherCallback( + fileName: string, + callback: FileWatcherCallback, + getModifiedTime: NonNullable +): FsWatchCallback { + return (eventName, _relativeFileName, modifiedTime) => { + if (eventName === "rename") { + // Check time stamps rather than file system entry checks + modifiedTime ||= getModifiedTime(fileName) || missingFileModifiedTime; + callback(fileName, modifiedTime !== missingFileModifiedTime ? FileWatcherEventKind.Created : FileWatcherEventKind.Deleted, modifiedTime); + } + else { + // Change + callback(fileName, FileWatcherEventKind.Changed, modifiedTime); } + }; +} - function ensureDynamicPollingWatchFile() { - return dynamicPollingWatchFile ||= createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout }); +function isIgnoredByWatchOptions( + pathToCheck: string, + options: WatchOptions | undefined, + useCaseSensitiveFileNames: boolean, + getCurrentDirectory: System["getCurrentDirectory"], +) { + return (options?.excludeDirectories || options?.excludeFiles) && ( + matchesExclude(pathToCheck, options?.excludeFiles, useCaseSensitiveFileNames, getCurrentDirectory()) || + matchesExclude(pathToCheck, options?.excludeDirectories, useCaseSensitiveFileNames, getCurrentDirectory()) + ); +} + +function createFsWatchCallbackForDirectoryWatcherCallback( + directoryName: string, + callback: DirectoryWatcherCallback, + options: WatchOptions | undefined, + useCaseSensitiveFileNames: boolean, + getCurrentDirectory: System["getCurrentDirectory"], +): FsWatchCallback { + return (eventName, relativeFileName) => { + // In watchDirectory we only care about adding and removing files (when event name is + // "rename"); changes made within files are handled by corresponding fileWatchers (when + // event name is "change") + if (eventName === "rename") { + // When deleting a file, the passed baseFileName is null + const fileName = !relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName)); + if (!relativeFileName || !isIgnoredByWatchOptions(fileName, options, useCaseSensitiveFileNames, getCurrentDirectory)) { + callback(fileName); + } } + }; +} - function ensureFixedChunkSizePollingWatchFile() { - return fixedChunkSizePollingWatchFile ||= createFixedChunkSizePollingWatchFile({ getModifiedTime, setTimeout }); +/** @internal */ +export type FileSystemEntryExists = (fileorDirectrory: string, entryKind: FileSystemEntryKind) => boolean; + +/** @internal */ +export interface CreateSystemWatchFunctions { + // Polling watch file + pollingWatchFileWorker: HostWatchFile; + // For dynamic polling watch file + getModifiedTime: NonNullable; + setTimeout: NonNullable; + clearTimeout: NonNullable; + // For fs events : + fsWatchWorker: FsWatchWorker; + fileSystemEntryExists: FileSystemEntryExists; + useCaseSensitiveFileNames: boolean; + getCurrentDirectory: System["getCurrentDirectory"]; + fsSupportsRecursiveFsWatch: boolean; + getAccessibleSortedChildDirectories(path: string): readonly string[]; + realpath(s: string): string; + // For backward compatibility environment variables + tscWatchFile: string | undefined; + useNonPollingWatchers?: boolean; + tscWatchDirectory: string | undefined; + inodeWatching: boolean; + sysLog: (s: string) => void; +} + +/** @internal */ +export function createSystemWatchFunctions({ + pollingWatchFileWorker, + getModifiedTime, + setTimeout, + clearTimeout, + fsWatchWorker, + fileSystemEntryExists, + useCaseSensitiveFileNames, + getCurrentDirectory, + fsSupportsRecursiveFsWatch, + getAccessibleSortedChildDirectories, + realpath, + tscWatchFile, + useNonPollingWatchers, + tscWatchDirectory, + inodeWatching, + sysLog, +}: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } { + const pollingWatches = new Map>(); + const fsWatches = new Map>(); + const fsWatchesRecursive = new Map>(); + let dynamicPollingWatchFile: HostWatchFile | undefined; + let fixedChunkSizePollingWatchFile: HostWatchFile | undefined; + let nonPollingWatchFile: HostWatchFile | undefined; + let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined; + let hitSystemWatcherLimit = false; + return { + watchFile, + watchDirectory + }; + + function watchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined): FileWatcher { + options = updateOptionsForWatchFile(options, useNonPollingWatchers); + const watchFileKind = Debug.checkDefined(options.watchFile); + switch (watchFileKind) { + case WatchFileKind.FixedPollingInterval: + return pollingWatchFile(fileName, callback, PollingInterval.Low, /*options*/ undefined); + case WatchFileKind.PriorityPollingInterval: + return pollingWatchFile(fileName, callback, pollingInterval, /*options*/ undefined); + case WatchFileKind.DynamicPriorityPolling: + return ensureDynamicPollingWatchFile()(fileName, callback, pollingInterval, /*options*/ undefined); + case WatchFileKind.FixedChunkSizePolling: + return ensureFixedChunkSizePollingWatchFile()(fileName, callback, /* pollingInterval */ undefined!, /*options*/ undefined); + case WatchFileKind.UseFsEvents: + return fsWatch( + fileName, + FileSystemEntryKind.File, + createFsWatchCallbackForFileWatcherCallback(fileName, callback, getModifiedTime), + /*recursive*/ false, + pollingInterval, + getFallbackOptions(options) + ); + case WatchFileKind.UseFsEventsOnParentDirectory: + if (!nonPollingWatchFile) { + nonPollingWatchFile = createUseFsEventsOnParentDirectoryWatchFile(fsWatch, useCaseSensitiveFileNames); + } + return nonPollingWatchFile(fileName, callback, pollingInterval, getFallbackOptions(options)); + default: + Debug.assertNever(watchFileKind); } + } + + function ensureDynamicPollingWatchFile() { + return dynamicPollingWatchFile ||= createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout }); + } - function updateOptionsForWatchFile(options: WatchOptions | undefined, useNonPollingWatchers?: boolean): WatchOptions { - if (options && options.watchFile !== undefined) return options; - switch (tscWatchFile) { - case "PriorityPollingInterval": - // Use polling interval based on priority when create watch using host.watchFile - return { watchFile: WatchFileKind.PriorityPollingInterval }; - case "DynamicPriorityPolling": - // Use polling interval but change the interval depending on file changes and their default polling interval - return { watchFile: WatchFileKind.DynamicPriorityPolling }; - case "UseFsEvents": + function ensureFixedChunkSizePollingWatchFile() { + return fixedChunkSizePollingWatchFile ||= createFixedChunkSizePollingWatchFile({ getModifiedTime, setTimeout }); + } + + function updateOptionsForWatchFile(options: WatchOptions | undefined, useNonPollingWatchers?: boolean): WatchOptions { + if (options && options.watchFile !== undefined) return options; + switch (tscWatchFile) { + case "PriorityPollingInterval": + // Use polling interval based on priority when create watch using host.watchFile + return { watchFile: WatchFileKind.PriorityPollingInterval }; + case "DynamicPriorityPolling": + // Use polling interval but change the interval depending on file changes and their default polling interval + return { watchFile: WatchFileKind.DynamicPriorityPolling }; + case "UseFsEvents": + // Use notifications from FS to watch with falling back to fs.watchFile + return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.PriorityInterval, options); + case "UseFsEventsWithFallbackDynamicPolling": + // Use notifications from FS to watch with falling back to dynamic watch file + return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.DynamicPriority, options); + case "UseFsEventsOnParentDirectory": + useNonPollingWatchers = true; + // fall through + default: + return useNonPollingWatchers ? // Use notifications from FS to watch with falling back to fs.watchFile - return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.PriorityInterval, options); - case "UseFsEventsWithFallbackDynamicPolling": - // Use notifications from FS to watch with falling back to dynamic watch file - return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.DynamicPriority, options); - case "UseFsEventsOnParentDirectory": - useNonPollingWatchers = true; - // fall through - default: - return useNonPollingWatchers ? - // Use notifications from FS to watch with falling back to fs.watchFile - generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) : - // Default to using fs events - { watchFile: WatchFileKind.UseFsEvents }; - } + generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) : + // Default to using fs events + { watchFile: WatchFileKind.UseFsEvents }; } + } - function generateWatchFileOptions( - watchFile: WatchFileKind, - fallbackPolling: PollingWatchKind, - options: WatchOptions | undefined - ): WatchOptions { - const defaultFallbackPolling = options?.fallbackPolling; - return { - watchFile, - fallbackPolling: defaultFallbackPolling === undefined ? - fallbackPolling : - defaultFallbackPolling - }; + function generateWatchFileOptions( + watchFile: WatchFileKind, + fallbackPolling: PollingWatchKind, + options: WatchOptions | undefined + ): WatchOptions { + const defaultFallbackPolling = options?.fallbackPolling; + return { + watchFile, + fallbackPolling: defaultFallbackPolling === undefined ? + fallbackPolling : + defaultFallbackPolling + }; + } + + function watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { + if (fsSupportsRecursiveFsWatch) { + return fsWatch( + directoryName, + FileSystemEntryKind.Directory, + createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory), + recursive, + PollingInterval.Medium, + getFallbackOptions(options) + ); + } + + if (!hostRecursiveDirectoryWatcher) { + hostRecursiveDirectoryWatcher = createDirectoryWatcherSupportingRecursive({ + useCaseSensitiveFileNames, + getCurrentDirectory, + fileSystemEntryExists, + getAccessibleSortedChildDirectories, + watchDirectory: nonRecursiveWatchDirectory, + realpath, + setTimeout, + clearTimeout + }); } + return hostRecursiveDirectoryWatcher(directoryName, callback, recursive, options); + } - function watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { - if (fsSupportsRecursiveFsWatch) { + function nonRecursiveWatchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { + Debug.assert(!recursive); + const watchDirectoryOptions = updateOptionsForWatchDirectory(options); + const watchDirectoryKind = Debug.checkDefined(watchDirectoryOptions.watchDirectory); + switch (watchDirectoryKind) { + case WatchDirectoryKind.FixedPollingInterval: + return pollingWatchFile( + directoryName, + () => callback(directoryName), + PollingInterval.Medium, + /*options*/ undefined + ); + case WatchDirectoryKind.DynamicPriorityPolling: + return ensureDynamicPollingWatchFile()( + directoryName, + () => callback(directoryName), + PollingInterval.Medium, + /*options*/ undefined + ); + case WatchDirectoryKind.FixedChunkSizePolling: + return ensureFixedChunkSizePollingWatchFile()( + directoryName, + () => callback(directoryName), + /* pollingInterval */ undefined!, + /*options*/ undefined + ); + case WatchDirectoryKind.UseFsEvents: return fsWatch( directoryName, FileSystemEntryKind.Directory, createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory), recursive, PollingInterval.Medium, - getFallbackOptions(options) + getFallbackOptions(watchDirectoryOptions) ); - } - - if (!hostRecursiveDirectoryWatcher) { - hostRecursiveDirectoryWatcher = createDirectoryWatcherSupportingRecursive({ - useCaseSensitiveFileNames, - getCurrentDirectory, - fileSystemEntryExists, - getAccessibleSortedChildDirectories, - watchDirectory: nonRecursiveWatchDirectory, - realpath, - setTimeout, - clearTimeout - }); - } - return hostRecursiveDirectoryWatcher(directoryName, callback, recursive, options); - } - - function nonRecursiveWatchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { - Debug.assert(!recursive); - const watchDirectoryOptions = updateOptionsForWatchDirectory(options); - const watchDirectoryKind = Debug.checkDefined(watchDirectoryOptions.watchDirectory); - switch (watchDirectoryKind) { - case WatchDirectoryKind.FixedPollingInterval: - return pollingWatchFile( - directoryName, - () => callback(directoryName), - PollingInterval.Medium, - /*options*/ undefined - ); - case WatchDirectoryKind.DynamicPriorityPolling: - return ensureDynamicPollingWatchFile()( - directoryName, - () => callback(directoryName), - PollingInterval.Medium, - /*options*/ undefined - ); - case WatchDirectoryKind.FixedChunkSizePolling: - return ensureFixedChunkSizePollingWatchFile()( - directoryName, - () => callback(directoryName), - /* pollingInterval */ undefined!, - /*options*/ undefined - ); - case WatchDirectoryKind.UseFsEvents: - return fsWatch( - directoryName, - FileSystemEntryKind.Directory, - createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory), - recursive, - PollingInterval.Medium, - getFallbackOptions(watchDirectoryOptions) - ); - default: - Debug.assertNever(watchDirectoryKind); - } - } - - function updateOptionsForWatchDirectory(options: WatchOptions | undefined): WatchOptions { - if (options && options.watchDirectory !== undefined) return options; - switch (tscWatchDirectory) { - case "RecursiveDirectoryUsingFsWatchFile": - // Use polling interval based on priority when create watch using host.watchFile - return { watchDirectory: WatchDirectoryKind.FixedPollingInterval }; - case "RecursiveDirectoryUsingDynamicPriorityPolling": - // Use polling interval but change the interval depending on file changes and their default polling interval - return { watchDirectory: WatchDirectoryKind.DynamicPriorityPolling }; - default: - const defaultFallbackPolling = options?.fallbackPolling; - return { - watchDirectory: WatchDirectoryKind.UseFsEvents, - fallbackPolling: defaultFallbackPolling !== undefined ? - defaultFallbackPolling : - undefined - }; - } + default: + Debug.assertNever(watchDirectoryKind); } + } - function pollingWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) { - return createSingleWatcherPerName( - pollingWatches, - useCaseSensitiveFileNames, - fileName, - callback, - cb => pollingWatchFileWorker(fileName, cb, pollingInterval, options), - ); - } - function fsWatch( - fileOrDirectory: string, - entryKind: FileSystemEntryKind, - callback: FsWatchCallback, - recursive: boolean, - fallbackPollingInterval: PollingInterval, - fallbackOptions: WatchOptions | undefined - ): FileWatcher { - return createSingleWatcherPerName( - recursive ? fsWatchesRecursive : fsWatches, - useCaseSensitiveFileNames, - fileOrDirectory, - callback, - cb => fsWatchHandlingExistenceOnHost(fileOrDirectory, entryKind, cb, recursive, fallbackPollingInterval, fallbackOptions), - ); + function updateOptionsForWatchDirectory(options: WatchOptions | undefined): WatchOptions { + if (options && options.watchDirectory !== undefined) return options; + switch (tscWatchDirectory) { + case "RecursiveDirectoryUsingFsWatchFile": + // Use polling interval based on priority when create watch using host.watchFile + return { watchDirectory: WatchDirectoryKind.FixedPollingInterval }; + case "RecursiveDirectoryUsingDynamicPriorityPolling": + // Use polling interval but change the interval depending on file changes and their default polling interval + return { watchDirectory: WatchDirectoryKind.DynamicPriorityPolling }; + default: + const defaultFallbackPolling = options?.fallbackPolling; + return { + watchDirectory: WatchDirectoryKind.UseFsEvents, + fallbackPolling: defaultFallbackPolling !== undefined ? + defaultFallbackPolling : + undefined + }; } + } - function fsWatchHandlingExistenceOnHost( - fileOrDirectory: string, - entryKind: FileSystemEntryKind, - callback: FsWatchCallback, - recursive: boolean, - fallbackPollingInterval: PollingInterval, - fallbackOptions: WatchOptions | undefined - ): FileWatcher { - let lastDirectoryPartWithDirectorySeparator: string | undefined; - let lastDirectoryPart: string | undefined; - if (inodeWatching) { - lastDirectoryPartWithDirectorySeparator = fileOrDirectory.substring(fileOrDirectory.lastIndexOf(directorySeparator)); - lastDirectoryPart = lastDirectoryPartWithDirectorySeparator.slice(directorySeparator.length); - } - /** Watcher for the file system entry depending on whether it is missing or present */ - let watcher: FileWatcher | undefined = !fileSystemEntryExists(fileOrDirectory, entryKind) ? - watchMissingFileSystemEntry() : - watchPresentFileSystemEntry(); - return { - close: () => { - // Close the watcher (either existing file system entry watcher or missing file system entry watcher) - if (watcher) { - watcher.close(); - watcher = undefined; - } - } - }; + function pollingWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) { + return createSingleWatcherPerName( + pollingWatches, + useCaseSensitiveFileNames, + fileName, + callback, + cb => pollingWatchFileWorker(fileName, cb, pollingInterval, options), + ); + } + function fsWatch( + fileOrDirectory: string, + entryKind: FileSystemEntryKind, + callback: FsWatchCallback, + recursive: boolean, + fallbackPollingInterval: PollingInterval, + fallbackOptions: WatchOptions | undefined + ): FileWatcher { + return createSingleWatcherPerName( + recursive ? fsWatchesRecursive : fsWatches, + useCaseSensitiveFileNames, + fileOrDirectory, + callback, + cb => fsWatchHandlingExistenceOnHost(fileOrDirectory, entryKind, cb, recursive, fallbackPollingInterval, fallbackOptions), + ); + } - function updateWatcher(createWatcher: () => FileWatcher) { - // If watcher is not closed, update it + function fsWatchHandlingExistenceOnHost( + fileOrDirectory: string, + entryKind: FileSystemEntryKind, + callback: FsWatchCallback, + recursive: boolean, + fallbackPollingInterval: PollingInterval, + fallbackOptions: WatchOptions | undefined + ): FileWatcher { + let lastDirectoryPartWithDirectorySeparator: string | undefined; + let lastDirectoryPart: string | undefined; + if (inodeWatching) { + lastDirectoryPartWithDirectorySeparator = fileOrDirectory.substring(fileOrDirectory.lastIndexOf(directorySeparator)); + lastDirectoryPart = lastDirectoryPartWithDirectorySeparator.slice(directorySeparator.length); + } + /** Watcher for the file system entry depending on whether it is missing or present */ + let watcher: FileWatcher | undefined = !fileSystemEntryExists(fileOrDirectory, entryKind) ? + watchMissingFileSystemEntry() : + watchPresentFileSystemEntry(); + return { + close: () => { + // Close the watcher (either existing file system entry watcher or missing file system entry watcher) if (watcher) { - sysLog(`sysLog:: ${fileOrDirectory}:: Changing watcher to ${createWatcher === watchPresentFileSystemEntry ? "Present" : "Missing"}FileSystemEntryWatcher`); watcher.close(); - watcher = createWatcher(); + watcher = undefined; } } + }; - /** - * Watch the file or directory that is currently present - * and when the watched file or directory is deleted, switch to missing file system entry watcher - */ - function watchPresentFileSystemEntry(): FileWatcher { - if (hitSystemWatcherLimit) { - sysLog(`sysLog:: ${fileOrDirectory}:: Defaulting to watchFile`); - return watchPresentFileSystemEntryWithFsWatchFile(); - } - try { - const presentWatcher = fsWatchWorker( - fileOrDirectory, - recursive, - inodeWatching ? - callbackChangingToMissingFileSystemEntry : - callback - ); - // Watch the missing file or directory or error - presentWatcher.on("error", () => { - callback("rename", ""); - updateWatcher(watchMissingFileSystemEntry); - }); - return presentWatcher; - } - catch (e) { - // Catch the exception and use polling instead - // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point - // so instead of throwing error, use fs.watchFile - hitSystemWatcherLimit ||= e.code === "ENOSPC"; - sysLog(`sysLog:: ${fileOrDirectory}:: Changing to watchFile`); - return watchPresentFileSystemEntryWithFsWatchFile(); - } + function updateWatcher(createWatcher: () => FileWatcher) { + // If watcher is not closed, update it + if (watcher) { + sysLog(`sysLog:: ${fileOrDirectory}:: Changing watcher to ${createWatcher === watchPresentFileSystemEntry ? "Present" : "Missing"}FileSystemEntryWatcher`); + watcher.close(); + watcher = createWatcher(); } + } - function callbackChangingToMissingFileSystemEntry(event: "rename" | "change", relativeName: string | undefined) { - // In some scenarios, file save operation fires event with fileName.ext~ instead of fileName.ext - // To ensure we see the file going missing and coming back up (file delete and then recreated) - // and watches being updated correctly we are calling back with fileName.ext as well as fileName.ext~ - // The worst is we have fired event that was not needed but we wont miss any changes - // especially in cases where file goes missing and watches wrong inode - let originalRelativeName: string | undefined; - if (relativeName && endsWith(relativeName, "~")) { - originalRelativeName = relativeName; - relativeName = relativeName.slice(0, relativeName.length - 1); - } - // because relativeName is not guaranteed to be correct we need to check on each rename with few combinations - // Eg on ubuntu while watching app/node_modules the relativeName is "node_modules" which is neither relative nor full path - if (event === "rename" && - (!relativeName || - relativeName === lastDirectoryPart || - endsWith(relativeName, lastDirectoryPartWithDirectorySeparator!))) { - const modifiedTime = getModifiedTime(fileOrDirectory) || missingFileModifiedTime; - if (originalRelativeName) callback(event, originalRelativeName, modifiedTime); - callback(event, relativeName, modifiedTime); - if (inodeWatching) { - // If this was rename event, inode has changed means we need to update watcher - updateWatcher(modifiedTime === missingFileModifiedTime ? watchMissingFileSystemEntry : watchPresentFileSystemEntry); - } - else if (modifiedTime === missingFileModifiedTime) { - updateWatcher(watchMissingFileSystemEntry); - } - } - else { - if (originalRelativeName) callback(event, originalRelativeName); - callback(event, relativeName); - } + /** + * Watch the file or directory that is currently present + * and when the watched file or directory is deleted, switch to missing file system entry watcher + */ + function watchPresentFileSystemEntry(): FileWatcher { + if (hitSystemWatcherLimit) { + sysLog(`sysLog:: ${fileOrDirectory}:: Defaulting to watchFile`); + return watchPresentFileSystemEntryWithFsWatchFile(); } - - /** - * Watch the file or directory using fs.watchFile since fs.watch threw exception - * Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point - */ - function watchPresentFileSystemEntryWithFsWatchFile(): FileWatcher { - return watchFile( + try { + const presentWatcher = fsWatchWorker( fileOrDirectory, - createFileWatcherCallback(callback), - fallbackPollingInterval, - fallbackOptions + recursive, + inodeWatching ? + callbackChangingToMissingFileSystemEntry : + callback ); + // Watch the missing file or directory or error + presentWatcher.on("error", () => { + callback("rename", ""); + updateWatcher(watchMissingFileSystemEntry); + }); + return presentWatcher; + } + catch (e) { + // Catch the exception and use polling instead + // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point + // so instead of throwing error, use fs.watchFile + hitSystemWatcherLimit ||= e.code === "ENOSPC"; + sysLog(`sysLog:: ${fileOrDirectory}:: Changing to watchFile`); + return watchPresentFileSystemEntryWithFsWatchFile(); } + } - /** - * Watch the file or directory that is missing - * and switch to existing file or directory when the missing filesystem entry is created - */ - function watchMissingFileSystemEntry(): FileWatcher { - return watchFile( - fileOrDirectory, - (_fileName, eventKind, modifiedTime) => { - if (eventKind === FileWatcherEventKind.Created) { - modifiedTime ||= getModifiedTime(fileOrDirectory) || missingFileModifiedTime; - if (modifiedTime !== missingFileModifiedTime) { - callback("rename", "", modifiedTime); - // Call the callback for current file or directory - // For now it could be callback for the inner directory creation, - // but just return current directory, better than current no-op - updateWatcher(watchPresentFileSystemEntry); - } - } - }, - fallbackPollingInterval, - fallbackOptions - ); + function callbackChangingToMissingFileSystemEntry(event: "rename" | "change", relativeName: string | undefined) { + // In some scenarios, file save operation fires event with fileName.ext~ instead of fileName.ext + // To ensure we see the file going missing and coming back up (file delete and then recreated) + // and watches being updated correctly we are calling back with fileName.ext as well as fileName.ext~ + // The worst is we have fired event that was not needed but we wont miss any changes + // especially in cases where file goes missing and watches wrong inode + let originalRelativeName: string | undefined; + if (relativeName && endsWith(relativeName, "~")) { + originalRelativeName = relativeName; + relativeName = relativeName.slice(0, relativeName.length - 1); + } + // because relativeName is not guaranteed to be correct we need to check on each rename with few combinations + // Eg on ubuntu while watching app/node_modules the relativeName is "node_modules" which is neither relative nor full path + if (event === "rename" && + (!relativeName || + relativeName === lastDirectoryPart || + endsWith(relativeName, lastDirectoryPartWithDirectorySeparator!))) { + const modifiedTime = getModifiedTime(fileOrDirectory) || missingFileModifiedTime; + if (originalRelativeName) callback(event, originalRelativeName, modifiedTime); + callback(event, relativeName, modifiedTime); + if (inodeWatching) { + // If this was rename event, inode has changed means we need to update watcher + updateWatcher(modifiedTime === missingFileModifiedTime ? watchMissingFileSystemEntry : watchPresentFileSystemEntry); + } + else if (modifiedTime === missingFileModifiedTime) { + updateWatcher(watchMissingFileSystemEntry); + } + } + else { + if (originalRelativeName) callback(event, originalRelativeName); + callback(event, relativeName); } } + + /** + * Watch the file or directory using fs.watchFile since fs.watch threw exception + * Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point + */ + function watchPresentFileSystemEntryWithFsWatchFile(): FileWatcher { + return watchFile( + fileOrDirectory, + createFileWatcherCallback(callback), + fallbackPollingInterval, + fallbackOptions + ); + } + + /** + * Watch the file or directory that is missing + * and switch to existing file or directory when the missing filesystem entry is created + */ + function watchMissingFileSystemEntry(): FileWatcher { + return watchFile( + fileOrDirectory, + (_fileName, eventKind, modifiedTime) => { + if (eventKind === FileWatcherEventKind.Created) { + modifiedTime ||= getModifiedTime(fileOrDirectory) || missingFileModifiedTime; + if (modifiedTime !== missingFileModifiedTime) { + callback("rename", "", modifiedTime); + // Call the callback for current file or directory + // For now it could be callback for the inner directory creation, + // but just return current directory, better than current no-op + updateWatcher(watchPresentFileSystemEntry); + } + } + }, + fallbackPollingInterval, + fallbackOptions + ); + } } +} + +/** + * patch writefile to create folder before writing the file + * + * @internal + */ +export function patchWriteFileEnsuringDirectory(sys: System) { + // patch writefile to create folder before writing the file + const originalWriteFile = sys.writeFile; + sys.writeFile = (path, data, writeBom) => + writeFileEnsuringDirectories( + path, + data, + !!writeBom, + (path, data, writeByteOrderMark) => originalWriteFile.call(sys, path, data, writeByteOrderMark), + path => sys.createDirectory(path), + path => sys.directoryExists(path)); +} + +/** @internal */ +export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex"; + +/** @internal */ +export interface NodeBuffer extends Uint8Array { + constructor: any; + write(str: string, encoding?: BufferEncoding): number; + write(str: string, offset: number, encoding?: BufferEncoding): number; + write(str: string, offset: number, length: number, encoding?: BufferEncoding): number; + toString(encoding?: string, start?: number, end?: number): string; + toJSON(): { type: "Buffer"; data: number[] }; + equals(otherBuffer: Uint8Array): boolean; + compare( + otherBuffer: Uint8Array, + targetStart?: number, + targetEnd?: number, + sourceStart?: number, + sourceEnd?: number + ): number; + copy(targetBuffer: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; + slice(begin?: number, end?: number): Buffer; + subarray(begin?: number, end?: number): Buffer; + writeUIntLE(value: number, offset: number, byteLength: number): number; + writeUIntBE(value: number, offset: number, byteLength: number): number; + writeIntLE(value: number, offset: number, byteLength: number): number; + writeIntBE(value: number, offset: number, byteLength: number): number; + readUIntLE(offset: number, byteLength: number): number; + readUIntBE(offset: number, byteLength: number): number; + readIntLE(offset: number, byteLength: number): number; + readIntBE(offset: number, byteLength: number): number; + readUInt8(offset: number): number; + readUInt16LE(offset: number): number; + readUInt16BE(offset: number): number; + readUInt32LE(offset: number): number; + readUInt32BE(offset: number): number; + readInt8(offset: number): number; + readInt16LE(offset: number): number; + readInt16BE(offset: number): number; + readInt32LE(offset: number): number; + readInt32BE(offset: number): number; + readFloatLE(offset: number): number; + readFloatBE(offset: number): number; + readDoubleLE(offset: number): number; + readDoubleBE(offset: number): number; + reverse(): this; + swap16(): Buffer; + swap32(): Buffer; + swap64(): Buffer; + writeUInt8(value: number, offset: number): number; + writeUInt16LE(value: number, offset: number): number; + writeUInt16BE(value: number, offset: number): number; + writeUInt32LE(value: number, offset: number): number; + writeUInt32BE(value: number, offset: number): number; + writeInt8(value: number, offset: number): number; + writeInt16LE(value: number, offset: number): number; + writeInt16BE(value: number, offset: number): number; + writeInt32LE(value: number, offset: number): number; + writeInt32BE(value: number, offset: number): number; + writeFloatLE(value: number, offset: number): number; + writeFloatBE(value: number, offset: number): number; + writeDoubleLE(value: number, offset: number): number; + writeDoubleBE(value: number, offset: number): number; + readBigUInt64BE?(offset?: number): bigint; + readBigUInt64LE?(offset?: number): bigint; + readBigInt64BE?(offset?: number): bigint; + readBigInt64LE?(offset?: number): bigint; + writeBigInt64BE?(value: bigint, offset?: number): number; + writeBigInt64LE?(value: bigint, offset?: number): number; + writeBigUInt64BE?(value: bigint, offset?: number): number; + writeBigUInt64LE?(value: bigint, offset?: number): number; + fill(value: string | Uint8Array | number, offset?: number, end?: number, encoding?: BufferEncoding): this; + indexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; + lastIndexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; + entries(): IterableIterator<[number, number]>; + includes(value: string | number | Buffer, byteOffset?: number, encoding?: BufferEncoding): boolean; + keys(): IterableIterator; + values(): IterableIterator; +} + +/** @internal */ +export interface Buffer extends NodeBuffer { } + +// TODO: GH#18217 Methods on System are often used as if they are certainly defined +export interface System { + args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; + write(s: string): void; + writeOutputIsTTY?(): boolean; + getWidthOfTerminal?(): number; + readFile(path: string, encoding?: string): string | undefined; + getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** - * patch writefile to create folder before writing the file + * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that + * use native OS file watching */ - /*@internal*/ - export function patchWriteFileEnsuringDirectory(sys: System) { - // patch writefile to create folder before writing the file - const originalWriteFile = sys.writeFile; - sys.writeFile = (path, data, writeBom) => - writeFileEnsuringDirectories( - path, - data, - !!writeBom, - (path, data, writeByteOrderMark) => originalWriteFile.call(sys, path, data, writeByteOrderMark), - path => sys.createDirectory(path), - path => sys.directoryExists(path)); - } + watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher; + watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher; + resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; + getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; + getModifiedTime?(path: string): Date | undefined; + setModifiedTime?(path: string, time: Date): void; + deleteFile?(path: string): void; + /** + * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) + */ + createHash?(data: string): string; + /** This must be cryptographically secure. Only implement this method using `crypto.createHash("sha256")`. */ + createSHA256Hash?(data: string): string; + getMemoryUsage?(): number; + exit(exitCode?: number): void; + /** @internal */ enableCPUProfiler?(path: string, continuation: () => void): boolean; + /** @internal */ disableCPUProfiler?(continuation: () => void): boolean; + /** @internal */ cpuProfilingEnabled?(): boolean; + realpath?(path: string): string; + /** @internal */ getEnvironmentVariable(name: string): string; + /** @internal */ tryEnableSourceMapsForHost?(): void; + /** @internal */ debugMode?: boolean; + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + clearTimeout?(timeoutId: any): void; + clearScreen?(): void; + /** @internal */ setBlocking?(): void; + base64decode?(input: string): string; + base64encode?(input: string): string; + /** @internal */ bufferFrom?(input: string, encoding?: string): Buffer; + /** @internal */ require?(baseDir: string, moduleName: string): RequireResult; + + // For testing + /** @internal */ now?(): Date; + /** @internal */ disableUseFileVersionAsSignature?: boolean; + /** @internal */ storeFilesChangingSignatureDuringEmit?: boolean; +} - /*@internal*/ - export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex"; - - /*@internal*/ - interface NodeBuffer extends Uint8Array { - constructor: any; - write(str: string, encoding?: BufferEncoding): number; - write(str: string, offset: number, encoding?: BufferEncoding): number; - write(str: string, offset: number, length: number, encoding?: BufferEncoding): number; - toString(encoding?: string, start?: number, end?: number): string; - toJSON(): { type: "Buffer"; data: number[] }; - equals(otherBuffer: Uint8Array): boolean; - compare( - otherBuffer: Uint8Array, - targetStart?: number, - targetEnd?: number, - sourceStart?: number, - sourceEnd?: number - ): number; - copy(targetBuffer: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; - slice(begin?: number, end?: number): Buffer; - subarray(begin?: number, end?: number): Buffer; - writeUIntLE(value: number, offset: number, byteLength: number): number; - writeUIntBE(value: number, offset: number, byteLength: number): number; - writeIntLE(value: number, offset: number, byteLength: number): number; - writeIntBE(value: number, offset: number, byteLength: number): number; - readUIntLE(offset: number, byteLength: number): number; - readUIntBE(offset: number, byteLength: number): number; - readIntLE(offset: number, byteLength: number): number; - readIntBE(offset: number, byteLength: number): number; - readUInt8(offset: number): number; - readUInt16LE(offset: number): number; - readUInt16BE(offset: number): number; - readUInt32LE(offset: number): number; - readUInt32BE(offset: number): number; - readInt8(offset: number): number; - readInt16LE(offset: number): number; - readInt16BE(offset: number): number; - readInt32LE(offset: number): number; - readInt32BE(offset: number): number; - readFloatLE(offset: number): number; - readFloatBE(offset: number): number; - readDoubleLE(offset: number): number; - readDoubleBE(offset: number): number; - reverse(): this; - swap16(): Buffer; - swap32(): Buffer; - swap64(): Buffer; - writeUInt8(value: number, offset: number): number; - writeUInt16LE(value: number, offset: number): number; - writeUInt16BE(value: number, offset: number): number; - writeUInt32LE(value: number, offset: number): number; - writeUInt32BE(value: number, offset: number): number; - writeInt8(value: number, offset: number): number; - writeInt16LE(value: number, offset: number): number; - writeInt16BE(value: number, offset: number): number; - writeInt32LE(value: number, offset: number): number; - writeInt32BE(value: number, offset: number): number; - writeFloatLE(value: number, offset: number): number; - writeFloatBE(value: number, offset: number): number; - writeDoubleLE(value: number, offset: number): number; - writeDoubleBE(value: number, offset: number): number; - readBigUInt64BE?(offset?: number): bigint; - readBigUInt64LE?(offset?: number): bigint; - readBigInt64BE?(offset?: number): bigint; - readBigInt64LE?(offset?: number): bigint; - writeBigInt64BE?(value: bigint, offset?: number): number; - writeBigInt64LE?(value: bigint, offset?: number): number; - writeBigUInt64BE?(value: bigint, offset?: number): number; - writeBigUInt64LE?(value: bigint, offset?: number): number; - fill(value: string | Uint8Array | number, offset?: number, end?: number, encoding?: BufferEncoding): this; - indexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; - lastIndexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; - entries(): IterableIterator<[number, number]>; - includes(value: string | number | Buffer, byteOffset?: number, encoding?: BufferEncoding): boolean; - keys(): IterableIterator; - values(): IterableIterator; - } +export interface FileWatcher { + close(): void; +} - /*@internal*/ - interface Buffer extends NodeBuffer { } - - // TODO: GH#18217 Methods on System are often used as if they are certainly defined - export interface System { - args: string[]; - newLine: string; - useCaseSensitiveFileNames: boolean; - write(s: string): void; - writeOutputIsTTY?(): boolean; - getWidthOfTerminal?(): number; - readFile(path: string, encoding?: string): string | undefined; - getFileSize?(path: string): number; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; +interface DirectoryWatcher extends FileWatcher { + referenceCount: number; +} - /** - * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that - * use native OS file watching - */ - watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher; - watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher; - resolvePath(path: string): string; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getExecutingFilePath(): string; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; - getModifiedTime?(path: string): Date | undefined; - setModifiedTime?(path: string, time: Date): void; - deleteFile?(path: string): void; - /** - * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) - */ - createHash?(data: string): string; - /** This must be cryptographically secure. Only implement this method using `crypto.createHash("sha256")`. */ - createSHA256Hash?(data: string): string; - getMemoryUsage?(): number; - exit(exitCode?: number): void; - /*@internal*/ enableCPUProfiler?(path: string, continuation: () => void): boolean; - /*@internal*/ disableCPUProfiler?(continuation: () => void): boolean; - /*@internal*/ cpuProfilingEnabled?(): boolean; - realpath?(path: string): string; - /*@internal*/ getEnvironmentVariable(name: string): string; - /*@internal*/ tryEnableSourceMapsForHost?(): void; - /*@internal*/ debugMode?: boolean; - setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; - clearTimeout?(timeoutId: any): void; - clearScreen?(): void; - /*@internal*/ setBlocking?(): void; - base64decode?(input: string): string; - base64encode?(input: string): string; - /*@internal*/ bufferFrom?(input: string, encoding?: string): Buffer; - /*@internal*/ require?(baseDir: string, moduleName: string): RequireResult; - - // For testing - /*@internal*/ now?(): Date; - /*@internal*/ disableUseFileVersionAsSignature?: boolean; - /*@internal*/ storeFilesChangingSignatureDuringEmit?: boolean; - } +declare const require: any; +declare const process: any; +declare const global: any; +declare const __filename: string; +declare const __dirname: string; - export interface FileWatcher { - close(): void; +export function getNodeMajorVersion(): number | undefined { + if (typeof process === "undefined") { + return undefined; } - - interface DirectoryWatcher extends FileWatcher { - referenceCount: number; + const version: string = process.version; + if (!version) { + return undefined; } + const dot = version.indexOf("."); + if (dot === -1) { + return undefined; + } + return parseInt(version.substring(1, dot)); +} - declare const require: any; - declare const process: any; - declare const global: any; - declare const __filename: string; - declare const __dirname: string; - - export function getNodeMajorVersion(): number | undefined { - if (typeof process === "undefined") { - return undefined; - } - const version: string = process.version; - if (!version) { - return undefined; +// TODO: GH#18217 this is used as if it's certainly defined in many places. +// eslint-disable-next-line prefer-const +export let sys: System = (() => { + // NodeJS detects "\uFEFF" at the start of the string and *replaces* it with the actual + // byte order mark from the specified encoding. Using any other byte order mark does + // not actually work. + const byteOrderMarkIndicator = "\uFEFF"; + + function getNodeSystem(): System { + const nativePattern = /^native |^\([^)]+\)$|^(internal[\\/]|[a-zA-Z0-9_\s]+(\.js)?$)/; + const _fs: typeof import("fs") = require("fs"); + const _path: typeof import("path") = require("path"); + const _os = require("os"); + // crypto can be absent on reduced node installations + let _crypto: typeof import("crypto") | undefined; + try { + _crypto = require("crypto"); } - const dot = version.indexOf("."); - if (dot === -1) { - return undefined; + catch { + _crypto = undefined; } - return parseInt(version.substring(1, dot)); - } - - // TODO: GH#18217 this is used as if it's certainly defined in many places. - // eslint-disable-next-line prefer-const - export let sys: System = (() => { - // NodeJS detects "\uFEFF" at the start of the string and *replaces* it with the actual - // byte order mark from the specified encoding. Using any other byte order mark does - // not actually work. - const byteOrderMarkIndicator = "\uFEFF"; - - function getNodeSystem(): System { - const nativePattern = /^native |^\([^)]+\)$|^(internal[\\/]|[a-zA-Z0-9_\s]+(\.js)?$)/; - const _fs: typeof import("fs") = require("fs"); - const _path: typeof import("path") = require("path"); - const _os = require("os"); - // crypto can be absent on reduced node installations - let _crypto: typeof import("crypto") | undefined; - try { - _crypto = require("crypto"); - } - catch { - _crypto = undefined; - } - let activeSession: import("inspector").Session | "stopping" | undefined; - let profilePath = "./profile.cpuprofile"; - - const Buffer: { - new (input: string, encoding?: string): any; - from?(input: string, encoding?: string): any; - } = require("buffer").Buffer; - - const nodeVersion = getNodeMajorVersion(); - const isNode4OrLater = nodeVersion! >= 4; - const isLinuxOrMacOs = process.platform === "linux" || process.platform === "darwin"; - - const platform: string = _os.platform(); - const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); - const fsRealpath = !!_fs.realpathSync.native ? process.platform === "win32" ? fsRealPathHandlingLongPath : _fs.realpathSync.native : _fs.realpathSync; - - const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin"); - const getCurrentDirectory = memoize(() => process.cwd()); - const { watchFile, watchDirectory } = createSystemWatchFunctions({ - pollingWatchFileWorker: fsWatchFileWorker, - getModifiedTime, - setTimeout, - clearTimeout, - fsWatchWorker, - useCaseSensitiveFileNames, - getCurrentDirectory, - fileSystemEntryExists, - // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows - // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) - fsSupportsRecursiveFsWatch, - getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, - realpath, - tscWatchFile: process.env.TSC_WATCHFILE, - useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER, - tscWatchDirectory: process.env.TSC_WATCHDIRECTORY, - inodeWatching: isLinuxOrMacOs, - sysLog, - }); - const nodeSystem: System = { - args: process.argv.slice(2), - newLine: _os.EOL, - useCaseSensitiveFileNames, - write(s: string): void { - process.stdout.write(s); - }, - getWidthOfTerminal(){ - return process.stdout.columns; - }, - writeOutputIsTTY() { - return process.stdout.isTTY; - }, - readFile, - writeFile, - watchFile, - watchDirectory, - resolvePath: path => _path.resolve(path), - fileExists, - directoryExists, - createDirectory(directoryName: string) { - if (!nodeSystem.directoryExists(directoryName)) { - // Wrapped in a try-catch to prevent crashing if we are in a race - // with another copy of ourselves to create the same directory - try { - _fs.mkdirSync(directoryName); - } - catch (e) { - if (e.code !== "EEXIST") { - // Failed for some other reason (access denied?); still throw - throw e; - } - } - } - }, - getExecutingFilePath() { - return __filename; - }, - getCurrentDirectory, - getDirectories, - getEnvironmentVariable(name: string) { - return process.env[name] || ""; - }, - readDirectory, - getModifiedTime, - setModifiedTime, - deleteFile, - createHash: _crypto ? createSHA256Hash : generateDjb2Hash, - createSHA256Hash: _crypto ? createSHA256Hash : undefined, - getMemoryUsage() { - if (global.gc) { - global.gc(); - } - return process.memoryUsage().heapUsed; - }, - getFileSize(path) { - try { - const stat = statSync(path); - if (stat?.isFile()) { - return stat.size; - } - } - catch { /*ignore*/ } - return 0; - }, - exit(exitCode?: number): void { - disableCPUProfiler(() => process.exit(exitCode)); - }, - enableCPUProfiler, - disableCPUProfiler, - cpuProfilingEnabled: () => !!activeSession || contains(process.execArgv, "--cpu-prof") || contains(process.execArgv, "--prof"), - realpath, - debugMode: !!process.env.NODE_INSPECTOR_IPC || !!process.env.VSCODE_INSPECTOR_OPTIONS || some(process.execArgv as string[], arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)), - tryEnableSourceMapsForHost() { + let activeSession: import("inspector").Session | "stopping" | undefined; + let profilePath = "./profile.cpuprofile"; + + const Buffer: { + new (input: string, encoding?: string): any; + from?(input: string, encoding?: string): any; + } = require("buffer").Buffer; + + const nodeVersion = getNodeMajorVersion(); + const isNode4OrLater = nodeVersion! >= 4; + const isLinuxOrMacOs = process.platform === "linux" || process.platform === "darwin"; + + const platform: string = _os.platform(); + const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); + const fsRealpath = !!_fs.realpathSync.native ? process.platform === "win32" ? fsRealPathHandlingLongPath : _fs.realpathSync.native : _fs.realpathSync; + + // If our filename is "sys.js", then we are executing unbundled on the raw tsc output. + // In that case, simulate a faked path in the directory where a bundle would normally + // appear (e.g. the directory containing lib.*.d.ts files). + // + // Note that if we ever emit as files like cjs/mjs, this check will be wrong. + const executingFilePath = __filename.endsWith("sys.js") ? _path.join(_path.dirname(__dirname), "__fake__.js") : __filename; + + const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin"); + const getCurrentDirectory = memoize(() => process.cwd()); + const { watchFile, watchDirectory } = createSystemWatchFunctions({ + pollingWatchFileWorker: fsWatchFileWorker, + getModifiedTime, + setTimeout, + clearTimeout, + fsWatchWorker, + useCaseSensitiveFileNames, + getCurrentDirectory, + fileSystemEntryExists, + // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows + // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) + fsSupportsRecursiveFsWatch, + getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, + realpath, + tscWatchFile: process.env.TSC_WATCHFILE, + useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER, + tscWatchDirectory: process.env.TSC_WATCHDIRECTORY, + inodeWatching: isLinuxOrMacOs, + sysLog, + }); + const nodeSystem: System = { + args: process.argv.slice(2), + newLine: _os.EOL, + useCaseSensitiveFileNames, + write(s: string): void { + process.stdout.write(s); + }, + getWidthOfTerminal(){ + return process.stdout.columns; + }, + writeOutputIsTTY() { + return process.stdout.isTTY; + }, + readFile, + writeFile, + watchFile, + watchDirectory, + resolvePath: path => _path.resolve(path), + fileExists, + directoryExists, + createDirectory(directoryName: string) { + if (!nodeSystem.directoryExists(directoryName)) { + // Wrapped in a try-catch to prevent crashing if we are in a race + // with another copy of ourselves to create the same directory try { - require("source-map-support").install(); + _fs.mkdirSync(directoryName); } - catch { - // Could not enable source maps. - } - }, - setTimeout, - clearTimeout, - clearScreen: () => { - process.stdout.write("\x1Bc"); - }, - setBlocking: () => { - if (process.stdout && process.stdout._handle && process.stdout._handle.setBlocking) { - process.stdout._handle.setBlocking(true); - } - }, - bufferFrom, - base64decode: input => bufferFrom(input, "base64").toString("utf8"), - base64encode: input => bufferFrom(input).toString("base64"), - require: (baseDir, moduleName) => { - try { - const modulePath = resolveJSModule(moduleName, baseDir, nodeSystem); - return { module: require(modulePath), modulePath, error: undefined }; + catch (e) { + if (e.code !== "EEXIST") { + // Failed for some other reason (access denied?); still throw + throw e; + } } - catch (error) { - return { module: undefined, modulePath: undefined, error }; + } + }, + getExecutingFilePath() { + return executingFilePath; + }, + getCurrentDirectory, + getDirectories, + getEnvironmentVariable(name: string) { + return process.env[name] || ""; + }, + readDirectory, + getModifiedTime, + setModifiedTime, + deleteFile, + createHash: _crypto ? createSHA256Hash : generateDjb2Hash, + createSHA256Hash: _crypto ? createSHA256Hash : undefined, + getMemoryUsage() { + if (global.gc) { + global.gc(); + } + return process.memoryUsage().heapUsed; + }, + getFileSize(path) { + try { + const stat = statSync(path); + if (stat?.isFile()) { + return stat.size; } } - }; - return nodeSystem; - - /** - * `throwIfNoEntry` was added so recently that it's not in the node types. - * This helper encapsulates the mitigating usage of `any`. - * See https://github.com/nodejs/node/pull/33716 - */ - function statSync(path: string): import("fs").Stats | undefined { - // throwIfNoEntry will be ignored by older versions of node - return (_fs as any).statSync(path, { throwIfNoEntry: false }); + catch { /*ignore*/ } + return 0; + }, + exit(exitCode?: number): void { + disableCPUProfiler(() => process.exit(exitCode)); + }, + enableCPUProfiler, + disableCPUProfiler, + cpuProfilingEnabled: () => !!activeSession || contains(process.execArgv, "--cpu-prof") || contains(process.execArgv, "--prof"), + realpath, + debugMode: !!process.env.NODE_INSPECTOR_IPC || !!process.env.VSCODE_INSPECTOR_OPTIONS || some(process.execArgv as string[], arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)), + tryEnableSourceMapsForHost() { + try { + (require("source-map-support") as typeof import("source-map-support")).install(); + } + catch { + // Could not enable source maps. + } + }, + setTimeout, + clearTimeout, + clearScreen: () => { + process.stdout.write("\x1Bc"); + }, + setBlocking: () => { + if (process.stdout && process.stdout._handle && process.stdout._handle.setBlocking) { + process.stdout._handle.setBlocking(true); + } + }, + bufferFrom, + base64decode: input => bufferFrom(input, "base64").toString("utf8"), + base64encode: input => bufferFrom(input).toString("base64"), + require: (baseDir, moduleName) => { + try { + const modulePath = resolveJSModule(moduleName, baseDir, nodeSystem); + return { module: require(modulePath), modulePath, error: undefined }; + } + catch (error) { + return { module: undefined, modulePath: undefined, error }; + } } + }; + return nodeSystem; - /** - * Uses the builtin inspector APIs to capture a CPU profile - * See https://nodejs.org/api/inspector.html#inspector_example_usage for details - */ - function enableCPUProfiler(path: string, cb: () => void) { - if (activeSession) { - cb(); - return false; - } - const inspector: typeof import("inspector") = require("inspector"); - if (!inspector || !inspector.Session) { + /** + * `throwIfNoEntry` was added so recently that it's not in the node types. + * This helper encapsulates the mitigating usage of `any`. + * See https://github.com/nodejs/node/pull/33716 + */ + function statSync(path: string): import("fs").Stats | undefined { + // throwIfNoEntry will be ignored by older versions of node + return (_fs as any).statSync(path, { throwIfNoEntry: false }); + } + + /** + * Uses the builtin inspector APIs to capture a CPU profile + * See https://nodejs.org/api/inspector.html#inspector_example_usage for details + */ + function enableCPUProfiler(path: string, cb: () => void) { + if (activeSession) { + cb(); + return false; + } + const inspector: typeof import("inspector") = require("inspector"); + if (!inspector || !inspector.Session) { + cb(); + return false; + } + const session = new inspector.Session(); + session.connect(); + + session.post("Profiler.enable", () => { + session.post("Profiler.start", () => { + activeSession = session; + profilePath = path; cb(); - return false; - } - const session = new inspector.Session(); - session.connect(); - - session.post("Profiler.enable", () => { - session.post("Profiler.start", () => { - activeSession = session; - profilePath = path; - cb(); - }); }); - return true; - } + }); + return true; + } - /** - * Strips non-TS paths from the profile, so users with private projects shouldn't - * need to worry about leaking paths by submitting a cpu profile to us - */ - function cleanupPaths(profile: import("inspector").Profiler.Profile) { - let externalFileCounter = 0; - const remappedPaths = new Map(); - const normalizedDir = normalizeSlashes(__dirname); - // Windows rooted dir names need an extra `/` prepended to be valid file:/// urls - const fileUrlRoot = `file://${getRootLength(normalizedDir) === 1 ? "" : "/"}${normalizedDir}`; - for (const node of profile.nodes) { - if (node.callFrame.url) { - const url = normalizeSlashes(node.callFrame.url); - if (containsPath(fileUrlRoot, url, useCaseSensitiveFileNames)) { - node.callFrame.url = getRelativePathToDirectoryOrUrl(fileUrlRoot, url, fileUrlRoot, createGetCanonicalFileName(useCaseSensitiveFileNames), /*isAbsolutePathAnUrl*/ true); - } - else if (!nativePattern.test(url)) { - node.callFrame.url = (remappedPaths.has(url) ? remappedPaths : remappedPaths.set(url, `external${externalFileCounter}.js`)).get(url)!; - externalFileCounter++; - } + /** + * Strips non-TS paths from the profile, so users with private projects shouldn't + * need to worry about leaking paths by submitting a cpu profile to us + */ + function cleanupPaths(profile: import("inspector").Profiler.Profile) { + let externalFileCounter = 0; + const remappedPaths = new Map(); + const normalizedDir = normalizeSlashes(_path.dirname(executingFilePath)); + // Windows rooted dir names need an extra `/` prepended to be valid file:/// urls + const fileUrlRoot = `file://${getRootLength(normalizedDir) === 1 ? "" : "/"}${normalizedDir}`; + for (const node of profile.nodes) { + if (node.callFrame.url) { + const url = normalizeSlashes(node.callFrame.url); + if (containsPath(fileUrlRoot, url, useCaseSensitiveFileNames)) { + node.callFrame.url = getRelativePathToDirectoryOrUrl(fileUrlRoot, url, fileUrlRoot, createGetCanonicalFileName(useCaseSensitiveFileNames), /*isAbsolutePathAnUrl*/ true); + } + else if (!nativePattern.test(url)) { + node.callFrame.url = (remappedPaths.has(url) ? remappedPaths : remappedPaths.set(url, `external${externalFileCounter}.js`)).get(url)!; + externalFileCounter++; } } - return profile; } + return profile; + } - function disableCPUProfiler(cb: () => void) { - if (activeSession && activeSession !== "stopping") { - const s = activeSession; - activeSession.post("Profiler.stop", (err, { profile }) => { - if (!err) { - try { - if (statSync(profilePath)?.isDirectory()) { - profilePath = _path.join(profilePath, `${(new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`); - } - } - catch { - // do nothing and ignore fallible fs operation - } - try { - _fs.mkdirSync(_path.dirname(profilePath), { recursive: true }); - } - catch { - // do nothing and ignore fallible fs operation + function disableCPUProfiler(cb: () => void) { + if (activeSession && activeSession !== "stopping") { + const s = activeSession; + activeSession.post("Profiler.stop", (err, { profile }) => { + if (!err) { + try { + if (statSync(profilePath)?.isDirectory()) { + profilePath = _path.join(profilePath, `${(new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`); } - _fs.writeFileSync(profilePath, JSON.stringify(cleanupPaths(profile))); } - activeSession = undefined; - s.disconnect(); - cb(); - }); - activeSession = "stopping"; - return true; - } - else { + catch { + // do nothing and ignore fallible fs operation + } + try { + _fs.mkdirSync(_path.dirname(profilePath), { recursive: true }); + } + catch { + // do nothing and ignore fallible fs operation + } + _fs.writeFileSync(profilePath, JSON.stringify(cleanupPaths(profile))); + } + activeSession = undefined; + s.disconnect(); cb(); - return false; - } + }); + activeSession = "stopping"; + return true; } - - function bufferFrom(input: string, encoding?: string): Buffer { - // See https://github.com/Microsoft/TypeScript/issues/25652 - return Buffer.from && (Buffer.from as Function) !== Int8Array.from - ? Buffer.from(input, encoding) - : new Buffer(input, encoding); + else { + cb(); + return false; } + } - function isFileSystemCaseSensitive(): boolean { - // win32\win64 are case insensitive platforms - if (platform === "win32" || platform === "win64") { - return false; - } - // If this file exists under a different case, we must be case-insensitve. - return !fileExists(swapCase(__filename)); - } + function bufferFrom(input: string, encoding?: string): Buffer { + // See https://github.com/Microsoft/TypeScript/issues/25652 + return Buffer.from && (Buffer.from as Function) !== Int8Array.from + ? Buffer.from(input, encoding) + : new Buffer(input, encoding); + } - /** Convert all lowercase chars to uppercase, and vice-versa */ - function swapCase(s: string): string { - return s.replace(/\w/g, (ch) => { - const up = ch.toUpperCase(); - return ch === up ? ch.toLowerCase() : up; - }); + function isFileSystemCaseSensitive(): boolean { + // win32\win64 are case insensitive platforms + if (platform === "win32" || platform === "win64") { + return false; } + // If this file exists under a different case, we must be case-insensitve. + return !fileExists(swapCase(__filename)); + } - function fsWatchFileWorker(fileName: string, callback: FileWatcherCallback, pollingInterval: number): FileWatcher { - _fs.watchFile(fileName, { persistent: true, interval: pollingInterval }, fileChanged); - let eventKind: FileWatcherEventKind; - return { - close: () => _fs.unwatchFile(fileName, fileChanged) - }; + /** Convert all lowercase chars to uppercase, and vice-versa */ + function swapCase(s: string): string { + return s.replace(/\w/g, (ch) => { + const up = ch.toUpperCase(); + return ch === up ? ch.toLowerCase() : up; + }); + } - function fileChanged(curr: import("fs").Stats, prev: import("fs").Stats) { - // previous event kind check is to ensure we recongnize the file as previously also missing when it is restored or renamed twice (that is it disappears and reappears) - // In such case, prevTime returned is same as prev time of event when file was deleted as per node documentation - const isPreviouslyDeleted = +prev.mtime === 0 || eventKind === FileWatcherEventKind.Deleted; - if (+curr.mtime === 0) { - if (isPreviouslyDeleted) { - // Already deleted file, no need to callback again - return; - } - eventKind = FileWatcherEventKind.Deleted; - } - else if (isPreviouslyDeleted) { - eventKind = FileWatcherEventKind.Created; - } - // If there is no change in modified time, ignore the event - else if (+curr.mtime === +prev.mtime) { + function fsWatchFileWorker(fileName: string, callback: FileWatcherCallback, pollingInterval: number): FileWatcher { + _fs.watchFile(fileName, { persistent: true, interval: pollingInterval }, fileChanged); + let eventKind: FileWatcherEventKind; + return { + close: () => _fs.unwatchFile(fileName, fileChanged) + }; + + function fileChanged(curr: import("fs").Stats, prev: import("fs").Stats) { + // previous event kind check is to ensure we recongnize the file as previously also missing when it is restored or renamed twice (that is it disappears and reappears) + // In such case, prevTime returned is same as prev time of event when file was deleted as per node documentation + const isPreviouslyDeleted = +prev.mtime === 0 || eventKind === FileWatcherEventKind.Deleted; + if (+curr.mtime === 0) { + if (isPreviouslyDeleted) { + // Already deleted file, no need to callback again return; } - else { - // File changed - eventKind = FileWatcherEventKind.Changed; - } - callback(fileName, eventKind, curr.mtime); - } - } - - function fsWatchWorker( - fileOrDirectory: string, - recursive: boolean, - callback: FsWatchCallback, - ) { - // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows - // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) - return _fs.watch( - fileOrDirectory, - fsSupportsRecursiveFsWatch ? - { persistent: true, recursive: !!recursive } : { persistent: true }, - callback - ); - } - - function readFileWorker(fileName: string, _encoding?: string): string | undefined { - let buffer: Buffer; - try { - buffer = _fs.readFileSync(fileName); + eventKind = FileWatcherEventKind.Deleted; } - catch (e) { - return undefined; + else if (isPreviouslyDeleted) { + eventKind = FileWatcherEventKind.Created; } - let len = buffer.length; - if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) { - // Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js, - // flip all byte pairs and treat as little endian. - len &= ~1; // Round down to a multiple of 2 - for (let i = 0; i < len; i += 2) { - const temp = buffer[i]; - buffer[i] = buffer[i + 1]; - buffer[i + 1] = temp; - } - return buffer.toString("utf16le", 2); - } - if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) { - // Little endian UTF-16 byte order mark detected - return buffer.toString("utf16le", 2); + // If there is no change in modified time, ignore the event + else if (+curr.mtime === +prev.mtime) { + return; } - if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) { - // UTF-8 byte order mark detected - return buffer.toString("utf8", 3); + else { + // File changed + eventKind = FileWatcherEventKind.Changed; } - // Default is UTF-8 with no byte order mark - return buffer.toString("utf8"); + callback(fileName, eventKind, curr.mtime); } + } - function readFile(fileName: string, _encoding?: string): string | undefined { - perfLogger.logStartReadFile(fileName); - const file = readFileWorker(fileName, _encoding); - perfLogger.logStopReadFile(); - return file; - } + function fsWatchWorker( + fileOrDirectory: string, + recursive: boolean, + callback: FsWatchCallback, + ) { + // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows + // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) + return _fs.watch( + fileOrDirectory, + fsSupportsRecursiveFsWatch ? + { persistent: true, recursive: !!recursive } : { persistent: true }, + callback + ); + } - function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { - perfLogger.logEvent("WriteFile: " + fileName); - // If a BOM is required, emit one - if (writeByteOrderMark) { - data = byteOrderMarkIndicator + data; + function readFileWorker(fileName: string, _encoding?: string): string | undefined { + let buffer: Buffer; + try { + buffer = _fs.readFileSync(fileName); + } + catch (e) { + return undefined; + } + let len = buffer.length; + if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) { + // Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js, + // flip all byte pairs and treat as little endian. + len &= ~1; // Round down to a multiple of 2 + for (let i = 0; i < len; i += 2) { + const temp = buffer[i]; + buffer[i] = buffer[i + 1]; + buffer[i + 1] = temp; } + return buffer.toString("utf16le", 2); + } + if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) { + // Little endian UTF-16 byte order mark detected + return buffer.toString("utf16le", 2); + } + if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) { + // UTF-8 byte order mark detected + return buffer.toString("utf8", 3); + } + // Default is UTF-8 with no byte order mark + return buffer.toString("utf8"); + } - let fd: number | undefined; + function readFile(fileName: string, _encoding?: string): string | undefined { + perfLogger.logStartReadFile(fileName); + const file = readFileWorker(fileName, _encoding); + perfLogger.logStopReadFile(); + return file; + } - try { - fd = _fs.openSync(fileName, "w"); - _fs.writeSync(fd, data, /*position*/ undefined, "utf8"); - } - finally { - if (fd !== undefined) { - _fs.closeSync(fd); - } + function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { + perfLogger.logEvent("WriteFile: " + fileName); + // If a BOM is required, emit one + if (writeByteOrderMark) { + data = byteOrderMarkIndicator + data; + } + + let fd: number | undefined; + + try { + fd = _fs.openSync(fileName, "w"); + _fs.writeSync(fd, data, /*position*/ undefined, "utf8"); + } + finally { + if (fd !== undefined) { + _fs.closeSync(fd); } } + } - function getAccessibleFileSystemEntries(path: string): FileSystemEntries { - perfLogger.logEvent("ReadDir: " + (path || ".")); - try { - const entries = _fs.readdirSync(path || ".", { withFileTypes: true }); - const files: string[] = []; - const directories: string[] = []; - for (const dirent of entries) { - // withFileTypes is not supported before Node 10.10. - const entry = typeof dirent === "string" ? dirent : dirent.name; - - // This is necessary because on some file system node fails to exclude - // "." and "..". See https://github.com/nodejs/node/issues/4002 - if (entry === "." || entry === "..") { - continue; - } + function getAccessibleFileSystemEntries(path: string): FileSystemEntries { + perfLogger.logEvent("ReadDir: " + (path || ".")); + try { + const entries = _fs.readdirSync(path || ".", { withFileTypes: true }); + const files: string[] = []; + const directories: string[] = []; + for (const dirent of entries) { + // withFileTypes is not supported before Node 10.10. + const entry = typeof dirent === "string" ? dirent : dirent.name; + + // This is necessary because on some file system node fails to exclude + // "." and "..". See https://github.com/nodejs/node/issues/4002 + if (entry === "." || entry === "..") { + continue; + } - let stat: any; - if (typeof dirent === "string" || dirent.isSymbolicLink()) { - const name = combinePaths(path, entry); + let stat: any; + if (typeof dirent === "string" || dirent.isSymbolicLink()) { + const name = combinePaths(path, entry); - try { - stat = statSync(name); - if (!stat) { - continue; - } - } - catch (e) { + try { + stat = statSync(name); + if (!stat) { continue; } } - else { - stat = dirent; + catch (e) { + continue; } + } + else { + stat = dirent; + } - if (stat.isFile()) { - files.push(entry); - } - else if (stat.isDirectory()) { - directories.push(entry); - } + if (stat.isFile()) { + files.push(entry); + } + else if (stat.isDirectory()) { + directories.push(entry); } - files.sort(); - directories.sort(); - return { files, directories }; - } - catch (e) { - return emptyFileSystemEntries; } + files.sort(); + directories.sort(); + return { files, directories }; } - - function readDirectory(path: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] { - return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath); + catch (e) { + return emptyFileSystemEntries; } + } - function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean { - // Since the error thrown by fs.statSync isn't used, we can avoid collecting a stack trace to improve - // the CPU time performance. - const originalStackTraceLimit = Error.stackTraceLimit; - Error.stackTraceLimit = 0; + function readDirectory(path: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] { + return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath); + } - try { - const stat = statSync(path); - if (!stat) { - return false; - } - switch (entryKind) { - case FileSystemEntryKind.File: return stat.isFile(); - case FileSystemEntryKind.Directory: return stat.isDirectory(); - default: return false; - } - } - catch (e) { + function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean { + // Since the error thrown by fs.statSync isn't used, we can avoid collecting a stack trace to improve + // the CPU time performance. + const originalStackTraceLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + + try { + const stat = statSync(path); + if (!stat) { return false; } - finally { - Error.stackTraceLimit = originalStackTraceLimit; + switch (entryKind) { + case FileSystemEntryKind.File: return stat.isFile(); + case FileSystemEntryKind.Directory: return stat.isDirectory(); + default: return false; } } - - function fileExists(path: string): boolean { - return fileSystemEntryExists(path, FileSystemEntryKind.File); + catch (e) { + return false; } - - function directoryExists(path: string): boolean { - return fileSystemEntryExists(path, FileSystemEntryKind.Directory); + finally { + Error.stackTraceLimit = originalStackTraceLimit; } + } - function getDirectories(path: string): string[] { - return getAccessibleFileSystemEntries(path).directories.slice(); - } + function fileExists(path: string): boolean { + return fileSystemEntryExists(path, FileSystemEntryKind.File); + } - function fsRealPathHandlingLongPath(path: string): string { - return path.length < 260 ? _fs.realpathSync.native(path) : _fs.realpathSync(path); - } + function directoryExists(path: string): boolean { + return fileSystemEntryExists(path, FileSystemEntryKind.Directory); + } - function realpath(path: string): string { - try { - return fsRealpath(path); - } - catch { - return path; - } - } + function getDirectories(path: string): string[] { + return getAccessibleFileSystemEntries(path).directories.slice(); + } - function getModifiedTime(path: string) { - // Since the error thrown by fs.statSync isn't used, we can avoid collecting a stack trace to improve - // the CPU time performance. - const originalStackTraceLimit = Error.stackTraceLimit; - Error.stackTraceLimit = 0; - try { - return statSync(path)?.mtime; - } - catch (e) { - return undefined; - } - finally { - Error.stackTraceLimit = originalStackTraceLimit; - } - } + function fsRealPathHandlingLongPath(path: string): string { + return path.length < 260 ? _fs.realpathSync.native(path) : _fs.realpathSync(path); + } - function setModifiedTime(path: string, time: Date) { - try { - _fs.utimesSync(path, time, time); - } - catch (e) { - return; - } + function realpath(path: string): string { + try { + return fsRealpath(path); } - - function deleteFile(path: string) { - try { - return _fs.unlinkSync(path); - } - catch (e) { - return; - } + catch { + return path; } + } - function createSHA256Hash(data: string): string { - const hash = _crypto!.createHash("sha256"); - hash.update(data); - return hash.digest("hex"); + function getModifiedTime(path: string) { + // Since the error thrown by fs.statSync isn't used, we can avoid collecting a stack trace to improve + // the CPU time performance. + const originalStackTraceLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + try { + return statSync(path)?.mtime; + } + catch (e) { + return undefined; + } + finally { + Error.stackTraceLimit = originalStackTraceLimit; } } - let sys: System | undefined; - if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof require !== "undefined") { - // process and process.nextTick checks if current environment is node-like - // process.browser check excludes webpack and browserify - sys = getNodeSystem(); + function setModifiedTime(path: string, time: Date) { + try { + _fs.utimesSync(path, time, time); + } + catch (e) { + return; + } } - if (sys) { - // patch writefile to create folder before writing the file - patchWriteFileEnsuringDirectory(sys); + + function deleteFile(path: string) { + try { + return _fs.unlinkSync(path); + } + catch (e) { + return; + } } - return sys!; - })(); - /*@internal*/ - export function setSys(s: System) { - sys = s; + function createSHA256Hash(data: string): string { + const hash = _crypto!.createHash("sha256"); + hash.update(data); + return hash.digest("hex"); + } } - if (sys && sys.getEnvironmentVariable) { - setCustomPollingValues(sys); - Debug.setAssertionLevel(/^development$/i.test(sys.getEnvironmentVariable("NODE_ENV")) - ? AssertionLevel.Normal - : AssertionLevel.None); + let sys: System | undefined; + if (isNodeLikeSystem()) { + sys = getNodeSystem(); } - if (sys && sys.debugMode) { - Debug.isDebugging = true; + if (sys) { + // patch writefile to create folder before writing the file + patchWriteFileEnsuringDirectory(sys); } + return sys!; +})(); + +/** @internal */ +export function setSys(s: System) { + sys = s; +} + +if (sys && sys.getEnvironmentVariable) { + setCustomPollingValues(sys); + Debug.setAssertionLevel(/^development$/i.test(sys.getEnvironmentVariable("NODE_ENV")) + ? AssertionLevel.Normal + : AssertionLevel.None); +} +if (sys && sys.debugMode) { + Debug.isDebugging = true; } diff --git a/src/compiler/tracing.ts b/src/compiler/tracing.ts index 54f378d61cc16..2102592d1a6d9 100644 --- a/src/compiler/tracing.ts +++ b/src/compiler/tracing.ts @@ -1,349 +1,360 @@ +import { + combinePaths, ConditionalType, Debug, EvolvingArrayType, getLineAndCharacterOfPosition, getSourceFileOfNode, + IndexedAccessType, IndexType, IntersectionType, LineAndCharacter, Map, Node, ObjectFlags, Path, ReverseMappedType, + SubstitutionType, timestamp, Type, TypeFlags, TypeReference, unescapeLeadingUnderscores, UnionType, +} from "./_namespaces/ts"; +import * as performance from "./_namespaces/ts.performance"; + /* Tracing events for the compiler. */ -/*@internal*/ -namespace ts { // eslint-disable-line local/one-namespace-per-file - // should be used as tracing?.___ - export let tracing: typeof tracingEnabled | undefined; - // enable the above using startTracing() +// should be used as tracing?.___ +/** @internal */ +export let tracing: typeof tracingEnabled | undefined; +// enable the above using startTracing() - // `tracingEnabled` should never be used directly, only through the above - namespace tracingEnabled { // eslint-disable-line local/one-namespace-per-file - type Mode = "project" | "build" | "server"; +/** + * Do not use this directly; instead @see {tracing}. + * @internal + */ +export namespace tracingEnabled { + type Mode = "project" | "build" | "server"; - let fs: typeof import("fs"); + let fs: typeof import("fs"); - let traceCount = 0; - let traceFd = 0; + let traceCount = 0; + let traceFd = 0; - let mode: Mode; + let mode: Mode; - const typeCatalog: Type[] = []; // NB: id is index + 1 + const typeCatalog: Type[] = []; // NB: id is index + 1 - let legendPath: string | undefined; - const legend: TraceRecord[] = []; + let legendPath: string | undefined; + const legend: TraceRecord[] = []; - // The actual constraint is that JSON.stringify be able to serialize it without throwing. - interface Args { - [key: string]: string | number | boolean | null | undefined | Args | readonly (string | number | boolean | null | undefined | Args)[]; - } + // The actual constraint is that JSON.stringify be able to serialize it without throwing. + interface Args { + [key: string]: string | number | boolean | null | undefined | Args | readonly (string | number | boolean | null | undefined | Args)[]; + } - /** Starts tracing for the given project. */ - export function startTracing(tracingMode: Mode, traceDir: string, configFilePath?: string) { - Debug.assert(!tracing, "Tracing already started"); + /** Starts tracing for the given project. */ + export function startTracing(tracingMode: Mode, traceDir: string, configFilePath?: string) { + Debug.assert(!tracing, "Tracing already started"); - if (fs === undefined) { - try { - fs = require("fs"); - } - catch (e) { - throw new Error(`tracing requires having fs\n(original error: ${e.message || e})`); - } + if (fs === undefined) { + try { + fs = require("fs"); } - - mode = tracingMode; - typeCatalog.length = 0; - - if (legendPath === undefined) { - legendPath = combinePaths(traceDir, "legend.json"); + catch (e) { + throw new Error(`tracing requires having fs\n(original error: ${e.message || e})`); } + } - // Note that writing will fail later on if it exists and is not a directory - if (!fs.existsSync(traceDir)) { - fs.mkdirSync(traceDir, { recursive: true }); - } + mode = tracingMode; + typeCatalog.length = 0; - const countPart = - mode === "build" ? `.${process.pid}-${++traceCount}` - : mode === "server" ? `.${process.pid}` - : ``; - const tracePath = combinePaths(traceDir, `trace${countPart}.json`); - const typesPath = combinePaths(traceDir, `types${countPart}.json`); - - legend.push({ - configFilePath, - tracePath, - typesPath, - }); - - traceFd = fs.openSync(tracePath, "w"); - tracing = tracingEnabled; // only when traceFd is properly set - - // Start with a prefix that contains some metadata that the devtools profiler expects (also avoids a warning on import) - const meta = { cat: "__metadata", ph: "M", ts: 1000 * timestamp(), pid: 1, tid: 1 }; - fs.writeSync(traceFd, - "[\n" - + [{ name: "process_name", args: { name: "tsc" }, ...meta }, - { name: "thread_name", args: { name: "Main" }, ...meta }, - { name: "TracingStartedInBrowser", ...meta, cat: "disabled-by-default-devtools.timeline" }] - .map(v => JSON.stringify(v)).join(",\n")); + if (legendPath === undefined) { + legendPath = combinePaths(traceDir, "legend.json"); } - /** Stops tracing for the in-progress project and dumps the type catalog. */ - export function stopTracing() { - Debug.assert(tracing, "Tracing is not in progress"); - Debug.assert(!!typeCatalog.length === (mode !== "server")); // Have a type catalog iff not in server mode + // Note that writing will fail later on if it exists and is not a directory + if (!fs.existsSync(traceDir)) { + fs.mkdirSync(traceDir, { recursive: true }); + } - fs.writeSync(traceFd, `\n]\n`); - fs.closeSync(traceFd); - tracing = undefined; + const countPart = + mode === "build" ? `.${process.pid}-${++traceCount}` + : mode === "server" ? `.${process.pid}` + : ``; + const tracePath = combinePaths(traceDir, `trace${countPart}.json`); + const typesPath = combinePaths(traceDir, `types${countPart}.json`); + + legend.push({ + configFilePath, + tracePath, + typesPath, + }); + + traceFd = fs.openSync(tracePath, "w"); + tracing = tracingEnabled; // only when traceFd is properly set + + // Start with a prefix that contains some metadata that the devtools profiler expects (also avoids a warning on import) + const meta = { cat: "__metadata", ph: "M", ts: 1000 * timestamp(), pid: 1, tid: 1 }; + fs.writeSync(traceFd, + "[\n" + + [{ name: "process_name", args: { name: "tsc" }, ...meta }, + { name: "thread_name", args: { name: "Main" }, ...meta }, + { name: "TracingStartedInBrowser", ...meta, cat: "disabled-by-default-devtools.timeline" }] + .map(v => JSON.stringify(v)).join(",\n")); + } - if (typeCatalog.length) { - dumpTypes(typeCatalog); - } - else { - // We pre-computed this path for convenience, but clear it - // now that the file won't be created. - legend[legend.length - 1].typesPath = undefined; - } - } + /** Stops tracing for the in-progress project and dumps the type catalog. */ + export function stopTracing() { + Debug.assert(tracing, "Tracing is not in progress"); + Debug.assert(!!typeCatalog.length === (mode !== "server")); // Have a type catalog iff not in server mode - export function recordType(type: Type): void { - if (mode !== "server") { - typeCatalog.push(type); - } - } + fs.writeSync(traceFd, `\n]\n`); + fs.closeSync(traceFd); + tracing = undefined; - export const enum Phase { - Parse = "parse", - Program = "program", - Bind = "bind", - Check = "check", // Before we get into checking types (e.g. checkSourceFile) - CheckTypes = "checkTypes", - Emit = "emit", - Session = "session", + if (typeCatalog.length) { + dumpTypes(typeCatalog); + } + else { + // We pre-computed this path for convenience, but clear it + // now that the file won't be created. + legend[legend.length - 1].typesPath = undefined; } + } - export function instant(phase: Phase, name: string, args?: Args) { - writeEvent("I", phase, name, args, `"s":"g"`); + export function recordType(type: Type): void { + if (mode !== "server") { + typeCatalog.push(type); } + } - const eventStack: { phase: Phase, name: string, args?: Args, time: number, separateBeginAndEnd: boolean }[] = []; - - /** - * @param separateBeginAndEnd - used for special cases where we need the trace point even if the event - * never terminates (typically for reducing a scenario too big to trace to one that can be completed). - * In the future we might implement an exit handler to dump unfinished events which would deprecate - * these operations. - */ - export function push(phase: Phase, name: string, args?: Args, separateBeginAndEnd = false) { - if (separateBeginAndEnd) { - writeEvent("B", phase, name, args); - } - eventStack.push({ phase, name, args, time: 1000 * timestamp(), separateBeginAndEnd }); + export const enum Phase { + Parse = "parse", + Program = "program", + Bind = "bind", + Check = "check", // Before we get into checking types (e.g. checkSourceFile) + CheckTypes = "checkTypes", + Emit = "emit", + Session = "session", + } + + export function instant(phase: Phase, name: string, args?: Args) { + writeEvent("I", phase, name, args, `"s":"g"`); + } + + const eventStack: { phase: Phase, name: string, args?: Args, time: number, separateBeginAndEnd: boolean }[] = []; + + /** + * @param separateBeginAndEnd - used for special cases where we need the trace point even if the event + * never terminates (typically for reducing a scenario too big to trace to one that can be completed). + * In the future we might implement an exit handler to dump unfinished events which would deprecate + * these operations. + */ + export function push(phase: Phase, name: string, args?: Args, separateBeginAndEnd = false) { + if (separateBeginAndEnd) { + writeEvent("B", phase, name, args); } - export function pop(results?: Args) { - Debug.assert(eventStack.length > 0); - writeStackEvent(eventStack.length - 1, 1000 * timestamp(), results); - eventStack.length--; + eventStack.push({ phase, name, args, time: 1000 * timestamp(), separateBeginAndEnd }); + } + export function pop(results?: Args) { + Debug.assert(eventStack.length > 0); + writeStackEvent(eventStack.length - 1, 1000 * timestamp(), results); + eventStack.length--; + } + export function popAll() { + const endTime = 1000 * timestamp(); + for (let i = eventStack.length - 1; i >= 0; i--) { + writeStackEvent(i, endTime); } - export function popAll() { - const endTime = 1000 * timestamp(); - for (let i = eventStack.length - 1; i >= 0; i--) { - writeStackEvent(i, endTime); - } - eventStack.length = 0; + eventStack.length = 0; + } + // sample every 10ms + const sampleInterval = 1000 * 10; + function writeStackEvent(index: number, endTime: number, results?: Args) { + const { phase, name, args, time, separateBeginAndEnd } = eventStack[index]; + if (separateBeginAndEnd) { + Debug.assert(!results, "`results` are not supported for events with `separateBeginAndEnd`"); + writeEvent("E", phase, name, args, /*extras*/ undefined, endTime); } - // sample every 10ms - const sampleInterval = 1000 * 10; - function writeStackEvent(index: number, endTime: number, results?: Args) { - const { phase, name, args, time, separateBeginAndEnd } = eventStack[index]; - if (separateBeginAndEnd) { - Debug.assert(!results, "`results` are not supported for events with `separateBeginAndEnd`"); - writeEvent("E", phase, name, args, /*extras*/ undefined, endTime); - } - // test if [time,endTime) straddles a sampling point - else if (sampleInterval - (time % sampleInterval) <= endTime - time) { - writeEvent("X", phase, name, { ...args, results }, `"dur":${endTime - time}`, time); - } + // test if [time,endTime) straddles a sampling point + else if (sampleInterval - (time % sampleInterval) <= endTime - time) { + writeEvent("X", phase, name, { ...args, results }, `"dur":${endTime - time}`, time); } + } - function writeEvent(eventType: string, phase: Phase, name: string, args: Args | undefined, extras?: string, - time: number = 1000 * timestamp()) { - - // In server mode, there's no easy way to dump type information, so we drop events that would require it. - if (mode === "server" && phase === Phase.CheckTypes) return; + function writeEvent(eventType: string, phase: Phase, name: string, args: Args | undefined, extras?: string, + time: number = 1000 * timestamp()) { - performance.mark("beginTracing"); - fs.writeSync(traceFd, `,\n{"pid":1,"tid":1,"ph":"${eventType}","cat":"${phase}","ts":${time},"name":"${name}"`); - if (extras) fs.writeSync(traceFd, `,${extras}`); - if (args) fs.writeSync(traceFd, `,"args":${JSON.stringify(args)}`); - fs.writeSync(traceFd, `}`); - performance.mark("endTracing"); - performance.measure("Tracing", "beginTracing", "endTracing"); - } + // In server mode, there's no easy way to dump type information, so we drop events that would require it. + if (mode === "server" && phase === Phase.CheckTypes) return; - function getLocation(node: Node | undefined) { - const file = getSourceFileOfNode(node); - return !file - ? undefined - : { - path: file.path, - start: indexFromOne(getLineAndCharacterOfPosition(file, node!.pos)), - end: indexFromOne(getLineAndCharacterOfPosition(file, node!.end)), - }; + performance.mark("beginTracing"); + fs.writeSync(traceFd, `,\n{"pid":1,"tid":1,"ph":"${eventType}","cat":"${phase}","ts":${time},"name":"${name}"`); + if (extras) fs.writeSync(traceFd, `,${extras}`); + if (args) fs.writeSync(traceFd, `,"args":${JSON.stringify(args)}`); + fs.writeSync(traceFd, `}`); + performance.mark("endTracing"); + performance.measure("Tracing", "beginTracing", "endTracing"); + } - function indexFromOne(lc: LineAndCharacter): LineAndCharacter { - return { - line: lc.line + 1, - character: lc.character + 1, - }; - } + function getLocation(node: Node | undefined) { + const file = getSourceFileOfNode(node); + return !file + ? undefined + : { + path: file.path, + start: indexFromOne(getLineAndCharacterOfPosition(file, node!.pos)), + end: indexFromOne(getLineAndCharacterOfPosition(file, node!.end)), + }; + + function indexFromOne(lc: LineAndCharacter): LineAndCharacter { + return { + line: lc.line + 1, + character: lc.character + 1, + }; } + } - function dumpTypes(types: readonly Type[]) { - performance.mark("beginDumpTypes"); + function dumpTypes(types: readonly Type[]) { + performance.mark("beginDumpTypes"); - const typesPath = legend[legend.length - 1].typesPath!; - const typesFd = fs.openSync(typesPath, "w"); + const typesPath = legend[legend.length - 1].typesPath!; + const typesFd = fs.openSync(typesPath, "w"); - const recursionIdentityMap = new Map(); + const recursionIdentityMap = new Map(); - // Cleverness: no line break here so that the type ID will match the line number - fs.writeSync(typesFd, "["); + // Cleverness: no line break here so that the type ID will match the line number + fs.writeSync(typesFd, "["); - const numTypes = types.length; - for (let i = 0; i < numTypes; i++) { - const type = types[i]; - const objectFlags = (type as any).objectFlags; - const symbol = type.aliasSymbol ?? type.symbol; + const numTypes = types.length; + for (let i = 0; i < numTypes; i++) { + const type = types[i]; + const objectFlags = (type as any).objectFlags; + const symbol = type.aliasSymbol ?? type.symbol; - // It's slow to compute the display text, so skip it unless it's really valuable (or cheap) - let display: string | undefined; - if ((objectFlags & ObjectFlags.Anonymous) | (type.flags & TypeFlags.Literal)) { - try { - display = type.checker?.typeToString(type); - } - catch { - display = undefined; - } - } - - let indexedAccessProperties: object = {}; - if (type.flags & TypeFlags.IndexedAccess) { - const indexedAccessType = type as IndexedAccessType; - indexedAccessProperties = { - indexedAccessObjectType: indexedAccessType.objectType?.id, - indexedAccessIndexType: indexedAccessType.indexType?.id, - }; + // It's slow to compute the display text, so skip it unless it's really valuable (or cheap) + let display: string | undefined; + if ((objectFlags & ObjectFlags.Anonymous) | (type.flags & TypeFlags.Literal)) { + try { + display = type.checker?.typeToString(type); } - - let referenceProperties: object = {}; - if (objectFlags & ObjectFlags.Reference) { - const referenceType = type as TypeReference; - referenceProperties = { - instantiatedType: referenceType.target?.id, - typeArguments: referenceType.resolvedTypeArguments?.map(t => t.id), - referenceLocation: getLocation(referenceType.node), - }; + catch { + display = undefined; } + } - let conditionalProperties: object = {}; - if (type.flags & TypeFlags.Conditional) { - const conditionalType = type as ConditionalType; - conditionalProperties = { - conditionalCheckType: conditionalType.checkType?.id, - conditionalExtendsType: conditionalType.extendsType?.id, - conditionalTrueType: conditionalType.resolvedTrueType?.id ?? -1, - conditionalFalseType: conditionalType.resolvedFalseType?.id ?? -1, - }; - } + let indexedAccessProperties: object = {}; + if (type.flags & TypeFlags.IndexedAccess) { + const indexedAccessType = type as IndexedAccessType; + indexedAccessProperties = { + indexedAccessObjectType: indexedAccessType.objectType?.id, + indexedAccessIndexType: indexedAccessType.indexType?.id, + }; + } - let substitutionProperties: object = {}; - if (type.flags & TypeFlags.Substitution) { - const substitutionType = type as SubstitutionType; - substitutionProperties = { - substitutionBaseType: substitutionType.baseType?.id, - constraintType: substitutionType.constraint?.id, - }; - } + let referenceProperties: object = {}; + if (objectFlags & ObjectFlags.Reference) { + const referenceType = type as TypeReference; + referenceProperties = { + instantiatedType: referenceType.target?.id, + typeArguments: referenceType.resolvedTypeArguments?.map(t => t.id), + referenceLocation: getLocation(referenceType.node), + }; + } - let reverseMappedProperties: object = {}; - if (objectFlags & ObjectFlags.ReverseMapped) { - const reverseMappedType = type as ReverseMappedType; - reverseMappedProperties = { - reverseMappedSourceType: reverseMappedType.source?.id, - reverseMappedMappedType: reverseMappedType.mappedType?.id, - reverseMappedConstraintType: reverseMappedType.constraintType?.id, - }; - } + let conditionalProperties: object = {}; + if (type.flags & TypeFlags.Conditional) { + const conditionalType = type as ConditionalType; + conditionalProperties = { + conditionalCheckType: conditionalType.checkType?.id, + conditionalExtendsType: conditionalType.extendsType?.id, + conditionalTrueType: conditionalType.resolvedTrueType?.id ?? -1, + conditionalFalseType: conditionalType.resolvedFalseType?.id ?? -1, + }; + } - let evolvingArrayProperties: object = {}; - if (objectFlags & ObjectFlags.EvolvingArray) { - const evolvingArrayType = type as EvolvingArrayType; - evolvingArrayProperties = { - evolvingArrayElementType: evolvingArrayType.elementType.id, - evolvingArrayFinalType: evolvingArrayType.finalArrayType?.id, - }; - } + let substitutionProperties: object = {}; + if (type.flags & TypeFlags.Substitution) { + const substitutionType = type as SubstitutionType; + substitutionProperties = { + substitutionBaseType: substitutionType.baseType?.id, + constraintType: substitutionType.constraint?.id, + }; + } - // We can't print out an arbitrary object, so just assign each one a unique number. - // Don't call it an "id" so people don't treat it as a type id. - let recursionToken: number | undefined; - const recursionIdentity = type.checker.getRecursionIdentity(type); - if (recursionIdentity) { - recursionToken = recursionIdentityMap.get(recursionIdentity); - if (!recursionToken) { - recursionToken = recursionIdentityMap.size; - recursionIdentityMap.set(recursionIdentity, recursionToken); - } - } + let reverseMappedProperties: object = {}; + if (objectFlags & ObjectFlags.ReverseMapped) { + const reverseMappedType = type as ReverseMappedType; + reverseMappedProperties = { + reverseMappedSourceType: reverseMappedType.source?.id, + reverseMappedMappedType: reverseMappedType.mappedType?.id, + reverseMappedConstraintType: reverseMappedType.constraintType?.id, + }; + } - const descriptor = { - id: type.id, - intrinsicName: (type as any).intrinsicName, - symbolName: symbol?.escapedName && unescapeLeadingUnderscores(symbol.escapedName), - recursionId: recursionToken, - isTuple: objectFlags & ObjectFlags.Tuple ? true : undefined, - unionTypes: (type.flags & TypeFlags.Union) ? (type as UnionType).types?.map(t => t.id) : undefined, - intersectionTypes: (type.flags & TypeFlags.Intersection) ? (type as IntersectionType).types.map(t => t.id) : undefined, - aliasTypeArguments: type.aliasTypeArguments?.map(t => t.id), - keyofType: (type.flags & TypeFlags.Index) ? (type as IndexType).type?.id : undefined, - ...indexedAccessProperties, - ...referenceProperties, - ...conditionalProperties, - ...substitutionProperties, - ...reverseMappedProperties, - ...evolvingArrayProperties, - destructuringPattern: getLocation(type.pattern), - firstDeclaration: getLocation(symbol?.declarations?.[0]), - flags: Debug.formatTypeFlags(type.flags).split("|"), - display, + let evolvingArrayProperties: object = {}; + if (objectFlags & ObjectFlags.EvolvingArray) { + const evolvingArrayType = type as EvolvingArrayType; + evolvingArrayProperties = { + evolvingArrayElementType: evolvingArrayType.elementType.id, + evolvingArrayFinalType: evolvingArrayType.finalArrayType?.id, }; + } - fs.writeSync(typesFd, JSON.stringify(descriptor)); - if (i < numTypes - 1) { - fs.writeSync(typesFd, ",\n"); + // We can't print out an arbitrary object, so just assign each one a unique number. + // Don't call it an "id" so people don't treat it as a type id. + let recursionToken: number | undefined; + const recursionIdentity = type.checker.getRecursionIdentity(type); + if (recursionIdentity) { + recursionToken = recursionIdentityMap.get(recursionIdentity); + if (!recursionToken) { + recursionToken = recursionIdentityMap.size; + recursionIdentityMap.set(recursionIdentity, recursionToken); } } - fs.writeSync(typesFd, "]\n"); + const descriptor = { + id: type.id, + intrinsicName: (type as any).intrinsicName, + symbolName: symbol?.escapedName && unescapeLeadingUnderscores(symbol.escapedName), + recursionId: recursionToken, + isTuple: objectFlags & ObjectFlags.Tuple ? true : undefined, + unionTypes: (type.flags & TypeFlags.Union) ? (type as UnionType).types?.map(t => t.id) : undefined, + intersectionTypes: (type.flags & TypeFlags.Intersection) ? (type as IntersectionType).types.map(t => t.id) : undefined, + aliasTypeArguments: type.aliasTypeArguments?.map(t => t.id), + keyofType: (type.flags & TypeFlags.Index) ? (type as IndexType).type?.id : undefined, + ...indexedAccessProperties, + ...referenceProperties, + ...conditionalProperties, + ...substitutionProperties, + ...reverseMappedProperties, + ...evolvingArrayProperties, + destructuringPattern: getLocation(type.pattern), + firstDeclaration: getLocation(symbol?.declarations?.[0]), + flags: Debug.formatTypeFlags(type.flags).split("|"), + display, + }; + + fs.writeSync(typesFd, JSON.stringify(descriptor)); + if (i < numTypes - 1) { + fs.writeSync(typesFd, ",\n"); + } + } - fs.closeSync(typesFd); + fs.writeSync(typesFd, "]\n"); - performance.mark("endDumpTypes"); - performance.measure("Dump types", "beginDumpTypes", "endDumpTypes"); - } + fs.closeSync(typesFd); - export function dumpLegend() { - if (!legendPath) { - return; - } + performance.mark("endDumpTypes"); + performance.measure("Dump types", "beginDumpTypes", "endDumpTypes"); + } - fs.writeFileSync(legendPath, JSON.stringify(legend)); + export function dumpLegend() { + if (!legendPath) { + return; } - interface TraceRecord { - configFilePath?: string; - tracePath: string; - typesPath?: string; - } + fs.writeFileSync(legendPath, JSON.stringify(legend)); } - // define after tracingEnabled is initialized - export const startTracing = tracingEnabled.startTracing; - export const dumpTracingLegend = tracingEnabled.dumpLegend; - - export interface TracingNode { - tracingPath?: Path; + interface TraceRecord { + configFilePath?: string; + tracePath: string; + typesPath?: string; } } + +// define after tracingEnabled is initialized +/** @internal */ +export const startTracing = tracingEnabled.startTracing; +/** @internal */ +export const dumpTracingLegend = tracingEnabled.dumpLegend; + +/** @internal */ +export interface TracingNode { + tracingPath?: Path; +} diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index f47bd4f5ed36d..72cc5fde5ad36 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -1,597 +1,616 @@ -/* @internal */ -namespace ts { - function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory { - switch (moduleKind) { - case ModuleKind.ESNext: - case ModuleKind.ES2022: - case ModuleKind.ES2020: - case ModuleKind.ES2015: - return transformECMAScriptModule; - case ModuleKind.System: - return transformSystemModule; - case ModuleKind.Node16: - case ModuleKind.NodeNext: - return transformNodeModule; - default: - return transformModule; - } +import { + addRange, append, Bundle, chainBundle, CompilerOptions, createEmitHelperFactory, CustomTransformer, + CustomTransformerFactory, CustomTransformers, Debug, DiagnosticWithLocation, disposeEmitNodes, EmitFlags, + EmitHelper, EmitHint, EmitHost, EmitOnly, EmitResolver, EmitTransformers, emptyArray, factory, FunctionDeclaration, + getEmitFlags, getEmitModuleKind, getEmitScriptTarget, getJSXTransformEnabled, getParseTreeNode, getSourceFileOfNode, + Identifier, isBundle, isSourceFile, LexicalEnvironmentFlags, map, memoize, ModuleKind, Node, NodeFactory, NodeFlags, + noop, notImplemented, returnUndefined, ScriptTarget, setEmitFlags, some, SourceFile, Statement, SyntaxKind, tracing, + TransformationContext, TransformationResult, transformClassFields, transformDeclarations, transformECMAScriptModule, + Transformer, TransformerFactory, transformES2015, transformES2016, transformES2017, transformES2018, + transformES2019, transformES2020, transformES2021, transformES5, transformESNext, transformGenerators, transformJsx, + transformLegacyDecorators, transformModule, transformNodeModule, transformSystemModule, transformTypeScript, + VariableDeclaration, +} from "./_namespaces/ts"; +import * as performance from "./_namespaces/ts.performance"; + +function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory { + switch (moduleKind) { + case ModuleKind.ESNext: + case ModuleKind.ES2022: + case ModuleKind.ES2020: + case ModuleKind.ES2015: + return transformECMAScriptModule; + case ModuleKind.System: + return transformSystemModule; + case ModuleKind.Node16: + case ModuleKind.NodeNext: + return transformNodeModule; + default: + return transformModule; } +} + +const enum TransformationState { + Uninitialized, + Initialized, + Completed, + Disposed +} + +const enum SyntaxKindFeatureFlags { + Substitution = 1 << 0, + EmitNotifications = 1 << 1, +} + +/** @internal */ +export const noTransformers: EmitTransformers = { scriptTransformers: emptyArray, declarationTransformers: emptyArray }; + +/** @internal */ +export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnly?: boolean | EmitOnly): EmitTransformers { + return { + scriptTransformers: getScriptTransformers(compilerOptions, customTransformers, emitOnly), + declarationTransformers: getDeclarationTransformers(customTransformers), + }; +} - const enum TransformationState { - Uninitialized, - Initialized, - Completed, - Disposed +function getScriptTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnly?: boolean | EmitOnly) { + if (emitOnly) return emptyArray; + + const languageVersion = getEmitScriptTarget(compilerOptions); + const moduleKind = getEmitModuleKind(compilerOptions); + const transformers: TransformerFactory[] = []; + + addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory)); + + transformers.push(transformTypeScript); + transformers.push(transformLegacyDecorators); + transformers.push(transformClassFields); + + if (getJSXTransformEnabled(compilerOptions)) { + transformers.push(transformJsx); } - const enum SyntaxKindFeatureFlags { - Substitution = 1 << 0, - EmitNotifications = 1 << 1, + if (languageVersion < ScriptTarget.ESNext) { + transformers.push(transformESNext); } - export const noTransformers: EmitTransformers = { scriptTransformers: emptyArray, declarationTransformers: emptyArray }; + if (languageVersion < ScriptTarget.ES2021) { + transformers.push(transformES2021); + } + + if (languageVersion < ScriptTarget.ES2020) { + transformers.push(transformES2020); + } + + if (languageVersion < ScriptTarget.ES2019) { + transformers.push(transformES2019); + } - export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnly?: boolean | EmitOnly): EmitTransformers { - return { - scriptTransformers: getScriptTransformers(compilerOptions, customTransformers, emitOnly), - declarationTransformers: getDeclarationTransformers(customTransformers), - }; + if (languageVersion < ScriptTarget.ES2018) { + transformers.push(transformES2018); } - function getScriptTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnly?: boolean | EmitOnly) { - if (emitOnly) return emptyArray; + if (languageVersion < ScriptTarget.ES2017) { + transformers.push(transformES2017); + } - const languageVersion = getEmitScriptTarget(compilerOptions); - const moduleKind = getEmitModuleKind(compilerOptions); - const transformers: TransformerFactory[] = []; + if (languageVersion < ScriptTarget.ES2016) { + transformers.push(transformES2016); + } - addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory)); + if (languageVersion < ScriptTarget.ES2015) { + transformers.push(transformES2015); + transformers.push(transformGenerators); + } - transformers.push(transformTypeScript); - transformers.push(transformLegacyDecorators); - transformers.push(transformClassFields); + transformers.push(getModuleTransformer(moduleKind)); - if (getJSXTransformEnabled(compilerOptions)) { - transformers.push(transformJsx); - } + // The ES5 transformer is last so that it can substitute expressions like `exports.default` + // for ES3. + if (languageVersion < ScriptTarget.ES5) { + transformers.push(transformES5); + } - if (languageVersion < ScriptTarget.ESNext) { - transformers.push(transformESNext); - } + addRange(transformers, customTransformers && map(customTransformers.after, wrapScriptTransformerFactory)); + return transformers; +} - if (languageVersion < ScriptTarget.ES2021) { - transformers.push(transformES2021); - } +function getDeclarationTransformers(customTransformers?: CustomTransformers) { + const transformers: TransformerFactory[] = []; + transformers.push(transformDeclarations); + addRange(transformers, customTransformers && map(customTransformers.afterDeclarations, wrapDeclarationTransformerFactory)); + return transformers; +} - if (languageVersion < ScriptTarget.ES2020) { - transformers.push(transformES2020); - } +/** + * Wrap a custom script or declaration transformer object in a `Transformer` callback with fallback support for transforming bundles. + */ +function wrapCustomTransformer(transformer: CustomTransformer): Transformer { + return node => isBundle(node) ? transformer.transformBundle(node) : transformer.transformSourceFile(node); +} - if (languageVersion < ScriptTarget.ES2019) { - transformers.push(transformES2019); - } +/** + * Wrap a transformer factory that may return a custom script or declaration transformer object. + */ +function wrapCustomTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory, handleDefault: (context: TransformationContext, tx: Transformer) => Transformer): TransformerFactory { + return context => { + const customTransformer = transformer(context); + return typeof customTransformer === "function" + ? handleDefault(context, customTransformer) + : wrapCustomTransformer(customTransformer); + }; +} - if (languageVersion < ScriptTarget.ES2018) { - transformers.push(transformES2018); - } +function wrapScriptTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { + return wrapCustomTransformerFactory(transformer, chainBundle); +} - if (languageVersion < ScriptTarget.ES2017) { - transformers.push(transformES2017); - } +function wrapDeclarationTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { + return wrapCustomTransformerFactory(transformer, (_, node) => node); +} - if (languageVersion < ScriptTarget.ES2016) { - transformers.push(transformES2016); - } +/** @internal */ +export function noEmitSubstitution(_hint: EmitHint, node: Node) { + return node; +} + +/** @internal */ +export function noEmitNotification(hint: EmitHint, node: Node, callback: (hint: EmitHint, node: Node) => void) { + callback(hint, node); +} - if (languageVersion < ScriptTarget.ES2015) { - transformers.push(transformES2015); - transformers.push(transformGenerators); +/** + * Transforms an array of SourceFiles by passing them through each transformer. + * + * @param resolver The emit resolver provided by the checker. + * @param host The emit host object used to interact with the file system. + * @param options Compiler options to surface in the `TransformationContext`. + * @param nodes An array of nodes to transform. + * @param transforms An array of `TransformerFactory` callbacks. + * @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files. + * + * @internal + */ +export function transformNodes(resolver: EmitResolver | undefined, host: EmitHost | undefined, factory: NodeFactory, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory[], allowDtsFiles: boolean): TransformationResult { + const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); + let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; + let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; + let lexicalEnvironmentStatements: Statement[]; + let lexicalEnvironmentFlags = LexicalEnvironmentFlags.None; + let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; + let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; + let lexicalEnvironmentStatementsStack: Statement[][] = []; + let lexicalEnvironmentFlagsStack: LexicalEnvironmentFlags[] = []; + let lexicalEnvironmentStackOffset = 0; + let lexicalEnvironmentSuspended = false; + let blockScopedVariableDeclarationsStack: Identifier[][] = []; + let blockScopeStackOffset = 0; + let blockScopedVariableDeclarations: Identifier[]; + let emitHelpers: EmitHelper[] | undefined; + let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution; + let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification; + let state = TransformationState.Uninitialized; + const diagnostics: DiagnosticWithLocation[] = []; + + // The transformation context is provided to each transformer as part of transformer + // initialization. + const context: TransformationContext = { + factory, + getCompilerOptions: () => options, + getEmitResolver: () => resolver!, // TODO: GH#18217 + getEmitHost: () => host!, // TODO: GH#18217 + getEmitHelperFactory: memoize(() => createEmitHelperFactory(context)), + startLexicalEnvironment, + suspendLexicalEnvironment, + resumeLexicalEnvironment, + endLexicalEnvironment, + setLexicalEnvironmentFlags, + getLexicalEnvironmentFlags, + hoistVariableDeclaration, + hoistFunctionDeclaration, + addInitializationStatement, + startBlockScope, + endBlockScope, + addBlockScopedVariable, + requestEmitHelper, + readEmitHelpers, + enableSubstitution, + enableEmitNotification, + isSubstitutionEnabled, + isEmitNotificationEnabled, + get onSubstituteNode() { return onSubstituteNode; }, + set onSubstituteNode(value) { + Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); + Debug.assert(value !== undefined, "Value must not be 'undefined'"); + onSubstituteNode = value; + }, + get onEmitNode() { return onEmitNode; }, + set onEmitNode(value) { + Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); + Debug.assert(value !== undefined, "Value must not be 'undefined'"); + onEmitNode = value; + }, + addDiagnostic(diag) { + diagnostics.push(diag); } + }; + + // Ensure the parse tree is clean before applying transformations + for (const node of nodes) { + disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); + } - transformers.push(getModuleTransformer(moduleKind)); + performance.mark("beforeTransform"); - // The ES5 transformer is last so that it can substitute expressions like `exports.default` - // for ES3. - if (languageVersion < ScriptTarget.ES5) { - transformers.push(transformES5); + // Chain together and initialize each transformer. + const transformersWithContext = transformers.map(t => t(context)); + const transformation = (node: T): T => { + for (const transform of transformersWithContext) { + node = transform(node); } + return node; + }; - addRange(transformers, customTransformers && map(customTransformers.after, wrapScriptTransformerFactory)); - return transformers; + // prevent modification of transformation hooks. + state = TransformationState.Initialized; + + // Transform each node. + const transformed: T[] = []; + for (const node of nodes) { + tracing?.push(tracing.Phase.Emit, "transformNodes", node.kind === SyntaxKind.SourceFile ? { path: (node as any as SourceFile).path } : { kind: node.kind, pos: node.pos, end: node.end }); + transformed.push((allowDtsFiles ? transformation : transformRoot)(node)); + tracing?.pop(); } - function getDeclarationTransformers(customTransformers?: CustomTransformers) { - const transformers: TransformerFactory[] = []; - transformers.push(transformDeclarations); - addRange(transformers, customTransformers && map(customTransformers.afterDeclarations, wrapDeclarationTransformerFactory)); - return transformers; + // prevent modification of the lexical environment. + state = TransformationState.Completed; + + performance.mark("afterTransform"); + performance.measure("transformTime", "beforeTransform", "afterTransform"); + + return { + transformed, + substituteNode, + emitNodeWithNotification, + isEmitNotificationEnabled, + dispose, + diagnostics + }; + + function transformRoot(node: T) { + return node && (!isSourceFile(node) || !node.isDeclarationFile) ? transformation(node) : node; } /** - * Wrap a custom script or declaration transformer object in a `Transformer` callback with fallback support for transforming bundles. + * Enables expression substitutions in the pretty printer for the provided SyntaxKind. */ - function wrapCustomTransformer(transformer: CustomTransformer): Transformer { - return node => isBundle(node) ? transformer.transformBundle(node) : transformer.transformSourceFile(node); + function enableSubstitution(kind: SyntaxKind) { + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution; } /** - * Wrap a transformer factory that may return a custom script or declaration transformer object. + * Determines whether expression substitutions are enabled for the provided node. */ - function wrapCustomTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory, handleDefault: (context: TransformationContext, tx: Transformer) => Transformer): TransformerFactory { - return context => { - const customTransformer = transformer(context); - return typeof customTransformer === "function" - ? handleDefault(context, customTransformer) - : wrapCustomTransformer(customTransformer); - }; - } - - function wrapScriptTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { - return wrapCustomTransformerFactory(transformer, chainBundle); + function isSubstitutionEnabled(node: Node) { + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0 + && (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0; } - function wrapDeclarationTransformerFactory(transformer: TransformerFactory | CustomTransformerFactory): TransformerFactory { - return wrapCustomTransformerFactory(transformer, (_, node) => node); + /** + * Emits a node with possible substitution. + * + * @param hint A hint as to the intended usage of the node. + * @param node The node to emit. + * @param emitCallback The callback used to emit the node or its substitute. + */ + function substituteNode(hint: EmitHint, node: Node) { + Debug.assert(state < TransformationState.Disposed, "Cannot substitute a node after the result is disposed."); + return node && isSubstitutionEnabled(node) && onSubstituteNode(hint, node) || node; } - export function noEmitSubstitution(_hint: EmitHint, node: Node) { - return node; + /** + * Enables before/after emit notifications in the pretty printer for the provided SyntaxKind. + */ + function enableEmitNotification(kind: SyntaxKind) { + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; } - export function noEmitNotification(hint: EmitHint, node: Node, callback: (hint: EmitHint, node: Node) => void) { - callback(hint, node); + /** + * Determines whether before/after emit notifications should be raised in the pretty + * printer when it emits a node. + */ + function isEmitNotificationEnabled(node: Node) { + return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 + || (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0; } /** - * Transforms an array of SourceFiles by passing them through each transformer. + * Emits a node with possible emit notification. * - * @param resolver The emit resolver provided by the checker. - * @param host The emit host object used to interact with the file system. - * @param options Compiler options to surface in the `TransformationContext`. - * @param nodes An array of nodes to transform. - * @param transforms An array of `TransformerFactory` callbacks. - * @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files. + * @param hint A hint as to the intended usage of the node. + * @param node The node to emit. + * @param emitCallback The callback used to emit the node. */ - export function transformNodes(resolver: EmitResolver | undefined, host: EmitHost | undefined, factory: NodeFactory, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory[], allowDtsFiles: boolean): TransformationResult { - const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); - let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; - let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; - let lexicalEnvironmentStatements: Statement[]; - let lexicalEnvironmentFlags = LexicalEnvironmentFlags.None; - let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; - let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; - let lexicalEnvironmentStatementsStack: Statement[][] = []; - let lexicalEnvironmentFlagsStack: LexicalEnvironmentFlags[] = []; - let lexicalEnvironmentStackOffset = 0; - let lexicalEnvironmentSuspended = false; - let blockScopedVariableDeclarationsStack: Identifier[][] = []; - let blockScopeStackOffset = 0; - let blockScopedVariableDeclarations: Identifier[]; - let emitHelpers: EmitHelper[] | undefined; - let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution; - let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification; - let state = TransformationState.Uninitialized; - const diagnostics: DiagnosticWithLocation[] = []; - - // The transformation context is provided to each transformer as part of transformer - // initialization. - const context: TransformationContext = { - factory, - getCompilerOptions: () => options, - getEmitResolver: () => resolver!, // TODO: GH#18217 - getEmitHost: () => host!, // TODO: GH#18217 - getEmitHelperFactory: memoize(() => createEmitHelperFactory(context)), - startLexicalEnvironment, - suspendLexicalEnvironment, - resumeLexicalEnvironment, - endLexicalEnvironment, - setLexicalEnvironmentFlags, - getLexicalEnvironmentFlags, - hoistVariableDeclaration, - hoistFunctionDeclaration, - addInitializationStatement, - startBlockScope, - endBlockScope, - addBlockScopedVariable, - requestEmitHelper, - readEmitHelpers, - enableSubstitution, - enableEmitNotification, - isSubstitutionEnabled, - isEmitNotificationEnabled, - get onSubstituteNode() { return onSubstituteNode; }, - set onSubstituteNode(value) { - Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); - Debug.assert(value !== undefined, "Value must not be 'undefined'"); - onSubstituteNode = value; - }, - get onEmitNode() { return onEmitNode; }, - set onEmitNode(value) { - Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed."); - Debug.assert(value !== undefined, "Value must not be 'undefined'"); - onEmitNode = value; - }, - addDiagnostic(diag) { - diagnostics.push(diag); + function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { + Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); + if (node) { + // TODO: Remove check and unconditionally use onEmitNode when API is breakingly changed + // (see https://github.com/microsoft/TypeScript/pull/36248/files/5062623f39120171b98870c71344b3242eb03d23#r369766739) + if (isEmitNotificationEnabled(node)) { + onEmitNode(hint, node, emitCallback); } - }; - - // Ensure the parse tree is clean before applying transformations - for (const node of nodes) { - disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); - } - - performance.mark("beforeTransform"); - - // Chain together and initialize each transformer. - const transformersWithContext = transformers.map(t => t(context)); - const transformation = (node: T): T => { - for (const transform of transformersWithContext) { - node = transform(node); + else { + emitCallback(hint, node); } - return node; - }; - - // prevent modification of transformation hooks. - state = TransformationState.Initialized; - - // Transform each node. - const transformed: T[] = []; - for (const node of nodes) { - tracing?.push(tracing.Phase.Emit, "transformNodes", node.kind === SyntaxKind.SourceFile ? { path: (node as any as SourceFile).path } : { kind: node.kind, pos: node.pos, end: node.end }); - transformed.push((allowDtsFiles ? transformation : transformRoot)(node)); - tracing?.pop(); - } - - // prevent modification of the lexical environment. - state = TransformationState.Completed; - - performance.mark("afterTransform"); - performance.measure("transformTime", "beforeTransform", "afterTransform"); - - return { - transformed, - substituteNode, - emitNodeWithNotification, - isEmitNotificationEnabled, - dispose, - diagnostics - }; - - function transformRoot(node: T) { - return node && (!isSourceFile(node) || !node.isDeclarationFile) ? transformation(node) : node; - } - - /** - * Enables expression substitutions in the pretty printer for the provided SyntaxKind. - */ - function enableSubstitution(kind: SyntaxKind) { - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution; - } - - /** - * Determines whether expression substitutions are enabled for the provided node. - */ - function isSubstitutionEnabled(node: Node) { - return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0 - && (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0; } + } - /** - * Emits a node with possible substitution. - * - * @param hint A hint as to the intended usage of the node. - * @param node The node to emit. - * @param emitCallback The callback used to emit the node or its substitute. - */ - function substituteNode(hint: EmitHint, node: Node) { - Debug.assert(state < TransformationState.Disposed, "Cannot substitute a node after the result is disposed."); - return node && isSubstitutionEnabled(node) && onSubstituteNode(hint, node) || node; + /** + * Records a hoisted variable declaration for the provided name within a lexical environment. + */ + function hoistVariableDeclaration(name: Identifier): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + const decl = setEmitFlags(factory.createVariableDeclaration(name), EmitFlags.NoNestedSourceMaps); + if (!lexicalEnvironmentVariableDeclarations) { + lexicalEnvironmentVariableDeclarations = [decl]; } - - /** - * Enables before/after emit notifications in the pretty printer for the provided SyntaxKind. - */ - function enableEmitNotification(kind: SyntaxKind) { - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications; + else { + lexicalEnvironmentVariableDeclarations.push(decl); } - - /** - * Determines whether before/after emit notifications should be raised in the pretty - * printer when it emits a node. - */ - function isEmitNotificationEnabled(node: Node) { - return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0 - || (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0; + if (lexicalEnvironmentFlags & LexicalEnvironmentFlags.InParameters) { + lexicalEnvironmentFlags |= LexicalEnvironmentFlags.VariablesHoistedInParameters; } + } - /** - * Emits a node with possible emit notification. - * - * @param hint A hint as to the intended usage of the node. - * @param node The node to emit. - * @param emitCallback The callback used to emit the node. - */ - function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { - Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed."); - if (node) { - // TODO: Remove check and unconditionally use onEmitNode when API is breakingly changed - // (see https://github.com/microsoft/TypeScript/pull/36248/files/5062623f39120171b98870c71344b3242eb03d23#r369766739) - if (isEmitNotificationEnabled(node)) { - onEmitNode(hint, node, emitCallback); - } - else { - emitCallback(hint, node); - } - } + /** + * Records a hoisted function declaration within a lexical environment. + */ + function hoistFunctionDeclaration(func: FunctionDeclaration): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + setEmitFlags(func, EmitFlags.CustomPrologue); + if (!lexicalEnvironmentFunctionDeclarations) { + lexicalEnvironmentFunctionDeclarations = [func]; } - - /** - * Records a hoisted variable declaration for the provided name within a lexical environment. - */ - function hoistVariableDeclaration(name: Identifier): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - const decl = setEmitFlags(factory.createVariableDeclaration(name), EmitFlags.NoNestedSourceMaps); - if (!lexicalEnvironmentVariableDeclarations) { - lexicalEnvironmentVariableDeclarations = [decl]; - } - else { - lexicalEnvironmentVariableDeclarations.push(decl); - } - if (lexicalEnvironmentFlags & LexicalEnvironmentFlags.InParameters) { - lexicalEnvironmentFlags |= LexicalEnvironmentFlags.VariablesHoistedInParameters; - } + else { + lexicalEnvironmentFunctionDeclarations.push(func); } + } - /** - * Records a hoisted function declaration within a lexical environment. - */ - function hoistFunctionDeclaration(func: FunctionDeclaration): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - setEmitFlags(func, EmitFlags.CustomPrologue); - if (!lexicalEnvironmentFunctionDeclarations) { - lexicalEnvironmentFunctionDeclarations = [func]; - } - else { - lexicalEnvironmentFunctionDeclarations.push(func); - } + /** + * Adds an initialization statement to the top of the lexical environment. + */ + function addInitializationStatement(node: Statement): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + setEmitFlags(node, EmitFlags.CustomPrologue); + if (!lexicalEnvironmentStatements) { + lexicalEnvironmentStatements = [node]; } - - /** - * Adds an initialization statement to the top of the lexical environment. - */ - function addInitializationStatement(node: Statement): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - setEmitFlags(node, EmitFlags.CustomPrologue); - if (!lexicalEnvironmentStatements) { - lexicalEnvironmentStatements = [node]; - } - else { - lexicalEnvironmentStatements.push(node); - } + else { + lexicalEnvironmentStatements.push(node); } + } - /** - * Starts a new lexical environment. Any existing hoisted variable or function declarations - * are pushed onto a stack, and the related storage variables are reset. - */ - function startLexicalEnvironment(): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); - - // Save the current lexical environment. Rather than resizing the array we adjust the - // stack size variable. This allows us to reuse existing array slots we've - // already allocated between transformations to avoid allocation and GC overhead during - // transformation. - lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations; - lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations; - lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentStatements; - lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFlags; - lexicalEnvironmentStackOffset++; - lexicalEnvironmentVariableDeclarations = undefined!; - lexicalEnvironmentFunctionDeclarations = undefined!; - lexicalEnvironmentStatements = undefined!; - lexicalEnvironmentFlags = LexicalEnvironmentFlags.None; - } + /** + * Starts a new lexical environment. Any existing hoisted variable or function declarations + * are pushed onto a stack, and the related storage variables are reset. + */ + function startLexicalEnvironment(): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); + + // Save the current lexical environment. Rather than resizing the array we adjust the + // stack size variable. This allows us to reuse existing array slots we've + // already allocated between transformations to avoid allocation and GC overhead during + // transformation. + lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations; + lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations; + lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentStatements; + lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFlags; + lexicalEnvironmentStackOffset++; + lexicalEnvironmentVariableDeclarations = undefined!; + lexicalEnvironmentFunctionDeclarations = undefined!; + lexicalEnvironmentStatements = undefined!; + lexicalEnvironmentFlags = LexicalEnvironmentFlags.None; + } - /** Suspends the current lexical environment, usually after visiting a parameter list. */ - function suspendLexicalEnvironment(): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended."); - lexicalEnvironmentSuspended = true; - } + /** Suspends the current lexical environment, usually after visiting a parameter list. */ + function suspendLexicalEnvironment(): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended."); + lexicalEnvironmentSuspended = true; + } - /** Resumes a suspended lexical environment, usually before visiting a function body. */ - function resumeLexicalEnvironment(): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended."); - lexicalEnvironmentSuspended = false; - } + /** Resumes a suspended lexical environment, usually before visiting a function body. */ + function resumeLexicalEnvironment(): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended."); + lexicalEnvironmentSuspended = false; + } - /** - * Ends a lexical environment. The previous set of hoisted declarations are restored and - * any hoisted declarations added in this environment are returned. - */ - function endLexicalEnvironment(): Statement[] | undefined { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); - Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); - - let statements: Statement[] | undefined; - if (lexicalEnvironmentVariableDeclarations || - lexicalEnvironmentFunctionDeclarations || - lexicalEnvironmentStatements) { - if (lexicalEnvironmentFunctionDeclarations) { - statements = [...lexicalEnvironmentFunctionDeclarations]; - } + /** + * Ends a lexical environment. The previous set of hoisted declarations are restored and + * any hoisted declarations added in this environment are returned. + */ + function endLexicalEnvironment(): Statement[] | undefined { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); + + let statements: Statement[] | undefined; + if (lexicalEnvironmentVariableDeclarations || + lexicalEnvironmentFunctionDeclarations || + lexicalEnvironmentStatements) { + if (lexicalEnvironmentFunctionDeclarations) { + statements = [...lexicalEnvironmentFunctionDeclarations]; + } - if (lexicalEnvironmentVariableDeclarations) { - const statement = factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList(lexicalEnvironmentVariableDeclarations) - ); + if (lexicalEnvironmentVariableDeclarations) { + const statement = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList(lexicalEnvironmentVariableDeclarations) + ); - setEmitFlags(statement, EmitFlags.CustomPrologue); + setEmitFlags(statement, EmitFlags.CustomPrologue); - if (!statements) { - statements = [statement]; - } - else { - statements.push(statement); - } + if (!statements) { + statements = [statement]; } - - if (lexicalEnvironmentStatements) { - if (!statements) { - statements = [...lexicalEnvironmentStatements]; - } - else { - statements = [...statements, ...lexicalEnvironmentStatements]; - } + else { + statements.push(statement); } } - // Restore the previous lexical environment. - lexicalEnvironmentStackOffset--; - lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; - lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; - lexicalEnvironmentStatements = lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset]; - lexicalEnvironmentFlags = lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset]; - if (lexicalEnvironmentStackOffset === 0) { - lexicalEnvironmentVariableDeclarationsStack = []; - lexicalEnvironmentFunctionDeclarationsStack = []; - lexicalEnvironmentStatementsStack = []; - lexicalEnvironmentFlagsStack = []; + if (lexicalEnvironmentStatements) { + if (!statements) { + statements = [...lexicalEnvironmentStatements]; + } + else { + statements = [...statements, ...lexicalEnvironmentStatements]; + } } - return statements; } - function setLexicalEnvironmentFlags(flags: LexicalEnvironmentFlags, value: boolean): void { - lexicalEnvironmentFlags = value ? - lexicalEnvironmentFlags | flags : - lexicalEnvironmentFlags & ~flags; + // Restore the previous lexical environment. + lexicalEnvironmentStackOffset--; + lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentStatements = lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentFlags = lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset]; + if (lexicalEnvironmentStackOffset === 0) { + lexicalEnvironmentVariableDeclarationsStack = []; + lexicalEnvironmentFunctionDeclarationsStack = []; + lexicalEnvironmentStatementsStack = []; + lexicalEnvironmentFlagsStack = []; } + return statements; + } - function getLexicalEnvironmentFlags(): LexicalEnvironmentFlags { - return lexicalEnvironmentFlags; - } + function setLexicalEnvironmentFlags(flags: LexicalEnvironmentFlags, value: boolean): void { + lexicalEnvironmentFlags = value ? + lexicalEnvironmentFlags | flags : + lexicalEnvironmentFlags & ~flags; + } - /** - * Starts a block scope. Any existing block hoisted variables are pushed onto the stack and the related storage variables are reset. - */ - function startBlockScope() { - Debug.assert(state > TransformationState.Uninitialized, "Cannot start a block scope during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot start a block scope after transformation has completed."); - blockScopedVariableDeclarationsStack[blockScopeStackOffset] = blockScopedVariableDeclarations; - blockScopeStackOffset++; - blockScopedVariableDeclarations = undefined!; - } + function getLexicalEnvironmentFlags(): LexicalEnvironmentFlags { + return lexicalEnvironmentFlags; + } - /** - * Ends a block scope. The previous set of block hoisted variables are restored. Any hoisted declarations are returned. - */ - function endBlockScope() { - Debug.assert(state > TransformationState.Uninitialized, "Cannot end a block scope during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot end a block scope after transformation has completed."); - const statements: Statement[] | undefined = some(blockScopedVariableDeclarations) ? - [ - factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList( - blockScopedVariableDeclarations.map(identifier => factory.createVariableDeclaration(identifier)), - NodeFlags.Let - ) + /** + * Starts a block scope. Any existing block hoisted variables are pushed onto the stack and the related storage variables are reset. + */ + function startBlockScope() { + Debug.assert(state > TransformationState.Uninitialized, "Cannot start a block scope during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot start a block scope after transformation has completed."); + blockScopedVariableDeclarationsStack[blockScopeStackOffset] = blockScopedVariableDeclarations; + blockScopeStackOffset++; + blockScopedVariableDeclarations = undefined!; + } + + /** + * Ends a block scope. The previous set of block hoisted variables are restored. Any hoisted declarations are returned. + */ + function endBlockScope() { + Debug.assert(state > TransformationState.Uninitialized, "Cannot end a block scope during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot end a block scope after transformation has completed."); + const statements: Statement[] | undefined = some(blockScopedVariableDeclarations) ? + [ + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + blockScopedVariableDeclarations.map(identifier => factory.createVariableDeclaration(identifier)), + NodeFlags.Let ) - ] : undefined; - blockScopeStackOffset--; - blockScopedVariableDeclarations = blockScopedVariableDeclarationsStack[blockScopeStackOffset]; - if (blockScopeStackOffset === 0) { - blockScopedVariableDeclarationsStack = []; + ) + ] : undefined; + blockScopeStackOffset--; + blockScopedVariableDeclarations = blockScopedVariableDeclarationsStack[blockScopeStackOffset]; + if (blockScopeStackOffset === 0) { + blockScopedVariableDeclarationsStack = []; + } + return statements; + } + + function addBlockScopedVariable(name: Identifier): void { + Debug.assert(blockScopeStackOffset > 0, "Cannot add a block scoped variable outside of an iteration body."); + (blockScopedVariableDeclarations || (blockScopedVariableDeclarations = [])).push(name); + } + + function requestEmitHelper(helper: EmitHelper): void { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); + if (helper.dependencies) { + for (const h of helper.dependencies) { + requestEmitHelper(h); } - return statements; } + emitHelpers = append(emitHelpers, helper); + } - function addBlockScopedVariable(name: Identifier): void { - Debug.assert(blockScopeStackOffset > 0, "Cannot add a block scoped variable outside of an iteration body."); - (blockScopedVariableDeclarations || (blockScopedVariableDeclarations = [])).push(name); - } + function readEmitHelpers(): EmitHelper[] | undefined { + Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); + Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); + const helpers = emitHelpers; + emitHelpers = undefined; + return helpers; + } - function requestEmitHelper(helper: EmitHelper): void { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); - if (helper.dependencies) { - for (const h of helper.dependencies) { - requestEmitHelper(h); - } + function dispose() { + if (state < TransformationState.Disposed) { + // Clean up emit nodes on parse tree + for (const node of nodes) { + disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); } - emitHelpers = append(emitHelpers, helper); - } - function readEmitHelpers(): EmitHelper[] | undefined { - Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization."); - Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed."); - const helpers = emitHelpers; + // Release references to external entries for GC purposes. + lexicalEnvironmentVariableDeclarations = undefined!; + lexicalEnvironmentVariableDeclarationsStack = undefined!; + lexicalEnvironmentFunctionDeclarations = undefined!; + lexicalEnvironmentFunctionDeclarationsStack = undefined!; + onSubstituteNode = undefined!; + onEmitNode = undefined!; emitHelpers = undefined; - return helpers; - } - - function dispose() { - if (state < TransformationState.Disposed) { - // Clean up emit nodes on parse tree - for (const node of nodes) { - disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node))); - } - // Release references to external entries for GC purposes. - lexicalEnvironmentVariableDeclarations = undefined!; - lexicalEnvironmentVariableDeclarationsStack = undefined!; - lexicalEnvironmentFunctionDeclarations = undefined!; - lexicalEnvironmentFunctionDeclarationsStack = undefined!; - onSubstituteNode = undefined!; - onEmitNode = undefined!; - emitHelpers = undefined; - - // Prevent further use of the transformation result. - state = TransformationState.Disposed; - } + // Prevent further use of the transformation result. + state = TransformationState.Disposed; } } - - export const nullTransformationContext: TransformationContext = { - factory: factory, // eslint-disable-line object-shorthand - getCompilerOptions: () => ({}), - getEmitResolver: notImplemented, - getEmitHost: notImplemented, - getEmitHelperFactory: notImplemented, - startLexicalEnvironment: noop, - resumeLexicalEnvironment: noop, - suspendLexicalEnvironment: noop, - endLexicalEnvironment: returnUndefined, - setLexicalEnvironmentFlags: noop, - getLexicalEnvironmentFlags: () => 0, - hoistVariableDeclaration: noop, - hoistFunctionDeclaration: noop, - addInitializationStatement: noop, - startBlockScope: noop, - endBlockScope: returnUndefined, - addBlockScopedVariable: noop, - requestEmitHelper: noop, - readEmitHelpers: notImplemented, - enableSubstitution: noop, - enableEmitNotification: noop, - isSubstitutionEnabled: notImplemented, - isEmitNotificationEnabled: notImplemented, - onSubstituteNode: noEmitSubstitution, - onEmitNode: noEmitNotification, - addDiagnostic: noop, - }; } + +/** @internal */ +export const nullTransformationContext: TransformationContext = { + factory: factory, // eslint-disable-line object-shorthand + getCompilerOptions: () => ({}), + getEmitResolver: notImplemented, + getEmitHost: notImplemented, + getEmitHelperFactory: notImplemented, + startLexicalEnvironment: noop, + resumeLexicalEnvironment: noop, + suspendLexicalEnvironment: noop, + endLexicalEnvironment: returnUndefined, + setLexicalEnvironmentFlags: noop, + getLexicalEnvironmentFlags: () => 0, + hoistVariableDeclaration: noop, + hoistFunctionDeclaration: noop, + addInitializationStatement: noop, + startBlockScope: noop, + endBlockScope: returnUndefined, + addBlockScopedVariable: noop, + requestEmitHelper: noop, + readEmitHelpers: notImplemented, + enableSubstitution: noop, + enableEmitNotification: noop, + isSubstitutionEnabled: notImplemented, + isEmitNotificationEnabled: notImplemented, + onSubstituteNode: noEmitSubstitution, + onEmitNode: noEmitNotification, + addDiagnostic: noop, +}; diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts index 3ae7696d7c96e..28be1f5f87a43 100644 --- a/src/compiler/transformers/classFields.ts +++ b/src/compiler/transformers/classFields.ts @@ -1,747 +1,838 @@ -/*@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, - /** - * Enables substitutions for class expressions with static fields - * which have initializers that reference the 'this' or 'super'. - */ - ClassStaticThisOrSuperReference = 1 << 1, - } - - export const enum PrivateIdentifierKind { - Field = "f", - Method = "m", - Accessor = "a" - } - - interface PrivateIdentifierInfoBase { - /** - * brandCheckIdentifier can contain: - * - For instance field: The WeakMap that will be the storage for the field. - * - For instance methods or accessors: The WeakSet that will be used for brand checking. - * - For static members: The constructor that will be used for brand checking. - */ - brandCheckIdentifier: Identifier; - /** - * Stores if the identifier is static or not - */ - isStatic: boolean; - /** - * Stores if the identifier declaration is valid or not. Reserved names (e.g. #constructor) - * or duplicate identifiers are considered invalid. - */ - isValid: boolean; - } - - interface PrivateIdentifierAccessorInfo extends PrivateIdentifierInfoBase { - kind: PrivateIdentifierKind.Accessor; - /** - * Identifier for a variable that will contain the private get accessor implementation, if any. - */ - getterName?: Identifier; - /** - * Identifier for a variable that will contain the private set accessor implementation, if any. - */ - setterName?: Identifier; - } - - interface PrivateIdentifierMethodInfo extends PrivateIdentifierInfoBase { - kind: PrivateIdentifierKind.Method; - /** - * Identifier for a variable that will contain the private method implementation. - */ - methodName: Identifier; - } - - interface PrivateIdentifierInstanceFieldInfo extends PrivateIdentifierInfoBase { - kind: PrivateIdentifierKind.Field; - isStatic: false; - /** - * Defined for ease of access when in a union with PrivateIdentifierStaticFieldInfo. - */ - variableName: undefined; - } - - interface PrivateIdentifierStaticFieldInfo extends PrivateIdentifierInfoBase { - kind: PrivateIdentifierKind.Field; - isStatic: true; - /** - * Contains the variable that will serve as the storage for the field. - */ - variableName: Identifier; - } - - type PrivateIdentifierInfo = - | PrivateIdentifierMethodInfo - | PrivateIdentifierInstanceFieldInfo - | PrivateIdentifierStaticFieldInfo - | PrivateIdentifierAccessorInfo; - - interface PrivateIdentifierEnvironment { - /** - * Used for prefixing generated variable names. - */ - className?: Identifier; - /** - * Used for brand check on private methods. - */ - weakSetName?: Identifier; - /** - * A mapping of private names to information needed for transformation. - */ - identifiers?: UnderscoreEscapedMap; - /** - * A mapping of generated private names to information needed for transformation. - */ - generatedIdentifiers?: ESMap; - } - - interface ClassLexicalEnvironment { - facts: ClassFacts; - /** - * Used for brand checks on static members, and `this` references in static initializers - */ - classConstructor: Identifier | undefined; - /** - * Used for `super` references in static initializers. - */ - superClassReference: Identifier | undefined; - privateIdentifierEnvironment: PrivateIdentifierEnvironment | undefined; - } - - const enum ClassFacts { - None = 0, - ClassWasDecorated = 1 << 0, - NeedsClassConstructorReference = 1 << 1, - NeedsClassSuperReference = 1 << 2, - NeedsSubstitutionForThisInClassStaticField = 1 << 3, - } +import { + __String, AccessorDeclaration, addEmitFlags, addEmitHelpers, addRange, append, AssignmentOperator, + AssignmentPattern, AutoAccessorPropertyDeclaration, BinaryExpression, BindingOrAssignmentElement, CallExpression, + chainBundle, ClassDeclaration, ClassElement, ClassExpression, ClassLikeDeclaration, + classOrConstructorParameterIsDecorated, ClassStaticBlockDeclaration, compact, ComputedPropertyName, + ConstructorDeclaration, createAccessorPropertyBackingField, createAccessorPropertyGetRedirector, + createAccessorPropertySetRedirector, createMemberAccessForPropertyName, Debug, ElementAccessExpression, EmitFlags, + EmitHint, ESMap, expandPreOrPostfixIncrementOrDecrementExpression, Expression, ExpressionStatement, + ExpressionWithTypeArguments, factory, filter, findSuperStatementIndex, ForStatement, GeneratedIdentifier, + GeneratedIdentifierFlags, GeneratedNamePart, GeneratedPrivateIdentifier, GetAccessorDeclaration, getCommentRange, + getEffectiveBaseTypeNode, getEmitFlags, getEmitScriptTarget, getInitializerOfBindingOrAssignmentElement, + getNameOfDeclaration, getNodeForGeneratedName, getNonAssignmentOperatorForCompoundAssignment, getOriginalNode, + getOriginalNodeId, getProperties, getSourceMapRange, getStaticPropertiesAndClassStaticBlock, + getTargetOfBindingOrAssignmentElement, getUseDefineForClassFields, hasAbstractModifier, hasAccessorModifier, + hasDecorators, hasStaticModifier, hasSyntacticModifier, Identifier, InKeyword, isAccessorModifier, + isArrayLiteralExpression, isArrowFunction, isAssignmentExpression, isAutoAccessorPropertyDeclaration, isCallChain, + isClassDeclaration, isClassElement, isClassStaticBlockDeclaration, isCompoundAssignment, isComputedPropertyName, + isConstructorDeclaration, isDestructuringAssignment, isElementAccessExpression, isExpression, isForInitializer, + isGeneratedIdentifier, isGeneratedPrivateIdentifier, isGetAccessor, isGetAccessorDeclaration, isHeritageClause, + isIdentifier, isInitializedProperty, isMethodDeclaration, isModifier, isModifierLike, + isNonStaticMethodOrAccessorWithPrivateName, isObjectBindingOrAssignmentElement, isObjectLiteralElementLike, + isParameterPropertyDeclaration, isParenthesizedExpression, isPrefixUnaryExpression, isPrivateIdentifier, + isPrivateIdentifierClassElementDeclaration, isPrivateIdentifierPropertyAccessExpression, isPropertyAccessExpression, + isPropertyAssignment, isPropertyDeclaration, isPropertyName, isSetAccessor, isSetAccessorDeclaration, + isShorthandPropertyAssignment, isSimpleCopiableExpression, isSimpleInlineableExpression, isSpreadAssignment, + isSpreadElement, isStatement, isStatic, isStaticModifier, isSuperProperty, isTemplateLiteral, isThisProperty, + LeftHandSideExpression, map, Map, MethodDeclaration, Modifier, ModifierFlags, moveRangePastModifiers, moveRangePos, + Node, NodeCheckFlags, nodeIsSynthesized, ObjectLiteralElementLike, PostfixUnaryExpression, PrefixUnaryExpression, + PrivateIdentifier, PrivateIdentifierPropertyAccessExpression, PrivateIdentifierPropertyDeclaration, + PropertyAccessExpression, PropertyDeclaration, PropertyName, ScriptTarget, SetAccessorDeclaration, setCommentRange, + setEmitFlags, setOriginalNode, setSourceMapRange, setSyntheticLeadingComments, setSyntheticTrailingComments, + setTextRange, skipOuterExpressions, skipParentheses, skipPartiallyEmittedExpressions, some, SourceFile, + startOnNewLine, Statement, SuperProperty, SyntaxKind, TaggedTemplateExpression, ThisExpression, + TransformationContext, TransformFlags, tryGetTextOfPropertyName, UnderscoreEscapedMap, unescapeLeadingUnderscores, + VariableStatement, visitArray, visitEachChild, visitFunctionBody, visitIterationBody, visitNode, visitNodes, + visitParameterList, VisitResult, Bundle, +} from "../_namespaces/ts"; + +const enum ClassPropertySubstitutionFlags { + /** + * Enables substitutions for class expressions with static fields + * which have initializers that reference the class name. + */ + ClassAliases = 1 << 0, + /** + * Enables substitutions for class expressions with static fields + * which have initializers that reference the 'this' or 'super'. + */ + ClassStaticThisOrSuperReference = 1 << 1, +} +/** @internal */ +export const enum PrivateIdentifierKind { + Field = "f", + Method = "m", + Accessor = "a" +} + +interface PrivateIdentifierInfoBase { /** - * 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 declarations are elided and initializers are transformed as assignments in the constructor. - * When --useDefineForClassFields is on, this transforms to ECMAScript semantics, with Object.defineProperty. + * brandCheckIdentifier can contain: + * - For instance field: The WeakMap that will be the storage for the field. + * - For instance methods or accessors: The WeakSet that will be used for brand checking. + * - For static members: The constructor that will be used for brand checking. */ - export function transformClassFields(context: TransformationContext) { - const { - factory, - hoistVariableDeclaration, - endLexicalEnvironment, - startLexicalEnvironment, - resumeLexicalEnvironment, - addBlockScopedVariable - } = context; - const resolver = context.getEmitResolver(); - const compilerOptions = context.getCompilerOptions(); - const languageVersion = getEmitScriptTarget(compilerOptions); - const useDefineForClassFields = getUseDefineForClassFields(compilerOptions); - - // Always transform field initializers using Set semantics when `useDefineForClassFields: false`. - const shouldTransformInitializersUsingSet = !useDefineForClassFields; - - // Transform field initializers using Define semantics when `useDefineForClassFields: true` and target < ES2022. - const shouldTransformInitializersUsingDefine = useDefineForClassFields && languageVersion < ScriptTarget.ES2022; - const shouldTransformInitializers = shouldTransformInitializersUsingSet || shouldTransformInitializersUsingDefine; - - // We need to transform private members and class static blocks when target < ES2022. - const shouldTransformPrivateElementsOrClassStaticBlocks = languageVersion < ScriptTarget.ES2022; - - // We need to transform `accessor` fields when target < ESNext - const shouldTransformAutoAccessors = languageVersion < ScriptTarget.ESNext; - - // We need to transform `this` in a static initializer into a reference to the class - // when target < ES2022 since the assignment will be moved outside of the class body. - const shouldTransformThisInStaticInitializers = languageVersion < ScriptTarget.ES2022; - - // We don't need to transform `super` property access when target <= ES5 because - // the es2015 transformation handles those. - const shouldTransformSuperInStaticInitializers = shouldTransformThisInStaticInitializers && languageVersion >= ScriptTarget.ES2015; - - const shouldTransformAnything = - shouldTransformInitializers || - shouldTransformPrivateElementsOrClassStaticBlocks || - shouldTransformAutoAccessors; - - const previousOnSubstituteNode = context.onSubstituteNode; - context.onSubstituteNode = onSubstituteNode; - - const previousOnEmitNode = context.onEmitNode; - context.onEmitNode = onEmitNode; - - 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; - - const classLexicalEnvironmentStack: (ClassLexicalEnvironment | undefined)[] = []; - const classLexicalEnvironmentMap = new Map(); - - let currentClassLexicalEnvironment: ClassLexicalEnvironment | undefined; - let currentClassContainer: ClassLikeDeclaration | undefined; - let currentComputedPropertyNameClassLexicalEnvironment: ClassLexicalEnvironment | undefined; - let currentStaticPropertyDeclarationOrStaticBlock: PropertyDeclaration | ClassStaticBlockDeclaration | undefined; - - return chainBundle(context, transformSourceFile); - - function transformSourceFile(node: SourceFile) { - if (node.isDeclarationFile || !shouldTransformAnything) { - return node; - } + brandCheckIdentifier: Identifier; + /** + * Stores if the identifier is static or not + */ + isStatic: boolean; + /** + * Stores if the identifier declaration is valid or not. Reserved names (e.g. #constructor) + * or duplicate identifiers are considered invalid. + */ + isValid: boolean; +} - const visited = visitEachChild(node, visitor, context); - addEmitHelpers(visited, context.readEmitHelpers()); - return visited; - } +interface PrivateIdentifierAccessorInfo extends PrivateIdentifierInfoBase { + kind: PrivateIdentifierKind.Accessor; + /** + * Identifier for a variable that will contain the private get accessor implementation, if any. + */ + getterName?: Identifier; + /** + * Identifier for a variable that will contain the private set accessor implementation, if any. + */ + setterName?: Identifier; +} - function visitor(node: Node): VisitResult { - if (!(node.transformFlags & TransformFlags.ContainsClassFields) && - !(node.transformFlags & TransformFlags.ContainsLexicalThisOrSuper)) { - return node; - } +interface PrivateIdentifierMethodInfo extends PrivateIdentifierInfoBase { + kind: PrivateIdentifierKind.Method; + /** + * Identifier for a variable that will contain the private method implementation. + */ + methodName: Identifier; +} - switch (node.kind) { - case SyntaxKind.AccessorKeyword: - return shouldTransformAutoAccessors ? undefined : node; - case SyntaxKind.ClassDeclaration: - return visitClassDeclaration(node as ClassDeclaration); - case SyntaxKind.ClassExpression: - return visitClassExpression(node as ClassExpression); - case SyntaxKind.ClassStaticBlockDeclaration: - return visitClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); - case SyntaxKind.PropertyDeclaration: - return visitPropertyDeclaration(node as PropertyDeclaration); - case SyntaxKind.VariableStatement: - return visitVariableStatement(node as VariableStatement); - case SyntaxKind.PrivateIdentifier: - return visitPrivateIdentifier(node as PrivateIdentifier); - case SyntaxKind.PropertyAccessExpression: - return visitPropertyAccessExpression(node as PropertyAccessExpression); - case SyntaxKind.ElementAccessExpression: - return visitElementAccessExpression(node as ElementAccessExpression); - case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: - return visitPreOrPostfixUnaryExpression(node as PrefixUnaryExpression | PostfixUnaryExpression, /*valueIsDiscarded*/ false); - case SyntaxKind.BinaryExpression: - return visitBinaryExpression(node as BinaryExpression, /*valueIsDiscarded*/ false); - case SyntaxKind.CallExpression: - return visitCallExpression(node as CallExpression); - case SyntaxKind.ExpressionStatement: - return visitExpressionStatement(node as ExpressionStatement); - case SyntaxKind.TaggedTemplateExpression: - return visitTaggedTemplateExpression(node as TaggedTemplateExpression); - case SyntaxKind.ForStatement: - return visitForStatement(node as ForStatement); - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: { - // If we are descending into a new scope, clear the current static property or block - return setCurrentStaticPropertyDeclarationOrStaticBlockAnd( - /*current*/ undefined, - fallbackVisitor, - node - ); - } - default: - return fallbackVisitor(node); - } - } +interface PrivateIdentifierInstanceFieldInfo extends PrivateIdentifierInfoBase { + kind: PrivateIdentifierKind.Field; + isStatic: false; + /** + * Defined for ease of access when in a union with PrivateIdentifierStaticFieldInfo. + */ + variableName: undefined; +} - function fallbackVisitor(node: Node) { - return visitEachChild(node, visitor, context); +interface PrivateIdentifierStaticFieldInfo extends PrivateIdentifierInfoBase { + kind: PrivateIdentifierKind.Field; + isStatic: true; + /** + * Contains the variable that will serve as the storage for the field. + */ + variableName: Identifier; +} + +type PrivateIdentifierInfo = + | PrivateIdentifierMethodInfo + | PrivateIdentifierInstanceFieldInfo + | PrivateIdentifierStaticFieldInfo + | PrivateIdentifierAccessorInfo; + +interface PrivateIdentifierEnvironment { + /** + * Used for prefixing generated variable names. + */ + className?: Identifier; + /** + * Used for brand check on private methods. + */ + weakSetName?: Identifier; + /** + * A mapping of private names to information needed for transformation. + */ + identifiers?: UnderscoreEscapedMap; + /** + * A mapping of generated private names to information needed for transformation. + */ + generatedIdentifiers?: ESMap; +} + +interface ClassLexicalEnvironment { + facts: ClassFacts; + /** + * Used for brand checks on static members, and `this` references in static initializers + */ + classConstructor: Identifier | undefined; + /** + * Used for `super` references in static initializers. + */ + superClassReference: Identifier | undefined; + privateIdentifierEnvironment: PrivateIdentifierEnvironment | undefined; +} + +const enum ClassFacts { + None = 0, + ClassWasDecorated = 1 << 0, + NeedsClassConstructorReference = 1 << 1, + NeedsClassSuperReference = 1 << 2, + NeedsSubstitutionForThisInClassStaticField = 1 << 3, +} + +/** + * 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 declarations are elided and initializers are transformed as assignments in the constructor. + * When --useDefineForClassFields is on, this transforms to ECMAScript semantics, with Object.defineProperty. + * + * @internal + */ +export function transformClassFields(context: TransformationContext): (x: SourceFile | Bundle) => SourceFile | Bundle { + const { + factory, + hoistVariableDeclaration, + endLexicalEnvironment, + startLexicalEnvironment, + resumeLexicalEnvironment, + addBlockScopedVariable + } = context; + const resolver = context.getEmitResolver(); + const compilerOptions = context.getCompilerOptions(); + const languageVersion = getEmitScriptTarget(compilerOptions); + const useDefineForClassFields = getUseDefineForClassFields(compilerOptions); + + // Always transform field initializers using Set semantics when `useDefineForClassFields: false`. + const shouldTransformInitializersUsingSet = !useDefineForClassFields; + + // Transform field initializers using Define semantics when `useDefineForClassFields: true` and target < ES2022. + const shouldTransformInitializersUsingDefine = useDefineForClassFields && languageVersion < ScriptTarget.ES2022; + const shouldTransformInitializers = shouldTransformInitializersUsingSet || shouldTransformInitializersUsingDefine; + + // We need to transform private members and class static blocks when target < ES2022. + const shouldTransformPrivateElementsOrClassStaticBlocks = languageVersion < ScriptTarget.ES2022; + + // We need to transform `accessor` fields when target < ESNext + const shouldTransformAutoAccessors = languageVersion < ScriptTarget.ESNext; + + // We need to transform `this` in a static initializer into a reference to the class + // when target < ES2022 since the assignment will be moved outside of the class body. + const shouldTransformThisInStaticInitializers = languageVersion < ScriptTarget.ES2022; + + // We don't need to transform `super` property access when target <= ES5 because + // the es2015 transformation handles those. + const shouldTransformSuperInStaticInitializers = shouldTransformThisInStaticInitializers && languageVersion >= ScriptTarget.ES2015; + + const shouldTransformAnything = + shouldTransformInitializers || + shouldTransformPrivateElementsOrClassStaticBlocks || + shouldTransformAutoAccessors; + + const previousOnSubstituteNode = context.onSubstituteNode; + context.onSubstituteNode = onSubstituteNode; + + const previousOnEmitNode = context.onEmitNode; + context.onEmitNode = onEmitNode; + + 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; + + const classLexicalEnvironmentStack: (ClassLexicalEnvironment | undefined)[] = []; + const classLexicalEnvironmentMap = new Map(); + + let currentClassLexicalEnvironment: ClassLexicalEnvironment | undefined; + let currentClassContainer: ClassLikeDeclaration | undefined; + let currentComputedPropertyNameClassLexicalEnvironment: ClassLexicalEnvironment | undefined; + let currentStaticPropertyDeclarationOrStaticBlock: PropertyDeclaration | ClassStaticBlockDeclaration | undefined; + + return chainBundle(context, transformSourceFile); + + function transformSourceFile(node: SourceFile) { + if (node.isDeclarationFile || !shouldTransformAnything) { + return node; } - /** - * Visits a node in an expression whose result is discarded. - */ - function discardedValueVisitor(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: - return visitPreOrPostfixUnaryExpression(node as PrefixUnaryExpression | PostfixUnaryExpression, /*valueIsDiscarded*/ true); - case SyntaxKind.BinaryExpression: - return visitBinaryExpression(node as BinaryExpression, /*valueIsDiscarded*/ true); - default: - return visitor(node); - } + const visited = visitEachChild(node, visitor, context); + addEmitHelpers(visited, context.readEmitHelpers()); + return visited; + } + + function visitor(node: Node): VisitResult { + if (!(node.transformFlags & TransformFlags.ContainsClassFields) && + !(node.transformFlags & TransformFlags.ContainsLexicalThisOrSuper)) { + return node; } - /** - * Visits a node in a {@link HeritageClause}. - */ - function heritageClauseVisitor(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.HeritageClause: - return visitEachChild(node, heritageClauseVisitor, context); - case SyntaxKind.ExpressionWithTypeArguments: - return visitExpressionWithTypeArgumentsInHeritageClause(node as ExpressionWithTypeArguments); - default: - return visitor(node); + switch (node.kind) { + case SyntaxKind.AccessorKeyword: + return shouldTransformAutoAccessors ? undefined : node; + case SyntaxKind.ClassDeclaration: + return visitClassDeclaration(node as ClassDeclaration); + case SyntaxKind.ClassExpression: + return visitClassExpression(node as ClassExpression); + case SyntaxKind.ClassStaticBlockDeclaration: + return visitClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); + case SyntaxKind.PropertyDeclaration: + return visitPropertyDeclaration(node as PropertyDeclaration); + case SyntaxKind.VariableStatement: + return visitVariableStatement(node as VariableStatement); + case SyntaxKind.PrivateIdentifier: + return visitPrivateIdentifier(node as PrivateIdentifier); + case SyntaxKind.PropertyAccessExpression: + return visitPropertyAccessExpression(node as PropertyAccessExpression); + case SyntaxKind.ElementAccessExpression: + return visitElementAccessExpression(node as ElementAccessExpression); + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.PostfixUnaryExpression: + return visitPreOrPostfixUnaryExpression(node as PrefixUnaryExpression | PostfixUnaryExpression, /*valueIsDiscarded*/ false); + case SyntaxKind.BinaryExpression: + return visitBinaryExpression(node as BinaryExpression, /*valueIsDiscarded*/ false); + case SyntaxKind.CallExpression: + return visitCallExpression(node as CallExpression); + case SyntaxKind.ExpressionStatement: + return visitExpressionStatement(node as ExpressionStatement); + case SyntaxKind.TaggedTemplateExpression: + return visitTaggedTemplateExpression(node as TaggedTemplateExpression); + case SyntaxKind.ForStatement: + return visitForStatement(node as ForStatement); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: { + // If we are descending into a new scope, clear the current static property or block + return setCurrentStaticPropertyDeclarationOrStaticBlockAnd( + /*current*/ undefined, + fallbackVisitor, + node + ); } + default: + return fallbackVisitor(node); } + } - /** - * Visits the assignment target of a destructuring assignment. - */ - function assignmentTargetVisitor(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ArrayLiteralExpression: - return visitAssignmentPattern(node as AssignmentPattern); - default: - return visitor(node); - } + function fallbackVisitor(node: Node) { + return visitEachChild(node, visitor, context); + } + + /** + * Visits a node in an expression whose result is discarded. + */ + function discardedValueVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.PostfixUnaryExpression: + return visitPreOrPostfixUnaryExpression(node as PrefixUnaryExpression | PostfixUnaryExpression, /*valueIsDiscarded*/ true); + case SyntaxKind.BinaryExpression: + return visitBinaryExpression(node as BinaryExpression, /*valueIsDiscarded*/ true); + default: + return visitor(node); } + } - /** - * Visits a member of a class. - */ - function classElementVisitor(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.Constructor: - return visitConstructorDeclaration(node as ConstructorDeclaration); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - return setCurrentStaticPropertyDeclarationOrStaticBlockAnd( - /*current*/ undefined, - visitMethodOrAccessorDeclaration, - node as MethodDeclaration | AccessorDeclaration); - case SyntaxKind.PropertyDeclaration: - return setCurrentStaticPropertyDeclarationOrStaticBlockAnd( - /*current*/ undefined, - visitPropertyDeclaration, - node as PropertyDeclaration); - case SyntaxKind.ComputedPropertyName: - return visitComputedPropertyName(node as ComputedPropertyName); - case SyntaxKind.SemicolonClassElement: - return node; - default: - return visitor(node); - } + /** + * Visits a node in a {@link HeritageClause}. + */ + function heritageClauseVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.HeritageClause: + return visitEachChild(node, heritageClauseVisitor, context); + case SyntaxKind.ExpressionWithTypeArguments: + return visitExpressionWithTypeArgumentsInHeritageClause(node as ExpressionWithTypeArguments); + default: + return visitor(node); } + } - /** - * Visits the results of an auto-accessor field transformation in a second pass. - */ - function accessorFieldResultVisitor(node: Node) { - switch (node.kind) { - case SyntaxKind.PropertyDeclaration: - return transformFieldInitializer(node as PropertyDeclaration); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return classElementVisitor(node); - default: - Debug.assertMissingNode(node, "Expected node to either be a PropertyDeclaration, GetAccessorDeclaration, or SetAccessorDeclaration"); - break; - } + /** + * Visits the assignment target of a destructuring assignment. + */ + function assignmentTargetVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ArrayLiteralExpression: + return visitAssignmentPattern(node as AssignmentPattern); + default: + return visitor(node); } + } - /** - * If we visit a private name, this means it is an undeclared private name. - * Replace it with an empty identifier to indicate a problem with the code, - * unless we are in a statement position - otherwise this will not trigger - * a SyntaxError. - */ - function visitPrivateIdentifier(node: PrivateIdentifier) { - if (!shouldTransformPrivateElementsOrClassStaticBlocks) { - return node; - } - if (isStatement(node.parent)) { + /** + * Visits a member of a class. + */ + function classElementVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.Constructor: + return visitConstructorDeclaration(node as ConstructorDeclaration); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + return setCurrentStaticPropertyDeclarationOrStaticBlockAnd( + /*current*/ undefined, + visitMethodOrAccessorDeclaration, + node as MethodDeclaration | AccessorDeclaration); + case SyntaxKind.PropertyDeclaration: + return setCurrentStaticPropertyDeclarationOrStaticBlockAnd( + /*current*/ undefined, + visitPropertyDeclaration, + node as PropertyDeclaration); + case SyntaxKind.ComputedPropertyName: + return visitComputedPropertyName(node as ComputedPropertyName); + case SyntaxKind.SemicolonClassElement: return node; - } - return setOriginalNode(factory.createIdentifier(""), node); + default: + return visitor(node); } + } - type PrivateIdentifierInExpression = BinaryExpression & { readonly left: PrivateIdentifier, readonly token: InKeyword }; + /** + * Visits the results of an auto-accessor field transformation in a second pass. + */ + function accessorFieldResultVisitor(node: Node) { + switch (node.kind) { + case SyntaxKind.PropertyDeclaration: + return transformFieldInitializer(node as PropertyDeclaration); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return classElementVisitor(node); + default: + Debug.assertMissingNode(node, "Expected node to either be a PropertyDeclaration, GetAccessorDeclaration, or SetAccessorDeclaration"); + break; + } + } - function isPrivateIdentifierInExpression(node: BinaryExpression): node is PrivateIdentifierInExpression { - return isPrivateIdentifier(node.left) - && node.operatorToken.kind === SyntaxKind.InKeyword; + /** + * If we visit a private name, this means it is an undeclared private name. + * Replace it with an empty identifier to indicate a problem with the code, + * unless we are in a statement position - otherwise this will not trigger + * a SyntaxError. + */ + function visitPrivateIdentifier(node: PrivateIdentifier) { + if (!shouldTransformPrivateElementsOrClassStaticBlocks) { + return node; } + if (isStatement(node.parent)) { + return node; + } + return setOriginalNode(factory.createIdentifier(""), node); + } - /** - * Visits `#id in expr` - */ - function transformPrivateIdentifierInInExpression(node: PrivateIdentifierInExpression) { - const info = accessPrivateIdentifier(node.left); - if (info) { - const receiver = visitNode(node.right, visitor, isExpression); + type PrivateIdentifierInExpression = BinaryExpression & { readonly left: PrivateIdentifier, readonly token: InKeyword }; - return setOriginalNode( - context.getEmitHelperFactory().createClassPrivateFieldInHelper(info.brandCheckIdentifier, receiver), - node - ); - } + function isPrivateIdentifierInExpression(node: BinaryExpression): node is PrivateIdentifierInExpression { + return isPrivateIdentifier(node.left) + && node.operatorToken.kind === SyntaxKind.InKeyword; + } - // Private name has not been declared. Subsequent transformers will handle this error - return visitEachChild(node, visitor, context); + /** + * Visits `#id in expr` + */ + function transformPrivateIdentifierInInExpression(node: PrivateIdentifierInExpression) { + const info = accessPrivateIdentifier(node.left); + if (info) { + const receiver = visitNode(node.right, visitor, isExpression); + + return setOriginalNode( + context.getEmitHelperFactory().createClassPrivateFieldInHelper(info.brandCheckIdentifier, receiver), + node + ); } - function visitVariableStatement(node: VariableStatement) { - const savedPendingStatements = pendingStatements; - pendingStatements = []; + // Private name has not been declared. Subsequent transformers will handle this error + return visitEachChild(node, visitor, context); + } + + function visitVariableStatement(node: VariableStatement) { + const savedPendingStatements = pendingStatements; + pendingStatements = []; - const visitedNode = visitEachChild(node, visitor, context); - const statement = some(pendingStatements) ? - [visitedNode, ...pendingStatements] : - visitedNode; + const visitedNode = visitEachChild(node, visitor, context); + const statement = some(pendingStatements) ? + [visitedNode, ...pendingStatements] : + visitedNode; - pendingStatements = savedPendingStatements; - return statement; - } + pendingStatements = savedPendingStatements; + return statement; + } - function visitComputedPropertyName(node: ComputedPropertyName) { - let expression = visitNode(node.expression, visitor, isExpression); - if (some(pendingExpressions)) { - if (isParenthesizedExpression(expression)) { - expression = factory.updateParenthesizedExpression(expression, factory.inlineExpressions([...pendingExpressions, expression.expression])); - } - else { - expression = factory.inlineExpressions([...pendingExpressions, expression]); - } - pendingExpressions = undefined; + function visitComputedPropertyName(node: ComputedPropertyName) { + let expression = visitNode(node.expression, visitor, isExpression); + if (some(pendingExpressions)) { + if (isParenthesizedExpression(expression)) { + expression = factory.updateParenthesizedExpression(expression, factory.inlineExpressions([...pendingExpressions, expression.expression])); } - return factory.updateComputedPropertyName(node, expression); + else { + expression = factory.inlineExpressions([...pendingExpressions, expression]); + } + pendingExpressions = undefined; } + return factory.updateComputedPropertyName(node, expression); + } - function visitConstructorDeclaration(node: ConstructorDeclaration) { - if (currentClassContainer) { - return transformConstructor(node, currentClassContainer); - } - return fallbackVisitor(node); + function visitConstructorDeclaration(node: ConstructorDeclaration) { + if (currentClassContainer) { + return transformConstructor(node, currentClassContainer); } + return fallbackVisitor(node); + } - function visitMethodOrAccessorDeclaration(node: MethodDeclaration | AccessorDeclaration) { - Debug.assert(!hasDecorators(node)); + function visitMethodOrAccessorDeclaration(node: MethodDeclaration | AccessorDeclaration) { + Debug.assert(!hasDecorators(node)); - if (!shouldTransformPrivateElementsOrClassStaticBlocks || !isPrivateIdentifier(node.name)) { - return visitEachChild(node, classElementVisitor, context); - } + if (!shouldTransformPrivateElementsOrClassStaticBlocks || !isPrivateIdentifier(node.name)) { + return visitEachChild(node, classElementVisitor, context); + } - // leave invalid code untransformed - const info = accessPrivateIdentifier(node.name); - Debug.assert(info, "Undeclared private name for property declaration."); - if (!info.isValid) { - return node; - } + // leave invalid code untransformed + const info = accessPrivateIdentifier(node.name); + Debug.assert(info, "Undeclared private name for property declaration."); + if (!info.isValid) { + return node; + } - const functionName = getHoistedFunctionName(node); - if (functionName) { - getPendingExpressions().push( - factory.createAssignment( + const functionName = getHoistedFunctionName(node); + if (functionName) { + getPendingExpressions().push( + factory.createAssignment( + functionName, + factory.createFunctionExpression( + filter(node.modifiers, (m): m is Modifier => isModifier(m) && !isStaticModifier(m) && !isAccessorModifier(m)), + node.asteriskToken, functionName, - factory.createFunctionExpression( - filter(node.modifiers, (m): m is Modifier => isModifier(m) && !isStaticModifier(m) && !isAccessorModifier(m)), - node.asteriskToken, - functionName, - /* typeParameters */ undefined, - visitParameterList(node.parameters, visitor, context), - /* type */ undefined, - visitFunctionBody(node.body!, visitor, context) - ) + /* typeParameters */ undefined, + visitParameterList(node.parameters, visitor, context), + /* type */ undefined, + visitFunctionBody(node.body!, visitor, context) ) - ); - } - - // remove method declaration from class - return undefined; + ) + ); } - function setCurrentStaticPropertyDeclarationOrStaticBlockAnd( - current: ClassStaticBlockDeclaration | PropertyDeclaration | undefined, - visitor: (arg: T) => U, - arg: T, - ) { - const savedCurrentStaticPropertyDeclarationOrStaticBlock = currentStaticPropertyDeclarationOrStaticBlock; - currentStaticPropertyDeclarationOrStaticBlock = current; - const result = visitor(arg); - currentStaticPropertyDeclarationOrStaticBlock = savedCurrentStaticPropertyDeclarationOrStaticBlock; - return result; - } + // remove method declaration from class + return undefined; + } - function getHoistedFunctionName(node: MethodDeclaration | AccessorDeclaration) { - Debug.assert(isPrivateIdentifier(node.name)); - const info = accessPrivateIdentifier(node.name); - Debug.assert(info, "Undeclared private name for property declaration."); + function setCurrentStaticPropertyDeclarationOrStaticBlockAnd( + current: ClassStaticBlockDeclaration | PropertyDeclaration | undefined, + visitor: (arg: T) => U, + arg: T, + ) { + const savedCurrentStaticPropertyDeclarationOrStaticBlock = currentStaticPropertyDeclarationOrStaticBlock; + currentStaticPropertyDeclarationOrStaticBlock = current; + const result = visitor(arg); + currentStaticPropertyDeclarationOrStaticBlock = savedCurrentStaticPropertyDeclarationOrStaticBlock; + return result; + } - if (info.kind === PrivateIdentifierKind.Method) { - return info.methodName; - } + function getHoistedFunctionName(node: MethodDeclaration | AccessorDeclaration) { + Debug.assert(isPrivateIdentifier(node.name)); + const info = accessPrivateIdentifier(node.name); + Debug.assert(info, "Undeclared private name for property declaration."); - if (info.kind === PrivateIdentifierKind.Accessor) { - if (isGetAccessor(node)) { - return info.getterName; - } - if (isSetAccessor(node)) { - return info.setterName; - } - } + if (info.kind === PrivateIdentifierKind.Method) { + return info.methodName; } - function transformAutoAccessor(node: AutoAccessorPropertyDeclaration): VisitResult { - // transforms: - // accessor x = 1; - // into: - // #x = 1; - // get x() { return this.#x; } - // set x(value) { this.#x = value; } - - Debug.assertEachNode(node.modifiers, isModifier); - - const commentRange = getCommentRange(node); - const sourceMapRange = getSourceMapRange(node); - - // Since we're creating two declarations where there was previously one, cache - // the expression for any computed property names. - const name = node.name; - let getterName = name; - let setterName = name; - if (isComputedPropertyName(name) && !isSimpleInlineableExpression(name.expression)) { - const temp = factory.createTempVariable(hoistVariableDeclaration); - setSourceMapRange(temp, name.expression); - const expression = visitNode(name.expression, visitor, isExpression); - const assignment = factory.createAssignment(temp, expression); - setSourceMapRange(assignment, name.expression); - getterName = factory.updateComputedPropertyName(name, factory.inlineExpressions([assignment, temp])); - setterName = factory.updateComputedPropertyName(name, temp); + if (info.kind === PrivateIdentifierKind.Accessor) { + if (isGetAccessor(node)) { + return info.getterName; + } + if (isSetAccessor(node)) { + return info.setterName; } + } + } - const backingField = createAccessorPropertyBackingField(factory, node, node.modifiers, node.initializer); - setOriginalNode(backingField, node); - setEmitFlags(backingField, EmitFlags.NoComments); - setSourceMapRange(backingField, sourceMapRange); + function transformAutoAccessor(node: AutoAccessorPropertyDeclaration): VisitResult { + // transforms: + // accessor x = 1; + // into: + // #x = 1; + // get x() { return this.#x; } + // set x(value) { this.#x = value; } + + Debug.assertEachNode(node.modifiers, isModifier); + + const commentRange = getCommentRange(node); + const sourceMapRange = getSourceMapRange(node); + + // Since we're creating two declarations where there was previously one, cache + // the expression for any computed property names. + const name = node.name; + let getterName = name; + let setterName = name; + if (isComputedPropertyName(name) && !isSimpleInlineableExpression(name.expression)) { + const temp = factory.createTempVariable(hoistVariableDeclaration); + setSourceMapRange(temp, name.expression); + const expression = visitNode(name.expression, visitor, isExpression); + const assignment = factory.createAssignment(temp, expression); + setSourceMapRange(assignment, name.expression); + getterName = factory.updateComputedPropertyName(name, factory.inlineExpressions([assignment, temp])); + setterName = factory.updateComputedPropertyName(name, temp); + } + + const backingField = createAccessorPropertyBackingField(factory, node, node.modifiers, node.initializer); + setOriginalNode(backingField, node); + setEmitFlags(backingField, EmitFlags.NoComments); + setSourceMapRange(backingField, sourceMapRange); + + const getter = createAccessorPropertyGetRedirector(factory, node, node.modifiers, getterName); + setOriginalNode(getter, node); + setCommentRange(getter, commentRange); + setSourceMapRange(getter, sourceMapRange); + + const setter = createAccessorPropertySetRedirector(factory, node, node.modifiers, setterName); + setOriginalNode(setter, node); + setEmitFlags(setter, EmitFlags.NoComments); + setSourceMapRange(setter, sourceMapRange); + + return visitArray([backingField, getter, setter], accessorFieldResultVisitor, isClassElement); + } - const getter = createAccessorPropertyGetRedirector(factory, node, node.modifiers, getterName); - setOriginalNode(getter, node); - setCommentRange(getter, commentRange); - setSourceMapRange(getter, sourceMapRange); + function transformPrivateFieldInitializer(node: PrivateIdentifierPropertyDeclaration) { + if (shouldTransformPrivateElementsOrClassStaticBlocks) { + // If we are transforming private elements into WeakMap/WeakSet, we should elide the node. + const info = accessPrivateIdentifier(node.name); + Debug.assert(info, "Undeclared private name for property declaration."); - const setter = createAccessorPropertySetRedirector(factory, node, node.modifiers, setterName); - setOriginalNode(setter, node); - setEmitFlags(setter, EmitFlags.NoComments); - setSourceMapRange(setter, sourceMapRange); + // Leave invalid code untransformed; otherwise, elide the node as it is transformed elsewhere. + return info.isValid ? undefined : node; + } - return visitArray([backingField, getter, setter], accessorFieldResultVisitor, isClassElement); + if (shouldTransformInitializersUsingSet && !isStatic(node)) { + // If we are transforming initializers using Set semantics we will elide the initializer as it will + // be moved to the constructor to preserve evaluation order next to public instance fields. We don't + // need to do this transformation for private static fields since public static fields can be + // transformed into `static {}` blocks. + return factory.updatePropertyDeclaration( + node, + visitNodes(node.modifiers, visitor, isModifierLike), + node.name, + /*questionOrExclamationToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + ); } - function transformPrivateFieldInitializer(node: PrivateIdentifierPropertyDeclaration) { - if (shouldTransformPrivateElementsOrClassStaticBlocks) { - // If we are transforming private elements into WeakMap/WeakSet, we should elide the node. - const info = accessPrivateIdentifier(node.name); - Debug.assert(info, "Undeclared private name for property declaration."); + return visitEachChild(node, visitor, context); + } - // Leave invalid code untransformed; otherwise, elide the node as it is transformed elsewhere. - return info.isValid ? undefined : node; - } + function transformPublicFieldInitializer(node: PropertyDeclaration) { + if (shouldTransformInitializers) { + // 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. - if (shouldTransformInitializersUsingSet && !isStatic(node)) { - // If we are transforming initializers using Set semantics we will elide the initializer as it will - // be moved to the constructor to preserve evaluation order next to public instance fields. We don't - // need to do this transformation for private static fields since public static fields can be - // transformed into `static {}` blocks. - return factory.updatePropertyDeclaration( - node, - visitNodes(node.modifiers, visitor, isModifierLike), - node.name, - /*questionOrExclamationToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - ); + const expr = getPropertyNameExpressionIfNeeded(node.name, /*shouldHoist*/ !!node.initializer || useDefineForClassFields); + if (expr) { + getPendingExpressions().push(expr); } - return visitEachChild(node, visitor, context); - } + if (isStatic(node) && !shouldTransformPrivateElementsOrClassStaticBlocks) { + const initializerStatement = transformPropertyOrClassStaticBlock(node, factory.createThis()); + if (initializerStatement) { + const staticBlock = factory.createClassStaticBlockDeclaration( + factory.createBlock([initializerStatement]) + ); - function transformPublicFieldInitializer(node: PropertyDeclaration) { - if (shouldTransformInitializers) { - // 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. + setOriginalNode(staticBlock, node); + setCommentRange(staticBlock, node); - const expr = getPropertyNameExpressionIfNeeded(node.name, /*shouldHoist*/ !!node.initializer || useDefineForClassFields); - if (expr) { - getPendingExpressions().push(expr); + // Set the comment range for the statement to an empty synthetic range + // and drop synthetic comments from the statement to avoid printing them twice. + setCommentRange(initializerStatement, { pos: -1, end: -1 }); + setSyntheticLeadingComments(initializerStatement, undefined); + setSyntheticTrailingComments(initializerStatement, undefined); + return staticBlock; } + } - if (isStatic(node) && !shouldTransformPrivateElementsOrClassStaticBlocks) { - const initializerStatement = transformPropertyOrClassStaticBlock(node, factory.createThis()); - if (initializerStatement) { - const staticBlock = factory.createClassStaticBlockDeclaration( - factory.createBlock([initializerStatement]) - ); - - setOriginalNode(staticBlock, node); - setCommentRange(staticBlock, node); + return undefined; + } - // Set the comment range for the statement to an empty synthetic range - // and drop synthetic comments from the statement to avoid printing them twice. - setCommentRange(initializerStatement, { pos: -1, end: -1 }); - setSyntheticLeadingComments(initializerStatement, undefined); - setSyntheticTrailingComments(initializerStatement, undefined); - return staticBlock; - } - } + return visitEachChild(node, classElementVisitor, context); + } - return undefined; - } + function transformFieldInitializer(node: PropertyDeclaration) { + Debug.assert(!hasDecorators(node), "Decorators should already have been transformed and elided."); + return isPrivateIdentifierClassElementDeclaration(node) ? + transformPrivateFieldInitializer(node) : + transformPublicFieldInitializer(node); + } - return visitEachChild(node, classElementVisitor, context); + function visitPropertyDeclaration(node: PropertyDeclaration) { + // If this is an auto-accessor, we defer to `transformAutoAccessor`. That function + // will in turn call `transformFieldInitializer` as needed. + if (shouldTransformAutoAccessors && isAutoAccessorPropertyDeclaration(node)) { + return transformAutoAccessor(node); } - function transformFieldInitializer(node: PropertyDeclaration) { - Debug.assert(!hasDecorators(node), "Decorators should already have been transformed and elided."); - return isPrivateIdentifierClassElementDeclaration(node) ? - transformPrivateFieldInitializer(node) : - transformPublicFieldInitializer(node); - } + return transformFieldInitializer(node); + } - function visitPropertyDeclaration(node: PropertyDeclaration) { - // If this is an auto-accessor, we defer to `transformAutoAccessor`. That function - // will in turn call `transformFieldInitializer` as needed. - if (shouldTransformAutoAccessors && isAutoAccessorPropertyDeclaration(node)) { - return transformAutoAccessor(node); - } + function createPrivateIdentifierAccess(info: PrivateIdentifierInfo, receiver: Expression): Expression { + return createPrivateIdentifierAccessHelper(info, visitNode(receiver, visitor, isExpression)); + } - return transformFieldInitializer(node); - } + function createPrivateIdentifierAccessHelper(info: PrivateIdentifierInfo, receiver: Expression): Expression { + setCommentRange(receiver, moveRangePos(receiver, -1)); - function createPrivateIdentifierAccess(info: PrivateIdentifierInfo, receiver: Expression): Expression { - return createPrivateIdentifierAccessHelper(info, visitNode(receiver, visitor, isExpression)); + switch(info.kind) { + case PrivateIdentifierKind.Accessor: + return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( + receiver, + info.brandCheckIdentifier, + info.kind, + info.getterName + ); + case PrivateIdentifierKind.Method: + return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( + receiver, + info.brandCheckIdentifier, + info.kind, + info.methodName + ); + case PrivateIdentifierKind.Field: + return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( + receiver, + info.brandCheckIdentifier, + info.kind, + info.variableName + ); + default: + Debug.assertNever(info, "Unknown private element type"); } + } - function createPrivateIdentifierAccessHelper(info: PrivateIdentifierInfo, receiver: Expression): Expression { - setCommentRange(receiver, moveRangePos(receiver, -1)); - - switch(info.kind) { - case PrivateIdentifierKind.Accessor: - return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( - receiver, - info.brandCheckIdentifier, - info.kind, - info.getterName - ); - case PrivateIdentifierKind.Method: - return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( - receiver, - info.brandCheckIdentifier, - info.kind, - info.methodName - ); - case PrivateIdentifierKind.Field: - return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( - receiver, - info.brandCheckIdentifier, - info.kind, - info.variableName - ); - default: - Debug.assertNever(info, "Unknown private element type"); + function visitPropertyAccessExpression(node: PropertyAccessExpression) { + if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(node.name)) { + const privateIdentifierInfo = accessPrivateIdentifier(node.name); + if (privateIdentifierInfo) { + return setTextRange( + setOriginalNode( + createPrivateIdentifierAccess(privateIdentifierInfo, node.expression), + node + ), + node + ); + } + } + if (shouldTransformSuperInStaticInitializers && + isSuperProperty(node) && + isIdentifier(node.name) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment) { + const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; + if (facts & ClassFacts.ClassWasDecorated) { + return visitInvalidSuperProperty(node); + } + if (classConstructor && superClassReference) { + // converts `super.x` into `Reflect.get(_baseTemp, "x", _classTemp)` + const superProperty = factory.createReflectGetCall( + superClassReference, + factory.createStringLiteralFromNode(node.name), + classConstructor + ); + setOriginalNode(superProperty, node.expression); + setTextRange(superProperty, node.expression); + return superProperty; } } + return visitEachChild(node, visitor, context); + } - function visitPropertyAccessExpression(node: PropertyAccessExpression) { - if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(node.name)) { - const privateIdentifierInfo = accessPrivateIdentifier(node.name); - if (privateIdentifierInfo) { - return setTextRange( - setOriginalNode( - createPrivateIdentifierAccess(privateIdentifierInfo, node.expression), - node - ), - node - ); - } + function visitElementAccessExpression(node: ElementAccessExpression) { + if (shouldTransformSuperInStaticInitializers && + isSuperProperty(node) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment) { + const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; + if (facts & ClassFacts.ClassWasDecorated) { + return visitInvalidSuperProperty(node); + } + + if (classConstructor && superClassReference) { + // converts `super[x]` into `Reflect.get(_baseTemp, x, _classTemp)` + const superProperty = factory.createReflectGetCall( + superClassReference, + visitNode(node.argumentExpression, visitor, isExpression), + classConstructor + ); + setOriginalNode(superProperty, node.expression); + setTextRange(superProperty, node.expression); + return superProperty; } - if (shouldTransformSuperInStaticInitializers && - isSuperProperty(node) && - isIdentifier(node.name) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment) { - const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; - if (facts & ClassFacts.ClassWasDecorated) { - return visitInvalidSuperProperty(node); - } - if (classConstructor && superClassReference) { - // converts `super.x` into `Reflect.get(_baseTemp, "x", _classTemp)` - const superProperty = factory.createReflectGetCall( - superClassReference, - factory.createStringLiteralFromNode(node.name), - classConstructor + } + return visitEachChild(node, visitor, context); + } + + function visitPreOrPostfixUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded: boolean) { + if (node.operator === SyntaxKind.PlusPlusToken || + node.operator === SyntaxKind.MinusMinusToken) { + const operand = skipParentheses(node.operand); + if (shouldTransformPrivateElementsOrClassStaticBlocks && + isPrivateIdentifierPropertyAccessExpression(operand)) { + let info: PrivateIdentifierInfo | undefined; + if (info = accessPrivateIdentifier(operand.name)) { + const receiver = visitNode(operand.expression, visitor, isExpression); + const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver); + + let expression: Expression = createPrivateIdentifierAccess(info, readExpression); + const temp = isPrefixUnaryExpression(node) || valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); + expression = expandPreOrPostfixIncrementOrDecrementExpression(factory, node, expression, hoistVariableDeclaration, temp); + expression = createPrivateIdentifierAssignment( + info, + initializeExpression || readExpression, + expression, + SyntaxKind.EqualsToken ); - setOriginalNode(superProperty, node.expression); - setTextRange(superProperty, node.expression); - return superProperty; + setOriginalNode(expression, node); + setTextRange(expression, node); + if (temp) { + expression = factory.createComma(expression, temp); + setTextRange(expression, node); + } + return expression; } } - return visitEachChild(node, visitor, context); - } - - function visitElementAccessExpression(node: ElementAccessExpression) { - if (shouldTransformSuperInStaticInitializers && - isSuperProperty(node) && + else if (shouldTransformSuperInStaticInitializers && + isSuperProperty(operand) && currentStaticPropertyDeclarationOrStaticBlock && currentClassLexicalEnvironment) { + // converts `++super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = ++_a), _classTemp), _b)` + // converts `++super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = ++_b), _classTemp), _c)` + // converts `--super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = --_a), _classTemp), _b)` + // converts `--super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = --_b), _classTemp), _c)` + // converts `super.a++` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a++), _classTemp), _b)` + // converts `super[f()]++` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b++), _classTemp), _c)` + // converts `super.a--` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a--), _classTemp), _b)` + // converts `super[f()]--` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b--), _classTemp), _c)` const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; if (facts & ClassFacts.ClassWasDecorated) { - return visitInvalidSuperProperty(node); + const expression = visitInvalidSuperProperty(operand); + return isPrefixUnaryExpression(node) ? + factory.updatePrefixUnaryExpression(node, expression) : + factory.updatePostfixUnaryExpression(node, expression); } - if (classConstructor && superClassReference) { - // converts `super[x]` into `Reflect.get(_baseTemp, x, _classTemp)` - const superProperty = factory.createReflectGetCall( - superClassReference, - visitNode(node.argumentExpression, visitor, isExpression), - classConstructor - ); - setOriginalNode(superProperty, node.expression); - setTextRange(superProperty, node.expression); - return superProperty; - } - } - return visitEachChild(node, visitor, context); - } - - function visitPreOrPostfixUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded: boolean) { - if (node.operator === SyntaxKind.PlusPlusToken || - node.operator === SyntaxKind.MinusMinusToken) { - const operand = skipParentheses(node.operand); - if (shouldTransformPrivateElementsOrClassStaticBlocks && - isPrivateIdentifierPropertyAccessExpression(operand)) { - let info: PrivateIdentifierInfo | undefined; - if (info = accessPrivateIdentifier(operand.name)) { - const receiver = visitNode(operand.expression, visitor, isExpression); - const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver); + let setterName: Expression | undefined; + let getterName: Expression | undefined; + if (isPropertyAccessExpression(operand)) { + if (isIdentifier(operand.name)) { + getterName = setterName = factory.createStringLiteralFromNode(operand.name); + } + } + else { + if (isSimpleInlineableExpression(operand.argumentExpression)) { + getterName = setterName = operand.argumentExpression; + } + else { + getterName = factory.createTempVariable(hoistVariableDeclaration); + setterName = factory.createAssignment(getterName, visitNode(operand.argumentExpression, visitor, isExpression)); + } + } + if (setterName && getterName) { + let expression: Expression = factory.createReflectGetCall(superClassReference, getterName, classConstructor); + setTextRange(expression, operand); - let expression: Expression = createPrivateIdentifierAccess(info, readExpression); - const temp = isPrefixUnaryExpression(node) || valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); + const temp = valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); expression = expandPreOrPostfixIncrementOrDecrementExpression(factory, node, expression, hoistVariableDeclaration, temp); - expression = createPrivateIdentifierAssignment( - info, - initializeExpression || readExpression, - expression, - SyntaxKind.EqualsToken - ); + expression = factory.createReflectSetCall(superClassReference, setterName, expression, classConstructor); setOriginalNode(expression, node); setTextRange(expression, node); if (temp) { @@ -751,1609 +842,1612 @@ namespace ts { return expression; } } - else if (shouldTransformSuperInStaticInitializers && - isSuperProperty(operand) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment) { - // converts `++super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = ++_a), _classTemp), _b)` - // converts `++super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = ++_b), _classTemp), _c)` - // converts `--super.a` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = --_a), _classTemp), _b)` - // converts `--super[f()]` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = --_b), _classTemp), _c)` - // converts `super.a++` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a++), _classTemp), _b)` - // converts `super[f()]++` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b++), _classTemp), _c)` - // converts `super.a--` into `(Reflect.set(_baseTemp, "a", (_a = Reflect.get(_baseTemp, "a", _classTemp), _b = _a--), _classTemp), _b)` - // converts `super[f()]--` into `(Reflect.set(_baseTemp, _a = f(), (_b = Reflect.get(_baseTemp, _a, _classTemp), _c = _b--), _classTemp), _c)` - const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; - if (facts & ClassFacts.ClassWasDecorated) { - const expression = visitInvalidSuperProperty(operand); - return isPrefixUnaryExpression(node) ? - factory.updatePrefixUnaryExpression(node, expression) : - factory.updatePostfixUnaryExpression(node, expression); - } - if (classConstructor && superClassReference) { - let setterName: Expression | undefined; - let getterName: Expression | undefined; - if (isPropertyAccessExpression(operand)) { - if (isIdentifier(operand.name)) { - getterName = setterName = factory.createStringLiteralFromNode(operand.name); - } - } - else { - if (isSimpleInlineableExpression(operand.argumentExpression)) { - getterName = setterName = operand.argumentExpression; - } - else { - getterName = factory.createTempVariable(hoistVariableDeclaration); - setterName = factory.createAssignment(getterName, visitNode(operand.argumentExpression, visitor, isExpression)); - } - } - if (setterName && getterName) { - let expression: Expression = factory.createReflectGetCall(superClassReference, getterName, classConstructor); - setTextRange(expression, operand); - - const temp = valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); - expression = expandPreOrPostfixIncrementOrDecrementExpression(factory, node, expression, hoistVariableDeclaration, temp); - expression = factory.createReflectSetCall(superClassReference, setterName, expression, classConstructor); - setOriginalNode(expression, node); - setTextRange(expression, node); - if (temp) { - expression = factory.createComma(expression, temp); - setTextRange(expression, node); - } - return expression; - } - } - } } - return visitEachChild(node, visitor, context); } + return visitEachChild(node, visitor, context); + } - function visitForStatement(node: ForStatement) { - return factory.updateForStatement( - node, - visitNode(node.initializer, discardedValueVisitor, isForInitializer), - visitNode(node.condition, visitor, isExpression), - visitNode(node.incrementor, discardedValueVisitor, isExpression), - visitIterationBody(node.statement, visitor, context) - ); - } + function visitForStatement(node: ForStatement) { + return factory.updateForStatement( + node, + visitNode(node.initializer, discardedValueVisitor, isForInitializer), + visitNode(node.condition, visitor, isExpression), + visitNode(node.incrementor, discardedValueVisitor, isExpression), + visitIterationBody(node.statement, visitor, context) + ); + } - function visitExpressionStatement(node: ExpressionStatement) { - return factory.updateExpressionStatement( - node, - visitNode(node.expression, discardedValueVisitor, isExpression) - ); - } + function visitExpressionStatement(node: ExpressionStatement) { + return factory.updateExpressionStatement( + node, + visitNode(node.expression, discardedValueVisitor, isExpression) + ); + } - function createCopiableReceiverExpr(receiver: Expression): { readExpression: Expression; initializeExpression: Expression | undefined } { - const clone = nodeIsSynthesized(receiver) ? receiver : factory.cloneNode(receiver); - if (isSimpleInlineableExpression(receiver)) { - return { readExpression: clone, initializeExpression: undefined }; - } - const readExpression = factory.createTempVariable(hoistVariableDeclaration); - const initializeExpression = factory.createAssignment(readExpression, clone); - return { readExpression, initializeExpression }; + function createCopiableReceiverExpr(receiver: Expression): { readExpression: Expression; initializeExpression: Expression | undefined } { + const clone = nodeIsSynthesized(receiver) ? receiver : factory.cloneNode(receiver); + if (isSimpleInlineableExpression(receiver)) { + return { readExpression: clone, initializeExpression: undefined }; } + const readExpression = factory.createTempVariable(hoistVariableDeclaration); + const initializeExpression = factory.createAssignment(readExpression, clone); + return { readExpression, initializeExpression }; + } - function visitCallExpression(node: CallExpression) { - if (shouldTransformPrivateElementsOrClassStaticBlocks && - isPrivateIdentifierPropertyAccessExpression(node.expression)) { - // obj.#x() + function visitCallExpression(node: CallExpression) { + if (shouldTransformPrivateElementsOrClassStaticBlocks && + isPrivateIdentifierPropertyAccessExpression(node.expression)) { + // obj.#x() - // Transform call expressions of private names to properly bind the `this` parameter. - const { thisArg, target } = factory.createCallBinding(node.expression, hoistVariableDeclaration, languageVersion); - if (isCallChain(node)) { - return factory.updateCallChain( - node, - factory.createPropertyAccessChain(visitNode(target, visitor), node.questionDotToken, "call"), - /*questionDotToken*/ undefined, - /*typeArguments*/ undefined, - [visitNode(thisArg, visitor, isExpression), ...visitNodes(node.arguments, visitor, isExpression)] - ); - } - return factory.updateCallExpression( + // Transform call expressions of private names to properly bind the `this` parameter. + const { thisArg, target } = factory.createCallBinding(node.expression, hoistVariableDeclaration, languageVersion); + if (isCallChain(node)) { + return factory.updateCallChain( node, - factory.createPropertyAccessExpression(visitNode(target, visitor), "call"), + factory.createPropertyAccessChain(visitNode(target, visitor), node.questionDotToken, "call"), + /*questionDotToken*/ undefined, /*typeArguments*/ undefined, [visitNode(thisArg, visitor, isExpression), ...visitNodes(node.arguments, visitor, isExpression)] ); } + return factory.updateCallExpression( + node, + factory.createPropertyAccessExpression(visitNode(target, visitor), "call"), + /*typeArguments*/ undefined, + [visitNode(thisArg, visitor, isExpression), ...visitNodes(node.arguments, visitor, isExpression)] + ); + } - if (shouldTransformSuperInStaticInitializers && - isSuperProperty(node.expression) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment?.classConstructor) { - // super.x() - // super[x]() - - // converts `super.f(...)` into `Reflect.get(_baseTemp, "f", _classTemp).call(_classTemp, ...)` - const invocation = factory.createFunctionCallCall( - visitNode(node.expression, visitor, isExpression), - currentClassLexicalEnvironment.classConstructor, - visitNodes(node.arguments, visitor, isExpression) - ); - setOriginalNode(invocation, node); - setTextRange(invocation, node); - return invocation; - } + if (shouldTransformSuperInStaticInitializers && + isSuperProperty(node.expression) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment?.classConstructor) { + // super.x() + // super[x]() - return visitEachChild(node, visitor, context); + // converts `super.f(...)` into `Reflect.get(_baseTemp, "f", _classTemp).call(_classTemp, ...)` + const invocation = factory.createFunctionCallCall( + visitNode(node.expression, visitor, isExpression), + currentClassLexicalEnvironment.classConstructor, + visitNodes(node.arguments, visitor, isExpression) + ); + setOriginalNode(invocation, node); + setTextRange(invocation, node); + return invocation; } - function visitTaggedTemplateExpression(node: TaggedTemplateExpression) { - if (shouldTransformPrivateElementsOrClassStaticBlocks && - isPrivateIdentifierPropertyAccessExpression(node.tag)) { - // Bind the `this` correctly for tagged template literals when the tag is a private identifier property access. - const { thisArg, target } = factory.createCallBinding(node.tag, hoistVariableDeclaration, languageVersion); - return factory.updateTaggedTemplateExpression( - node, - factory.createCallExpression( - factory.createPropertyAccessExpression(visitNode(target, visitor), "bind"), - /*typeArguments*/ undefined, - [visitNode(thisArg, visitor, isExpression)] - ), - /*typeArguments*/ undefined, - visitNode(node.template, visitor, isTemplateLiteral) - ); - } - if (shouldTransformSuperInStaticInitializers && - isSuperProperty(node.tag) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment?.classConstructor) { + return visitEachChild(node, visitor, context); + } - // converts `` super.f`x` `` into `` Reflect.get(_baseTemp, "f", _classTemp).bind(_classTemp)`x` `` - const invocation = factory.createFunctionBindCall( - visitNode(node.tag, visitor, isExpression), - currentClassLexicalEnvironment.classConstructor, - [] - ); - setOriginalNode(invocation, node); - setTextRange(invocation, node); - return factory.updateTaggedTemplateExpression( - node, - invocation, + function visitTaggedTemplateExpression(node: TaggedTemplateExpression) { + if (shouldTransformPrivateElementsOrClassStaticBlocks && + isPrivateIdentifierPropertyAccessExpression(node.tag)) { + // Bind the `this` correctly for tagged template literals when the tag is a private identifier property access. + const { thisArg, target } = factory.createCallBinding(node.tag, hoistVariableDeclaration, languageVersion); + return factory.updateTaggedTemplateExpression( + node, + factory.createCallExpression( + factory.createPropertyAccessExpression(visitNode(target, visitor), "bind"), /*typeArguments*/ undefined, - visitNode(node.template, visitor, isTemplateLiteral) - ); - } - return visitEachChild(node, visitor, context); + [visitNode(thisArg, visitor, isExpression)] + ), + /*typeArguments*/ undefined, + visitNode(node.template, visitor, isTemplateLiteral) + ); } + if (shouldTransformSuperInStaticInitializers && + isSuperProperty(node.tag) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment?.classConstructor) { - function transformClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { - if (shouldTransformPrivateElementsOrClassStaticBlocks) { - if (currentClassLexicalEnvironment) { - classLexicalEnvironmentMap.set(getOriginalNodeId(node), currentClassLexicalEnvironment); - } - - startLexicalEnvironment(); - let statements = setCurrentStaticPropertyDeclarationOrStaticBlockAnd( - node, - statements => visitNodes(statements, visitor, isStatement), - node.body.statements - ); - statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment()); + // converts `` super.f`x` `` into `` Reflect.get(_baseTemp, "f", _classTemp).bind(_classTemp)`x` `` + const invocation = factory.createFunctionBindCall( + visitNode(node.tag, visitor, isExpression), + currentClassLexicalEnvironment.classConstructor, + [] + ); + setOriginalNode(invocation, node); + setTextRange(invocation, node); + return factory.updateTaggedTemplateExpression( + node, + invocation, + /*typeArguments*/ undefined, + visitNode(node.template, visitor, isTemplateLiteral) + ); + } + return visitEachChild(node, visitor, context); + } - const iife = factory.createImmediatelyInvokedArrowFunction(statements); - setOriginalNode(iife, node); - setTextRange(iife, node); - addEmitFlags(iife, EmitFlags.AdviseOnEmitNode); - return iife; + function transformClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + if (shouldTransformPrivateElementsOrClassStaticBlocks) { + if (currentClassLexicalEnvironment) { + classLexicalEnvironmentMap.set(getOriginalNodeId(node), currentClassLexicalEnvironment); } + + startLexicalEnvironment(); + let statements = setCurrentStaticPropertyDeclarationOrStaticBlockAnd( + node, + statements => visitNodes(statements, visitor, isStatement), + node.body.statements + ); + statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment()); + + const iife = factory.createImmediatelyInvokedArrowFunction(statements); + setOriginalNode(iife, node); + setTextRange(iife, node); + addEmitFlags(iife, EmitFlags.AdviseOnEmitNode); + return iife; } + } - function visitBinaryExpression(node: BinaryExpression, valueIsDiscarded: boolean) { - if (isDestructuringAssignment(node)) { - // ({ x: obj.#x } = ...) - // ({ x: super.x } = ...) - // ({ x: super[x] } = ...) - const savedPendingExpressions = pendingExpressions; - pendingExpressions = undefined; - node = factory.updateBinaryExpression( - node, - visitNode(node.left, assignmentTargetVisitor), - node.operatorToken, - visitNode(node.right, visitor) - ); - const expr = some(pendingExpressions) ? - factory.inlineExpressions(compact([...pendingExpressions, node])) : - node; - pendingExpressions = savedPendingExpressions; - return expr; - } - if (isAssignmentExpression(node)) { - if (shouldTransformPrivateElementsOrClassStaticBlocks && - isPrivateIdentifierPropertyAccessExpression(node.left)) { - // obj.#x = ... - const info = accessPrivateIdentifier(node.left.name); - if (info) { - return setTextRange( - setOriginalNode( - createPrivateIdentifierAssignment(info, node.left.expression, node.right, node.operatorToken.kind), - node - ), + function visitBinaryExpression(node: BinaryExpression, valueIsDiscarded: boolean) { + if (isDestructuringAssignment(node)) { + // ({ x: obj.#x } = ...) + // ({ x: super.x } = ...) + // ({ x: super[x] } = ...) + const savedPendingExpressions = pendingExpressions; + pendingExpressions = undefined; + node = factory.updateBinaryExpression( + node, + visitNode(node.left, assignmentTargetVisitor), + node.operatorToken, + visitNode(node.right, visitor) + ); + const expr = some(pendingExpressions) ? + factory.inlineExpressions(compact([...pendingExpressions, node])) : + node; + pendingExpressions = savedPendingExpressions; + return expr; + } + if (isAssignmentExpression(node)) { + if (shouldTransformPrivateElementsOrClassStaticBlocks && + isPrivateIdentifierPropertyAccessExpression(node.left)) { + // obj.#x = ... + const info = accessPrivateIdentifier(node.left.name); + if (info) { + return setTextRange( + setOriginalNode( + createPrivateIdentifierAssignment(info, node.left.expression, node.right, node.operatorToken.kind), node - ); - } + ), + node + ); } - else if (shouldTransformSuperInStaticInitializers && - isSuperProperty(node.left) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment) { - // super.x = ... - // super[x] = ... - // super.x += ... - // super.x -= ... - const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; - if (facts & ClassFacts.ClassWasDecorated) { - return factory.updateBinaryExpression( - node, - visitInvalidSuperProperty(node.left), - node.operatorToken, - visitNode(node.right, visitor, isExpression)); - } - if (classConstructor && superClassReference) { - let setterName = - isElementAccessExpression(node.left) ? visitNode(node.left.argumentExpression, visitor, isExpression) : - isIdentifier(node.left.name) ? factory.createStringLiteralFromNode(node.left.name) : - undefined; - if (setterName) { - // converts `super.x = 1` into `(Reflect.set(_baseTemp, "x", _a = 1, _classTemp), _a)` - // converts `super[f()] = 1` into `(Reflect.set(_baseTemp, f(), _a = 1, _classTemp), _a)` - // converts `super.x += 1` into `(Reflect.set(_baseTemp, "x", _a = Reflect.get(_baseTemp, "x", _classtemp) + 1, _classTemp), _a)` - // converts `super[f()] += 1` into `(Reflect.set(_baseTemp, _a = f(), _b = Reflect.get(_baseTemp, _a, _classtemp) + 1, _classTemp), _b)` - - let expression = visitNode(node.right, visitor, isExpression); - if (isCompoundAssignment(node.operatorToken.kind)) { - let getterName = setterName; - if (!isSimpleInlineableExpression(setterName)) { - getterName = factory.createTempVariable(hoistVariableDeclaration); - setterName = factory.createAssignment(getterName, setterName); - } - const superPropertyGet = factory.createReflectGetCall( - superClassReference, - getterName, - classConstructor - ); - setOriginalNode(superPropertyGet, node.left); - setTextRange(superPropertyGet, node.left); - - expression = factory.createBinaryExpression( - superPropertyGet, - getNonAssignmentOperatorForCompoundAssignment(node.operatorToken.kind), - expression - ); - setTextRange(expression, node); - } - - const temp = valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); - if (temp) { - expression = factory.createAssignment(temp, expression); - setTextRange(temp, node); + } + else if (shouldTransformSuperInStaticInitializers && + isSuperProperty(node.left) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment) { + // super.x = ... + // super[x] = ... + // super.x += ... + // super.x -= ... + const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; + if (facts & ClassFacts.ClassWasDecorated) { + return factory.updateBinaryExpression( + node, + visitInvalidSuperProperty(node.left), + node.operatorToken, + visitNode(node.right, visitor, isExpression)); + } + if (classConstructor && superClassReference) { + let setterName = + isElementAccessExpression(node.left) ? visitNode(node.left.argumentExpression, visitor, isExpression) : + isIdentifier(node.left.name) ? factory.createStringLiteralFromNode(node.left.name) : + undefined; + if (setterName) { + // converts `super.x = 1` into `(Reflect.set(_baseTemp, "x", _a = 1, _classTemp), _a)` + // converts `super[f()] = 1` into `(Reflect.set(_baseTemp, f(), _a = 1, _classTemp), _a)` + // converts `super.x += 1` into `(Reflect.set(_baseTemp, "x", _a = Reflect.get(_baseTemp, "x", _classtemp) + 1, _classTemp), _a)` + // converts `super[f()] += 1` into `(Reflect.set(_baseTemp, _a = f(), _b = Reflect.get(_baseTemp, _a, _classtemp) + 1, _classTemp), _b)` + + let expression = visitNode(node.right, visitor, isExpression); + if (isCompoundAssignment(node.operatorToken.kind)) { + let getterName = setterName; + if (!isSimpleInlineableExpression(setterName)) { + getterName = factory.createTempVariable(hoistVariableDeclaration); + setterName = factory.createAssignment(getterName, setterName); } - - expression = factory.createReflectSetCall( + const superPropertyGet = factory.createReflectGetCall( superClassReference, - setterName, - expression, + getterName, classConstructor ); - setOriginalNode(expression, node); + setOriginalNode(superPropertyGet, node.left); + setTextRange(superPropertyGet, node.left); + + expression = factory.createBinaryExpression( + superPropertyGet, + getNonAssignmentOperatorForCompoundAssignment(node.operatorToken.kind), + expression + ); setTextRange(expression, node); + } - if (temp) { - expression = factory.createComma(expression, temp); - setTextRange(expression, node); - } + const temp = valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); + if (temp) { + expression = factory.createAssignment(temp, expression); + setTextRange(temp, node); + } + + expression = factory.createReflectSetCall( + superClassReference, + setterName, + expression, + classConstructor + ); + setOriginalNode(expression, node); + setTextRange(expression, node); - return expression; + if (temp) { + expression = factory.createComma(expression, temp); + setTextRange(expression, node); } + + return expression; } } } - if (shouldTransformPrivateElementsOrClassStaticBlocks && - isPrivateIdentifierInExpression(node)) { - // #x in obj - return transformPrivateIdentifierInInExpression(node); - } - return visitEachChild(node, visitor, context); } + if (shouldTransformPrivateElementsOrClassStaticBlocks && + isPrivateIdentifierInExpression(node)) { + // #x in obj + return transformPrivateIdentifierInInExpression(node); + } + return visitEachChild(node, visitor, context); + } - function createPrivateIdentifierAssignment(info: PrivateIdentifierInfo, receiver: Expression, right: Expression, operator: AssignmentOperator): Expression { - receiver = visitNode(receiver, visitor, isExpression); - right = visitNode(right, visitor, isExpression); - - if (isCompoundAssignment(operator)) { - const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver); - receiver = initializeExpression || readExpression; - right = factory.createBinaryExpression( - createPrivateIdentifierAccessHelper(info, readExpression), - getNonAssignmentOperatorForCompoundAssignment(operator), - right - ); - } + function createPrivateIdentifierAssignment(info: PrivateIdentifierInfo, receiver: Expression, right: Expression, operator: AssignmentOperator): Expression { + receiver = visitNode(receiver, visitor, isExpression); + right = visitNode(right, visitor, isExpression); + + if (isCompoundAssignment(operator)) { + const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver); + receiver = initializeExpression || readExpression; + right = factory.createBinaryExpression( + createPrivateIdentifierAccessHelper(info, readExpression), + getNonAssignmentOperatorForCompoundAssignment(operator), + right + ); + } - setCommentRange(receiver, moveRangePos(receiver, -1)); + setCommentRange(receiver, moveRangePos(receiver, -1)); - switch(info.kind) { - case PrivateIdentifierKind.Accessor: - return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( - receiver, - info.brandCheckIdentifier, - right, - info.kind, - info.setterName - ); - case PrivateIdentifierKind.Method: - return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( - receiver, - info.brandCheckIdentifier, - right, - info.kind, - /* f */ undefined - ); - case PrivateIdentifierKind.Field: - return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( - receiver, - info.brandCheckIdentifier, - right, - info.kind, - info.variableName - ); - default: - Debug.assertNever(info, "Unknown private element type"); - } + switch(info.kind) { + case PrivateIdentifierKind.Accessor: + return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( + receiver, + info.brandCheckIdentifier, + right, + info.kind, + info.setterName + ); + case PrivateIdentifierKind.Method: + return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( + receiver, + info.brandCheckIdentifier, + right, + info.kind, + /* f */ undefined + ); + case PrivateIdentifierKind.Field: + return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( + receiver, + info.brandCheckIdentifier, + right, + info.kind, + info.variableName + ); + default: + Debug.assertNever(info, "Unknown private element type"); } + } - function getPrivateInstanceMethodsAndAccessors(node: ClassLikeDeclaration) { - return filter(node.members, isNonStaticMethodOrAccessorWithPrivateName); - } + function getPrivateInstanceMethodsAndAccessors(node: ClassLikeDeclaration) { + return filter(node.members, isNonStaticMethodOrAccessorWithPrivateName); + } - function getClassFacts(node: ClassLikeDeclaration) { - let facts = ClassFacts.None; - const original = getOriginalNode(node); - if (isClassDeclaration(original) && classOrConstructorParameterIsDecorated(original)) { - facts |= ClassFacts.ClassWasDecorated; - } - for (const member of node.members) { - if (!isStatic(member)) continue; - if (member.name && (isPrivateIdentifier(member.name) || isAutoAccessorPropertyDeclaration(member)) && shouldTransformPrivateElementsOrClassStaticBlocks) { - facts |= ClassFacts.NeedsClassConstructorReference; - } - if (isPropertyDeclaration(member) || isClassStaticBlockDeclaration(member)) { - if (shouldTransformThisInStaticInitializers && member.transformFlags & TransformFlags.ContainsLexicalThis) { - facts |= ClassFacts.NeedsSubstitutionForThisInClassStaticField; - if (!(facts & ClassFacts.ClassWasDecorated)) { - facts |= ClassFacts.NeedsClassConstructorReference; - } + function getClassFacts(node: ClassLikeDeclaration) { + let facts = ClassFacts.None; + const original = getOriginalNode(node); + if (isClassDeclaration(original) && classOrConstructorParameterIsDecorated(original)) { + facts |= ClassFacts.ClassWasDecorated; + } + for (const member of node.members) { + if (!isStatic(member)) continue; + if (member.name && (isPrivateIdentifier(member.name) || isAutoAccessorPropertyDeclaration(member)) && shouldTransformPrivateElementsOrClassStaticBlocks) { + facts |= ClassFacts.NeedsClassConstructorReference; + } + if (isPropertyDeclaration(member) || isClassStaticBlockDeclaration(member)) { + if (shouldTransformThisInStaticInitializers && member.transformFlags & TransformFlags.ContainsLexicalThis) { + facts |= ClassFacts.NeedsSubstitutionForThisInClassStaticField; + if (!(facts & ClassFacts.ClassWasDecorated)) { + facts |= ClassFacts.NeedsClassConstructorReference; } - if (shouldTransformSuperInStaticInitializers && member.transformFlags & TransformFlags.ContainsLexicalSuper) { - if (!(facts & ClassFacts.ClassWasDecorated)) { - facts |= ClassFacts.NeedsClassConstructorReference | ClassFacts.NeedsClassSuperReference; - } + } + if (shouldTransformSuperInStaticInitializers && member.transformFlags & TransformFlags.ContainsLexicalSuper) { + if (!(facts & ClassFacts.ClassWasDecorated)) { + facts |= ClassFacts.NeedsClassConstructorReference | ClassFacts.NeedsClassSuperReference; } } } - return facts; } + return facts; + } - function visitExpressionWithTypeArgumentsInHeritageClause(node: ExpressionWithTypeArguments) { - const facts = currentClassLexicalEnvironment?.facts || ClassFacts.None; - if (facts & ClassFacts.NeedsClassSuperReference) { - const temp = factory.createTempVariable(hoistVariableDeclaration, /*reserveInNestedScopes*/ true); - getClassLexicalEnvironment().superClassReference = temp; - return factory.updateExpressionWithTypeArguments( - node, - factory.createAssignment( - temp, - visitNode(node.expression, visitor, isExpression) - ), - /*typeArguments*/ undefined - ); - } - return visitEachChild(node, visitor, context); + function visitExpressionWithTypeArgumentsInHeritageClause(node: ExpressionWithTypeArguments) { + const facts = currentClassLexicalEnvironment?.facts || ClassFacts.None; + if (facts & ClassFacts.NeedsClassSuperReference) { + const temp = factory.createTempVariable(hoistVariableDeclaration, /*reserveInNestedScopes*/ true); + getClassLexicalEnvironment().superClassReference = temp; + return factory.updateExpressionWithTypeArguments( + node, + factory.createAssignment( + temp, + visitNode(node.expression, visitor, isExpression) + ), + /*typeArguments*/ undefined + ); } + return visitEachChild(node, visitor, context); + } - function visitInNewClassLexicalEnvironment(node: T, visitor: (node: T, facts: ClassFacts) => U) { - const savedCurrentClassContainer = currentClassContainer; - const savedPendingExpressions = pendingExpressions; - currentClassContainer = node; - pendingExpressions = undefined; - startClassLexicalEnvironment(); - - if (shouldTransformPrivateElementsOrClassStaticBlocks) { - const name = getNameOfDeclaration(node); - if (name && isIdentifier(name)) { - getPrivateIdentifierEnvironment().className = name; - } - - const privateInstanceMethodsAndAccessors = getPrivateInstanceMethodsAndAccessors(node); - if (some(privateInstanceMethodsAndAccessors)) { - getPrivateIdentifierEnvironment().weakSetName = createHoistedVariableForClass( - "instances", - privateInstanceMethodsAndAccessors[0].name - ); - } - } + function visitInNewClassLexicalEnvironment(node: T, visitor: (node: T, facts: ClassFacts) => U) { + const savedCurrentClassContainer = currentClassContainer; + const savedPendingExpressions = pendingExpressions; + currentClassContainer = node; + pendingExpressions = undefined; + startClassLexicalEnvironment(); - const facts = getClassFacts(node); - if (facts) { - getClassLexicalEnvironment().facts = facts; + if (shouldTransformPrivateElementsOrClassStaticBlocks) { + const name = getNameOfDeclaration(node); + if (name && isIdentifier(name)) { + getPrivateIdentifierEnvironment().className = name; } - if (facts & ClassFacts.NeedsSubstitutionForThisInClassStaticField) { - enableSubstitutionForClassStaticThisOrSuperReference(); + const privateInstanceMethodsAndAccessors = getPrivateInstanceMethodsAndAccessors(node); + if (some(privateInstanceMethodsAndAccessors)) { + getPrivateIdentifierEnvironment().weakSetName = createHoistedVariableForClass( + "instances", + privateInstanceMethodsAndAccessors[0].name + ); } - - const result = visitor(node, facts); - endClassLexicalEnvironment(); - currentClassContainer = savedCurrentClassContainer; - pendingExpressions = savedPendingExpressions; - return result; - } - function visitClassDeclaration(node: ClassDeclaration) { - return visitInNewClassLexicalEnvironment(node, visitClassDeclarationInNewClassLexicalEnvironment); + const facts = getClassFacts(node); + if (facts) { + getClassLexicalEnvironment().facts = facts; } - function visitClassDeclarationInNewClassLexicalEnvironment(node: ClassDeclaration, facts: ClassFacts) { - // If a class has private static fields, or a static field has a `this` or `super` reference, - // then we need to allocate a temp variable to hold on to that reference. - let pendingClassReferenceAssignment: BinaryExpression | undefined; - if (facts & ClassFacts.NeedsClassConstructorReference) { - const temp = factory.createTempVariable(hoistVariableDeclaration, /*reservedInNestedScopes*/ true); - getClassLexicalEnvironment().classConstructor = factory.cloneNode(temp); - pendingClassReferenceAssignment = factory.createAssignment(temp, factory.getInternalName(node)); - } - - const modifiers = visitNodes(node.modifiers, visitor, isModifierLike); - const heritageClauses = visitNodes(node.heritageClauses, heritageClauseVisitor, isHeritageClause); - const { members, prologue } = transformClassMembers(node); - const classDecl = factory.updateClassDeclaration( - node, - modifiers, - node.name, - /*typeParameters*/ undefined, - heritageClauses, - members - ); + if (facts & ClassFacts.NeedsSubstitutionForThisInClassStaticField) { + enableSubstitutionForClassStaticThisOrSuperReference(); + } - const statements: Statement[] = []; - if (prologue) { - statements.push(factory.createExpressionStatement(prologue)); - } + const result = visitor(node, facts); + endClassLexicalEnvironment(); + currentClassContainer = savedCurrentClassContainer; + pendingExpressions = savedPendingExpressions; + return result; - statements.push(classDecl); + } - if (pendingClassReferenceAssignment) { - getPendingExpressions().unshift(pendingClassReferenceAssignment); - } + function visitClassDeclaration(node: ClassDeclaration) { + return visitInNewClassLexicalEnvironment(node, visitClassDeclarationInNewClassLexicalEnvironment); + } - // Write any pending expressions from elided or moved computed property names - if (some(pendingExpressions)) { - statements.push(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))); - } + function visitClassDeclarationInNewClassLexicalEnvironment(node: ClassDeclaration, facts: ClassFacts) { + // If a class has private static fields, or a static field has a `this` or `super` reference, + // then we need to allocate a temp variable to hold on to that reference. + let pendingClassReferenceAssignment: BinaryExpression | undefined; + if (facts & ClassFacts.NeedsClassConstructorReference) { + const temp = factory.createTempVariable(hoistVariableDeclaration, /*reservedInNestedScopes*/ true); + getClassLexicalEnvironment().classConstructor = factory.cloneNode(temp); + pendingClassReferenceAssignment = factory.createAssignment(temp, factory.getInternalName(node)); + } + + const modifiers = visitNodes(node.modifiers, visitor, isModifierLike); + const heritageClauses = visitNodes(node.heritageClauses, heritageClauseVisitor, isHeritageClause); + const { members, prologue } = transformClassMembers(node); + const classDecl = factory.updateClassDeclaration( + node, + modifiers, + node.name, + /*typeParameters*/ undefined, + heritageClauses, + members + ); - if (shouldTransformInitializersUsingSet || shouldTransformPrivateElementsOrClassStaticBlocks) { - // 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 statements: Statement[] = []; + if (prologue) { + statements.push(factory.createExpressionStatement(prologue)); + } - const staticProperties = getStaticPropertiesAndClassStaticBlock(node); - if (some(staticProperties)) { - addPropertyOrClassStaticBlockStatements(statements, staticProperties, factory.getInternalName(node)); - } - } + statements.push(classDecl); - return statements; + if (pendingClassReferenceAssignment) { + getPendingExpressions().unshift(pendingClassReferenceAssignment); } - function visitClassExpression(node: ClassExpression): Expression { - return visitInNewClassLexicalEnvironment(node, visitClassExpressionInNewClassLexicalEnvironment); + // Write any pending expressions from elided or moved computed property names + if (some(pendingExpressions)) { + statements.push(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))); } - function visitClassExpressionInNewClassLexicalEnvironment(node: ClassExpression, facts: ClassFacts): Expression { - // 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 = !!(facts & ClassFacts.ClassWasDecorated); - - const staticPropertiesOrClassStaticBlocks = getStaticPropertiesAndClassStaticBlock(node); - - const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; - let temp: Identifier | undefined; - function createClassTempVar() { - const classCheckFlags = resolver.getNodeCheckFlags(node); - const isClassWithConstructorReference = classCheckFlags & NodeCheckFlags.ClassWithConstructorReference; - const requiresBlockScopedVar = classCheckFlags & NodeCheckFlags.BlockScopedBindingInLoop; - return factory.createTempVariable(requiresBlockScopedVar ? addBlockScopedVariable : hoistVariableDeclaration, !!isClassWithConstructorReference); - } + if (shouldTransformInitializersUsingSet || shouldTransformPrivateElementsOrClassStaticBlocks) { + // 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.NeedsClassConstructorReference) { - temp = createClassTempVar(); - getClassLexicalEnvironment().classConstructor = factory.cloneNode(temp); + const staticProperties = getStaticPropertiesAndClassStaticBlock(node); + if (some(staticProperties)) { + addPropertyOrClassStaticBlockStatements(statements, staticProperties, factory.getInternalName(node)); } + } - const modifiers = visitNodes(node.modifiers, visitor, isModifierLike); - const heritageClauses = visitNodes(node.heritageClauses, heritageClauseVisitor, isHeritageClause); - const { members, prologue } = transformClassMembers(node); - const classExpression = factory.updateClassExpression( - node, - modifiers, - node.name, - /*typeParameters*/ undefined, - heritageClauses, - members - ); + return statements; + } - const expressions: Expression[] = []; - if (prologue) { - expressions.push(prologue); - } + function visitClassExpression(node: ClassExpression): Expression { + return visitInNewClassLexicalEnvironment(node, visitClassExpressionInNewClassLexicalEnvironment); + } - // Static initializers are transformed to `static {}` blocks when `useDefineForClassFields: false` - // and not also transforming static blocks. - const hasTransformableStatics = - shouldTransformPrivateElementsOrClassStaticBlocks && - some(staticPropertiesOrClassStaticBlocks, node => - isClassStaticBlockDeclaration(node) || - isPrivateIdentifierClassElementDeclaration(node) || - shouldTransformInitializers && isInitializedProperty(node)); - - if (hasTransformableStatics || some(pendingExpressions)) { - if (isDecoratedClassDeclaration) { - Debug.assertIsDefined(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(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))); - } + function visitClassExpressionInNewClassLexicalEnvironment(node: ClassExpression, facts: ClassFacts): Expression { + // 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 = !!(facts & ClassFacts.ClassWasDecorated); + + const staticPropertiesOrClassStaticBlocks = getStaticPropertiesAndClassStaticBlock(node); + + const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; + let temp: Identifier | undefined; + function createClassTempVar() { + const classCheckFlags = resolver.getNodeCheckFlags(node); + const isClassWithConstructorReference = classCheckFlags & NodeCheckFlags.ClassWithConstructorReference; + const requiresBlockScopedVar = classCheckFlags & NodeCheckFlags.BlockScopedBindingInLoop; + return factory.createTempVariable(requiresBlockScopedVar ? addBlockScopedVariable : hoistVariableDeclaration, !!isClassWithConstructorReference); + } + + if (facts & ClassFacts.NeedsClassConstructorReference) { + temp = createClassTempVar(); + getClassLexicalEnvironment().classConstructor = factory.cloneNode(temp); + } + + const modifiers = visitNodes(node.modifiers, visitor, isModifierLike); + const heritageClauses = visitNodes(node.heritageClauses, heritageClauseVisitor, isHeritageClause); + const { members, prologue } = transformClassMembers(node); + const classExpression = factory.updateClassExpression( + node, + modifiers, + node.name, + /*typeParameters*/ undefined, + heritageClauses, + members + ); - if (pendingStatements && some(staticPropertiesOrClassStaticBlocks)) { - addPropertyOrClassStaticBlockStatements(pendingStatements, staticPropertiesOrClassStaticBlocks, factory.getInternalName(node)); - } + const expressions: Expression[] = []; + if (prologue) { + expressions.push(prologue); + } - if (temp) { - expressions.push( - startOnNewLine(factory.createAssignment(temp, classExpression)), - startOnNewLine(temp)); - } - else { - expressions.push(classExpression); - if (prologue) { - startOnNewLine(classExpression); - } - } + // Static initializers are transformed to `static {}` blocks when `useDefineForClassFields: false` + // and not also transforming static blocks. + const hasTransformableStatics = + shouldTransformPrivateElementsOrClassStaticBlocks && + some(staticPropertiesOrClassStaticBlocks, node => + isClassStaticBlockDeclaration(node) || + isPrivateIdentifierClassElementDeclaration(node) || + shouldTransformInitializers && isInitializedProperty(node)); + + if (hasTransformableStatics || some(pendingExpressions)) { + if (isDecoratedClassDeclaration) { + Debug.assertIsDefined(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(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))); + } + + if (pendingStatements && some(staticPropertiesOrClassStaticBlocks)) { + addPropertyOrClassStaticBlockStatements(pendingStatements, staticPropertiesOrClassStaticBlocks, factory.getInternalName(node)); + } + + if (temp) { + expressions.push( + startOnNewLine(factory.createAssignment(temp, classExpression)), + startOnNewLine(temp)); } else { - temp ||= createClassTempVar(); - if (isClassWithConstructorReference) { - // record an alias as the class name is not in scope for statics. - enableSubstitutionForClassAliases(); - const alias = factory.cloneNode(temp) as GeneratedIdentifier; - alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; - classAliases[getOriginalNodeId(node)] = alias; + expressions.push(classExpression); + if (prologue) { + startOnNewLine(classExpression); } - - // 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(factory.createAssignment(temp, classExpression))); - // Add any pending expressions leftover from elided or relocated computed property names - addRange(expressions, map(pendingExpressions, startOnNewLine)); - addRange(expressions, generateInitializedPropertyExpressionsOrClassStaticBlock(staticPropertiesOrClassStaticBlocks, temp)); - expressions.push(startOnNewLine(temp)); } } else { - expressions.push(classExpression); - if (prologue) { - startOnNewLine(classExpression); + temp ||= createClassTempVar(); + if (isClassWithConstructorReference) { + // record an alias as the class name is not in scope for statics. + enableSubstitutionForClassAliases(); + const alias = factory.cloneNode(temp) as GeneratedIdentifier; + alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; + classAliases[getOriginalNodeId(node)] = alias; } - } - return factory.inlineExpressions(expressions); + // 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(factory.createAssignment(temp, classExpression))); + // Add any pending expressions leftover from elided or relocated computed property names + addRange(expressions, map(pendingExpressions, startOnNewLine)); + addRange(expressions, generateInitializedPropertyExpressionsOrClassStaticBlock(staticPropertiesOrClassStaticBlocks, temp)); + expressions.push(startOnNewLine(temp)); + } } - - function visitClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { - if (!shouldTransformPrivateElementsOrClassStaticBlocks) { - return visitEachChild(node, visitor, context); + else { + expressions.push(classExpression); + if (prologue) { + startOnNewLine(classExpression); } - // ClassStaticBlockDeclaration for classes are transformed in `visitClassDeclaration` or `visitClassExpression`. - return undefined; } - function transformClassMembers(node: ClassDeclaration | ClassExpression) { - // Declare private names - if (shouldTransformPrivateElementsOrClassStaticBlocks) { - for (const member of node.members) { - if (isPrivateIdentifierClassElementDeclaration(member)) { - addPrivateIdentifierToEnvironment(member, member.name, addPrivateIdentifierClassElementToEnvironment); - } - } - if (some(getPrivateInstanceMethodsAndAccessors(node))) { - createBrandCheckWeakSetForPrivateMethods(); + return factory.inlineExpressions(expressions); + } + + function visitClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + if (!shouldTransformPrivateElementsOrClassStaticBlocks) { + return visitEachChild(node, visitor, context); + } + // ClassStaticBlockDeclaration for classes are transformed in `visitClassDeclaration` or `visitClassExpression`. + return undefined; + } + + function transformClassMembers(node: ClassDeclaration | ClassExpression) { + // Declare private names + if (shouldTransformPrivateElementsOrClassStaticBlocks) { + for (const member of node.members) { + if (isPrivateIdentifierClassElementDeclaration(member)) { + addPrivateIdentifierToEnvironment(member, member.name, addPrivateIdentifierClassElementToEnvironment); } - if (shouldTransformAutoAccessors) { - for (const member of node.members) { - if (isAutoAccessorPropertyDeclaration(member)) { - const storageName = factory.getGeneratedPrivateNameForNode(member.name, /*prefix*/ undefined, "_accessor_storage"); - addPrivateIdentifierToEnvironment(member, storageName, addPrivateIdentifierPropertyDeclarationToEnvironment); - } + } + if (some(getPrivateInstanceMethodsAndAccessors(node))) { + createBrandCheckWeakSetForPrivateMethods(); + } + if (shouldTransformAutoAccessors) { + for (const member of node.members) { + if (isAutoAccessorPropertyDeclaration(member)) { + const storageName = factory.getGeneratedPrivateNameForNode(member.name, /*prefix*/ undefined, "_accessor_storage"); + addPrivateIdentifierToEnvironment(member, storageName, addPrivateIdentifierPropertyDeclarationToEnvironment); } } } + } - let members = visitNodes(node.members, classElementVisitor, isClassElement); + let members = visitNodes(node.members, classElementVisitor, isClassElement); - // Create a synthetic constructor if necessary - let syntheticConstructor: ConstructorDeclaration | undefined; - if (!some(members, isConstructorDeclaration)) { - syntheticConstructor = transformConstructor(/*constructor*/ undefined, node); - } + // Create a synthetic constructor if necessary + let syntheticConstructor: ConstructorDeclaration | undefined; + if (!some(members, isConstructorDeclaration)) { + syntheticConstructor = transformConstructor(/*constructor*/ undefined, node); + } - let prologue: Expression | undefined; - - // If there are pending expressions create a class static block in which to evaluate them, but only if - // class static blocks are not also being transformed. This block will be injected at the top of the class - // to ensure that expressions from computed property names are evaluated before any other static - // initializers. - let syntheticStaticBlock: ClassStaticBlockDeclaration | undefined; - if (!shouldTransformPrivateElementsOrClassStaticBlocks && some(pendingExpressions)) { - let statement = factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions)); - if (statement.transformFlags & TransformFlags.ContainsLexicalThisOrSuper) { - // If there are `this` or `super` references from computed property names, shift the expression - // into an arrow function to be evaluated in the outer scope so that `this` and `super` are - // properly captured. - const temp = factory.createTempVariable(hoistVariableDeclaration); - const arrow = factory.createArrowFunction( - /*modifiers*/ undefined, - /*typeParameters*/ undefined, - /*parameters*/ [], - /*type*/ undefined, - /*equalsGreaterThanToken*/ undefined, - factory.createBlock([statement])); - prologue = factory.createAssignment(temp, arrow); - statement = factory.createExpressionStatement(factory.createCallExpression(temp, /*typeArguments*/ undefined, [])); - } + let prologue: Expression | undefined; - const block = factory.createBlock([statement]); - syntheticStaticBlock = factory.createClassStaticBlockDeclaration(block); - pendingExpressions = undefined; + // If there are pending expressions create a class static block in which to evaluate them, but only if + // class static blocks are not also being transformed. This block will be injected at the top of the class + // to ensure that expressions from computed property names are evaluated before any other static + // initializers. + let syntheticStaticBlock: ClassStaticBlockDeclaration | undefined; + if (!shouldTransformPrivateElementsOrClassStaticBlocks && some(pendingExpressions)) { + let statement = factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions)); + if (statement.transformFlags & TransformFlags.ContainsLexicalThisOrSuper) { + // If there are `this` or `super` references from computed property names, shift the expression + // into an arrow function to be evaluated in the outer scope so that `this` and `super` are + // properly captured. + const temp = factory.createTempVariable(hoistVariableDeclaration); + const arrow = factory.createArrowFunction( + /*modifiers*/ undefined, + /*typeParameters*/ undefined, + /*parameters*/ [], + /*type*/ undefined, + /*equalsGreaterThanToken*/ undefined, + factory.createBlock([statement])); + prologue = factory.createAssignment(temp, arrow); + statement = factory.createExpressionStatement(factory.createCallExpression(temp, /*typeArguments*/ undefined, [])); } - // If we created a synthetic constructor or class static block, add them to the visited members - // and return a new array. - if (syntheticConstructor || syntheticStaticBlock) { - let membersArray: ClassElement[] | undefined; - membersArray = append(membersArray, syntheticConstructor); - membersArray = append(membersArray, syntheticStaticBlock); - membersArray = addRange(membersArray, members); - members = setTextRange(factory.createNodeArray(membersArray), /*location*/ node.members); - } + const block = factory.createBlock([statement]); + syntheticStaticBlock = factory.createClassStaticBlockDeclaration(block); + pendingExpressions = undefined; + } - return { members, prologue }; + // If we created a synthetic constructor or class static block, add them to the visited members + // and return a new array. + if (syntheticConstructor || syntheticStaticBlock) { + let membersArray: ClassElement[] | undefined; + membersArray = append(membersArray, syntheticConstructor); + membersArray = append(membersArray, syntheticStaticBlock); + membersArray = addRange(membersArray, members); + members = setTextRange(factory.createNodeArray(membersArray), /*location*/ node.members); } - function createBrandCheckWeakSetForPrivateMethods() { - const { weakSetName } = getPrivateIdentifierEnvironment(); - Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); + return { members, prologue }; + } + + function createBrandCheckWeakSetForPrivateMethods() { + const { weakSetName } = getPrivateIdentifierEnvironment(); + Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); - getPendingExpressions().push( - factory.createAssignment( - weakSetName, - factory.createNewExpression( - factory.createIdentifier("WeakSet"), - /*typeArguments*/ undefined, - [] - ) + getPendingExpressions().push( + factory.createAssignment( + weakSetName, + factory.createNewExpression( + factory.createIdentifier("WeakSet"), + /*typeArguments*/ undefined, + [] ) - ); + ) + ); + } + + function isClassElementThatRequiresConstructorStatement(member: ClassElement) { + if (isStatic(member) || hasAbstractModifier(getOriginalNode(member))) { + return false; } - function isClassElementThatRequiresConstructorStatement(member: ClassElement) { - if (isStatic(member) || hasAbstractModifier(getOriginalNode(member))) { - return false; - } + return shouldTransformInitializersUsingDefine && isPropertyDeclaration(member) || + shouldTransformInitializersUsingSet && isInitializedProperty(member) || + shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierClassElementDeclaration(member) || + shouldTransformPrivateElementsOrClassStaticBlocks && shouldTransformAutoAccessors && isAutoAccessorPropertyDeclaration(member); + } - return shouldTransformInitializersUsingDefine && isPropertyDeclaration(member) || - shouldTransformInitializersUsingSet && isInitializedProperty(member) || - shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierClassElementDeclaration(member) || - shouldTransformPrivateElementsOrClassStaticBlocks && shouldTransformAutoAccessors && isAutoAccessorPropertyDeclaration(member); + function transformConstructor(constructor: ConstructorDeclaration | undefined, container: ClassDeclaration | ClassExpression) { + constructor = visitNode(constructor, visitor, isConstructorDeclaration); + if (!some(container.members, isClassElementThatRequiresConstructorStatement)) { + return constructor; } - function transformConstructor(constructor: ConstructorDeclaration | undefined, container: ClassDeclaration | ClassExpression) { - constructor = visitNode(constructor, visitor, isConstructorDeclaration); - if (!some(container.members, isClassElementThatRequiresConstructorStatement)) { - return constructor; - } - - const extendsClauseElement = getEffectiveBaseTypeNode(container); - const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); - const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context); - const body = transformConstructorBody(container, constructor, isDerivedClass); - if (!body) { - return constructor; - } + const extendsClauseElement = getEffectiveBaseTypeNode(container); + const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); + const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context); + const body = transformConstructorBody(container, constructor, isDerivedClass); + if (!body) { + return constructor; + } - if (constructor) { - Debug.assert(parameters); - return factory.updateConstructorDeclaration(constructor, /*modifiers*/ undefined, parameters, body); - } + if (constructor) { + Debug.assert(parameters); + return factory.updateConstructorDeclaration(constructor, /*modifiers*/ undefined, parameters, body); + } - return startOnNewLine( - setOriginalNode( - setTextRange( - factory.createConstructorDeclaration( - /*modifiers*/ undefined, - parameters ?? [], - body - ), - constructor || container + return startOnNewLine( + setOriginalNode( + setTextRange( + factory.createConstructorDeclaration( + /*modifiers*/ undefined, + parameters ?? [], + body ), - constructor - ) - ); + constructor || container + ), + constructor + ) + ); + } + + function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) { + let properties = getProperties(node, /*requireInitializer*/ false, /*isStatic*/ false); + if (!useDefineForClassFields) { + properties = filter(properties, property => !!property.initializer || isPrivateIdentifier(property.name) || hasAccessorModifier(property)); } - function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) { - let properties = getProperties(node, /*requireInitializer*/ false, /*isStatic*/ false); - if (!useDefineForClassFields) { - properties = filter(properties, property => !!property.initializer || isPrivateIdentifier(property.name) || hasAccessorModifier(property)); - } + const privateMethodsAndAccessors = getPrivateInstanceMethodsAndAccessors(node); + const needsConstructorBody = some(properties) || some(privateMethodsAndAccessors); - const privateMethodsAndAccessors = getPrivateInstanceMethodsAndAccessors(node); - const needsConstructorBody = some(properties) || some(privateMethodsAndAccessors); + // Only generate synthetic constructor when there are property initializers to move. + if (!constructor && !needsConstructorBody) { + return visitFunctionBody(/*node*/ undefined, visitor, context); + } - // Only generate synthetic constructor when there are property initializers to move. - if (!constructor && !needsConstructorBody) { - return visitFunctionBody(/*node*/ undefined, visitor, context); - } + resumeLexicalEnvironment(); - resumeLexicalEnvironment(); - - const needsSyntheticConstructor = !constructor && isDerivedClass; - let indexOfFirstStatementAfterSuperAndPrologue = 0; - let prologueStatementCount = 0; - let superStatementIndex = -1; - let statements: Statement[] = []; - - if (constructor?.body?.statements) { - prologueStatementCount = factory.copyPrologue(constructor.body.statements, statements, /*ensureUseStrict*/ false, visitor); - superStatementIndex = findSuperStatementIndex(constructor.body.statements, prologueStatementCount); - - // If there was a super call, visit existing statements up to and including it - if (superStatementIndex >= 0) { - indexOfFirstStatementAfterSuperAndPrologue = superStatementIndex + 1; - statements = [ - ...statements.slice(0, prologueStatementCount), - ...visitNodes(constructor.body.statements, visitor, isStatement, prologueStatementCount, indexOfFirstStatementAfterSuperAndPrologue - prologueStatementCount), - ...statements.slice(prologueStatementCount), - ]; - } - else if (prologueStatementCount >= 0) { - indexOfFirstStatementAfterSuperAndPrologue = prologueStatementCount; - } - } + const needsSyntheticConstructor = !constructor && isDerivedClass; + let indexOfFirstStatementAfterSuperAndPrologue = 0; + let prologueStatementCount = 0; + let superStatementIndex = -1; + let statements: Statement[] = []; - if (needsSyntheticConstructor) { - // Add a synthetic `super` call: - // - // super(...arguments); - // - statements.push( - factory.createExpressionStatement( - factory.createCallExpression( - factory.createSuper(), - /*typeArguments*/ undefined, - [factory.createSpreadElement(factory.createIdentifier("arguments"))] - ) - ) - ); + if (constructor?.body?.statements) { + prologueStatementCount = factory.copyPrologue(constructor.body.statements, statements, /*ensureUseStrict*/ false, visitor); + superStatementIndex = findSuperStatementIndex(constructor.body.statements, prologueStatementCount); + + // If there was a super call, visit existing statements up to and including it + if (superStatementIndex >= 0) { + indexOfFirstStatementAfterSuperAndPrologue = superStatementIndex + 1; + statements = [ + ...statements.slice(0, prologueStatementCount), + ...visitNodes(constructor.body.statements, visitor, isStatement, prologueStatementCount, indexOfFirstStatementAfterSuperAndPrologue - prologueStatementCount), + ...statements.slice(prologueStatementCount), + ]; } + else if (prologueStatementCount >= 0) { + indexOfFirstStatementAfterSuperAndPrologue = prologueStatementCount; + } + } - // Add the property initializers. Transforms this: - // - // public x = 1; - // - // Into this: + if (needsSyntheticConstructor) { + // Add a synthetic `super` call: // - // constructor() { - // this.x = 1; - // } + // super(...arguments); // - // If we do useDefineForClassFields, they'll be converted elsewhere. - // We instead *remove* them from the transformed output at this stage. - let parameterPropertyDeclarationCount = 0; - if (constructor?.body) { - if (useDefineForClassFields) { - statements = statements.filter(statement => !isParameterPropertyDeclaration(getOriginalNode(statement), constructor)); - } - else { - for (const statement of constructor.body.statements) { - if (isParameterPropertyDeclaration(getOriginalNode(statement), constructor)) { - parameterPropertyDeclarationCount++; - } - } - if (parameterPropertyDeclarationCount > 0) { - const parameterProperties = visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatementAfterSuperAndPrologue, parameterPropertyDeclarationCount); - - // If there was a super() call found, add parameter properties immediately after it - if (superStatementIndex >= 0) { - addRange(statements, parameterProperties); - } - else { - // Add add parameter properties to the top of the constructor after the prologue - let superAndPrologueStatementCount = prologueStatementCount; - // If a synthetic super() call was added, need to account for that - if (needsSyntheticConstructor) superAndPrologueStatementCount++; - statements = [ - ...statements.slice(0, superAndPrologueStatementCount), - ...parameterProperties, - ...statements.slice(superAndPrologueStatementCount), - ]; - } + statements.push( + factory.createExpressionStatement( + factory.createCallExpression( + factory.createSuper(), + /*typeArguments*/ undefined, + [factory.createSpreadElement(factory.createIdentifier("arguments"))] + ) + ) + ); + } - indexOfFirstStatementAfterSuperAndPrologue += parameterPropertyDeclarationCount; + // Add the property initializers. Transforms this: + // + // public x = 1; + // + // Into this: + // + // constructor() { + // this.x = 1; + // } + // + // If we do useDefineForClassFields, they'll be converted elsewhere. + // We instead *remove* them from the transformed output at this stage. + let parameterPropertyDeclarationCount = 0; + if (constructor?.body) { + if (useDefineForClassFields) { + statements = statements.filter(statement => !isParameterPropertyDeclaration(getOriginalNode(statement), constructor)); + } + else { + for (const statement of constructor.body.statements) { + if (isParameterPropertyDeclaration(getOriginalNode(statement), constructor)) { + parameterPropertyDeclarationCount++; } } - } - - const receiver = factory.createThis(); - // private methods can be called in property initializers, they should execute first. - addMethodStatements(statements, privateMethodsAndAccessors, receiver); - addPropertyOrClassStaticBlockStatements(statements, properties, receiver); + if (parameterPropertyDeclarationCount > 0) { + const parameterProperties = visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatementAfterSuperAndPrologue, parameterPropertyDeclarationCount); - // Add existing statements after the initial prologues and super call - if (constructor) { - addRange(statements, visitNodes(constructor.body!.statements, visitBodyStatement, isStatement, indexOfFirstStatementAfterSuperAndPrologue)); - } - - statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment()); + // If there was a super() call found, add parameter properties immediately after it + if (superStatementIndex >= 0) { + addRange(statements, parameterProperties); + } + else { + // Add add parameter properties to the top of the constructor after the prologue + let superAndPrologueStatementCount = prologueStatementCount; + // If a synthetic super() call was added, need to account for that + if (needsSyntheticConstructor) superAndPrologueStatementCount++; + statements = [ + ...statements.slice(0, superAndPrologueStatementCount), + ...parameterProperties, + ...statements.slice(superAndPrologueStatementCount), + ]; + } - if (statements.length === 0 && !constructor) { - return undefined; + indexOfFirstStatementAfterSuperAndPrologue += parameterPropertyDeclarationCount; + } } + } - const multiLine = constructor?.body && constructor.body.statements.length >= statements.length ? - constructor.body.multiLine ?? statements.length > 0 : - statements.length > 0; + const receiver = factory.createThis(); + // private methods can be called in property initializers, they should execute first. + addMethodStatements(statements, privateMethodsAndAccessors, receiver); + addPropertyOrClassStaticBlockStatements(statements, properties, receiver); - return setTextRange( - factory.createBlock( - setTextRange( - factory.createNodeArray(statements), - /*location*/ constructor ? constructor.body!.statements : node.members - ), - multiLine - ), - /*location*/ constructor ? constructor.body : undefined - ); + // Add existing statements after the initial prologues and super call + if (constructor) { + addRange(statements, visitNodes(constructor.body!.statements, visitBodyStatement, isStatement, indexOfFirstStatementAfterSuperAndPrologue)); + } - function visitBodyStatement(statement: Node) { - if (useDefineForClassFields && isParameterPropertyDeclaration(getOriginalNode(statement), constructor!)) { - return undefined; - } + statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment()); - return visitor(statement); - } + if (statements.length === 0 && !constructor) { + return 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 addPropertyOrClassStaticBlockStatements(statements: Statement[], properties: readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[], receiver: LeftHandSideExpression) { - for (const property of properties) { - if (isStatic(property) && !shouldTransformPrivateElementsOrClassStaticBlocks && !useDefineForClassFields) { - continue; - } + const multiLine = constructor?.body && constructor.body.statements.length >= statements.length ? + constructor.body.multiLine ?? statements.length > 0 : + statements.length > 0; - const statement = transformPropertyOrClassStaticBlock(property, receiver); - if (!statement) { - continue; - } + return setTextRange( + factory.createBlock( + setTextRange( + factory.createNodeArray(statements), + /*location*/ constructor ? constructor.body!.statements : node.members + ), + multiLine + ), + /*location*/ constructor ? constructor.body : undefined + ); - statements.push(statement); + function visitBodyStatement(statement: Node) { + if (useDefineForClassFields && isParameterPropertyDeclaration(getOriginalNode(statement), constructor!)) { + return undefined; } + + return visitor(statement); } + } - function transformPropertyOrClassStaticBlock(property: PropertyDeclaration | ClassStaticBlockDeclaration, receiver: LeftHandSideExpression) { - const expression = isClassStaticBlockDeclaration(property) ? - transformClassStaticBlockDeclaration(property) : - transformProperty(property, receiver); - if (!expression) { - return 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 addPropertyOrClassStaticBlockStatements(statements: Statement[], properties: readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[], receiver: LeftHandSideExpression) { + for (const property of properties) { + if (isStatic(property) && !shouldTransformPrivateElementsOrClassStaticBlocks && !useDefineForClassFields) { + continue; } - const statement = factory.createExpressionStatement(expression); - setOriginalNode(statement, property); - addEmitFlags(statement, getEmitFlags(property) & EmitFlags.NoComments); - setSourceMapRange(statement, moveRangePastModifiers(property)); - setCommentRange(statement, property); - - // `setOriginalNode` *copies* the `emitNode` from `property`, so now both - // `statement` and `expression` have a copy of the synthesized comments. - // Drop the comments from expression to avoid printing them twice. - setSyntheticLeadingComments(expression, undefined); - setSyntheticTrailingComments(expression, undefined); - - return statement; - } - - /** - * Generates assignment expressions for property initializers. - * - * @param propertiesOrClassStaticBlocks An array of property declarations to transform. - * @param receiver The receiver on which each property should be assigned. - */ - function generateInitializedPropertyExpressionsOrClassStaticBlock(propertiesOrClassStaticBlocks: readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[], receiver: LeftHandSideExpression) { - const expressions: Expression[] = []; - for (const property of propertiesOrClassStaticBlocks) { - const expression = isClassStaticBlockDeclaration(property) ? transformClassStaticBlockDeclaration(property) : transformProperty(property, receiver); - if (!expression) { - continue; - } - startOnNewLine(expression); - setOriginalNode(expression, property); - addEmitFlags(expression, getEmitFlags(property) & EmitFlags.NoComments); - setSourceMapRange(expression, moveRangePastModifiers(property)); - setCommentRange(expression, property); - expressions.push(expression); + const statement = transformPropertyOrClassStaticBlock(property, receiver); + if (!statement) { + continue; } - return expressions; - } - - /** - * Transforms a property initializer into an assignment statement. - * - * @param property The property declaration. - * @param receiver The object receiving the property assignment. - */ - function transformProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) { - const savedCurrentStaticPropertyDeclarationOrStaticBlock = currentStaticPropertyDeclarationOrStaticBlock; - const transformed = transformPropertyWorker(property, receiver); - if (transformed && hasStaticModifier(property) && currentClassLexicalEnvironment?.facts) { - // capture the lexical environment for the member - setOriginalNode(transformed, property); - addEmitFlags(transformed, EmitFlags.AdviseOnEmitNode); - classLexicalEnvironmentMap.set(getOriginalNodeId(transformed), currentClassLexicalEnvironment); - } - currentStaticPropertyDeclarationOrStaticBlock = savedCurrentStaticPropertyDeclarationOrStaticBlock; - return transformed; + statements.push(statement); + } + } + + function transformPropertyOrClassStaticBlock(property: PropertyDeclaration | ClassStaticBlockDeclaration, receiver: LeftHandSideExpression) { + const expression = isClassStaticBlockDeclaration(property) ? + transformClassStaticBlockDeclaration(property) : + transformProperty(property, receiver); + if (!expression) { + return undefined; } - function transformPropertyWorker(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 emitAssignment = !useDefineForClassFields; + const statement = factory.createExpressionStatement(expression); + setOriginalNode(statement, property); + addEmitFlags(statement, getEmitFlags(property) & EmitFlags.NoComments); + setSourceMapRange(statement, moveRangePastModifiers(property)); + setCommentRange(statement, property); - const propertyName = - hasAccessorModifier(property) ? - factory.getGeneratedPrivateNameForNode(property.name) : - isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression) ? - factory.updateComputedPropertyName(property.name, factory.getGeneratedNameForNode(property.name)) : - property.name; + // `setOriginalNode` *copies* the `emitNode` from `property`, so now both + // `statement` and `expression` have a copy of the synthesized comments. + // Drop the comments from expression to avoid printing them twice. + setSyntheticLeadingComments(expression, undefined); + setSyntheticTrailingComments(expression, undefined); + + return statement; + } - if (hasStaticModifier(property)) { - currentStaticPropertyDeclarationOrStaticBlock = property; + /** + * Generates assignment expressions for property initializers. + * + * @param propertiesOrClassStaticBlocks An array of property declarations to transform. + * @param receiver The receiver on which each property should be assigned. + */ + function generateInitializedPropertyExpressionsOrClassStaticBlock(propertiesOrClassStaticBlocks: readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[], receiver: LeftHandSideExpression) { + const expressions: Expression[] = []; + for (const property of propertiesOrClassStaticBlocks) { + const expression = isClassStaticBlockDeclaration(property) ? transformClassStaticBlockDeclaration(property) : transformProperty(property, receiver); + if (!expression) { + continue; } + startOnNewLine(expression); + setOriginalNode(expression, property); + addEmitFlags(expression, getEmitFlags(property) & EmitFlags.NoComments); + setSourceMapRange(expression, moveRangePastModifiers(property)); + setCommentRange(expression, property); + expressions.push(expression); + } - if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(propertyName)) { - const privateIdentifierInfo = accessPrivateIdentifier(propertyName); - if (privateIdentifierInfo) { - if (privateIdentifierInfo.kind === PrivateIdentifierKind.Field) { - if (!privateIdentifierInfo.isStatic) { - return createPrivateInstanceFieldInitializer( - receiver, - visitNode(property.initializer, visitor, isExpression), - privateIdentifierInfo.brandCheckIdentifier - ); - } - else { - return createPrivateStaticFieldInitializer( - privateIdentifierInfo.variableName, - visitNode(property.initializer, visitor, isExpression) - ); - } + return expressions; + } + + /** + * Transforms a property initializer into an assignment statement. + * + * @param property The property declaration. + * @param receiver The object receiving the property assignment. + */ + function transformProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) { + const savedCurrentStaticPropertyDeclarationOrStaticBlock = currentStaticPropertyDeclarationOrStaticBlock; + const transformed = transformPropertyWorker(property, receiver); + if (transformed && hasStaticModifier(property) && currentClassLexicalEnvironment?.facts) { + // capture the lexical environment for the member + setOriginalNode(transformed, property); + addEmitFlags(transformed, EmitFlags.AdviseOnEmitNode); + classLexicalEnvironmentMap.set(getOriginalNodeId(transformed), currentClassLexicalEnvironment); + } + currentStaticPropertyDeclarationOrStaticBlock = savedCurrentStaticPropertyDeclarationOrStaticBlock; + return transformed; + } + + function transformPropertyWorker(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 emitAssignment = !useDefineForClassFields; + + const propertyName = + hasAccessorModifier(property) ? + factory.getGeneratedPrivateNameForNode(property.name) : + isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression) ? + factory.updateComputedPropertyName(property.name, factory.getGeneratedNameForNode(property.name)) : + property.name; + + if (hasStaticModifier(property)) { + currentStaticPropertyDeclarationOrStaticBlock = property; + } + + if (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(propertyName)) { + const privateIdentifierInfo = accessPrivateIdentifier(propertyName); + if (privateIdentifierInfo) { + if (privateIdentifierInfo.kind === PrivateIdentifierKind.Field) { + if (!privateIdentifierInfo.isStatic) { + return createPrivateInstanceFieldInitializer( + receiver, + visitNode(property.initializer, visitor, isExpression), + privateIdentifierInfo.brandCheckIdentifier + ); } else { - return undefined; + return createPrivateStaticFieldInitializer( + privateIdentifierInfo.variableName, + visitNode(property.initializer, visitor, isExpression) + ); } } else { - Debug.fail("Undeclared private name for property declaration."); + return undefined; } } - if ((isPrivateIdentifier(propertyName) || hasStaticModifier(property)) && !property.initializer) { - return undefined; + else { + Debug.fail("Undeclared private name for property declaration."); } + } + if ((isPrivateIdentifier(propertyName) || hasStaticModifier(property)) && !property.initializer) { + return undefined; + } - const propertyOriginalNode = getOriginalNode(property); - if (hasSyntacticModifier(propertyOriginalNode, ModifierFlags.Abstract)) { - return undefined; - } + const propertyOriginalNode = getOriginalNode(property); + if (hasSyntacticModifier(propertyOriginalNode, ModifierFlags.Abstract)) { + return undefined; + } - const initializer = property.initializer || emitAssignment ? visitNode(property.initializer, visitor, isExpression) ?? factory.createVoidZero() - : isParameterPropertyDeclaration(propertyOriginalNode, propertyOriginalNode.parent) && isIdentifier(propertyName) ? propertyName - : factory.createVoidZero(); + const initializer = property.initializer || emitAssignment ? visitNode(property.initializer, visitor, isExpression) ?? factory.createVoidZero() + : isParameterPropertyDeclaration(propertyOriginalNode, propertyOriginalNode.parent) && isIdentifier(propertyName) ? propertyName + : factory.createVoidZero(); - if (emitAssignment || isPrivateIdentifier(propertyName)) { - const memberAccess = createMemberAccessForPropertyName(factory, receiver, propertyName, /*location*/ propertyName); - return factory.createAssignment(memberAccess, initializer); - } - else { - const name = isComputedPropertyName(propertyName) ? propertyName.expression - : isIdentifier(propertyName) ? factory.createStringLiteral(unescapeLeadingUnderscores(propertyName.escapedText)) - : propertyName; - const descriptor = factory.createPropertyDescriptor({ value: initializer, configurable: true, writable: true, enumerable: true }); - return factory.createObjectDefinePropertyCall(receiver, name, descriptor); - } + if (emitAssignment || isPrivateIdentifier(propertyName)) { + const memberAccess = createMemberAccessForPropertyName(factory, receiver, propertyName, /*location*/ propertyName); + return factory.createAssignment(memberAccess, initializer); + } + else { + const name = isComputedPropertyName(propertyName) ? propertyName.expression + : isIdentifier(propertyName) ? factory.createStringLiteral(unescapeLeadingUnderscores(propertyName.escapedText)) + : propertyName; + const descriptor = factory.createPropertyDescriptor({ value: initializer, configurable: true, writable: true, enumerable: true }); + return factory.createObjectDefinePropertyCall(receiver, name, descriptor); } + } - function enableSubstitutionForClassAliases() { - if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) === 0) { - enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassAliases; + 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); + // 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 = []; - } + // Keep track of class aliases. + classAliases = []; } + } - function enableSubstitutionForClassStaticThisOrSuperReference() { - if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference) === 0) { - enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference; + function enableSubstitutionForClassStaticThisOrSuperReference() { + if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference) === 0) { + enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference; - // substitute `this` in a static field initializer - context.enableSubstitution(SyntaxKind.ThisKeyword); + // substitute `this` in a static field initializer + context.enableSubstitution(SyntaxKind.ThisKeyword); - // these push a new lexical environment that is not the class lexical environment - context.enableEmitNotification(SyntaxKind.FunctionDeclaration); - context.enableEmitNotification(SyntaxKind.FunctionExpression); - context.enableEmitNotification(SyntaxKind.Constructor); + // these push a new lexical environment that is not the class lexical environment + context.enableEmitNotification(SyntaxKind.FunctionDeclaration); + context.enableEmitNotification(SyntaxKind.FunctionExpression); + context.enableEmitNotification(SyntaxKind.Constructor); - // these push a new lexical environment that is not the class lexical environment, except - // when they have a computed property name - context.enableEmitNotification(SyntaxKind.GetAccessor); - context.enableEmitNotification(SyntaxKind.SetAccessor); - context.enableEmitNotification(SyntaxKind.MethodDeclaration); - context.enableEmitNotification(SyntaxKind.PropertyDeclaration); + // these push a new lexical environment that is not the class lexical environment, except + // when they have a computed property name + context.enableEmitNotification(SyntaxKind.GetAccessor); + context.enableEmitNotification(SyntaxKind.SetAccessor); + context.enableEmitNotification(SyntaxKind.MethodDeclaration); + context.enableEmitNotification(SyntaxKind.PropertyDeclaration); - // class lexical environments are restored when entering a computed property name - context.enableEmitNotification(SyntaxKind.ComputedPropertyName); - } + // class lexical environments are restored when entering a computed property name + context.enableEmitNotification(SyntaxKind.ComputedPropertyName); + } + } + + /** + * Generates brand-check initializer for private methods. + * + * @param statements Statement list that should be used to append new statements. + * @param methods An array of method declarations. + * @param receiver The receiver on which each method should be assigned. + */ + function addMethodStatements(statements: Statement[], methods: readonly (MethodDeclaration | AccessorDeclaration | AutoAccessorPropertyDeclaration)[], receiver: LeftHandSideExpression) { + if (!shouldTransformPrivateElementsOrClassStaticBlocks || !some(methods)) { + return; } - /** - * Generates brand-check initializer for private methods. - * - * @param statements Statement list that should be used to append new statements. - * @param methods An array of method declarations. - * @param receiver The receiver on which each method should be assigned. - */ - function addMethodStatements(statements: Statement[], methods: readonly (MethodDeclaration | AccessorDeclaration | AutoAccessorPropertyDeclaration)[], receiver: LeftHandSideExpression) { - if (!shouldTransformPrivateElementsOrClassStaticBlocks || !some(methods)) { + const { weakSetName } = getPrivateIdentifierEnvironment(); + Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); + statements.push( + factory.createExpressionStatement( + createPrivateInstanceMethodInitializer(receiver, weakSetName) + ) + ); + } + + function visitInvalidSuperProperty(node: SuperProperty) { + return isPropertyAccessExpression(node) ? + factory.updatePropertyAccessExpression( + node, + factory.createVoidZero(), + node.name) : + factory.updateElementAccessExpression( + node, + factory.createVoidZero(), + visitNode(node.argumentExpression, visitor, isExpression)); + } + + function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { + const original = getOriginalNode(node); + if (original.id) { + const classLexicalEnvironment = classLexicalEnvironmentMap.get(original.id); + if (classLexicalEnvironment) { + const savedClassLexicalEnvironment = currentClassLexicalEnvironment; + const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; + currentClassLexicalEnvironment = classLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = classLexicalEnvironment; + previousOnEmitNode(hint, node, emitCallback); + currentClassLexicalEnvironment = savedClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; return; } - - const { weakSetName } = getPrivateIdentifierEnvironment(); - Debug.assert(weakSetName, "weakSetName should be set in private identifier environment"); - statements.push( - factory.createExpressionStatement( - createPrivateInstanceMethodInitializer(receiver, weakSetName) - ) - ); } - function visitInvalidSuperProperty(node: SuperProperty) { - return isPropertyAccessExpression(node) ? - factory.updatePropertyAccessExpression( - node, - factory.createVoidZero(), - node.name) : - factory.updateElementAccessExpression( - node, - factory.createVoidZero(), - visitNode(node.argumentExpression, visitor, isExpression)); - } - - function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { - const original = getOriginalNode(node); - if (original.id) { - const classLexicalEnvironment = classLexicalEnvironmentMap.get(original.id); - if (classLexicalEnvironment) { - const savedClassLexicalEnvironment = currentClassLexicalEnvironment; - const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; - currentClassLexicalEnvironment = classLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = classLexicalEnvironment; - previousOnEmitNode(hint, node, emitCallback); - currentClassLexicalEnvironment = savedClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; - return; + switch (node.kind) { + case SyntaxKind.FunctionExpression: + if (isArrowFunction(original) || getEmitFlags(node) & EmitFlags.AsyncFunctionBody) { + break; } - } - - switch (node.kind) { - case SyntaxKind.FunctionExpression: - if (isArrowFunction(original) || getEmitFlags(node) & EmitFlags.AsyncFunctionBody) { - break; - } - // falls through - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.Constructor: { - const savedClassLexicalEnvironment = currentClassLexicalEnvironment; - const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; - currentClassLexicalEnvironment = undefined; - currentComputedPropertyNameClassLexicalEnvironment = undefined; - previousOnEmitNode(hint, node, emitCallback); - currentClassLexicalEnvironment = savedClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; - return; - } + // falls through + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.Constructor: { + const savedClassLexicalEnvironment = currentClassLexicalEnvironment; + const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; + currentClassLexicalEnvironment = undefined; + currentComputedPropertyNameClassLexicalEnvironment = undefined; + previousOnEmitNode(hint, node, emitCallback); + currentClassLexicalEnvironment = savedClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; + return; + } - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.PropertyDeclaration: { - const savedClassLexicalEnvironment = currentClassLexicalEnvironment; - const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = currentClassLexicalEnvironment; - currentClassLexicalEnvironment = undefined; - previousOnEmitNode(hint, node, emitCallback); - currentClassLexicalEnvironment = savedClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; - return; - } - case SyntaxKind.ComputedPropertyName: { - const savedClassLexicalEnvironment = currentClassLexicalEnvironment; - const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; - currentClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = undefined; - previousOnEmitNode(hint, node, emitCallback); - currentClassLexicalEnvironment = savedClassLexicalEnvironment; - currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; - return; - } + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.PropertyDeclaration: { + const savedClassLexicalEnvironment = currentClassLexicalEnvironment; + const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = currentClassLexicalEnvironment; + currentClassLexicalEnvironment = undefined; + previousOnEmitNode(hint, node, emitCallback); + currentClassLexicalEnvironment = savedClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; + return; } - previousOnEmitNode(hint, node, emitCallback); - } - - /** - * 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); + case SyntaxKind.ComputedPropertyName: { + const savedClassLexicalEnvironment = currentClassLexicalEnvironment; + const savedCurrentComputedPropertyNameClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; + currentClassLexicalEnvironment = currentComputedPropertyNameClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = undefined; + previousOnEmitNode(hint, node, emitCallback); + currentClassLexicalEnvironment = savedClassLexicalEnvironment; + currentComputedPropertyNameClassLexicalEnvironment = savedCurrentComputedPropertyNameClassLexicalEnvironment; + return; } - return node; } + previousOnEmitNode(hint, node, emitCallback); + } - function substituteExpression(node: Expression) { - switch (node.kind) { - case SyntaxKind.Identifier: - return substituteExpressionIdentifier(node as Identifier); - case SyntaxKind.ThisKeyword: - return substituteThisExpression(node as ThisExpression); - } - return node; + /** + * 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 substituteThisExpression(node: ThisExpression) { - if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference && currentClassLexicalEnvironment) { - const { facts, classConstructor } = currentClassLexicalEnvironment; - if (facts & ClassFacts.ClassWasDecorated) { - return factory.createParenthesizedExpression(factory.createVoidZero()); - } - if (classConstructor) { - return setTextRange( - setOriginalNode( - factory.cloneNode(classConstructor), - node, - ), - node - ); - } + function substituteExpression(node: Expression) { + switch (node.kind) { + case SyntaxKind.Identifier: + return substituteExpressionIdentifier(node as Identifier); + case SyntaxKind.ThisKeyword: + return substituteThisExpression(node as ThisExpression); + } + return node; + } + + function substituteThisExpression(node: ThisExpression) { + if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassStaticThisOrSuperReference && currentClassLexicalEnvironment) { + const { facts, classConstructor } = currentClassLexicalEnvironment; + if (facts & ClassFacts.ClassWasDecorated) { + return factory.createParenthesizedExpression(factory.createVoidZero()); + } + if (classConstructor) { + return setTextRange( + setOriginalNode( + factory.cloneNode(classConstructor), + node, + ), + node + ); } - return node; } + 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 = factory.cloneNode(classAlias); - setSourceMapRange(clone, node); - setCommentRange(clone, node); - return clone; - } + 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 = factory.cloneNode(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 = factory.getGeneratedNameForNode(name); - if (resolver.getNodeCheckFlags(name) & NodeCheckFlags.BlockScopedBindingInLoop) { - addBlockScopedVariable(generatedName); - } - else { - hoistVariableDeclaration(generatedName); - } - return factory.createAssignment(generatedName, expression); + 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 = factory.getGeneratedNameForNode(name); + if (resolver.getNodeCheckFlags(name) & NodeCheckFlags.BlockScopedBindingInLoop) { + addBlockScopedVariable(generatedName); + } + else { + hoistVariableDeclaration(generatedName); } - return (inlinable || isIdentifier(innerExpression)) ? undefined : expression; + return factory.createAssignment(generatedName, expression); } + return (inlinable || isIdentifier(innerExpression)) ? undefined : expression; } + } - function startClassLexicalEnvironment() { - classLexicalEnvironmentStack.push(currentClassLexicalEnvironment); - currentClassLexicalEnvironment = undefined; - } + function startClassLexicalEnvironment() { + classLexicalEnvironmentStack.push(currentClassLexicalEnvironment); + currentClassLexicalEnvironment = undefined; + } - function endClassLexicalEnvironment() { - currentClassLexicalEnvironment = classLexicalEnvironmentStack.pop(); - } + function endClassLexicalEnvironment() { + currentClassLexicalEnvironment = classLexicalEnvironmentStack.pop(); + } - function getClassLexicalEnvironment() { - return currentClassLexicalEnvironment ||= { - facts: ClassFacts.None, - classConstructor: undefined, - superClassReference: undefined, - privateIdentifierEnvironment: undefined, - }; - } + function getClassLexicalEnvironment() { + return currentClassLexicalEnvironment ||= { + facts: ClassFacts.None, + classConstructor: undefined, + superClassReference: undefined, + privateIdentifierEnvironment: undefined, + }; + } - function getPrivateIdentifierEnvironment() { - const lex = getClassLexicalEnvironment(); - lex.privateIdentifierEnvironment ||= { - className: undefined, - weakSetName: undefined, - identifiers: undefined, - generatedIdentifiers: undefined, - }; - return lex.privateIdentifierEnvironment; - } + function getPrivateIdentifierEnvironment() { + const lex = getClassLexicalEnvironment(); + lex.privateIdentifierEnvironment ||= { + className: undefined, + weakSetName: undefined, + identifiers: undefined, + generatedIdentifiers: undefined, + }; + return lex.privateIdentifierEnvironment; + } + + function getPendingExpressions() { + return pendingExpressions ??= []; + } - function getPendingExpressions() { - return pendingExpressions ??= []; + function addPrivateIdentifierClassElementToEnvironment( + node: PropertyDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration, + name: PrivateIdentifier, + lex: ClassLexicalEnvironment, + privateEnv: PrivateIdentifierEnvironment, + isStatic: boolean, + isValid: boolean, + previousInfo: PrivateIdentifierInfo | undefined + ) { + if (isAutoAccessorPropertyDeclaration(node)) { + addPrivateIdentifierAutoAccessorPropertyDeclarationToEnvironment(node, name, lex, privateEnv, isStatic, isValid, previousInfo); + } + else if (isPropertyDeclaration(node)) { + addPrivateIdentifierPropertyDeclarationToEnvironment(node, name, lex, privateEnv, isStatic, isValid, previousInfo); + } + else if (isMethodDeclaration(node)) { + addPrivateIdentifierMethodDeclarationToEnvironment(node, name, lex, privateEnv, isStatic, isValid, previousInfo); + } + else if (isGetAccessorDeclaration(node)) { + addPrivateIdentifierGetAccessorDeclarationToEnvironment(node, name, lex, privateEnv, isStatic, isValid, previousInfo); + } + else if (isSetAccessorDeclaration(node)) { + addPrivateIdentifierSetAccessorDeclarationToEnvironment(node, name, lex, privateEnv, isStatic, isValid, previousInfo); } + } - function addPrivateIdentifierClassElementToEnvironment( - node: PropertyDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration, - name: PrivateIdentifier, - lex: ClassLexicalEnvironment, - privateEnv: PrivateIdentifierEnvironment, - isStatic: boolean, - isValid: boolean, - previousInfo: PrivateIdentifierInfo | undefined - ) { - if (isAutoAccessorPropertyDeclaration(node)) { - addPrivateIdentifierAutoAccessorPropertyDeclarationToEnvironment(node, name, lex, privateEnv, isStatic, isValid, previousInfo); - } - else if (isPropertyDeclaration(node)) { - addPrivateIdentifierPropertyDeclarationToEnvironment(node, name, lex, privateEnv, isStatic, isValid, previousInfo); - } - else if (isMethodDeclaration(node)) { - addPrivateIdentifierMethodDeclarationToEnvironment(node, name, lex, privateEnv, isStatic, isValid, previousInfo); - } - else if (isGetAccessorDeclaration(node)) { - addPrivateIdentifierGetAccessorDeclarationToEnvironment(node, name, lex, privateEnv, isStatic, isValid, previousInfo); - } - else if (isSetAccessorDeclaration(node)) { - addPrivateIdentifierSetAccessorDeclarationToEnvironment(node, name, lex, privateEnv, isStatic, isValid, previousInfo); - } + function addPrivateIdentifierPropertyDeclarationToEnvironment( + _node: PropertyDeclaration, + name: PrivateIdentifier, + lex: ClassLexicalEnvironment, + privateEnv: PrivateIdentifierEnvironment, + isStatic: boolean, + isValid: boolean, + _previousInfo: PrivateIdentifierInfo | undefined + ) { + if (isStatic) { + Debug.assert(lex.classConstructor, "classConstructor should be set in private identifier environment"); + + const variableName = createHoistedVariableForPrivateName(name); + setPrivateIdentifier(privateEnv, name, { + kind: PrivateIdentifierKind.Field, + brandCheckIdentifier: lex.classConstructor, + variableName, + isStatic: true, + isValid, + }); } + else { + const weakMapName = createHoistedVariableForPrivateName(name); - function addPrivateIdentifierPropertyDeclarationToEnvironment( - _node: PropertyDeclaration, - name: PrivateIdentifier, - lex: ClassLexicalEnvironment, - privateEnv: PrivateIdentifierEnvironment, - isStatic: boolean, - isValid: boolean, - _previousInfo: PrivateIdentifierInfo | undefined - ) { - if (isStatic) { - Debug.assert(lex.classConstructor, "classConstructor should be set in private identifier environment"); - - const variableName = createHoistedVariableForPrivateName(name); - setPrivateIdentifier(privateEnv, name, { - kind: PrivateIdentifierKind.Field, - brandCheckIdentifier: lex.classConstructor, - variableName, - isStatic: true, - isValid, - }); - } - else { - const weakMapName = createHoistedVariableForPrivateName(name); - - setPrivateIdentifier(privateEnv, name, { - kind: PrivateIdentifierKind.Field, - brandCheckIdentifier: weakMapName, - variableName: undefined, - isStatic: false, - isValid, - }); - - getPendingExpressions().push(factory.createAssignment( - weakMapName, - factory.createNewExpression( - factory.createIdentifier("WeakMap"), - /*typeArguments*/ undefined, - [] - ) - )); - } + setPrivateIdentifier(privateEnv, name, { + kind: PrivateIdentifierKind.Field, + brandCheckIdentifier: weakMapName, + variableName: undefined, + isStatic: false, + isValid, + }); + + getPendingExpressions().push(factory.createAssignment( + weakMapName, + factory.createNewExpression( + factory.createIdentifier("WeakMap"), + /*typeArguments*/ undefined, + [] + ) + )); } + } - function addPrivateIdentifierMethodDeclarationToEnvironment( - _node: MethodDeclaration, - name: PrivateIdentifier, - lex: ClassLexicalEnvironment, - privateEnv: PrivateIdentifierEnvironment, - isStatic: boolean, - isValid: boolean, - _previousInfo: PrivateIdentifierInfo | undefined - ) { - const methodName = createHoistedVariableForPrivateName(name); - const brandCheckIdentifier = isStatic ? - Debug.checkDefined(lex.classConstructor, "classConstructor should be set in private identifier environment") : - Debug.checkDefined(privateEnv.weakSetName, "weakSetName should be set in private identifier environment"); + function addPrivateIdentifierMethodDeclarationToEnvironment( + _node: MethodDeclaration, + name: PrivateIdentifier, + lex: ClassLexicalEnvironment, + privateEnv: PrivateIdentifierEnvironment, + isStatic: boolean, + isValid: boolean, + _previousInfo: PrivateIdentifierInfo | undefined + ) { + const methodName = createHoistedVariableForPrivateName(name); + const brandCheckIdentifier = isStatic ? + Debug.checkDefined(lex.classConstructor, "classConstructor should be set in private identifier environment") : + Debug.checkDefined(privateEnv.weakSetName, "weakSetName should be set in private identifier environment"); + + setPrivateIdentifier(privateEnv, name, { + kind: PrivateIdentifierKind.Method, + methodName, + brandCheckIdentifier, + isStatic, + isValid, + }); + } + function addPrivateIdentifierGetAccessorDeclarationToEnvironment( + _node: GetAccessorDeclaration, + name: PrivateIdentifier, + lex: ClassLexicalEnvironment, + privateEnv: PrivateIdentifierEnvironment, + isStatic: boolean, + isValid: boolean, + previousInfo: PrivateIdentifierInfo | undefined + ) { + const getterName = createHoistedVariableForPrivateName(name, "_get"); + const brandCheckIdentifier = isStatic ? + Debug.checkDefined(lex.classConstructor, "classConstructor should be set in private identifier environment") : + Debug.checkDefined(privateEnv.weakSetName, "weakSetName should be set in private identifier environment"); + + if (previousInfo?.kind === PrivateIdentifierKind.Accessor && previousInfo.isStatic === isStatic && !previousInfo.getterName) { + previousInfo.getterName = getterName; + } + else { setPrivateIdentifier(privateEnv, name, { - kind: PrivateIdentifierKind.Method, - methodName, + kind: PrivateIdentifierKind.Accessor, + getterName, + setterName: undefined, brandCheckIdentifier, isStatic, isValid, }); } + } - function addPrivateIdentifierGetAccessorDeclarationToEnvironment( - _node: GetAccessorDeclaration, - name: PrivateIdentifier, - lex: ClassLexicalEnvironment, - privateEnv: PrivateIdentifierEnvironment, - isStatic: boolean, - isValid: boolean, - previousInfo: PrivateIdentifierInfo | undefined - ) { - const getterName = createHoistedVariableForPrivateName(name, "_get"); - const brandCheckIdentifier = isStatic ? - Debug.checkDefined(lex.classConstructor, "classConstructor should be set in private identifier environment") : - Debug.checkDefined(privateEnv.weakSetName, "weakSetName should be set in private identifier environment"); - - if (previousInfo?.kind === PrivateIdentifierKind.Accessor && previousInfo.isStatic === isStatic && !previousInfo.getterName) { - previousInfo.getterName = getterName; - } - else { - setPrivateIdentifier(privateEnv, name, { - kind: PrivateIdentifierKind.Accessor, - getterName, - setterName: undefined, - brandCheckIdentifier, - isStatic, - isValid, - }); - } - } - - function addPrivateIdentifierSetAccessorDeclarationToEnvironment( - _node: SetAccessorDeclaration, - name: PrivateIdentifier, - lex: ClassLexicalEnvironment, - privateEnv: PrivateIdentifierEnvironment, - isStatic: boolean, - isValid: boolean, - previousInfo: PrivateIdentifierInfo | undefined - ) { - const setterName = createHoistedVariableForPrivateName(name, "_set"); - const brandCheckIdentifier = isStatic ? - Debug.checkDefined(lex.classConstructor, "classConstructor should be set in private identifier environment") : - Debug.checkDefined(privateEnv.weakSetName, "weakSetName should be set in private identifier environment"); - - if (previousInfo?.kind === PrivateIdentifierKind.Accessor && previousInfo.isStatic === isStatic && !previousInfo.setterName) { - previousInfo.setterName = setterName; - } - else { - setPrivateIdentifier(privateEnv, name, { - kind: PrivateIdentifierKind.Accessor, - getterName: undefined, - setterName, - brandCheckIdentifier, - isStatic, - isValid, - }); - } + function addPrivateIdentifierSetAccessorDeclarationToEnvironment( + _node: SetAccessorDeclaration, + name: PrivateIdentifier, + lex: ClassLexicalEnvironment, + privateEnv: PrivateIdentifierEnvironment, + isStatic: boolean, + isValid: boolean, + previousInfo: PrivateIdentifierInfo | undefined + ) { + const setterName = createHoistedVariableForPrivateName(name, "_set"); + const brandCheckIdentifier = isStatic ? + Debug.checkDefined(lex.classConstructor, "classConstructor should be set in private identifier environment") : + Debug.checkDefined(privateEnv.weakSetName, "weakSetName should be set in private identifier environment"); + + if (previousInfo?.kind === PrivateIdentifierKind.Accessor && previousInfo.isStatic === isStatic && !previousInfo.setterName) { + previousInfo.setterName = setterName; } - - function addPrivateIdentifierAutoAccessorPropertyDeclarationToEnvironment( - _node: AutoAccessorPropertyDeclaration, - name: PrivateIdentifier, - lex: ClassLexicalEnvironment, - privateEnv: PrivateIdentifierEnvironment, - isStatic: boolean, - isValid: boolean, - _previousInfo: PrivateIdentifierInfo | undefined - ) { - const getterName = createHoistedVariableForPrivateName(name, "_get"); - const setterName = createHoistedVariableForPrivateName(name, "_set"); - const brandCheckIdentifier = isStatic ? - Debug.checkDefined(lex.classConstructor, "classConstructor should be set in private identifier environment") : - Debug.checkDefined(privateEnv.weakSetName, "weakSetName should be set in private identifier environment"); - + else { setPrivateIdentifier(privateEnv, name, { kind: PrivateIdentifierKind.Accessor, - getterName, + getterName: undefined, setterName, brandCheckIdentifier, isStatic, isValid, }); } + } + + function addPrivateIdentifierAutoAccessorPropertyDeclarationToEnvironment( + _node: AutoAccessorPropertyDeclaration, + name: PrivateIdentifier, + lex: ClassLexicalEnvironment, + privateEnv: PrivateIdentifierEnvironment, + isStatic: boolean, + isValid: boolean, + _previousInfo: PrivateIdentifierInfo | undefined + ) { + const getterName = createHoistedVariableForPrivateName(name, "_get"); + const setterName = createHoistedVariableForPrivateName(name, "_set"); + const brandCheckIdentifier = isStatic ? + Debug.checkDefined(lex.classConstructor, "classConstructor should be set in private identifier environment") : + Debug.checkDefined(privateEnv.weakSetName, "weakSetName should be set in private identifier environment"); + + setPrivateIdentifier(privateEnv, name, { + kind: PrivateIdentifierKind.Accessor, + getterName, + setterName, + brandCheckIdentifier, + isStatic, + isValid, + }); + } - function addPrivateIdentifierToEnvironment( + function addPrivateIdentifierToEnvironment( + node: T, + name: PrivateIdentifier, + addDeclaration: ( node: T, name: PrivateIdentifier, - addDeclaration: ( - node: T, - name: PrivateIdentifier, - lex: ClassLexicalEnvironment, - privateEnv: PrivateIdentifierEnvironment, - isStatic: boolean, - isValid: boolean, - previousInfo: PrivateIdentifierInfo | undefined - ) => void - ) { - const lex = getClassLexicalEnvironment(); - const privateEnv = getPrivateIdentifierEnvironment(); - const previousInfo = getPrivateIdentifier(privateEnv, name); - const isStatic = hasStaticModifier(node); - const isValid = !isReservedPrivateName(name) && previousInfo === undefined; - addDeclaration(node, name, lex, privateEnv, isStatic, isValid, previousInfo); - } - - function createHoistedVariableForClass(name: string | PrivateIdentifier | undefined, node: PrivateIdentifier | ClassStaticBlockDeclaration, suffix?: string): Identifier { - const { className } = getPrivateIdentifierEnvironment(); - const prefix: GeneratedNamePart | string = className ? { prefix: "_", node: className, suffix: "_" } : "_"; - const identifier = - typeof name === "object" ? factory.getGeneratedNameForNode(name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.ReservedInNestedScopes, prefix, suffix) : - typeof name === "string" ? factory.createUniqueName(name, GeneratedIdentifierFlags.Optimistic, prefix, suffix) : - factory.createTempVariable(/*recordTempVariable*/ undefined, /*reserveInNestedScopes*/ true, prefix, suffix); - - if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.BlockScopedBindingInLoop) { - addBlockScopedVariable(identifier); - } - else { - hoistVariableDeclaration(identifier); - } + lex: ClassLexicalEnvironment, + privateEnv: PrivateIdentifierEnvironment, + isStatic: boolean, + isValid: boolean, + previousInfo: PrivateIdentifierInfo | undefined + ) => void + ) { + const lex = getClassLexicalEnvironment(); + const privateEnv = getPrivateIdentifierEnvironment(); + const previousInfo = getPrivateIdentifier(privateEnv, name); + const isStatic = hasStaticModifier(node); + const isValid = !isReservedPrivateName(name) && previousInfo === undefined; + addDeclaration(node, name, lex, privateEnv, isStatic, isValid, previousInfo); + } - return identifier; - } + function createHoistedVariableForClass(name: string | PrivateIdentifier | undefined, node: PrivateIdentifier | ClassStaticBlockDeclaration, suffix?: string): Identifier { + const { className } = getPrivateIdentifierEnvironment(); + const prefix: GeneratedNamePart | string = className ? { prefix: "_", node: className, suffix: "_" } : "_"; + const identifier = + typeof name === "object" ? factory.getGeneratedNameForNode(name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.ReservedInNestedScopes, prefix, suffix) : + typeof name === "string" ? factory.createUniqueName(name, GeneratedIdentifierFlags.Optimistic, prefix, suffix) : + factory.createTempVariable(/*recordTempVariable*/ undefined, /*reserveInNestedScopes*/ true, prefix, suffix); - function createHoistedVariableForPrivateName(name: PrivateIdentifier, suffix?: string): Identifier { - const text = tryGetTextOfPropertyName(name) as string | undefined; - return createHoistedVariableForClass(text?.substring(1) ?? name, name, suffix); + if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.BlockScopedBindingInLoop) { + addBlockScopedVariable(identifier); } - - /** - * Access an already defined {@link PrivateIdentifier} in the current {@link PrivateIdentifierEnvironment}. - * - * @seealso {@link addPrivateIdentifierToEnvironment} - */ - function accessPrivateIdentifier(name: PrivateIdentifier) { - if (isGeneratedPrivateIdentifier(name)) { - return accessGeneratedPrivateIdentifier(name); - } - else { - return accessPrivateIdentifierByText(name.escapedText); - } + else { + hoistVariableDeclaration(identifier); } - function accessPrivateIdentifierByText(text: __String) { - return accessPrivateIdentifierWorker(getPrivateIdentifierInfo, text); - } + return identifier; + } + + function createHoistedVariableForPrivateName(name: PrivateIdentifier, suffix?: string): Identifier { + const text = tryGetTextOfPropertyName(name) as string | undefined; + return createHoistedVariableForClass(text?.substring(1) ?? name, name, suffix); + } - function accessGeneratedPrivateIdentifier(name: GeneratedPrivateIdentifier) { - return accessPrivateIdentifierWorker(getGeneratedPrivateIdentifierInfo, getNodeForGeneratedName(name)); + /** + * Access an already defined {@link PrivateIdentifier} in the current {@link PrivateIdentifierEnvironment}. + * + * @seealso {@link addPrivateIdentifierToEnvironment} + */ + function accessPrivateIdentifier(name: PrivateIdentifier) { + if (isGeneratedPrivateIdentifier(name)) { + return accessGeneratedPrivateIdentifier(name); + } + else { + return accessPrivateIdentifierByText(name.escapedText); } + } + + function accessPrivateIdentifierByText(text: __String) { + return accessPrivateIdentifierWorker(getPrivateIdentifierInfo, text); + } - function accessPrivateIdentifierWorker( - getPrivateIdentifierInfo: (privateEnv: PrivateIdentifierEnvironment, key: K) => PrivateIdentifierInfo | undefined, - privateIdentifierKey: K - ) { - if (currentClassLexicalEnvironment?.privateIdentifierEnvironment) { - const info = getPrivateIdentifierInfo(currentClassLexicalEnvironment.privateIdentifierEnvironment, privateIdentifierKey); + function accessGeneratedPrivateIdentifier(name: GeneratedPrivateIdentifier) { + return accessPrivateIdentifierWorker(getGeneratedPrivateIdentifierInfo, getNodeForGeneratedName(name)); + } + + function accessPrivateIdentifierWorker( + getPrivateIdentifierInfo: (privateEnv: PrivateIdentifierEnvironment, key: K) => PrivateIdentifierInfo | undefined, + privateIdentifierKey: K + ) { + if (currentClassLexicalEnvironment?.privateIdentifierEnvironment) { + const info = getPrivateIdentifierInfo(currentClassLexicalEnvironment.privateIdentifierEnvironment, privateIdentifierKey); + if (info) { + return info; + } + } + for (let i = classLexicalEnvironmentStack.length - 1; i >= 0; --i) { + const env = classLexicalEnvironmentStack[i]; + if (!env) { + continue; + } + if (env.privateIdentifierEnvironment) { + const info = getPrivateIdentifierInfo(env.privateIdentifierEnvironment, privateIdentifierKey); if (info) { return info; } } - for (let i = classLexicalEnvironmentStack.length - 1; i >= 0; --i) { - const env = classLexicalEnvironmentStack[i]; - if (!env) { - continue; + } + return undefined; + } + + function wrapPrivateIdentifierForDestructuringTarget(node: PrivateIdentifierPropertyAccessExpression) { + const parameter = factory.getGeneratedNameForNode(node); + const info = accessPrivateIdentifier(node.name); + if (!info) { + return visitEachChild(node, visitor, context); + } + let receiver = node.expression; + // We cannot copy `this` or `super` into the function because they will be bound + // differently inside the function. + if (isThisProperty(node) || isSuperProperty(node) || !isSimpleCopiableExpression(node.expression)) { + receiver = factory.createTempVariable(hoistVariableDeclaration, /*reservedInNestedScopes*/ true); + getPendingExpressions().push(factory.createBinaryExpression(receiver, SyntaxKind.EqualsToken, visitNode(node.expression, visitor, isExpression))); + } + return factory.createAssignmentTargetWrapper( + parameter, + createPrivateIdentifierAssignment( + info, + receiver, + parameter, + SyntaxKind.EqualsToken + ) + ); + } + + function visitArrayAssignmentTarget(node: BindingOrAssignmentElement) { + const target = getTargetOfBindingOrAssignmentElement(node); + if (target) { + let wrapped: LeftHandSideExpression | undefined; + if (isPrivateIdentifierPropertyAccessExpression(target)) { + wrapped = wrapPrivateIdentifierForDestructuringTarget(target); + } + else if (shouldTransformSuperInStaticInitializers && + isSuperProperty(target) && + currentStaticPropertyDeclarationOrStaticBlock && + currentClassLexicalEnvironment) { + const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; + if (facts & ClassFacts.ClassWasDecorated) { + wrapped = visitInvalidSuperProperty(target); } - if (env.privateIdentifierEnvironment) { - const info = getPrivateIdentifierInfo(env.privateIdentifierEnvironment, privateIdentifierKey); - if (info) { - return info; + else if (classConstructor && superClassReference) { + const name = + isElementAccessExpression(target) ? visitNode(target.argumentExpression, visitor, isExpression) : + isIdentifier(target.name) ? factory.createStringLiteralFromNode(target.name) : + undefined; + if (name) { + const temp = factory.createTempVariable(/*recordTempVariable*/ undefined); + wrapped = factory.createAssignmentTargetWrapper( + temp, + factory.createReflectSetCall( + superClassReference, + name, + temp, + classConstructor, + ) + ); } } } - return undefined; - } - - function wrapPrivateIdentifierForDestructuringTarget(node: PrivateIdentifierPropertyAccessExpression) { - const parameter = factory.getGeneratedNameForNode(node); - const info = accessPrivateIdentifier(node.name); - if (!info) { - return visitEachChild(node, visitor, context); - } - let receiver = node.expression; - // We cannot copy `this` or `super` into the function because they will be bound - // differently inside the function. - if (isThisProperty(node) || isSuperProperty(node) || !isSimpleCopiableExpression(node.expression)) { - receiver = factory.createTempVariable(hoistVariableDeclaration, /*reservedInNestedScopes*/ true); - getPendingExpressions().push(factory.createBinaryExpression(receiver, SyntaxKind.EqualsToken, visitNode(node.expression, visitor, isExpression))); + if (wrapped) { + if (isAssignmentExpression(node)) { + return factory.updateBinaryExpression( + node, + wrapped, + node.operatorToken, + visitNode(node.right, visitor, isExpression) + ); + } + else if (isSpreadElement(node)) { + return factory.updateSpreadElement(node, wrapped); + } + else { + return wrapped; + } } - return factory.createAssignmentTargetWrapper( - parameter, - createPrivateIdentifierAssignment( - info, - receiver, - parameter, - SyntaxKind.EqualsToken - ) - ); } + return visitNode(node, assignmentTargetVisitor); + } - function visitArrayAssignmentTarget(node: BindingOrAssignmentElement) { + function visitObjectAssignmentTarget(node: ObjectLiteralElementLike) { + if (isObjectBindingOrAssignmentElement(node) && !isShorthandPropertyAssignment(node)) { const target = getTargetOfBindingOrAssignmentElement(node); + let wrapped: LeftHandSideExpression | undefined; if (target) { - let wrapped: LeftHandSideExpression | undefined; if (isPrivateIdentifierPropertyAccessExpression(target)) { wrapped = wrapPrivateIdentifierForDestructuringTarget(target); } @@ -2384,166 +2478,110 @@ namespace ts { } } } - if (wrapped) { - if (isAssignmentExpression(node)) { - return factory.updateBinaryExpression( - node, - wrapped, - node.operatorToken, - visitNode(node.right, visitor, isExpression) - ); - } - else if (isSpreadElement(node)) { - return factory.updateSpreadElement(node, wrapped); - } - else { - return wrapped; - } - } - } - return visitNode(node, assignmentTargetVisitor); - } - - function visitObjectAssignmentTarget(node: ObjectLiteralElementLike) { - if (isObjectBindingOrAssignmentElement(node) && !isShorthandPropertyAssignment(node)) { - const target = getTargetOfBindingOrAssignmentElement(node); - let wrapped: LeftHandSideExpression | undefined; - if (target) { - if (isPrivateIdentifierPropertyAccessExpression(target)) { - wrapped = wrapPrivateIdentifierForDestructuringTarget(target); - } - else if (shouldTransformSuperInStaticInitializers && - isSuperProperty(target) && - currentStaticPropertyDeclarationOrStaticBlock && - currentClassLexicalEnvironment) { - const { classConstructor, superClassReference, facts } = currentClassLexicalEnvironment; - if (facts & ClassFacts.ClassWasDecorated) { - wrapped = visitInvalidSuperProperty(target); - } - else if (classConstructor && superClassReference) { - const name = - isElementAccessExpression(target) ? visitNode(target.argumentExpression, visitor, isExpression) : - isIdentifier(target.name) ? factory.createStringLiteralFromNode(target.name) : - undefined; - if (name) { - const temp = factory.createTempVariable(/*recordTempVariable*/ undefined); - wrapped = factory.createAssignmentTargetWrapper( - temp, - factory.createReflectSetCall( - superClassReference, - name, - temp, - classConstructor, - ) - ); - } - } - } - } - if (isPropertyAssignment(node)) { - const initializer = getInitializerOfBindingOrAssignmentElement(node); - return factory.updatePropertyAssignment( - node, - visitNode(node.name, visitor, isPropertyName), - wrapped ? - initializer ? factory.createAssignment(wrapped, visitNode(initializer, visitor)) : wrapped : - visitNode(node.initializer, assignmentTargetVisitor, isExpression) - ); - } - if (isSpreadAssignment(node)) { - return factory.updateSpreadAssignment( - node, - wrapped || visitNode(node.expression, assignmentTargetVisitor, isExpression) - ); - } - Debug.assert(wrapped === undefined, "Should not have generated a wrapped target"); } - return visitNode(node, visitor); - } - - function visitAssignmentPattern(node: AssignmentPattern) { - if (isArrayLiteralExpression(node)) { - // Transforms private names in destructuring assignment array bindings. - // Transforms SuperProperty assignments in destructuring assignment array bindings in static initializers. - // - // Source: - // ([ this.#myProp ] = [ "hello" ]); - // - // Transformation: - // [ { set value(x) { this.#myProp = x; } }.value ] = [ "hello" ]; - return factory.updateArrayLiteralExpression( + if (isPropertyAssignment(node)) { + const initializer = getInitializerOfBindingOrAssignmentElement(node); + return factory.updatePropertyAssignment( node, - visitNodes(node.elements, visitArrayAssignmentTarget, isExpression) + visitNode(node.name, visitor, isPropertyName), + wrapped ? + initializer ? factory.createAssignment(wrapped, visitNode(initializer, visitor)) : wrapped : + visitNode(node.initializer, assignmentTargetVisitor, isExpression) ); } - else { - // Transforms private names in destructuring assignment object bindings. - // Transforms SuperProperty assignments in destructuring assignment object bindings in static initializers. - // - // Source: - // ({ stringProperty: this.#myProp } = { stringProperty: "hello" }); - // - // Transformation: - // ({ stringProperty: { set value(x) { this.#myProp = x; } }.value }) = { stringProperty: "hello" }; - return factory.updateObjectLiteralExpression( + if (isSpreadAssignment(node)) { + return factory.updateSpreadAssignment( node, - visitNodes(node.properties, visitObjectAssignmentTarget, isObjectLiteralElementLike) + wrapped || visitNode(node.expression, assignmentTargetVisitor, isExpression) ); } + Debug.assert(wrapped === undefined, "Should not have generated a wrapped target"); } + return visitNode(node, visitor); } - function createPrivateStaticFieldInitializer(variableName: Identifier, initializer: Expression | undefined) { - return factory.createAssignment( - variableName, - factory.createObjectLiteralExpression([ - factory.createPropertyAssignment("value", initializer || factory.createVoidZero()) - ]) - ); + function visitAssignmentPattern(node: AssignmentPattern) { + if (isArrayLiteralExpression(node)) { + // Transforms private names in destructuring assignment array bindings. + // Transforms SuperProperty assignments in destructuring assignment array bindings in static initializers. + // + // Source: + // ([ this.#myProp ] = [ "hello" ]); + // + // Transformation: + // [ { set value(x) { this.#myProp = x; } }.value ] = [ "hello" ]; + return factory.updateArrayLiteralExpression( + node, + visitNodes(node.elements, visitArrayAssignmentTarget, isExpression) + ); + } + else { + // Transforms private names in destructuring assignment object bindings. + // Transforms SuperProperty assignments in destructuring assignment object bindings in static initializers. + // + // Source: + // ({ stringProperty: this.#myProp } = { stringProperty: "hello" }); + // + // Transformation: + // ({ stringProperty: { set value(x) { this.#myProp = x; } }.value }) = { stringProperty: "hello" }; + return factory.updateObjectLiteralExpression( + node, + visitNodes(node.properties, visitObjectAssignmentTarget, isObjectLiteralElementLike) + ); + } } +} - function createPrivateInstanceFieldInitializer(receiver: LeftHandSideExpression, initializer: Expression | undefined, weakMapName: Identifier) { - return factory.createCallExpression( - factory.createPropertyAccessExpression(weakMapName, "set"), - /*typeArguments*/ undefined, - [receiver, initializer || factory.createVoidZero()] - ); - } +function createPrivateStaticFieldInitializer(variableName: Identifier, initializer: Expression | undefined) { + return factory.createAssignment( + variableName, + factory.createObjectLiteralExpression([ + factory.createPropertyAssignment("value", initializer || factory.createVoidZero()) + ]) + ); +} - function createPrivateInstanceMethodInitializer(receiver: LeftHandSideExpression, weakSetName: Identifier) { - return factory.createCallExpression( - factory.createPropertyAccessExpression(weakSetName, "add"), - /*typeArguments*/ undefined, - [receiver] - ); - } +function createPrivateInstanceFieldInitializer(receiver: LeftHandSideExpression, initializer: Expression | undefined, weakMapName: Identifier) { + return factory.createCallExpression( + factory.createPropertyAccessExpression(weakMapName, "set"), + /*typeArguments*/ undefined, + [receiver, initializer || factory.createVoidZero()] + ); +} - function isReservedPrivateName(node: PrivateIdentifier) { - return !isGeneratedPrivateIdentifier(node) && node.escapedText === "#constructor"; - } +function createPrivateInstanceMethodInitializer(receiver: LeftHandSideExpression, weakSetName: Identifier) { + return factory.createCallExpression( + factory.createPropertyAccessExpression(weakSetName, "add"), + /*typeArguments*/ undefined, + [receiver] + ); +} - function getPrivateIdentifier(privateEnv: PrivateIdentifierEnvironment, name: PrivateIdentifier) { - return isGeneratedPrivateIdentifier(name) ? - getGeneratedPrivateIdentifierInfo(privateEnv, getNodeForGeneratedName(name)) : - getPrivateIdentifierInfo(privateEnv, name.escapedText); - } +function isReservedPrivateName(node: PrivateIdentifier) { + return !isGeneratedPrivateIdentifier(node) && node.escapedText === "#constructor"; +} - function setPrivateIdentifier(privateEnv: PrivateIdentifierEnvironment, name: PrivateIdentifier, info: PrivateIdentifierInfo) { - if (isGeneratedPrivateIdentifier(name)) { - privateEnv.generatedIdentifiers ??= new Map(); - privateEnv.generatedIdentifiers.set(getNodeForGeneratedName(name), info); - } - else { - privateEnv.identifiers ??= new Map(); - privateEnv.identifiers.set(name.escapedText, info); - } - } +function getPrivateIdentifier(privateEnv: PrivateIdentifierEnvironment, name: PrivateIdentifier) { + return isGeneratedPrivateIdentifier(name) ? + getGeneratedPrivateIdentifierInfo(privateEnv, getNodeForGeneratedName(name)) : + getPrivateIdentifierInfo(privateEnv, name.escapedText); +} - function getPrivateIdentifierInfo(privateEnv: PrivateIdentifierEnvironment, key: __String) { - return privateEnv.identifiers?.get(key); +function setPrivateIdentifier(privateEnv: PrivateIdentifierEnvironment, name: PrivateIdentifier, info: PrivateIdentifierInfo) { + if (isGeneratedPrivateIdentifier(name)) { + privateEnv.generatedIdentifiers ??= new Map(); + privateEnv.generatedIdentifiers.set(getNodeForGeneratedName(name), info); } - - function getGeneratedPrivateIdentifierInfo(privateEnv: PrivateIdentifierEnvironment, key: Node) { - return privateEnv.generatedIdentifiers?.get(key); + else { + privateEnv.identifiers ??= new Map(); + privateEnv.identifiers.set(name.escapedText, info); } } + +function getPrivateIdentifierInfo(privateEnv: PrivateIdentifierEnvironment, key: __String) { + return privateEnv.identifiers?.get(key); +} + +function getGeneratedPrivateIdentifierInfo(privateEnv: PrivateIdentifierEnvironment, key: Node) { + return privateEnv.generatedIdentifiers?.get(key); +} diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index e453b77a3bff2..3e0eaed09cb96 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -1,1745 +1,1789 @@ -/*@internal*/ -namespace ts { - export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, file: SourceFile | undefined): DiagnosticWithLocation[] | undefined { - const compilerOptions = host.getCompilerOptions(); - const result = transformNodes(resolver, host, factory, compilerOptions, file ? [file] : filter(host.getSourceFiles(), isSourceFileNotJson), [transformDeclarations], /*allowDtsFiles*/ false); - return result.diagnostics; - } +import { + AccessorDeclaration, addRelatedInfo, AllAccessorDeclarations, AnyImportSyntax, append, ArrayBindingElement, + arrayFrom, AssertClause, BindingElement, BindingName, BindingPattern, Bundle, CallSignatureDeclaration, + canHaveModifiers, canProduceDiagnostics, ClassDeclaration, CommentRange, compact, concatenate, ConditionalTypeNode, + ConstructorDeclaration, ConstructorTypeNode, ConstructSignatureDeclaration, contains, createDiagnosticForNode, + createEmptyExports, createGetSymbolAccessibilityDiagnosticForNode, + createGetSymbolAccessibilityDiagnosticForNodeName, createSymbolTable, createUnparsedSourceFile, Debug, Declaration, + DeclarationDiagnosticProducing, DeclarationName, declarationNameToString, Diagnostics, DiagnosticWithLocation, + EmitFlags, EmitHost, EmitResolver, emptyArray, EntityNameOrEntityNameExpression, EnumDeclaration, ESMap, + ExportAssignment, ExportDeclaration, ExpressionWithTypeArguments, factory, FileReference, filter, flatMap, flatten, + forEach, FunctionDeclaration, FunctionTypeNode, GeneratedIdentifierFlags, GetAccessorDeclaration, getCommentRange, + getDirectoryPath, getEffectiveBaseTypeNode, getEffectiveModifierFlags, + getExternalModuleImportEqualsDeclarationExpression, getExternalModuleNameFromDeclaration, + getFirstConstructorWithBody, getLeadingCommentRanges, getLeadingCommentRangesOfNode, getLineAndCharacterOfPosition, + getNameOfDeclaration, getOriginalNodeId, getOutputPathsFor, getParseTreeNode, getRelativePathToDirectoryOrUrl, + getResolutionModeOverrideForClause, getResolvedExternalModuleName, getSetAccessorValueParameter, + getSourceFileOfNode, GetSymbolAccessibilityDiagnostic, getTextOfNode, getThisParameter, getTrailingCommentRanges, + hasDynamicName, hasEffectiveModifier, hasExtension, hasJSDocNodes, HasModifiers, hasSyntacticModifier, + HeritageClause, Identifier, ImportDeclaration, ImportEqualsDeclaration, ImportTypeNode, IndexSignatureDeclaration, + InterfaceDeclaration, isAnyImportSyntax, isArray, isBindingPattern, isClassDeclaration, isDeclaration, isEntityName, + isEntityNameExpression, isExportAssignment, isExportDeclaration, isExternalModule, isExternalModuleAugmentation, + isExternalModuleIndicator, isExternalModuleReference, isExternalOrCommonJsModule, isFunctionDeclaration, + isFunctionLike, isGlobalScopeAugmentation, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, + isIndexSignatureDeclaration, isInterfaceDeclaration, isJsonSourceFile, isLateVisibilityPaintedStatement, + isLiteralImportTypeNode, isMappedTypeNode, isMethodDeclaration, isMethodSignature, isModifier, isModuleDeclaration, + isNightly, isOmittedExpression, isPrivateIdentifier, isPropertyAccessExpression, isPropertySignature, + isSemicolonClassElement, isSetAccessorDeclaration, isSourceFile, isSourceFileJS, isSourceFileNotJson, + isStringANonContextualKeyword, isStringLiteral, isStringLiteralLike, isTupleTypeNode, isTypeAliasDeclaration, + isTypeNode, isTypeParameterDeclaration, isTypeQueryNode, isUnparsedSource, last, LateBoundDeclaration, + LateVisibilityPaintedStatement, length, map, Map, mapDefined, MethodDeclaration, MethodSignature, Modifier, + ModifierFlags, ModuleBody, ModuleDeclaration, NamedDeclaration, NamespaceDeclaration, + needsScopeMarker, Node, NodeArray, NodeBuilderFlags, NodeFlags, NodeId, normalizeSlashes, OmittedExpression, + orderedRemoveItem, ParameterDeclaration, parseNodeFactory, pathContainsNodeModules, pathIsRelative, + PropertyDeclaration, PropertySignature, pushIfUnique, removeAllComments, Set, SetAccessorDeclaration, + setCommentRange, setEmitFlags, setOriginalNode, setParent, setTextRange, SignatureDeclaration, skipTrivia, some, + SourceFile, startsWith, Statement, stringContains, StringLiteral, Symbol, SymbolAccessibility, + SymbolAccessibilityResult, SymbolFlags, SymbolTracker, SyntaxKind, toFileNameLowerCase, toPath, + TransformationContext, transformNodes, tryCast, TypeAliasDeclaration, TypeNode, TypeParameterDeclaration, + TypeReferenceNode, unescapeLeadingUnderscores, UnparsedSource, VariableDeclaration, VariableStatement, visitArray, + visitEachChild, visitNode, visitNodes, VisitResult, +} from "../_namespaces/ts"; +import * as moduleSpecifiers from "../_namespaces/ts.moduleSpecifiers"; + +/** @internal */ +export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, file: SourceFile | undefined): DiagnosticWithLocation[] | undefined { + const compilerOptions = host.getCompilerOptions(); + const result = transformNodes(resolver, host, factory, compilerOptions, file ? [file] : filter(host.getSourceFiles(), isSourceFileNotJson), [transformDeclarations], /*allowDtsFiles*/ false); + return result.diagnostics; +} - function hasInternalAnnotation(range: CommentRange, currentSourceFile: SourceFile) { - const comment = currentSourceFile.text.substring(range.pos, range.end); - return stringContains(comment, "@internal"); - } +function hasInternalAnnotation(range: CommentRange, currentSourceFile: SourceFile) { + const comment = currentSourceFile.text.substring(range.pos, range.end); + return stringContains(comment, "@internal"); +} - export function isInternalDeclaration(node: Node, currentSourceFile: SourceFile) { - const parseTreeNode = getParseTreeNode(node); - if (parseTreeNode && parseTreeNode.kind === SyntaxKind.Parameter) { - const paramIdx = (parseTreeNode.parent as SignatureDeclaration).parameters.indexOf(parseTreeNode as ParameterDeclaration); - const previousSibling = paramIdx > 0 ? (parseTreeNode.parent as SignatureDeclaration).parameters[paramIdx - 1] : undefined; - const text = currentSourceFile.text; - const commentRanges = previousSibling - ? concatenate( - // to handle - // ... parameters, /* @internal */ - // public param: string - getTrailingCommentRanges(text, skipTrivia(text, previousSibling.end + 1, /* stopAfterLineBreak */ false, /* stopAtComments */ true)), - getLeadingCommentRanges(text, node.pos) - ) - : getTrailingCommentRanges(text, skipTrivia(text, node.pos, /* stopAfterLineBreak */ false, /* stopAtComments */ true)); - return commentRanges && commentRanges.length && hasInternalAnnotation(last(commentRanges), currentSourceFile); - } - const leadingCommentRanges = parseTreeNode && getLeadingCommentRangesOfNode(parseTreeNode, currentSourceFile); - return !!forEach(leadingCommentRanges, range => { - return hasInternalAnnotation(range, currentSourceFile); - }); +/** @internal */ +export function isInternalDeclaration(node: Node, currentSourceFile: SourceFile) { + const parseTreeNode = getParseTreeNode(node); + if (parseTreeNode && parseTreeNode.kind === SyntaxKind.Parameter) { + const paramIdx = (parseTreeNode.parent as SignatureDeclaration).parameters.indexOf(parseTreeNode as ParameterDeclaration); + const previousSibling = paramIdx > 0 ? (parseTreeNode.parent as SignatureDeclaration).parameters[paramIdx - 1] : undefined; + const text = currentSourceFile.text; + const commentRanges = previousSibling + ? concatenate( + // to handle + // ... parameters, /** @internal */ + // public param: string + getTrailingCommentRanges(text, skipTrivia(text, previousSibling.end + 1, /* stopAfterLineBreak */ false, /* stopAtComments */ true)), + getLeadingCommentRanges(text, node.pos) + ) + : getTrailingCommentRanges(text, skipTrivia(text, node.pos, /* stopAfterLineBreak */ false, /* stopAtComments */ true)); + return commentRanges && commentRanges.length && hasInternalAnnotation(last(commentRanges), currentSourceFile); } + const leadingCommentRanges = parseTreeNode && getLeadingCommentRangesOfNode(parseTreeNode, currentSourceFile); + return !!forEach(leadingCommentRanges, range => { + return hasInternalAnnotation(range, currentSourceFile); + }); +} - const declarationEmitNodeBuilderFlags = - NodeBuilderFlags.MultilineObjectLiterals | - NodeBuilderFlags.WriteClassExpressionAsTypeLiteral | - NodeBuilderFlags.UseTypeOfFunction | - NodeBuilderFlags.UseStructuralFallback | - NodeBuilderFlags.AllowEmptyTuple | - NodeBuilderFlags.GenerateNamesForShadowedTypeParams | - NodeBuilderFlags.NoTruncation; - - /** - * Transforms a ts file into a .d.ts file - * This process requires type information, which is retrieved through the emit resolver. Because of this, - * in many places this transformer assumes it will be operating on parse tree nodes directly. - * This means that _no transforms should be allowed to occur before this one_. - */ - export function transformDeclarations(context: TransformationContext) { - const throwDiagnostic = () => Debug.fail("Diagnostic emitted without context"); - let getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic = throwDiagnostic; - let needsDeclare = true; - let isBundledEmit = false; - let resultHasExternalModuleIndicator = false; - let needsScopeFixMarker = false; - let resultHasScopeMarker = false; - let enclosingDeclaration: Node; - let necessaryTypeReferences: Set<[specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined]> | undefined; - let lateMarkedStatements: LateVisibilityPaintedStatement[] | undefined; - let lateStatementReplacementMap: ESMap>; - let suppressNewDiagnosticContexts: boolean; - let exportedModulesFromDeclarationEmit: Symbol[] | undefined; - - const { factory } = context; - const host = context.getEmitHost(); - const symbolTracker: SymbolTracker = { - trackSymbol, - reportInaccessibleThisError, - reportInaccessibleUniqueSymbolError, - reportCyclicStructureError, - reportPrivateInBaseOfClassExpression, - reportLikelyUnsafeImportRequiredError, - reportTruncationError, - moduleResolverHost: host, - trackReferencedAmbientModule, - trackExternalModuleSymbolOfImportTypeNode, - reportNonlocalAugmentation, - reportNonSerializableProperty, - reportImportTypeNodeResolutionModeOverride, - }; - let errorNameNode: DeclarationName | undefined; - let errorFallbackNode: Declaration | undefined; - - let currentSourceFile: SourceFile; - let refs: ESMap; - let libs: ESMap; - let emittedImports: readonly AnyImportSyntax[] | undefined; // must be declared in container so it can be `undefined` while transformer's first pass - const resolver = context.getEmitResolver(); - const options = context.getCompilerOptions(); - const { noResolve, stripInternal } = options; - return transformRoot; - - function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: readonly [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined): void { - if (!typeReferenceDirectives) { - return; - } - necessaryTypeReferences = necessaryTypeReferences || new Set(); - for (const ref of typeReferenceDirectives) { - necessaryTypeReferences.add(ref); - } +const declarationEmitNodeBuilderFlags = + NodeBuilderFlags.MultilineObjectLiterals | + NodeBuilderFlags.WriteClassExpressionAsTypeLiteral | + NodeBuilderFlags.UseTypeOfFunction | + NodeBuilderFlags.UseStructuralFallback | + NodeBuilderFlags.AllowEmptyTuple | + NodeBuilderFlags.GenerateNamesForShadowedTypeParams | + NodeBuilderFlags.NoTruncation; + +/** + * Transforms a ts file into a .d.ts file + * This process requires type information, which is retrieved through the emit resolver. Because of this, + * in many places this transformer assumes it will be operating on parse tree nodes directly. + * This means that _no transforms should be allowed to occur before this one_. + * + * @internal + */ +export function transformDeclarations(context: TransformationContext) { + const throwDiagnostic = () => Debug.fail("Diagnostic emitted without context"); + let getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic = throwDiagnostic; + let needsDeclare = true; + let isBundledEmit = false; + let resultHasExternalModuleIndicator = false; + let needsScopeFixMarker = false; + let resultHasScopeMarker = false; + let enclosingDeclaration: Node; + let necessaryTypeReferences: Set<[specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined]> | undefined; + let lateMarkedStatements: LateVisibilityPaintedStatement[] | undefined; + let lateStatementReplacementMap: ESMap>; + let suppressNewDiagnosticContexts: boolean; + let exportedModulesFromDeclarationEmit: Symbol[] | undefined; + + const { factory } = context; + const host = context.getEmitHost(); + const symbolTracker: SymbolTracker = { + trackSymbol, + reportInaccessibleThisError, + reportInaccessibleUniqueSymbolError, + reportCyclicStructureError, + reportPrivateInBaseOfClassExpression, + reportLikelyUnsafeImportRequiredError, + reportTruncationError, + moduleResolverHost: host, + trackReferencedAmbientModule, + trackExternalModuleSymbolOfImportTypeNode, + reportNonlocalAugmentation, + reportNonSerializableProperty, + reportImportTypeNodeResolutionModeOverride, + }; + let errorNameNode: DeclarationName | undefined; + let errorFallbackNode: Declaration | undefined; + + let currentSourceFile: SourceFile; + let refs: ESMap; + let libs: ESMap; + let emittedImports: readonly AnyImportSyntax[] | undefined; // must be declared in container so it can be `undefined` while transformer's first pass + const resolver = context.getEmitResolver(); + const options = context.getCompilerOptions(); + const { noResolve, stripInternal } = options; + return transformRoot; + + function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: readonly [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined): void { + if (!typeReferenceDirectives) { + return; + } + necessaryTypeReferences = necessaryTypeReferences || new Set(); + for (const ref of typeReferenceDirectives) { + necessaryTypeReferences.add(ref); } + } - function trackReferencedAmbientModule(node: ModuleDeclaration, symbol: Symbol) { - // If it is visible via `// `, then we should just use that - const directives = resolver.getTypeReferenceDirectivesForSymbol(symbol, SymbolFlags.All); - if (length(directives)) { - return recordTypeReferenceDirectivesIfNecessary(directives); - } - // Otherwise we should emit a path-based reference - const container = getSourceFileOfNode(node); - refs.set(getOriginalNodeId(container), container); + function trackReferencedAmbientModule(node: ModuleDeclaration, symbol: Symbol) { + // If it is visible via `// `, then we should just use that + const directives = resolver.getTypeReferenceDirectivesForSymbol(symbol, SymbolFlags.All); + if (length(directives)) { + return recordTypeReferenceDirectivesIfNecessary(directives); } + // Otherwise we should emit a path-based reference + const container = getSourceFileOfNode(node); + refs.set(getOriginalNodeId(container), container); + } - function handleSymbolAccessibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { - if (symbolAccessibilityResult.accessibility === SymbolAccessibility.Accessible) { - // Add aliases back onto the possible imports list if they're not there so we can try them again with updated visibility info - if (symbolAccessibilityResult && symbolAccessibilityResult.aliasesToMakeVisible) { - if (!lateMarkedStatements) { - lateMarkedStatements = symbolAccessibilityResult.aliasesToMakeVisible; - } - else { - for (const ref of symbolAccessibilityResult.aliasesToMakeVisible) { - pushIfUnique(lateMarkedStatements, ref); - } - } + function handleSymbolAccessibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (symbolAccessibilityResult.accessibility === SymbolAccessibility.Accessible) { + // Add aliases back onto the possible imports list if they're not there so we can try them again with updated visibility info + if (symbolAccessibilityResult && symbolAccessibilityResult.aliasesToMakeVisible) { + if (!lateMarkedStatements) { + lateMarkedStatements = symbolAccessibilityResult.aliasesToMakeVisible; } - - // TODO: Do all these accessibility checks inside/after the first pass in the checker when declarations are enabled, if possible - } - else { - // Report error - const errorInfo = getSymbolAccessibilityDiagnostic(symbolAccessibilityResult); - if (errorInfo) { - if (errorInfo.typeName) { - context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, - errorInfo.diagnosticMessage, - getTextOfNode(errorInfo.typeName), - symbolAccessibilityResult.errorSymbolName, - symbolAccessibilityResult.errorModuleName)); - } - else { - context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, - errorInfo.diagnosticMessage, - symbolAccessibilityResult.errorSymbolName, - symbolAccessibilityResult.errorModuleName)); + else { + for (const ref of symbolAccessibilityResult.aliasesToMakeVisible) { + pushIfUnique(lateMarkedStatements, ref); } - return true; } } - return false; - } - function trackExternalModuleSymbolOfImportTypeNode(symbol: Symbol) { - if (!isBundledEmit) { - (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); + // TODO: Do all these accessibility checks inside/after the first pass in the checker when declarations are enabled, if possible + } + else { + // Report error + const errorInfo = getSymbolAccessibilityDiagnostic(symbolAccessibilityResult); + if (errorInfo) { + if (errorInfo.typeName) { + context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, + errorInfo.diagnosticMessage, + getTextOfNode(errorInfo.typeName), + symbolAccessibilityResult.errorSymbolName, + symbolAccessibilityResult.errorModuleName)); + } + else { + context.addDiagnostic(createDiagnosticForNode(symbolAccessibilityResult.errorNode || errorInfo.errorNode, + errorInfo.diagnosticMessage, + symbolAccessibilityResult.errorSymbolName, + symbolAccessibilityResult.errorModuleName)); + } + return true; } } + return false; + } - function trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) { - if (symbol.flags & SymbolFlags.TypeParameter) return false; - const issuedDiagnostic = handleSymbolAccessibilityError(resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ true)); - recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForSymbol(symbol, meaning)); - return issuedDiagnostic; + function trackExternalModuleSymbolOfImportTypeNode(symbol: Symbol) { + if (!isBundledEmit) { + (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); } + } - function reportPrivateInBaseOfClassExpression(propertyName: string) { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic( - createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.Property_0_of_exported_class_expression_may_not_be_private_or_protected, propertyName)); - } - } + function trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) { + if (symbol.flags & SymbolFlags.TypeParameter) return false; + const issuedDiagnostic = handleSymbolAccessibilityError(resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ true)); + recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForSymbol(symbol, meaning)); + return issuedDiagnostic; + } - function errorDeclarationNameWithFallback() { - return errorNameNode ? declarationNameToString(errorNameNode) : - errorFallbackNode && getNameOfDeclaration(errorFallbackNode) ? declarationNameToString(getNameOfDeclaration(errorFallbackNode)) : - errorFallbackNode && isExportAssignment(errorFallbackNode) ? errorFallbackNode.isExportEquals ? "export=" : "default" : - "(Missing)"; // same fallback declarationNameToString uses when node is zero-width (ie, nameless) + function reportPrivateInBaseOfClassExpression(propertyName: string) { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic( + createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.Property_0_of_exported_class_expression_may_not_be_private_or_protected, propertyName)); } + } - function reportInaccessibleUniqueSymbolError() { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, - errorDeclarationNameWithFallback(), - "unique symbol")); - } - } + function errorDeclarationNameWithFallback() { + return errorNameNode ? declarationNameToString(errorNameNode) : + errorFallbackNode && getNameOfDeclaration(errorFallbackNode) ? declarationNameToString(getNameOfDeclaration(errorFallbackNode)) : + errorFallbackNode && isExportAssignment(errorFallbackNode) ? errorFallbackNode.isExportEquals ? "export=" : "default" : + "(Missing)"; // same fallback declarationNameToString uses when node is zero-width (ie, nameless) + } - function reportCyclicStructureError() { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_a_type_with_a_cyclic_structure_which_cannot_be_trivially_serialized_A_type_annotation_is_necessary, - errorDeclarationNameWithFallback())); - } + function reportInaccessibleUniqueSymbolError() { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, + errorDeclarationNameWithFallback(), + "unique symbol")); } + } - function reportInaccessibleThisError() { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, - errorDeclarationNameWithFallback(), - "this")); - } + function reportCyclicStructureError() { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_a_type_with_a_cyclic_structure_which_cannot_be_trivially_serialized_A_type_annotation_is_necessary, + errorDeclarationNameWithFallback())); } + } - function reportLikelyUnsafeImportRequiredError(specifier: string) { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_cannot_be_named_without_a_reference_to_1_This_is_likely_not_portable_A_type_annotation_is_necessary, - errorDeclarationNameWithFallback(), - specifier)); - } + function reportInaccessibleThisError() { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_references_an_inaccessible_1_type_A_type_annotation_is_necessary, + errorDeclarationNameWithFallback(), + "this")); } + } - function reportTruncationError() { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_this_node_exceeds_the_maximum_length_the_compiler_will_serialize_An_explicit_type_annotation_is_needed)); - } + function reportLikelyUnsafeImportRequiredError(specifier: string) { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_0_cannot_be_named_without_a_reference_to_1_This_is_likely_not_portable_A_type_annotation_is_necessary, + errorDeclarationNameWithFallback(), + specifier)); } + } - function reportNonlocalAugmentation(containingFile: SourceFile, parentSymbol: Symbol, symbol: Symbol) { - const primaryDeclaration = parentSymbol.declarations?.find(d => getSourceFileOfNode(d) === containingFile); - const augmentingDeclarations = filter(symbol.declarations, d => getSourceFileOfNode(d) !== containingFile); - if (primaryDeclaration && augmentingDeclarations) { - for (const augmentations of augmentingDeclarations) { - context.addDiagnostic(addRelatedInfo( - createDiagnosticForNode(augmentations, Diagnostics.Declaration_augments_declaration_in_another_file_This_cannot_be_serialized), - createDiagnosticForNode(primaryDeclaration, Diagnostics.This_is_the_declaration_being_augmented_Consider_moving_the_augmenting_declaration_into_the_same_file) - )); - } - } + function reportTruncationError() { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_inferred_type_of_this_node_exceeds_the_maximum_length_the_compiler_will_serialize_An_explicit_type_annotation_is_needed)); } + } - function reportNonSerializableProperty(propertyName: string) { - if (errorNameNode || errorFallbackNode) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_type_of_this_node_cannot_be_serialized_because_its_property_0_cannot_be_serialized, propertyName)); + function reportNonlocalAugmentation(containingFile: SourceFile, parentSymbol: Symbol, symbol: Symbol) { + const primaryDeclaration = parentSymbol.declarations?.find(d => getSourceFileOfNode(d) === containingFile); + const augmentingDeclarations = filter(symbol.declarations, d => getSourceFileOfNode(d) !== containingFile); + if (primaryDeclaration && augmentingDeclarations) { + for (const augmentations of augmentingDeclarations) { + context.addDiagnostic(addRelatedInfo( + createDiagnosticForNode(augmentations, Diagnostics.Declaration_augments_declaration_in_another_file_This_cannot_be_serialized), + createDiagnosticForNode(primaryDeclaration, Diagnostics.This_is_the_declaration_being_augmented_Consider_moving_the_augmenting_declaration_into_the_same_file) + )); } } + } - function reportImportTypeNodeResolutionModeOverride() { - if (!isNightly() && (errorNameNode || errorFallbackNode)) { - context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_type_of_this_expression_cannot_be_named_without_a_resolution_mode_assertion_which_is_an_unstable_feature_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next)); - } + function reportNonSerializableProperty(propertyName: string) { + if (errorNameNode || errorFallbackNode) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_type_of_this_node_cannot_be_serialized_because_its_property_0_cannot_be_serialized, propertyName)); } + } - function transformDeclarationsForJS(sourceFile: SourceFile, bundled?: boolean) { - const oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = (s) => (s.errorNode && canProduceDiagnostics(s.errorNode) ? createGetSymbolAccessibilityDiagnosticForNode(s.errorNode)(s) : ({ - diagnosticMessage: s.errorModuleName - ? Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_from_module_1_An_explicit_type_annotation_may_unblock_declaration_emit - : Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_An_explicit_type_annotation_may_unblock_declaration_emit, - errorNode: s.errorNode || sourceFile - })); - const result = resolver.getDeclarationStatementsForSourceFile(sourceFile, declarationEmitNodeBuilderFlags, symbolTracker, bundled); - getSymbolAccessibilityDiagnostic = oldDiag; - return result; + function reportImportTypeNodeResolutionModeOverride() { + if (!isNightly() && (errorNameNode || errorFallbackNode)) { + context.addDiagnostic(createDiagnosticForNode((errorNameNode || errorFallbackNode)!, Diagnostics.The_type_of_this_expression_cannot_be_named_without_a_resolution_mode_assertion_which_is_an_unstable_feature_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next)); } + } - function transformRoot(node: Bundle): Bundle; - function transformRoot(node: SourceFile): SourceFile; - function transformRoot(node: SourceFile | Bundle): SourceFile | Bundle; - function transformRoot(node: SourceFile | Bundle) { - if (node.kind === SyntaxKind.SourceFile && node.isDeclarationFile) { - return node; - } + function transformDeclarationsForJS(sourceFile: SourceFile, bundled?: boolean) { + const oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = (s) => (s.errorNode && canProduceDiagnostics(s.errorNode) ? createGetSymbolAccessibilityDiagnosticForNode(s.errorNode)(s) : ({ + diagnosticMessage: s.errorModuleName + ? Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_from_module_1_An_explicit_type_annotation_may_unblock_declaration_emit + : Diagnostics.Declaration_emit_for_this_file_requires_using_private_name_0_An_explicit_type_annotation_may_unblock_declaration_emit, + errorNode: s.errorNode || sourceFile + })); + const result = resolver.getDeclarationStatementsForSourceFile(sourceFile, declarationEmitNodeBuilderFlags, symbolTracker, bundled); + getSymbolAccessibilityDiagnostic = oldDiag; + return result; + } - if (node.kind === SyntaxKind.Bundle) { - isBundledEmit = true; - refs = new Map(); - libs = new Map(); - let hasNoDefaultLib = false; - const bundle = factory.createBundle(map(node.sourceFiles, - sourceFile => { - if (sourceFile.isDeclarationFile) return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217 - hasNoDefaultLib = hasNoDefaultLib || sourceFile.hasNoDefaultLib; - currentSourceFile = sourceFile; - enclosingDeclaration = sourceFile; - lateMarkedStatements = undefined; - suppressNewDiagnosticContexts = false; - lateStatementReplacementMap = new Map(); - getSymbolAccessibilityDiagnostic = throwDiagnostic; - needsScopeFixMarker = false; - resultHasScopeMarker = false; - collectReferences(sourceFile, refs); - collectLibs(sourceFile, libs); - if (isExternalOrCommonJsModule(sourceFile) || isJsonSourceFile(sourceFile)) { - resultHasExternalModuleIndicator = false; // unused in external module bundle emit (all external modules are within module blocks, therefore are known to be modules) - needsDeclare = false; - const statements = isSourceFileJS(sourceFile) ? factory.createNodeArray(transformDeclarationsForJS(sourceFile, /*bundled*/ true)) : visitNodes(sourceFile.statements, visitDeclarationStatements); - const newFile = factory.updateSourceFile(sourceFile, [factory.createModuleDeclaration( - [factory.createModifier(SyntaxKind.DeclareKeyword)], - factory.createStringLiteral(getResolvedExternalModuleName(context.getEmitHost(), sourceFile)), - factory.createModuleBlock(setTextRange(factory.createNodeArray(transformAndReplaceLatePaintedStatements(statements)), sourceFile.statements)) - )], /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); - return newFile; - } - needsDeclare = true; - const updated = isSourceFileJS(sourceFile) ? factory.createNodeArray(transformDeclarationsForJS(sourceFile)) : visitNodes(sourceFile.statements, visitDeclarationStatements); - return factory.updateSourceFile(sourceFile, transformAndReplaceLatePaintedStatements(updated), /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); - } - ), mapDefined(node.prepends, prepend => { - if (prepend.kind === SyntaxKind.InputFiles) { - const sourceFile = createUnparsedSourceFile(prepend, "dts", stripInternal); - hasNoDefaultLib = hasNoDefaultLib || !!sourceFile.hasNoDefaultLib; - collectReferences(sourceFile, refs); - recordTypeReferenceDirectivesIfNecessary(map(sourceFile.typeReferenceDirectives, ref => [ref.fileName, ref.resolutionMode])); - collectLibs(sourceFile, libs); - return sourceFile; - } - return prepend; - })); - bundle.syntheticFileReferences = []; - bundle.syntheticTypeReferences = getFileReferencesForUsedTypeReferences(); - bundle.syntheticLibReferences = getLibReferences(); - bundle.hasNoDefaultLib = hasNoDefaultLib; - const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!)); - const referenceVisitor = mapReferencesIntoArray(bundle.syntheticFileReferences as FileReference[], outputFilePath); - refs.forEach(referenceVisitor); - return bundle; - } + function transformRoot(node: Bundle): Bundle; + function transformRoot(node: SourceFile): SourceFile; + function transformRoot(node: SourceFile | Bundle): SourceFile | Bundle; + function transformRoot(node: SourceFile | Bundle) { + if (node.kind === SyntaxKind.SourceFile && node.isDeclarationFile) { + return node; + } - // Single source file - needsDeclare = true; - needsScopeFixMarker = false; - resultHasScopeMarker = false; - enclosingDeclaration = node; - currentSourceFile = node; - getSymbolAccessibilityDiagnostic = throwDiagnostic; - isBundledEmit = false; - resultHasExternalModuleIndicator = false; - suppressNewDiagnosticContexts = false; - lateMarkedStatements = undefined; - lateStatementReplacementMap = new Map(); - necessaryTypeReferences = undefined; - refs = collectReferences(currentSourceFile, new Map()); - libs = collectLibs(currentSourceFile, new Map()); - const references: FileReference[] = []; - const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!)); - const referenceVisitor = mapReferencesIntoArray(references, outputFilePath); - let combinedStatements: NodeArray; - if (isSourceFileJS(currentSourceFile)) { - combinedStatements = factory.createNodeArray(transformDeclarationsForJS(node)); - refs.forEach(referenceVisitor); - emittedImports = filter(combinedStatements, isAnyImportSyntax); - } - else { - const statements = visitNodes(node.statements, visitDeclarationStatements); - combinedStatements = setTextRange(factory.createNodeArray(transformAndReplaceLatePaintedStatements(statements)), node.statements); - refs.forEach(referenceVisitor); - emittedImports = filter(combinedStatements, isAnyImportSyntax); - if (isExternalModule(node) && (!resultHasExternalModuleIndicator || (needsScopeFixMarker && !resultHasScopeMarker))) { - combinedStatements = setTextRange(factory.createNodeArray([...combinedStatements, createEmptyExports(factory)]), combinedStatements); + if (node.kind === SyntaxKind.Bundle) { + isBundledEmit = true; + refs = new Map(); + libs = new Map(); + let hasNoDefaultLib = false; + const bundle = factory.createBundle(map(node.sourceFiles, + sourceFile => { + if (sourceFile.isDeclarationFile) return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217 + hasNoDefaultLib = hasNoDefaultLib || sourceFile.hasNoDefaultLib; + currentSourceFile = sourceFile; + enclosingDeclaration = sourceFile; + lateMarkedStatements = undefined; + suppressNewDiagnosticContexts = false; + lateStatementReplacementMap = new Map(); + getSymbolAccessibilityDiagnostic = throwDiagnostic; + needsScopeFixMarker = false; + resultHasScopeMarker = false; + collectReferences(sourceFile, refs); + collectLibs(sourceFile, libs); + if (isExternalOrCommonJsModule(sourceFile) || isJsonSourceFile(sourceFile)) { + resultHasExternalModuleIndicator = false; // unused in external module bundle emit (all external modules are within module blocks, therefore are known to be modules) + needsDeclare = false; + const statements = isSourceFileJS(sourceFile) ? factory.createNodeArray(transformDeclarationsForJS(sourceFile, /*bundled*/ true)) : visitNodes(sourceFile.statements, visitDeclarationStatements); + const newFile = factory.updateSourceFile(sourceFile, [factory.createModuleDeclaration( + [factory.createModifier(SyntaxKind.DeclareKeyword)], + factory.createStringLiteral(getResolvedExternalModuleName(context.getEmitHost(), sourceFile)), + factory.createModuleBlock(setTextRange(factory.createNodeArray(transformAndReplaceLatePaintedStatements(statements)), sourceFile.statements)) + )], /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); + return newFile; + } + needsDeclare = true; + const updated = isSourceFileJS(sourceFile) ? factory.createNodeArray(transformDeclarationsForJS(sourceFile)) : visitNodes(sourceFile.statements, visitDeclarationStatements); + return factory.updateSourceFile(sourceFile, transformAndReplaceLatePaintedStatements(updated), /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []); } - } - const updated = factory.updateSourceFile(node, combinedStatements, /*isDeclarationFile*/ true, references, getFileReferencesForUsedTypeReferences(), node.hasNoDefaultLib, getLibReferences()); - updated.exportedModulesFromDeclarationEmit = exportedModulesFromDeclarationEmit; - return updated; + ), mapDefined(node.prepends, prepend => { + if (prepend.kind === SyntaxKind.InputFiles) { + const sourceFile = createUnparsedSourceFile(prepend, "dts", stripInternal); + hasNoDefaultLib = hasNoDefaultLib || !!sourceFile.hasNoDefaultLib; + collectReferences(sourceFile, refs); + recordTypeReferenceDirectivesIfNecessary(map(sourceFile.typeReferenceDirectives, ref => [ref.fileName, ref.resolutionMode])); + collectLibs(sourceFile, libs); + return sourceFile; + } + return prepend; + })); + bundle.syntheticFileReferences = []; + bundle.syntheticTypeReferences = getFileReferencesForUsedTypeReferences(); + bundle.syntheticLibReferences = getLibReferences(); + bundle.hasNoDefaultLib = hasNoDefaultLib; + const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!)); + const referenceVisitor = mapReferencesIntoArray(bundle.syntheticFileReferences as FileReference[], outputFilePath); + refs.forEach(referenceVisitor); + return bundle; + } - function getLibReferences() { - return map(arrayFrom(libs.keys()), lib => ({ fileName: lib, pos: -1, end: -1 })); + // Single source file + needsDeclare = true; + needsScopeFixMarker = false; + resultHasScopeMarker = false; + enclosingDeclaration = node; + currentSourceFile = node; + getSymbolAccessibilityDiagnostic = throwDiagnostic; + isBundledEmit = false; + resultHasExternalModuleIndicator = false; + suppressNewDiagnosticContexts = false; + lateMarkedStatements = undefined; + lateStatementReplacementMap = new Map(); + necessaryTypeReferences = undefined; + refs = collectReferences(currentSourceFile, new Map()); + libs = collectLibs(currentSourceFile, new Map()); + const references: FileReference[] = []; + const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!)); + const referenceVisitor = mapReferencesIntoArray(references, outputFilePath); + let combinedStatements: NodeArray; + if (isSourceFileJS(currentSourceFile)) { + combinedStatements = factory.createNodeArray(transformDeclarationsForJS(node)); + refs.forEach(referenceVisitor); + emittedImports = filter(combinedStatements, isAnyImportSyntax); + } + else { + const statements = visitNodes(node.statements, visitDeclarationStatements); + combinedStatements = setTextRange(factory.createNodeArray(transformAndReplaceLatePaintedStatements(statements)), node.statements); + refs.forEach(referenceVisitor); + emittedImports = filter(combinedStatements, isAnyImportSyntax); + if (isExternalModule(node) && (!resultHasExternalModuleIndicator || (needsScopeFixMarker && !resultHasScopeMarker))) { + combinedStatements = setTextRange(factory.createNodeArray([...combinedStatements, createEmptyExports(factory)]), combinedStatements); } + } + const updated = factory.updateSourceFile(node, combinedStatements, /*isDeclarationFile*/ true, references, getFileReferencesForUsedTypeReferences(), node.hasNoDefaultLib, getLibReferences()); + updated.exportedModulesFromDeclarationEmit = exportedModulesFromDeclarationEmit; + return updated; - function getFileReferencesForUsedTypeReferences() { - return necessaryTypeReferences ? mapDefined(arrayFrom(necessaryTypeReferences.keys()), getFileReferenceForSpecifierModeTuple) : []; - } + function getLibReferences() { + return map(arrayFrom(libs.keys()), lib => ({ fileName: lib, pos: -1, end: -1 })); + } - function getFileReferenceForSpecifierModeTuple([typeName, mode]: [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined]): FileReference | undefined { - // Elide type references for which we have imports - if (emittedImports) { - for (const importStatement of emittedImports) { - if (isImportEqualsDeclaration(importStatement) && isExternalModuleReference(importStatement.moduleReference)) { - const expr = importStatement.moduleReference.expression; - if (isStringLiteralLike(expr) && expr.text === typeName) { - return undefined; - } - } - else if (isImportDeclaration(importStatement) && isStringLiteral(importStatement.moduleSpecifier) && importStatement.moduleSpecifier.text === typeName) { + function getFileReferencesForUsedTypeReferences() { + return necessaryTypeReferences ? mapDefined(arrayFrom(necessaryTypeReferences.keys()), getFileReferenceForSpecifierModeTuple) : []; + } + + function getFileReferenceForSpecifierModeTuple([typeName, mode]: [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined]): FileReference | undefined { + // Elide type references for which we have imports + if (emittedImports) { + for (const importStatement of emittedImports) { + if (isImportEqualsDeclaration(importStatement) && isExternalModuleReference(importStatement.moduleReference)) { + const expr = importStatement.moduleReference.expression; + if (isStringLiteralLike(expr) && expr.text === typeName) { return undefined; } } + else if (isImportDeclaration(importStatement) && isStringLiteral(importStatement.moduleSpecifier) && importStatement.moduleSpecifier.text === typeName) { + return undefined; + } } - return { fileName: typeName, pos: -1, end: -1, ...(mode ? { resolutionMode: mode } : undefined) }; } + return { fileName: typeName, pos: -1, end: -1, ...(mode ? { resolutionMode: mode } : undefined) }; + } - function mapReferencesIntoArray(references: FileReference[], outputFilePath: string): (file: SourceFile) => void { - return file => { - let declFileName: string; - if (file.isDeclarationFile) { // Neither decl files or js should have their refs changed - declFileName = file.fileName; - } - else { - if (isBundledEmit && contains((node as Bundle).sourceFiles, file)) return; // Omit references to files which are being merged - const paths = getOutputPathsFor(file, host, /*forceDtsPaths*/ true); - declFileName = paths.declarationFilePath || paths.jsFilePath || file.fileName; - } - - if (declFileName) { - const specifier = moduleSpecifiers.getModuleSpecifier( - options, - currentSourceFile, - toPath(outputFilePath, host.getCurrentDirectory(), host.getCanonicalFileName), - toPath(declFileName, host.getCurrentDirectory(), host.getCanonicalFileName), - host, - ); - if (!pathIsRelative(specifier)) { - // If some compiler option/symlink/whatever allows access to the file containing the ambient module declaration - // via a non-relative name, emit a type reference directive to that non-relative name, rather than - // a relative path to the declaration file - recordTypeReferenceDirectivesIfNecessary([[specifier, /*mode*/ undefined]]); - return; - } + function mapReferencesIntoArray(references: FileReference[], outputFilePath: string): (file: SourceFile) => void { + return file => { + let declFileName: string; + if (file.isDeclarationFile) { // Neither decl files or js should have their refs changed + declFileName = file.fileName; + } + else { + if (isBundledEmit && contains((node as Bundle).sourceFiles, file)) return; // Omit references to files which are being merged + const paths = getOutputPathsFor(file, host, /*forceDtsPaths*/ true); + declFileName = paths.declarationFilePath || paths.jsFilePath || file.fileName; + } - let fileName = getRelativePathToDirectoryOrUrl( - outputFilePath, - declFileName, - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ false - ); - if (startsWith(fileName, "./") && hasExtension(fileName)) { - fileName = fileName.substring(2); - } + if (declFileName) { + const specifier = moduleSpecifiers.getModuleSpecifier( + options, + currentSourceFile, + toPath(outputFilePath, host.getCurrentDirectory(), host.getCanonicalFileName), + toPath(declFileName, host.getCurrentDirectory(), host.getCanonicalFileName), + host, + ); + if (!pathIsRelative(specifier)) { + // If some compiler option/symlink/whatever allows access to the file containing the ambient module declaration + // via a non-relative name, emit a type reference directive to that non-relative name, rather than + // a relative path to the declaration file + recordTypeReferenceDirectivesIfNecessary([[specifier, /*mode*/ undefined]]); + return; + } - // omit references to files from node_modules (npm may disambiguate module - // references when installing this package, making the path is unreliable). - if (startsWith(fileName, "node_modules/") || pathContainsNodeModules(fileName)) { - return; - } + let fileName = getRelativePathToDirectoryOrUrl( + outputFilePath, + declFileName, + host.getCurrentDirectory(), + host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ false + ); + if (startsWith(fileName, "./") && hasExtension(fileName)) { + fileName = fileName.substring(2); + } - references.push({ pos: -1, end: -1, fileName }); + // omit references to files from node_modules (npm may disambiguate module + // references when installing this package, making the path is unreliable). + if (startsWith(fileName, "node_modules/") || pathContainsNodeModules(fileName)) { + return; } - }; - } - } - function collectReferences(sourceFile: SourceFile | UnparsedSource, ret: ESMap) { - if (noResolve || (!isUnparsedSource(sourceFile) && isSourceFileJS(sourceFile))) return ret; - forEach(sourceFile.referencedFiles, f => { - const elem = host.getSourceFileFromReference(sourceFile, f); - if (elem) { - ret.set(getOriginalNodeId(elem), elem); + references.push({ pos: -1, end: -1, fileName }); } - }); - return ret; + }; } + } - function collectLibs(sourceFile: SourceFile | UnparsedSource, ret: ESMap) { - forEach(sourceFile.libReferenceDirectives, ref => { - const lib = host.getLibFileFromReference(ref); - if (lib) { - ret.set(toFileNameLowerCase(ref.fileName), true); - } - }); - return ret; - } + function collectReferences(sourceFile: SourceFile | UnparsedSource, ret: ESMap) { + if (noResolve || (!isUnparsedSource(sourceFile) && isSourceFileJS(sourceFile))) return ret; + forEach(sourceFile.referencedFiles, f => { + const elem = host.getSourceFileFromReference(sourceFile, f); + if (elem) { + ret.set(getOriginalNodeId(elem), elem); + } + }); + return ret; + } + + function collectLibs(sourceFile: SourceFile | UnparsedSource, ret: ESMap) { + forEach(sourceFile.libReferenceDirectives, ref => { + const lib = host.getLibFileFromReference(ref); + if (lib) { + ret.set(toFileNameLowerCase(ref.fileName), true); + } + }); + return ret; + } - function filterBindingPatternInitializersAndRenamings(name: BindingName) { - if (name.kind === SyntaxKind.Identifier) { - return name; + function filterBindingPatternInitializersAndRenamings(name: BindingName) { + if (name.kind === SyntaxKind.Identifier) { + return name; + } + else { + if (name.kind === SyntaxKind.ArrayBindingPattern) { + return factory.updateArrayBindingPattern(name, visitNodes(name.elements, visitBindingElement)); } else { - if (name.kind === SyntaxKind.ArrayBindingPattern) { - return factory.updateArrayBindingPattern(name, visitNodes(name.elements, visitBindingElement)); - } - else { - return factory.updateObjectBindingPattern(name, visitNodes(name.elements, visitBindingElement)); - } + return factory.updateObjectBindingPattern(name, visitNodes(name.elements, visitBindingElement)); } + } - function visitBindingElement(elem: T): T; - function visitBindingElement(elem: ArrayBindingElement): ArrayBindingElement { - if (elem.kind === SyntaxKind.OmittedExpression) { - return elem; - } - if (elem.propertyName && isIdentifier(elem.propertyName) && isIdentifier(elem.name) && !elem.symbol.isReferenced) { - // Unnecessary property renaming is forbidden in types, so remove renaming - return factory.updateBindingElement( - elem, - elem.dotDotDotToken, - /* propertyName */ undefined, - elem.propertyName, - shouldPrintWithInitializer(elem) ? elem.initializer : undefined - ); - } + function visitBindingElement(elem: T): T; + function visitBindingElement(elem: ArrayBindingElement): ArrayBindingElement { + if (elem.kind === SyntaxKind.OmittedExpression) { + return elem; + } + if (elem.propertyName && isIdentifier(elem.propertyName) && isIdentifier(elem.name) && !elem.symbol.isReferenced) { + // Unnecessary property renaming is forbidden in types, so remove renaming return factory.updateBindingElement( elem, elem.dotDotDotToken, + /* propertyName */ undefined, elem.propertyName, - filterBindingPatternInitializersAndRenamings(elem.name), shouldPrintWithInitializer(elem) ? elem.initializer : undefined ); } + return factory.updateBindingElement( + elem, + elem.dotDotDotToken, + elem.propertyName, + filterBindingPatternInitializersAndRenamings(elem.name), + shouldPrintWithInitializer(elem) ? elem.initializer : undefined + ); } + } - function ensureParameter(p: ParameterDeclaration, modifierMask?: ModifierFlags, type?: TypeNode): ParameterDeclaration { - let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; - if (!suppressNewDiagnosticContexts) { - oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p); - } - const newParam = factory.updateParameterDeclaration( - p, - maskModifiers(p, modifierMask), - p.dotDotDotToken, - filterBindingPatternInitializersAndRenamings(p.name), - resolver.isOptionalParameter(p) ? (p.questionToken || factory.createToken(SyntaxKind.QuestionToken)) : undefined, - ensureType(p, type || p.type, /*ignorePrivate*/ true), // Ignore private param props, since this type is going straight back into a param - ensureNoInitializer(p) - ); - if (!suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag!; - } - return newParam; + function ensureParameter(p: ParameterDeclaration, modifierMask?: ModifierFlags, type?: TypeNode): ParameterDeclaration { + let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; + if (!suppressNewDiagnosticContexts) { + oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p); + } + const newParam = factory.updateParameterDeclaration( + p, + maskModifiers(p, modifierMask), + p.dotDotDotToken, + filterBindingPatternInitializersAndRenamings(p.name), + resolver.isOptionalParameter(p) ? (p.questionToken || factory.createToken(SyntaxKind.QuestionToken)) : undefined, + ensureType(p, type || p.type, /*ignorePrivate*/ true), // Ignore private param props, since this type is going straight back into a param + ensureNoInitializer(p) + ); + if (!suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = oldDiag!; } + return newParam; + } + + function shouldPrintWithInitializer(node: Node) { + return canHaveLiteralInitializer(node) && resolver.isLiteralConstDeclaration(getParseTreeNode(node) as CanHaveLiteralInitializer); // TODO: Make safe + } - function shouldPrintWithInitializer(node: Node) { - return canHaveLiteralInitializer(node) && resolver.isLiteralConstDeclaration(getParseTreeNode(node) as CanHaveLiteralInitializer); // TODO: Make safe + function ensureNoInitializer(node: CanHaveLiteralInitializer) { + if (shouldPrintWithInitializer(node)) { + return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe } + return undefined; + } - function ensureNoInitializer(node: CanHaveLiteralInitializer) { - if (shouldPrintWithInitializer(node)) { - return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe - } - return undefined; - } - - type HasInferredType = - | FunctionDeclaration - | MethodDeclaration - | GetAccessorDeclaration - | SetAccessorDeclaration - | BindingElement - | ConstructSignatureDeclaration - | VariableDeclaration - | MethodSignature - | CallSignatureDeclaration - | ParameterDeclaration - | PropertyDeclaration - | PropertySignature; - - function ensureType(node: HasInferredType, type: TypeNode | undefined, ignorePrivate?: boolean): TypeNode | undefined { - if (!ignorePrivate && hasEffectiveModifier(node, ModifierFlags.Private)) { - // Private nodes emit no types (except private parameter properties, whose parameter types are actually visible) - return; - } - if (shouldPrintWithInitializer(node)) { - // Literal const declarations will have an initializer ensured rather than a type - return; - } - const shouldUseResolverType = node.kind === SyntaxKind.Parameter && - (resolver.isRequiredInitializedParameter(node) || - resolver.isOptionalUninitializedParameterProperty(node)); - if (type && !shouldUseResolverType) { - return visitNode(type, visitDeclarationSubtree); - } - if (!getParseTreeNode(node)) { - return type ? visitNode(type, visitDeclarationSubtree) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - if (node.kind === SyntaxKind.SetAccessor) { - // Set accessors with no associated type node (from it's param or get accessor return) are `any` since they are never contextually typed right now - // (The inferred type here will be void, but the old declaration emitter printed `any`, so this replicates that) - return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - errorNameNode = node.name; - let oldDiag: typeof getSymbolAccessibilityDiagnostic; - if (!suppressNewDiagnosticContexts) { - oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node); - } - if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { - return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); - } - if (node.kind === SyntaxKind.Parameter - || node.kind === SyntaxKind.PropertyDeclaration - || node.kind === SyntaxKind.PropertySignature) { - if (isPropertySignature(node) || !node.initializer) return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType)); - return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType) || resolver.createTypeOfExpression(node.initializer, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); - } - return cleanup(resolver.createReturnTypeOfSignatureDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); + type HasInferredType = + | FunctionDeclaration + | MethodDeclaration + | GetAccessorDeclaration + | SetAccessorDeclaration + | BindingElement + | ConstructSignatureDeclaration + | VariableDeclaration + | MethodSignature + | CallSignatureDeclaration + | ParameterDeclaration + | PropertyDeclaration + | PropertySignature; - function cleanup(returnValue: TypeNode | undefined) { - errorNameNode = undefined; - if (!suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag; - } - return returnValue || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); - } + function ensureType(node: HasInferredType, type: TypeNode | undefined, ignorePrivate?: boolean): TypeNode | undefined { + if (!ignorePrivate && hasEffectiveModifier(node, ModifierFlags.Private)) { + // Private nodes emit no types (except private parameter properties, whose parameter types are actually visible) + return; + } + if (shouldPrintWithInitializer(node)) { + // Literal const declarations will have an initializer ensured rather than a type + return; + } + const shouldUseResolverType = node.kind === SyntaxKind.Parameter && + (resolver.isRequiredInitializedParameter(node) || + resolver.isOptionalUninitializedParameterProperty(node)); + if (type && !shouldUseResolverType) { + return visitNode(type, visitDeclarationSubtree); } + if (!getParseTreeNode(node)) { + return type ? visitNode(type, visitDeclarationSubtree) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + if (node.kind === SyntaxKind.SetAccessor) { + // Set accessors with no associated type node (from it's param or get accessor return) are `any` since they are never contextually typed right now + // (The inferred type here will be void, but the old declaration emitter printed `any`, so this replicates that) + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + errorNameNode = node.name; + let oldDiag: typeof getSymbolAccessibilityDiagnostic; + if (!suppressNewDiagnosticContexts) { + oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(node); + } + if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { + return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); + } + if (node.kind === SyntaxKind.Parameter + || node.kind === SyntaxKind.PropertyDeclaration + || node.kind === SyntaxKind.PropertySignature) { + if (isPropertySignature(node) || !node.initializer) return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType)); + return cleanup(resolver.createTypeOfDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker, shouldUseResolverType) || resolver.createTypeOfExpression(node.initializer, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); + } + return cleanup(resolver.createReturnTypeOfSignatureDeclaration(node, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker)); - function isDeclarationAndNotVisible(node: NamedDeclaration) { - node = getParseTreeNode(node) as NamedDeclaration; - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.EnumDeclaration: - return !resolver.isDeclarationVisible(node); - // The following should be doing their own visibility checks based on filtering their members - case SyntaxKind.VariableDeclaration: - return !getBindingNameVisible(node as VariableDeclaration); - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExportAssignment: - return false; - case SyntaxKind.ClassStaticBlockDeclaration: - return true; + function cleanup(returnValue: TypeNode | undefined) { + errorNameNode = undefined; + if (!suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = oldDiag; } - return false; + return returnValue || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } + } - // If the ExpandoFunctionDeclaration have multiple overloads, then we only need to emit properties for the last one. - function shouldEmitFunctionProperties(input: FunctionDeclaration) { - if (input.body) { + function isDeclarationAndNotVisible(node: NamedDeclaration) { + node = getParseTreeNode(node) as NamedDeclaration; + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + return !resolver.isDeclarationVisible(node); + // The following should be doing their own visibility checks based on filtering their members + case SyntaxKind.VariableDeclaration: + return !getBindingNameVisible(node as VariableDeclaration); + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExportAssignment: + return false; + case SyntaxKind.ClassStaticBlockDeclaration: return true; - } + } + return false; + } - const overloadSignatures = input.symbol.declarations?.filter(decl => isFunctionDeclaration(decl) && !decl.body); - return !overloadSignatures || overloadSignatures.indexOf(input) === overloadSignatures.length - 1; + // If the ExpandoFunctionDeclaration have multiple overloads, then we only need to emit properties for the last one. + function shouldEmitFunctionProperties(input: FunctionDeclaration) { + if (input.body) { + return true; } - function getBindingNameVisible(elem: BindingElement | VariableDeclaration | OmittedExpression): boolean { - if (isOmittedExpression(elem)) { - return false; + const overloadSignatures = input.symbol.declarations?.filter(decl => isFunctionDeclaration(decl) && !decl.body); + return !overloadSignatures || overloadSignatures.indexOf(input) === overloadSignatures.length - 1; + } + + function getBindingNameVisible(elem: BindingElement | VariableDeclaration | OmittedExpression): boolean { + if (isOmittedExpression(elem)) { + return false; + } + if (isBindingPattern(elem.name)) { + // If any child binding pattern element has been marked visible (usually by collect linked aliases), then this is visible + return some(elem.name.elements, getBindingNameVisible); + } + else { + return resolver.isDeclarationVisible(elem); + } + } + + function updateParamsList(node: Node, params: NodeArray, modifierMask?: ModifierFlags) { + if (hasEffectiveModifier(node, ModifierFlags.Private)) { + return undefined!; // TODO: GH#18217 + } + const newParams = map(params, p => ensureParameter(p, modifierMask)); + if (!newParams) { + return undefined!; // TODO: GH#18217 + } + return factory.createNodeArray(newParams, params.hasTrailingComma); + } + + function updateAccessorParamsList(input: AccessorDeclaration, isPrivate: boolean) { + let newParams: ParameterDeclaration[] | undefined; + if (!isPrivate) { + const thisParameter = getThisParameter(input); + if (thisParameter) { + newParams = [ensureParameter(thisParameter)]; } - if (isBindingPattern(elem.name)) { - // If any child binding pattern element has been marked visible (usually by collect linked aliases), then this is visible - return some(elem.name.elements, getBindingNameVisible); + } + if (isSetAccessorDeclaration(input)) { + let newValueParameter: ParameterDeclaration | undefined; + if (!isPrivate) { + const valueParameter = getSetAccessorValueParameter(input); + if (valueParameter) { + const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); + newValueParameter = ensureParameter(valueParameter, /*modifierMask*/ undefined, accessorType); + } } - else { - return resolver.isDeclarationVisible(elem); + if (!newValueParameter) { + newValueParameter = factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + "value" + ); } + newParams = append(newParams, newValueParameter); } + return factory.createNodeArray(newParams || emptyArray); + } - function updateParamsList(node: Node, params: NodeArray, modifierMask?: ModifierFlags) { - if (hasEffectiveModifier(node, ModifierFlags.Private)) { - return undefined!; // TODO: GH#18217 - } - const newParams = map(params, p => ensureParameter(p, modifierMask)); - if (!newParams) { - return undefined!; // TODO: GH#18217 - } - return factory.createNodeArray(newParams, params.hasTrailingComma); + function ensureTypeParams(node: Node, params: NodeArray | undefined) { + return hasEffectiveModifier(node, ModifierFlags.Private) ? undefined : visitNodes(params, visitDeclarationSubtree); + } + + function isEnclosingDeclaration(node: Node) { + return isSourceFile(node) + || isTypeAliasDeclaration(node) + || isModuleDeclaration(node) + || isClassDeclaration(node) + || isInterfaceDeclaration(node) + || isFunctionLike(node) + || isIndexSignatureDeclaration(node) + || isMappedTypeNode(node); + } + + function checkEntityNameVisibility(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node) { + const visibilityResult = resolver.isEntityNameVisible(entityName, enclosingDeclaration); + handleSymbolAccessibilityError(visibilityResult); + recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForEntityName(entityName)); + } + + function preserveJsDoc(updated: T, original: Node): T { + if (hasJSDocNodes(updated) && hasJSDocNodes(original)) { + updated.jsDoc = original.jsDoc; } + return setCommentRange(updated, getCommentRange(original)); + } - function updateAccessorParamsList(input: AccessorDeclaration, isPrivate: boolean) { - let newParams: ParameterDeclaration[] | undefined; - if (!isPrivate) { - const thisParameter = getThisParameter(input); - if (thisParameter) { - newParams = [ensureParameter(thisParameter)]; + function rewriteModuleSpecifier(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode, input: T | undefined): T | StringLiteral { + if (!input) return undefined!; // TODO: GH#18217 + resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || (parent.kind !== SyntaxKind.ModuleDeclaration && parent.kind !== SyntaxKind.ImportType); + if (isStringLiteralLike(input)) { + if (isBundledEmit) { + const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent); + if (newName) { + return factory.createStringLiteral(newName); } } - if (isSetAccessorDeclaration(input)) { - let newValueParameter: ParameterDeclaration | undefined; - if (!isPrivate) { - const valueParameter = getSetAccessorValueParameter(input); - if (valueParameter) { - const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); - newValueParameter = ensureParameter(valueParameter, /*modifierMask*/ undefined, accessorType); - } - } - if (!newValueParameter) { - newValueParameter = factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - "value" - ); + else { + const symbol = resolver.getSymbolOfExternalModuleSpecifier(input); + if (symbol) { + (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); } - newParams = append(newParams, newValueParameter); } - return factory.createNodeArray(newParams || emptyArray); } + return input; + } - function ensureTypeParams(node: Node, params: NodeArray | undefined) { - return hasEffectiveModifier(node, ModifierFlags.Private) ? undefined : visitNodes(params, visitDeclarationSubtree); + function transformImportEqualsDeclaration(decl: ImportEqualsDeclaration) { + if (!resolver.isDeclarationVisible(decl)) return; + if (decl.moduleReference.kind === SyntaxKind.ExternalModuleReference) { + // Rewrite external module names if necessary + const specifier = getExternalModuleImportEqualsDeclarationExpression(decl); + return factory.updateImportEqualsDeclaration( + decl, + decl.modifiers, + decl.isTypeOnly, + decl.name, + factory.updateExternalModuleReference(decl.moduleReference, rewriteModuleSpecifier(decl, specifier)) + ); } - - function isEnclosingDeclaration(node: Node) { - return isSourceFile(node) - || isTypeAliasDeclaration(node) - || isModuleDeclaration(node) - || isClassDeclaration(node) - || isInterfaceDeclaration(node) - || isFunctionLike(node) - || isIndexSignatureDeclaration(node) - || isMappedTypeNode(node); + else { + const oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(decl); + checkEntityNameVisibility(decl.moduleReference, enclosingDeclaration); + getSymbolAccessibilityDiagnostic = oldDiag; + return decl; } + } - function checkEntityNameVisibility(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node) { - const visibilityResult = resolver.isEntityNameVisible(entityName, enclosingDeclaration); - handleSymbolAccessibilityError(visibilityResult); - recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForEntityName(entityName)); + function transformImportDeclaration(decl: ImportDeclaration) { + if (!decl.importClause) { + // import "mod" - possibly needed for side effects? (global interface patches, module augmentations, etc) + return factory.updateImportDeclaration( + decl, + decl.modifiers, + decl.importClause, + rewriteModuleSpecifier(decl, decl.moduleSpecifier), + getResolutionModeOverrideForClauseInNightly(decl.assertClause) + ); + } + // The `importClause` visibility corresponds to the default's visibility. + const visibleDefaultBinding = decl.importClause && decl.importClause.name && resolver.isDeclarationVisible(decl.importClause) ? decl.importClause.name : undefined; + if (!decl.importClause.namedBindings) { + // No named bindings (either namespace or list), meaning the import is just default or should be elided + return visibleDefaultBinding && factory.updateImportDeclaration(decl, decl.modifiers, factory.updateImportClause( + decl.importClause, + decl.importClause.isTypeOnly, + visibleDefaultBinding, + /*namedBindings*/ undefined, + ), rewriteModuleSpecifier(decl, decl.moduleSpecifier), getResolutionModeOverrideForClauseInNightly(decl.assertClause)); + } + if (decl.importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + // Namespace import (optionally with visible default) + const namedBindings = resolver.isDeclarationVisible(decl.importClause.namedBindings) ? decl.importClause.namedBindings : /*namedBindings*/ undefined; + return visibleDefaultBinding || namedBindings ? factory.updateImportDeclaration(decl, decl.modifiers, factory.updateImportClause( + decl.importClause, + decl.importClause.isTypeOnly, + visibleDefaultBinding, + namedBindings, + ), rewriteModuleSpecifier(decl, decl.moduleSpecifier), getResolutionModeOverrideForClauseInNightly(decl.assertClause)) : undefined; + } + // Named imports (optionally with visible default) + const bindingList = mapDefined(decl.importClause.namedBindings.elements, b => resolver.isDeclarationVisible(b) ? b : undefined); + if ((bindingList && bindingList.length) || visibleDefaultBinding) { + return factory.updateImportDeclaration( + decl, + decl.modifiers, + factory.updateImportClause( + decl.importClause, + decl.importClause.isTypeOnly, + visibleDefaultBinding, + bindingList && bindingList.length ? factory.updateNamedImports(decl.importClause.namedBindings, bindingList) : undefined, + ), + rewriteModuleSpecifier(decl, decl.moduleSpecifier), + getResolutionModeOverrideForClauseInNightly(decl.assertClause) + ); } + // Augmentation of export depends on import + if (resolver.isImportRequiredByAugmentation(decl)) { + return factory.updateImportDeclaration( + decl, + decl.modifiers, + /*importClause*/ undefined, + rewriteModuleSpecifier(decl, decl.moduleSpecifier), + getResolutionModeOverrideForClauseInNightly(decl.assertClause) + ); + } + // Nothing visible + } - function preserveJsDoc(updated: T, original: Node): T { - if (hasJSDocNodes(updated) && hasJSDocNodes(original)) { - updated.jsDoc = original.jsDoc; + function getResolutionModeOverrideForClauseInNightly(assertClause: AssertClause | undefined) { + const mode = getResolutionModeOverrideForClause(assertClause); + if (mode !== undefined) { + if (!isNightly()) { + context.addDiagnostic(createDiagnosticForNode(assertClause!, Diagnostics.resolution_mode_assertions_are_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next)); } - return setCommentRange(updated, getCommentRange(original)); + return assertClause; + } + return undefined; + } + + function transformAndReplaceLatePaintedStatements(statements: NodeArray): NodeArray { + // This is a `while` loop because `handleSymbolAccessibilityError` can see additional import aliases marked as visible during + // error handling which must now be included in the output and themselves checked for errors. + // For example: + // ``` + // module A { + // export module Q {} + // import B = Q; + // import C = B; + // export import D = C; + // } + // ``` + // In such a scenario, only Q and D are initially visible, but we don't consider imports as private names - instead we say they if they are referenced they must + // be recorded. So while checking D's visibility we mark C as visible, then we must check C which in turn marks B, completing the chain of + // dependent imports and allowing a valid declaration file output. Today, this dependent alias marking only happens for internal import aliases. + while (length(lateMarkedStatements)) { + const i = lateMarkedStatements!.shift()!; + if (!isLateVisibilityPaintedStatement(i)) { + return Debug.fail(`Late replaced statement was found which is not handled by the declaration transformer!: ${Debug.formatSyntaxKind((i as Node).kind)}`); + } + const priorNeedsDeclare = needsDeclare; + needsDeclare = i.parent && isSourceFile(i.parent) && !(isExternalModule(i.parent) && isBundledEmit); + const result = transformTopLevelDeclaration(i); + needsDeclare = priorNeedsDeclare; + lateStatementReplacementMap.set(getOriginalNodeId(i), result); } - function rewriteModuleSpecifier(parent: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode, input: T | undefined): T | StringLiteral { - if (!input) return undefined!; // TODO: GH#18217 - resultHasExternalModuleIndicator = resultHasExternalModuleIndicator || (parent.kind !== SyntaxKind.ModuleDeclaration && parent.kind !== SyntaxKind.ImportType); - if (isStringLiteralLike(input)) { - if (isBundledEmit) { - const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent); - if (newName) { - return factory.createStringLiteral(newName); - } - } - else { - const symbol = resolver.getSymbolOfExternalModuleSpecifier(input); - if (symbol) { - (exportedModulesFromDeclarationEmit || (exportedModulesFromDeclarationEmit = [])).push(symbol); + // And lastly, we need to get the final form of all those indetermine import declarations from before and add them to the output list + // (and remove them from the set to examine for outter declarations) + return visitNodes(statements, visitLateVisibilityMarkedStatements); + + function visitLateVisibilityMarkedStatements(statement: Statement) { + if (isLateVisibilityPaintedStatement(statement)) { + const key = getOriginalNodeId(statement); + if (lateStatementReplacementMap.has(key)) { + const result = lateStatementReplacementMap.get(key); + lateStatementReplacementMap.delete(key); + 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 (isSourceFile(statement.parent) && (isArray(result) ? some(result, isExternalModuleIndicator) : isExternalModuleIndicator(result))) { + resultHasExternalModuleIndicator = true; + } } + return result; } } - return input; + return statement; } + } - function transformImportEqualsDeclaration(decl: ImportEqualsDeclaration) { - if (!resolver.isDeclarationVisible(decl)) return; - if (decl.moduleReference.kind === SyntaxKind.ExternalModuleReference) { - // Rewrite external module names if necessary - const specifier = getExternalModuleImportEqualsDeclarationExpression(decl); - return factory.updateImportEqualsDeclaration( - decl, - decl.modifiers, - decl.isTypeOnly, - decl.name, - factory.updateExternalModuleReference(decl.moduleReference, rewriteModuleSpecifier(decl, specifier)) - ); - } - else { - const oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(decl); - checkEntityNameVisibility(decl.moduleReference, enclosingDeclaration); - getSymbolAccessibilityDiagnostic = oldDiag; - return decl; + function visitDeclarationSubtree(input: Node): VisitResult { + if (shouldStripInternal(input)) return; + if (isDeclaration(input)) { + if (isDeclarationAndNotVisible(input)) return; + if (hasDynamicName(input) && !resolver.isLateBound(getParseTreeNode(input) as Declaration)) { + return; } } - function transformImportDeclaration(decl: ImportDeclaration) { - if (!decl.importClause) { - // import "mod" - possibly needed for side effects? (global interface patches, module augmentations, etc) - return factory.updateImportDeclaration( - decl, - decl.modifiers, - decl.importClause, - rewriteModuleSpecifier(decl, decl.moduleSpecifier), - getResolutionModeOverrideForClauseInNightly(decl.assertClause) - ); - } - // The `importClause` visibility corresponds to the default's visibility. - const visibleDefaultBinding = decl.importClause && decl.importClause.name && resolver.isDeclarationVisible(decl.importClause) ? decl.importClause.name : undefined; - if (!decl.importClause.namedBindings) { - // No named bindings (either namespace or list), meaning the import is just default or should be elided - return visibleDefaultBinding && factory.updateImportDeclaration(decl, decl.modifiers, factory.updateImportClause( - decl.importClause, - decl.importClause.isTypeOnly, - visibleDefaultBinding, - /*namedBindings*/ undefined, - ), rewriteModuleSpecifier(decl, decl.moduleSpecifier), getResolutionModeOverrideForClauseInNightly(decl.assertClause)); - } - if (decl.importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - // Namespace import (optionally with visible default) - const namedBindings = resolver.isDeclarationVisible(decl.importClause.namedBindings) ? decl.importClause.namedBindings : /*namedBindings*/ undefined; - return visibleDefaultBinding || namedBindings ? factory.updateImportDeclaration(decl, decl.modifiers, factory.updateImportClause( - decl.importClause, - decl.importClause.isTypeOnly, - visibleDefaultBinding, - namedBindings, - ), rewriteModuleSpecifier(decl, decl.moduleSpecifier), getResolutionModeOverrideForClauseInNightly(decl.assertClause)) : undefined; - } - // Named imports (optionally with visible default) - const bindingList = mapDefined(decl.importClause.namedBindings.elements, b => resolver.isDeclarationVisible(b) ? b : undefined); - if ((bindingList && bindingList.length) || visibleDefaultBinding) { - return factory.updateImportDeclaration( - decl, - decl.modifiers, - factory.updateImportClause( - decl.importClause, - decl.importClause.isTypeOnly, - visibleDefaultBinding, - bindingList && bindingList.length ? factory.updateNamedImports(decl.importClause.namedBindings, bindingList) : undefined, - ), - rewriteModuleSpecifier(decl, decl.moduleSpecifier), - getResolutionModeOverrideForClauseInNightly(decl.assertClause) - ); - } - // Augmentation of export depends on import - if (resolver.isImportRequiredByAugmentation(decl)) { - return factory.updateImportDeclaration( - decl, - decl.modifiers, - /*importClause*/ undefined, - rewriteModuleSpecifier(decl, decl.moduleSpecifier), - getResolutionModeOverrideForClauseInNightly(decl.assertClause) - ); - } - // Nothing visible - } + // Elide implementation signatures from overload sets + if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) return; - function getResolutionModeOverrideForClauseInNightly(assertClause: AssertClause | undefined) { - const mode = getResolutionModeOverrideForClause(assertClause); - if (mode !== undefined) { - if (!isNightly()) { - context.addDiagnostic(createDiagnosticForNode(assertClause!, Diagnostics.resolution_mode_assertions_are_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next)); - } - return assertClause; - } - return undefined; - } - - function transformAndReplaceLatePaintedStatements(statements: NodeArray): NodeArray { - // This is a `while` loop because `handleSymbolAccessibilityError` can see additional import aliases marked as visible during - // error handling which must now be included in the output and themselves checked for errors. - // For example: - // ``` - // module A { - // export module Q {} - // import B = Q; - // import C = B; - // export import D = C; - // } - // ``` - // In such a scenario, only Q and D are initially visible, but we don't consider imports as private names - instead we say they if they are referenced they must - // be recorded. So while checking D's visibility we mark C as visible, then we must check C which in turn marks B, completing the chain of - // dependent imports and allowing a valid declaration file output. Today, this dependent alias marking only happens for internal import aliases. - while (length(lateMarkedStatements)) { - const i = lateMarkedStatements!.shift()!; - if (!isLateVisibilityPaintedStatement(i)) { - return Debug.fail(`Late replaced statement was found which is not handled by the declaration transformer!: ${Debug.formatSyntaxKind((i as Node).kind)}`); - } - const priorNeedsDeclare = needsDeclare; - needsDeclare = i.parent && isSourceFile(i.parent) && !(isExternalModule(i.parent) && isBundledEmit); - const result = transformTopLevelDeclaration(i); - needsDeclare = priorNeedsDeclare; - lateStatementReplacementMap.set(getOriginalNodeId(i), result); - } + // Elide semicolon class statements + if (isSemicolonClassElement(input)) return; - // And lastly, we need to get the final form of all those indetermine import declarations from before and add them to the output list - // (and remove them from the set to examine for outter declarations) - return visitNodes(statements, visitLateVisibilityMarkedStatements); - - function visitLateVisibilityMarkedStatements(statement: Statement) { - if (isLateVisibilityPaintedStatement(statement)) { - const key = getOriginalNodeId(statement); - if (lateStatementReplacementMap.has(key)) { - const result = lateStatementReplacementMap.get(key); - lateStatementReplacementMap.delete(key); - 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 (isSourceFile(statement.parent) && (isArray(result) ? some(result, isExternalModuleIndicator) : isExternalModuleIndicator(result))) { - resultHasExternalModuleIndicator = true; - } - } - return result; - } - } - return statement; - } + let previousEnclosingDeclaration: typeof enclosingDeclaration; + if (isEnclosingDeclaration(input)) { + previousEnclosingDeclaration = enclosingDeclaration; + enclosingDeclaration = input as Declaration; } + const oldDiag = getSymbolAccessibilityDiagnostic; - function visitDeclarationSubtree(input: Node): VisitResult { - if (shouldStripInternal(input)) return; - if (isDeclaration(input)) { - if (isDeclarationAndNotVisible(input)) return; - if (hasDynamicName(input) && !resolver.isLateBound(getParseTreeNode(input) as Declaration)) { - return; - } - } - - // Elide implementation signatures from overload sets - if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) return; - - // Elide semicolon class statements - if (isSemicolonClassElement(input)) return; - - let previousEnclosingDeclaration: typeof enclosingDeclaration; - if (isEnclosingDeclaration(input)) { - previousEnclosingDeclaration = enclosingDeclaration; - enclosingDeclaration = input as Declaration; - } - const oldDiag = getSymbolAccessibilityDiagnostic; + // Setup diagnostic-related flags before first potential `cleanup` call, otherwise + // We'd see a TDZ violation at runtime + const canProduceDiagnostic = canProduceDiagnostics(input); + const oldWithinObjectLiteralType = suppressNewDiagnosticContexts; + let shouldEnterSuppressNewDiagnosticsContextContext = ((input.kind === SyntaxKind.TypeLiteral || input.kind === SyntaxKind.MappedType) && input.parent.kind !== SyntaxKind.TypeAliasDeclaration); - // Setup diagnostic-related flags before first potential `cleanup` call, otherwise - // We'd see a TDZ violation at runtime - const canProduceDiagnostic = canProduceDiagnostics(input); - const oldWithinObjectLiteralType = suppressNewDiagnosticContexts; - let shouldEnterSuppressNewDiagnosticsContextContext = ((input.kind === SyntaxKind.TypeLiteral || input.kind === SyntaxKind.MappedType) && input.parent.kind !== SyntaxKind.TypeAliasDeclaration); - - // Emit methods which are private as properties with no type information - if (isMethodDeclaration(input) || isMethodSignature(input)) { - if (hasEffectiveModifier(input, ModifierFlags.Private)) { - if (input.symbol && input.symbol.declarations && input.symbol.declarations[0] !== input) return; // Elide all but the first overload - return cleanup(factory.createPropertyDeclaration(ensureModifiers(input), input.name, /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)); - } + // Emit methods which are private as properties with no type information + if (isMethodDeclaration(input) || isMethodSignature(input)) { + if (hasEffectiveModifier(input, ModifierFlags.Private)) { + if (input.symbol && input.symbol.declarations && input.symbol.declarations[0] !== input) return; // Elide all but the first overload + return cleanup(factory.createPropertyDeclaration(ensureModifiers(input), input.name, /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)); } + } - if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input); - } + if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input); + } - if (isTypeQueryNode(input)) { - checkEntityNameVisibility(input.exprName, enclosingDeclaration); - } + if (isTypeQueryNode(input)) { + checkEntityNameVisibility(input.exprName, enclosingDeclaration); + } - if (shouldEnterSuppressNewDiagnosticsContextContext) { - // We stop making new diagnostic contexts within object literal types. Unless it's an object type on the RHS of a type alias declaration. Then we do. - suppressNewDiagnosticContexts = true; - } + if (shouldEnterSuppressNewDiagnosticsContextContext) { + // We stop making new diagnostic contexts within object literal types. Unless it's an object type on the RHS of a type alias declaration. Then we do. + suppressNewDiagnosticContexts = true; + } - if (isProcessedComponent(input)) { - switch (input.kind) { - case SyntaxKind.ExpressionWithTypeArguments: { - if ((isEntityName(input.expression) || isEntityNameExpression(input.expression))) { - checkEntityNameVisibility(input.expression, enclosingDeclaration); - } - const node = visitEachChild(input, visitDeclarationSubtree, context); - return cleanup(factory.updateExpressionWithTypeArguments(node, node.expression, node.typeArguments)); - } - case SyntaxKind.TypeReference: { - checkEntityNameVisibility(input.typeName, enclosingDeclaration); - const node = visitEachChild(input, visitDeclarationSubtree, context); - return cleanup(factory.updateTypeReferenceNode(node, node.typeName, node.typeArguments)); - } - case SyntaxKind.ConstructSignature: - return cleanup(factory.updateConstructSignature( - input, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type) - )); - case SyntaxKind.Constructor: { - // A constructor declaration may not have a type annotation - const ctor = factory.createConstructorDeclaration( - /*modifiers*/ ensureModifiers(input), - updateParamsList(input, input.parameters, ModifierFlags.None), - /*body*/ undefined - ); - return cleanup(ctor); - } - case SyntaxKind.MethodDeclaration: { - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - const sig = factory.createMethodDeclaration( - ensureModifiers(input), - /*asteriskToken*/ undefined, - input.name, - input.questionToken, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type), - /*body*/ undefined - ); - return cleanup(sig); - } - case SyntaxKind.GetAccessor: { - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); - return cleanup(factory.updateGetAccessorDeclaration( - input, - ensureModifiers(input), - input.name, - updateAccessorParamsList(input, hasEffectiveModifier(input, ModifierFlags.Private)), - ensureType(input, accessorType), - /*body*/ undefined)); - } - case SyntaxKind.SetAccessor: { - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - return cleanup(factory.updateSetAccessorDeclaration( - input, - ensureModifiers(input), - input.name, - updateAccessorParamsList(input, hasEffectiveModifier(input, ModifierFlags.Private)), - /*body*/ undefined)); - } - case SyntaxKind.PropertyDeclaration: - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - return cleanup(factory.updatePropertyDeclaration( - input, - ensureModifiers(input), - input.name, - input.questionToken, - ensureType(input, input.type), - ensureNoInitializer(input) - )); - case SyntaxKind.PropertySignature: - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - return cleanup(factory.updatePropertySignature( - input, - ensureModifiers(input), - input.name, - input.questionToken, - ensureType(input, input.type) - )); - case SyntaxKind.MethodSignature: { - if (isPrivateIdentifier(input.name)) { - return cleanup(/*returnValue*/ undefined); - } - return cleanup(factory.updateMethodSignature( - input, - ensureModifiers(input), - input.name, - input.questionToken, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type) - )); - } - case SyntaxKind.CallSignature: { - return cleanup(factory.updateCallSignature( - input, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type) - )); + if (isProcessedComponent(input)) { + switch (input.kind) { + case SyntaxKind.ExpressionWithTypeArguments: { + if ((isEntityName(input.expression) || isEntityNameExpression(input.expression))) { + checkEntityNameVisibility(input.expression, enclosingDeclaration); } - case SyntaxKind.IndexSignature: { - return cleanup(factory.updateIndexSignature( - input, - ensureModifiers(input), - updateParamsList(input, input.parameters), - visitNode(input.type, visitDeclarationSubtree) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - )); + const node = visitEachChild(input, visitDeclarationSubtree, context); + return cleanup(factory.updateExpressionWithTypeArguments(node, node.expression, node.typeArguments)); + } + case SyntaxKind.TypeReference: { + checkEntityNameVisibility(input.typeName, enclosingDeclaration); + const node = visitEachChild(input, visitDeclarationSubtree, context); + return cleanup(factory.updateTypeReferenceNode(node, node.typeName, node.typeArguments)); + } + case SyntaxKind.ConstructSignature: + return cleanup(factory.updateConstructSignature( + input, + ensureTypeParams(input, input.typeParameters), + updateParamsList(input, input.parameters), + ensureType(input, input.type) + )); + case SyntaxKind.Constructor: { + // A constructor declaration may not have a type annotation + const ctor = factory.createConstructorDeclaration( + /*modifiers*/ ensureModifiers(input), + updateParamsList(input, input.parameters, ModifierFlags.None), + /*body*/ undefined + ); + return cleanup(ctor); + } + case SyntaxKind.MethodDeclaration: { + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.VariableDeclaration: { - if (isBindingPattern(input.name)) { - return recreateBindingPattern(input.name); - } - shouldEnterSuppressNewDiagnosticsContextContext = true; - suppressNewDiagnosticContexts = true; // Variable declaration types also suppress new diagnostic contexts, provided the contexts wouldn't be made for binding pattern types - return cleanup(factory.updateVariableDeclaration(input, input.name, /*exclamationToken*/ undefined, ensureType(input, input.type), ensureNoInitializer(input))); + const sig = factory.createMethodDeclaration( + ensureModifiers(input), + /*asteriskToken*/ undefined, + input.name, + input.questionToken, + ensureTypeParams(input, input.typeParameters), + updateParamsList(input, input.parameters), + ensureType(input, input.type), + /*body*/ undefined + ); + return cleanup(sig); + } + case SyntaxKind.GetAccessor: { + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.TypeParameter: { - if (isPrivateMethodTypeParameter(input) && (input.default || input.constraint)) { - return cleanup(factory.updateTypeParameterDeclaration(input, input.modifiers, input.name, /*constraint*/ undefined, /*defaultType*/ undefined)); - } - return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); + const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); + return cleanup(factory.updateGetAccessorDeclaration( + input, + ensureModifiers(input), + input.name, + updateAccessorParamsList(input, hasEffectiveModifier(input, ModifierFlags.Private)), + ensureType(input, accessorType), + /*body*/ undefined)); + } + case SyntaxKind.SetAccessor: { + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.ConditionalType: { - // We have to process conditional types in a special way because for visibility purposes we need to push a new enclosingDeclaration - // just for the `infer` types in the true branch. It's an implicit declaration scope that only applies to _part_ of the type. - const checkType = visitNode(input.checkType, visitDeclarationSubtree); - const extendsType = visitNode(input.extendsType, visitDeclarationSubtree); - const oldEnclosingDecl = enclosingDeclaration; - enclosingDeclaration = input.trueType; - const trueType = visitNode(input.trueType, visitDeclarationSubtree); - enclosingDeclaration = oldEnclosingDecl; - const falseType = visitNode(input.falseType, visitDeclarationSubtree); - return cleanup(factory.updateConditionalTypeNode(input, checkType, extendsType, trueType, falseType)); + return cleanup(factory.updateSetAccessorDeclaration( + input, + ensureModifiers(input), + input.name, + updateAccessorParamsList(input, hasEffectiveModifier(input, ModifierFlags.Private)), + /*body*/ undefined)); + } + case SyntaxKind.PropertyDeclaration: + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.FunctionType: { - return cleanup(factory.updateFunctionTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); + return cleanup(factory.updatePropertyDeclaration( + input, + ensureModifiers(input), + input.name, + input.questionToken, + ensureType(input, input.type), + ensureNoInitializer(input) + )); + case SyntaxKind.PropertySignature: + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.ConstructorType: { - return cleanup(factory.updateConstructorTypeNode(input, ensureModifiers(input), visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); + return cleanup(factory.updatePropertySignature( + input, + ensureModifiers(input), + input.name, + input.questionToken, + ensureType(input, input.type) + )); + case SyntaxKind.MethodSignature: { + if (isPrivateIdentifier(input.name)) { + return cleanup(/*returnValue*/ undefined); } - case SyntaxKind.ImportType: { - if (!isLiteralImportTypeNode(input)) return cleanup(input); - return cleanup(factory.updateImportTypeNode( - input, - factory.updateLiteralTypeNode(input.argument, rewriteModuleSpecifier(input, input.argument.literal)), - input.assertions, - input.qualifier, - visitNodes(input.typeArguments, visitDeclarationSubtree, isTypeNode), - input.isTypeOf - )); + return cleanup(factory.updateMethodSignature( + input, + ensureModifiers(input), + input.name, + input.questionToken, + ensureTypeParams(input, input.typeParameters), + updateParamsList(input, input.parameters), + ensureType(input, input.type) + )); + } + case SyntaxKind.CallSignature: { + return cleanup(factory.updateCallSignature( + input, + ensureTypeParams(input, input.typeParameters), + updateParamsList(input, input.parameters), + ensureType(input, input.type) + )); + } + case SyntaxKind.IndexSignature: { + return cleanup(factory.updateIndexSignature( + input, + ensureModifiers(input), + updateParamsList(input, input.parameters), + visitNode(input.type, visitDeclarationSubtree) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + )); + } + case SyntaxKind.VariableDeclaration: { + if (isBindingPattern(input.name)) { + return recreateBindingPattern(input.name); } - default: Debug.assertNever(input, `Attempted to process unhandled node kind: ${Debug.formatSyntaxKind((input as Node).kind)}`); + shouldEnterSuppressNewDiagnosticsContextContext = true; + suppressNewDiagnosticContexts = true; // Variable declaration types also suppress new diagnostic contexts, provided the contexts wouldn't be made for binding pattern types + return cleanup(factory.updateVariableDeclaration(input, input.name, /*exclamationToken*/ undefined, ensureType(input, input.type), ensureNoInitializer(input))); } - } - - if (isTupleTypeNode(input) && (getLineAndCharacterOfPosition(currentSourceFile, input.pos).line === getLineAndCharacterOfPosition(currentSourceFile, input.end).line)) { - setEmitFlags(input, EmitFlags.SingleLine); - } - - return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); - - function cleanup(returnValue: T | undefined): T | undefined { - if (returnValue && canProduceDiagnostic && hasDynamicName(input as Declaration)) { - checkName(input); + case SyntaxKind.TypeParameter: { + if (isPrivateMethodTypeParameter(input) && (input.default || input.constraint)) { + return cleanup(factory.updateTypeParameterDeclaration(input, input.modifiers, input.name, /*constraint*/ undefined, /*defaultType*/ undefined)); + } + return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); } - if (isEnclosingDeclaration(input)) { - enclosingDeclaration = previousEnclosingDeclaration; + case SyntaxKind.ConditionalType: { + // We have to process conditional types in a special way because for visibility purposes we need to push a new enclosingDeclaration + // just for the `infer` types in the true branch. It's an implicit declaration scope that only applies to _part_ of the type. + const checkType = visitNode(input.checkType, visitDeclarationSubtree); + const extendsType = visitNode(input.extendsType, visitDeclarationSubtree); + const oldEnclosingDecl = enclosingDeclaration; + enclosingDeclaration = input.trueType; + const trueType = visitNode(input.trueType, visitDeclarationSubtree); + enclosingDeclaration = oldEnclosingDecl; + const falseType = visitNode(input.falseType, visitDeclarationSubtree); + return cleanup(factory.updateConditionalTypeNode(input, checkType, extendsType, trueType, falseType)); } - if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag; + case SyntaxKind.FunctionType: { + return cleanup(factory.updateFunctionTypeNode(input, visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); } - if (shouldEnterSuppressNewDiagnosticsContextContext) { - suppressNewDiagnosticContexts = oldWithinObjectLiteralType; + case SyntaxKind.ConstructorType: { + return cleanup(factory.updateConstructorTypeNode(input, ensureModifiers(input), visitNodes(input.typeParameters, visitDeclarationSubtree), updateParamsList(input, input.parameters), visitNode(input.type, visitDeclarationSubtree))); } - if (returnValue === input) { - return returnValue; + case SyntaxKind.ImportType: { + if (!isLiteralImportTypeNode(input)) return cleanup(input); + return cleanup(factory.updateImportTypeNode( + input, + factory.updateLiteralTypeNode(input.argument, rewriteModuleSpecifier(input, input.argument.literal)), + input.assertions, + input.qualifier, + visitNodes(input.typeArguments, visitDeclarationSubtree, isTypeNode), + input.isTypeOf + )); } - return returnValue && setOriginalNode(preserveJsDoc(returnValue, input), input); + default: Debug.assertNever(input, `Attempted to process unhandled node kind: ${Debug.formatSyntaxKind((input as Node).kind)}`); } } - function isPrivateMethodTypeParameter(node: TypeParameterDeclaration) { - return node.parent.kind === SyntaxKind.MethodDeclaration && hasEffectiveModifier(node.parent, ModifierFlags.Private); + if (isTupleTypeNode(input) && (getLineAndCharacterOfPosition(currentSourceFile, input.pos).line === getLineAndCharacterOfPosition(currentSourceFile, input.end).line)) { + setEmitFlags(input, EmitFlags.SingleLine); } - function visitDeclarationStatements(input: Node): VisitResult { - if (!isPreservedDeclarationStatement(input)) { - // return undefined for unmatched kinds to omit them from the tree - return; - } - if (shouldStripInternal(input)) return; + return cleanup(visitEachChild(input, visitDeclarationSubtree, context)); - switch (input.kind) { - case SyntaxKind.ExportDeclaration: { - if (isSourceFile(input.parent)) { - resultHasExternalModuleIndicator = true; - } - resultHasScopeMarker = true; - // Always visible if the parent node isn't dropped for being not visible - // Rewrite external module names if necessary - return factory.updateExportDeclaration( - input, - input.modifiers, - input.isTypeOnly, - input.exportClause, - rewriteModuleSpecifier(input, input.moduleSpecifier), - getResolutionModeOverrideForClause(input.assertClause) ? input.assertClause : undefined - ); - } - case SyntaxKind.ExportAssignment: { - // Always visible if the parent node isn't dropped for being not visible - if (isSourceFile(input.parent)) { - resultHasExternalModuleIndicator = true; - } - resultHasScopeMarker = true; - if (input.expression.kind === SyntaxKind.Identifier) { - return input; - } - else { - const newId = factory.createUniqueName("_default", GeneratedIdentifierFlags.Optimistic); - getSymbolAccessibilityDiagnostic = () => ({ - diagnosticMessage: Diagnostics.Default_export_of_the_module_has_or_is_using_private_name_0, - errorNode: input - }); - errorFallbackNode = input; - const varDecl = factory.createVariableDeclaration(newId, /*exclamationToken*/ undefined, resolver.createTypeOfExpression(input.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); - errorFallbackNode = undefined; - const statement = factory.createVariableStatement(needsDeclare ? [factory.createModifier(SyntaxKind.DeclareKeyword)] : [], factory.createVariableDeclarationList([varDecl], NodeFlags.Const)); - - preserveJsDoc(statement, input); - removeAllComments(input); - return [statement, factory.updateExportAssignment(input, input.modifiers, newId)]; - } - } + function cleanup(returnValue: T | undefined): T | undefined { + if (returnValue && canProduceDiagnostic && hasDynamicName(input as Declaration)) { + checkName(input); } - - const result = transformTopLevelDeclaration(input); - // Don't actually transform yet; just leave as original node - will be elided/swapped by late pass - lateStatementReplacementMap.set(getOriginalNodeId(input), result); - return input; + if (isEnclosingDeclaration(input)) { + enclosingDeclaration = previousEnclosingDeclaration; + } + if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = oldDiag; + } + if (shouldEnterSuppressNewDiagnosticsContextContext) { + suppressNewDiagnosticContexts = oldWithinObjectLiteralType; + } + if (returnValue === input) { + return returnValue; + } + return returnValue && setOriginalNode(preserveJsDoc(returnValue, input), input); } + } - function stripExportModifiers(statement: Statement): Statement { - if (isImportEqualsDeclaration(statement) || hasEffectiveModifier(statement, ModifierFlags.Default) || !canHaveModifiers(statement)) { - // `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; - } + function isPrivateMethodTypeParameter(node: TypeParameterDeclaration) { + return node.parent.kind === SyntaxKind.MethodDeclaration && hasEffectiveModifier(node.parent, ModifierFlags.Private); + } - const modifiers = factory.createModifiersFromModifierFlags(getEffectiveModifierFlags(statement) & (ModifierFlags.All ^ ModifierFlags.Export)); - return factory.updateModifiers(statement, modifiers); + function visitDeclarationStatements(input: Node): VisitResult { + if (!isPreservedDeclarationStatement(input)) { + // return undefined for unmatched kinds to omit them from the tree + return; } + if (shouldStripInternal(input)) return; - function transformTopLevelDeclaration(input: LateVisibilityPaintedStatement) { - if (lateMarkedStatements) { - while (orderedRemoveItem(lateMarkedStatements, input)); + switch (input.kind) { + case SyntaxKind.ExportDeclaration: { + if (isSourceFile(input.parent)) { + resultHasExternalModuleIndicator = true; + } + resultHasScopeMarker = true; + // Always visible if the parent node isn't dropped for being not visible + // Rewrite external module names if necessary + return factory.updateExportDeclaration( + input, + input.modifiers, + input.isTypeOnly, + input.exportClause, + rewriteModuleSpecifier(input, input.moduleSpecifier), + getResolutionModeOverrideForClause(input.assertClause) ? input.assertClause : undefined + ); } - if (shouldStripInternal(input)) return; - switch (input.kind) { - case SyntaxKind.ImportEqualsDeclaration: { - return transformImportEqualsDeclaration(input); + case SyntaxKind.ExportAssignment: { + // Always visible if the parent node isn't dropped for being not visible + if (isSourceFile(input.parent)) { + resultHasExternalModuleIndicator = true; + } + resultHasScopeMarker = true; + if (input.expression.kind === SyntaxKind.Identifier) { + return input; } - case SyntaxKind.ImportDeclaration: { - return transformImportDeclaration(input); + else { + const newId = factory.createUniqueName("_default", GeneratedIdentifierFlags.Optimistic); + getSymbolAccessibilityDiagnostic = () => ({ + diagnosticMessage: Diagnostics.Default_export_of_the_module_has_or_is_using_private_name_0, + errorNode: input + }); + errorFallbackNode = input; + const varDecl = factory.createVariableDeclaration(newId, /*exclamationToken*/ undefined, resolver.createTypeOfExpression(input.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); + errorFallbackNode = undefined; + const statement = factory.createVariableStatement(needsDeclare ? [factory.createModifier(SyntaxKind.DeclareKeyword)] : [], factory.createVariableDeclarationList([varDecl], NodeFlags.Const)); + + preserveJsDoc(statement, input); + removeAllComments(input); + return [statement, factory.updateExportAssignment(input, input.modifiers, newId)]; } } - if (isDeclaration(input) && isDeclarationAndNotVisible(input)) return; + } - // Elide implementation signatures from overload sets - if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) return; + const result = transformTopLevelDeclaration(input); + // Don't actually transform yet; just leave as original node - will be elided/swapped by late pass + lateStatementReplacementMap.set(getOriginalNodeId(input), result); + return input; + } - let previousEnclosingDeclaration: typeof enclosingDeclaration; - if (isEnclosingDeclaration(input)) { - previousEnclosingDeclaration = enclosingDeclaration; - enclosingDeclaration = input as Declaration; - } + function stripExportModifiers(statement: Statement): Statement { + if (isImportEqualsDeclaration(statement) || hasEffectiveModifier(statement, ModifierFlags.Default) || !canHaveModifiers(statement)) { + // `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 canProdiceDiagnostic = canProduceDiagnostics(input); - const oldDiag = getSymbolAccessibilityDiagnostic; - if (canProdiceDiagnostic) { - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input as DeclarationDiagnosticProducing); + const modifiers = factory.createModifiersFromModifierFlags(getEffectiveModifierFlags(statement) & (ModifierFlags.All ^ ModifierFlags.Export)); + return factory.updateModifiers(statement, modifiers); + } + + function transformTopLevelDeclaration(input: LateVisibilityPaintedStatement) { + if (lateMarkedStatements) { + while (orderedRemoveItem(lateMarkedStatements, input)); + } + if (shouldStripInternal(input)) return; + switch (input.kind) { + case SyntaxKind.ImportEqualsDeclaration: { + return transformImportEqualsDeclaration(input); } + case SyntaxKind.ImportDeclaration: { + return transformImportDeclaration(input); + } + } + if (isDeclaration(input) && isDeclarationAndNotVisible(input)) return; - const previousNeedsDeclare = needsDeclare; - switch (input.kind) { - case SyntaxKind.TypeAliasDeclaration: { - needsDeclare = false; - const clean = cleanup(factory.updateTypeAliasDeclaration( - input, - ensureModifiers(input), - input.name, - visitNodes(input.typeParameters, visitDeclarationSubtree, isTypeParameterDeclaration), - visitNode(input.type, visitDeclarationSubtree, isTypeNode) - )); - needsDeclare = previousNeedsDeclare; - return clean; - } - case SyntaxKind.InterfaceDeclaration: { - return cleanup(factory.updateInterfaceDeclaration( - input, - ensureModifiers(input), - input.name, - ensureTypeParams(input, input.typeParameters), - transformHeritageClauses(input.heritageClauses), - visitNodes(input.members, visitDeclarationSubtree) - )); - } - case SyntaxKind.FunctionDeclaration: { - // Generators lose their generator-ness, excepting their return type - const clean = cleanup(factory.updateFunctionDeclaration( - input, - ensureModifiers(input), - /*asteriskToken*/ undefined, - input.name, - ensureTypeParams(input, input.typeParameters), - updateParamsList(input, input.parameters), - ensureType(input, input.type), - /*body*/ undefined - )); - if (clean && resolver.isExpandoFunctionDeclaration(input) && shouldEmitFunctionProperties(input)) { - const props = resolver.getPropertiesOfContainerFunction(input); - // Use parseNodeFactory so it is usable as an enclosing declaration - const fakespace = parseNodeFactory.createModuleDeclaration(/*modifiers*/ undefined, clean.name || factory.createIdentifier("_default"), factory.createModuleBlock([]), NodeFlags.Namespace); - setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration); - fakespace.locals = createSymbolTable(props); - fakespace.symbol = props[0].parent!; - const exportMappings: [Identifier, string][] = []; - let declarations: (VariableStatement | ExportDeclaration)[] = mapDefined(props, p => { - if (!p.valueDeclaration || !isPropertyAccessExpression(p.valueDeclaration)) { - return undefined; // TODO GH#33569: Handle element access expressions that created late bound names (rather than silently omitting them) - } - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.valueDeclaration); - const type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, symbolTracker); - getSymbolAccessibilityDiagnostic = oldDiag; - const nameStr = unescapeLeadingUnderscores(p.escapedName); - const isNonContextualKeywordName = isStringANonContextualKeyword(nameStr); - const name = isNonContextualKeywordName ? factory.getGeneratedNameForNode(p.valueDeclaration) : factory.createIdentifier(nameStr); - if (isNonContextualKeywordName) { - exportMappings.push([name, nameStr]); - } - const varDecl = factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, /*initializer*/ undefined); - return factory.createVariableStatement(isNonContextualKeywordName ? undefined : [factory.createToken(SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([varDecl])); - }); - if (!exportMappings.length) { - declarations = mapDefined(declarations, declaration => factory.updateModifiers(declaration, ModifierFlags.None)); - } - else { - declarations.push(factory.createExportDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - factory.createNamedExports(map(exportMappings, ([gen, exp]) => { - return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, exp); - })) - )); - } - const namespaceDecl = factory.createModuleDeclaration(ensureModifiers(input), input.name!, factory.createModuleBlock(declarations), NodeFlags.Namespace); - if (!hasEffectiveModifier(clean, ModifierFlags.Default)) { - return [clean, namespaceDecl]; - } + // Elide implementation signatures from overload sets + if (isFunctionLike(input) && resolver.isImplementationOfOverload(input)) return; - const modifiers = factory.createModifiersFromModifierFlags((getEffectiveModifierFlags(clean) & ~ModifierFlags.ExportDefault) | ModifierFlags.Ambient); - const cleanDeclaration = factory.updateFunctionDeclaration( - clean, - modifiers, - /*asteriskToken*/ undefined, - clean.name, - clean.typeParameters, - clean.parameters, - clean.type, - /*body*/ undefined - ); - - const namespaceDeclaration = factory.updateModuleDeclaration( - namespaceDecl, - modifiers, - namespaceDecl.name, - namespaceDecl.body - ); - - const exportDefaultDeclaration = factory.createExportAssignment( - /*modifiers*/ undefined, - /*isExportEquals*/ false, - namespaceDecl.name - ); + let previousEnclosingDeclaration: typeof enclosingDeclaration; + if (isEnclosingDeclaration(input)) { + previousEnclosingDeclaration = enclosingDeclaration; + enclosingDeclaration = input as Declaration; + } - if (isSourceFile(input.parent)) { - resultHasExternalModuleIndicator = true; - } - resultHasScopeMarker = true; + const canProdiceDiagnostic = canProduceDiagnostics(input); + const oldDiag = getSymbolAccessibilityDiagnostic; + if (canProdiceDiagnostic) { + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input as DeclarationDiagnosticProducing); + } - return [cleanDeclaration, namespaceDeclaration, exportDefaultDeclaration]; - } - else { - return clean; - } - } - case SyntaxKind.ModuleDeclaration: { - 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); - 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" + const previousNeedsDeclare = needsDeclare; + switch (input.kind) { + case SyntaxKind.TypeAliasDeclaration: { + needsDeclare = false; + const clean = cleanup(factory.updateTypeAliasDeclaration( + input, + ensureModifiers(input), + input.name, + visitNodes(input.typeParameters, visitDeclarationSubtree, isTypeParameterDeclaration), + visitNode(input.type, visitDeclarationSubtree, isTypeNode) + )); + needsDeclare = previousNeedsDeclare; + return clean; + } + case SyntaxKind.InterfaceDeclaration: { + return cleanup(factory.updateInterfaceDeclaration( + input, + ensureModifiers(input), + input.name, + ensureTypeParams(input, input.typeParameters), + transformHeritageClauses(input.heritageClauses), + visitNodes(input.members, visitDeclarationSubtree) + )); + } + case SyntaxKind.FunctionDeclaration: { + // Generators lose their generator-ness, excepting their return type + const clean = cleanup(factory.updateFunctionDeclaration( + input, + ensureModifiers(input), + /*asteriskToken*/ undefined, + input.name, + ensureTypeParams(input, input.typeParameters), + updateParamsList(input, input.parameters), + ensureType(input, input.type), + /*body*/ undefined + )); + if (clean && resolver.isExpandoFunctionDeclaration(input) && shouldEmitFunctionProperties(input)) { + const props = resolver.getPropertiesOfContainerFunction(input); + // Use parseNodeFactory so it is usable as an enclosing declaration + const fakespace = parseNodeFactory.createModuleDeclaration(/*modifiers*/ undefined, clean.name || factory.createIdentifier("_default"), factory.createModuleBlock([]), NodeFlags.Namespace); + setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration); + fakespace.locals = createSymbolTable(props); + fakespace.symbol = props[0].parent!; + const exportMappings: [Identifier, string][] = []; + let declarations: (VariableStatement | ExportDeclaration)[] = mapDefined(props, p => { + if (!p.valueDeclaration || !isPropertyAccessExpression(p.valueDeclaration)) { + return undefined; // TODO GH#33569: Handle element access expressions that created late bound names (rather than silently omitting them) } - // 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 = factory.createNodeArray([...lateStatements, createEmptyExports(factory)]); - } - else { - lateStatements = visitNodes(lateStatements, stripExportModifiers); - } + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.valueDeclaration); + const type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, symbolTracker); + getSymbolAccessibilityDiagnostic = oldDiag; + const nameStr = unescapeLeadingUnderscores(p.escapedName); + const isNonContextualKeywordName = isStringANonContextualKeyword(nameStr); + const name = isNonContextualKeywordName ? factory.getGeneratedNameForNode(p.valueDeclaration) : factory.createIdentifier(nameStr); + if (isNonContextualKeywordName) { + exportMappings.push([name, nameStr]); } - const body = factory.updateModuleBlock(inner, lateStatements); - needsDeclare = previousNeedsDeclare; - needsScopeFixMarker = oldNeedsScopeFix; - resultHasScopeMarker = oldHasScopeFix; - const mods = ensureModifiers(input); - return cleanup(factory.updateModuleDeclaration( - input, - mods, - isExternalModuleAugmentation(input) ? rewriteModuleSpecifier(input, input.name) : input.name, - body - )); + const varDecl = factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, /*initializer*/ undefined); + return factory.createVariableStatement(isNonContextualKeywordName ? undefined : [factory.createToken(SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([varDecl])); + }); + if (!exportMappings.length) { + declarations = mapDefined(declarations, declaration => factory.updateModifiers(declaration, ModifierFlags.None)); } else { - needsDeclare = previousNeedsDeclare; - const mods = ensureModifiers(input); - needsDeclare = false; - visitNode(inner, visitDeclarationStatements); - // eagerly transform nested namespaces (the nesting doesn't need any elision or painting done) - const id = getOriginalNodeId(inner!); // TODO: GH#18217 - const body = lateStatementReplacementMap.get(id); - lateStatementReplacementMap.delete(id); - return cleanup(factory.updateModuleDeclaration( - input, - mods, - input.name, - body as ModuleBody + declarations.push(factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(map(exportMappings, ([gen, exp]) => { + return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, exp); + })) )); } - } - case SyntaxKind.ClassDeclaration: { - errorNameNode = input.name; - errorFallbackNode = input; - const modifiers = factory.createNodeArray(ensureModifiers(input)); - const typeParameters = ensureTypeParams(input, input.typeParameters); - const ctor = getFirstConstructorWithBody(input); - let parameterProperties: readonly PropertyDeclaration[] | undefined; - if (ctor) { - const oldDiag = getSymbolAccessibilityDiagnostic; - parameterProperties = compact(flatMap(ctor.parameters, (param) => { - if (!hasSyntacticModifier(param, ModifierFlags.ParameterPropertyModifier) || shouldStripInternal(param)) return; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(param); - if (param.name.kind === SyntaxKind.Identifier) { - return preserveJsDoc(factory.createPropertyDeclaration( - ensureModifiers(param), - param.name, - param.questionToken, - ensureType(param, param.type), - ensureNoInitializer(param)), param); - } - else { - // Pattern - this is currently an error, but we emit declarations for it somewhat correctly - return walkBindingPattern(param.name); - } - - function walkBindingPattern(pattern: BindingPattern) { - let elems: PropertyDeclaration[] | undefined; - for (const elem of pattern.elements) { - if (isOmittedExpression(elem)) continue; - if (isBindingPattern(elem.name)) { - elems = concatenate(elems, walkBindingPattern(elem.name)); - } - elems = elems || []; - elems.push(factory.createPropertyDeclaration( - ensureModifiers(param), - elem.name as Identifier, - /*questionToken*/ undefined, - ensureType(elem, /*type*/ undefined), - /*initializer*/ undefined - )); - } - return elems; - } - })); - getSymbolAccessibilityDiagnostic = oldDiag; + const namespaceDecl = factory.createModuleDeclaration(ensureModifiers(input), input.name!, factory.createModuleBlock(declarations), NodeFlags.Namespace); + if (!hasEffectiveModifier(clean, ModifierFlags.Default)) { + return [clean, namespaceDecl]; } - const hasPrivateIdentifier = some(input.members, member => !!member.name && isPrivateIdentifier(member.name)); - // When the class has at least one private identifier, create a unique constant identifier to retain the nominal typing behavior - // Prevents other classes with the same public members from being used in place of the current class - const privateIdentifier = hasPrivateIdentifier ? [ - factory.createPropertyDeclaration( - /*modifiers*/ undefined, - factory.createPrivateIdentifier("#private"), - /*questionToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - ) - ] : undefined; - const memberNodes = concatenate(concatenate(privateIdentifier, parameterProperties), visitNodes(input.members, visitDeclarationSubtree)); - const members = factory.createNodeArray(memberNodes); - - const extendsClause = getEffectiveBaseTypeNode(input); - if (extendsClause && !isEntityNameExpression(extendsClause.expression) && extendsClause.expression.kind !== SyntaxKind.NullKeyword) { - // We must add a temporary declaration for the extends clause expression - - const oldId = input.name ? unescapeLeadingUnderscores(input.name.escapedText) : "default"; - const newId = factory.createUniqueName(`${oldId}_base`, GeneratedIdentifierFlags.Optimistic); - getSymbolAccessibilityDiagnostic = () => ({ - diagnosticMessage: Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1, - errorNode: extendsClause, - typeName: input.name - }); - const varDecl = factory.createVariableDeclaration(newId, /*exclamationToken*/ undefined, resolver.createTypeOfExpression(extendsClause.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); - const statement = factory.createVariableStatement(needsDeclare ? [factory.createModifier(SyntaxKind.DeclareKeyword)] : [], factory.createVariableDeclarationList([varDecl], NodeFlags.Const)); - const heritageClauses = factory.createNodeArray(map(input.heritageClauses, clause => { - if (clause.token === SyntaxKind.ExtendsKeyword) { - const oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(clause.types[0]); - const newClause = factory.updateHeritageClause(clause, map(clause.types, t => factory.updateExpressionWithTypeArguments(t, newId, visitNodes(t.typeArguments, visitDeclarationSubtree)))); - getSymbolAccessibilityDiagnostic = oldDiag; - return newClause; - } - return factory.updateHeritageClause(clause, visitNodes(factory.createNodeArray(filter(clause.types, t => isEntityNameExpression(t.expression) || t.expression.kind === SyntaxKind.NullKeyword)), visitDeclarationSubtree)); - })); - return [statement, cleanup(factory.updateClassDeclaration( - input, - modifiers, - input.name, - typeParameters, - heritageClauses, - members - ))!]; // TODO: GH#18217 - } - else { - const heritageClauses = transformHeritageClauses(input.heritageClauses); - return cleanup(factory.updateClassDeclaration( - input, - modifiers, - input.name, - typeParameters, - heritageClauses, - members - )); + const modifiers = factory.createModifiersFromModifierFlags((getEffectiveModifierFlags(clean) & ~ModifierFlags.ExportDefault) | ModifierFlags.Ambient); + const cleanDeclaration = factory.updateFunctionDeclaration( + clean, + modifiers, + /*asteriskToken*/ undefined, + clean.name, + clean.typeParameters, + clean.parameters, + clean.type, + /*body*/ undefined + ); + + const namespaceDeclaration = factory.updateModuleDeclaration( + namespaceDecl, + modifiers, + namespaceDecl.name, + namespaceDecl.body + ); + + const exportDefaultDeclaration = factory.createExportAssignment( + /*modifiers*/ undefined, + /*isExportEquals*/ false, + namespaceDecl.name + ); + + if (isSourceFile(input.parent)) { + resultHasExternalModuleIndicator = true; } - } - case SyntaxKind.VariableStatement: { - return cleanup(transformVariableStatement(input)); - } - case SyntaxKind.EnumDeclaration: { - return cleanup(factory.updateEnumDeclaration(input, factory.createNodeArray(ensureModifiers(input)), input.name, factory.createNodeArray(mapDefined(input.members, m => { - if (shouldStripInternal(m)) return; - // Rewrite enum values to their constants, if available - const constValue = resolver.getConstantValue(m); - return preserveJsDoc(factory.updateEnumMember(m, m.name, constValue !== undefined ? typeof constValue === "string" ? factory.createStringLiteral(constValue) : factory.createNumericLiteral(constValue) : undefined), m); - })))); - } - } - // Anything left unhandled is an error, so this should be unreachable - return Debug.assertNever(input, `Unhandled top-level node in declaration emit: ${Debug.formatSyntaxKind((input as Node).kind)}`); + resultHasScopeMarker = true; - function cleanup(node: T | undefined): T | undefined { - if (isEnclosingDeclaration(input)) { - enclosingDeclaration = previousEnclosingDeclaration; + return [cleanDeclaration, namespaceDeclaration, exportDefaultDeclaration]; } - if (canProdiceDiagnostic) { - getSymbolAccessibilityDiagnostic = oldDiag; + else { + return clean; } - if (input.kind === SyntaxKind.ModuleDeclaration) { + } + case SyntaxKind.ModuleDeclaration: { + 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); + 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 = factory.createNodeArray([...lateStatements, createEmptyExports(factory)]); + } + else { + lateStatements = visitNodes(lateStatements, stripExportModifiers); + } + } + const body = factory.updateModuleBlock(inner, lateStatements); needsDeclare = previousNeedsDeclare; + needsScopeFixMarker = oldNeedsScopeFix; + resultHasScopeMarker = oldHasScopeFix; + const mods = ensureModifiers(input); + return cleanup(factory.updateModuleDeclaration( + input, + mods, + isExternalModuleAugmentation(input) ? rewriteModuleSpecifier(input, input.name) : input.name, + body + )); } - if (node as Node === input) { - return node; + else { + needsDeclare = previousNeedsDeclare; + const mods = ensureModifiers(input); + needsDeclare = false; + visitNode(inner, visitDeclarationStatements); + // eagerly transform nested namespaces (the nesting doesn't need any elision or painting done) + const id = getOriginalNodeId(inner!); // TODO: GH#18217 + const body = lateStatementReplacementMap.get(id); + lateStatementReplacementMap.delete(id); + return cleanup(factory.updateModuleDeclaration( + input, + mods, + input.name, + body as ModuleBody + )); } - errorFallbackNode = undefined; - errorNameNode = undefined; - return node && setOriginalNode(preserveJsDoc(node, input), input); } - } - - function transformVariableStatement(input: VariableStatement) { - if (!forEach(input.declarationList.declarations, getBindingNameVisible)) return; - const nodes = visitNodes(input.declarationList.declarations, visitDeclarationSubtree); - if (!length(nodes)) return; - return factory.updateVariableStatement(input, factory.createNodeArray(ensureModifiers(input)), factory.updateVariableDeclarationList(input.declarationList, nodes)); - } + case SyntaxKind.ClassDeclaration: { + errorNameNode = input.name; + errorFallbackNode = input; + const modifiers = factory.createNodeArray(ensureModifiers(input)); + const typeParameters = ensureTypeParams(input, input.typeParameters); + const ctor = getFirstConstructorWithBody(input); + let parameterProperties: readonly PropertyDeclaration[] | undefined; + if (ctor) { + const oldDiag = getSymbolAccessibilityDiagnostic; + parameterProperties = compact(flatMap(ctor.parameters, (param) => { + if (!hasSyntacticModifier(param, ModifierFlags.ParameterPropertyModifier) || shouldStripInternal(param)) return; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(param); + if (param.name.kind === SyntaxKind.Identifier) { + return preserveJsDoc(factory.createPropertyDeclaration( + ensureModifiers(param), + param.name, + param.questionToken, + ensureType(param, param.type), + ensureNoInitializer(param)), param); + } + else { + // Pattern - this is currently an error, but we emit declarations for it somewhat correctly + return walkBindingPattern(param.name); + } - function recreateBindingPattern(d: BindingPattern): VariableDeclaration[] { - return flatten(mapDefined(d.elements, e => recreateBindingElement(e))); - } + function walkBindingPattern(pattern: BindingPattern) { + let elems: PropertyDeclaration[] | undefined; + for (const elem of pattern.elements) { + if (isOmittedExpression(elem)) continue; + if (isBindingPattern(elem.name)) { + elems = concatenate(elems, walkBindingPattern(elem.name)); + } + elems = elems || []; + elems.push(factory.createPropertyDeclaration( + ensureModifiers(param), + elem.name as Identifier, + /*questionToken*/ undefined, + ensureType(elem, /*type*/ undefined), + /*initializer*/ undefined + )); + } + return elems; + } + })); + getSymbolAccessibilityDiagnostic = oldDiag; + } - function recreateBindingElement(e: ArrayBindingElement) { - if (e.kind === SyntaxKind.OmittedExpression) { - return; - } - if (e.name) { - if (!getBindingNameVisible(e)) return; - if (isBindingPattern(e.name)) { - return recreateBindingPattern(e.name); + const hasPrivateIdentifier = some(input.members, member => !!member.name && isPrivateIdentifier(member.name)); + // When the class has at least one private identifier, create a unique constant identifier to retain the nominal typing behavior + // Prevents other classes with the same public members from being used in place of the current class + const privateIdentifier = hasPrivateIdentifier ? [ + factory.createPropertyDeclaration( + /*modifiers*/ undefined, + factory.createPrivateIdentifier("#private"), + /*questionToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + ) + ] : undefined; + const memberNodes = concatenate(concatenate(privateIdentifier, parameterProperties), visitNodes(input.members, visitDeclarationSubtree)); + const members = factory.createNodeArray(memberNodes); + + const extendsClause = getEffectiveBaseTypeNode(input); + if (extendsClause && !isEntityNameExpression(extendsClause.expression) && extendsClause.expression.kind !== SyntaxKind.NullKeyword) { + // We must add a temporary declaration for the extends clause expression + + const oldId = input.name ? unescapeLeadingUnderscores(input.name.escapedText) : "default"; + const newId = factory.createUniqueName(`${oldId}_base`, GeneratedIdentifierFlags.Optimistic); + getSymbolAccessibilityDiagnostic = () => ({ + diagnosticMessage: Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1, + errorNode: extendsClause, + typeName: input.name + }); + const varDecl = factory.createVariableDeclaration(newId, /*exclamationToken*/ undefined, resolver.createTypeOfExpression(extendsClause.expression, input, declarationEmitNodeBuilderFlags, symbolTracker), /*initializer*/ undefined); + const statement = factory.createVariableStatement(needsDeclare ? [factory.createModifier(SyntaxKind.DeclareKeyword)] : [], factory.createVariableDeclarationList([varDecl], NodeFlags.Const)); + const heritageClauses = factory.createNodeArray(map(input.heritageClauses, clause => { + if (clause.token === SyntaxKind.ExtendsKeyword) { + const oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(clause.types[0]); + const newClause = factory.updateHeritageClause(clause, map(clause.types, t => factory.updateExpressionWithTypeArguments(t, newId, visitNodes(t.typeArguments, visitDeclarationSubtree)))); + getSymbolAccessibilityDiagnostic = oldDiag; + return newClause; + } + return factory.updateHeritageClause(clause, visitNodes(factory.createNodeArray(filter(clause.types, t => isEntityNameExpression(t.expression) || t.expression.kind === SyntaxKind.NullKeyword)), visitDeclarationSubtree)); + })); + return [statement, cleanup(factory.updateClassDeclaration( + input, + modifiers, + input.name, + typeParameters, + heritageClauses, + members + ))!]; // TODO: GH#18217 } else { - return factory.createVariableDeclaration(e.name, /*exclamationToken*/ undefined, ensureType(e, /*type*/ undefined), /*initializer*/ undefined); + const heritageClauses = transformHeritageClauses(input.heritageClauses); + return cleanup(factory.updateClassDeclaration( + input, + modifiers, + input.name, + typeParameters, + heritageClauses, + members + )); } } + case SyntaxKind.VariableStatement: { + return cleanup(transformVariableStatement(input)); + } + case SyntaxKind.EnumDeclaration: { + return cleanup(factory.updateEnumDeclaration(input, factory.createNodeArray(ensureModifiers(input)), input.name, factory.createNodeArray(mapDefined(input.members, m => { + if (shouldStripInternal(m)) return; + // Rewrite enum values to their constants, if available + const constValue = resolver.getConstantValue(m); + return preserveJsDoc(factory.updateEnumMember(m, m.name, constValue !== undefined ? typeof constValue === "string" ? factory.createStringLiteral(constValue) : factory.createNumericLiteral(constValue) : undefined), m); + })))); + } } + // Anything left unhandled is an error, so this should be unreachable + return Debug.assertNever(input, `Unhandled top-level node in declaration emit: ${Debug.formatSyntaxKind((input as Node).kind)}`); - function checkName(node: DeclarationDiagnosticProducing) { - let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; - if (!suppressNewDiagnosticContexts) { - oldDiag = getSymbolAccessibilityDiagnostic; - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNodeName(node); + function cleanup(node: T | undefined): T | undefined { + if (isEnclosingDeclaration(input)) { + enclosingDeclaration = previousEnclosingDeclaration; } - errorNameNode = (node as NamedDeclaration).name; - Debug.assert(resolver.isLateBound(getParseTreeNode(node) as Declaration)); // Should only be called with dynamic names - const decl = node as NamedDeclaration as LateBoundDeclaration; - const entityName = decl.name.expression; - checkEntityNameVisibility(entityName, enclosingDeclaration); - if (!suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = oldDiag!; + if (canProdiceDiagnostic) { + getSymbolAccessibilityDiagnostic = oldDiag; + } + if (input.kind === SyntaxKind.ModuleDeclaration) { + needsDeclare = previousNeedsDeclare; } + if (node as Node === input) { + return node; + } + errorFallbackNode = undefined; errorNameNode = undefined; + return node && setOriginalNode(preserveJsDoc(node, input), input); } + } - function shouldStripInternal(node: Node) { - return !!stripInternal && !!node && isInternalDeclaration(node, currentSourceFile); - } + function transformVariableStatement(input: VariableStatement) { + if (!forEach(input.declarationList.declarations, getBindingNameVisible)) return; + const nodes = visitNodes(input.declarationList.declarations, visitDeclarationSubtree); + if (!length(nodes)) return; + return factory.updateVariableStatement(input, factory.createNodeArray(ensureModifiers(input)), factory.updateVariableDeclarationList(input.declarationList, nodes)); + } - function isScopeMarker(node: Node) { - return isExportAssignment(node) || isExportDeclaration(node); - } + function recreateBindingPattern(d: BindingPattern): VariableDeclaration[] { + return flatten(mapDefined(d.elements, e => recreateBindingElement(e))); + } - function hasScopeMarker(statements: readonly Statement[]) { - return some(statements, isScopeMarker); + function recreateBindingElement(e: ArrayBindingElement) { + if (e.kind === SyntaxKind.OmittedExpression) { + return; } - - function ensureModifiers(node: T): readonly Modifier[] | undefined { - const currentFlags = getEffectiveModifierFlags(node); - const newFlags = ensureModifierFlags(node); - if (currentFlags === newFlags) { - return visitArray(node.modifiers, n => tryCast(n, isModifier), isModifier); + if (e.name) { + if (!getBindingNameVisible(e)) return; + if (isBindingPattern(e.name)) { + return recreateBindingPattern(e.name); } - return factory.createModifiersFromModifierFlags(newFlags); - } - - function ensureModifierFlags(node: Node): ModifierFlags { - let mask = ModifierFlags.All ^ (ModifierFlags.Public | ModifierFlags.Async | ModifierFlags.Override); // No async and override 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 ^= ModifierFlags.Ambient; - additions = ModifierFlags.None; + else { + return factory.createVariableDeclaration(e.name, /*exclamationToken*/ undefined, ensureType(e, /*type*/ undefined), /*initializer*/ undefined); } - return maskModifierFlags(node, mask, additions); } + } - function getTypeAnnotationFromAllAccessorDeclarations(node: AccessorDeclaration, accessors: AllAccessorDeclarations) { - let accessorType = getTypeAnnotationFromAccessor(node); - if (!accessorType && node !== accessors.firstAccessor) { - accessorType = getTypeAnnotationFromAccessor(accessors.firstAccessor); - // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.firstAccessor); - } - if (!accessorType && accessors.secondAccessor && node !== accessors.secondAccessor) { - accessorType = getTypeAnnotationFromAccessor(accessors.secondAccessor); - // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.secondAccessor); - } - return accessorType; + function checkName(node: DeclarationDiagnosticProducing) { + let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined; + if (!suppressNewDiagnosticContexts) { + oldDiag = getSymbolAccessibilityDiagnostic; + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNodeName(node); } - - function transformHeritageClauses(nodes: NodeArray | undefined) { - return factory.createNodeArray(filter(map(nodes, clause => factory.updateHeritageClause(clause, visitNodes(factory.createNodeArray(filter(clause.types, t => { - return isEntityNameExpression(t.expression) || (clause.token === SyntaxKind.ExtendsKeyword && t.expression.kind === SyntaxKind.NullKeyword); - })), visitDeclarationSubtree))), clause => clause.types && !!clause.types.length)); + errorNameNode = (node as NamedDeclaration).name; + Debug.assert(resolver.isLateBound(getParseTreeNode(node) as Declaration)); // Should only be called with dynamic names + const decl = node as NamedDeclaration as LateBoundDeclaration; + const entityName = decl.name.expression; + checkEntityNameVisibility(entityName, enclosingDeclaration); + if (!suppressNewDiagnosticContexts) { + getSymbolAccessibilityDiagnostic = oldDiag!; } + errorNameNode = undefined; } - function isAlwaysType(node: Node) { - if (node.kind === SyntaxKind.InterfaceDeclaration) { - return true; - } - return false; + function shouldStripInternal(node: Node) { + return !!stripInternal && !!node && isInternalDeclaration(node, currentSourceFile); } - // Elide "public" modifier, as it is the default - function maskModifiers(node: Node, modifierMask?: ModifierFlags, modifierAdditions?: ModifierFlags): Modifier[] | undefined { - return factory.createModifiersFromModifierFlags(maskModifierFlags(node, modifierMask, modifierAdditions)); + function isScopeMarker(node: Node) { + return isExportAssignment(node) || isExportDeclaration(node); } - function maskModifierFlags(node: Node, modifierMask: ModifierFlags = ModifierFlags.All ^ ModifierFlags.Public, modifierAdditions: ModifierFlags = ModifierFlags.None): ModifierFlags { - let flags = (getEffectiveModifierFlags(node) & modifierMask) | modifierAdditions; - if (flags & ModifierFlags.Default && !(flags & ModifierFlags.Export)) { - // A non-exported default is a nonsequitor - we usually try to remove all export modifiers - // from statements in ambient declarations; but a default export must retain its export modifier to be syntactically valid - flags ^= ModifierFlags.Export; - } - if (flags & ModifierFlags.Default && flags & ModifierFlags.Ambient) { - flags ^= ModifierFlags.Ambient; // `declare` is never required alongside `default` (and would be an error if printed) - } - return flags; + function hasScopeMarker(statements: readonly Statement[]) { + return some(statements, isScopeMarker); } - function getTypeAnnotationFromAccessor(accessor: AccessorDeclaration): TypeNode | undefined { - if (accessor) { - return accessor.kind === SyntaxKind.GetAccessor - ? accessor.type // Getter - return type - : accessor.parameters.length > 0 - ? accessor.parameters[0].type // Setter parameter type - : undefined; + function ensureModifiers(node: T): readonly Modifier[] | undefined { + const currentFlags = getEffectiveModifierFlags(node); + const newFlags = ensureModifierFlags(node); + if (currentFlags === newFlags) { + return visitArray(node.modifiers, n => tryCast(n, isModifier), isModifier); } + return factory.createModifiersFromModifierFlags(newFlags); } - type CanHaveLiteralInitializer = VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration; - function canHaveLiteralInitializer(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return !hasEffectiveModifier(node, ModifierFlags.Private); - case SyntaxKind.Parameter: - case SyntaxKind.VariableDeclaration: - return true; + function ensureModifierFlags(node: Node): ModifierFlags { + let mask = ModifierFlags.All ^ (ModifierFlags.Public | ModifierFlags.Async | ModifierFlags.Override); // No async and override 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 ^= ModifierFlags.Ambient; + additions = ModifierFlags.None; } - return false; + return maskModifierFlags(node, mask, additions); } - type ProcessedDeclarationStatement = - | FunctionDeclaration - | ModuleDeclaration - | ImportEqualsDeclaration - | InterfaceDeclaration - | ClassDeclaration - | TypeAliasDeclaration - | EnumDeclaration - | VariableStatement - | ImportDeclaration - | ExportDeclaration - | ExportAssignment; - - function isPreservedDeclarationStatement(node: Node): node is ProcessedDeclarationStatement { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.VariableStatement: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExportAssignment: - return true; + function getTypeAnnotationFromAllAccessorDeclarations(node: AccessorDeclaration, accessors: AllAccessorDeclarations) { + let accessorType = getTypeAnnotationFromAccessor(node); + if (!accessorType && node !== accessors.firstAccessor) { + accessorType = getTypeAnnotationFromAccessor(accessors.firstAccessor); + // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.firstAccessor); } - return false; + if (!accessorType && accessors.secondAccessor && node !== accessors.secondAccessor) { + accessorType = getTypeAnnotationFromAccessor(accessors.secondAccessor); + // If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.secondAccessor); + } + return accessorType; } - type ProcessedComponent = - | ConstructSignatureDeclaration - | ConstructorDeclaration - | MethodDeclaration - | GetAccessorDeclaration - | SetAccessorDeclaration - | PropertyDeclaration - | PropertySignature - | MethodSignature - | CallSignatureDeclaration - | IndexSignatureDeclaration - | VariableDeclaration - | TypeParameterDeclaration - | ExpressionWithTypeArguments - | TypeReferenceNode - | ConditionalTypeNode - | FunctionTypeNode - | ConstructorTypeNode - | ImportTypeNode; - - function isProcessedComponent(node: Node): node is ProcessedComponent { - switch (node.kind) { - case SyntaxKind.ConstructSignature: - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.VariableDeclaration: - case SyntaxKind.TypeParameter: - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.TypeReference: - case SyntaxKind.ConditionalType: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.ImportType: - return true; - } - return false; + function transformHeritageClauses(nodes: NodeArray | undefined) { + return factory.createNodeArray(filter(map(nodes, clause => factory.updateHeritageClause(clause, visitNodes(factory.createNodeArray(filter(clause.types, t => { + return isEntityNameExpression(t.expression) || (clause.token === SyntaxKind.ExtendsKeyword && t.expression.kind === SyntaxKind.NullKeyword); + })), visitDeclarationSubtree))), clause => clause.types && !!clause.types.length)); + } +} + +function isAlwaysType(node: Node) { + if (node.kind === SyntaxKind.InterfaceDeclaration) { + return true; + } + return false; +} + +// Elide "public" modifier, as it is the default +function maskModifiers(node: Node, modifierMask?: ModifierFlags, modifierAdditions?: ModifierFlags): Modifier[] | undefined { + return factory.createModifiersFromModifierFlags(maskModifierFlags(node, modifierMask, modifierAdditions)); +} + +function maskModifierFlags(node: Node, modifierMask: ModifierFlags = ModifierFlags.All ^ ModifierFlags.Public, modifierAdditions: ModifierFlags = ModifierFlags.None): ModifierFlags { + let flags = (getEffectiveModifierFlags(node) & modifierMask) | modifierAdditions; + if (flags & ModifierFlags.Default && !(flags & ModifierFlags.Export)) { + // A non-exported default is a nonsequitor - we usually try to remove all export modifiers + // from statements in ambient declarations; but a default export must retain its export modifier to be syntactically valid + flags ^= ModifierFlags.Export; + } + if (flags & ModifierFlags.Default && flags & ModifierFlags.Ambient) { + flags ^= ModifierFlags.Ambient; // `declare` is never required alongside `default` (and would be an error if printed) + } + return flags; +} + +function getTypeAnnotationFromAccessor(accessor: AccessorDeclaration): TypeNode | undefined { + if (accessor) { + return accessor.kind === SyntaxKind.GetAccessor + ? accessor.type // Getter - return type + : accessor.parameters.length > 0 + ? accessor.parameters[0].type // Setter parameter type + : undefined; + } +} + +type CanHaveLiteralInitializer = VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration; +function canHaveLiteralInitializer(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return !hasEffectiveModifier(node, ModifierFlags.Private); + case SyntaxKind.Parameter: + case SyntaxKind.VariableDeclaration: + return true; + } + return false; +} + +type ProcessedDeclarationStatement = + | FunctionDeclaration + | ModuleDeclaration + | ImportEqualsDeclaration + | InterfaceDeclaration + | ClassDeclaration + | TypeAliasDeclaration + | EnumDeclaration + | VariableStatement + | ImportDeclaration + | ExportDeclaration + | ExportAssignment; + +function isPreservedDeclarationStatement(node: Node): node is ProcessedDeclarationStatement { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.VariableStatement: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExportAssignment: + return true; + } + return false; +} + +type ProcessedComponent = + | ConstructSignatureDeclaration + | ConstructorDeclaration + | MethodDeclaration + | GetAccessorDeclaration + | SetAccessorDeclaration + | PropertyDeclaration + | PropertySignature + | MethodSignature + | CallSignatureDeclaration + | IndexSignatureDeclaration + | VariableDeclaration + | TypeParameterDeclaration + | ExpressionWithTypeArguments + | TypeReferenceNode + | ConditionalTypeNode + | FunctionTypeNode + | ConstructorTypeNode + | ImportTypeNode; + +function isProcessedComponent(node: Node): node is ProcessedComponent { + switch (node.kind) { + case SyntaxKind.ConstructSignature: + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.VariableDeclaration: + case SyntaxKind.TypeParameter: + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.TypeReference: + case SyntaxKind.ConditionalType: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.ImportType: + return true; } + return false; } diff --git a/src/compiler/transformers/declarations/diagnostics.ts b/src/compiler/transformers/declarations/diagnostics.ts index 5d3d950cd6fae..bdd9903796ea4 100644 --- a/src/compiler/transformers/declarations/diagnostics.ts +++ b/src/compiler/transformers/declarations/diagnostics.ts @@ -1,80 +1,198 @@ -/* @internal */ -namespace ts { - export type GetSymbolAccessibilityDiagnostic = (symbolAccessibilityResult: SymbolAccessibilityResult) => (SymbolAccessibilityDiagnostic | undefined); - - export interface SymbolAccessibilityDiagnostic { - errorNode: Node; - diagnosticMessage: DiagnosticMessage; - typeName?: DeclarationName | QualifiedName; +import { + BindingElement, CallSignatureDeclaration, ConstructorDeclaration, ConstructSignatureDeclaration, Debug, Declaration, + DeclarationName, DiagnosticMessage, Diagnostics, ExpressionWithTypeArguments, FunctionDeclaration, + GetAccessorDeclaration, getNameOfDeclaration, hasSyntacticModifier, ImportEqualsDeclaration, + IndexSignatureDeclaration, isBindingElement, isCallSignatureDeclaration, isClassDeclaration, + isConstructorDeclaration, isConstructSignatureDeclaration, isExpressionWithTypeArguments, isFunctionDeclaration, + isGetAccessor, isHeritageClause, isImportEqualsDeclaration, isIndexSignatureDeclaration, isJSDocTypeAlias, + isMethodDeclaration, isMethodSignature, isParameter, isParameterPropertyDeclaration, isPropertyAccessExpression, + isPropertyDeclaration, isPropertySignature, isSetAccessor, isStatic, isTypeAliasDeclaration, + isTypeParameterDeclaration, isVariableDeclaration, JSDocCallbackTag, JSDocEnumTag, JSDocTypedefTag, + MethodDeclaration, MethodSignature, ModifierFlags, NamedDeclaration, Node, ParameterDeclaration, + PropertyAccessExpression, PropertyDeclaration, PropertySignature, QualifiedName, SetAccessorDeclaration, + SymbolAccessibility, SymbolAccessibilityResult, SyntaxKind, TypeAliasDeclaration, TypeParameterDeclaration, + VariableDeclaration, +} from "../../_namespaces/ts"; + +/** @internal */ +export type GetSymbolAccessibilityDiagnostic = (symbolAccessibilityResult: SymbolAccessibilityResult) => (SymbolAccessibilityDiagnostic | undefined); + +/** @internal */ +export interface SymbolAccessibilityDiagnostic { + errorNode: Node; + diagnosticMessage: DiagnosticMessage; + typeName?: DeclarationName | QualifiedName; +} + +/** @internal */ +export type DeclarationDiagnosticProducing = + | VariableDeclaration + | PropertyDeclaration + | PropertySignature + | BindingElement + | SetAccessorDeclaration + | GetAccessorDeclaration + | ConstructSignatureDeclaration + | CallSignatureDeclaration + | MethodDeclaration + | MethodSignature + | FunctionDeclaration + | ParameterDeclaration + | TypeParameterDeclaration + | ExpressionWithTypeArguments + | ImportEqualsDeclaration + | TypeAliasDeclaration + | ConstructorDeclaration + | IndexSignatureDeclaration + | PropertyAccessExpression + | JSDocTypedefTag + | JSDocCallbackTag + | JSDocEnumTag; + +/** @internal */ +export function canProduceDiagnostics(node: Node): node is DeclarationDiagnosticProducing { + return isVariableDeclaration(node) || + isPropertyDeclaration(node) || + isPropertySignature(node) || + isBindingElement(node) || + isSetAccessor(node) || + isGetAccessor(node) || + isConstructSignatureDeclaration(node) || + isCallSignatureDeclaration(node) || + isMethodDeclaration(node) || + isMethodSignature(node) || + isFunctionDeclaration(node) || + isParameter(node) || + isTypeParameterDeclaration(node) || + isExpressionWithTypeArguments(node) || + isImportEqualsDeclaration(node) || + isTypeAliasDeclaration(node) || + isConstructorDeclaration(node) || + isIndexSignatureDeclaration(node) || + isPropertyAccessExpression(node) || + isJSDocTypeAlias(node); +} + +/** @internal */ +export function createGetSymbolAccessibilityDiagnosticForNodeName(node: DeclarationDiagnosticProducing) { + if (isSetAccessor(node) || isGetAccessor(node)) { + return getAccessorNameVisibilityError; + } + else if (isMethodSignature(node) || isMethodDeclaration(node)) { + return getMethodNameVisibilityError; + } + else { + return createGetSymbolAccessibilityDiagnosticForNode(node); + } + function getAccessorNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { + const diagnosticMessage = getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; } - export type DeclarationDiagnosticProducing = - | VariableDeclaration - | PropertyDeclaration - | PropertySignature - | BindingElement - | SetAccessorDeclaration - | GetAccessorDeclaration - | ConstructSignatureDeclaration - | CallSignatureDeclaration - | MethodDeclaration - | MethodSignature - | FunctionDeclaration - | ParameterDeclaration - | TypeParameterDeclaration - | ExpressionWithTypeArguments - | ImportEqualsDeclaration - | TypeAliasDeclaration - | ConstructorDeclaration - | IndexSignatureDeclaration - | PropertyAccessExpression - | JSDocTypedefTag - | JSDocCallbackTag - | JSDocEnumTag; - - export function canProduceDiagnostics(node: Node): node is DeclarationDiagnosticProducing { - return isVariableDeclaration(node) || - isPropertyDeclaration(node) || - isPropertySignature(node) || - isBindingElement(node) || - isSetAccessor(node) || - isGetAccessor(node) || - isConstructSignatureDeclaration(node) || - isCallSignatureDeclaration(node) || - isMethodDeclaration(node) || - isMethodSignature(node) || - isFunctionDeclaration(node) || - isParameter(node) || - isTypeParameterDeclaration(node) || - isExpressionWithTypeArguments(node) || - isImportEqualsDeclaration(node) || - isTypeAliasDeclaration(node) || - isConstructorDeclaration(node) || - isIndexSignatureDeclaration(node) || - isPropertyAccessExpression(node) || - isJSDocTypeAlias(node); + function getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (isStatic(node)) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_private_name_1; + } + else if (node.parent.kind === SyntaxKind.ClassDeclaration) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_property_0_of_exported_class_has_or_is_using_private_name_1; + } + else { + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Property_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Property_0_of_exported_interface_has_or_is_using_private_name_1; + } + } + + function getMethodNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { + const diagnosticMessage = getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; } - export function createGetSymbolAccessibilityDiagnosticForNodeName(node: DeclarationDiagnosticProducing) { - if (isSetAccessor(node) || isGetAccessor(node)) { - return getAccessorNameVisibilityError; + function getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (isStatic(node)) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_private_name_1; } - else if (isMethodSignature(node) || isMethodDeclaration(node)) { - return getMethodNameVisibilityError; + else if (node.parent.kind === SyntaxKind.ClassDeclaration) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Public_method_0_of_exported_class_has_or_is_using_private_name_1; } else { - return createGetSymbolAccessibilityDiagnosticForNode(node); + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Method_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Method_0_of_exported_interface_has_or_is_using_private_name_1; } - function getAccessorNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { - const diagnosticMessage = getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; + } +} + +/** @internal */ +export function createGetSymbolAccessibilityDiagnosticForNode(node: DeclarationDiagnosticProducing): GetSymbolAccessibilityDiagnostic { + if (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isPropertyAccessExpression(node) || isBindingElement(node) || isConstructorDeclaration(node)) { + return getVariableDeclarationTypeVisibilityError; + } + else if (isSetAccessor(node) || isGetAccessor(node)) { + return getAccessorDeclarationTypeVisibilityError; + } + else if (isConstructSignatureDeclaration(node) || isCallSignatureDeclaration(node) || isMethodDeclaration(node) || isMethodSignature(node) || isFunctionDeclaration(node) || isIndexSignatureDeclaration(node)) { + return getReturnTypeVisibilityError; + } + else if (isParameter(node)) { + if (isParameterPropertyDeclaration(node, node.parent) && hasSyntacticModifier(node.parent, ModifierFlags.Private)) { + return getVariableDeclarationTypeVisibilityError; } + return getParameterDeclarationTypeVisibilityError; + } + else if (isTypeParameterDeclaration(node)) { + return getTypeParameterConstraintVisibilityError; + } + else if (isExpressionWithTypeArguments(node)) { + return getHeritageClauseVisibilityError; + } + else if (isImportEqualsDeclaration(node)) { + return getImportEntityNameVisibilityError; + } + else if (isTypeAliasDeclaration(node) || isJSDocTypeAlias(node)) { + return getTypeAliasDeclarationVisibilityError; + } + else { + return Debug.assertNever(node, `Attempted to set a declaration diagnostic context for unhandled node kind: ${Debug.formatSyntaxKind((node as Node).kind)}`); + } - function getAccessorNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + function getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Exported_variable_0_has_or_is_using_private_name_1; + } + // This check is to ensure we don't report error on constructor parameter property as that error would be reported during parameter emit + // The only exception here is if the constructor was marked as private. we are not emitting the constructor parameters at all. + else if (node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.PropertySignature || + (node.kind === SyntaxKind.Parameter && hasSyntacticModifier(node.parent, ModifierFlags.Private))) { + // TODO(jfreeman): Deal with computed properties in error reporting. if (isStatic(node)) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? @@ -82,7 +200,7 @@ namespace ts { Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_private_name_1; } - else if (node.parent.kind === SyntaxKind.ClassDeclaration) { + else if (node.parent.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.Parameter) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : @@ -90,402 +208,303 @@ namespace ts { Diagnostics.Public_property_0_of_exported_class_has_or_is_using_private_name_1; } else { + // Interfaces cannot have types that cannot be named return symbolAccessibilityResult.errorModuleName ? Diagnostics.Property_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : Diagnostics.Property_0_of_exported_interface_has_or_is_using_private_name_1; } } + } - function getMethodNameVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { - const diagnosticMessage = getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; - } + function getVariableDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { + const diagnosticMessage = getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; + } - function getMethodNameVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { + function getAccessorDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { + let diagnosticMessage: DiagnosticMessage; + if (node.kind === SyntaxKind.SetAccessor) { + // Getters can infer the return type from the returned expression, but setters cannot, so the + // "_from_external_module_1_but_cannot_be_named" case cannot occur. if (isStatic(node)) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_static_method_0_of_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.kind === SyntaxKind.ClassDeclaration) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_method_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_method_0_of_exported_class_has_or_is_using_private_name_1; + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_private_name_1; } else { - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Method_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Method_0_of_exported_interface_has_or_is_using_private_name_1; + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_private_name_1; } } - } - - export function createGetSymbolAccessibilityDiagnosticForNode(node: DeclarationDiagnosticProducing): GetSymbolAccessibilityDiagnostic { - if (isVariableDeclaration(node) || isPropertyDeclaration(node) || isPropertySignature(node) || isPropertyAccessExpression(node) || isBindingElement(node) || isConstructorDeclaration(node)) { - return getVariableDeclarationTypeVisibilityError; - } - else if (isSetAccessor(node) || isGetAccessor(node)) { - return getAccessorDeclarationTypeVisibilityError; - } - else if (isConstructSignatureDeclaration(node) || isCallSignatureDeclaration(node) || isMethodDeclaration(node) || isMethodSignature(node) || isFunctionDeclaration(node) || isIndexSignatureDeclaration(node)) { - return getReturnTypeVisibilityError; - } - else if (isParameter(node)) { - if (isParameterPropertyDeclaration(node, node.parent) && hasSyntacticModifier(node.parent, ModifierFlags.Private)) { - return getVariableDeclarationTypeVisibilityError; - } - return getParameterDeclarationTypeVisibilityError; - } - else if (isTypeParameterDeclaration(node)) { - return getTypeParameterConstraintVisibilityError; - } - else if (isExpressionWithTypeArguments(node)) { - return getHeritageClauseVisibilityError; - } - else if (isImportEqualsDeclaration(node)) { - return getImportEntityNameVisibilityError; - } - else if (isTypeAliasDeclaration(node) || isJSDocTypeAlias(node)) { - return getTypeAliasDeclarationVisibilityError; - } else { - return Debug.assertNever(node, `Attempted to set a declaration diagnostic context for unhandled node kind: ${Debug.formatSyntaxKind((node as Node).kind)}`); - } - - function getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult) { - if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { - return symbolAccessibilityResult.errorModuleName ? + if (isStatic(node)) { + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Exported_variable_0_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Exported_variable_0_has_or_is_using_private_name_1; + Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_private_name_1; } - // This check is to ensure we don't report error on constructor parameter property as that error would be reported during parameter emit - // The only exception here is if the constructor was marked as private. we are not emitting the constructor parameters at all. - else if (node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.PropertySignature || - (node.kind === SyntaxKind.Parameter && hasSyntacticModifier(node.parent, ModifierFlags.Private))) { - // TODO(jfreeman): Deal with computed properties in error reporting. - if (isStatic(node)) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_static_property_0_of_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.Parameter) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Public_property_0_of_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Public_property_0_of_exported_class_has_or_is_using_private_name_1; - } - else { - // Interfaces cannot have types that cannot be named - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Property_0_of_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Property_0_of_exported_interface_has_or_is_using_private_name_1; - } + else { + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_private_name_1; } } + return { + diagnosticMessage, + errorNode: (node as NamedDeclaration).name!, + typeName: (node as NamedDeclaration).name + }; + } - function getVariableDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { - const diagnosticMessage = getVariableDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; - } - - function getAccessorDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { - let diagnosticMessage: DiagnosticMessage; - if (node.kind === SyntaxKind.SetAccessor) { - // Getters can infer the return type from the returned expression, but setters cannot, so the - // "_from_external_module_1_but_cannot_be_named" case cannot occur. + function getReturnTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { + let diagnosticMessage: DiagnosticMessage; + switch (node.kind) { + case SyntaxKind.ConstructSignature: + // Interfaces cannot have return types that cannot be named + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_0; + break; + + case SyntaxKind.CallSignature: + // Interfaces cannot have return types that cannot be named + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_private_name_0; + break; + + case SyntaxKind.IndexSignature: + // Interfaces cannot have return types that cannot be named + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_private_name_0; + break; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: if (isStatic(node)) { diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_type_of_public_static_setter_0_from_exported_class_has_or_is_using_private_name_1; - } - else { - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_type_of_public_setter_0_from_exported_class_has_or_is_using_private_name_1; + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : + Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_private_name_0; } - } - else { - if (isStatic(node)) { + else if (node.parent.kind === SyntaxKind.ClassDeclaration) { diagnosticMessage = symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Return_type_of_public_static_getter_0_from_exported_class_has_or_is_using_private_name_1; + Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : + Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_private_name_0; } else { + // Interfaces cannot have return types that cannot be named diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Return_type_of_public_getter_0_from_exported_class_has_or_is_using_private_name_1; + Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_private_name_0; } - } - return { - diagnosticMessage, - errorNode: (node as NamedDeclaration).name!, - typeName: (node as NamedDeclaration).name - }; + break; + + case SyntaxKind.FunctionDeclaration: + diagnosticMessage = symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : + Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_private_module_1 : + Diagnostics.Return_type_of_exported_function_has_or_is_using_private_name_0; + break; + + default: + return Debug.fail("This is unknown kind for signature: " + node.kind); } - function getReturnTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { - let diagnosticMessage: DiagnosticMessage; - switch (node.kind) { - case SyntaxKind.ConstructSignature: - // Interfaces cannot have return types that cannot be named - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_0; - break; + return { + diagnosticMessage, + errorNode: (node as NamedDeclaration).name || node + }; + } - case SyntaxKind.CallSignature: - // Interfaces cannot have return types that cannot be named - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_call_signature_from_exported_interface_has_or_is_using_private_name_0; - break; + function getParameterDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { + const diagnosticMessage: DiagnosticMessage = getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); + return diagnosticMessage !== undefined ? { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + } : undefined; + } - case SyntaxKind.IndexSignature: - // Interfaces cannot have return types that cannot be named - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_index_signature_from_exported_interface_has_or_is_using_private_name_0; - break; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (isStatic(node)) { - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : - Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_public_static_method_from_exported_class_has_or_is_using_private_name_0; - } - else if (node.parent.kind === SyntaxKind.ClassDeclaration) { - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : - Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_public_method_from_exported_class_has_or_is_using_private_name_0; - } - else { - // Interfaces cannot have return types that cannot be named - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_method_from_exported_interface_has_or_is_using_private_name_0; - } - break; - - case SyntaxKind.FunctionDeclaration: - diagnosticMessage = symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_external_module_1_but_cannot_be_named : - Diagnostics.Return_type_of_exported_function_has_or_is_using_name_0_from_private_module_1 : - Diagnostics.Return_type_of_exported_function_has_or_is_using_private_name_0; - break; + function getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult): DiagnosticMessage { + switch (node.parent.kind) { + case SyntaxKind.Constructor: + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_private_name_1; - default: - return Debug.fail("This is unknown kind for signature: " + node.kind); - } + case SyntaxKind.ConstructSignature: + case SyntaxKind.ConstructorType: + // Interfaces cannot have parameter types that cannot be named + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; - return { - diagnosticMessage, - errorNode: (node as NamedDeclaration).name || node - }; - } + case SyntaxKind.CallSignature: + // Interfaces cannot have parameter types that cannot be named + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; - function getParameterDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic | undefined { - const diagnosticMessage: DiagnosticMessage = getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult); - return diagnosticMessage !== undefined ? { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - } : undefined; - } + case SyntaxKind.IndexSignature: + // Interfaces cannot have parameter types that cannot be named + return symbolAccessibilityResult.errorModuleName ? + Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_private_name_1; - function getParameterDeclarationTypeVisibilityDiagnosticMessage(symbolAccessibilityResult: SymbolAccessibilityResult): DiagnosticMessage { - switch (node.parent.kind) { - case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (isStatic(node.parent)) { return symbolAccessibilityResult.errorModuleName ? symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_constructor_from_exported_class_has_or_is_using_private_name_1; - - case SyntaxKind.ConstructSignature: - case SyntaxKind.ConstructorType: - // Interfaces cannot have parameter types that cannot be named + Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; + } + else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; - - case SyntaxKind.CallSignature: + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; + } + else { // Interfaces cannot have parameter types that cannot be named return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; + Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; + } - case SyntaxKind.IndexSignature: - // Interfaces cannot have parameter types that cannot be named - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_index_signature_from_exported_interface_has_or_is_using_private_name_1; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (isStatic(node.parent)) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; - } - else { - // Interfaces cannot have parameter types that cannot be named - return symbolAccessibilityResult.errorModuleName ? - Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; - } - - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionType: - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_exported_function_has_or_is_using_private_name_1; - case SyntaxKind.SetAccessor: - case SyntaxKind.GetAccessor: - return symbolAccessibilityResult.errorModuleName ? - symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? - Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : - Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_private_module_2 : - Diagnostics.Parameter_0_of_accessor_has_or_is_using_private_name_1; + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionType: + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_exported_function_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_exported_function_has_or_is_using_private_name_1; + case SyntaxKind.SetAccessor: + case SyntaxKind.GetAccessor: + return symbolAccessibilityResult.errorModuleName ? + symbolAccessibilityResult.accessibility === SymbolAccessibility.CannotBeNamed ? + Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named : + Diagnostics.Parameter_0_of_accessor_has_or_is_using_name_1_from_private_module_2 : + Diagnostics.Parameter_0_of_accessor_has_or_is_using_private_name_1; - default: - return Debug.fail(`Unknown parent for parameter: ${Debug.formatSyntaxKind(node.parent.kind)}`); - } + default: + return Debug.fail(`Unknown parent for parameter: ${Debug.formatSyntaxKind(node.parent.kind)}`); } + } - function getTypeParameterConstraintVisibilityError(): SymbolAccessibilityDiagnostic { - // Type parameter constraints are named by user so we should always be able to name it - let diagnosticMessage: DiagnosticMessage; - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_class_has_or_is_using_private_name_1; - break; - - case SyntaxKind.InterfaceDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1; - break; - - case SyntaxKind.MappedType: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_mapped_object_type_is_using_private_name_1; - break; - - case SyntaxKind.ConstructorType: - case SyntaxKind.ConstructSignature: - diagnosticMessage = Diagnostics.Type_parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; - break; - - case SyntaxKind.CallSignature: - diagnosticMessage = Diagnostics.Type_parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; - break; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (isStatic(node.parent)) { - diagnosticMessage = Diagnostics.Type_parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; - } - else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { - diagnosticMessage = Diagnostics.Type_parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; - } - else { - diagnosticMessage = Diagnostics.Type_parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; - } - break; - - case SyntaxKind.FunctionType: - case SyntaxKind.FunctionDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_function_has_or_is_using_private_name_1; - break; - - case SyntaxKind.TypeAliasDeclaration: - diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_type_alias_has_or_is_using_private_name_1; - break; - - default: - return Debug.fail("This is unknown parent for type parameter: " + node.parent.kind); - } + function getTypeParameterConstraintVisibilityError(): SymbolAccessibilityDiagnostic { + // Type parameter constraints are named by user so we should always be able to name it + let diagnosticMessage: DiagnosticMessage; + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_class_has_or_is_using_private_name_1; + break; + + case SyntaxKind.InterfaceDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1; + break; + + case SyntaxKind.MappedType: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_mapped_object_type_is_using_private_name_1; + break; + + case SyntaxKind.ConstructorType: + case SyntaxKind.ConstructSignature: + diagnosticMessage = Diagnostics.Type_parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1; + break; + + case SyntaxKind.CallSignature: + diagnosticMessage = Diagnostics.Type_parameter_0_of_call_signature_from_exported_interface_has_or_is_using_private_name_1; + break; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (isStatic(node.parent)) { + diagnosticMessage = Diagnostics.Type_parameter_0_of_public_static_method_from_exported_class_has_or_is_using_private_name_1; + } + else if (node.parent.parent.kind === SyntaxKind.ClassDeclaration) { + diagnosticMessage = Diagnostics.Type_parameter_0_of_public_method_from_exported_class_has_or_is_using_private_name_1; + } + else { + diagnosticMessage = Diagnostics.Type_parameter_0_of_method_from_exported_interface_has_or_is_using_private_name_1; + } + break; - return { - diagnosticMessage, - errorNode: node, - typeName: (node as NamedDeclaration).name - }; - } + case SyntaxKind.FunctionType: + case SyntaxKind.FunctionDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_function_has_or_is_using_private_name_1; + break; - function getHeritageClauseVisibilityError(): SymbolAccessibilityDiagnostic { - let diagnosticMessage: DiagnosticMessage; - // Heritage clause is written by user so it can always be named - if (isClassDeclaration(node.parent.parent)) { - // Class or Interface implemented/extended is inaccessible - diagnosticMessage = isHeritageClause(node.parent) && node.parent.token === SyntaxKind.ImplementsKeyword ? - Diagnostics.Implements_clause_of_exported_class_0_has_or_is_using_private_name_1 : - node.parent.parent.name ? Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1 : - Diagnostics.extends_clause_of_exported_class_has_or_is_using_private_name_0; - } - else { - // interface is inaccessible - diagnosticMessage = Diagnostics.extends_clause_of_exported_interface_0_has_or_is_using_private_name_1; - } + case SyntaxKind.TypeAliasDeclaration: + diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_type_alias_has_or_is_using_private_name_1; + break; - return { - diagnosticMessage, - errorNode: node, - typeName: getNameOfDeclaration(node.parent.parent as Declaration) - }; + default: + return Debug.fail("This is unknown parent for type parameter: " + node.parent.kind); } - function getImportEntityNameVisibilityError(): SymbolAccessibilityDiagnostic { - return { - diagnosticMessage: Diagnostics.Import_declaration_0_is_using_private_name_1, - errorNode: node, - typeName: (node as NamedDeclaration).name - }; - } + return { + diagnosticMessage, + errorNode: node, + typeName: (node as NamedDeclaration).name + }; + } - function getTypeAliasDeclarationVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { - return { - diagnosticMessage: symbolAccessibilityResult.errorModuleName - ? Diagnostics.Exported_type_alias_0_has_or_is_using_private_name_1_from_module_2 - : Diagnostics.Exported_type_alias_0_has_or_is_using_private_name_1, - errorNode: isJSDocTypeAlias(node) ? Debug.checkDefined(node.typeExpression) : (node as TypeAliasDeclaration).type, - typeName: isJSDocTypeAlias(node) ? getNameOfDeclaration(node) : (node as TypeAliasDeclaration).name, - }; + function getHeritageClauseVisibilityError(): SymbolAccessibilityDiagnostic { + let diagnosticMessage: DiagnosticMessage; + // Heritage clause is written by user so it can always be named + if (isClassDeclaration(node.parent.parent)) { + // Class or Interface implemented/extended is inaccessible + diagnosticMessage = isHeritageClause(node.parent) && node.parent.token === SyntaxKind.ImplementsKeyword ? + Diagnostics.Implements_clause_of_exported_class_0_has_or_is_using_private_name_1 : + node.parent.parent.name ? Diagnostics.extends_clause_of_exported_class_0_has_or_is_using_private_name_1 : + Diagnostics.extends_clause_of_exported_class_has_or_is_using_private_name_0; + } + else { + // interface is inaccessible + diagnosticMessage = Diagnostics.extends_clause_of_exported_interface_0_has_or_is_using_private_name_1; } + + return { + diagnosticMessage, + errorNode: node, + typeName: getNameOfDeclaration(node.parent.parent as Declaration) + }; + } + + function getImportEntityNameVisibilityError(): SymbolAccessibilityDiagnostic { + return { + diagnosticMessage: Diagnostics.Import_declaration_0_is_using_private_name_1, + errorNode: node, + typeName: (node as NamedDeclaration).name + }; + } + + function getTypeAliasDeclarationVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { + return { + diagnosticMessage: symbolAccessibilityResult.errorModuleName + ? Diagnostics.Exported_type_alias_0_has_or_is_using_private_name_1_from_module_2 + : Diagnostics.Exported_type_alias_0_has_or_is_using_private_name_1, + errorNode: isJSDocTypeAlias(node) ? Debug.checkDefined(node.typeExpression) : (node as TypeAliasDeclaration).type, + typeName: isJSDocTypeAlias(node) ? getNameOfDeclaration(node) : (node as TypeAliasDeclaration).name, + }; } } diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 64900c56a887a..b4f4739a803bd 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -1,544 +1,562 @@ -/*@internal*/ -namespace ts { - interface FlattenContext { - context: TransformationContext; - level: FlattenLevel; - downlevelIteration: boolean; - hoistTempVariables: boolean; - hasTransformedPriorElement?: boolean; // indicates whether we've transformed a prior declaration - emitExpression: (value: Expression) => void; - emitBindingOrAssignment: (target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node | undefined) => void; - createArrayBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ArrayBindingOrAssignmentPattern; - createObjectBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ObjectBindingOrAssignmentPattern; - createArrayBindingOrAssignmentElement: (node: Identifier) => BindingOrAssignmentElement; - visitor?: (node: Node) => VisitResult; - } - - export const enum FlattenLevel { - All, - ObjectRest, - } +import { + __String, addRange, append, ArrayBindingElement, ArrayBindingOrAssignmentPattern, BindingElement, BindingName, + BindingOrAssignmentElement, BindingOrAssignmentElementTarget, BindingOrAssignmentPattern, Debug, + DestructuringAssignment, ElementAccessExpression, every, Expression, factory, forEach, + getElementsOfBindingOrAssignmentPattern, getInitializerOfBindingOrAssignmentElement, + getPropertyNameOfBindingOrAssignmentElement, getRestIndicatorOfBindingOrAssignmentElement, + getTargetOfBindingOrAssignmentElement, Identifier, idText, isArrayBindingElement, isArrayBindingOrAssignmentPattern, + isBindingElement, isBindingName, isBindingOrAssignmentPattern, isComputedPropertyName, isDeclarationBindingElement, + isDestructuringAssignment, isEmptyArrayLiteral, isEmptyObjectLiteral, isExpression, isIdentifier, + isLiteralExpression, isObjectBindingOrAssignmentPattern, isOmittedExpression, isPropertyNameLiteral, + isSimpleInlineableExpression, isStringOrNumericLiteralLike, isVariableDeclaration, last, LeftHandSideExpression, + map, Node, NodeFactory, nodeIsSynthesized, ObjectBindingOrAssignmentPattern, ParameterDeclaration, PropertyName, + setTextRange, some, TextRange, TransformationContext, TransformFlags, + tryGetPropertyNameOfBindingOrAssignmentElement, VariableDeclaration, visitNode, VisitResult, +} from "../_namespaces/ts"; + +interface FlattenContext { + context: TransformationContext; + level: FlattenLevel; + downlevelIteration: boolean; + hoistTempVariables: boolean; + hasTransformedPriorElement?: boolean; // indicates whether we've transformed a prior declaration + emitExpression: (value: Expression) => void; + emitBindingOrAssignment: (target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node | undefined) => void; + createArrayBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ArrayBindingOrAssignmentPattern; + createObjectBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ObjectBindingOrAssignmentPattern; + createArrayBindingOrAssignmentElement: (node: Identifier) => BindingOrAssignmentElement; + visitor?: (node: Node) => VisitResult; +} - /** - * Flattens a DestructuringAssignment or a VariableDeclaration to an expression. - * - * @param node The node to flatten. - * @param visitor An optional visitor used to visit initializers. - * @param context The transformation context. - * @param level Indicates the extent to which flattening should occur. - * @param needsValue An optional value indicating whether the value from the right-hand-side of - * the destructuring assignment is needed as part of a larger expression. - * @param createAssignmentCallback An optional callback used to create the assignment expression. - */ - export function flattenDestructuringAssignment( - node: VariableDeclaration | DestructuringAssignment, - visitor: ((node: Node) => VisitResult) | undefined, - context: TransformationContext, - level: FlattenLevel, - needsValue?: boolean, - createAssignmentCallback?: (name: Identifier, value: Expression, location?: TextRange) => Expression): Expression { - let location: TextRange = node; - let value: Expression | undefined; - if (isDestructuringAssignment(node)) { - value = node.right; - while (isEmptyArrayLiteral(node.left) || isEmptyObjectLiteral(node.left)) { - if (isDestructuringAssignment(value)) { - location = node = value; - value = node.right; - } - else { - return visitNode(value, visitor, isExpression); - } - } - } +/** @internal */ +export const enum FlattenLevel { + All, + ObjectRest, +} - let expressions: Expression[] | undefined; - const flattenContext: FlattenContext = { - context, - level, - downlevelIteration: !!context.getCompilerOptions().downlevelIteration, - hoistTempVariables: true, - emitExpression, - emitBindingOrAssignment, - createArrayBindingOrAssignmentPattern: elements => makeArrayAssignmentPattern(context.factory, elements), - createObjectBindingOrAssignmentPattern: elements => makeObjectAssignmentPattern(context.factory, elements), - createArrayBindingOrAssignmentElement: makeAssignmentElement, - visitor - }; - - if (value) { - value = visitNode(value, visitor, isExpression); - - if (isIdentifier(value) && bindingOrAssignmentElementAssignsToName(node, value.escapedText) || - bindingOrAssignmentElementContainsNonLiteralComputedName(node)) { - // If the right-hand value of the assignment is also an assignment target then - // we need to cache the right-hand value. - value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ false, location); - } - else if (needsValue) { - // If the right-hand value of the destructuring assignment needs to be preserved (as - // is the case when the destructuring assignment is part of a larger expression), - // then we need to cache the right-hand value. - // - // The source map location for the assignment should point to the entire binary - // expression. - value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); +/** + * Flattens a DestructuringAssignment or a VariableDeclaration to an expression. + * + * @param node The node to flatten. + * @param visitor An optional visitor used to visit initializers. + * @param context The transformation context. + * @param level Indicates the extent to which flattening should occur. + * @param needsValue An optional value indicating whether the value from the right-hand-side of + * the destructuring assignment is needed as part of a larger expression. + * @param createAssignmentCallback An optional callback used to create the assignment expression. + * + * @internal + */ +export function flattenDestructuringAssignment( + node: VariableDeclaration | DestructuringAssignment, + visitor: ((node: Node) => VisitResult) | undefined, + context: TransformationContext, + level: FlattenLevel, + needsValue?: boolean, + createAssignmentCallback?: (name: Identifier, value: Expression, location?: TextRange) => Expression): Expression { + let location: TextRange = node; + let value: Expression | undefined; + if (isDestructuringAssignment(node)) { + value = node.right; + while (isEmptyArrayLiteral(node.left) || isEmptyObjectLiteral(node.left)) { + if (isDestructuringAssignment(value)) { + location = node = value; + value = node.right; } - else if (nodeIsSynthesized(node)) { - // Generally, the source map location for a destructuring assignment is the root - // expression. - // - // However, if the root expression is synthesized (as in the case - // of the initializer when transforming a ForOfStatement), then the source map - // location should point to the right-hand value of the expression. - location = value; + else { + return visitNode(value, visitor, isExpression); } } + } - flattenBindingOrAssignmentElement(flattenContext, node, value, location, /*skipInitializer*/ isDestructuringAssignment(node)); + let expressions: Expression[] | undefined; + const flattenContext: FlattenContext = { + context, + level, + downlevelIteration: !!context.getCompilerOptions().downlevelIteration, + hoistTempVariables: true, + emitExpression, + emitBindingOrAssignment, + createArrayBindingOrAssignmentPattern: elements => makeArrayAssignmentPattern(context.factory, elements), + createObjectBindingOrAssignmentPattern: elements => makeObjectAssignmentPattern(context.factory, elements), + createArrayBindingOrAssignmentElement: makeAssignmentElement, + visitor + }; + + if (value) { + value = visitNode(value, visitor, isExpression); + + if (isIdentifier(value) && bindingOrAssignmentElementAssignsToName(node, value.escapedText) || + bindingOrAssignmentElementContainsNonLiteralComputedName(node)) { + // If the right-hand value of the assignment is also an assignment target then + // we need to cache the right-hand value. + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ false, location); + } + else if (needsValue) { + // If the right-hand value of the destructuring assignment needs to be preserved (as + // is the case when the destructuring assignment is part of a larger expression), + // then we need to cache the right-hand value. + // + // The source map location for the assignment should point to the entire binary + // expression. + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); + } + else if (nodeIsSynthesized(node)) { + // Generally, the source map location for a destructuring assignment is the root + // expression. + // + // However, if the root expression is synthesized (as in the case + // of the initializer when transforming a ForOfStatement), then the source map + // location should point to the right-hand value of the expression. + location = value; + } + } - if (value && needsValue) { - if (!some(expressions)) { - return value; - } + flattenBindingOrAssignmentElement(flattenContext, node, value, location, /*skipInitializer*/ isDestructuringAssignment(node)); - expressions.push(value); + if (value && needsValue) { + if (!some(expressions)) { + return value; } - return context.factory.inlineExpressions(expressions!) || context.factory.createOmittedExpression(); + expressions.push(value); + } - function emitExpression(expression: Expression) { - expressions = append(expressions, expression); - } + return context.factory.inlineExpressions(expressions!) || context.factory.createOmittedExpression(); - function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) { - Debug.assertNode(target, createAssignmentCallback ? isIdentifier : isExpression); - const expression = createAssignmentCallback - ? createAssignmentCallback(target as Identifier, value, location) - : setTextRange( - context.factory.createAssignment(visitNode(target as Expression, visitor, isExpression), value), - location - ); - expression.original = original; - emitExpression(expression); - } + function emitExpression(expression: Expression) { + expressions = append(expressions, expression); } - function bindingOrAssignmentElementAssignsToName(element: BindingOrAssignmentElement, escapedName: __String): boolean { - const target = getTargetOfBindingOrAssignmentElement(element)!; // TODO: GH#18217 - if (isBindingOrAssignmentPattern(target)) { - return bindingOrAssignmentPatternAssignsToName(target, escapedName); - } - else if (isIdentifier(target)) { - return target.escapedText === escapedName; - } - return false; + function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) { + Debug.assertNode(target, createAssignmentCallback ? isIdentifier : isExpression); + const expression = createAssignmentCallback + ? createAssignmentCallback(target as Identifier, value, location) + : setTextRange( + context.factory.createAssignment(visitNode(target as Expression, visitor, isExpression), value), + location + ); + expression.original = original; + emitExpression(expression); } +} - function bindingOrAssignmentPatternAssignsToName(pattern: BindingOrAssignmentPattern, escapedName: __String): boolean { - const elements = getElementsOfBindingOrAssignmentPattern(pattern); - for (const element of elements) { - if (bindingOrAssignmentElementAssignsToName(element, escapedName)) { - return true; - } - } - return false; +function bindingOrAssignmentElementAssignsToName(element: BindingOrAssignmentElement, escapedName: __String): boolean { + const target = getTargetOfBindingOrAssignmentElement(element)!; // TODO: GH#18217 + if (isBindingOrAssignmentPattern(target)) { + return bindingOrAssignmentPatternAssignsToName(target, escapedName); + } + else if (isIdentifier(target)) { + return target.escapedText === escapedName; } + return false; +} - function bindingOrAssignmentElementContainsNonLiteralComputedName(element: BindingOrAssignmentElement): boolean { - const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(element); - if (propertyName && isComputedPropertyName(propertyName) && !isLiteralExpression(propertyName.expression)) { +function bindingOrAssignmentPatternAssignsToName(pattern: BindingOrAssignmentPattern, escapedName: __String): boolean { + const elements = getElementsOfBindingOrAssignmentPattern(pattern); + for (const element of elements) { + if (bindingOrAssignmentElementAssignsToName(element, escapedName)) { return true; } - const target = getTargetOfBindingOrAssignmentElement(element); - return !!target && isBindingOrAssignmentPattern(target) && bindingOrAssignmentPatternContainsNonLiteralComputedName(target); } + return false; +} - function bindingOrAssignmentPatternContainsNonLiteralComputedName(pattern: BindingOrAssignmentPattern): boolean { - return !!forEach(getElementsOfBindingOrAssignmentPattern(pattern), bindingOrAssignmentElementContainsNonLiteralComputedName); +function bindingOrAssignmentElementContainsNonLiteralComputedName(element: BindingOrAssignmentElement): boolean { + const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(element); + if (propertyName && isComputedPropertyName(propertyName) && !isLiteralExpression(propertyName.expression)) { + return true; } + const target = getTargetOfBindingOrAssignmentElement(element); + return !!target && isBindingOrAssignmentPattern(target) && bindingOrAssignmentPatternContainsNonLiteralComputedName(target); +} - /** - * Flattens a VariableDeclaration or ParameterDeclaration to one or more variable declarations. - * - * @param node The node to flatten. - * @param visitor An optional visitor used to visit initializers. - * @param context The transformation context. - * @param boundValue The value bound to the declaration. - * @param skipInitializer A value indicating whether to ignore the initializer of `node`. - * @param hoistTempVariables Indicates whether temporary variables should not be recorded in-line. - * @param level Indicates the extent to which flattening should occur. - */ - export function flattenDestructuringBinding( - node: VariableDeclaration | ParameterDeclaration, - visitor: (node: Node) => VisitResult, - context: TransformationContext, - level: FlattenLevel, - rval?: Expression, - hoistTempVariables = false, - skipInitializer?: boolean): VariableDeclaration[] { - let pendingExpressions: Expression[] | undefined; - const pendingDeclarations: { pendingExpressions?: Expression[], name: BindingName, value: Expression, location?: TextRange, original?: Node; }[] = []; - const declarations: VariableDeclaration[] = []; - const flattenContext: FlattenContext = { - context, - level, - downlevelIteration: !!context.getCompilerOptions().downlevelIteration, - hoistTempVariables, - emitExpression, - emitBindingOrAssignment, - createArrayBindingOrAssignmentPattern: elements => makeArrayBindingPattern(context.factory, elements), - createObjectBindingOrAssignmentPattern: elements => makeObjectBindingPattern(context.factory, elements), - createArrayBindingOrAssignmentElement: name => makeBindingElement(context.factory, name), - visitor - }; - - if (isVariableDeclaration(node)) { - let initializer = getInitializerOfBindingOrAssignmentElement(node); - if (initializer && (isIdentifier(initializer) && bindingOrAssignmentElementAssignsToName(node, initializer.escapedText) || - bindingOrAssignmentElementContainsNonLiteralComputedName(node))) { - // If the right-hand value of the assignment is also an assignment target then - // we need to cache the right-hand value. - initializer = ensureIdentifier(flattenContext, visitNode(initializer, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, initializer); - node = context.factory.updateVariableDeclaration(node, node.name, /*exclamationToken*/ undefined, /*type*/ undefined, initializer); - } +function bindingOrAssignmentPatternContainsNonLiteralComputedName(pattern: BindingOrAssignmentPattern): boolean { + return !!forEach(getElementsOfBindingOrAssignmentPattern(pattern), bindingOrAssignmentElementContainsNonLiteralComputedName); +} + +/** + * Flattens a VariableDeclaration or ParameterDeclaration to one or more variable declarations. + * + * @param node The node to flatten. + * @param visitor An optional visitor used to visit initializers. + * @param context The transformation context. + * @param boundValue The value bound to the declaration. + * @param skipInitializer A value indicating whether to ignore the initializer of `node`. + * @param hoistTempVariables Indicates whether temporary variables should not be recorded in-line. + * @param level Indicates the extent to which flattening should occur. + * + * @internal + */ +export function flattenDestructuringBinding( + node: VariableDeclaration | ParameterDeclaration, + visitor: (node: Node) => VisitResult, + context: TransformationContext, + level: FlattenLevel, + rval?: Expression, + hoistTempVariables = false, + skipInitializer?: boolean): VariableDeclaration[] { + let pendingExpressions: Expression[] | undefined; + const pendingDeclarations: { pendingExpressions?: Expression[], name: BindingName, value: Expression, location?: TextRange, original?: Node; }[] = []; + const declarations: VariableDeclaration[] = []; + const flattenContext: FlattenContext = { + context, + level, + downlevelIteration: !!context.getCompilerOptions().downlevelIteration, + hoistTempVariables, + emitExpression, + emitBindingOrAssignment, + createArrayBindingOrAssignmentPattern: elements => makeArrayBindingPattern(context.factory, elements), + createObjectBindingOrAssignmentPattern: elements => makeObjectBindingPattern(context.factory, elements), + createArrayBindingOrAssignmentElement: name => makeBindingElement(context.factory, name), + visitor + }; + + if (isVariableDeclaration(node)) { + let initializer = getInitializerOfBindingOrAssignmentElement(node); + if (initializer && (isIdentifier(initializer) && bindingOrAssignmentElementAssignsToName(node, initializer.escapedText) || + bindingOrAssignmentElementContainsNonLiteralComputedName(node))) { + // If the right-hand value of the assignment is also an assignment target then + // we need to cache the right-hand value. + initializer = ensureIdentifier(flattenContext, visitNode(initializer, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, initializer); + node = context.factory.updateVariableDeclaration(node, node.name, /*exclamationToken*/ undefined, /*type*/ undefined, initializer); } + } - flattenBindingOrAssignmentElement(flattenContext, node, rval, node, skipInitializer); - if (pendingExpressions) { - const temp = context.factory.createTempVariable(/*recordTempVariable*/ undefined); - if (hoistTempVariables) { - const value = context.factory.inlineExpressions(pendingExpressions); - pendingExpressions = undefined; - emitBindingOrAssignment(temp, value, /*location*/ undefined, /*original*/ undefined); - } - else { - context.hoistVariableDeclaration(temp); - const pendingDeclaration = last(pendingDeclarations); - pendingDeclaration.pendingExpressions = append( - pendingDeclaration.pendingExpressions, - context.factory.createAssignment(temp, pendingDeclaration.value) - ); - addRange(pendingDeclaration.pendingExpressions, pendingExpressions); - pendingDeclaration.value = temp; - } + flattenBindingOrAssignmentElement(flattenContext, node, rval, node, skipInitializer); + if (pendingExpressions) { + const temp = context.factory.createTempVariable(/*recordTempVariable*/ undefined); + if (hoistTempVariables) { + const value = context.factory.inlineExpressions(pendingExpressions); + pendingExpressions = undefined; + emitBindingOrAssignment(temp, value, /*location*/ undefined, /*original*/ undefined); } - for (const { pendingExpressions, name, value, location, original } of pendingDeclarations) { - const variable = context.factory.createVariableDeclaration( - name, - /*exclamationToken*/ undefined, - /*type*/ undefined, - pendingExpressions ? context.factory.inlineExpressions(append(pendingExpressions, value)) : value + else { + context.hoistVariableDeclaration(temp); + const pendingDeclaration = last(pendingDeclarations); + pendingDeclaration.pendingExpressions = append( + pendingDeclaration.pendingExpressions, + context.factory.createAssignment(temp, pendingDeclaration.value) ); - variable.original = original; - setTextRange(variable, location); - declarations.push(variable); + addRange(pendingDeclaration.pendingExpressions, pendingExpressions); + pendingDeclaration.value = temp; } - return declarations; + } + for (const { pendingExpressions, name, value, location, original } of pendingDeclarations) { + const variable = context.factory.createVariableDeclaration( + name, + /*exclamationToken*/ undefined, + /*type*/ undefined, + pendingExpressions ? context.factory.inlineExpressions(append(pendingExpressions, value)) : value + ); + variable.original = original; + setTextRange(variable, location); + declarations.push(variable); + } + return declarations; - function emitExpression(value: Expression) { - pendingExpressions = append(pendingExpressions, value); - } + function emitExpression(value: Expression) { + pendingExpressions = append(pendingExpressions, value); + } - function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange | undefined, original: Node | undefined) { - Debug.assertNode(target, isBindingName); - if (pendingExpressions) { - value = context.factory.inlineExpressions(append(pendingExpressions, value)); - pendingExpressions = undefined; - } - pendingDeclarations.push({ pendingExpressions, name: target, value, location, original }); + function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange | undefined, original: Node | undefined) { + Debug.assertNode(target, isBindingName); + if (pendingExpressions) { + value = context.factory.inlineExpressions(append(pendingExpressions, value)); + pendingExpressions = undefined; } + pendingDeclarations.push({ pendingExpressions, name: target, value, location, original }); } +} - /** - * Flattens a BindingOrAssignmentElement into zero or more bindings or assignments. - * - * @param flattenContext Options used to control flattening. - * @param element The element to flatten. - * @param value The current RHS value to assign to the element. - * @param location The location to use for source maps and comments. - * @param skipInitializer An optional value indicating whether to include the initializer - * for the element. - */ - function flattenBindingOrAssignmentElement( - flattenContext: FlattenContext, - element: BindingOrAssignmentElement, - value: Expression | undefined, - location: TextRange, - skipInitializer?: boolean) { - const bindingTarget = getTargetOfBindingOrAssignmentElement(element)!; // TODO: GH#18217 - if (!skipInitializer) { - const initializer = visitNode(getInitializerOfBindingOrAssignmentElement(element), flattenContext.visitor, isExpression); - if (initializer) { - // Combine value and initializer - if (value) { - value = createDefaultValueCheck(flattenContext, value, initializer, location); - // If 'value' is not a simple expression, it could contain side-effecting code that should evaluate before an object or array binding pattern. - if (!isSimpleInlineableExpression(initializer) && isBindingOrAssignmentPattern(bindingTarget)) { - value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); - } - } - else { - value = initializer; +/** + * Flattens a BindingOrAssignmentElement into zero or more bindings or assignments. + * + * @param flattenContext Options used to control flattening. + * @param element The element to flatten. + * @param value The current RHS value to assign to the element. + * @param location The location to use for source maps and comments. + * @param skipInitializer An optional value indicating whether to include the initializer + * for the element. + */ +function flattenBindingOrAssignmentElement( + flattenContext: FlattenContext, + element: BindingOrAssignmentElement, + value: Expression | undefined, + location: TextRange, + skipInitializer?: boolean) { + const bindingTarget = getTargetOfBindingOrAssignmentElement(element)!; // TODO: GH#18217 + if (!skipInitializer) { + const initializer = visitNode(getInitializerOfBindingOrAssignmentElement(element), flattenContext.visitor, isExpression); + if (initializer) { + // Combine value and initializer + if (value) { + value = createDefaultValueCheck(flattenContext, value, initializer, location); + // If 'value' is not a simple expression, it could contain side-effecting code that should evaluate before an object or array binding pattern. + if (!isSimpleInlineableExpression(initializer) && isBindingOrAssignmentPattern(bindingTarget)) { + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); } } - else if (!value) { - // Use 'void 0' in absence of value and initializer - value = flattenContext.context.factory.createVoidZero(); + else { + value = initializer; } } - if (isObjectBindingOrAssignmentPattern(bindingTarget)) { - flattenObjectBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); - } - else if (isArrayBindingOrAssignmentPattern(bindingTarget)) { - flattenArrayBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); - } - else { - flattenContext.emitBindingOrAssignment(bindingTarget, value!, location, /*original*/ element); // TODO: GH#18217 + else if (!value) { + // Use 'void 0' in absence of value and initializer + value = flattenContext.context.factory.createVoidZero(); } } + if (isObjectBindingOrAssignmentPattern(bindingTarget)) { + flattenObjectBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); + } + else if (isArrayBindingOrAssignmentPattern(bindingTarget)) { + flattenArrayBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value!, location); + } + else { + flattenContext.emitBindingOrAssignment(bindingTarget, value!, location, /*original*/ element); // TODO: GH#18217 + } +} - /** - * Flattens an ObjectBindingOrAssignmentPattern into zero or more bindings or assignments. - * - * @param flattenContext Options used to control flattening. - * @param parent The parent element of the pattern. - * @param pattern The ObjectBindingOrAssignmentPattern to flatten. - * @param value The current RHS value to assign to the element. - * @param location The location to use for source maps and comments. - */ - function flattenObjectBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ObjectBindingOrAssignmentPattern, value: Expression, location: TextRange) { - const elements = getElementsOfBindingOrAssignmentPattern(pattern); - const numElements = elements.length; - if (numElements !== 1) { - // For anything other than a single-element destructuring we need to generate a temporary - // to ensure value is evaluated exactly once. Additionally, if we have zero elements - // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, - // so in that case, we'll intentionally create that temporary. - const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; - value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); - } - let bindingElements: BindingOrAssignmentElement[] | undefined; - let computedTempVariables: Expression[] | undefined; - for (let i = 0; i < numElements; i++) { - const element = elements[i]; - if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { - const propertyName = getPropertyNameOfBindingOrAssignmentElement(element)!; - if (flattenContext.level >= FlattenLevel.ObjectRest - && !(element.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) - && !(getTargetOfBindingOrAssignmentElement(element)!.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) - && !isComputedPropertyName(propertyName)) { - bindingElements = append(bindingElements, visitNode(element, flattenContext.visitor)); - } - else { - if (bindingElements) { - flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); - bindingElements = undefined; - } - const rhsValue = createDestructuringPropertyAccess(flattenContext, value, propertyName); - if (isComputedPropertyName(propertyName)) { - computedTempVariables = append(computedTempVariables, (rhsValue as ElementAccessExpression).argumentExpression); - } - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); - } +/** + * Flattens an ObjectBindingOrAssignmentPattern into zero or more bindings or assignments. + * + * @param flattenContext Options used to control flattening. + * @param parent The parent element of the pattern. + * @param pattern The ObjectBindingOrAssignmentPattern to flatten. + * @param value The current RHS value to assign to the element. + * @param location The location to use for source maps and comments. + */ +function flattenObjectBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ObjectBindingOrAssignmentPattern, value: Expression, location: TextRange) { + const elements = getElementsOfBindingOrAssignmentPattern(pattern); + const numElements = elements.length; + if (numElements !== 1) { + // For anything other than a single-element destructuring we need to generate a temporary + // to ensure value is evaluated exactly once. Additionally, if we have zero elements + // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, + // so in that case, we'll intentionally create that temporary. + const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; + value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); + } + let bindingElements: BindingOrAssignmentElement[] | undefined; + let computedTempVariables: Expression[] | undefined; + for (let i = 0; i < numElements; i++) { + const element = elements[i]; + if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { + const propertyName = getPropertyNameOfBindingOrAssignmentElement(element)!; + if (flattenContext.level >= FlattenLevel.ObjectRest + && !(element.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) + && !(getTargetOfBindingOrAssignmentElement(element)!.transformFlags & (TransformFlags.ContainsRestOrSpread | TransformFlags.ContainsObjectRestOrSpread)) + && !isComputedPropertyName(propertyName)) { + bindingElements = append(bindingElements, visitNode(element, flattenContext.visitor)); } - else if (i === numElements - 1) { + else { if (bindingElements) { flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); bindingElements = undefined; } - const rhsValue = flattenContext.context.getEmitHelperFactory().createRestHelper(value, elements, computedTempVariables, pattern); - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, element); + const rhsValue = createDestructuringPropertyAccess(flattenContext, value, propertyName); + if (isComputedPropertyName(propertyName)) { + computedTempVariables = append(computedTempVariables, (rhsValue as ElementAccessExpression).argumentExpression); + } + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); } } - if (bindingElements) { - flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); + else if (i === numElements - 1) { + if (bindingElements) { + flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); + bindingElements = undefined; + } + const rhsValue = flattenContext.context.getEmitHelperFactory().createRestHelper(value, elements, computedTempVariables, pattern); + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, element); } } + if (bindingElements) { + flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern); + } +} - /** - * Flattens an ArrayBindingOrAssignmentPattern into zero or more bindings or assignments. - * - * @param flattenContext Options used to control flattening. - * @param parent The parent element of the pattern. - * @param pattern The ArrayBindingOrAssignmentPattern to flatten. - * @param value The current RHS value to assign to the element. - * @param location The location to use for source maps and comments. - */ - function flattenArrayBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ArrayBindingOrAssignmentPattern, value: Expression, location: TextRange) { - const elements = getElementsOfBindingOrAssignmentPattern(pattern); - const numElements = elements.length; - if (flattenContext.level < FlattenLevel.ObjectRest && flattenContext.downlevelIteration) { - // Read the elements of the iterable into an array - value = ensureIdentifier( - flattenContext, - setTextRange( - flattenContext.context.getEmitHelperFactory().createReadHelper( - value, - numElements > 0 && getRestIndicatorOfBindingOrAssignmentElement(elements[numElements - 1]) - ? undefined - : numElements - ), - location +/** + * Flattens an ArrayBindingOrAssignmentPattern into zero or more bindings or assignments. + * + * @param flattenContext Options used to control flattening. + * @param parent The parent element of the pattern. + * @param pattern The ArrayBindingOrAssignmentPattern to flatten. + * @param value The current RHS value to assign to the element. + * @param location The location to use for source maps and comments. + */ +function flattenArrayBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ArrayBindingOrAssignmentPattern, value: Expression, location: TextRange) { + const elements = getElementsOfBindingOrAssignmentPattern(pattern); + const numElements = elements.length; + if (flattenContext.level < FlattenLevel.ObjectRest && flattenContext.downlevelIteration) { + // Read the elements of the iterable into an array + value = ensureIdentifier( + flattenContext, + setTextRange( + flattenContext.context.getEmitHelperFactory().createReadHelper( + value, + numElements > 0 && getRestIndicatorOfBindingOrAssignmentElement(elements[numElements - 1]) + ? undefined + : numElements ), - /*reuseIdentifierExpressions*/ false, location - ); - } - else if (numElements !== 1 && (flattenContext.level < FlattenLevel.ObjectRest || numElements === 0) - || every(elements, isOmittedExpression)) { - // For anything other than a single-element destructuring we need to generate a temporary - // to ensure value is evaluated exactly once. Additionally, if we have zero elements - // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, - // so in that case, we'll intentionally create that temporary. - // Or all the elements of the binding pattern are omitted expression such as "var [,] = [1,2]", - // then we will create temporary variable. - const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; - value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); - } - let bindingElements: BindingOrAssignmentElement[] | undefined; - let restContainingElements: [Identifier, BindingOrAssignmentElement][] | undefined; - for (let i = 0; i < numElements; i++) { - const element = elements[i]; - if (flattenContext.level >= FlattenLevel.ObjectRest) { - // If an array pattern contains an ObjectRest, we must cache the result so that we - // can perform the ObjectRest destructuring in a different declaration - if (element.transformFlags & TransformFlags.ContainsObjectRestOrSpread || flattenContext.hasTransformedPriorElement && !isSimpleBindingOrAssignmentElement(element)) { - flattenContext.hasTransformedPriorElement = true; - const temp = flattenContext.context.factory.createTempVariable(/*recordTempVariable*/ undefined); - if (flattenContext.hoistTempVariables) { - flattenContext.context.hoistVariableDeclaration(temp); - } - - restContainingElements = append(restContainingElements, [temp, element] as [Identifier, BindingOrAssignmentElement]); - bindingElements = append(bindingElements, flattenContext.createArrayBindingOrAssignmentElement(temp)); - } - else { - bindingElements = append(bindingElements, element); + ), + /*reuseIdentifierExpressions*/ false, + location + ); + } + else if (numElements !== 1 && (flattenContext.level < FlattenLevel.ObjectRest || numElements === 0) + || every(elements, isOmittedExpression)) { + // For anything other than a single-element destructuring we need to generate a temporary + // to ensure value is evaluated exactly once. Additionally, if we have zero elements + // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, + // so in that case, we'll intentionally create that temporary. + // Or all the elements of the binding pattern are omitted expression such as "var [,] = [1,2]", + // then we will create temporary variable. + const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0; + value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location); + } + let bindingElements: BindingOrAssignmentElement[] | undefined; + let restContainingElements: [Identifier, BindingOrAssignmentElement][] | undefined; + for (let i = 0; i < numElements; i++) { + const element = elements[i]; + if (flattenContext.level >= FlattenLevel.ObjectRest) { + // If an array pattern contains an ObjectRest, we must cache the result so that we + // can perform the ObjectRest destructuring in a different declaration + if (element.transformFlags & TransformFlags.ContainsObjectRestOrSpread || flattenContext.hasTransformedPriorElement && !isSimpleBindingOrAssignmentElement(element)) { + flattenContext.hasTransformedPriorElement = true; + const temp = flattenContext.context.factory.createTempVariable(/*recordTempVariable*/ undefined); + if (flattenContext.hoistTempVariables) { + flattenContext.context.hoistVariableDeclaration(temp); } + + restContainingElements = append(restContainingElements, [temp, element] as [Identifier, BindingOrAssignmentElement]); + bindingElements = append(bindingElements, flattenContext.createArrayBindingOrAssignmentElement(temp)); } - else if (isOmittedExpression(element)) { - continue; - } - else if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { - const rhsValue = flattenContext.context.factory.createElementAccessExpression(value, i); - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); - } - else if (i === numElements - 1) { - const rhsValue = flattenContext.context.factory.createArraySliceCall(value, i); - flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); + else { + bindingElements = append(bindingElements, element); } } - if (bindingElements) { - flattenContext.emitBindingOrAssignment(flattenContext.createArrayBindingOrAssignmentPattern(bindingElements), value, location, pattern); + else if (isOmittedExpression(element)) { + continue; } - if (restContainingElements) { - for (const [id, element] of restContainingElements) { - flattenBindingOrAssignmentElement(flattenContext, element, id, element); - } + else if (!getRestIndicatorOfBindingOrAssignmentElement(element)) { + const rhsValue = flattenContext.context.factory.createElementAccessExpression(value, i); + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); + } + else if (i === numElements - 1) { + const rhsValue = flattenContext.context.factory.createArraySliceCall(value, i); + flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element); } } - - function isSimpleBindingOrAssignmentElement(element: BindingOrAssignmentElement): boolean { - const target = getTargetOfBindingOrAssignmentElement(element); - if (!target || isOmittedExpression(target)) return true; - const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(element); - if (propertyName && !isPropertyNameLiteral(propertyName)) return false; - const initializer = getInitializerOfBindingOrAssignmentElement(element); - if (initializer && !isSimpleInlineableExpression(initializer)) return false; - if (isBindingOrAssignmentPattern(target)) return every(getElementsOfBindingOrAssignmentPattern(target), isSimpleBindingOrAssignmentElement); - return isIdentifier(target); + if (bindingElements) { + flattenContext.emitBindingOrAssignment(flattenContext.createArrayBindingOrAssignmentPattern(bindingElements), value, location, pattern); } - - /** - * Creates an expression used to provide a default value if a value is `undefined` at runtime. - * - * @param flattenContext Options used to control flattening. - * @param value The RHS value to test. - * @param defaultValue The default value to use if `value` is `undefined` at runtime. - * @param location The location to use for source maps and comments. - */ - function createDefaultValueCheck(flattenContext: FlattenContext, value: Expression, defaultValue: Expression, location: TextRange): Expression { - value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); - return flattenContext.context.factory.createConditionalExpression(flattenContext.context.factory.createTypeCheck(value, "undefined"), /*questionToken*/ undefined, defaultValue, /*colonToken*/ undefined, value); + if (restContainingElements) { + for (const [id, element] of restContainingElements) { + flattenBindingOrAssignmentElement(flattenContext, element, id, element); + } } +} - /** - * Creates either a PropertyAccessExpression or an ElementAccessExpression for the - * right-hand side of a transformed destructuring assignment. - * - * @link https://tc39.github.io/ecma262/#sec-runtime-semantics-keyeddestructuringassignmentevaluation - * - * @param flattenContext Options used to control flattening. - * @param value The RHS value that is the source of the property. - * @param propertyName The destructuring property name. - */ - function createDestructuringPropertyAccess(flattenContext: FlattenContext, value: Expression, propertyName: PropertyName): LeftHandSideExpression { - if (isComputedPropertyName(propertyName)) { - const argumentExpression = ensureIdentifier(flattenContext, visitNode(propertyName.expression, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, /*location*/ propertyName); - return flattenContext.context.factory.createElementAccessExpression(value, argumentExpression); - } - else if (isStringOrNumericLiteralLike(propertyName)) { - const argumentExpression = factory.cloneNode(propertyName); - return flattenContext.context.factory.createElementAccessExpression(value, argumentExpression); - } - else { - const name = flattenContext.context.factory.createIdentifier(idText(propertyName)); - return flattenContext.context.factory.createPropertyAccessExpression(value, name); - } +function isSimpleBindingOrAssignmentElement(element: BindingOrAssignmentElement): boolean { + const target = getTargetOfBindingOrAssignmentElement(element); + if (!target || isOmittedExpression(target)) return true; + const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(element); + if (propertyName && !isPropertyNameLiteral(propertyName)) return false; + const initializer = getInitializerOfBindingOrAssignmentElement(element); + if (initializer && !isSimpleInlineableExpression(initializer)) return false; + if (isBindingOrAssignmentPattern(target)) return every(getElementsOfBindingOrAssignmentPattern(target), isSimpleBindingOrAssignmentElement); + return isIdentifier(target); +} + +/** + * Creates an expression used to provide a default value if a value is `undefined` at runtime. + * + * @param flattenContext Options used to control flattening. + * @param value The RHS value to test. + * @param defaultValue The default value to use if `value` is `undefined` at runtime. + * @param location The location to use for source maps and comments. + */ +function createDefaultValueCheck(flattenContext: FlattenContext, value: Expression, defaultValue: Expression, location: TextRange): Expression { + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location); + return flattenContext.context.factory.createConditionalExpression(flattenContext.context.factory.createTypeCheck(value, "undefined"), /*questionToken*/ undefined, defaultValue, /*colonToken*/ undefined, value); +} + +/** + * Creates either a PropertyAccessExpression or an ElementAccessExpression for the + * right-hand side of a transformed destructuring assignment. + * + * @link https://tc39.github.io/ecma262/#sec-runtime-semantics-keyeddestructuringassignmentevaluation + * + * @param flattenContext Options used to control flattening. + * @param value The RHS value that is the source of the property. + * @param propertyName The destructuring property name. + */ +function createDestructuringPropertyAccess(flattenContext: FlattenContext, value: Expression, propertyName: PropertyName): LeftHandSideExpression { + if (isComputedPropertyName(propertyName)) { + const argumentExpression = ensureIdentifier(flattenContext, visitNode(propertyName.expression, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, /*location*/ propertyName); + return flattenContext.context.factory.createElementAccessExpression(value, argumentExpression); + } + else if (isStringOrNumericLiteralLike(propertyName)) { + const argumentExpression = factory.cloneNode(propertyName); + return flattenContext.context.factory.createElementAccessExpression(value, argumentExpression); } + else { + const name = flattenContext.context.factory.createIdentifier(idText(propertyName)); + return flattenContext.context.factory.createPropertyAccessExpression(value, name); + } +} - /** - * Ensures that there exists a declared identifier whose value holds the given expression. - * This function is useful to ensure that the expression's value can be read from in subsequent expressions. - * Unless 'reuseIdentifierExpressions' is false, 'value' will be returned if it is just an identifier. - * - * @param flattenContext Options used to control flattening. - * @param value the expression whose value needs to be bound. - * @param reuseIdentifierExpressions true if identifier expressions can simply be returned; - * false if it is necessary to always emit an identifier. - * @param location The location to use for source maps and comments. - */ - function ensureIdentifier(flattenContext: FlattenContext, value: Expression, reuseIdentifierExpressions: boolean, location: TextRange) { - if (isIdentifier(value) && reuseIdentifierExpressions) { - return value; +/** + * Ensures that there exists a declared identifier whose value holds the given expression. + * This function is useful to ensure that the expression's value can be read from in subsequent expressions. + * Unless 'reuseIdentifierExpressions' is false, 'value' will be returned if it is just an identifier. + * + * @param flattenContext Options used to control flattening. + * @param value the expression whose value needs to be bound. + * @param reuseIdentifierExpressions true if identifier expressions can simply be returned; + * false if it is necessary to always emit an identifier. + * @param location The location to use for source maps and comments. + */ +function ensureIdentifier(flattenContext: FlattenContext, value: Expression, reuseIdentifierExpressions: boolean, location: TextRange) { + if (isIdentifier(value) && reuseIdentifierExpressions) { + return value; + } + else { + const temp = flattenContext.context.factory.createTempVariable(/*recordTempVariable*/ undefined); + if (flattenContext.hoistTempVariables) { + flattenContext.context.hoistVariableDeclaration(temp); + flattenContext.emitExpression(setTextRange(flattenContext.context.factory.createAssignment(temp, value), location)); } else { - const temp = flattenContext.context.factory.createTempVariable(/*recordTempVariable*/ undefined); - if (flattenContext.hoistTempVariables) { - flattenContext.context.hoistVariableDeclaration(temp); - flattenContext.emitExpression(setTextRange(flattenContext.context.factory.createAssignment(temp, value), location)); - } - else { - flattenContext.emitBindingOrAssignment(temp, value, location, /*original*/ undefined); - } - return temp; + flattenContext.emitBindingOrAssignment(temp, value, location, /*original*/ undefined); } + return temp; } +} - function makeArrayBindingPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { - Debug.assertEachNode(elements, isArrayBindingElement); - return factory.createArrayBindingPattern(elements as ArrayBindingElement[]); - } +function makeArrayBindingPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { + Debug.assertEachNode(elements, isArrayBindingElement); + return factory.createArrayBindingPattern(elements as ArrayBindingElement[]); +} - function makeArrayAssignmentPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { - return factory.createArrayLiteralExpression(map(elements, factory.converters.convertToArrayAssignmentElement)); - } +function makeArrayAssignmentPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { + return factory.createArrayLiteralExpression(map(elements, factory.converters.convertToArrayAssignmentElement)); +} - function makeObjectBindingPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { - Debug.assertEachNode(elements, isBindingElement); - return factory.createObjectBindingPattern(elements as BindingElement[]); - } +function makeObjectBindingPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { + Debug.assertEachNode(elements, isBindingElement); + return factory.createObjectBindingPattern(elements as BindingElement[]); +} - function makeObjectAssignmentPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { - return factory.createObjectLiteralExpression(map(elements, factory.converters.convertToObjectAssignmentElement)); - } +function makeObjectAssignmentPattern(factory: NodeFactory, elements: BindingOrAssignmentElement[]) { + return factory.createObjectLiteralExpression(map(elements, factory.converters.convertToObjectAssignmentElement)); +} - function makeBindingElement(factory: NodeFactory, name: Identifier) { - return factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name); - } +function makeBindingElement(factory: NodeFactory, name: Identifier) { + return factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name); +} - function makeAssignmentElement(name: Identifier) { - return name; - } +function makeAssignmentElement(name: Identifier) { + return name; } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 7feb0c09c8278..545c48a3dd6e3 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -1,4420 +1,4456 @@ -/*@internal*/ -namespace ts { - const enum ES2015SubstitutionFlags { - /** Enables substitutions for captured `this` */ - CapturedThis = 1 << 0, - /** Enables substitutions for block-scoped bindings. */ - BlockScopedBindings = 1 << 1, - } +import { + __String, AccessorDeclaration, addEmitHelpers, addRange, addSyntheticLeadingComment, AllAccessorDeclarations, + append, arrayIsEqualTo, ArrayLiteralExpression, ArrowFunction, BinaryExpression, BindingElement, BindingPattern, + Block, BreakOrContinueStatement, CallExpression, CaseBlock, CaseClause, cast, CatchClause, chainBundle, + ClassDeclaration, ClassElement, ClassExpression, ClassLikeDeclaration, CommaListExpression, ComputedPropertyName, + concatenate, ConstructorDeclaration, createExpressionForPropertyName, createMemberAccessForPropertyName, + createRange, createTokenRange, Debug, Declaration, DoStatement, elementAt, EmitFlags, EmitHint, emptyArray, ESMap, + Expression, ExpressionStatement, ExpressionWithTypeArguments, filter, first, firstOrUndefined, flatMap, flatten, + flattenDestructuringAssignment, flattenDestructuringBinding, FlattenLevel, ForInStatement, ForOfStatement, + ForStatement, FunctionBody, FunctionDeclaration, FunctionExpression, FunctionLikeDeclaration, + GeneratedIdentifierFlags, getAllAccessorDeclarations, getClassExtendsHeritageElement, getCombinedNodeFlags, + getCommentRange, getEmitFlags, getEnclosingBlockScopeContainer, getFirstConstructorWithBody, getNameOfDeclaration, + getOriginalNode, getParseTreeNode, getSourceMapRange, getSuperCallFromStatement, getUseDefineForClassFields, + hasStaticModifier, hasSyntacticModifier, Identifier, idText, IfStatement, insertStatementAfterCustomPrologue, + insertStatementsAfterCustomPrologue, insertStatementsAfterStandardPrologue, isArrayLiteralExpression, + isArrowFunction, isAssignmentExpression, isBinaryExpression, isBindingPattern, isBlock, isCallExpression, + isCallToHelper, isCaseBlock, isCaseClause, isCatchClause, isClassElement, isClassLike, isComputedPropertyName, + isDefaultClause, isDestructuringAssignment, isExpression, isExpressionStatement, isForInitializer, isForStatement, + isFunctionExpression, isFunctionLike, isHoistedFunction, isHoistedVariableStatement, isIdentifier, + isIdentifierANonContextualKeyword, isIfStatement, isInternalName, isIterationStatement, isLabeledStatement, + isModifier, isObjectLiteralElementLike, isOmittedExpression, isPackedArrayLiteral, isPrivateIdentifier, + isPrologueDirective, isPropertyDeclaration, isPropertyName, isReturnStatement, isSpreadElement, isStatement, + isStatic, isSuperProperty, isSwitchStatement, isTryStatement, isVariableDeclarationList, isVariableStatement, + isWithStatement, IterationStatement, LabeledStatement, last, lastOrUndefined, LeftHandSideExpression, + LiteralExpression, map, Map, MetaProperty, MethodDeclaration, ModifierFlags, moveRangeEnd, moveRangePos, + moveSyntheticComments, NamedDeclaration, NewExpression, Node, NodeArray, NodeCheckFlags, NodeFlags, + nodeIsSynthesized, NumericLiteral, ObjectLiteralElementLike, ObjectLiteralExpression, ParameterDeclaration, + ParenthesizedExpression, PrimaryExpression, ProcessLevel, processTaggedTemplateExpression, PropertyAssignment, + rangeEndIsOnSameLineAsRangeStart, ReturnStatement, SemicolonClassElement, setCommentRange, setEmitFlags, + setOriginalNode, setParent, setSourceMapRange, setTextRange, setTextRangeEnd, setTextRangePos, + setTokenSourceMapRange, ShorthandPropertyAssignment, singleOrMany, singleOrUndefined, skipOuterExpressions, + skipTrivia, some, SourceFile, spanMap, SpreadElement, startOnNewLine, Statement, StringLiteral, SwitchStatement, + SyntaxKind, TaggedTemplateExpression, takeWhile, TemplateExpression, TextRange, TokenFlags, TransformationContext, + TransformFlags, tryCast, unescapeLeadingUnderscores, unwrapInnermostStatementOfLabel, VariableDeclaration, + VariableDeclarationList, VariableStatement, visitEachChild, visitNode, visitNodes, visitParameterList, VisitResult, + VoidExpression, WhileStatement, YieldExpression, Bundle, +} from "../_namespaces/ts"; + +const enum ES2015SubstitutionFlags { + /** Enables substitutions for captured `this` */ + CapturedThis = 1 << 0, + /** Enables substitutions for block-scoped bindings. */ + BlockScopedBindings = 1 << 1, +} - /** - * If loop contains block scoped binding captured in some function then loop body is converted to a function. - * Lexical bindings declared in loop initializer will be passed into the loop body function as parameters, - * however if this binding is modified inside the body - this new value should be propagated back to the original binding. - * This is done by declaring new variable (out parameter holder) outside of the loop for every binding that is reassigned inside the body. - * On every iteration this variable is initialized with value of corresponding binding. - * At every point where control flow leaves the loop either explicitly (break/continue) or implicitly (at the end of loop body) - * we copy the value inside the loop to the out parameter holder. - * - * for (let x;;) { - * let a = 1; - * let b = () => a; - * x++ - * if (...) break; - * ... - * } - * - * will be converted to - * - * var out_x; - * var loop = function(x) { - * var a = 1; - * var b = function() { return a; } - * x++; - * if (...) return out_x = x, "break"; - * ... - * out_x = x; - * } - * for (var x;;) { - * out_x = x; - * var state = loop(x); - * x = out_x; - * if (state === "break") break; - * } - * - * NOTE: values to out parameters are not copies if loop is abrupted with 'return' - in this case this will end the entire enclosing function - * so nobody can observe this new value. - */ - interface LoopOutParameter { - flags: LoopOutParameterFlags; - originalName: Identifier; - outParamName: Identifier; - } - - const enum LoopOutParameterFlags { - Body = 1 << 0, // Modified in the body of the iteration statement - Initializer = 1 << 1, // Set in the initializer of a ForStatement - } - - const enum CopyDirection { - ToOriginal, - ToOutParameter - } - - const enum Jump { - Break = 1 << 1, - Continue = 1 << 2, - Return = 1 << 3 - } - - interface ConvertedLoopState { - /* - * set of labels that occurred inside the converted loop - * used to determine if labeled jump can be emitted as is or it should be dispatched to calling code - */ - labels?: ESMap; - /* - * collection of labeled jumps that transfer control outside the converted loop. - * maps store association 'label -> labelMarker' where - * - label - value of label as it appear in code - * - label marker - return value that should be interpreted by calling code as 'jump to
" - const lessThanToken = getTokenAtPosition(sourceFile, pos); - const firstJsxElementOrOpenElement = lessThanToken.parent; - let binaryExpr = firstJsxElementOrOpenElement.parent; - if (!isBinaryExpression(binaryExpr)) { - // In case the start element is a JsxSelfClosingElement, it the end. - // For JsxOpenElement, find one more parent - binaryExpr = binaryExpr.parent; - if (!isBinaryExpression(binaryExpr)) return undefined; - } - if (!nodeIsMissing(binaryExpr.operatorToken)) return undefined; - return binaryExpr; - } +const fixID = "wrapJsxInFragment"; +const errorCodes = [Diagnostics.JSX_expressions_must_have_one_parent_element.code]; +registerCodeFix({ + errorCodes, + getCodeActions: function getCodeActionsToWrapJsxInFragment(context) { + const { sourceFile, span } = context; + const node = findNodeToFix(sourceFile, span.start); + if (!node) return undefined; + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node)); + return [createCodeFixAction(fixID, changes, Diagnostics.Wrap_in_JSX_fragment, fixID, Diagnostics.Wrap_all_unparented_JSX_in_JSX_fragment)]; + }, + fixIds: [fixID], + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + const node = findNodeToFix(context.sourceFile, diag.start); + if (!node) return undefined; + doChange(changes, context.sourceFile, node); + }), +}); - function doChange(changeTracker: textChanges.ChangeTracker, sf: SourceFile, node: Node) { - const jsx = flattenInvalidBinaryExpr(node); - if (jsx) changeTracker.replaceNode(sf, node, factory.createJsxFragment(factory.createJsxOpeningFragment(), jsx, factory.createJsxJsxClosingFragment())); +function findNodeToFix(sourceFile: SourceFile, pos: number): BinaryExpression | undefined { + // The error always at 1st token that is "<" in "" + const lessThanToken = getTokenAtPosition(sourceFile, pos); + const firstJsxElementOrOpenElement = lessThanToken.parent; + let binaryExpr = firstJsxElementOrOpenElement.parent; + if (!isBinaryExpression(binaryExpr)) { + // In case the start element is a JsxSelfClosingElement, it the end. + // For JsxOpenElement, find one more parent + binaryExpr = binaryExpr.parent; + if (!isBinaryExpression(binaryExpr)) return undefined; } - // The invalid syntax is constructed as - // InvalidJsxTree :: One of - // JsxElement CommaToken InvalidJsxTree - // JsxElement CommaToken JsxElement - function flattenInvalidBinaryExpr(node: Node): JsxChild[] | undefined { - const children: JsxChild[] = []; - let current = node; - while (true) { - if (isBinaryExpression(current) && nodeIsMissing(current.operatorToken) && current.operatorToken.kind === SyntaxKind.CommaToken) { - children.push(current.left as JsxChild); - if (isJsxChild(current.right)) { - children.push(current.right); - // Indicates the tree has go to the bottom - return children; - } - else if (isBinaryExpression(current.right)) { - current = current.right; - continue; - } - // Unreachable case - else return undefined; + if (!nodeIsMissing(binaryExpr.operatorToken)) return undefined; + return binaryExpr; +} + +function doChange(changeTracker: textChanges.ChangeTracker, sf: SourceFile, node: Node) { + const jsx = flattenInvalidBinaryExpr(node); + if (jsx) changeTracker.replaceNode(sf, node, factory.createJsxFragment(factory.createJsxOpeningFragment(), jsx, factory.createJsxJsxClosingFragment())); +} +// The invalid syntax is constructed as +// InvalidJsxTree :: One of +// JsxElement CommaToken InvalidJsxTree +// JsxElement CommaToken JsxElement +function flattenInvalidBinaryExpr(node: Node): JsxChild[] | undefined { + const children: JsxChild[] = []; + let current = node; + while (true) { + if (isBinaryExpression(current) && nodeIsMissing(current.operatorToken) && current.operatorToken.kind === SyntaxKind.CommaToken) { + children.push(current.left as JsxChild); + if (isJsxChild(current.right)) { + children.push(current.right); + // Indicates the tree has go to the bottom + return children; + } + else if (isBinaryExpression(current.right)) { + current = current.right; + continue; } // Unreachable case else return undefined; } + // Unreachable case + else return undefined; } } diff --git a/src/services/completions.ts b/src/services/completions.ts index 9bdc38195da54..a9d7b81dbfe25 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1,4468 +1,4558 @@ -/* @internal */ -namespace ts.Completions { - // Exported only for tests - export const moduleSpecifierResolutionLimit = 100; - export const moduleSpecifierResolutionCacheAttemptLimit = 1000; - - export type Log = (message: string) => void; - - export type SortText = string & { __sortText: any }; - export const SortText = { - // Presets - LocalDeclarationPriority: "10" as SortText, - LocationPriority: "11" as SortText, - OptionalMember: "12" as SortText, - MemberDeclaredBySpreadAssignment: "13" as SortText, - SuggestedClassMembers: "14" as SortText, - GlobalsOrKeywords: "15" as SortText, - AutoImportSuggestions: "16" as SortText, - ClassMemberSnippets: "17" as SortText, - JavascriptIdentifiers: "18" as SortText, - - // Transformations - Deprecated(sortText: SortText): SortText { - return "z" + sortText as SortText; - }, +import { + __String, addToSeen, append, BinaryExpression, BreakOrContinueStatement, CancellationToken, cast, CharacterCodes, + ClassElement, CodeAction, codefix, compareNumberOfDirectorySeparators, compareStringsCaseSensitiveUI, + compareTextSpans, Comparison, CompilerOptions, compilerOptionsIndicateEsModules, CompletionEntry, + CompletionEntryData, CompletionEntryDataAutoImport, CompletionEntryDataResolved, CompletionEntryDataUnresolved, + CompletionEntryDetails, CompletionEntryLabelDetails, CompletionInfo, CompletionInfoFlags, + CompletionsTriggerCharacter, CompletionTriggerKind, concatenate, ConstructorDeclaration, ContextFlags, + createModuleSpecifierResolutionHost, createPackageJsonImportFilter, createPrinter, createSortedArray, + createTextRangeFromSpan, createTextSpanFromBounds, createTextSpanFromNode, createTextSpanFromRange, Debug, + Declaration, Diagnostics, diagnosticToString, displayPart, EmitHint, EmitTextWriter, escapeSnippetText, every, + ExportKind, Expression, factory, filter, find, findAncestor, findChildOfKind, findPrecedingToken, first, + firstDefined, flatMap, formatting, FunctionLikeDeclaration, getAllSuperTypeNodes, getAncestor, + getCombinedLocalAndExportSymbolFlags, getContainingClass, getContextualTypeFromParent, + getDeclarationModifierFlagsFromSymbol, getEffectiveBaseTypeNode, getEffectiveModifierFlags, + getEffectiveTypeAnnotationNode, getEmitModuleResolutionKind, getEmitScriptTarget, + getEscapedTextOfIdentifierOrLiteral, getExportInfoMap, getFormatCodeSettingsForWriting, getLanguageVariant, + getLeftmostAccessExpression, getLineAndCharacterOfPosition, getLineStartPositionForPosition, + getLocalSymbolForExportDefault, getNameForExportedSymbol, getNameOfDeclaration, getNameTable, getNewLineCharacter, + getNewLineKind, getPropertyNameForPropertyNameNode, getQuotePreference, getReplacementSpanForContextToken, + getRootDeclaration, getSourceFileOfModule, getSwitchedType, getSymbolId, getSynthesizedDeepClone, + getTokenAtPosition, getTouchingPropertyName, hasDocComment, hasEffectiveModifier, hasInitializer, hasType, + Identifier, ImportDeclaration, ImportEqualsDeclaration, ImportKind, ImportOrExportSpecifier, ImportSpecifier, + ImportTypeNode, IncompleteCompletionsCache, insertSorted, InternalSymbolName, isAbstractConstructorSymbol, + isArrowFunction, isAssertionExpression, isBigIntLiteral, isBinaryExpression, isBindingElement, isBindingPattern, + isBreakOrContinueStatement, isCallExpression, isCaseClause, isCheckJsEnabledForFile, isClassElement, isClassLike, + isClassMemberModifier, isClassOrTypeElement, isClassStaticBlockDeclaration, isComputedPropertyName, + isConstructorDeclaration, isContextualKeyword, isDeclarationName, isDeprecatedDeclaration, isEntityName, + isEqualityOperatorKind, isExportAssignment, isExportDeclaration, isExpression, isExternalModuleNameRelative, + isExternalModuleReference, isExternalModuleSymbol, isFunctionBlock, isFunctionLike, isFunctionLikeDeclaration, + isFunctionLikeKind, isFunctionTypeNode, isIdentifier, isIdentifierText, isImportableFile, isImportDeclaration, + isImportEqualsDeclaration, isImportKeyword, isImportSpecifier, isInComment, isInitializedProperty, isInJSFile, + isInRightSideOfInternalImportEqualsDeclaration, isInString, isIntersectionTypeNode, isJSDoc, isJSDocParameterTag, + isJSDocTag, isJSDocTemplateTag, isJsxAttribute, isJsxClosingElement, isJsxElement, isJsxExpression, isJsxFragment, + isJsxOpeningLikeElement, isJsxSpreadAttribute, isKeyword, isKnownSymbol, isLabeledStatement, + isLiteralImportTypeNode, isMemberName, isMethodDeclaration, isModifier, isModifierKind, isModuleDeclaration, + isNamedExports, isNamedImports, isNamedImportsOrExports, isNamespaceImport, isObjectBindingPattern, + isObjectLiteralExpression, isObjectTypeDeclaration, isParameter, isParameterPropertyModifier, isPartOfTypeNode, + isPossiblyTypeArgumentPosition, isPrivateIdentifier, isPrivateIdentifierClassElementDeclaration, + isPropertyAccessExpression, isPropertyDeclaration, isPropertyNameLiteral, isRegularExpressionLiteral, + isShorthandPropertyAssignment, isSingleOrDoubleQuote, isSourceFile, isSourceFileJS, isSpreadAssignment, isStatement, + isStatic, isString, isStringANonContextualKeyword, isStringLiteralLike, isStringLiteralOrTemplate, + isStringTextContainingNode, isSyntaxList, isTypeKeyword, isTypeKeywordTokenOrIdentifier, isTypeLiteralNode, + isTypeNode, isTypeOfExpression, isTypeOnlyImportOrExportDeclaration, isTypeReferenceType, + isValidTypeOnlyAliasUseSite, isVariableDeclaration, isVariableLike, JsDoc, JSDocParameterTag, JSDocPropertyTag, + JSDocReturnTag, JSDocTag, JSDocTagInfo, JSDocTemplateTag, JSDocTypedefTag, JSDocTypeExpression, JSDocTypeTag, + JsTyping, JsxAttribute, JsxAttributes, JsxClosingElement, JsxElement, JsxOpeningLikeElement, JsxSpreadAttribute, + LanguageServiceHost, LanguageVariant, last, lastOrUndefined, length, ListFormat, Map, mapDefined, maybeBind, + MemberOverrideStatus, memoize, memoizeOne, MethodDeclaration, ModifierFlags, modifiersToFlags, ModifierSyntaxKind, + modifierToFlag, ModuleDeclaration, ModuleReference, moduleResolutionRespectsExports, NamedImportBindings, Node, + NodeArray, NodeBuilderFlags, NodeFlags, nodeIsMissing, ObjectBindingPattern, ObjectLiteralExpression, ObjectType, + ObjectTypeDeclaration, or, positionBelongsToNode, positionIsASICandidate, positionsAreOnSameLine, PrinterOptions, + probablyUsesSemicolons, Program, programContainsModules, PropertyAccessExpression, PropertyDeclaration, + PropertyName, PseudoBigInt, pseudoBigIntToString, QualifiedName, quote, QuotePreference, rangeContainsPosition, + rangeContainsPositionExclusive, rangeIsOnSingleLine, ScriptElementKind, ScriptElementKindModifier, ScriptTarget, + SemanticMeaning, Set, setSnippetElement, shouldUseUriStyleNodeCoreModules, SignatureHelp, SignatureKind, + singleElementArray, skipAlias, SnippetKind, some, SortedArray, SourceFile, SpreadAssignment, stableSort, startsWith, + stringToToken, stripQuotes, Symbol, SymbolDisplay, SymbolDisplayPart, SymbolDisplayPartKind, SymbolExportInfo, + SymbolFlags, SymbolId, SyntaxKind, TextChange, textChanges, textPart, TextRange, TextSpan, timestamp, Token, + TokenSyntaxKind, tokenToString, tryCast, tryGetImportFromModuleSpecifier, Type, TypeChecker, TypeElement, TypeFlags, + typeHasCallOrConstructSignatures, TypeLiteralNode, TypeOnlyAliasDeclaration, unescapeLeadingUnderscores, + UnionReduction, UnionType, UserPreferences, VariableDeclaration, walkUpParenthesizedExpressions, +} from "./_namespaces/ts"; +import { StringCompletions } from "./_namespaces/ts.Completions"; + +// Exported only for tests +/** @internal */ +export const moduleSpecifierResolutionLimit = 100; +/** @internal */ +export const moduleSpecifierResolutionCacheAttemptLimit = 1000; + +/** @internal */ +export type Log = (message: string) => void; + +/** @internal */ +export type SortText = string & { __sortText: any }; +/** @internal */ +export const SortText = { + // Presets + LocalDeclarationPriority: "10" as SortText, + LocationPriority: "11" as SortText, + OptionalMember: "12" as SortText, + MemberDeclaredBySpreadAssignment: "13" as SortText, + SuggestedClassMembers: "14" as SortText, + GlobalsOrKeywords: "15" as SortText, + AutoImportSuggestions: "16" as SortText, + ClassMemberSnippets: "17" as SortText, + JavascriptIdentifiers: "18" as SortText, + + // Transformations + Deprecated(sortText: SortText): SortText { + return "z" + sortText as SortText; + }, + + ObjectLiteralProperty(presetSortText: SortText, symbolDisplayName: string): SortText { + return `${presetSortText}\0${symbolDisplayName}\0` as SortText; + }, + + SortBelow(sortText: SortText): SortText { + return sortText + "1" as SortText; + }, +}; + +/** + * Special values for `CompletionInfo['source']` used to disambiguate + * completion items with the same `name`. (Each completion item must + * have a unique name/source combination, because those two fields + * comprise `CompletionEntryIdentifier` in `getCompletionEntryDetails`. + * + * When the completion item is an auto-import suggestion, the source + * is the module specifier of the suggestion. To avoid collisions, + * the values here should not be a module specifier we would ever + * generate for an auto-import. + * + * @internal + */ +export enum CompletionSource { + /** Completions that require `this.` insertion text */ + ThisProperty = "ThisProperty/", + /** Auto-import that comes attached to a class member snippet */ + ClassMemberSnippet = "ClassMemberSnippet/", + /** A type-only import that needs to be promoted in order to be used at the completion location */ + TypeOnlyAlias = "TypeOnlyAlias/", + /** Auto-import that comes attached to an object literal method snippet */ + ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/", +} - ObjectLiteralProperty(presetSortText: SortText, symbolDisplayName: string): SortText { - return `${presetSortText}\0${symbolDisplayName}\0` as SortText; - }, +/** @internal */ +export const enum SymbolOriginInfoKind { + ThisType = 1 << 0, + SymbolMember = 1 << 1, + Export = 1 << 2, + Promise = 1 << 3, + Nullable = 1 << 4, + ResolvedExport = 1 << 5, + TypeOnlyAlias = 1 << 6, + ObjectLiteralMethod = 1 << 7, + + SymbolMemberNoExport = SymbolMember, + SymbolMemberExport = SymbolMember | Export, +} - SortBelow(sortText: SortText): SortText { - return sortText + "1" as SortText; - }, - }; +/** @internal */ +export interface SymbolOriginInfo { + kind: SymbolOriginInfoKind; + isDefaultExport?: boolean; + isFromPackageJson?: boolean; + fileName?: string; +} - /** - * Special values for `CompletionInfo['source']` used to disambiguate - * completion items with the same `name`. (Each completion item must - * have a unique name/source combination, because those two fields - * comprise `CompletionEntryIdentifier` in `getCompletionEntryDetails`. - * - * When the completion item is an auto-import suggestion, the source - * is the module specifier of the suggestion. To avoid collisions, - * the values here should not be a module specifier we would ever - * generate for an auto-import. - */ - export enum CompletionSource { - /** Completions that require `this.` insertion text */ - ThisProperty = "ThisProperty/", - /** Auto-import that comes attached to a class member snippet */ - ClassMemberSnippet = "ClassMemberSnippet/", - /** A type-only import that needs to be promoted in order to be used at the completion location */ - TypeOnlyAlias = "TypeOnlyAlias/", - /** Auto-import that comes attached to an object literal method snippet */ - ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/", - } +interface SymbolOriginInfoExport extends SymbolOriginInfo { + symbolName: string; + moduleSymbol: Symbol; + isDefaultExport: boolean; + exportName: string; + exportMapKey: string; +} - const enum SymbolOriginInfoKind { - ThisType = 1 << 0, - SymbolMember = 1 << 1, - Export = 1 << 2, - Promise = 1 << 3, - Nullable = 1 << 4, - ResolvedExport = 1 << 5, - TypeOnlyAlias = 1 << 6, - ObjectLiteralMethod = 1 << 7, +interface SymbolOriginInfoResolvedExport extends SymbolOriginInfo { + symbolName: string; + moduleSymbol: Symbol; + exportName: string; + moduleSpecifier: string; +} - SymbolMemberNoExport = SymbolMember, - SymbolMemberExport = SymbolMember | Export, - } +interface SymbolOriginInfoTypeOnlyAlias extends SymbolOriginInfo { + declaration: TypeOnlyAliasDeclaration; +} - interface SymbolOriginInfo { - kind: SymbolOriginInfoKind; - isDefaultExport?: boolean; - isFromPackageJson?: boolean; - fileName?: string; - } +interface SymbolOriginInfoObjectLiteralMethod extends SymbolOriginInfo { + insertText: string, + labelDetails: CompletionEntryLabelDetails, + isSnippet?: true, +} - interface SymbolOriginInfoExport extends SymbolOriginInfo { - symbolName: string; - moduleSymbol: Symbol; - isDefaultExport: boolean; - exportName: string; - exportMapKey: string; - } +function originIsThisType(origin: SymbolOriginInfo): boolean { + return !!(origin.kind & SymbolOriginInfoKind.ThisType); +} - interface SymbolOriginInfoResolvedExport extends SymbolOriginInfo { - symbolName: string; - moduleSymbol: Symbol; - exportName: string; - moduleSpecifier: string; - } +function originIsSymbolMember(origin: SymbolOriginInfo): boolean { + return !!(origin.kind & SymbolOriginInfoKind.SymbolMember); +} - interface SymbolOriginInfoTypeOnlyAlias extends SymbolOriginInfo { - declaration: TypeOnlyAliasDeclaration; - } +function originIsExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { + return !!(origin && origin.kind & SymbolOriginInfoKind.Export); +} - interface SymbolOriginInfoObjectLiteralMethod extends SymbolOriginInfo { - insertText: string, - labelDetails: CompletionEntryLabelDetails, - isSnippet?: true, - } +function originIsResolvedExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoResolvedExport { + return !!(origin && origin.kind === SymbolOriginInfoKind.ResolvedExport); +} - function originIsThisType(origin: SymbolOriginInfo): boolean { - return !!(origin.kind & SymbolOriginInfoKind.ThisType); - } +function originIncludesSymbolName(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport | SymbolOriginInfoResolvedExport { + return originIsExport(origin) || originIsResolvedExport(origin); +} - function originIsSymbolMember(origin: SymbolOriginInfo): boolean { - return !!(origin.kind & SymbolOriginInfoKind.SymbolMember); - } +function originIsPackageJsonImport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { + return (originIsExport(origin) || originIsResolvedExport(origin)) && !!origin.isFromPackageJson; +} - function originIsExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { - return !!(origin && origin.kind & SymbolOriginInfoKind.Export); - } +function originIsPromise(origin: SymbolOriginInfo): boolean { + return !!(origin.kind & SymbolOriginInfoKind.Promise); +} - function originIsResolvedExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoResolvedExport { - return !!(origin && origin.kind === SymbolOriginInfoKind.ResolvedExport); - } +function originIsNullableMember(origin: SymbolOriginInfo): boolean { + return !!(origin.kind & SymbolOriginInfoKind.Nullable); +} - function originIncludesSymbolName(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport | SymbolOriginInfoResolvedExport { - return originIsExport(origin) || originIsResolvedExport(origin); - } +function originIsTypeOnlyAlias(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoTypeOnlyAlias { + return !!(origin && origin.kind & SymbolOriginInfoKind.TypeOnlyAlias); +} - function originIsPackageJsonImport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { - return (originIsExport(origin) || originIsResolvedExport(origin)) && !!origin.isFromPackageJson; - } +function originIsObjectLiteralMethod(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoObjectLiteralMethod { + return !!(origin && origin.kind & SymbolOriginInfoKind.ObjectLiteralMethod); +} - function originIsPromise(origin: SymbolOriginInfo): boolean { - return !!(origin.kind & SymbolOriginInfoKind.Promise); - } +/** @internal */ +export interface UniqueNameSet { + add(name: string): void; + has(name: string): boolean; +} + +/** + * Map from symbol index in `symbols` -> SymbolOriginInfo. + * + * @internal + */ +export type SymbolOriginInfoMap = Record; + +/** + * Map from symbol id -> SortText. + * + * @internal + */ +export type SymbolSortTextMap = (SortText | undefined)[]; + +const enum KeywordCompletionFilters { + None, // No keywords + All, // Every possible keyword (TODO: This is never appropriate) + ClassElementKeywords, // Keywords inside class body + InterfaceElementKeywords, // Keywords inside interface body + ConstructorParameterKeywords, // Keywords at constructor parameter + FunctionLikeBodyKeywords, // Keywords at function like body + TypeAssertionKeywords, + TypeKeywords, + TypeKeyword, // Literally just `type` + Last = TypeKeyword +} + +const enum GlobalsSearch { Continue, Success, Fail } + +interface ModuleSpecifierResolutioContext { + tryResolve: (exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean) => ModuleSpecifierResolutionResult; + resolvedAny: () => boolean; + skippedAny: () => boolean; + resolvedBeyondLimit: () => boolean; +} + +type ModuleSpecifierResolutionResult = "skipped" | "failed" | { + exportInfo?: SymbolExportInfo; + moduleSpecifier: string; +}; + +function resolvingModuleSpecifiers( + logPrefix: string, + host: LanguageServiceHost, + resolver: codefix.ImportSpecifierResolver, + program: Program, + position: number, + preferences: UserPreferences, + isForImportStatementCompletion: boolean, + isValidTypeOnlyUseSite: boolean, + cb: (context: ModuleSpecifierResolutioContext) => TReturn, +): TReturn { + const start = timestamp(); + // Under `--moduleResolution nodenext`, we have to resolve module specifiers up front, because + // package.json exports can mean we *can't* resolve a module specifier (that doesn't include a + // relative path into node_modules), and we want to filter those completions out entirely. + // Import statement completions always need specifier resolution because the module specifier is + // part of their `insertText`, not the `codeActions` creating edits away from the cursor. + const needsFullResolution = isForImportStatementCompletion || moduleResolutionRespectsExports(getEmitModuleResolutionKind(program.getCompilerOptions())); + let skippedAny = false; + let ambientCount = 0; + let resolvedCount = 0; + let resolvedFromCacheCount = 0; + let cacheAttemptCount = 0; + + const result = cb({ + tryResolve, + skippedAny: () => skippedAny, + resolvedAny: () => resolvedCount > 0, + resolvedBeyondLimit: () => resolvedCount > moduleSpecifierResolutionLimit, + }); + + const hitRateMessage = cacheAttemptCount ? ` (${(resolvedFromCacheCount / cacheAttemptCount * 100).toFixed(1)}% hit rate)` : ""; + host.log?.(`${logPrefix}: resolved ${resolvedCount} module specifiers, plus ${ambientCount} ambient and ${resolvedFromCacheCount} from cache${hitRateMessage}`); + host.log?.(`${logPrefix}: response is ${skippedAny ? "incomplete" : "complete"}`); + host.log?.(`${logPrefix}: ${timestamp() - start}`); + return result; + + function tryResolve(exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean): ModuleSpecifierResolutionResult { + if (isFromAmbientModule) { + const result = resolver.getModuleSpecifierForBestExportInfo(exportInfo, symbolName, position, isValidTypeOnlyUseSite); + if (result) { + ambientCount++; + } + return result || "failed"; + } + const shouldResolveModuleSpecifier = needsFullResolution || preferences.allowIncompleteCompletions && resolvedCount < moduleSpecifierResolutionLimit; + const shouldGetModuleSpecifierFromCache = !shouldResolveModuleSpecifier && preferences.allowIncompleteCompletions && cacheAttemptCount < moduleSpecifierResolutionCacheAttemptLimit; + const result = (shouldResolveModuleSpecifier || shouldGetModuleSpecifierFromCache) + ? resolver.getModuleSpecifierForBestExportInfo(exportInfo, symbolName, position, isValidTypeOnlyUseSite, shouldGetModuleSpecifierFromCache) + : undefined; + + if (!shouldResolveModuleSpecifier && !shouldGetModuleSpecifierFromCache || shouldGetModuleSpecifierFromCache && !result) { + skippedAny = true; + } + + resolvedCount += result?.computedWithoutCacheCount || 0; + resolvedFromCacheCount += exportInfo.length - (result?.computedWithoutCacheCount || 0); + if (shouldGetModuleSpecifierFromCache) { + cacheAttemptCount++; + } - function originIsNullableMember(origin: SymbolOriginInfo): boolean { - return !!(origin.kind & SymbolOriginInfoKind.Nullable); + return result || (needsFullResolution ? "failed" : "skipped"); } +} - function originIsTypeOnlyAlias(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoTypeOnlyAlias { - return !!(origin && origin.kind & SymbolOriginInfoKind.TypeOnlyAlias); +/** @internal */ +export function getCompletionsAtPosition( + host: LanguageServiceHost, + program: Program, + log: Log, + sourceFile: SourceFile, + position: number, + preferences: UserPreferences, + triggerCharacter: CompletionsTriggerCharacter | undefined, + completionKind: CompletionTriggerKind | undefined, + cancellationToken: CancellationToken, + formatContext?: formatting.FormatContext, +): CompletionInfo | undefined { + const { previousToken } = getRelevantTokens(position, sourceFile); + if (triggerCharacter && !isInString(sourceFile, position, previousToken) && !isValidTrigger(sourceFile, triggerCharacter, previousToken, position)) { + return undefined; } - function originIsObjectLiteralMethod(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoObjectLiteralMethod { - return !!(origin && origin.kind & SymbolOriginInfoKind.ObjectLiteralMethod); + if (triggerCharacter === " ") { + // `isValidTrigger` ensures we are at `import |` + if (preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) { + return { isGlobalCompletion: true, isMemberCompletion: false, isNewIdentifierLocation: true, isIncomplete: true, entries: [] }; + } + return undefined; + } - interface UniqueNameSet { - add(name: string): void; - has(name: string): boolean; + // If the request is a continuation of an earlier `isIncomplete` response, + // we can continue it from the cached previous response. + const compilerOptions = program.getCompilerOptions(); + const incompleteCompletionsCache = preferences.allowIncompleteCompletions ? host.getIncompleteCompletionsCache?.() : undefined; + if (incompleteCompletionsCache && completionKind === CompletionTriggerKind.TriggerForIncompleteCompletions && previousToken && isIdentifier(previousToken)) { + const incompleteContinuation = continuePreviousIncompleteResponse(incompleteCompletionsCache, sourceFile, previousToken, program, host, preferences, cancellationToken); + if (incompleteContinuation) { + return incompleteContinuation; + } + } + else { + incompleteCompletionsCache?.clear(); } - /** - * Map from symbol index in `symbols` -> SymbolOriginInfo. - */ - type SymbolOriginInfoMap = Record; + const stringCompletions = StringCompletions.getStringLiteralCompletions(sourceFile, position, previousToken, compilerOptions, host, program, log, preferences); + if (stringCompletions) { + return stringCompletions; + } - /** Map from symbol id -> SortText. */ - type SymbolSortTextMap = (SortText | undefined)[]; + if (previousToken && isBreakOrContinueStatement(previousToken.parent) + && (previousToken.kind === SyntaxKind.BreakKeyword || previousToken.kind === SyntaxKind.ContinueKeyword || previousToken.kind === SyntaxKind.Identifier)) { + return getLabelCompletionAtPosition(previousToken.parent); + } - const enum KeywordCompletionFilters { - None, // No keywords - All, // Every possible keyword (TODO: This is never appropriate) - ClassElementKeywords, // Keywords inside class body - InterfaceElementKeywords, // Keywords inside interface body - ConstructorParameterKeywords, // Keywords at constructor parameter - FunctionLikeBodyKeywords, // Keywords at function like body - TypeAssertionKeywords, - TypeKeywords, - TypeKeyword, // Literally just `type` - Last = TypeKeyword + const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, preferences, /*detailsEntryId*/ undefined, host, formatContext, cancellationToken); + if (!completionData) { + return undefined; } - const enum GlobalsSearch { Continue, Success, Fail } + switch (completionData.kind) { + case CompletionDataKind.Data: + const response = completionInfoFromData(sourceFile, host, program, compilerOptions, log, completionData, preferences, formatContext, position); + if (response?.isIncomplete) { + incompleteCompletionsCache?.set(response); + } + return response; + case CompletionDataKind.JsDocTagName: + // If the current position is a jsDoc tag name, only tag names should be provided for completion + return jsdocCompletionInfo(JsDoc.getJSDocTagNameCompletions()); + case CompletionDataKind.JsDocTag: + // If the current position is a jsDoc tag, only tags should be provided for completion + return jsdocCompletionInfo(JsDoc.getJSDocTagCompletions()); + case CompletionDataKind.JsDocParameterName: + return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag)); + case CompletionDataKind.Keywords: + return specificKeywordCompletionInfo(completionData.keywordCompletions, completionData.isNewIdentifierLocation); + default: + return Debug.assertNever(completionData); + } +} - interface ModuleSpecifierResolutioContext { - tryResolve: (exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean) => ModuleSpecifierResolutionResult; - resolvedAny: () => boolean; - skippedAny: () => boolean; - resolvedBeyondLimit: () => boolean; +// Editors will use the `sortText` and then fall back to `name` for sorting, but leave ties in response order. +// So, it's important that we sort those ties in the order we want them displayed if it matters. We don't +// strictly need to sort by name or SortText here since clients are going to do it anyway, but we have to +// do the work of comparing them so we can sort those ties appropriately; plus, it makes the order returned +// by the language service consistent with what TS Server does and what editors typically do. This also makes +// completions tests make more sense. We used to sort only alphabetically and only in the server layer, but +// this made tests really weird, since most fourslash tests don't use the server. +function compareCompletionEntries(entryInArray: CompletionEntry, entryToInsert: CompletionEntry): Comparison { + let result = compareStringsCaseSensitiveUI(entryInArray.sortText, entryToInsert.sortText); + if (result === Comparison.EqualTo) { + result = compareStringsCaseSensitiveUI(entryInArray.name, entryToInsert.name); } + if (result === Comparison.EqualTo && entryInArray.data?.moduleSpecifier && entryToInsert.data?.moduleSpecifier) { + // Sort same-named auto-imports by module specifier + result = compareNumberOfDirectorySeparators( + (entryInArray.data as CompletionEntryDataResolved).moduleSpecifier, + (entryToInsert.data as CompletionEntryDataResolved).moduleSpecifier, + ); + } + if (result === Comparison.EqualTo) { + // Fall back to symbol order - if we return `EqualTo`, `insertSorted` will put later symbols first. + return Comparison.LessThan; + } + return result; +} - type ModuleSpecifierResolutionResult = "skipped" | "failed" | { - exportInfo?: SymbolExportInfo; - moduleSpecifier: string; - }; +function completionEntryDataIsResolved(data: CompletionEntryDataAutoImport | undefined): data is CompletionEntryDataResolved { + return !!data?.moduleSpecifier; +} - function resolvingModuleSpecifiers( - logPrefix: string, - host: LanguageServiceHost, - resolver: codefix.ImportSpecifierResolver, - program: Program, - position: number, - preferences: UserPreferences, - isForImportStatementCompletion: boolean, - isValidTypeOnlyUseSite: boolean, - cb: (context: ModuleSpecifierResolutioContext) => TReturn, - ): TReturn { - const start = timestamp(); - // Under `--moduleResolution nodenext`, we have to resolve module specifiers up front, because - // package.json exports can mean we *can't* resolve a module specifier (that doesn't include a - // relative path into node_modules), and we want to filter those completions out entirely. - // Import statement completions always need specifier resolution because the module specifier is - // part of their `insertText`, not the `codeActions` creating edits away from the cursor. - const needsFullResolution = isForImportStatementCompletion || moduleResolutionRespectsExports(getEmitModuleResolutionKind(program.getCompilerOptions())); - let skippedAny = false; - let ambientCount = 0; - let resolvedCount = 0; - let resolvedFromCacheCount = 0; - let cacheAttemptCount = 0; - - const result = cb({ - tryResolve, - skippedAny: () => skippedAny, - resolvedAny: () => resolvedCount > 0, - resolvedBeyondLimit: () => resolvedCount > moduleSpecifierResolutionLimit, - }); +function continuePreviousIncompleteResponse( + cache: IncompleteCompletionsCache, + file: SourceFile, + location: Identifier, + program: Program, + host: LanguageServiceHost, + preferences: UserPreferences, + cancellationToken: CancellationToken, +): CompletionInfo | undefined { + const previousResponse = cache.get(); + if (!previousResponse) return undefined; + + const lowerCaseTokenText = location.text.toLowerCase(); + const exportMap = getExportInfoMap(file, host, program, preferences, cancellationToken); + const newEntries = resolvingModuleSpecifiers( + "continuePreviousIncompleteResponse", + host, + codefix.createImportSpecifierResolver(file, program, host, preferences), + program, + location.getStart(), + preferences, + /*isForImportStatementCompletion*/ false, + isValidTypeOnlyAliasUseSite(location), + context => { + const entries = mapDefined(previousResponse.entries, entry => { + if (!entry.hasAction || !entry.source || !entry.data || completionEntryDataIsResolved(entry.data)) { + // Not an auto import or already resolved; keep as is + return entry; + } + if (!charactersFuzzyMatchInString(entry.name, lowerCaseTokenText)) { + // No longer matches typed characters; filter out + return undefined; + } - const hitRateMessage = cacheAttemptCount ? ` (${(resolvedFromCacheCount / cacheAttemptCount * 100).toFixed(1)}% hit rate)` : ""; - host.log?.(`${logPrefix}: resolved ${resolvedCount} module specifiers, plus ${ambientCount} ambient and ${resolvedFromCacheCount} from cache${hitRateMessage}`); - host.log?.(`${logPrefix}: response is ${skippedAny ? "incomplete" : "complete"}`); - host.log?.(`${logPrefix}: ${timestamp() - start}`); - return result; + const { origin } = Debug.checkDefined(getAutoImportSymbolFromCompletionEntryData(entry.name, entry.data, program, host)); + const info = exportMap.get(file.path, entry.data.exportMapKey); - function tryResolve(exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean): ModuleSpecifierResolutionResult { - if (isFromAmbientModule) { - const result = resolver.getModuleSpecifierForBestExportInfo(exportInfo, symbolName, position, isValidTypeOnlyUseSite); - if (result) { - ambientCount++; + const result = info && context.tryResolve(info, entry.name, !isExternalModuleNameRelative(stripQuotes(origin.moduleSymbol.name))); + if (result === "skipped") return entry; + if (!result || result === "failed") { + host.log?.(`Unexpected failure resolving auto import for '${entry.name}' from '${entry.source}'`); + return undefined; } - return result || "failed"; - } - const shouldResolveModuleSpecifier = needsFullResolution || preferences.allowIncompleteCompletions && resolvedCount < moduleSpecifierResolutionLimit; - const shouldGetModuleSpecifierFromCache = !shouldResolveModuleSpecifier && preferences.allowIncompleteCompletions && cacheAttemptCount < moduleSpecifierResolutionCacheAttemptLimit; - const result = (shouldResolveModuleSpecifier || shouldGetModuleSpecifierFromCache) - ? resolver.getModuleSpecifierForBestExportInfo(exportInfo, symbolName, position, isValidTypeOnlyUseSite, shouldGetModuleSpecifierFromCache) - : undefined; - if (!shouldResolveModuleSpecifier && !shouldGetModuleSpecifierFromCache || shouldGetModuleSpecifierFromCache && !result) { - skippedAny = true; - } + const newOrigin: SymbolOriginInfoResolvedExport = { + ...origin, + kind: SymbolOriginInfoKind.ResolvedExport, + moduleSpecifier: result.moduleSpecifier, + }; + // Mutating for performance... feels sketchy but nobody else uses the cache, + // so why bother allocating a bunch of new objects? + entry.data = originToCompletionEntryData(newOrigin); + entry.source = getSourceFromOrigin(newOrigin); + entry.sourceDisplay = [textPart(newOrigin.moduleSpecifier)]; + return entry; + }); - resolvedCount += result?.computedWithoutCacheCount || 0; - resolvedFromCacheCount += exportInfo.length - (result?.computedWithoutCacheCount || 0); - if (shouldGetModuleSpecifierFromCache) { - cacheAttemptCount++; + if (!context.skippedAny()) { + previousResponse.isIncomplete = undefined; } - return result || (needsFullResolution ? "failed" : "skipped"); - } - } + return entries; + }, + ); - export function getCompletionsAtPosition( - host: LanguageServiceHost, - program: Program, - log: Log, - sourceFile: SourceFile, - position: number, - preferences: UserPreferences, - triggerCharacter: CompletionsTriggerCharacter | undefined, - completionKind: CompletionTriggerKind | undefined, - cancellationToken: CancellationToken, - formatContext?: formatting.FormatContext, - ): CompletionInfo | undefined { - const { previousToken } = getRelevantTokens(position, sourceFile); - if (triggerCharacter && !isInString(sourceFile, position, previousToken) && !isValidTrigger(sourceFile, triggerCharacter, previousToken, position)) { - return undefined; - } + previousResponse.entries = newEntries; + previousResponse.flags = (previousResponse.flags || 0) | CompletionInfoFlags.IsContinuation; + return previousResponse; +} - if (triggerCharacter === " ") { - // `isValidTrigger` ensures we are at `import |` - if (preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) { - return { isGlobalCompletion: true, isMemberCompletion: false, isNewIdentifierLocation: true, isIncomplete: true, entries: [] }; - } - return undefined; +function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo { + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; +} - } +function keywordToCompletionEntry(keyword: TokenSyntaxKind) { + return { + name: tokenToString(keyword)!, + kind: ScriptElementKind.keyword, + kindModifiers: ScriptElementKindModifier.none, + sortText: SortText.GlobalsOrKeywords, + }; +} - // If the request is a continuation of an earlier `isIncomplete` response, - // we can continue it from the cached previous response. - const compilerOptions = program.getCompilerOptions(); - const incompleteCompletionsCache = preferences.allowIncompleteCompletions ? host.getIncompleteCompletionsCache?.() : undefined; - if (incompleteCompletionsCache && completionKind === CompletionTriggerKind.TriggerForIncompleteCompletions && previousToken && isIdentifier(previousToken)) { - const incompleteContinuation = continuePreviousIncompleteResponse(incompleteCompletionsCache, sourceFile, previousToken, program, host, preferences, cancellationToken); - if (incompleteContinuation) { - return incompleteContinuation; - } - } - else { - incompleteCompletionsCache?.clear(); - } +function specificKeywordCompletionInfo(entries: readonly CompletionEntry[], isNewIdentifierLocation: boolean): CompletionInfo { + return { + isGlobalCompletion: false, + isMemberCompletion: false, + isNewIdentifierLocation, + entries: entries.slice(), + }; +} - const stringCompletions = StringCompletions.getStringLiteralCompletions(sourceFile, position, previousToken, compilerOptions, host, program, log, preferences); - if (stringCompletions) { - return stringCompletions; - } +function keywordCompletionData(keywordFilters: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean, isNewIdentifierLocation: boolean): Request { + return { + kind: CompletionDataKind.Keywords, + keywordCompletions: getKeywordCompletions(keywordFilters, filterOutTsOnlyKeywords), + isNewIdentifierLocation, + }; +} - if (previousToken && isBreakOrContinueStatement(previousToken.parent) - && (previousToken.kind === SyntaxKind.BreakKeyword || previousToken.kind === SyntaxKind.ContinueKeyword || previousToken.kind === SyntaxKind.Identifier)) { - return getLabelCompletionAtPosition(previousToken.parent); - } +function keywordFiltersFromSyntaxKind(keywordCompletion: TokenSyntaxKind): KeywordCompletionFilters { + switch (keywordCompletion) { + case SyntaxKind.TypeKeyword: return KeywordCompletionFilters.TypeKeyword; + default: Debug.fail("Unknown mapping from SyntaxKind to KeywordCompletionFilters"); + } +} - const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, preferences, /*detailsEntryId*/ undefined, host, formatContext, cancellationToken); - if (!completionData) { - return undefined; - } +function getOptionalReplacementSpan(location: Node | undefined) { + // StringLiteralLike locations are handled separately in stringCompletions.ts + return location?.kind === SyntaxKind.Identifier ? createTextSpanFromNode(location) : undefined; +} - switch (completionData.kind) { - case CompletionDataKind.Data: - const response = completionInfoFromData(sourceFile, host, program, compilerOptions, log, completionData, preferences, formatContext, position); - if (response?.isIncomplete) { - incompleteCompletionsCache?.set(response); - } - return response; - case CompletionDataKind.JsDocTagName: - // If the current position is a jsDoc tag name, only tag names should be provided for completion - return jsdocCompletionInfo(JsDoc.getJSDocTagNameCompletions()); - case CompletionDataKind.JsDocTag: - // If the current position is a jsDoc tag, only tags should be provided for completion - return jsdocCompletionInfo(JsDoc.getJSDocTagCompletions()); - case CompletionDataKind.JsDocParameterName: - return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag)); - case CompletionDataKind.Keywords: - return specificKeywordCompletionInfo(completionData.keywordCompletions, completionData.isNewIdentifierLocation); - default: - return Debug.assertNever(completionData); +function completionInfoFromData( + sourceFile: SourceFile, + host: LanguageServiceHost, + program: Program, + compilerOptions: CompilerOptions, + log: Log, + completionData: CompletionData, + preferences: UserPreferences, + formatContext: formatting.FormatContext | undefined, + position: number +): CompletionInfo | undefined { + const { + symbols, + contextToken, + completionKind, + isInSnippetScope, + isNewIdentifierLocation, + location, + propertyAccessToConvert, + keywordFilters, + literals, + symbolToOriginInfoMap, + recommendedCompletion, + isJsxInitializer, + isTypeOnlyLocation, + isJsxIdentifierExpected, + isRightOfOpenTag, + importStatementCompletion, + insideJsDocTagTypeExpression, + symbolToSortTextMap: symbolToSortTextMap, + hasUnresolvedAutoImports, + } = completionData; + + // Verify if the file is JSX language variant + if (getLanguageVariant(sourceFile.scriptKind) === LanguageVariant.JSX) { + const completionInfo = getJsxClosingTagCompletion(location, sourceFile); + if (completionInfo) { + return completionInfo; } } - // Editors will use the `sortText` and then fall back to `name` for sorting, but leave ties in response order. - // So, it's important that we sort those ties in the order we want them displayed if it matters. We don't - // strictly need to sort by name or SortText here since clients are going to do it anyway, but we have to - // do the work of comparing them so we can sort those ties appropriately; plus, it makes the order returned - // by the language service consistent with what TS Server does and what editors typically do. This also makes - // completions tests make more sense. We used to sort only alphabetically and only in the server layer, but - // this made tests really weird, since most fourslash tests don't use the server. - function compareCompletionEntries(entryInArray: CompletionEntry, entryToInsert: CompletionEntry): Comparison { - let result = compareStringsCaseSensitiveUI(entryInArray.sortText, entryToInsert.sortText); - if (result === Comparison.EqualTo) { - result = compareStringsCaseSensitiveUI(entryInArray.name, entryToInsert.name); - } - if (result === Comparison.EqualTo && entryInArray.data?.moduleSpecifier && entryToInsert.data?.moduleSpecifier) { - // Sort same-named auto-imports by module specifier - result = compareNumberOfDirectorySeparators( - (entryInArray.data as CompletionEntryDataResolved).moduleSpecifier, - (entryToInsert.data as CompletionEntryDataResolved).moduleSpecifier, - ); + const entries = createSortedArray(); + const isChecked = isCheckedFile(sourceFile, compilerOptions); + if (isChecked && !isNewIdentifierLocation && (!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) { + return undefined; + } + const uniqueNames = getCompletionEntriesFromSymbols( + symbols, + entries, + /*replacementToken*/ undefined, + contextToken, + location, + sourceFile, + host, + program, + getEmitScriptTarget(compilerOptions), + log, + completionKind, + preferences, + compilerOptions, + formatContext, + isTypeOnlyLocation, + propertyAccessToConvert, + isJsxIdentifierExpected, + isJsxInitializer, + importStatementCompletion, + recommendedCompletion, + symbolToOriginInfoMap, + symbolToSortTextMap, + isJsxIdentifierExpected, + isRightOfOpenTag, + ); + + if (keywordFilters !== KeywordCompletionFilters.None) { + for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) { + if (isTypeOnlyLocation && isTypeKeyword(stringToToken(keywordEntry.name)!) || !uniqueNames.has(keywordEntry.name)) { + uniqueNames.add(keywordEntry.name); + insertSorted(entries, keywordEntry, compareCompletionEntries, /*allowDuplicates*/ true); + } } - if (result === Comparison.EqualTo) { - // Fall back to symbol order - if we return `EqualTo`, `insertSorted` will put later symbols first. - return Comparison.LessThan; + } + + for (const keywordEntry of getContextualKeywords(contextToken, position)) { + if (!uniqueNames.has(keywordEntry.name)) { + uniqueNames.add(keywordEntry.name); + insertSorted(entries, keywordEntry, compareCompletionEntries, /*allowDuplicates*/ true); } - return result; } - function completionEntryDataIsResolved(data: CompletionEntryDataAutoImport | undefined): data is CompletionEntryDataResolved { - return !!data?.moduleSpecifier; + for (const literal of literals) { + const literalEntry = createCompletionEntryForLiteral(sourceFile, preferences, literal); + uniqueNames.add(literalEntry.name); + insertSorted(entries, literalEntry, compareCompletionEntries, /*allowDuplicates*/ true); } - function continuePreviousIncompleteResponse( - cache: IncompleteCompletionsCache, - file: SourceFile, - location: Identifier, - program: Program, - host: LanguageServiceHost, - preferences: UserPreferences, - cancellationToken: CancellationToken, - ): CompletionInfo | undefined { - const previousResponse = cache.get(); - if (!previousResponse) return undefined; + if (!isChecked) { + getJSCompletionEntries(sourceFile, location.pos, uniqueNames, getEmitScriptTarget(compilerOptions), entries); + } - const lowerCaseTokenText = location.text.toLowerCase(); - const exportMap = getExportInfoMap(file, host, program, preferences, cancellationToken); - const newEntries = resolvingModuleSpecifiers( - "continuePreviousIncompleteResponse", - host, - codefix.createImportSpecifierResolver(file, program, host, preferences), - program, - location.getStart(), - preferences, - /*isForImportStatementCompletion*/ false, - isValidTypeOnlyAliasUseSite(location), - context => { - const entries = mapDefined(previousResponse.entries, entry => { - if (!entry.hasAction || !entry.source || !entry.data || completionEntryDataIsResolved(entry.data)) { - // Not an auto import or already resolved; keep as is - return entry; - } - if (!charactersFuzzyMatchInString(entry.name, lowerCaseTokenText)) { - // No longer matches typed characters; filter out - return undefined; - } + return { + flags: completionData.flags, + isGlobalCompletion: isInSnippetScope, + isIncomplete: preferences.allowIncompleteCompletions && hasUnresolvedAutoImports ? true : undefined, + isMemberCompletion: isMemberCompletionKind(completionKind), + isNewIdentifierLocation, + optionalReplacementSpan: getOptionalReplacementSpan(location), + entries, + }; +} - const { origin } = Debug.checkDefined(getAutoImportSymbolFromCompletionEntryData(entry.name, entry.data, program, host)); - const info = exportMap.get(file.path, entry.data.exportMapKey); +function isCheckedFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean { + return !isSourceFileJS(sourceFile) || !!isCheckJsEnabledForFile(sourceFile, compilerOptions); +} - const result = info && context.tryResolve(info, entry.name, !isExternalModuleNameRelative(stripQuotes(origin.moduleSymbol.name))); - if (result === "skipped") return entry; - if (!result || result === "failed") { - host.log?.(`Unexpected failure resolving auto import for '${entry.name}' from '${entry.source}'`); - return undefined; - } +function isMemberCompletionKind(kind: CompletionKind): boolean { + switch (kind) { + case CompletionKind.ObjectPropertyDeclaration: + case CompletionKind.MemberLike: + case CompletionKind.PropertyAccess: + return true; + default: + return false; + } +} - const newOrigin: SymbolOriginInfoResolvedExport = { - ...origin, - kind: SymbolOriginInfoKind.ResolvedExport, - moduleSpecifier: result.moduleSpecifier, - }; - // Mutating for performance... feels sketchy but nobody else uses the cache, - // so why bother allocating a bunch of new objects? - entry.data = originToCompletionEntryData(newOrigin); - entry.source = getSourceFromOrigin(newOrigin); - entry.sourceDisplay = [textPart(newOrigin.moduleSpecifier)]; - return entry; - }); +function getJsxClosingTagCompletion(location: Node | undefined, sourceFile: SourceFile): CompletionInfo | undefined { + // We wanna walk up the tree till we find a JSX closing element + const jsxClosingElement = findAncestor(location, node => { + switch (node.kind) { + case SyntaxKind.JsxClosingElement: + return true; + case SyntaxKind.SlashToken: + case SyntaxKind.GreaterThanToken: + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: + return false; + default: + return "quit"; + } + }) as JsxClosingElement | undefined; + + if (jsxClosingElement) { + // In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag, + // instead of simply giving unknown value, the completion will return the tag-name of an associated opening-element. + // For example: + // var x =
" with type any + // And at `
` (with a closing `>`), the completion list will contain "div". + // And at property access expressions ` ` the completion will + // return full closing tag with an optional replacement span + // For example: + // var x = + // var y = + // the completion list at "1" and "2" will contain "MainComponent.Child" with a replacement span of closing tag name + const hasClosingAngleBracket = !!findChildOfKind(jsxClosingElement, SyntaxKind.GreaterThanToken, sourceFile); + const tagName = jsxClosingElement.parent.openingElement.tagName; + const closingTag = tagName.getText(sourceFile); + const fullClosingTag = closingTag + (hasClosingAngleBracket ? "" : ">"); + const replacementSpan = createTextSpanFromNode(jsxClosingElement.tagName); + + const entry: CompletionEntry = { + name: fullClosingTag, + kind: ScriptElementKind.classElement, + kindModifiers: undefined, + sortText: SortText.LocationPriority, + }; + return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, optionalReplacementSpan: replacementSpan, entries: [entry] }; + } + return; +} - if (!context.skippedAny()) { - previousResponse.isIncomplete = undefined; - } +function getJSCompletionEntries( + sourceFile: SourceFile, + position: number, + uniqueNames: UniqueNameSet, + target: ScriptTarget, + entries: SortedArray): void { + getNameTable(sourceFile).forEach((pos, name) => { + // Skip identifiers produced only from the current location + if (pos === position) { + return; + } + const realName = unescapeLeadingUnderscores(name); + if (!uniqueNames.has(realName) && isIdentifierText(realName, target)) { + uniqueNames.add(realName); + insertSorted(entries, { + name: realName, + kind: ScriptElementKind.warning, + kindModifiers: "", + sortText: SortText.JavascriptIdentifiers, + isFromUncheckedFile: true + }, compareCompletionEntries); + } + }); +} - return entries; - }, - ); +function completionNameForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): string { + return typeof literal === "object" ? pseudoBigIntToString(literal) + "n" : + isString(literal) ? quote(sourceFile, preferences, literal) : JSON.stringify(literal); +} - previousResponse.entries = newEntries; - previousResponse.flags = (previousResponse.flags || 0) | CompletionInfoFlags.IsContinuation; - return previousResponse; - } +function createCompletionEntryForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): CompletionEntry { + return { name: completionNameForLiteral(sourceFile, preferences, literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority }; +} - function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo { - return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; +function createCompletionEntry( + symbol: Symbol, + sortText: SortText, + replacementToken: Node | undefined, + contextToken: Node | undefined, + location: Node, + sourceFile: SourceFile, + host: LanguageServiceHost, + program: Program, + name: string, + needsConvertPropertyAccess: boolean, + origin: SymbolOriginInfo | undefined, + recommendedCompletion: Symbol | undefined, + propertyAccessToConvert: PropertyAccessExpression | undefined, + isJsxInitializer: IsJsxInitializer | undefined, + importStatementCompletion: ImportStatementCompletionInfo | undefined, + useSemicolons: boolean, + options: CompilerOptions, + preferences: UserPreferences, + completionKind: CompletionKind, + formatContext: formatting.FormatContext | undefined, + isJsxIdentifierExpected: boolean | undefined, + isRightOfOpenTag: boolean | undefined, +): CompletionEntry | undefined { + let insertText: string | undefined; + let replacementSpan = getReplacementSpanForContextToken(replacementToken); + let data: CompletionEntryData | undefined; + let isSnippet: true | undefined; + let source = getSourceFromOrigin(origin); + let sourceDisplay; + let hasAction; + let labelDetails; + + const typeChecker = program.getTypeChecker(); + const insertQuestionDot = origin && originIsNullableMember(origin); + const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; + if (origin && originIsThisType(origin)) { + insertText = needsConvertPropertyAccess + ? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(sourceFile, preferences, name)}]` + : `this${insertQuestionDot ? "?." : "."}${name}`; } + // We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790. + // Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro. + else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) { + insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(sourceFile, preferences, name)}]` : `[${name}]` : name; + if (insertQuestionDot || propertyAccessToConvert.questionDotToken) { + insertText = `?.${insertText}`; + } - function keywordToCompletionEntry(keyword: TokenSyntaxKind) { - return { - name: tokenToString(keyword)!, - kind: ScriptElementKind.keyword, - kindModifiers: ScriptElementKindModifier.none, - sortText: SortText.GlobalsOrKeywords, - }; + const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile) || + findChildOfKind(propertyAccessToConvert, SyntaxKind.QuestionDotToken, sourceFile); + if (!dot) { + return undefined; + } + // If the text after the '.' starts with this name, write over it. Else, add new text. + const end = startsWith(name, propertyAccessToConvert.name.text) ? propertyAccessToConvert.name.end : dot.end; + replacementSpan = createTextSpanFromBounds(dot.getStart(sourceFile), end); } - function specificKeywordCompletionInfo(entries: readonly CompletionEntry[], isNewIdentifierLocation: boolean): CompletionInfo { - return { - isGlobalCompletion: false, - isMemberCompletion: false, - isNewIdentifierLocation, - entries: entries.slice(), - }; + if (isJsxInitializer) { + if (insertText === undefined) insertText = name; + insertText = `{${insertText}}`; + if (typeof isJsxInitializer !== "boolean") { + replacementSpan = createTextSpanFromNode(isJsxInitializer, sourceFile); + } } + if (origin && originIsPromise(origin) && propertyAccessToConvert) { + if (insertText === undefined) insertText = name; + const precedingToken = findPrecedingToken(propertyAccessToConvert.pos, sourceFile); + let awaitText = ""; + if (precedingToken && positionIsASICandidate(precedingToken.end, precedingToken.parent, sourceFile)) { + awaitText = ";"; + } - function keywordCompletionData(keywordFilters: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean, isNewIdentifierLocation: boolean): Request { - return { - kind: CompletionDataKind.Keywords, - keywordCompletions: getKeywordCompletions(keywordFilters, filterOutTsOnlyKeywords), - isNewIdentifierLocation, - }; + awaitText += `(await ${propertyAccessToConvert.expression.getText()})`; + insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}${insertQuestionDot ? "?." : "."}${insertText}`; + replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end); } - function keywordFiltersFromSyntaxKind(keywordCompletion: TokenSyntaxKind): KeywordCompletionFilters { - switch (keywordCompletion) { - case SyntaxKind.TypeKeyword: return KeywordCompletionFilters.TypeKeyword; - default: Debug.fail("Unknown mapping from SyntaxKind to KeywordCompletionFilters"); + if (originIsResolvedExport(origin)) { + sourceDisplay = [textPart(origin.moduleSpecifier)]; + if (importStatementCompletion) { + ({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importStatementCompletion, origin, useSemicolons, sourceFile, options, preferences)); + isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined; } } - function getOptionalReplacementSpan(location: Node | undefined) { - // StringLiteralLike locations are handled separately in stringCompletions.ts - return location?.kind === SyntaxKind.Identifier ? createTextSpanFromNode(location) : undefined; + if (origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias) { + hasAction = true; } - function completionInfoFromData( - sourceFile: SourceFile, - host: LanguageServiceHost, - program: Program, - compilerOptions: CompilerOptions, - log: Log, - completionData: CompletionData, - preferences: UserPreferences, - formatContext: formatting.FormatContext | undefined, - position: number - ): CompletionInfo | undefined { - const { - symbols, - contextToken, - completionKind, - isInSnippetScope, - isNewIdentifierLocation, - location, - propertyAccessToConvert, - keywordFilters, - literals, - symbolToOriginInfoMap, - recommendedCompletion, - isJsxInitializer, - isTypeOnlyLocation, - isJsxIdentifierExpected, - isRightOfOpenTag, - importStatementCompletion, - insideJsDocTagTypeExpression, - symbolToSortTextMap: symbolToSortTextMap, - hasUnresolvedAutoImports, - } = completionData; - - // Verify if the file is JSX language variant - if (getLanguageVariant(sourceFile.scriptKind) === LanguageVariant.JSX) { - const completionInfo = getJsxClosingTagCompletion(location, sourceFile); - if (completionInfo) { - return completionInfo; - } + if (preferences.includeCompletionsWithClassMemberSnippets && + preferences.includeCompletionsWithInsertText && + completionKind === CompletionKind.MemberLike && + isClassLikeMemberCompletion(symbol, location, sourceFile)) { + let importAdder; + ({ insertText, isSnippet, importAdder, replacementSpan } = getEntryForMemberCompletion(host, program, options, preferences, name, symbol, location, contextToken, formatContext)); + sortText = SortText.ClassMemberSnippets; // sortText has to be lower priority than the sortText for keywords. See #47852. + if (importAdder?.hasFixes()) { + hasAction = true; + source = CompletionSource.ClassMemberSnippet; } + } - const entries = createSortedArray(); - const isChecked = isCheckedFile(sourceFile, compilerOptions); - if (isChecked && !isNewIdentifierLocation && (!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) { - return undefined; + if (origin && originIsObjectLiteralMethod(origin)) { + ({ insertText, isSnippet, labelDetails } = origin); + if (!preferences.useLabelDetailsInCompletionEntries) { + name = name + labelDetails.detail; + labelDetails = undefined; } - const uniqueNames = getCompletionEntriesFromSymbols( - symbols, - entries, - /*replacementToken*/ undefined, - contextToken, - location, - sourceFile, - host, - program, - getEmitScriptTarget(compilerOptions), - log, - completionKind, - preferences, - compilerOptions, - formatContext, - isTypeOnlyLocation, - propertyAccessToConvert, - isJsxIdentifierExpected, - isJsxInitializer, - importStatementCompletion, - recommendedCompletion, - symbolToOriginInfoMap, - symbolToSortTextMap, - isJsxIdentifierExpected, - isRightOfOpenTag, - ); + source = CompletionSource.ObjectLiteralMethodSnippet; + sortText = SortText.SortBelow(sortText); + } - if (keywordFilters !== KeywordCompletionFilters.None) { - for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) { - if (isTypeOnlyLocation && isTypeKeyword(stringToToken(keywordEntry.name)!) || !uniqueNames.has(keywordEntry.name)) { - uniqueNames.add(keywordEntry.name); - insertSorted(entries, keywordEntry, compareCompletionEntries, /*allowDuplicates*/ true); - } + if (isJsxIdentifierExpected && !isRightOfOpenTag && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { + let useBraces = preferences.jsxAttributeCompletionStyle === "braces"; + const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location); + + // If is boolean like or undefined, don't return a snippet we want just to return the completion. + if (preferences.jsxAttributeCompletionStyle === "auto" + && !(type.flags & TypeFlags.BooleanLike) + && !(type.flags & TypeFlags.Union && find((type as UnionType).types, type => !!(type.flags & TypeFlags.BooleanLike))) + ) { + if (type.flags & TypeFlags.StringLike || (type.flags & TypeFlags.Union && every((type as UnionType).types, type => !!(type.flags & (TypeFlags.StringLike | TypeFlags.Undefined))))) { + // If is string like or undefined use quotes + insertText = `${escapeSnippetText(name)}=${quote(sourceFile, preferences, "$1")}`; + isSnippet = true; } - } - - for (const keywordEntry of getContextualKeywords(contextToken, position)) { - if (!uniqueNames.has(keywordEntry.name)) { - uniqueNames.add(keywordEntry.name); - insertSorted(entries, keywordEntry, compareCompletionEntries, /*allowDuplicates*/ true); + else { + // Use braces for everything else + useBraces = true; } } - for (const literal of literals) { - const literalEntry = createCompletionEntryForLiteral(sourceFile, preferences, literal); - uniqueNames.add(literalEntry.name); - insertSorted(entries, literalEntry, compareCompletionEntries, /*allowDuplicates*/ true); - } - - if (!isChecked) { - getJSCompletionEntries(sourceFile, location.pos, uniqueNames, getEmitScriptTarget(compilerOptions), entries); + if (useBraces) { + insertText = `${escapeSnippetText(name)}={$1}`; + isSnippet = true; } - - return { - flags: completionData.flags, - isGlobalCompletion: isInSnippetScope, - isIncomplete: preferences.allowIncompleteCompletions && hasUnresolvedAutoImports ? true : undefined, - isMemberCompletion: isMemberCompletionKind(completionKind), - isNewIdentifierLocation, - optionalReplacementSpan: getOptionalReplacementSpan(location), - entries, - }; } - function isCheckedFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean { - return !isSourceFileJS(sourceFile) || !!isCheckJsEnabledForFile(sourceFile, compilerOptions); + if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) { + return undefined; } - function isMemberCompletionKind(kind: CompletionKind): boolean { - switch (kind) { - case CompletionKind.ObjectPropertyDeclaration: - case CompletionKind.MemberLike: - case CompletionKind.PropertyAccess: - return true; - default: - return false; - } + if (originIsExport(origin) || originIsResolvedExport(origin)) { + data = originToCompletionEntryData(origin); + hasAction = !importStatementCompletion; } - function getJsxClosingTagCompletion(location: Node | undefined, sourceFile: SourceFile): CompletionInfo | undefined { - // We wanna walk up the tree till we find a JSX closing element - const jsxClosingElement = findAncestor(location, node => { - switch (node.kind) { - case SyntaxKind.JsxClosingElement: - return true; - case SyntaxKind.SlashToken: - case SyntaxKind.GreaterThanToken: - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: - return false; - default: - return "quit"; - } - }) as JsxClosingElement | undefined; - - if (jsxClosingElement) { - // In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag, - // instead of simply giving unknown value, the completion will return the tag-name of an associated opening-element. - // For example: - // var x =
" with type any - // And at `
` (with a closing `>`), the completion list will contain "div". - // And at property access expressions ` ` the completion will - // return full closing tag with an optional replacement span - // For example: - // var x = - // var y = - // the completion list at "1" and "2" will contain "MainComponent.Child" with a replacement span of closing tag name - const hasClosingAngleBracket = !!findChildOfKind(jsxClosingElement, SyntaxKind.GreaterThanToken, sourceFile); - const tagName = jsxClosingElement.parent.openingElement.tagName; - const closingTag = tagName.getText(sourceFile); - const fullClosingTag = closingTag + (hasClosingAngleBracket ? "" : ">"); - const replacementSpan = createTextSpanFromNode(jsxClosingElement.tagName); - - const entry: CompletionEntry = { - name: fullClosingTag, - kind: ScriptElementKind.classElement, - kindModifiers: undefined, - sortText: SortText.LocationPriority, - }; - return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, optionalReplacementSpan: replacementSpan, entries: [entry] }; - } - return; - } + // TODO(drosen): Right now we just permit *all* semantic meanings when calling + // 'getSymbolKind' which is permissible given that it is backwards compatible; but + // really we should consider passing the meaning for the node so that we don't report + // that a suggestion for a value is an interface. We COULD also just do what + // 'getSymbolModifiers' does, which is to use the first declaration. + + // Use a 'sortText' of 0' so that all symbol completion entries come before any other + // entries (like JavaScript identifier entries). + return { + name, + kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location), + kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), + sortText, + source, + hasAction: hasAction ? true : undefined, + isRecommended: isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker) || undefined, + insertText, + replacementSpan, + sourceDisplay, + labelDetails, + isSnippet, + isPackageJsonImport: originIsPackageJsonImport(origin) || undefined, + isImportStatementCompletion: !!importStatementCompletion || undefined, + data, + }; +} - function getJSCompletionEntries( - sourceFile: SourceFile, - position: number, - uniqueNames: UniqueNameSet, - target: ScriptTarget, - entries: SortedArray): void { - getNameTable(sourceFile).forEach((pos, name) => { - // Skip identifiers produced only from the current location - if (pos === position) { - return; - } - const realName = unescapeLeadingUnderscores(name); - if (!uniqueNames.has(realName) && isIdentifierText(realName, target)) { - uniqueNames.add(realName); - insertSorted(entries, { - name: realName, - kind: ScriptElementKind.warning, - kindModifiers: "", - sortText: SortText.JavascriptIdentifiers, - isFromUncheckedFile: true - }, compareCompletionEntries); - } - }); +function isClassLikeMemberCompletion(symbol: Symbol, location: Node, sourceFile: SourceFile): boolean { + // TODO: support JS files. + if (isInJSFile(location)) { + return false; } - function completionNameForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): string { - return typeof literal === "object" ? pseudoBigIntToString(literal) + "n" : - isString(literal) ? quote(sourceFile, preferences, literal) : JSON.stringify(literal); + // Completion symbol must be for a class member. + const memberFlags = + SymbolFlags.ClassMember + & SymbolFlags.EnumMemberExcludes; + /* In + `class C { + | + }` + `location` is a class-like declaration. + In + `class C { + m| + }` + `location` is an identifier, + `location.parent` is a class element declaration, + and `location.parent.parent` is a class-like declaration. + In + `abstract class C { + abstract + abstract m| + }` + `location` is a syntax list (with modifiers as children), + and `location.parent` is a class-like declaration. + */ + return !!(symbol.flags & memberFlags) && + ( + isClassLike(location) || + ( + location.parent && + location.parent.parent && + isClassElement(location.parent) && + location === location.parent.name && + location.parent.getLastToken(sourceFile) === location.parent.name && + isClassLike(location.parent.parent) + ) || + ( + location.parent && + isSyntaxList(location) && + isClassLike(location.parent) + ) + ); +} + +function getEntryForMemberCompletion( + host: LanguageServiceHost, + program: Program, + options: CompilerOptions, + preferences: UserPreferences, + name: string, + symbol: Symbol, + location: Node, + contextToken: Node | undefined, + formatContext: formatting.FormatContext | undefined, +): { insertText: string, isSnippet?: true, importAdder?: codefix.ImportAdder, replacementSpan?: TextSpan } { + const classLikeDeclaration = findAncestor(location, isClassLike); + if (!classLikeDeclaration) { + return { insertText: name }; } - function createCompletionEntryForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): CompletionEntry { - return { name: completionNameForLiteral(sourceFile, preferences, literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority }; + let isSnippet: true | undefined; + let replacementSpan: TextSpan | undefined; + let insertText: string = name; + + const checker = program.getTypeChecker(); + const sourceFile = location.getSourceFile(); + const printer = createSnippetPrinter({ + removeComments: true, + module: options.module, + target: options.target, + omitTrailingSemicolon: false, + newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), + }); + const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); + + // Create empty body for possible method implementation. + let body; + if (preferences.includeCompletionsWithSnippetText) { + isSnippet = true; + // We are adding a tabstop (i.e. `$0`) in the body of the suggested member, + // if it has one, so that the cursor ends up in the body once the completion is inserted. + // Note: this assumes we won't have more than one body in the completion nodes, which should be the case. + const emptyStmt = factory.createEmptyStatement(); + body = factory.createBlock([emptyStmt], /* multiline */ true); + setSnippetElement(emptyStmt, { kind: SnippetKind.TabStop, order: 0 }); + } + else { + body = factory.createBlock([], /* multiline */ true); } - function createCompletionEntry( - symbol: Symbol, - sortText: SortText, - replacementToken: Node | undefined, - contextToken: Node | undefined, - location: Node, - sourceFile: SourceFile, - host: LanguageServiceHost, - program: Program, - name: string, - needsConvertPropertyAccess: boolean, - origin: SymbolOriginInfo | undefined, - recommendedCompletion: Symbol | undefined, - propertyAccessToConvert: PropertyAccessExpression | undefined, - isJsxInitializer: IsJsxInitializer | undefined, - importStatementCompletion: ImportStatementCompletionInfo | undefined, - useSemicolons: boolean, - options: CompilerOptions, - preferences: UserPreferences, - completionKind: CompletionKind, - formatContext: formatting.FormatContext | undefined, - isJsxIdentifierExpected: boolean | undefined, - isRightOfOpenTag: boolean | undefined, - ): CompletionEntry | undefined { - let insertText: string | undefined; - let replacementSpan = getReplacementSpanForContextToken(replacementToken); - let data: CompletionEntryData | undefined; - let isSnippet: true | undefined; - let source = getSourceFromOrigin(origin); - let sourceDisplay; - let hasAction; - let labelDetails; - - const typeChecker = program.getTypeChecker(); - const insertQuestionDot = origin && originIsNullableMember(origin); - const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; - if (origin && originIsThisType(origin)) { - insertText = needsConvertPropertyAccess - ? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(sourceFile, preferences, name)}]` - : `this${insertQuestionDot ? "?." : "."}${name}`; - } - // We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790. - // Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro. - else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) { - insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(sourceFile, preferences, name)}]` : `[${name}]` : name; - if (insertQuestionDot || propertyAccessToConvert.questionDotToken) { - insertText = `?.${insertText}`; + let modifiers = ModifierFlags.None; + // Whether the suggested member should be abstract. + // e.g. in `abstract class C { abstract | }`, we should offer abstract method signatures at position `|`. + const { modifiers: presentModifiers, span: modifiersSpan } = getPresentModifiers(contextToken); + const isAbstract = !!(presentModifiers & ModifierFlags.Abstract); + const completionNodes: Node[] = []; + codefix.addNewNodeForMemberSymbol( + symbol, + classLikeDeclaration, + sourceFile, + { program, host }, + preferences, + importAdder, + // `addNewNodeForMemberSymbol` calls this callback function for each new member node + // it adds for the given member symbol. + // We store these member nodes in the `completionNodes` array. + // Note: there might be: + // - No nodes if `addNewNodeForMemberSymbol` cannot figure out a node for the member; + // - One node; + // - More than one node if the member is overloaded (e.g. a method with overload signatures). + node => { + let requiredModifiers = ModifierFlags.None; + if (isAbstract) { + requiredModifiers |= ModifierFlags.Abstract; } - - const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile) || - findChildOfKind(propertyAccessToConvert, SyntaxKind.QuestionDotToken, sourceFile); - if (!dot) { - return undefined; + if (isClassElement(node) + && checker.getMemberOverrideModifierStatus(classLikeDeclaration, node) === MemberOverrideStatus.NeedsOverride) { + requiredModifiers |= ModifierFlags.Override; } - // If the text after the '.' starts with this name, write over it. Else, add new text. - const end = startsWith(name, propertyAccessToConvert.name.text) ? propertyAccessToConvert.name.end : dot.end; - replacementSpan = createTextSpanFromBounds(dot.getStart(sourceFile), end); - } - if (isJsxInitializer) { - if (insertText === undefined) insertText = name; - insertText = `{${insertText}}`; - if (typeof isJsxInitializer !== "boolean") { - replacementSpan = createTextSpanFromNode(isJsxInitializer, sourceFile); - } - } - if (origin && originIsPromise(origin) && propertyAccessToConvert) { - if (insertText === undefined) insertText = name; - const precedingToken = findPrecedingToken(propertyAccessToConvert.pos, sourceFile); - let awaitText = ""; - if (precedingToken && positionIsASICandidate(precedingToken.end, precedingToken.parent, sourceFile)) { - awaitText = ";"; + if (!completionNodes.length) { + // Keep track of added missing required modifiers and modifiers already present. + // This is needed when we have overloaded signatures, + // so this callback will be called for multiple nodes/signatures, + // and we need to make sure the modifiers are uniform for all nodes/signatures. + modifiers = node.modifierFlagsCache | requiredModifiers | presentModifiers; } - - awaitText += `(await ${propertyAccessToConvert.expression.getText()})`; - insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}${insertQuestionDot ? "?." : "."}${insertText}`; - replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end); - } - - if (originIsResolvedExport(origin)) { - sourceDisplay = [textPart(origin.moduleSpecifier)]; - if (importStatementCompletion) { - ({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importStatementCompletion, origin, useSemicolons, sourceFile, options, preferences)); - isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined; - } - } - - if (origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias) { - hasAction = true; - } - - if (preferences.includeCompletionsWithClassMemberSnippets && - preferences.includeCompletionsWithInsertText && - completionKind === CompletionKind.MemberLike && - isClassLikeMemberCompletion(symbol, location, sourceFile)) { - let importAdder; - ({ insertText, isSnippet, importAdder, replacementSpan } = getEntryForMemberCompletion(host, program, options, preferences, name, symbol, location, contextToken, formatContext)); - sortText = SortText.ClassMemberSnippets; // sortText has to be lower priority than the sortText for keywords. See #47852. - if (importAdder?.hasFixes()) { - hasAction = true; - source = CompletionSource.ClassMemberSnippet; - } - } - - if (origin && originIsObjectLiteralMethod(origin)) { - ({ insertText, isSnippet, labelDetails } = origin); - if (!preferences.useLabelDetailsInCompletionEntries) { - name = name + labelDetails.detail; - labelDetails = undefined; - } - source = CompletionSource.ObjectLiteralMethodSnippet; - sortText = SortText.SortBelow(sortText); - } - - if (isJsxIdentifierExpected && !isRightOfOpenTag && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { - let useBraces = preferences.jsxAttributeCompletionStyle === "braces"; - const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location); - - // If is boolean like or undefined, don't return a snippet we want just to return the completion. - if (preferences.jsxAttributeCompletionStyle === "auto" - && !(type.flags & TypeFlags.BooleanLike) - && !(type.flags & TypeFlags.Union && find((type as UnionType).types, type => !!(type.flags & TypeFlags.BooleanLike))) - ) { - if (type.flags & TypeFlags.StringLike || (type.flags & TypeFlags.Union && every((type as UnionType).types, type => !!(type.flags & (TypeFlags.StringLike | TypeFlags.Undefined))))) { - // If is string like or undefined use quotes - insertText = `${escapeSnippetText(name)}=${quote(sourceFile, preferences, "$1")}`; - isSnippet = true; - } - else { - // Use braces for everything else - useBraces = true; - } - } - - if (useBraces) { - insertText = `${escapeSnippetText(name)}={$1}`; - isSnippet = true; - } - } - - if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) { - return undefined; + node = factory.updateModifiers(node, modifiers); + completionNodes.push(node); + }, + body, + codefix.PreserveOptionalFlags.Property, + isAbstract); + + if (completionNodes.length) { + const format = ListFormat.MultiLine | ListFormat.NoTrailingNewLine; + replacementSpan = modifiersSpan; + // If we have access to formatting settings, we print the nodes using the emitter, + // and then format the printed text. + if (formatContext) { + insertText = printer.printAndFormatSnippetList( + format, + factory.createNodeArray(completionNodes), + sourceFile, + formatContext); } - - if (originIsExport(origin) || originIsResolvedExport(origin)) { - data = originToCompletionEntryData(origin); - hasAction = !importStatementCompletion; + else { // Otherwise, just use emitter to print the new nodes. + insertText = printer.printSnippetList( + format, + factory.createNodeArray(completionNodes), + sourceFile); } - - // TODO(drosen): Right now we just permit *all* semantic meanings when calling - // 'getSymbolKind' which is permissible given that it is backwards compatible; but - // really we should consider passing the meaning for the node so that we don't report - // that a suggestion for a value is an interface. We COULD also just do what - // 'getSymbolModifiers' does, which is to use the first declaration. - - // Use a 'sortText' of 0' so that all symbol completion entries come before any other - // entries (like JavaScript identifier entries). - return { - name, - kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location), - kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), - sortText, - source, - hasAction: hasAction ? true : undefined, - isRecommended: isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker) || undefined, - insertText, - replacementSpan, - sourceDisplay, - labelDetails, - isSnippet, - isPackageJsonImport: originIsPackageJsonImport(origin) || undefined, - isImportStatementCompletion: !!importStatementCompletion || undefined, - data, - }; } - function isClassLikeMemberCompletion(symbol: Symbol, location: Node, sourceFile: SourceFile): boolean { - // TODO: support JS files. - if (isInJSFile(location)) { - return false; - } - - // Completion symbol must be for a class member. - const memberFlags = - SymbolFlags.ClassMember - & SymbolFlags.EnumMemberExcludes; - /* In - `class C { - | - }` - `location` is a class-like declaration. - In - `class C { - m| - }` - `location` is an identifier, - `location.parent` is a class element declaration, - and `location.parent.parent` is a class-like declaration. - In - `abstract class C { - abstract - abstract m| - }` - `location` is a syntax list (with modifiers as children), - and `location.parent` is a class-like declaration. - */ - return !!(symbol.flags & memberFlags) && - ( - isClassLike(location) || - ( - location.parent && - location.parent.parent && - isClassElement(location.parent) && - location === location.parent.name && - location.parent.getLastToken(sourceFile) === location.parent.name && - isClassLike(location.parent.parent) - ) || - ( - location.parent && - isSyntaxList(location) && - isClassLike(location.parent) - ) - ); - } - - function getEntryForMemberCompletion( - host: LanguageServiceHost, - program: Program, - options: CompilerOptions, - preferences: UserPreferences, - name: string, - symbol: Symbol, - location: Node, - contextToken: Node | undefined, - formatContext: formatting.FormatContext | undefined, - ): { insertText: string, isSnippet?: true, importAdder?: codefix.ImportAdder, replacementSpan?: TextSpan } { - const classLikeDeclaration = findAncestor(location, isClassLike); - if (!classLikeDeclaration) { - return { insertText: name }; - } - - let isSnippet: true | undefined; - let replacementSpan: TextSpan | undefined; - let insertText: string = name; - - const checker = program.getTypeChecker(); - const sourceFile = location.getSourceFile(); - const printer = createSnippetPrinter({ - removeComments: true, - module: options.module, - target: options.target, - omitTrailingSemicolon: false, - newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), - }); - const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); - - // Create empty body for possible method implementation. - let body; - if (preferences.includeCompletionsWithSnippetText) { - isSnippet = true; - // We are adding a tabstop (i.e. `$0`) in the body of the suggested member, - // if it has one, so that the cursor ends up in the body once the completion is inserted. - // Note: this assumes we won't have more than one body in the completion nodes, which should be the case. - const emptyStmt = factory.createEmptyStatement(); - body = factory.createBlock([emptyStmt], /* multiline */ true); - setSnippetElement(emptyStmt, { kind: SnippetKind.TabStop, order: 0 }); - } - else { - body = factory.createBlock([], /* multiline */ true); - } - - let modifiers = ModifierFlags.None; - // Whether the suggested member should be abstract. - // e.g. in `abstract class C { abstract | }`, we should offer abstract method signatures at position `|`. - const { modifiers: presentModifiers, span: modifiersSpan } = getPresentModifiers(contextToken); - const isAbstract = !!(presentModifiers & ModifierFlags.Abstract); - const completionNodes: Node[] = []; - codefix.addNewNodeForMemberSymbol( - symbol, - classLikeDeclaration, - sourceFile, - { program, host }, - preferences, - importAdder, - // `addNewNodeForMemberSymbol` calls this callback function for each new member node - // it adds for the given member symbol. - // We store these member nodes in the `completionNodes` array. - // Note: there might be: - // - No nodes if `addNewNodeForMemberSymbol` cannot figure out a node for the member; - // - One node; - // - More than one node if the member is overloaded (e.g. a method with overload signatures). - node => { - let requiredModifiers = ModifierFlags.None; - if (isAbstract) { - requiredModifiers |= ModifierFlags.Abstract; - } - if (isClassElement(node) - && checker.getMemberOverrideModifierStatus(classLikeDeclaration, node) === MemberOverrideStatus.NeedsOverride) { - requiredModifiers |= ModifierFlags.Override; - } - - if (!completionNodes.length) { - // Keep track of added missing required modifiers and modifiers already present. - // This is needed when we have overloaded signatures, - // so this callback will be called for multiple nodes/signatures, - // and we need to make sure the modifiers are uniform for all nodes/signatures. - modifiers = node.modifierFlagsCache | requiredModifiers | presentModifiers; - } - node = factory.updateModifiers(node, modifiers); - completionNodes.push(node); - }, - body, - codefix.PreserveOptionalFlags.Property, - isAbstract); - - if (completionNodes.length) { - const format = ListFormat.MultiLine | ListFormat.NoTrailingNewLine; - replacementSpan = modifiersSpan; - // If we have access to formatting settings, we print the nodes using the emitter, - // and then format the printed text. - if (formatContext) { - insertText = printer.printAndFormatSnippetList( - format, - factory.createNodeArray(completionNodes), - sourceFile, - formatContext); - } - else { // Otherwise, just use emitter to print the new nodes. - insertText = printer.printSnippetList( - format, - factory.createNodeArray(completionNodes), - sourceFile); - } - } + return { insertText, isSnippet, importAdder, replacementSpan }; +} - return { insertText, isSnippet, importAdder, replacementSpan }; +function getPresentModifiers(contextToken: Node | undefined): { modifiers: ModifierFlags, span?: TextSpan } { + if (!contextToken) { + return { modifiers: ModifierFlags.None }; + } + let modifiers = ModifierFlags.None; + let span; + let contextMod; + /* + Cases supported: + In + `class C { + public abstract | + }` + `contextToken` is ``abstract`` (as an identifier), + `contextToken.parent` is property declaration, + `location` is class declaration ``class C { ... }``. + In + `class C { + protected override m| + }` + `contextToken` is ``override`` (as a keyword), + `contextToken.parent` is property declaration, + `location` is identifier ``m``, + `location.parent` is property declaration ``protected override m``, + `location.parent.parent` is class declaration ``class C { ... }``. + */ + if (contextMod = isModifierLike(contextToken)) { + modifiers |= modifierToFlag(contextMod); + span = createTextSpanFromNode(contextToken); } + if (isPropertyDeclaration(contextToken.parent)) { + modifiers |= modifiersToFlags(contextToken.parent.modifiers) & ModifierFlags.Modifier; + span = createTextSpanFromNode(contextToken.parent); + } + return { modifiers, span }; +} - function getPresentModifiers(contextToken: Node | undefined): { modifiers: ModifierFlags, span?: TextSpan } { - if (!contextToken) { - return { modifiers: ModifierFlags.None }; - } - let modifiers = ModifierFlags.None; - let span; - let contextMod; - /* - Cases supported: - In - `class C { - public abstract | - }` - `contextToken` is ``abstract`` (as an identifier), - `contextToken.parent` is property declaration, - `location` is class declaration ``class C { ... }``. - In - `class C { - protected override m| - }` - `contextToken` is ``override`` (as a keyword), - `contextToken.parent` is property declaration, - `location` is identifier ``m``, - `location.parent` is property declaration ``protected override m``, - `location.parent.parent` is class declaration ``class C { ... }``. - */ - if (contextMod = isModifierLike(contextToken)) { - modifiers |= modifierToFlag(contextMod); - span = createTextSpanFromNode(contextToken); - } - if (isPropertyDeclaration(contextToken.parent)) { - modifiers |= modifiersToFlags(contextToken.parent.modifiers) & ModifierFlags.Modifier; - span = createTextSpanFromNode(contextToken.parent); - } - return { modifiers, span }; +function isModifierLike(node: Node): ModifierSyntaxKind | undefined { + if (isModifier(node)) { + return node.kind; + } + if (isIdentifier(node) && node.originalKeywordKind && isModifierKind(node.originalKeywordKind)) { + return node.originalKeywordKind; } + return undefined; +} - function isModifierLike(node: Node): ModifierSyntaxKind | undefined { - if (isModifier(node)) { - return node.kind; - } - if (isIdentifier(node) && node.originalKeywordKind && isModifierKind(node.originalKeywordKind)) { - return node.originalKeywordKind; - } +function getEntryForObjectLiteralMethodCompletion( + symbol: Symbol, + name: string, + enclosingDeclaration: ObjectLiteralExpression, + program: Program, + host: LanguageServiceHost, + options: CompilerOptions, + preferences: UserPreferences, + formatContext: formatting.FormatContext | undefined, +): { insertText: string, isSnippet?: true, labelDetails: CompletionEntryLabelDetails } | undefined { + const isSnippet = preferences.includeCompletionsWithSnippetText || undefined; + let insertText: string = name; + + const sourceFile = enclosingDeclaration.getSourceFile(); + + const method = createObjectLiteralMethod(symbol, enclosingDeclaration, sourceFile, program, host, preferences); + if (!method) { return undefined; } - function getEntryForObjectLiteralMethodCompletion( - symbol: Symbol, - name: string, - enclosingDeclaration: ObjectLiteralExpression, - program: Program, - host: LanguageServiceHost, - options: CompilerOptions, - preferences: UserPreferences, - formatContext: formatting.FormatContext | undefined, - ): { insertText: string, isSnippet?: true, labelDetails: CompletionEntryLabelDetails } | undefined { - const isSnippet = preferences.includeCompletionsWithSnippetText || undefined; - let insertText: string = name; - - const sourceFile = enclosingDeclaration.getSourceFile(); - - const method = createObjectLiteralMethod(symbol, enclosingDeclaration, sourceFile, program, host, preferences); - if (!method) { - return undefined; - } - - const printer = createSnippetPrinter({ - removeComments: true, - module: options.module, - target: options.target, - omitTrailingSemicolon: false, - newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), - }); - if (formatContext) { - insertText = printer.printAndFormatSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([method], /*hasTrailingComma*/ true), sourceFile, formatContext); - } - else { - insertText = printer.printSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([method], /*hasTrailingComma*/ true), sourceFile); - } + const printer = createSnippetPrinter({ + removeComments: true, + module: options.module, + target: options.target, + omitTrailingSemicolon: false, + newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), + }); + if (formatContext) { + insertText = printer.printAndFormatSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([method], /*hasTrailingComma*/ true), sourceFile, formatContext); + } + else { + insertText = printer.printSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([method], /*hasTrailingComma*/ true), sourceFile); + } - const signaturePrinter = createPrinter({ - removeComments: true, - module: options.module, - target: options.target, - omitTrailingSemicolon: true, - }); - // The `labelDetails.detail` will be displayed right beside the method name, - // so we drop the name (and modifiers) from the signature. - const methodSignature = factory.createMethodSignature( - /*modifiers*/ undefined, - /*name*/ "", - method.questionToken, - method.typeParameters, - method.parameters, - method.type); - const labelDetails = { detail: signaturePrinter.printNode(EmitHint.Unspecified, methodSignature, sourceFile) }; + const signaturePrinter = createPrinter({ + removeComments: true, + module: options.module, + target: options.target, + omitTrailingSemicolon: true, + }); + // The `labelDetails.detail` will be displayed right beside the method name, + // so we drop the name (and modifiers) from the signature. + const methodSignature = factory.createMethodSignature( + /*modifiers*/ undefined, + /*name*/ "", + method.questionToken, + method.typeParameters, + method.parameters, + method.type); + const labelDetails = { detail: signaturePrinter.printNode(EmitHint.Unspecified, methodSignature, sourceFile) }; + + return { isSnippet, insertText, labelDetails }; - return { isSnippet, insertText, labelDetails }; +} +function createObjectLiteralMethod( + symbol: Symbol, + enclosingDeclaration: ObjectLiteralExpression, + sourceFile: SourceFile, + program: Program, + host: LanguageServiceHost, + preferences: UserPreferences, +): MethodDeclaration | undefined { + const declarations = symbol.getDeclarations(); + if (!(declarations && declarations.length)) { + return undefined; } - - function createObjectLiteralMethod( - symbol: Symbol, - enclosingDeclaration: ObjectLiteralExpression, - sourceFile: SourceFile, - program: Program, - host: LanguageServiceHost, - preferences: UserPreferences, - ): MethodDeclaration | undefined { - const declarations = symbol.getDeclarations(); - if (!(declarations && declarations.length)) { - return undefined; - } - const checker = program.getTypeChecker(); - const declaration = declarations[0]; - const name = getSynthesizedDeepClone(getNameOfDeclaration(declaration), /*includeTrivia*/ false) as PropertyName; - const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); - const quotePreference = getQuotePreference(sourceFile, preferences); - const builderFlags = NodeBuilderFlags.OmitThisParameter | (quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : NodeBuilderFlags.None); - - switch (declaration.kind) { - case SyntaxKind.PropertySignature: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.MethodDeclaration: { - let effectiveType = type.flags & TypeFlags.Union && (type as UnionType).types.length < 10 - ? checker.getUnionType((type as UnionType).types, UnionReduction.Subtype) - : type; - if (effectiveType.flags & TypeFlags.Union) { - // Only offer the completion if there's a single function type component. - const functionTypes = filter((effectiveType as UnionType).types, type => checker.getSignaturesOfType(type, SignatureKind.Call).length > 0); - if (functionTypes.length === 1) { - effectiveType = functionTypes[0]; - } - else { - return undefined; - } + const checker = program.getTypeChecker(); + const declaration = declarations[0]; + const name = getSynthesizedDeepClone(getNameOfDeclaration(declaration), /*includeTrivia*/ false) as PropertyName; + const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); + const quotePreference = getQuotePreference(sourceFile, preferences); + const builderFlags = NodeBuilderFlags.OmitThisParameter | (quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : NodeBuilderFlags.None); + + switch (declaration.kind) { + case SyntaxKind.PropertySignature: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.MethodDeclaration: { + let effectiveType = type.flags & TypeFlags.Union && (type as UnionType).types.length < 10 + ? checker.getUnionType((type as UnionType).types, UnionReduction.Subtype) + : type; + if (effectiveType.flags & TypeFlags.Union) { + // Only offer the completion if there's a single function type component. + const functionTypes = filter((effectiveType as UnionType).types, type => checker.getSignaturesOfType(type, SignatureKind.Call).length > 0); + if (functionTypes.length === 1) { + effectiveType = functionTypes[0]; } - const signatures = checker.getSignaturesOfType(effectiveType, SignatureKind.Call); - if (signatures.length !== 1) { - // We don't support overloads in object literals. - return undefined; - } - const typeNode = checker.typeToTypeNode(effectiveType, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); - if (!typeNode || !isFunctionTypeNode(typeNode)) { + else { return undefined; } + } + const signatures = checker.getSignaturesOfType(effectiveType, SignatureKind.Call); + if (signatures.length !== 1) { + // We don't support overloads in object literals. + return undefined; + } + const typeNode = checker.typeToTypeNode(effectiveType, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); + if (!typeNode || !isFunctionTypeNode(typeNode)) { + return undefined; + } - let body; - if (preferences.includeCompletionsWithSnippetText) { - const emptyStmt = factory.createEmptyStatement(); - body = factory.createBlock([emptyStmt], /* multiline */ true); - setSnippetElement(emptyStmt, { kind: SnippetKind.TabStop, order: 0 }); - } - else { - body = factory.createBlock([], /* multiline */ true); - } + let body; + if (preferences.includeCompletionsWithSnippetText) { + const emptyStmt = factory.createEmptyStatement(); + body = factory.createBlock([emptyStmt], /* multiline */ true); + setSnippetElement(emptyStmt, { kind: SnippetKind.TabStop, order: 0 }); + } + else { + body = factory.createBlock([], /* multiline */ true); + } - const parameters = typeNode.parameters.map(typedParam => - factory.createParameterDeclaration( - /*modifiers*/ undefined, - typedParam.dotDotDotToken, - typedParam.name, - /*questionToken*/ undefined, - /*type*/ undefined, - typedParam.initializer, - )); - return factory.createMethodDeclaration( + const parameters = typeNode.parameters.map(typedParam => + factory.createParameterDeclaration( /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - name, + typedParam.dotDotDotToken, + typedParam.name, /*questionToken*/ undefined, - /*typeParameters*/ undefined, - parameters, /*type*/ undefined, - body); - } - default: - return undefined; - } + typedParam.initializer, + )); + return factory.createMethodDeclaration( + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + name, + /*questionToken*/ undefined, + /*typeParameters*/ undefined, + parameters, + /*type*/ undefined, + body); + } + default: + return undefined; } +} - function createSnippetPrinter( - printerOptions: PrinterOptions, - ) { - let escapes: TextChange[] | undefined; - const baseWriter = textChanges.createWriter(getNewLineCharacter(printerOptions)); - const printer = createPrinter(printerOptions, baseWriter); - const writer: EmitTextWriter = { - ...baseWriter, - write: s => escapingWrite(s, () => baseWriter.write(s)), - nonEscapingWrite: baseWriter.write, - writeLiteral: s => escapingWrite(s, () => baseWriter.writeLiteral(s)), - writeStringLiteral: s => escapingWrite(s, () => baseWriter.writeStringLiteral(s)), - writeSymbol: (s, symbol) => escapingWrite(s, () => baseWriter.writeSymbol(s, symbol)), - writeParameter: s => escapingWrite(s, () => baseWriter.writeParameter(s)), - writeComment: s => escapingWrite(s, () => baseWriter.writeComment(s)), - writeProperty: s => escapingWrite(s, () => baseWriter.writeProperty(s)), - }; +function createSnippetPrinter( + printerOptions: PrinterOptions, +) { + let escapes: TextChange[] | undefined; + const baseWriter = textChanges.createWriter(getNewLineCharacter(printerOptions)); + const printer = createPrinter(printerOptions, baseWriter); + const writer: EmitTextWriter = { + ...baseWriter, + write: s => escapingWrite(s, () => baseWriter.write(s)), + nonEscapingWrite: baseWriter.write, + writeLiteral: s => escapingWrite(s, () => baseWriter.writeLiteral(s)), + writeStringLiteral: s => escapingWrite(s, () => baseWriter.writeStringLiteral(s)), + writeSymbol: (s, symbol) => escapingWrite(s, () => baseWriter.writeSymbol(s, symbol)), + writeParameter: s => escapingWrite(s, () => baseWriter.writeParameter(s)), + writeComment: s => escapingWrite(s, () => baseWriter.writeComment(s)), + writeProperty: s => escapingWrite(s, () => baseWriter.writeProperty(s)), + }; - return { - printSnippetList, - printAndFormatSnippetList, - }; + return { + printSnippetList, + printAndFormatSnippetList, + }; - // The formatter/scanner will have issues with snippet-escaped text, - // so instead of writing the escaped text directly to the writer, - // generate a set of changes that can be applied to the unescaped text - // to escape it post-formatting. - function escapingWrite(s: string, write: () => void) { - const escaped = escapeSnippetText(s); - if (escaped !== s) { - const start = baseWriter.getTextPos(); - write(); - const end = baseWriter.getTextPos(); - escapes = append(escapes ||= [], { newText: escaped, span: { start, length: end - start } }); - } - else { - write(); - } + // The formatter/scanner will have issues with snippet-escaped text, + // so instead of writing the escaped text directly to the writer, + // generate a set of changes that can be applied to the unescaped text + // to escape it post-formatting. + function escapingWrite(s: string, write: () => void) { + const escaped = escapeSnippetText(s); + if (escaped !== s) { + const start = baseWriter.getTextPos(); + write(); + const end = baseWriter.getTextPos(); + escapes = append(escapes ||= [], { newText: escaped, span: { start, length: end - start } }); } - - /* Snippet-escaping version of `printer.printList`. */ - function printSnippetList( - format: ListFormat, - list: NodeArray, - sourceFile: SourceFile | undefined, - ): string { - const unescaped = printUnescapedSnippetList(format, list, sourceFile); - return escapes ? textChanges.applyChanges(unescaped, escapes) : unescaped; + else { + write(); } + } - function printUnescapedSnippetList( - format: ListFormat, - list: NodeArray, - sourceFile: SourceFile | undefined, - ): string { - escapes = undefined; - writer.clear(); - printer.writeList(format, list, sourceFile, writer); - return writer.getText(); - } + /* Snippet-escaping version of `printer.printList`. */ + function printSnippetList( + format: ListFormat, + list: NodeArray, + sourceFile: SourceFile | undefined, + ): string { + const unescaped = printUnescapedSnippetList(format, list, sourceFile); + return escapes ? textChanges.applyChanges(unescaped, escapes) : unescaped; + } - function printAndFormatSnippetList( - format: ListFormat, - list: NodeArray, - sourceFile: SourceFile, - formatContext: formatting.FormatContext, - ): string { - const syntheticFile = { - text: printUnescapedSnippetList( - format, - list, - sourceFile), - getLineAndCharacterOfPosition(pos: number) { - return getLineAndCharacterOfPosition(this, pos); - }, - }; + function printUnescapedSnippetList( + format: ListFormat, + list: NodeArray, + sourceFile: SourceFile | undefined, + ): string { + escapes = undefined; + writer.clear(); + printer.writeList(format, list, sourceFile, writer); + return writer.getText(); + } - const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); - const changes = flatMap(list, node => { - const nodeWithPos = textChanges.assignPositionsToNode(node); - return formatting.formatNodeGivenIndentation( - nodeWithPos, - syntheticFile, - sourceFile.languageVariant, - /* indentation */ 0, - /* delta */ 0, - { ...formatContext, options: formatOptions }); - }); + function printAndFormatSnippetList( + format: ListFormat, + list: NodeArray, + sourceFile: SourceFile, + formatContext: formatting.FormatContext, + ): string { + const syntheticFile = { + text: printUnescapedSnippetList( + format, + list, + sourceFile), + getLineAndCharacterOfPosition(pos: number) { + return getLineAndCharacterOfPosition(this, pos); + }, + }; - const allChanges = escapes - ? stableSort(concatenate(changes, escapes), (a, b) => compareTextSpans(a.span, b.span)) - : changes; - return textChanges.applyChanges(syntheticFile.text, allChanges); - } + const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); + const changes = flatMap(list, node => { + const nodeWithPos = textChanges.assignPositionsToNode(node); + return formatting.formatNodeGivenIndentation( + nodeWithPos, + syntheticFile, + sourceFile.languageVariant, + /* indentation */ 0, + /* delta */ 0, + { ...formatContext, options: formatOptions }); + }); + + const allChanges = escapes + ? stableSort(concatenate(changes, escapes), (a, b) => compareTextSpans(a.span, b.span)) + : changes; + return textChanges.applyChanges(syntheticFile.text, allChanges); } +} - function originToCompletionEntryData(origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport): CompletionEntryData | undefined { - const ambientModuleName = origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name); - const isPackageJsonImport = origin.isFromPackageJson ? true : undefined; - if (originIsResolvedExport(origin)) { - const resolvedData: CompletionEntryDataResolved = { - exportName: origin.exportName, - moduleSpecifier: origin.moduleSpecifier, - ambientModuleName, - fileName: origin.fileName, - isPackageJsonImport, - }; - return resolvedData; - } - const unresolvedData: CompletionEntryDataUnresolved = { +function originToCompletionEntryData(origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport): CompletionEntryData | undefined { + const ambientModuleName = origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name); + const isPackageJsonImport = origin.isFromPackageJson ? true : undefined; + if (originIsResolvedExport(origin)) { + const resolvedData: CompletionEntryDataResolved = { exportName: origin.exportName, - exportMapKey: origin.exportMapKey, + moduleSpecifier: origin.moduleSpecifier, + ambientModuleName, fileName: origin.fileName, - ambientModuleName: origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name), - isPackageJsonImport: origin.isFromPackageJson ? true : undefined, + isPackageJsonImport, }; - return unresolvedData; - } - - function completionEntryDataToSymbolOriginInfo(data: CompletionEntryData, completionName: string, moduleSymbol: Symbol): SymbolOriginInfoExport | SymbolOriginInfoResolvedExport { - const isDefaultExport = data.exportName === InternalSymbolName.Default; - const isFromPackageJson = !!data.isPackageJsonImport; - if (completionEntryDataIsResolved(data)) { - const resolvedOrigin: SymbolOriginInfoResolvedExport = { - kind: SymbolOriginInfoKind.ResolvedExport, - exportName: data.exportName, - moduleSpecifier: data.moduleSpecifier, - symbolName: completionName, - fileName: data.fileName, - moduleSymbol, - isDefaultExport, - isFromPackageJson, - }; - return resolvedOrigin; - } - const unresolvedOrigin: SymbolOriginInfoExport = { - kind: SymbolOriginInfoKind.Export, + return resolvedData; + } + const unresolvedData: CompletionEntryDataUnresolved = { + exportName: origin.exportName, + exportMapKey: origin.exportMapKey, + fileName: origin.fileName, + ambientModuleName: origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name), + isPackageJsonImport: origin.isFromPackageJson ? true : undefined, + }; + return unresolvedData; +} + +function completionEntryDataToSymbolOriginInfo(data: CompletionEntryData, completionName: string, moduleSymbol: Symbol): SymbolOriginInfoExport | SymbolOriginInfoResolvedExport { + const isDefaultExport = data.exportName === InternalSymbolName.Default; + const isFromPackageJson = !!data.isPackageJsonImport; + if (completionEntryDataIsResolved(data)) { + const resolvedOrigin: SymbolOriginInfoResolvedExport = { + kind: SymbolOriginInfoKind.ResolvedExport, exportName: data.exportName, - exportMapKey: data.exportMapKey, + moduleSpecifier: data.moduleSpecifier, symbolName: completionName, fileName: data.fileName, moduleSymbol, isDefaultExport, isFromPackageJson, }; - return unresolvedOrigin; - } - - function getInsertTextAndReplacementSpanForImportCompletion(name: string, importStatementCompletion: ImportStatementCompletionInfo, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, sourceFile: SourceFile, options: CompilerOptions, preferences: UserPreferences) { - const replacementSpan = importStatementCompletion.replacementSpan; - const quotedModuleSpecifier = quote(sourceFile, preferences, origin.moduleSpecifier); - const exportKind = - origin.isDefaultExport ? ExportKind.Default : - origin.exportName === InternalSymbolName.ExportEquals ? ExportKind.ExportEquals : - ExportKind.Named; - const tabStop = preferences.includeCompletionsWithSnippetText ? "$1" : ""; - const importKind = codefix.getImportKind(sourceFile, exportKind, options, /*forceImportKeyword*/ true); - const isImportSpecifierTypeOnly = importStatementCompletion.couldBeTypeOnlyImportSpecifier; - const topLevelTypeOnlyText = importStatementCompletion.isTopLevelTypeOnly ? ` ${tokenToString(SyntaxKind.TypeKeyword)} ` : " "; - const importSpecifierTypeOnlyText = isImportSpecifierTypeOnly ? `${tokenToString(SyntaxKind.TypeKeyword)} ` : ""; - const suffix = useSemicolons ? ";" : ""; - switch (importKind) { - case ImportKind.CommonJS: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` }; - case ImportKind.Default: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` }; - case ImportKind.Namespace: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}* as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` }; - case ImportKind.Named: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}{ ${importSpecifierTypeOnlyText}${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` }; - } + return resolvedOrigin; } + const unresolvedOrigin: SymbolOriginInfoExport = { + kind: SymbolOriginInfoKind.Export, + exportName: data.exportName, + exportMapKey: data.exportMapKey, + symbolName: completionName, + fileName: data.fileName, + moduleSymbol, + isDefaultExport, + isFromPackageJson, + }; + return unresolvedOrigin; +} - function quotePropertyName(sourceFile: SourceFile, preferences: UserPreferences, name: string,): string { - if (/^\d+$/.test(name)) { - return name; - } - - return quote(sourceFile, preferences, name); +function getInsertTextAndReplacementSpanForImportCompletion(name: string, importStatementCompletion: ImportStatementCompletionInfo, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, sourceFile: SourceFile, options: CompilerOptions, preferences: UserPreferences) { + const replacementSpan = importStatementCompletion.replacementSpan; + const quotedModuleSpecifier = quote(sourceFile, preferences, origin.moduleSpecifier); + const exportKind = + origin.isDefaultExport ? ExportKind.Default : + origin.exportName === InternalSymbolName.ExportEquals ? ExportKind.ExportEquals : + ExportKind.Named; + const tabStop = preferences.includeCompletionsWithSnippetText ? "$1" : ""; + const importKind = codefix.getImportKind(sourceFile, exportKind, options, /*forceImportKeyword*/ true); + const isImportSpecifierTypeOnly = importStatementCompletion.couldBeTypeOnlyImportSpecifier; + const topLevelTypeOnlyText = importStatementCompletion.isTopLevelTypeOnly ? ` ${tokenToString(SyntaxKind.TypeKeyword)} ` : " "; + const importSpecifierTypeOnlyText = isImportSpecifierTypeOnly ? `${tokenToString(SyntaxKind.TypeKeyword)} ` : ""; + const suffix = useSemicolons ? ";" : ""; + switch (importKind) { + case ImportKind.CommonJS: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` }; + case ImportKind.Default: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` }; + case ImportKind.Namespace: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}* as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` }; + case ImportKind.Named: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}{ ${importSpecifierTypeOnlyText}${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` }; } +} - function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol | undefined, checker: TypeChecker): boolean { - return localSymbol === recommendedCompletion || - !!(localSymbol.flags & SymbolFlags.ExportValue) && checker.getExportSymbolOfSymbol(localSymbol) === recommendedCompletion; +function quotePropertyName(sourceFile: SourceFile, preferences: UserPreferences, name: string,): string { + if (/^\d+$/.test(name)) { + return name; } - function getSourceFromOrigin(origin: SymbolOriginInfo | undefined): string | undefined { - if (originIsExport(origin)) { - return stripQuotes(origin.moduleSymbol.name); - } - if (originIsResolvedExport(origin)) { - return origin.moduleSpecifier; - } - if (origin?.kind === SymbolOriginInfoKind.ThisType) { - return CompletionSource.ThisProperty; - } - if (origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias) { - return CompletionSource.TypeOnlyAlias; - } - } + return quote(sourceFile, preferences, name); +} - export function getCompletionEntriesFromSymbols( - symbols: readonly Symbol[], - entries: SortedArray, - replacementToken: Node | undefined, - contextToken: Node | undefined, - location: Node, - sourceFile: SourceFile, - host: LanguageServiceHost, - program: Program, - target: ScriptTarget, - log: Log, - kind: CompletionKind, - preferences: UserPreferences, - compilerOptions: CompilerOptions, - formatContext: formatting.FormatContext | undefined, - isTypeOnlyLocation?: boolean, - propertyAccessToConvert?: PropertyAccessExpression, - jsxIdentifierExpected?: boolean, - isJsxInitializer?: IsJsxInitializer, - importStatementCompletion?: ImportStatementCompletionInfo, - recommendedCompletion?: Symbol, - symbolToOriginInfoMap?: SymbolOriginInfoMap, - symbolToSortTextMap?: SymbolSortTextMap, - isJsxIdentifierExpected?: boolean, - isRightOfOpenTag?: boolean, - ): UniqueNameSet { - const start = timestamp(); - const variableDeclaration = getVariableDeclaration(location); - const useSemicolons = probablyUsesSemicolons(sourceFile); - const typeChecker = program.getTypeChecker(); - // Tracks unique names. - // Value is set to false for global variables or completions from external module exports, because we can have multiple of those; - // true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports. - // So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name. - const uniques = new Map(); - for (let i = 0; i < symbols.length; i++) { - const symbol = symbols[i]; - const origin = symbolToOriginInfoMap?.[i]; - const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind, !!jsxIdentifierExpected); - if (!info || (uniques.get(info.name) && (!origin || !originIsObjectLiteralMethod(origin))) || kind === CompletionKind.Global && symbolToSortTextMap && !shouldIncludeSymbol(symbol, symbolToSortTextMap)) { - continue; - } +function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol | undefined, checker: TypeChecker): boolean { + return localSymbol === recommendedCompletion || + !!(localSymbol.flags & SymbolFlags.ExportValue) && checker.getExportSymbolOfSymbol(localSymbol) === recommendedCompletion; +} - const { name, needsConvertPropertyAccess } = info; - const originalSortText = symbolToSortTextMap?.[getSymbolId(symbol)] ?? SortText.LocationPriority; - const sortText = (isDeprecated(symbol, typeChecker) ? SortText.Deprecated(originalSortText) : originalSortText); - const entry = createCompletionEntry( - symbol, - sortText, - replacementToken, - contextToken, - location, - sourceFile, - host, - program, - name, - needsConvertPropertyAccess, - origin, - recommendedCompletion, - propertyAccessToConvert, - isJsxInitializer, - importStatementCompletion, - useSemicolons, - compilerOptions, - preferences, - kind, - formatContext, - isJsxIdentifierExpected, - isRightOfOpenTag, - ); - if (!entry) { - continue; - } +function getSourceFromOrigin(origin: SymbolOriginInfo | undefined): string | undefined { + if (originIsExport(origin)) { + return stripQuotes(origin.moduleSymbol.name); + } + if (originIsResolvedExport(origin)) { + return origin.moduleSpecifier; + } + if (origin?.kind === SymbolOriginInfoKind.ThisType) { + return CompletionSource.ThisProperty; + } + if (origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias) { + return CompletionSource.TypeOnlyAlias; + } +} - /** True for locals; false for globals, module exports from other files, `this.` completions. */ - const shouldShadowLaterSymbols = (!origin || originIsTypeOnlyAlias(origin)) && !(symbol.parent === undefined && !some(symbol.declarations, d => d.getSourceFile() === location.getSourceFile())); - uniques.set(name, shouldShadowLaterSymbols); - insertSorted(entries, entry, compareCompletionEntries, /*allowDuplicates*/ true); +/** @internal */ +export function getCompletionEntriesFromSymbols( + symbols: readonly Symbol[], + entries: SortedArray, + replacementToken: Node | undefined, + contextToken: Node | undefined, + location: Node, + sourceFile: SourceFile, + host: LanguageServiceHost, + program: Program, + target: ScriptTarget, + log: Log, + kind: CompletionKind, + preferences: UserPreferences, + compilerOptions: CompilerOptions, + formatContext: formatting.FormatContext | undefined, + isTypeOnlyLocation?: boolean, + propertyAccessToConvert?: PropertyAccessExpression, + jsxIdentifierExpected?: boolean, + isJsxInitializer?: IsJsxInitializer, + importStatementCompletion?: ImportStatementCompletionInfo, + recommendedCompletion?: Symbol, + symbolToOriginInfoMap?: SymbolOriginInfoMap, + symbolToSortTextMap?: SymbolSortTextMap, + isJsxIdentifierExpected?: boolean, + isRightOfOpenTag?: boolean, +): UniqueNameSet { + const start = timestamp(); + const variableDeclaration = getVariableDeclaration(location); + const useSemicolons = probablyUsesSemicolons(sourceFile); + const typeChecker = program.getTypeChecker(); + // Tracks unique names. + // Value is set to false for global variables or completions from external module exports, because we can have multiple of those; + // true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports. + // So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name. + const uniques = new Map(); + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + const origin = symbolToOriginInfoMap?.[i]; + const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind, !!jsxIdentifierExpected); + if (!info || (uniques.get(info.name) && (!origin || !originIsObjectLiteralMethod(origin))) || kind === CompletionKind.Global && symbolToSortTextMap && !shouldIncludeSymbol(symbol, symbolToSortTextMap)) { + continue; + } + + const { name, needsConvertPropertyAccess } = info; + const originalSortText = symbolToSortTextMap?.[getSymbolId(symbol)] ?? SortText.LocationPriority; + const sortText = (isDeprecated(symbol, typeChecker) ? SortText.Deprecated(originalSortText) : originalSortText); + const entry = createCompletionEntry( + symbol, + sortText, + replacementToken, + contextToken, + location, + sourceFile, + host, + program, + name, + needsConvertPropertyAccess, + origin, + recommendedCompletion, + propertyAccessToConvert, + isJsxInitializer, + importStatementCompletion, + useSemicolons, + compilerOptions, + preferences, + kind, + formatContext, + isJsxIdentifierExpected, + isRightOfOpenTag, + ); + if (!entry) { + continue; } - log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (timestamp() - start)); + /** True for locals; false for globals, module exports from other files, `this.` completions. */ + const shouldShadowLaterSymbols = (!origin || originIsTypeOnlyAlias(origin)) && !(symbol.parent === undefined && !some(symbol.declarations, d => d.getSourceFile() === location.getSourceFile())); + uniques.set(name, shouldShadowLaterSymbols); + insertSorted(entries, entry, compareCompletionEntries, /*allowDuplicates*/ true); + } - // Prevent consumers of this map from having to worry about - // the boolean value. Externally, it should be seen as the - // set of all names. - return { - has: name => uniques.has(name), - add: name => uniques.set(name, true), - }; + log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (timestamp() - start)); - function shouldIncludeSymbol(symbol: Symbol, symbolToSortTextMap: SymbolSortTextMap): boolean { - let allFlags = symbol.flags; - if (!isSourceFile(location)) { - // export = /**/ here we want to get all meanings, so any symbol is ok - if (isExportAssignment(location.parent)) { - return true; - } - // Filter out variables from their own initializers - // `const a = /* no 'a' here */` - if (variableDeclaration && symbol.valueDeclaration === variableDeclaration) { - return false; - } + // Prevent consumers of this map from having to worry about + // the boolean value. Externally, it should be seen as the + // set of all names. + return { + has: name => uniques.has(name), + add: name => uniques.set(name, true), + }; - // External modules can have global export declarations that will be - // available as global keywords in all scopes. But if the external module - // already has an explicit export and user only wants to user explicit - // module imports then the global keywords will be filtered out so auto - // import suggestions will win in the completion - const symbolOrigin = skipAlias(symbol, typeChecker); - // We only want to filter out the global keywords - // Auto Imports are not available for scripts so this conditional is always false - if (!!sourceFile.externalModuleIndicator - && !compilerOptions.allowUmdGlobalAccess - && symbolToSortTextMap[getSymbolId(symbol)] === SortText.GlobalsOrKeywords - && (symbolToSortTextMap[getSymbolId(symbolOrigin)] === SortText.AutoImportSuggestions - || symbolToSortTextMap[getSymbolId(symbolOrigin)] === SortText.LocationPriority)) { - return false; - } + function shouldIncludeSymbol(symbol: Symbol, symbolToSortTextMap: SymbolSortTextMap): boolean { + let allFlags = symbol.flags; + if (!isSourceFile(location)) { + // export = /**/ here we want to get all meanings, so any symbol is ok + if (isExportAssignment(location.parent)) { + return true; + } + // Filter out variables from their own initializers + // `const a = /* no 'a' here */` + if (variableDeclaration && symbol.valueDeclaration === variableDeclaration) { + return false; + } - allFlags |= getCombinedLocalAndExportSymbolFlags(symbolOrigin); + // External modules can have global export declarations that will be + // available as global keywords in all scopes. But if the external module + // already has an explicit export and user only wants to user explicit + // module imports then the global keywords will be filtered out so auto + // import suggestions will win in the completion + const symbolOrigin = skipAlias(symbol, typeChecker); + // We only want to filter out the global keywords + // Auto Imports are not available for scripts so this conditional is always false + if (!!sourceFile.externalModuleIndicator + && !compilerOptions.allowUmdGlobalAccess + && symbolToSortTextMap[getSymbolId(symbol)] === SortText.GlobalsOrKeywords + && (symbolToSortTextMap[getSymbolId(symbolOrigin)] === SortText.AutoImportSuggestions + || symbolToSortTextMap[getSymbolId(symbolOrigin)] === SortText.LocationPriority)) { + return false; + } - // import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace) - if (isInRightSideOfInternalImportEqualsDeclaration(location)) { - return !!(allFlags & SymbolFlags.Namespace); - } + allFlags |= getCombinedLocalAndExportSymbolFlags(symbolOrigin); - if (isTypeOnlyLocation) { - // It's a type, but you can reach it by namespace.type as well - return symbolCanBeReferencedAtTypeLocation(symbol, typeChecker); - } + // import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace) + if (isInRightSideOfInternalImportEqualsDeclaration(location)) { + return !!(allFlags & SymbolFlags.Namespace); } - // expressions are value space (which includes the value namespaces) - return !!(allFlags & SymbolFlags.Value); + if (isTypeOnlyLocation) { + // It's a type, but you can reach it by namespace.type as well + return symbolCanBeReferencedAtTypeLocation(symbol, typeChecker); + } } + + // expressions are value space (which includes the value namespaces) + return !!(allFlags & SymbolFlags.Value); } +} - function getLabelCompletionAtPosition(node: BreakOrContinueStatement): CompletionInfo | undefined { - const entries = getLabelStatementCompletions(node); - if (entries.length) { - return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; - } +function getLabelCompletionAtPosition(node: BreakOrContinueStatement): CompletionInfo | undefined { + const entries = getLabelStatementCompletions(node); + if (entries.length) { + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; } +} - function getLabelStatementCompletions(node: Node): CompletionEntry[] { - const entries: CompletionEntry[] = []; - const uniques = new Map(); - let current = node; +function getLabelStatementCompletions(node: Node): CompletionEntry[] { + const entries: CompletionEntry[] = []; + const uniques = new Map(); + let current = node; - while (current) { - if (isFunctionLike(current)) { - break; - } - if (isLabeledStatement(current)) { - const name = current.label.text; - if (!uniques.has(name)) { - uniques.set(name, true); - entries.push({ - name, - kindModifiers: ScriptElementKindModifier.none, - kind: ScriptElementKind.label, - sortText: SortText.LocationPriority - }); - } - } - current = current.parent; + while (current) { + if (isFunctionLike(current)) { + break; } - return entries; - } - - interface SymbolCompletion { - type: "symbol"; - symbol: Symbol; - location: Node; - origin: SymbolOriginInfo | SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | undefined; - previousToken: Node | undefined; - contextToken: Node | undefined; - readonly isJsxInitializer: IsJsxInitializer; - readonly isTypeOnlyLocation: boolean; - } - function getSymbolCompletionFromEntryId( - program: Program, - log: Log, - sourceFile: SourceFile, - position: number, - entryId: CompletionEntryIdentifier, - host: LanguageServiceHost, - preferences: UserPreferences, - ): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } { - if (entryId.data) { - const autoImport = getAutoImportSymbolFromCompletionEntryData(entryId.name, entryId.data, program, host); - if (autoImport) { - const { contextToken, previousToken } = getRelevantTokens(position, sourceFile); - return { - type: "symbol", - symbol: autoImport.symbol, - location: getTouchingPropertyName(sourceFile, position), - previousToken, - contextToken, - isJsxInitializer: false, - isTypeOnlyLocation: false, - origin: autoImport.origin, - }; + if (isLabeledStatement(current)) { + const name = current.label.text; + if (!uniques.has(name)) { + uniques.set(name, true); + entries.push({ + name, + kindModifiers: ScriptElementKindModifier.none, + kind: ScriptElementKind.label, + sortText: SortText.LocationPriority + }); } } + current = current.parent; + } + return entries; +} - const compilerOptions = program.getCompilerOptions(); - const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host, /*formatContext*/ undefined); - if (!completionData) { - return { type: "none" }; - } - if (completionData.kind !== CompletionDataKind.Data) { - return { type: "request", request: completionData }; +interface SymbolCompletion { + type: "symbol"; + symbol: Symbol; + location: Node; + origin: SymbolOriginInfo | SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | undefined; + previousToken: Node | undefined; + contextToken: Node | undefined; + readonly isJsxInitializer: IsJsxInitializer; + readonly isTypeOnlyLocation: boolean; +} +function getSymbolCompletionFromEntryId( + program: Program, + log: Log, + sourceFile: SourceFile, + position: number, + entryId: CompletionEntryIdentifier, + host: LanguageServiceHost, + preferences: UserPreferences, +): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } { + if (entryId.data) { + const autoImport = getAutoImportSymbolFromCompletionEntryData(entryId.name, entryId.data, program, host); + if (autoImport) { + const { contextToken, previousToken } = getRelevantTokens(position, sourceFile); + return { + type: "symbol", + symbol: autoImport.symbol, + location: getTouchingPropertyName(sourceFile, position), + previousToken, + contextToken, + isJsxInitializer: false, + isTypeOnlyLocation: false, + origin: autoImport.origin, + }; } - - const { symbols, literals, location, completionKind, symbolToOriginInfoMap, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } = completionData; - - const literal = find(literals, l => completionNameForLiteral(sourceFile, preferences, l) === entryId.name); - if (literal !== undefined) return { type: "literal", literal }; - - // Find the symbol with the matching entry name. - // We don't need to perform character checks here because we're only comparing the - // name against 'entryName' (which is known to be good), not building a new - // completion entry. - return firstDefined(symbols, (symbol, index): SymbolCompletion | undefined => { - const origin = symbolToOriginInfoMap[index]; - const info = getCompletionEntryDisplayNameForSymbol(symbol, getEmitScriptTarget(compilerOptions), origin, completionKind, completionData.isJsxIdentifierExpected); - return info && info.name === entryId.name && ( - entryId.source === CompletionSource.ClassMemberSnippet && symbol.flags & SymbolFlags.ClassMember - || entryId.source === CompletionSource.ObjectLiteralMethodSnippet && symbol.flags & (SymbolFlags.Property | SymbolFlags.Method) - || getSourceFromOrigin(origin) === entryId.source) - ? { type: "symbol" as const, symbol, location, origin, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } - : undefined; - }) || { type: "none" }; } - export interface CompletionEntryIdentifier { - name: string; - source?: string; - data?: CompletionEntryData; + const compilerOptions = program.getCompilerOptions(); + const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host, /*formatContext*/ undefined); + if (!completionData) { + return { type: "none" }; } - - export function getCompletionEntryDetails( - program: Program, - log: Log, - sourceFile: SourceFile, - position: number, - entryId: CompletionEntryIdentifier, - host: LanguageServiceHost, - formatContext: formatting.FormatContext, - preferences: UserPreferences, - cancellationToken: CancellationToken, - ): CompletionEntryDetails | undefined { - const typeChecker = program.getTypeChecker(); - const compilerOptions = program.getCompilerOptions(); - const { name, source, data } = entryId; - - const contextToken = findPrecedingToken(position, sourceFile); - if (isInString(sourceFile, position, contextToken)) { - return StringCompletions.getStringLiteralCompletionDetails(name, sourceFile, position, contextToken, typeChecker, compilerOptions, host, cancellationToken, preferences); - } - - // Compute all the completion symbols again. - const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); - switch (symbolCompletion.type) { - case "request": { - const { request } = symbolCompletion; - switch (request.kind) { - case CompletionDataKind.JsDocTagName: - return JsDoc.getJSDocTagNameCompletionDetails(name); - case CompletionDataKind.JsDocTag: - return JsDoc.getJSDocTagCompletionDetails(name); - case CompletionDataKind.JsDocParameterName: - return JsDoc.getJSDocParameterNameCompletionDetails(name); - case CompletionDataKind.Keywords: - return some(request.keywordCompletions, c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; - default: - return Debug.assertNever(request); - } - } - case "symbol": { - const { symbol, location, contextToken, origin, previousToken } = symbolCompletion; - const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(name, location, contextToken, origin, symbol, program, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences, data, source, cancellationToken); - return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 - } - case "literal": { - const { literal } = symbolCompletion; - return createSimpleDetails(completionNameForLiteral(sourceFile, preferences, literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral); - } - case "none": - // Didn't find a symbol with this name. See if we can find a keyword instead. - return allKeywordsCompletions().some(c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; - default: - Debug.assertNever(symbolCompletion); - } + if (completionData.kind !== CompletionDataKind.Data) { + return { type: "request", request: completionData }; } - function createSimpleDetails(name: string, kind: ScriptElementKind, kind2: SymbolDisplayPartKind): CompletionEntryDetails { - return createCompletionDetails(name, ScriptElementKindModifier.none, kind, [displayPart(name, kind2)]); - } + const { symbols, literals, location, completionKind, symbolToOriginInfoMap, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } = completionData; + + const literal = find(literals, l => completionNameForLiteral(sourceFile, preferences, l) === entryId.name); + if (literal !== undefined) return { type: "literal", literal }; + + // Find the symbol with the matching entry name. + // We don't need to perform character checks here because we're only comparing the + // name against 'entryName' (which is known to be good), not building a new + // completion entry. + return firstDefined(symbols, (symbol, index): SymbolCompletion | undefined => { + const origin = symbolToOriginInfoMap[index]; + const info = getCompletionEntryDisplayNameForSymbol(symbol, getEmitScriptTarget(compilerOptions), origin, completionKind, completionData.isJsxIdentifierExpected); + return info && info.name === entryId.name && ( + entryId.source === CompletionSource.ClassMemberSnippet && symbol.flags & SymbolFlags.ClassMember + || entryId.source === CompletionSource.ObjectLiteralMethodSnippet && symbol.flags & (SymbolFlags.Property | SymbolFlags.Method) + || getSourceFromOrigin(origin) === entryId.source) + ? { type: "symbol" as const, symbol, location, origin, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } + : undefined; + }) || { type: "none" }; +} - export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { - const { displayParts, documentation, symbolKind, tags } = - checker.runWithCancellationToken(cancellationToken, checker => - SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All) - ); - return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); - } +/** @internal */ +export interface CompletionEntryIdentifier { + name: string; + source?: string; + data?: CompletionEntryData; +} - export function createCompletionDetails(name: string, kindModifiers: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], documentation?: SymbolDisplayPart[], tags?: JSDocTagInfo[], codeActions?: CodeAction[], source?: SymbolDisplayPart[]): CompletionEntryDetails { - return { name, kindModifiers, kind, displayParts, documentation, tags, codeActions, source, sourceDisplay: source }; +/** @internal */ +export function getCompletionEntryDetails( + program: Program, + log: Log, + sourceFile: SourceFile, + position: number, + entryId: CompletionEntryIdentifier, + host: LanguageServiceHost, + formatContext: formatting.FormatContext, + preferences: UserPreferences, + cancellationToken: CancellationToken, +): CompletionEntryDetails | undefined { + const typeChecker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); + const { name, source, data } = entryId; + + const contextToken = findPrecedingToken(position, sourceFile); + if (isInString(sourceFile, position, contextToken)) { + return StringCompletions.getStringLiteralCompletionDetails(name, sourceFile, position, contextToken, typeChecker, compilerOptions, host, cancellationToken, preferences); } - interface CodeActionsAndSourceDisplay { - readonly codeActions: CodeAction[] | undefined; - readonly sourceDisplay: SymbolDisplayPart[] | undefined; - } - function getCompletionEntryCodeActionsAndSourceDisplay( - name: string, - location: Node, - contextToken: Node | undefined, - origin: SymbolOriginInfo | SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | undefined, - symbol: Symbol, - program: Program, - host: LanguageServiceHost, - compilerOptions: CompilerOptions, - sourceFile: SourceFile, - position: number, - previousToken: Node | undefined, - formatContext: formatting.FormatContext, - preferences: UserPreferences, - data: CompletionEntryData | undefined, - source: string | undefined, - cancellationToken: CancellationToken, - ): CodeActionsAndSourceDisplay { - if (data?.moduleSpecifier) { - if (previousToken && getImportStatementCompletionInfo(contextToken || previousToken).replacementSpan) { - // Import statement completion: 'import c|' - return { codeActions: undefined, sourceDisplay: [textPart(data.moduleSpecifier)] }; + // Compute all the completion symbols again. + const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); + switch (symbolCompletion.type) { + case "request": { + const { request } = symbolCompletion; + switch (request.kind) { + case CompletionDataKind.JsDocTagName: + return JsDoc.getJSDocTagNameCompletionDetails(name); + case CompletionDataKind.JsDocTag: + return JsDoc.getJSDocTagCompletionDetails(name); + case CompletionDataKind.JsDocParameterName: + return JsDoc.getJSDocParameterNameCompletionDetails(name); + case CompletionDataKind.Keywords: + return some(request.keywordCompletions, c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; + default: + return Debug.assertNever(request); } } - - if (source === CompletionSource.ClassMemberSnippet) { - const { importAdder } = getEntryForMemberCompletion( - host, - program, - compilerOptions, - preferences, - name, - symbol, - location, - contextToken, - formatContext); - if (importAdder) { - const changes = textChanges.ChangeTracker.with( - { host, formatContext, preferences }, - importAdder.writeFixes); - return { - sourceDisplay: undefined, - codeActions: [{ - changes, - description: diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]), - }], - }; - } + case "symbol": { + const { symbol, location, contextToken, origin, previousToken } = symbolCompletion; + const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(name, location, contextToken, origin, symbol, program, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences, data, source, cancellationToken); + return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 + } + case "literal": { + const { literal } = symbolCompletion; + return createSimpleDetails(completionNameForLiteral(sourceFile, preferences, literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral); } + case "none": + // Didn't find a symbol with this name. See if we can find a keyword instead. + return allKeywordsCompletions().some(c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; + default: + Debug.assertNever(symbolCompletion); + } +} - if (originIsTypeOnlyAlias(origin)) { - const codeAction = codefix.getPromoteTypeOnlyCompletionAction( - sourceFile, - origin.declaration.name, - program, - host, - formatContext, - preferences); +function createSimpleDetails(name: string, kind: ScriptElementKind, kind2: SymbolDisplayPartKind): CompletionEntryDetails { + return createCompletionDetails(name, ScriptElementKindModifier.none, kind, [displayPart(name, kind2)]); +} - Debug.assertIsDefined(codeAction, "Expected to have a code action for promoting type-only alias"); - return { codeActions: [codeAction], sourceDisplay: undefined }; - } +/** @internal */ +export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { + const { displayParts, documentation, symbolKind, tags } = + checker.runWithCancellationToken(cancellationToken, checker => + SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All) + ); + return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); +} - if (!origin || !(originIsExport(origin) || originIsResolvedExport(origin))) { - return { codeActions: undefined, sourceDisplay: undefined }; +/** @internal */ +export function createCompletionDetails(name: string, kindModifiers: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], documentation?: SymbolDisplayPart[], tags?: JSDocTagInfo[], codeActions?: CodeAction[], source?: SymbolDisplayPart[]): CompletionEntryDetails { + return { name, kindModifiers, kind, displayParts, documentation, tags, codeActions, source, sourceDisplay: source }; +} + +interface CodeActionsAndSourceDisplay { + readonly codeActions: CodeAction[] | undefined; + readonly sourceDisplay: SymbolDisplayPart[] | undefined; +} +function getCompletionEntryCodeActionsAndSourceDisplay( + name: string, + location: Node, + contextToken: Node | undefined, + origin: SymbolOriginInfo | SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | undefined, + symbol: Symbol, + program: Program, + host: LanguageServiceHost, + compilerOptions: CompilerOptions, + sourceFile: SourceFile, + position: number, + previousToken: Node | undefined, + formatContext: formatting.FormatContext, + preferences: UserPreferences, + data: CompletionEntryData | undefined, + source: string | undefined, + cancellationToken: CancellationToken, +): CodeActionsAndSourceDisplay { + if (data?.moduleSpecifier) { + if (previousToken && getImportStatementCompletionInfo(contextToken || previousToken).replacementSpan) { + // Import statement completion: 'import c|' + return { codeActions: undefined, sourceDisplay: [textPart(data.moduleSpecifier)] }; } + } - const checker = origin.isFromPackageJson ? host.getPackageJsonAutoImportProvider!()!.getTypeChecker() : program.getTypeChecker(); - const { moduleSymbol } = origin; - const targetSymbol = checker.getMergedSymbol(skipAlias(symbol.exportSymbol || symbol, checker)); - const isJsxOpeningTagName = contextToken?.kind === SyntaxKind.LessThanToken && isJsxOpeningLikeElement(contextToken.parent); - const { moduleSpecifier, codeAction } = codefix.getImportCompletionAction( - targetSymbol, - moduleSymbol, - sourceFile, - getNameForExportedSymbol(symbol, getEmitScriptTarget(compilerOptions), isJsxOpeningTagName), - isJsxOpeningTagName, + if (source === CompletionSource.ClassMemberSnippet) { + const { importAdder } = getEntryForMemberCompletion( host, program, - formatContext, - previousToken && isIdentifier(previousToken) ? previousToken.getStart(sourceFile) : position, + compilerOptions, preferences, - cancellationToken); - Debug.assert(!data?.moduleSpecifier || moduleSpecifier === data.moduleSpecifier); - return { sourceDisplay: [textPart(moduleSpecifier)], codeActions: [codeAction] }; - } - - export function getCompletionEntrySymbol( - program: Program, - log: Log, - sourceFile: SourceFile, - position: number, - entryId: CompletionEntryIdentifier, - host: LanguageServiceHost, - preferences: UserPreferences, - ): Symbol | undefined { - const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); - return completion.type === "symbol" ? completion.symbol : undefined; - } - - const enum CompletionDataKind { Data, JsDocTagName, JsDocTag, JsDocParameterName, Keywords } - /** true: after the `=` sign but no identifier has been typed yet. Else is the Identifier after the initializer. */ - type IsJsxInitializer = boolean | Identifier; - interface CompletionData { - readonly kind: CompletionDataKind.Data; - readonly symbols: readonly Symbol[]; - readonly completionKind: CompletionKind; - readonly isInSnippetScope: boolean; - /** Note that the presence of this alone doesn't mean that we need a conversion. Only do that if the completion is not an ordinary identifier. */ - readonly propertyAccessToConvert: PropertyAccessExpression | undefined; - readonly isNewIdentifierLocation: boolean; - readonly location: Node; - readonly keywordFilters: KeywordCompletionFilters; - readonly literals: readonly (string | number | PseudoBigInt)[]; - readonly symbolToOriginInfoMap: SymbolOriginInfoMap; - readonly recommendedCompletion: Symbol | undefined; - readonly previousToken: Node | undefined; - readonly contextToken: Node | undefined; - readonly isJsxInitializer: IsJsxInitializer; - readonly insideJsDocTagTypeExpression: boolean; - readonly symbolToSortTextMap: SymbolSortTextMap; - readonly isTypeOnlyLocation: boolean; - /** In JSX tag name and attribute names, identifiers like "my-tag" or "aria-name" is valid identifier. */ - readonly isJsxIdentifierExpected: boolean; - readonly isRightOfOpenTag: boolean; - readonly importStatementCompletion?: ImportStatementCompletionInfo; - readonly hasUnresolvedAutoImports?: boolean; - readonly flags: CompletionInfoFlags; - } - type Request = - | { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } - | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag } - | { readonly kind: CompletionDataKind.Keywords, keywordCompletions: readonly CompletionEntry[], isNewIdentifierLocation: boolean }; - - export const enum CompletionKind { - ObjectPropertyDeclaration, - Global, - PropertyAccess, - MemberLike, - String, - None, - } - - function getRecommendedCompletion(previousToken: Node, contextualType: Type, checker: TypeChecker): Symbol | undefined { - // For a union, return the first one with a recommended completion. - return firstDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), type => { - const symbol = type && type.symbol; - // Don't include make a recommended completion for an abstract class - return symbol && (symbol.flags & (SymbolFlags.EnumMember | SymbolFlags.Enum | SymbolFlags.Class) && !isAbstractConstructorSymbol(symbol)) - ? getFirstSymbolInChain(symbol, previousToken, checker) - : undefined; - }); - } - - function getContextualType(previousToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Type | undefined { - const { parent } = previousToken; - switch (previousToken.kind) { - case SyntaxKind.Identifier: - return getContextualTypeFromParent(previousToken as Identifier, checker); - case SyntaxKind.EqualsToken: - switch (parent.kind) { - case SyntaxKind.VariableDeclaration: - return checker.getContextualType((parent as VariableDeclaration).initializer!); // TODO: GH#18217 - case SyntaxKind.BinaryExpression: - return checker.getTypeAtLocation((parent as BinaryExpression).left); - case SyntaxKind.JsxAttribute: - return checker.getContextualTypeForJsxAttribute(parent as JsxAttribute); - default: - return undefined; - } - case SyntaxKind.NewKeyword: - return checker.getContextualType(parent as Expression); - case SyntaxKind.CaseKeyword: - const caseClause = tryCast(parent, isCaseClause); - return caseClause ? getSwitchedType(caseClause, checker) : undefined; - case SyntaxKind.OpenBraceToken: - return isJsxExpression(parent) && !isJsxElement(parent.parent) && !isJsxFragment(parent.parent) ? checker.getContextualTypeForJsxAttribute(parent.parent) : undefined; - default: - const argInfo = SignatureHelp.getArgumentInfoForCompletions(previousToken, position, sourceFile); - return argInfo ? - // At `,`, treat this as the next argument after the comma. - checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (previousToken.kind === SyntaxKind.CommaToken ? 1 : 0)) : - isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) ? - // completion at `x ===/**/` should be for the right side - checker.getTypeAtLocation(parent.left) : - checker.getContextualType(previousToken as Expression); + name, + symbol, + location, + contextToken, + formatContext); + if (importAdder) { + const changes = textChanges.ChangeTracker.with( + { host, formatContext, preferences }, + importAdder.writeFixes); + return { + sourceDisplay: undefined, + codeActions: [{ + changes, + description: diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]), + }], + }; } } - function getFirstSymbolInChain(symbol: Symbol, enclosingDeclaration: Node, checker: TypeChecker): Symbol | undefined { - const chain = checker.getAccessibleSymbolChain(symbol, enclosingDeclaration, /*meaning*/ SymbolFlags.All, /*useOnlyExternalAliasing*/ false); - if (chain) return first(chain); - return symbol.parent && (isModuleSymbol(symbol.parent) ? symbol : getFirstSymbolInChain(symbol.parent, enclosingDeclaration, checker)); - } + if (originIsTypeOnlyAlias(origin)) { + const codeAction = codefix.getPromoteTypeOnlyCompletionAction( + sourceFile, + origin.declaration.name, + program, + host, + formatContext, + preferences); - function isModuleSymbol(symbol: Symbol): boolean { - return !!symbol.declarations?.some(d => d.kind === SyntaxKind.SourceFile); + Debug.assertIsDefined(codeAction, "Expected to have a code action for promoting type-only alias"); + return { codeActions: [codeAction], sourceDisplay: undefined }; } - function getCompletionData( - program: Program, - log: (message: string) => void, - sourceFile: SourceFile, - compilerOptions: CompilerOptions, - position: number, - preferences: UserPreferences, - detailsEntryId: CompletionEntryIdentifier | undefined, - host: LanguageServiceHost, - formatContext: formatting.FormatContext | undefined, - cancellationToken?: CancellationToken, - ): CompletionData | Request | undefined { - const typeChecker = program.getTypeChecker(); - const inCheckedFile = isCheckedFile(sourceFile, compilerOptions); - let start = timestamp(); - let currentToken = getTokenAtPosition(sourceFile, position); // TODO: GH#15853 - // We will check for jsdoc comments with insideComment and getJsDocTagAtPosition. (TODO: that seems rather inefficient to check the same thing so many times.) - - log("getCompletionData: Get current token: " + (timestamp() - start)); - - start = timestamp(); - const insideComment = isInComment(sourceFile, position, currentToken); - log("getCompletionData: Is inside comment: " + (timestamp() - start)); - - let insideJsDocTagTypeExpression = false; - let isInSnippetScope = false; - if (insideComment) { - if (hasDocComment(sourceFile, position)) { - if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { - // The current position is next to the '@' sign, when no tag name being provided yet. - // Provide a full list of tag names - return { kind: CompletionDataKind.JsDocTagName }; - } - else { - // When completion is requested without "@", we will have check to make sure that - // there are no comments prefix the request position. We will only allow "*" and space. - // e.g - // /** |c| /* - // - // /** - // |c| - // */ - // - // /** - // * |c| - // */ - // - // /** - // * |c| - // */ - const lineStart = getLineStartPositionForPosition(position, sourceFile); - if (!/[^\*|\s(/)]/.test(sourceFile.text.substring(lineStart, position))) { - return { kind: CompletionDataKind.JsDocTag }; - } - } - } + if (!origin || !(originIsExport(origin) || originIsResolvedExport(origin))) { + return { codeActions: undefined, sourceDisplay: undefined }; + } - // Completion should work inside certain JsDoc tags. For example: - // /** @type {number | string} */ - // Completion should work in the brackets - const tag = getJsDocTagAtPosition(currentToken, position); - if (tag) { - if (tag.tagName.pos <= position && position <= tag.tagName.end) { - return { kind: CompletionDataKind.JsDocTagName }; - } - const typeExpression = tryGetTypeExpressionFromTag(tag); - if (typeExpression) { - currentToken = getTokenAtPosition(sourceFile, position); - if (!currentToken || - (!isDeclarationName(currentToken) && - (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || - (currentToken.parent as JSDocPropertyTag).name !== currentToken))) { - // Use as type location if inside tag's type expression - insideJsDocTagTypeExpression = isCurrentlyEditingNode(typeExpression); - } - } - if (!insideJsDocTagTypeExpression && isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { - return { kind: CompletionDataKind.JsDocParameterName, tag }; - } - } + const checker = origin.isFromPackageJson ? host.getPackageJsonAutoImportProvider!()!.getTypeChecker() : program.getTypeChecker(); + const { moduleSymbol } = origin; + const targetSymbol = checker.getMergedSymbol(skipAlias(symbol.exportSymbol || symbol, checker)); + const isJsxOpeningTagName = contextToken?.kind === SyntaxKind.LessThanToken && isJsxOpeningLikeElement(contextToken.parent); + const { moduleSpecifier, codeAction } = codefix.getImportCompletionAction( + targetSymbol, + moduleSymbol, + sourceFile, + getNameForExportedSymbol(symbol, getEmitScriptTarget(compilerOptions), isJsxOpeningTagName), + isJsxOpeningTagName, + host, + program, + formatContext, + previousToken && isIdentifier(previousToken) ? previousToken.getStart(sourceFile) : position, + preferences, + cancellationToken); + Debug.assert(!data?.moduleSpecifier || moduleSpecifier === data.moduleSpecifier); + return { sourceDisplay: [textPart(moduleSpecifier)], codeActions: [codeAction] }; +} - if (!insideJsDocTagTypeExpression) { - // Proceed if the current position is in jsDoc tag expression; otherwise it is a normal - // comment or the plain text part of a jsDoc comment, so no completion should be available - log("Returning an empty list because completion was inside a regular comment or plain text part of a JsDoc comment."); - return undefined; - } - } +/** @internal */ +export function getCompletionEntrySymbol( + program: Program, + log: Log, + sourceFile: SourceFile, + position: number, + entryId: CompletionEntryIdentifier, + host: LanguageServiceHost, + preferences: UserPreferences, +): Symbol | undefined { + const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); + return completion.type === "symbol" ? completion.symbol : undefined; +} - start = timestamp(); - // The decision to provide completion depends on the contextToken, which is determined through the previousToken. - // Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file - const isJsOnlyLocation = !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile); - const tokens = getRelevantTokens(position, sourceFile); - const previousToken = tokens.previousToken!; - let contextToken = tokens.contextToken!; - log("getCompletionData: Get previous token: " + (timestamp() - start)); - - // Find the node where completion is requested on. - // Also determine whether we are trying to complete with members of that node - // or attributes of a JSX tag. - let node = currentToken; - let propertyAccessToConvert: PropertyAccessExpression | undefined; - let isRightOfDot = false; - let isRightOfQuestionDot = false; - let isRightOfOpenTag = false; - let isStartingCloseTag = false; - let isJsxInitializer: IsJsxInitializer = false; - let isJsxIdentifierExpected = false; - let importStatementCompletion: ImportStatementCompletionInfo | undefined; - let location = getTouchingPropertyName(sourceFile, position); - let keywordFilters = KeywordCompletionFilters.None; - let isNewIdentifierLocation = false; - let flags = CompletionInfoFlags.None; +const enum CompletionDataKind { Data, JsDocTagName, JsDocTag, JsDocParameterName, Keywords } +/** + * true: after the `=` sign but no identifier has been typed yet. Else is the Identifier after the initializer. + * + * @internal + */ +export type IsJsxInitializer = boolean | Identifier; +interface CompletionData { + readonly kind: CompletionDataKind.Data; + readonly symbols: readonly Symbol[]; + readonly completionKind: CompletionKind; + readonly isInSnippetScope: boolean; + /** Note that the presence of this alone doesn't mean that we need a conversion. Only do that if the completion is not an ordinary identifier. */ + readonly propertyAccessToConvert: PropertyAccessExpression | undefined; + readonly isNewIdentifierLocation: boolean; + readonly location: Node; + readonly keywordFilters: KeywordCompletionFilters; + readonly literals: readonly (string | number | PseudoBigInt)[]; + readonly symbolToOriginInfoMap: SymbolOriginInfoMap; + readonly recommendedCompletion: Symbol | undefined; + readonly previousToken: Node | undefined; + readonly contextToken: Node | undefined; + readonly isJsxInitializer: IsJsxInitializer; + readonly insideJsDocTagTypeExpression: boolean; + readonly symbolToSortTextMap: SymbolSortTextMap; + readonly isTypeOnlyLocation: boolean; + /** In JSX tag name and attribute names, identifiers like "my-tag" or "aria-name" is valid identifier. */ + readonly isJsxIdentifierExpected: boolean; + readonly isRightOfOpenTag: boolean; + readonly importStatementCompletion?: ImportStatementCompletionInfo; + readonly hasUnresolvedAutoImports?: boolean; + readonly flags: CompletionInfoFlags; +} +type Request = + | { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } + | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag } + | { readonly kind: CompletionDataKind.Keywords, keywordCompletions: readonly CompletionEntry[], isNewIdentifierLocation: boolean }; + +/** @internal */ +export const enum CompletionKind { + ObjectPropertyDeclaration, + Global, + PropertyAccess, + MemberLike, + String, + None, +} - if (contextToken) { - const importStatementCompletionInfo = getImportStatementCompletionInfo(contextToken); - if (importStatementCompletionInfo.keywordCompletion) { - if (importStatementCompletionInfo.isKeywordOnlyCompletion) { - return { - kind: CompletionDataKind.Keywords, - keywordCompletions: [keywordToCompletionEntry(importStatementCompletionInfo.keywordCompletion)], - isNewIdentifierLocation: importStatementCompletionInfo.isNewIdentifierLocation, - }; - } - keywordFilters = keywordFiltersFromSyntaxKind(importStatementCompletionInfo.keywordCompletion); - } - if (importStatementCompletionInfo.replacementSpan && preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) { - // Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier` - // added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature - // is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients - // to opt in with the `includeCompletionsForImportStatements` user preference. - flags |= CompletionInfoFlags.IsImportStatementCompletion; - importStatementCompletion = importStatementCompletionInfo; - isNewIdentifierLocation = importStatementCompletionInfo.isNewIdentifierLocation; - } - // Bail out if this is a known invalid completion location - if (!importStatementCompletionInfo.replacementSpan && isCompletionListBlocker(contextToken)) { - log("Returning an empty list because completion was requested in an invalid position."); - return keywordFilters - ? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierDefinitionLocation()) - : undefined; - } +function getRecommendedCompletion(previousToken: Node, contextualType: Type, checker: TypeChecker): Symbol | undefined { + // For a union, return the first one with a recommended completion. + return firstDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), type => { + const symbol = type && type.symbol; + // Don't include make a recommended completion for an abstract class + return symbol && (symbol.flags & (SymbolFlags.EnumMember | SymbolFlags.Enum | SymbolFlags.Class) && !isAbstractConstructorSymbol(symbol)) + ? getFirstSymbolInChain(symbol, previousToken, checker) + : undefined; + }); +} - let parent = contextToken.parent; - if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { - isRightOfDot = contextToken.kind === SyntaxKind.DotToken; - isRightOfQuestionDot = contextToken.kind === SyntaxKind.QuestionDotToken; - switch (parent.kind) { - case SyntaxKind.PropertyAccessExpression: - propertyAccessToConvert = parent as PropertyAccessExpression; - node = propertyAccessToConvert.expression; - const leftmostAccessExpression = getLeftmostAccessExpression(propertyAccessToConvert); - if (nodeIsMissing(leftmostAccessExpression) || - ((isCallExpression(node) || isFunctionLike(node)) && - node.end === contextToken.pos && - node.getChildCount(sourceFile) && - last(node.getChildren(sourceFile)).kind !== SyntaxKind.CloseParenToken)) { - // This is likely dot from incorrectly parsed expression and user is starting to write spread - // eg: Math.min(./**/) - // const x = function (./**/) {} - // ({./**/}) - return undefined; - } - break; - case SyntaxKind.QualifiedName: - node = (parent as QualifiedName).left; - break; - case SyntaxKind.ModuleDeclaration: - node = (parent as ModuleDeclaration).name; - break; - case SyntaxKind.ImportType: - node = parent; - break; - case SyntaxKind.MetaProperty: - node = parent.getFirstToken(sourceFile)!; - Debug.assert(node.kind === SyntaxKind.ImportKeyword || node.kind === SyntaxKind.NewKeyword); - break; - default: - // There is nothing that precedes the dot, so this likely just a stray character - // or leading into a '...' token. Just bail out instead. - return undefined; - } +function getContextualType(previousToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Type | undefined { + const { parent } = previousToken; + switch (previousToken.kind) { + case SyntaxKind.Identifier: + return getContextualTypeFromParent(previousToken as Identifier, checker); + case SyntaxKind.EqualsToken: + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + return checker.getContextualType((parent as VariableDeclaration).initializer!); // TODO: GH#18217 + case SyntaxKind.BinaryExpression: + return checker.getTypeAtLocation((parent as BinaryExpression).left); + case SyntaxKind.JsxAttribute: + return checker.getContextualTypeForJsxAttribute(parent as JsxAttribute); + default: + return undefined; } - else if (!importStatementCompletion) { - // - // If the tagname is a property access expression, we will then walk up to the top most of property access expression. - // Then, try to get a JSX container and its associated attributes type. - if (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { - contextToken = parent; - parent = parent.parent; - } - - // Fix location - if (currentToken.parent === location) { - switch (currentToken.kind) { - case SyntaxKind.GreaterThanToken: - if (currentToken.parent.kind === SyntaxKind.JsxElement || currentToken.parent.kind === SyntaxKind.JsxOpeningElement) { - location = currentToken; - } - break; - - case SyntaxKind.SlashToken: - if (currentToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { - location = currentToken; - } - break; - } - } - - switch (parent.kind) { - case SyntaxKind.JsxClosingElement: - if (contextToken.kind === SyntaxKind.SlashToken) { - isStartingCloseTag = true; - location = contextToken; - } - break; - - case SyntaxKind.BinaryExpression: - if (!binaryExpressionMayBeOpenTag(parent as BinaryExpression)) { - break; - } - // falls through + case SyntaxKind.NewKeyword: + return checker.getContextualType(parent as Expression); + case SyntaxKind.CaseKeyword: + const caseClause = tryCast(parent, isCaseClause); + return caseClause ? getSwitchedType(caseClause, checker) : undefined; + case SyntaxKind.OpenBraceToken: + return isJsxExpression(parent) && !isJsxElement(parent.parent) && !isJsxFragment(parent.parent) ? checker.getContextualTypeForJsxAttribute(parent.parent) : undefined; + default: + const argInfo = SignatureHelp.getArgumentInfoForCompletions(previousToken, position, sourceFile); + return argInfo ? + // At `,`, treat this as the next argument after the comma. + checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (previousToken.kind === SyntaxKind.CommaToken ? 1 : 0)) : + isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) ? + // completion at `x ===/**/` should be for the right side + checker.getTypeAtLocation(parent.left) : + checker.getContextualType(previousToken as Expression); + } +} - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxElement: - case SyntaxKind.JsxOpeningElement: - isJsxIdentifierExpected = true; - if (contextToken.kind === SyntaxKind.LessThanToken) { - isRightOfOpenTag = true; - location = contextToken; - } - break; +function getFirstSymbolInChain(symbol: Symbol, enclosingDeclaration: Node, checker: TypeChecker): Symbol | undefined { + const chain = checker.getAccessibleSymbolChain(symbol, enclosingDeclaration, /*meaning*/ SymbolFlags.All, /*useOnlyExternalAliasing*/ false); + if (chain) return first(chain); + return symbol.parent && (isModuleSymbol(symbol.parent) ? symbol : getFirstSymbolInChain(symbol.parent, enclosingDeclaration, checker)); +} - case SyntaxKind.JsxExpression: - case SyntaxKind.JsxSpreadAttribute: - // For `
`, `parent` will be `{true}` and `previousToken` will be `}` - if (previousToken.kind === SyntaxKind.CloseBraceToken && currentToken.kind === SyntaxKind.GreaterThanToken) { - isJsxIdentifierExpected = true; - } - break; +function isModuleSymbol(symbol: Symbol): boolean { + return !!symbol.declarations?.some(d => d.kind === SyntaxKind.SourceFile); +} - case SyntaxKind.JsxAttribute: - // For `
`, `parent` will be JsxAttribute and `previousToken` will be its initializer - if ((parent as JsxAttribute).initializer === previousToken && - previousToken.end < position) { - isJsxIdentifierExpected = true; - break; - } - switch (previousToken.kind) { - case SyntaxKind.EqualsToken: - isJsxInitializer = true; - break; - case SyntaxKind.Identifier: - isJsxIdentifierExpected = true; - // For `
` we don't want to treat this as a jsx inializer, instead it's the attribute name. - if (parent !== previousToken.parent && - !(parent as JsxAttribute).initializer && - findChildOfKind(parent, SyntaxKind.EqualsToken, sourceFile)) { - isJsxInitializer = previousToken as Identifier; - } - } - break; +function getCompletionData( + program: Program, + log: (message: string) => void, + sourceFile: SourceFile, + compilerOptions: CompilerOptions, + position: number, + preferences: UserPreferences, + detailsEntryId: CompletionEntryIdentifier | undefined, + host: LanguageServiceHost, + formatContext: formatting.FormatContext | undefined, + cancellationToken?: CancellationToken, +): CompletionData | Request | undefined { + const typeChecker = program.getTypeChecker(); + const inCheckedFile = isCheckedFile(sourceFile, compilerOptions); + let start = timestamp(); + let currentToken = getTokenAtPosition(sourceFile, position); // TODO: GH#15853 + // We will check for jsdoc comments with insideComment and getJsDocTagAtPosition. (TODO: that seems rather inefficient to check the same thing so many times.) + + log("getCompletionData: Get current token: " + (timestamp() - start)); + + start = timestamp(); + const insideComment = isInComment(sourceFile, position, currentToken); + log("getCompletionData: Is inside comment: " + (timestamp() - start)); + + let insideJsDocTagTypeExpression = false; + let isInSnippetScope = false; + if (insideComment) { + if (hasDocComment(sourceFile, position)) { + if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { + // The current position is next to the '@' sign, when no tag name being provided yet. + // Provide a full list of tag names + return { kind: CompletionDataKind.JsDocTagName }; + } + else { + // When completion is requested without "@", we will have check to make sure that + // there are no comments prefix the request position. We will only allow "*" and space. + // e.g + // /** |c| /* + // + // /** + // |c| + // */ + // + // /** + // * |c| + // */ + // + // /** + // * |c| + // */ + const lineStart = getLineStartPositionForPosition(position, sourceFile); + if (!/[^\*|\s(/)]/.test(sourceFile.text.substring(lineStart, position))) { + return { kind: CompletionDataKind.JsDocTag }; } } } - const semanticStart = timestamp(); - let completionKind = CompletionKind.None; - let isNonContextualObjectLiteral = false; - let hasUnresolvedAutoImports = false; - // This also gets mutated in nested-functions after the return - let symbols: Symbol[] = []; - let importSpecifierResolver: codefix.ImportSpecifierResolver | undefined; - const symbolToOriginInfoMap: SymbolOriginInfoMap = []; - const symbolToSortTextMap: SymbolSortTextMap = []; - const seenPropertySymbols = new Map(); - const isTypeOnlyLocation = isTypeOnlyCompletion(); - const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => { - return createModuleSpecifierResolutionHost(isFromPackageJson ? host.getPackageJsonAutoImportProvider!()! : program, host); - }); - - if (isRightOfDot || isRightOfQuestionDot) { - getTypeScriptMemberSymbols(); - } - else if (isRightOfOpenTag) { - symbols = typeChecker.getJsxIntrinsicTagNamesAt(location); - Debug.assertEachIsDefined(symbols, "getJsxIntrinsicTagNames() should all be defined"); - tryGetGlobalSymbols(); - completionKind = CompletionKind.Global; - keywordFilters = KeywordCompletionFilters.None; - } - else if (isStartingCloseTag) { - const tagName = (contextToken.parent.parent as JsxElement).openingElement.tagName; - const tagSymbol = typeChecker.getSymbolAtLocation(tagName); - if (tagSymbol) { - symbols = [tagSymbol]; + // Completion should work inside certain JsDoc tags. For example: + // /** @type {number | string} */ + // Completion should work in the brackets + const tag = getJsDocTagAtPosition(currentToken, position); + if (tag) { + if (tag.tagName.pos <= position && position <= tag.tagName.end) { + return { kind: CompletionDataKind.JsDocTagName }; } - completionKind = CompletionKind.Global; - keywordFilters = KeywordCompletionFilters.None; - } - else { - // For JavaScript or TypeScript, if we're not after a dot, then just try to get the - // global symbols in scope. These results should be valid for either language as - // the set of symbols that can be referenced from this location. - if (!tryGetGlobalSymbols()) { - return keywordFilters - ? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierLocation) - : undefined; + const typeExpression = tryGetTypeExpressionFromTag(tag); + if (typeExpression) { + currentToken = getTokenAtPosition(sourceFile, position); + if (!currentToken || + (!isDeclarationName(currentToken) && + (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || + (currentToken.parent as JSDocPropertyTag).name !== currentToken))) { + // Use as type location if inside tag's type expression + insideJsDocTagTypeExpression = isCurrentlyEditingNode(typeExpression); + } } - } - - log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); - const contextualType = previousToken && getContextualType(previousToken, position, sourceFile, typeChecker); - - const literals = mapDefined( - contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), - t => t.isLiteral() && !(t.flags & TypeFlags.EnumLiteral) ? t.value : undefined); - - const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion(previousToken, contextualType, typeChecker); - return { - kind: CompletionDataKind.Data, - symbols, - completionKind, - isInSnippetScope, - propertyAccessToConvert, - isNewIdentifierLocation, - location, - keywordFilters, - literals, - symbolToOriginInfoMap, - recommendedCompletion, - previousToken, - contextToken, - isJsxInitializer, - insideJsDocTagTypeExpression, - symbolToSortTextMap, - isTypeOnlyLocation, - isJsxIdentifierExpected, - isRightOfOpenTag, - importStatementCompletion, - hasUnresolvedAutoImports, - flags, - }; - - type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag | JSDocTemplateTag; - - function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { - switch (tag.kind) { - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - case SyntaxKind.JSDocReturnTag: - case SyntaxKind.JSDocTypeTag: - case SyntaxKind.JSDocTypedefTag: - return true; - case SyntaxKind.JSDocTemplateTag: - return !!(tag as JSDocTemplateTag).constraint; - default: - return false; + if (!insideJsDocTagTypeExpression && isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { + return { kind: CompletionDataKind.JsDocParameterName, tag }; } } - function tryGetTypeExpressionFromTag(tag: JSDocTag): JSDocTypeExpression | undefined { - if (isTagWithTypeExpression(tag)) { - const typeExpression = isJSDocTemplateTag(tag) ? tag.constraint : tag.typeExpression; - return typeExpression && typeExpression.kind === SyntaxKind.JSDocTypeExpression ? typeExpression : undefined; - } + if (!insideJsDocTagTypeExpression) { + // Proceed if the current position is in jsDoc tag expression; otherwise it is a normal + // comment or the plain text part of a jsDoc comment, so no completion should be available + log("Returning an empty list because completion was inside a regular comment or plain text part of a JsDoc comment."); return undefined; } + } - function getTypeScriptMemberSymbols(): void { - // Right of dot member completion list - completionKind = CompletionKind.PropertyAccess; - - // Since this is qualified name check it's a type node location - const isImportType = isLiteralImportTypeNode(node); - const isTypeLocation = insideJsDocTagTypeExpression - || (isImportType && !(node as ImportTypeNode).isTypeOf) - || isPartOfTypeNode(node.parent) - || isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker); - const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node); - if (isEntityName(node) || isImportType || isPropertyAccessExpression(node)) { - const isNamespaceName = isModuleDeclaration(node.parent); - if (isNamespaceName) isNewIdentifierLocation = true; - let symbol = typeChecker.getSymbolAtLocation(node); - if (symbol) { - symbol = skipAlias(symbol, typeChecker); - if (symbol.flags & (SymbolFlags.Module | SymbolFlags.Enum)) { - // Extract module or enum members - const exportedSymbols = typeChecker.getExportsOfModule(symbol); - Debug.assertEachIsDefined(exportedSymbols, "getExportsOfModule() should all be defined"); - const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(isImportType ? node as ImportTypeNode : (node.parent as PropertyAccessExpression), symbol.name); - const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol, typeChecker); - const isValidAccess: (symbol: Symbol) => boolean = - isNamespaceName - // At `namespace N.M/**/`, if this is the only declaration of `M`, don't include `M` as a completion. - ? symbol => !!(symbol.flags & SymbolFlags.Namespace) && !symbol.declarations?.every(d => d.parent === node.parent) - : isRhsOfImportDeclaration ? - // Any kind is allowed when dotting off namespace in internal import equals declaration - symbol => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : - isTypeLocation ? isValidTypeAccess : isValidValueAccess; - for (const exportedSymbol of exportedSymbols) { - if (isValidAccess(exportedSymbol)) { - symbols.push(exportedSymbol); - } - } - - // If the module is merged with a value, we must get the type of the class and add its propertes (for inherited static methods). - if (!isTypeLocation && - symbol.declarations && - symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { - let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType(); - let insertQuestionDot = false; - if (type.isNullableType()) { - const canCorrectToQuestionDot = - isRightOfDot && - !isRightOfQuestionDot && - preferences.includeAutomaticOptionalChainCompletions !== false; - - if (canCorrectToQuestionDot || isRightOfQuestionDot) { - type = type.getNonNullableType(); - if (canCorrectToQuestionDot) { - insertQuestionDot = true; - } - } - } - addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); - } - - return; - } - } + start = timestamp(); + // The decision to provide completion depends on the contextToken, which is determined through the previousToken. + // Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file + const isJsOnlyLocation = !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile); + const tokens = getRelevantTokens(position, sourceFile); + const previousToken = tokens.previousToken!; + let contextToken = tokens.contextToken!; + log("getCompletionData: Get previous token: " + (timestamp() - start)); + + // Find the node where completion is requested on. + // Also determine whether we are trying to complete with members of that node + // or attributes of a JSX tag. + let node = currentToken; + let propertyAccessToConvert: PropertyAccessExpression | undefined; + let isRightOfDot = false; + let isRightOfQuestionDot = false; + let isRightOfOpenTag = false; + let isStartingCloseTag = false; + let isJsxInitializer: IsJsxInitializer = false; + let isJsxIdentifierExpected = false; + let importStatementCompletion: ImportStatementCompletionInfo | undefined; + let location = getTouchingPropertyName(sourceFile, position); + let keywordFilters = KeywordCompletionFilters.None; + let isNewIdentifierLocation = false; + let flags = CompletionInfoFlags.None; + + if (contextToken) { + const importStatementCompletionInfo = getImportStatementCompletionInfo(contextToken); + if (importStatementCompletionInfo.keywordCompletion) { + if (importStatementCompletionInfo.isKeywordOnlyCompletion) { + return { + kind: CompletionDataKind.Keywords, + keywordCompletions: [keywordToCompletionEntry(importStatementCompletionInfo.keywordCompletion)], + isNewIdentifierLocation: importStatementCompletionInfo.isNewIdentifierLocation, + }; } + keywordFilters = keywordFiltersFromSyntaxKind(importStatementCompletionInfo.keywordCompletion); + } + if (importStatementCompletionInfo.replacementSpan && preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) { + // Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier` + // added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature + // is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients + // to opt in with the `includeCompletionsForImportStatements` user preference. + flags |= CompletionInfoFlags.IsImportStatementCompletion; + importStatementCompletion = importStatementCompletionInfo; + isNewIdentifierLocation = importStatementCompletionInfo.isNewIdentifierLocation; + } + // Bail out if this is a known invalid completion location + if (!importStatementCompletionInfo.replacementSpan && isCompletionListBlocker(contextToken)) { + log("Returning an empty list because completion was requested in an invalid position."); + return keywordFilters + ? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierDefinitionLocation()) + : undefined; + } - if (!isTypeLocation) { - // GH#39946. Pulling on the type of a node inside of a function with a contextual `this` parameter can result in a circularity - // if the `node` is part of the exprssion of a `yield` or `return`. This circularity doesn't exist at compile time because - // we will check (and cache) the type of `this` *before* checking the type of the node. - typeChecker.tryGetThisTypeAt(node, /*includeGlobalThis*/ false); - - let type = typeChecker.getTypeAtLocation(node).getNonOptionalType(); - let insertQuestionDot = false; - if (type.isNullableType()) { - const canCorrectToQuestionDot = - isRightOfDot && - !isRightOfQuestionDot && - preferences.includeAutomaticOptionalChainCompletions !== false; - - if (canCorrectToQuestionDot || isRightOfQuestionDot) { - type = type.getNonNullableType(); - if (canCorrectToQuestionDot) { - insertQuestionDot = true; - } + let parent = contextToken.parent; + if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { + isRightOfDot = contextToken.kind === SyntaxKind.DotToken; + isRightOfQuestionDot = contextToken.kind === SyntaxKind.QuestionDotToken; + switch (parent.kind) { + case SyntaxKind.PropertyAccessExpression: + propertyAccessToConvert = parent as PropertyAccessExpression; + node = propertyAccessToConvert.expression; + const leftmostAccessExpression = getLeftmostAccessExpression(propertyAccessToConvert); + if (nodeIsMissing(leftmostAccessExpression) || + ((isCallExpression(node) || isFunctionLike(node)) && + node.end === contextToken.pos && + node.getChildCount(sourceFile) && + last(node.getChildren(sourceFile)).kind !== SyntaxKind.CloseParenToken)) { + // This is likely dot from incorrectly parsed expression and user is starting to write spread + // eg: Math.min(./**/) + // const x = function (./**/) {} + // ({./**/}) + return undefined; } - } - addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); + break; + case SyntaxKind.QualifiedName: + node = (parent as QualifiedName).left; + break; + case SyntaxKind.ModuleDeclaration: + node = (parent as ModuleDeclaration).name; + break; + case SyntaxKind.ImportType: + node = parent; + break; + case SyntaxKind.MetaProperty: + node = parent.getFirstToken(sourceFile)!; + Debug.assert(node.kind === SyntaxKind.ImportKeyword || node.kind === SyntaxKind.NewKeyword); + break; + default: + // There is nothing that precedes the dot, so this likely just a stray character + // or leading into a '...' token. Just bail out instead. + return undefined; } } - - function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void { - isNewIdentifierLocation = !!type.getStringIndexType(); - if (isRightOfQuestionDot && some(type.getCallSignatures())) { - isNewIdentifierLocation = true; + else if (!importStatementCompletion) { + // + // If the tagname is a property access expression, we will then walk up to the top most of property access expression. + // Then, try to get a JSX container and its associated attributes type. + if (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { + contextToken = parent; + parent = parent.parent; } - const propertyAccess = node.kind === SyntaxKind.ImportType ? node as ImportTypeNode : node.parent as PropertyAccessExpression | QualifiedName; - if (inCheckedFile) { - for (const symbol of type.getApparentProperties()) { - if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) { - addPropertySymbol(symbol, /* insertAwait */ false, insertQuestionDot); - } - } - } - else { - // In javascript files, for union types, we don't just get the members that - // the individual types have in common, we also include all the members that - // each individual type has. This is because we're going to add all identifiers - // anyways. So we might as well elevate the members that were at least part - // of the individual types to a higher status since we know what they are. - symbols.push(...filter(getPropertiesForCompletion(type, typeChecker), s => typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, s))); - } + // Fix location + if (currentToken.parent === location) { + switch (currentToken.kind) { + case SyntaxKind.GreaterThanToken: + if (currentToken.parent.kind === SyntaxKind.JsxElement || currentToken.parent.kind === SyntaxKind.JsxOpeningElement) { + location = currentToken; + } + break; - if (insertAwait && preferences.includeCompletionsWithInsertText) { - const promiseType = typeChecker.getPromisedTypeOfPromise(type); - if (promiseType) { - for (const symbol of promiseType.getApparentProperties()) { - if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, promiseType, symbol)) { - addPropertySymbol(symbol, /* insertAwait */ true, insertQuestionDot); + case SyntaxKind.SlashToken: + if (currentToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { + location = currentToken; } - } + break; } } - } - function addPropertySymbol(symbol: Symbol, insertAwait: boolean, insertQuestionDot: boolean) { - // For a computed property with an accessible name like `Symbol.iterator`, - // we'll add a completion for the *name* `Symbol` instead of for the property. - // If this is e.g. [Symbol.iterator], add a completion for `Symbol`. - const computedPropertyName = firstDefined(symbol.declarations, decl => tryCast(getNameOfDeclaration(decl), isComputedPropertyName)); - if (computedPropertyName) { - const leftMostName = getLeftMostName(computedPropertyName.expression); // The completion is for `Symbol`, not `iterator`. - const nameSymbol = leftMostName && typeChecker.getSymbolAtLocation(leftMostName); - // If this is nested like for `namespace N { export const sym = Symbol(); }`, we'll add the completion for `N`. - const firstAccessibleSymbol = nameSymbol && getFirstSymbolInChain(nameSymbol, contextToken, typeChecker); - if (firstAccessibleSymbol && addToSeen(seenPropertySymbols, getSymbolId(firstAccessibleSymbol))) { - const index = symbols.length; - symbols.push(firstAccessibleSymbol); - const moduleSymbol = firstAccessibleSymbol.parent; - if (!moduleSymbol || - !isExternalModuleSymbol(moduleSymbol) || - typeChecker.tryGetMemberInModuleExportsAndProperties(firstAccessibleSymbol.name, moduleSymbol) !== firstAccessibleSymbol - ) { - symbolToOriginInfoMap[index] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberNoExport) }; + switch (parent.kind) { + case SyntaxKind.JsxClosingElement: + if (contextToken.kind === SyntaxKind.SlashToken) { + isStartingCloseTag = true; + location = contextToken; } - else { - const fileName = isExternalModuleNameRelative(stripQuotes(moduleSymbol.name)) ? getSourceFileOfModule(moduleSymbol)?.fileName : undefined; - const { moduleSpecifier } = (importSpecifierResolver ||= codefix.createImportSpecifierResolver(sourceFile, program, host, preferences)).getModuleSpecifierForBestExportInfo([{ - exportKind: ExportKind.Named, - moduleFileName: fileName, - isFromPackageJson: false, - moduleSymbol, - symbol: firstAccessibleSymbol, - targetFlags: skipAlias(firstAccessibleSymbol, typeChecker).flags, - }], firstAccessibleSymbol.name, position, isValidTypeOnlyAliasUseSite(location)) || {}; - - if (moduleSpecifier) { - const origin: SymbolOriginInfoResolvedExport = { - kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport), - moduleSymbol, - isDefaultExport: false, - symbolName: firstAccessibleSymbol.name, - exportName: firstAccessibleSymbol.name, - fileName, - moduleSpecifier, - }; - symbolToOriginInfoMap[index] = origin; - } + break; + + case SyntaxKind.BinaryExpression: + if (!binaryExpressionMayBeOpenTag(parent as BinaryExpression)) { + break; } - } - else if (preferences.includeCompletionsWithInsertText) { - addSymbolOriginInfo(symbol); - addSymbolSortInfo(symbol); - symbols.push(symbol); - } - } - else { - addSymbolOriginInfo(symbol); - addSymbolSortInfo(symbol); - symbols.push(symbol); - } + // falls through + + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxElement: + case SyntaxKind.JsxOpeningElement: + isJsxIdentifierExpected = true; + if (contextToken.kind === SyntaxKind.LessThanToken) { + isRightOfOpenTag = true; + location = contextToken; + } + break; - function addSymbolSortInfo(symbol: Symbol) { - if (isStaticProperty(symbol)) { - symbolToSortTextMap[getSymbolId(symbol)] = SortText.LocalDeclarationPriority; - } - } + case SyntaxKind.JsxExpression: + case SyntaxKind.JsxSpreadAttribute: + // For `
`, `parent` will be `{true}` and `previousToken` will be `}` + if (previousToken.kind === SyntaxKind.CloseBraceToken && currentToken.kind === SyntaxKind.GreaterThanToken) { + isJsxIdentifierExpected = true; + } + break; - function addSymbolOriginInfo(symbol: Symbol) { - if (preferences.includeCompletionsWithInsertText) { - if (insertAwait && addToSeen(seenPropertySymbols, getSymbolId(symbol))) { - symbolToOriginInfoMap[symbols.length] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.Promise) }; + case SyntaxKind.JsxAttribute: + // For `
`, `parent` will be JsxAttribute and `previousToken` will be its initializer + if ((parent as JsxAttribute).initializer === previousToken && + previousToken.end < position) { + isJsxIdentifierExpected = true; + break; } - else if (insertQuestionDot) { - symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.Nullable }; + switch (previousToken.kind) { + case SyntaxKind.EqualsToken: + isJsxInitializer = true; + break; + case SyntaxKind.Identifier: + isJsxIdentifierExpected = true; + // For `
` we don't want to treat this as a jsx inializer, instead it's the attribute name. + if (parent !== previousToken.parent && + !(parent as JsxAttribute).initializer && + findChildOfKind(parent, SyntaxKind.EqualsToken, sourceFile)) { + isJsxInitializer = previousToken as Identifier; + } } - } - } - - function getNullableSymbolOriginInfoKind(kind: SymbolOriginInfoKind) { - return insertQuestionDot ? kind | SymbolOriginInfoKind.Nullable : kind; + break; } } + } - /** Given 'a.b.c', returns 'a'. */ - function getLeftMostName(e: Expression): Identifier | undefined { - return isIdentifier(e) ? e : isPropertyAccessExpression(e) ? getLeftMostName(e.expression) : undefined; - } + const semanticStart = timestamp(); + let completionKind = CompletionKind.None; + let isNonContextualObjectLiteral = false; + let hasUnresolvedAutoImports = false; + // This also gets mutated in nested-functions after the return + let symbols: Symbol[] = []; + let importSpecifierResolver: codefix.ImportSpecifierResolver | undefined; + const symbolToOriginInfoMap: SymbolOriginInfoMap = []; + const symbolToSortTextMap: SymbolSortTextMap = []; + const seenPropertySymbols = new Map(); + const isTypeOnlyLocation = isTypeOnlyCompletion(); + const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => { + return createModuleSpecifierResolutionHost(isFromPackageJson ? host.getPackageJsonAutoImportProvider!()! : program, host); + }); - function tryGetGlobalSymbols(): boolean { - const result: GlobalsSearch = tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols() - || tryGetObjectLikeCompletionSymbols() - || tryGetImportCompletionSymbols() - || tryGetImportOrExportClauseCompletionSymbols() - || tryGetLocalNamedExportCompletionSymbols() - || tryGetConstructorCompletion() - || tryGetClassLikeCompletionSymbols() - || tryGetJsxCompletionSymbols() - || (getGlobalCompletions(), GlobalsSearch.Success); - return result === GlobalsSearch.Success; + if (isRightOfDot || isRightOfQuestionDot) { + getTypeScriptMemberSymbols(); + } + else if (isRightOfOpenTag) { + symbols = typeChecker.getJsxIntrinsicTagNamesAt(location); + Debug.assertEachIsDefined(symbols, "getJsxIntrinsicTagNames() should all be defined"); + tryGetGlobalSymbols(); + completionKind = CompletionKind.Global; + keywordFilters = KeywordCompletionFilters.None; + } + else if (isStartingCloseTag) { + const tagName = (contextToken.parent.parent as JsxElement).openingElement.tagName; + const tagSymbol = typeChecker.getSymbolAtLocation(tagName); + if (tagSymbol) { + symbols = [tagSymbol]; + } + completionKind = CompletionKind.Global; + keywordFilters = KeywordCompletionFilters.None; + } + else { + // For JavaScript or TypeScript, if we're not after a dot, then just try to get the + // global symbols in scope. These results should be valid for either language as + // the set of symbols that can be referenced from this location. + if (!tryGetGlobalSymbols()) { + return keywordFilters + ? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierLocation) + : undefined; } + } - function tryGetConstructorCompletion(): GlobalsSearch { - if (!tryGetConstructorLikeCompletionContainer(contextToken)) return GlobalsSearch.Continue; - // no members, only keywords - completionKind = CompletionKind.None; - // Declaring new property/method/accessor - isNewIdentifierLocation = true; - // Has keywords for constructor parameter - keywordFilters = KeywordCompletionFilters.ConstructorParameterKeywords; - return GlobalsSearch.Success; - } + log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); + const contextualType = previousToken && getContextualType(previousToken, position, sourceFile, typeChecker); + + const literals = mapDefined( + contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), + t => t.isLiteral() && !(t.flags & TypeFlags.EnumLiteral) ? t.value : undefined); + + const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion(previousToken, contextualType, typeChecker); + return { + kind: CompletionDataKind.Data, + symbols, + completionKind, + isInSnippetScope, + propertyAccessToConvert, + isNewIdentifierLocation, + location, + keywordFilters, + literals, + symbolToOriginInfoMap, + recommendedCompletion, + previousToken, + contextToken, + isJsxInitializer, + insideJsDocTagTypeExpression, + symbolToSortTextMap, + isTypeOnlyLocation, + isJsxIdentifierExpected, + isRightOfOpenTag, + importStatementCompletion, + hasUnresolvedAutoImports, + flags, + }; - function tryGetJsxCompletionSymbols(): GlobalsSearch { - const jsxContainer = tryGetContainingJsxElement(contextToken); - // Cursor is inside a JSX self-closing element or opening element - const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes); - if (!attrsType) return GlobalsSearch.Continue; - const completionsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.Completions); - symbols = concatenate(symbols, filterJsxAttributes(getPropertiesForObjectExpression(attrsType, completionsType, jsxContainer.attributes, typeChecker), jsxContainer.attributes.properties)); - setSortTextToOptionalMember(); - completionKind = CompletionKind.MemberLike; - isNewIdentifierLocation = false; - return GlobalsSearch.Success; - } + type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag | JSDocTemplateTag; - function tryGetImportCompletionSymbols(): GlobalsSearch { - if (!importStatementCompletion) return GlobalsSearch.Continue; - isNewIdentifierLocation = true; - collectAutoImports(); - return GlobalsSearch.Success; + function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { + switch (tag.kind) { + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + case SyntaxKind.JSDocReturnTag: + case SyntaxKind.JSDocTypeTag: + case SyntaxKind.JSDocTypedefTag: + return true; + case SyntaxKind.JSDocTemplateTag: + return !!(tag as JSDocTemplateTag).constraint; + default: + return false; } + } - function getGlobalCompletions(): void { - keywordFilters = tryGetFunctionLikeBodyCompletionContainer(contextToken) ? KeywordCompletionFilters.FunctionLikeBodyKeywords : KeywordCompletionFilters.All; + function tryGetTypeExpressionFromTag(tag: JSDocTag): JSDocTypeExpression | undefined { + if (isTagWithTypeExpression(tag)) { + const typeExpression = isJSDocTemplateTag(tag) ? tag.constraint : tag.typeExpression; + return typeExpression && typeExpression.kind === SyntaxKind.JSDocTypeExpression ? typeExpression : undefined; + } + return undefined; + } - // Get all entities in the current scope. - completionKind = CompletionKind.Global; - isNewIdentifierLocation = isNewIdentifierDefinitionLocation(); + function getTypeScriptMemberSymbols(): void { + // Right of dot member completion list + completionKind = CompletionKind.PropertyAccess; + + // Since this is qualified name check it's a type node location + const isImportType = isLiteralImportTypeNode(node); + const isTypeLocation = insideJsDocTagTypeExpression + || (isImportType && !(node as ImportTypeNode).isTypeOf) + || isPartOfTypeNode(node.parent) + || isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker); + const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node); + if (isEntityName(node) || isImportType || isPropertyAccessExpression(node)) { + const isNamespaceName = isModuleDeclaration(node.parent); + if (isNamespaceName) isNewIdentifierLocation = true; + let symbol = typeChecker.getSymbolAtLocation(node); + if (symbol) { + symbol = skipAlias(symbol, typeChecker); + if (symbol.flags & (SymbolFlags.Module | SymbolFlags.Enum)) { + // Extract module or enum members + const exportedSymbols = typeChecker.getExportsOfModule(symbol); + Debug.assertEachIsDefined(exportedSymbols, "getExportsOfModule() should all be defined"); + const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(isImportType ? node as ImportTypeNode : (node.parent as PropertyAccessExpression), symbol.name); + const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol, typeChecker); + const isValidAccess: (symbol: Symbol) => boolean = + isNamespaceName + // At `namespace N.M/**/`, if this is the only declaration of `M`, don't include `M` as a completion. + ? symbol => !!(symbol.flags & SymbolFlags.Namespace) && !symbol.declarations?.every(d => d.parent === node.parent) + : isRhsOfImportDeclaration ? + // Any kind is allowed when dotting off namespace in internal import equals declaration + symbol => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : + isTypeLocation ? isValidTypeAccess : isValidValueAccess; + for (const exportedSymbol of exportedSymbols) { + if (isValidAccess(exportedSymbol)) { + symbols.push(exportedSymbol); + } + } - if (previousToken !== contextToken) { - Debug.assert(!!previousToken, "Expected 'contextToken' to be defined when different from 'previousToken'."); - } - // We need to find the node that will give us an appropriate scope to begin - // aggregating completion candidates. This is achieved in 'getScopeNode' - // by finding the first node that encompasses a position, accounting for whether a node - // is "complete" to decide whether a position belongs to the node. - // - // However, at the end of an identifier, we are interested in the scope of the identifier - // itself, but fall outside of the identifier. For instance: - // - // xyz => x$ - // - // the cursor is outside of both the 'x' and the arrow function 'xyz => x', - // so 'xyz' is not returned in our results. - // - // We define 'adjustedPosition' so that we may appropriately account for - // being at the end of an identifier. The intention is that if requesting completion - // at the end of an identifier, it should be effectively equivalent to requesting completion - // anywhere inside/at the beginning of the identifier. So in the previous case, the - // 'adjustedPosition' will work as if requesting completion in the following: - // - // xyz => $x - // - // If previousToken !== contextToken, then - // - 'contextToken' was adjusted to the token prior to 'previousToken' - // because we were at the end of an identifier. - // - 'previousToken' is defined. - const adjustedPosition = previousToken !== contextToken ? - previousToken.getStart() : - position; - - const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile; - isInSnippetScope = isSnippetScope(scopeNode); - - const symbolMeanings = (isTypeOnlyLocation ? SymbolFlags.None : SymbolFlags.Value) | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias; - const typeOnlyAliasNeedsPromotion = previousToken && !isValidTypeOnlyAliasUseSite(previousToken); - - symbols = concatenate(symbols, typeChecker.getSymbolsInScope(scopeNode, symbolMeanings)); - Debug.assertEachIsDefined(symbols, "getSymbolsInScope() should all be defined"); - for (let i = 0; i < symbols.length; i++) { - const symbol = symbols[i]; - if (!typeChecker.isArgumentsSymbol(symbol) && - !some(symbol.declarations, d => d.getSourceFile() === sourceFile)) { - symbolToSortTextMap[getSymbolId(symbol)] = SortText.GlobalsOrKeywords; - } - if (typeOnlyAliasNeedsPromotion && !(symbol.flags & SymbolFlags.Value)) { - const typeOnlyAliasDeclaration = symbol.declarations && find(symbol.declarations, isTypeOnlyImportOrExportDeclaration); - if (typeOnlyAliasDeclaration) { - const origin: SymbolOriginInfoTypeOnlyAlias = { kind: SymbolOriginInfoKind.TypeOnlyAlias, declaration: typeOnlyAliasDeclaration }; - symbolToOriginInfoMap[i] = origin; + // If the module is merged with a value, we must get the type of the class and add its propertes (for inherited static methods). + if (!isTypeLocation && + symbol.declarations && + symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { + let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType(); + let insertQuestionDot = false; + if (type.isNullableType()) { + const canCorrectToQuestionDot = + isRightOfDot && + !isRightOfQuestionDot && + preferences.includeAutomaticOptionalChainCompletions !== false; + + if (canCorrectToQuestionDot || isRightOfQuestionDot) { + type = type.getNonNullableType(); + if (canCorrectToQuestionDot) { + insertQuestionDot = true; + } + } + } + addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } + + return; } } + } - // Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions` - if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) { - const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false, isClassLike(scopeNode.parent) ? scopeNode : undefined); - if (thisType && !isProbablyGlobalType(thisType, sourceFile, typeChecker)) { - for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) { - symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.ThisType }; - symbols.push(symbol); - symbolToSortTextMap[getSymbolId(symbol)] = SortText.SuggestedClassMembers; + if (!isTypeLocation) { + // GH#39946. Pulling on the type of a node inside of a function with a contextual `this` parameter can result in a circularity + // if the `node` is part of the exprssion of a `yield` or `return`. This circularity doesn't exist at compile time because + // we will check (and cache) the type of `this` *before* checking the type of the node. + typeChecker.tryGetThisTypeAt(node, /*includeGlobalThis*/ false); + + let type = typeChecker.getTypeAtLocation(node).getNonOptionalType(); + let insertQuestionDot = false; + if (type.isNullableType()) { + const canCorrectToQuestionDot = + isRightOfDot && + !isRightOfQuestionDot && + preferences.includeAutomaticOptionalChainCompletions !== false; + + if (canCorrectToQuestionDot || isRightOfQuestionDot) { + type = type.getNonNullableType(); + if (canCorrectToQuestionDot) { + insertQuestionDot = true; } } } - collectAutoImports(); - if (isTypeOnlyLocation) { - keywordFilters = contextToken && isAssertionExpression(contextToken.parent) - ? KeywordCompletionFilters.TypeAssertionKeywords - : KeywordCompletionFilters.TypeKeywords; - } + addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } + } - function shouldOfferImportCompletions(): boolean { - // If already typing an import statement, provide completions for it. - if (importStatementCompletion) return true; - // If current completion is for non-contextual Object literal shortahands, ignore auto-import symbols - if (isNonContextualObjectLiteral) return false; - // If not already a module, must have modules enabled. - if (!preferences.includeCompletionsForModuleExports) return false; - // If already using ES modules, OK to continue using them. - if (sourceFile.externalModuleIndicator || sourceFile.commonJsModuleIndicator) return true; - // If module transpilation is enabled or we're targeting es6 or above, or not emitting, OK. - if (compilerOptionsIndicateEsModules(program.getCompilerOptions())) return true; - // If some file is using ES6 modules, assume that it's OK to add more. - return programContainsModules(program); + function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void { + isNewIdentifierLocation = !!type.getStringIndexType(); + if (isRightOfQuestionDot && some(type.getCallSignatures())) { + isNewIdentifierLocation = true; } - function isSnippetScope(scopeNode: Node): boolean { - switch (scopeNode.kind) { - case SyntaxKind.SourceFile: - case SyntaxKind.TemplateExpression: - case SyntaxKind.JsxExpression: - case SyntaxKind.Block: - return true; - default: - return isStatement(scopeNode); + const propertyAccess = node.kind === SyntaxKind.ImportType ? node as ImportTypeNode : node.parent as PropertyAccessExpression | QualifiedName; + if (inCheckedFile) { + for (const symbol of type.getApparentProperties()) { + if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) { + addPropertySymbol(symbol, /* insertAwait */ false, insertQuestionDot); + } } } - - function isTypeOnlyCompletion(): boolean { - return insideJsDocTagTypeExpression - || !!importStatementCompletion && isTypeOnlyImportOrExportDeclaration(location.parent) - || !isContextTokenValueLocation(contextToken) && - (isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker) - || isPartOfTypeNode(location) - || isContextTokenTypeLocation(contextToken)); - } - - function isContextTokenValueLocation(contextToken: Node) { - return contextToken && - ((contextToken.kind === SyntaxKind.TypeOfKeyword && - (contextToken.parent.kind === SyntaxKind.TypeQuery || isTypeOfExpression(contextToken.parent))) || - (contextToken.kind === SyntaxKind.AssertsKeyword && contextToken.parent.kind === SyntaxKind.TypePredicate)); - } - - function isContextTokenTypeLocation(contextToken: Node): boolean { - if (contextToken) { - const parentKind = contextToken.parent.kind; - switch (contextToken.kind) { - case SyntaxKind.ColonToken: - return parentKind === SyntaxKind.PropertyDeclaration || - parentKind === SyntaxKind.PropertySignature || - parentKind === SyntaxKind.Parameter || - parentKind === SyntaxKind.VariableDeclaration || - isFunctionLikeKind(parentKind); - - case SyntaxKind.EqualsToken: - return parentKind === SyntaxKind.TypeAliasDeclaration; - - case SyntaxKind.AsKeyword: - return parentKind === SyntaxKind.AsExpression; - - case SyntaxKind.LessThanToken: - return parentKind === SyntaxKind.TypeReference || - parentKind === SyntaxKind.TypeAssertionExpression; - - case SyntaxKind.ExtendsKeyword: - return parentKind === SyntaxKind.TypeParameter; - - case SyntaxKind.SatisfiesKeyword: - return parentKind === SyntaxKind.SatisfiesExpression; + else { + // In javascript files, for union types, we don't just get the members that + // the individual types have in common, we also include all the members that + // each individual type has. This is because we're going to add all identifiers + // anyways. So we might as well elevate the members that were at least part + // of the individual types to a higher status since we know what they are. + symbols.push(...filter(getPropertiesForCompletion(type, typeChecker), s => typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, s))); + } + + if (insertAwait && preferences.includeCompletionsWithInsertText) { + const promiseType = typeChecker.getPromisedTypeOfPromise(type); + if (promiseType) { + for (const symbol of promiseType.getApparentProperties()) { + if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, promiseType, symbol)) { + addPropertySymbol(symbol, /* insertAwait */ true, insertQuestionDot); + } } } - return false; } + } - /** Mutates `symbols`, `symbolToOriginInfoMap`, and `symbolToSortTextMap` */ - function collectAutoImports() { - if (!shouldOfferImportCompletions()) return; - Debug.assert(!detailsEntryId?.data, "Should not run 'collectAutoImports' when faster path is available via `data`"); - if (detailsEntryId && !detailsEntryId.source) { - // Asking for completion details for an item that is not an auto-import - return; - } - - flags |= CompletionInfoFlags.MayIncludeAutoImports; - // import { type | -> token text should be blank - const isAfterTypeOnlyImportSpecifierModifier = previousToken === contextToken - && importStatementCompletion; - - const lowerCaseTokenText = - isAfterTypeOnlyImportSpecifierModifier ? "" : - previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() : - ""; - - const moduleSpecifierCache = host.getModuleSpecifierCache?.(); - const exportInfo = getExportInfoMap(sourceFile, host, program, preferences, cancellationToken); - const packageJsonAutoImportProvider = host.getPackageJsonAutoImportProvider?.(); - const packageJsonFilter = detailsEntryId ? undefined : createPackageJsonImportFilter(sourceFile, preferences, host); - resolvingModuleSpecifiers( - "collectAutoImports", - host, - importSpecifierResolver ||= codefix.createImportSpecifierResolver(sourceFile, program, host, preferences), - program, - position, - preferences, - !!importStatementCompletion, - isValidTypeOnlyAliasUseSite(location), - context => { - exportInfo.search( - sourceFile.path, - /*preferCapitalized*/ isRightOfOpenTag, - (symbolName, targetFlags) => { - if (!isIdentifierText(symbolName, getEmitScriptTarget(host.getCompilationSettings()))) return false; - if (!detailsEntryId && isStringANonContextualKeyword(symbolName)) return false; - if (!isTypeOnlyLocation && !importStatementCompletion && !(targetFlags & SymbolFlags.Value)) return false; - if (isTypeOnlyLocation && !(targetFlags & (SymbolFlags.Module | SymbolFlags.Type))) return false; - // Do not try to auto-import something with a lowercase first letter for a JSX tag - const firstChar = symbolName.charCodeAt(0); - if (isRightOfOpenTag && (firstChar < CharacterCodes.A || firstChar > CharacterCodes.Z)) return false; - - if (detailsEntryId) return true; - return charactersFuzzyMatchInString(symbolName, lowerCaseTokenText); - }, - (info, symbolName, isFromAmbientModule, exportMapKey) => { - if (detailsEntryId && !some(info, i => detailsEntryId.source === stripQuotes(i.moduleSymbol.name))) { - return; - } - - // Do a relatively cheap check to bail early if all re-exports are non-importable - // due to file location or package.json dependency filtering. For non-node16+ - // module resolution modes, getting past this point guarantees that we'll be - // able to generate a suitable module specifier, so we can safely show a completion, - // even if we defer computing the module specifier. - const firstImportableExportInfo = find(info, isImportableExportInfo); - if (!firstImportableExportInfo) { - return; - } - - // In node16+, module specifier resolution can fail due to modules being blocked - // by package.json `exports`. If that happens, don't show a completion item. - // N.B. in this resolution mode we always try to resolve module specifiers here, - // because we have to know now if it's going to fail so we can omit the completion - // from the list. - const result = context.tryResolve(info, symbolName, isFromAmbientModule) || {}; - if (result === "failed") return; - - // If we skipped resolving module specifiers, our selection of which ExportInfo - // to use here is arbitrary, since the info shown in the completion list derived from - // it should be identical regardless of which one is used. During the subsequent - // `CompletionEntryDetails` request, we'll get all the ExportInfos again and pick - // the best one based on the module specifier it produces. - let exportInfo = firstImportableExportInfo, moduleSpecifier; - if (result !== "skipped") { - ({ exportInfo = firstImportableExportInfo, moduleSpecifier } = result); - } - - const isDefaultExport = exportInfo.exportKind === ExportKind.Default; - const symbol = isDefaultExport && getLocalSymbolForExportDefault(exportInfo.symbol) || exportInfo.symbol; - - pushAutoImportSymbol(symbol, { - kind: moduleSpecifier ? SymbolOriginInfoKind.ResolvedExport : SymbolOriginInfoKind.Export, - moduleSpecifier, - symbolName, - exportMapKey, - exportName: exportInfo.exportKind === ExportKind.ExportEquals ? InternalSymbolName.ExportEquals : exportInfo.symbol.name, - fileName: exportInfo.moduleFileName, - isDefaultExport, - moduleSymbol: exportInfo.moduleSymbol, - isFromPackageJson: exportInfo.isFromPackageJson, - }); - } - ); - - hasUnresolvedAutoImports = context.skippedAny(); - flags |= context.resolvedAny() ? CompletionInfoFlags.ResolvedModuleSpecifiers : 0; - flags |= context.resolvedBeyondLimit() ? CompletionInfoFlags.ResolvedModuleSpecifiersBeyondLimit : 0; + function addPropertySymbol(symbol: Symbol, insertAwait: boolean, insertQuestionDot: boolean) { + // For a computed property with an accessible name like `Symbol.iterator`, + // we'll add a completion for the *name* `Symbol` instead of for the property. + // If this is e.g. [Symbol.iterator], add a completion for `Symbol`. + const computedPropertyName = firstDefined(symbol.declarations, decl => tryCast(getNameOfDeclaration(decl), isComputedPropertyName)); + if (computedPropertyName) { + const leftMostName = getLeftMostName(computedPropertyName.expression); // The completion is for `Symbol`, not `iterator`. + const nameSymbol = leftMostName && typeChecker.getSymbolAtLocation(leftMostName); + // If this is nested like for `namespace N { export const sym = Symbol(); }`, we'll add the completion for `N`. + const firstAccessibleSymbol = nameSymbol && getFirstSymbolInChain(nameSymbol, contextToken, typeChecker); + if (firstAccessibleSymbol && addToSeen(seenPropertySymbols, getSymbolId(firstAccessibleSymbol))) { + const index = symbols.length; + symbols.push(firstAccessibleSymbol); + const moduleSymbol = firstAccessibleSymbol.parent; + if (!moduleSymbol || + !isExternalModuleSymbol(moduleSymbol) || + typeChecker.tryGetMemberInModuleExportsAndProperties(firstAccessibleSymbol.name, moduleSymbol) !== firstAccessibleSymbol + ) { + symbolToOriginInfoMap[index] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberNoExport) }; } - ); - - function isImportableExportInfo(info: SymbolExportInfo) { - const moduleFile = tryCast(info.moduleSymbol.valueDeclaration, isSourceFile); - if (!moduleFile) { - const moduleName = stripQuotes(info.moduleSymbol.name); - if (JsTyping.nodeCoreModules.has(moduleName) && startsWith(moduleName, "node:") !== shouldUseUriStyleNodeCoreModules(sourceFile, program)) { - return false; + else { + const fileName = isExternalModuleNameRelative(stripQuotes(moduleSymbol.name)) ? getSourceFileOfModule(moduleSymbol)?.fileName : undefined; + const { moduleSpecifier } = (importSpecifierResolver ||= codefix.createImportSpecifierResolver(sourceFile, program, host, preferences)).getModuleSpecifierForBestExportInfo([{ + exportKind: ExportKind.Named, + moduleFileName: fileName, + isFromPackageJson: false, + moduleSymbol, + symbol: firstAccessibleSymbol, + targetFlags: skipAlias(firstAccessibleSymbol, typeChecker).flags, + }], firstAccessibleSymbol.name, position, isValidTypeOnlyAliasUseSite(location)) || {}; + + if (moduleSpecifier) { + const origin: SymbolOriginInfoResolvedExport = { + kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport), + moduleSymbol, + isDefaultExport: false, + symbolName: firstAccessibleSymbol.name, + exportName: firstAccessibleSymbol.name, + fileName, + moduleSpecifier, + }; + symbolToOriginInfoMap[index] = origin; } - return packageJsonFilter - ? packageJsonFilter.allowsImportingAmbientModule(info.moduleSymbol, getModuleSpecifierResolutionHost(info.isFromPackageJson)) - : true; } - return isImportableFile( - info.isFromPackageJson ? packageJsonAutoImportProvider! : program, - sourceFile, - moduleFile, - preferences, - packageJsonFilter, - getModuleSpecifierResolutionHost(info.isFromPackageJson), - moduleSpecifierCache); } - } - - function pushAutoImportSymbol(symbol: Symbol, origin: SymbolOriginInfoResolvedExport | SymbolOriginInfoExport) { - const symbolId = getSymbolId(symbol); - if (symbolToSortTextMap[symbolId] === SortText.GlobalsOrKeywords) { - // If an auto-importable symbol is available as a global, don't add the auto import - return; + else if (preferences.includeCompletionsWithInsertText) { + addSymbolOriginInfo(symbol); + addSymbolSortInfo(symbol); + symbols.push(symbol); } - symbolToOriginInfoMap[symbols.length] = origin; - symbolToSortTextMap[symbolId] = importStatementCompletion ? SortText.LocationPriority : SortText.AutoImportSuggestions; + } + else { + addSymbolOriginInfo(symbol); + addSymbolSortInfo(symbol); symbols.push(symbol); } - /* Mutates `symbols` and `symbolToOriginInfoMap`. */ - function collectObjectLiteralMethodSymbols(members: Symbol[], enclosingDeclaration: ObjectLiteralExpression): void { - // TODO: support JS files. - if (isInJSFile(location)) { - return; + function addSymbolSortInfo(symbol: Symbol) { + if (isStaticProperty(symbol)) { + symbolToSortTextMap[getSymbolId(symbol)] = SortText.LocalDeclarationPriority; } - members.forEach(member => { - if (!isObjectLiteralMethodSymbol(member)) { - return; - } - const displayName = getCompletionEntryDisplayNameForSymbol( - member, - getEmitScriptTarget(compilerOptions), - /*origin*/ undefined, - CompletionKind.ObjectPropertyDeclaration, - /*jsxIdentifierExpected*/ false); - if (!displayName) { - return; - } - const { name } = displayName; - const entryProps = getEntryForObjectLiteralMethodCompletion( - member, - name, - enclosingDeclaration, - program, - host, - compilerOptions, - preferences, - formatContext); - if (!entryProps) { - return; - } - const origin: SymbolOriginInfoObjectLiteralMethod = { kind: SymbolOriginInfoKind.ObjectLiteralMethod, ...entryProps }; - flags |= CompletionInfoFlags.MayIncludeMethodSnippets; - symbolToOriginInfoMap[symbols.length] = origin; - symbols.push(member); - }); } - function isObjectLiteralMethodSymbol(symbol: Symbol): boolean { - /* - For an object type - `type Foo = { - bar(x: number): void; - foo: (x: string) => string; - }`, - `bar` will have symbol flag `Method`, - `foo` will have symbol flag `Property`. - */ - if (!(symbol.flags & (SymbolFlags.Property | SymbolFlags.Method))) { - return false; + function addSymbolOriginInfo(symbol: Symbol) { + if (preferences.includeCompletionsWithInsertText) { + if (insertAwait && addToSeen(seenPropertySymbols, getSymbolId(symbol))) { + symbolToOriginInfoMap[symbols.length] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.Promise) }; + } + else if (insertQuestionDot) { + symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.Nullable }; + } } - return true; } - /** - * Finds the first node that "embraces" the position, so that one may - * accurately aggregate locals from the closest containing scope. - */ - function getScopeNode(initialToken: Node | undefined, position: number, sourceFile: SourceFile) { - let scope: Node | undefined = initialToken; - while (scope && !positionBelongsToNode(scope, position, sourceFile)) { - scope = scope.parent; - } - return scope; + function getNullableSymbolOriginInfoKind(kind: SymbolOriginInfoKind) { + return insertQuestionDot ? kind | SymbolOriginInfoKind.Nullable : kind; } + } - function isCompletionListBlocker(contextToken: Node): boolean { - const start = timestamp(); - const result = isInStringOrRegularExpressionOrTemplateLiteral(contextToken) || - isSolelyIdentifierDefinitionLocation(contextToken) || - isDotOfNumericLiteral(contextToken) || - isInJsxText(contextToken) || - isBigIntLiteral(contextToken); - log("getCompletionsAtPosition: isCompletionListBlocker: " + (timestamp() - start)); - return result; - } + /** Given 'a.b.c', returns 'a'. */ + function getLeftMostName(e: Expression): Identifier | undefined { + return isIdentifier(e) ? e : isPropertyAccessExpression(e) ? getLeftMostName(e.expression) : undefined; + } - function isInJsxText(contextToken: Node): boolean { - if (contextToken.kind === SyntaxKind.JsxText) { - return true; - } + function tryGetGlobalSymbols(): boolean { + const result: GlobalsSearch = tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols() + || tryGetObjectLikeCompletionSymbols() + || tryGetImportCompletionSymbols() + || tryGetImportOrExportClauseCompletionSymbols() + || tryGetLocalNamedExportCompletionSymbols() + || tryGetConstructorCompletion() + || tryGetClassLikeCompletionSymbols() + || tryGetJsxCompletionSymbols() + || (getGlobalCompletions(), GlobalsSearch.Success); + return result === GlobalsSearch.Success; + } - if (contextToken.kind === SyntaxKind.GreaterThanToken && contextToken.parent) { - // /**/ /> - // /**/ > - // - contextToken: GreaterThanToken (before cursor) - // - location: JsxSelfClosingElement or JsxOpeningElement - // - contextToken.parent === location - if (location === contextToken.parent && (location.kind === SyntaxKind.JsxOpeningElement || location.kind === SyntaxKind.JsxSelfClosingElement)) { - return false; - } + function tryGetConstructorCompletion(): GlobalsSearch { + if (!tryGetConstructorLikeCompletionContainer(contextToken)) return GlobalsSearch.Continue; + // no members, only keywords + completionKind = CompletionKind.None; + // Declaring new property/method/accessor + isNewIdentifierLocation = true; + // Has keywords for constructor parameter + keywordFilters = KeywordCompletionFilters.ConstructorParameterKeywords; + return GlobalsSearch.Success; + } - if (contextToken.parent.kind === SyntaxKind.JsxOpeningElement) { - //
/**/ - // - contextToken: GreaterThanToken (before cursor) - // - location: JSXElement - // - different parents (JSXOpeningElement, JSXElement) - return location.parent.kind !== SyntaxKind.JsxOpeningElement; - } + function tryGetJsxCompletionSymbols(): GlobalsSearch { + const jsxContainer = tryGetContainingJsxElement(contextToken); + // Cursor is inside a JSX self-closing element or opening element + const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes); + if (!attrsType) return GlobalsSearch.Continue; + const completionsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.Completions); + symbols = concatenate(symbols, filterJsxAttributes(getPropertiesForObjectExpression(attrsType, completionsType, jsxContainer.attributes, typeChecker), jsxContainer.attributes.properties)); + setSortTextToOptionalMember(); + completionKind = CompletionKind.MemberLike; + isNewIdentifierLocation = false; + return GlobalsSearch.Success; + } - if (contextToken.parent.kind === SyntaxKind.JsxClosingElement || contextToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { - return !!contextToken.parent.parent && contextToken.parent.parent.kind === SyntaxKind.JsxElement; + function tryGetImportCompletionSymbols(): GlobalsSearch { + if (!importStatementCompletion) return GlobalsSearch.Continue; + isNewIdentifierLocation = true; + collectAutoImports(); + return GlobalsSearch.Success; + } + + function getGlobalCompletions(): void { + keywordFilters = tryGetFunctionLikeBodyCompletionContainer(contextToken) ? KeywordCompletionFilters.FunctionLikeBodyKeywords : KeywordCompletionFilters.All; + + // Get all entities in the current scope. + completionKind = CompletionKind.Global; + isNewIdentifierLocation = isNewIdentifierDefinitionLocation(); + + if (previousToken !== contextToken) { + Debug.assert(!!previousToken, "Expected 'contextToken' to be defined when different from 'previousToken'."); + } + // We need to find the node that will give us an appropriate scope to begin + // aggregating completion candidates. This is achieved in 'getScopeNode' + // by finding the first node that encompasses a position, accounting for whether a node + // is "complete" to decide whether a position belongs to the node. + // + // However, at the end of an identifier, we are interested in the scope of the identifier + // itself, but fall outside of the identifier. For instance: + // + // xyz => x$ + // + // the cursor is outside of both the 'x' and the arrow function 'xyz => x', + // so 'xyz' is not returned in our results. + // + // We define 'adjustedPosition' so that we may appropriately account for + // being at the end of an identifier. The intention is that if requesting completion + // at the end of an identifier, it should be effectively equivalent to requesting completion + // anywhere inside/at the beginning of the identifier. So in the previous case, the + // 'adjustedPosition' will work as if requesting completion in the following: + // + // xyz => $x + // + // If previousToken !== contextToken, then + // - 'contextToken' was adjusted to the token prior to 'previousToken' + // because we were at the end of an identifier. + // - 'previousToken' is defined. + const adjustedPosition = previousToken !== contextToken ? + previousToken.getStart() : + position; + + const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile; + isInSnippetScope = isSnippetScope(scopeNode); + + const symbolMeanings = (isTypeOnlyLocation ? SymbolFlags.None : SymbolFlags.Value) | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias; + const typeOnlyAliasNeedsPromotion = previousToken && !isValidTypeOnlyAliasUseSite(previousToken); + + symbols = concatenate(symbols, typeChecker.getSymbolsInScope(scopeNode, symbolMeanings)); + Debug.assertEachIsDefined(symbols, "getSymbolsInScope() should all be defined"); + for (let i = 0; i < symbols.length; i++) { + const symbol = symbols[i]; + if (!typeChecker.isArgumentsSymbol(symbol) && + !some(symbol.declarations, d => d.getSourceFile() === sourceFile)) { + symbolToSortTextMap[getSymbolId(symbol)] = SortText.GlobalsOrKeywords; + } + if (typeOnlyAliasNeedsPromotion && !(symbol.flags & SymbolFlags.Value)) { + const typeOnlyAliasDeclaration = symbol.declarations && find(symbol.declarations, isTypeOnlyImportOrExportDeclaration); + if (typeOnlyAliasDeclaration) { + const origin: SymbolOriginInfoTypeOnlyAlias = { kind: SymbolOriginInfoKind.TypeOnlyAlias, declaration: typeOnlyAliasDeclaration }; + symbolToOriginInfoMap[i] = origin; } } - return false; } - function isNewIdentifierDefinitionLocation(): boolean { - if (contextToken) { - const containingNodeKind = contextToken.parent.kind; - const tokenKind = keywordForNode(contextToken); - // Previous token may have been a keyword that was converted to an identifier. - switch (tokenKind) { - case SyntaxKind.CommaToken: - return containingNodeKind === SyntaxKind.CallExpression // func( a, | - || containingNodeKind === SyntaxKind.Constructor // constructor( a, | /* public, protected, private keywords are allowed here, so show completion */ - || containingNodeKind === SyntaxKind.NewExpression // new C(a, | - || containingNodeKind === SyntaxKind.ArrayLiteralExpression // [a, | - || containingNodeKind === SyntaxKind.BinaryExpression // const x = (a, | - || containingNodeKind === SyntaxKind.FunctionType // var x: (s: string, list| - || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { x, | - - case SyntaxKind.OpenParenToken: - return containingNodeKind === SyntaxKind.CallExpression // func( | - || containingNodeKind === SyntaxKind.Constructor // constructor( | - || containingNodeKind === SyntaxKind.NewExpression // new C(a| - || containingNodeKind === SyntaxKind.ParenthesizedExpression // const x = (a| - || containingNodeKind === SyntaxKind.ParenthesizedType; // function F(pred: (a| /* this can become an arrow function, where 'a' is the argument */ - - case SyntaxKind.OpenBracketToken: - return containingNodeKind === SyntaxKind.ArrayLiteralExpression // [ | - || containingNodeKind === SyntaxKind.IndexSignature // [ | : string ] - || containingNodeKind === SyntaxKind.ComputedPropertyName; // [ | /* this can become an index signature */ - - case SyntaxKind.ModuleKeyword: // module | - case SyntaxKind.NamespaceKeyword: // namespace | - case SyntaxKind.ImportKeyword: // import | - return true; - - case SyntaxKind.DotToken: - return containingNodeKind === SyntaxKind.ModuleDeclaration; // module A.| - - case SyntaxKind.OpenBraceToken: - return containingNodeKind === SyntaxKind.ClassDeclaration // class A { | - || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { | - - case SyntaxKind.EqualsToken: - return containingNodeKind === SyntaxKind.VariableDeclaration // const x = a| - || containingNodeKind === SyntaxKind.BinaryExpression; // x = a| - - case SyntaxKind.TemplateHead: - return containingNodeKind === SyntaxKind.TemplateExpression; // `aa ${| - - case SyntaxKind.TemplateMiddle: - return containingNodeKind === SyntaxKind.TemplateSpan; // `aa ${10} dd ${| - - case SyntaxKind.AsyncKeyword: - return containingNodeKind === SyntaxKind.MethodDeclaration // const obj = { async c|() - || containingNodeKind === SyntaxKind.ShorthandPropertyAssignment; // const obj = { async c| - - case SyntaxKind.AsteriskToken: - return containingNodeKind === SyntaxKind.MethodDeclaration; // const obj = { * c| - } - - if (isClassMemberCompletionKeyword(tokenKind)) { - return true; + // Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions` + if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) { + const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false, isClassLike(scopeNode.parent) ? scopeNode : undefined); + if (thisType && !isProbablyGlobalType(thisType, sourceFile, typeChecker)) { + for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) { + symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.ThisType }; + symbols.push(symbol); + symbolToSortTextMap[getSymbolId(symbol)] = SortText.SuggestedClassMembers; } } - - return false; } - - function isInStringOrRegularExpressionOrTemplateLiteral(contextToken: Node): boolean { - // To be "in" one of these literals, the position has to be: - // 1. entirely within the token text. - // 2. at the end position of an unterminated token. - // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). - return (isRegularExpressionLiteral(contextToken) || isStringTextContainingNode(contextToken)) && ( - rangeContainsPositionExclusive(createTextRangeFromSpan(createTextSpanFromNode(contextToken)), position) || - position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken))); + collectAutoImports(); + if (isTypeOnlyLocation) { + keywordFilters = contextToken && isAssertionExpression(contextToken.parent) + ? KeywordCompletionFilters.TypeAssertionKeywords + : KeywordCompletionFilters.TypeKeywords; } + } - function tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols(): GlobalsSearch | undefined { - const typeLiteralNode = tryGetTypeLiteralNode(contextToken); - if (!typeLiteralNode) return GlobalsSearch.Continue; - - const intersectionTypeNode = isIntersectionTypeNode(typeLiteralNode.parent) ? typeLiteralNode.parent : undefined; - const containerTypeNode = intersectionTypeNode || typeLiteralNode; + function shouldOfferImportCompletions(): boolean { + // If already typing an import statement, provide completions for it. + if (importStatementCompletion) return true; + // If current completion is for non-contextual Object literal shortahands, ignore auto-import symbols + if (isNonContextualObjectLiteral) return false; + // If not already a module, must have modules enabled. + if (!preferences.includeCompletionsForModuleExports) return false; + // If already using ES modules, OK to continue using them. + if (sourceFile.externalModuleIndicator || sourceFile.commonJsModuleIndicator) return true; + // If module transpilation is enabled or we're targeting es6 or above, or not emitting, OK. + if (compilerOptionsIndicateEsModules(program.getCompilerOptions())) return true; + // If some file is using ES6 modules, assume that it's OK to add more. + return programContainsModules(program); + } - const containerExpectedType = getConstraintOfTypeArgumentProperty(containerTypeNode, typeChecker); - if (!containerExpectedType) return GlobalsSearch.Continue; + function isSnippetScope(scopeNode: Node): boolean { + switch (scopeNode.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.TemplateExpression: + case SyntaxKind.JsxExpression: + case SyntaxKind.Block: + return true; + default: + return isStatement(scopeNode); + } + } - const containerActualType = typeChecker.getTypeFromTypeNode(containerTypeNode); + function isTypeOnlyCompletion(): boolean { + return insideJsDocTagTypeExpression + || !!importStatementCompletion && isTypeOnlyImportOrExportDeclaration(location.parent) + || !isContextTokenValueLocation(contextToken) && + (isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker) + || isPartOfTypeNode(location) + || isContextTokenTypeLocation(contextToken)); + } - const members = getPropertiesForCompletion(containerExpectedType, typeChecker); - const existingMembers = getPropertiesForCompletion(containerActualType, typeChecker); + function isContextTokenValueLocation(contextToken: Node) { + return contextToken && + ((contextToken.kind === SyntaxKind.TypeOfKeyword && + (contextToken.parent.kind === SyntaxKind.TypeQuery || isTypeOfExpression(contextToken.parent))) || + (contextToken.kind === SyntaxKind.AssertsKeyword && contextToken.parent.kind === SyntaxKind.TypePredicate)); + } - const existingMemberEscapedNames: Set<__String> = new Set(); - existingMembers.forEach(s => existingMemberEscapedNames.add(s.escapedName)); + function isContextTokenTypeLocation(contextToken: Node): boolean { + if (contextToken) { + const parentKind = contextToken.parent.kind; + switch (contextToken.kind) { + case SyntaxKind.ColonToken: + return parentKind === SyntaxKind.PropertyDeclaration || + parentKind === SyntaxKind.PropertySignature || + parentKind === SyntaxKind.Parameter || + parentKind === SyntaxKind.VariableDeclaration || + isFunctionLikeKind(parentKind); - symbols = concatenate(symbols, filter(members, s => !existingMemberEscapedNames.has(s.escapedName))); + case SyntaxKind.EqualsToken: + return parentKind === SyntaxKind.TypeAliasDeclaration; - completionKind = CompletionKind.ObjectPropertyDeclaration; - isNewIdentifierLocation = true; + case SyntaxKind.AsKeyword: + return parentKind === SyntaxKind.AsExpression; - return GlobalsSearch.Success; - } + case SyntaxKind.LessThanToken: + return parentKind === SyntaxKind.TypeReference || + parentKind === SyntaxKind.TypeAssertionExpression; - /** - * Aggregates relevant symbols for completion in object literals and object binding patterns. - * Relevant symbols are stored in the captured 'symbols' variable. - * - * @returns true if 'symbols' was successfully populated; false otherwise. - */ - function tryGetObjectLikeCompletionSymbols(): GlobalsSearch | undefined { - const symbolsStartIndex = symbols.length; - const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); - if (!objectLikeContainer) return GlobalsSearch.Continue; - - // We're looking up possible property names from contextual/inferred/declared type. - completionKind = CompletionKind.ObjectPropertyDeclaration; - - let typeMembers: Symbol[] | undefined; - let existingMembers: readonly Declaration[] | undefined; - - if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { - const instantiatedType = tryGetObjectLiteralContextualType(objectLikeContainer, typeChecker); - - // Check completions for Object property value shorthand - if (instantiatedType === undefined) { - if (objectLikeContainer.flags & NodeFlags.InWithStatement) { - return GlobalsSearch.Fail; - } - isNonContextualObjectLiteral = true; - return GlobalsSearch.Continue; - } - const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions); - const hasStringIndexType = (completionsType || instantiatedType).getStringIndexType(); - const hasNumberIndextype = (completionsType || instantiatedType).getNumberIndexType(); - isNewIdentifierLocation = !!hasStringIndexType || !!hasNumberIndextype; - typeMembers = getPropertiesForObjectExpression(instantiatedType, completionsType, objectLikeContainer, typeChecker); - existingMembers = objectLikeContainer.properties; - - if (typeMembers.length === 0) { - // Edge case: If NumberIndexType exists - if (!hasNumberIndextype) { - isNonContextualObjectLiteral = true; - return GlobalsSearch.Continue; - } - } - } - else { - Debug.assert(objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern); - // We are *only* completing on properties from the type being destructured. - isNewIdentifierLocation = false; - - const rootDeclaration = getRootDeclaration(objectLikeContainer.parent); - if (!isVariableLike(rootDeclaration)) return Debug.fail("Root declaration is not variable-like."); - - // We don't want to complete using the type acquired by the shape - // of the binding pattern; we are only interested in types acquired - // through type declaration or inference. - // Also proceed if rootDeclaration is a parameter and if its containing function expression/arrow function is contextually typed - - // type of parameter will flow in from the contextual type of the function - let canGetType = hasInitializer(rootDeclaration) || !!getEffectiveTypeAnnotationNode(rootDeclaration) || rootDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement; - if (!canGetType && rootDeclaration.kind === SyntaxKind.Parameter) { - if (isExpression(rootDeclaration.parent)) { - canGetType = !!typeChecker.getContextualType(rootDeclaration.parent as Expression); - } - else if (rootDeclaration.parent.kind === SyntaxKind.MethodDeclaration || rootDeclaration.parent.kind === SyntaxKind.SetAccessor) { - canGetType = isExpression(rootDeclaration.parent.parent) && !!typeChecker.getContextualType(rootDeclaration.parent.parent as Expression); - } - } - if (canGetType) { - const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); - if (!typeForObject) return GlobalsSearch.Fail; - typeMembers = typeChecker.getPropertiesOfType(typeForObject).filter(propertySymbol => { - return typeChecker.isPropertyAccessible(objectLikeContainer, /*isSuper*/ false, /*writing*/ false, typeForObject, propertySymbol); - }); - existingMembers = objectLikeContainer.elements; - } - } + case SyntaxKind.ExtendsKeyword: + return parentKind === SyntaxKind.TypeParameter; - if (typeMembers && typeMembers.length > 0) { - // Add filtered items to the completion list - const filteredMembers = filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers)); - symbols = concatenate(symbols, filteredMembers); - setSortTextToOptionalMember(); - if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression - && preferences.includeCompletionsWithObjectLiteralMethodSnippets - && preferences.includeCompletionsWithInsertText) { - transformObjectLiteralMembersSortText(symbolsStartIndex); - collectObjectLiteralMethodSymbols(filteredMembers, objectLikeContainer); - } + case SyntaxKind.SatisfiesKeyword: + return parentKind === SyntaxKind.SatisfiesExpression; } - - return GlobalsSearch.Success; } + return false; + } - /** - * Aggregates relevant symbols for completion in import clauses and export clauses - * whose declarations have a module specifier; for instance, symbols will be aggregated for - * - * import { | } from "moduleName"; - * export { a as foo, | } from "moduleName"; - * - * but not for - * - * export { | }; - * - * Relevant symbols are stored in the captured 'symbols' variable. - */ - function tryGetImportOrExportClauseCompletionSymbols(): GlobalsSearch { - if (!contextToken) return GlobalsSearch.Continue; - - // `import { |` or `import { a as 0, | }` or `import { type | }` - const namedImportsOrExports = - contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken ? tryCast(contextToken.parent, isNamedImportsOrExports) : - isTypeKeywordTokenOrIdentifier(contextToken) ? tryCast(contextToken.parent.parent, isNamedImportsOrExports) : undefined; - - if (!namedImportsOrExports) return GlobalsSearch.Continue; - - // We can at least offer `type` at `import { |` - if (!isTypeKeywordTokenOrIdentifier(contextToken)) { - keywordFilters = KeywordCompletionFilters.TypeKeyword; - } + /** Mutates `symbols`, `symbolToOriginInfoMap`, and `symbolToSortTextMap` */ + function collectAutoImports() { + if (!shouldOfferImportCompletions()) return; + Debug.assert(!detailsEntryId?.data, "Should not run 'collectAutoImports' when faster path is available via `data`"); + if (detailsEntryId && !detailsEntryId.source) { + // Asking for completion details for an item that is not an auto-import + return; + } + + flags |= CompletionInfoFlags.MayIncludeAutoImports; + // import { type | -> token text should be blank + const isAfterTypeOnlyImportSpecifierModifier = previousToken === contextToken + && importStatementCompletion; + + const lowerCaseTokenText = + isAfterTypeOnlyImportSpecifierModifier ? "" : + previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() : + ""; + + const moduleSpecifierCache = host.getModuleSpecifierCache?.(); + const exportInfo = getExportInfoMap(sourceFile, host, program, preferences, cancellationToken); + const packageJsonAutoImportProvider = host.getPackageJsonAutoImportProvider?.(); + const packageJsonFilter = detailsEntryId ? undefined : createPackageJsonImportFilter(sourceFile, preferences, host); + resolvingModuleSpecifiers( + "collectAutoImports", + host, + importSpecifierResolver ||= codefix.createImportSpecifierResolver(sourceFile, program, host, preferences), + program, + position, + preferences, + !!importStatementCompletion, + isValidTypeOnlyAliasUseSite(location), + context => { + exportInfo.search( + sourceFile.path, + /*preferCapitalized*/ isRightOfOpenTag, + (symbolName, targetFlags) => { + if (!isIdentifierText(symbolName, getEmitScriptTarget(host.getCompilationSettings()))) return false; + if (!detailsEntryId && isStringANonContextualKeyword(symbolName)) return false; + if (!isTypeOnlyLocation && !importStatementCompletion && !(targetFlags & SymbolFlags.Value)) return false; + if (isTypeOnlyLocation && !(targetFlags & (SymbolFlags.Module | SymbolFlags.Type))) return false; + // Do not try to auto-import something with a lowercase first letter for a JSX tag + const firstChar = symbolName.charCodeAt(0); + if (isRightOfOpenTag && (firstChar < CharacterCodes.A || firstChar > CharacterCodes.Z)) return false; + + if (detailsEntryId) return true; + return charactersFuzzyMatchInString(symbolName, lowerCaseTokenText); + }, + (info, symbolName, isFromAmbientModule, exportMapKey) => { + if (detailsEntryId && !some(info, i => detailsEntryId.source === stripQuotes(i.moduleSymbol.name))) { + return; + } - // try to show exported member for imported/re-exported module - const { moduleSpecifier } = namedImportsOrExports.kind === SyntaxKind.NamedImports ? namedImportsOrExports.parent.parent : namedImportsOrExports.parent; - if (!moduleSpecifier) { - isNewIdentifierLocation = true; - return namedImportsOrExports.kind === SyntaxKind.NamedImports ? GlobalsSearch.Fail : GlobalsSearch.Continue; - } - const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); // TODO: GH#18217 - if (!moduleSpecifierSymbol) { - isNewIdentifierLocation = true; - return GlobalsSearch.Fail; - } + // Do a relatively cheap check to bail early if all re-exports are non-importable + // due to file location or package.json dependency filtering. For non-node16+ + // module resolution modes, getting past this point guarantees that we'll be + // able to generate a suitable module specifier, so we can safely show a completion, + // even if we defer computing the module specifier. + const firstImportableExportInfo = find(info, isImportableExportInfo); + if (!firstImportableExportInfo) { + return; + } - completionKind = CompletionKind.MemberLike; - isNewIdentifierLocation = false; - const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol); - const existing = new Set((namedImportsOrExports.elements as NodeArray).filter(n => !isCurrentlyEditingNode(n)).map(n => (n.propertyName || n.name).escapedText)); - const uniques = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName)); - symbols = concatenate(symbols, uniques); - if (!uniques.length) { - // If there's nothing else to import, don't offer `type` either - keywordFilters = KeywordCompletionFilters.None; - } - return GlobalsSearch.Success; - } + // In node16+, module specifier resolution can fail due to modules being blocked + // by package.json `exports`. If that happens, don't show a completion item. + // N.B. in this resolution mode we always try to resolve module specifiers here, + // because we have to know now if it's going to fail so we can omit the completion + // from the list. + const result = context.tryResolve(info, symbolName, isFromAmbientModule) || {}; + if (result === "failed") return; + + // If we skipped resolving module specifiers, our selection of which ExportInfo + // to use here is arbitrary, since the info shown in the completion list derived from + // it should be identical regardless of which one is used. During the subsequent + // `CompletionEntryDetails` request, we'll get all the ExportInfos again and pick + // the best one based on the module specifier it produces. + let exportInfo = firstImportableExportInfo, moduleSpecifier; + if (result !== "skipped") { + ({ exportInfo = firstImportableExportInfo, moduleSpecifier } = result); + } - /** - * Adds local declarations for completions in named exports: - * - * export { | }; - * - * Does not check for the absence of a module specifier (`export {} from "./other"`) - * because `tryGetImportOrExportClauseCompletionSymbols` runs first and handles that, - * preventing this function from running. - */ - function tryGetLocalNamedExportCompletionSymbols(): GlobalsSearch { - const namedExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken) - ? tryCast(contextToken.parent, isNamedExports) - : undefined; + const isDefaultExport = exportInfo.exportKind === ExportKind.Default; + const symbol = isDefaultExport && getLocalSymbolForExportDefault(exportInfo.symbol) || exportInfo.symbol; + + pushAutoImportSymbol(symbol, { + kind: moduleSpecifier ? SymbolOriginInfoKind.ResolvedExport : SymbolOriginInfoKind.Export, + moduleSpecifier, + symbolName, + exportMapKey, + exportName: exportInfo.exportKind === ExportKind.ExportEquals ? InternalSymbolName.ExportEquals : exportInfo.symbol.name, + fileName: exportInfo.moduleFileName, + isDefaultExport, + moduleSymbol: exportInfo.moduleSymbol, + isFromPackageJson: exportInfo.isFromPackageJson, + }); + } + ); - if (!namedExports) { - return GlobalsSearch.Continue; + hasUnresolvedAutoImports = context.skippedAny(); + flags |= context.resolvedAny() ? CompletionInfoFlags.ResolvedModuleSpecifiers : 0; + flags |= context.resolvedBeyondLimit() ? CompletionInfoFlags.ResolvedModuleSpecifiersBeyondLimit : 0; } + ); - const localsContainer = findAncestor(namedExports, or(isSourceFile, isModuleDeclaration))!; - completionKind = CompletionKind.None; - isNewIdentifierLocation = false; - localsContainer.locals?.forEach((symbol, name) => { - symbols.push(symbol); - if (localsContainer.symbol?.exports?.has(name)) { - symbolToSortTextMap[getSymbolId(symbol)] = SortText.OptionalMember; + function isImportableExportInfo(info: SymbolExportInfo) { + const moduleFile = tryCast(info.moduleSymbol.valueDeclaration, isSourceFile); + if (!moduleFile) { + const moduleName = stripQuotes(info.moduleSymbol.name); + if (JsTyping.nodeCoreModules.has(moduleName) && startsWith(moduleName, "node:") !== shouldUseUriStyleNodeCoreModules(sourceFile, program)) { + return false; } - }); - return GlobalsSearch.Success; + return packageJsonFilter + ? packageJsonFilter.allowsImportingAmbientModule(info.moduleSymbol, getModuleSpecifierResolutionHost(info.isFromPackageJson)) + : true; + } + return isImportableFile( + info.isFromPackageJson ? packageJsonAutoImportProvider! : program, + sourceFile, + moduleFile, + preferences, + packageJsonFilter, + getModuleSpecifierResolutionHost(info.isFromPackageJson), + moduleSpecifierCache); } + } - /** - * Aggregates relevant symbols for completion in class declaration - * Relevant symbols are stored in the captured 'symbols' variable. - */ - function tryGetClassLikeCompletionSymbols(): GlobalsSearch { - const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location, position); - if (!decl) return GlobalsSearch.Continue; - - // We're looking up possible property names from parent type. - completionKind = CompletionKind.MemberLike; - // Declaring new property/method/accessor - isNewIdentifierLocation = true; - keywordFilters = contextToken.kind === SyntaxKind.AsteriskToken ? KeywordCompletionFilters.None : - isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords; - - // If you're in an interface you don't want to repeat things from super-interface. So just stop here. - if (!isClassLike(decl)) return GlobalsSearch.Success; - - const classElement = contextToken.kind === SyntaxKind.SemicolonToken ? contextToken.parent.parent : contextToken.parent; - let classElementModifierFlags = isClassElement(classElement) ? getEffectiveModifierFlags(classElement) : ModifierFlags.None; - // If this is context token is not something we are editing now, consider if this would lead to be modifier - if (contextToken.kind === SyntaxKind.Identifier && !isCurrentlyEditingNode(contextToken)) { - switch (contextToken.getText()) { - case "private": - classElementModifierFlags = classElementModifierFlags | ModifierFlags.Private; - break; - case "static": - classElementModifierFlags = classElementModifierFlags | ModifierFlags.Static; - break; - case "override": - classElementModifierFlags = classElementModifierFlags | ModifierFlags.Override; - break; - } + function pushAutoImportSymbol(symbol: Symbol, origin: SymbolOriginInfoResolvedExport | SymbolOriginInfoExport) { + const symbolId = getSymbolId(symbol); + if (symbolToSortTextMap[symbolId] === SortText.GlobalsOrKeywords) { + // If an auto-importable symbol is available as a global, don't add the auto import + return; + } + symbolToOriginInfoMap[symbols.length] = origin; + symbolToSortTextMap[symbolId] = importStatementCompletion ? SortText.LocationPriority : SortText.AutoImportSuggestions; + symbols.push(symbol); + } + + /* Mutates `symbols` and `symbolToOriginInfoMap`. */ + function collectObjectLiteralMethodSymbols(members: Symbol[], enclosingDeclaration: ObjectLiteralExpression): void { + // TODO: support JS files. + if (isInJSFile(location)) { + return; + } + members.forEach(member => { + if (!isObjectLiteralMethodSymbol(member)) { + return; } - if (isClassStaticBlockDeclaration(classElement)) { - classElementModifierFlags |= ModifierFlags.Static; + const displayName = getCompletionEntryDisplayNameForSymbol( + member, + getEmitScriptTarget(compilerOptions), + /*origin*/ undefined, + CompletionKind.ObjectPropertyDeclaration, + /*jsxIdentifierExpected*/ false); + if (!displayName) { + return; } - - // No member list for private methods - if (!(classElementModifierFlags & ModifierFlags.Private)) { - // List of property symbols of base type that are not private and already implemented - const baseTypeNodes = isClassLike(decl) && classElementModifierFlags & ModifierFlags.Override ? singleElementArray(getEffectiveBaseTypeNode(decl)) : getAllSuperTypeNodes(decl); - const baseSymbols = flatMap(baseTypeNodes, baseTypeNode => { - const type = typeChecker.getTypeAtLocation(baseTypeNode); - return classElementModifierFlags & ModifierFlags.Static ? - type?.symbol && typeChecker.getPropertiesOfType(typeChecker.getTypeOfSymbolAtLocation(type.symbol, decl)) : - type && typeChecker.getPropertiesOfType(type); - }); - symbols = concatenate(symbols, filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags)); + const { name } = displayName; + const entryProps = getEntryForObjectLiteralMethodCompletion( + member, + name, + enclosingDeclaration, + program, + host, + compilerOptions, + preferences, + formatContext); + if (!entryProps) { + return; } + const origin: SymbolOriginInfoObjectLiteralMethod = { kind: SymbolOriginInfoKind.ObjectLiteralMethod, ...entryProps }; + flags |= CompletionInfoFlags.MayIncludeMethodSnippets; + symbolToOriginInfoMap[symbols.length] = origin; + symbols.push(member); + }); + } - return GlobalsSearch.Success; + function isObjectLiteralMethodSymbol(symbol: Symbol): boolean { + /* + For an object type + `type Foo = { + bar(x: number): void; + foo: (x: string) => string; + }`, + `bar` will have symbol flag `Method`, + `foo` will have symbol flag `Property`. + */ + if (!(symbol.flags & (SymbolFlags.Property | SymbolFlags.Method))) { + return false; } + return true; + } - function isConstructorParameterCompletion(node: Node): boolean { - return !!node.parent && isParameter(node.parent) && isConstructorDeclaration(node.parent.parent) - && (isParameterPropertyModifier(node.kind) || isDeclarationName(node)); + /** + * Finds the first node that "embraces" the position, so that one may + * accurately aggregate locals from the closest containing scope. + */ + function getScopeNode(initialToken: Node | undefined, position: number, sourceFile: SourceFile) { + let scope: Node | undefined = initialToken; + while (scope && !positionBelongsToNode(scope, position, sourceFile)) { + scope = scope.parent; } + return scope; + } - /** - * Returns the immediate owning class declaration of a context token, - * on the condition that one exists and that the context implies completion should be given. - */ - function tryGetConstructorLikeCompletionContainer(contextToken: Node): ConstructorDeclaration | undefined { - if (contextToken) { - const parent = contextToken.parent; - switch (contextToken.kind) { - case SyntaxKind.OpenParenToken: - case SyntaxKind.CommaToken: - return isConstructorDeclaration(contextToken.parent) ? contextToken.parent : undefined; - - default: - if (isConstructorParameterCompletion(contextToken)) { - return parent.parent as ConstructorDeclaration; - } - } - } - return undefined; - } + function isCompletionListBlocker(contextToken: Node): boolean { + const start = timestamp(); + const result = isInStringOrRegularExpressionOrTemplateLiteral(contextToken) || + isSolelyIdentifierDefinitionLocation(contextToken) || + isDotOfNumericLiteral(contextToken) || + isInJsxText(contextToken) || + isBigIntLiteral(contextToken); + log("getCompletionsAtPosition: isCompletionListBlocker: " + (timestamp() - start)); + return result; + } - function tryGetFunctionLikeBodyCompletionContainer(contextToken: Node): FunctionLikeDeclaration | undefined { - if (contextToken) { - let prev: Node; - const container = findAncestor(contextToken.parent, (node: Node) => { - if (isClassLike(node)) { - return "quit"; - } - if (isFunctionLikeDeclaration(node) && prev === node.body) { - return true; - } - prev = node; - return false; - }); - return container && container as FunctionLikeDeclaration; - } + function isInJsxText(contextToken: Node): boolean { + if (contextToken.kind === SyntaxKind.JsxText) { + return true; } - function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement | undefined { - if (contextToken) { - const parent = contextToken.parent; - switch (contextToken.kind) { - case SyntaxKind.GreaterThanToken: // End of a type argument list - case SyntaxKind.LessThanSlashToken: - case SyntaxKind.SlashToken: - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.JsxAttributes: - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxSpreadAttribute: - if (parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) { - if (contextToken.kind === SyntaxKind.GreaterThanToken) { - const precedingToken = findPrecedingToken(contextToken.pos, sourceFile, /*startNode*/ undefined); - if (!(parent as JsxOpeningLikeElement).typeArguments || (precedingToken && precedingToken.kind === SyntaxKind.SlashToken)) break; - } - return parent as JsxOpeningLikeElement; - } - else if (parent.kind === SyntaxKind.JsxAttribute) { - // Currently we parse JsxOpeningLikeElement as: - // JsxOpeningLikeElement - // attributes: JsxAttributes - // properties: NodeArray - return parent.parent.parent as JsxOpeningLikeElement; - } - break; - - // The context token is the closing } or " of an attribute, which means - // its parent is a JsxExpression, whose parent is a JsxAttribute, - // whose parent is a JsxOpeningLikeElement - case SyntaxKind.StringLiteral: - if (parent && ((parent.kind === SyntaxKind.JsxAttribute) || (parent.kind === SyntaxKind.JsxSpreadAttribute))) { - // Currently we parse JsxOpeningLikeElement as: - // JsxOpeningLikeElement - // attributes: JsxAttributes - // properties: NodeArray - return parent.parent.parent as JsxOpeningLikeElement; - } - - break; - - case SyntaxKind.CloseBraceToken: - if (parent && - parent.kind === SyntaxKind.JsxExpression && - parent.parent && parent.parent.kind === SyntaxKind.JsxAttribute) { - // Currently we parse JsxOpeningLikeElement as: - // JsxOpeningLikeElement - // attributes: JsxAttributes - // properties: NodeArray - // each JsxAttribute can have initializer as JsxExpression - return parent.parent.parent.parent as JsxOpeningLikeElement; - } + if (contextToken.kind === SyntaxKind.GreaterThanToken && contextToken.parent) { + // /**/ /> + // /**/ > + // - contextToken: GreaterThanToken (before cursor) + // - location: JsxSelfClosingElement or JsxOpeningElement + // - contextToken.parent === location + if (location === contextToken.parent && (location.kind === SyntaxKind.JsxOpeningElement || location.kind === SyntaxKind.JsxSelfClosingElement)) { + return false; + } - if (parent && parent.kind === SyntaxKind.JsxSpreadAttribute) { - // Currently we parse JsxOpeningLikeElement as: - // JsxOpeningLikeElement - // attributes: JsxAttributes - // properties: NodeArray - return parent.parent.parent as JsxOpeningLikeElement; - } + if (contextToken.parent.kind === SyntaxKind.JsxOpeningElement) { + //
/**/ + // - contextToken: GreaterThanToken (before cursor) + // - location: JSXElement + // - different parents (JSXOpeningElement, JSXElement) + return location.parent.kind !== SyntaxKind.JsxOpeningElement; + } - break; - } + if (contextToken.parent.kind === SyntaxKind.JsxClosingElement || contextToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { + return !!contextToken.parent.parent && contextToken.parent.parent.kind === SyntaxKind.JsxElement; } - return undefined; } + return false; + } - /** - * @returns true if we are certain that the currently edited location must define a new location; false otherwise. - */ - function isSolelyIdentifierDefinitionLocation(contextToken: Node): boolean { - const parent = contextToken.parent; - const containingNodeKind = parent.kind; - switch (contextToken.kind) { + function isNewIdentifierDefinitionLocation(): boolean { + if (contextToken) { + const containingNodeKind = contextToken.parent.kind; + const tokenKind = keywordForNode(contextToken); + // Previous token may have been a keyword that was converted to an identifier. + switch (tokenKind) { case SyntaxKind.CommaToken: - return containingNodeKind === SyntaxKind.VariableDeclaration || - isVariableDeclarationListButNotTypeArgument(contextToken) || - containingNodeKind === SyntaxKind.VariableStatement || - containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, | - isFunctionLikeButNotConstructor(containingNodeKind) || - containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A= contextToken.pos); - - case SyntaxKind.DotToken: - return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [.| - - case SyntaxKind.ColonToken: - return containingNodeKind === SyntaxKind.BindingElement; // var {x :html| - - case SyntaxKind.OpenBracketToken: - return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x| + return containingNodeKind === SyntaxKind.CallExpression // func( a, | + || containingNodeKind === SyntaxKind.Constructor // constructor( a, | /* public, protected, private keywords are allowed here, so show completion */ + || containingNodeKind === SyntaxKind.NewExpression // new C(a, | + || containingNodeKind === SyntaxKind.ArrayLiteralExpression // [a, | + || containingNodeKind === SyntaxKind.BinaryExpression // const x = (a, | + || containingNodeKind === SyntaxKind.FunctionType // var x: (s: string, list| + || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { x, | case SyntaxKind.OpenParenToken: - return containingNodeKind === SyntaxKind.CatchClause || - isFunctionLikeButNotConstructor(containingNodeKind); - - case SyntaxKind.OpenBraceToken: - return containingNodeKind === SyntaxKind.EnumDeclaration; // enum a { | - - case SyntaxKind.LessThanToken: - return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< | - containingNodeKind === SyntaxKind.ClassExpression || // var C = class D< | - containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A< | - containingNodeKind === SyntaxKind.TypeAliasDeclaration || // type List< | - isFunctionLikeKind(containingNodeKind); + return containingNodeKind === SyntaxKind.CallExpression // func( | + || containingNodeKind === SyntaxKind.Constructor // constructor( | + || containingNodeKind === SyntaxKind.NewExpression // new C(a| + || containingNodeKind === SyntaxKind.ParenthesizedExpression // const x = (a| + || containingNodeKind === SyntaxKind.ParenthesizedType; // function F(pred: (a| /* this can become an arrow function, where 'a' is the argument */ - case SyntaxKind.StaticKeyword: - return containingNodeKind === SyntaxKind.PropertyDeclaration && !isClassLike(parent.parent); + case SyntaxKind.OpenBracketToken: + return containingNodeKind === SyntaxKind.ArrayLiteralExpression // [ | + || containingNodeKind === SyntaxKind.IndexSignature // [ | : string ] + || containingNodeKind === SyntaxKind.ComputedPropertyName; // [ | /* this can become an index signature */ - case SyntaxKind.DotDotDotToken: - return containingNodeKind === SyntaxKind.Parameter || - (!!parent.parent && parent.parent.kind === SyntaxKind.ArrayBindingPattern); // var [...z| + case SyntaxKind.ModuleKeyword: // module | + case SyntaxKind.NamespaceKeyword: // namespace | + case SyntaxKind.ImportKeyword: // import | + return true; - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - return containingNodeKind === SyntaxKind.Parameter && !isConstructorDeclaration(parent.parent); + case SyntaxKind.DotToken: + return containingNodeKind === SyntaxKind.ModuleDeclaration; // module A.| - case SyntaxKind.AsKeyword: - return containingNodeKind === SyntaxKind.ImportSpecifier || - containingNodeKind === SyntaxKind.ExportSpecifier || - containingNodeKind === SyntaxKind.NamespaceImport; + case SyntaxKind.OpenBraceToken: + return containingNodeKind === SyntaxKind.ClassDeclaration // class A { | + || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { | - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - return !isFromObjectTypeDeclaration(contextToken); + case SyntaxKind.EqualsToken: + return containingNodeKind === SyntaxKind.VariableDeclaration // const x = a| + || containingNodeKind === SyntaxKind.BinaryExpression; // x = a| - case SyntaxKind.Identifier: - if (containingNodeKind === SyntaxKind.ImportSpecifier && - contextToken === (parent as ImportSpecifier).name && - (contextToken as Identifier).text === "type" - ) { - // import { type | } - return false; - } - break; + case SyntaxKind.TemplateHead: + return containingNodeKind === SyntaxKind.TemplateExpression; // `aa ${| - case SyntaxKind.ClassKeyword: - case SyntaxKind.EnumKeyword: - case SyntaxKind.InterfaceKeyword: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.VarKeyword: - case SyntaxKind.ImportKeyword: - case SyntaxKind.LetKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.InferKeyword: - return true; + case SyntaxKind.TemplateMiddle: + return containingNodeKind === SyntaxKind.TemplateSpan; // `aa ${10} dd ${| - case SyntaxKind.TypeKeyword: - // import { type foo| } - return containingNodeKind !== SyntaxKind.ImportSpecifier; + case SyntaxKind.AsyncKeyword: + return containingNodeKind === SyntaxKind.MethodDeclaration // const obj = { async c|() + || containingNodeKind === SyntaxKind.ShorthandPropertyAssignment; // const obj = { async c| case SyntaxKind.AsteriskToken: - return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent); + return containingNodeKind === SyntaxKind.MethodDeclaration; // const obj = { * c| } - // If the previous token is keyword corresponding to class member completion keyword - // there will be completion available here - if (isClassMemberCompletionKeyword(keywordForNode(contextToken)) && isFromObjectTypeDeclaration(contextToken)) { - return false; + if (isClassMemberCompletionKeyword(tokenKind)) { + return true; } + } - if (isConstructorParameterCompletion(contextToken)) { - // constructor parameter completion is available only if - // - its modifier of the constructor parameter or - // - its name of the parameter and not being edited - // eg. constructor(a |<- this shouldnt show completion - if (!isIdentifier(contextToken) || - isParameterPropertyModifier(keywordForNode(contextToken)) || - isCurrentlyEditingNode(contextToken)) { - return false; - } - } + return false; + } - // Previous token may have been a keyword that was converted to an identifier. - switch (keywordForNode(contextToken)) { - case SyntaxKind.AbstractKeyword: - case SyntaxKind.ClassKeyword: - case SyntaxKind.ConstKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.EnumKeyword: - case SyntaxKind.FunctionKeyword: - case SyntaxKind.InterfaceKeyword: - case SyntaxKind.LetKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - case SyntaxKind.PublicKeyword: - case SyntaxKind.StaticKeyword: - case SyntaxKind.VarKeyword: - return true; - case SyntaxKind.AsyncKeyword: - return isPropertyDeclaration(contextToken.parent); - } + function isInStringOrRegularExpressionOrTemplateLiteral(contextToken: Node): boolean { + // To be "in" one of these literals, the position has to be: + // 1. entirely within the token text. + // 2. at the end position of an unterminated token. + // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). + return (isRegularExpressionLiteral(contextToken) || isStringTextContainingNode(contextToken)) && ( + rangeContainsPositionExclusive(createTextRangeFromSpan(createTextSpanFromNode(contextToken)), position) || + position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken))); + } - // If we are inside a class declaration, and `constructor` is totally not present, - // but we request a completion manually at a whitespace... - const ancestorClassLike = findAncestor(contextToken.parent, isClassLike); - if (ancestorClassLike && contextToken === previousToken && isPreviousPropertyDeclarationTerminated(contextToken, position)) { - return false; // Don't block completions. - } + function tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols(): GlobalsSearch | undefined { + const typeLiteralNode = tryGetTypeLiteralNode(contextToken); + if (!typeLiteralNode) return GlobalsSearch.Continue; - const ancestorPropertyDeclaraion = getAncestor(contextToken.parent, SyntaxKind.PropertyDeclaration); - // If we are inside a class declaration and typing `constructor` after property declaration... - if (ancestorPropertyDeclaraion - && contextToken !== previousToken - && isClassLike(previousToken.parent.parent) - // And the cursor is at the token... - && position <= previousToken.end) { - // If we are sure that the previous property declaration is terminated according to newline or semicolon... - if (isPreviousPropertyDeclarationTerminated(contextToken, previousToken.end)) { - return false; // Don't block completions. - } - else if (contextToken.kind !== SyntaxKind.EqualsToken - // Should not block: `class C { blah = c/**/ }` - // But should block: `class C { blah = somewhat c/**/ }` and `class C { blah: SomeType c/**/ }` - && (isInitializedProperty(ancestorPropertyDeclaraion as PropertyDeclaration) - || hasType(ancestorPropertyDeclaraion))) { - return true; - } - } + const intersectionTypeNode = isIntersectionTypeNode(typeLiteralNode.parent) ? typeLiteralNode.parent : undefined; + const containerTypeNode = intersectionTypeNode || typeLiteralNode; - return isDeclarationName(contextToken) - && !isShorthandPropertyAssignment(contextToken.parent) - && !isJsxAttribute(contextToken.parent) - // Don't block completions if we're in `class C /**/`, because we're *past* the end of the identifier and might want to complete `extends`. - // If `contextToken !== previousToken`, this is `class C ex/**/`. - && !(isClassLike(contextToken.parent) && (contextToken !== previousToken || position > previousToken.end)); - } + const containerExpectedType = getConstraintOfTypeArgumentProperty(containerTypeNode, typeChecker); + if (!containerExpectedType) return GlobalsSearch.Continue; - function isPreviousPropertyDeclarationTerminated(contextToken: Node, position: number) { - return contextToken.kind !== SyntaxKind.EqualsToken && - (contextToken.kind === SyntaxKind.SemicolonToken - || !positionsAreOnSameLine(contextToken.end, position, sourceFile)); - } + const containerActualType = typeChecker.getTypeFromTypeNode(containerTypeNode); - function isFunctionLikeButNotConstructor(kind: SyntaxKind) { - return isFunctionLikeKind(kind) && kind !== SyntaxKind.Constructor; - } + const members = getPropertiesForCompletion(containerExpectedType, typeChecker); + const existingMembers = getPropertiesForCompletion(containerActualType, typeChecker); - function isDotOfNumericLiteral(contextToken: Node): boolean { - if (contextToken.kind === SyntaxKind.NumericLiteral) { - const text = contextToken.getFullText(); - return text.charAt(text.length - 1) === "."; - } + const existingMemberEscapedNames: Set<__String> = new Set(); + existingMembers.forEach(s => existingMemberEscapedNames.add(s.escapedName)); - return false; - } + symbols = concatenate(symbols, filter(members, s => !existingMemberEscapedNames.has(s.escapedName))); - function isVariableDeclarationListButNotTypeArgument(node: Node): boolean { - return node.parent.kind === SyntaxKind.VariableDeclarationList - && !isPossiblyTypeArgumentPosition(node, sourceFile, typeChecker); - } + completionKind = CompletionKind.ObjectPropertyDeclaration; + isNewIdentifierLocation = true; - /** - * Filters out completion suggestions for named imports or exports. - * - * @returns Symbols to be suggested in an object binding pattern or object literal expression, barring those whose declarations - * do not occur at the current position and have not otherwise been typed. - */ - function filterObjectMembersList(contextualMemberSymbols: Symbol[], existingMembers: readonly Declaration[]): Symbol[] { - if (existingMembers.length === 0) { - return contextualMemberSymbols; - } + return GlobalsSearch.Success; + } - const membersDeclaredBySpreadAssignment = new Set(); - const existingMemberNames = new Set<__String>(); - for (const m of existingMembers) { - // Ignore omitted expressions for missing members - if (m.kind !== SyntaxKind.PropertyAssignment && - m.kind !== SyntaxKind.ShorthandPropertyAssignment && - m.kind !== SyntaxKind.BindingElement && - m.kind !== SyntaxKind.MethodDeclaration && - m.kind !== SyntaxKind.GetAccessor && - m.kind !== SyntaxKind.SetAccessor && - m.kind !== SyntaxKind.SpreadAssignment) { - continue; - } + /** + * Aggregates relevant symbols for completion in object literals and object binding patterns. + * Relevant symbols are stored in the captured 'symbols' variable. + * + * @returns true if 'symbols' was successfully populated; false otherwise. + */ + function tryGetObjectLikeCompletionSymbols(): GlobalsSearch | undefined { + const symbolsStartIndex = symbols.length; + const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); + if (!objectLikeContainer) return GlobalsSearch.Continue; - // If this is the current item we are editing right now, do not filter it out - if (isCurrentlyEditingNode(m)) { - continue; - } + // We're looking up possible property names from contextual/inferred/declared type. + completionKind = CompletionKind.ObjectPropertyDeclaration; - let existingName: __String | undefined; + let typeMembers: Symbol[] | undefined; + let existingMembers: readonly Declaration[] | undefined; - if (isSpreadAssignment(m)) { - setMembersDeclaredBySpreadAssignment(m, membersDeclaredBySpreadAssignment); - } - else if (isBindingElement(m) && m.propertyName) { - // include only identifiers in completion list - if (m.propertyName.kind === SyntaxKind.Identifier) { - existingName = m.propertyName.escapedText; - } - } - else { - // TODO: Account for computed property name - // NOTE: if one only performs this step when m.name is an identifier, - // things like '__proto__' are not filtered out. - const name = getNameOfDeclaration(m); - existingName = name && isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; - } + if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { + const instantiatedType = tryGetObjectLiteralContextualType(objectLikeContainer, typeChecker); - if (existingName !== undefined) { - existingMemberNames.add(existingName); + // Check completions for Object property value shorthand + if (instantiatedType === undefined) { + if (objectLikeContainer.flags & NodeFlags.InWithStatement) { + return GlobalsSearch.Fail; } + isNonContextualObjectLiteral = true; + return GlobalsSearch.Continue; } - - const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.has(m.escapedName)); - setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); - - return filteredSymbols; - } - - function setMembersDeclaredBySpreadAssignment(declaration: SpreadAssignment | JsxSpreadAttribute, membersDeclaredBySpreadAssignment: Set) { - const expression = declaration.expression; - const symbol = typeChecker.getSymbolAtLocation(expression); - const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); - const properties = type && (type as ObjectType).properties; - if (properties) { - properties.forEach(property => { - membersDeclaredBySpreadAssignment.add(property.name); - }); + const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions); + const hasStringIndexType = (completionsType || instantiatedType).getStringIndexType(); + const hasNumberIndextype = (completionsType || instantiatedType).getNumberIndexType(); + isNewIdentifierLocation = !!hasStringIndexType || !!hasNumberIndextype; + typeMembers = getPropertiesForObjectExpression(instantiatedType, completionsType, objectLikeContainer, typeChecker); + existingMembers = objectLikeContainer.properties; + + if (typeMembers.length === 0) { + // Edge case: If NumberIndexType exists + if (!hasNumberIndextype) { + isNonContextualObjectLiteral = true; + return GlobalsSearch.Continue; + } } } + else { + Debug.assert(objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern); + // We are *only* completing on properties from the type being destructured. + isNewIdentifierLocation = false; - // Set SortText to OptionalMember if it is an optional member - function setSortTextToOptionalMember() { - symbols.forEach(m => { - if (m.flags & SymbolFlags.Optional) { - const symbolId = getSymbolId(m); - symbolToSortTextMap[symbolId] = symbolToSortTextMap[symbolId] ?? SortText.OptionalMember; + const rootDeclaration = getRootDeclaration(objectLikeContainer.parent); + if (!isVariableLike(rootDeclaration)) return Debug.fail("Root declaration is not variable-like."); + + // We don't want to complete using the type acquired by the shape + // of the binding pattern; we are only interested in types acquired + // through type declaration or inference. + // Also proceed if rootDeclaration is a parameter and if its containing function expression/arrow function is contextually typed - + // type of parameter will flow in from the contextual type of the function + let canGetType = hasInitializer(rootDeclaration) || !!getEffectiveTypeAnnotationNode(rootDeclaration) || rootDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement; + if (!canGetType && rootDeclaration.kind === SyntaxKind.Parameter) { + if (isExpression(rootDeclaration.parent)) { + canGetType = !!typeChecker.getContextualType(rootDeclaration.parent as Expression); } - }); - } - - // Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment - function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Set, contextualMemberSymbols: Symbol[]): void { - if (membersDeclaredBySpreadAssignment.size === 0) { - return; - } - for (const contextualMemberSymbol of contextualMemberSymbols) { - if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) { - symbolToSortTextMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment; + else if (rootDeclaration.parent.kind === SyntaxKind.MethodDeclaration || rootDeclaration.parent.kind === SyntaxKind.SetAccessor) { + canGetType = isExpression(rootDeclaration.parent.parent) && !!typeChecker.getContextualType(rootDeclaration.parent.parent as Expression); } } + if (canGetType) { + const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); + if (!typeForObject) return GlobalsSearch.Fail; + typeMembers = typeChecker.getPropertiesOfType(typeForObject).filter(propertySymbol => { + return typeChecker.isPropertyAccessible(objectLikeContainer, /*isSuper*/ false, /*writing*/ false, typeForObject, propertySymbol); + }); + existingMembers = objectLikeContainer.elements; + } } - function transformObjectLiteralMembersSortText(start: number): void { - for (let i = start; i < symbols.length; i++) { - const symbol = symbols[i]; - const symbolId = getSymbolId(symbol); - const origin = symbolToOriginInfoMap?.[i]; - const target = getEmitScriptTarget(compilerOptions); - const displayName = getCompletionEntryDisplayNameForSymbol( - symbol, - target, - origin, - CompletionKind.ObjectPropertyDeclaration, - /*jsxIdentifierExpected*/ false); - if (displayName) { - const originalSortText = symbolToSortTextMap[symbolId] ?? SortText.LocationPriority; - const { name } = displayName; - symbolToSortTextMap[symbolId] = SortText.ObjectLiteralProperty(originalSortText, name); - } + if (typeMembers && typeMembers.length > 0) { + // Add filtered items to the completion list + const filteredMembers = filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers)); + symbols = concatenate(symbols, filteredMembers); + setSortTextToOptionalMember(); + if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression + && preferences.includeCompletionsWithObjectLiteralMethodSnippets + && preferences.includeCompletionsWithInsertText) { + transformObjectLiteralMembersSortText(symbolsStartIndex); + collectObjectLiteralMethodSymbols(filteredMembers, objectLikeContainer); } } - /** - * Filters out completion suggestions for class elements. - * - * @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags - */ - function filterClassMembersList(baseSymbols: readonly Symbol[], existingMembers: readonly ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] { - const existingMemberNames = new Set<__String>(); - for (const m of existingMembers) { - // Ignore omitted expressions for missing members - if (m.kind !== SyntaxKind.PropertyDeclaration && - m.kind !== SyntaxKind.MethodDeclaration && - m.kind !== SyntaxKind.GetAccessor && - m.kind !== SyntaxKind.SetAccessor) { - continue; - } + return GlobalsSearch.Success; + } - // If this is the current item we are editing right now, do not filter it out - if (isCurrentlyEditingNode(m)) { - continue; - } + /** + * Aggregates relevant symbols for completion in import clauses and export clauses + * whose declarations have a module specifier; for instance, symbols will be aggregated for + * + * import { | } from "moduleName"; + * export { a as foo, | } from "moduleName"; + * + * but not for + * + * export { | }; + * + * Relevant symbols are stored in the captured 'symbols' variable. + */ + function tryGetImportOrExportClauseCompletionSymbols(): GlobalsSearch { + if (!contextToken) return GlobalsSearch.Continue; - // Dont filter member even if the name matches if it is declared private in the list - if (hasEffectiveModifier(m, ModifierFlags.Private)) { - continue; - } + // `import { |` or `import { a as 0, | }` or `import { type | }` + const namedImportsOrExports = + contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken ? tryCast(contextToken.parent, isNamedImportsOrExports) : + isTypeKeywordTokenOrIdentifier(contextToken) ? tryCast(contextToken.parent.parent, isNamedImportsOrExports) : undefined; - // do not filter it out if the static presence doesnt match - if (isStatic(m) !== !!(currentClassElementModifierFlags & ModifierFlags.Static)) { - continue; - } + if (!namedImportsOrExports) return GlobalsSearch.Continue; - const existingName = getPropertyNameForPropertyNameNode(m.name!); - if (existingName) { - existingMemberNames.add(existingName); - } - } + // We can at least offer `type` at `import { |` + if (!isTypeKeywordTokenOrIdentifier(contextToken)) { + keywordFilters = KeywordCompletionFilters.TypeKeyword; + } - return baseSymbols.filter(propertySymbol => - !existingMemberNames.has(propertySymbol.escapedName) && - !!propertySymbol.declarations && - !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private) && - !(propertySymbol.valueDeclaration && isPrivateIdentifierClassElementDeclaration(propertySymbol.valueDeclaration))); + // try to show exported member for imported/re-exported module + const { moduleSpecifier } = namedImportsOrExports.kind === SyntaxKind.NamedImports ? namedImportsOrExports.parent.parent : namedImportsOrExports.parent; + if (!moduleSpecifier) { + isNewIdentifierLocation = true; + return namedImportsOrExports.kind === SyntaxKind.NamedImports ? GlobalsSearch.Fail : GlobalsSearch.Continue; + } + const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); // TODO: GH#18217 + if (!moduleSpecifierSymbol) { + isNewIdentifierLocation = true; + return GlobalsSearch.Fail; } - /** - * Filters out completion suggestions from 'symbols' according to existing JSX attributes. - * - * @returns Symbols to be suggested in a JSX element, barring those whose attributes - * do not occur at the current position and have not otherwise been typed. - */ - function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { - const seenNames = new Set<__String>(); - const membersDeclaredBySpreadAssignment = new Set(); - for (const attr of attributes) { - // If this is the current item we are editing right now, do not filter it out - if (isCurrentlyEditingNode(attr)) { - continue; - } + completionKind = CompletionKind.MemberLike; + isNewIdentifierLocation = false; + const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol); + const existing = new Set((namedImportsOrExports.elements as NodeArray).filter(n => !isCurrentlyEditingNode(n)).map(n => (n.propertyName || n.name).escapedText)); + const uniques = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName)); + symbols = concatenate(symbols, uniques); + if (!uniques.length) { + // If there's nothing else to import, don't offer `type` either + keywordFilters = KeywordCompletionFilters.None; + } + return GlobalsSearch.Success; + } - if (attr.kind === SyntaxKind.JsxAttribute) { - seenNames.add(attr.name.escapedText); - } - else if (isJsxSpreadAttribute(attr)) { - setMembersDeclaredBySpreadAssignment(attr, membersDeclaredBySpreadAssignment); - } - } - const filteredSymbols = symbols.filter(a => !seenNames.has(a.escapedName)); + /** + * Adds local declarations for completions in named exports: + * + * export { | }; + * + * Does not check for the absence of a module specifier (`export {} from "./other"`) + * because `tryGetImportOrExportClauseCompletionSymbols` runs first and handles that, + * preventing this function from running. + */ + function tryGetLocalNamedExportCompletionSymbols(): GlobalsSearch { + const namedExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken) + ? tryCast(contextToken.parent, isNamedExports) + : undefined; + + if (!namedExports) { + return GlobalsSearch.Continue; + } - setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); + const localsContainer = findAncestor(namedExports, or(isSourceFile, isModuleDeclaration))!; + completionKind = CompletionKind.None; + isNewIdentifierLocation = false; + localsContainer.locals?.forEach((symbol, name) => { + symbols.push(symbol); + if (localsContainer.symbol?.exports?.has(name)) { + symbolToSortTextMap[getSymbolId(symbol)] = SortText.OptionalMember; + } + }); + return GlobalsSearch.Success; + } - return filteredSymbols; + /** + * Aggregates relevant symbols for completion in class declaration + * Relevant symbols are stored in the captured 'symbols' variable. + */ + function tryGetClassLikeCompletionSymbols(): GlobalsSearch { + const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location, position); + if (!decl) return GlobalsSearch.Continue; + + // We're looking up possible property names from parent type. + completionKind = CompletionKind.MemberLike; + // Declaring new property/method/accessor + isNewIdentifierLocation = true; + keywordFilters = contextToken.kind === SyntaxKind.AsteriskToken ? KeywordCompletionFilters.None : + isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords; + + // If you're in an interface you don't want to repeat things from super-interface. So just stop here. + if (!isClassLike(decl)) return GlobalsSearch.Success; + + const classElement = contextToken.kind === SyntaxKind.SemicolonToken ? contextToken.parent.parent : contextToken.parent; + let classElementModifierFlags = isClassElement(classElement) ? getEffectiveModifierFlags(classElement) : ModifierFlags.None; + // If this is context token is not something we are editing now, consider if this would lead to be modifier + if (contextToken.kind === SyntaxKind.Identifier && !isCurrentlyEditingNode(contextToken)) { + switch (contextToken.getText()) { + case "private": + classElementModifierFlags = classElementModifierFlags | ModifierFlags.Private; + break; + case "static": + classElementModifierFlags = classElementModifierFlags | ModifierFlags.Static; + break; + case "override": + classElementModifierFlags = classElementModifierFlags | ModifierFlags.Override; + break; + } + } + if (isClassStaticBlockDeclaration(classElement)) { + classElementModifierFlags |= ModifierFlags.Static; } - function isCurrentlyEditingNode(node: Node): boolean { - return node.getStart(sourceFile) <= position && position <= node.getEnd(); + // No member list for private methods + if (!(classElementModifierFlags & ModifierFlags.Private)) { + // List of property symbols of base type that are not private and already implemented + const baseTypeNodes = isClassLike(decl) && classElementModifierFlags & ModifierFlags.Override ? singleElementArray(getEffectiveBaseTypeNode(decl)) : getAllSuperTypeNodes(decl); + const baseSymbols = flatMap(baseTypeNodes, baseTypeNode => { + const type = typeChecker.getTypeAtLocation(baseTypeNode); + return classElementModifierFlags & ModifierFlags.Static ? + type?.symbol && typeChecker.getPropertiesOfType(typeChecker.getTypeOfSymbolAtLocation(type.symbol, decl)) : + type && typeChecker.getPropertiesOfType(type); + }); + symbols = concatenate(symbols, filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags)); } + + return GlobalsSearch.Success; + } + + function isConstructorParameterCompletion(node: Node): boolean { + return !!node.parent && isParameter(node.parent) && isConstructorDeclaration(node.parent.parent) + && (isParameterPropertyModifier(node.kind) || isDeclarationName(node)); } /** - * Returns the immediate owning object literal or binding pattern of a context token, + * Returns the immediate owning class declaration of a context token, * on the condition that one exists and that the context implies completion should be given. */ - function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): ObjectLiteralExpression | ObjectBindingPattern | undefined { + function tryGetConstructorLikeCompletionContainer(contextToken: Node): ConstructorDeclaration | undefined { if (contextToken) { - const { parent } = contextToken; + const parent = contextToken.parent; switch (contextToken.kind) { - case SyntaxKind.OpenBraceToken: // const x = { | - case SyntaxKind.CommaToken: // const x = { a: 0, | - if (isObjectLiteralExpression(parent) || isObjectBindingPattern(parent)) { - return parent; + case SyntaxKind.OpenParenToken: + case SyntaxKind.CommaToken: + return isConstructorDeclaration(contextToken.parent) ? contextToken.parent : undefined; + + default: + if (isConstructorParameterCompletion(contextToken)) { + return parent.parent as ConstructorDeclaration; } - break; - case SyntaxKind.AsteriskToken: - return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined; - case SyntaxKind.Identifier: - return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) - ? contextToken.parent.parent : undefined; } } - return undefined; } - function getRelevantTokens(position: number, sourceFile: SourceFile): { contextToken: Node, previousToken: Node } | { contextToken: undefined, previousToken: undefined } { - const previousToken = findPrecedingToken(position, sourceFile); - if (previousToken && position <= previousToken.end && (isMemberName(previousToken) || isKeyword(previousToken.kind))) { - const contextToken = findPrecedingToken(previousToken.getFullStart(), sourceFile, /*startNode*/ undefined)!; // TODO: GH#18217 - return { contextToken, previousToken }; - } - return { contextToken: previousToken as Node, previousToken: previousToken as Node }; - } - - function getAutoImportSymbolFromCompletionEntryData(name: string, data: CompletionEntryData, program: Program, host: LanguageServiceHost): { symbol: Symbol, origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport } | undefined { - const containingProgram = data.isPackageJsonImport ? host.getPackageJsonAutoImportProvider!()! : program; - const checker = containingProgram.getTypeChecker(); - const moduleSymbol = - data.ambientModuleName ? checker.tryFindAmbientModule(data.ambientModuleName) : - data.fileName ? checker.getMergedSymbol(Debug.checkDefined(containingProgram.getSourceFile(data.fileName)).symbol) : - undefined; - - if (!moduleSymbol) return undefined; - let symbol = data.exportName === InternalSymbolName.ExportEquals - ? checker.resolveExternalModuleSymbol(moduleSymbol) - : checker.tryGetMemberInModuleExportsAndProperties(data.exportName, moduleSymbol); - if (!symbol) return undefined; - const isDefaultExport = data.exportName === InternalSymbolName.Default; - symbol = isDefaultExport && getLocalSymbolForExportDefault(symbol) || symbol; - return { symbol, origin: completionEntryDataToSymbolOriginInfo(data, name, moduleSymbol) }; - } - - interface CompletionEntryDisplayNameForSymbol { - readonly name: string; - readonly needsConvertPropertyAccess: boolean; - } - function getCompletionEntryDisplayNameForSymbol( - symbol: Symbol, - target: ScriptTarget, - origin: SymbolOriginInfo | undefined, - kind: CompletionKind, - jsxIdentifierExpected: boolean, - ): CompletionEntryDisplayNameForSymbol | undefined { - const name = originIncludesSymbolName(origin) ? origin.symbolName : symbol.name; - if (name === undefined - // If the symbol is external module, don't show it in the completion list - // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there) - || symbol.flags & SymbolFlags.Module && isSingleOrDoubleQuote(name.charCodeAt(0)) - // If the symbol is the internal name of an ES symbol, it is not a valid entry. Internal names for ES symbols start with "__@" - || isKnownSymbol(symbol)) { - return undefined; + function tryGetFunctionLikeBodyCompletionContainer(contextToken: Node): FunctionLikeDeclaration | undefined { + if (contextToken) { + let prev: Node; + const container = findAncestor(contextToken.parent, (node: Node) => { + if (isClassLike(node)) { + return "quit"; + } + if (isFunctionLikeDeclaration(node) && prev === node.body) { + return true; + } + prev = node; + return false; + }); + return container && container as FunctionLikeDeclaration; } + } - const validNameResult: CompletionEntryDisplayNameForSymbol = { name, needsConvertPropertyAccess: false }; - if (isIdentifierText(name, target, jsxIdentifierExpected ? LanguageVariant.JSX : LanguageVariant.Standard) || symbol.valueDeclaration && isPrivateIdentifierClassElementDeclaration(symbol.valueDeclaration)) { - return validNameResult; - } - switch (kind) { - case CompletionKind.MemberLike: - return undefined; - case CompletionKind.ObjectPropertyDeclaration: - // TODO: GH#18169 - return { name: JSON.stringify(name), needsConvertPropertyAccess: false }; - case CompletionKind.PropertyAccess: - case CompletionKind.Global: // For a 'this.' completion it will be in a global context, but may have a non-identifier name. - // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547 - return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true }; - case CompletionKind.None: - case CompletionKind.String: - return validNameResult; - default: - Debug.assertNever(kind); + function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement | undefined { + if (contextToken) { + const parent = contextToken.parent; + switch (contextToken.kind) { + case SyntaxKind.GreaterThanToken: // End of a type argument list + case SyntaxKind.LessThanSlashToken: + case SyntaxKind.SlashToken: + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.JsxAttributes: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + if (parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) { + if (contextToken.kind === SyntaxKind.GreaterThanToken) { + const precedingToken = findPrecedingToken(contextToken.pos, sourceFile, /*startNode*/ undefined); + if (!(parent as JsxOpeningLikeElement).typeArguments || (precedingToken && precedingToken.kind === SyntaxKind.SlashToken)) break; + } + return parent as JsxOpeningLikeElement; + } + else if (parent.kind === SyntaxKind.JsxAttribute) { + // Currently we parse JsxOpeningLikeElement as: + // JsxOpeningLikeElement + // attributes: JsxAttributes + // properties: NodeArray + return parent.parent.parent as JsxOpeningLikeElement; + } + break; + + // The context token is the closing } or " of an attribute, which means + // its parent is a JsxExpression, whose parent is a JsxAttribute, + // whose parent is a JsxOpeningLikeElement + case SyntaxKind.StringLiteral: + if (parent && ((parent.kind === SyntaxKind.JsxAttribute) || (parent.kind === SyntaxKind.JsxSpreadAttribute))) { + // Currently we parse JsxOpeningLikeElement as: + // JsxOpeningLikeElement + // attributes: JsxAttributes + // properties: NodeArray + return parent.parent.parent as JsxOpeningLikeElement; + } + + break; + + case SyntaxKind.CloseBraceToken: + if (parent && + parent.kind === SyntaxKind.JsxExpression && + parent.parent && parent.parent.kind === SyntaxKind.JsxAttribute) { + // Currently we parse JsxOpeningLikeElement as: + // JsxOpeningLikeElement + // attributes: JsxAttributes + // properties: NodeArray + // each JsxAttribute can have initializer as JsxExpression + return parent.parent.parent.parent as JsxOpeningLikeElement; + } + + if (parent && parent.kind === SyntaxKind.JsxSpreadAttribute) { + // Currently we parse JsxOpeningLikeElement as: + // JsxOpeningLikeElement + // attributes: JsxAttributes + // properties: NodeArray + return parent.parent.parent as JsxOpeningLikeElement; + } + + break; + } } + return undefined; } - // A cache of completion entries for keywords, these do not change between sessions - const _keywordCompletions: CompletionEntry[][] = []; - const allKeywordsCompletions: () => readonly CompletionEntry[] = memoize(() => { - const res: CompletionEntry[] = []; - for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { - res.push({ - name: tokenToString(i)!, - kind: ScriptElementKind.keyword, - kindModifiers: ScriptElementKindModifier.none, - sortText: SortText.GlobalsOrKeywords - }); - } - return res; - }); + /** + * @returns true if we are certain that the currently edited location must define a new location; false otherwise. + */ + function isSolelyIdentifierDefinitionLocation(contextToken: Node): boolean { + const parent = contextToken.parent; + const containingNodeKind = parent.kind; + switch (contextToken.kind) { + case SyntaxKind.CommaToken: + return containingNodeKind === SyntaxKind.VariableDeclaration || + isVariableDeclarationListButNotTypeArgument(contextToken) || + containingNodeKind === SyntaxKind.VariableStatement || + containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, | + isFunctionLikeButNotConstructor(containingNodeKind) || + containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A= contextToken.pos); + + case SyntaxKind.DotToken: + return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [.| + + case SyntaxKind.ColonToken: + return containingNodeKind === SyntaxKind.BindingElement; // var {x :html| + + case SyntaxKind.OpenBracketToken: + return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x| + + case SyntaxKind.OpenParenToken: + return containingNodeKind === SyntaxKind.CatchClause || + isFunctionLikeButNotConstructor(containingNodeKind); + + case SyntaxKind.OpenBraceToken: + return containingNodeKind === SyntaxKind.EnumDeclaration; // enum a { | - function getKeywordCompletions(keywordFilter: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean): readonly CompletionEntry[] { - if (!filterOutTsOnlyKeywords) return getTypescriptKeywordCompletions(keywordFilter); + case SyntaxKind.LessThanToken: + return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< | + containingNodeKind === SyntaxKind.ClassExpression || // var C = class D< | + containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A< | + containingNodeKind === SyntaxKind.TypeAliasDeclaration || // type List< | + isFunctionLikeKind(containingNodeKind); - const index = keywordFilter + KeywordCompletionFilters.Last + 1; - return _keywordCompletions[index] || - (_keywordCompletions[index] = getTypescriptKeywordCompletions(keywordFilter) - .filter(entry => !isTypeScriptOnlyKeyword(stringToToken(entry.name)!)) - ); - } + case SyntaxKind.StaticKeyword: + return containingNodeKind === SyntaxKind.PropertyDeclaration && !isClassLike(parent.parent); + + case SyntaxKind.DotDotDotToken: + return containingNodeKind === SyntaxKind.Parameter || + (!!parent.parent && parent.parent.kind === SyntaxKind.ArrayBindingPattern); // var [...z| + + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + return containingNodeKind === SyntaxKind.Parameter && !isConstructorDeclaration(parent.parent); + + case SyntaxKind.AsKeyword: + return containingNodeKind === SyntaxKind.ImportSpecifier || + containingNodeKind === SyntaxKind.ExportSpecifier || + containingNodeKind === SyntaxKind.NamespaceImport; - function getTypescriptKeywordCompletions(keywordFilter: KeywordCompletionFilters): readonly CompletionEntry[] { - return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => { - const kind = stringToToken(entry.name)!; - switch (keywordFilter) { - case KeywordCompletionFilters.None: + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + return !isFromObjectTypeDeclaration(contextToken); + + case SyntaxKind.Identifier: + if (containingNodeKind === SyntaxKind.ImportSpecifier && + contextToken === (parent as ImportSpecifier).name && + (contextToken as Identifier).text === "type" + ) { + // import { type | } return false; - case KeywordCompletionFilters.All: - return isFunctionLikeBodyKeyword(kind) - || kind === SyntaxKind.DeclareKeyword - || kind === SyntaxKind.ModuleKeyword - || kind === SyntaxKind.TypeKeyword - || kind === SyntaxKind.NamespaceKeyword - || kind === SyntaxKind.AbstractKeyword - || isTypeKeyword(kind) && kind !== SyntaxKind.UndefinedKeyword; - case KeywordCompletionFilters.FunctionLikeBodyKeywords: - return isFunctionLikeBodyKeyword(kind); - case KeywordCompletionFilters.ClassElementKeywords: - return isClassMemberCompletionKeyword(kind); - case KeywordCompletionFilters.InterfaceElementKeywords: - return isInterfaceOrTypeLiteralCompletionKeyword(kind); - case KeywordCompletionFilters.ConstructorParameterKeywords: - return isParameterPropertyModifier(kind); - case KeywordCompletionFilters.TypeAssertionKeywords: - return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword; - case KeywordCompletionFilters.TypeKeywords: - return isTypeKeyword(kind); - case KeywordCompletionFilters.TypeKeyword: - return kind === SyntaxKind.TypeKeyword; - default: - return Debug.assertNever(keywordFilter); + } + break; + + case SyntaxKind.ClassKeyword: + case SyntaxKind.EnumKeyword: + case SyntaxKind.InterfaceKeyword: + case SyntaxKind.FunctionKeyword: + case SyntaxKind.VarKeyword: + case SyntaxKind.ImportKeyword: + case SyntaxKind.LetKeyword: + case SyntaxKind.ConstKeyword: + case SyntaxKind.InferKeyword: + return true; + + case SyntaxKind.TypeKeyword: + // import { type foo| } + return containingNodeKind !== SyntaxKind.ImportSpecifier; + + case SyntaxKind.AsteriskToken: + return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent); + } + + // If the previous token is keyword corresponding to class member completion keyword + // there will be completion available here + if (isClassMemberCompletionKeyword(keywordForNode(contextToken)) && isFromObjectTypeDeclaration(contextToken)) { + return false; + } + + if (isConstructorParameterCompletion(contextToken)) { + // constructor parameter completion is available only if + // - its modifier of the constructor parameter or + // - its name of the parameter and not being edited + // eg. constructor(a |<- this shouldnt show completion + if (!isIdentifier(contextToken) || + isParameterPropertyModifier(keywordForNode(contextToken)) || + isCurrentlyEditingNode(contextToken)) { + return false; } - })); - } + } - function isTypeScriptOnlyKeyword(kind: SyntaxKind) { - switch (kind) { + // Previous token may have been a keyword that was converted to an identifier. + switch (keywordForNode(contextToken)) { case SyntaxKind.AbstractKeyword: - case SyntaxKind.AnyKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.BooleanKeyword: + case SyntaxKind.ClassKeyword: + case SyntaxKind.ConstKeyword: case SyntaxKind.DeclareKeyword: case SyntaxKind.EnumKeyword: - case SyntaxKind.GlobalKeyword: - case SyntaxKind.ImplementsKeyword: - case SyntaxKind.InferKeyword: + case SyntaxKind.FunctionKeyword: case SyntaxKind.InterfaceKeyword: - case SyntaxKind.IsKeyword: - case SyntaxKind.KeyOfKeyword: - case SyntaxKind.ModuleKeyword: - case SyntaxKind.NamespaceKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.OverrideKeyword: + case SyntaxKind.LetKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.PublicKeyword: - case SyntaxKind.ReadonlyKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.TypeKeyword: - case SyntaxKind.UniqueKeyword: - case SyntaxKind.UnknownKeyword: + case SyntaxKind.StaticKeyword: + case SyntaxKind.VarKeyword: return true; - default: - return false; + case SyntaxKind.AsyncKeyword: + return isPropertyDeclaration(contextToken.parent); + } + + // If we are inside a class declaration, and `constructor` is totally not present, + // but we request a completion manually at a whitespace... + const ancestorClassLike = findAncestor(contextToken.parent, isClassLike); + if (ancestorClassLike && contextToken === previousToken && isPreviousPropertyDeclarationTerminated(contextToken, position)) { + return false; // Don't block completions. + } + + const ancestorPropertyDeclaraion = getAncestor(contextToken.parent, SyntaxKind.PropertyDeclaration); + // If we are inside a class declaration and typing `constructor` after property declaration... + if (ancestorPropertyDeclaraion + && contextToken !== previousToken + && isClassLike(previousToken.parent.parent) + // And the cursor is at the token... + && position <= previousToken.end) { + // If we are sure that the previous property declaration is terminated according to newline or semicolon... + if (isPreviousPropertyDeclarationTerminated(contextToken, previousToken.end)) { + return false; // Don't block completions. + } + else if (contextToken.kind !== SyntaxKind.EqualsToken + // Should not block: `class C { blah = c/**/ }` + // But should block: `class C { blah = somewhat c/**/ }` and `class C { blah: SomeType c/**/ }` + && (isInitializedProperty(ancestorPropertyDeclaraion as PropertyDeclaration) + || hasType(ancestorPropertyDeclaraion))) { + return true; + } + } + + return isDeclarationName(contextToken) + && !isShorthandPropertyAssignment(contextToken.parent) + && !isJsxAttribute(contextToken.parent) + // Don't block completions if we're in `class C /**/`, because we're *past* the end of the identifier and might want to complete `extends`. + // If `contextToken !== previousToken`, this is `class C ex/**/`. + && !(isClassLike(contextToken.parent) && (contextToken !== previousToken || position > previousToken.end)); + } + + function isPreviousPropertyDeclarationTerminated(contextToken: Node, position: number) { + return contextToken.kind !== SyntaxKind.EqualsToken && + (contextToken.kind === SyntaxKind.SemicolonToken + || !positionsAreOnSameLine(contextToken.end, position, sourceFile)); + } + + function isFunctionLikeButNotConstructor(kind: SyntaxKind) { + return isFunctionLikeKind(kind) && kind !== SyntaxKind.Constructor; + } + + function isDotOfNumericLiteral(contextToken: Node): boolean { + if (contextToken.kind === SyntaxKind.NumericLiteral) { + const text = contextToken.getFullText(); + return text.charAt(text.length - 1) === "."; + } + + return false; + } + + function isVariableDeclarationListButNotTypeArgument(node: Node): boolean { + return node.parent.kind === SyntaxKind.VariableDeclarationList + && !isPossiblyTypeArgumentPosition(node, sourceFile, typeChecker); + } + + /** + * Filters out completion suggestions for named imports or exports. + * + * @returns Symbols to be suggested in an object binding pattern or object literal expression, barring those whose declarations + * do not occur at the current position and have not otherwise been typed. + */ + function filterObjectMembersList(contextualMemberSymbols: Symbol[], existingMembers: readonly Declaration[]): Symbol[] { + if (existingMembers.length === 0) { + return contextualMemberSymbols; + } + + const membersDeclaredBySpreadAssignment = new Set(); + const existingMemberNames = new Set<__String>(); + for (const m of existingMembers) { + // Ignore omitted expressions for missing members + if (m.kind !== SyntaxKind.PropertyAssignment && + m.kind !== SyntaxKind.ShorthandPropertyAssignment && + m.kind !== SyntaxKind.BindingElement && + m.kind !== SyntaxKind.MethodDeclaration && + m.kind !== SyntaxKind.GetAccessor && + m.kind !== SyntaxKind.SetAccessor && + m.kind !== SyntaxKind.SpreadAssignment) { + continue; + } + + // If this is the current item we are editing right now, do not filter it out + if (isCurrentlyEditingNode(m)) { + continue; + } + + let existingName: __String | undefined; + + if (isSpreadAssignment(m)) { + setMembersDeclaredBySpreadAssignment(m, membersDeclaredBySpreadAssignment); + } + else if (isBindingElement(m) && m.propertyName) { + // include only identifiers in completion list + if (m.propertyName.kind === SyntaxKind.Identifier) { + existingName = m.propertyName.escapedText; + } + } + else { + // TODO: Account for computed property name + // NOTE: if one only performs this step when m.name is an identifier, + // things like '__proto__' are not filtered out. + const name = getNameOfDeclaration(m); + existingName = name && isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; + } + + if (existingName !== undefined) { + existingMemberNames.add(existingName); + } } - } - function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean { - return kind === SyntaxKind.ReadonlyKeyword; + const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.has(m.escapedName)); + setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); + + return filteredSymbols; } - function isClassMemberCompletionKeyword(kind: SyntaxKind) { - switch (kind) { - case SyntaxKind.AbstractKeyword: - case SyntaxKind.AccessorKeyword: - case SyntaxKind.ConstructorKeyword: - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - case SyntaxKind.AsyncKeyword: - case SyntaxKind.DeclareKeyword: - case SyntaxKind.OverrideKeyword: - return true; - default: - return isClassMemberModifier(kind); + function setMembersDeclaredBySpreadAssignment(declaration: SpreadAssignment | JsxSpreadAttribute, membersDeclaredBySpreadAssignment: Set) { + const expression = declaration.expression; + const symbol = typeChecker.getSymbolAtLocation(expression); + const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); + const properties = type && (type as ObjectType).properties; + if (properties) { + properties.forEach(property => { + membersDeclaredBySpreadAssignment.add(property.name); + }); } } - function isFunctionLikeBodyKeyword(kind: SyntaxKind) { - return kind === SyntaxKind.AsyncKeyword - || kind === SyntaxKind.AwaitKeyword - || kind === SyntaxKind.AsKeyword - || kind === SyntaxKind.SatisfiesKeyword - || kind === SyntaxKind.TypeKeyword - || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind); - } - - function keywordForNode(node: Node): SyntaxKind { - return isIdentifier(node) ? node.originalKeywordKind || SyntaxKind.Unknown : node.kind; - } - - function getContextualKeywords( - contextToken: Node | undefined, - position: number, - ): readonly CompletionEntry[] { - const entries = []; - /** - * An `AssertClause` can come after an import declaration: - * import * from "foo" | - * import "foo" | - * or after a re-export declaration that has a module specifier: - * export { foo } from "foo" | - * Source: https://tc39.es/proposal-import-assertions/ - */ - if (contextToken) { - const file = contextToken.getSourceFile(); - const parent = contextToken.parent; - const tokenLine = file.getLineAndCharacterOfPosition(contextToken.end).line; - const currentLine = file.getLineAndCharacterOfPosition(position).line; - if ((isImportDeclaration(parent) || isExportDeclaration(parent) && parent.moduleSpecifier) - && contextToken === parent.moduleSpecifier - && tokenLine === currentLine) { - entries.push({ - name: tokenToString(SyntaxKind.AssertKeyword)!, - kind: ScriptElementKind.keyword, - kindModifiers: ScriptElementKindModifier.none, - sortText: SortText.GlobalsOrKeywords, - }); + // Set SortText to OptionalMember if it is an optional member + function setSortTextToOptionalMember() { + symbols.forEach(m => { + if (m.flags & SymbolFlags.Optional) { + const symbolId = getSymbolId(m); + symbolToSortTextMap[symbolId] = symbolToSortTextMap[symbolId] ?? SortText.OptionalMember; + } + }); + } + + // Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment + function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Set, contextualMemberSymbols: Symbol[]): void { + if (membersDeclaredBySpreadAssignment.size === 0) { + return; + } + for (const contextualMemberSymbol of contextualMemberSymbols) { + if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) { + symbolToSortTextMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment; } } - return entries; } - /** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ - function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefined { - return findAncestor(node, n => - isJSDocTag(n) && rangeContainsPosition(n, position) ? true : - isJSDoc(n) ? "quit" : false) as JSDocTag | undefined; + function transformObjectLiteralMembersSortText(start: number): void { + for (let i = start; i < symbols.length; i++) { + const symbol = symbols[i]; + const symbolId = getSymbolId(symbol); + const origin = symbolToOriginInfoMap?.[i]; + const target = getEmitScriptTarget(compilerOptions); + const displayName = getCompletionEntryDisplayNameForSymbol( + symbol, + target, + origin, + CompletionKind.ObjectPropertyDeclaration, + /*jsxIdentifierExpected*/ false); + if (displayName) { + const originalSortText = symbolToSortTextMap[symbolId] ?? SortText.LocationPriority; + const { name } = displayName; + symbolToSortTextMap[symbolId] = SortText.ObjectLiteralProperty(originalSortText, name); + } + } } - export function getPropertiesForObjectExpression(contextualType: Type, completionsType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { - const hasCompletionsType = completionsType && completionsType !== contextualType; - const type = hasCompletionsType && !(completionsType.flags & TypeFlags.AnyOrUnknown) - ? checker.getUnionType([contextualType, completionsType]) - : contextualType; + /** + * Filters out completion suggestions for class elements. + * + * @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags + */ + function filterClassMembersList(baseSymbols: readonly Symbol[], existingMembers: readonly ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] { + const existingMemberNames = new Set<__String>(); + for (const m of existingMembers) { + // Ignore omitted expressions for missing members + if (m.kind !== SyntaxKind.PropertyDeclaration && + m.kind !== SyntaxKind.MethodDeclaration && + m.kind !== SyntaxKind.GetAccessor && + m.kind !== SyntaxKind.SetAccessor) { + continue; + } - const properties = getApparentProperties(type, obj, checker); - return type.isClass() && containsNonPublicProperties(properties) ? [] : - hasCompletionsType ? filter(properties, hasDeclarationOtherThanSelf) : properties; + // If this is the current item we are editing right now, do not filter it out + if (isCurrentlyEditingNode(m)) { + continue; + } - // Filter out members whose only declaration is the object literal itself to avoid - // self-fulfilling completions like: - // - // function f(x: T) {} - // f({ abc/**/: "" }) // `abc` is a member of `T` but only because it declares itself - function hasDeclarationOtherThanSelf(member: Symbol) { - if (!length(member.declarations)) return true; - return some(member.declarations, decl => decl.parent !== obj); - } - } + // Dont filter member even if the name matches if it is declared private in the list + if (hasEffectiveModifier(m, ModifierFlags.Private)) { + continue; + } - function getApparentProperties(type: Type, node: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker) { - if (!type.isUnion()) return type.getApparentProperties(); - return checker.getAllPossiblePropertiesOfTypes(filter(type.types, memberType => - !(memberType.flags & TypeFlags.Primitive - || checker.isArrayLikeType(memberType) - || checker.isTypeInvalidDueToUnionDiscriminant(memberType, node) - || typeHasCallOrConstructSignatures(memberType, checker) - || memberType.isClass() && containsNonPublicProperties(memberType.getApparentProperties())))); - } + // do not filter it out if the static presence doesnt match + if (isStatic(m) !== !!(currentClassElementModifierFlags & ModifierFlags.Static)) { + continue; + } - function containsNonPublicProperties(props: Symbol[]) { - return some(props, p => !!(getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.NonPublicAccessibilityModifier)); - } + const existingName = getPropertyNameForPropertyNameNode(m.name!); + if (existingName) { + existingMemberNames.add(existingName); + } + } - /** - * Gets all properties on a type, but if that type is a union of several types, - * excludes array-like types or callable/constructable types. - */ - function getPropertiesForCompletion(type: Type, checker: TypeChecker): Symbol[] { - return type.isUnion() - ? Debug.checkEachDefined(checker.getAllPossiblePropertiesOfTypes(type.types), "getAllPossiblePropertiesOfTypes() should all be defined") - : Debug.checkEachDefined(type.getApparentProperties(), "getApparentProperties() should all be defined"); + return baseSymbols.filter(propertySymbol => + !existingMemberNames.has(propertySymbol.escapedName) && + !!propertySymbol.declarations && + !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private) && + !(propertySymbol.valueDeclaration && isPrivateIdentifierClassElementDeclaration(propertySymbol.valueDeclaration))); } /** - * Returns the immediate owning class declaration of a context token, - * on the condition that one exists and that the context implies completion should be given. + * Filters out completion suggestions from 'symbols' according to existing JSX attributes. + * + * @returns Symbols to be suggested in a JSX element, barring those whose attributes + * do not occur at the current position and have not otherwise been typed. */ - function tryGetObjectTypeDeclarationCompletionContainer(sourceFile: SourceFile, contextToken: Node | undefined, location: Node, position: number): ObjectTypeDeclaration | undefined { - // class c { method() { } | method2() { } } - switch (location.kind) { - case SyntaxKind.SyntaxList: - return tryCast(location.parent, isObjectTypeDeclaration); - case SyntaxKind.EndOfFileToken: - const cls = tryCast(lastOrUndefined(cast(location.parent, isSourceFile).statements), isObjectTypeDeclaration); - if (cls && !findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)) { - return cls; - } - break; - case SyntaxKind.Identifier: { - const originalKeywordKind = (location as Identifier).originalKeywordKind; - if (originalKeywordKind && isKeyword(originalKeywordKind)) { - return undefined; - } - // class c { public prop = c| } - if (isPropertyDeclaration(location.parent) && location.parent.initializer === location) { - return undefined; - } - // class c extends React.Component { a: () => 1\n compon| } - if (isFromObjectTypeDeclaration(location)) { - return findAncestor(location, isObjectTypeDeclaration); - } + function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { + const seenNames = new Set<__String>(); + const membersDeclaredBySpreadAssignment = new Set(); + for (const attr of attributes) { + // If this is the current item we are editing right now, do not filter it out + if (isCurrentlyEditingNode(attr)) { + continue; } - } - if (!contextToken) return undefined; - - // class C { blah; constructor/**/ } and so on - if (location.kind === SyntaxKind.ConstructorKeyword - // class C { blah \n constructor/**/ } - || (isIdentifier(contextToken) && isPropertyDeclaration(contextToken.parent) && isClassLike(location))) { - return findAncestor(contextToken, isClassLike) as ObjectTypeDeclaration; + if (attr.kind === SyntaxKind.JsxAttribute) { + seenNames.add(attr.name.escapedText); + } + else if (isJsxSpreadAttribute(attr)) { + setMembersDeclaredBySpreadAssignment(attr, membersDeclaredBySpreadAssignment); + } } + const filteredSymbols = symbols.filter(a => !seenNames.has(a.escapedName)); - switch (contextToken.kind) { - case SyntaxKind.EqualsToken: // class c { public prop = | /* global completions */ } - return undefined; + setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); - case SyntaxKind.SemicolonToken: // class c {getValue(): number; | } - case SyntaxKind.CloseBraceToken: // class c { method() { } | } - // class c { method() { } b| } - return isFromObjectTypeDeclaration(location) && (location.parent as ClassElement | TypeElement).name === location - ? location.parent.parent as ObjectTypeDeclaration - : tryCast(location, isObjectTypeDeclaration); - case SyntaxKind.OpenBraceToken: // class c { | - case SyntaxKind.CommaToken: // class c {getValue(): number, | } - return tryCast(contextToken.parent, isObjectTypeDeclaration); - default: - if (!isFromObjectTypeDeclaration(contextToken)) { - // class c extends React.Component { a: () => 1\n| } - if (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line && isObjectTypeDeclaration(location)) { - return location; - } - return undefined; - } - const isValidKeyword = isClassLike(contextToken.parent.parent) ? isClassMemberCompletionKeyword : isInterfaceOrTypeLiteralCompletionKeyword; - return (isValidKeyword(contextToken.kind) || contextToken.kind === SyntaxKind.AsteriskToken || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text)!)) // TODO: GH#18217 - ? contextToken.parent.parent as ObjectTypeDeclaration : undefined; - } + return filteredSymbols; } - function tryGetTypeLiteralNode(node: Node): TypeLiteralNode | undefined { - if (!node) return undefined; - - const parent = node.parent; + function isCurrentlyEditingNode(node: Node): boolean { + return node.getStart(sourceFile) <= position && position <= node.getEnd(); + } +} - switch (node.kind) { - case SyntaxKind.OpenBraceToken: - if (isTypeLiteralNode(parent)) { +/** + * Returns the immediate owning object literal or binding pattern of a context token, + * on the condition that one exists and that the context implies completion should be given. + */ +function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): ObjectLiteralExpression | ObjectBindingPattern | undefined { + if (contextToken) { + const { parent } = contextToken; + switch (contextToken.kind) { + case SyntaxKind.OpenBraceToken: // const x = { | + case SyntaxKind.CommaToken: // const x = { a: 0, | + if (isObjectLiteralExpression(parent) || isObjectBindingPattern(parent)) { return parent; } break; - case SyntaxKind.SemicolonToken: - case SyntaxKind.CommaToken: + case SyntaxKind.AsteriskToken: + return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined; case SyntaxKind.Identifier: - if (parent.kind === SyntaxKind.PropertySignature && isTypeLiteralNode(parent.parent)) { - return parent.parent; - } - break; + return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) + ? contextToken.parent.parent : undefined; } - - return undefined; } - function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): Type | undefined { - if (!node) return undefined; + return undefined; +} - if (isTypeNode(node) && isTypeReferenceType(node.parent)) { - return checker.getTypeArgumentConstraint(node); - } +function getRelevantTokens(position: number, sourceFile: SourceFile): { contextToken: Node, previousToken: Node } | { contextToken: undefined, previousToken: undefined } { + const previousToken = findPrecedingToken(position, sourceFile); + if (previousToken && position <= previousToken.end && (isMemberName(previousToken) || isKeyword(previousToken.kind))) { + const contextToken = findPrecedingToken(previousToken.getFullStart(), sourceFile, /*startNode*/ undefined)!; // TODO: GH#18217 + return { contextToken, previousToken }; + } + return { contextToken: previousToken as Node, previousToken: previousToken as Node }; +} - const t = getConstraintOfTypeArgumentProperty(node.parent, checker); - if (!t) return undefined; +function getAutoImportSymbolFromCompletionEntryData(name: string, data: CompletionEntryData, program: Program, host: LanguageServiceHost): { symbol: Symbol, origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport } | undefined { + const containingProgram = data.isPackageJsonImport ? host.getPackageJsonAutoImportProvider!()! : program; + const checker = containingProgram.getTypeChecker(); + const moduleSymbol = + data.ambientModuleName ? checker.tryFindAmbientModule(data.ambientModuleName) : + data.fileName ? checker.getMergedSymbol(Debug.checkDefined(containingProgram.getSourceFile(data.fileName)).symbol) : + undefined; + + if (!moduleSymbol) return undefined; + let symbol = data.exportName === InternalSymbolName.ExportEquals + ? checker.resolveExternalModuleSymbol(moduleSymbol) + : checker.tryGetMemberInModuleExportsAndProperties(data.exportName, moduleSymbol); + if (!symbol) return undefined; + const isDefaultExport = data.exportName === InternalSymbolName.Default; + symbol = isDefaultExport && getLocalSymbolForExportDefault(symbol) || symbol; + return { symbol, origin: completionEntryDataToSymbolOriginInfo(data, name, moduleSymbol) }; +} - switch (node.kind) { - case SyntaxKind.PropertySignature: - return checker.getTypeOfPropertyOfContextualType(t, node.symbol.escapedName); - case SyntaxKind.IntersectionType: - case SyntaxKind.TypeLiteral: - case SyntaxKind.UnionType: - return t; - } +interface CompletionEntryDisplayNameForSymbol { + readonly name: string; + readonly needsConvertPropertyAccess: boolean; +} +function getCompletionEntryDisplayNameForSymbol( + symbol: Symbol, + target: ScriptTarget, + origin: SymbolOriginInfo | undefined, + kind: CompletionKind, + jsxIdentifierExpected: boolean, +): CompletionEntryDisplayNameForSymbol | undefined { + const name = originIncludesSymbolName(origin) ? origin.symbolName : symbol.name; + if (name === undefined + // If the symbol is external module, don't show it in the completion list + // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there) + || symbol.flags & SymbolFlags.Module && isSingleOrDoubleQuote(name.charCodeAt(0)) + // If the symbol is the internal name of an ES symbol, it is not a valid entry. Internal names for ES symbols start with "__@" + || isKnownSymbol(symbol)) { + return undefined; } - // TODO: GH#19856 Would like to return `node is Node & { parent: (ClassElement | TypeElement) & { parent: ObjectTypeDeclaration } }` but then compilation takes > 10 minutes - function isFromObjectTypeDeclaration(node: Node): boolean { - return node.parent && isClassOrTypeElement(node.parent) && isObjectTypeDeclaration(node.parent.parent); + const validNameResult: CompletionEntryDisplayNameForSymbol = { name, needsConvertPropertyAccess: false }; + if (isIdentifierText(name, target, jsxIdentifierExpected ? LanguageVariant.JSX : LanguageVariant.Standard) || symbol.valueDeclaration && isPrivateIdentifierClassElementDeclaration(symbol.valueDeclaration)) { + return validNameResult; } - - function isValidTrigger(sourceFile: SourceFile, triggerCharacter: CompletionsTriggerCharacter, contextToken: Node | undefined, position: number): boolean { - switch (triggerCharacter) { - case ".": - case "@": - return true; - case '"': - case "'": - case "`": - // Only automatically bring up completions if this is an opening quote. - return !!contextToken && isStringLiteralOrTemplate(contextToken) && position === contextToken.getStart(sourceFile) + 1; - case "#": - return !!contextToken && isPrivateIdentifier(contextToken) && !!getContainingClass(contextToken); - case "<": - // Opening JSX tag - return !!contextToken && contextToken.kind === SyntaxKind.LessThanToken && (!isBinaryExpression(contextToken.parent) || binaryExpressionMayBeOpenTag(contextToken.parent)); - case "/": - return !!contextToken && (isStringLiteralLike(contextToken) - ? !!tryGetImportFromModuleSpecifier(contextToken) - : contextToken.kind === SyntaxKind.SlashToken && isJsxClosingElement(contextToken.parent)); - case " ": - return !!contextToken && isImportKeyword(contextToken) && contextToken.parent.kind === SyntaxKind.SourceFile; - default: - return Debug.assertNever(triggerCharacter); - } + switch (kind) { + case CompletionKind.MemberLike: + return undefined; + case CompletionKind.ObjectPropertyDeclaration: + // TODO: GH#18169 + return { name: JSON.stringify(name), needsConvertPropertyAccess: false }; + case CompletionKind.PropertyAccess: + case CompletionKind.Global: // For a 'this.' completion it will be in a global context, but may have a non-identifier name. + // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547 + return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true }; + case CompletionKind.None: + case CompletionKind.String: + return validNameResult; + default: + Debug.assertNever(kind); } +} - function binaryExpressionMayBeOpenTag({ left }: BinaryExpression): boolean { - return nodeIsMissing(left); +// A cache of completion entries for keywords, these do not change between sessions +const _keywordCompletions: CompletionEntry[][] = []; +const allKeywordsCompletions: () => readonly CompletionEntry[] = memoize(() => { + const res: CompletionEntry[] = []; + for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { + res.push({ + name: tokenToString(i)!, + kind: ScriptElementKind.keyword, + kindModifiers: ScriptElementKindModifier.none, + sortText: SortText.GlobalsOrKeywords + }); } + return res; +}); - /** Determines if a type is exactly the same type resolved by the global 'self', 'global', or 'globalThis'. */ - function isProbablyGlobalType(type: Type, sourceFile: SourceFile, checker: TypeChecker) { - // The type of `self` and `window` is the same in lib.dom.d.ts, but `window` does not exist in - // lib.webworker.d.ts, so checking against `self` is also a check against `window` when it exists. - const selfSymbol = checker.resolveName("self", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); - if (selfSymbol && checker.getTypeOfSymbolAtLocation(selfSymbol, sourceFile) === type) { - return true; - } - const globalSymbol = checker.resolveName("global", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); - if (globalSymbol && checker.getTypeOfSymbolAtLocation(globalSymbol, sourceFile) === type) { - return true; +function getKeywordCompletions(keywordFilter: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean): readonly CompletionEntry[] { + if (!filterOutTsOnlyKeywords) return getTypescriptKeywordCompletions(keywordFilter); + + const index = keywordFilter + KeywordCompletionFilters.Last + 1; + return _keywordCompletions[index] || + (_keywordCompletions[index] = getTypescriptKeywordCompletions(keywordFilter) + .filter(entry => !isTypeScriptOnlyKeyword(stringToToken(entry.name)!)) + ); +} + +function getTypescriptKeywordCompletions(keywordFilter: KeywordCompletionFilters): readonly CompletionEntry[] { + return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => { + const kind = stringToToken(entry.name)!; + switch (keywordFilter) { + case KeywordCompletionFilters.None: + return false; + case KeywordCompletionFilters.All: + return isFunctionLikeBodyKeyword(kind) + || kind === SyntaxKind.DeclareKeyword + || kind === SyntaxKind.ModuleKeyword + || kind === SyntaxKind.TypeKeyword + || kind === SyntaxKind.NamespaceKeyword + || kind === SyntaxKind.AbstractKeyword + || isTypeKeyword(kind) && kind !== SyntaxKind.UndefinedKeyword; + case KeywordCompletionFilters.FunctionLikeBodyKeywords: + return isFunctionLikeBodyKeyword(kind); + case KeywordCompletionFilters.ClassElementKeywords: + return isClassMemberCompletionKeyword(kind); + case KeywordCompletionFilters.InterfaceElementKeywords: + return isInterfaceOrTypeLiteralCompletionKeyword(kind); + case KeywordCompletionFilters.ConstructorParameterKeywords: + return isParameterPropertyModifier(kind); + case KeywordCompletionFilters.TypeAssertionKeywords: + return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword; + case KeywordCompletionFilters.TypeKeywords: + return isTypeKeyword(kind); + case KeywordCompletionFilters.TypeKeyword: + return kind === SyntaxKind.TypeKeyword; + default: + return Debug.assertNever(keywordFilter); } - const globalThisSymbol = checker.resolveName("globalThis", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); - if (globalThisSymbol && checker.getTypeOfSymbolAtLocation(globalThisSymbol, sourceFile) === type) { + })); +} + +function isTypeScriptOnlyKeyword(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.AbstractKeyword: + case SyntaxKind.AnyKeyword: + case SyntaxKind.BigIntKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.EnumKeyword: + case SyntaxKind.GlobalKeyword: + case SyntaxKind.ImplementsKeyword: + case SyntaxKind.InferKeyword: + case SyntaxKind.InterfaceKeyword: + case SyntaxKind.IsKeyword: + case SyntaxKind.KeyOfKeyword: + case SyntaxKind.ModuleKeyword: + case SyntaxKind.NamespaceKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.OverrideKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PublicKeyword: + case SyntaxKind.ReadonlyKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.TypeKeyword: + case SyntaxKind.UniqueKeyword: + case SyntaxKind.UnknownKeyword: return true; - } - return false; + default: + return false; } +} + +function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean { + return kind === SyntaxKind.ReadonlyKeyword; +} - function isStaticProperty(symbol: Symbol) { - return !!(symbol.valueDeclaration && getEffectiveModifierFlags(symbol.valueDeclaration) & ModifierFlags.Static && isClassLike(symbol.valueDeclaration.parent)); +function isClassMemberCompletionKeyword(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.AbstractKeyword: + case SyntaxKind.AccessorKeyword: + case SyntaxKind.ConstructorKeyword: + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + case SyntaxKind.AsyncKeyword: + case SyntaxKind.DeclareKeyword: + case SyntaxKind.OverrideKeyword: + return true; + default: + return isClassMemberModifier(kind); } +} - function tryGetObjectLiteralContextualType(node: ObjectLiteralExpression, typeChecker: TypeChecker) { - const type = typeChecker.getContextualType(node); - if (type) { - return type; - } - const parent = walkUpParenthesizedExpressions(node.parent); - if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken && node === parent.left) { - // Object literal is assignment pattern: ({ | } = x) - return typeChecker.getTypeAtLocation(parent); - } - if (isExpression(parent)) { - // f(() => (({ | }))); - return typeChecker.getContextualType(parent); +function isFunctionLikeBodyKeyword(kind: SyntaxKind) { + return kind === SyntaxKind.AsyncKeyword + || kind === SyntaxKind.AwaitKeyword + || kind === SyntaxKind.AsKeyword + || kind === SyntaxKind.SatisfiesKeyword + || kind === SyntaxKind.TypeKeyword + || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind); +} + +function keywordForNode(node: Node): SyntaxKind { + return isIdentifier(node) ? node.originalKeywordKind || SyntaxKind.Unknown : node.kind; +} + +function getContextualKeywords( + contextToken: Node | undefined, + position: number, +): readonly CompletionEntry[] { + const entries = []; + /** + * An `AssertClause` can come after an import declaration: + * import * from "foo" | + * import "foo" | + * or after a re-export declaration that has a module specifier: + * export { foo } from "foo" | + * Source: https://tc39.es/proposal-import-assertions/ + */ + if (contextToken) { + const file = contextToken.getSourceFile(); + const parent = contextToken.parent; + const tokenLine = file.getLineAndCharacterOfPosition(contextToken.end).line; + const currentLine = file.getLineAndCharacterOfPosition(position).line; + if ((isImportDeclaration(parent) || isExportDeclaration(parent) && parent.moduleSpecifier) + && contextToken === parent.moduleSpecifier + && tokenLine === currentLine) { + entries.push({ + name: tokenToString(SyntaxKind.AssertKeyword)!, + kind: ScriptElementKind.keyword, + kindModifiers: ScriptElementKindModifier.none, + sortText: SortText.GlobalsOrKeywords, + }); } - return undefined; } + return entries; +} - interface ImportStatementCompletionInfo { - isKeywordOnlyCompletion: boolean; - keywordCompletion: TokenSyntaxKind | undefined; - isNewIdentifierLocation: boolean; - isTopLevelTypeOnly: boolean; - couldBeTypeOnlyImportSpecifier: boolean; - replacementSpan: TextSpan | undefined; - } - - function getImportStatementCompletionInfo(contextToken: Node): ImportStatementCompletionInfo { - let keywordCompletion: TokenSyntaxKind | undefined; - let isKeywordOnlyCompletion = false; - const candidate = getCandidate(); - return { - isKeywordOnlyCompletion, - keywordCompletion, - isNewIdentifierLocation: !!(candidate || keywordCompletion === SyntaxKind.TypeKeyword), - isTopLevelTypeOnly: !!tryCast(candidate, isImportDeclaration)?.importClause?.isTypeOnly || !!tryCast(candidate, isImportEqualsDeclaration)?.isTypeOnly, - couldBeTypeOnlyImportSpecifier: !!candidate && couldBeTypeOnlyImportSpecifier(candidate, contextToken), - replacementSpan: getSingleLineReplacementSpanForImportCompletionNode(candidate), - }; +/** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ +function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefined { + return findAncestor(node, n => + isJSDocTag(n) && rangeContainsPosition(n, position) ? true : + isJSDoc(n) ? "quit" : false) as JSDocTag | undefined; +} - function getCandidate() { - const parent = contextToken.parent; - if (isImportEqualsDeclaration(parent)) { - keywordCompletion = contextToken.kind === SyntaxKind.TypeKeyword ? undefined : SyntaxKind.TypeKeyword; - return isModuleSpecifierMissingOrEmpty(parent.moduleReference) ? parent : undefined; - } - if (couldBeTypeOnlyImportSpecifier(parent, contextToken) && canCompleteFromNamedBindings(parent.parent)) { - return parent; - } - if (isNamedImports(parent) || isNamespaceImport(parent)) { - if (!parent.parent.isTypeOnly && ( - contextToken.kind === SyntaxKind.OpenBraceToken || - contextToken.kind === SyntaxKind.ImportKeyword || - contextToken.kind === SyntaxKind.CommaToken - )) { - keywordCompletion = SyntaxKind.TypeKeyword; - } +/** @internal */ +export function getPropertiesForObjectExpression(contextualType: Type, completionsType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { + const hasCompletionsType = completionsType && completionsType !== contextualType; + const type = hasCompletionsType && !(completionsType.flags & TypeFlags.AnyOrUnknown) + ? checker.getUnionType([contextualType, completionsType]) + : contextualType; + + const properties = getApparentProperties(type, obj, checker); + return type.isClass() && containsNonPublicProperties(properties) ? [] : + hasCompletionsType ? filter(properties, hasDeclarationOtherThanSelf) : properties; + + // Filter out members whose only declaration is the object literal itself to avoid + // self-fulfilling completions like: + // + // function f(x: T) {} + // f({ abc/**/: "" }) // `abc` is a member of `T` but only because it declares itself + function hasDeclarationOtherThanSelf(member: Symbol) { + if (!length(member.declarations)) return true; + return some(member.declarations, decl => decl.parent !== obj); + } +} - if (canCompleteFromNamedBindings(parent)) { - // At `import { ... } |` or `import * as Foo |`, the only possible completion is `from` - if (contextToken.kind === SyntaxKind.CloseBraceToken || contextToken.kind === SyntaxKind.Identifier) { - isKeywordOnlyCompletion = true; - keywordCompletion = SyntaxKind.FromKeyword; - } - else { - return parent.parent.parent; - } - } +function getApparentProperties(type: Type, node: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker) { + if (!type.isUnion()) return type.getApparentProperties(); + return checker.getAllPossiblePropertiesOfTypes(filter(type.types, memberType => + !(memberType.flags & TypeFlags.Primitive + || checker.isArrayLikeType(memberType) + || checker.isTypeInvalidDueToUnionDiscriminant(memberType, node) + || typeHasCallOrConstructSignatures(memberType, checker) + || memberType.isClass() && containsNonPublicProperties(memberType.getApparentProperties())))); +} + +function containsNonPublicProperties(props: Symbol[]) { + return some(props, p => !!(getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.NonPublicAccessibilityModifier)); +} + +/** + * Gets all properties on a type, but if that type is a union of several types, + * excludes array-like types or callable/constructable types. + */ +function getPropertiesForCompletion(type: Type, checker: TypeChecker): Symbol[] { + return type.isUnion() + ? Debug.checkEachDefined(checker.getAllPossiblePropertiesOfTypes(type.types), "getAllPossiblePropertiesOfTypes() should all be defined") + : Debug.checkEachDefined(type.getApparentProperties(), "getApparentProperties() should all be defined"); +} + +/** + * Returns the immediate owning class declaration of a context token, + * on the condition that one exists and that the context implies completion should be given. + */ +function tryGetObjectTypeDeclarationCompletionContainer(sourceFile: SourceFile, contextToken: Node | undefined, location: Node, position: number): ObjectTypeDeclaration | undefined { + // class c { method() { } | method2() { } } + switch (location.kind) { + case SyntaxKind.SyntaxList: + return tryCast(location.parent, isObjectTypeDeclaration); + case SyntaxKind.EndOfFileToken: + const cls = tryCast(lastOrUndefined(cast(location.parent, isSourceFile).statements), isObjectTypeDeclaration); + if (cls && !findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)) { + return cls; + } + break; + case SyntaxKind.Identifier: { + const originalKeywordKind = (location as Identifier).originalKeywordKind; + if (originalKeywordKind && isKeyword(originalKeywordKind)) { return undefined; } - if (isImportKeyword(contextToken) && isSourceFile(parent)) { - // A lone import keyword with nothing following it does not parse as a statement at all - keywordCompletion = SyntaxKind.TypeKeyword; - return contextToken as Token; + // class c { public prop = c| } + if (isPropertyDeclaration(location.parent) && location.parent.initializer === location) { + return undefined; } - if (isImportKeyword(contextToken) && isImportDeclaration(parent)) { - // `import s| from` - keywordCompletion = SyntaxKind.TypeKeyword; - return isModuleSpecifierMissingOrEmpty(parent.moduleSpecifier) ? parent : undefined; + // class c extends React.Component { a: () => 1\n compon| } + if (isFromObjectTypeDeclaration(location)) { + return findAncestor(location, isObjectTypeDeclaration); } - return undefined; } } - function getSingleLineReplacementSpanForImportCompletionNode(node: ImportDeclaration | ImportEqualsDeclaration | ImportSpecifier | Token | undefined) { - if (!node) return undefined; - const top = findAncestor(node, or(isImportDeclaration, isImportEqualsDeclaration)) ?? node; - const sourceFile = top.getSourceFile(); - if (rangeIsOnSingleLine(top, sourceFile)) { - return createTextSpanFromNode(top, sourceFile); - } - // ImportKeyword was necessarily on one line; ImportSpecifier was necessarily parented in an ImportDeclaration - Debug.assert(top.kind !== SyntaxKind.ImportKeyword && top.kind !== SyntaxKind.ImportSpecifier); - // Guess which point in the import might actually be a later statement parsed as part of the import - // during parser recovery - either in the middle of named imports, or the module specifier. - const potentialSplitPoint = top.kind === SyntaxKind.ImportDeclaration - ? getPotentiallyInvalidImportSpecifier(top.importClause?.namedBindings) ?? top.moduleSpecifier - : top.moduleReference; - const withoutModuleSpecifier: TextRange = { - pos: top.getFirstToken()!.getStart(), - end: potentialSplitPoint.pos, - }; - // The module specifier/reference was previously found to be missing, empty, or - // not a string literal - in this last case, it's likely that statement on a following - // line was parsed as the module specifier of a partially-typed import, e.g. - // import Foo| - // interface Blah {} - // This appears to be a multiline-import, and editors can't replace multiple lines. - // But if everything but the "module specifier" is on one line, by this point we can - // assume that the "module specifier" is actually just another statement, and return - // the single-line range of the import excluding that probable statement. - if (rangeIsOnSingleLine(withoutModuleSpecifier, sourceFile)) { - return createTextSpanFromRange(withoutModuleSpecifier); - } - } + if (!contextToken) return undefined; - // Tries to identify the first named import that is not really a named import, but rather - // just parser recovery for a situation like: - // import { Foo| - // interface Bar {} - // in which `Foo`, `interface`, and `Bar` are all parsed as import specifiers. The caller - // will also check if this token is on a separate line from the rest of the import. - function getPotentiallyInvalidImportSpecifier(namedBindings: NamedImportBindings | undefined) { - return find( - tryCast(namedBindings, isNamedImports)?.elements, - e => !e.propertyName && - isStringANonContextualKeyword(e.name.text) && - findPrecedingToken(e.name.pos, namedBindings!.getSourceFile(), namedBindings)?.kind !== SyntaxKind.CommaToken); + // class C { blah; constructor/**/ } and so on + if (location.kind === SyntaxKind.ConstructorKeyword + // class C { blah \n constructor/**/ } + || (isIdentifier(contextToken) && isPropertyDeclaration(contextToken.parent) && isClassLike(location))) { + return findAncestor(contextToken, isClassLike) as ObjectTypeDeclaration; } - function couldBeTypeOnlyImportSpecifier(importSpecifier: Node, contextToken: Node | undefined): importSpecifier is ImportSpecifier { - return isImportSpecifier(importSpecifier) - && (importSpecifier.isTypeOnly || contextToken === importSpecifier.name && isTypeKeywordTokenOrIdentifier(contextToken)); + switch (contextToken.kind) { + case SyntaxKind.EqualsToken: // class c { public prop = | /* global completions */ } + return undefined; + + case SyntaxKind.SemicolonToken: // class c {getValue(): number; | } + case SyntaxKind.CloseBraceToken: // class c { method() { } | } + // class c { method() { } b| } + return isFromObjectTypeDeclaration(location) && (location.parent as ClassElement | TypeElement).name === location + ? location.parent.parent as ObjectTypeDeclaration + : tryCast(location, isObjectTypeDeclaration); + case SyntaxKind.OpenBraceToken: // class c { | + case SyntaxKind.CommaToken: // class c {getValue(): number, | } + return tryCast(contextToken.parent, isObjectTypeDeclaration); + default: + if (!isFromObjectTypeDeclaration(contextToken)) { + // class c extends React.Component { a: () => 1\n| } + if (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line && isObjectTypeDeclaration(location)) { + return location; + } + return undefined; + } + const isValidKeyword = isClassLike(contextToken.parent.parent) ? isClassMemberCompletionKeyword : isInterfaceOrTypeLiteralCompletionKeyword; + return (isValidKeyword(contextToken.kind) || contextToken.kind === SyntaxKind.AsteriskToken || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text)!)) // TODO: GH#18217 + ? contextToken.parent.parent as ObjectTypeDeclaration : undefined; } +} - function canCompleteFromNamedBindings(namedBindings: NamedImportBindings) { - if (!isModuleSpecifierMissingOrEmpty(namedBindings.parent.parent.moduleSpecifier) || namedBindings.parent.name) { - return false; - } - if (isNamedImports(namedBindings)) { - // We can only complete on named imports if there are no other named imports already, - // but parser recovery sometimes puts later statements in the named imports list, so - // we try to only consider the probably-valid ones. - const invalidNamedImport = getPotentiallyInvalidImportSpecifier(namedBindings); - const validImports = invalidNamedImport ? namedBindings.elements.indexOf(invalidNamedImport) : namedBindings.elements.length; - return validImports < 2; - } - return true; +function tryGetTypeLiteralNode(node: Node): TypeLiteralNode | undefined { + if (!node) return undefined; + + const parent = node.parent; + + switch (node.kind) { + case SyntaxKind.OpenBraceToken: + if (isTypeLiteralNode(parent)) { + return parent; + } + break; + case SyntaxKind.SemicolonToken: + case SyntaxKind.CommaToken: + case SyntaxKind.Identifier: + if (parent.kind === SyntaxKind.PropertySignature && isTypeLiteralNode(parent.parent)) { + return parent.parent; + } + break; } - function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression) { - if (nodeIsMissing(specifier)) return true; - return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text; + return undefined; +} + +function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): Type | undefined { + if (!node) return undefined; + + if (isTypeNode(node) && isTypeReferenceType(node.parent)) { + return checker.getTypeArgumentConstraint(node); } - function getVariableDeclaration(property: Node): VariableDeclaration | undefined { - const variableDeclaration = findAncestor(property, node => - isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node) - ? "quit" - : isVariableDeclaration(node)); + const t = getConstraintOfTypeArgumentProperty(node.parent, checker); + if (!t) return undefined; - return variableDeclaration as VariableDeclaration | undefined; + switch (node.kind) { + case SyntaxKind.PropertySignature: + return checker.getTypeOfPropertyOfContextualType(t, node.symbol.escapedName); + case SyntaxKind.IntersectionType: + case SyntaxKind.TypeLiteral: + case SyntaxKind.UnionType: + return t; } +} + +// TODO: GH#19856 Would like to return `node is Node & { parent: (ClassElement | TypeElement) & { parent: ObjectTypeDeclaration } }` but then compilation takes > 10 minutes +function isFromObjectTypeDeclaration(node: Node): boolean { + return node.parent && isClassOrTypeElement(node.parent) && isObjectTypeDeclaration(node.parent.parent); +} - function isArrowFunctionBody(node: Node) { - return node.parent && isArrowFunction(node.parent) && node.parent.body === node; +function isValidTrigger(sourceFile: SourceFile, triggerCharacter: CompletionsTriggerCharacter, contextToken: Node | undefined, position: number): boolean { + switch (triggerCharacter) { + case ".": + case "@": + return true; + case '"': + case "'": + case "`": + // Only automatically bring up completions if this is an opening quote. + return !!contextToken && isStringLiteralOrTemplate(contextToken) && position === contextToken.getStart(sourceFile) + 1; + case "#": + return !!contextToken && isPrivateIdentifier(contextToken) && !!getContainingClass(contextToken); + case "<": + // Opening JSX tag + return !!contextToken && contextToken.kind === SyntaxKind.LessThanToken && (!isBinaryExpression(contextToken.parent) || binaryExpressionMayBeOpenTag(contextToken.parent)); + case "/": + return !!contextToken && (isStringLiteralLike(contextToken) + ? !!tryGetImportFromModuleSpecifier(contextToken) + : contextToken.kind === SyntaxKind.SlashToken && isJsxClosingElement(contextToken.parent)); + case " ": + return !!contextToken && isImportKeyword(contextToken) && contextToken.parent.kind === SyntaxKind.SourceFile; + default: + return Debug.assertNever(triggerCharacter); } +} - /** True if symbol is a type or a module containing at least one type. */ - function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, checker: TypeChecker, seenModules = new Map()): boolean { - // Since an alias can be merged with a local declaration, we need to test both the alias and its target. - // This code used to just test the result of `skipAlias`, but that would ignore any locally introduced meanings. - return nonAliasCanBeReferencedAtTypeLocation(symbol) || nonAliasCanBeReferencedAtTypeLocation(skipAlias(symbol.exportSymbol || symbol, checker)); +function binaryExpressionMayBeOpenTag({ left }: BinaryExpression): boolean { + return nodeIsMissing(left); +} - function nonAliasCanBeReferencedAtTypeLocation(symbol: Symbol): boolean { - return !!(symbol.flags & SymbolFlags.Type) || checker.isUnknownSymbol(symbol) || - !!(symbol.flags & SymbolFlags.Module) && addToSeen(seenModules, getSymbolId(symbol)) && - checker.getExportsOfModule(symbol).some(e => symbolCanBeReferencedAtTypeLocation(e, checker, seenModules)); - } +/** Determines if a type is exactly the same type resolved by the global 'self', 'global', or 'globalThis'. */ +function isProbablyGlobalType(type: Type, sourceFile: SourceFile, checker: TypeChecker) { + // The type of `self` and `window` is the same in lib.dom.d.ts, but `window` does not exist in + // lib.webworker.d.ts, so checking against `self` is also a check against `window` when it exists. + const selfSymbol = checker.resolveName("self", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); + if (selfSymbol && checker.getTypeOfSymbolAtLocation(selfSymbol, sourceFile) === type) { + return true; + } + const globalSymbol = checker.resolveName("global", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); + if (globalSymbol && checker.getTypeOfSymbolAtLocation(globalSymbol, sourceFile) === type) { + return true; + } + const globalThisSymbol = checker.resolveName("globalThis", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); + if (globalThisSymbol && checker.getTypeOfSymbolAtLocation(globalThisSymbol, sourceFile) === type) { + return true; } + return false; +} + +function isStaticProperty(symbol: Symbol) { + return !!(symbol.valueDeclaration && getEffectiveModifierFlags(symbol.valueDeclaration) & ModifierFlags.Static && isClassLike(symbol.valueDeclaration.parent)); +} - function isDeprecated(symbol: Symbol, checker: TypeChecker) { - const declarations = skipAlias(symbol, checker).declarations; - return !!length(declarations) && every(declarations, isDeprecatedDeclaration); +function tryGetObjectLiteralContextualType(node: ObjectLiteralExpression, typeChecker: TypeChecker) { + const type = typeChecker.getContextualType(node); + if (type) { + return type; } + const parent = walkUpParenthesizedExpressions(node.parent); + if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken && node === parent.left) { + // Object literal is assignment pattern: ({ | } = x) + return typeChecker.getTypeAtLocation(parent); + } + if (isExpression(parent)) { + // f(() => (({ | }))); + return typeChecker.getContextualType(parent); + } + return undefined; +} - /** - * True if the first character of `lowercaseCharacters` is the first character - * of some "word" in `identiferString` (where the string is split into "words" - * by camelCase and snake_case segments), then if the remaining characters of - * `lowercaseCharacters` appear, in order, in the rest of `identifierString`. - * - * True: - * 'state' in 'useState' - * 'sae' in 'useState' - * 'viable' in 'ENVIRONMENT_VARIABLE' - * - * False: - * 'staet' in 'useState' - * 'tate' in 'useState' - * 'ment' in 'ENVIRONMENT_VARIABLE' - */ - function charactersFuzzyMatchInString(identifierString: string, lowercaseCharacters: string): boolean { - if (lowercaseCharacters.length === 0) { - return true; - } +/** @internal */ +export interface ImportStatementCompletionInfo { + isKeywordOnlyCompletion: boolean; + keywordCompletion: TokenSyntaxKind | undefined; + isNewIdentifierLocation: boolean; + isTopLevelTypeOnly: boolean; + couldBeTypeOnlyImportSpecifier: boolean; + replacementSpan: TextSpan | undefined; +} + +function getImportStatementCompletionInfo(contextToken: Node): ImportStatementCompletionInfo { + let keywordCompletion: TokenSyntaxKind | undefined; + let isKeywordOnlyCompletion = false; + const candidate = getCandidate(); + return { + isKeywordOnlyCompletion, + keywordCompletion, + isNewIdentifierLocation: !!(candidate || keywordCompletion === SyntaxKind.TypeKeyword), + isTopLevelTypeOnly: !!tryCast(candidate, isImportDeclaration)?.importClause?.isTypeOnly || !!tryCast(candidate, isImportEqualsDeclaration)?.isTypeOnly, + couldBeTypeOnlyImportSpecifier: !!candidate && couldBeTypeOnlyImportSpecifier(candidate, contextToken), + replacementSpan: getSingleLineReplacementSpanForImportCompletionNode(candidate), + }; - let matchedFirstCharacter = false; - let prevChar: number | undefined; - let characterIndex = 0; - const len = identifierString.length; - for (let strIndex = 0; strIndex < len; strIndex++) { - const strChar = identifierString.charCodeAt(strIndex); - const testChar = lowercaseCharacters.charCodeAt(characterIndex); - if (strChar === testChar || strChar === toUpperCharCode(testChar)) { - matchedFirstCharacter ||= - prevChar === undefined || // Beginning of word - CharacterCodes.a <= prevChar && prevChar <= CharacterCodes.z && CharacterCodes.A <= strChar && strChar <= CharacterCodes.Z || // camelCase transition - prevChar === CharacterCodes._ && strChar !== CharacterCodes._; // snake_case transition - if (matchedFirstCharacter) { - characterIndex++; + function getCandidate() { + const parent = contextToken.parent; + if (isImportEqualsDeclaration(parent)) { + keywordCompletion = contextToken.kind === SyntaxKind.TypeKeyword ? undefined : SyntaxKind.TypeKeyword; + return isModuleSpecifierMissingOrEmpty(parent.moduleReference) ? parent : undefined; + } + if (couldBeTypeOnlyImportSpecifier(parent, contextToken) && canCompleteFromNamedBindings(parent.parent)) { + return parent; + } + if (isNamedImports(parent) || isNamespaceImport(parent)) { + if (!parent.parent.isTypeOnly && ( + contextToken.kind === SyntaxKind.OpenBraceToken || + contextToken.kind === SyntaxKind.ImportKeyword || + contextToken.kind === SyntaxKind.CommaToken + )) { + keywordCompletion = SyntaxKind.TypeKeyword; + } + + if (canCompleteFromNamedBindings(parent)) { + // At `import { ... } |` or `import * as Foo |`, the only possible completion is `from` + if (contextToken.kind === SyntaxKind.CloseBraceToken || contextToken.kind === SyntaxKind.Identifier) { + isKeywordOnlyCompletion = true; + keywordCompletion = SyntaxKind.FromKeyword; } - if (characterIndex === lowercaseCharacters.length) { - return true; + else { + return parent.parent.parent; } } - prevChar = strChar; + return undefined; + } + if (isImportKeyword(contextToken) && isSourceFile(parent)) { + // A lone import keyword with nothing following it does not parse as a statement at all + keywordCompletion = SyntaxKind.TypeKeyword; + return contextToken as Token; + } + if (isImportKeyword(contextToken) && isImportDeclaration(parent)) { + // `import s| from` + keywordCompletion = SyntaxKind.TypeKeyword; + return isModuleSpecifierMissingOrEmpty(parent.moduleSpecifier) ? parent : undefined; } + return undefined; + } +} + +function getSingleLineReplacementSpanForImportCompletionNode(node: ImportDeclaration | ImportEqualsDeclaration | ImportSpecifier | Token | undefined) { + if (!node) return undefined; + const top = findAncestor(node, or(isImportDeclaration, isImportEqualsDeclaration)) ?? node; + const sourceFile = top.getSourceFile(); + if (rangeIsOnSingleLine(top, sourceFile)) { + return createTextSpanFromNode(top, sourceFile); + } + // ImportKeyword was necessarily on one line; ImportSpecifier was necessarily parented in an ImportDeclaration + Debug.assert(top.kind !== SyntaxKind.ImportKeyword && top.kind !== SyntaxKind.ImportSpecifier); + // Guess which point in the import might actually be a later statement parsed as part of the import + // during parser recovery - either in the middle of named imports, or the module specifier. + const potentialSplitPoint = top.kind === SyntaxKind.ImportDeclaration + ? getPotentiallyInvalidImportSpecifier(top.importClause?.namedBindings) ?? top.moduleSpecifier + : top.moduleReference; + const withoutModuleSpecifier: TextRange = { + pos: top.getFirstToken()!.getStart(), + end: potentialSplitPoint.pos, + }; + // The module specifier/reference was previously found to be missing, empty, or + // not a string literal - in this last case, it's likely that statement on a following + // line was parsed as the module specifier of a partially-typed import, e.g. + // import Foo| + // interface Blah {} + // This appears to be a multiline-import, and editors can't replace multiple lines. + // But if everything but the "module specifier" is on one line, by this point we can + // assume that the "module specifier" is actually just another statement, and return + // the single-line range of the import excluding that probable statement. + if (rangeIsOnSingleLine(withoutModuleSpecifier, sourceFile)) { + return createTextSpanFromRange(withoutModuleSpecifier); + } +} + +// Tries to identify the first named import that is not really a named import, but rather +// just parser recovery for a situation like: +// import { Foo| +// interface Bar {} +// in which `Foo`, `interface`, and `Bar` are all parsed as import specifiers. The caller +// will also check if this token is on a separate line from the rest of the import. +function getPotentiallyInvalidImportSpecifier(namedBindings: NamedImportBindings | undefined) { + return find( + tryCast(namedBindings, isNamedImports)?.elements, + e => !e.propertyName && + isStringANonContextualKeyword(e.name.text) && + findPrecedingToken(e.name.pos, namedBindings!.getSourceFile(), namedBindings)?.kind !== SyntaxKind.CommaToken); +} + +function couldBeTypeOnlyImportSpecifier(importSpecifier: Node, contextToken: Node | undefined): importSpecifier is ImportSpecifier { + return isImportSpecifier(importSpecifier) + && (importSpecifier.isTypeOnly || contextToken === importSpecifier.name && isTypeKeywordTokenOrIdentifier(contextToken)); +} - // Did not find all characters +function canCompleteFromNamedBindings(namedBindings: NamedImportBindings) { + if (!isModuleSpecifierMissingOrEmpty(namedBindings.parent.parent.moduleSpecifier) || namedBindings.parent.name) { return false; } + if (isNamedImports(namedBindings)) { + // We can only complete on named imports if there are no other named imports already, + // but parser recovery sometimes puts later statements in the named imports list, so + // we try to only consider the probably-valid ones. + const invalidNamedImport = getPotentiallyInvalidImportSpecifier(namedBindings); + const validImports = invalidNamedImport ? namedBindings.elements.indexOf(invalidNamedImport) : namedBindings.elements.length; + return validImports < 2; + } + return true; +} + +function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression) { + if (nodeIsMissing(specifier)) return true; + return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text; +} + +function getVariableDeclaration(property: Node): VariableDeclaration | undefined { + const variableDeclaration = findAncestor(property, node => + isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node) + ? "quit" + : isVariableDeclaration(node)); + + return variableDeclaration as VariableDeclaration | undefined; +} + +function isArrowFunctionBody(node: Node) { + return node.parent && isArrowFunction(node.parent) && node.parent.body === node; +} + +/** True if symbol is a type or a module containing at least one type. */ +function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, checker: TypeChecker, seenModules = new Map()): boolean { + // Since an alias can be merged with a local declaration, we need to test both the alias and its target. + // This code used to just test the result of `skipAlias`, but that would ignore any locally introduced meanings. + return nonAliasCanBeReferencedAtTypeLocation(symbol) || nonAliasCanBeReferencedAtTypeLocation(skipAlias(symbol.exportSymbol || symbol, checker)); + + function nonAliasCanBeReferencedAtTypeLocation(symbol: Symbol): boolean { + return !!(symbol.flags & SymbolFlags.Type) || checker.isUnknownSymbol(symbol) || + !!(symbol.flags & SymbolFlags.Module) && addToSeen(seenModules, getSymbolId(symbol)) && + checker.getExportsOfModule(symbol).some(e => symbolCanBeReferencedAtTypeLocation(e, checker, seenModules)); + } +} + +function isDeprecated(symbol: Symbol, checker: TypeChecker) { + const declarations = skipAlias(symbol, checker).declarations; + return !!length(declarations) && every(declarations, isDeprecatedDeclaration); +} + +/** + * True if the first character of `lowercaseCharacters` is the first character + * of some "word" in `identiferString` (where the string is split into "words" + * by camelCase and snake_case segments), then if the remaining characters of + * `lowercaseCharacters` appear, in order, in the rest of `identifierString`. + * + * True: + * 'state' in 'useState' + * 'sae' in 'useState' + * 'viable' in 'ENVIRONMENT_VARIABLE' + * + * False: + * 'staet' in 'useState' + * 'tate' in 'useState' + * 'ment' in 'ENVIRONMENT_VARIABLE' + */ + function charactersFuzzyMatchInString(identifierString: string, lowercaseCharacters: string): boolean { + if (lowercaseCharacters.length === 0) { + return true; + } - function toUpperCharCode(charCode: number) { - if (CharacterCodes.a <= charCode && charCode <= CharacterCodes.z) { - return charCode - 32; + let matchedFirstCharacter = false; + let prevChar: number | undefined; + let characterIndex = 0; + const len = identifierString.length; + for (let strIndex = 0; strIndex < len; strIndex++) { + const strChar = identifierString.charCodeAt(strIndex); + const testChar = lowercaseCharacters.charCodeAt(characterIndex); + if (strChar === testChar || strChar === toUpperCharCode(testChar)) { + matchedFirstCharacter ||= + prevChar === undefined || // Beginning of word + CharacterCodes.a <= prevChar && prevChar <= CharacterCodes.z && CharacterCodes.A <= strChar && strChar <= CharacterCodes.Z || // camelCase transition + prevChar === CharacterCodes._ && strChar !== CharacterCodes._; // snake_case transition + if (matchedFirstCharacter) { + characterIndex++; + } + if (characterIndex === lowercaseCharacters.length) { + return true; + } } - return charCode; + prevChar = strChar; } + // Did not find all characters + return false; +} + +function toUpperCharCode(charCode: number) { + if (CharacterCodes.a <= charCode && charCode <= CharacterCodes.z) { + return charCode - 32; + } + return charCode; } diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index 698f82eda51b4..fb049851355b7 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -1,521 +1,534 @@ -namespace ts { - export interface DocumentHighlights { - fileName: string; - highlightSpans: HighlightSpan[]; - } - - /* @internal */ - export namespace DocumentHighlights { - export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { - const node = getTouchingPropertyName(sourceFile, position); - - if (node.parent && (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent))) { - // For a JSX element, just highlight the matching tag, not all references. - const { openingElement, closingElement } = node.parent.parent; - const highlightSpans = [openingElement, closingElement].map(({ tagName }) => getHighlightSpanForNode(tagName, sourceFile)); - return [{ fileName: sourceFile.fileName, highlightSpans }]; - } +import { + __String, arrayFrom, arrayToMultiMap, Block, BreakOrContinueStatement, CancellationToken, CaseClause, cast, + concatenate, ConstructorDeclaration, contains, createGetCanonicalFileName, createTextSpanFromBounds, + createTextSpanFromNode, Debug, DefaultClause, find, FindAllReferences, findAncestor, findChildOfKind, findModifier, + forEach, forEachChild, forEachReturnStatement, FunctionDeclaration, FunctionLikeDeclaration, getContainingFunction, + getTouchingPropertyName, HighlightSpan, HighlightSpanKind, IfStatement, isAccessor, isAwaitExpression, isBlock, + isBreakOrContinueStatement, isCaseClause, isClassDeclaration, isClassLike, isConstructorDeclaration, isDeclaration, + isDefaultClause, isFunctionBlock, isFunctionLike, isIfStatement, isInterfaceDeclaration, isIterationStatement, + isJsxClosingElement, isJsxOpeningElement, isLabeledStatement, isModifierKind, isModuleDeclaration, + isReturnStatement, isSwitchStatement, isThrowStatement, isTryStatement, isTypeAliasDeclaration, isTypeNode, + isVariableStatement, isWhiteSpaceSingleLine, isYieldExpression, IterationStatement, mapDefined, MethodDeclaration, + Modifier, ModifierFlags, modifierToFlag, ModuleBlock, Node, ObjectLiteralExpression, ObjectTypeDeclaration, Program, + Push, ReturnStatement, Set, SourceFile, SwitchStatement, SyntaxKind, ThrowStatement, toArray, toPath, TryStatement, +} from "./_namespaces/ts"; + +export interface DocumentHighlights { + fileName: string; + highlightSpans: HighlightSpan[]; +} - return getSemanticDocumentHighlights(position, node, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile); - } +/** @internal */ +export namespace DocumentHighlights { + export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { + const node = getTouchingPropertyName(sourceFile, position); - function getHighlightSpanForNode(node: Node, sourceFile: SourceFile): HighlightSpan { - return { - fileName: sourceFile.fileName, - textSpan: createTextSpanFromNode(node, sourceFile), - kind: HighlightSpanKind.none - }; + if (node.parent && (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent))) { + // For a JSX element, just highlight the matching tag, not all references. + const { openingElement, closingElement } = node.parent.parent; + const highlightSpans = [openingElement, closingElement].map(({ tagName }) => getHighlightSpanForNode(tagName, sourceFile)); + return [{ fileName: sourceFile.fileName, highlightSpans }]; } - function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { - const sourceFilesSet = new Set(sourceFilesToSearch.map(f => f.fileName)); - const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*options*/ undefined, sourceFilesSet); - if (!referenceEntries) return undefined; - const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span); - const getCanonicalFileName = createGetCanonicalFileName(program.useCaseSensitiveFileNames()); - return mapDefined(arrayFrom(map.entries()), ([fileName, highlightSpans]) => { - if (!sourceFilesSet.has(fileName)) { - if (!program.redirectTargetsMap.has(toPath(fileName, program.getCurrentDirectory(), getCanonicalFileName))) { - return undefined; - } - const redirectTarget = program.getSourceFile(fileName); - const redirect = find(sourceFilesToSearch, f => !!f.redirectInfo && f.redirectInfo.redirectTarget === redirectTarget)!; - fileName = redirect.fileName; - Debug.assert(sourceFilesSet.has(fileName)); - } - return { fileName, highlightSpans }; - }); - } + return getSemanticDocumentHighlights(position, node, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile); + } - function getSyntacticDocumentHighlights(node: Node, sourceFile: SourceFile): DocumentHighlights[] | undefined { - const highlightSpans = getHighlightSpans(node, sourceFile); - return highlightSpans && [{ fileName: sourceFile.fileName, highlightSpans }]; - } + function getHighlightSpanForNode(node: Node, sourceFile: SourceFile): HighlightSpan { + return { + fileName: sourceFile.fileName, + textSpan: createTextSpanFromNode(node, sourceFile), + kind: HighlightSpanKind.none + }; + } - function getHighlightSpans(node: Node, sourceFile: SourceFile): HighlightSpan[] | undefined { - switch (node.kind) { - case SyntaxKind.IfKeyword: - case SyntaxKind.ElseKeyword: - return isIfStatement(node.parent) ? getIfElseOccurrences(node.parent, sourceFile) : undefined; - case SyntaxKind.ReturnKeyword: - return useParent(node.parent, isReturnStatement, getReturnOccurrences); - case SyntaxKind.ThrowKeyword: - return useParent(node.parent, isThrowStatement, getThrowOccurrences); - case SyntaxKind.TryKeyword: - case SyntaxKind.CatchKeyword: - case SyntaxKind.FinallyKeyword: - const tryStatement = node.kind === SyntaxKind.CatchKeyword ? node.parent.parent : node.parent; - return useParent(tryStatement, isTryStatement, getTryCatchFinallyOccurrences); - case SyntaxKind.SwitchKeyword: - return useParent(node.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); - case SyntaxKind.CaseKeyword: - case SyntaxKind.DefaultKeyword: { - if (isDefaultClause(node.parent) || isCaseClause(node.parent)) { - return useParent(node.parent.parent.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); - } + function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { + const sourceFilesSet = new Set(sourceFilesToSearch.map(f => f.fileName)); + const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*options*/ undefined, sourceFilesSet); + if (!referenceEntries) return undefined; + const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span); + const getCanonicalFileName = createGetCanonicalFileName(program.useCaseSensitiveFileNames()); + return mapDefined(arrayFrom(map.entries()), ([fileName, highlightSpans]) => { + if (!sourceFilesSet.has(fileName)) { + if (!program.redirectTargetsMap.has(toPath(fileName, program.getCurrentDirectory(), getCanonicalFileName))) { return undefined; } - case SyntaxKind.BreakKeyword: - case SyntaxKind.ContinueKeyword: - return useParent(node.parent, isBreakOrContinueStatement, getBreakOrContinueStatementOccurrences); - case SyntaxKind.ForKeyword: - case SyntaxKind.WhileKeyword: - case SyntaxKind.DoKeyword: - return useParent(node.parent, (n): n is IterationStatement => isIterationStatement(n, /*lookInLabeledStatements*/ true), getLoopBreakContinueOccurrences); - case SyntaxKind.ConstructorKeyword: - return getFromAllDeclarations(isConstructorDeclaration, [SyntaxKind.ConstructorKeyword]); - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - return getFromAllDeclarations(isAccessor, [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword]); - case SyntaxKind.AwaitKeyword: - return useParent(node.parent, isAwaitExpression, getAsyncAndAwaitOccurrences); - case SyntaxKind.AsyncKeyword: - return highlightSpans(getAsyncAndAwaitOccurrences(node)); - case SyntaxKind.YieldKeyword: - return highlightSpans(getYieldOccurrences(node)); - case SyntaxKind.InKeyword: - return undefined; - default: - return isModifierKind(node.kind) && (isDeclaration(node.parent) || isVariableStatement(node.parent)) - ? highlightSpans(getModifierOccurrences(node.kind, node.parent)) - : undefined; + const redirectTarget = program.getSourceFile(fileName); + const redirect = find(sourceFilesToSearch, f => !!f.redirectInfo && f.redirectInfo.redirectTarget === redirectTarget)!; + fileName = redirect.fileName; + Debug.assert(sourceFilesSet.has(fileName)); } + return { fileName, highlightSpans }; + }); + } - function getFromAllDeclarations(nodeTest: (node: Node) => node is T, keywords: readonly SyntaxKind[]): HighlightSpan[] | undefined { - return useParent(node.parent, nodeTest, decl => mapDefined(decl.symbol.declarations, d => - nodeTest(d) ? find(d.getChildren(sourceFile), c => contains(keywords, c.kind)) : undefined)); - } + function getSyntacticDocumentHighlights(node: Node, sourceFile: SourceFile): DocumentHighlights[] | undefined { + const highlightSpans = getHighlightSpans(node, sourceFile); + return highlightSpans && [{ fileName: sourceFile.fileName, highlightSpans }]; + } - function useParent(node: Node, nodeTest: (node: Node) => node is T, getNodes: (node: T, sourceFile: SourceFile) => readonly Node[] | undefined): HighlightSpan[] | undefined { - return nodeTest(node) ? highlightSpans(getNodes(node, sourceFile)) : undefined; + function getHighlightSpans(node: Node, sourceFile: SourceFile): HighlightSpan[] | undefined { + switch (node.kind) { + case SyntaxKind.IfKeyword: + case SyntaxKind.ElseKeyword: + return isIfStatement(node.parent) ? getIfElseOccurrences(node.parent, sourceFile) : undefined; + case SyntaxKind.ReturnKeyword: + return useParent(node.parent, isReturnStatement, getReturnOccurrences); + case SyntaxKind.ThrowKeyword: + return useParent(node.parent, isThrowStatement, getThrowOccurrences); + case SyntaxKind.TryKeyword: + case SyntaxKind.CatchKeyword: + case SyntaxKind.FinallyKeyword: + const tryStatement = node.kind === SyntaxKind.CatchKeyword ? node.parent.parent : node.parent; + return useParent(tryStatement, isTryStatement, getTryCatchFinallyOccurrences); + case SyntaxKind.SwitchKeyword: + return useParent(node.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); + case SyntaxKind.CaseKeyword: + case SyntaxKind.DefaultKeyword: { + if (isDefaultClause(node.parent) || isCaseClause(node.parent)) { + return useParent(node.parent.parent.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); + } + return undefined; } + case SyntaxKind.BreakKeyword: + case SyntaxKind.ContinueKeyword: + return useParent(node.parent, isBreakOrContinueStatement, getBreakOrContinueStatementOccurrences); + case SyntaxKind.ForKeyword: + case SyntaxKind.WhileKeyword: + case SyntaxKind.DoKeyword: + return useParent(node.parent, (n): n is IterationStatement => isIterationStatement(n, /*lookInLabeledStatements*/ true), getLoopBreakContinueOccurrences); + case SyntaxKind.ConstructorKeyword: + return getFromAllDeclarations(isConstructorDeclaration, [SyntaxKind.ConstructorKeyword]); + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + return getFromAllDeclarations(isAccessor, [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword]); + case SyntaxKind.AwaitKeyword: + return useParent(node.parent, isAwaitExpression, getAsyncAndAwaitOccurrences); + case SyntaxKind.AsyncKeyword: + return highlightSpans(getAsyncAndAwaitOccurrences(node)); + case SyntaxKind.YieldKeyword: + return highlightSpans(getYieldOccurrences(node)); + case SyntaxKind.InKeyword: + return undefined; + default: + return isModifierKind(node.kind) && (isDeclaration(node.parent) || isVariableStatement(node.parent)) + ? highlightSpans(getModifierOccurrences(node.kind, node.parent)) + : undefined; + } - function highlightSpans(nodes: readonly Node[] | undefined): HighlightSpan[] | undefined { - return nodes && nodes.map(node => getHighlightSpanForNode(node, sourceFile)); - } + function getFromAllDeclarations(nodeTest: (node: Node) => node is T, keywords: readonly SyntaxKind[]): HighlightSpan[] | undefined { + return useParent(node.parent, nodeTest, decl => mapDefined(decl.symbol.declarations, d => + nodeTest(d) ? find(d.getChildren(sourceFile), c => contains(keywords, c.kind)) : undefined)); } - /** - * Aggregates all throw-statements within this node *without* crossing - * into function boundaries and try-blocks with catch-clauses. - */ - function aggregateOwnedThrowStatements(node: Node): readonly ThrowStatement[] | undefined { - if (isThrowStatement(node)) { - return [node]; - } - else if (isTryStatement(node)) { - // Exceptions thrown within a try block lacking a catch clause are "owned" in the current context. - return concatenate( - node.catchClause ? aggregateOwnedThrowStatements(node.catchClause) : node.tryBlock && aggregateOwnedThrowStatements(node.tryBlock), - node.finallyBlock && aggregateOwnedThrowStatements(node.finallyBlock)); - } - // Do not cross function boundaries. - return isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateOwnedThrowStatements); + function useParent(node: Node, nodeTest: (node: Node) => node is T, getNodes: (node: T, sourceFile: SourceFile) => readonly Node[] | undefined): HighlightSpan[] | undefined { + return nodeTest(node) ? highlightSpans(getNodes(node, sourceFile)) : undefined; } - /** - * For lack of a better name, this function takes a throw statement and returns the - * nearest ancestor that is a try-block (whose try statement has a catch clause), - * function-block, or source file. - */ - function getThrowStatementOwner(throwStatement: ThrowStatement): Node | undefined { - let child: Node = throwStatement; + function highlightSpans(nodes: readonly Node[] | undefined): HighlightSpan[] | undefined { + return nodes && nodes.map(node => getHighlightSpanForNode(node, sourceFile)); + } + } - while (child.parent) { - const parent = child.parent; + /** + * Aggregates all throw-statements within this node *without* crossing + * into function boundaries and try-blocks with catch-clauses. + */ + function aggregateOwnedThrowStatements(node: Node): readonly ThrowStatement[] | undefined { + if (isThrowStatement(node)) { + return [node]; + } + else if (isTryStatement(node)) { + // Exceptions thrown within a try block lacking a catch clause are "owned" in the current context. + return concatenate( + node.catchClause ? aggregateOwnedThrowStatements(node.catchClause) : node.tryBlock && aggregateOwnedThrowStatements(node.tryBlock), + node.finallyBlock && aggregateOwnedThrowStatements(node.finallyBlock)); + } + // Do not cross function boundaries. + return isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateOwnedThrowStatements); + } - if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) { - return parent; - } + /** + * For lack of a better name, this function takes a throw statement and returns the + * nearest ancestor that is a try-block (whose try statement has a catch clause), + * function-block, or source file. + */ + function getThrowStatementOwner(throwStatement: ThrowStatement): Node | undefined { + let child: Node = throwStatement; - // A throw-statement is only owned by a try-statement if the try-statement has - // a catch clause, and if the throw-statement occurs within the try block. - if (isTryStatement(parent) && parent.tryBlock === child && parent.catchClause) { - return child; - } + while (child.parent) { + const parent = child.parent; - child = parent; + if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) { + return parent; } - return undefined; - } + // A throw-statement is only owned by a try-statement if the try-statement has + // a catch clause, and if the throw-statement occurs within the try block. + if (isTryStatement(parent) && parent.tryBlock === child && parent.catchClause) { + return child; + } - function aggregateAllBreakAndContinueStatements(node: Node): readonly BreakOrContinueStatement[] | undefined { - return isBreakOrContinueStatement(node) ? [node] : isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateAllBreakAndContinueStatements); + child = parent; } - function flatMapChildren(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] { - const result: T[] = []; - node.forEachChild(child => { - const value = cb(child); - if (value !== undefined) { - result.push(...toArray(value)); - } - }); - return result; - } + return undefined; + } - function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean { - const actualOwner = getBreakOrContinueOwner(statement); - return !!actualOwner && actualOwner === owner; - } + function aggregateAllBreakAndContinueStatements(node: Node): readonly BreakOrContinueStatement[] | undefined { + return isBreakOrContinueStatement(node) ? [node] : isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateAllBreakAndContinueStatements); + } - function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node | undefined { - return findAncestor(statement, node => { - switch (node.kind) { - case SyntaxKind.SwitchStatement: - if (statement.kind === SyntaxKind.ContinueStatement) { - return false; - } - // falls through - - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.DoStatement: - return !statement.label || isLabeledBy(node, statement.label.escapedText); - default: - // Don't cross function boundaries. - // TODO: GH#20090 - return isFunctionLike(node) && "quit"; - } - }); - } + function flatMapChildren(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] { + const result: T[] = []; + node.forEachChild(child => { + const value = cb(child); + if (value !== undefined) { + result.push(...toArray(value)); + } + }); + return result; + } - function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] { - return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier)); - } + function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean { + const actualOwner = getBreakOrContinueOwner(statement); + return !!actualOwner && actualOwner === owner; + } - function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): readonly Node[] | undefined { - // Types of node whose children might have modifiers. - const container = declaration.parent as ModuleBlock | SourceFile | Block | CaseClause | DefaultClause | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | ObjectTypeDeclaration | ObjectLiteralExpression; - switch (container.kind) { - case SyntaxKind.ModuleBlock: - case SyntaxKind.SourceFile: - case SyntaxKind.Block: - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - // Container is either a class declaration or the declaration is a classDeclaration - if (modifierFlag & ModifierFlags.Abstract && isClassDeclaration(declaration)) { - return [...declaration.members, declaration]; - } - else { - return container.statements; - } - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionDeclaration: - return [...container.parameters, ...(isClassLike(container.parent) ? container.parent.members : [])]; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeLiteral: - const nodes = container.members; - - // If we're an accessibility modifier, we're in an instance member and should search - // the constructor's parameter list for instance members as well. - if (modifierFlag & (ModifierFlags.AccessibilityModifier | ModifierFlags.Readonly)) { - const constructor = find(container.members, isConstructorDeclaration); - if (constructor) { - return [...nodes, ...constructor.parameters]; - } + function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node | undefined { + return findAncestor(statement, node => { + switch (node.kind) { + case SyntaxKind.SwitchStatement: + if (statement.kind === SyntaxKind.ContinueStatement) { + return false; } - else if (modifierFlag & ModifierFlags.Abstract) { - return [...nodes, container]; + // falls through + + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.DoStatement: + return !statement.label || isLabeledBy(node, statement.label.escapedText); + default: + // Don't cross function boundaries. + // TODO: GH#20090 + return isFunctionLike(node) && "quit"; + } + }); + } + + function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] { + return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier)); + } + + function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): readonly Node[] | undefined { + // Types of node whose children might have modifiers. + const container = declaration.parent as ModuleBlock | SourceFile | Block | CaseClause | DefaultClause | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | ObjectTypeDeclaration | ObjectLiteralExpression; + switch (container.kind) { + case SyntaxKind.ModuleBlock: + case SyntaxKind.SourceFile: + case SyntaxKind.Block: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + // Container is either a class declaration or the declaration is a classDeclaration + if (modifierFlag & ModifierFlags.Abstract && isClassDeclaration(declaration)) { + return [...declaration.members, declaration]; + } + else { + return container.statements; + } + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: + return [...container.parameters, ...(isClassLike(container.parent) ? container.parent.members : [])]; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeLiteral: + const nodes = container.members; + + // If we're an accessibility modifier, we're in an instance member and should search + // the constructor's parameter list for instance members as well. + if (modifierFlag & (ModifierFlags.AccessibilityModifier | ModifierFlags.Readonly)) { + const constructor = find(container.members, isConstructorDeclaration); + if (constructor) { + return [...nodes, ...constructor.parameters]; } - return nodes; + } + else if (modifierFlag & ModifierFlags.Abstract) { + return [...nodes, container]; + } + return nodes; - // Syntactically invalid positions that the parser might produce anyway - case SyntaxKind.ObjectLiteralExpression: - return undefined; + // Syntactically invalid positions that the parser might produce anyway + case SyntaxKind.ObjectLiteralExpression: + return undefined; - default: - Debug.assertNever(container, "Invalid container kind."); - } + default: + Debug.assertNever(container, "Invalid container kind."); } + } - function pushKeywordIf(keywordList: Push, token: Node | undefined, ...expected: SyntaxKind[]): boolean { - if (token && contains(expected, token.kind)) { - keywordList.push(token); - return true; - } - - return false; + function pushKeywordIf(keywordList: Push, token: Node | undefined, ...expected: SyntaxKind[]): boolean { + if (token && contains(expected, token.kind)) { + keywordList.push(token); + return true; } - function getLoopBreakContinueOccurrences(loopNode: IterationStatement): Node[] { - const keywords: Node[] = []; + return false; + } + + function getLoopBreakContinueOccurrences(loopNode: IterationStatement): Node[] { + const keywords: Node[] = []; - if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) { - // If we succeeded and got a do-while loop, then start looking for a 'while' keyword. - if (loopNode.kind === SyntaxKind.DoStatement) { - const loopTokens = loopNode.getChildren(); + if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) { + // If we succeeded and got a do-while loop, then start looking for a 'while' keyword. + if (loopNode.kind === SyntaxKind.DoStatement) { + const loopTokens = loopNode.getChildren(); - for (let i = loopTokens.length - 1; i >= 0; i--) { - if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) { - break; - } + for (let i = loopTokens.length - 1; i >= 0; i--) { + if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) { + break; } } } + } - forEach(aggregateAllBreakAndContinueStatements(loopNode.statement), statement => { - if (ownsBreakOrContinueStatement(loopNode, statement)) { - pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); - } - }); + forEach(aggregateAllBreakAndContinueStatements(loopNode.statement), statement => { + if (ownsBreakOrContinueStatement(loopNode, statement)) { + pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); + } + }); - return keywords; - } + return keywords; + } - function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): Node[] | undefined { - const owner = getBreakOrContinueOwner(breakOrContinueStatement); + function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): Node[] | undefined { + const owner = getBreakOrContinueOwner(breakOrContinueStatement); - if (owner) { - switch (owner.kind) { - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - return getLoopBreakContinueOccurrences(owner as IterationStatement); - case SyntaxKind.SwitchStatement: - return getSwitchCaseDefaultOccurrences(owner as SwitchStatement); + if (owner) { + switch (owner.kind) { + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + return getLoopBreakContinueOccurrences(owner as IterationStatement); + case SyntaxKind.SwitchStatement: + return getSwitchCaseDefaultOccurrences(owner as SwitchStatement); - } } - - return undefined; } - function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): Node[] { - const keywords: Node[] = []; + return undefined; + } - pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword); + function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): Node[] { + const keywords: Node[] = []; - // Go through each clause in the switch statement, collecting the 'case'/'default' keywords. - forEach(switchStatement.caseBlock.clauses, clause => { - pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword); + pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword); - forEach(aggregateAllBreakAndContinueStatements(clause), statement => { - if (ownsBreakOrContinueStatement(switchStatement, statement)) { - pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword); - } - }); - }); + // Go through each clause in the switch statement, collecting the 'case'/'default' keywords. + forEach(switchStatement.caseBlock.clauses, clause => { + pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword); - return keywords; - } + forEach(aggregateAllBreakAndContinueStatements(clause), statement => { + if (ownsBreakOrContinueStatement(switchStatement, statement)) { + pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword); + } + }); + }); - function getTryCatchFinallyOccurrences(tryStatement: TryStatement, sourceFile: SourceFile): Node[] { - const keywords: Node[] = []; + return keywords; + } - pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword); + function getTryCatchFinallyOccurrences(tryStatement: TryStatement, sourceFile: SourceFile): Node[] { + const keywords: Node[] = []; - if (tryStatement.catchClause) { - pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword); - } + pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword); - if (tryStatement.finallyBlock) { - const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile)!; - pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword); - } + if (tryStatement.catchClause) { + pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword); + } - return keywords; + if (tryStatement.finallyBlock) { + const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile)!; + pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword); } - function getThrowOccurrences(throwStatement: ThrowStatement, sourceFile: SourceFile): Node[] | undefined { - const owner = getThrowStatementOwner(throwStatement); + return keywords; + } - if (!owner) { - return undefined; - } + function getThrowOccurrences(throwStatement: ThrowStatement, sourceFile: SourceFile): Node[] | undefined { + const owner = getThrowStatementOwner(throwStatement); - const keywords: Node[] = []; + if (!owner) { + return undefined; + } - forEach(aggregateOwnedThrowStatements(owner), throwStatement => { - keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); + const keywords: Node[] = []; + + forEach(aggregateOwnedThrowStatements(owner), throwStatement => { + keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); + }); + + // If the "owner" is a function, then we equate 'return' and 'throw' statements in their + // ability to "jump out" of the function, and include occurrences for both. + if (isFunctionBlock(owner)) { + forEachReturnStatement(owner as Block, returnStatement => { + keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!); }); + } - // If the "owner" is a function, then we equate 'return' and 'throw' statements in their - // ability to "jump out" of the function, and include occurrences for both. - if (isFunctionBlock(owner)) { - forEachReturnStatement(owner as Block, returnStatement => { - keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!); - }); - } + return keywords; + } - return keywords; + function getReturnOccurrences(returnStatement: ReturnStatement, sourceFile: SourceFile): Node[] | undefined { + const func = getContainingFunction(returnStatement) as FunctionLikeDeclaration; + if (!func) { + return undefined; } - function getReturnOccurrences(returnStatement: ReturnStatement, sourceFile: SourceFile): Node[] | undefined { - const func = getContainingFunction(returnStatement) as FunctionLikeDeclaration; - if (!func) { - return undefined; - } + const keywords: Node[] = []; + forEachReturnStatement(cast(func.body, isBlock), returnStatement => { + keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!); + }); - const keywords: Node[] = []; - forEachReturnStatement(cast(func.body, isBlock), returnStatement => { - keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!); - }); + // Include 'throw' statements that do not occur within a try block. + forEach(aggregateOwnedThrowStatements(func.body!), throwStatement => { + keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); + }); - // Include 'throw' statements that do not occur within a try block. - forEach(aggregateOwnedThrowStatements(func.body!), throwStatement => { - keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); - }); + return keywords; + } - return keywords; + function getAsyncAndAwaitOccurrences(node: Node): Node[] | undefined { + const func = getContainingFunction(node) as FunctionLikeDeclaration; + if (!func) { + return undefined; } - function getAsyncAndAwaitOccurrences(node: Node): Node[] | undefined { - const func = getContainingFunction(node) as FunctionLikeDeclaration; - if (!func) { - return undefined; - } + const keywords: Node[] = []; - const keywords: Node[] = []; - - if (func.modifiers) { - func.modifiers.forEach(modifier => { - pushKeywordIf(keywords, modifier, SyntaxKind.AsyncKeyword); - }); - } + if (func.modifiers) { + func.modifiers.forEach(modifier => { + pushKeywordIf(keywords, modifier, SyntaxKind.AsyncKeyword); + }); + } - forEachChild(func, child => { - traverseWithoutCrossingFunction(child, node => { - if (isAwaitExpression(node)) { - pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.AwaitKeyword); - } - }); + forEachChild(func, child => { + traverseWithoutCrossingFunction(child, node => { + if (isAwaitExpression(node)) { + pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.AwaitKeyword); + } }); + }); - return keywords; - } + return keywords; + } - function getYieldOccurrences(node: Node): Node[] | undefined { - const func = getContainingFunction(node) as FunctionDeclaration; - if (!func) { - return undefined; - } + function getYieldOccurrences(node: Node): Node[] | undefined { + const func = getContainingFunction(node) as FunctionDeclaration; + if (!func) { + return undefined; + } - const keywords: Node[] = []; + const keywords: Node[] = []; - forEachChild(func, child => { - traverseWithoutCrossingFunction(child, node => { - if (isYieldExpression(node)) { - pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.YieldKeyword); - } - }); + forEachChild(func, child => { + traverseWithoutCrossingFunction(child, node => { + if (isYieldExpression(node)) { + pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.YieldKeyword); + } }); + }); - return keywords; - } + return keywords; + } - // Do not cross function/class/interface/module/type boundaries. - function traverseWithoutCrossingFunction(node: Node, cb: (node: Node) => void) { - cb(node); - if (!isFunctionLike(node) && !isClassLike(node) && !isInterfaceDeclaration(node) && !isModuleDeclaration(node) && !isTypeAliasDeclaration(node) && !isTypeNode(node)) { - forEachChild(node, child => traverseWithoutCrossingFunction(child, cb)); - } + // Do not cross function/class/interface/module/type boundaries. + function traverseWithoutCrossingFunction(node: Node, cb: (node: Node) => void) { + cb(node); + if (!isFunctionLike(node) && !isClassLike(node) && !isInterfaceDeclaration(node) && !isModuleDeclaration(node) && !isTypeAliasDeclaration(node) && !isTypeNode(node)) { + forEachChild(node, child => traverseWithoutCrossingFunction(child, cb)); } + } - function getIfElseOccurrences(ifStatement: IfStatement, sourceFile: SourceFile): HighlightSpan[] { - const keywords = getIfElseKeywords(ifStatement, sourceFile); - const result: HighlightSpan[] = []; - - // We'd like to highlight else/ifs together if they are only separated by whitespace - // (i.e. the keywords are separated by no comments, no newlines). - for (let i = 0; i < keywords.length; i++) { - if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) { - const elseKeyword = keywords[i]; - const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword. - - let shouldCombineElseAndIf = true; - - // Avoid recalculating getStart() by iterating backwards. - for (let j = ifKeyword.getStart(sourceFile) - 1; j >= elseKeyword.end; j--) { - if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) { - shouldCombineElseAndIf = false; - break; - } - } + function getIfElseOccurrences(ifStatement: IfStatement, sourceFile: SourceFile): HighlightSpan[] { + const keywords = getIfElseKeywords(ifStatement, sourceFile); + const result: HighlightSpan[] = []; + + // We'd like to highlight else/ifs together if they are only separated by whitespace + // (i.e. the keywords are separated by no comments, no newlines). + for (let i = 0; i < keywords.length; i++) { + if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) { + const elseKeyword = keywords[i]; + const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword. - if (shouldCombineElseAndIf) { - result.push({ - fileName: sourceFile.fileName, - textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end), - kind: HighlightSpanKind.reference - }); - i++; // skip the next keyword - continue; + let shouldCombineElseAndIf = true; + + // Avoid recalculating getStart() by iterating backwards. + for (let j = ifKeyword.getStart(sourceFile) - 1; j >= elseKeyword.end; j--) { + if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) { + shouldCombineElseAndIf = false; + break; } } - // Ordinary case: just highlight the keyword. - result.push(getHighlightSpanForNode(keywords[i], sourceFile)); + if (shouldCombineElseAndIf) { + result.push({ + fileName: sourceFile.fileName, + textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end), + kind: HighlightSpanKind.reference + }); + i++; // skip the next keyword + continue; + } } - return result; + // Ordinary case: just highlight the keyword. + result.push(getHighlightSpanForNode(keywords[i], sourceFile)); } - function getIfElseKeywords(ifStatement: IfStatement, sourceFile: SourceFile): Node[] { - const keywords: Node[] = []; + return result; + } - // Traverse upwards through all parent if-statements linked by their else-branches. - while (isIfStatement(ifStatement.parent) && ifStatement.parent.elseStatement === ifStatement) { - ifStatement = ifStatement.parent; - } + function getIfElseKeywords(ifStatement: IfStatement, sourceFile: SourceFile): Node[] { + const keywords: Node[] = []; - // Now traverse back down through the else branches, aggregating if/else keywords of if-statements. - while (true) { - const children = ifStatement.getChildren(sourceFile); - pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword); + // Traverse upwards through all parent if-statements linked by their else-branches. + while (isIfStatement(ifStatement.parent) && ifStatement.parent.elseStatement === ifStatement) { + ifStatement = ifStatement.parent; + } - // Generally the 'else' keyword is second-to-last, so we traverse backwards. - for (let i = children.length - 1; i >= 0; i--) { - if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) { - break; - } - } + // Now traverse back down through the else branches, aggregating if/else keywords of if-statements. + while (true) { + const children = ifStatement.getChildren(sourceFile); + pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword); - if (!ifStatement.elseStatement || !isIfStatement(ifStatement.elseStatement)) { + // Generally the 'else' keyword is second-to-last, so we traverse backwards. + for (let i = children.length - 1; i >= 0; i--) { + if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) { break; } + } - ifStatement = ifStatement.elseStatement; + if (!ifStatement.elseStatement || !isIfStatement(ifStatement.elseStatement)) { + break; } - return keywords; + ifStatement = ifStatement.elseStatement; } - /** - * Whether or not a 'node' is preceded by a label of the given string. - * Note: 'node' cannot be a SourceFile. - */ - function isLabeledBy(node: Node, labelName: __String): boolean { - return !!findAncestor(node.parent, owner => !isLabeledStatement(owner) ? "quit" : owner.label.escapedText === labelName); - } + return keywords; + } + + /** + * Whether or not a 'node' is preceded by a label of the given string. + * Note: 'node' cannot be a SourceFile. + */ + function isLabeledBy(node: Node, labelName: __String): boolean { + return !!findAncestor(node.parent, owner => !isLabeledStatement(owner) ? "quit" : owner.label.escapedText === labelName); } } diff --git a/src/services/documentRegistry.ts b/src/services/documentRegistry.ts index 21a17a2d30045..7ca869b38b038 100644 --- a/src/services/documentRegistry.ts +++ b/src/services/documentRegistry.ts @@ -1,400 +1,407 @@ -namespace ts { +import { + arrayFrom, CompilerOptions, createGetCanonicalFileName, createLanguageServiceSourceFile, CreateSourceFileOptions, + Debug, ensureScriptKind, ESMap, firstDefinedIterator, forEachEntry, getCompilerOptionValue, getEmitScriptTarget, + getImpliedNodeFormatForFile, getOrUpdate, getSetExternalModuleIndicator, hasProperty, identity, isArray, + IScriptSnapshot, isDeclarationFileName, map, Map, MinimalResolutionCacheHost, ModuleKind, Path, ScriptKind, + ScriptTarget, SourceFile, sourceFileAffectingCompilerOptions, toPath, tracing, updateLanguageServiceSourceFile, +} from "./_namespaces/ts"; + +/** + * The document registry represents a store of SourceFile objects that can be shared between + * multiple LanguageService instances. A LanguageService instance holds on the SourceFile (AST) + * of files in the context. + * SourceFile objects account for most of the memory usage by the language service. Sharing + * the same DocumentRegistry instance between different instances of LanguageService allow + * for more efficient memory utilization since all projects will share at least the library + * file (lib.d.ts). + * + * A more advanced use of the document registry is to serialize sourceFile objects to disk + * and re-hydrate them when needed. + * + * To create a default DocumentRegistry, use createDocumentRegistry to create one, and pass it + * to all subsequent createLanguageService calls. + */ +export interface DocumentRegistry { /** - * The document registry represents a store of SourceFile objects that can be shared between - * multiple LanguageService instances. A LanguageService instance holds on the SourceFile (AST) - * of files in the context. - * SourceFile objects account for most of the memory usage by the language service. Sharing - * the same DocumentRegistry instance between different instances of LanguageService allow - * for more efficient memory utilization since all projects will share at least the library - * file (lib.d.ts). + * Request a stored SourceFile with a given fileName and compilationSettings. + * The first call to acquire will call createLanguageServiceSourceFile to generate + * the SourceFile if was not found in the registry. * - * A more advanced use of the document registry is to serialize sourceFile objects to disk - * and re-hydrate them when needed. + * @param fileName The name of the file requested + * @param compilationSettingsOrHost Some compilation settings like target affects the + * shape of a the resulting SourceFile. This allows the DocumentRegistry to store + * multiple copies of the same file for different compilation settings. A minimal + * resolution cache is needed to fully define a source file's shape when + * the compilation settings include `module: node16`+, so providing a cache host + * object should be preferred. A common host is a language service `ConfiguredProject`. + * @param scriptSnapshot Text of the file. Only used if the file was not found + * in the registry and a new one was created. + * @param version Current version of the file. Only used if the file was not found + * in the registry and a new one was created. + */ + acquireDocument( + fileName: string, + compilationSettingsOrHost: CompilerOptions | MinimalResolutionCacheHost, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind, + sourceFileOptions?: CreateSourceFileOptions | ScriptTarget, + ): SourceFile; + + acquireDocumentWithKey( + fileName: string, + path: Path, + compilationSettingsOrHost: CompilerOptions | MinimalResolutionCacheHost, + key: DocumentRegistryBucketKey, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind, + sourceFileOptions?: CreateSourceFileOptions | ScriptTarget, + ): SourceFile; + + /** + * Request an updated version of an already existing SourceFile with a given fileName + * and compilationSettings. The update will in-turn call updateLanguageServiceSourceFile + * to get an updated SourceFile. * - * To create a default DocumentRegistry, use createDocumentRegistry to create one, and pass it - * to all subsequent createLanguageService calls. + * @param fileName The name of the file requested + * @param compilationSettingsOrHost Some compilation settings like target affects the + * shape of a the resulting SourceFile. This allows the DocumentRegistry to store + * multiple copies of the same file for different compilation settings. A minimal + * resolution cache is needed to fully define a source file's shape when + * the compilation settings include `module: node16`+, so providing a cache host + * object should be preferred. A common host is a language service `ConfiguredProject`. + * @param scriptSnapshot Text of the file. + * @param version Current version of the file. */ - export interface DocumentRegistry { - /** - * Request a stored SourceFile with a given fileName and compilationSettings. - * The first call to acquire will call createLanguageServiceSourceFile to generate - * the SourceFile if was not found in the registry. - * - * @param fileName The name of the file requested - * @param compilationSettingsOrHost Some compilation settings like target affects the - * shape of a the resulting SourceFile. This allows the DocumentRegistry to store - * multiple copies of the same file for different compilation settings. A minimal - * resolution cache is needed to fully define a source file's shape when - * the compilation settings include `module: node16`+, so providing a cache host - * object should be preferred. A common host is a language service `ConfiguredProject`. - * @param scriptSnapshot Text of the file. Only used if the file was not found - * in the registry and a new one was created. - * @param version Current version of the file. Only used if the file was not found - * in the registry and a new one was created. - */ - acquireDocument( - fileName: string, - compilationSettingsOrHost: CompilerOptions | MinimalResolutionCacheHost, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind, - sourceFileOptions?: CreateSourceFileOptions | ScriptTarget, - ): SourceFile; - - acquireDocumentWithKey( - fileName: string, - path: Path, - compilationSettingsOrHost: CompilerOptions | MinimalResolutionCacheHost, - key: DocumentRegistryBucketKey, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind, - sourceFileOptions?: CreateSourceFileOptions | ScriptTarget, - ): SourceFile; - - /** - * Request an updated version of an already existing SourceFile with a given fileName - * and compilationSettings. The update will in-turn call updateLanguageServiceSourceFile - * to get an updated SourceFile. - * - * @param fileName The name of the file requested - * @param compilationSettingsOrHost Some compilation settings like target affects the - * shape of a the resulting SourceFile. This allows the DocumentRegistry to store - * multiple copies of the same file for different compilation settings. A minimal - * resolution cache is needed to fully define a source file's shape when - * the compilation settings include `module: node16`+, so providing a cache host - * object should be preferred. A common host is a language service `ConfiguredProject`. - * @param scriptSnapshot Text of the file. - * @param version Current version of the file. - */ - updateDocument( - fileName: string, - compilationSettingsOrHost: CompilerOptions | MinimalResolutionCacheHost, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind, - sourceFileOptions?: CreateSourceFileOptions | ScriptTarget, - ): SourceFile; - - updateDocumentWithKey( - fileName: string, - path: Path, - compilationSettingsOrHost: CompilerOptions | MinimalResolutionCacheHost, - key: DocumentRegistryBucketKey, - scriptSnapshot: IScriptSnapshot, - version: string, - scriptKind?: ScriptKind, - sourceFileOptions?: CreateSourceFileOptions | ScriptTarget, - ): SourceFile; - - getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey; - /** - * Informs the DocumentRegistry that a file is not needed any longer. - * - * Note: It is not allowed to call release on a SourceFile that was not acquired from - * this registry originally. - * - * @param fileName The name of the file to be released - * @param compilationSettings The compilation settings used to acquire the file - * @param scriptKind The script kind of the file to be released - */ - /**@deprecated pass scriptKind and impliedNodeFormat for correctness */ - releaseDocument(fileName: string, compilationSettings: CompilerOptions, scriptKind?: ScriptKind): void; - /** - * Informs the DocumentRegistry that a file is not needed any longer. - * - * Note: It is not allowed to call release on a SourceFile that was not acquired from - * this registry originally. - * - * @param fileName The name of the file to be released - * @param compilationSettings The compilation settings used to acquire the file - * @param scriptKind The script kind of the file to be released - * @param impliedNodeFormat The implied source file format of the file to be released - */ - releaseDocument(fileName: string, compilationSettings: CompilerOptions, scriptKind: ScriptKind, impliedNodeFormat: SourceFile["impliedNodeFormat"]): void; // eslint-disable-line @typescript-eslint/unified-signatures - /** - * @deprecated pass scriptKind for and impliedNodeFormat correctness */ - releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey, scriptKind?: ScriptKind): void; - releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey, scriptKind: ScriptKind, impliedNodeFormat: SourceFile["impliedNodeFormat"]): void; // eslint-disable-line @typescript-eslint/unified-signatures - - /*@internal*/ - getLanguageServiceRefCounts(path: Path, scriptKind: ScriptKind): [string, number | undefined][]; - - reportStats(): string; - } + updateDocument( + fileName: string, + compilationSettingsOrHost: CompilerOptions | MinimalResolutionCacheHost, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind, + sourceFileOptions?: CreateSourceFileOptions | ScriptTarget, + ): SourceFile; + + updateDocumentWithKey( + fileName: string, + path: Path, + compilationSettingsOrHost: CompilerOptions | MinimalResolutionCacheHost, + key: DocumentRegistryBucketKey, + scriptSnapshot: IScriptSnapshot, + version: string, + scriptKind?: ScriptKind, + sourceFileOptions?: CreateSourceFileOptions | ScriptTarget, + ): SourceFile; + + getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey; + /** + * Informs the DocumentRegistry that a file is not needed any longer. + * + * Note: It is not allowed to call release on a SourceFile that was not acquired from + * this registry originally. + * + * @param fileName The name of the file to be released + * @param compilationSettings The compilation settings used to acquire the file + * @param scriptKind The script kind of the file to be released + * + * @deprecated pass scriptKind and impliedNodeFormat for correctness + */ + releaseDocument(fileName: string, compilationSettings: CompilerOptions, scriptKind?: ScriptKind): void; + /** + * Informs the DocumentRegistry that a file is not needed any longer. + * + * Note: It is not allowed to call release on a SourceFile that was not acquired from + * this registry originally. + * + * @param fileName The name of the file to be released + * @param compilationSettings The compilation settings used to acquire the file + * @param scriptKind The script kind of the file to be released + * @param impliedNodeFormat The implied source file format of the file to be released + */ + releaseDocument(fileName: string, compilationSettings: CompilerOptions, scriptKind: ScriptKind, impliedNodeFormat: SourceFile["impliedNodeFormat"]): void; // eslint-disable-line @typescript-eslint/unified-signatures + /** + * @deprecated pass scriptKind for and impliedNodeFormat correctness */ + releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey, scriptKind?: ScriptKind): void; + releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey, scriptKind: ScriptKind, impliedNodeFormat: SourceFile["impliedNodeFormat"]): void; // eslint-disable-line @typescript-eslint/unified-signatures - /*@internal*/ - export interface ExternalDocumentCache { - setDocument(key: DocumentRegistryBucketKeyWithMode, path: Path, sourceFile: SourceFile): void; - getDocument(key: DocumentRegistryBucketKeyWithMode, path: Path): SourceFile | undefined; - } + /** @internal */ + getLanguageServiceRefCounts(path: Path, scriptKind: ScriptKind): [string, number | undefined][]; - export type DocumentRegistryBucketKey = string & { __bucketKey: any }; + reportStats(): string; +} - interface DocumentRegistryEntry { - sourceFile: SourceFile; +/** @internal */ +export interface ExternalDocumentCache { + setDocument(key: DocumentRegistryBucketKeyWithMode, path: Path, sourceFile: SourceFile): void; + getDocument(key: DocumentRegistryBucketKeyWithMode, path: Path): SourceFile | undefined; +} - // The number of language services that this source file is referenced in. When no more - // language services are referencing the file, then the file can be removed from the - // registry. - languageServiceRefCount: number; - } +export type DocumentRegistryBucketKey = string & { __bucketKey: any }; - type BucketEntry = DocumentRegistryEntry | ESMap; - function isDocumentRegistryEntry(entry: BucketEntry): entry is DocumentRegistryEntry { - return !!(entry as DocumentRegistryEntry).sourceFile; - } +interface DocumentRegistryEntry { + sourceFile: SourceFile; - export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory?: string): DocumentRegistry { - return createDocumentRegistryInternal(useCaseSensitiveFileNames, currentDirectory); - } + // The number of language services that this source file is referenced in. When no more + // language services are referencing the file, then the file can be removed from the + // registry. + languageServiceRefCount: number; +} - /*@internal*/ - export type DocumentRegistryBucketKeyWithMode = string & { __documentRegistryBucketKeyWithMode: any; }; - /*@internal*/ - export function createDocumentRegistryInternal(useCaseSensitiveFileNames?: boolean, currentDirectory = "", externalCache?: ExternalDocumentCache): DocumentRegistry { - // Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have - // for those settings. - const buckets = new Map>(); - const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); - - function reportStats() { - const bucketInfoArray = arrayFrom(buckets.keys()).filter(name => name && name.charAt(0) === "_").map(name => { - const entries = buckets.get(name)!; - const sourceFiles: { name: string; scriptKind: ScriptKind, refCount: number; }[] = []; - entries.forEach((entry, name) => { - if (isDocumentRegistryEntry(entry)) { - sourceFiles.push({ - name, - scriptKind: entry.sourceFile.scriptKind, - refCount: entry.languageServiceRefCount - }); - } - else { - entry.forEach((value, scriptKind) => sourceFiles.push({ name, scriptKind, refCount: value.languageServiceRefCount })); - } - }); - sourceFiles.sort((x, y) => y.refCount - x.refCount); - return { - bucket: name, - sourceFiles - }; - }); - return JSON.stringify(bucketInfoArray, undefined, 2); - } +type BucketEntry = DocumentRegistryEntry | ESMap; +function isDocumentRegistryEntry(entry: BucketEntry): entry is DocumentRegistryEntry { + return !!(entry as DocumentRegistryEntry).sourceFile; +} - function getCompilationSettings(settingsOrHost: CompilerOptions | MinimalResolutionCacheHost) { - if (typeof settingsOrHost.getCompilationSettings === "function") { - return (settingsOrHost as MinimalResolutionCacheHost).getCompilationSettings(); - } - return settingsOrHost as CompilerOptions; - } +export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory?: string): DocumentRegistry { + return createDocumentRegistryInternal(useCaseSensitiveFileNames, currentDirectory); +} - function acquireDocument(fileName: string, compilationSettings: CompilerOptions | MinimalResolutionCacheHost, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind, languageVersionOrOptions?: CreateSourceFileOptions | ScriptTarget): SourceFile { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const key = getKeyForCompilationSettings(getCompilationSettings(compilationSettings)); - return acquireDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind, languageVersionOrOptions); - } +/** @internal */ +export type DocumentRegistryBucketKeyWithMode = string & { __documentRegistryBucketKeyWithMode: any; }; +/** @internal */ +export function createDocumentRegistryInternal(useCaseSensitiveFileNames?: boolean, currentDirectory = "", externalCache?: ExternalDocumentCache): DocumentRegistry { + // Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have + // for those settings. + const buckets = new Map>(); + const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); + + function reportStats() { + const bucketInfoArray = arrayFrom(buckets.keys()).filter(name => name && name.charAt(0) === "_").map(name => { + const entries = buckets.get(name)!; + const sourceFiles: { name: string; scriptKind: ScriptKind, refCount: number; }[] = []; + entries.forEach((entry, name) => { + if (isDocumentRegistryEntry(entry)) { + sourceFiles.push({ + name, + scriptKind: entry.sourceFile.scriptKind, + refCount: entry.languageServiceRefCount + }); + } + else { + entry.forEach((value, scriptKind) => sourceFiles.push({ name, scriptKind, refCount: value.languageServiceRefCount })); + } + }); + sourceFiles.sort((x, y) => y.refCount - x.refCount); + return { + bucket: name, + sourceFiles + }; + }); + return JSON.stringify(bucketInfoArray, undefined, 2); + } - function acquireDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions | MinimalResolutionCacheHost, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind, languageVersionOrOptions?: CreateSourceFileOptions | ScriptTarget): SourceFile { - return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ true, scriptKind, languageVersionOrOptions); + function getCompilationSettings(settingsOrHost: CompilerOptions | MinimalResolutionCacheHost) { + if (typeof settingsOrHost.getCompilationSettings === "function") { + return (settingsOrHost as MinimalResolutionCacheHost).getCompilationSettings(); } + return settingsOrHost as CompilerOptions; + } - function updateDocument(fileName: string, compilationSettings: CompilerOptions | MinimalResolutionCacheHost, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind, languageVersionOrOptions?: CreateSourceFileOptions | ScriptTarget): SourceFile { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const key = getKeyForCompilationSettings(getCompilationSettings(compilationSettings)); - return updateDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind, languageVersionOrOptions); - } + function acquireDocument(fileName: string, compilationSettings: CompilerOptions | MinimalResolutionCacheHost, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind, languageVersionOrOptions?: CreateSourceFileOptions | ScriptTarget): SourceFile { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const key = getKeyForCompilationSettings(getCompilationSettings(compilationSettings)); + return acquireDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind, languageVersionOrOptions); + } - function updateDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions | MinimalResolutionCacheHost, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind, languageVersionOrOptions?: CreateSourceFileOptions | ScriptTarget): SourceFile { - return acquireOrUpdateDocument(fileName, path, getCompilationSettings(compilationSettings), key, scriptSnapshot, version, /*acquiring*/ false, scriptKind, languageVersionOrOptions); - } + function acquireDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions | MinimalResolutionCacheHost, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind, languageVersionOrOptions?: CreateSourceFileOptions | ScriptTarget): SourceFile { + return acquireOrUpdateDocument(fileName, path, compilationSettings, key, scriptSnapshot, version, /*acquiring*/ true, scriptKind, languageVersionOrOptions); + } - function getDocumentRegistryEntry(bucketEntry: BucketEntry, scriptKind: ScriptKind | undefined) { - const entry = isDocumentRegistryEntry(bucketEntry) ? bucketEntry : bucketEntry.get(Debug.checkDefined(scriptKind, "If there are more than one scriptKind's for same document the scriptKind should be provided")); - Debug.assert(scriptKind === undefined || !entry || entry.sourceFile.scriptKind === scriptKind, `Script kind should match provided ScriptKind:${scriptKind} and sourceFile.scriptKind: ${entry?.sourceFile.scriptKind}, !entry: ${!entry}`); - return entry; - } + function updateDocument(fileName: string, compilationSettings: CompilerOptions | MinimalResolutionCacheHost, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind, languageVersionOrOptions?: CreateSourceFileOptions | ScriptTarget): SourceFile { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const key = getKeyForCompilationSettings(getCompilationSettings(compilationSettings)); + return updateDocumentWithKey(fileName, path, compilationSettings, key, scriptSnapshot, version, scriptKind, languageVersionOrOptions); + } - function acquireOrUpdateDocument( - fileName: string, - path: Path, - compilationSettingsOrHost: CompilerOptions | MinimalResolutionCacheHost, - key: DocumentRegistryBucketKey, - scriptSnapshot: IScriptSnapshot, - version: string, - acquiring: boolean, - scriptKind: ScriptKind | undefined, - languageVersionOrOptions: CreateSourceFileOptions | ScriptTarget | undefined, - ): SourceFile { - scriptKind = ensureScriptKind(fileName, scriptKind); - const compilationSettings = getCompilationSettings(compilationSettingsOrHost); - const host: MinimalResolutionCacheHost | undefined = compilationSettingsOrHost === compilationSettings ? undefined : compilationSettingsOrHost as MinimalResolutionCacheHost; - const scriptTarget = scriptKind === ScriptKind.JSON ? ScriptTarget.JSON : getEmitScriptTarget(compilationSettings); - const sourceFileOptions: CreateSourceFileOptions = typeof languageVersionOrOptions === "object" ? - languageVersionOrOptions : - { - languageVersion: scriptTarget, - impliedNodeFormat: host && getImpliedNodeFormatForFile(path, host.getCompilerHost?.()?.getModuleResolutionCache?.()?.getPackageJsonInfoCache(), host, compilationSettings), - setExternalModuleIndicator: getSetExternalModuleIndicator(compilationSettings) - }; - sourceFileOptions.languageVersion = scriptTarget; - const oldBucketCount = buckets.size; - const keyWithMode = getDocumentRegistryBucketKeyWithMode(key, sourceFileOptions.impliedNodeFormat); - const bucket = getOrUpdate(buckets, keyWithMode, () => new Map()); - if (tracing) { - if (buckets.size > oldBucketCount) { - // It is interesting, but not definitively problematic if a build requires multiple document registry buckets - - // perhaps they are for two projects that don't have any overlap. - // Bonus: these events can help us interpret the more interesting event below. - tracing.instant(tracing.Phase.Session, "createdDocumentRegistryBucket", { configFilePath: compilationSettings.configFilePath, key: keyWithMode }); - } + function updateDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions | MinimalResolutionCacheHost, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind, languageVersionOrOptions?: CreateSourceFileOptions | ScriptTarget): SourceFile { + return acquireOrUpdateDocument(fileName, path, getCompilationSettings(compilationSettings), key, scriptSnapshot, version, /*acquiring*/ false, scriptKind, languageVersionOrOptions); + } - // It is fairly suspicious to have one path in two buckets - you'd expect dependencies to have similar configurations. - // If this occurs unexpectedly, the fix is likely to synchronize the project settings. - // Skip .d.ts files to reduce noise (should also cover most of node_modules). - const otherBucketKey = !isDeclarationFileName(path) && - forEachEntry(buckets, (bucket, bucketKey) => bucketKey !== keyWithMode && bucket.has(path) && bucketKey); - if (otherBucketKey) { - tracing.instant(tracing.Phase.Session, "documentRegistryBucketOverlap", { path, key1: otherBucketKey, key2: keyWithMode }); - } + function getDocumentRegistryEntry(bucketEntry: BucketEntry, scriptKind: ScriptKind | undefined) { + const entry = isDocumentRegistryEntry(bucketEntry) ? bucketEntry : bucketEntry.get(Debug.checkDefined(scriptKind, "If there are more than one scriptKind's for same document the scriptKind should be provided")); + Debug.assert(scriptKind === undefined || !entry || entry.sourceFile.scriptKind === scriptKind, `Script kind should match provided ScriptKind:${scriptKind} and sourceFile.scriptKind: ${entry?.sourceFile.scriptKind}, !entry: ${!entry}`); + return entry; + } + + function acquireOrUpdateDocument( + fileName: string, + path: Path, + compilationSettingsOrHost: CompilerOptions | MinimalResolutionCacheHost, + key: DocumentRegistryBucketKey, + scriptSnapshot: IScriptSnapshot, + version: string, + acquiring: boolean, + scriptKind: ScriptKind | undefined, + languageVersionOrOptions: CreateSourceFileOptions | ScriptTarget | undefined, + ): SourceFile { + scriptKind = ensureScriptKind(fileName, scriptKind); + const compilationSettings = getCompilationSettings(compilationSettingsOrHost); + const host: MinimalResolutionCacheHost | undefined = compilationSettingsOrHost === compilationSettings ? undefined : compilationSettingsOrHost as MinimalResolutionCacheHost; + const scriptTarget = scriptKind === ScriptKind.JSON ? ScriptTarget.JSON : getEmitScriptTarget(compilationSettings); + const sourceFileOptions: CreateSourceFileOptions = typeof languageVersionOrOptions === "object" ? + languageVersionOrOptions : + { + languageVersion: scriptTarget, + impliedNodeFormat: host && getImpliedNodeFormatForFile(path, host.getCompilerHost?.()?.getModuleResolutionCache?.()?.getPackageJsonInfoCache(), host, compilationSettings), + setExternalModuleIndicator: getSetExternalModuleIndicator(compilationSettings) + }; + sourceFileOptions.languageVersion = scriptTarget; + const oldBucketCount = buckets.size; + const keyWithMode = getDocumentRegistryBucketKeyWithMode(key, sourceFileOptions.impliedNodeFormat); + const bucket = getOrUpdate(buckets, keyWithMode, () => new Map()); + if (tracing) { + if (buckets.size > oldBucketCount) { + // It is interesting, but not definitively problematic if a build requires multiple document registry buckets - + // perhaps they are for two projects that don't have any overlap. + // Bonus: these events can help us interpret the more interesting event below. + tracing.instant(tracing.Phase.Session, "createdDocumentRegistryBucket", { configFilePath: compilationSettings.configFilePath, key: keyWithMode }); } - const bucketEntry = bucket.get(path); - let entry = bucketEntry && getDocumentRegistryEntry(bucketEntry, scriptKind); - if (!entry && externalCache) { - const sourceFile = externalCache.getDocument(keyWithMode, path); - if (sourceFile) { - Debug.assert(acquiring); - entry = { - sourceFile, - languageServiceRefCount: 0 - }; - setBucketEntry(); - } + // It is fairly suspicious to have one path in two buckets - you'd expect dependencies to have similar configurations. + // If this occurs unexpectedly, the fix is likely to synchronize the project settings. + // Skip .d.ts files to reduce noise (should also cover most of node_modules). + const otherBucketKey = !isDeclarationFileName(path) && + forEachEntry(buckets, (bucket, bucketKey) => bucketKey !== keyWithMode && bucket.has(path) && bucketKey); + if (otherBucketKey) { + tracing.instant(tracing.Phase.Session, "documentRegistryBucketOverlap", { path, key1: otherBucketKey, key2: keyWithMode }); } + } - if (!entry) { - // Have never seen this file with these settings. Create a new source file for it. - const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, sourceFileOptions, version, /*setNodeParents*/ false, scriptKind); - if (externalCache) { - externalCache.setDocument(keyWithMode, path, sourceFile); - } + const bucketEntry = bucket.get(path); + let entry = bucketEntry && getDocumentRegistryEntry(bucketEntry, scriptKind); + if (!entry && externalCache) { + const sourceFile = externalCache.getDocument(keyWithMode, path); + if (sourceFile) { + Debug.assert(acquiring); entry = { sourceFile, - languageServiceRefCount: 1, + languageServiceRefCount: 0 }; setBucketEntry(); } - else { - // We have an entry for this file. However, it may be for a different version of - // the script snapshot. If so, update it appropriately. Otherwise, we can just - // return it as is. - if (entry.sourceFile.version !== version) { - entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, - scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot!)); // TODO: GH#18217 - if (externalCache) { - externalCache.setDocument(keyWithMode, path, entry.sourceFile); - } - } + } - // If we're acquiring, then this is the first time this LS is asking for this document. - // Increase our ref count so we know there's another LS using the document. If we're - // not acquiring, then that means the LS is 'updating' the file instead, and that means - // it has already acquired the document previously. As such, we do not need to increase - // the ref count. - if (acquiring) { - entry.languageServiceRefCount++; - } + if (!entry) { + // Have never seen this file with these settings. Create a new source file for it. + const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, sourceFileOptions, version, /*setNodeParents*/ false, scriptKind); + if (externalCache) { + externalCache.setDocument(keyWithMode, path, sourceFile); } - Debug.assert(entry.languageServiceRefCount !== 0); - - return entry.sourceFile; - - function setBucketEntry() { - if (!bucketEntry) { - bucket.set(path, entry!); - } - else if (isDocumentRegistryEntry(bucketEntry)) { - const scriptKindMap = new Map(); - scriptKindMap.set(bucketEntry.sourceFile.scriptKind, bucketEntry); - scriptKindMap.set(scriptKind!, entry!); - bucket.set(path, scriptKindMap); - } - else { - bucketEntry.set(scriptKind!, entry!); + entry = { + sourceFile, + languageServiceRefCount: 1, + }; + setBucketEntry(); + } + else { + // We have an entry for this file. However, it may be for a different version of + // the script snapshot. If so, update it appropriately. Otherwise, we can just + // return it as is. + if (entry.sourceFile.version !== version) { + entry.sourceFile = updateLanguageServiceSourceFile(entry.sourceFile, scriptSnapshot, version, + scriptSnapshot.getChangeRange(entry.sourceFile.scriptSnapshot!)); // TODO: GH#18217 + if (externalCache) { + externalCache.setDocument(keyWithMode, path, entry.sourceFile); } } - } - function releaseDocument(fileName: string, compilationSettings: CompilerOptions, scriptKind?: ScriptKind, impliedNodeFormat?: SourceFile["impliedNodeFormat"]): void { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const key = getKeyForCompilationSettings(compilationSettings); - return releaseDocumentWithKey(path, key, scriptKind, impliedNodeFormat); + // If we're acquiring, then this is the first time this LS is asking for this document. + // Increase our ref count so we know there's another LS using the document. If we're + // not acquiring, then that means the LS is 'updating' the file instead, and that means + // it has already acquired the document previously. As such, we do not need to increase + // the ref count. + if (acquiring) { + entry.languageServiceRefCount++; + } } + Debug.assert(entry.languageServiceRefCount !== 0); - function releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey, scriptKind?: ScriptKind, impliedNodeFormat?: SourceFile["impliedNodeFormat"]): void { - const bucket = Debug.checkDefined(buckets.get(getDocumentRegistryBucketKeyWithMode(key, impliedNodeFormat))); - const bucketEntry = bucket.get(path)!; - const entry = getDocumentRegistryEntry(bucketEntry, scriptKind)!; - entry.languageServiceRefCount--; + return entry.sourceFile; - Debug.assert(entry.languageServiceRefCount >= 0); - if (entry.languageServiceRefCount === 0) { - if (isDocumentRegistryEntry(bucketEntry)) { - bucket.delete(path); - } - else { - bucketEntry.delete(scriptKind!); - if (bucketEntry.size === 1) { - bucket.set(path, firstDefinedIterator(bucketEntry.values(), identity)!); - } - } + function setBucketEntry() { + if (!bucketEntry) { + bucket.set(path, entry!); + } + else if (isDocumentRegistryEntry(bucketEntry)) { + const scriptKindMap = new Map(); + scriptKindMap.set(bucketEntry.sourceFile.scriptKind, bucketEntry); + scriptKindMap.set(scriptKind!, entry!); + bucket.set(path, scriptKindMap); + } + else { + bucketEntry.set(scriptKind!, entry!); } } + } - function getLanguageServiceRefCounts(path: Path, scriptKind: ScriptKind) { - return arrayFrom(buckets.entries(), ([key, bucket]): [string, number | undefined] => { - const bucketEntry = bucket.get(path); - const entry = bucketEntry && getDocumentRegistryEntry(bucketEntry, scriptKind); - return [key, entry && entry.languageServiceRefCount]; - }); - } - - return { - acquireDocument, - acquireDocumentWithKey, - updateDocument, - updateDocumentWithKey, - releaseDocument, - releaseDocumentWithKey, - getLanguageServiceRefCounts, - reportStats, - getKeyForCompilationSettings - }; + function releaseDocument(fileName: string, compilationSettings: CompilerOptions, scriptKind?: ScriptKind, impliedNodeFormat?: SourceFile["impliedNodeFormat"]): void { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const key = getKeyForCompilationSettings(compilationSettings); + return releaseDocumentWithKey(path, key, scriptKind, impliedNodeFormat); } - function compilerOptionValueToString(value: unknown): string { - if (value === null || typeof value !== "object") { // eslint-disable-line no-null/no-null - return "" + value; - } - if (isArray(value)) { - return `[${map(value, e => compilerOptionValueToString(e))?.join(",")}]`; - } - let str = "{"; - for (const key in value) { - if (hasProperty(value, key)) { - str += `${key}: ${compilerOptionValueToString((value as any)[key])}`; + function releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey, scriptKind?: ScriptKind, impliedNodeFormat?: SourceFile["impliedNodeFormat"]): void { + const bucket = Debug.checkDefined(buckets.get(getDocumentRegistryBucketKeyWithMode(key, impliedNodeFormat))); + const bucketEntry = bucket.get(path)!; + const entry = getDocumentRegistryEntry(bucketEntry, scriptKind)!; + entry.languageServiceRefCount--; + + Debug.assert(entry.languageServiceRefCount >= 0); + if (entry.languageServiceRefCount === 0) { + if (isDocumentRegistryEntry(bucketEntry)) { + bucket.delete(path); + } + else { + bucketEntry.delete(scriptKind!); + if (bucketEntry.size === 1) { + bucket.set(path, firstDefinedIterator(bucketEntry.values(), identity)!); + } } } - return str + "}"; } - function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey { - return sourceFileAffectingCompilerOptions.map(option => compilerOptionValueToString(getCompilerOptionValue(settings, option))).join("|") + (settings.pathsBasePath ? `|${settings.pathsBasePath}` : undefined) as DocumentRegistryBucketKey; + function getLanguageServiceRefCounts(path: Path, scriptKind: ScriptKind) { + return arrayFrom(buckets.entries(), ([key, bucket]): [string, number | undefined] => { + const bucketEntry = bucket.get(path); + const entry = bucketEntry && getDocumentRegistryEntry(bucketEntry, scriptKind); + return [key, entry && entry.languageServiceRefCount]; + }); } - function getDocumentRegistryBucketKeyWithMode(key: DocumentRegistryBucketKey, mode: ModuleKind.ESNext | ModuleKind.CommonJS | undefined) { - return (mode ? `${key}|${mode}` : key) as DocumentRegistryBucketKeyWithMode; + return { + acquireDocument, + acquireDocumentWithKey, + updateDocument, + updateDocumentWithKey, + releaseDocument, + releaseDocumentWithKey, + getLanguageServiceRefCounts, + reportStats, + getKeyForCompilationSettings + }; +} + +function compilerOptionValueToString(value: unknown): string { + if (value === null || typeof value !== "object") { // eslint-disable-line no-null/no-null + return "" + value; + } + if (isArray(value)) { + return `[${map(value, e => compilerOptionValueToString(e))?.join(",")}]`; } + let str = "{"; + for (const key in value) { + if (hasProperty(value, key)) { + str += `${key}: ${compilerOptionValueToString((value as any)[key])}`; + } + } + return str + "}"; +} + +function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey { + return sourceFileAffectingCompilerOptions.map(option => compilerOptionValueToString(getCompilerOptionValue(settings, option))).join("|") + (settings.pathsBasePath ? `|${settings.pathsBasePath}` : undefined) as DocumentRegistryBucketKey; +} + +function getDocumentRegistryBucketKeyWithMode(key: DocumentRegistryBucketKey, mode: ModuleKind.ESNext | ModuleKind.CommonJS | undefined) { + return (mode ? `${key}|${mode}` : key) as DocumentRegistryBucketKeyWithMode; } diff --git a/src/services/exportAsModule.ts b/src/services/exportAsModule.ts deleted file mode 100644 index 6760e9c38c2d0..0000000000000 --- a/src/services/exportAsModule.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Here we expose the TypeScript services as an external module -// so that it may be consumed easily like a node module. -// @ts-ignore -/* @internal */ declare const module: { exports: {} }; -if (typeof module !== "undefined" && module.exports) { - module.exports = ts; -} \ No newline at end of file diff --git a/src/services/exportInfoMap.ts b/src/services/exportInfoMap.ts index 5e927b3ff8afb..b9725329056ad 100644 --- a/src/services/exportInfoMap.ts +++ b/src/services/exportInfoMap.ts @@ -1,494 +1,515 @@ -/*@internal*/ -namespace ts { - export const enum ImportKind { - Named, - Default, - Namespace, - CommonJS, - } +import { + __String, addToSeen, arrayIsEqualTo, CancellationToken, CompilerOptions, consumesNodeCoreModules, createMultiMap, + Debug, emptyArray, findIndex, firstDefined, forEachAncestorDirectory, forEachEntry, getBaseFileName, + GetCanonicalFileName, getDirectoryPath, getLocalSymbolForExportDefault, getNameForExportedSymbol, + getNamesForExportedSymbol, getNodeModulePathParts, getPackageNameFromTypesPackageName, getPatternFromSpec, + getRegexFromPattern, getSymbolId, hostGetCanonicalFileName, hostUsesCaseSensitiveFileNames, InternalSymbolName, + isExportAssignment, isExportSpecifier, isExternalModuleNameRelative, isExternalModuleSymbol, + isExternalOrCommonJsModule, isIdentifier, isKnownSymbol, isNonGlobalAmbientModule, isPrivateIdentifierSymbol, + LanguageServiceHost, Map, mapDefined, ModuleSpecifierCache, ModuleSpecifierResolutionHost, moduleSpecifiers, + nodeModulesPathPart, PackageJsonImportFilter, Path, Program, skipAlias, skipOuterExpressions, SourceFile, + startsWith, Statement, stringContains, stripQuotes, Symbol, SymbolFlags, timestamp, tryCast, TypeChecker, + unescapeLeadingUnderscores, unmangleScopedPackageName, UserPreferences, +} from "./_namespaces/ts"; + +/** @internal */ +export const enum ImportKind { + Named, + Default, + Namespace, + CommonJS, +} - export const enum ExportKind { - Named, - Default, - ExportEquals, - UMD, - } +/** @internal */ +export const enum ExportKind { + Named, + Default, + ExportEquals, + UMD, +} - export interface SymbolExportInfo { - readonly symbol: Symbol; - readonly moduleSymbol: Symbol; - /** Set if `moduleSymbol` is an external module, not an ambient module */ - moduleFileName: string | undefined; - exportKind: ExportKind; - targetFlags: SymbolFlags; - /** True if export was only found via the package.json AutoImportProvider (for telemetry). */ - isFromPackageJson: boolean; - } +/** @internal */ +export interface SymbolExportInfo { + readonly symbol: Symbol; + readonly moduleSymbol: Symbol; + /** Set if `moduleSymbol` is an external module, not an ambient module */ + moduleFileName: string | undefined; + exportKind: ExportKind; + targetFlags: SymbolFlags; + /** True if export was only found via the package.json AutoImportProvider (for telemetry). */ + isFromPackageJson: boolean; +} - interface CachedSymbolExportInfo { - // Used to rehydrate `symbol` and `moduleSymbol` when transient - id: number; - symbolName: string; - capitalizedSymbolName: string | undefined; - symbolTableKey: __String; - moduleName: string; - moduleFile: SourceFile | undefined; - packageName: string | undefined; - - // SymbolExportInfo, but optional symbols - readonly symbol: Symbol | undefined; - readonly moduleSymbol: Symbol | undefined; - moduleFileName: string | undefined; - exportKind: ExportKind; - targetFlags: SymbolFlags; - isFromPackageJson: boolean; - } +interface CachedSymbolExportInfo { + // Used to rehydrate `symbol` and `moduleSymbol` when transient + id: number; + symbolName: string; + capitalizedSymbolName: string | undefined; + symbolTableKey: __String; + moduleName: string; + moduleFile: SourceFile | undefined; + packageName: string | undefined; + + // SymbolExportInfo, but optional symbols + readonly symbol: Symbol | undefined; + readonly moduleSymbol: Symbol | undefined; + moduleFileName: string | undefined; + exportKind: ExportKind; + targetFlags: SymbolFlags; + isFromPackageJson: boolean; +} - export interface ExportInfoMap { - isUsableByFile(importingFile: Path): boolean; - clear(): void; - add(importingFile: Path, symbol: Symbol, key: __String, moduleSymbol: Symbol, moduleFile: SourceFile | undefined, exportKind: ExportKind, isFromPackageJson: boolean, checker: TypeChecker): void; - get(importingFile: Path, key: string): readonly SymbolExportInfo[] | undefined; - search(importingFile: Path, preferCapitalized: boolean, matches: (name: string, targetFlags: SymbolFlags) => boolean, action: (info: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean, key: string) => T | undefined): T | undefined; - releaseSymbols(): void; - isEmpty(): boolean; - /** @returns Whether the change resulted in the cache being cleared */ - onFileChanged(oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean): boolean; - } +/** @internal */ +export interface ExportInfoMap { + isUsableByFile(importingFile: Path): boolean; + clear(): void; + add(importingFile: Path, symbol: Symbol, key: __String, moduleSymbol: Symbol, moduleFile: SourceFile | undefined, exportKind: ExportKind, isFromPackageJson: boolean, checker: TypeChecker): void; + get(importingFile: Path, key: string): readonly SymbolExportInfo[] | undefined; + search(importingFile: Path, preferCapitalized: boolean, matches: (name: string, targetFlags: SymbolFlags) => boolean, action: (info: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean, key: string) => T | undefined): T | undefined; + releaseSymbols(): void; + isEmpty(): boolean; + /** @returns Whether the change resulted in the cache being cleared */ + onFileChanged(oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean): boolean; +} - export interface CacheableExportInfoMapHost { - getCurrentProgram(): Program | undefined; - getPackageJsonAutoImportProvider(): Program | undefined; - getGlobalTypingsCacheLocation(): string | undefined; - } +/** @internal */ +export interface CacheableExportInfoMapHost { + getCurrentProgram(): Program | undefined; + getPackageJsonAutoImportProvider(): Program | undefined; + getGlobalTypingsCacheLocation(): string | undefined; +} - export function createCacheableExportInfoMap(host: CacheableExportInfoMapHost): ExportInfoMap { - let exportInfoId = 1; - const exportInfo = createMultiMap(); - const symbols = new Map(); - /** - * Key: node_modules package name (no @types). - * Value: path to deepest node_modules folder seen that is - * both visible to `usableByFileName` and contains the package. - * - * Later, we can see if a given SymbolExportInfo is shadowed by - * a another installation of the same package in a deeper - * node_modules folder by seeing if its path starts with the - * value stored here. - */ - const packages = new Map(); - let usableByFileName: Path | undefined; - const cache: ExportInfoMap = { - isUsableByFile: importingFile => importingFile === usableByFileName, - isEmpty: () => !exportInfo.size, - clear: () => { - exportInfo.clear(); - symbols.clear(); - usableByFileName = undefined; - }, - add: (importingFile, symbol, symbolTableKey, moduleSymbol, moduleFile, exportKind, isFromPackageJson, checker) => { - if (importingFile !== usableByFileName) { - cache.clear(); - usableByFileName = importingFile; - } +/** @internal */ +export function createCacheableExportInfoMap(host: CacheableExportInfoMapHost): ExportInfoMap { + let exportInfoId = 1; + const exportInfo = createMultiMap(); + const symbols = new Map(); + /** + * Key: node_modules package name (no @types). + * Value: path to deepest node_modules folder seen that is + * both visible to `usableByFileName` and contains the package. + * + * Later, we can see if a given SymbolExportInfo is shadowed by + * a another installation of the same package in a deeper + * node_modules folder by seeing if its path starts with the + * value stored here. + */ + const packages = new Map(); + let usableByFileName: Path | undefined; + const cache: ExportInfoMap = { + isUsableByFile: importingFile => importingFile === usableByFileName, + isEmpty: () => !exportInfo.size, + clear: () => { + exportInfo.clear(); + symbols.clear(); + usableByFileName = undefined; + }, + add: (importingFile, symbol, symbolTableKey, moduleSymbol, moduleFile, exportKind, isFromPackageJson, checker) => { + if (importingFile !== usableByFileName) { + cache.clear(); + usableByFileName = importingFile; + } - let packageName; - if (moduleFile) { - const nodeModulesPathParts = getNodeModulePathParts(moduleFile.fileName); - if (nodeModulesPathParts) { - const { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex } = nodeModulesPathParts; - packageName = unmangleScopedPackageName(getPackageNameFromTypesPackageName(moduleFile.fileName.substring(topLevelPackageNameIndex + 1, packageRootIndex))); - if (startsWith(importingFile, moduleFile.path.substring(0, topLevelNodeModulesIndex))) { - const prevDeepestNodeModulesPath = packages.get(packageName); - const nodeModulesPath = moduleFile.fileName.substring(0, topLevelPackageNameIndex + 1); - if (prevDeepestNodeModulesPath) { - const prevDeepestNodeModulesIndex = prevDeepestNodeModulesPath.indexOf(nodeModulesPathPart); - if (topLevelNodeModulesIndex > prevDeepestNodeModulesIndex) { - packages.set(packageName, nodeModulesPath); - } - } - else { + let packageName; + if (moduleFile) { + const nodeModulesPathParts = getNodeModulePathParts(moduleFile.fileName); + if (nodeModulesPathParts) { + const { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex } = nodeModulesPathParts; + packageName = unmangleScopedPackageName(getPackageNameFromTypesPackageName(moduleFile.fileName.substring(topLevelPackageNameIndex + 1, packageRootIndex))); + if (startsWith(importingFile, moduleFile.path.substring(0, topLevelNodeModulesIndex))) { + const prevDeepestNodeModulesPath = packages.get(packageName); + const nodeModulesPath = moduleFile.fileName.substring(0, topLevelPackageNameIndex + 1); + if (prevDeepestNodeModulesPath) { + const prevDeepestNodeModulesIndex = prevDeepestNodeModulesPath.indexOf(nodeModulesPathPart); + if (topLevelNodeModulesIndex > prevDeepestNodeModulesIndex) { packages.set(packageName, nodeModulesPath); } } + else { + packages.set(packageName, nodeModulesPath); + } } } + } - const isDefault = exportKind === ExportKind.Default; - const namedSymbol = isDefault && getLocalSymbolForExportDefault(symbol) || symbol; - // 1. A named export must be imported by its key in `moduleSymbol.exports` or `moduleSymbol.members`. - // 2. A re-export merged with an export from a module augmentation can result in `symbol` - // being an external module symbol; the name it is re-exported by will be `symbolTableKey` - // (which comes from the keys of `moduleSymbol.exports`.) - // 3. Otherwise, we have a default/namespace import that can be imported by any name, and - // `symbolTableKey` will be something undesirable like `export=` or `default`, so we try to - // get a better name. - const names = exportKind === ExportKind.Named || isExternalModuleSymbol(namedSymbol) - ? unescapeLeadingUnderscores(symbolTableKey) - : getNamesForExportedSymbol(namedSymbol, /*scriptTarget*/ undefined); - - const symbolName = typeof names === "string" ? names : names[0]; - const capitalizedSymbolName = typeof names === "string" ? undefined : names[1]; - - const moduleName = stripQuotes(moduleSymbol.name); - const id = exportInfoId++; - const target = skipAlias(symbol, checker); - const storedSymbol = symbol.flags & SymbolFlags.Transient ? undefined : symbol; - const storedModuleSymbol = moduleSymbol.flags & SymbolFlags.Transient ? undefined : moduleSymbol; - if (!storedSymbol || !storedModuleSymbol) symbols.set(id, [symbol, moduleSymbol]); - - exportInfo.add(key(symbolName, symbol, isExternalModuleNameRelative(moduleName) ? undefined : moduleName, checker), { - id, - symbolTableKey, - symbolName, - capitalizedSymbolName, - moduleName, - moduleFile, - moduleFileName: moduleFile?.fileName, - packageName, - exportKind, - targetFlags: target.flags, - isFromPackageJson, - symbol: storedSymbol, - moduleSymbol: storedModuleSymbol, - }); - }, - get: (importingFile, key) => { - if (importingFile !== usableByFileName) return; - const result = exportInfo.get(key); - return result?.map(rehydrateCachedInfo); - }, - search: (importingFile, preferCapitalized, matches, action) => { - if (importingFile !== usableByFileName) return; - return forEachEntry(exportInfo, (info, key) => { - const { symbolName, ambientModuleName } = parseKey(key); - const name = preferCapitalized && info[0].capitalizedSymbolName || symbolName; - if (matches(name, info[0].targetFlags)) { - const rehydrated = info.map(rehydrateCachedInfo); - const filtered = rehydrated.filter((r, i) => isNotShadowedByDeeperNodeModulesPackage(r, info[i].packageName)); - if (filtered.length) { - const res = action(filtered, name, !!ambientModuleName, key); - if (res !== undefined) return res; - } + const isDefault = exportKind === ExportKind.Default; + const namedSymbol = isDefault && getLocalSymbolForExportDefault(symbol) || symbol; + // 1. A named export must be imported by its key in `moduleSymbol.exports` or `moduleSymbol.members`. + // 2. A re-export merged with an export from a module augmentation can result in `symbol` + // being an external module symbol; the name it is re-exported by will be `symbolTableKey` + // (which comes from the keys of `moduleSymbol.exports`.) + // 3. Otherwise, we have a default/namespace import that can be imported by any name, and + // `symbolTableKey` will be something undesirable like `export=` or `default`, so we try to + // get a better name. + const names = exportKind === ExportKind.Named || isExternalModuleSymbol(namedSymbol) + ? unescapeLeadingUnderscores(symbolTableKey) + : getNamesForExportedSymbol(namedSymbol, /*scriptTarget*/ undefined); + + const symbolName = typeof names === "string" ? names : names[0]; + const capitalizedSymbolName = typeof names === "string" ? undefined : names[1]; + + const moduleName = stripQuotes(moduleSymbol.name); + const id = exportInfoId++; + const target = skipAlias(symbol, checker); + const storedSymbol = symbol.flags & SymbolFlags.Transient ? undefined : symbol; + const storedModuleSymbol = moduleSymbol.flags & SymbolFlags.Transient ? undefined : moduleSymbol; + if (!storedSymbol || !storedModuleSymbol) symbols.set(id, [symbol, moduleSymbol]); + + exportInfo.add(key(symbolName, symbol, isExternalModuleNameRelative(moduleName) ? undefined : moduleName, checker), { + id, + symbolTableKey, + symbolName, + capitalizedSymbolName, + moduleName, + moduleFile, + moduleFileName: moduleFile?.fileName, + packageName, + exportKind, + targetFlags: target.flags, + isFromPackageJson, + symbol: storedSymbol, + moduleSymbol: storedModuleSymbol, + }); + }, + get: (importingFile, key) => { + if (importingFile !== usableByFileName) return; + const result = exportInfo.get(key); + return result?.map(rehydrateCachedInfo); + }, + search: (importingFile, preferCapitalized, matches, action) => { + if (importingFile !== usableByFileName) return; + return forEachEntry(exportInfo, (info, key) => { + const { symbolName, ambientModuleName } = parseKey(key); + const name = preferCapitalized && info[0].capitalizedSymbolName || symbolName; + if (matches(name, info[0].targetFlags)) { + const rehydrated = info.map(rehydrateCachedInfo); + const filtered = rehydrated.filter((r, i) => isNotShadowedByDeeperNodeModulesPackage(r, info[i].packageName)); + if (filtered.length) { + const res = action(filtered, name, !!ambientModuleName, key); + if (res !== undefined) return res; } - }); - }, - releaseSymbols: () => { - symbols.clear(); - }, - onFileChanged: (oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean) => { - if (fileIsGlobalOnly(oldSourceFile) && fileIsGlobalOnly(newSourceFile)) { - // File is purely global; doesn't affect export map - return false; } - if ( - usableByFileName && usableByFileName !== newSourceFile.path || - // If ATA is enabled, auto-imports uses existing imports to guess whether you want auto-imports from node. - // Adding or removing imports from node could change the outcome of that guess, so could change the suggestions list. - typeAcquisitionEnabled && consumesNodeCoreModules(oldSourceFile) !== consumesNodeCoreModules(newSourceFile) || - // Module agumentation and ambient module changes can add or remove exports available to be auto-imported. - // Changes elsewhere in the file can change the *type* of an export in a module augmentation, - // but type info is gathered in getCompletionEntryDetails, which doesn’t use the cache. - !arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations) || - !ambientModuleDeclarationsAreEqual(oldSourceFile, newSourceFile) - ) { - cache.clear(); - return true; - } - usableByFileName = newSourceFile.path; + }); + }, + releaseSymbols: () => { + symbols.clear(); + }, + onFileChanged: (oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean) => { + if (fileIsGlobalOnly(oldSourceFile) && fileIsGlobalOnly(newSourceFile)) { + // File is purely global; doesn't affect export map return false; - }, - }; - if (Debug.isDebugging) { - Object.defineProperty(cache, "__cache", { get: () => exportInfo }); - } - return cache; - - function rehydrateCachedInfo(info: CachedSymbolExportInfo): SymbolExportInfo { - if (info.symbol && info.moduleSymbol) return info as SymbolExportInfo; - const { id, exportKind, targetFlags, isFromPackageJson, moduleFileName } = info; - const [cachedSymbol, cachedModuleSymbol] = symbols.get(id) || emptyArray; - if (cachedSymbol && cachedModuleSymbol) { - return { - symbol: cachedSymbol, - moduleSymbol: cachedModuleSymbol, - moduleFileName, - exportKind, - targetFlags, - isFromPackageJson, - }; } - const checker = (isFromPackageJson - ? host.getPackageJsonAutoImportProvider()! - : host.getCurrentProgram()!).getTypeChecker(); - const moduleSymbol = info.moduleSymbol || cachedModuleSymbol || Debug.checkDefined(info.moduleFile - ? checker.getMergedSymbol(info.moduleFile.symbol) - : checker.tryFindAmbientModule(info.moduleName)); - const symbol = info.symbol || cachedSymbol || Debug.checkDefined(exportKind === ExportKind.ExportEquals - ? checker.resolveExternalModuleSymbol(moduleSymbol) - : checker.tryGetMemberInModuleExportsAndProperties(unescapeLeadingUnderscores(info.symbolTableKey), moduleSymbol), - `Could not find symbol '${info.symbolName}' by key '${info.symbolTableKey}' in module ${moduleSymbol.name}`); - symbols.set(id, [symbol, moduleSymbol]); + if ( + usableByFileName && usableByFileName !== newSourceFile.path || + // If ATA is enabled, auto-imports uses existing imports to guess whether you want auto-imports from node. + // Adding or removing imports from node could change the outcome of that guess, so could change the suggestions list. + typeAcquisitionEnabled && consumesNodeCoreModules(oldSourceFile) !== consumesNodeCoreModules(newSourceFile) || + // Module agumentation and ambient module changes can add or remove exports available to be auto-imported. + // Changes elsewhere in the file can change the *type* of an export in a module augmentation, + // but type info is gathered in getCompletionEntryDetails, which doesn’t use the cache. + !arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations) || + !ambientModuleDeclarationsAreEqual(oldSourceFile, newSourceFile) + ) { + cache.clear(); + return true; + } + usableByFileName = newSourceFile.path; + return false; + }, + }; + if (Debug.isDebugging) { + Object.defineProperty(cache, "__cache", { get: () => exportInfo }); + } + return cache; + + function rehydrateCachedInfo(info: CachedSymbolExportInfo): SymbolExportInfo { + if (info.symbol && info.moduleSymbol) return info as SymbolExportInfo; + const { id, exportKind, targetFlags, isFromPackageJson, moduleFileName } = info; + const [cachedSymbol, cachedModuleSymbol] = symbols.get(id) || emptyArray; + if (cachedSymbol && cachedModuleSymbol) { return { - symbol, - moduleSymbol, + symbol: cachedSymbol, + moduleSymbol: cachedModuleSymbol, moduleFileName, exportKind, targetFlags, isFromPackageJson, }; } + const checker = (isFromPackageJson + ? host.getPackageJsonAutoImportProvider()! + : host.getCurrentProgram()!).getTypeChecker(); + const moduleSymbol = info.moduleSymbol || cachedModuleSymbol || Debug.checkDefined(info.moduleFile + ? checker.getMergedSymbol(info.moduleFile.symbol) + : checker.tryFindAmbientModule(info.moduleName)); + const symbol = info.symbol || cachedSymbol || Debug.checkDefined(exportKind === ExportKind.ExportEquals + ? checker.resolveExternalModuleSymbol(moduleSymbol) + : checker.tryGetMemberInModuleExportsAndProperties(unescapeLeadingUnderscores(info.symbolTableKey), moduleSymbol), + `Could not find symbol '${info.symbolName}' by key '${info.symbolTableKey}' in module ${moduleSymbol.name}`); + symbols.set(id, [symbol, moduleSymbol]); + return { + symbol, + moduleSymbol, + moduleFileName, + exportKind, + targetFlags, + isFromPackageJson, + }; + } - function key(importedName: string, symbol: Symbol, ambientModuleName: string | undefined, checker: TypeChecker): string { - const moduleKey = ambientModuleName || ""; - return `${importedName}|${getSymbolId(skipAlias(symbol, checker))}|${moduleKey}`; - } + function key(importedName: string, symbol: Symbol, ambientModuleName: string | undefined, checker: TypeChecker): string { + const moduleKey = ambientModuleName || ""; + return `${importedName}|${getSymbolId(skipAlias(symbol, checker))}|${moduleKey}`; + } - function parseKey(key: string) { - const symbolName = key.substring(0, key.indexOf("|")); - const moduleKey = key.substring(key.lastIndexOf("|") + 1); - const ambientModuleName = moduleKey === "" ? undefined : moduleKey; - return { symbolName, ambientModuleName }; - } + function parseKey(key: string) { + const symbolName = key.substring(0, key.indexOf("|")); + const moduleKey = key.substring(key.lastIndexOf("|") + 1); + const ambientModuleName = moduleKey === "" ? undefined : moduleKey; + return { symbolName, ambientModuleName }; + } - function fileIsGlobalOnly(file: SourceFile) { - return !file.commonJsModuleIndicator && !file.externalModuleIndicator && !file.moduleAugmentations && !file.ambientModuleNames; - } + function fileIsGlobalOnly(file: SourceFile) { + return !file.commonJsModuleIndicator && !file.externalModuleIndicator && !file.moduleAugmentations && !file.ambientModuleNames; + } - function ambientModuleDeclarationsAreEqual(oldSourceFile: SourceFile, newSourceFile: SourceFile) { - if (!arrayIsEqualTo(oldSourceFile.ambientModuleNames, newSourceFile.ambientModuleNames)) { + function ambientModuleDeclarationsAreEqual(oldSourceFile: SourceFile, newSourceFile: SourceFile) { + if (!arrayIsEqualTo(oldSourceFile.ambientModuleNames, newSourceFile.ambientModuleNames)) { + return false; + } + let oldFileStatementIndex = -1; + let newFileStatementIndex = -1; + for (const ambientModuleName of newSourceFile.ambientModuleNames) { + const isMatchingModuleDeclaration = (node: Statement) => isNonGlobalAmbientModule(node) && node.name.text === ambientModuleName; + oldFileStatementIndex = findIndex(oldSourceFile.statements, isMatchingModuleDeclaration, oldFileStatementIndex + 1); + newFileStatementIndex = findIndex(newSourceFile.statements, isMatchingModuleDeclaration, newFileStatementIndex + 1); + if (oldSourceFile.statements[oldFileStatementIndex] !== newSourceFile.statements[newFileStatementIndex]) { return false; } - let oldFileStatementIndex = -1; - let newFileStatementIndex = -1; - for (const ambientModuleName of newSourceFile.ambientModuleNames) { - const isMatchingModuleDeclaration = (node: Statement) => isNonGlobalAmbientModule(node) && node.name.text === ambientModuleName; - oldFileStatementIndex = findIndex(oldSourceFile.statements, isMatchingModuleDeclaration, oldFileStatementIndex + 1); - newFileStatementIndex = findIndex(newSourceFile.statements, isMatchingModuleDeclaration, newFileStatementIndex + 1); - if (oldSourceFile.statements[oldFileStatementIndex] !== newSourceFile.statements[newFileStatementIndex]) { - return false; - } - } - return true; - } - - function isNotShadowedByDeeperNodeModulesPackage(info: SymbolExportInfo, packageName: string | undefined) { - if (!packageName || !info.moduleFileName) return true; - const typingsCacheLocation = host.getGlobalTypingsCacheLocation(); - if (typingsCacheLocation && startsWith(info.moduleFileName, typingsCacheLocation)) return true; - const packageDeepestNodeModulesPath = packages.get(packageName); - return !packageDeepestNodeModulesPath || startsWith(info.moduleFileName, packageDeepestNodeModulesPath); } + return true; } - export function isImportableFile( - program: Program, - from: SourceFile, - to: SourceFile, - preferences: UserPreferences, - packageJsonFilter: PackageJsonImportFilter | undefined, - moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost, - moduleSpecifierCache: ModuleSpecifierCache | undefined, - ): boolean { - if (from === to) return false; - const cachedResult = moduleSpecifierCache?.get(from.path, to.path, preferences, {}); - if (cachedResult?.isBlockedByPackageJsonDependencies !== undefined) { - return !cachedResult.isBlockedByPackageJsonDependencies; - } + function isNotShadowedByDeeperNodeModulesPackage(info: SymbolExportInfo, packageName: string | undefined) { + if (!packageName || !info.moduleFileName) return true; + const typingsCacheLocation = host.getGlobalTypingsCacheLocation(); + if (typingsCacheLocation && startsWith(info.moduleFileName, typingsCacheLocation)) return true; + const packageDeepestNodeModulesPath = packages.get(packageName); + return !packageDeepestNodeModulesPath || startsWith(info.moduleFileName, packageDeepestNodeModulesPath); + } +} - const getCanonicalFileName = hostGetCanonicalFileName(moduleSpecifierResolutionHost); - const globalTypingsCache = moduleSpecifierResolutionHost.getGlobalTypingsCacheLocation?.(); - const hasImportablePath = !!moduleSpecifiers.forEachFileNameOfModule( - from.fileName, - to.fileName, - moduleSpecifierResolutionHost, - /*preferSymlinks*/ false, - toPath => { - const toFile = program.getSourceFile(toPath); - // Determine to import using toPath only if toPath is what we were looking at - // or there doesnt exist the file in the program by the symlink - return (toFile === to || !toFile) && - isImportablePath(from.fileName, toPath, getCanonicalFileName, globalTypingsCache); - } - ); +/** @internal */ +export function isImportableFile( + program: Program, + from: SourceFile, + to: SourceFile, + preferences: UserPreferences, + packageJsonFilter: PackageJsonImportFilter | undefined, + moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost, + moduleSpecifierCache: ModuleSpecifierCache | undefined, +): boolean { + if (from === to) return false; + const cachedResult = moduleSpecifierCache?.get(from.path, to.path, preferences, {}); + if (cachedResult?.isBlockedByPackageJsonDependencies !== undefined) { + return !cachedResult.isBlockedByPackageJsonDependencies; + } - if (packageJsonFilter) { - const isAutoImportable = hasImportablePath && packageJsonFilter.allowsImportingSourceFile(to, moduleSpecifierResolutionHost); - moduleSpecifierCache?.setBlockedByPackageJsonDependencies(from.path, to.path, preferences, {}, !isAutoImportable); - return isAutoImportable; + const getCanonicalFileName = hostGetCanonicalFileName(moduleSpecifierResolutionHost); + const globalTypingsCache = moduleSpecifierResolutionHost.getGlobalTypingsCacheLocation?.(); + const hasImportablePath = !!moduleSpecifiers.forEachFileNameOfModule( + from.fileName, + to.fileName, + moduleSpecifierResolutionHost, + /*preferSymlinks*/ false, + toPath => { + const toFile = program.getSourceFile(toPath); + // Determine to import using toPath only if toPath is what we were looking at + // or there doesnt exist the file in the program by the symlink + return (toFile === to || !toFile) && + isImportablePath(from.fileName, toPath, getCanonicalFileName, globalTypingsCache); } + ); - return hasImportablePath; + if (packageJsonFilter) { + const isAutoImportable = hasImportablePath && packageJsonFilter.allowsImportingSourceFile(to, moduleSpecifierResolutionHost); + moduleSpecifierCache?.setBlockedByPackageJsonDependencies(from.path, to.path, preferences, {}, !isAutoImportable); + return isAutoImportable; } - /** - * Don't include something from a `node_modules` that isn't actually reachable by a global import. - * A relative import to node_modules is usually a bad idea. - */ - function isImportablePath(fromPath: string, toPath: string, getCanonicalFileName: GetCanonicalFileName, globalCachePath?: string): boolean { - // If it's in a `node_modules` but is not reachable from here via a global import, don't bother. - const toNodeModules = forEachAncestorDirectory(toPath, ancestor => getBaseFileName(ancestor) === "node_modules" ? ancestor : undefined); - const toNodeModulesParent = toNodeModules && getDirectoryPath(getCanonicalFileName(toNodeModules)); - return toNodeModulesParent === undefined - || startsWith(getCanonicalFileName(fromPath), toNodeModulesParent) - || (!!globalCachePath && startsWith(getCanonicalFileName(globalCachePath), toNodeModulesParent)); - } + return hasImportablePath; +} - export function forEachExternalModuleToImportFrom( - program: Program, - host: LanguageServiceHost, - preferences: UserPreferences, - useAutoImportProvider: boolean, - cb: (module: Symbol, moduleFile: SourceFile | undefined, program: Program, isFromPackageJson: boolean) => void, - ) { - const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); - const excludePatterns = preferences.autoImportFileExcludePatterns && mapDefined(preferences.autoImportFileExcludePatterns, spec => { - // The client is expected to send rooted path specs since we don't know - // what directory a relative path is relative to. - const pattern = getPatternFromSpec(spec, "", "exclude"); - return pattern ? getRegexFromPattern(pattern, useCaseSensitiveFileNames) : undefined; - }); +/** + * Don't include something from a `node_modules` that isn't actually reachable by a global import. + * A relative import to node_modules is usually a bad idea. + */ +function isImportablePath(fromPath: string, toPath: string, getCanonicalFileName: GetCanonicalFileName, globalCachePath?: string): boolean { + // If it's in a `node_modules` but is not reachable from here via a global import, don't bother. + const toNodeModules = forEachAncestorDirectory(toPath, ancestor => getBaseFileName(ancestor) === "node_modules" ? ancestor : undefined); + const toNodeModulesParent = toNodeModules && getDirectoryPath(getCanonicalFileName(toNodeModules)); + return toNodeModulesParent === undefined + || startsWith(getCanonicalFileName(fromPath), toNodeModulesParent) + || (!!globalCachePath && startsWith(getCanonicalFileName(globalCachePath), toNodeModulesParent)); +} - forEachExternalModule(program.getTypeChecker(), program.getSourceFiles(), excludePatterns, (module, file) => cb(module, file, program, /*isFromPackageJson*/ false)); - const autoImportProvider = useAutoImportProvider && host.getPackageJsonAutoImportProvider?.(); - if (autoImportProvider) { - const start = timestamp(); - forEachExternalModule(autoImportProvider.getTypeChecker(), autoImportProvider.getSourceFiles(), excludePatterns, (module, file) => cb(module, file, autoImportProvider, /*isFromPackageJson*/ true)); - host.log?.(`forEachExternalModuleToImportFrom autoImportProvider: ${timestamp() - start}`); - } +/** @internal */ +export function forEachExternalModuleToImportFrom( + program: Program, + host: LanguageServiceHost, + preferences: UserPreferences, + useAutoImportProvider: boolean, + cb: (module: Symbol, moduleFile: SourceFile | undefined, program: Program, isFromPackageJson: boolean) => void, +) { + const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); + const excludePatterns = preferences.autoImportFileExcludePatterns && mapDefined(preferences.autoImportFileExcludePatterns, spec => { + // The client is expected to send rooted path specs since we don't know + // what directory a relative path is relative to. + const pattern = getPatternFromSpec(spec, "", "exclude"); + return pattern ? getRegexFromPattern(pattern, useCaseSensitiveFileNames) : undefined; + }); + + forEachExternalModule(program.getTypeChecker(), program.getSourceFiles(), excludePatterns, (module, file) => cb(module, file, program, /*isFromPackageJson*/ false)); + const autoImportProvider = useAutoImportProvider && host.getPackageJsonAutoImportProvider?.(); + if (autoImportProvider) { + const start = timestamp(); + forEachExternalModule(autoImportProvider.getTypeChecker(), autoImportProvider.getSourceFiles(), excludePatterns, (module, file) => cb(module, file, autoImportProvider, /*isFromPackageJson*/ true)); + host.log?.(`forEachExternalModuleToImportFrom autoImportProvider: ${timestamp() - start}`); } +} - function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly SourceFile[], excludePatterns: readonly RegExp[] | undefined, cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) { - const isExcluded = (fileName: string) => excludePatterns?.some(p => p.test(fileName)); - for (const ambient of checker.getAmbientModules()) { - if (!stringContains(ambient.name, "*") && !(excludePatterns && ambient.declarations?.every(d => isExcluded(d.getSourceFile().fileName)))) { - cb(ambient, /*sourceFile*/ undefined); - } +function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly SourceFile[], excludePatterns: readonly RegExp[] | undefined, cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) { + const isExcluded = (fileName: string) => excludePatterns?.some(p => p.test(fileName)); + for (const ambient of checker.getAmbientModules()) { + if (!stringContains(ambient.name, "*") && !(excludePatterns && ambient.declarations?.every(d => isExcluded(d.getSourceFile().fileName)))) { + cb(ambient, /*sourceFile*/ undefined); } - for (const sourceFile of allSourceFiles) { - if (isExternalOrCommonJsModule(sourceFile) && !isExcluded(sourceFile.fileName)) { - cb(checker.getMergedSymbol(sourceFile.symbol), sourceFile); - } + } + for (const sourceFile of allSourceFiles) { + if (isExternalOrCommonJsModule(sourceFile) && !isExcluded(sourceFile.fileName)) { + cb(checker.getMergedSymbol(sourceFile.symbol), sourceFile); } } +} - export function getExportInfoMap(importingFile: SourceFile, host: LanguageServiceHost, program: Program, preferences: UserPreferences, cancellationToken: CancellationToken | undefined): ExportInfoMap { - const start = timestamp(); - // Pulling the AutoImportProvider project will trigger its updateGraph if pending, - // which will invalidate the export map cache if things change, so pull it before - // checking the cache. - host.getPackageJsonAutoImportProvider?.(); - const cache = host.getCachedExportInfoMap?.() || createCacheableExportInfoMap({ - getCurrentProgram: () => program, - getPackageJsonAutoImportProvider: () => host.getPackageJsonAutoImportProvider?.(), - getGlobalTypingsCacheLocation: () => host.getGlobalTypingsCacheLocation?.(), - }); - - if (cache.isUsableByFile(importingFile.path)) { - host.log?.("getExportInfoMap: cache hit"); - return cache; - } +/** @internal */ +export function getExportInfoMap(importingFile: SourceFile, host: LanguageServiceHost, program: Program, preferences: UserPreferences, cancellationToken: CancellationToken | undefined): ExportInfoMap { + const start = timestamp(); + // Pulling the AutoImportProvider project will trigger its updateGraph if pending, + // which will invalidate the export map cache if things change, so pull it before + // checking the cache. + host.getPackageJsonAutoImportProvider?.(); + const cache = host.getCachedExportInfoMap?.() || createCacheableExportInfoMap({ + getCurrentProgram: () => program, + getPackageJsonAutoImportProvider: () => host.getPackageJsonAutoImportProvider?.(), + getGlobalTypingsCacheLocation: () => host.getGlobalTypingsCacheLocation?.(), + }); + + if (cache.isUsableByFile(importingFile.path)) { + host.log?.("getExportInfoMap: cache hit"); + return cache; + } - host.log?.("getExportInfoMap: cache miss or empty; calculating new results"); - const compilerOptions = program.getCompilerOptions(); - let moduleCount = 0; - try { - forEachExternalModuleToImportFrom(program, host, preferences, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => { - if (++moduleCount % 100 === 0) cancellationToken?.throwIfCancellationRequested(); - const seenExports = new Map<__String, true>(); - const checker = program.getTypeChecker(); - const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions); - // Note: I think we shouldn't actually see resolved module symbols here, but weird merges - // can cause it to happen: see 'completionsImport_mergedReExport.ts' - if (defaultInfo && isImportableSymbol(defaultInfo.symbol, checker)) { + host.log?.("getExportInfoMap: cache miss or empty; calculating new results"); + const compilerOptions = program.getCompilerOptions(); + let moduleCount = 0; + try { + forEachExternalModuleToImportFrom(program, host, preferences, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => { + if (++moduleCount % 100 === 0) cancellationToken?.throwIfCancellationRequested(); + const seenExports = new Map<__String, true>(); + const checker = program.getTypeChecker(); + const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions); + // Note: I think we shouldn't actually see resolved module symbols here, but weird merges + // can cause it to happen: see 'completionsImport_mergedReExport.ts' + if (defaultInfo && isImportableSymbol(defaultInfo.symbol, checker)) { + cache.add( + importingFile.path, + defaultInfo.symbol, + defaultInfo.exportKind === ExportKind.Default ? InternalSymbolName.Default : InternalSymbolName.ExportEquals, + moduleSymbol, + moduleFile, + defaultInfo.exportKind, + isFromPackageJson, + checker); + } + checker.forEachExportAndPropertyOfModule(moduleSymbol, (exported, key) => { + if (exported !== defaultInfo?.symbol && isImportableSymbol(exported, checker) && addToSeen(seenExports, key)) { cache.add( importingFile.path, - defaultInfo.symbol, - defaultInfo.exportKind === ExportKind.Default ? InternalSymbolName.Default : InternalSymbolName.ExportEquals, + exported, + key, moduleSymbol, moduleFile, - defaultInfo.exportKind, + ExportKind.Named, isFromPackageJson, checker); } - checker.forEachExportAndPropertyOfModule(moduleSymbol, (exported, key) => { - if (exported !== defaultInfo?.symbol && isImportableSymbol(exported, checker) && addToSeen(seenExports, key)) { - cache.add( - importingFile.path, - exported, - key, - moduleSymbol, - moduleFile, - ExportKind.Named, - isFromPackageJson, - checker); - } - }); }); - } - catch (err) { - // Ensure cache is reset if operation is cancelled - cache.clear(); - throw err; - } - - host.log?.(`getExportInfoMap: done in ${timestamp() - start} ms`); - return cache; + }); } - - export function getDefaultLikeExportInfo(moduleSymbol: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions) { - const exported = getDefaultLikeExportWorker(moduleSymbol, checker); - if (!exported) return undefined; - const { symbol, exportKind } = exported; - const info = getDefaultExportInfoWorker(symbol, checker, compilerOptions); - return info && { symbol, exportKind, ...info }; + catch (err) { + // Ensure cache is reset if operation is cancelled + cache.clear(); + throw err; } - function isImportableSymbol(symbol: Symbol, checker: TypeChecker) { - return !checker.isUndefinedSymbol(symbol) && !checker.isUnknownSymbol(symbol) && !isKnownSymbol(symbol) && !isPrivateIdentifierSymbol(symbol); - } + host.log?.(`getExportInfoMap: done in ${timestamp() - start} ms`); + return cache; +} - function getDefaultLikeExportWorker(moduleSymbol: Symbol, checker: TypeChecker): { readonly symbol: Symbol, readonly exportKind: ExportKind } | undefined { - const exportEquals = checker.resolveExternalModuleSymbol(moduleSymbol); - if (exportEquals !== moduleSymbol) return { symbol: exportEquals, exportKind: ExportKind.ExportEquals }; - const defaultExport = checker.tryGetMemberInModuleExports(InternalSymbolName.Default, moduleSymbol); - if (defaultExport) return { symbol: defaultExport, exportKind: ExportKind.Default }; - } +/** @internal */ +export function getDefaultLikeExportInfo(moduleSymbol: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions) { + const exported = getDefaultLikeExportWorker(moduleSymbol, checker); + if (!exported) return undefined; + const { symbol, exportKind } = exported; + const info = getDefaultExportInfoWorker(symbol, checker, compilerOptions); + return info && { symbol, exportKind, ...info }; +} - function getDefaultExportInfoWorker(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly symbolForMeaning: Symbol, readonly name: string } | undefined { - const localSymbol = getLocalSymbolForExportDefault(defaultExport); - if (localSymbol) return { symbolForMeaning: localSymbol, name: localSymbol.name }; - - const name = getNameForExportDefault(defaultExport); - if (name !== undefined) return { symbolForMeaning: defaultExport, name }; - - if (defaultExport.flags & SymbolFlags.Alias) { - const aliased = checker.getImmediateAliasedSymbol(defaultExport); - if (aliased && aliased.parent) { - // - `aliased` will be undefined if the module is exporting an unresolvable name, - // but we can still offer completions for it. - // - `aliased.parent` will be undefined if the module is exporting `globalThis.something`, - // or another expression that resolves to a global. - return getDefaultExportInfoWorker(aliased, checker, compilerOptions); - } - } +function isImportableSymbol(symbol: Symbol, checker: TypeChecker) { + return !checker.isUndefinedSymbol(symbol) && !checker.isUnknownSymbol(symbol) && !isKnownSymbol(symbol) && !isPrivateIdentifierSymbol(symbol); +} - if (defaultExport.escapedName !== InternalSymbolName.Default && - defaultExport.escapedName !== InternalSymbolName.ExportEquals) { - return { symbolForMeaning: defaultExport, name: defaultExport.getName() }; +function getDefaultLikeExportWorker(moduleSymbol: Symbol, checker: TypeChecker): { readonly symbol: Symbol, readonly exportKind: ExportKind } | undefined { + const exportEquals = checker.resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals !== moduleSymbol) return { symbol: exportEquals, exportKind: ExportKind.ExportEquals }; + const defaultExport = checker.tryGetMemberInModuleExports(InternalSymbolName.Default, moduleSymbol); + if (defaultExport) return { symbol: defaultExport, exportKind: ExportKind.Default }; +} + +function getDefaultExportInfoWorker(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly symbolForMeaning: Symbol, readonly name: string } | undefined { + const localSymbol = getLocalSymbolForExportDefault(defaultExport); + if (localSymbol) return { symbolForMeaning: localSymbol, name: localSymbol.name }; + + const name = getNameForExportDefault(defaultExport); + if (name !== undefined) return { symbolForMeaning: defaultExport, name }; + + if (defaultExport.flags & SymbolFlags.Alias) { + const aliased = checker.getImmediateAliasedSymbol(defaultExport); + if (aliased && aliased.parent) { + // - `aliased` will be undefined if the module is exporting an unresolvable name, + // but we can still offer completions for it. + // - `aliased.parent` will be undefined if the module is exporting `globalThis.something`, + // or another expression that resolves to a global. + return getDefaultExportInfoWorker(aliased, checker, compilerOptions); } - return { symbolForMeaning: defaultExport, name: getNameForExportedSymbol(defaultExport, compilerOptions.target) }; } - function getNameForExportDefault(symbol: Symbol): string | undefined { - return symbol.declarations && firstDefined(symbol.declarations, declaration => { - if (isExportAssignment(declaration)) { - return tryCast(skipOuterExpressions(declaration.expression), isIdentifier)?.text; - } - else if (isExportSpecifier(declaration)) { - Debug.assert(declaration.name.text === InternalSymbolName.Default, "Expected the specifier to be a default export"); - return declaration.propertyName && declaration.propertyName.text; - } - }); + if (defaultExport.escapedName !== InternalSymbolName.Default && + defaultExport.escapedName !== InternalSymbolName.ExportEquals) { + return { symbolForMeaning: defaultExport, name: defaultExport.getName() }; } + return { symbolForMeaning: defaultExport, name: getNameForExportedSymbol(defaultExport, compilerOptions.target) }; +} + +function getNameForExportDefault(symbol: Symbol): string | undefined { + return symbol.declarations && firstDefined(symbol.declarations, declaration => { + if (isExportAssignment(declaration)) { + return tryCast(skipOuterExpressions(declaration.expression), isIdentifier)?.text; + } + else if (isExportSpecifier(declaration)) { + Debug.assert(declaration.name.text === InternalSymbolName.Default, "Expected the specifier to be a default export"); + return declaration.propertyName && declaration.propertyName.text; + } + }); } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index d11a447b1428b..5ef8f6436b66e 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1,2402 +1,2484 @@ -/* @internal */ -namespace ts.FindAllReferences { - export interface SymbolAndEntries { - readonly definition: Definition | undefined; - readonly references: readonly Entry[]; - } - - export const enum DefinitionKind { Symbol, Label, Keyword, This, String, TripleSlashReference } - export type Definition = - | { readonly type: DefinitionKind.Symbol; readonly symbol: Symbol } - | { readonly type: DefinitionKind.Label; readonly node: Identifier } - | { readonly type: DefinitionKind.Keyword; readonly node: Node } - | { readonly type: DefinitionKind.This; readonly node: Node } - | { readonly type: DefinitionKind.String; readonly node: StringLiteralLike } - | { readonly type: DefinitionKind.TripleSlashReference; readonly reference: FileReference, readonly file: SourceFile }; - - export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal } - export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal; - export type Entry = NodeEntry | SpanEntry; - export interface ContextWithStartAndEndNode { - start: Node; - end: Node; - } - export type ContextNode = Node | ContextWithStartAndEndNode; - export interface NodeEntry { - readonly kind: NodeEntryKind; - readonly node: Node; - readonly context?: ContextNode; - } - export interface SpanEntry { - readonly kind: EntryKind.Span; - readonly fileName: string; - readonly textSpan: TextSpan; - } - export function nodeEntry(node: Node, kind: NodeEntryKind = EntryKind.Node): NodeEntry { - return { - kind, - node: (node as NamedDeclaration).name || node, - context: getContextNodeForNodeEntry(node) - }; - } +import { + __String, addToSeen, append, AssignmentDeclarationKind, BinaryExpression, BindingElement, Block, CallExpression, + CancellationToken, cast, CheckFlags, ClassLikeDeclaration, climbPastPropertyAccess, compareValues, + ConstructorDeclaration, contains, createQueue, createTextSpan, createTextSpanFromBounds, createTextSpanFromRange, + Debug, Declaration, displayPart, DocumentSpan, emptyArray, emptyOptions, escapeLeadingUnderscores, ESMap, + ExportSpecifier, Expression, FileIncludeReason, FileReference, filter, find, findAncestor, findChildOfKind, + findIndex, first, firstDefined, firstOrUndefined, flatMap, forEachChild, forEachReturnStatement, ForInOrOfStatement, + FunctionDeclaration, FunctionExpression, FunctionLikeDeclaration, GetAccessorDeclaration, + getAdjustedReferenceLocation, getAdjustedRenameLocation, getAllSuperTypeNodes, getAncestor, + getAssignmentDeclarationKind, getCheckFlags, getContainerNode, getContainingObjectLiteralElement, + getContextualTypeFromParentOrAncestorTypeNode, getDeclarationFromName, getDeclarationOfKind, + getEffectiveModifierFlags, getLocalSymbolForExportDefault, getMeaningFromDeclaration, getMeaningFromLocation, + getModeForUsageLocation, getNameOfDeclaration, getNameTable, getNextJSDocCommentLocation, getNodeId, getNodeKind, + getPropertySymbolFromBindingElement, getPropertySymbolsFromContextualType, getReferencedFileLocation, + getSuperContainer, getSymbolId, getSyntacticModifierFlags, getTargetLabel, getTextOfNode, getThisContainer, + getTouchingPropertyName, GoToDefinition, hasEffectiveModifier, hasInitializer, hasSyntacticModifier, hasType, + HighlightSpan, HighlightSpanKind, Identifier, ImplementationLocation, InterfaceDeclaration, InternalSymbolName, + isAccessExpression, isArrayLiteralOrObjectLiteralDestructuringPattern, isAssertionExpression, isBinaryExpression, + isBindableObjectDefinePropertyCall, isBindingElement, isBreakOrContinueStatement, isCallExpression, + isCallExpressionTarget, isCatchClause, isClassLike, isClassStaticBlockDeclaration, isComputedPropertyName, + isConstructorDeclaration, isDeclaration, isDeclarationName, isExportAssignment, isExportSpecifier, + isExpressionOfExternalModuleImportEqualsDeclaration, isExpressionStatement, isExpressionWithTypeArguments, + isExternalModule, isExternalModuleSymbol, isExternalOrCommonJsModule, isForInOrOfStatement, isFunctionExpression, + isFunctionLike, isFunctionLikeDeclaration, isIdentifier, isIdentifierPart, isImportMeta, isImportOrExportSpecifier, + isImportSpecifier, isImportTypeNode, isInJSFile, isInNonReferenceComment, isInString, isInterfaceDeclaration, + isJSDocMemberName, isJSDocTag, isJsxClosingElement, isJsxOpeningElement, isJsxSelfClosingElement, + isJumpStatementTarget, isLabeledStatement, isLabelOfLabeledStatement, isLiteralComputedPropertyDeclarationName, + isLiteralNameOfPropertyDeclarationOrIndexAccess, isLiteralTypeNode, isMethodOrAccessor, isModuleDeclaration, + isModuleExportsAccessExpression, isModuleOrEnumDeclaration, isModuleSpecifierLike, isNameOfModuleDeclaration, + isNamespaceExportDeclaration, isNewExpressionTarget, isNoSubstitutionTemplateLiteral, + isObjectBindingElementWithoutPropertyName, isObjectLiteralExpression, isObjectLiteralMethod, isParameter, + isParameterPropertyDeclaration, isPrivateIdentifierClassElementDeclaration, isPropertyAccessExpression, + isQualifiedName, isReferencedFile, isReferenceFileLocation, isRightSideOfPropertyAccess, + isShorthandPropertyAssignment, isSourceFile, isStatement, isStatic, isStaticModifier, isStringLiteralLike, + isSuperProperty, isThis, isTypeAliasDeclaration, isTypeElement, isTypeKeyword, isTypeLiteralNode, isTypeNode, + isTypeOperatorNode, isUnionTypeNode, isVariableDeclarationInitializedToBareOrAccessedRequire, + isVariableDeclarationList, isVariableLike, isVariableStatement, isVoidExpression, isWriteAccess, JSDocTag, map, Map, + mapDefined, MethodDeclaration, ModifierFlags, ModuleDeclaration, MultiMap, NamedDeclaration, Node, NodeFlags, + nodeSeenTracker, NumericLiteral, ParameterDeclaration, ParenthesizedExpression, Path, PrivateIdentifier, Program, + PropertyAccessExpression, PropertyAssignment, PropertyDeclaration, punctuationPart, Push, rangeIsOnSingleLine, + ReadonlySet, ReferencedSymbol, ReferencedSymbolDefinitionInfo, ReferencedSymbolEntry, ReferenceEntry, + RenameLocation, ScriptElementKind, ScriptTarget, SemanticMeaning, Set, SetAccessorDeclaration, SignatureDeclaration, + skipAlias, some, SourceFile, Statement, StringLiteral, StringLiteralLike, stripQuotes, Symbol, SymbolDisplay, + SymbolDisplayPart, SymbolDisplayPartKind, SymbolFlags, SymbolId, symbolName, SyntaxKind, textPart, TextSpan, + tokenToString, tryAddToSet, tryCast, tryGetClassExtendingExpressionWithTypeArguments, + tryGetImportFromModuleSpecifier, TypeChecker, VariableDeclaration, +} from "./_namespaces/ts"; +import { + createImportTracker, ExportInfo, ExportKind, findModuleReferences, getExportInfo, getImportOrExportSymbol, + ImportExport, ImportsResult, ImportTracker, ModuleReference, +} from "./_namespaces/ts.FindAllReferences"; + +/** @internal */ +export interface SymbolAndEntries { + readonly definition: Definition | undefined; + readonly references: readonly Entry[]; +} - export function isContextWithStartAndEndNode(node: ContextNode): node is ContextWithStartAndEndNode { - return node && (node as Node).kind === undefined; - } +/** @internal */ +export const enum DefinitionKind { Symbol, Label, Keyword, This, String, TripleSlashReference } +/** @internal */ +export type Definition = + | { readonly type: DefinitionKind.Symbol; readonly symbol: Symbol } + | { readonly type: DefinitionKind.Label; readonly node: Identifier } + | { readonly type: DefinitionKind.Keyword; readonly node: Node } + | { readonly type: DefinitionKind.This; readonly node: Node } + | { readonly type: DefinitionKind.String; readonly node: StringLiteralLike } + | { readonly type: DefinitionKind.TripleSlashReference; readonly reference: FileReference, readonly file: SourceFile }; + +/** @internal */ +export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal } +/** @internal */ +export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal; +/** @internal */ +export type Entry = NodeEntry | SpanEntry; +/** @internal */ +export interface ContextWithStartAndEndNode { + start: Node; + end: Node; +} +/** @internal */ +export type ContextNode = Node | ContextWithStartAndEndNode; +/** @internal */ +export interface NodeEntry { + readonly kind: NodeEntryKind; + readonly node: Node; + readonly context?: ContextNode; +} +/** @internal */ +export interface SpanEntry { + readonly kind: EntryKind.Span; + readonly fileName: string; + readonly textSpan: TextSpan; +} +/** @internal */ +export function nodeEntry(node: Node, kind: NodeEntryKind = EntryKind.Node): NodeEntry { + return { + kind, + node: (node as NamedDeclaration).name || node, + context: getContextNodeForNodeEntry(node) + }; +} - function getContextNodeForNodeEntry(node: Node): ContextNode | undefined { - if (isDeclaration(node)) { - return getContextNode(node); - } +/** @internal */ +export function isContextWithStartAndEndNode(node: ContextNode): node is ContextWithStartAndEndNode { + return node && (node as Node).kind === undefined; +} - if (!node.parent) return undefined; +function getContextNodeForNodeEntry(node: Node): ContextNode | undefined { + if (isDeclaration(node)) { + return getContextNode(node); + } - if (!isDeclaration(node.parent) && !isExportAssignment(node.parent)) { - // Special property assignment in javascript - if (isInJSFile(node)) { - const binaryExpression = isBinaryExpression(node.parent) ? - node.parent : - isAccessExpression(node.parent) && - isBinaryExpression(node.parent.parent) && - node.parent.parent.left === node.parent ? - node.parent.parent : - undefined; - if (binaryExpression && getAssignmentDeclarationKind(binaryExpression) !== AssignmentDeclarationKind.None) { - return getContextNode(binaryExpression); - } - } + if (!node.parent) return undefined; + + if (!isDeclaration(node.parent) && !isExportAssignment(node.parent)) { + // Special property assignment in javascript + if (isInJSFile(node)) { + const binaryExpression = isBinaryExpression(node.parent) ? + node.parent : + isAccessExpression(node.parent) && + isBinaryExpression(node.parent.parent) && + node.parent.parent.left === node.parent ? + node.parent.parent : + undefined; + if (binaryExpression && getAssignmentDeclarationKind(binaryExpression) !== AssignmentDeclarationKind.None) { + return getContextNode(binaryExpression); + } + } + + // Jsx Tags + if (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) { + return node.parent.parent; + } + else if (isJsxSelfClosingElement(node.parent) || + isLabeledStatement(node.parent) || + isBreakOrContinueStatement(node.parent)) { + return node.parent; + } + else if (isStringLiteralLike(node)) { + const validImport = tryGetImportFromModuleSpecifier(node); + if (validImport) { + const declOrStatement = findAncestor(validImport, node => + isDeclaration(node) || + isStatement(node) || + isJSDocTag(node) + )! as NamedDeclaration | Statement | JSDocTag; + return isDeclaration(declOrStatement) ? + getContextNode(declOrStatement) : + declOrStatement; + } + } + + // Handle computed property name + const propertyName = findAncestor(node, isComputedPropertyName); + return propertyName ? + getContextNode(propertyName.parent) : + undefined; + } - // Jsx Tags - if (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) { - return node.parent.parent; - } - else if (isJsxSelfClosingElement(node.parent) || - isLabeledStatement(node.parent) || - isBreakOrContinueStatement(node.parent)) { - return node.parent; - } - else if (isStringLiteralLike(node)) { - const validImport = tryGetImportFromModuleSpecifier(node); - if (validImport) { - const declOrStatement = findAncestor(validImport, node => - isDeclaration(node) || - isStatement(node) || - isJSDocTag(node) - )! as NamedDeclaration | Statement | JSDocTag; - return isDeclaration(declOrStatement) ? - getContextNode(declOrStatement) : - declOrStatement; - } - } + if (node.parent.name === node || // node is name of declaration, use parent + isConstructorDeclaration(node.parent) || + isExportAssignment(node.parent) || + // Property name of the import export specifier or binding pattern, use parent + ((isImportOrExportSpecifier(node.parent) || isBindingElement(node.parent)) + && node.parent.propertyName === node) || + // Is default export + (node.kind === SyntaxKind.DefaultKeyword && hasSyntacticModifier(node.parent, ModifierFlags.ExportDefault))) { + return getContextNode(node.parent); + } - // Handle computed property name - const propertyName = findAncestor(node, isComputedPropertyName); - return propertyName ? - getContextNode(propertyName.parent) : - undefined; - } + return undefined; +} - if (node.parent.name === node || // node is name of declaration, use parent - isConstructorDeclaration(node.parent) || - isExportAssignment(node.parent) || - // Property name of the import export specifier or binding pattern, use parent - ((isImportOrExportSpecifier(node.parent) || isBindingElement(node.parent)) - && node.parent.propertyName === node) || - // Is default export - (node.kind === SyntaxKind.DefaultKeyword && hasSyntacticModifier(node.parent, ModifierFlags.ExportDefault))) { - return getContextNode(node.parent); - } +/** @internal */ +export function getContextNode(node: NamedDeclaration | BinaryExpression | ForInOrOfStatement | undefined): ContextNode | undefined { + if (!node) return undefined; + switch (node.kind) { + case SyntaxKind.VariableDeclaration: + return !isVariableDeclarationList(node.parent) || node.parent.declarations.length !== 1 ? + node : + isVariableStatement(node.parent.parent) ? + node.parent.parent : + isForInOrOfStatement(node.parent.parent) ? + getContextNode(node.parent.parent) : + node.parent; + + case SyntaxKind.BindingElement: + return getContextNode(node.parent.parent as NamedDeclaration); + + case SyntaxKind.ImportSpecifier: + return node.parent.parent.parent; + + case SyntaxKind.ExportSpecifier: + case SyntaxKind.NamespaceImport: + return node.parent.parent; + + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceExport: + return node.parent; + + case SyntaxKind.BinaryExpression: + return isExpressionStatement(node.parent) ? + node.parent : + node; + + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForInStatement: + return { + start: (node as ForInOrOfStatement).initializer, + end: (node as ForInOrOfStatement).expression + }; - return undefined; + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + return isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent) ? + getContextNode( + findAncestor(node.parent, node => + isBinaryExpression(node) || isForInOrOfStatement(node) + ) as BinaryExpression | ForInOrOfStatement + ) : + node; + + default: + return node; } +} - export function getContextNode(node: NamedDeclaration | BinaryExpression | ForInOrOfStatement | undefined): ContextNode | undefined { - if (!node) return undefined; - switch (node.kind) { - case SyntaxKind.VariableDeclaration: - return !isVariableDeclarationList(node.parent) || node.parent.declarations.length !== 1 ? - node : - isVariableStatement(node.parent.parent) ? - node.parent.parent : - isForInOrOfStatement(node.parent.parent) ? - getContextNode(node.parent.parent) : - node.parent; - - case SyntaxKind.BindingElement: - return getContextNode(node.parent.parent as NamedDeclaration); - - case SyntaxKind.ImportSpecifier: - return node.parent.parent.parent; - - case SyntaxKind.ExportSpecifier: - case SyntaxKind.NamespaceImport: - return node.parent.parent; - - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceExport: - return node.parent; - - case SyntaxKind.BinaryExpression: - return isExpressionStatement(node.parent) ? - node.parent : - node; - - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForInStatement: - return { - start: (node as ForInOrOfStatement).initializer, - end: (node as ForInOrOfStatement).expression - }; - - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - return isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent) ? - getContextNode( - findAncestor(node.parent, node => - isBinaryExpression(node) || isForInOrOfStatement(node) - ) as BinaryExpression | ForInOrOfStatement - ) : - node; +/** @internal */ +export function toContextSpan(textSpan: TextSpan, sourceFile: SourceFile, context?: ContextNode): { contextSpan: TextSpan } | undefined { + if (!context) return undefined; + const contextSpan = isContextWithStartAndEndNode(context) ? + getTextSpan(context.start, sourceFile, context.end) : + getTextSpan(context, sourceFile); + return contextSpan.start !== textSpan.start || contextSpan.length !== textSpan.length ? + { contextSpan } : + undefined; +} - default: - return node; - } - } +/** @internal */ +export const enum FindReferencesUse { + /** + * When searching for references to a symbol, the location will not be adjusted (this is the default behavior when not specified). + */ + Other, + /** + * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. + */ + References, + /** + * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. + * Unlike `References`, the location will only be adjusted keyword belonged to a declaration with a valid name. + * If set, we will find fewer references -- if it is referenced by several different names, we still only find references for the original name. + */ + Rename, +} - export function toContextSpan(textSpan: TextSpan, sourceFile: SourceFile, context?: ContextNode): { contextSpan: TextSpan } | undefined { - if (!context) return undefined; - const contextSpan = isContextWithStartAndEndNode(context) ? - getTextSpan(context.start, sourceFile, context.end) : - getTextSpan(context, sourceFile); - return contextSpan.start !== textSpan.start || contextSpan.length !== textSpan.length ? - { contextSpan } : - undefined; - } +/** @internal */ +export interface Options { + readonly findInStrings?: boolean; + readonly findInComments?: boolean; + readonly use?: FindReferencesUse; + /** True if we are searching for implementations. We will have a different method of adding references if so. */ + readonly implementations?: boolean; + /** + * True to opt in for enhanced renaming of shorthand properties and import/export specifiers. + * The options controls the behavior for the whole rename operation; it cannot be changed on a per-file basis. + * Default is false for backwards compatibility. + */ + readonly providePrefixAndSuffixTextForRename?: boolean; +} - export const enum FindReferencesUse { - /** - * When searching for references to a symbol, the location will not be adjusted (this is the default behavior when not specified). - */ - Other, - /** - * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. - */ - References, - /** - * When searching for references to a symbol, the location will be adjusted if the cursor was on a keyword. - * Unlike `References`, the location will only be adjusted keyword belonged to a declaration with a valid name. - * If set, we will find fewer references -- if it is referenced by several different names, we still only find references for the original name. - */ - Rename, - } +/** @internal */ +export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { + const node = getTouchingPropertyName(sourceFile, position); + const options = { use: FindReferencesUse.References }; + const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options); + const checker = program.getTypeChecker(); + // Unless the starting node is a declaration (vs e.g. JSDoc), don't attempt to compute isDefinition + const adjustedNode = Core.getAdjustedNode(node, options); + const symbol = isDefinitionForReference(adjustedNode) ? checker.getSymbolAtLocation(adjustedNode) : undefined; + return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined(referencedSymbols, ({ definition, references }) => + // Only include referenced symbols that have a valid definition. + definition && { + definition: checker.runWithCancellationToken(cancellationToken, checker => definitionToReferencedSymbolDefinitionInfo(definition, checker, node)), + references: references.map(r => toReferencedSymbolEntry(r, symbol)) + }); +} - export interface Options { - readonly findInStrings?: boolean; - readonly findInComments?: boolean; - readonly use?: FindReferencesUse; - /** True if we are searching for implementations. We will have a different method of adding references if so. */ - readonly implementations?: boolean; - /** - * True to opt in for enhanced renaming of shorthand properties and import/export specifiers. - * The options controls the behavior for the whole rename operation; it cannot be changed on a per-file basis. - * Default is false for backwards compatibility. - */ - readonly providePrefixAndSuffixTextForRename?: boolean; - } +function isDefinitionForReference(node: Node): boolean { + return node.kind === SyntaxKind.DefaultKeyword + || !!getDeclarationFromName(node) + || isLiteralComputedPropertyDeclarationName(node) + || (node.kind === SyntaxKind.ConstructorKeyword && isConstructorDeclaration(node.parent)); +} - export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { - const node = getTouchingPropertyName(sourceFile, position); - const options = { use: FindReferencesUse.References }; - const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options); - const checker = program.getTypeChecker(); - // Unless the starting node is a declaration (vs e.g. JSDoc), don't attempt to compute isDefinition - const adjustedNode = Core.getAdjustedNode(node, options); - const symbol = isDefinitionForReference(adjustedNode) ? checker.getSymbolAtLocation(adjustedNode) : undefined; - return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined(referencedSymbols, ({ definition, references }) => - // Only include referenced symbols that have a valid definition. - definition && { - definition: checker.runWithCancellationToken(cancellationToken, checker => definitionToReferencedSymbolDefinitionInfo(definition, checker, node)), - references: references.map(r => toReferencedSymbolEntry(r, symbol)) - }); +/** @internal */ +export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined { + const node = getTouchingPropertyName(sourceFile, position); + let referenceEntries: Entry[] | undefined; + const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node, position); + + if ( + node.parent.kind === SyntaxKind.PropertyAccessExpression + || node.parent.kind === SyntaxKind.BindingElement + || node.parent.kind === SyntaxKind.ElementAccessExpression + || node.kind === SyntaxKind.SuperKeyword + ) { + referenceEntries = entries && [...entries]; } - - function isDefinitionForReference(node: Node): boolean { - return node.kind === SyntaxKind.DefaultKeyword - || !!getDeclarationFromName(node) - || isLiteralComputedPropertyDeclarationName(node) - || (node.kind === SyntaxKind.ConstructorKeyword && isConstructorDeclaration(node.parent)); - } - - export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined { - const node = getTouchingPropertyName(sourceFile, position); - let referenceEntries: Entry[] | undefined; - const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node, position); - - if ( - node.parent.kind === SyntaxKind.PropertyAccessExpression - || node.parent.kind === SyntaxKind.BindingElement - || node.parent.kind === SyntaxKind.ElementAccessExpression - || node.kind === SyntaxKind.SuperKeyword - ) { - referenceEntries = entries && [...entries]; - } - else if (entries) { - const queue = createQueue(entries); - const seenNodes = new Map(); - while (!queue.isEmpty()) { - const entry = queue.dequeue() as NodeEntry; - if (!addToSeen(seenNodes, getNodeId(entry.node))) { - continue; - } - referenceEntries = append(referenceEntries, entry); - const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, entry.node, entry.node.pos); - if (entries) { - queue.enqueue(...entries); - } + else if (entries) { + const queue = createQueue(entries); + const seenNodes = new Map(); + while (!queue.isEmpty()) { + const entry = queue.dequeue() as NodeEntry; + if (!addToSeen(seenNodes, getNodeId(entry.node))) { + continue; + } + referenceEntries = append(referenceEntries, entry); + const entries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, entry.node, entry.node.pos); + if (entries) { + queue.enqueue(...entries); } } - const checker = program.getTypeChecker(); - return map(referenceEntries, entry => toImplementationLocation(entry, checker)); } + const checker = program.getTypeChecker(); + return map(referenceEntries, entry => toImplementationLocation(entry, checker)); +} - function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number): readonly Entry[] | undefined { - if (node.kind === SyntaxKind.SourceFile) { - return undefined; - } - - const checker = program.getTypeChecker(); - // If invoked directly on a shorthand property assignment, then return - // the declaration of the symbol being assigned (not the symbol being assigned to). - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - const result: NodeEntry[] = []; - Core.getReferenceEntriesForShorthandPropertyAssignment(node, checker, node => result.push(nodeEntry(node))); - return result; - } - else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) { - // References to and accesses on the super keyword only have one possible implementation, so no - // need to "Find all References" - const symbol = checker.getSymbolAtLocation(node)!; - return symbol.valueDeclaration && [nodeEntry(symbol.valueDeclaration)]; - } - else { - // Perform "Find all References" and retrieve only those that are implementations - return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, use: FindReferencesUse.References }); - } +function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number): readonly Entry[] | undefined { + if (node.kind === SyntaxKind.SourceFile) { + return undefined; } - export function findReferenceOrRenameEntries( - program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number, options: Options | undefined, - convertEntry: ToReferenceOrRenameEntry, - ): T[] | undefined { - return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), entry => convertEntry(entry, node, program.getTypeChecker())); + const checker = program.getTypeChecker(); + // If invoked directly on a shorthand property assignment, then return + // the declaration of the symbol being assigned (not the symbol being assigned to). + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + const result: NodeEntry[] = []; + Core.getReferenceEntriesForShorthandPropertyAssignment(node, checker, node => result.push(nodeEntry(node))); + return result; + } + else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) { + // References to and accesses on the super keyword only have one possible implementation, so no + // need to "Find all References" + const symbol = checker.getSymbolAtLocation(node)!; + return symbol.valueDeclaration && [nodeEntry(symbol.valueDeclaration)]; } + else { + // Perform "Find all References" and retrieve only those that are implementations + return getReferenceEntriesForNode(position, node, program, sourceFiles, cancellationToken, { implementations: true, use: FindReferencesUse.References }); + } +} - export type ToReferenceOrRenameEntry = (entry: Entry, originalNode: Node, checker: TypeChecker) => T; +/** @internal */ +export function findReferenceOrRenameEntries( + program: Program, cancellationToken: CancellationToken, sourceFiles: readonly SourceFile[], node: Node, position: number, options: Options | undefined, + convertEntry: ToReferenceOrRenameEntry, +): T[] | undefined { + return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), entry => convertEntry(entry, node, program.getTypeChecker())); +} - export function getReferenceEntriesForNode( - position: number, - node: Node, - program: Program, - sourceFiles: readonly SourceFile[], - cancellationToken: CancellationToken, - options: Options = {}, - sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName)), - ): readonly Entry[] | undefined { - return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet)); - } - - function flattenEntries(referenceSymbols: readonly SymbolAndEntries[] | undefined): readonly Entry[] | undefined { - return referenceSymbols && flatMap(referenceSymbols, r => r.references); - } - - function definitionToReferencedSymbolDefinitionInfo(def: Definition, checker: TypeChecker, originalNode: Node): ReferencedSymbolDefinitionInfo { - const info = ((): { sourceFile: SourceFile, textSpan: TextSpan, name: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], context?: Node | ContextWithStartAndEndNode } => { - switch (def.type) { - case DefinitionKind.Symbol: { - const { symbol } = def; - const { displayParts, kind } = getDefinitionKindAndDisplayParts(symbol, checker, originalNode); - const name = displayParts.map(p => p.text).join(""); - const declaration = symbol.declarations && firstOrUndefined(symbol.declarations); - const node = declaration ? (getNameOfDeclaration(declaration) || declaration) : originalNode; - return { - ...getFileAndTextSpanFromNode(node), - name, - kind, - displayParts, - context: getContextNode(declaration) - }; - } - case DefinitionKind.Label: { - const { node } = def; - return { ...getFileAndTextSpanFromNode(node), name: node.text, kind: ScriptElementKind.label, displayParts: [displayPart(node.text, SymbolDisplayPartKind.text)] }; - } - case DefinitionKind.Keyword: { - const { node } = def; - const name = tokenToString(node.kind)!; - return { ...getFileAndTextSpanFromNode(node), name, kind: ScriptElementKind.keyword, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] }; - } - case DefinitionKind.This: { - const { node } = def; - const symbol = checker.getSymbolAtLocation(node); - const displayParts = symbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind( - checker, symbol, node.getSourceFile(), getContainerNode(node), node).displayParts || [textPart("this")]; - return { ...getFileAndTextSpanFromNode(node), name: "this", kind: ScriptElementKind.variableElement, displayParts }; - } - case DefinitionKind.String: { - const { node } = def; - return { - ...getFileAndTextSpanFromNode(node), - name: node.text, - kind: ScriptElementKind.variableElement, - displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)] - }; - } - case DefinitionKind.TripleSlashReference: { - return { - textSpan: createTextSpanFromRange(def.reference), - sourceFile: def.file, - name: def.reference.fileName, - kind: ScriptElementKind.string, - displayParts: [displayPart(`"${def.reference.fileName}"`, SymbolDisplayPartKind.stringLiteral)] - }; - } - default: - return Debug.assertNever(def); +/** @internal */ +export type ToReferenceOrRenameEntry = (entry: Entry, originalNode: Node, checker: TypeChecker) => T; + +/** @internal */ +export function getReferenceEntriesForNode( + position: number, + node: Node, + program: Program, + sourceFiles: readonly SourceFile[], + cancellationToken: CancellationToken, + options: Options = {}, + sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName)), +): readonly Entry[] | undefined { + return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet)); +} + +function flattenEntries(referenceSymbols: readonly SymbolAndEntries[] | undefined): readonly Entry[] | undefined { + return referenceSymbols && flatMap(referenceSymbols, r => r.references); +} + +function definitionToReferencedSymbolDefinitionInfo(def: Definition, checker: TypeChecker, originalNode: Node): ReferencedSymbolDefinitionInfo { + const info = ((): { sourceFile: SourceFile, textSpan: TextSpan, name: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], context?: Node | ContextWithStartAndEndNode } => { + switch (def.type) { + case DefinitionKind.Symbol: { + const { symbol } = def; + const { displayParts, kind } = getDefinitionKindAndDisplayParts(symbol, checker, originalNode); + const name = displayParts.map(p => p.text).join(""); + const declaration = symbol.declarations && firstOrUndefined(symbol.declarations); + const node = declaration ? (getNameOfDeclaration(declaration) || declaration) : originalNode; + return { + ...getFileAndTextSpanFromNode(node), + name, + kind, + displayParts, + context: getContextNode(declaration) + }; } - })(); + case DefinitionKind.Label: { + const { node } = def; + return { ...getFileAndTextSpanFromNode(node), name: node.text, kind: ScriptElementKind.label, displayParts: [displayPart(node.text, SymbolDisplayPartKind.text)] }; + } + case DefinitionKind.Keyword: { + const { node } = def; + const name = tokenToString(node.kind)!; + return { ...getFileAndTextSpanFromNode(node), name, kind: ScriptElementKind.keyword, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] }; + } + case DefinitionKind.This: { + const { node } = def; + const symbol = checker.getSymbolAtLocation(node); + const displayParts = symbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind( + checker, symbol, node.getSourceFile(), getContainerNode(node), node).displayParts || [textPart("this")]; + return { ...getFileAndTextSpanFromNode(node), name: "this", kind: ScriptElementKind.variableElement, displayParts }; + } + case DefinitionKind.String: { + const { node } = def; + return { + ...getFileAndTextSpanFromNode(node), + name: node.text, + kind: ScriptElementKind.variableElement, + displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)] + }; + } + case DefinitionKind.TripleSlashReference: { + return { + textSpan: createTextSpanFromRange(def.reference), + sourceFile: def.file, + name: def.reference.fileName, + kind: ScriptElementKind.string, + displayParts: [displayPart(`"${def.reference.fileName}"`, SymbolDisplayPartKind.stringLiteral)] + }; + } + default: + return Debug.assertNever(def); + } + })(); + + const { sourceFile, textSpan, name, kind, displayParts, context } = info; + return { + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: sourceFile.fileName, + kind, + name, + textSpan, + displayParts, + ...toContextSpan(textSpan, sourceFile, context) + }; +} - const { sourceFile, textSpan, name, kind, displayParts, context } = info; - return { - containerKind: ScriptElementKind.unknown, - containerName: "", - fileName: sourceFile.fileName, - kind, - name, - textSpan, - displayParts, - ...toContextSpan(textSpan, sourceFile, context) - }; - } +function getFileAndTextSpanFromNode(node: Node) { + const sourceFile = node.getSourceFile(); + return { + sourceFile, + textSpan: getTextSpan(isComputedPropertyName(node) ? node.expression : node, sourceFile) + }; +} - function getFileAndTextSpanFromNode(node: Node) { - const sourceFile = node.getSourceFile(); - return { - sourceFile, - textSpan: getTextSpan(isComputedPropertyName(node) ? node.expression : node, sourceFile) - }; - } +function getDefinitionKindAndDisplayParts(symbol: Symbol, checker: TypeChecker, node: Node): { displayParts: SymbolDisplayPart[], kind: ScriptElementKind } { + const meaning = Core.getIntersectingMeaningFromDeclarations(node, symbol); + const enclosingDeclaration = symbol.declarations && firstOrUndefined(symbol.declarations) || node; + const { displayParts, symbolKind } = + SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, enclosingDeclaration.getSourceFile(), enclosingDeclaration, enclosingDeclaration, meaning); + return { displayParts, kind: symbolKind }; +} - function getDefinitionKindAndDisplayParts(symbol: Symbol, checker: TypeChecker, node: Node): { displayParts: SymbolDisplayPart[], kind: ScriptElementKind } { - const meaning = Core.getIntersectingMeaningFromDeclarations(node, symbol); - const enclosingDeclaration = symbol.declarations && firstOrUndefined(symbol.declarations) || node; - const { displayParts, symbolKind } = - SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, enclosingDeclaration.getSourceFile(), enclosingDeclaration, enclosingDeclaration, meaning); - return { displayParts, kind: symbolKind }; - } +/** @internal */ +export function toRenameLocation(entry: Entry, originalNode: Node, checker: TypeChecker, providePrefixAndSuffixText: boolean): RenameLocation { + return { ...entryToDocumentSpan(entry), ...(providePrefixAndSuffixText && getPrefixAndSuffixText(entry, originalNode, checker)) }; +} - export function toRenameLocation(entry: Entry, originalNode: Node, checker: TypeChecker, providePrefixAndSuffixText: boolean): RenameLocation { - return { ...entryToDocumentSpan(entry), ...(providePrefixAndSuffixText && getPrefixAndSuffixText(entry, originalNode, checker)) }; - } +function toReferencedSymbolEntry(entry: Entry, symbol: Symbol | undefined): ReferencedSymbolEntry { + const referenceEntry = toReferenceEntry(entry); + if (!symbol) return referenceEntry; + return { + ...referenceEntry, + isDefinition: entry.kind !== EntryKind.Span && isDeclarationOfSymbol(entry.node, symbol) + }; +} - function toReferencedSymbolEntry(entry: Entry, symbol: Symbol | undefined): ReferencedSymbolEntry { - const referenceEntry = toReferenceEntry(entry); - if (!symbol) return referenceEntry; - return { - ...referenceEntry, - isDefinition: entry.kind !== EntryKind.Span && isDeclarationOfSymbol(entry.node, symbol) - }; +/** @internal */ +export function toReferenceEntry(entry: Entry): ReferenceEntry { + const documentSpan = entryToDocumentSpan(entry); + if (entry.kind === EntryKind.Span) { + return { ...documentSpan, isWriteAccess: false }; } + const { kind, node } = entry; + return { + ...documentSpan, + isWriteAccess: isWriteAccessForReference(node), + isInString: kind === EntryKind.StringLiteral ? true : undefined, + }; +} - export function toReferenceEntry(entry: Entry): ReferenceEntry { - const documentSpan = entryToDocumentSpan(entry); - if (entry.kind === EntryKind.Span) { - return { ...documentSpan, isWriteAccess: false }; - } - const { kind, node } = entry; +function entryToDocumentSpan(entry: Entry): DocumentSpan { + if (entry.kind === EntryKind.Span) { + return { textSpan: entry.textSpan, fileName: entry.fileName }; + } + else { + const sourceFile = entry.node.getSourceFile(); + const textSpan = getTextSpan(entry.node, sourceFile); return { - ...documentSpan, - isWriteAccess: isWriteAccessForReference(node), - isInString: kind === EntryKind.StringLiteral ? true : undefined, + textSpan, + fileName: sourceFile.fileName, + ...toContextSpan(textSpan, sourceFile, entry.context) }; } +} - function entryToDocumentSpan(entry: Entry): DocumentSpan { - if (entry.kind === EntryKind.Span) { - return { textSpan: entry.textSpan, fileName: entry.fileName }; - } - else { - const sourceFile = entry.node.getSourceFile(); - const textSpan = getTextSpan(entry.node, sourceFile); - return { - textSpan, - fileName: sourceFile.fileName, - ...toContextSpan(textSpan, sourceFile, entry.context) - }; - } - } - - interface PrefixAndSuffix { readonly prefixText?: string; readonly suffixText?: string; } - function getPrefixAndSuffixText(entry: Entry, originalNode: Node, checker: TypeChecker): PrefixAndSuffix { - if (entry.kind !== EntryKind.Span && isIdentifier(originalNode)) { - const { node, kind } = entry; - const parent = node.parent; - const name = originalNode.text; - const isShorthandAssignment = isShorthandPropertyAssignment(parent); - if (isShorthandAssignment || (isObjectBindingElementWithoutPropertyName(parent) && parent.name === node && parent.dotDotDotToken === undefined)) { - const prefixColon: PrefixAndSuffix = { prefixText: name + ": " }; - const suffixColon: PrefixAndSuffix = { suffixText: ": " + name }; - if (kind === EntryKind.SearchedLocalFoundProperty) { +interface PrefixAndSuffix { readonly prefixText?: string; readonly suffixText?: string; } +function getPrefixAndSuffixText(entry: Entry, originalNode: Node, checker: TypeChecker): PrefixAndSuffix { + if (entry.kind !== EntryKind.Span && isIdentifier(originalNode)) { + const { node, kind } = entry; + const parent = node.parent; + const name = originalNode.text; + const isShorthandAssignment = isShorthandPropertyAssignment(parent); + if (isShorthandAssignment || (isObjectBindingElementWithoutPropertyName(parent) && parent.name === node && parent.dotDotDotToken === undefined)) { + const prefixColon: PrefixAndSuffix = { prefixText: name + ": " }; + const suffixColon: PrefixAndSuffix = { suffixText: ": " + name }; + if (kind === EntryKind.SearchedLocalFoundProperty) { + return prefixColon; + } + if (kind === EntryKind.SearchedPropertyFoundLocal) { + return suffixColon; + } + + // In `const o = { x }; o.x`, symbolAtLocation at `x` in `{ x }` is the property symbol. + // For a binding element `const { x } = o;`, symbolAtLocation at `x` is the property symbol. + if (isShorthandAssignment) { + const grandParent = parent.parent; + if (isObjectLiteralExpression(grandParent) && + isBinaryExpression(grandParent.parent) && + isModuleExportsAccessExpression(grandParent.parent.left)) { return prefixColon; } - if (kind === EntryKind.SearchedPropertyFoundLocal) { - return suffixColon; - } - - // In `const o = { x }; o.x`, symbolAtLocation at `x` in `{ x }` is the property symbol. - // For a binding element `const { x } = o;`, symbolAtLocation at `x` is the property symbol. - if (isShorthandAssignment) { - const grandParent = parent.parent; - if (isObjectLiteralExpression(grandParent) && - isBinaryExpression(grandParent.parent) && - isModuleExportsAccessExpression(grandParent.parent.left)) { - return prefixColon; - } - return suffixColon; - } - else { - return prefixColon; - } - } - else if (isImportSpecifier(parent) && !parent.propertyName) { - // If the original symbol was using this alias, just rename the alias. - const originalSymbol = isExportSpecifier(originalNode.parent) ? checker.getExportSpecifierLocalTargetSymbol(originalNode.parent) : checker.getSymbolAtLocation(originalNode); - return contains(originalSymbol!.declarations, parent) ? { prefixText: name + " as " } : emptyOptions; + return suffixColon; } - else if (isExportSpecifier(parent) && !parent.propertyName) { - // If the symbol for the node is same as declared node symbol use prefix text - return originalNode === entry.node || checker.getSymbolAtLocation(originalNode) === checker.getSymbolAtLocation(entry.node) ? - { prefixText: name + " as " } : - { suffixText: " as " + name }; + else { + return prefixColon; } } - - return emptyOptions; - } - - function toImplementationLocation(entry: Entry, checker: TypeChecker): ImplementationLocation { - const documentSpan = entryToDocumentSpan(entry); - if (entry.kind !== EntryKind.Span) { - const { node } = entry; - return { - ...documentSpan, - ...implementationKindDisplayParts(node, checker) - }; + else if (isImportSpecifier(parent) && !parent.propertyName) { + // If the original symbol was using this alias, just rename the alias. + const originalSymbol = isExportSpecifier(originalNode.parent) ? checker.getExportSpecifierLocalTargetSymbol(originalNode.parent) : checker.getSymbolAtLocation(originalNode); + return contains(originalSymbol!.declarations, parent) ? { prefixText: name + " as " } : emptyOptions; } - else { - return { ...documentSpan, kind: ScriptElementKind.unknown, displayParts: [] }; + else if (isExportSpecifier(parent) && !parent.propertyName) { + // If the symbol for the node is same as declared node symbol use prefix text + return originalNode === entry.node || checker.getSymbolAtLocation(originalNode) === checker.getSymbolAtLocation(entry.node) ? + { prefixText: name + " as " } : + { suffixText: " as " + name }; } } - function implementationKindDisplayParts(node: Node, checker: TypeChecker): { kind: ScriptElementKind, displayParts: SymbolDisplayPart[] } { - const symbol = checker.getSymbolAtLocation(isDeclaration(node) && node.name ? node.name : node); - if (symbol) { - return getDefinitionKindAndDisplayParts(symbol, checker, node); - } - else if (node.kind === SyntaxKind.ObjectLiteralExpression) { - return { - kind: ScriptElementKind.interfaceElement, - displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("object literal"), punctuationPart(SyntaxKind.CloseParenToken)] - }; - } - else if (node.kind === SyntaxKind.ClassExpression) { - return { - kind: ScriptElementKind.localClassElement, - displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("anonymous local class"), punctuationPart(SyntaxKind.CloseParenToken)] - }; - } - else { - return { kind: getNodeKind(node), displayParts: [] }; - } - } - - export function toHighlightSpan(entry: Entry): { fileName: string, span: HighlightSpan } { - const documentSpan = entryToDocumentSpan(entry); - if (entry.kind === EntryKind.Span) { - return { - fileName: documentSpan.fileName, - span: { - textSpan: documentSpan.textSpan, - kind: HighlightSpanKind.reference - } - }; - } + return emptyOptions; +} - const writeAccess = isWriteAccessForReference(entry.node); - const span: HighlightSpan = { - textSpan: documentSpan.textSpan, - kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference, - isInString: entry.kind === EntryKind.StringLiteral ? true : undefined, - ...documentSpan.contextSpan && { contextSpan: documentSpan.contextSpan } +function toImplementationLocation(entry: Entry, checker: TypeChecker): ImplementationLocation { + const documentSpan = entryToDocumentSpan(entry); + if (entry.kind !== EntryKind.Span) { + const { node } = entry; + return { + ...documentSpan, + ...implementationKindDisplayParts(node, checker) }; - return { fileName: documentSpan.fileName, span }; } - - function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSpan { - let start = node.getStart(sourceFile); - let end = (endNode || node).getEnd(); - if (isStringLiteralLike(node) && (end - start) > 2) { - Debug.assert(endNode === undefined); - start += 1; - end -= 1; - } - return createTextSpanFromBounds(start, end); + else { + return { ...documentSpan, kind: ScriptElementKind.unknown, displayParts: [] }; } +} - export function getTextSpanOfEntry(entry: Entry) { - return entry.kind === EntryKind.Span ? entry.textSpan : - getTextSpan(entry.node, entry.node.getSourceFile()); +function implementationKindDisplayParts(node: Node, checker: TypeChecker): { kind: ScriptElementKind, displayParts: SymbolDisplayPart[] } { + const symbol = checker.getSymbolAtLocation(isDeclaration(node) && node.name ? node.name : node); + if (symbol) { + return getDefinitionKindAndDisplayParts(symbol, checker, node); } - - /** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */ - function isWriteAccessForReference(node: Node): boolean { - const decl = getDeclarationFromName(node); - return !!decl && declarationIsWriteAccess(decl) || node.kind === SyntaxKind.DefaultKeyword || isWriteAccess(node); + else if (node.kind === SyntaxKind.ObjectLiteralExpression) { + return { + kind: ScriptElementKind.interfaceElement, + displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("object literal"), punctuationPart(SyntaxKind.CloseParenToken)] + }; } - - /** Whether a reference, `node`, is a definition of the `target` symbol */ - export function isDeclarationOfSymbol(node: Node, target: Symbol | undefined): boolean { - if (!target) return false; - const source = getDeclarationFromName(node) || - (node.kind === SyntaxKind.DefaultKeyword ? node.parent - : isLiteralComputedPropertyDeclarationName(node) ? node.parent.parent - : node.kind === SyntaxKind.ConstructorKeyword && isConstructorDeclaration(node.parent) ? node.parent.parent - : undefined); - const commonjsSource = source && isBinaryExpression(source) ? source.left as unknown as Declaration : undefined; - return !!(source && target.declarations?.some(d => d === source || d === commonjsSource)); + else if (node.kind === SyntaxKind.ClassExpression) { + return { + kind: ScriptElementKind.localClassElement, + displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("anonymous local class"), punctuationPart(SyntaxKind.CloseParenToken)] + }; + } + else { + return { kind: getNodeKind(node), displayParts: [] }; } +} - /** - * True if 'decl' provides a value, as in `function f() {}`; - * false if 'decl' is just a location for a future write, as in 'let x;' - */ - function declarationIsWriteAccess(decl: Declaration): boolean { - // Consider anything in an ambient declaration to be a write access since it may be coming from JS. - if (!!(decl.flags & NodeFlags.Ambient)) return true; - - switch (decl.kind) { - case SyntaxKind.BinaryExpression: - case SyntaxKind.BindingElement: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.DefaultKeyword: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.EnumMember: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportClause: // default import - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JsxAttribute: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.NamespaceExportDeclaration: - case SyntaxKind.NamespaceImport: - case SyntaxKind.NamespaceExport: - case SyntaxKind.Parameter: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.TypeParameter: - return true; +/** @internal */ +export function toHighlightSpan(entry: Entry): { fileName: string, span: HighlightSpan } { + const documentSpan = entryToDocumentSpan(entry); + if (entry.kind === EntryKind.Span) { + return { + fileName: documentSpan.fileName, + span: { + textSpan: documentSpan.textSpan, + kind: HighlightSpanKind.reference + } + }; + } - case SyntaxKind.PropertyAssignment: - // In `({ x: y } = 0);`, `x` is not a write access. (Won't call this function for `y`.) - return !isArrayLiteralOrObjectLiteralDestructuringPattern((decl as PropertyAssignment).parent); + const writeAccess = isWriteAccessForReference(entry.node); + const span: HighlightSpan = { + textSpan: documentSpan.textSpan, + kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference, + isInString: entry.kind === EntryKind.StringLiteral ? true : undefined, + ...documentSpan.contextSpan && { contextSpan: documentSpan.contextSpan } + }; + return { fileName: documentSpan.fileName, span }; +} - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return !!(decl as FunctionDeclaration | FunctionExpression | ConstructorDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration).body; +function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSpan { + let start = node.getStart(sourceFile); + let end = (endNode || node).getEnd(); + if (isStringLiteralLike(node) && (end - start) > 2) { + Debug.assert(endNode === undefined); + start += 1; + end -= 1; + } + return createTextSpanFromBounds(start, end); +} - case SyntaxKind.VariableDeclaration: - case SyntaxKind.PropertyDeclaration: - return !!(decl as VariableDeclaration | PropertyDeclaration).initializer || isCatchClause(decl.parent); +/** @internal */ +export function getTextSpanOfEntry(entry: Entry) { + return entry.kind === EntryKind.Span ? entry.textSpan : + getTextSpan(entry.node, entry.node.getSourceFile()); +} - case SyntaxKind.MethodSignature: - case SyntaxKind.PropertySignature: - case SyntaxKind.JSDocPropertyTag: - case SyntaxKind.JSDocParameterTag: - return false; +/** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */ +function isWriteAccessForReference(node: Node): boolean { + const decl = getDeclarationFromName(node); + return !!decl && declarationIsWriteAccess(decl) || node.kind === SyntaxKind.DefaultKeyword || isWriteAccess(node); +} - default: - return Debug.failBadSyntaxKind(decl); - } - } +/** + * Whether a reference, `node`, is a definition of the `target` symbol + * + * @internal + */ +export function isDeclarationOfSymbol(node: Node, target: Symbol | undefined): boolean { + if (!target) return false; + const source = getDeclarationFromName(node) || + (node.kind === SyntaxKind.DefaultKeyword ? node.parent + : isLiteralComputedPropertyDeclarationName(node) ? node.parent.parent + : node.kind === SyntaxKind.ConstructorKeyword && isConstructorDeclaration(node.parent) ? node.parent.parent + : undefined); + const commonjsSource = source && isBinaryExpression(source) ? source.left as unknown as Declaration : undefined; + return !!(source && target.declarations?.some(d => d === source || d === commonjsSource)); +} - /** Encapsulates the core find-all-references algorithm. */ - export namespace Core { - /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ - export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly SymbolAndEntries[] | undefined { - node = getAdjustedNode(node, options); - if (isSourceFile(node)) { - const resolvedRef = GoToDefinition.getReferenceAtPosition(node, position, program); - if (!resolvedRef?.file) { - return undefined; - } - const moduleSymbol = program.getTypeChecker().getMergedSymbol(resolvedRef.file.symbol); - if (moduleSymbol) { - return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); - } - const fileIncludeReasons = program.getFileIncludeReasons(); - if (!fileIncludeReasons) { - return undefined; - } - return [{ - definition: { type: DefinitionKind.TripleSlashReference, reference: resolvedRef.reference, file: node }, - references: getReferencesForNonModule(resolvedRef.file, fileIncludeReasons, program) || emptyArray - }]; - } +/** + * True if 'decl' provides a value, as in `function f() {}`; + * false if 'decl' is just a location for a future write, as in 'let x;' + */ +function declarationIsWriteAccess(decl: Declaration): boolean { + // Consider anything in an ambient declaration to be a write access since it may be coming from JS. + if (!!(decl.flags & NodeFlags.Ambient)) return true; + + switch (decl.kind) { + case SyntaxKind.BinaryExpression: + case SyntaxKind.BindingElement: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.DefaultKeyword: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.EnumMember: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportClause: // default import + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JsxAttribute: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.NamespaceExportDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.NamespaceExport: + case SyntaxKind.Parameter: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.TypeParameter: + return true; + + case SyntaxKind.PropertyAssignment: + // In `({ x: y } = 0);`, `x` is not a write access. (Won't call this function for `y`.) + return !isArrayLiteralOrObjectLiteralDestructuringPattern((decl as PropertyAssignment).parent); + + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return !!(decl as FunctionDeclaration | FunctionExpression | ConstructorDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration).body; + + case SyntaxKind.VariableDeclaration: + case SyntaxKind.PropertyDeclaration: + return !!(decl as VariableDeclaration | PropertyDeclaration).initializer || isCatchClause(decl.parent); + + case SyntaxKind.MethodSignature: + case SyntaxKind.PropertySignature: + case SyntaxKind.JSDocPropertyTag: + case SyntaxKind.JSDocParameterTag: + return false; - if (!options.implementations) { - const special = getReferencedSymbolsSpecial(node, sourceFiles, cancellationToken); - if (special) { - return special; - } - } + default: + return Debug.failBadSyntaxKind(decl); + } +} - const checker = program.getTypeChecker(); - // constructors should use the class symbol, detected by name, if present - const symbol = checker.getSymbolAtLocation(isConstructorDeclaration(node) && node.parent.name || node); - - // Could not find a symbol e.g. unknown identifier - if (!symbol) { - // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. - if (!options.implementations && isStringLiteralLike(node)) { - if (isModuleSpecifierLike(node)) { - const fileIncludeReasons = program.getFileIncludeReasons(); - const referencedFileName = node.getSourceFile().resolvedModules?.get(node.text, getModeForUsageLocation(node.getSourceFile(), node))?.resolvedFileName; - const referencedFile = referencedFileName ? program.getSourceFile(referencedFileName) : undefined; - if (referencedFile) { - return [{ definition: { type: DefinitionKind.String, node }, references: getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray }]; - } - // Fall through to string literal references. This is not very likely to return - // anything useful, but I guess it's better than nothing, and there's an existing - // test that expects this to happen (fourslash/cases/untypedModuleImport.ts). - } - return getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken); - } +/** + * Encapsulates the core find-all-references algorithm. + * + * @internal + */ +export namespace Core { + /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ + export function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly SymbolAndEntries[] | undefined { + node = getAdjustedNode(node, options); + if (isSourceFile(node)) { + const resolvedRef = GoToDefinition.getReferenceAtPosition(node, position, program); + if (!resolvedRef?.file) { return undefined; } - - if (symbol.escapedName === InternalSymbolName.ExportEquals) { - return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); + const moduleSymbol = program.getTypeChecker().getMergedSymbol(resolvedRef.file.symbol); + if (moduleSymbol) { + return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); } - - const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); - if (moduleReferences && !(symbol.flags & SymbolFlags.Transient)) { - return moduleReferences; + const fileIncludeReasons = program.getFileIncludeReasons(); + if (!fileIncludeReasons) { + return undefined; } - - const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker); - const moduleReferencesOfExportTarget = aliasedSymbol && - getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); - - const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options); - return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); + return [{ + definition: { type: DefinitionKind.TripleSlashReference, reference: resolvedRef.reference, file: node }, + references: getReferencesForNonModule(resolvedRef.file, fileIncludeReasons, program) || emptyArray + }]; } - export function getAdjustedNode(node: Node, options: Options) { - if (options.use === FindReferencesUse.References) { - node = getAdjustedReferenceLocation(node); - } - else if (options.use === FindReferencesUse.Rename) { - node = getAdjustedRenameLocation(node); + if (!options.implementations) { + const special = getReferencedSymbolsSpecial(node, sourceFiles, cancellationToken); + if (special) { + return special; } - return node; } - export function getReferencesForFileName(fileName: string, program: Program, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly Entry[] { - const moduleSymbol = program.getSourceFile(fileName)?.symbol; - if (moduleSymbol) { - return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet)[0]?.references || emptyArray; - } - const fileIncludeReasons = program.getFileIncludeReasons(); - const referencedFile = program.getSourceFile(fileName); - return referencedFile && fileIncludeReasons && getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray; - } - - function getReferencesForNonModule(referencedFile: SourceFile, refFileMap: MultiMap, program: Program): readonly SpanEntry[] | undefined { - let entries: SpanEntry[] | undefined; - const references = refFileMap.get(referencedFile.path) || emptyArray; - for (const ref of references) { - if (isReferencedFile(ref)) { - const referencingFile = program.getSourceFileByPath(ref.file)!; - const location = getReferencedFileLocation(program.getSourceFileByPath, ref); - if (isReferenceFileLocation(location)) { - entries = append(entries, { - kind: EntryKind.Span, - fileName: referencingFile.fileName, - textSpan: createTextSpanFromRange(location) - }); + const checker = program.getTypeChecker(); + // constructors should use the class symbol, detected by name, if present + const symbol = checker.getSymbolAtLocation(isConstructorDeclaration(node) && node.parent.name || node); + + // Could not find a symbol e.g. unknown identifier + if (!symbol) { + // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. + if (!options.implementations && isStringLiteralLike(node)) { + if (isModuleSpecifierLike(node)) { + const fileIncludeReasons = program.getFileIncludeReasons(); + const referencedFileName = node.getSourceFile().resolvedModules?.get(node.text, getModeForUsageLocation(node.getSourceFile(), node))?.resolvedFileName; + const referencedFile = referencedFileName ? program.getSourceFile(referencedFileName) : undefined; + if (referencedFile) { + return [{ definition: { type: DefinitionKind.String, node }, references: getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray }]; } + // Fall through to string literal references. This is not very likely to return + // anything useful, but I guess it's better than nothing, and there's an existing + // test that expects this to happen (fourslash/cases/untypedModuleImport.ts). } + return getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken); } - return entries; + return undefined; } - function getMergedAliasedSymbolOfNamespaceExportDeclaration(node: Node, symbol: Symbol, checker: TypeChecker) { - if (node.parent && isNamespaceExportDeclaration(node.parent)) { - const aliasedSymbol = checker.getAliasedSymbol(symbol); - const targetSymbol = checker.getMergedSymbol(aliasedSymbol); - if (aliasedSymbol !== targetSymbol) { - return targetSymbol; - } - } - return undefined; + if (symbol.escapedName === InternalSymbolName.ExportEquals) { + return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet); } - function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlySet) { - const moduleSourceFile = (symbol.flags & SymbolFlags.Module) && symbol.declarations && find(symbol.declarations, isSourceFile); - if (!moduleSourceFile) return undefined; - const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals); - // If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them. - const moduleReferences = getReferencedSymbolsForModule(program, symbol, !!exportEquals, sourceFiles, sourceFilesSet); - if (!exportEquals || !sourceFilesSet.has(moduleSourceFile.fileName)) return moduleReferences; - // Continue to get references to 'export ='. - const checker = program.getTypeChecker(); - symbol = skipAlias(exportEquals, checker); - return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options)); + const moduleReferences = getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); + if (moduleReferences && !(symbol.flags & SymbolFlags.Transient)) { + return moduleReferences; } - /** - * Merges the references by sorting them (by file index in sourceFiles and their location in it) that point to same definition symbol - */ - function mergeReferences(program: Program, ...referencesToMerge: (SymbolAndEntries[] | undefined)[]): SymbolAndEntries[] | undefined { - let result: SymbolAndEntries[] | undefined; - for (const references of referencesToMerge) { - if (!references || !references.length) continue; - if (!result) { - result = references; - continue; - } - for (const entry of references) { - if (!entry.definition || entry.definition.type !== DefinitionKind.Symbol) { - result.push(entry); - continue; - } - const symbol = entry.definition.symbol; - const refIndex = findIndex(result, ref => !!ref.definition && - ref.definition.type === DefinitionKind.Symbol && - ref.definition.symbol === symbol); - if (refIndex === -1) { - result.push(entry); - continue; - } + const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker); + const moduleReferencesOfExportTarget = aliasedSymbol && + getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, cancellationToken, options, sourceFilesSet); - const reference = result[refIndex]; - result[refIndex] = { - definition: reference.definition, - references: reference.references.concat(entry.references).sort((entry1, entry2) => { - const entry1File = getSourceFileIndexOfEntry(program, entry1); - const entry2File = getSourceFileIndexOfEntry(program, entry2); - if (entry1File !== entry2File) { - return compareValues(entry1File, entry2File); - } + const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options); + return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); + } - const entry1Span = getTextSpanOfEntry(entry1); - const entry2Span = getTextSpanOfEntry(entry2); - return entry1Span.start !== entry2Span.start ? - compareValues(entry1Span.start, entry2Span.start) : - compareValues(entry1Span.length, entry2Span.length); - }) - }; + export function getAdjustedNode(node: Node, options: Options) { + if (options.use === FindReferencesUse.References) { + node = getAdjustedReferenceLocation(node); + } + else if (options.use === FindReferencesUse.Rename) { + node = getAdjustedRenameLocation(node); + } + return node; + } + + export function getReferencesForFileName(fileName: string, program: Program, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet = new Set(sourceFiles.map(f => f.fileName))): readonly Entry[] { + const moduleSymbol = program.getSourceFile(fileName)?.symbol; + if (moduleSymbol) { + return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet)[0]?.references || emptyArray; + } + const fileIncludeReasons = program.getFileIncludeReasons(); + const referencedFile = program.getSourceFile(fileName); + return referencedFile && fileIncludeReasons && getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray; + } + + function getReferencesForNonModule(referencedFile: SourceFile, refFileMap: MultiMap, program: Program): readonly SpanEntry[] | undefined { + let entries: SpanEntry[] | undefined; + const references = refFileMap.get(referencedFile.path) || emptyArray; + for (const ref of references) { + if (isReferencedFile(ref)) { + const referencingFile = program.getSourceFileByPath(ref.file)!; + const location = getReferencedFileLocation(program.getSourceFileByPath, ref); + if (isReferenceFileLocation(location)) { + entries = append(entries, { + kind: EntryKind.Span, + fileName: referencingFile.fileName, + textSpan: createTextSpanFromRange(location) + }); } } - return result; } + return entries; + } - function getSourceFileIndexOfEntry(program: Program, entry: Entry) { - const sourceFile = entry.kind === EntryKind.Span ? - program.getSourceFile(entry.fileName)! : - entry.node.getSourceFile(); - return program.getSourceFiles().indexOf(sourceFile); + function getMergedAliasedSymbolOfNamespaceExportDeclaration(node: Node, symbol: Symbol, checker: TypeChecker) { + if (node.parent && isNamespaceExportDeclaration(node.parent)) { + const aliasedSymbol = checker.getAliasedSymbol(symbol); + const targetSymbol = checker.getMergedSymbol(aliasedSymbol); + if (aliasedSymbol !== targetSymbol) { + return targetSymbol; + } } + return undefined; + } - function getReferencedSymbolsForModule(program: Program, symbol: Symbol, excludeImportTypeOfExportEquals: boolean, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet): SymbolAndEntries[] { - Debug.assert(!!symbol.valueDeclaration); + function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlySet) { + const moduleSourceFile = (symbol.flags & SymbolFlags.Module) && symbol.declarations && find(symbol.declarations, isSourceFile); + if (!moduleSourceFile) return undefined; + const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals); + // If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them. + const moduleReferences = getReferencedSymbolsForModule(program, symbol, !!exportEquals, sourceFiles, sourceFilesSet); + if (!exportEquals || !sourceFilesSet.has(moduleSourceFile.fileName)) return moduleReferences; + // Continue to get references to 'export ='. + const checker = program.getTypeChecker(); + symbol = skipAlias(exportEquals, checker); + return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol, /*node*/ undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options)); + } - const references = mapDefined(findModuleReferences(program, sourceFiles, symbol), reference => { - if (reference.kind === "import") { - const parent = reference.literal.parent; - if (isLiteralTypeNode(parent)) { - const importType = cast(parent.parent, isImportTypeNode); - if (excludeImportTypeOfExportEquals && !importType.qualifier) { - return undefined; - } - } - // import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway. - return nodeEntry(reference.literal); + /** + * Merges the references by sorting them (by file index in sourceFiles and their location in it) that point to same definition symbol + */ + function mergeReferences(program: Program, ...referencesToMerge: (SymbolAndEntries[] | undefined)[]): SymbolAndEntries[] | undefined { + let result: SymbolAndEntries[] | undefined; + for (const references of referencesToMerge) { + if (!references || !references.length) continue; + if (!result) { + result = references; + continue; + } + for (const entry of references) { + if (!entry.definition || entry.definition.type !== DefinitionKind.Symbol) { + result.push(entry); + continue; } - else { - return { - kind: EntryKind.Span, - fileName: reference.referencingFile.fileName, - textSpan: createTextSpanFromRange(reference.ref), - }; + const symbol = entry.definition.symbol; + const refIndex = findIndex(result, ref => !!ref.definition && + ref.definition.type === DefinitionKind.Symbol && + ref.definition.symbol === symbol); + if (refIndex === -1) { + result.push(entry); + continue; } - }); - if (symbol.declarations) { - for (const decl of symbol.declarations) { - switch (decl.kind) { - case SyntaxKind.SourceFile: - // Don't include the source file itself. (This may not be ideal behavior, but awkward to include an entire file as a reference.) - break; - case SyntaxKind.ModuleDeclaration: - if (sourceFilesSet.has(decl.getSourceFile().fileName)) { - references.push(nodeEntry((decl as ModuleDeclaration).name)); - } - break; - default: - // This may be merged with something. - Debug.assert(!!(symbol.flags & SymbolFlags.Transient), "Expected a module symbol to be declared by a SourceFile or ModuleDeclaration."); + const reference = result[refIndex]; + result[refIndex] = { + definition: reference.definition, + references: reference.references.concat(entry.references).sort((entry1, entry2) => { + const entry1File = getSourceFileIndexOfEntry(program, entry1); + const entry2File = getSourceFileIndexOfEntry(program, entry2); + if (entry1File !== entry2File) { + return compareValues(entry1File, entry2File); + } + + const entry1Span = getTextSpanOfEntry(entry1); + const entry2Span = getTextSpanOfEntry(entry2); + return entry1Span.start !== entry2Span.start ? + compareValues(entry1Span.start, entry2Span.start) : + compareValues(entry1Span.length, entry2Span.length); + }) + }; + } + } + return result; + } + + function getSourceFileIndexOfEntry(program: Program, entry: Entry) { + const sourceFile = entry.kind === EntryKind.Span ? + program.getSourceFile(entry.fileName)! : + entry.node.getSourceFile(); + return program.getSourceFiles().indexOf(sourceFile); + } + + function getReferencedSymbolsForModule(program: Program, symbol: Symbol, excludeImportTypeOfExportEquals: boolean, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet): SymbolAndEntries[] { + Debug.assert(!!symbol.valueDeclaration); + + const references = mapDefined(findModuleReferences(program, sourceFiles, symbol), reference => { + if (reference.kind === "import") { + const parent = reference.literal.parent; + if (isLiteralTypeNode(parent)) { + const importType = cast(parent.parent, isImportTypeNode); + if (excludeImportTypeOfExportEquals && !importType.qualifier) { + return undefined; } } + // import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway. + return nodeEntry(reference.literal); + } + else { + return { + kind: EntryKind.Span, + fileName: reference.referencingFile.fileName, + textSpan: createTextSpanFromRange(reference.ref), + }; } + }); - const exported = symbol.exports!.get(InternalSymbolName.ExportEquals); - if (exported?.declarations) { - for (const decl of exported.declarations) { - const sourceFile = decl.getSourceFile(); - if (sourceFilesSet.has(sourceFile.fileName)) { - // At `module.exports = ...`, reference node is `module` - const node = isBinaryExpression(decl) && isPropertyAccessExpression(decl.left) ? decl.left.expression : - isExportAssignment(decl) ? Debug.checkDefined(findChildOfKind(decl, SyntaxKind.ExportKeyword, sourceFile)) : - getNameOfDeclaration(decl) || decl; - references.push(nodeEntry(node)); - } + if (symbol.declarations) { + for (const decl of symbol.declarations) { + switch (decl.kind) { + case SyntaxKind.SourceFile: + // Don't include the source file itself. (This may not be ideal behavior, but awkward to include an entire file as a reference.) + break; + case SyntaxKind.ModuleDeclaration: + if (sourceFilesSet.has(decl.getSourceFile().fileName)) { + references.push(nodeEntry((decl as ModuleDeclaration).name)); + } + break; + default: + // This may be merged with something. + Debug.assert(!!(symbol.flags & SymbolFlags.Transient), "Expected a module symbol to be declared by a SourceFile or ModuleDeclaration."); } } - - return references.length ? [{ definition: { type: DefinitionKind.Symbol, symbol }, references }] : emptyArray; } - /** As in a `readonly prop: any` or `constructor(readonly prop: any)`, not a `readonly any[]`. */ - function isReadonlyTypeOperator(node: Node): boolean { - return node.kind === SyntaxKind.ReadonlyKeyword - && isTypeOperatorNode(node.parent) - && node.parent.operator === SyntaxKind.ReadonlyKeyword; + const exported = symbol.exports!.get(InternalSymbolName.ExportEquals); + if (exported?.declarations) { + for (const decl of exported.declarations) { + const sourceFile = decl.getSourceFile(); + if (sourceFilesSet.has(sourceFile.fileName)) { + // At `module.exports = ...`, reference node is `module` + const node = isBinaryExpression(decl) && isPropertyAccessExpression(decl.left) ? decl.left.expression : + isExportAssignment(decl) ? Debug.checkDefined(findChildOfKind(decl, SyntaxKind.ExportKeyword, sourceFile)) : + getNameOfDeclaration(decl) || decl; + references.push(nodeEntry(node)); + } + } } - /** getReferencedSymbols for special node kinds. */ - function getReferencedSymbolsSpecial(node: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { - if (isTypeKeyword(node.kind)) { - // A void expression (i.e., `void foo()`) is not special, but the `void` type is. - if (node.kind === SyntaxKind.VoidKeyword && isVoidExpression(node.parent)) { - return undefined; - } + return references.length ? [{ definition: { type: DefinitionKind.Symbol, symbol }, references }] : emptyArray; + } - // A modifier readonly (like on a property declaration) is not special; - // a readonly type keyword (like `readonly string[]`) is. - if (node.kind === SyntaxKind.ReadonlyKeyword && !isReadonlyTypeOperator(node)) { - return undefined; - } - // Likewise, when we *are* looking for a special keyword, make sure we - // *don’t* include readonly member modifiers. - return getAllReferencesForKeyword( - sourceFiles, - node.kind, - cancellationToken, - node.kind === SyntaxKind.ReadonlyKeyword ? isReadonlyTypeOperator : undefined); - } + /** As in a `readonly prop: any` or `constructor(readonly prop: any)`, not a `readonly any[]`. */ + function isReadonlyTypeOperator(node: Node): boolean { + return node.kind === SyntaxKind.ReadonlyKeyword + && isTypeOperatorNode(node.parent) + && node.parent.operator === SyntaxKind.ReadonlyKeyword; + } - if (isImportMeta(node.parent) && node.parent.name === node) { - return getAllReferencesForImportMeta(sourceFiles, cancellationToken); + /** getReferencedSymbols for special node kinds. */ + function getReferencedSymbolsSpecial(node: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { + if (isTypeKeyword(node.kind)) { + // A void expression (i.e., `void foo()`) is not special, but the `void` type is. + if (node.kind === SyntaxKind.VoidKeyword && isVoidExpression(node.parent)) { + return undefined; } - if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) { - return [{ definition: { type: DefinitionKind.Keyword, node }, references: [nodeEntry(node)] }]; + // A modifier readonly (like on a property declaration) is not special; + // a readonly type keyword (like `readonly string[]`) is. + if (node.kind === SyntaxKind.ReadonlyKeyword && !isReadonlyTypeOperator(node)) { + return undefined; } + // Likewise, when we *are* looking for a special keyword, make sure we + // *don’t* include readonly member modifiers. + return getAllReferencesForKeyword( + sourceFiles, + node.kind, + cancellationToken, + node.kind === SyntaxKind.ReadonlyKeyword ? isReadonlyTypeOperator : undefined); + } - // Labels - if (isJumpStatementTarget(node)) { - const labelDefinition = getTargetLabel(node.parent, node.text); - // if we have a label definition, look within its statement for references, if not, then - // the label is undefined and we have no results.. - return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition); - } - else if (isLabelOfLabeledStatement(node)) { - // it is a label definition and not a target, search within the parent labeledStatement - return getLabelReferencesInNode(node.parent, node); - } + if (isImportMeta(node.parent) && node.parent.name === node) { + return getAllReferencesForImportMeta(sourceFiles, cancellationToken); + } - if (isThis(node)) { - return getReferencesForThisKeyword(node, sourceFiles, cancellationToken); - } + if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) { + return [{ definition: { type: DefinitionKind.Keyword, node }, references: [nodeEntry(node)] }]; + } - if (node.kind === SyntaxKind.SuperKeyword) { - return getReferencesForSuperKeyword(node); - } + // Labels + if (isJumpStatementTarget(node)) { + const labelDefinition = getTargetLabel(node.parent, node.text); + // if we have a label definition, look within its statement for references, if not, then + // the label is undefined and we have no results.. + return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition); + } + else if (isLabelOfLabeledStatement(node)) { + // it is a label definition and not a target, search within the parent labeledStatement + return getLabelReferencesInNode(node.parent, node); + } - return undefined; + if (isThis(node)) { + return getReferencesForThisKeyword(node, sourceFiles, cancellationToken); } - /** Core find-all-references algorithm for a normal symbol. */ - function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { - const symbol = node && skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker, /*useLocalSymbolForExportSpecifier*/ !isForRenameWithPrefixAndSuffixText(options)) || originalSymbol; + if (node.kind === SyntaxKind.SuperKeyword) { + return getReferencesForSuperKeyword(node); + } - // Compute the meaning from the location and the symbol it references - const searchMeaning = node ? getIntersectingMeaningFromDeclarations(node, symbol) : SemanticMeaning.All; - const result: SymbolAndEntries[] = []; - const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, searchMeaning, options, result); + return undefined; + } - const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) || !symbol.declarations ? undefined : find(symbol.declarations, isExportSpecifier); - if (exportSpecifier) { - // When renaming at an export specifier, rename the export and not the thing being exported. - getReferencesAtExportSpecifier(exportSpecifier.name, symbol, exportSpecifier, state.createSearch(node, originalSymbol, /*comingFrom*/ undefined), state, /*addReferencesHere*/ true, /*alwaysGetReferences*/ true); - } - else if (node && node.kind === SyntaxKind.DefaultKeyword && symbol.escapedName === InternalSymbolName.Default && symbol.parent) { - addReference(node, symbol, state); - searchForImportsOfExport(node, symbol, { exportingModuleSymbol: symbol.parent, exportKind: ExportKind.Default }, state); - } - else { - const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, options.use === FindReferencesUse.Rename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] }); - getReferencesInContainerOrFiles(symbol, state, search); - } + /** Core find-all-references algorithm for a normal symbol. */ + function getReferencedSymbolsForSymbol(originalSymbol: Symbol, node: Node | undefined, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { + const symbol = node && skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker, /*useLocalSymbolForExportSpecifier*/ !isForRenameWithPrefixAndSuffixText(options)) || originalSymbol; - return result; - } + // Compute the meaning from the location and the symbol it references + const searchMeaning = node ? getIntersectingMeaningFromDeclarations(node, symbol) : SemanticMeaning.All; + const result: SymbolAndEntries[] = []; + const state = new State(sourceFiles, sourceFilesSet, node ? getSpecialSearchKind(node) : SpecialSearchKind.None, checker, cancellationToken, searchMeaning, options, result); - function getReferencesInContainerOrFiles(symbol: Symbol, state: State, search: Search): void { - // Try to get the smallest valid scope that we can limit our search to; - // otherwise we'll need to search globally (i.e. include each file). - const scope = getSymbolScope(symbol); - if (scope) { - getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(state.sourceFiles, scope))); - } - else { - // Global search - for (const sourceFile of state.sourceFiles) { - state.cancellationToken.throwIfCancellationRequested(); - searchForName(sourceFile, search, state); - } - } + const exportSpecifier = !isForRenameWithPrefixAndSuffixText(options) || !symbol.declarations ? undefined : find(symbol.declarations, isExportSpecifier); + if (exportSpecifier) { + // When renaming at an export specifier, rename the export and not the thing being exported. + getReferencesAtExportSpecifier(exportSpecifier.name, symbol, exportSpecifier, state.createSearch(node, originalSymbol, /*comingFrom*/ undefined), state, /*addReferencesHere*/ true, /*alwaysGetReferences*/ true); + } + else if (node && node.kind === SyntaxKind.DefaultKeyword && symbol.escapedName === InternalSymbolName.Default && symbol.parent) { + addReference(node, symbol, state); + searchForImportsOfExport(node, symbol, { exportingModuleSymbol: symbol.parent, exportKind: ExportKind.Default }, state); + } + else { + const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, options.use === FindReferencesUse.Rename, !!options.providePrefixAndSuffixTextForRename, !!options.implementations) : [symbol] }); + getReferencesInContainerOrFiles(symbol, state, search); } - function getSpecialSearchKind(node: Node): SpecialSearchKind { - switch (node.kind) { - case SyntaxKind.Constructor: - case SyntaxKind.ConstructorKeyword: - return SpecialSearchKind.Constructor; - case SyntaxKind.Identifier: - if (isClassLike(node.parent)) { - Debug.assert(node.parent.name === node); - return SpecialSearchKind.Class; - } - // falls through - default: - return SpecialSearchKind.None; + return result; + } + + function getReferencesInContainerOrFiles(symbol: Symbol, state: State, search: Search): void { + // Try to get the smallest valid scope that we can limit our search to; + // otherwise we'll need to search globally (i.e. include each file). + const scope = getSymbolScope(symbol); + if (scope) { + getReferencesInContainer(scope, scope.getSourceFile(), search, state, /*addReferencesHere*/ !(isSourceFile(scope) && !contains(state.sourceFiles, scope))); + } + else { + // Global search + for (const sourceFile of state.sourceFiles) { + state.cancellationToken.throwIfCancellationRequested(); + searchForName(sourceFile, search, state); } } + } - /** Handle a few special cases relating to export/import specifiers. */ - function skipPastExportOrImportSpecifierOrUnion(symbol: Symbol, node: Node, checker: TypeChecker, useLocalSymbolForExportSpecifier: boolean): Symbol | undefined { - const { parent } = node; - if (isExportSpecifier(parent) && useLocalSymbolForExportSpecifier) { - return getLocalSymbolForExportSpecifier(node as Identifier, symbol, parent, checker); - } - // If the symbol is declared as part of a declaration like `{ type: "a" } | { type: "b" }`, use the property on the union type to get more references. - return firstDefined(symbol.declarations, decl => { - if (!decl.parent) { - // Ignore UMD module and global merge - if (symbol.flags & SymbolFlags.Transient) return undefined; - // Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here. - Debug.fail(`Unexpected symbol at ${Debug.formatSyntaxKind(node.kind)}: ${Debug.formatSymbol(symbol)}`); - } - return isTypeLiteralNode(decl.parent) && isUnionTypeNode(decl.parent.parent) - ? checker.getPropertyOfType(checker.getTypeFromTypeNode(decl.parent.parent), symbol.name) - : undefined; - }); + function getSpecialSearchKind(node: Node): SpecialSearchKind { + switch (node.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.ConstructorKeyword: + return SpecialSearchKind.Constructor; + case SyntaxKind.Identifier: + if (isClassLike(node.parent)) { + Debug.assert(node.parent.name === node); + return SpecialSearchKind.Class; + } + // falls through + default: + return SpecialSearchKind.None; } + } + + /** Handle a few special cases relating to export/import specifiers. */ + function skipPastExportOrImportSpecifierOrUnion(symbol: Symbol, node: Node, checker: TypeChecker, useLocalSymbolForExportSpecifier: boolean): Symbol | undefined { + const { parent } = node; + if (isExportSpecifier(parent) && useLocalSymbolForExportSpecifier) { + return getLocalSymbolForExportSpecifier(node as Identifier, symbol, parent, checker); + } + // If the symbol is declared as part of a declaration like `{ type: "a" } | { type: "b" }`, use the property on the union type to get more references. + return firstDefined(symbol.declarations, decl => { + if (!decl.parent) { + // Ignore UMD module and global merge + if (symbol.flags & SymbolFlags.Transient) return undefined; + // Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here. + Debug.fail(`Unexpected symbol at ${Debug.formatSyntaxKind(node.kind)}: ${Debug.formatSymbol(symbol)}`); + } + return isTypeLiteralNode(decl.parent) && isUnionTypeNode(decl.parent.parent) + ? checker.getPropertyOfType(checker.getTypeFromTypeNode(decl.parent.parent), symbol.name) + : undefined; + }); + } + + /** + * Symbol that is currently being searched for. + * This will be replaced if we find an alias for the symbol. + */ + interface Search { + /** If coming from an export, we will not recursively search for the imported symbol (since that's where we came from). */ + readonly comingFrom?: ImportExport; + + readonly symbol: Symbol; + readonly text: string; + readonly escapedText: __String; + /** Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access. */ + readonly parents: readonly Symbol[] | undefined; + readonly allSearchSymbols: readonly Symbol[]; /** - * Symbol that is currently being searched for. - * This will be replaced if we find an alias for the symbol. + * Whether a symbol is in the search set. + * Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`. */ - interface Search { - /** If coming from an export, we will not recursively search for the imported symbol (since that's where we came from). */ - readonly comingFrom?: ImportExport; + includes(symbol: Symbol): boolean; + } - readonly symbol: Symbol; - readonly text: string; - readonly escapedText: __String; - /** Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access. */ - readonly parents: readonly Symbol[] | undefined; - readonly allSearchSymbols: readonly Symbol[]; + const enum SpecialSearchKind { + None, + Constructor, + Class, + } - /** - * Whether a symbol is in the search set. - * Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`. - */ - includes(symbol: Symbol): boolean; - } + function getNonModuleSymbolOfMergedModuleSymbol(symbol: Symbol) { + if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Transient))) return undefined; + const decl = symbol.declarations && find(symbol.declarations, d => !isSourceFile(d) && !isModuleDeclaration(d)); + return decl && decl.symbol; + } - const enum SpecialSearchKind { - None, - Constructor, - Class, - } + /** + * Holds all state needed for the finding references. + * Unlike `Search`, there is only one `State`. + */ + class State { + /** Cache for `explicitlyinheritsFrom`. */ + readonly inheritsFromCache = new Map(); - function getNonModuleSymbolOfMergedModuleSymbol(symbol: Symbol) { - if (!(symbol.flags & (SymbolFlags.Module | SymbolFlags.Transient))) return undefined; - const decl = symbol.declarations && find(symbol.declarations, d => !isSourceFile(d) && !isModuleDeclaration(d)); - return decl && decl.symbol; - } + /** + * Type nodes can contain multiple references to the same type. For example: + * let x: Foo & (Foo & Bar) = ... + * Because we are returning the implementation locations and not the identifier locations, + * duplicate entries would be returned here as each of the type references is part of + * the same implementation. For that reason, check before we add a new entry. + */ + readonly markSeenContainingTypeReference = nodeSeenTracker(); /** - * Holds all state needed for the finding references. - * Unlike `Search`, there is only one `State`. + * It's possible that we will encounter the right side of `export { foo as bar } from "x";` more than once. + * For example: + * // b.ts + * export { foo as bar } from "./a"; + * import { bar } from "./b"; + * + * Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local). + * But another reference to it may appear in the same source file. + * See `tests/cases/fourslash/transitiveExportImports3.ts`. */ - class State { - /** Cache for `explicitlyinheritsFrom`. */ - readonly inheritsFromCache = new Map(); - - /** - * Type nodes can contain multiple references to the same type. For example: - * let x: Foo & (Foo & Bar) = ... - * Because we are returning the implementation locations and not the identifier locations, - * duplicate entries would be returned here as each of the type references is part of - * the same implementation. For that reason, check before we add a new entry. - */ - readonly markSeenContainingTypeReference = nodeSeenTracker(); - - /** - * It's possible that we will encounter the right side of `export { foo as bar } from "x";` more than once. - * For example: - * // b.ts - * export { foo as bar } from "./a"; - * import { bar } from "./b"; - * - * Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local). - * But another reference to it may appear in the same source file. - * See `tests/cases/fourslash/transitiveExportImports3.ts`. - */ - readonly markSeenReExportRHS = nodeSeenTracker(); - - constructor( - readonly sourceFiles: readonly SourceFile[], - readonly sourceFilesSet: ReadonlySet, - readonly specialSearchKind: SpecialSearchKind, - readonly checker: TypeChecker, - readonly cancellationToken: CancellationToken, - readonly searchMeaning: SemanticMeaning, - readonly options: Options, - private readonly result: Push) { - } - - includesSourceFile(sourceFile: SourceFile): boolean { - return this.sourceFilesSet.has(sourceFile.fileName); - } - - private importTracker: ImportTracker | undefined; - /** Gets every place to look for references of an exported symbols. See `ImportsResult` in `importTracker.ts` for more documentation. */ - getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult { - if (!this.importTracker) this.importTracker = createImportTracker(this.sourceFiles, this.sourceFilesSet, this.checker, this.cancellationToken); - return this.importTracker(exportSymbol, exportInfo, this.options.use === FindReferencesUse.Rename); - } - - /** @param allSearchSymbols set of additional symbols for use by `includes`. */ - createSearch(location: Node | undefined, symbol: Symbol, comingFrom: ImportExport | undefined, searchOptions: { text?: string, allSearchSymbols?: Symbol[] } = {}): Search { - // Note: if this is an external module symbol, the name doesn't include quotes. - // Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`. - // The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form - // here appears to be intentional). - const { - text = stripQuotes(symbolName(getLocalSymbolForExportDefault(symbol) || getNonModuleSymbolOfMergedModuleSymbol(symbol) || symbol)), - allSearchSymbols = [symbol], - } = searchOptions; - const escapedText = escapeLeadingUnderscores(text); - const parents = this.options.implementations && location ? getParentSymbolsOfPropertyAccess(location, symbol, this.checker) : undefined; - return { symbol, comingFrom, text, escapedText, parents, allSearchSymbols, includes: sym => contains(allSearchSymbols, sym) }; - } - - private readonly symbolIdToReferences: Entry[][] = []; - /** - * Callback to add references for a particular searched symbol. - * This initializes a reference group, so only call this if you will add at least one reference. - */ - referenceAdder(searchSymbol: Symbol): (node: Node, kind?: NodeEntryKind) => void { - const symbolId = getSymbolId(searchSymbol); - let references = this.symbolIdToReferences[symbolId]; - if (!references) { - references = this.symbolIdToReferences[symbolId] = []; - this.result.push({ definition: { type: DefinitionKind.Symbol, symbol: searchSymbol }, references }); - } - return (node, kind) => references.push(nodeEntry(node, kind)); + readonly markSeenReExportRHS = nodeSeenTracker(); + + constructor( + readonly sourceFiles: readonly SourceFile[], + readonly sourceFilesSet: ReadonlySet, + readonly specialSearchKind: SpecialSearchKind, + readonly checker: TypeChecker, + readonly cancellationToken: CancellationToken, + readonly searchMeaning: SemanticMeaning, + readonly options: Options, + private readonly result: Push) { + } + + includesSourceFile(sourceFile: SourceFile): boolean { + return this.sourceFilesSet.has(sourceFile.fileName); + } + + private importTracker: ImportTracker | undefined; + /** Gets every place to look for references of an exported symbols. See `ImportsResult` in `importTracker.ts` for more documentation. */ + getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult { + if (!this.importTracker) this.importTracker = createImportTracker(this.sourceFiles, this.sourceFilesSet, this.checker, this.cancellationToken); + return this.importTracker(exportSymbol, exportInfo, this.options.use === FindReferencesUse.Rename); + } + + /** @param allSearchSymbols set of additional symbols for use by `includes`. */ + createSearch(location: Node | undefined, symbol: Symbol, comingFrom: ImportExport | undefined, searchOptions: { text?: string, allSearchSymbols?: Symbol[] } = {}): Search { + // Note: if this is an external module symbol, the name doesn't include quotes. + // Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`. + // The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form + // here appears to be intentional). + const { + text = stripQuotes(symbolName(getLocalSymbolForExportDefault(symbol) || getNonModuleSymbolOfMergedModuleSymbol(symbol) || symbol)), + allSearchSymbols = [symbol], + } = searchOptions; + const escapedText = escapeLeadingUnderscores(text); + const parents = this.options.implementations && location ? getParentSymbolsOfPropertyAccess(location, symbol, this.checker) : undefined; + return { symbol, comingFrom, text, escapedText, parents, allSearchSymbols, includes: sym => contains(allSearchSymbols, sym) }; + } + + private readonly symbolIdToReferences: Entry[][] = []; + /** + * Callback to add references for a particular searched symbol. + * This initializes a reference group, so only call this if you will add at least one reference. + */ + referenceAdder(searchSymbol: Symbol): (node: Node, kind?: NodeEntryKind) => void { + const symbolId = getSymbolId(searchSymbol); + let references = this.symbolIdToReferences[symbolId]; + if (!references) { + references = this.symbolIdToReferences[symbolId] = []; + this.result.push({ definition: { type: DefinitionKind.Symbol, symbol: searchSymbol }, references }); } + return (node, kind) => references.push(nodeEntry(node, kind)); + } - /** Add a reference with no associated definition. */ - addStringOrCommentReference(fileName: string, textSpan: TextSpan): void { - this.result.push({ - definition: undefined, - references: [{ kind: EntryKind.Span, fileName, textSpan }] - }); - } + /** Add a reference with no associated definition. */ + addStringOrCommentReference(fileName: string, textSpan: TextSpan): void { + this.result.push({ + definition: undefined, + references: [{ kind: EntryKind.Span, fileName, textSpan }] + }); + } - // Source file ID → symbol ID → Whether the symbol has been searched for in the source file. - private readonly sourceFileToSeenSymbols: Set[] = []; - /** Returns `true` the first time we search for a symbol in a file and `false` afterwards. */ - markSearchedSymbols(sourceFile: SourceFile, symbols: readonly Symbol[]): boolean { - const sourceId = getNodeId(sourceFile); - const seenSymbols = this.sourceFileToSeenSymbols[sourceId] || (this.sourceFileToSeenSymbols[sourceId] = new Set()); + // Source file ID → symbol ID → Whether the symbol has been searched for in the source file. + private readonly sourceFileToSeenSymbols: Set[] = []; + /** Returns `true` the first time we search for a symbol in a file and `false` afterwards. */ + markSearchedSymbols(sourceFile: SourceFile, symbols: readonly Symbol[]): boolean { + const sourceId = getNodeId(sourceFile); + const seenSymbols = this.sourceFileToSeenSymbols[sourceId] || (this.sourceFileToSeenSymbols[sourceId] = new Set()); - let anyNewSymbols = false; - for (const sym of symbols) { - anyNewSymbols = tryAddToSet(seenSymbols, getSymbolId(sym)) || anyNewSymbols; - } - return anyNewSymbols; + let anyNewSymbols = false; + for (const sym of symbols) { + anyNewSymbols = tryAddToSet(seenSymbols, getSymbolId(sym)) || anyNewSymbols; } + return anyNewSymbols; } + } - /** Search for all imports of a given exported symbol using `State.getImportSearches`. */ - function searchForImportsOfExport(exportLocation: Node, exportSymbol: Symbol, exportInfo: ExportInfo, state: State): void { - const { importSearches, singleReferences, indirectUsers } = state.getImportSearches(exportSymbol, exportInfo); + /** Search for all imports of a given exported symbol using `State.getImportSearches`. */ + function searchForImportsOfExport(exportLocation: Node, exportSymbol: Symbol, exportInfo: ExportInfo, state: State): void { + const { importSearches, singleReferences, indirectUsers } = state.getImportSearches(exportSymbol, exportInfo); - // For `import { foo as bar }` just add the reference to `foo`, and don't otherwise search in the file. - if (singleReferences.length) { - const addRef = state.referenceAdder(exportSymbol); - for (const singleRef of singleReferences) { - if (shouldAddSingleReference(singleRef, state)) addRef(singleRef); - } + // For `import { foo as bar }` just add the reference to `foo`, and don't otherwise search in the file. + if (singleReferences.length) { + const addRef = state.referenceAdder(exportSymbol); + for (const singleRef of singleReferences) { + if (shouldAddSingleReference(singleRef, state)) addRef(singleRef); } + } - // For each import, find all references to that import in its source file. - for (const [importLocation, importSymbol] of importSearches) { - getReferencesInSourceFile(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state); - } + // For each import, find all references to that import in its source file. + for (const [importLocation, importSymbol] of importSearches) { + getReferencesInSourceFile(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state); + } - if (indirectUsers.length) { - let indirectSearch: Search | undefined; - switch (exportInfo.exportKind) { - case ExportKind.Named: - indirectSearch = state.createSearch(exportLocation, exportSymbol, ImportExport.Export); - break; - case ExportKind.Default: - // Search for a property access to '.default'. This can't be renamed. - indirectSearch = state.options.use === FindReferencesUse.Rename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); - break; - case ExportKind.ExportEquals: - break; - } - if (indirectSearch) { - for (const indirectUser of indirectUsers) { - searchForName(indirectUser, indirectSearch, state); - } + if (indirectUsers.length) { + let indirectSearch: Search | undefined; + switch (exportInfo.exportKind) { + case ExportKind.Named: + indirectSearch = state.createSearch(exportLocation, exportSymbol, ImportExport.Export); + break; + case ExportKind.Default: + // Search for a property access to '.default'. This can't be renamed. + indirectSearch = state.options.use === FindReferencesUse.Rename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); + break; + case ExportKind.ExportEquals: + break; + } + if (indirectSearch) { + for (const indirectUser of indirectUsers) { + searchForName(indirectUser, indirectSearch, state); } } } + } - export function eachExportReference( - sourceFiles: readonly SourceFile[], - checker: TypeChecker, - cancellationToken: CancellationToken | undefined, - exportSymbol: Symbol, - exportingModuleSymbol: Symbol, - exportName: string, - isDefaultExport: boolean, - cb: (ref: Identifier) => void, - ): void { - const importTracker = createImportTracker(sourceFiles, new Set(sourceFiles.map(f => f.fileName)), checker, cancellationToken); - const { importSearches, indirectUsers, singleReferences } = importTracker(exportSymbol, { exportKind: isDefaultExport ? ExportKind.Default : ExportKind.Named, exportingModuleSymbol }, /*isForRename*/ false); - for (const [importLocation] of importSearches) { - cb(importLocation); - } - for (const singleReference of singleReferences) { - if (isIdentifier(singleReference) && isImportTypeNode(singleReference.parent)) { - cb(singleReference); - } - } - for (const indirectUser of indirectUsers) { - for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) { - // Import specifiers should be handled by importSearches - const symbol = checker.getSymbolAtLocation(node); - const hasExportAssignmentDeclaration = some(symbol?.declarations, d => tryCast(d, isExportAssignment) ? true : false); - if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && (symbol === exportSymbol || hasExportAssignmentDeclaration)) { - cb(node); - } + export function eachExportReference( + sourceFiles: readonly SourceFile[], + checker: TypeChecker, + cancellationToken: CancellationToken | undefined, + exportSymbol: Symbol, + exportingModuleSymbol: Symbol, + exportName: string, + isDefaultExport: boolean, + cb: (ref: Identifier) => void, + ): void { + const importTracker = createImportTracker(sourceFiles, new Set(sourceFiles.map(f => f.fileName)), checker, cancellationToken); + const { importSearches, indirectUsers, singleReferences } = importTracker(exportSymbol, { exportKind: isDefaultExport ? ExportKind.Default : ExportKind.Named, exportingModuleSymbol }, /*isForRename*/ false); + for (const [importLocation] of importSearches) { + cb(importLocation); + } + for (const singleReference of singleReferences) { + if (isIdentifier(singleReference) && isImportTypeNode(singleReference.parent)) { + cb(singleReference); + } + } + for (const indirectUser of indirectUsers) { + for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) { + // Import specifiers should be handled by importSearches + const symbol = checker.getSymbolAtLocation(node); + const hasExportAssignmentDeclaration = some(symbol?.declarations, d => tryCast(d, isExportAssignment) ? true : false); + if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && (symbol === exportSymbol || hasExportAssignmentDeclaration)) { + cb(node); } } } + } + + function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean { + if (!hasMatchingMeaning(singleRef, state)) return false; + if (state.options.use !== FindReferencesUse.Rename) return true; + // Don't rename an import type `import("./module-name")` when renaming `name` in `export = name;` + if (!isIdentifier(singleRef)) return false; + // At `default` in `import { default as x }` or `export { default as x }`, do add a reference, but do not rename. + return !(isImportOrExportSpecifier(singleRef.parent) && singleRef.escapedText === InternalSymbolName.Default); + } + + // Go to the symbol we imported from and find references for it. + function searchForImportedSymbol(symbol: Symbol, state: State): void { + if (!symbol.declarations) return; + + for (const declaration of symbol.declarations) { + const exportingFile = declaration.getSourceFile(); + // Need to search in the file even if it's not in the search-file set, because it might export the symbol. + getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImportExport.Import), state, state.includesSourceFile(exportingFile)); + } + } - function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean { - if (!hasMatchingMeaning(singleRef, state)) return false; - if (state.options.use !== FindReferencesUse.Rename) return true; - // Don't rename an import type `import("./module-name")` when renaming `name` in `export = name;` - if (!isIdentifier(singleRef)) return false; - // At `default` in `import { default as x }` or `export { default as x }`, do add a reference, but do not rename. - return !(isImportOrExportSpecifier(singleRef.parent) && singleRef.escapedText === InternalSymbolName.Default); + /** Search for all occurrences of an identifier in a source file (and filter out the ones that match). */ + function searchForName(sourceFile: SourceFile, search: Search, state: State): void { + if (getNameTable(sourceFile).get(search.escapedText) !== undefined) { + getReferencesInSourceFile(sourceFile, search, state); } + } - // Go to the symbol we imported from and find references for it. - function searchForImportedSymbol(symbol: Symbol, state: State): void { - if (!symbol.declarations) return; + function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined { + return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) + ? checker.getPropertySymbolOfDestructuringAssignment(location as Identifier) + : undefined; + } - for (const declaration of symbol.declarations) { - const exportingFile = declaration.getSourceFile(); - // Need to search in the file even if it's not in the search-file set, because it might export the symbol. - getReferencesInSourceFile(exportingFile, state.createSearch(declaration, symbol, ImportExport.Import), state, state.includesSourceFile(exportingFile)); - } + /** + * Determines the smallest scope in which a symbol may have named references. + * Note that not every construct has been accounted for. This function can + * probably be improved. + * + * @returns undefined if the scope cannot be determined, implying that + * a reference to a symbol can occur anywhere. + */ + function getSymbolScope(symbol: Symbol): Node | undefined { + // If this is the symbol of a named function expression or named class expression, + // then named references are limited to its own scope. + const { declarations, flags, parent, valueDeclaration } = symbol; + if (valueDeclaration && (valueDeclaration.kind === SyntaxKind.FunctionExpression || valueDeclaration.kind === SyntaxKind.ClassExpression)) { + return valueDeclaration; + } + + if (!declarations) { + return undefined; } - /** Search for all occurrences of an identifier in a source file (and filter out the ones that match). */ - function searchForName(sourceFile: SourceFile, search: Search, state: State): void { - if (getNameTable(sourceFile).get(search.escapedText) !== undefined) { - getReferencesInSourceFile(sourceFile, search, state); + // If this is private property or method, the scope is the containing class + if (flags & (SymbolFlags.Property | SymbolFlags.Method)) { + const privateDeclaration = find(declarations, d => hasEffectiveModifier(d, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(d)); + if (privateDeclaration) { + return getAncestor(privateDeclaration, SyntaxKind.ClassDeclaration); } + // Else this is a public property and could be accessed from anywhere. + return undefined; } - function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined { - return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) - ? checker.getPropertySymbolOfDestructuringAssignment(location as Identifier) - : undefined; + // If symbol is of object binding pattern element without property name we would want to + // look for property too and that could be anywhere + if (declarations.some(isObjectBindingElementWithoutPropertyName)) { + return undefined; } - /** - * Determines the smallest scope in which a symbol may have named references. - * Note that not every construct has been accounted for. This function can - * probably be improved. - * - * @returns undefined if the scope cannot be determined, implying that - * a reference to a symbol can occur anywhere. - */ - function getSymbolScope(symbol: Symbol): Node | undefined { - // If this is the symbol of a named function expression or named class expression, - // then named references are limited to its own scope. - const { declarations, flags, parent, valueDeclaration } = symbol; - if (valueDeclaration && (valueDeclaration.kind === SyntaxKind.FunctionExpression || valueDeclaration.kind === SyntaxKind.ClassExpression)) { - return valueDeclaration; - } + /* + If the symbol has a parent, it's globally visible unless: + - It's a private property (handled above). + - It's a type parameter. + - The parent is an external module: then we should only search in the module (and recurse on the export later). + - But if the parent has `export as namespace`, the symbol is globally visible through that namespace. + */ + const exposedByParent = parent && !(symbol.flags & SymbolFlags.TypeParameter); + if (exposedByParent && !(isExternalModuleSymbol(parent) && !parent.globalExports)) { + return undefined; + } - if (!declarations) { + let scope: Node | undefined; + for (const declaration of declarations) { + const container = getContainerNode(declaration); + if (scope && scope !== container) { + // Different declarations have different containers, bail out return undefined; } - // If this is private property or method, the scope is the containing class - if (flags & (SymbolFlags.Property | SymbolFlags.Method)) { - const privateDeclaration = find(declarations, d => hasEffectiveModifier(d, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(d)); - if (privateDeclaration) { - return getAncestor(privateDeclaration, SyntaxKind.ClassDeclaration); - } - // Else this is a public property and could be accessed from anywhere. + if (!container || container.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(container as SourceFile)) { + // This is a global variable and not an external module, any declaration defined + // within this scope is visible outside the file return undefined; } - // If symbol is of object binding pattern element without property name we would want to - // look for property too and that could be anywhere - if (declarations.some(isObjectBindingElementWithoutPropertyName)) { - return undefined; + scope = container; + if (isFunctionExpression(scope)) { + let next: Node | undefined; + while (next = getNextJSDocCommentLocation(scope)) { + scope = next; + } } + } - /* - If the symbol has a parent, it's globally visible unless: - - It's a private property (handled above). - - It's a type parameter. - - The parent is an external module: then we should only search in the module (and recurse on the export later). - - But if the parent has `export as namespace`, the symbol is globally visible through that namespace. - */ - const exposedByParent = parent && !(symbol.flags & SymbolFlags.TypeParameter); - if (exposedByParent && !(isExternalModuleSymbol(parent) && !parent.globalExports)) { - return undefined; - } + // If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.) + // For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.: + // declare module "a" { export type T = number; } + // declare module "b" { import { T } from "a"; export const x: T; } + // So we must search the whole source file. (Because we will mark the source file as seen, we we won't return to it when searching for imports.) + return exposedByParent ? scope!.getSourceFile() : scope; // TODO: GH#18217 + } - let scope: Node | undefined; - for (const declaration of declarations) { - const container = getContainerNode(declaration); - if (scope && scope !== container) { - // Different declarations have different containers, bail out - return undefined; - } + /** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */ + export function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, searchContainer: Node = sourceFile): boolean { + return eachSymbolReferenceInFile(definition, checker, sourceFile, () => true, searchContainer) || false; + } - if (!container || container.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(container as SourceFile)) { - // This is a global variable and not an external module, any declaration defined - // within this scope is visible outside the file - return undefined; - } + export function eachSymbolReferenceInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T, searchContainer: Node = sourceFile): T | undefined { + const symbol = isParameterPropertyDeclaration(definition.parent, definition.parent.parent) + ? first(checker.getSymbolsOfParameterPropertyDeclaration(definition.parent, definition.text)) + : checker.getSymbolAtLocation(definition); + if (!symbol) return undefined; + for (const token of getPossibleSymbolReferenceNodes(sourceFile, symbol.name, searchContainer)) { + if (!isIdentifier(token) || token === definition || token.escapedText !== definition.escapedText) continue; + const referenceSymbol = checker.getSymbolAtLocation(token)!; + if (referenceSymbol === symbol + || checker.getShorthandAssignmentValueSymbol(token.parent) === symbol + || isExportSpecifier(token.parent) && getLocalSymbolForExportSpecifier(token, referenceSymbol, token.parent, checker) === symbol) { + const res = cb(token); + if (res) return res; + } + } + } - scope = container; - if (isFunctionExpression(scope)) { - let next: Node | undefined; - while (next = getNextJSDocCommentLocation(scope)) { - scope = next; - } - } + export function getTopMostDeclarationNamesInFile(declarationName: string, sourceFile: SourceFile): readonly Node[] { + const candidates = filter(getPossibleSymbolReferenceNodes(sourceFile, declarationName), name => !!getDeclarationFromName(name)); + return candidates.reduce((topMost, decl) => { + const depth = getDepth(decl); + if (!some(topMost.declarationNames) || depth === topMost.depth) { + topMost.declarationNames.push(decl); + topMost.depth = depth; + } + else if (depth < topMost.depth) { + topMost.declarationNames = [decl]; + topMost.depth = depth; } + return topMost; + }, { depth: Infinity, declarationNames: [] as Node[] }).declarationNames; - // If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.) - // For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.: - // declare module "a" { export type T = number; } - // declare module "b" { import { T } from "a"; export const x: T; } - // So we must search the whole source file. (Because we will mark the source file as seen, we we won't return to it when searching for imports.) - return exposedByParent ? scope!.getSourceFile() : scope; // TODO: GH#18217 - } - - /** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */ - export function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, searchContainer: Node = sourceFile): boolean { - return eachSymbolReferenceInFile(definition, checker, sourceFile, () => true, searchContainer) || false; - } - - export function eachSymbolReferenceInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T, searchContainer: Node = sourceFile): T | undefined { - const symbol = isParameterPropertyDeclaration(definition.parent, definition.parent.parent) - ? first(checker.getSymbolsOfParameterPropertyDeclaration(definition.parent, definition.text)) - : checker.getSymbolAtLocation(definition); - if (!symbol) return undefined; - for (const token of getPossibleSymbolReferenceNodes(sourceFile, symbol.name, searchContainer)) { - if (!isIdentifier(token) || token === definition || token.escapedText !== definition.escapedText) continue; - const referenceSymbol = checker.getSymbolAtLocation(token)!; - if (referenceSymbol === symbol - || checker.getShorthandAssignmentValueSymbol(token.parent) === symbol - || isExportSpecifier(token.parent) && getLocalSymbolForExportSpecifier(token, referenceSymbol, token.parent, checker) === symbol) { - const res = cb(token); - if (res) return res; - } + function getDepth(declaration: Node | undefined) { + let depth = 0; + while (declaration) { + declaration = getContainerNode(declaration); + depth++; } + return depth; } + } - export function getTopMostDeclarationNamesInFile(declarationName: string, sourceFile: SourceFile): readonly Node[] { - const candidates = filter(getPossibleSymbolReferenceNodes(sourceFile, declarationName), name => !!getDeclarationFromName(name)); - return candidates.reduce((topMost, decl) => { - const depth = getDepth(decl); - if (!some(topMost.declarationNames) || depth === topMost.depth) { - topMost.declarationNames.push(decl); - topMost.depth = depth; - } - else if (depth < topMost.depth) { - topMost.declarationNames = [decl]; - topMost.depth = depth; - } - return topMost; - }, { depth: Infinity, declarationNames: [] as Node[] }).declarationNames; - - function getDepth(declaration: Node | undefined) { - let depth = 0; - while (declaration) { - declaration = getContainerNode(declaration); - depth++; + export function someSignatureUsage( + signature: SignatureDeclaration, + sourceFiles: readonly SourceFile[], + checker: TypeChecker, + cb: (name: Identifier, call?: CallExpression) => boolean + ): boolean { + if (!signature.name || !isIdentifier(signature.name)) return false; + + const symbol = Debug.checkDefined(checker.getSymbolAtLocation(signature.name)); + + for (const sourceFile of sourceFiles) { + for (const name of getPossibleSymbolReferenceNodes(sourceFile, symbol.name)) { + if (!isIdentifier(name) || name === signature.name || name.escapedText !== signature.name.escapedText) continue; + const called = climbPastPropertyAccess(name); + const call = isCallExpression(called.parent) && called.parent.expression === called ? called.parent : undefined; + const referenceSymbol = checker.getSymbolAtLocation(name); + if (referenceSymbol && checker.getRootSymbols(referenceSymbol).some(s => s === symbol)) { + if (cb(name, call)) { + return true; + } } - return depth; } } + return false; + } - export function someSignatureUsage( - signature: SignatureDeclaration, - sourceFiles: readonly SourceFile[], - checker: TypeChecker, - cb: (name: Identifier, call?: CallExpression) => boolean - ): boolean { - if (!signature.name || !isIdentifier(signature.name)) return false; + function getPossibleSymbolReferenceNodes(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly Node[] { + return getPossibleSymbolReferencePositions(sourceFile, symbolName, container).map(pos => getTouchingPropertyName(sourceFile, pos)); + } - const symbol = Debug.checkDefined(checker.getSymbolAtLocation(signature.name)); + function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly number[] { + const positions: number[] = []; - for (const sourceFile of sourceFiles) { - for (const name of getPossibleSymbolReferenceNodes(sourceFile, symbol.name)) { - if (!isIdentifier(name) || name === signature.name || name.escapedText !== signature.name.escapedText) continue; - const called = climbPastPropertyAccess(name); - const call = isCallExpression(called.parent) && called.parent.expression === called ? called.parent : undefined; - const referenceSymbol = checker.getSymbolAtLocation(name); - if (referenceSymbol && checker.getRootSymbols(referenceSymbol).some(s => s === symbol)) { - if (cb(name, call)) { - return true; - } - } - } - } - return false; - } + /// TODO: Cache symbol existence for files to save text search + // Also, need to make this work for unicode escapes. - function getPossibleSymbolReferenceNodes(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly Node[] { - return getPossibleSymbolReferencePositions(sourceFile, symbolName, container).map(pos => getTouchingPropertyName(sourceFile, pos)); + // Be resilient in the face of a symbol with no name or zero length name + if (!symbolName || !symbolName.length) { + return positions; } - function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): readonly number[] { - const positions: number[] = []; + const text = sourceFile.text; + const sourceLength = text.length; + const symbolNameLength = symbolName.length; - /// TODO: Cache symbol existence for files to save text search - // Also, need to make this work for unicode escapes. + let position = text.indexOf(symbolName, container.pos); + while (position >= 0) { + // If we are past the end, stop looking + if (position > container.end) break; - // Be resilient in the face of a symbol with no name or zero length name - if (!symbolName || !symbolName.length) { - return positions; - } + // We found a match. Make sure it's not part of a larger word (i.e. the char + // before and after it have to be a non-identifier char). + const endPosition = position + symbolNameLength; - const text = sourceFile.text; - const sourceLength = text.length; - const symbolNameLength = symbolName.length; + if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) && + (endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) { + // Found a real match. Keep searching. + positions.push(position); + } + position = text.indexOf(symbolName, position + symbolNameLength + 1); + } - let position = text.indexOf(symbolName, container.pos); - while (position >= 0) { - // If we are past the end, stop looking - if (position > container.end) break; + return positions; + } - // We found a match. Make sure it's not part of a larger word (i.e. the char - // before and after it have to be a non-identifier char). - const endPosition = position + symbolNameLength; + function getLabelReferencesInNode(container: Node, targetLabel: Identifier): SymbolAndEntries[] { + const sourceFile = container.getSourceFile(); + const labelName = targetLabel.text; + const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, labelName, container), node => + // Only pick labels that are either the target label, or have a target that is the target label + node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel) ? nodeEntry(node) : undefined); + return [{ definition: { type: DefinitionKind.Label, node: targetLabel }, references }]; + } - if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) && - (endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) { - // Found a real match. Keep searching. - positions.push(position); + function isValidReferencePosition(node: Node, searchSymbolName: string): boolean { + // Compare the length so we filter out strict superstrings of the symbol we are looking for + switch (node.kind) { + case SyntaxKind.PrivateIdentifier: + if (isJSDocMemberName(node.parent)) { + return true; } - position = text.indexOf(symbolName, position + symbolNameLength + 1); + // falls through I guess + case SyntaxKind.Identifier: + return (node as PrivateIdentifier | Identifier).text.length === searchSymbolName.length; + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.StringLiteral: { + const str = node as StringLiteralLike; + return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || (isCallExpression(node.parent) && isBindableObjectDefinePropertyCall(node.parent) && node.parent.arguments[1] === node)) && + str.text.length === searchSymbolName.length; } - return positions; - } + case SyntaxKind.NumericLiteral: + return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral) && (node as NumericLiteral).text.length === searchSymbolName.length; + + case SyntaxKind.DefaultKeyword: + return "default".length === searchSymbolName.length; - function getLabelReferencesInNode(container: Node, targetLabel: Identifier): SymbolAndEntries[] { - const sourceFile = container.getSourceFile(); - const labelName = targetLabel.text; - const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, labelName, container), node => - // Only pick labels that are either the target label, or have a target that is the target label - node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel) ? nodeEntry(node) : undefined); - return [{ definition: { type: DefinitionKind.Label, node: targetLabel }, references }]; + default: + return false; } + } - function isValidReferencePosition(node: Node, searchSymbolName: string): boolean { - // Compare the length so we filter out strict superstrings of the symbol we are looking for - switch (node.kind) { - case SyntaxKind.PrivateIdentifier: - if (isJSDocMemberName(node.parent)) { - return true; - } - // falls through I guess - case SyntaxKind.Identifier: - return (node as PrivateIdentifier | Identifier).text.length === searchSymbolName.length; - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.StringLiteral: { - const str = node as StringLiteralLike; - return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || (isCallExpression(node.parent) && isBindableObjectDefinePropertyCall(node.parent) && node.parent.arguments[1] === node)) && - str.text.length === searchSymbolName.length; + function getAllReferencesForImportMeta(sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { + const references = flatMap(sourceFiles, sourceFile => { + cancellationToken.throwIfCancellationRequested(); + return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, "meta", sourceFile), node => { + const parent = node.parent; + if (isImportMeta(parent)) { + return nodeEntry(parent); + } + }); + }); + return references.length ? [{ definition: { type: DefinitionKind.Keyword, node: references[0].node }, references }] : undefined; + } + + function getAllReferencesForKeyword(sourceFiles: readonly SourceFile[], keywordKind: SyntaxKind, cancellationToken: CancellationToken, filter?: (node: Node) => boolean): SymbolAndEntries[] | undefined { + const references = flatMap(sourceFiles, sourceFile => { + cancellationToken.throwIfCancellationRequested(); + return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, tokenToString(keywordKind)!, sourceFile), referenceLocation => { + if (referenceLocation.kind === keywordKind && (!filter || filter(referenceLocation))) { + return nodeEntry(referenceLocation); } + }); + }); + return references.length ? [{ definition: { type: DefinitionKind.Keyword, node: references[0].node }, references }] : undefined; + } - case SyntaxKind.NumericLiteral: - return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral) && (node as NumericLiteral).text.length === searchSymbolName.length; + function getReferencesInSourceFile(sourceFile: SourceFile, search: Search, state: State, addReferencesHere = true): void { + state.cancellationToken.throwIfCancellationRequested(); + return getReferencesInContainer(sourceFile, sourceFile, search, state, addReferencesHere); + } - case SyntaxKind.DefaultKeyword: - return "default".length === searchSymbolName.length; + /** + * Search within node "container" for references for a search value, where the search value is defined as a + * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). + * searchLocation: a node where the search value + */ + function getReferencesInContainer(container: Node, sourceFile: SourceFile, search: Search, state: State, addReferencesHere: boolean): void { + if (!state.markSearchedSymbols(sourceFile, search.allSearchSymbols)) { + return; + } - default: - return false; - } + for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container)) { + getReferencesAtLocation(sourceFile, position, search, state, addReferencesHere); } + } - function getAllReferencesForImportMeta(sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { - const references = flatMap(sourceFiles, sourceFile => { - cancellationToken.throwIfCancellationRequested(); - return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, "meta", sourceFile), node => { - const parent = node.parent; - if (isImportMeta(parent)) { - return nodeEntry(parent); - } - }); - }); - return references.length ? [{ definition: { type: DefinitionKind.Keyword, node: references[0].node }, references }] : undefined; + function hasMatchingMeaning(referenceLocation: Node, state: State): boolean { + return !!(getMeaningFromLocation(referenceLocation) & state.searchMeaning); + } + + function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State, addReferencesHere: boolean): void { + const referenceLocation = getTouchingPropertyName(sourceFile, position); + + if (!isValidReferencePosition(referenceLocation, search.text)) { + // This wasn't the start of a token. Check to see if it might be a + // match in a comment or string if that's what the caller is asking + // for. + if (!state.options.implementations && (state.options.findInStrings && isInString(sourceFile, position) || state.options.findInComments && isInNonReferenceComment(sourceFile, position))) { + // In the case where we're looking inside comments/strings, we don't have + // an actual definition. So just use 'undefined' here. Features like + // 'Rename' won't care (as they ignore the definitions), and features like + // 'FindReferences' will just filter out these results. + state.addStringOrCommentReference(sourceFile.fileName, createTextSpan(position, search.text.length)); + } + + return; } - function getAllReferencesForKeyword(sourceFiles: readonly SourceFile[], keywordKind: SyntaxKind, cancellationToken: CancellationToken, filter?: (node: Node) => boolean): SymbolAndEntries[] | undefined { - const references = flatMap(sourceFiles, sourceFile => { - cancellationToken.throwIfCancellationRequested(); - return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, tokenToString(keywordKind)!, sourceFile), referenceLocation => { - if (referenceLocation.kind === keywordKind && (!filter || filter(referenceLocation))) { - return nodeEntry(referenceLocation); - } - }); - }); - return references.length ? [{ definition: { type: DefinitionKind.Keyword, node: references[0].node }, references }] : undefined; + if (!hasMatchingMeaning(referenceLocation, state)) return; + + let referenceSymbol = state.checker.getSymbolAtLocation(referenceLocation); + if (!referenceSymbol) { + return; } - function getReferencesInSourceFile(sourceFile: SourceFile, search: Search, state: State, addReferencesHere = true): void { - state.cancellationToken.throwIfCancellationRequested(); - return getReferencesInContainer(sourceFile, sourceFile, search, state, addReferencesHere); + const parent = referenceLocation.parent; + if (isImportSpecifier(parent) && parent.propertyName === referenceLocation) { + // This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again. + return; } - /** - * Search within node "container" for references for a search value, where the search value is defined as a - * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). - * searchLocation: a node where the search value - */ - function getReferencesInContainer(container: Node, sourceFile: SourceFile, search: Search, state: State, addReferencesHere: boolean): void { - if (!state.markSearchedSymbols(sourceFile, search.allSearchSymbols)) { - return; - } + if (isExportSpecifier(parent)) { + Debug.assert(referenceLocation.kind === SyntaxKind.Identifier); + getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state, addReferencesHere); + return; + } - for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container)) { - getReferencesAtLocation(sourceFile, position, search, state, addReferencesHere); - } + const relatedSymbol = getRelatedSymbol(search, referenceSymbol, referenceLocation, state); + if (!relatedSymbol) { + getReferenceForShorthandProperty(referenceSymbol, search, state); + return; } - function hasMatchingMeaning(referenceLocation: Node, state: State): boolean { - return !!(getMeaningFromLocation(referenceLocation) & state.searchMeaning); + switch (state.specialSearchKind) { + case SpecialSearchKind.None: + if (addReferencesHere) addReference(referenceLocation, relatedSymbol, state); + break; + case SpecialSearchKind.Constructor: + addConstructorReferences(referenceLocation, sourceFile, search, state); + break; + case SpecialSearchKind.Class: + addClassStaticThisReferences(referenceLocation, search, state); + break; + default: + Debug.assertNever(state.specialSearchKind); } - function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State, addReferencesHere: boolean): void { - const referenceLocation = getTouchingPropertyName(sourceFile, position); + // Use the parent symbol if the location is commonjs require syntax on javascript files only. + if (isInJSFile(referenceLocation) + && referenceLocation.parent.kind === SyntaxKind.BindingElement + && isVariableDeclarationInitializedToBareOrAccessedRequire(referenceLocation.parent.parent.parent)) { + referenceSymbol = referenceLocation.parent.symbol; + // The parent will not have a symbol if it's an ObjectBindingPattern (when destructuring is used). In + // this case, just skip it, since the bound identifiers are not an alias of the import. + if (!referenceSymbol) return; + } - if (!isValidReferencePosition(referenceLocation, search.text)) { - // This wasn't the start of a token. Check to see if it might be a - // match in a comment or string if that's what the caller is asking - // for. - if (!state.options.implementations && (state.options.findInStrings && isInString(sourceFile, position) || state.options.findInComments && isInNonReferenceComment(sourceFile, position))) { - // In the case where we're looking inside comments/strings, we don't have - // an actual definition. So just use 'undefined' here. Features like - // 'Rename' won't care (as they ignore the definitions), and features like - // 'FindReferences' will just filter out these results. - state.addStringOrCommentReference(sourceFile.fileName, createTextSpan(position, search.text.length)); - } + getImportOrExportReferences(referenceLocation, referenceSymbol, search, state); + } - return; - } + function getReferencesAtExportSpecifier( + referenceLocation: Identifier, + referenceSymbol: Symbol, + exportSpecifier: ExportSpecifier, + search: Search, + state: State, + addReferencesHere: boolean, + alwaysGetReferences?: boolean, + ): void { + Debug.assert(!alwaysGetReferences || !!state.options.providePrefixAndSuffixTextForRename, "If alwaysGetReferences is true, then prefix/suffix text must be enabled"); - if (!hasMatchingMeaning(referenceLocation, state)) return; + const { parent, propertyName, name } = exportSpecifier; + const exportDeclaration = parent.parent; + const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker); + if (!alwaysGetReferences && !search.includes(localSymbol)) { + return; + } - let referenceSymbol = state.checker.getSymbolAtLocation(referenceLocation); - if (!referenceSymbol) { - return; + if (!propertyName) { + // Don't rename at `export { default } from "m";`. (but do continue to search for imports of the re-export) + if (!(state.options.use === FindReferencesUse.Rename && (name.escapedText === InternalSymbolName.Default))) { + addRef(); } - - const parent = referenceLocation.parent; - if (isImportSpecifier(parent) && parent.propertyName === referenceLocation) { - // This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again. - return; + } + else if (referenceLocation === propertyName) { + // For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export. + // For `export { foo as bar };`, where `foo` is a local, so add it now. + if (!exportDeclaration.moduleSpecifier) { + addRef(); } - if (isExportSpecifier(parent)) { - Debug.assert(referenceLocation.kind === SyntaxKind.Identifier); - getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state, addReferencesHere); - return; + if (addReferencesHere && state.options.use !== FindReferencesUse.Rename && state.markSeenReExportRHS(name)) { + addReference(name, Debug.checkDefined(exportSpecifier.symbol), state); } - - const relatedSymbol = getRelatedSymbol(search, referenceSymbol, referenceLocation, state); - if (!relatedSymbol) { - getReferenceForShorthandProperty(referenceSymbol, search, state); - return; + } + else { + if (state.markSeenReExportRHS(referenceLocation)) { + addRef(); } + } - switch (state.specialSearchKind) { - case SpecialSearchKind.None: - if (addReferencesHere) addReference(referenceLocation, relatedSymbol, state); - break; - case SpecialSearchKind.Constructor: - addConstructorReferences(referenceLocation, sourceFile, search, state); - break; - case SpecialSearchKind.Class: - addClassStaticThisReferences(referenceLocation, search, state); - break; - default: - Debug.assertNever(state.specialSearchKind); - } - - // Use the parent symbol if the location is commonjs require syntax on javascript files only. - if (isInJSFile(referenceLocation) - && referenceLocation.parent.kind === SyntaxKind.BindingElement - && isVariableDeclarationInitializedToBareOrAccessedRequire(referenceLocation.parent.parent.parent)) { - referenceSymbol = referenceLocation.parent.symbol; - // The parent will not have a symbol if it's an ObjectBindingPattern (when destructuring is used). In - // this case, just skip it, since the bound identifiers are not an alias of the import. - if (!referenceSymbol) return; - } - - getImportOrExportReferences(referenceLocation, referenceSymbol, search, state); - } - - function getReferencesAtExportSpecifier( - referenceLocation: Identifier, - referenceSymbol: Symbol, - exportSpecifier: ExportSpecifier, - search: Search, - state: State, - addReferencesHere: boolean, - alwaysGetReferences?: boolean, - ): void { - Debug.assert(!alwaysGetReferences || !!state.options.providePrefixAndSuffixTextForRename, "If alwaysGetReferences is true, then prefix/suffix text must be enabled"); - - const { parent, propertyName, name } = exportSpecifier; - const exportDeclaration = parent.parent; - const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker); - if (!alwaysGetReferences && !search.includes(localSymbol)) { - return; + // For `export { foo as bar }`, rename `foo`, but not `bar`. + if (!isForRenameWithPrefixAndSuffixText(state.options) || alwaysGetReferences) { + const isDefaultExport = referenceLocation.originalKeywordKind === SyntaxKind.DefaultKeyword + || exportSpecifier.name.originalKeywordKind === SyntaxKind.DefaultKeyword; + const exportKind = isDefaultExport ? ExportKind.Default : ExportKind.Named; + const exportSymbol = Debug.checkDefined(exportSpecifier.symbol); + const exportInfo = getExportInfo(exportSymbol, exportKind, state.checker); + if (exportInfo) { + searchForImportsOfExport(referenceLocation, exportSymbol, exportInfo, state); } + } - if (!propertyName) { - // Don't rename at `export { default } from "m";`. (but do continue to search for imports of the re-export) - if (!(state.options.use === FindReferencesUse.Rename && (name.escapedText === InternalSymbolName.Default))) { - addRef(); - } - } - else if (referenceLocation === propertyName) { - // For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export. - // For `export { foo as bar };`, where `foo` is a local, so add it now. - if (!exportDeclaration.moduleSpecifier) { - addRef(); - } + // At `export { x } from "foo"`, also search for the imported symbol `"foo".x`. + if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName && !isForRenameWithPrefixAndSuffixText(state.options)) { + const imported = state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); + if (imported) searchForImportedSymbol(imported, state); + } - if (addReferencesHere && state.options.use !== FindReferencesUse.Rename && state.markSeenReExportRHS(name)) { - addReference(name, Debug.checkDefined(exportSpecifier.symbol), state); - } - } - else { - if (state.markSeenReExportRHS(referenceLocation)) { - addRef(); - } - } + function addRef() { + if (addReferencesHere) addReference(referenceLocation, localSymbol, state); + } + } + + function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol { + return isExportSpecifierAlias(referenceLocation, exportSpecifier) && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier) || referenceSymbol; + } + + function isExportSpecifierAlias(referenceLocation: Identifier, exportSpecifier: ExportSpecifier): boolean { + const { parent, propertyName, name } = exportSpecifier; + Debug.assert(propertyName === referenceLocation || name === referenceLocation); + if (propertyName) { + // Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol. + return propertyName === referenceLocation; + } + else { + // `export { foo } from "foo"` is a re-export. + // `export { foo };` is not a re-export, it creates an alias for the local variable `foo`. + return !parent.parent.moduleSpecifier; + } + } - // For `export { foo as bar }`, rename `foo`, but not `bar`. - if (!isForRenameWithPrefixAndSuffixText(state.options) || alwaysGetReferences) { - const isDefaultExport = referenceLocation.originalKeywordKind === SyntaxKind.DefaultKeyword - || exportSpecifier.name.originalKeywordKind === SyntaxKind.DefaultKeyword; - const exportKind = isDefaultExport ? ExportKind.Default : ExportKind.Named; - const exportSymbol = Debug.checkDefined(exportSpecifier.symbol); - const exportInfo = getExportInfo(exportSymbol, exportKind, state.checker); - if (exportInfo) { - searchForImportsOfExport(referenceLocation, exportSymbol, exportInfo, state); - } - } + function getImportOrExportReferences(referenceLocation: Node, referenceSymbol: Symbol, search: Search, state: State): void { + const importOrExport = getImportOrExportSymbol(referenceLocation, referenceSymbol, state.checker, search.comingFrom === ImportExport.Export); + if (!importOrExport) return; - // At `export { x } from "foo"`, also search for the imported symbol `"foo".x`. - if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName && !isForRenameWithPrefixAndSuffixText(state.options)) { - const imported = state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); - if (imported) searchForImportedSymbol(imported, state); - } + const { symbol } = importOrExport; - function addRef() { - if (addReferencesHere) addReference(referenceLocation, localSymbol, state); + if (importOrExport.kind === ImportExport.Import) { + if (!(isForRenameWithPrefixAndSuffixText(state.options))) { + searchForImportedSymbol(symbol, state); } } - - function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol { - return isExportSpecifierAlias(referenceLocation, exportSpecifier) && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier) || referenceSymbol; + else { + searchForImportsOfExport(referenceLocation, symbol, importOrExport.exportInfo, state); } + } - function isExportSpecifierAlias(referenceLocation: Identifier, exportSpecifier: ExportSpecifier): boolean { - const { parent, propertyName, name } = exportSpecifier; - Debug.assert(propertyName === referenceLocation || name === referenceLocation); - if (propertyName) { - // Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol. - return propertyName === referenceLocation; - } - else { - // `export { foo } from "foo"` is a re-export. - // `export { foo };` is not a re-export, it creates an alias for the local variable `foo`. - return !parent.parent.moduleSpecifier; - } + function getReferenceForShorthandProperty({ flags, valueDeclaration }: Symbol, search: Search, state: State): void { + const shorthandValueSymbol = state.checker.getShorthandAssignmentValueSymbol(valueDeclaration)!; + const name = valueDeclaration && getNameOfDeclaration(valueDeclaration); + /* + * Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment + * has two meanings: property name and property value. Therefore when we do findAllReference at the position where + * an identifier is declared, the language service should return the position of the variable declaration as well as + * the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the + * position of property accessing, the referenceEntry of such position will be handled in the first case. + */ + if (!(flags & SymbolFlags.Transient) && name && search.includes(shorthandValueSymbol)) { + addReference(name, shorthandValueSymbol, state); } + } - function getImportOrExportReferences(referenceLocation: Node, referenceSymbol: Symbol, search: Search, state: State): void { - const importOrExport = getImportOrExportSymbol(referenceLocation, referenceSymbol, state.checker, search.comingFrom === ImportExport.Export); - if (!importOrExport) return; - - const { symbol } = importOrExport; + function addReference(referenceLocation: Node, relatedSymbol: Symbol | RelatedSymbol, state: State): void { + const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line local/no-in-operator - if (importOrExport.kind === ImportExport.Import) { - if (!(isForRenameWithPrefixAndSuffixText(state.options))) { - searchForImportedSymbol(symbol, state); - } - } - else { - searchForImportsOfExport(referenceLocation, symbol, importOrExport.exportInfo, state); - } + // if rename symbol from default export anonymous function, for example `export default function() {}`, we do not need to add reference + if (state.options.use === FindReferencesUse.Rename && referenceLocation.kind === SyntaxKind.DefaultKeyword) { + return; } - function getReferenceForShorthandProperty({ flags, valueDeclaration }: Symbol, search: Search, state: State): void { - const shorthandValueSymbol = state.checker.getShorthandAssignmentValueSymbol(valueDeclaration)!; - const name = valueDeclaration && getNameOfDeclaration(valueDeclaration); - /* - * Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment - * has two meanings: property name and property value. Therefore when we do findAllReference at the position where - * an identifier is declared, the language service should return the position of the variable declaration as well as - * the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the - * position of property accessing, the referenceEntry of such position will be handled in the first case. - */ - if (!(flags & SymbolFlags.Transient) && name && search.includes(shorthandValueSymbol)) { - addReference(name, shorthandValueSymbol, state); - } + const addRef = state.referenceAdder(symbol); + if (state.options.implementations) { + addImplementationReferences(referenceLocation, addRef, state); + } + else { + addRef(referenceLocation, kind); } + } - function addReference(referenceLocation: Node, relatedSymbol: Symbol | RelatedSymbol, state: State): void { - const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line local/no-in-operator + /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ + function addConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void { + if (isNewExpressionTarget(referenceLocation)) { + addReference(referenceLocation, search.symbol, state); + } - // if rename symbol from default export anonymous function, for example `export default function() {}`, we do not need to add reference - if (state.options.use === FindReferencesUse.Rename && referenceLocation.kind === SyntaxKind.DefaultKeyword) { - return; - } + const pusher = () => state.referenceAdder(search.symbol); - const addRef = state.referenceAdder(symbol); - if (state.options.implementations) { - addImplementationReferences(referenceLocation, addRef, state); - } - else { - addRef(referenceLocation, kind); - } + if (isClassLike(referenceLocation.parent)) { + Debug.assert(referenceLocation.kind === SyntaxKind.DefaultKeyword || referenceLocation.parent.name === referenceLocation); + // This is the class declaration containing the constructor. + findOwnConstructorReferences(search.symbol, sourceFile, pusher()); } - - /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ - function addConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void { - if (isNewExpressionTarget(referenceLocation)) { - addReference(referenceLocation, search.symbol, state); + else { + // If this class appears in `extends C`, then the extending class' "super" calls are references. + const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); + if (classExtending) { + findSuperConstructorAccesses(classExtending, pusher()); + findInheritedConstructorReferences(classExtending, state); } + } + } - const pusher = () => state.referenceAdder(search.symbol); - - if (isClassLike(referenceLocation.parent)) { - Debug.assert(referenceLocation.kind === SyntaxKind.DefaultKeyword || referenceLocation.parent.name === referenceLocation); - // This is the class declaration containing the constructor. - findOwnConstructorReferences(search.symbol, sourceFile, pusher()); - } - else { - // If this class appears in `extends C`, then the extending class' "super" calls are references. - const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); - if (classExtending) { - findSuperConstructorAccesses(classExtending, pusher()); - findInheritedConstructorReferences(classExtending, state); - } + function addClassStaticThisReferences(referenceLocation: Node, search: Search, state: State): void { + addReference(referenceLocation, search.symbol, state); + const classLike = referenceLocation.parent; + if (state.options.use === FindReferencesUse.Rename || !isClassLike(classLike)) return; + Debug.assert(classLike.name === referenceLocation); + const addRef = state.referenceAdder(search.symbol); + for (const member of classLike.members) { + if (!(isMethodOrAccessor(member) && isStatic(member))) { + continue; + } + if (member.body) { + member.body.forEachChild(function cb(node) { + if (node.kind === SyntaxKind.ThisKeyword) { + addRef(node); + } + else if (!isFunctionLike(node) && !isClassLike(node)) { + node.forEachChild(cb); + } + }); } } + } - function addClassStaticThisReferences(referenceLocation: Node, search: Search, state: State): void { - addReference(referenceLocation, search.symbol, state); - const classLike = referenceLocation.parent; - if (state.options.use === FindReferencesUse.Rename || !isClassLike(classLike)) return; - Debug.assert(classLike.name === referenceLocation); - const addRef = state.referenceAdder(search.symbol); - for (const member of classLike.members) { - if (!(isMethodOrAccessor(member) && isStatic(member))) { - continue; - } - if (member.body) { - member.body.forEachChild(function cb(node) { - if (node.kind === SyntaxKind.ThisKeyword) { - addRef(node); - } - else if (!isFunctionLike(node) && !isClassLike(node)) { - node.forEachChild(cb); - } - }); + /** + * `classSymbol` is the class where the constructor was defined. + * Reference the constructor and all calls to `new this()`. + */ + function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void { + const constructorSymbol = getClassConstructorSymbol(classSymbol); + if (constructorSymbol && constructorSymbol.declarations) { + for (const decl of constructorSymbol.declarations) { + const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!; + Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); + addNode(ctrKeyword); + } + } + + if (classSymbol.exports) { + classSymbol.exports.forEach(member => { + const decl = member.valueDeclaration; + if (decl && decl.kind === SyntaxKind.MethodDeclaration) { + const body = (decl as MethodDeclaration).body; + if (body) { + forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { + if (isNewExpressionTarget(thisKeyword)) { + addNode(thisKeyword); + } + }); + } } - } + }); } + } - /** - * `classSymbol` is the class where the constructor was defined. - * Reference the constructor and all calls to `new this()`. - */ - function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void { - const constructorSymbol = getClassConstructorSymbol(classSymbol); - if (constructorSymbol && constructorSymbol.declarations) { - for (const decl of constructorSymbol.declarations) { - const ctrKeyword = findChildOfKind(decl, SyntaxKind.ConstructorKeyword, sourceFile)!; - Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); - addNode(ctrKeyword); - } - } + function getClassConstructorSymbol(classSymbol: Symbol): Symbol | undefined { + return classSymbol.members && classSymbol.members.get(InternalSymbolName.Constructor); + } - if (classSymbol.exports) { - classSymbol.exports.forEach(member => { - const decl = member.valueDeclaration; - if (decl && decl.kind === SyntaxKind.MethodDeclaration) { - const body = (decl as MethodDeclaration).body; - if (body) { - forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { - if (isNewExpressionTarget(thisKeyword)) { - addNode(thisKeyword); - } - }); - } + /** Find references to `super` in the constructor of an extending class. */ + function findSuperConstructorAccesses(classDeclaration: ClassLikeDeclaration, addNode: (node: Node) => void): void { + const constructor = getClassConstructorSymbol(classDeclaration.symbol); + if (!(constructor && constructor.declarations)) { + return; + } + + for (const decl of constructor.declarations) { + Debug.assert(decl.kind === SyntaxKind.Constructor); + const body = (decl as ConstructorDeclaration).body; + if (body) { + forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { + if (isCallExpressionTarget(node)) { + addNode(node); } }); } } + } - function getClassConstructorSymbol(classSymbol: Symbol): Symbol | undefined { - return classSymbol.members && classSymbol.members.get(InternalSymbolName.Constructor); - } + function hasOwnConstructor(classDeclaration: ClassLikeDeclaration): boolean { + return !!getClassConstructorSymbol(classDeclaration.symbol); + } - /** Find references to `super` in the constructor of an extending class. */ - function findSuperConstructorAccesses(classDeclaration: ClassLikeDeclaration, addNode: (node: Node) => void): void { - const constructor = getClassConstructorSymbol(classDeclaration.symbol); - if (!(constructor && constructor.declarations)) { - return; - } + function findInheritedConstructorReferences(classDeclaration: ClassLikeDeclaration, state: State): void { + if (hasOwnConstructor(classDeclaration)) return; + const classSymbol = classDeclaration.symbol; + const search = state.createSearch(/*location*/ undefined, classSymbol, /*comingFrom*/ undefined); + getReferencesInContainerOrFiles(classSymbol, state, search); + } - for (const decl of constructor.declarations) { - Debug.assert(decl.kind === SyntaxKind.Constructor); - const body = (decl as ConstructorDeclaration).body; - if (body) { - forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { - if (isCallExpressionTarget(node)) { - addNode(node); - } - }); - } - } + function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void { + // Check if we found a function/propertyAssignment/method with an implementation or initializer + if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { + addReference(refNode); + return; } - function hasOwnConstructor(classDeclaration: ClassLikeDeclaration): boolean { - return !!getClassConstructorSymbol(classDeclaration.symbol); + if (refNode.kind !== SyntaxKind.Identifier) { + return; } - function findInheritedConstructorReferences(classDeclaration: ClassLikeDeclaration, state: State): void { - if (hasOwnConstructor(classDeclaration)) return; - const classSymbol = classDeclaration.symbol; - const search = state.createSearch(/*location*/ undefined, classSymbol, /*comingFrom*/ undefined); - getReferencesInContainerOrFiles(classSymbol, state, search); + if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + // Go ahead and dereference the shorthand assignment by going to its definition + getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addReference); } - function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void { - // Check if we found a function/propertyAssignment/method with an implementation or initializer - if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { - addReference(refNode); - return; - } - - if (refNode.kind !== SyntaxKind.Identifier) { - return; - } - - if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - // Go ahead and dereference the shorthand assignment by going to its definition - getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addReference); - } + // Check if the node is within an extends or implements clause + const containingClass = getContainingClassIfInHeritageClause(refNode); + if (containingClass) { + addReference(containingClass); + return; + } - // Check if the node is within an extends or implements clause - const containingClass = getContainingClassIfInHeritageClause(refNode); - if (containingClass) { - addReference(containingClass); - return; + // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface + // Find the first node whose parent isn't a type node -- i.e., the highest type node. + const typeNode = findAncestor(refNode, a => !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent))!; + const typeHavingNode = typeNode.parent; + if (hasType(typeHavingNode) && typeHavingNode.type === typeNode && state.markSeenContainingTypeReference(typeHavingNode)) { + if (hasInitializer(typeHavingNode)) { + addIfImplementation(typeHavingNode.initializer!); } - - // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface - // Find the first node whose parent isn't a type node -- i.e., the highest type node. - const typeNode = findAncestor(refNode, a => !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent))!; - const typeHavingNode = typeNode.parent; - if (hasType(typeHavingNode) && typeHavingNode.type === typeNode && state.markSeenContainingTypeReference(typeHavingNode)) { - if (hasInitializer(typeHavingNode)) { - addIfImplementation(typeHavingNode.initializer!); - } - else if (isFunctionLike(typeHavingNode) && (typeHavingNode as FunctionLikeDeclaration).body) { - const body = (typeHavingNode as FunctionLikeDeclaration).body!; - if (body.kind === SyntaxKind.Block) { - forEachReturnStatement(body as Block, returnStatement => { - if (returnStatement.expression) addIfImplementation(returnStatement.expression); - }); - } - else { - addIfImplementation(body); - } + else if (isFunctionLike(typeHavingNode) && (typeHavingNode as FunctionLikeDeclaration).body) { + const body = (typeHavingNode as FunctionLikeDeclaration).body!; + if (body.kind === SyntaxKind.Block) { + forEachReturnStatement(body as Block, returnStatement => { + if (returnStatement.expression) addIfImplementation(returnStatement.expression); + }); } - else if (isAssertionExpression(typeHavingNode)) { - addIfImplementation(typeHavingNode.expression); + else { + addIfImplementation(body); } } - - function addIfImplementation(e: Expression): void { - if (isImplementationExpression(e)) addReference(e); + else if (isAssertionExpression(typeHavingNode)) { + addIfImplementation(typeHavingNode.expression); } } - function getContainingClassIfInHeritageClause(node: Node): ClassLikeDeclaration | InterfaceDeclaration | undefined { - return isIdentifier(node) || isPropertyAccessExpression(node) ? getContainingClassIfInHeritageClause(node.parent) - : isExpressionWithTypeArguments(node) ? tryCast(node.parent.parent, isClassLike) : undefined; + function addIfImplementation(e: Expression): void { + if (isImplementationExpression(e)) addReference(e); } + } - /** - * Returns true if this is an expression that can be considered an implementation - */ - function isImplementationExpression(node: Expression): boolean { - switch (node.kind) { - case SyntaxKind.ParenthesizedExpression: - return isImplementationExpression((node as ParenthesizedExpression).expression); - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ClassExpression: - case SyntaxKind.ArrayLiteralExpression: - return true; - default: - return false; - } - } + function getContainingClassIfInHeritageClause(node: Node): ClassLikeDeclaration | InterfaceDeclaration | undefined { + return isIdentifier(node) || isPropertyAccessExpression(node) ? getContainingClassIfInHeritageClause(node.parent) + : isExpressionWithTypeArguments(node) ? tryCast(node.parent.parent, isClassLike) : undefined; + } - /** - * Determines if the parent symbol occurs somewhere in the child's ancestry. If the parent symbol - * is an interface, determines if some ancestor of the child symbol extends or inherits from it. - * Also takes in a cache of previous results which makes this slightly more efficient and is - * necessary to avoid potential loops like so: - * class A extends B { } - * class B extends A { } - * - * We traverse the AST rather than using the type checker because users are typically only interested - * in explicit implementations of an interface/class when calling "Go to Implementation". Sibling - * implementations of types that share a common ancestor with the type whose implementation we are - * searching for need to be filtered out of the results. The type checker doesn't let us make the - * distinction between structurally compatible implementations and explicit implementations, so we - * must use the AST. - * - * @param symbol A class or interface Symbol - * @param parent Another class or interface Symbol - * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results - */ - function explicitlyInheritsFrom(symbol: Symbol, parent: Symbol, cachedResults: ESMap, checker: TypeChecker): boolean { - if (symbol === parent) { + /** + * Returns true if this is an expression that can be considered an implementation + */ + function isImplementationExpression(node: Expression): boolean { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: + return isImplementationExpression((node as ParenthesizedExpression).expression); + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ClassExpression: + case SyntaxKind.ArrayLiteralExpression: return true; - } + default: + return false; + } + } - const key = getSymbolId(symbol) + "," + getSymbolId(parent); - const cached = cachedResults.get(key); - if (cached !== undefined) { - return cached; - } + /** + * Determines if the parent symbol occurs somewhere in the child's ancestry. If the parent symbol + * is an interface, determines if some ancestor of the child symbol extends or inherits from it. + * Also takes in a cache of previous results which makes this slightly more efficient and is + * necessary to avoid potential loops like so: + * class A extends B { } + * class B extends A { } + * + * We traverse the AST rather than using the type checker because users are typically only interested + * in explicit implementations of an interface/class when calling "Go to Implementation". Sibling + * implementations of types that share a common ancestor with the type whose implementation we are + * searching for need to be filtered out of the results. The type checker doesn't let us make the + * distinction between structurally compatible implementations and explicit implementations, so we + * must use the AST. + * + * @param symbol A class or interface Symbol + * @param parent Another class or interface Symbol + * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results + */ + function explicitlyInheritsFrom(symbol: Symbol, parent: Symbol, cachedResults: ESMap, checker: TypeChecker): boolean { + if (symbol === parent) { + return true; + } - // Set the key so that we don't infinitely recurse - cachedResults.set(key, false); + const key = getSymbolId(symbol) + "," + getSymbolId(parent); + const cached = cachedResults.get(key); + if (cached !== undefined) { + return cached; + } + + // Set the key so that we don't infinitely recurse + cachedResults.set(key, false); + + const inherits = !!symbol.declarations && symbol.declarations.some(declaration => + getAllSuperTypeNodes(declaration).some(typeReference => { + const type = checker.getTypeAtLocation(typeReference); + return !!type && !!type.symbol && explicitlyInheritsFrom(type.symbol, parent, cachedResults, checker); + })); + cachedResults.set(key, inherits); + return inherits; + } - const inherits = !!symbol.declarations && symbol.declarations.some(declaration => - getAllSuperTypeNodes(declaration).some(typeReference => { - const type = checker.getTypeAtLocation(typeReference); - return !!type && !!type.symbol && explicitlyInheritsFrom(type.symbol, parent, cachedResults, checker); - })); - cachedResults.set(key, inherits); - return inherits; + function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] | undefined { + let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); + if (!searchSpaceNode) { + return undefined; } + // Whether 'super' occurs in a static context within a class. + let staticFlag = ModifierFlags.Static; - function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] | undefined { - let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); - if (!searchSpaceNode) { + switch (searchSpaceNode.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + staticFlag &= getSyntacticModifierFlags(searchSpaceNode); + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class + break; + default: return undefined; - } - // Whether 'super' occurs in a static context within a class. - let staticFlag = ModifierFlags.Static; - - switch (searchSpaceNode.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - staticFlag &= getSyntacticModifierFlags(searchSpaceNode); - searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class - break; - default: - return undefined; - } + } - const sourceFile = searchSpaceNode.getSourceFile(); - const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), node => { - if (node.kind !== SyntaxKind.SuperKeyword) { - return; - } + const sourceFile = searchSpaceNode.getSourceFile(); + const references = mapDefined(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), node => { + if (node.kind !== SyntaxKind.SuperKeyword) { + return; + } - const container = getSuperContainer(node, /*stopOnFunctions*/ false); + const container = getSuperContainer(node, /*stopOnFunctions*/ false); - // If we have a 'super' container, we must have an enclosing class. - // Now make sure the owning class is the same as the search-space - // and has the same static qualifier as the original 'super's owner. - return container && isStatic(container) === !!staticFlag && container.parent.symbol === searchSpaceNode.symbol ? nodeEntry(node) : undefined; - }); + // If we have a 'super' container, we must have an enclosing class. + // Now make sure the owning class is the same as the search-space + // and has the same static qualifier as the original 'super's owner. + return container && isStatic(container) === !!staticFlag && container.parent.symbol === searchSpaceNode.symbol ? nodeEntry(node) : undefined; + }); - return [{ definition: { type: DefinitionKind.Symbol, symbol: searchSpaceNode.symbol }, references }]; - } + return [{ definition: { type: DefinitionKind.Symbol, symbol: searchSpaceNode.symbol }, references }]; + } - function isParameterName(node: Node) { - return node.kind === SyntaxKind.Identifier && node.parent.kind === SyntaxKind.Parameter && (node.parent as ParameterDeclaration).name === node; - } + function isParameterName(node: Node) { + return node.kind === SyntaxKind.Identifier && node.parent.kind === SyntaxKind.Parameter && (node.parent as ParameterDeclaration).name === node; + } - function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { - let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); + function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { + let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); - // Whether 'this' occurs in a static context within a class. - let staticFlag = ModifierFlags.Static; + // Whether 'this' occurs in a static context within a class. + let staticFlag = ModifierFlags.Static; - switch (searchSpaceNode.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - if (isObjectLiteralMethod(searchSpaceNode)) { - staticFlag &= getSyntacticModifierFlags(searchSpaceNode); - searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning object literals - break; - } - // falls through - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: + switch (searchSpaceNode.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + if (isObjectLiteralMethod(searchSpaceNode)) { staticFlag &= getSyntacticModifierFlags(searchSpaceNode); - searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class - break; - case SyntaxKind.SourceFile: - if (isExternalModule(searchSpaceNode as SourceFile) || isParameterName(thisOrSuperKeyword)) { - return undefined; - } - // falls through - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning object literals break; - // Computed properties in classes are not handled here because references to this are illegal, - // so there is no point finding references to them. - default: + } + // falls through + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + staticFlag &= getSyntacticModifierFlags(searchSpaceNode); + searchSpaceNode = searchSpaceNode.parent; // re-assign to be the owning class + break; + case SyntaxKind.SourceFile: + if (isExternalModule(searchSpaceNode as SourceFile) || isParameterName(thisOrSuperKeyword)) { return undefined; - } + } + // falls through + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + break; + // Computed properties in classes are not handled here because references to this are illegal, + // so there is no point finding references to them. + default: + return undefined; + } - const references = flatMap(searchSpaceNode.kind === SyntaxKind.SourceFile ? sourceFiles : [searchSpaceNode.getSourceFile()], sourceFile => { - cancellationToken.throwIfCancellationRequested(); - return getPossibleSymbolReferenceNodes(sourceFile, "this", isSourceFile(searchSpaceNode) ? sourceFile : searchSpaceNode).filter(node => { - if (!isThis(node)) { - return false; - } - const container = getThisContainer(node, /* includeArrowFunctions */ false); - switch (searchSpaceNode.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - return searchSpaceNode.symbol === container.symbol; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol; - case SyntaxKind.ClassExpression: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ObjectLiteralExpression: - // Make sure the container belongs to the same class/object literals - // and has the appropriate static modifier from the original container. - return container.parent && searchSpaceNode.symbol === container.parent.symbol && isStatic(container) === !!staticFlag; - case SyntaxKind.SourceFile: - return container.kind === SyntaxKind.SourceFile && !isExternalModule(container as SourceFile) && !isParameterName(node); - } - }); - }).map(n => nodeEntry(n)); + const references = flatMap(searchSpaceNode.kind === SyntaxKind.SourceFile ? sourceFiles : [searchSpaceNode.getSourceFile()], sourceFile => { + cancellationToken.throwIfCancellationRequested(); + return getPossibleSymbolReferenceNodes(sourceFile, "this", isSourceFile(searchSpaceNode) ? sourceFile : searchSpaceNode).filter(node => { + if (!isThis(node)) { + return false; + } + const container = getThisContainer(node, /* includeArrowFunctions */ false); + switch (searchSpaceNode.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + return searchSpaceNode.symbol === container.symbol; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol; + case SyntaxKind.ClassExpression: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ObjectLiteralExpression: + // Make sure the container belongs to the same class/object literals + // and has the appropriate static modifier from the original container. + return container.parent && searchSpaceNode.symbol === container.parent.symbol && isStatic(container) === !!staticFlag; + case SyntaxKind.SourceFile: + return container.kind === SyntaxKind.SourceFile && !isExternalModule(container as SourceFile) && !isParameterName(node); + } + }); + }).map(n => nodeEntry(n)); - const thisParameter = firstDefined(references, r => isParameter(r.node.parent) ? r.node : undefined); - return [{ - definition: { type: DefinitionKind.This, node: thisParameter || thisOrSuperKeyword }, - references - }]; - } + const thisParameter = firstDefined(references, r => isParameter(r.node.parent) ? r.node : undefined); + return [{ + definition: { type: DefinitionKind.This, node: thisParameter || thisOrSuperKeyword }, + references + }]; + } - function getReferencesForStringLiteral(node: StringLiteralLike, sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] { - const type = getContextualTypeFromParentOrAncestorTypeNode(node, checker); - const references = flatMap(sourceFiles, sourceFile => { - cancellationToken.throwIfCancellationRequested(); - return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => { - if (isStringLiteralLike(ref) && ref.text === node.text) { - if (type) { - const refType = getContextualTypeFromParentOrAncestorTypeNode(ref, checker); - if (type !== checker.getStringType() && type === refType) { - return nodeEntry(ref, EntryKind.StringLiteral); - } - } - else { - return isNoSubstitutionTemplateLiteral(ref) && !rangeIsOnSingleLine(ref, sourceFile) ? undefined : - nodeEntry(ref, EntryKind.StringLiteral); + function getReferencesForStringLiteral(node: StringLiteralLike, sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] { + const type = getContextualTypeFromParentOrAncestorTypeNode(node, checker); + const references = flatMap(sourceFiles, sourceFile => { + cancellationToken.throwIfCancellationRequested(); + return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => { + if (isStringLiteralLike(ref) && ref.text === node.text) { + if (type) { + const refType = getContextualTypeFromParentOrAncestorTypeNode(ref, checker); + if (type !== checker.getStringType() && type === refType) { + return nodeEntry(ref, EntryKind.StringLiteral); } } - }); + else { + return isNoSubstitutionTemplateLiteral(ref) && !rangeIsOnSingleLine(ref, sourceFile) ? undefined : + nodeEntry(ref, EntryKind.StringLiteral); + } + } }); + }); - return [{ - definition: { type: DefinitionKind.String, node }, - references - }]; - } + return [{ + definition: { type: DefinitionKind.String, node }, + references + }]; + } - // For certain symbol kinds, we need to include other symbols in the search set. - // This is not needed when searching for re-exports. - function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, isForRename: boolean, providePrefixAndSuffixText: boolean, implementations: boolean): Symbol[] { - const result: Symbol[] = []; - forEachRelatedSymbol(symbol, location, checker, isForRename, !(isForRename && providePrefixAndSuffixText), - (sym, root, base) => { - // static method/property and instance method/property might have the same name. Only include static or only include instance. - if (base) { - if (isStaticSymbol(symbol) !== isStaticSymbol(base)) { - base = undefined; - } + // For certain symbol kinds, we need to include other symbols in the search set. + // This is not needed when searching for re-exports. + function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, isForRename: boolean, providePrefixAndSuffixText: boolean, implementations: boolean): Symbol[] { + const result: Symbol[] = []; + forEachRelatedSymbol(symbol, location, checker, isForRename, !(isForRename && providePrefixAndSuffixText), + (sym, root, base) => { + // static method/property and instance method/property might have the same name. Only include static or only include instance. + if (base) { + if (isStaticSymbol(symbol) !== isStaticSymbol(base)) { + base = undefined; } - result.push(base || root || sym); - }, - // when try to find implementation, implementations is true, and not allowed to find base class - /*allowBaseTypes*/() => !implementations); - return result; - } + } + result.push(base || root || sym); + }, + // when try to find implementation, implementations is true, and not allowed to find base class + /*allowBaseTypes*/() => !implementations); + return result; + } + /** + * @param allowBaseTypes return true means it would try to find in base class or interface. + */ + function forEachRelatedSymbol( + symbol: Symbol, location: Node, checker: TypeChecker, isForRenamePopulateSearchSymbolSet: boolean, onlyIncludeBindingElementAtReferenceLocation: boolean, /** - * @param allowBaseTypes return true means it would try to find in base class or interface. + * @param baseSymbol This symbol means one property/mehtod from base class or interface when it is not null or undefined, */ - function forEachRelatedSymbol( - symbol: Symbol, location: Node, checker: TypeChecker, isForRenamePopulateSearchSymbolSet: boolean, onlyIncludeBindingElementAtReferenceLocation: boolean, - /** - * @param baseSymbol This symbol means one property/mehtod from base class or interface when it is not null or undefined, - */ - cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol, kind?: NodeEntryKind) => T | undefined, - allowBaseTypes: (rootSymbol: Symbol) => boolean, - ): T | undefined { - const containingObjectLiteralElement = getContainingObjectLiteralElement(location); - if (containingObjectLiteralElement) { - /* Because in short-hand property assignment, location has two meaning : property name and as value of the property - * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of - * property name and variable declaration of the identifier. - * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service - * should show both 'name' in 'obj' and 'name' in variable declaration - * const name = "Foo"; - * const obj = { name }; - * In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment - * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration - * will be included correctly. - */ - const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); // gets the local symbol - if (shorthandValueSymbol && isForRenamePopulateSearchSymbolSet) { - // When renaming 'x' in `const o = { x }`, just rename the local variable, not the property. - return cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); - } + cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol, kind?: NodeEntryKind) => T | undefined, + allowBaseTypes: (rootSymbol: Symbol) => boolean, + ): T | undefined { + const containingObjectLiteralElement = getContainingObjectLiteralElement(location); + if (containingObjectLiteralElement) { + /* Because in short-hand property assignment, location has two meaning : property name and as value of the property + * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of + * property name and variable declaration of the identifier. + * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service + * should show both 'name' in 'obj' and 'name' in variable declaration + * const name = "Foo"; + * const obj = { name }; + * In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment + * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration + * will be included correctly. + */ + const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); // gets the local symbol + if (shorthandValueSymbol && isForRenamePopulateSearchSymbolSet) { + // When renaming 'x' in `const o = { x }`, just rename the local variable, not the property. + return cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); + } + + // If the location is in a context sensitive location (i.e. in an object literal) try + // to get a contextual type for it, and add the property symbol from the contextual + // type to the search set + const contextualType = checker.getContextualType(containingObjectLiteralElement.parent); + const res = contextualType && firstDefined( + getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true), + sym => fromRoot(sym, EntryKind.SearchedPropertyFoundLocal)); + if (res) return res; - // If the location is in a context sensitive location (i.e. in an object literal) try - // to get a contextual type for it, and add the property symbol from the contextual - // type to the search set - const contextualType = checker.getContextualType(containingObjectLiteralElement.parent); - const res = contextualType && firstDefined( - getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true), - sym => fromRoot(sym, EntryKind.SearchedPropertyFoundLocal)); - if (res) return res; + // If the location is name of property symbol from object literal destructuring pattern + // Search the property symbol + // for ( { property: p2 } of elems) { } + const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker); + const res1 = propertySymbol && cbSymbol(propertySymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedPropertyFoundLocal); + if (res1) return res1; - // If the location is name of property symbol from object literal destructuring pattern - // Search the property symbol - // for ( { property: p2 } of elems) { } - const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker); - const res1 = propertySymbol && cbSymbol(propertySymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedPropertyFoundLocal); - if (res1) return res1; + const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); + if (res2) return res2; + } - const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); - if (res2) return res2; - } + const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, checker); + if (aliasedSymbol) { + // In case of UMD module and global merging, search for global as well + const res = cbSymbol(aliasedSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); + if (res) return res; + } - const aliasedSymbol = getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, checker); - if (aliasedSymbol) { - // In case of UMD module and global merging, search for global as well - const res = cbSymbol(aliasedSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); - if (res) return res; - } + const res = fromRoot(symbol); + if (res) return res; - const res = fromRoot(symbol); - if (res) return res; + if (symbol.valueDeclaration && isParameterPropertyDeclaration(symbol.valueDeclaration, symbol.valueDeclaration.parent)) { + // For a parameter property, now try on the other symbol (property if this was a parameter, parameter if this was a property). + const paramProps = checker.getSymbolsOfParameterPropertyDeclaration(cast(symbol.valueDeclaration, isParameter), symbol.name); + Debug.assert(paramProps.length === 2 && !!(paramProps[0].flags & SymbolFlags.FunctionScopedVariable) && !!(paramProps[1].flags & SymbolFlags.Property)); // is [parameter, property] + return fromRoot(symbol.flags & SymbolFlags.FunctionScopedVariable ? paramProps[1] : paramProps[0]); + } - if (symbol.valueDeclaration && isParameterPropertyDeclaration(symbol.valueDeclaration, symbol.valueDeclaration.parent)) { - // For a parameter property, now try on the other symbol (property if this was a parameter, parameter if this was a property). - const paramProps = checker.getSymbolsOfParameterPropertyDeclaration(cast(symbol.valueDeclaration, isParameter), symbol.name); - Debug.assert(paramProps.length === 2 && !!(paramProps[0].flags & SymbolFlags.FunctionScopedVariable) && !!(paramProps[1].flags & SymbolFlags.Property)); // is [parameter, property] - return fromRoot(symbol.flags & SymbolFlags.FunctionScopedVariable ? paramProps[1] : paramProps[0]); + const exportSpecifier = getDeclarationOfKind(symbol, SyntaxKind.ExportSpecifier); + if (!isForRenamePopulateSearchSymbolSet || exportSpecifier && !exportSpecifier.propertyName) { + const localSymbol = exportSpecifier && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); + if (localSymbol) { + const res = cbSymbol(localSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); + if (res) return res; } + } - const exportSpecifier = getDeclarationOfKind(symbol, SyntaxKind.ExportSpecifier); - if (!isForRenamePopulateSearchSymbolSet || exportSpecifier && !exportSpecifier.propertyName) { - const localSymbol = exportSpecifier && checker.getExportSpecifierLocalTargetSymbol(exportSpecifier); - if (localSymbol) { - const res = cbSymbol(localSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.Node); - if (res) return res; - } + // symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property. + // Don't do this when populating search set for a rename when prefix and suffix text will be provided -- just rename the local. + if (!isForRenamePopulateSearchSymbolSet) { + let bindingElementPropertySymbol: Symbol | undefined; + if (onlyIncludeBindingElementAtReferenceLocation) { + bindingElementPropertySymbol = isObjectBindingElementWithoutPropertyName(location.parent) ? getPropertySymbolFromBindingElement(checker, location.parent) : undefined; } - - // symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property. - // Don't do this when populating search set for a rename when prefix and suffix text will be provided -- just rename the local. - if (!isForRenamePopulateSearchSymbolSet) { - let bindingElementPropertySymbol: Symbol | undefined; - if (onlyIncludeBindingElementAtReferenceLocation) { - bindingElementPropertySymbol = isObjectBindingElementWithoutPropertyName(location.parent) ? getPropertySymbolFromBindingElement(checker, location.parent) : undefined; - } - else { - bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); - } - return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); - } - - Debug.assert(isForRenamePopulateSearchSymbolSet); - // due to the above assert and the arguments at the uses of this function, - // (onlyIncludeBindingElementAtReferenceLocation <=> !providePrefixAndSuffixTextForRename) holds - const includeOriginalSymbolOfBindingElement = onlyIncludeBindingElementAtReferenceLocation; - - if (includeOriginalSymbolOfBindingElement) { - const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); - return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); - } - - function fromRoot(sym: Symbol, kind?: NodeEntryKind): T | undefined { - // If this is a union property: - // - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types. - // - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search. - // If the symbol is an instantiation from a another symbol (e.g. widened symbol): - // - In populateSearchSymbolsSet, add the root the list - // - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.) - return firstDefined(checker.getRootSymbols(sym), rootSymbol => - cbSymbol(sym, rootSymbol, /*baseSymbol*/ undefined, kind) - // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions - || (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && allowBaseTypes(rootSymbol) - ? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base, kind)) - : undefined)); - } - - function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, checker: TypeChecker): Symbol | undefined { - const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement); - if (bindingElement && isObjectBindingElementWithoutPropertyName(bindingElement)) { - return getPropertySymbolFromBindingElement(checker, bindingElement); - } + else { + bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); } + return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); } - /** - * Find symbol of the given property-name and add the symbol to the given result array - * @param symbol a symbol to start searching for the given propertyName - * @param propertyName a name of property to search for - * @param result an array of symbol of found property symbols - * @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol. - * The value of previousIterationSymbol is undefined when the function is first called. - */ - function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined { - const seen = new Map(); - return recur(symbol); - - function recur(symbol: Symbol): T | undefined { - // Use `addToSeen` to ensure we don't infinitely recurse in this situation: - // interface C extends C { - // /*findRef*/propName: string; - // } - if (!(symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) || !addToSeen(seen, getSymbolId(symbol))) return; - - return firstDefined(symbol.declarations, declaration => firstDefined(getAllSuperTypeNodes(declaration), typeReference => { - const type = checker.getTypeAtLocation(typeReference); - const propertySymbol = type && type.symbol && checker.getPropertyOfType(type, propertyName); - // Visit the typeReference as well to see if it directly or indirectly uses that property - return type && propertySymbol && (firstDefined(checker.getRootSymbols(propertySymbol), cb) || recur(type.symbol)); - })); - } - } - - interface RelatedSymbol { - readonly symbol: Symbol; - readonly kind: NodeEntryKind | undefined; - } - - function isStaticSymbol(symbol: Symbol): boolean { - if (!symbol.valueDeclaration) return false; - const modifierFlags = getEffectiveModifierFlags(symbol.valueDeclaration); - return !!(modifierFlags & ModifierFlags.Static); - } - - function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): RelatedSymbol | undefined { - const { checker } = state; - return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, /*isForRenamePopulateSearchSymbolSet*/ false, - /*onlyIncludeBindingElementAtReferenceLocation*/ state.options.use !== FindReferencesUse.Rename || !!state.options.providePrefixAndSuffixTextForRename, - (sym, rootSymbol, baseSymbol, kind): RelatedSymbol | undefined => { - // check whether the symbol used to search itself is just the searched one. - if (baseSymbol) { - // static method/property and instance method/property might have the same name. Only check static or only check instance. - if (isStaticSymbol(referenceSymbol) !== isStaticSymbol(baseSymbol)) { - baseSymbol = undefined; - } - } - return search.includes(baseSymbol || rootSymbol || sym) - // For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol. - ? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind } - : undefined; - }, - /*allowBaseTypes*/ rootSymbol => - !(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker))) - ); + Debug.assert(isForRenamePopulateSearchSymbolSet); + // due to the above assert and the arguments at the uses of this function, + // (onlyIncludeBindingElementAtReferenceLocation <=> !providePrefixAndSuffixTextForRename) holds + const includeOriginalSymbolOfBindingElement = onlyIncludeBindingElementAtReferenceLocation; + + if (includeOriginalSymbolOfBindingElement) { + const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); + return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); } - /** - * Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations - * of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class - * then we need to widen the search to include type positions as well. - * On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated - * module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module) - * do not intersect in any of the three spaces. - */ - export function getIntersectingMeaningFromDeclarations(node: Node, symbol: Symbol): SemanticMeaning { - let meaning = getMeaningFromLocation(node); - const { declarations } = symbol; - if (declarations) { - let lastIterationMeaning: SemanticMeaning; - do { - // The result is order-sensitive, for instance if initialMeaning === Namespace, and declarations = [class, instantiated module] - // we need to consider both as they initialMeaning intersects with the module in the namespace space, and the module - // intersects with the class in the value space. - // To achieve that we will keep iterating until the result stabilizes. - - // Remember the last meaning - lastIterationMeaning = meaning; - - for (const declaration of declarations) { - const declarationMeaning = getMeaningFromDeclaration(declaration); - - if (declarationMeaning & meaning) { - meaning |= declarationMeaning; - } - } - } - while (meaning !== lastIterationMeaning); + function fromRoot(sym: Symbol, kind?: NodeEntryKind): T | undefined { + // If this is a union property: + // - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types. + // - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search. + // If the symbol is an instantiation from a another symbol (e.g. widened symbol): + // - In populateSearchSymbolsSet, add the root the list + // - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.) + return firstDefined(checker.getRootSymbols(sym), rootSymbol => + cbSymbol(sym, rootSymbol, /*baseSymbol*/ undefined, kind) + // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions + || (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && allowBaseTypes(rootSymbol) + ? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base, kind)) + : undefined)); + } + + function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, checker: TypeChecker): Symbol | undefined { + const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement); + if (bindingElement && isObjectBindingElementWithoutPropertyName(bindingElement)) { + return getPropertySymbolFromBindingElement(checker, bindingElement); } - return meaning; } + } - function isImplementation(node: Node): boolean { - return !!(node.flags & NodeFlags.Ambient) ? !(isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)) : - (isVariableLike(node) ? hasInitializer(node) : - isFunctionLikeDeclaration(node) ? !!node.body : - isClassLike(node) || isModuleOrEnumDeclaration(node)); + /** + * Find symbol of the given property-name and add the symbol to the given result array + * @param symbol a symbol to start searching for the given propertyName + * @param propertyName a name of property to search for + * @param result an array of symbol of found property symbols + * @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol. + * The value of previousIterationSymbol is undefined when the function is first called. + */ + function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined { + const seen = new Map(); + return recur(symbol); + + function recur(symbol: Symbol): T | undefined { + // Use `addToSeen` to ensure we don't infinitely recurse in this situation: + // interface C extends C { + // /*findRef*/propName: string; + // } + if (!(symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) || !addToSeen(seen, getSymbolId(symbol))) return; + + return firstDefined(symbol.declarations, declaration => firstDefined(getAllSuperTypeNodes(declaration), typeReference => { + const type = checker.getTypeAtLocation(typeReference); + const propertySymbol = type && type.symbol && checker.getPropertyOfType(type, propertyName); + // Visit the typeReference as well to see if it directly or indirectly uses that property + return type && propertySymbol && (firstDefined(checker.getRootSymbols(propertySymbol), cb) || recur(type.symbol)); + })); } + } + + interface RelatedSymbol { + readonly symbol: Symbol; + readonly kind: NodeEntryKind | undefined; + } + + function isStaticSymbol(symbol: Symbol): boolean { + if (!symbol.valueDeclaration) return false; + const modifierFlags = getEffectiveModifierFlags(symbol.valueDeclaration); + return !!(modifierFlags & ModifierFlags.Static); + } - export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { - const refSymbol = checker.getSymbolAtLocation(node)!; - const shorthandSymbol = checker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); + function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): RelatedSymbol | undefined { + const { checker } = state; + return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, /*isForRenamePopulateSearchSymbolSet*/ false, + /*onlyIncludeBindingElementAtReferenceLocation*/ state.options.use !== FindReferencesUse.Rename || !!state.options.providePrefixAndSuffixTextForRename, + (sym, rootSymbol, baseSymbol, kind): RelatedSymbol | undefined => { + // check whether the symbol used to search itself is just the searched one. + if (baseSymbol) { + // static method/property and instance method/property might have the same name. Only check static or only check instance. + if (isStaticSymbol(referenceSymbol) !== isStaticSymbol(baseSymbol)) { + baseSymbol = undefined; + } + } + return search.includes(baseSymbol || rootSymbol || sym) + // For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol. + ? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind } + : undefined; + }, + /*allowBaseTypes*/ rootSymbol => + !(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker))) + ); + } - if (shorthandSymbol) { - for (const declaration of shorthandSymbol.getDeclarations()!) { - if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { - addReference(declaration); + /** + * Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations + * of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class + * then we need to widen the search to include type positions as well. + * On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated + * module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module) + * do not intersect in any of the three spaces. + */ + export function getIntersectingMeaningFromDeclarations(node: Node, symbol: Symbol): SemanticMeaning { + let meaning = getMeaningFromLocation(node); + const { declarations } = symbol; + if (declarations) { + let lastIterationMeaning: SemanticMeaning; + do { + // The result is order-sensitive, for instance if initialMeaning === Namespace, and declarations = [class, instantiated module] + // we need to consider both as they initialMeaning intersects with the module in the namespace space, and the module + // intersects with the class in the value space. + // To achieve that we will keep iterating until the result stabilizes. + + // Remember the last meaning + lastIterationMeaning = meaning; + + for (const declaration of declarations) { + const declarationMeaning = getMeaningFromDeclaration(declaration); + + if (declarationMeaning & meaning) { + meaning |= declarationMeaning; } } } + while (meaning !== lastIterationMeaning); } + return meaning; + } + + function isImplementation(node: Node): boolean { + return !!(node.flags & NodeFlags.Ambient) ? !(isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)) : + (isVariableLike(node) ? hasInitializer(node) : + isFunctionLikeDeclaration(node) ? !!node.body : + isClassLike(node) || isModuleOrEnumDeclaration(node)); + } - function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void): void { - forEachChild(node, child => { - if (child.kind === kind) { - action(child); + export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { + const refSymbol = checker.getSymbolAtLocation(node)!; + const shorthandSymbol = checker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); + + if (shorthandSymbol) { + for (const declaration of shorthandSymbol.getDeclarations()!) { + if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { + addReference(declaration); } - forEachDescendantOfKind(child, kind, action); - }); + } } + } - /** Get `C` given `N` if `N` is in the position `class C extends N` or `class C extends foo.N` where `N` is an identifier. */ - function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { - return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent); - } + function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void): void { + forEachChild(node, child => { + if (child.kind === kind) { + action(child); + } + forEachDescendantOfKind(child, kind, action); + }); + } - /** - * If we are just looking for implementations and this is a property access expression, we need to get the - * symbol of the local type of the symbol the property is being accessed on. This is because our search - * symbol may have a different parent symbol if the local type's symbol does not declare the property - * being accessed (i.e. it is declared in some parent class or interface) - */ - function getParentSymbolsOfPropertyAccess(location: Node, symbol: Symbol, checker: TypeChecker): readonly Symbol[] | undefined { - const propertyAccessExpression = isRightSideOfPropertyAccess(location) ? location.parent as PropertyAccessExpression : undefined; - const lhsType = propertyAccessExpression && checker.getTypeAtLocation(propertyAccessExpression.expression); - const res = mapDefined(lhsType && (lhsType.isUnionOrIntersection() ? lhsType.types : lhsType.symbol === symbol.parent ? undefined : [lhsType]), t => - t.symbol && t.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? t.symbol : undefined); - return res.length === 0 ? undefined : res; - } + /** Get `C` given `N` if `N` is in the position `class C extends N` or `class C extends foo.N` where `N` is an identifier. */ + function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { + return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent); + } - function isForRenameWithPrefixAndSuffixText(options: Options) { - return options.use === FindReferencesUse.Rename && options.providePrefixAndSuffixTextForRename; - } + /** + * If we are just looking for implementations and this is a property access expression, we need to get the + * symbol of the local type of the symbol the property is being accessed on. This is because our search + * symbol may have a different parent symbol if the local type's symbol does not declare the property + * being accessed (i.e. it is declared in some parent class or interface) + */ + function getParentSymbolsOfPropertyAccess(location: Node, symbol: Symbol, checker: TypeChecker): readonly Symbol[] | undefined { + const propertyAccessExpression = isRightSideOfPropertyAccess(location) ? location.parent as PropertyAccessExpression : undefined; + const lhsType = propertyAccessExpression && checker.getTypeAtLocation(propertyAccessExpression.expression); + const res = mapDefined(lhsType && (lhsType.isUnionOrIntersection() ? lhsType.types : lhsType.symbol === symbol.parent ? undefined : [lhsType]), t => + t.symbol && t.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? t.symbol : undefined); + return res.length === 0 ? undefined : res; + } + + function isForRenameWithPrefixAndSuffixText(options: Options) { + return options.use === FindReferencesUse.Rename && options.providePrefixAndSuffixTextForRename; } } diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 3b8534b8ef326..0d53a1474d57a 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -1,1450 +1,1479 @@ -/* @internal */ -namespace ts.formatting { - export interface FormatContext { - readonly options: FormatCodeSettings; - readonly getRules: RulesMap; - readonly host: FormattingHost; - } +import { + Block, CallExpression, canHaveModifiers, CatchClause, CharacterCodes, ClassDeclaration, CommentRange, concatenate, + createTextChangeFromStartLength, Debug, Declaration, Diagnostic, EditorSettings, find, findAncestor, findIndex, + findPrecedingToken, forEachChild, forEachRight, FormatCodeSettings, FormattingHost, FunctionDeclaration, + getEndLinePosition, getLeadingCommentRangesOfNode, getLineStartPositionForPosition, getNameOfDeclaration, + getNewLineOrDefaultFromHost, getNonDecoratorTokenPosOfNode, getStartPositionOfLine, getTokenAtPosition, + getTrailingCommentRanges, hasDecorators, InterfaceDeclaration, isComment, isDecorator, isJSDoc, isLineBreak, + isModifier, isNodeArray, isStringOrRegularExpressionOrTemplateLiteral, isToken, isWhiteSpaceSingleLine, + LanguageVariant, last, LineAndCharacter, MethodDeclaration, ModuleDeclaration, Node, NodeArray, nodeIsMissing, + nodeIsSynthesized, rangeContainsPositionExclusive, rangeContainsRange, rangeContainsStartEnd, + rangeOverlapsWithStartEnd, repeatString, SourceFile, SourceFileLike, startEndContainsRange, + startEndOverlapsWithStartEnd, SyntaxKind, TextChange, TextRange, TriviaSyntaxKind, TypeReferenceNode, +} from "../_namespaces/ts"; +import { + FormattingContext, FormattingRequestKind, FormattingScanner, getFormattingScanner, Rule, RuleAction, RuleFlags, + RulesMap, SmartIndenter, +} from "../_namespaces/ts.formatting"; + +/** @internal */ +export interface FormatContext { + readonly options: FormatCodeSettings; + readonly getRules: RulesMap; + readonly host: FormattingHost; +} - export interface TextRangeWithKind extends TextRange { - kind: T; - } +/** @internal */ +export interface TextRangeWithKind extends TextRange { + kind: T; +} - export type TextRangeWithTriviaKind = TextRangeWithKind; +/** @internal */ +export type TextRangeWithTriviaKind = TextRangeWithKind; - export interface TokenInfo { - leadingTrivia: TextRangeWithTriviaKind[] | undefined; - token: TextRangeWithKind; - trailingTrivia: TextRangeWithTriviaKind[] | undefined; - } +/** @internal */ +export interface TokenInfo { + leadingTrivia: TextRangeWithTriviaKind[] | undefined; + token: TextRangeWithKind; + trailingTrivia: TextRangeWithTriviaKind[] | undefined; +} - export function createTextRangeWithKind(pos: number, end: number, kind: T): TextRangeWithKind { - const textRangeWithKind: TextRangeWithKind = { pos, end, kind }; - if (Debug.isDebugging) { - Object.defineProperty(textRangeWithKind, "__debugKind", { - get: () => Debug.formatSyntaxKind(kind), - }); - } - return textRangeWithKind; +/** @internal */ +export function createTextRangeWithKind(pos: number, end: number, kind: T): TextRangeWithKind { + const textRangeWithKind: TextRangeWithKind = { pos, end, kind }; + if (Debug.isDebugging) { + Object.defineProperty(textRangeWithKind, "__debugKind", { + get: () => Debug.formatSyntaxKind(kind), + }); } + return textRangeWithKind; +} - const enum Constants { - Unknown = -1 - } +const enum Constants { + Unknown = -1 +} - /* - * Indentation for the scope that can be dynamically recomputed. - * i.e - * while(true) - * { let x; - * } - * Normally indentation is applied only to the first token in line so at glance 'let' should not be touched. - * However if some format rule adds new line between '}' and 'let' 'let' will become - * the first token in line so it should be indented +/* + * Indentation for the scope that can be dynamically recomputed. + * i.e + * while(true) + * { let x; + * } + * Normally indentation is applied only to the first token in line so at glance 'let' should not be touched. + * However if some format rule adds new line between '}' and 'let' 'let' will become + * the first token in line so it should be indented + */ +interface DynamicIndentation { + getIndentationForToken(tokenLine: number, tokenKind: SyntaxKind, container: Node, suppressDelta: boolean): number; + getIndentationForComment(owningToken: SyntaxKind, tokenIndentation: number, container: Node): number; + /** + * Indentation for open and close tokens of the node if it is block or another node that needs special indentation + * ... { + * ......... + * ....} + * ____ - indentation + * ____ - delta */ - interface DynamicIndentation { - getIndentationForToken(tokenLine: number, tokenKind: SyntaxKind, container: Node, suppressDelta: boolean): number; - getIndentationForComment(owningToken: SyntaxKind, tokenIndentation: number, container: Node): number; - /** - * Indentation for open and close tokens of the node if it is block or another node that needs special indentation - * ... { - * ......... - * ....} - * ____ - indentation - * ____ - delta - */ - getIndentation(): number; - /** - * Prefered relative indentation for child nodes. - * Delta is used to carry the indentation info - * foo(bar({ - * $ - * })) - * Both 'foo', 'bar' introduce new indentation with delta = 4, but total indentation in $ is not 8. - * foo: { indentation: 0, delta: 4 } - * bar: { indentation: foo.indentation + foo.delta = 4, delta: 4} however 'foo' and 'bar' are on the same line - * so bar inherits indentation from foo and bar.delta will be 4 - * - */ - getDelta(child: TextRangeWithKind): number; - /** - * Formatter calls this function when rule adds or deletes new lines from the text - * so indentation scope can adjust values of indentation and delta. - */ - recomputeIndentation(lineAddedByFormatting: boolean, parent: Node): void; - } - - export function formatOnEnter(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - const line = sourceFile.getLineAndCharacterOfPosition(position).line; - if (line === 0) { - return []; - } - // After the enter key, the cursor is now at a new line. The new line may or may not contain non-whitespace characters. - // If the new line has only whitespaces, we won't want to format this line, because that would remove the indentation as - // trailing whitespaces. So the end of the formatting span should be the later one between: - // 1. the end of the previous line - // 2. the last non-whitespace character in the current line - let endOfFormatSpan = getEndLinePosition(line, sourceFile); - while (isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(endOfFormatSpan))) { - endOfFormatSpan--; - } - // if the character at the end of the span is a line break, we shouldn't include it, because it indicates we don't want to - // touch the current line at all. Also, on some OSes the line break consists of two characters (\r\n), we should test if the - // previous character before the end of format span is line break character as well. - if (isLineBreak(sourceFile.text.charCodeAt(endOfFormatSpan))) { - endOfFormatSpan--; - } - const span = { - // get start position for the previous line - pos: getStartPositionOfLine(line - 1, sourceFile), - // end value is exclusive so add 1 to the result - end: endOfFormatSpan + 1 - }; - return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatOnEnter); - } - - export function formatOnSemicolon(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - const semicolon = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.SemicolonToken, sourceFile); - return formatNodeLines(findOutermostNodeWithinListLevel(semicolon), sourceFile, formatContext, FormattingRequestKind.FormatOnSemicolon); - } - - export function formatOnOpeningCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - const openingCurly = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.OpenBraceToken, sourceFile); - if (!openingCurly) { - return []; - } - const curlyBraceRange = openingCurly.parent; - const outermostNode = findOutermostNodeWithinListLevel(curlyBraceRange); - - /** - * We limit the span to end at the opening curly to handle the case where - * the brace matched to that just typed will be incorrect after further edits. - * For example, we could type the opening curly for the following method - * body without brace-matching activated: - * ``` - * class C { - * foo() - * } - * ``` - * and we wouldn't want to move the closing brace. - */ - const textRange: TextRange = { - pos: getLineStartPositionForPosition(outermostNode!.getStart(sourceFile), sourceFile), // TODO: GH#18217 - end: position - }; - - return formatSpan(textRange, sourceFile, formatContext, FormattingRequestKind.FormatOnOpeningCurlyBrace); - } + getIndentation(): number; + /** + * Prefered relative indentation for child nodes. + * Delta is used to carry the indentation info + * foo(bar({ + * $ + * })) + * Both 'foo', 'bar' introduce new indentation with delta = 4, but total indentation in $ is not 8. + * foo: { indentation: 0, delta: 4 } + * bar: { indentation: foo.indentation + foo.delta = 4, delta: 4} however 'foo' and 'bar' are on the same line + * so bar inherits indentation from foo and bar.delta will be 4 + * + */ + getDelta(child: TextRangeWithKind): number; + /** + * Formatter calls this function when rule adds or deletes new lines from the text + * so indentation scope can adjust values of indentation and delta. + */ + recomputeIndentation(lineAddedByFormatting: boolean, parent: Node): void; +} - export function formatOnClosingCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - const precedingToken = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.CloseBraceToken, sourceFile); - return formatNodeLines(findOutermostNodeWithinListLevel(precedingToken), sourceFile, formatContext, FormattingRequestKind.FormatOnClosingCurlyBrace); +/** @internal */ +export function formatOnEnter(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + const line = sourceFile.getLineAndCharacterOfPosition(position).line; + if (line === 0) { + return []; } - - export function formatDocument(sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - const span = { - pos: 0, - end: sourceFile.text.length - }; - return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatDocument); + // After the enter key, the cursor is now at a new line. The new line may or may not contain non-whitespace characters. + // If the new line has only whitespaces, we won't want to format this line, because that would remove the indentation as + // trailing whitespaces. So the end of the formatting span should be the later one between: + // 1. the end of the previous line + // 2. the last non-whitespace character in the current line + let endOfFormatSpan = getEndLinePosition(line, sourceFile); + while (isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(endOfFormatSpan))) { + endOfFormatSpan--; } - - export function formatSelection(start: number, end: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { - // format from the beginning of the line - const span = { - pos: getLineStartPositionForPosition(start, sourceFile), - end, - }; - return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatSelection); + // if the character at the end of the span is a line break, we shouldn't include it, because it indicates we don't want to + // touch the current line at all. Also, on some OSes the line break consists of two characters (\r\n), we should test if the + // previous character before the end of format span is line break character as well. + if (isLineBreak(sourceFile.text.charCodeAt(endOfFormatSpan))) { + endOfFormatSpan--; } + const span = { + // get start position for the previous line + pos: getStartPositionOfLine(line - 1, sourceFile), + // end value is exclusive so add 1 to the result + end: endOfFormatSpan + 1 + }; + return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatOnEnter); +} - /** - * Validating `expectedTokenKind` ensures the token was typed in the context we expect (eg: not a comment). - * @param expectedTokenKind The kind of the last token constituting the desired parent node. - */ - function findImmediatelyPrecedingTokenOfKind(end: number, expectedTokenKind: SyntaxKind, sourceFile: SourceFile): Node | undefined { - const precedingToken = findPrecedingToken(end, sourceFile); +/** @internal */ +export function formatOnSemicolon(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + const semicolon = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.SemicolonToken, sourceFile); + return formatNodeLines(findOutermostNodeWithinListLevel(semicolon), sourceFile, formatContext, FormattingRequestKind.FormatOnSemicolon); +} - return precedingToken && precedingToken.kind === expectedTokenKind && end === precedingToken.getEnd() ? - precedingToken : - undefined; +/** @internal */ +export function formatOnOpeningCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + const openingCurly = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.OpenBraceToken, sourceFile); + if (!openingCurly) { + return []; } + const curlyBraceRange = openingCurly.parent; + const outermostNode = findOutermostNodeWithinListLevel(curlyBraceRange); /** - * Finds the highest node enclosing `node` at the same list level as `node` - * and whose end does not exceed `node.end`. - * - * Consider typing the following + * We limit the span to end at the opening curly to handle the case where + * the brace matched to that just typed will be incorrect after further edits. + * For example, we could type the opening curly for the following method + * body without brace-matching activated: * ``` - * let x = 1; - * while (true) { + * class C { + * foo() * } * ``` - * Upon typing the closing curly, we want to format the entire `while`-statement, but not the preceding - * variable declaration. + * and we wouldn't want to move the closing brace. */ - function findOutermostNodeWithinListLevel(node: Node | undefined) { - let current = node; - while (current && - current.parent && - current.parent.end === node!.end && - !isListElement(current.parent, current)) { - current = current.parent; - } + const textRange: TextRange = { + pos: getLineStartPositionForPosition(outermostNode!.getStart(sourceFile), sourceFile), // TODO: GH#18217 + end: position + }; + + return formatSpan(textRange, sourceFile, formatContext, FormattingRequestKind.FormatOnOpeningCurlyBrace); +} + +/** @internal */ +export function formatOnClosingCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + const precedingToken = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.CloseBraceToken, sourceFile); + return formatNodeLines(findOutermostNodeWithinListLevel(precedingToken), sourceFile, formatContext, FormattingRequestKind.FormatOnClosingCurlyBrace); +} + +/** @internal */ +export function formatDocument(sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + const span = { + pos: 0, + end: sourceFile.text.length + }; + return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatDocument); +} + +/** @internal */ +export function formatSelection(start: number, end: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { + // format from the beginning of the line + const span = { + pos: getLineStartPositionForPosition(start, sourceFile), + end, + }; + return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatSelection); +} + +/** + * Validating `expectedTokenKind` ensures the token was typed in the context we expect (eg: not a comment). + * @param expectedTokenKind The kind of the last token constituting the desired parent node. + */ +function findImmediatelyPrecedingTokenOfKind(end: number, expectedTokenKind: SyntaxKind, sourceFile: SourceFile): Node | undefined { + const precedingToken = findPrecedingToken(end, sourceFile); - return current; + return precedingToken && precedingToken.kind === expectedTokenKind && end === precedingToken.getEnd() ? + precedingToken : + undefined; +} + +/** + * Finds the highest node enclosing `node` at the same list level as `node` + * and whose end does not exceed `node.end`. + * + * Consider typing the following + * ``` + * let x = 1; + * while (true) { + * } + * ``` + * Upon typing the closing curly, we want to format the entire `while`-statement, but not the preceding + * variable declaration. + */ +function findOutermostNodeWithinListLevel(node: Node | undefined) { + let current = node; + while (current && + current.parent && + current.parent.end === node!.end && + !isListElement(current.parent, current)) { + current = current.parent; } - // Returns true if node is a element in some list in parent - // i.e. parent is class declaration with the list of members and node is one of members. - function isListElement(parent: Node, node: Node): boolean { - switch (parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - return rangeContainsRange((parent as InterfaceDeclaration).members, node); - case SyntaxKind.ModuleDeclaration: - const body = (parent as ModuleDeclaration).body; - return !!body && body.kind === SyntaxKind.ModuleBlock && rangeContainsRange(body.statements, node); - case SyntaxKind.SourceFile: - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - return rangeContainsRange((parent as Block).statements, node); - case SyntaxKind.CatchClause: - return rangeContainsRange((parent as CatchClause).block.statements, node); - } + return current; +} - return false; +// Returns true if node is a element in some list in parent +// i.e. parent is class declaration with the list of members and node is one of members. +function isListElement(parent: Node, node: Node): boolean { + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + return rangeContainsRange((parent as InterfaceDeclaration).members, node); + case SyntaxKind.ModuleDeclaration: + const body = (parent as ModuleDeclaration).body; + return !!body && body.kind === SyntaxKind.ModuleBlock && rangeContainsRange(body.statements, node); + case SyntaxKind.SourceFile: + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + return rangeContainsRange((parent as Block).statements, node); + case SyntaxKind.CatchClause: + return rangeContainsRange((parent as CatchClause).block.statements, node); } - /** find node that fully contains given text range */ - function findEnclosingNode(range: TextRange, sourceFile: SourceFile): Node { - return find(sourceFile); + return false; +} - function find(n: Node): Node { - const candidate = forEachChild(n, c => startEndContainsRange(c.getStart(sourceFile), c.end, range) && c); - if (candidate) { - const result = find(candidate); - if (result) { - return result; - } - } +/** find node that fully contains given text range */ +function findEnclosingNode(range: TextRange, sourceFile: SourceFile): Node { + return find(sourceFile); - return n; + function find(n: Node): Node { + const candidate = forEachChild(n, c => startEndContainsRange(c.getStart(sourceFile), c.end, range) && c); + if (candidate) { + const result = find(candidate); + if (result) { + return result; + } } - } - /** formatting is not applied to ranges that contain parse errors. - * This function will return a predicate that for a given text range will tell - * if there are any parse errors that overlap with the range. - */ - function prepareRangeContainsErrorFunction(errors: readonly Diagnostic[], originalRange: TextRange): (r: TextRange) => boolean { - if (!errors.length) { - return rangeHasNoErrors; - } + return n; + } +} - // pick only errors that fall in range - const sorted = errors - .filter(d => rangeOverlapsWithStartEnd(originalRange, d.start!, d.start! + d.length!)) // TODO: GH#18217 - .sort((e1, e2) => e1.start! - e2.start!); +/** formatting is not applied to ranges that contain parse errors. + * This function will return a predicate that for a given text range will tell + * if there are any parse errors that overlap with the range. + */ +function prepareRangeContainsErrorFunction(errors: readonly Diagnostic[], originalRange: TextRange): (r: TextRange) => boolean { + if (!errors.length) { + return rangeHasNoErrors; + } - if (!sorted.length) { - return rangeHasNoErrors; - } + // pick only errors that fall in range + const sorted = errors + .filter(d => rangeOverlapsWithStartEnd(originalRange, d.start!, d.start! + d.length!)) // TODO: GH#18217 + .sort((e1, e2) => e1.start! - e2.start!); - let index = 0; + if (!sorted.length) { + return rangeHasNoErrors; + } - return r => { - // in current implementation sequence of arguments [r1, r2...] is monotonically increasing. - // 'index' tracks the index of the most recent error that was checked. - while (true) { - if (index >= sorted.length) { - // all errors in the range were already checked -> no error in specified range - return false; - } + let index = 0; - const error = sorted[index]; - if (r.end <= error.start!) { - // specified range ends before the error referred by 'index' - no error in range - return false; - } + return r => { + // in current implementation sequence of arguments [r1, r2...] is monotonically increasing. + // 'index' tracks the index of the most recent error that was checked. + while (true) { + if (index >= sorted.length) { + // all errors in the range were already checked -> no error in specified range + return false; + } - if (startEndOverlapsWithStartEnd(r.pos, r.end, error.start!, error.start! + error.length!)) { - // specified range overlaps with error range - return true; - } + const error = sorted[index]; + if (r.end <= error.start!) { + // specified range ends before the error referred by 'index' - no error in range + return false; + } - index++; + if (startEndOverlapsWithStartEnd(r.pos, r.end, error.start!, error.start! + error.length!)) { + // specified range overlaps with error range + return true; } - }; - function rangeHasNoErrors(): boolean { - return false; + index++; } + }; + + function rangeHasNoErrors(): boolean { + return false; } +} - /** - * Start of the original range might fall inside the comment - scanner will not yield appropriate results - * This function will look for token that is located before the start of target range - * and return its end as start position for the scanner. - */ - function getScanStartPosition(enclosingNode: Node, originalRange: TextRange, sourceFile: SourceFile): number { - const start = enclosingNode.getStart(sourceFile); - if (start === originalRange.pos && enclosingNode.end === originalRange.end) { - return start; - } +/** + * Start of the original range might fall inside the comment - scanner will not yield appropriate results + * This function will look for token that is located before the start of target range + * and return its end as start position for the scanner. + */ +function getScanStartPosition(enclosingNode: Node, originalRange: TextRange, sourceFile: SourceFile): number { + const start = enclosingNode.getStart(sourceFile); + if (start === originalRange.pos && enclosingNode.end === originalRange.end) { + return start; + } - const precedingToken = findPrecedingToken(originalRange.pos, sourceFile); - if (!precedingToken) { - // no preceding token found - start from the beginning of enclosing node - return enclosingNode.pos; + const precedingToken = findPrecedingToken(originalRange.pos, sourceFile); + if (!precedingToken) { + // no preceding token found - start from the beginning of enclosing node + return enclosingNode.pos; + } + + // preceding token ends after the start of original range (i.e when originalRange.pos falls in the middle of literal) + // start from the beginning of enclosingNode to handle the entire 'originalRange' + if (precedingToken.end >= originalRange.pos) { + return enclosingNode.pos; + } + + return precedingToken.end; +} + +/* + * For cases like + * if (a || + * b ||$ + * c) {...} + * If we hit Enter at $ we want line ' b ||' to be indented. + * Formatting will be applied to the last two lines. + * Node that fully encloses these lines is binary expression 'a ||...'. + * Initial indentation for this node will be 0. + * Binary expressions don't introduce new indentation scopes, however it is possible + * that some parent node on the same line does - like if statement in this case. + * Note that we are considering parents only from the same line with initial node - + * if parent is on the different line - its delta was already contributed + * to the initial indentation. + */ +function getOwnOrInheritedDelta(n: Node, options: FormatCodeSettings, sourceFile: SourceFile): number { + let previousLine = Constants.Unknown; + let child: Node | undefined; + while (n) { + const line = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)).line; + if (previousLine !== Constants.Unknown && line !== previousLine) { + break; } - // preceding token ends after the start of original range (i.e when originalRange.pos falls in the middle of literal) - // start from the beginning of enclosingNode to handle the entire 'originalRange' - if (precedingToken.end >= originalRange.pos) { - return enclosingNode.pos; + if (SmartIndenter.shouldIndentChildNode(options, n, child, sourceFile)) { + return options.indentSize!; } - return precedingToken.end; + previousLine = line; + child = n; + n = n.parent; } + return 0; +} - /* - * For cases like - * if (a || - * b ||$ - * c) {...} - * If we hit Enter at $ we want line ' b ||' to be indented. - * Formatting will be applied to the last two lines. - * Node that fully encloses these lines is binary expression 'a ||...'. - * Initial indentation for this node will be 0. - * Binary expressions don't introduce new indentation scopes, however it is possible - * that some parent node on the same line does - like if statement in this case. - * Note that we are considering parents only from the same line with initial node - - * if parent is on the different line - its delta was already contributed - * to the initial indentation. - */ - function getOwnOrInheritedDelta(n: Node, options: FormatCodeSettings, sourceFile: SourceFile): number { - let previousLine = Constants.Unknown; - let child: Node | undefined; - while (n) { - const line = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)).line; - if (previousLine !== Constants.Unknown && line !== previousLine) { - break; - } - - if (SmartIndenter.shouldIndentChildNode(options, n, child, sourceFile)) { - return options.indentSize!; - } +/** @internal */ +export function formatNodeGivenIndentation(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, formatContext: FormatContext): TextChange[] { + const range = { pos: node.pos, end: node.end }; + return getFormattingScanner(sourceFileLike.text, languageVariant, range.pos, range.end, scanner => formatSpanWorker( + range, + node, + initialIndentation, + delta, + scanner, + formatContext, + FormattingRequestKind.FormatSelection, + _ => false, // assume that node does not have any errors + sourceFileLike)); +} - previousLine = line; - child = n; - n = n.parent; - } - return 0; +function formatNodeLines(node: Node | undefined, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] { + if (!node) { + return []; } - export function formatNodeGivenIndentation(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, formatContext: FormatContext): TextChange[] { - const range = { pos: node.pos, end: node.end }; - return getFormattingScanner(sourceFileLike.text, languageVariant, range.pos, range.end, scanner => formatSpanWorker( - range, - node, - initialIndentation, - delta, + const span = { + pos: getLineStartPositionForPosition(node.getStart(sourceFile), sourceFile), + end: node.end + }; + + return formatSpan(span, sourceFile, formatContext, requestKind); +} + +function formatSpan(originalRange: TextRange, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] { + // find the smallest node that fully wraps the range and compute the initial indentation for the node + const enclosingNode = findEnclosingNode(originalRange, sourceFile); + return getFormattingScanner( + sourceFile.text, + sourceFile.languageVariant, + getScanStartPosition(enclosingNode, originalRange, sourceFile), + originalRange.end, + scanner => formatSpanWorker( + originalRange, + enclosingNode, + SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, formatContext.options), + getOwnOrInheritedDelta(enclosingNode, formatContext.options, sourceFile), scanner, formatContext, - FormattingRequestKind.FormatSelection, - _ => false, // assume that node does not have any errors - sourceFileLike)); - } + requestKind, + prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange), + sourceFile)); +} - function formatNodeLines(node: Node | undefined, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] { - if (!node) { - return []; +function formatSpanWorker( + originalRange: TextRange, + enclosingNode: Node, + initialIndentation: number, + delta: number, + formattingScanner: FormattingScanner, + { options, getRules, host }: FormatContext, + requestKind: FormattingRequestKind, + rangeContainsError: (r: TextRange) => boolean, + sourceFile: SourceFileLike): TextChange[] { + + // formatting context is used by rules provider + const formattingContext = new FormattingContext(sourceFile, requestKind, options); + let previousRangeTriviaEnd: number; + let previousRange: TextRangeWithKind; + let previousParent: Node; + let previousRangeStartLine: number; + + let lastIndentedLine: number; + let indentationOnLastIndentedLine = Constants.Unknown; + + const edits: TextChange[] = []; + + formattingScanner.advance(); + + if (formattingScanner.isOnToken()) { + const startLine = sourceFile.getLineAndCharacterOfPosition(enclosingNode.getStart(sourceFile)).line; + let undecoratedStartLine = startLine; + if (hasDecorators(enclosingNode)) { + undecoratedStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(enclosingNode, sourceFile)).line; } - const span = { - pos: getLineStartPositionForPosition(node.getStart(sourceFile), sourceFile), - end: node.end - }; - - return formatSpan(span, sourceFile, formatContext, requestKind); - } - - function formatSpan(originalRange: TextRange, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] { - // find the smallest node that fully wraps the range and compute the initial indentation for the node - const enclosingNode = findEnclosingNode(originalRange, sourceFile); - return getFormattingScanner( - sourceFile.text, - sourceFile.languageVariant, - getScanStartPosition(enclosingNode, originalRange, sourceFile), - originalRange.end, - scanner => formatSpanWorker( - originalRange, - enclosingNode, - SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, formatContext.options), - getOwnOrInheritedDelta(enclosingNode, formatContext.options, sourceFile), - scanner, - formatContext, - requestKind, - prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange), - sourceFile)); + processNode(enclosingNode, enclosingNode, startLine, undecoratedStartLine, initialIndentation, delta); } - function formatSpanWorker( - originalRange: TextRange, - enclosingNode: Node, - initialIndentation: number, - delta: number, - formattingScanner: FormattingScanner, - { options, getRules, host }: FormatContext, - requestKind: FormattingRequestKind, - rangeContainsError: (r: TextRange) => boolean, - sourceFile: SourceFileLike): TextChange[] { - - // formatting context is used by rules provider - const formattingContext = new FormattingContext(sourceFile, requestKind, options); - let previousRangeTriviaEnd: number; - let previousRange: TextRangeWithKind; - let previousParent: Node; - let previousRangeStartLine: number; - - let lastIndentedLine: number; - let indentationOnLastIndentedLine = Constants.Unknown; - - const edits: TextChange[] = []; - - formattingScanner.advance(); - - if (formattingScanner.isOnToken()) { - const startLine = sourceFile.getLineAndCharacterOfPosition(enclosingNode.getStart(sourceFile)).line; - let undecoratedStartLine = startLine; - if (hasDecorators(enclosingNode)) { - undecoratedStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(enclosingNode, sourceFile)).line; + if (!formattingScanner.isOnToken()) { + const indentation = SmartIndenter.nodeWillIndentChild(options, enclosingNode, /*child*/ undefined, sourceFile, /*indentByDefault*/ false) + ? initialIndentation + options.indentSize! + : initialIndentation; + const leadingTrivia = formattingScanner.getCurrentLeadingTrivia(); + if (leadingTrivia) { + indentTriviaItems(leadingTrivia, indentation, /*indentNextTokenOrTrivia*/ false, + item => processRange(item, sourceFile.getLineAndCharacterOfPosition(item.pos), enclosingNode, enclosingNode, /*dynamicIndentation*/ undefined!)); + if (options.trimTrailingWhitespace !== false) { + trimTrailingWhitespacesForRemainingRange(leadingTrivia); } - - processNode(enclosingNode, enclosingNode, startLine, undecoratedStartLine, initialIndentation, delta); } + } - if (!formattingScanner.isOnToken()) { - const indentation = SmartIndenter.nodeWillIndentChild(options, enclosingNode, /*child*/ undefined, sourceFile, /*indentByDefault*/ false) - ? initialIndentation + options.indentSize! - : initialIndentation; - const leadingTrivia = formattingScanner.getCurrentLeadingTrivia(); - if (leadingTrivia) { - indentTriviaItems(leadingTrivia, indentation, /*indentNextTokenOrTrivia*/ false, - item => processRange(item, sourceFile.getLineAndCharacterOfPosition(item.pos), enclosingNode, enclosingNode, /*dynamicIndentation*/ undefined!)); - if (options.trimTrailingWhitespace !== false) { - trimTrailingWhitespacesForRemainingRange(leadingTrivia); - } - } - } + if (previousRange! && formattingScanner.getStartPos() >= originalRange.end) { + // Formatting edits happen by looking at pairs of contiguous tokens (see `processPair`), + // typically inserting or deleting whitespace between them. The recursive `processNode` + // logic above bails out as soon as it encounters a token that is beyond the end of the + // range we're supposed to format (or if we reach the end of the file). But this potentially + // leaves out an edit that would occur *inside* the requested range but cannot be discovered + // without looking at one token *beyond* the end of the range: consider the line `x = { }` + // with a selection from the beginning of the line to the space inside the curly braces, + // inclusive. We would expect a format-selection would delete the space (if rules apply), + // but in order to do that, we need to process the pair ["{", "}"], but we stopped processing + // just before getting there. This block handles this trailing edit. + const tokenInfo = + formattingScanner.isOnEOF() ? formattingScanner.readEOFTokenRange() : + formattingScanner.isOnToken() ? formattingScanner.readTokenInfo(enclosingNode).token : + undefined; - if (previousRange! && formattingScanner.getStartPos() >= originalRange.end) { - // Formatting edits happen by looking at pairs of contiguous tokens (see `processPair`), - // typically inserting or deleting whitespace between them. The recursive `processNode` - // logic above bails out as soon as it encounters a token that is beyond the end of the - // range we're supposed to format (or if we reach the end of the file). But this potentially - // leaves out an edit that would occur *inside* the requested range but cannot be discovered - // without looking at one token *beyond* the end of the range: consider the line `x = { }` - // with a selection from the beginning of the line to the space inside the curly braces, - // inclusive. We would expect a format-selection would delete the space (if rules apply), - // but in order to do that, we need to process the pair ["{", "}"], but we stopped processing - // just before getting there. This block handles this trailing edit. - const tokenInfo = - formattingScanner.isOnEOF() ? formattingScanner.readEOFTokenRange() : - formattingScanner.isOnToken() ? formattingScanner.readTokenInfo(enclosingNode).token : - undefined; - - if (tokenInfo && tokenInfo.pos === previousRangeTriviaEnd!) { - // We need to check that tokenInfo and previousRange are contiguous: the `originalRange` - // may have ended in the middle of a token, which means we will have stopped formatting - // on that token, leaving `previousRange` pointing to the token before it, but already - // having moved the formatting scanner (where we just got `tokenInfo`) to the next token. - // If this happens, our supposed pair [previousRange, tokenInfo] actually straddles the - // token that intersects the end of the range we're supposed to format, so the pair will - // produce bogus edits if we try to `processPair`. Recall that the point of this logic is - // to perform a trailing edit at the end of the selection range: but there can be no valid - // edit in the middle of a token where the range ended, so if we have a non-contiguous - // pair here, we're already done and we can ignore it. - const parent = findPrecedingToken(tokenInfo.end, sourceFile, enclosingNode)?.parent || previousParent!; - processPair( - tokenInfo, - sourceFile.getLineAndCharacterOfPosition(tokenInfo.pos).line, - parent, - previousRange, - previousRangeStartLine!, - previousParent!, - parent, - /*dynamicIndentation*/ undefined); - } + if (tokenInfo && tokenInfo.pos === previousRangeTriviaEnd!) { + // We need to check that tokenInfo and previousRange are contiguous: the `originalRange` + // may have ended in the middle of a token, which means we will have stopped formatting + // on that token, leaving `previousRange` pointing to the token before it, but already + // having moved the formatting scanner (where we just got `tokenInfo`) to the next token. + // If this happens, our supposed pair [previousRange, tokenInfo] actually straddles the + // token that intersects the end of the range we're supposed to format, so the pair will + // produce bogus edits if we try to `processPair`. Recall that the point of this logic is + // to perform a trailing edit at the end of the selection range: but there can be no valid + // edit in the middle of a token where the range ended, so if we have a non-contiguous + // pair here, we're already done and we can ignore it. + const parent = findPrecedingToken(tokenInfo.end, sourceFile, enclosingNode)?.parent || previousParent!; + processPair( + tokenInfo, + sourceFile.getLineAndCharacterOfPosition(tokenInfo.pos).line, + parent, + previousRange, + previousRangeStartLine!, + previousParent!, + parent, + /*dynamicIndentation*/ undefined); } + } - return edits; + return edits; - // local functions + // local functions - /** Tries to compute the indentation for a list element. - * If list element is not in range then - * function will pick its actual indentation - * so it can be pushed downstream as inherited indentation. - * If list element is in the range - its indentation will be equal - * to inherited indentation from its predecessors. - */ - function tryComputeIndentationForListItem(startPos: number, - endPos: number, - parentStartLine: number, - range: TextRange, - inheritedIndentation: number): number { + /** Tries to compute the indentation for a list element. + * If list element is not in range then + * function will pick its actual indentation + * so it can be pushed downstream as inherited indentation. + * If list element is in the range - its indentation will be equal + * to inherited indentation from its predecessors. + */ + function tryComputeIndentationForListItem(startPos: number, + endPos: number, + parentStartLine: number, + range: TextRange, + inheritedIndentation: number): number { - if (rangeOverlapsWithStartEnd(range, startPos, endPos) || - rangeContainsStartEnd(range, startPos, endPos) /* Not to miss zero-range nodes e.g. JsxText */) { + if (rangeOverlapsWithStartEnd(range, startPos, endPos) || + rangeContainsStartEnd(range, startPos, endPos) /* Not to miss zero-range nodes e.g. JsxText */) { - if (inheritedIndentation !== Constants.Unknown) { - return inheritedIndentation; - } + if (inheritedIndentation !== Constants.Unknown) { + return inheritedIndentation; } - else { - const startLine = sourceFile.getLineAndCharacterOfPosition(startPos).line; - const startLinePosition = getLineStartPositionForPosition(startPos, sourceFile); - const column = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, startPos, sourceFile, options); - if (startLine !== parentStartLine || startPos === column) { - // Use the base indent size if it is greater than - // the indentation of the inherited predecessor. - const baseIndentSize = SmartIndenter.getBaseIndentation(options); - return baseIndentSize > column ? baseIndentSize : column; - } + } + else { + const startLine = sourceFile.getLineAndCharacterOfPosition(startPos).line; + const startLinePosition = getLineStartPositionForPosition(startPos, sourceFile); + const column = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, startPos, sourceFile, options); + if (startLine !== parentStartLine || startPos === column) { + // Use the base indent size if it is greater than + // the indentation of the inherited predecessor. + const baseIndentSize = SmartIndenter.getBaseIndentation(options); + return baseIndentSize > column ? baseIndentSize : column; } - - return Constants.Unknown; } - function computeIndentation( - node: TextRangeWithKind, - startLine: number, - inheritedIndentation: number, - parent: Node, - parentDynamicIndentation: DynamicIndentation, - effectiveParentStartLine: number - ): { indentation: number, delta: number; } { - const delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0; - - if (effectiveParentStartLine === startLine) { - // if node is located on the same line with the parent - // - inherit indentation from the parent - // - push children if either parent of node itself has non-zero delta - return { - indentation: startLine === lastIndentedLine ? indentationOnLastIndentedLine : parentDynamicIndentation.getIndentation(), - delta: Math.min(options.indentSize!, parentDynamicIndentation.getDelta(node) + delta) - }; + return Constants.Unknown; + } + + function computeIndentation( + node: TextRangeWithKind, + startLine: number, + inheritedIndentation: number, + parent: Node, + parentDynamicIndentation: DynamicIndentation, + effectiveParentStartLine: number + ): { indentation: number, delta: number; } { + const delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0; + + if (effectiveParentStartLine === startLine) { + // if node is located on the same line with the parent + // - inherit indentation from the parent + // - push children if either parent of node itself has non-zero delta + return { + indentation: startLine === lastIndentedLine ? indentationOnLastIndentedLine : parentDynamicIndentation.getIndentation(), + delta: Math.min(options.indentSize!, parentDynamicIndentation.getDelta(node) + delta) + }; + } + else if (inheritedIndentation === Constants.Unknown) { + if (node.kind === SyntaxKind.OpenParenToken && startLine === lastIndentedLine) { + // the is used for chaining methods formatting + // - we need to get the indentation on last line and the delta of parent + return { indentation: indentationOnLastIndentedLine, delta: parentDynamicIndentation.getDelta(node) }; } - else if (inheritedIndentation === Constants.Unknown) { - if (node.kind === SyntaxKind.OpenParenToken && startLine === lastIndentedLine) { - // the is used for chaining methods formatting - // - we need to get the indentation on last line and the delta of parent - return { indentation: indentationOnLastIndentedLine, delta: parentDynamicIndentation.getDelta(node) }; - } - else if ( - SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(parent, node, startLine, sourceFile) || - SmartIndenter.childIsUnindentedBranchOfConditionalExpression(parent, node, startLine, sourceFile) || - SmartIndenter.argumentStartsOnSameLineAsPreviousArgument(parent, node, startLine, sourceFile) - ) { - return { indentation: parentDynamicIndentation.getIndentation(), delta }; - } - else { - return { indentation: parentDynamicIndentation.getIndentation() + parentDynamicIndentation.getDelta(node), delta }; - } + else if ( + SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(parent, node, startLine, sourceFile) || + SmartIndenter.childIsUnindentedBranchOfConditionalExpression(parent, node, startLine, sourceFile) || + SmartIndenter.argumentStartsOnSameLineAsPreviousArgument(parent, node, startLine, sourceFile) + ) { + return { indentation: parentDynamicIndentation.getIndentation(), delta }; } else { - return { indentation: inheritedIndentation, delta }; + return { indentation: parentDynamicIndentation.getIndentation() + parentDynamicIndentation.getDelta(node), delta }; } } + else { + return { indentation: inheritedIndentation, delta }; + } + } - function getFirstNonDecoratorTokenOfNode(node: Node) { - if (canHaveModifiers(node)) { - const modifier = find(node.modifiers, isModifier, findIndex(node.modifiers, isDecorator)); - if (modifier) return modifier.kind; - } - - switch (node.kind) { - case SyntaxKind.ClassDeclaration: return SyntaxKind.ClassKeyword; - case SyntaxKind.InterfaceDeclaration: return SyntaxKind.InterfaceKeyword; - case SyntaxKind.FunctionDeclaration: return SyntaxKind.FunctionKeyword; - case SyntaxKind.EnumDeclaration: return SyntaxKind.EnumDeclaration; - case SyntaxKind.GetAccessor: return SyntaxKind.GetKeyword; - case SyntaxKind.SetAccessor: return SyntaxKind.SetKeyword; - case SyntaxKind.MethodDeclaration: - if ((node as MethodDeclaration).asteriskToken) { - return SyntaxKind.AsteriskToken; - } - // falls through - - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.Parameter: - const name = getNameOfDeclaration(node as Declaration); - if (name) { - return name.kind; - } - } + function getFirstNonDecoratorTokenOfNode(node: Node) { + if (canHaveModifiers(node)) { + const modifier = find(node.modifiers, isModifier, findIndex(node.modifiers, isDecorator)); + if (modifier) return modifier.kind; } - function getDynamicIndentation(node: Node, nodeStartLine: number, indentation: number, delta: number): DynamicIndentation { - return { - getIndentationForComment: (kind, tokenIndentation, container) => { - switch (kind) { - // preceding comment to the token that closes the indentation scope inherits the indentation from the scope - // .. { - // // comment - // } - case SyntaxKind.CloseBraceToken: - case SyntaxKind.CloseBracketToken: - case SyntaxKind.CloseParenToken: - return indentation + getDelta(container); - } - return tokenIndentation !== Constants.Unknown ? tokenIndentation : indentation; - }, - // if list end token is LessThanToken '>' then its delta should be explicitly suppressed - // so that LessThanToken as a binary operator can still be indented. - // foo.then - // < - // number, - // string, - // >(); - // vs - // var a = xValue - // > yValue; - getIndentationForToken: (line, kind, container, suppressDelta) => - !suppressDelta && shouldAddDelta(line, kind, container) ? indentation + getDelta(container) : indentation, - getIndentation: () => indentation, - getDelta, - recomputeIndentation: (lineAdded, parent) => { - if (SmartIndenter.shouldIndentChildNode(options, parent, node, sourceFile)) { - indentation += lineAdded ? options.indentSize! : -options.indentSize!; - delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0; - } + switch (node.kind) { + case SyntaxKind.ClassDeclaration: return SyntaxKind.ClassKeyword; + case SyntaxKind.InterfaceDeclaration: return SyntaxKind.InterfaceKeyword; + case SyntaxKind.FunctionDeclaration: return SyntaxKind.FunctionKeyword; + case SyntaxKind.EnumDeclaration: return SyntaxKind.EnumDeclaration; + case SyntaxKind.GetAccessor: return SyntaxKind.GetKeyword; + case SyntaxKind.SetAccessor: return SyntaxKind.SetKeyword; + case SyntaxKind.MethodDeclaration: + if ((node as MethodDeclaration).asteriskToken) { + return SyntaxKind.AsteriskToken; } - }; + // falls through + + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.Parameter: + const name = getNameOfDeclaration(node as Declaration); + if (name) { + return name.kind; + } + } + } - function shouldAddDelta(line: number, kind: SyntaxKind, container: Node): boolean { + function getDynamicIndentation(node: Node, nodeStartLine: number, indentation: number, delta: number): DynamicIndentation { + return { + getIndentationForComment: (kind, tokenIndentation, container) => { switch (kind) { - // open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent - case SyntaxKind.OpenBraceToken: + // preceding comment to the token that closes the indentation scope inherits the indentation from the scope + // .. { + // // comment + // } case SyntaxKind.CloseBraceToken: - case SyntaxKind.CloseParenToken: - case SyntaxKind.ElseKeyword: - case SyntaxKind.WhileKeyword: - case SyntaxKind.AtToken: - return false; - case SyntaxKind.SlashToken: - case SyntaxKind.GreaterThanToken: - switch (container.kind) { - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxClosingElement: - case SyntaxKind.JsxSelfClosingElement: - return false; - } - break; - case SyntaxKind.OpenBracketToken: case SyntaxKind.CloseBracketToken: - if (container.kind !== SyntaxKind.MappedType) { - return false; - } - break; + case SyntaxKind.CloseParenToken: + return indentation + getDelta(container); + } + return tokenIndentation !== Constants.Unknown ? tokenIndentation : indentation; + }, + // if list end token is LessThanToken '>' then its delta should be explicitly suppressed + // so that LessThanToken as a binary operator can still be indented. + // foo.then + // < + // number, + // string, + // >(); + // vs + // var a = xValue + // > yValue; + getIndentationForToken: (line, kind, container, suppressDelta) => + !suppressDelta && shouldAddDelta(line, kind, container) ? indentation + getDelta(container) : indentation, + getIndentation: () => indentation, + getDelta, + recomputeIndentation: (lineAdded, parent) => { + if (SmartIndenter.shouldIndentChildNode(options, parent, node, sourceFile)) { + indentation += lineAdded ? options.indentSize! : -options.indentSize!; + delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0; } - // if token line equals to the line of containing node (this is a first token in the node) - use node indentation - return nodeStartLine !== line - // if this token is the first token following the list of decorators, we do not need to indent - && !(hasDecorators(node) && kind === getFirstNonDecoratorTokenOfNode(node)); } + }; - function getDelta(child: TextRangeWithKind) { - // Delta value should be zero when the node explicitly prevents indentation of the child node - return SmartIndenter.nodeWillIndentChild(options, node, child, sourceFile, /*indentByDefault*/ true) ? delta : 0; + function shouldAddDelta(line: number, kind: SyntaxKind, container: Node): boolean { + switch (kind) { + // open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent + case SyntaxKind.OpenBraceToken: + case SyntaxKind.CloseBraceToken: + case SyntaxKind.CloseParenToken: + case SyntaxKind.ElseKeyword: + case SyntaxKind.WhileKeyword: + case SyntaxKind.AtToken: + return false; + case SyntaxKind.SlashToken: + case SyntaxKind.GreaterThanToken: + switch (container.kind) { + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxSelfClosingElement: + return false; + } + break; + case SyntaxKind.OpenBracketToken: + case SyntaxKind.CloseBracketToken: + if (container.kind !== SyntaxKind.MappedType) { + return false; + } + break; } + // if token line equals to the line of containing node (this is a first token in the node) - use node indentation + return nodeStartLine !== line + // if this token is the first token following the list of decorators, we do not need to indent + && !(hasDecorators(node) && kind === getFirstNonDecoratorTokenOfNode(node)); } - function processNode(node: Node, contextNode: Node, nodeStartLine: number, undecoratedNodeStartLine: number, indentation: number, delta: number) { - if (!rangeOverlapsWithStartEnd(originalRange, node.getStart(sourceFile), node.getEnd())) { - return; - } + function getDelta(child: TextRangeWithKind) { + // Delta value should be zero when the node explicitly prevents indentation of the child node + return SmartIndenter.nodeWillIndentChild(options, node, child, sourceFile, /*indentByDefault*/ true) ? delta : 0; + } + } - const nodeDynamicIndentation = getDynamicIndentation(node, nodeStartLine, indentation, delta); - - // a useful observations when tracking context node - // / - // [a] - // / | \ - // [b] [c] [d] - // node 'a' is a context node for nodes 'b', 'c', 'd' - // except for the leftmost leaf token in [b] - in this case context node ('e') is located somewhere above 'a' - // this rule can be applied recursively to child nodes of 'a'. - // - // context node is set to parent node value after processing every child node - // context node is set to parent of the token after processing every token - - let childContextNode = contextNode; - - // if there are any tokens that logically belong to node and interleave child nodes - // such tokens will be consumed in processChildNode for the child that follows them - forEachChild( - node, - child => { - processChildNode(child, /*inheritedIndentation*/ Constants.Unknown, node, nodeDynamicIndentation, nodeStartLine, undecoratedNodeStartLine, /*isListItem*/ false); - }, - nodes => { - processChildNodes(nodes, node, nodeStartLine, nodeDynamicIndentation); - }); - - // proceed any tokens in the node that are located after child nodes - while (formattingScanner.isOnToken() && formattingScanner.getStartPos() < originalRange.end) { - const tokenInfo = formattingScanner.readTokenInfo(node); - if (tokenInfo.token.end > Math.min(node.end, originalRange.end)) { - break; - } - consumeTokenAndAdvanceScanner(tokenInfo, node, nodeDynamicIndentation, node); + function processNode(node: Node, contextNode: Node, nodeStartLine: number, undecoratedNodeStartLine: number, indentation: number, delta: number) { + if (!rangeOverlapsWithStartEnd(originalRange, node.getStart(sourceFile), node.getEnd())) { + return; + } + + const nodeDynamicIndentation = getDynamicIndentation(node, nodeStartLine, indentation, delta); + + // a useful observations when tracking context node + // / + // [a] + // / | \ + // [b] [c] [d] + // node 'a' is a context node for nodes 'b', 'c', 'd' + // except for the leftmost leaf token in [b] - in this case context node ('e') is located somewhere above 'a' + // this rule can be applied recursively to child nodes of 'a'. + // + // context node is set to parent node value after processing every child node + // context node is set to parent of the token after processing every token + + let childContextNode = contextNode; + + // if there are any tokens that logically belong to node and interleave child nodes + // such tokens will be consumed in processChildNode for the child that follows them + forEachChild( + node, + child => { + processChildNode(child, /*inheritedIndentation*/ Constants.Unknown, node, nodeDynamicIndentation, nodeStartLine, undecoratedNodeStartLine, /*isListItem*/ false); + }, + nodes => { + processChildNodes(nodes, node, nodeStartLine, nodeDynamicIndentation); + }); + + // proceed any tokens in the node that are located after child nodes + while (formattingScanner.isOnToken() && formattingScanner.getStartPos() < originalRange.end) { + const tokenInfo = formattingScanner.readTokenInfo(node); + if (tokenInfo.token.end > Math.min(node.end, originalRange.end)) { + break; } + consumeTokenAndAdvanceScanner(tokenInfo, node, nodeDynamicIndentation, node); + } - function processChildNode( - child: Node, - inheritedIndentation: number, - parent: Node, - parentDynamicIndentation: DynamicIndentation, - parentStartLine: number, - undecoratedParentStartLine: number, - isListItem: boolean, - isFirstListItem?: boolean): number { - Debug.assert(!nodeIsSynthesized(child)); - - if (nodeIsMissing(child)) { - return inheritedIndentation; - } + function processChildNode( + child: Node, + inheritedIndentation: number, + parent: Node, + parentDynamicIndentation: DynamicIndentation, + parentStartLine: number, + undecoratedParentStartLine: number, + isListItem: boolean, + isFirstListItem?: boolean): number { + Debug.assert(!nodeIsSynthesized(child)); - const childStartPos = child.getStart(sourceFile); + if (nodeIsMissing(child)) { + return inheritedIndentation; + } - const childStartLine = sourceFile.getLineAndCharacterOfPosition(childStartPos).line; + const childStartPos = child.getStart(sourceFile); - let undecoratedChildStartLine = childStartLine; - if (hasDecorators(child)) { - undecoratedChildStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(child, sourceFile)).line; - } + const childStartLine = sourceFile.getLineAndCharacterOfPosition(childStartPos).line; - // if child is a list item - try to get its indentation, only if parent is within the original range. - let childIndentationAmount = Constants.Unknown; + let undecoratedChildStartLine = childStartLine; + if (hasDecorators(child)) { + undecoratedChildStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(child, sourceFile)).line; + } - if (isListItem && rangeContainsRange(originalRange, parent)) { - childIndentationAmount = tryComputeIndentationForListItem(childStartPos, child.end, parentStartLine, originalRange, inheritedIndentation); - if (childIndentationAmount !== Constants.Unknown) { - inheritedIndentation = childIndentationAmount; - } + // if child is a list item - try to get its indentation, only if parent is within the original range. + let childIndentationAmount = Constants.Unknown; + + if (isListItem && rangeContainsRange(originalRange, parent)) { + childIndentationAmount = tryComputeIndentationForListItem(childStartPos, child.end, parentStartLine, originalRange, inheritedIndentation); + if (childIndentationAmount !== Constants.Unknown) { + inheritedIndentation = childIndentationAmount; } + } - // child node is outside the target range - do not dive inside - if (!rangeOverlapsWithStartEnd(originalRange, child.pos, child.end)) { - if (child.end < originalRange.pos) { - formattingScanner.skipToEndOf(child); - } - return inheritedIndentation; + // child node is outside the target range - do not dive inside + if (!rangeOverlapsWithStartEnd(originalRange, child.pos, child.end)) { + if (child.end < originalRange.pos) { + formattingScanner.skipToEndOf(child); } + return inheritedIndentation; + } + + if (child.getFullWidth() === 0) { + return inheritedIndentation; + } - if (child.getFullWidth() === 0) { + while (formattingScanner.isOnToken() && formattingScanner.getStartPos() < originalRange.end) { + // proceed any parent tokens that are located prior to child.getStart() + const tokenInfo = formattingScanner.readTokenInfo(node); + if (tokenInfo.token.end > originalRange.end) { return inheritedIndentation; } - - while (formattingScanner.isOnToken() && formattingScanner.getStartPos() < originalRange.end) { - // proceed any parent tokens that are located prior to child.getStart() - const tokenInfo = formattingScanner.readTokenInfo(node); - if (tokenInfo.token.end > originalRange.end) { - return inheritedIndentation; - } - if (tokenInfo.token.end > childStartPos) { - if (tokenInfo.token.pos > childStartPos) { - formattingScanner.skipToStartOf(child); - } - // stop when formatting scanner advances past the beginning of the child - break; + if (tokenInfo.token.end > childStartPos) { + if (tokenInfo.token.pos > childStartPos) { + formattingScanner.skipToStartOf(child); } - - consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, node); + // stop when formatting scanner advances past the beginning of the child + break; } - if (!formattingScanner.isOnToken() || formattingScanner.getStartPos() >= originalRange.end) { + consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, node); + } + + if (!formattingScanner.isOnToken() || formattingScanner.getStartPos() >= originalRange.end) { + return inheritedIndentation; + } + + if (isToken(child)) { + // if child node is a token, it does not impact indentation, proceed it using parent indentation scope rules + const tokenInfo = formattingScanner.readTokenInfo(child); + // JSX text shouldn't affect indenting + if (child.kind !== SyntaxKind.JsxText) { + Debug.assert(tokenInfo.token.end === child.end, "Token end is child end"); + consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, child); return inheritedIndentation; } + } - if (isToken(child)) { - // if child node is a token, it does not impact indentation, proceed it using parent indentation scope rules - const tokenInfo = formattingScanner.readTokenInfo(child); - // JSX text shouldn't affect indenting - if (child.kind !== SyntaxKind.JsxText) { - Debug.assert(tokenInfo.token.end === child.end, "Token end is child end"); - consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, child); - return inheritedIndentation; - } - } + const effectiveParentStartLine = child.kind === SyntaxKind.Decorator ? childStartLine : undecoratedParentStartLine; + const childIndentation = computeIndentation(child, childStartLine, childIndentationAmount, node, parentDynamicIndentation, effectiveParentStartLine); - const effectiveParentStartLine = child.kind === SyntaxKind.Decorator ? childStartLine : undecoratedParentStartLine; - const childIndentation = computeIndentation(child, childStartLine, childIndentationAmount, node, parentDynamicIndentation, effectiveParentStartLine); + processNode(child, childContextNode, childStartLine, undecoratedChildStartLine, childIndentation.indentation, childIndentation.delta); - processNode(child, childContextNode, childStartLine, undecoratedChildStartLine, childIndentation.indentation, childIndentation.delta); + childContextNode = node; - childContextNode = node; + if (isFirstListItem && parent.kind === SyntaxKind.ArrayLiteralExpression && inheritedIndentation === Constants.Unknown) { + inheritedIndentation = childIndentation.indentation; + } - if (isFirstListItem && parent.kind === SyntaxKind.ArrayLiteralExpression && inheritedIndentation === Constants.Unknown) { - inheritedIndentation = childIndentation.indentation; - } + return inheritedIndentation; + } - return inheritedIndentation; + function processChildNodes(nodes: NodeArray, + parent: Node, + parentStartLine: number, + parentDynamicIndentation: DynamicIndentation): void { + Debug.assert(isNodeArray(nodes)); + Debug.assert(!nodeIsSynthesized(nodes)); + + const listStartToken = getOpenTokenForList(parent, nodes); + + let listDynamicIndentation = parentDynamicIndentation; + let startLine = parentStartLine; + // node range is outside the target range - do not dive inside + if (!rangeOverlapsWithStartEnd(originalRange, nodes.pos, nodes.end)) { + if (nodes.end < originalRange.pos) { + formattingScanner.skipToEndOf(nodes); + } + return; } - function processChildNodes(nodes: NodeArray, - parent: Node, - parentStartLine: number, - parentDynamicIndentation: DynamicIndentation): void { - Debug.assert(isNodeArray(nodes)); - Debug.assert(!nodeIsSynthesized(nodes)); - - const listStartToken = getOpenTokenForList(parent, nodes); - - let listDynamicIndentation = parentDynamicIndentation; - let startLine = parentStartLine; - // node range is outside the target range - do not dive inside - if (!rangeOverlapsWithStartEnd(originalRange, nodes.pos, nodes.end)) { - if (nodes.end < originalRange.pos) { - formattingScanner.skipToEndOf(nodes); + if (listStartToken !== SyntaxKind.Unknown) { + // introduce a new indentation scope for lists (including list start and end tokens) + while (formattingScanner.isOnToken() && formattingScanner.getStartPos() < originalRange.end) { + const tokenInfo = formattingScanner.readTokenInfo(parent); + if (tokenInfo.token.end > nodes.pos) { + // stop when formatting scanner moves past the beginning of node list + break; } - return; - } - - if (listStartToken !== SyntaxKind.Unknown) { - // introduce a new indentation scope for lists (including list start and end tokens) - while (formattingScanner.isOnToken() && formattingScanner.getStartPos() < originalRange.end) { - const tokenInfo = formattingScanner.readTokenInfo(parent); - if (tokenInfo.token.end > nodes.pos) { - // stop when formatting scanner moves past the beginning of node list - break; - } - else if (tokenInfo.token.kind === listStartToken) { - // consume list start token - startLine = sourceFile.getLineAndCharacterOfPosition(tokenInfo.token.pos).line; - - consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent); - - let indentationOnListStartToken: number; - if (indentationOnLastIndentedLine !== Constants.Unknown) { - // scanner just processed list start token so consider last indentation as list indentation - // function foo(): { // last indentation was 0, list item will be indented based on this value - // foo: number; - // }: {}; - indentationOnListStartToken = indentationOnLastIndentedLine; - } - else { - const startLinePosition = getLineStartPositionForPosition(tokenInfo.token.pos, sourceFile); - indentationOnListStartToken = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, tokenInfo.token.pos, sourceFile, options); - } - - listDynamicIndentation = getDynamicIndentation(parent, parentStartLine, indentationOnListStartToken, options.indentSize!); // TODO: GH#18217 + else if (tokenInfo.token.kind === listStartToken) { + // consume list start token + startLine = sourceFile.getLineAndCharacterOfPosition(tokenInfo.token.pos).line; + + consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent); + + let indentationOnListStartToken: number; + if (indentationOnLastIndentedLine !== Constants.Unknown) { + // scanner just processed list start token so consider last indentation as list indentation + // function foo(): { // last indentation was 0, list item will be indented based on this value + // foo: number; + // }: {}; + indentationOnListStartToken = indentationOnLastIndentedLine; } else { - // consume any tokens that precede the list as child elements of 'node' using its indentation scope - consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent); + const startLinePosition = getLineStartPositionForPosition(tokenInfo.token.pos, sourceFile); + indentationOnListStartToken = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, tokenInfo.token.pos, sourceFile, options); } - } - } - let inheritedIndentation = Constants.Unknown; - for (let i = 0; i < nodes.length; i++) { - const child = nodes[i]; - inheritedIndentation = processChildNode(child, inheritedIndentation, node, listDynamicIndentation, startLine, startLine, /*isListItem*/ true, /*isFirstListItem*/ i === 0); - } - - const listEndToken = getCloseTokenForOpenToken(listStartToken); - if (listEndToken !== SyntaxKind.Unknown && formattingScanner.isOnToken() && formattingScanner.getStartPos() < originalRange.end) { - let tokenInfo: TokenInfo | undefined = formattingScanner.readTokenInfo(parent); - if (tokenInfo.token.kind === SyntaxKind.CommaToken) { - // consume the comma - consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent); - tokenInfo = formattingScanner.isOnToken() ? formattingScanner.readTokenInfo(parent) : undefined; + listDynamicIndentation = getDynamicIndentation(parent, parentStartLine, indentationOnListStartToken, options.indentSize!); // TODO: GH#18217 } - - // consume the list end token only if it is still belong to the parent - // there might be the case when current token matches end token but does not considered as one - // function (x: function) <-- - // without this check close paren will be interpreted as list end token for function expression which is wrong - if (tokenInfo && tokenInfo.token.kind === listEndToken && rangeContainsRange(parent, tokenInfo.token)) { - // consume list end token - consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent, /*isListEndToken*/ true); + else { + // consume any tokens that precede the list as child elements of 'node' using its indentation scope + consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent); } } } - function consumeTokenAndAdvanceScanner(currentTokenInfo: TokenInfo, parent: Node, dynamicIndentation: DynamicIndentation, container: Node, isListEndToken?: boolean): void { - Debug.assert(rangeContainsRange(parent, currentTokenInfo.token)); - - const lastTriviaWasNewLine = formattingScanner.lastTrailingTriviaWasNewLine(); - let indentToken = false; + let inheritedIndentation = Constants.Unknown; + for (let i = 0; i < nodes.length; i++) { + const child = nodes[i]; + inheritedIndentation = processChildNode(child, inheritedIndentation, node, listDynamicIndentation, startLine, startLine, /*isListItem*/ true, /*isFirstListItem*/ i === 0); + } - if (currentTokenInfo.leadingTrivia) { - processTrivia(currentTokenInfo.leadingTrivia, parent, childContextNode, dynamicIndentation); + const listEndToken = getCloseTokenForOpenToken(listStartToken); + if (listEndToken !== SyntaxKind.Unknown && formattingScanner.isOnToken() && formattingScanner.getStartPos() < originalRange.end) { + let tokenInfo: TokenInfo | undefined = formattingScanner.readTokenInfo(parent); + if (tokenInfo.token.kind === SyntaxKind.CommaToken) { + // consume the comma + consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent); + tokenInfo = formattingScanner.isOnToken() ? formattingScanner.readTokenInfo(parent) : undefined; } - let lineAction = LineAction.None; - const isTokenInRange = rangeContainsRange(originalRange, currentTokenInfo.token); - - const tokenStart = sourceFile.getLineAndCharacterOfPosition(currentTokenInfo.token.pos); - if (isTokenInRange) { - const rangeHasError = rangeContainsError(currentTokenInfo.token); - // save previousRange since processRange will overwrite this value with current one - const savePreviousRange = previousRange; - lineAction = processRange(currentTokenInfo.token, tokenStart, parent, childContextNode, dynamicIndentation); - // do not indent comments\token if token range overlaps with some error - if (!rangeHasError) { - if (lineAction === LineAction.None) { - // indent token only if end line of previous range does not match start line of the token - const prevEndLine = savePreviousRange && sourceFile.getLineAndCharacterOfPosition(savePreviousRange.end).line; - indentToken = lastTriviaWasNewLine && tokenStart.line !== prevEndLine; - } - else { - indentToken = lineAction === LineAction.LineAdded; - } - } - } - - if (currentTokenInfo.trailingTrivia) { - previousRangeTriviaEnd = last(currentTokenInfo.trailingTrivia).end; - processTrivia(currentTokenInfo.trailingTrivia, parent, childContextNode, dynamicIndentation); + // consume the list end token only if it is still belong to the parent + // there might be the case when current token matches end token but does not considered as one + // function (x: function) <-- + // without this check close paren will be interpreted as list end token for function expression which is wrong + if (tokenInfo && tokenInfo.token.kind === listEndToken && rangeContainsRange(parent, tokenInfo.token)) { + // consume list end token + consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent, /*isListEndToken*/ true); } + } + } - if (indentToken) { - const tokenIndentation = (isTokenInRange && !rangeContainsError(currentTokenInfo.token)) ? - dynamicIndentation.getIndentationForToken(tokenStart.line, currentTokenInfo.token.kind, container, !!isListEndToken) : - Constants.Unknown; + function consumeTokenAndAdvanceScanner(currentTokenInfo: TokenInfo, parent: Node, dynamicIndentation: DynamicIndentation, container: Node, isListEndToken?: boolean): void { + Debug.assert(rangeContainsRange(parent, currentTokenInfo.token)); - let indentNextTokenOrTrivia = true; - if (currentTokenInfo.leadingTrivia) { - const commentIndentation = dynamicIndentation.getIndentationForComment(currentTokenInfo.token.kind, tokenIndentation, container); - indentNextTokenOrTrivia = indentTriviaItems(currentTokenInfo.leadingTrivia, commentIndentation, indentNextTokenOrTrivia, - item => insertIndentation(item.pos, commentIndentation, /*lineAdded*/ false)); - } + const lastTriviaWasNewLine = formattingScanner.lastTrailingTriviaWasNewLine(); + let indentToken = false; - // indent token only if is it is in target range and does not overlap with any error ranges - if (tokenIndentation !== Constants.Unknown && indentNextTokenOrTrivia) { - insertIndentation(currentTokenInfo.token.pos, tokenIndentation, lineAction === LineAction.LineAdded); + if (currentTokenInfo.leadingTrivia) { + processTrivia(currentTokenInfo.leadingTrivia, parent, childContextNode, dynamicIndentation); + } - lastIndentedLine = tokenStart.line; - indentationOnLastIndentedLine = tokenIndentation; + let lineAction = LineAction.None; + const isTokenInRange = rangeContainsRange(originalRange, currentTokenInfo.token); + + const tokenStart = sourceFile.getLineAndCharacterOfPosition(currentTokenInfo.token.pos); + if (isTokenInRange) { + const rangeHasError = rangeContainsError(currentTokenInfo.token); + // save previousRange since processRange will overwrite this value with current one + const savePreviousRange = previousRange; + lineAction = processRange(currentTokenInfo.token, tokenStart, parent, childContextNode, dynamicIndentation); + // do not indent comments\token if token range overlaps with some error + if (!rangeHasError) { + if (lineAction === LineAction.None) { + // indent token only if end line of previous range does not match start line of the token + const prevEndLine = savePreviousRange && sourceFile.getLineAndCharacterOfPosition(savePreviousRange.end).line; + indentToken = lastTriviaWasNewLine && tokenStart.line !== prevEndLine; + } + else { + indentToken = lineAction === LineAction.LineAdded; } } - - formattingScanner.advance(); - - childContextNode = parent; } - } - function indentTriviaItems( - trivia: TextRangeWithKind[], - commentIndentation: number, - indentNextTokenOrTrivia: boolean, - indentSingleLine: (item: TextRangeWithKind) => void) { - for (const triviaItem of trivia) { - const triviaInRange = rangeContainsRange(originalRange, triviaItem); - switch (triviaItem.kind) { - case SyntaxKind.MultiLineCommentTrivia: - if (triviaInRange) { - indentMultilineComment(triviaItem, commentIndentation, /*firstLineIsIndented*/ !indentNextTokenOrTrivia); - } - indentNextTokenOrTrivia = false; - break; - case SyntaxKind.SingleLineCommentTrivia: - if (indentNextTokenOrTrivia && triviaInRange) { - indentSingleLine(triviaItem); - } - indentNextTokenOrTrivia = false; - break; - case SyntaxKind.NewLineTrivia: - indentNextTokenOrTrivia = true; - break; - } + if (currentTokenInfo.trailingTrivia) { + previousRangeTriviaEnd = last(currentTokenInfo.trailingTrivia).end; + processTrivia(currentTokenInfo.trailingTrivia, parent, childContextNode, dynamicIndentation); } - return indentNextTokenOrTrivia; - } - function processTrivia(trivia: TextRangeWithKind[], parent: Node, contextNode: Node, dynamicIndentation: DynamicIndentation): void { - for (const triviaItem of trivia) { - if (isComment(triviaItem.kind) && rangeContainsRange(originalRange, triviaItem)) { - const triviaItemStart = sourceFile.getLineAndCharacterOfPosition(triviaItem.pos); - processRange(triviaItem, triviaItemStart, parent, contextNode, dynamicIndentation); + if (indentToken) { + const tokenIndentation = (isTokenInRange && !rangeContainsError(currentTokenInfo.token)) ? + dynamicIndentation.getIndentationForToken(tokenStart.line, currentTokenInfo.token.kind, container, !!isListEndToken) : + Constants.Unknown; + + let indentNextTokenOrTrivia = true; + if (currentTokenInfo.leadingTrivia) { + const commentIndentation = dynamicIndentation.getIndentationForComment(currentTokenInfo.token.kind, tokenIndentation, container); + indentNextTokenOrTrivia = indentTriviaItems(currentTokenInfo.leadingTrivia, commentIndentation, indentNextTokenOrTrivia, + item => insertIndentation(item.pos, commentIndentation, /*lineAdded*/ false)); } - } - } - function processRange(range: TextRangeWithKind, - rangeStart: LineAndCharacter, - parent: Node, - contextNode: Node, - dynamicIndentation: DynamicIndentation): LineAction { + // indent token only if is it is in target range and does not overlap with any error ranges + if (tokenIndentation !== Constants.Unknown && indentNextTokenOrTrivia) { + insertIndentation(currentTokenInfo.token.pos, tokenIndentation, lineAction === LineAction.LineAdded); - const rangeHasError = rangeContainsError(range); - let lineAction = LineAction.None; - if (!rangeHasError) { - if (!previousRange) { - // trim whitespaces starting from the beginning of the span up to the current line - const originalStart = sourceFile.getLineAndCharacterOfPosition(originalRange.pos); - trimTrailingWhitespacesForLines(originalStart.line, rangeStart.line); - } - else { - lineAction = - processPair(range, rangeStart.line, parent, previousRange, previousRangeStartLine, previousParent, contextNode, dynamicIndentation); + lastIndentedLine = tokenStart.line; + indentationOnLastIndentedLine = tokenIndentation; } } - previousRange = range; - previousRangeTriviaEnd = range.end; - previousParent = parent; - previousRangeStartLine = rangeStart.line; + formattingScanner.advance(); - return lineAction; + childContextNode = parent; } + } - function processPair(currentItem: TextRangeWithKind, - currentStartLine: number, - currentParent: Node, - previousItem: TextRangeWithKind, - previousStartLine: number, - previousParent: Node, - contextNode: Node, - dynamicIndentation: DynamicIndentation | undefined): LineAction { - - formattingContext.updateContext(previousItem, previousParent, currentItem, currentParent, contextNode); - - const rules = getRules(formattingContext); - - let trimTrailingWhitespaces = formattingContext.options.trimTrailingWhitespace !== false; - let lineAction = LineAction.None; - if (rules) { - // Apply rules in reverse order so that higher priority rules (which are first in the array) - // win in a conflict with lower priority rules. - forEachRight(rules, rule => { - lineAction = applyRuleEdits(rule, previousItem, previousStartLine, currentItem, currentStartLine); - if (dynamicIndentation) { - switch (lineAction) { - case LineAction.LineRemoved: - // Handle the case where the next line is moved to be the end of this line. - // In this case we don't indent the next line in the next pass. - if (currentParent.getStart(sourceFile) === currentItem.pos) { - dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ false, contextNode); - } - break; - case LineAction.LineAdded: - // Handle the case where token2 is moved to the new line. - // In this case we indent token2 in the next pass but we set - // sameLineIndent flag to notify the indenter that the indentation is within the line. - if (currentParent.getStart(sourceFile) === currentItem.pos) { - dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ true, contextNode); - } - break; - default: - Debug.assert(lineAction === LineAction.None); - } + function indentTriviaItems( + trivia: TextRangeWithKind[], + commentIndentation: number, + indentNextTokenOrTrivia: boolean, + indentSingleLine: (item: TextRangeWithKind) => void) { + for (const triviaItem of trivia) { + const triviaInRange = rangeContainsRange(originalRange, triviaItem); + switch (triviaItem.kind) { + case SyntaxKind.MultiLineCommentTrivia: + if (triviaInRange) { + indentMultilineComment(triviaItem, commentIndentation, /*firstLineIsIndented*/ !indentNextTokenOrTrivia); } - - // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line - trimTrailingWhitespaces = trimTrailingWhitespaces && !(rule.action & RuleAction.DeleteSpace) && rule.flags !== RuleFlags.CanDeleteNewLines; - }); - } - else { - trimTrailingWhitespaces = trimTrailingWhitespaces && currentItem.kind !== SyntaxKind.EndOfFileToken; + indentNextTokenOrTrivia = false; + break; + case SyntaxKind.SingleLineCommentTrivia: + if (indentNextTokenOrTrivia && triviaInRange) { + indentSingleLine(triviaItem); + } + indentNextTokenOrTrivia = false; + break; + case SyntaxKind.NewLineTrivia: + indentNextTokenOrTrivia = true; + break; } + } + return indentNextTokenOrTrivia; + } - if (currentStartLine !== previousStartLine && trimTrailingWhitespaces) { - // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line - trimTrailingWhitespacesForLines(previousStartLine, currentStartLine, previousItem); + function processTrivia(trivia: TextRangeWithKind[], parent: Node, contextNode: Node, dynamicIndentation: DynamicIndentation): void { + for (const triviaItem of trivia) { + if (isComment(triviaItem.kind) && rangeContainsRange(originalRange, triviaItem)) { + const triviaItemStart = sourceFile.getLineAndCharacterOfPosition(triviaItem.pos); + processRange(triviaItem, triviaItemStart, parent, contextNode, dynamicIndentation); } - - return lineAction; } + } - function insertIndentation(pos: number, indentation: number, lineAdded: boolean | undefined): void { - const indentationString = getIndentationString(indentation, options); - if (lineAdded) { - // new line is added before the token by the formatting rules - // insert indentation string at the very beginning of the token - recordReplace(pos, 0, indentationString); + function processRange(range: TextRangeWithKind, + rangeStart: LineAndCharacter, + parent: Node, + contextNode: Node, + dynamicIndentation: DynamicIndentation): LineAction { + + const rangeHasError = rangeContainsError(range); + let lineAction = LineAction.None; + if (!rangeHasError) { + if (!previousRange) { + // trim whitespaces starting from the beginning of the span up to the current line + const originalStart = sourceFile.getLineAndCharacterOfPosition(originalRange.pos); + trimTrailingWhitespacesForLines(originalStart.line, rangeStart.line); } else { - const tokenStart = sourceFile.getLineAndCharacterOfPosition(pos); - const startLinePosition = getStartPositionOfLine(tokenStart.line, sourceFile); - if (indentation !== characterToColumn(startLinePosition, tokenStart.character) || indentationIsDifferent(indentationString, startLinePosition)) { - recordReplace(startLinePosition, tokenStart.character, indentationString); - } + lineAction = + processPair(range, rangeStart.line, parent, previousRange, previousRangeStartLine, previousParent, contextNode, dynamicIndentation); } } - function characterToColumn(startLinePosition: number, characterInLine: number): number { - let column = 0; - for (let i = 0; i < characterInLine; i++) { - if (sourceFile.text.charCodeAt(startLinePosition + i) === CharacterCodes.tab) { - column += options.tabSize! - column % options.tabSize!; - } - else { - column++; + previousRange = range; + previousRangeTriviaEnd = range.end; + previousParent = parent; + previousRangeStartLine = rangeStart.line; + + return lineAction; + } + + function processPair(currentItem: TextRangeWithKind, + currentStartLine: number, + currentParent: Node, + previousItem: TextRangeWithKind, + previousStartLine: number, + previousParent: Node, + contextNode: Node, + dynamicIndentation: DynamicIndentation | undefined): LineAction { + + formattingContext.updateContext(previousItem, previousParent, currentItem, currentParent, contextNode); + + const rules = getRules(formattingContext); + + let trimTrailingWhitespaces = formattingContext.options.trimTrailingWhitespace !== false; + let lineAction = LineAction.None; + if (rules) { + // Apply rules in reverse order so that higher priority rules (which are first in the array) + // win in a conflict with lower priority rules. + forEachRight(rules, rule => { + lineAction = applyRuleEdits(rule, previousItem, previousStartLine, currentItem, currentStartLine); + if (dynamicIndentation) { + switch (lineAction) { + case LineAction.LineRemoved: + // Handle the case where the next line is moved to be the end of this line. + // In this case we don't indent the next line in the next pass. + if (currentParent.getStart(sourceFile) === currentItem.pos) { + dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ false, contextNode); + } + break; + case LineAction.LineAdded: + // Handle the case where token2 is moved to the new line. + // In this case we indent token2 in the next pass but we set + // sameLineIndent flag to notify the indenter that the indentation is within the line. + if (currentParent.getStart(sourceFile) === currentItem.pos) { + dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ true, contextNode); + } + break; + default: + Debug.assert(lineAction === LineAction.None); + } } - } - return column; + + // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line + trimTrailingWhitespaces = trimTrailingWhitespaces && !(rule.action & RuleAction.DeleteSpace) && rule.flags !== RuleFlags.CanDeleteNewLines; + }); + } + else { + trimTrailingWhitespaces = trimTrailingWhitespaces && currentItem.kind !== SyntaxKind.EndOfFileToken; } - function indentationIsDifferent(indentationString: string, startLinePosition: number): boolean { - return indentationString !== sourceFile.text.substr(startLinePosition, indentationString.length); + if (currentStartLine !== previousStartLine && trimTrailingWhitespaces) { + // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line + trimTrailingWhitespacesForLines(previousStartLine, currentStartLine, previousItem); } - function indentMultilineComment(commentRange: TextRange, indentation: number, firstLineIsIndented: boolean, indentFinalLine = true) { - // split comment in lines - let startLine = sourceFile.getLineAndCharacterOfPosition(commentRange.pos).line; - const endLine = sourceFile.getLineAndCharacterOfPosition(commentRange.end).line; - if (startLine === endLine) { - if (!firstLineIsIndented) { - // treat as single line comment - insertIndentation(commentRange.pos, indentation, /*lineAdded*/ false); - } - return; - } + return lineAction; + } - const parts: TextRange[] = []; - let startPos = commentRange.pos; - for (let line = startLine; line < endLine; line++) { - const endOfLine = getEndLinePosition(line, sourceFile); - parts.push({ pos: startPos, end: endOfLine }); - startPos = getStartPositionOfLine(line + 1, sourceFile); + function insertIndentation(pos: number, indentation: number, lineAdded: boolean | undefined): void { + const indentationString = getIndentationString(indentation, options); + if (lineAdded) { + // new line is added before the token by the formatting rules + // insert indentation string at the very beginning of the token + recordReplace(pos, 0, indentationString); + } + else { + const tokenStart = sourceFile.getLineAndCharacterOfPosition(pos); + const startLinePosition = getStartPositionOfLine(tokenStart.line, sourceFile); + if (indentation !== characterToColumn(startLinePosition, tokenStart.character) || indentationIsDifferent(indentationString, startLinePosition)) { + recordReplace(startLinePosition, tokenStart.character, indentationString); } + } + } - if (indentFinalLine) { - parts.push({ pos: startPos, end: commentRange.end }); + function characterToColumn(startLinePosition: number, characterInLine: number): number { + let column = 0; + for (let i = 0; i < characterInLine; i++) { + if (sourceFile.text.charCodeAt(startLinePosition + i) === CharacterCodes.tab) { + column += options.tabSize! - column % options.tabSize!; } + else { + column++; + } + } + return column; + } - if (parts.length === 0) return; - - const startLinePos = getStartPositionOfLine(startLine, sourceFile); - - const nonWhitespaceColumnInFirstPart = - SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(startLinePos, parts[0].pos, sourceFile, options); + function indentationIsDifferent(indentationString: string, startLinePosition: number): boolean { + return indentationString !== sourceFile.text.substr(startLinePosition, indentationString.length); + } - let startIndex = 0; - if (firstLineIsIndented) { - startIndex = 1; - startLine++; + function indentMultilineComment(commentRange: TextRange, indentation: number, firstLineIsIndented: boolean, indentFinalLine = true) { + // split comment in lines + let startLine = sourceFile.getLineAndCharacterOfPosition(commentRange.pos).line; + const endLine = sourceFile.getLineAndCharacterOfPosition(commentRange.end).line; + if (startLine === endLine) { + if (!firstLineIsIndented) { + // treat as single line comment + insertIndentation(commentRange.pos, indentation, /*lineAdded*/ false); } + return; + } - // shift all parts on the delta size - const delta = indentation - nonWhitespaceColumnInFirstPart.column; - for (let i = startIndex; i < parts.length; i++ , startLine++) { - const startLinePos = getStartPositionOfLine(startLine, sourceFile); - const nonWhitespaceCharacterAndColumn = - i === 0 - ? nonWhitespaceColumnInFirstPart - : SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(parts[i].pos, parts[i].end, sourceFile, options); - const newIndentation = nonWhitespaceCharacterAndColumn.column + delta; - if (newIndentation > 0) { - const indentationString = getIndentationString(newIndentation, options); - recordReplace(startLinePos, nonWhitespaceCharacterAndColumn.character, indentationString); - } - else { - recordDelete(startLinePos, nonWhitespaceCharacterAndColumn.character); - } - } + const parts: TextRange[] = []; + let startPos = commentRange.pos; + for (let line = startLine; line < endLine; line++) { + const endOfLine = getEndLinePosition(line, sourceFile); + parts.push({ pos: startPos, end: endOfLine }); + startPos = getStartPositionOfLine(line + 1, sourceFile); } - function trimTrailingWhitespacesForLines(line1: number, line2: number, range?: TextRangeWithKind) { - for (let line = line1; line < line2; line++) { - const lineStartPosition = getStartPositionOfLine(line, sourceFile); - const lineEndPosition = getEndLinePosition(line, sourceFile); + if (indentFinalLine) { + parts.push({ pos: startPos, end: commentRange.end }); + } - // do not trim whitespaces in comments or template expression - if (range && (isComment(range.kind) || isStringOrRegularExpressionOrTemplateLiteral(range.kind)) && range.pos <= lineEndPosition && range.end > lineEndPosition) { - continue; - } + if (parts.length === 0) return; - const whitespaceStart = getTrailingWhitespaceStartPosition(lineStartPosition, lineEndPosition); - if (whitespaceStart !== -1) { - Debug.assert(whitespaceStart === lineStartPosition || !isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(whitespaceStart - 1))); - recordDelete(whitespaceStart, lineEndPosition + 1 - whitespaceStart); - } - } + const startLinePos = getStartPositionOfLine(startLine, sourceFile); + + const nonWhitespaceColumnInFirstPart = + SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(startLinePos, parts[0].pos, sourceFile, options); + + let startIndex = 0; + if (firstLineIsIndented) { + startIndex = 1; + startLine++; } - /** - * @param start The position of the first character in range - * @param end The position of the last character in range - */ - function getTrailingWhitespaceStartPosition(start: number, end: number) { - let pos = end; - while (pos >= start && isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(pos))) { - pos--; + // shift all parts on the delta size + const delta = indentation - nonWhitespaceColumnInFirstPart.column; + for (let i = startIndex; i < parts.length; i++ , startLine++) { + const startLinePos = getStartPositionOfLine(startLine, sourceFile); + const nonWhitespaceCharacterAndColumn = + i === 0 + ? nonWhitespaceColumnInFirstPart + : SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(parts[i].pos, parts[i].end, sourceFile, options); + const newIndentation = nonWhitespaceCharacterAndColumn.column + delta; + if (newIndentation > 0) { + const indentationString = getIndentationString(newIndentation, options); + recordReplace(startLinePos, nonWhitespaceCharacterAndColumn.character, indentationString); } - if (pos !== end) { - return pos + 1; + else { + recordDelete(startLinePos, nonWhitespaceCharacterAndColumn.character); } - return -1; } + } - /** - * Trimming will be done for lines after the previous range. - * Exclude comments as they had been previously processed. - */ - function trimTrailingWhitespacesForRemainingRange(trivias: TextRangeWithKind[]) { - let startPos = previousRange ? previousRange.end : originalRange.pos; - for (const trivia of trivias) { - if (isComment(trivia.kind)) { - if (startPos < trivia.pos) { - trimTrailingWitespacesForPositions(startPos, trivia.pos - 1, previousRange); - } + function trimTrailingWhitespacesForLines(line1: number, line2: number, range?: TextRangeWithKind) { + for (let line = line1; line < line2; line++) { + const lineStartPosition = getStartPositionOfLine(line, sourceFile); + const lineEndPosition = getEndLinePosition(line, sourceFile); - startPos = trivia.end + 1; - } + // do not trim whitespaces in comments or template expression + if (range && (isComment(range.kind) || isStringOrRegularExpressionOrTemplateLiteral(range.kind)) && range.pos <= lineEndPosition && range.end > lineEndPosition) { + continue; } - if (startPos < originalRange.end) { - trimTrailingWitespacesForPositions(startPos, originalRange.end, previousRange); + const whitespaceStart = getTrailingWhitespaceStartPosition(lineStartPosition, lineEndPosition); + if (whitespaceStart !== -1) { + Debug.assert(whitespaceStart === lineStartPosition || !isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(whitespaceStart - 1))); + recordDelete(whitespaceStart, lineEndPosition + 1 - whitespaceStart); } } + } - function trimTrailingWitespacesForPositions(startPos: number, endPos: number, previousRange: TextRangeWithKind) { - const startLine = sourceFile.getLineAndCharacterOfPosition(startPos).line; - const endLine = sourceFile.getLineAndCharacterOfPosition(endPos).line; - - trimTrailingWhitespacesForLines(startLine, endLine + 1, previousRange); + /** + * @param start The position of the first character in range + * @param end The position of the last character in range + */ + function getTrailingWhitespaceStartPosition(start: number, end: number) { + let pos = end; + while (pos >= start && isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(pos))) { + pos--; } - - function recordDelete(start: number, len: number) { - if (len) { - edits.push(createTextChangeFromStartLength(start, len, "")); - } + if (pos !== end) { + return pos + 1; } + return -1; + } - function recordReplace(start: number, len: number, newText: string) { - if (len || newText) { - edits.push(createTextChangeFromStartLength(start, len, newText)); + /** + * Trimming will be done for lines after the previous range. + * Exclude comments as they had been previously processed. + */ + function trimTrailingWhitespacesForRemainingRange(trivias: TextRangeWithKind[]) { + let startPos = previousRange ? previousRange.end : originalRange.pos; + for (const trivia of trivias) { + if (isComment(trivia.kind)) { + if (startPos < trivia.pos) { + trimTrailingWitespacesForPositions(startPos, trivia.pos - 1, previousRange); + } + + startPos = trivia.end + 1; } } - function recordInsert(start: number, text: string) { - if (text) { - edits.push(createTextChangeFromStartLength(start, 0, text)); - } + if (startPos < originalRange.end) { + trimTrailingWitespacesForPositions(startPos, originalRange.end, previousRange); } + } - function applyRuleEdits(rule: Rule, - previousRange: TextRangeWithKind, - previousStartLine: number, - currentRange: TextRangeWithKind, - currentStartLine: number - ): LineAction { - const onLaterLine = currentStartLine !== previousStartLine; - switch (rule.action) { - case RuleAction.StopProcessingSpaceActions: - // no action required - return LineAction.None; - case RuleAction.DeleteSpace: - if (previousRange.end !== currentRange.pos) { - // delete characters starting from t1.end up to t2.pos exclusive - recordDelete(previousRange.end, currentRange.pos - previousRange.end); - return onLaterLine ? LineAction.LineRemoved : LineAction.None; - } - break; - case RuleAction.DeleteToken: - recordDelete(previousRange.pos, previousRange.end - previousRange.pos); - break; - case RuleAction.InsertNewLine: - // exit early if we on different lines and rule cannot change number of newlines - // if line1 and line2 are on subsequent lines then no edits are required - ok to exit - // if line1 and line2 are separated with more than one newline - ok to exit since we cannot delete extra new lines - if (rule.flags !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { - return LineAction.None; - } + function trimTrailingWitespacesForPositions(startPos: number, endPos: number, previousRange: TextRangeWithKind) { + const startLine = sourceFile.getLineAndCharacterOfPosition(startPos).line; + const endLine = sourceFile.getLineAndCharacterOfPosition(endPos).line; - // edit should not be applied if we have one line feed between elements - const lineDelta = currentStartLine - previousStartLine; - if (lineDelta !== 1) { - recordReplace(previousRange.end, currentRange.pos - previousRange.end, getNewLineOrDefaultFromHost(host, options)); - return onLaterLine ? LineAction.None : LineAction.LineAdded; - } - break; - case RuleAction.InsertSpace: - // exit early if we on different lines and rule cannot change number of newlines - if (rule.flags !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { - return LineAction.None; - } + trimTrailingWhitespacesForLines(startLine, endLine + 1, previousRange); + } - const posDelta = currentRange.pos - previousRange.end; - if (posDelta !== 1 || sourceFile.text.charCodeAt(previousRange.end) !== CharacterCodes.space) { - recordReplace(previousRange.end, currentRange.pos - previousRange.end, " "); - return onLaterLine ? LineAction.LineRemoved : LineAction.None; - } - break; - case RuleAction.InsertTrailingSemicolon: - recordInsert(previousRange.end, ";"); - } - return LineAction.None; + function recordDelete(start: number, len: number) { + if (len) { + edits.push(createTextChangeFromStartLength(start, len, "")); } } - const enum LineAction { None, LineAdded, LineRemoved } - - /** - * @param precedingToken pass `null` if preceding token was already computed and result was `undefined`. - */ - export function getRangeOfEnclosingComment( - sourceFile: SourceFile, - position: number, - precedingToken?: Node | null, - tokenAtPosition = getTokenAtPosition(sourceFile, position), - ): CommentRange | undefined { - const jsdoc = findAncestor(tokenAtPosition, isJSDoc); - if (jsdoc) tokenAtPosition = jsdoc.parent; - const tokenStart = tokenAtPosition.getStart(sourceFile); - if (tokenStart <= position && position < tokenAtPosition.getEnd()) { - return undefined; + function recordReplace(start: number, len: number, newText: string) { + if (len || newText) { + edits.push(createTextChangeFromStartLength(start, len, newText)); } + } - // eslint-disable-next-line no-null/no-null - precedingToken = precedingToken === null ? undefined : precedingToken === undefined ? findPrecedingToken(position, sourceFile) : precedingToken; - - // Between two consecutive tokens, all comments are either trailing on the former - // or leading on the latter (and none are in both lists). - const trailingRangesOfPreviousToken = precedingToken && getTrailingCommentRanges(sourceFile.text, precedingToken.end); - const leadingCommentRangesOfNextToken = getLeadingCommentRangesOfNode(tokenAtPosition, sourceFile); - const commentRanges = concatenate(trailingRangesOfPreviousToken, leadingCommentRangesOfNextToken); - return commentRanges && find(commentRanges, range => rangeContainsPositionExclusive(range, position) || - // The end marker of a single-line comment does not include the newline character. - // With caret at `^`, in the following case, we are inside a comment (^ denotes the cursor position): - // - // // asdf ^\n - // - // But for closed multi-line comments, we don't want to be inside the comment in the following case: - // - // /* asdf */^ - // - // However, unterminated multi-line comments *do* contain their end. - // - // Internally, we represent the end of the comment at the newline and closing '/', respectively. - // - position === range.end && (range.kind === SyntaxKind.SingleLineCommentTrivia || position === sourceFile.getFullWidth())); + function recordInsert(start: number, text: string) { + if (text) { + edits.push(createTextChangeFromStartLength(start, 0, text)); + } } - function getOpenTokenForList(node: Node, list: readonly Node[]) { - switch (node.kind) { - case SyntaxKind.Constructor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.ArrowFunction: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - if ((node as FunctionDeclaration).typeParameters === list) { - return SyntaxKind.LessThanToken; - } - else if ((node as FunctionDeclaration).parameters === list) { - return SyntaxKind.OpenParenToken; + function applyRuleEdits(rule: Rule, + previousRange: TextRangeWithKind, + previousStartLine: number, + currentRange: TextRangeWithKind, + currentStartLine: number + ): LineAction { + const onLaterLine = currentStartLine !== previousStartLine; + switch (rule.action) { + case RuleAction.StopProcessingSpaceActions: + // no action required + return LineAction.None; + case RuleAction.DeleteSpace: + if (previousRange.end !== currentRange.pos) { + // delete characters starting from t1.end up to t2.pos exclusive + recordDelete(previousRange.end, currentRange.pos - previousRange.end); + return onLaterLine ? LineAction.LineRemoved : LineAction.None; } break; - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - if ((node as CallExpression).typeArguments === list) { - return SyntaxKind.LessThanToken; + case RuleAction.DeleteToken: + recordDelete(previousRange.pos, previousRange.end - previousRange.pos); + break; + case RuleAction.InsertNewLine: + // exit early if we on different lines and rule cannot change number of newlines + // if line1 and line2 are on subsequent lines then no edits are required - ok to exit + // if line1 and line2 are separated with more than one newline - ok to exit since we cannot delete extra new lines + if (rule.flags !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { + return LineAction.None; } - else if ((node as CallExpression).arguments === list) { - return SyntaxKind.OpenParenToken; + + // edit should not be applied if we have one line feed between elements + const lineDelta = currentStartLine - previousStartLine; + if (lineDelta !== 1) { + recordReplace(previousRange.end, currentRange.pos - previousRange.end, getNewLineOrDefaultFromHost(host, options)); + return onLaterLine ? LineAction.None : LineAction.LineAdded; } break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - if ((node as ClassDeclaration).typeParameters === list) { - return SyntaxKind.LessThanToken; + case RuleAction.InsertSpace: + // exit early if we on different lines and rule cannot change number of newlines + if (rule.flags !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { + return LineAction.None; } - break; - case SyntaxKind.TypeReference: - case SyntaxKind.TaggedTemplateExpression: - case SyntaxKind.TypeQuery: - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.ImportType: - if ((node as TypeReferenceNode).typeArguments === list) { - return SyntaxKind.LessThanToken; + + const posDelta = currentRange.pos - previousRange.end; + if (posDelta !== 1 || sourceFile.text.charCodeAt(previousRange.end) !== CharacterCodes.space) { + recordReplace(previousRange.end, currentRange.pos - previousRange.end, " "); + return onLaterLine ? LineAction.LineRemoved : LineAction.None; } break; - case SyntaxKind.TypeLiteral: - return SyntaxKind.OpenBraceToken; + case RuleAction.InsertTrailingSemicolon: + recordInsert(previousRange.end, ";"); } + return LineAction.None; + } +} - return SyntaxKind.Unknown; +const enum LineAction { None, LineAdded, LineRemoved } + +/** + * + * @internal + */ +export function getRangeOfEnclosingComment( + sourceFile: SourceFile, + position: number, + precedingToken?: Node | null, + tokenAtPosition = getTokenAtPosition(sourceFile, position), +): CommentRange | undefined { + const jsdoc = findAncestor(tokenAtPosition, isJSDoc); + if (jsdoc) tokenAtPosition = jsdoc.parent; + const tokenStart = tokenAtPosition.getStart(sourceFile); + if (tokenStart <= position && position < tokenAtPosition.getEnd()) { + return undefined; } - function getCloseTokenForOpenToken(kind: SyntaxKind) { - switch (kind) { - case SyntaxKind.OpenParenToken: - return SyntaxKind.CloseParenToken; - case SyntaxKind.LessThanToken: - return SyntaxKind.GreaterThanToken; - case SyntaxKind.OpenBraceToken: - return SyntaxKind.CloseBraceToken; - } + // eslint-disable-next-line no-null/no-null + precedingToken = precedingToken === null ? undefined : precedingToken === undefined ? findPrecedingToken(position, sourceFile) : precedingToken; + + // Between two consecutive tokens, all comments are either trailing on the former + // or leading on the latter (and none are in both lists). + const trailingRangesOfPreviousToken = precedingToken && getTrailingCommentRanges(sourceFile.text, precedingToken.end); + const leadingCommentRangesOfNextToken = getLeadingCommentRangesOfNode(tokenAtPosition, sourceFile); + const commentRanges = concatenate(trailingRangesOfPreviousToken, leadingCommentRangesOfNextToken); + return commentRanges && find(commentRanges, range => rangeContainsPositionExclusive(range, position) || + // The end marker of a single-line comment does not include the newline character. + // With caret at `^`, in the following case, we are inside a comment (^ denotes the cursor position): + // + // // asdf ^\n + // + // But for closed multi-line comments, we don't want to be inside the comment in the following case: + // + // /* asdf */^ + // + // However, unterminated multi-line comments *do* contain their end. + // + // Internally, we represent the end of the comment at the newline and closing '/', respectively. + // + position === range.end && (range.kind === SyntaxKind.SingleLineCommentTrivia || position === sourceFile.getFullWidth())); +} + +function getOpenTokenForList(node: Node, list: readonly Node[]) { + switch (node.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.ArrowFunction: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + if ((node as FunctionDeclaration).typeParameters === list) { + return SyntaxKind.LessThanToken; + } + else if ((node as FunctionDeclaration).parameters === list) { + return SyntaxKind.OpenParenToken; + } + break; + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + if ((node as CallExpression).typeArguments === list) { + return SyntaxKind.LessThanToken; + } + else if ((node as CallExpression).arguments === list) { + return SyntaxKind.OpenParenToken; + } + break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + if ((node as ClassDeclaration).typeParameters === list) { + return SyntaxKind.LessThanToken; + } + break; + case SyntaxKind.TypeReference: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.TypeQuery: + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.ImportType: + if ((node as TypeReferenceNode).typeArguments === list) { + return SyntaxKind.LessThanToken; + } + break; + case SyntaxKind.TypeLiteral: + return SyntaxKind.OpenBraceToken; + } - return SyntaxKind.Unknown; + return SyntaxKind.Unknown; +} + +function getCloseTokenForOpenToken(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.OpenParenToken: + return SyntaxKind.CloseParenToken; + case SyntaxKind.LessThanToken: + return SyntaxKind.GreaterThanToken; + case SyntaxKind.OpenBraceToken: + return SyntaxKind.CloseBraceToken; } - let internedSizes: { tabSize: number; indentSize: number; }; - let internedTabsIndentation: string[] | undefined; - let internedSpacesIndentation: string[] | undefined; + return SyntaxKind.Unknown; +} - export function getIndentationString(indentation: number, options: EditorSettings): string { - // reset interned strings if FormatCodeOptions were changed - const resetInternedStrings = - !internedSizes || (internedSizes.tabSize !== options.tabSize || internedSizes.indentSize !== options.indentSize); +let internedSizes: { tabSize: number; indentSize: number; }; +let internedTabsIndentation: string[] | undefined; +let internedSpacesIndentation: string[] | undefined; - if (resetInternedStrings) { - internedSizes = { tabSize: options.tabSize!, indentSize: options.indentSize! }; - internedTabsIndentation = internedSpacesIndentation = undefined; - } +/** @internal */ +export function getIndentationString(indentation: number, options: EditorSettings): string { + // reset interned strings if FormatCodeOptions were changed + const resetInternedStrings = + !internedSizes || (internedSizes.tabSize !== options.tabSize || internedSizes.indentSize !== options.indentSize); - if (!options.convertTabsToSpaces) { - const tabs = Math.floor(indentation / options.tabSize!); - const spaces = indentation - tabs * options.tabSize!; + if (resetInternedStrings) { + internedSizes = { tabSize: options.tabSize!, indentSize: options.indentSize! }; + internedTabsIndentation = internedSpacesIndentation = undefined; + } - let tabString: string; - if (!internedTabsIndentation) { - internedTabsIndentation = []; - } + if (!options.convertTabsToSpaces) { + const tabs = Math.floor(indentation / options.tabSize!); + const spaces = indentation - tabs * options.tabSize!; - if (internedTabsIndentation[tabs] === undefined) { - internedTabsIndentation[tabs] = tabString = repeatString("\t", tabs); - } - else { - tabString = internedTabsIndentation[tabs]; - } + let tabString: string; + if (!internedTabsIndentation) { + internedTabsIndentation = []; + } - return spaces ? tabString + repeatString(" ", spaces) : tabString; + if (internedTabsIndentation[tabs] === undefined) { + internedTabsIndentation[tabs] = tabString = repeatString("\t", tabs); } else { - let spacesString: string; - const quotient = Math.floor(indentation / options.indentSize!); - const remainder = indentation % options.indentSize!; - if (!internedSpacesIndentation) { - internedSpacesIndentation = []; - } + tabString = internedTabsIndentation[tabs]; + } - if (internedSpacesIndentation[quotient] === undefined) { - spacesString = repeatString(" ", options.indentSize! * quotient); - internedSpacesIndentation[quotient] = spacesString; - } - else { - spacesString = internedSpacesIndentation[quotient]; - } + return spaces ? tabString + repeatString(" ", spaces) : tabString; + } + else { + let spacesString: string; + const quotient = Math.floor(indentation / options.indentSize!); + const remainder = indentation % options.indentSize!; + if (!internedSpacesIndentation) { + internedSpacesIndentation = []; + } - return remainder ? spacesString + repeatString(" ", remainder) : spacesString; + if (internedSpacesIndentation[quotient] === undefined) { + spacesString = repeatString(" ", options.indentSize! * quotient); + internedSpacesIndentation[quotient] = spacesString; } + else { + spacesString = internedSpacesIndentation[quotient]; + } + + return remainder ? spacesString + repeatString(" ", remainder) : spacesString; } } diff --git a/src/services/formatting/formattingContext.ts b/src/services/formatting/formattingContext.ts index 5701f0cd0ad0e..c42ff32dedacd 100644 --- a/src/services/formatting/formattingContext.ts +++ b/src/services/formatting/formattingContext.ts @@ -1,102 +1,104 @@ -/* @internal */ -namespace ts.formatting { - export const enum FormattingRequestKind { - FormatDocument, - FormatSelection, - FormatOnEnter, - FormatOnSemicolon, - FormatOnOpeningCurlyBrace, - FormatOnClosingCurlyBrace - } - - export class FormattingContext { - public currentTokenSpan!: TextRangeWithKind; - public nextTokenSpan!: TextRangeWithKind; - public contextNode!: Node; - public currentTokenParent!: Node; - public nextTokenParent!: Node; +import { TextRangeWithKind } from "../_namespaces/ts.formatting"; +import { Debug, findChildOfKind, FormatCodeSettings, Node, SourceFileLike, SyntaxKind } from "../_namespaces/ts"; + +/** @internal */ +export const enum FormattingRequestKind { + FormatDocument, + FormatSelection, + FormatOnEnter, + FormatOnSemicolon, + FormatOnOpeningCurlyBrace, + FormatOnClosingCurlyBrace +} - private contextNodeAllOnSameLine: boolean | undefined; - private nextNodeAllOnSameLine: boolean | undefined; - private tokensAreOnSameLine: boolean | undefined; - private contextNodeBlockIsOnOneLine: boolean | undefined; - private nextNodeBlockIsOnOneLine: boolean | undefined; +/** @internal */ +export class FormattingContext { + public currentTokenSpan!: TextRangeWithKind; + public nextTokenSpan!: TextRangeWithKind; + public contextNode!: Node; + public currentTokenParent!: Node; + public nextTokenParent!: Node; + + private contextNodeAllOnSameLine: boolean | undefined; + private nextNodeAllOnSameLine: boolean | undefined; + private tokensAreOnSameLine: boolean | undefined; + private contextNodeBlockIsOnOneLine: boolean | undefined; + private nextNodeBlockIsOnOneLine: boolean | undefined; + + constructor(public readonly sourceFile: SourceFileLike, public formattingRequestKind: FormattingRequestKind, public options: FormatCodeSettings) { + } - constructor(public readonly sourceFile: SourceFileLike, public formattingRequestKind: FormattingRequestKind, public options: FormatCodeSettings) { - } + public updateContext(currentRange: TextRangeWithKind, currentTokenParent: Node, nextRange: TextRangeWithKind, nextTokenParent: Node, commonParent: Node) { + this.currentTokenSpan = Debug.checkDefined(currentRange); + this.currentTokenParent = Debug.checkDefined(currentTokenParent); + this.nextTokenSpan = Debug.checkDefined(nextRange); + this.nextTokenParent = Debug.checkDefined(nextTokenParent); + this.contextNode = Debug.checkDefined(commonParent); + + // drop cached results + this.contextNodeAllOnSameLine = undefined; + this.nextNodeAllOnSameLine = undefined; + this.tokensAreOnSameLine = undefined; + this.contextNodeBlockIsOnOneLine = undefined; + this.nextNodeBlockIsOnOneLine = undefined; + } - public updateContext(currentRange: TextRangeWithKind, currentTokenParent: Node, nextRange: TextRangeWithKind, nextTokenParent: Node, commonParent: Node) { - this.currentTokenSpan = Debug.checkDefined(currentRange); - this.currentTokenParent = Debug.checkDefined(currentTokenParent); - this.nextTokenSpan = Debug.checkDefined(nextRange); - this.nextTokenParent = Debug.checkDefined(nextTokenParent); - this.contextNode = Debug.checkDefined(commonParent); - - // drop cached results - this.contextNodeAllOnSameLine = undefined; - this.nextNodeAllOnSameLine = undefined; - this.tokensAreOnSameLine = undefined; - this.contextNodeBlockIsOnOneLine = undefined; - this.nextNodeBlockIsOnOneLine = undefined; + public ContextNodeAllOnSameLine(): boolean { + if (this.contextNodeAllOnSameLine === undefined) { + this.contextNodeAllOnSameLine = this.NodeIsOnOneLine(this.contextNode); } - public ContextNodeAllOnSameLine(): boolean { - if (this.contextNodeAllOnSameLine === undefined) { - this.contextNodeAllOnSameLine = this.NodeIsOnOneLine(this.contextNode); - } + return this.contextNodeAllOnSameLine; + } - return this.contextNodeAllOnSameLine; + public NextNodeAllOnSameLine(): boolean { + if (this.nextNodeAllOnSameLine === undefined) { + this.nextNodeAllOnSameLine = this.NodeIsOnOneLine(this.nextTokenParent); } - public NextNodeAllOnSameLine(): boolean { - if (this.nextNodeAllOnSameLine === undefined) { - this.nextNodeAllOnSameLine = this.NodeIsOnOneLine(this.nextTokenParent); - } + return this.nextNodeAllOnSameLine; + } - return this.nextNodeAllOnSameLine; + public TokensAreOnSameLine(): boolean { + if (this.tokensAreOnSameLine === undefined) { + const startLine = this.sourceFile.getLineAndCharacterOfPosition(this.currentTokenSpan.pos).line; + const endLine = this.sourceFile.getLineAndCharacterOfPosition(this.nextTokenSpan.pos).line; + this.tokensAreOnSameLine = (startLine === endLine); } - public TokensAreOnSameLine(): boolean { - if (this.tokensAreOnSameLine === undefined) { - const startLine = this.sourceFile.getLineAndCharacterOfPosition(this.currentTokenSpan.pos).line; - const endLine = this.sourceFile.getLineAndCharacterOfPosition(this.nextTokenSpan.pos).line; - this.tokensAreOnSameLine = (startLine === endLine); - } + return this.tokensAreOnSameLine; + } - return this.tokensAreOnSameLine; + public ContextNodeBlockIsOnOneLine() { + if (this.contextNodeBlockIsOnOneLine === undefined) { + this.contextNodeBlockIsOnOneLine = this.BlockIsOnOneLine(this.contextNode); } - public ContextNodeBlockIsOnOneLine() { - if (this.contextNodeBlockIsOnOneLine === undefined) { - this.contextNodeBlockIsOnOneLine = this.BlockIsOnOneLine(this.contextNode); - } + return this.contextNodeBlockIsOnOneLine; + } - return this.contextNodeBlockIsOnOneLine; + public NextNodeBlockIsOnOneLine() { + if (this.nextNodeBlockIsOnOneLine === undefined) { + this.nextNodeBlockIsOnOneLine = this.BlockIsOnOneLine(this.nextTokenParent); } - public NextNodeBlockIsOnOneLine() { - if (this.nextNodeBlockIsOnOneLine === undefined) { - this.nextNodeBlockIsOnOneLine = this.BlockIsOnOneLine(this.nextTokenParent); - } + return this.nextNodeBlockIsOnOneLine; + } - return this.nextNodeBlockIsOnOneLine; - } + private NodeIsOnOneLine(node: Node): boolean { + const startLine = this.sourceFile.getLineAndCharacterOfPosition(node.getStart(this.sourceFile)).line; + const endLine = this.sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line; + return startLine === endLine; + } - private NodeIsOnOneLine(node: Node): boolean { - const startLine = this.sourceFile.getLineAndCharacterOfPosition(node.getStart(this.sourceFile)).line; - const endLine = this.sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line; + private BlockIsOnOneLine(node: Node): boolean { + const openBrace = findChildOfKind(node, SyntaxKind.OpenBraceToken, this.sourceFile); + const closeBrace = findChildOfKind(node, SyntaxKind.CloseBraceToken, this.sourceFile); + if (openBrace && closeBrace) { + const startLine = this.sourceFile.getLineAndCharacterOfPosition(openBrace.getEnd()).line; + const endLine = this.sourceFile.getLineAndCharacterOfPosition(closeBrace.getStart(this.sourceFile)).line; return startLine === endLine; } - - private BlockIsOnOneLine(node: Node): boolean { - const openBrace = findChildOfKind(node, SyntaxKind.OpenBraceToken, this.sourceFile); - const closeBrace = findChildOfKind(node, SyntaxKind.CloseBraceToken, this.sourceFile); - if (openBrace && closeBrace) { - const startLine = this.sourceFile.getLineAndCharacterOfPosition(openBrace.getEnd()).line; - const endLine = this.sourceFile.getLineAndCharacterOfPosition(closeBrace.getStart(this.sourceFile)).line; - return startLine === endLine; - } - return false; - } + return false; } } diff --git a/src/services/formatting/formattingScanner.ts b/src/services/formatting/formattingScanner.ts index 4eeaf1b705804..0941d277d2ac3 100644 --- a/src/services/formatting/formattingScanner.ts +++ b/src/services/formatting/formattingScanner.ts @@ -1,309 +1,316 @@ -/* @internal */ -namespace ts.formatting { - const standardScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); - const jsxScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.JSX); - - export interface FormattingScanner { - advance(): void; - getStartPos(): number; - isOnToken(): boolean; - isOnEOF(): boolean; - readTokenInfo(n: Node): TokenInfo; - readEOFTokenRange(): TextRangeWithKind; - getCurrentLeadingTrivia(): TextRangeWithKind[] | undefined; - lastTrailingTriviaWasNewLine(): boolean; - skipToEndOf(node: Node | NodeArray): void; - skipToStartOf(node: Node): void; - } +import { + append, createScanner, Debug, isJsxAttribute, isJsxElement, isJsxText, isKeyword, isToken, isTrivia, + LanguageVariant, last, Node, NodeArray, ScriptTarget, SyntaxKind, +} from "../_namespaces/ts"; +import { + createTextRangeWithKind, TextRangeWithKind, TextRangeWithTriviaKind, TokenInfo, +} from "../_namespaces/ts.formatting"; + +const standardScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.Standard); +const jsxScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, LanguageVariant.JSX); + +/** @internal */ +export interface FormattingScanner { + advance(): void; + getStartPos(): number; + isOnToken(): boolean; + isOnEOF(): boolean; + readTokenInfo(n: Node): TokenInfo; + readEOFTokenRange(): TextRangeWithKind; + getCurrentLeadingTrivia(): TextRangeWithKind[] | undefined; + lastTrailingTriviaWasNewLine(): boolean; + skipToEndOf(node: Node | NodeArray): void; + skipToStartOf(node: Node): void; +} - const enum ScanAction { - Scan, - RescanGreaterThanToken, - RescanSlashToken, - RescanTemplateToken, - RescanJsxIdentifier, - RescanJsxText, - RescanJsxAttributeValue, - } +const enum ScanAction { + Scan, + RescanGreaterThanToken, + RescanSlashToken, + RescanTemplateToken, + RescanJsxIdentifier, + RescanJsxText, + RescanJsxAttributeValue, +} - export function getFormattingScanner(text: string, languageVariant: LanguageVariant, startPos: number, endPos: number, cb: (scanner: FormattingScanner) => T): T { - const scanner = languageVariant === LanguageVariant.JSX ? jsxScanner : standardScanner; - - scanner.setText(text); - scanner.setTextPos(startPos); - - let wasNewLine = true; - let leadingTrivia: TextRangeWithTriviaKind[] | undefined; - let trailingTrivia: TextRangeWithTriviaKind[] | undefined; - - let savedPos: number; - let lastScanAction: ScanAction | undefined; - let lastTokenInfo: TokenInfo | undefined; - - const res = cb({ - advance, - readTokenInfo, - readEOFTokenRange, - isOnToken, - isOnEOF, - getCurrentLeadingTrivia: () => leadingTrivia, - lastTrailingTriviaWasNewLine: () => wasNewLine, - skipToEndOf, - skipToStartOf, - getStartPos: () => lastTokenInfo?.token.pos ?? scanner.getTokenPos(), - }); +/** @internal */ +export function getFormattingScanner(text: string, languageVariant: LanguageVariant, startPos: number, endPos: number, cb: (scanner: FormattingScanner) => T): T { + const scanner = languageVariant === LanguageVariant.JSX ? jsxScanner : standardScanner; - lastTokenInfo = undefined; - scanner.setText(undefined); + scanner.setText(text); + scanner.setTextPos(startPos); - return res; + let wasNewLine = true; + let leadingTrivia: TextRangeWithTriviaKind[] | undefined; + let trailingTrivia: TextRangeWithTriviaKind[] | undefined; - function advance(): void { - lastTokenInfo = undefined; - const isStarted = scanner.getStartPos() !== startPos; + let savedPos: number; + let lastScanAction: ScanAction | undefined; + let lastTokenInfo: TokenInfo | undefined; - if (isStarted) { - wasNewLine = !!trailingTrivia && last(trailingTrivia).kind === SyntaxKind.NewLineTrivia; - } - else { - scanner.scan(); - } + const res = cb({ + advance, + readTokenInfo, + readEOFTokenRange, + isOnToken, + isOnEOF, + getCurrentLeadingTrivia: () => leadingTrivia, + lastTrailingTriviaWasNewLine: () => wasNewLine, + skipToEndOf, + skipToStartOf, + getStartPos: () => lastTokenInfo?.token.pos ?? scanner.getTokenPos(), + }); - leadingTrivia = undefined; - trailingTrivia = undefined; + lastTokenInfo = undefined; + scanner.setText(undefined); - let pos = scanner.getStartPos(); + return res; - // Read leading trivia and token - while (pos < endPos) { - const t = scanner.getToken(); - if (!isTrivia(t)) { - break; - } + function advance(): void { + lastTokenInfo = undefined; + const isStarted = scanner.getStartPos() !== startPos; - // consume leading trivia - scanner.scan(); - const item: TextRangeWithTriviaKind = { - pos, - end: scanner.getStartPos(), - kind: t - }; + if (isStarted) { + wasNewLine = !!trailingTrivia && last(trailingTrivia).kind === SyntaxKind.NewLineTrivia; + } + else { + scanner.scan(); + } - pos = scanner.getStartPos(); + leadingTrivia = undefined; + trailingTrivia = undefined; - leadingTrivia = append(leadingTrivia, item); + let pos = scanner.getStartPos(); + + // Read leading trivia and token + while (pos < endPos) { + const t = scanner.getToken(); + if (!isTrivia(t)) { + break; } - savedPos = scanner.getStartPos(); - } + // consume leading trivia + scanner.scan(); + const item: TextRangeWithTriviaKind = { + pos, + end: scanner.getStartPos(), + kind: t + }; - function shouldRescanGreaterThanToken(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.GreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanToken: - return true; - } + pos = scanner.getStartPos(); - return false; + leadingTrivia = append(leadingTrivia, item); } - function shouldRescanJsxIdentifier(node: Node): boolean { - if (node.parent) { - switch (node.parent.kind) { - case SyntaxKind.JsxAttribute: - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxClosingElement: - case SyntaxKind.JsxSelfClosingElement: - // May parse an identifier like `module-layout`; that will be scanned as a keyword at first, but we should parse the whole thing to get an identifier. - return isKeyword(node.kind) || node.kind === SyntaxKind.Identifier; - } - } + savedPos = scanner.getStartPos(); + } - return false; + function shouldRescanGreaterThanToken(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.GreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanToken: + return true; } - function shouldRescanJsxText(node: Node): boolean { - return isJsxText(node) || isJsxElement(node) && lastTokenInfo?.token.kind === SyntaxKind.JsxText; - } + return false; + } - function shouldRescanSlashToken(container: Node): boolean { - return container.kind === SyntaxKind.RegularExpressionLiteral; + function shouldRescanJsxIdentifier(node: Node): boolean { + if (node.parent) { + switch (node.parent.kind) { + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxSelfClosingElement: + // May parse an identifier like `module-layout`; that will be scanned as a keyword at first, but we should parse the whole thing to get an identifier. + return isKeyword(node.kind) || node.kind === SyntaxKind.Identifier; + } } - function shouldRescanTemplateToken(container: Node): boolean { - return container.kind === SyntaxKind.TemplateMiddle || - container.kind === SyntaxKind.TemplateTail; - } + return false; + } - function shouldRescanJsxAttributeValue(node: Node): boolean { - return node.parent && isJsxAttribute(node.parent) && node.parent.initializer === node; - } + function shouldRescanJsxText(node: Node): boolean { + return isJsxText(node) || isJsxElement(node) && lastTokenInfo?.token.kind === SyntaxKind.JsxText; + } - function startsWithSlashToken(t: SyntaxKind): boolean { - return t === SyntaxKind.SlashToken || t === SyntaxKind.SlashEqualsToken; + function shouldRescanSlashToken(container: Node): boolean { + return container.kind === SyntaxKind.RegularExpressionLiteral; + } + + function shouldRescanTemplateToken(container: Node): boolean { + return container.kind === SyntaxKind.TemplateMiddle || + container.kind === SyntaxKind.TemplateTail; + } + + function shouldRescanJsxAttributeValue(node: Node): boolean { + return node.parent && isJsxAttribute(node.parent) && node.parent.initializer === node; + } + + function startsWithSlashToken(t: SyntaxKind): boolean { + return t === SyntaxKind.SlashToken || t === SyntaxKind.SlashEqualsToken; + } + + function readTokenInfo(n: Node): TokenInfo { + Debug.assert(isOnToken()); + + // normally scanner returns the smallest available token + // check the kind of context node to determine if scanner should have more greedy behavior and consume more text. + const expectedScanAction = shouldRescanGreaterThanToken(n) ? ScanAction.RescanGreaterThanToken : + shouldRescanSlashToken(n) ? ScanAction.RescanSlashToken : + shouldRescanTemplateToken(n) ? ScanAction.RescanTemplateToken : + shouldRescanJsxIdentifier(n) ? ScanAction.RescanJsxIdentifier : + shouldRescanJsxText(n) ? ScanAction.RescanJsxText : + shouldRescanJsxAttributeValue(n) ? ScanAction.RescanJsxAttributeValue : + ScanAction.Scan; + + if (lastTokenInfo && expectedScanAction === lastScanAction) { + // readTokenInfo was called before with the same expected scan action. + // No need to re-scan text, return existing 'lastTokenInfo' + // it is ok to call fixTokenKind here since it does not affect + // what portion of text is consumed. In contrast rescanning can change it, + // i.e. for '>=' when originally scanner eats just one character + // and rescanning forces it to consume more. + return fixTokenKind(lastTokenInfo, n); } - function readTokenInfo(n: Node): TokenInfo { - Debug.assert(isOnToken()); - - // normally scanner returns the smallest available token - // check the kind of context node to determine if scanner should have more greedy behavior and consume more text. - const expectedScanAction = shouldRescanGreaterThanToken(n) ? ScanAction.RescanGreaterThanToken : - shouldRescanSlashToken(n) ? ScanAction.RescanSlashToken : - shouldRescanTemplateToken(n) ? ScanAction.RescanTemplateToken : - shouldRescanJsxIdentifier(n) ? ScanAction.RescanJsxIdentifier : - shouldRescanJsxText(n) ? ScanAction.RescanJsxText : - shouldRescanJsxAttributeValue(n) ? ScanAction.RescanJsxAttributeValue : - ScanAction.Scan; - - if (lastTokenInfo && expectedScanAction === lastScanAction) { - // readTokenInfo was called before with the same expected scan action. - // No need to re-scan text, return existing 'lastTokenInfo' - // it is ok to call fixTokenKind here since it does not affect - // what portion of text is consumed. In contrast rescanning can change it, - // i.e. for '>=' when originally scanner eats just one character - // and rescanning forces it to consume more. - return fixTokenKind(lastTokenInfo, n); - } + if (scanner.getStartPos() !== savedPos) { + Debug.assert(lastTokenInfo !== undefined); + // readTokenInfo was called before but scan action differs - rescan text + scanner.setTextPos(savedPos); + scanner.scan(); + } - if (scanner.getStartPos() !== savedPos) { - Debug.assert(lastTokenInfo !== undefined); - // readTokenInfo was called before but scan action differs - rescan text - scanner.setTextPos(savedPos); - scanner.scan(); - } + let currentToken = getNextToken(n, expectedScanAction); - let currentToken = getNextToken(n, expectedScanAction); + const token = createTextRangeWithKind( + scanner.getStartPos(), + scanner.getTextPos(), + currentToken, + ); - const token = createTextRangeWithKind( + // consume trailing trivia + if (trailingTrivia) { + trailingTrivia = undefined; + } + while (scanner.getStartPos() < endPos) { + currentToken = scanner.scan(); + if (!isTrivia(currentToken)) { + break; + } + const trivia = createTextRangeWithKind( scanner.getStartPos(), scanner.getTextPos(), currentToken, ); - // consume trailing trivia - if (trailingTrivia) { - trailingTrivia = undefined; + if (!trailingTrivia) { + trailingTrivia = []; } - while (scanner.getStartPos() < endPos) { - currentToken = scanner.scan(); - if (!isTrivia(currentToken)) { - break; - } - const trivia = createTextRangeWithKind( - scanner.getStartPos(), - scanner.getTextPos(), - currentToken, - ); - - if (!trailingTrivia) { - trailingTrivia = []; - } - trailingTrivia.push(trivia); + trailingTrivia.push(trivia); - if (currentToken === SyntaxKind.NewLineTrivia) { - // move past new line - scanner.scan(); - break; - } + if (currentToken === SyntaxKind.NewLineTrivia) { + // move past new line + scanner.scan(); + break; } + } - lastTokenInfo = { leadingTrivia, trailingTrivia, token }; + lastTokenInfo = { leadingTrivia, trailingTrivia, token }; - return fixTokenKind(lastTokenInfo, n); - } + return fixTokenKind(lastTokenInfo, n); + } - function getNextToken(n: Node, expectedScanAction: ScanAction): SyntaxKind { - const token = scanner.getToken(); - lastScanAction = ScanAction.Scan; - switch (expectedScanAction) { - case ScanAction.RescanGreaterThanToken: - if (token === SyntaxKind.GreaterThanToken) { - lastScanAction = ScanAction.RescanGreaterThanToken; - const newToken = scanner.reScanGreaterToken(); - Debug.assert(n.kind === newToken); - return newToken; - } - break; - case ScanAction.RescanSlashToken: - if (startsWithSlashToken(token)) { - lastScanAction = ScanAction.RescanSlashToken; - const newToken = scanner.reScanSlashToken(); - Debug.assert(n.kind === newToken); - return newToken; - } - break; - case ScanAction.RescanTemplateToken: - if (token === SyntaxKind.CloseBraceToken) { - lastScanAction = ScanAction.RescanTemplateToken; - return scanner.reScanTemplateToken(/* isTaggedTemplate */ false); - } - break; - case ScanAction.RescanJsxIdentifier: - lastScanAction = ScanAction.RescanJsxIdentifier; - return scanner.scanJsxIdentifier(); - case ScanAction.RescanJsxText: - lastScanAction = ScanAction.RescanJsxText; - return scanner.reScanJsxToken(/* allowMultilineJsxText */ false); - case ScanAction.RescanJsxAttributeValue: - lastScanAction = ScanAction.RescanJsxAttributeValue; - return scanner.reScanJsxAttributeValue(); - case ScanAction.Scan: - break; - default: - Debug.assertNever(expectedScanAction); - } - return token; + function getNextToken(n: Node, expectedScanAction: ScanAction): SyntaxKind { + const token = scanner.getToken(); + lastScanAction = ScanAction.Scan; + switch (expectedScanAction) { + case ScanAction.RescanGreaterThanToken: + if (token === SyntaxKind.GreaterThanToken) { + lastScanAction = ScanAction.RescanGreaterThanToken; + const newToken = scanner.reScanGreaterToken(); + Debug.assert(n.kind === newToken); + return newToken; + } + break; + case ScanAction.RescanSlashToken: + if (startsWithSlashToken(token)) { + lastScanAction = ScanAction.RescanSlashToken; + const newToken = scanner.reScanSlashToken(); + Debug.assert(n.kind === newToken); + return newToken; + } + break; + case ScanAction.RescanTemplateToken: + if (token === SyntaxKind.CloseBraceToken) { + lastScanAction = ScanAction.RescanTemplateToken; + return scanner.reScanTemplateToken(/* isTaggedTemplate */ false); + } + break; + case ScanAction.RescanJsxIdentifier: + lastScanAction = ScanAction.RescanJsxIdentifier; + return scanner.scanJsxIdentifier(); + case ScanAction.RescanJsxText: + lastScanAction = ScanAction.RescanJsxText; + return scanner.reScanJsxToken(/* allowMultilineJsxText */ false); + case ScanAction.RescanJsxAttributeValue: + lastScanAction = ScanAction.RescanJsxAttributeValue; + return scanner.reScanJsxAttributeValue(); + case ScanAction.Scan: + break; + default: + Debug.assertNever(expectedScanAction); } + return token; + } - function readEOFTokenRange(): TextRangeWithKind { - Debug.assert(isOnEOF()); - return createTextRangeWithKind(scanner.getStartPos(), scanner.getTextPos(), SyntaxKind.EndOfFileToken); - } + function readEOFTokenRange(): TextRangeWithKind { + Debug.assert(isOnEOF()); + return createTextRangeWithKind(scanner.getStartPos(), scanner.getTextPos(), SyntaxKind.EndOfFileToken); + } - function isOnToken(): boolean { - const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); - return current !== SyntaxKind.EndOfFileToken && !isTrivia(current); - } + function isOnToken(): boolean { + const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); + return current !== SyntaxKind.EndOfFileToken && !isTrivia(current); + } - function isOnEOF(): boolean { - const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); - return current === SyntaxKind.EndOfFileToken; - } + function isOnEOF(): boolean { + const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); + return current === SyntaxKind.EndOfFileToken; + } - // when containing node in the tree is token - // but its kind differs from the kind that was returned by the scanner, - // then kind needs to be fixed. This might happen in cases - // when parser interprets token differently, i.e keyword treated as identifier - function fixTokenKind(tokenInfo: TokenInfo, container: Node): TokenInfo { - if (isToken(container) && tokenInfo.token.kind !== container.kind) { - tokenInfo.token.kind = container.kind; - } - return tokenInfo; + // when containing node in the tree is token + // but its kind differs from the kind that was returned by the scanner, + // then kind needs to be fixed. This might happen in cases + // when parser interprets token differently, i.e keyword treated as identifier + function fixTokenKind(tokenInfo: TokenInfo, container: Node): TokenInfo { + if (isToken(container) && tokenInfo.token.kind !== container.kind) { + tokenInfo.token.kind = container.kind; } + return tokenInfo; + } - function skipToEndOf(node: Node | NodeArray): void { - scanner.setTextPos(node.end); - savedPos = scanner.getStartPos(); - lastScanAction = undefined; - lastTokenInfo = undefined; - wasNewLine = false; - leadingTrivia = undefined; - trailingTrivia = undefined; - } + function skipToEndOf(node: Node | NodeArray): void { + scanner.setTextPos(node.end); + savedPos = scanner.getStartPos(); + lastScanAction = undefined; + lastTokenInfo = undefined; + wasNewLine = false; + leadingTrivia = undefined; + trailingTrivia = undefined; + } - function skipToStartOf(node: Node): void { - scanner.setTextPos(node.pos); - savedPos = scanner.getStartPos(); - lastScanAction = undefined; - lastTokenInfo = undefined; - wasNewLine = false; - leadingTrivia = undefined; - trailingTrivia = undefined; - } + function skipToStartOf(node: Node): void { + scanner.setTextPos(node.pos); + savedPos = scanner.getStartPos(); + lastScanAction = undefined; + lastTokenInfo = undefined; + wasNewLine = false; + leadingTrivia = undefined; + trailingTrivia = undefined; } } diff --git a/src/services/formatting/rule.ts b/src/services/formatting/rule.ts index ef98461738891..ed4e96232d5d1 100644 --- a/src/services/formatting/rule.ts +++ b/src/services/formatting/rule.ts @@ -1,37 +1,43 @@ -/* @internal */ -namespace ts.formatting { - export interface Rule { - // Used for debugging to identify each rule based on the property name it's assigned to. - readonly debugName: string; - readonly context: readonly ContextPredicate[]; - readonly action: RuleAction; - readonly flags: RuleFlags; - } +import { FormattingContext } from "../_namespaces/ts.formatting"; +import { emptyArray, SyntaxKind } from "../_namespaces/ts"; - export type ContextPredicate = (context: FormattingContext) => boolean; - export const anyContext: readonly ContextPredicate[] = emptyArray; +/** @internal */ +export interface Rule { + // Used for debugging to identify each rule based on the property name it's assigned to. + readonly debugName: string; + readonly context: readonly ContextPredicate[]; + readonly action: RuleAction; + readonly flags: RuleFlags; +} + +/** @internal */ +export type ContextPredicate = (context: FormattingContext) => boolean; +/** @internal */ +export const anyContext: readonly ContextPredicate[] = emptyArray; - export const enum RuleAction { - StopProcessingSpaceActions = 1 << 0, - StopProcessingTokenActions = 1 << 1, - InsertSpace = 1 << 2, - InsertNewLine = 1 << 3, - DeleteSpace = 1 << 4, - DeleteToken = 1 << 5, - InsertTrailingSemicolon = 1 << 6, +/** @internal */ +export const enum RuleAction { + StopProcessingSpaceActions = 1 << 0, + StopProcessingTokenActions = 1 << 1, + InsertSpace = 1 << 2, + InsertNewLine = 1 << 3, + DeleteSpace = 1 << 4, + DeleteToken = 1 << 5, + InsertTrailingSemicolon = 1 << 6, - StopAction = StopProcessingSpaceActions | StopProcessingTokenActions, - ModifySpaceAction = InsertSpace | InsertNewLine | DeleteSpace, - ModifyTokenAction = DeleteToken | InsertTrailingSemicolon, - } + StopAction = StopProcessingSpaceActions | StopProcessingTokenActions, + ModifySpaceAction = InsertSpace | InsertNewLine | DeleteSpace, + ModifyTokenAction = DeleteToken | InsertTrailingSemicolon, +} - export const enum RuleFlags { - None, - CanDeleteNewLines, - } +/** @internal */ +export const enum RuleFlags { + None, + CanDeleteNewLines, +} - export interface TokenRange { - readonly tokens: readonly SyntaxKind[]; - readonly isSpecific: boolean; - } +/** @internal */ +export interface TokenRange { + readonly tokens: readonly SyntaxKind[]; + readonly isSpecific: boolean; } diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index 07724be25ba0e..df1cfeb76a992 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -1,903 +1,913 @@ -/* @internal */ -namespace ts.formatting { - export interface RuleSpec { - readonly leftTokenRange: TokenRange; - readonly rightTokenRange: TokenRange; - readonly rule: Rule; - } +import { + anyContext, ContextPredicate, FormattingContext, FormattingRequestKind, Rule, RuleAction, RuleFlags, + TextRangeWithKind, TokenRange, +} from "../_namespaces/ts.formatting"; +import { + BinaryExpression, contains, findAncestor, findNextToken, FormatCodeSettings, hasDecorators, hasProperty, isArray, + isExpression, isFunctionLikeKind, isNumericLiteral, isPropertyAccessExpression, isPropertyDeclaration, + isPropertySignature, isTrivia, Node, positionIsASICandidate, SemicolonPreference, SyntaxKind, typeKeywords, + YieldExpression, +} from "../_namespaces/ts"; + +/** @internal */ +export interface RuleSpec { + readonly leftTokenRange: TokenRange; + readonly rightTokenRange: TokenRange; + readonly rule: Rule; +} - export function getAllRules(): RuleSpec[] { - const allTokens: SyntaxKind[] = []; - for (let token = SyntaxKind.FirstToken; token <= SyntaxKind.LastToken; token++) { - if (token !== SyntaxKind.EndOfFileToken) { - allTokens.push(token); - } +/** @internal */ +export function getAllRules(): RuleSpec[] { + const allTokens: SyntaxKind[] = []; + for (let token = SyntaxKind.FirstToken; token <= SyntaxKind.LastToken; token++) { + if (token !== SyntaxKind.EndOfFileToken) { + allTokens.push(token); } - function anyTokenExcept(...tokens: SyntaxKind[]): TokenRange { - return { tokens: allTokens.filter(t => !tokens.some(t2 => t2 === t)), isSpecific: false }; - } - - const anyToken: TokenRange = { tokens: allTokens, isSpecific: false }; - const anyTokenIncludingMultilineComments = tokenRangeFrom([...allTokens, SyntaxKind.MultiLineCommentTrivia]); - const anyTokenIncludingEOF = tokenRangeFrom([...allTokens, SyntaxKind.EndOfFileToken]); - const keywords = tokenRangeFromRange(SyntaxKind.FirstKeyword, SyntaxKind.LastKeyword); - const binaryOperators = tokenRangeFromRange(SyntaxKind.FirstBinaryOperator, SyntaxKind.LastBinaryOperator); - const binaryKeywordOperators = [SyntaxKind.InKeyword, SyntaxKind.InstanceOfKeyword, SyntaxKind.OfKeyword, SyntaxKind.AsKeyword, SyntaxKind.IsKeyword]; - const unaryPrefixOperators = [SyntaxKind.PlusPlusToken, SyntaxKind.MinusMinusToken, SyntaxKind.TildeToken, SyntaxKind.ExclamationToken]; - const unaryPrefixExpressions = [ - SyntaxKind.NumericLiteral, SyntaxKind.BigIntLiteral, SyntaxKind.Identifier, SyntaxKind.OpenParenToken, - SyntaxKind.OpenBracketToken, SyntaxKind.OpenBraceToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; - const unaryPreincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; - const unaryPostincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]; - const unaryPredecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; - const unaryPostdecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]; - const comments = [SyntaxKind.SingleLineCommentTrivia, SyntaxKind.MultiLineCommentTrivia]; - const typeNames = [SyntaxKind.Identifier, ...typeKeywords]; - - // Place a space before open brace in a function declaration - // TypeScript: Function can have return types, which can be made of tons of different token kinds - const functionOpenBraceLeftTokenRange = anyTokenIncludingMultilineComments; - - // Place a space before open brace in a TypeScript declaration that has braces as children (class, module, enum, etc) - const typeScriptOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.Identifier, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.ClassKeyword, SyntaxKind.ExportKeyword, SyntaxKind.ImportKeyword]); - - // Place a space before open brace in a control flow construct - const controlOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.CloseParenToken, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.DoKeyword, SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword, SyntaxKind.ElseKeyword]); - - // These rules are higher in priority than user-configurable - const highPriorityCommonRules = [ - // Leave comments alone - rule("IgnoreBeforeComment", anyToken, comments, anyContext, RuleAction.StopProcessingSpaceActions), - rule("IgnoreAfterLineComment", SyntaxKind.SingleLineCommentTrivia, anyToken, anyContext, RuleAction.StopProcessingSpaceActions), - - rule("NotSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.DeleteSpace), - rule("SpaceAfterColon", SyntaxKind.ColonToken, anyToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.InsertSpace), - rule("NoSpaceBeforeQuestionMark", anyToken, SyntaxKind.QuestionToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.DeleteSpace), - // insert space after '?' only when it is used in conditional operator - rule("SpaceAfterQuestionMarkInConditionalOperator", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext, isConditionalOperatorContext], RuleAction.InsertSpace), - - // in other cases there should be no space between '?' and next token - rule("NoSpaceAfterQuestionMark", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - rule("NoSpaceBeforeDot", anyToken, [SyntaxKind.DotToken, SyntaxKind.QuestionDotToken], [isNonJsxSameLineTokenContext, isNotPropertyAccessOnIntegerLiteral], RuleAction.DeleteSpace), - rule("NoSpaceAfterDot", [SyntaxKind.DotToken, SyntaxKind.QuestionDotToken], anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - rule("NoSpaceBetweenImportParenInImportType", SyntaxKind.ImportKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isImportTypeContext], RuleAction.DeleteSpace), - - // Special handling of unary operators. - // Prefix operators generally shouldn't have a space between - // them and their target unary expression. - rule("NoSpaceAfterUnaryPrefixOperator", unaryPrefixOperators, unaryPrefixExpressions, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterUnaryPreincrementOperator", SyntaxKind.PlusPlusToken, unaryPreincrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterUnaryPredecrementOperator", SyntaxKind.MinusMinusToken, unaryPredecrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeUnaryPostincrementOperator", unaryPostincrementExpressions, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isNotStatementConditionContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeUnaryPostdecrementOperator", unaryPostdecrementExpressions, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isNotStatementConditionContext], RuleAction.DeleteSpace), - - // More unary operator special-casing. - // DevDiv 181814: Be careful when removing leading whitespace - // around unary operators. Examples: - // 1 - -2 --X--> 1--2 - // a + ++b --X--> a+++b - rule("SpaceAfterPostincrementWhenFollowedByAdd", SyntaxKind.PlusPlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterAddWhenFollowedByUnaryPlus", SyntaxKind.PlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterAddWhenFollowedByPreincrement", SyntaxKind.PlusToken, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterPostdecrementWhenFollowedBySubtract", SyntaxKind.MinusMinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterSubtractWhenFollowedByUnaryMinus", SyntaxKind.MinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterSubtractWhenFollowedByPredecrement", SyntaxKind.MinusToken, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - - rule("NoSpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, [SyntaxKind.CommaToken, SyntaxKind.SemicolonToken], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - // For functions and control block place } on a new line [multi-line rule] - rule("NewLineBeforeCloseBraceInBlockContext", anyTokenIncludingMultilineComments, SyntaxKind.CloseBraceToken, [isMultilineBlockContext], RuleAction.InsertNewLine), - - // Space/new line after }. - rule("SpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, anyTokenExcept(SyntaxKind.CloseParenToken), [isNonJsxSameLineTokenContext, isAfterCodeBlockContext], RuleAction.InsertSpace), - // Special case for (}, else) and (}, while) since else & while tokens are not part of the tree which makes SpaceAfterCloseBrace rule not applied - // Also should not apply to }) - rule("SpaceBetweenCloseBraceAndElse", SyntaxKind.CloseBraceToken, SyntaxKind.ElseKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBetweenCloseBraceAndWhile", SyntaxKind.CloseBraceToken, SyntaxKind.WhileKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace), - - // Add a space after control dec context if the next character is an open bracket ex: 'if (false)[a, b] = [1, 2];' -> 'if (false) [a, b] = [1, 2];' - rule("SpaceAfterConditionalClosingParen", SyntaxKind.CloseParenToken, SyntaxKind.OpenBracketToken, [isControlDeclContext], RuleAction.InsertSpace), - - rule("NoSpaceBetweenFunctionKeywordAndStar", SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.DeleteSpace), - rule("SpaceAfterStarInGeneratorDeclaration", SyntaxKind.AsteriskToken, SyntaxKind.Identifier, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.InsertSpace), - - rule("SpaceAfterFunctionInFuncDecl", SyntaxKind.FunctionKeyword, anyToken, [isFunctionDeclContext], RuleAction.InsertSpace), - // Insert new line after { and before } in multi-line contexts. - rule("NewLineAfterOpenBraceInBlockContext", SyntaxKind.OpenBraceToken, anyToken, [isMultilineBlockContext], RuleAction.InsertNewLine), - - // For get/set members, we check for (identifier,identifier) since get/set don't have tokens and they are represented as just an identifier token. - // Though, we do extra check on the context to make sure we are dealing with get/set node. Example: - // get x() {} - // set x(val) {} - rule("SpaceAfterGetSetInMember", [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword], SyntaxKind.Identifier, [isFunctionDeclContext], RuleAction.InsertSpace), - - rule("NoSpaceBetweenYieldKeywordAndStar", SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.DeleteSpace), - rule("SpaceBetweenYieldOrYieldStarAndOperand", [SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken], anyToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.InsertSpace), - - rule("NoSpaceBetweenReturnAndSemicolon", SyntaxKind.ReturnKeyword, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("SpaceAfterCertainKeywords", [SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword, SyntaxKind.AwaitKeyword], anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceAfterLetConstInVariableDeclaration", [SyntaxKind.LetKeyword, SyntaxKind.ConstKeyword], anyToken, [isNonJsxSameLineTokenContext, isStartOfVariableDeclarationList], RuleAction.InsertSpace), - rule("NoSpaceBeforeOpenParenInFuncCall", anyToken, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isFunctionCallOrNewContext, isPreviousTokenNotComma], RuleAction.DeleteSpace), - - // Special case for binary operators (that are keywords). For these we have to add a space and shouldn't follow any user options. - rule("SpaceBeforeBinaryKeywordOperator", anyToken, binaryKeywordOperators, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterBinaryKeywordOperator", binaryKeywordOperators, anyToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - - rule("SpaceAfterVoidOperator", SyntaxKind.VoidKeyword, anyToken, [isNonJsxSameLineTokenContext, isVoidOpContext], RuleAction.InsertSpace), - - // Async-await - rule("SpaceBetweenAsyncAndOpenParen", SyntaxKind.AsyncKeyword, SyntaxKind.OpenParenToken, [isArrowFunctionContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBetweenAsyncAndFunctionKeyword", SyntaxKind.AsyncKeyword, [SyntaxKind.FunctionKeyword, SyntaxKind.Identifier], [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - - // Template string - rule("NoSpaceBetweenTagAndTemplateString", [SyntaxKind.Identifier, SyntaxKind.CloseParenToken], [SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // JSX opening elements - rule("SpaceBeforeJsxAttribute", anyToken, SyntaxKind.Identifier, [isNextTokenParentJsxAttribute, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBeforeSlashInJsxOpeningElement", anyToken, SyntaxKind.SlashToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceBeforeGreaterThanTokenInJsxOpeningElement", SyntaxKind.SlashToken, SyntaxKind.GreaterThanToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeEqualInJsxAttribute", anyToken, SyntaxKind.EqualsToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterEqualInJsxAttribute", SyntaxKind.EqualsToken, anyToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // TypeScript-specific rules - // Use of module as a function call. e.g.: import m2 = module("m2"); - rule("NoSpaceAfterModuleImport", [SyntaxKind.ModuleKeyword, SyntaxKind.RequireKeyword], SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - // Add a space around certain TypeScript keywords - rule( - "SpaceAfterCertainTypeScriptKeywords", - [ - SyntaxKind.AbstractKeyword, - SyntaxKind.AccessorKeyword, - SyntaxKind.ClassKeyword, - SyntaxKind.DeclareKeyword, - SyntaxKind.DefaultKeyword, - SyntaxKind.EnumKeyword, - SyntaxKind.ExportKeyword, - SyntaxKind.ExtendsKeyword, - SyntaxKind.GetKeyword, - SyntaxKind.ImplementsKeyword, - SyntaxKind.ImportKeyword, - SyntaxKind.InterfaceKeyword, - SyntaxKind.ModuleKeyword, - SyntaxKind.NamespaceKeyword, - SyntaxKind.PrivateKeyword, - SyntaxKind.PublicKeyword, - SyntaxKind.ProtectedKeyword, - SyntaxKind.ReadonlyKeyword, - SyntaxKind.SetKeyword, - SyntaxKind.StaticKeyword, - SyntaxKind.TypeKeyword, - SyntaxKind.FromKeyword, - SyntaxKind.KeyOfKeyword, - SyntaxKind.InferKeyword, - ], - anyToken, - [isNonJsxSameLineTokenContext], - RuleAction.InsertSpace), - rule( - "SpaceBeforeCertainTypeScriptKeywords", - anyToken, - [SyntaxKind.ExtendsKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.FromKeyword], - [isNonJsxSameLineTokenContext], - RuleAction.InsertSpace), - // Treat string literals in module names as identifiers, and add a space between the literal and the opening Brace braces, e.g.: module "m2" { - rule("SpaceAfterModuleName", SyntaxKind.StringLiteral, SyntaxKind.OpenBraceToken, [isModuleDeclContext], RuleAction.InsertSpace), - - // Lambda expressions - rule("SpaceBeforeArrow", anyToken, SyntaxKind.EqualsGreaterThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceAfterArrow", SyntaxKind.EqualsGreaterThanToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - - // Optional parameters and let args - rule("NoSpaceAfterEllipsis", SyntaxKind.DotDotDotToken, SyntaxKind.Identifier, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterOptionalParameters", SyntaxKind.QuestionToken, [SyntaxKind.CloseParenToken, SyntaxKind.CommaToken], [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace), - - // Remove spaces in empty interface literals. e.g.: x: {} - rule("NoSpaceBetweenEmptyInterfaceBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectTypeContext], RuleAction.DeleteSpace), - - // generics and type assertions - rule("NoSpaceBeforeOpenAngularBracket", typeNames, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), - rule("NoSpaceBetweenCloseParenAndAngularBracket", SyntaxKind.CloseParenToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterOpenAngularBracket", SyntaxKind.LessThanToken, anyToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeCloseAngularBracket", anyToken, SyntaxKind.GreaterThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterCloseAngularBracket", - SyntaxKind.GreaterThanToken, - [SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, SyntaxKind.GreaterThanToken, SyntaxKind.CommaToken], - [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext, isNotFunctionDeclContext /*To prevent an interference with the SpaceBeforeOpenParenInFuncDecl rule*/], - RuleAction.DeleteSpace), - - // decorators - rule("SpaceBeforeAt", [SyntaxKind.CloseParenToken, SyntaxKind.Identifier], SyntaxKind.AtToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceAfterAt", SyntaxKind.AtToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - // Insert space after @ in decorator - rule("SpaceAfterDecorator", - anyToken, - [ - SyntaxKind.AbstractKeyword, - SyntaxKind.Identifier, - SyntaxKind.ExportKeyword, - SyntaxKind.DefaultKeyword, - SyntaxKind.ClassKeyword, - SyntaxKind.StaticKeyword, - SyntaxKind.PublicKeyword, - SyntaxKind.PrivateKeyword, - SyntaxKind.ProtectedKeyword, - SyntaxKind.GetKeyword, - SyntaxKind.SetKeyword, - SyntaxKind.OpenBracketToken, - SyntaxKind.AsteriskToken, - ], - [isEndOfDecoratorContextOnSameLine], - RuleAction.InsertSpace), - - rule("NoSpaceBeforeNonNullAssertionOperator", anyToken, SyntaxKind.ExclamationToken, [isNonJsxSameLineTokenContext, isNonNullAssertionContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterNewKeywordOnConstructorSignature", SyntaxKind.NewKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isConstructorSignatureContext], RuleAction.DeleteSpace), - rule("SpaceLessThanAndNonJSXTypeAnnotation", SyntaxKind.LessThanToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - ]; - - // These rules are applied after high priority - const userConfigurableRules = [ - // Treat constructor as an identifier in a function declaration, and remove spaces between constructor and following left parentheses - rule("SpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - rule("SpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionEnabled("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNextTokenNotCloseBracket, isNextTokenNotCloseParen], RuleAction.InsertSpace), - rule("NoSpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext], RuleAction.DeleteSpace), - - // Insert space after function keyword for anonymous functions - rule("SpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.InsertSpace), - rule("NoSpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.DeleteSpace), - - // Insert space after keywords in control flow statements - rule("SpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.InsertSpace), - rule("NoSpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.DeleteSpace), - - // Insert space after opening and before closing nonempty parenthesis - rule("SpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBetweenOpenParens", SyntaxKind.OpenParenToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceBetweenParens", SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // Insert space after opening and before closing nonempty brackets - rule("SpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("SpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceBetweenBrackets", SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}. - rule("SpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace), - rule("SpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace), - rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // Insert a space after opening and before closing empty brace brackets - rule("SpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingEmptyBraces")], RuleAction.InsertSpace), - rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingEmptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // Insert space after opening and before closing template string braces - rule("SpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxTextContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), - rule("SpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - rule("NoSpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxTextContext], RuleAction.DeleteSpace, RuleFlags.CanDeleteNewLines), - rule("NoSpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // No space after { and before } in JSX expression - rule("SpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace), - rule("SpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace), - rule("NoSpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace), - - // Insert space after semicolon in for statement - rule("SpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionEnabled("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.InsertSpace), - rule("NoSpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.DeleteSpace), - - // Insert space before and after binary operators - rule("SpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("NoSpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace), - - rule("SpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.InsertSpace), - rule("NoSpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.DeleteSpace), - - // Open Brace braces after control block - rule("NewLineBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), - - // Open Brace braces after function - // TypeScript: Function can have return types, which can be made of tons of different token kinds - rule("NewLineBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), - // Open Brace braces after TypeScript module/class/interface - rule("NewLineBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), - - rule("SpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionEnabled("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.InsertSpace), - rule("NoSpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.DeleteSpace), - - rule("SpaceBeforeTypeAnnotation", anyToken, [SyntaxKind.QuestionToken, SyntaxKind.ColonToken], [isOptionEnabled("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.InsertSpace), - rule("NoSpaceBeforeTypeAnnotation", anyToken, [SyntaxKind.QuestionToken, SyntaxKind.ColonToken], [isOptionDisabledOrUndefined("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.DeleteSpace), - - rule("NoOptionalSemicolon", SyntaxKind.SemicolonToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Remove), isSemicolonDeletionContext], RuleAction.DeleteToken), - rule("OptionalSemicolon", anyToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Insert), isSemicolonInsertionContext], RuleAction.InsertTrailingSemicolon), - ]; - - // These rules are lower in priority than user-configurable. Rules earlier in this list have priority over rules later in the list. - const lowPriorityCommonRules = [ - // Space after keyword but not before ; or : or ? - rule("NoSpaceBeforeSemicolon", anyToken, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - rule("SpaceBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), - rule("SpaceBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), - rule("SpaceBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), - - rule("NoSpaceBeforeComma", anyToken, SyntaxKind.CommaToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - - // No space before and after indexer `x[]` - rule("NoSpaceBeforeOpenBracket", anyTokenExcept(SyntaxKind.AsyncKeyword, SyntaxKind.CaseKeyword), SyntaxKind.OpenBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceAfterCloseBracket", SyntaxKind.CloseBracketToken, anyToken, [isNonJsxSameLineTokenContext, isNotBeforeBlockInFunctionDeclarationContext], RuleAction.DeleteSpace), - rule("SpaceAfterSemicolon", SyntaxKind.SemicolonToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - - // Remove extra space between for and await - rule("SpaceBetweenForAndAwaitKeyword", SyntaxKind.ForKeyword, SyntaxKind.AwaitKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - - // Add a space between statements. All keywords except (do,else,case) has open/close parens after them. - // So, we have a rule to add a space for [),Any], [do,Any], [else,Any], and [case,Any] - rule( - "SpaceBetweenStatements", - [SyntaxKind.CloseParenToken, SyntaxKind.DoKeyword, SyntaxKind.ElseKeyword, SyntaxKind.CaseKeyword], - anyToken, - [isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNotForContext], - RuleAction.InsertSpace), - // This low-pri rule takes care of "try {", "catch {" and "finally {" in case the rule SpaceBeforeOpenBraceInControl didn't execute on FormatOnEnter. - rule("SpaceAfterTryCatchFinally", [SyntaxKind.TryKeyword, SyntaxKind.CatchKeyword, SyntaxKind.FinallyKeyword], SyntaxKind.OpenBraceToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), - ]; - - return [ - ...highPriorityCommonRules, - ...userConfigurableRules, - ...lowPriorityCommonRules, - ]; } + function anyTokenExcept(...tokens: SyntaxKind[]): TokenRange { + return { tokens: allTokens.filter(t => !tokens.some(t2 => t2 === t)), isSpecific: false }; + } + + const anyToken: TokenRange = { tokens: allTokens, isSpecific: false }; + const anyTokenIncludingMultilineComments = tokenRangeFrom([...allTokens, SyntaxKind.MultiLineCommentTrivia]); + const anyTokenIncludingEOF = tokenRangeFrom([...allTokens, SyntaxKind.EndOfFileToken]); + const keywords = tokenRangeFromRange(SyntaxKind.FirstKeyword, SyntaxKind.LastKeyword); + const binaryOperators = tokenRangeFromRange(SyntaxKind.FirstBinaryOperator, SyntaxKind.LastBinaryOperator); + const binaryKeywordOperators = [SyntaxKind.InKeyword, SyntaxKind.InstanceOfKeyword, SyntaxKind.OfKeyword, SyntaxKind.AsKeyword, SyntaxKind.IsKeyword]; + const unaryPrefixOperators = [SyntaxKind.PlusPlusToken, SyntaxKind.MinusMinusToken, SyntaxKind.TildeToken, SyntaxKind.ExclamationToken]; + const unaryPrefixExpressions = [ + SyntaxKind.NumericLiteral, SyntaxKind.BigIntLiteral, SyntaxKind.Identifier, SyntaxKind.OpenParenToken, + SyntaxKind.OpenBracketToken, SyntaxKind.OpenBraceToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; + const unaryPreincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; + const unaryPostincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]; + const unaryPredecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; + const unaryPostdecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]; + const comments = [SyntaxKind.SingleLineCommentTrivia, SyntaxKind.MultiLineCommentTrivia]; + const typeNames = [SyntaxKind.Identifier, ...typeKeywords]; + + // Place a space before open brace in a function declaration + // TypeScript: Function can have return types, which can be made of tons of different token kinds + const functionOpenBraceLeftTokenRange = anyTokenIncludingMultilineComments; + + // Place a space before open brace in a TypeScript declaration that has braces as children (class, module, enum, etc) + const typeScriptOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.Identifier, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.ClassKeyword, SyntaxKind.ExportKeyword, SyntaxKind.ImportKeyword]); + + // Place a space before open brace in a control flow construct + const controlOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.CloseParenToken, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.DoKeyword, SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword, SyntaxKind.ElseKeyword]); + + // These rules are higher in priority than user-configurable + const highPriorityCommonRules = [ + // Leave comments alone + rule("IgnoreBeforeComment", anyToken, comments, anyContext, RuleAction.StopProcessingSpaceActions), + rule("IgnoreAfterLineComment", SyntaxKind.SingleLineCommentTrivia, anyToken, anyContext, RuleAction.StopProcessingSpaceActions), + + rule("NotSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.DeleteSpace), + rule("SpaceAfterColon", SyntaxKind.ColonToken, anyToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeQuestionMark", anyToken, SyntaxKind.QuestionToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.DeleteSpace), + // insert space after '?' only when it is used in conditional operator + rule("SpaceAfterQuestionMarkInConditionalOperator", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext, isConditionalOperatorContext], RuleAction.InsertSpace), + + // in other cases there should be no space between '?' and next token + rule("NoSpaceAfterQuestionMark", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + rule("NoSpaceBeforeDot", anyToken, [SyntaxKind.DotToken, SyntaxKind.QuestionDotToken], [isNonJsxSameLineTokenContext, isNotPropertyAccessOnIntegerLiteral], RuleAction.DeleteSpace), + rule("NoSpaceAfterDot", [SyntaxKind.DotToken, SyntaxKind.QuestionDotToken], anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + rule("NoSpaceBetweenImportParenInImportType", SyntaxKind.ImportKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isImportTypeContext], RuleAction.DeleteSpace), + + // Special handling of unary operators. + // Prefix operators generally shouldn't have a space between + // them and their target unary expression. + rule("NoSpaceAfterUnaryPrefixOperator", unaryPrefixOperators, unaryPrefixExpressions, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterUnaryPreincrementOperator", SyntaxKind.PlusPlusToken, unaryPreincrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterUnaryPredecrementOperator", SyntaxKind.MinusMinusToken, unaryPredecrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeUnaryPostincrementOperator", unaryPostincrementExpressions, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isNotStatementConditionContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeUnaryPostdecrementOperator", unaryPostdecrementExpressions, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isNotStatementConditionContext], RuleAction.DeleteSpace), + + // More unary operator special-casing. + // DevDiv 181814: Be careful when removing leading whitespace + // around unary operators. Examples: + // 1 - -2 --X--> 1--2 + // a + ++b --X--> a+++b + rule("SpaceAfterPostincrementWhenFollowedByAdd", SyntaxKind.PlusPlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterAddWhenFollowedByUnaryPlus", SyntaxKind.PlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterAddWhenFollowedByPreincrement", SyntaxKind.PlusToken, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterPostdecrementWhenFollowedBySubtract", SyntaxKind.MinusMinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterSubtractWhenFollowedByUnaryMinus", SyntaxKind.MinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterSubtractWhenFollowedByPredecrement", SyntaxKind.MinusToken, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + + rule("NoSpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, [SyntaxKind.CommaToken, SyntaxKind.SemicolonToken], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + // For functions and control block place } on a new line [multi-line rule] + rule("NewLineBeforeCloseBraceInBlockContext", anyTokenIncludingMultilineComments, SyntaxKind.CloseBraceToken, [isMultilineBlockContext], RuleAction.InsertNewLine), + + // Space/new line after }. + rule("SpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, anyTokenExcept(SyntaxKind.CloseParenToken), [isNonJsxSameLineTokenContext, isAfterCodeBlockContext], RuleAction.InsertSpace), + // Special case for (}, else) and (}, while) since else & while tokens are not part of the tree which makes SpaceAfterCloseBrace rule not applied + // Also should not apply to }) + rule("SpaceBetweenCloseBraceAndElse", SyntaxKind.CloseBraceToken, SyntaxKind.ElseKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBetweenCloseBraceAndWhile", SyntaxKind.CloseBraceToken, SyntaxKind.WhileKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace), + + // Add a space after control dec context if the next character is an open bracket ex: 'if (false)[a, b] = [1, 2];' -> 'if (false) [a, b] = [1, 2];' + rule("SpaceAfterConditionalClosingParen", SyntaxKind.CloseParenToken, SyntaxKind.OpenBracketToken, [isControlDeclContext], RuleAction.InsertSpace), + + rule("NoSpaceBetweenFunctionKeywordAndStar", SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.DeleteSpace), + rule("SpaceAfterStarInGeneratorDeclaration", SyntaxKind.AsteriskToken, SyntaxKind.Identifier, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.InsertSpace), + + rule("SpaceAfterFunctionInFuncDecl", SyntaxKind.FunctionKeyword, anyToken, [isFunctionDeclContext], RuleAction.InsertSpace), + // Insert new line after { and before } in multi-line contexts. + rule("NewLineAfterOpenBraceInBlockContext", SyntaxKind.OpenBraceToken, anyToken, [isMultilineBlockContext], RuleAction.InsertNewLine), + + // For get/set members, we check for (identifier,identifier) since get/set don't have tokens and they are represented as just an identifier token. + // Though, we do extra check on the context to make sure we are dealing with get/set node. Example: + // get x() {} + // set x(val) {} + rule("SpaceAfterGetSetInMember", [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword], SyntaxKind.Identifier, [isFunctionDeclContext], RuleAction.InsertSpace), + + rule("NoSpaceBetweenYieldKeywordAndStar", SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.DeleteSpace), + rule("SpaceBetweenYieldOrYieldStarAndOperand", [SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken], anyToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.InsertSpace), + + rule("NoSpaceBetweenReturnAndSemicolon", SyntaxKind.ReturnKeyword, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("SpaceAfterCertainKeywords", [SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword, SyntaxKind.AwaitKeyword], anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceAfterLetConstInVariableDeclaration", [SyntaxKind.LetKeyword, SyntaxKind.ConstKeyword], anyToken, [isNonJsxSameLineTokenContext, isStartOfVariableDeclarationList], RuleAction.InsertSpace), + rule("NoSpaceBeforeOpenParenInFuncCall", anyToken, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isFunctionCallOrNewContext, isPreviousTokenNotComma], RuleAction.DeleteSpace), + + // Special case for binary operators (that are keywords). For these we have to add a space and shouldn't follow any user options. + rule("SpaceBeforeBinaryKeywordOperator", anyToken, binaryKeywordOperators, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterBinaryKeywordOperator", binaryKeywordOperators, anyToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + + rule("SpaceAfterVoidOperator", SyntaxKind.VoidKeyword, anyToken, [isNonJsxSameLineTokenContext, isVoidOpContext], RuleAction.InsertSpace), + + // Async-await + rule("SpaceBetweenAsyncAndOpenParen", SyntaxKind.AsyncKeyword, SyntaxKind.OpenParenToken, [isArrowFunctionContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBetweenAsyncAndFunctionKeyword", SyntaxKind.AsyncKeyword, [SyntaxKind.FunctionKeyword, SyntaxKind.Identifier], [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + + // Template string + rule("NoSpaceBetweenTagAndTemplateString", [SyntaxKind.Identifier, SyntaxKind.CloseParenToken], [SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // JSX opening elements + rule("SpaceBeforeJsxAttribute", anyToken, SyntaxKind.Identifier, [isNextTokenParentJsxAttribute, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBeforeSlashInJsxOpeningElement", anyToken, SyntaxKind.SlashToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeGreaterThanTokenInJsxOpeningElement", SyntaxKind.SlashToken, SyntaxKind.GreaterThanToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeEqualInJsxAttribute", anyToken, SyntaxKind.EqualsToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterEqualInJsxAttribute", SyntaxKind.EqualsToken, anyToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // TypeScript-specific rules + // Use of module as a function call. e.g.: import m2 = module("m2"); + rule("NoSpaceAfterModuleImport", [SyntaxKind.ModuleKeyword, SyntaxKind.RequireKeyword], SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + // Add a space around certain TypeScript keywords + rule( + "SpaceAfterCertainTypeScriptKeywords", + [ + SyntaxKind.AbstractKeyword, + SyntaxKind.AccessorKeyword, + SyntaxKind.ClassKeyword, + SyntaxKind.DeclareKeyword, + SyntaxKind.DefaultKeyword, + SyntaxKind.EnumKeyword, + SyntaxKind.ExportKeyword, + SyntaxKind.ExtendsKeyword, + SyntaxKind.GetKeyword, + SyntaxKind.ImplementsKeyword, + SyntaxKind.ImportKeyword, + SyntaxKind.InterfaceKeyword, + SyntaxKind.ModuleKeyword, + SyntaxKind.NamespaceKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.ReadonlyKeyword, + SyntaxKind.SetKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.TypeKeyword, + SyntaxKind.FromKeyword, + SyntaxKind.KeyOfKeyword, + SyntaxKind.InferKeyword, + ], + anyToken, + [isNonJsxSameLineTokenContext], + RuleAction.InsertSpace), + rule( + "SpaceBeforeCertainTypeScriptKeywords", + anyToken, + [SyntaxKind.ExtendsKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.FromKeyword], + [isNonJsxSameLineTokenContext], + RuleAction.InsertSpace), + // Treat string literals in module names as identifiers, and add a space between the literal and the opening Brace braces, e.g.: module "m2" { + rule("SpaceAfterModuleName", SyntaxKind.StringLiteral, SyntaxKind.OpenBraceToken, [isModuleDeclContext], RuleAction.InsertSpace), + + // Lambda expressions + rule("SpaceBeforeArrow", anyToken, SyntaxKind.EqualsGreaterThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceAfterArrow", SyntaxKind.EqualsGreaterThanToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + + // Optional parameters and let args + rule("NoSpaceAfterEllipsis", SyntaxKind.DotDotDotToken, SyntaxKind.Identifier, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOptionalParameters", SyntaxKind.QuestionToken, [SyntaxKind.CloseParenToken, SyntaxKind.CommaToken], [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace), + + // Remove spaces in empty interface literals. e.g.: x: {} + rule("NoSpaceBetweenEmptyInterfaceBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectTypeContext], RuleAction.DeleteSpace), + + // generics and type assertions + rule("NoSpaceBeforeOpenAngularBracket", typeNames, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceBetweenCloseParenAndAngularBracket", SyntaxKind.CloseParenToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenAngularBracket", SyntaxKind.LessThanToken, anyToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseAngularBracket", anyToken, SyntaxKind.GreaterThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterCloseAngularBracket", + SyntaxKind.GreaterThanToken, + [SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, SyntaxKind.GreaterThanToken, SyntaxKind.CommaToken], + [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext, isNotFunctionDeclContext /*To prevent an interference with the SpaceBeforeOpenParenInFuncDecl rule*/], + RuleAction.DeleteSpace), + + // decorators + rule("SpaceBeforeAt", [SyntaxKind.CloseParenToken, SyntaxKind.Identifier], SyntaxKind.AtToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceAfterAt", SyntaxKind.AtToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + // Insert space after @ in decorator + rule("SpaceAfterDecorator", + anyToken, + [ + SyntaxKind.AbstractKeyword, + SyntaxKind.Identifier, + SyntaxKind.ExportKeyword, + SyntaxKind.DefaultKeyword, + SyntaxKind.ClassKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.GetKeyword, + SyntaxKind.SetKeyword, + SyntaxKind.OpenBracketToken, + SyntaxKind.AsteriskToken, + ], + [isEndOfDecoratorContextOnSameLine], + RuleAction.InsertSpace), + + rule("NoSpaceBeforeNonNullAssertionOperator", anyToken, SyntaxKind.ExclamationToken, [isNonJsxSameLineTokenContext, isNonNullAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterNewKeywordOnConstructorSignature", SyntaxKind.NewKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isConstructorSignatureContext], RuleAction.DeleteSpace), + rule("SpaceLessThanAndNonJSXTypeAnnotation", SyntaxKind.LessThanToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + ]; + + // These rules are applied after high priority + const userConfigurableRules = [ + // Treat constructor as an identifier in a function declaration, and remove spaces between constructor and following left parentheses + rule("SpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + rule("SpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionEnabled("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNextTokenNotCloseBracket, isNextTokenNotCloseParen], RuleAction.InsertSpace), + rule("NoSpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext], RuleAction.DeleteSpace), + + // Insert space after function keyword for anonymous functions + rule("SpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.InsertSpace), + rule("NoSpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.DeleteSpace), + + // Insert space after keywords in control flow statements + rule("SpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.InsertSpace), + rule("NoSpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.DeleteSpace), + + // Insert space after opening and before closing nonempty parenthesis + rule("SpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBetweenOpenParens", SyntaxKind.OpenParenToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenParens", SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // Insert space after opening and before closing nonempty brackets + rule("SpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenBrackets", SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}. + rule("SpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // Insert a space after opening and before closing empty brace brackets + rule("SpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingEmptyBraces")], RuleAction.InsertSpace), + rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingEmptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // Insert space after opening and before closing template string braces + rule("SpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxTextContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), + rule("SpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxTextContext], RuleAction.DeleteSpace, RuleFlags.CanDeleteNewLines), + rule("NoSpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // No space after { and before } in JSX expression + rule("SpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace), + rule("NoSpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace), + + // Insert space after semicolon in for statement + rule("SpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionEnabled("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.InsertSpace), + rule("NoSpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.DeleteSpace), + + // Insert space before and after binary operators + rule("SpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace), + + rule("SpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.DeleteSpace), + + // Open Brace braces after control block + rule("NewLineBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), + + // Open Brace braces after function + // TypeScript: Function can have return types, which can be made of tons of different token kinds + rule("NewLineBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), + // Open Brace braces after TypeScript module/class/interface + rule("NewLineBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), + + rule("SpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionEnabled("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.InsertSpace), + rule("NoSpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.DeleteSpace), + + rule("SpaceBeforeTypeAnnotation", anyToken, [SyntaxKind.QuestionToken, SyntaxKind.ColonToken], [isOptionEnabled("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeTypeAnnotation", anyToken, [SyntaxKind.QuestionToken, SyntaxKind.ColonToken], [isOptionDisabledOrUndefined("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.DeleteSpace), + + rule("NoOptionalSemicolon", SyntaxKind.SemicolonToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Remove), isSemicolonDeletionContext], RuleAction.DeleteToken), + rule("OptionalSemicolon", anyToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Insert), isSemicolonInsertionContext], RuleAction.InsertTrailingSemicolon), + ]; + + // These rules are lower in priority than user-configurable. Rules earlier in this list have priority over rules later in the list. + const lowPriorityCommonRules = [ + // Space after keyword but not before ; or : or ? + rule("NoSpaceBeforeSemicolon", anyToken, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + rule("SpaceBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), + rule("SpaceBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), + rule("SpaceBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), + + rule("NoSpaceBeforeComma", anyToken, SyntaxKind.CommaToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + + // No space before and after indexer `x[]` + rule("NoSpaceBeforeOpenBracket", anyTokenExcept(SyntaxKind.AsyncKeyword, SyntaxKind.CaseKeyword), SyntaxKind.OpenBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterCloseBracket", SyntaxKind.CloseBracketToken, anyToken, [isNonJsxSameLineTokenContext, isNotBeforeBlockInFunctionDeclarationContext], RuleAction.DeleteSpace), + rule("SpaceAfterSemicolon", SyntaxKind.SemicolonToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + + // Remove extra space between for and await + rule("SpaceBetweenForAndAwaitKeyword", SyntaxKind.ForKeyword, SyntaxKind.AwaitKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + + // Add a space between statements. All keywords except (do,else,case) has open/close parens after them. + // So, we have a rule to add a space for [),Any], [do,Any], [else,Any], and [case,Any] + rule( + "SpaceBetweenStatements", + [SyntaxKind.CloseParenToken, SyntaxKind.DoKeyword, SyntaxKind.ElseKeyword, SyntaxKind.CaseKeyword], + anyToken, + [isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNotForContext], + RuleAction.InsertSpace), + // This low-pri rule takes care of "try {", "catch {" and "finally {" in case the rule SpaceBeforeOpenBraceInControl didn't execute on FormatOnEnter. + rule("SpaceAfterTryCatchFinally", [SyntaxKind.TryKeyword, SyntaxKind.CatchKeyword, SyntaxKind.FinallyKeyword], SyntaxKind.OpenBraceToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + ]; + + return [ + ...highPriorityCommonRules, + ...userConfigurableRules, + ...lowPriorityCommonRules, + ]; +} - /** - * A rule takes a two tokens (left/right) and a particular context - * for which you're meant to look at them. You then declare what should the - * whitespace annotation be between these tokens via the action param. - * - * @param debugName Name to print - * @param left The left side of the comparison - * @param right The right side of the comparison - * @param context A set of filters to narrow down the space in which this formatter rule applies - * @param action a declaration of the expected whitespace - * @param flags whether the rule deletes a line or not, defaults to no-op - */ - function rule( - debugName: string, - left: SyntaxKind | readonly SyntaxKind[] | TokenRange, - right: SyntaxKind | readonly SyntaxKind[] | TokenRange, - context: readonly ContextPredicate[], - action: RuleAction, - flags: RuleFlags = RuleFlags.None, - ): RuleSpec { - return { leftTokenRange: toTokenRange(left), rightTokenRange: toTokenRange(right), rule: { debugName, context, action, flags } }; - } +/** + * A rule takes a two tokens (left/right) and a particular context + * for which you're meant to look at them. You then declare what should the + * whitespace annotation be between these tokens via the action param. + * + * @param debugName Name to print + * @param left The left side of the comparison + * @param right The right side of the comparison + * @param context A set of filters to narrow down the space in which this formatter rule applies + * @param action a declaration of the expected whitespace + * @param flags whether the rule deletes a line or not, defaults to no-op + */ +function rule( + debugName: string, + left: SyntaxKind | readonly SyntaxKind[] | TokenRange, + right: SyntaxKind | readonly SyntaxKind[] | TokenRange, + context: readonly ContextPredicate[], + action: RuleAction, + flags: RuleFlags = RuleFlags.None, +): RuleSpec { + return { leftTokenRange: toTokenRange(left), rightTokenRange: toTokenRange(right), rule: { debugName, context, action, flags } }; +} - function tokenRangeFrom(tokens: readonly SyntaxKind[]): TokenRange { - return { tokens, isSpecific: true }; - } +function tokenRangeFrom(tokens: readonly SyntaxKind[]): TokenRange { + return { tokens, isSpecific: true }; +} - function toTokenRange(arg: SyntaxKind | readonly SyntaxKind[] | TokenRange): TokenRange { - return typeof arg === "number" ? tokenRangeFrom([arg]) : isArray(arg) ? tokenRangeFrom(arg) : arg; - } +function toTokenRange(arg: SyntaxKind | readonly SyntaxKind[] | TokenRange): TokenRange { + return typeof arg === "number" ? tokenRangeFrom([arg]) : isArray(arg) ? tokenRangeFrom(arg) : arg; +} - function tokenRangeFromRange(from: SyntaxKind, to: SyntaxKind, except: readonly SyntaxKind[] = []): TokenRange { - const tokens: SyntaxKind[] = []; - for (let token = from; token <= to; token++) { - if (!contains(except, token)) { - tokens.push(token); - } +function tokenRangeFromRange(from: SyntaxKind, to: SyntaxKind, except: readonly SyntaxKind[] = []): TokenRange { + const tokens: SyntaxKind[] = []; + for (let token = from; token <= to; token++) { + if (!contains(except, token)) { + tokens.push(token); } - return tokenRangeFrom(tokens); } + return tokenRangeFrom(tokens); +} - /// - /// Contexts - /// +/// +/// Contexts +/// - function optionEquals(optionName: K, optionValue: FormatCodeSettings[K]): (context: FormattingContext) => boolean { - return (context) => context.options && context.options[optionName] === optionValue; - } +function optionEquals(optionName: K, optionValue: FormatCodeSettings[K]): (context: FormattingContext) => boolean { + return (context) => context.options && context.options[optionName] === optionValue; +} - function isOptionEnabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => context.options && hasProperty(context.options, optionName) && !!context.options[optionName]; - } +function isOptionEnabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => context.options && hasProperty(context.options, optionName) && !!context.options[optionName]; +} - function isOptionDisabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => context.options && hasProperty(context.options, optionName) && !context.options[optionName]; - } +function isOptionDisabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => context.options && hasProperty(context.options, optionName) && !context.options[optionName]; +} - function isOptionDisabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => !context.options || !hasProperty(context.options, optionName) || !context.options[optionName]; - } +function isOptionDisabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => !context.options || !hasProperty(context.options, optionName) || !context.options[optionName]; +} - function isOptionDisabledOrUndefinedOrTokensOnSameLine(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => !context.options || !hasProperty(context.options, optionName) || !context.options[optionName] || context.TokensAreOnSameLine(); - } +function isOptionDisabledOrUndefinedOrTokensOnSameLine(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => !context.options || !hasProperty(context.options, optionName) || !context.options[optionName] || context.TokensAreOnSameLine(); +} - function isOptionEnabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => !context.options || !hasProperty(context.options, optionName) || !!context.options[optionName]; - } +function isOptionEnabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => !context.options || !hasProperty(context.options, optionName) || !!context.options[optionName]; +} - function isForContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ForStatement; - } +function isForContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ForStatement; +} - function isNotForContext(context: FormattingContext): boolean { - return !isForContext(context); - } +function isNotForContext(context: FormattingContext): boolean { + return !isForContext(context); +} - function isBinaryOpContext(context: FormattingContext): boolean { - switch (context.contextNode.kind) { - case SyntaxKind.BinaryExpression: - return (context.contextNode as BinaryExpression).operatorToken.kind !== SyntaxKind.CommaToken; - case SyntaxKind.ConditionalExpression: - case SyntaxKind.ConditionalType: - case SyntaxKind.AsExpression: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.TypePredicate: - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - case SyntaxKind.SatisfiesExpression: - return true; +function isBinaryOpContext(context: FormattingContext): boolean { + switch (context.contextNode.kind) { + case SyntaxKind.BinaryExpression: + return (context.contextNode as BinaryExpression).operatorToken.kind !== SyntaxKind.CommaToken; + case SyntaxKind.ConditionalExpression: + case SyntaxKind.ConditionalType: + case SyntaxKind.AsExpression: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.TypePredicate: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.SatisfiesExpression: + return true; - // equals in binding elements: function foo([[x, y] = [1, 2]]) - case SyntaxKind.BindingElement: - // equals in type X = ... - // falls through - case SyntaxKind.TypeAliasDeclaration: - // equal in import a = module('a'); - // falls through - case SyntaxKind.ImportEqualsDeclaration: - // equal in export = 1 - // falls through - case SyntaxKind.ExportAssignment: - // equal in let a = 0 - // falls through - case SyntaxKind.VariableDeclaration: - // equal in p = 0 - // falls through - case SyntaxKind.Parameter: - case SyntaxKind.EnumMember: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken; - // "in" keyword in for (let x in []) { } - case SyntaxKind.ForInStatement: - // "in" keyword in [P in keyof T]: T[P] - // falls through - case SyntaxKind.TypeParameter: - return context.currentTokenSpan.kind === SyntaxKind.InKeyword || context.nextTokenSpan.kind === SyntaxKind.InKeyword || context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken; - // Technically, "of" is not a binary operator, but format it the same way as "in" - case SyntaxKind.ForOfStatement: - return context.currentTokenSpan.kind === SyntaxKind.OfKeyword || context.nextTokenSpan.kind === SyntaxKind.OfKeyword; - } - return false; - } + // equals in binding elements: function foo([[x, y] = [1, 2]]) + case SyntaxKind.BindingElement: + // equals in type X = ... + // falls through + case SyntaxKind.TypeAliasDeclaration: + // equal in import a = module('a'); + // falls through + case SyntaxKind.ImportEqualsDeclaration: + // equal in export = 1 + // falls through + case SyntaxKind.ExportAssignment: + // equal in let a = 0 + // falls through + case SyntaxKind.VariableDeclaration: + // equal in p = 0 + // falls through + case SyntaxKind.Parameter: + case SyntaxKind.EnumMember: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken; + // "in" keyword in for (let x in []) { } + case SyntaxKind.ForInStatement: + // "in" keyword in [P in keyof T]: T[P] + // falls through + case SyntaxKind.TypeParameter: + return context.currentTokenSpan.kind === SyntaxKind.InKeyword || context.nextTokenSpan.kind === SyntaxKind.InKeyword || context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken; + // Technically, "of" is not a binary operator, but format it the same way as "in" + case SyntaxKind.ForOfStatement: + return context.currentTokenSpan.kind === SyntaxKind.OfKeyword || context.nextTokenSpan.kind === SyntaxKind.OfKeyword; + } + return false; +} - function isNotBinaryOpContext(context: FormattingContext): boolean { - return !isBinaryOpContext(context); - } +function isNotBinaryOpContext(context: FormattingContext): boolean { + return !isBinaryOpContext(context); +} - function isNotTypeAnnotationContext(context: FormattingContext): boolean { - return !isTypeAnnotationContext(context); - } +function isNotTypeAnnotationContext(context: FormattingContext): boolean { + return !isTypeAnnotationContext(context); +} - function isTypeAnnotationContext(context: FormattingContext): boolean { - const contextKind = context.contextNode.kind; - return contextKind === SyntaxKind.PropertyDeclaration || - contextKind === SyntaxKind.PropertySignature || - contextKind === SyntaxKind.Parameter || - contextKind === SyntaxKind.VariableDeclaration || - isFunctionLikeKind(contextKind); - } +function isTypeAnnotationContext(context: FormattingContext): boolean { + const contextKind = context.contextNode.kind; + return contextKind === SyntaxKind.PropertyDeclaration || + contextKind === SyntaxKind.PropertySignature || + contextKind === SyntaxKind.Parameter || + contextKind === SyntaxKind.VariableDeclaration || + isFunctionLikeKind(contextKind); +} - function isConditionalOperatorContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ConditionalExpression || - context.contextNode.kind === SyntaxKind.ConditionalType; - } +function isConditionalOperatorContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ConditionalExpression || + context.contextNode.kind === SyntaxKind.ConditionalType; +} - function isSameLineTokenOrBeforeBlockContext(context: FormattingContext): boolean { - return context.TokensAreOnSameLine() || isBeforeBlockContext(context); - } +function isSameLineTokenOrBeforeBlockContext(context: FormattingContext): boolean { + return context.TokensAreOnSameLine() || isBeforeBlockContext(context); +} - function isBraceWrappedContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ObjectBindingPattern || - context.contextNode.kind === SyntaxKind.MappedType || - isSingleLineBlockContext(context); - } +function isBraceWrappedContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ObjectBindingPattern || + context.contextNode.kind === SyntaxKind.MappedType || + isSingleLineBlockContext(context); +} - // This check is done before an open brace in a control construct, a function, or a typescript block declaration - function isBeforeMultilineBlockContext(context: FormattingContext): boolean { - return isBeforeBlockContext(context) && !(context.NextNodeAllOnSameLine() || context.NextNodeBlockIsOnOneLine()); - } +// This check is done before an open brace in a control construct, a function, or a typescript block declaration +function isBeforeMultilineBlockContext(context: FormattingContext): boolean { + return isBeforeBlockContext(context) && !(context.NextNodeAllOnSameLine() || context.NextNodeBlockIsOnOneLine()); +} - function isMultilineBlockContext(context: FormattingContext): boolean { - return isBlockContext(context) && !(context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); - } +function isMultilineBlockContext(context: FormattingContext): boolean { + return isBlockContext(context) && !(context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); +} - function isSingleLineBlockContext(context: FormattingContext): boolean { - return isBlockContext(context) && (context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); - } +function isSingleLineBlockContext(context: FormattingContext): boolean { + return isBlockContext(context) && (context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); +} - function isBlockContext(context: FormattingContext): boolean { - return nodeIsBlockContext(context.contextNode); - } +function isBlockContext(context: FormattingContext): boolean { + return nodeIsBlockContext(context.contextNode); +} - function isBeforeBlockContext(context: FormattingContext): boolean { - return nodeIsBlockContext(context.nextTokenParent); +function isBeforeBlockContext(context: FormattingContext): boolean { + return nodeIsBlockContext(context.nextTokenParent); +} + +// IMPORTANT!!! This method must return true ONLY for nodes with open and close braces as immediate children +function nodeIsBlockContext(node: Node): boolean { + if (nodeIsTypeScriptDeclWithBlockContext(node)) { + // This means we are in a context that looks like a block to the user, but in the grammar is actually not a node (it's a class, module, enum, object type literal, etc). + return true; } - // IMPORTANT!!! This method must return true ONLY for nodes with open and close braces as immediate children - function nodeIsBlockContext(node: Node): boolean { - if (nodeIsTypeScriptDeclWithBlockContext(node)) { - // This means we are in a context that looks like a block to the user, but in the grammar is actually not a node (it's a class, module, enum, object type literal, etc). + switch (node.kind) { + case SyntaxKind.Block: + case SyntaxKind.CaseBlock: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ModuleBlock: return true; - } - - switch (node.kind) { - case SyntaxKind.Block: - case SyntaxKind.CaseBlock: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ModuleBlock: - return true; - } - - return false; } - function isFunctionDeclContext(context: FormattingContext): boolean { - switch (context.contextNode.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - // case SyntaxKind.MemberFunctionDeclaration: - // falls through - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // case SyntaxKind.MethodSignature: - // falls through - case SyntaxKind.CallSignature: - case SyntaxKind.FunctionExpression: - case SyntaxKind.Constructor: - case SyntaxKind.ArrowFunction: - // case SyntaxKind.ConstructorDeclaration: - // case SyntaxKind.SimpleArrowFunctionExpression: - // case SyntaxKind.ParenthesizedArrowFunctionExpression: - // falls through - case SyntaxKind.InterfaceDeclaration: // This one is not truly a function, but for formatting purposes, it acts just like one - return true; - } + return false; +} - return false; +function isFunctionDeclContext(context: FormattingContext): boolean { + switch (context.contextNode.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + // case SyntaxKind.MemberFunctionDeclaration: + // falls through + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // case SyntaxKind.MethodSignature: + // falls through + case SyntaxKind.CallSignature: + case SyntaxKind.FunctionExpression: + case SyntaxKind.Constructor: + case SyntaxKind.ArrowFunction: + // case SyntaxKind.ConstructorDeclaration: + // case SyntaxKind.SimpleArrowFunctionExpression: + // case SyntaxKind.ParenthesizedArrowFunctionExpression: + // falls through + case SyntaxKind.InterfaceDeclaration: // This one is not truly a function, but for formatting purposes, it acts just like one + return true; } - function isNotFunctionDeclContext(context: FormattingContext): boolean { - return !isFunctionDeclContext(context); - } + return false; +} - function isFunctionDeclarationOrFunctionExpressionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.FunctionDeclaration || context.contextNode.kind === SyntaxKind.FunctionExpression; - } +function isNotFunctionDeclContext(context: FormattingContext): boolean { + return !isFunctionDeclContext(context); +} - function isTypeScriptDeclWithBlockContext(context: FormattingContext): boolean { - return nodeIsTypeScriptDeclWithBlockContext(context.contextNode); - } +function isFunctionDeclarationOrFunctionExpressionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.FunctionDeclaration || context.contextNode.kind === SyntaxKind.FunctionExpression; +} - function nodeIsTypeScriptDeclWithBlockContext(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeLiteral: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.NamedExports: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.NamedImports: - return true; - } +function isTypeScriptDeclWithBlockContext(context: FormattingContext): boolean { + return nodeIsTypeScriptDeclWithBlockContext(context.contextNode); +} - return false; +function nodeIsTypeScriptDeclWithBlockContext(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeLiteral: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.NamedExports: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.NamedImports: + return true; } - function isAfterCodeBlockContext(context: FormattingContext): boolean { - switch (context.currentTokenParent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.CatchClause: - case SyntaxKind.ModuleBlock: - case SyntaxKind.SwitchStatement: - return true; - case SyntaxKind.Block: { - const blockParent = context.currentTokenParent.parent; - // In a codefix scenario, we can't rely on parents being set. So just always return true. - if (!blockParent || blockParent.kind !== SyntaxKind.ArrowFunction && blockParent.kind !== SyntaxKind.FunctionExpression) { - return true; - } - } - } - return false; - } + return false; +} - function isControlDeclContext(context: FormattingContext): boolean { - switch (context.contextNode.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.SwitchStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.TryStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WithStatement: - // TODO - // case SyntaxKind.ElseClause: - // falls through - case SyntaxKind.CatchClause: +function isAfterCodeBlockContext(context: FormattingContext): boolean { + switch (context.currentTokenParent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.CatchClause: + case SyntaxKind.ModuleBlock: + case SyntaxKind.SwitchStatement: + return true; + case SyntaxKind.Block: { + const blockParent = context.currentTokenParent.parent; + // In a codefix scenario, we can't rely on parents being set. So just always return true. + if (!blockParent || blockParent.kind !== SyntaxKind.ArrowFunction && blockParent.kind !== SyntaxKind.FunctionExpression) { return true; - - default: - return false; + } } } + return false; +} - function isObjectContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ObjectLiteralExpression; - } - - function isFunctionCallContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.CallExpression; - } +function isControlDeclContext(context: FormattingContext): boolean { + switch (context.contextNode.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WithStatement: + // TODO + // case SyntaxKind.ElseClause: + // falls through + case SyntaxKind.CatchClause: + return true; - function isNewContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.NewExpression; + default: + return false; } +} - function isFunctionCallOrNewContext(context: FormattingContext): boolean { - return isFunctionCallContext(context) || isNewContext(context); - } +function isObjectContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ObjectLiteralExpression; +} - function isPreviousTokenNotComma(context: FormattingContext): boolean { - return context.currentTokenSpan.kind !== SyntaxKind.CommaToken; - } +function isFunctionCallContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.CallExpression; +} - function isNextTokenNotCloseBracket(context: FormattingContext): boolean { - return context.nextTokenSpan.kind !== SyntaxKind.CloseBracketToken; - } +function isNewContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.NewExpression; +} - function isNextTokenNotCloseParen(context: FormattingContext): boolean { - return context.nextTokenSpan.kind !== SyntaxKind.CloseParenToken; - } +function isFunctionCallOrNewContext(context: FormattingContext): boolean { + return isFunctionCallContext(context) || isNewContext(context); +} - function isArrowFunctionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ArrowFunction; - } +function isPreviousTokenNotComma(context: FormattingContext): boolean { + return context.currentTokenSpan.kind !== SyntaxKind.CommaToken; +} - function isImportTypeContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ImportType; - } +function isNextTokenNotCloseBracket(context: FormattingContext): boolean { + return context.nextTokenSpan.kind !== SyntaxKind.CloseBracketToken; +} - function isNonJsxSameLineTokenContext(context: FormattingContext): boolean { - return context.TokensAreOnSameLine() && context.contextNode.kind !== SyntaxKind.JsxText; - } +function isNextTokenNotCloseParen(context: FormattingContext): boolean { + return context.nextTokenSpan.kind !== SyntaxKind.CloseParenToken; +} - function isNonJsxTextContext(context: FormattingContext): boolean { - return context.contextNode.kind !== SyntaxKind.JsxText; - } +function isArrowFunctionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ArrowFunction; +} - function isNonJsxElementOrFragmentContext(context: FormattingContext): boolean { - return context.contextNode.kind !== SyntaxKind.JsxElement && context.contextNode.kind !== SyntaxKind.JsxFragment; - } +function isImportTypeContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ImportType; +} - function isJsxExpressionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.JsxExpression || context.contextNode.kind === SyntaxKind.JsxSpreadAttribute; - } +function isNonJsxSameLineTokenContext(context: FormattingContext): boolean { + return context.TokensAreOnSameLine() && context.contextNode.kind !== SyntaxKind.JsxText; +} - function isNextTokenParentJsxAttribute(context: FormattingContext): boolean { - return context.nextTokenParent.kind === SyntaxKind.JsxAttribute; - } +function isNonJsxTextContext(context: FormattingContext): boolean { + return context.contextNode.kind !== SyntaxKind.JsxText; +} - function isJsxAttributeContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.JsxAttribute; - } +function isNonJsxElementOrFragmentContext(context: FormattingContext): boolean { + return context.contextNode.kind !== SyntaxKind.JsxElement && context.contextNode.kind !== SyntaxKind.JsxFragment; +} - function isJsxSelfClosingElementContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.JsxSelfClosingElement; - } +function isJsxExpressionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.JsxExpression || context.contextNode.kind === SyntaxKind.JsxSpreadAttribute; +} - function isNotBeforeBlockInFunctionDeclarationContext(context: FormattingContext): boolean { - return !isFunctionDeclContext(context) && !isBeforeBlockContext(context); - } +function isNextTokenParentJsxAttribute(context: FormattingContext): boolean { + return context.nextTokenParent.kind === SyntaxKind.JsxAttribute; +} - function isEndOfDecoratorContextOnSameLine(context: FormattingContext): boolean { - return context.TokensAreOnSameLine() && - hasDecorators(context.contextNode) && - nodeIsInDecoratorContext(context.currentTokenParent) && - !nodeIsInDecoratorContext(context.nextTokenParent); - } +function isJsxAttributeContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.JsxAttribute; +} - function nodeIsInDecoratorContext(node: Node): boolean { - while (node && isExpression(node)) { - node = node.parent; - } - return node && node.kind === SyntaxKind.Decorator; - } +function isJsxSelfClosingElementContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.JsxSelfClosingElement; +} - function isStartOfVariableDeclarationList(context: FormattingContext): boolean { - return context.currentTokenParent.kind === SyntaxKind.VariableDeclarationList && - context.currentTokenParent.getStart(context.sourceFile) === context.currentTokenSpan.pos; - } +function isNotBeforeBlockInFunctionDeclarationContext(context: FormattingContext): boolean { + return !isFunctionDeclContext(context) && !isBeforeBlockContext(context); +} - function isNotFormatOnEnter(context: FormattingContext): boolean { - return context.formattingRequestKind !== FormattingRequestKind.FormatOnEnter; - } +function isEndOfDecoratorContextOnSameLine(context: FormattingContext): boolean { + return context.TokensAreOnSameLine() && + hasDecorators(context.contextNode) && + nodeIsInDecoratorContext(context.currentTokenParent) && + !nodeIsInDecoratorContext(context.nextTokenParent); +} - function isModuleDeclContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ModuleDeclaration; +function nodeIsInDecoratorContext(node: Node): boolean { + while (node && isExpression(node)) { + node = node.parent; } + return node && node.kind === SyntaxKind.Decorator; +} - function isObjectTypeContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.TypeLiteral; // && context.contextNode.parent.kind !== SyntaxKind.InterfaceDeclaration; - } +function isStartOfVariableDeclarationList(context: FormattingContext): boolean { + return context.currentTokenParent.kind === SyntaxKind.VariableDeclarationList && + context.currentTokenParent.getStart(context.sourceFile) === context.currentTokenSpan.pos; +} - function isConstructorSignatureContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ConstructSignature; - } +function isNotFormatOnEnter(context: FormattingContext): boolean { + return context.formattingRequestKind !== FormattingRequestKind.FormatOnEnter; +} - function isTypeArgumentOrParameterOrAssertion(token: TextRangeWithKind, parent: Node): boolean { - if (token.kind !== SyntaxKind.LessThanToken && token.kind !== SyntaxKind.GreaterThanToken) { - return false; - } - switch (parent.kind) { - case SyntaxKind.TypeReference: - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.ExpressionWithTypeArguments: - return true; - default: - return false; +function isModuleDeclContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ModuleDeclaration; +} - } - } +function isObjectTypeContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.TypeLiteral; // && context.contextNode.parent.kind !== SyntaxKind.InterfaceDeclaration; +} - function isTypeArgumentOrParameterOrAssertionContext(context: FormattingContext): boolean { - return isTypeArgumentOrParameterOrAssertion(context.currentTokenSpan, context.currentTokenParent) || - isTypeArgumentOrParameterOrAssertion(context.nextTokenSpan, context.nextTokenParent); - } +function isConstructorSignatureContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ConstructSignature; +} - function isTypeAssertionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.TypeAssertionExpression; +function isTypeArgumentOrParameterOrAssertion(token: TextRangeWithKind, parent: Node): boolean { + if (token.kind !== SyntaxKind.LessThanToken && token.kind !== SyntaxKind.GreaterThanToken) { + return false; } + switch (parent.kind) { + case SyntaxKind.TypeReference: + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.ExpressionWithTypeArguments: + return true; + default: + return false; - function isVoidOpContext(context: FormattingContext): boolean { - return context.currentTokenSpan.kind === SyntaxKind.VoidKeyword && context.currentTokenParent.kind === SyntaxKind.VoidExpression; } +} - function isYieldOrYieldStarWithOperand(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.YieldExpression && (context.contextNode as YieldExpression).expression !== undefined; - } +function isTypeArgumentOrParameterOrAssertionContext(context: FormattingContext): boolean { + return isTypeArgumentOrParameterOrAssertion(context.currentTokenSpan, context.currentTokenParent) || + isTypeArgumentOrParameterOrAssertion(context.nextTokenSpan, context.nextTokenParent); +} - function isNonNullAssertionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.NonNullExpression; - } +function isTypeAssertionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.TypeAssertionExpression; +} - function isNotStatementConditionContext(context: FormattingContext): boolean { - return !isStatementConditionContext(context); - } +function isVoidOpContext(context: FormattingContext): boolean { + return context.currentTokenSpan.kind === SyntaxKind.VoidKeyword && context.currentTokenParent.kind === SyntaxKind.VoidExpression; +} - function isStatementConditionContext(context: FormattingContext): boolean { - switch (context.contextNode.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - return true; +function isYieldOrYieldStarWithOperand(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.YieldExpression && (context.contextNode as YieldExpression).expression !== undefined; +} - default: - return false; - } - } +function isNonNullAssertionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.NonNullExpression; +} - function isSemicolonDeletionContext(context: FormattingContext): boolean { - let nextTokenKind = context.nextTokenSpan.kind; - let nextTokenStart = context.nextTokenSpan.pos; - if (isTrivia(nextTokenKind)) { - const nextRealToken = context.nextTokenParent === context.currentTokenParent - ? findNextToken( - context.currentTokenParent, - findAncestor(context.currentTokenParent, a => !a.parent)!, - context.sourceFile) - : context.nextTokenParent.getFirstToken(context.sourceFile); - if (!nextRealToken) { - return true; - } - nextTokenKind = nextRealToken.kind; - nextTokenStart = nextRealToken.getStart(context.sourceFile); - } +function isNotStatementConditionContext(context: FormattingContext): boolean { + return !isStatementConditionContext(context); +} - const startLine = context.sourceFile.getLineAndCharacterOfPosition(context.currentTokenSpan.pos).line; - const endLine = context.sourceFile.getLineAndCharacterOfPosition(nextTokenStart).line; - if (startLine === endLine) { - return nextTokenKind === SyntaxKind.CloseBraceToken - || nextTokenKind === SyntaxKind.EndOfFileToken; - } +function isStatementConditionContext(context: FormattingContext): boolean { + switch (context.contextNode.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + return true; - if (nextTokenKind === SyntaxKind.SemicolonClassElement || - nextTokenKind === SyntaxKind.SemicolonToken - ) { + default: return false; - } - - if (context.contextNode.kind === SyntaxKind.InterfaceDeclaration || - context.contextNode.kind === SyntaxKind.TypeAliasDeclaration - ) { - // Can’t remove semicolon after `foo`; it would parse as a method declaration: - // - // interface I { - // foo; - // (): void - // } - return !isPropertySignature(context.currentTokenParent) - || !!context.currentTokenParent.type - || nextTokenKind !== SyntaxKind.OpenParenToken; - } + } +} - if (isPropertyDeclaration(context.currentTokenParent)) { - return !context.currentTokenParent.initializer; +function isSemicolonDeletionContext(context: FormattingContext): boolean { + let nextTokenKind = context.nextTokenSpan.kind; + let nextTokenStart = context.nextTokenSpan.pos; + if (isTrivia(nextTokenKind)) { + const nextRealToken = context.nextTokenParent === context.currentTokenParent + ? findNextToken( + context.currentTokenParent, + findAncestor(context.currentTokenParent, a => !a.parent)!, + context.sourceFile) + : context.nextTokenParent.getFirstToken(context.sourceFile); + if (!nextRealToken) { + return true; } - - return context.currentTokenParent.kind !== SyntaxKind.ForStatement - && context.currentTokenParent.kind !== SyntaxKind.EmptyStatement - && context.currentTokenParent.kind !== SyntaxKind.SemicolonClassElement - && nextTokenKind !== SyntaxKind.OpenBracketToken - && nextTokenKind !== SyntaxKind.OpenParenToken - && nextTokenKind !== SyntaxKind.PlusToken - && nextTokenKind !== SyntaxKind.MinusToken - && nextTokenKind !== SyntaxKind.SlashToken - && nextTokenKind !== SyntaxKind.RegularExpressionLiteral - && nextTokenKind !== SyntaxKind.CommaToken - && nextTokenKind !== SyntaxKind.TemplateExpression - && nextTokenKind !== SyntaxKind.TemplateHead - && nextTokenKind !== SyntaxKind.NoSubstitutionTemplateLiteral - && nextTokenKind !== SyntaxKind.DotToken; + nextTokenKind = nextRealToken.kind; + nextTokenStart = nextRealToken.getStart(context.sourceFile); } - function isSemicolonInsertionContext(context: FormattingContext): boolean { - return positionIsASICandidate(context.currentTokenSpan.end, context.currentTokenParent, context.sourceFile); + const startLine = context.sourceFile.getLineAndCharacterOfPosition(context.currentTokenSpan.pos).line; + const endLine = context.sourceFile.getLineAndCharacterOfPosition(nextTokenStart).line; + if (startLine === endLine) { + return nextTokenKind === SyntaxKind.CloseBraceToken + || nextTokenKind === SyntaxKind.EndOfFileToken; } - function isNotPropertyAccessOnIntegerLiteral(context: FormattingContext): boolean { - return !isPropertyAccessExpression(context.contextNode) - || !isNumericLiteral(context.contextNode.expression) - || context.contextNode.expression.getText().indexOf(".") !== -1; + if (nextTokenKind === SyntaxKind.SemicolonClassElement || + nextTokenKind === SyntaxKind.SemicolonToken + ) { + return false; } + + if (context.contextNode.kind === SyntaxKind.InterfaceDeclaration || + context.contextNode.kind === SyntaxKind.TypeAliasDeclaration + ) { + // Can’t remove semicolon after `foo`; it would parse as a method declaration: + // + // interface I { + // foo; + // (): void + // } + return !isPropertySignature(context.currentTokenParent) + || !!context.currentTokenParent.type + || nextTokenKind !== SyntaxKind.OpenParenToken; + } + + if (isPropertyDeclaration(context.currentTokenParent)) { + return !context.currentTokenParent.initializer; + } + + return context.currentTokenParent.kind !== SyntaxKind.ForStatement + && context.currentTokenParent.kind !== SyntaxKind.EmptyStatement + && context.currentTokenParent.kind !== SyntaxKind.SemicolonClassElement + && nextTokenKind !== SyntaxKind.OpenBracketToken + && nextTokenKind !== SyntaxKind.OpenParenToken + && nextTokenKind !== SyntaxKind.PlusToken + && nextTokenKind !== SyntaxKind.MinusToken + && nextTokenKind !== SyntaxKind.SlashToken + && nextTokenKind !== SyntaxKind.RegularExpressionLiteral + && nextTokenKind !== SyntaxKind.CommaToken + && nextTokenKind !== SyntaxKind.TemplateExpression + && nextTokenKind !== SyntaxKind.TemplateHead + && nextTokenKind !== SyntaxKind.NoSubstitutionTemplateLiteral + && nextTokenKind !== SyntaxKind.DotToken; +} + +function isSemicolonInsertionContext(context: FormattingContext): boolean { + return positionIsASICandidate(context.currentTokenSpan.end, context.currentTokenParent, context.sourceFile); +} + +function isNotPropertyAccessOnIntegerLiteral(context: FormattingContext): boolean { + return !isPropertyAccessExpression(context.contextNode) + || !isNumericLiteral(context.contextNode.expression) + || context.contextNode.expression.getText().indexOf(".") !== -1; } diff --git a/src/services/formatting/rulesMap.ts b/src/services/formatting/rulesMap.ts index ccee491040fc6..4cdd1b4ca0864 100644 --- a/src/services/formatting/rulesMap.ts +++ b/src/services/formatting/rulesMap.ts @@ -1,140 +1,144 @@ -/* @internal */ -namespace ts.formatting { - export function getFormatContext(options: FormatCodeSettings, host: FormattingHost): FormatContext { - return { options, getRules: getRulesMap(), host }; - } +import { Debug, every, FormatCodeSettings, FormattingHost, SyntaxKind } from "../_namespaces/ts"; +import { + anyContext, FormatContext, FormattingContext, getAllRules, Rule, RuleAction, RuleSpec, +} from "../_namespaces/ts.formatting"; + +/** @internal */ +export function getFormatContext(options: FormatCodeSettings, host: FormattingHost): FormatContext { + return { options, getRules: getRulesMap(), host }; +} - let rulesMapCache: RulesMap | undefined; +let rulesMapCache: RulesMap | undefined; - function getRulesMap(): RulesMap { - if (rulesMapCache === undefined) { - rulesMapCache = createRulesMap(getAllRules()); - } - return rulesMapCache; +function getRulesMap(): RulesMap { + if (rulesMapCache === undefined) { + rulesMapCache = createRulesMap(getAllRules()); } + return rulesMapCache; +} - /** - * For a given rule action, gets a mask of other rule actions that - * cannot be applied at the same position. - */ - function getRuleActionExclusion(ruleAction: RuleAction): RuleAction { - let mask: RuleAction = 0; - if (ruleAction & RuleAction.StopProcessingSpaceActions) { - mask |= RuleAction.ModifySpaceAction; - } - if (ruleAction & RuleAction.StopProcessingTokenActions) { - mask |= RuleAction.ModifyTokenAction; - } - if (ruleAction & RuleAction.ModifySpaceAction) { - mask |= RuleAction.ModifySpaceAction; - } - if (ruleAction & RuleAction.ModifyTokenAction) { - mask |= RuleAction.ModifyTokenAction; - } - return mask; +/** + * For a given rule action, gets a mask of other rule actions that + * cannot be applied at the same position. + */ +function getRuleActionExclusion(ruleAction: RuleAction): RuleAction { + let mask: RuleAction = 0; + if (ruleAction & RuleAction.StopProcessingSpaceActions) { + mask |= RuleAction.ModifySpaceAction; + } + if (ruleAction & RuleAction.StopProcessingTokenActions) { + mask |= RuleAction.ModifyTokenAction; + } + if (ruleAction & RuleAction.ModifySpaceAction) { + mask |= RuleAction.ModifySpaceAction; } + if (ruleAction & RuleAction.ModifyTokenAction) { + mask |= RuleAction.ModifyTokenAction; + } + return mask; +} - export type RulesMap = (context: FormattingContext) => readonly Rule[] | undefined; - function createRulesMap(rules: readonly RuleSpec[]): RulesMap { - const map = buildMap(rules); - return context => { - const bucket = map[getRuleBucketIndex(context.currentTokenSpan.kind, context.nextTokenSpan.kind)]; - if (bucket) { - const rules: Rule[] = []; - let ruleActionMask: RuleAction = 0; - for (const rule of bucket) { - const acceptRuleActions = ~getRuleActionExclusion(ruleActionMask); - if (rule.action & acceptRuleActions && every(rule.context, c => c(context))) { - rules.push(rule); - ruleActionMask |= rule.action; - } - } - if (rules.length) { - return rules; +/** @internal */ +export type RulesMap = (context: FormattingContext) => readonly Rule[] | undefined; +function createRulesMap(rules: readonly RuleSpec[]): RulesMap { + const map = buildMap(rules); + return context => { + const bucket = map[getRuleBucketIndex(context.currentTokenSpan.kind, context.nextTokenSpan.kind)]; + if (bucket) { + const rules: Rule[] = []; + let ruleActionMask: RuleAction = 0; + for (const rule of bucket) { + const acceptRuleActions = ~getRuleActionExclusion(ruleActionMask); + if (rule.action & acceptRuleActions && every(rule.context, c => c(context))) { + rules.push(rule); + ruleActionMask |= rule.action; } } - }; - } + if (rules.length) { + return rules; + } + } + }; +} - function buildMap(rules: readonly RuleSpec[]): readonly (readonly Rule[])[] { - // Map from bucket index to array of rules - const map: Rule[][] = new Array(mapRowLength * mapRowLength); - // This array is used only during construction of the rulesbucket in the map - const rulesBucketConstructionStateList = new Array(map.length); - for (const rule of rules) { - const specificRule = rule.leftTokenRange.isSpecific && rule.rightTokenRange.isSpecific; +function buildMap(rules: readonly RuleSpec[]): readonly (readonly Rule[])[] { + // Map from bucket index to array of rules + const map: Rule[][] = new Array(mapRowLength * mapRowLength); + // This array is used only during construction of the rulesbucket in the map + const rulesBucketConstructionStateList = new Array(map.length); + for (const rule of rules) { + const specificRule = rule.leftTokenRange.isSpecific && rule.rightTokenRange.isSpecific; - for (const left of rule.leftTokenRange.tokens) { - for (const right of rule.rightTokenRange.tokens) { - const index = getRuleBucketIndex(left, right); - let rulesBucket = map[index]; - if (rulesBucket === undefined) { - rulesBucket = map[index] = []; - } - addRule(rulesBucket, rule.rule, specificRule, rulesBucketConstructionStateList, index); + for (const left of rule.leftTokenRange.tokens) { + for (const right of rule.rightTokenRange.tokens) { + const index = getRuleBucketIndex(left, right); + let rulesBucket = map[index]; + if (rulesBucket === undefined) { + rulesBucket = map[index] = []; } + addRule(rulesBucket, rule.rule, specificRule, rulesBucketConstructionStateList, index); } } - return map; } + return map; +} - function getRuleBucketIndex(row: number, column: number): number { - Debug.assert(row <= SyntaxKind.LastKeyword && column <= SyntaxKind.LastKeyword, "Must compute formatting context from tokens"); - return (row * mapRowLength) + column; - } +function getRuleBucketIndex(row: number, column: number): number { + Debug.assert(row <= SyntaxKind.LastKeyword && column <= SyntaxKind.LastKeyword, "Must compute formatting context from tokens"); + return (row * mapRowLength) + column; +} - const maskBitSize = 5; - const mask = 0b11111; // MaskBitSize bits - const mapRowLength = SyntaxKind.LastToken + 1; +const maskBitSize = 5; +const mask = 0b11111; // MaskBitSize bits +const mapRowLength = SyntaxKind.LastToken + 1; - enum RulesPosition { - StopRulesSpecific = 0, - StopRulesAny = maskBitSize * 1, - ContextRulesSpecific = maskBitSize * 2, - ContextRulesAny = maskBitSize * 3, - NoContextRulesSpecific = maskBitSize * 4, - NoContextRulesAny = maskBitSize * 5 - } +enum RulesPosition { + StopRulesSpecific = 0, + StopRulesAny = maskBitSize * 1, + ContextRulesSpecific = maskBitSize * 2, + ContextRulesAny = maskBitSize * 3, + NoContextRulesSpecific = maskBitSize * 4, + NoContextRulesAny = maskBitSize * 5 +} - // The Rules list contains all the inserted rules into a rulebucket in the following order: - // 1- Ignore rules with specific token combination - // 2- Ignore rules with any token combination - // 3- Context rules with specific token combination - // 4- Context rules with any token combination - // 5- Non-context rules with specific token combination - // 6- Non-context rules with any token combination - // - // The member rulesInsertionIndexBitmap is used to describe the number of rules - // in each sub-bucket (above) hence can be used to know the index of where to insert - // the next rule. It's a bitmap which contains 6 different sections each is given 5 bits. - // - // Example: - // In order to insert a rule to the end of sub-bucket (3), we get the index by adding - // the values in the bitmap segments 3rd, 2nd, and 1st. - function addRule(rules: Rule[], rule: Rule, specificTokens: boolean, constructionState: number[], rulesBucketIndex: number): void { - const position = rule.action & RuleAction.StopAction ? - specificTokens ? RulesPosition.StopRulesSpecific : RulesPosition.StopRulesAny : - rule.context !== anyContext ? - specificTokens ? RulesPosition.ContextRulesSpecific : RulesPosition.ContextRulesAny : - specificTokens ? RulesPosition.NoContextRulesSpecific : RulesPosition.NoContextRulesAny; +// The Rules list contains all the inserted rules into a rulebucket in the following order: +// 1- Ignore rules with specific token combination +// 2- Ignore rules with any token combination +// 3- Context rules with specific token combination +// 4- Context rules with any token combination +// 5- Non-context rules with specific token combination +// 6- Non-context rules with any token combination +// +// The member rulesInsertionIndexBitmap is used to describe the number of rules +// in each sub-bucket (above) hence can be used to know the index of where to insert +// the next rule. It's a bitmap which contains 6 different sections each is given 5 bits. +// +// Example: +// In order to insert a rule to the end of sub-bucket (3), we get the index by adding +// the values in the bitmap segments 3rd, 2nd, and 1st. +function addRule(rules: Rule[], rule: Rule, specificTokens: boolean, constructionState: number[], rulesBucketIndex: number): void { + const position = rule.action & RuleAction.StopAction ? + specificTokens ? RulesPosition.StopRulesSpecific : RulesPosition.StopRulesAny : + rule.context !== anyContext ? + specificTokens ? RulesPosition.ContextRulesSpecific : RulesPosition.ContextRulesAny : + specificTokens ? RulesPosition.NoContextRulesSpecific : RulesPosition.NoContextRulesAny; - const state = constructionState[rulesBucketIndex] || 0; - rules.splice(getInsertionIndex(state, position), 0, rule); - constructionState[rulesBucketIndex] = increaseInsertionIndex(state, position); - } + const state = constructionState[rulesBucketIndex] || 0; + rules.splice(getInsertionIndex(state, position), 0, rule); + constructionState[rulesBucketIndex] = increaseInsertionIndex(state, position); +} - function getInsertionIndex(indexBitmap: number, maskPosition: RulesPosition) { - let index = 0; - for (let pos = 0; pos <= maskPosition; pos += maskBitSize) { - index += indexBitmap & mask; - indexBitmap >>= maskBitSize; - } - return index; +function getInsertionIndex(indexBitmap: number, maskPosition: RulesPosition) { + let index = 0; + for (let pos = 0; pos <= maskPosition; pos += maskBitSize) { + index += indexBitmap & mask; + indexBitmap >>= maskBitSize; } + return index; +} - function increaseInsertionIndex(indexBitmap: number, maskPosition: RulesPosition): number { - const value = ((indexBitmap >> maskPosition) & mask) + 1; - Debug.assert((value & mask) === value, "Adding more rules into the sub-bucket than allowed. Maximum allowed is 32 rules."); - return (indexBitmap & ~(mask << maskPosition)) | (value << maskPosition); - } +function increaseInsertionIndex(indexBitmap: number, maskPosition: RulesPosition): number { + const value = ((indexBitmap >> maskPosition) & mask) + 1; + Debug.assert((value & mask) === value, "Adding more rules into the sub-bucket than allowed. Maximum allowed is 32 rules."); + return (indexBitmap & ~(mask << maskPosition)) | (value << maskPosition); } diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index 78b8a95890e50..bc522163651de 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -1,710 +1,722 @@ -/* @internal */ -namespace ts.formatting { - export namespace SmartIndenter { - - const enum Value { - Unknown = -1 - } - - /** - * @param assumeNewLineBeforeCloseBrace - * `false` when called on text from a real source file. - * `true` when we need to assume `position` is on a newline. - * - * This is useful for codefixes. Consider - * ``` - * function f() { - * |} - * ``` - * with `position` at `|`. - * - * When inserting some text after an open brace, we would like to get indentation as if a newline was already there. - * By default indentation at `position` will be 0 so 'assumeNewLineBeforeCloseBrace' overrides this behavior. - */ - export function getIndentation(position: number, sourceFile: SourceFile, options: EditorSettings, assumeNewLineBeforeCloseBrace = false): number { - if (position > sourceFile.text.length) { - return getBaseIndentation(options); // past EOF - } - - // no indentation when the indent style is set to none, - // so we can return fast - if (options.indentStyle === IndentStyle.None) { - return 0; - } +import { + ArrayBindingPattern, ArrayLiteralExpression, CallExpression, CharacterCodes, ClassDeclaration, ClassExpression, + CommentRange, contains, Debug, EditorSettings, find, findChildOfKind, findListItemInfo, findNextToken, + findPrecedingToken, FormatCodeSettings, GetAccessorDeclaration, getLineAndCharacterOfPosition, + getLineStartPositionForPosition, getStartPositionOfLine, getTokenAtPosition, IfStatement, ImportClause, IndentStyle, + InterfaceDeclaration, isCallExpression, isCallOrNewExpression, isConditionalExpression, isDeclaration, + isStatementButNotDeclaration, isStringOrRegularExpressionOrTemplateLiteral, isWhiteSpaceLike, + isWhiteSpaceSingleLine, JSDocTemplateTag, LineAndCharacter, NamedImportsOrExports, Node, NodeArray, + ObjectBindingPattern, ObjectLiteralExpression, positionBelongsToNode, rangeContainsRange, rangeContainsStartEnd, + SignatureDeclaration, skipTrivia, SourceFile, SourceFileLike, SyntaxKind, TextRange, TypeAliasDeclaration, + TypeLiteralNode, TypeReferenceNode, VariableDeclarationList, +} from "../_namespaces/ts"; +import { getRangeOfEnclosingComment, TextRangeWithKind } from "../_namespaces/ts.formatting"; + +/** @internal */ +export namespace SmartIndenter { + + const enum Value { + Unknown = -1 + } - const precedingToken = findPrecedingToken(position, sourceFile, /*startNode*/ undefined, /*excludeJsdoc*/ true); + /** + * @param assumeNewLineBeforeCloseBrace + * `false` when called on text from a real source file. + * `true` when we need to assume `position` is on a newline. + * + * This is useful for codefixes. Consider + * ``` + * function f() { + * |} + * ``` + * with `position` at `|`. + * + * When inserting some text after an open brace, we would like to get indentation as if a newline was already there. + * By default indentation at `position` will be 0 so 'assumeNewLineBeforeCloseBrace' overrides this behavior. + */ + export function getIndentation(position: number, sourceFile: SourceFile, options: EditorSettings, assumeNewLineBeforeCloseBrace = false): number { + if (position > sourceFile.text.length) { + return getBaseIndentation(options); // past EOF + } + + // no indentation when the indent style is set to none, + // so we can return fast + if (options.indentStyle === IndentStyle.None) { + return 0; + } + + const precedingToken = findPrecedingToken(position, sourceFile, /*startNode*/ undefined, /*excludeJsdoc*/ true); + + // eslint-disable-next-line no-null/no-null + const enclosingCommentRange = getRangeOfEnclosingComment(sourceFile, position, precedingToken || null); + if (enclosingCommentRange && enclosingCommentRange.kind === SyntaxKind.MultiLineCommentTrivia) { + return getCommentIndent(sourceFile, position, options, enclosingCommentRange); + } + + if (!precedingToken) { + return getBaseIndentation(options); + } - // eslint-disable-next-line no-null/no-null - const enclosingCommentRange = getRangeOfEnclosingComment(sourceFile, position, precedingToken || null); - if (enclosingCommentRange && enclosingCommentRange.kind === SyntaxKind.MultiLineCommentTrivia) { - return getCommentIndent(sourceFile, position, options, enclosingCommentRange); + // no indentation in string \regex\template literals + const precedingTokenIsLiteral = isStringOrRegularExpressionOrTemplateLiteral(precedingToken.kind); + if (precedingTokenIsLiteral && precedingToken.getStart(sourceFile) <= position && position < precedingToken.end) { + return 0; + } + + const lineAtPosition = sourceFile.getLineAndCharacterOfPosition(position).line; + + // indentation is first non-whitespace character in a previous line + // for block indentation, we should look for a line which contains something that's not + // whitespace. + const currentToken = getTokenAtPosition(sourceFile, position); + // For object literals, we want indentation to work just like with blocks. + // If the `{` starts in any position (even in the middle of a line), then + // the following indentation should treat `{` as the start of that line (including leading whitespace). + // ``` + // const a: { x: undefined, y: undefined } = {} // leading 4 whitespaces and { starts in the middle of line + // -> + // const a: { x: undefined, y: undefined } = { + // x: undefined, + // y: undefined, + // } + // --------------------- + // const a: {x : undefined, y: undefined } = + // {} + // -> + // const a: { x: undefined, y: undefined } = + // { // leading 5 whitespaces and { starts at 6 column + // x: undefined, + // y: undefined, + // } + // ``` + const isObjectLiteral = currentToken.kind === SyntaxKind.OpenBraceToken && currentToken.parent.kind === SyntaxKind.ObjectLiteralExpression; + if (options.indentStyle === IndentStyle.Block || isObjectLiteral) { + return getBlockIndent(sourceFile, position, options); + } + + if (precedingToken.kind === SyntaxKind.CommaToken && precedingToken.parent.kind !== SyntaxKind.BinaryExpression) { + // previous token is comma that separates items in list - find the previous item and try to derive indentation from it + const actualIndentation = getActualIndentationForListItemBeforeComma(precedingToken, sourceFile, options); + if (actualIndentation !== Value.Unknown) { + return actualIndentation; } + } - if (!precedingToken) { - return getBaseIndentation(options); - } + const containerList = getListByPosition(position, precedingToken.parent, sourceFile); + // use list position if the preceding token is before any list items + if (containerList && !rangeContainsRange(containerList, precedingToken)) { + const useTheSameBaseIndentation = [SyntaxKind.FunctionExpression, SyntaxKind.ArrowFunction].indexOf(currentToken.parent.kind) !== -1; + const indentSize = useTheSameBaseIndentation ? 0 : options.indentSize!; + return getActualIndentationForListStartLine(containerList, sourceFile, options) + indentSize; // TODO: GH#18217 + } - // no indentation in string \regex\template literals - const precedingTokenIsLiteral = isStringOrRegularExpressionOrTemplateLiteral(precedingToken.kind); - if (precedingTokenIsLiteral && precedingToken.getStart(sourceFile) <= position && position < precedingToken.end) { - return 0; - } + return getSmartIndent(sourceFile, position, precedingToken, lineAtPosition, assumeNewLineBeforeCloseBrace, options); + } - const lineAtPosition = sourceFile.getLineAndCharacterOfPosition(position).line; - - // indentation is first non-whitespace character in a previous line - // for block indentation, we should look for a line which contains something that's not - // whitespace. - const currentToken = getTokenAtPosition(sourceFile, position); - // For object literals, we want indentation to work just like with blocks. - // If the `{` starts in any position (even in the middle of a line), then - // the following indentation should treat `{` as the start of that line (including leading whitespace). - // ``` - // const a: { x: undefined, y: undefined } = {} // leading 4 whitespaces and { starts in the middle of line - // -> - // const a: { x: undefined, y: undefined } = { - // x: undefined, - // y: undefined, - // } - // --------------------- - // const a: {x : undefined, y: undefined } = - // {} - // -> - // const a: { x: undefined, y: undefined } = - // { // leading 5 whitespaces and { starts at 6 column - // x: undefined, - // y: undefined, - // } - // ``` - const isObjectLiteral = currentToken.kind === SyntaxKind.OpenBraceToken && currentToken.parent.kind === SyntaxKind.ObjectLiteralExpression; - if (options.indentStyle === IndentStyle.Block || isObjectLiteral) { - return getBlockIndent(sourceFile, position, options); - } + function getCommentIndent(sourceFile: SourceFile, position: number, options: EditorSettings, enclosingCommentRange: CommentRange): number { + const previousLine = getLineAndCharacterOfPosition(sourceFile, position).line - 1; + const commentStartLine = getLineAndCharacterOfPosition(sourceFile, enclosingCommentRange.pos).line; - if (precedingToken.kind === SyntaxKind.CommaToken && precedingToken.parent.kind !== SyntaxKind.BinaryExpression) { - // previous token is comma that separates items in list - find the previous item and try to derive indentation from it - const actualIndentation = getActualIndentationForListItemBeforeComma(precedingToken, sourceFile, options); - if (actualIndentation !== Value.Unknown) { - return actualIndentation; - } - } + Debug.assert(commentStartLine >= 0); - const containerList = getListByPosition(position, precedingToken.parent, sourceFile); - // use list position if the preceding token is before any list items - if (containerList && !rangeContainsRange(containerList, precedingToken)) { - const useTheSameBaseIndentation = [SyntaxKind.FunctionExpression, SyntaxKind.ArrowFunction].indexOf(currentToken.parent.kind) !== -1; - const indentSize = useTheSameBaseIndentation ? 0 : options.indentSize!; - return getActualIndentationForListStartLine(containerList, sourceFile, options) + indentSize; // TODO: GH#18217 - } - - return getSmartIndent(sourceFile, position, precedingToken, lineAtPosition, assumeNewLineBeforeCloseBrace, options); + if (previousLine <= commentStartLine) { + return findFirstNonWhitespaceColumn(getStartPositionOfLine(commentStartLine, sourceFile), position, sourceFile, options); } - function getCommentIndent(sourceFile: SourceFile, position: number, options: EditorSettings, enclosingCommentRange: CommentRange): number { - const previousLine = getLineAndCharacterOfPosition(sourceFile, position).line - 1; - const commentStartLine = getLineAndCharacterOfPosition(sourceFile, enclosingCommentRange.pos).line; + const startPositionOfLine = getStartPositionOfLine(previousLine, sourceFile); + const { column, character } = findFirstNonWhitespaceCharacterAndColumn(startPositionOfLine, position, sourceFile, options); + + if (column === 0) { + return column; + } - Debug.assert(commentStartLine >= 0); + const firstNonWhitespaceCharacterCode = sourceFile.text.charCodeAt(startPositionOfLine + character); + return firstNonWhitespaceCharacterCode === CharacterCodes.asterisk ? column - 1 : column; + } - if (previousLine <= commentStartLine) { - return findFirstNonWhitespaceColumn(getStartPositionOfLine(commentStartLine, sourceFile), position, sourceFile, options); + function getBlockIndent(sourceFile: SourceFile, position: number, options: EditorSettings): number { + // move backwards until we find a line with a non-whitespace character, + // then find the first non-whitespace character for that line. + let current = position; + while (current > 0) { + const char = sourceFile.text.charCodeAt(current); + if (!isWhiteSpaceLike(char)) { + break; } + current--; + } - const startPositionOfLine = getStartPositionOfLine(previousLine, sourceFile); - const { column, character } = findFirstNonWhitespaceCharacterAndColumn(startPositionOfLine, position, sourceFile, options); + const lineStart = getLineStartPositionForPosition(current, sourceFile); + return findFirstNonWhitespaceColumn(lineStart, current, sourceFile, options); + } - if (column === 0) { - return column; - } + function getSmartIndent(sourceFile: SourceFile, position: number, precedingToken: Node, lineAtPosition: number, assumeNewLineBeforeCloseBrace: boolean, options: EditorSettings): number { + // try to find node that can contribute to indentation and includes 'position' starting from 'precedingToken' + // if such node is found - compute initial indentation for 'position' inside this node + let previous: Node | undefined; + let current = precedingToken; + + while (current) { + if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(options, current, previous, sourceFile, /*isNextChild*/ true)) { + const currentStart = getStartLineAndCharacterForNode(current, sourceFile); + const nextTokenKind = nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile); + const indentationDelta = nextTokenKind !== NextTokenKind.Unknown + // handle cases when codefix is about to be inserted before the close brace + ? assumeNewLineBeforeCloseBrace && nextTokenKind === NextTokenKind.CloseBrace ? options.indentSize : 0 + : lineAtPosition !== currentStart.line ? options.indentSize : 0; + return getIndentationForNodeWorker(current, currentStart, /*ignoreActualIndentationRange*/ undefined, indentationDelta!, sourceFile, /*isNextChild*/ true, options); // TODO: GH#18217 + } + + // check if current node is a list item - if yes, take indentation from it + // do not consider parent-child line sharing yet: + // function foo(a + // | preceding node 'a' does share line with its parent but indentation is expected + const actualIndentation = getActualIndentationForListItem(current, sourceFile, options, /*listIndentsChild*/ true); + if (actualIndentation !== Value.Unknown) { + return actualIndentation; + } + + previous = current; + current = current.parent; + } + // no parent was found - return the base indentation of the SourceFile + return getBaseIndentation(options); + } - const firstNonWhitespaceCharacterCode = sourceFile.text.charCodeAt(startPositionOfLine + character); - return firstNonWhitespaceCharacterCode === CharacterCodes.asterisk ? column - 1 : column; - } + export function getIndentationForNode(n: Node, ignoreActualIndentationRange: TextRange, sourceFile: SourceFile, options: EditorSettings): number { + const start = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)); + return getIndentationForNodeWorker(n, start, ignoreActualIndentationRange, /*indentationDelta*/ 0, sourceFile, /*isNextChild*/ false, options); + } - function getBlockIndent(sourceFile: SourceFile, position: number, options: EditorSettings): number { - // move backwards until we find a line with a non-whitespace character, - // then find the first non-whitespace character for that line. - let current = position; - while (current > 0) { - const char = sourceFile.text.charCodeAt(current); - if (!isWhiteSpaceLike(char)) { - break; - } - current--; - } + export function getBaseIndentation(options: EditorSettings) { + return options.baseIndentSize || 0; + } - const lineStart = getLineStartPositionForPosition(current, sourceFile); - return findFirstNonWhitespaceColumn(lineStart, current, sourceFile, options); - } - - function getSmartIndent(sourceFile: SourceFile, position: number, precedingToken: Node, lineAtPosition: number, assumeNewLineBeforeCloseBrace: boolean, options: EditorSettings): number { - // try to find node that can contribute to indentation and includes 'position' starting from 'precedingToken' - // if such node is found - compute initial indentation for 'position' inside this node - let previous: Node | undefined; - let current = precedingToken; - - while (current) { - if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(options, current, previous, sourceFile, /*isNextChild*/ true)) { - const currentStart = getStartLineAndCharacterForNode(current, sourceFile); - const nextTokenKind = nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile); - const indentationDelta = nextTokenKind !== NextTokenKind.Unknown - // handle cases when codefix is about to be inserted before the close brace - ? assumeNewLineBeforeCloseBrace && nextTokenKind === NextTokenKind.CloseBrace ? options.indentSize : 0 - : lineAtPosition !== currentStart.line ? options.indentSize : 0; - return getIndentationForNodeWorker(current, currentStart, /*ignoreActualIndentationRange*/ undefined, indentationDelta!, sourceFile, /*isNextChild*/ true, options); // TODO: GH#18217 + function getIndentationForNodeWorker( + current: Node, + currentStart: LineAndCharacter, + ignoreActualIndentationRange: TextRange | undefined, + indentationDelta: number, + sourceFile: SourceFile, + isNextChild: boolean, + options: EditorSettings): number { + let parent = current.parent; + + // Walk up the tree and collect indentation for parent-child node pairs. Indentation is not added if + // * parent and child nodes start on the same line, or + // * parent is an IfStatement and child starts on the same line as an 'else clause'. + while (parent) { + let useActualIndentation = true; + if (ignoreActualIndentationRange) { + const start = current.getStart(sourceFile); + useActualIndentation = start < ignoreActualIndentationRange.pos || start > ignoreActualIndentationRange.end; + } + + const containingListOrParentStart = getContainingListOrParentStart(parent, current, sourceFile); + const parentAndChildShareLine = + containingListOrParentStart.line === currentStart.line || + childStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStart.line, sourceFile); + + if (useActualIndentation) { + // check if current node is a list item - if yes, take indentation from it + const firstListChild = getContainingList(current, sourceFile)?.[0]; + // A list indents its children if the children begin on a later line than the list itself: + // + // f1( L0 - List start + // { L1 - First child start: indented, along with all other children + // prop: 0 + // }, + // { + // prop: 1 + // } + // ) + // + // f2({ L0 - List start and first child start: children are not indented. + // prop: 0 Object properties are indented only one level, because the list + // }, { itself contributes nothing. + // prop: 1 L3 - The indentation of the second object literal is best understood by + // }) looking at the relationship between the list and *first* list item. + const listIndentsChild = !!firstListChild && getStartLineAndCharacterForNode(firstListChild, sourceFile).line > containingListOrParentStart.line; + let actualIndentation = getActualIndentationForListItem(current, sourceFile, options, listIndentsChild); + if (actualIndentation !== Value.Unknown) { + return actualIndentation + indentationDelta; } - // check if current node is a list item - if yes, take indentation from it - // do not consider parent-child line sharing yet: - // function foo(a - // | preceding node 'a' does share line with its parent but indentation is expected - const actualIndentation = getActualIndentationForListItem(current, sourceFile, options, /*listIndentsChild*/ true); + // try to fetch actual indentation for current node from source text + actualIndentation = getActualIndentationForNode(current, parent, currentStart, parentAndChildShareLine, sourceFile, options); if (actualIndentation !== Value.Unknown) { - return actualIndentation; + return actualIndentation + indentationDelta; } + } - previous = current; - current = current.parent; + // increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line + if (shouldIndentChildNode(options, parent, current, sourceFile, isNextChild) && !parentAndChildShareLine) { + indentationDelta += options.indentSize!; } - // no parent was found - return the base indentation of the SourceFile - return getBaseIndentation(options); - } - export function getIndentationForNode(n: Node, ignoreActualIndentationRange: TextRange, sourceFile: SourceFile, options: EditorSettings): number { - const start = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)); - return getIndentationForNodeWorker(n, start, ignoreActualIndentationRange, /*indentationDelta*/ 0, sourceFile, /*isNextChild*/ false, options); - } + // In our AST, a call argument's `parent` is the call-expression, not the argument list. + // We would like to increase indentation based on the relationship between an argument and its argument-list, + // so we spoof the starting position of the (parent) call-expression to match the (non-parent) argument-list. + // But, the spoofed start-value could then cause a problem when comparing the start position of the call-expression + // to *its* parent (in the case of an iife, an expression statement), adding an extra level of indentation. + // + // Instead, when at an argument, we unspoof the starting position of the enclosing call expression + // *after* applying indentation for the argument. - export function getBaseIndentation(options: EditorSettings) { - return options.baseIndentSize || 0; - } + const useTrueStart = + isArgumentAndStartLineOverlapsExpressionBeingCalled(parent, current, currentStart.line, sourceFile); - function getIndentationForNodeWorker( - current: Node, - currentStart: LineAndCharacter, - ignoreActualIndentationRange: TextRange | undefined, - indentationDelta: number, - sourceFile: SourceFile, - isNextChild: boolean, - options: EditorSettings): number { - let parent = current.parent; + current = parent; + parent = current.parent; + currentStart = useTrueStart ? sourceFile.getLineAndCharacterOfPosition(current.getStart(sourceFile)) : containingListOrParentStart; + } - // Walk up the tree and collect indentation for parent-child node pairs. Indentation is not added if - // * parent and child nodes start on the same line, or - // * parent is an IfStatement and child starts on the same line as an 'else clause'. - while (parent) { - let useActualIndentation = true; - if (ignoreActualIndentationRange) { - const start = current.getStart(sourceFile); - useActualIndentation = start < ignoreActualIndentationRange.pos || start > ignoreActualIndentationRange.end; - } + return indentationDelta + getBaseIndentation(options); + } - const containingListOrParentStart = getContainingListOrParentStart(parent, current, sourceFile); - const parentAndChildShareLine = - containingListOrParentStart.line === currentStart.line || - childStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStart.line, sourceFile); - - if (useActualIndentation) { - // check if current node is a list item - if yes, take indentation from it - const firstListChild = getContainingList(current, sourceFile)?.[0]; - // A list indents its children if the children begin on a later line than the list itself: - // - // f1( L0 - List start - // { L1 - First child start: indented, along with all other children - // prop: 0 - // }, - // { - // prop: 1 - // } - // ) - // - // f2({ L0 - List start and first child start: children are not indented. - // prop: 0 Object properties are indented only one level, because the list - // }, { itself contributes nothing. - // prop: 1 L3 - The indentation of the second object literal is best understood by - // }) looking at the relationship between the list and *first* list item. - const listIndentsChild = !!firstListChild && getStartLineAndCharacterForNode(firstListChild, sourceFile).line > containingListOrParentStart.line; - let actualIndentation = getActualIndentationForListItem(current, sourceFile, options, listIndentsChild); - if (actualIndentation !== Value.Unknown) { - return actualIndentation + indentationDelta; - } - - // try to fetch actual indentation for current node from source text - actualIndentation = getActualIndentationForNode(current, parent, currentStart, parentAndChildShareLine, sourceFile, options); - if (actualIndentation !== Value.Unknown) { - return actualIndentation + indentationDelta; - } - } + function getContainingListOrParentStart(parent: Node, child: Node, sourceFile: SourceFile): LineAndCharacter { + const containingList = getContainingList(child, sourceFile); + const startPos = containingList ? containingList.pos : parent.getStart(sourceFile); + return sourceFile.getLineAndCharacterOfPosition(startPos); + } - // increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line - if (shouldIndentChildNode(options, parent, current, sourceFile, isNextChild) && !parentAndChildShareLine) { - indentationDelta += options.indentSize!; - } + /* + * Function returns Value.Unknown if indentation cannot be determined + */ + function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: EditorSettings): number { + // previous token is comma that separates items in list - find the previous item and try to derive indentation from it + const commaItemInfo = findListItemInfo(commaToken); + if (commaItemInfo && commaItemInfo.listItemIndex > 0) { + return deriveActualIndentationFromList(commaItemInfo.list.getChildren(), commaItemInfo.listItemIndex - 1, sourceFile, options); + } + else { + // handle broken code gracefully + return Value.Unknown; + } + } - // In our AST, a call argument's `parent` is the call-expression, not the argument list. - // We would like to increase indentation based on the relationship between an argument and its argument-list, - // so we spoof the starting position of the (parent) call-expression to match the (non-parent) argument-list. - // But, the spoofed start-value could then cause a problem when comparing the start position of the call-expression - // to *its* parent (in the case of an iife, an expression statement), adding an extra level of indentation. - // - // Instead, when at an argument, we unspoof the starting position of the enclosing call expression - // *after* applying indentation for the argument. + /* + * Function returns Value.Unknown if actual indentation for node should not be used (i.e because node is nested expression) + */ + function getActualIndentationForNode(current: Node, + parent: Node, + currentLineAndChar: LineAndCharacter, + parentAndChildShareLine: boolean, + sourceFile: SourceFile, + options: EditorSettings): number { + + // actual indentation is used for statements\declarations if one of cases below is true: + // - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually + // - parent and child are not on the same line + const useActualIndentation = + (isDeclaration(current) || isStatementButNotDeclaration(current)) && + (parent.kind === SyntaxKind.SourceFile || !parentAndChildShareLine); + + if (!useActualIndentation) { + return Value.Unknown; + } - const useTrueStart = - isArgumentAndStartLineOverlapsExpressionBeingCalled(parent, current, currentStart.line, sourceFile); + return findColumnForFirstNonWhitespaceCharacterInLine(currentLineAndChar, sourceFile, options); + } - current = parent; - parent = current.parent; - currentStart = useTrueStart ? sourceFile.getLineAndCharacterOfPosition(current.getStart(sourceFile)) : containingListOrParentStart; - } + const enum NextTokenKind { + Unknown, + OpenBrace, + CloseBrace + } - return indentationDelta + getBaseIndentation(options); + function nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): NextTokenKind { + const nextToken = findNextToken(precedingToken, current, sourceFile); + if (!nextToken) { + return NextTokenKind.Unknown; } - function getContainingListOrParentStart(parent: Node, child: Node, sourceFile: SourceFile): LineAndCharacter { - const containingList = getContainingList(child, sourceFile); - const startPos = containingList ? containingList.pos : parent.getStart(sourceFile); - return sourceFile.getLineAndCharacterOfPosition(startPos); + if (nextToken.kind === SyntaxKind.OpenBraceToken) { + // open braces are always indented at the parent level + return NextTokenKind.OpenBrace; } + else if (nextToken.kind === SyntaxKind.CloseBraceToken) { + // close braces are indented at the parent level if they are located on the same line with cursor + // this means that if new line will be added at $ position, this case will be indented + // class A { + // $ + // } + /// and this one - not + // class A { + // $} - /* - * Function returns Value.Unknown if indentation cannot be determined - */ - function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: EditorSettings): number { - // previous token is comma that separates items in list - find the previous item and try to derive indentation from it - const commaItemInfo = findListItemInfo(commaToken); - if (commaItemInfo && commaItemInfo.listItemIndex > 0) { - return deriveActualIndentationFromList(commaItemInfo.list.getChildren(), commaItemInfo.listItemIndex - 1, sourceFile, options); - } - else { - // handle broken code gracefully - return Value.Unknown; - } + const nextTokenStartLine = getStartLineAndCharacterForNode(nextToken, sourceFile).line; + return lineAtPosition === nextTokenStartLine ? NextTokenKind.CloseBrace : NextTokenKind.Unknown; } - /* - * Function returns Value.Unknown if actual indentation for node should not be used (i.e because node is nested expression) - */ - function getActualIndentationForNode(current: Node, - parent: Node, - currentLineAndChar: LineAndCharacter, - parentAndChildShareLine: boolean, - sourceFile: SourceFile, - options: EditorSettings): number { - - // actual indentation is used for statements\declarations if one of cases below is true: - // - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually - // - parent and child are not on the same line - const useActualIndentation = - (isDeclaration(current) || isStatementButNotDeclaration(current)) && - (parent.kind === SyntaxKind.SourceFile || !parentAndChildShareLine); - - if (!useActualIndentation) { - return Value.Unknown; - } + return NextTokenKind.Unknown; + } - return findColumnForFirstNonWhitespaceCharacterInLine(currentLineAndChar, sourceFile, options); - } + function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFileLike): LineAndCharacter { + return sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)); + } - const enum NextTokenKind { - Unknown, - OpenBrace, - CloseBrace + export function isArgumentAndStartLineOverlapsExpressionBeingCalled(parent: Node, child: Node, childStartLine: number, sourceFile: SourceFileLike): boolean { + if (!(isCallExpression(parent) && contains(parent.arguments, child))) { + return false; } - function nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): NextTokenKind { - const nextToken = findNextToken(precedingToken, current, sourceFile); - if (!nextToken) { - return NextTokenKind.Unknown; - } + const expressionOfCallExpressionEnd = parent.expression.getEnd(); + const expressionOfCallExpressionEndLine = getLineAndCharacterOfPosition(sourceFile, expressionOfCallExpressionEnd).line; + return expressionOfCallExpressionEndLine === childStartLine; + } - if (nextToken.kind === SyntaxKind.OpenBraceToken) { - // open braces are always indented at the parent level - return NextTokenKind.OpenBrace; - } - else if (nextToken.kind === SyntaxKind.CloseBraceToken) { - // close braces are indented at the parent level if they are located on the same line with cursor - // this means that if new line will be added at $ position, this case will be indented - // class A { - // $ - // } - /// and this one - not - // class A { - // $} - - const nextTokenStartLine = getStartLineAndCharacterForNode(nextToken, sourceFile).line; - return lineAtPosition === nextTokenStartLine ? NextTokenKind.CloseBrace : NextTokenKind.Unknown; - } + export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { + if (parent.kind === SyntaxKind.IfStatement && (parent as IfStatement).elseStatement === child) { + const elseKeyword = findChildOfKind(parent, SyntaxKind.ElseKeyword, sourceFile)!; + Debug.assert(elseKeyword !== undefined); - return NextTokenKind.Unknown; + const elseKeywordStartLine = getStartLineAndCharacterForNode(elseKeyword, sourceFile).line; + return elseKeywordStartLine === childStartLine; } - function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFileLike): LineAndCharacter { - return sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)); - } + return false; + } - export function isArgumentAndStartLineOverlapsExpressionBeingCalled(parent: Node, child: Node, childStartLine: number, sourceFile: SourceFileLike): boolean { - if (!(isCallExpression(parent) && contains(parent.arguments, child))) { - return false; + // A multiline conditional typically increases the indentation of its whenTrue and whenFalse children: + // + // condition + // ? whenTrue + // : whenFalse; + // + // However, that indentation does not apply if the subexpressions themselves span multiple lines, + // applying their own indentation: + // + // (() => { + // return complexCalculationForCondition(); + // })() ? { + // whenTrue: 'multiline object literal' + // } : ( + // whenFalse('multiline parenthesized expression') + // ); + // + // In these cases, we must discard the indentation increase that would otherwise be applied to the + // whenTrue and whenFalse children to avoid double-indenting their contents. To identify this scenario, + // we check for the whenTrue branch beginning on the line that the condition ends, and the whenFalse + // branch beginning on the line that the whenTrue branch ends. + export function childIsUnindentedBranchOfConditionalExpression(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { + if (isConditionalExpression(parent) && (child === parent.whenTrue || child === parent.whenFalse)) { + const conditionEndLine = getLineAndCharacterOfPosition(sourceFile, parent.condition.end).line; + if (child === parent.whenTrue) { + return childStartLine === conditionEndLine; + } + else { + // On the whenFalse side, we have to look at the whenTrue side, because if that one was + // indented, whenFalse must also be indented: + // + // const y = true + // ? 1 : ( L1: whenTrue indented because it's on a new line + // 0 L2: indented two stops, one because whenTrue was indented + // ); and one because of the parentheses spanning multiple lines + const trueStartLine = getStartLineAndCharacterForNode(parent.whenTrue, sourceFile).line; + const trueEndLine = getLineAndCharacterOfPosition(sourceFile, parent.whenTrue.end).line; + return conditionEndLine === trueStartLine && trueEndLine === childStartLine; } - - const expressionOfCallExpressionEnd = parent.expression.getEnd(); - const expressionOfCallExpressionEndLine = getLineAndCharacterOfPosition(sourceFile, expressionOfCallExpressionEnd).line; - return expressionOfCallExpressionEndLine === childStartLine; } + return false; + } - export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { - if (parent.kind === SyntaxKind.IfStatement && (parent as IfStatement).elseStatement === child) { - const elseKeyword = findChildOfKind(parent, SyntaxKind.ElseKeyword, sourceFile)!; - Debug.assert(elseKeyword !== undefined); - - const elseKeywordStartLine = getStartLineAndCharacterForNode(elseKeyword, sourceFile).line; - return elseKeywordStartLine === childStartLine; - } + export function argumentStartsOnSameLineAsPreviousArgument(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { + if (isCallOrNewExpression(parent)) { + if (!parent.arguments) return false; + const currentNode = find(parent.arguments, arg => arg.pos === child.pos); + // If it's not one of the arguments, don't look past this + if (!currentNode) return false; + const currentIndex = parent.arguments.indexOf(currentNode); + if (currentIndex === 0) return false; // Can't look at previous node if first - return false; - } + const previousNode = parent.arguments[currentIndex - 1]; + const lineOfPreviousNode = getLineAndCharacterOfPosition(sourceFile, previousNode.getEnd()).line; - // A multiline conditional typically increases the indentation of its whenTrue and whenFalse children: - // - // condition - // ? whenTrue - // : whenFalse; - // - // However, that indentation does not apply if the subexpressions themselves span multiple lines, - // applying their own indentation: - // - // (() => { - // return complexCalculationForCondition(); - // })() ? { - // whenTrue: 'multiline object literal' - // } : ( - // whenFalse('multiline parenthesized expression') - // ); - // - // In these cases, we must discard the indentation increase that would otherwise be applied to the - // whenTrue and whenFalse children to avoid double-indenting their contents. To identify this scenario, - // we check for the whenTrue branch beginning on the line that the condition ends, and the whenFalse - // branch beginning on the line that the whenTrue branch ends. - export function childIsUnindentedBranchOfConditionalExpression(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { - if (isConditionalExpression(parent) && (child === parent.whenTrue || child === parent.whenFalse)) { - const conditionEndLine = getLineAndCharacterOfPosition(sourceFile, parent.condition.end).line; - if (child === parent.whenTrue) { - return childStartLine === conditionEndLine; - } - else { - // On the whenFalse side, we have to look at the whenTrue side, because if that one was - // indented, whenFalse must also be indented: - // - // const y = true - // ? 1 : ( L1: whenTrue indented because it's on a new line - // 0 L2: indented two stops, one because whenTrue was indented - // ); and one because of the parentheses spanning multiple lines - const trueStartLine = getStartLineAndCharacterForNode(parent.whenTrue, sourceFile).line; - const trueEndLine = getLineAndCharacterOfPosition(sourceFile, parent.whenTrue.end).line; - return conditionEndLine === trueStartLine && trueEndLine === childStartLine; - } + if (childStartLine === lineOfPreviousNode) { + return true; } - return false; } - export function argumentStartsOnSameLineAsPreviousArgument(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { - if (isCallOrNewExpression(parent)) { - if (!parent.arguments) return false; - const currentNode = find(parent.arguments, arg => arg.pos === child.pos); - // If it's not one of the arguments, don't look past this - if (!currentNode) return false; - const currentIndex = parent.arguments.indexOf(currentNode); - if (currentIndex === 0) return false; // Can't look at previous node if first + return false; + } - const previousNode = parent.arguments[currentIndex - 1]; - const lineOfPreviousNode = getLineAndCharacterOfPosition(sourceFile, previousNode.getEnd()).line; + export function getContainingList(node: Node, sourceFile: SourceFile): NodeArray | undefined { + return node.parent && getListByRange(node.getStart(sourceFile), node.getEnd(), node.parent, sourceFile); + } - if (childStartLine === lineOfPreviousNode) { - return true; - } - } + function getListByPosition(pos: number, node: Node, sourceFile: SourceFile): NodeArray | undefined { + return node && getListByRange(pos, pos, node, sourceFile); + } - return false; + function getListByRange(start: number, end: number, node: Node, sourceFile: SourceFile): NodeArray | undefined { + switch (node.kind) { + case SyntaxKind.TypeReference: + return getList((node as TypeReferenceNode).typeArguments); + case SyntaxKind.ObjectLiteralExpression: + return getList((node as ObjectLiteralExpression).properties); + case SyntaxKind.ArrayLiteralExpression: + return getList((node as ArrayLiteralExpression).elements); + case SyntaxKind.TypeLiteral: + return getList((node as TypeLiteralNode).members); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.Constructor: + case SyntaxKind.ConstructorType: + case SyntaxKind.ConstructSignature: + return getList((node as SignatureDeclaration).typeParameters) || getList((node as SignatureDeclaration).parameters); + case SyntaxKind.GetAccessor: + return getList((node as GetAccessorDeclaration).parameters); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocTemplateTag: + return getList((node as ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag).typeParameters); + case SyntaxKind.NewExpression: + case SyntaxKind.CallExpression: + return getList((node as CallExpression).typeArguments) || getList((node as CallExpression).arguments); + case SyntaxKind.VariableDeclarationList: + return getList((node as VariableDeclarationList).declarations); + case SyntaxKind.NamedImports: + case SyntaxKind.NamedExports: + return getList((node as NamedImportsOrExports).elements); + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + return getList((node as ObjectBindingPattern | ArrayBindingPattern).elements); + } + + function getList(list: NodeArray | undefined): NodeArray | undefined { + return list && rangeContainsStartEnd(getVisualListRange(node, list, sourceFile), start, end) ? list : undefined; } + } - export function getContainingList(node: Node, sourceFile: SourceFile): NodeArray | undefined { - return node.parent && getListByRange(node.getStart(sourceFile), node.getEnd(), node.parent, sourceFile); - } - - function getListByPosition(pos: number, node: Node, sourceFile: SourceFile): NodeArray | undefined { - return node && getListByRange(pos, pos, node, sourceFile); - } - - function getListByRange(start: number, end: number, node: Node, sourceFile: SourceFile): NodeArray | undefined { - switch (node.kind) { - case SyntaxKind.TypeReference: - return getList((node as TypeReferenceNode).typeArguments); - case SyntaxKind.ObjectLiteralExpression: - return getList((node as ObjectLiteralExpression).properties); - case SyntaxKind.ArrayLiteralExpression: - return getList((node as ArrayLiteralExpression).elements); - case SyntaxKind.TypeLiteral: - return getList((node as TypeLiteralNode).members); - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.Constructor: - case SyntaxKind.ConstructorType: - case SyntaxKind.ConstructSignature: - return getList((node as SignatureDeclaration).typeParameters) || getList((node as SignatureDeclaration).parameters); - case SyntaxKind.GetAccessor: - return getList((node as GetAccessorDeclaration).parameters); - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.JSDocTemplateTag: - return getList((node as ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag).typeParameters); - case SyntaxKind.NewExpression: - case SyntaxKind.CallExpression: - return getList((node as CallExpression).typeArguments) || getList((node as CallExpression).arguments); - case SyntaxKind.VariableDeclarationList: - return getList((node as VariableDeclarationList).declarations); - case SyntaxKind.NamedImports: - case SyntaxKind.NamedExports: - return getList((node as NamedImportsOrExports).elements); - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - return getList((node as ObjectBindingPattern | ArrayBindingPattern).elements); + function getVisualListRange(node: Node, list: TextRange, sourceFile: SourceFile): TextRange { + const children = node.getChildren(sourceFile); + for (let i = 1; i < children.length - 1; i++) { + if (children[i].pos === list.pos && children[i].end === list.end) { + return { pos: children[i - 1].end, end: children[i + 1].getStart(sourceFile) }; } + } + return list; + } - function getList(list: NodeArray | undefined): NodeArray | undefined { - return list && rangeContainsStartEnd(getVisualListRange(node, list, sourceFile), start, end) ? list : undefined; - } + function getActualIndentationForListStartLine(list: NodeArray, sourceFile: SourceFile, options: EditorSettings): number { + if (!list) { + return Value.Unknown; } + return findColumnForFirstNonWhitespaceCharacterInLine(sourceFile.getLineAndCharacterOfPosition(list.pos), sourceFile, options); + } - function getVisualListRange(node: Node, list: TextRange, sourceFile: SourceFile): TextRange { - const children = node.getChildren(sourceFile); - for (let i = 1; i < children.length - 1; i++) { - if (children[i].pos === list.pos && children[i].end === list.end) { - return { pos: children[i - 1].end, end: children[i + 1].getStart(sourceFile) }; + function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorSettings, listIndentsChild: boolean): number { + if (node.parent && node.parent.kind === SyntaxKind.VariableDeclarationList) { + // VariableDeclarationList has no wrapping tokens + return Value.Unknown; + } + const containingList = getContainingList(node, sourceFile); + if (containingList) { + const index = containingList.indexOf(node); + if (index !== -1) { + const result = deriveActualIndentationFromList(containingList, index, sourceFile, options); + if (result !== Value.Unknown) { + return result; } } - return list; + return getActualIndentationForListStartLine(containingList, sourceFile, options) + (listIndentsChild ? options.indentSize! : 0); // TODO: GH#18217 } + return Value.Unknown; + } - function getActualIndentationForListStartLine(list: NodeArray, sourceFile: SourceFile, options: EditorSettings): number { - if (!list) { - return Value.Unknown; - } - return findColumnForFirstNonWhitespaceCharacterInLine(sourceFile.getLineAndCharacterOfPosition(list.pos), sourceFile, options); - } + function deriveActualIndentationFromList(list: readonly Node[], index: number, sourceFile: SourceFile, options: EditorSettings): number { + Debug.assert(index >= 0 && index < list.length); + const node = list[index]; - function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorSettings, listIndentsChild: boolean): number { - if (node.parent && node.parent.kind === SyntaxKind.VariableDeclarationList) { - // VariableDeclarationList has no wrapping tokens - return Value.Unknown; + // walk toward the start of the list starting from current node and check if the line is the same for all items. + // if end line for item [i - 1] differs from the start line for item [i] - find column of the first non-whitespace character on the line of item [i] + let lineAndCharacter = getStartLineAndCharacterForNode(node, sourceFile); + for (let i = index - 1; i >= 0; i--) { + if (list[i].kind === SyntaxKind.CommaToken) { + continue; } - const containingList = getContainingList(node, sourceFile); - if (containingList) { - const index = containingList.indexOf(node); - if (index !== -1) { - const result = deriveActualIndentationFromList(containingList, index, sourceFile, options); - if (result !== Value.Unknown) { - return result; - } - } - return getActualIndentationForListStartLine(containingList, sourceFile, options) + (listIndentsChild ? options.indentSize! : 0); // TODO: GH#18217 + // skip list items that ends on the same line with the current list element + const prevEndLine = sourceFile.getLineAndCharacterOfPosition(list[i].end).line; + if (prevEndLine !== lineAndCharacter.line) { + return findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter, sourceFile, options); } - return Value.Unknown; - } - function deriveActualIndentationFromList(list: readonly Node[], index: number, sourceFile: SourceFile, options: EditorSettings): number { - Debug.assert(index >= 0 && index < list.length); - const node = list[index]; + lineAndCharacter = getStartLineAndCharacterForNode(list[i], sourceFile); + } + return Value.Unknown; + } - // walk toward the start of the list starting from current node and check if the line is the same for all items. - // if end line for item [i - 1] differs from the start line for item [i] - find column of the first non-whitespace character on the line of item [i] - let lineAndCharacter = getStartLineAndCharacterForNode(node, sourceFile); - for (let i = index - 1; i >= 0; i--) { - if (list[i].kind === SyntaxKind.CommaToken) { - continue; - } - // skip list items that ends on the same line with the current list element - const prevEndLine = sourceFile.getLineAndCharacterOfPosition(list[i].end).line; - if (prevEndLine !== lineAndCharacter.line) { - return findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter, sourceFile, options); - } + function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: EditorSettings): number { + const lineStart = sourceFile.getPositionOfLineAndCharacter(lineAndCharacter.line, 0); + return findFirstNonWhitespaceColumn(lineStart, lineStart + lineAndCharacter.character, sourceFile, options); + } - lineAndCharacter = getStartLineAndCharacterForNode(list[i], sourceFile); + /** + * Character is the actual index of the character since the beginning of the line. + * Column - position of the character after expanding tabs to spaces. + * "0\t2$" + * value of 'character' for '$' is 3 + * value of 'column' for '$' is 6 (assuming that tab size is 4) + */ + export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings) { + let character = 0; + let column = 0; + for (let pos = startPos; pos < endPos; pos++) { + const ch = sourceFile.text.charCodeAt(pos); + if (!isWhiteSpaceSingleLine(ch)) { + break; + } + + if (ch === CharacterCodes.tab) { + column += options.tabSize! + (column % options.tabSize!); } - return Value.Unknown; + else { + column++; + } + + character++; } + return { column, character }; + } - function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: EditorSettings): number { - const lineStart = sourceFile.getPositionOfLineAndCharacter(lineAndCharacter.line, 0); - return findFirstNonWhitespaceColumn(lineStart, lineStart + lineAndCharacter.character, sourceFile, options); - } - - /** - * Character is the actual index of the character since the beginning of the line. - * Column - position of the character after expanding tabs to spaces. - * "0\t2$" - * value of 'character' for '$' is 3 - * value of 'column' for '$' is 6 (assuming that tab size is 4) - */ - export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings) { - let character = 0; - let column = 0; - for (let pos = startPos; pos < endPos; pos++) { - const ch = sourceFile.text.charCodeAt(pos); - if (!isWhiteSpaceSingleLine(ch)) { - break; - } + export function findFirstNonWhitespaceColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings): number { + return findFirstNonWhitespaceCharacterAndColumn(startPos, endPos, sourceFile, options).column; + } - if (ch === CharacterCodes.tab) { - column += options.tabSize! + (column % options.tabSize!); + export function nodeWillIndentChild(settings: FormatCodeSettings, parent: TextRangeWithKind, child: TextRangeWithKind | undefined, sourceFile: SourceFileLike | undefined, indentByDefault: boolean): boolean { + const childKind = child ? child.kind : SyntaxKind.Unknown; + + switch (parent.kind) { + case SyntaxKind.ExpressionStatement: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.TypeLiteral: + case SyntaxKind.MappedType: + case SyntaxKind.TupleType: + case SyntaxKind.CaseBlock: + case SyntaxKind.DefaultClause: + case SyntaxKind.CaseClause: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.VariableStatement: + case SyntaxKind.ExportAssignment: + case SyntaxKind.ReturnStatement: + case SyntaxKind.ConditionalExpression: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxOpeningFragment: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxExpression: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.Parameter: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.ParenthesizedType: + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.AwaitExpression: + case SyntaxKind.NamedExports: + case SyntaxKind.NamedImports: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.PropertyDeclaration: + return true; + case SyntaxKind.VariableDeclaration: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.BinaryExpression: + if (!settings.indentMultiLineObjectLiteralBeginningOnBlankLine && sourceFile && childKind === SyntaxKind.ObjectLiteralExpression) { // TODO: GH#18217 + return rangeIsOnOneLine(sourceFile, child!); } - else { - column++; + if (parent.kind === SyntaxKind.BinaryExpression && sourceFile && child && childKind === SyntaxKind.JsxElement) { + const parentStartLine = sourceFile.getLineAndCharacterOfPosition(skipTrivia(sourceFile.text, parent.pos)).line; + const childStartLine = sourceFile.getLineAndCharacterOfPosition(skipTrivia(sourceFile.text, child.pos)).line; + return parentStartLine !== childStartLine; } - - character++; - } - return { column, character }; - } - - export function findFirstNonWhitespaceColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings): number { - return findFirstNonWhitespaceCharacterAndColumn(startPos, endPos, sourceFile, options).column; - } - - export function nodeWillIndentChild(settings: FormatCodeSettings, parent: TextRangeWithKind, child: TextRangeWithKind | undefined, sourceFile: SourceFileLike | undefined, indentByDefault: boolean): boolean { - const childKind = child ? child.kind : SyntaxKind.Unknown; - - switch (parent.kind) { - case SyntaxKind.ExpressionStatement: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.TypeLiteral: - case SyntaxKind.MappedType: - case SyntaxKind.TupleType: - case SyntaxKind.CaseBlock: - case SyntaxKind.DefaultClause: - case SyntaxKind.CaseClause: - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.VariableStatement: - case SyntaxKind.ExportAssignment: - case SyntaxKind.ReturnStatement: - case SyntaxKind.ConditionalExpression: - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxOpeningFragment: - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxExpression: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.Parameter: - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.ParenthesizedType: - case SyntaxKind.TaggedTemplateExpression: - case SyntaxKind.AwaitExpression: - case SyntaxKind.NamedExports: - case SyntaxKind.NamedImports: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.PropertyDeclaration: + if (parent.kind !== SyntaxKind.BinaryExpression) { return true; - case SyntaxKind.VariableDeclaration: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.BinaryExpression: - if (!settings.indentMultiLineObjectLiteralBeginningOnBlankLine && sourceFile && childKind === SyntaxKind.ObjectLiteralExpression) { // TODO: GH#18217 - return rangeIsOnOneLine(sourceFile, child!); - } - if (parent.kind === SyntaxKind.BinaryExpression && sourceFile && child && childKind === SyntaxKind.JsxElement) { - const parentStartLine = sourceFile.getLineAndCharacterOfPosition(skipTrivia(sourceFile.text, parent.pos)).line; - const childStartLine = sourceFile.getLineAndCharacterOfPosition(skipTrivia(sourceFile.text, child.pos)).line; - return parentStartLine !== childStartLine; - } - if (parent.kind !== SyntaxKind.BinaryExpression) { - return true; - } - break; - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.IfStatement: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return childKind !== SyntaxKind.Block; - case SyntaxKind.ArrowFunction: - if (sourceFile && childKind === SyntaxKind.ParenthesizedExpression) { - return rangeIsOnOneLine(sourceFile, child!); - } - return childKind !== SyntaxKind.Block; - case SyntaxKind.ExportDeclaration: - return childKind !== SyntaxKind.NamedExports; - case SyntaxKind.ImportDeclaration: - return childKind !== SyntaxKind.ImportClause || - (!!(child as ImportClause).namedBindings && (child as ImportClause).namedBindings!.kind !== SyntaxKind.NamedImports); - case SyntaxKind.JsxElement: - return childKind !== SyntaxKind.JsxClosingElement; - case SyntaxKind.JsxFragment: - return childKind !== SyntaxKind.JsxClosingFragment; - case SyntaxKind.IntersectionType: - case SyntaxKind.UnionType: - if (childKind === SyntaxKind.TypeLiteral || childKind === SyntaxKind.TupleType) { - return false; - } - break; - } - // No explicit rule for given nodes so the result will follow the default value argument - return indentByDefault; - } - - function isControlFlowEndingStatement(kind: SyntaxKind, parent: TextRangeWithKind): boolean { - switch (kind) { - case SyntaxKind.ReturnStatement: - case SyntaxKind.ThrowStatement: - case SyntaxKind.ContinueStatement: - case SyntaxKind.BreakStatement: - return parent.kind !== SyntaxKind.Block; - default: + } + break; + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return childKind !== SyntaxKind.Block; + case SyntaxKind.ArrowFunction: + if (sourceFile && childKind === SyntaxKind.ParenthesizedExpression) { + return rangeIsOnOneLine(sourceFile, child!); + } + return childKind !== SyntaxKind.Block; + case SyntaxKind.ExportDeclaration: + return childKind !== SyntaxKind.NamedExports; + case SyntaxKind.ImportDeclaration: + return childKind !== SyntaxKind.ImportClause || + (!!(child as ImportClause).namedBindings && (child as ImportClause).namedBindings!.kind !== SyntaxKind.NamedImports); + case SyntaxKind.JsxElement: + return childKind !== SyntaxKind.JsxClosingElement; + case SyntaxKind.JsxFragment: + return childKind !== SyntaxKind.JsxClosingFragment; + case SyntaxKind.IntersectionType: + case SyntaxKind.UnionType: + if (childKind === SyntaxKind.TypeLiteral || childKind === SyntaxKind.TupleType) { return false; - } + } + break; } + // No explicit rule for given nodes so the result will follow the default value argument + return indentByDefault; + } - /** - * True when the parent node should indent the given child by an explicit rule. - * @param isNextChild If true, we are judging indent of a hypothetical child *after* this one, not the current child. - */ - export function shouldIndentChildNode(settings: FormatCodeSettings, parent: TextRangeWithKind, child?: Node, sourceFile?: SourceFileLike, isNextChild = false): boolean { - return nodeWillIndentChild(settings, parent, child, sourceFile, /*indentByDefault*/ false) - && !(isNextChild && child && isControlFlowEndingStatement(child.kind, parent)); + function isControlFlowEndingStatement(kind: SyntaxKind, parent: TextRangeWithKind): boolean { + switch (kind) { + case SyntaxKind.ReturnStatement: + case SyntaxKind.ThrowStatement: + case SyntaxKind.ContinueStatement: + case SyntaxKind.BreakStatement: + return parent.kind !== SyntaxKind.Block; + default: + return false; } + } - function rangeIsOnOneLine(sourceFile: SourceFileLike, range: TextRangeWithKind) { - const rangeStart = skipTrivia(sourceFile.text, range.pos); - const startLine = sourceFile.getLineAndCharacterOfPosition(rangeStart).line; - const endLine = sourceFile.getLineAndCharacterOfPosition(range.end).line; - return startLine === endLine; - } + /** + * True when the parent node should indent the given child by an explicit rule. + * @param isNextChild If true, we are judging indent of a hypothetical child *after* this one, not the current child. + */ + export function shouldIndentChildNode(settings: FormatCodeSettings, parent: TextRangeWithKind, child?: Node, sourceFile?: SourceFileLike, isNextChild = false): boolean { + return nodeWillIndentChild(settings, parent, child, sourceFile, /*indentByDefault*/ false) + && !(isNextChild && child && isControlFlowEndingStatement(child.kind, parent)); + } + + function rangeIsOnOneLine(sourceFile: SourceFileLike, range: TextRangeWithKind) { + const rangeStart = skipTrivia(sourceFile.text, range.pos); + const startLine = sourceFile.getLineAndCharacterOfPosition(rangeStart).line; + const endLine = sourceFile.getLineAndCharacterOfPosition(range.end).line; + return startLine === endLine; } } diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index 5120560da4cee..2c2096a86004d 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -1,261 +1,276 @@ -/* @internal */ -namespace ts { - export function getEditsForFileRename( - program: Program, - oldFileOrDirPath: string, - newFileOrDirPath: string, - host: LanguageServiceHost, - formatContext: formatting.FormatContext, - preferences: UserPreferences, - sourceMapper: SourceMapper, - ): readonly FileTextChanges[] { - const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - const oldToNew = getPathUpdater(oldFileOrDirPath, newFileOrDirPath, getCanonicalFileName, sourceMapper); - const newToOld = getPathUpdater(newFileOrDirPath, oldFileOrDirPath, getCanonicalFileName, sourceMapper); - return textChanges.ChangeTracker.with({ host, formatContext, preferences }, changeTracker => { - updateTsconfigFiles(program, changeTracker, oldToNew, oldFileOrDirPath, newFileOrDirPath, host.getCurrentDirectory(), useCaseSensitiveFileNames); - updateImports(program, changeTracker, oldToNew, newToOld, host, getCanonicalFileName); - }); - } +import { + combinePaths, createGetCanonicalFileName, createModuleSpecifierResolutionHost, createRange, Debug, emptyArray, + endsWith, ensurePathIsNonModuleName, Expression, factory, FileTextChanges, find, forEach, formatting, + GetCanonicalFileName, getDirectoryPath, getFileMatcherPatterns, getModeForUsageLocation, getOptionFromName, + getRegexFromPattern, getRelativePathFromDirectory, getRelativePathFromFile, getTsConfigObjectLiteralExpression, + hostUsesCaseSensitiveFileNames, isAmbientModule, isArrayLiteralExpression, isObjectLiteralExpression, + isPropertyAssignment, isSourceFile, isStringLiteral, LanguageServiceHost, last, mapDefined, ModuleResolutionHost, + moduleSpecifiers, normalizePath, Path, pathIsRelative, Program, PropertyAssignment, + ResolvedModuleWithFailedLookupLocations, resolveModuleName, SourceFile, SourceFileLike, SourceMapper, + StringLiteralLike, Symbol, textChanges, TextRange, tryRemoveDirectoryPrefix, UserPreferences, +} from "./_namespaces/ts"; - /** If 'path' refers to an old directory, returns path in the new directory. */ - type PathUpdater = (path: string) => string | undefined; - // exported for tests - export function getPathUpdater(oldFileOrDirPath: string, newFileOrDirPath: string, getCanonicalFileName: GetCanonicalFileName, sourceMapper: SourceMapper | undefined): PathUpdater { - const canonicalOldPath = getCanonicalFileName(oldFileOrDirPath); - return path => { - const originalPath = sourceMapper && sourceMapper.tryGetSourcePosition({ fileName: path, pos: 0 }); - const updatedPath = getUpdatedPath(originalPath ? originalPath.fileName : path); - return originalPath - ? updatedPath === undefined ? undefined : makeCorrespondingRelativeChange(originalPath.fileName, updatedPath, path, getCanonicalFileName) - : updatedPath; - }; +/** @internal */ +export function getEditsForFileRename( + program: Program, + oldFileOrDirPath: string, + newFileOrDirPath: string, + host: LanguageServiceHost, + formatContext: formatting.FormatContext, + preferences: UserPreferences, + sourceMapper: SourceMapper, +): readonly FileTextChanges[] { + const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + const oldToNew = getPathUpdater(oldFileOrDirPath, newFileOrDirPath, getCanonicalFileName, sourceMapper); + const newToOld = getPathUpdater(newFileOrDirPath, oldFileOrDirPath, getCanonicalFileName, sourceMapper); + return textChanges.ChangeTracker.with({ host, formatContext, preferences }, changeTracker => { + updateTsconfigFiles(program, changeTracker, oldToNew, oldFileOrDirPath, newFileOrDirPath, host.getCurrentDirectory(), useCaseSensitiveFileNames); + updateImports(program, changeTracker, oldToNew, newToOld, host, getCanonicalFileName); + }); +} - function getUpdatedPath(pathToUpdate: string): string | undefined { - if (getCanonicalFileName(pathToUpdate) === canonicalOldPath) return newFileOrDirPath; - const suffix = tryRemoveDirectoryPrefix(pathToUpdate, canonicalOldPath, getCanonicalFileName); - return suffix === undefined ? undefined : newFileOrDirPath + "/" + suffix; - } - } +/** + * If 'path' refers to an old directory, returns path in the new directory. + * + * @internal + */ +export type PathUpdater = (path: string) => string | undefined; +// exported for tests +/** @internal */ +export function getPathUpdater(oldFileOrDirPath: string, newFileOrDirPath: string, getCanonicalFileName: GetCanonicalFileName, sourceMapper: SourceMapper | undefined): PathUpdater { + const canonicalOldPath = getCanonicalFileName(oldFileOrDirPath); + return path => { + const originalPath = sourceMapper && sourceMapper.tryGetSourcePosition({ fileName: path, pos: 0 }); + const updatedPath = getUpdatedPath(originalPath ? originalPath.fileName : path); + return originalPath + ? updatedPath === undefined ? undefined : makeCorrespondingRelativeChange(originalPath.fileName, updatedPath, path, getCanonicalFileName) + : updatedPath; + }; - // Relative path from a0 to b0 should be same as relative path from a1 to b1. Returns b1. - function makeCorrespondingRelativeChange(a0: string, b0: string, a1: string, getCanonicalFileName: GetCanonicalFileName): string { - const rel = getRelativePathFromFile(a0, b0, getCanonicalFileName); - return combinePathsSafe(getDirectoryPath(a1), rel); + function getUpdatedPath(pathToUpdate: string): string | undefined { + if (getCanonicalFileName(pathToUpdate) === canonicalOldPath) return newFileOrDirPath; + const suffix = tryRemoveDirectoryPrefix(pathToUpdate, canonicalOldPath, getCanonicalFileName); + return suffix === undefined ? undefined : newFileOrDirPath + "/" + suffix; } +} - function updateTsconfigFiles(program: Program, changeTracker: textChanges.ChangeTracker, oldToNew: PathUpdater, oldFileOrDirPath: string, newFileOrDirPath: string, currentDirectory: string, useCaseSensitiveFileNames: boolean): void { - const { configFile } = program.getCompilerOptions(); - if (!configFile) return; - const configDir = getDirectoryPath(configFile.fileName); +// Relative path from a0 to b0 should be same as relative path from a1 to b1. Returns b1. +function makeCorrespondingRelativeChange(a0: string, b0: string, a1: string, getCanonicalFileName: GetCanonicalFileName): string { + const rel = getRelativePathFromFile(a0, b0, getCanonicalFileName); + return combinePathsSafe(getDirectoryPath(a1), rel); +} - const jsonObjectLiteral = getTsConfigObjectLiteralExpression(configFile); - if (!jsonObjectLiteral) return; +function updateTsconfigFiles(program: Program, changeTracker: textChanges.ChangeTracker, oldToNew: PathUpdater, oldFileOrDirPath: string, newFileOrDirPath: string, currentDirectory: string, useCaseSensitiveFileNames: boolean): void { + const { configFile } = program.getCompilerOptions(); + if (!configFile) return; + const configDir = getDirectoryPath(configFile.fileName); - forEachProperty(jsonObjectLiteral, (property, propertyName) => { - switch (propertyName) { - case "files": - case "include": - case "exclude": { - const foundExactMatch = updatePaths(property); - if (foundExactMatch || propertyName !== "include" || !isArrayLiteralExpression(property.initializer)) return; - const includes = mapDefined(property.initializer.elements, e => isStringLiteral(e) ? e.text : undefined); - if (includes.length === 0) return; - const matchers = getFileMatcherPatterns(configDir, /*excludes*/ [], includes, useCaseSensitiveFileNames, currentDirectory); - // If there isn't some include for this, add a new one. - if (getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(oldFileOrDirPath) && - !getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(newFileOrDirPath)) { - changeTracker.insertNodeAfter(configFile, last(property.initializer.elements), factory.createStringLiteral(relativePath(newFileOrDirPath))); - } - return; + const jsonObjectLiteral = getTsConfigObjectLiteralExpression(configFile); + if (!jsonObjectLiteral) return; + + forEachProperty(jsonObjectLiteral, (property, propertyName) => { + switch (propertyName) { + case "files": + case "include": + case "exclude": { + const foundExactMatch = updatePaths(property); + if (foundExactMatch || propertyName !== "include" || !isArrayLiteralExpression(property.initializer)) return; + const includes = mapDefined(property.initializer.elements, e => isStringLiteral(e) ? e.text : undefined); + if (includes.length === 0) return; + const matchers = getFileMatcherPatterns(configDir, /*excludes*/ [], includes, useCaseSensitiveFileNames, currentDirectory); + // If there isn't some include for this, add a new one. + if (getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(oldFileOrDirPath) && + !getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(newFileOrDirPath)) { + changeTracker.insertNodeAfter(configFile, last(property.initializer.elements), factory.createStringLiteral(relativePath(newFileOrDirPath))); } - case "compilerOptions": - forEachProperty(property.initializer, (property, propertyName) => { - const option = getOptionFromName(propertyName); - if (option && (option.isFilePath || option.type === "list" && option.element.isFilePath)) { - updatePaths(property); - } - else if (propertyName === "paths") { - forEachProperty(property.initializer, (pathsProperty) => { - if (!isArrayLiteralExpression(pathsProperty.initializer)) return; - for (const e of pathsProperty.initializer.elements) { - tryUpdateString(e); - } - }); - } - }); - return; + return; } - }); + case "compilerOptions": + forEachProperty(property.initializer, (property, propertyName) => { + const option = getOptionFromName(propertyName); + if (option && (option.isFilePath || option.type === "list" && option.element.isFilePath)) { + updatePaths(property); + } + else if (propertyName === "paths") { + forEachProperty(property.initializer, (pathsProperty) => { + if (!isArrayLiteralExpression(pathsProperty.initializer)) return; + for (const e of pathsProperty.initializer.elements) { + tryUpdateString(e); + } + }); + } + }); + return; + } + }); - function updatePaths(property: PropertyAssignment): boolean { - const elements = isArrayLiteralExpression(property.initializer) ? property.initializer.elements : [property.initializer]; - let foundExactMatch = false; - for (const element of elements) { - foundExactMatch = tryUpdateString(element) || foundExactMatch; - } - return foundExactMatch; + function updatePaths(property: PropertyAssignment): boolean { + const elements = isArrayLiteralExpression(property.initializer) ? property.initializer.elements : [property.initializer]; + let foundExactMatch = false; + for (const element of elements) { + foundExactMatch = tryUpdateString(element) || foundExactMatch; } + return foundExactMatch; + } - function tryUpdateString(element: Expression): boolean { - if (!isStringLiteral(element)) return false; - const elementFileName = combinePathsSafe(configDir, element.text); + function tryUpdateString(element: Expression): boolean { + if (!isStringLiteral(element)) return false; + const elementFileName = combinePathsSafe(configDir, element.text); - const updated = oldToNew(elementFileName); - if (updated !== undefined) { - changeTracker.replaceRangeWithText(configFile!, createStringRange(element, configFile!), relativePath(updated)); - return true; - } - return false; + const updated = oldToNew(elementFileName); + if (updated !== undefined) { + changeTracker.replaceRangeWithText(configFile!, createStringRange(element, configFile!), relativePath(updated)); + return true; } + return false; + } - function relativePath(path: string): string { - return getRelativePathFromDirectory(configDir, path, /*ignoreCase*/ !useCaseSensitiveFileNames); - } + function relativePath(path: string): string { + return getRelativePathFromDirectory(configDir, path, /*ignoreCase*/ !useCaseSensitiveFileNames); } +} - function updateImports( - program: Program, - changeTracker: textChanges.ChangeTracker, - oldToNew: PathUpdater, - newToOld: PathUpdater, - host: LanguageServiceHost, - getCanonicalFileName: GetCanonicalFileName, - ): void { - const allFiles = program.getSourceFiles(); - for (const sourceFile of allFiles) { - const newFromOld = oldToNew(sourceFile.fileName); - const newImportFromPath = newFromOld ?? sourceFile.fileName; - const newImportFromDirectory = getDirectoryPath(newImportFromPath); +function updateImports( + program: Program, + changeTracker: textChanges.ChangeTracker, + oldToNew: PathUpdater, + newToOld: PathUpdater, + host: LanguageServiceHost, + getCanonicalFileName: GetCanonicalFileName, +): void { + const allFiles = program.getSourceFiles(); + for (const sourceFile of allFiles) { + const newFromOld = oldToNew(sourceFile.fileName); + const newImportFromPath = newFromOld ?? sourceFile.fileName; + const newImportFromDirectory = getDirectoryPath(newImportFromPath); - const oldFromNew: string | undefined = newToOld(sourceFile.fileName); - const oldImportFromPath: string = oldFromNew || sourceFile.fileName; - const oldImportFromDirectory = getDirectoryPath(oldImportFromPath); + const oldFromNew: string | undefined = newToOld(sourceFile.fileName); + const oldImportFromPath: string = oldFromNew || sourceFile.fileName; + const oldImportFromDirectory = getDirectoryPath(oldImportFromPath); - const importingSourceFileMoved = newFromOld !== undefined || oldFromNew !== undefined; + const importingSourceFileMoved = newFromOld !== undefined || oldFromNew !== undefined; - updateImportsWorker(sourceFile, changeTracker, - referenceText => { - if (!pathIsRelative(referenceText)) return undefined; - const oldAbsolute = combinePathsSafe(oldImportFromDirectory, referenceText); - const newAbsolute = oldToNew(oldAbsolute); - return newAbsolute === undefined ? undefined : ensurePathIsNonModuleName(getRelativePathFromDirectory(newImportFromDirectory, newAbsolute, getCanonicalFileName)); - }, - importLiteral => { - const importedModuleSymbol = program.getTypeChecker().getSymbolAtLocation(importLiteral); - // No need to update if it's an ambient module^M - if (importedModuleSymbol?.declarations && importedModuleSymbol.declarations.some(d => isAmbientModule(d))) return undefined; + updateImportsWorker(sourceFile, changeTracker, + referenceText => { + if (!pathIsRelative(referenceText)) return undefined; + const oldAbsolute = combinePathsSafe(oldImportFromDirectory, referenceText); + const newAbsolute = oldToNew(oldAbsolute); + return newAbsolute === undefined ? undefined : ensurePathIsNonModuleName(getRelativePathFromDirectory(newImportFromDirectory, newAbsolute, getCanonicalFileName)); + }, + importLiteral => { + const importedModuleSymbol = program.getTypeChecker().getSymbolAtLocation(importLiteral); + // No need to update if it's an ambient module^M + if (importedModuleSymbol?.declarations && importedModuleSymbol.declarations.some(d => isAmbientModule(d))) return undefined; - const toImport = oldFromNew !== undefined - // If we're at the new location (file was already renamed), need to redo module resolution starting from the old location. - // TODO:GH#18217 - ? getSourceFileToImportFromResolved(importLiteral, resolveModuleName(importLiteral.text, oldImportFromPath, program.getCompilerOptions(), host as ModuleResolutionHost), - oldToNew, allFiles) - : getSourceFileToImport(importedModuleSymbol, importLiteral, sourceFile, program, host, oldToNew); + const toImport = oldFromNew !== undefined + // If we're at the new location (file was already renamed), need to redo module resolution starting from the old location. + // TODO:GH#18217 + ? getSourceFileToImportFromResolved(importLiteral, resolveModuleName(importLiteral.text, oldImportFromPath, program.getCompilerOptions(), host as ModuleResolutionHost), + oldToNew, allFiles) + : getSourceFileToImport(importedModuleSymbol, importLiteral, sourceFile, program, host, oldToNew); - // Need an update if the imported file moved, or the importing file moved and was using a relative path. - return toImport !== undefined && (toImport.updated || (importingSourceFileMoved && pathIsRelative(importLiteral.text))) - ? moduleSpecifiers.updateModuleSpecifier(program.getCompilerOptions(), sourceFile, getCanonicalFileName(newImportFromPath) as Path, toImport.newFileName, createModuleSpecifierResolutionHost(program, host), importLiteral.text) - : undefined; - }); - } + // Need an update if the imported file moved, or the importing file moved and was using a relative path. + return toImport !== undefined && (toImport.updated || (importingSourceFileMoved && pathIsRelative(importLiteral.text))) + ? moduleSpecifiers.updateModuleSpecifier(program.getCompilerOptions(), sourceFile, getCanonicalFileName(newImportFromPath) as Path, toImport.newFileName, createModuleSpecifierResolutionHost(program, host), importLiteral.text) + : undefined; + }); } +} - function combineNormal(pathA: string, pathB: string): string { - return normalizePath(combinePaths(pathA, pathB)); - } - function combinePathsSafe(pathA: string, pathB: string): string { - return ensurePathIsNonModuleName(combineNormal(pathA, pathB)); - } +function combineNormal(pathA: string, pathB: string): string { + return normalizePath(combinePaths(pathA, pathB)); +} +function combinePathsSafe(pathA: string, pathB: string): string { + return ensurePathIsNonModuleName(combineNormal(pathA, pathB)); +} - interface ToImport { - readonly newFileName: string; - /** True if the imported file was renamed. */ - readonly updated: boolean; +interface ToImport { + readonly newFileName: string; + /** True if the imported file was renamed. */ + readonly updated: boolean; +} +function getSourceFileToImport( + importedModuleSymbol: Symbol | undefined, + importLiteral: StringLiteralLike, + importingSourceFile: SourceFile, + program: Program, + host: LanguageServiceHost, + oldToNew: PathUpdater, +): ToImport | undefined { + if (importedModuleSymbol) { + // `find` should succeed because we checked for ambient modules before calling this function. + const oldFileName = find(importedModuleSymbol.declarations, isSourceFile)!.fileName; + const newFileName = oldToNew(oldFileName); + return newFileName === undefined ? { newFileName: oldFileName, updated: false } : { newFileName, updated: true }; } - function getSourceFileToImport( - importedModuleSymbol: Symbol | undefined, - importLiteral: StringLiteralLike, - importingSourceFile: SourceFile, - program: Program, - host: LanguageServiceHost, - oldToNew: PathUpdater, - ): ToImport | undefined { - if (importedModuleSymbol) { - // `find` should succeed because we checked for ambient modules before calling this function. - const oldFileName = find(importedModuleSymbol.declarations, isSourceFile)!.fileName; - const newFileName = oldToNew(oldFileName); - return newFileName === undefined ? { newFileName: oldFileName, updated: false } : { newFileName, updated: true }; - } - else { - const mode = getModeForUsageLocation(importingSourceFile, importLiteral); - const resolved = host.resolveModuleNames - ? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName, mode) - : program.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName, mode); - return getSourceFileToImportFromResolved(importLiteral, resolved, oldToNew, program.getSourceFiles()); - } + else { + const mode = getModeForUsageLocation(importingSourceFile, importLiteral); + const resolved = host.resolveModuleNames + ? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName, mode) + : program.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName, mode); + return getSourceFileToImportFromResolved(importLiteral, resolved, oldToNew, program.getSourceFiles()); } +} - function getSourceFileToImportFromResolved(importLiteral: StringLiteralLike, resolved: ResolvedModuleWithFailedLookupLocations | undefined, oldToNew: PathUpdater, sourceFiles: readonly SourceFile[]): ToImport | undefined { - // Search through all locations looking for a moved file, and only then test already existing files. - // This is because if `a.ts` is compiled to `a.js` and `a.ts` is moved, we don't want to resolve anything to `a.js`, but to `a.ts`'s new location. - if (!resolved) return undefined; - - // First try resolved module - if (resolved.resolvedModule) { - const result = tryChange(resolved.resolvedModule.resolvedFileName); - if (result) return result; - } +function getSourceFileToImportFromResolved(importLiteral: StringLiteralLike, resolved: ResolvedModuleWithFailedLookupLocations | undefined, oldToNew: PathUpdater, sourceFiles: readonly SourceFile[]): ToImport | undefined { + // Search through all locations looking for a moved file, and only then test already existing files. + // This is because if `a.ts` is compiled to `a.js` and `a.ts` is moved, we don't want to resolve anything to `a.js`, but to `a.ts`'s new location. + if (!resolved) return undefined; - // Then failed lookups that are in the list of sources - const result = forEach(resolved.failedLookupLocations, tryChangeWithIgnoringPackageJsonExisting) - // Then failed lookups except package.json since we dont want to touch them (only included ts/js files). - // At this point, the confidence level of this fix being correct is too low to change bare specifiers or absolute paths. - || pathIsRelative(importLiteral.text) && forEach(resolved.failedLookupLocations, tryChangeWithIgnoringPackageJson); + // First try resolved module + if (resolved.resolvedModule) { + const result = tryChange(resolved.resolvedModule.resolvedFileName); if (result) return result; + } - // If nothing changed, then result is resolved module file thats not updated - return resolved.resolvedModule && { newFileName: resolved.resolvedModule.resolvedFileName, updated: false }; + // Then failed lookups that are in the list of sources + const result = forEach(resolved.failedLookupLocations, tryChangeWithIgnoringPackageJsonExisting) + // Then failed lookups except package.json since we dont want to touch them (only included ts/js files). + // At this point, the confidence level of this fix being correct is too low to change bare specifiers or absolute paths. + || pathIsRelative(importLiteral.text) && forEach(resolved.failedLookupLocations, tryChangeWithIgnoringPackageJson); + if (result) return result; - function tryChangeWithIgnoringPackageJsonExisting(oldFileName: string) { - const newFileName = oldToNew(oldFileName); - return newFileName && find(sourceFiles, src => src.fileName === newFileName) - ? tryChangeWithIgnoringPackageJson(oldFileName) : undefined; - } + // If nothing changed, then result is resolved module file thats not updated + return resolved.resolvedModule && { newFileName: resolved.resolvedModule.resolvedFileName, updated: false }; - function tryChangeWithIgnoringPackageJson(oldFileName: string) { - return !endsWith(oldFileName, "/package.json") ? tryChange(oldFileName) : undefined; - } + function tryChangeWithIgnoringPackageJsonExisting(oldFileName: string) { + const newFileName = oldToNew(oldFileName); + return newFileName && find(sourceFiles, src => src.fileName === newFileName) + ? tryChangeWithIgnoringPackageJson(oldFileName) : undefined; + } - function tryChange(oldFileName: string) { - const newFileName = oldToNew(oldFileName); - return newFileName && { newFileName, updated: true }; - } + function tryChangeWithIgnoringPackageJson(oldFileName: string) { + return !endsWith(oldFileName, "/package.json") ? tryChange(oldFileName) : undefined; } - function updateImportsWorker(sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker, updateRef: (refText: string) => string | undefined, updateImport: (importLiteral: StringLiteralLike) => string | undefined) { - for (const ref of sourceFile.referencedFiles || emptyArray) { // TODO: GH#26162 - const updated = updateRef(ref.fileName); - if (updated !== undefined && updated !== sourceFile.text.slice(ref.pos, ref.end)) changeTracker.replaceRangeWithText(sourceFile, ref, updated); - } + function tryChange(oldFileName: string) { + const newFileName = oldToNew(oldFileName); + return newFileName && { newFileName, updated: true }; + } +} - for (const importStringLiteral of sourceFile.imports) { - const updated = updateImport(importStringLiteral); - if (updated !== undefined && updated !== importStringLiteral.text) changeTracker.replaceRangeWithText(sourceFile, createStringRange(importStringLiteral, sourceFile), updated); - } +function updateImportsWorker(sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker, updateRef: (refText: string) => string | undefined, updateImport: (importLiteral: StringLiteralLike) => string | undefined) { + for (const ref of sourceFile.referencedFiles || emptyArray) { // TODO: GH#26162 + const updated = updateRef(ref.fileName); + if (updated !== undefined && updated !== sourceFile.text.slice(ref.pos, ref.end)) changeTracker.replaceRangeWithText(sourceFile, ref, updated); } - function createStringRange(node: StringLiteralLike, sourceFile: SourceFileLike): TextRange { - return createRange(node.getStart(sourceFile) + 1, node.end - 1); + for (const importStringLiteral of sourceFile.imports) { + const updated = updateImport(importStringLiteral); + if (updated !== undefined && updated !== importStringLiteral.text) changeTracker.replaceRangeWithText(sourceFile, createStringRange(importStringLiteral, sourceFile), updated); } +} - function forEachProperty(objectLiteral: Expression, cb: (property: PropertyAssignment, propertyName: string) => void) { - if (!isObjectLiteralExpression(objectLiteral)) return; - for (const property of objectLiteral.properties) { - if (isPropertyAssignment(property) && isStringLiteral(property.name)) { - cb(property, property.name.text); - } +function createStringRange(node: StringLiteralLike, sourceFile: SourceFileLike): TextRange { + return createRange(node.getStart(sourceFile) + 1, node.end - 1); +} + +function forEachProperty(objectLiteral: Expression, cb: (property: PropertyAssignment, propertyName: string) => void) { + if (!isObjectLiteralExpression(objectLiteral)) return; + for (const property of objectLiteral.properties) { + if (isPropertyAssignment(property) && isStringLiteral(property.name)) { + cb(property, property.name.text); } } } diff --git a/src/services/globalThisShim.ts b/src/services/globalThisShim.ts deleted file mode 100644 index 899bb009895c0..0000000000000 --- a/src/services/globalThisShim.ts +++ /dev/null @@ -1,59 +0,0 @@ -// We polyfill `globalThis` here so re can reliably patch the global scope -// in the contexts we want to in the same way across script and module formats - -// https://mathiasbynens.be/notes/globalthis - -// #region The polyfill starts here. -/* eslint-disable no-var */ -/* @internal */ -declare var window: {}; -/* eslint-enable no-var */ -((() => { - if (typeof globalThis === "object") return; - try { - Object.defineProperty(Object.prototype, "__magic__", { - get() { - return this; - }, - configurable: true - }); - //@ts-ignore - __magic__.globalThis = __magic__; - // The previous line should have made `globalThis` globally - // available, but it fails in Internet Explorer 10 and older. - // Detect this failure and fall back. - if (typeof globalThis === "undefined") { - // Assume `window` exists. - //@ts-ignore - window.globalThis = window; - } - //@ts-ignore - delete Object.prototype.__magic__; - } - catch (error) { - // In IE8, Object.defineProperty only works on DOM objects. - // If we hit this code path, assume `window` exists. - //@ts-ignore - window.globalThis = window; - } -})()); -// #endregion The polyfill ends here. - -// if `process` is undefined, we're probably not running in node - patch legacy members onto the global scope -// @ts-ignore -if (typeof process === "undefined" || process.browser) { - /// TODO: this is used by VS, clean this up on both sides of the interface - - //@ts-ignore - globalThis.TypeScript = globalThis.TypeScript || {}; - //@ts-ignore - globalThis.TypeScript.Services = globalThis.TypeScript.Services || {}; - //@ts-ignore - globalThis.TypeScript.Services.TypeScriptServicesFactory = ts.TypeScriptServicesFactory; - - // 'toolsVersion' gets consumed by the managed side, so it's not unused. - // TODO: it should be moved into a namespace though. - - //@ts-ignore - globalThis.toolsVersion = ts.versionMajorMinor; -} \ No newline at end of file diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 0ed8284b5df62..1a8ebd80f4843 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -1,513 +1,540 @@ -/* @internal */ -namespace ts.GoToDefinition { - export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number, searchOtherFilesOnly?: boolean, stopAtAlias?: boolean): readonly DefinitionInfo[] | undefined { - const resolvedRef = getReferenceAtPosition(sourceFile, position, program); - const fileReferenceDefinition = resolvedRef && [getDefinitionInfoForFileReference(resolvedRef.reference.fileName, resolvedRef.fileName, resolvedRef.unverified)] || emptyArray; - if (resolvedRef?.file) { - // If `file` is missing, do a symbol-based lookup as well - return fileReferenceDefinition; - } +import { + AssignmentDeclarationKind, AssignmentExpression, AssignmentOperatorToken, CallLikeExpression, concatenate, + createTextSpan, createTextSpanFromBounds, createTextSpanFromNode, createTextSpanFromRange, Debug, Declaration, + DefinitionInfo, DefinitionInfoAndBoundSpan, emptyArray, every, FileReference, filter, find, FindAllReferences, + findAncestor, first, flatMap, forEach, FunctionLikeDeclaration, getAssignmentDeclarationKind, + getContainingObjectLiteralElement, getDirectoryPath, getEffectiveBaseTypeNode, getInvokedExpression, + getModeForUsageLocation, getNameFromPropertyName, getNameOfDeclaration, getPropertySymbolsFromContextualType, + getTargetLabel, getTextOfPropertyName, getTouchingPropertyName, getTouchingToken, hasEffectiveModifier, + hasInitializer, hasStaticModifier, isAnyImportOrBareOrAccessedRequire, isAssignmentDeclaration, + isAssignmentExpression, isBindingElement, isCallLikeExpression, isCallOrNewExpressionTarget, isClassElement, + isClassExpression, isClassLike, isClassStaticBlockDeclaration, isConstructorDeclaration, isDeclarationFileName, + isExternalModuleNameRelative, isFunctionLike, isFunctionLikeDeclaration, isFunctionTypeNode, isIdentifier, + isImportMeta, isJSDocOverrideTag, isJsxOpeningLikeElement, isJumpStatementTarget, isModuleSpecifierLike, + isNameOfFunctionDeclaration, isNewExpressionTarget, isObjectBindingPattern, isPropertyName, + isRightSideOfPropertyAccess, isStaticModifier, isVariableDeclaration, last, map, mapDefined, ModifierFlags, + moveRangePastModifiers, Node, NodeFlags, Program, resolvePath, ScriptElementKind, SignatureDeclaration, skipAlias, + skipParentheses, skipTrivia, some, SourceFile, Symbol, SymbolDisplay, SymbolFlags, SyntaxKind, + textRangeContainsPositionInclusive, TextSpan, tryCast, tryGetModuleSpecifierFromDeclaration, Type, TypeChecker, + TypeFlags, unescapeLeadingUnderscores, +} from "./_namespaces/ts"; + +/** @internal */ +export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number, searchOtherFilesOnly?: boolean, stopAtAlias?: boolean): readonly DefinitionInfo[] | undefined { + const resolvedRef = getReferenceAtPosition(sourceFile, position, program); + const fileReferenceDefinition = resolvedRef && [getDefinitionInfoForFileReference(resolvedRef.reference.fileName, resolvedRef.fileName, resolvedRef.unverified)] || emptyArray; + if (resolvedRef?.file) { + // If `file` is missing, do a symbol-based lookup as well + return fileReferenceDefinition; + } - const node = getTouchingPropertyName(sourceFile, position); - if (node === sourceFile) { - return undefined; - } + const node = getTouchingPropertyName(sourceFile, position); + if (node === sourceFile) { + return undefined; + } - const { parent } = node; - const typeChecker = program.getTypeChecker(); + const { parent } = node; + const typeChecker = program.getTypeChecker(); - if (node.kind === SyntaxKind.OverrideKeyword || (isIdentifier(node) && isJSDocOverrideTag(parent) && parent.tagName === node)) { - return getDefinitionFromOverriddenMember(typeChecker, node) || emptyArray; - } + if (node.kind === SyntaxKind.OverrideKeyword || (isIdentifier(node) && isJSDocOverrideTag(parent) && parent.tagName === node)) { + return getDefinitionFromOverriddenMember(typeChecker, node) || emptyArray; + } - // Labels - if (isJumpStatementTarget(node)) { - const label = getTargetLabel(node.parent, node.text); - return label ? [createDefinitionInfoFromName(typeChecker, label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217 - } + // Labels + if (isJumpStatementTarget(node)) { + const label = getTargetLabel(node.parent, node.text); + return label ? [createDefinitionInfoFromName(typeChecker, label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217 + } - if (node.kind === SyntaxKind.ReturnKeyword) { - const functionDeclaration = findAncestor(node.parent, n => - isClassStaticBlockDeclaration(n) ? "quit" : isFunctionLikeDeclaration(n)) as FunctionLikeDeclaration | undefined; - return functionDeclaration ? [createDefinitionFromSignatureDeclaration(typeChecker, functionDeclaration)] : undefined; - } + if (node.kind === SyntaxKind.ReturnKeyword) { + const functionDeclaration = findAncestor(node.parent, n => + isClassStaticBlockDeclaration(n) ? "quit" : isFunctionLikeDeclaration(n)) as FunctionLikeDeclaration | undefined; + return functionDeclaration ? [createDefinitionFromSignatureDeclaration(typeChecker, functionDeclaration)] : undefined; + } - if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) { - const classDecl = node.parent.parent; - const { symbol, failedAliasResolution } = getSymbol(classDecl, typeChecker, stopAtAlias); - - const staticBlocks = filter(classDecl.members, isClassStaticBlockDeclaration); - const containerName = symbol ? typeChecker.symbolToString(symbol, classDecl) : ""; - const sourceFile = node.getSourceFile(); - return map(staticBlocks, staticBlock => { - let { pos } = moveRangePastModifiers(staticBlock); - pos = skipTrivia(sourceFile.text, pos); - return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, /*unverified*/ false, failedAliasResolution, { start: pos, length: "static".length }); - }); - } + if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) { + const classDecl = node.parent.parent; + const { symbol, failedAliasResolution } = getSymbol(classDecl, typeChecker, stopAtAlias); + + const staticBlocks = filter(classDecl.members, isClassStaticBlockDeclaration); + const containerName = symbol ? typeChecker.symbolToString(symbol, classDecl) : ""; + const sourceFile = node.getSourceFile(); + return map(staticBlocks, staticBlock => { + let { pos } = moveRangePastModifiers(staticBlock); + pos = skipTrivia(sourceFile.text, pos); + return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, /*unverified*/ false, failedAliasResolution, { start: pos, length: "static".length }); + }); + } - let { symbol, failedAliasResolution } = getSymbol(node, typeChecker, stopAtAlias); - let fallbackNode = node; - - if (searchOtherFilesOnly && failedAliasResolution) { - // We couldn't resolve the specific import, try on the module specifier. - const importDeclaration = forEach([node, ...symbol?.declarations || emptyArray], n => findAncestor(n, isAnyImportOrBareOrAccessedRequire)); - const moduleSpecifier = importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration); - if (moduleSpecifier) { - ({ symbol, failedAliasResolution } = getSymbol(moduleSpecifier, typeChecker, stopAtAlias)); - fallbackNode = moduleSpecifier; - } - } + let { symbol, failedAliasResolution } = getSymbol(node, typeChecker, stopAtAlias); + let fallbackNode = node; - if (!symbol && isModuleSpecifierLike(fallbackNode)) { - // We couldn't resolve the module specifier as an external module, but it could - // be that module resolution succeeded but the target was not a module. - const ref = sourceFile.resolvedModules?.get(fallbackNode.text, getModeForUsageLocation(sourceFile, fallbackNode)); - if (ref) { - return [{ - name: fallbackNode.text, - fileName: ref.resolvedFileName, - containerName: undefined!, - containerKind: undefined!, - kind: ScriptElementKind.scriptElement, - textSpan: createTextSpan(0, 0), - failedAliasResolution, - isAmbient: isDeclarationFileName(ref.resolvedFileName), - unverified: fallbackNode !== node, - }]; - } + if (searchOtherFilesOnly && failedAliasResolution) { + // We couldn't resolve the specific import, try on the module specifier. + const importDeclaration = forEach([node, ...symbol?.declarations || emptyArray], n => findAncestor(n, isAnyImportOrBareOrAccessedRequire)); + const moduleSpecifier = importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration); + if (moduleSpecifier) { + ({ symbol, failedAliasResolution } = getSymbol(moduleSpecifier, typeChecker, stopAtAlias)); + fallbackNode = moduleSpecifier; } + } - // Could not find a symbol e.g. node is string or number keyword, - // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol - if (!symbol) { - return concatenate(fileReferenceDefinition, getDefinitionInfoForIndexSignatures(node, typeChecker)); + if (!symbol && isModuleSpecifierLike(fallbackNode)) { + // We couldn't resolve the module specifier as an external module, but it could + // be that module resolution succeeded but the target was not a module. + const ref = sourceFile.resolvedModules?.get(fallbackNode.text, getModeForUsageLocation(sourceFile, fallbackNode)); + if (ref) { + return [{ + name: fallbackNode.text, + fileName: ref.resolvedFileName, + containerName: undefined!, + containerKind: undefined!, + kind: ScriptElementKind.scriptElement, + textSpan: createTextSpan(0, 0), + failedAliasResolution, + isAmbient: isDeclarationFileName(ref.resolvedFileName), + unverified: fallbackNode !== node, + }]; } + } - if (searchOtherFilesOnly && every(symbol.declarations, d => d.getSourceFile().fileName === sourceFile.fileName)) return undefined; - - const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node); - // Don't go to the component constructor definition for a JSX element, just go to the component definition. - if (calledDeclaration && !(isJsxOpeningLikeElement(node.parent) && isConstructorLike(calledDeclaration))) { - const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration, failedAliasResolution); - // For a function, if this is the original function definition, return just sigInfo. - // If this is the original constructor definition, parent is the class. - if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration))) { - return [sigInfo]; - } - else { - const defs = getDefinitionFromSymbol(typeChecker, symbol, node, failedAliasResolution, calledDeclaration) || emptyArray; - // For a 'super()' call, put the signature first, else put the variable first. - return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo]; - } - } + // Could not find a symbol e.g. node is string or number keyword, + // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol + if (!symbol) { + return concatenate(fileReferenceDefinition, getDefinitionInfoForIndexSignatures(node, typeChecker)); + } - // Because name in short-hand property assignment has two different meanings: property name and property value, - // using go-to-definition at such position should go to the variable declaration of the property value rather than - // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition - // is performed at the location of property access, we would like to go to definition of the property in the short-hand - // assignment. This case and others are handled by the following code. - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration); - const definitions = shorthandSymbol?.declarations ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node, /*unverified*/ false, failedAliasResolution)) : emptyArray; - return concatenate(definitions, getDefinitionFromObjectLiteralElement(typeChecker, node) || emptyArray); - } + if (searchOtherFilesOnly && every(symbol.declarations, d => d.getSourceFile().fileName === sourceFile.fileName)) return undefined; - // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the - // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern - // and return the property declaration for the referenced property. - // For example: - // import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo" - // - // function bar(onfulfilled: (value: T) => void) { //....} - // interface Test { - // pr/*destination*/op1: number - // } - // bar(({pr/*goto*/op1})=>{}); - if (isPropertyName(node) && isBindingElement(parent) && isObjectBindingPattern(parent.parent) && - (node === (parent.propertyName || parent.name))) { - const name = getNameFromPropertyName(node); - const type = typeChecker.getTypeAtLocation(parent.parent); - return name === undefined ? emptyArray : flatMap(type.isUnion() ? type.types : [type], t => { - const prop = t.getProperty(name); - return prop && getDefinitionFromSymbol(typeChecker, prop, node); - }); + const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node); + // Don't go to the component constructor definition for a JSX element, just go to the component definition. + if (calledDeclaration && !(isJsxOpeningLikeElement(node.parent) && isConstructorLike(calledDeclaration))) { + const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration, failedAliasResolution); + // For a function, if this is the original function definition, return just sigInfo. + // If this is the original constructor definition, parent is the class. + if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration))) { + return [sigInfo]; + } + else { + const defs = getDefinitionFromSymbol(typeChecker, symbol, node, failedAliasResolution, calledDeclaration) || emptyArray; + // For a 'super()' call, put the signature first, else put the variable first. + return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo]; } - - return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node, failedAliasResolution)); } - /** - * True if we should not add definitions for both the signature symbol and the definition symbol. - * True for `const |f = |() => 0`, false for `function |f() {} const |g = f;`. - * Also true for any assignment RHS. - */ - function symbolMatchesSignature(s: Symbol, calledDeclaration: SignatureDeclaration) { - return s === calledDeclaration.symbol - || s === calledDeclaration.symbol.parent - || isAssignmentExpression(calledDeclaration.parent) - || (!isCallLikeExpression(calledDeclaration.parent) && s === calledDeclaration.parent.symbol); + // Because name in short-hand property assignment has two different meanings: property name and property value, + // using go-to-definition at such position should go to the variable declaration of the property value rather than + // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition + // is performed at the location of property access, we would like to go to definition of the property in the short-hand + // assignment. This case and others are handled by the following code. + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration); + const definitions = shorthandSymbol?.declarations ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node, /*unverified*/ false, failedAliasResolution)) : emptyArray; + return concatenate(definitions, getDefinitionFromObjectLiteralElement(typeChecker, node) || emptyArray); } - // If the current location we want to find its definition is in an object literal, try to get the contextual type for the - // object literal, lookup the property symbol in the contextual type, and use this for goto-definition. - // For example - // interface Props{ - // /*first*/prop1: number - // prop2: boolean + // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the + // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern + // and return the property declaration for the referenced property. + // For example: + // import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo" + // + // function bar(onfulfilled: (value: T) => void) { //....} + // interface Test { + // pr/*destination*/op1: number // } - // function Foo(arg: Props) {} - // Foo( { pr/*1*/op1: 10, prop2: true }) - function getDefinitionFromObjectLiteralElement(typeChecker: TypeChecker, node: Node) { - const element = getContainingObjectLiteralElement(node); - if (element) { - const contextualType = element && typeChecker.getContextualType(element.parent); - if (contextualType) { - return flatMap(getPropertySymbolsFromContextualType(element, typeChecker, contextualType, /*unionSymbolOk*/ false), propertySymbol => - getDefinitionFromSymbol(typeChecker, propertySymbol, node)); - } + // bar(({pr/*goto*/op1})=>{}); + if (isPropertyName(node) && isBindingElement(parent) && isObjectBindingPattern(parent.parent) && + (node === (parent.propertyName || parent.name))) { + const name = getNameFromPropertyName(node); + const type = typeChecker.getTypeAtLocation(parent.parent); + return name === undefined ? emptyArray : flatMap(type.isUnion() ? type.types : [type], t => { + const prop = t.getProperty(name); + return prop && getDefinitionFromSymbol(typeChecker, prop, node); + }); + } + + return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node, failedAliasResolution)); +} + +/** + * True if we should not add definitions for both the signature symbol and the definition symbol. + * True for `const |f = |() => 0`, false for `function |f() {} const |g = f;`. + * Also true for any assignment RHS. + */ +function symbolMatchesSignature(s: Symbol, calledDeclaration: SignatureDeclaration) { + return s === calledDeclaration.symbol + || s === calledDeclaration.symbol.parent + || isAssignmentExpression(calledDeclaration.parent) + || (!isCallLikeExpression(calledDeclaration.parent) && s === calledDeclaration.parent.symbol); +} + +// If the current location we want to find its definition is in an object literal, try to get the contextual type for the +// object literal, lookup the property symbol in the contextual type, and use this for goto-definition. +// For example +// interface Props{ +// /*first*/prop1: number +// prop2: boolean +// } +// function Foo(arg: Props) {} +// Foo( { pr/*1*/op1: 10, prop2: true }) +function getDefinitionFromObjectLiteralElement(typeChecker: TypeChecker, node: Node) { + const element = getContainingObjectLiteralElement(node); + if (element) { + const contextualType = element && typeChecker.getContextualType(element.parent); + if (contextualType) { + return flatMap(getPropertySymbolsFromContextualType(element, typeChecker, contextualType, /*unionSymbolOk*/ false), propertySymbol => + getDefinitionFromSymbol(typeChecker, propertySymbol, node)); } } +} - function getDefinitionFromOverriddenMember(typeChecker: TypeChecker, node: Node) { - const classElement = findAncestor(node, isClassElement); - if (!(classElement && classElement.name)) return; +function getDefinitionFromOverriddenMember(typeChecker: TypeChecker, node: Node) { + const classElement = findAncestor(node, isClassElement); + if (!(classElement && classElement.name)) return; - const baseDeclaration = findAncestor(classElement, isClassLike); - if (!baseDeclaration) return; + const baseDeclaration = findAncestor(classElement, isClassLike); + if (!baseDeclaration) return; - const baseTypeNode = getEffectiveBaseTypeNode(baseDeclaration); - if (!baseTypeNode) return; - const expression = skipParentheses(baseTypeNode.expression); - const base = isClassExpression(expression) ? expression.symbol : typeChecker.getSymbolAtLocation(expression); - if (!base) return; + const baseTypeNode = getEffectiveBaseTypeNode(baseDeclaration); + if (!baseTypeNode) return; + const expression = skipParentheses(baseTypeNode.expression); + const base = isClassExpression(expression) ? expression.symbol : typeChecker.getSymbolAtLocation(expression); + if (!base) return; - const name = unescapeLeadingUnderscores(getTextOfPropertyName(classElement.name)); - const symbol = hasStaticModifier(classElement) - ? typeChecker.getPropertyOfType(typeChecker.getTypeOfSymbol(base), name) - : typeChecker.getPropertyOfType(typeChecker.getDeclaredTypeOfSymbol(base), name); - if (!symbol) return; + const name = unescapeLeadingUnderscores(getTextOfPropertyName(classElement.name)); + const symbol = hasStaticModifier(classElement) + ? typeChecker.getPropertyOfType(typeChecker.getTypeOfSymbol(base), name) + : typeChecker.getPropertyOfType(typeChecker.getDeclaredTypeOfSymbol(base), name); + if (!symbol) return; - return getDefinitionFromSymbol(typeChecker, symbol, node); + return getDefinitionFromSymbol(typeChecker, symbol, node); +} + +/** @internal */ +export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { reference: FileReference, fileName: string, unverified: boolean, file?: SourceFile } | undefined { + const referencePath = findReferenceInPosition(sourceFile.referencedFiles, position); + if (referencePath) { + const file = program.getSourceFileFromReference(sourceFile, referencePath); + return file && { reference: referencePath, fileName: file.fileName, file, unverified: false }; } - export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { reference: FileReference, fileName: string, unverified: boolean, file?: SourceFile } | undefined { - const referencePath = findReferenceInPosition(sourceFile.referencedFiles, position); - if (referencePath) { - const file = program.getSourceFileFromReference(sourceFile, referencePath); - return file && { reference: referencePath, fileName: file.fileName, file, unverified: false }; - } + const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position); + if (typeReferenceDirective) { + const reference = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName, typeReferenceDirective.resolutionMode || sourceFile.impliedNodeFormat); + const file = reference && program.getSourceFile(reference.resolvedFileName!); // TODO:GH#18217 + return file && { reference: typeReferenceDirective, fileName: file.fileName, file, unverified: false }; + } - const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position); - if (typeReferenceDirective) { - const reference = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName, typeReferenceDirective.resolutionMode || sourceFile.impliedNodeFormat); - const file = reference && program.getSourceFile(reference.resolvedFileName!); // TODO:GH#18217 - return file && { reference: typeReferenceDirective, fileName: file.fileName, file, unverified: false }; - } + const libReferenceDirective = findReferenceInPosition(sourceFile.libReferenceDirectives, position); + if (libReferenceDirective) { + const file = program.getLibFileFromReference(libReferenceDirective); + return file && { reference: libReferenceDirective, fileName: file.fileName, file, unverified: false }; + } - const libReferenceDirective = findReferenceInPosition(sourceFile.libReferenceDirectives, position); - if (libReferenceDirective) { - const file = program.getLibFileFromReference(libReferenceDirective); - return file && { reference: libReferenceDirective, fileName: file.fileName, file, unverified: false }; + if (sourceFile.resolvedModules?.size()) { + const node = getTouchingToken(sourceFile, position); + if (isModuleSpecifierLike(node) && isExternalModuleNameRelative(node.text) && sourceFile.resolvedModules.has(node.text, getModeForUsageLocation(sourceFile, node))) { + const verifiedFileName = sourceFile.resolvedModules.get(node.text, getModeForUsageLocation(sourceFile, node))?.resolvedFileName; + const fileName = verifiedFileName || resolvePath(getDirectoryPath(sourceFile.fileName), node.text); + return { + file: program.getSourceFile(fileName), + fileName, + reference: { + pos: node.getStart(), + end: node.getEnd(), + fileName: node.text + }, + unverified: !verifiedFileName, + }; } + } - if (sourceFile.resolvedModules?.size()) { - const node = getTouchingToken(sourceFile, position); - if (isModuleSpecifierLike(node) && isExternalModuleNameRelative(node.text) && sourceFile.resolvedModules.has(node.text, getModeForUsageLocation(sourceFile, node))) { - const verifiedFileName = sourceFile.resolvedModules.get(node.text, getModeForUsageLocation(sourceFile, node))?.resolvedFileName; - const fileName = verifiedFileName || resolvePath(getDirectoryPath(sourceFile.fileName), node.text); - return { - file: program.getSourceFile(fileName), - fileName, - reference: { - pos: node.getStart(), - end: node.getEnd(), - fileName: node.text - }, - unverified: !verifiedFileName, - }; - } - } + return undefined; +} +/// Goto type +/** @internal */ +export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined { + const node = getTouchingPropertyName(sourceFile, position); + if (node === sourceFile) { return undefined; } - /// Goto type - export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined { - const node = getTouchingPropertyName(sourceFile, position); - if (node === sourceFile) { - return undefined; - } + if (isImportMeta(node.parent) && node.parent.name === node) { + return definitionFromType(typeChecker.getTypeAtLocation(node.parent), typeChecker, node.parent, /*failedAliasResolution*/ false); + } - if (isImportMeta(node.parent) && node.parent.name === node) { - return definitionFromType(typeChecker.getTypeAtLocation(node.parent), typeChecker, node.parent, /*failedAliasResolution*/ false); - } + const { symbol, failedAliasResolution } = getSymbol(node, typeChecker, /*stopAtAlias*/ false); + if (!symbol) return undefined; + + const typeAtLocation = typeChecker.getTypeOfSymbolAtLocation(symbol, node); + const returnType = tryGetReturnTypeOfFunction(symbol, typeAtLocation, typeChecker); + const fromReturnType = returnType && definitionFromType(returnType, typeChecker, node, failedAliasResolution); + // If a function returns 'void' or some other type with no definition, just return the function definition. + const typeDefinitions = fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node, failedAliasResolution); + return typeDefinitions.length ? typeDefinitions + : !(symbol.flags & SymbolFlags.Value) && symbol.flags & SymbolFlags.Type ? getDefinitionFromSymbol(typeChecker, skipAlias(symbol, typeChecker), node, failedAliasResolution) + : undefined; +} - const { symbol, failedAliasResolution } = getSymbol(node, typeChecker, /*stopAtAlias*/ false); - if (!symbol) return undefined; +function definitionFromType(type: Type, checker: TypeChecker, node: Node, failedAliasResolution: boolean | undefined): readonly DefinitionInfo[] { + return flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t => + t.symbol && getDefinitionFromSymbol(checker, t.symbol, node, failedAliasResolution)); +} - const typeAtLocation = typeChecker.getTypeOfSymbolAtLocation(symbol, node); - const returnType = tryGetReturnTypeOfFunction(symbol, typeAtLocation, typeChecker); - const fromReturnType = returnType && definitionFromType(returnType, typeChecker, node, failedAliasResolution); - // If a function returns 'void' or some other type with no definition, just return the function definition. - const typeDefinitions = fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node, failedAliasResolution); - return typeDefinitions.length ? typeDefinitions - : !(symbol.flags & SymbolFlags.Value) && symbol.flags & SymbolFlags.Type ? getDefinitionFromSymbol(typeChecker, skipAlias(symbol, typeChecker), node, failedAliasResolution) - : undefined; +function tryGetReturnTypeOfFunction(symbol: Symbol, type: Type, checker: TypeChecker): Type | undefined { + // If the type is just a function's inferred type, + // go-to-type should go to the return type instead, since go-to-definition takes you to the function anyway. + if (type.symbol === symbol || + // At `const f = () => {}`, the symbol is `f` and the type symbol is at `() => {}` + symbol.valueDeclaration && type.symbol && isVariableDeclaration(symbol.valueDeclaration) && symbol.valueDeclaration.initializer === type.symbol.valueDeclaration as Node) { + const sigs = type.getCallSignatures(); + if (sigs.length === 1) return checker.getReturnTypeOfSignature(first(sigs)); } + return undefined; +} - function definitionFromType(type: Type, checker: TypeChecker, node: Node, failedAliasResolution: boolean | undefined): readonly DefinitionInfo[] { - return flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t => - t.symbol && getDefinitionFromSymbol(checker, t.symbol, node, failedAliasResolution)); - } +/** @internal */ +export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number): DefinitionInfoAndBoundSpan | undefined { + const definitions = getDefinitionAtPosition(program, sourceFile, position); - function tryGetReturnTypeOfFunction(symbol: Symbol, type: Type, checker: TypeChecker): Type | undefined { - // If the type is just a function's inferred type, - // go-to-type should go to the return type instead, since go-to-definition takes you to the function anyway. - if (type.symbol === symbol || - // At `const f = () => {}`, the symbol is `f` and the type symbol is at `() => {}` - symbol.valueDeclaration && type.symbol && isVariableDeclaration(symbol.valueDeclaration) && symbol.valueDeclaration.initializer === type.symbol.valueDeclaration as Node) { - const sigs = type.getCallSignatures(); - if (sigs.length === 1) return checker.getReturnTypeOfSignature(first(sigs)); - } + if (!definitions || definitions.length === 0) { return undefined; } - export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number): DefinitionInfoAndBoundSpan | undefined { - const definitions = getDefinitionAtPosition(program, sourceFile, position); + // Check if position is on triple slash reference. + const comment = findReferenceInPosition(sourceFile.referencedFiles, position) || + findReferenceInPosition(sourceFile.typeReferenceDirectives, position) || + findReferenceInPosition(sourceFile.libReferenceDirectives, position); - if (!definitions || definitions.length === 0) { - return undefined; - } - - // Check if position is on triple slash reference. - const comment = findReferenceInPosition(sourceFile.referencedFiles, position) || - findReferenceInPosition(sourceFile.typeReferenceDirectives, position) || - findReferenceInPosition(sourceFile.libReferenceDirectives, position); - - if (comment) { - return { definitions, textSpan: createTextSpanFromRange(comment) }; - } + if (comment) { + return { definitions, textSpan: createTextSpanFromRange(comment) }; + } - const node = getTouchingPropertyName(sourceFile, position); - const textSpan = createTextSpan(node.getStart(), node.getWidth()); + const node = getTouchingPropertyName(sourceFile, position); + const textSpan = createTextSpan(node.getStart(), node.getWidth()); - return { definitions, textSpan }; - } + return { definitions, textSpan }; +} - // At 'x.foo', see if the type of 'x' has an index signature, and if so find its declarations. - function getDefinitionInfoForIndexSignatures(node: Node, checker: TypeChecker): DefinitionInfo[] | undefined { - return mapDefined(checker.getIndexInfosAtLocation(node), info => info.declaration && createDefinitionFromSignatureDeclaration(checker, info.declaration)); - } +// At 'x.foo', see if the type of 'x' has an index signature, and if so find its declarations. +function getDefinitionInfoForIndexSignatures(node: Node, checker: TypeChecker): DefinitionInfo[] | undefined { + return mapDefined(checker.getIndexInfosAtLocation(node), info => info.declaration && createDefinitionFromSignatureDeclaration(checker, info.declaration)); +} - function getSymbol(node: Node, checker: TypeChecker, stopAtAlias: boolean | undefined) { - const symbol = checker.getSymbolAtLocation(node); - // If this is an alias, and the request came at the declaration location - // get the aliased symbol instead. This allows for goto def on an import e.g. - // import {A, B} from "mod"; - // to jump to the implementation directly. - let failedAliasResolution = false; - if (symbol?.declarations && symbol.flags & SymbolFlags.Alias && !stopAtAlias && shouldSkipAlias(node, symbol.declarations[0])) { - const aliased = checker.getAliasedSymbol(symbol); - if (aliased.declarations) { - return { symbol: aliased }; - } - else { - failedAliasResolution = true; - } +function getSymbol(node: Node, checker: TypeChecker, stopAtAlias: boolean | undefined) { + const symbol = checker.getSymbolAtLocation(node); + // If this is an alias, and the request came at the declaration location + // get the aliased symbol instead. This allows for goto def on an import e.g. + // import {A, B} from "mod"; + // to jump to the implementation directly. + let failedAliasResolution = false; + if (symbol?.declarations && symbol.flags & SymbolFlags.Alias && !stopAtAlias && shouldSkipAlias(node, symbol.declarations[0])) { + const aliased = checker.getAliasedSymbol(symbol); + if (aliased.declarations) { + return { symbol: aliased }; + } + else { + failedAliasResolution = true; } - return { symbol, failedAliasResolution }; } + return { symbol, failedAliasResolution }; +} - // Go to the original declaration for cases: - // - // (1) when the aliased symbol was declared in the location(parent). - // (2) when the aliased symbol is originating from an import. - // - function shouldSkipAlias(node: Node, declaration: Node): boolean { - if (node.kind !== SyntaxKind.Identifier) { - return false; - } - if (node.parent === declaration) { - return true; - } - if (declaration.kind === SyntaxKind.NamespaceImport) { - return false; - } +// Go to the original declaration for cases: +// +// (1) when the aliased symbol was declared in the location(parent). +// (2) when the aliased symbol is originating from an import. +// +function shouldSkipAlias(node: Node, declaration: Node): boolean { + if (node.kind !== SyntaxKind.Identifier) { + return false; + } + if (node.parent === declaration) { return true; } + if (declaration.kind === SyntaxKind.NamespaceImport) { + return false; + } + return true; +} - /** - * ```ts - * function f() {} - * f.foo = 0; - * ``` - * - * Here, `f` has two declarations: the function declaration, and the identifier in the next line. - * The latter is a declaration for `f` because it gives `f` the `SymbolFlags.Namespace` meaning so - * it can contain `foo`. However, that declaration is pretty uninteresting and not intuitively a - * "definition" for `f`. Ideally, the question we'd like to answer is "what SymbolFlags does this - * declaration contribute to the symbol for `f`?" If the answer is just `Namespace` and the - * declaration looks like an assignment, that declaration is in no sense a definition for `f`. - * But that information is totally lost during binding and/or symbol merging, so we need to do - * our best to reconstruct it or use other heuristics. This function (and the logic around its - * calling) covers our tests but feels like a hack, and it would be great if someone could come - * up with a more precise definition of what counts as a definition. - */ - function isExpandoDeclaration(node: Declaration): boolean { - if (!isAssignmentDeclaration(node)) return false; - const containingAssignment = findAncestor(node, p => { - if (isAssignmentExpression(p)) return true; - if (!isAssignmentDeclaration(p as Declaration)) return "quit"; - return false; - }) as AssignmentExpression | undefined; - return !!containingAssignment && getAssignmentDeclarationKind(containingAssignment) === AssignmentDeclarationKind.Property; - } - - function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, failedAliasResolution?: boolean, excludeDeclaration?: Node): DefinitionInfo[] | undefined { - const filteredDeclarations = filter(symbol.declarations, d => d !== excludeDeclaration); - const withoutExpandos = filter(filteredDeclarations, d => !isExpandoDeclaration(d)); - const results = some(withoutExpandos) ? withoutExpandos : filteredDeclarations; - return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(results, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node, /*unverified*/ false, failedAliasResolution)); - - function getConstructSignatureDefinition(): DefinitionInfo[] | undefined { - // Applicable only if we are in a new expression, or we are on a constructor declaration - // and in either case the symbol has a construct signature definition, i.e. class - if (symbol.flags & SymbolFlags.Class && !(symbol.flags & (SymbolFlags.Function | SymbolFlags.Variable)) && (isNewExpressionTarget(node) || node.kind === SyntaxKind.ConstructorKeyword)) { - const cls = find(filteredDeclarations, isClassLike) || Debug.fail("Expected declaration to have at least one class-like declaration"); - return getSignatureDefinition(cls.members, /*selectConstructors*/ true); - } - } +/** + * ```ts + * function f() {} + * f.foo = 0; + * ``` + * + * Here, `f` has two declarations: the function declaration, and the identifier in the next line. + * The latter is a declaration for `f` because it gives `f` the `SymbolFlags.Namespace` meaning so + * it can contain `foo`. However, that declaration is pretty uninteresting and not intuitively a + * "definition" for `f`. Ideally, the question we'd like to answer is "what SymbolFlags does this + * declaration contribute to the symbol for `f`?" If the answer is just `Namespace` and the + * declaration looks like an assignment, that declaration is in no sense a definition for `f`. + * But that information is totally lost during binding and/or symbol merging, so we need to do + * our best to reconstruct it or use other heuristics. This function (and the logic around its + * calling) covers our tests but feels like a hack, and it would be great if someone could come + * up with a more precise definition of what counts as a definition. + */ +function isExpandoDeclaration(node: Declaration): boolean { + if (!isAssignmentDeclaration(node)) return false; + const containingAssignment = findAncestor(node, p => { + if (isAssignmentExpression(p)) return true; + if (!isAssignmentDeclaration(p as Declaration)) return "quit"; + return false; + }) as AssignmentExpression | undefined; + return !!containingAssignment && getAssignmentDeclarationKind(containingAssignment) === AssignmentDeclarationKind.Property; +} - function getCallSignatureDefinition(): DefinitionInfo[] | undefined { - return isCallOrNewExpressionTarget(node) || isNameOfFunctionDeclaration(node) - ? getSignatureDefinition(filteredDeclarations, /*selectConstructors*/ false) - : undefined; - } +function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, failedAliasResolution?: boolean, excludeDeclaration?: Node): DefinitionInfo[] | undefined { + const filteredDeclarations = filter(symbol.declarations, d => d !== excludeDeclaration); + const withoutExpandos = filter(filteredDeclarations, d => !isExpandoDeclaration(d)); + const results = some(withoutExpandos) ? withoutExpandos : filteredDeclarations; + return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(results, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node, /*unverified*/ false, failedAliasResolution)); - function getSignatureDefinition(signatureDeclarations: readonly Declaration[] | undefined, selectConstructors: boolean): DefinitionInfo[] | undefined { - if (!signatureDeclarations) { - return undefined; - } - const declarations = signatureDeclarations.filter(selectConstructors ? isConstructorDeclaration : isFunctionLike); - const declarationsWithBody = declarations.filter(d => !!(d as FunctionLikeDeclaration).body); - - // declarations defined on the global scope can be defined on multiple files. Get all of them. - return declarations.length - ? declarationsWithBody.length !== 0 - ? declarationsWithBody.map(x => createDefinitionInfo(x, typeChecker, symbol, node)) - : [createDefinitionInfo(last(declarations), typeChecker, symbol, node, /*unverified*/ false, failedAliasResolution)] - : undefined; + function getConstructSignatureDefinition(): DefinitionInfo[] | undefined { + // Applicable only if we are in a new expression, or we are on a constructor declaration + // and in either case the symbol has a construct signature definition, i.e. class + if (symbol.flags & SymbolFlags.Class && !(symbol.flags & (SymbolFlags.Function | SymbolFlags.Variable)) && (isNewExpressionTarget(node) || node.kind === SyntaxKind.ConstructorKeyword)) { + const cls = find(filteredDeclarations, isClassLike) || Debug.fail("Expected declaration to have at least one class-like declaration"); + return getSignatureDefinition(cls.members, /*selectConstructors*/ true); } } - /** Creates a DefinitionInfo from a Declaration, using the declaration's name if possible. */ - export function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, unverified?: boolean, failedAliasResolution?: boolean): DefinitionInfo { - const symbolName = checker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol - const symbolKind = SymbolDisplay.getSymbolKind(checker, symbol, node); - const containerName = symbol.parent ? checker.symbolToString(symbol.parent, node) : ""; - return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName, unverified, failedAliasResolution); + function getCallSignatureDefinition(): DefinitionInfo[] | undefined { + return isCallOrNewExpressionTarget(node) || isNameOfFunctionDeclaration(node) + ? getSignatureDefinition(filteredDeclarations, /*selectConstructors*/ false) + : undefined; } - /** Creates a DefinitionInfo directly from the name of a declaration. */ - function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, unverified?: boolean, failedAliasResolution?: boolean, textSpan?: TextSpan): DefinitionInfo { - const sourceFile = declaration.getSourceFile(); - if (!textSpan) { - const name = getNameOfDeclaration(declaration) || declaration; - textSpan = createTextSpanFromNode(name, sourceFile); - } - return { - fileName: sourceFile.fileName, - textSpan, - kind: symbolKind, - name: symbolName, - containerKind: undefined!, // TODO: GH#18217 - containerName, - ...FindAllReferences.toContextSpan( - textSpan, - sourceFile, - FindAllReferences.getContextNode(declaration) - ), - isLocal: !isDefinitionVisible(checker, declaration), - isAmbient: !!(declaration.flags & NodeFlags.Ambient), - unverified, - failedAliasResolution, - }; - } - - function isDefinitionVisible(checker: TypeChecker, declaration: Declaration): boolean { - if (checker.isDeclarationVisible(declaration)) return true; - if (!declaration.parent) return false; - - // Variable initializers are visible if variable is visible - if (hasInitializer(declaration.parent) && declaration.parent.initializer === declaration) return isDefinitionVisible(checker, declaration.parent as Declaration); - - // Handle some exceptions here like arrow function, members of class and object literal expression which are technically not visible but we want the definition to be determined by its parent - switch (declaration.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - // Private/protected properties/methods are not visible - if (hasEffectiveModifier(declaration, ModifierFlags.Private)) return false; - // Public properties/methods are visible if its parents are visible, so: - // falls through - - case SyntaxKind.Constructor: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ClassExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionExpression: - return isDefinitionVisible(checker, declaration.parent as Declaration); - default: - return false; + function getSignatureDefinition(signatureDeclarations: readonly Declaration[] | undefined, selectConstructors: boolean): DefinitionInfo[] | undefined { + if (!signatureDeclarations) { + return undefined; } - } + const declarations = signatureDeclarations.filter(selectConstructors ? isConstructorDeclaration : isFunctionLike); + const declarationsWithBody = declarations.filter(d => !!(d as FunctionLikeDeclaration).body); - function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration, failedAliasResolution?: boolean): DefinitionInfo { - return createDefinitionInfo(decl, typeChecker, decl.symbol, decl, /*unverified*/ false, failedAliasResolution); + // declarations defined on the global scope can be defined on multiple files. Get all of them. + return declarations.length + ? declarationsWithBody.length !== 0 + ? declarationsWithBody.map(x => createDefinitionInfo(x, typeChecker, symbol, node)) + : [createDefinitionInfo(last(declarations), typeChecker, symbol, node, /*unverified*/ false, failedAliasResolution)] + : undefined; } +} - export function findReferenceInPosition(refs: readonly FileReference[], pos: number): FileReference | undefined { - return find(refs, ref => textRangeContainsPositionInclusive(ref, pos)); - } +/** + * Creates a DefinitionInfo from a Declaration, using the declaration's name if possible. + * + * @internal + */ +export function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, unverified?: boolean, failedAliasResolution?: boolean): DefinitionInfo { + const symbolName = checker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol + const symbolKind = SymbolDisplay.getSymbolKind(checker, symbol, node); + const containerName = symbol.parent ? checker.symbolToString(symbol.parent, node) : ""; + return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName, unverified, failedAliasResolution); +} - function getDefinitionInfoForFileReference(name: string, targetFileName: string, unverified: boolean): DefinitionInfo { - return { - fileName: targetFileName, - textSpan: createTextSpanFromBounds(0, 0), - kind: ScriptElementKind.scriptElement, - name, - containerName: undefined!, - containerKind: undefined!, // TODO: GH#18217 - unverified, - }; +/** Creates a DefinitionInfo directly from the name of a declaration. */ +function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, unverified?: boolean, failedAliasResolution?: boolean, textSpan?: TextSpan): DefinitionInfo { + const sourceFile = declaration.getSourceFile(); + if (!textSpan) { + const name = getNameOfDeclaration(declaration) || declaration; + textSpan = createTextSpanFromNode(name, sourceFile); } + return { + fileName: sourceFile.fileName, + textSpan, + kind: symbolKind, + name: symbolName, + containerKind: undefined!, // TODO: GH#18217 + containerName, + ...FindAllReferences.toContextSpan( + textSpan, + sourceFile, + FindAllReferences.getContextNode(declaration) + ), + isLocal: !isDefinitionVisible(checker, declaration), + isAmbient: !!(declaration.flags & NodeFlags.Ambient), + unverified, + failedAliasResolution, + }; +} - /** Returns a CallLikeExpression where `node` is the target being invoked. */ - function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined { - const target = findAncestor(node, n => !isRightSideOfPropertyAccess(n)); - const callLike = target?.parent; - return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target ? callLike : undefined; +function isDefinitionVisible(checker: TypeChecker, declaration: Declaration): boolean { + if (checker.isDeclarationVisible(declaration)) return true; + if (!declaration.parent) return false; + + // Variable initializers are visible if variable is visible + if (hasInitializer(declaration.parent) && declaration.parent.initializer === declaration) return isDefinitionVisible(checker, declaration.parent as Declaration); + + // Handle some exceptions here like arrow function, members of class and object literal expression which are technically not visible but we want the definition to be determined by its parent + switch (declaration.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + // Private/protected properties/methods are not visible + if (hasEffectiveModifier(declaration, ModifierFlags.Private)) return false; + // Public properties/methods are visible if its parents are visible, so: + // falls through + + case SyntaxKind.Constructor: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ClassExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + return isDefinitionVisible(checker, declaration.parent as Declaration); + default: + return false; } +} - function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined { - const callLike = getAncestorCallLikeExpression(node); - const signature = callLike && typeChecker.getResolvedSignature(callLike); - // Don't go to a function type, go to the value having that type. - return tryCast(signature && signature.declaration, (d): d is SignatureDeclaration => isFunctionLike(d) && !isFunctionTypeNode(d)); - } +function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration, failedAliasResolution?: boolean): DefinitionInfo { + return createDefinitionInfo(decl, typeChecker, decl.symbol, decl, /*unverified*/ false, failedAliasResolution); +} - function isConstructorLike(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.Constructor: - case SyntaxKind.ConstructorType: - case SyntaxKind.ConstructSignature: - return true; - default: - return false; - } +/** @internal */ +export function findReferenceInPosition(refs: readonly FileReference[], pos: number): FileReference | undefined { + return find(refs, ref => textRangeContainsPositionInclusive(ref, pos)); +} + +function getDefinitionInfoForFileReference(name: string, targetFileName: string, unverified: boolean): DefinitionInfo { + return { + fileName: targetFileName, + textSpan: createTextSpanFromBounds(0, 0), + kind: ScriptElementKind.scriptElement, + name, + containerName: undefined!, + containerKind: undefined!, // TODO: GH#18217 + unverified, + }; +} + +/** Returns a CallLikeExpression where `node` is the target being invoked. */ +function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined { + const target = findAncestor(node, n => !isRightSideOfPropertyAccess(n)); + const callLike = target?.parent; + return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target ? callLike : undefined; +} + +function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined { + const callLike = getAncestorCallLikeExpression(node); + const signature = callLike && typeChecker.getResolvedSignature(callLike); + // Don't go to a function type, go to the value having that type. + return tryCast(signature && signature.declaration, (d): d is SignatureDeclaration => isFunctionLike(d) && !isFunctionTypeNode(d)); +} + +function isConstructorLike(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.ConstructorType: + case SyntaxKind.ConstructSignature: + return true; + default: + return false; } } diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index 7bce1b660b4cd..33b2162189449 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -1,685 +1,719 @@ +import { + __String, AnyImportOrReExport, AssignmentDeclarationKind, BinaryExpression, BindingElement, CallExpression, + CancellationToken, canHaveModifiers, cast, Debug, ESMap, ExportAssignment, ExportDeclaration, FileReference, + findAncestor, forEach, getAssignmentDeclarationKind, getFirstIdentifier, getNameOfAccessExpression, + getSourceFileOfNode, getSymbolId, hasSyntacticModifier, Identifier, ImportCall, ImportClause, ImportDeclaration, + ImportEqualsDeclaration, importFromModuleSpecifier, ImportSpecifier, InternalSymbolName, isAccessExpression, + isBinaryExpression, isBindingElement, isCatchClause, isDefaultImport, isExportAssignment, isExportDeclaration, + isExportModifier, isExportSpecifier, isExternalModuleAugmentation, isExternalModuleSymbol, isImportCall, + isImportEqualsDeclaration, isImportTypeNode, isInJSFile, isJSDocTypedefTag, isModuleExportsAccessExpression, + isNamedExports, isNamespaceExport, isPrivateIdentifier, isPropertyAccessExpression, isShorthandPropertyAssignment, + isSourceFile, isStringLiteral, isVariableDeclaration, isVariableDeclarationInitializedToBareOrAccessedRequire, + isVariableStatement, Map, ModifierFlags, ModuleBlock, ModuleDeclaration, NamedImportsOrExports, NamespaceImport, + Node, nodeSeenTracker, Program, ReadonlySet, some, SourceFile, Statement, StringLiteral, StringLiteralLike, Symbol, + symbolEscapedNameNoDefault, SymbolFlags, symbolName, SyntaxKind, TypeChecker, ValidImportTypeNode, + VariableDeclaration, walkUpBindingElementsAndPatterns, +} from "./_namespaces/ts"; + /* Code for finding imports of an exported symbol. Used only by FindAllReferences. */ -/* @internal */ -namespace ts.FindAllReferences { - export interface ImportsResult { - /** For every import of the symbol, the location and local symbol for the import. */ - importSearches: readonly [Identifier, Symbol][]; - /** For rename imports/exports `{ foo as bar }`, `foo` is not a local, so it may be added as a reference immediately without further searching. */ - singleReferences: readonly (Identifier | StringLiteral)[]; - /** List of source files that may (or may not) use the symbol via a namespace. (For UMD modules this is every file.) */ - indirectUsers: readonly SourceFile[]; - } - export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult; - - /** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */ - export function createImportTracker(sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker { - const allDirectImports = getDirectImportsMap(sourceFiles, checker, cancellationToken); - return (exportSymbol, exportInfo, isForRename) => { - const { directImports, indirectUsers } = getImportersForExport(sourceFiles, sourceFilesSet, allDirectImports, exportInfo, checker, cancellationToken); - return { indirectUsers, ...getSearchesFromDirectImports(directImports, exportSymbol, exportInfo.exportKind, checker, isForRename) }; - }; - } - /** Info about an exported symbol to perform recursive search on. */ - export interface ExportInfo { - exportingModuleSymbol: Symbol; - exportKind: ExportKind; - } +/** @internal */ +export interface ImportsResult { + /** For every import of the symbol, the location and local symbol for the import. */ + importSearches: readonly [Identifier, Symbol][]; + /** For rename imports/exports `{ foo as bar }`, `foo` is not a local, so it may be added as a reference immediately without further searching. */ + singleReferences: readonly (Identifier | StringLiteral)[]; + /** List of source files that may (or may not) use the symbol via a namespace. (For UMD modules this is every file.) */ + indirectUsers: readonly SourceFile[]; +} +/** @internal */ +export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult; + +/** + * Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. + * + * @internal + */ +export function createImportTracker(sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker { + const allDirectImports = getDirectImportsMap(sourceFiles, checker, cancellationToken); + return (exportSymbol, exportInfo, isForRename) => { + const { directImports, indirectUsers } = getImportersForExport(sourceFiles, sourceFilesSet, allDirectImports, exportInfo, checker, cancellationToken); + return { indirectUsers, ...getSearchesFromDirectImports(directImports, exportSymbol, exportInfo.exportKind, checker, isForRename) }; + }; +} - export const enum ExportKind { Named, Default, ExportEquals } - - export const enum ImportExport { Import, Export } - - interface AmbientModuleDeclaration extends ModuleDeclaration { body?: ModuleBlock; } - type SourceFileLike = SourceFile | AmbientModuleDeclaration; - // Identifier for the case of `const x = require("y")`. - type Importer = AnyImportOrReExport | ValidImportTypeNode | Identifier; - type ImporterOrCallExpression = Importer | CallExpression; - - /** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ - function getImportersForExport( - sourceFiles: readonly SourceFile[], - sourceFilesSet: ReadonlySet, - allDirectImports: ESMap, - { exportingModuleSymbol, exportKind }: ExportInfo, - checker: TypeChecker, - cancellationToken: CancellationToken | undefined, - ): { directImports: Importer[], indirectUsers: readonly SourceFile[] } { - const markSeenDirectImport = nodeSeenTracker(); - const markSeenIndirectUser = nodeSeenTracker(); - const directImports: Importer[] = []; - const isAvailableThroughGlobal = !!exportingModuleSymbol.globalExports; - const indirectUserDeclarations: SourceFileLike[] | undefined = isAvailableThroughGlobal ? undefined : []; - - handleDirectImports(exportingModuleSymbol); - - return { directImports, indirectUsers: getIndirectUsers() }; - - function getIndirectUsers(): readonly SourceFile[] { - if (isAvailableThroughGlobal) { - // It has `export as namespace`, so anything could potentially use it. - return sourceFiles; - } +/** + * Info about an exported symbol to perform recursive search on. + * + * @internal + */ +export interface ExportInfo { + exportingModuleSymbol: Symbol; + exportKind: ExportKind; +} - // Module augmentations may use this module's exports without importing it. - if (exportingModuleSymbol.declarations) { - for (const decl of exportingModuleSymbol.declarations) { - if (isExternalModuleAugmentation(decl) && sourceFilesSet.has(decl.getSourceFile().fileName)) { - addIndirectUser(decl); - } +/** @internal */ +export const enum ExportKind { Named, Default, ExportEquals } + +/** @internal */ +export const enum ImportExport { Import, Export } + +interface AmbientModuleDeclaration extends ModuleDeclaration { body?: ModuleBlock; } +type SourceFileLike = SourceFile | AmbientModuleDeclaration; +// Identifier for the case of `const x = require("y")`. +type Importer = AnyImportOrReExport | ValidImportTypeNode | Identifier; +type ImporterOrCallExpression = Importer | CallExpression; + +/** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ +function getImportersForExport( + sourceFiles: readonly SourceFile[], + sourceFilesSet: ReadonlySet, + allDirectImports: ESMap, + { exportingModuleSymbol, exportKind }: ExportInfo, + checker: TypeChecker, + cancellationToken: CancellationToken | undefined, +): { directImports: Importer[], indirectUsers: readonly SourceFile[] } { + const markSeenDirectImport = nodeSeenTracker(); + const markSeenIndirectUser = nodeSeenTracker(); + const directImports: Importer[] = []; + const isAvailableThroughGlobal = !!exportingModuleSymbol.globalExports; + const indirectUserDeclarations: SourceFileLike[] | undefined = isAvailableThroughGlobal ? undefined : []; + + handleDirectImports(exportingModuleSymbol); + + return { directImports, indirectUsers: getIndirectUsers() }; + + function getIndirectUsers(): readonly SourceFile[] { + if (isAvailableThroughGlobal) { + // It has `export as namespace`, so anything could potentially use it. + return sourceFiles; + } + + // Module augmentations may use this module's exports without importing it. + if (exportingModuleSymbol.declarations) { + for (const decl of exportingModuleSymbol.declarations) { + if (isExternalModuleAugmentation(decl) && sourceFilesSet.has(decl.getSourceFile().fileName)) { + addIndirectUser(decl); } } - - // This may return duplicates (if there are multiple module declarations in a single source file, all importing the same thing as a namespace), but `State.markSearchedSymbol` will handle that. - return indirectUserDeclarations!.map(getSourceFileOfNode); } - function handleDirectImports(exportingModuleSymbol: Symbol): void { - const theseDirectImports = getDirectImports(exportingModuleSymbol); - if (theseDirectImports) { - for (const direct of theseDirectImports) { - if (!markSeenDirectImport(direct)) { - continue; - } + // This may return duplicates (if there are multiple module declarations in a single source file, all importing the same thing as a namespace), but `State.markSearchedSymbol` will handle that. + return indirectUserDeclarations!.map(getSourceFileOfNode); + } - if (cancellationToken) cancellationToken.throwIfCancellationRequested(); + function handleDirectImports(exportingModuleSymbol: Symbol): void { + const theseDirectImports = getDirectImports(exportingModuleSymbol); + if (theseDirectImports) { + for (const direct of theseDirectImports) { + if (!markSeenDirectImport(direct)) { + continue; + } - switch (direct.kind) { - case SyntaxKind.CallExpression: - if (isImportCall(direct)) { - handleImportCall(direct); - break; - } - if (!isAvailableThroughGlobal) { - const parent = direct.parent; - if (exportKind === ExportKind.ExportEquals && parent.kind === SyntaxKind.VariableDeclaration) { - const { name } = parent as VariableDeclaration; - if (name.kind === SyntaxKind.Identifier) { - directImports.push(name); - break; - } + if (cancellationToken) cancellationToken.throwIfCancellationRequested(); + + switch (direct.kind) { + case SyntaxKind.CallExpression: + if (isImportCall(direct)) { + handleImportCall(direct); + break; + } + if (!isAvailableThroughGlobal) { + const parent = direct.parent; + if (exportKind === ExportKind.ExportEquals && parent.kind === SyntaxKind.VariableDeclaration) { + const { name } = parent as VariableDeclaration; + if (name.kind === SyntaxKind.Identifier) { + directImports.push(name); + break; } } - break; - - case SyntaxKind.Identifier: // for 'const x = require("y"); - break; // TODO: GH#23879 + } + break; - case SyntaxKind.ImportEqualsDeclaration: - handleNamespaceImport(direct, direct.name, hasSyntacticModifier(direct, ModifierFlags.Export), /*alreadyAddedDirect*/ false); - break; + case SyntaxKind.Identifier: // for 'const x = require("y"); + break; // TODO: GH#23879 - case SyntaxKind.ImportDeclaration: - directImports.push(direct); - const namedBindings = direct.importClause && direct.importClause.namedBindings; - if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) { - handleNamespaceImport(direct, namedBindings.name, /*isReExport*/ false, /*alreadyAddedDirect*/ true); - } - else if (!isAvailableThroughGlobal && isDefaultImport(direct)) { - addIndirectUser(getSourceFileLikeForImportDeclaration(direct)); // Add a check for indirect uses to handle synthetic default imports - } - break; + case SyntaxKind.ImportEqualsDeclaration: + handleNamespaceImport(direct, direct.name, hasSyntacticModifier(direct, ModifierFlags.Export), /*alreadyAddedDirect*/ false); + break; - case SyntaxKind.ExportDeclaration: - if (!direct.exportClause) { - // This is `export * from "foo"`, so imports of this module may import the export too. - handleDirectImports(getContainingModuleSymbol(direct, checker)); - } - else if (direct.exportClause.kind === SyntaxKind.NamespaceExport) { - // `export * as foo from "foo"` add to indirect uses - addIndirectUser(getSourceFileLikeForImportDeclaration(direct), /** addTransitiveDependencies */ true); - } - else { - // This is `export { foo } from "foo"` and creates an alias symbol, so recursive search will get handle re-exports. - directImports.push(direct); - } - break; + case SyntaxKind.ImportDeclaration: + directImports.push(direct); + const namedBindings = direct.importClause && direct.importClause.namedBindings; + if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) { + handleNamespaceImport(direct, namedBindings.name, /*isReExport*/ false, /*alreadyAddedDirect*/ true); + } + else if (!isAvailableThroughGlobal && isDefaultImport(direct)) { + addIndirectUser(getSourceFileLikeForImportDeclaration(direct)); // Add a check for indirect uses to handle synthetic default imports + } + break; - case SyntaxKind.ImportType: - // Only check for typeof import('xyz') - if (!isAvailableThroughGlobal && direct.isTypeOf && !direct.qualifier && isExported(direct)) { - addIndirectUser(direct.getSourceFile(), /** addTransitiveDependencies */ true); - } + case SyntaxKind.ExportDeclaration: + if (!direct.exportClause) { + // This is `export * from "foo"`, so imports of this module may import the export too. + handleDirectImports(getContainingModuleSymbol(direct, checker)); + } + else if (direct.exportClause.kind === SyntaxKind.NamespaceExport) { + // `export * as foo from "foo"` add to indirect uses + addIndirectUser(getSourceFileLikeForImportDeclaration(direct), /** addTransitiveDependencies */ true); + } + else { + // This is `export { foo } from "foo"` and creates an alias symbol, so recursive search will get handle re-exports. directImports.push(direct); - break; + } + break; - default: - Debug.failBadSyntaxKind(direct, "Unexpected import kind."); - } + case SyntaxKind.ImportType: + // Only check for typeof import('xyz') + if (!isAvailableThroughGlobal && direct.isTypeOf && !direct.qualifier && isExported(direct)) { + addIndirectUser(direct.getSourceFile(), /** addTransitiveDependencies */ true); + } + directImports.push(direct); + break; + + default: + Debug.failBadSyntaxKind(direct, "Unexpected import kind."); } } } + } - function handleImportCall(importCall: ImportCall) { - const top = findAncestor(importCall, isAmbientModuleDeclaration) || importCall.getSourceFile(); - addIndirectUser(top, /** addTransitiveDependencies */ !!isExported(importCall, /** stopAtAmbientModule */ true)); - } + function handleImportCall(importCall: ImportCall) { + const top = findAncestor(importCall, isAmbientModuleDeclaration) || importCall.getSourceFile(); + addIndirectUser(top, /** addTransitiveDependencies */ !!isExported(importCall, /** stopAtAmbientModule */ true)); + } - function isExported(node: Node, stopAtAmbientModule = false) { - return findAncestor(node, node => { - if (stopAtAmbientModule && isAmbientModuleDeclaration(node)) return "quit"; - return canHaveModifiers(node) && some(node.modifiers, isExportModifier); - }); - } + function isExported(node: Node, stopAtAmbientModule = false) { + return findAncestor(node, node => { + if (stopAtAmbientModule && isAmbientModuleDeclaration(node)) return "quit"; + return canHaveModifiers(node) && some(node.modifiers, isExportModifier); + }); + } - function handleNamespaceImport(importDeclaration: ImportEqualsDeclaration | ImportDeclaration, name: Identifier, isReExport: boolean, alreadyAddedDirect: boolean): void { - if (exportKind === ExportKind.ExportEquals) { - // This is a direct import, not import-as-namespace. - if (!alreadyAddedDirect) directImports.push(importDeclaration); + function handleNamespaceImport(importDeclaration: ImportEqualsDeclaration | ImportDeclaration, name: Identifier, isReExport: boolean, alreadyAddedDirect: boolean): void { + if (exportKind === ExportKind.ExportEquals) { + // This is a direct import, not import-as-namespace. + if (!alreadyAddedDirect) directImports.push(importDeclaration); + } + else if (!isAvailableThroughGlobal) { + const sourceFileLike = getSourceFileLikeForImportDeclaration(importDeclaration); + Debug.assert(sourceFileLike.kind === SyntaxKind.SourceFile || sourceFileLike.kind === SyntaxKind.ModuleDeclaration); + if (isReExport || findNamespaceReExports(sourceFileLike, name, checker)) { + addIndirectUser(sourceFileLike, /** addTransitiveDependencies */ true); } - else if (!isAvailableThroughGlobal) { - const sourceFileLike = getSourceFileLikeForImportDeclaration(importDeclaration); - Debug.assert(sourceFileLike.kind === SyntaxKind.SourceFile || sourceFileLike.kind === SyntaxKind.ModuleDeclaration); - if (isReExport || findNamespaceReExports(sourceFileLike, name, checker)) { - addIndirectUser(sourceFileLike, /** addTransitiveDependencies */ true); - } - else { - addIndirectUser(sourceFileLike); - } + else { + addIndirectUser(sourceFileLike); } } + } - /** Adds a module and all of its transitive dependencies as possible indirect users. */ - function addIndirectUser(sourceFileLike: SourceFileLike, addTransitiveDependencies = false): void { - Debug.assert(!isAvailableThroughGlobal); - const isNew = markSeenIndirectUser(sourceFileLike); - if (!isNew) return; - indirectUserDeclarations!.push(sourceFileLike); // TODO: GH#18217 - - if (!addTransitiveDependencies) return; - const moduleSymbol = checker.getMergedSymbol(sourceFileLike.symbol); - if (!moduleSymbol) return; - Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module)); - const directImports = getDirectImports(moduleSymbol); - if (directImports) { - for (const directImport of directImports) { - if (!isImportTypeNode(directImport)) { - addIndirectUser(getSourceFileLikeForImportDeclaration(directImport), /** addTransitiveDependencies */ true); - } + /** Adds a module and all of its transitive dependencies as possible indirect users. */ + function addIndirectUser(sourceFileLike: SourceFileLike, addTransitiveDependencies = false): void { + Debug.assert(!isAvailableThroughGlobal); + const isNew = markSeenIndirectUser(sourceFileLike); + if (!isNew) return; + indirectUserDeclarations!.push(sourceFileLike); // TODO: GH#18217 + + if (!addTransitiveDependencies) return; + const moduleSymbol = checker.getMergedSymbol(sourceFileLike.symbol); + if (!moduleSymbol) return; + Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module)); + const directImports = getDirectImports(moduleSymbol); + if (directImports) { + for (const directImport of directImports) { + if (!isImportTypeNode(directImport)) { + addIndirectUser(getSourceFileLikeForImportDeclaration(directImport), /** addTransitiveDependencies */ true); } } } + } - function getDirectImports(moduleSymbol: Symbol): ImporterOrCallExpression[] | undefined { - return allDirectImports.get(getSymbolId(moduleSymbol).toString()); - } + function getDirectImports(moduleSymbol: Symbol): ImporterOrCallExpression[] | undefined { + return allDirectImports.get(getSymbolId(moduleSymbol).toString()); } +} - /** - * Given the set of direct imports of a module, we need to find which ones import the particular exported symbol. - * The returned `importSearches` will result in the entire source file being searched. - * But re-exports will be placed in 'singleReferences' since they cannot be locally referenced. - */ - function getSearchesFromDirectImports(directImports: Importer[], exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker, isForRename: boolean): Pick { - const importSearches: [Identifier, Symbol][] = []; - const singleReferences: (Identifier | StringLiteral)[] = []; - function addSearch(location: Identifier, symbol: Symbol): void { - importSearches.push([location, symbol]); +/** + * Given the set of direct imports of a module, we need to find which ones import the particular exported symbol. + * The returned `importSearches` will result in the entire source file being searched. + * But re-exports will be placed in 'singleReferences' since they cannot be locally referenced. + */ +function getSearchesFromDirectImports(directImports: Importer[], exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker, isForRename: boolean): Pick { + const importSearches: [Identifier, Symbol][] = []; + const singleReferences: (Identifier | StringLiteral)[] = []; + function addSearch(location: Identifier, symbol: Symbol): void { + importSearches.push([location, symbol]); + } + + if (directImports) { + for (const decl of directImports) { + handleImport(decl); } + } - if (directImports) { - for (const decl of directImports) { - handleImport(decl); + return { importSearches, singleReferences }; + + function handleImport(decl: Importer): void { + if (decl.kind === SyntaxKind.ImportEqualsDeclaration) { + if (isExternalModuleImportEquals(decl)) { + handleNamespaceImportLike(decl.name); } + return; } - return { importSearches, singleReferences }; + if (decl.kind === SyntaxKind.Identifier) { + handleNamespaceImportLike(decl); + return; + } - function handleImport(decl: Importer): void { - if (decl.kind === SyntaxKind.ImportEqualsDeclaration) { - if (isExternalModuleImportEquals(decl)) { - handleNamespaceImportLike(decl.name); + if (decl.kind === SyntaxKind.ImportType) { + if (decl.qualifier) { + const firstIdentifier = getFirstIdentifier(decl.qualifier); + if (firstIdentifier.escapedText === symbolName(exportSymbol)) { + singleReferences.push(firstIdentifier); } - return; } - - if (decl.kind === SyntaxKind.Identifier) { - handleNamespaceImportLike(decl); - return; + else if (exportKind === ExportKind.ExportEquals) { + singleReferences.push(decl.argument.literal); } + return; + } - if (decl.kind === SyntaxKind.ImportType) { - if (decl.qualifier) { - const firstIdentifier = getFirstIdentifier(decl.qualifier); - if (firstIdentifier.escapedText === symbolName(exportSymbol)) { - singleReferences.push(firstIdentifier); - } - } - else if (exportKind === ExportKind.ExportEquals) { - singleReferences.push(decl.argument.literal); - } - return; - } + // Ignore if there's a grammar error + if (decl.moduleSpecifier!.kind !== SyntaxKind.StringLiteral) { + return; + } - // Ignore if there's a grammar error - if (decl.moduleSpecifier!.kind !== SyntaxKind.StringLiteral) { - return; + if (decl.kind === SyntaxKind.ExportDeclaration) { + if (decl.exportClause && isNamedExports(decl.exportClause)) { + searchForNamedImport(decl.exportClause); } + return; + } - if (decl.kind === SyntaxKind.ExportDeclaration) { - if (decl.exportClause && isNamedExports(decl.exportClause)) { - searchForNamedImport(decl.exportClause); - } - return; + const { name, namedBindings } = decl.importClause || { name: undefined, namedBindings: undefined }; + + if (namedBindings) { + switch (namedBindings.kind) { + case SyntaxKind.NamespaceImport: + handleNamespaceImportLike(namedBindings.name); + break; + case SyntaxKind.NamedImports: + // 'default' might be accessed as a named import `{ default as foo }`. + if (exportKind === ExportKind.Named || exportKind === ExportKind.Default) { + searchForNamedImport(namedBindings); + } + break; + default: + Debug.assertNever(namedBindings); } + } - const { name, namedBindings } = decl.importClause || { name: undefined, namedBindings: undefined }; - - if (namedBindings) { - switch (namedBindings.kind) { - case SyntaxKind.NamespaceImport: - handleNamespaceImportLike(namedBindings.name); - break; - case SyntaxKind.NamedImports: - // 'default' might be accessed as a named import `{ default as foo }`. - if (exportKind === ExportKind.Named || exportKind === ExportKind.Default) { - searchForNamedImport(namedBindings); - } - break; - default: - Debug.assertNever(namedBindings); - } - } + // `export =` might be imported by a default import if `--allowSyntheticDefaultImports` is on, so this handles both ExportKind.Default and ExportKind.ExportEquals. + // If a default import has the same name as the default export, allow to rename it. + // Given `import f` and `export default function f`, we will rename both, but for `import g` we will rename just that. + if (name && (exportKind === ExportKind.Default || exportKind === ExportKind.ExportEquals) && (!isForRename || name.escapedText === symbolEscapedNameNoDefault(exportSymbol))) { + const defaultImportAlias = checker.getSymbolAtLocation(name)!; + addSearch(name, defaultImportAlias); + } + } - // `export =` might be imported by a default import if `--allowSyntheticDefaultImports` is on, so this handles both ExportKind.Default and ExportKind.ExportEquals. - // If a default import has the same name as the default export, allow to rename it. - // Given `import f` and `export default function f`, we will rename both, but for `import g` we will rename just that. - if (name && (exportKind === ExportKind.Default || exportKind === ExportKind.ExportEquals) && (!isForRename || name.escapedText === symbolEscapedNameNoDefault(exportSymbol))) { - const defaultImportAlias = checker.getSymbolAtLocation(name)!; - addSearch(name, defaultImportAlias); - } + /** + * `import x = require("./x")` or `import * as x from "./x"`. + * An `export =` may be imported by this syntax, so it may be a direct import. + * If it's not a direct import, it will be in `indirectUsers`, so we don't have to do anything here. + */ + function handleNamespaceImportLike(importName: Identifier): void { + // Don't rename an import that already has a different name than the export. + if (exportKind === ExportKind.ExportEquals && (!isForRename || isNameMatch(importName.escapedText))) { + addSearch(importName, checker.getSymbolAtLocation(importName)!); } + } - /** - * `import x = require("./x")` or `import * as x from "./x"`. - * An `export =` may be imported by this syntax, so it may be a direct import. - * If it's not a direct import, it will be in `indirectUsers`, so we don't have to do anything here. - */ - function handleNamespaceImportLike(importName: Identifier): void { - // Don't rename an import that already has a different name than the export. - if (exportKind === ExportKind.ExportEquals && (!isForRename || isNameMatch(importName.escapedText))) { - addSearch(importName, checker.getSymbolAtLocation(importName)!); - } + function searchForNamedImport(namedBindings: NamedImportsOrExports | undefined): void { + if (!namedBindings) { + return; } - function searchForNamedImport(namedBindings: NamedImportsOrExports | undefined): void { - if (!namedBindings) { - return; + for (const element of namedBindings.elements) { + const { name, propertyName } = element; + if (!isNameMatch((propertyName || name).escapedText)) { + continue; } - for (const element of namedBindings.elements) { - const { name, propertyName } = element; - if (!isNameMatch((propertyName || name).escapedText)) { - continue; - } - - if (propertyName) { - // This is `import { foo as bar } from "./a"` or `export { foo as bar } from "./a"`. `foo` isn't a local in the file, so just add it as a single reference. - singleReferences.push(propertyName); - // If renaming `{ foo as bar }`, don't touch `bar`, just `foo`. - // But do rename `foo` in ` { default as foo }` if that's the original export name. - if (!isForRename || name.escapedText === exportSymbol.escapedName) { - // Search locally for `bar`. - addSearch(name, checker.getSymbolAtLocation(name)!); - } - } - else { - const localSymbol = element.kind === SyntaxKind.ExportSpecifier && element.propertyName - ? checker.getExportSpecifierLocalTargetSymbol(element)! // For re-exporting under a different name, we want to get the re-exported symbol. - : checker.getSymbolAtLocation(name)!; - addSearch(name, localSymbol); + if (propertyName) { + // This is `import { foo as bar } from "./a"` or `export { foo as bar } from "./a"`. `foo` isn't a local in the file, so just add it as a single reference. + singleReferences.push(propertyName); + // If renaming `{ foo as bar }`, don't touch `bar`, just `foo`. + // But do rename `foo` in ` { default as foo }` if that's the original export name. + if (!isForRename || name.escapedText === exportSymbol.escapedName) { + // Search locally for `bar`. + addSearch(name, checker.getSymbolAtLocation(name)!); } } + else { + const localSymbol = element.kind === SyntaxKind.ExportSpecifier && element.propertyName + ? checker.getExportSpecifierLocalTargetSymbol(element)! // For re-exporting under a different name, we want to get the re-exported symbol. + : checker.getSymbolAtLocation(name)!; + addSearch(name, localSymbol); + } } + } - function isNameMatch(name: __String): boolean { - // Use name of "default" even in `export =` case because we may have allowSyntheticDefaultImports - return name === exportSymbol.escapedName || exportKind !== ExportKind.Named && name === InternalSymbolName.Default; - } + function isNameMatch(name: __String): boolean { + // Use name of "default" even in `export =` case because we may have allowSyntheticDefaultImports + return name === exportSymbol.escapedName || exportKind !== ExportKind.Named && name === InternalSymbolName.Default; } +} - /** Returns 'true' is the namespace 'name' is re-exported from this module, and 'false' if it is only used locally. */ - function findNamespaceReExports(sourceFileLike: SourceFileLike, name: Identifier, checker: TypeChecker): boolean { - const namespaceImportSymbol = checker.getSymbolAtLocation(name); +/** Returns 'true' is the namespace 'name' is re-exported from this module, and 'false' if it is only used locally. */ +function findNamespaceReExports(sourceFileLike: SourceFileLike, name: Identifier, checker: TypeChecker): boolean { + const namespaceImportSymbol = checker.getSymbolAtLocation(name); - return !!forEachPossibleImportOrExportStatement(sourceFileLike, statement => { - if (!isExportDeclaration(statement)) return; - const { exportClause, moduleSpecifier } = statement; - return !moduleSpecifier && exportClause && isNamedExports(exportClause) && - exportClause.elements.some(element => checker.getExportSpecifierLocalTargetSymbol(element) === namespaceImportSymbol); - }); - } + return !!forEachPossibleImportOrExportStatement(sourceFileLike, statement => { + if (!isExportDeclaration(statement)) return; + const { exportClause, moduleSpecifier } = statement; + return !moduleSpecifier && exportClause && isNamedExports(exportClause) && + exportClause.elements.some(element => checker.getExportSpecifierLocalTargetSymbol(element) === namespaceImportSymbol); + }); +} - export type ModuleReference = - /** "import" also includes require() calls. */ - | { kind: "import", literal: StringLiteralLike } - /** or */ - | { kind: "reference", referencingFile: SourceFile, ref: FileReference }; - export function findModuleReferences(program: Program, sourceFiles: readonly SourceFile[], searchModuleSymbol: Symbol): ModuleReference[] { - const refs: ModuleReference[] = []; - const checker = program.getTypeChecker(); - for (const referencingFile of sourceFiles) { - const searchSourceFile = searchModuleSymbol.valueDeclaration; - if (searchSourceFile?.kind === SyntaxKind.SourceFile) { - for (const ref of referencingFile.referencedFiles) { - if (program.getSourceFileFromReference(referencingFile, ref) === searchSourceFile) { - refs.push({ kind: "reference", referencingFile, ref }); - } - } - for (const ref of referencingFile.typeReferenceDirectives) { - const referenced = program.getResolvedTypeReferenceDirectives().get(ref.fileName, ref.resolutionMode || referencingFile.impliedNodeFormat); - if (referenced !== undefined && referenced.resolvedFileName === (searchSourceFile as SourceFile).fileName) { - refs.push({ kind: "reference", referencingFile, ref }); - } +/** @internal */ +export type ModuleReference = + /** "import" also includes require() calls. */ + | { kind: "import", literal: StringLiteralLike } + /** or */ + | { kind: "reference", referencingFile: SourceFile, ref: FileReference }; +/** @internal */ +export function findModuleReferences(program: Program, sourceFiles: readonly SourceFile[], searchModuleSymbol: Symbol): ModuleReference[] { + const refs: ModuleReference[] = []; + const checker = program.getTypeChecker(); + for (const referencingFile of sourceFiles) { + const searchSourceFile = searchModuleSymbol.valueDeclaration; + if (searchSourceFile?.kind === SyntaxKind.SourceFile) { + for (const ref of referencingFile.referencedFiles) { + if (program.getSourceFileFromReference(referencingFile, ref) === searchSourceFile) { + refs.push({ kind: "reference", referencingFile, ref }); } } - - forEachImport(referencingFile, (_importDecl, moduleSpecifier) => { - const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); - if (moduleSymbol === searchModuleSymbol) { - refs.push({ kind: "import", literal: moduleSpecifier }); + for (const ref of referencingFile.typeReferenceDirectives) { + const referenced = program.getResolvedTypeReferenceDirectives().get(ref.fileName, ref.resolutionMode || referencingFile.impliedNodeFormat); + if (referenced !== undefined && referenced.resolvedFileName === (searchSourceFile as SourceFile).fileName) { + refs.push({ kind: "reference", referencingFile, ref }); } - }); + } } - return refs; + + forEachImport(referencingFile, (_importDecl, moduleSpecifier) => { + const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); + if (moduleSymbol === searchModuleSymbol) { + refs.push({ kind: "import", literal: moduleSpecifier }); + } + }); } + return refs; +} - /** Returns a map from a module symbol Id to all import statements that directly reference the module. */ - function getDirectImportsMap(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken | undefined): ESMap { - const map = new Map(); - - for (const sourceFile of sourceFiles) { - if (cancellationToken) cancellationToken.throwIfCancellationRequested(); - forEachImport(sourceFile, (importDecl, moduleSpecifier) => { - const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); - if (moduleSymbol) { - const id = getSymbolId(moduleSymbol).toString(); - let imports = map.get(id); - if (!imports) { - map.set(id, imports = []); - } - imports.push(importDecl); +/** Returns a map from a module symbol Id to all import statements that directly reference the module. */ +function getDirectImportsMap(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken | undefined): ESMap { + const map = new Map(); + + for (const sourceFile of sourceFiles) { + if (cancellationToken) cancellationToken.throwIfCancellationRequested(); + forEachImport(sourceFile, (importDecl, moduleSpecifier) => { + const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); + if (moduleSymbol) { + const id = getSymbolId(moduleSymbol).toString(); + let imports = map.get(id); + if (!imports) { + map.set(id, imports = []); } - }); - } - - return map; + imports.push(importDecl); + } + }); } - /** Iterates over all statements at the top level or in module declarations. Returns the first truthy result. */ - function forEachPossibleImportOrExportStatement(sourceFileLike: SourceFileLike, action: (statement: Statement) => T) { - return forEach(sourceFileLike.kind === SyntaxKind.SourceFile ? sourceFileLike.statements : sourceFileLike.body!.statements, statement => // TODO: GH#18217 - action(statement) || (isAmbientModuleDeclaration(statement) && forEach(statement.body && statement.body.statements, action))); - } + return map; +} - /** Calls `action` for each import, re-export, or require() in a file. */ - function forEachImport(sourceFile: SourceFile, action: (importStatement: ImporterOrCallExpression, imported: StringLiteralLike) => void): void { - if (sourceFile.externalModuleIndicator || sourceFile.imports !== undefined) { - for (const i of sourceFile.imports) { - action(importFromModuleSpecifier(i), i); - } +/** Iterates over all statements at the top level or in module declarations. Returns the first truthy result. */ +function forEachPossibleImportOrExportStatement(sourceFileLike: SourceFileLike, action: (statement: Statement) => T) { + return forEach(sourceFileLike.kind === SyntaxKind.SourceFile ? sourceFileLike.statements : sourceFileLike.body!.statements, statement => // TODO: GH#18217 + action(statement) || (isAmbientModuleDeclaration(statement) && forEach(statement.body && statement.body.statements, action))); +} + +/** Calls `action` for each import, re-export, or require() in a file. */ +function forEachImport(sourceFile: SourceFile, action: (importStatement: ImporterOrCallExpression, imported: StringLiteralLike) => void): void { + if (sourceFile.externalModuleIndicator || sourceFile.imports !== undefined) { + for (const i of sourceFile.imports) { + action(importFromModuleSpecifier(i), i); } - else { - forEachPossibleImportOrExportStatement(sourceFile, statement => { - switch (statement.kind) { - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ImportDeclaration: { - const decl = statement as ImportDeclaration | ExportDeclaration; - if (decl.moduleSpecifier && isStringLiteral(decl.moduleSpecifier)) { - action(decl, decl.moduleSpecifier); - } - break; + } + else { + forEachPossibleImportOrExportStatement(sourceFile, statement => { + switch (statement.kind) { + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ImportDeclaration: { + const decl = statement as ImportDeclaration | ExportDeclaration; + if (decl.moduleSpecifier && isStringLiteral(decl.moduleSpecifier)) { + action(decl, decl.moduleSpecifier); } + break; + } - case SyntaxKind.ImportEqualsDeclaration: { - const decl = statement as ImportEqualsDeclaration; - if (isExternalModuleImportEquals(decl)) { - action(decl, decl.moduleReference.expression); - } - break; + case SyntaxKind.ImportEqualsDeclaration: { + const decl = statement as ImportEqualsDeclaration; + if (isExternalModuleImportEquals(decl)) { + action(decl, decl.moduleReference.expression); } + break; } - }); - } + } + }); } +} - export interface ImportedSymbol { - kind: ImportExport.Import; - symbol: Symbol; - } - export interface ExportedSymbol { - kind: ImportExport.Export; - symbol: Symbol; - exportInfo: ExportInfo; - } +/** @internal */ +export interface ImportedSymbol { + kind: ImportExport.Import; + symbol: Symbol; +} +/** @internal */ +export interface ExportedSymbol { + kind: ImportExport.Export; + symbol: Symbol; + exportInfo: ExportInfo; +} - /** - * Given a local reference, we might notice that it's an import/export and recursively search for references of that. - * If at an import, look locally for the symbol it imports. - * If at an export, look for all imports of it. - * This doesn't handle export specifiers; that is done in `getReferencesAtExportSpecifier`. - * @param comingFromExport If we are doing a search for all exports, don't bother looking backwards for the imported symbol, since that's the reason we're here. - */ - export function getImportOrExportSymbol(node: Node, symbol: Symbol, checker: TypeChecker, comingFromExport: boolean): ImportedSymbol | ExportedSymbol | undefined { - return comingFromExport ? getExport() : getExport() || getImport(); - - function getExport(): ExportedSymbol | ImportedSymbol | undefined { - const { parent } = node; - const grandparent = parent.parent; - if (symbol.exportSymbol) { - if (parent.kind === SyntaxKind.PropertyAccessExpression) { - // When accessing an export of a JS module, there's no alias. The symbol will still be flagged as an export even though we're at the use. - // So check that we are at the declaration. - return symbol.declarations?.some(d => d === parent) && isBinaryExpression(grandparent) - ? getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ false) - : undefined; - } - else { - return exportInfo(symbol.exportSymbol, getExportKindForDeclaration(parent)); - } +/** + * Given a local reference, we might notice that it's an import/export and recursively search for references of that. + * If at an import, look locally for the symbol it imports. + * If at an export, look for all imports of it. + * This doesn't handle export specifiers; that is done in `getReferencesAtExportSpecifier`. + * @param comingFromExport If we are doing a search for all exports, don't bother looking backwards for the imported symbol, since that's the reason we're here. + * + * @internal + */ +export function getImportOrExportSymbol(node: Node, symbol: Symbol, checker: TypeChecker, comingFromExport: boolean): ImportedSymbol | ExportedSymbol | undefined { + return comingFromExport ? getExport() : getExport() || getImport(); + + function getExport(): ExportedSymbol | ImportedSymbol | undefined { + const { parent } = node; + const grandparent = parent.parent; + if (symbol.exportSymbol) { + if (parent.kind === SyntaxKind.PropertyAccessExpression) { + // When accessing an export of a JS module, there's no alias. The symbol will still be flagged as an export even though we're at the use. + // So check that we are at the declaration. + return symbol.declarations?.some(d => d === parent) && isBinaryExpression(grandparent) + ? getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ false) + : undefined; } else { - const exportNode = getExportNode(parent, node); - if (exportNode && hasSyntacticModifier(exportNode, ModifierFlags.Export)) { - if (isImportEqualsDeclaration(exportNode) && exportNode.moduleReference === node) { - // We're at `Y` in `export import X = Y`. This is not the exported symbol, the left-hand-side is. So treat this as an import statement. - if (comingFromExport) { - return undefined; - } - - const lhsSymbol = checker.getSymbolAtLocation(exportNode.name)!; - return { kind: ImportExport.Import, symbol: lhsSymbol }; - } - else { - return exportInfo(symbol, getExportKindForDeclaration(exportNode)); + return exportInfo(symbol.exportSymbol, getExportKindForDeclaration(parent)); + } + } + else { + const exportNode = getExportNode(parent, node); + if (exportNode && hasSyntacticModifier(exportNode, ModifierFlags.Export)) { + if (isImportEqualsDeclaration(exportNode) && exportNode.moduleReference === node) { + // We're at `Y` in `export import X = Y`. This is not the exported symbol, the left-hand-side is. So treat this as an import statement. + if (comingFromExport) { + return undefined; } + + const lhsSymbol = checker.getSymbolAtLocation(exportNode.name)!; + return { kind: ImportExport.Import, symbol: lhsSymbol }; } - else if (isNamespaceExport(parent)) { - return exportInfo(symbol, ExportKind.Named); - } - // If we are in `export = a;` or `export default a;`, `parent` is the export assignment. - else if (isExportAssignment(parent)) { - return getExportAssignmentExport(parent); - } - // If we are in `export = class A {};` (or `export = class A {};`) at `A`, `parent.parent` is the export assignment. - else if (isExportAssignment(grandparent)) { - return getExportAssignmentExport(grandparent); - } - // Similar for `module.exports =` and `exports.A =`. - else if (isBinaryExpression(parent)) { - return getSpecialPropertyExport(parent, /*useLhsSymbol*/ true); - } - else if (isBinaryExpression(grandparent)) { - return getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ true); - } - else if (isJSDocTypedefTag(parent)) { - return exportInfo(symbol, ExportKind.Named); + else { + return exportInfo(symbol, getExportKindForDeclaration(exportNode)); } } - - function getExportAssignmentExport(ex: ExportAssignment): ExportedSymbol | undefined { - // Get the symbol for the `export =` node; its parent is the module it's the export of. - if (!ex.symbol.parent) return undefined; - const exportKind = ex.isExportEquals ? ExportKind.ExportEquals : ExportKind.Default; - return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol: ex.symbol.parent, exportKind } }; + else if (isNamespaceExport(parent)) { + return exportInfo(symbol, ExportKind.Named); } - - function getSpecialPropertyExport(node: BinaryExpression, useLhsSymbol: boolean): ExportedSymbol | undefined { - let kind: ExportKind; - switch (getAssignmentDeclarationKind(node)) { - case AssignmentDeclarationKind.ExportsProperty: - kind = ExportKind.Named; - break; - case AssignmentDeclarationKind.ModuleExports: - kind = ExportKind.ExportEquals; - break; - default: - return undefined; - } - - const sym = useLhsSymbol ? checker.getSymbolAtLocation(getNameOfAccessExpression(cast(node.left, isAccessExpression))) : symbol; - return sym && exportInfo(sym, kind); + // If we are in `export = a;` or `export default a;`, `parent` is the export assignment. + else if (isExportAssignment(parent)) { + return getExportAssignmentExport(parent); } - } - - function getImport(): ImportedSymbol | undefined { - const isImport = isNodeImport(node); - if (!isImport) return undefined; - - // A symbol being imported is always an alias. So get what that aliases to find the local symbol. - let importedSymbol = checker.getImmediateAliasedSymbol(symbol); - if (!importedSymbol) return undefined; - - // Search on the local symbol in the exporting module, not the exported symbol. - importedSymbol = skipExportSpecifierSymbol(importedSymbol, checker); - // Similarly, skip past the symbol for 'export =' - if (importedSymbol.escapedName === "export=") { - importedSymbol = getExportEqualsLocalSymbol(importedSymbol, checker); - if (importedSymbol === undefined) return undefined; + // If we are in `export = class A {};` (or `export = class A {};`) at `A`, `parent.parent` is the export assignment. + else if (isExportAssignment(grandparent)) { + return getExportAssignmentExport(grandparent); } - - // If the import has a different name than the export, do not continue searching. - // If `importedName` is undefined, do continue searching as the export is anonymous. - // (All imports returned from this function will be ignored anyway if we are in rename and this is a not a named export.) - const importedName = symbolEscapedNameNoDefault(importedSymbol); - if (importedName === undefined || importedName === InternalSymbolName.Default || importedName === symbol.escapedName) { - return { kind: ImportExport.Import, symbol: importedSymbol }; + // Similar for `module.exports =` and `exports.A =`. + else if (isBinaryExpression(parent)) { + return getSpecialPropertyExport(parent, /*useLhsSymbol*/ true); + } + else if (isBinaryExpression(grandparent)) { + return getSpecialPropertyExport(grandparent, /*useLhsSymbol*/ true); + } + else if (isJSDocTypedefTag(parent)) { + return exportInfo(symbol, ExportKind.Named); } } - function exportInfo(symbol: Symbol, kind: ExportKind): ExportedSymbol | undefined { - const exportInfo = getExportInfo(symbol, kind, checker); - return exportInfo && { kind: ImportExport.Export, symbol, exportInfo }; + function getExportAssignmentExport(ex: ExportAssignment): ExportedSymbol | undefined { + // Get the symbol for the `export =` node; its parent is the module it's the export of. + if (!ex.symbol.parent) return undefined; + const exportKind = ex.isExportEquals ? ExportKind.ExportEquals : ExportKind.Default; + return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol: ex.symbol.parent, exportKind } }; } - // Not meant for use with export specifiers or export assignment. - function getExportKindForDeclaration(node: Node): ExportKind { - return hasSyntacticModifier(node, ModifierFlags.Default) ? ExportKind.Default : ExportKind.Named; + function getSpecialPropertyExport(node: BinaryExpression, useLhsSymbol: boolean): ExportedSymbol | undefined { + let kind: ExportKind; + switch (getAssignmentDeclarationKind(node)) { + case AssignmentDeclarationKind.ExportsProperty: + kind = ExportKind.Named; + break; + case AssignmentDeclarationKind.ModuleExports: + kind = ExportKind.ExportEquals; + break; + default: + return undefined; + } + + const sym = useLhsSymbol ? checker.getSymbolAtLocation(getNameOfAccessExpression(cast(node.left, isAccessExpression))) : symbol; + return sym && exportInfo(sym, kind); } } - function getExportEqualsLocalSymbol(importedSymbol: Symbol, checker: TypeChecker): Symbol | undefined { - if (importedSymbol.flags & SymbolFlags.Alias) { - return checker.getImmediateAliasedSymbol(importedSymbol); - } + function getImport(): ImportedSymbol | undefined { + const isImport = isNodeImport(node); + if (!isImport) return undefined; - const decl = Debug.checkDefined(importedSymbol.valueDeclaration); - if (isExportAssignment(decl)) { // `export = class {}` - return decl.expression.symbol; - } - else if (isBinaryExpression(decl)) { // `module.exports = class {}` - return decl.right.symbol; + // A symbol being imported is always an alias. So get what that aliases to find the local symbol. + let importedSymbol = checker.getImmediateAliasedSymbol(symbol); + if (!importedSymbol) return undefined; + + // Search on the local symbol in the exporting module, not the exported symbol. + importedSymbol = skipExportSpecifierSymbol(importedSymbol, checker); + // Similarly, skip past the symbol for 'export =' + if (importedSymbol.escapedName === "export=") { + importedSymbol = getExportEqualsLocalSymbol(importedSymbol, checker); + if (importedSymbol === undefined) return undefined; } - else if (isSourceFile(decl)) { // json module - return decl.symbol; + + // If the import has a different name than the export, do not continue searching. + // If `importedName` is undefined, do continue searching as the export is anonymous. + // (All imports returned from this function will be ignored anyway if we are in rename and this is a not a named export.) + const importedName = symbolEscapedNameNoDefault(importedSymbol); + if (importedName === undefined || importedName === InternalSymbolName.Default || importedName === symbol.escapedName) { + return { kind: ImportExport.Import, symbol: importedSymbol }; } - return undefined; } - // If a reference is a class expression, the exported node would be its parent. - // If a reference is a variable declaration, the exported node would be the variable statement. - function getExportNode(parent: Node, node: Node): Node | undefined { - const declaration = isVariableDeclaration(parent) ? parent : isBindingElement(parent) ? walkUpBindingElementsAndPatterns(parent) : undefined; - if (declaration) { - return (parent as VariableDeclaration | BindingElement).name !== node ? undefined : - isCatchClause(declaration.parent) ? undefined : isVariableStatement(declaration.parent.parent) ? declaration.parent.parent : undefined; - } - else { - return parent; - } + function exportInfo(symbol: Symbol, kind: ExportKind): ExportedSymbol | undefined { + const exportInfo = getExportInfo(symbol, kind, checker); + return exportInfo && { kind: ImportExport.Export, symbol, exportInfo }; } - function isNodeImport(node: Node): boolean { - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.ImportEqualsDeclaration: - return (parent as ImportEqualsDeclaration).name === node && isExternalModuleImportEquals(parent as ImportEqualsDeclaration); - case SyntaxKind.ImportSpecifier: - // For a rename import `{ foo as bar }`, don't search for the imported symbol. Just find local uses of `bar`. - return !(parent as ImportSpecifier).propertyName; - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - Debug.assert((parent as ImportClause | NamespaceImport).name === node); - return true; - case SyntaxKind.BindingElement: - return isInJSFile(node) && isVariableDeclarationInitializedToBareOrAccessedRequire(parent.parent.parent); - default: - return false; - } + // Not meant for use with export specifiers or export assignment. + function getExportKindForDeclaration(node: Node): ExportKind { + return hasSyntacticModifier(node, ModifierFlags.Default) ? ExportKind.Default : ExportKind.Named; } +} - export function getExportInfo(exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker): ExportInfo | undefined { - const moduleSymbol = exportSymbol.parent; - if (!moduleSymbol) return undefined; // This can happen if an `export` is not at the top-level (which is a compile error). - const exportingModuleSymbol = checker.getMergedSymbol(moduleSymbol); // Need to get merged symbol in case there's an augmentation. - // `export` may appear in a namespace. In that case, just rely on global search. - return isExternalModuleSymbol(exportingModuleSymbol) ? { exportingModuleSymbol, exportKind } : undefined; +function getExportEqualsLocalSymbol(importedSymbol: Symbol, checker: TypeChecker): Symbol | undefined { + if (importedSymbol.flags & SymbolFlags.Alias) { + return checker.getImmediateAliasedSymbol(importedSymbol); } - /** If at an export specifier, go to the symbol it refers to. */ - function skipExportSpecifierSymbol(symbol: Symbol, checker: TypeChecker): Symbol { - // For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does. - if (symbol.declarations) { - for (const declaration of symbol.declarations) { - if (isExportSpecifier(declaration) && !declaration.propertyName && !declaration.parent.parent.moduleSpecifier) { - return checker.getExportSpecifierLocalTargetSymbol(declaration)!; - } - else if (isPropertyAccessExpression(declaration) && isModuleExportsAccessExpression(declaration.expression) && !isPrivateIdentifier(declaration.name)) { - // Export of form 'module.exports.propName = expr'; - return checker.getSymbolAtLocation(declaration)!; - } - else if (isShorthandPropertyAssignment(declaration) - && isBinaryExpression(declaration.parent.parent) - && getAssignmentDeclarationKind(declaration.parent.parent) === AssignmentDeclarationKind.ModuleExports) { - return checker.getExportSpecifierLocalTargetSymbol(declaration.name)!; - } - } - } - return symbol; + const decl = Debug.checkDefined(importedSymbol.valueDeclaration); + if (isExportAssignment(decl)) { // `export = class {}` + return decl.expression.symbol; + } + else if (isBinaryExpression(decl)) { // `module.exports = class {}` + return decl.right.symbol; } + else if (isSourceFile(decl)) { // json module + return decl.symbol; + } + return undefined; +} - function getContainingModuleSymbol(importer: Importer, checker: TypeChecker): Symbol { - return checker.getMergedSymbol(getSourceFileLikeForImportDeclaration(importer).symbol); +// If a reference is a class expression, the exported node would be its parent. +// If a reference is a variable declaration, the exported node would be the variable statement. +function getExportNode(parent: Node, node: Node): Node | undefined { + const declaration = isVariableDeclaration(parent) ? parent : isBindingElement(parent) ? walkUpBindingElementsAndPatterns(parent) : undefined; + if (declaration) { + return (parent as VariableDeclaration | BindingElement).name !== node ? undefined : + isCatchClause(declaration.parent) ? undefined : isVariableStatement(declaration.parent.parent) ? declaration.parent.parent : undefined; } + else { + return parent; + } +} - function getSourceFileLikeForImportDeclaration(node: ImporterOrCallExpression): SourceFileLike { - if (node.kind === SyntaxKind.CallExpression) { - return node.getSourceFile(); - } +function isNodeImport(node: Node): boolean { + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return (parent as ImportEqualsDeclaration).name === node && isExternalModuleImportEquals(parent as ImportEqualsDeclaration); + case SyntaxKind.ImportSpecifier: + // For a rename import `{ foo as bar }`, don't search for the imported symbol. Just find local uses of `bar`. + return !(parent as ImportSpecifier).propertyName; + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + Debug.assert((parent as ImportClause | NamespaceImport).name === node); + return true; + case SyntaxKind.BindingElement: + return isInJSFile(node) && isVariableDeclarationInitializedToBareOrAccessedRequire(parent.parent.parent); + default: + return false; + } +} - const { parent } = node; - if (parent.kind === SyntaxKind.SourceFile) { - return parent as SourceFile; +/** @internal */ +export function getExportInfo(exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker): ExportInfo | undefined { + const moduleSymbol = exportSymbol.parent; + if (!moduleSymbol) return undefined; // This can happen if an `export` is not at the top-level (which is a compile error). + const exportingModuleSymbol = checker.getMergedSymbol(moduleSymbol); // Need to get merged symbol in case there's an augmentation. + // `export` may appear in a namespace. In that case, just rely on global search. + return isExternalModuleSymbol(exportingModuleSymbol) ? { exportingModuleSymbol, exportKind } : undefined; +} + +/** If at an export specifier, go to the symbol it refers to. */ +function skipExportSpecifierSymbol(symbol: Symbol, checker: TypeChecker): Symbol { + // For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does. + if (symbol.declarations) { + for (const declaration of symbol.declarations) { + if (isExportSpecifier(declaration) && !declaration.propertyName && !declaration.parent.parent.moduleSpecifier) { + return checker.getExportSpecifierLocalTargetSymbol(declaration)!; + } + else if (isPropertyAccessExpression(declaration) && isModuleExportsAccessExpression(declaration.expression) && !isPrivateIdentifier(declaration.name)) { + // Export of form 'module.exports.propName = expr'; + return checker.getSymbolAtLocation(declaration)!; + } + else if (isShorthandPropertyAssignment(declaration) + && isBinaryExpression(declaration.parent.parent) + && getAssignmentDeclarationKind(declaration.parent.parent) === AssignmentDeclarationKind.ModuleExports) { + return checker.getExportSpecifierLocalTargetSymbol(declaration.name)!; + } } - Debug.assert(parent.kind === SyntaxKind.ModuleBlock); - return cast(parent.parent, isAmbientModuleDeclaration); } + return symbol; +} - function isAmbientModuleDeclaration(node: Node): node is AmbientModuleDeclaration { - return node.kind === SyntaxKind.ModuleDeclaration && (node as ModuleDeclaration).name.kind === SyntaxKind.StringLiteral; +function getContainingModuleSymbol(importer: Importer, checker: TypeChecker): Symbol { + return checker.getMergedSymbol(getSourceFileLikeForImportDeclaration(importer).symbol); +} + +function getSourceFileLikeForImportDeclaration(node: ImporterOrCallExpression): SourceFileLike { + if (node.kind === SyntaxKind.CallExpression) { + return node.getSourceFile(); } - function isExternalModuleImportEquals(eq: ImportEqualsDeclaration): eq is ImportEqualsDeclaration & { moduleReference: { expression: StringLiteral } } { - return eq.moduleReference.kind === SyntaxKind.ExternalModuleReference && eq.moduleReference.expression.kind === SyntaxKind.StringLiteral; + const { parent } = node; + if (parent.kind === SyntaxKind.SourceFile) { + return parent as SourceFile; } + Debug.assert(parent.kind === SyntaxKind.ModuleBlock); + return cast(parent.parent, isAmbientModuleDeclaration); +} + +function isAmbientModuleDeclaration(node: Node): node is AmbientModuleDeclaration { + return node.kind === SyntaxKind.ModuleDeclaration && (node as ModuleDeclaration).name.kind === SyntaxKind.StringLiteral; +} + +function isExternalModuleImportEquals(eq: ImportEqualsDeclaration): eq is ImportEqualsDeclaration & { moduleReference: { expression: StringLiteral } } { + return eq.moduleReference.kind === SyntaxKind.ExternalModuleReference && eq.moduleReference.expression.kind === SyntaxKind.StringLiteral; } diff --git a/src/services/inlayHints.ts b/src/services/inlayHints.ts index 7462a2c6f2c7c..2d890d7f31cfc 100644 --- a/src/services/inlayHints.ts +++ b/src/services/inlayHints.ts @@ -1,338 +1,351 @@ -/* @internal */ -namespace ts.InlayHints { +import { + __String, ArrowFunction, CallExpression, createPrinter, Debug, EmitHint, EnumMember, equateStringsCaseInsensitive, + Expression, findChildOfKind, forEachChild, FunctionDeclaration, FunctionExpression, FunctionLikeDeclaration, + GetAccessorDeclaration, getEffectiveReturnTypeNode, getEffectiveTypeAnnotationNode, getLanguageVariant, + getLeadingCommentRanges, hasContextSensitiveParameters, Identifier, InlayHint, InlayHintKind, InlayHintsContext, + isArrowFunction, isAssertionExpression, isBindingPattern, isCallExpression, isEnumMember, + isExpressionWithTypeArguments, isFunctionDeclaration, isFunctionExpression, isFunctionLikeDeclaration, + isGetAccessorDeclaration, isIdentifier, isIdentifierText, isInfinityOrNaNString, isLiteralExpression, + isMethodDeclaration, isNewExpression, isObjectLiteralExpression, isParameter, isParameterDeclaration, + isPropertyAccessExpression, isPropertyDeclaration, isTypeNode, isVarConst, isVariableDeclaration, MethodDeclaration, + NewExpression, Node, NodeBuilderFlags, ParameterDeclaration, PrefixUnaryExpression, PrinterOptions, + PropertyDeclaration, Signature, skipParentheses, some, Symbol, SymbolFlags, SyntaxKind, textSpanIntersectsWith, + Type, TypeFormatFlags, unescapeLeadingUnderscores, UserPreferences, usingSingleLineStringWriter, + VariableDeclaration, +} from "./_namespaces/ts"; + +const maxHintsLength = 30; + +const leadingParameterNameCommentRegexFactory = (name: string) => { + return new RegExp(`^\\s?/\\*\\*?\\s?${name}\\s?\\*\\/\\s?$`); +}; + +function shouldShowParameterNameHints(preferences: UserPreferences) { + return preferences.includeInlayParameterNameHints === "literals" || preferences.includeInlayParameterNameHints === "all"; +} - const maxHintsLength = 30; +function shouldShowLiteralParameterNameHintsOnly(preferences: UserPreferences) { + return preferences.includeInlayParameterNameHints === "literals"; +} - const leadingParameterNameCommentRegexFactory = (name: string) => { - return new RegExp(`^\\s?/\\*\\*?\\s?${name}\\s?\\*\\/\\s?$`); - }; +/** @internal */ +export function provideInlayHints(context: InlayHintsContext): InlayHint[] { + const { file, program, span, cancellationToken, preferences } = context; + const sourceFileText = file.text; + const compilerOptions = program.getCompilerOptions(); - function shouldShowParameterNameHints(preferences: UserPreferences) { - return preferences.includeInlayParameterNameHints === "literals" || preferences.includeInlayParameterNameHints === "all"; - } + const checker = program.getTypeChecker(); + const result: InlayHint[] = []; - function shouldShowLiteralParameterNameHintsOnly(preferences: UserPreferences) { - return preferences.includeInlayParameterNameHints === "literals"; - } + visitor(file); + return result; - export function provideInlayHints(context: InlayHintsContext): InlayHint[] { - const { file, program, span, cancellationToken, preferences } = context; - const sourceFileText = file.text; - const compilerOptions = program.getCompilerOptions(); + function visitor(node: Node): true | undefined { + if (!node || node.getFullWidth() === 0) { + return; + } - const checker = program.getTypeChecker(); - const result: InlayHint[] = []; + switch (node.kind) { + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.ArrowFunction: + cancellationToken.throwIfCancellationRequested(); + } - visitor(file); - return result; + if (!textSpanIntersectsWith(span, node.pos, node.getFullWidth())) { + return; + } - function visitor(node: Node): true | undefined { - if (!node || node.getFullWidth() === 0) { - return; - } + if (isTypeNode(node) && !isExpressionWithTypeArguments(node)) { + return; + } - switch (node.kind) { - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.ArrowFunction: - cancellationToken.throwIfCancellationRequested(); + if (preferences.includeInlayVariableTypeHints && isVariableDeclaration(node)) { + visitVariableLikeDeclaration(node); + } + else if (preferences.includeInlayPropertyDeclarationTypeHints && isPropertyDeclaration(node)) { + visitVariableLikeDeclaration(node); + } + else if (preferences.includeInlayEnumMemberValueHints && isEnumMember(node)) { + visitEnumMember(node); + } + else if (shouldShowParameterNameHints(preferences) && (isCallExpression(node) || isNewExpression(node))) { + visitCallOrNewExpression(node); + } + else { + if (preferences.includeInlayFunctionParameterTypeHints && isFunctionLikeDeclaration(node) && hasContextSensitiveParameters(node)) { + visitFunctionLikeForParameterType(node); } - - if (!textSpanIntersectsWith(span, node.pos, node.getFullWidth())) { - return; + if (preferences.includeInlayFunctionLikeReturnTypeHints && isSignatureSupportingReturnAnnotation(node)) { + visitFunctionDeclarationLikeForReturnType(node); } + } + return forEachChild(node, visitor); + } - if (isTypeNode(node) && !isExpressionWithTypeArguments(node)) { - return; - } + function isSignatureSupportingReturnAnnotation(node: Node): node is FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration { + return isArrowFunction(node) || isFunctionExpression(node) || isFunctionDeclaration(node) || isMethodDeclaration(node) || isGetAccessorDeclaration(node); + } - if (preferences.includeInlayVariableTypeHints && isVariableDeclaration(node)) { - visitVariableLikeDeclaration(node); - } - else if (preferences.includeInlayPropertyDeclarationTypeHints && isPropertyDeclaration(node)) { - visitVariableLikeDeclaration(node); - } - else if (preferences.includeInlayEnumMemberValueHints && isEnumMember(node)) { - visitEnumMember(node); - } - else if (shouldShowParameterNameHints(preferences) && (isCallExpression(node) || isNewExpression(node))) { - visitCallOrNewExpression(node); - } - else { - if (preferences.includeInlayFunctionParameterTypeHints && isFunctionLikeDeclaration(node) && hasContextSensitiveParameters(node)) { - visitFunctionLikeForParameterType(node); - } - if (preferences.includeInlayFunctionLikeReturnTypeHints && isSignatureSupportingReturnAnnotation(node)) { - visitFunctionDeclarationLikeForReturnType(node); - } - } - return forEachChild(node, visitor); - } + function addParameterHints(text: string, position: number, isFirstVariadicArgument: boolean) { + result.push({ + text: `${isFirstVariadicArgument ? "..." : ""}${truncation(text, maxHintsLength)}:`, + position, + kind: InlayHintKind.Parameter, + whitespaceAfter: true, + }); + } - function isSignatureSupportingReturnAnnotation(node: Node): node is FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration { - return isArrowFunction(node) || isFunctionExpression(node) || isFunctionDeclaration(node) || isMethodDeclaration(node) || isGetAccessorDeclaration(node); - } + function addTypeHints(text: string, position: number) { + result.push({ + text: `: ${truncation(text, maxHintsLength)}`, + position, + kind: InlayHintKind.Type, + whitespaceBefore: true, + }); + } - function addParameterHints(text: string, position: number, isFirstVariadicArgument: boolean) { - result.push({ - text: `${isFirstVariadicArgument ? "..." : ""}${truncation(text, maxHintsLength)}:`, - position, - kind: InlayHintKind.Parameter, - whitespaceAfter: true, - }); - } + function addEnumMemberValueHints(text: string, position: number) { + result.push({ + text: `= ${truncation(text, maxHintsLength)}`, + position, + kind: InlayHintKind.Enum, + whitespaceBefore: true, + }); + } - function addTypeHints(text: string, position: number) { - result.push({ - text: `: ${truncation(text, maxHintsLength)}`, - position, - kind: InlayHintKind.Type, - whitespaceBefore: true, - }); + function visitEnumMember(member: EnumMember) { + if (member.initializer) { + return; } - function addEnumMemberValueHints(text: string, position: number) { - result.push({ - text: `= ${truncation(text, maxHintsLength)}`, - position, - kind: InlayHintKind.Enum, - whitespaceBefore: true, - }); + const enumValue = checker.getConstantValue(member); + if (enumValue !== undefined) { + addEnumMemberValueHints(enumValue.toString(), member.end); } + } - function visitEnumMember(member: EnumMember) { - if (member.initializer) { - return; - } + function isModuleReferenceType(type: Type) { + return type.symbol && (type.symbol.flags & SymbolFlags.Module); + } - const enumValue = checker.getConstantValue(member); - if (enumValue !== undefined) { - addEnumMemberValueHints(enumValue.toString(), member.end); - } + function visitVariableLikeDeclaration(decl: VariableDeclaration | PropertyDeclaration) { + if (!decl.initializer || isBindingPattern(decl.name) || isVariableDeclaration(decl) && !isHintableDeclaration(decl)) { + return; } - function isModuleReferenceType(type: Type) { - return type.symbol && (type.symbol.flags & SymbolFlags.Module); + const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(decl); + if (effectiveTypeAnnotation) { + return; } - function visitVariableLikeDeclaration(decl: VariableDeclaration | PropertyDeclaration) { - if (!decl.initializer || isBindingPattern(decl.name) || isVariableDeclaration(decl) && !isHintableDeclaration(decl)) { - return; - } - - const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(decl); - if (effectiveTypeAnnotation) { - return; - } + const declarationType = checker.getTypeAtLocation(decl); + if (isModuleReferenceType(declarationType)) { + return; + } - const declarationType = checker.getTypeAtLocation(decl); - if (isModuleReferenceType(declarationType)) { + const typeDisplayString = printTypeInSingleLine(declarationType); + if (typeDisplayString) { + const isVariableNameMatchesType = preferences.includeInlayVariableTypeHintsWhenTypeMatchesName === false && equateStringsCaseInsensitive(decl.name.getText(), typeDisplayString); + if (isVariableNameMatchesType) { return; } + addTypeHints(typeDisplayString, decl.name.end); + } + } - const typeDisplayString = printTypeInSingleLine(declarationType); - if (typeDisplayString) { - const isVariableNameMatchesType = preferences.includeInlayVariableTypeHintsWhenTypeMatchesName === false && equateStringsCaseInsensitive(decl.name.getText(), typeDisplayString); - if (isVariableNameMatchesType) { - return; - } - addTypeHints(typeDisplayString, decl.name.end); - } + function visitCallOrNewExpression(expr: CallExpression | NewExpression) { + const args = expr.arguments; + if (!args || !args.length) { + return; } - function visitCallOrNewExpression(expr: CallExpression | NewExpression) { - const args = expr.arguments; - if (!args || !args.length) { - return; - } + const candidates: Signature[] = []; + const signature = checker.getResolvedSignatureForSignatureHelp(expr, candidates); + if (!signature || !candidates.length) { + return; + } - const candidates: Signature[] = []; - const signature = checker.getResolvedSignatureForSignatureHelp(expr, candidates); - if (!signature || !candidates.length) { - return; + for (let i = 0; i < args.length; ++i) { + const originalArg = args[i]; + const arg = skipParentheses(originalArg); + if (shouldShowLiteralParameterNameHintsOnly(preferences) && !isHintableLiteral(arg)) { + continue; } - for (let i = 0; i < args.length; ++i) { - const originalArg = args[i]; - const arg = skipParentheses(originalArg); - if (shouldShowLiteralParameterNameHintsOnly(preferences) && !isHintableLiteral(arg)) { + const identifierNameInfo = checker.getParameterIdentifierNameAtPosition(signature, i); + if (identifierNameInfo) { + const [parameterName, isFirstVariadicArgument] = identifierNameInfo; + const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName); + if (!isParameterNameNotSameAsArgument && !isFirstVariadicArgument) { continue; } - const identifierNameInfo = checker.getParameterIdentifierNameAtPosition(signature, i); - if (identifierNameInfo) { - const [parameterName, isFirstVariadicArgument] = identifierNameInfo; - const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName); - if (!isParameterNameNotSameAsArgument && !isFirstVariadicArgument) { - continue; - } - - const name = unescapeLeadingUnderscores(parameterName); - if (leadingCommentsContainsParameterName(arg, name)) { - continue; - } - - addParameterHints(name, originalArg.getStart(), isFirstVariadicArgument); + const name = unescapeLeadingUnderscores(parameterName); + if (leadingCommentsContainsParameterName(arg, name)) { + continue; } - } - } - function identifierOrAccessExpressionPostfixMatchesParameterName(expr: Expression, parameterName: __String) { - if (isIdentifier(expr)) { - return expr.text === parameterName; - } - if (isPropertyAccessExpression(expr)) { - return expr.name.text === parameterName; + addParameterHints(name, originalArg.getStart(), isFirstVariadicArgument); } - return false; } + } - function leadingCommentsContainsParameterName(node: Node, name: string) { - if (!isIdentifierText(name, compilerOptions.target, getLanguageVariant(file.scriptKind))) { - return false; - } - - const ranges = getLeadingCommentRanges(sourceFileText, node.pos); - if (!ranges?.length) { - return false; - } - - const regex = leadingParameterNameCommentRegexFactory(name); - return some(ranges, range => regex.test(sourceFileText.substring(range.pos, range.end))); + function identifierOrAccessExpressionPostfixMatchesParameterName(expr: Expression, parameterName: __String) { + if (isIdentifier(expr)) { + return expr.text === parameterName; } + if (isPropertyAccessExpression(expr)) { + return expr.name.text === parameterName; + } + return false; + } - function isHintableLiteral(node: Node) { - switch (node.kind) { - case SyntaxKind.PrefixUnaryExpression: { - const operand = (node as PrefixUnaryExpression).operand; - return isLiteralExpression(operand) || isIdentifier(operand) && isInfinityOrNaNString(operand.escapedText); - } - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateExpression: - return true; - case SyntaxKind.Identifier: { - const name = (node as Identifier).escapedText; - return isUndefined(name) || isInfinityOrNaNString(name); - } - } - return isLiteralExpression(node); + function leadingCommentsContainsParameterName(node: Node, name: string) { + if (!isIdentifierText(name, compilerOptions.target, getLanguageVariant(file.scriptKind))) { + return false; } - function visitFunctionDeclarationLikeForReturnType(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { - if (isArrowFunction(decl)) { - if (!findChildOfKind(decl, SyntaxKind.OpenParenToken, file)) { - return; - } - } + const ranges = getLeadingCommentRanges(sourceFileText, node.pos); + if (!ranges?.length) { + return false; + } - const effectiveTypeAnnotation = getEffectiveReturnTypeNode(decl); - if (effectiveTypeAnnotation || !decl.body) { - return; - } + const regex = leadingParameterNameCommentRegexFactory(name); + return some(ranges, range => regex.test(sourceFileText.substring(range.pos, range.end))); + } - const signature = checker.getSignatureFromDeclaration(decl); - if (!signature) { - return; + function isHintableLiteral(node: Node) { + switch (node.kind) { + case SyntaxKind.PrefixUnaryExpression: { + const operand = (node as PrefixUnaryExpression).operand; + return isLiteralExpression(operand) || isIdentifier(operand) && isInfinityOrNaNString(operand.escapedText); + } + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateExpression: + return true; + case SyntaxKind.Identifier: { + const name = (node as Identifier).escapedText; + return isUndefined(name) || isInfinityOrNaNString(name); } + } + return isLiteralExpression(node); + } - const returnType = checker.getReturnTypeOfSignature(signature); - if (isModuleReferenceType(returnType)) { + function visitFunctionDeclarationLikeForReturnType(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { + if (isArrowFunction(decl)) { + if (!findChildOfKind(decl, SyntaxKind.OpenParenToken, file)) { return; } + } - const typeDisplayString = printTypeInSingleLine(returnType); - if (!typeDisplayString) { - return; - } + const effectiveTypeAnnotation = getEffectiveReturnTypeNode(decl); + if (effectiveTypeAnnotation || !decl.body) { + return; + } - addTypeHints(typeDisplayString, getTypeAnnotationPosition(decl)); + const signature = checker.getSignatureFromDeclaration(decl); + if (!signature) { + return; } - function getTypeAnnotationPosition(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { - const closeParenToken = findChildOfKind(decl, SyntaxKind.CloseParenToken, file); - if (closeParenToken) { - return closeParenToken.end; - } - return decl.parameters.end; + const returnType = checker.getReturnTypeOfSignature(signature); + if (isModuleReferenceType(returnType)) { + return; } - function visitFunctionLikeForParameterType(node: FunctionLikeDeclaration) { - const signature = checker.getSignatureFromDeclaration(node); - if (!signature) { - return; - } + const typeDisplayString = printTypeInSingleLine(returnType); + if (!typeDisplayString) { + return; + } - for (let i = 0; i < node.parameters.length && i < signature.parameters.length; ++i) { - const param = node.parameters[i]; - if (!isHintableDeclaration(param)) { - continue; - } + addTypeHints(typeDisplayString, getTypeAnnotationPosition(decl)); + } - const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(param); - if (effectiveTypeAnnotation) { - continue; - } + function getTypeAnnotationPosition(decl: FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration) { + const closeParenToken = findChildOfKind(decl, SyntaxKind.CloseParenToken, file); + if (closeParenToken) { + return closeParenToken.end; + } + return decl.parameters.end; + } - const typeDisplayString = getParameterDeclarationTypeDisplayString(signature.parameters[i]); - if (!typeDisplayString) { - continue; - } + function visitFunctionLikeForParameterType(node: FunctionLikeDeclaration) { + const signature = checker.getSignatureFromDeclaration(node); + if (!signature) { + return; + } - addTypeHints(typeDisplayString, param.questionToken ? param.questionToken.end : param.name.end); + for (let i = 0; i < node.parameters.length && i < signature.parameters.length; ++i) { + const param = node.parameters[i]; + if (!isHintableDeclaration(param)) { + continue; } - } - function getParameterDeclarationTypeDisplayString(symbol: Symbol) { - const valueDeclaration = symbol.valueDeclaration; - if (!valueDeclaration || !isParameter(valueDeclaration)) { - return undefined; + const effectiveTypeAnnotation = getEffectiveTypeAnnotationNode(param); + if (effectiveTypeAnnotation) { + continue; } - const signatureParamType = checker.getTypeOfSymbolAtLocation(symbol, valueDeclaration); - if (isModuleReferenceType(signatureParamType)) { - return undefined; + const typeDisplayString = getParameterDeclarationTypeDisplayString(signature.parameters[i]); + if (!typeDisplayString) { + continue; } - return printTypeInSingleLine(signatureParamType); + addTypeHints(typeDisplayString, param.questionToken ? param.questionToken.end : param.name.end); } + } - function truncation(text: string, maxLength: number) { - if (text.length > maxLength) { - return text.substr(0, maxLength - "...".length) + "..."; - } - return text; + function getParameterDeclarationTypeDisplayString(symbol: Symbol) { + const valueDeclaration = symbol.valueDeclaration; + if (!valueDeclaration || !isParameter(valueDeclaration)) { + return undefined; } - function printTypeInSingleLine(type: Type) { - const flags = NodeBuilderFlags.IgnoreErrors | TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope; - const options: PrinterOptions = { removeComments: true }; - const printer = createPrinter(options); - - return usingSingleLineStringWriter(writer => { - const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags, writer); - Debug.assertIsDefined(typeNode, "should always get typenode"); - printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ file, writer); - }); + const signatureParamType = checker.getTypeOfSymbolAtLocation(symbol, valueDeclaration); + if (isModuleReferenceType(signatureParamType)) { + return undefined; } - function isUndefined(name: __String) { - return name === "undefined"; + return printTypeInSingleLine(signatureParamType); + } + + function truncation(text: string, maxLength: number) { + if (text.length > maxLength) { + return text.substr(0, maxLength - "...".length) + "..."; } + return text; + } - function isHintableDeclaration(node: VariableDeclaration | ParameterDeclaration) { - if ((isParameterDeclaration(node) || isVariableDeclaration(node) && isVarConst(node)) && node.initializer) { - const initializer = skipParentheses(node.initializer); - return !(isHintableLiteral(initializer) || isNewExpression(initializer) || isObjectLiteralExpression(initializer) || isAssertionExpression(initializer)); - } - return true; + function printTypeInSingleLine(type: Type) { + const flags = NodeBuilderFlags.IgnoreErrors | TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope; + const options: PrinterOptions = { removeComments: true }; + const printer = createPrinter(options); + + return usingSingleLineStringWriter(writer => { + const typeNode = checker.typeToTypeNode(type, /*enclosingDeclaration*/ undefined, flags, writer); + Debug.assertIsDefined(typeNode, "should always get typenode"); + printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ file, writer); + }); + } + + function isUndefined(name: __String) { + return name === "undefined"; + } + + function isHintableDeclaration(node: VariableDeclaration | ParameterDeclaration) { + if ((isParameterDeclaration(node) || isVariableDeclaration(node) && isVarConst(node)) && node.initializer) { + const initializer = skipParentheses(node.initializer); + return !(isHintableLiteral(initializer) || isNewExpression(initializer) || isObjectLiteralExpression(initializer) || isAssertionExpression(initializer)); } + return true; } } diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index a8d4c1a4e03fd..b6b7ba77b0cbe 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -1,512 +1,536 @@ -/* @internal */ -namespace ts.JsDoc { - const jsDocTagNames = [ - "abstract", - "access", - "alias", - "argument", - "async", - "augments", - "author", - "borrows", - "callback", - "class", - "classdesc", - "constant", - "constructor", - "constructs", - "copyright", - "default", - "deprecated", - "description", - "emits", - "enum", - "event", - "example", - "exports", - "extends", - "external", - "field", - "file", - "fileoverview", - "fires", - "function", - "generator", - "global", - "hideconstructor", - "host", - "ignore", - "implements", - "inheritdoc", - "inner", - "instance", - "interface", - "kind", - "lends", - "license", - "link", - "listens", - "member", - "memberof", - "method", - "mixes", - "module", - "name", - "namespace", - "override", - "package", - "param", - "private", - "property", - "protected", - "public", - "readonly", - "requires", - "returns", - "see", - "since", - "static", - "summary", - "template", - "this", - "throws", - "todo", - "tutorial", - "type", - "typedef", - "var", - "variation", - "version", - "virtual", - "yields" - ]; - let jsDocTagNameCompletionEntries: CompletionEntry[]; - let jsDocTagCompletionEntries: CompletionEntry[]; - - export function getJsDocCommentsFromDeclarations(declarations: readonly Declaration[], checker?: TypeChecker): SymbolDisplayPart[] { - // Only collect doc comments from duplicate declarations once: - // In case of a union property there might be same declaration multiple times - // which only varies in type parameter - // Eg. const a: Array | Array; a.length - // The property length will have two declarations of property length coming - // from Array - Array and Array - const parts: SymbolDisplayPart[][] = []; - forEachUnique(declarations, declaration => { - for (const jsdoc of getCommentHavingNodes(declaration)) { - const inheritDoc = isJSDoc(jsdoc) && jsdoc.tags && find(jsdoc.tags, t => t.kind === SyntaxKind.JSDocTag && (t.tagName.escapedText === "inheritDoc" || t.tagName.escapedText === "inheritdoc")); - // skip comments containing @typedefs since they're not associated with particular declarations - // Exceptions: - // - @typedefs are themselves declarations with associated comments - // - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation - if (jsdoc.comment === undefined && !inheritDoc - || isJSDoc(jsdoc) - && declaration.kind !== SyntaxKind.JSDocTypedefTag && declaration.kind !== SyntaxKind.JSDocCallbackTag - && jsdoc.tags - && jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag) - && !jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) { - continue; - } - let newparts = jsdoc.comment ? getDisplayPartsFromComment(jsdoc.comment, checker) : []; - if (inheritDoc && inheritDoc.comment) { - newparts = newparts.concat(getDisplayPartsFromComment(inheritDoc.comment, checker)); - } - if (!contains(parts, newparts, isIdenticalListOfDisplayParts)) { - parts.push(newparts); - } - } - }); - return flatten(intersperse(parts, [lineBreakPart()])); - } - - function isIdenticalListOfDisplayParts(parts1: SymbolDisplayPart[], parts2: SymbolDisplayPart[]) { - return arraysEqual(parts1, parts2, (p1, p2) => p1.kind === p2.kind && p1.text === p2.text); - } - - function getCommentHavingNodes(declaration: Declaration): readonly (JSDoc | JSDocTag)[] { - switch (declaration.kind) { - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocPropertyTag: - return [declaration as JSDocPropertyTag]; - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocTypedefTag: - return [(declaration as JSDocTypedefTag), (declaration as JSDocTypedefTag).parent]; - default: - return getJSDocCommentsAndTags(declaration); - } - } - - export function getJsDocTagsFromDeclarations(declarations?: Declaration[], checker?: TypeChecker): JSDocTagInfo[] { - // Only collect doc comments from duplicate declarations once. - const infos: JSDocTagInfo[] = []; - forEachUnique(declarations, declaration => { - const tags = getJSDocTags(declaration); +import { + arraysEqual, ArrowFunction, AssignmentDeclarationKind, BinaryExpression, buildLinkParts, ClassExpression, + CompletionEntry, CompletionEntryDetails, Completions, ConstructorDeclaration, contains, Declaration, + DocCommentTemplateOptions, emptyArray, Expression, ExpressionStatement, find, findAncestor, flatMap, flatten, + forEach, forEachAncestor, forEachReturnStatement, forEachUnique, FunctionDeclaration, FunctionExpression, + getAssignmentDeclarationKind, getJSDocCommentsAndTags, getJSDocTags, getLineStartPositionForPosition, + getTokenAtPosition, hasJSDocNodes, hasJSFileExtension, intersperse, isArrowFunction, isBlock, + isConstructorDeclaration, isExpression, isFunctionExpression, isFunctionLike, isFunctionLikeDeclaration, + isFunctionTypeNode, isIdentifier, isJSDoc, isJSDocParameterTag, isWhiteSpaceSingleLine, JSDoc, JSDocAugmentsTag, + JSDocCallbackTag, JSDocComment, JSDocImplementsTag, JSDocParameterTag, JSDocPropertyTag, JSDocSeeTag, JSDocTag, + JSDocTagInfo, JSDocTemplateTag, JSDocTypedefTag, JSDocTypeTag, lastOrUndefined, length, lineBreakPart, map, + mapDefined, MethodDeclaration, MethodSignature, Node, ParameterDeclaration, parameterNamePart, + ParenthesizedExpression, PropertyAssignment, PropertyDeclaration, propertyNamePart, PropertySignature, + punctuationPart, ScriptElementKind, SourceFile, spacePart, startsWith, SymbolDisplayPart, SyntaxKind, TextInsertion, + textPart, typeAliasNamePart, TypeChecker, typeParameterNamePart, VariableStatement, +} from "./_namespaces/ts"; + +const jsDocTagNames = [ + "abstract", + "access", + "alias", + "argument", + "async", + "augments", + "author", + "borrows", + "callback", + "class", + "classdesc", + "constant", + "constructor", + "constructs", + "copyright", + "default", + "deprecated", + "description", + "emits", + "enum", + "event", + "example", + "exports", + "extends", + "external", + "field", + "file", + "fileoverview", + "fires", + "function", + "generator", + "global", + "hideconstructor", + "host", + "ignore", + "implements", + "inheritdoc", + "inner", + "instance", + "interface", + "kind", + "lends", + "license", + "link", + "listens", + "member", + "memberof", + "method", + "mixes", + "module", + "name", + "namespace", + "override", + "package", + "param", + "private", + "property", + "protected", + "public", + "readonly", + "requires", + "returns", + "see", + "since", + "static", + "summary", + "template", + "this", + "throws", + "todo", + "tutorial", + "type", + "typedef", + "var", + "variation", + "version", + "virtual", + "yields" +]; +let jsDocTagNameCompletionEntries: CompletionEntry[]; +let jsDocTagCompletionEntries: CompletionEntry[]; + +/** @internal */ +export function getJsDocCommentsFromDeclarations(declarations: readonly Declaration[], checker?: TypeChecker): SymbolDisplayPart[] { + // Only collect doc comments from duplicate declarations once: + // In case of a union property there might be same declaration multiple times + // which only varies in type parameter + // Eg. const a: Array | Array; a.length + // The property length will have two declarations of property length coming + // from Array - Array and Array + const parts: SymbolDisplayPart[][] = []; + forEachUnique(declarations, declaration => { + for (const jsdoc of getCommentHavingNodes(declaration)) { + const inheritDoc = isJSDoc(jsdoc) && jsdoc.tags && find(jsdoc.tags, t => t.kind === SyntaxKind.JSDocTag && (t.tagName.escapedText === "inheritDoc" || t.tagName.escapedText === "inheritdoc")); // skip comments containing @typedefs since they're not associated with particular declarations // Exceptions: + // - @typedefs are themselves declarations with associated comments // - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation - if (tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag) - && !tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) { - return; + if (jsdoc.comment === undefined && !inheritDoc + || isJSDoc(jsdoc) + && declaration.kind !== SyntaxKind.JSDocTypedefTag && declaration.kind !== SyntaxKind.JSDocCallbackTag + && jsdoc.tags + && jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag) + && !jsdoc.tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) { + continue; } - for (const tag of tags) { - infos.push({ name: tag.tagName.text, text: getCommentDisplayParts(tag, checker) }); + let newparts = jsdoc.comment ? getDisplayPartsFromComment(jsdoc.comment, checker) : []; + if (inheritDoc && inheritDoc.comment) { + newparts = newparts.concat(getDisplayPartsFromComment(inheritDoc.comment, checker)); } - }); - return infos; + if (!contains(parts, newparts, isIdenticalListOfDisplayParts)) { + parts.push(newparts); + } + } + }); + return flatten(intersperse(parts, [lineBreakPart()])); +} + +function isIdenticalListOfDisplayParts(parts1: SymbolDisplayPart[], parts2: SymbolDisplayPart[]) { + return arraysEqual(parts1, parts2, (p1, p2) => p1.kind === p2.kind && p1.text === p2.text); +} + +function getCommentHavingNodes(declaration: Declaration): readonly (JSDoc | JSDocTag)[] { + switch (declaration.kind) { + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocPropertyTag: + return [declaration as JSDocPropertyTag]; + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + return [(declaration as JSDocTypedefTag), (declaration as JSDocTypedefTag).parent]; + default: + return getJSDocCommentsAndTags(declaration); } +} - function getDisplayPartsFromComment(comment: string | readonly JSDocComment[], checker: TypeChecker | undefined): SymbolDisplayPart[] { - if (typeof comment === "string") { - return [textPart(comment)]; +/** @internal */ +export function getJsDocTagsFromDeclarations(declarations?: Declaration[], checker?: TypeChecker): JSDocTagInfo[] { + // Only collect doc comments from duplicate declarations once. + const infos: JSDocTagInfo[] = []; + forEachUnique(declarations, declaration => { + const tags = getJSDocTags(declaration); + // skip comments containing @typedefs since they're not associated with particular declarations + // Exceptions: + // - @param or @return indicate that the author thinks of it as a 'local' @typedef that's part of the function documentation + if (tags.some(t => t.kind === SyntaxKind.JSDocTypedefTag || t.kind === SyntaxKind.JSDocCallbackTag) + && !tags.some(t => t.kind === SyntaxKind.JSDocParameterTag || t.kind === SyntaxKind.JSDocReturnTag)) { + return; + } + for (const tag of tags) { + infos.push({ name: tag.tagName.text, text: getCommentDisplayParts(tag, checker) }); } - return flatMap( - comment, - node => node.kind === SyntaxKind.JSDocText ? [textPart(node.text)] : buildLinkParts(node, checker) - ) as SymbolDisplayPart[]; + }); + return infos; +} + +function getDisplayPartsFromComment(comment: string | readonly JSDocComment[], checker: TypeChecker | undefined): SymbolDisplayPart[] { + if (typeof comment === "string") { + return [textPart(comment)]; } + return flatMap( + comment, + node => node.kind === SyntaxKind.JSDocText ? [textPart(node.text)] : buildLinkParts(node, checker) + ) as SymbolDisplayPart[]; +} - function getCommentDisplayParts(tag: JSDocTag, checker?: TypeChecker): SymbolDisplayPart[] | undefined { - const { comment, kind } = tag; - const namePart = getTagNameDisplayPart(kind); - switch (kind) { - case SyntaxKind.JSDocImplementsTag: - return withNode((tag as JSDocImplementsTag).class); - case SyntaxKind.JSDocAugmentsTag: - return withNode((tag as JSDocAugmentsTag).class); - case SyntaxKind.JSDocTemplateTag: - const templateTag = tag as JSDocTemplateTag; - const displayParts: SymbolDisplayPart[] = []; - if (templateTag.constraint) { - displayParts.push(textPart(templateTag.constraint.getText())); +function getCommentDisplayParts(tag: JSDocTag, checker?: TypeChecker): SymbolDisplayPart[] | undefined { + const { comment, kind } = tag; + const namePart = getTagNameDisplayPart(kind); + switch (kind) { + case SyntaxKind.JSDocImplementsTag: + return withNode((tag as JSDocImplementsTag).class); + case SyntaxKind.JSDocAugmentsTag: + return withNode((tag as JSDocAugmentsTag).class); + case SyntaxKind.JSDocTemplateTag: + const templateTag = tag as JSDocTemplateTag; + const displayParts: SymbolDisplayPart[] = []; + if (templateTag.constraint) { + displayParts.push(textPart(templateTag.constraint.getText())); + } + if (length(templateTag.typeParameters)) { + if (length(displayParts)) { + displayParts.push(spacePart()); } - if (length(templateTag.typeParameters)) { - if (length(displayParts)) { - displayParts.push(spacePart()); + const lastTypeParameter = templateTag.typeParameters[templateTag.typeParameters.length - 1]; + forEach(templateTag.typeParameters, tp => { + displayParts.push(namePart(tp.getText())); + if (lastTypeParameter !== tp) { + displayParts.push(...[punctuationPart(SyntaxKind.CommaToken), spacePart()]); } - const lastTypeParameter = templateTag.typeParameters[templateTag.typeParameters.length - 1]; - forEach(templateTag.typeParameters, tp => { - displayParts.push(namePart(tp.getText())); - if (lastTypeParameter !== tp) { - displayParts.push(...[punctuationPart(SyntaxKind.CommaToken), spacePart()]); - } - }); - } - if (comment) { - displayParts.push(...[spacePart(), ...getDisplayPartsFromComment(comment, checker)]); - } - return displayParts; - case SyntaxKind.JSDocTypeTag: - return withNode((tag as JSDocTypeTag).typeExpression); - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocPropertyTag: - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocSeeTag: - const { name } = tag as JSDocTypedefTag | JSDocCallbackTag | JSDocPropertyTag | JSDocParameterTag | JSDocSeeTag; - return name ? withNode(name) - : comment === undefined ? undefined - : getDisplayPartsFromComment(comment, checker); - default: - return comment === undefined ? undefined : getDisplayPartsFromComment(comment, checker); - } + }); + } + if (comment) { + displayParts.push(...[spacePart(), ...getDisplayPartsFromComment(comment, checker)]); + } + return displayParts; + case SyntaxKind.JSDocTypeTag: + return withNode((tag as JSDocTypeTag).typeExpression); + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocPropertyTag: + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocSeeTag: + const { name } = tag as JSDocTypedefTag | JSDocCallbackTag | JSDocPropertyTag | JSDocParameterTag | JSDocSeeTag; + return name ? withNode(name) + : comment === undefined ? undefined + : getDisplayPartsFromComment(comment, checker); + default: + return comment === undefined ? undefined : getDisplayPartsFromComment(comment, checker); + } - function withNode(node: Node) { - return addComment(node.getText()); - } + function withNode(node: Node) { + return addComment(node.getText()); + } - function addComment(s: string) { - if (comment) { - if (s.match(/^https?$/)) { - return [textPart(s), ...getDisplayPartsFromComment(comment, checker)]; - } - else { - return [namePart(s), spacePart(), ...getDisplayPartsFromComment(comment, checker)]; - } + function addComment(s: string) { + if (comment) { + if (s.match(/^https?$/)) { + return [textPart(s), ...getDisplayPartsFromComment(comment, checker)]; } else { - return [textPart(s)]; + return [namePart(s), spacePart(), ...getDisplayPartsFromComment(comment, checker)]; } } - } - - function getTagNameDisplayPart(kind: SyntaxKind): (text: string) => SymbolDisplayPart { - switch (kind) { - case SyntaxKind.JSDocParameterTag: - return parameterNamePart; - case SyntaxKind.JSDocPropertyTag: - return propertyNamePart; - case SyntaxKind.JSDocTemplateTag: - return typeParameterNamePart; - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - return typeAliasNamePart; - default: - return textPart; + else { + return [textPart(s)]; } } +} - export function getJSDocTagNameCompletions(): CompletionEntry[] { - return jsDocTagNameCompletionEntries || (jsDocTagNameCompletionEntries = map(jsDocTagNames, tagName => { - return { - name: tagName, - kind: ScriptElementKind.keyword, - kindModifiers: "", - sortText: Completions.SortText.LocationPriority, - }; - })); - } - - export const getJSDocTagNameCompletionDetails = getJSDocTagCompletionDetails; - - export function getJSDocTagCompletions(): CompletionEntry[] { - return jsDocTagCompletionEntries || (jsDocTagCompletionEntries = map(jsDocTagNames, tagName => { - return { - name: `@${tagName}`, - kind: ScriptElementKind.keyword, - kindModifiers: "", - sortText: Completions.SortText.LocationPriority - }; - })); +function getTagNameDisplayPart(kind: SyntaxKind): (text: string) => SymbolDisplayPart { + switch (kind) { + case SyntaxKind.JSDocParameterTag: + return parameterNamePart; + case SyntaxKind.JSDocPropertyTag: + return propertyNamePart; + case SyntaxKind.JSDocTemplateTag: + return typeParameterNamePart; + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + return typeAliasNamePart; + default: + return textPart; } +} - export function getJSDocTagCompletionDetails(name: string): CompletionEntryDetails { +/** @internal */ +export function getJSDocTagNameCompletions(): CompletionEntry[] { + return jsDocTagNameCompletionEntries || (jsDocTagNameCompletionEntries = map(jsDocTagNames, tagName => { return { - name, - kind: ScriptElementKind.unknown, // TODO: should have its own kind? + name: tagName, + kind: ScriptElementKind.keyword, kindModifiers: "", - displayParts: [textPart(name)], - documentation: emptyArray, - tags: undefined, - codeActions: undefined, + sortText: Completions.SortText.LocationPriority, }; - } - - export function getJSDocParameterNameCompletions(tag: JSDocParameterTag): CompletionEntry[] { - if (!isIdentifier(tag.name)) { - return emptyArray; - } - const nameThusFar = tag.name.text; - const jsdoc = tag.parent; - const fn = jsdoc.parent; - if (!isFunctionLike(fn)) return []; - - return mapDefined(fn.parameters, param => { - if (!isIdentifier(param.name)) return undefined; - - const name = param.name.text; - if (jsdoc.tags!.some(t => t !== tag && isJSDocParameterTag(t) && isIdentifier(t.name) && t.name.escapedText === name) // TODO: GH#18217 - || nameThusFar !== undefined && !startsWith(name, nameThusFar)) { - return undefined; - } + })); +} - return { name, kind: ScriptElementKind.parameterElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority }; - }); - } +/** @internal */ +export const getJSDocTagNameCompletionDetails = getJSDocTagCompletionDetails; - export function getJSDocParameterNameCompletionDetails(name: string): CompletionEntryDetails { +/** @internal */ +export function getJSDocTagCompletions(): CompletionEntry[] { + return jsDocTagCompletionEntries || (jsDocTagCompletionEntries = map(jsDocTagNames, tagName => { return { - name, - kind: ScriptElementKind.parameterElement, + name: `@${tagName}`, + kind: ScriptElementKind.keyword, kindModifiers: "", - displayParts: [textPart(name)], - documentation: emptyArray, - tags: undefined, - codeActions: undefined, + sortText: Completions.SortText.LocationPriority }; + })); +} + +/** @internal */ +export function getJSDocTagCompletionDetails(name: string): CompletionEntryDetails { + return { + name, + kind: ScriptElementKind.unknown, // TODO: should have its own kind? + kindModifiers: "", + displayParts: [textPart(name)], + documentation: emptyArray, + tags: undefined, + codeActions: undefined, + }; +} + +/** @internal */ +export function getJSDocParameterNameCompletions(tag: JSDocParameterTag): CompletionEntry[] { + if (!isIdentifier(tag.name)) { + return emptyArray; } + const nameThusFar = tag.name.text; + const jsdoc = tag.parent; + const fn = jsdoc.parent; + if (!isFunctionLike(fn)) return []; - /** - * Checks if position points to a valid position to add JSDoc comments, and if so, - * returns the appropriate template. Otherwise returns an empty string. - * Valid positions are - * - outside of comments, statements, and expressions, and - * - preceding a: - * - function/constructor/method declaration - * - class declarations - * - variable statements - * - namespace declarations - * - interface declarations - * - method signatures - * - type alias declarations - * - * Hosts should ideally check that: - * - The line is all whitespace up to 'position' before performing the insertion. - * - If the keystroke sequence "/\*\*" induced the call, we also check that the next - * non-whitespace character is '*', which (approximately) indicates whether we added - * the second '*' to complete an existing (JSDoc) comment. - * @param fileName The file in which to perform the check. - * @param position The (character-indexed) position in the file where the check should - * be performed. - */ - export function getDocCommentTemplateAtPosition(newLine: string, sourceFile: SourceFile, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined { - const tokenAtPos = getTokenAtPosition(sourceFile, position); - const existingDocComment = findAncestor(tokenAtPos, isJSDoc); - if (existingDocComment && (existingDocComment.comment !== undefined || length(existingDocComment.tags))) { - // Non-empty comment already exists. - return undefined; - } + return mapDefined(fn.parameters, param => { + if (!isIdentifier(param.name)) return undefined; - const tokenStart = tokenAtPos.getStart(sourceFile); - // Don't provide a doc comment template based on a *previous* node. (But an existing empty jsdoc comment will likely start before `position`.) - if (!existingDocComment && tokenStart < position) { + const name = param.name.text; + if (jsdoc.tags!.some(t => t !== tag && isJSDocParameterTag(t) && isIdentifier(t.name) && t.name.escapedText === name) // TODO: GH#18217 + || nameThusFar !== undefined && !startsWith(name, nameThusFar)) { return undefined; } - const commentOwnerInfo = getCommentOwnerInfo(tokenAtPos, options); - if (!commentOwnerInfo) { - return undefined; - } + return { name, kind: ScriptElementKind.parameterElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority }; + }); +} - const { commentOwner, parameters, hasReturn } = commentOwnerInfo; - const commentOwnerJsDoc = hasJSDocNodes(commentOwner) && commentOwner.jsDoc ? commentOwner.jsDoc : undefined; - const lastJsDoc = lastOrUndefined(commentOwnerJsDoc); - if (commentOwner.getStart(sourceFile) < position - || lastJsDoc - && existingDocComment - && lastJsDoc !== existingDocComment) { - return undefined; - } +/** @internal */ +export function getJSDocParameterNameCompletionDetails(name: string): CompletionEntryDetails { + return { + name, + kind: ScriptElementKind.parameterElement, + kindModifiers: "", + displayParts: [textPart(name)], + documentation: emptyArray, + tags: undefined, + codeActions: undefined, + }; +} - const indentationStr = getIndentationStringAtPosition(sourceFile, position); - const isJavaScriptFile = hasJSFileExtension(sourceFile.fileName); - const tags = - (parameters ? parameterDocComments(parameters || [], isJavaScriptFile, indentationStr, newLine) : "") + - (hasReturn ? returnsDocComment(indentationStr, newLine) : ""); - - // A doc comment consists of the following - // * The opening comment line - // * the first line (without a param) for the object's untagged info (this is also where the caret ends up) - // * the '@param'-tagged lines - // * the '@returns'-tag - // * TODO: other tags. - // * the closing comment line - // * if the caret was directly in front of the object, then we add an extra line and indentation. - const openComment = "/**"; - const closeComment = " */"; - - // If any of the existing jsDoc has tags, ignore adding new ones. - const hasTag = (commentOwnerJsDoc || []).some(jsDoc => !!jsDoc.tags); - - if (tags && !hasTag) { - const preamble = openComment + newLine + indentationStr + " * "; - const endLine = tokenStart === position ? newLine + indentationStr : ""; - const result = preamble + newLine + tags + indentationStr + closeComment + endLine; - return { newText: result, caretOffset: preamble.length }; - } - return { newText: openComment + closeComment, caretOffset: 3 }; +/** + * Checks if position points to a valid position to add JSDoc comments, and if so, + * returns the appropriate template. Otherwise returns an empty string. + * Valid positions are + * - outside of comments, statements, and expressions, and + * - preceding a: + * - function/constructor/method declaration + * - class declarations + * - variable statements + * - namespace declarations + * - interface declarations + * - method signatures + * - type alias declarations + * + * Hosts should ideally check that: + * - The line is all whitespace up to 'position' before performing the insertion. + * - If the keystroke sequence "/\*\*" induced the call, we also check that the next + * non-whitespace character is '*', which (approximately) indicates whether we added + * the second '*' to complete an existing (JSDoc) comment. + * @param fileName The file in which to perform the check. + * @param position The (character-indexed) position in the file where the check should + * be performed. + * + * @internal + */ +export function getDocCommentTemplateAtPosition(newLine: string, sourceFile: SourceFile, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined { + const tokenAtPos = getTokenAtPosition(sourceFile, position); + const existingDocComment = findAncestor(tokenAtPos, isJSDoc); + if (existingDocComment && (existingDocComment.comment !== undefined || length(existingDocComment.tags))) { + // Non-empty comment already exists. + return undefined; } - function getIndentationStringAtPosition(sourceFile: SourceFile, position: number): string { - const { text } = sourceFile; - const lineStart = getLineStartPositionForPosition(position, sourceFile); - let pos = lineStart; - for (; pos <= position && isWhiteSpaceSingleLine(text.charCodeAt(pos)); pos++); - return text.slice(lineStart, pos); + const tokenStart = tokenAtPos.getStart(sourceFile); + // Don't provide a doc comment template based on a *previous* node. (But an existing empty jsdoc comment will likely start before `position`.) + if (!existingDocComment && tokenStart < position) { + return undefined; } - function parameterDocComments(parameters: readonly ParameterDeclaration[], isJavaScriptFile: boolean, indentationStr: string, newLine: string): string { - return parameters.map(({ name, dotDotDotToken }, i) => { - const paramName = name.kind === SyntaxKind.Identifier ? name.text : "param" + i; - const type = isJavaScriptFile ? (dotDotDotToken ? "{...any} " : "{any} ") : ""; - return `${indentationStr} * @param ${type}${paramName}${newLine}`; - }).join(""); + const commentOwnerInfo = getCommentOwnerInfo(tokenAtPos, options); + if (!commentOwnerInfo) { + return undefined; } - function returnsDocComment(indentationStr: string, newLine: string) { - return `${indentationStr} * @returns${newLine}`; + const { commentOwner, parameters, hasReturn } = commentOwnerInfo; + const commentOwnerJsDoc = hasJSDocNodes(commentOwner) && commentOwner.jsDoc ? commentOwner.jsDoc : undefined; + const lastJsDoc = lastOrUndefined(commentOwnerJsDoc); + if (commentOwner.getStart(sourceFile) < position + || lastJsDoc + && existingDocComment + && lastJsDoc !== existingDocComment) { + return undefined; } - interface CommentOwnerInfo { - readonly commentOwner: Node; - readonly parameters?: readonly ParameterDeclaration[]; - readonly hasReturn?: boolean; - } - function getCommentOwnerInfo(tokenAtPos: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined { - return forEachAncestor(tokenAtPos, n => getCommentOwnerInfoWorker(n, options)); + const indentationStr = getIndentationStringAtPosition(sourceFile, position); + const isJavaScriptFile = hasJSFileExtension(sourceFile.fileName); + const tags = + (parameters ? parameterDocComments(parameters || [], isJavaScriptFile, indentationStr, newLine) : "") + + (hasReturn ? returnsDocComment(indentationStr, newLine) : ""); + + // A doc comment consists of the following + // * The opening comment line + // * the first line (without a param) for the object's untagged info (this is also where the caret ends up) + // * the '@param'-tagged lines + // * the '@returns'-tag + // * TODO: other tags. + // * the closing comment line + // * if the caret was directly in front of the object, then we add an extra line and indentation. + const openComment = "/**"; + const closeComment = " */"; + + // If any of the existing jsDoc has tags, ignore adding new ones. + const hasTag = (commentOwnerJsDoc || []).some(jsDoc => !!jsDoc.tags); + + if (tags && !hasTag) { + const preamble = openComment + newLine + indentationStr + " * "; + const endLine = tokenStart === position ? newLine + indentationStr : ""; + const result = preamble + newLine + tags + indentationStr + closeComment + endLine; + return { newText: result, caretOffset: preamble.length }; } - function getCommentOwnerInfoWorker(commentOwner: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined | "quit" { - switch (commentOwner.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.MethodSignature: - case SyntaxKind.ArrowFunction: - const host = commentOwner as ArrowFunction | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature; - return { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) }; - - case SyntaxKind.PropertyAssignment: - return getCommentOwnerInfoWorker((commentOwner as PropertyAssignment).initializer, options); - - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.EnumMember: - case SyntaxKind.TypeAliasDeclaration: - return { commentOwner }; - - case SyntaxKind.PropertySignature: { - const host = commentOwner as PropertySignature; - return host.type && isFunctionTypeNode(host.type) - ? { commentOwner, parameters: host.type.parameters, hasReturn: hasReturn(host.type, options) } - : { commentOwner }; - } + return { newText: openComment + closeComment, caretOffset: 3 }; +} - case SyntaxKind.VariableStatement: { - const varStatement = commentOwner as VariableStatement; - const varDeclarations = varStatement.declarationList.declarations; - const host = varDeclarations.length === 1 && varDeclarations[0].initializer - ? getRightHandSideOfAssignment(varDeclarations[0].initializer) - : undefined; - return host - ? { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) } - : { commentOwner }; - } +function getIndentationStringAtPosition(sourceFile: SourceFile, position: number): string { + const { text } = sourceFile; + const lineStart = getLineStartPositionForPosition(position, sourceFile); + let pos = lineStart; + for (; pos <= position && isWhiteSpaceSingleLine(text.charCodeAt(pos)); pos++); + return text.slice(lineStart, pos); +} - case SyntaxKind.SourceFile: - return "quit"; +function parameterDocComments(parameters: readonly ParameterDeclaration[], isJavaScriptFile: boolean, indentationStr: string, newLine: string): string { + return parameters.map(({ name, dotDotDotToken }, i) => { + const paramName = name.kind === SyntaxKind.Identifier ? name.text : "param" + i; + const type = isJavaScriptFile ? (dotDotDotToken ? "{...any} " : "{any} ") : ""; + return `${indentationStr} * @param ${type}${paramName}${newLine}`; + }).join(""); +} - case SyntaxKind.ModuleDeclaration: - // If in walking up the tree, we hit a a nested namespace declaration, - // then we must be somewhere within a dotted namespace name; however we don't - // want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'. - return commentOwner.parent.kind === SyntaxKind.ModuleDeclaration ? undefined : { commentOwner }; - - case SyntaxKind.ExpressionStatement: - return getCommentOwnerInfoWorker((commentOwner as ExpressionStatement).expression, options); - case SyntaxKind.BinaryExpression: { - const be = commentOwner as BinaryExpression; - if (getAssignmentDeclarationKind(be) === AssignmentDeclarationKind.None) { - return "quit"; - } - return isFunctionLike(be.right) - ? { commentOwner, parameters: be.right.parameters, hasReturn: hasReturn(be.right, options) } - : { commentOwner }; +function returnsDocComment(indentationStr: string, newLine: string) { + return `${indentationStr} * @returns${newLine}`; +} + +interface CommentOwnerInfo { + readonly commentOwner: Node; + readonly parameters?: readonly ParameterDeclaration[]; + readonly hasReturn?: boolean; +} +function getCommentOwnerInfo(tokenAtPos: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined { + return forEachAncestor(tokenAtPos, n => getCommentOwnerInfoWorker(n, options)); +} +function getCommentOwnerInfoWorker(commentOwner: Node, options: DocCommentTemplateOptions | undefined): CommentOwnerInfo | undefined | "quit" { + switch (commentOwner.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.MethodSignature: + case SyntaxKind.ArrowFunction: + const host = commentOwner as ArrowFunction | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | MethodSignature; + return { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) }; + + case SyntaxKind.PropertyAssignment: + return getCommentOwnerInfoWorker((commentOwner as PropertyAssignment).initializer, options); + + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.EnumMember: + case SyntaxKind.TypeAliasDeclaration: + return { commentOwner }; + + case SyntaxKind.PropertySignature: { + const host = commentOwner as PropertySignature; + return host.type && isFunctionTypeNode(host.type) + ? { commentOwner, parameters: host.type.parameters, hasReturn: hasReturn(host.type, options) } + : { commentOwner }; + } + + case SyntaxKind.VariableStatement: { + const varStatement = commentOwner as VariableStatement; + const varDeclarations = varStatement.declarationList.declarations; + const host = varDeclarations.length === 1 && varDeclarations[0].initializer + ? getRightHandSideOfAssignment(varDeclarations[0].initializer) + : undefined; + return host + ? { commentOwner, parameters: host.parameters, hasReturn: hasReturn(host, options) } + : { commentOwner }; + } + + case SyntaxKind.SourceFile: + return "quit"; + + case SyntaxKind.ModuleDeclaration: + // If in walking up the tree, we hit a a nested namespace declaration, + // then we must be somewhere within a dotted namespace name; however we don't + // want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'. + return commentOwner.parent.kind === SyntaxKind.ModuleDeclaration ? undefined : { commentOwner }; + + case SyntaxKind.ExpressionStatement: + return getCommentOwnerInfoWorker((commentOwner as ExpressionStatement).expression, options); + case SyntaxKind.BinaryExpression: { + const be = commentOwner as BinaryExpression; + if (getAssignmentDeclarationKind(be) === AssignmentDeclarationKind.None) { + return "quit"; } - case SyntaxKind.PropertyDeclaration: - const init = (commentOwner as PropertyDeclaration).initializer; - if (init && (isFunctionExpression(init) || isArrowFunction(init))) { - return { commentOwner, parameters: init.parameters, hasReturn: hasReturn(init, options) }; - } + return isFunctionLike(be.right) + ? { commentOwner, parameters: be.right.parameters, hasReturn: hasReturn(be.right, options) } + : { commentOwner }; } + case SyntaxKind.PropertyDeclaration: + const init = (commentOwner as PropertyDeclaration).initializer; + if (init && (isFunctionExpression(init) || isArrowFunction(init))) { + return { commentOwner, parameters: init.parameters, hasReturn: hasReturn(init, options) }; + } } +} - function hasReturn(node: Node, options: DocCommentTemplateOptions | undefined) { - return !!options?.generateReturnInDocTemplate && - (isFunctionTypeNode(node) || isArrowFunction(node) && isExpression(node.body) - || isFunctionLikeDeclaration(node) && node.body && isBlock(node.body) && !!forEachReturnStatement(node.body, n => n)); - } +function hasReturn(node: Node, options: DocCommentTemplateOptions | undefined) { + return !!options?.generateReturnInDocTemplate && + (isFunctionTypeNode(node) || isArrowFunction(node) && isExpression(node.body) + || isFunctionLikeDeclaration(node) && node.body && isBlock(node.body) && !!forEachReturnStatement(node.body, n => n)); +} - function getRightHandSideOfAssignment(rightHandSide: Expression): FunctionExpression | ArrowFunction | ConstructorDeclaration | undefined { - while (rightHandSide.kind === SyntaxKind.ParenthesizedExpression) { - rightHandSide = (rightHandSide as ParenthesizedExpression).expression; - } +function getRightHandSideOfAssignment(rightHandSide: Expression): FunctionExpression | ArrowFunction | ConstructorDeclaration | undefined { + while (rightHandSide.kind === SyntaxKind.ParenthesizedExpression) { + rightHandSide = (rightHandSide as ParenthesizedExpression).expression; + } - switch (rightHandSide.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return (rightHandSide as FunctionExpression); - case SyntaxKind.ClassExpression: - return find((rightHandSide as ClassExpression).members, isConstructorDeclaration); - } + switch (rightHandSide.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return (rightHandSide as FunctionExpression); + case SyntaxKind.ClassExpression: + return find((rightHandSide as ClassExpression).members, isConstructorDeclaration); } } diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index 462d4fd0ac3ef..0d0f78140556d 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -1,136 +1,142 @@ -/* @internal */ -namespace ts.NavigateTo { - interface RawNavigateToItem { - readonly name: string; - readonly fileName: string; - readonly matchKind: PatternMatchKind; - readonly isCaseSensitive: boolean; - readonly declaration: Declaration; - } - - export function getNavigateToItems(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number | undefined, excludeDtsFiles: boolean): NavigateToItem[] { - const patternMatcher = createPatternMatcher(searchValue); - if (!patternMatcher) return emptyArray; - const rawItems: RawNavigateToItem[] = []; +import { + CancellationToken, compareStringsCaseSensitiveUI, compareValues, createPatternMatcher, createTextSpanFromNode, + Declaration, emptyArray, Expression, getContainerNode, getNameOfDeclaration, getNodeKind, getNodeModifiers, + getTextOfIdentifierOrLiteral, Identifier, ImportClause, ImportEqualsDeclaration, ImportSpecifier, + isPropertyAccessExpression, isPropertyNameLiteral, NavigateToItem, Node, PatternMatcher, PatternMatchKind, Push, + ScriptElementKind, SourceFile, SyntaxKind, TypeChecker, +} from "./_namespaces/ts"; + +interface RawNavigateToItem { + readonly name: string; + readonly fileName: string; + readonly matchKind: PatternMatchKind; + readonly isCaseSensitive: boolean; + readonly declaration: Declaration; +} - // Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[] - for (const sourceFile of sourceFiles) { - cancellationToken.throwIfCancellationRequested(); +/** @internal */ +export function getNavigateToItems(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, searchValue: string, maxResultCount: number | undefined, excludeDtsFiles: boolean): NavigateToItem[] { + const patternMatcher = createPatternMatcher(searchValue); + if (!patternMatcher) return emptyArray; + const rawItems: RawNavigateToItem[] = []; - if (excludeDtsFiles && sourceFile.isDeclarationFile) { - continue; - } + // Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[] + for (const sourceFile of sourceFiles) { + cancellationToken.throwIfCancellationRequested(); - sourceFile.getNamedDeclarations().forEach((declarations, name) => { - getItemsFromNamedDeclaration(patternMatcher, name, declarations, checker, sourceFile.fileName, rawItems); - }); + if (excludeDtsFiles && sourceFile.isDeclarationFile) { + continue; } - rawItems.sort(compareNavigateToItems); - return (maxResultCount === undefined ? rawItems : rawItems.slice(0, maxResultCount)).map(createNavigateToItem); + sourceFile.getNamedDeclarations().forEach((declarations, name) => { + getItemsFromNamedDeclaration(patternMatcher, name, declarations, checker, sourceFile.fileName, rawItems); + }); } - function getItemsFromNamedDeclaration(patternMatcher: PatternMatcher, name: string, declarations: readonly Declaration[], checker: TypeChecker, fileName: string, rawItems: Push): void { - // First do a quick check to see if the name of the declaration matches the - // last portion of the (possibly) dotted name they're searching for. - const match = patternMatcher.getMatchForLastSegmentOfPattern(name); - if (!match) { - return; // continue to next named declarations - } + rawItems.sort(compareNavigateToItems); + return (maxResultCount === undefined ? rawItems : rawItems.slice(0, maxResultCount)).map(createNavigateToItem); +} + +function getItemsFromNamedDeclaration(patternMatcher: PatternMatcher, name: string, declarations: readonly Declaration[], checker: TypeChecker, fileName: string, rawItems: Push): void { + // First do a quick check to see if the name of the declaration matches the + // last portion of the (possibly) dotted name they're searching for. + const match = patternMatcher.getMatchForLastSegmentOfPattern(name); + if (!match) { + return; // continue to next named declarations + } - for (const declaration of declarations) { - if (!shouldKeepItem(declaration, checker)) continue; + for (const declaration of declarations) { + if (!shouldKeepItem(declaration, checker)) continue; - if (patternMatcher.patternContainsDots) { - // If the pattern has dots in it, then also see if the declaration container matches as well. - const fullMatch = patternMatcher.getFullMatch(getContainers(declaration), name); - if (fullMatch) { - rawItems.push({ name, fileName, matchKind: fullMatch.kind, isCaseSensitive: fullMatch.isCaseSensitive, declaration }); - } - } - else { - rawItems.push({ name, fileName, matchKind: match.kind, isCaseSensitive: match.isCaseSensitive, declaration }); + if (patternMatcher.patternContainsDots) { + // If the pattern has dots in it, then also see if the declaration container matches as well. + const fullMatch = patternMatcher.getFullMatch(getContainers(declaration), name); + if (fullMatch) { + rawItems.push({ name, fileName, matchKind: fullMatch.kind, isCaseSensitive: fullMatch.isCaseSensitive, declaration }); } } - } - - function shouldKeepItem(declaration: Declaration, checker: TypeChecker): boolean { - switch (declaration.kind) { - case SyntaxKind.ImportClause: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportEqualsDeclaration: - const importer = checker.getSymbolAtLocation((declaration as ImportClause | ImportSpecifier | ImportEqualsDeclaration).name!)!; // TODO: GH#18217 - const imported = checker.getAliasedSymbol(importer); - return importer.escapedName !== imported.escapedName; - default: - return true; + else { + rawItems.push({ name, fileName, matchKind: match.kind, isCaseSensitive: match.isCaseSensitive, declaration }); } } +} - function tryAddSingleDeclarationName(declaration: Declaration, containers: Push): boolean { - const name = getNameOfDeclaration(declaration); - return !!name && (pushLiteral(name, containers) || name.kind === SyntaxKind.ComputedPropertyName && tryAddComputedPropertyName(name.expression, containers)); +function shouldKeepItem(declaration: Declaration, checker: TypeChecker): boolean { + switch (declaration.kind) { + case SyntaxKind.ImportClause: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportEqualsDeclaration: + const importer = checker.getSymbolAtLocation((declaration as ImportClause | ImportSpecifier | ImportEqualsDeclaration).name!)!; // TODO: GH#18217 + const imported = checker.getAliasedSymbol(importer); + return importer.escapedName !== imported.escapedName; + default: + return true; } +} - // Only added the names of computed properties if they're simple dotted expressions, like: - // - // [X.Y.Z]() { } - function tryAddComputedPropertyName(expression: Expression, containers: Push): boolean { - return pushLiteral(expression, containers) - || isPropertyAccessExpression(expression) && (containers.push(expression.name.text), true) && tryAddComputedPropertyName(expression.expression, containers); - } +function tryAddSingleDeclarationName(declaration: Declaration, containers: Push): boolean { + const name = getNameOfDeclaration(declaration); + return !!name && (pushLiteral(name, containers) || name.kind === SyntaxKind.ComputedPropertyName && tryAddComputedPropertyName(name.expression, containers)); +} - function pushLiteral(node: Node, containers: Push): boolean { - return isPropertyNameLiteral(node) && (containers.push(getTextOfIdentifierOrLiteral(node)), true); - } +// Only added the names of computed properties if they're simple dotted expressions, like: +// +// [X.Y.Z]() { } +function tryAddComputedPropertyName(expression: Expression, containers: Push): boolean { + return pushLiteral(expression, containers) + || isPropertyAccessExpression(expression) && (containers.push(expression.name.text), true) && tryAddComputedPropertyName(expression.expression, containers); +} - function getContainers(declaration: Declaration): readonly string[] { - const containers: string[] = []; +function pushLiteral(node: Node, containers: Push): boolean { + return isPropertyNameLiteral(node) && (containers.push(getTextOfIdentifierOrLiteral(node)), true); +} - // First, if we started with a computed property name, then add all but the last - // portion into the container array. - const name = getNameOfDeclaration(declaration); - if (name && name.kind === SyntaxKind.ComputedPropertyName && !tryAddComputedPropertyName(name.expression, containers)) { - return emptyArray; - } - // Don't include the last portion. - containers.shift(); +function getContainers(declaration: Declaration): readonly string[] { + const containers: string[] = []; - // Now, walk up our containers, adding all their names to the container array. - let container = getContainerNode(declaration); + // First, if we started with a computed property name, then add all but the last + // portion into the container array. + const name = getNameOfDeclaration(declaration); + if (name && name.kind === SyntaxKind.ComputedPropertyName && !tryAddComputedPropertyName(name.expression, containers)) { + return emptyArray; + } + // Don't include the last portion. + containers.shift(); - while (container) { - if (!tryAddSingleDeclarationName(container, containers)) { - return emptyArray; - } + // Now, walk up our containers, adding all their names to the container array. + let container = getContainerNode(declaration); - container = getContainerNode(container); + while (container) { + if (!tryAddSingleDeclarationName(container, containers)) { + return emptyArray; } - return containers.reverse(); + container = getContainerNode(container); } - function compareNavigateToItems(i1: RawNavigateToItem, i2: RawNavigateToItem) { - // TODO(cyrusn): get the gamut of comparisons that VS already uses here. - return compareValues(i1.matchKind, i2.matchKind) - || compareStringsCaseSensitiveUI(i1.name, i2.name); - } + return containers.reverse(); +} - function createNavigateToItem(rawItem: RawNavigateToItem): NavigateToItem { - const declaration = rawItem.declaration; - const container = getContainerNode(declaration); - const containerName = container && getNameOfDeclaration(container); - return { - name: rawItem.name, - kind: getNodeKind(declaration), - kindModifiers: getNodeModifiers(declaration), - matchKind: PatternMatchKind[rawItem.matchKind] as keyof typeof PatternMatchKind, - isCaseSensitive: rawItem.isCaseSensitive, - fileName: rawItem.fileName, - textSpan: createTextSpanFromNode(declaration), - // TODO(jfreeman): What should be the containerName when the container has a computed name? - containerName: containerName ? (containerName as Identifier).text : "", - containerKind: containerName ? getNodeKind(container) : ScriptElementKind.unknown, - }; - } +function compareNavigateToItems(i1: RawNavigateToItem, i2: RawNavigateToItem) { + // TODO(cyrusn): get the gamut of comparisons that VS already uses here. + return compareValues(i1.matchKind, i2.matchKind) + || compareStringsCaseSensitiveUI(i1.name, i2.name); +} + +function createNavigateToItem(rawItem: RawNavigateToItem): NavigateToItem { + const declaration = rawItem.declaration; + const container = getContainerNode(declaration); + const containerName = container && getNameOfDeclaration(container); + return { + name: rawItem.name, + kind: getNodeKind(declaration), + kindModifiers: getNodeModifiers(declaration), + matchKind: PatternMatchKind[rawItem.matchKind] as keyof typeof PatternMatchKind, + isCaseSensitive: rawItem.isCaseSensitive, + fileName: rawItem.fileName, + textSpan: createTextSpanFromNode(declaration), + // TODO(jfreeman): What should be the containerName when the container has a computed name? + containerName: containerName ? (containerName as Identifier).text : "", + containerKind: containerName ? getNodeKind(container) : ScriptElementKind.unknown, + }; } diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index ba870fb872037..dda82911fba14 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -1,986 +1,1008 @@ -/* @internal */ -namespace ts.NavigationBar { - /** - * Matches all whitespace characters in a string. Eg: - * - * "app. - * - * onactivated" - * - * matches because of the newline, whereas - * - * "app.onactivated" - * - * does not match. - */ - const whiteSpaceRegex = /\s+/g; - - /** - * Maximum amount of characters to return - * The amount was chosen arbitrarily. - */ - const maxLength = 150; - - // Keep sourceFile handy so we don't have to search for it every time we need to call `getText`. - let curCancellationToken: CancellationToken; - let curSourceFile: SourceFile; - - /** - * For performance, we keep navigation bar parents on a stack rather than passing them through each recursion. - * `parent` is the current parent and is *not* stored in parentsStack. - * `startNode` sets a new parent and `endNode` returns to the previous parent. - */ - let parentsStack: NavigationBarNode[] = []; - let parent: NavigationBarNode; - - const trackedEs5ClassesStack: (ESMap | undefined)[] = []; - let trackedEs5Classes: ESMap | undefined; - - // NavigationBarItem requires an array, but will not mutate it, so just give it this for performance. - let emptyChildItemArray: NavigationBarItem[] = []; - - /** - * Represents a navigation bar item and its children. - * The returned NavigationBarItem is more complicated and doesn't include 'parent', so we use these to do work before converting. - */ - interface NavigationBarNode { - node: Node; - name: DeclarationName | undefined; - additionalNodes: Node[] | undefined; - parent: NavigationBarNode | undefined; // Present for all but root node - children: NavigationBarNode[] | undefined; - indent: number; // # of parents - } - - export function getNavigationBarItems(sourceFile: SourceFile, cancellationToken: CancellationToken): NavigationBarItem[] { - curCancellationToken = cancellationToken; - curSourceFile = sourceFile; - try { - return map(primaryNavBarMenuItems(rootNavigationBarNode(sourceFile)), convertToPrimaryNavBarMenuItem); - } - finally { - reset(); - } - } +import { + ArrowFunction, AssignmentDeclarationKind, BinaryExpression, BindableElementAccessExpression, + BindableObjectDefinePropertyCall, BindableStaticNameExpression, BindingElement, CallExpression, CancellationToken, + ClassElement, ClassExpression, ClassLikeDeclaration, compareStringsCaseSensitiveUI, compareValues, concatenate, + ConstructorDeclaration, contains, createTextSpanFromNode, createTextSpanFromRange, Debug, Declaration, + DeclarationName, declarationNameToString, EntityNameExpression, EnumDeclaration, EnumMember, escapeString, ESMap, + ExportAssignment, Expression, factory, filterMutate, forEach, forEachChild, FunctionDeclaration, FunctionExpression, + FunctionLikeDeclaration, getAssignmentDeclarationKind, getBaseFileName, getElementOrPropertyAccessName, + getFullWidth, getNameOfDeclaration, getNameOrArgument, getNodeKind, getNodeModifiers, + getPropertyNameForPropertyNameNode, getSyntacticModifierFlags, getTextOfIdentifierOrLiteral, getTextOfNode, + hasDynamicName, hasJSDocNodes, Identifier, idText, ImportClause, InterfaceDeclaration, InternalSymbolName, + isAmbientModule, isArrowFunction, isBinaryExpression, isBindableStaticAccessExpression, isBindingPattern, + isCallExpression, isClassDeclaration, isClassLike, isDeclaration, isElementAccessExpression, isExportAssignment, + isExpression, isExternalModule, isFunctionDeclaration, isFunctionExpression, isIdentifier, isJSDocTypeAlias, + isModuleBlock, isModuleDeclaration, isObjectLiteralExpression, isParameterPropertyDeclaration, isPrivateIdentifier, + isPropertyAccessExpression, isPropertyAssignment, isPropertyName, isPropertyNameLiteral, isStatic, + isStringLiteralLike, isToken, isVariableDeclaration, lastOrUndefined, map, Map, mapDefined, ModifierFlags, + ModuleDeclaration, NavigationBarItem, NavigationTree, Node, NodeFlags, normalizePath, PropertyAccessExpression, + PropertyAssignment, PropertyDeclaration, PropertyNameLiteral, removeFileExtension, setTextRange, + ShorthandPropertyAssignment, SourceFile, SpreadAssignment, SyntaxKind, TextSpan, TypeElement, + unescapeLeadingUnderscores, VariableDeclaration, +} from "./_namespaces/ts"; + +/** + * Matches all whitespace characters in a string. Eg: + * + * "app. + * + * onactivated" + * + * matches because of the newline, whereas + * + * "app.onactivated" + * + * does not match. + */ +const whiteSpaceRegex = /\s+/g; + +/** + * Maximum amount of characters to return + * The amount was chosen arbitrarily. + */ +const maxLength = 150; + +// Keep sourceFile handy so we don't have to search for it every time we need to call `getText`. +let curCancellationToken: CancellationToken; +let curSourceFile: SourceFile; + +/** + * For performance, we keep navigation bar parents on a stack rather than passing them through each recursion. + * `parent` is the current parent and is *not* stored in parentsStack. + * `startNode` sets a new parent and `endNode` returns to the previous parent. + */ +let parentsStack: NavigationBarNode[] = []; +let parent: NavigationBarNode; + +const trackedEs5ClassesStack: (ESMap | undefined)[] = []; +let trackedEs5Classes: ESMap | undefined; + +// NavigationBarItem requires an array, but will not mutate it, so just give it this for performance. +let emptyChildItemArray: NavigationBarItem[] = []; + +/** + * Represents a navigation bar item and its children. + * The returned NavigationBarItem is more complicated and doesn't include 'parent', so we use these to do work before converting. + */ +interface NavigationBarNode { + node: Node; + name: DeclarationName | undefined; + additionalNodes: Node[] | undefined; + parent: NavigationBarNode | undefined; // Present for all but root node + children: NavigationBarNode[] | undefined; + indent: number; // # of parents +} - export function getNavigationTree(sourceFile: SourceFile, cancellationToken: CancellationToken): NavigationTree { - curCancellationToken = cancellationToken; - curSourceFile = sourceFile; - try { - return convertToTree(rootNavigationBarNode(sourceFile)); - } - finally { - reset(); - } +/** @internal */ +export function getNavigationBarItems(sourceFile: SourceFile, cancellationToken: CancellationToken): NavigationBarItem[] { + curCancellationToken = cancellationToken; + curSourceFile = sourceFile; + try { + return map(primaryNavBarMenuItems(rootNavigationBarNode(sourceFile)), convertToPrimaryNavBarMenuItem); } - - function reset() { - curSourceFile = undefined!; - curCancellationToken = undefined!; - parentsStack = []; - parent = undefined!; - emptyChildItemArray = []; + finally { + reset(); } +} - function nodeText(node: Node): string { - return cleanText(node.getText(curSourceFile)); +/** @internal */ +export function getNavigationTree(sourceFile: SourceFile, cancellationToken: CancellationToken): NavigationTree { + curCancellationToken = cancellationToken; + curSourceFile = sourceFile; + try { + return convertToTree(rootNavigationBarNode(sourceFile)); } - - function navigationBarNodeKind(n: NavigationBarNode): SyntaxKind { - return n.node.kind; + finally { + reset(); } +} - function pushChild(parent: NavigationBarNode, child: NavigationBarNode): void { - if (parent.children) { - parent.children.push(child); - } - else { - parent.children = [child]; - } - } +function reset() { + curSourceFile = undefined!; + curCancellationToken = undefined!; + parentsStack = []; + parent = undefined!; + emptyChildItemArray = []; +} - function rootNavigationBarNode(sourceFile: SourceFile): NavigationBarNode { - Debug.assert(!parentsStack.length); - const root: NavigationBarNode = { node: sourceFile, name: undefined, additionalNodes: undefined, parent: undefined, children: undefined, indent: 0 }; - parent = root; - for (const statement of sourceFile.statements) { - addChildrenRecursively(statement); - } - endNode(); - Debug.assert(!parent && !parentsStack.length); - return root; - } +function nodeText(node: Node): string { + return cleanText(node.getText(curSourceFile)); +} - function addLeafNode(node: Node, name?: DeclarationName): void { - pushChild(parent, emptyNavigationBarNode(node, name)); - } +function navigationBarNodeKind(n: NavigationBarNode): SyntaxKind { + return n.node.kind; +} - function emptyNavigationBarNode(node: Node, name?: DeclarationName): NavigationBarNode { - return { - node, - name: name || (isDeclaration(node) || isExpression(node) ? getNameOfDeclaration(node) : undefined), - additionalNodes: undefined, - parent, - children: undefined, - indent: parent.indent + 1 - }; +function pushChild(parent: NavigationBarNode, child: NavigationBarNode): void { + if (parent.children) { + parent.children.push(child); } + else { + parent.children = [child]; + } +} - function addTrackedEs5Class(name: string) { - if (!trackedEs5Classes) { - trackedEs5Classes = new Map(); - } - trackedEs5Classes.set(name, true); - } - function endNestedNodes(depth: number): void { - for (let i = 0; i < depth; i++) endNode(); - } - function startNestedNodes(targetNode: Node, entityName: BindableStaticNameExpression) { - const names: PropertyNameLiteral[] = []; - while (!isPropertyNameLiteral(entityName)) { - const name = getNameOrArgument(entityName); - const nameText = getElementOrPropertyAccessName(entityName); - entityName = entityName.expression; - if (nameText === "prototype" || isPrivateIdentifier(name)) continue; - names.push(name); - } - names.push(entityName); - for (let i = names.length - 1; i > 0; i--) { - const name = names[i]; - startNode(targetNode, name); - } - return [names.length - 1, names[0]] as const; +function rootNavigationBarNode(sourceFile: SourceFile): NavigationBarNode { + Debug.assert(!parentsStack.length); + const root: NavigationBarNode = { node: sourceFile, name: undefined, additionalNodes: undefined, parent: undefined, children: undefined, indent: 0 }; + parent = root; + for (const statement of sourceFile.statements) { + addChildrenRecursively(statement); } + endNode(); + Debug.assert(!parent && !parentsStack.length); + return root; +} + +function addLeafNode(node: Node, name?: DeclarationName): void { + pushChild(parent, emptyNavigationBarNode(node, name)); +} - /** - * Add a new level of NavigationBarNodes. - * This pushes to the stack, so you must call `endNode` when you are done adding to this node. - */ - function startNode(node: Node, name?: DeclarationName): void { - const navNode: NavigationBarNode = emptyNavigationBarNode(node, name); - pushChild(parent, navNode); +function emptyNavigationBarNode(node: Node, name?: DeclarationName): NavigationBarNode { + return { + node, + name: name || (isDeclaration(node) || isExpression(node) ? getNameOfDeclaration(node) : undefined), + additionalNodes: undefined, + parent, + children: undefined, + indent: parent.indent + 1 + }; +} - // Save the old parent - parentsStack.push(parent); - trackedEs5ClassesStack.push(trackedEs5Classes); - trackedEs5Classes = undefined; - parent = navNode; +function addTrackedEs5Class(name: string) { + if (!trackedEs5Classes) { + trackedEs5Classes = new Map(); } + trackedEs5Classes.set(name, true); +} +function endNestedNodes(depth: number): void { + for (let i = 0; i < depth; i++) endNode(); +} +function startNestedNodes(targetNode: Node, entityName: BindableStaticNameExpression) { + const names: PropertyNameLiteral[] = []; + while (!isPropertyNameLiteral(entityName)) { + const name = getNameOrArgument(entityName); + const nameText = getElementOrPropertyAccessName(entityName); + entityName = entityName.expression; + if (nameText === "prototype" || isPrivateIdentifier(name)) continue; + names.push(name); + } + names.push(entityName); + for (let i = names.length - 1; i > 0; i--) { + const name = names[i]; + startNode(targetNode, name); + } + return [names.length - 1, names[0]] as const; +} - /** Call after calling `startNode` and adding children to it. */ - function endNode(): void { - if (parent.children) { - mergeChildren(parent.children, parent); - sortChildren(parent.children); - } - parent = parentsStack.pop()!; - trackedEs5Classes = trackedEs5ClassesStack.pop(); +/** + * Add a new level of NavigationBarNodes. + * This pushes to the stack, so you must call `endNode` when you are done adding to this node. + */ +function startNode(node: Node, name?: DeclarationName): void { + const navNode: NavigationBarNode = emptyNavigationBarNode(node, name); + pushChild(parent, navNode); + + // Save the old parent + parentsStack.push(parent); + trackedEs5ClassesStack.push(trackedEs5Classes); + trackedEs5Classes = undefined; + parent = navNode; +} + +/** Call after calling `startNode` and adding children to it. */ +function endNode(): void { + if (parent.children) { + mergeChildren(parent.children, parent); + sortChildren(parent.children); } + parent = parentsStack.pop()!; + trackedEs5Classes = trackedEs5ClassesStack.pop(); +} - function addNodeWithRecursiveChild(node: Node, child: Node | undefined, name?: DeclarationName): void { - startNode(node, name); - addChildrenRecursively(child); +function addNodeWithRecursiveChild(node: Node, child: Node | undefined, name?: DeclarationName): void { + startNode(node, name); + addChildrenRecursively(child); + endNode(); +} + +function addNodeWithRecursiveInitializer(node: VariableDeclaration | PropertyAssignment | BindingElement | PropertyDeclaration): void { + if (node.initializer && isFunctionOrClassExpression(node.initializer)) { + startNode(node); + forEachChild(node.initializer, addChildrenRecursively); endNode(); } - - function addNodeWithRecursiveInitializer(node: VariableDeclaration | PropertyAssignment | BindingElement | PropertyDeclaration): void { - if (node.initializer && isFunctionOrClassExpression(node.initializer)) { - startNode(node); - forEachChild(node.initializer, addChildrenRecursively); - endNode(); - } - else { - addNodeWithRecursiveChild(node, node.initializer); - } + else { + addNodeWithRecursiveChild(node, node.initializer); } +} - /** - * Historically, we've elided dynamic names from the nav tree (including late bound names), - * but included certain "well known" symbol names. While we no longer distinguish those well-known - * symbols from other unique symbols, we do the below to retain those members in the nav tree. - */ - function hasNavigationBarName(node: Declaration) { - return !hasDynamicName(node) || - ( - node.kind !== SyntaxKind.BinaryExpression && - isPropertyAccessExpression(node.name.expression) && - isIdentifier(node.name.expression.expression) && - idText(node.name.expression.expression) === "Symbol" - ); - } +/** + * Historically, we've elided dynamic names from the nav tree (including late bound names), + * but included certain "well known" symbol names. While we no longer distinguish those well-known + * symbols from other unique symbols, we do the below to retain those members in the nav tree. + */ +function hasNavigationBarName(node: Declaration) { + return !hasDynamicName(node) || + ( + node.kind !== SyntaxKind.BinaryExpression && + isPropertyAccessExpression(node.name.expression) && + isIdentifier(node.name.expression.expression) && + idText(node.name.expression.expression) === "Symbol" + ); +} - /** Look for navigation bar items in node's subtree, adding them to the current `parent`. */ - function addChildrenRecursively(node: Node | undefined): void { - curCancellationToken.throwIfCancellationRequested(); +/** Look for navigation bar items in node's subtree, adding them to the current `parent`. */ +function addChildrenRecursively(node: Node | undefined): void { + curCancellationToken.throwIfCancellationRequested(); - if (!node || isToken(node)) { - return; - } + if (!node || isToken(node)) { + return; + } - switch (node.kind) { - case SyntaxKind.Constructor: - // Get parameter properties, and treat them as being on the *same* level as the constructor, not under it. - const ctr = node as ConstructorDeclaration; - addNodeWithRecursiveChild(ctr, ctr.body); + switch (node.kind) { + case SyntaxKind.Constructor: + // Get parameter properties, and treat them as being on the *same* level as the constructor, not under it. + const ctr = node as ConstructorDeclaration; + addNodeWithRecursiveChild(ctr, ctr.body); - // Parameter properties are children of the class, not the constructor. - for (const param of ctr.parameters) { - if (isParameterPropertyDeclaration(param, ctr)) { - addLeafNode(param); - } - } - break; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodSignature: - if (hasNavigationBarName(node as ClassElement | TypeElement)) { - addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); + // Parameter properties are children of the class, not the constructor. + for (const param of ctr.parameters) { + if (isParameterPropertyDeclaration(param, ctr)) { + addLeafNode(param); } - break; + } + break; - case SyntaxKind.PropertyDeclaration: - if (hasNavigationBarName(node as ClassElement)) { - addNodeWithRecursiveInitializer(node as PropertyDeclaration); - } - break; - case SyntaxKind.PropertySignature: - if (hasNavigationBarName(node as TypeElement)) { - addLeafNode(node); - } - break; - - case SyntaxKind.ImportClause: - const importClause = node as ImportClause; - // Handle default import case e.g.: - // import d from "mod"; - if (importClause.name) { - addLeafNode(importClause.name); - } + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodSignature: + if (hasNavigationBarName(node as ClassElement | TypeElement)) { + addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); + } + break; - // Handle named bindings in imports e.g.: - // import * as NS from "mod"; - // import {a, b as B} from "mod"; - const { namedBindings } = importClause; - if (namedBindings) { - if (namedBindings.kind === SyntaxKind.NamespaceImport) { - addLeafNode(namedBindings); - } - else { - for (const element of namedBindings.elements) { - addLeafNode(element); - } - } - } - break; - - case SyntaxKind.ShorthandPropertyAssignment: - addNodeWithRecursiveChild(node, (node as ShorthandPropertyAssignment).name); - break; - case SyntaxKind.SpreadAssignment: - const { expression } = node as SpreadAssignment; - // Use the expression as the name of the SpreadAssignment, otherwise show as . - isIdentifier(expression) ? addLeafNode(node, expression) : addLeafNode(node); - break; - case SyntaxKind.BindingElement: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.VariableDeclaration: { - const child = node as VariableDeclaration | PropertyAssignment | BindingElement; - if (isBindingPattern(child.name)) { - addChildrenRecursively(child.name); + case SyntaxKind.PropertyDeclaration: + if (hasNavigationBarName(node as ClassElement)) { + addNodeWithRecursiveInitializer(node as PropertyDeclaration); + } + break; + case SyntaxKind.PropertySignature: + if (hasNavigationBarName(node as TypeElement)) { + addLeafNode(node); + } + break; + + case SyntaxKind.ImportClause: + const importClause = node as ImportClause; + // Handle default import case e.g.: + // import d from "mod"; + if (importClause.name) { + addLeafNode(importClause.name); + } + + // Handle named bindings in imports e.g.: + // import * as NS from "mod"; + // import {a, b as B} from "mod"; + const { namedBindings } = importClause; + if (namedBindings) { + if (namedBindings.kind === SyntaxKind.NamespaceImport) { + addLeafNode(namedBindings); } else { - addNodeWithRecursiveInitializer(child); + for (const element of namedBindings.elements) { + addLeafNode(element); + } } - break; } - case SyntaxKind.FunctionDeclaration: - const nameNode = (node as FunctionLikeDeclaration).name; - // If we see a function declaration track as a possible ES5 class - if (nameNode && isIdentifier(nameNode)) { - addTrackedEs5Class(nameNode.text); + break; + + case SyntaxKind.ShorthandPropertyAssignment: + addNodeWithRecursiveChild(node, (node as ShorthandPropertyAssignment).name); + break; + case SyntaxKind.SpreadAssignment: + const { expression } = node as SpreadAssignment; + // Use the expression as the name of the SpreadAssignment, otherwise show as . + isIdentifier(expression) ? addLeafNode(node, expression) : addLeafNode(node); + break; + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.VariableDeclaration: { + const child = node as VariableDeclaration | PropertyAssignment | BindingElement; + if (isBindingPattern(child.name)) { + addChildrenRecursively(child.name); + } + else { + addNodeWithRecursiveInitializer(child); + } + break; + } + case SyntaxKind.FunctionDeclaration: + const nameNode = (node as FunctionLikeDeclaration).name; + // If we see a function declaration track as a possible ES5 class + if (nameNode && isIdentifier(nameNode)) { + addTrackedEs5Class(nameNode.text); + } + addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); + break; + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); + break; + + case SyntaxKind.EnumDeclaration: + startNode(node); + for (const member of (node as EnumDeclaration).members) { + if (!isComputedProperty(member)) { + addLeafNode(member); } - addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); - break; - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionExpression: - addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body); - break; + } + endNode(); + break; - case SyntaxKind.EnumDeclaration: - startNode(node); - for (const member of (node as EnumDeclaration).members) { - if (!isComputedProperty(member)) { - addLeafNode(member); - } - } - endNode(); - break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + startNode(node); + for (const member of (node as InterfaceDeclaration).members) { + addChildrenRecursively(member); + } + endNode(); + break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ModuleDeclaration: + addNodeWithRecursiveChild(node, getInteriorModule(node as ModuleDeclaration).body); + break; + + case SyntaxKind.ExportAssignment: { + const expression = (node as ExportAssignment).expression; + const child = isObjectLiteralExpression(expression) || isCallExpression(expression) ? expression : + isArrowFunction(expression) || isFunctionExpression(expression) ? expression.body : undefined; + if (child) { startNode(node); - for (const member of (node as InterfaceDeclaration).members) { - addChildrenRecursively(member); - } + addChildrenRecursively(child); endNode(); - break; - - case SyntaxKind.ModuleDeclaration: - addNodeWithRecursiveChild(node, getInteriorModule(node as ModuleDeclaration).body); - break; - - case SyntaxKind.ExportAssignment: { - const expression = (node as ExportAssignment).expression; - const child = isObjectLiteralExpression(expression) || isCallExpression(expression) ? expression : - isArrowFunction(expression) || isFunctionExpression(expression) ? expression.body : undefined; - if (child) { - startNode(node); - addChildrenRecursively(child); - endNode(); - } - else { - addLeafNode(node); - } - break; } - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.IndexSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.TypeAliasDeclaration: + else { addLeafNode(node); - break; - - case SyntaxKind.CallExpression: - case SyntaxKind.BinaryExpression: { - const special = getAssignmentDeclarationKind(node as BinaryExpression); - switch (special) { - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.ModuleExports: - addNodeWithRecursiveChild(node, (node as BinaryExpression).right); - return; - case AssignmentDeclarationKind.Prototype: - case AssignmentDeclarationKind.PrototypeProperty: { - const binaryExpression = (node as BinaryExpression); - const assignmentTarget = binaryExpression.left as PropertyAccessExpression; - - const prototypeAccess = special === AssignmentDeclarationKind.PrototypeProperty ? - assignmentTarget.expression as PropertyAccessExpression : - assignmentTarget; - - let depth = 0; - let className: PropertyNameLiteral; - // If we see a prototype assignment, start tracking the target as a class - // This is only done for simple classes not nested assignments. - if (isIdentifier(prototypeAccess.expression)) { - addTrackedEs5Class(prototypeAccess.expression.text); - className = prototypeAccess.expression; - } - else { - [depth, className] = startNestedNodes(binaryExpression, prototypeAccess.expression as EntityNameExpression); - } - if (special === AssignmentDeclarationKind.Prototype) { - if (isObjectLiteralExpression(binaryExpression.right)) { - if (binaryExpression.right.properties.length > 0) { - startNode(binaryExpression, className); - forEachChild(binaryExpression.right, addChildrenRecursively); - endNode(); - } + } + break; + } + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.IndexSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.TypeAliasDeclaration: + addLeafNode(node); + break; + + case SyntaxKind.CallExpression: + case SyntaxKind.BinaryExpression: { + const special = getAssignmentDeclarationKind(node as BinaryExpression); + switch (special) { + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.ModuleExports: + addNodeWithRecursiveChild(node, (node as BinaryExpression).right); + return; + case AssignmentDeclarationKind.Prototype: + case AssignmentDeclarationKind.PrototypeProperty: { + const binaryExpression = (node as BinaryExpression); + const assignmentTarget = binaryExpression.left as PropertyAccessExpression; + + const prototypeAccess = special === AssignmentDeclarationKind.PrototypeProperty ? + assignmentTarget.expression as PropertyAccessExpression : + assignmentTarget; + + let depth = 0; + let className: PropertyNameLiteral; + // If we see a prototype assignment, start tracking the target as a class + // This is only done for simple classes not nested assignments. + if (isIdentifier(prototypeAccess.expression)) { + addTrackedEs5Class(prototypeAccess.expression.text); + className = prototypeAccess.expression; + } + else { + [depth, className] = startNestedNodes(binaryExpression, prototypeAccess.expression as EntityNameExpression); + } + if (special === AssignmentDeclarationKind.Prototype) { + if (isObjectLiteralExpression(binaryExpression.right)) { + if (binaryExpression.right.properties.length > 0) { + startNode(binaryExpression, className); + forEachChild(binaryExpression.right, addChildrenRecursively); + endNode(); } } - else if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) { - addNodeWithRecursiveChild(node, - binaryExpression.right, - className); - } - else { - startNode(binaryExpression, className); - addNodeWithRecursiveChild(node, binaryExpression.right, assignmentTarget.name); + } + else if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) { + addNodeWithRecursiveChild(node, + binaryExpression.right, + className); + } + else { + startNode(binaryExpression, className); + addNodeWithRecursiveChild(node, binaryExpression.right, assignmentTarget.name); + endNode(); + } + endNestedNodes(depth); + return; + } + case AssignmentDeclarationKind.ObjectDefinePropertyValue: + case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: { + const defineCall = node as BindableObjectDefinePropertyCall; + const className = special === AssignmentDeclarationKind.ObjectDefinePropertyValue ? + defineCall.arguments[0] : + (defineCall.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression; + + const memberName = defineCall.arguments[1]; + const [depth, classNameIdentifier] = startNestedNodes(node, className); + startNode(node, classNameIdentifier); + startNode(node, setTextRange(factory.createIdentifier(memberName.text), memberName)); + addChildrenRecursively((node as CallExpression).arguments[2]); endNode(); + endNode(); + endNestedNodes(depth); + return; + } + case AssignmentDeclarationKind.Property: { + const binaryExpression = (node as BinaryExpression); + const assignmentTarget = binaryExpression.left as PropertyAccessExpression | BindableElementAccessExpression; + const targetFunction = assignmentTarget.expression; + if (isIdentifier(targetFunction) && getElementOrPropertyAccessName(assignmentTarget) !== "prototype" && + trackedEs5Classes && trackedEs5Classes.has(targetFunction.text)) { + if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) { + addNodeWithRecursiveChild(node, binaryExpression.right, targetFunction); } - endNestedNodes(depth); - return; - } - case AssignmentDeclarationKind.ObjectDefinePropertyValue: - case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: { - const defineCall = node as BindableObjectDefinePropertyCall; - const className = special === AssignmentDeclarationKind.ObjectDefinePropertyValue ? - defineCall.arguments[0] : - (defineCall.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression; - - const memberName = defineCall.arguments[1]; - const [depth, classNameIdentifier] = startNestedNodes(node, className); - startNode(node, classNameIdentifier); - startNode(node, setTextRange(factory.createIdentifier(memberName.text), memberName)); - addChildrenRecursively((node as CallExpression).arguments[2]); - endNode(); + else if (isBindableStaticAccessExpression(assignmentTarget)) { + startNode(binaryExpression, targetFunction); + addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, getNameOrArgument(assignmentTarget)); endNode(); - endNestedNodes(depth); - return; - } - case AssignmentDeclarationKind.Property: { - const binaryExpression = (node as BinaryExpression); - const assignmentTarget = binaryExpression.left as PropertyAccessExpression | BindableElementAccessExpression; - const targetFunction = assignmentTarget.expression; - if (isIdentifier(targetFunction) && getElementOrPropertyAccessName(assignmentTarget) !== "prototype" && - trackedEs5Classes && trackedEs5Classes.has(targetFunction.text)) { - if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) { - addNodeWithRecursiveChild(node, binaryExpression.right, targetFunction); - } - else if (isBindableStaticAccessExpression(assignmentTarget)) { - startNode(binaryExpression, targetFunction); - addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, getNameOrArgument(assignmentTarget)); - endNode(); - } - return; } - break; + return; } - case AssignmentDeclarationKind.ThisProperty: - case AssignmentDeclarationKind.None: - case AssignmentDeclarationKind.ObjectDefinePropertyExports: - break; - default: - Debug.assertNever(special); + break; } + case AssignmentDeclarationKind.ThisProperty: + case AssignmentDeclarationKind.None: + case AssignmentDeclarationKind.ObjectDefinePropertyExports: + break; + default: + Debug.assertNever(special); } - // falls through + } + // falls through - default: - if (hasJSDocNodes(node)) { - forEach(node.jsDoc, jsDoc => { - forEach(jsDoc.tags, tag => { - if (isJSDocTypeAlias(tag)) { - addLeafNode(tag); - } - }); + default: + if (hasJSDocNodes(node)) { + forEach(node.jsDoc, jsDoc => { + forEach(jsDoc.tags, tag => { + if (isJSDocTypeAlias(tag)) { + addLeafNode(tag); + } }); - } + }); + } - forEachChild(node, addChildrenRecursively); - } + forEachChild(node, addChildrenRecursively); } +} - /** Merge declarations of the same kind. */ - function mergeChildren(children: NavigationBarNode[], node: NavigationBarNode): void { - const nameToItems = new Map(); - filterMutate(children, (child, index) => { - const declName = child.name || getNameOfDeclaration(child.node as Declaration); - const name = declName && nodeText(declName); - if (!name) { - // Anonymous items are never merged. - return true; - } +/** Merge declarations of the same kind. */ +function mergeChildren(children: NavigationBarNode[], node: NavigationBarNode): void { + const nameToItems = new Map(); + filterMutate(children, (child, index) => { + const declName = child.name || getNameOfDeclaration(child.node as Declaration); + const name = declName && nodeText(declName); + if (!name) { + // Anonymous items are never merged. + return true; + } - const itemsWithSameName = nameToItems.get(name); - if (!itemsWithSameName) { - nameToItems.set(name, child); - return true; - } + const itemsWithSameName = nameToItems.get(name); + if (!itemsWithSameName) { + nameToItems.set(name, child); + return true; + } - if (itemsWithSameName instanceof Array) { - for (const itemWithSameName of itemsWithSameName) { - if (tryMerge(itemWithSameName, child, index, node)) { - return false; - } - } - itemsWithSameName.push(child); - return true; - } - else { - const itemWithSameName = itemsWithSameName; + if (itemsWithSameName instanceof Array) { + for (const itemWithSameName of itemsWithSameName) { if (tryMerge(itemWithSameName, child, index, node)) { return false; } - nameToItems.set(name, [itemWithSameName, child]); - return true; } - }); - } - const isEs5ClassMember: Record = { - [AssignmentDeclarationKind.Property]: true, - [AssignmentDeclarationKind.PrototypeProperty]: true, - [AssignmentDeclarationKind.ObjectDefinePropertyValue]: true, - [AssignmentDeclarationKind.ObjectDefinePrototypeProperty]: true, - [AssignmentDeclarationKind.None]: false, - [AssignmentDeclarationKind.ExportsProperty]: false, - [AssignmentDeclarationKind.ModuleExports]: false, - [AssignmentDeclarationKind.ObjectDefinePropertyExports]: false, - [AssignmentDeclarationKind.Prototype]: true, - [AssignmentDeclarationKind.ThisProperty]: false, - }; - function tryMergeEs5Class(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean | undefined { - function isPossibleConstructor(node: Node) { - return isFunctionExpression(node) || isFunctionDeclaration(node) || isVariableDeclaration(node); + itemsWithSameName.push(child); + return true; } - const bAssignmentDeclarationKind = isBinaryExpression(b.node) || isCallExpression(b.node) ? - getAssignmentDeclarationKind(b.node) : - AssignmentDeclarationKind.None; - - const aAssignmentDeclarationKind = isBinaryExpression(a.node) || isCallExpression(a.node) ? - getAssignmentDeclarationKind(a.node) : - AssignmentDeclarationKind.None; - - // We treat this as an es5 class and merge the nodes in in one of several cases - if ((isEs5ClassMember[bAssignmentDeclarationKind] && isEs5ClassMember[aAssignmentDeclarationKind]) // merge two class elements - || (isPossibleConstructor(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // ctor function & member - || (isPossibleConstructor(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & ctor function - || (isClassDeclaration(a.node) && isSynthesized(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // class (generated) & member - || (isClassDeclaration(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & class (generated) - || (isClassDeclaration(a.node) && isSynthesized(a.node) && isPossibleConstructor(b.node)) // class (generated) & ctor - || (isClassDeclaration(b.node) && isPossibleConstructor(a.node) && isSynthesized(a.node)) // ctor & class (generated) + else { + const itemWithSameName = itemsWithSameName; + if (tryMerge(itemWithSameName, child, index, node)) { + return false; + } + nameToItems.set(name, [itemWithSameName, child]); + return true; + } + }); +} +const isEs5ClassMember: Record = { + [AssignmentDeclarationKind.Property]: true, + [AssignmentDeclarationKind.PrototypeProperty]: true, + [AssignmentDeclarationKind.ObjectDefinePropertyValue]: true, + [AssignmentDeclarationKind.ObjectDefinePrototypeProperty]: true, + [AssignmentDeclarationKind.None]: false, + [AssignmentDeclarationKind.ExportsProperty]: false, + [AssignmentDeclarationKind.ModuleExports]: false, + [AssignmentDeclarationKind.ObjectDefinePropertyExports]: false, + [AssignmentDeclarationKind.Prototype]: true, + [AssignmentDeclarationKind.ThisProperty]: false, +}; +function tryMergeEs5Class(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean | undefined { + function isPossibleConstructor(node: Node) { + return isFunctionExpression(node) || isFunctionDeclaration(node) || isVariableDeclaration(node); + } + const bAssignmentDeclarationKind = isBinaryExpression(b.node) || isCallExpression(b.node) ? + getAssignmentDeclarationKind(b.node) : + AssignmentDeclarationKind.None; + + const aAssignmentDeclarationKind = isBinaryExpression(a.node) || isCallExpression(a.node) ? + getAssignmentDeclarationKind(a.node) : + AssignmentDeclarationKind.None; + + // We treat this as an es5 class and merge the nodes in in one of several cases + if ((isEs5ClassMember[bAssignmentDeclarationKind] && isEs5ClassMember[aAssignmentDeclarationKind]) // merge two class elements + || (isPossibleConstructor(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // ctor function & member + || (isPossibleConstructor(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & ctor function + || (isClassDeclaration(a.node) && isSynthesized(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // class (generated) & member + || (isClassDeclaration(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & class (generated) + || (isClassDeclaration(a.node) && isSynthesized(a.node) && isPossibleConstructor(b.node)) // class (generated) & ctor + || (isClassDeclaration(b.node) && isPossibleConstructor(a.node) && isSynthesized(a.node)) // ctor & class (generated) + ) { + + let lastANode = a.additionalNodes && lastOrUndefined(a.additionalNodes) || a.node; + + if ((!isClassDeclaration(a.node) && !isClassDeclaration(b.node)) // If neither outline node is a class + || isPossibleConstructor(a.node) || isPossibleConstructor(b.node) // If either function is a constructor function ) { - - let lastANode = a.additionalNodes && lastOrUndefined(a.additionalNodes) || a.node; - - if ((!isClassDeclaration(a.node) && !isClassDeclaration(b.node)) // If neither outline node is a class - || isPossibleConstructor(a.node) || isPossibleConstructor(b.node) // If either function is a constructor function - ) { - const ctorFunction = isPossibleConstructor(a.node) ? a.node : - isPossibleConstructor(b.node) ? b.node : - undefined; - - if (ctorFunction !== undefined) { - const ctorNode = setTextRange( - factory.createConstructorDeclaration(/* modifiers */ undefined, [], /* body */ undefined), - ctorFunction); - const ctor = emptyNavigationBarNode(ctorNode); - ctor.indent = a.indent + 1; - ctor.children = a.node === ctorFunction ? a.children : b.children; - a.children = a.node === ctorFunction ? concatenate([ctor], b.children || [b]) : concatenate(a.children || [{ ...a }], [ctor]); - } - else { - if (a.children || b.children) { - a.children = concatenate(a.children || [{ ...a }], b.children || [b]); - if (a.children) { - mergeChildren(a.children, a); - sortChildren(a.children); - } - } - } - - lastANode = a.node = setTextRange(factory.createClassDeclaration( - /* modifiers */ undefined, - a.name as Identifier || factory.createIdentifier("__class__"), - /* typeParameters */ undefined, - /* heritageClauses */ undefined, - [] - ), a.node); + const ctorFunction = isPossibleConstructor(a.node) ? a.node : + isPossibleConstructor(b.node) ? b.node : + undefined; + + if (ctorFunction !== undefined) { + const ctorNode = setTextRange( + factory.createConstructorDeclaration(/* modifiers */ undefined, [], /* body */ undefined), + ctorFunction); + const ctor = emptyNavigationBarNode(ctorNode); + ctor.indent = a.indent + 1; + ctor.children = a.node === ctorFunction ? a.children : b.children; + a.children = a.node === ctorFunction ? concatenate([ctor], b.children || [b]) : concatenate(a.children || [{ ...a }], [ctor]); } else { - a.children = concatenate(a.children, b.children); - if (a.children) { - mergeChildren(a.children, a); + if (a.children || b.children) { + a.children = concatenate(a.children || [{ ...a }], b.children || [b]); + if (a.children) { + mergeChildren(a.children, a); + sortChildren(a.children); + } } } - const bNode = b.node; - // We merge if the outline node previous to b (bIndex - 1) is already part of the current class - // We do this so that statements between class members that do not generate outline nodes do not split up the class outline: - // Ex This should produce one outline node C: - // function C() {}; a = 1; C.prototype.m = function () {} - // Ex This will produce 3 outline nodes: C, a, C - // function C() {}; let a = 1; C.prototype.m = function () {} - if (parent.children![bIndex - 1].node.end === lastANode.end) { - setTextRange(lastANode, { pos: lastANode.pos, end: bNode.end }); - } - else { - if (!a.additionalNodes) a.additionalNodes = []; - a.additionalNodes.push(setTextRange(factory.createClassDeclaration( - /* modifiers */ undefined, - a.name as Identifier || factory.createIdentifier("__class__"), - /* typeParameters */ undefined, - /* heritageClauses */ undefined, - [] - ), b.node)); + lastANode = a.node = setTextRange(factory.createClassDeclaration( + /* modifiers */ undefined, + a.name as Identifier || factory.createIdentifier("__class__"), + /* typeParameters */ undefined, + /* heritageClauses */ undefined, + [] + ), a.node); + } + else { + a.children = concatenate(a.children, b.children); + if (a.children) { + mergeChildren(a.children, a); } - return true; } - return bAssignmentDeclarationKind === AssignmentDeclarationKind.None ? false : true; - } - function tryMerge(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean { - // const v = false as boolean; - if (tryMergeEs5Class(a, b, bIndex, parent)) { - return true; + const bNode = b.node; + // We merge if the outline node previous to b (bIndex - 1) is already part of the current class + // We do this so that statements between class members that do not generate outline nodes do not split up the class outline: + // Ex This should produce one outline node C: + // function C() {}; a = 1; C.prototype.m = function () {} + // Ex This will produce 3 outline nodes: C, a, C + // function C() {}; let a = 1; C.prototype.m = function () {} + if (parent.children![bIndex - 1].node.end === lastANode.end) { + setTextRange(lastANode, { pos: lastANode.pos, end: bNode.end }); } - if (shouldReallyMerge(a.node, b.node, parent)) { - merge(a, b); - return true; - } - return false; + else { + if (!a.additionalNodes) a.additionalNodes = []; + a.additionalNodes.push(setTextRange(factory.createClassDeclaration( + /* modifiers */ undefined, + a.name as Identifier || factory.createIdentifier("__class__"), + /* typeParameters */ undefined, + /* heritageClauses */ undefined, + [] + ), b.node)); + } + return true; + } + return bAssignmentDeclarationKind === AssignmentDeclarationKind.None ? false : true; +} + +function tryMerge(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean { + // const v = false as boolean; + if (tryMergeEs5Class(a, b, bIndex, parent)) { + return true; } + if (shouldReallyMerge(a.node, b.node, parent)) { + merge(a, b); + return true; + } + return false; +} - /** a and b have the same name, but they may not be mergeable. */ - function shouldReallyMerge(a: Node, b: Node, parent: NavigationBarNode): boolean { - if (a.kind !== b.kind || a.parent !== b.parent && !(isOwnChild(a, parent) && isOwnChild(b, parent))) { - return false; - } - switch (a.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return isStatic(a) === isStatic(b); - case SyntaxKind.ModuleDeclaration: - return areSameModule(a as ModuleDeclaration, b as ModuleDeclaration) - && getFullyQualifiedModuleName(a as ModuleDeclaration) === getFullyQualifiedModuleName(b as ModuleDeclaration); - default: - return true; - } +/** a and b have the same name, but they may not be mergeable. */ +function shouldReallyMerge(a: Node, b: Node, parent: NavigationBarNode): boolean { + if (a.kind !== b.kind || a.parent !== b.parent && !(isOwnChild(a, parent) && isOwnChild(b, parent))) { + return false; } + switch (a.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return isStatic(a) === isStatic(b); + case SyntaxKind.ModuleDeclaration: + return areSameModule(a as ModuleDeclaration, b as ModuleDeclaration) + && getFullyQualifiedModuleName(a as ModuleDeclaration) === getFullyQualifiedModuleName(b as ModuleDeclaration); + default: + return true; + } +} - function isSynthesized(node: Node) { - return !!(node.flags & NodeFlags.Synthesized); +function isSynthesized(node: Node) { + return !!(node.flags & NodeFlags.Synthesized); +} + +// We want to merge own children like `I` in in `module A { interface I {} } module A { interface I {} }` +// We don't want to merge unrelated children like `m` in `const o = { a: { m() {} }, b: { m() {} } };` +function isOwnChild(n: Node, parent: NavigationBarNode): boolean { + const par = isModuleBlock(n.parent) ? n.parent.parent : n.parent; + return par === parent.node || contains(parent.additionalNodes, par); +} + +// We use 1 NavNode to represent 'A.B.C', but there are multiple source nodes. +// Only merge module nodes that have the same chain. Don't merge 'A.B.C' with 'A'! +function areSameModule(a: ModuleDeclaration, b: ModuleDeclaration): boolean { + if (!a.body || !b.body) { + return a.body === b.body; } + return a.body.kind === b.body.kind && (a.body.kind !== SyntaxKind.ModuleDeclaration || areSameModule(a.body as ModuleDeclaration, b.body as ModuleDeclaration)); +} - // We want to merge own children like `I` in in `module A { interface I {} } module A { interface I {} }` - // We don't want to merge unrelated children like `m` in `const o = { a: { m() {} }, b: { m() {} } };` - function isOwnChild(n: Node, parent: NavigationBarNode): boolean { - const par = isModuleBlock(n.parent) ? n.parent.parent : n.parent; - return par === parent.node || contains(parent.additionalNodes, par); +/** Merge source into target. Source should be thrown away after this is called. */ +function merge(target: NavigationBarNode, source: NavigationBarNode): void { + target.additionalNodes = target.additionalNodes || []; + target.additionalNodes.push(source.node); + if (source.additionalNodes) { + target.additionalNodes.push(...source.additionalNodes); } - // We use 1 NavNode to represent 'A.B.C', but there are multiple source nodes. - // Only merge module nodes that have the same chain. Don't merge 'A.B.C' with 'A'! - function areSameModule(a: ModuleDeclaration, b: ModuleDeclaration): boolean { - if (!a.body || !b.body) { - return a.body === b.body; - } - return a.body.kind === b.body.kind && (a.body.kind !== SyntaxKind.ModuleDeclaration || areSameModule(a.body as ModuleDeclaration, b.body as ModuleDeclaration)); + target.children = concatenate(target.children, source.children); + if (target.children) { + mergeChildren(target.children, target); + sortChildren(target.children); } +} - /** Merge source into target. Source should be thrown away after this is called. */ - function merge(target: NavigationBarNode, source: NavigationBarNode): void { - target.additionalNodes = target.additionalNodes || []; - target.additionalNodes.push(source.node); - if (source.additionalNodes) { - target.additionalNodes.push(...source.additionalNodes); - } +/** Recursively ensure that each NavNode's children are in sorted order. */ +function sortChildren(children: NavigationBarNode[]): void { + children.sort(compareChildren); +} - target.children = concatenate(target.children, source.children); - if (target.children) { - mergeChildren(target.children, target); - sortChildren(target.children); - } - } +function compareChildren(child1: NavigationBarNode, child2: NavigationBarNode) { + return compareStringsCaseSensitiveUI(tryGetName(child1.node)!, tryGetName(child2.node)!) // TODO: GH#18217 + || compareValues(navigationBarNodeKind(child1), navigationBarNodeKind(child2)); +} - /** Recursively ensure that each NavNode's children are in sorted order. */ - function sortChildren(children: NavigationBarNode[]): void { - children.sort(compareChildren); +/** + * This differs from getItemName because this is just used for sorting. + * We only sort nodes by name that have a more-or-less "direct" name, as opposed to `new()` and the like. + * So `new()` can still come before an `aardvark` method. + */ +function tryGetName(node: Node): string | undefined { + if (node.kind === SyntaxKind.ModuleDeclaration) { + return getModuleName(node as ModuleDeclaration); + } + + const declName = getNameOfDeclaration(node as Declaration); + if (declName && isPropertyName(declName)) { + const propertyName = getPropertyNameForPropertyNameNode(declName); + return propertyName && unescapeLeadingUnderscores(propertyName); + } + switch (node.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ClassExpression: + return getFunctionOrClassName(node as FunctionExpression | ArrowFunction | ClassExpression); + default: + return undefined; } +} - function compareChildren(child1: NavigationBarNode, child2: NavigationBarNode) { - return compareStringsCaseSensitiveUI(tryGetName(child1.node)!, tryGetName(child2.node)!) // TODO: GH#18217 - || compareValues(navigationBarNodeKind(child1), navigationBarNodeKind(child2)); +function getItemName(node: Node, name: Node | undefined): string { + if (node.kind === SyntaxKind.ModuleDeclaration) { + return cleanText(getModuleName(node as ModuleDeclaration)); } - /** - * This differs from getItemName because this is just used for sorting. - * We only sort nodes by name that have a more-or-less "direct" name, as opposed to `new()` and the like. - * So `new()` can still come before an `aardvark` method. - */ - function tryGetName(node: Node): string | undefined { - if (node.kind === SyntaxKind.ModuleDeclaration) { - return getModuleName(node as ModuleDeclaration); + if (name) { + const text = isIdentifier(name) ? name.text + : isElementAccessExpression(name) ? `[${nodeText(name.argumentExpression)}]` + : nodeText(name); + if (text.length > 0) { + return cleanText(text); } + } - const declName = getNameOfDeclaration(node as Declaration); - if (declName && isPropertyName(declName)) { - const propertyName = getPropertyNameForPropertyNameNode(declName); - return propertyName && unescapeLeadingUnderscores(propertyName); - } - switch (node.kind) { - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.ClassExpression: - return getFunctionOrClassName(node as FunctionExpression | ArrowFunction | ClassExpression); - default: - return undefined; - } + switch (node.kind) { + case SyntaxKind.SourceFile: + const sourceFile = node as SourceFile; + return isExternalModule(sourceFile) + ? `"${escapeString(getBaseFileName(removeFileExtension(normalizePath(sourceFile.fileName))))}"` + : ""; + case SyntaxKind.ExportAssignment: + return isExportAssignment(node) && node.isExportEquals ? InternalSymbolName.ExportEquals : InternalSymbolName.Default; + + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + if (getSyntacticModifierFlags(node) & ModifierFlags.Default) { + return "default"; + } + // We may get a string with newlines or other whitespace in the case of an object dereference + // (eg: "app\n.onactivated"), so we should remove the whitespace for readability in the + // navigation bar. + return getFunctionOrClassName(node as ArrowFunction | FunctionExpression | ClassExpression); + case SyntaxKind.Constructor: + return "constructor"; + case SyntaxKind.ConstructSignature: + return "new()"; + case SyntaxKind.CallSignature: + return "()"; + case SyntaxKind.IndexSignature: + return "[]"; + default: + return ""; } +} - function getItemName(node: Node, name: Node | undefined): string { - if (node.kind === SyntaxKind.ModuleDeclaration) { - return cleanText(getModuleName(node as ModuleDeclaration)); +/** Flattens the NavNode tree to a list of items to appear in the primary navbar menu. */ +function primaryNavBarMenuItems(root: NavigationBarNode): NavigationBarNode[] { + // The primary (middle) navbar menu displays the general code navigation hierarchy, similar to the navtree. + // The secondary (right) navbar menu displays the child items of whichever primary item is selected. + // Some less interesting items without their own child navigation items (e.g. a local variable declaration) only show up in the secondary menu. + const primaryNavBarMenuItems: NavigationBarNode[] = []; + function recur(item: NavigationBarNode) { + if (shouldAppearInPrimaryNavBarMenu(item)) { + primaryNavBarMenuItems.push(item); + if (item.children) { + for (const child of item.children) { + recur(child); + } + } } + } + recur(root); + return primaryNavBarMenuItems; - if (name) { - const text = isIdentifier(name) ? name.text - : isElementAccessExpression(name) ? `[${nodeText(name.argumentExpression)}]` - : nodeText(name); - if (text.length > 0) { - return cleanText(text); - } + /** Determines if a node should appear in the primary navbar menu. */ + function shouldAppearInPrimaryNavBarMenu(item: NavigationBarNode): boolean { + // Items with children should always appear in the primary navbar menu. + if (item.children) { + return true; } - switch (node.kind) { + // Some nodes are otherwise important enough to always include in the primary navigation menu. + switch (navigationBarNodeKind(item)) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ModuleDeclaration: case SyntaxKind.SourceFile: - const sourceFile = node as SourceFile; - return isExternalModule(sourceFile) - ? `"${escapeString(getBaseFileName(removeFileExtension(normalizePath(sourceFile.fileName))))}"` - : ""; - case SyntaxKind.ExportAssignment: - return isExportAssignment(node) && node.isExportEquals ? InternalSymbolName.ExportEquals : InternalSymbolName.Default; + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + return true; case SyntaxKind.ArrowFunction: case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - if (getSyntacticModifierFlags(node) & ModifierFlags.Default) { - return "default"; - } - // We may get a string with newlines or other whitespace in the case of an object dereference - // (eg: "app\n.onactivated"), so we should remove the whitespace for readability in the - // navigation bar. - return getFunctionOrClassName(node as ArrowFunction | FunctionExpression | ClassExpression); - case SyntaxKind.Constructor: - return "constructor"; - case SyntaxKind.ConstructSignature: - return "new()"; - case SyntaxKind.CallSignature: - return "()"; - case SyntaxKind.IndexSignature: - return "[]"; - default: - return ""; - } - } + return isTopLevelFunctionDeclaration(item); - /** Flattens the NavNode tree to a list of items to appear in the primary navbar menu. */ - function primaryNavBarMenuItems(root: NavigationBarNode): NavigationBarNode[] { - // The primary (middle) navbar menu displays the general code navigation hierarchy, similar to the navtree. - // The secondary (right) navbar menu displays the child items of whichever primary item is selected. - // Some less interesting items without their own child navigation items (e.g. a local variable declaration) only show up in the secondary menu. - const primaryNavBarMenuItems: NavigationBarNode[] = []; - function recur(item: NavigationBarNode) { - if (shouldAppearInPrimaryNavBarMenu(item)) { - primaryNavBarMenuItems.push(item); - if (item.children) { - for (const child of item.children) { - recur(child); - } - } - } + default: + return false; } - recur(root); - return primaryNavBarMenuItems; - - /** Determines if a node should appear in the primary navbar menu. */ - function shouldAppearInPrimaryNavBarMenu(item: NavigationBarNode): boolean { - // Items with children should always appear in the primary navbar menu. - if (item.children) { - return true; + function isTopLevelFunctionDeclaration(item: NavigationBarNode): boolean { + if (!(item.node as FunctionDeclaration).body) { + return false; } - // Some nodes are otherwise important enough to always include in the primary navigation menu. - switch (navigationBarNodeKind(item)) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ModuleDeclaration: + switch (navigationBarNodeKind(item.parent!)) { + case SyntaxKind.ModuleBlock: case SyntaxKind.SourceFile: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: return true; - - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - return isTopLevelFunctionDeclaration(item); - default: return false; } - function isTopLevelFunctionDeclaration(item: NavigationBarNode): boolean { - if (!(item.node as FunctionDeclaration).body) { - return false; - } - - switch (navigationBarNodeKind(item.parent!)) { - case SyntaxKind.ModuleBlock: - case SyntaxKind.SourceFile: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - return true; - default: - return false; - } - } } } +} - function convertToTree(n: NavigationBarNode): NavigationTree { - return { - text: getItemName(n.node, n.name), - kind: getNodeKind(n.node), - kindModifiers: getModifiers(n.node), - spans: getSpans(n), - nameSpan: n.name && getNodeSpan(n.name), - childItems: map(n.children, convertToTree) - }; - } +function convertToTree(n: NavigationBarNode): NavigationTree { + return { + text: getItemName(n.node, n.name), + kind: getNodeKind(n.node), + kindModifiers: getModifiers(n.node), + spans: getSpans(n), + nameSpan: n.name && getNodeSpan(n.name), + childItems: map(n.children, convertToTree) + }; +} + +function convertToPrimaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem { + return { + text: getItemName(n.node, n.name), + kind: getNodeKind(n.node), + kindModifiers: getModifiers(n.node), + spans: getSpans(n), + childItems: map(n.children, convertToSecondaryNavBarMenuItem) || emptyChildItemArray, + indent: n.indent, + bolded: false, + grayed: false + }; - function convertToPrimaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem { + function convertToSecondaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem { return { text: getItemName(n.node, n.name), kind: getNodeKind(n.node), - kindModifiers: getModifiers(n.node), + kindModifiers: getNodeModifiers(n.node), spans: getSpans(n), - childItems: map(n.children, convertToSecondaryNavBarMenuItem) || emptyChildItemArray, - indent: n.indent, + childItems: emptyChildItemArray, + indent: 0, bolded: false, grayed: false }; + } +} - function convertToSecondaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem { - return { - text: getItemName(n.node, n.name), - kind: getNodeKind(n.node), - kindModifiers: getNodeModifiers(n.node), - spans: getSpans(n), - childItems: emptyChildItemArray, - indent: 0, - bolded: false, - grayed: false - }; +function getSpans(n: NavigationBarNode): TextSpan[] { + const spans = [getNodeSpan(n.node)]; + if (n.additionalNodes) { + for (const node of n.additionalNodes) { + spans.push(getNodeSpan(node)); } } + return spans; +} - function getSpans(n: NavigationBarNode): TextSpan[] { - const spans = [getNodeSpan(n.node)]; - if (n.additionalNodes) { - for (const node of n.additionalNodes) { - spans.push(getNodeSpan(node)); - } - } - return spans; +function getModuleName(moduleDeclaration: ModuleDeclaration): string { + // We want to maintain quotation marks. + if (isAmbientModule(moduleDeclaration)) { + return getTextOfNode(moduleDeclaration.name); } - function getModuleName(moduleDeclaration: ModuleDeclaration): string { - // We want to maintain quotation marks. - if (isAmbientModule(moduleDeclaration)) { - return getTextOfNode(moduleDeclaration.name); - } + return getFullyQualifiedModuleName(moduleDeclaration); +} - return getFullyQualifiedModuleName(moduleDeclaration); +function getFullyQualifiedModuleName(moduleDeclaration: ModuleDeclaration): string { + // Otherwise, we need to aggregate each identifier to build up the qualified name. + const result = [getTextOfIdentifierOrLiteral(moduleDeclaration.name)]; + while (moduleDeclaration.body && moduleDeclaration.body.kind === SyntaxKind.ModuleDeclaration) { + moduleDeclaration = moduleDeclaration.body; + result.push(getTextOfIdentifierOrLiteral(moduleDeclaration.name)); } + return result.join("."); +} - function getFullyQualifiedModuleName(moduleDeclaration: ModuleDeclaration): string { - // Otherwise, we need to aggregate each identifier to build up the qualified name. - const result = [getTextOfIdentifierOrLiteral(moduleDeclaration.name)]; - while (moduleDeclaration.body && moduleDeclaration.body.kind === SyntaxKind.ModuleDeclaration) { - moduleDeclaration = moduleDeclaration.body; - result.push(getTextOfIdentifierOrLiteral(moduleDeclaration.name)); - } - return result.join("."); - } +/** + * For 'module A.B.C', we want to get the node for 'C'. + * We store 'A' as associated with a NavNode, and use getModuleName to traverse down again. + */ +function getInteriorModule(decl: ModuleDeclaration): ModuleDeclaration { + return decl.body && isModuleDeclaration(decl.body) ? getInteriorModule(decl.body) : decl; +} - /** - * For 'module A.B.C', we want to get the node for 'C'. - * We store 'A' as associated with a NavNode, and use getModuleName to traverse down again. - */ - function getInteriorModule(decl: ModuleDeclaration): ModuleDeclaration { - return decl.body && isModuleDeclaration(decl.body) ? getInteriorModule(decl.body) : decl; - } +function isComputedProperty(member: EnumMember): boolean { + return !member.name || member.name.kind === SyntaxKind.ComputedPropertyName; +} - function isComputedProperty(member: EnumMember): boolean { - return !member.name || member.name.kind === SyntaxKind.ComputedPropertyName; - } +function getNodeSpan(node: Node): TextSpan { + return node.kind === SyntaxKind.SourceFile ? createTextSpanFromRange(node) : createTextSpanFromNode(node, curSourceFile); +} - function getNodeSpan(node: Node): TextSpan { - return node.kind === SyntaxKind.SourceFile ? createTextSpanFromRange(node) : createTextSpanFromNode(node, curSourceFile); +function getModifiers(node: Node): string { + if (node.parent && node.parent.kind === SyntaxKind.VariableDeclaration) { + node = node.parent; } + return getNodeModifiers(node); +} - function getModifiers(node: Node): string { - if (node.parent && node.parent.kind === SyntaxKind.VariableDeclaration) { - node = node.parent; - } - return getNodeModifiers(node); +function getFunctionOrClassName(node: FunctionExpression | FunctionDeclaration | ArrowFunction | ClassLikeDeclaration): string { + const { parent } = node; + if (node.name && getFullWidth(node.name) > 0) { + return cleanText(declarationNameToString(node.name)); } + // See if it is a var initializer. If so, use the var name. + else if (isVariableDeclaration(parent)) { + return cleanText(declarationNameToString(parent.name)); + } + // See if it is of the form " = function(){...}". If so, use the text from the left-hand side. + else if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken) { + return nodeText(parent.left).replace(whiteSpaceRegex, ""); + } + // See if it is a property assignment, and if so use the property name + else if (isPropertyAssignment(parent)) { + return nodeText(parent.name); + } + // Default exports are named "default" + else if (getSyntacticModifierFlags(node) & ModifierFlags.Default) { + return "default"; + } + else if (isClassLike(node)) { + return ""; + } + else if (isCallExpression(parent)) { + let name = getCalledExpressionName(parent.expression); + if (name !== undefined) { + name = cleanText(name); - function getFunctionOrClassName(node: FunctionExpression | FunctionDeclaration | ArrowFunction | ClassLikeDeclaration): string { - const { parent } = node; - if (node.name && getFullWidth(node.name) > 0) { - return cleanText(declarationNameToString(node.name)); - } - // See if it is a var initializer. If so, use the var name. - else if (isVariableDeclaration(parent)) { - return cleanText(declarationNameToString(parent.name)); - } - // See if it is of the form " = function(){...}". If so, use the text from the left-hand side. - else if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken) { - return nodeText(parent.left).replace(whiteSpaceRegex, ""); - } - // See if it is a property assignment, and if so use the property name - else if (isPropertyAssignment(parent)) { - return nodeText(parent.name); - } - // Default exports are named "default" - else if (getSyntacticModifierFlags(node) & ModifierFlags.Default) { - return "default"; - } - else if (isClassLike(node)) { - return ""; - } - else if (isCallExpression(parent)) { - let name = getCalledExpressionName(parent.expression); - if (name !== undefined) { - name = cleanText(name); - - if (name.length > maxLength) { - return `${name} callback`; - } - - const args = cleanText(mapDefined(parent.arguments, a => isStringLiteralLike(a) ? a.getText(curSourceFile) : undefined).join(", ")); - return `${name}(${args}) callback`; + if (name.length > maxLength) { + return `${name} callback`; } + + const args = cleanText(mapDefined(parent.arguments, a => isStringLiteralLike(a) ? a.getText(curSourceFile) : undefined).join(", ")); + return `${name}(${args}) callback`; } - return ""; } + return ""; +} - // See also 'tryGetPropertyAccessOrIdentifierToString' - function getCalledExpressionName(expr: Expression): string | undefined { - if (isIdentifier(expr)) { - return expr.text; - } - else if (isPropertyAccessExpression(expr)) { - const left = getCalledExpressionName(expr.expression); - const right = expr.name.text; - return left === undefined ? right : `${left}.${right}`; - } - else { - return undefined; - } +// See also 'tryGetPropertyAccessOrIdentifierToString' +function getCalledExpressionName(expr: Expression): string | undefined { + if (isIdentifier(expr)) { + return expr.text; } + else if (isPropertyAccessExpression(expr)) { + const left = getCalledExpressionName(expr.expression); + const right = expr.name.text; + return left === undefined ? right : `${left}.${right}`; + } + else { + return undefined; + } +} - function isFunctionOrClassExpression(node: Node): node is ArrowFunction | FunctionExpression | ClassExpression { - switch (node.kind) { - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ClassExpression: - return true; - default: - return false; - } +function isFunctionOrClassExpression(node: Node): node is ArrowFunction | FunctionExpression | ClassExpression { + switch (node.kind) { + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ClassExpression: + return true; + default: + return false; } +} - function cleanText(text: string): string { - // Truncate to maximum amount of characters as we don't want to do a big replace operation. - text = text.length > maxLength ? text.substring(0, maxLength) + "..." : text; +function cleanText(text: string): string { + // Truncate to maximum amount of characters as we don't want to do a big replace operation. + text = text.length > maxLength ? text.substring(0, maxLength) + "..." : text; - // Replaces ECMAScript line terminators and removes the trailing `\` from each line: - // \n - Line Feed - // \r - Carriage Return - // \u2028 - Line separator - // \u2029 - Paragraph separator - return text.replace(/\\?(\r?\n|\r|\u2028|\u2029)/g, ""); - } + // Replaces ECMAScript line terminators and removes the trailing `\` from each line: + // \n - Line Feed + // \r - Carriage Return + // \u2028 - Line separator + // \u2029 - Paragraph separator + return text.replace(/\\?(\r?\n|\r|\u2028|\u2029)/g, ""); } diff --git a/src/services/organizeImports.ts b/src/services/organizeImports.ts index 2928d604acdf3..f8204d4f4b4be 100644 --- a/src/services/organizeImports.ts +++ b/src/services/organizeImports.ts @@ -1,561 +1,586 @@ -/* @internal */ -namespace ts.OrganizeImports { - - /** - * Organize imports by: - * 1) Removing unused imports - * 2) Coalescing imports from the same module - * 3) Sorting imports - */ - export function organizeImports( - sourceFile: SourceFile, - formatContext: formatting.FormatContext, - host: LanguageServiceHost, - program: Program, - preferences: UserPreferences, - mode: OrganizeImportsMode, - ) { - const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext, preferences }); - const shouldSort = mode === OrganizeImportsMode.SortAndCombine || mode === OrganizeImportsMode.All; - const shouldCombine = shouldSort; // These are currently inseparable, but I draw a distinction for clarity and in case we add modes in the future. - const shouldRemove = mode === OrganizeImportsMode.RemoveUnused || mode === OrganizeImportsMode.All; - const maybeRemove = shouldRemove ? removeUnusedImports : identity; - const maybeCoalesce = shouldCombine ? coalesceImports : identity; - const processImportsOfSameModuleSpecifier = (importGroup: readonly ImportDeclaration[]) => { - const processedDeclarations = maybeCoalesce(maybeRemove(importGroup, sourceFile, program)); - return shouldSort - ? stableSort(processedDeclarations, (s1, s2) => compareImportsOrRequireStatements(s1, s2)) - : processedDeclarations; - }; +import { + AnyImportOrRequireStatement, arrayIsSorted, binarySearch, compareBooleans, compareStringsCaseInsensitive, + compareValues, Comparison, createScanner, emptyArray, ExportDeclaration, ExportSpecifier, Expression, factory, + FileTextChanges, + FindAllReferences, flatMap, formatting, getNewLineOrDefaultFromHost, group, Identifier, identity, ImportDeclaration, + ImportOrExportSpecifier, ImportSpecifier, isAmbientModule, isExportDeclaration, isExternalModuleNameRelative, + isExternalModuleReference, isImportDeclaration, isNamedExports, isNamedImports, isNamespaceImport, isString, + isStringLiteral, isStringLiteralLike, jsxModeNeedsExplicitImport, LanguageServiceHost, length, map, + NamedImportBindings, NamedImports, NamespaceImport, OrganizeImportsMode, Program, Scanner, some, + SortedReadonlyArray, SourceFile, stableSort, suppressLeadingTrivia, SyntaxKind, textChanges, TransformFlags, + tryCast, UserPreferences, +} from "./_namespaces/ts"; + +/** + * Organize imports by: + * 1) Removing unused imports + * 2) Coalescing imports from the same module + * 3) Sorting imports + * + * @internal + */ +export function organizeImports( + sourceFile: SourceFile, + formatContext: formatting.FormatContext, + host: LanguageServiceHost, + program: Program, + preferences: UserPreferences, + mode: OrganizeImportsMode, +): FileTextChanges[] { + const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext, preferences }); + const shouldSort = mode === OrganizeImportsMode.SortAndCombine || mode === OrganizeImportsMode.All; + const shouldCombine = shouldSort; // These are currently inseparable, but I draw a distinction for clarity and in case we add modes in the future. + const shouldRemove = mode === OrganizeImportsMode.RemoveUnused || mode === OrganizeImportsMode.All; + const maybeRemove = shouldRemove ? removeUnusedImports : identity; + const maybeCoalesce = shouldCombine ? coalesceImports : identity; + const processImportsOfSameModuleSpecifier = (importGroup: readonly ImportDeclaration[]) => { + const processedDeclarations = maybeCoalesce(maybeRemove(importGroup, sourceFile, program)); + return shouldSort + ? stableSort(processedDeclarations, (s1, s2) => compareImportsOrRequireStatements(s1, s2)) + : processedDeclarations; + }; + + // All of the old ImportDeclarations in the file, in syntactic order. + const topLevelImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration)); + topLevelImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier)); + + // Exports are always used + if (mode !== OrganizeImportsMode.RemoveUnused) { + // All of the old ExportDeclarations in the file, in syntactic order. + const topLevelExportDecls = sourceFile.statements.filter(isExportDeclaration); + organizeImportsWorker(topLevelExportDecls, coalesceExports); + } + + for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) { + if (!ambientModule.body) continue; - // All of the old ImportDeclarations in the file, in syntactic order. - const topLevelImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration)); - topLevelImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier)); + const ambientModuleImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, ambientModule.body.statements.filter(isImportDeclaration)); + ambientModuleImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier)); // Exports are always used if (mode !== OrganizeImportsMode.RemoveUnused) { - // All of the old ExportDeclarations in the file, in syntactic order. - const topLevelExportDecls = sourceFile.statements.filter(isExportDeclaration); - organizeImportsWorker(topLevelExportDecls, coalesceExports); + const ambientModuleExportDecls = ambientModule.body.statements.filter(isExportDeclaration); + organizeImportsWorker(ambientModuleExportDecls, coalesceExports); } + } - for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) { - if (!ambientModule.body) continue; - - const ambientModuleImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, ambientModule.body.statements.filter(isImportDeclaration)); - ambientModuleImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier)); + return changeTracker.getChanges(); - // Exports are always used - if (mode !== OrganizeImportsMode.RemoveUnused) { - const ambientModuleExportDecls = ambientModule.body.statements.filter(isExportDeclaration); - organizeImportsWorker(ambientModuleExportDecls, coalesceExports); - } + function organizeImportsWorker( + oldImportDecls: readonly T[], + coalesce: (group: readonly T[]) => readonly T[], + ) { + if (length(oldImportDecls) === 0) { + return; } - return changeTracker.getChanges(); - - function organizeImportsWorker( - oldImportDecls: readonly T[], - coalesce: (group: readonly T[]) => readonly T[], - ) { - if (length(oldImportDecls) === 0) { - return; - } - - // Special case: normally, we'd expect leading and trailing trivia to follow each import - // around as it's sorted. However, we do not want this to happen for leading trivia - // on the first import because it is probably the header comment for the file. - // Consider: we could do a more careful check that this trivia is actually a header, - // but the consequences of being wrong are very minor. - suppressLeadingTrivia(oldImportDecls[0]); - - const oldImportGroups = shouldCombine - ? group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier!)!) - : [oldImportDecls]; - const sortedImportGroups = shouldSort - ? stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiers(group1[0].moduleSpecifier, group2[0].moduleSpecifier)) - : oldImportGroups; - const newImportDecls = flatMap(sortedImportGroups, importGroup => - getExternalModuleName(importGroup[0].moduleSpecifier!) - ? coalesce(importGroup) - : importGroup); - - // Delete all nodes if there are no imports. - if (newImportDecls.length === 0) { - // Consider the first node to have trailingTrivia as we want to exclude the - // "header" comment. - changeTracker.deleteNodes(sourceFile, oldImportDecls, { - trailingTriviaOption: textChanges.TrailingTriviaOption.Include, - }, /*hasTrailingComment*/ true); - } - else { - // Note: Delete the surrounding trivia because it will have been retained in newImportDecls. - const replaceOptions = { - leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, // Leave header comment in place - trailingTriviaOption: textChanges.TrailingTriviaOption.Include, - suffix: getNewLineOrDefaultFromHost(host, formatContext.options), - }; - changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, replaceOptions); - const hasTrailingComment = changeTracker.nodeHasTrailingComment(sourceFile, oldImportDecls[0], replaceOptions); - changeTracker.deleteNodes(sourceFile, oldImportDecls.slice(1), { - trailingTriviaOption: textChanges.TrailingTriviaOption.Include, - }, hasTrailingComment); - } + // Special case: normally, we'd expect leading and trailing trivia to follow each import + // around as it's sorted. However, we do not want this to happen for leading trivia + // on the first import because it is probably the header comment for the file. + // Consider: we could do a more careful check that this trivia is actually a header, + // but the consequences of being wrong are very minor. + suppressLeadingTrivia(oldImportDecls[0]); + + const oldImportGroups = shouldCombine + ? group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier!)!) + : [oldImportDecls]; + const sortedImportGroups = shouldSort + ? stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiers(group1[0].moduleSpecifier, group2[0].moduleSpecifier)) + : oldImportGroups; + const newImportDecls = flatMap(sortedImportGroups, importGroup => + getExternalModuleName(importGroup[0].moduleSpecifier!) + ? coalesce(importGroup) + : importGroup); + + // Delete all nodes if there are no imports. + if (newImportDecls.length === 0) { + // Consider the first node to have trailingTrivia as we want to exclude the + // "header" comment. + changeTracker.deleteNodes(sourceFile, oldImportDecls, { + trailingTriviaOption: textChanges.TrailingTriviaOption.Include, + }, /*hasTrailingComment*/ true); + } + else { + // Note: Delete the surrounding trivia because it will have been retained in newImportDecls. + const replaceOptions = { + leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, // Leave header comment in place + trailingTriviaOption: textChanges.TrailingTriviaOption.Include, + suffix: getNewLineOrDefaultFromHost(host, formatContext.options), + }; + changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, replaceOptions); + const hasTrailingComment = changeTracker.nodeHasTrailingComment(sourceFile, oldImportDecls[0], replaceOptions); + changeTracker.deleteNodes(sourceFile, oldImportDecls.slice(1), { + trailingTriviaOption: textChanges.TrailingTriviaOption.Include, + }, hasTrailingComment); } } +} - function groupImportsByNewlineContiguous(sourceFile: SourceFile, importDecls: ImportDeclaration[]): ImportDeclaration[][] { - const scanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/ false, sourceFile.languageVariant); - const groupImports: ImportDeclaration[][] = []; - let groupIndex = 0; - for (const topLevelImportDecl of importDecls) { - if (isNewGroup(sourceFile, topLevelImportDecl, scanner)) { - groupIndex++; - } - - if (!groupImports[groupIndex]) { - groupImports[groupIndex] = []; - } +function groupImportsByNewlineContiguous(sourceFile: SourceFile, importDecls: ImportDeclaration[]): ImportDeclaration[][] { + const scanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/ false, sourceFile.languageVariant); + const groupImports: ImportDeclaration[][] = []; + let groupIndex = 0; + for (const topLevelImportDecl of importDecls) { + if (isNewGroup(sourceFile, topLevelImportDecl, scanner)) { + groupIndex++; + } - groupImports[groupIndex].push(topLevelImportDecl); + if (!groupImports[groupIndex]) { + groupImports[groupIndex] = []; } - return groupImports; + groupImports[groupIndex].push(topLevelImportDecl); } - // a new group is created if an import includes at least two new line - // new line from multi-line comment doesn't count - function isNewGroup(sourceFile: SourceFile, topLevelImportDecl: ImportDeclaration, scanner: Scanner) { - const startPos = topLevelImportDecl.getFullStart(); - const endPos = topLevelImportDecl.getStart(); - scanner.setText(sourceFile.text, startPos, endPos - startPos); + return groupImports; +} - let numberOfNewLines = 0; - while (scanner.getTokenPos() < endPos) { - const tokenKind = scanner.scan(); +// a new group is created if an import includes at least two new line +// new line from multi-line comment doesn't count +function isNewGroup(sourceFile: SourceFile, topLevelImportDecl: ImportDeclaration, scanner: Scanner) { + const startPos = topLevelImportDecl.getFullStart(); + const endPos = topLevelImportDecl.getStart(); + scanner.setText(sourceFile.text, startPos, endPos - startPos); - if (tokenKind === SyntaxKind.NewLineTrivia) { - numberOfNewLines++; + let numberOfNewLines = 0; + while (scanner.getTokenPos() < endPos) { + const tokenKind = scanner.scan(); - if (numberOfNewLines >= 2) { - return true; - } + if (tokenKind === SyntaxKind.NewLineTrivia) { + numberOfNewLines++; + + if (numberOfNewLines >= 2) { + return true; } } - - return false; } - function removeUnusedImports(oldImports: readonly ImportDeclaration[], sourceFile: SourceFile, program: Program) { - const typeChecker = program.getTypeChecker(); - const compilerOptions = program.getCompilerOptions(); - const jsxNamespace = typeChecker.getJsxNamespace(sourceFile); - const jsxFragmentFactory = typeChecker.getJsxFragmentFactory(sourceFile); - const jsxElementsPresent = !!(sourceFile.transformFlags & TransformFlags.ContainsJsx); + return false; +} - const usedImports: ImportDeclaration[] = []; +function removeUnusedImports(oldImports: readonly ImportDeclaration[], sourceFile: SourceFile, program: Program) { + const typeChecker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); + const jsxNamespace = typeChecker.getJsxNamespace(sourceFile); + const jsxFragmentFactory = typeChecker.getJsxFragmentFactory(sourceFile); + const jsxElementsPresent = !!(sourceFile.transformFlags & TransformFlags.ContainsJsx); - for (const importDecl of oldImports) { - const { importClause, moduleSpecifier } = importDecl; + const usedImports: ImportDeclaration[] = []; - if (!importClause) { - // Imports without import clauses are assumed to be included for their side effects and are not removed. - usedImports.push(importDecl); - continue; - } + for (const importDecl of oldImports) { + const { importClause, moduleSpecifier } = importDecl; + + if (!importClause) { + // Imports without import clauses are assumed to be included for their side effects and are not removed. + usedImports.push(importDecl); + continue; + } - let { name, namedBindings } = importClause; + let { name, namedBindings } = importClause; - // Default import - if (name && !isDeclarationUsed(name)) { - name = undefined; - } + // Default import + if (name && !isDeclarationUsed(name)) { + name = undefined; + } - if (namedBindings) { - if (isNamespaceImport(namedBindings)) { - // Namespace import - if (!isDeclarationUsed(namedBindings.name)) { - namedBindings = undefined; - } + if (namedBindings) { + if (isNamespaceImport(namedBindings)) { + // Namespace import + if (!isDeclarationUsed(namedBindings.name)) { + namedBindings = undefined; } - else { - // List of named imports - const newElements = namedBindings.elements.filter(e => isDeclarationUsed(e.name)); - if (newElements.length < namedBindings.elements.length) { - namedBindings = newElements.length - ? factory.updateNamedImports(namedBindings, newElements) - : undefined; - } + } + else { + // List of named imports + const newElements = namedBindings.elements.filter(e => isDeclarationUsed(e.name)); + if (newElements.length < namedBindings.elements.length) { + namedBindings = newElements.length + ? factory.updateNamedImports(namedBindings, newElements) + : undefined; } } + } - if (name || namedBindings) { - usedImports.push(updateImportDeclarationAndClause(importDecl, name, namedBindings)); + if (name || namedBindings) { + usedImports.push(updateImportDeclarationAndClause(importDecl, name, namedBindings)); + } + // If a module is imported to be augmented, it’s used + else if (hasModuleDeclarationMatchingSpecifier(sourceFile, moduleSpecifier)) { + // If we’re in a declaration file, it’s safe to remove the import clause from it + if (sourceFile.isDeclarationFile) { + usedImports.push(factory.createImportDeclaration( + importDecl.modifiers, + /*importClause*/ undefined, + moduleSpecifier, + /*assertClause*/ undefined)); } - // If a module is imported to be augmented, it’s used - else if (hasModuleDeclarationMatchingSpecifier(sourceFile, moduleSpecifier)) { - // If we’re in a declaration file, it’s safe to remove the import clause from it - if (sourceFile.isDeclarationFile) { - usedImports.push(factory.createImportDeclaration( - importDecl.modifiers, - /*importClause*/ undefined, - moduleSpecifier, - /*assertClause*/ undefined)); - } - // If we’re not in a declaration file, we can’t remove the import clause even though - // the imported symbols are unused, because removing them makes it look like the import - // declaration has side effects, which will cause it to be preserved in the JS emit. - else { - usedImports.push(importDecl); - } + // If we’re not in a declaration file, we can’t remove the import clause even though + // the imported symbols are unused, because removing them makes it look like the import + // declaration has side effects, which will cause it to be preserved in the JS emit. + else { + usedImports.push(importDecl); } } + } - return usedImports; + return usedImports; - function isDeclarationUsed(identifier: Identifier) { - // The JSX factory symbol is always used if JSX elements are present - even if they are not allowed. - return jsxElementsPresent && (identifier.text === jsxNamespace || jsxFragmentFactory && identifier.text === jsxFragmentFactory) && jsxModeNeedsExplicitImport(compilerOptions.jsx) || - FindAllReferences.Core.isSymbolReferencedInFile(identifier, typeChecker, sourceFile); - } + function isDeclarationUsed(identifier: Identifier) { + // The JSX factory symbol is always used if JSX elements are present - even if they are not allowed. + return jsxElementsPresent && (identifier.text === jsxNamespace || jsxFragmentFactory && identifier.text === jsxFragmentFactory) && jsxModeNeedsExplicitImport(compilerOptions.jsx) || + FindAllReferences.Core.isSymbolReferencedInFile(identifier, typeChecker, sourceFile); } +} + +function hasModuleDeclarationMatchingSpecifier(sourceFile: SourceFile, moduleSpecifier: Expression) { + const moduleSpecifierText = isStringLiteral(moduleSpecifier) && moduleSpecifier.text; + return isString(moduleSpecifierText) && some(sourceFile.moduleAugmentations, moduleName => + isStringLiteral(moduleName) + && moduleName.text === moduleSpecifierText); +} + +function getExternalModuleName(specifier: Expression) { + return specifier !== undefined && isStringLiteralLike(specifier) + ? specifier.text + : undefined; +} - function hasModuleDeclarationMatchingSpecifier(sourceFile: SourceFile, moduleSpecifier: Expression) { - const moduleSpecifierText = isStringLiteral(moduleSpecifier) && moduleSpecifier.text; - return isString(moduleSpecifierText) && some(sourceFile.moduleAugmentations, moduleName => - isStringLiteral(moduleName) - && moduleName.text === moduleSpecifierText); +// Internal for testing +/** + * @param importGroup a list of ImportDeclarations, all with the same module name. + * + * @internal + */ +export function coalesceImports(importGroup: readonly ImportDeclaration[]) { + if (importGroup.length === 0) { + return importGroup; } - function getExternalModuleName(specifier: Expression) { - return specifier !== undefined && isStringLiteralLike(specifier) - ? specifier.text - : undefined; + const { importWithoutClause, typeOnlyImports, regularImports } = getCategorizedImports(importGroup); + + const coalescedImports: ImportDeclaration[] = []; + + if (importWithoutClause) { + coalescedImports.push(importWithoutClause); } - // Internal for testing - /** - * @param importGroup a list of ImportDeclarations, all with the same module name. - */ - export function coalesceImports(importGroup: readonly ImportDeclaration[]) { - if (importGroup.length === 0) { - return importGroup; + for (const group of [regularImports, typeOnlyImports]) { + const isTypeOnly = group === typeOnlyImports; + const { defaultImports, namespaceImports, namedImports } = group; + // Normally, we don't combine default and namespace imports, but it would be silly to + // produce two import declarations in this special case. + if (!isTypeOnly && defaultImports.length === 1 && namespaceImports.length === 1 && namedImports.length === 0) { + // Add the namespace import to the existing default ImportDeclaration. + const defaultImport = defaultImports[0]; + coalescedImports.push( + updateImportDeclarationAndClause(defaultImport, defaultImport.importClause!.name, namespaceImports[0].importClause!.namedBindings)); // TODO: GH#18217 + + continue; } - const { importWithoutClause, typeOnlyImports, regularImports } = getCategorizedImports(importGroup); + const sortedNamespaceImports = stableSort(namespaceImports, (i1, i2) => + compareIdentifiers((i1.importClause!.namedBindings as NamespaceImport).name, (i2.importClause!.namedBindings as NamespaceImport).name)); // TODO: GH#18217 - const coalescedImports: ImportDeclaration[] = []; + for (const namespaceImport of sortedNamespaceImports) { + // Drop the name, if any + coalescedImports.push( + updateImportDeclarationAndClause(namespaceImport, /*name*/ undefined, namespaceImport.importClause!.namedBindings)); // TODO: GH#18217 + } - if (importWithoutClause) { - coalescedImports.push(importWithoutClause); + if (defaultImports.length === 0 && namedImports.length === 0) { + continue; } - for (const group of [regularImports, typeOnlyImports]) { - const isTypeOnly = group === typeOnlyImports; - const { defaultImports, namespaceImports, namedImports } = group; - // Normally, we don't combine default and namespace imports, but it would be silly to - // produce two import declarations in this special case. - if (!isTypeOnly && defaultImports.length === 1 && namespaceImports.length === 1 && namedImports.length === 0) { - // Add the namespace import to the existing default ImportDeclaration. - const defaultImport = defaultImports[0]; - coalescedImports.push( - updateImportDeclarationAndClause(defaultImport, defaultImport.importClause!.name, namespaceImports[0].importClause!.namedBindings)); // TODO: GH#18217 - - continue; + let newDefaultImport: Identifier | undefined; + const newImportSpecifiers: ImportSpecifier[] = []; + if (defaultImports.length === 1) { + newDefaultImport = defaultImports[0].importClause!.name; + } + else { + for (const defaultImport of defaultImports) { + newImportSpecifiers.push( + factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("default"), defaultImport.importClause!.name!)); // TODO: GH#18217 } + } - const sortedNamespaceImports = stableSort(namespaceImports, (i1, i2) => - compareIdentifiers((i1.importClause!.namedBindings as NamespaceImport).name, (i2.importClause!.namedBindings as NamespaceImport).name)); // TODO: GH#18217 + newImportSpecifiers.push(...getNewImportSpecifiers(namedImports)); + + const sortedImportSpecifiers = sortSpecifiers(newImportSpecifiers); + const importDecl = defaultImports.length > 0 + ? defaultImports[0] + : namedImports[0]; + + const newNamedImports = sortedImportSpecifiers.length === 0 + ? newDefaultImport + ? undefined + : factory.createNamedImports(emptyArray) + : namedImports.length === 0 + ? factory.createNamedImports(sortedImportSpecifiers) + : factory.updateNamedImports(namedImports[0].importClause!.namedBindings as NamedImports, sortedImportSpecifiers); // TODO: GH#18217 + + // Type-only imports are not allowed to mix default, namespace, and named imports in any combination. + // We could rewrite a default import as a named import (`import { default as name }`), but we currently + // choose not to as a stylistic preference. + if (isTypeOnly && newDefaultImport && newNamedImports) { + coalescedImports.push( + updateImportDeclarationAndClause(importDecl, newDefaultImport, /*namedBindings*/ undefined)); + coalescedImports.push( + updateImportDeclarationAndClause(namedImports[0] ?? importDecl, /*name*/ undefined, newNamedImports)); + } + else { + coalescedImports.push( + updateImportDeclarationAndClause(importDecl, newDefaultImport, newNamedImports)); + } + } - for (const namespaceImport of sortedNamespaceImports) { - // Drop the name, if any - coalescedImports.push( - updateImportDeclarationAndClause(namespaceImport, /*name*/ undefined, namespaceImport.importClause!.namedBindings)); // TODO: GH#18217 - } + return coalescedImports; - if (defaultImports.length === 0 && namedImports.length === 0) { - continue; - } +} - let newDefaultImport: Identifier | undefined; - const newImportSpecifiers: ImportSpecifier[] = []; - if (defaultImports.length === 1) { - newDefaultImport = defaultImports[0].importClause!.name; - } - else { - for (const defaultImport of defaultImports) { - newImportSpecifiers.push( - factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("default"), defaultImport.importClause!.name!)); // TODO: GH#18217 - } - } +interface ImportGroup { + defaultImports: ImportDeclaration[]; + namespaceImports: ImportDeclaration[]; + namedImports: ImportDeclaration[]; +} + +/* + * Returns entire import declarations because they may already have been rewritten and + * may lack parent pointers. The desired parts can easily be recovered based on the + * categorization. + * + * NB: There may be overlap between `defaultImports` and `namespaceImports`/`namedImports`. + */ +function getCategorizedImports(importGroup: readonly ImportDeclaration[]) { + let importWithoutClause: ImportDeclaration | undefined; + const typeOnlyImports: ImportGroup = { defaultImports: [], namespaceImports: [], namedImports: [] }; + const regularImports: ImportGroup = { defaultImports: [], namespaceImports: [], namedImports: [] }; + + for (const importDeclaration of importGroup) { + if (importDeclaration.importClause === undefined) { + // Only the first such import is interesting - the others are redundant. + // Note: Unfortunately, we will lose trivia that was on this node. + importWithoutClause = importWithoutClause || importDeclaration; + continue; + } + + const group = importDeclaration.importClause.isTypeOnly ? typeOnlyImports : regularImports; + const { name, namedBindings } = importDeclaration.importClause; - newImportSpecifiers.push(...getNewImportSpecifiers(namedImports)); - - const sortedImportSpecifiers = sortSpecifiers(newImportSpecifiers); - const importDecl = defaultImports.length > 0 - ? defaultImports[0] - : namedImports[0]; - - const newNamedImports = sortedImportSpecifiers.length === 0 - ? newDefaultImport - ? undefined - : factory.createNamedImports(emptyArray) - : namedImports.length === 0 - ? factory.createNamedImports(sortedImportSpecifiers) - : factory.updateNamedImports(namedImports[0].importClause!.namedBindings as NamedImports, sortedImportSpecifiers); // TODO: GH#18217 - - // Type-only imports are not allowed to mix default, namespace, and named imports in any combination. - // We could rewrite a default import as a named import (`import { default as name }`), but we currently - // choose not to as a stylistic preference. - if (isTypeOnly && newDefaultImport && newNamedImports) { - coalescedImports.push( - updateImportDeclarationAndClause(importDecl, newDefaultImport, /*namedBindings*/ undefined)); - coalescedImports.push( - updateImportDeclarationAndClause(namedImports[0] ?? importDecl, /*name*/ undefined, newNamedImports)); + if (name) { + group.defaultImports.push(importDeclaration); + } + + if (namedBindings) { + if (isNamespaceImport(namedBindings)) { + group.namespaceImports.push(importDeclaration); } else { - coalescedImports.push( - updateImportDeclarationAndClause(importDecl, newDefaultImport, newNamedImports)); + group.namedImports.push(importDeclaration); } } + } - return coalescedImports; + return { + importWithoutClause, + typeOnlyImports, + regularImports, + }; +} +// Internal for testing +/** + * @param exportGroup a list of ExportDeclarations, all with the same module name. + * + * @internal + */ +export function coalesceExports(exportGroup: readonly ExportDeclaration[]) { + if (exportGroup.length === 0) { + return exportGroup; } - interface ImportGroup { - defaultImports: ImportDeclaration[]; - namespaceImports: ImportDeclaration[]; - namedImports: ImportDeclaration[]; + const { exportWithoutClause, namedExports, typeOnlyExports } = getCategorizedExports(exportGroup); + + const coalescedExports: ExportDeclaration[] = []; + + if (exportWithoutClause) { + coalescedExports.push(exportWithoutClause); } + for (const exportGroup of [namedExports, typeOnlyExports]) { + if (exportGroup.length === 0) { + continue; + } + const newExportSpecifiers: ExportSpecifier[] = []; + newExportSpecifiers.push(...flatMap(exportGroup, i => i.exportClause && isNamedExports(i.exportClause) ? i.exportClause.elements : emptyArray)); + + const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers); + + const exportDecl = exportGroup[0]; + coalescedExports.push( + factory.updateExportDeclaration( + exportDecl, + exportDecl.modifiers, + exportDecl.isTypeOnly, + exportDecl.exportClause && ( + isNamedExports(exportDecl.exportClause) ? + factory.updateNamedExports(exportDecl.exportClause, sortedExportSpecifiers) : + factory.updateNamespaceExport(exportDecl.exportClause, exportDecl.exportClause.name) + ), + exportDecl.moduleSpecifier, + exportDecl.assertClause)); + } + + return coalescedExports; + /* - * Returns entire import declarations because they may already have been rewritten and + * Returns entire export declarations because they may already have been rewritten and * may lack parent pointers. The desired parts can easily be recovered based on the * categorization. - * - * NB: There may be overlap between `defaultImports` and `namespaceImports`/`namedImports`. */ - function getCategorizedImports(importGroup: readonly ImportDeclaration[]) { - let importWithoutClause: ImportDeclaration | undefined; - const typeOnlyImports: ImportGroup = { defaultImports: [], namespaceImports: [], namedImports: [] }; - const regularImports: ImportGroup = { defaultImports: [], namespaceImports: [], namedImports: [] }; - - for (const importDeclaration of importGroup) { - if (importDeclaration.importClause === undefined) { - // Only the first such import is interesting - the others are redundant. + function getCategorizedExports(exportGroup: readonly ExportDeclaration[]) { + let exportWithoutClause: ExportDeclaration | undefined; + const namedExports: ExportDeclaration[] = []; + const typeOnlyExports: ExportDeclaration[] = []; + + for (const exportDeclaration of exportGroup) { + if (exportDeclaration.exportClause === undefined) { + // Only the first such export is interesting - the others are redundant. // Note: Unfortunately, we will lose trivia that was on this node. - importWithoutClause = importWithoutClause || importDeclaration; - continue; + exportWithoutClause = exportWithoutClause || exportDeclaration; } - - const group = importDeclaration.importClause.isTypeOnly ? typeOnlyImports : regularImports; - const { name, namedBindings } = importDeclaration.importClause; - - if (name) { - group.defaultImports.push(importDeclaration); + else if (exportDeclaration.isTypeOnly) { + typeOnlyExports.push(exportDeclaration); } - - if (namedBindings) { - if (isNamespaceImport(namedBindings)) { - group.namespaceImports.push(importDeclaration); - } - else { - group.namedImports.push(importDeclaration); - } + else { + namedExports.push(exportDeclaration); } } return { - importWithoutClause, - typeOnlyImports, - regularImports, + exportWithoutClause, + namedExports, + typeOnlyExports, }; } +} - // Internal for testing - /** - * @param exportGroup a list of ExportDeclarations, all with the same module name. - */ - export function coalesceExports(exportGroup: readonly ExportDeclaration[]) { - if (exportGroup.length === 0) { - return exportGroup; - } - - const { exportWithoutClause, namedExports, typeOnlyExports } = getCategorizedExports(exportGroup); - - const coalescedExports: ExportDeclaration[] = []; - - if (exportWithoutClause) { - coalescedExports.push(exportWithoutClause); - } - - for (const exportGroup of [namedExports, typeOnlyExports]) { - if (exportGroup.length === 0) { - continue; - } - const newExportSpecifiers: ExportSpecifier[] = []; - newExportSpecifiers.push(...flatMap(exportGroup, i => i.exportClause && isNamedExports(i.exportClause) ? i.exportClause.elements : emptyArray)); - - const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers); - - const exportDecl = exportGroup[0]; - coalescedExports.push( - factory.updateExportDeclaration( - exportDecl, - exportDecl.modifiers, - exportDecl.isTypeOnly, - exportDecl.exportClause && ( - isNamedExports(exportDecl.exportClause) ? - factory.updateNamedExports(exportDecl.exportClause, sortedExportSpecifiers) : - factory.updateNamespaceExport(exportDecl.exportClause, exportDecl.exportClause.name) - ), - exportDecl.moduleSpecifier, - exportDecl.assertClause)); - } - - return coalescedExports; - - /* - * Returns entire export declarations because they may already have been rewritten and - * may lack parent pointers. The desired parts can easily be recovered based on the - * categorization. - */ - function getCategorizedExports(exportGroup: readonly ExportDeclaration[]) { - let exportWithoutClause: ExportDeclaration | undefined; - const namedExports: ExportDeclaration[] = []; - const typeOnlyExports: ExportDeclaration[] = []; - - for (const exportDeclaration of exportGroup) { - if (exportDeclaration.exportClause === undefined) { - // Only the first such export is interesting - the others are redundant. - // Note: Unfortunately, we will lose trivia that was on this node. - exportWithoutClause = exportWithoutClause || exportDeclaration; - } - else if (exportDeclaration.isTypeOnly) { - typeOnlyExports.push(exportDeclaration); - } - else { - namedExports.push(exportDeclaration); - } - } - - return { - exportWithoutClause, - namedExports, - typeOnlyExports, - }; - } - } - - function updateImportDeclarationAndClause( - importDeclaration: ImportDeclaration, - name: Identifier | undefined, - namedBindings: NamedImportBindings | undefined) { - - return factory.updateImportDeclaration( - importDeclaration, - importDeclaration.modifiers, - factory.updateImportClause(importDeclaration.importClause!, importDeclaration.importClause!.isTypeOnly, name, namedBindings), // TODO: GH#18217 - importDeclaration.moduleSpecifier, - importDeclaration.assertClause); - } +function updateImportDeclarationAndClause( + importDeclaration: ImportDeclaration, + name: Identifier | undefined, + namedBindings: NamedImportBindings | undefined) { + + return factory.updateImportDeclaration( + importDeclaration, + importDeclaration.modifiers, + factory.updateImportClause(importDeclaration.importClause!, importDeclaration.importClause!.isTypeOnly, name, namedBindings), // TODO: GH#18217 + importDeclaration.moduleSpecifier, + importDeclaration.assertClause); +} - function sortSpecifiers(specifiers: readonly T[]) { - return stableSort(specifiers, compareImportOrExportSpecifiers); - } +function sortSpecifiers(specifiers: readonly T[]) { + return stableSort(specifiers, compareImportOrExportSpecifiers); +} - export function compareImportOrExportSpecifiers(s1: T, s2: T) { - return compareBooleans(s1.isTypeOnly, s2.isTypeOnly) - || compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name) - || compareIdentifiers(s1.name, s2.name); - } +/** @internal */ +export function compareImportOrExportSpecifiers(s1: T, s2: T): Comparison { + return compareBooleans(s1.isTypeOnly, s2.isTypeOnly) + || compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name) + || compareIdentifiers(s1.name, s2.name); +} - /* internal */ // Exported for testing - export function compareModuleSpecifiers(m1: Expression | undefined, m2: Expression | undefined) { - const name1 = m1 === undefined ? undefined : getExternalModuleName(m1); - const name2 = m2 === undefined ? undefined : getExternalModuleName(m2); - return compareBooleans(name1 === undefined, name2 === undefined) || - compareBooleans(isExternalModuleNameRelative(name1!), isExternalModuleNameRelative(name2!)) || - compareStringsCaseInsensitive(name1!, name2!); - } +/** + * Exported for testing + * + * @internal + */ +export function compareModuleSpecifiers(m1: Expression | undefined, m2: Expression | undefined) { + const name1 = m1 === undefined ? undefined : getExternalModuleName(m1); + const name2 = m2 === undefined ? undefined : getExternalModuleName(m2); + return compareBooleans(name1 === undefined, name2 === undefined) || + compareBooleans(isExternalModuleNameRelative(name1!), isExternalModuleNameRelative(name2!)) || + compareStringsCaseInsensitive(name1!, name2!); +} - function compareIdentifiers(s1: Identifier, s2: Identifier) { - return compareStringsCaseInsensitive(s1.text, s2.text); - } +function compareIdentifiers(s1: Identifier, s2: Identifier) { + return compareStringsCaseInsensitive(s1.text, s2.text); +} - function getModuleSpecifierExpression(declaration: AnyImportOrRequireStatement): Expression | undefined { - switch (declaration.kind) { - case SyntaxKind.ImportEqualsDeclaration: - return tryCast(declaration.moduleReference, isExternalModuleReference)?.expression; - case SyntaxKind.ImportDeclaration: - return declaration.moduleSpecifier; - case SyntaxKind.VariableStatement: - return declaration.declarationList.declarations[0].initializer.arguments[0]; - } +function getModuleSpecifierExpression(declaration: AnyImportOrRequireStatement): Expression | undefined { + switch (declaration.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return tryCast(declaration.moduleReference, isExternalModuleReference)?.expression; + case SyntaxKind.ImportDeclaration: + return declaration.moduleSpecifier; + case SyntaxKind.VariableStatement: + return declaration.declarationList.declarations[0].initializer.arguments[0]; } +} - export function importsAreSorted(imports: readonly AnyImportOrRequireStatement[]): imports is SortedReadonlyArray { - return arrayIsSorted(imports, compareImportsOrRequireStatements); - } +/** @internal */ +export function importsAreSorted(imports: readonly AnyImportOrRequireStatement[]): imports is SortedReadonlyArray { + return arrayIsSorted(imports, compareImportsOrRequireStatements); +} - export function importSpecifiersAreSorted(imports: readonly ImportSpecifier[]): imports is SortedReadonlyArray { - return arrayIsSorted(imports, compareImportOrExportSpecifiers); - } +/** @internal */ +export function importSpecifiersAreSorted(imports: readonly ImportSpecifier[]): imports is SortedReadonlyArray { + return arrayIsSorted(imports, compareImportOrExportSpecifiers); +} - export function getImportDeclarationInsertionIndex(sortedImports: SortedReadonlyArray, newImport: AnyImportOrRequireStatement) { - const index = binarySearch(sortedImports, newImport, identity, compareImportsOrRequireStatements); - return index < 0 ? ~index : index; - } +/** @internal */ +export function getImportDeclarationInsertionIndex(sortedImports: SortedReadonlyArray, newImport: AnyImportOrRequireStatement) { + const index = binarySearch(sortedImports, newImport, identity, compareImportsOrRequireStatements); + return index < 0 ? ~index : index; +} - export function getImportSpecifierInsertionIndex(sortedImports: SortedReadonlyArray, newImport: ImportSpecifier) { - const index = binarySearch(sortedImports, newImport, identity, compareImportOrExportSpecifiers); - return index < 0 ? ~index : index; - } +/** @internal */ +export function getImportSpecifierInsertionIndex(sortedImports: SortedReadonlyArray, newImport: ImportSpecifier) { + const index = binarySearch(sortedImports, newImport, identity, compareImportOrExportSpecifiers); + return index < 0 ? ~index : index; +} - export function compareImportsOrRequireStatements(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) { - return compareModuleSpecifiers(getModuleSpecifierExpression(s1), getModuleSpecifierExpression(s2)) || compareImportKind(s1, s2); - } +/** @internal */ +export function compareImportsOrRequireStatements(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) { + return compareModuleSpecifiers(getModuleSpecifierExpression(s1), getModuleSpecifierExpression(s2)) || compareImportKind(s1, s2); +} - function compareImportKind(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) { - return compareValues(getImportKindOrder(s1), getImportKindOrder(s2)); - } +function compareImportKind(s1: AnyImportOrRequireStatement, s2: AnyImportOrRequireStatement) { + return compareValues(getImportKindOrder(s1), getImportKindOrder(s2)); +} - // 1. Side-effect imports - // 2. Type-only imports - // 3. Namespace imports - // 4. Default imports - // 5. Named imports - // 6. ImportEqualsDeclarations - // 7. Require variable statements - function getImportKindOrder(s1: AnyImportOrRequireStatement) { - switch (s1.kind) { - case SyntaxKind.ImportDeclaration: - if (!s1.importClause) return 0; - if (s1.importClause.isTypeOnly) return 1; - if (s1.importClause.namedBindings?.kind === SyntaxKind.NamespaceImport) return 2; - if (s1.importClause.name) return 3; - return 4; - case SyntaxKind.ImportEqualsDeclaration: - return 5; - case SyntaxKind.VariableStatement: - return 6; - } +// 1. Side-effect imports +// 2. Type-only imports +// 3. Namespace imports +// 4. Default imports +// 5. Named imports +// 6. ImportEqualsDeclarations +// 7. Require variable statements +function getImportKindOrder(s1: AnyImportOrRequireStatement) { + switch (s1.kind) { + case SyntaxKind.ImportDeclaration: + if (!s1.importClause) return 0; + if (s1.importClause.isTypeOnly) return 1; + if (s1.importClause.namedBindings?.kind === SyntaxKind.NamespaceImport) return 2; + if (s1.importClause.name) return 3; + return 4; + case SyntaxKind.ImportEqualsDeclaration: + return 5; + case SyntaxKind.VariableStatement: + return 6; } +} - function getNewImportSpecifiers(namedImports: ImportDeclaration[]) { - return flatMap(namedImports, namedImport => - map(tryGetNamedBindingElements(namedImport), importSpecifier => - importSpecifier.name && importSpecifier.propertyName && importSpecifier.name.escapedText === importSpecifier.propertyName.escapedText - ? factory.updateImportSpecifier(importSpecifier, importSpecifier.isTypeOnly, /*propertyName*/ undefined, importSpecifier.name) - : importSpecifier - ) - ); - } +function getNewImportSpecifiers(namedImports: ImportDeclaration[]) { + return flatMap(namedImports, namedImport => + map(tryGetNamedBindingElements(namedImport), importSpecifier => + importSpecifier.name && importSpecifier.propertyName && importSpecifier.name.escapedText === importSpecifier.propertyName.escapedText + ? factory.updateImportSpecifier(importSpecifier, importSpecifier.isTypeOnly, /*propertyName*/ undefined, importSpecifier.name) + : importSpecifier + ) + ); +} - function tryGetNamedBindingElements(namedImport: ImportDeclaration) { - return namedImport.importClause?.namedBindings && isNamedImports(namedImport.importClause.namedBindings) - ? namedImport.importClause.namedBindings.elements - : undefined; - } +function tryGetNamedBindingElements(namedImport: ImportDeclaration) { + return namedImport.importClause?.namedBindings && isNamedImports(namedImport.importClause.namedBindings) + ? namedImport.importClause.namedBindings.elements + : undefined; } diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index ee06b0c3a6833..5a80301e00055 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -1,344 +1,354 @@ -/* @internal */ -namespace ts.OutliningElementsCollector { - export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] { - const res: OutliningSpan[] = []; - addNodeOutliningSpans(sourceFile, cancellationToken, res); - addRegionOutliningSpans(sourceFile, res); - return res.sort((span1, span2) => span1.textSpan.start - span2.textSpan.start); - } +import { + ArrowFunction, Block, CallExpression, CancellationToken, CaseClause, createTextSpanFromBounds, + createTextSpanFromNode, createTextSpanFromRange, Debug, DefaultClause, findChildOfKind, getLeadingCommentRanges, + isAnyImportSyntax, isArrayLiteralExpression, isBinaryExpression, isBindingElement, isBlock, isCallExpression, + isCallOrNewExpression, isClassLike, isDeclaration, isFunctionLike, isIfStatement, isInComment, + isInterfaceDeclaration, isJsxText, isModuleBlock, isNodeArrayMultiLine, isParenthesizedExpression, + isPropertyAccessExpression, isReturnStatement, isTupleTypeNode, isVariableStatement, JsxAttributes, JsxElement, + JsxFragment, JsxOpeningLikeElement, Node, NodeArray, NoSubstitutionTemplateLiteral, OutliningSpan, + OutliningSpanKind, ParenthesizedExpression, positionsAreOnSameLine, Push, SignatureDeclaration, SourceFile, + startsWith, SyntaxKind, TemplateExpression, TextSpan, trimString, trimStringStart, TryStatement, +} from "./_namespaces/ts"; + +/** @internal */ +export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] { + const res: OutliningSpan[] = []; + addNodeOutliningSpans(sourceFile, cancellationToken, res); + addRegionOutliningSpans(sourceFile, res); + return res.sort((span1, span2) => span1.textSpan.start - span2.textSpan.start); +} - function addNodeOutliningSpans(sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { - let depthRemaining = 40; - let current = 0; - // Includes the EOF Token so that comments which aren't attached to statements are included - const statements = [...sourceFile.statements, sourceFile.endOfFileToken]; - const n = statements.length; - while (current < n) { - while (current < n && !isAnyImportSyntax(statements[current])) { - visitNonImportNode(statements[current]); - current++; - } - if (current === n) break; - const firstImport = current; - while (current < n && isAnyImportSyntax(statements[current])) { - addOutliningForLeadingCommentsForNode(statements[current], sourceFile, cancellationToken, out); - current++; - } - const lastImport = current - 1; - if (lastImport !== firstImport) { - out.push(createOutliningSpanFromBounds(findChildOfKind(statements[firstImport], SyntaxKind.ImportKeyword, sourceFile)!.getStart(sourceFile), statements[lastImport].getEnd(), OutliningSpanKind.Imports)); - } +function addNodeOutliningSpans(sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { + let depthRemaining = 40; + let current = 0; + // Includes the EOF Token so that comments which aren't attached to statements are included + const statements = [...sourceFile.statements, sourceFile.endOfFileToken]; + const n = statements.length; + while (current < n) { + while (current < n && !isAnyImportSyntax(statements[current])) { + visitNonImportNode(statements[current]); + current++; } + if (current === n) break; + const firstImport = current; + while (current < n && isAnyImportSyntax(statements[current])) { + addOutliningForLeadingCommentsForNode(statements[current], sourceFile, cancellationToken, out); + current++; + } + const lastImport = current - 1; + if (lastImport !== firstImport) { + out.push(createOutliningSpanFromBounds(findChildOfKind(statements[firstImport], SyntaxKind.ImportKeyword, sourceFile)!.getStart(sourceFile), statements[lastImport].getEnd(), OutliningSpanKind.Imports)); + } + } - function visitNonImportNode(n: Node) { - if (depthRemaining === 0) return; - cancellationToken.throwIfCancellationRequested(); + function visitNonImportNode(n: Node) { + if (depthRemaining === 0) return; + cancellationToken.throwIfCancellationRequested(); - if (isDeclaration(n) || isVariableStatement(n) || isReturnStatement(n) || isCallOrNewExpression(n) || n.kind === SyntaxKind.EndOfFileToken) { - addOutliningForLeadingCommentsForNode(n, sourceFile, cancellationToken, out); - } + if (isDeclaration(n) || isVariableStatement(n) || isReturnStatement(n) || isCallOrNewExpression(n) || n.kind === SyntaxKind.EndOfFileToken) { + addOutliningForLeadingCommentsForNode(n, sourceFile, cancellationToken, out); + } - if (isFunctionLike(n) && isBinaryExpression(n.parent) && isPropertyAccessExpression(n.parent.left)) { - addOutliningForLeadingCommentsForNode(n.parent.left, sourceFile, cancellationToken, out); - } + if (isFunctionLike(n) && isBinaryExpression(n.parent) && isPropertyAccessExpression(n.parent.left)) { + addOutliningForLeadingCommentsForNode(n.parent.left, sourceFile, cancellationToken, out); + } - if (isBlock(n) || isModuleBlock(n)) { - addOutliningForLeadingCommentsForPos(n.statements.end, sourceFile, cancellationToken, out); - } + if (isBlock(n) || isModuleBlock(n)) { + addOutliningForLeadingCommentsForPos(n.statements.end, sourceFile, cancellationToken, out); + } - if (isClassLike(n) || isInterfaceDeclaration(n)) { - addOutliningForLeadingCommentsForPos(n.members.end, sourceFile, cancellationToken, out); - } + if (isClassLike(n) || isInterfaceDeclaration(n)) { + addOutliningForLeadingCommentsForPos(n.members.end, sourceFile, cancellationToken, out); + } - const span = getOutliningSpanForNode(n, sourceFile); - if (span) out.push(span); + const span = getOutliningSpanForNode(n, sourceFile); + if (span) out.push(span); + depthRemaining--; + if (isCallExpression(n)) { + depthRemaining++; + visitNonImportNode(n.expression); depthRemaining--; - if (isCallExpression(n)) { - depthRemaining++; - visitNonImportNode(n.expression); - depthRemaining--; - n.arguments.forEach(visitNonImportNode); - n.typeArguments?.forEach(visitNonImportNode); - } - else if (isIfStatement(n) && n.elseStatement && isIfStatement(n.elseStatement)) { - // Consider an 'else if' to be on the same depth as the 'if'. - visitNonImportNode(n.expression); - visitNonImportNode(n.thenStatement); - depthRemaining++; - visitNonImportNode(n.elseStatement); - depthRemaining--; - } - else { - n.forEachChild(visitNonImportNode); - } + n.arguments.forEach(visitNonImportNode); + n.typeArguments?.forEach(visitNonImportNode); + } + else if (isIfStatement(n) && n.elseStatement && isIfStatement(n.elseStatement)) { + // Consider an 'else if' to be on the same depth as the 'if'. + visitNonImportNode(n.expression); + visitNonImportNode(n.thenStatement); depthRemaining++; + visitNonImportNode(n.elseStatement); + depthRemaining--; + } + else { + n.forEachChild(visitNonImportNode); } + depthRemaining++; } +} - function addRegionOutliningSpans(sourceFile: SourceFile, out: Push): void { - const regions: OutliningSpan[] = []; - const lineStarts = sourceFile.getLineStarts(); - for (const currentLineStart of lineStarts) { - const lineEnd = sourceFile.getLineEndOfPosition(currentLineStart); - const lineText = sourceFile.text.substring(currentLineStart, lineEnd); - const result = isRegionDelimiter(lineText); - if (!result || isInComment(sourceFile, currentLineStart)) { - continue; - } +function addRegionOutliningSpans(sourceFile: SourceFile, out: Push): void { + const regions: OutliningSpan[] = []; + const lineStarts = sourceFile.getLineStarts(); + for (const currentLineStart of lineStarts) { + const lineEnd = sourceFile.getLineEndOfPosition(currentLineStart); + const lineText = sourceFile.text.substring(currentLineStart, lineEnd); + const result = isRegionDelimiter(lineText); + if (!result || isInComment(sourceFile, currentLineStart)) { + continue; + } - if (!result[1]) { - const span = createTextSpanFromBounds(sourceFile.text.indexOf("//", currentLineStart), lineEnd); - regions.push(createOutliningSpan(span, OutliningSpanKind.Region, span, /*autoCollapse*/ false, result[2] || "#region")); - } - else { - const region = regions.pop(); - if (region) { - region.textSpan.length = lineEnd - region.textSpan.start; - region.hintSpan.length = lineEnd - region.textSpan.start; - out.push(region); - } + if (!result[1]) { + const span = createTextSpanFromBounds(sourceFile.text.indexOf("//", currentLineStart), lineEnd); + regions.push(createOutliningSpan(span, OutliningSpanKind.Region, span, /*autoCollapse*/ false, result[2] || "#region")); + } + else { + const region = regions.pop(); + if (region) { + region.textSpan.length = lineEnd - region.textSpan.start; + region.hintSpan.length = lineEnd - region.textSpan.start; + out.push(region); } } } +} - const regionDelimiterRegExp = /^#(end)?region(?:\s+(.*))?(?:\r)?$/; - function isRegionDelimiter(lineText: string) { - // We trim the leading whitespace and // without the regex since the - // multiple potential whitespace matches can make for some gnarly backtracking behavior - lineText = trimStringStart(lineText); - if (!startsWith(lineText, "\/\/")) { - return null; // eslint-disable-line no-null/no-null - } - lineText = trimString(lineText.slice(2)); - return regionDelimiterRegExp.exec(lineText); +const regionDelimiterRegExp = /^#(end)?region(?:\s+(.*))?(?:\r)?$/; +function isRegionDelimiter(lineText: string) { + // We trim the leading whitespace and // without the regex since the + // multiple potential whitespace matches can make for some gnarly backtracking behavior + lineText = trimStringStart(lineText); + if (!startsWith(lineText, "\/\/")) { + return null; // eslint-disable-line no-null/no-null } + lineText = trimString(lineText.slice(2)); + return regionDelimiterRegExp.exec(lineText); +} - function addOutliningForLeadingCommentsForPos(pos: number, sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { - const comments = getLeadingCommentRanges(sourceFile.text, pos); - if (!comments) return; - - let firstSingleLineCommentStart = -1; - let lastSingleLineCommentEnd = -1; - let singleLineCommentCount = 0; - const sourceText = sourceFile.getFullText(); - for (const { kind, pos, end } of comments) { - cancellationToken.throwIfCancellationRequested(); - switch (kind) { - case SyntaxKind.SingleLineCommentTrivia: - // never fold region delimiters into single-line comment regions - const commentText = sourceText.slice(pos, end); - if (isRegionDelimiter(commentText)) { - combineAndAddMultipleSingleLineComments(); - singleLineCommentCount = 0; - break; - } - - // For single line comments, combine consecutive ones (2 or more) into - // a single span from the start of the first till the end of the last - if (singleLineCommentCount === 0) { - firstSingleLineCommentStart = pos; - } - lastSingleLineCommentEnd = end; - singleLineCommentCount++; - break; - case SyntaxKind.MultiLineCommentTrivia: +function addOutliningForLeadingCommentsForPos(pos: number, sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { + const comments = getLeadingCommentRanges(sourceFile.text, pos); + if (!comments) return; + + let firstSingleLineCommentStart = -1; + let lastSingleLineCommentEnd = -1; + let singleLineCommentCount = 0; + const sourceText = sourceFile.getFullText(); + for (const { kind, pos, end } of comments) { + cancellationToken.throwIfCancellationRequested(); + switch (kind) { + case SyntaxKind.SingleLineCommentTrivia: + // never fold region delimiters into single-line comment regions + const commentText = sourceText.slice(pos, end); + if (isRegionDelimiter(commentText)) { combineAndAddMultipleSingleLineComments(); - out.push(createOutliningSpanFromBounds(pos, end, OutliningSpanKind.Comment)); singleLineCommentCount = 0; break; - default: - Debug.assertNever(kind); - } - } - combineAndAddMultipleSingleLineComments(); + } - function combineAndAddMultipleSingleLineComments(): void { - // Only outline spans of two or more consecutive single line comments - if (singleLineCommentCount > 1) { - out.push(createOutliningSpanFromBounds(firstSingleLineCommentStart, lastSingleLineCommentEnd, OutliningSpanKind.Comment)); - } + // For single line comments, combine consecutive ones (2 or more) into + // a single span from the start of the first till the end of the last + if (singleLineCommentCount === 0) { + firstSingleLineCommentStart = pos; + } + lastSingleLineCommentEnd = end; + singleLineCommentCount++; + break; + case SyntaxKind.MultiLineCommentTrivia: + combineAndAddMultipleSingleLineComments(); + out.push(createOutliningSpanFromBounds(pos, end, OutliningSpanKind.Comment)); + singleLineCommentCount = 0; + break; + default: + Debug.assertNever(kind); } } + combineAndAddMultipleSingleLineComments(); - function addOutliningForLeadingCommentsForNode(n: Node, sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { - if (isJsxText(n)) return; - addOutliningForLeadingCommentsForPos(n.pos, sourceFile, cancellationToken, out); + function combineAndAddMultipleSingleLineComments(): void { + // Only outline spans of two or more consecutive single line comments + if (singleLineCommentCount > 1) { + out.push(createOutliningSpanFromBounds(firstSingleLineCommentStart, lastSingleLineCommentEnd, OutliningSpanKind.Comment)); + } } +} - function createOutliningSpanFromBounds(pos: number, end: number, kind: OutliningSpanKind): OutliningSpan { - return createOutliningSpan(createTextSpanFromBounds(pos, end), kind); - } +function addOutliningForLeadingCommentsForNode(n: Node, sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { + if (isJsxText(n)) return; + addOutliningForLeadingCommentsForPos(n.pos, sourceFile, cancellationToken, out); +} - function getOutliningSpanForNode(n: Node, sourceFile: SourceFile): OutliningSpan | undefined { - switch (n.kind) { - case SyntaxKind.Block: - if (isFunctionLike(n.parent)) { - return functionSpan(n.parent, n as Block, sourceFile); - } - // Check if the block is standalone, or 'attached' to some parent statement. - // If the latter, we want to collapse the block, but consider its hint span - // to be the entire span of the parent. - switch (n.parent.kind) { - case SyntaxKind.DoStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.IfStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.CatchClause: - return spanForNode(n.parent); - case SyntaxKind.TryStatement: - // Could be the try-block, or the finally-block. - const tryStatement = n.parent as TryStatement; - if (tryStatement.tryBlock === n) { - return spanForNode(n.parent); - } - else if (tryStatement.finallyBlock === n) { - const node = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile); - if (node) return spanForNode(node); - } - // falls through - default: - // Block was a standalone block. In this case we want to only collapse - // the span of the block, independent of any parent span. - return createOutliningSpan(createTextSpanFromNode(n, sourceFile), OutliningSpanKind.Code); - } - case SyntaxKind.ModuleBlock: - return spanForNode(n.parent); - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.CaseBlock: - case SyntaxKind.TypeLiteral: - case SyntaxKind.ObjectBindingPattern: - return spanForNode(n); - case SyntaxKind.TupleType: - return spanForNode(n, /*autoCollapse*/ false, /*useFullStart*/ !isTupleTypeNode(n.parent), SyntaxKind.OpenBracketToken); - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - return spanForNodeArray((n as CaseClause | DefaultClause).statements); - case SyntaxKind.ObjectLiteralExpression: - return spanForObjectOrArrayLiteral(n); - case SyntaxKind.ArrayLiteralExpression: - return spanForObjectOrArrayLiteral(n, SyntaxKind.OpenBracketToken); - case SyntaxKind.JsxElement: - return spanForJSXElement(n as JsxElement); - case SyntaxKind.JsxFragment: - return spanForJSXFragment(n as JsxFragment); - case SyntaxKind.JsxSelfClosingElement: - case SyntaxKind.JsxOpeningElement: - return spanForJSXAttributes((n as JsxOpeningLikeElement).attributes); - case SyntaxKind.TemplateExpression: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return spanForTemplateLiteral(n as TemplateExpression | NoSubstitutionTemplateLiteral); - case SyntaxKind.ArrayBindingPattern: - return spanForNode(n, /*autoCollapse*/ false, /*useFullStart*/ !isBindingElement(n.parent), SyntaxKind.OpenBracketToken); - case SyntaxKind.ArrowFunction: - return spanForArrowFunction(n as ArrowFunction); - case SyntaxKind.CallExpression: - return spanForCallExpression(n as CallExpression); - case SyntaxKind.ParenthesizedExpression: - return spanForParenthesizedExpression(n as ParenthesizedExpression); - } +function createOutliningSpanFromBounds(pos: number, end: number, kind: OutliningSpanKind): OutliningSpan { + return createOutliningSpan(createTextSpanFromBounds(pos, end), kind); +} - function spanForCallExpression(node: CallExpression): OutliningSpan | undefined { - if (!node.arguments.length) { - return undefined; +function getOutliningSpanForNode(n: Node, sourceFile: SourceFile): OutliningSpan | undefined { + switch (n.kind) { + case SyntaxKind.Block: + if (isFunctionLike(n.parent)) { + return functionSpan(n.parent, n as Block, sourceFile); } - const openToken = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); - const closeToken = findChildOfKind(node, SyntaxKind.CloseParenToken, sourceFile); - if (!openToken || !closeToken || positionsAreOnSameLine(openToken.pos, closeToken.pos, sourceFile)) { - return undefined; + // Check if the block is standalone, or 'attached' to some parent statement. + // If the latter, we want to collapse the block, but consider its hint span + // to be the entire span of the parent. + switch (n.parent.kind) { + case SyntaxKind.DoStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.CatchClause: + return spanForNode(n.parent); + case SyntaxKind.TryStatement: + // Could be the try-block, or the finally-block. + const tryStatement = n.parent as TryStatement; + if (tryStatement.tryBlock === n) { + return spanForNode(n.parent); + } + else if (tryStatement.finallyBlock === n) { + const node = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile); + if (node) return spanForNode(node); + } + // falls through + default: + // Block was a standalone block. In this case we want to only collapse + // the span of the block, independent of any parent span. + return createOutliningSpan(createTextSpanFromNode(n, sourceFile), OutliningSpanKind.Code); } + case SyntaxKind.ModuleBlock: + return spanForNode(n.parent); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.CaseBlock: + case SyntaxKind.TypeLiteral: + case SyntaxKind.ObjectBindingPattern: + return spanForNode(n); + case SyntaxKind.TupleType: + return spanForNode(n, /*autoCollapse*/ false, /*useFullStart*/ !isTupleTypeNode(n.parent), SyntaxKind.OpenBracketToken); + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + return spanForNodeArray((n as CaseClause | DefaultClause).statements); + case SyntaxKind.ObjectLiteralExpression: + return spanForObjectOrArrayLiteral(n); + case SyntaxKind.ArrayLiteralExpression: + return spanForObjectOrArrayLiteral(n, SyntaxKind.OpenBracketToken); + case SyntaxKind.JsxElement: + return spanForJSXElement(n as JsxElement); + case SyntaxKind.JsxFragment: + return spanForJSXFragment(n as JsxFragment); + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningElement: + return spanForJSXAttributes((n as JsxOpeningLikeElement).attributes); + case SyntaxKind.TemplateExpression: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return spanForTemplateLiteral(n as TemplateExpression | NoSubstitutionTemplateLiteral); + case SyntaxKind.ArrayBindingPattern: + return spanForNode(n, /*autoCollapse*/ false, /*useFullStart*/ !isBindingElement(n.parent), SyntaxKind.OpenBracketToken); + case SyntaxKind.ArrowFunction: + return spanForArrowFunction(n as ArrowFunction); + case SyntaxKind.CallExpression: + return spanForCallExpression(n as CallExpression); + case SyntaxKind.ParenthesizedExpression: + return spanForParenthesizedExpression(n as ParenthesizedExpression); + } - return spanBetweenTokens(openToken, closeToken, node, sourceFile, /*autoCollapse*/ false, /*useFullStart*/ true); + function spanForCallExpression(node: CallExpression): OutliningSpan | undefined { + if (!node.arguments.length) { + return undefined; } - - function spanForArrowFunction(node: ArrowFunction): OutliningSpan | undefined { - if (isBlock(node.body) || isParenthesizedExpression(node.body) || positionsAreOnSameLine(node.body.getFullStart(), node.body.getEnd(), sourceFile)) { - return undefined; - } - const textSpan = createTextSpanFromBounds(node.body.getFullStart(), node.body.getEnd()); - return createOutliningSpan(textSpan, OutliningSpanKind.Code, createTextSpanFromNode(node)); + const openToken = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); + const closeToken = findChildOfKind(node, SyntaxKind.CloseParenToken, sourceFile); + if (!openToken || !closeToken || positionsAreOnSameLine(openToken.pos, closeToken.pos, sourceFile)) { + return undefined; } - function spanForJSXElement(node: JsxElement): OutliningSpan | undefined { - const textSpan = createTextSpanFromBounds(node.openingElement.getStart(sourceFile), node.closingElement.getEnd()); - const tagName = node.openingElement.tagName.getText(sourceFile); - const bannerText = "<" + tagName + ">..."; - return createOutliningSpan(textSpan, OutliningSpanKind.Code, textSpan, /*autoCollapse*/ false, bannerText); - } + return spanBetweenTokens(openToken, closeToken, node, sourceFile, /*autoCollapse*/ false, /*useFullStart*/ true); + } - function spanForJSXFragment(node: JsxFragment): OutliningSpan | undefined { - const textSpan = createTextSpanFromBounds(node.openingFragment.getStart(sourceFile), node.closingFragment.getEnd()); - const bannerText = "<>..."; - return createOutliningSpan(textSpan, OutliningSpanKind.Code, textSpan, /*autoCollapse*/ false, bannerText); + function spanForArrowFunction(node: ArrowFunction): OutliningSpan | undefined { + if (isBlock(node.body) || isParenthesizedExpression(node.body) || positionsAreOnSameLine(node.body.getFullStart(), node.body.getEnd(), sourceFile)) { + return undefined; } + const textSpan = createTextSpanFromBounds(node.body.getFullStart(), node.body.getEnd()); + return createOutliningSpan(textSpan, OutliningSpanKind.Code, createTextSpanFromNode(node)); + } - function spanForJSXAttributes(node: JsxAttributes): OutliningSpan | undefined { - if (node.properties.length === 0) { - return undefined; - } - - return createOutliningSpanFromBounds(node.getStart(sourceFile), node.getEnd(), OutliningSpanKind.Code); - } + function spanForJSXElement(node: JsxElement): OutliningSpan | undefined { + const textSpan = createTextSpanFromBounds(node.openingElement.getStart(sourceFile), node.closingElement.getEnd()); + const tagName = node.openingElement.tagName.getText(sourceFile); + const bannerText = "<" + tagName + ">..."; + return createOutliningSpan(textSpan, OutliningSpanKind.Code, textSpan, /*autoCollapse*/ false, bannerText); + } - function spanForTemplateLiteral(node: TemplateExpression | NoSubstitutionTemplateLiteral) { - if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && node.text.length === 0) { - return undefined; - } - return createOutliningSpanFromBounds(node.getStart(sourceFile), node.getEnd(), OutliningSpanKind.Code); - } + function spanForJSXFragment(node: JsxFragment): OutliningSpan | undefined { + const textSpan = createTextSpanFromBounds(node.openingFragment.getStart(sourceFile), node.closingFragment.getEnd()); + const bannerText = "<>..."; + return createOutliningSpan(textSpan, OutliningSpanKind.Code, textSpan, /*autoCollapse*/ false, bannerText); + } - function spanForObjectOrArrayLiteral(node: Node, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined { - // If the block has no leading keywords and is inside an array literal or call expression, - // we only want to collapse the span of the block. - // Otherwise, the collapsed section will include the end of the previous line. - return spanForNode(node, /*autoCollapse*/ false, /*useFullStart*/ !isArrayLiteralExpression(node.parent) && !isCallExpression(node.parent), open); + function spanForJSXAttributes(node: JsxAttributes): OutliningSpan | undefined { + if (node.properties.length === 0) { + return undefined; } - function spanForNode(hintSpanNode: Node, autoCollapse = false, useFullStart = true, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken, close: SyntaxKind = open === SyntaxKind.OpenBraceToken ? SyntaxKind.CloseBraceToken : SyntaxKind.CloseBracketToken): OutliningSpan | undefined { - const openToken = findChildOfKind(n, open, sourceFile); - const closeToken = findChildOfKind(n, close, sourceFile); - return openToken && closeToken && spanBetweenTokens(openToken, closeToken, hintSpanNode, sourceFile, autoCollapse, useFullStart); - } + return createOutliningSpanFromBounds(node.getStart(sourceFile), node.getEnd(), OutliningSpanKind.Code); + } - function spanForNodeArray(nodeArray: NodeArray): OutliningSpan | undefined { - return nodeArray.length ? createOutliningSpan(createTextSpanFromRange(nodeArray), OutliningSpanKind.Code) : undefined; + function spanForTemplateLiteral(node: TemplateExpression | NoSubstitutionTemplateLiteral) { + if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && node.text.length === 0) { + return undefined; } + return createOutliningSpanFromBounds(node.getStart(sourceFile), node.getEnd(), OutliningSpanKind.Code); + } - function spanForParenthesizedExpression(node: ParenthesizedExpression): OutliningSpan | undefined { - if (positionsAreOnSameLine(node.getStart(), node.getEnd(), sourceFile)) return undefined; - const textSpan = createTextSpanFromBounds(node.getStart(), node.getEnd()); - return createOutliningSpan(textSpan, OutliningSpanKind.Code, createTextSpanFromNode(node)); - } + function spanForObjectOrArrayLiteral(node: Node, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined { + // If the block has no leading keywords and is inside an array literal or call expression, + // we only want to collapse the span of the block. + // Otherwise, the collapsed section will include the end of the previous line. + return spanForNode(node, /*autoCollapse*/ false, /*useFullStart*/ !isArrayLiteralExpression(node.parent) && !isCallExpression(node.parent), open); } - function functionSpan(node: SignatureDeclaration, body: Block, sourceFile: SourceFile): OutliningSpan | undefined { - const openToken = tryGetFunctionOpenToken(node, body, sourceFile); - const closeToken = findChildOfKind(body, SyntaxKind.CloseBraceToken, sourceFile); - return openToken && closeToken && spanBetweenTokens(openToken, closeToken, node, sourceFile, /*autoCollapse*/ node.kind !== SyntaxKind.ArrowFunction); + function spanForNode(hintSpanNode: Node, autoCollapse = false, useFullStart = true, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken, close: SyntaxKind = open === SyntaxKind.OpenBraceToken ? SyntaxKind.CloseBraceToken : SyntaxKind.CloseBracketToken): OutliningSpan | undefined { + const openToken = findChildOfKind(n, open, sourceFile); + const closeToken = findChildOfKind(n, close, sourceFile); + return openToken && closeToken && spanBetweenTokens(openToken, closeToken, hintSpanNode, sourceFile, autoCollapse, useFullStart); } - function spanBetweenTokens(openToken: Node, closeToken: Node, hintSpanNode: Node, sourceFile: SourceFile, autoCollapse = false, useFullStart = true): OutliningSpan { - const textSpan = createTextSpanFromBounds(useFullStart ? openToken.getFullStart() : openToken.getStart(sourceFile), closeToken.getEnd()); - return createOutliningSpan(textSpan, OutliningSpanKind.Code, createTextSpanFromNode(hintSpanNode, sourceFile), autoCollapse); + function spanForNodeArray(nodeArray: NodeArray): OutliningSpan | undefined { + return nodeArray.length ? createOutliningSpan(createTextSpanFromRange(nodeArray), OutliningSpanKind.Code) : undefined; } - function createOutliningSpan(textSpan: TextSpan, kind: OutliningSpanKind, hintSpan: TextSpan = textSpan, autoCollapse = false, bannerText = "..."): OutliningSpan { - return { textSpan, kind, hintSpan, bannerText, autoCollapse }; + function spanForParenthesizedExpression(node: ParenthesizedExpression): OutliningSpan | undefined { + if (positionsAreOnSameLine(node.getStart(), node.getEnd(), sourceFile)) return undefined; + const textSpan = createTextSpanFromBounds(node.getStart(), node.getEnd()); + return createOutliningSpan(textSpan, OutliningSpanKind.Code, createTextSpanFromNode(node)); } +} - function tryGetFunctionOpenToken(node: SignatureDeclaration, body: Block, sourceFile: SourceFile): Node | undefined { - if (isNodeArrayMultiLine(node.parameters, sourceFile)) { - const openParenToken = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); - if (openParenToken) { - return openParenToken; - } +function functionSpan(node: SignatureDeclaration, body: Block, sourceFile: SourceFile): OutliningSpan | undefined { + const openToken = tryGetFunctionOpenToken(node, body, sourceFile); + const closeToken = findChildOfKind(body, SyntaxKind.CloseBraceToken, sourceFile); + return openToken && closeToken && spanBetweenTokens(openToken, closeToken, node, sourceFile, /*autoCollapse*/ node.kind !== SyntaxKind.ArrowFunction); +} + +function spanBetweenTokens(openToken: Node, closeToken: Node, hintSpanNode: Node, sourceFile: SourceFile, autoCollapse = false, useFullStart = true): OutliningSpan { + const textSpan = createTextSpanFromBounds(useFullStart ? openToken.getFullStart() : openToken.getStart(sourceFile), closeToken.getEnd()); + return createOutliningSpan(textSpan, OutliningSpanKind.Code, createTextSpanFromNode(hintSpanNode, sourceFile), autoCollapse); +} + +function createOutliningSpan(textSpan: TextSpan, kind: OutliningSpanKind, hintSpan: TextSpan = textSpan, autoCollapse = false, bannerText = "..."): OutliningSpan { + return { textSpan, kind, hintSpan, bannerText, autoCollapse }; +} + +function tryGetFunctionOpenToken(node: SignatureDeclaration, body: Block, sourceFile: SourceFile): Node | undefined { + if (isNodeArrayMultiLine(node.parameters, sourceFile)) { + const openParenToken = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); + if (openParenToken) { + return openParenToken; } - return findChildOfKind(body, SyntaxKind.OpenBraceToken, sourceFile); } + return findChildOfKind(body, SyntaxKind.OpenBraceToken, sourceFile); } diff --git a/src/services/patternMatcher.ts b/src/services/patternMatcher.ts index 66e4a6217933a..abb19c785f9ba 100644 --- a/src/services/patternMatcher.ts +++ b/src/services/patternMatcher.ts @@ -1,593 +1,601 @@ -/* @internal */ -namespace ts { - // Note(cyrusn): this enum is ordered from strongest match type to weakest match type. - export enum PatternMatchKind { - exact, - prefix, - substring, - camelCase - } +import { + CharacterCodes, compareBooleans, compareValues, Comparison, createTextSpan, ESMap, isUnicodeIdentifierStart, last, + Map, min, ScriptTarget, startsWith, TextSpan, +} from "./_namespaces/ts"; + +// Note(cyrusn): this enum is ordered from strongest match type to weakest match type. +/** @internal */ +export enum PatternMatchKind { + exact, + prefix, + substring, + camelCase +} - // Information about a match made by the pattern matcher between a candidate and the - // search pattern. - export interface PatternMatch { - // What kind of match this was. Exact matches are better than prefix matches which are - // better than substring matches which are better than CamelCase matches. - kind: PatternMatchKind; - - // If this was a match where all constituent parts of the candidate and search pattern - // matched case sensitively or case insensitively. Case sensitive matches of the kind - // are better matches than insensitive matches. - isCaseSensitive: boolean; - } +// Information about a match made by the pattern matcher between a candidate and the +// search pattern. +/** @internal */ +export interface PatternMatch { + // What kind of match this was. Exact matches are better than prefix matches which are + // better than substring matches which are better than CamelCase matches. + kind: PatternMatchKind; + + // If this was a match where all constituent parts of the candidate and search pattern + // matched case sensitively or case insensitively. Case sensitive matches of the kind + // are better matches than insensitive matches. + isCaseSensitive: boolean; +} - // The pattern matcher maintains an internal cache of information as it is used. Therefore, - // you should not keep it around forever and should get and release the matcher appropriately - // once you no longer need it. - export interface PatternMatcher { - // Used to match a candidate against the last segment of a possibly dotted pattern. This - // is useful as a quick check to prevent having to compute a container before calling - // "getMatches". - // - // For example, if the search pattern is "ts.c.SK" and the candidate is "SyntaxKind", then - // this will return a successful match, having only tested "SK" against "SyntaxKind". At - // that point a call can be made to 'getMatches("SyntaxKind", "ts.compiler")', with the - // work to create 'ts.compiler' only being done once the first match succeeded. - getMatchForLastSegmentOfPattern(candidate: string): PatternMatch | undefined; - - // Fully checks a candidate, with an dotted container, against the search pattern. - // The candidate must match the last part of the search pattern, and the dotted container - // must match the preceding segments of the pattern. - getFullMatch(candidateContainers: readonly string[], candidate: string): PatternMatch | undefined; - - // Whether or not the pattern contained dots or not. Clients can use this to determine - // If they should call getMatches, or if getMatchesForLastSegmentOfPattern is sufficient. - patternContainsDots: boolean; - } +// The pattern matcher maintains an internal cache of information as it is used. Therefore, +// you should not keep it around forever and should get and release the matcher appropriately +// once you no longer need it. +/** @internal */ +export interface PatternMatcher { + // Used to match a candidate against the last segment of a possibly dotted pattern. This + // is useful as a quick check to prevent having to compute a container before calling + // "getMatches". + // + // For example, if the search pattern is "ts.c.SK" and the candidate is "SyntaxKind", then + // this will return a successful match, having only tested "SK" against "SyntaxKind". At + // that point a call can be made to 'getMatches("SyntaxKind", "ts.compiler")', with the + // work to create 'ts.compiler' only being done once the first match succeeded. + getMatchForLastSegmentOfPattern(candidate: string): PatternMatch | undefined; + + // Fully checks a candidate, with an dotted container, against the search pattern. + // The candidate must match the last part of the search pattern, and the dotted container + // must match the preceding segments of the pattern. + getFullMatch(candidateContainers: readonly string[], candidate: string): PatternMatch | undefined; + + // Whether or not the pattern contained dots or not. Clients can use this to determine + // If they should call getMatches, or if getMatchesForLastSegmentOfPattern is sufficient. + patternContainsDots: boolean; +} - // First we break up the pattern given by dots. Each portion of the pattern between the - // dots is a 'Segment'. The 'Segment' contains information about the entire section of - // text between the dots, as well as information about any individual 'Words' that we - // can break the segment into. A 'Word' is simply a contiguous sequence of characters - // that can appear in a typescript identifier. So "GetKeyword" would be one word, while - // "Get Keyword" would be two words. Once we have the individual 'words', we break those - // into constituent 'character spans' of interest. For example, while 'UIElement' is one - // word, it make character spans corresponding to "U", "I" and "Element". These spans - // are then used when doing camel cased matches against candidate patterns. - interface Segment { - // Information about the entire piece of text between the dots. For example, if the - // text between the dots is 'GetKeyword', then TotalTextChunk.Text will be 'GetKeyword' and - // TotalTextChunk.CharacterSpans will correspond to 'Get', 'Keyword'. - totalTextChunk: TextChunk; - - // Information about the subwords compromising the total word. For example, if the - // text between the dots is 'GetFoo KeywordBar', then the subwords will be 'GetFoo' - // and 'KeywordBar'. Those individual words will have CharacterSpans of ('Get' and - // 'Foo') and('Keyword' and 'Bar') respectively. - subWordTextChunks: TextChunk[]; - } +// First we break up the pattern given by dots. Each portion of the pattern between the +// dots is a 'Segment'. The 'Segment' contains information about the entire section of +// text between the dots, as well as information about any individual 'Words' that we +// can break the segment into. A 'Word' is simply a contiguous sequence of characters +// that can appear in a typescript identifier. So "GetKeyword" would be one word, while +// "Get Keyword" would be two words. Once we have the individual 'words', we break those +// into constituent 'character spans' of interest. For example, while 'UIElement' is one +// word, it make character spans corresponding to "U", "I" and "Element". These spans +// are then used when doing camel cased matches against candidate patterns. +interface Segment { + // Information about the entire piece of text between the dots. For example, if the + // text between the dots is 'GetKeyword', then TotalTextChunk.Text will be 'GetKeyword' and + // TotalTextChunk.CharacterSpans will correspond to 'Get', 'Keyword'. + totalTextChunk: TextChunk; + + // Information about the subwords compromising the total word. For example, if the + // text between the dots is 'GetFoo KeywordBar', then the subwords will be 'GetFoo' + // and 'KeywordBar'. Those individual words will have CharacterSpans of ('Get' and + // 'Foo') and('Keyword' and 'Bar') respectively. + subWordTextChunks: TextChunk[]; +} - // Information about a chunk of text from the pattern. The chunk is a piece of text, with - // cached information about the character spans within in. Character spans are used for - // camel case matching. - interface TextChunk { - // The text of the chunk. This should be a contiguous sequence of character that could - // occur in a symbol name. - text: string; - - // The text of a chunk in lower case. Cached because it is needed often to check for - // case insensitive matches. - textLowerCase: string; - - // Whether or not this chunk is entirely lowercase. We have different rules when searching - // for something entirely lowercase or not. - isLowerCase: boolean; - - // The spans in this text chunk that we think are of interest and should be matched - // independently. For example, if the chunk is for "UIElement" the the spans of interest - // correspond to "U", "I" and "Element". If "UIElement" isn't found as an exact, prefix. - // or substring match, then the character spans will be used to attempt a camel case match. - characterSpans: TextSpan[]; - } +// Information about a chunk of text from the pattern. The chunk is a piece of text, with +// cached information about the character spans within in. Character spans are used for +// camel case matching. +interface TextChunk { + // The text of the chunk. This should be a contiguous sequence of character that could + // occur in a symbol name. + text: string; + + // The text of a chunk in lower case. Cached because it is needed often to check for + // case insensitive matches. + textLowerCase: string; + + // Whether or not this chunk is entirely lowercase. We have different rules when searching + // for something entirely lowercase or not. + isLowerCase: boolean; + + // The spans in this text chunk that we think are of interest and should be matched + // independently. For example, if the chunk is for "UIElement" the the spans of interest + // correspond to "U", "I" and "Element". If "UIElement" isn't found as an exact, prefix. + // or substring match, then the character spans will be used to attempt a camel case match. + characterSpans: TextSpan[]; +} - function createPatternMatch(kind: PatternMatchKind, isCaseSensitive: boolean): PatternMatch { - return { - kind, - isCaseSensitive - }; - } +function createPatternMatch(kind: PatternMatchKind, isCaseSensitive: boolean): PatternMatch { + return { + kind, + isCaseSensitive + }; +} - export function createPatternMatcher(pattern: string): PatternMatcher | undefined { - // We'll often see the same candidate string many times when searching (For example, when - // we see the name of a module that is used everywhere, or the name of an overload). As - // such, we cache the information we compute about the candidate for the life of this - // pattern matcher so we don't have to compute it multiple times. - const stringToWordSpans = new Map(); - - const dotSeparatedSegments = pattern.trim().split(".").map(p => createSegment(p.trim())); - // A segment is considered invalid if we couldn't find any words in it. - if (dotSeparatedSegments.some(segment => !segment.subWordTextChunks.length)) return undefined; - - return { - getFullMatch: (containers, candidate) => getFullMatch(containers, candidate, dotSeparatedSegments, stringToWordSpans), - getMatchForLastSegmentOfPattern: candidate => matchSegment(candidate, last(dotSeparatedSegments), stringToWordSpans), - patternContainsDots: dotSeparatedSegments.length > 1 - }; - } +/** @internal */ +export function createPatternMatcher(pattern: string): PatternMatcher | undefined { + // We'll often see the same candidate string many times when searching (For example, when + // we see the name of a module that is used everywhere, or the name of an overload). As + // such, we cache the information we compute about the candidate for the life of this + // pattern matcher so we don't have to compute it multiple times. + const stringToWordSpans = new Map(); + + const dotSeparatedSegments = pattern.trim().split(".").map(p => createSegment(p.trim())); + // A segment is considered invalid if we couldn't find any words in it. + if (dotSeparatedSegments.some(segment => !segment.subWordTextChunks.length)) return undefined; + + return { + getFullMatch: (containers, candidate) => getFullMatch(containers, candidate, dotSeparatedSegments, stringToWordSpans), + getMatchForLastSegmentOfPattern: candidate => matchSegment(candidate, last(dotSeparatedSegments), stringToWordSpans), + patternContainsDots: dotSeparatedSegments.length > 1 + }; +} - function getFullMatch(candidateContainers: readonly string[], candidate: string, dotSeparatedSegments: readonly Segment[], stringToWordSpans: ESMap): PatternMatch | undefined { - // First, check that the last part of the dot separated pattern matches the name of the - // candidate. If not, then there's no point in proceeding and doing the more - // expensive work. - const candidateMatch = matchSegment(candidate, last(dotSeparatedSegments), stringToWordSpans); - if (!candidateMatch) { - return undefined; - } +function getFullMatch(candidateContainers: readonly string[], candidate: string, dotSeparatedSegments: readonly Segment[], stringToWordSpans: ESMap): PatternMatch | undefined { + // First, check that the last part of the dot separated pattern matches the name of the + // candidate. If not, then there's no point in proceeding and doing the more + // expensive work. + const candidateMatch = matchSegment(candidate, last(dotSeparatedSegments), stringToWordSpans); + if (!candidateMatch) { + return undefined; + } - // -1 because the last part was checked against the name, and only the rest - // of the parts are checked against the container. - if (dotSeparatedSegments.length - 1 > candidateContainers.length) { - // There weren't enough container parts to match against the pattern parts. - // So this definitely doesn't match. - return undefined; - } + // -1 because the last part was checked against the name, and only the rest + // of the parts are checked against the container. + if (dotSeparatedSegments.length - 1 > candidateContainers.length) { + // There weren't enough container parts to match against the pattern parts. + // So this definitely doesn't match. + return undefined; + } - let bestMatch: PatternMatch | undefined; - for (let i = dotSeparatedSegments.length - 2, j = candidateContainers.length - 1; - i >= 0; - i -= 1, j -= 1) { - bestMatch = betterMatch(bestMatch, matchSegment(candidateContainers[j], dotSeparatedSegments[i], stringToWordSpans)); - } - return bestMatch; + let bestMatch: PatternMatch | undefined; + for (let i = dotSeparatedSegments.length - 2, j = candidateContainers.length - 1; + i >= 0; + i -= 1, j -= 1) { + bestMatch = betterMatch(bestMatch, matchSegment(candidateContainers[j], dotSeparatedSegments[i], stringToWordSpans)); } + return bestMatch; +} - function getWordSpans(word: string, stringToWordSpans: ESMap): TextSpan[] { - let spans = stringToWordSpans.get(word); - if (!spans) { - stringToWordSpans.set(word, spans = breakIntoWordSpans(word)); - } - return spans; +function getWordSpans(word: string, stringToWordSpans: ESMap): TextSpan[] { + let spans = stringToWordSpans.get(word); + if (!spans) { + stringToWordSpans.set(word, spans = breakIntoWordSpans(word)); } + return spans; +} - function matchTextChunk(candidate: string, chunk: TextChunk, stringToWordSpans: ESMap): PatternMatch | undefined { - const index = indexOfIgnoringCase(candidate, chunk.textLowerCase); - if (index === 0) { - // a) Check if the word is a prefix of the candidate, in a case insensitive or - // sensitive manner. If it does, return that there was an exact match if the word and candidate are the same length, else a prefix match. - return createPatternMatch(chunk.text.length === candidate.length ? PatternMatchKind.exact : PatternMatchKind.prefix, /*isCaseSensitive:*/ startsWith(candidate, chunk.text)); - } +function matchTextChunk(candidate: string, chunk: TextChunk, stringToWordSpans: ESMap): PatternMatch | undefined { + const index = indexOfIgnoringCase(candidate, chunk.textLowerCase); + if (index === 0) { + // a) Check if the word is a prefix of the candidate, in a case insensitive or + // sensitive manner. If it does, return that there was an exact match if the word and candidate are the same length, else a prefix match. + return createPatternMatch(chunk.text.length === candidate.length ? PatternMatchKind.exact : PatternMatchKind.prefix, /*isCaseSensitive:*/ startsWith(candidate, chunk.text)); + } - if (chunk.isLowerCase) { - if (index === -1) return undefined; - // b) If the part is entirely lowercase, then check if it is contained anywhere in the - // candidate in a case insensitive manner. If so, return that there was a substring - // match. - // - // Note: We only have a substring match if the lowercase part is prefix match of some - // word part. That way we don't match something like 'Class' when the user types 'a'. - // But we would match 'FooAttribute' (since 'Attribute' starts with 'a'). - const wordSpans = getWordSpans(candidate, stringToWordSpans); - for (const span of wordSpans) { - if (partStartsWith(candidate, span, chunk.text, /*ignoreCase:*/ true)) { - return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ partStartsWith(candidate, span, chunk.text, /*ignoreCase:*/ false)); - } - } - // c) Is the pattern a substring of the candidate starting on one of the candidate's word boundaries? - // We could check every character boundary start of the candidate for the pattern. However, that's - // an m * n operation in the wost case. Instead, find the first instance of the pattern - // substring, and see if it starts on a capital letter. It seems unlikely that the user will try to - // filter the list based on a substring that starts on a capital letter and also with a lowercase one. - // (Pattern: fogbar, Candidate: quuxfogbarFogBar). - if (chunk.text.length < candidate.length && isUpperCaseLetter(candidate.charCodeAt(index))) { - return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ false); + if (chunk.isLowerCase) { + if (index === -1) return undefined; + // b) If the part is entirely lowercase, then check if it is contained anywhere in the + // candidate in a case insensitive manner. If so, return that there was a substring + // match. + // + // Note: We only have a substring match if the lowercase part is prefix match of some + // word part. That way we don't match something like 'Class' when the user types 'a'. + // But we would match 'FooAttribute' (since 'Attribute' starts with 'a'). + const wordSpans = getWordSpans(candidate, stringToWordSpans); + for (const span of wordSpans) { + if (partStartsWith(candidate, span, chunk.text, /*ignoreCase:*/ true)) { + return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ partStartsWith(candidate, span, chunk.text, /*ignoreCase:*/ false)); } } - else { - // d) If the part was not entirely lowercase, then check if it is contained in the - // candidate in a case *sensitive* manner. If so, return that there was a substring - // match. - if (candidate.indexOf(chunk.text) > 0) { - return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ true); - } - // e) If the part was not entirely lowercase, then attempt a camel cased match as well. - if (chunk.characterSpans.length > 0) { - const candidateParts = getWordSpans(candidate, stringToWordSpans); - const isCaseSensitive = tryCamelCaseMatch(candidate, candidateParts, chunk, /*ignoreCase:*/ false) ? true - : tryCamelCaseMatch(candidate, candidateParts, chunk, /*ignoreCase:*/ true) ? false : undefined; - if (isCaseSensitive !== undefined) { - return createPatternMatch(PatternMatchKind.camelCase, isCaseSensitive); - } - } + // c) Is the pattern a substring of the candidate starting on one of the candidate's word boundaries? + // We could check every character boundary start of the candidate for the pattern. However, that's + // an m * n operation in the wost case. Instead, find the first instance of the pattern + // substring, and see if it starts on a capital letter. It seems unlikely that the user will try to + // filter the list based on a substring that starts on a capital letter and also with a lowercase one. + // (Pattern: fogbar, Candidate: quuxfogbarFogBar). + if (chunk.text.length < candidate.length && isUpperCaseLetter(candidate.charCodeAt(index))) { + return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ false); } } - - function matchSegment(candidate: string, segment: Segment, stringToWordSpans: ESMap): PatternMatch | undefined { - // First check if the segment matches as is. This is also useful if the segment contains - // characters we would normally strip when splitting into parts that we also may want to - // match in the candidate. For example if the segment is "@int" and the candidate is - // "@int", then that will show up as an exact match here. - // - // Note: if the segment contains a space or an asterisk then we must assume that it's a - // multi-word segment. - if (every(segment.totalTextChunk.text, ch => ch !== CharacterCodes.space && ch !== CharacterCodes.asterisk)) { - const match = matchTextChunk(candidate, segment.totalTextChunk, stringToWordSpans); - if (match) return match; + else { + // d) If the part was not entirely lowercase, then check if it is contained in the + // candidate in a case *sensitive* manner. If so, return that there was a substring + // match. + if (candidate.indexOf(chunk.text) > 0) { + return createPatternMatch(PatternMatchKind.substring, /*isCaseSensitive:*/ true); } - - // The logic for pattern matching is now as follows: - // - // 1) Break the segment passed in into words. Breaking is rather simple and a - // good way to think about it that if gives you all the individual alphanumeric words - // of the pattern. - // - // 2) For each word try to match the word against the candidate value. - // - // 3) Matching is as follows: - // - // a) Check if the word is a prefix of the candidate, in a case insensitive or - // sensitive manner. If it does, return that there was an exact match if the word and candidate are the same length, else a prefix match. - // - // If the word is entirely lowercase: - // b) Then check if it is contained anywhere in the - // candidate in a case insensitive manner. If so, return that there was a substring - // match. - // - // Note: We only have a substring match if the lowercase part is prefix match of - // some word part. That way we don't match something like 'Class' when the user - // types 'a'. But we would match 'FooAttribute' (since 'Attribute' starts with - // 'a'). - // - // c) The word is all lower case. Is it a case insensitive substring of the candidate starting - // on a part boundary of the candidate? - // - // Else: - // d) If the word was not entirely lowercase, then check if it is contained in the - // candidate in a case *sensitive* manner. If so, return that there was a substring - // match. - // - // e) If the word was not entirely lowercase, then attempt a camel cased match as - // well. - // - // Only if all words have some sort of match is the pattern considered matched. - - const subWordTextChunks = segment.subWordTextChunks; - let bestMatch: PatternMatch | undefined; - for (const subWordTextChunk of subWordTextChunks) { - bestMatch = betterMatch(bestMatch, matchTextChunk(candidate, subWordTextChunk, stringToWordSpans)); + // e) If the part was not entirely lowercase, then attempt a camel cased match as well. + if (chunk.characterSpans.length > 0) { + const candidateParts = getWordSpans(candidate, stringToWordSpans); + const isCaseSensitive = tryCamelCaseMatch(candidate, candidateParts, chunk, /*ignoreCase:*/ false) ? true + : tryCamelCaseMatch(candidate, candidateParts, chunk, /*ignoreCase:*/ true) ? false : undefined; + if (isCaseSensitive !== undefined) { + return createPatternMatch(PatternMatchKind.camelCase, isCaseSensitive); + } } - return bestMatch; } +} - function betterMatch(a: PatternMatch | undefined, b: PatternMatch | undefined): PatternMatch | undefined { - return min([a, b], compareMatches); - } - function compareMatches(a: PatternMatch | undefined, b: PatternMatch | undefined): Comparison { - return a === undefined ? Comparison.GreaterThan : b === undefined ? Comparison.LessThan - : compareValues(a.kind, b.kind) || compareBooleans(!a.isCaseSensitive, !b.isCaseSensitive); - } +function matchSegment(candidate: string, segment: Segment, stringToWordSpans: ESMap): PatternMatch | undefined { + // First check if the segment matches as is. This is also useful if the segment contains + // characters we would normally strip when splitting into parts that we also may want to + // match in the candidate. For example if the segment is "@int" and the candidate is + // "@int", then that will show up as an exact match here. + // + // Note: if the segment contains a space or an asterisk then we must assume that it's a + // multi-word segment. + if (every(segment.totalTextChunk.text, ch => ch !== CharacterCodes.space && ch !== CharacterCodes.asterisk)) { + const match = matchTextChunk(candidate, segment.totalTextChunk, stringToWordSpans); + if (match) return match; + } + + // The logic for pattern matching is now as follows: + // + // 1) Break the segment passed in into words. Breaking is rather simple and a + // good way to think about it that if gives you all the individual alphanumeric words + // of the pattern. + // + // 2) For each word try to match the word against the candidate value. + // + // 3) Matching is as follows: + // + // a) Check if the word is a prefix of the candidate, in a case insensitive or + // sensitive manner. If it does, return that there was an exact match if the word and candidate are the same length, else a prefix match. + // + // If the word is entirely lowercase: + // b) Then check if it is contained anywhere in the + // candidate in a case insensitive manner. If so, return that there was a substring + // match. + // + // Note: We only have a substring match if the lowercase part is prefix match of + // some word part. That way we don't match something like 'Class' when the user + // types 'a'. But we would match 'FooAttribute' (since 'Attribute' starts with + // 'a'). + // + // c) The word is all lower case. Is it a case insensitive substring of the candidate starting + // on a part boundary of the candidate? + // + // Else: + // d) If the word was not entirely lowercase, then check if it is contained in the + // candidate in a case *sensitive* manner. If so, return that there was a substring + // match. + // + // e) If the word was not entirely lowercase, then attempt a camel cased match as + // well. + // + // Only if all words have some sort of match is the pattern considered matched. + + const subWordTextChunks = segment.subWordTextChunks; + let bestMatch: PatternMatch | undefined; + for (const subWordTextChunk of subWordTextChunks) { + bestMatch = betterMatch(bestMatch, matchTextChunk(candidate, subWordTextChunk, stringToWordSpans)); + } + return bestMatch; +} - function partStartsWith(candidate: string, candidateSpan: TextSpan, pattern: string, ignoreCase: boolean, patternSpan: TextSpan = { start: 0, length: pattern.length }): boolean { - return patternSpan.length <= candidateSpan.length // If pattern part is longer than the candidate part there can never be a match. - && everyInRange(0, patternSpan.length, i => equalChars(pattern.charCodeAt(patternSpan.start + i), candidate.charCodeAt(candidateSpan.start + i), ignoreCase)); - } +function betterMatch(a: PatternMatch | undefined, b: PatternMatch | undefined): PatternMatch | undefined { + return min([a, b], compareMatches); +} +function compareMatches(a: PatternMatch | undefined, b: PatternMatch | undefined): Comparison { + return a === undefined ? Comparison.GreaterThan : b === undefined ? Comparison.LessThan + : compareValues(a.kind, b.kind) || compareBooleans(!a.isCaseSensitive, !b.isCaseSensitive); +} - function equalChars(ch1: number, ch2: number, ignoreCase: boolean): boolean { - return ignoreCase ? toLowerCase(ch1) === toLowerCase(ch2) : ch1 === ch2; - } +function partStartsWith(candidate: string, candidateSpan: TextSpan, pattern: string, ignoreCase: boolean, patternSpan: TextSpan = { start: 0, length: pattern.length }): boolean { + return patternSpan.length <= candidateSpan.length // If pattern part is longer than the candidate part there can never be a match. + && everyInRange(0, patternSpan.length, i => equalChars(pattern.charCodeAt(patternSpan.start + i), candidate.charCodeAt(candidateSpan.start + i), ignoreCase)); +} - function tryCamelCaseMatch(candidate: string, candidateParts: TextSpan[], chunk: TextChunk, ignoreCase: boolean): boolean { - const chunkCharacterSpans = chunk.characterSpans; +function equalChars(ch1: number, ch2: number, ignoreCase: boolean): boolean { + return ignoreCase ? toLowerCase(ch1) === toLowerCase(ch2) : ch1 === ch2; +} - // Note: we may have more pattern parts than candidate parts. This is because multiple - // pattern parts may match a candidate part. For example "SiUI" against "SimpleUI". - // We'll have 3 pattern parts Si/U/I against two candidate parts Simple/UI. However, U - // and I will both match in UI. +function tryCamelCaseMatch(candidate: string, candidateParts: TextSpan[], chunk: TextChunk, ignoreCase: boolean): boolean { + const chunkCharacterSpans = chunk.characterSpans; - let currentCandidate = 0; - let currentChunkSpan = 0; - let firstMatch: number | undefined; - let contiguous: boolean | undefined; + // Note: we may have more pattern parts than candidate parts. This is because multiple + // pattern parts may match a candidate part. For example "SiUI" against "SimpleUI". + // We'll have 3 pattern parts Si/U/I against two candidate parts Simple/UI. However, U + // and I will both match in UI. - while (true) { - // Let's consider our termination cases - if (currentChunkSpan === chunkCharacterSpans.length) { - return true; - } - else if (currentCandidate === candidateParts.length) { - // No match, since we still have more of the pattern to hit - return false; - } + let currentCandidate = 0; + let currentChunkSpan = 0; + let firstMatch: number | undefined; + let contiguous: boolean | undefined; - let candidatePart = candidateParts[currentCandidate]; - let gotOneMatchThisCandidate = false; - - // Consider the case of matching SiUI against SimpleUIElement. The candidate parts - // will be Simple/UI/Element, and the pattern parts will be Si/U/I. We'll match 'Si' - // against 'Simple' first. Then we'll match 'U' against 'UI'. However, we want to - // still keep matching pattern parts against that candidate part. - for (; currentChunkSpan < chunkCharacterSpans.length; currentChunkSpan++) { - const chunkCharacterSpan = chunkCharacterSpans[currentChunkSpan]; - - if (gotOneMatchThisCandidate) { - // We've already gotten one pattern part match in this candidate. We will - // only continue trying to consumer pattern parts if the last part and this - // part are both upper case. - if (!isUpperCaseLetter(chunk.text.charCodeAt(chunkCharacterSpans[currentChunkSpan - 1].start)) || - !isUpperCaseLetter(chunk.text.charCodeAt(chunkCharacterSpans[currentChunkSpan].start))) { - break; - } - } + while (true) { + // Let's consider our termination cases + if (currentChunkSpan === chunkCharacterSpans.length) { + return true; + } + else if (currentCandidate === candidateParts.length) { + // No match, since we still have more of the pattern to hit + return false; + } - if (!partStartsWith(candidate, candidatePart, chunk.text, ignoreCase, chunkCharacterSpan)) { + let candidatePart = candidateParts[currentCandidate]; + let gotOneMatchThisCandidate = false; + + // Consider the case of matching SiUI against SimpleUIElement. The candidate parts + // will be Simple/UI/Element, and the pattern parts will be Si/U/I. We'll match 'Si' + // against 'Simple' first. Then we'll match 'U' against 'UI'. However, we want to + // still keep matching pattern parts against that candidate part. + for (; currentChunkSpan < chunkCharacterSpans.length; currentChunkSpan++) { + const chunkCharacterSpan = chunkCharacterSpans[currentChunkSpan]; + + if (gotOneMatchThisCandidate) { + // We've already gotten one pattern part match in this candidate. We will + // only continue trying to consumer pattern parts if the last part and this + // part are both upper case. + if (!isUpperCaseLetter(chunk.text.charCodeAt(chunkCharacterSpans[currentChunkSpan - 1].start)) || + !isUpperCaseLetter(chunk.text.charCodeAt(chunkCharacterSpans[currentChunkSpan].start))) { break; } + } - gotOneMatchThisCandidate = true; + if (!partStartsWith(candidate, candidatePart, chunk.text, ignoreCase, chunkCharacterSpan)) { + break; + } - firstMatch = firstMatch === undefined ? currentCandidate : firstMatch; + gotOneMatchThisCandidate = true; - // If we were contiguous, then keep that value. If we weren't, then keep that - // value. If we don't know, then set the value to 'true' as an initial match is - // obviously contiguous. - contiguous = contiguous === undefined ? true : contiguous; + firstMatch = firstMatch === undefined ? currentCandidate : firstMatch; - candidatePart = createTextSpan(candidatePart.start + chunkCharacterSpan.length, candidatePart.length - chunkCharacterSpan.length); - } + // If we were contiguous, then keep that value. If we weren't, then keep that + // value. If we don't know, then set the value to 'true' as an initial match is + // obviously contiguous. + contiguous = contiguous === undefined ? true : contiguous; - // Check if we matched anything at all. If we didn't, then we need to unset the - // contiguous bit if we currently had it set. - // If we haven't set the bit yet, then that means we haven't matched anything so - // far, and we don't want to change that. - if (!gotOneMatchThisCandidate && contiguous !== undefined) { - contiguous = false; - } + candidatePart = createTextSpan(candidatePart.start + chunkCharacterSpan.length, candidatePart.length - chunkCharacterSpan.length); + } - // Move onto the next candidate. - currentCandidate++; + // Check if we matched anything at all. If we didn't, then we need to unset the + // contiguous bit if we currently had it set. + // If we haven't set the bit yet, then that means we haven't matched anything so + // far, and we don't want to change that. + if (!gotOneMatchThisCandidate && contiguous !== undefined) { + contiguous = false; } - } - function createSegment(text: string): Segment { - return { - totalTextChunk: createTextChunk(text), - subWordTextChunks: breakPatternIntoTextChunks(text) - }; + // Move onto the next candidate. + currentCandidate++; } +} - function isUpperCaseLetter(ch: number) { - // Fast check for the ascii range. - if (ch >= CharacterCodes.A && ch <= CharacterCodes.Z) { - return true; - } - - if (ch < CharacterCodes.maxAsciiCharacter || !isUnicodeIdentifierStart(ch, ScriptTarget.Latest)) { - return false; - } +function createSegment(text: string): Segment { + return { + totalTextChunk: createTextChunk(text), + subWordTextChunks: breakPatternIntoTextChunks(text) + }; +} - // TODO: find a way to determine this for any unicode characters in a - // non-allocating manner. - const str = String.fromCharCode(ch); - return str === str.toUpperCase(); +function isUpperCaseLetter(ch: number) { + // Fast check for the ascii range. + if (ch >= CharacterCodes.A && ch <= CharacterCodes.Z) { + return true; } - function isLowerCaseLetter(ch: number) { - // Fast check for the ascii range. - if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { - return true; - } + if (ch < CharacterCodes.maxAsciiCharacter || !isUnicodeIdentifierStart(ch, ScriptTarget.Latest)) { + return false; + } - if (ch < CharacterCodes.maxAsciiCharacter || !isUnicodeIdentifierStart(ch, ScriptTarget.Latest)) { - return false; - } + // TODO: find a way to determine this for any unicode characters in a + // non-allocating manner. + const str = String.fromCharCode(ch); + return str === str.toUpperCase(); +} +function isLowerCaseLetter(ch: number) { + // Fast check for the ascii range. + if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { + return true; + } - // TODO: find a way to determine this for any unicode characters in a - // non-allocating manner. - const str = String.fromCharCode(ch); - return str === str.toLowerCase(); + if (ch < CharacterCodes.maxAsciiCharacter || !isUnicodeIdentifierStart(ch, ScriptTarget.Latest)) { + return false; } - // Assumes 'value' is already lowercase. - function indexOfIgnoringCase(str: string, value: string): number { - const n = str.length - value.length; - for (let start = 0; start <= n; start++) { - if (every(value, (valueChar, i) => toLowerCase(str.charCodeAt(i + start)) === valueChar)) { - return start; - } - } - return -1; - } + // TODO: find a way to determine this for any unicode characters in a + // non-allocating manner. + const str = String.fromCharCode(ch); + return str === str.toLowerCase(); +} - function toLowerCase(ch: number): number { - // Fast convert for the ascii range. - if (ch >= CharacterCodes.A && ch <= CharacterCodes.Z) { - return CharacterCodes.a + (ch - CharacterCodes.A); +// Assumes 'value' is already lowercase. +function indexOfIgnoringCase(str: string, value: string): number { + const n = str.length - value.length; + for (let start = 0; start <= n; start++) { + if (every(value, (valueChar, i) => toLowerCase(str.charCodeAt(i + start)) === valueChar)) { + return start; } + } - if (ch < CharacterCodes.maxAsciiCharacter) { - return ch; - } + return -1; +} - // TODO: find a way to compute this for any unicode characters in a - // non-allocating manner. - return String.fromCharCode(ch).toLowerCase().charCodeAt(0); +function toLowerCase(ch: number): number { + // Fast convert for the ascii range. + if (ch >= CharacterCodes.A && ch <= CharacterCodes.Z) { + return CharacterCodes.a + (ch - CharacterCodes.A); } - function isDigit(ch: number) { - // TODO(cyrusn): Find a way to support this for unicode digits. - return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; + if (ch < CharacterCodes.maxAsciiCharacter) { + return ch; } - function isWordChar(ch: number) { - return isUpperCaseLetter(ch) || isLowerCaseLetter(ch) || isDigit(ch) || ch === CharacterCodes._ || ch === CharacterCodes.$; - } + // TODO: find a way to compute this for any unicode characters in a + // non-allocating manner. + return String.fromCharCode(ch).toLowerCase().charCodeAt(0); +} + +function isDigit(ch: number) { + // TODO(cyrusn): Find a way to support this for unicode digits. + return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; +} - function breakPatternIntoTextChunks(pattern: string): TextChunk[] { - const result: TextChunk[] = []; - let wordStart = 0; - let wordLength = 0; +function isWordChar(ch: number) { + return isUpperCaseLetter(ch) || isLowerCaseLetter(ch) || isDigit(ch) || ch === CharacterCodes._ || ch === CharacterCodes.$; +} - for (let i = 0; i < pattern.length; i++) { - const ch = pattern.charCodeAt(i); - if (isWordChar(ch)) { - if (wordLength === 0) { - wordStart = i; - } - wordLength++; - } - else { - if (wordLength > 0) { - result.push(createTextChunk(pattern.substr(wordStart, wordLength))); - wordLength = 0; - } +function breakPatternIntoTextChunks(pattern: string): TextChunk[] { + const result: TextChunk[] = []; + let wordStart = 0; + let wordLength = 0; + + for (let i = 0; i < pattern.length; i++) { + const ch = pattern.charCodeAt(i); + if (isWordChar(ch)) { + if (wordLength === 0) { + wordStart = i; } + wordLength++; } - - if (wordLength > 0) { - result.push(createTextChunk(pattern.substr(wordStart, wordLength))); + else { + if (wordLength > 0) { + result.push(createTextChunk(pattern.substr(wordStart, wordLength))); + wordLength = 0; + } } - - return result; } - function createTextChunk(text: string): TextChunk { - const textLowerCase = text.toLowerCase(); - return { - text, - textLowerCase, - isLowerCase: text === textLowerCase, - characterSpans: breakIntoCharacterSpans(text) - }; + if (wordLength > 0) { + result.push(createTextChunk(pattern.substr(wordStart, wordLength))); } - export function breakIntoCharacterSpans(identifier: string): TextSpan[] { - return breakIntoSpans(identifier, /*word:*/ false); - } + return result; +} - export function breakIntoWordSpans(identifier: string): TextSpan[] { - return breakIntoSpans(identifier, /*word:*/ true); - } +function createTextChunk(text: string): TextChunk { + const textLowerCase = text.toLowerCase(); + return { + text, + textLowerCase, + isLowerCase: text === textLowerCase, + characterSpans: breakIntoCharacterSpans(text) + }; +} - function breakIntoSpans(identifier: string, word: boolean): TextSpan[] { - const result: TextSpan[] = []; +/** @internal */ +export function breakIntoCharacterSpans(identifier: string): TextSpan[] { + return breakIntoSpans(identifier, /*word:*/ false); +} - let wordStart = 0; - for (let i = 1; i < identifier.length; i++) { - const lastIsDigit = isDigit(identifier.charCodeAt(i - 1)); - const currentIsDigit = isDigit(identifier.charCodeAt(i)); +/** @internal */ +export function breakIntoWordSpans(identifier: string): TextSpan[] { + return breakIntoSpans(identifier, /*word:*/ true); +} - const hasTransitionFromLowerToUpper = transitionFromLowerToUpper(identifier, word, i); - const hasTransitionFromUpperToLower = word && transitionFromUpperToLower(identifier, i, wordStart); +function breakIntoSpans(identifier: string, word: boolean): TextSpan[] { + const result: TextSpan[] = []; - if (charIsPunctuation(identifier.charCodeAt(i - 1)) || - charIsPunctuation(identifier.charCodeAt(i)) || - lastIsDigit !== currentIsDigit || - hasTransitionFromLowerToUpper || - hasTransitionFromUpperToLower) { + let wordStart = 0; + for (let i = 1; i < identifier.length; i++) { + const lastIsDigit = isDigit(identifier.charCodeAt(i - 1)); + const currentIsDigit = isDigit(identifier.charCodeAt(i)); - if (!isAllPunctuation(identifier, wordStart, i)) { - result.push(createTextSpan(wordStart, i - wordStart)); - } + const hasTransitionFromLowerToUpper = transitionFromLowerToUpper(identifier, word, i); + const hasTransitionFromUpperToLower = word && transitionFromUpperToLower(identifier, i, wordStart); - wordStart = i; + if (charIsPunctuation(identifier.charCodeAt(i - 1)) || + charIsPunctuation(identifier.charCodeAt(i)) || + lastIsDigit !== currentIsDigit || + hasTransitionFromLowerToUpper || + hasTransitionFromUpperToLower) { + + if (!isAllPunctuation(identifier, wordStart, i)) { + result.push(createTextSpan(wordStart, i - wordStart)); } - } - if (!isAllPunctuation(identifier, wordStart, identifier.length)) { - result.push(createTextSpan(wordStart, identifier.length - wordStart)); + wordStart = i; } + } - return result; + if (!isAllPunctuation(identifier, wordStart, identifier.length)) { + result.push(createTextSpan(wordStart, identifier.length - wordStart)); } - function charIsPunctuation(ch: number) { - switch (ch) { - case CharacterCodes.exclamation: - case CharacterCodes.doubleQuote: - case CharacterCodes.hash: - case CharacterCodes.percent: - case CharacterCodes.ampersand: - case CharacterCodes.singleQuote: - case CharacterCodes.openParen: - case CharacterCodes.closeParen: - case CharacterCodes.asterisk: - case CharacterCodes.comma: - case CharacterCodes.minus: - case CharacterCodes.dot: - case CharacterCodes.slash: - case CharacterCodes.colon: - case CharacterCodes.semicolon: - case CharacterCodes.question: - case CharacterCodes.at: - case CharacterCodes.openBracket: - case CharacterCodes.backslash: - case CharacterCodes.closeBracket: - case CharacterCodes._: - case CharacterCodes.openBrace: - case CharacterCodes.closeBrace: - return true; - } + return result; +} - return false; +function charIsPunctuation(ch: number) { + switch (ch) { + case CharacterCodes.exclamation: + case CharacterCodes.doubleQuote: + case CharacterCodes.hash: + case CharacterCodes.percent: + case CharacterCodes.ampersand: + case CharacterCodes.singleQuote: + case CharacterCodes.openParen: + case CharacterCodes.closeParen: + case CharacterCodes.asterisk: + case CharacterCodes.comma: + case CharacterCodes.minus: + case CharacterCodes.dot: + case CharacterCodes.slash: + case CharacterCodes.colon: + case CharacterCodes.semicolon: + case CharacterCodes.question: + case CharacterCodes.at: + case CharacterCodes.openBracket: + case CharacterCodes.backslash: + case CharacterCodes.closeBracket: + case CharacterCodes._: + case CharacterCodes.openBrace: + case CharacterCodes.closeBrace: + return true; } - function isAllPunctuation(identifier: string, start: number, end: number): boolean { - return every(identifier, ch => charIsPunctuation(ch) && ch !== CharacterCodes._, start, end); - } + return false; +} - function transitionFromUpperToLower(identifier: string, index: number, wordStart: number): boolean { - // Cases this supports: - // 1) IDisposable -> I, Disposable - // 2) UIElement -> UI, Element - // 3) HTMLDocument -> HTML, Document - // - // etc. - // We have a transition from an upper to a lower letter here. But we only - // want to break if all the letters that preceded are uppercase. i.e. if we - // have "Foo" we don't want to break that into "F, oo". But if we have - // "IFoo" or "UIFoo", then we want to break that into "I, Foo" and "UI, - // Foo". i.e. the last uppercase letter belongs to the lowercase letters - // that follows. Note: this will make the following not split properly: - // "HELLOthere". However, these sorts of names do not show up in .Net - // programs. - return index !== wordStart - && index + 1 < identifier.length - && isUpperCaseLetter(identifier.charCodeAt(index)) - && isLowerCaseLetter(identifier.charCodeAt(index + 1)) - && every(identifier, isUpperCaseLetter, wordStart, index); - } +function isAllPunctuation(identifier: string, start: number, end: number): boolean { + return every(identifier, ch => charIsPunctuation(ch) && ch !== CharacterCodes._, start, end); +} - function transitionFromLowerToUpper(identifier: string, word: boolean, index: number): boolean { - const lastIsUpper = isUpperCaseLetter(identifier.charCodeAt(index - 1)); - const currentIsUpper = isUpperCaseLetter(identifier.charCodeAt(index)); +function transitionFromUpperToLower(identifier: string, index: number, wordStart: number): boolean { + // Cases this supports: + // 1) IDisposable -> I, Disposable + // 2) UIElement -> UI, Element + // 3) HTMLDocument -> HTML, Document + // + // etc. + // We have a transition from an upper to a lower letter here. But we only + // want to break if all the letters that preceded are uppercase. i.e. if we + // have "Foo" we don't want to break that into "F, oo". But if we have + // "IFoo" or "UIFoo", then we want to break that into "I, Foo" and "UI, + // Foo". i.e. the last uppercase letter belongs to the lowercase letters + // that follows. Note: this will make the following not split properly: + // "HELLOthere". However, these sorts of names do not show up in .Net + // programs. + return index !== wordStart + && index + 1 < identifier.length + && isUpperCaseLetter(identifier.charCodeAt(index)) + && isLowerCaseLetter(identifier.charCodeAt(index + 1)) + && every(identifier, isUpperCaseLetter, wordStart, index); +} - // See if the casing indicates we're starting a new word. Note: if we're breaking on - // words, then just seeing an upper case character isn't enough. Instead, it has to - // be uppercase and the previous character can't be uppercase. - // - // For example, breaking "AddMetadata" on words would make: Add Metadata - // - // on characters would be: A dd M etadata - // - // Break "AM" on words would be: AM - // - // on characters would be: A M - // - // We break the search string on characters. But we break the symbol name on words. - return currentIsUpper && (!word || !lastIsUpper); - } +function transitionFromLowerToUpper(identifier: string, word: boolean, index: number): boolean { + const lastIsUpper = isUpperCaseLetter(identifier.charCodeAt(index - 1)); + const currentIsUpper = isUpperCaseLetter(identifier.charCodeAt(index)); + + // See if the casing indicates we're starting a new word. Note: if we're breaking on + // words, then just seeing an upper case character isn't enough. Instead, it has to + // be uppercase and the previous character can't be uppercase. + // + // For example, breaking "AddMetadata" on words would make: Add Metadata + // + // on characters would be: A dd M etadata + // + // Break "AM" on words would be: AM + // + // on characters would be: A M + // + // We break the search string on characters. But we break the symbol name on words. + return currentIsUpper && (!word || !lastIsUpper); +} - function everyInRange(start: number, end: number, pred: (n: number) => boolean): boolean { - for (let i = start; i < end; i++) { - if (!pred(i)) { - return false; - } +function everyInRange(start: number, end: number, pred: (n: number) => boolean): boolean { + for (let i = start; i < end; i++) { + if (!pred(i)) { + return false; } - return true; } + return true; +} - function every(s: string, pred: (ch: number, index: number) => boolean, start = 0, end = s.length): boolean { - return everyInRange(start, end, i => pred(s.charCodeAt(i), i)); - } +function every(s: string, pred: (ch: number, index: number) => boolean, start = 0, end = s.length): boolean { + return everyInRange(start, end, i => pred(s.charCodeAt(i), i)); } diff --git a/src/services/preProcess.ts b/src/services/preProcess.ts index f891891deb76f..be3823693ea75 100644 --- a/src/services/preProcess.ts +++ b/src/services/preProcess.ts @@ -1,210 +1,154 @@ -namespace ts { - export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo { - const pragmaContext: PragmaContext = { - languageVersion: ScriptTarget.ES5, // controls whether the token scanner considers unicode identifiers or not - shouldn't matter, since we're only using it for trivia - pragmas: undefined, - checkJsDirective: undefined, - referencedFiles: [], - typeReferenceDirectives: [], - libReferenceDirectives: [], - amdDependencies: [], - hasNoDefaultLib: undefined, - moduleName: undefined - }; - const importedFiles: FileReference[] = []; - let ambientExternalModules: { ref: FileReference, depth: number }[] | undefined; - let lastToken: SyntaxKind; - let currentToken: SyntaxKind; - let braceNesting = 0; - // assume that text represent an external module if it contains at least one top level import/export - // ambient modules that are found inside external modules are interpreted as module augmentations - let externalModule = false; - - function nextToken() { - lastToken = currentToken; - currentToken = scanner.scan(); - if (currentToken === SyntaxKind.OpenBraceToken) { - braceNesting++; - } - else if (currentToken === SyntaxKind.CloseBraceToken) { - braceNesting--; - } - return currentToken; +import { + FileReference, isKeyword, lastOrUndefined, length, noop, PragmaContext, PreProcessedFileInfo, processCommentPragmas, + processPragmasIntoFields, scanner, ScriptTarget, SyntaxKind, +} from "./_namespaces/ts"; + +export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo { + const pragmaContext: PragmaContext = { + languageVersion: ScriptTarget.ES5, // controls whether the token scanner considers unicode identifiers or not - shouldn't matter, since we're only using it for trivia + pragmas: undefined, + checkJsDirective: undefined, + referencedFiles: [], + typeReferenceDirectives: [], + libReferenceDirectives: [], + amdDependencies: [], + hasNoDefaultLib: undefined, + moduleName: undefined + }; + const importedFiles: FileReference[] = []; + let ambientExternalModules: { ref: FileReference, depth: number }[] | undefined; + let lastToken: SyntaxKind; + let currentToken: SyntaxKind; + let braceNesting = 0; + // assume that text represent an external module if it contains at least one top level import/export + // ambient modules that are found inside external modules are interpreted as module augmentations + let externalModule = false; + + function nextToken() { + lastToken = currentToken; + currentToken = scanner.scan(); + if (currentToken === SyntaxKind.OpenBraceToken) { + braceNesting++; } - - function getFileReference() { - const fileName = scanner.getTokenValue(); - const pos = scanner.getTokenPos(); - return { fileName, pos, end: pos + fileName.length }; + else if (currentToken === SyntaxKind.CloseBraceToken) { + braceNesting--; } + return currentToken; + } - function recordAmbientExternalModule(): void { - if (!ambientExternalModules) { - ambientExternalModules = []; - } - ambientExternalModules.push({ ref: getFileReference(), depth: braceNesting }); + function getFileReference() { + const fileName = scanner.getTokenValue(); + const pos = scanner.getTokenPos(); + return { fileName, pos, end: pos + fileName.length }; + } + + function recordAmbientExternalModule(): void { + if (!ambientExternalModules) { + ambientExternalModules = []; } + ambientExternalModules.push({ ref: getFileReference(), depth: braceNesting }); + } - function recordModuleName() { - importedFiles.push(getFileReference()); + function recordModuleName() { + importedFiles.push(getFileReference()); - markAsExternalModuleIfTopLevel(); - } + markAsExternalModuleIfTopLevel(); + } - function markAsExternalModuleIfTopLevel() { - if (braceNesting === 0) { - externalModule = true; - } + function markAsExternalModuleIfTopLevel() { + if (braceNesting === 0) { + externalModule = true; } + } - /** - * Returns true if at least one token was consumed from the stream - */ - function tryConsumeDeclare(): boolean { - let token = scanner.getToken(); - if (token === SyntaxKind.DeclareKeyword) { - // declare module "mod" + /** + * Returns true if at least one token was consumed from the stream + */ + function tryConsumeDeclare(): boolean { + let token = scanner.getToken(); + if (token === SyntaxKind.DeclareKeyword) { + // declare module "mod" + token = nextToken(); + if (token === SyntaxKind.ModuleKeyword) { token = nextToken(); - if (token === SyntaxKind.ModuleKeyword) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral) { - recordAmbientExternalModule(); - } + if (token === SyntaxKind.StringLiteral) { + recordAmbientExternalModule(); } - return true; } + return true; + } + + return false; + } + /** + * Returns true if at least one token was consumed from the stream + */ + function tryConsumeImport(): boolean { + if (lastToken === SyntaxKind.DotToken) { return false; } - - /** - * Returns true if at least one token was consumed from the stream - */ - function tryConsumeImport(): boolean { - if (lastToken === SyntaxKind.DotToken) { - return false; - } - let token = scanner.getToken(); - if (token === SyntaxKind.ImportKeyword) { + let token = scanner.getToken(); + if (token === SyntaxKind.ImportKeyword) { + token = nextToken(); + if (token === SyntaxKind.OpenParenToken) { token = nextToken(); - if (token === SyntaxKind.OpenParenToken) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { - // import("mod"); - recordModuleName(); - return true; - } - } - else if (token === SyntaxKind.StringLiteral) { - // import "mod"; + if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { + // import("mod"); recordModuleName(); return true; } - else { - if (token === SyntaxKind.TypeKeyword) { - const skipTypeKeyword = scanner.lookAhead(() => { - const token = scanner.scan(); - return token !== SyntaxKind.FromKeyword && ( - token === SyntaxKind.AsteriskToken || - token === SyntaxKind.OpenBraceToken || - token === SyntaxKind.Identifier || - isKeyword(token) - ); - }); - if (skipTypeKeyword) { - token = nextToken(); - } + } + else if (token === SyntaxKind.StringLiteral) { + // import "mod"; + recordModuleName(); + return true; + } + else { + if (token === SyntaxKind.TypeKeyword) { + const skipTypeKeyword = scanner.lookAhead(() => { + const token = scanner.scan(); + return token !== SyntaxKind.FromKeyword && ( + token === SyntaxKind.AsteriskToken || + token === SyntaxKind.OpenBraceToken || + token === SyntaxKind.Identifier || + isKeyword(token) + ); + }); + if (skipTypeKeyword) { + token = nextToken(); } + } - if (token === SyntaxKind.Identifier || isKeyword(token)) { + if (token === SyntaxKind.Identifier || isKeyword(token)) { + token = nextToken(); + if (token === SyntaxKind.FromKeyword) { token = nextToken(); - if (token === SyntaxKind.FromKeyword) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral) { - // import d from "mod"; - recordModuleName(); - return true; - } - } - else if (token === SyntaxKind.EqualsToken) { - if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) { - return true; - } - } - else if (token === SyntaxKind.CommaToken) { - // consume comma and keep going - token = nextToken(); - } - else { - // unknown syntax + if (token === SyntaxKind.StringLiteral) { + // import d from "mod"; + recordModuleName(); return true; } } - - if (token === SyntaxKind.OpenBraceToken) { - token = nextToken(); - // consume "{ a as B, c, d as D}" clauses - // make sure that it stops on EOF - while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) { - token = nextToken(); - } - - if (token === SyntaxKind.CloseBraceToken) { - token = nextToken(); - if (token === SyntaxKind.FromKeyword) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral) { - // import {a as A} from "mod"; - // import d, {a, b as B} from "mod" - recordModuleName(); - } - } + else if (token === SyntaxKind.EqualsToken) { + if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) { + return true; } } - else if (token === SyntaxKind.AsteriskToken) { + else if (token === SyntaxKind.CommaToken) { + // consume comma and keep going token = nextToken(); - if (token === SyntaxKind.AsKeyword) { - token = nextToken(); - if (token === SyntaxKind.Identifier || isKeyword(token)) { - token = nextToken(); - if (token === SyntaxKind.FromKeyword) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral) { - // import * as NS from "mod" - // import d, * as NS from "mod" - recordModuleName(); - } - } - } - } } - } - - return true; - } - - return false; - } - - function tryConsumeExport(): boolean { - let token = scanner.getToken(); - if (token === SyntaxKind.ExportKeyword) { - markAsExternalModuleIfTopLevel(); - token = nextToken(); - if (token === SyntaxKind.TypeKeyword) { - const skipTypeKeyword = scanner.lookAhead(() => { - const token = scanner.scan(); - return token === SyntaxKind.AsteriskToken || - token === SyntaxKind.OpenBraceToken; - }); - if (skipTypeKeyword) { - token = nextToken(); + else { + // unknown syntax + return true; } } + if (token === SyntaxKind.OpenBraceToken) { token = nextToken(); // consume "{ a as B, c, d as D}" clauses - // make sure it stops on EOF + // make sure that it stops on EOF while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) { token = nextToken(); } @@ -214,222 +158,281 @@ namespace ts { if (token === SyntaxKind.FromKeyword) { token = nextToken(); if (token === SyntaxKind.StringLiteral) { - // export {a as A} from "mod"; - // export {a, b as B} from "mod" + // import {a as A} from "mod"; + // import d, {a, b as B} from "mod" recordModuleName(); } } } } else if (token === SyntaxKind.AsteriskToken) { + token = nextToken(); + if (token === SyntaxKind.AsKeyword) { + token = nextToken(); + if (token === SyntaxKind.Identifier || isKeyword(token)) { + token = nextToken(); + if (token === SyntaxKind.FromKeyword) { + token = nextToken(); + if (token === SyntaxKind.StringLiteral) { + // import * as NS from "mod" + // import d, * as NS from "mod" + recordModuleName(); + } + } + } + } + } + } + + return true; + } + + return false; + } + + function tryConsumeExport(): boolean { + let token = scanner.getToken(); + if (token === SyntaxKind.ExportKeyword) { + markAsExternalModuleIfTopLevel(); + token = nextToken(); + if (token === SyntaxKind.TypeKeyword) { + const skipTypeKeyword = scanner.lookAhead(() => { + const token = scanner.scan(); + return token === SyntaxKind.AsteriskToken || + token === SyntaxKind.OpenBraceToken; + }); + if (skipTypeKeyword) { + token = nextToken(); + } + } + if (token === SyntaxKind.OpenBraceToken) { + token = nextToken(); + // consume "{ a as B, c, d as D}" clauses + // make sure it stops on EOF + while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) { + token = nextToken(); + } + + if (token === SyntaxKind.CloseBraceToken) { token = nextToken(); if (token === SyntaxKind.FromKeyword) { token = nextToken(); if (token === SyntaxKind.StringLiteral) { - // export * from "mod" + // export {a as A} from "mod"; + // export {a, b as B} from "mod" recordModuleName(); } } } - else if (token === SyntaxKind.ImportKeyword) { + } + else if (token === SyntaxKind.AsteriskToken) { + token = nextToken(); + if (token === SyntaxKind.FromKeyword) { token = nextToken(); - if (token === SyntaxKind.TypeKeyword) { - const skipTypeKeyword = scanner.lookAhead(() => { - const token = scanner.scan(); - return token === SyntaxKind.Identifier || - isKeyword(token); - }); - if (skipTypeKeyword) { - token = nextToken(); - } + if (token === SyntaxKind.StringLiteral) { + // export * from "mod" + recordModuleName(); } - if (token === SyntaxKind.Identifier || isKeyword(token)) { + } + } + else if (token === SyntaxKind.ImportKeyword) { + token = nextToken(); + if (token === SyntaxKind.TypeKeyword) { + const skipTypeKeyword = scanner.lookAhead(() => { + const token = scanner.scan(); + return token === SyntaxKind.Identifier || + isKeyword(token); + }); + if (skipTypeKeyword) { token = nextToken(); - if (token === SyntaxKind.EqualsToken) { - if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) { - return true; - } + } + } + if (token === SyntaxKind.Identifier || isKeyword(token)) { + token = nextToken(); + if (token === SyntaxKind.EqualsToken) { + if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) { + return true; } } } - - return true; } - return false; + return true; } - function tryConsumeRequireCall(skipCurrentToken: boolean, allowTemplateLiterals = false): boolean { - let token = skipCurrentToken ? nextToken() : scanner.getToken(); - if (token === SyntaxKind.RequireKeyword) { + return false; + } + + function tryConsumeRequireCall(skipCurrentToken: boolean, allowTemplateLiterals = false): boolean { + let token = skipCurrentToken ? nextToken() : scanner.getToken(); + if (token === SyntaxKind.RequireKeyword) { + token = nextToken(); + if (token === SyntaxKind.OpenParenToken) { token = nextToken(); - if (token === SyntaxKind.OpenParenToken) { - token = nextToken(); - if (token === SyntaxKind.StringLiteral || - allowTemplateLiterals && token === SyntaxKind.NoSubstitutionTemplateLiteral) { - // require("mod"); - recordModuleName(); - } + if (token === SyntaxKind.StringLiteral || + allowTemplateLiterals && token === SyntaxKind.NoSubstitutionTemplateLiteral) { + // require("mod"); + recordModuleName(); } - return true; } - return false; + return true; } + return false; + } - function tryConsumeDefine(): boolean { - let token = scanner.getToken(); - if (token === SyntaxKind.Identifier && scanner.getTokenValue() === "define") { - token = nextToken(); - if (token !== SyntaxKind.OpenParenToken) { - return true; - } + function tryConsumeDefine(): boolean { + let token = scanner.getToken(); + if (token === SyntaxKind.Identifier && scanner.getTokenValue() === "define") { + token = nextToken(); + if (token !== SyntaxKind.OpenParenToken) { + return true; + } + token = nextToken(); + if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { + // looks like define ("modname", ... - skip string literal and comma token = nextToken(); - if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { - // looks like define ("modname", ... - skip string literal and comma + if (token === SyntaxKind.CommaToken) { token = nextToken(); - if (token === SyntaxKind.CommaToken) { - token = nextToken(); - } - else { - // unexpected token - return true; - } } - - // should be start of dependency list - if (token !== SyntaxKind.OpenBracketToken) { + else { + // unexpected token return true; } + } - // skip open bracket - token = nextToken(); - // scan until ']' or EOF - while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) { - // record string literals as module names - if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { - recordModuleName(); - } + // should be start of dependency list + if (token !== SyntaxKind.OpenBracketToken) { + return true; + } - token = nextToken(); + // skip open bracket + token = nextToken(); + // scan until ']' or EOF + while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) { + // record string literals as module names + if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) { + recordModuleName(); } - return true; + token = nextToken(); } - return false; + return true; + } + return false; + } - function processImports(): void { - scanner.setText(sourceText); - nextToken(); - // Look for: - // import "mod"; - // import d from "mod" - // import {a as A } from "mod"; - // import * as NS from "mod" - // import d, {a, b as B} from "mod" - // import i = require("mod"); - // import("mod"); - - // export * from "mod" - // export {a as b} from "mod" - // export import i = require("mod") - // (for JavaScript files) require("mod") - - // Do not look for: - // AnySymbol.import("mod") - // AnySymbol.nested.import("mod") - - while (true) { - if (scanner.getToken() === SyntaxKind.EndOfFileToken) { - break; - } + function processImports(): void { + scanner.setText(sourceText); + nextToken(); + // Look for: + // import "mod"; + // import d from "mod" + // import {a as A } from "mod"; + // import * as NS from "mod" + // import d, {a, b as B} from "mod" + // import i = require("mod"); + // import("mod"); + + // export * from "mod" + // export {a as b} from "mod" + // export import i = require("mod") + // (for JavaScript files) require("mod") + + // Do not look for: + // AnySymbol.import("mod") + // AnySymbol.nested.import("mod") + + while (true) { + if (scanner.getToken() === SyntaxKind.EndOfFileToken) { + break; + } - if (scanner.getToken() === SyntaxKind.TemplateHead) { - const stack = [scanner.getToken()]; - loop: while (length(stack)) { - const token = scanner.scan(); - switch (token) { - case SyntaxKind.EndOfFileToken: - break loop; - case SyntaxKind.ImportKeyword: - tryConsumeImport(); - break; - case SyntaxKind.TemplateHead: + if (scanner.getToken() === SyntaxKind.TemplateHead) { + const stack = [scanner.getToken()]; + loop: while (length(stack)) { + const token = scanner.scan(); + switch (token) { + case SyntaxKind.EndOfFileToken: + break loop; + case SyntaxKind.ImportKeyword: + tryConsumeImport(); + break; + case SyntaxKind.TemplateHead: + stack.push(token); + break; + case SyntaxKind.OpenBraceToken: + if (length(stack)) { stack.push(token); - break; - case SyntaxKind.OpenBraceToken: - if (length(stack)) { - stack.push(token); - } - break; - case SyntaxKind.CloseBraceToken: - if (length(stack)) { - if (lastOrUndefined(stack) === SyntaxKind.TemplateHead) { - if (scanner.reScanTemplateToken(/* isTaggedTemplate */ false) === SyntaxKind.TemplateTail) { - stack.pop(); - } - } - else { + } + break; + case SyntaxKind.CloseBraceToken: + if (length(stack)) { + if (lastOrUndefined(stack) === SyntaxKind.TemplateHead) { + if (scanner.reScanTemplateToken(/* isTaggedTemplate */ false) === SyntaxKind.TemplateTail) { stack.pop(); } } - break; - } + else { + stack.pop(); + } + } + break; } - nextToken(); - } - - // check if at least one of alternative have moved scanner forward - if (tryConsumeDeclare() || - tryConsumeImport() || - tryConsumeExport() || - (detectJavaScriptImports && ( - tryConsumeRequireCall(/*skipCurrentToken*/ false, /*allowTemplateLiterals*/ true) || - tryConsumeDefine() - ))) { - continue; - } - else { - nextToken(); } + nextToken(); } - scanner.setText(undefined); + // check if at least one of alternative have moved scanner forward + if (tryConsumeDeclare() || + tryConsumeImport() || + tryConsumeExport() || + (detectJavaScriptImports && ( + tryConsumeRequireCall(/*skipCurrentToken*/ false, /*allowTemplateLiterals*/ true) || + tryConsumeDefine() + ))) { + continue; + } + else { + nextToken(); + } } - if (readImportFiles) { - processImports(); - } - processCommentPragmas(pragmaContext, sourceText); - processPragmasIntoFields(pragmaContext, noop); - if (externalModule) { - // for external modules module all nested ambient modules are augmentations - if (ambientExternalModules) { - // move all detected ambient modules to imported files since they need to be resolved - for (const decl of ambientExternalModules) { - importedFiles.push(decl.ref); - } + scanner.setText(undefined); + } + + if (readImportFiles) { + processImports(); + } + processCommentPragmas(pragmaContext, sourceText); + processPragmasIntoFields(pragmaContext, noop); + if (externalModule) { + // for external modules module all nested ambient modules are augmentations + if (ambientExternalModules) { + // move all detected ambient modules to imported files since they need to be resolved + for (const decl of ambientExternalModules) { + importedFiles.push(decl.ref); } - return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: undefined }; } - else { - // for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0 - let ambientModuleNames: string[] | undefined; - if (ambientExternalModules) { - for (const decl of ambientExternalModules) { - if (decl.depth === 0) { - if (!ambientModuleNames) { - ambientModuleNames = []; - } - ambientModuleNames.push(decl.ref.fileName); - } - else { - importedFiles.push(decl.ref); + return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: undefined }; + } + else { + // for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0 + let ambientModuleNames: string[] | undefined; + if (ambientExternalModules) { + for (const decl of ambientExternalModules) { + if (decl.depth === 0) { + if (!ambientModuleNames) { + ambientModuleNames = []; } + ambientModuleNames.push(decl.ref.fileName); + } + else { + importedFiles.push(decl.ref); } } - return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: ambientModuleNames }; } + return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: ambientModuleNames }; } } diff --git a/src/services/refactorProvider.ts b/src/services/refactorProvider.ts index ccc6f81d2924d..3fb1751875d36 100644 --- a/src/services/refactorProvider.ts +++ b/src/services/refactorProvider.ts @@ -1,23 +1,31 @@ -/* @internal */ -namespace ts.refactor { - // A map with the refactor code as key, the refactor itself as value - // e.g. nonSuggestableRefactors[refactorCode] -> the refactor you want - const refactors = new Map(); +import { + ApplicableRefactorInfo, arrayFrom, flatMapIterator, Map, Refactor, RefactorContext, RefactorEditInfo, +} from "./_namespaces/ts"; +import { refactorKindBeginsWith } from "./_namespaces/ts.refactor"; - /** @param name An unique code associated with each refactor. Does not have to be human-readable. */ - export function registerRefactor(name: string, refactor: Refactor) { - refactors.set(name, refactor); - } +// A map with the refactor code as key, the refactor itself as value +// e.g. nonSuggestableRefactors[refactorCode] -> the refactor you want +const refactors = new Map(); - export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] { - return arrayFrom(flatMapIterator(refactors.values(), refactor => - context.cancellationToken && context.cancellationToken.isCancellationRequested() || - !refactor.kinds?.some(kind => refactorKindBeginsWith(kind, context.kind)) ? undefined : - refactor.getAvailableActions(context))); - } +/** + * @param name An unique code associated with each refactor. Does not have to be human-readable. + * + * @internal + */ +export function registerRefactor(name: string, refactor: Refactor) { + refactors.set(name, refactor); +} + +/** @internal */ +export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] { + return arrayFrom(flatMapIterator(refactors.values(), refactor => + context.cancellationToken && context.cancellationToken.isCancellationRequested() || + !refactor.kinds?.some(kind => refactorKindBeginsWith(kind, context.kind)) ? undefined : + refactor.getAvailableActions(context))); +} - export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined { - const refactor = refactors.get(refactorName); - return refactor && refactor.getEditsForAction(context, actionName); - } +/** @internal */ +export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined { + const refactor = refactors.get(refactorName); + return refactor && refactor.getEditsForAction(context, actionName); } diff --git a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts index aba1a33664085..d0548b1d785ab 100644 --- a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts +++ b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts @@ -1,121 +1,129 @@ -/* @internal */ -namespace ts.refactor.addOrRemoveBracesToArrowFunction { - const refactorName = "Add or remove braces in an arrow function"; - const refactorDescription = Diagnostics.Add_or_remove_braces_in_an_arrow_function.message; - - const addBracesAction = { - name: "Add braces to arrow function", - description: Diagnostics.Add_braces_to_arrow_function.message, - kind: "refactor.rewrite.arrow.braces.add", - }; - const removeBracesAction = { - name: "Remove braces from arrow function", - description: Diagnostics.Remove_braces_from_arrow_function.message, - kind: "refactor.rewrite.arrow.braces.remove" - }; - registerRefactor(refactorName, { - kinds: [removeBracesAction.kind], - getEditsForAction: getRefactorEditsToRemoveFunctionBraces, - getAvailableActions: getRefactorActionsToRemoveFunctionBraces - }); +import { + ApplicableRefactorInfo, ArrowFunction, ConciseBody, copyLeadingComments, copyTrailingAsLeadingComments, + copyTrailingComments, Debug, Diagnostics, emptyArray, Expression, factory, first, getContainingFunction, + getLocaleSpecificMessage, getTokenAtPosition, isArrowFunction, isBlock, isExpression, isReturnStatement, + needsParentheses, rangeContainsRange, RefactorContext, RefactorEditInfo, ReturnStatement, SourceFile, SyntaxKind, + textChanges, +} from "../_namespaces/ts"; +import { + isRefactorErrorInfo, RefactorErrorInfo, refactorKindBeginsWith, registerRefactor, +} from "../_namespaces/ts.refactor"; + +const refactorName = "Add or remove braces in an arrow function"; +const refactorDescription = Diagnostics.Add_or_remove_braces_in_an_arrow_function.message; + +const addBracesAction = { + name: "Add braces to arrow function", + description: Diagnostics.Add_braces_to_arrow_function.message, + kind: "refactor.rewrite.arrow.braces.add", +}; +const removeBracesAction = { + name: "Remove braces from arrow function", + description: Diagnostics.Remove_braces_from_arrow_function.message, + kind: "refactor.rewrite.arrow.braces.remove" +}; +registerRefactor(refactorName, { + kinds: [removeBracesAction.kind], + getEditsForAction: getRefactorEditsToRemoveFunctionBraces, + getAvailableActions: getRefactorActionsToRemoveFunctionBraces +}); + +interface FunctionBracesInfo { + func: ArrowFunction; + expression: Expression | undefined; + returnStatement?: ReturnStatement; + addBraces: boolean; +} - interface FunctionBracesInfo { - func: ArrowFunction; - expression: Expression | undefined; - returnStatement?: ReturnStatement; - addBraces: boolean; +function getRefactorActionsToRemoveFunctionBraces(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const { file, startPosition, triggerReason } = context; + const info = getConvertibleArrowFunctionAtPosition(file, startPosition, triggerReason === "invoked"); + if (!info) return emptyArray; + + if (!isRefactorErrorInfo(info)) { + return [{ + name: refactorName, + description: refactorDescription, + actions: [ + info.addBraces ? addBracesAction : removeBracesAction + ] + }]; } - function getRefactorActionsToRemoveFunctionBraces(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const { file, startPosition, triggerReason } = context; - const info = getConvertibleArrowFunctionAtPosition(file, startPosition, triggerReason === "invoked"); - if (!info) return emptyArray; - - if (!isRefactorErrorInfo(info)) { - return [{ - name: refactorName, - description: refactorDescription, - actions: [ - info.addBraces ? addBracesAction : removeBracesAction - ] - }]; - } - - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ - name: refactorName, - description: refactorDescription, - actions: [ - { ...addBracesAction, notApplicableReason: info.error }, - { ...removeBracesAction, notApplicableReason: info.error }, - ] - }]; - } - - return emptyArray; + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: refactorName, + description: refactorDescription, + actions: [ + { ...addBracesAction, notApplicableReason: info.error }, + { ...removeBracesAction, notApplicableReason: info.error }, + ] + }]; } - function getRefactorEditsToRemoveFunctionBraces(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const { file, startPosition } = context; - const info = getConvertibleArrowFunctionAtPosition(file, startPosition); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - - const { expression, returnStatement, func } = info; + return emptyArray; +} - let body: ConciseBody; +function getRefactorEditsToRemoveFunctionBraces(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const { file, startPosition } = context; + const info = getConvertibleArrowFunctionAtPosition(file, startPosition); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - if (actionName === addBracesAction.name) { - const returnStatement = factory.createReturnStatement(expression); - body = factory.createBlock([returnStatement], /* multiLine */ true); - copyLeadingComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true); - } - else if (actionName === removeBracesAction.name && returnStatement) { - const actualExpression = expression || factory.createVoidZero(); - body = needsParentheses(actualExpression) ? factory.createParenthesizedExpression(actualExpression) : actualExpression; - copyTrailingAsLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - copyLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - copyTrailingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - } - else { - Debug.fail("invalid action"); - } + const { expression, returnStatement, func } = info; - const edits = textChanges.ChangeTracker.with(context, t => { - t.replaceNode(file, func.body, body); - }); + let body: ConciseBody; - return { renameFilename: undefined, renameLocation: undefined, edits }; + if (actionName === addBracesAction.name) { + const returnStatement = factory.createReturnStatement(expression); + body = factory.createBlock([returnStatement], /* multiLine */ true); + copyLeadingComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true); + } + else if (actionName === removeBracesAction.name && returnStatement) { + const actualExpression = expression || factory.createVoidZero(); + body = needsParentheses(actualExpression) ? factory.createParenthesizedExpression(actualExpression) : actualExpression; + copyTrailingAsLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); + copyLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); + copyTrailingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); + } + else { + Debug.fail("invalid action"); } - function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, considerFunctionBodies = true, kind?: string): FunctionBracesInfo | RefactorErrorInfo | undefined { - const node = getTokenAtPosition(file, startPosition); - const func = getContainingFunction(node); + const edits = textChanges.ChangeTracker.with(context, t => { + t.replaceNode(file, func.body, body); + }); - if (!func) { - return { - error: getLocaleSpecificMessage(Diagnostics.Could_not_find_a_containing_arrow_function) - }; - } + return { renameFilename: undefined, renameLocation: undefined, edits }; +} - if (!isArrowFunction(func)) { - return { - error: getLocaleSpecificMessage(Diagnostics.Containing_function_is_not_an_arrow_function) - }; - } +function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, considerFunctionBodies = true, kind?: string): FunctionBracesInfo | RefactorErrorInfo | undefined { + const node = getTokenAtPosition(file, startPosition); + const func = getContainingFunction(node); - if ((!rangeContainsRange(func, node) || rangeContainsRange(func.body, node) && !considerFunctionBodies)) { - return undefined; - } + if (!func) { + return { + error: getLocaleSpecificMessage(Diagnostics.Could_not_find_a_containing_arrow_function) + }; + } - if (refactorKindBeginsWith(addBracesAction.kind, kind) && isExpression(func.body)) { - return { func, addBraces: true, expression: func.body }; - } - else if (refactorKindBeginsWith(removeBracesAction.kind, kind) && isBlock(func.body) && func.body.statements.length === 1) { - const firstStatement = first(func.body.statements); - if (isReturnStatement(firstStatement)) { - return { func, addBraces: false, expression: firstStatement.expression, returnStatement: firstStatement }; - } - } + if (!isArrowFunction(func)) { + return { + error: getLocaleSpecificMessage(Diagnostics.Containing_function_is_not_an_arrow_function) + }; + } + + if ((!rangeContainsRange(func, node) || rangeContainsRange(func.body, node) && !considerFunctionBodies)) { return undefined; } + + if (refactorKindBeginsWith(addBracesAction.kind, kind) && isExpression(func.body)) { + return { func, addBraces: true, expression: func.body }; + } + else if (refactorKindBeginsWith(removeBracesAction.kind, kind) && isBlock(func.body) && func.body.statements.length === 1) { + const firstStatement = first(func.body.statements); + if (isReturnStatement(firstStatement)) { + return { func, addBraces: false, expression: firstStatement.expression, returnStatement: firstStatement }; + } + } + return undefined; } diff --git a/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts b/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts index 649514a637b48..416f7caf3c1b1 100644 --- a/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts +++ b/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts @@ -1,258 +1,268 @@ -/* @internal */ -namespace ts.refactor.convertArrowFunctionOrFunctionExpression { - const refactorName = "Convert arrow function or function expression"; - const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_arrow_function_or_function_expression); - - const toAnonymousFunctionAction = { - name: "Convert to anonymous function", - description: getLocaleSpecificMessage(Diagnostics.Convert_to_anonymous_function), - kind: "refactor.rewrite.function.anonymous", - }; - const toNamedFunctionAction = { - name: "Convert to named function", - description: getLocaleSpecificMessage(Diagnostics.Convert_to_named_function), - kind: "refactor.rewrite.function.named", - }; - const toArrowFunctionAction = { - name: "Convert to arrow function", - description: getLocaleSpecificMessage(Diagnostics.Convert_to_arrow_function), - kind: "refactor.rewrite.function.arrow", - }; - registerRefactor(refactorName, { - kinds: [ - toAnonymousFunctionAction.kind, - toNamedFunctionAction.kind, - toArrowFunctionAction.kind - ], - getEditsForAction: getRefactorEditsToConvertFunctionExpressions, - getAvailableActions: getRefactorActionsToConvertFunctionExpressions - }); +import { + ApplicableRefactorInfo, ArrowFunction, Block, ConciseBody, copyComments, copyTrailingAsLeadingComments, Debug, + Diagnostics, emptyArray, factory, FileTextChanges, FindAllReferences, first, forEachChild, FunctionExpression, + getCombinedModifierFlags, getContainingFunction, getEffectiveModifierFlags, getLocaleSpecificMessage, + getTokenAtPosition, Identifier, isArrowFunction, isClassLike, isExpression, isFunctionDeclaration, + isFunctionExpression, isIdentifier, isReturnStatement, isThis, isVariableDeclaration, + isVariableDeclarationInVariableStatement, isVariableDeclarationList, isVariableStatement, length, ModifierFlags, + Node, Program, rangeContainsRange, RefactorActionInfo, RefactorContext, RefactorEditInfo, ReturnStatement, + SourceFile, Statement, suppressLeadingAndTrailingTrivia, suppressLeadingTrivia, SyntaxKind, textChanges, + TypeChecker, VariableDeclaration, VariableDeclarationList, VariableStatement, +} from "../_namespaces/ts"; +import { refactorKindBeginsWith, registerRefactor } from "../_namespaces/ts.refactor"; + +const refactorName = "Convert arrow function or function expression"; +const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_arrow_function_or_function_expression); + +const toAnonymousFunctionAction = { + name: "Convert to anonymous function", + description: getLocaleSpecificMessage(Diagnostics.Convert_to_anonymous_function), + kind: "refactor.rewrite.function.anonymous", +}; +const toNamedFunctionAction = { + name: "Convert to named function", + description: getLocaleSpecificMessage(Diagnostics.Convert_to_named_function), + kind: "refactor.rewrite.function.named", +}; +const toArrowFunctionAction = { + name: "Convert to arrow function", + description: getLocaleSpecificMessage(Diagnostics.Convert_to_arrow_function), + kind: "refactor.rewrite.function.arrow", +}; +registerRefactor(refactorName, { + kinds: [ + toAnonymousFunctionAction.kind, + toNamedFunctionAction.kind, + toArrowFunctionAction.kind + ], + getEditsForAction: getRefactorEditsToConvertFunctionExpressions, + getAvailableActions: getRefactorActionsToConvertFunctionExpressions +}); + +interface FunctionInfo { + readonly selectedVariableDeclaration: boolean; + readonly func: FunctionExpression | ArrowFunction; +} - interface FunctionInfo { - readonly selectedVariableDeclaration: boolean; - readonly func: FunctionExpression | ArrowFunction; - } +interface VariableInfo { + readonly variableDeclaration: VariableDeclaration; + readonly variableDeclarationList: VariableDeclarationList; + readonly statement: VariableStatement; + readonly name: Identifier; +} - interface VariableInfo { - readonly variableDeclaration: VariableDeclaration; - readonly variableDeclarationList: VariableDeclarationList; - readonly statement: VariableStatement; - readonly name: Identifier; +function getRefactorActionsToConvertFunctionExpressions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const { file, startPosition, program, kind } = context; + const info = getFunctionInfo(file, startPosition, program); + + if (!info) return emptyArray; + const { selectedVariableDeclaration, func } = info; + const possibleActions: RefactorActionInfo[] = []; + const errors: RefactorActionInfo[] = []; + if (refactorKindBeginsWith(toNamedFunctionAction.kind, kind)) { + const error = selectedVariableDeclaration || (isArrowFunction(func) && isVariableDeclaration(func.parent)) ? + undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_named_function); + if (error) { + errors.push({ ...toNamedFunctionAction, notApplicableReason: error }); + } + else { + possibleActions.push(toNamedFunctionAction); + } } - function getRefactorActionsToConvertFunctionExpressions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const { file, startPosition, program, kind } = context; - const info = getFunctionInfo(file, startPosition, program); - - if (!info) return emptyArray; - const { selectedVariableDeclaration, func } = info; - const possibleActions: RefactorActionInfo[] = []; - const errors: RefactorActionInfo[] = []; - if (refactorKindBeginsWith(toNamedFunctionAction.kind, kind)) { - const error = selectedVariableDeclaration || (isArrowFunction(func) && isVariableDeclaration(func.parent)) ? - undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_named_function); - if (error) { - errors.push({ ...toNamedFunctionAction, notApplicableReason: error }); - } - else { - possibleActions.push(toNamedFunctionAction); - } + if (refactorKindBeginsWith(toAnonymousFunctionAction.kind, kind)) { + const error = !selectedVariableDeclaration && isArrowFunction(func) ? + undefined: getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_anonymous_function); + if (error) { + errors.push({ ...toAnonymousFunctionAction, notApplicableReason: error }); } - - if (refactorKindBeginsWith(toAnonymousFunctionAction.kind, kind)) { - const error = !selectedVariableDeclaration && isArrowFunction(func) ? - undefined: getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_anonymous_function); - if (error) { - errors.push({ ...toAnonymousFunctionAction, notApplicableReason: error }); - } - else { - possibleActions.push(toAnonymousFunctionAction); - } + else { + possibleActions.push(toAnonymousFunctionAction); } + } - if (refactorKindBeginsWith(toArrowFunctionAction.kind, kind)) { - const error = isFunctionExpression(func) ? undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_arrow_function); - if (error) { - errors.push({ ...toArrowFunctionAction, notApplicableReason: error }); - } - else { - possibleActions.push(toArrowFunctionAction); - } + if (refactorKindBeginsWith(toArrowFunctionAction.kind, kind)) { + const error = isFunctionExpression(func) ? undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_arrow_function); + if (error) { + errors.push({ ...toArrowFunctionAction, notApplicableReason: error }); + } + else { + possibleActions.push(toArrowFunctionAction); } - - return [{ - name: refactorName, - description: refactorDescription, - actions: possibleActions.length === 0 && context.preferences.provideRefactorNotApplicableReason ? - errors : possibleActions - }]; } - function getRefactorEditsToConvertFunctionExpressions(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const { file, startPosition, program } = context; - const info = getFunctionInfo(file, startPosition, program); + return [{ + name: refactorName, + description: refactorDescription, + actions: possibleActions.length === 0 && context.preferences.provideRefactorNotApplicableReason ? + errors : possibleActions + }]; +} - if (!info) return undefined; - const { func } = info; - const edits: FileTextChanges[] = []; +function getRefactorEditsToConvertFunctionExpressions(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const { file, startPosition, program } = context; + const info = getFunctionInfo(file, startPosition, program); - switch (actionName) { - case toAnonymousFunctionAction.name: - edits.push(...getEditInfoForConvertToAnonymousFunction(context, func)); - break; + if (!info) return undefined; + const { func } = info; + const edits: FileTextChanges[] = []; - case toNamedFunctionAction.name: - const variableInfo = getVariableInfo(func); - if (!variableInfo) return undefined; + switch (actionName) { + case toAnonymousFunctionAction.name: + edits.push(...getEditInfoForConvertToAnonymousFunction(context, func)); + break; - edits.push(...getEditInfoForConvertToNamedFunction(context, func, variableInfo)); - break; + case toNamedFunctionAction.name: + const variableInfo = getVariableInfo(func); + if (!variableInfo) return undefined; - case toArrowFunctionAction.name: - if (!isFunctionExpression(func)) return undefined; - edits.push(...getEditInfoForConvertToArrowFunction(context, func)); - break; + edits.push(...getEditInfoForConvertToNamedFunction(context, func, variableInfo)); + break; - default: - return Debug.fail("invalid action"); - } + case toArrowFunctionAction.name: + if (!isFunctionExpression(func)) return undefined; + edits.push(...getEditInfoForConvertToArrowFunction(context, func)); + break; - return { renameFilename: undefined, renameLocation: undefined, edits }; + default: + return Debug.fail("invalid action"); } - function containingThis(node: Node): boolean { - let containsThis = false; - node.forEachChild(function checkThis(child) { - - if (isThis(child)) { - containsThis = true; - return; - } + return { renameFilename: undefined, renameLocation: undefined, edits }; +} - if (!isClassLike(child) && !isFunctionDeclaration(child) && !isFunctionExpression(child)) { - forEachChild(child, checkThis); - } - }); +function containingThis(node: Node): boolean { + let containsThis = false; + node.forEachChild(function checkThis(child) { - return containsThis; - } - - function getFunctionInfo(file: SourceFile, startPosition: number, program: Program): FunctionInfo | undefined { - const token = getTokenAtPosition(file, startPosition); - const typeChecker = program.getTypeChecker(); - const func = tryGetFunctionFromVariableDeclaration(file, typeChecker, token.parent); - if (func && !containingThis(func.body) && !typeChecker.containsArgumentsReference(func)) { - return { selectedVariableDeclaration: true, func }; + if (isThis(child)) { + containsThis = true; + return; } - const maybeFunc = getContainingFunction(token); - if ( - maybeFunc && - (isFunctionExpression(maybeFunc) || isArrowFunction(maybeFunc)) && - !rangeContainsRange(maybeFunc.body, token) && - !containingThis(maybeFunc.body) && - !typeChecker.containsArgumentsReference(maybeFunc) - ) { - if (isFunctionExpression(maybeFunc) && isFunctionReferencedInFile(file, typeChecker, maybeFunc)) return undefined; - return { selectedVariableDeclaration: false, func: maybeFunc }; + if (!isClassLike(child) && !isFunctionDeclaration(child) && !isFunctionExpression(child)) { + forEachChild(child, checkThis); } + }); - return undefined; + return containsThis; +} + +function getFunctionInfo(file: SourceFile, startPosition: number, program: Program): FunctionInfo | undefined { + const token = getTokenAtPosition(file, startPosition); + const typeChecker = program.getTypeChecker(); + const func = tryGetFunctionFromVariableDeclaration(file, typeChecker, token.parent); + if (func && !containingThis(func.body) && !typeChecker.containsArgumentsReference(func)) { + return { selectedVariableDeclaration: true, func }; } - function isSingleVariableDeclaration(parent: Node): parent is VariableDeclarationList { - return isVariableDeclaration(parent) || (isVariableDeclarationList(parent) && parent.declarations.length === 1); + const maybeFunc = getContainingFunction(token); + if ( + maybeFunc && + (isFunctionExpression(maybeFunc) || isArrowFunction(maybeFunc)) && + !rangeContainsRange(maybeFunc.body, token) && + !containingThis(maybeFunc.body) && + !typeChecker.containsArgumentsReference(maybeFunc) + ) { + if (isFunctionExpression(maybeFunc) && isFunctionReferencedInFile(file, typeChecker, maybeFunc)) return undefined; + return { selectedVariableDeclaration: false, func: maybeFunc }; } - function tryGetFunctionFromVariableDeclaration(sourceFile: SourceFile, typeChecker: TypeChecker, parent: Node): ArrowFunction | FunctionExpression | undefined { - if (!isSingleVariableDeclaration(parent)) { - return undefined; - } - const variableDeclaration = isVariableDeclaration(parent) ? parent : first(parent.declarations); - const initializer = variableDeclaration.initializer; - if (initializer && (isArrowFunction(initializer) || isFunctionExpression(initializer) && !isFunctionReferencedInFile(sourceFile, typeChecker, initializer))) { - return initializer; - } + return undefined; +} + +function isSingleVariableDeclaration(parent: Node): parent is VariableDeclarationList { + return isVariableDeclaration(parent) || (isVariableDeclarationList(parent) && parent.declarations.length === 1); +} + +function tryGetFunctionFromVariableDeclaration(sourceFile: SourceFile, typeChecker: TypeChecker, parent: Node): ArrowFunction | FunctionExpression | undefined { + if (!isSingleVariableDeclaration(parent)) { return undefined; } + const variableDeclaration = isVariableDeclaration(parent) ? parent : first(parent.declarations); + const initializer = variableDeclaration.initializer; + if (initializer && (isArrowFunction(initializer) || isFunctionExpression(initializer) && !isFunctionReferencedInFile(sourceFile, typeChecker, initializer))) { + return initializer; + } + return undefined; +} - function convertToBlock(body: ConciseBody): Block { - if (isExpression(body)) { - const returnStatement = factory.createReturnStatement(body); - const file = body.getSourceFile(); - suppressLeadingAndTrailingTrivia(returnStatement); - copyTrailingAsLeadingComments(body, returnStatement, file, /* commentKind */ undefined, /* hasTrailingNewLine */ true); - return factory.createBlock([returnStatement], /* multiLine */ true); - } - else { - return body; - } +function convertToBlock(body: ConciseBody): Block { + if (isExpression(body)) { + const returnStatement = factory.createReturnStatement(body); + const file = body.getSourceFile(); + suppressLeadingAndTrailingTrivia(returnStatement); + copyTrailingAsLeadingComments(body, returnStatement, file, /* commentKind */ undefined, /* hasTrailingNewLine */ true); + return factory.createBlock([returnStatement], /* multiLine */ true); + } + else { + return body; } +} - function getVariableInfo(func: FunctionExpression | ArrowFunction): VariableInfo | undefined { - const variableDeclaration = func.parent; - if (!isVariableDeclaration(variableDeclaration) || !isVariableDeclarationInVariableStatement(variableDeclaration)) return undefined; +function getVariableInfo(func: FunctionExpression | ArrowFunction): VariableInfo | undefined { + const variableDeclaration = func.parent; + if (!isVariableDeclaration(variableDeclaration) || !isVariableDeclarationInVariableStatement(variableDeclaration)) return undefined; - const variableDeclarationList = variableDeclaration.parent; - const statement = variableDeclarationList.parent; - if (!isVariableDeclarationList(variableDeclarationList) || !isVariableStatement(statement) || !isIdentifier(variableDeclaration.name)) return undefined; + const variableDeclarationList = variableDeclaration.parent; + const statement = variableDeclarationList.parent; + if (!isVariableDeclarationList(variableDeclarationList) || !isVariableStatement(statement) || !isIdentifier(variableDeclaration.name)) return undefined; - return { variableDeclaration, variableDeclarationList, statement, name: variableDeclaration.name }; - } + return { variableDeclaration, variableDeclarationList, statement, name: variableDeclaration.name }; +} - function getEditInfoForConvertToAnonymousFunction(context: RefactorContext, func: FunctionExpression | ArrowFunction): FileTextChanges[] { - const { file } = context; - const body = convertToBlock(func.body); - const newNode = factory.createFunctionExpression(func.modifiers, func.asteriskToken, /* name */ undefined, func.typeParameters, func.parameters, func.type, body); - return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func, newNode)); - } +function getEditInfoForConvertToAnonymousFunction(context: RefactorContext, func: FunctionExpression | ArrowFunction): FileTextChanges[] { + const { file } = context; + const body = convertToBlock(func.body); + const newNode = factory.createFunctionExpression(func.modifiers, func.asteriskToken, /* name */ undefined, func.typeParameters, func.parameters, func.type, body); + return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func, newNode)); +} - function getEditInfoForConvertToNamedFunction(context: RefactorContext, func: FunctionExpression | ArrowFunction, variableInfo: VariableInfo): FileTextChanges[] { - const { file } = context; - const body = convertToBlock(func.body); +function getEditInfoForConvertToNamedFunction(context: RefactorContext, func: FunctionExpression | ArrowFunction, variableInfo: VariableInfo): FileTextChanges[] { + const { file } = context; + const body = convertToBlock(func.body); - const { variableDeclaration, variableDeclarationList, statement, name } = variableInfo; - suppressLeadingTrivia(statement); + const { variableDeclaration, variableDeclarationList, statement, name } = variableInfo; + suppressLeadingTrivia(statement); - const modifiersFlags = (getCombinedModifierFlags(variableDeclaration) & ModifierFlags.Export) | getEffectiveModifierFlags(func); - const modifiers = factory.createModifiersFromModifierFlags(modifiersFlags); - const newNode = factory.createFunctionDeclaration(length(modifiers) ? modifiers : undefined, func.asteriskToken, name, func.typeParameters, func.parameters, func.type, body); + const modifiersFlags = (getCombinedModifierFlags(variableDeclaration) & ModifierFlags.Export) | getEffectiveModifierFlags(func); + const modifiers = factory.createModifiersFromModifierFlags(modifiersFlags); + const newNode = factory.createFunctionDeclaration(length(modifiers) ? modifiers : undefined, func.asteriskToken, name, func.typeParameters, func.parameters, func.type, body); - if (variableDeclarationList.declarations.length === 1) { - return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, statement, newNode)); - } - else { - return textChanges.ChangeTracker.with(context, t => { - t.delete(file, variableDeclaration); - t.insertNodeAfter(file, statement, newNode); - }); - } + if (variableDeclarationList.declarations.length === 1) { + return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, statement, newNode)); } + else { + return textChanges.ChangeTracker.with(context, t => { + t.delete(file, variableDeclaration); + t.insertNodeAfter(file, statement, newNode); + }); + } +} - function getEditInfoForConvertToArrowFunction(context: RefactorContext, func: FunctionExpression): FileTextChanges[] { - const { file } = context; - const statements = func.body.statements; - const head = statements[0]; - let body: ConciseBody; - - if (canBeConvertedToExpression(func.body, head)) { - body = head.expression!; - suppressLeadingAndTrailingTrivia(body); - copyComments(head, body); - } - else { - body = func.body; - } +function getEditInfoForConvertToArrowFunction(context: RefactorContext, func: FunctionExpression): FileTextChanges[] { + const { file } = context; + const statements = func.body.statements; + const head = statements[0]; + let body: ConciseBody; - const newNode = factory.createArrowFunction(func.modifiers, func.typeParameters, func.parameters, func.type, factory.createToken(SyntaxKind.EqualsGreaterThanToken), body); - return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func, newNode)); + if (canBeConvertedToExpression(func.body, head)) { + body = head.expression!; + suppressLeadingAndTrailingTrivia(body); + copyComments(head, body); } - - function canBeConvertedToExpression(body: Block, head: Statement): head is ReturnStatement { - return body.statements.length === 1 && ((isReturnStatement(head) && !!head.expression)); + else { + body = func.body; } - function isFunctionReferencedInFile(sourceFile: SourceFile, typeChecker: TypeChecker, node: FunctionExpression): boolean { - return !!node.name && FindAllReferences.Core.isSymbolReferencedInFile(node.name, typeChecker, sourceFile); - } + const newNode = factory.createArrowFunction(func.modifiers, func.typeParameters, func.parameters, func.type, factory.createToken(SyntaxKind.EqualsGreaterThanToken), body); + return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, func, newNode)); +} + +function canBeConvertedToExpression(body: Block, head: Statement): head is ReturnStatement { + return body.statements.length === 1 && ((isReturnStatement(head) && !!head.expression)); +} + +function isFunctionReferencedInFile(sourceFile: SourceFile, typeChecker: TypeChecker, node: FunctionExpression): boolean { + return !!node.name && FindAllReferences.Core.isSymbolReferencedInFile(node.name, typeChecker, sourceFile); } diff --git a/src/services/refactors/convertExport.ts b/src/services/refactors/convertExport.ts index ffde52b9571ab..d759fdfbffce0 100644 --- a/src/services/refactors/convertExport.ts +++ b/src/services/refactors/convertExport.ts @@ -1,277 +1,286 @@ -/* @internal */ -namespace ts.refactor { - const refactorName = "Convert export"; +import { + ApplicableRefactorInfo, CancellationToken, ClassDeclaration, Debug, Diagnostics, emptyArray, EnumDeclaration, + ExportAssignment, ExportSpecifier, factory, FindAllReferences, findModifier, first, FunctionDeclaration, + getLocaleSpecificMessage, getParentNodeInSpan, getRefactorContextSpan, getSyntacticModifierFlags, + getTokenAtPosition, Identifier, ImportClause, ImportSpecifier, ImportTypeNode, InterfaceDeclaration, + InternalSymbolName, isAmbientModule, isExportAssignment, isExternalModuleAugmentation, isIdentifier, isModuleBlock, + isSourceFile, isStringLiteral, makeImport, ModifierFlags, NamespaceDeclaration, Node, NodeFlags, Program, + PropertyAccessExpression, QuotePreference, quotePreferenceFromString, RefactorContext, RefactorEditInfo, SourceFile, + Symbol, SyntaxKind, textChanges, TypeAliasDeclaration, TypeChecker, VariableStatement, +} from "../_namespaces/ts"; +import { isRefactorErrorInfo, RefactorErrorInfo, registerRefactor } from "../_namespaces/ts.refactor"; - const defaultToNamedAction = { - name: "Convert default export to named export", - description: Diagnostics.Convert_default_export_to_named_export.message, - kind: "refactor.rewrite.export.named" - }; - const namedToDefaultAction = { - name: "Convert named export to default export", - description: Diagnostics.Convert_named_export_to_default_export.message, - kind: "refactor.rewrite.export.default" - }; +const refactorName = "Convert export"; - registerRefactor(refactorName, { - kinds: [ - defaultToNamedAction.kind, - namedToDefaultAction.kind - ], - getAvailableActions: function getRefactorActionsToConvertBetweenNamedAndDefaultExports(context): readonly ApplicableRefactorInfo[] { - const info = getInfo(context, context.triggerReason === "invoked"); - if (!info) return emptyArray; +const defaultToNamedAction = { + name: "Convert default export to named export", + description: Diagnostics.Convert_default_export_to_named_export.message, + kind: "refactor.rewrite.export.named" +}; +const namedToDefaultAction = { + name: "Convert named export to default export", + description: Diagnostics.Convert_named_export_to_default_export.message, + kind: "refactor.rewrite.export.default" +}; - if (!isRefactorErrorInfo(info)) { - const action = info.wasDefault ? defaultToNamedAction : namedToDefaultAction; - return [{ name: refactorName, description: action.description, actions: [action] }]; - } +registerRefactor(refactorName, { + kinds: [ + defaultToNamedAction.kind, + namedToDefaultAction.kind + ], + getAvailableActions: function getRefactorActionsToConvertBetweenNamedAndDefaultExports(context): readonly ApplicableRefactorInfo[] { + const info = getInfo(context, context.triggerReason === "invoked"); + if (!info) return emptyArray; - if (context.preferences.provideRefactorNotApplicableReason) { - return [ - { name: refactorName, description: Diagnostics.Convert_default_export_to_named_export.message, actions: [ - { ...defaultToNamedAction, notApplicableReason: info.error }, - { ...namedToDefaultAction, notApplicableReason: info.error }, - ]} - ]; - } + if (!isRefactorErrorInfo(info)) { + const action = info.wasDefault ? defaultToNamedAction : namedToDefaultAction; + return [{ name: refactorName, description: action.description, actions: [action] }]; + } - return emptyArray; - }, - getEditsForAction: function getRefactorEditsToConvertBetweenNamedAndDefaultExports(context, actionName): RefactorEditInfo { - Debug.assert(actionName === defaultToNamedAction.name || actionName === namedToDefaultAction.name, "Unexpected action name"); - const info = getInfo(context); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, info, t, context.cancellationToken)); - return { edits, renameFilename: undefined, renameLocation: undefined }; - }, - }); + if (context.preferences.provideRefactorNotApplicableReason) { + return [ + { name: refactorName, description: Diagnostics.Convert_default_export_to_named_export.message, actions: [ + { ...defaultToNamedAction, notApplicableReason: info.error }, + { ...namedToDefaultAction, notApplicableReason: info.error }, + ]} + ]; + } - // If a VariableStatement, will have exactly one VariableDeclaration, with an Identifier for a name. - type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement | ExportAssignment; - interface ExportInfo { - readonly exportNode: ExportToConvert; - readonly exportName: Identifier; // This is exportNode.name except for VariableStatement_s. - readonly wasDefault: boolean; - readonly exportingModuleSymbol: Symbol; + return emptyArray; + }, + getEditsForAction: function getRefactorEditsToConvertBetweenNamedAndDefaultExports(context, actionName): RefactorEditInfo { + Debug.assert(actionName === defaultToNamedAction.name || actionName === namedToDefaultAction.name, "Unexpected action name"); + const info = getInfo(context); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); + const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, info, t, context.cancellationToken)); + return { edits, renameFilename: undefined, renameLocation: undefined }; + }, +}); + +// If a VariableStatement, will have exactly one VariableDeclaration, with an Identifier for a name. +type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement | ExportAssignment; +interface ExportInfo { + readonly exportNode: ExportToConvert; + readonly exportName: Identifier; // This is exportNode.name except for VariableStatement_s. + readonly wasDefault: boolean; + readonly exportingModuleSymbol: Symbol; +} + +function getInfo(context: RefactorContext, considerPartialSpans = true): ExportInfo | RefactorErrorInfo | undefined { + const { file, program } = context; + const span = getRefactorContextSpan(context); + const token = getTokenAtPosition(file, span.start); + const exportNode = !!(token.parent && getSyntacticModifierFlags(token.parent) & ModifierFlags.Export) && considerPartialSpans ? token.parent : getParentNodeInSpan(token, file, span); + if (!exportNode || (!isSourceFile(exportNode.parent) && !(isModuleBlock(exportNode.parent) && isAmbientModule(exportNode.parent.parent)))) { + return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_export_statement) }; } - function getInfo(context: RefactorContext, considerPartialSpans = true): ExportInfo | RefactorErrorInfo | undefined { - const { file, program } = context; - const span = getRefactorContextSpan(context); - const token = getTokenAtPosition(file, span.start); - const exportNode = !!(token.parent && getSyntacticModifierFlags(token.parent) & ModifierFlags.Export) && considerPartialSpans ? token.parent : getParentNodeInSpan(token, file, span); - if (!exportNode || (!isSourceFile(exportNode.parent) && !(isModuleBlock(exportNode.parent) && isAmbientModule(exportNode.parent.parent)))) { - return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_export_statement) }; - } + const checker = program.getTypeChecker(); + const exportingModuleSymbol = getExportingModuleSymbol(exportNode, checker); + const flags = getSyntacticModifierFlags(exportNode) || ((isExportAssignment(exportNode) && !exportNode.isExportEquals) ? ModifierFlags.ExportDefault : ModifierFlags.None); - const checker = program.getTypeChecker(); - const exportingModuleSymbol = getExportingModuleSymbol(exportNode, checker); - const flags = getSyntacticModifierFlags(exportNode) || ((isExportAssignment(exportNode) && !exportNode.isExportEquals) ? ModifierFlags.ExportDefault : ModifierFlags.None); + const wasDefault = !!(flags & ModifierFlags.Default); + // If source file already has a default export, don't offer refactor. + if (!(flags & ModifierFlags.Export) || !wasDefault && exportingModuleSymbol.exports!.has(InternalSymbolName.Default)) { + return { error: getLocaleSpecificMessage(Diagnostics.This_file_already_has_a_default_export) }; + } - const wasDefault = !!(flags & ModifierFlags.Default); - // If source file already has a default export, don't offer refactor. - if (!(flags & ModifierFlags.Export) || !wasDefault && exportingModuleSymbol.exports!.has(InternalSymbolName.Default)) { - return { error: getLocaleSpecificMessage(Diagnostics.This_file_already_has_a_default_export) }; + const noSymbolError = (id: Node) => + (isIdentifier(id) && checker.getSymbolAtLocation(id)) ? undefined + : { error: getLocaleSpecificMessage(Diagnostics.Can_only_convert_named_export) }; + + switch (exportNode.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ModuleDeclaration: { + const node = exportNode as FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | TypeAliasDeclaration | NamespaceDeclaration; + if (!node.name) return undefined; + return noSymbolError(node.name) + || { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol }; + } + case SyntaxKind.VariableStatement: { + const vs = exportNode as VariableStatement; + // Must be `export const x = something;`. + if (!(vs.declarationList.flags & NodeFlags.Const) || vs.declarationList.declarations.length !== 1) { + return undefined; + } + const decl = first(vs.declarationList.declarations); + if (!decl.initializer) return undefined; + Debug.assert(!wasDefault, "Can't have a default flag here"); + return noSymbolError(decl.name) + || { exportNode: vs, exportName: decl.name as Identifier, wasDefault, exportingModuleSymbol }; + } + case SyntaxKind.ExportAssignment: { + const node = exportNode as ExportAssignment; + if (node.isExportEquals) return undefined; + return noSymbolError(node.expression) + || { exportNode: node, exportName: node.expression as Identifier, wasDefault, exportingModuleSymbol }; } + default: + return undefined; + } +} - const noSymbolError = (id: Node) => - (isIdentifier(id) && checker.getSymbolAtLocation(id)) ? undefined - : { error: getLocaleSpecificMessage(Diagnostics.Can_only_convert_named_export) }; +function doChange(exportingSourceFile: SourceFile, program: Program, info: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { + changeExport(exportingSourceFile, info, changes, program.getTypeChecker()); + changeImports(program, info, changes, cancellationToken); +} +function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: ExportInfo, changes: textChanges.ChangeTracker, checker: TypeChecker): void { + if (wasDefault) { + if (isExportAssignment(exportNode) && !exportNode.isExportEquals) { + const exp = exportNode.expression as Identifier; + const spec = makeExportSpecifier(exp.text, exp.text); + changes.replaceNode(exportingSourceFile, exportNode, factory.createExportDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([spec]))); + } + else { + changes.delete(exportingSourceFile, Debug.checkDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword), "Should find a default keyword in modifier list")); + } + } + else { + const exportKeyword = Debug.checkDefined(findModifier(exportNode, SyntaxKind.ExportKeyword), "Should find an export keyword in modifier list"); switch (exportNode.kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: + changes.insertNodeAfter(exportingSourceFile, exportKeyword, factory.createToken(SyntaxKind.DefaultKeyword)); + break; + case SyntaxKind.VariableStatement: + // If 'x' isn't used in this file and doesn't have type definition, `export const x = 0;` --> `export default 0;` + const decl = first(exportNode.declarationList.declarations); + if (!FindAllReferences.Core.isSymbolReferencedInFile(exportName, checker, exportingSourceFile) && !decl.type) { + // We checked in `getInfo` that an initializer exists. + changes.replaceNode(exportingSourceFile, exportNode, factory.createExportDefault(Debug.checkDefined(decl.initializer, "Initializer was previously known to be present"))); + break; + } + // falls through case SyntaxKind.EnumDeclaration: case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ModuleDeclaration: { - const node = exportNode as FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | TypeAliasDeclaration | NamespaceDeclaration; - if (!node.name) return undefined; - return noSymbolError(node.name) - || { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol }; - } - case SyntaxKind.VariableStatement: { - const vs = exportNode as VariableStatement; - // Must be `export const x = something;`. - if (!(vs.declarationList.flags & NodeFlags.Const) || vs.declarationList.declarations.length !== 1) { - return undefined; - } - const decl = first(vs.declarationList.declarations); - if (!decl.initializer) return undefined; - Debug.assert(!wasDefault, "Can't have a default flag here"); - return noSymbolError(decl.name) - || { exportNode: vs, exportName: decl.name as Identifier, wasDefault, exportingModuleSymbol }; - } - case SyntaxKind.ExportAssignment: { - const node = exportNode as ExportAssignment; - if (node.isExportEquals) return undefined; - return noSymbolError(node.expression) - || { exportNode: node, exportName: node.expression as Identifier, wasDefault, exportingModuleSymbol }; - } + case SyntaxKind.ModuleDeclaration: + // `export type T = number;` -> `type T = number; export default T;` + changes.deleteModifier(exportingSourceFile, exportKeyword); + changes.insertNodeAfter(exportingSourceFile, exportNode, factory.createExportDefault(factory.createIdentifier(exportName.text))); + break; default: - return undefined; + Debug.fail(`Unexpected exportNode kind ${(exportNode as ExportToConvert).kind}`); } } +} - function doChange(exportingSourceFile: SourceFile, program: Program, info: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { - changeExport(exportingSourceFile, info, changes, program.getTypeChecker()); - changeImports(program, info, changes, cancellationToken); - } - - function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: ExportInfo, changes: textChanges.ChangeTracker, checker: TypeChecker): void { +function changeImports(program: Program, { wasDefault, exportName, exportingModuleSymbol }: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { + const checker = program.getTypeChecker(); + const exportSymbol = Debug.checkDefined(checker.getSymbolAtLocation(exportName), "Export name should resolve to a symbol"); + FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingModuleSymbol, exportName.text, wasDefault, ref => { + if (exportName === ref) return; + const importingSourceFile = ref.getSourceFile(); if (wasDefault) { - if (isExportAssignment(exportNode) && !exportNode.isExportEquals) { - const exp = exportNode.expression as Identifier; - const spec = makeExportSpecifier(exp.text, exp.text); - changes.replaceNode(exportingSourceFile, exportNode, factory.createExportDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([spec]))); - } - else { - changes.delete(exportingSourceFile, Debug.checkDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword), "Should find a default keyword in modifier list")); - } + changeDefaultToNamedImport(importingSourceFile, ref, changes, exportName.text); } else { - const exportKeyword = Debug.checkDefined(findModifier(exportNode, SyntaxKind.ExportKeyword), "Should find an export keyword in modifier list"); - switch (exportNode.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - changes.insertNodeAfter(exportingSourceFile, exportKeyword, factory.createToken(SyntaxKind.DefaultKeyword)); - break; - case SyntaxKind.VariableStatement: - // If 'x' isn't used in this file and doesn't have type definition, `export const x = 0;` --> `export default 0;` - const decl = first(exportNode.declarationList.declarations); - if (!FindAllReferences.Core.isSymbolReferencedInFile(exportName, checker, exportingSourceFile) && !decl.type) { - // We checked in `getInfo` that an initializer exists. - changes.replaceNode(exportingSourceFile, exportNode, factory.createExportDefault(Debug.checkDefined(decl.initializer, "Initializer was previously known to be present"))); - break; - } - // falls through - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ModuleDeclaration: - // `export type T = number;` -> `type T = number; export default T;` - changes.deleteModifier(exportingSourceFile, exportKeyword); - changes.insertNodeAfter(exportingSourceFile, exportNode, factory.createExportDefault(factory.createIdentifier(exportName.text))); - break; - default: - Debug.fail(`Unexpected exportNode kind ${(exportNode as ExportToConvert).kind}`); - } + changeNamedToDefaultImport(importingSourceFile, ref, changes); } - } + }); +} - function changeImports(program: Program, { wasDefault, exportName, exportingModuleSymbol }: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { - const checker = program.getTypeChecker(); - const exportSymbol = Debug.checkDefined(checker.getSymbolAtLocation(exportName), "Export name should resolve to a symbol"); - FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingModuleSymbol, exportName.text, wasDefault, ref => { - if (exportName === ref) return; - const importingSourceFile = ref.getSourceFile(); - if (wasDefault) { - changeDefaultToNamedImport(importingSourceFile, ref, changes, exportName.text); +function changeDefaultToNamedImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker, exportName: string): void { + const { parent } = ref; + switch (parent.kind) { + case SyntaxKind.PropertyAccessExpression: + // `a.default` --> `a.foo` + changes.replaceNode(importingSourceFile, ref, factory.createIdentifier(exportName)); + break; + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: { + const spec = parent as ImportSpecifier | ExportSpecifier; + // `default as foo` --> `foo`, `default as bar` --> `foo as bar` + changes.replaceNode(importingSourceFile, spec, makeImportSpecifier(exportName, spec.name.text)); + break; + } + case SyntaxKind.ImportClause: { + const clause = parent as ImportClause; + Debug.assert(clause.name === ref, "Import clause name should match provided ref"); + const spec = makeImportSpecifier(exportName, ref.text); + const { namedBindings } = clause; + if (!namedBindings) { + // `import foo from "./a";` --> `import { foo } from "./a";` + changes.replaceNode(importingSourceFile, ref, factory.createNamedImports([spec])); + } + else if (namedBindings.kind === SyntaxKind.NamespaceImport) { + // `import foo, * as a from "./a";` --> `import * as a from ".a/"; import { foo } from "./a";` + changes.deleteRange(importingSourceFile, { pos: ref.getStart(importingSourceFile), end: namedBindings.getStart(importingSourceFile) }); + const quotePreference = isStringLiteral(clause.parent.moduleSpecifier) ? quotePreferenceFromString(clause.parent.moduleSpecifier, importingSourceFile) : QuotePreference.Double; + const newImport = makeImport(/*default*/ undefined, [makeImportSpecifier(exportName, ref.text)], clause.parent.moduleSpecifier, quotePreference); + changes.insertNodeAfter(importingSourceFile, clause.parent, newImport); } else { - changeNamedToDefaultImport(importingSourceFile, ref, changes); + // `import foo, { bar } from "./a"` --> `import { bar, foo } from "./a";` + changes.delete(importingSourceFile, ref); + changes.insertNodeAtEndOfList(importingSourceFile, namedBindings.elements, spec); } - }); + break; + } + case SyntaxKind.ImportType: + const importTypeNode = parent as ImportTypeNode; + changes.replaceNode(importingSourceFile, parent, factory.createImportTypeNode(importTypeNode.argument, importTypeNode.assertions, factory.createIdentifier(exportName), importTypeNode.typeArguments, importTypeNode.isTypeOf)); + break; + default: + Debug.failBadSyntaxKind(parent); } +} - function changeDefaultToNamedImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker, exportName: string): void { - const { parent } = ref; - switch (parent.kind) { - case SyntaxKind.PropertyAccessExpression: - // `a.default` --> `a.foo` - changes.replaceNode(importingSourceFile, ref, factory.createIdentifier(exportName)); - break; - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: { - const spec = parent as ImportSpecifier | ExportSpecifier; - // `default as foo` --> `foo`, `default as bar` --> `foo as bar` - changes.replaceNode(importingSourceFile, spec, makeImportSpecifier(exportName, spec.name.text)); - break; +function changeNamedToDefaultImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker): void { + const parent = ref.parent as PropertyAccessExpression | ImportSpecifier | ExportSpecifier; + switch (parent.kind) { + case SyntaxKind.PropertyAccessExpression: + // `a.foo` --> `a.default` + changes.replaceNode(importingSourceFile, ref, factory.createIdentifier("default")); + break; + case SyntaxKind.ImportSpecifier: { + // `import { foo } from "./a";` --> `import foo from "./a";` + // `import { foo as bar } from "./a";` --> `import bar from "./a";` + const defaultImport = factory.createIdentifier(parent.name.text); + if (parent.parent.elements.length === 1) { + changes.replaceNode(importingSourceFile, parent.parent, defaultImport); } - case SyntaxKind.ImportClause: { - const clause = parent as ImportClause; - Debug.assert(clause.name === ref, "Import clause name should match provided ref"); - const spec = makeImportSpecifier(exportName, ref.text); - const { namedBindings } = clause; - if (!namedBindings) { - // `import foo from "./a";` --> `import { foo } from "./a";` - changes.replaceNode(importingSourceFile, ref, factory.createNamedImports([spec])); - } - else if (namedBindings.kind === SyntaxKind.NamespaceImport) { - // `import foo, * as a from "./a";` --> `import * as a from ".a/"; import { foo } from "./a";` - changes.deleteRange(importingSourceFile, { pos: ref.getStart(importingSourceFile), end: namedBindings.getStart(importingSourceFile) }); - const quotePreference = isStringLiteral(clause.parent.moduleSpecifier) ? quotePreferenceFromString(clause.parent.moduleSpecifier, importingSourceFile) : QuotePreference.Double; - const newImport = makeImport(/*default*/ undefined, [makeImportSpecifier(exportName, ref.text)], clause.parent.moduleSpecifier, quotePreference); - changes.insertNodeAfter(importingSourceFile, clause.parent, newImport); - } - else { - // `import foo, { bar } from "./a"` --> `import { bar, foo } from "./a";` - changes.delete(importingSourceFile, ref); - changes.insertNodeAtEndOfList(importingSourceFile, namedBindings.elements, spec); - } - break; + else { + changes.delete(importingSourceFile, parent); + changes.insertNodeBefore(importingSourceFile, parent.parent, defaultImport); } - case SyntaxKind.ImportType: - const importTypeNode = parent as ImportTypeNode; - changes.replaceNode(importingSourceFile, parent, factory.createImportTypeNode(importTypeNode.argument, importTypeNode.assertions, factory.createIdentifier(exportName), importTypeNode.typeArguments, importTypeNode.isTypeOf)); - break; - default: - Debug.failBadSyntaxKind(parent); + break; } + case SyntaxKind.ExportSpecifier: { + // `export { foo } from "./a";` --> `export { default as foo } from "./a";` + // `export { foo as bar } from "./a";` --> `export { default as bar } from "./a";` + // `export { foo as default } from "./a";` --> `export { default } from "./a";` + // (Because `export foo from "./a";` isn't valid syntax.) + changes.replaceNode(importingSourceFile, parent, makeExportSpecifier("default", parent.name.text)); + break; + } + default: + Debug.assertNever(parent, `Unexpected parent kind ${(parent as Node).kind}`); } - function changeNamedToDefaultImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker): void { - const parent = ref.parent as PropertyAccessExpression | ImportSpecifier | ExportSpecifier; - switch (parent.kind) { - case SyntaxKind.PropertyAccessExpression: - // `a.foo` --> `a.default` - changes.replaceNode(importingSourceFile, ref, factory.createIdentifier("default")); - break; - case SyntaxKind.ImportSpecifier: { - // `import { foo } from "./a";` --> `import foo from "./a";` - // `import { foo as bar } from "./a";` --> `import bar from "./a";` - const defaultImport = factory.createIdentifier(parent.name.text); - if (parent.parent.elements.length === 1) { - changes.replaceNode(importingSourceFile, parent.parent, defaultImport); - } - else { - changes.delete(importingSourceFile, parent); - changes.insertNodeBefore(importingSourceFile, parent.parent, defaultImport); - } - break; - } - case SyntaxKind.ExportSpecifier: { - // `export { foo } from "./a";` --> `export { default as foo } from "./a";` - // `export { foo as bar } from "./a";` --> `export { default as bar } from "./a";` - // `export { foo as default } from "./a";` --> `export { default } from "./a";` - // (Because `export foo from "./a";` isn't valid syntax.) - changes.replaceNode(importingSourceFile, parent, makeExportSpecifier("default", parent.name.text)); - break; - } - default: - Debug.assertNever(parent, `Unexpected parent kind ${(parent as Node).kind}`); - } +} - } +function makeImportSpecifier(propertyName: string, name: string): ImportSpecifier { + return factory.createImportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)); +} - function makeImportSpecifier(propertyName: string, name: string): ImportSpecifier { - return factory.createImportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)); - } +function makeExportSpecifier(propertyName: string, name: string): ExportSpecifier { + return factory.createExportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)); +} - function makeExportSpecifier(propertyName: string, name: string): ExportSpecifier { - return factory.createExportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)); +function getExportingModuleSymbol(node: Node, checker: TypeChecker) { + const parent = node.parent; + if (isSourceFile(parent)) { + return parent.symbol; } - - function getExportingModuleSymbol(node: Node, checker: TypeChecker) { - const parent = node.parent; - if (isSourceFile(parent)) { - return parent.symbol; - } - const symbol = parent.parent.symbol; - if (symbol.valueDeclaration && isExternalModuleAugmentation(symbol.valueDeclaration)) { - return checker.getMergedSymbol(symbol); - } - return symbol; + const symbol = parent.parent.symbol; + if (symbol.valueDeclaration && isExternalModuleAugmentation(symbol.valueDeclaration)) { + return checker.getMergedSymbol(symbol); } + return symbol; } diff --git a/src/services/refactors/convertImport.ts b/src/services/refactors/convertImport.ts index 5a7bca087a095..40fa29ea4bb52 100644 --- a/src/services/refactors/convertImport.ts +++ b/src/services/refactors/convertImport.ts @@ -1,232 +1,241 @@ -/* @internal */ -namespace ts.refactor { - const refactorName = "Convert import"; - - const actions = { - [ImportKind.Named]: { - name: "Convert namespace import to named imports", - description: Diagnostics.Convert_namespace_import_to_named_imports.message, - kind: "refactor.rewrite.import.named", - }, - [ImportKind.Namespace]: { - name: "Convert named imports to namespace import", - description: Diagnostics.Convert_named_imports_to_namespace_import.message, - kind: "refactor.rewrite.import.namespace", - }, - [ImportKind.Default]: { - name: "Convert named imports to default import", - description: Diagnostics.Convert_named_imports_to_default_import.message, - kind: "refactor.rewrite.import.default", - }, - }; - - registerRefactor(refactorName, { - kinds: getOwnValues(actions).map(a => a.kind), - getAvailableActions: function getRefactorActionsToConvertBetweenNamedAndNamespacedImports(context): readonly ApplicableRefactorInfo[] { - const info = getImportConversionInfo(context, context.triggerReason === "invoked"); - if (!info) return emptyArray; - - if (!isRefactorErrorInfo(info)) { - const action = actions[info.convertTo]; - return [{ name: refactorName, description: action.description, actions: [action] }]; - } - - if (context.preferences.provideRefactorNotApplicableReason) { - return getOwnValues(actions).map(action => ({ - name: refactorName, - description: action.description, - actions: [{ ...action, notApplicableReason: info.error }] - })); - } - - return emptyArray; - }, - getEditsForAction: function getRefactorEditsToConvertBetweenNamedAndNamespacedImports(context, actionName): RefactorEditInfo { - Debug.assert(some(getOwnValues(actions), action => action.name === actionName), "Unexpected action name"); - const info = getImportConversionInfo(context); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, t, info)); - return { edits, renameFilename: undefined, renameLocation: undefined }; - } - }); - - // Can convert imports of the form `import * as m from "m";` or `import d, { x, y } from "m";`. - type ImportConversionInfo = - | { convertTo: ImportKind.Default, import: NamedImports } - | { convertTo: ImportKind.Namespace, import: NamedImports } - | { convertTo: ImportKind.Named, import: NamespaceImport }; - - function getImportConversionInfo(context: RefactorContext, considerPartialSpans = true): ImportConversionInfo | RefactorErrorInfo | undefined { - const { file } = context; - const span = getRefactorContextSpan(context); - const token = getTokenAtPosition(file, span.start); - const importDecl = considerPartialSpans ? findAncestor(token, isImportDeclaration) : getParentNodeInSpan(token, file, span); - if (!importDecl || !isImportDeclaration(importDecl)) return { error: "Selection is not an import declaration." }; - - const end = span.start + span.length; - const nextToken = findNextToken(importDecl, importDecl.parent, file); - if (nextToken && end > nextToken.getStart()) return undefined; - - const { importClause } = importDecl; - if (!importClause) { - return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_import_clause) }; +import { + ApplicableRefactorInfo, arrayFrom, codefix, Debug, Diagnostics, emptyArray, Expression, factory, FindAllReferences, + findAncestor, findNextToken, getAllowSyntheticDefaultImports, getLocaleSpecificMessage, getOwnValues, + getParentNodeInSpan, getRefactorContextSpan, getTokenAtPosition, getUniqueName, Identifier, ImportClause, + ImportDeclaration, ImportKind, ImportSpecifier, isExportSpecifier, isImportDeclaration, isPropertyAccessExpression, + isPropertyAccessOrQualifiedName, isShorthandPropertyAssignment, isStringLiteral, Map, NamedImports, NamespaceImport, + Program, PropertyAccessExpression, QualifiedName, RefactorContext, RefactorEditInfo, ScriptTarget, Set, some, + SourceFile, Symbol, SymbolFlags, SyntaxKind, textChanges, TypeChecker, +} from "../_namespaces/ts"; +import { isRefactorErrorInfo, RefactorErrorInfo, registerRefactor } from "../_namespaces/ts.refactor"; + +const refactorName = "Convert import"; + +const actions = { + [ImportKind.Named]: { + name: "Convert namespace import to named imports", + description: Diagnostics.Convert_namespace_import_to_named_imports.message, + kind: "refactor.rewrite.import.named", + }, + [ImportKind.Namespace]: { + name: "Convert named imports to namespace import", + description: Diagnostics.Convert_named_imports_to_namespace_import.message, + kind: "refactor.rewrite.import.namespace", + }, + [ImportKind.Default]: { + name: "Convert named imports to default import", + description: Diagnostics.Convert_named_imports_to_default_import.message, + kind: "refactor.rewrite.import.default", + }, +}; + +registerRefactor(refactorName, { + kinds: getOwnValues(actions).map(a => a.kind), + getAvailableActions: function getRefactorActionsToConvertBetweenNamedAndNamespacedImports(context): readonly ApplicableRefactorInfo[] { + const info = getImportConversionInfo(context, context.triggerReason === "invoked"); + if (!info) return emptyArray; + + if (!isRefactorErrorInfo(info)) { + const action = actions[info.convertTo]; + return [{ name: refactorName, description: action.description, actions: [action] }]; } - if (!importClause.namedBindings) { - return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_namespace_import_or_named_imports) }; + if (context.preferences.provideRefactorNotApplicableReason) { + return getOwnValues(actions).map(action => ({ + name: refactorName, + description: action.description, + actions: [{ ...action, notApplicableReason: info.error }] + })); } - if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - return { convertTo: ImportKind.Named, import: importClause.namedBindings }; - } - const shouldUseDefault = getShouldUseDefault(context.program, importClause); - - return shouldUseDefault - ? { convertTo: ImportKind.Default, import: importClause.namedBindings } - : { convertTo: ImportKind.Namespace, import: importClause.namedBindings }; + return emptyArray; + }, + getEditsForAction: function getRefactorEditsToConvertBetweenNamedAndNamespacedImports(context, actionName): RefactorEditInfo { + Debug.assert(some(getOwnValues(actions), action => action.name === actionName), "Unexpected action name"); + const info = getImportConversionInfo(context); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); + const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, t, info)); + return { edits, renameFilename: undefined, renameLocation: undefined }; + } +}); + +// Can convert imports of the form `import * as m from "m";` or `import d, { x, y } from "m";`. +type ImportConversionInfo = + | { convertTo: ImportKind.Default, import: NamedImports } + | { convertTo: ImportKind.Namespace, import: NamedImports } + | { convertTo: ImportKind.Named, import: NamespaceImport }; + +function getImportConversionInfo(context: RefactorContext, considerPartialSpans = true): ImportConversionInfo | RefactorErrorInfo | undefined { + const { file } = context; + const span = getRefactorContextSpan(context); + const token = getTokenAtPosition(file, span.start); + const importDecl = considerPartialSpans ? findAncestor(token, isImportDeclaration) : getParentNodeInSpan(token, file, span); + if (!importDecl || !isImportDeclaration(importDecl)) return { error: "Selection is not an import declaration." }; + + const end = span.start + span.length; + const nextToken = findNextToken(importDecl, importDecl.parent, file); + if (nextToken && end > nextToken.getStart()) return undefined; + + const { importClause } = importDecl; + if (!importClause) { + return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_import_clause) }; } - function getShouldUseDefault(program: Program, importClause: ImportClause) { - return getAllowSyntheticDefaultImports(program.getCompilerOptions()) - && isExportEqualsModule(importClause.parent.moduleSpecifier, program.getTypeChecker()); + if (!importClause.namedBindings) { + return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_namespace_import_or_named_imports) }; } - function doChange(sourceFile: SourceFile, program: Program, changes: textChanges.ChangeTracker, info: ImportConversionInfo): void { - const checker = program.getTypeChecker(); - if (info.convertTo === ImportKind.Named) { - doChangeNamespaceToNamed(sourceFile, checker, changes, info.import, getAllowSyntheticDefaultImports(program.getCompilerOptions())); - } - else { - doChangeNamedToNamespaceOrDefault(sourceFile, program, changes, info.import, info.convertTo === ImportKind.Default); - } + if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + return { convertTo: ImportKind.Named, import: importClause.namedBindings }; } + const shouldUseDefault = getShouldUseDefault(context.program, importClause); - function doChangeNamespaceToNamed(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, toConvert: NamespaceImport, allowSyntheticDefaultImports: boolean): void { - let usedAsNamespaceOrDefault = false; + return shouldUseDefault + ? { convertTo: ImportKind.Default, import: importClause.namedBindings } + : { convertTo: ImportKind.Namespace, import: importClause.namedBindings }; +} - const nodesToReplace: (PropertyAccessExpression | QualifiedName)[] = []; - const conflictingNames = new Map(); +function getShouldUseDefault(program: Program, importClause: ImportClause) { + return getAllowSyntheticDefaultImports(program.getCompilerOptions()) + && isExportEqualsModule(importClause.parent.moduleSpecifier, program.getTypeChecker()); +} - FindAllReferences.Core.eachSymbolReferenceInFile(toConvert.name, checker, sourceFile, id => { - if (!isPropertyAccessOrQualifiedName(id.parent)) { - usedAsNamespaceOrDefault = true; - } - else { - const exportName = getRightOfPropertyAccessOrQualifiedName(id.parent).text; - if (checker.resolveName(exportName, id, SymbolFlags.All, /*excludeGlobals*/ true)) { - conflictingNames.set(exportName, true); - } - Debug.assert(getLeftOfPropertyAccessOrQualifiedName(id.parent) === id, "Parent expression should match id"); - nodesToReplace.push(id.parent); - } - }); +function doChange(sourceFile: SourceFile, program: Program, changes: textChanges.ChangeTracker, info: ImportConversionInfo): void { + const checker = program.getTypeChecker(); + if (info.convertTo === ImportKind.Named) { + doChangeNamespaceToNamed(sourceFile, checker, changes, info.import, getAllowSyntheticDefaultImports(program.getCompilerOptions())); + } + else { + doChangeNamedToNamespaceOrDefault(sourceFile, program, changes, info.import, info.convertTo === ImportKind.Default); + } +} + +function doChangeNamespaceToNamed(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, toConvert: NamespaceImport, allowSyntheticDefaultImports: boolean): void { + let usedAsNamespaceOrDefault = false; - // We may need to change `mod.x` to `_x` to avoid a name conflict. - const exportNameToImportName = new Map(); + const nodesToReplace: (PropertyAccessExpression | QualifiedName)[] = []; + const conflictingNames = new Map(); - for (const propertyAccessOrQualifiedName of nodesToReplace) { - const exportName = getRightOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName).text; - let importName = exportNameToImportName.get(exportName); - if (importName === undefined) { - exportNameToImportName.set(exportName, importName = conflictingNames.has(exportName) ? getUniqueName(exportName, sourceFile) : exportName); + FindAllReferences.Core.eachSymbolReferenceInFile(toConvert.name, checker, sourceFile, id => { + if (!isPropertyAccessOrQualifiedName(id.parent)) { + usedAsNamespaceOrDefault = true; + } + else { + const exportName = getRightOfPropertyAccessOrQualifiedName(id.parent).text; + if (checker.resolveName(exportName, id, SymbolFlags.All, /*excludeGlobals*/ true)) { + conflictingNames.set(exportName, true); } - changes.replaceNode(sourceFile, propertyAccessOrQualifiedName, factory.createIdentifier(importName)); + Debug.assert(getLeftOfPropertyAccessOrQualifiedName(id.parent) === id, "Parent expression should match id"); + nodesToReplace.push(id.parent); } + }); - const importSpecifiers: ImportSpecifier[] = []; - exportNameToImportName.forEach((name, propertyName) => { - importSpecifiers.push(factory.createImportSpecifier(/*isTypeOnly*/ false, name === propertyName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name))); - }); + // We may need to change `mod.x` to `_x` to avoid a name conflict. + const exportNameToImportName = new Map(); - const importDecl = toConvert.parent.parent; - if (usedAsNamespaceOrDefault && !allowSyntheticDefaultImports) { - // Need to leave the namespace import alone - changes.insertNodeAfter(sourceFile, importDecl, updateImport(importDecl, /*defaultImportName*/ undefined, importSpecifiers)); - } - else { - changes.replaceNode(sourceFile, importDecl, updateImport(importDecl, usedAsNamespaceOrDefault ? factory.createIdentifier(toConvert.name.text) : undefined, importSpecifiers)); + for (const propertyAccessOrQualifiedName of nodesToReplace) { + const exportName = getRightOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName).text; + let importName = exportNameToImportName.get(exportName); + if (importName === undefined) { + exportNameToImportName.set(exportName, importName = conflictingNames.has(exportName) ? getUniqueName(exportName, sourceFile) : exportName); } + changes.replaceNode(sourceFile, propertyAccessOrQualifiedName, factory.createIdentifier(importName)); } - function getRightOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName: PropertyAccessExpression | QualifiedName) { - return isPropertyAccessExpression(propertyAccessOrQualifiedName) ? propertyAccessOrQualifiedName.name : propertyAccessOrQualifiedName.right; - } + const importSpecifiers: ImportSpecifier[] = []; + exportNameToImportName.forEach((name, propertyName) => { + importSpecifiers.push(factory.createImportSpecifier(/*isTypeOnly*/ false, name === propertyName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name))); + }); - function getLeftOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName: PropertyAccessExpression | QualifiedName) { - return isPropertyAccessExpression(propertyAccessOrQualifiedName) ? propertyAccessOrQualifiedName.expression : propertyAccessOrQualifiedName.left; + const importDecl = toConvert.parent.parent; + if (usedAsNamespaceOrDefault && !allowSyntheticDefaultImports) { + // Need to leave the namespace import alone + changes.insertNodeAfter(sourceFile, importDecl, updateImport(importDecl, /*defaultImportName*/ undefined, importSpecifiers)); + } + else { + changes.replaceNode(sourceFile, importDecl, updateImport(importDecl, usedAsNamespaceOrDefault ? factory.createIdentifier(toConvert.name.text) : undefined, importSpecifiers)); } +} - export function doChangeNamedToNamespaceOrDefault(sourceFile: SourceFile, program: Program, changes: textChanges.ChangeTracker, toConvert: NamedImports, shouldUseDefault = getShouldUseDefault(program, toConvert.parent)): void { - const checker = program.getTypeChecker(); - const importDecl = toConvert.parent.parent; - const { moduleSpecifier } = importDecl; +function getRightOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName: PropertyAccessExpression | QualifiedName) { + return isPropertyAccessExpression(propertyAccessOrQualifiedName) ? propertyAccessOrQualifiedName.name : propertyAccessOrQualifiedName.right; +} - const toConvertSymbols: Set = new Set(); - toConvert.elements.forEach(namedImport => { - const symbol = checker.getSymbolAtLocation(namedImport.name); - if (symbol) { - toConvertSymbols.add(symbol); - } - }); - const preferredName = moduleSpecifier && isStringLiteral(moduleSpecifier) ? codefix.moduleSpecifierToValidIdentifier(moduleSpecifier.text, ScriptTarget.ESNext) : "module"; - function hasNamespaceNameConflict(namedImport: ImportSpecifier): boolean { - // We need to check if the preferred namespace name (`preferredName`) we'd like to use in the refactored code will present a name conflict. - // A name conflict means that, in a scope where we would like to use the preferred namespace name, there already exists a symbol with that name in that scope. - // We are going to use the namespace name in the scopes the named imports being refactored are referenced, - // so we look for conflicts by looking at every reference to those named imports. - return !!FindAllReferences.Core.eachSymbolReferenceInFile(namedImport.name, checker, sourceFile, id => { - const symbol = checker.resolveName(preferredName, id, SymbolFlags.All, /*excludeGlobals*/ true); - if (symbol) { // There already is a symbol with the same name as the preferred namespace name. - if (toConvertSymbols.has(symbol)) { // `preferredName` resolves to a symbol for one of the named import references we are going to transform into namespace import references... - return isExportSpecifier(id.parent); // ...but if this reference is an export specifier, it will not be transformed, so it is a conflict; otherwise, it will be renamed and is not a conflict. - } - return true; // `preferredName` resolves to any other symbol, which will be present in the refactored code and so poses a name conflict. - } - return false; // There is no symbol with the same name as the preferred namespace name, so no conflict. - }); - } - const namespaceNameConflicts = toConvert.elements.some(hasNamespaceNameConflict); - const namespaceImportName = namespaceNameConflicts ? getUniqueName(preferredName, sourceFile) : preferredName; - - // Imports that need to be kept as named imports in the refactored code, to avoid changing the semantics. - // More specifically, those are named imports that appear in named exports in the original code, e.g. `a` in `import { a } from "m"; export { a }`. - const neededNamedImports: Set = new Set(); - - for (const element of toConvert.elements) { - const propertyName = (element.propertyName || element.name).text; - FindAllReferences.Core.eachSymbolReferenceInFile(element.name, checker, sourceFile, id => { - const access = factory.createPropertyAccessExpression(factory.createIdentifier(namespaceImportName), propertyName); - if (isShorthandPropertyAssignment(id.parent)) { - changes.replaceNode(sourceFile, id.parent, factory.createPropertyAssignment(id.text, access)); - } - else if (isExportSpecifier(id.parent)) { - neededNamedImports.add(element); - } - else { - changes.replaceNode(sourceFile, id, access); - } - }); - } +function getLeftOfPropertyAccessOrQualifiedName(propertyAccessOrQualifiedName: PropertyAccessExpression | QualifiedName) { + return isPropertyAccessExpression(propertyAccessOrQualifiedName) ? propertyAccessOrQualifiedName.expression : propertyAccessOrQualifiedName.left; +} - changes.replaceNode(sourceFile, toConvert, shouldUseDefault - ? factory.createIdentifier(namespaceImportName) - : factory.createNamespaceImport(factory.createIdentifier(namespaceImportName))); - if (neededNamedImports.size) { - const newNamedImports: ImportSpecifier[] = arrayFrom(neededNamedImports.values()).map(element => - factory.createImportSpecifier(element.isTypeOnly, element.propertyName && factory.createIdentifier(element.propertyName.text), factory.createIdentifier(element.name.text))); - changes.insertNodeAfter(sourceFile, toConvert.parent.parent, updateImport(importDecl, /*defaultImportName*/ undefined, newNamedImports)); +/** @internal */ +export function doChangeNamedToNamespaceOrDefault(sourceFile: SourceFile, program: Program, changes: textChanges.ChangeTracker, toConvert: NamedImports, shouldUseDefault = getShouldUseDefault(program, toConvert.parent)): void { + const checker = program.getTypeChecker(); + const importDecl = toConvert.parent.parent; + const { moduleSpecifier } = importDecl; + + const toConvertSymbols: Set = new Set(); + toConvert.elements.forEach(namedImport => { + const symbol = checker.getSymbolAtLocation(namedImport.name); + if (symbol) { + toConvertSymbols.add(symbol); } + }); + const preferredName = moduleSpecifier && isStringLiteral(moduleSpecifier) ? codefix.moduleSpecifierToValidIdentifier(moduleSpecifier.text, ScriptTarget.ESNext) : "module"; + function hasNamespaceNameConflict(namedImport: ImportSpecifier): boolean { + // We need to check if the preferred namespace name (`preferredName`) we'd like to use in the refactored code will present a name conflict. + // A name conflict means that, in a scope where we would like to use the preferred namespace name, there already exists a symbol with that name in that scope. + // We are going to use the namespace name in the scopes the named imports being refactored are referenced, + // so we look for conflicts by looking at every reference to those named imports. + return !!FindAllReferences.Core.eachSymbolReferenceInFile(namedImport.name, checker, sourceFile, id => { + const symbol = checker.resolveName(preferredName, id, SymbolFlags.All, /*excludeGlobals*/ true); + if (symbol) { // There already is a symbol with the same name as the preferred namespace name. + if (toConvertSymbols.has(symbol)) { // `preferredName` resolves to a symbol for one of the named import references we are going to transform into namespace import references... + return isExportSpecifier(id.parent); // ...but if this reference is an export specifier, it will not be transformed, so it is a conflict; otherwise, it will be renamed and is not a conflict. + } + return true; // `preferredName` resolves to any other symbol, which will be present in the refactored code and so poses a name conflict. + } + return false; // There is no symbol with the same name as the preferred namespace name, so no conflict. + }); } - - function isExportEqualsModule(moduleSpecifier: Expression, checker: TypeChecker) { - const externalModule = checker.resolveExternalModuleName(moduleSpecifier); - if (!externalModule) return false; - const exportEquals = checker.resolveExternalModuleSymbol(externalModule); - return externalModule !== exportEquals; + const namespaceNameConflicts = toConvert.elements.some(hasNamespaceNameConflict); + const namespaceImportName = namespaceNameConflicts ? getUniqueName(preferredName, sourceFile) : preferredName; + + // Imports that need to be kept as named imports in the refactored code, to avoid changing the semantics. + // More specifically, those are named imports that appear in named exports in the original code, e.g. `a` in `import { a } from "m"; export { a }`. + const neededNamedImports: Set = new Set(); + + for (const element of toConvert.elements) { + const propertyName = (element.propertyName || element.name).text; + FindAllReferences.Core.eachSymbolReferenceInFile(element.name, checker, sourceFile, id => { + const access = factory.createPropertyAccessExpression(factory.createIdentifier(namespaceImportName), propertyName); + if (isShorthandPropertyAssignment(id.parent)) { + changes.replaceNode(sourceFile, id.parent, factory.createPropertyAssignment(id.text, access)); + } + else if (isExportSpecifier(id.parent)) { + neededNamedImports.add(element); + } + else { + changes.replaceNode(sourceFile, id, access); + } + }); } - function updateImport(old: ImportDeclaration, defaultImportName: Identifier | undefined, elements: readonly ImportSpecifier[] | undefined): ImportDeclaration { - return factory.createImportDeclaration(/*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, defaultImportName, elements && elements.length ? factory.createNamedImports(elements) : undefined), old.moduleSpecifier, /*assertClause*/ undefined); + changes.replaceNode(sourceFile, toConvert, shouldUseDefault + ? factory.createIdentifier(namespaceImportName) + : factory.createNamespaceImport(factory.createIdentifier(namespaceImportName))); + if (neededNamedImports.size) { + const newNamedImports: ImportSpecifier[] = arrayFrom(neededNamedImports.values()).map(element => + factory.createImportSpecifier(element.isTypeOnly, element.propertyName && factory.createIdentifier(element.propertyName.text), factory.createIdentifier(element.name.text))); + changes.insertNodeAfter(sourceFile, toConvert.parent.parent, updateImport(importDecl, /*defaultImportName*/ undefined, newNamedImports)); } } + +function isExportEqualsModule(moduleSpecifier: Expression, checker: TypeChecker) { + const externalModule = checker.resolveExternalModuleName(moduleSpecifier); + if (!externalModule) return false; + const exportEquals = checker.resolveExternalModuleSymbol(externalModule); + return externalModule !== exportEquals; +} + +function updateImport(old: ImportDeclaration, defaultImportName: Identifier | undefined, elements: readonly ImportSpecifier[] | undefined): ImportDeclaration { + return factory.createImportDeclaration(/*modifiers*/ undefined, + factory.createImportClause(/*isTypeOnly*/ false, defaultImportName, elements && elements.length ? factory.createNamedImports(elements) : undefined), old.moduleSpecifier, /*assertClause*/ undefined); +} diff --git a/src/services/refactors/convertOverloadListToSingleSignature.ts b/src/services/refactors/convertOverloadListToSingleSignature.ts index a4beb8179045d..ebe0535e0098e 100644 --- a/src/services/refactors/convertOverloadListToSingleSignature.ts +++ b/src/services/refactors/convertOverloadListToSingleSignature.ts @@ -1,226 +1,233 @@ -/* @internal */ -namespace ts.refactor.addOrRemoveBracesToArrowFunction { - const refactorName = "Convert overload list to single signature"; - const refactorDescription = Diagnostics.Convert_overload_list_to_single_signature.message; - - const functionOverloadAction = { +import { + ApplicableRefactorInfo, CallSignatureDeclaration, ConstructorDeclaration, ConstructSignatureDeclaration, Debug, + Diagnostics, displayPartsToString, EmitFlags, emptyArray, every, factory, findAncestor, FunctionDeclaration, + getSourceFileOfNode, getSyntheticLeadingComments, getTokenAtPosition, isFunctionLikeDeclaration, isIdentifier, + length, map, mapDefined, MethodDeclaration, MethodSignature, NamedTupleMember, Node, NodeArray, + ParameterDeclaration, Program, rangeContainsPosition, RefactorContext, RefactorEditInfo, setEmitFlags, + setSyntheticLeadingComments, setTextRange, some, SourceFile, SyntaxKind, textChanges, TupleTypeNode, +} from "../_namespaces/ts"; +import { registerRefactor } from "../_namespaces/ts.refactor"; + +const refactorName = "Convert overload list to single signature"; +const refactorDescription = Diagnostics.Convert_overload_list_to_single_signature.message; + +const functionOverloadAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.function.overloadList", +}; +registerRefactor(refactorName, { + kinds: [functionOverloadAction.kind], + getEditsForAction: getRefactorEditsToConvertOverloadsToOneSignature, + getAvailableActions: getRefactorActionsToConvertOverloadsToOneSignature +}); + +function getRefactorActionsToConvertOverloadsToOneSignature(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const { file, startPosition, program } = context; + const info = getConvertableOverloadListAtPosition(file, startPosition, program); + if (!info) return emptyArray; + + return [{ name: refactorName, description: refactorDescription, - kind: "refactor.rewrite.function.overloadList", - }; - registerRefactor(refactorName, { - kinds: [functionOverloadAction.kind], - getEditsForAction: getRefactorEditsToConvertOverloadsToOneSignature, - getAvailableActions: getRefactorActionsToConvertOverloadsToOneSignature - }); - - function getRefactorActionsToConvertOverloadsToOneSignature(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const { file, startPosition, program } = context; - const info = getConvertableOverloadListAtPosition(file, startPosition, program); - if (!info) return emptyArray; - - return [{ - name: refactorName, - description: refactorDescription, - actions: [functionOverloadAction] - }]; - } - - function getRefactorEditsToConvertOverloadsToOneSignature(context: RefactorContext): RefactorEditInfo | undefined { - const { file, startPosition, program } = context; - const signatureDecls = getConvertableOverloadListAtPosition(file, startPosition, program); - if (!signatureDecls) return undefined; - - const checker = program.getTypeChecker(); - - const lastDeclaration = signatureDecls[signatureDecls.length - 1]; - let updated = lastDeclaration; - switch (lastDeclaration.kind) { - case SyntaxKind.MethodSignature: { - updated = factory.updateMethodSignature( - lastDeclaration, - lastDeclaration.modifiers, - lastDeclaration.name, - lastDeclaration.questionToken, - lastDeclaration.typeParameters, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.type, - ); - break; - } - case SyntaxKind.MethodDeclaration: { - updated = factory.updateMethodDeclaration( - lastDeclaration, - lastDeclaration.modifiers, - lastDeclaration.asteriskToken, - lastDeclaration.name, - lastDeclaration.questionToken, - lastDeclaration.typeParameters, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.type, - lastDeclaration.body - ); - break; - } - case SyntaxKind.CallSignature: { - updated = factory.updateCallSignature( - lastDeclaration, - lastDeclaration.typeParameters, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.type, - ); - break; - } - case SyntaxKind.Constructor: { - updated = factory.updateConstructorDeclaration( - lastDeclaration, - lastDeclaration.modifiers, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.body - ); - break; - } - case SyntaxKind.ConstructSignature: { - updated = factory.updateConstructSignature( - lastDeclaration, - lastDeclaration.typeParameters, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.type, - ); - break; - } - case SyntaxKind.FunctionDeclaration: { - updated = factory.updateFunctionDeclaration( - lastDeclaration, - lastDeclaration.modifiers, - lastDeclaration.asteriskToken, - lastDeclaration.name, - lastDeclaration.typeParameters, - getNewParametersForCombinedSignature(signatureDecls), - lastDeclaration.type, - lastDeclaration.body - ); - break; - } - default: return Debug.failBadSyntaxKind(lastDeclaration, "Unhandled signature kind in overload list conversion refactoring"); - } + actions: [functionOverloadAction] + }]; +} - if (updated === lastDeclaration) { - return; // No edits to apply, do nothing - } +function getRefactorEditsToConvertOverloadsToOneSignature(context: RefactorContext): RefactorEditInfo | undefined { + const { file, startPosition, program } = context; + const signatureDecls = getConvertableOverloadListAtPosition(file, startPosition, program); + if (!signatureDecls) return undefined; + + const checker = program.getTypeChecker(); + + const lastDeclaration = signatureDecls[signatureDecls.length - 1]; + let updated = lastDeclaration; + switch (lastDeclaration.kind) { + case SyntaxKind.MethodSignature: { + updated = factory.updateMethodSignature( + lastDeclaration, + lastDeclaration.modifiers, + lastDeclaration.name, + lastDeclaration.questionToken, + lastDeclaration.typeParameters, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.type, + ); + break; + } + case SyntaxKind.MethodDeclaration: { + updated = factory.updateMethodDeclaration( + lastDeclaration, + lastDeclaration.modifiers, + lastDeclaration.asteriskToken, + lastDeclaration.name, + lastDeclaration.questionToken, + lastDeclaration.typeParameters, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.type, + lastDeclaration.body + ); + break; + } + case SyntaxKind.CallSignature: { + updated = factory.updateCallSignature( + lastDeclaration, + lastDeclaration.typeParameters, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.type, + ); + break; + } + case SyntaxKind.Constructor: { + updated = factory.updateConstructorDeclaration( + lastDeclaration, + lastDeclaration.modifiers, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.body + ); + break; + } + case SyntaxKind.ConstructSignature: { + updated = factory.updateConstructSignature( + lastDeclaration, + lastDeclaration.typeParameters, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.type, + ); + break; + } + case SyntaxKind.FunctionDeclaration: { + updated = factory.updateFunctionDeclaration( + lastDeclaration, + lastDeclaration.modifiers, + lastDeclaration.asteriskToken, + lastDeclaration.name, + lastDeclaration.typeParameters, + getNewParametersForCombinedSignature(signatureDecls), + lastDeclaration.type, + lastDeclaration.body + ); + break; + } + default: return Debug.failBadSyntaxKind(lastDeclaration, "Unhandled signature kind in overload list conversion refactoring"); + } - const edits = textChanges.ChangeTracker.with(context, t => { - t.replaceNodeRange(file, signatureDecls[0], signatureDecls[signatureDecls.length - 1], updated); - }); + if (updated === lastDeclaration) { + return; // No edits to apply, do nothing + } - return { renameFilename: undefined, renameLocation: undefined, edits }; + const edits = textChanges.ChangeTracker.with(context, t => { + t.replaceNodeRange(file, signatureDecls[0], signatureDecls[signatureDecls.length - 1], updated); + }); - function getNewParametersForCombinedSignature(signatureDeclarations: (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]): NodeArray { - const lastSig = signatureDeclarations[signatureDeclarations.length - 1]; - if (isFunctionLikeDeclaration(lastSig) && lastSig.body) { - // Trim away implementation signature arguments (they should already be compatible with overloads, but are likely less precise to guarantee compatability with the overloads) - signatureDeclarations = signatureDeclarations.slice(0, signatureDeclarations.length - 1); - } - return factory.createNodeArray([ - factory.createParameterDeclaration( - /*modifiers*/ undefined, - factory.createToken(SyntaxKind.DotDotDotToken), - "args", - /*questionToken*/ undefined, - factory.createUnionTypeNode(map(signatureDeclarations, convertSignatureParametersToTuple)) - ) - ]); - } + return { renameFilename: undefined, renameLocation: undefined, edits }; + + function getNewParametersForCombinedSignature(signatureDeclarations: (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]): NodeArray { + const lastSig = signatureDeclarations[signatureDeclarations.length - 1]; + if (isFunctionLikeDeclaration(lastSig) && lastSig.body) { + // Trim away implementation signature arguments (they should already be compatible with overloads, but are likely less precise to guarantee compatability with the overloads) + signatureDeclarations = signatureDeclarations.slice(0, signatureDeclarations.length - 1); + } + return factory.createNodeArray([ + factory.createParameterDeclaration( + /*modifiers*/ undefined, + factory.createToken(SyntaxKind.DotDotDotToken), + "args", + /*questionToken*/ undefined, + factory.createUnionTypeNode(map(signatureDeclarations, convertSignatureParametersToTuple)) + ) + ]); + } - function convertSignatureParametersToTuple(decl: MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration): TupleTypeNode { - const members = map(decl.parameters, convertParameterToNamedTupleMember); - return setEmitFlags(factory.createTupleTypeNode(members), some(members, m => !!length(getSyntheticLeadingComments(m))) ? EmitFlags.None : EmitFlags.SingleLine); - } + function convertSignatureParametersToTuple(decl: MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration): TupleTypeNode { + const members = map(decl.parameters, convertParameterToNamedTupleMember); + return setEmitFlags(factory.createTupleTypeNode(members), some(members, m => !!length(getSyntheticLeadingComments(m))) ? EmitFlags.None : EmitFlags.SingleLine); + } - function convertParameterToNamedTupleMember(p: ParameterDeclaration): NamedTupleMember { - Debug.assert(isIdentifier(p.name)); // This is checked during refactoring applicability checking - const result = setTextRange(factory.createNamedTupleMember( - p.dotDotDotToken, - p.name, - p.questionToken, - p.type || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ), p); - const parameterDocComment = p.symbol && p.symbol.getDocumentationComment(checker); - if (parameterDocComment) { - const newComment = displayPartsToString(parameterDocComment); - if (newComment.length) { - setSyntheticLeadingComments(result, [{ - text: `* + function convertParameterToNamedTupleMember(p: ParameterDeclaration): NamedTupleMember { + Debug.assert(isIdentifier(p.name)); // This is checked during refactoring applicability checking + const result = setTextRange(factory.createNamedTupleMember( + p.dotDotDotToken, + p.name, + p.questionToken, + p.type || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ), p); + const parameterDocComment = p.symbol && p.symbol.getDocumentationComment(checker); + if (parameterDocComment) { + const newComment = displayPartsToString(parameterDocComment); + if (newComment.length) { + setSyntheticLeadingComments(result, [{ + text: `* ${newComment.split("\n").map(c => ` * ${c}`).join("\n")} `, - kind: SyntaxKind.MultiLineCommentTrivia, - pos: -1, - end: -1, - hasTrailingNewLine: true, - hasLeadingNewline: true, - }]); - } + kind: SyntaxKind.MultiLineCommentTrivia, + pos: -1, + end: -1, + hasTrailingNewLine: true, + hasLeadingNewline: true, + }]); } - return result; } - + return result; } - function isConvertableSignatureDeclaration(d: Node): d is MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration { - switch (d.kind) { - case SyntaxKind.MethodSignature: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.CallSignature: - case SyntaxKind.Constructor: - case SyntaxKind.ConstructSignature: - case SyntaxKind.FunctionDeclaration: - return true; - } - return false; - } +} - function getConvertableOverloadListAtPosition(file: SourceFile, startPosition: number, program: Program) { - const node = getTokenAtPosition(file, startPosition); - const containingDecl = findAncestor(node, isConvertableSignatureDeclaration); - if (!containingDecl) { - return; - } - if (isFunctionLikeDeclaration(containingDecl) && containingDecl.body && rangeContainsPosition(containingDecl.body, startPosition)) { - return; - } +function isConvertableSignatureDeclaration(d: Node): d is MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration { + switch (d.kind) { + case SyntaxKind.MethodSignature: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.CallSignature: + case SyntaxKind.Constructor: + case SyntaxKind.ConstructSignature: + case SyntaxKind.FunctionDeclaration: + return true; + } + return false; +} - const checker = program.getTypeChecker(); - const signatureSymbol = containingDecl.symbol; - if (!signatureSymbol) { - return; - } - const decls = signatureSymbol.declarations; - if (length(decls) <= 1) { - return; - } - if (!every(decls, d => getSourceFileOfNode(d) === file)) { - return; - } - if (!isConvertableSignatureDeclaration(decls![0])) { - return; - } - const kindOne = decls![0].kind; - if (!every(decls, d => d.kind === kindOne)) { - return; - } - const signatureDecls = decls as (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]; - if (some(signatureDecls, d => !!d.typeParameters || some(d.parameters, p => !!p.modifiers || !isIdentifier(p.name)))) { - return; - } - const signatures = mapDefined(signatureDecls, d => checker.getSignatureFromDeclaration(d)); - if (length(signatures) !== length(decls)) { - return; - } - const returnOne = checker.getReturnTypeOfSignature(signatures[0]); - if (!every(signatures, s => checker.getReturnTypeOfSignature(s) === returnOne)) { - return; - } +function getConvertableOverloadListAtPosition(file: SourceFile, startPosition: number, program: Program) { + const node = getTokenAtPosition(file, startPosition); + const containingDecl = findAncestor(node, isConvertableSignatureDeclaration); + if (!containingDecl) { + return; + } + if (isFunctionLikeDeclaration(containingDecl) && containingDecl.body && rangeContainsPosition(containingDecl.body, startPosition)) { + return; + } - return signatureDecls; + const checker = program.getTypeChecker(); + const signatureSymbol = containingDecl.symbol; + if (!signatureSymbol) { + return; + } + const decls = signatureSymbol.declarations; + if (length(decls) <= 1) { + return; + } + if (!every(decls, d => getSourceFileOfNode(d) === file)) { + return; + } + if (!isConvertableSignatureDeclaration(decls![0])) { + return; } + const kindOne = decls![0].kind; + if (!every(decls, d => d.kind === kindOne)) { + return; + } + const signatureDecls = decls as (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]; + if (some(signatureDecls, d => !!d.typeParameters || some(d.parameters, p => !!p.modifiers || !isIdentifier(p.name)))) { + return; + } + const signatures = mapDefined(signatureDecls, d => checker.getSignatureFromDeclaration(d)); + if (length(signatures) !== length(decls)) { + return; + } + const returnOne = checker.getReturnTypeOfSignature(signatures[0]); + if (!every(signatures, s => checker.getReturnTypeOfSignature(s) === returnOne)) { + return; + } + + return signatureDecls; } diff --git a/src/services/refactors/convertParamsToDestructuredObject.ts b/src/services/refactors/convertParamsToDestructuredObject.ts index 015960707ee49..a4072a335b8b7 100644 --- a/src/services/refactors/convertParamsToDestructuredObject.ts +++ b/src/services/refactors/convertParamsToDestructuredObject.ts @@ -1,670 +1,689 @@ -/* @internal */ -namespace ts.refactor.convertParamsToDestructuredObject { - const refactorName = "Convert parameters to destructured object"; - const minimumParameterLength = 1; - const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object); - - const toDestructuredAction = { +import { + addEmitFlags, ApplicableRefactorInfo, ArrowFunction, BindingElement, CallExpression, CancellationToken, CheckFlags, + ClassDeclaration, ClassExpression, compareValues, ConstructorDeclaration, contains, copyComments, Debug, + deduplicate, Diagnostics, ElementAccessExpression, EmitFlags, emptyArray, equateValues, every, Expression, factory, + FindAllReferences, findAncestor, findChildOfKind, findModifier, first, flatMap, FunctionBody, FunctionDeclaration, + FunctionExpression, FunctionLikeDeclaration, getCheckFlags, getContainingFunctionDeclaration, + getContainingObjectLiteralElement, getLocaleSpecificMessage, getMeaningFromLocation, getSourceFileOfNode, + getSymbolTarget, getSynthesizedDeepClone, getTextOfIdentifierOrLiteral, getTouchingToken, getTypeNodeIfAccessible, + Identifier, isCallOrNewExpression, isClassDeclaration, isConstructorDeclaration, isDeclaration, + isElementAccessExpression, isExportAssignment, isExportSpecifier, isExpressionWithTypeArgumentsInClassExtendsClause, + isFunctionLikeDeclaration, isIdentifier, isImportClause, isImportEqualsDeclaration, isImportSpecifier, + isInterfaceDeclaration, isJSDocNode, isMethodSignature, isNamespaceImport, isNewExpressionTarget, + isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isRestParameter, isSourceFileJS, + isThis, isTypeLiteralNode, isVarConst, isVariableDeclaration, LanguageServiceHost, last, map, MethodDeclaration, + MethodSignature, Modifier, NewExpression, Node, NodeArray, ObjectLiteralElementLike, ObjectLiteralExpression, + ParameterDeclaration, Program, PropertyAccessExpression, PropertyAssignment, PropertySignature, rangeContainsRange, + RefactorContext, RefactorEditInfo, SemanticMeaning, ShorthandPropertyAssignment, sortAndDeduplicate, SourceFile, + suppressLeadingAndTrailingTrivia, Symbol, SyntaxKind, textChanges, tryCast, TypeChecker, TypeLiteralNode, TypeNode, + VariableDeclaration, +} from "../_namespaces/ts"; +import { registerRefactor } from "../_namespaces/ts.refactor"; + +const refactorName = "Convert parameters to destructured object"; +const minimumParameterLength = 1; +const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object); + +const toDestructuredAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.parameters.toDestructured" +}; +registerRefactor(refactorName, { + kinds: [toDestructuredAction.kind], + getEditsForAction: getRefactorEditsToConvertParametersToDestructuredObject, + getAvailableActions: getRefactorActionsToConvertParametersToDestructuredObject +}); + +function getRefactorActionsToConvertParametersToDestructuredObject(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const { file, startPosition } = context; + const isJSFile = isSourceFileJS(file); + if (isJSFile) return emptyArray; // TODO: GH#30113 + const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, context.program.getTypeChecker()); + if (!functionDeclaration) return emptyArray; + + return [{ name: refactorName, description: refactorDescription, - kind: "refactor.rewrite.parameters.toDestructured" - }; - registerRefactor(refactorName, { - kinds: [toDestructuredAction.kind], - getEditsForAction: getRefactorEditsToConvertParametersToDestructuredObject, - getAvailableActions: getRefactorActionsToConvertParametersToDestructuredObject - }); + actions: [toDestructuredAction] + }]; +} - function getRefactorActionsToConvertParametersToDestructuredObject(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const { file, startPosition } = context; - const isJSFile = isSourceFileJS(file); - if (isJSFile) return emptyArray; // TODO: GH#30113 - const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, context.program.getTypeChecker()); - if (!functionDeclaration) return emptyArray; - - return [{ - name: refactorName, - description: refactorDescription, - actions: [toDestructuredAction] - }]; +function getRefactorEditsToConvertParametersToDestructuredObject(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + Debug.assert(actionName === refactorName, "Unexpected action name"); + const { file, startPosition, program, cancellationToken, host } = context; + const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, program.getTypeChecker()); + if (!functionDeclaration || !cancellationToken) return undefined; + + const groupedReferences = getGroupedReferences(functionDeclaration, program, cancellationToken); + if (groupedReferences.valid) { + const edits = textChanges.ChangeTracker.with(context, t => doChange(file, program, host, t, functionDeclaration, groupedReferences)); + return { renameFilename: undefined, renameLocation: undefined, edits }; } - function getRefactorEditsToConvertParametersToDestructuredObject(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - Debug.assert(actionName === refactorName, "Unexpected action name"); - const { file, startPosition, program, cancellationToken, host } = context; - const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, program.getTypeChecker()); - if (!functionDeclaration || !cancellationToken) return undefined; + return { edits: [] }; // TODO: GH#30113 +} - const groupedReferences = getGroupedReferences(functionDeclaration, program, cancellationToken); - if (groupedReferences.valid) { - const edits = textChanges.ChangeTracker.with(context, t => doChange(file, program, host, t, functionDeclaration, groupedReferences)); - return { renameFilename: undefined, renameLocation: undefined, edits }; +function doChange( + sourceFile: SourceFile, + program: Program, + host: LanguageServiceHost, + changes: textChanges.ChangeTracker, + functionDeclaration: ValidFunctionDeclaration, + groupedReferences: GroupedReferences): void { + const signature = groupedReferences.signature; + const newFunctionDeclarationParams = map(createNewParameters(functionDeclaration, program, host), param => getSynthesizedDeepClone(param)); + + if (signature) { + const newSignatureParams = map(createNewParameters(signature, program, host), param => getSynthesizedDeepClone(param)); + replaceParameters(signature, newSignatureParams); + } + replaceParameters(functionDeclaration, newFunctionDeclarationParams); + + const functionCalls = sortAndDeduplicate(groupedReferences.functionCalls, /*comparer*/ (a, b) => compareValues(a.pos, b.pos)); + for (const call of functionCalls) { + if (call.arguments && call.arguments.length) { + const newArgument = getSynthesizedDeepClone(createNewArgument(functionDeclaration, call.arguments), /*includeTrivia*/ true); + changes.replaceNodeRange( + getSourceFileOfNode(call), + first(call.arguments), + last(call.arguments), + newArgument, + { leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, trailingTriviaOption: textChanges.TrailingTriviaOption.Include }); } + } - return { edits: [] }; // TODO: GH#30113 + function replaceParameters(declarationOrSignature: ValidFunctionDeclaration | ValidMethodSignature, parameterDeclarations: ParameterDeclaration[]) { + changes.replaceNodeRangeWithNodes( + sourceFile, + first(declarationOrSignature.parameters), + last(declarationOrSignature.parameters), + parameterDeclarations, + { + joiner: ", ", + // indentation is set to 0 because otherwise the object parameter will be indented if there is a `this` parameter + indentation: 0, + leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, + trailingTriviaOption: textChanges.TrailingTriviaOption.Include + }); } +} - function doChange( - sourceFile: SourceFile, - program: Program, - host: LanguageServiceHost, - changes: textChanges.ChangeTracker, - functionDeclaration: ValidFunctionDeclaration, - groupedReferences: GroupedReferences): void { - const signature = groupedReferences.signature; - const newFunctionDeclarationParams = map(createNewParameters(functionDeclaration, program, host), param => getSynthesizedDeepClone(param)); - - if (signature) { - const newSignatureParams = map(createNewParameters(signature, program, host), param => getSynthesizedDeepClone(param)); - replaceParameters(signature, newSignatureParams); - } - replaceParameters(functionDeclaration, newFunctionDeclarationParams); - - const functionCalls = sortAndDeduplicate(groupedReferences.functionCalls, /*comparer*/ (a, b) => compareValues(a.pos, b.pos)); - for (const call of functionCalls) { - if (call.arguments && call.arguments.length) { - const newArgument = getSynthesizedDeepClone(createNewArgument(functionDeclaration, call.arguments), /*includeTrivia*/ true); - changes.replaceNodeRange( - getSourceFileOfNode(call), - first(call.arguments), - last(call.arguments), - newArgument, - { leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, trailingTriviaOption: textChanges.TrailingTriviaOption.Include }); - } - } +function getGroupedReferences(functionDeclaration: ValidFunctionDeclaration, program: Program, cancellationToken: CancellationToken): GroupedReferences { + const functionNames = getFunctionNames(functionDeclaration); + const classNames = isConstructorDeclaration(functionDeclaration) ? getClassNames(functionDeclaration) : []; + const names = deduplicate([...functionNames, ...classNames], equateValues); + const checker = program.getTypeChecker(); - function replaceParameters(declarationOrSignature: ValidFunctionDeclaration | ValidMethodSignature, parameterDeclarations: ParameterDeclaration[]) { - changes.replaceNodeRangeWithNodes( - sourceFile, - first(declarationOrSignature.parameters), - last(declarationOrSignature.parameters), - parameterDeclarations, - { - joiner: ", ", - // indentation is set to 0 because otherwise the object parameter will be indented if there is a `this` parameter - indentation: 0, - leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, - trailingTriviaOption: textChanges.TrailingTriviaOption.Include - }); - } - } + const references = flatMap(names, /*mapfn*/ name => FindAllReferences.getReferenceEntriesForNode(-1, name, program, program.getSourceFiles(), cancellationToken)); + const groupedReferences = groupReferences(references); - function getGroupedReferences(functionDeclaration: ValidFunctionDeclaration, program: Program, cancellationToken: CancellationToken): GroupedReferences { - const functionNames = getFunctionNames(functionDeclaration); - const classNames = isConstructorDeclaration(functionDeclaration) ? getClassNames(functionDeclaration) : []; - const names = deduplicate([...functionNames, ...classNames], equateValues); - const checker = program.getTypeChecker(); + if (!every(groupedReferences.declarations, /*callback*/ decl => contains(names, decl))) { + groupedReferences.valid = false; + } - const references = flatMap(names, /*mapfn*/ name => FindAllReferences.getReferenceEntriesForNode(-1, name, program, program.getSourceFiles(), cancellationToken)); - const groupedReferences = groupReferences(references); + return groupedReferences; - if (!every(groupedReferences.declarations, /*callback*/ decl => contains(names, decl))) { - groupedReferences.valid = false; - } + function groupReferences(referenceEntries: readonly FindAllReferences.Entry[]): GroupedReferences { + const classReferences: ClassReferences = { accessExpressions: [], typeUsages: [] }; + const groupedReferences: GroupedReferences = { functionCalls: [], declarations: [], classReferences, valid: true }; + const functionSymbols = map(functionNames, getSymbolTargetAtLocation); + const classSymbols = map(classNames, getSymbolTargetAtLocation); + const isConstructor = isConstructorDeclaration(functionDeclaration); + const contextualSymbols = map(functionNames, name => getSymbolForContextualType(name, checker)); - return groupedReferences; + for (const entry of referenceEntries) { + if (entry.kind === FindAllReferences.EntryKind.Span) { + groupedReferences.valid = false; + continue; + } - function groupReferences(referenceEntries: readonly FindAllReferences.Entry[]): GroupedReferences { - const classReferences: ClassReferences = { accessExpressions: [], typeUsages: [] }; - const groupedReferences: GroupedReferences = { functionCalls: [], declarations: [], classReferences, valid: true }; - const functionSymbols = map(functionNames, getSymbolTargetAtLocation); - const classSymbols = map(classNames, getSymbolTargetAtLocation); - const isConstructor = isConstructorDeclaration(functionDeclaration); - const contextualSymbols = map(functionNames, name => getSymbolForContextualType(name, checker)); - - for (const entry of referenceEntries) { - if (entry.kind === FindAllReferences.EntryKind.Span) { - groupedReferences.valid = false; + /* Declarations in object literals may be implementations of method signatures which have a different symbol from the declaration + For example: + interface IFoo { m(a: number): void } + const foo: IFoo = { m(a: number): void {} } + In these cases we get the symbol for the signature from the contextual type. + */ + if (contains(contextualSymbols, getSymbolTargetAtLocation(entry.node))) { + if (isValidMethodSignature(entry.node.parent)) { + groupedReferences.signature = entry.node.parent; + continue; + } + const call = entryToFunctionCall(entry); + if (call) { + groupedReferences.functionCalls.push(call); continue; } + } - /* Declarations in object literals may be implementations of method signatures which have a different symbol from the declaration - For example: - interface IFoo { m(a: number): void } - const foo: IFoo = { m(a: number): void {} } - In these cases we get the symbol for the signature from the contextual type. - */ - if (contains(contextualSymbols, getSymbolTargetAtLocation(entry.node))) { - if (isValidMethodSignature(entry.node.parent)) { - groupedReferences.signature = entry.node.parent; - continue; - } - const call = entryToFunctionCall(entry); - if (call) { - groupedReferences.functionCalls.push(call); - continue; - } + const contextualSymbol = getSymbolForContextualType(entry.node, checker); + if (contextualSymbol && contains(contextualSymbols, contextualSymbol)) { + const decl = entryToDeclaration(entry); + if (decl) { + groupedReferences.declarations.push(decl); + continue; } + } - const contextualSymbol = getSymbolForContextualType(entry.node, checker); - if (contextualSymbol && contains(contextualSymbols, contextualSymbol)) { - const decl = entryToDeclaration(entry); - if (decl) { - groupedReferences.declarations.push(decl); - continue; - } + /* We compare symbols because in some cases find all references wil return a reference that may or may not be to the refactored function. + Example from the refactorConvertParamsToDestructuredObject_methodCallUnion.ts test: + class A { foo(a: number, b: number) { return a + b; } } + class B { foo(c: number, d: number) { return c + d; } } + declare const ab: A | B; + ab.foo(1, 2); + Find all references will return `ab.foo(1, 2)` as a reference to A's `foo` but we could be calling B's `foo`. + When looking for constructor calls, however, the symbol on the constructor call reference is going to be the corresponding class symbol. + So we need to add a special case for this because when calling a constructor of a class through one of its subclasses, + the symbols are going to be different. + */ + if (contains(functionSymbols, getSymbolTargetAtLocation(entry.node)) || isNewExpressionTarget(entry.node)) { + const importOrExportReference = entryToImportOrExport(entry); + if (importOrExportReference) { + continue; + } + const decl = entryToDeclaration(entry); + if (decl) { + groupedReferences.declarations.push(decl); + continue; } - /* We compare symbols because in some cases find all references wil return a reference that may or may not be to the refactored function. - Example from the refactorConvertParamsToDestructuredObject_methodCallUnion.ts test: - class A { foo(a: number, b: number) { return a + b; } } - class B { foo(c: number, d: number) { return c + d; } } - declare const ab: A | B; - ab.foo(1, 2); - Find all references will return `ab.foo(1, 2)` as a reference to A's `foo` but we could be calling B's `foo`. - When looking for constructor calls, however, the symbol on the constructor call reference is going to be the corresponding class symbol. - So we need to add a special case for this because when calling a constructor of a class through one of its subclasses, - the symbols are going to be different. - */ - if (contains(functionSymbols, getSymbolTargetAtLocation(entry.node)) || isNewExpressionTarget(entry.node)) { - const importOrExportReference = entryToImportOrExport(entry); - if (importOrExportReference) { - continue; - } - const decl = entryToDeclaration(entry); - if (decl) { - groupedReferences.declarations.push(decl); - continue; - } + const call = entryToFunctionCall(entry); + if (call) { + groupedReferences.functionCalls.push(call); + continue; + } + } + // if the refactored function is a constructor, we must also check if the references to its class are valid + if (isConstructor && contains(classSymbols, getSymbolTargetAtLocation(entry.node))) { + const importOrExportReference = entryToImportOrExport(entry); + if (importOrExportReference) { + continue; + } - const call = entryToFunctionCall(entry); - if (call) { - groupedReferences.functionCalls.push(call); - continue; - } + const decl = entryToDeclaration(entry); + if (decl) { + groupedReferences.declarations.push(decl); + continue; } - // if the refactored function is a constructor, we must also check if the references to its class are valid - if (isConstructor && contains(classSymbols, getSymbolTargetAtLocation(entry.node))) { - const importOrExportReference = entryToImportOrExport(entry); - if (importOrExportReference) { - continue; - } - const decl = entryToDeclaration(entry); - if (decl) { - groupedReferences.declarations.push(decl); - continue; - } + const accessExpression = entryToAccessExpression(entry); + if (accessExpression) { + classReferences.accessExpressions.push(accessExpression); + continue; + } - const accessExpression = entryToAccessExpression(entry); - if (accessExpression) { - classReferences.accessExpressions.push(accessExpression); + // Only class declarations are allowed to be used as a type (in a heritage clause), + // otherwise `findAllReferences` might not be able to track constructor calls. + if (isClassDeclaration(functionDeclaration.parent)) { + const type = entryToType(entry); + if (type) { + classReferences.typeUsages.push(type); continue; } - - // Only class declarations are allowed to be used as a type (in a heritage clause), - // otherwise `findAllReferences` might not be able to track constructor calls. - if (isClassDeclaration(functionDeclaration.parent)) { - const type = entryToType(entry); - if (type) { - classReferences.typeUsages.push(type); - continue; - } - } } - groupedReferences.valid = false; } - - return groupedReferences; + groupedReferences.valid = false; } - function getSymbolTargetAtLocation(node: Node) { - const symbol = checker.getSymbolAtLocation(node); - return symbol && getSymbolTarget(symbol, checker); - } + return groupedReferences; } - /** - * Gets the symbol for the contextual type of the node if it is not a union or intersection. - */ - function getSymbolForContextualType(node: Node, checker: TypeChecker): Symbol | undefined { - const element = getContainingObjectLiteralElement(node); - if (element) { - const contextualType = checker.getContextualTypeForObjectLiteralElement(element as ObjectLiteralElementLike); - const symbol = contextualType?.getSymbol(); - if (symbol && !(getCheckFlags(symbol) & CheckFlags.Synthetic)) { - return symbol; - } + function getSymbolTargetAtLocation(node: Node) { + const symbol = checker.getSymbolAtLocation(node); + return symbol && getSymbolTarget(symbol, checker); + } +} + +/** + * Gets the symbol for the contextual type of the node if it is not a union or intersection. + */ +function getSymbolForContextualType(node: Node, checker: TypeChecker): Symbol | undefined { + const element = getContainingObjectLiteralElement(node); + if (element) { + const contextualType = checker.getContextualTypeForObjectLiteralElement(element as ObjectLiteralElementLike); + const symbol = contextualType?.getSymbol(); + if (symbol && !(getCheckFlags(symbol) & CheckFlags.Synthetic)) { + return symbol; } } +} - function entryToImportOrExport(entry: FindAllReferences.NodeEntry): Node | undefined { - const node = entry.node; +function entryToImportOrExport(entry: FindAllReferences.NodeEntry): Node | undefined { + const node = entry.node; - if (isImportSpecifier(node.parent) - || isImportClause(node.parent) - || isImportEqualsDeclaration(node.parent) - || isNamespaceImport(node.parent)) { - return node; - } + if (isImportSpecifier(node.parent) + || isImportClause(node.parent) + || isImportEqualsDeclaration(node.parent) + || isNamespaceImport(node.parent)) { + return node; + } - if (isExportSpecifier(node.parent) || isExportAssignment(node.parent)) { - return node; - } - return undefined; + if (isExportSpecifier(node.parent) || isExportAssignment(node.parent)) { + return node; } + return undefined; +} - function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | undefined { - if (isDeclaration(entry.node.parent)) { - return entry.node; - } - return undefined; +function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | undefined { + if (isDeclaration(entry.node.parent)) { + return entry.node; } + return undefined; +} - function entryToFunctionCall(entry: FindAllReferences.NodeEntry): CallExpression | NewExpression | undefined { - if (entry.node.parent) { - const functionReference = entry.node; - const parent = functionReference.parent; - switch (parent.kind) { - // foo(...) or super(...) or new Foo(...) - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - const callOrNewExpression = tryCast(parent, isCallOrNewExpression); - if (callOrNewExpression && callOrNewExpression.expression === functionReference) { +function entryToFunctionCall(entry: FindAllReferences.NodeEntry): CallExpression | NewExpression | undefined { + if (entry.node.parent) { + const functionReference = entry.node; + const parent = functionReference.parent; + switch (parent.kind) { + // foo(...) or super(...) or new Foo(...) + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + const callOrNewExpression = tryCast(parent, isCallOrNewExpression); + if (callOrNewExpression && callOrNewExpression.expression === functionReference) { + return callOrNewExpression; + } + break; + // x.foo(...) + case SyntaxKind.PropertyAccessExpression: + const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); + if (propertyAccessExpression && propertyAccessExpression.parent && propertyAccessExpression.name === functionReference) { + const callOrNewExpression = tryCast(propertyAccessExpression.parent, isCallOrNewExpression); + if (callOrNewExpression && callOrNewExpression.expression === propertyAccessExpression) { return callOrNewExpression; } - break; - // x.foo(...) - case SyntaxKind.PropertyAccessExpression: - const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); - if (propertyAccessExpression && propertyAccessExpression.parent && propertyAccessExpression.name === functionReference) { - const callOrNewExpression = tryCast(propertyAccessExpression.parent, isCallOrNewExpression); - if (callOrNewExpression && callOrNewExpression.expression === propertyAccessExpression) { - return callOrNewExpression; - } - } - break; - // x["foo"](...) - case SyntaxKind.ElementAccessExpression: - const elementAccessExpression = tryCast(parent, isElementAccessExpression); - if (elementAccessExpression && elementAccessExpression.parent && elementAccessExpression.argumentExpression === functionReference) { - const callOrNewExpression = tryCast(elementAccessExpression.parent, isCallOrNewExpression); - if (callOrNewExpression && callOrNewExpression.expression === elementAccessExpression) { - return callOrNewExpression; - } + } + break; + // x["foo"](...) + case SyntaxKind.ElementAccessExpression: + const elementAccessExpression = tryCast(parent, isElementAccessExpression); + if (elementAccessExpression && elementAccessExpression.parent && elementAccessExpression.argumentExpression === functionReference) { + const callOrNewExpression = tryCast(elementAccessExpression.parent, isCallOrNewExpression); + if (callOrNewExpression && callOrNewExpression.expression === elementAccessExpression) { + return callOrNewExpression; } - break; - } + } + break; } - return undefined; } + return undefined; +} - function entryToAccessExpression(entry: FindAllReferences.NodeEntry): ElementAccessExpression | PropertyAccessExpression | undefined { - if (entry.node.parent) { - const reference = entry.node; - const parent = reference.parent; - switch (parent.kind) { - // `C.foo` - case SyntaxKind.PropertyAccessExpression: - const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); - if (propertyAccessExpression && propertyAccessExpression.expression === reference) { - return propertyAccessExpression; - } - break; - // `C["foo"]` - case SyntaxKind.ElementAccessExpression: - const elementAccessExpression = tryCast(parent, isElementAccessExpression); - if (elementAccessExpression && elementAccessExpression.expression === reference) { - return elementAccessExpression; - } - break; - } +function entryToAccessExpression(entry: FindAllReferences.NodeEntry): ElementAccessExpression | PropertyAccessExpression | undefined { + if (entry.node.parent) { + const reference = entry.node; + const parent = reference.parent; + switch (parent.kind) { + // `C.foo` + case SyntaxKind.PropertyAccessExpression: + const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); + if (propertyAccessExpression && propertyAccessExpression.expression === reference) { + return propertyAccessExpression; + } + break; + // `C["foo"]` + case SyntaxKind.ElementAccessExpression: + const elementAccessExpression = tryCast(parent, isElementAccessExpression); + if (elementAccessExpression && elementAccessExpression.expression === reference) { + return elementAccessExpression; + } + break; } - return undefined; } + return undefined; +} - function entryToType(entry: FindAllReferences.NodeEntry): Node | undefined { - const reference = entry.node; - if (getMeaningFromLocation(reference) === SemanticMeaning.Type || isExpressionWithTypeArgumentsInClassExtendsClause(reference.parent)) { - return reference; - } - return undefined; +function entryToType(entry: FindAllReferences.NodeEntry): Node | undefined { + const reference = entry.node; + if (getMeaningFromLocation(reference) === SemanticMeaning.Type || isExpressionWithTypeArgumentsInClassExtendsClause(reference.parent)) { + return reference; } + return undefined; +} - function getFunctionDeclarationAtPosition(file: SourceFile, startPosition: number, checker: TypeChecker): ValidFunctionDeclaration | undefined { - const node = getTouchingToken(file, startPosition); - const functionDeclaration = getContainingFunctionDeclaration(node); +function getFunctionDeclarationAtPosition(file: SourceFile, startPosition: number, checker: TypeChecker): ValidFunctionDeclaration | undefined { + const node = getTouchingToken(file, startPosition); + const functionDeclaration = getContainingFunctionDeclaration(node); - // don't offer refactor on top-level JSDoc - if (isTopLevelJSDoc(node)) return undefined; + // don't offer refactor on top-level JSDoc + if (isTopLevelJSDoc(node)) return undefined; - if (functionDeclaration - && isValidFunctionDeclaration(functionDeclaration, checker) - && rangeContainsRange(functionDeclaration, node) - && !(functionDeclaration.body && rangeContainsRange(functionDeclaration.body, node))) return functionDeclaration; + if (functionDeclaration + && isValidFunctionDeclaration(functionDeclaration, checker) + && rangeContainsRange(functionDeclaration, node) + && !(functionDeclaration.body && rangeContainsRange(functionDeclaration.body, node))) return functionDeclaration; - return undefined; - } + return undefined; +} - function isTopLevelJSDoc(node: Node): boolean { - const containingJSDoc = findAncestor(node, isJSDocNode); - if (containingJSDoc) { - const containingNonJSDoc = findAncestor(containingJSDoc, n => !isJSDocNode(n)); - return !!containingNonJSDoc && isFunctionLikeDeclaration(containingNonJSDoc); - } - return false; +function isTopLevelJSDoc(node: Node): boolean { + const containingJSDoc = findAncestor(node, isJSDocNode); + if (containingJSDoc) { + const containingNonJSDoc = findAncestor(containingJSDoc, n => !isJSDocNode(n)); + return !!containingNonJSDoc && isFunctionLikeDeclaration(containingNonJSDoc); } + return false; +} - function isValidMethodSignature(node: Node): node is ValidMethodSignature { - return isMethodSignature(node) && (isInterfaceDeclaration(node.parent) || isTypeLiteralNode(node.parent)); - } +function isValidMethodSignature(node: Node): node is ValidMethodSignature { + return isMethodSignature(node) && (isInterfaceDeclaration(node.parent) || isTypeLiteralNode(node.parent)); +} - function isValidFunctionDeclaration( - functionDeclaration: FunctionLikeDeclaration, - checker: TypeChecker): functionDeclaration is ValidFunctionDeclaration { - if (!isValidParameterNodeArray(functionDeclaration.parameters, checker)) return false; - switch (functionDeclaration.kind) { - case SyntaxKind.FunctionDeclaration: - return hasNameOrDefault(functionDeclaration) && isSingleImplementation(functionDeclaration, checker); - case SyntaxKind.MethodDeclaration: - if (isObjectLiteralExpression(functionDeclaration.parent)) { - const contextualSymbol = getSymbolForContextualType(functionDeclaration.name, checker); - // don't offer the refactor when there are multiple signatures since we won't know which ones the user wants to change - return contextualSymbol?.declarations?.length === 1 && isSingleImplementation(functionDeclaration, checker); - } - return isSingleImplementation(functionDeclaration, checker); - case SyntaxKind.Constructor: - if (isClassDeclaration(functionDeclaration.parent)) { - return hasNameOrDefault(functionDeclaration.parent) && isSingleImplementation(functionDeclaration, checker); - } - else { - return isValidVariableDeclaration(functionDeclaration.parent.parent) - && isSingleImplementation(functionDeclaration, checker); - } - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return isValidVariableDeclaration(functionDeclaration.parent); - } - return false; +function isValidFunctionDeclaration( + functionDeclaration: FunctionLikeDeclaration, + checker: TypeChecker): functionDeclaration is ValidFunctionDeclaration { + if (!isValidParameterNodeArray(functionDeclaration.parameters, checker)) return false; + switch (functionDeclaration.kind) { + case SyntaxKind.FunctionDeclaration: + return hasNameOrDefault(functionDeclaration) && isSingleImplementation(functionDeclaration, checker); + case SyntaxKind.MethodDeclaration: + if (isObjectLiteralExpression(functionDeclaration.parent)) { + const contextualSymbol = getSymbolForContextualType(functionDeclaration.name, checker); + // don't offer the refactor when there are multiple signatures since we won't know which ones the user wants to change + return contextualSymbol?.declarations?.length === 1 && isSingleImplementation(functionDeclaration, checker); + } + return isSingleImplementation(functionDeclaration, checker); + case SyntaxKind.Constructor: + if (isClassDeclaration(functionDeclaration.parent)) { + return hasNameOrDefault(functionDeclaration.parent) && isSingleImplementation(functionDeclaration, checker); + } + else { + return isValidVariableDeclaration(functionDeclaration.parent.parent) + && isSingleImplementation(functionDeclaration, checker); + } + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return isValidVariableDeclaration(functionDeclaration.parent); } + return false; +} - function isSingleImplementation(functionDeclaration: FunctionLikeDeclaration, checker: TypeChecker): boolean { - return !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration); - } +function isSingleImplementation(functionDeclaration: FunctionLikeDeclaration, checker: TypeChecker): boolean { + return !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration); +} - function hasNameOrDefault(functionOrClassDeclaration: FunctionDeclaration | ClassDeclaration): boolean { - if (!functionOrClassDeclaration.name) { - const defaultKeyword = findModifier(functionOrClassDeclaration, SyntaxKind.DefaultKeyword); - return !!defaultKeyword; - } - return true; +function hasNameOrDefault(functionOrClassDeclaration: FunctionDeclaration | ClassDeclaration): boolean { + if (!functionOrClassDeclaration.name) { + const defaultKeyword = findModifier(functionOrClassDeclaration, SyntaxKind.DefaultKeyword); + return !!defaultKeyword; } + return true; +} - function isValidParameterNodeArray( - parameters: NodeArray, - checker: TypeChecker): parameters is ValidParameterNodeArray { - return getRefactorableParametersLength(parameters) >= minimumParameterLength - && every(parameters, /*callback*/ paramDecl => isValidParameterDeclaration(paramDecl, checker)); - } +function isValidParameterNodeArray( + parameters: NodeArray, + checker: TypeChecker): parameters is ValidParameterNodeArray { + return getRefactorableParametersLength(parameters) >= minimumParameterLength + && every(parameters, /*callback*/ paramDecl => isValidParameterDeclaration(paramDecl, checker)); +} - function isValidParameterDeclaration( - parameterDeclaration: ParameterDeclaration, - checker: TypeChecker): parameterDeclaration is ValidParameterDeclaration { - if (isRestParameter(parameterDeclaration)) { - const type = checker.getTypeAtLocation(parameterDeclaration); - if (!checker.isArrayType(type) && !checker.isTupleType(type)) return false; - } - return !parameterDeclaration.modifiers && isIdentifier(parameterDeclaration.name); +function isValidParameterDeclaration( + parameterDeclaration: ParameterDeclaration, + checker: TypeChecker): parameterDeclaration is ValidParameterDeclaration { + if (isRestParameter(parameterDeclaration)) { + const type = checker.getTypeAtLocation(parameterDeclaration); + if (!checker.isArrayType(type) && !checker.isTupleType(type)) return false; } + return !parameterDeclaration.modifiers && isIdentifier(parameterDeclaration.name); +} - function isValidVariableDeclaration(node: Node): node is ValidVariableDeclaration { - return isVariableDeclaration(node) && isVarConst(node) && isIdentifier(node.name) && !node.type; // TODO: GH#30113 - } +function isValidVariableDeclaration(node: Node): node is ValidVariableDeclaration { + return isVariableDeclaration(node) && isVarConst(node) && isIdentifier(node.name) && !node.type; // TODO: GH#30113 +} - function hasThisParameter(parameters: NodeArray): boolean { - return parameters.length > 0 && isThis(parameters[0].name); - } +function hasThisParameter(parameters: NodeArray): boolean { + return parameters.length > 0 && isThis(parameters[0].name); +} - function getRefactorableParametersLength(parameters: NodeArray): number { - if (hasThisParameter(parameters)) { - return parameters.length - 1; - } - return parameters.length; +function getRefactorableParametersLength(parameters: NodeArray): number { + if (hasThisParameter(parameters)) { + return parameters.length - 1; } + return parameters.length; +} - function getRefactorableParameters(parameters: NodeArray): NodeArray { - if (hasThisParameter(parameters)) { - parameters = factory.createNodeArray(parameters.slice(1), parameters.hasTrailingComma); - } - return parameters; +function getRefactorableParameters(parameters: NodeArray): NodeArray { + if (hasThisParameter(parameters)) { + parameters = factory.createNodeArray(parameters.slice(1), parameters.hasTrailingComma); } + return parameters; +} - function createPropertyOrShorthandAssignment(name: string, initializer: Expression): PropertyAssignment | ShorthandPropertyAssignment { - if (isIdentifier(initializer) && getTextOfIdentifierOrLiteral(initializer) === name) { - return factory.createShorthandPropertyAssignment(name); - } - return factory.createPropertyAssignment(name, initializer); +function createPropertyOrShorthandAssignment(name: string, initializer: Expression): PropertyAssignment | ShorthandPropertyAssignment { + if (isIdentifier(initializer) && getTextOfIdentifierOrLiteral(initializer) === name) { + return factory.createShorthandPropertyAssignment(name); } + return factory.createPropertyAssignment(name, initializer); +} - function createNewArgument(functionDeclaration: ValidFunctionDeclaration, functionArguments: NodeArray): ObjectLiteralExpression { - const parameters = getRefactorableParameters(functionDeclaration.parameters); - const hasRestParameter = isRestParameter(last(parameters)); - const nonRestArguments = hasRestParameter ? functionArguments.slice(0, parameters.length - 1) : functionArguments; - const properties = map(nonRestArguments, (arg, i) => { - const parameterName = getParameterName(parameters[i]); - const property = createPropertyOrShorthandAssignment(parameterName, arg); - - suppressLeadingAndTrailingTrivia(property.name); - if (isPropertyAssignment(property)) suppressLeadingAndTrailingTrivia(property.initializer); - copyComments(arg, property); - return property; - }); - - if (hasRestParameter && functionArguments.length >= parameters.length) { - const restArguments = functionArguments.slice(parameters.length - 1); - const restProperty = factory.createPropertyAssignment(getParameterName(last(parameters)), factory.createArrayLiteralExpression(restArguments)); - properties.push(restProperty); - } +function createNewArgument(functionDeclaration: ValidFunctionDeclaration, functionArguments: NodeArray): ObjectLiteralExpression { + const parameters = getRefactorableParameters(functionDeclaration.parameters); + const hasRestParameter = isRestParameter(last(parameters)); + const nonRestArguments = hasRestParameter ? functionArguments.slice(0, parameters.length - 1) : functionArguments; + const properties = map(nonRestArguments, (arg, i) => { + const parameterName = getParameterName(parameters[i]); + const property = createPropertyOrShorthandAssignment(parameterName, arg); + + suppressLeadingAndTrailingTrivia(property.name); + if (isPropertyAssignment(property)) suppressLeadingAndTrailingTrivia(property.initializer); + copyComments(arg, property); + return property; + }); - const objectLiteral = factory.createObjectLiteralExpression(properties, /*multiLine*/ false); - return objectLiteral; + if (hasRestParameter && functionArguments.length >= parameters.length) { + const restArguments = functionArguments.slice(parameters.length - 1); + const restProperty = factory.createPropertyAssignment(getParameterName(last(parameters)), factory.createArrayLiteralExpression(restArguments)); + properties.push(restProperty); } - function createNewParameters(functionDeclaration: ValidFunctionDeclaration | ValidMethodSignature, program: Program, host: LanguageServiceHost): NodeArray { - const checker = program.getTypeChecker(); - const refactorableParameters = getRefactorableParameters(functionDeclaration.parameters); - const bindingElements = map(refactorableParameters, createBindingElementFromParameterDeclaration); - const objectParameterName = factory.createObjectBindingPattern(bindingElements); - const objectParameterType = createParameterTypeNode(refactorableParameters); - - let objectInitializer: Expression | undefined; - // If every parameter in the original function was optional, add an empty object initializer to the new object parameter - if (every(refactorableParameters, isOptionalParameter)) { - objectInitializer = factory.createObjectLiteralExpression(); - } + const objectLiteral = factory.createObjectLiteralExpression(properties, /*multiLine*/ false); + return objectLiteral; +} - const objectParameter = factory.createParameterDeclaration( +function createNewParameters(functionDeclaration: ValidFunctionDeclaration | ValidMethodSignature, program: Program, host: LanguageServiceHost): NodeArray { + const checker = program.getTypeChecker(); + const refactorableParameters = getRefactorableParameters(functionDeclaration.parameters); + const bindingElements = map(refactorableParameters, createBindingElementFromParameterDeclaration); + const objectParameterName = factory.createObjectBindingPattern(bindingElements); + const objectParameterType = createParameterTypeNode(refactorableParameters); + + let objectInitializer: Expression | undefined; + // If every parameter in the original function was optional, add an empty object initializer to the new object parameter + if (every(refactorableParameters, isOptionalParameter)) { + objectInitializer = factory.createObjectLiteralExpression(); + } + + const objectParameter = factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + objectParameterName, + /*questionToken*/ undefined, + objectParameterType, + objectInitializer); + + if (hasThisParameter(functionDeclaration.parameters)) { + const thisParameter = functionDeclaration.parameters[0]; + const newThisParameter = factory.createParameterDeclaration( /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, - objectParameterName, + thisParameter.name, /*questionToken*/ undefined, - objectParameterType, - objectInitializer); - - if (hasThisParameter(functionDeclaration.parameters)) { - const thisParameter = functionDeclaration.parameters[0]; - const newThisParameter = factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - thisParameter.name, - /*questionToken*/ undefined, - thisParameter.type); - - suppressLeadingAndTrailingTrivia(newThisParameter.name); - copyComments(thisParameter.name, newThisParameter.name); - if (thisParameter.type) { - suppressLeadingAndTrailingTrivia(newThisParameter.type!); - copyComments(thisParameter.type, newThisParameter.type!); - } + thisParameter.type); - return factory.createNodeArray([newThisParameter, objectParameter]); - } - return factory.createNodeArray([objectParameter]); - - function createBindingElementFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): BindingElement { - const element = factory.createBindingElement( - /*dotDotDotToken*/ undefined, - /*propertyName*/ undefined, - getParameterName(parameterDeclaration), - isRestParameter(parameterDeclaration) && isOptionalParameter(parameterDeclaration) ? factory.createArrayLiteralExpression() : parameterDeclaration.initializer); - - suppressLeadingAndTrailingTrivia(element); - if (parameterDeclaration.initializer && element.initializer) { - copyComments(parameterDeclaration.initializer, element.initializer); - } - return element; + suppressLeadingAndTrailingTrivia(newThisParameter.name); + copyComments(thisParameter.name, newThisParameter.name); + if (thisParameter.type) { + suppressLeadingAndTrailingTrivia(newThisParameter.type!); + copyComments(thisParameter.type, newThisParameter.type!); } - function createParameterTypeNode(parameters: NodeArray): TypeLiteralNode { - const members = map(parameters, createPropertySignatureFromParameterDeclaration); - const typeNode = addEmitFlags(factory.createTypeLiteralNode(members), EmitFlags.SingleLine); - return typeNode; - } - - function createPropertySignatureFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): PropertySignature { - let parameterType = parameterDeclaration.type; - if (!parameterType && (parameterDeclaration.initializer || isRestParameter(parameterDeclaration))) { - parameterType = getTypeNode(parameterDeclaration); - } - - const propertySignature = factory.createPropertySignature( - /*modifiers*/ undefined, - getParameterName(parameterDeclaration), - isOptionalParameter(parameterDeclaration) ? factory.createToken(SyntaxKind.QuestionToken) : parameterDeclaration.questionToken, - parameterType); + return factory.createNodeArray([newThisParameter, objectParameter]); + } + return factory.createNodeArray([objectParameter]); - suppressLeadingAndTrailingTrivia(propertySignature); - copyComments(parameterDeclaration.name, propertySignature.name); - if (parameterDeclaration.type && propertySignature.type) { - copyComments(parameterDeclaration.type, propertySignature.type); - } + function createBindingElementFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): BindingElement { + const element = factory.createBindingElement( + /*dotDotDotToken*/ undefined, + /*propertyName*/ undefined, + getParameterName(parameterDeclaration), + isRestParameter(parameterDeclaration) && isOptionalParameter(parameterDeclaration) ? factory.createArrayLiteralExpression() : parameterDeclaration.initializer); - return propertySignature; + suppressLeadingAndTrailingTrivia(element); + if (parameterDeclaration.initializer && element.initializer) { + copyComments(parameterDeclaration.initializer, element.initializer); } + return element; + } - function getTypeNode(node: Node): TypeNode | undefined { - const type = checker.getTypeAtLocation(node); - return getTypeNodeIfAccessible(type, node, program, host); + function createParameterTypeNode(parameters: NodeArray): TypeLiteralNode { + const members = map(parameters, createPropertySignatureFromParameterDeclaration); + const typeNode = addEmitFlags(factory.createTypeLiteralNode(members), EmitFlags.SingleLine); + return typeNode; + } + + function createPropertySignatureFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): PropertySignature { + let parameterType = parameterDeclaration.type; + if (!parameterType && (parameterDeclaration.initializer || isRestParameter(parameterDeclaration))) { + parameterType = getTypeNode(parameterDeclaration); } - function isOptionalParameter(parameterDeclaration: ValidParameterDeclaration): boolean { - if (isRestParameter(parameterDeclaration)) { - const type = checker.getTypeAtLocation(parameterDeclaration); - return !checker.isTupleType(type); - } - return checker.isOptionalParameter(parameterDeclaration); + const propertySignature = factory.createPropertySignature( + /*modifiers*/ undefined, + getParameterName(parameterDeclaration), + isOptionalParameter(parameterDeclaration) ? factory.createToken(SyntaxKind.QuestionToken) : parameterDeclaration.questionToken, + parameterType); + + suppressLeadingAndTrailingTrivia(propertySignature); + copyComments(parameterDeclaration.name, propertySignature.name); + if (parameterDeclaration.type && propertySignature.type) { + copyComments(parameterDeclaration.type, propertySignature.type); } - } - function getParameterName(paramDeclaration: ValidParameterDeclaration) { - return getTextOfIdentifierOrLiteral(paramDeclaration.name); + return propertySignature; } - function getClassNames(constructorDeclaration: ValidConstructor): (Identifier | Modifier)[] { - switch (constructorDeclaration.parent.kind) { - case SyntaxKind.ClassDeclaration: - const classDeclaration = constructorDeclaration.parent; - if (classDeclaration.name) return [classDeclaration.name]; - // If the class declaration doesn't have a name, it should have a default modifier. - // We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault` - const defaultModifier = Debug.checkDefined( - findModifier(classDeclaration, SyntaxKind.DefaultKeyword), - "Nameless class declaration should be a default export"); - return [defaultModifier]; - case SyntaxKind.ClassExpression: - const classExpression = constructorDeclaration.parent; - const variableDeclaration = constructorDeclaration.parent.parent; - const className = classExpression.name; - if (className) return [className, variableDeclaration.name]; - return [variableDeclaration.name]; - } + function getTypeNode(node: Node): TypeNode | undefined { + const type = checker.getTypeAtLocation(node); + return getTypeNodeIfAccessible(type, node, program, host); } - function getFunctionNames(functionDeclaration: ValidFunctionDeclaration): Node[] { - switch (functionDeclaration.kind) { - case SyntaxKind.FunctionDeclaration: - if (functionDeclaration.name) return [functionDeclaration.name]; - // If the function declaration doesn't have a name, it should have a default modifier. - // We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault` - const defaultModifier = Debug.checkDefined( - findModifier(functionDeclaration, SyntaxKind.DefaultKeyword), - "Nameless function declaration should be a default export"); - return [defaultModifier]; - case SyntaxKind.MethodDeclaration: - return [functionDeclaration.name]; - case SyntaxKind.Constructor: - const ctrKeyword = Debug.checkDefined( - findChildOfKind(functionDeclaration, SyntaxKind.ConstructorKeyword, functionDeclaration.getSourceFile()), - "Constructor declaration should have constructor keyword"); - if (functionDeclaration.parent.kind === SyntaxKind.ClassExpression) { - const variableDeclaration = functionDeclaration.parent.parent; - return [variableDeclaration.name, ctrKeyword]; - } - return [ctrKeyword]; - case SyntaxKind.ArrowFunction: - return [functionDeclaration.parent.name]; - case SyntaxKind.FunctionExpression: - if (functionDeclaration.name) return [functionDeclaration.name, functionDeclaration.parent.name]; - return [functionDeclaration.parent.name]; - default: - return Debug.assertNever(functionDeclaration, `Unexpected function declaration kind ${(functionDeclaration as ValidFunctionDeclaration).kind}`); + function isOptionalParameter(parameterDeclaration: ValidParameterDeclaration): boolean { + if (isRestParameter(parameterDeclaration)) { + const type = checker.getTypeAtLocation(parameterDeclaration); + return !checker.isTupleType(type); } + return checker.isOptionalParameter(parameterDeclaration); } +} - type ValidParameterNodeArray = NodeArray; +function getParameterName(paramDeclaration: ValidParameterDeclaration) { + return getTextOfIdentifierOrLiteral(paramDeclaration.name); +} - interface ValidVariableDeclaration extends VariableDeclaration { - name: Identifier; - type: undefined; +function getClassNames(constructorDeclaration: ValidConstructor): (Identifier | Modifier)[] { + switch (constructorDeclaration.parent.kind) { + case SyntaxKind.ClassDeclaration: + const classDeclaration = constructorDeclaration.parent; + if (classDeclaration.name) return [classDeclaration.name]; + // If the class declaration doesn't have a name, it should have a default modifier. + // We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault` + const defaultModifier = Debug.checkDefined( + findModifier(classDeclaration, SyntaxKind.DefaultKeyword), + "Nameless class declaration should be a default export"); + return [defaultModifier]; + case SyntaxKind.ClassExpression: + const classExpression = constructorDeclaration.parent; + const variableDeclaration = constructorDeclaration.parent.parent; + const className = classExpression.name; + if (className) return [className, variableDeclaration.name]; + return [variableDeclaration.name]; } +} - interface ValidConstructor extends ConstructorDeclaration { - parent: ClassDeclaration | (ClassExpression & { parent: ValidVariableDeclaration }); - parameters: NodeArray; - body: FunctionBody; +function getFunctionNames(functionDeclaration: ValidFunctionDeclaration): Node[] { + switch (functionDeclaration.kind) { + case SyntaxKind.FunctionDeclaration: + if (functionDeclaration.name) return [functionDeclaration.name]; + // If the function declaration doesn't have a name, it should have a default modifier. + // We validated this in `isValidFunctionDeclaration` through `hasNameOrDefault` + const defaultModifier = Debug.checkDefined( + findModifier(functionDeclaration, SyntaxKind.DefaultKeyword), + "Nameless function declaration should be a default export"); + return [defaultModifier]; + case SyntaxKind.MethodDeclaration: + return [functionDeclaration.name]; + case SyntaxKind.Constructor: + const ctrKeyword = Debug.checkDefined( + findChildOfKind(functionDeclaration, SyntaxKind.ConstructorKeyword, functionDeclaration.getSourceFile()), + "Constructor declaration should have constructor keyword"); + if (functionDeclaration.parent.kind === SyntaxKind.ClassExpression) { + const variableDeclaration = functionDeclaration.parent.parent; + return [variableDeclaration.name, ctrKeyword]; + } + return [ctrKeyword]; + case SyntaxKind.ArrowFunction: + return [functionDeclaration.parent.name]; + case SyntaxKind.FunctionExpression: + if (functionDeclaration.name) return [functionDeclaration.name, functionDeclaration.parent.name]; + return [functionDeclaration.parent.name]; + default: + return Debug.assertNever(functionDeclaration, `Unexpected function declaration kind ${(functionDeclaration as ValidFunctionDeclaration).kind}`); } +} - interface ValidFunction extends FunctionDeclaration { - parameters: NodeArray; - body: FunctionBody; - } +type ValidParameterNodeArray = NodeArray; - interface ValidMethod extends MethodDeclaration { - parameters: NodeArray; - body: FunctionBody; - } +interface ValidVariableDeclaration extends VariableDeclaration { + name: Identifier; + type: undefined; +} - interface ValidFunctionExpression extends FunctionExpression { - parent: ValidVariableDeclaration; - parameters: NodeArray; - } +interface ValidConstructor extends ConstructorDeclaration { + parent: ClassDeclaration | (ClassExpression & { parent: ValidVariableDeclaration }); + parameters: NodeArray; + body: FunctionBody; +} - interface ValidArrowFunction extends ArrowFunction { - parent: ValidVariableDeclaration; - parameters: NodeArray; - } +interface ValidFunction extends FunctionDeclaration { + parameters: NodeArray; + body: FunctionBody; +} - interface ValidMethodSignature extends MethodSignature { - parameters: NodeArray; - } +interface ValidMethod extends MethodDeclaration { + parameters: NodeArray; + body: FunctionBody; +} - type ValidFunctionDeclaration = ValidConstructor | ValidFunction | ValidMethod | ValidArrowFunction | ValidFunctionExpression; +interface ValidFunctionExpression extends FunctionExpression { + parent: ValidVariableDeclaration; + parameters: NodeArray; +} - interface ValidParameterDeclaration extends ParameterDeclaration { - name: Identifier; - modifiers: undefined; - illegalDecorators: undefined; - } +interface ValidArrowFunction extends ArrowFunction { + parent: ValidVariableDeclaration; + parameters: NodeArray; +} - interface GroupedReferences { - functionCalls: (CallExpression | NewExpression)[]; - declarations: Node[]; - signature?: ValidMethodSignature; - classReferences?: ClassReferences; - valid: boolean; - } - interface ClassReferences { - accessExpressions: Node[]; - typeUsages: Node[]; - } +interface ValidMethodSignature extends MethodSignature { + parameters: NodeArray; +} + +type ValidFunctionDeclaration = ValidConstructor | ValidFunction | ValidMethod | ValidArrowFunction | ValidFunctionExpression; + +interface ValidParameterDeclaration extends ParameterDeclaration { + name: Identifier; + modifiers: undefined; + illegalDecorators: undefined; +} + +interface GroupedReferences { + functionCalls: (CallExpression | NewExpression)[]; + declarations: Node[]; + signature?: ValidMethodSignature; + classReferences?: ClassReferences; + valid: boolean; +} +interface ClassReferences { + accessExpressions: Node[]; + typeUsages: Node[]; } diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 82c7452f81923..f3bff71ac5849 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -1,251 +1,258 @@ -/* @internal */ -namespace ts.refactor.convertStringOrTemplateLiteral { - const refactorName = "Convert to template string"; - const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_string); - - const convertStringAction = { - name: refactorName, - description: refactorDescription, - kind: "refactor.rewrite.string" - }; - registerRefactor(refactorName, { - kinds: [convertStringAction.kind], - getEditsForAction: getRefactorEditsToConvertToTemplateString, - getAvailableActions: getRefactorActionsToConvertToTemplateString - }); - - function getRefactorActionsToConvertToTemplateString(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const { file, startPosition } = context; - const node = getNodeOrParentOfParentheses(file, startPosition); - const maybeBinary = getParentBinaryExpression(node); - const refactorInfo: ApplicableRefactorInfo = { name: refactorName, description: refactorDescription, actions: [] }; - - if (isBinaryExpression(maybeBinary) && treeToArray(maybeBinary).isValidConcatenation) { - refactorInfo.actions.push(convertStringAction); - return [refactorInfo]; - } - else if (context.preferences.provideRefactorNotApplicableReason) { - refactorInfo.actions.push({ ...convertStringAction, - notApplicableReason: getLocaleSpecificMessage(Diagnostics.Can_only_convert_string_concatenation) - }); - return [refactorInfo]; - } - return emptyArray; +import { + ApplicableRefactorInfo, BinaryExpression, BinaryOperator, copyTrailingAsLeadingComments, copyTrailingComments, + Debug, Diagnostics, emptyArray, Expression, factory, findAncestor, getLocaleSpecificMessage, getTextOfNode, + getTokenAtPosition, getTrailingCommentRanges, isBinaryExpression, isNoSubstitutionTemplateLiteral, + isParenthesizedExpression, isStringLiteral, isStringLiteralLike, isTemplateExpression, isTemplateHead, + isTemplateMiddle, map, Node, ParenthesizedExpression, RefactorContext, RefactorEditInfo, SourceFile, SyntaxKind, + TemplateHead, TemplateMiddle, TemplateSpan, TemplateTail, textChanges, Token, +} from "../_namespaces/ts"; +import { registerRefactor } from "../_namespaces/ts.refactor"; + +const refactorName = "Convert to template string"; +const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_string); + +const convertStringAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.string" +}; +registerRefactor(refactorName, { + kinds: [convertStringAction.kind], + getEditsForAction: getRefactorEditsToConvertToTemplateString, + getAvailableActions: getRefactorActionsToConvertToTemplateString +}); + +function getRefactorActionsToConvertToTemplateString(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const { file, startPosition } = context; + const node = getNodeOrParentOfParentheses(file, startPosition); + const maybeBinary = getParentBinaryExpression(node); + const refactorInfo: ApplicableRefactorInfo = { name: refactorName, description: refactorDescription, actions: [] }; + + if (isBinaryExpression(maybeBinary) && treeToArray(maybeBinary).isValidConcatenation) { + refactorInfo.actions.push(convertStringAction); + return [refactorInfo]; } + else if (context.preferences.provideRefactorNotApplicableReason) { + refactorInfo.actions.push({ ...convertStringAction, + notApplicableReason: getLocaleSpecificMessage(Diagnostics.Can_only_convert_string_concatenation) + }); + return [refactorInfo]; + } + return emptyArray; +} - function getNodeOrParentOfParentheses(file: SourceFile, startPosition: number) { - const node = getTokenAtPosition(file, startPosition); - const nestedBinary = getParentBinaryExpression(node); - const isNonStringBinary = !treeToArray(nestedBinary).isValidConcatenation; - - if ( - isNonStringBinary && - isParenthesizedExpression(nestedBinary.parent) && - isBinaryExpression(nestedBinary.parent.parent) - ) { - return nestedBinary.parent.parent; - } - return node; +function getNodeOrParentOfParentheses(file: SourceFile, startPosition: number) { + const node = getTokenAtPosition(file, startPosition); + const nestedBinary = getParentBinaryExpression(node); + const isNonStringBinary = !treeToArray(nestedBinary).isValidConcatenation; + + if ( + isNonStringBinary && + isParenthesizedExpression(nestedBinary.parent) && + isBinaryExpression(nestedBinary.parent.parent) + ) { + return nestedBinary.parent.parent; } + return node; +} - function getRefactorEditsToConvertToTemplateString(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const { file, startPosition } = context; - const node = getNodeOrParentOfParentheses(file, startPosition); +function getRefactorEditsToConvertToTemplateString(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const { file, startPosition } = context; + const node = getNodeOrParentOfParentheses(file, startPosition); - switch (actionName) { - case refactorDescription: - return { edits: getEditsForToTemplateLiteral(context, node) }; - default: - return Debug.fail("invalid action"); - } + switch (actionName) { + case refactorDescription: + return { edits: getEditsForToTemplateLiteral(context, node) }; + default: + return Debug.fail("invalid action"); } +} - function getEditsForToTemplateLiteral(context: RefactorContext, node: Node) { - const maybeBinary = getParentBinaryExpression(node); - const file = context.file; +function getEditsForToTemplateLiteral(context: RefactorContext, node: Node) { + const maybeBinary = getParentBinaryExpression(node); + const file = context.file; - const templateLiteral = nodesToTemplate(treeToArray(maybeBinary), file); - const trailingCommentRanges = getTrailingCommentRanges(file.text, maybeBinary.end); + const templateLiteral = nodesToTemplate(treeToArray(maybeBinary), file); + const trailingCommentRanges = getTrailingCommentRanges(file.text, maybeBinary.end); - if (trailingCommentRanges) { - const lastComment = trailingCommentRanges[trailingCommentRanges.length - 1]; - const trailingRange = { pos: trailingCommentRanges[0].pos, end: lastComment.end }; + if (trailingCommentRanges) { + const lastComment = trailingCommentRanges[trailingCommentRanges.length - 1]; + const trailingRange = { pos: trailingCommentRanges[0].pos, end: lastComment.end }; - // since suppressTrailingTrivia(maybeBinary) does not work, the trailing comment is removed manually - // otherwise it would have the trailing comment twice - return textChanges.ChangeTracker.with(context, t => { - t.deleteRange(file, trailingRange); - t.replaceNode(file, maybeBinary, templateLiteral); - }); - } - else { - return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); - } + // since suppressTrailingTrivia(maybeBinary) does not work, the trailing comment is removed manually + // otherwise it would have the trailing comment twice + return textChanges.ChangeTracker.with(context, t => { + t.deleteRange(file, trailingRange); + t.replaceNode(file, maybeBinary, templateLiteral); + }); } - - function isNotEqualsOperator(node: BinaryExpression) { - return node.operatorToken.kind !== SyntaxKind.EqualsToken; + else { + return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); } +} - function getParentBinaryExpression(expr: Node) { - const container = findAncestor(expr.parent, n => { - switch (n.kind) { - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return false; - case SyntaxKind.TemplateExpression: - case SyntaxKind.BinaryExpression: - return !(isBinaryExpression(n.parent) && isNotEqualsOperator(n.parent)); - default: - return "quit"; - } - }); - - return (container || expr) as Expression; - } +function isNotEqualsOperator(node: BinaryExpression) { + return node.operatorToken.kind !== SyntaxKind.EqualsToken; +} - function treeToArray(current: Expression) { - const loop = (current: Node): { nodes: Expression[], operators: Token[], hasString: boolean, validOperators: boolean} => { - if (!isBinaryExpression(current)) { - return { nodes: [current as Expression], operators: [], validOperators: true, - hasString: isStringLiteral(current) || isNoSubstitutionTemplateLiteral(current) }; - } - const { nodes, operators, hasString: leftHasString, validOperators: leftOperatorValid } = loop(current.left); +function getParentBinaryExpression(expr: Node) { + const container = findAncestor(expr.parent, n => { + switch (n.kind) { + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return false; + case SyntaxKind.TemplateExpression: + case SyntaxKind.BinaryExpression: + return !(isBinaryExpression(n.parent) && isNotEqualsOperator(n.parent)); + default: + return "quit"; + } + }); - if (!(leftHasString || isStringLiteral(current.right) || isTemplateExpression(current.right))) { - return { nodes: [current], operators: [], hasString: false, validOperators: true }; - } + return (container || expr) as Expression; +} - const currentOperatorValid = current.operatorToken.kind === SyntaxKind.PlusToken; - const validOperators = leftOperatorValid && currentOperatorValid; +function treeToArray(current: Expression) { + const loop = (current: Node): { nodes: Expression[], operators: Token[], hasString: boolean, validOperators: boolean} => { + if (!isBinaryExpression(current)) { + return { nodes: [current as Expression], operators: [], validOperators: true, + hasString: isStringLiteral(current) || isNoSubstitutionTemplateLiteral(current) }; + } + const { nodes, operators, hasString: leftHasString, validOperators: leftOperatorValid } = loop(current.left); - nodes.push(current.right); - operators.push(current.operatorToken); + if (!(leftHasString || isStringLiteral(current.right) || isTemplateExpression(current.right))) { + return { nodes: [current], operators: [], hasString: false, validOperators: true }; + } - return { nodes, operators, hasString: true, validOperators }; - }; - const { nodes, operators, validOperators, hasString } = loop(current); - return { nodes, operators, isValidConcatenation: validOperators && hasString }; - } + const currentOperatorValid = current.operatorToken.kind === SyntaxKind.PlusToken; + const validOperators = leftOperatorValid && currentOperatorValid; - // to copy comments following the operator - // "foo" + /* comment */ "bar" - const copyTrailingOperatorComments = (operators: Token[], file: SourceFile) => (index: number, targetNode: Node) => { - if (index < operators.length) { - copyTrailingComments(operators[index], targetNode, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - } - }; + nodes.push(current.right); + operators.push(current.operatorToken); - // to copy comments following the string - // "foo" /* comment */ + "bar" /* comment */ + "bar2" - const copyCommentFromMultiNode = (nodes: readonly Expression[], file: SourceFile, copyOperatorComments: (index: number, targetNode: Node) => void) => - (indexes: number[], targetNode: Node) => { - while (indexes.length > 0) { - const index = indexes.shift()!; - copyTrailingComments(nodes[index], targetNode, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - copyOperatorComments(index, targetNode); - } + return { nodes, operators, hasString: true, validOperators }; }; + const { nodes, operators, validOperators, hasString } = loop(current); + return { nodes, operators, isValidConcatenation: validOperators && hasString }; +} - function escapeRawStringForTemplate(s: string) { - // Escaping for $s in strings that are to be used in template strings - // Naive implementation: replace \x by itself and otherwise $ and ` by \$ and \`. - // But to complicate it a bit, this should work for raw strings too. - return s.replace(/\\.|[$`]/g, m => m[0] === "\\" ? m : "\\" + m); - // Finally, a less-backslash-happy version can work too, doing only ${ instead of all $s: - // s.replace(/\\.|\${|`/g, m => m[0] === "\\" ? m : "\\" + m); - // but `\$${foo}` is likely more clear than the more-confusing-but-still-working `$${foo}`. +// to copy comments following the operator +// "foo" + /* comment */ "bar" +const copyTrailingOperatorComments = (operators: Token[], file: SourceFile) => (index: number, targetNode: Node) => { + if (index < operators.length) { + copyTrailingComments(operators[index], targetNode, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); } - - function getRawTextOfTemplate(node: TemplateHead | TemplateMiddle | TemplateTail) { - // in these cases the right side is ${ - const rightShaving = isTemplateHead(node) || isTemplateMiddle(node) ? -2 : -1; - return getTextOfNode(node).slice(1, rightShaving); +}; + +// to copy comments following the string +// "foo" /* comment */ + "bar" /* comment */ + "bar2" +const copyCommentFromMultiNode = (nodes: readonly Expression[], file: SourceFile, copyOperatorComments: (index: number, targetNode: Node) => void) => +(indexes: number[], targetNode: Node) => { + while (indexes.length > 0) { + const index = indexes.shift()!; + copyTrailingComments(nodes[index], targetNode, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); + copyOperatorComments(index, targetNode); } +}; + +function escapeRawStringForTemplate(s: string) { + // Escaping for $s in strings that are to be used in template strings + // Naive implementation: replace \x by itself and otherwise $ and ` by \$ and \`. + // But to complicate it a bit, this should work for raw strings too. + return s.replace(/\\.|[$`]/g, m => m[0] === "\\" ? m : "\\" + m); + // Finally, a less-backslash-happy version can work too, doing only ${ instead of all $s: + // s.replace(/\\.|\${|`/g, m => m[0] === "\\" ? m : "\\" + m); + // but `\$${foo}` is likely more clear than the more-confusing-but-still-working `$${foo}`. +} - function concatConsecutiveString(index: number, nodes: readonly Expression[]): [nextIndex: number, text: string, rawText: string, usedIndexes: number[]] { - const indexes = []; - let text = "", rawText = ""; - while (index < nodes.length) { - const node = nodes[index]; - if (isStringLiteralLike(node)) { // includes isNoSubstitutionTemplateLiteral(node) - text += node.text; - rawText += escapeRawStringForTemplate(getTextOfNode(node).slice(1, -1)); - indexes.push(index); - index++; - } - else if (isTemplateExpression(node)) { - text += node.head.text; - rawText += getRawTextOfTemplate(node.head); - break; - } - else { - break; - } +function getRawTextOfTemplate(node: TemplateHead | TemplateMiddle | TemplateTail) { + // in these cases the right side is ${ + const rightShaving = isTemplateHead(node) || isTemplateMiddle(node) ? -2 : -1; + return getTextOfNode(node).slice(1, rightShaving); +} + +function concatConsecutiveString(index: number, nodes: readonly Expression[]): [nextIndex: number, text: string, rawText: string, usedIndexes: number[]] { + const indexes = []; + let text = "", rawText = ""; + while (index < nodes.length) { + const node = nodes[index]; + if (isStringLiteralLike(node)) { // includes isNoSubstitutionTemplateLiteral(node) + text += node.text; + rawText += escapeRawStringForTemplate(getTextOfNode(node).slice(1, -1)); + indexes.push(index); + index++; + } + else if (isTemplateExpression(node)) { + text += node.head.text; + rawText += getRawTextOfTemplate(node.head); + break; + } + else { + break; } - return [index, text, rawText, indexes]; } + return [index, text, rawText, indexes]; +} - function nodesToTemplate({ nodes, operators }: { nodes: readonly Expression[], operators: Token[] }, file: SourceFile) { - const copyOperatorComments = copyTrailingOperatorComments(operators, file); - const copyCommentFromStringLiterals = copyCommentFromMultiNode(nodes, file, copyOperatorComments); - const [begin, headText, rawHeadText, headIndexes] = concatConsecutiveString(0, nodes); +function nodesToTemplate({ nodes, operators }: { nodes: readonly Expression[], operators: Token[] }, file: SourceFile) { + const copyOperatorComments = copyTrailingOperatorComments(operators, file); + const copyCommentFromStringLiterals = copyCommentFromMultiNode(nodes, file, copyOperatorComments); + const [begin, headText, rawHeadText, headIndexes] = concatConsecutiveString(0, nodes); - if (begin === nodes.length) { - const noSubstitutionTemplateLiteral = factory.createNoSubstitutionTemplateLiteral(headText, rawHeadText); - copyCommentFromStringLiterals(headIndexes, noSubstitutionTemplateLiteral); - return noSubstitutionTemplateLiteral; - } + if (begin === nodes.length) { + const noSubstitutionTemplateLiteral = factory.createNoSubstitutionTemplateLiteral(headText, rawHeadText); + copyCommentFromStringLiterals(headIndexes, noSubstitutionTemplateLiteral); + return noSubstitutionTemplateLiteral; + } - const templateSpans: TemplateSpan[] = []; - const templateHead = factory.createTemplateHead(headText, rawHeadText); - copyCommentFromStringLiterals(headIndexes, templateHead); - - for (let i = begin; i < nodes.length; i++) { - const currentNode = getExpressionFromParenthesesOrExpression(nodes[i]); - copyOperatorComments(i, currentNode); - - const [newIndex, subsequentText, rawSubsequentText, stringIndexes] = concatConsecutiveString(i + 1, nodes); - i = newIndex - 1; - const isLast = i === nodes.length - 1; - - if (isTemplateExpression(currentNode)) { - const spans = map(currentNode.templateSpans, (span, index) => { - copyExpressionComments(span); - const isLastSpan = index === currentNode.templateSpans.length - 1; - const text = span.literal.text + (isLastSpan ? subsequentText : ""); - const rawText = getRawTextOfTemplate(span.literal) + (isLastSpan ? rawSubsequentText : ""); - return factory.createTemplateSpan(span.expression, isLast && isLastSpan - ? factory.createTemplateTail(text, rawText) - : factory.createTemplateMiddle(text, rawText)); - }); - templateSpans.push(...spans); - } - else { - const templatePart = isLast - ? factory.createTemplateTail(subsequentText, rawSubsequentText) - : factory.createTemplateMiddle(subsequentText, rawSubsequentText); - copyCommentFromStringLiterals(stringIndexes, templatePart); - templateSpans.push(factory.createTemplateSpan(currentNode, templatePart)); - } + const templateSpans: TemplateSpan[] = []; + const templateHead = factory.createTemplateHead(headText, rawHeadText); + copyCommentFromStringLiterals(headIndexes, templateHead); + + for (let i = begin; i < nodes.length; i++) { + const currentNode = getExpressionFromParenthesesOrExpression(nodes[i]); + copyOperatorComments(i, currentNode); + + const [newIndex, subsequentText, rawSubsequentText, stringIndexes] = concatConsecutiveString(i + 1, nodes); + i = newIndex - 1; + const isLast = i === nodes.length - 1; + + if (isTemplateExpression(currentNode)) { + const spans = map(currentNode.templateSpans, (span, index) => { + copyExpressionComments(span); + const isLastSpan = index === currentNode.templateSpans.length - 1; + const text = span.literal.text + (isLastSpan ? subsequentText : ""); + const rawText = getRawTextOfTemplate(span.literal) + (isLastSpan ? rawSubsequentText : ""); + return factory.createTemplateSpan(span.expression, isLast && isLastSpan + ? factory.createTemplateTail(text, rawText) + : factory.createTemplateMiddle(text, rawText)); + }); + templateSpans.push(...spans); + } + else { + const templatePart = isLast + ? factory.createTemplateTail(subsequentText, rawSubsequentText) + : factory.createTemplateMiddle(subsequentText, rawSubsequentText); + copyCommentFromStringLiterals(stringIndexes, templatePart); + templateSpans.push(factory.createTemplateSpan(currentNode, templatePart)); } - - return factory.createTemplateExpression(templateHead, templateSpans); } - // to copy comments following the opening & closing parentheses - // "foo" + ( /* comment */ 5 + 5 ) /* comment */ + "bar" - function copyExpressionComments(node: ParenthesizedExpression | TemplateSpan) { - const file = node.getSourceFile(); - copyTrailingComments(node, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - copyTrailingAsLeadingComments(node.expression, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); - } + return factory.createTemplateExpression(templateHead, templateSpans); +} - function getExpressionFromParenthesesOrExpression(node: Expression) { - if (isParenthesizedExpression(node)) { - copyExpressionComments(node); - node = node.expression; - } - return node; +// to copy comments following the opening & closing parentheses +// "foo" + ( /* comment */ 5 + 5 ) /* comment */ + "bar" +function copyExpressionComments(node: ParenthesizedExpression | TemplateSpan) { + const file = node.getSourceFile(); + copyTrailingComments(node, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); + copyTrailingAsLeadingComments(node.expression, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); +} + +function getExpressionFromParenthesesOrExpression(node: Expression) { + if (isParenthesizedExpression(node)) { + copyExpressionComments(node); + node = node.expression; } + return node; } diff --git a/src/services/refactors/convertToOptionalChainExpression.ts b/src/services/refactors/convertToOptionalChainExpression.ts index a8c13e42f94fc..5c8153ef5f876 100644 --- a/src/services/refactors/convertToOptionalChainExpression.ts +++ b/src/services/refactors/convertToOptionalChainExpression.ts @@ -1,300 +1,309 @@ -/* @internal */ -namespace ts.refactor.convertToOptionalChainExpression { - const refactorName = "Convert to optional chain expression"; - const convertToOptionalChainExpressionMessage = getLocaleSpecificMessage(Diagnostics.Convert_to_optional_chain_expression); - - const toOptionalChainAction = { - name: refactorName, - description: convertToOptionalChainExpressionMessage, - kind: "refactor.rewrite.expression.optionalChain", - }; - registerRefactor(refactorName, { - kinds: [toOptionalChainAction.kind], - getEditsForAction: getRefactorEditsToConvertToOptionalChain, - getAvailableActions: getRefactorActionsToConvertToOptionalChain, - }); - - function getRefactorActionsToConvertToOptionalChain(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const info = getInfo(context, context.triggerReason === "invoked"); - if (!info) return emptyArray; - - if (!isRefactorErrorInfo(info)) { - return [{ - name: refactorName, - description: convertToOptionalChainExpressionMessage, - actions: [toOptionalChainAction], - }]; - } - - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ - name: refactorName, - description: convertToOptionalChainExpressionMessage, - actions: [{ ...toOptionalChainAction, notApplicableReason: info.error }], - }]; - } - return emptyArray; +import { + ApplicableRefactorInfo, BinaryExpression, CallExpression, ConditionalExpression, createTextSpanFromBounds, Debug, + Diagnostics, ElementAccessExpression, emptyArray, Expression, ExpressionStatement, factory, + findTokenOnLeftOfPosition, getLocaleSpecificMessage, getRefactorContextSpan, getSingleVariableOfVariableStatement, + getTokenAtPosition, Identifier, isBinaryExpression, isCallExpression, isConditionalExpression, + isElementAccessExpression, isExpressionStatement, isIdentifier, isOptionalChain, isPropertyAccessExpression, + isReturnStatement, isStringOrNumericLiteralLike, isVariableStatement, Node, PropertyAccessExpression, + RefactorContext, RefactorEditInfo, ReturnStatement, skipParentheses, SourceFile, SyntaxKind, textChanges, TextSpan, + TypeChecker, VariableStatement, +} from "../_namespaces/ts"; +import { isRefactorErrorInfo, RefactorErrorInfo, registerRefactor } from "../_namespaces/ts.refactor"; + +const refactorName = "Convert to optional chain expression"; +const convertToOptionalChainExpressionMessage = getLocaleSpecificMessage(Diagnostics.Convert_to_optional_chain_expression); + +const toOptionalChainAction = { + name: refactorName, + description: convertToOptionalChainExpressionMessage, + kind: "refactor.rewrite.expression.optionalChain", +}; +registerRefactor(refactorName, { + kinds: [toOptionalChainAction.kind], + getEditsForAction: getRefactorEditsToConvertToOptionalChain, + getAvailableActions: getRefactorActionsToConvertToOptionalChain, +}); + +function getRefactorActionsToConvertToOptionalChain(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const info = getInfo(context, context.triggerReason === "invoked"); + if (!info) return emptyArray; + + if (!isRefactorErrorInfo(info)) { + return [{ + name: refactorName, + description: convertToOptionalChainExpressionMessage, + actions: [toOptionalChainAction], + }]; } - function getRefactorEditsToConvertToOptionalChain(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const info = getInfo(context); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - const edits = textChanges.ChangeTracker.with(context, t => - doChange(context.file, context.program.getTypeChecker(), t, info, actionName) - ); - return { edits, renameFilename: undefined, renameLocation: undefined }; + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: refactorName, + description: convertToOptionalChainExpressionMessage, + actions: [{ ...toOptionalChainAction, notApplicableReason: info.error }], + }]; } + return emptyArray; +} - type Occurrence = PropertyAccessExpression | ElementAccessExpression | Identifier; +function getRefactorEditsToConvertToOptionalChain(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const info = getInfo(context); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); + const edits = textChanges.ChangeTracker.with(context, t => + doChange(context.file, context.program.getTypeChecker(), t, info, actionName) + ); + return { edits, renameFilename: undefined, renameLocation: undefined }; +} - interface OptionalChainInfo { - finalExpression: PropertyAccessExpression | ElementAccessExpression | CallExpression, - occurrences: Occurrence[], - expression: ValidExpression, - } +type Occurrence = PropertyAccessExpression | ElementAccessExpression | Identifier; - type ValidExpressionOrStatement = ValidExpression | ValidStatement; +interface OptionalChainInfo { + finalExpression: PropertyAccessExpression | ElementAccessExpression | CallExpression, + occurrences: Occurrence[], + expression: ValidExpression, +} - /** - * Types for which a "Convert to optional chain refactor" are offered. - */ - type ValidExpression = BinaryExpression | ConditionalExpression; +type ValidExpressionOrStatement = ValidExpression | ValidStatement; - /** - * Types of statements which are likely to include a valid expression for extraction. - */ - type ValidStatement = ExpressionStatement | ReturnStatement | VariableStatement; +/** + * Types for which a "Convert to optional chain refactor" are offered. + */ +type ValidExpression = BinaryExpression | ConditionalExpression; - function isValidExpression(node: Node): node is ValidExpression { - return isBinaryExpression(node) || isConditionalExpression(node); - } +/** + * Types of statements which are likely to include a valid expression for extraction. + */ +type ValidStatement = ExpressionStatement | ReturnStatement | VariableStatement; - function isValidStatement(node: Node): node is ValidStatement { - return isExpressionStatement(node) || isReturnStatement(node) || isVariableStatement(node); - } +function isValidExpression(node: Node): node is ValidExpression { + return isBinaryExpression(node) || isConditionalExpression(node); +} - function isValidExpressionOrStatement(node: Node): node is ValidExpressionOrStatement { - return isValidExpression(node) || isValidStatement(node); - } +function isValidStatement(node: Node): node is ValidStatement { + return isExpressionStatement(node) || isReturnStatement(node) || isVariableStatement(node); +} - function getInfo(context: RefactorContext, considerEmptySpans = true): OptionalChainInfo | RefactorErrorInfo | undefined { - const { file, program } = context; - const span = getRefactorContextSpan(context); +function isValidExpressionOrStatement(node: Node): node is ValidExpressionOrStatement { + return isValidExpression(node) || isValidStatement(node); +} - const forEmptySpan = span.length === 0; - if (forEmptySpan && !considerEmptySpans) return undefined; +function getInfo(context: RefactorContext, considerEmptySpans = true): OptionalChainInfo | RefactorErrorInfo | undefined { + const { file, program } = context; + const span = getRefactorContextSpan(context); - // selecting fo[|o && foo.ba|]r should be valid, so adjust span to fit start and end tokens - const startToken = getTokenAtPosition(file, span.start); - const endToken = findTokenOnLeftOfPosition(file, span.start + span.length); - const adjustedSpan = createTextSpanFromBounds(startToken.pos, endToken && endToken.end >= startToken.pos ? endToken.getEnd() : startToken.getEnd()); + const forEmptySpan = span.length === 0; + if (forEmptySpan && !considerEmptySpans) return undefined; - const parent = forEmptySpan ? getValidParentNodeOfEmptySpan(startToken) : getValidParentNodeContainingSpan(startToken, adjustedSpan); - const expression = parent && isValidExpressionOrStatement(parent) ? getExpression(parent) : undefined; - if (!expression) return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; + // selecting fo[|o && foo.ba|]r should be valid, so adjust span to fit start and end tokens + const startToken = getTokenAtPosition(file, span.start); + const endToken = findTokenOnLeftOfPosition(file, span.start + span.length); + const adjustedSpan = createTextSpanFromBounds(startToken.pos, endToken && endToken.end >= startToken.pos ? endToken.getEnd() : startToken.getEnd()); - const checker = program.getTypeChecker(); - return isConditionalExpression(expression) ? getConditionalInfo(expression, checker) : getBinaryInfo(expression); - } + const parent = forEmptySpan ? getValidParentNodeOfEmptySpan(startToken) : getValidParentNodeContainingSpan(startToken, adjustedSpan); + const expression = parent && isValidExpressionOrStatement(parent) ? getExpression(parent) : undefined; + if (!expression) return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; - function getConditionalInfo(expression: ConditionalExpression, checker: TypeChecker): OptionalChainInfo | RefactorErrorInfo | undefined { - const condition = expression.condition; - const finalExpression = getFinalExpressionInChain(expression.whenTrue); + const checker = program.getTypeChecker(); + return isConditionalExpression(expression) ? getConditionalInfo(expression, checker) : getBinaryInfo(expression); +} - if (!finalExpression || checker.isNullableType(checker.getTypeAtLocation(finalExpression))) { - return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; - } +function getConditionalInfo(expression: ConditionalExpression, checker: TypeChecker): OptionalChainInfo | RefactorErrorInfo | undefined { + const condition = expression.condition; + const finalExpression = getFinalExpressionInChain(expression.whenTrue); - if ((isPropertyAccessExpression(condition) || isIdentifier(condition)) - && getMatchingStart(condition, finalExpression.expression)) { - return { finalExpression, occurrences: [condition], expression }; - } - else if (isBinaryExpression(condition)) { - const occurrences = getOccurrencesInExpression(finalExpression.expression, condition); - return occurrences ? { finalExpression, occurrences, expression } : - { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_matching_access_expressions) }; - } + if (!finalExpression || checker.isNullableType(checker.getTypeAtLocation(finalExpression))) { + return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; } - function getBinaryInfo(expression: BinaryExpression): OptionalChainInfo | RefactorErrorInfo | undefined { - if (expression.operatorToken.kind !== SyntaxKind.AmpersandAmpersandToken) { - return { error: getLocaleSpecificMessage(Diagnostics.Can_only_convert_logical_AND_access_chains) }; - } - const finalExpression = getFinalExpressionInChain(expression.right); - - if (!finalExpression) return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; - - const occurrences = getOccurrencesInExpression(finalExpression.expression, expression.left); + if ((isPropertyAccessExpression(condition) || isIdentifier(condition)) + && getMatchingStart(condition, finalExpression.expression)) { + return { finalExpression, occurrences: [condition], expression }; + } + else if (isBinaryExpression(condition)) { + const occurrences = getOccurrencesInExpression(finalExpression.expression, condition); return occurrences ? { finalExpression, occurrences, expression } : { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_matching_access_expressions) }; } +} - /** - * Gets a list of property accesses that appear in matchTo and occur in sequence in expression. - */ - function getOccurrencesInExpression(matchTo: Expression, expression: Expression): Occurrence[] | undefined { - const occurrences: Occurrence[] = []; - while (isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { - const match = getMatchingStart(skipParentheses(matchTo), skipParentheses(expression.right)); - if (!match) { - break; - } - occurrences.push(match); - matchTo = match; - expression = expression.left; - } - const finalMatch = getMatchingStart(matchTo, expression); - if (finalMatch) { - occurrences.push(finalMatch); - } - return occurrences.length > 0 ? occurrences: undefined; +function getBinaryInfo(expression: BinaryExpression): OptionalChainInfo | RefactorErrorInfo | undefined { + if (expression.operatorToken.kind !== SyntaxKind.AmpersandAmpersandToken) { + return { error: getLocaleSpecificMessage(Diagnostics.Can_only_convert_logical_AND_access_chains) }; } + const finalExpression = getFinalExpressionInChain(expression.right); - /** - * Returns subchain if chain begins with subchain syntactically. - */ - function getMatchingStart(chain: Expression, subchain: Expression): PropertyAccessExpression | ElementAccessExpression | Identifier | undefined { - if (!isIdentifier(subchain) && !isPropertyAccessExpression(subchain) && !isElementAccessExpression(subchain)) { - return undefined; - } - return chainStartsWith(chain, subchain) ? subchain : undefined; - } + if (!finalExpression) return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; - /** - * Returns true if chain begins with subchain syntactically. - */ - function chainStartsWith(chain: Node, subchain: Node): boolean { - // skip until we find a matching identifier. - while (isCallExpression(chain) || isPropertyAccessExpression(chain) || isElementAccessExpression(chain)) { - if (getTextOfChainNode(chain) === getTextOfChainNode(subchain)) break; - chain = chain.expression; - } - // check that the chains match at each access. Call chains in subchain are not valid. - while ((isPropertyAccessExpression(chain) && isPropertyAccessExpression(subchain)) || - (isElementAccessExpression(chain) && isElementAccessExpression(subchain))) { - if (getTextOfChainNode(chain) !== getTextOfChainNode(subchain)) return false; - chain = chain.expression; - subchain = subchain.expression; + const occurrences = getOccurrencesInExpression(finalExpression.expression, expression.left); + return occurrences ? { finalExpression, occurrences, expression } : + { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_matching_access_expressions) }; +} + +/** + * Gets a list of property accesses that appear in matchTo and occur in sequence in expression. + */ +function getOccurrencesInExpression(matchTo: Expression, expression: Expression): Occurrence[] | undefined { + const occurrences: Occurrence[] = []; + while (isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + const match = getMatchingStart(skipParentheses(matchTo), skipParentheses(expression.right)); + if (!match) { + break; } - // check if we have reached a final identifier. - return isIdentifier(chain) && isIdentifier(subchain) && chain.getText() === subchain.getText(); + occurrences.push(match); + matchTo = match; + expression = expression.left; + } + const finalMatch = getMatchingStart(matchTo, expression); + if (finalMatch) { + occurrences.push(finalMatch); } + return occurrences.length > 0 ? occurrences: undefined; +} - function getTextOfChainNode(node: Node): string | undefined { - if (isIdentifier(node) || isStringOrNumericLiteralLike(node)) { - return node.getText(); - } - if (isPropertyAccessExpression(node)) { - return getTextOfChainNode(node.name); - } - if (isElementAccessExpression(node)) { - return getTextOfChainNode(node.argumentExpression); - } +/** + * Returns subchain if chain begins with subchain syntactically. + */ +function getMatchingStart(chain: Expression, subchain: Expression): PropertyAccessExpression | ElementAccessExpression | Identifier | undefined { + if (!isIdentifier(subchain) && !isPropertyAccessExpression(subchain) && !isElementAccessExpression(subchain)) { return undefined; } + return chainStartsWith(chain, subchain) ? subchain : undefined; +} - /** - * Find the least ancestor of the input node that is a valid type for extraction and contains the input span. - */ - function getValidParentNodeContainingSpan(node: Node, span: TextSpan): ValidExpressionOrStatement | undefined { - while (node.parent) { - if (isValidExpressionOrStatement(node) && span.length !== 0 && node.end >= span.start + span.length) { - return node; - } - node = node.parent; - } - return undefined; +/** + * Returns true if chain begins with subchain syntactically. + */ +function chainStartsWith(chain: Node, subchain: Node): boolean { + // skip until we find a matching identifier. + while (isCallExpression(chain) || isPropertyAccessExpression(chain) || isElementAccessExpression(chain)) { + if (getTextOfChainNode(chain) === getTextOfChainNode(subchain)) break; + chain = chain.expression; + } + // check that the chains match at each access. Call chains in subchain are not valid. + while ((isPropertyAccessExpression(chain) && isPropertyAccessExpression(subchain)) || + (isElementAccessExpression(chain) && isElementAccessExpression(subchain))) { + if (getTextOfChainNode(chain) !== getTextOfChainNode(subchain)) return false; + chain = chain.expression; + subchain = subchain.expression; } + // check if we have reached a final identifier. + return isIdentifier(chain) && isIdentifier(subchain) && chain.getText() === subchain.getText(); +} - /** - * Finds an ancestor of the input node that is a valid type for extraction, skipping subexpressions. - */ - function getValidParentNodeOfEmptySpan(node: Node): ValidExpressionOrStatement | undefined { - while (node.parent) { - if (isValidExpressionOrStatement(node) && !isValidExpressionOrStatement(node.parent)) { - return node; - } - node = node.parent; - } - return undefined; +function getTextOfChainNode(node: Node): string | undefined { + if (isIdentifier(node) || isStringOrNumericLiteralLike(node)) { + return node.getText(); + } + if (isPropertyAccessExpression(node)) { + return getTextOfChainNode(node.name); } + if (isElementAccessExpression(node)) { + return getTextOfChainNode(node.argumentExpression); + } + return undefined; +} - /** - * Gets an expression of valid extraction type from a valid statement or expression. - */ - function getExpression(node: ValidExpressionOrStatement): ValidExpression | undefined { - if (isValidExpression(node)) { +/** + * Find the least ancestor of the input node that is a valid type for extraction and contains the input span. + */ +function getValidParentNodeContainingSpan(node: Node, span: TextSpan): ValidExpressionOrStatement | undefined { + while (node.parent) { + if (isValidExpressionOrStatement(node) && span.length !== 0 && node.end >= span.start + span.length) { return node; } - if (isVariableStatement(node)) { - const variable = getSingleVariableOfVariableStatement(node); - const initializer = variable?.initializer; - return initializer && isValidExpression(initializer) ? initializer : undefined; - } - return node.expression && isValidExpression(node.expression) ? node.expression : undefined; + node = node.parent; } + return undefined; +} - /** - * Gets a property access expression which may be nested inside of a binary expression. The final - * expression in an && chain will occur as the right child of the parent binary expression, unless - * it is followed by a different binary operator. - * @param node the right child of a binary expression or a call expression. - */ - function getFinalExpressionInChain(node: Expression): CallExpression | PropertyAccessExpression | ElementAccessExpression | undefined { - // foo && |foo.bar === 1|; - here the right child of the && binary expression is another binary expression. - // the rightmost member of the && chain should be the leftmost child of that expression. - node = skipParentheses(node); - if (isBinaryExpression(node)) { - return getFinalExpressionInChain(node.left); - } - // foo && |foo.bar()()| - nested calls are treated like further accesses. - else if ((isPropertyAccessExpression(node) || isElementAccessExpression(node) || isCallExpression(node)) && !isOptionalChain(node)) { +/** + * Finds an ancestor of the input node that is a valid type for extraction, skipping subexpressions. + */ +function getValidParentNodeOfEmptySpan(node: Node): ValidExpressionOrStatement | undefined { + while (node.parent) { + if (isValidExpressionOrStatement(node) && !isValidExpressionOrStatement(node.parent)) { return node; } - return undefined; + node = node.parent; + } + return undefined; +} + +/** + * Gets an expression of valid extraction type from a valid statement or expression. + */ +function getExpression(node: ValidExpressionOrStatement): ValidExpression | undefined { + if (isValidExpression(node)) { + return node; + } + if (isVariableStatement(node)) { + const variable = getSingleVariableOfVariableStatement(node); + const initializer = variable?.initializer; + return initializer && isValidExpression(initializer) ? initializer : undefined; } + return node.expression && isValidExpression(node.expression) ? node.expression : undefined; +} - /** - * Creates an access chain from toConvert with '?.' accesses at expressions appearing in occurrences. - */ - function convertOccurrences(checker: TypeChecker, toConvert: Expression, occurrences: Occurrence[]): Expression { - if (isPropertyAccessExpression(toConvert) || isElementAccessExpression(toConvert) || isCallExpression(toConvert)) { - const chain = convertOccurrences(checker, toConvert.expression, occurrences); - const lastOccurrence = occurrences.length > 0 ? occurrences[occurrences.length - 1] : undefined; - const isOccurrence = lastOccurrence?.getText() === toConvert.expression.getText(); - if (isOccurrence) occurrences.pop(); - if (isCallExpression(toConvert)) { - return isOccurrence ? - factory.createCallChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.typeArguments, toConvert.arguments) : - factory.createCallChain(chain, toConvert.questionDotToken, toConvert.typeArguments, toConvert.arguments); - } - else if (isPropertyAccessExpression(toConvert)) { - return isOccurrence ? - factory.createPropertyAccessChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.name) : - factory.createPropertyAccessChain(chain, toConvert.questionDotToken, toConvert.name); - } - else if (isElementAccessExpression(toConvert)) { - return isOccurrence ? - factory.createElementAccessChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.argumentExpression) : - factory.createElementAccessChain(chain, toConvert.questionDotToken, toConvert.argumentExpression); - } +/** + * Gets a property access expression which may be nested inside of a binary expression. The final + * expression in an && chain will occur as the right child of the parent binary expression, unless + * it is followed by a different binary operator. + * @param node the right child of a binary expression or a call expression. + */ +function getFinalExpressionInChain(node: Expression): CallExpression | PropertyAccessExpression | ElementAccessExpression | undefined { + // foo && |foo.bar === 1|; - here the right child of the && binary expression is another binary expression. + // the rightmost member of the && chain should be the leftmost child of that expression. + node = skipParentheses(node); + if (isBinaryExpression(node)) { + return getFinalExpressionInChain(node.left); + } + // foo && |foo.bar()()| - nested calls are treated like further accesses. + else if ((isPropertyAccessExpression(node) || isElementAccessExpression(node) || isCallExpression(node)) && !isOptionalChain(node)) { + return node; + } + return undefined; +} + +/** + * Creates an access chain from toConvert with '?.' accesses at expressions appearing in occurrences. + */ +function convertOccurrences(checker: TypeChecker, toConvert: Expression, occurrences: Occurrence[]): Expression { + if (isPropertyAccessExpression(toConvert) || isElementAccessExpression(toConvert) || isCallExpression(toConvert)) { + const chain = convertOccurrences(checker, toConvert.expression, occurrences); + const lastOccurrence = occurrences.length > 0 ? occurrences[occurrences.length - 1] : undefined; + const isOccurrence = lastOccurrence?.getText() === toConvert.expression.getText(); + if (isOccurrence) occurrences.pop(); + if (isCallExpression(toConvert)) { + return isOccurrence ? + factory.createCallChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.typeArguments, toConvert.arguments) : + factory.createCallChain(chain, toConvert.questionDotToken, toConvert.typeArguments, toConvert.arguments); + } + else if (isPropertyAccessExpression(toConvert)) { + return isOccurrence ? + factory.createPropertyAccessChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.name) : + factory.createPropertyAccessChain(chain, toConvert.questionDotToken, toConvert.name); + } + else if (isElementAccessExpression(toConvert)) { + return isOccurrence ? + factory.createElementAccessChain(chain, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.argumentExpression) : + factory.createElementAccessChain(chain, toConvert.questionDotToken, toConvert.argumentExpression); } - return toConvert; } + return toConvert; +} - function doChange(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, info: OptionalChainInfo, _actionName: string): void { - const { finalExpression, occurrences, expression } = info; - const firstOccurrence = occurrences[occurrences.length - 1]; - const convertedChain = convertOccurrences(checker, finalExpression, occurrences); - if (convertedChain && (isPropertyAccessExpression(convertedChain) || isElementAccessExpression(convertedChain) || isCallExpression(convertedChain))) { - if (isBinaryExpression(expression)) { - changes.replaceNodeRange(sourceFile, firstOccurrence, finalExpression, convertedChain); - } - else if (isConditionalExpression(expression)) { - changes.replaceNode(sourceFile, expression, - factory.createBinaryExpression(convertedChain, factory.createToken(SyntaxKind.QuestionQuestionToken), expression.whenFalse) - ); - } +function doChange(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, info: OptionalChainInfo, _actionName: string): void { + const { finalExpression, occurrences, expression } = info; + const firstOccurrence = occurrences[occurrences.length - 1]; + const convertedChain = convertOccurrences(checker, finalExpression, occurrences); + if (convertedChain && (isPropertyAccessExpression(convertedChain) || isElementAccessExpression(convertedChain) || isCallExpression(convertedChain))) { + if (isBinaryExpression(expression)) { + changes.replaceNodeRange(sourceFile, firstOccurrence, finalExpression, convertedChain); + } + else if (isConditionalExpression(expression)) { + changes.replaceNode(sourceFile, expression, + factory.createBinaryExpression(convertedChain, factory.createToken(SyntaxKind.QuestionQuestionToken), expression.whenFalse) + ); } } } diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index d58044953ac4a..473b84111121b 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -1,2051 +1,2092 @@ -/* @internal */ -namespace ts.refactor.extractSymbol { - const refactorName = "Extract Symbol"; - - const extractConstantAction = { - name: "Extract Constant", - description: getLocaleSpecificMessage(Diagnostics.Extract_constant), - kind: "refactor.extract.constant", - }; - const extractFunctionAction = { - name: "Extract Function", - description: getLocaleSpecificMessage(Diagnostics.Extract_function), - kind: "refactor.extract.function", - }; - registerRefactor(refactorName, { - kinds: [ - extractConstantAction.kind, - extractFunctionAction.kind - ], - getEditsForAction: getRefactorEditsToExtractSymbol, - getAvailableActions: getRefactorActionsToExtractSymbol, - }); - - /** - * Compute the associated code actions - * Exported for tests. - */ - export function getRefactorActionsToExtractSymbol(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const requestedRefactor = context.kind; - const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), context.triggerReason === "invoked"); - const targetRange = rangeToExtract.targetRange; - - if (targetRange === undefined) { - if (!rangeToExtract.errors || rangeToExtract.errors.length === 0 || !context.preferences.provideRefactorNotApplicableReason) { - return emptyArray; - } - - const errors = []; - if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) { - errors.push({ - name: refactorName, - description: extractFunctionAction.description, - actions: [{ ...extractFunctionAction, notApplicableReason: getStringError(rangeToExtract.errors) }] - }); - } - if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) { - errors.push({ - name: refactorName, - description: extractConstantAction.description, - actions: [{ ...extractConstantAction, notApplicableReason: getStringError(rangeToExtract.errors) }] - }); - } - return errors; - } - - const extractions = getPossibleExtractions(targetRange, context); - if (extractions === undefined) { - // No extractions possible +import { + __String, ANONYMOUS, ApplicableRefactorInfo, arrayFrom, assertType, BindingElement, Block, BlockLike, + BreakStatement, CancellationToken, canHaveModifiers, CharacterCodes, ClassElement, ClassLikeDeclaration, codefix, + compareProperties, compareStringsCaseSensitive, compareValues, contains, ContinueStatement, createDiagnosticForNode, + createFileDiagnostic, Debug, Declaration, Diagnostic, DiagnosticCategory, DiagnosticMessage, Diagnostics, EmitFlags, + emptyArray, EntityName, ESMap, Expression, ExpressionStatement, factory, find, findAncestor, + findFirstNonJsxWhitespaceToken, findTokenOnLeftOfPosition, first, firstOrUndefined, forEachChild, + formatStringFromArgs, FunctionDeclaration, FunctionLikeDeclaration, getContainingClass, getContainingFunction, + getEffectiveTypeParameterDeclarations, getEmitScriptTarget, getEnclosingBlockScopeContainer, + getLocaleSpecificMessage, getModifiers, getNodeId, getParentNodeInSpan, getRefactorContextSpan, getRenameLocation, + getSymbolId, getSynthesizedDeepClone, getThisContainer, getUniqueName, hasEffectiveModifier, hasSyntacticModifier, + Identifier, isArray, isArrowFunction, isAssignmentExpression, isBinaryExpression, isBlock, isBlockScope, + isCaseClause, isClassLike, isConstructorDeclaration, isDeclaration, isDeclarationWithTypeParameters, + isElementAccessExpression, isExpression, isExpressionNode, isExpressionStatement, isFunctionBody, + isFunctionExpression, isFunctionLike, isFunctionLikeDeclaration, isIdentifier, isInJSFile, isIterationStatement, + isJsxAttribute, isJsxElement, isJsxFragment, isJsxSelfClosingElement, isKeyword, isModuleBlock, + isParenthesizedTypeNode, isPartOfTypeNode, isPrivateIdentifier, isPropertyAccessExpression, isPropertyDeclaration, + isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isSourceFile, isStatement, isStatic, + isStringLiteral, isSwitchStatement, isThis, isUnaryExpressionWithWrite, isUnionTypeNode, isVariableDeclaration, + isVariableDeclarationList, isVariableStatement, LabeledStatement, last, map, Map, MethodDeclaration, Modifier, + ModifierFlags, ModuleBlock, NamedDeclaration, Node, NodeBuilderFlags, NodeFlags, nullTransformationContext, + ObjectLiteralElementLike, ParameterDeclaration, positionIsSynthesized, PropertyAccessExpression, + rangeContainsStartEnd, ReadonlyESMap, RefactorActionInfo, RefactorContext, RefactorEditInfo, setEmitFlags, + ShorthandPropertyAssignment, SignatureKind, singleOrUndefined, skipParentheses, SourceFile, Statement, + StringLiteral, suppressLeadingAndTrailingTrivia, Symbol, SymbolFlags, SyntaxKind, textChanges, TextRange, TextSpan, + textSpanEnd, TryStatement, Type, TypeChecker, TypeElement, TypeFlags, TypeLiteralNode, TypeNode, TypeParameter, + TypeParameterDeclaration, VariableDeclaration, visitEachChild, visitNode, visitNodes, VisitResult, +} from "../_namespaces/ts"; +import { refactorKindBeginsWith, registerRefactor } from "../_namespaces/ts.refactor"; + +const refactorName = "Extract Symbol"; + +const extractConstantAction = { + name: "Extract Constant", + description: getLocaleSpecificMessage(Diagnostics.Extract_constant), + kind: "refactor.extract.constant", +}; +const extractFunctionAction = { + name: "Extract Function", + description: getLocaleSpecificMessage(Diagnostics.Extract_function), + kind: "refactor.extract.function", +}; +registerRefactor(refactorName, { + kinds: [ + extractConstantAction.kind, + extractFunctionAction.kind + ], + getEditsForAction: getRefactorEditsToExtractSymbol, + getAvailableActions: getRefactorActionsToExtractSymbol, +}); + +/** + * Compute the associated code actions + * Exported for tests. + * + * @internal + */ +export function getRefactorActionsToExtractSymbol(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const requestedRefactor = context.kind; + const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), context.triggerReason === "invoked"); + const targetRange = rangeToExtract.targetRange; + + if (targetRange === undefined) { + if (!rangeToExtract.errors || rangeToExtract.errors.length === 0 || !context.preferences.provideRefactorNotApplicableReason) { return emptyArray; } - const functionActions: RefactorActionInfo[] = []; - const usedFunctionNames = new Map(); - let innermostErrorFunctionAction: RefactorActionInfo | undefined; + const errors = []; + if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) { + errors.push({ + name: refactorName, + description: extractFunctionAction.description, + actions: [{ ...extractFunctionAction, notApplicableReason: getStringError(rangeToExtract.errors) }] + }); + } + if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) { + errors.push({ + name: refactorName, + description: extractConstantAction.description, + actions: [{ ...extractConstantAction, notApplicableReason: getStringError(rangeToExtract.errors) }] + }); + } + return errors; + } - const constantActions: RefactorActionInfo[] = []; - const usedConstantNames = new Map(); - let innermostErrorConstantAction: RefactorActionInfo | undefined; + const extractions = getPossibleExtractions(targetRange, context); + if (extractions === undefined) { + // No extractions possible + return emptyArray; + } - let i = 0; - for (const { functionExtraction, constantExtraction } of extractions) { - if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) { - const description = functionExtraction.description; - if (functionExtraction.errors.length === 0) { - // Don't issue refactorings with duplicated names. - // Scopes come back in "innermost first" order, so extractions will - // preferentially go into nearer scopes - if (!usedFunctionNames.has(description)) { - usedFunctionNames.set(description, true); - functionActions.push({ - description, - name: `function_scope_${i}`, - kind: extractFunctionAction.kind - }); - } - } - else if (!innermostErrorFunctionAction) { - innermostErrorFunctionAction = { + const functionActions: RefactorActionInfo[] = []; + const usedFunctionNames = new Map(); + let innermostErrorFunctionAction: RefactorActionInfo | undefined; + + const constantActions: RefactorActionInfo[] = []; + const usedConstantNames = new Map(); + let innermostErrorConstantAction: RefactorActionInfo | undefined; + + let i = 0; + for (const { functionExtraction, constantExtraction } of extractions) { + if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) { + const description = functionExtraction.description; + if (functionExtraction.errors.length === 0) { + // Don't issue refactorings with duplicated names. + // Scopes come back in "innermost first" order, so extractions will + // preferentially go into nearer scopes + if (!usedFunctionNames.has(description)) { + usedFunctionNames.set(description, true); + functionActions.push({ description, name: `function_scope_${i}`, - notApplicableReason: getStringError(functionExtraction.errors), kind: extractFunctionAction.kind - }; + }); } } - - if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) { - const description = constantExtraction.description; - if (constantExtraction.errors.length === 0) { - // Don't issue refactorings with duplicated names. - // Scopes come back in "innermost first" order, so extractions will - // preferentially go into nearer scopes - if (!usedConstantNames.has(description)) { - usedConstantNames.set(description, true); - constantActions.push({ - description, - name: `constant_scope_${i}`, - kind: extractConstantAction.kind - }); - } - } - else if (!innermostErrorConstantAction) { - innermostErrorConstantAction = { + else if (!innermostErrorFunctionAction) { + innermostErrorFunctionAction = { + description, + name: `function_scope_${i}`, + notApplicableReason: getStringError(functionExtraction.errors), + kind: extractFunctionAction.kind + }; + } + } + + if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) { + const description = constantExtraction.description; + if (constantExtraction.errors.length === 0) { + // Don't issue refactorings with duplicated names. + // Scopes come back in "innermost first" order, so extractions will + // preferentially go into nearer scopes + if (!usedConstantNames.has(description)) { + usedConstantNames.set(description, true); + constantActions.push({ description, name: `constant_scope_${i}`, - notApplicableReason: getStringError(constantExtraction.errors), kind: extractConstantAction.kind - }; + }); } } - - // *do* increment i anyway because we'll look for the i-th scope - // later when actually doing the refactoring if the user requests it - i++; + else if (!innermostErrorConstantAction) { + innermostErrorConstantAction = { + description, + name: `constant_scope_${i}`, + notApplicableReason: getStringError(constantExtraction.errors), + kind: extractConstantAction.kind + }; + } } - const infos: ApplicableRefactorInfo[] = []; + // *do* increment i anyway because we'll look for the i-th scope + // later when actually doing the refactoring if the user requests it + i++; + } - if (functionActions.length) { - infos.push({ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_function), - actions: functionActions, - }); - } - else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorFunctionAction) { - infos.push({ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_function), - actions: [ innermostErrorFunctionAction ] - }); - } + const infos: ApplicableRefactorInfo[] = []; - if (constantActions.length) { - infos.push({ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_constant), - actions: constantActions - }); - } - else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorConstantAction) { - infos.push({ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_constant), - actions: [ innermostErrorConstantAction ] - }); + if (functionActions.length) { + infos.push({ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_function), + actions: functionActions, + }); + } + else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorFunctionAction) { + infos.push({ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_function), + actions: [ innermostErrorFunctionAction ] + }); + } + + if (constantActions.length) { + infos.push({ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_constant), + actions: constantActions + }); + } + else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorConstantAction) { + infos.push({ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_constant), + actions: [ innermostErrorConstantAction ] + }); + } + + return infos.length ? infos : emptyArray; + + function getStringError(errors: readonly Diagnostic[]) { + let error = errors[0].messageText; + if (typeof error !== "string") { + error = error.messageText; } + return error; + } +} - return infos.length ? infos : emptyArray; +/** + * Exported for tests + * + * @internal + */ +export function getRefactorEditsToExtractSymbol(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context)); + const targetRange = rangeToExtract.targetRange!; // TODO:GH#18217 + + const parsedFunctionIndexMatch = /^function_scope_(\d+)$/.exec(actionName); + if (parsedFunctionIndexMatch) { + const index = +parsedFunctionIndexMatch[1]; + Debug.assert(isFinite(index), "Expected to parse a finite number from the function scope index"); + return getFunctionExtractionAtIndex(targetRange, context, index); + } - function getStringError(errors: readonly Diagnostic[]) { - let error = errors[0].messageText; - if (typeof error !== "string") { - error = error.messageText; - } - return error; - } - } - - /* Exported for tests */ - export function getRefactorEditsToExtractSymbol(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context)); - const targetRange = rangeToExtract.targetRange!; // TODO:GH#18217 - - const parsedFunctionIndexMatch = /^function_scope_(\d+)$/.exec(actionName); - if (parsedFunctionIndexMatch) { - const index = +parsedFunctionIndexMatch[1]; - Debug.assert(isFinite(index), "Expected to parse a finite number from the function scope index"); - return getFunctionExtractionAtIndex(targetRange, context, index); - } - - const parsedConstantIndexMatch = /^constant_scope_(\d+)$/.exec(actionName); - if (parsedConstantIndexMatch) { - const index = +parsedConstantIndexMatch[1]; - Debug.assert(isFinite(index), "Expected to parse a finite number from the constant scope index"); - return getConstantExtractionAtIndex(targetRange, context, index); - } - - Debug.fail("Unrecognized action name"); - } - - // Move these into diagnostic messages if they become user-facing - export namespace Messages { - function createMessage(message: string): DiagnosticMessage { - return { message, code: 0, category: DiagnosticCategory.Message, key: message }; - } - - export const cannotExtractRange: DiagnosticMessage = createMessage("Cannot extract range."); - export const cannotExtractImport: DiagnosticMessage = createMessage("Cannot extract import statement."); - export const cannotExtractSuper: DiagnosticMessage = createMessage("Cannot extract super call."); - export const cannotExtractJSDoc: DiagnosticMessage = createMessage("Cannot extract JSDoc."); - export const cannotExtractEmpty: DiagnosticMessage = createMessage("Cannot extract empty range."); - export const expressionExpected: DiagnosticMessage = createMessage("expression expected."); - export const uselessConstantType: DiagnosticMessage = createMessage("No reason to extract constant of type."); - export const statementOrExpressionExpected: DiagnosticMessage = createMessage("Statement or expression expected."); - export const cannotExtractRangeContainingConditionalBreakOrContinueStatements: DiagnosticMessage = createMessage("Cannot extract range containing conditional break or continue statements."); - export const cannotExtractRangeContainingConditionalReturnStatement: DiagnosticMessage = createMessage("Cannot extract range containing conditional return statement."); - export const cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange: DiagnosticMessage = createMessage("Cannot extract range containing labeled break or continue with target outside of the range."); - export const cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators: DiagnosticMessage = createMessage("Cannot extract range containing writes to references located outside of the target range in generators."); - export const typeWillNotBeVisibleInTheNewScope = createMessage("Type will not visible in the new scope."); - export const functionWillNotBeVisibleInTheNewScope = createMessage("Function will not visible in the new scope."); - export const cannotExtractIdentifier = createMessage("Select more than a single identifier."); - export const cannotExtractExportedEntity = createMessage("Cannot extract exported declaration"); - export const cannotWriteInExpression = createMessage("Cannot write back side-effects when extracting an expression"); - export const cannotExtractReadonlyPropertyInitializerOutsideConstructor = createMessage("Cannot move initialization of read-only class property outside of the constructor"); - export const cannotExtractAmbientBlock = createMessage("Cannot extract code from ambient contexts"); - export const cannotAccessVariablesFromNestedScopes = createMessage("Cannot access variables from nested scopes"); - export const cannotExtractToJSClass = createMessage("Cannot extract constant to a class scope in JS"); - export const cannotExtractToExpressionArrowFunction = createMessage("Cannot extract constant to an arrow function without a block"); - export const cannotExtractFunctionsContainingThisToMethod = createMessage("Cannot extract functions containing this to method"); - } - - enum RangeFacts { - None = 0, - HasReturn = 1 << 0, - IsGenerator = 1 << 1, - IsAsyncFunction = 1 << 2, - UsesThis = 1 << 3, - UsesThisInFunction = 1 << 4, - /** - * The range is in a function which needs the 'static' modifier in a class - */ - InStaticRegion = 1 << 5, + const parsedConstantIndexMatch = /^constant_scope_(\d+)$/.exec(actionName); + if (parsedConstantIndexMatch) { + const index = +parsedConstantIndexMatch[1]; + Debug.assert(isFinite(index), "Expected to parse a finite number from the constant scope index"); + return getConstantExtractionAtIndex(targetRange, context, index); } - /** - * Represents an expression or a list of statements that should be extracted with some extra information - */ - interface TargetRange { - readonly range: Expression | Statement[]; - readonly facts: RangeFacts; - /** - * If `this` is referring to a function instead of class, we need to retrieve its type. - */ - readonly thisNode: Node | undefined; + Debug.fail("Unrecognized action name"); +} + +// Move these into diagnostic messages if they become user-facing +/** @internal */ +export namespace Messages { + function createMessage(message: string): DiagnosticMessage { + return { message, code: 0, category: DiagnosticCategory.Message, key: message }; } + export const cannotExtractRange: DiagnosticMessage = createMessage("Cannot extract range."); + export const cannotExtractImport: DiagnosticMessage = createMessage("Cannot extract import statement."); + export const cannotExtractSuper: DiagnosticMessage = createMessage("Cannot extract super call."); + export const cannotExtractJSDoc: DiagnosticMessage = createMessage("Cannot extract JSDoc."); + export const cannotExtractEmpty: DiagnosticMessage = createMessage("Cannot extract empty range."); + export const expressionExpected: DiagnosticMessage = createMessage("expression expected."); + export const uselessConstantType: DiagnosticMessage = createMessage("No reason to extract constant of type."); + export const statementOrExpressionExpected: DiagnosticMessage = createMessage("Statement or expression expected."); + export const cannotExtractRangeContainingConditionalBreakOrContinueStatements: DiagnosticMessage = createMessage("Cannot extract range containing conditional break or continue statements."); + export const cannotExtractRangeContainingConditionalReturnStatement: DiagnosticMessage = createMessage("Cannot extract range containing conditional return statement."); + export const cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange: DiagnosticMessage = createMessage("Cannot extract range containing labeled break or continue with target outside of the range."); + export const cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators: DiagnosticMessage = createMessage("Cannot extract range containing writes to references located outside of the target range in generators."); + export const typeWillNotBeVisibleInTheNewScope = createMessage("Type will not visible in the new scope."); + export const functionWillNotBeVisibleInTheNewScope = createMessage("Function will not visible in the new scope."); + export const cannotExtractIdentifier = createMessage("Select more than a single identifier."); + export const cannotExtractExportedEntity = createMessage("Cannot extract exported declaration"); + export const cannotWriteInExpression = createMessage("Cannot write back side-effects when extracting an expression"); + export const cannotExtractReadonlyPropertyInitializerOutsideConstructor = createMessage("Cannot move initialization of read-only class property outside of the constructor"); + export const cannotExtractAmbientBlock = createMessage("Cannot extract code from ambient contexts"); + export const cannotAccessVariablesFromNestedScopes = createMessage("Cannot access variables from nested scopes"); + export const cannotExtractToJSClass = createMessage("Cannot extract constant to a class scope in JS"); + export const cannotExtractToExpressionArrowFunction = createMessage("Cannot extract constant to an arrow function without a block"); + export const cannotExtractFunctionsContainingThisToMethod = createMessage("Cannot extract functions containing this to method"); +} + +/** @internal */ +export enum RangeFacts { + None = 0, + HasReturn = 1 << 0, + IsGenerator = 1 << 1, + IsAsyncFunction = 1 << 2, + UsesThis = 1 << 3, + UsesThisInFunction = 1 << 4, /** - * Result of 'getRangeToExtract' operation: contains either a range or a list of errors - */ - type RangeToExtract = { - readonly targetRange?: never; - readonly errors: readonly Diagnostic[]; - } | { - readonly targetRange: TargetRange; - readonly errors?: never; - }; - - /* - * Scopes that can store newly extracted method + * The range is in a function which needs the 'static' modifier in a class */ - type Scope = FunctionLikeDeclaration | SourceFile | ModuleBlock | ClassLikeDeclaration; + InStaticRegion = 1 << 5, +} +/** + * Represents an expression or a list of statements that should be extracted with some extra information + * + * @internal + */ +export interface TargetRange { + readonly range: Expression | Statement[]; + readonly facts: RangeFacts; /** - * getRangeToExtract takes a span inside a text file and returns either an expression or an array - * of statements representing the minimum set of nodes needed to extract the entire span. This - * process may fail, in which case a set of errors is returned instead. These errors are shown to - * users if they have the provideRefactorNotApplicableReason option set. + * If `this` is referring to a function instead of class, we need to retrieve its type. */ - // exported only for tests - export function getRangeToExtract(sourceFile: SourceFile, span: TextSpan, invoked = true): RangeToExtract { - const { length } = span; - if (length === 0 && !invoked) { - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractEmpty)] }; - } - const cursorRequest = length === 0 && invoked; + readonly thisNode: Node | undefined; +} - const startToken = findFirstNonJsxWhitespaceToken(sourceFile, span.start); - const endToken = findTokenOnLeftOfPosition(sourceFile, textSpanEnd(span)); - /* If the refactoring command is invoked through a keyboard action it's safe to assume that the user is actively looking for - refactoring actions at the span location. As they may not know the exact range that will trigger a refactoring, we expand the - searched span to cover a real node range making it more likely that something useful will show up. */ - const adjustedSpan = startToken && endToken && invoked ? getAdjustedSpanFromNodes(startToken, endToken, sourceFile) : span; +/** + * Result of 'getRangeToExtract' operation: contains either a range or a list of errors + * + * @internal + */ +export type RangeToExtract = { + readonly targetRange?: never; + readonly errors: readonly Diagnostic[]; +} | { + readonly targetRange: TargetRange; + readonly errors?: never; +}; + +/* + * Scopes that can store newly extracted method + */ +type Scope = FunctionLikeDeclaration | SourceFile | ModuleBlock | ClassLikeDeclaration; + +// exported only for tests +/** + * getRangeToExtract takes a span inside a text file and returns either an expression or an array + * of statements representing the minimum set of nodes needed to extract the entire span. This + * process may fail, in which case a set of errors is returned instead. These errors are shown to + * users if they have the provideRefactorNotApplicableReason option set. + * + * @internal + */ +export function getRangeToExtract(sourceFile: SourceFile, span: TextSpan, invoked = true): RangeToExtract { + const { length } = span; + if (length === 0 && !invoked) { + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractEmpty)] }; + } + const cursorRequest = length === 0 && invoked; - // Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span. - // This may fail (e.g. you select two statements in the root of a source file) - const start = cursorRequest ? getExtractableParent(startToken) : getParentNodeInSpan(startToken, sourceFile, adjustedSpan); + const startToken = findFirstNonJsxWhitespaceToken(sourceFile, span.start); + const endToken = findTokenOnLeftOfPosition(sourceFile, textSpanEnd(span)); + /* If the refactoring command is invoked through a keyboard action it's safe to assume that the user is actively looking for + refactoring actions at the span location. As they may not know the exact range that will trigger a refactoring, we expand the + searched span to cover a real node range making it more likely that something useful will show up. */ + const adjustedSpan = startToken && endToken && invoked ? getAdjustedSpanFromNodes(startToken, endToken, sourceFile) : span; - // Do the same for the ending position - const end = cursorRequest ? start : getParentNodeInSpan(endToken, sourceFile, adjustedSpan); + // Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span. + // This may fail (e.g. you select two statements in the root of a source file) + const start = cursorRequest ? getExtractableParent(startToken) : getParentNodeInSpan(startToken, sourceFile, adjustedSpan); - // We'll modify these flags as we walk the tree to collect data - // about what things need to be done as part of the extraction. - let rangeFacts = RangeFacts.None; + // Do the same for the ending position + const end = cursorRequest ? start : getParentNodeInSpan(endToken, sourceFile, adjustedSpan); - let thisNode: Node | undefined; + // We'll modify these flags as we walk the tree to collect data + // about what things need to be done as part of the extraction. + let rangeFacts = RangeFacts.None; - if (!start || !end) { - // cannot find either start or end node - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; - } + let thisNode: Node | undefined; - if (start.flags & NodeFlags.JSDoc) { - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractJSDoc)] }; - } + if (!start || !end) { + // cannot find either start or end node + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; + } - if (start.parent !== end.parent) { - // start and end nodes belong to different subtrees + if (start.flags & NodeFlags.JSDoc) { + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractJSDoc)] }; + } + + if (start.parent !== end.parent) { + // start and end nodes belong to different subtrees + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; + } + + if (start !== end) { + // start and end should be statements and parent should be either block or a source file + if (!isBlockLike(start.parent)) { return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; } - - if (start !== end) { - // start and end should be statements and parent should be either block or a source file - if (!isBlockLike(start.parent)) { - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; - } - const statements: Statement[] = []; - for (const statement of start.parent.statements) { - if (statement === start || statements.length) { - const errors = checkNode(statement); - if (errors) { - return { errors }; - } - statements.push(statement); - } - if (statement === end) { - break; + const statements: Statement[] = []; + for (const statement of start.parent.statements) { + if (statement === start || statements.length) { + const errors = checkNode(statement); + if (errors) { + return { errors }; } + statements.push(statement); } - - if (!statements.length) { - // https://github.com/Microsoft/TypeScript/issues/20559 - // Ranges like [|case 1: break;|] will fail to populate `statements` because - // they will never find `start` in `start.parent.statements`. - // Consider: We could support ranges like [|case 1:|] by refining them to just - // the expression. - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; + if (statement === end) { + break; } - - return { targetRange: { range: statements, facts: rangeFacts, thisNode } }; } - if (isReturnStatement(start) && !start.expression) { - // Makes no sense to extract an expression-less return statement. + if (!statements.length) { + // https://github.com/Microsoft/TypeScript/issues/20559 + // Ranges like [|case 1: break;|] will fail to populate `statements` because + // they will never find `start` in `start.parent.statements`. + // Consider: We could support ranges like [|case 1:|] by refining them to just + // the expression. return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; } - // We have a single node (start) - const node = refineNode(start); + return { targetRange: { range: statements, facts: rangeFacts, thisNode } }; + } + + if (isReturnStatement(start) && !start.expression) { + // Makes no sense to extract an expression-less return statement. + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] }; + } - const errors = checkRootNode(node) || checkNode(node); - if (errors) { - return { errors }; - } - return { targetRange: { range: getStatementOrExpressionRange(node)!, facts: rangeFacts, thisNode } }; // TODO: GH#18217 + // We have a single node (start) + const node = refineNode(start); - /** - * Attempt to refine the extraction node (generally, by shrinking it) to produce better results. - * @param node The unrefined extraction node. - */ - function refineNode(node: Node): Node { - if (isReturnStatement(node)) { - if (node.expression) { - return node.expression; + const errors = checkRootNode(node) || checkNode(node); + if (errors) { + return { errors }; + } + return { targetRange: { range: getStatementOrExpressionRange(node)!, facts: rangeFacts, thisNode } }; // TODO: GH#18217 + + /** + * Attempt to refine the extraction node (generally, by shrinking it) to produce better results. + * @param node The unrefined extraction node. + */ + function refineNode(node: Node): Node { + if (isReturnStatement(node)) { + if (node.expression) { + return node.expression; + } + } + else if (isVariableStatement(node) || isVariableDeclarationList(node)) { + const declarations = isVariableStatement(node) ? node.declarationList.declarations : node.declarations; + let numInitializers = 0; + let lastInitializer: Expression | undefined; + for (const declaration of declarations) { + if (declaration.initializer) { + numInitializers++; + lastInitializer = declaration.initializer; } } - else if (isVariableStatement(node) || isVariableDeclarationList(node)) { - const declarations = isVariableStatement(node) ? node.declarationList.declarations : node.declarations; - let numInitializers = 0; - let lastInitializer: Expression | undefined; - for (const declaration of declarations) { - if (declaration.initializer) { - numInitializers++; - lastInitializer = declaration.initializer; - } - } - if (numInitializers === 1) { - return lastInitializer!; - } - // No special handling if there are multiple initializers. + if (numInitializers === 1) { + return lastInitializer!; } - else if (isVariableDeclaration(node)) { - if (node.initializer) { - return node.initializer; - } + // No special handling if there are multiple initializers. + } + else if (isVariableDeclaration(node)) { + if (node.initializer) { + return node.initializer; } - return node; } + return node; + } - function checkRootNode(node: Node): Diagnostic[] | undefined { - if (isIdentifier(isExpressionStatement(node) ? node.expression : node)) { - return [createDiagnosticForNode(node, Messages.cannotExtractIdentifier)]; - } - return undefined; + function checkRootNode(node: Node): Diagnostic[] | undefined { + if (isIdentifier(isExpressionStatement(node) ? node.expression : node)) { + return [createDiagnosticForNode(node, Messages.cannotExtractIdentifier)]; } + return undefined; + } - function checkForStaticContext(nodeToCheck: Node, containingClass: Node) { - let current: Node = nodeToCheck; - while (current !== containingClass) { - if (current.kind === SyntaxKind.PropertyDeclaration) { - if (isStatic(current)) { - rangeFacts |= RangeFacts.InStaticRegion; - } - break; + function checkForStaticContext(nodeToCheck: Node, containingClass: Node) { + let current: Node = nodeToCheck; + while (current !== containingClass) { + if (current.kind === SyntaxKind.PropertyDeclaration) { + if (isStatic(current)) { + rangeFacts |= RangeFacts.InStaticRegion; } - else if (current.kind === SyntaxKind.Parameter) { - const ctorOrMethod = getContainingFunction(current)!; - if (ctorOrMethod.kind === SyntaxKind.Constructor) { - rangeFacts |= RangeFacts.InStaticRegion; - } - break; + break; + } + else if (current.kind === SyntaxKind.Parameter) { + const ctorOrMethod = getContainingFunction(current)!; + if (ctorOrMethod.kind === SyntaxKind.Constructor) { + rangeFacts |= RangeFacts.InStaticRegion; } - else if (current.kind === SyntaxKind.MethodDeclaration) { - if (isStatic(current)) { - rangeFacts |= RangeFacts.InStaticRegion; - } + break; + } + else if (current.kind === SyntaxKind.MethodDeclaration) { + if (isStatic(current)) { + rangeFacts |= RangeFacts.InStaticRegion; } - current = current.parent; } + current = current.parent; } + } - // Verifies whether we can actually extract this node or not. - function checkNode(nodeToCheck: Node): Diagnostic[] | undefined { - const enum PermittedJumps { - None = 0, - Break = 1 << 0, - Continue = 1 << 1, - Return = 1 << 2 - } + // Verifies whether we can actually extract this node or not. + function checkNode(nodeToCheck: Node): Diagnostic[] | undefined { + const enum PermittedJumps { + None = 0, + Break = 1 << 0, + Continue = 1 << 1, + Return = 1 << 2 + } - // We believe it's true because the node is from the (unmodified) tree. - Debug.assert(nodeToCheck.pos <= nodeToCheck.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (1)"); + // We believe it's true because the node is from the (unmodified) tree. + Debug.assert(nodeToCheck.pos <= nodeToCheck.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (1)"); - // For understanding how skipTrivia functioned: - Debug.assert(!positionIsSynthesized(nodeToCheck.pos), "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (2)"); + // For understanding how skipTrivia functioned: + Debug.assert(!positionIsSynthesized(nodeToCheck.pos), "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (2)"); - if (!isStatement(nodeToCheck) && !(isExpressionNode(nodeToCheck) && isExtractableExpression(nodeToCheck)) && !isStringLiteralJsxAttribute(nodeToCheck)) { - return [createDiagnosticForNode(nodeToCheck, Messages.statementOrExpressionExpected)]; - } + if (!isStatement(nodeToCheck) && !(isExpressionNode(nodeToCheck) && isExtractableExpression(nodeToCheck)) && !isStringLiteralJsxAttribute(nodeToCheck)) { + return [createDiagnosticForNode(nodeToCheck, Messages.statementOrExpressionExpected)]; + } - if (nodeToCheck.flags & NodeFlags.Ambient) { - return [createDiagnosticForNode(nodeToCheck, Messages.cannotExtractAmbientBlock)]; - } + if (nodeToCheck.flags & NodeFlags.Ambient) { + return [createDiagnosticForNode(nodeToCheck, Messages.cannotExtractAmbientBlock)]; + } - // If we're in a class, see whether we're in a static region (static property initializer, static method, class constructor parameter default) - const containingClass = getContainingClass(nodeToCheck); - if (containingClass) { - checkForStaticContext(nodeToCheck, containingClass); - } + // If we're in a class, see whether we're in a static region (static property initializer, static method, class constructor parameter default) + const containingClass = getContainingClass(nodeToCheck); + if (containingClass) { + checkForStaticContext(nodeToCheck, containingClass); + } - let errors: Diagnostic[] | undefined; - let permittedJumps = PermittedJumps.Return; - let seenLabels: __String[]; + let errors: Diagnostic[] | undefined; + let permittedJumps = PermittedJumps.Return; + let seenLabels: __String[]; - visit(nodeToCheck); + visit(nodeToCheck); - if (rangeFacts & RangeFacts.UsesThis) { - const container = getThisContainer(nodeToCheck, /** includeArrowFunctions */ false); - if ( - container.kind === SyntaxKind.FunctionDeclaration || - (container.kind === SyntaxKind.MethodDeclaration && container.parent.kind === SyntaxKind.ObjectLiteralExpression) || - container.kind === SyntaxKind.FunctionExpression - ) { - rangeFacts |= RangeFacts.UsesThisInFunction; - } + if (rangeFacts & RangeFacts.UsesThis) { + const container = getThisContainer(nodeToCheck, /** includeArrowFunctions */ false); + if ( + container.kind === SyntaxKind.FunctionDeclaration || + (container.kind === SyntaxKind.MethodDeclaration && container.parent.kind === SyntaxKind.ObjectLiteralExpression) || + container.kind === SyntaxKind.FunctionExpression + ) { + rangeFacts |= RangeFacts.UsesThisInFunction; } + } - return errors; + return errors; - function visit(node: Node) { - if (errors) { - // already found an error - can stop now - return true; - } - - if (isDeclaration(node)) { - const declaringNode = (node.kind === SyntaxKind.VariableDeclaration) ? node.parent.parent : node; - if (hasSyntacticModifier(declaringNode, ModifierFlags.Export)) { - // TODO: GH#18217 Silly to use `errors ||` since it's definitely not defined (see top of `visit`) - // Also, if we're only pushing one error, just use `let error: Diagnostic | undefined`! - // Also TODO: GH#19956 - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractExportedEntity)); - return true; - } - } + function visit(node: Node) { + if (errors) { + // already found an error - can stop now + return true; + } - // Some things can't be extracted in certain situations - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractImport)); - return true; - case SyntaxKind.ExportAssignment: - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractExportedEntity)); - return true; - case SyntaxKind.SuperKeyword: - // For a super *constructor call*, we have to be extracting the entire class, - // but a super *method call* simply implies a 'this' reference - if (node.parent.kind === SyntaxKind.CallExpression) { - // Super constructor call - const containingClass = getContainingClass(node); - if (containingClass === undefined || containingClass.pos < span.start || containingClass.end >= (span.start + span.length)) { - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractSuper)); - return true; - } - } - else { - rangeFacts |= RangeFacts.UsesThis; - thisNode = node; - } - break; - case SyntaxKind.ArrowFunction: - // check if arrow function uses this - forEachChild(node, function check(n) { - if (isThis(n)) { - rangeFacts |= RangeFacts.UsesThis; - thisNode = node; - } - else if (isClassLike(n) || (isFunctionLike(n) && !isArrowFunction(n))) { - return false; - } - else { - forEachChild(n, check); - } - }); - // falls through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.FunctionDeclaration: - if (isSourceFile(node.parent) && node.parent.externalModuleIndicator === undefined) { - // You cannot extract global declarations - (errors ||= []).push(createDiagnosticForNode(node, Messages.functionWillNotBeVisibleInTheNewScope)); - } - // falls through - case SyntaxKind.ClassExpression: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // do not dive into functions or classes - return false; + if (isDeclaration(node)) { + const declaringNode = (node.kind === SyntaxKind.VariableDeclaration) ? node.parent.parent : node; + if (hasSyntacticModifier(declaringNode, ModifierFlags.Export)) { + // TODO: GH#18217 Silly to use `errors ||` since it's definitely not defined (see top of `visit`) + // Also, if we're only pushing one error, just use `let error: Diagnostic | undefined`! + // Also TODO: GH#19956 + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractExportedEntity)); + return true; } + } - const savedPermittedJumps = permittedJumps; - switch (node.kind) { - case SyntaxKind.IfStatement: - permittedJumps &= ~PermittedJumps.Return; - break; - case SyntaxKind.TryStatement: - // forbid all jumps inside try blocks - permittedJumps = PermittedJumps.None; - break; - case SyntaxKind.Block: - if (node.parent && node.parent.kind === SyntaxKind.TryStatement && (node.parent as TryStatement).finallyBlock === node) { - // allow unconditional returns from finally blocks - permittedJumps = PermittedJumps.Return; - } - break; - case SyntaxKind.DefaultClause: - case SyntaxKind.CaseClause: - // allow unlabeled break inside case clauses - permittedJumps |= PermittedJumps.Break; - break; - default: - if (isIterationStatement(node, /*lookInLabeledStatements*/ false)) { - // allow unlabeled break/continue inside loops - permittedJumps |= PermittedJumps.Break | PermittedJumps.Continue; + // Some things can't be extracted in certain situations + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractImport)); + return true; + case SyntaxKind.ExportAssignment: + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractExportedEntity)); + return true; + case SyntaxKind.SuperKeyword: + // For a super *constructor call*, we have to be extracting the entire class, + // but a super *method call* simply implies a 'this' reference + if (node.parent.kind === SyntaxKind.CallExpression) { + // Super constructor call + const containingClass = getContainingClass(node); + if (containingClass === undefined || containingClass.pos < span.start || containingClass.end >= (span.start + span.length)) { + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractSuper)); + return true; } - break; - } - - switch (node.kind) { - case SyntaxKind.ThisType: - case SyntaxKind.ThisKeyword: + } + else { rangeFacts |= RangeFacts.UsesThis; thisNode = node; - break; - case SyntaxKind.LabeledStatement: { - const label = (node as LabeledStatement).label; - (seenLabels || (seenLabels = [])).push(label.escapedText); - forEachChild(node, visit); - seenLabels.pop(); - break; } - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: { - const label = (node as BreakStatement | ContinueStatement).label; - if (label) { - if (!contains(seenLabels, label.escapedText)) { - // attempts to jump to label that is not in range to be extracted - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange)); - } + break; + case SyntaxKind.ArrowFunction: + // check if arrow function uses this + forEachChild(node, function check(n) { + if (isThis(n)) { + rangeFacts |= RangeFacts.UsesThis; + thisNode = node; + } + else if (isClassLike(n) || (isFunctionLike(n) && !isArrowFunction(n))) { + return false; } else { - if (!(permittedJumps & (node.kind === SyntaxKind.BreakStatement ? PermittedJumps.Break : PermittedJumps.Continue))) { - // attempt to break or continue in a forbidden context - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements)); - } + forEachChild(n, check); } - break; + }); + // falls through + case SyntaxKind.ClassDeclaration: + case SyntaxKind.FunctionDeclaration: + if (isSourceFile(node.parent) && node.parent.externalModuleIndicator === undefined) { + // You cannot extract global declarations + (errors ||= []).push(createDiagnosticForNode(node, Messages.functionWillNotBeVisibleInTheNewScope)); + } + // falls through + case SyntaxKind.ClassExpression: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // do not dive into functions or classes + return false; + } + + const savedPermittedJumps = permittedJumps; + switch (node.kind) { + case SyntaxKind.IfStatement: + permittedJumps &= ~PermittedJumps.Return; + break; + case SyntaxKind.TryStatement: + // forbid all jumps inside try blocks + permittedJumps = PermittedJumps.None; + break; + case SyntaxKind.Block: + if (node.parent && node.parent.kind === SyntaxKind.TryStatement && (node.parent as TryStatement).finallyBlock === node) { + // allow unconditional returns from finally blocks + permittedJumps = PermittedJumps.Return; + } + break; + case SyntaxKind.DefaultClause: + case SyntaxKind.CaseClause: + // allow unlabeled break inside case clauses + permittedJumps |= PermittedJumps.Break; + break; + default: + if (isIterationStatement(node, /*lookInLabeledStatements*/ false)) { + // allow unlabeled break/continue inside loops + permittedJumps |= PermittedJumps.Break | PermittedJumps.Continue; } - case SyntaxKind.AwaitExpression: - rangeFacts |= RangeFacts.IsAsyncFunction; - break; - case SyntaxKind.YieldExpression: - rangeFacts |= RangeFacts.IsGenerator; - break; - case SyntaxKind.ReturnStatement: - if (permittedJumps & PermittedJumps.Return) { - rangeFacts |= RangeFacts.HasReturn; + break; + } + + switch (node.kind) { + case SyntaxKind.ThisType: + case SyntaxKind.ThisKeyword: + rangeFacts |= RangeFacts.UsesThis; + thisNode = node; + break; + case SyntaxKind.LabeledStatement: { + const label = (node as LabeledStatement).label; + (seenLabels || (seenLabels = [])).push(label.escapedText); + forEachChild(node, visit); + seenLabels.pop(); + break; + } + case SyntaxKind.BreakStatement: + case SyntaxKind.ContinueStatement: { + const label = (node as BreakStatement | ContinueStatement).label; + if (label) { + if (!contains(seenLabels, label.escapedText)) { + // attempts to jump to label that is not in range to be extracted + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange)); } - else { - (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalReturnStatement)); + } + else { + if (!(permittedJumps & (node.kind === SyntaxKind.BreakStatement ? PermittedJumps.Break : PermittedJumps.Continue))) { + // attempt to break or continue in a forbidden context + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements)); } - break; - default: - forEachChild(node, visit); - break; + } + break; } - - permittedJumps = savedPermittedJumps; + case SyntaxKind.AwaitExpression: + rangeFacts |= RangeFacts.IsAsyncFunction; + break; + case SyntaxKind.YieldExpression: + rangeFacts |= RangeFacts.IsGenerator; + break; + case SyntaxKind.ReturnStatement: + if (permittedJumps & PermittedJumps.Return) { + rangeFacts |= RangeFacts.HasReturn; + } + else { + (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalReturnStatement)); + } + break; + default: + forEachChild(node, visit); + break; } - } - } - /** - * Includes the final semicolon so that the span covers statements in cases where it would otherwise - * only cover the declaration list. - */ - function getAdjustedSpanFromNodes(startNode: Node, endNode: Node, sourceFile: SourceFile): TextSpan { - const start = startNode.getStart(sourceFile); - let end = endNode.getEnd(); - if (sourceFile.text.charCodeAt(end) === CharacterCodes.semicolon) { - end++; + permittedJumps = savedPermittedJumps; } - return { start, length: end - start }; } +} - function getStatementOrExpressionRange(node: Node): Statement[] | Expression | undefined { - if (isStatement(node)) { - return [node]; - } - if (isExpressionNode(node)) { - // If our selection is the expression in an ExpressionStatement, expand - // the selection to include the enclosing Statement (this stops us - // from trying to care about the return value of the extracted function - // and eliminates double semicolon insertion in certain scenarios) - return isExpressionStatement(node.parent) ? [node.parent] : node as Expression; - } - if (isStringLiteralJsxAttribute(node)) { - return node; - } - return undefined; +/** + * Includes the final semicolon so that the span covers statements in cases where it would otherwise + * only cover the declaration list. + */ +function getAdjustedSpanFromNodes(startNode: Node, endNode: Node, sourceFile: SourceFile): TextSpan { + const start = startNode.getStart(sourceFile); + let end = endNode.getEnd(); + if (sourceFile.text.charCodeAt(end) === CharacterCodes.semicolon) { + end++; } + return { start, length: end - start }; +} - function isScope(node: Node): node is Scope { - return isArrowFunction(node) ? isFunctionBody(node.body) : - isFunctionLikeDeclaration(node) || isSourceFile(node) || isModuleBlock(node) || isClassLike(node); +function getStatementOrExpressionRange(node: Node): Statement[] | Expression | undefined { + if (isStatement(node)) { + return [node]; } + if (isExpressionNode(node)) { + // If our selection is the expression in an ExpressionStatement, expand + // the selection to include the enclosing Statement (this stops us + // from trying to care about the return value of the extracted function + // and eliminates double semicolon insertion in certain scenarios) + return isExpressionStatement(node.parent) ? [node.parent] : node as Expression; + } + if (isStringLiteralJsxAttribute(node)) { + return node; + } + return undefined; +} - /** - * Computes possible places we could extract the function into. For example, - * you may be able to extract into a class method *or* local closure *or* namespace function, - * depending on what's in the extracted body. - */ - function collectEnclosingScopes(range: TargetRange): Scope[] { - let current: Node = isReadonlyArray(range.range) ? first(range.range) : range.range; - if (range.facts & RangeFacts.UsesThis && !(range.facts & RangeFacts.UsesThisInFunction)) { - // if range uses this as keyword or as type inside the class then it can only be extracted to a method of the containing class - const containingClass = getContainingClass(current); - if (containingClass) { - const containingFunction = findAncestor(current, isFunctionLikeDeclaration); - return containingFunction - ? [containingFunction, containingClass] - : [containingClass]; - } +function isScope(node: Node): node is Scope { + return isArrowFunction(node) ? isFunctionBody(node.body) : + isFunctionLikeDeclaration(node) || isSourceFile(node) || isModuleBlock(node) || isClassLike(node); +} + +/** + * Computes possible places we could extract the function into. For example, + * you may be able to extract into a class method *or* local closure *or* namespace function, + * depending on what's in the extracted body. + */ +function collectEnclosingScopes(range: TargetRange): Scope[] { + let current: Node = isReadonlyArray(range.range) ? first(range.range) : range.range; + if (range.facts & RangeFacts.UsesThis && !(range.facts & RangeFacts.UsesThisInFunction)) { + // if range uses this as keyword or as type inside the class then it can only be extracted to a method of the containing class + const containingClass = getContainingClass(current); + if (containingClass) { + const containingFunction = findAncestor(current, isFunctionLikeDeclaration); + return containingFunction + ? [containingFunction, containingClass] + : [containingClass]; } + } - const scopes: Scope[] = []; - while (true) { - current = current.parent; - // A function parameter's initializer is actually in the outer scope, not the function declaration - if (current.kind === SyntaxKind.Parameter) { - // Skip all the way to the outer scope of the function that declared this parameter - current = findAncestor(current, parent => isFunctionLikeDeclaration(parent))!.parent; - } + const scopes: Scope[] = []; + while (true) { + current = current.parent; + // A function parameter's initializer is actually in the outer scope, not the function declaration + if (current.kind === SyntaxKind.Parameter) { + // Skip all the way to the outer scope of the function that declared this parameter + current = findAncestor(current, parent => isFunctionLikeDeclaration(parent))!.parent; + } - // We want to find the nearest parent where we can place an "equivalent" sibling to the node we're extracting out of. - // Walk up to the closest parent of a place where we can logically put a sibling: - // * Function declaration - // * Class declaration or expression - // * Module/namespace or source file - if (isScope(current)) { - scopes.push(current); - if (current.kind === SyntaxKind.SourceFile) { - return scopes; - } + // We want to find the nearest parent where we can place an "equivalent" sibling to the node we're extracting out of. + // Walk up to the closest parent of a place where we can logically put a sibling: + // * Function declaration + // * Class declaration or expression + // * Module/namespace or source file + if (isScope(current)) { + scopes.push(current); + if (current.kind === SyntaxKind.SourceFile) { + return scopes; } } } +} - function getFunctionExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo { - const { scopes, readsAndWrites: { target, usagesPerScope, functionErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context); - Debug.assert(!functionErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); - context.cancellationToken!.throwIfCancellationRequested(); // TODO: GH#18217 - return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], exposedVariableDeclarations, targetRange, context); - } +function getFunctionExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo { + const { scopes, readsAndWrites: { target, usagesPerScope, functionErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context); + Debug.assert(!functionErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); + context.cancellationToken!.throwIfCancellationRequested(); // TODO: GH#18217 + return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], exposedVariableDeclarations, targetRange, context); +} - function getConstantExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo { - const { scopes, readsAndWrites: { target, usagesPerScope, constantErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context); - Debug.assert(!constantErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); - Debug.assert(exposedVariableDeclarations.length === 0, "Extract constant accepted a range containing a variable declaration?"); - context.cancellationToken!.throwIfCancellationRequested(); - const expression = isExpression(target) - ? target - : (target.statements[0] as ExpressionStatement).expression; - return extractConstantInScope(expression, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange.facts, context); - } +function getConstantExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo { + const { scopes, readsAndWrites: { target, usagesPerScope, constantErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context); + Debug.assert(!constantErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); + Debug.assert(exposedVariableDeclarations.length === 0, "Extract constant accepted a range containing a variable declaration?"); + context.cancellationToken!.throwIfCancellationRequested(); + const expression = isExpression(target) + ? target + : (target.statements[0] as ExpressionStatement).expression; + return extractConstantInScope(expression, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange.facts, context); +} - interface Extraction { - readonly description: string; - readonly errors: readonly Diagnostic[]; - } +interface Extraction { + readonly description: string; + readonly errors: readonly Diagnostic[]; +} - interface ScopeExtractions { - readonly functionExtraction: Extraction; - readonly constantExtraction: Extraction; - } +interface ScopeExtractions { + readonly functionExtraction: Extraction; + readonly constantExtraction: Extraction; +} - /** - * Given a piece of text to extract ('targetRange'), computes a list of possible extractions. - * Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes - * or an error explaining why we can't extract into that scope. - */ - function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): readonly ScopeExtractions[] | undefined { - const { scopes, readsAndWrites: { functionErrorsPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); - // Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547 - const extractions = scopes.map((scope, i): ScopeExtractions => { - const functionDescriptionPart = getDescriptionForFunctionInScope(scope); - const constantDescriptionPart = getDescriptionForConstantInScope(scope); - - const scopeDescription = isFunctionLikeDeclaration(scope) - ? getDescriptionForFunctionLikeDeclaration(scope) - : isClassLike(scope) - ? getDescriptionForClassLikeDeclaration(scope) - : getDescriptionForModuleLikeDeclaration(scope); - - let functionDescription: string; - let constantDescription: string; - if (scopeDescription === SpecialScope.Global) { - functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "global"]); - constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "global"]); - } - else if (scopeDescription === SpecialScope.Module) { - functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "module"]); - constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "module"]); - } - else { - functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [functionDescriptionPart, scopeDescription]); - constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [constantDescriptionPart, scopeDescription]); - } +/** + * Given a piece of text to extract ('targetRange'), computes a list of possible extractions. + * Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes + * or an error explaining why we can't extract into that scope. + */ +function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): readonly ScopeExtractions[] | undefined { + const { scopes, readsAndWrites: { functionErrorsPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); + // Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547 + const extractions = scopes.map((scope, i): ScopeExtractions => { + const functionDescriptionPart = getDescriptionForFunctionInScope(scope); + const constantDescriptionPart = getDescriptionForConstantInScope(scope); + + const scopeDescription = isFunctionLikeDeclaration(scope) + ? getDescriptionForFunctionLikeDeclaration(scope) + : isClassLike(scope) + ? getDescriptionForClassLikeDeclaration(scope) + : getDescriptionForModuleLikeDeclaration(scope); - // Customize the phrasing for the innermost scope to increase clarity. - if (i === 0 && !isClassLike(scope)) { - constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_enclosing_scope), [constantDescriptionPart]); - } + let functionDescription: string; + let constantDescription: string; + if (scopeDescription === SpecialScope.Global) { + functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "global"]); + constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "global"]); + } + else if (scopeDescription === SpecialScope.Module) { + functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "module"]); + constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "module"]); + } + else { + functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [functionDescriptionPart, scopeDescription]); + constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [constantDescriptionPart, scopeDescription]); + } - return { - functionExtraction: { - description: functionDescription, - errors: functionErrorsPerScope[i], - }, - constantExtraction: { - description: constantDescription, - errors: constantErrorsPerScope[i], - }, - }; - }); - return extractions; - } + // Customize the phrasing for the innermost scope to increase clarity. + if (i === 0 && !isClassLike(scope)) { + constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_enclosing_scope), [constantDescriptionPart]); + } - function getPossibleExtractionsWorker(targetRange: TargetRange, context: RefactorContext): { readonly scopes: Scope[], readonly readsAndWrites: ReadsAndWrites } { - const { file: sourceFile } = context; + return { + functionExtraction: { + description: functionDescription, + errors: functionErrorsPerScope[i], + }, + constantExtraction: { + description: constantDescription, + errors: constantErrorsPerScope[i], + }, + }; + }); + return extractions; +} - const scopes = collectEnclosingScopes(targetRange); - const enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile); - const readsAndWrites = collectReadsAndWrites( - targetRange, - scopes, - enclosingTextRange, - sourceFile, - context.program.getTypeChecker(), - context.cancellationToken!); - return { scopes, readsAndWrites }; - } +function getPossibleExtractionsWorker(targetRange: TargetRange, context: RefactorContext): { readonly scopes: Scope[], readonly readsAndWrites: ReadsAndWrites } { + const { file: sourceFile } = context; + + const scopes = collectEnclosingScopes(targetRange); + const enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile); + const readsAndWrites = collectReadsAndWrites( + targetRange, + scopes, + enclosingTextRange, + sourceFile, + context.program.getTypeChecker(), + context.cancellationToken!); + return { scopes, readsAndWrites }; +} - function getDescriptionForFunctionInScope(scope: Scope): string { - return isFunctionLikeDeclaration(scope) - ? "inner function" - : isClassLike(scope) - ? "method" - : "function"; - } - function getDescriptionForConstantInScope(scope: Scope): string { - return isClassLike(scope) - ? "readonly field" - : "constant"; - } - function getDescriptionForFunctionLikeDeclaration(scope: FunctionLikeDeclaration): string { - switch (scope.kind) { - case SyntaxKind.Constructor: - return "constructor"; - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - return scope.name - ? `function '${scope.name.text}'` - : ANONYMOUS; - case SyntaxKind.ArrowFunction: - return "arrow function"; - case SyntaxKind.MethodDeclaration: - return `method '${scope.name.getText()}'`; - case SyntaxKind.GetAccessor: - return `'get ${scope.name.getText()}'`; - case SyntaxKind.SetAccessor: - return `'set ${scope.name.getText()}'`; - default: - throw Debug.assertNever(scope, `Unexpected scope kind ${(scope as FunctionLikeDeclaration).kind}`); - } - } - function getDescriptionForClassLikeDeclaration(scope: ClassLikeDeclaration): string { - return scope.kind === SyntaxKind.ClassDeclaration - ? scope.name ? `class '${scope.name.text}'` : "anonymous class declaration" - : scope.name ? `class expression '${scope.name.text}'` : "anonymous class expression"; - } - function getDescriptionForModuleLikeDeclaration(scope: SourceFile | ModuleBlock): string | SpecialScope { - return scope.kind === SyntaxKind.ModuleBlock - ? `namespace '${scope.parent.name.getText()}'` - : scope.externalModuleIndicator ? SpecialScope.Module : SpecialScope.Global; - } - - const enum SpecialScope { - Module, - Global, +function getDescriptionForFunctionInScope(scope: Scope): string { + return isFunctionLikeDeclaration(scope) + ? "inner function" + : isClassLike(scope) + ? "method" + : "function"; +} +function getDescriptionForConstantInScope(scope: Scope): string { + return isClassLike(scope) + ? "readonly field" + : "constant"; +} +function getDescriptionForFunctionLikeDeclaration(scope: FunctionLikeDeclaration): string { + switch (scope.kind) { + case SyntaxKind.Constructor: + return "constructor"; + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + return scope.name + ? `function '${scope.name.text}'` + : ANONYMOUS; + case SyntaxKind.ArrowFunction: + return "arrow function"; + case SyntaxKind.MethodDeclaration: + return `method '${scope.name.getText()}'`; + case SyntaxKind.GetAccessor: + return `'get ${scope.name.getText()}'`; + case SyntaxKind.SetAccessor: + return `'set ${scope.name.getText()}'`; + default: + throw Debug.assertNever(scope, `Unexpected scope kind ${(scope as FunctionLikeDeclaration).kind}`); } +} +function getDescriptionForClassLikeDeclaration(scope: ClassLikeDeclaration): string { + return scope.kind === SyntaxKind.ClassDeclaration + ? scope.name ? `class '${scope.name.text}'` : "anonymous class declaration" + : scope.name ? `class expression '${scope.name.text}'` : "anonymous class expression"; +} +function getDescriptionForModuleLikeDeclaration(scope: SourceFile | ModuleBlock): string | SpecialScope { + return scope.kind === SyntaxKind.ModuleBlock + ? `namespace '${scope.parent.name.getText()}'` + : scope.externalModuleIndicator ? SpecialScope.Module : SpecialScope.Global; +} - /** - * Result of 'extractRange' operation for a specific scope. - * Stores either a list of changes that should be applied to extract a range or a list of errors - */ - function extractFunctionInScope( - node: Statement | Expression | Block, - scope: Scope, - { usages: usagesInScope, typeParameterUsages, substitutions }: ScopeUsages, - exposedVariableDeclarations: readonly VariableDeclaration[], - range: TargetRange, - context: RefactorContext): RefactorEditInfo { - - const checker = context.program.getTypeChecker(); - const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions()); - const importAdder = codefix.createImportAdder(context.file, context.program, context.preferences, context.host); - - // Make a unique name for the extracted function - const file = scope.getSourceFile(); - const functionNameText = getUniqueName(isClassLike(scope) ? "newMethod" : "newFunction", file); - const isJS = isInJSFile(scope); - - const functionName = factory.createIdentifier(functionNameText); - - let returnType: TypeNode | undefined; - const parameters: ParameterDeclaration[] = []; - const callArguments: Identifier[] = []; - let writes: UsageEntry[] | undefined; - usagesInScope.forEach((usage, name) => { - let typeNode: TypeNode | undefined; - if (!isJS) { - let type = checker.getTypeOfSymbolAtLocation(usage.symbol, usage.node); - // Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {" - type = checker.getBaseTypeOfLiteralType(type); - typeNode = codefix.typeToAutoImportableTypeNode(checker, importAdder, type, scope, scriptTarget, NodeBuilderFlags.NoTruncation); - } +const enum SpecialScope { + Module, + Global, +} - const paramDecl = factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - /*name*/ name, - /*questionToken*/ undefined, - typeNode - ); - parameters.push(paramDecl); - if (usage.usage === Usage.Write) { - (writes || (writes = [])).push(usage); - } - callArguments.push(factory.createIdentifier(name)); - }); +/** + * Result of 'extractRange' operation for a specific scope. + * Stores either a list of changes that should be applied to extract a range or a list of errors + */ +function extractFunctionInScope( + node: Statement | Expression | Block, + scope: Scope, + { usages: usagesInScope, typeParameterUsages, substitutions }: ScopeUsages, + exposedVariableDeclarations: readonly VariableDeclaration[], + range: TargetRange, + context: RefactorContext): RefactorEditInfo { + + const checker = context.program.getTypeChecker(); + const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions()); + const importAdder = codefix.createImportAdder(context.file, context.program, context.preferences, context.host); + + // Make a unique name for the extracted function + const file = scope.getSourceFile(); + const functionNameText = getUniqueName(isClassLike(scope) ? "newMethod" : "newFunction", file); + const isJS = isInJSFile(scope); + + const functionName = factory.createIdentifier(functionNameText); + + let returnType: TypeNode | undefined; + const parameters: ParameterDeclaration[] = []; + const callArguments: Identifier[] = []; + let writes: UsageEntry[] | undefined; + usagesInScope.forEach((usage, name) => { + let typeNode: TypeNode | undefined; + if (!isJS) { + let type = checker.getTypeOfSymbolAtLocation(usage.symbol, usage.node); + // Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {" + type = checker.getBaseTypeOfLiteralType(type); + typeNode = codefix.typeToAutoImportableTypeNode(checker, importAdder, type, scope, scriptTarget, NodeBuilderFlags.NoTruncation); + } + + const paramDecl = factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + /*name*/ name, + /*questionToken*/ undefined, + typeNode + ); + parameters.push(paramDecl); + if (usage.usage === Usage.Write) { + (writes || (writes = [])).push(usage); + } + callArguments.push(factory.createIdentifier(name)); + }); - const typeParametersAndDeclarations = arrayFrom(typeParameterUsages.values()).map(type => ({ type, declaration: getFirstDeclaration(type) })); - const sortedTypeParametersAndDeclarations = typeParametersAndDeclarations.sort(compareTypesByDeclarationOrder); + const typeParametersAndDeclarations = arrayFrom(typeParameterUsages.values()).map(type => ({ type, declaration: getFirstDeclaration(type) })); + const sortedTypeParametersAndDeclarations = typeParametersAndDeclarations.sort(compareTypesByDeclarationOrder); - const typeParameters: readonly TypeParameterDeclaration[] | undefined = sortedTypeParametersAndDeclarations.length === 0 - ? undefined - : sortedTypeParametersAndDeclarations.map(t => t.declaration as TypeParameterDeclaration); + const typeParameters: readonly TypeParameterDeclaration[] | undefined = sortedTypeParametersAndDeclarations.length === 0 + ? undefined + : sortedTypeParametersAndDeclarations.map(t => t.declaration as TypeParameterDeclaration); - // Strictly speaking, we should check whether each name actually binds to the appropriate type - // parameter. In cases of shadowing, they may not. - const callTypeArguments: readonly TypeNode[] | undefined = typeParameters !== undefined - ? typeParameters.map(decl => factory.createTypeReferenceNode(decl.name, /*typeArguments*/ undefined)) - : undefined; + // Strictly speaking, we should check whether each name actually binds to the appropriate type + // parameter. In cases of shadowing, they may not. + const callTypeArguments: readonly TypeNode[] | undefined = typeParameters !== undefined + ? typeParameters.map(decl => factory.createTypeReferenceNode(decl.name, /*typeArguments*/ undefined)) + : undefined; - // Provide explicit return types for contextually-typed functions - // to avoid problems when there are literal types present - if (isExpression(node) && !isJS) { - const contextualType = checker.getContextualType(node); - returnType = checker.typeToTypeNode(contextualType!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217 - } + // Provide explicit return types for contextually-typed functions + // to avoid problems when there are literal types present + if (isExpression(node) && !isJS) { + const contextualType = checker.getContextualType(node); + returnType = checker.typeToTypeNode(contextualType!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217 + } - const { body, returnValueProperty } = transformFunctionBody(node, exposedVariableDeclarations, writes, substitutions, !!(range.facts & RangeFacts.HasReturn)); - suppressLeadingAndTrailingTrivia(body); + const { body, returnValueProperty } = transformFunctionBody(node, exposedVariableDeclarations, writes, substitutions, !!(range.facts & RangeFacts.HasReturn)); + suppressLeadingAndTrailingTrivia(body); - let newFunction: MethodDeclaration | FunctionDeclaration; + let newFunction: MethodDeclaration | FunctionDeclaration; - const callThis = !!(range.facts & RangeFacts.UsesThisInFunction); + const callThis = !!(range.facts & RangeFacts.UsesThisInFunction); - if (isClassLike(scope)) { - // always create private method in TypeScript files - const modifiers: Modifier[] = isJS ? [] : [factory.createModifier(SyntaxKind.PrivateKeyword)]; - if (range.facts & RangeFacts.InStaticRegion) { - modifiers.push(factory.createModifier(SyntaxKind.StaticKeyword)); - } - if (range.facts & RangeFacts.IsAsyncFunction) { - modifiers.push(factory.createModifier(SyntaxKind.AsyncKeyword)); - } - newFunction = factory.createMethodDeclaration( - modifiers.length ? modifiers : undefined, - range.facts & RangeFacts.IsGenerator ? factory.createToken(SyntaxKind.AsteriskToken) : undefined, - functionName, - /*questionToken*/ undefined, - typeParameters, - parameters, - returnType, - body - ); + if (isClassLike(scope)) { + // always create private method in TypeScript files + const modifiers: Modifier[] = isJS ? [] : [factory.createModifier(SyntaxKind.PrivateKeyword)]; + if (range.facts & RangeFacts.InStaticRegion) { + modifiers.push(factory.createModifier(SyntaxKind.StaticKeyword)); } - else { - if (callThis) { - parameters.unshift( - factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - /*name*/ "this", - /*questionToken*/ undefined, - checker.typeToTypeNode( - checker.getTypeAtLocation(range.thisNode!), - scope, - NodeBuilderFlags.NoTruncation - ), - /*initializer*/ undefined, - ) - ); - } - newFunction = factory.createFunctionDeclaration( - range.facts & RangeFacts.IsAsyncFunction ? [factory.createToken(SyntaxKind.AsyncKeyword)] : undefined, - range.facts & RangeFacts.IsGenerator ? factory.createToken(SyntaxKind.AsteriskToken) : undefined, - functionName, - typeParameters, - parameters, - returnType, - body + if (range.facts & RangeFacts.IsAsyncFunction) { + modifiers.push(factory.createModifier(SyntaxKind.AsyncKeyword)); + } + newFunction = factory.createMethodDeclaration( + modifiers.length ? modifiers : undefined, + range.facts & RangeFacts.IsGenerator ? factory.createToken(SyntaxKind.AsteriskToken) : undefined, + functionName, + /*questionToken*/ undefined, + typeParameters, + parameters, + returnType, + body + ); + } + else { + if (callThis) { + parameters.unshift( + factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + /*name*/ "this", + /*questionToken*/ undefined, + checker.typeToTypeNode( + checker.getTypeAtLocation(range.thisNode!), + scope, + NodeBuilderFlags.NoTruncation + ), + /*initializer*/ undefined, + ) ); } + newFunction = factory.createFunctionDeclaration( + range.facts & RangeFacts.IsAsyncFunction ? [factory.createToken(SyntaxKind.AsyncKeyword)] : undefined, + range.facts & RangeFacts.IsGenerator ? factory.createToken(SyntaxKind.AsteriskToken) : undefined, + functionName, + typeParameters, + parameters, + returnType, + body + ); + } - const changeTracker = textChanges.ChangeTracker.fromContext(context); - const minInsertionPos = (isReadonlyArray(range.range) ? last(range.range) : range.range).end; - const nodeToInsertBefore = getNodeToInsertFunctionBefore(minInsertionPos, scope); - if (nodeToInsertBefore) { - changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newFunction, /*blankLineBetween*/ true); - } - else { - changeTracker.insertNodeAtEndOfScope(context.file, scope, newFunction); - } - importAdder.writeFixes(changeTracker); + const changeTracker = textChanges.ChangeTracker.fromContext(context); + const minInsertionPos = (isReadonlyArray(range.range) ? last(range.range) : range.range).end; + const nodeToInsertBefore = getNodeToInsertFunctionBefore(minInsertionPos, scope); + if (nodeToInsertBefore) { + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newFunction, /*blankLineBetween*/ true); + } + else { + changeTracker.insertNodeAtEndOfScope(context.file, scope, newFunction); + } + importAdder.writeFixes(changeTracker); - const newNodes: Node[] = []; - // replace range with function call - const called = getCalledExpression(scope, range, functionNameText); + const newNodes: Node[] = []; + // replace range with function call + const called = getCalledExpression(scope, range, functionNameText); - if (callThis) { - callArguments.unshift(factory.createIdentifier("this")); - } + if (callThis) { + callArguments.unshift(factory.createIdentifier("this")); + } - let call: Expression = factory.createCallExpression( - callThis ? factory.createPropertyAccessExpression( - called, - "call" - ) : called, - callTypeArguments, // Note that no attempt is made to take advantage of type argument inference - callArguments); - if (range.facts & RangeFacts.IsGenerator) { - call = factory.createYieldExpression(factory.createToken(SyntaxKind.AsteriskToken), call); - } - if (range.facts & RangeFacts.IsAsyncFunction) { - call = factory.createAwaitExpression(call); - } - if (isInJSXContent(node)) { - call = factory.createJsxExpression(/*dotDotDotToken*/ undefined, call); - } + let call: Expression = factory.createCallExpression( + callThis ? factory.createPropertyAccessExpression( + called, + "call" + ) : called, + callTypeArguments, // Note that no attempt is made to take advantage of type argument inference + callArguments); + if (range.facts & RangeFacts.IsGenerator) { + call = factory.createYieldExpression(factory.createToken(SyntaxKind.AsteriskToken), call); + } + if (range.facts & RangeFacts.IsAsyncFunction) { + call = factory.createAwaitExpression(call); + } + if (isInJSXContent(node)) { + call = factory.createJsxExpression(/*dotDotDotToken*/ undefined, call); + } - if (exposedVariableDeclarations.length && !writes) { - // No need to mix declarations and writes. + if (exposedVariableDeclarations.length && !writes) { + // No need to mix declarations and writes. - // How could any variables be exposed if there's a return statement? - Debug.assert(!returnValueProperty, "Expected no returnValueProperty"); - Debug.assert(!(range.facts & RangeFacts.HasReturn), "Expected RangeFacts.HasReturn flag to be unset"); + // How could any variables be exposed if there's a return statement? + Debug.assert(!returnValueProperty, "Expected no returnValueProperty"); + Debug.assert(!(range.facts & RangeFacts.HasReturn), "Expected RangeFacts.HasReturn flag to be unset"); - if (exposedVariableDeclarations.length === 1) { - // Declaring exactly one variable: let x = newFunction(); - const variableDeclaration = exposedVariableDeclarations[0]; - newNodes.push(factory.createVariableStatement( + if (exposedVariableDeclarations.length === 1) { + // Declaring exactly one variable: let x = newFunction(); + const variableDeclaration = exposedVariableDeclarations[0]; + newNodes.push(factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration(getSynthesizedDeepClone(variableDeclaration.name), /*exclamationToken*/ undefined, /*type*/ getSynthesizedDeepClone(variableDeclaration.type), /*initializer*/ call)], + variableDeclaration.parent.flags))); + } + else { + // Declaring multiple variables / return properties: + // let {x, y} = newFunction(); + const bindingElements: BindingElement[] = []; + const typeElements: TypeElement[] = []; + let commonNodeFlags = exposedVariableDeclarations[0].parent.flags; + let sawExplicitType = false; + for (const variableDeclaration of exposedVariableDeclarations) { + bindingElements.push(factory.createBindingElement( + /*dotDotDotToken*/ undefined, + /*propertyName*/ undefined, + /*name*/ getSynthesizedDeepClone(variableDeclaration.name))); + + // Being returned through an object literal will have widened the type. + const variableType: TypeNode | undefined = checker.typeToTypeNode( + checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(variableDeclaration)), + scope, + NodeBuilderFlags.NoTruncation); + + typeElements.push(factory.createPropertySignature( /*modifiers*/ undefined, - factory.createVariableDeclarationList( - [factory.createVariableDeclaration(getSynthesizedDeepClone(variableDeclaration.name), /*exclamationToken*/ undefined, /*type*/ getSynthesizedDeepClone(variableDeclaration.type), /*initializer*/ call)], - variableDeclaration.parent.flags))); + /*name*/ variableDeclaration.symbol.name, + /*questionToken*/ undefined, + /*type*/ variableType)); + sawExplicitType = sawExplicitType || variableDeclaration.type !== undefined; + commonNodeFlags = commonNodeFlags & variableDeclaration.parent.flags; } - else { - // Declaring multiple variables / return properties: - // let {x, y} = newFunction(); - const bindingElements: BindingElement[] = []; - const typeElements: TypeElement[] = []; - let commonNodeFlags = exposedVariableDeclarations[0].parent.flags; - let sawExplicitType = false; - for (const variableDeclaration of exposedVariableDeclarations) { - bindingElements.push(factory.createBindingElement( - /*dotDotDotToken*/ undefined, - /*propertyName*/ undefined, - /*name*/ getSynthesizedDeepClone(variableDeclaration.name))); - - // Being returned through an object literal will have widened the type. - const variableType: TypeNode | undefined = checker.typeToTypeNode( - checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(variableDeclaration)), - scope, - NodeBuilderFlags.NoTruncation); - - typeElements.push(factory.createPropertySignature( - /*modifiers*/ undefined, - /*name*/ variableDeclaration.symbol.name, - /*questionToken*/ undefined, - /*type*/ variableType)); - sawExplicitType = sawExplicitType || variableDeclaration.type !== undefined; - commonNodeFlags = commonNodeFlags & variableDeclaration.parent.flags; - } - - const typeLiteral: TypeLiteralNode | undefined = sawExplicitType ? factory.createTypeLiteralNode(typeElements) : undefined; - if (typeLiteral) { - setEmitFlags(typeLiteral, EmitFlags.SingleLine); - } - newNodes.push(factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList( - [factory.createVariableDeclaration( - factory.createObjectBindingPattern(bindingElements), - /*exclamationToken*/ undefined, - /*type*/ typeLiteral, - /*initializer*/call)], - commonNodeFlags))); + const typeLiteral: TypeLiteralNode | undefined = sawExplicitType ? factory.createTypeLiteralNode(typeElements) : undefined; + if (typeLiteral) { + setEmitFlags(typeLiteral, EmitFlags.SingleLine); } - } - else if (exposedVariableDeclarations.length || writes) { - if (exposedVariableDeclarations.length) { - // CONSIDER: we're going to create one statement per variable, but we could actually preserve their original grouping. - for (const variableDeclaration of exposedVariableDeclarations) { - let flags: NodeFlags = variableDeclaration.parent.flags; - if (flags & NodeFlags.Const) { - flags = (flags & ~NodeFlags.Const) | NodeFlags.Let; - } - newNodes.push(factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList( - [factory.createVariableDeclaration(variableDeclaration.symbol.name, /*exclamationToken*/ undefined, getTypeDeepCloneUnionUndefined(variableDeclaration.type))], - flags))); + newNodes.push(factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration( + factory.createObjectBindingPattern(bindingElements), + /*exclamationToken*/ undefined, + /*type*/ typeLiteral, + /*initializer*/call)], + commonNodeFlags))); + } + } + else if (exposedVariableDeclarations.length || writes) { + if (exposedVariableDeclarations.length) { + // CONSIDER: we're going to create one statement per variable, but we could actually preserve their original grouping. + for (const variableDeclaration of exposedVariableDeclarations) { + let flags: NodeFlags = variableDeclaration.parent.flags; + if (flags & NodeFlags.Const) { + flags = (flags & ~NodeFlags.Const) | NodeFlags.Let; } - } - if (returnValueProperty) { - // has both writes and return, need to create variable declaration to hold return value; newNodes.push(factory.createVariableStatement( /*modifiers*/ undefined, factory.createVariableDeclarationList( - [factory.createVariableDeclaration(returnValueProperty, /*exclamationToken*/ undefined, getTypeDeepCloneUnionUndefined(returnType))], - NodeFlags.Let))); + [factory.createVariableDeclaration(variableDeclaration.symbol.name, /*exclamationToken*/ undefined, getTypeDeepCloneUnionUndefined(variableDeclaration.type))], + flags))); } + } - const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); - if (returnValueProperty) { - assignments.unshift(factory.createShorthandPropertyAssignment(returnValueProperty)); - } + if (returnValueProperty) { + // has both writes and return, need to create variable declaration to hold return value; + newNodes.push(factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [factory.createVariableDeclaration(returnValueProperty, /*exclamationToken*/ undefined, getTypeDeepCloneUnionUndefined(returnType))], + NodeFlags.Let))); + } - // propagate writes back - if (assignments.length === 1) { - // We would only have introduced a return value property if there had been - // other assignments to make. - Debug.assert(!returnValueProperty, "Shouldn't have returnValueProperty here"); + const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); + if (returnValueProperty) { + assignments.unshift(factory.createShorthandPropertyAssignment(returnValueProperty)); + } - newNodes.push(factory.createExpressionStatement(factory.createAssignment(assignments[0].name, call))); + // propagate writes back + if (assignments.length === 1) { + // We would only have introduced a return value property if there had been + // other assignments to make. + Debug.assert(!returnValueProperty, "Shouldn't have returnValueProperty here"); - if (range.facts & RangeFacts.HasReturn) { - newNodes.push(factory.createReturnStatement()); - } - } - else { - // emit e.g. - // { a, b, __return } = newFunction(a, b); - // return __return; - newNodes.push(factory.createExpressionStatement(factory.createAssignment(factory.createObjectLiteralExpression(assignments), call))); - if (returnValueProperty) { - newNodes.push(factory.createReturnStatement(factory.createIdentifier(returnValueProperty))); - } + newNodes.push(factory.createExpressionStatement(factory.createAssignment(assignments[0].name, call))); + + if (range.facts & RangeFacts.HasReturn) { + newNodes.push(factory.createReturnStatement()); } } else { - if (range.facts & RangeFacts.HasReturn) { - newNodes.push(factory.createReturnStatement(call)); - } - else if (isReadonlyArray(range.range)) { - newNodes.push(factory.createExpressionStatement(call)); - } - else { - newNodes.push(call); + // emit e.g. + // { a, b, __return } = newFunction(a, b); + // return __return; + newNodes.push(factory.createExpressionStatement(factory.createAssignment(factory.createObjectLiteralExpression(assignments), call))); + if (returnValueProperty) { + newNodes.push(factory.createReturnStatement(factory.createIdentifier(returnValueProperty))); } } - - if (isReadonlyArray(range.range)) { - changeTracker.replaceNodeRangeWithNodes(context.file, first(range.range), last(range.range), newNodes); + } + else { + if (range.facts & RangeFacts.HasReturn) { + newNodes.push(factory.createReturnStatement(call)); + } + else if (isReadonlyArray(range.range)) { + newNodes.push(factory.createExpressionStatement(call)); } else { - changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes); + newNodes.push(call); } + } - const edits = changeTracker.getChanges(); - const renameRange = isReadonlyArray(range.range) ? first(range.range) : range.range; + if (isReadonlyArray(range.range)) { + changeTracker.replaceNodeRangeWithNodes(context.file, first(range.range), last(range.range), newNodes); + } + else { + changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes); + } - const renameFilename = renameRange.getSourceFile().fileName; - const renameLocation = getRenameLocation(edits, renameFilename, functionNameText, /*isDeclaredBeforeUse*/ false); - return { renameFilename, renameLocation, edits }; + const edits = changeTracker.getChanges(); + const renameRange = isReadonlyArray(range.range) ? first(range.range) : range.range; - function getTypeDeepCloneUnionUndefined(typeNode: TypeNode | undefined): TypeNode | undefined { - if (typeNode === undefined) { - return undefined; - } + const renameFilename = renameRange.getSourceFile().fileName; + const renameLocation = getRenameLocation(edits, renameFilename, functionNameText, /*isDeclaredBeforeUse*/ false); + return { renameFilename, renameLocation, edits }; - const clone = getSynthesizedDeepClone(typeNode); - let withoutParens = clone; - while (isParenthesizedTypeNode(withoutParens)) { - withoutParens = withoutParens.type; - } - return isUnionTypeNode(withoutParens) && find(withoutParens.types, t => t.kind === SyntaxKind.UndefinedKeyword) - ? clone - : factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + function getTypeDeepCloneUnionUndefined(typeNode: TypeNode | undefined): TypeNode | undefined { + if (typeNode === undefined) { + return undefined; } + + const clone = getSynthesizedDeepClone(typeNode); + let withoutParens = clone; + while (isParenthesizedTypeNode(withoutParens)) { + withoutParens = withoutParens.type; + } + return isUnionTypeNode(withoutParens) && find(withoutParens.types, t => t.kind === SyntaxKind.UndefinedKeyword) + ? clone + : factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); } +} - /** - * Result of 'extractRange' operation for a specific scope. - * Stores either a list of changes that should be applied to extract a range or a list of errors - */ - function extractConstantInScope( - node: Expression, - scope: Scope, - { substitutions }: ScopeUsages, - rangeFacts: RangeFacts, - context: RefactorContext): RefactorEditInfo { +/** + * Result of 'extractRange' operation for a specific scope. + * Stores either a list of changes that should be applied to extract a range or a list of errors + */ +function extractConstantInScope( + node: Expression, + scope: Scope, + { substitutions }: ScopeUsages, + rangeFacts: RangeFacts, + context: RefactorContext): RefactorEditInfo { + + const checker = context.program.getTypeChecker(); + + // Make a unique name for the extracted variable + const file = scope.getSourceFile(); + const localNameText = isPropertyAccessExpression(node) && !isClassLike(scope) && !checker.resolveName(node.name.text, node, SymbolFlags.Value, /*excludeGlobals*/ false) && !isPrivateIdentifier(node.name) && !isKeyword(node.name.originalKeywordKind!) + ? node.name.text + : getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file); + const isJS = isInJSFile(scope); + + let variableType = isJS || !checker.isContextSensitive(node) + ? undefined + : checker.typeToTypeNode(checker.getContextualType(node)!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217 - const checker = context.program.getTypeChecker(); + let initializer = transformConstantInitializer(skipParentheses(node), substitutions); - // Make a unique name for the extracted variable - const file = scope.getSourceFile(); - const localNameText = isPropertyAccessExpression(node) && !isClassLike(scope) && !checker.resolveName(node.name.text, node, SymbolFlags.Value, /*excludeGlobals*/ false) && !isPrivateIdentifier(node.name) && !isKeyword(node.name.originalKeywordKind!) - ? node.name.text - : getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file); - const isJS = isInJSFile(scope); + ({ variableType, initializer } = transformFunctionInitializerAndType(variableType, initializer)); - let variableType = isJS || !checker.isContextSensitive(node) - ? undefined - : checker.typeToTypeNode(checker.getContextualType(node)!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217 + suppressLeadingAndTrailingTrivia(initializer); - let initializer = transformConstantInitializer(skipParentheses(node), substitutions); + const changeTracker = textChanges.ChangeTracker.fromContext(context); - ({ variableType, initializer } = transformFunctionInitializerAndType(variableType, initializer)); + if (isClassLike(scope)) { + Debug.assert(!isJS, "Cannot extract to a JS class"); // See CannotExtractToJSClass + const modifiers: Modifier[] = []; + modifiers.push(factory.createModifier(SyntaxKind.PrivateKeyword)); + if (rangeFacts & RangeFacts.InStaticRegion) { + modifiers.push(factory.createModifier(SyntaxKind.StaticKeyword)); + } + modifiers.push(factory.createModifier(SyntaxKind.ReadonlyKeyword)); - suppressLeadingAndTrailingTrivia(initializer); + const newVariable = factory.createPropertyDeclaration( + modifiers, + localNameText, + /*questionToken*/ undefined, + variableType, + initializer); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + let localReference: Expression = factory.createPropertyAccessExpression( + rangeFacts & RangeFacts.InStaticRegion + ? factory.createIdentifier(scope.name!.getText()) // TODO: GH#18217 + : factory.createThis(), + factory.createIdentifier(localNameText)); - if (isClassLike(scope)) { - Debug.assert(!isJS, "Cannot extract to a JS class"); // See CannotExtractToJSClass - const modifiers: Modifier[] = []; - modifiers.push(factory.createModifier(SyntaxKind.PrivateKeyword)); - if (rangeFacts & RangeFacts.InStaticRegion) { - modifiers.push(factory.createModifier(SyntaxKind.StaticKeyword)); - } - modifiers.push(factory.createModifier(SyntaxKind.ReadonlyKeyword)); - - const newVariable = factory.createPropertyDeclaration( - modifiers, - localNameText, - /*questionToken*/ undefined, - variableType, - initializer); - - let localReference: Expression = factory.createPropertyAccessExpression( - rangeFacts & RangeFacts.InStaticRegion - ? factory.createIdentifier(scope.name!.getText()) // TODO: GH#18217 - : factory.createThis(), - factory.createIdentifier(localNameText)); - - if (isInJSXContent(node)) { - localReference = factory.createJsxExpression(/*dotDotDotToken*/ undefined, localReference); - } + if (isInJSXContent(node)) { + localReference = factory.createJsxExpression(/*dotDotDotToken*/ undefined, localReference); + } + + // Declare + const maxInsertionPos = node.pos; + const nodeToInsertBefore = getNodeToInsertPropertyBefore(maxInsertionPos, scope); + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, /*blankLineBetween*/ true); + // Consume + changeTracker.replaceNode(context.file, node, localReference); + } + else { + const newVariableDeclaration = factory.createVariableDeclaration(localNameText, /*exclamationToken*/ undefined, variableType, initializer); + + // If the node is part of an initializer in a list of variable declarations, insert a new + // variable declaration into the list (in case it depends on earlier ones). + // CONSIDER: If the declaration list isn't const, we might want to split it into multiple + // lists so that the newly extracted one can be const. + const oldVariableDeclaration = getContainingVariableDeclarationIfInList(node, scope); + if (oldVariableDeclaration) { // Declare - const maxInsertionPos = node.pos; - const nodeToInsertBefore = getNodeToInsertPropertyBefore(maxInsertionPos, scope); - changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, /*blankLineBetween*/ true); + // CONSIDER: could detect that each is on a separate line (See `extractConstant_VariableList_MultipleLines` in `extractConstants.ts`) + changeTracker.insertNodeBefore(context.file, oldVariableDeclaration, newVariableDeclaration); // Consume + const localReference = factory.createIdentifier(localNameText); changeTracker.replaceNode(context.file, node, localReference); } + else if (node.parent.kind === SyntaxKind.ExpressionStatement && scope === findAncestor(node, isScope)) { + // If the parent is an expression statement and the target scope is the immediately enclosing one, + // replace the statement with the declaration. + const newVariableStatement = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const)); + changeTracker.replaceNode(context.file, node.parent, newVariableStatement); + } else { - const newVariableDeclaration = factory.createVariableDeclaration(localNameText, /*exclamationToken*/ undefined, variableType, initializer); - - // If the node is part of an initializer in a list of variable declarations, insert a new - // variable declaration into the list (in case it depends on earlier ones). - // CONSIDER: If the declaration list isn't const, we might want to split it into multiple - // lists so that the newly extracted one can be const. - const oldVariableDeclaration = getContainingVariableDeclarationIfInList(node, scope); - if (oldVariableDeclaration) { - // Declare - // CONSIDER: could detect that each is on a separate line (See `extractConstant_VariableList_MultipleLines` in `extractConstants.ts`) - changeTracker.insertNodeBefore(context.file, oldVariableDeclaration, newVariableDeclaration); - - // Consume - const localReference = factory.createIdentifier(localNameText); - changeTracker.replaceNode(context.file, node, localReference); - } - else if (node.parent.kind === SyntaxKind.ExpressionStatement && scope === findAncestor(node, isScope)) { - // If the parent is an expression statement and the target scope is the immediately enclosing one, - // replace the statement with the declaration. - const newVariableStatement = factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const)); - changeTracker.replaceNode(context.file, node.parent, newVariableStatement); + const newVariableStatement = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const)); + + // Declare + const nodeToInsertBefore = getNodeToInsertConstantBefore(node, scope); + if (nodeToInsertBefore.pos === 0) { + changeTracker.insertNodeAtTopOfFile(context.file, newVariableStatement, /*blankLineBetween*/ false); } else { - const newVariableStatement = factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const)); - - // Declare - const nodeToInsertBefore = getNodeToInsertConstantBefore(node, scope); - if (nodeToInsertBefore.pos === 0) { - changeTracker.insertNodeAtTopOfFile(context.file, newVariableStatement, /*blankLineBetween*/ false); - } - else { - changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, /*blankLineBetween*/ false); - } + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, /*blankLineBetween*/ false); + } - // Consume - if (node.parent.kind === SyntaxKind.ExpressionStatement) { - // If the parent is an expression statement, delete it. - changeTracker.delete(context.file, node.parent); - } - else { - let localReference: Expression = factory.createIdentifier(localNameText); - // When extract to a new variable in JSX content, need to wrap a {} out of the new variable - // or it will become a plain text - if (isInJSXContent(node)) { - localReference = factory.createJsxExpression(/*dotDotDotToken*/ undefined, localReference); - } - changeTracker.replaceNode(context.file, node, localReference); + // Consume + if (node.parent.kind === SyntaxKind.ExpressionStatement) { + // If the parent is an expression statement, delete it. + changeTracker.delete(context.file, node.parent); + } + else { + let localReference: Expression = factory.createIdentifier(localNameText); + // When extract to a new variable in JSX content, need to wrap a {} out of the new variable + // or it will become a plain text + if (isInJSXContent(node)) { + localReference = factory.createJsxExpression(/*dotDotDotToken*/ undefined, localReference); } + changeTracker.replaceNode(context.file, node, localReference); } } + } - const edits = changeTracker.getChanges(); - - const renameFilename = node.getSourceFile().fileName; - const renameLocation = getRenameLocation(edits, renameFilename, localNameText, /*isDeclaredBeforeUse*/ true); - return { renameFilename, renameLocation, edits }; + const edits = changeTracker.getChanges(); - function transformFunctionInitializerAndType(variableType: TypeNode | undefined, initializer: Expression): { variableType: TypeNode | undefined, initializer: Expression } { - // If no contextual type exists there is nothing to transfer to the function signature - if (variableType === undefined) return { variableType, initializer }; - // Only do this for function expressions and arrow functions that are not generic - if (!isFunctionExpression(initializer) && !isArrowFunction(initializer) || !!initializer.typeParameters) return { variableType, initializer }; - const functionType = checker.getTypeAtLocation(node); - const functionSignature = singleOrUndefined(checker.getSignaturesOfType(functionType, SignatureKind.Call)); + const renameFilename = node.getSourceFile().fileName; + const renameLocation = getRenameLocation(edits, renameFilename, localNameText, /*isDeclaredBeforeUse*/ true); + return { renameFilename, renameLocation, edits }; - // If no function signature, maybe there was an error, do nothing - if (!functionSignature) return { variableType, initializer }; - // If the function signature has generic type parameters we don't attempt to move the parameters - if (!!functionSignature.getTypeParameters()) return { variableType, initializer }; + function transformFunctionInitializerAndType(variableType: TypeNode | undefined, initializer: Expression): { variableType: TypeNode | undefined, initializer: Expression } { + // If no contextual type exists there is nothing to transfer to the function signature + if (variableType === undefined) return { variableType, initializer }; + // Only do this for function expressions and arrow functions that are not generic + if (!isFunctionExpression(initializer) && !isArrowFunction(initializer) || !!initializer.typeParameters) return { variableType, initializer }; + const functionType = checker.getTypeAtLocation(node); + const functionSignature = singleOrUndefined(checker.getSignaturesOfType(functionType, SignatureKind.Call)); - // We add parameter types if needed - const parameters: ParameterDeclaration[] = []; - let hasAny = false; - for (const p of initializer.parameters) { - if (p.type) { - parameters.push(p); - } - else { - const paramType = checker.getTypeAtLocation(p); - if (paramType === checker.getAnyType()) hasAny = true; + // If no function signature, maybe there was an error, do nothing + if (!functionSignature) return { variableType, initializer }; + // If the function signature has generic type parameters we don't attempt to move the parameters + if (!!functionSignature.getTypeParameters()) return { variableType, initializer }; - parameters.push(factory.updateParameterDeclaration(p, - p.modifiers, p.dotDotDotToken, - p.name, p.questionToken, p.type || checker.typeToTypeNode(paramType, scope, NodeBuilderFlags.NoTruncation), p.initializer)); - } - } - // If a parameter was inferred as any we skip adding function parameters at all. - // Turning an implicit any (which under common settings is a error) to an explicit - // is probably actually a worse refactor outcome. - if (hasAny) return { variableType, initializer }; - variableType = undefined; - if (isArrowFunction(initializer)) { - initializer = factory.updateArrowFunction(initializer, canHaveModifiers(node) ? getModifiers(node) : undefined, initializer.typeParameters, - parameters, - initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation), - initializer.equalsGreaterThanToken, - initializer.body); + // We add parameter types if needed + const parameters: ParameterDeclaration[] = []; + let hasAny = false; + for (const p of initializer.parameters) { + if (p.type) { + parameters.push(p); } else { - if (functionSignature && !!functionSignature.thisParameter) { - const firstParameter = firstOrUndefined(parameters); - // If the function signature has a this parameter and if the first defined parameter is not the this parameter, we must add it - // Note: If this parameter was already there, it would have been previously updated with the type if not type was present - if ((!firstParameter || (isIdentifier(firstParameter.name) && firstParameter.name.escapedText !== "this"))) { - const thisType = checker.getTypeOfSymbolAtLocation(functionSignature.thisParameter, node); - parameters.splice(0, 0, factory.createParameterDeclaration( - /* modifiers */ undefined, - /* dotDotDotToken */ undefined, - "this", - /* questionToken */ undefined, - checker.typeToTypeNode(thisType, scope, NodeBuilderFlags.NoTruncation) - )); - } + const paramType = checker.getTypeAtLocation(p); + if (paramType === checker.getAnyType()) hasAny = true; + + parameters.push(factory.updateParameterDeclaration(p, + p.modifiers, p.dotDotDotToken, + p.name, p.questionToken, p.type || checker.typeToTypeNode(paramType, scope, NodeBuilderFlags.NoTruncation), p.initializer)); + } + } + // If a parameter was inferred as any we skip adding function parameters at all. + // Turning an implicit any (which under common settings is a error) to an explicit + // is probably actually a worse refactor outcome. + if (hasAny) return { variableType, initializer }; + variableType = undefined; + if (isArrowFunction(initializer)) { + initializer = factory.updateArrowFunction(initializer, canHaveModifiers(node) ? getModifiers(node) : undefined, initializer.typeParameters, + parameters, + initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation), + initializer.equalsGreaterThanToken, + initializer.body); + } + else { + if (functionSignature && !!functionSignature.thisParameter) { + const firstParameter = firstOrUndefined(parameters); + // If the function signature has a this parameter and if the first defined parameter is not the this parameter, we must add it + // Note: If this parameter was already there, it would have been previously updated with the type if not type was present + if ((!firstParameter || (isIdentifier(firstParameter.name) && firstParameter.name.escapedText !== "this"))) { + const thisType = checker.getTypeOfSymbolAtLocation(functionSignature.thisParameter, node); + parameters.splice(0, 0, factory.createParameterDeclaration( + /* modifiers */ undefined, + /* dotDotDotToken */ undefined, + "this", + /* questionToken */ undefined, + checker.typeToTypeNode(thisType, scope, NodeBuilderFlags.NoTruncation) + )); } - initializer = factory.updateFunctionExpression(initializer, canHaveModifiers(node) ? getModifiers(node) : undefined, initializer.asteriskToken, - initializer.name, initializer.typeParameters, - parameters, - initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation), - initializer.body); } - return { variableType, initializer }; + initializer = factory.updateFunctionExpression(initializer, canHaveModifiers(node) ? getModifiers(node) : undefined, initializer.asteriskToken, + initializer.name, initializer.typeParameters, + parameters, + initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation), + initializer.body); } + return { variableType, initializer }; } +} - function getContainingVariableDeclarationIfInList(node: Node, scope: Scope) { - let prevNode; - while (node !== undefined && node !== scope) { - if (isVariableDeclaration(node) && - node.initializer === prevNode && - isVariableDeclarationList(node.parent) && - node.parent.declarations.length > 1) { - - return node; - } +function getContainingVariableDeclarationIfInList(node: Node, scope: Scope) { + let prevNode; + while (node !== undefined && node !== scope) { + if (isVariableDeclaration(node) && + node.initializer === prevNode && + isVariableDeclarationList(node.parent) && + node.parent.declarations.length > 1) { - prevNode = node; - node = node.parent; + return node; } + + prevNode = node; + node = node.parent; } +} - function getFirstDeclaration(type: Type): Declaration | undefined { - let firstDeclaration; +function getFirstDeclaration(type: Type): Declaration | undefined { + let firstDeclaration; - const symbol = type.symbol; - if (symbol && symbol.declarations) { - for (const declaration of symbol.declarations) { - if (firstDeclaration === undefined || declaration.pos < firstDeclaration.pos) { - firstDeclaration = declaration; - } + const symbol = type.symbol; + if (symbol && symbol.declarations) { + for (const declaration of symbol.declarations) { + if (firstDeclaration === undefined || declaration.pos < firstDeclaration.pos) { + firstDeclaration = declaration; } } - - return firstDeclaration; } - function compareTypesByDeclarationOrder( - { type: type1, declaration: declaration1 }: { type: Type, declaration?: Declaration }, - { type: type2, declaration: declaration2 }: { type: Type, declaration?: Declaration }) { + return firstDeclaration; +} + +function compareTypesByDeclarationOrder( + { type: type1, declaration: declaration1 }: { type: Type, declaration?: Declaration }, + { type: type2, declaration: declaration2 }: { type: Type, declaration?: Declaration }) { + + return compareProperties(declaration1, declaration2, "pos", compareValues) + || compareStringsCaseSensitive( + type1.symbol ? type1.symbol.getName() : "", + type2.symbol ? type2.symbol.getName() : "") + || compareValues(type1.id, type2.id); +} - return compareProperties(declaration1, declaration2, "pos", compareValues) - || compareStringsCaseSensitive( - type1.symbol ? type1.symbol.getName() : "", - type2.symbol ? type2.symbol.getName() : "") - || compareValues(type1.id, type2.id); +function getCalledExpression(scope: Node, range: TargetRange, functionNameText: string): Expression { + const functionReference = factory.createIdentifier(functionNameText); + if (isClassLike(scope)) { + const lhs = range.facts & RangeFacts.InStaticRegion ? factory.createIdentifier(scope.name!.text) : factory.createThis(); // TODO: GH#18217 + return factory.createPropertyAccessExpression(lhs, functionReference); + } + else { + return functionReference; } +} - function getCalledExpression(scope: Node, range: TargetRange, functionNameText: string): Expression { - const functionReference = factory.createIdentifier(functionNameText); - if (isClassLike(scope)) { - const lhs = range.facts & RangeFacts.InStaticRegion ? factory.createIdentifier(scope.name!.text) : factory.createThis(); // TODO: GH#18217 - return factory.createPropertyAccessExpression(lhs, functionReference); - } - else { - return functionReference; - } - } - - function transformFunctionBody(body: Node, exposedVariableDeclarations: readonly VariableDeclaration[], writes: readonly UsageEntry[] | undefined, substitutions: ReadonlyESMap, hasReturn: boolean): { body: Block, returnValueProperty: string | undefined } { - const hasWritesOrVariableDeclarations = writes !== undefined || exposedVariableDeclarations.length > 0; - if (isBlock(body) && !hasWritesOrVariableDeclarations && substitutions.size === 0) { - // already block, no declarations or writes to propagate back, no substitutions - can use node as is - return { body: factory.createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined }; - } - let returnValueProperty: string | undefined; - let ignoreReturns = false; - const statements = factory.createNodeArray(isBlock(body) ? body.statements.slice(0) : [isStatement(body) ? body : factory.createReturnStatement(skipParentheses(body as Expression))]); - // rewrite body if either there are writes that should be propagated back via return statements or there are substitutions - if (hasWritesOrVariableDeclarations || substitutions.size) { - const rewrittenStatements = visitNodes(statements, visitor).slice(); - if (hasWritesOrVariableDeclarations && !hasReturn && isStatement(body)) { - // add return at the end to propagate writes back in case if control flow falls out of the function body - // it is ok to know that range has at least one return since it we only allow unconditional returns - const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); - if (assignments.length === 1) { - rewrittenStatements.push(factory.createReturnStatement(assignments[0].name)); - } - else { - rewrittenStatements.push(factory.createReturnStatement(factory.createObjectLiteralExpression(assignments))); - } +function transformFunctionBody(body: Node, exposedVariableDeclarations: readonly VariableDeclaration[], writes: readonly UsageEntry[] | undefined, substitutions: ReadonlyESMap, hasReturn: boolean): { body: Block, returnValueProperty: string | undefined } { + const hasWritesOrVariableDeclarations = writes !== undefined || exposedVariableDeclarations.length > 0; + if (isBlock(body) && !hasWritesOrVariableDeclarations && substitutions.size === 0) { + // already block, no declarations or writes to propagate back, no substitutions - can use node as is + return { body: factory.createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined }; + } + let returnValueProperty: string | undefined; + let ignoreReturns = false; + const statements = factory.createNodeArray(isBlock(body) ? body.statements.slice(0) : [isStatement(body) ? body : factory.createReturnStatement(skipParentheses(body as Expression))]); + // rewrite body if either there are writes that should be propagated back via return statements or there are substitutions + if (hasWritesOrVariableDeclarations || substitutions.size) { + const rewrittenStatements = visitNodes(statements, visitor).slice(); + if (hasWritesOrVariableDeclarations && !hasReturn && isStatement(body)) { + // add return at the end to propagate writes back in case if control flow falls out of the function body + // it is ok to know that range has at least one return since it we only allow unconditional returns + const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); + if (assignments.length === 1) { + rewrittenStatements.push(factory.createReturnStatement(assignments[0].name)); + } + else { + rewrittenStatements.push(factory.createReturnStatement(factory.createObjectLiteralExpression(assignments))); } - return { body: factory.createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty }; - } - else { - return { body: factory.createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined }; } + return { body: factory.createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty }; + } + else { + return { body: factory.createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined }; + } - function visitor(node: Node): VisitResult { - if (!ignoreReturns && isReturnStatement(node) && hasWritesOrVariableDeclarations) { - const assignments: ObjectLiteralElementLike[] = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); - if (node.expression) { - if (!returnValueProperty) { - returnValueProperty = "__return"; - } - assignments.unshift(factory.createPropertyAssignment(returnValueProperty, visitNode(node.expression, visitor))); - } - if (assignments.length === 1) { - return factory.createReturnStatement(assignments[0].name as Expression); - } - else { - return factory.createReturnStatement(factory.createObjectLiteralExpression(assignments)); + function visitor(node: Node): VisitResult { + if (!ignoreReturns && isReturnStatement(node) && hasWritesOrVariableDeclarations) { + const assignments: ObjectLiteralElementLike[] = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes); + if (node.expression) { + if (!returnValueProperty) { + returnValueProperty = "__return"; } + assignments.unshift(factory.createPropertyAssignment(returnValueProperty, visitNode(node.expression, visitor))); + } + if (assignments.length === 1) { + return factory.createReturnStatement(assignments[0].name as Expression); } else { - const oldIgnoreReturns = ignoreReturns; - ignoreReturns = ignoreReturns || isFunctionLikeDeclaration(node) || isClassLike(node); - const substitution = substitutions.get(getNodeId(node).toString()); - const result = substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext); - ignoreReturns = oldIgnoreReturns; - return result; + return factory.createReturnStatement(factory.createObjectLiteralExpression(assignments)); } } - } - - function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyESMap): Expression { - return substitutions.size - ? visitor(initializer) as Expression - : initializer; - - function visitor(node: Node): VisitResult { + else { + const oldIgnoreReturns = ignoreReturns; + ignoreReturns = ignoreReturns || isFunctionLikeDeclaration(node) || isClassLike(node); const substitution = substitutions.get(getNodeId(node).toString()); - return substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext); + const result = substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext); + ignoreReturns = oldIgnoreReturns; + return result; } } +} - function getStatementsOrClassElements(scope: Scope): readonly Statement[] | readonly ClassElement[] { - if (isFunctionLikeDeclaration(scope)) { - const body = scope.body!; // TODO: GH#18217 - if (isBlock(body)) { - return body.statements; - } - } - else if (isModuleBlock(scope) || isSourceFile(scope)) { - return scope.statements; - } - else if (isClassLike(scope)) { - return scope.members; - } - else { - assertType(scope); - } +function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyESMap): Expression { + return substitutions.size + ? visitor(initializer) as Expression + : initializer; - return emptyArray; + function visitor(node: Node): VisitResult { + const substitution = substitutions.get(getNodeId(node).toString()); + return substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext); } +} - /** - * If `scope` contains a function after `minPos`, then return the first such function. - * Otherwise, return `undefined`. - */ - function getNodeToInsertFunctionBefore(minPos: number, scope: Scope): Statement | ClassElement | undefined { - return find(getStatementsOrClassElements(scope), child => - child.pos >= minPos && isFunctionLikeDeclaration(child) && !isConstructorDeclaration(child)); +function getStatementsOrClassElements(scope: Scope): readonly Statement[] | readonly ClassElement[] { + if (isFunctionLikeDeclaration(scope)) { + const body = scope.body!; // TODO: GH#18217 + if (isBlock(body)) { + return body.statements; + } + } + else if (isModuleBlock(scope) || isSourceFile(scope)) { + return scope.statements; + } + else if (isClassLike(scope)) { + return scope.members; + } + else { + assertType(scope); } - function getNodeToInsertPropertyBefore(maxPos: number, scope: ClassLikeDeclaration): ClassElement { - const members = scope.members; - Debug.assert(members.length > 0, "Found no members"); // There must be at least one child, since we extracted from one. + return emptyArray; +} - let prevMember: ClassElement | undefined; - let allProperties = true; - for (const member of members) { - if (member.pos > maxPos) { - return prevMember || members[0]; - } - if (allProperties && !isPropertyDeclaration(member)) { - // If it is non-vacuously true that all preceding members are properties, - // insert before the current member (i.e. at the end of the list of properties). - if (prevMember !== undefined) { - return member; - } +/** + * If `scope` contains a function after `minPos`, then return the first such function. + * Otherwise, return `undefined`. + */ +function getNodeToInsertFunctionBefore(minPos: number, scope: Scope): Statement | ClassElement | undefined { + return find(getStatementsOrClassElements(scope), child => + child.pos >= minPos && isFunctionLikeDeclaration(child) && !isConstructorDeclaration(child)); +} - allProperties = false; - } - prevMember = member; +function getNodeToInsertPropertyBefore(maxPos: number, scope: ClassLikeDeclaration): ClassElement { + const members = scope.members; + Debug.assert(members.length > 0, "Found no members"); // There must be at least one child, since we extracted from one. + + let prevMember: ClassElement | undefined; + let allProperties = true; + for (const member of members) { + if (member.pos > maxPos) { + return prevMember || members[0]; } + if (allProperties && !isPropertyDeclaration(member)) { + // If it is non-vacuously true that all preceding members are properties, + // insert before the current member (i.e. at the end of the list of properties). + if (prevMember !== undefined) { + return member; + } - if (prevMember === undefined) return Debug.fail(); // If the loop didn't return, then it did set prevMember. - return prevMember; + allProperties = false; + } + prevMember = member; } - function getNodeToInsertConstantBefore(node: Node, scope: Scope): Statement { - Debug.assert(!isClassLike(scope)); + if (prevMember === undefined) return Debug.fail(); // If the loop didn't return, then it did set prevMember. + return prevMember; +} - let prevScope: Scope | undefined; - for (let curr = node; curr !== scope; curr = curr.parent) { - if (isScope(curr)) { - prevScope = curr; - } - } +function getNodeToInsertConstantBefore(node: Node, scope: Scope): Statement { + Debug.assert(!isClassLike(scope)); - for (let curr = (prevScope || node).parent; ; curr = curr.parent) { - if (isBlockLike(curr)) { - let prevStatement: Statement | undefined; - for (const statement of curr.statements) { - if (statement.pos > node.pos) { - break; - } - prevStatement = statement; - } + let prevScope: Scope | undefined; + for (let curr = node; curr !== scope; curr = curr.parent) { + if (isScope(curr)) { + prevScope = curr; + } + } - if (!prevStatement && isCaseClause(curr)) { - // We must have been in the expression of the case clause. - Debug.assert(isSwitchStatement(curr.parent.parent), "Grandparent isn't a switch statement"); - return curr.parent.parent; + for (let curr = (prevScope || node).parent; ; curr = curr.parent) { + if (isBlockLike(curr)) { + let prevStatement: Statement | undefined; + for (const statement of curr.statements) { + if (statement.pos > node.pos) { + break; } + prevStatement = statement; + } - // There must be at least one statement since we started in one. - return Debug.checkDefined(prevStatement, "prevStatement failed to get set"); + if (!prevStatement && isCaseClause(curr)) { + // We must have been in the expression of the case clause. + Debug.assert(isSwitchStatement(curr.parent.parent), "Grandparent isn't a switch statement"); + return curr.parent.parent; } - Debug.assert(curr !== scope, "Didn't encounter a block-like before encountering scope"); + // There must be at least one statement since we started in one. + return Debug.checkDefined(prevStatement, "prevStatement failed to get set"); } + + Debug.assert(curr !== scope, "Didn't encounter a block-like before encountering scope"); } +} + +function getPropertyAssignmentsForWritesAndVariableDeclarations( + exposedVariableDeclarations: readonly VariableDeclaration[], + writes: readonly UsageEntry[] | undefined +): ShorthandPropertyAssignment[] { + const variableAssignments = map(exposedVariableDeclarations, v => factory.createShorthandPropertyAssignment(v.symbol.name)); + const writeAssignments = map(writes, w => factory.createShorthandPropertyAssignment(w.symbol.name)); + + // TODO: GH#18217 `variableAssignments` not possibly undefined! + return variableAssignments === undefined + ? writeAssignments! + : writeAssignments === undefined + ? variableAssignments + : variableAssignments.concat(writeAssignments); +} - function getPropertyAssignmentsForWritesAndVariableDeclarations( - exposedVariableDeclarations: readonly VariableDeclaration[], - writes: readonly UsageEntry[] | undefined - ): ShorthandPropertyAssignment[] { - const variableAssignments = map(exposedVariableDeclarations, v => factory.createShorthandPropertyAssignment(v.symbol.name)); - const writeAssignments = map(writes, w => factory.createShorthandPropertyAssignment(w.symbol.name)); +function isReadonlyArray(v: any): v is readonly any[] { + return isArray(v); +} - // TODO: GH#18217 `variableAssignments` not possibly undefined! - return variableAssignments === undefined - ? writeAssignments! - : writeAssignments === undefined - ? variableAssignments - : variableAssignments.concat(writeAssignments); - } +/** + * Produces a range that spans the entirety of nodes, given a selection + * that might start/end in the middle of nodes. + * + * For example, when the user makes a selection like this + * v---v + * var someThing = foo + bar; + * this returns ^-------^ + */ +function getEnclosingTextRange(targetRange: TargetRange, sourceFile: SourceFile): TextRange { + return isReadonlyArray(targetRange.range) + ? { pos: first(targetRange.range).getStart(sourceFile), end: last(targetRange.range).getEnd() } + : targetRange.range; +} + +const enum Usage { + // value should be passed to extracted method + Read = 1, + // value should be passed to extracted method and propagated back + Write = 2 +} + +interface UsageEntry { + readonly usage: Usage; + readonly symbol: Symbol; + readonly node: Node; +} + +interface ScopeUsages { + readonly usages: ESMap; + readonly typeParameterUsages: ESMap; // Key is type ID + readonly substitutions: ESMap; +} + +interface ReadsAndWrites { + readonly target: Expression | Block; + readonly usagesPerScope: readonly ScopeUsages[]; + readonly functionErrorsPerScope: readonly (readonly Diagnostic[])[]; + readonly constantErrorsPerScope: readonly (readonly Diagnostic[])[]; + readonly exposedVariableDeclarations: readonly VariableDeclaration[]; +} +function collectReadsAndWrites( + targetRange: TargetRange, + scopes: Scope[], + enclosingTextRange: TextRange, + sourceFile: SourceFile, + checker: TypeChecker, + cancellationToken: CancellationToken): ReadsAndWrites { + + const allTypeParameterUsages = new Map(); // Key is type ID + const usagesPerScope: ScopeUsages[] = []; + const substitutionsPerScope: ESMap[] = []; + const functionErrorsPerScope: Diagnostic[][] = []; + const constantErrorsPerScope: Diagnostic[][] = []; + const visibleDeclarationsInExtractedRange: NamedDeclaration[] = []; + const exposedVariableSymbolSet = new Map(); // Key is symbol ID + const exposedVariableDeclarations: VariableDeclaration[] = []; + let firstExposedNonVariableDeclaration: NamedDeclaration | undefined; + + const expression = !isReadonlyArray(targetRange.range) + ? targetRange.range + : targetRange.range.length === 1 && isExpressionStatement(targetRange.range[0]) + ? targetRange.range[0].expression + : undefined; - function isReadonlyArray(v: any): v is readonly any[] { - return isArray(v); + let expressionDiagnostic: Diagnostic | undefined; + if (expression === undefined) { + const statements = targetRange.range as readonly Statement[]; + const start = first(statements).getStart(); + const end = last(statements).end; + expressionDiagnostic = createFileDiagnostic(sourceFile, start, end - start, Messages.expressionExpected); + } + else if (checker.getTypeAtLocation(expression).flags & (TypeFlags.Void | TypeFlags.Never)) { + expressionDiagnostic = createDiagnosticForNode(expression, Messages.uselessConstantType); } - /** - * Produces a range that spans the entirety of nodes, given a selection - * that might start/end in the middle of nodes. - * - * For example, when the user makes a selection like this - * v---v - * var someThing = foo + bar; - * this returns ^-------^ - */ - function getEnclosingTextRange(targetRange: TargetRange, sourceFile: SourceFile): TextRange { - return isReadonlyArray(targetRange.range) - ? { pos: first(targetRange.range).getStart(sourceFile), end: last(targetRange.range).getEnd() } - : targetRange.range; - } - - const enum Usage { - // value should be passed to extracted method - Read = 1, - // value should be passed to extracted method and propagated back - Write = 2 - } - - interface UsageEntry { - readonly usage: Usage; - readonly symbol: Symbol; - readonly node: Node; - } - - interface ScopeUsages { - readonly usages: ESMap; - readonly typeParameterUsages: ESMap; // Key is type ID - readonly substitutions: ESMap; - } - - interface ReadsAndWrites { - readonly target: Expression | Block; - readonly usagesPerScope: readonly ScopeUsages[]; - readonly functionErrorsPerScope: readonly (readonly Diagnostic[])[]; - readonly constantErrorsPerScope: readonly (readonly Diagnostic[])[]; - readonly exposedVariableDeclarations: readonly VariableDeclaration[]; - } - function collectReadsAndWrites( - targetRange: TargetRange, - scopes: Scope[], - enclosingTextRange: TextRange, - sourceFile: SourceFile, - checker: TypeChecker, - cancellationToken: CancellationToken): ReadsAndWrites { - - const allTypeParameterUsages = new Map(); // Key is type ID - const usagesPerScope: ScopeUsages[] = []; - const substitutionsPerScope: ESMap[] = []; - const functionErrorsPerScope: Diagnostic[][] = []; - const constantErrorsPerScope: Diagnostic[][] = []; - const visibleDeclarationsInExtractedRange: NamedDeclaration[] = []; - const exposedVariableSymbolSet = new Map(); // Key is symbol ID - const exposedVariableDeclarations: VariableDeclaration[] = []; - let firstExposedNonVariableDeclaration: NamedDeclaration | undefined; - - const expression = !isReadonlyArray(targetRange.range) - ? targetRange.range - : targetRange.range.length === 1 && isExpressionStatement(targetRange.range[0]) - ? targetRange.range[0].expression - : undefined; - - let expressionDiagnostic: Diagnostic | undefined; - if (expression === undefined) { - const statements = targetRange.range as readonly Statement[]; - const start = first(statements).getStart(); - const end = last(statements).end; - expressionDiagnostic = createFileDiagnostic(sourceFile, start, end - start, Messages.expressionExpected); - } - else if (checker.getTypeAtLocation(expression).flags & (TypeFlags.Void | TypeFlags.Never)) { - expressionDiagnostic = createDiagnosticForNode(expression, Messages.uselessConstantType); - } - - // initialize results - for (const scope of scopes) { - usagesPerScope.push({ usages: new Map(), typeParameterUsages: new Map(), substitutions: new Map() }); - substitutionsPerScope.push(new Map()); - - functionErrorsPerScope.push([]); - - const constantErrors = []; - if (expressionDiagnostic) { - constantErrors.push(expressionDiagnostic); - } - if (isClassLike(scope) && isInJSFile(scope)) { - constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToJSClass)); - } - if (isArrowFunction(scope) && !isBlock(scope.body)) { - // TODO (https://github.com/Microsoft/TypeScript/issues/18924): allow this - constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToExpressionArrowFunction)); - } - constantErrorsPerScope.push(constantErrors); + // initialize results + for (const scope of scopes) { + usagesPerScope.push({ usages: new Map(), typeParameterUsages: new Map(), substitutions: new Map() }); + substitutionsPerScope.push(new Map()); + + functionErrorsPerScope.push([]); + + const constantErrors = []; + if (expressionDiagnostic) { + constantErrors.push(expressionDiagnostic); + } + if (isClassLike(scope) && isInJSFile(scope)) { + constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToJSClass)); } + if (isArrowFunction(scope) && !isBlock(scope.body)) { + // TODO (https://github.com/Microsoft/TypeScript/issues/18924): allow this + constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToExpressionArrowFunction)); + } + constantErrorsPerScope.push(constantErrors); + } - const seenUsages = new Map(); - const target = isReadonlyArray(targetRange.range) ? factory.createBlock(targetRange.range) : targetRange.range; + const seenUsages = new Map(); + const target = isReadonlyArray(targetRange.range) ? factory.createBlock(targetRange.range) : targetRange.range; - const unmodifiedNode = isReadonlyArray(targetRange.range) ? first(targetRange.range) : targetRange.range; - const inGenericContext = isInGenericContext(unmodifiedNode); + const unmodifiedNode = isReadonlyArray(targetRange.range) ? first(targetRange.range) : targetRange.range; + const inGenericContext = isInGenericContext(unmodifiedNode); - collectUsages(target); + collectUsages(target); - // Unfortunately, this code takes advantage of the knowledge that the generated method - // will use the contextual type of an expression as the return type of the extracted - // method (and will therefore "use" all the types involved). - if (inGenericContext && !isReadonlyArray(targetRange.range) && !isJsxAttribute(targetRange.range)) { - const contextualType = checker.getContextualType(targetRange.range)!; // TODO: GH#18217 - recordTypeParameterUsages(contextualType); - } + // Unfortunately, this code takes advantage of the knowledge that the generated method + // will use the contextual type of an expression as the return type of the extracted + // method (and will therefore "use" all the types involved). + if (inGenericContext && !isReadonlyArray(targetRange.range) && !isJsxAttribute(targetRange.range)) { + const contextualType = checker.getContextualType(targetRange.range)!; // TODO: GH#18217 + recordTypeParameterUsages(contextualType); + } - if (allTypeParameterUsages.size > 0) { - const seenTypeParameterUsages = new Map(); // Key is type ID + if (allTypeParameterUsages.size > 0) { + const seenTypeParameterUsages = new Map(); // Key is type ID - let i = 0; - for (let curr: Node = unmodifiedNode; curr !== undefined && i < scopes.length; curr = curr.parent) { - if (curr === scopes[i]) { - // Copy current contents of seenTypeParameterUsages into scope. - seenTypeParameterUsages.forEach((typeParameter, id) => { - usagesPerScope[i].typeParameterUsages.set(id, typeParameter); - }); + let i = 0; + for (let curr: Node = unmodifiedNode; curr !== undefined && i < scopes.length; curr = curr.parent) { + if (curr === scopes[i]) { + // Copy current contents of seenTypeParameterUsages into scope. + seenTypeParameterUsages.forEach((typeParameter, id) => { + usagesPerScope[i].typeParameterUsages.set(id, typeParameter); + }); - i++; - } + i++; + } - // Note that we add the current node's type parameters *after* updating the corresponding scope. - if (isDeclarationWithTypeParameters(curr)) { - for (const typeParameterDecl of getEffectiveTypeParameterDeclarations(curr)) { - const typeParameter = checker.getTypeAtLocation(typeParameterDecl) as TypeParameter; - if (allTypeParameterUsages.has(typeParameter.id.toString())) { - seenTypeParameterUsages.set(typeParameter.id.toString(), typeParameter); - } + // Note that we add the current node's type parameters *after* updating the corresponding scope. + if (isDeclarationWithTypeParameters(curr)) { + for (const typeParameterDecl of getEffectiveTypeParameterDeclarations(curr)) { + const typeParameter = checker.getTypeAtLocation(typeParameterDecl) as TypeParameter; + if (allTypeParameterUsages.has(typeParameter.id.toString())) { + seenTypeParameterUsages.set(typeParameter.id.toString(), typeParameter); } } } - - // If we didn't get through all the scopes, then there were some that weren't in our - // parent chain (impossible at time of writing). A conservative solution would be to - // copy allTypeParameterUsages into all remaining scopes. - Debug.assert(i === scopes.length, "Should have iterated all scopes"); - } - - // If there are any declarations in the extracted block that are used in the same enclosing - // lexical scope, we can't move the extraction "up" as those declarations will become unreachable - if (visibleDeclarationsInExtractedRange.length) { - const containingLexicalScopeOfExtraction = isBlockScope(scopes[0], scopes[0].parent) - ? scopes[0] - : getEnclosingBlockScopeContainer(scopes[0]); - forEachChild(containingLexicalScopeOfExtraction, checkForUsedDeclarations); } - for (let i = 0; i < scopes.length; i++) { - const scopeUsages = usagesPerScope[i]; - // Special case: in the innermost scope, all usages are available. - // (The computed value reflects the value at the top-level of the scope, but the - // local will actually be declared at the same level as the extracted expression). - if (i > 0 && (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0)) { - const errorNode = isReadonlyArray(targetRange.range) ? targetRange.range[0] : targetRange.range; - constantErrorsPerScope[i].push(createDiagnosticForNode(errorNode, Messages.cannotAccessVariablesFromNestedScopes)); - } + // If we didn't get through all the scopes, then there were some that weren't in our + // parent chain (impossible at time of writing). A conservative solution would be to + // copy allTypeParameterUsages into all remaining scopes. + Debug.assert(i === scopes.length, "Should have iterated all scopes"); + } - if (targetRange.facts & RangeFacts.UsesThisInFunction && isClassLike(scopes[i])) { - functionErrorsPerScope[i].push(createDiagnosticForNode(targetRange.thisNode!, Messages.cannotExtractFunctionsContainingThisToMethod)); - } + // If there are any declarations in the extracted block that are used in the same enclosing + // lexical scope, we can't move the extraction "up" as those declarations will become unreachable + if (visibleDeclarationsInExtractedRange.length) { + const containingLexicalScopeOfExtraction = isBlockScope(scopes[0], scopes[0].parent) + ? scopes[0] + : getEnclosingBlockScopeContainer(scopes[0]); + forEachChild(containingLexicalScopeOfExtraction, checkForUsedDeclarations); + } - let hasWrite = false; - let readonlyClassPropertyWrite: Declaration | undefined; - usagesPerScope[i].usages.forEach(value => { - if (value.usage === Usage.Write) { - hasWrite = true; - if (value.symbol.flags & SymbolFlags.ClassMember && - value.symbol.valueDeclaration && - hasEffectiveModifier(value.symbol.valueDeclaration, ModifierFlags.Readonly)) { - readonlyClassPropertyWrite = value.symbol.valueDeclaration; - } + for (let i = 0; i < scopes.length; i++) { + const scopeUsages = usagesPerScope[i]; + // Special case: in the innermost scope, all usages are available. + // (The computed value reflects the value at the top-level of the scope, but the + // local will actually be declared at the same level as the extracted expression). + if (i > 0 && (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0)) { + const errorNode = isReadonlyArray(targetRange.range) ? targetRange.range[0] : targetRange.range; + constantErrorsPerScope[i].push(createDiagnosticForNode(errorNode, Messages.cannotAccessVariablesFromNestedScopes)); + } + + if (targetRange.facts & RangeFacts.UsesThisInFunction && isClassLike(scopes[i])) { + functionErrorsPerScope[i].push(createDiagnosticForNode(targetRange.thisNode!, Messages.cannotExtractFunctionsContainingThisToMethod)); + } + + let hasWrite = false; + let readonlyClassPropertyWrite: Declaration | undefined; + usagesPerScope[i].usages.forEach(value => { + if (value.usage === Usage.Write) { + hasWrite = true; + if (value.symbol.flags & SymbolFlags.ClassMember && + value.symbol.valueDeclaration && + hasEffectiveModifier(value.symbol.valueDeclaration, ModifierFlags.Readonly)) { + readonlyClassPropertyWrite = value.symbol.valueDeclaration; } - }); + } + }); - // If an expression was extracted, then there shouldn't have been any variable declarations. - Debug.assert(isReadonlyArray(targetRange.range) || exposedVariableDeclarations.length === 0, "No variable declarations expected if something was extracted"); + // If an expression was extracted, then there shouldn't have been any variable declarations. + Debug.assert(isReadonlyArray(targetRange.range) || exposedVariableDeclarations.length === 0, "No variable declarations expected if something was extracted"); - if (hasWrite && !isReadonlyArray(targetRange.range)) { - const diag = createDiagnosticForNode(targetRange.range, Messages.cannotWriteInExpression); - functionErrorsPerScope[i].push(diag); - constantErrorsPerScope[i].push(diag); - } - else if (readonlyClassPropertyWrite && i > 0) { - const diag = createDiagnosticForNode(readonlyClassPropertyWrite, Messages.cannotExtractReadonlyPropertyInitializerOutsideConstructor); - functionErrorsPerScope[i].push(diag); - constantErrorsPerScope[i].push(diag); - } - else if (firstExposedNonVariableDeclaration) { - const diag = createDiagnosticForNode(firstExposedNonVariableDeclaration, Messages.cannotExtractExportedEntity); - functionErrorsPerScope[i].push(diag); - constantErrorsPerScope[i].push(diag); - } + if (hasWrite && !isReadonlyArray(targetRange.range)) { + const diag = createDiagnosticForNode(targetRange.range, Messages.cannotWriteInExpression); + functionErrorsPerScope[i].push(diag); + constantErrorsPerScope[i].push(diag); + } + else if (readonlyClassPropertyWrite && i > 0) { + const diag = createDiagnosticForNode(readonlyClassPropertyWrite, Messages.cannotExtractReadonlyPropertyInitializerOutsideConstructor); + functionErrorsPerScope[i].push(diag); + constantErrorsPerScope[i].push(diag); } + else if (firstExposedNonVariableDeclaration) { + const diag = createDiagnosticForNode(firstExposedNonVariableDeclaration, Messages.cannotExtractExportedEntity); + functionErrorsPerScope[i].push(diag); + constantErrorsPerScope[i].push(diag); + } + } - return { target, usagesPerScope, functionErrorsPerScope, constantErrorsPerScope, exposedVariableDeclarations }; + return { target, usagesPerScope, functionErrorsPerScope, constantErrorsPerScope, exposedVariableDeclarations }; - function isInGenericContext(node: Node) { - return !!findAncestor(node, n => isDeclarationWithTypeParameters(n) && getEffectiveTypeParameterDeclarations(n).length !== 0); - } + function isInGenericContext(node: Node) { + return !!findAncestor(node, n => isDeclarationWithTypeParameters(n) && getEffectiveTypeParameterDeclarations(n).length !== 0); + } - function recordTypeParameterUsages(type: Type) { - // PERF: This is potentially very expensive. `type` could be a library type with - // a lot of properties, each of which the walker will visit. Unfortunately, the - // solution isn't as trivial as filtering to user types because of (e.g.) Array. - const symbolWalker = checker.getSymbolWalker(() => (cancellationToken.throwIfCancellationRequested(), true)); - const { visitedTypes } = symbolWalker.walkType(type); + function recordTypeParameterUsages(type: Type) { + // PERF: This is potentially very expensive. `type` could be a library type with + // a lot of properties, each of which the walker will visit. Unfortunately, the + // solution isn't as trivial as filtering to user types because of (e.g.) Array. + const symbolWalker = checker.getSymbolWalker(() => (cancellationToken.throwIfCancellationRequested(), true)); + const { visitedTypes } = symbolWalker.walkType(type); - for (const visitedType of visitedTypes) { - if (visitedType.isTypeParameter()) { - allTypeParameterUsages.set(visitedType.id.toString(), visitedType); - } + for (const visitedType of visitedTypes) { + if (visitedType.isTypeParameter()) { + allTypeParameterUsages.set(visitedType.id.toString(), visitedType); } } + } - function collectUsages(node: Node, valueUsage = Usage.Read) { - if (inGenericContext) { - const type = checker.getTypeAtLocation(node); - recordTypeParameterUsages(type); - } + function collectUsages(node: Node, valueUsage = Usage.Read) { + if (inGenericContext) { + const type = checker.getTypeAtLocation(node); + recordTypeParameterUsages(type); + } - if (isDeclaration(node) && node.symbol) { - visibleDeclarationsInExtractedRange.push(node); - } + if (isDeclaration(node) && node.symbol) { + visibleDeclarationsInExtractedRange.push(node); + } - if (isAssignmentExpression(node)) { - // use 'write' as default usage for values - collectUsages(node.left, Usage.Write); - collectUsages(node.right); - } - else if (isUnaryExpressionWithWrite(node)) { - collectUsages(node.operand, Usage.Write); - } - else if (isPropertyAccessExpression(node) || isElementAccessExpression(node)) { - // use 'write' as default usage for values - forEachChild(node, collectUsages); + if (isAssignmentExpression(node)) { + // use 'write' as default usage for values + collectUsages(node.left, Usage.Write); + collectUsages(node.right); + } + else if (isUnaryExpressionWithWrite(node)) { + collectUsages(node.operand, Usage.Write); + } + else if (isPropertyAccessExpression(node) || isElementAccessExpression(node)) { + // use 'write' as default usage for values + forEachChild(node, collectUsages); + } + else if (isIdentifier(node)) { + if (!node.parent) { + return; } - else if (isIdentifier(node)) { - if (!node.parent) { - return; - } - if (isQualifiedName(node.parent) && node !== node.parent.left) { - return; - } - if (isPropertyAccessExpression(node.parent) && node !== node.parent.expression) { - return; - } - recordUsage(node, valueUsage, /*isTypeNode*/ isPartOfTypeNode(node)); + if (isQualifiedName(node.parent) && node !== node.parent.left) { + return; } - else { - forEachChild(node, collectUsages); + if (isPropertyAccessExpression(node.parent) && node !== node.parent.expression) { + return; } + recordUsage(node, valueUsage, /*isTypeNode*/ isPartOfTypeNode(node)); + } + else { + forEachChild(node, collectUsages); } + } - function recordUsage(n: Identifier, usage: Usage, isTypeNode: boolean) { - const symbolId = recordUsagebySymbol(n, usage, isTypeNode); - if (symbolId) { - for (let i = 0; i < scopes.length; i++) { - // push substitution from map to map to simplify rewriting - const substitution = substitutionsPerScope[i].get(symbolId); - if (substitution) { - usagesPerScope[i].substitutions.set(getNodeId(n).toString(), substitution); - } + function recordUsage(n: Identifier, usage: Usage, isTypeNode: boolean) { + const symbolId = recordUsagebySymbol(n, usage, isTypeNode); + if (symbolId) { + for (let i = 0; i < scopes.length; i++) { + // push substitution from map to map to simplify rewriting + const substitution = substitutionsPerScope[i].get(symbolId); + if (substitution) { + usagesPerScope[i].substitutions.set(getNodeId(n).toString(), substitution); } } } + } - function recordUsagebySymbol(identifier: Identifier, usage: Usage, isTypeName: boolean) { - const symbol = getSymbolReferencedByIdentifier(identifier); - if (!symbol) { - // cannot find symbol - do nothing - return undefined; - } - const symbolId = getSymbolId(symbol).toString(); - const lastUsage = seenUsages.get(symbolId); - // there are two kinds of value usages - // - reads - if range contains a read from the value located outside of the range then value should be passed as a parameter - // - writes - if range contains a write to a value located outside the range the value should be passed as a parameter and - // returned as a return value - // 'write' case is a superset of 'read' so if we already have processed 'write' of some symbol there is not need to handle 'read' - // since all information is already recorded - if (lastUsage && lastUsage >= usage) { - return symbolId; - } + function recordUsagebySymbol(identifier: Identifier, usage: Usage, isTypeName: boolean) { + const symbol = getSymbolReferencedByIdentifier(identifier); + if (!symbol) { + // cannot find symbol - do nothing + return undefined; + } + const symbolId = getSymbolId(symbol).toString(); + const lastUsage = seenUsages.get(symbolId); + // there are two kinds of value usages + // - reads - if range contains a read from the value located outside of the range then value should be passed as a parameter + // - writes - if range contains a write to a value located outside the range the value should be passed as a parameter and + // returned as a return value + // 'write' case is a superset of 'read' so if we already have processed 'write' of some symbol there is not need to handle 'read' + // since all information is already recorded + if (lastUsage && lastUsage >= usage) { + return symbolId; + } - seenUsages.set(symbolId, usage); - if (lastUsage) { - // if we get here this means that we are trying to handle 'write' and 'read' was already processed - // walk scopes and update existing records. - for (const perScope of usagesPerScope) { - const prevEntry = perScope.usages.get(identifier.text); - if (prevEntry) { - perScope.usages.set(identifier.text, { usage, symbol, node: identifier }); - } + seenUsages.set(symbolId, usage); + if (lastUsage) { + // if we get here this means that we are trying to handle 'write' and 'read' was already processed + // walk scopes and update existing records. + for (const perScope of usagesPerScope) { + const prevEntry = perScope.usages.get(identifier.text); + if (prevEntry) { + perScope.usages.set(identifier.text, { usage, symbol, node: identifier }); } - return symbolId; - } - // find first declaration in this file - const decls = symbol.getDeclarations(); - const declInFile = decls && find(decls, d => d.getSourceFile() === sourceFile); - if (!declInFile) { - return undefined; } - if (rangeContainsStartEnd(enclosingTextRange, declInFile.getStart(), declInFile.end)) { - // declaration is located in range to be extracted - do nothing - return undefined; + return symbolId; + } + // find first declaration in this file + const decls = symbol.getDeclarations(); + const declInFile = decls && find(decls, d => d.getSourceFile() === sourceFile); + if (!declInFile) { + return undefined; + } + if (rangeContainsStartEnd(enclosingTextRange, declInFile.getStart(), declInFile.end)) { + // declaration is located in range to be extracted - do nothing + return undefined; + } + if (targetRange.facts & RangeFacts.IsGenerator && usage === Usage.Write) { + // this is write to a reference located outside of the target scope and range is extracted into generator + // currently this is unsupported scenario + const diag = createDiagnosticForNode(identifier, Messages.cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators); + for (const errors of functionErrorsPerScope) { + errors.push(diag); } - if (targetRange.facts & RangeFacts.IsGenerator && usage === Usage.Write) { - // this is write to a reference located outside of the target scope and range is extracted into generator - // currently this is unsupported scenario - const diag = createDiagnosticForNode(identifier, Messages.cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators); - for (const errors of functionErrorsPerScope) { - errors.push(diag); - } - for (const errors of constantErrorsPerScope) { - errors.push(diag); - } + for (const errors of constantErrorsPerScope) { + errors.push(diag); } - for (let i = 0; i < scopes.length; i++) { - const scope = scopes[i]; - const resolvedSymbol = checker.resolveName(symbol.name, scope, symbol.flags, /*excludeGlobals*/ false); - if (resolvedSymbol === symbol) { - continue; + } + for (let i = 0; i < scopes.length; i++) { + const scope = scopes[i]; + const resolvedSymbol = checker.resolveName(symbol.name, scope, symbol.flags, /*excludeGlobals*/ false); + if (resolvedSymbol === symbol) { + continue; + } + if (!substitutionsPerScope[i].has(symbolId)) { + const substitution = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.exportSymbol || symbol, scope, isTypeName); + if (substitution) { + substitutionsPerScope[i].set(symbolId, substitution); } - if (!substitutionsPerScope[i].has(symbolId)) { - const substitution = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.exportSymbol || symbol, scope, isTypeName); - if (substitution) { - substitutionsPerScope[i].set(symbolId, substitution); - } - else if (isTypeName) { - // If the symbol is a type parameter that won't be in scope, we'll pass it as a type argument - // so there's no problem. - if (!(symbol.flags & SymbolFlags.TypeParameter)) { - const diag = createDiagnosticForNode(identifier, Messages.typeWillNotBeVisibleInTheNewScope); - functionErrorsPerScope[i].push(diag); - constantErrorsPerScope[i].push(diag); - } - } - else { - usagesPerScope[i].usages.set(identifier.text, { usage, symbol, node: identifier }); + else if (isTypeName) { + // If the symbol is a type parameter that won't be in scope, we'll pass it as a type argument + // so there's no problem. + if (!(symbol.flags & SymbolFlags.TypeParameter)) { + const diag = createDiagnosticForNode(identifier, Messages.typeWillNotBeVisibleInTheNewScope); + functionErrorsPerScope[i].push(diag); + constantErrorsPerScope[i].push(diag); } } + else { + usagesPerScope[i].usages.set(identifier.text, { usage, symbol, node: identifier }); + } } - return symbolId; } + return symbolId; + } - function checkForUsedDeclarations(node: Node) { - // If this node is entirely within the original extraction range, we don't need to do anything. - if (node === targetRange.range || (isReadonlyArray(targetRange.range) && targetRange.range.indexOf(node as Statement) >= 0)) { - return; - } - - // Otherwise check and recurse. - const sym = isIdentifier(node) - ? getSymbolReferencedByIdentifier(node) - : checker.getSymbolAtLocation(node); - if (sym) { - const decl = find(visibleDeclarationsInExtractedRange, d => d.symbol === sym); - if (decl) { - if (isVariableDeclaration(decl)) { - const idString = decl.symbol.id!.toString(); - if (!exposedVariableSymbolSet.has(idString)) { - exposedVariableDeclarations.push(decl); - exposedVariableSymbolSet.set(idString, true); - } - } - else { - // CONSIDER: this includes binding elements, which we could - // expose in the same way as variables. - firstExposedNonVariableDeclaration = firstExposedNonVariableDeclaration || decl; + function checkForUsedDeclarations(node: Node) { + // If this node is entirely within the original extraction range, we don't need to do anything. + if (node === targetRange.range || (isReadonlyArray(targetRange.range) && targetRange.range.indexOf(node as Statement) >= 0)) { + return; + } + + // Otherwise check and recurse. + const sym = isIdentifier(node) + ? getSymbolReferencedByIdentifier(node) + : checker.getSymbolAtLocation(node); + if (sym) { + const decl = find(visibleDeclarationsInExtractedRange, d => d.symbol === sym); + if (decl) { + if (isVariableDeclaration(decl)) { + const idString = decl.symbol.id!.toString(); + if (!exposedVariableSymbolSet.has(idString)) { + exposedVariableDeclarations.push(decl); + exposedVariableSymbolSet.set(idString, true); } } + else { + // CONSIDER: this includes binding elements, which we could + // expose in the same way as variables. + firstExposedNonVariableDeclaration = firstExposedNonVariableDeclaration || decl; + } } - - forEachChild(node, checkForUsedDeclarations); - } - - /** - * Return the symbol referenced by an identifier (even if it declares a different symbol). - */ - function getSymbolReferencedByIdentifier(identifier: Identifier) { - // If the identifier is both a property name and its value, we're only interested in its value - // (since the name is a declaration and will be included in the extracted range). - return identifier.parent && isShorthandPropertyAssignment(identifier.parent) && identifier.parent.name === identifier - ? checker.getShorthandAssignmentValueSymbol(identifier.parent) - : checker.getSymbolAtLocation(identifier); - } - - function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol | undefined, scopeDecl: Node, isTypeNode: boolean): PropertyAccessExpression | EntityName | undefined { - if (!symbol) { - return undefined; - } - const decls = symbol.getDeclarations(); - if (decls && decls.some(d => d.parent === scopeDecl)) { - return factory.createIdentifier(symbol.name); - } - const prefix = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.parent, scopeDecl, isTypeNode); - if (prefix === undefined) { - return undefined; - } - return isTypeNode - ? factory.createQualifiedName(prefix as EntityName, factory.createIdentifier(symbol.name)) - : factory.createPropertyAccessExpression(prefix as Expression, symbol.name); } - } - function getExtractableParent(node: Node | undefined): Node | undefined { - return findAncestor(node, node => node.parent && isExtractableExpression(node) && !isBinaryExpression(node.parent)); + forEachChild(node, checkForUsedDeclarations); } /** - * Computes whether or not a node represents an expression in a position where it could - * be extracted. - * The isExpression() in utilities.ts returns some false positives we need to handle, - * such as `import x from 'y'` -- the 'y' is a StringLiteral but is *not* an expression - * in the sense of something that you could extract on + * Return the symbol referenced by an identifier (even if it declares a different symbol). */ - function isExtractableExpression(node: Node): boolean { - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.EnumMember: - return false; - } - - switch (node.kind) { - case SyntaxKind.StringLiteral: - return parent.kind !== SyntaxKind.ImportDeclaration && - parent.kind !== SyntaxKind.ImportSpecifier; - - case SyntaxKind.SpreadElement: - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.BindingElement: - return false; + function getSymbolReferencedByIdentifier(identifier: Identifier) { + // If the identifier is both a property name and its value, we're only interested in its value + // (since the name is a declaration and will be included in the extracted range). + return identifier.parent && isShorthandPropertyAssignment(identifier.parent) && identifier.parent.name === identifier + ? checker.getShorthandAssignmentValueSymbol(identifier.parent) + : checker.getSymbolAtLocation(identifier); + } - case SyntaxKind.Identifier: - return parent.kind !== SyntaxKind.BindingElement && - parent.kind !== SyntaxKind.ImportSpecifier && - parent.kind !== SyntaxKind.ExportSpecifier; + function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol | undefined, scopeDecl: Node, isTypeNode: boolean): PropertyAccessExpression | EntityName | undefined { + if (!symbol) { + return undefined; + } + const decls = symbol.getDeclarations(); + if (decls && decls.some(d => d.parent === scopeDecl)) { + return factory.createIdentifier(symbol.name); + } + const prefix = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.parent, scopeDecl, isTypeNode); + if (prefix === undefined) { + return undefined; } - return true; + return isTypeNode + ? factory.createQualifiedName(prefix as EntityName, factory.createIdentifier(symbol.name)) + : factory.createPropertyAccessExpression(prefix as Expression, symbol.name); } +} - function isBlockLike(node: Node): node is BlockLike { - switch (node.kind) { - case SyntaxKind.Block: - case SyntaxKind.SourceFile: - case SyntaxKind.ModuleBlock: - case SyntaxKind.CaseClause: - return true; - default: - return false; - } +function getExtractableParent(node: Node | undefined): Node | undefined { + return findAncestor(node, node => node.parent && isExtractableExpression(node) && !isBinaryExpression(node.parent)); +} + +/** + * Computes whether or not a node represents an expression in a position where it could + * be extracted. + * The isExpression() in utilities.ts returns some false positives we need to handle, + * such as `import x from 'y'` -- the 'y' is a StringLiteral but is *not* an expression + * in the sense of something that you could extract on + */ +function isExtractableExpression(node: Node): boolean { + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.EnumMember: + return false; } - function isInJSXContent(node: Node) { - return isStringLiteralJsxAttribute(node) || - (isJsxElement(node) || isJsxSelfClosingElement(node) || isJsxFragment(node)) && (isJsxElement(node.parent) || isJsxFragment(node.parent)); + switch (node.kind) { + case SyntaxKind.StringLiteral: + return parent.kind !== SyntaxKind.ImportDeclaration && + parent.kind !== SyntaxKind.ImportSpecifier; + + case SyntaxKind.SpreadElement: + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.BindingElement: + return false; + + case SyntaxKind.Identifier: + return parent.kind !== SyntaxKind.BindingElement && + parent.kind !== SyntaxKind.ImportSpecifier && + parent.kind !== SyntaxKind.ExportSpecifier; } + return true; +} - function isStringLiteralJsxAttribute(node: Node): node is StringLiteral { - return isStringLiteral(node) && node.parent && isJsxAttribute(node.parent); +function isBlockLike(node: Node): node is BlockLike { + switch (node.kind) { + case SyntaxKind.Block: + case SyntaxKind.SourceFile: + case SyntaxKind.ModuleBlock: + case SyntaxKind.CaseClause: + return true; + default: + return false; } } + +function isInJSXContent(node: Node) { + return isStringLiteralJsxAttribute(node) || + (isJsxElement(node) || isJsxSelfClosingElement(node) || isJsxFragment(node)) && (isJsxElement(node.parent) || isJsxFragment(node.parent)); +} + +function isStringLiteralJsxAttribute(node: Node): node is StringLiteral { + return isStringLiteral(node) && node.parent && isJsxAttribute(node.parent); +} diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 36bc6ad3dc48e..a026a1091455b 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -1,252 +1,263 @@ -/* @internal */ -namespace ts.refactor { - const refactorName = "Extract type"; - - const extractToTypeAliasAction = { - name: "Extract to type alias", - description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias), - kind: "refactor.extract.type", - }; - const extractToInterfaceAction = { - name: "Extract to interface", - description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface), - kind: "refactor.extract.interface", - }; - const extractToTypeDefAction = { - name: "Extract to typedef", - description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef), - kind: "refactor.extract.typedef" - }; - - registerRefactor(refactorName, { - kinds: [ - extractToTypeAliasAction.kind, - extractToInterfaceAction.kind, - extractToTypeDefAction.kind - ], - getAvailableActions: function getRefactorActionsToExtractType(context): readonly ApplicableRefactorInfo[] { - const info = getRangeToExtract(context, context.triggerReason === "invoked"); - if (!info) return emptyArray; - - if (!isRefactorErrorInfo(info)) { - return [{ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_type), - actions: info.isJS ? - [extractToTypeDefAction] : append([extractToTypeAliasAction], info.typeElements && extractToInterfaceAction) - }]; - } +import { + addRange, addToSeen, append, ApplicableRefactorInfo, cast, concatenate, createTextRangeFromSpan, Debug, Diagnostics, + EmitFlags, emptyArray, factory, findAncestor, forEach, forEachChild, getEffectiveConstraintOfTypeParameter, + getLineAndCharacterOfPosition, getLocaleSpecificMessage, getNameFromPropertyName, getRefactorContextSpan, + getRenameLocation, getTokenAtPosition, getUniqueName, ignoreSourceNewlines, isConditionalTypeNode, isFunctionLike, + isIdentifier, isInferTypeNode, isIntersectionTypeNode, isJSDocTypeExpression, isParenthesizedTypeNode, + isSourceFileJS, isStatement, isThisIdentifier, isThisTypeNode, isTupleTypeNode, isTypeLiteralNode, isTypeNode, + isTypeParameterDeclaration, isTypePredicateNode, isTypeQueryNode, isTypeReferenceNode, JSDocTag, JSDocTemplateTag, + Map, Node, nodeOverlapsWithStartEnd, pushIfUnique, rangeContainsStartEnd, RefactorContext, RefactorEditInfo, + setEmitFlags, setTextRange, skipTrivia, SourceFile, Statement, SymbolFlags, textChanges, TextRange, TypeChecker, + TypeElement, TypeNode, TypeParameterDeclaration, +} from "../_namespaces/ts"; +import { isRefactorErrorInfo, RefactorErrorInfo, registerRefactor } from "../_namespaces/ts.refactor"; - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_type), - actions: [ - { ...extractToTypeDefAction, notApplicableReason: info.error }, - { ...extractToTypeAliasAction, notApplicableReason: info.error }, - { ...extractToInterfaceAction, notApplicableReason: info.error }, - ] - }]; - } +const refactorName = "Extract type"; - return emptyArray; - }, - getEditsForAction: function getRefactorEditsToExtractType(context, actionName): RefactorEditInfo { - const { file, } = context; - const info = getRangeToExtract(context); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected to find a range to extract"); - - const name = getUniqueName("NewType", file); - const edits = textChanges.ChangeTracker.with(context, changes => { - switch (actionName) { - case extractToTypeAliasAction.name: - Debug.assert(!info.isJS, "Invalid actionName/JS combo"); - return doTypeAliasChange(changes, file, name, info); - case extractToTypeDefAction.name: - Debug.assert(info.isJS, "Invalid actionName/JS combo"); - return doTypedefChange(changes, file, name, info); - case extractToInterfaceAction.name: - Debug.assert(!info.isJS && !!info.typeElements, "Invalid actionName/JS combo"); - return doInterfaceChange(changes, file, name, info as InterfaceInfo); - default: - Debug.fail("Unexpected action name"); - } - }); +const extractToTypeAliasAction = { + name: "Extract to type alias", + description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias), + kind: "refactor.extract.type", +}; +const extractToInterfaceAction = { + name: "Extract to interface", + description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface), + kind: "refactor.extract.interface", +}; +const extractToTypeDefAction = { + name: "Extract to typedef", + description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef), + kind: "refactor.extract.typedef" +}; + +registerRefactor(refactorName, { + kinds: [ + extractToTypeAliasAction.kind, + extractToInterfaceAction.kind, + extractToTypeDefAction.kind + ], + getAvailableActions: function getRefactorActionsToExtractType(context): readonly ApplicableRefactorInfo[] { + const info = getRangeToExtract(context, context.triggerReason === "invoked"); + if (!info) return emptyArray; - const renameFilename = file.fileName; - const renameLocation = getRenameLocation(edits, renameFilename, name, /*preferLastLocation*/ false); - return { edits, renameFilename, renameLocation }; + if (!isRefactorErrorInfo(info)) { + return [{ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_type), + actions: info.isJS ? + [extractToTypeDefAction] : append([extractToTypeAliasAction], info.typeElements && extractToInterfaceAction) + }]; } - }); - interface TypeAliasInfo { - isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements?: readonly TypeElement[]; - } + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_type), + actions: [ + { ...extractToTypeDefAction, notApplicableReason: info.error }, + { ...extractToTypeAliasAction, notApplicableReason: info.error }, + { ...extractToInterfaceAction, notApplicableReason: info.error }, + ] + }]; + } - interface InterfaceInfo { - isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements: readonly TypeElement[]; + return emptyArray; + }, + getEditsForAction: function getRefactorEditsToExtractType(context, actionName): RefactorEditInfo { + const { file, } = context; + const info = getRangeToExtract(context); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected to find a range to extract"); + + const name = getUniqueName("NewType", file); + const edits = textChanges.ChangeTracker.with(context, changes => { + switch (actionName) { + case extractToTypeAliasAction.name: + Debug.assert(!info.isJS, "Invalid actionName/JS combo"); + return doTypeAliasChange(changes, file, name, info); + case extractToTypeDefAction.name: + Debug.assert(info.isJS, "Invalid actionName/JS combo"); + return doTypedefChange(changes, file, name, info); + case extractToInterfaceAction.name: + Debug.assert(!info.isJS && !!info.typeElements, "Invalid actionName/JS combo"); + return doInterfaceChange(changes, file, name, info as InterfaceInfo); + default: + Debug.fail("Unexpected action name"); + } + }); + + const renameFilename = file.fileName; + const renameLocation = getRenameLocation(edits, renameFilename, name, /*preferLastLocation*/ false); + return { edits, renameFilename, renameLocation }; } +}); - type ExtractInfo = TypeAliasInfo | InterfaceInfo; +interface TypeAliasInfo { + isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements?: readonly TypeElement[]; +} - function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): ExtractInfo | RefactorErrorInfo | undefined { - const { file, startPosition } = context; - const isJS = isSourceFileJS(file); - const current = getTokenAtPosition(file, startPosition); - const range = createTextRangeFromSpan(getRefactorContextSpan(context)); - const cursorRequest = range.pos === range.end && considerEmptySpans; +interface InterfaceInfo { + isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements: readonly TypeElement[]; +} - const selection = findAncestor(current, (node => node.parent && isTypeNode(node) && !rangeContainsSkipTrivia(range, node.parent, file) && - (cursorRequest || nodeOverlapsWithStartEnd(current, file, range.pos, range.end)))); - if (!selection || !isTypeNode(selection)) return { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) }; +type ExtractInfo = TypeAliasInfo | InterfaceInfo; - const checker = context.program.getTypeChecker(); - const firstStatement = Debug.checkDefined(findAncestor(selection, isStatement), "Should find a statement"); - const typeParameters = collectTypeParameters(checker, selection, firstStatement, file); - if (!typeParameters) return { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) }; +function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): ExtractInfo | RefactorErrorInfo | undefined { + const { file, startPosition } = context; + const isJS = isSourceFileJS(file); + const current = getTokenAtPosition(file, startPosition); + const range = createTextRangeFromSpan(getRefactorContextSpan(context)); + const cursorRequest = range.pos === range.end && considerEmptySpans; - const typeElements = flattenTypeLiteralNodeReference(checker, selection); - return { isJS, selection, firstStatement, typeParameters, typeElements }; - } + const selection = findAncestor(current, (node => node.parent && isTypeNode(node) && !rangeContainsSkipTrivia(range, node.parent, file) && + (cursorRequest || nodeOverlapsWithStartEnd(current, file, range.pos, range.end)))); + if (!selection || !isTypeNode(selection)) return { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) }; - function flattenTypeLiteralNodeReference(checker: TypeChecker, node: TypeNode | undefined): readonly TypeElement[] | undefined { - if (!node) return undefined; - if (isIntersectionTypeNode(node)) { - const result: TypeElement[] = []; - const seen = new Map(); - for (const type of node.types) { - const flattenedTypeMembers = flattenTypeLiteralNodeReference(checker, type); - if (!flattenedTypeMembers || !flattenedTypeMembers.every(type => type.name && addToSeen(seen, getNameFromPropertyName(type.name) as string))) { - return undefined; - } + const checker = context.program.getTypeChecker(); + const firstStatement = Debug.checkDefined(findAncestor(selection, isStatement), "Should find a statement"); + const typeParameters = collectTypeParameters(checker, selection, firstStatement, file); + if (!typeParameters) return { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) }; + + const typeElements = flattenTypeLiteralNodeReference(checker, selection); + return { isJS, selection, firstStatement, typeParameters, typeElements }; +} - addRange(result, flattenedTypeMembers); +function flattenTypeLiteralNodeReference(checker: TypeChecker, node: TypeNode | undefined): readonly TypeElement[] | undefined { + if (!node) return undefined; + if (isIntersectionTypeNode(node)) { + const result: TypeElement[] = []; + const seen = new Map(); + for (const type of node.types) { + const flattenedTypeMembers = flattenTypeLiteralNodeReference(checker, type); + if (!flattenedTypeMembers || !flattenedTypeMembers.every(type => type.name && addToSeen(seen, getNameFromPropertyName(type.name) as string))) { + return undefined; } - return result; - } - else if (isParenthesizedTypeNode(node)) { - return flattenTypeLiteralNodeReference(checker, node.type); - } - else if (isTypeLiteralNode(node)) { - return node.members; + + addRange(result, flattenedTypeMembers); } - return undefined; + return result; } - - function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean { - return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); + else if (isParenthesizedTypeNode(node)) { + return flattenTypeLiteralNodeReference(checker, node.type); } + else if (isTypeLiteralNode(node)) { + return node.members; + } + return undefined; +} + +function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean { + return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); +} + +function collectTypeParameters(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): TypeParameterDeclaration[] | undefined { + const result: TypeParameterDeclaration[] = []; + return visitor(selection) ? undefined : result; + + function visitor(node: Node): true | undefined { + if (isTypeReferenceNode(node)) { + if (isIdentifier(node.typeName)) { + const typeName = node.typeName; + const symbol = checker.resolveName(typeName.text, typeName, SymbolFlags.TypeParameter, /* excludeGlobals */ true); + for (const decl of symbol?.declarations || emptyArray) { + if (isTypeParameterDeclaration(decl) && decl.getSourceFile() === file) { + // skip extraction if the type node is in the range of the type parameter declaration. + // function foo(): void; + if (decl.name.escapedText === typeName.escapedText && rangeContainsSkipTrivia(decl, selection, file)) { + return true; + } - function collectTypeParameters(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): TypeParameterDeclaration[] | undefined { - const result: TypeParameterDeclaration[] = []; - return visitor(selection) ? undefined : result; - - function visitor(node: Node): true | undefined { - if (isTypeReferenceNode(node)) { - if (isIdentifier(node.typeName)) { - const typeName = node.typeName; - const symbol = checker.resolveName(typeName.text, typeName, SymbolFlags.TypeParameter, /* excludeGlobals */ true); - for (const decl of symbol?.declarations || emptyArray) { - if (isTypeParameterDeclaration(decl) && decl.getSourceFile() === file) { - // skip extraction if the type node is in the range of the type parameter declaration. - // function foo(): void; - if (decl.name.escapedText === typeName.escapedText && rangeContainsSkipTrivia(decl, selection, file)) { - return true; - } - - if (rangeContainsSkipTrivia(statement, decl, file) && !rangeContainsSkipTrivia(selection, decl, file)) { - pushIfUnique(result, decl); - break; - } + if (rangeContainsSkipTrivia(statement, decl, file) && !rangeContainsSkipTrivia(selection, decl, file)) { + pushIfUnique(result, decl); + break; } } } } - else if (isInferTypeNode(node)) { - const conditionalTypeNode = findAncestor(node, n => isConditionalTypeNode(n) && rangeContainsSkipTrivia(n.extendsType, node, file)); - if (!conditionalTypeNode || !rangeContainsSkipTrivia(selection, conditionalTypeNode, file)) { - return true; - } + } + else if (isInferTypeNode(node)) { + const conditionalTypeNode = findAncestor(node, n => isConditionalTypeNode(n) && rangeContainsSkipTrivia(n.extendsType, node, file)); + if (!conditionalTypeNode || !rangeContainsSkipTrivia(selection, conditionalTypeNode, file)) { + return true; } - else if ((isTypePredicateNode(node) || isThisTypeNode(node))) { - const functionLikeNode = findAncestor(node.parent, isFunctionLike); - if (functionLikeNode && functionLikeNode.type && rangeContainsSkipTrivia(functionLikeNode.type, node, file) && !rangeContainsSkipTrivia(selection, functionLikeNode, file)) { + } + else if ((isTypePredicateNode(node) || isThisTypeNode(node))) { + const functionLikeNode = findAncestor(node.parent, isFunctionLike); + if (functionLikeNode && functionLikeNode.type && rangeContainsSkipTrivia(functionLikeNode.type, node, file) && !rangeContainsSkipTrivia(selection, functionLikeNode, file)) { + return true; + } + } + else if (isTypeQueryNode(node)) { + if (isIdentifier(node.exprName)) { + const symbol = checker.resolveName(node.exprName.text, node.exprName, SymbolFlags.Value, /* excludeGlobals */ false); + if (symbol?.valueDeclaration && rangeContainsSkipTrivia(statement, symbol.valueDeclaration, file) && !rangeContainsSkipTrivia(selection, symbol.valueDeclaration, file)) { return true; } } - else if (isTypeQueryNode(node)) { - if (isIdentifier(node.exprName)) { - const symbol = checker.resolveName(node.exprName.text, node.exprName, SymbolFlags.Value, /* excludeGlobals */ false); - if (symbol?.valueDeclaration && rangeContainsSkipTrivia(statement, symbol.valueDeclaration, file) && !rangeContainsSkipTrivia(selection, symbol.valueDeclaration, file)) { - return true; - } - } - else { - if (isThisIdentifier(node.exprName.left) && !rangeContainsSkipTrivia(selection, node.parent, file)) { - return true; - } + else { + if (isThisIdentifier(node.exprName.left) && !rangeContainsSkipTrivia(selection, node.parent, file)) { + return true; } } + } - if (file && isTupleTypeNode(node) && (getLineAndCharacterOfPosition(file, node.pos).line === getLineAndCharacterOfPosition(file, node.end).line)) { - setEmitFlags(node, EmitFlags.SingleLine); - } - - return forEachChild(node, visitor); + if (file && isTupleTypeNode(node) && (getLineAndCharacterOfPosition(file, node.pos).line === getLineAndCharacterOfPosition(file, node.end).line)) { + setEmitFlags(node, EmitFlags.SingleLine); } + + return forEachChild(node, visitor); } +} - function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: TypeAliasInfo) { - const { firstStatement, selection, typeParameters } = info; +function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: TypeAliasInfo) { + const { firstStatement, selection, typeParameters } = info; - const newTypeNode = factory.createTypeAliasDeclaration( - /* modifiers */ undefined, - name, - typeParameters.map(id => factory.updateTypeParameterDeclaration(id, id.modifiers, id.name, id.constraint, /* defaultType */ undefined)), - selection - ); - changes.insertNodeBefore(file, firstStatement, ignoreSourceNewlines(newTypeNode), /* blankLineBetween */ true); - changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace }); - } + const newTypeNode = factory.createTypeAliasDeclaration( + /* modifiers */ undefined, + name, + typeParameters.map(id => factory.updateTypeParameterDeclaration(id, id.modifiers, id.name, id.constraint, /* defaultType */ undefined)), + selection + ); + changes.insertNodeBefore(file, firstStatement, ignoreSourceNewlines(newTypeNode), /* blankLineBetween */ true); + changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace }); +} - function doInterfaceChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: InterfaceInfo) { - const { firstStatement, selection, typeParameters, typeElements } = info; +function doInterfaceChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: InterfaceInfo) { + const { firstStatement, selection, typeParameters, typeElements } = info; - const newTypeNode = factory.createInterfaceDeclaration( - /* modifiers */ undefined, - name, - typeParameters, - /* heritageClauses */ undefined, - typeElements - ); - setTextRange(newTypeNode, typeElements[0]?.parent); - changes.insertNodeBefore(file, firstStatement, ignoreSourceNewlines(newTypeNode), /* blankLineBetween */ true); - changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace }); - } + const newTypeNode = factory.createInterfaceDeclaration( + /* modifiers */ undefined, + name, + typeParameters, + /* heritageClauses */ undefined, + typeElements + ); + setTextRange(newTypeNode, typeElements[0]?.parent); + changes.insertNodeBefore(file, firstStatement, ignoreSourceNewlines(newTypeNode), /* blankLineBetween */ true); + changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace }); +} - function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: ExtractInfo) { - const { firstStatement, selection, typeParameters } = info; - - setEmitFlags(selection, EmitFlags.NoComments | EmitFlags.NoNestedComments); - - const node = factory.createJSDocTypedefTag( - factory.createIdentifier("typedef"), - factory.createJSDocTypeExpression(selection), - factory.createIdentifier(name)); - - const templates: JSDocTemplateTag[] = []; - forEach(typeParameters, typeParameter => { - const constraint = getEffectiveConstraintOfTypeParameter(typeParameter); - const parameter = factory.createTypeParameterDeclaration(/*modifiers*/ undefined, typeParameter.name); - const template = factory.createJSDocTemplateTag( - factory.createIdentifier("template"), - constraint && cast(constraint, isJSDocTypeExpression), - [parameter] - ); - templates.push(template); - }); +function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: ExtractInfo) { + const { firstStatement, selection, typeParameters } = info; - changes.insertNodeBefore(file, firstStatement, factory.createJSDocComment(/* comment */ undefined, factory.createNodeArray(concatenate(templates, [node]))), /* blankLineBetween */ true); - changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined)))); - } + setEmitFlags(selection, EmitFlags.NoComments | EmitFlags.NoNestedComments); + + const node = factory.createJSDocTypedefTag( + factory.createIdentifier("typedef"), + factory.createJSDocTypeExpression(selection), + factory.createIdentifier(name)); + + const templates: JSDocTemplateTag[] = []; + forEach(typeParameters, typeParameter => { + const constraint = getEffectiveConstraintOfTypeParameter(typeParameter); + const parameter = factory.createTypeParameterDeclaration(/*modifiers*/ undefined, typeParameter.name); + const template = factory.createJSDocTemplateTag( + factory.createIdentifier("template"), + constraint && cast(constraint, isJSDocTypeExpression), + [parameter] + ); + templates.push(template); + }); + + changes.insertNodeBefore(file, firstStatement, factory.createJSDocComment(/* comment */ undefined, factory.createNodeArray(concatenate(templates, [node]))), /* blankLineBetween */ true); + changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined)))); } diff --git a/src/services/refactors/generateGetAccessorAndSetAccessor.ts b/src/services/refactors/generateGetAccessorAndSetAccessor.ts index 70f1da47dfc8e..d8d70a1ea3abf 100644 --- a/src/services/refactors/generateGetAccessorAndSetAccessor.ts +++ b/src/services/refactors/generateGetAccessorAndSetAccessor.ts @@ -1,51 +1,54 @@ -/* @internal */ -namespace ts.refactor.generateGetAccessorAndSetAccessor { - const actionName = "Generate 'get' and 'set' accessors"; - const actionDescription = Diagnostics.Generate_get_and_set_accessors.message; +import { + ApplicableRefactorInfo, codefix, Debug, Diagnostics, emptyArray, getRenameLocation, isIdentifier, isParameter, + RefactorContext, +} from "../_namespaces/ts"; +import { isRefactorErrorInfo, registerRefactor } from "../_namespaces/ts.refactor"; - const generateGetSetAction = { - name: actionName, - description: actionDescription, - kind: "refactor.rewrite.property.generateAccessors", - }; - registerRefactor(actionName, { - kinds: [generateGetSetAction.kind], - getEditsForAction: function getRefactorActionsToGenerateGetAndSetAccessors(context, actionName) { - if (!context.endPosition) return undefined; - const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition); - Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - const edits = codefix.generateAccessorFromProperty(context.file, context.program, context.startPosition, context.endPosition, context, actionName); - if (!edits) return undefined; +const actionName = "Generate 'get' and 'set' accessors"; +const actionDescription = Diagnostics.Generate_get_and_set_accessors.message; - const renameFilename = context.file.fileName; - const nameNeedRename = info.renameAccessor ? info.accessorName : info.fieldName; - const renameLocationOffset = isIdentifier(nameNeedRename) ? 0 : -1; - const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, nameNeedRename.text, /*preferLastLocation*/ isParameter(info.declaration)); +const generateGetSetAction = { + name: actionName, + description: actionDescription, + kind: "refactor.rewrite.property.generateAccessors", +}; +registerRefactor(actionName, { + kinds: [generateGetSetAction.kind], + getEditsForAction: function getRefactorActionsToGenerateGetAndSetAccessors(context, actionName) { + if (!context.endPosition) return undefined; + const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); + const edits = codefix.generateAccessorFromProperty(context.file, context.program, context.startPosition, context.endPosition, context, actionName); + if (!edits) return undefined; - return { renameFilename, renameLocation, edits }; - }, - getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - if (!context.endPosition) return emptyArray; - const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition, context.triggerReason === "invoked"); - if (!info) return emptyArray; + const renameFilename = context.file.fileName; + const nameNeedRename = info.renameAccessor ? info.accessorName : info.fieldName; + const renameLocationOffset = isIdentifier(nameNeedRename) ? 0 : -1; + const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, nameNeedRename.text, /*preferLastLocation*/ isParameter(info.declaration)); - if (!isRefactorErrorInfo(info)) { - return [{ - name: actionName, - description: actionDescription, - actions: [generateGetSetAction], - }]; - } + return { renameFilename, renameLocation, edits }; + }, + getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + if (!context.endPosition) return emptyArray; + const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition, context.triggerReason === "invoked"); + if (!info) return emptyArray; - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ - name: actionName, - description: actionDescription, - actions: [{ ...generateGetSetAction, notApplicableReason: info.error }], - }]; - } + if (!isRefactorErrorInfo(info)) { + return [{ + name: actionName, + description: actionDescription, + actions: [generateGetSetAction], + }]; + } - return emptyArray; + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: actionName, + description: actionDescription, + actions: [{ ...generateGetSetAction, notApplicableReason: info.error }], + }]; } - }); -} + + return emptyArray; + } +}); diff --git a/src/services/refactors/helpers.ts b/src/services/refactors/helpers.ts index 25fcc1a3f5bc1..acafb54f1e36a 100644 --- a/src/services/refactors/helpers.ts +++ b/src/services/refactors/helpers.ts @@ -1,25 +1,29 @@ -/* @internal */ -namespace ts.refactor { - /** - * Returned by refactor functions when some error message needs to be surfaced to users. - */ - export interface RefactorErrorInfo { - error: string; - } - /** - * Checks if some refactor info has refactor error info. - */ - export function isRefactorErrorInfo(info: unknown): info is RefactorErrorInfo { - return (info as RefactorErrorInfo).error !== undefined; - } +/** + * Returned by refactor functions when some error message needs to be surfaced to users. + * + * @internal + */ +export interface RefactorErrorInfo { + error: string; +} + +/** + * Checks if some refactor info has refactor error info. + * + * @internal + */ +export function isRefactorErrorInfo(info: unknown): info is RefactorErrorInfo { + return (info as RefactorErrorInfo).error !== undefined; +} - /** - * Checks if string "known" begins with string "requested". - * Used to match requested kinds with a known kind. - */ - export function refactorKindBeginsWith(known: string, requested: string | undefined): boolean { - if(!requested) return true; - return known.substr(0, requested.length) === requested; - } +/** + * Checks if string "known" begins with string "requested". + * Used to match requested kinds with a known kind. + * + * @internal + */ +export function refactorKindBeginsWith(known: string, requested: string | undefined): boolean { + if(!requested) return true; + return known.substr(0, requested.length) === requested; } diff --git a/src/services/refactors/inferFunctionReturnType.ts b/src/services/refactors/inferFunctionReturnType.ts index f2e4a8f66edc1..c81a08bf4b164 100644 --- a/src/services/refactors/inferFunctionReturnType.ts +++ b/src/services/refactors/inferFunctionReturnType.ts @@ -1,117 +1,124 @@ -/* @internal */ -namespace ts.refactor.inferFunctionReturnType { - const refactorName = "Infer function return type"; - const refactorDescription = Diagnostics.Infer_function_return_type.message; +import { + ApplicableRefactorInfo, ArrowFunction, Diagnostics, emptyArray, factory, findAncestor, findChildOfKind, first, + FunctionDeclaration, FunctionExpression, getLocaleSpecificMessage, getTokenAtPosition, isArrowFunction, isBlock, + isInJSFile, mapDefined, MethodDeclaration, Node, NodeBuilderFlags, RefactorContext, RefactorEditInfo, SourceFile, + SyntaxKind, textChanges, Type, TypeChecker, TypeNode, +} from "../_namespaces/ts"; +import { + isRefactorErrorInfo, RefactorErrorInfo, refactorKindBeginsWith, registerRefactor, +} from "../_namespaces/ts.refactor"; - const inferReturnTypeAction = { - name: refactorName, - description: refactorDescription, - kind: "refactor.rewrite.function.returnType" - }; - registerRefactor(refactorName, { - kinds: [inferReturnTypeAction.kind], - getEditsForAction: getRefactorEditsToInferReturnType, - getAvailableActions: getRefactorActionsToInferReturnType - }); +const refactorName = "Infer function return type"; +const refactorDescription = Diagnostics.Infer_function_return_type.message; - function getRefactorEditsToInferReturnType(context: RefactorContext): RefactorEditInfo | undefined { - const info = getInfo(context); - if (info && !isRefactorErrorInfo(info)) { - const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, t, info.declaration, info.returnTypeNode)); - return { renameFilename: undefined, renameLocation: undefined, edits }; - } - return undefined; +const inferReturnTypeAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.function.returnType" +}; +registerRefactor(refactorName, { + kinds: [inferReturnTypeAction.kind], + getEditsForAction: getRefactorEditsToInferReturnType, + getAvailableActions: getRefactorActionsToInferReturnType +}); + +function getRefactorEditsToInferReturnType(context: RefactorContext): RefactorEditInfo | undefined { + const info = getInfo(context); + if (info && !isRefactorErrorInfo(info)) { + const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, t, info.declaration, info.returnTypeNode)); + return { renameFilename: undefined, renameLocation: undefined, edits }; } + return undefined; +} - function getRefactorActionsToInferReturnType(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const info = getInfo(context); - if (!info) return emptyArray; - if (!isRefactorErrorInfo(info)) { - return [{ - name: refactorName, - description: refactorDescription, - actions: [inferReturnTypeAction] - }]; - } - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ - name: refactorName, - description: refactorDescription, - actions: [{ ...inferReturnTypeAction, notApplicableReason: info.error }] - }]; - } - return emptyArray; +function getRefactorActionsToInferReturnType(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const info = getInfo(context); + if (!info) return emptyArray; + if (!isRefactorErrorInfo(info)) { + return [{ + name: refactorName, + description: refactorDescription, + actions: [inferReturnTypeAction] + }]; } + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: refactorName, + description: refactorDescription, + actions: [{ ...inferReturnTypeAction, notApplicableReason: info.error }] + }]; + } + return emptyArray; +} - type ConvertibleDeclaration = - | FunctionDeclaration - | FunctionExpression - | ArrowFunction - | MethodDeclaration; +type ConvertibleDeclaration = + | FunctionDeclaration + | FunctionExpression + | ArrowFunction + | MethodDeclaration; - interface FunctionInfo { - declaration: ConvertibleDeclaration; - returnTypeNode: TypeNode; - } +interface FunctionInfo { + declaration: ConvertibleDeclaration; + returnTypeNode: TypeNode; +} - function doChange(sourceFile: SourceFile, changes: textChanges.ChangeTracker, declaration: ConvertibleDeclaration, typeNode: TypeNode) { - const closeParen = findChildOfKind(declaration, SyntaxKind.CloseParenToken, sourceFile); - const needParens = isArrowFunction(declaration) && closeParen === undefined; - const endNode = needParens ? first(declaration.parameters) : closeParen; - if (endNode) { - if (needParens) { - changes.insertNodeBefore(sourceFile, endNode, factory.createToken(SyntaxKind.OpenParenToken)); - changes.insertNodeAfter(sourceFile, endNode, factory.createToken(SyntaxKind.CloseParenToken)); - } - changes.insertNodeAt(sourceFile, endNode.end, typeNode, { prefix: ": " }); +function doChange(sourceFile: SourceFile, changes: textChanges.ChangeTracker, declaration: ConvertibleDeclaration, typeNode: TypeNode) { + const closeParen = findChildOfKind(declaration, SyntaxKind.CloseParenToken, sourceFile); + const needParens = isArrowFunction(declaration) && closeParen === undefined; + const endNode = needParens ? first(declaration.parameters) : closeParen; + if (endNode) { + if (needParens) { + changes.insertNodeBefore(sourceFile, endNode, factory.createToken(SyntaxKind.OpenParenToken)); + changes.insertNodeAfter(sourceFile, endNode, factory.createToken(SyntaxKind.CloseParenToken)); } + changes.insertNodeAt(sourceFile, endNode.end, typeNode, { prefix: ": " }); } +} - function getInfo(context: RefactorContext): FunctionInfo | RefactorErrorInfo | undefined { - if (isInJSFile(context.file) || !refactorKindBeginsWith(inferReturnTypeAction.kind, context.kind)) return; +function getInfo(context: RefactorContext): FunctionInfo | RefactorErrorInfo | undefined { + if (isInJSFile(context.file) || !refactorKindBeginsWith(inferReturnTypeAction.kind, context.kind)) return; - const token = getTokenAtPosition(context.file, context.startPosition); - const declaration = findAncestor(token, n => - isBlock(n) || n.parent && isArrowFunction(n.parent) && (n.kind === SyntaxKind.EqualsGreaterThanToken || n.parent.body === n) ? "quit" : - isConvertibleDeclaration(n)) as ConvertibleDeclaration | undefined; - if (!declaration || !declaration.body || declaration.type) { - return { error: getLocaleSpecificMessage(Diagnostics.Return_type_must_be_inferred_from_a_function) }; - } + const token = getTokenAtPosition(context.file, context.startPosition); + const declaration = findAncestor(token, n => + isBlock(n) || n.parent && isArrowFunction(n.parent) && (n.kind === SyntaxKind.EqualsGreaterThanToken || n.parent.body === n) ? "quit" : + isConvertibleDeclaration(n)) as ConvertibleDeclaration | undefined; + if (!declaration || !declaration.body || declaration.type) { + return { error: getLocaleSpecificMessage(Diagnostics.Return_type_must_be_inferred_from_a_function) }; + } - const typeChecker = context.program.getTypeChecker(); - const returnType = tryGetReturnType(typeChecker, declaration); - if (!returnType) { - return { error: getLocaleSpecificMessage(Diagnostics.Could_not_determine_function_return_type) }; - } + const typeChecker = context.program.getTypeChecker(); + const returnType = tryGetReturnType(typeChecker, declaration); + if (!returnType) { + return { error: getLocaleSpecificMessage(Diagnostics.Could_not_determine_function_return_type) }; + } - const returnTypeNode = typeChecker.typeToTypeNode(returnType, declaration, NodeBuilderFlags.NoTruncation); - if (returnTypeNode) { - return { declaration, returnTypeNode }; - } + const returnTypeNode = typeChecker.typeToTypeNode(returnType, declaration, NodeBuilderFlags.NoTruncation); + if (returnTypeNode) { + return { declaration, returnTypeNode }; } +} - function isConvertibleDeclaration(node: Node): node is ConvertibleDeclaration { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - return true; - default: - return false; - } +function isConvertibleDeclaration(node: Node): node is ConvertibleDeclaration { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + return true; + default: + return false; } +} - function tryGetReturnType(typeChecker: TypeChecker, node: ConvertibleDeclaration): Type | undefined { - if (typeChecker.isImplementationOfOverload(node)) { - const signatures = typeChecker.getTypeAtLocation(node).getCallSignatures(); - if (signatures.length > 1) { - return typeChecker.getUnionType(mapDefined(signatures, s => s.getReturnType())); - } - } - const signature = typeChecker.getSignatureFromDeclaration(node); - if (signature) { - return typeChecker.getReturnTypeOfSignature(signature); +function tryGetReturnType(typeChecker: TypeChecker, node: ConvertibleDeclaration): Type | undefined { + if (typeChecker.isImplementationOfOverload(node)) { + const signatures = typeChecker.getTypeAtLocation(node).getCallSignatures(); + if (signatures.length > 1) { + return typeChecker.getUnionType(mapDefined(signatures, s => s.getReturnType())); } } + const signature = typeChecker.getSignatureFromDeclaration(node); + if (signature) { + return typeChecker.getReturnTypeOfSignature(signature); + } } diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index 1b9c9e04c8c51..8963d7d7534bf 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -1,853 +1,875 @@ -/* @internal */ -namespace ts.refactor { - const refactorName = "Move to a new file"; - const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file); - - const moveToNewFileAction = { - name: refactorName, - description, - kind: "refactor.move.newFile", +import { + AnyImportOrRequireStatement, append, ApplicableRefactorInfo, AssignmentDeclarationKind, BinaryExpression, + BindingElement, BindingName, CallExpression, canHaveDecorators, canHaveModifiers, cast, ClassDeclaration, codefix, + combinePaths, concatenate, contains, copyEntries, createTextRangeFromSpan, Debug, Declaration, DeclarationStatement, + Diagnostics, emptyArray, ensurePathIsNonModuleName, EnumDeclaration, escapeLeadingUnderscores, Expression, + ExpressionStatement, extensionFromPath, ExternalModuleReference, factory, find, FindAllReferences, findIndex, + firstDefined, flatMap, forEachEntry, FunctionDeclaration, getAssignmentDeclarationKind, getBaseFileName, + GetCanonicalFileName, getDecorators, getDirectoryPath, getLocaleSpecificMessage, getModifiers, + getPropertySymbolFromBindingElement, getQuotePreference, getRangesWhere, getRefactorContextSpan, + getRelativePathFromFile, getSymbolId, getUniqueName, hasSyntacticModifier, hostGetCanonicalFileName, Identifier, + ImportDeclaration, ImportEqualsDeclaration, insertImports, InterfaceDeclaration, InternalSymbolName, + isArrayLiteralExpression, isBinaryExpression, isBindingElement, isDeclarationName, isExpressionStatement, + isExternalModuleReference, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isNamedDeclaration, + isObjectLiteralExpression, isOmittedExpression, isPrologueDirective, isPropertyAccessExpression, + isPropertyAssignment, isRequireCall, isSourceFile, isStringLiteral, isStringLiteralLike, isVariableDeclaration, + isVariableDeclarationList, isVariableStatement, LanguageServiceHost, last, length, makeImportIfNecessary, Map, + mapDefined, ModifierFlags, ModifierLike, ModuleDeclaration, NamedImportBindings, Node, NodeFlags, nodeSeenTracker, + normalizePath, ObjectBindingElementWithoutPropertyName, Program, PropertyAccessExpression, PropertyAssignment, + QuotePreference, rangeContainsRange, RefactorContext, RefactorEditInfo, removeFileExtension, RequireOrImportCall, + RequireVariableStatement, ScriptTarget, skipAlias, some, SourceFile, Statement, StringLiteralLike, Symbol, + SymbolFlags, symbolNameNoDefault, SyntaxKind, takeWhile, textChanges, TransformFlags, tryCast, TypeAliasDeclaration, + TypeChecker, TypeNode, UserPreferences, VariableDeclaration, VariableDeclarationList, VariableStatement, +} from "../_namespaces/ts"; +import { registerRefactor } from "../_namespaces/ts.refactor"; + +const refactorName = "Move to a new file"; +const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file); + +const moveToNewFileAction = { + name: refactorName, + description, + kind: "refactor.move.newFile", +}; +registerRefactor(refactorName, { + kinds: [moveToNewFileAction.kind], + getAvailableActions: function getRefactorActionsToMoveToNewFile(context): readonly ApplicableRefactorInfo[] { + const statements = getStatementsToMove(context); + if (context.preferences.allowTextChangesInNewFiles && statements) { + return [{ name: refactorName, description, actions: [moveToNewFileAction] }]; + } + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ name: refactorName, description, actions: + [{ ...moveToNewFileAction, notApplicableReason: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_statement_or_statements) }] + }]; + } + return emptyArray; + }, + getEditsForAction: function getRefactorEditsToMoveToNewFile(context, actionName): RefactorEditInfo { + Debug.assert(actionName === refactorName, "Wrong refactor invoked"); + const statements = Debug.checkDefined(getStatementsToMove(context)); + const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, statements, t, context.host, context.preferences)); + return { edits, renameFilename: undefined, renameLocation: undefined }; + } +}); + +interface RangeToMove { readonly toMove: readonly Statement[]; readonly afterLast: Statement | undefined; } +function getRangeToMove(context: RefactorContext): RangeToMove | undefined { + const { file } = context; + const range = createTextRangeFromSpan(getRefactorContextSpan(context)); + const { statements } = file; + + const startNodeIndex = findIndex(statements, s => s.end > range.pos); + if (startNodeIndex === -1) return undefined; + + const startStatement = statements[startNodeIndex]; + if (isNamedDeclaration(startStatement) && startStatement.name && rangeContainsRange(startStatement.name, range)) { + return { toMove: [statements[startNodeIndex]], afterLast: statements[startNodeIndex + 1] }; + } + + // Can't only partially include the start node or be partially into the next node + if (range.pos > startStatement.getStart(file)) return undefined; + const afterEndNodeIndex = findIndex(statements, s => s.end > range.end, startNodeIndex); + // Can't be partially into the next node + if (afterEndNodeIndex !== -1 && (afterEndNodeIndex === 0 || statements[afterEndNodeIndex].getStart(file) < range.end)) return undefined; + + return { + toMove: statements.slice(startNodeIndex, afterEndNodeIndex === -1 ? statements.length : afterEndNodeIndex), + afterLast: afterEndNodeIndex === -1 ? undefined : statements[afterEndNodeIndex], }; - registerRefactor(refactorName, { - kinds: [moveToNewFileAction.kind], - getAvailableActions: function getRefactorActionsToMoveToNewFile(context): readonly ApplicableRefactorInfo[] { - const statements = getStatementsToMove(context); - if (context.preferences.allowTextChangesInNewFiles && statements) { - return [{ name: refactorName, description, actions: [moveToNewFileAction] }]; - } - if (context.preferences.provideRefactorNotApplicableReason) { - return [{ name: refactorName, description, actions: - [{ ...moveToNewFileAction, notApplicableReason: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_statement_or_statements) }] - }]; - } - return emptyArray; - }, - getEditsForAction: function getRefactorEditsToMoveToNewFile(context, actionName): RefactorEditInfo { - Debug.assert(actionName === refactorName, "Wrong refactor invoked"); - const statements = Debug.checkDefined(getStatementsToMove(context)); - const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, statements, t, context.host, context.preferences)); - return { edits, renameFilename: undefined, renameLocation: undefined }; - } - }); - - interface RangeToMove { readonly toMove: readonly Statement[]; readonly afterLast: Statement | undefined; } - function getRangeToMove(context: RefactorContext): RangeToMove | undefined { - const { file } = context; - const range = createTextRangeFromSpan(getRefactorContextSpan(context)); - const { statements } = file; - - const startNodeIndex = findIndex(statements, s => s.end > range.pos); - if (startNodeIndex === -1) return undefined; - - const startStatement = statements[startNodeIndex]; - if (isNamedDeclaration(startStatement) && startStatement.name && rangeContainsRange(startStatement.name, range)) { - return { toMove: [statements[startNodeIndex]], afterLast: statements[startNodeIndex + 1] }; - } +} - // Can't only partially include the start node or be partially into the next node - if (range.pos > startStatement.getStart(file)) return undefined; - const afterEndNodeIndex = findIndex(statements, s => s.end > range.end, startNodeIndex); - // Can't be partially into the next node - if (afterEndNodeIndex !== -1 && (afterEndNodeIndex === 0 || statements[afterEndNodeIndex].getStart(file) < range.end)) return undefined; +function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost, preferences: UserPreferences): void { + const checker = program.getTypeChecker(); + const usage = getUsageInfo(oldFile, toMove.all, checker); - return { - toMove: statements.slice(startNodeIndex, afterEndNodeIndex === -1 ? statements.length : afterEndNodeIndex), - afterLast: afterEndNodeIndex === -1 ? undefined : statements[afterEndNodeIndex], - }; - } + const currentDirectory = getDirectoryPath(oldFile.fileName); + const extension = extensionFromPath(oldFile.fileName); + const newModuleName = makeUniqueModuleName(getNewModuleName(usage.oldFileImportsFromNewFile, usage.movedSymbols), extension, currentDirectory, host); + const newFileNameWithExtension = newModuleName + extension; - function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost, preferences: UserPreferences): void { - const checker = program.getTypeChecker(); - const usage = getUsageInfo(oldFile, toMove.all, checker); + // If previous file was global, this is easy. + changes.createNewFile(oldFile, combinePaths(currentDirectory, newFileNameWithExtension), getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, newModuleName, preferences)); - const currentDirectory = getDirectoryPath(oldFile.fileName); - const extension = extensionFromPath(oldFile.fileName); - const newModuleName = makeUniqueModuleName(getNewModuleName(usage.oldFileImportsFromNewFile, usage.movedSymbols), extension, currentDirectory, host); - const newFileNameWithExtension = newModuleName + extension; + addNewFileToTsconfig(program, changes, oldFile.fileName, newFileNameWithExtension, hostGetCanonicalFileName(host)); +} - // If previous file was global, this is easy. - changes.createNewFile(oldFile, combinePaths(currentDirectory, newFileNameWithExtension), getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, newModuleName, preferences)); +interface StatementRange { + readonly first: Statement; + readonly afterLast: Statement | undefined; +} +interface ToMove { + readonly all: readonly Statement[]; + readonly ranges: readonly StatementRange[]; +} - addNewFileToTsconfig(program, changes, oldFile.fileName, newFileNameWithExtension, hostGetCanonicalFileName(host)); - } +function getStatementsToMove(context: RefactorContext): ToMove | undefined { + const rangeToMove = getRangeToMove(context); + if (rangeToMove === undefined) return undefined; + const all: Statement[] = []; + const ranges: StatementRange[] = []; + const { toMove, afterLast } = rangeToMove; + getRangesWhere(toMove, isAllowedStatementToMove, (start, afterEndIndex) => { + for (let i = start; i < afterEndIndex; i++) all.push(toMove[i]); + ranges.push({ first: toMove[start], afterLast }); + }); + return all.length === 0 ? undefined : { all, ranges }; +} - interface StatementRange { - readonly first: Statement; - readonly afterLast: Statement | undefined; - } - interface ToMove { - readonly all: readonly Statement[]; - readonly ranges: readonly StatementRange[]; - } +function isAllowedStatementToMove(statement: Statement): boolean { + // Filters imports and prologue directives out of the range of statements to move. + // Imports will be copied to the new file anyway, and may still be needed in the old file. + // Prologue directives will be copied to the new file and should be left in the old file. + return !isPureImport(statement) && !isPrologueDirective(statement); +} - function getStatementsToMove(context: RefactorContext): ToMove | undefined { - const rangeToMove = getRangeToMove(context); - if (rangeToMove === undefined) return undefined; - const all: Statement[] = []; - const ranges: StatementRange[] = []; - const { toMove, afterLast } = rangeToMove; - getRangesWhere(toMove, isAllowedStatementToMove, (start, afterEndIndex) => { - for (let i = start; i < afterEndIndex; i++) all.push(toMove[i]); - ranges.push({ first: toMove[start], afterLast }); - }); - return all.length === 0 ? undefined : { all, ranges }; - } - - function isAllowedStatementToMove(statement: Statement): boolean { - // Filters imports and prologue directives out of the range of statements to move. - // Imports will be copied to the new file anyway, and may still be needed in the old file. - // Prologue directives will be copied to the new file and should be left in the old file. - return !isPureImport(statement) && !isPrologueDirective(statement); - } - - function isPureImport(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return true; - case SyntaxKind.ImportEqualsDeclaration: - return !hasSyntacticModifier(node, ModifierFlags.Export); - case SyntaxKind.VariableStatement: - return (node as VariableStatement).declarationList.declarations.every(d => !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ true)); - default: - return false; - } +function isPureImport(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return true; + case SyntaxKind.ImportEqualsDeclaration: + return !hasSyntacticModifier(node, ModifierFlags.Export); + case SyntaxKind.VariableStatement: + return (node as VariableStatement).declarationList.declarations.every(d => !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ true)); + default: + return false; } +} - function addNewFileToTsconfig(program: Program, changes: textChanges.ChangeTracker, oldFileName: string, newFileNameWithExtension: string, getCanonicalFileName: GetCanonicalFileName): void { - const cfg = program.getCompilerOptions().configFile; - if (!cfg) return; +function addNewFileToTsconfig(program: Program, changes: textChanges.ChangeTracker, oldFileName: string, newFileNameWithExtension: string, getCanonicalFileName: GetCanonicalFileName): void { + const cfg = program.getCompilerOptions().configFile; + if (!cfg) return; - const newFileAbsolutePath = normalizePath(combinePaths(oldFileName, "..", newFileNameWithExtension)); - const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName); + const newFileAbsolutePath = normalizePath(combinePaths(oldFileName, "..", newFileNameWithExtension)); + const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName); - const cfgObject = cfg.statements[0] && tryCast(cfg.statements[0].expression, isObjectLiteralExpression); - const filesProp = cfgObject && find(cfgObject.properties, (prop): prop is PropertyAssignment => - isPropertyAssignment(prop) && isStringLiteral(prop.name) && prop.name.text === "files"); - if (filesProp && isArrayLiteralExpression(filesProp.initializer)) { - changes.insertNodeInListAfter(cfg, last(filesProp.initializer.elements), factory.createStringLiteral(newFilePath), filesProp.initializer.elements); - } + const cfgObject = cfg.statements[0] && tryCast(cfg.statements[0].expression, isObjectLiteralExpression); + const filesProp = cfgObject && find(cfgObject.properties, (prop): prop is PropertyAssignment => + isPropertyAssignment(prop) && isStringLiteral(prop.name) && prop.name.text === "files"); + if (filesProp && isArrayLiteralExpression(filesProp.initializer)) { + changes.insertNodeInListAfter(cfg, last(filesProp.initializer.elements), factory.createStringLiteral(newFilePath), filesProp.initializer.elements); } +} - function getNewStatementsAndRemoveFromOldFile( - oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, newModuleName: string, preferences: UserPreferences, - ) { - const checker = program.getTypeChecker(); - const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective); - if (oldFile.externalModuleIndicator === undefined && oldFile.commonJsModuleIndicator === undefined && usage.oldImportsNeededByNewFile.size() === 0) { - deleteMovedStatements(oldFile, toMove.ranges, changes); - return [...prologueDirectives, ...toMove.all]; - } +function getNewStatementsAndRemoveFromOldFile( + oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, newModuleName: string, preferences: UserPreferences, +) { + const checker = program.getTypeChecker(); + const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective); + if (oldFile.externalModuleIndicator === undefined && oldFile.commonJsModuleIndicator === undefined && usage.oldImportsNeededByNewFile.size() === 0) { + deleteMovedStatements(oldFile, toMove.ranges, changes); + return [...prologueDirectives, ...toMove.all]; + } - const useEsModuleSyntax = !!oldFile.externalModuleIndicator; - const quotePreference = getQuotePreference(oldFile, preferences); - const importsFromNewFile = createOldFileImportsFromNewFile(usage.oldFileImportsFromNewFile, newModuleName, useEsModuleSyntax, quotePreference); - if (importsFromNewFile) { - insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true); - } + const useEsModuleSyntax = !!oldFile.externalModuleIndicator; + const quotePreference = getQuotePreference(oldFile, preferences); + const importsFromNewFile = createOldFileImportsFromNewFile(usage.oldFileImportsFromNewFile, newModuleName, useEsModuleSyntax, quotePreference); + if (importsFromNewFile) { + insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true); + } - deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker); - deleteMovedStatements(oldFile, toMove.ranges, changes); - updateImportsInOtherFiles(changes, program, oldFile, usage.movedSymbols, newModuleName); - - const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, useEsModuleSyntax, quotePreference); - const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEsModuleSyntax); - if (imports.length && body.length) { - return [ - ...prologueDirectives, - ...imports, - SyntaxKind.NewLineTrivia as const, - ...body - ]; - } + deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker); + deleteMovedStatements(oldFile, toMove.ranges, changes); + updateImportsInOtherFiles(changes, program, oldFile, usage.movedSymbols, newModuleName); + const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, useEsModuleSyntax, quotePreference); + const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEsModuleSyntax); + if (imports.length && body.length) { return [ ...prologueDirectives, ...imports, - ...body, + SyntaxKind.NewLineTrivia as const, + ...body ]; } - function deleteMovedStatements(sourceFile: SourceFile, moved: readonly StatementRange[], changes: textChanges.ChangeTracker) { - for (const { first, afterLast } of moved) { - changes.deleteNodeRangeExcludingEnd(sourceFile, first, afterLast); - } - } + return [ + ...prologueDirectives, + ...imports, + ...body, + ]; +} - function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[], changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) { - for (const statement of oldFile.statements) { - if (contains(toMove, statement)) continue; - forEachImportInStatement(statement, i => deleteUnusedImports(oldFile, i, changes, name => toDelete.has(checker.getSymbolAtLocation(name)!))); - } +function deleteMovedStatements(sourceFile: SourceFile, moved: readonly StatementRange[], changes: textChanges.ChangeTracker) { + for (const { first, afterLast } of moved) { + changes.deleteNodeRangeExcludingEnd(sourceFile, first, afterLast); } +} - function updateImportsInOtherFiles(changes: textChanges.ChangeTracker, program: Program, oldFile: SourceFile, movedSymbols: ReadonlySymbolSet, newModuleName: string): void { - const checker = program.getTypeChecker(); - for (const sourceFile of program.getSourceFiles()) { - if (sourceFile === oldFile) continue; - for (const statement of sourceFile.statements) { - forEachImportInStatement(statement, importNode => { - if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return; - - const shouldMove = (name: Identifier): boolean => { - const symbol = isBindingElement(name.parent) - ? getPropertySymbolFromBindingElement(checker, name.parent as ObjectBindingElementWithoutPropertyName) - : skipAlias(checker.getSymbolAtLocation(name)!, checker); // TODO: GH#18217 - return !!symbol && movedSymbols.has(symbol); - }; - deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file - const newModuleSpecifier = combinePaths(getDirectoryPath(moduleSpecifierFromImport(importNode).text), newModuleName); - const newImportDeclaration = filterImport(importNode, factory.createStringLiteral(newModuleSpecifier), shouldMove); - if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration); - - const ns = getNamespaceLikeImport(importNode); - if (ns) updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode); - }); - } - } +function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[], changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) { + for (const statement of oldFile.statements) { + if (contains(toMove, statement)) continue; + forEachImportInStatement(statement, i => deleteUnusedImports(oldFile, i, changes, name => toDelete.has(checker.getSymbolAtLocation(name)!))); } +} - function getNamespaceLikeImport(node: SupportedImport): Identifier | undefined { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? - node.importClause.namedBindings.name : undefined; - case SyntaxKind.ImportEqualsDeclaration: - return node.name; - case SyntaxKind.VariableDeclaration: - return tryCast(node.name, isIdentifier); - default: - return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); +function updateImportsInOtherFiles(changes: textChanges.ChangeTracker, program: Program, oldFile: SourceFile, movedSymbols: ReadonlySymbolSet, newModuleName: string): void { + const checker = program.getTypeChecker(); + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile === oldFile) continue; + for (const statement of sourceFile.statements) { + forEachImportInStatement(statement, importNode => { + if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return; + + const shouldMove = (name: Identifier): boolean => { + const symbol = isBindingElement(name.parent) + ? getPropertySymbolFromBindingElement(checker, name.parent as ObjectBindingElementWithoutPropertyName) + : skipAlias(checker.getSymbolAtLocation(name)!, checker); // TODO: GH#18217 + return !!symbol && movedSymbols.has(symbol); + }; + deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file + const newModuleSpecifier = combinePaths(getDirectoryPath(moduleSpecifierFromImport(importNode).text), newModuleName); + const newImportDeclaration = filterImport(importNode, factory.createStringLiteral(newModuleSpecifier), shouldMove); + if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration); + + const ns = getNamespaceLikeImport(importNode); + if (ns) updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode); + }); } } +} - function updateNamespaceLikeImport( - changes: textChanges.ChangeTracker, - sourceFile: SourceFile, - checker: TypeChecker, - movedSymbols: ReadonlySymbolSet, - newModuleName: string, - newModuleSpecifier: string, - oldImportId: Identifier, - oldImportNode: SupportedImport, - ): void { - const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleName, ScriptTarget.ESNext); - let needUniqueName = false; - const toChange: Identifier[] = []; - FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => { - if (!isPropertyAccessExpression(ref.parent)) return; - needUniqueName = needUniqueName || !!checker.resolveName(preferredNewNamespaceName, ref, SymbolFlags.All, /*excludeGlobals*/ true); - if (movedSymbols.has(checker.getSymbolAtLocation(ref.parent.name)!)) { - toChange.push(ref); - } - }); +function getNamespaceLikeImport(node: SupportedImport): Identifier | undefined { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? + node.importClause.namedBindings.name : undefined; + case SyntaxKind.ImportEqualsDeclaration: + return node.name; + case SyntaxKind.VariableDeclaration: + return tryCast(node.name, isIdentifier); + default: + return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); + } +} - if (toChange.length) { - const newNamespaceName = needUniqueName ? getUniqueName(preferredNewNamespaceName, sourceFile) : preferredNewNamespaceName; - for (const ref of toChange) { - changes.replaceNode(sourceFile, ref, factory.createIdentifier(newNamespaceName)); - } - changes.insertNodeAfter(sourceFile, oldImportNode, updateNamespaceLikeImportNode(oldImportNode, newModuleName, newModuleSpecifier)); +function updateNamespaceLikeImport( + changes: textChanges.ChangeTracker, + sourceFile: SourceFile, + checker: TypeChecker, + movedSymbols: ReadonlySymbolSet, + newModuleName: string, + newModuleSpecifier: string, + oldImportId: Identifier, + oldImportNode: SupportedImport, +): void { + const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleName, ScriptTarget.ESNext); + let needUniqueName = false; + const toChange: Identifier[] = []; + FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => { + if (!isPropertyAccessExpression(ref.parent)) return; + needUniqueName = needUniqueName || !!checker.resolveName(preferredNewNamespaceName, ref, SymbolFlags.All, /*excludeGlobals*/ true); + if (movedSymbols.has(checker.getSymbolAtLocation(ref.parent.name)!)) { + toChange.push(ref); } - } + }); - function updateNamespaceLikeImportNode(node: SupportedImport, newNamespaceName: string, newModuleSpecifier: string): Node { - const newNamespaceId = factory.createIdentifier(newNamespaceName); - const newModuleString = factory.createStringLiteral(newModuleSpecifier); - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - return factory.createImportDeclaration( - /*modifiers*/ undefined, - factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(newNamespaceId)), - newModuleString, - /*assertClause*/ undefined); - case SyntaxKind.ImportEqualsDeclaration: - return factory.createImportEqualsDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, newNamespaceId, factory.createExternalModuleReference(newModuleString)); - case SyntaxKind.VariableDeclaration: - return factory.createVariableDeclaration(newNamespaceId, /*exclamationToken*/ undefined, /*type*/ undefined, createRequireCall(newModuleString)); - default: - return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); + if (toChange.length) { + const newNamespaceName = needUniqueName ? getUniqueName(preferredNewNamespaceName, sourceFile) : preferredNewNamespaceName; + for (const ref of toChange) { + changes.replaceNode(sourceFile, ref, factory.createIdentifier(newNamespaceName)); } + changes.insertNodeAfter(sourceFile, oldImportNode, updateNamespaceLikeImportNode(oldImportNode, newModuleName, newModuleSpecifier)); } +} - function moduleSpecifierFromImport(i: SupportedImport): StringLiteralLike { - return (i.kind === SyntaxKind.ImportDeclaration ? i.moduleSpecifier - : i.kind === SyntaxKind.ImportEqualsDeclaration ? i.moduleReference.expression - : i.initializer.arguments[0]); +function updateNamespaceLikeImportNode(node: SupportedImport, newNamespaceName: string, newModuleSpecifier: string): Node { + const newNamespaceId = factory.createIdentifier(newNamespaceName); + const newModuleString = factory.createStringLiteral(newModuleSpecifier); + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(newNamespaceId)), + newModuleString, + /*assertClause*/ undefined); + case SyntaxKind.ImportEqualsDeclaration: + return factory.createImportEqualsDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, newNamespaceId, factory.createExternalModuleReference(newModuleString)); + case SyntaxKind.VariableDeclaration: + return factory.createVariableDeclaration(newNamespaceId, /*exclamationToken*/ undefined, /*type*/ undefined, createRequireCall(newModuleString)); + default: + return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`); } +} - function forEachImportInStatement(statement: Statement, cb: (importNode: SupportedImport) => void): void { - if (isImportDeclaration(statement)) { - if (isStringLiteral(statement.moduleSpecifier)) cb(statement as SupportedImport); - } - else if (isImportEqualsDeclaration(statement)) { - if (isExternalModuleReference(statement.moduleReference) && isStringLiteralLike(statement.moduleReference.expression)) { - cb(statement as SupportedImport); - } - } - else if (isVariableStatement(statement)) { - for (const decl of statement.declarationList.declarations) { - if (decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true)) { - cb(decl as SupportedImport); - } - } +function moduleSpecifierFromImport(i: SupportedImport): StringLiteralLike { + return (i.kind === SyntaxKind.ImportDeclaration ? i.moduleSpecifier + : i.kind === SyntaxKind.ImportEqualsDeclaration ? i.moduleReference.expression + : i.initializer.arguments[0]); +} + +function forEachImportInStatement(statement: Statement, cb: (importNode: SupportedImport) => void): void { + if (isImportDeclaration(statement)) { + if (isStringLiteral(statement.moduleSpecifier)) cb(statement as SupportedImport); + } + else if (isImportEqualsDeclaration(statement)) { + if (isExternalModuleReference(statement.moduleReference) && isStringLiteralLike(statement.moduleReference.expression)) { + cb(statement as SupportedImport); } } - - type SupportedImport = - | ImportDeclaration & { moduleSpecifier: StringLiteralLike } - | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteralLike } } - | VariableDeclaration & { initializer: RequireOrImportCall }; - type SupportedImportStatement = - | ImportDeclaration - | ImportEqualsDeclaration - | VariableStatement; - - function createOldFileImportsFromNewFile(newFileNeedExport: ReadonlySymbolSet, newFileNameWithExtension: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { - let defaultImport: Identifier | undefined; - const imports: string[] = []; - newFileNeedExport.forEach(symbol => { - if (symbol.escapedName === InternalSymbolName.Default) { - defaultImport = factory.createIdentifier(symbolNameNoDefault(symbol)!); // TODO: GH#18217 - } - else { - imports.push(symbol.name); + else if (isVariableStatement(statement)) { + for (const decl of statement.declarationList.declarations) { + if (decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true)) { + cb(decl as SupportedImport); } - }); - return makeImportOrRequire(defaultImport, imports, newFileNameWithExtension, useEs6Imports, quotePreference); + } } +} - function makeImportOrRequire(defaultImport: Identifier | undefined, imports: readonly string[], path: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { - path = ensurePathIsNonModuleName(path); - if (useEs6Imports) { - const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i))); - return makeImportIfNecessary(defaultImport, specifiers, path, quotePreference); +type SupportedImport = + | ImportDeclaration & { moduleSpecifier: StringLiteralLike } + | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteralLike } } + | VariableDeclaration & { initializer: RequireOrImportCall }; +type SupportedImportStatement = + | ImportDeclaration + | ImportEqualsDeclaration + | VariableStatement; + +function createOldFileImportsFromNewFile(newFileNeedExport: ReadonlySymbolSet, newFileNameWithExtension: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { + let defaultImport: Identifier | undefined; + const imports: string[] = []; + newFileNeedExport.forEach(symbol => { + if (symbol.escapedName === InternalSymbolName.Default) { + defaultImport = factory.createIdentifier(symbolNameNoDefault(symbol)!); // TODO: GH#18217 } else { - Debug.assert(!defaultImport, "No default import should exist"); // If there's a default export, it should have been an es6 module. - const bindingElements = imports.map(i => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, i)); - return bindingElements.length - ? makeVariableStatement(factory.createObjectBindingPattern(bindingElements), /*type*/ undefined, createRequireCall(factory.createStringLiteral(path))) as RequireVariableStatement - : undefined; + imports.push(symbol.name); } - } + }); + return makeImportOrRequire(defaultImport, imports, newFileNameWithExtension, useEs6Imports, quotePreference); +} - function makeVariableStatement(name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined, flags: NodeFlags = NodeFlags.Const) { - return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, initializer)], flags)); +function makeImportOrRequire(defaultImport: Identifier | undefined, imports: readonly string[], path: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined { + path = ensurePathIsNonModuleName(path); + if (useEs6Imports) { + const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i))); + return makeImportIfNecessary(defaultImport, specifiers, path, quotePreference); } - - function createRequireCall(moduleSpecifier: StringLiteralLike): CallExpression { - return factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [moduleSpecifier]); + else { + Debug.assert(!defaultImport, "No default import should exist"); // If there's a default export, it should have been an es6 module. + const bindingElements = imports.map(i => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, i)); + return bindingElements.length + ? makeVariableStatement(factory.createObjectBindingPattern(bindingElements), /*type*/ undefined, createRequireCall(factory.createStringLiteral(path))) as RequireVariableStatement + : undefined; } +} - function addExports(sourceFile: SourceFile, toMove: readonly Statement[], needExport: ReadonlySymbolSet, useEs6Exports: boolean): readonly Statement[] { - return flatMap(toMove, statement => { - if (isTopLevelDeclarationStatement(statement) && - !isExported(sourceFile, statement, useEs6Exports) && - forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(d.symbol)))) { - const exports = addExport(statement, useEs6Exports); - if (exports) return exports; - } - return statement; - }); - } +function makeVariableStatement(name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined, flags: NodeFlags = NodeFlags.Const) { + return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, initializer)], flags)); +} - function deleteUnusedImports(sourceFile: SourceFile, importDecl: SupportedImport, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void { - switch (importDecl.kind) { - case SyntaxKind.ImportDeclaration: - deleteUnusedImportsInDeclaration(sourceFile, importDecl, changes, isUnused); - break; - case SyntaxKind.ImportEqualsDeclaration: - if (isUnused(importDecl.name)) { - changes.delete(sourceFile, importDecl); - } - break; - case SyntaxKind.VariableDeclaration: - deleteUnusedImportsInVariableDeclaration(sourceFile, importDecl, changes, isUnused); - break; - default: - Debug.assertNever(importDecl, `Unexpected import decl kind ${(importDecl as SupportedImport).kind}`); +function createRequireCall(moduleSpecifier: StringLiteralLike): CallExpression { + return factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [moduleSpecifier]); +} + +function addExports(sourceFile: SourceFile, toMove: readonly Statement[], needExport: ReadonlySymbolSet, useEs6Exports: boolean): readonly Statement[] { + return flatMap(toMove, statement => { + if (isTopLevelDeclarationStatement(statement) && + !isExported(sourceFile, statement, useEs6Exports) && + forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(d.symbol)))) { + const exports = addExport(statement, useEs6Exports); + if (exports) return exports; } + return statement; + }); +} + +function deleteUnusedImports(sourceFile: SourceFile, importDecl: SupportedImport, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void { + switch (importDecl.kind) { + case SyntaxKind.ImportDeclaration: + deleteUnusedImportsInDeclaration(sourceFile, importDecl, changes, isUnused); + break; + case SyntaxKind.ImportEqualsDeclaration: + if (isUnused(importDecl.name)) { + changes.delete(sourceFile, importDecl); + } + break; + case SyntaxKind.VariableDeclaration: + deleteUnusedImportsInVariableDeclaration(sourceFile, importDecl, changes, isUnused); + break; + default: + Debug.assertNever(importDecl, `Unexpected import decl kind ${(importDecl as SupportedImport).kind}`); } - function deleteUnusedImportsInDeclaration(sourceFile: SourceFile, importDecl: ImportDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void { - if (!importDecl.importClause) return; - const { name, namedBindings } = importDecl.importClause; - const defaultUnused = !name || isUnused(name); - const namedBindingsUnused = !namedBindings || - (namedBindings.kind === SyntaxKind.NamespaceImport ? isUnused(namedBindings.name) : namedBindings.elements.length !== 0 && namedBindings.elements.every(e => isUnused(e.name))); - if (defaultUnused && namedBindingsUnused) { - changes.delete(sourceFile, importDecl); - } - else { - if (name && defaultUnused) { - changes.delete(sourceFile, name); +} +function deleteUnusedImportsInDeclaration(sourceFile: SourceFile, importDecl: ImportDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void { + if (!importDecl.importClause) return; + const { name, namedBindings } = importDecl.importClause; + const defaultUnused = !name || isUnused(name); + const namedBindingsUnused = !namedBindings || + (namedBindings.kind === SyntaxKind.NamespaceImport ? isUnused(namedBindings.name) : namedBindings.elements.length !== 0 && namedBindings.elements.every(e => isUnused(e.name))); + if (defaultUnused && namedBindingsUnused) { + changes.delete(sourceFile, importDecl); + } + else { + if (name && defaultUnused) { + changes.delete(sourceFile, name); + } + if (namedBindings) { + if (namedBindingsUnused) { + changes.replaceNode( + sourceFile, + importDecl.importClause, + factory.updateImportClause(importDecl.importClause, importDecl.importClause.isTypeOnly, name, /*namedBindings*/ undefined) + ); } - if (namedBindings) { - if (namedBindingsUnused) { - changes.replaceNode( - sourceFile, - importDecl.importClause, - factory.updateImportClause(importDecl.importClause, importDecl.importClause.isTypeOnly, name, /*namedBindings*/ undefined) - ); - } - else if (namedBindings.kind === SyntaxKind.NamedImports) { - for (const element of namedBindings.elements) { - if (isUnused(element.name)) changes.delete(sourceFile, element); - } + else if (namedBindings.kind === SyntaxKind.NamedImports) { + for (const element of namedBindings.elements) { + if (isUnused(element.name)) changes.delete(sourceFile, element); } } } } - function deleteUnusedImportsInVariableDeclaration(sourceFile: SourceFile, varDecl: VariableDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean) { - const { name } = varDecl; - switch (name.kind) { - case SyntaxKind.Identifier: - if (isUnused(name)) { - if (varDecl.initializer && isRequireCall(varDecl.initializer, /*requireStringLiteralLikeArgument*/ true)) { - changes.delete(sourceFile, - isVariableDeclarationList(varDecl.parent) && length(varDecl.parent.declarations) === 1 ? varDecl.parent.parent : varDecl); - } - else { - changes.delete(sourceFile, name); - } - } - break; - case SyntaxKind.ArrayBindingPattern: - break; - case SyntaxKind.ObjectBindingPattern: - if (name.elements.every(e => isIdentifier(e.name) && isUnused(e.name))) { +} +function deleteUnusedImportsInVariableDeclaration(sourceFile: SourceFile, varDecl: VariableDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean) { + const { name } = varDecl; + switch (name.kind) { + case SyntaxKind.Identifier: + if (isUnused(name)) { + if (varDecl.initializer && isRequireCall(varDecl.initializer, /*requireStringLiteralLikeArgument*/ true)) { changes.delete(sourceFile, - isVariableDeclarationList(varDecl.parent) && varDecl.parent.declarations.length === 1 ? varDecl.parent.parent : varDecl); + isVariableDeclarationList(varDecl.parent) && length(varDecl.parent.declarations) === 1 ? varDecl.parent.parent : varDecl); } else { - for (const element of name.elements) { - if (isIdentifier(element.name) && isUnused(element.name)) { - changes.delete(sourceFile, element.name); - } + changes.delete(sourceFile, name); + } + } + break; + case SyntaxKind.ArrayBindingPattern: + break; + case SyntaxKind.ObjectBindingPattern: + if (name.elements.every(e => isIdentifier(e.name) && isUnused(e.name))) { + changes.delete(sourceFile, + isVariableDeclarationList(varDecl.parent) && varDecl.parent.declarations.length === 1 ? varDecl.parent.parent : varDecl); + } + else { + for (const element of name.elements) { + if (isIdentifier(element.name) && isUnused(element.name)) { + changes.delete(sourceFile, element.name); } } - break; - } + } + break; } +} - function getNewFileImportsAndAddExportInOldFile( - oldFile: SourceFile, - importsToCopy: ReadonlySymbolSet, - newFileImportsFromOldFile: ReadonlySymbolSet, - changes: textChanges.ChangeTracker, - checker: TypeChecker, - useEsModuleSyntax: boolean, - quotePreference: QuotePreference, - ): readonly SupportedImportStatement[] { - const copiedOldImports: SupportedImportStatement[] = []; - for (const oldStatement of oldFile.statements) { - forEachImportInStatement(oldStatement, i => { - append(copiedOldImports, filterImport(i, moduleSpecifierFromImport(i), name => importsToCopy.has(checker.getSymbolAtLocation(name)!))); - }); +function getNewFileImportsAndAddExportInOldFile( + oldFile: SourceFile, + importsToCopy: ReadonlySymbolSet, + newFileImportsFromOldFile: ReadonlySymbolSet, + changes: textChanges.ChangeTracker, + checker: TypeChecker, + useEsModuleSyntax: boolean, + quotePreference: QuotePreference, +): readonly SupportedImportStatement[] { + const copiedOldImports: SupportedImportStatement[] = []; + for (const oldStatement of oldFile.statements) { + forEachImportInStatement(oldStatement, i => { + append(copiedOldImports, filterImport(i, moduleSpecifierFromImport(i), name => importsToCopy.has(checker.getSymbolAtLocation(name)!))); + }); + } + + // Also, import things used from the old file, and insert 'export' modifiers as necessary in the old file. + let oldFileDefault: Identifier | undefined; + const oldFileNamedImports: string[] = []; + const markSeenTop = nodeSeenTracker(); // Needed because multiple declarations may appear in `const x = 0, y = 1;`. + newFileImportsFromOldFile.forEach(symbol => { + if (!symbol.declarations) { + return; } + for (const decl of symbol.declarations) { + if (!isTopLevelDeclaration(decl)) continue; + const name = nameOfTopLevelDeclaration(decl); + if (!name) continue; - // Also, import things used from the old file, and insert 'export' modifiers as necessary in the old file. - let oldFileDefault: Identifier | undefined; - const oldFileNamedImports: string[] = []; - const markSeenTop = nodeSeenTracker(); // Needed because multiple declarations may appear in `const x = 0, y = 1;`. - newFileImportsFromOldFile.forEach(symbol => { - if (!symbol.declarations) { - return; + const top = getTopLevelDeclarationStatement(decl); + if (markSeenTop(top)) { + addExportToChanges(oldFile, top, name, changes, useEsModuleSyntax); } - for (const decl of symbol.declarations) { - if (!isTopLevelDeclaration(decl)) continue; - const name = nameOfTopLevelDeclaration(decl); - if (!name) continue; - - const top = getTopLevelDeclarationStatement(decl); - if (markSeenTop(top)) { - addExportToChanges(oldFile, top, name, changes, useEsModuleSyntax); - } - if (hasSyntacticModifier(decl, ModifierFlags.Default)) { - oldFileDefault = name; - } - else { - oldFileNamedImports.push(name.text); - } + if (hasSyntacticModifier(decl, ModifierFlags.Default)) { + oldFileDefault = name; } - }); + else { + oldFileNamedImports.push(name.text); + } + } + }); - append(copiedOldImports, makeImportOrRequire(oldFileDefault, oldFileNamedImports, removeFileExtension(getBaseFileName(oldFile.fileName)), useEsModuleSyntax, quotePreference)); - return copiedOldImports; - } + append(copiedOldImports, makeImportOrRequire(oldFileDefault, oldFileNamedImports, removeFileExtension(getBaseFileName(oldFile.fileName)), useEsModuleSyntax, quotePreference)); + return copiedOldImports; +} - function makeUniqueModuleName(moduleName: string, extension: string, inDirectory: string, host: LanguageServiceHost): string { - let newModuleName = moduleName; - for (let i = 1; ; i++) { - const name = combinePaths(inDirectory, newModuleName + extension); - if (!host.fileExists(name)) return newModuleName; - newModuleName = `${moduleName}.${i}`; - } +function makeUniqueModuleName(moduleName: string, extension: string, inDirectory: string, host: LanguageServiceHost): string { + let newModuleName = moduleName; + for (let i = 1; ; i++) { + const name = combinePaths(inDirectory, newModuleName + extension); + if (!host.fileExists(name)) return newModuleName; + newModuleName = `${moduleName}.${i}`; } +} - function getNewModuleName(importsFromNewFile: ReadonlySymbolSet, movedSymbols: ReadonlySymbolSet): string { - return importsFromNewFile.forEachEntry(symbolNameNoDefault) || movedSymbols.forEachEntry(symbolNameNoDefault) || "newFile"; - } +function getNewModuleName(importsFromNewFile: ReadonlySymbolSet, movedSymbols: ReadonlySymbolSet): string { + return importsFromNewFile.forEachEntry(symbolNameNoDefault) || movedSymbols.forEachEntry(symbolNameNoDefault) || "newFile"; +} - interface UsageInfo { - // Symbols whose declarations are moved from the old file to the new file. - readonly movedSymbols: ReadonlySymbolSet; +interface UsageInfo { + // Symbols whose declarations are moved from the old file to the new file. + readonly movedSymbols: ReadonlySymbolSet; - // Symbols declared in the old file that must be imported by the new file. (May not already be exported.) - readonly newFileImportsFromOldFile: ReadonlySymbolSet; - // Subset of movedSymbols that are still used elsewhere in the old file and must be imported back. - readonly oldFileImportsFromNewFile: ReadonlySymbolSet; + // Symbols declared in the old file that must be imported by the new file. (May not already be exported.) + readonly newFileImportsFromOldFile: ReadonlySymbolSet; + // Subset of movedSymbols that are still used elsewhere in the old file and must be imported back. + readonly oldFileImportsFromNewFile: ReadonlySymbolSet; - readonly oldImportsNeededByNewFile: ReadonlySymbolSet; - // Subset of oldImportsNeededByNewFile that are will no longer be used in the old file. - readonly unusedImportsFromOldFile: ReadonlySymbolSet; - } - function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker): UsageInfo { - const movedSymbols = new SymbolSet(); - const oldImportsNeededByNewFile = new SymbolSet(); - const newFileImportsFromOldFile = new SymbolSet(); + readonly oldImportsNeededByNewFile: ReadonlySymbolSet; + // Subset of oldImportsNeededByNewFile that are will no longer be used in the old file. + readonly unusedImportsFromOldFile: ReadonlySymbolSet; +} +function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker): UsageInfo { + const movedSymbols = new SymbolSet(); + const oldImportsNeededByNewFile = new SymbolSet(); + const newFileImportsFromOldFile = new SymbolSet(); - const containsJsx = find(toMove, statement => !!(statement.transformFlags & TransformFlags.ContainsJsx)); - const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx); - if (jsxNamespaceSymbol) { // Might not exist (e.g. in non-compiling code) - oldImportsNeededByNewFile.add(jsxNamespaceSymbol); - } + const containsJsx = find(toMove, statement => !!(statement.transformFlags & TransformFlags.ContainsJsx)); + const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx); + if (jsxNamespaceSymbol) { // Might not exist (e.g. in non-compiling code) + oldImportsNeededByNewFile.add(jsxNamespaceSymbol); + } - for (const statement of toMove) { - forEachTopLevelDeclaration(statement, decl => { - movedSymbols.add(Debug.checkDefined(isExpressionStatement(decl) ? checker.getSymbolAtLocation(decl.expression.left) : decl.symbol, "Need a symbol here")); - }); - } - for (const statement of toMove) { - forEachReference(statement, checker, symbol => { - if (!symbol.declarations) return; - for (const decl of symbol.declarations) { - if (isInImport(decl)) { - oldImportsNeededByNewFile.add(symbol); - } - else if (isTopLevelDeclaration(decl) && sourceFileOfTopLevelDeclaration(decl) === oldFile && !movedSymbols.has(symbol)) { - newFileImportsFromOldFile.add(symbol); - } + for (const statement of toMove) { + forEachTopLevelDeclaration(statement, decl => { + movedSymbols.add(Debug.checkDefined(isExpressionStatement(decl) ? checker.getSymbolAtLocation(decl.expression.left) : decl.symbol, "Need a symbol here")); + }); + } + for (const statement of toMove) { + forEachReference(statement, checker, symbol => { + if (!symbol.declarations) return; + for (const decl of symbol.declarations) { + if (isInImport(decl)) { + oldImportsNeededByNewFile.add(symbol); } - }); - } + else if (isTopLevelDeclaration(decl) && sourceFileOfTopLevelDeclaration(decl) === oldFile && !movedSymbols.has(symbol)) { + newFileImportsFromOldFile.add(symbol); + } + } + }); + } - const unusedImportsFromOldFile = oldImportsNeededByNewFile.clone(); + const unusedImportsFromOldFile = oldImportsNeededByNewFile.clone(); - const oldFileImportsFromNewFile = new SymbolSet(); - for (const statement of oldFile.statements) { - if (contains(toMove, statement)) continue; + const oldFileImportsFromNewFile = new SymbolSet(); + for (const statement of oldFile.statements) { + if (contains(toMove, statement)) continue; - // jsxNamespaceSymbol will only be set iff it is in oldImportsNeededByNewFile. - if (jsxNamespaceSymbol && !!(statement.transformFlags & TransformFlags.ContainsJsx)) { - unusedImportsFromOldFile.delete(jsxNamespaceSymbol); - } + // jsxNamespaceSymbol will only be set iff it is in oldImportsNeededByNewFile. + if (jsxNamespaceSymbol && !!(statement.transformFlags & TransformFlags.ContainsJsx)) { + unusedImportsFromOldFile.delete(jsxNamespaceSymbol); + } - forEachReference(statement, checker, symbol => { - if (movedSymbols.has(symbol)) oldFileImportsFromNewFile.add(symbol); - unusedImportsFromOldFile.delete(symbol); - }); + forEachReference(statement, checker, symbol => { + if (movedSymbols.has(symbol)) oldFileImportsFromNewFile.add(symbol); + unusedImportsFromOldFile.delete(symbol); + }); + } + + return { movedSymbols, newFileImportsFromOldFile, oldFileImportsFromNewFile, oldImportsNeededByNewFile, unusedImportsFromOldFile }; + + function getJsxNamespaceSymbol(containsJsx: Node | undefined) { + if (containsJsx === undefined) { + return undefined; } - return { movedSymbols, newFileImportsFromOldFile, oldFileImportsFromNewFile, oldImportsNeededByNewFile, unusedImportsFromOldFile }; + const jsxNamespace = checker.getJsxNamespace(containsJsx); - function getJsxNamespaceSymbol(containsJsx: Node | undefined) { - if (containsJsx === undefined) { - return undefined; - } + // Strictly speaking, this could resolve to a symbol other than the JSX namespace. + // This will produce erroneous output (probably, an incorrectly copied import) but + // is expected to be very rare and easily reversible. + const jsxNamespaceSymbol = checker.resolveName(jsxNamespace, containsJsx, SymbolFlags.Namespace, /*excludeGlobals*/ true); - const jsxNamespace = checker.getJsxNamespace(containsJsx); + return !!jsxNamespaceSymbol && some(jsxNamespaceSymbol.declarations, isInImport) + ? jsxNamespaceSymbol + : undefined; + } +} - // Strictly speaking, this could resolve to a symbol other than the JSX namespace. - // This will produce erroneous output (probably, an incorrectly copied import) but - // is expected to be very rare and easily reversible. - const jsxNamespaceSymbol = checker.resolveName(jsxNamespace, containsJsx, SymbolFlags.Namespace, /*excludeGlobals*/ true); +// Below should all be utilities + +function isInImport(decl: Declaration) { + switch (decl.kind) { + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + return true; + case SyntaxKind.VariableDeclaration: + return isVariableDeclarationInImport(decl as VariableDeclaration); + case SyntaxKind.BindingElement: + return isVariableDeclaration(decl.parent.parent) && isVariableDeclarationInImport(decl.parent.parent); + default: + return false; + } +} +function isVariableDeclarationInImport(decl: VariableDeclaration) { + return isSourceFile(decl.parent.parent.parent) && + !!decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true); +} - return !!jsxNamespaceSymbol && some(jsxNamespaceSymbol.declarations, isInImport) - ? jsxNamespaceSymbol +function filterImport(i: SupportedImport, moduleSpecifier: StringLiteralLike, keep: (name: Identifier) => boolean): SupportedImportStatement | undefined { + switch (i.kind) { + case SyntaxKind.ImportDeclaration: { + const clause = i.importClause; + if (!clause) return undefined; + const defaultImport = clause.name && keep(clause.name) ? clause.name : undefined; + const namedBindings = clause.namedBindings && filterNamedBindings(clause.namedBindings, keep); + return defaultImport || namedBindings + ? factory.createImportDeclaration(/*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, defaultImport, namedBindings), moduleSpecifier, /*assertClause*/ undefined) : undefined; } - } - - // Below should all be utilities - - function isInImport(decl: Declaration) { - switch (decl.kind) { - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - return true; - case SyntaxKind.VariableDeclaration: - return isVariableDeclarationInImport(decl as VariableDeclaration); - case SyntaxKind.BindingElement: - return isVariableDeclaration(decl.parent.parent) && isVariableDeclarationInImport(decl.parent.parent); - default: - return false; + case SyntaxKind.ImportEqualsDeclaration: + return keep(i.name) ? i : undefined; + case SyntaxKind.VariableDeclaration: { + const name = filterBindingName(i.name, keep); + return name ? makeVariableStatement(name, i.type, createRequireCall(moduleSpecifier), i.parent.flags) : undefined; } + default: + return Debug.assertNever(i, `Unexpected import kind ${(i as SupportedImport).kind}`); } - function isVariableDeclarationInImport(decl: VariableDeclaration) { - return isSourceFile(decl.parent.parent.parent) && - !!decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true); - } - - function filterImport(i: SupportedImport, moduleSpecifier: StringLiteralLike, keep: (name: Identifier) => boolean): SupportedImportStatement | undefined { - switch (i.kind) { - case SyntaxKind.ImportDeclaration: { - const clause = i.importClause; - if (!clause) return undefined; - const defaultImport = clause.name && keep(clause.name) ? clause.name : undefined; - const namedBindings = clause.namedBindings && filterNamedBindings(clause.namedBindings, keep); - return defaultImport || namedBindings - ? factory.createImportDeclaration(/*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, defaultImport, namedBindings), moduleSpecifier, /*assertClause*/ undefined) - : undefined; - } - case SyntaxKind.ImportEqualsDeclaration: - return keep(i.name) ? i : undefined; - case SyntaxKind.VariableDeclaration: { - const name = filterBindingName(i.name, keep); - return name ? makeVariableStatement(name, i.type, createRequireCall(moduleSpecifier), i.parent.flags) : undefined; - } - default: - return Debug.assertNever(i, `Unexpected import kind ${(i as SupportedImport).kind}`); +} +function filterNamedBindings(namedBindings: NamedImportBindings, keep: (name: Identifier) => boolean): NamedImportBindings | undefined { + if (namedBindings.kind === SyntaxKind.NamespaceImport) { + return keep(namedBindings.name) ? namedBindings : undefined; + } + else { + const newElements = namedBindings.elements.filter(e => keep(e.name)); + return newElements.length ? factory.createNamedImports(newElements) : undefined; + } +} +function filterBindingName(name: BindingName, keep: (name: Identifier) => boolean): BindingName | undefined { + switch (name.kind) { + case SyntaxKind.Identifier: + return keep(name) ? name : undefined; + case SyntaxKind.ArrayBindingPattern: + return name; + case SyntaxKind.ObjectBindingPattern: { + // We can't handle nested destructurings or property names well here, so just copy them all. + const newElements = name.elements.filter(prop => prop.propertyName || !isIdentifier(prop.name) || keep(prop.name)); + return newElements.length ? factory.createObjectBindingPattern(newElements) : undefined; } } - function filterNamedBindings(namedBindings: NamedImportBindings, keep: (name: Identifier) => boolean): NamedImportBindings | undefined { - if (namedBindings.kind === SyntaxKind.NamespaceImport) { - return keep(namedBindings.name) ? namedBindings : undefined; +} + +function forEachReference(node: Node, checker: TypeChecker, onReference: (s: Symbol) => void) { + node.forEachChild(function cb(node) { + if (isIdentifier(node) && !isDeclarationName(node)) { + const sym = checker.getSymbolAtLocation(node); + if (sym) onReference(sym); } else { - const newElements = namedBindings.elements.filter(e => keep(e.name)); - return newElements.length ? factory.createNamedImports(newElements) : undefined; + node.forEachChild(cb); } + }); +} + +interface ReadonlySymbolSet { + size(): number; + has(symbol: Symbol): boolean; + forEach(cb: (symbol: Symbol) => void): void; + forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined; +} + +class SymbolSet implements ReadonlySymbolSet { + private map = new Map(); + add(symbol: Symbol): void { + this.map.set(String(getSymbolId(symbol)), symbol); } - function filterBindingName(name: BindingName, keep: (name: Identifier) => boolean): BindingName | undefined { - switch (name.kind) { - case SyntaxKind.Identifier: - return keep(name) ? name : undefined; - case SyntaxKind.ArrayBindingPattern: - return name; - case SyntaxKind.ObjectBindingPattern: { - // We can't handle nested destructurings or property names well here, so just copy them all. - const newElements = name.elements.filter(prop => prop.propertyName || !isIdentifier(prop.name) || keep(prop.name)); - return newElements.length ? factory.createObjectBindingPattern(newElements) : undefined; - } - } + has(symbol: Symbol): boolean { + return this.map.has(String(getSymbolId(symbol))); } - - function forEachReference(node: Node, checker: TypeChecker, onReference: (s: Symbol) => void) { - node.forEachChild(function cb(node) { - if (isIdentifier(node) && !isDeclarationName(node)) { - const sym = checker.getSymbolAtLocation(node); - if (sym) onReference(sym); - } - else { - node.forEachChild(cb); - } - }); + delete(symbol: Symbol): void { + this.map.delete(String(getSymbolId(symbol))); } - - interface ReadonlySymbolSet { - size(): number; - has(symbol: Symbol): boolean; - forEach(cb: (symbol: Symbol) => void): void; - forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined; + forEach(cb: (symbol: Symbol) => void): void { + this.map.forEach(cb); } - - class SymbolSet implements ReadonlySymbolSet { - private map = new Map(); - add(symbol: Symbol): void { - this.map.set(String(getSymbolId(symbol)), symbol); - } - has(symbol: Symbol): boolean { - return this.map.has(String(getSymbolId(symbol))); - } - delete(symbol: Symbol): void { - this.map.delete(String(getSymbolId(symbol))); - } - forEach(cb: (symbol: Symbol) => void): void { - this.map.forEach(cb); - } - forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined { - return forEachEntry(this.map, cb); - } - clone(): SymbolSet { - const clone = new SymbolSet(); - copyEntries(this.map, clone.map); - return clone; - } - size() { - return this.map.size; - } + forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined { + return forEachEntry(this.map, cb); } + clone(): SymbolSet { + const clone = new SymbolSet(); + copyEntries(this.map, clone.map); + return clone; + } + size() { + return this.map.size; + } +} - type TopLevelExpressionStatement = ExpressionStatement & { expression: BinaryExpression & { left: PropertyAccessExpression } }; // 'exports.x = ...' - type NonVariableTopLevelDeclaration = - | FunctionDeclaration - | ClassDeclaration - | EnumDeclaration - | TypeAliasDeclaration - | InterfaceDeclaration - | ModuleDeclaration - | TopLevelExpressionStatement - | ImportEqualsDeclaration; - type TopLevelDeclarationStatement = NonVariableTopLevelDeclaration | VariableStatement; - interface TopLevelVariableDeclaration extends VariableDeclaration { parent: VariableDeclarationList & { parent: VariableStatement; }; } - type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration | BindingElement; - function isTopLevelDeclaration(node: Node): node is TopLevelDeclaration { - return isNonVariableTopLevelDeclaration(node) && isSourceFile(node.parent) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent); - } - - function sourceFileOfTopLevelDeclaration(node: TopLevelDeclaration): Node { - return isVariableDeclaration(node) ? node.parent.parent.parent : node.parent; - } - - function isTopLevelDeclarationStatement(node: Node): node is TopLevelDeclarationStatement { - Debug.assert(isSourceFile(node.parent), "Node parent should be a SourceFile"); - return isNonVariableTopLevelDeclaration(node) || isVariableStatement(node); - } - - function isNonVariableTopLevelDeclaration(node: Node): node is NonVariableTopLevelDeclaration { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return true; - default: - return false; - } +type TopLevelExpressionStatement = ExpressionStatement & { expression: BinaryExpression & { left: PropertyAccessExpression } }; // 'exports.x = ...' +type NonVariableTopLevelDeclaration = + | FunctionDeclaration + | ClassDeclaration + | EnumDeclaration + | TypeAliasDeclaration + | InterfaceDeclaration + | ModuleDeclaration + | TopLevelExpressionStatement + | ImportEqualsDeclaration; +type TopLevelDeclarationStatement = NonVariableTopLevelDeclaration | VariableStatement; +interface TopLevelVariableDeclaration extends VariableDeclaration { parent: VariableDeclarationList & { parent: VariableStatement; }; } +type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration | BindingElement; +function isTopLevelDeclaration(node: Node): node is TopLevelDeclaration { + return isNonVariableTopLevelDeclaration(node) && isSourceFile(node.parent) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent); +} + +function sourceFileOfTopLevelDeclaration(node: TopLevelDeclaration): Node { + return isVariableDeclaration(node) ? node.parent.parent.parent : node.parent; +} + +function isTopLevelDeclarationStatement(node: Node): node is TopLevelDeclarationStatement { + Debug.assert(isSourceFile(node.parent), "Node parent should be a SourceFile"); + return isNonVariableTopLevelDeclaration(node) || isVariableStatement(node); +} + +function isNonVariableTopLevelDeclaration(node: Node): node is NonVariableTopLevelDeclaration { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return true; + default: + return false; } +} - function forEachTopLevelDeclaration(statement: Statement, cb: (node: TopLevelDeclaration) => T): T | undefined { - switch (statement.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return cb(statement as FunctionDeclaration | ClassDeclaration | EnumDeclaration | ModuleDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ImportEqualsDeclaration); - - case SyntaxKind.VariableStatement: - return firstDefined((statement as VariableStatement).declarationList.declarations, decl => forEachTopLevelDeclarationInBindingName(decl.name, cb)); - - case SyntaxKind.ExpressionStatement: { - const { expression } = statement as ExpressionStatement; - return isBinaryExpression(expression) && getAssignmentDeclarationKind(expression) === AssignmentDeclarationKind.ExportsProperty - ? cb(statement as TopLevelExpressionStatement) - : undefined; - } +function forEachTopLevelDeclaration(statement: Statement, cb: (node: TopLevelDeclaration) => T): T | undefined { + switch (statement.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return cb(statement as FunctionDeclaration | ClassDeclaration | EnumDeclaration | ModuleDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ImportEqualsDeclaration); + + case SyntaxKind.VariableStatement: + return firstDefined((statement as VariableStatement).declarationList.declarations, decl => forEachTopLevelDeclarationInBindingName(decl.name, cb)); + + case SyntaxKind.ExpressionStatement: { + const { expression } = statement as ExpressionStatement; + return isBinaryExpression(expression) && getAssignmentDeclarationKind(expression) === AssignmentDeclarationKind.ExportsProperty + ? cb(statement as TopLevelExpressionStatement) + : undefined; } } - function forEachTopLevelDeclarationInBindingName(name: BindingName, cb: (node: TopLevelDeclaration) => T): T | undefined { - switch (name.kind) { - case SyntaxKind.Identifier: - return cb(cast(name.parent, (x): x is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(x) || isBindingElement(x))); - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ObjectBindingPattern: - return firstDefined(name.elements, em => isOmittedExpression(em) ? undefined : forEachTopLevelDeclarationInBindingName(em.name, cb)); - default: - return Debug.assertNever(name, `Unexpected name kind ${(name as BindingName).kind}`); - } +} +function forEachTopLevelDeclarationInBindingName(name: BindingName, cb: (node: TopLevelDeclaration) => T): T | undefined { + switch (name.kind) { + case SyntaxKind.Identifier: + return cb(cast(name.parent, (x): x is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(x) || isBindingElement(x))); + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ObjectBindingPattern: + return firstDefined(name.elements, em => isOmittedExpression(em) ? undefined : forEachTopLevelDeclarationInBindingName(em.name, cb)); + default: + return Debug.assertNever(name, `Unexpected name kind ${(name as BindingName).kind}`); } +} - function nameOfTopLevelDeclaration(d: TopLevelDeclaration): Identifier | undefined { - return isExpressionStatement(d) ? tryCast(d.expression.left.name, isIdentifier) : tryCast(d.name, isIdentifier); +function nameOfTopLevelDeclaration(d: TopLevelDeclaration): Identifier | undefined { + return isExpressionStatement(d) ? tryCast(d.expression.left.name, isIdentifier) : tryCast(d.name, isIdentifier); +} + +function getTopLevelDeclarationStatement(d: TopLevelDeclaration): TopLevelDeclarationStatement { + switch (d.kind) { + case SyntaxKind.VariableDeclaration: + return d.parent.parent; + case SyntaxKind.BindingElement: + return getTopLevelDeclarationStatement( + cast(d.parent.parent, (p): p is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(p) || isBindingElement(p))); + default: + return d; } +} - function getTopLevelDeclarationStatement(d: TopLevelDeclaration): TopLevelDeclarationStatement { - switch (d.kind) { - case SyntaxKind.VariableDeclaration: - return d.parent.parent; - case SyntaxKind.BindingElement: - return getTopLevelDeclarationStatement( - cast(d.parent.parent, (p): p is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(p) || isBindingElement(p))); - default: - return d; - } +function addExportToChanges(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, name: Identifier, changes: textChanges.ChangeTracker, useEs6Exports: boolean): void { + if (isExported(sourceFile, decl, useEs6Exports, name)) return; + if (useEs6Exports) { + if (!isExpressionStatement(decl)) changes.insertExportModifier(sourceFile, decl); + } + else { + const names = getNamesToExportInCommonJS(decl); + if (names.length !== 0) changes.insertNodesAfter(sourceFile, decl, names.map(createExportAssignment)); } +} - function addExportToChanges(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, name: Identifier, changes: textChanges.ChangeTracker, useEs6Exports: boolean): void { - if (isExported(sourceFile, decl, useEs6Exports, name)) return; - if (useEs6Exports) { - if (!isExpressionStatement(decl)) changes.insertExportModifier(sourceFile, decl); - } - else { - const names = getNamesToExportInCommonJS(decl); - if (names.length !== 0) changes.insertNodesAfter(sourceFile, decl, names.map(createExportAssignment)); - } +function isExported(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, useEs6Exports: boolean, name?: Identifier): boolean { + if (useEs6Exports) { + return !isExpressionStatement(decl) && hasSyntacticModifier(decl, ModifierFlags.Export) || !!(name && sourceFile.symbol.exports?.has(name.escapedText)); } + return !!sourceFile.symbol && !!sourceFile.symbol.exports && + getNamesToExportInCommonJS(decl).some(name => sourceFile.symbol.exports!.has(escapeLeadingUnderscores(name))); +} - function isExported(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, useEs6Exports: boolean, name?: Identifier): boolean { - if (useEs6Exports) { - return !isExpressionStatement(decl) && hasSyntacticModifier(decl, ModifierFlags.Export) || !!(name && sourceFile.symbol.exports?.has(name.escapedText)); - } - return !!sourceFile.symbol && !!sourceFile.symbol.exports && - getNamesToExportInCommonJS(decl).some(name => sourceFile.symbol.exports!.has(escapeLeadingUnderscores(name))); - } - - function addExport(decl: TopLevelDeclarationStatement, useEs6Exports: boolean): readonly Statement[] | undefined { - return useEs6Exports ? [addEs6Export(decl)] : addCommonjsExport(decl); - } - function addEs6Export(d: TopLevelDeclarationStatement): TopLevelDeclarationStatement { - const modifiers = canHaveModifiers(d) ? concatenate([factory.createModifier(SyntaxKind.ExportKeyword)], getModifiers(d)) : undefined; - switch (d.kind) { - case SyntaxKind.FunctionDeclaration: - return factory.updateFunctionDeclaration(d, modifiers, d.asteriskToken, d.name, d.typeParameters, d.parameters, d.type, d.body); - case SyntaxKind.ClassDeclaration: - const decorators = canHaveDecorators(d) ? getDecorators(d) : undefined; - return factory.updateClassDeclaration(d, concatenate(decorators, modifiers), d.name, d.typeParameters, d.heritageClauses, d.members); - case SyntaxKind.VariableStatement: - return factory.updateVariableStatement(d, modifiers, d.declarationList); - case SyntaxKind.ModuleDeclaration: - return factory.updateModuleDeclaration(d, modifiers, d.name, d.body); - case SyntaxKind.EnumDeclaration: - return factory.updateEnumDeclaration(d, modifiers, d.name, d.members); - case SyntaxKind.TypeAliasDeclaration: - return factory.updateTypeAliasDeclaration(d, modifiers, d.name, d.typeParameters, d.type); - case SyntaxKind.InterfaceDeclaration: - return factory.updateInterfaceDeclaration(d, modifiers, d.name, d.typeParameters, d.heritageClauses, d.members); - case SyntaxKind.ImportEqualsDeclaration: - return factory.updateImportEqualsDeclaration(d, modifiers, d.isTypeOnly, d.name, d.moduleReference); - case SyntaxKind.ExpressionStatement: - return Debug.fail(); // Shouldn't try to add 'export' keyword to `exports.x = ...` - default: - return Debug.assertNever(d, `Unexpected declaration kind ${(d as DeclarationStatement).kind}`); - } +function addExport(decl: TopLevelDeclarationStatement, useEs6Exports: boolean): readonly Statement[] | undefined { + return useEs6Exports ? [addEs6Export(decl)] : addCommonjsExport(decl); +} +function addEs6Export(d: TopLevelDeclarationStatement): TopLevelDeclarationStatement { + const modifiers = canHaveModifiers(d) ? concatenate([factory.createModifier(SyntaxKind.ExportKeyword)], getModifiers(d)) : undefined; + switch (d.kind) { + case SyntaxKind.FunctionDeclaration: + return factory.updateFunctionDeclaration(d, modifiers, d.asteriskToken, d.name, d.typeParameters, d.parameters, d.type, d.body); + case SyntaxKind.ClassDeclaration: + const decorators = canHaveDecorators(d) ? getDecorators(d) : undefined; + return factory.updateClassDeclaration(d, concatenate(decorators, modifiers), d.name, d.typeParameters, d.heritageClauses, d.members); + case SyntaxKind.VariableStatement: + return factory.updateVariableStatement(d, modifiers, d.declarationList); + case SyntaxKind.ModuleDeclaration: + return factory.updateModuleDeclaration(d, modifiers, d.name, d.body); + case SyntaxKind.EnumDeclaration: + return factory.updateEnumDeclaration(d, modifiers, d.name, d.members); + case SyntaxKind.TypeAliasDeclaration: + return factory.updateTypeAliasDeclaration(d, modifiers, d.name, d.typeParameters, d.type); + case SyntaxKind.InterfaceDeclaration: + return factory.updateInterfaceDeclaration(d, modifiers, d.name, d.typeParameters, d.heritageClauses, d.members); + case SyntaxKind.ImportEqualsDeclaration: + return factory.updateImportEqualsDeclaration(d, modifiers, d.isTypeOnly, d.name, d.moduleReference); + case SyntaxKind.ExpressionStatement: + return Debug.fail(); // Shouldn't try to add 'export' keyword to `exports.x = ...` + default: + return Debug.assertNever(d, `Unexpected declaration kind ${(d as DeclarationStatement).kind}`); } - function addCommonjsExport(decl: TopLevelDeclarationStatement): readonly Statement[] | undefined { - return [decl, ...getNamesToExportInCommonJS(decl).map(createExportAssignment)]; - } - function getNamesToExportInCommonJS(decl: TopLevelDeclarationStatement): readonly string[] { - switch (decl.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - return [decl.name!.text]; // TODO: GH#18217 - case SyntaxKind.VariableStatement: - return mapDefined(decl.declarationList.declarations, d => isIdentifier(d.name) ? d.name.text : undefined); - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - return emptyArray; - case SyntaxKind.ExpressionStatement: - return Debug.fail("Can't export an ExpressionStatement"); // Shouldn't try to add 'export' keyword to `exports.x = ...` - default: - return Debug.assertNever(decl, `Unexpected decl kind ${(decl as TopLevelDeclarationStatement).kind}`); - } +} +function addCommonjsExport(decl: TopLevelDeclarationStatement): readonly Statement[] | undefined { + return [decl, ...getNamesToExportInCommonJS(decl).map(createExportAssignment)]; +} +function getNamesToExportInCommonJS(decl: TopLevelDeclarationStatement): readonly string[] { + switch (decl.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + return [decl.name!.text]; // TODO: GH#18217 + case SyntaxKind.VariableStatement: + return mapDefined(decl.declarationList.declarations, d => isIdentifier(d.name) ? d.name.text : undefined); + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return emptyArray; + case SyntaxKind.ExpressionStatement: + return Debug.fail("Can't export an ExpressionStatement"); // Shouldn't try to add 'export' keyword to `exports.x = ...` + default: + return Debug.assertNever(decl, `Unexpected decl kind ${(decl as TopLevelDeclarationStatement).kind}`); } +} - /** Creates `exports.x = x;` */ - function createExportAssignment(name: string): Statement { - return factory.createExpressionStatement( - factory.createBinaryExpression( - factory.createPropertyAccessExpression(factory.createIdentifier("exports"), factory.createIdentifier(name)), - SyntaxKind.EqualsToken, - factory.createIdentifier(name))); - } +/** Creates `exports.x = x;` */ +function createExportAssignment(name: string): Statement { + return factory.createExpressionStatement( + factory.createBinaryExpression( + factory.createPropertyAccessExpression(factory.createIdentifier("exports"), factory.createIdentifier(name)), + SyntaxKind.EqualsToken, + factory.createIdentifier(name))); } diff --git a/src/services/rename.ts b/src/services/rename.ts index e2aa01b3110ee..7508c150d41b4 100644 --- a/src/services/rename.ts +++ b/src/services/rename.ts @@ -1,188 +1,199 @@ -/* @internal */ -namespace ts.Rename { - export function getRenameInfo(program: Program, sourceFile: SourceFile, position: number, preferences: UserPreferences): RenameInfo { - const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); - if (nodeIsEligibleForRename(node)) { - const renameInfo = getRenameInfoForNode(node, program.getTypeChecker(), sourceFile, program, preferences); - if (renameInfo) { - return renameInfo; - } +import { + compareStringsCaseSensitive, Comparison, createTextSpan, DiagnosticMessage, Diagnostics, endsWith, every, Extension, + fileExtensionIs, find, getAdjustedRenameLocation, getContextualTypeFromParentOrAncestorTypeNode, + getLocaleSpecificMessage, getPathComponents, getTextOfIdentifierOrLiteral, getTextOfNode, getTouchingPropertyName, + ImportSpecifier, isExternalModuleNameRelative, isIdentifier, isImportOrExportSpecifierName, isImportSpecifier, + isInsideNodeModules, isLabelName, isLiteralNameOfPropertyDeclarationOrIndexAccess, isSourceFile, + isStringLiteralLike, isStringOrNumericLiteralLike, Node, NumericLiteral, Path, Program, removeFileExtension, + RenameInfo, RenameInfoFailure, RenameInfoSuccess, ScriptElementKind, ScriptElementKindModifier, some, SourceFile, + StringLiteralLike, stripQuotes, Symbol, SymbolDisplay, SymbolFlags, SyntaxKind, tryGetImportFromModuleSpecifier, + tryRemoveSuffix, TypeChecker, TypeFlags, UnionType, UserPreferences, +} from "./_namespaces/ts"; + +/** @internal */ +export function getRenameInfo(program: Program, sourceFile: SourceFile, position: number, preferences: UserPreferences): RenameInfo { + const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); + if (nodeIsEligibleForRename(node)) { + const renameInfo = getRenameInfoForNode(node, program.getTypeChecker(), sourceFile, program, preferences); + if (renameInfo) { + return renameInfo; } - return getRenameInfoError(Diagnostics.You_cannot_rename_this_element); } + return getRenameInfoError(Diagnostics.You_cannot_rename_this_element); +} - function getRenameInfoForNode( - node: Node, - typeChecker: TypeChecker, - sourceFile: SourceFile, - program: Program, - preferences: UserPreferences): RenameInfo | undefined { - const symbol = typeChecker.getSymbolAtLocation(node); - if (!symbol) { - if (isStringLiteralLike(node)) { - const type = getContextualTypeFromParentOrAncestorTypeNode(node, typeChecker); - if (type && ((type.flags & TypeFlags.StringLiteral) || ( - (type.flags & TypeFlags.Union) && every((type as UnionType).types, type => !!(type.flags & TypeFlags.StringLiteral)) - ))) { - return getRenameInfoSuccess(node.text, node.text, ScriptElementKind.string, "", node, sourceFile); - } - } - else if (isLabelName(node)) { - const name = getTextOfNode(node); - return getRenameInfoSuccess(name, name, ScriptElementKind.label, ScriptElementKindModifier.none, node, sourceFile); +function getRenameInfoForNode( + node: Node, + typeChecker: TypeChecker, + sourceFile: SourceFile, + program: Program, + preferences: UserPreferences): RenameInfo | undefined { + const symbol = typeChecker.getSymbolAtLocation(node); + if (!symbol) { + if (isStringLiteralLike(node)) { + const type = getContextualTypeFromParentOrAncestorTypeNode(node, typeChecker); + if (type && ((type.flags & TypeFlags.StringLiteral) || ( + (type.flags & TypeFlags.Union) && every((type as UnionType).types, type => !!(type.flags & TypeFlags.StringLiteral)) + ))) { + return getRenameInfoSuccess(node.text, node.text, ScriptElementKind.string, "", node, sourceFile); } - return undefined; } - // Only allow a symbol to be renamed if it actually has at least one declaration. - const { declarations } = symbol; - if (!declarations || declarations.length === 0) return; - - // Disallow rename for elements that are defined in the standard TypeScript library. - if (declarations.some(declaration => isDefinedInLibraryFile(program, declaration))) { - return getRenameInfoError(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library); - } - - // Cannot rename `default` as in `import { default as foo } from "./someModule"; - if (isIdentifier(node) && node.originalKeywordKind === SyntaxKind.DefaultKeyword && symbol.parent && symbol.parent.flags & SymbolFlags.Module) { - return undefined; + else if (isLabelName(node)) { + const name = getTextOfNode(node); + return getRenameInfoSuccess(name, name, ScriptElementKind.label, ScriptElementKindModifier.none, node, sourceFile); } + return undefined; + } + // Only allow a symbol to be renamed if it actually has at least one declaration. + const { declarations } = symbol; + if (!declarations || declarations.length === 0) return; - if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) { - return preferences.allowRenameOfImportPath ? getRenameInfoForModule(node, sourceFile, symbol) : undefined; - } + // Disallow rename for elements that are defined in the standard TypeScript library. + if (declarations.some(declaration => isDefinedInLibraryFile(program, declaration))) { + return getRenameInfoError(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library); + } - // Disallow rename for elements that would rename across `*/node_modules/*` packages. - const wouldRenameNodeModules = wouldRenameInOtherNodeModules(sourceFile, symbol, typeChecker, preferences); - if (wouldRenameNodeModules) { - return getRenameInfoError(wouldRenameNodeModules); - } + // Cannot rename `default` as in `import { default as foo } from "./someModule"; + if (isIdentifier(node) && node.originalKeywordKind === SyntaxKind.DefaultKeyword && symbol.parent && symbol.parent.flags & SymbolFlags.Module) { + return undefined; + } - const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node); - const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteralLike(node) && node.parent.kind === SyntaxKind.ComputedPropertyName) - ? stripQuotes(getTextOfIdentifierOrLiteral(node)) - : undefined; - const displayName = specifierName || typeChecker.symbolToString(symbol); - const fullDisplayName = specifierName || typeChecker.getFullyQualifiedName(symbol); - return getRenameInfoSuccess(displayName, fullDisplayName, kind, SymbolDisplay.getSymbolModifiers(typeChecker,symbol), node, sourceFile); + if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) { + return preferences.allowRenameOfImportPath ? getRenameInfoForModule(node, sourceFile, symbol) : undefined; } - function isDefinedInLibraryFile(program: Program, declaration: Node) { - const sourceFile = declaration.getSourceFile(); - return program.isSourceFileDefaultLibrary(sourceFile) && fileExtensionIs(sourceFile.fileName, Extension.Dts); + // Disallow rename for elements that would rename across `*/node_modules/*` packages. + const wouldRenameNodeModules = wouldRenameInOtherNodeModules(sourceFile, symbol, typeChecker, preferences); + if (wouldRenameNodeModules) { + return getRenameInfoError(wouldRenameNodeModules); } - function wouldRenameInOtherNodeModules( - originalFile: SourceFile, - symbol: Symbol, - checker: TypeChecker, - preferences: UserPreferences - ): DiagnosticMessage | undefined { - if (!preferences.providePrefixAndSuffixTextForRename && symbol.flags & SymbolFlags.Alias) { - const importSpecifier = symbol.declarations && find(symbol.declarations, decl => isImportSpecifier(decl)); - if (importSpecifier && !(importSpecifier as ImportSpecifier).propertyName) { - symbol = checker.getAliasedSymbol(symbol); - } + const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node); + const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteralLike(node) && node.parent.kind === SyntaxKind.ComputedPropertyName) + ? stripQuotes(getTextOfIdentifierOrLiteral(node)) + : undefined; + const displayName = specifierName || typeChecker.symbolToString(symbol); + const fullDisplayName = specifierName || typeChecker.getFullyQualifiedName(symbol); + return getRenameInfoSuccess(displayName, fullDisplayName, kind, SymbolDisplay.getSymbolModifiers(typeChecker,symbol), node, sourceFile); +} + +function isDefinedInLibraryFile(program: Program, declaration: Node) { + const sourceFile = declaration.getSourceFile(); + return program.isSourceFileDefaultLibrary(sourceFile) && fileExtensionIs(sourceFile.fileName, Extension.Dts); +} + +function wouldRenameInOtherNodeModules( + originalFile: SourceFile, + symbol: Symbol, + checker: TypeChecker, + preferences: UserPreferences +): DiagnosticMessage | undefined { + if (!preferences.providePrefixAndSuffixTextForRename && symbol.flags & SymbolFlags.Alias) { + const importSpecifier = symbol.declarations && find(symbol.declarations, decl => isImportSpecifier(decl)); + if (importSpecifier && !(importSpecifier as ImportSpecifier).propertyName) { + symbol = checker.getAliasedSymbol(symbol); } - const { declarations } = symbol; - if (!declarations) { - return undefined; + } + const { declarations } = symbol; + if (!declarations) { + return undefined; + } + const originalPackage = getPackagePathComponents(originalFile.path); + if (originalPackage === undefined) { // original source file is not in node_modules + if (some(declarations, declaration => isInsideNodeModules(declaration.getSourceFile().path))) { + return Diagnostics.You_cannot_rename_elements_that_are_defined_in_a_node_modules_folder; } - const originalPackage = getPackagePathComponents(originalFile.path); - if (originalPackage === undefined) { // original source file is not in node_modules - if (some(declarations, declaration => isInsideNodeModules(declaration.getSourceFile().path))) { - return Diagnostics.You_cannot_rename_elements_that_are_defined_in_a_node_modules_folder; - } - else { - return undefined; - } + else { + return undefined; } - // original source file is in node_modules - for (const declaration of declarations) { - const declPackage = getPackagePathComponents(declaration.getSourceFile().path); - if (declPackage) { - const length = Math.min(originalPackage.length, declPackage.length); - for (let i = 0; i <= length; i++) { - if (compareStringsCaseSensitive(originalPackage[i], declPackage[i]) !== Comparison.EqualTo) { - return Diagnostics.You_cannot_rename_elements_that_are_defined_in_another_node_modules_folder; - } + } + // original source file is in node_modules + for (const declaration of declarations) { + const declPackage = getPackagePathComponents(declaration.getSourceFile().path); + if (declPackage) { + const length = Math.min(originalPackage.length, declPackage.length); + for (let i = 0; i <= length; i++) { + if (compareStringsCaseSensitive(originalPackage[i], declPackage[i]) !== Comparison.EqualTo) { + return Diagnostics.You_cannot_rename_elements_that_are_defined_in_another_node_modules_folder; } } } - return undefined; } + return undefined; +} - function getPackagePathComponents(filePath: Path): string[] | undefined { - const components = getPathComponents(filePath); - const nodeModulesIdx = components.lastIndexOf("node_modules"); - if (nodeModulesIdx === -1) { - return undefined; - } - return components.slice(0, nodeModulesIdx + 2); +function getPackagePathComponents(filePath: Path): string[] | undefined { + const components = getPathComponents(filePath); + const nodeModulesIdx = components.lastIndexOf("node_modules"); + if (nodeModulesIdx === -1) { + return undefined; } + return components.slice(0, nodeModulesIdx + 2); +} - function getRenameInfoForModule(node: StringLiteralLike, sourceFile: SourceFile, moduleSymbol: Symbol): RenameInfo | undefined { - if (!isExternalModuleNameRelative(node.text)) { - return getRenameInfoError(Diagnostics.You_cannot_rename_a_module_via_a_global_import); - } - - const moduleSourceFile = moduleSymbol.declarations && find(moduleSymbol.declarations, isSourceFile); - if (!moduleSourceFile) return undefined; - const withoutIndex = endsWith(node.text, "/index") || endsWith(node.text, "/index.js") ? undefined : tryRemoveSuffix(removeFileExtension(moduleSourceFile.fileName), "/index"); - const name = withoutIndex === undefined ? moduleSourceFile.fileName : withoutIndex; - const kind = withoutIndex === undefined ? ScriptElementKind.moduleElement : ScriptElementKind.directory; - const indexAfterLastSlash = node.text.lastIndexOf("/") + 1; - // Span should only be the last component of the path. + 1 to account for the quote character. - const triggerSpan = createTextSpan(node.getStart(sourceFile) + 1 + indexAfterLastSlash, node.text.length - indexAfterLastSlash); - return { - canRename: true, - fileToRename: name, - kind, - displayName: name, - fullDisplayName: name, - kindModifiers: ScriptElementKindModifier.none, - triggerSpan, - }; +function getRenameInfoForModule(node: StringLiteralLike, sourceFile: SourceFile, moduleSymbol: Symbol): RenameInfo | undefined { + if (!isExternalModuleNameRelative(node.text)) { + return getRenameInfoError(Diagnostics.You_cannot_rename_a_module_via_a_global_import); } - function getRenameInfoSuccess(displayName: string, fullDisplayName: string, kind: ScriptElementKind, kindModifiers: string, node: Node, sourceFile: SourceFile): RenameInfoSuccess { - return { - canRename: true, - fileToRename: undefined, - kind, - displayName, - fullDisplayName, - kindModifiers, - triggerSpan: createTriggerSpanForNode(node, sourceFile) - }; - } + const moduleSourceFile = moduleSymbol.declarations && find(moduleSymbol.declarations, isSourceFile); + if (!moduleSourceFile) return undefined; + const withoutIndex = endsWith(node.text, "/index") || endsWith(node.text, "/index.js") ? undefined : tryRemoveSuffix(removeFileExtension(moduleSourceFile.fileName), "/index"); + const name = withoutIndex === undefined ? moduleSourceFile.fileName : withoutIndex; + const kind = withoutIndex === undefined ? ScriptElementKind.moduleElement : ScriptElementKind.directory; + const indexAfterLastSlash = node.text.lastIndexOf("/") + 1; + // Span should only be the last component of the path. + 1 to account for the quote character. + const triggerSpan = createTextSpan(node.getStart(sourceFile) + 1 + indexAfterLastSlash, node.text.length - indexAfterLastSlash); + return { + canRename: true, + fileToRename: name, + kind, + displayName: name, + fullDisplayName: name, + kindModifiers: ScriptElementKindModifier.none, + triggerSpan, + }; +} - function getRenameInfoError(diagnostic: DiagnosticMessage): RenameInfoFailure { - return { canRename: false, localizedErrorMessage: getLocaleSpecificMessage(diagnostic) }; - } +function getRenameInfoSuccess(displayName: string, fullDisplayName: string, kind: ScriptElementKind, kindModifiers: string, node: Node, sourceFile: SourceFile): RenameInfoSuccess { + return { + canRename: true, + fileToRename: undefined, + kind, + displayName, + fullDisplayName, + kindModifiers, + triggerSpan: createTriggerSpanForNode(node, sourceFile) + }; +} - function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) { - let start = node.getStart(sourceFile); - let width = node.getWidth(sourceFile); - if (isStringLiteralLike(node)) { - // Exclude the quotes - start += 1; - width -= 2; - } - return createTextSpan(start, width); +function getRenameInfoError(diagnostic: DiagnosticMessage): RenameInfoFailure { + return { canRename: false, localizedErrorMessage: getLocaleSpecificMessage(diagnostic) }; +} + +function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) { + let start = node.getStart(sourceFile); + let width = node.getWidth(sourceFile); + if (isStringLiteralLike(node)) { + // Exclude the quotes + start += 1; + width -= 2; } + return createTextSpan(start, width); +} - export function nodeIsEligibleForRename(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PrivateIdentifier: - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.ThisKeyword: - return true; - case SyntaxKind.NumericLiteral: - return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral); - default: - return false; - } +/** @internal */ +export function nodeIsEligibleForRename(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.ThisKeyword: + return true; + case SyntaxKind.NumericLiteral: + return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral); + default: + return false; } } diff --git a/src/services/services.ts b/src/services/services.ts index 42f6fd27077a9..3380fe9ff2dcc 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1,2875 +1,2938 @@ -namespace ts { - /** The version of the language service API */ - export const servicesVersion = "0.8"; - - function createNode(kind: TKind, pos: number, end: number, parent: Node): NodeObject | TokenObject | IdentifierObject | PrivateIdentifierObject { - const node = isNodeKind(kind) ? new NodeObject(kind, pos, end) : - kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) : - kind === SyntaxKind.PrivateIdentifier ? new PrivateIdentifierObject(SyntaxKind.PrivateIdentifier, pos, end) : - new TokenObject(kind, pos, end); - node.parent = parent; - node.flags = parent.flags & NodeFlags.ContextFlags; - return node; - } - - class NodeObject implements Node { - public kind: SyntaxKind; - public pos: number; - public end: number; - public flags: NodeFlags; - public modifierFlagsCache: ModifierFlags; - public transformFlags: TransformFlags; - public parent: Node; - public symbol!: Symbol; // Actually optional, but it was too annoying to access `node.symbol!` everywhere since in many cases we know it must be defined - public jsDoc?: JSDoc[]; - public original?: Node; - private _children: Node[] | undefined; - - constructor(kind: SyntaxKind, pos: number, end: number) { - this.pos = pos; - this.end = end; - this.flags = NodeFlags.None; - this.modifierFlagsCache = ModifierFlags.None; - this.transformFlags = TransformFlags.None; - this.parent = undefined!; - this.kind = kind; - } +import * as ts from "./_namespaces/ts"; +import * as NavigateTo from "./_namespaces/ts.NavigateTo"; +import * as NavigationBar from "./_namespaces/ts.NavigationBar"; +import { + __String, ApplicableRefactorInfo, ApplyCodeActionCommandResult, AssignmentDeclarationKind, BaseType, + BinaryExpression, BreakpointResolver, CallHierarchy, CallHierarchyIncomingCall, CallHierarchyItem, + CallHierarchyOutgoingCall, CancellationToken, changeCompilerHostLikeToUseCache, CharacterCodes, CheckJsDirective, + Classifications, ClassifiedSpan, ClassifiedSpan2020, classifier, CodeActionCommand, codefix, CodeFixAction, + CombinedCodeActions, CombinedCodeFixScope, combinePaths, compareValues, CompilerHost, CompilerOptions, + CompletionEntryData, CompletionEntryDetails, CompletionInfo, Completions, computePositionOfLineAndCharacter, + computeSuggestionDiagnostics, createDocumentRegistry, createGetCanonicalFileName, createMultiMap, createProgram, + CreateProgramOptions, createSourceFile, CreateSourceFileOptions, createTextSpanFromBounds, createTextSpanFromNode, + createTextSpanFromRange, Debug, Declaration, deduplicate, DefinitionInfo, DefinitionInfoAndBoundSpan, Diagnostic, + DiagnosticWithLocation, directoryProbablyExists, DocCommentTemplateOptions, DocumentHighlights, DocumentRegistry, + DocumentSpan, EditorOptions, EditorSettings, ElementAccessExpression, EmitTextWriter, emptyArray, emptyOptions, + EndOfFileToken, EntityName, equateValues, ESMap, ExportDeclaration, FileReference, FileTextChanges, filter, find, + FindAllReferences, findChildOfKind, findPrecedingToken, first, firstDefined, firstOrOnly, flatMap, forEach, + forEachChild, FormatCodeOptions, FormatCodeSettings, formatting, FunctionLikeDeclaration, GeneratedIdentifierFlags, + getAdjustedRenameLocation, getAllSuperTypeNodes, getAssignmentDeclarationKind, GetCompletionsAtPositionOptions, + getContainerNode, getDefaultLibFileName, getDirectoryPath, getEmitDeclarations, getEntries, + getEscapedTextOfIdentifierOrLiteral, getFileEmitOutput, getImpliedNodeFormatForFile, getJSDocTags, + getLineAndCharacterOfPosition, getLineStarts, getMappedDocumentSpan, getNameFromPropertyName, getNewLineCharacter, + getNewLineOrDefaultFromHost, getNonAssignedNameOfDeclaration, getNormalizedAbsolutePath, getObjectFlags, + getScriptKind, getSetExternalModuleIndicator, getSnapshotText, getSourceFileOfNode, getSourceMapper, + getTokenPosOfNode, getTouchingPropertyName, getTouchingToken, GoToDefinition, HasInvalidatedResolutions, + hasJSDocNodes, hasProperty, hasStaticModifier, hasSyntacticModifier, HighlightSpanKind, HostCancellationToken, + hostGetCanonicalFileName, hostUsesCaseSensitiveFileNames, Identifier, identity, idText, ImplementationLocation, + ImportDeclaration, IndexKind, IndexType, InlayHint, InlayHints, InlayHintsContext, insertSorted, InterfaceType, + IntersectionType, isArray, isBindingPattern, isComputedPropertyName, isConstTypeReference, IScriptSnapshot, + isDeclarationName, isGetAccessor, isIdentifier, isImportMeta, isInComment, isInsideJsxElement, + isInsideJsxElementOrAttribute, isInString, isInTemplateString, isIntrinsicJsxName, isJSDocCommentContainingNode, + isJsxAttributes, isJsxClosingElement, isJsxElement, isJsxFragment, isJsxOpeningElement, isJsxOpeningFragment, + isJsxText, isLabelName, isLiteralComputedPropertyDeclarationName, isNamedExports, isNamedTupleMember, + isNameOfModuleDeclaration, isNewExpression, isNodeKind, isObjectLiteralElement, isObjectLiteralExpression, + isPrivateIdentifier, isProgramUptoDate, isPropertyAccessExpression, isPropertyName, isRightSideOfPropertyAccess, + isRightSideOfQualifiedName, isSetAccessor, isStringOrNumericLiteralLike, isTagName, isTextWhiteSpaceLike, + isThisTypeParameter, JsDoc, JSDoc, JSDocContainer, JSDocTagInfo, JsonSourceFile, JsxAttributes, JsxClosingTagInfo, + JsxElement, JsxEmit, JsxFragment, LanguageService, LanguageServiceHost, LanguageServiceMode, LanguageVariant, + lastOrUndefined, length, LineAndCharacter, lineBreakPart, LiteralType, map, Map, mapDefined, MapLike, mapOneOrMany, + maybeBind, maybeSetLocalizedDiagnosticMessages, ModeAwareCache, ModifierFlags, ModuleDeclaration, NavigateToItem, + NavigationBarItem, NavigationTree, Node, NodeArray, NodeFlags, noop, normalizePath, NumberLiteralType, + NumericLiteral, ObjectAllocator, ObjectFlags, ObjectLiteralElement, ObjectLiteralExpression, + OperationCanceledException, OrganizeImports, OrganizeImportsArgs, OrganizeImportsMode, OutliningElementsCollector, + OutliningSpan, ParseConfigFileHost, ParsedCommandLine, parseJsonSourceFileConfigFileContent, Path, + positionIsSynthesized, PossibleProgramFileInfo, PragmaMap, PrivateIdentifier, Program, PropertyName, Push, + QuickInfo, refactor, RefactorContext, RefactorEditInfo, RefactorTriggerReason, ReferencedSymbol, ReferenceEntry, + Rename, RenameInfo, RenameInfoOptions, RenameLocation, ResolvedModuleFull, ResolvedProjectReference, + ResolvedTypeReferenceDirective, returnFalse, scanner, ScriptElementKind, ScriptElementKindModifier, ScriptKind, + ScriptTarget, SelectionRange, SemanticClassificationFormat, Set, setObjectAllocator, Signature, + SignatureDeclaration, SignatureFlags, SignatureHelp, SignatureHelpItems, SignatureHelpItemsOptions, SignatureKind, + singleElementArray, SmartSelectionRange, SortedArray, SourceFile, SourceFileLike, SourceMapSource, Statement, + stringContains, StringLiteral, StringLiteralLike, StringLiteralType, Symbol, SymbolDisplay, SymbolDisplayPart, + SymbolFlags, symbolName, SyntaxKind, SyntaxList, tagNamesAreEquivalent, TextChange, TextChangeRange, TextInsertion, + TextRange, TextSpan, textSpanEnd, timestamp, TodoComment, TodoCommentDescriptor, Token, toPath, tracing, + TransformFlags, TransientSymbol, Type, TypeChecker, TypeFlags, TypeNode, TypeParameter, TypePredicate, + TypeReference, typeToDisplayParts, UnderscoreEscapedMap, UnionOrIntersectionType, UnionType, updateSourceFile, + UserPreferences, VariableDeclaration, +} from "./_namespaces/ts"; + +/** The version of the language service API */ +export const servicesVersion = "0.8"; + +function createNode(kind: TKind, pos: number, end: number, parent: Node): NodeObject | TokenObject | IdentifierObject | PrivateIdentifierObject { + const node = isNodeKind(kind) ? new NodeObject(kind, pos, end) : + kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) : + kind === SyntaxKind.PrivateIdentifier ? new PrivateIdentifierObject(SyntaxKind.PrivateIdentifier, pos, end) : + new TokenObject(kind, pos, end); + node.parent = parent; + node.flags = parent.flags & NodeFlags.ContextFlags; + return node; +} - private assertHasRealPosition(message?: string) { - // eslint-disable-next-line local/debug-assert - Debug.assert(!positionIsSynthesized(this.pos) && !positionIsSynthesized(this.end), message || "Node must have a real position for this operation"); - } +class NodeObject implements Node { + public kind: SyntaxKind; + public pos: number; + public end: number; + public flags: NodeFlags; + public modifierFlagsCache: ModifierFlags; + public transformFlags: TransformFlags; + public parent: Node; + public symbol!: Symbol; // Actually optional, but it was too annoying to access `node.symbol!` everywhere since in many cases we know it must be defined + public jsDoc?: JSDoc[]; + public original?: Node; + private _children: Node[] | undefined; + + constructor(kind: SyntaxKind, pos: number, end: number) { + this.pos = pos; + this.end = end; + this.flags = NodeFlags.None; + this.modifierFlagsCache = ModifierFlags.None; + this.transformFlags = TransformFlags.None; + this.parent = undefined!; + this.kind = kind; + } - public getSourceFile(): SourceFile { - return getSourceFileOfNode(this); - } + private assertHasRealPosition(message?: string) { + // eslint-disable-next-line local/debug-assert + Debug.assert(!positionIsSynthesized(this.pos) && !positionIsSynthesized(this.end), message || "Node must have a real position for this operation"); + } - public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { - this.assertHasRealPosition(); - return getTokenPosOfNode(this, sourceFile, includeJsDocComment); - } + public getSourceFile(): SourceFile { + return getSourceFileOfNode(this); + } - public getFullStart(): number { - this.assertHasRealPosition(); - return this.pos; - } + public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { + this.assertHasRealPosition(); + return getTokenPosOfNode(this, sourceFile, includeJsDocComment); + } - public getEnd(): number { - this.assertHasRealPosition(); - return this.end; - } + public getFullStart(): number { + this.assertHasRealPosition(); + return this.pos; + } - public getWidth(sourceFile?: SourceFile): number { - this.assertHasRealPosition(); - return this.getEnd() - this.getStart(sourceFile); - } + public getEnd(): number { + this.assertHasRealPosition(); + return this.end; + } - public getFullWidth(): number { - this.assertHasRealPosition(); - return this.end - this.pos; - } + public getWidth(sourceFile?: SourceFile): number { + this.assertHasRealPosition(); + return this.getEnd() - this.getStart(sourceFile); + } - public getLeadingTriviaWidth(sourceFile?: SourceFile): number { - this.assertHasRealPosition(); - return this.getStart(sourceFile) - this.pos; - } + public getFullWidth(): number { + this.assertHasRealPosition(); + return this.end - this.pos; + } - public getFullText(sourceFile?: SourceFile): string { - this.assertHasRealPosition(); - return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end); - } + public getLeadingTriviaWidth(sourceFile?: SourceFile): number { + this.assertHasRealPosition(); + return this.getStart(sourceFile) - this.pos; + } - public getText(sourceFile?: SourceFile): string { - this.assertHasRealPosition(); - if (!sourceFile) { - sourceFile = this.getSourceFile(); - } - return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd()); - } + public getFullText(sourceFile?: SourceFile): string { + this.assertHasRealPosition(); + return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end); + } - public getChildCount(sourceFile?: SourceFile): number { - return this.getChildren(sourceFile).length; + public getText(sourceFile?: SourceFile): string { + this.assertHasRealPosition(); + if (!sourceFile) { + sourceFile = this.getSourceFile(); } + return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd()); + } - public getChildAt(index: number, sourceFile?: SourceFile): Node { - return this.getChildren(sourceFile)[index]; - } + public getChildCount(sourceFile?: SourceFile): number { + return this.getChildren(sourceFile).length; + } - public getChildren(sourceFile?: SourceFileLike): Node[] { - this.assertHasRealPosition("Node without a real position cannot be scanned and thus has no token nodes - use forEachChild and collect the result if that's fine"); - return this._children || (this._children = createChildren(this, sourceFile)); - } + public getChildAt(index: number, sourceFile?: SourceFile): Node { + return this.getChildren(sourceFile)[index]; + } - public getFirstToken(sourceFile?: SourceFileLike): Node | undefined { - this.assertHasRealPosition(); - const children = this.getChildren(sourceFile); - if (!children.length) { - return undefined; - } + public getChildren(sourceFile?: SourceFileLike): Node[] { + this.assertHasRealPosition("Node without a real position cannot be scanned and thus has no token nodes - use forEachChild and collect the result if that's fine"); + return this._children || (this._children = createChildren(this, sourceFile)); + } - const child = find(children, kid => kid.kind < SyntaxKind.FirstJSDocNode || kid.kind > SyntaxKind.LastJSDocNode)!; - return child.kind < SyntaxKind.FirstNode ? - child : - child.getFirstToken(sourceFile); + public getFirstToken(sourceFile?: SourceFileLike): Node | undefined { + this.assertHasRealPosition(); + const children = this.getChildren(sourceFile); + if (!children.length) { + return undefined; } - public getLastToken(sourceFile?: SourceFileLike): Node | undefined { - this.assertHasRealPosition(); - const children = this.getChildren(sourceFile); + const child = find(children, kid => kid.kind < SyntaxKind.FirstJSDocNode || kid.kind > SyntaxKind.LastJSDocNode)!; + return child.kind < SyntaxKind.FirstNode ? + child : + child.getFirstToken(sourceFile); + } - const child = lastOrUndefined(children); - if (!child) { - return undefined; - } + public getLastToken(sourceFile?: SourceFileLike): Node | undefined { + this.assertHasRealPosition(); + const children = this.getChildren(sourceFile); - return child.kind < SyntaxKind.FirstNode ? child : child.getLastToken(sourceFile); + const child = lastOrUndefined(children); + if (!child) { + return undefined; } - public forEachChild(cbNode: (node: Node) => T, cbNodeArray?: (nodes: NodeArray) => T): T | undefined { - return forEachChild(this, cbNode, cbNodeArray); - } + return child.kind < SyntaxKind.FirstNode ? child : child.getLastToken(sourceFile); } - function createChildren(node: Node, sourceFile: SourceFileLike | undefined): Node[] { - if (!isNodeKind(node.kind)) { - return emptyArray; - } + public forEachChild(cbNode: (node: Node) => T, cbNodeArray?: (nodes: NodeArray) => T): T | undefined { + return forEachChild(this, cbNode, cbNodeArray); + } +} - const children: Node[] = []; +function createChildren(node: Node, sourceFile: SourceFileLike | undefined): Node[] { + if (!isNodeKind(node.kind)) { + return emptyArray; + } - if (isJSDocCommentContainingNode(node)) { - /** Don't add trivia for "tokens" since this is in a comment. */ - node.forEachChild(child => { - children.push(child); - }); - return children; - } + const children: Node[] = []; - scanner.setText((sourceFile || node.getSourceFile()).text); - let pos = node.pos; - const processNode = (child: Node) => { - addSyntheticNodes(children, pos, child.pos, node); + if (isJSDocCommentContainingNode(node)) { + /** Don't add trivia for "tokens" since this is in a comment. */ + node.forEachChild(child => { children.push(child); - pos = child.end; - }; - const processNodes = (nodes: NodeArray) => { - addSyntheticNodes(children, pos, nodes.pos, node); - children.push(createSyntaxList(nodes, node)); - pos = nodes.end; - }; - // jsDocComments need to be the first children - forEach((node as JSDocContainer).jsDoc, processNode); - // For syntactic classifications, all trivia are classified together, including jsdoc comments. - // For that to work, the jsdoc comments should still be the leading trivia of the first child. - // Restoring the scanner position ensures that. - pos = node.pos; - node.forEachChild(processNode, processNodes); - addSyntheticNodes(children, pos, node.end, node); - scanner.setText(undefined); + }); return children; } - function addSyntheticNodes(nodes: Push, pos: number, end: number, parent: Node): void { - scanner.setTextPos(pos); - while (pos < end) { - const token = scanner.scan(); - const textPos = scanner.getTextPos(); - if (textPos <= end) { - if (token === SyntaxKind.Identifier) { - Debug.fail(`Did not expect ${Debug.formatSyntaxKind(parent.kind)} to have an Identifier in its trivia`); - } - nodes.push(createNode(token, pos, textPos, parent)); - } - pos = textPos; - if (token === SyntaxKind.EndOfFileToken) { - break; + scanner.setText((sourceFile || node.getSourceFile()).text); + let pos = node.pos; + const processNode = (child: Node) => { + addSyntheticNodes(children, pos, child.pos, node); + children.push(child); + pos = child.end; + }; + const processNodes = (nodes: NodeArray) => { + addSyntheticNodes(children, pos, nodes.pos, node); + children.push(createSyntaxList(nodes, node)); + pos = nodes.end; + }; + // jsDocComments need to be the first children + forEach((node as JSDocContainer).jsDoc, processNode); + // For syntactic classifications, all trivia are classified together, including jsdoc comments. + // For that to work, the jsdoc comments should still be the leading trivia of the first child. + // Restoring the scanner position ensures that. + pos = node.pos; + node.forEachChild(processNode, processNodes); + addSyntheticNodes(children, pos, node.end, node); + scanner.setText(undefined); + return children; +} + +function addSyntheticNodes(nodes: Push, pos: number, end: number, parent: Node): void { + scanner.setTextPos(pos); + while (pos < end) { + const token = scanner.scan(); + const textPos = scanner.getTextPos(); + if (textPos <= end) { + if (token === SyntaxKind.Identifier) { + Debug.fail(`Did not expect ${Debug.formatSyntaxKind(parent.kind)} to have an Identifier in its trivia`); } + nodes.push(createNode(token, pos, textPos, parent)); } - } - - function createSyntaxList(nodes: NodeArray, parent: Node): Node { - const list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, parent) as any as SyntaxList; - list._children = []; - let pos = nodes.pos; - for (const node of nodes) { - addSyntheticNodes(list._children, pos, node.pos, parent); - list._children.push(node); - pos = node.end; + pos = textPos; + if (token === SyntaxKind.EndOfFileToken) { + break; } - addSyntheticNodes(list._children, pos, nodes.end, parent); - return list; } +} - class TokenOrIdentifierObject implements Node { - public kind!: SyntaxKind; - public pos: number; - public end: number; - public flags: NodeFlags; - public modifierFlagsCache: ModifierFlags; - public transformFlags: TransformFlags; - public parent: Node; - public symbol!: Symbol; - public jsDocComments?: JSDoc[]; +function createSyntaxList(nodes: NodeArray, parent: Node): Node { + const list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, parent) as any as SyntaxList; + list._children = []; + let pos = nodes.pos; + for (const node of nodes) { + addSyntheticNodes(list._children, pos, node.pos, parent); + list._children.push(node); + pos = node.end; + } + addSyntheticNodes(list._children, pos, nodes.end, parent); + return list; +} - constructor(pos: number, end: number) { - // Set properties in same order as NodeObject - this.pos = pos; - this.end = end; - this.flags = NodeFlags.None; - this.modifierFlagsCache = ModifierFlags.None; - this.transformFlags = TransformFlags.None; - this.parent = undefined!; - } +class TokenOrIdentifierObject implements Node { + public kind!: SyntaxKind; + public pos: number; + public end: number; + public flags: NodeFlags; + public modifierFlagsCache: ModifierFlags; + public transformFlags: TransformFlags; + public parent: Node; + public symbol!: Symbol; + public jsDocComments?: JSDoc[]; + + constructor(pos: number, end: number) { + // Set properties in same order as NodeObject + this.pos = pos; + this.end = end; + this.flags = NodeFlags.None; + this.modifierFlagsCache = ModifierFlags.None; + this.transformFlags = TransformFlags.None; + this.parent = undefined!; + } - public getSourceFile(): SourceFile { - return getSourceFileOfNode(this); - } + public getSourceFile(): SourceFile { + return getSourceFileOfNode(this); + } - public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { - return getTokenPosOfNode(this, sourceFile, includeJsDocComment); - } + public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { + return getTokenPosOfNode(this, sourceFile, includeJsDocComment); + } - public getFullStart(): number { - return this.pos; - } + public getFullStart(): number { + return this.pos; + } - public getEnd(): number { - return this.end; - } + public getEnd(): number { + return this.end; + } - public getWidth(sourceFile?: SourceFile): number { - return this.getEnd() - this.getStart(sourceFile); - } + public getWidth(sourceFile?: SourceFile): number { + return this.getEnd() - this.getStart(sourceFile); + } - public getFullWidth(): number { - return this.end - this.pos; - } + public getFullWidth(): number { + return this.end - this.pos; + } - public getLeadingTriviaWidth(sourceFile?: SourceFile): number { - return this.getStart(sourceFile) - this.pos; - } + public getLeadingTriviaWidth(sourceFile?: SourceFile): number { + return this.getStart(sourceFile) - this.pos; + } - public getFullText(sourceFile?: SourceFile): string { - return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end); - } + public getFullText(sourceFile?: SourceFile): string { + return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end); + } - public getText(sourceFile?: SourceFile): string { - if (!sourceFile) { - sourceFile = this.getSourceFile(); - } - return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd()); + public getText(sourceFile?: SourceFile): string { + if (!sourceFile) { + sourceFile = this.getSourceFile(); } + return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd()); + } - public getChildCount(): number { - return this.getChildren().length; - } + public getChildCount(): number { + return this.getChildren().length; + } - public getChildAt(index: number): Node { - return this.getChildren()[index]; - } + public getChildAt(index: number): Node { + return this.getChildren()[index]; + } - public getChildren(): Node[] { - return this.kind === SyntaxKind.EndOfFileToken ? (this as EndOfFileToken).jsDoc || emptyArray : emptyArray; - } + public getChildren(): Node[] { + return this.kind === SyntaxKind.EndOfFileToken ? (this as EndOfFileToken).jsDoc || emptyArray : emptyArray; + } - public getFirstToken(): Node | undefined { - return undefined; - } + public getFirstToken(): Node | undefined { + return undefined; + } - public getLastToken(): Node | undefined { - return undefined; - } + public getLastToken(): Node | undefined { + return undefined; + } - public forEachChild(): T | undefined { - return undefined; - } + public forEachChild(): T | undefined { + return undefined; } +} - class SymbolObject implements Symbol { - flags: SymbolFlags; - escapedName: __String; - declarations!: Declaration[]; - valueDeclaration!: Declaration; +class SymbolObject implements Symbol { + flags: SymbolFlags; + escapedName: __String; + declarations!: Declaration[]; + valueDeclaration!: Declaration; - // Undefined is used to indicate the value has not been computed. If, after computing, the - // symbol has no doc comment, then the empty array will be returned. - documentationComment?: SymbolDisplayPart[]; - tags?: JSDocTagInfo[]; // same + // Undefined is used to indicate the value has not been computed. If, after computing, the + // symbol has no doc comment, then the empty array will be returned. + documentationComment?: SymbolDisplayPart[]; + tags?: JSDocTagInfo[]; // same - contextualGetAccessorDocumentationComment?: SymbolDisplayPart[]; - contextualSetAccessorDocumentationComment?: SymbolDisplayPart[]; + contextualGetAccessorDocumentationComment?: SymbolDisplayPart[]; + contextualSetAccessorDocumentationComment?: SymbolDisplayPart[]; - contextualGetAccessorTags?: JSDocTagInfo[]; - contextualSetAccessorTags?: JSDocTagInfo[]; + contextualGetAccessorTags?: JSDocTagInfo[]; + contextualSetAccessorTags?: JSDocTagInfo[]; - constructor(flags: SymbolFlags, name: __String) { - this.flags = flags; - this.escapedName = name; - } + constructor(flags: SymbolFlags, name: __String) { + this.flags = flags; + this.escapedName = name; + } - getFlags(): SymbolFlags { - return this.flags; - } + getFlags(): SymbolFlags { + return this.flags; + } - get name(): string { - return symbolName(this); - } + get name(): string { + return symbolName(this); + } - getEscapedName(): __String { - return this.escapedName; - } + getEscapedName(): __String { + return this.escapedName; + } - getName(): string { - return this.name; - } + getName(): string { + return this.name; + } - getDeclarations(): Declaration[] | undefined { - return this.declarations; - } + getDeclarations(): Declaration[] | undefined { + return this.declarations; + } - getDocumentationComment(checker: TypeChecker | undefined): SymbolDisplayPart[] { - if (!this.documentationComment) { - this.documentationComment = emptyArray; // Set temporarily to avoid an infinite loop finding inherited docs + getDocumentationComment(checker: TypeChecker | undefined): SymbolDisplayPart[] { + if (!this.documentationComment) { + this.documentationComment = emptyArray; // Set temporarily to avoid an infinite loop finding inherited docs - if (!this.declarations && (this as Symbol as TransientSymbol).target && ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) { - const labelDecl = ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!; - this.documentationComment = getDocumentationComment([labelDecl], checker); - } - else { - this.documentationComment = getDocumentationComment(this.declarations, checker); - } + if (!this.declarations && (this as Symbol as TransientSymbol).target && ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) { + const labelDecl = ((this as Symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!; + this.documentationComment = getDocumentationComment([labelDecl], checker); + } + else { + this.documentationComment = getDocumentationComment(this.declarations, checker); } - return this.documentationComment; } + return this.documentationComment; + } - getContextualDocumentationComment(context: Node | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { - if (context) { - if (isGetAccessor(context)) { - if (!this.contextualGetAccessorDocumentationComment) { - this.contextualGetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isGetAccessor), checker); - } - if (length(this.contextualGetAccessorDocumentationComment)) { - return this.contextualGetAccessorDocumentationComment; - } + getContextualDocumentationComment(context: Node | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { + if (context) { + if (isGetAccessor(context)) { + if (!this.contextualGetAccessorDocumentationComment) { + this.contextualGetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isGetAccessor), checker); } - if (isSetAccessor(context)) { - if (!this.contextualSetAccessorDocumentationComment) { - this.contextualSetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isSetAccessor), checker); - } - if (length(this.contextualSetAccessorDocumentationComment)) { - return this.contextualSetAccessorDocumentationComment; - } + if (length(this.contextualGetAccessorDocumentationComment)) { + return this.contextualGetAccessorDocumentationComment; } } - return this.getDocumentationComment(checker); - } - - getJsDocTags(checker?: TypeChecker): JSDocTagInfo[] { - if (this.tags === undefined) { - this.tags = getJsDocTagsOfDeclarations(this.declarations, checker); + if (isSetAccessor(context)) { + if (!this.contextualSetAccessorDocumentationComment) { + this.contextualSetAccessorDocumentationComment = getDocumentationComment(filter(this.declarations, isSetAccessor), checker); + } + if (length(this.contextualSetAccessorDocumentationComment)) { + return this.contextualSetAccessorDocumentationComment; + } } + } + return this.getDocumentationComment(checker); + } - return this.tags; + getJsDocTags(checker?: TypeChecker): JSDocTagInfo[] { + if (this.tags === undefined) { + this.tags = getJsDocTagsOfDeclarations(this.declarations, checker); } - getContextualJsDocTags(context: Node | undefined, checker: TypeChecker | undefined): JSDocTagInfo[] { - if (context) { - if (isGetAccessor(context)) { - if (!this.contextualGetAccessorTags) { - this.contextualGetAccessorTags = getJsDocTagsOfDeclarations(filter(this.declarations, isGetAccessor), checker); - } - if (length(this.contextualGetAccessorTags)) { - return this.contextualGetAccessorTags; - } + return this.tags; + } + + getContextualJsDocTags(context: Node | undefined, checker: TypeChecker | undefined): JSDocTagInfo[] { + if (context) { + if (isGetAccessor(context)) { + if (!this.contextualGetAccessorTags) { + this.contextualGetAccessorTags = getJsDocTagsOfDeclarations(filter(this.declarations, isGetAccessor), checker); } - if (isSetAccessor(context)) { - if (!this.contextualSetAccessorTags) { - this.contextualSetAccessorTags = getJsDocTagsOfDeclarations(filter(this.declarations, isSetAccessor), checker); - } - if (length(this.contextualSetAccessorTags)) { - return this.contextualSetAccessorTags; - } + if (length(this.contextualGetAccessorTags)) { + return this.contextualGetAccessorTags; + } + } + if (isSetAccessor(context)) { + if (!this.contextualSetAccessorTags) { + this.contextualSetAccessorTags = getJsDocTagsOfDeclarations(filter(this.declarations, isSetAccessor), checker); + } + if (length(this.contextualSetAccessorTags)) { + return this.contextualSetAccessorTags; } } - return this.getJsDocTags(checker); } + return this.getJsDocTags(checker); } +} - class TokenObject extends TokenOrIdentifierObject implements Token { - public kind: TKind; +class TokenObject extends TokenOrIdentifierObject implements Token { + public kind: TKind; - constructor(kind: TKind, pos: number, end: number) { - super(pos, end); - this.kind = kind; - } + constructor(kind: TKind, pos: number, end: number) { + super(pos, end); + this.kind = kind; } +} - class IdentifierObject extends TokenOrIdentifierObject implements Identifier { - public kind: SyntaxKind.Identifier = SyntaxKind.Identifier; - public escapedText!: __String; - public autoGenerateFlags!: GeneratedIdentifierFlags; - _primaryExpressionBrand: any; - _memberExpressionBrand: any; - _leftHandSideExpressionBrand: any; - _updateExpressionBrand: any; - _unaryExpressionBrand: any; - _expressionBrand: any; - _declarationBrand: any; - /*@internal*/typeArguments!: NodeArray; - constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) { - super(pos, end); - } +class IdentifierObject extends TokenOrIdentifierObject implements Identifier { + public kind: SyntaxKind.Identifier = SyntaxKind.Identifier; + public escapedText!: __String; + public autoGenerateFlags!: GeneratedIdentifierFlags; + _primaryExpressionBrand: any; + _memberExpressionBrand: any; + _leftHandSideExpressionBrand: any; + _updateExpressionBrand: any; + _unaryExpressionBrand: any; + _expressionBrand: any; + _declarationBrand: any; + /** @internal */typeArguments!: NodeArray; + constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) { + super(pos, end); + } - get text(): string { - return idText(this); - } + get text(): string { + return idText(this); + } +} +IdentifierObject.prototype.kind = SyntaxKind.Identifier; +class PrivateIdentifierObject extends TokenOrIdentifierObject implements PrivateIdentifier { + public kind: SyntaxKind.PrivateIdentifier = SyntaxKind.PrivateIdentifier; + public escapedText!: __String; + // public symbol!: Symbol; + _primaryExpressionBrand: any; + _memberExpressionBrand: any; + _leftHandSideExpressionBrand: any; + _updateExpressionBrand: any; + _unaryExpressionBrand: any; + _expressionBrand: any; + constructor(_kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) { + super(pos, end); } - IdentifierObject.prototype.kind = SyntaxKind.Identifier; - class PrivateIdentifierObject extends TokenOrIdentifierObject implements PrivateIdentifier { - public kind!: SyntaxKind.PrivateIdentifier; - public escapedText!: __String; - public symbol!: Symbol; - _primaryExpressionBrand: any; - _memberExpressionBrand: any; - _leftHandSideExpressionBrand: any; - _updateExpressionBrand: any; - _unaryExpressionBrand: any; - _expressionBrand: any; - constructor(_kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) { - super(pos, end); - } - get text(): string { - return idText(this); - } + get text(): string { + return idText(this); + } +} +PrivateIdentifierObject.prototype.kind = SyntaxKind.PrivateIdentifier; + +class TypeObject implements Type { + checker: TypeChecker; + flags: TypeFlags; + objectFlags?: ObjectFlags; + id!: number; + symbol!: Symbol; + constructor(checker: TypeChecker, flags: TypeFlags) { + this.checker = checker; + this.flags = flags; + } + getFlags(): TypeFlags { + return this.flags; + } + getSymbol(): Symbol | undefined { + return this.symbol; + } + getProperties(): Symbol[] { + return this.checker.getPropertiesOfType(this); + } + getProperty(propertyName: string): Symbol | undefined { + return this.checker.getPropertyOfType(this, propertyName); + } + getApparentProperties(): Symbol[] { + return this.checker.getAugmentedPropertiesOfType(this); + } + getCallSignatures(): readonly Signature[] { + return this.checker.getSignaturesOfType(this, SignatureKind.Call); + } + getConstructSignatures(): readonly Signature[] { + return this.checker.getSignaturesOfType(this, SignatureKind.Construct); + } + getStringIndexType(): Type | undefined { + return this.checker.getIndexTypeOfType(this, IndexKind.String); + } + getNumberIndexType(): Type | undefined { + return this.checker.getIndexTypeOfType(this, IndexKind.Number); + } + getBaseTypes(): BaseType[] | undefined { + return this.isClassOrInterface() ? this.checker.getBaseTypes(this) : undefined; + } + isNullableType(): boolean { + return this.checker.isNullableType(this); + } + getNonNullableType(): Type { + return this.checker.getNonNullableType(this); + } + getNonOptionalType(): Type { + return this.checker.getNonOptionalType(this); + } + getConstraint(): Type | undefined { + return this.checker.getBaseConstraintOfType(this); + } + getDefault(): Type | undefined { + return this.checker.getDefaultFromTypeParameter(this); } - PrivateIdentifierObject.prototype.kind = SyntaxKind.PrivateIdentifier; - class TypeObject implements Type { - checker: TypeChecker; - flags: TypeFlags; - objectFlags?: ObjectFlags; - id!: number; - symbol!: Symbol; - constructor(checker: TypeChecker, flags: TypeFlags) { - this.checker = checker; - this.flags = flags; - } - getFlags(): TypeFlags { - return this.flags; - } - getSymbol(): Symbol | undefined { - return this.symbol; - } - getProperties(): Symbol[] { - return this.checker.getPropertiesOfType(this); - } - getProperty(propertyName: string): Symbol | undefined { - return this.checker.getPropertyOfType(this, propertyName); - } - getApparentProperties(): Symbol[] { - return this.checker.getAugmentedPropertiesOfType(this); - } - getCallSignatures(): readonly Signature[] { - return this.checker.getSignaturesOfType(this, SignatureKind.Call); - } - getConstructSignatures(): readonly Signature[] { - return this.checker.getSignaturesOfType(this, SignatureKind.Construct); - } - getStringIndexType(): Type | undefined { - return this.checker.getIndexTypeOfType(this, IndexKind.String); - } - getNumberIndexType(): Type | undefined { - return this.checker.getIndexTypeOfType(this, IndexKind.Number); - } - getBaseTypes(): BaseType[] | undefined { - return this.isClassOrInterface() ? this.checker.getBaseTypes(this) : undefined; - } - isNullableType(): boolean { - return this.checker.isNullableType(this); - } - getNonNullableType(): Type { - return this.checker.getNonNullableType(this); - } - getNonOptionalType(): Type { - return this.checker.getNonOptionalType(this); - } - getConstraint(): Type | undefined { - return this.checker.getBaseConstraintOfType(this); - } - getDefault(): Type | undefined { - return this.checker.getDefaultFromTypeParameter(this); + isUnion(): this is UnionType { + return !!(this.flags & TypeFlags.Union); + } + isIntersection(): this is IntersectionType { + return !!(this.flags & TypeFlags.Intersection); + } + isUnionOrIntersection(): this is UnionOrIntersectionType { + return !!(this.flags & TypeFlags.UnionOrIntersection); + } + isLiteral(): this is LiteralType { + return !!(this.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.BigIntLiteral)); + } + isStringLiteral(): this is StringLiteralType { + return !!(this.flags & TypeFlags.StringLiteral); + } + isNumberLiteral(): this is NumberLiteralType { + return !!(this.flags & TypeFlags.NumberLiteral); + } + isTypeParameter(): this is TypeParameter { + return !!(this.flags & TypeFlags.TypeParameter); + } + isClassOrInterface(): this is InterfaceType { + return !!(getObjectFlags(this) & ObjectFlags.ClassOrInterface); + } + isClass(): this is InterfaceType { + return !!(getObjectFlags(this) & ObjectFlags.Class); + } + isIndexType(): this is IndexType { + return !!(this.flags & TypeFlags.Index); + } + /** + * This polyfills `referenceType.typeArguments` for API consumers + */ + get typeArguments() { + if (getObjectFlags(this) & ObjectFlags.Reference) { + return this.checker.getTypeArguments(this as Type as TypeReference); } + return undefined; + } +} - isUnion(): this is UnionType { - return !!(this.flags & TypeFlags.Union); - } - isIntersection(): this is IntersectionType { - return !!(this.flags & TypeFlags.Intersection); - } - isUnionOrIntersection(): this is UnionOrIntersectionType { - return !!(this.flags & TypeFlags.UnionOrIntersection); - } - isLiteral(): this is LiteralType { - return !!(this.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.BigIntLiteral)); - } - isStringLiteral(): this is StringLiteralType { - return !!(this.flags & TypeFlags.StringLiteral); - } - isNumberLiteral(): this is NumberLiteralType { - return !!(this.flags & TypeFlags.NumberLiteral); - } - isTypeParameter(): this is TypeParameter { - return !!(this.flags & TypeFlags.TypeParameter); - } - isClassOrInterface(): this is InterfaceType { - return !!(getObjectFlags(this) & ObjectFlags.ClassOrInterface); - } - isClass(): this is InterfaceType { - return !!(getObjectFlags(this) & ObjectFlags.Class); - } - isIndexType(): this is IndexType { - return !!(this.flags & TypeFlags.Index); - } - /** - * This polyfills `referenceType.typeArguments` for API consumers - */ - get typeArguments() { - if (getObjectFlags(this) & ObjectFlags.Reference) { - return this.checker.getTypeArguments(this as Type as TypeReference); +class SignatureObject implements Signature { + flags: SignatureFlags; + checker: TypeChecker; + declaration!: SignatureDeclaration; + typeParameters?: TypeParameter[]; + parameters!: Symbol[]; + thisParameter!: Symbol; + resolvedReturnType!: Type; + resolvedTypePredicate: TypePredicate | undefined; + minTypeArgumentCount!: number; + minArgumentCount!: number; + + // Undefined is used to indicate the value has not been computed. If, after computing, the + // symbol has no doc comment, then the empty array will be returned. + documentationComment?: SymbolDisplayPart[]; + jsDocTags?: JSDocTagInfo[]; // same + + constructor(checker: TypeChecker, flags: SignatureFlags) { + this.checker = checker; + this.flags = flags; + } + + getDeclaration(): SignatureDeclaration { + return this.declaration; + } + getTypeParameters(): TypeParameter[] | undefined { + return this.typeParameters; + } + getParameters(): Symbol[] { + return this.parameters; + } + getReturnType(): Type { + return this.checker.getReturnTypeOfSignature(this); + } + getTypeParameterAtPosition(pos: number): Type { + const type = this.checker.getParameterType(this, pos); + if (type.isIndexType() && isThisTypeParameter(type.type)) { + const constraint = type.type.getConstraint(); + if (constraint) { + return this.checker.getIndexType(constraint); } - return undefined; } + return type; } - class SignatureObject implements Signature { - flags: SignatureFlags; - checker: TypeChecker; - declaration!: SignatureDeclaration; - typeParameters?: TypeParameter[]; - parameters!: Symbol[]; - thisParameter!: Symbol; - resolvedReturnType!: Type; - resolvedTypePredicate: TypePredicate | undefined; - minTypeArgumentCount!: number; - minArgumentCount!: number; + getDocumentationComment(): SymbolDisplayPart[] { + return this.documentationComment || (this.documentationComment = getDocumentationComment(singleElementArray(this.declaration), this.checker)); + } - // Undefined is used to indicate the value has not been computed. If, after computing, the - // symbol has no doc comment, then the empty array will be returned. - documentationComment?: SymbolDisplayPart[]; - jsDocTags?: JSDocTagInfo[]; // same + getJsDocTags(): JSDocTagInfo[] { + return this.jsDocTags || (this.jsDocTags = getJsDocTagsOfDeclarations(singleElementArray(this.declaration), this.checker)); + } +} - constructor(checker: TypeChecker, flags: SignatureFlags) { - this.checker = checker; - this.flags = flags; - } +/** + * Returns whether or not the given node has a JSDoc "inheritDoc" tag on it. + * @param node the Node in question. + * @returns `true` if `node` has a JSDoc "inheritDoc" tag on it, otherwise `false`. + */ +function hasJSDocInheritDocTag(node: Node) { + return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc" || tag.tagName.text === "inheritdoc"); +} - getDeclaration(): SignatureDeclaration { - return this.declaration; - } - getTypeParameters(): TypeParameter[] | undefined { - return this.typeParameters; - } - getParameters(): Symbol[] { - return this.parameters; - } - getReturnType(): Type { - return this.checker.getReturnTypeOfSignature(this); - } - getTypeParameterAtPosition(pos: number): Type { - const type = this.checker.getParameterType(this, pos); - if (type.isIndexType() && isThisTypeParameter(type.type)) { - const constraint = type.type.getConstraint(); - if (constraint) { - return this.checker.getIndexType(constraint); +function getJsDocTagsOfDeclarations(declarations: Declaration[] | undefined, checker: TypeChecker | undefined): JSDocTagInfo[] { + if (!declarations) return emptyArray; + + let tags = JsDoc.getJsDocTagsFromDeclarations(declarations, checker); + if (checker && (tags.length === 0 || declarations.some(hasJSDocInheritDocTag))) { + const seenSymbols = new Set(); + for (const declaration of declarations) { + const inheritedTags = findBaseOfDeclaration(checker, declaration, symbol => { + if (!seenSymbols.has(symbol)) { + seenSymbols.add(symbol); + if (declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) { + return symbol.getContextualJsDocTags(declaration, checker); + } + return symbol.declarations?.length === 1 ? symbol.getJsDocTags() : undefined; } + }); + if (inheritedTags) { + tags = [...inheritedTags, ...tags]; } - return type; - } - - getDocumentationComment(): SymbolDisplayPart[] { - return this.documentationComment || (this.documentationComment = getDocumentationComment(singleElementArray(this.declaration), this.checker)); - } - - getJsDocTags(): JSDocTagInfo[] { - return this.jsDocTags || (this.jsDocTags = getJsDocTagsOfDeclarations(singleElementArray(this.declaration), this.checker)); } } + return tags; +} - /** - * Returns whether or not the given node has a JSDoc "inheritDoc" tag on it. - * @param node the Node in question. - * @returns `true` if `node` has a JSDoc "inheritDoc" tag on it, otherwise `false`. - */ - function hasJSDocInheritDocTag(node: Node) { - return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc" || tag.tagName.text === "inheritdoc"); - } - - function getJsDocTagsOfDeclarations(declarations: Declaration[] | undefined, checker: TypeChecker | undefined): JSDocTagInfo[] { - if (!declarations) return emptyArray; - - let tags = JsDoc.getJsDocTagsFromDeclarations(declarations, checker); - if (checker && (tags.length === 0 || declarations.some(hasJSDocInheritDocTag))) { - const seenSymbols = new Set(); - for (const declaration of declarations) { - const inheritedTags = findBaseOfDeclaration(checker, declaration, symbol => { - if (!seenSymbols.has(symbol)) { - seenSymbols.add(symbol); - if (declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) { - return symbol.getContextualJsDocTags(declaration, checker); - } - return symbol.declarations?.length === 1 ? symbol.getJsDocTags() : undefined; +function getDocumentationComment(declarations: readonly Declaration[] | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { + if (!declarations) return emptyArray; + + let doc = JsDoc.getJsDocCommentsFromDeclarations(declarations, checker); + if (checker && (doc.length === 0 || declarations.some(hasJSDocInheritDocTag))) { + const seenSymbols = new Set(); + for (const declaration of declarations) { + const inheritedDocs = findBaseOfDeclaration(checker, declaration, symbol => { + if (!seenSymbols.has(symbol)) { + seenSymbols.add(symbol); + if (declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) { + return symbol.getContextualDocumentationComment(declaration, checker); } - }); - if (inheritedTags) { - tags = [...inheritedTags, ...tags]; + return symbol.getDocumentationComment(checker); } - } + }); + // TODO: GH#16312 Return a ReadonlyArray, avoid copying inheritedDocs + if (inheritedDocs) doc = doc.length === 0 ? inheritedDocs.slice() : inheritedDocs.concat(lineBreakPart(), doc); } - return tags; } + return doc; +} - function getDocumentationComment(declarations: readonly Declaration[] | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { - if (!declarations) return emptyArray; +function findBaseOfDeclaration(checker: TypeChecker, declaration: Declaration, cb: (symbol: Symbol) => T[] | undefined): T[] | undefined { + const classOrInterfaceDeclaration = declaration.parent?.kind === SyntaxKind.Constructor ? declaration.parent.parent : declaration.parent; + if (!classOrInterfaceDeclaration) return; + + const isStaticMember = hasStaticModifier(declaration); + return firstDefined(getAllSuperTypeNodes(classOrInterfaceDeclaration), superTypeNode => { + const baseType = checker.getTypeAtLocation(superTypeNode); + const type = isStaticMember && baseType.symbol ? checker.getTypeOfSymbol(baseType.symbol) : baseType; + const symbol = checker.getPropertyOfType(type, declaration.symbol.name); + return symbol ? cb(symbol) : undefined; + }); +} - let doc = JsDoc.getJsDocCommentsFromDeclarations(declarations, checker); - if (checker && (doc.length === 0 || declarations.some(hasJSDocInheritDocTag))) { - const seenSymbols = new Set(); - for (const declaration of declarations) { - const inheritedDocs = findBaseOfDeclaration(checker, declaration, symbol => { - if (!seenSymbols.has(symbol)) { - seenSymbols.add(symbol); - if (declaration.kind === SyntaxKind.GetAccessor || declaration.kind === SyntaxKind.SetAccessor) { - return symbol.getContextualDocumentationComment(declaration, checker); - } - return symbol.getDocumentationComment(checker); - } - }); - // TODO: GH#16312 Return a ReadonlyArray, avoid copying inheritedDocs - if (inheritedDocs) doc = doc.length === 0 ? inheritedDocs.slice() : inheritedDocs.concat(lineBreakPart(), doc); - } - } - return doc; +class SourceFileObject extends NodeObject implements SourceFile { + public kind: SyntaxKind.SourceFile = SyntaxKind.SourceFile; + public _declarationBrand: any; + public fileName!: string; + public path!: Path; + public resolvedPath!: Path; + public originalFileName!: string; + public text!: string; + public scriptSnapshot!: IScriptSnapshot; + public lineMap!: readonly number[]; + + public statements!: NodeArray; + public endOfFileToken!: Token; + + public amdDependencies!: { name: string; path: string }[]; + public moduleName!: string; + public referencedFiles!: FileReference[]; + public typeReferenceDirectives!: FileReference[]; + public libReferenceDirectives!: FileReference[]; + + public syntacticDiagnostics!: DiagnosticWithLocation[]; + public parseDiagnostics!: DiagnosticWithLocation[]; + public bindDiagnostics!: DiagnosticWithLocation[]; + public bindSuggestionDiagnostics?: DiagnosticWithLocation[]; + + public isDeclarationFile!: boolean; + public isDefaultLib!: boolean; + public hasNoDefaultLib!: boolean; + public externalModuleIndicator!: Node; // The first node that causes this file to be an external module + public commonJsModuleIndicator!: Node; // The first node that causes this file to be a CommonJS module + public nodeCount!: number; + public identifierCount!: number; + public symbolCount!: number; + public version!: string; + public scriptKind!: ScriptKind; + public languageVersion!: ScriptTarget; + public languageVariant!: LanguageVariant; + public identifiers!: ESMap; + public nameTable: UnderscoreEscapedMap | undefined; + public resolvedModules: ModeAwareCache | undefined; + public resolvedTypeReferenceDirectiveNames!: ModeAwareCache; + public imports!: readonly StringLiteralLike[]; + public moduleAugmentations!: StringLiteral[]; + private namedDeclarations: ESMap | undefined; + public ambientModuleNames!: string[]; + public checkJsDirective: CheckJsDirective | undefined; + public errorExpectations: TextRange[] | undefined; + public possiblyContainDynamicImport?: boolean; + public pragmas!: PragmaMap; + public localJsxFactory: EntityName | undefined; + public localJsxNamespace: __String | undefined; + + constructor(kind: SyntaxKind, pos: number, end: number) { + super(kind, pos, end); } - function findBaseOfDeclaration(checker: TypeChecker, declaration: Declaration, cb: (symbol: Symbol) => T[] | undefined): T[] | undefined { - const classOrInterfaceDeclaration = declaration.parent?.kind === SyntaxKind.Constructor ? declaration.parent.parent : declaration.parent; - if (!classOrInterfaceDeclaration) return; + public update(newText: string, textChangeRange: TextChangeRange): SourceFile { + return updateSourceFile(this, newText, textChangeRange); + } - const isStaticMember = hasStaticModifier(declaration); - return firstDefined(getAllSuperTypeNodes(classOrInterfaceDeclaration), superTypeNode => { - const baseType = checker.getTypeAtLocation(superTypeNode); - const type = isStaticMember && baseType.symbol ? checker.getTypeOfSymbol(baseType.symbol) : baseType; - const symbol = checker.getPropertyOfType(type, declaration.symbol.name); - return symbol ? cb(symbol) : undefined; - }); + public getLineAndCharacterOfPosition(position: number): LineAndCharacter { + return getLineAndCharacterOfPosition(this, position); } - class SourceFileObject extends NodeObject implements SourceFile { - public kind: SyntaxKind.SourceFile = SyntaxKind.SourceFile; - public _declarationBrand: any; - public fileName!: string; - public path!: Path; - public resolvedPath!: Path; - public originalFileName!: string; - public text!: string; - public scriptSnapshot!: IScriptSnapshot; - public lineMap!: readonly number[]; - - public statements!: NodeArray; - public endOfFileToken!: Token; - - public amdDependencies!: { name: string; path: string }[]; - public moduleName!: string; - public referencedFiles!: FileReference[]; - public typeReferenceDirectives!: FileReference[]; - public libReferenceDirectives!: FileReference[]; - - public syntacticDiagnostics!: DiagnosticWithLocation[]; - public parseDiagnostics!: DiagnosticWithLocation[]; - public bindDiagnostics!: DiagnosticWithLocation[]; - public bindSuggestionDiagnostics?: DiagnosticWithLocation[]; - - public isDeclarationFile!: boolean; - public isDefaultLib!: boolean; - public hasNoDefaultLib!: boolean; - public externalModuleIndicator!: Node; // The first node that causes this file to be an external module - public commonJsModuleIndicator!: Node; // The first node that causes this file to be a CommonJS module - public nodeCount!: number; - public identifierCount!: number; - public symbolCount!: number; - public version!: string; - public scriptKind!: ScriptKind; - public languageVersion!: ScriptTarget; - public languageVariant!: LanguageVariant; - public identifiers!: ESMap; - public nameTable: UnderscoreEscapedMap | undefined; - public resolvedModules: ModeAwareCache | undefined; - public resolvedTypeReferenceDirectiveNames!: ModeAwareCache; - public imports!: readonly StringLiteralLike[]; - public moduleAugmentations!: StringLiteral[]; - private namedDeclarations: ESMap | undefined; - public ambientModuleNames!: string[]; - public checkJsDirective: CheckJsDirective | undefined; - public errorExpectations: TextRange[] | undefined; - public possiblyContainDynamicImport?: boolean; - public pragmas!: PragmaMap; - public localJsxFactory: EntityName | undefined; - public localJsxNamespace: __String | undefined; - - constructor(kind: SyntaxKind, pos: number, end: number) { - super(kind, pos, end); - } - - public update(newText: string, textChangeRange: TextChangeRange): SourceFile { - return updateSourceFile(this, newText, textChangeRange); - } - - public getLineAndCharacterOfPosition(position: number): LineAndCharacter { - return getLineAndCharacterOfPosition(this, position); - } - - public getLineStarts(): readonly number[] { - return getLineStarts(this); - } - - public getPositionOfLineAndCharacter(line: number, character: number, allowEdits?: true): number { - return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text, allowEdits); - } - - public getLineEndOfPosition(pos: number): number { - const { line } = this.getLineAndCharacterOfPosition(pos); - const lineStarts = this.getLineStarts(); - - let lastCharPos: number | undefined; - if (line + 1 >= lineStarts.length) { - lastCharPos = this.getEnd(); - } - if (!lastCharPos) { - lastCharPos = lineStarts[line + 1] - 1; - } + public getLineStarts(): readonly number[] { + return getLineStarts(this); + } + + public getPositionOfLineAndCharacter(line: number, character: number, allowEdits?: true): number { + return computePositionOfLineAndCharacter(getLineStarts(this), line, character, this.text, allowEdits); + } - const fullText = this.getFullText(); - // if the new line is "\r\n", we should return the last non-new-line-character position - return fullText[lastCharPos] === "\n" && fullText[lastCharPos - 1] === "\r" ? lastCharPos - 1 : lastCharPos; + public getLineEndOfPosition(pos: number): number { + const { line } = this.getLineAndCharacterOfPosition(pos); + const lineStarts = this.getLineStarts(); + + let lastCharPos: number | undefined; + if (line + 1 >= lineStarts.length) { + lastCharPos = this.getEnd(); + } + if (!lastCharPos) { + lastCharPos = lineStarts[line + 1] - 1; } - public getNamedDeclarations(): ESMap { - if (!this.namedDeclarations) { - this.namedDeclarations = this.computeNamedDeclarations(); - } + const fullText = this.getFullText(); + // if the new line is "\r\n", we should return the last non-new-line-character position + return fullText[lastCharPos] === "\n" && fullText[lastCharPos - 1] === "\r" ? lastCharPos - 1 : lastCharPos; + } - return this.namedDeclarations; + public getNamedDeclarations(): ESMap { + if (!this.namedDeclarations) { + this.namedDeclarations = this.computeNamedDeclarations(); } - private computeNamedDeclarations(): ESMap { - const result = createMultiMap(); + return this.namedDeclarations; + } - this.forEachChild(visit); + private computeNamedDeclarations(): ESMap { + const result = createMultiMap(); - return result; + this.forEachChild(visit); - function addDeclaration(declaration: Declaration) { - const name = getDeclarationName(declaration); - if (name) { - result.add(name, declaration); - } - } + return result; - function getDeclarations(name: string) { - let declarations = result.get(name); - if (!declarations) { - result.set(name, declarations = []); - } - return declarations; + function addDeclaration(declaration: Declaration) { + const name = getDeclarationName(declaration); + if (name) { + result.add(name, declaration); } + } - function getDeclarationName(declaration: Declaration) { - const name = getNonAssignedNameOfDeclaration(declaration); - return name && (isComputedPropertyName(name) && isPropertyAccessExpression(name.expression) ? name.expression.name.text - : isPropertyName(name) ? getNameFromPropertyName(name) : undefined); + function getDeclarations(name: string) { + let declarations = result.get(name); + if (!declarations) { + result.set(name, declarations = []); } + return declarations; + } - function visit(node: Node): void { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - const functionDeclaration = node as FunctionLikeDeclaration; - const declarationName = getDeclarationName(functionDeclaration); - - if (declarationName) { - const declarations = getDeclarations(declarationName); - const lastDeclaration = lastOrUndefined(declarations); - - // Check whether this declaration belongs to an "overload group". - if (lastDeclaration && functionDeclaration.parent === lastDeclaration.parent && functionDeclaration.symbol === lastDeclaration.symbol) { - // Overwrite the last declaration if it was an overload - // and this one is an implementation. - if (functionDeclaration.body && !(lastDeclaration as FunctionLikeDeclaration).body) { - declarations[declarations.length - 1] = functionDeclaration; - } - } - else { - declarations.push(functionDeclaration); + function getDeclarationName(declaration: Declaration) { + const name = getNonAssignedNameOfDeclaration(declaration); + return name && (isComputedPropertyName(name) && isPropertyAccessExpression(name.expression) ? name.expression.name.text + : isPropertyName(name) ? getNameFromPropertyName(name) : undefined); + } + + function visit(node: Node): void { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + const functionDeclaration = node as FunctionLikeDeclaration; + const declarationName = getDeclarationName(functionDeclaration); + + if (declarationName) { + const declarations = getDeclarations(declarationName); + const lastDeclaration = lastOrUndefined(declarations); + + // Check whether this declaration belongs to an "overload group". + if (lastDeclaration && functionDeclaration.parent === lastDeclaration.parent && functionDeclaration.symbol === lastDeclaration.symbol) { + // Overwrite the last declaration if it was an overload + // and this one is an implementation. + if (functionDeclaration.body && !(lastDeclaration as FunctionLikeDeclaration).body) { + declarations[declarations.length - 1] = functionDeclaration; } } - forEachChild(node, visit); + else { + declarations.push(functionDeclaration); + } + } + forEachChild(node, visit); + break; + + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.TypeLiteral: + addDeclaration(node as Declaration); + forEachChild(node, visit); + break; + + case SyntaxKind.Parameter: + // Only consider parameter properties + if (!hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { break; + } + // falls through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.TypeLiteral: - addDeclaration(node as Declaration); - forEachChild(node, visit); + case SyntaxKind.VariableDeclaration: + case SyntaxKind.BindingElement: { + const decl = node as VariableDeclaration; + if (isBindingPattern(decl.name)) { + forEachChild(decl.name, visit); break; + } + if (decl.initializer) { + visit(decl.initializer); + } + } + // falls through + case SyntaxKind.EnumMember: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + addDeclaration(node as Declaration); + break; - case SyntaxKind.Parameter: - // Only consider parameter properties - if (!hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { - break; + case SyntaxKind.ExportDeclaration: + // Handle named exports case e.g.: + // export {a, b as B} from "mod"; + const exportDeclaration = node as ExportDeclaration; + if (exportDeclaration.exportClause) { + if (isNamedExports(exportDeclaration.exportClause)) { + forEach(exportDeclaration.exportClause.elements, visit); } - // falls through - - case SyntaxKind.VariableDeclaration: - case SyntaxKind.BindingElement: { - const decl = node as VariableDeclaration; - if (isBindingPattern(decl.name)) { - forEachChild(decl.name, visit); - break; - } - if (decl.initializer) { - visit(decl.initializer); + else { + visit(exportDeclaration.exportClause.name); } } - // falls through - case SyntaxKind.EnumMember: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - addDeclaration(node as Declaration); - break; + break; - case SyntaxKind.ExportDeclaration: - // Handle named exports case e.g.: - // export {a, b as B} from "mod"; - const exportDeclaration = node as ExportDeclaration; - if (exportDeclaration.exportClause) { - if (isNamedExports(exportDeclaration.exportClause)) { - forEach(exportDeclaration.exportClause.elements, visit); - } - else { - visit(exportDeclaration.exportClause.name); - } + case SyntaxKind.ImportDeclaration: + const importClause = (node as ImportDeclaration).importClause; + if (importClause) { + // Handle default import case e.g.: + // import d from "mod"; + if (importClause.name) { + addDeclaration(importClause.name); } - break; - case SyntaxKind.ImportDeclaration: - const importClause = (node as ImportDeclaration).importClause; - if (importClause) { - // Handle default import case e.g.: - // import d from "mod"; - if (importClause.name) { - addDeclaration(importClause.name); + // Handle named bindings in imports e.g.: + // import * as NS from "mod"; + // import {a, b as B} from "mod"; + if (importClause.namedBindings) { + if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + addDeclaration(importClause.namedBindings); } - - // Handle named bindings in imports e.g.: - // import * as NS from "mod"; - // import {a, b as B} from "mod"; - if (importClause.namedBindings) { - if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - addDeclaration(importClause.namedBindings); - } - else { - forEach(importClause.namedBindings.elements, visit); - } + else { + forEach(importClause.namedBindings.elements, visit); } } - break; + } + break; - case SyntaxKind.BinaryExpression: - if (getAssignmentDeclarationKind(node as BinaryExpression) !== AssignmentDeclarationKind.None) { - addDeclaration(node as BinaryExpression); - } - // falls through + case SyntaxKind.BinaryExpression: + if (getAssignmentDeclarationKind(node as BinaryExpression) !== AssignmentDeclarationKind.None) { + addDeclaration(node as BinaryExpression); + } + // falls through - default: - forEachChild(node, visit); - } + default: + forEachChild(node, visit); } } } +} - class SourceMapSourceObject implements SourceMapSource { - lineMap!: number[]; - constructor(public fileName: string, public text: string, public skipTrivia?: (pos: number) => number) { } +class SourceMapSourceObject implements SourceMapSource { + lineMap!: number[]; + constructor(public fileName: string, public text: string, public skipTrivia?: (pos: number) => number) { } - public getLineAndCharacterOfPosition(pos: number): LineAndCharacter { - return getLineAndCharacterOfPosition(this, pos); - } + public getLineAndCharacterOfPosition(pos: number): LineAndCharacter { + return getLineAndCharacterOfPosition(this, pos); } +} - function getServicesObjectAllocator(): ObjectAllocator { - return { - getNodeConstructor: () => NodeObject, - getTokenConstructor: () => TokenObject, - - getIdentifierConstructor: () => IdentifierObject, - getPrivateIdentifierConstructor: () => PrivateIdentifierObject, - getSourceFileConstructor: () => SourceFileObject, - getSymbolConstructor: () => SymbolObject, - getTypeConstructor: () => TypeObject, - getSignatureConstructor: () => SignatureObject, - getSourceMapSourceConstructor: () => SourceMapSourceObject, - }; - } +function getServicesObjectAllocator(): ObjectAllocator { + return { + getNodeConstructor: () => NodeObject, + getTokenConstructor: () => TokenObject, + + getIdentifierConstructor: () => IdentifierObject, + getPrivateIdentifierConstructor: () => PrivateIdentifierObject, + getSourceFileConstructor: () => SourceFileObject, + getSymbolConstructor: () => SymbolObject, + getTypeConstructor: () => TypeObject, + getSignatureConstructor: () => SignatureObject, + getSourceMapSourceConstructor: () => SourceMapSourceObject, + }; +} - /// Language Service +/// Language Service - /* @internal */ - export interface DisplayPartsSymbolWriter extends EmitTextWriter { - displayParts(): SymbolDisplayPart[]; - } +/** @internal */ +export interface DisplayPartsSymbolWriter extends EmitTextWriter { + displayParts(): SymbolDisplayPart[]; +} - /* @internal */ - export function toEditorSettings(options: FormatCodeOptions | FormatCodeSettings): FormatCodeSettings; - export function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings; - export function toEditorSettings(optionsAsMap: MapLike): MapLike { - let allPropertiesAreCamelCased = true; - for (const key in optionsAsMap) { - if (hasProperty(optionsAsMap, key) && !isCamelCase(key)) { - allPropertiesAreCamelCased = false; - break; - } - } - if (allPropertiesAreCamelCased) { - return optionsAsMap; +/** @internal */ +export function toEditorSettings(options: FormatCodeOptions | FormatCodeSettings): FormatCodeSettings; +export function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings; +export function toEditorSettings(optionsAsMap: MapLike): MapLike { + let allPropertiesAreCamelCased = true; + for (const key in optionsAsMap) { + if (hasProperty(optionsAsMap, key) && !isCamelCase(key)) { + allPropertiesAreCamelCased = false; + break; } - const settings: MapLike = {}; - for (const key in optionsAsMap) { - if (hasProperty(optionsAsMap, key)) { - const newKey = isCamelCase(key) ? key : key.charAt(0).toLowerCase() + key.substr(1); - settings[newKey] = optionsAsMap[key]; - } - } - return settings; } - - function isCamelCase(s: string) { - return !s.length || s.charAt(0) === s.charAt(0).toLowerCase(); + if (allPropertiesAreCamelCased) { + return optionsAsMap; } - - export function displayPartsToString(displayParts: SymbolDisplayPart[] | undefined) { - if (displayParts) { - return map(displayParts, displayPart => displayPart.text).join(""); + const settings: MapLike = {}; + for (const key in optionsAsMap) { + if (hasProperty(optionsAsMap, key)) { + const newKey = isCamelCase(key) ? key : key.charAt(0).toLowerCase() + key.substr(1); + settings[newKey] = optionsAsMap[key]; } - - return ""; } + return settings; +} - export function getDefaultCompilerOptions(): CompilerOptions { - // Always default to "ScriptTarget.ES5" for the language service - return { - target: ScriptTarget.ES5, - jsx: JsxEmit.Preserve - }; - } +function isCamelCase(s: string) { + return !s.length || s.charAt(0) === s.charAt(0).toLowerCase(); +} - export function getSupportedCodeFixes() { - return codefix.getSupportedErrorCodes(); +export function displayPartsToString(displayParts: SymbolDisplayPart[] | undefined) { + if (displayParts) { + return map(displayParts, displayPart => displayPart.text).join(""); } - class SyntaxTreeCache { - // For our syntactic only features, we also keep a cache of the syntax tree for the - // currently edited file. - private currentFileName: string | undefined; - private currentFileVersion: string | undefined; - private currentFileScriptSnapshot: IScriptSnapshot | undefined; - private currentSourceFile: SourceFile | undefined; + return ""; +} - constructor(private host: LanguageServiceHost) { - } +export function getDefaultCompilerOptions(): CompilerOptions { + // Always default to "ScriptTarget.ES5" for the language service + return { + target: ScriptTarget.ES5, + jsx: JsxEmit.Preserve + }; +} - public getCurrentSourceFile(fileName: string): SourceFile { - const scriptSnapshot = this.host.getScriptSnapshot(fileName); - if (!scriptSnapshot) { - // The host does not know about this file. - throw new Error("Could not find file: '" + fileName + "'."); - } +export function getSupportedCodeFixes() { + return codefix.getSupportedErrorCodes(); +} - const scriptKind = getScriptKind(fileName, this.host); - const version = this.host.getScriptVersion(fileName); - let sourceFile: SourceFile | undefined; - - if (this.currentFileName !== fileName) { - // This is a new file, just parse it - const options: CreateSourceFileOptions = { - languageVersion: ScriptTarget.Latest, - impliedNodeFormat: getImpliedNodeFormatForFile( - toPath(fileName, this.host.getCurrentDirectory(), this.host.getCompilerHost?.()?.getCanonicalFileName || hostGetCanonicalFileName(this.host)), - this.host.getCompilerHost?.()?.getModuleResolutionCache?.()?.getPackageJsonInfoCache(), - this.host, - this.host.getCompilationSettings() - ), - setExternalModuleIndicator: getSetExternalModuleIndicator(this.host.getCompilationSettings()) - }; - sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, options, version, /*setNodeParents*/ true, scriptKind); - } - else if (this.currentFileVersion !== version) { - // This is the same file, just a newer version. Incrementally parse the file. - const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot!); - sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile!, scriptSnapshot, version, editRange); - } +class SyntaxTreeCache { + // For our syntactic only features, we also keep a cache of the syntax tree for the + // currently edited file. + private currentFileName: string | undefined; + private currentFileVersion: string | undefined; + private currentFileScriptSnapshot: IScriptSnapshot | undefined; + private currentSourceFile: SourceFile | undefined; - if (sourceFile) { - // All done, ensure state is up to date - this.currentFileVersion = version; - this.currentFileName = fileName; - this.currentFileScriptSnapshot = scriptSnapshot; - this.currentSourceFile = sourceFile; - } + constructor(private host: LanguageServiceHost) { + } - return this.currentSourceFile!; + public getCurrentSourceFile(fileName: string): SourceFile { + const scriptSnapshot = this.host.getScriptSnapshot(fileName); + if (!scriptSnapshot) { + // The host does not know about this file. + throw new Error("Could not find file: '" + fileName + "'."); + } + + const scriptKind = getScriptKind(fileName, this.host); + const version = this.host.getScriptVersion(fileName); + let sourceFile: SourceFile | undefined; + + if (this.currentFileName !== fileName) { + // This is a new file, just parse it + const options: CreateSourceFileOptions = { + languageVersion: ScriptTarget.Latest, + impliedNodeFormat: getImpliedNodeFormatForFile( + toPath(fileName, this.host.getCurrentDirectory(), this.host.getCompilerHost?.()?.getCanonicalFileName || hostGetCanonicalFileName(this.host)), + this.host.getCompilerHost?.()?.getModuleResolutionCache?.()?.getPackageJsonInfoCache(), + this.host, + this.host.getCompilationSettings() + ), + setExternalModuleIndicator: getSetExternalModuleIndicator(this.host.getCompilationSettings()) + }; + sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, options, version, /*setNodeParents*/ true, scriptKind); + } + else if (this.currentFileVersion !== version) { + // This is the same file, just a newer version. Incrementally parse the file. + const editRange = scriptSnapshot.getChangeRange(this.currentFileScriptSnapshot!); + sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile!, scriptSnapshot, version, editRange); } - } - function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) { - sourceFile.version = version; - sourceFile.scriptSnapshot = scriptSnapshot; - } + if (sourceFile) { + // All done, ensure state is up to date + this.currentFileVersion = version; + this.currentFileName = fileName; + this.currentFileScriptSnapshot = scriptSnapshot; + this.currentSourceFile = sourceFile; + } - export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTargetOrOptions: ScriptTarget | CreateSourceFileOptions, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile { - const sourceFile = createSourceFile(fileName, getSnapshotText(scriptSnapshot), scriptTargetOrOptions, setNodeParents, scriptKind); - setSourceFileFields(sourceFile, scriptSnapshot, version); - return sourceFile; + return this.currentSourceFile!; } +} - export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile { - // If we were given a text change range, and our version or open-ness changed, then - // incrementally parse this file. - if (textChangeRange) { - if (version !== sourceFile.version) { - let newText: string; - - // grab the fragment from the beginning of the original text to the beginning of the span - const prefix = textChangeRange.span.start !== 0 - ? sourceFile.text.substr(0, textChangeRange.span.start) - : ""; +function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string) { + sourceFile.version = version; + sourceFile.scriptSnapshot = scriptSnapshot; +} - // grab the fragment from the end of the span till the end of the original text - const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length - ? sourceFile.text.substr(textSpanEnd(textChangeRange.span)) - : ""; +export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTargetOrOptions: ScriptTarget | CreateSourceFileOptions, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile { + const sourceFile = createSourceFile(fileName, getSnapshotText(scriptSnapshot), scriptTargetOrOptions, setNodeParents, scriptKind); + setSourceFileFields(sourceFile, scriptSnapshot, version); + return sourceFile; +} - if (textChangeRange.newLength === 0) { - // edit was a deletion - just combine prefix and suffix - newText = prefix && suffix ? prefix + suffix : prefix || suffix; - } - else { - // it was actual edit, fetch the fragment of new text that correspond to new span - const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength); - // combine prefix, changed text and suffix - newText = prefix && suffix - ? prefix + changedText + suffix - : prefix - ? (prefix + changedText) - : (changedText + suffix); - } +export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile { + // If we were given a text change range, and our version or open-ness changed, then + // incrementally parse this file. + if (textChangeRange) { + if (version !== sourceFile.version) { + let newText: string; - const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); - setSourceFileFields(newSourceFile, scriptSnapshot, version); - // after incremental parsing nameTable might not be up-to-date - // drop it so it can be lazily recreated later - newSourceFile.nameTable = undefined; + // grab the fragment from the beginning of the original text to the beginning of the span + const prefix = textChangeRange.span.start !== 0 + ? sourceFile.text.substr(0, textChangeRange.span.start) + : ""; - // dispose all resources held by old script snapshot - if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) { - if (sourceFile.scriptSnapshot.dispose) { - sourceFile.scriptSnapshot.dispose(); - } + // grab the fragment from the end of the span till the end of the original text + const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length + ? sourceFile.text.substr(textSpanEnd(textChangeRange.span)) + : ""; - sourceFile.scriptSnapshot = undefined; + if (textChangeRange.newLength === 0) { + // edit was a deletion - just combine prefix and suffix + newText = prefix && suffix ? prefix + suffix : prefix || suffix; + } + else { + // it was actual edit, fetch the fragment of new text that correspond to new span + const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength); + // combine prefix, changed text and suffix + newText = prefix && suffix + ? prefix + changedText + suffix + : prefix + ? (prefix + changedText) + : (changedText + suffix); + } + + const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); + setSourceFileFields(newSourceFile, scriptSnapshot, version); + // after incremental parsing nameTable might not be up-to-date + // drop it so it can be lazily recreated later + newSourceFile.nameTable = undefined; + + // dispose all resources held by old script snapshot + if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) { + if (sourceFile.scriptSnapshot.dispose) { + sourceFile.scriptSnapshot.dispose(); } - return newSourceFile; + sourceFile.scriptSnapshot = undefined; } - } - const options: CreateSourceFileOptions = { - languageVersion: sourceFile.languageVersion, - impliedNodeFormat: sourceFile.impliedNodeFormat, - setExternalModuleIndicator: sourceFile.setExternalModuleIndicator, - }; - // Otherwise, just create a new source file. - return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, options, version, /*setNodeParents*/ true, sourceFile.scriptKind); + return newSourceFile; + } } - const NoopCancellationToken: CancellationToken = { - isCancellationRequested: returnFalse, - throwIfCancellationRequested: noop, + const options: CreateSourceFileOptions = { + languageVersion: sourceFile.languageVersion, + impliedNodeFormat: sourceFile.impliedNodeFormat, + setExternalModuleIndicator: sourceFile.setExternalModuleIndicator, }; + // Otherwise, just create a new source file. + return createLanguageServiceSourceFile(sourceFile.fileName, scriptSnapshot, options, version, /*setNodeParents*/ true, sourceFile.scriptKind); +} - class CancellationTokenObject implements CancellationToken { - constructor(private cancellationToken: HostCancellationToken) { - } - - public isCancellationRequested(): boolean { - return this.cancellationToken.isCancellationRequested(); - } +const NoopCancellationToken: CancellationToken = { + isCancellationRequested: returnFalse, + throwIfCancellationRequested: noop, +}; - public throwIfCancellationRequested(): void { - if (this.isCancellationRequested()) { - tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "CancellationTokenObject" }); - throw new OperationCanceledException(); - } - } +class CancellationTokenObject implements CancellationToken { + constructor(private cancellationToken: HostCancellationToken) { } - /* @internal */ - /** A cancellation that throttles calls to the host */ - export class ThrottledCancellationToken implements CancellationToken { - // Store when we last tried to cancel. Checking cancellation can be expensive (as we have - // to marshall over to the host layer). So we only bother actually checking once enough - // time has passed. - private lastCancellationCheckTime = 0; + public isCancellationRequested(): boolean { + return this.cancellationToken.isCancellationRequested(); + } - constructor(private hostCancellationToken: HostCancellationToken, private readonly throttleWaitMilliseconds = 20) { + public throwIfCancellationRequested(): void { + if (this.isCancellationRequested()) { + tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "CancellationTokenObject" }); + throw new OperationCanceledException(); } + } +} - public isCancellationRequested(): boolean { - const time = timestamp(); - const duration = Math.abs(time - this.lastCancellationCheckTime); - if (duration >= this.throttleWaitMilliseconds) { - // Check no more than once every throttle wait milliseconds - this.lastCancellationCheckTime = time; - return this.hostCancellationToken.isCancellationRequested(); - } +/** + * A cancellation that throttles calls to the host + * + * @internal + */ +export class ThrottledCancellationToken implements CancellationToken { + // Store when we last tried to cancel. Checking cancellation can be expensive (as we have + // to marshall over to the host layer). So we only bother actually checking once enough + // time has passed. + private lastCancellationCheckTime = 0; + + constructor(private hostCancellationToken: HostCancellationToken, private readonly throttleWaitMilliseconds = 20) { + } - return false; + public isCancellationRequested(): boolean { + const time = timestamp(); + const duration = Math.abs(time - this.lastCancellationCheckTime); + if (duration >= this.throttleWaitMilliseconds) { + // Check no more than once every throttle wait milliseconds + this.lastCancellationCheckTime = time; + return this.hostCancellationToken.isCancellationRequested(); } - public throwIfCancellationRequested(): void { - if (this.isCancellationRequested()) { - tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "ThrottledCancellationToken" }); - throw new OperationCanceledException(); - } - } + return false; } - const invalidOperationsInPartialSemanticMode: readonly (keyof LanguageService)[] = [ - "getSemanticDiagnostics", - "getSuggestionDiagnostics", - "getCompilerOptionsDiagnostics", - "getSemanticClassifications", - "getEncodedSemanticClassifications", - "getCodeFixesAtPosition", - "getCombinedCodeFix", - "applyCodeActionCommand", - "organizeImports", - "getEditsForFileRename", - "getEmitOutput", - "getApplicableRefactors", - "getEditsForRefactor", - "prepareCallHierarchy", - "provideCallHierarchyIncomingCalls", - "provideCallHierarchyOutgoingCalls", - "provideInlayHints" - ]; - - const invalidOperationsInSyntacticMode: readonly (keyof LanguageService)[] = [ - ...invalidOperationsInPartialSemanticMode, - "getCompletionsAtPosition", - "getCompletionEntryDetails", - "getCompletionEntrySymbol", - "getSignatureHelpItems", - "getQuickInfoAtPosition", - "getDefinitionAtPosition", - "getDefinitionAndBoundSpan", - "getImplementationAtPosition", - "getTypeDefinitionAtPosition", - "getReferencesAtPosition", - "findReferences", - "getOccurrencesAtPosition", - "getDocumentHighlights", - "getNavigateToItems", - "getRenameInfo", - "findRenameLocations", - "getApplicableRefactors", - ]; - export function createLanguageService( - host: LanguageServiceHost, - documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()), - syntaxOnlyOrLanguageServiceMode?: boolean | LanguageServiceMode, - ): LanguageService { - let languageServiceMode: LanguageServiceMode; - if (syntaxOnlyOrLanguageServiceMode === undefined) { - languageServiceMode = LanguageServiceMode.Semantic; - } - else if (typeof syntaxOnlyOrLanguageServiceMode === "boolean") { - // languageServiceMode = SyntaxOnly - languageServiceMode = syntaxOnlyOrLanguageServiceMode ? LanguageServiceMode.Syntactic : LanguageServiceMode.Semantic; - } - else { - languageServiceMode = syntaxOnlyOrLanguageServiceMode; + public throwIfCancellationRequested(): void { + if (this.isCancellationRequested()) { + tracing?.instant(tracing.Phase.Session, "cancellationThrown", { kind: "ThrottledCancellationToken" }); + throw new OperationCanceledException(); } + } +} + +const invalidOperationsInPartialSemanticMode: readonly (keyof LanguageService)[] = [ + "getSemanticDiagnostics", + "getSuggestionDiagnostics", + "getCompilerOptionsDiagnostics", + "getSemanticClassifications", + "getEncodedSemanticClassifications", + "getCodeFixesAtPosition", + "getCombinedCodeFix", + "applyCodeActionCommand", + "organizeImports", + "getEditsForFileRename", + "getEmitOutput", + "getApplicableRefactors", + "getEditsForRefactor", + "prepareCallHierarchy", + "provideCallHierarchyIncomingCalls", + "provideCallHierarchyOutgoingCalls", + "provideInlayHints" +]; + +const invalidOperationsInSyntacticMode: readonly (keyof LanguageService)[] = [ + ...invalidOperationsInPartialSemanticMode, + "getCompletionsAtPosition", + "getCompletionEntryDetails", + "getCompletionEntrySymbol", + "getSignatureHelpItems", + "getQuickInfoAtPosition", + "getDefinitionAtPosition", + "getDefinitionAndBoundSpan", + "getImplementationAtPosition", + "getTypeDefinitionAtPosition", + "getReferencesAtPosition", + "findReferences", + "getOccurrencesAtPosition", + "getDocumentHighlights", + "getNavigateToItems", + "getRenameInfo", + "findRenameLocations", + "getApplicableRefactors", +]; +export function createLanguageService( + host: LanguageServiceHost, + documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()), + syntaxOnlyOrLanguageServiceMode?: boolean | LanguageServiceMode, +): LanguageService { + let languageServiceMode: LanguageServiceMode; + if (syntaxOnlyOrLanguageServiceMode === undefined) { + languageServiceMode = LanguageServiceMode.Semantic; + } + else if (typeof syntaxOnlyOrLanguageServiceMode === "boolean") { + // languageServiceMode = SyntaxOnly + languageServiceMode = syntaxOnlyOrLanguageServiceMode ? LanguageServiceMode.Syntactic : LanguageServiceMode.Semantic; + } + else { + languageServiceMode = syntaxOnlyOrLanguageServiceMode; + } - const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); - let program: Program; - let lastProjectVersion: string; - let lastTypesRootVersion = 0; + const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); + let program: Program; + let lastProjectVersion: string; + let lastTypesRootVersion = 0; - const cancellationToken = host.getCancellationToken - ? new CancellationTokenObject(host.getCancellationToken()) - : NoopCancellationToken; + const cancellationToken = host.getCancellationToken + ? new CancellationTokenObject(host.getCancellationToken()) + : NoopCancellationToken; - const currentDirectory = host.getCurrentDirectory(); + const currentDirectory = host.getCurrentDirectory(); - // Checks if the localized messages json is set, and if not, query the host for it - maybeSetLocalizedDiagnosticMessages(host.getLocalizedDiagnosticMessages?.bind(host)); + // Checks if the localized messages json is set, and if not, query the host for it + maybeSetLocalizedDiagnosticMessages(host.getLocalizedDiagnosticMessages?.bind(host)); - function log(message: string) { - if (host.log) { - host.log(message); - } + function log(message: string) { + if (host.log) { + host.log(message); } + } - const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - const sourceMapper = getSourceMapper({ - useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, - getCurrentDirectory: () => currentDirectory, - getProgram, - fileExists: maybeBind(host, host.fileExists), - readFile: maybeBind(host, host.readFile), - getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper), - getSourceFileLike: maybeBind(host, host.getSourceFileLike), - log - }); + const sourceMapper = getSourceMapper({ + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, + getCurrentDirectory: () => currentDirectory, + getProgram, + fileExists: maybeBind(host, host.fileExists), + readFile: maybeBind(host, host.readFile), + getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper), + getSourceFileLike: maybeBind(host, host.getSourceFileLike), + log + }); - function getValidSourceFile(fileName: string): SourceFile { - const sourceFile = program.getSourceFile(fileName); - if (!sourceFile) { - const error: Error & PossibleProgramFileInfo = new Error(`Could not find source file: '${fileName}'.`); + function getValidSourceFile(fileName: string): SourceFile { + const sourceFile = program.getSourceFile(fileName); + if (!sourceFile) { + const error: Error & PossibleProgramFileInfo = new Error(`Could not find source file: '${fileName}'.`); - // We've been having trouble debugging this, so attach sidecar data for the tsserver log. - // See https://github.com/microsoft/TypeScript/issues/30180. - error.ProgramFiles = program.getSourceFiles().map(f => f.fileName); + // We've been having trouble debugging this, so attach sidecar data for the tsserver log. + // See https://github.com/microsoft/TypeScript/issues/30180. + error.ProgramFiles = program.getSourceFiles().map(f => f.fileName); - throw error; - } - return sourceFile; + throw error; } + return sourceFile; + } - function synchronizeHostData(): void { - Debug.assert(languageServiceMode !== LanguageServiceMode.Syntactic); - // perform fast check if host supports it - if (host.getProjectVersion) { - const hostProjectVersion = host.getProjectVersion(); - if (hostProjectVersion) { - if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames?.()) { - return; - } - - lastProjectVersion = hostProjectVersion; + function synchronizeHostData(): void { + Debug.assert(languageServiceMode !== LanguageServiceMode.Syntactic); + // perform fast check if host supports it + if (host.getProjectVersion) { + const hostProjectVersion = host.getProjectVersion(); + if (hostProjectVersion) { + if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames?.()) { + return; } - } - const typeRootsVersion = host.getTypeRootsVersion ? host.getTypeRootsVersion() : 0; - if (lastTypesRootVersion !== typeRootsVersion) { - log("TypeRoots version has changed; provide new program"); - program = undefined!; // TODO: GH#18217 - lastTypesRootVersion = typeRootsVersion; + lastProjectVersion = hostProjectVersion; } + } - // This array is retained by the program and will be used to determine if the program is up to date, - // so we need to make a copy in case the host mutates the underlying array - otherwise it would look - // like every program always has the host's current list of root files. - const rootFileNames = host.getScriptFileNames().slice(); - - // Get a fresh cache of the host information - const newSettings = host.getCompilationSettings() || getDefaultCompilerOptions(); - const hasInvalidatedResolutions: HasInvalidatedResolutions = host.hasInvalidatedResolutions || returnFalse; - const hasChangedAutomaticTypeDirectiveNames = maybeBind(host, host.hasChangedAutomaticTypeDirectiveNames); - const projectReferences = host.getProjectReferences?.(); - let parsedCommandLines: ESMap | undefined; - - // Now create a new compiler - let compilerHost: CompilerHost | undefined = { - getSourceFile: getOrCreateSourceFile, - getSourceFileByPath: getOrCreateSourceFileByPath, - getCancellationToken: () => cancellationToken, - getCanonicalFileName, - useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, - getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)), - getDefaultLibFileName: options => host.getDefaultLibFileName(options), - writeFile: noop, - getCurrentDirectory: () => currentDirectory, - fileExists: fileName => host.fileExists(fileName), - readFile: fileName => host.readFile && host.readFile(fileName), - getSymlinkCache: maybeBind(host, host.getSymlinkCache), - realpath: maybeBind(host, host.realpath), - directoryExists: directoryName => { - return directoryProbablyExists(directoryName, host); - }, - getDirectories: path => { - return host.getDirectories ? host.getDirectories(path) : []; - }, - readDirectory: (path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number) => { - Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'"); - return host.readDirectory!(path, extensions, exclude, include, depth); - }, - onReleaseOldSourceFile, - onReleaseParsedCommandLine, - hasInvalidatedResolutions, - hasChangedAutomaticTypeDirectiveNames, - trace: maybeBind(host, host.trace), - resolveModuleNames: maybeBind(host, host.resolveModuleNames), - getModuleResolutionCache: maybeBind(host, host.getModuleResolutionCache), - resolveTypeReferenceDirectives: maybeBind(host, host.resolveTypeReferenceDirectives), - useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect), - getParsedCommandLine, - }; - - const originalGetSourceFile = compilerHost.getSourceFile; - - const { getSourceFileWithCache } = changeCompilerHostLikeToUseCache( - compilerHost, - fileName => toPath(fileName, currentDirectory, getCanonicalFileName), - (...args) => originalGetSourceFile.call(compilerHost, ...args) - ); - compilerHost.getSourceFile = getSourceFileWithCache!; - - host.setCompilerHost?.(compilerHost); - - const parseConfigHost: ParseConfigFileHost = { - useCaseSensitiveFileNames, - fileExists: fileName => compilerHost!.fileExists(fileName), - readFile: fileName => compilerHost!.readFile(fileName), - readDirectory: (...args) => compilerHost!.readDirectory!(...args), - trace: compilerHost.trace, - getCurrentDirectory: compilerHost.getCurrentDirectory, - onUnRecoverableConfigFileDiagnostic: noop, - }; - - // The call to isProgramUptoDate below may refer back to documentRegistryBucketKey; - // calculate this early so it's not undefined if downleveled to a var (or, if emitted - // as a const variable without downleveling, doesn't crash). - const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); + const typeRootsVersion = host.getTypeRootsVersion ? host.getTypeRootsVersion() : 0; + if (lastTypesRootVersion !== typeRootsVersion) { + log("TypeRoots version has changed; provide new program"); + program = undefined!; // TODO: GH#18217 + lastTypesRootVersion = typeRootsVersion; + } + + // This array is retained by the program and will be used to determine if the program is up to date, + // so we need to make a copy in case the host mutates the underlying array - otherwise it would look + // like every program always has the host's current list of root files. + const rootFileNames = host.getScriptFileNames().slice(); + + // Get a fresh cache of the host information + const newSettings = host.getCompilationSettings() || getDefaultCompilerOptions(); + const hasInvalidatedResolutions: HasInvalidatedResolutions = host.hasInvalidatedResolutions || returnFalse; + const hasChangedAutomaticTypeDirectiveNames = maybeBind(host, host.hasChangedAutomaticTypeDirectiveNames); + const projectReferences = host.getProjectReferences?.(); + let parsedCommandLines: ESMap | undefined; + + // Now create a new compiler + let compilerHost: CompilerHost | undefined = { + getSourceFile: getOrCreateSourceFile, + getSourceFileByPath: getOrCreateSourceFileByPath, + getCancellationToken: () => cancellationToken, + getCanonicalFileName, + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, + getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)), + getDefaultLibFileName: options => host.getDefaultLibFileName(options), + writeFile: noop, + getCurrentDirectory: () => currentDirectory, + fileExists: fileName => host.fileExists(fileName), + readFile: fileName => host.readFile && host.readFile(fileName), + getSymlinkCache: maybeBind(host, host.getSymlinkCache), + realpath: maybeBind(host, host.realpath), + directoryExists: directoryName => { + return directoryProbablyExists(directoryName, host); + }, + getDirectories: path => { + return host.getDirectories ? host.getDirectories(path) : []; + }, + readDirectory: (path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number) => { + Debug.checkDefined(host.readDirectory, "'LanguageServiceHost.readDirectory' must be implemented to correctly process 'projectReferences'"); + return host.readDirectory!(path, extensions, exclude, include, depth); + }, + onReleaseOldSourceFile, + onReleaseParsedCommandLine, + hasInvalidatedResolutions, + hasChangedAutomaticTypeDirectiveNames, + trace: maybeBind(host, host.trace), + resolveModuleNames: maybeBind(host, host.resolveModuleNames), + getModuleResolutionCache: maybeBind(host, host.getModuleResolutionCache), + resolveTypeReferenceDirectives: maybeBind(host, host.resolveTypeReferenceDirectives), + useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect), + getParsedCommandLine, + }; - // If the program is already up-to-date, we can reuse it - if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileName => compilerHost!.fileExists(fileName), hasInvalidatedResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { - return; - } + const originalGetSourceFile = compilerHost.getSourceFile; + + const { getSourceFileWithCache } = changeCompilerHostLikeToUseCache( + compilerHost, + fileName => toPath(fileName, currentDirectory, getCanonicalFileName), + (...args) => originalGetSourceFile.call(compilerHost, ...args) + ); + compilerHost.getSourceFile = getSourceFileWithCache!; + + host.setCompilerHost?.(compilerHost); + + const parseConfigHost: ParseConfigFileHost = { + useCaseSensitiveFileNames, + fileExists: fileName => compilerHost!.fileExists(fileName), + readFile: fileName => compilerHost!.readFile(fileName), + readDirectory: (...args) => compilerHost!.readDirectory!(...args), + trace: compilerHost.trace, + getCurrentDirectory: compilerHost.getCurrentDirectory, + onUnRecoverableConfigFileDiagnostic: noop, + }; - // IMPORTANT - It is critical from this moment onward that we do not check - // cancellation tokens. We are about to mutate source files from a previous program - // instance. If we cancel midway through, we may end up in an inconsistent state where - // the program points to old source files that have been invalidated because of - // incremental parsing. - - const options: CreateProgramOptions = { - rootNames: rootFileNames, - options: newSettings, - host: compilerHost, - oldProgram: program, - projectReferences - }; - program = createProgram(options); + // The call to isProgramUptoDate below may refer back to documentRegistryBucketKey; + // calculate this early so it's not undefined if downleveled to a var (or, if emitted + // as a const variable without downleveling, doesn't crash). + const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); - // 'getOrCreateSourceFile' depends on caching but should be used past this point. - // After this point, the cache needs to be cleared to allow all collected snapshots to be released - compilerHost = undefined; - parsedCommandLines = undefined; + // If the program is already up-to-date, we can reuse it + if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileName => compilerHost!.fileExists(fileName), hasInvalidatedResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { + return; + } - // We reset this cache on structure invalidation so we don't hold on to outdated files for long; however we can't use the `compilerHost` above, - // Because it only functions until `hostCache` is cleared, while we'll potentially need the functionality to lazily read sourcemap files during - // the course of whatever called `synchronizeHostData` - sourceMapper.clearCache(); + // IMPORTANT - It is critical from this moment onward that we do not check + // cancellation tokens. We are about to mutate source files from a previous program + // instance. If we cancel midway through, we may end up in an inconsistent state where + // the program points to old source files that have been invalidated because of + // incremental parsing. - // Make sure all the nodes in the program are both bound, and have their parent - // pointers set property. - program.getTypeChecker(); - return; + const options: CreateProgramOptions = { + rootNames: rootFileNames, + options: newSettings, + host: compilerHost, + oldProgram: program, + projectReferences + }; + program = createProgram(options); + + // 'getOrCreateSourceFile' depends on caching but should be used past this point. + // After this point, the cache needs to be cleared to allow all collected snapshots to be released + compilerHost = undefined; + parsedCommandLines = undefined; + + // We reset this cache on structure invalidation so we don't hold on to outdated files for long; however we can't use the `compilerHost` above, + // Because it only functions until `hostCache` is cleared, while we'll potentially need the functionality to lazily read sourcemap files during + // the course of whatever called `synchronizeHostData` + sourceMapper.clearCache(); + + // Make sure all the nodes in the program are both bound, and have their parent + // pointers set property. + program.getTypeChecker(); + return; + + function getParsedCommandLine(fileName: string): ParsedCommandLine | undefined { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const existing = parsedCommandLines?.get(path); + if (existing !== undefined) return existing || undefined; + + const result = host.getParsedCommandLine ? + host.getParsedCommandLine(fileName) : + getParsedCommandLineOfConfigFileUsingSourceFile(fileName); + (parsedCommandLines ||= new Map()).set(path, result || false); + return result; + } - function getParsedCommandLine(fileName: string): ParsedCommandLine | undefined { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const existing = parsedCommandLines?.get(path); - if (existing !== undefined) return existing || undefined; + function getParsedCommandLineOfConfigFileUsingSourceFile(configFileName: string): ParsedCommandLine | undefined { + const result = getOrCreateSourceFile(configFileName, ScriptTarget.JSON) as JsonSourceFile | undefined; + if (!result) return undefined; + result.path = toPath(configFileName, currentDirectory, getCanonicalFileName); + result.resolvedPath = result.path; + result.originalFileName = result.fileName; + return parseJsonSourceFileConfigFileContent( + result, + parseConfigHost, + getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), + /*optionsToExtend*/ undefined, + getNormalizedAbsolutePath(configFileName, currentDirectory), + ); + } - const result = host.getParsedCommandLine ? - host.getParsedCommandLine(fileName) : - getParsedCommandLineOfConfigFileUsingSourceFile(fileName); - (parsedCommandLines ||= new Map()).set(path, result || false); - return result; + function onReleaseParsedCommandLine(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, oldOptions: CompilerOptions) { + if (host.getParsedCommandLine) { + host.onReleaseParsedCommandLine?.(configFileName, oldResolvedRef, oldOptions); } - - function getParsedCommandLineOfConfigFileUsingSourceFile(configFileName: string): ParsedCommandLine | undefined { - const result = getOrCreateSourceFile(configFileName, ScriptTarget.JSON) as JsonSourceFile | undefined; - if (!result) return undefined; - result.path = toPath(configFileName, currentDirectory, getCanonicalFileName); - result.resolvedPath = result.path; - result.originalFileName = result.fileName; - return parseJsonSourceFileConfigFileContent( - result, - parseConfigHost, - getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), - /*optionsToExtend*/ undefined, - getNormalizedAbsolutePath(configFileName, currentDirectory), - ); + else if (oldResolvedRef) { + onReleaseOldSourceFile(oldResolvedRef.sourceFile, oldOptions); } + } - function onReleaseParsedCommandLine(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, oldOptions: CompilerOptions) { - if (host.getParsedCommandLine) { - host.onReleaseParsedCommandLine?.(configFileName, oldResolvedRef, oldOptions); - } - else if (oldResolvedRef) { - onReleaseOldSourceFile(oldResolvedRef.sourceFile, oldOptions); - } - } + // Release any files we have acquired in the old program but are + // not part of the new program. + function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { + const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions); + documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey, oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat); + } - // Release any files we have acquired in the old program but are - // not part of the new program. - function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { - const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions); - documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, oldSettingsKey, oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat); - } + function getOrCreateSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { + return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersionOrOptions, onError, shouldCreateNewSourceFile); + } - function getOrCreateSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { - return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersionOrOptions, onError, shouldCreateNewSourceFile); + function getOrCreateSourceFileByPath(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { + Debug.assert(compilerHost, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host."); + // The program is asking for this file, check first if the host can locate it. + // If the host can not locate the file, then it does not exist. return undefined + // to the program to allow reporting of errors for missing files. + const scriptSnapshot = host.getScriptSnapshot(fileName); + if (!scriptSnapshot) { + return undefined; } - function getOrCreateSourceFileByPath(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { - Debug.assert(compilerHost, "getOrCreateSourceFileByPath called after typical CompilerHost lifetime, check the callstack something with a reference to an old host."); - // The program is asking for this file, check first if the host can locate it. - // If the host can not locate the file, then it does not exist. return undefined - // to the program to allow reporting of errors for missing files. - const scriptSnapshot = host.getScriptSnapshot(fileName); - if (!scriptSnapshot) { - return undefined; - } + const scriptKind = getScriptKind(fileName, host); + const scriptVersion = host.getScriptVersion(fileName); - const scriptKind = getScriptKind(fileName, host); - const scriptVersion = host.getScriptVersion(fileName); - - // Check if the language version has changed since we last created a program; if they are the same, - // it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile - // can not be reused. we have to dump all syntax trees and create new ones. - if (!shouldCreateNewSourceFile) { - // Check if the old program had this file already - const oldSourceFile = program && program.getSourceFileByPath(path); - if (oldSourceFile) { - // We already had a source file for this file name. Go to the registry to - // ensure that we get the right up to date version of it. We need this to - // address the following race-condition. Specifically, say we have the following: - // - // LS1 - // \ - // DocumentRegistry - // / - // LS2 - // - // Each LS has a reference to file 'foo.ts' at version 1. LS2 then updates - // it's version of 'foo.ts' to version 2. This will cause LS2 and the - // DocumentRegistry to have version 2 of the document. However, LS1 will - // have version 1. And *importantly* this source file will be *corrupt*. - // The act of creating version 2 of the file irrevocably damages the version - // 1 file. - // - // So, later when we call into LS1, we need to make sure that it doesn't use - // it's source file any more, and instead defers to DocumentRegistry to get - // either version 1, version 2 (or some other version) depending on what the - // host says should be used. - - // We do not support the scenario where a host can modify a registered - // file's script kind, i.e. in one project some file is treated as ".ts" - // and in another as ".js" - if (scriptKind === oldSourceFile.scriptKind) { - return documentRegistry.updateDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind, languageVersionOrOptions); - } - else { - // Release old source file and fall through to aquire new file with new script kind - documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()), oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat); - } + // Check if the language version has changed since we last created a program; if they are the same, + // it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile + // can not be reused. we have to dump all syntax trees and create new ones. + if (!shouldCreateNewSourceFile) { + // Check if the old program had this file already + const oldSourceFile = program && program.getSourceFileByPath(path); + if (oldSourceFile) { + // We already had a source file for this file name. Go to the registry to + // ensure that we get the right up to date version of it. We need this to + // address the following race-condition. Specifically, say we have the following: + // + // LS1 + // \ + // DocumentRegistry + // / + // LS2 + // + // Each LS has a reference to file 'foo.ts' at version 1. LS2 then updates + // it's version of 'foo.ts' to version 2. This will cause LS2 and the + // DocumentRegistry to have version 2 of the document. However, LS1 will + // have version 1. And *importantly* this source file will be *corrupt*. + // The act of creating version 2 of the file irrevocably damages the version + // 1 file. + // + // So, later when we call into LS1, we need to make sure that it doesn't use + // it's source file any more, and instead defers to DocumentRegistry to get + // either version 1, version 2 (or some other version) depending on what the + // host says should be used. + + // We do not support the scenario where a host can modify a registered + // file's script kind, i.e. in one project some file is treated as ".ts" + // and in another as ".js" + if (scriptKind === oldSourceFile.scriptKind) { + return documentRegistry.updateDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind, languageVersionOrOptions); + } + else { + // Release old source file and fall through to aquire new file with new script kind + documentRegistry.releaseDocumentWithKey(oldSourceFile.resolvedPath, documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()), oldSourceFile.scriptKind, oldSourceFile.impliedNodeFormat); } - - // We didn't already have the file. Fall through and acquire it from the registry. } - // Could not find this file in the old program, create a new SourceFile for it. - return documentRegistry.acquireDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind, languageVersionOrOptions); - } - } - - // TODO: GH#18217 frequently asserted as defined - function getProgram(): Program | undefined { - if (languageServiceMode === LanguageServiceMode.Syntactic) { - Debug.assert(program === undefined); - return undefined; + // We didn't already have the file. Fall through and acquire it from the registry. } - synchronizeHostData(); - - return program; + // Could not find this file in the old program, create a new SourceFile for it. + return documentRegistry.acquireDocumentWithKey(fileName, path, host, documentRegistryBucketKey, scriptSnapshot, scriptVersion, scriptKind, languageVersionOrOptions); } + } - function getAutoImportProvider(): Program | undefined { - return host.getPackageJsonAutoImportProvider?.(); + // TODO: GH#18217 frequently asserted as defined + function getProgram(): Program | undefined { + if (languageServiceMode === LanguageServiceMode.Syntactic) { + Debug.assert(program === undefined); + return undefined; } - function updateIsDefinitionOfReferencedSymbols(referencedSymbols: readonly ReferencedSymbol[], knownSymbolSpans: Set): boolean { - const checker = program.getTypeChecker(); - const symbol = getSymbolForProgram(); + synchronizeHostData(); + + return program; + } - if (!symbol) return false; + function getAutoImportProvider(): Program | undefined { + return host.getPackageJsonAutoImportProvider?.(); + } - for (const referencedSymbol of referencedSymbols) { - for (const ref of referencedSymbol.references) { - const refNode = getNodeForSpan(ref); - Debug.assertIsDefined(refNode); - if (knownSymbolSpans.has(ref) || FindAllReferences.isDeclarationOfSymbol(refNode, symbol)) { - knownSymbolSpans.add(ref); - ref.isDefinition = true; - const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists)); - if (mappedSpan) { - knownSymbolSpans.add(mappedSpan); - } - } - else { - ref.isDefinition = false; + function updateIsDefinitionOfReferencedSymbols(referencedSymbols: readonly ReferencedSymbol[], knownSymbolSpans: Set): boolean { + const checker = program.getTypeChecker(); + const symbol = getSymbolForProgram(); + + if (!symbol) return false; + + for (const referencedSymbol of referencedSymbols) { + for (const ref of referencedSymbol.references) { + const refNode = getNodeForSpan(ref); + Debug.assertIsDefined(refNode); + if (knownSymbolSpans.has(ref) || FindAllReferences.isDeclarationOfSymbol(refNode, symbol)) { + knownSymbolSpans.add(ref); + ref.isDefinition = true; + const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists)); + if (mappedSpan) { + knownSymbolSpans.add(mappedSpan); } } + else { + ref.isDefinition = false; + } } + } - return true; + return true; - function getSymbolForProgram(): Symbol | undefined { - for (const referencedSymbol of referencedSymbols) { - for (const ref of referencedSymbol.references) { - if (knownSymbolSpans.has(ref)) { - const refNode = getNodeForSpan(ref); - Debug.assertIsDefined(refNode); + function getSymbolForProgram(): Symbol | undefined { + for (const referencedSymbol of referencedSymbols) { + for (const ref of referencedSymbol.references) { + if (knownSymbolSpans.has(ref)) { + const refNode = getNodeForSpan(ref); + Debug.assertIsDefined(refNode); + return checker.getSymbolAtLocation(refNode); + } + const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists)); + if (mappedSpan && knownSymbolSpans.has(mappedSpan)) { + const refNode = getNodeForSpan(mappedSpan); + if (refNode) { return checker.getSymbolAtLocation(refNode); } - const mappedSpan = getMappedDocumentSpan(ref, sourceMapper, maybeBind(host, host.fileExists)); - if (mappedSpan && knownSymbolSpans.has(mappedSpan)) { - const refNode = getNodeForSpan(mappedSpan); - if (refNode) { - return checker.getSymbolAtLocation(refNode); - } - } } } - - return undefined; - } - - function getNodeForSpan(docSpan: DocumentSpan): Node | undefined { - const sourceFile = program.getSourceFile(docSpan.fileName); - if (!sourceFile) return undefined; - const rawNode = getTouchingPropertyName(sourceFile, docSpan.textSpan.start); - const adjustedNode = FindAllReferences.Core.getAdjustedNode(rawNode, { use: FindAllReferences.FindReferencesUse.References }); - return adjustedNode; } - } - function cleanupSemanticCache(): void { - program = undefined!; // TODO: GH#18217 + return undefined; } - function dispose(): void { - if (program) { - // Use paths to ensure we are using correct key and paths as document registry could be created with different current directory than host - const key = documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()); - forEach(program.getSourceFiles(), f => - documentRegistry.releaseDocumentWithKey(f.resolvedPath, key, f.scriptKind, f.impliedNodeFormat)); - program = undefined!; // TODO: GH#18217 - } - host = undefined!; + function getNodeForSpan(docSpan: DocumentSpan): Node | undefined { + const sourceFile = program.getSourceFile(docSpan.fileName); + if (!sourceFile) return undefined; + const rawNode = getTouchingPropertyName(sourceFile, docSpan.textSpan.start); + const adjustedNode = FindAllReferences.Core.getAdjustedNode(rawNode, { use: FindAllReferences.FindReferencesUse.References }); + return adjustedNode; } + } - /// Diagnostics - function getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[] { - synchronizeHostData(); + function cleanupSemanticCache(): void { + program = undefined!; // TODO: GH#18217 + } - return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken).slice(); + function dispose(): void { + if (program) { + // Use paths to ensure we are using correct key and paths as document registry could be created with different current directory than host + const key = documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()); + forEach(program.getSourceFiles(), f => + documentRegistry.releaseDocumentWithKey(f.resolvedPath, key, f.scriptKind, f.impliedNodeFormat)); + program = undefined!; // TODO: GH#18217 } + host = undefined!; + } - /** - * getSemanticDiagnostics return array of Diagnostics. If '-d' is not enabled, only report semantic errors - * If '-d' enabled, report both semantic and emitter errors - */ - function getSemanticDiagnostics(fileName: string): Diagnostic[] { - synchronizeHostData(); - - const targetSourceFile = getValidSourceFile(fileName); - - // Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file. - // Therefore only get diagnostics for given file. + /// Diagnostics + function getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[] { + synchronizeHostData(); - const semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken); - if (!getEmitDeclarations(program.getCompilerOptions())) { - return semanticDiagnostics.slice(); - } + return program.getSyntacticDiagnostics(getValidSourceFile(fileName), cancellationToken).slice(); + } - // If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface - const declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken); - return [...semanticDiagnostics, ...declarationDiagnostics]; - } + /** + * getSemanticDiagnostics return array of Diagnostics. If '-d' is not enabled, only report semantic errors + * If '-d' enabled, report both semantic and emitter errors + */ + function getSemanticDiagnostics(fileName: string): Diagnostic[] { + synchronizeHostData(); - function getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[] { - synchronizeHostData(); - return computeSuggestionDiagnostics(getValidSourceFile(fileName), program, cancellationToken); - } + const targetSourceFile = getValidSourceFile(fileName); - function getCompilerOptionsDiagnostics() { - synchronizeHostData(); - return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)]; - } + // Only perform the action per file regardless of '-out' flag as LanguageServiceHost is expected to call this function per file. + // Therefore only get diagnostics for given file. - function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = emptyOptions, formattingSettings?: FormatCodeSettings): CompletionInfo | undefined { - // Convert from deprecated options names to new names - const fullPreferences: UserPreferences = { - ...identity(options), // avoid excess property check - includeCompletionsForModuleExports: options.includeCompletionsForModuleExports || options.includeExternalModuleExports, - includeCompletionsWithInsertText: options.includeCompletionsWithInsertText || options.includeInsertTextCompletions, - }; - synchronizeHostData(); - return Completions.getCompletionsAtPosition( - host, - program, - log, - getValidSourceFile(fileName), - position, - fullPreferences, - options.triggerCharacter, - options.triggerKind, - cancellationToken, - formattingSettings && formatting.getFormatContext(formattingSettings, host)); - } - - function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions, data?: CompletionEntryData): CompletionEntryDetails | undefined { - synchronizeHostData(); - return Completions.getCompletionEntryDetails( - program, - log, - getValidSourceFile(fileName), - position, - { name, source, data }, - host, - (formattingOptions && formatting.getFormatContext(formattingOptions, host))!, // TODO: GH#18217 - preferences, - cancellationToken, - ); + const semanticDiagnostics = program.getSemanticDiagnostics(targetSourceFile, cancellationToken); + if (!getEmitDeclarations(program.getCompilerOptions())) { + return semanticDiagnostics.slice(); } - function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string, preferences: UserPreferences = emptyOptions): Symbol | undefined { - synchronizeHostData(); - return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host, preferences); - } + // If '-d' is enabled, check for emitter error. One example of emitter error is export class implements non-export interface + const declarationDiagnostics = program.getDeclarationDiagnostics(targetSourceFile, cancellationToken); + return [...semanticDiagnostics, ...declarationDiagnostics]; + } - function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined { - synchronizeHostData(); + function getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[] { + synchronizeHostData(); + return computeSuggestionDiagnostics(getValidSourceFile(fileName), program, cancellationToken); + } - const sourceFile = getValidSourceFile(fileName); - const node = getTouchingPropertyName(sourceFile, position); - if (node === sourceFile) { - // Avoid giving quickInfo for the sourceFile as a whole. - return undefined; - } + function getCompilerOptionsDiagnostics() { + synchronizeHostData(); + return [...program.getOptionsDiagnostics(cancellationToken), ...program.getGlobalDiagnostics(cancellationToken)]; + } - const typeChecker = program.getTypeChecker(); - const nodeForQuickInfo = getNodeForQuickInfo(node); - const symbol = getSymbolAtLocationForQuickInfo(nodeForQuickInfo, typeChecker); - - if (!symbol || typeChecker.isUnknownSymbol(symbol)) { - const type = shouldGetType(sourceFile, nodeForQuickInfo, position) ? typeChecker.getTypeAtLocation(nodeForQuickInfo) : undefined; - return type && { - kind: ScriptElementKind.unknown, - kindModifiers: ScriptElementKindModifier.none, - textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), - displayParts: typeChecker.runWithCancellationToken(cancellationToken, typeChecker => typeToDisplayParts(typeChecker, type, getContainerNode(nodeForQuickInfo))), - documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined, - tags: type.symbol ? type.symbol.getJsDocTags(typeChecker) : undefined - }; - } + function getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions = emptyOptions, formattingSettings?: FormatCodeSettings): CompletionInfo | undefined { + // Convert from deprecated options names to new names + const fullPreferences: UserPreferences = { + ...identity(options), // avoid excess property check + includeCompletionsForModuleExports: options.includeCompletionsForModuleExports || options.includeExternalModuleExports, + includeCompletionsWithInsertText: options.includeCompletionsWithInsertText || options.includeInsertTextCompletions, + }; + synchronizeHostData(); + return Completions.getCompletionsAtPosition( + host, + program, + log, + getValidSourceFile(fileName), + position, + fullPreferences, + options.triggerCharacter, + options.triggerKind, + cancellationToken, + formattingSettings && formatting.getFormatContext(formattingSettings, host)); + } - const { symbolKind, displayParts, documentation, tags } = typeChecker.runWithCancellationToken(cancellationToken, typeChecker => - SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(nodeForQuickInfo), nodeForQuickInfo) - ); - return { - kind: symbolKind, - kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), - textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), - displayParts, - documentation, - tags, - }; - } + function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions: FormatCodeSettings | undefined, source: string | undefined, preferences: UserPreferences = emptyOptions, data?: CompletionEntryData): CompletionEntryDetails | undefined { + synchronizeHostData(); + return Completions.getCompletionEntryDetails( + program, + log, + getValidSourceFile(fileName), + position, + { name, source, data }, + host, + (formattingOptions && formatting.getFormatContext(formattingOptions, host))!, // TODO: GH#18217 + preferences, + cancellationToken, + ); + } - function getNodeForQuickInfo(node: Node): Node { - if (isNewExpression(node.parent) && node.pos === node.parent.pos) { - return node.parent.expression; - } - if (isNamedTupleMember(node.parent) && node.pos === node.parent.pos) { - return node.parent; - } - if (isImportMeta(node.parent) && node.parent.name === node) { - return node.parent; - } - return node; - } + function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string, preferences: UserPreferences = emptyOptions): Symbol | undefined { + synchronizeHostData(); + return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host, preferences); + } - function shouldGetType(sourceFile: SourceFile, node: Node, position: number): boolean { - switch (node.kind) { - case SyntaxKind.Identifier: - return !isLabelName(node) && !isTagName(node) && !isConstTypeReference(node.parent); - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.QualifiedName: - // Don't return quickInfo if inside the comment in `a/**/.b` - return !isInComment(sourceFile, position); - case SyntaxKind.ThisKeyword: - case SyntaxKind.ThisType: - case SyntaxKind.SuperKeyword: - case SyntaxKind.NamedTupleMember: - return true; - case SyntaxKind.MetaProperty: - return isImportMeta(node); - default: - return false; - } - } + function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined { + synchronizeHostData(); - /// Goto definition - function getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly?: boolean, stopAtAlias?: boolean): readonly DefinitionInfo[] | undefined { - synchronizeHostData(); - return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position, searchOtherFilesOnly, stopAtAlias); + const sourceFile = getValidSourceFile(fileName); + const node = getTouchingPropertyName(sourceFile, position); + if (node === sourceFile) { + // Avoid giving quickInfo for the sourceFile as a whole. + return undefined; } - function getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined { - synchronizeHostData(); - return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position); - } + const typeChecker = program.getTypeChecker(); + const nodeForQuickInfo = getNodeForQuickInfo(node); + const symbol = getSymbolAtLocationForQuickInfo(nodeForQuickInfo, typeChecker); - function getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined { - synchronizeHostData(); - return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position); + if (!symbol || typeChecker.isUnknownSymbol(symbol)) { + const type = shouldGetType(sourceFile, nodeForQuickInfo, position) ? typeChecker.getTypeAtLocation(nodeForQuickInfo) : undefined; + return type && { + kind: ScriptElementKind.unknown, + kindModifiers: ScriptElementKindModifier.none, + textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), + displayParts: typeChecker.runWithCancellationToken(cancellationToken, typeChecker => typeToDisplayParts(typeChecker, type, getContainerNode(nodeForQuickInfo))), + documentation: type.symbol ? type.symbol.getDocumentationComment(typeChecker) : undefined, + tags: type.symbol ? type.symbol.getJsDocTags(typeChecker) : undefined + }; } - /// Goto implementation + const { symbolKind, displayParts, documentation, tags } = typeChecker.runWithCancellationToken(cancellationToken, typeChecker => + SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, getContainerNode(nodeForQuickInfo), nodeForQuickInfo) + ); + return { + kind: symbolKind, + kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), + textSpan: createTextSpanFromNode(nodeForQuickInfo, sourceFile), + displayParts, + documentation, + tags, + }; + } - function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] | undefined { - synchronizeHostData(); - return FindAllReferences.getImplementationsAtPosition(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); + function getNodeForQuickInfo(node: Node): Node { + if (isNewExpression(node.parent) && node.pos === node.parent.pos) { + return node.parent.expression; } - - /// References and Occurrences - function getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined { - return flatMap( - getDocumentHighlights(fileName, position, [fileName]), - entry => entry.highlightSpans.map(highlightSpan => ({ - fileName: entry.fileName, - textSpan: highlightSpan.textSpan, - isWriteAccess: highlightSpan.kind === HighlightSpanKind.writtenReference, - ...highlightSpan.isInString && { isInString: true }, - ...highlightSpan.contextSpan && { contextSpan: highlightSpan.contextSpan } - })) - ); + if (isNamedTupleMember(node.parent) && node.pos === node.parent.pos) { + return node.parent; } - - function getDocumentHighlights(fileName: string, position: number, filesToSearch: readonly string[]): DocumentHighlights[] | undefined { - const normalizedFileName = normalizePath(fileName); - Debug.assert(filesToSearch.some(f => normalizePath(f) === normalizedFileName)); - synchronizeHostData(); - const sourceFilesToSearch = mapDefined(filesToSearch, fileName => program.getSourceFile(fileName)); - const sourceFile = getValidSourceFile(fileName); - return DocumentHighlights.getDocumentHighlights(program, cancellationToken, sourceFile, position, sourceFilesToSearch); - } - - function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] | undefined { - synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); - const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); - if (!Rename.nodeIsEligibleForRename(node)) return undefined; - if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) { - const { openingElement, closingElement } = node.parent.parent; - return [openingElement, closingElement].map((node): RenameLocation => { - const textSpan = createTextSpanFromNode(node.tagName, sourceFile); - return { - fileName: sourceFile.fileName, - textSpan, - ...FindAllReferences.toContextSpan(textSpan, sourceFile, node.parent) - }; - }); - } - else { - return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, use: FindAllReferences.FindReferencesUse.Rename }, - (entry, originalNode, checker) => FindAllReferences.toRenameLocation(entry, originalNode, checker, providePrefixAndSuffixTextForRename || false)); - } + if (isImportMeta(node.parent) && node.parent.name === node) { + return node.parent; } + return node; + } - function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined { - synchronizeHostData(); - return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { use: FindAllReferences.FindReferencesUse.References }, FindAllReferences.toReferenceEntry); + function shouldGetType(sourceFile: SourceFile, node: Node, position: number): boolean { + switch (node.kind) { + case SyntaxKind.Identifier: + return !isLabelName(node) && !isTagName(node) && !isConstTypeReference(node.parent); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.QualifiedName: + // Don't return quickInfo if inside the comment in `a/**/.b` + return !isInComment(sourceFile, position); + case SyntaxKind.ThisKeyword: + case SyntaxKind.ThisType: + case SyntaxKind.SuperKeyword: + case SyntaxKind.NamedTupleMember: + return true; + case SyntaxKind.MetaProperty: + return isImportMeta(node); + default: + return false; } + } - function getReferencesWorker(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry): T[] | undefined { - synchronizeHostData(); + /// Goto definition + function getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly?: boolean, stopAtAlias?: boolean): readonly DefinitionInfo[] | undefined { + synchronizeHostData(); + return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position, searchOtherFilesOnly, stopAtAlias); + } - // Exclude default library when renaming as commonly user don't want to change that file. - const sourceFiles = options && options.use === FindAllReferences.FindReferencesUse.Rename - ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile)) - : program.getSourceFiles(); + function getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined { + synchronizeHostData(); + return GoToDefinition.getDefinitionAndBoundSpan(program, getValidSourceFile(fileName), position); + } - return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb); - } + function getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined { + synchronizeHostData(); + return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position); + } - function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined { - synchronizeHostData(); - return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); - } + /// Goto implementation - function getFileReferences(fileName: string): ReferenceEntry[] { - synchronizeHostData(); - return FindAllReferences.Core.getReferencesForFileName(fileName, program, program.getSourceFiles()).map(FindAllReferences.toReferenceEntry); - } + function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] | undefined { + synchronizeHostData(); + return FindAllReferences.getImplementationsAtPosition(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); + } - function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles = false): NavigateToItem[] { - synchronizeHostData(); - const sourceFiles = fileName ? [getValidSourceFile(fileName)] : program.getSourceFiles(); - return NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles); - } + /// References and Occurrences + function getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined { + return flatMap( + getDocumentHighlights(fileName, position, [fileName]), + entry => entry.highlightSpans.map(highlightSpan => ({ + fileName: entry.fileName, + textSpan: highlightSpan.textSpan, + isWriteAccess: highlightSpan.kind === HighlightSpanKind.writtenReference, + ...highlightSpan.isInString && { isInString: true }, + ...highlightSpan.contextSpan && { contextSpan: highlightSpan.contextSpan } + })) + ); + } - function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean) { - synchronizeHostData(); + function getDocumentHighlights(fileName: string, position: number, filesToSearch: readonly string[]): DocumentHighlights[] | undefined { + const normalizedFileName = normalizePath(fileName); + Debug.assert(filesToSearch.some(f => normalizePath(f) === normalizedFileName)); + synchronizeHostData(); + const sourceFilesToSearch = mapDefined(filesToSearch, fileName => program.getSourceFile(fileName)); + const sourceFile = getValidSourceFile(fileName); + return DocumentHighlights.getDocumentHighlights(program, cancellationToken, sourceFile, position, sourceFilesToSearch); + } - const sourceFile = getValidSourceFile(fileName); - const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); - return getFileEmitOutput(program, sourceFile, !!emitOnlyDtsFiles, cancellationToken, customTransformers, forceDtsEmit); + function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] | undefined { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position)); + if (!Rename.nodeIsEligibleForRename(node)) return undefined; + if (isIdentifier(node) && (isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) && isIntrinsicJsxName(node.escapedText)) { + const { openingElement, closingElement } = node.parent.parent; + return [openingElement, closingElement].map((node): RenameLocation => { + const textSpan = createTextSpanFromNode(node.tagName, sourceFile); + return { + fileName: sourceFile.fileName, + textSpan, + ...FindAllReferences.toContextSpan(textSpan, sourceFile, node.parent) + }; + }); + } + else { + return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, use: FindAllReferences.FindReferencesUse.Rename }, + (entry, originalNode, checker) => FindAllReferences.toRenameLocation(entry, originalNode, checker, providePrefixAndSuffixTextForRename || false)); } + } - // Signature help - /** - * This is a semantic operation. - */ - function getSignatureHelpItems(fileName: string, position: number, { triggerReason }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined { - synchronizeHostData(); + function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined { + synchronizeHostData(); + return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, { use: FindAllReferences.FindReferencesUse.References }, FindAllReferences.toReferenceEntry); + } - const sourceFile = getValidSourceFile(fileName); + function getReferencesWorker(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry): T[] | undefined { + synchronizeHostData(); - return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerReason, cancellationToken); - } + // Exclude default library when renaming as commonly user don't want to change that file. + const sourceFiles = options && options.use === FindAllReferences.FindReferencesUse.Rename + ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile)) + : program.getSourceFiles(); - /// Syntactic features - function getNonBoundSourceFile(fileName: string): SourceFile { - return syntaxTreeCache.getCurrentSourceFile(fileName); - } + return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb); + } - function getNameOrDottedNameSpan(fileName: string, startPos: number, _endPos: number): TextSpan | undefined { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined { + synchronizeHostData(); + return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); + } - // Get node at the location - const node = getTouchingPropertyName(sourceFile, startPos); + function getFileReferences(fileName: string): ReferenceEntry[] { + synchronizeHostData(); + return FindAllReferences.Core.getReferencesForFileName(fileName, program, program.getSourceFiles()).map(FindAllReferences.toReferenceEntry); + } - if (node === sourceFile) { - return undefined; - } + function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles = false): NavigateToItem[] { + synchronizeHostData(); + const sourceFiles = fileName ? [getValidSourceFile(fileName)] : program.getSourceFiles(); + return NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles); + } - switch (node.kind) { - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.QualifiedName: - case SyntaxKind.StringLiteral: - case SyntaxKind.FalseKeyword: - case SyntaxKind.TrueKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.SuperKeyword: - case SyntaxKind.ThisKeyword: - case SyntaxKind.ThisType: - case SyntaxKind.Identifier: - break; + function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean) { + synchronizeHostData(); - // Cant create the text span - default: - return undefined; - } + const sourceFile = getValidSourceFile(fileName); + const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); + return getFileEmitOutput(program, sourceFile, !!emitOnlyDtsFiles, cancellationToken, customTransformers, forceDtsEmit); + } - let nodeForStartPos = node; - while (true) { - if (isRightSideOfPropertyAccess(nodeForStartPos) || isRightSideOfQualifiedName(nodeForStartPos)) { - // If on the span is in right side of the the property or qualified name, return the span from the qualified name pos to end of this node - nodeForStartPos = nodeForStartPos.parent; - } - else if (isNameOfModuleDeclaration(nodeForStartPos)) { - // If this is name of a module declarations, check if this is right side of dotted module name - // If parent of the module declaration which is parent of this node is module declaration and its body is the module declaration that this node is name of - // Then this name is name from dotted module - if (nodeForStartPos.parent.parent.kind === SyntaxKind.ModuleDeclaration && - (nodeForStartPos.parent.parent as ModuleDeclaration).body === nodeForStartPos.parent) { - // Use parent module declarations name for start pos - nodeForStartPos = (nodeForStartPos.parent.parent as ModuleDeclaration).name; - } - else { - // We have to use this name for start pos - break; - } - } - else { - // Is not a member expression so we have found the node for start pos - break; - } - } + // Signature help + /** + * This is a semantic operation. + */ + function getSignatureHelpItems(fileName: string, position: number, { triggerReason }: SignatureHelpItemsOptions = emptyOptions): SignatureHelpItems | undefined { + synchronizeHostData(); - return createTextSpanFromBounds(nodeForStartPos.getStart(), node.getEnd()); - } + const sourceFile = getValidSourceFile(fileName); - function getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined { - // doesn't use compiler - no need to synchronize with host - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + return SignatureHelp.getSignatureHelpItems(program, sourceFile, position, triggerReason, cancellationToken); + } - return BreakpointResolver.spanInSourceFileAtLocation(sourceFile, position); - } + /// Syntactic features + function getNonBoundSourceFile(fileName: string): SourceFile { + return syntaxTreeCache.getCurrentSourceFile(fileName); + } - function getNavigationBarItems(fileName: string): NavigationBarItem[] { - return NavigationBar.getNavigationBarItems(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); - } + function getNameOrDottedNameSpan(fileName: string, startPos: number, _endPos: number): TextSpan | undefined { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + + // Get node at the location + const node = getTouchingPropertyName(sourceFile, startPos); - function getNavigationTree(fileName: string): NavigationTree { - return NavigationBar.getNavigationTree(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); + if (node === sourceFile) { + return undefined; } - function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; - function getSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[] { - synchronizeHostData(); + switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.QualifiedName: + case SyntaxKind.StringLiteral: + case SyntaxKind.FalseKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.ThisKeyword: + case SyntaxKind.ThisType: + case SyntaxKind.Identifier: + break; - const responseFormat = format || SemanticClassificationFormat.Original; - if (responseFormat === SemanticClassificationFormat.TwentyTwenty) { - return classifier.v2020.getSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); - } - else { - return ts.getSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); - } + // Cant create the text span + default: + return undefined; } - function getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications { - synchronizeHostData(); - - const responseFormat = format || SemanticClassificationFormat.Original; - if (responseFormat === SemanticClassificationFormat.Original) { - return ts.getEncodedSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); + let nodeForStartPos = node; + while (true) { + if (isRightSideOfPropertyAccess(nodeForStartPos) || isRightSideOfQualifiedName(nodeForStartPos)) { + // If on the span is in right side of the the property or qualified name, return the span from the qualified name pos to end of this node + nodeForStartPos = nodeForStartPos.parent; + } + else if (isNameOfModuleDeclaration(nodeForStartPos)) { + // If this is name of a module declarations, check if this is right side of dotted module name + // If parent of the module declaration which is parent of this node is module declaration and its body is the module declaration that this node is name of + // Then this name is name from dotted module + if (nodeForStartPos.parent.parent.kind === SyntaxKind.ModuleDeclaration && + (nodeForStartPos.parent.parent as ModuleDeclaration).body === nodeForStartPos.parent) { + // Use parent module declarations name for start pos + nodeForStartPos = (nodeForStartPos.parent.parent as ModuleDeclaration).name; + } + else { + // We have to use this name for start pos + break; + } } else { - return classifier.v2020.getEncodedSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); + // Is not a member expression so we have found the node for start pos + break; } } - function getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] { - // doesn't use compiler - no need to synchronize with host - return ts.getSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); - } - - function getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications { - // doesn't use compiler - no need to synchronize with host - return ts.getEncodedSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); - } - - function getOutliningSpans(fileName: string): OutliningSpan[] { - // doesn't use compiler - no need to synchronize with host - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - return OutliningElementsCollector.collectElements(sourceFile, cancellationToken); - } - - const braceMatching = new Map(getEntries({ - [SyntaxKind.OpenBraceToken]: SyntaxKind.CloseBraceToken, - [SyntaxKind.OpenParenToken]: SyntaxKind.CloseParenToken, - [SyntaxKind.OpenBracketToken]: SyntaxKind.CloseBracketToken, - [SyntaxKind.GreaterThanToken]: SyntaxKind.LessThanToken, - })); - braceMatching.forEach((value, key) => braceMatching.set(value.toString(), Number(key) as SyntaxKind)); + return createTextSpanFromBounds(nodeForStartPos.getStart(), node.getEnd()); + } - function getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const token = getTouchingToken(sourceFile, position); - const matchKind = token.getStart(sourceFile) === position ? braceMatching.get(token.kind.toString()) : undefined; - const match = matchKind && findChildOfKind(token.parent, matchKind, sourceFile); - // We want to order the braces when we return the result. - return match ? [createTextSpanFromNode(token, sourceFile), createTextSpanFromNode(match, sourceFile)].sort((a, b) => a.start - b.start) : emptyArray; - } + function getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined { + // doesn't use compiler - no need to synchronize with host + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - function getIndentationAtPosition(fileName: string, position: number, editorOptions: EditorOptions | EditorSettings) { - let start = timestamp(); - const settings = toEditorSettings(editorOptions); - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - log("getIndentationAtPosition: getCurrentSourceFile: " + (timestamp() - start)); + return BreakpointResolver.spanInSourceFileAtLocation(sourceFile, position); + } - start = timestamp(); + function getNavigationBarItems(fileName: string): NavigationBarItem[] { + return NavigationBar.getNavigationBarItems(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); + } - const result = formatting.SmartIndenter.getIndentation(position, sourceFile, settings); - log("getIndentationAtPosition: computeIndentation : " + (timestamp() - start)); + function getNavigationTree(fileName: string): NavigationTree { + return NavigationBar.getNavigationTree(syntaxTreeCache.getCurrentSourceFile(fileName), cancellationToken); + } - return result; - } + function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; + function getSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[] { + synchronizeHostData(); - function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - return formatting.formatSelection(start, end, sourceFile, formatting.getFormatContext(toEditorSettings(options), host)); + const responseFormat = format || SemanticClassificationFormat.Original; + if (responseFormat === SemanticClassificationFormat.TwentyTwenty) { + return classifier.v2020.getSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); } - - function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { - return formatting.formatDocument(syntaxTreeCache.getCurrentSourceFile(fileName), formatting.getFormatContext(toEditorSettings(options), host)); + else { + return ts.getSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); } + } - function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const formatContext = formatting.getFormatContext(toEditorSettings(options), host); - - if (!isInComment(sourceFile, position)) { - switch (key) { - case "{": - return formatting.formatOnOpeningCurly(position, sourceFile, formatContext); - case "}": - return formatting.formatOnClosingCurly(position, sourceFile, formatContext); - case ";": - return formatting.formatOnSemicolon(position, sourceFile, formatContext); - case "\n": - return formatting.formatOnEnter(position, sourceFile, formatContext); - } - } + function getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications { + synchronizeHostData(); - return []; + const responseFormat = format || SemanticClassificationFormat.Original; + if (responseFormat === SemanticClassificationFormat.Original) { + return ts.getEncodedSemanticClassifications(program.getTypeChecker(), cancellationToken, getValidSourceFile(fileName), program.getClassifiableNames(), span); } - - function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly CodeFixAction[] { - synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); - const span = createTextSpanFromBounds(start, end); - const formatContext = formatting.getFormatContext(formatOptions, host); - - return flatMap(deduplicate(errorCodes, equateValues, compareValues), errorCode => { - cancellationToken.throwIfCancellationRequested(); - return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext, preferences }); - }); + else { + return classifier.v2020.getEncodedSemanticClassifications(program, cancellationToken, getValidSourceFile(fileName), span); } + } - function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): CombinedCodeActions { - synchronizeHostData(); - Debug.assert(scope.type === "file"); - const sourceFile = getValidSourceFile(scope.fileName); - const formatContext = formatting.getFormatContext(formatOptions, host); + function getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] { + // doesn't use compiler - no need to synchronize with host + return ts.getSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); + } - return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext, preferences }); - } + function getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications { + // doesn't use compiler - no need to synchronize with host + return ts.getEncodedSyntacticClassifications(cancellationToken, syntaxTreeCache.getCurrentSourceFile(fileName), span); + } - function organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { - synchronizeHostData(); - Debug.assert(args.type === "file"); - const sourceFile = getValidSourceFile(args.fileName); - const formatContext = formatting.getFormatContext(formatOptions, host); + function getOutliningSpans(fileName: string): OutliningSpan[] { + // doesn't use compiler - no need to synchronize with host + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + return OutliningElementsCollector.collectElements(sourceFile, cancellationToken); + } - const mode = args.mode ?? (args.skipDestructiveCodeActions ? OrganizeImportsMode.SortAndCombine : OrganizeImportsMode.All); - return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences, mode); - } + const braceMatching = new Map(getEntries({ + [SyntaxKind.OpenBraceToken]: SyntaxKind.CloseBraceToken, + [SyntaxKind.OpenParenToken]: SyntaxKind.CloseParenToken, + [SyntaxKind.OpenBracketToken]: SyntaxKind.CloseBracketToken, + [SyntaxKind.GreaterThanToken]: SyntaxKind.LessThanToken, + })); + braceMatching.forEach((value, key) => braceMatching.set(value.toString(), Number(key) as SyntaxKind)); + + function getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const token = getTouchingToken(sourceFile, position); + const matchKind = token.getStart(sourceFile) === position ? braceMatching.get(token.kind.toString()) : undefined; + const match = matchKind && findChildOfKind(token.parent, matchKind, sourceFile); + // We want to order the braces when we return the result. + return match ? [createTextSpanFromNode(token, sourceFile), createTextSpanFromNode(match, sourceFile)].sort((a, b) => a.start - b.start) : emptyArray; + } - function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { - return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions, host), preferences, sourceMapper); - } + function getIndentationAtPosition(fileName: string, position: number, editorOptions: EditorOptions | EditorSettings) { + let start = timestamp(); + const settings = toEditorSettings(editorOptions); + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + log("getIndentationAtPosition: getCurrentSourceFile: " + (timestamp() - start)); - function applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; - function applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; - function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; - function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): Promise; - function applyCodeActionCommand(fileName: Path, action: CodeActionCommand[]): Promise; - function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrFormatSettingsOrUndefined?: CodeActionCommand | CodeActionCommand[] | FormatCodeSettings): Promise { - const action = typeof fileName === "string" ? actionOrFormatSettingsOrUndefined as CodeActionCommand | CodeActionCommand[] : fileName as CodeActionCommand[]; - return isArray(action) ? Promise.all(action.map(a => applySingleCodeActionCommand(a))) : applySingleCodeActionCommand(action); - } + start = timestamp(); - function applySingleCodeActionCommand(action: CodeActionCommand): Promise { - const getPath = (path: string): Path => toPath(path, currentDirectory, getCanonicalFileName); - Debug.assertEqual(action.type, "install package"); - return host.installPackage - ? host.installPackage({ fileName: getPath(action.file), packageName: action.packageName }) - : Promise.reject("Host does not implement `installPackage`"); - } + const result = formatting.SmartIndenter.getIndentation(position, sourceFile, settings); + log("getIndentationAtPosition: computeIndentation : " + (timestamp() - start)); - function getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined { - return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position, options); - } + return result; + } - function isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { - // '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too - // expensive to do during typing scenarios - // i.e. whether we're dealing with: - // var x = new foo<| ( with class foo{} ) - // or - // var y = 3 <| - if (openingBrace === CharacterCodes.lessThan) { - return false; - } + function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + return formatting.formatSelection(start, end, sourceFile, formatting.getFormatContext(toEditorSettings(options), host)); + } - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { + return formatting.formatDocument(syntaxTreeCache.getCurrentSourceFile(fileName), formatting.getFormatContext(toEditorSettings(options), host)); + } - // Check if in a context where we don't want to perform any insertion - if (isInString(sourceFile, position)) { - return false; - } + function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const formatContext = formatting.getFormatContext(toEditorSettings(options), host); - if (isInsideJsxElementOrAttribute(sourceFile, position)) { - return openingBrace === CharacterCodes.openBrace; + if (!isInComment(sourceFile, position)) { + switch (key) { + case "{": + return formatting.formatOnOpeningCurly(position, sourceFile, formatContext); + case "}": + return formatting.formatOnClosingCurly(position, sourceFile, formatContext); + case ";": + return formatting.formatOnSemicolon(position, sourceFile, formatContext); + case "\n": + return formatting.formatOnEnter(position, sourceFile, formatContext); } + } - if (isInTemplateString(sourceFile, position)) { - return false; - } + return []; + } - switch (openingBrace) { - case CharacterCodes.singleQuote: - case CharacterCodes.doubleQuote: - case CharacterCodes.backtick: - return !isInComment(sourceFile, position); - } + function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly CodeFixAction[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const span = createTextSpanFromBounds(start, end); + const formatContext = formatting.getFormatContext(formatOptions, host); - return true; - } + return flatMap(deduplicate(errorCodes, equateValues, compareValues), errorCode => { + cancellationToken.throwIfCancellationRequested(); + return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext, preferences }); + }); + } - function getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const token = findPrecedingToken(position, sourceFile); - if (!token) return undefined; - const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent - : isJsxText(token) && isJsxElement(token.parent) ? token.parent : undefined; - if (element && isUnclosedTag(element)) { - return { newText: `` }; - } - const fragment = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningFragment(token.parent) ? token.parent.parent - : isJsxText(token) && isJsxFragment(token.parent) ? token.parent : undefined; - if (fragment && isUnclosedFragment(fragment)) { - return { newText: "" }; - } - } + function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): CombinedCodeActions { + synchronizeHostData(); + Debug.assert(scope.type === "file"); + const sourceFile = getValidSourceFile(scope.fileName); + const formatContext = formatting.getFormatContext(formatOptions, host); - function getLinesForRange(sourceFile: SourceFile, textRange: TextRange) { - return { - lineStarts: sourceFile.getLineStarts(), - firstLine: sourceFile.getLineAndCharacterOfPosition(textRange.pos).line, - lastLine: sourceFile.getLineAndCharacterOfPosition(textRange.end).line - }; - } + return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext, preferences }); + } - function toggleLineComment(fileName: string, textRange: TextRange, insertComment?: boolean): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const textChanges: TextChange[] = []; - const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange); + function organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { + synchronizeHostData(); + Debug.assert(args.type === "file"); + const sourceFile = getValidSourceFile(args.fileName); + const formatContext = formatting.getFormatContext(formatOptions, host); - let isCommenting = insertComment || false; - let leftMostPosition = Number.MAX_VALUE; - const lineTextStarts = new Map(); - const firstNonWhitespaceCharacterRegex = new RegExp(/\S/); - const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]); - const openComment = isJsx ? "{/*" : "//"; + const mode = args.mode ?? (args.skipDestructiveCodeActions ? OrganizeImportsMode.SortAndCombine : OrganizeImportsMode.All); + return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences, mode); + } - // Check each line before any text changes. - for (let i = firstLine; i <= lastLine; i++) { - const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i])); + function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): readonly FileTextChanges[] { + return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions, host), preferences, sourceMapper); + } - // Find the start of text and the left-most character. No-op on empty lines. - const regExec = firstNonWhitespaceCharacterRegex.exec(lineText); - if (regExec) { - leftMostPosition = Math.min(leftMostPosition, regExec.index); - lineTextStarts.set(i.toString(), regExec.index); + function applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; + function applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + function applyCodeActionCommand(fileName: Path, action: CodeActionCommand): Promise; + function applyCodeActionCommand(fileName: Path, action: CodeActionCommand[]): Promise; + function applyCodeActionCommand(fileName: Path | CodeActionCommand | CodeActionCommand[], actionOrFormatSettingsOrUndefined?: CodeActionCommand | CodeActionCommand[] | FormatCodeSettings): Promise { + const action = typeof fileName === "string" ? actionOrFormatSettingsOrUndefined as CodeActionCommand | CodeActionCommand[] : fileName as CodeActionCommand[]; + return isArray(action) ? Promise.all(action.map(a => applySingleCodeActionCommand(a))) : applySingleCodeActionCommand(action); + } - if (lineText.substr(regExec.index, openComment.length) !== openComment) { - isCommenting = insertComment === undefined || insertComment; - } - } - } + function applySingleCodeActionCommand(action: CodeActionCommand): Promise { + const getPath = (path: string): Path => toPath(path, currentDirectory, getCanonicalFileName); + Debug.assertEqual(action.type, "install package"); + return host.installPackage + ? host.installPackage({ fileName: getPath(action.file), packageName: action.packageName }) + : Promise.reject("Host does not implement `installPackage`"); + } - // Push all text changes. - for (let i = firstLine; i <= lastLine; i++) { - // If the range is multiline and ends on a beginning of a line, don't comment/uncomment. - if (firstLine !== lastLine && lineStarts[i] === textRange.end) { - continue; - } + function getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined { + return JsDoc.getDocCommentTemplateAtPosition(getNewLineOrDefaultFromHost(host), syntaxTreeCache.getCurrentSourceFile(fileName), position, options); + } - const lineTextStart = lineTextStarts.get(i.toString()); + function isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { + // '<' is currently not supported, figuring out if we're in a Generic Type vs. a comparison is too + // expensive to do during typing scenarios + // i.e. whether we're dealing with: + // var x = new foo<| ( with class foo{} ) + // or + // var y = 3 <| + if (openingBrace === CharacterCodes.lessThan) { + return false; + } - // If the line is not an empty line; otherwise no-op. - if (lineTextStart !== undefined) { - if (isJsx) { - textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }, isCommenting, isJsx)); - } - else if (isCommenting) { - textChanges.push({ - newText: openComment, - span: { - length: 0, - start: lineStarts[i] + leftMostPosition - } - }); - } - else if (sourceFile.text.substr(lineStarts[i] + lineTextStart, openComment.length) === openComment) { - textChanges.push({ - newText: "", - span: { - length: openComment.length, - start: lineStarts[i] + lineTextStart - } - }); - } - } - } + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - return textChanges; + // Check if in a context where we don't want to perform any insertion + if (isInString(sourceFile, position)) { + return false; } - function toggleMultilineComment(fileName: string, textRange: TextRange, insertComment?: boolean, isInsideJsx?: boolean): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const textChanges: TextChange[] = []; - const { text } = sourceFile; - - let hasComment = false; - let isCommenting = insertComment || false; - const positions = [] as number[] as SortedArray; + if (isInsideJsxElementOrAttribute(sourceFile, position)) { + return openingBrace === CharacterCodes.openBrace; + } - let { pos } = textRange; - const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos); + if (isInTemplateString(sourceFile, position)) { + return false; + } - const openMultiline = isJsx ? "{/*" : "/*"; - const closeMultiline = isJsx ? "*/}" : "*/"; - const openMultilineRegex = isJsx ? "\\{\\/\\*" : "\\/\\*"; - const closeMultilineRegex = isJsx ? "\\*\\/\\}" : "\\*\\/"; + switch (openingBrace) { + case CharacterCodes.singleQuote: + case CharacterCodes.doubleQuote: + case CharacterCodes.backtick: + return !isInComment(sourceFile, position); + } - // Get all comment positions - while (pos <= textRange.end) { - // Start of comment is considered inside comment. - const offset = text.substr(pos, openMultiline.length) === openMultiline ? openMultiline.length : 0; - const commentRange = isInComment(sourceFile, pos + offset); + return true; + } - // If position is in a comment add it to the positions array. - if (commentRange) { - // Comment range doesn't include the brace character. Increase it to include them. - if (isJsx) { - commentRange.pos--; - commentRange.end++; - } + function getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const token = findPrecedingToken(position, sourceFile); + if (!token) return undefined; + const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent + : isJsxText(token) && isJsxElement(token.parent) ? token.parent : undefined; + if (element && isUnclosedTag(element)) { + return { newText: `` }; + } + const fragment = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningFragment(token.parent) ? token.parent.parent + : isJsxText(token) && isJsxFragment(token.parent) ? token.parent : undefined; + if (fragment && isUnclosedFragment(fragment)) { + return { newText: "" }; + } + } - positions.push(commentRange.pos); - if (commentRange.kind === SyntaxKind.MultiLineCommentTrivia) { - positions.push(commentRange.end); - } + function getLinesForRange(sourceFile: SourceFile, textRange: TextRange) { + return { + lineStarts: sourceFile.getLineStarts(), + firstLine: sourceFile.getLineAndCharacterOfPosition(textRange.pos).line, + lastLine: sourceFile.getLineAndCharacterOfPosition(textRange.end).line + }; + } - hasComment = true; - pos = commentRange.end + 1; + function toggleLineComment(fileName: string, textRange: TextRange, insertComment?: boolean): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const textChanges: TextChange[] = []; + const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange); + + let isCommenting = insertComment || false; + let leftMostPosition = Number.MAX_VALUE; + const lineTextStarts = new Map(); + const firstNonWhitespaceCharacterRegex = new RegExp(/\S/); + const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]); + const openComment = isJsx ? "{/*" : "//"; + + // Check each line before any text changes. + for (let i = firstLine; i <= lastLine; i++) { + const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i])); + + // Find the start of text and the left-most character. No-op on empty lines. + const regExec = firstNonWhitespaceCharacterRegex.exec(lineText); + if (regExec) { + leftMostPosition = Math.min(leftMostPosition, regExec.index); + lineTextStarts.set(i.toString(), regExec.index); + + if (lineText.substr(regExec.index, openComment.length) !== openComment) { + isCommenting = insertComment === undefined || insertComment; } - else { // If it's not in a comment range, then we need to comment the uncommented portions. - const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); + } + } - isCommenting = insertComment !== undefined - ? insertComment - : isCommenting || !isTextWhiteSpaceLike(text, pos, newPos === -1 ? textRange.end : pos + newPos); // If isCommenting is already true we don't need to check whitespace again. - pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length; - } + // Push all text changes. + for (let i = firstLine; i <= lastLine; i++) { + // If the range is multiline and ends on a beginning of a line, don't comment/uncomment. + if (firstLine !== lastLine && lineStarts[i] === textRange.end) { + continue; } - // If it didn't found a comment and isCommenting is false means is only empty space. - // We want to insert comment in this scenario. - if (isCommenting || !hasComment) { - if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) { - insertSorted(positions, textRange.pos, compareValues); - } - insertSorted(positions, textRange.end, compareValues); + const lineTextStart = lineTextStarts.get(i.toString()); - // Insert open comment if the first position is not a comment already. - const firstPos = positions[0]; - if (text.substr(firstPos, openMultiline.length) !== openMultiline) { + // If the line is not an empty line; otherwise no-op. + if (lineTextStart !== undefined) { + if (isJsx) { + textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }, isCommenting, isJsx)); + } + else if (isCommenting) { textChanges.push({ - newText: openMultiline, + newText: openComment, span: { length: 0, - start: firstPos + start: lineStarts[i] + leftMostPosition + } + }); + } + else if (sourceFile.text.substr(lineStarts[i] + lineTextStart, openComment.length) === openComment) { + textChanges.push({ + newText: "", + span: { + length: openComment.length, + start: lineStarts[i] + lineTextStart } }); } + } + } - // Insert open and close comment to all positions between first and last. Exclusive. - for (let i = 1; i < positions.length - 1; i++) { - if (text.substr(positions[i] - closeMultiline.length, closeMultiline.length) !== closeMultiline) { - textChanges.push({ - newText: closeMultiline, - span: { - length: 0, - start: positions[i] - } - }); - } + return textChanges; + } - if (text.substr(positions[i], openMultiline.length) !== openMultiline) { - textChanges.push({ - newText: openMultiline, - span: { - length: 0, - start: positions[i] - } - }); - } + function toggleMultilineComment(fileName: string, textRange: TextRange, insertComment?: boolean, isInsideJsx?: boolean): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const textChanges: TextChange[] = []; + const { text } = sourceFile; + + let hasComment = false; + let isCommenting = insertComment || false; + const positions = [] as number[] as SortedArray; + + let { pos } = textRange; + const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos); + + const openMultiline = isJsx ? "{/*" : "/*"; + const closeMultiline = isJsx ? "*/}" : "*/"; + const openMultilineRegex = isJsx ? "\\{\\/\\*" : "\\/\\*"; + const closeMultilineRegex = isJsx ? "\\*\\/\\}" : "\\*\\/"; + + // Get all comment positions + while (pos <= textRange.end) { + // Start of comment is considered inside comment. + const offset = text.substr(pos, openMultiline.length) === openMultiline ? openMultiline.length : 0; + const commentRange = isInComment(sourceFile, pos + offset); + + // If position is in a comment add it to the positions array. + if (commentRange) { + // Comment range doesn't include the brace character. Increase it to include them. + if (isJsx) { + commentRange.pos--; + commentRange.end++; } - // Insert open comment if the last position is not a comment already. - if (textChanges.length % 2 !== 0) { + positions.push(commentRange.pos); + if (commentRange.kind === SyntaxKind.MultiLineCommentTrivia) { + positions.push(commentRange.end); + } + + hasComment = true; + pos = commentRange.end + 1; + } + else { // If it's not in a comment range, then we need to comment the uncommented portions. + const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); + + isCommenting = insertComment !== undefined + ? insertComment + : isCommenting || !isTextWhiteSpaceLike(text, pos, newPos === -1 ? textRange.end : pos + newPos); // If isCommenting is already true we don't need to check whitespace again. + pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length; + } + } + + // If it didn't found a comment and isCommenting is false means is only empty space. + // We want to insert comment in this scenario. + if (isCommenting || !hasComment) { + if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) { + insertSorted(positions, textRange.pos, compareValues); + } + insertSorted(positions, textRange.end, compareValues); + + // Insert open comment if the first position is not a comment already. + const firstPos = positions[0]; + if (text.substr(firstPos, openMultiline.length) !== openMultiline) { + textChanges.push({ + newText: openMultiline, + span: { + length: 0, + start: firstPos + } + }); + } + + // Insert open and close comment to all positions between first and last. Exclusive. + for (let i = 1; i < positions.length - 1; i++) { + if (text.substr(positions[i] - closeMultiline.length, closeMultiline.length) !== closeMultiline) { textChanges.push({ newText: closeMultiline, span: { length: 0, - start: positions[positions.length - 1] + start: positions[i] } }); } - } - else { - // If is not commenting then remove all comments found. - for (const pos of positions) { - const from = pos - closeMultiline.length > 0 ? pos - closeMultiline.length : 0; - const offset = text.substr(from, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; + + if (text.substr(positions[i], openMultiline.length) !== openMultiline) { textChanges.push({ - newText: "", + newText: openMultiline, span: { - length: openMultiline.length, - start: pos - offset + length: 0, + start: positions[i] } }); } } - return textChanges; + // Insert open comment if the last position is not a comment already. + if (textChanges.length % 2 !== 0) { + textChanges.push({ + newText: closeMultiline, + span: { + length: 0, + start: positions[positions.length - 1] + } + }); + } } - - function commentSelection(fileName: string, textRange: TextRange): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const { firstLine, lastLine } = getLinesForRange(sourceFile, textRange); - - // If there is a selection that is on the same line, add multiline. - return firstLine === lastLine && textRange.pos !== textRange.end - ? toggleMultilineComment(fileName, textRange, /*insertComment*/ true) - : toggleLineComment(fileName, textRange, /*insertComment*/ true); + else { + // If is not commenting then remove all comments found. + for (const pos of positions) { + const from = pos - closeMultiline.length > 0 ? pos - closeMultiline.length : 0; + const offset = text.substr(from, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; + textChanges.push({ + newText: "", + span: { + length: openMultiline.length, + start: pos - offset + } + }); + } } - function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const textChanges: TextChange[] = []; - const { pos } = textRange; - let { end } = textRange; - - // If cursor is not a selection we need to increase the end position - // to include the start of the comment. - if (pos === end) { - end += isInsideJsxElement(sourceFile, pos) ? 2 : 1; - } + return textChanges; + } - for (let i = pos; i <= end; i++) { - const commentRange = isInComment(sourceFile, i); - if (commentRange) { - switch (commentRange.kind) { - case SyntaxKind.SingleLineCommentTrivia: - textChanges.push.apply(textChanges, toggleLineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); - break; - case SyntaxKind.MultiLineCommentTrivia: - textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); - } + function commentSelection(fileName: string, textRange: TextRange): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const { firstLine, lastLine } = getLinesForRange(sourceFile, textRange); - i = commentRange.end + 1; - } - } + // If there is a selection that is on the same line, add multiline. + return firstLine === lastLine && textRange.pos !== textRange.end + ? toggleMultilineComment(fileName, textRange, /*insertComment*/ true) + : toggleLineComment(fileName, textRange, /*insertComment*/ true); + } - return textChanges; - } + function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const textChanges: TextChange[] = []; + const { pos } = textRange; + let { end } = textRange; - function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean { - return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) || - isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent); + // If cursor is not a selection we need to increase the end position + // to include the start of the comment. + if (pos === end) { + end += isInsideJsxElement(sourceFile, pos) ? 2 : 1; } - function isUnclosedFragment({ closingFragment, parent }: JsxFragment): boolean { - return !!(closingFragment.flags & NodeFlags.ThisNodeHasError) || (isJsxFragment(parent) && isUnclosedFragment(parent)); - } + for (let i = pos; i <= end; i++) { + const commentRange = isInComment(sourceFile, i); + if (commentRange) { + switch (commentRange.kind) { + case SyntaxKind.SingleLineCommentTrivia: + textChanges.push.apply(textChanges, toggleLineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); + break; + case SyntaxKind.MultiLineCommentTrivia: + textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); + } - function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const range = formatting.getRangeOfEnclosingComment(sourceFile, position); - return range && (!onlyMultiLine || range.kind === SyntaxKind.MultiLineCommentTrivia) ? createTextSpanFromRange(range) : undefined; + i = commentRange.end + 1; + } } - function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { - // Note: while getting todo comments seems like a syntactic operation, we actually - // treat it as a semantic operation here. This is because we expect our host to call - // this on every single file. If we treat this syntactically, then that will cause - // us to populate and throw away the tree in our syntax tree cache for each file. By - // treating this as a semantic operation, we can access any tree without throwing - // anything away. - synchronizeHostData(); - - const sourceFile = getValidSourceFile(fileName); - - cancellationToken.throwIfCancellationRequested(); - - const fileContents = sourceFile.text; - const result: TodoComment[] = []; + return textChanges; + } - // Exclude node_modules files as we don't want to show the todos of external libraries. - if (descriptors.length > 0 && !isNodeModulesFile(sourceFile.fileName)) { - const regExp = getTodoCommentsRegExp(); + function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean { + return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) || + isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent); + } - let matchArray: RegExpExecArray | null; - while (matchArray = regExp.exec(fileContents)) { - cancellationToken.throwIfCancellationRequested(); + function isUnclosedFragment({ closingFragment, parent }: JsxFragment): boolean { + return !!(closingFragment.flags & NodeFlags.ThisNodeHasError) || (isJsxFragment(parent) && isUnclosedFragment(parent)); + } - // If we got a match, here is what the match array will look like. Say the source text is: - // - // " // hack 1" - // - // The result array with the regexp: will be: - // - // ["// hack 1", "// ", "hack 1", undefined, "hack"] - // - // Here are the relevant capture groups: - // 0) The full match for the entire regexp. - // 1) The preamble to the message portion. - // 2) The message portion. - // 3...N) The descriptor that was matched - by index. 'undefined' for each - // descriptor that didn't match. an actual value if it did match. - // - // i.e. 'undefined' in position 3 above means TODO(jason) didn't match. - // "hack" in position 4 means HACK did match. - const firstDescriptorCaptureIndex = 3; - Debug.assert(matchArray.length === descriptors.length + firstDescriptorCaptureIndex); - - const preamble = matchArray[1]; - const matchPosition = matchArray.index + preamble.length; - - // OK, we have found a match in the file. This is only an acceptable match if - // it is contained within a comment. - if (!isInComment(sourceFile, matchPosition)) { - continue; - } + function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const range = formatting.getRangeOfEnclosingComment(sourceFile, position); + return range && (!onlyMultiLine || range.kind === SyntaxKind.MultiLineCommentTrivia) ? createTextSpanFromRange(range) : undefined; + } - let descriptor: TodoCommentDescriptor | undefined; - for (let i = 0; i < descriptors.length; i++) { - if (matchArray[i + firstDescriptorCaptureIndex]) { - descriptor = descriptors[i]; - } - } - if (descriptor === undefined) return Debug.fail(); + function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { + // Note: while getting todo comments seems like a syntactic operation, we actually + // treat it as a semantic operation here. This is because we expect our host to call + // this on every single file. If we treat this syntactically, then that will cause + // us to populate and throw away the tree in our syntax tree cache for each file. By + // treating this as a semantic operation, we can access any tree without throwing + // anything away. + synchronizeHostData(); - // We don't want to match something like 'TODOBY', so we make sure a non - // letter/digit follows the match. - if (isLetterOrDigit(fileContents.charCodeAt(matchPosition + descriptor.text.length))) { - continue; - } + const sourceFile = getValidSourceFile(fileName); - const message = matchArray[2]; - result.push({ descriptor, message, position: matchPosition }); - } - } + cancellationToken.throwIfCancellationRequested(); - return result; + const fileContents = sourceFile.text; + const result: TodoComment[] = []; - function escapeRegExp(str: string): string { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - } + // Exclude node_modules files as we don't want to show the todos of external libraries. + if (descriptors.length > 0 && !isNodeModulesFile(sourceFile.fileName)) { + const regExp = getTodoCommentsRegExp(); - function getTodoCommentsRegExp(): RegExp { - // NOTE: `?:` means 'non-capture group'. It allows us to have groups without having to - // filter them out later in the final result array. + let matchArray: RegExpExecArray | null; + while (matchArray = regExp.exec(fileContents)) { + cancellationToken.throwIfCancellationRequested(); - // TODO comments can appear in one of the following forms: - // - // 1) // TODO or /////////// TODO - // - // 2) /* TODO or /********** TODO + // If we got a match, here is what the match array will look like. Say the source text is: // - // 3) /* - // * TODO - // */ + // " // hack 1" // - // The following three regexps are used to match the start of the text up to the TODO - // comment portion. - const singleLineCommentStart = /(?:\/\/+\s*)/.source; - const multiLineCommentStart = /(?:\/\*+\s*)/.source; - const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source; - - // Match any of the above three TODO comment start regexps. - // Note that the outermost group *is* a capture group. We want to capture the preamble - // so that we can determine the starting position of the TODO comment match. - const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")"; - - // Takes the descriptors and forms a regexp that matches them as if they were literals. - // For example, if the descriptors are "TODO(jason)" and "HACK", then this will be: + // The result array with the regexp: will be: // - // (?:(TODO\(jason\))|(HACK)) + // ["// hack 1", "// ", "hack 1", undefined, "hack"] // - // Note that the outermost group is *not* a capture group, but the innermost groups - // *are* capture groups. By capturing the inner literals we can determine after - // matching which descriptor we are dealing with. - const literals = "(?:" + map(descriptors, d => "(" + escapeRegExp(d.text) + ")").join("|") + ")"; - - // After matching a descriptor literal, the following regexp matches the rest of the - // text up to the end of the line (or */). - const endOfLineOrEndOfComment = /(?:$|\*\/)/.source; - const messageRemainder = /(?:.*?)/.source; - - // This is the portion of the match we'll return as part of the TODO comment result. We - // match the literal portion up to the end of the line or end of comment. - const messagePortion = "(" + literals + messageRemainder + ")"; - const regExpString = preamble + messagePortion + endOfLineOrEndOfComment; - - // The final regexp will look like this: - // /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim - - // The flags of the regexp are important here. - // 'g' is so that we are doing a global search and can find matches several times - // in the input. + // Here are the relevant capture groups: + // 0) The full match for the entire regexp. + // 1) The preamble to the message portion. + // 2) The message portion. + // 3...N) The descriptor that was matched - by index. 'undefined' for each + // descriptor that didn't match. an actual value if it did match. // - // 'i' is for case insensitivity (We do this to match C# TODO comment code). - // - // 'm' is so we can find matches in a multi-line input. - return new RegExp(regExpString, "gim"); - } + // i.e. 'undefined' in position 3 above means TODO(jason) didn't match. + // "hack" in position 4 means HACK did match. + const firstDescriptorCaptureIndex = 3; + Debug.assert(matchArray.length === descriptors.length + firstDescriptorCaptureIndex); - function isLetterOrDigit(char: number): boolean { - return (char >= CharacterCodes.a && char <= CharacterCodes.z) || - (char >= CharacterCodes.A && char <= CharacterCodes.Z) || - (char >= CharacterCodes._0 && char <= CharacterCodes._9); - } + const preamble = matchArray[1]; + const matchPosition = matchArray.index + preamble.length; + + // OK, we have found a match in the file. This is only an acceptable match if + // it is contained within a comment. + if (!isInComment(sourceFile, matchPosition)) { + continue; + } + + let descriptor: TodoCommentDescriptor | undefined; + for (let i = 0; i < descriptors.length; i++) { + if (matchArray[i + firstDescriptorCaptureIndex]) { + descriptor = descriptors[i]; + } + } + if (descriptor === undefined) return Debug.fail(); + + // We don't want to match something like 'TODOBY', so we make sure a non + // letter/digit follows the match. + if (isLetterOrDigit(fileContents.charCodeAt(matchPosition + descriptor.text.length))) { + continue; + } - function isNodeModulesFile(path: string): boolean { - return stringContains(path, "/node_modules/"); + const message = matchArray[2]; + result.push({ descriptor, message, position: matchPosition }); } } - function getRenameInfo(fileName: string, position: number, preferences: UserPreferences | RenameInfoOptions | undefined): RenameInfo { - synchronizeHostData(); - return Rename.getRenameInfo(program, getValidSourceFile(fileName), position, preferences || {}); - } - - function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings, triggerReason?: RefactorTriggerReason, kind?: string): RefactorContext { - const [startPosition, endPosition] = typeof positionOrRange === "number" ? [positionOrRange, undefined] : [positionOrRange.pos, positionOrRange.end]; - return { - file, - startPosition, - endPosition, - program: getProgram()!, - host, - formatContext: formatting.getFormatContext(formatOptions!, host), // TODO: GH#18217 - cancellationToken, - preferences, - triggerReason, - kind - }; - } + return result; - function getInlayHintsContext(file: SourceFile, span: TextSpan, preferences: UserPreferences): InlayHintsContext { - return { - file, - program: getProgram()!, - host, - span, - preferences, - cancellationToken, - }; + function escapeRegExp(str: string): string { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); } - function getSmartSelectionRange(fileName: string, position: number): SelectionRange { - return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName)); + function getTodoCommentsRegExp(): RegExp { + // NOTE: `?:` means 'non-capture group'. It allows us to have groups without having to + // filter them out later in the final result array. + + // TODO comments can appear in one of the following forms: + // + // 1) // TODO or /////////// TODO + // + // 2) /* TODO or /********** TODO + // + // 3) /* + // * TODO + // */ + // + // The following three regexps are used to match the start of the text up to the TODO + // comment portion. + const singleLineCommentStart = /(?:\/\/+\s*)/.source; + const multiLineCommentStart = /(?:\/\*+\s*)/.source; + const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source; + + // Match any of the above three TODO comment start regexps. + // Note that the outermost group *is* a capture group. We want to capture the preamble + // so that we can determine the starting position of the TODO comment match. + const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")"; + + // Takes the descriptors and forms a regexp that matches them as if they were literals. + // For example, if the descriptors are "TODO(jason)" and "HACK", then this will be: + // + // (?:(TODO\(jason\))|(HACK)) + // + // Note that the outermost group is *not* a capture group, but the innermost groups + // *are* capture groups. By capturing the inner literals we can determine after + // matching which descriptor we are dealing with. + const literals = "(?:" + map(descriptors, d => "(" + escapeRegExp(d.text) + ")").join("|") + ")"; + + // After matching a descriptor literal, the following regexp matches the rest of the + // text up to the end of the line (or */). + const endOfLineOrEndOfComment = /(?:$|\*\/)/.source; + const messageRemainder = /(?:.*?)/.source; + + // This is the portion of the match we'll return as part of the TODO comment result. We + // match the literal portion up to the end of the line or end of comment. + const messagePortion = "(" + literals + messageRemainder + ")"; + const regExpString = preamble + messagePortion + endOfLineOrEndOfComment; + + // The final regexp will look like this: + // /((?:\/\/+\s*)|(?:\/\*+\s*)|(?:^(?:\s|\*)*))((?:(TODO\(jason\))|(HACK))(?:.*?))(?:$|\*\/)/gim + + // The flags of the regexp are important here. + // 'g' is so that we are doing a global search and can find matches several times + // in the input. + // + // 'i' is for case insensitivity (We do this to match C# TODO comment code). + // + // 'm' is so we can find matches in a multi-line input. + return new RegExp(regExpString, "gim"); } - function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string): ApplicableRefactorInfo[] { - synchronizeHostData(); - const file = getValidSourceFile(fileName); - return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind)); + function isLetterOrDigit(char: number): boolean { + return (char >= CharacterCodes.a && char <= CharacterCodes.z) || + (char >= CharacterCodes.A && char <= CharacterCodes.Z) || + (char >= CharacterCodes._0 && char <= CharacterCodes._9); } - function getEditsForRefactor( - fileName: string, - formatOptions: FormatCodeSettings, - positionOrRange: number | TextRange, - refactorName: string, - actionName: string, - preferences: UserPreferences = emptyOptions, - ): RefactorEditInfo | undefined { - synchronizeHostData(); - const file = getValidSourceFile(fileName); - return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName); + function isNodeModulesFile(path: string): boolean { + return stringContains(path, "/node_modules/"); } + } - function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { - // Go to Definition supports returning a zero-length span at position 0 for - // non-existent files. We need to special-case the conversion of position 0 - // to avoid a crash trying to get the text for that file, since this function - // otherwise assumes that 'fileName' is the name of a file that exists. - if (position === 0) { - return { line: 0, character: 0 }; - } - return sourceMapper.toLineColumnOffset(fileName, position); - } - - function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { - synchronizeHostData(); - const declarations = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position)); - return declarations && mapOneOrMany(declarations, declaration => CallHierarchy.createCallHierarchyItem(program, declaration)); - } - - function provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[] { - synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); - const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); - return declaration ? CallHierarchy.getIncomingCalls(program, declaration, cancellationToken) : []; - } - - function provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[] { - synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); - const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); - return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : []; - } - - function provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences = emptyOptions): InlayHint[] { - synchronizeHostData(); - const sourceFile = getValidSourceFile(fileName); - return InlayHints.provideInlayHints(getInlayHintsContext(sourceFile, span, preferences)); - } - - const ls: LanguageService = { - dispose, - cleanupSemanticCache, - getSyntacticDiagnostics, - getSemanticDiagnostics, - getSuggestionDiagnostics, - getCompilerOptionsDiagnostics, - getSyntacticClassifications, - getSemanticClassifications, - getEncodedSyntacticClassifications, - getEncodedSemanticClassifications, - getCompletionsAtPosition, - getCompletionEntryDetails, - getCompletionEntrySymbol, - getSignatureHelpItems, - getQuickInfoAtPosition, - getDefinitionAtPosition, - getDefinitionAndBoundSpan, - getImplementationAtPosition, - getTypeDefinitionAtPosition, - getReferencesAtPosition, - findReferences, - getFileReferences, - getOccurrencesAtPosition, - getDocumentHighlights, - getNameOrDottedNameSpan, - getBreakpointStatementAtPosition, - getNavigateToItems, - getRenameInfo, - getSmartSelectionRange, - findRenameLocations, - getNavigationBarItems, - getNavigationTree, - getOutliningSpans, - getTodoComments, - getBraceMatchingAtPosition, - getIndentationAtPosition, - getFormattingEditsForRange, - getFormattingEditsForDocument, - getFormattingEditsAfterKeystroke, - getDocCommentTemplateAtPosition, - isValidBraceCompletionAtPosition, - getJsxClosingTagAtPosition, - getSpanOfEnclosingComment, - getCodeFixesAtPosition, - getCombinedCodeFix, - applyCodeActionCommand, - organizeImports, - getEditsForFileRename, - getEmitOutput, - getNonBoundSourceFile, - getProgram, - getCurrentProgram: () => program, - getAutoImportProvider, - updateIsDefinitionOfReferencedSymbols, - getApplicableRefactors, - getEditsForRefactor, - toLineColumnOffset, - getSourceMapper: () => sourceMapper, - clearSourceMapperCache: () => sourceMapper.clearCache(), - prepareCallHierarchy, - provideCallHierarchyIncomingCalls, - provideCallHierarchyOutgoingCalls, - toggleLineComment, - toggleMultilineComment, - commentSelection, - uncommentSelection, - provideInlayHints, + function getRenameInfo(fileName: string, position: number, preferences: UserPreferences | RenameInfoOptions | undefined): RenameInfo { + synchronizeHostData(); + return Rename.getRenameInfo(program, getValidSourceFile(fileName), position, preferences || {}); + } + + function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings, triggerReason?: RefactorTriggerReason, kind?: string): RefactorContext { + const [startPosition, endPosition] = typeof positionOrRange === "number" ? [positionOrRange, undefined] : [positionOrRange.pos, positionOrRange.end]; + return { + file, + startPosition, + endPosition, + program: getProgram()!, + host, + formatContext: formatting.getFormatContext(formatOptions!, host), // TODO: GH#18217 + cancellationToken, + preferences, + triggerReason, + kind }; + } - switch (languageServiceMode) { - case LanguageServiceMode.Semantic: - break; - case LanguageServiceMode.PartialSemantic: - invalidOperationsInPartialSemanticMode.forEach(key => - ls[key] = () => { - throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.PartialSemantic`); - } - ); - break; - case LanguageServiceMode.Syntactic: - invalidOperationsInSyntacticMode.forEach(key => - ls[key] = () => { - throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.Syntactic`); - } - ); - break; - default: - Debug.assertNever(languageServiceMode); - } - return ls; + function getInlayHintsContext(file: SourceFile, span: TextSpan, preferences: UserPreferences): InlayHintsContext { + return { + file, + program: getProgram()!, + host, + span, + preferences, + cancellationToken, + }; } - /* @internal */ - /** Names in the name table are escaped, so an identifier `__foo` will have a name table entry `___foo`. */ - export function getNameTable(sourceFile: SourceFile): UnderscoreEscapedMap { - if (!sourceFile.nameTable) { - initializeNameTable(sourceFile); - } + function getSmartSelectionRange(fileName: string, position: number): SelectionRange { + return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName)); + } - return sourceFile.nameTable!; // TODO: GH#18217 + function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string): ApplicableRefactorInfo[] { + synchronizeHostData(); + const file = getValidSourceFile(fileName); + return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind)); } - function initializeNameTable(sourceFile: SourceFile): void { - const nameTable = sourceFile.nameTable = new Map(); - sourceFile.forEachChild(function walk(node) { - if (isIdentifier(node) && !isTagName(node) && node.escapedText || isStringOrNumericLiteralLike(node) && literalIsName(node)) { - const text = getEscapedTextOfIdentifierOrLiteral(node); - nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); - } - else if (isPrivateIdentifier(node)) { - const text = node.escapedText; - nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); - } + function getEditsForRefactor( + fileName: string, + formatOptions: FormatCodeSettings, + positionOrRange: number | TextRange, + refactorName: string, + actionName: string, + preferences: UserPreferences = emptyOptions, + ): RefactorEditInfo | undefined { + synchronizeHostData(); + const file = getValidSourceFile(fileName); + return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName); + } - forEachChild(node, walk); - if (hasJSDocNodes(node)) { - for (const jsDoc of node.jsDoc!) { - forEachChild(jsDoc, walk); - } - } - }); + function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { + // Go to Definition supports returning a zero-length span at position 0 for + // non-existent files. We need to special-case the conversion of position 0 + // to avoid a crash trying to get the text for that file, since this function + // otherwise assumes that 'fileName' is the name of a file that exists. + if (position === 0) { + return { line: 0, character: 0 }; + } + return sourceMapper.toLineColumnOffset(fileName, position); } - /** - * We want to store any numbers/strings if they were a name that could be - * related to a declaration. So, if we have 'import x = require("something")' - * then we want 'something' to be in the name table. Similarly, if we have - * "a['propname']" then we want to store "propname" in the name table. - */ - function literalIsName(node: StringLiteralLike | NumericLiteral): boolean { - return isDeclarationName(node) || - node.parent.kind === SyntaxKind.ExternalModuleReference || - isArgumentOfElementAccessExpression(node) || - isLiteralComputedPropertyDeclarationName(node); + function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { + synchronizeHostData(); + const declarations = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position)); + return declarations && mapOneOrMany(declarations, declaration => CallHierarchy.createCallHierarchyItem(program, declaration)); } - /** - * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 } - */ - /* @internal */ - export function getContainingObjectLiteralElement(node: Node): ObjectLiteralElementWithName | undefined { - const element = getContainingObjectLiteralElementWorker(node); - return element && (isObjectLiteralExpression(element.parent) || isJsxAttributes(element.parent)) ? element as ObjectLiteralElementWithName : undefined; + function provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); + return declaration ? CallHierarchy.getIncomingCalls(program, declaration, cancellationToken) : []; } - function getContainingObjectLiteralElementWorker(node: Node): ObjectLiteralElement | undefined { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.NumericLiteral: - if (node.parent.kind === SyntaxKind.ComputedPropertyName) { - return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined; + + function provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position))); + return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : []; + } + + function provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences = emptyOptions): InlayHint[] { + synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + return InlayHints.provideInlayHints(getInlayHintsContext(sourceFile, span, preferences)); + } + + const ls: LanguageService = { + dispose, + cleanupSemanticCache, + getSyntacticDiagnostics, + getSemanticDiagnostics, + getSuggestionDiagnostics, + getCompilerOptionsDiagnostics, + getSyntacticClassifications, + getSemanticClassifications, + getEncodedSyntacticClassifications, + getEncodedSemanticClassifications, + getCompletionsAtPosition, + getCompletionEntryDetails, + getCompletionEntrySymbol, + getSignatureHelpItems, + getQuickInfoAtPosition, + getDefinitionAtPosition, + getDefinitionAndBoundSpan, + getImplementationAtPosition, + getTypeDefinitionAtPosition, + getReferencesAtPosition, + findReferences, + getFileReferences, + getOccurrencesAtPosition, + getDocumentHighlights, + getNameOrDottedNameSpan, + getBreakpointStatementAtPosition, + getNavigateToItems, + getRenameInfo, + getSmartSelectionRange, + findRenameLocations, + getNavigationBarItems, + getNavigationTree, + getOutliningSpans, + getTodoComments, + getBraceMatchingAtPosition, + getIndentationAtPosition, + getFormattingEditsForRange, + getFormattingEditsForDocument, + getFormattingEditsAfterKeystroke, + getDocCommentTemplateAtPosition, + isValidBraceCompletionAtPosition, + getJsxClosingTagAtPosition, + getSpanOfEnclosingComment, + getCodeFixesAtPosition, + getCombinedCodeFix, + applyCodeActionCommand, + organizeImports, + getEditsForFileRename, + getEmitOutput, + getNonBoundSourceFile, + getProgram, + getCurrentProgram: () => program, + getAutoImportProvider, + updateIsDefinitionOfReferencedSymbols, + getApplicableRefactors, + getEditsForRefactor, + toLineColumnOffset, + getSourceMapper: () => sourceMapper, + clearSourceMapperCache: () => sourceMapper.clearCache(), + prepareCallHierarchy, + provideCallHierarchyIncomingCalls, + provideCallHierarchyOutgoingCalls, + toggleLineComment, + toggleMultilineComment, + commentSelection, + uncommentSelection, + provideInlayHints, + }; + + switch (languageServiceMode) { + case LanguageServiceMode.Semantic: + break; + case LanguageServiceMode.PartialSemantic: + invalidOperationsInPartialSemanticMode.forEach(key => + ls[key] = () => { + throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.PartialSemantic`); + } + ); + break; + case LanguageServiceMode.Syntactic: + invalidOperationsInSyntacticMode.forEach(key => + ls[key] = () => { + throw new Error(`LanguageService Operation: ${key} not allowed in LanguageServiceMode.Syntactic`); } - // falls through + ); + break; + default: + Debug.assertNever(languageServiceMode); + } + return ls; +} - case SyntaxKind.Identifier: - return isObjectLiteralElement(node.parent) && - (node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === SyntaxKind.JsxAttributes) && - node.parent.name === node ? node.parent : undefined; - } - return undefined; +/** + * Names in the name table are escaped, so an identifier `__foo` will have a name table entry `___foo`. + * + * @internal + */ +export function getNameTable(sourceFile: SourceFile): UnderscoreEscapedMap { + if (!sourceFile.nameTable) { + initializeNameTable(sourceFile); } - /* @internal */ - export type ObjectLiteralElementWithName = ObjectLiteralElement & { name: PropertyName; parent: ObjectLiteralExpression | JsxAttributes }; + return sourceFile.nameTable!; // TODO: GH#18217 +} + +function initializeNameTable(sourceFile: SourceFile): void { + const nameTable = sourceFile.nameTable = new Map(); + sourceFile.forEachChild(function walk(node) { + if (isIdentifier(node) && !isTagName(node) && node.escapedText || isStringOrNumericLiteralLike(node) && literalIsName(node)) { + const text = getEscapedTextOfIdentifierOrLiteral(node); + nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); + } + else if (isPrivateIdentifier(node)) { + const text = node.escapedText; + nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1); + } - function getSymbolAtLocationForQuickInfo(node: Node, checker: TypeChecker): Symbol | undefined { - const object = getContainingObjectLiteralElement(node); - if (object) { - const contextualType = checker.getContextualType(object.parent); - const properties = contextualType && getPropertySymbolsFromContextualType(object, checker, contextualType, /*unionSymbolOk*/ false); - if (properties && properties.length === 1) { - return first(properties); + forEachChild(node, walk); + if (hasJSDocNodes(node)) { + for (const jsDoc of node.jsDoc!) { + forEachChild(jsDoc, walk); } } - return checker.getSymbolAtLocation(node); + }); +} + +/** + * We want to store any numbers/strings if they were a name that could be + * related to a declaration. So, if we have 'import x = require("something")' + * then we want 'something' to be in the name table. Similarly, if we have + * "a['propname']" then we want to store "propname" in the name table. + */ +function literalIsName(node: StringLiteralLike | NumericLiteral): boolean { + return isDeclarationName(node) || + node.parent.kind === SyntaxKind.ExternalModuleReference || + isArgumentOfElementAccessExpression(node) || + isLiteralComputedPropertyDeclarationName(node); +} + +/** + * Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 } + * + * @internal + */ +export function getContainingObjectLiteralElement(node: Node): ObjectLiteralElementWithName | undefined { + const element = getContainingObjectLiteralElementWorker(node); + return element && (isObjectLiteralExpression(element.parent) || isJsxAttributes(element.parent)) ? element as ObjectLiteralElementWithName : undefined; +} +function getContainingObjectLiteralElementWorker(node: Node): ObjectLiteralElement | undefined { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.NumericLiteral: + if (node.parent.kind === SyntaxKind.ComputedPropertyName) { + return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined; + } + // falls through + + case SyntaxKind.Identifier: + return isObjectLiteralElement(node.parent) && + (node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression || node.parent.parent.kind === SyntaxKind.JsxAttributes) && + node.parent.name === node ? node.parent : undefined; } + return undefined; +} - /** Gets all symbols for one property. Does not get symbols for every property. */ - /* @internal */ - export function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: TypeChecker, contextualType: Type, unionSymbolOk: boolean): readonly Symbol[] { - const name = getNameFromPropertyName(node.name); - if (!name) return emptyArray; - if (!contextualType.isUnion()) { - const symbol = contextualType.getProperty(name); - return symbol ? [symbol] : emptyArray; - } +/** @internal */ +export type ObjectLiteralElementWithName = ObjectLiteralElement & { name: PropertyName; parent: ObjectLiteralExpression | JsxAttributes }; - const discriminatedPropertySymbols = mapDefined(contextualType.types, t => (isObjectLiteralExpression(node.parent)|| isJsxAttributes(node.parent)) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name)); - if (unionSymbolOk && (discriminatedPropertySymbols.length === 0 || discriminatedPropertySymbols.length === contextualType.types.length)) { - const symbol = contextualType.getProperty(name); - if (symbol) return [symbol]; +function getSymbolAtLocationForQuickInfo(node: Node, checker: TypeChecker): Symbol | undefined { + const object = getContainingObjectLiteralElement(node); + if (object) { + const contextualType = checker.getContextualType(object.parent); + const properties = contextualType && getPropertySymbolsFromContextualType(object, checker, contextualType, /*unionSymbolOk*/ false); + if (properties && properties.length === 1) { + return first(properties); } - if (discriminatedPropertySymbols.length === 0) { - // Bad discriminant -- do again without discriminating - return mapDefined(contextualType.types, t => t.getProperty(name)); - } - return discriminatedPropertySymbols; } + return checker.getSymbolAtLocation(node); +} - function isArgumentOfElementAccessExpression(node: Node) { - return node && - node.parent && - node.parent.kind === SyntaxKind.ElementAccessExpression && - (node.parent as ElementAccessExpression).argumentExpression === node; +/** + * Gets all symbols for one property. Does not get symbols for every property. + * + * @internal + */ +export function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: TypeChecker, contextualType: Type, unionSymbolOk: boolean): readonly Symbol[] { + const name = getNameFromPropertyName(node.name); + if (!name) return emptyArray; + if (!contextualType.isUnion()) { + const symbol = contextualType.getProperty(name); + return symbol ? [symbol] : emptyArray; } - /// getDefaultLibraryFilePath - declare const __dirname: string; + const discriminatedPropertySymbols = mapDefined(contextualType.types, t => (isObjectLiteralExpression(node.parent)|| isJsxAttributes(node.parent)) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name)); + if (unionSymbolOk && (discriminatedPropertySymbols.length === 0 || discriminatedPropertySymbols.length === contextualType.types.length)) { + const symbol = contextualType.getProperty(name); + if (symbol) return [symbol]; + } + if (discriminatedPropertySymbols.length === 0) { + // Bad discriminant -- do again without discriminating + return mapDefined(contextualType.types, t => t.getProperty(name)); + } + return discriminatedPropertySymbols; +} - /** - * Get the path of the default library files (lib.d.ts) as distributed with the typescript - * node package. - * The functionality is not supported if the ts module is consumed outside of a node module. - */ - export function getDefaultLibFilePath(options: CompilerOptions): string { - // Check __dirname is defined and that we are on a node.js system. - if (typeof __dirname !== "undefined") { - return combinePaths(__dirname, getDefaultLibFileName(options)); - } +function isArgumentOfElementAccessExpression(node: Node) { + return node && + node.parent && + node.parent.kind === SyntaxKind.ElementAccessExpression && + (node.parent as ElementAccessExpression).argumentExpression === node; +} - throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. "); +/** + * Get the path of the default library files (lib.d.ts) as distributed with the typescript + * node package. + * The functionality is not supported if the ts module is consumed outside of a node module. + */ +export function getDefaultLibFilePath(options: CompilerOptions): string { + if (ts.sys) { + return combinePaths(getDirectoryPath(normalizePath(ts.sys.getExecutingFilePath())), getDefaultLibFileName(options)); } - setObjectAllocator(getServicesObjectAllocator()); + throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. "); } + +setObjectAllocator(getServicesObjectAllocator()); diff --git a/src/services/shims.ts b/src/services/shims.ts index af0f424a736ff..6303302620d7b 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -1,3 +1,18 @@ +import { + Classifications, Classifier, clear, CompilerOptions, CompletionEntryData, createClassifier, createDocumentRegistry, + createGetCanonicalFileName, createLanguageService, createTextChangeRange, createTextSpan, Diagnostic, + diagnosticCategoryName, DocCommentTemplateOptions, DocumentRegistry, EditorOptions, EmitOutput, emptyOptions, + EndOfLineState, ESMap, Extension, extensionFromPath, FileReference, filter, flattenDiagnosticMessageText, + FormatCodeOptions, FormatCodeSettings, getAutomaticTypeDirectiveNames, GetCompletionsAtPositionOptions, + getDefaultCompilerOptions, getDirectoryPath, getFileMatcherPatterns, getNewLineOrDefaultFromHost, getProperty, + getSnapshotText, HostCancellationToken, IScriptSnapshot, isString, JsTyping, LanguageService, LanguageServiceHost, + map, MapLike, ModuleResolutionHost, normalizeSlashes, OperationCanceledException, ParseConfigHost, + parseJsonSourceFileConfigFileContent, parseJsonText, preProcessFile, ReadonlyESMap, ResolvedModuleFull, + ResolvedTypeReferenceDirective, resolveModuleName, resolveTypeReferenceDirective, ScriptKind, + SemanticClassificationFormat, servicesVersion, SignatureHelpItemsOptions, TextChangeRange, TextRange, TextSpan, + ThrottledCancellationToken, timestamp, toFileNameLowerCase, toPath, TypeAcquisition, UserPreferences, +} from "./_namespaces/ts"; + // // Copyright (c) Microsoft Corporation. All rights reserved. // @@ -13,7 +28,7 @@ // limitations under the License. // -/* @internal */ +/** @internal */ let debugObjectHost: { CollectGarbage(): void } = (function (this: any) { // eslint-disable-line prefer-const return this; })(); @@ -21,1339 +36,1360 @@ let debugObjectHost: { CollectGarbage(): void } = (function (this: any) { // esl // We need to use 'null' to interface with the managed side. /* eslint-disable local/no-in-operator */ -/* @internal */ -namespace ts { - interface DiscoverTypingsInfo { - fileNames: string[]; // The file names that belong to the same project. - projectRootPath: string; // The path to the project root directory - safeListPath: string; // The path used to retrieve the safe list - packageNameToTypingLocation: ESMap; // The map of package names to their cached typing locations and installed versions - typeAcquisition: TypeAcquisition; // Used to customize the type acquisition process - compilerOptions: CompilerOptions; // Used as a source for typing inference - unresolvedImports: readonly string[]; // List of unresolved module ids from imports - typesRegistry: ReadonlyESMap>; // The map of available typings in npm to maps of TS versions to their latest supported versions - } - - export interface ScriptSnapshotShim { - /** Gets a portion of the script snapshot specified by [start, end). */ - getText(start: number, end: number): string; - - /** Gets the length of this script snapshot. */ - getLength(): number; - - /** - * Returns a JSON-encoded value of the type: - * { span: { start: number; length: number }; newLength: number } - * - * Or undefined value if there was no change. - */ - getChangeRange(oldSnapshot: ScriptSnapshotShim): string | undefined; - - /** Releases all resources held by this script snapshot */ - dispose?(): void; - } - - export interface Logger { - log(s: string): void; - trace(s: string): void; - error(s: string): void; - } +interface DiscoverTypingsInfo { + fileNames: string[]; // The file names that belong to the same project. + projectRootPath: string; // The path to the project root directory + safeListPath: string; // The path used to retrieve the safe list + packageNameToTypingLocation: ESMap; // The map of package names to their cached typing locations and installed versions + typeAcquisition: TypeAcquisition; // Used to customize the type acquisition process + compilerOptions: CompilerOptions; // Used as a source for typing inference + unresolvedImports: readonly string[]; // List of unresolved module ids from imports + typesRegistry: ReadonlyESMap>; // The map of available typings in npm to maps of TS versions to their latest supported versions +} - /** Public interface of the host of a language service shim instance. */ - export interface LanguageServiceShimHost extends Logger { - getCompilationSettings(): string; +/** @internal */ +export interface ScriptSnapshotShim { + /** Gets a portion of the script snapshot specified by [start, end). */ + getText(start: number, end: number): string; - /** Returns a JSON-encoded value of the type: string[] */ - getScriptFileNames(): string; - getScriptKind?(fileName: string): ScriptKind; - getScriptVersion(fileName: string): string; - getScriptSnapshot(fileName: string): ScriptSnapshotShim; - getLocalizedDiagnosticMessages(): string; - getCancellationToken(): HostCancellationToken; - getCurrentDirectory(): string; - getDirectories(path: string): string; - getDefaultLibFileName(options: string): string; - getNewLine?(): string; - getProjectVersion?(): string; - useCaseSensitiveFileNames?(): boolean; - - getTypeRootsVersion?(): number; - readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; - readFile(path: string, encoding?: string): string | undefined; - fileExists(path: string): boolean; - - getModuleResolutionsForFile?(fileName: string): string; - getTypeReferenceDirectiveResolutionsForFile?(fileName: string): string; - directoryExists(directoryName: string): boolean; - } - - /** Public interface of the core-services host instance used in managed side */ - export interface CoreServicesShimHost extends Logger { - directoryExists(directoryName: string): boolean; - fileExists(fileName: string): boolean; - getCurrentDirectory(): string; - getDirectories(path: string): string; - - /** - * Returns a JSON-encoded value of the type: string[] - * - * @param exclude A JSON encoded string[] containing the paths to exclude - * when enumerating the directory. - */ - readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; - - /** - * Read arbitrary text files on disk, i.e. when resolution procedure needs the content of 'package.json' to determine location of bundled typings for node modules - */ - readFile(fileName: string): string | undefined; - realpath?(path: string): string; - trace(s: string): void; - useCaseSensitiveFileNames?(): boolean; - } - - /// - /// Pre-processing - /// - // Note: This is being using by the host (VS) and is marshaled back and forth. - // When changing this make sure the changes are reflected in the managed side as well - export interface ShimsFileReference { - path: string; - position: number; - length: number; - } - - /** Public interface of a language service instance shim. */ - export interface ShimFactory { - registerShim(shim: Shim): void; - unregisterShim(shim: Shim): void; - } - - export interface Shim { - dispose(_dummy: {}): void; - } - - export interface LanguageServiceShim extends Shim { - languageService: LanguageService; - - dispose(_dummy: {}): void; - - refresh(throwOnError: boolean): void; - - cleanupSemanticCache(): void; - - getSyntacticDiagnostics(fileName: string): string; - getSemanticDiagnostics(fileName: string): string; - getSuggestionDiagnostics(fileName: string): string; - getCompilerOptionsDiagnostics(): string; - - getSyntacticClassifications(fileName: string, start: number, length: number): string; - getSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string; - getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string; - getEncodedSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string; - - getCompletionsAtPosition(fileName: string, position: number, preferences: UserPreferences | undefined, formattingSettings: FormatCodeSettings | undefined): string; - getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined): string; - - getQuickInfoAtPosition(fileName: string, position: number): string; - - getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string; - getBreakpointStatementAtPosition(fileName: string, position: number): string; - - getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string; - - /** - * Returns a JSON-encoded value of the type: - * { canRename: boolean, localizedErrorMessage: string, displayName: string, fullDisplayName: string, kind: string, kindModifiers: string, triggerSpan: { start; length } } - */ - getRenameInfo(fileName: string, position: number, preferences: UserPreferences): string; - getSmartSelectionRange(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string, textSpan: { start: number, length: number } }[] - */ - findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string } - * - * Or undefined value if no definition can be found. - */ - getDefinitionAtPosition(fileName: string, position: number): string; - - getDefinitionAndBoundSpan(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string } - * - * Or undefined value if no definition can be found. - */ - getTypeDefinitionAtPosition(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; }[] - */ - getImplementationAtPosition(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[] - */ - getReferencesAtPosition(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { definition: ; references: [] }[] - */ - findReferences(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[] - */ - getFileReferences(fileName: string): string; - - /** - * @deprecated - * Returns a JSON-encoded value of the type: - * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean }[] - */ - getOccurrencesAtPosition(fileName: string, position: number): string; - - /** - * Returns a JSON-encoded value of the type: - * { fileName: string; highlights: { start: number; length: number }[] }[] - * - * @param fileToSearch A JSON encoded string[] containing the file names that should be - * considered when searching. - */ - getDocumentHighlights(fileName: string, position: number, filesToSearch: string): string; - - /** - * Returns a JSON-encoded value of the type: - * { name: string; kind: string; kindModifiers: string; containerName: string; containerKind: string; matchKind: string; fileName: string; textSpan: { start: number; length: number}; } [] = []; - */ - getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string; - - /** - * Returns a JSON-encoded value of the type: - * { text: string; kind: string; kindModifiers: string; bolded: boolean; grayed: boolean; indent: number; spans: { start: number; length: number; }[]; childItems: [] } [] = []; - */ - getNavigationBarItems(fileName: string): string; - - /** Returns a JSON-encoded value of the type ts.NavigationTree. */ - getNavigationTree(fileName: string): string; - - /** - * Returns a JSON-encoded value of the type: - * { textSpan: { start: number, length: number }; hintSpan: { start: number, length: number }; bannerText: string; autoCollapse: boolean } [] = []; - */ - getOutliningSpans(fileName: string): string; - - getTodoComments(fileName: string, todoCommentDescriptors: string): string; - - getBraceMatchingAtPosition(fileName: string, position: number): string; - getIndentationAtPosition(fileName: string, position: number, options: string/*Services.EditorOptions*/): string; - - getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string; - getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string; - getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string; - - /** - * Returns JSON-encoded value of the type TextInsertion. - */ - getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): string; - - /** - * Returns JSON-encoded boolean to indicate whether we should support brace location - * at the current position. - * E.g. we don't want brace completion inside string-literals, comments, etc. - */ - isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string; - - /** - * Returns a JSON-encoded TextSpan | undefined indicating the range of the enclosing comment, if it exists. - */ - getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string; - - prepareCallHierarchy(fileName: string, position: number): string; - provideCallHierarchyIncomingCalls(fileName: string, position: number): string; - provideCallHierarchyOutgoingCalls(fileName: string, position: number): string; - provideInlayHints(fileName: string, span: TextSpan, preference: UserPreferences | undefined): string; - getEmitOutput(fileName: string): string; - getEmitOutputObject(fileName: string): EmitOutput; - - toggleLineComment(fileName: string, textChange: TextRange): string; - toggleMultilineComment(fileName: string, textChange: TextRange): string; - commentSelection(fileName: string, textChange: TextRange): string; - uncommentSelection(fileName: string, textChange: TextRange): string; - } - - export interface ClassifierShim extends Shim { - getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent?: boolean): string; - getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent?: boolean): string; - } - - export interface CoreServicesShim extends Shim { - getAutomaticTypeDirectiveNames(compilerOptionsJson: string): string; - getPreProcessedFileInfo(fileName: string, sourceText: IScriptSnapshot): string; - getTSConfigFileInfo(fileName: string, sourceText: IScriptSnapshot): string; - getDefaultCompilationSettings(): string; - discoverTypings(discoverTypingsJson: string): string; - } - - function logInternalError(logger: Logger, err: Error) { - if (logger) { - logger.log("*INTERNAL ERROR* - Exception in typescript services: " + err.message); - } - } + /** Gets the length of this script snapshot. */ + getLength(): number; - class ScriptSnapshotShimAdapter implements IScriptSnapshot { - constructor(private scriptSnapshotShim: ScriptSnapshotShim) { - } + /** + * Returns a JSON-encoded value of the type: + * { span: { start: number; length: number }; newLength: number } + * + * Or undefined value if there was no change. + */ + getChangeRange(oldSnapshot: ScriptSnapshotShim): string | undefined; - public getText(start: number, end: number): string { - return this.scriptSnapshotShim.getText(start, end); - } + /** Releases all resources held by this script snapshot */ + dispose?(): void; +} - public getLength(): number { - return this.scriptSnapshotShim.getLength(); - } +/** @internal */ +export interface Logger { + log(s: string): void; + trace(s: string): void; + error(s: string): void; +} - public getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined { - const oldSnapshotShim = oldSnapshot as ScriptSnapshotShimAdapter; - const encoded = this.scriptSnapshotShim.getChangeRange(oldSnapshotShim.scriptSnapshotShim); - /* eslint-disable no-null/no-null */ - if (encoded === null) { - return null!; // TODO: GH#18217 - } - /* eslint-enable no-null/no-null */ +/** + * Public interface of the host of a language service shim instance. + * + * @internal + */ +export interface LanguageServiceShimHost extends Logger { + getCompilationSettings(): string; + + /** Returns a JSON-encoded value of the type: string[] */ + getScriptFileNames(): string; + getScriptKind?(fileName: string): ScriptKind; + getScriptVersion(fileName: string): string; + getScriptSnapshot(fileName: string): ScriptSnapshotShim; + getLocalizedDiagnosticMessages(): string; + getCancellationToken(): HostCancellationToken; + getCurrentDirectory(): string; + getDirectories(path: string): string; + getDefaultLibFileName(options: string): string; + getNewLine?(): string; + getProjectVersion?(): string; + useCaseSensitiveFileNames?(): boolean; + + getTypeRootsVersion?(): number; + readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; + readFile(path: string, encoding?: string): string | undefined; + fileExists(path: string): boolean; + + getModuleResolutionsForFile?(fileName: string): string; + getTypeReferenceDirectiveResolutionsForFile?(fileName: string): string; + directoryExists(directoryName: string): boolean; +} - const decoded: { span: { start: number; length: number; }; newLength: number; } = JSON.parse(encoded!); // TODO: GH#18217 - return createTextChangeRange( - createTextSpan(decoded.span.start, decoded.span.length), decoded.newLength); - } +/** + * Public interface of the core-services host instance used in managed side + * + * @internal + */ +export interface CoreServicesShimHost extends Logger { + directoryExists(directoryName: string): boolean; + fileExists(fileName: string): boolean; + getCurrentDirectory(): string; + getDirectories(path: string): string; + + /** + * Returns a JSON-encoded value of the type: string[] + * + * @param exclude A JSON encoded string[] containing the paths to exclude + * when enumerating the directory. + */ + readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; + + /** + * Read arbitrary text files on disk, i.e. when resolution procedure needs the content of 'package.json' to determine location of bundled typings for node modules + */ + readFile(fileName: string): string | undefined; + realpath?(path: string): string; + trace(s: string): void; + useCaseSensitiveFileNames?(): boolean; +} - public dispose(): void { - // if scriptSnapshotShim is a COM object then property check becomes method call with no arguments - // 'in' does not have this effect - if ("dispose" in this.scriptSnapshotShim) { - this.scriptSnapshotShim.dispose!(); // TODO: GH#18217 Can we just use `if (this.scriptSnapshotShim.dispose)`? - } - } +/// +/// Pre-processing +/// +// Note: This is being using by the host (VS) and is marshaled back and forth. +// When changing this make sure the changes are reflected in the managed side as well +/** @internal */ +export interface ShimsFileReference { + path: string; + position: number; + length: number; +} + +/** + * Public interface of a language service instance shim. + * + * @internal + */ +export interface ShimFactory { + registerShim(shim: Shim): void; + unregisterShim(shim: Shim): void; +} + +/** @internal */ +export interface Shim { + dispose(_dummy: {}): void; +} + +/** @internal */ +export interface LanguageServiceShim extends Shim { + languageService: LanguageService; + + dispose(_dummy: {}): void; + + refresh(throwOnError: boolean): void; + + cleanupSemanticCache(): void; + + getSyntacticDiagnostics(fileName: string): string; + getSemanticDiagnostics(fileName: string): string; + getSuggestionDiagnostics(fileName: string): string; + getCompilerOptionsDiagnostics(): string; + + getSyntacticClassifications(fileName: string, start: number, length: number): string; + getSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string; + getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string; + getEncodedSemanticClassifications(fileName: string, start: number, length: number, format?: SemanticClassificationFormat): string; + + getCompletionsAtPosition(fileName: string, position: number, preferences: UserPreferences | undefined, formattingSettings: FormatCodeSettings | undefined): string; + getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined): string; + + getQuickInfoAtPosition(fileName: string, position: number): string; + + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string; + getBreakpointStatementAtPosition(fileName: string, position: number): string; + + getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string; + + /** + * Returns a JSON-encoded value of the type: + * { canRename: boolean, localizedErrorMessage: string, displayName: string, fullDisplayName: string, kind: string, kindModifiers: string, triggerSpan: { start; length } } + */ + getRenameInfo(fileName: string, position: number, preferences: UserPreferences): string; + getSmartSelectionRange(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string, textSpan: { start: number, length: number } }[] + */ + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string } + * + * Or undefined value if no definition can be found. + */ + getDefinitionAtPosition(fileName: string, position: number): string; + + getDefinitionAndBoundSpan(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; kind: string; name: string; containerKind: string; containerName: string } + * + * Or undefined value if no definition can be found. + */ + getTypeDefinitionAtPosition(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; }[] + */ + getImplementationAtPosition(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[] + */ + getReferencesAtPosition(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { definition: ; references: [] }[] + */ + findReferences(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[] + */ + getFileReferences(fileName: string): string; + + /** + * @deprecated + * Returns a JSON-encoded value of the type: + * { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean }[] + */ + getOccurrencesAtPosition(fileName: string, position: number): string; + + /** + * Returns a JSON-encoded value of the type: + * { fileName: string; highlights: { start: number; length: number }[] }[] + * + * @param fileToSearch A JSON encoded string[] containing the file names that should be + * considered when searching. + */ + getDocumentHighlights(fileName: string, position: number, filesToSearch: string): string; + + /** + * Returns a JSON-encoded value of the type: + * { name: string; kind: string; kindModifiers: string; containerName: string; containerKind: string; matchKind: string; fileName: string; textSpan: { start: number; length: number}; } [] = []; + */ + getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string; + + /** + * Returns a JSON-encoded value of the type: + * { text: string; kind: string; kindModifiers: string; bolded: boolean; grayed: boolean; indent: number; spans: { start: number; length: number; }[]; childItems: [] } [] = []; + */ + getNavigationBarItems(fileName: string): string; + + /** Returns a JSON-encoded value of the type ts.NavigationTree. */ + getNavigationTree(fileName: string): string; + + /** + * Returns a JSON-encoded value of the type: + * { textSpan: { start: number, length: number }; hintSpan: { start: number, length: number }; bannerText: string; autoCollapse: boolean } [] = []; + */ + getOutliningSpans(fileName: string): string; + + getTodoComments(fileName: string, todoCommentDescriptors: string): string; + + getBraceMatchingAtPosition(fileName: string, position: number): string; + getIndentationAtPosition(fileName: string, position: number, options: string/*Services.EditorOptions*/): string; + + getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string; + getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string; + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string; + + /** + * Returns JSON-encoded value of the type TextInsertion. + */ + getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): string; + + /** + * Returns JSON-encoded boolean to indicate whether we should support brace location + * at the current position. + * E.g. we don't want brace completion inside string-literals, comments, etc. + */ + isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string; + + /** + * Returns a JSON-encoded TextSpan | undefined indicating the range of the enclosing comment, if it exists. + */ + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string; + + prepareCallHierarchy(fileName: string, position: number): string; + provideCallHierarchyIncomingCalls(fileName: string, position: number): string; + provideCallHierarchyOutgoingCalls(fileName: string, position: number): string; + provideInlayHints(fileName: string, span: TextSpan, preference: UserPreferences | undefined): string; + getEmitOutput(fileName: string): string; + getEmitOutputObject(fileName: string): EmitOutput; + + toggleLineComment(fileName: string, textChange: TextRange): string; + toggleMultilineComment(fileName: string, textChange: TextRange): string; + commentSelection(fileName: string, textChange: TextRange): string; + uncommentSelection(fileName: string, textChange: TextRange): string; +} + +/** @internal */ +export interface ClassifierShim extends Shim { + getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent?: boolean): string; + getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent?: boolean): string; +} + +/** @internal */ +export interface CoreServicesShim extends Shim { + getAutomaticTypeDirectiveNames(compilerOptionsJson: string): string; + getPreProcessedFileInfo(fileName: string, sourceText: IScriptSnapshot): string; + getTSConfigFileInfo(fileName: string, sourceText: IScriptSnapshot): string; + getDefaultCompilationSettings(): string; + discoverTypings(discoverTypingsJson: string): string; +} + +function logInternalError(logger: Logger, err: Error) { + if (logger) { + logger.log("*INTERNAL ERROR* - Exception in typescript services: " + err.message); } +} - export class LanguageServiceShimHostAdapter implements LanguageServiceHost { - private loggingEnabled = false; - private tracingEnabled = false; - - public resolveModuleNames: ((moduleName: string[], containingFile: string) => (ResolvedModuleFull | undefined)[]) | undefined; - public resolveTypeReferenceDirectives: ((typeDirectiveNames: string[] | readonly FileReference[], containingFile: string) => (ResolvedTypeReferenceDirective | undefined)[]) | undefined; - public directoryExists: ((directoryName: string) => boolean) | undefined; - - constructor(private shimHost: LanguageServiceShimHost) { - // if shimHost is a COM object then property check will become method call with no arguments. - // 'in' does not have this effect. - if ("getModuleResolutionsForFile" in this.shimHost) { - this.resolveModuleNames = (moduleNames, containingFile) => { - const resolutionsInFile = JSON.parse(this.shimHost.getModuleResolutionsForFile!(containingFile)) as MapLike; // TODO: GH#18217 - return map(moduleNames, name => { - const result = getProperty(resolutionsInFile, name); - return result ? { resolvedFileName: result, extension: extensionFromPath(result), isExternalLibraryImport: false } : undefined; - }); - }; - } - if ("directoryExists" in this.shimHost) { - this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName); - } - if ("getTypeReferenceDirectiveResolutionsForFile" in this.shimHost) { - this.resolveTypeReferenceDirectives = (typeDirectiveNames, containingFile) => { - const typeDirectivesForFile = JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile!(containingFile)) as MapLike; // TODO: GH#18217 - return map(typeDirectiveNames as (string | FileReference)[], name => getProperty(typeDirectivesForFile, isString(name) ? name : name.fileName.toLowerCase())); - }; - } - } +class ScriptSnapshotShimAdapter implements IScriptSnapshot { + constructor(private scriptSnapshotShim: ScriptSnapshotShim) { + } - public log(s: string): void { - if (this.loggingEnabled) { - this.shimHost.log(s); - } - } + public getText(start: number, end: number): string { + return this.scriptSnapshotShim.getText(start, end); + } - public trace(s: string): void { - if (this.tracingEnabled) { - this.shimHost.trace(s); - } - } + public getLength(): number { + return this.scriptSnapshotShim.getLength(); + } - public error(s: string): void { - this.shimHost.error(s); + public getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined { + const oldSnapshotShim = oldSnapshot as ScriptSnapshotShimAdapter; + const encoded = this.scriptSnapshotShim.getChangeRange(oldSnapshotShim.scriptSnapshotShim); + /* eslint-disable no-null/no-null */ + if (encoded === null) { + return null!; // TODO: GH#18217 } + /* eslint-enable no-null/no-null */ - public getProjectVersion(): string { - if (!this.shimHost.getProjectVersion) { - // shimmed host does not support getProjectVersion - return undefined!; // TODO: GH#18217 - } + const decoded: { span: { start: number; length: number; }; newLength: number; } = JSON.parse(encoded!); // TODO: GH#18217 + return createTextChangeRange( + createTextSpan(decoded.span.start, decoded.span.length), decoded.newLength); + } - return this.shimHost.getProjectVersion(); + public dispose(): void { + // if scriptSnapshotShim is a COM object then property check becomes method call with no arguments + // 'in' does not have this effect + if ("dispose" in this.scriptSnapshotShim) { + this.scriptSnapshotShim.dispose!(); // TODO: GH#18217 Can we just use `if (this.scriptSnapshotShim.dispose)`? } + } +} - public getTypeRootsVersion(): number { - if (!this.shimHost.getTypeRootsVersion) { - return 0; - } - return this.shimHost.getTypeRootsVersion(); +/** @internal */ +export class LanguageServiceShimHostAdapter implements LanguageServiceHost { + private loggingEnabled = false; + private tracingEnabled = false; + + public resolveModuleNames: ((moduleName: string[], containingFile: string) => (ResolvedModuleFull | undefined)[]) | undefined; + public resolveTypeReferenceDirectives: ((typeDirectiveNames: string[] | readonly FileReference[], containingFile: string) => (ResolvedTypeReferenceDirective | undefined)[]) | undefined; + public directoryExists: ((directoryName: string) => boolean) | undefined; + + constructor(private shimHost: LanguageServiceShimHost) { + // if shimHost is a COM object then property check will become method call with no arguments. + // 'in' does not have this effect. + if ("getModuleResolutionsForFile" in this.shimHost) { + this.resolveModuleNames = (moduleNames, containingFile) => { + const resolutionsInFile = JSON.parse(this.shimHost.getModuleResolutionsForFile!(containingFile)) as MapLike; // TODO: GH#18217 + return map(moduleNames, name => { + const result = getProperty(resolutionsInFile, name); + return result ? { resolvedFileName: result, extension: extensionFromPath(result), isExternalLibraryImport: false } : undefined; + }); + }; } - - public useCaseSensitiveFileNames(): boolean { - return this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false; + if ("directoryExists" in this.shimHost) { + this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName); } - - public getCompilationSettings(): CompilerOptions { - const settingsJson = this.shimHost.getCompilationSettings(); - // eslint-disable-next-line no-null/no-null - if (settingsJson === null || settingsJson === "") { - throw Error("LanguageServiceShimHostAdapter.getCompilationSettings: empty compilationSettings"); - } - const compilerOptions = JSON.parse(settingsJson) as CompilerOptions; - // permit language service to handle all files (filtering should be performed on the host side) - compilerOptions.allowNonTsExtensions = true; - return compilerOptions; + if ("getTypeReferenceDirectiveResolutionsForFile" in this.shimHost) { + this.resolveTypeReferenceDirectives = (typeDirectiveNames, containingFile) => { + const typeDirectivesForFile = JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile!(containingFile)) as MapLike; // TODO: GH#18217 + return map(typeDirectiveNames as (string | FileReference)[], name => getProperty(typeDirectivesForFile, isString(name) ? name : name.fileName.toLowerCase())); + }; } + } - public getScriptFileNames(): string[] { - const encoded = this.shimHost.getScriptFileNames(); - return JSON.parse(encoded); + public log(s: string): void { + if (this.loggingEnabled) { + this.shimHost.log(s); } + } - public getScriptSnapshot(fileName: string): IScriptSnapshot | undefined { - const scriptSnapshot = this.shimHost.getScriptSnapshot(fileName); - return scriptSnapshot && new ScriptSnapshotShimAdapter(scriptSnapshot); + public trace(s: string): void { + if (this.tracingEnabled) { + this.shimHost.trace(s); } + } - public getScriptKind(fileName: string): ScriptKind { - if ("getScriptKind" in this.shimHost) { - return this.shimHost.getScriptKind!(fileName); // TODO: GH#18217 - } - else { - return ScriptKind.Unknown; - } - } + public error(s: string): void { + this.shimHost.error(s); + } - public getScriptVersion(fileName: string): string { - return this.shimHost.getScriptVersion(fileName); + public getProjectVersion(): string { + if (!this.shimHost.getProjectVersion) { + // shimmed host does not support getProjectVersion + return undefined!; // TODO: GH#18217 } - public getLocalizedDiagnosticMessages() { - /* eslint-disable no-null/no-null */ - const diagnosticMessagesJson = this.shimHost.getLocalizedDiagnosticMessages(); - if (diagnosticMessagesJson === null || diagnosticMessagesJson === "") { - return null; - } + return this.shimHost.getProjectVersion(); + } - try { - return JSON.parse(diagnosticMessagesJson); - } - catch (e) { - this.log(e.description || "diagnosticMessages.generated.json has invalid JSON format"); - return null; - } - /* eslint-enable no-null/no-null */ + public getTypeRootsVersion(): number { + if (!this.shimHost.getTypeRootsVersion) { + return 0; } + return this.shimHost.getTypeRootsVersion(); + } - public getCancellationToken(): HostCancellationToken { - const hostCancellationToken = this.shimHost.getCancellationToken(); - return new ThrottledCancellationToken(hostCancellationToken); - } + public useCaseSensitiveFileNames(): boolean { + return this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false; + } - public getCurrentDirectory(): string { - return this.shimHost.getCurrentDirectory(); + public getCompilationSettings(): CompilerOptions { + const settingsJson = this.shimHost.getCompilationSettings(); + // eslint-disable-next-line no-null/no-null + if (settingsJson === null || settingsJson === "") { + throw Error("LanguageServiceShimHostAdapter.getCompilationSettings: empty compilationSettings"); } + const compilerOptions = JSON.parse(settingsJson) as CompilerOptions; + // permit language service to handle all files (filtering should be performed on the host side) + compilerOptions.allowNonTsExtensions = true; + return compilerOptions; + } - public getDirectories(path: string): string[] { - return JSON.parse(this.shimHost.getDirectories(path)); - } + public getScriptFileNames(): string[] { + const encoded = this.shimHost.getScriptFileNames(); + return JSON.parse(encoded); + } - public getDefaultLibFileName(options: CompilerOptions): string { - return this.shimHost.getDefaultLibFileName(JSON.stringify(options)); - } + public getScriptSnapshot(fileName: string): IScriptSnapshot | undefined { + const scriptSnapshot = this.shimHost.getScriptSnapshot(fileName); + return scriptSnapshot && new ScriptSnapshotShimAdapter(scriptSnapshot); + } - public readDirectory(path: string, extensions?: readonly string[], exclude?: string[], include?: string[], depth?: number): string[] { - const pattern = getFileMatcherPatterns(path, exclude, include, - this.shimHost.useCaseSensitiveFileNames!(), this.shimHost.getCurrentDirectory()); // TODO: GH#18217 - return JSON.parse(this.shimHost.readDirectory( - path, - JSON.stringify(extensions), - JSON.stringify(pattern.basePaths), - pattern.excludePattern, - pattern.includeFilePattern, - pattern.includeDirectoryPattern, - depth - )); + public getScriptKind(fileName: string): ScriptKind { + if ("getScriptKind" in this.shimHost) { + return this.shimHost.getScriptKind!(fileName); // TODO: GH#18217 + } + else { + return ScriptKind.Unknown; } + } + + public getScriptVersion(fileName: string): string { + return this.shimHost.getScriptVersion(fileName); + } - public readFile(path: string, encoding?: string): string | undefined { - return this.shimHost.readFile(path, encoding); + public getLocalizedDiagnosticMessages() { + /* eslint-disable no-null/no-null */ + const diagnosticMessagesJson = this.shimHost.getLocalizedDiagnosticMessages(); + if (diagnosticMessagesJson === null || diagnosticMessagesJson === "") { + return null; } - public fileExists(path: string): boolean { - return this.shimHost.fileExists(path); + try { + return JSON.parse(diagnosticMessagesJson); + } + catch (e) { + this.log(e.description || "diagnosticMessages.generated.json has invalid JSON format"); + return null; } + /* eslint-enable no-null/no-null */ } - export class CoreServicesShimHostAdapter implements ParseConfigHost, ModuleResolutionHost, JsTyping.TypingResolutionHost { + public getCancellationToken(): HostCancellationToken { + const hostCancellationToken = this.shimHost.getCancellationToken(); + return new ThrottledCancellationToken(hostCancellationToken); + } - public directoryExists: (directoryName: string) => boolean; - public realpath: (path: string) => string; - public useCaseSensitiveFileNames: boolean; + public getCurrentDirectory(): string { + return this.shimHost.getCurrentDirectory(); + } - constructor(private shimHost: CoreServicesShimHost) { - this.useCaseSensitiveFileNames = this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false; - if ("directoryExists" in this.shimHost) { - this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName); - } - else { - this.directoryExists = undefined!; // TODO: GH#18217 - } - if ("realpath" in this.shimHost) { - this.realpath = path => this.shimHost.realpath!(path); // TODO: GH#18217 - } - else { - this.realpath = undefined!; // TODO: GH#18217 - } - } + public getDirectories(path: string): string[] { + return JSON.parse(this.shimHost.getDirectories(path)); + } - public readDirectory(rootDir: string, extensions: readonly string[], exclude: readonly string[], include: readonly string[], depth?: number): string[] { - const pattern = getFileMatcherPatterns(rootDir, exclude, include, - this.shimHost.useCaseSensitiveFileNames!(), this.shimHost.getCurrentDirectory()); // TODO: GH#18217 - return JSON.parse(this.shimHost.readDirectory( - rootDir, - JSON.stringify(extensions), - JSON.stringify(pattern.basePaths), - pattern.excludePattern, - pattern.includeFilePattern, - pattern.includeDirectoryPattern, - depth - )); - } + public getDefaultLibFileName(options: CompilerOptions): string { + return this.shimHost.getDefaultLibFileName(JSON.stringify(options)); + } - public fileExists(fileName: string): boolean { - return this.shimHost.fileExists(fileName); - } + public readDirectory(path: string, extensions?: readonly string[], exclude?: string[], include?: string[], depth?: number): string[] { + const pattern = getFileMatcherPatterns(path, exclude, include, + this.shimHost.useCaseSensitiveFileNames!(), this.shimHost.getCurrentDirectory()); // TODO: GH#18217 + return JSON.parse(this.shimHost.readDirectory( + path, + JSON.stringify(extensions), + JSON.stringify(pattern.basePaths), + pattern.excludePattern, + pattern.includeFilePattern, + pattern.includeDirectoryPattern, + depth + )); + } - public readFile(fileName: string): string | undefined { - return this.shimHost.readFile(fileName); - } + public readFile(path: string, encoding?: string): string | undefined { + return this.shimHost.readFile(path, encoding); + } - public getDirectories(path: string): string[] { - return JSON.parse(this.shimHost.getDirectories(path)); - } + public fileExists(path: string): boolean { + return this.shimHost.fileExists(path); } +} - function simpleForwardCall(logger: Logger, actionDescription: string, action: () => unknown, logPerformance: boolean): unknown { - let start: number | undefined; - if (logPerformance) { - logger.log(actionDescription); - start = timestamp(); - } +/** @internal */ +export class CoreServicesShimHostAdapter implements ParseConfigHost, ModuleResolutionHost, JsTyping.TypingResolutionHost { - const result = action(); - - if (logPerformance) { - const end = timestamp(); - logger.log(`${actionDescription} completed in ${end - start!} msec`); - if (isString(result)) { - let str = result; - if (str.length > 128) { - str = str.substring(0, 128) + "..."; - } - logger.log(` result.length=${str.length}, result='${JSON.stringify(str)}'`); - } + public directoryExists: (directoryName: string) => boolean; + public realpath: (path: string) => string; + public useCaseSensitiveFileNames: boolean; + + constructor(private shimHost: CoreServicesShimHost) { + this.useCaseSensitiveFileNames = this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false; + if ("directoryExists" in this.shimHost) { + this.directoryExists = directoryName => this.shimHost.directoryExists(directoryName); } + else { + this.directoryExists = undefined!; // TODO: GH#18217 + } + if ("realpath" in this.shimHost) { + this.realpath = path => this.shimHost.realpath!(path); // TODO: GH#18217 + } + else { + this.realpath = undefined!; // TODO: GH#18217 + } + } - return result; + public readDirectory(rootDir: string, extensions: readonly string[], exclude: readonly string[], include: readonly string[], depth?: number): string[] { + const pattern = getFileMatcherPatterns(rootDir, exclude, include, + this.shimHost.useCaseSensitiveFileNames!(), this.shimHost.getCurrentDirectory()); // TODO: GH#18217 + return JSON.parse(this.shimHost.readDirectory( + rootDir, + JSON.stringify(extensions), + JSON.stringify(pattern.basePaths), + pattern.excludePattern, + pattern.includeFilePattern, + pattern.includeDirectoryPattern, + depth + )); } - function forwardJSONCall(logger: Logger, actionDescription: string, action: () => {} | null | undefined, logPerformance: boolean): string { - return forwardCall(logger, actionDescription, /*returnJson*/ true, action, logPerformance) as string; + public fileExists(fileName: string): boolean { + return this.shimHost.fileExists(fileName); } - function forwardCall(logger: Logger, actionDescription: string, returnJson: boolean, action: () => T, logPerformance: boolean): T | string { - try { - const result = simpleForwardCall(logger, actionDescription, action, logPerformance); - return returnJson ? JSON.stringify({ result }) : result as T; - } - catch (err) { - if (err instanceof OperationCanceledException) { - return JSON.stringify({ canceled: true }); - } - logInternalError(logger, err); - err.description = actionDescription; - return JSON.stringify({ error: err }); - } + public readFile(fileName: string): string | undefined { + return this.shimHost.readFile(fileName); } + public getDirectories(path: string): string[] { + return JSON.parse(this.shimHost.getDirectories(path)); + } +} - class ShimBase implements Shim { - constructor(private factory: ShimFactory) { - factory.registerShim(this); - } - public dispose(_dummy: {}): void { - this.factory.unregisterShim(this); - } +function simpleForwardCall(logger: Logger, actionDescription: string, action: () => unknown, logPerformance: boolean): unknown { + let start: number | undefined; + if (logPerformance) { + logger.log(actionDescription); + start = timestamp(); } - export interface RealizedDiagnostic { - message: string; - start: number; - length: number; - category: string; - code: number; - reportsUnnecessary?: {}; - reportsDeprecated?: {}; - } - export function realizeDiagnostics(diagnostics: readonly Diagnostic[], newLine: string): RealizedDiagnostic[] { - return diagnostics.map(d => realizeDiagnostic(d, newLine)); - } - - function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): RealizedDiagnostic { - return { - message: flattenDiagnosticMessageText(diagnostic.messageText, newLine), - start: diagnostic.start!, // TODO: GH#18217 - length: diagnostic.length!, // TODO: GH#18217 - category: diagnosticCategoryName(diagnostic), - code: diagnostic.code, - reportsUnnecessary: diagnostic.reportsUnnecessary, - reportsDeprecated: diagnostic.reportsDeprecated - }; - } - - class LanguageServiceShimObject extends ShimBase implements LanguageServiceShim { - private logger: Logger; - private logPerformance = false; - - constructor(factory: ShimFactory, - private host: LanguageServiceShimHost, - public languageService: LanguageService) { - super(factory); - this.logger = this.host; - } + const result = action(); - public forwardJSONCall(actionDescription: string, action: () => {} | null | undefined): string { - return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance); + if (logPerformance) { + const end = timestamp(); + logger.log(`${actionDescription} completed in ${end - start!} msec`); + if (isString(result)) { + let str = result; + if (str.length > 128) { + str = str.substring(0, 128) + "..."; + } + logger.log(` result.length=${str.length}, result='${JSON.stringify(str)}'`); } + } - /// DISPOSE - - /** - * Ensure (almost) deterministic release of internal Javascript resources when - * some external native objects holds onto us (e.g. Com/Interop). - */ - public dispose(dummy: {}): void { - this.logger.log("dispose()"); - this.languageService.dispose(); - this.languageService = null!; // eslint-disable-line no-null/no-null - - // force a GC - if (debugObjectHost && debugObjectHost.CollectGarbage) { - debugObjectHost.CollectGarbage(); - this.logger.log("CollectGarbage()"); - } + return result; +} - this.logger = null!; // eslint-disable-line no-null/no-null +function forwardJSONCall(logger: Logger, actionDescription: string, action: () => {} | null | undefined, logPerformance: boolean): string { + return forwardCall(logger, actionDescription, /*returnJson*/ true, action, logPerformance) as string; +} - super.dispose(dummy); +function forwardCall(logger: Logger, actionDescription: string, returnJson: boolean, action: () => T, logPerformance: boolean): T | string { + try { + const result = simpleForwardCall(logger, actionDescription, action, logPerformance); + return returnJson ? JSON.stringify({ result }) : result as T; + } + catch (err) { + if (err instanceof OperationCanceledException) { + return JSON.stringify({ canceled: true }); } + logInternalError(logger, err); + err.description = actionDescription; + return JSON.stringify({ error: err }); + } +} - /// REFRESH - /** - * Update the list of scripts known to the compiler - */ - public refresh(throwOnError: boolean): void { - this.forwardJSONCall( - `refresh(${throwOnError})`, - () => null // eslint-disable-line no-null/no-null - ); - } +class ShimBase implements Shim { + constructor(private factory: ShimFactory) { + factory.registerShim(this); + } + public dispose(_dummy: {}): void { + this.factory.unregisterShim(this); + } +} - public cleanupSemanticCache(): void { - this.forwardJSONCall( - "cleanupSemanticCache()", - () => { - this.languageService.cleanupSemanticCache(); - return null; // eslint-disable-line no-null/no-null - }); - } +/** @internal */ +export interface RealizedDiagnostic { + message: string; + start: number; + length: number; + category: string; + code: number; + reportsUnnecessary?: {}; + reportsDeprecated?: {}; +} +/** @internal */ +export function realizeDiagnostics(diagnostics: readonly Diagnostic[], newLine: string): RealizedDiagnostic[] { + return diagnostics.map(d => realizeDiagnostic(d, newLine)); +} - private realizeDiagnostics(diagnostics: readonly Diagnostic[]): { message: string; start: number; length: number; category: string; }[] { - const newLine = getNewLineOrDefaultFromHost(this.host); - return realizeDiagnostics(diagnostics, newLine); - } +function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): RealizedDiagnostic { + return { + message: flattenDiagnosticMessageText(diagnostic.messageText, newLine), + start: diagnostic.start!, // TODO: GH#18217 + length: diagnostic.length!, // TODO: GH#18217 + category: diagnosticCategoryName(diagnostic), + code: diagnostic.code, + reportsUnnecessary: diagnostic.reportsUnnecessary, + reportsDeprecated: diagnostic.reportsDeprecated + }; +} - public getSyntacticClassifications(fileName: string, start: number, length: number): string { - return this.forwardJSONCall( - `getSyntacticClassifications('${fileName}', ${start}, ${length})`, - () => this.languageService.getSyntacticClassifications(fileName, createTextSpan(start, length)) - ); - } +class LanguageServiceShimObject extends ShimBase implements LanguageServiceShim { + private logger: Logger; + private logPerformance = false; - public getSemanticClassifications(fileName: string, start: number, length: number): string { - return this.forwardJSONCall( - `getSemanticClassifications('${fileName}', ${start}, ${length})`, - () => this.languageService.getSemanticClassifications(fileName, createTextSpan(start, length)) - ); - } + constructor(factory: ShimFactory, + private host: LanguageServiceShimHost, + public languageService: LanguageService) { + super(factory); + this.logger = this.host; + } - public getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string { - return this.forwardJSONCall( - `getEncodedSyntacticClassifications('${fileName}', ${start}, ${length})`, - // directly serialize the spans out to a string. This is much faster to decode - // on the managed side versus a full JSON array. - () => convertClassifications(this.languageService.getEncodedSyntacticClassifications(fileName, createTextSpan(start, length))) - ); - } + public forwardJSONCall(actionDescription: string, action: () => {} | null | undefined): string { + return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance); + } - public getEncodedSemanticClassifications(fileName: string, start: number, length: number): string { - return this.forwardJSONCall( - `getEncodedSemanticClassifications('${fileName}', ${start}, ${length})`, - // directly serialize the spans out to a string. This is much faster to decode - // on the managed side versus a full JSON array. - () => convertClassifications(this.languageService.getEncodedSemanticClassifications(fileName, createTextSpan(start, length))) - ); - } + /// DISPOSE - public getSyntacticDiagnostics(fileName: string): string { - return this.forwardJSONCall( - `getSyntacticDiagnostics('${fileName}')`, - () => { - const diagnostics = this.languageService.getSyntacticDiagnostics(fileName); - return this.realizeDiagnostics(diagnostics); - }); - } + /** + * Ensure (almost) deterministic release of internal Javascript resources when + * some external native objects holds onto us (e.g. Com/Interop). + */ + public dispose(dummy: {}): void { + this.logger.log("dispose()"); + this.languageService.dispose(); + this.languageService = null!; // eslint-disable-line no-null/no-null - public getSemanticDiagnostics(fileName: string): string { - return this.forwardJSONCall( - `getSemanticDiagnostics('${fileName}')`, - () => { - const diagnostics = this.languageService.getSemanticDiagnostics(fileName); - return this.realizeDiagnostics(diagnostics); - }); + // force a GC + if (debugObjectHost && debugObjectHost.CollectGarbage) { + debugObjectHost.CollectGarbage(); + this.logger.log("CollectGarbage()"); } - public getSuggestionDiagnostics(fileName: string): string { - return this.forwardJSONCall(`getSuggestionDiagnostics('${fileName}')`, () => this.realizeDiagnostics(this.languageService.getSuggestionDiagnostics(fileName))); - } + this.logger = null!; // eslint-disable-line no-null/no-null - public getCompilerOptionsDiagnostics(): string { - return this.forwardJSONCall( - "getCompilerOptionsDiagnostics()", - () => { - const diagnostics = this.languageService.getCompilerOptionsDiagnostics(); - return this.realizeDiagnostics(diagnostics); - }); - } + super.dispose(dummy); + } - /// QUICKINFO - - /** - * Computes a string representation of the type at the requested position - * in the active file. - */ - public getQuickInfoAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getQuickInfoAtPosition('${fileName}', ${position})`, - () => this.languageService.getQuickInfoAtPosition(fileName, position) - ); - } + /// REFRESH + /** + * Update the list of scripts known to the compiler + */ + public refresh(throwOnError: boolean): void { + this.forwardJSONCall( + `refresh(${throwOnError})`, + () => null // eslint-disable-line no-null/no-null + ); + } - /// NAMEORDOTTEDNAMESPAN + public cleanupSemanticCache(): void { + this.forwardJSONCall( + "cleanupSemanticCache()", + () => { + this.languageService.cleanupSemanticCache(); + return null; // eslint-disable-line no-null/no-null + }); + } - /** - * Computes span information of the name or dotted name at the requested position - * in the active file. - */ - public getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string { - return this.forwardJSONCall( - `getNameOrDottedNameSpan('${fileName}', ${startPos}, ${endPos})`, - () => this.languageService.getNameOrDottedNameSpan(fileName, startPos, endPos) - ); - } + private realizeDiagnostics(diagnostics: readonly Diagnostic[]): { message: string; start: number; length: number; category: string; }[] { + const newLine = getNewLineOrDefaultFromHost(this.host); + return realizeDiagnostics(diagnostics, newLine); + } - /** - * STATEMENTSPAN - * Computes span information of statement at the requested position in the active file. - */ - public getBreakpointStatementAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getBreakpointStatementAtPosition('${fileName}', ${position})`, - () => this.languageService.getBreakpointStatementAtPosition(fileName, position) - ); - } + public getSyntacticClassifications(fileName: string, start: number, length: number): string { + return this.forwardJSONCall( + `getSyntacticClassifications('${fileName}', ${start}, ${length})`, + () => this.languageService.getSyntacticClassifications(fileName, createTextSpan(start, length)) + ); + } - /// SIGNATUREHELP + public getSemanticClassifications(fileName: string, start: number, length: number): string { + return this.forwardJSONCall( + `getSemanticClassifications('${fileName}', ${start}, ${length})`, + () => this.languageService.getSemanticClassifications(fileName, createTextSpan(start, length)) + ); + } - public getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string { - return this.forwardJSONCall( - `getSignatureHelpItems('${fileName}', ${position})`, - () => this.languageService.getSignatureHelpItems(fileName, position, options) - ); - } + public getEncodedSyntacticClassifications(fileName: string, start: number, length: number): string { + return this.forwardJSONCall( + `getEncodedSyntacticClassifications('${fileName}', ${start}, ${length})`, + // directly serialize the spans out to a string. This is much faster to decode + // on the managed side versus a full JSON array. + () => convertClassifications(this.languageService.getEncodedSyntacticClassifications(fileName, createTextSpan(start, length))) + ); + } - /// GOTO DEFINITION - - /** - * Computes the definition location and file for the symbol - * at the requested position. - */ - public getDefinitionAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getDefinitionAtPosition('${fileName}', ${position})`, - () => this.languageService.getDefinitionAtPosition(fileName, position) - ); - } + public getEncodedSemanticClassifications(fileName: string, start: number, length: number): string { + return this.forwardJSONCall( + `getEncodedSemanticClassifications('${fileName}', ${start}, ${length})`, + // directly serialize the spans out to a string. This is much faster to decode + // on the managed side versus a full JSON array. + () => convertClassifications(this.languageService.getEncodedSemanticClassifications(fileName, createTextSpan(start, length))) + ); + } - /** - * Computes the definition location and file for the symbol - * at the requested position. - */ - public getDefinitionAndBoundSpan(fileName: string, position: number): string { - return this.forwardJSONCall( - `getDefinitionAndBoundSpan('${fileName}', ${position})`, - () => this.languageService.getDefinitionAndBoundSpan(fileName, position) - ); - } + public getSyntacticDiagnostics(fileName: string): string { + return this.forwardJSONCall( + `getSyntacticDiagnostics('${fileName}')`, + () => { + const diagnostics = this.languageService.getSyntacticDiagnostics(fileName); + return this.realizeDiagnostics(diagnostics); + }); + } - /// GOTO Type - - /** - * Computes the definition location of the type of the symbol - * at the requested position. - */ - public getTypeDefinitionAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getTypeDefinitionAtPosition('${fileName}', ${position})`, - () => this.languageService.getTypeDefinitionAtPosition(fileName, position) - ); - } + public getSemanticDiagnostics(fileName: string): string { + return this.forwardJSONCall( + `getSemanticDiagnostics('${fileName}')`, + () => { + const diagnostics = this.languageService.getSemanticDiagnostics(fileName); + return this.realizeDiagnostics(diagnostics); + }); + } - /// GOTO Implementation - - /** - * Computes the implementation location of the symbol - * at the requested position. - */ - public getImplementationAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getImplementationAtPosition('${fileName}', ${position})`, - () => this.languageService.getImplementationAtPosition(fileName, position) - ); - } + public getSuggestionDiagnostics(fileName: string): string { + return this.forwardJSONCall(`getSuggestionDiagnostics('${fileName}')`, () => this.realizeDiagnostics(this.languageService.getSuggestionDiagnostics(fileName))); + } - public getRenameInfo(fileName: string, position: number, preferences: UserPreferences): string { - return this.forwardJSONCall( - `getRenameInfo('${fileName}', ${position})`, - () => this.languageService.getRenameInfo(fileName, position, preferences) - ); - } + public getCompilerOptionsDiagnostics(): string { + return this.forwardJSONCall( + "getCompilerOptionsDiagnostics()", + () => { + const diagnostics = this.languageService.getCompilerOptionsDiagnostics(); + return this.realizeDiagnostics(diagnostics); + }); + } - public getSmartSelectionRange(fileName: string, position: number): string { - return this.forwardJSONCall( - `getSmartSelectionRange('${fileName}', ${position})`, - () => this.languageService.getSmartSelectionRange(fileName, position) - ); - } + /// QUICKINFO + + /** + * Computes a string representation of the type at the requested position + * in the active file. + */ + public getQuickInfoAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getQuickInfoAtPosition('${fileName}', ${position})`, + () => this.languageService.getQuickInfoAtPosition(fileName, position) + ); + } - public findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string { - return this.forwardJSONCall( - `findRenameLocations('${fileName}', ${position}, ${findInStrings}, ${findInComments}, ${providePrefixAndSuffixTextForRename})`, - () => this.languageService.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename) - ); - } - /// GET BRACE MATCHING - public getBraceMatchingAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getBraceMatchingAtPosition('${fileName}', ${position})`, - () => this.languageService.getBraceMatchingAtPosition(fileName, position) - ); - } + /// NAMEORDOTTEDNAMESPAN - public isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string { - return this.forwardJSONCall( - `isValidBraceCompletionAtPosition('${fileName}', ${position}, ${openingBrace})`, - () => this.languageService.isValidBraceCompletionAtPosition(fileName, position, openingBrace) - ); - } + /** + * Computes span information of the name or dotted name at the requested position + * in the active file. + */ + public getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): string { + return this.forwardJSONCall( + `getNameOrDottedNameSpan('${fileName}', ${startPos}, ${endPos})`, + () => this.languageService.getNameOrDottedNameSpan(fileName, startPos, endPos) + ); + } - public getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string { - return this.forwardJSONCall( - `getSpanOfEnclosingComment('${fileName}', ${position})`, - () => this.languageService.getSpanOfEnclosingComment(fileName, position, onlyMultiLine) - ); - } + /** + * STATEMENTSPAN + * Computes span information of statement at the requested position in the active file. + */ + public getBreakpointStatementAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getBreakpointStatementAtPosition('${fileName}', ${position})`, + () => this.languageService.getBreakpointStatementAtPosition(fileName, position) + ); + } - /// GET SMART INDENT - public getIndentationAtPosition(fileName: string, position: number, options: string /*Services.EditorOptions*/): string { - return this.forwardJSONCall( - `getIndentationAtPosition('${fileName}', ${position})`, - () => { - const localOptions: EditorOptions = JSON.parse(options); - return this.languageService.getIndentationAtPosition(fileName, position, localOptions); - }); - } + /// SIGNATUREHELP - /// GET REFERENCES + public getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): string { + return this.forwardJSONCall( + `getSignatureHelpItems('${fileName}', ${position})`, + () => this.languageService.getSignatureHelpItems(fileName, position, options) + ); + } - public getReferencesAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getReferencesAtPosition('${fileName}', ${position})`, - () => this.languageService.getReferencesAtPosition(fileName, position) - ); - } + /// GOTO DEFINITION + + /** + * Computes the definition location and file for the symbol + * at the requested position. + */ + public getDefinitionAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getDefinitionAtPosition('${fileName}', ${position})`, + () => this.languageService.getDefinitionAtPosition(fileName, position) + ); + } - public findReferences(fileName: string, position: number): string { - return this.forwardJSONCall( - `findReferences('${fileName}', ${position})`, - () => this.languageService.findReferences(fileName, position) - ); - } + /** + * Computes the definition location and file for the symbol + * at the requested position. + */ + public getDefinitionAndBoundSpan(fileName: string, position: number): string { + return this.forwardJSONCall( + `getDefinitionAndBoundSpan('${fileName}', ${position})`, + () => this.languageService.getDefinitionAndBoundSpan(fileName, position) + ); + } - public getFileReferences(fileName: string) { - return this.forwardJSONCall( - `getFileReferences('${fileName})`, - () => this.languageService.getFileReferences(fileName) - ); - } + /// GOTO Type + + /** + * Computes the definition location of the type of the symbol + * at the requested position. + */ + public getTypeDefinitionAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getTypeDefinitionAtPosition('${fileName}', ${position})`, + () => this.languageService.getTypeDefinitionAtPosition(fileName, position) + ); + } - public getOccurrencesAtPosition(fileName: string, position: number): string { - return this.forwardJSONCall( - `getOccurrencesAtPosition('${fileName}', ${position})`, - () => this.languageService.getOccurrencesAtPosition(fileName, position) - ); - } + /// GOTO Implementation + + /** + * Computes the implementation location of the symbol + * at the requested position. + */ + public getImplementationAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getImplementationAtPosition('${fileName}', ${position})`, + () => this.languageService.getImplementationAtPosition(fileName, position) + ); + } - public getDocumentHighlights(fileName: string, position: number, filesToSearch: string): string { - return this.forwardJSONCall( - `getDocumentHighlights('${fileName}', ${position})`, - () => { - const results = this.languageService.getDocumentHighlights(fileName, position, JSON.parse(filesToSearch)); - // workaround for VS document highlighting issue - keep only items from the initial file - const normalizedName = toFileNameLowerCase(normalizeSlashes(fileName)); - return filter(results, r => toFileNameLowerCase(normalizeSlashes(r.fileName)) === normalizedName); - }); - } + public getRenameInfo(fileName: string, position: number, preferences: UserPreferences): string { + return this.forwardJSONCall( + `getRenameInfo('${fileName}', ${position})`, + () => this.languageService.getRenameInfo(fileName, position, preferences) + ); + } - /// COMPLETION LISTS - - /** - * Get a string based representation of the completions - * to provide at the given source position and providing a member completion - * list if requested. - */ - public getCompletionsAtPosition(fileName: string, position: number, preferences: GetCompletionsAtPositionOptions | undefined, formattingSettings: FormatCodeSettings | undefined) { - return this.forwardJSONCall( - `getCompletionsAtPosition('${fileName}', ${position}, ${preferences}, ${formattingSettings})`, - () => this.languageService.getCompletionsAtPosition(fileName, position, preferences, formattingSettings) - ); - } + public getSmartSelectionRange(fileName: string, position: number): string { + return this.forwardJSONCall( + `getSmartSelectionRange('${fileName}', ${position})`, + () => this.languageService.getSmartSelectionRange(fileName, position) + ); + } - /** Get a string based representation of a completion list entry details */ - public getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined) { - return this.forwardJSONCall( - `getCompletionEntryDetails('${fileName}', ${position}, '${entryName}')`, - () => { - const localOptions: FormatCodeOptions = formatOptions === undefined ? undefined : JSON.parse(formatOptions); - return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source, preferences, data); - } - ); - } + public findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string { + return this.forwardJSONCall( + `findRenameLocations('${fileName}', ${position}, ${findInStrings}, ${findInComments}, ${providePrefixAndSuffixTextForRename})`, + () => this.languageService.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename) + ); + } - public getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string { - return this.forwardJSONCall( - `getFormattingEditsForRange('${fileName}', ${start}, ${end})`, - () => { - const localOptions: FormatCodeOptions = JSON.parse(options); - return this.languageService.getFormattingEditsForRange(fileName, start, end, localOptions); - }); - } + /// GET BRACE MATCHING + public getBraceMatchingAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getBraceMatchingAtPosition('${fileName}', ${position})`, + () => this.languageService.getBraceMatchingAtPosition(fileName, position) + ); + } - public getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string { - return this.forwardJSONCall( - `getFormattingEditsForDocument('${fileName}')`, - () => { - const localOptions: FormatCodeOptions = JSON.parse(options); - return this.languageService.getFormattingEditsForDocument(fileName, localOptions); - }); - } + public isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string { + return this.forwardJSONCall( + `isValidBraceCompletionAtPosition('${fileName}', ${position}, ${openingBrace})`, + () => this.languageService.isValidBraceCompletionAtPosition(fileName, position, openingBrace) + ); + } - public getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string { - return this.forwardJSONCall( - `getFormattingEditsAfterKeystroke('${fileName}', ${position}, '${key}')`, - () => { - const localOptions: FormatCodeOptions = JSON.parse(options); - return this.languageService.getFormattingEditsAfterKeystroke(fileName, position, key, localOptions); - }); - } + public getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string { + return this.forwardJSONCall( + `getSpanOfEnclosingComment('${fileName}', ${position})`, + () => this.languageService.getSpanOfEnclosingComment(fileName, position, onlyMultiLine) + ); + } - public getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): string { - return this.forwardJSONCall( - `getDocCommentTemplateAtPosition('${fileName}', ${position})`, - () => this.languageService.getDocCommentTemplateAtPosition(fileName, position, options) - ); - } + /// GET SMART INDENT + public getIndentationAtPosition(fileName: string, position: number, options: string /*Services.EditorOptions*/): string { + return this.forwardJSONCall( + `getIndentationAtPosition('${fileName}', ${position})`, + () => { + const localOptions: EditorOptions = JSON.parse(options); + return this.languageService.getIndentationAtPosition(fileName, position, localOptions); + }); + } - /// NAVIGATE TO + /// GET REFERENCES - /** Return a list of symbols that are interesting to navigate to */ - public getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string { - return this.forwardJSONCall( - `getNavigateToItems('${searchValue}', ${maxResultCount}, ${fileName})`, - () => this.languageService.getNavigateToItems(searchValue, maxResultCount, fileName) - ); - } + public getReferencesAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getReferencesAtPosition('${fileName}', ${position})`, + () => this.languageService.getReferencesAtPosition(fileName, position) + ); + } - public getNavigationBarItems(fileName: string): string { - return this.forwardJSONCall( - `getNavigationBarItems('${fileName}')`, - () => this.languageService.getNavigationBarItems(fileName) - ); - } + public findReferences(fileName: string, position: number): string { + return this.forwardJSONCall( + `findReferences('${fileName}', ${position})`, + () => this.languageService.findReferences(fileName, position) + ); + } - public getNavigationTree(fileName: string): string { - return this.forwardJSONCall( - `getNavigationTree('${fileName}')`, - () => this.languageService.getNavigationTree(fileName) - ); - } + public getFileReferences(fileName: string) { + return this.forwardJSONCall( + `getFileReferences('${fileName})`, + () => this.languageService.getFileReferences(fileName) + ); + } - public getOutliningSpans(fileName: string): string { - return this.forwardJSONCall( - `getOutliningSpans('${fileName}')`, - () => this.languageService.getOutliningSpans(fileName) - ); - } + public getOccurrencesAtPosition(fileName: string, position: number): string { + return this.forwardJSONCall( + `getOccurrencesAtPosition('${fileName}', ${position})`, + () => this.languageService.getOccurrencesAtPosition(fileName, position) + ); + } - public getTodoComments(fileName: string, descriptors: string): string { - return this.forwardJSONCall( - `getTodoComments('${fileName}')`, - () => this.languageService.getTodoComments(fileName, JSON.parse(descriptors)) - ); - } + public getDocumentHighlights(fileName: string, position: number, filesToSearch: string): string { + return this.forwardJSONCall( + `getDocumentHighlights('${fileName}', ${position})`, + () => { + const results = this.languageService.getDocumentHighlights(fileName, position, JSON.parse(filesToSearch)); + // workaround for VS document highlighting issue - keep only items from the initial file + const normalizedName = toFileNameLowerCase(normalizeSlashes(fileName)); + return filter(results, r => toFileNameLowerCase(normalizeSlashes(r.fileName)) === normalizedName); + }); + } - /// CALL HIERARCHY + /// COMPLETION LISTS + + /** + * Get a string based representation of the completions + * to provide at the given source position and providing a member completion + * list if requested. + */ + public getCompletionsAtPosition(fileName: string, position: number, preferences: GetCompletionsAtPositionOptions | undefined, formattingSettings: FormatCodeSettings | undefined) { + return this.forwardJSONCall( + `getCompletionsAtPosition('${fileName}', ${position}, ${preferences}, ${formattingSettings})`, + () => this.languageService.getCompletionsAtPosition(fileName, position, preferences, formattingSettings) + ); + } - public prepareCallHierarchy(fileName: string, position: number): string { - return this.forwardJSONCall( - `prepareCallHierarchy('${fileName}', ${position})`, - () => this.languageService.prepareCallHierarchy(fileName, position) - ); - } + /** Get a string based representation of a completion list entry details */ + public getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: string/*Services.FormatCodeOptions*/ | undefined, source: string | undefined, preferences: UserPreferences | undefined, data: CompletionEntryData | undefined) { + return this.forwardJSONCall( + `getCompletionEntryDetails('${fileName}', ${position}, '${entryName}')`, + () => { + const localOptions: FormatCodeOptions = formatOptions === undefined ? undefined : JSON.parse(formatOptions); + return this.languageService.getCompletionEntryDetails(fileName, position, entryName, localOptions, source, preferences, data); + } + ); + } - public provideCallHierarchyIncomingCalls(fileName: string, position: number): string { - return this.forwardJSONCall( - `provideCallHierarchyIncomingCalls('${fileName}', ${position})`, - () => this.languageService.provideCallHierarchyIncomingCalls(fileName, position) - ); - } + public getFormattingEditsForRange(fileName: string, start: number, end: number, options: string/*Services.FormatCodeOptions*/): string { + return this.forwardJSONCall( + `getFormattingEditsForRange('${fileName}', ${start}, ${end})`, + () => { + const localOptions: FormatCodeOptions = JSON.parse(options); + return this.languageService.getFormattingEditsForRange(fileName, start, end, localOptions); + }); + } - public provideCallHierarchyOutgoingCalls(fileName: string, position: number): string { - return this.forwardJSONCall( - `provideCallHierarchyOutgoingCalls('${fileName}', ${position})`, - () => this.languageService.provideCallHierarchyOutgoingCalls(fileName, position) - ); - } + public getFormattingEditsForDocument(fileName: string, options: string/*Services.FormatCodeOptions*/): string { + return this.forwardJSONCall( + `getFormattingEditsForDocument('${fileName}')`, + () => { + const localOptions: FormatCodeOptions = JSON.parse(options); + return this.languageService.getFormattingEditsForDocument(fileName, localOptions); + }); + } - public provideInlayHints(fileName: string, span: TextSpan, preference: UserPreferences | undefined): string { - return this.forwardJSONCall( - `provideInlayHints('${fileName}', '${JSON.stringify(span)}', ${JSON.stringify(preference)})`, - () => this.languageService.provideInlayHints(fileName, span, preference) - ); - } + public getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: string/*Services.FormatCodeOptions*/): string { + return this.forwardJSONCall( + `getFormattingEditsAfterKeystroke('${fileName}', ${position}, '${key}')`, + () => { + const localOptions: FormatCodeOptions = JSON.parse(options); + return this.languageService.getFormattingEditsAfterKeystroke(fileName, position, key, localOptions); + }); + } - /// Emit - public getEmitOutput(fileName: string): string { - return this.forwardJSONCall( - `getEmitOutput('${fileName}')`, - () => { - const { diagnostics, ...rest } = this.languageService.getEmitOutput(fileName); - return { ...rest, diagnostics: this.realizeDiagnostics(diagnostics) }; - } - ); - } + public getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): string { + return this.forwardJSONCall( + `getDocCommentTemplateAtPosition('${fileName}', ${position})`, + () => this.languageService.getDocCommentTemplateAtPosition(fileName, position, options) + ); + } - public getEmitOutputObject(fileName: string): EmitOutput { - return forwardCall( - this.logger, - `getEmitOutput('${fileName}')`, - /*returnJson*/ false, - () => this.languageService.getEmitOutput(fileName), - this.logPerformance) as EmitOutput; - } + /// NAVIGATE TO - public toggleLineComment(fileName: string, textRange: TextRange): string { - return this.forwardJSONCall( - `toggleLineComment('${fileName}', '${JSON.stringify(textRange)}')`, - () => this.languageService.toggleLineComment(fileName, textRange) - ); - } + /** Return a list of symbols that are interesting to navigate to */ + public getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string): string { + return this.forwardJSONCall( + `getNavigateToItems('${searchValue}', ${maxResultCount}, ${fileName})`, + () => this.languageService.getNavigateToItems(searchValue, maxResultCount, fileName) + ); + } - public toggleMultilineComment(fileName: string, textRange: TextRange): string { - return this.forwardJSONCall( - `toggleMultilineComment('${fileName}', '${JSON.stringify(textRange)}')`, - () => this.languageService.toggleMultilineComment(fileName, textRange) - ); - } + public getNavigationBarItems(fileName: string): string { + return this.forwardJSONCall( + `getNavigationBarItems('${fileName}')`, + () => this.languageService.getNavigationBarItems(fileName) + ); + } - public commentSelection(fileName: string, textRange: TextRange): string { - return this.forwardJSONCall( - `commentSelection('${fileName}', '${JSON.stringify(textRange)}')`, - () => this.languageService.commentSelection(fileName, textRange) - ); - } + public getNavigationTree(fileName: string): string { + return this.forwardJSONCall( + `getNavigationTree('${fileName}')`, + () => this.languageService.getNavigationTree(fileName) + ); + } - public uncommentSelection(fileName: string, textRange: TextRange): string { - return this.forwardJSONCall( - `uncommentSelection('${fileName}', '${JSON.stringify(textRange)}')`, - () => this.languageService.uncommentSelection(fileName, textRange) - ); - } + public getOutliningSpans(fileName: string): string { + return this.forwardJSONCall( + `getOutliningSpans('${fileName}')`, + () => this.languageService.getOutliningSpans(fileName) + ); } - function convertClassifications(classifications: Classifications): { spans: string, endOfLineState: EndOfLineState } { - return { spans: classifications.spans.join(","), endOfLineState: classifications.endOfLineState }; + public getTodoComments(fileName: string, descriptors: string): string { + return this.forwardJSONCall( + `getTodoComments('${fileName}')`, + () => this.languageService.getTodoComments(fileName, JSON.parse(descriptors)) + ); } - class ClassifierShimObject extends ShimBase implements ClassifierShim { - public classifier: Classifier; - private logPerformance = false; + /// CALL HIERARCHY - constructor(factory: ShimFactory, private logger: Logger) { - super(factory); - this.classifier = createClassifier(); - } + public prepareCallHierarchy(fileName: string, position: number): string { + return this.forwardJSONCall( + `prepareCallHierarchy('${fileName}', ${position})`, + () => this.languageService.prepareCallHierarchy(fileName, position) + ); + } - public getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent = false): string { - return forwardJSONCall(this.logger, "getEncodedLexicalClassifications", - () => convertClassifications(this.classifier.getEncodedLexicalClassifications(text, lexState, syntacticClassifierAbsent)), - this.logPerformance); - } + public provideCallHierarchyIncomingCalls(fileName: string, position: number): string { + return this.forwardJSONCall( + `provideCallHierarchyIncomingCalls('${fileName}', ${position})`, + () => this.languageService.provideCallHierarchyIncomingCalls(fileName, position) + ); + } + + public provideCallHierarchyOutgoingCalls(fileName: string, position: number): string { + return this.forwardJSONCall( + `provideCallHierarchyOutgoingCalls('${fileName}', ${position})`, + () => this.languageService.provideCallHierarchyOutgoingCalls(fileName, position) + ); + } + + public provideInlayHints(fileName: string, span: TextSpan, preference: UserPreferences | undefined): string { + return this.forwardJSONCall( + `provideInlayHints('${fileName}', '${JSON.stringify(span)}', ${JSON.stringify(preference)})`, + () => this.languageService.provideInlayHints(fileName, span, preference) + ); + } - /// COLORIZATION - public getClassificationsForLine(text: string, lexState: EndOfLineState, classifyKeywordsInGenerics = false): string { - const classification = this.classifier.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics); - let result = ""; - for (const item of classification.entries) { - result += item.length + "\n"; - result += item.classification + "\n"; + /// Emit + public getEmitOutput(fileName: string): string { + return this.forwardJSONCall( + `getEmitOutput('${fileName}')`, + () => { + const { diagnostics, ...rest } = this.languageService.getEmitOutput(fileName); + return { ...rest, diagnostics: this.realizeDiagnostics(diagnostics) }; } - result += classification.finalLexState; - return result; - } + ); } - class CoreServicesShimObject extends ShimBase implements CoreServicesShim { - private logPerformance = false; - private safeList: JsTyping.SafeList | undefined; + public getEmitOutputObject(fileName: string): EmitOutput { + return forwardCall( + this.logger, + `getEmitOutput('${fileName}')`, + /*returnJson*/ false, + () => this.languageService.getEmitOutput(fileName), + this.logPerformance) as EmitOutput; + } - constructor(factory: ShimFactory, public readonly logger: Logger, private readonly host: CoreServicesShimHostAdapter) { - super(factory); - } + public toggleLineComment(fileName: string, textRange: TextRange): string { + return this.forwardJSONCall( + `toggleLineComment('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.toggleLineComment(fileName, textRange) + ); + } - private forwardJSONCall(actionDescription: string, action: () => {}): string { - return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance); + public toggleMultilineComment(fileName: string, textRange: TextRange): string { + return this.forwardJSONCall( + `toggleMultilineComment('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.toggleMultilineComment(fileName, textRange) + ); + } + + public commentSelection(fileName: string, textRange: TextRange): string { + return this.forwardJSONCall( + `commentSelection('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.commentSelection(fileName, textRange) + ); + } + + public uncommentSelection(fileName: string, textRange: TextRange): string { + return this.forwardJSONCall( + `uncommentSelection('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.uncommentSelection(fileName, textRange) + ); + } +} + +function convertClassifications(classifications: Classifications): { spans: string, endOfLineState: EndOfLineState } { + return { spans: classifications.spans.join(","), endOfLineState: classifications.endOfLineState }; +} + +class ClassifierShimObject extends ShimBase implements ClassifierShim { + public classifier: Classifier; + private logPerformance = false; + + constructor(factory: ShimFactory, private logger: Logger) { + super(factory); + this.classifier = createClassifier(); + } + + public getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent = false): string { + return forwardJSONCall(this.logger, "getEncodedLexicalClassifications", + () => convertClassifications(this.classifier.getEncodedLexicalClassifications(text, lexState, syntacticClassifierAbsent)), + this.logPerformance); + } + + /// COLORIZATION + public getClassificationsForLine(text: string, lexState: EndOfLineState, classifyKeywordsInGenerics = false): string { + const classification = this.classifier.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics); + let result = ""; + for (const item of classification.entries) { + result += item.length + "\n"; + result += item.classification + "\n"; } + result += classification.finalLexState; + return result; + } +} - public resolveModuleName(fileName: string, moduleName: string, compilerOptionsJson: string): string { - return this.forwardJSONCall(`resolveModuleName('${fileName}')`, () => { - const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions; - const result = resolveModuleName(moduleName, normalizeSlashes(fileName), compilerOptions, this.host); - let resolvedFileName = result.resolvedModule ? result.resolvedModule.resolvedFileName : undefined; - if (result.resolvedModule && result.resolvedModule.extension !== Extension.Ts && result.resolvedModule.extension !== Extension.Tsx && result.resolvedModule.extension !== Extension.Dts) { - resolvedFileName = undefined; - } +class CoreServicesShimObject extends ShimBase implements CoreServicesShim { + private logPerformance = false; + private safeList: JsTyping.SafeList | undefined; + + constructor(factory: ShimFactory, public readonly logger: Logger, private readonly host: CoreServicesShimHostAdapter) { + super(factory); + } + + private forwardJSONCall(actionDescription: string, action: () => {}): string { + return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance); + } + + public resolveModuleName(fileName: string, moduleName: string, compilerOptionsJson: string): string { + return this.forwardJSONCall(`resolveModuleName('${fileName}')`, () => { + const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions; + const result = resolveModuleName(moduleName, normalizeSlashes(fileName), compilerOptions, this.host); + let resolvedFileName = result.resolvedModule ? result.resolvedModule.resolvedFileName : undefined; + if (result.resolvedModule && result.resolvedModule.extension !== Extension.Ts && result.resolvedModule.extension !== Extension.Tsx && result.resolvedModule.extension !== Extension.Dts) { + resolvedFileName = undefined; + } + return { + resolvedFileName, + failedLookupLocations: result.failedLookupLocations, + affectingLocations: result.affectingLocations, + }; + }); + } + + public resolveTypeReferenceDirective(fileName: string, typeReferenceDirective: string, compilerOptionsJson: string): string { + return this.forwardJSONCall(`resolveTypeReferenceDirective(${fileName})`, () => { + const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions; + const result = resolveTypeReferenceDirective(typeReferenceDirective, normalizeSlashes(fileName), compilerOptions, this.host); + return { + resolvedFileName: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.resolvedFileName : undefined, + primary: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.primary : true, + failedLookupLocations: result.failedLookupLocations + }; + }); + } + + public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string { + return this.forwardJSONCall( + `getPreProcessedFileInfo('${fileName}')`, + () => { + // for now treat files as JavaScript + const result = preProcessFile(getSnapshotText(sourceTextSnapshot), /* readImportFiles */ true, /* detectJavaScriptImports */ true); return { - resolvedFileName, - failedLookupLocations: result.failedLookupLocations, - affectingLocations: result.affectingLocations, + referencedFiles: this.convertFileReferences(result.referencedFiles), + importedFiles: this.convertFileReferences(result.importedFiles), + ambientExternalModules: result.ambientExternalModules, + isLibFile: result.isLibFile, + typeReferenceDirectives: this.convertFileReferences(result.typeReferenceDirectives), + libReferenceDirectives: this.convertFileReferences(result.libReferenceDirectives) }; }); - } + } - public resolveTypeReferenceDirective(fileName: string, typeReferenceDirective: string, compilerOptionsJson: string): string { - return this.forwardJSONCall(`resolveTypeReferenceDirective(${fileName})`, () => { + public getAutomaticTypeDirectiveNames(compilerOptionsJson: string): string { + return this.forwardJSONCall( + `getAutomaticTypeDirectiveNames('${compilerOptionsJson}')`, + () => { const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions; - const result = resolveTypeReferenceDirective(typeReferenceDirective, normalizeSlashes(fileName), compilerOptions, this.host); - return { - resolvedFileName: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.resolvedFileName : undefined, - primary: result.resolvedTypeReferenceDirective ? result.resolvedTypeReferenceDirective.primary : true, - failedLookupLocations: result.failedLookupLocations - }; - }); - } + return getAutomaticTypeDirectiveNames(compilerOptions, this.host); + } + ); + } - public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string { - return this.forwardJSONCall( - `getPreProcessedFileInfo('${fileName}')`, - () => { - // for now treat files as JavaScript - const result = preProcessFile(getSnapshotText(sourceTextSnapshot), /* readImportFiles */ true, /* detectJavaScriptImports */ true); - return { - referencedFiles: this.convertFileReferences(result.referencedFiles), - importedFiles: this.convertFileReferences(result.importedFiles), - ambientExternalModules: result.ambientExternalModules, - isLibFile: result.isLibFile, - typeReferenceDirectives: this.convertFileReferences(result.typeReferenceDirectives), - libReferenceDirectives: this.convertFileReferences(result.libReferenceDirectives) - }; - }); + private convertFileReferences(refs: FileReference[]): ShimsFileReference[] | undefined { + if (!refs) { + return undefined; } - - public getAutomaticTypeDirectiveNames(compilerOptionsJson: string): string { - return this.forwardJSONCall( - `getAutomaticTypeDirectiveNames('${compilerOptionsJson}')`, - () => { - const compilerOptions = JSON.parse(compilerOptionsJson) as CompilerOptions; - return getAutomaticTypeDirectiveNames(compilerOptions, this.host); - } - ); + const result: ShimsFileReference[] = []; + for (const ref of refs) { + result.push({ + path: normalizeSlashes(ref.fileName), + position: ref.pos, + length: ref.end - ref.pos + }); } + return result; + } - private convertFileReferences(refs: FileReference[]): ShimsFileReference[] | undefined { - if (!refs) { - return undefined; - } - const result: ShimsFileReference[] = []; - for (const ref of refs) { - result.push({ - path: normalizeSlashes(ref.fileName), - position: ref.pos, - length: ref.end - ref.pos - }); - } - return result; - } + public getTSConfigFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string { + return this.forwardJSONCall( + `getTSConfigFileInfo('${fileName}')`, + () => { + const result = parseJsonText(fileName, getSnapshotText(sourceTextSnapshot)); + const normalizedFileName = normalizeSlashes(fileName); + const configFile = parseJsonSourceFileConfigFileContent(result, this.host, getDirectoryPath(normalizedFileName), /*existingOptions*/ {}, normalizedFileName); - public getTSConfigFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string { - return this.forwardJSONCall( - `getTSConfigFileInfo('${fileName}')`, - () => { - const result = parseJsonText(fileName, getSnapshotText(sourceTextSnapshot)); - const normalizedFileName = normalizeSlashes(fileName); - const configFile = parseJsonSourceFileConfigFileContent(result, this.host, getDirectoryPath(normalizedFileName), /*existingOptions*/ {}, normalizedFileName); - - return { - options: configFile.options, - typeAcquisition: configFile.typeAcquisition, - files: configFile.fileNames, - raw: configFile.raw, - errors: realizeDiagnostics([...result.parseDiagnostics, ...configFile.errors], "\r\n") - }; - }); - } + return { + options: configFile.options, + typeAcquisition: configFile.typeAcquisition, + files: configFile.fileNames, + raw: configFile.raw, + errors: realizeDiagnostics([...result.parseDiagnostics, ...configFile.errors], "\r\n") + }; + }); + } - public getDefaultCompilationSettings(): string { - return this.forwardJSONCall( - "getDefaultCompilationSettings()", - () => getDefaultCompilerOptions() - ); - } + public getDefaultCompilationSettings(): string { + return this.forwardJSONCall( + "getDefaultCompilationSettings()", + () => getDefaultCompilerOptions() + ); + } - public discoverTypings(discoverTypingsJson: string): string { - const getCanonicalFileName = createGetCanonicalFileName(/*useCaseSensitivefileNames:*/ false); - return this.forwardJSONCall("discoverTypings()", () => { - const info = JSON.parse(discoverTypingsJson) as DiscoverTypingsInfo; - if (this.safeList === undefined) { - this.safeList = JsTyping.loadSafeList(this.host, toPath(info.safeListPath, info.safeListPath, getCanonicalFileName)); - } - return JsTyping.discoverTypings( - this.host, - msg => this.logger.log(msg), - info.fileNames, - toPath(info.projectRootPath, info.projectRootPath, getCanonicalFileName), - this.safeList, - info.packageNameToTypingLocation, - info.typeAcquisition, - info.unresolvedImports, - info.typesRegistry, - emptyOptions); - }); - } + public discoverTypings(discoverTypingsJson: string): string { + const getCanonicalFileName = createGetCanonicalFileName(/*useCaseSensitivefileNames:*/ false); + return this.forwardJSONCall("discoverTypings()", () => { + const info = JSON.parse(discoverTypingsJson) as DiscoverTypingsInfo; + if (this.safeList === undefined) { + this.safeList = JsTyping.loadSafeList(this.host, toPath(info.safeListPath, info.safeListPath, getCanonicalFileName)); + } + return JsTyping.discoverTypings( + this.host, + msg => this.logger.log(msg), + info.fileNames, + toPath(info.projectRootPath, info.projectRootPath, getCanonicalFileName), + this.safeList, + info.packageNameToTypingLocation, + info.typeAcquisition, + info.unresolvedImports, + info.typesRegistry, + emptyOptions); + }); } +} - export class TypeScriptServicesFactory implements ShimFactory { - private _shims: Shim[] = []; - private documentRegistry: DocumentRegistry | undefined; +/** @internal */ +export class TypeScriptServicesFactory implements ShimFactory { + private _shims: Shim[] = []; + private documentRegistry: DocumentRegistry | undefined; - /* - * Returns script API version. - */ - public getServicesVersion(): string { - return servicesVersion; - } + /* + * Returns script API version. + */ + public getServicesVersion(): string { + return servicesVersion; + } - public createLanguageServiceShim(host: LanguageServiceShimHost): LanguageServiceShim { - try { - if (this.documentRegistry === undefined) { - this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()); - } - const hostAdapter = new LanguageServiceShimHostAdapter(host); - const languageService = createLanguageService(hostAdapter, this.documentRegistry, /*syntaxOnly*/ false); - return new LanguageServiceShimObject(this, host, languageService); - } - catch (err) { - logInternalError(host, err); - throw err; + public createLanguageServiceShim(host: LanguageServiceShimHost): LanguageServiceShim { + try { + if (this.documentRegistry === undefined) { + this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()); } + const hostAdapter = new LanguageServiceShimHostAdapter(host); + const languageService = createLanguageService(hostAdapter, this.documentRegistry, /*syntaxOnly*/ false); + return new LanguageServiceShimObject(this, host, languageService); } - - public createClassifierShim(logger: Logger): ClassifierShim { - try { - return new ClassifierShimObject(this, logger); - } - catch (err) { - logInternalError(logger, err); - throw err; - } + catch (err) { + logInternalError(host, err); + throw err; } + } - public createCoreServicesShim(host: CoreServicesShimHost): CoreServicesShim { - try { - const adapter = new CoreServicesShimHostAdapter(host); - return new CoreServicesShimObject(this, host as Logger, adapter); - } - catch (err) { - logInternalError(host as Logger, err); - throw err; - } + public createClassifierShim(logger: Logger): ClassifierShim { + try { + return new ClassifierShimObject(this, logger); } - - public close(): void { - // Forget all the registered shims - clear(this._shims); - this.documentRegistry = undefined; + catch (err) { + logInternalError(logger, err); + throw err; } + } - public registerShim(shim: Shim): void { - this._shims.push(shim); + public createCoreServicesShim(host: CoreServicesShimHost): CoreServicesShim { + try { + const adapter = new CoreServicesShimHostAdapter(host); + return new CoreServicesShimObject(this, host as Logger, adapter); + } + catch (err) { + logInternalError(host as Logger, err); + throw err; } + } - public unregisterShim(shim: Shim): void { - for (let i = 0; i < this._shims.length; i++) { - if (this._shims[i] === shim) { - delete this._shims[i]; - return; - } - } + public close(): void { + // Forget all the registered shims + clear(this._shims); + this.documentRegistry = undefined; + } + + public registerShim(shim: Shim): void { + this._shims.push(shim); + } - throw new Error("Invalid operation"); + public unregisterShim(shim: Shim): void { + for (let i = 0; i < this._shims.length; i++) { + if (this._shims[i] === shim) { + delete this._shims[i]; + return; + } } + + throw new Error("Invalid operation"); } } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 569ec1a677813..cf672d0699634 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -1,666 +1,682 @@ -/* @internal */ -namespace ts.SignatureHelp { - const enum InvocationKind { Call, TypeArgs, Contextual } - interface CallInvocation { readonly kind: InvocationKind.Call; readonly node: CallLikeExpression; } - interface TypeArgsInvocation { readonly kind: InvocationKind.TypeArgs; readonly called: Identifier; } - interface ContextualInvocation { - readonly kind: InvocationKind.Contextual; - readonly signature: Signature; - readonly node: Node; // Just for enclosingDeclaration for printing types - readonly symbol: Symbol; - } - type Invocation = CallInvocation | TypeArgsInvocation | ContextualInvocation; - - interface ArgumentListInfo { - readonly isTypeParameterList: boolean; - readonly invocation: Invocation; - readonly argumentsSpan: TextSpan; - readonly argumentIndex: number; - /** argumentCount is the *apparent* number of arguments. */ - readonly argumentCount: number; - } - - export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerReason: SignatureHelpTriggerReason | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined { - const typeChecker = program.getTypeChecker(); +import { + ArrowFunction, BinaryExpression, CallLikeExpression, CancellationToken, CheckFlags, contains, countWhere, + createPrinter, createTextSpan, createTextSpanFromBounds, createTextSpanFromNode, Debug, EmitHint, emptyArray, + Expression, factory, findContainingList, findIndex, findPrecedingToken, findTokenOnLeftOfPosition, first, + firstDefined, flatMapToMutable, FunctionExpression, getInvokedExpression, getPossibleGenericSignatures, + getPossibleTypeArgumentsInfo, Identifier, identity, InternalSymbolName, isBinaryExpression, isBlock, + isCallOrNewExpression, isFunctionTypeNode, isIdentifier, isInComment, isInsideTemplateLiteral, isInString, + isJsxOpeningLikeElement, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, + isSourceFile, isSourceFileJS, isTaggedTemplateExpression, isTemplateHead, isTemplateLiteralToken, isTemplateSpan, + isTemplateTail, last, lastOrUndefined, ListFormat, map, mapToDisplayParts, Node, NodeBuilderFlags, + ParameterDeclaration, ParenthesizedExpression, Printer, Program, punctuationPart, rangeContainsRange, Signature, + SignatureHelpItem, SignatureHelpItems, SignatureHelpParameter, SignatureHelpTriggerReason, skipTrivia, SourceFile, + spacePart, Symbol, SymbolDisplayPart, symbolToDisplayParts, SyntaxKind, TaggedTemplateExpression, + TemplateExpression, TextSpan, TransientSymbol, Type, TypeChecker, TypeParameter, +} from "./_namespaces/ts"; + +const enum InvocationKind { Call, TypeArgs, Contextual } +interface CallInvocation { readonly kind: InvocationKind.Call; readonly node: CallLikeExpression; } +interface TypeArgsInvocation { readonly kind: InvocationKind.TypeArgs; readonly called: Identifier; } +interface ContextualInvocation { + readonly kind: InvocationKind.Contextual; + readonly signature: Signature; + readonly node: Node; // Just for enclosingDeclaration for printing types + readonly symbol: Symbol; +} +type Invocation = CallInvocation | TypeArgsInvocation | ContextualInvocation; + +interface ArgumentListInfo { + readonly isTypeParameterList: boolean; + readonly invocation: Invocation; + readonly argumentsSpan: TextSpan; + readonly argumentIndex: number; + /** argumentCount is the *apparent* number of arguments. */ + readonly argumentCount: number; +} - // Decide whether to show signature help - const startingToken = findTokenOnLeftOfPosition(sourceFile, position); - if (!startingToken) { - // We are at the beginning of the file - return undefined; - } +/** @internal */ +export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerReason: SignatureHelpTriggerReason | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined { + const typeChecker = program.getTypeChecker(); - // Only need to be careful if the user typed a character and signature help wasn't showing. - const onlyUseSyntacticOwners = !!triggerReason && triggerReason.kind === "characterTyped"; + // Decide whether to show signature help + const startingToken = findTokenOnLeftOfPosition(sourceFile, position); + if (!startingToken) { + // We are at the beginning of the file + return undefined; + } - // Bail out quickly in the middle of a string or comment, don't provide signature help unless the user explicitly requested it. - if (onlyUseSyntacticOwners && (isInString(sourceFile, position, startingToken) || isInComment(sourceFile, position))) { - return undefined; - } + // Only need to be careful if the user typed a character and signature help wasn't showing. + const onlyUseSyntacticOwners = !!triggerReason && triggerReason.kind === "characterTyped"; - const isManuallyInvoked = !!triggerReason && triggerReason.kind === "invoked"; - const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile, typeChecker, isManuallyInvoked); - if (!argumentInfo) return undefined; + // Bail out quickly in the middle of a string or comment, don't provide signature help unless the user explicitly requested it. + if (onlyUseSyntacticOwners && (isInString(sourceFile, position, startingToken) || isInComment(sourceFile, position))) { + return undefined; + } - cancellationToken.throwIfCancellationRequested(); + const isManuallyInvoked = !!triggerReason && triggerReason.kind === "invoked"; + const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile, typeChecker, isManuallyInvoked); + if (!argumentInfo) return undefined; - // Extra syntactic and semantic filtering of signature help - const candidateInfo = getCandidateOrTypeInfo(argumentInfo, typeChecker, sourceFile, startingToken, onlyUseSyntacticOwners); - cancellationToken.throwIfCancellationRequested(); + cancellationToken.throwIfCancellationRequested(); - if (!candidateInfo) { - // We didn't have any sig help items produced by the TS compiler. If this is a JS - // file, then see if we can figure out anything better. - return isSourceFileJS(sourceFile) ? createJSSignatureHelpItems(argumentInfo, program, cancellationToken) : undefined; - } + // Extra syntactic and semantic filtering of signature help + const candidateInfo = getCandidateOrTypeInfo(argumentInfo, typeChecker, sourceFile, startingToken, onlyUseSyntacticOwners); + cancellationToken.throwIfCancellationRequested(); - return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => - candidateInfo.kind === CandidateOrTypeKind.Candidate - ? createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker) - : createTypeHelpItems(candidateInfo.symbol, argumentInfo, sourceFile, typeChecker)); + if (!candidateInfo) { + // We didn't have any sig help items produced by the TS compiler. If this is a JS + // file, then see if we can figure out anything better. + return isSourceFileJS(sourceFile) ? createJSSignatureHelpItems(argumentInfo, program, cancellationToken) : undefined; } - const enum CandidateOrTypeKind { Candidate, Type } - interface CandidateInfo { - readonly kind: CandidateOrTypeKind.Candidate; - readonly candidates: readonly Signature[]; - readonly resolvedSignature: Signature; - } - interface TypeInfo { - readonly kind: CandidateOrTypeKind.Type; - readonly symbol: Symbol; - } + return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => + candidateInfo.kind === CandidateOrTypeKind.Candidate + ? createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker) + : createTypeHelpItems(candidateInfo.symbol, argumentInfo, sourceFile, typeChecker)); +} - function getCandidateOrTypeInfo({ invocation, argumentCount }: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean): CandidateInfo | TypeInfo | undefined { - switch (invocation.kind) { - case InvocationKind.Call: { - if (onlyUseSyntacticOwners && !isSyntacticOwner(startingToken, invocation.node, sourceFile)) { - return undefined; - } - const candidates: Signature[] = []; - const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentCount)!; // TODO: GH#18217 - return candidates.length === 0 ? undefined : { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature }; - } - case InvocationKind.TypeArgs: { - const { called } = invocation; - if (onlyUseSyntacticOwners && !containsPrecedingToken(startingToken, sourceFile, isIdentifier(called) ? called.parent : called)) { - return undefined; - } - const candidates = getPossibleGenericSignatures(called, argumentCount, checker); - if (candidates.length !== 0) return { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature: first(candidates) }; +const enum CandidateOrTypeKind { Candidate, Type } +interface CandidateInfo { + readonly kind: CandidateOrTypeKind.Candidate; + readonly candidates: readonly Signature[]; + readonly resolvedSignature: Signature; +} +interface TypeInfo { + readonly kind: CandidateOrTypeKind.Type; + readonly symbol: Symbol; +} - const symbol = checker.getSymbolAtLocation(called); - return symbol && { kind: CandidateOrTypeKind.Type, symbol }; +function getCandidateOrTypeInfo({ invocation, argumentCount }: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean): CandidateInfo | TypeInfo | undefined { + switch (invocation.kind) { + case InvocationKind.Call: { + if (onlyUseSyntacticOwners && !isSyntacticOwner(startingToken, invocation.node, sourceFile)) { + return undefined; } - case InvocationKind.Contextual: - return { kind: CandidateOrTypeKind.Candidate, candidates: [invocation.signature], resolvedSignature: invocation.signature }; - default: - return Debug.assertNever(invocation); + const candidates: Signature[] = []; + const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentCount)!; // TODO: GH#18217 + return candidates.length === 0 ? undefined : { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature }; } - } - - function isSyntacticOwner(startingToken: Node, node: CallLikeExpression, sourceFile: SourceFile): boolean { - if (!isCallOrNewExpression(node)) return false; - const invocationChildren = node.getChildren(sourceFile); - switch (startingToken.kind) { - case SyntaxKind.OpenParenToken: - return contains(invocationChildren, startingToken); - case SyntaxKind.CommaToken: { - const containingList = findContainingList(startingToken); - return !!containingList && contains(invocationChildren, containingList); + case InvocationKind.TypeArgs: { + const { called } = invocation; + if (onlyUseSyntacticOwners && !containsPrecedingToken(startingToken, sourceFile, isIdentifier(called) ? called.parent : called)) { + return undefined; } - case SyntaxKind.LessThanToken: - return containsPrecedingToken(startingToken, sourceFile, node.expression); - default: - return false; + const candidates = getPossibleGenericSignatures(called, argumentCount, checker); + if (candidates.length !== 0) return { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature: first(candidates) }; + + const symbol = checker.getSymbolAtLocation(called); + return symbol && { kind: CandidateOrTypeKind.Type, symbol }; } + case InvocationKind.Contextual: + return { kind: CandidateOrTypeKind.Candidate, candidates: [invocation.signature], resolvedSignature: invocation.signature }; + default: + return Debug.assertNever(invocation); } +} - function createJSSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined { - if (argumentInfo.invocation.kind === InvocationKind.Contextual) return undefined; - // See if we can find some symbol with the call expression name that has call signatures. - const expression = getExpressionFromInvocation(argumentInfo.invocation); - const name = isPropertyAccessExpression(expression) ? expression.name.text : undefined; - const typeChecker = program.getTypeChecker(); - return name === undefined ? undefined : firstDefined(program.getSourceFiles(), sourceFile => - firstDefined(sourceFile.getNamedDeclarations().get(name), declaration => { - const type = declaration.symbol && typeChecker.getTypeOfSymbolAtLocation(declaration.symbol, declaration); - const callSignatures = type && type.getCallSignatures(); - if (callSignatures && callSignatures.length) { - return typeChecker.runWithCancellationToken( - cancellationToken, - typeChecker => createSignatureHelpItems( - callSignatures, - callSignatures[0], - argumentInfo, - sourceFile, - typeChecker, - /*useFullPrefix*/ true)); - } - })); +function isSyntacticOwner(startingToken: Node, node: CallLikeExpression, sourceFile: SourceFile): boolean { + if (!isCallOrNewExpression(node)) return false; + const invocationChildren = node.getChildren(sourceFile); + switch (startingToken.kind) { + case SyntaxKind.OpenParenToken: + return contains(invocationChildren, startingToken); + case SyntaxKind.CommaToken: { + const containingList = findContainingList(startingToken); + return !!containingList && contains(invocationChildren, containingList); + } + case SyntaxKind.LessThanToken: + return containsPrecedingToken(startingToken, sourceFile, node.expression); + default: + return false; } +} - function containsPrecedingToken(startingToken: Node, sourceFile: SourceFile, container: Node) { - const pos = startingToken.getFullStart(); - // There’s a possibility that `startingToken.parent` contains only `startingToken` and - // missing nodes, none of which are valid to be returned by `findPrecedingToken`. In that - // case, the preceding token we want is actually higher up the tree—almost definitely the - // next parent, but theoretically the situation with missing nodes might be happening on - // multiple nested levels. - let currentParent: Node | undefined = startingToken.parent; - while (currentParent) { - const precedingToken = findPrecedingToken(pos, sourceFile, currentParent, /*excludeJsdoc*/ true); - if (precedingToken) { - return rangeContainsRange(container, precedingToken); +function createJSSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined { + if (argumentInfo.invocation.kind === InvocationKind.Contextual) return undefined; + // See if we can find some symbol with the call expression name that has call signatures. + const expression = getExpressionFromInvocation(argumentInfo.invocation); + const name = isPropertyAccessExpression(expression) ? expression.name.text : undefined; + const typeChecker = program.getTypeChecker(); + return name === undefined ? undefined : firstDefined(program.getSourceFiles(), sourceFile => + firstDefined(sourceFile.getNamedDeclarations().get(name), declaration => { + const type = declaration.symbol && typeChecker.getTypeOfSymbolAtLocation(declaration.symbol, declaration); + const callSignatures = type && type.getCallSignatures(); + if (callSignatures && callSignatures.length) { + return typeChecker.runWithCancellationToken( + cancellationToken, + typeChecker => createSignatureHelpItems( + callSignatures, + callSignatures[0], + argumentInfo, + sourceFile, + typeChecker, + /*useFullPrefix*/ true)); } - currentParent = currentParent.parent; + })); +} + +function containsPrecedingToken(startingToken: Node, sourceFile: SourceFile, container: Node) { + const pos = startingToken.getFullStart(); + // There’s a possibility that `startingToken.parent` contains only `startingToken` and + // missing nodes, none of which are valid to be returned by `findPrecedingToken`. In that + // case, the preceding token we want is actually higher up the tree—almost definitely the + // next parent, but theoretically the situation with missing nodes might be happening on + // multiple nested levels. + let currentParent: Node | undefined = startingToken.parent; + while (currentParent) { + const precedingToken = findPrecedingToken(pos, sourceFile, currentParent, /*excludeJsdoc*/ true); + if (precedingToken) { + return rangeContainsRange(container, precedingToken); } - return Debug.fail("Could not find preceding token"); + currentParent = currentParent.parent; } + return Debug.fail("Could not find preceding token"); +} - export interface ArgumentInfoForCompletions { - readonly invocation: CallLikeExpression; - readonly argumentIndex: number; - readonly argumentCount: number; +/** @internal */ +export interface ArgumentInfoForCompletions { + readonly invocation: CallLikeExpression; + readonly argumentIndex: number; + readonly argumentCount: number; +} +/** @internal */ +export function getArgumentInfoForCompletions(node: Node, position: number, sourceFile: SourceFile): ArgumentInfoForCompletions | undefined { + const info = getImmediatelyContainingArgumentInfo(node, position, sourceFile); + return !info || info.isTypeParameterList || info.invocation.kind !== InvocationKind.Call ? undefined + : { invocation: info.invocation.node, argumentCount: info.argumentCount, argumentIndex: info.argumentIndex }; +} + +function getArgumentOrParameterListInfo(node: Node, position: number, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number, readonly argumentCount: number, readonly argumentsSpan: TextSpan } | undefined { + const info = getArgumentOrParameterListAndIndex(node, sourceFile); + if (!info) return undefined; + const { list, argumentIndex } = info; + + const argumentCount = getArgumentCount(list, /*ignoreTrailingComma*/ isInString(sourceFile, position, node)); + if (argumentIndex !== 0) { + Debug.assertLessThan(argumentIndex, argumentCount); } - export function getArgumentInfoForCompletions(node: Node, position: number, sourceFile: SourceFile): ArgumentInfoForCompletions | undefined { - const info = getImmediatelyContainingArgumentInfo(node, position, sourceFile); - return !info || info.isTypeParameterList || info.invocation.kind !== InvocationKind.Call ? undefined - : { invocation: info.invocation.node, argumentCount: info.argumentCount, argumentIndex: info.argumentIndex }; + const argumentsSpan = getApplicableSpanForArguments(list, sourceFile); + return { list, argumentIndex, argumentCount, argumentsSpan }; +} +function getArgumentOrParameterListAndIndex(node: Node, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number } | undefined { + if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) { + // Find the list that starts right *after* the < or ( token. + // If the user has just opened a list, consider this item 0. + return { list: getChildListThatStartsWithOpenerToken(node.parent, node, sourceFile), argumentIndex: 0 }; + } + else { + // findListItemInfo can return undefined if we are not in parent's argument list + // or type argument list. This includes cases where the cursor is: + // - To the right of the closing parenthesis, non-substitution template, or template tail. + // - Between the type arguments and the arguments (greater than token) + // - On the target of the call (parent.func) + // - On the 'new' keyword in a 'new' expression + const list = findContainingList(node); + return list && { list, argumentIndex: getArgumentIndex(list, node) }; } +} - function getArgumentOrParameterListInfo(node: Node, position: number, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number, readonly argumentCount: number, readonly argumentsSpan: TextSpan } | undefined { - const info = getArgumentOrParameterListAndIndex(node, sourceFile); +/** + * Returns relevant information for the argument list and the current argument if we are + * in the argument of an invocation; returns undefined otherwise. + */ +function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined { + const { parent } = node; + if (isCallOrNewExpression(parent)) { + const invocation = parent; + + // There are 3 cases to handle: + // 1. The token introduces a list, and should begin a signature help session + // 2. The token is either not associated with a list, or ends a list, so the session should end + // 3. The token is buried inside a list, and should give signature help + // + // The following are examples of each: + // + // Case 1: + // foo<#T, U>(#a, b) -> The token introduces a list, and should begin a signature help session + // Case 2: + // fo#o#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end + // Case 3: + // foo(a#, #b#) -> The token is buried inside a list, and should give signature help + // Find out if 'node' is an argument, a type argument, or neither + const info = getArgumentOrParameterListInfo(node, position, sourceFile); if (!info) return undefined; - const { list, argumentIndex } = info; - - const argumentCount = getArgumentCount(list, /*ignoreTrailingComma*/ isInString(sourceFile, position, node)); - if (argumentIndex !== 0) { - Debug.assertLessThan(argumentIndex, argumentCount); - } - const argumentsSpan = getApplicableSpanForArguments(list, sourceFile); - return { list, argumentIndex, argumentCount, argumentsSpan }; - } - function getArgumentOrParameterListAndIndex(node: Node, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number } | undefined { - if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) { - // Find the list that starts right *after* the < or ( token. - // If the user has just opened a list, consider this item 0. - return { list: getChildListThatStartsWithOpenerToken(node.parent, node, sourceFile), argumentIndex: 0 }; - } - else { - // findListItemInfo can return undefined if we are not in parent's argument list - // or type argument list. This includes cases where the cursor is: - // - To the right of the closing parenthesis, non-substitution template, or template tail. - // - Between the type arguments and the arguments (greater than token) - // - On the target of the call (parent.func) - // - On the 'new' keyword in a 'new' expression - const list = findContainingList(node); - return list && { list, argumentIndex: getArgumentIndex(list, node) }; + const { list, argumentIndex, argumentCount, argumentsSpan } = info; + const isTypeParameterList = !!parent.typeArguments && parent.typeArguments.pos === list.pos; + return { isTypeParameterList, invocation: { kind: InvocationKind.Call, node: invocation }, argumentsSpan, argumentIndex, argumentCount }; + } + else if (isNoSubstitutionTemplateLiteral(node) && isTaggedTemplateExpression(parent)) { + // Check if we're actually inside the template; + // otherwise we'll fall out and return undefined. + if (isInsideTemplateLiteral(node, position, sourceFile)) { + return getArgumentListInfoForTemplate(parent, /*argumentIndex*/ 0, sourceFile); } + return undefined; } + else if (isTemplateHead(node) && parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { + const templateExpression = parent as TemplateExpression; + const tagExpression = templateExpression.parent as TaggedTemplateExpression; + Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); - /** - * Returns relevant information for the argument list and the current argument if we are - * in the argument of an invocation; returns undefined otherwise. - */ - function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined { - const { parent } = node; - if (isCallOrNewExpression(parent)) { - const invocation = parent; - - // There are 3 cases to handle: - // 1. The token introduces a list, and should begin a signature help session - // 2. The token is either not associated with a list, or ends a list, so the session should end - // 3. The token is buried inside a list, and should give signature help - // - // The following are examples of each: - // - // Case 1: - // foo<#T, U>(#a, b) -> The token introduces a list, and should begin a signature help session - // Case 2: - // fo#o#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end - // Case 3: - // foo(a#, #b#) -> The token is buried inside a list, and should give signature help - // Find out if 'node' is an argument, a type argument, or neither - const info = getArgumentOrParameterListInfo(node, position, sourceFile); - if (!info) return undefined; - const { list, argumentIndex, argumentCount, argumentsSpan } = info; - const isTypeParameterList = !!parent.typeArguments && parent.typeArguments.pos === list.pos; - return { isTypeParameterList, invocation: { kind: InvocationKind.Call, node: invocation }, argumentsSpan, argumentIndex, argumentCount }; - } - else if (isNoSubstitutionTemplateLiteral(node) && isTaggedTemplateExpression(parent)) { - // Check if we're actually inside the template; - // otherwise we'll fall out and return undefined. - if (isInsideTemplateLiteral(node, position, sourceFile)) { - return getArgumentListInfoForTemplate(parent, /*argumentIndex*/ 0, sourceFile); - } - return undefined; - } - else if (isTemplateHead(node) && parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { - const templateExpression = parent as TemplateExpression; - const tagExpression = templateExpression.parent as TaggedTemplateExpression; - Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); + const argumentIndex = isInsideTemplateLiteral(node, position, sourceFile) ? 0 : 1; - const argumentIndex = isInsideTemplateLiteral(node, position, sourceFile) ? 0 : 1; + return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); + } + else if (isTemplateSpan(parent) && isTaggedTemplateExpression(parent.parent.parent)) { + const templateSpan = parent; + const tagExpression = parent.parent.parent; - return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); + // If we're just after a template tail, don't show signature help. + if (isTemplateTail(node) && !isInsideTemplateLiteral(node, position, sourceFile)) { + return undefined; } - else if (isTemplateSpan(parent) && isTaggedTemplateExpression(parent.parent.parent)) { - const templateSpan = parent; - const tagExpression = parent.parent.parent; - // If we're just after a template tail, don't show signature help. - if (isTemplateTail(node) && !isInsideTemplateLiteral(node, position, sourceFile)) { - return undefined; - } + const spanIndex = templateSpan.parent.templateSpans.indexOf(templateSpan); + const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position, sourceFile); - const spanIndex = templateSpan.parent.templateSpans.indexOf(templateSpan); - const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position, sourceFile); - - return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); - } - else if (isJsxOpeningLikeElement(parent)) { - // Provide a signature help for JSX opening element or JSX self-closing element. - // This is not guarantee that JSX tag-name is resolved into stateless function component. (that is done in "getSignatureHelpItems") - // i.e - // export function MainButton(props: ButtonProps, context: any): JSX.Element { ... } - // isFunctionTypeNode(d) ? d.parent.symbol : undefined) || s - : s; - } +// The type of a function type node has a symbol at that node, but it's better to use the symbol for a parameter or type alias. +function chooseBetterSymbol(s: Symbol): Symbol { + return s.name === InternalSymbolName.Type + ? firstDefined(s.declarations, d => isFunctionTypeNode(d) ? d.parent.symbol : undefined) || s + : s; +} - function getArgumentIndex(argumentsList: Node, node: Node) { - // The list we got back can include commas. In the presence of errors it may - // also just have nodes without commas. For example "Foo(a b c)" will have 3 - // args without commas. We want to find what index we're at. So we count - // forward until we hit ourselves, only incrementing the index if it isn't a - // comma. - // - // Note: the subtlety around trailing commas (in getArgumentCount) does not apply - // here. That's because we're only walking forward until we hit the node we're - // on. In that case, even if we're after the trailing comma, we'll still see - // that trailing comma in the list, and we'll have generated the appropriate - // arg index. - let argumentIndex = 0; - for (const child of argumentsList.getChildren()) { - if (child === node) { - break; - } - if (child.kind !== SyntaxKind.CommaToken) { - argumentIndex++; - } +function getArgumentIndex(argumentsList: Node, node: Node) { + // The list we got back can include commas. In the presence of errors it may + // also just have nodes without commas. For example "Foo(a b c)" will have 3 + // args without commas. We want to find what index we're at. So we count + // forward until we hit ourselves, only incrementing the index if it isn't a + // comma. + // + // Note: the subtlety around trailing commas (in getArgumentCount) does not apply + // here. That's because we're only walking forward until we hit the node we're + // on. In that case, even if we're after the trailing comma, we'll still see + // that trailing comma in the list, and we'll have generated the appropriate + // arg index. + let argumentIndex = 0; + for (const child of argumentsList.getChildren()) { + if (child === node) { + break; } - - return argumentIndex; - } - - function getArgumentCount(argumentsList: Node, ignoreTrailingComma: boolean) { - // The argument count for a list is normally the number of non-comma children it has. - // For example, if you have "Foo(a,b)" then there will be three children of the arg - // list 'a' '' 'b'. So, in this case the arg count will be 2. However, there - // is a small subtlety. If you have "Foo(a,)", then the child list will just have - // 'a' ''. So, in the case where the last child is a comma, we increase the - // arg count by one to compensate. - // - // Note: this subtlety only applies to the last comma. If you had "Foo(a,," then - // we'll have: 'a' '' '' - // That will give us 2 non-commas. We then add one for the last comma, giving us an - // arg count of 3. - const listChildren = argumentsList.getChildren(); - - let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken); - if (!ignoreTrailingComma && listChildren.length > 0 && last(listChildren).kind === SyntaxKind.CommaToken) { - argumentCount++; + if (child.kind !== SyntaxKind.CommaToken) { + argumentIndex++; } - return argumentCount; } - // spanIndex is either the index for a given template span. - // This does not give appropriate results for a NoSubstitutionTemplateLiteral - function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node, position: number, sourceFile: SourceFile): number { - // Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1. - // There are three cases we can encounter: - // 1. We are precisely in the template literal (argIndex = 0). - // 2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1). - // 3. We are directly to the right of the template literal, but because we look for the token on the left, - // not enough to put us in the substitution expression; we should consider ourselves part of - // the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1). - // - /* eslint-disable local/no-double-space */ - // Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # ` - // ^ ^ ^ ^ ^ ^ ^ ^ ^ - // Case: 1 1 3 2 1 3 2 2 1 - /* eslint-enable local/no-double-space */ - Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node."); - if (isTemplateLiteralToken(node)) { - if (isInsideTemplateLiteral(node, position, sourceFile)) { - return 0; - } - return spanIndex + 2; - } - return spanIndex + 1; - } + return argumentIndex; +} + +function getArgumentCount(argumentsList: Node, ignoreTrailingComma: boolean) { + // The argument count for a list is normally the number of non-comma children it has. + // For example, if you have "Foo(a,b)" then there will be three children of the arg + // list 'a' '' 'b'. So, in this case the arg count will be 2. However, there + // is a small subtlety. If you have "Foo(a,)", then the child list will just have + // 'a' ''. So, in the case where the last child is a comma, we increase the + // arg count by one to compensate. + // + // Note: this subtlety only applies to the last comma. If you had "Foo(a,," then + // we'll have: 'a' '' '' + // That will give us 2 non-commas. We then add one for the last comma, giving us an + // arg count of 3. + const listChildren = argumentsList.getChildren(); + + let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken); + if (!ignoreTrailingComma && listChildren.length > 0 && last(listChildren).kind === SyntaxKind.CommaToken) { + argumentCount++; + } + return argumentCount; +} - function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number, sourceFile: SourceFile): ArgumentListInfo { - // argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument. - const argumentCount = isNoSubstitutionTemplateLiteral(tagExpression.template) ? 1 : tagExpression.template.templateSpans.length + 1; - if (argumentIndex !== 0) { - Debug.assertLessThan(argumentIndex, argumentCount); +// spanIndex is either the index for a given template span. +// This does not give appropriate results for a NoSubstitutionTemplateLiteral +function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node, position: number, sourceFile: SourceFile): number { + // Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1. + // There are three cases we can encounter: + // 1. We are precisely in the template literal (argIndex = 0). + // 2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1). + // 3. We are directly to the right of the template literal, but because we look for the token on the left, + // not enough to put us in the substitution expression; we should consider ourselves part of + // the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1). + // + /* eslint-disable local/no-double-space */ + // Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # ` + // ^ ^ ^ ^ ^ ^ ^ ^ ^ + // Case: 1 1 3 2 1 3 2 2 1 + /* eslint-enable local/no-double-space */ + Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node."); + if (isTemplateLiteralToken(node)) { + if (isInsideTemplateLiteral(node, position, sourceFile)) { + return 0; } - return { - isTypeParameterList: false, - invocation: { kind: InvocationKind.Call, node: tagExpression }, - argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression, sourceFile), - argumentIndex, - argumentCount - }; + return spanIndex + 2; } + return spanIndex + 1; +} - function getApplicableSpanForArguments(argumentsList: Node, sourceFile: SourceFile): TextSpan { - // We use full start and skip trivia on the end because we want to include trivia on - // both sides. For example, - // - // foo( /*comment */ a, b, c /*comment*/ ) - // | | - // - // The applicable span is from the first bar to the second bar (inclusive, - // but not including parentheses) - const applicableSpanStart = argumentsList.getFullStart(); - const applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false); - return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); - } +function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number, sourceFile: SourceFile): ArgumentListInfo { + // argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument. + const argumentCount = isNoSubstitutionTemplateLiteral(tagExpression.template) ? 1 : tagExpression.template.templateSpans.length + 1; + if (argumentIndex !== 0) { + Debug.assertLessThan(argumentIndex, argumentCount); + } + return { + isTypeParameterList: false, + invocation: { kind: InvocationKind.Call, node: tagExpression }, + argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression, sourceFile), + argumentIndex, + argumentCount + }; +} - function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression, sourceFile: SourceFile): TextSpan { - const template = taggedTemplate.template; - const applicableSpanStart = template.getStart(); - let applicableSpanEnd = template.getEnd(); +function getApplicableSpanForArguments(argumentsList: Node, sourceFile: SourceFile): TextSpan { + // We use full start and skip trivia on the end because we want to include trivia on + // both sides. For example, + // + // foo( /*comment */ a, b, c /*comment*/ ) + // | | + // + // The applicable span is from the first bar to the second bar (inclusive, + // but not including parentheses) + const applicableSpanStart = argumentsList.getFullStart(); + const applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false); + return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); +} - // We need to adjust the end position for the case where the template does not have a tail. - // Otherwise, we will not show signature help past the expression. - // For example, - // - // ` ${ 1 + 1 foo(10) - // | | - // This is because a Missing node has no width. However, what we actually want is to include trivia - // leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail. - if (template.kind === SyntaxKind.TemplateExpression) { - const lastSpan = last(template.templateSpans); - if (lastSpan.literal.getFullWidth() === 0) { - applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false); - } +function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression, sourceFile: SourceFile): TextSpan { + const template = taggedTemplate.template; + const applicableSpanStart = template.getStart(); + let applicableSpanEnd = template.getEnd(); + + // We need to adjust the end position for the case where the template does not have a tail. + // Otherwise, we will not show signature help past the expression. + // For example, + // + // ` ${ 1 + 1 foo(10) + // | | + // This is because a Missing node has no width. However, what we actually want is to include trivia + // leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail. + if (template.kind === SyntaxKind.TemplateExpression) { + const lastSpan = last(template.templateSpans); + if (lastSpan.literal.getFullWidth() === 0) { + applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false); } - - return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); } - function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker, isManuallyInvoked: boolean): ArgumentListInfo | undefined { - for (let n = node; !isSourceFile(n) && (isManuallyInvoked || !isBlock(n)); n = n.parent) { - // If the node is not a subspan of its parent, this is a big problem. - // There have been crashes that might be caused by this violation. - Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.formatSyntaxKind(n.kind)}, parent: ${Debug.formatSyntaxKind(n.parent.kind)}`); - const argumentInfo = getImmediatelyContainingArgumentOrContextualParameterInfo(n, position, sourceFile, checker); - if (argumentInfo) { - return argumentInfo; - } + return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); +} + +function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker, isManuallyInvoked: boolean): ArgumentListInfo | undefined { + for (let n = node; !isSourceFile(n) && (isManuallyInvoked || !isBlock(n)); n = n.parent) { + // If the node is not a subspan of its parent, this is a big problem. + // There have been crashes that might be caused by this violation. + Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.formatSyntaxKind(n.kind)}, parent: ${Debug.formatSyntaxKind(n.parent.kind)}`); + const argumentInfo = getImmediatelyContainingArgumentOrContextualParameterInfo(n, position, sourceFile, checker); + if (argumentInfo) { + return argumentInfo; } - return undefined; } + return undefined; +} - function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node { - const children = parent.getChildren(sourceFile); - const indexOfOpenerToken = children.indexOf(openerToken); - Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1); - return children[indexOfOpenerToken + 1]; - } +function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node { + const children = parent.getChildren(sourceFile); + const indexOfOpenerToken = children.indexOf(openerToken); + Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1); + return children[indexOfOpenerToken + 1]; +} - function getExpressionFromInvocation(invocation: CallInvocation | TypeArgsInvocation): Expression { - return invocation.kind === InvocationKind.Call ? getInvokedExpression(invocation.node) : invocation.called; - } +function getExpressionFromInvocation(invocation: CallInvocation | TypeArgsInvocation): Expression { + return invocation.kind === InvocationKind.Call ? getInvokedExpression(invocation.node) : invocation.called; +} - function getEnclosingDeclarationFromInvocation(invocation: Invocation): Node { - return invocation.kind === InvocationKind.Call ? invocation.node : invocation.kind === InvocationKind.TypeArgs ? invocation.called : invocation.node; - } +function getEnclosingDeclarationFromInvocation(invocation: Invocation): Node { + return invocation.kind === InvocationKind.Call ? invocation.node : invocation.kind === InvocationKind.TypeArgs ? invocation.called : invocation.node; +} - const signatureHelpNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; - function createSignatureHelpItems( - candidates: readonly Signature[], - resolvedSignature: Signature, - { isTypeParameterList, argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo, - sourceFile: SourceFile, - typeChecker: TypeChecker, - useFullPrefix?: boolean, - ): SignatureHelpItems { - const enclosingDeclaration = getEnclosingDeclarationFromInvocation(invocation); - const callTargetSymbol = invocation.kind === InvocationKind.Contextual ? invocation.symbol : (typeChecker.getSymbolAtLocation(getExpressionFromInvocation(invocation)) || useFullPrefix && resolvedSignature.declaration?.symbol); - const callTargetDisplayParts = callTargetSymbol ? symbolToDisplayParts(typeChecker, callTargetSymbol, useFullPrefix ? sourceFile : undefined, /*meaning*/ undefined) : emptyArray; - const items = map(candidates, candidateSignature => getSignatureHelpItem(candidateSignature, callTargetDisplayParts, isTypeParameterList, typeChecker, enclosingDeclaration, sourceFile)); - - if (argumentIndex !== 0) { - Debug.assertLessThan(argumentIndex, argumentCount); - } - let selectedItemIndex = 0; - let itemsSeen = 0; - for (let i = 0; i < items.length; i++) { - const item = items[i]; - if (candidates[i] === resolvedSignature) { - selectedItemIndex = itemsSeen; - if (item.length > 1) { - // check to see if any items in the list better match than the first one, as the checker isn't filtering the nested lists - // (those come from tuple parameter expansion) - let count = 0; - for (const i of item) { - if (i.isVariadic || i.parameters.length >= argumentCount) { - selectedItemIndex = itemsSeen + count; - break; - } - count++; +const signatureHelpNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; +function createSignatureHelpItems( + candidates: readonly Signature[], + resolvedSignature: Signature, + { isTypeParameterList, argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo, + sourceFile: SourceFile, + typeChecker: TypeChecker, + useFullPrefix?: boolean, +): SignatureHelpItems { + const enclosingDeclaration = getEnclosingDeclarationFromInvocation(invocation); + const callTargetSymbol = invocation.kind === InvocationKind.Contextual ? invocation.symbol : (typeChecker.getSymbolAtLocation(getExpressionFromInvocation(invocation)) || useFullPrefix && resolvedSignature.declaration?.symbol); + const callTargetDisplayParts = callTargetSymbol ? symbolToDisplayParts(typeChecker, callTargetSymbol, useFullPrefix ? sourceFile : undefined, /*meaning*/ undefined) : emptyArray; + const items = map(candidates, candidateSignature => getSignatureHelpItem(candidateSignature, callTargetDisplayParts, isTypeParameterList, typeChecker, enclosingDeclaration, sourceFile)); + + if (argumentIndex !== 0) { + Debug.assertLessThan(argumentIndex, argumentCount); + } + let selectedItemIndex = 0; + let itemsSeen = 0; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (candidates[i] === resolvedSignature) { + selectedItemIndex = itemsSeen; + if (item.length > 1) { + // check to see if any items in the list better match than the first one, as the checker isn't filtering the nested lists + // (those come from tuple parameter expansion) + let count = 0; + for (const i of item) { + if (i.isVariadic || i.parameters.length >= argumentCount) { + selectedItemIndex = itemsSeen + count; + break; } + count++; } } - itemsSeen += item.length; } - - Debug.assert(selectedItemIndex !== -1); // If candidates is non-empty it should always include bestSignature. We check for an empty candidates before calling this function. - const help = { items: flatMapToMutable(items, identity), applicableSpan, selectedItemIndex, argumentIndex, argumentCount }; - const selected = help.items[selectedItemIndex]; - if (selected.isVariadic) { - const firstRest = findIndex(selected.parameters, p => !!p.isRest); - if (-1 < firstRest && firstRest < selected.parameters.length - 1) { - // We don't have any code to get this correct; instead, don't highlight a current parameter AT ALL - help.argumentIndex = selected.parameters.length; - } - else { - help.argumentIndex = Math.min(help.argumentIndex, selected.parameters.length - 1); - } - } - return help; + itemsSeen += item.length; } - function createTypeHelpItems( - symbol: Symbol, - { argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo, - sourceFile: SourceFile, - checker: TypeChecker - ): SignatureHelpItems | undefined { - const typeParameters = checker.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - if (!typeParameters) return undefined; - const items = [getTypeHelpItem(symbol, typeParameters, checker, getEnclosingDeclarationFromInvocation(invocation), sourceFile)]; - return { items, applicableSpan, selectedItemIndex: 0, argumentIndex, argumentCount }; + Debug.assert(selectedItemIndex !== -1); // If candidates is non-empty it should always include bestSignature. We check for an empty candidates before calling this function. + const help = { items: flatMapToMutable(items, identity), applicableSpan, selectedItemIndex, argumentIndex, argumentCount }; + const selected = help.items[selectedItemIndex]; + if (selected.isVariadic) { + const firstRest = findIndex(selected.parameters, p => !!p.isRest); + if (-1 < firstRest && firstRest < selected.parameters.length - 1) { + // We don't have any code to get this correct; instead, don't highlight a current parameter AT ALL + help.argumentIndex = selected.parameters.length; + } + else { + help.argumentIndex = Math.min(help.argumentIndex, selected.parameters.length - 1); + } } + return help; +} - function getTypeHelpItem(symbol: Symbol, typeParameters: readonly TypeParameter[], checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem { - const typeSymbolDisplay = symbolToDisplayParts(checker, symbol); +function createTypeHelpItems( + symbol: Symbol, + { argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo, + sourceFile: SourceFile, + checker: TypeChecker +): SignatureHelpItems | undefined { + const typeParameters = checker.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + if (!typeParameters) return undefined; + const items = [getTypeHelpItem(symbol, typeParameters, checker, getEnclosingDeclarationFromInvocation(invocation), sourceFile)]; + return { items, applicableSpan, selectedItemIndex: 0, argumentIndex, argumentCount }; +} - const printer = createPrinter({ removeComments: true }); - const parameters = typeParameters.map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer)); +function getTypeHelpItem(symbol: Symbol, typeParameters: readonly TypeParameter[], checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem { + const typeSymbolDisplay = symbolToDisplayParts(checker, symbol); - const documentation = symbol.getDocumentationComment(checker); - const tags = symbol.getJsDocTags(checker); - const prefixDisplayParts = [...typeSymbolDisplay, punctuationPart(SyntaxKind.LessThanToken)]; - return { isVariadic: false, prefixDisplayParts, suffixDisplayParts: [punctuationPart(SyntaxKind.GreaterThanToken)], separatorDisplayParts, parameters, documentation, tags }; - } + const printer = createPrinter({ removeComments: true }); + const parameters = typeParameters.map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer)); - const separatorDisplayParts: SymbolDisplayPart[] = [punctuationPart(SyntaxKind.CommaToken), spacePart()]; + const documentation = symbol.getDocumentationComment(checker); + const tags = symbol.getJsDocTags(checker); + const prefixDisplayParts = [...typeSymbolDisplay, punctuationPart(SyntaxKind.LessThanToken)]; + return { isVariadic: false, prefixDisplayParts, suffixDisplayParts: [punctuationPart(SyntaxKind.GreaterThanToken)], separatorDisplayParts, parameters, documentation, tags }; +} - function getSignatureHelpItem(candidateSignature: Signature, callTargetDisplayParts: readonly SymbolDisplayPart[], isTypeParameterList: boolean, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem[] { - const infos = (isTypeParameterList ? itemInfoForTypeParameters : itemInfoForParameters)(candidateSignature, checker, enclosingDeclaration, sourceFile); - return map(infos, ({ isVariadic, parameters, prefix, suffix }) => { - const prefixDisplayParts = [...callTargetDisplayParts, ...prefix]; - const suffixDisplayParts = [...suffix, ...returnTypeToDisplayParts(candidateSignature, enclosingDeclaration, checker)]; - const documentation = candidateSignature.getDocumentationComment(checker); - const tags = candidateSignature.getJsDocTags(); - return { isVariadic, prefixDisplayParts, suffixDisplayParts, separatorDisplayParts, parameters, documentation, tags }; - }); - } +const separatorDisplayParts: SymbolDisplayPart[] = [punctuationPart(SyntaxKind.CommaToken), spacePart()]; + +function getSignatureHelpItem(candidateSignature: Signature, callTargetDisplayParts: readonly SymbolDisplayPart[], isTypeParameterList: boolean, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem[] { + const infos = (isTypeParameterList ? itemInfoForTypeParameters : itemInfoForParameters)(candidateSignature, checker, enclosingDeclaration, sourceFile); + return map(infos, ({ isVariadic, parameters, prefix, suffix }) => { + const prefixDisplayParts = [...callTargetDisplayParts, ...prefix]; + const suffixDisplayParts = [...suffix, ...returnTypeToDisplayParts(candidateSignature, enclosingDeclaration, checker)]; + const documentation = candidateSignature.getDocumentationComment(checker); + const tags = candidateSignature.getJsDocTags(); + return { isVariadic, prefixDisplayParts, suffixDisplayParts, separatorDisplayParts, parameters, documentation, tags }; + }); +} - function returnTypeToDisplayParts(candidateSignature: Signature, enclosingDeclaration: Node, checker: TypeChecker): readonly SymbolDisplayPart[] { - return mapToDisplayParts(writer => { - writer.writePunctuation(":"); - writer.writeSpace(" "); - const predicate = checker.getTypePredicateOfSignature(candidateSignature); - if (predicate) { - checker.writeTypePredicate(predicate, enclosingDeclaration, /*flags*/ undefined, writer); - } - else { - checker.writeType(checker.getReturnTypeOfSignature(candidateSignature), enclosingDeclaration, /*flags*/ undefined, writer); - } - }); - } +function returnTypeToDisplayParts(candidateSignature: Signature, enclosingDeclaration: Node, checker: TypeChecker): readonly SymbolDisplayPart[] { + return mapToDisplayParts(writer => { + writer.writePunctuation(":"); + writer.writeSpace(" "); + const predicate = checker.getTypePredicateOfSignature(candidateSignature); + if (predicate) { + checker.writeTypePredicate(predicate, enclosingDeclaration, /*flags*/ undefined, writer); + } + else { + checker.writeType(checker.getReturnTypeOfSignature(candidateSignature), enclosingDeclaration, /*flags*/ undefined, writer); + } + }); +} - interface SignatureHelpItemInfo { readonly isVariadic: boolean; readonly parameters: SignatureHelpParameter[]; readonly prefix: readonly SymbolDisplayPart[]; readonly suffix: readonly SymbolDisplayPart[]; } +interface SignatureHelpItemInfo { readonly isVariadic: boolean; readonly parameters: SignatureHelpParameter[]; readonly prefix: readonly SymbolDisplayPart[]; readonly suffix: readonly SymbolDisplayPart[]; } - function itemInfoForTypeParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo[] { - const typeParameters = (candidateSignature.target || candidateSignature).typeParameters; - const printer = createPrinter({ removeComments: true }); - const parameters = (typeParameters || emptyArray).map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer)); - const thisParameter = candidateSignature.thisParameter ? [checker.symbolToParameterDeclaration(candidateSignature.thisParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!] : []; +function itemInfoForTypeParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo[] { + const typeParameters = (candidateSignature.target || candidateSignature).typeParameters; + const printer = createPrinter({ removeComments: true }); + const parameters = (typeParameters || emptyArray).map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer)); + const thisParameter = candidateSignature.thisParameter ? [checker.symbolToParameterDeclaration(candidateSignature.thisParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!] : []; - return checker.getExpandedParameters(candidateSignature).map(paramList => { - const params = factory.createNodeArray([...thisParameter, ...map(paramList, param => checker.symbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)]); - const parameterParts = mapToDisplayParts(writer => { - printer.writeList(ListFormat.CallExpressionArguments, params, sourceFile, writer); - }); - return { isVariadic: false, parameters, prefix: [punctuationPart(SyntaxKind.LessThanToken)], suffix: [punctuationPart(SyntaxKind.GreaterThanToken), ...parameterParts] }; + return checker.getExpandedParameters(candidateSignature).map(paramList => { + const params = factory.createNodeArray([...thisParameter, ...map(paramList, param => checker.symbolToParameterDeclaration(param, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)]); + const parameterParts = mapToDisplayParts(writer => { + printer.writeList(ListFormat.CallExpressionArguments, params, sourceFile, writer); }); - } + return { isVariadic: false, parameters, prefix: [punctuationPart(SyntaxKind.LessThanToken)], suffix: [punctuationPart(SyntaxKind.GreaterThanToken), ...parameterParts] }; + }); +} - function itemInfoForParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo[] { - const printer = createPrinter({ removeComments: true }); - const typeParameterParts = mapToDisplayParts(writer => { - if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) { - const args = factory.createNodeArray(candidateSignature.typeParameters.map(p => checker.typeParameterToDeclaration(p, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)); - printer.writeList(ListFormat.TypeParameters, args, sourceFile, writer); - } - }); - const lists = checker.getExpandedParameters(candidateSignature); - const isVariadic: (parameterList: readonly Symbol[]) => boolean = - !checker.hasEffectiveRestParameter(candidateSignature) ? _ => false - : lists.length === 1 ? _ => true - : pList => !!(pList.length && (pList[pList.length - 1] as TransientSymbol).checkFlags & CheckFlags.RestParameter); - return lists.map(parameterList => ({ - isVariadic: isVariadic(parameterList), - parameters: parameterList.map(p => createSignatureHelpParameterForParameter(p, checker, enclosingDeclaration, sourceFile, printer)), - prefix: [...typeParameterParts, punctuationPart(SyntaxKind.OpenParenToken)], - suffix: [punctuationPart(SyntaxKind.CloseParenToken)] - })); - } +function itemInfoForParameters(candidateSignature: Signature, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItemInfo[] { + const printer = createPrinter({ removeComments: true }); + const typeParameterParts = mapToDisplayParts(writer => { + if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) { + const args = factory.createNodeArray(candidateSignature.typeParameters.map(p => checker.typeParameterToDeclaration(p, enclosingDeclaration, signatureHelpNodeBuilderFlags)!)); + printer.writeList(ListFormat.TypeParameters, args, sourceFile, writer); + } + }); + const lists = checker.getExpandedParameters(candidateSignature); + const isVariadic: (parameterList: readonly Symbol[]) => boolean = + !checker.hasEffectiveRestParameter(candidateSignature) ? _ => false + : lists.length === 1 ? _ => true + : pList => !!(pList.length && (pList[pList.length - 1] as TransientSymbol).checkFlags & CheckFlags.RestParameter); + return lists.map(parameterList => ({ + isVariadic: isVariadic(parameterList), + parameters: parameterList.map(p => createSignatureHelpParameterForParameter(p, checker, enclosingDeclaration, sourceFile, printer)), + prefix: [...typeParameterParts, punctuationPart(SyntaxKind.OpenParenToken)], + suffix: [punctuationPart(SyntaxKind.CloseParenToken)] + })); +} - function createSignatureHelpParameterForParameter(parameter: Symbol, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter { - const displayParts = mapToDisplayParts(writer => { - const param = checker.symbolToParameterDeclaration(parameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!; - printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); - }); - const isOptional = checker.isOptionalParameter(parameter.valueDeclaration as ParameterDeclaration); - const isRest = !!((parameter as TransientSymbol).checkFlags & CheckFlags.RestParameter); - return { name: parameter.name, documentation: parameter.getDocumentationComment(checker), displayParts, isOptional, isRest }; - } +function createSignatureHelpParameterForParameter(parameter: Symbol, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter { + const displayParts = mapToDisplayParts(writer => { + const param = checker.symbolToParameterDeclaration(parameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!; + printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); + }); + const isOptional = checker.isOptionalParameter(parameter.valueDeclaration as ParameterDeclaration); + const isRest = !!((parameter as TransientSymbol).checkFlags & CheckFlags.RestParameter); + return { name: parameter.name, documentation: parameter.getDocumentationComment(checker), displayParts, isOptional, isRest }; +} - function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter { - const displayParts = mapToDisplayParts(writer => { - const param = checker.typeParameterToDeclaration(typeParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!; - printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); - }); - return { name: typeParameter.symbol.name, documentation: typeParameter.symbol.getDocumentationComment(checker), displayParts, isOptional: false, isRest: false }; - } +function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter { + const displayParts = mapToDisplayParts(writer => { + const param = checker.typeParameterToDeclaration(typeParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!; + printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer); + }); + return { name: typeParameter.symbol.name, documentation: typeParameter.symbol.getDocumentationComment(checker), displayParts, isOptional: false, isRest: false }; } diff --git a/src/services/smartSelection.ts b/src/services/smartSelection.ts index bacaf86f29894..04ffc73c4b10a 100644 --- a/src/services/smartSelection.ts +++ b/src/services/smartSelection.ts @@ -1,320 +1,329 @@ -/* @internal */ -namespace ts.SmartSelectionRange { - export function getSmartSelectionRange(pos: number, sourceFile: SourceFile): SelectionRange { - let selectionRange: SelectionRange = { - textSpan: createTextSpanFromBounds(sourceFile.getFullStart(), sourceFile.getEnd()) - }; - - let parentNode: Node = sourceFile; - outer: while (true) { - const children = getSelectionChildren(parentNode); - if (!children.length) break; - for (let i = 0; i < children.length; i++) { - const prevNode: Node | undefined = children[i - 1]; - const node: Node = children[i]; - const nextNode: Node | undefined = children[i + 1]; - - if (getTokenPosOfNode(node, sourceFile, /*includeJsDoc*/ true) > pos) { - break outer; - } +import { + CharacterCodes, compact, contains, createTextSpanFromBounds, Debug, findIndex, first, getTokenPosOfNode, + getTouchingPropertyName, getTrailingCommentRanges, hasJSDocNodes, isBindingElement, isBlock, isFunctionBody, + isFunctionLikeDeclaration, isImportDeclaration, isImportEqualsDeclaration, isJSDocSignature, isJSDocTypeExpression, + isJSDocTypeLiteral, isMappedTypeNode, isParameter, isPropertySignature, isSourceFile, isStringLiteral, isSyntaxList, + isTemplateHead, isTemplateLiteral, isTemplateMiddleOrTemplateTail, isTemplateSpan, isTemplateTail, + isVariableDeclaration, isVariableDeclarationList, isVariableStatement, last, Node, or, parseNodeFactory, + positionsAreOnSameLine, SelectionRange, setTextRangePosEnd, singleOrUndefined, SourceFile, SyntaxKind, SyntaxList, + textSpanIntersectsWithPosition, textSpansEqual, +} from "./_namespaces/ts"; - const comment = singleOrUndefined(getTrailingCommentRanges(sourceFile.text, node.end)); - if (comment && comment.kind === SyntaxKind.SingleLineCommentTrivia) { - pushSelectionCommentRange(comment.pos, comment.end); - } +/** @internal */ +export function getSmartSelectionRange(pos: number, sourceFile: SourceFile): SelectionRange { + let selectionRange: SelectionRange = { + textSpan: createTextSpanFromBounds(sourceFile.getFullStart(), sourceFile.getEnd()) + }; - if (positionShouldSnapToNode(sourceFile, pos, node)) { - if (isFunctionBody(node) - && isFunctionLikeDeclaration(parentNode) && !positionsAreOnSameLine(node.getStart(sourceFile), node.getEnd(), sourceFile)) { - pushSelectionRange(node.getStart(sourceFile), node.getEnd()); - } + let parentNode: Node = sourceFile; + outer: while (true) { + const children = getSelectionChildren(parentNode); + if (!children.length) break; + for (let i = 0; i < children.length; i++) { + const prevNode: Node | undefined = children[i - 1]; + const node: Node = children[i]; + const nextNode: Node | undefined = children[i + 1]; - // 1. Blocks are effectively redundant with SyntaxLists. - // 2. TemplateSpans, along with the SyntaxLists containing them, are a somewhat unintuitive grouping - // of things that should be considered independently. - // 3. A VariableStatement’s children are just a VaraiableDeclarationList and a semicolon. - // 4. A lone VariableDeclaration in a VaraibleDeclaration feels redundant with the VariableStatement. - // Dive in without pushing a selection range. - if (isBlock(node) - || isTemplateSpan(node) || isTemplateHead(node) || isTemplateTail(node) - || prevNode && isTemplateHead(prevNode) - || isVariableDeclarationList(node) && isVariableStatement(parentNode) - || isSyntaxList(node) && isVariableDeclarationList(parentNode) - || isVariableDeclaration(node) && isSyntaxList(parentNode) && children.length === 1 - || isJSDocTypeExpression(node) || isJSDocSignature(node) || isJSDocTypeLiteral(node)) { - parentNode = node; - break; - } + if (getTokenPosOfNode(node, sourceFile, /*includeJsDoc*/ true) > pos) { + break outer; + } - // Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings. - if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) { - const start = node.getFullStart() - "${".length; - const end = nextNode.getStart() + "}".length; - pushSelectionRange(start, end); - } + const comment = singleOrUndefined(getTrailingCommentRanges(sourceFile.text, node.end)); + if (comment && comment.kind === SyntaxKind.SingleLineCommentTrivia) { + pushSelectionCommentRange(comment.pos, comment.end); + } - // Blocks with braces, brackets, parens, or JSX tags on separate lines should be - // selected from open to close, including whitespace but not including the braces/etc. themselves. - const isBetweenMultiLineBookends = isSyntaxList(node) && isListOpener(prevNode) && isListCloser(nextNode) - && !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile); - let start = isBetweenMultiLineBookends ? prevNode.getEnd() : node.getStart(); - const end = isBetweenMultiLineBookends ? nextNode.getStart() : getEndPos(sourceFile, node); + if (positionShouldSnapToNode(sourceFile, pos, node)) { + if (isFunctionBody(node) + && isFunctionLikeDeclaration(parentNode) && !positionsAreOnSameLine(node.getStart(sourceFile), node.getEnd(), sourceFile)) { + pushSelectionRange(node.getStart(sourceFile), node.getEnd()); + } - if (hasJSDocNodes(node) && node.jsDoc?.length) { - pushSelectionRange(first(node.jsDoc).getStart(), end); - } + // 1. Blocks are effectively redundant with SyntaxLists. + // 2. TemplateSpans, along with the SyntaxLists containing them, are a somewhat unintuitive grouping + // of things that should be considered independently. + // 3. A VariableStatement’s children are just a VaraiableDeclarationList and a semicolon. + // 4. A lone VariableDeclaration in a VaraibleDeclaration feels redundant with the VariableStatement. + // Dive in without pushing a selection range. + if (isBlock(node) + || isTemplateSpan(node) || isTemplateHead(node) || isTemplateTail(node) + || prevNode && isTemplateHead(prevNode) + || isVariableDeclarationList(node) && isVariableStatement(parentNode) + || isSyntaxList(node) && isVariableDeclarationList(parentNode) + || isVariableDeclaration(node) && isSyntaxList(parentNode) && children.length === 1 + || isJSDocTypeExpression(node) || isJSDocSignature(node) || isJSDocTypeLiteral(node)) { + parentNode = node; + break; + } - // (#39618 & #49807) - // When the node is a SyntaxList and its first child has a JSDoc comment, then the node's - // `start` (which usually is the result of calling `node.getStart()`) points to the first - // token after the JSDoc comment. So, we have to make sure we'd pushed the selection - // covering the JSDoc comment before diving further. - if (isSyntaxList(node)) { - const firstChild = node.getChildren()[0]; - if (firstChild && hasJSDocNodes(firstChild) && firstChild.jsDoc?.length && firstChild.getStart() !== node.pos) { - start = Math.min(start, first(firstChild.jsDoc).getStart()); - } - } + // Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings. + if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) { + const start = node.getFullStart() - "${".length; + const end = nextNode.getStart() + "}".length; pushSelectionRange(start, end); + } - // String literals should have a stop both inside and outside their quotes. - if (isStringLiteral(node) || isTemplateLiteral(node)) { - pushSelectionRange(start + 1, end - 1); - } + // Blocks with braces, brackets, parens, or JSX tags on separate lines should be + // selected from open to close, including whitespace but not including the braces/etc. themselves. + const isBetweenMultiLineBookends = isSyntaxList(node) && isListOpener(prevNode) && isListCloser(nextNode) + && !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile); + let start = isBetweenMultiLineBookends ? prevNode.getEnd() : node.getStart(); + const end = isBetweenMultiLineBookends ? nextNode.getStart() : getEndPos(sourceFile, node); - parentNode = node; - break; + if (hasJSDocNodes(node) && node.jsDoc?.length) { + pushSelectionRange(first(node.jsDoc).getStart(), end); } - // If we made it to the end of the for loop, we’re done. - // In practice, I’ve only seen this happen at the very end - // of a SourceFile. - if (i === children.length - 1) { - break outer; + // (#39618 & #49807) + // When the node is a SyntaxList and its first child has a JSDoc comment, then the node's + // `start` (which usually is the result of calling `node.getStart()`) points to the first + // token after the JSDoc comment. So, we have to make sure we'd pushed the selection + // covering the JSDoc comment before diving further. + if (isSyntaxList(node)) { + const firstChild = node.getChildren()[0]; + if (firstChild && hasJSDocNodes(firstChild) && firstChild.jsDoc?.length && firstChild.getStart() !== node.pos) { + start = Math.min(start, first(firstChild.jsDoc).getStart()); + } } - } - } + pushSelectionRange(start, end); - return selectionRange; - - function pushSelectionRange(start: number, end: number): void { - // Skip empty ranges - if (start !== end) { - const textSpan = createTextSpanFromBounds(start, end); - if (!selectionRange || ( - // Skip ranges that are identical to the parent - !textSpansEqual(textSpan, selectionRange.textSpan) && - // Skip ranges that don’t contain the original position - textSpanIntersectsWithPosition(textSpan, pos) - )) { - selectionRange = { textSpan, ...selectionRange && { parent: selectionRange } }; + // String literals should have a stop both inside and outside their quotes. + if (isStringLiteral(node) || isTemplateLiteral(node)) { + pushSelectionRange(start + 1, end - 1); } - } - } - function pushSelectionCommentRange(start: number, end: number): void { - pushSelectionRange(start, end); + parentNode = node; + break; + } - let pos = start; - while (sourceFile.text.charCodeAt(pos) === CharacterCodes.slash) { - pos++; + // If we made it to the end of the for loop, we’re done. + // In practice, I’ve only seen this happen at the very end + // of a SourceFile. + if (i === children.length - 1) { + break outer; } - pushSelectionRange(pos, end); } } - /** - * Like `ts.positionBelongsToNode`, except positions immediately after nodes - * count too, unless that position belongs to the next node. In effect, makes - * selections able to snap to preceding tokens when the cursor is on the tail - * end of them with only whitespace ahead. - * @param sourceFile The source file containing the nodes. - * @param pos The position to check. - * @param node The candidate node to snap to. - */ - function positionShouldSnapToNode(sourceFile: SourceFile, pos: number, node: Node) { - // Can’t use 'ts.positionBelongsToNode()' here because it cleverly accounts - // for missing nodes, which can’t really be considered when deciding what - // to select. - Debug.assert(node.pos <= pos); - if (pos < node.end) { - return true; - } - const nodeEnd = node.getEnd(); - if (nodeEnd === pos) { - return getTouchingPropertyName(sourceFile, pos).pos < node.end; + return selectionRange; + + function pushSelectionRange(start: number, end: number): void { + // Skip empty ranges + if (start !== end) { + const textSpan = createTextSpanFromBounds(start, end); + if (!selectionRange || ( + // Skip ranges that are identical to the parent + !textSpansEqual(textSpan, selectionRange.textSpan) && + // Skip ranges that don’t contain the original position + textSpanIntersectsWithPosition(textSpan, pos) + )) { + selectionRange = { textSpan, ...selectionRange && { parent: selectionRange } }; + } } - return false; } - const isImport = or(isImportDeclaration, isImportEqualsDeclaration); - - /** - * Gets the children of a node to be considered for selection ranging, - * transforming them into an artificial tree according to their intuitive - * grouping where no grouping actually exists in the parse tree. For example, - * top-level imports are grouped into their own SyntaxList so they can be - * selected all together, even though in the AST they’re just siblings of each - * other as well as of other top-level statements and declarations. - */ - function getSelectionChildren(node: Node): readonly Node[] { - // Group top-level imports - if (isSourceFile(node)) { - return groupChildren(node.getChildAt(0).getChildren(), isImport); - } + function pushSelectionCommentRange(start: number, end: number): void { + pushSelectionRange(start, end); - // Mapped types _look_ like ObjectTypes with a single member, - // but in fact don’t contain a SyntaxList or a node containing - // the “key/value” pair like ObjectTypes do, but it seems intuitive - // that the selection would snap to those points. The philosophy - // of choosing a selection range is not so much about what the - // syntax currently _is_ as what the syntax might easily become - // if the user is making a selection; e.g., we synthesize a selection - // around the “key/value” pair not because there’s a node there, but - // because it allows the mapped type to become an object type with a - // few keystrokes. - if (isMappedTypeNode(node)) { - const [openBraceToken, ...children] = node.getChildren(); - const closeBraceToken = Debug.checkDefined(children.pop()); - Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken); - Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken); - // Group `-/+readonly` and `-/+?` - const groupedWithPlusMinusTokens = groupChildren(children, child => - child === node.readonlyToken || child.kind === SyntaxKind.ReadonlyKeyword || - child === node.questionToken || child.kind === SyntaxKind.QuestionToken); - // Group type parameter with surrounding brackets - const groupedWithBrackets = groupChildren(groupedWithPlusMinusTokens, ({ kind }) => - kind === SyntaxKind.OpenBracketToken || - kind === SyntaxKind.TypeParameter || - kind === SyntaxKind.CloseBracketToken - ); - return [ - openBraceToken, - // Pivot on `:` - createSyntaxList(splitChildren(groupedWithBrackets, ({ kind }) => kind === SyntaxKind.ColonToken)), - closeBraceToken, - ]; + let pos = start; + while (sourceFile.text.charCodeAt(pos) === CharacterCodes.slash) { + pos++; } + pushSelectionRange(pos, end); + } +} - // Group modifiers and property name, then pivot on `:`. - if (isPropertySignature(node)) { - const children = groupChildren(node.getChildren(), child => - child === node.name || contains(node.modifiers, child)); - const firstJSDocChild = children[0]?.kind === SyntaxKind.JSDoc ? children[0] : undefined; - const withJSDocSeparated = firstJSDocChild? children.slice(1) : children; - const splittedChildren = splitChildren(withJSDocSeparated, ({ kind }) => kind === SyntaxKind.ColonToken); - return firstJSDocChild? [firstJSDocChild, createSyntaxList(splittedChildren)] : splittedChildren; - } +/** + * Like `ts.positionBelongsToNode`, except positions immediately after nodes + * count too, unless that position belongs to the next node. In effect, makes + * selections able to snap to preceding tokens when the cursor is on the tail + * end of them with only whitespace ahead. + * @param sourceFile The source file containing the nodes. + * @param pos The position to check. + * @param node The candidate node to snap to. + */ +function positionShouldSnapToNode(sourceFile: SourceFile, pos: number, node: Node) { + // Can’t use 'ts.positionBelongsToNode()' here because it cleverly accounts + // for missing nodes, which can’t really be considered when deciding what + // to select. + Debug.assert(node.pos <= pos); + if (pos < node.end) { + return true; + } + const nodeEnd = node.getEnd(); + if (nodeEnd === pos) { + return getTouchingPropertyName(sourceFile, pos).pos < node.end; + } + return false; +} - // Group the parameter name with its `...`, then that group with its `?`, then pivot on `=`. - if (isParameter(node)) { - const groupedDotDotDotAndName = groupChildren(node.getChildren(), child => - child === node.dotDotDotToken || child === node.name); - const groupedWithQuestionToken = groupChildren(groupedDotDotDotAndName, child => - child === groupedDotDotDotAndName[0] || child === node.questionToken); - return splitChildren(groupedWithQuestionToken, ({ kind }) => kind === SyntaxKind.EqualsToken); - } +const isImport = or(isImportDeclaration, isImportEqualsDeclaration); - // Pivot on '=' - if (isBindingElement(node)) { - return splitChildren(node.getChildren(), ({ kind }) => kind === SyntaxKind.EqualsToken); - } +/** + * Gets the children of a node to be considered for selection ranging, + * transforming them into an artificial tree according to their intuitive + * grouping where no grouping actually exists in the parse tree. For example, + * top-level imports are grouped into their own SyntaxList so they can be + * selected all together, even though in the AST they’re just siblings of each + * other as well as of other top-level statements and declarations. + */ +function getSelectionChildren(node: Node): readonly Node[] { + // Group top-level imports + if (isSourceFile(node)) { + return groupChildren(node.getChildAt(0).getChildren(), isImport); + } - return node.getChildren(); + // Mapped types _look_ like ObjectTypes with a single member, + // but in fact don’t contain a SyntaxList or a node containing + // the “key/value” pair like ObjectTypes do, but it seems intuitive + // that the selection would snap to those points. The philosophy + // of choosing a selection range is not so much about what the + // syntax currently _is_ as what the syntax might easily become + // if the user is making a selection; e.g., we synthesize a selection + // around the “key/value” pair not because there’s a node there, but + // because it allows the mapped type to become an object type with a + // few keystrokes. + if (isMappedTypeNode(node)) { + const [openBraceToken, ...children] = node.getChildren(); + const closeBraceToken = Debug.checkDefined(children.pop()); + Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken); + Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken); + // Group `-/+readonly` and `-/+?` + const groupedWithPlusMinusTokens = groupChildren(children, child => + child === node.readonlyToken || child.kind === SyntaxKind.ReadonlyKeyword || + child === node.questionToken || child.kind === SyntaxKind.QuestionToken); + // Group type parameter with surrounding brackets + const groupedWithBrackets = groupChildren(groupedWithPlusMinusTokens, ({ kind }) => + kind === SyntaxKind.OpenBracketToken || + kind === SyntaxKind.TypeParameter || + kind === SyntaxKind.CloseBracketToken + ); + return [ + openBraceToken, + // Pivot on `:` + createSyntaxList(splitChildren(groupedWithBrackets, ({ kind }) => kind === SyntaxKind.ColonToken)), + closeBraceToken, + ]; } - /** - * Groups sibling nodes together into their own SyntaxList if they - * a) are adjacent, AND b) match a predicate function. - */ - function groupChildren(children: Node[], groupOn: (child: Node) => boolean): Node[] { - const result: Node[] = []; - let group: Node[] | undefined; - for (const child of children) { - if (groupOn(child)) { - group = group || []; - group.push(child); - } - else { - if (group) { - result.push(createSyntaxList(group)); - group = undefined; - } - result.push(child); - } - } - if (group) { - result.push(createSyntaxList(group)); - } + // Group modifiers and property name, then pivot on `:`. + if (isPropertySignature(node)) { + const children = groupChildren(node.getChildren(), child => + child === node.name || contains(node.modifiers, child)); + const firstJSDocChild = children[0]?.kind === SyntaxKind.JSDoc ? children[0] : undefined; + const withJSDocSeparated = firstJSDocChild? children.slice(1) : children; + const splittedChildren = splitChildren(withJSDocSeparated, ({ kind }) => kind === SyntaxKind.ColonToken); + return firstJSDocChild? [firstJSDocChild, createSyntaxList(splittedChildren)] : splittedChildren; + } - return result; + // Group the parameter name with its `...`, then that group with its `?`, then pivot on `=`. + if (isParameter(node)) { + const groupedDotDotDotAndName = groupChildren(node.getChildren(), child => + child === node.dotDotDotToken || child === node.name); + const groupedWithQuestionToken = groupChildren(groupedDotDotDotAndName, child => + child === groupedDotDotDotAndName[0] || child === node.questionToken); + return splitChildren(groupedWithQuestionToken, ({ kind }) => kind === SyntaxKind.EqualsToken); } - /** - * Splits sibling nodes into up to four partitions: - * 1) everything left of the first node matched by `pivotOn`, - * 2) the first node matched by `pivotOn`, - * 3) everything right of the first node matched by `pivotOn`, - * 4) a trailing semicolon, if `separateTrailingSemicolon` is enabled. - * The left and right groups, if not empty, will each be grouped into their own containing SyntaxList. - * @param children The sibling nodes to split. - * @param pivotOn The predicate function to match the node to be the pivot. The first node that matches - * the predicate will be used; any others that may match will be included into the right-hand group. - * @param separateTrailingSemicolon If the last token is a semicolon, it will be returned as a separate - * child rather than be included in the right-hand group. - */ - function splitChildren(children: Node[], pivotOn: (child: Node) => boolean, separateTrailingSemicolon = true): Node[] { - if (children.length < 2) { - return children; + // Pivot on '=' + if (isBindingElement(node)) { + return splitChildren(node.getChildren(), ({ kind }) => kind === SyntaxKind.EqualsToken); + } + + return node.getChildren(); +} + +/** + * Groups sibling nodes together into their own SyntaxList if they + * a) are adjacent, AND b) match a predicate function. + */ +function groupChildren(children: Node[], groupOn: (child: Node) => boolean): Node[] { + const result: Node[] = []; + let group: Node[] | undefined; + for (const child of children) { + if (groupOn(child)) { + group = group || []; + group.push(child); } - const splitTokenIndex = findIndex(children, pivotOn); - if (splitTokenIndex === -1) { - return children; + else { + if (group) { + result.push(createSyntaxList(group)); + group = undefined; + } + result.push(child); } - const leftChildren = children.slice(0, splitTokenIndex); - const splitToken = children[splitTokenIndex]; - const lastToken = last(children); - const separateLastToken = separateTrailingSemicolon && lastToken.kind === SyntaxKind.SemicolonToken; - const rightChildren = children.slice(splitTokenIndex + 1, separateLastToken ? children.length - 1 : undefined); - const result = compact([ - leftChildren.length ? createSyntaxList(leftChildren) : undefined, - splitToken, - rightChildren.length ? createSyntaxList(rightChildren) : undefined, - ]); - return separateLastToken ? result.concat(lastToken) : result; } - - function createSyntaxList(children: Node[]): SyntaxList { - Debug.assertGreaterThanOrEqual(children.length, 1); - return setTextRangePosEnd(parseNodeFactory.createSyntaxList(children), children[0].pos, last(children).end); + if (group) { + result.push(createSyntaxList(group)); } - function isListOpener(token: Node | undefined): token is Node { - const kind = token && token.kind; - return kind === SyntaxKind.OpenBraceToken - || kind === SyntaxKind.OpenBracketToken - || kind === SyntaxKind.OpenParenToken - || kind === SyntaxKind.JsxOpeningElement; - } + return result; +} - function isListCloser(token: Node | undefined): token is Node { - const kind = token && token.kind; - return kind === SyntaxKind.CloseBraceToken - || kind === SyntaxKind.CloseBracketToken - || kind === SyntaxKind.CloseParenToken - || kind === SyntaxKind.JsxClosingElement; +/** + * Splits sibling nodes into up to four partitions: + * 1) everything left of the first node matched by `pivotOn`, + * 2) the first node matched by `pivotOn`, + * 3) everything right of the first node matched by `pivotOn`, + * 4) a trailing semicolon, if `separateTrailingSemicolon` is enabled. + * The left and right groups, if not empty, will each be grouped into their own containing SyntaxList. + * @param children The sibling nodes to split. + * @param pivotOn The predicate function to match the node to be the pivot. The first node that matches + * the predicate will be used; any others that may match will be included into the right-hand group. + * @param separateTrailingSemicolon If the last token is a semicolon, it will be returned as a separate + * child rather than be included in the right-hand group. + */ +function splitChildren(children: Node[], pivotOn: (child: Node) => boolean, separateTrailingSemicolon = true): Node[] { + if (children.length < 2) { + return children; + } + const splitTokenIndex = findIndex(children, pivotOn); + if (splitTokenIndex === -1) { + return children; } + const leftChildren = children.slice(0, splitTokenIndex); + const splitToken = children[splitTokenIndex]; + const lastToken = last(children); + const separateLastToken = separateTrailingSemicolon && lastToken.kind === SyntaxKind.SemicolonToken; + const rightChildren = children.slice(splitTokenIndex + 1, separateLastToken ? children.length - 1 : undefined); + const result = compact([ + leftChildren.length ? createSyntaxList(leftChildren) : undefined, + splitToken, + rightChildren.length ? createSyntaxList(rightChildren) : undefined, + ]); + return separateLastToken ? result.concat(lastToken) : result; +} - function getEndPos(sourceFile: SourceFile, node: Node): number { - switch (node.kind) { - case SyntaxKind.JSDocParameterTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocPropertyTag: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocThisTag: - return sourceFile.getLineEndOfPosition(node.getStart()); - default: - return node.getEnd(); - } +function createSyntaxList(children: Node[]): SyntaxList { + Debug.assertGreaterThanOrEqual(children.length, 1); + return setTextRangePosEnd(parseNodeFactory.createSyntaxList(children), children[0].pos, last(children).end); +} + +function isListOpener(token: Node | undefined): token is Node { + const kind = token && token.kind; + return kind === SyntaxKind.OpenBraceToken + || kind === SyntaxKind.OpenBracketToken + || kind === SyntaxKind.OpenParenToken + || kind === SyntaxKind.JsxOpeningElement; +} + +function isListCloser(token: Node | undefined): token is Node { + const kind = token && token.kind; + return kind === SyntaxKind.CloseBraceToken + || kind === SyntaxKind.CloseBracketToken + || kind === SyntaxKind.CloseParenToken + || kind === SyntaxKind.JsxClosingElement; +} + +function getEndPos(sourceFile: SourceFile, node: Node): number { + switch (node.kind) { + case SyntaxKind.JSDocParameterTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocPropertyTag: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocThisTag: + return sourceFile.getLineEndOfPosition(node.getStart()); + default: + return node.getEnd(); } } diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index ed4bb7c892822..73079ef532e76 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -1,199 +1,211 @@ -/* @internal */ -namespace ts { - const base64UrlRegExp = /^data:(?:application\/json(?:;charset=[uU][tT][fF]-8);base64,([A-Za-z0-9+\/=]+)$)?/; - - export interface SourceMapper { - toLineColumnOffset(fileName: string, position: number): LineAndCharacter; - tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined; - tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined; - clearCache(): void; - } +import * as ts from "./_namespaces/ts"; +import { + base64decode, computeLineAndCharacterOfPosition, createDocumentPositionMapper, createGetCanonicalFileName, + DocumentPosition, DocumentPositionMapper, DocumentPositionMapperHost, Extension, + getDeclarationEmitOutputFilePathWorker, getDirectoryPath, getLineInfo, getLineStarts, getNormalizedAbsolutePath, + identitySourceMapConsumer, isDeclarationFileName, isString, LineAndCharacter, LineInfo, Map, outFile, Program, + removeFileExtension, SourceFileLike, sys, tryGetSourceMappingURL, tryParseRawSourceMap, +} from "./_namespaces/ts"; + +const base64UrlRegExp = /^data:(?:application\/json(?:;charset=[uU][tT][fF]-8);base64,([A-Za-z0-9+\/=]+)$)?/; + +/** @internal */ +export interface SourceMapper { + toLineColumnOffset(fileName: string, position: number): LineAndCharacter; + tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined; + tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined; + clearCache(): void; +} - export interface SourceMapperHost { - useCaseSensitiveFileNames(): boolean; - getCurrentDirectory(): string; - getProgram(): Program | undefined; - fileExists?(path: string): boolean; - readFile?(path: string, encoding?: string): string | undefined; - getSourceFileLike?(fileName: string): SourceFileLike | undefined; - getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; - log(s: string): void; - } +/** @internal */ +export interface SourceMapperHost { + useCaseSensitiveFileNames(): boolean; + getCurrentDirectory(): string; + getProgram(): Program | undefined; + fileExists?(path: string): boolean; + readFile?(path: string, encoding?: string): string | undefined; + getSourceFileLike?(fileName: string): SourceFileLike | undefined; + getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; + log(s: string): void; +} - export function getSourceMapper(host: SourceMapperHost): SourceMapper { - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - const currentDirectory = host.getCurrentDirectory(); - const sourceFileLike = new Map(); - const documentPositionMappers = new Map(); - return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache }; +/** @internal */ +export function getSourceMapper(host: SourceMapperHost): SourceMapper { + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + const currentDirectory = host.getCurrentDirectory(); + const sourceFileLike = new Map(); + const documentPositionMappers = new Map(); + return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache }; - function toPath(fileName: string) { - return ts.toPath(fileName, currentDirectory, getCanonicalFileName); - } + function toPath(fileName: string) { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + } - function getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string) { - const path = toPath(generatedFileName); - const value = documentPositionMappers.get(path); - if (value) return value; + function getDocumentPositionMapper(generatedFileName: string, sourceFileName?: string) { + const path = toPath(generatedFileName); + const value = documentPositionMappers.get(path); + if (value) return value; - let mapper: DocumentPositionMapper | undefined; - if (host.getDocumentPositionMapper) { - mapper = host.getDocumentPositionMapper(generatedFileName, sourceFileName); - } - else if (host.readFile) { - const file = getSourceFileLike(generatedFileName); - mapper = file && ts.getDocumentPositionMapper( - { getSourceFileLike, getCanonicalFileName, log: s => host.log(s) }, - generatedFileName, - getLineInfo(file.text, getLineStarts(file)), - f => !host.fileExists || host.fileExists(f) ? host.readFile!(f) : undefined - ); - } - documentPositionMappers.set(path, mapper || identitySourceMapConsumer); - return mapper || identitySourceMapConsumer; + let mapper: DocumentPositionMapper | undefined; + if (host.getDocumentPositionMapper) { + mapper = host.getDocumentPositionMapper(generatedFileName, sourceFileName); } - - function tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined { - if (!isDeclarationFileName(info.fileName)) return undefined; - - const file = getSourceFile(info.fileName); - if (!file) return undefined; - - const newLoc = getDocumentPositionMapper(info.fileName).getSourcePosition(info); - return !newLoc || newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc; + else if (host.readFile) { + const file = getSourceFileLike(generatedFileName); + mapper = file && ts.getDocumentPositionMapper( + { getSourceFileLike, getCanonicalFileName, log: s => host.log(s) }, + generatedFileName, + getLineInfo(file.text, getLineStarts(file)), + f => !host.fileExists || host.fileExists(f) ? host.readFile!(f) : undefined + ); } + documentPositionMappers.set(path, mapper || identitySourceMapConsumer); + return mapper || identitySourceMapConsumer; + } - function tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined { - if (isDeclarationFileName(info.fileName)) return undefined; + function tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined { + if (!isDeclarationFileName(info.fileName)) return undefined; - const sourceFile = getSourceFile(info.fileName); - if (!sourceFile) return undefined; + const file = getSourceFile(info.fileName); + if (!file) return undefined; - const program = host.getProgram()!; - // If this is source file of project reference source (instead of redirect) there is no generated position - if (program.isSourceOfProjectReferenceRedirect(sourceFile.fileName)) { - return undefined; - } + const newLoc = getDocumentPositionMapper(info.fileName).getSourcePosition(info); + return !newLoc || newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc; + } - const options = program.getCompilerOptions(); - const outPath = outFile(options); + function tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined { + if (isDeclarationFileName(info.fileName)) return undefined; - const declarationPath = outPath ? - removeFileExtension(outPath) + Extension.Dts : - getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName); - if (declarationPath === undefined) return undefined; + const sourceFile = getSourceFile(info.fileName); + if (!sourceFile) return undefined; - const newLoc = getDocumentPositionMapper(declarationPath, info.fileName).getGeneratedPosition(info); - return newLoc === info ? undefined : newLoc; + const program = host.getProgram()!; + // If this is source file of project reference source (instead of redirect) there is no generated position + if (program.isSourceOfProjectReferenceRedirect(sourceFile.fileName)) { + return undefined; } - function getSourceFile(fileName: string) { - const program = host.getProgram(); - if (!program) return undefined; + const options = program.getCompilerOptions(); + const outPath = outFile(options); - const path = toPath(fileName); - // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file - const file = program.getSourceFileByPath(path); - return file && file.resolvedPath === path ? file : undefined; - } + const declarationPath = outPath ? + removeFileExtension(outPath) + Extension.Dts : + getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName); + if (declarationPath === undefined) return undefined; - function getOrCreateSourceFileLike(fileName: string): SourceFileLike | undefined { - const path = toPath(fileName); - const fileFromCache = sourceFileLike.get(path); - if (fileFromCache !== undefined) return fileFromCache ? fileFromCache : undefined; + const newLoc = getDocumentPositionMapper(declarationPath, info.fileName).getGeneratedPosition(info); + return newLoc === info ? undefined : newLoc; + } - if (!host.readFile || host.fileExists && !host.fileExists(path)) { - sourceFileLike.set(path, false); - return undefined; - } + function getSourceFile(fileName: string) { + const program = host.getProgram(); + if (!program) return undefined; - // And failing that, check the disk - const text = host.readFile(path); - const file = text ? createSourceFileLike(text) : false; - sourceFileLike.set(path, file); - return file ? file : undefined; - } + const path = toPath(fileName); + // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file + const file = program.getSourceFileByPath(path); + return file && file.resolvedPath === path ? file : undefined; + } - // This can be called from source mapper in either source program or program that includes generated file - function getSourceFileLike(fileName: string) { - return !host.getSourceFileLike ? - getSourceFile(fileName) || getOrCreateSourceFileLike(fileName) : - host.getSourceFileLike(fileName); - } + function getOrCreateSourceFileLike(fileName: string): SourceFileLike | undefined { + const path = toPath(fileName); + const fileFromCache = sourceFileLike.get(path); + if (fileFromCache !== undefined) return fileFromCache ? fileFromCache : undefined; - function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { - const file = getSourceFileLike(fileName)!; // TODO: GH#18217 - return file.getLineAndCharacterOfPosition(position); + if (!host.readFile || host.fileExists && !host.fileExists(path)) { + sourceFileLike.set(path, false); + return undefined; } - function clearCache(): void { - sourceFileLike.clear(); - documentPositionMappers.clear(); - } + // And failing that, check the disk + const text = host.readFile(path); + const file = text ? createSourceFileLike(text) : false; + sourceFileLike.set(path, file); + return file ? file : undefined; } - /** - * string | undefined to contents of map file to create DocumentPositionMapper from it - * DocumentPositionMapper | false to give back cached DocumentPositionMapper - */ - export type ReadMapFile = (mapFileName: string, mapFileNameFromDts: string | undefined) => string | undefined | DocumentPositionMapper | false; - - export function getDocumentPositionMapper( - host: DocumentPositionMapperHost, - generatedFileName: string, - generatedFileLineInfo: LineInfo, - readMapFile: ReadMapFile) { - let mapFileName = tryGetSourceMappingURL(generatedFileLineInfo); - if (mapFileName) { - const match = base64UrlRegExp.exec(mapFileName); - if (match) { - if (match[1]) { - const base64Object = match[1]; - return convertDocumentToSourceMapper(host, base64decode(sys, base64Object), generatedFileName); - } - // Not a data URL we can parse, skip it - mapFileName = undefined; + // This can be called from source mapper in either source program or program that includes generated file + function getSourceFileLike(fileName: string) { + return !host.getSourceFileLike ? + getSourceFile(fileName) || getOrCreateSourceFileLike(fileName) : + host.getSourceFileLike(fileName); + } + + function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { + const file = getSourceFileLike(fileName)!; // TODO: GH#18217 + return file.getLineAndCharacterOfPosition(position); + } + + function clearCache(): void { + sourceFileLike.clear(); + documentPositionMappers.clear(); + } +} + +/** + * string | undefined to contents of map file to create DocumentPositionMapper from it + * DocumentPositionMapper | false to give back cached DocumentPositionMapper + * + * @internal + */ +export type ReadMapFile = (mapFileName: string, mapFileNameFromDts: string | undefined) => string | undefined | DocumentPositionMapper | false; + +/** @internal */ +export function getDocumentPositionMapper( + host: DocumentPositionMapperHost, + generatedFileName: string, + generatedFileLineInfo: LineInfo, + readMapFile: ReadMapFile) { + let mapFileName = tryGetSourceMappingURL(generatedFileLineInfo); + if (mapFileName) { + const match = base64UrlRegExp.exec(mapFileName); + if (match) { + if (match[1]) { + const base64Object = match[1]; + return convertDocumentToSourceMapper(host, base64decode(sys, base64Object), generatedFileName); } + // Not a data URL we can parse, skip it + mapFileName = undefined; } - const possibleMapLocations: string[] = []; - if (mapFileName) { - possibleMapLocations.push(mapFileName); + } + const possibleMapLocations: string[] = []; + if (mapFileName) { + possibleMapLocations.push(mapFileName); + } + possibleMapLocations.push(generatedFileName + ".map"); + const originalMapFileName = mapFileName && getNormalizedAbsolutePath(mapFileName, getDirectoryPath(generatedFileName)); + for (const location of possibleMapLocations) { + const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(generatedFileName)); + const mapFileContents = readMapFile(mapFileName, originalMapFileName); + if (isString(mapFileContents)) { + return convertDocumentToSourceMapper(host, mapFileContents, mapFileName); } - possibleMapLocations.push(generatedFileName + ".map"); - const originalMapFileName = mapFileName && getNormalizedAbsolutePath(mapFileName, getDirectoryPath(generatedFileName)); - for (const location of possibleMapLocations) { - const mapFileName = getNormalizedAbsolutePath(location, getDirectoryPath(generatedFileName)); - const mapFileContents = readMapFile(mapFileName, originalMapFileName); - if (isString(mapFileContents)) { - return convertDocumentToSourceMapper(host, mapFileContents, mapFileName); - } - if (mapFileContents !== undefined) { - return mapFileContents || undefined; - } + if (mapFileContents !== undefined) { + return mapFileContents || undefined; } - return undefined; } + return undefined; +} - function convertDocumentToSourceMapper(host: DocumentPositionMapperHost, contents: string, mapFileName: string) { - const map = tryParseRawSourceMap(contents); - if (!map || !map.sources || !map.file || !map.mappings) { - // obviously invalid map - return undefined; - } +function convertDocumentToSourceMapper(host: DocumentPositionMapperHost, contents: string, mapFileName: string) { + const map = tryParseRawSourceMap(contents); + if (!map || !map.sources || !map.file || !map.mappings) { + // obviously invalid map + return undefined; + } - // Dont support sourcemaps that contain inlined sources - if (map.sourcesContent && map.sourcesContent.some(isString)) return undefined; + // Dont support sourcemaps that contain inlined sources + if (map.sourcesContent && map.sourcesContent.some(isString)) return undefined; - return createDocumentPositionMapper(host, map, mapFileName); - } + return createDocumentPositionMapper(host, map, mapFileName); +} - function createSourceFileLike(text: string, lineMap?: SourceFileLike["lineMap"]): SourceFileLike { - return { - text, - lineMap, - getLineAndCharacterOfPosition(pos: number) { - return computeLineAndCharacterOfPosition(getLineStarts(this), pos); - } - }; - } +function createSourceFileLike(text: string, lineMap?: SourceFileLike["lineMap"]): SourceFileLike { + return { + text, + lineMap, + getLineAndCharacterOfPosition(pos: number) { + return computeLineAndCharacterOfPosition(getLineStarts(this), pos); + } + }; } diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 33b508017683a..15117e0ce76f0 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -1,995 +1,1025 @@ -/* @internal */ -namespace ts.Completions.StringCompletions { - interface NameAndKindSet { - add(value: NameAndKind): void; - has(name: string): boolean; - values(): Iterator; - } - const kindPrecedence = { - [ScriptElementKind.directory]: 0, - [ScriptElementKind.scriptElement]: 1, - [ScriptElementKind.externalModuleName]: 2, - }; - function createNameAndKindSet(): NameAndKindSet { - const map = new Map(); - function add(value: NameAndKind) { - const existing = map.get(value.name); - if (!existing || kindPrecedence[existing.kind] < kindPrecedence[value.kind]) { - map.set(value.name, value); - } - } - return { - add, - has: map.has.bind(map), - values: map.values.bind(map), - }; - } - - export function getStringLiteralCompletions( - sourceFile: SourceFile, - position: number, - contextToken: Node | undefined, - options: CompilerOptions, - host: LanguageServiceHost, - program: Program, - log: Log, - preferences: UserPreferences): CompletionInfo | undefined { - if (isInReferenceComment(sourceFile, position)) { - const entries = getTripleSlashReferenceCompletion(sourceFile, position, options, host); - return entries && convertPathCompletions(entries); - } - if (isInString(sourceFile, position, contextToken)) { - if (!contextToken || !isStringLiteralLike(contextToken)) return undefined; - const entries = getStringLiteralCompletionEntries(sourceFile, contextToken, position, program.getTypeChecker(), options, host, preferences); - return convertStringLiteralCompletions(entries, contextToken, sourceFile, host, program, log, options, preferences); +import { + addToSeen, altDirectorySeparator, arrayFrom, CallLikeExpression, CancellationToken, changeExtension, CharacterCodes, + combinePaths, comparePaths, comparePatternKeys, compareStringsCaseSensitive, compareValues, Comparison, + CompilerOptions, CompletionEntry, CompletionEntryDetails, CompletionInfo, contains, containsPath, ContextFlags, + createSortedArray, createTextSpan, createTextSpanFromStringLiteralLikeContent, Debug, deduplicate, + directorySeparator, ElementAccessExpression, emptyArray, endsWith, ensureTrailingDirectorySeparator, + equateStringsCaseSensitive, Extension, fileExtensionIsOneOf, filter, find, findAncestor, findPackageJson, + findPackageJsons, firstDefined, firstOrUndefined, flatMap, flatten, forEachAncestorDirectory, getBaseFileName, + getContextualTypeFromParent, getDirectoryPath, getEffectiveTypeRoots, getEmitModuleResolutionKind, + getLeadingCommentRanges, getModeForUsageLocation, getOwnKeys, getPackageJsonTypesVersionsPaths, getPathComponents, + getReplacementSpanForContextToken, getSupportedExtensions, getSupportedExtensionsWithJsonIfResolveJsonModule, + getTokenAtPosition, hasIndexSignature, hasProperty, hasTrailingDirectorySeparator, hostGetCanonicalFileName, + IndexedAccessTypeNode, isApplicableVersionedTypesKey, isArray, isCallExpression, isIdentifier, isIdentifierText, + isImportCall, isInReferenceComment, isInString, isJsxAttribute, isJsxOpeningLikeElement, isLiteralTypeNode, + isObjectLiteralExpression, isPatternMatch, isPrivateIdentifierClassElementDeclaration, isRootedDiskPath, isString, + isStringLiteral, isStringLiteralLike, isTypeReferenceNode, isUrl, Iterator, JsxAttribute, LanguageServiceHost, + length, LiteralExpression, LiteralTypeNode, Map, mapDefined, MapLike, ModuleKind, ModuleResolutionKind, + moduleSpecifiers, Node, normalizePath, normalizeSlashes, ObjectLiteralExpression, Path, Program, PropertyAssignment, + rangeContainsPosition, readJson, removeFileExtension, removePrefix, removeTrailingDirectorySeparator, resolvePath, + ScriptElementKind, ScriptElementKindModifier, ScriptTarget, Signature, signatureHasRestParameter, SignatureHelp, + singleElementArray, skipConstraint, skipParentheses, SourceFile, startsWith, stringContains, StringLiteralLike, + StringLiteralType, stripQuotes, Symbol, SyntaxKind, textPart, TextSpan, tryAndIgnoreErrors, tryDirectoryExists, + tryFileExists, tryGetDirectories, tryGetExtensionFromPath, tryParsePattern, tryReadDirectory, + tryRemoveDirectoryPrefix, tryRemovePrefix, Type, TypeChecker, TypeFlags, UnionTypeNode, unmangleScopedPackageName, + UserPreferences, walkUpParenthesizedExpressions, walkUpParenthesizedTypes, +} from "./_namespaces/ts"; +import { + CompletionKind, createCompletionDetails, createCompletionDetailsForSymbol, getCompletionEntriesFromSymbols, + getPropertiesForObjectExpression, Log, SortText, +} from "./_namespaces/ts.Completions"; + +interface NameAndKindSet { + add(value: NameAndKind): void; + has(name: string): boolean; + values(): Iterator; +} +const kindPrecedence = { + [ScriptElementKind.directory]: 0, + [ScriptElementKind.scriptElement]: 1, + [ScriptElementKind.externalModuleName]: 2, +}; +function createNameAndKindSet(): NameAndKindSet { + const map = new Map(); + function add(value: NameAndKind) { + const existing = map.get(value.name); + if (!existing || kindPrecedence[existing.kind] < kindPrecedence[value.kind]) { + map.set(value.name, value); } } + return { + add, + has: map.has.bind(map), + values: map.values.bind(map), + }; +} - function convertStringLiteralCompletions( - completion: StringLiteralCompletion | undefined, - contextToken: StringLiteralLike, - sourceFile: SourceFile, - host: LanguageServiceHost, - program: Program, - log: Log, - options: CompilerOptions, - preferences: UserPreferences, - ): CompletionInfo | undefined { - if (completion === undefined) { - return undefined; - } - - const optionalReplacementSpan = createTextSpanFromStringLiteralLikeContent(contextToken); - switch (completion.kind) { - case StringLiteralCompletionKind.Paths: - return convertPathCompletions(completion.paths); - case StringLiteralCompletionKind.Properties: { - const entries = createSortedArray(); - getCompletionEntriesFromSymbols( - completion.symbols, - entries, - contextToken, - contextToken, - sourceFile, - sourceFile, - host, - program, - ScriptTarget.ESNext, - log, - CompletionKind.String, - preferences, - options, - /*formatContext*/ undefined, - ); // Target will not be used, so arbitrary - return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, optionalReplacementSpan, entries }; - } - case StringLiteralCompletionKind.Types: { - const entries = completion.types.map(type => ({ - name: type.value, - kindModifiers: ScriptElementKindModifier.none, - kind: ScriptElementKind.string, - sortText: SortText.LocationPriority, - replacementSpan: getReplacementSpanForContextToken(contextToken) - })); - return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, optionalReplacementSpan, entries }; - } - default: - return Debug.assertNever(completion); - } +/** @internal */ +export function getStringLiteralCompletions( + sourceFile: SourceFile, + position: number, + contextToken: Node | undefined, + options: CompilerOptions, + host: LanguageServiceHost, + program: Program, + log: Log, + preferences: UserPreferences): CompletionInfo | undefined { + if (isInReferenceComment(sourceFile, position)) { + const entries = getTripleSlashReferenceCompletion(sourceFile, position, options, host); + return entries && convertPathCompletions(entries); } - - export function getStringLiteralCompletionDetails(name: string, sourceFile: SourceFile, position: number, contextToken: Node | undefined, checker: TypeChecker, options: CompilerOptions, host: LanguageServiceHost, cancellationToken: CancellationToken, preferences: UserPreferences) { + if (isInString(sourceFile, position, contextToken)) { if (!contextToken || !isStringLiteralLike(contextToken)) return undefined; - const completions = getStringLiteralCompletionEntries(sourceFile, contextToken, position, checker, options, host, preferences); - return completions && stringLiteralCompletionDetails(name, contextToken, completions, sourceFile, checker, cancellationToken); + const entries = getStringLiteralCompletionEntries(sourceFile, contextToken, position, program.getTypeChecker(), options, host, preferences); + return convertStringLiteralCompletions(entries, contextToken, sourceFile, host, program, log, options, preferences); } +} - function stringLiteralCompletionDetails(name: string, location: Node, completion: StringLiteralCompletion, sourceFile: SourceFile, checker: TypeChecker, cancellationToken: CancellationToken): CompletionEntryDetails | undefined { - switch (completion.kind) { - case StringLiteralCompletionKind.Paths: { - const match = find(completion.paths, p => p.name === name); - return match && createCompletionDetails(name, kindModifiersFromExtension(match.extension), match.kind, [textPart(name)]); - } - case StringLiteralCompletionKind.Properties: { - const match = find(completion.symbols, s => s.name === name); - return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location, cancellationToken); - } - case StringLiteralCompletionKind.Types: - return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.typeElement, [textPart(name)]) : undefined; - default: - return Debug.assertNever(completion); +function convertStringLiteralCompletions( + completion: StringLiteralCompletion | undefined, + contextToken: StringLiteralLike, + sourceFile: SourceFile, + host: LanguageServiceHost, + program: Program, + log: Log, + options: CompilerOptions, + preferences: UserPreferences, +): CompletionInfo | undefined { + if (completion === undefined) { + return undefined; + } + + const optionalReplacementSpan = createTextSpanFromStringLiteralLikeContent(contextToken); + switch (completion.kind) { + case StringLiteralCompletionKind.Paths: + return convertPathCompletions(completion.paths); + case StringLiteralCompletionKind.Properties: { + const entries = createSortedArray(); + getCompletionEntriesFromSymbols( + completion.symbols, + entries, + contextToken, + contextToken, + sourceFile, + sourceFile, + host, + program, + ScriptTarget.ESNext, + log, + CompletionKind.String, + preferences, + options, + /*formatContext*/ undefined, + ); // Target will not be used, so arbitrary + return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: completion.hasIndexSignature, optionalReplacementSpan, entries }; + } + case StringLiteralCompletionKind.Types: { + const entries = completion.types.map(type => ({ + name: type.value, + kindModifiers: ScriptElementKindModifier.none, + kind: ScriptElementKind.string, + sortText: SortText.LocationPriority, + replacementSpan: getReplacementSpanForContextToken(contextToken) + })); + return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: completion.isNewIdentifier, optionalReplacementSpan, entries }; } + default: + return Debug.assertNever(completion); } +} - function convertPathCompletions(pathCompletions: readonly PathCompletion[]): CompletionInfo { - const isGlobalCompletion = false; // We don't want the editor to offer any other completions, such as snippets, inside a comment. - const isNewIdentifierLocation = true; // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of. - const entries = pathCompletions.map(({ name, kind, span, extension }): CompletionEntry => - ({ name, kind, kindModifiers: kindModifiersFromExtension(extension), sortText: SortText.LocationPriority, replacementSpan: span })); - return { isGlobalCompletion, isMemberCompletion: false, isNewIdentifierLocation, entries }; - } - function kindModifiersFromExtension(extension: Extension | undefined): ScriptElementKindModifier { - switch (extension) { - case Extension.Dts: return ScriptElementKindModifier.dtsModifier; - case Extension.Js: return ScriptElementKindModifier.jsModifier; - case Extension.Json: return ScriptElementKindModifier.jsonModifier; - case Extension.Jsx: return ScriptElementKindModifier.jsxModifier; - case Extension.Ts: return ScriptElementKindModifier.tsModifier; - case Extension.Tsx: return ScriptElementKindModifier.tsxModifier; - case Extension.Dmts: return ScriptElementKindModifier.dmtsModifier; - case Extension.Mjs: return ScriptElementKindModifier.mjsModifier; - case Extension.Mts: return ScriptElementKindModifier.mtsModifier; - case Extension.Dcts: return ScriptElementKindModifier.dctsModifier; - case Extension.Cjs: return ScriptElementKindModifier.cjsModifier; - case Extension.Cts: return ScriptElementKindModifier.ctsModifier; - case Extension.TsBuildInfo: return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported.`); - case undefined: return ScriptElementKindModifier.none; - default: - return Debug.assertNever(extension); +/** @internal */ +export function getStringLiteralCompletionDetails(name: string, sourceFile: SourceFile, position: number, contextToken: Node | undefined, checker: TypeChecker, options: CompilerOptions, host: LanguageServiceHost, cancellationToken: CancellationToken, preferences: UserPreferences) { + if (!contextToken || !isStringLiteralLike(contextToken)) return undefined; + const completions = getStringLiteralCompletionEntries(sourceFile, contextToken, position, checker, options, host, preferences); + return completions && stringLiteralCompletionDetails(name, contextToken, completions, sourceFile, checker, cancellationToken); +} + +function stringLiteralCompletionDetails(name: string, location: Node, completion: StringLiteralCompletion, sourceFile: SourceFile, checker: TypeChecker, cancellationToken: CancellationToken): CompletionEntryDetails | undefined { + switch (completion.kind) { + case StringLiteralCompletionKind.Paths: { + const match = find(completion.paths, p => p.name === name); + return match && createCompletionDetails(name, kindModifiersFromExtension(match.extension), match.kind, [textPart(name)]); } + case StringLiteralCompletionKind.Properties: { + const match = find(completion.symbols, s => s.name === name); + return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location, cancellationToken); + } + case StringLiteralCompletionKind.Types: + return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.typeElement, [textPart(name)]) : undefined; + default: + return Debug.assertNever(completion); } +} - const enum StringLiteralCompletionKind { Paths, Properties, Types } - interface StringLiteralCompletionsFromProperties { - readonly kind: StringLiteralCompletionKind.Properties; - readonly symbols: readonly Symbol[]; - readonly hasIndexSignature: boolean; - } - interface StringLiteralCompletionsFromTypes { - readonly kind: StringLiteralCompletionKind.Types; - readonly types: readonly StringLiteralType[]; - readonly isNewIdentifier: boolean; - } - type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: readonly PathCompletion[] } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes; - function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, preferences: UserPreferences): StringLiteralCompletion | undefined { - const parent = walkUpParentheses(node.parent); - switch (parent.kind) { - case SyntaxKind.LiteralType: { - const grandParent = walkUpParentheses(parent.parent); - switch (grandParent.kind) { - case SyntaxKind.ExpressionWithTypeArguments: - case SyntaxKind.TypeReference: { - const typeArgument = findAncestor(parent, n => n.parent === grandParent) as LiteralTypeNode; - if (typeArgument) { - return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false }; - } - return undefined; +function convertPathCompletions(pathCompletions: readonly PathCompletion[]): CompletionInfo { + const isGlobalCompletion = false; // We don't want the editor to offer any other completions, such as snippets, inside a comment. + const isNewIdentifierLocation = true; // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of. + const entries = pathCompletions.map(({ name, kind, span, extension }): CompletionEntry => + ({ name, kind, kindModifiers: kindModifiersFromExtension(extension), sortText: SortText.LocationPriority, replacementSpan: span })); + return { isGlobalCompletion, isMemberCompletion: false, isNewIdentifierLocation, entries }; +} +function kindModifiersFromExtension(extension: Extension | undefined): ScriptElementKindModifier { + switch (extension) { + case Extension.Dts: return ScriptElementKindModifier.dtsModifier; + case Extension.Js: return ScriptElementKindModifier.jsModifier; + case Extension.Json: return ScriptElementKindModifier.jsonModifier; + case Extension.Jsx: return ScriptElementKindModifier.jsxModifier; + case Extension.Ts: return ScriptElementKindModifier.tsModifier; + case Extension.Tsx: return ScriptElementKindModifier.tsxModifier; + case Extension.Dmts: return ScriptElementKindModifier.dmtsModifier; + case Extension.Mjs: return ScriptElementKindModifier.mjsModifier; + case Extension.Mts: return ScriptElementKindModifier.mtsModifier; + case Extension.Dcts: return ScriptElementKindModifier.dctsModifier; + case Extension.Cjs: return ScriptElementKindModifier.cjsModifier; + case Extension.Cts: return ScriptElementKindModifier.ctsModifier; + case Extension.TsBuildInfo: return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported.`); + case undefined: return ScriptElementKindModifier.none; + default: + return Debug.assertNever(extension); + } +} + +const enum StringLiteralCompletionKind { Paths, Properties, Types } +interface StringLiteralCompletionsFromProperties { + readonly kind: StringLiteralCompletionKind.Properties; + readonly symbols: readonly Symbol[]; + readonly hasIndexSignature: boolean; +} +interface StringLiteralCompletionsFromTypes { + readonly kind: StringLiteralCompletionKind.Types; + readonly types: readonly StringLiteralType[]; + readonly isNewIdentifier: boolean; +} +type StringLiteralCompletion = { readonly kind: StringLiteralCompletionKind.Paths, readonly paths: readonly PathCompletion[] } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes; +function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, preferences: UserPreferences): StringLiteralCompletion | undefined { + const parent = walkUpParentheses(node.parent); + switch (parent.kind) { + case SyntaxKind.LiteralType: { + const grandParent = walkUpParentheses(parent.parent); + switch (grandParent.kind) { + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.TypeReference: { + const typeArgument = findAncestor(parent, n => n.parent === grandParent) as LiteralTypeNode; + if (typeArgument) { + return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(typeArgument)), isNewIdentifier: false }; } - case SyntaxKind.IndexedAccessType: - // Get all apparent property names - // i.e. interface Foo { - // foo: string; - // bar: string; - // } - // let x: Foo["/*completion position*/"] - const { indexType, objectType } = grandParent as IndexedAccessTypeNode; - if (!rangeContainsPosition(indexType, position)) { - return undefined; - } - return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType)); - case SyntaxKind.ImportType: - return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) }; - case SyntaxKind.UnionType: { - if (!isTypeReferenceNode(grandParent.parent)) { - return undefined; - } - const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode); - const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(grandParent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value)); - return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false }; + return undefined; + } + case SyntaxKind.IndexedAccessType: + // Get all apparent property names + // i.e. interface Foo { + // foo: string; + // bar: string; + // } + // let x: Foo["/*completion position*/"] + const { indexType, objectType } = grandParent as IndexedAccessTypeNode; + if (!rangeContainsPosition(indexType, position)) { + return undefined; } - default: + return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType)); + case SyntaxKind.ImportType: + return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) }; + case SyntaxKind.UnionType: { + if (!isTypeReferenceNode(grandParent.parent)) { return undefined; + } + const alreadyUsedTypes = getAlreadyUsedTypesInStringLiteralUnion(grandParent as UnionTypeNode, parent as LiteralTypeNode); + const types = getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(grandParent as UnionTypeNode)).filter(t => !contains(alreadyUsedTypes, t.value)); + return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier: false }; } + default: + return undefined; } - case SyntaxKind.PropertyAssignment: - if (isObjectLiteralExpression(parent.parent) && (parent as PropertyAssignment).name === node) { - // Get quoted name of properties of the object literal expression - // i.e. interface ConfigFiles { - // 'jspm:dev': string - // } - // let files: ConfigFiles = { - // '/*completion position*/' - // } - // - // function foo(c: ConfigFiles) {} - // foo({ - // '/*completion position*/' - // }); - return stringLiteralCompletionsForObjectLiteral(typeChecker, parent.parent); - } - return fromContextualType(); - - case SyntaxKind.ElementAccessExpression: { - const { expression, argumentExpression } = parent as ElementAccessExpression; - if (node === skipParentheses(argumentExpression)) { - // Get all names of properties on the expression - // i.e. interface A { - // 'prop1': string - // } - // let a: A; - // a['/*completion position*/'] - return stringLiteralCompletionsFromProperties(typeChecker.getTypeAtLocation(expression)); - } - return undefined; + } + case SyntaxKind.PropertyAssignment: + if (isObjectLiteralExpression(parent.parent) && (parent as PropertyAssignment).name === node) { + // Get quoted name of properties of the object literal expression + // i.e. interface ConfigFiles { + // 'jspm:dev': string + // } + // let files: ConfigFiles = { + // '/*completion position*/' + // } + // + // function foo(c: ConfigFiles) {} + // foo({ + // '/*completion position*/' + // }); + return stringLiteralCompletionsForObjectLiteral(typeChecker, parent.parent); } - - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.JsxAttribute: - if (!isRequireCallArgument(node) && !isImportCall(parent)) { - const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(parent.kind === SyntaxKind.JsxAttribute ? parent.parent : node, position, sourceFile); - // Get string literal completions from specialized signatures of the target - // i.e. declare function f(a: 'A'); - // f("/*completion position*/") - return argumentInfo && getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) || fromContextualType(); - } - // falls through (is `require("")` or `require(""` or `import("")`) - - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ExternalModuleReference: - // Get all known external module names or complete a path to a module - // i.e. import * as ns from "/*completion position*/"; - // var y = import("/*completion position*/"); - // import x = require("/*completion position*/"); - // var y = require("/*completion position*/"); - // export * from "/*completion position*/"; - return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) }; - - default: - return fromContextualType(); + return fromContextualType(); + + case SyntaxKind.ElementAccessExpression: { + const { expression, argumentExpression } = parent as ElementAccessExpression; + if (node === skipParentheses(argumentExpression)) { + // Get all names of properties on the expression + // i.e. interface A { + // 'prop1': string + // } + // let a: A; + // a['/*completion position*/'] + return stringLiteralCompletionsFromProperties(typeChecker.getTypeAtLocation(expression)); + } + return undefined; } - function fromContextualType(): StringLiteralCompletion { - // Get completion for string literal from string literal type - // i.e. var x: "hi" | "hello" = "/*completion position*/" - return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker)), isNewIdentifier: false }; - } + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.JsxAttribute: + if (!isRequireCallArgument(node) && !isImportCall(parent)) { + const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(parent.kind === SyntaxKind.JsxAttribute ? parent.parent : node, position, sourceFile); + // Get string literal completions from specialized signatures of the target + // i.e. declare function f(a: 'A'); + // f("/*completion position*/") + return argumentInfo && getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) || fromContextualType(); + } + // falls through (is `require("")` or `require(""` or `import("")`) + + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExternalModuleReference: + // Get all known external module names or complete a path to a module + // i.e. import * as ns from "/*completion position*/"; + // var y = import("/*completion position*/"); + // import x = require("/*completion position*/"); + // var y = require("/*completion position*/"); + // export * from "/*completion position*/"; + return { kind: StringLiteralCompletionKind.Paths, paths: getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker, preferences) }; + + default: + return fromContextualType(); } - function walkUpParentheses(node: Node) { - switch (node.kind) { - case SyntaxKind.ParenthesizedType: - return walkUpParenthesizedTypes(node); - case SyntaxKind.ParenthesizedExpression: - return walkUpParenthesizedExpressions(node); - default: - return node; - } + function fromContextualType(): StringLiteralCompletion { + // Get completion for string literal from string literal type + // i.e. var x: "hi" | "hello" = "/*completion position*/" + return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker)), isNewIdentifier: false }; } +} - function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): readonly string[] { - return mapDefined(union.types, type => - type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined); - } - - function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes | undefined { - let isNewIdentifier = false; - const uniques = new Map(); - const candidates: Signature[] = []; - const editingArgument = isJsxOpeningLikeElement(call) ? Debug.checkDefined(findAncestor(arg.parent, isJsxAttribute)) : arg; - checker.getResolvedSignatureForStringLiteralCompletions(call, editingArgument, candidates); - const types = flatMap(candidates, candidate => { - if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return; - let type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex); - if (isJsxOpeningLikeElement(call)) { - const propType = checker.getTypeOfPropertyOfType(type, (editingArgument as JsxAttribute).name.text); - if (propType) { - type = propType; - } - } - isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String); - return getStringLiteralTypes(type, uniques); - }); - return length(types) ? { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier } : undefined; +function walkUpParentheses(node: Node) { + switch (node.kind) { + case SyntaxKind.ParenthesizedType: + return walkUpParenthesizedTypes(node); + case SyntaxKind.ParenthesizedExpression: + return walkUpParenthesizedExpressions(node); + default: + return node; } +} - function stringLiteralCompletionsFromProperties(type: Type | undefined): StringLiteralCompletionsFromProperties | undefined { - return type && { - kind: StringLiteralCompletionKind.Properties, - symbols: filter(type.getApparentProperties(), prop => !(prop.valueDeclaration && isPrivateIdentifierClassElementDeclaration(prop.valueDeclaration))), - hasIndexSignature: hasIndexSignature(type) - }; - } +function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: LiteralTypeNode): readonly string[] { + return mapDefined(union.types, type => + type !== current && isLiteralTypeNode(type) && isStringLiteral(type.literal) ? type.literal.text : undefined); +} - function stringLiteralCompletionsForObjectLiteral(checker: TypeChecker, objectLiteralExpression: ObjectLiteralExpression): StringLiteralCompletionsFromProperties | undefined { - const contextualType = checker.getContextualType(objectLiteralExpression); - if (!contextualType) return undefined; +function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes | undefined { + let isNewIdentifier = false; + const uniques = new Map(); + const candidates: Signature[] = []; + const editingArgument = isJsxOpeningLikeElement(call) ? Debug.checkDefined(findAncestor(arg.parent, isJsxAttribute)) : arg; + checker.getResolvedSignatureForStringLiteralCompletions(call, editingArgument, candidates); + const types = flatMap(candidates, candidate => { + if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return; + let type = candidate.getTypeParameterAtPosition(argumentInfo.argumentIndex); + if (isJsxOpeningLikeElement(call)) { + const propType = checker.getTypeOfPropertyOfType(type, (editingArgument as JsxAttribute).name.text); + if (propType) { + type = propType; + } + } + isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String); + return getStringLiteralTypes(type, uniques); + }); + return length(types) ? { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier } : undefined; +} - const completionsType = checker.getContextualType(objectLiteralExpression, ContextFlags.Completions); - const symbols = getPropertiesForObjectExpression( - contextualType, - completionsType, - objectLiteralExpression, - checker - ); +function stringLiteralCompletionsFromProperties(type: Type | undefined): StringLiteralCompletionsFromProperties | undefined { + return type && { + kind: StringLiteralCompletionKind.Properties, + symbols: filter(type.getApparentProperties(), prop => !(prop.valueDeclaration && isPrivateIdentifierClassElementDeclaration(prop.valueDeclaration))), + hasIndexSignature: hasIndexSignature(type) + }; +} - return { - kind: StringLiteralCompletionKind.Properties, - symbols, - hasIndexSignature: hasIndexSignature(contextualType) - }; - } +function stringLiteralCompletionsForObjectLiteral(checker: TypeChecker, objectLiteralExpression: ObjectLiteralExpression): StringLiteralCompletionsFromProperties | undefined { + const contextualType = checker.getContextualType(objectLiteralExpression); + if (!contextualType) return undefined; + + const completionsType = checker.getContextualType(objectLiteralExpression, ContextFlags.Completions); + const symbols = getPropertiesForObjectExpression( + contextualType, + completionsType, + objectLiteralExpression, + checker + ); + + return { + kind: StringLiteralCompletionKind.Properties, + symbols, + hasIndexSignature: hasIndexSignature(contextualType) + }; +} - function getStringLiteralTypes(type: Type | undefined, uniques = new Map()): readonly StringLiteralType[] { - if (!type) return emptyArray; - type = skipConstraint(type); - return type.isUnion() ? flatMap(type.types, t => getStringLiteralTypes(t, uniques)) : - type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) ? [type] : emptyArray; - } +function getStringLiteralTypes(type: Type | undefined, uniques = new Map()): readonly StringLiteralType[] { + if (!type) return emptyArray; + type = skipConstraint(type); + return type.isUnion() ? flatMap(type.types, t => getStringLiteralTypes(t, uniques)) : + type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) ? [type] : emptyArray; +} - interface NameAndKind { - readonly name: string; - readonly kind: ScriptElementKind.scriptElement | ScriptElementKind.directory | ScriptElementKind.externalModuleName; - readonly extension: Extension | undefined; - } - interface PathCompletion extends NameAndKind { - readonly span: TextSpan | undefined; - } +interface NameAndKind { + readonly name: string; + readonly kind: ScriptElementKind.scriptElement | ScriptElementKind.directory | ScriptElementKind.externalModuleName; + readonly extension: Extension | undefined; +} +interface PathCompletion extends NameAndKind { + readonly span: TextSpan | undefined; +} - function nameAndKind(name: string, kind: NameAndKind["kind"], extension: Extension | undefined): NameAndKind { - return { name, kind, extension }; - } - function directoryResult(name: string): NameAndKind { - return nameAndKind(name, ScriptElementKind.directory, /*extension*/ undefined); - } +function nameAndKind(name: string, kind: NameAndKind["kind"], extension: Extension | undefined): NameAndKind { + return { name, kind, extension }; +} +function directoryResult(name: string): NameAndKind { + return nameAndKind(name, ScriptElementKind.directory, /*extension*/ undefined); +} - function addReplacementSpans(text: string, textStart: number, names: readonly NameAndKind[]): readonly PathCompletion[] { - const span = getDirectoryFragmentTextSpan(text, textStart); - const wholeSpan = text.length === 0 ? undefined : createTextSpan(textStart, text.length); - return names.map(({ name, kind, extension }): PathCompletion => - Math.max(name.indexOf(directorySeparator), name.indexOf(altDirectorySeparator)) !== -1 ? { name, kind, extension, span: wholeSpan } : { name, kind, extension, span }); - } +function addReplacementSpans(text: string, textStart: number, names: readonly NameAndKind[]): readonly PathCompletion[] { + const span = getDirectoryFragmentTextSpan(text, textStart); + const wholeSpan = text.length === 0 ? undefined : createTextSpan(textStart, text.length); + return names.map(({ name, kind, extension }): PathCompletion => + Math.max(name.indexOf(directorySeparator), name.indexOf(altDirectorySeparator)) !== -1 ? { name, kind, extension, span: wholeSpan } : { name, kind, extension, span }); +} - function getStringLiteralCompletionsFromModuleNames(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker, preferences: UserPreferences): readonly PathCompletion[] { - return addReplacementSpans(node.text, node.getStart(sourceFile) + 1, getStringLiteralCompletionsFromModuleNamesWorker(sourceFile, node, compilerOptions, host, typeChecker, preferences)); - } +function getStringLiteralCompletionsFromModuleNames(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker, preferences: UserPreferences): readonly PathCompletion[] { + return addReplacementSpans(node.text, node.getStart(sourceFile) + 1, getStringLiteralCompletionsFromModuleNamesWorker(sourceFile, node, compilerOptions, host, typeChecker, preferences)); +} - function getStringLiteralCompletionsFromModuleNamesWorker(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker, preferences: UserPreferences): readonly NameAndKind[] { - const literalValue = normalizeSlashes(node.text); - const mode = isStringLiteralLike(node) ? getModeForUsageLocation(sourceFile, node) : undefined; +function getStringLiteralCompletionsFromModuleNamesWorker(sourceFile: SourceFile, node: LiteralExpression, compilerOptions: CompilerOptions, host: LanguageServiceHost, typeChecker: TypeChecker, preferences: UserPreferences): readonly NameAndKind[] { + const literalValue = normalizeSlashes(node.text); + const mode = isStringLiteralLike(node) ? getModeForUsageLocation(sourceFile, node) : undefined; - const scriptPath = sourceFile.path; - const scriptDirectory = getDirectoryPath(scriptPath); + const scriptPath = sourceFile.path; + const scriptDirectory = getDirectoryPath(scriptPath); - return isPathRelativeToScript(literalValue) || !compilerOptions.baseUrl && (isRootedDiskPath(literalValue) || isUrl(literalValue)) - ? getCompletionEntriesForRelativeModules(literalValue, scriptDirectory, compilerOptions, host, scriptPath, getIncludeExtensionOption()) - : getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, mode, compilerOptions, host, getIncludeExtensionOption(), typeChecker); + return isPathRelativeToScript(literalValue) || !compilerOptions.baseUrl && (isRootedDiskPath(literalValue) || isUrl(literalValue)) + ? getCompletionEntriesForRelativeModules(literalValue, scriptDirectory, compilerOptions, host, scriptPath, getIncludeExtensionOption()) + : getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, mode, compilerOptions, host, getIncludeExtensionOption(), typeChecker); - function getIncludeExtensionOption() { - const mode = isStringLiteralLike(node) ? getModeForUsageLocation(sourceFile, node) : undefined; - return preferences.importModuleSpecifierEnding === "js" || mode === ModuleKind.ESNext ? IncludeExtensionsOption.ModuleSpecifierCompletion : IncludeExtensionsOption.Exclude; - } + function getIncludeExtensionOption() { + const mode = isStringLiteralLike(node) ? getModeForUsageLocation(sourceFile, node) : undefined; + return preferences.importModuleSpecifierEnding === "js" || mode === ModuleKind.ESNext ? IncludeExtensionsOption.ModuleSpecifierCompletion : IncludeExtensionsOption.Exclude; } +} - interface ExtensionOptions { - readonly extensions: readonly Extension[]; - readonly includeExtensionsOption: IncludeExtensionsOption; - } - function getExtensionOptions(compilerOptions: CompilerOptions, includeExtensionsOption = IncludeExtensionsOption.Exclude): ExtensionOptions { - return { extensions: flatten(getSupportedExtensionsForModuleResolution(compilerOptions)), includeExtensionsOption }; - } - function getCompletionEntriesForRelativeModules(literalValue: string, scriptDirectory: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, scriptPath: Path, includeExtensions: IncludeExtensionsOption) { - const extensionOptions = getExtensionOptions(compilerOptions, includeExtensions); - if (compilerOptions.rootDirs) { - return getCompletionEntriesForDirectoryFragmentWithRootDirs( - compilerOptions.rootDirs, literalValue, scriptDirectory, extensionOptions, compilerOptions, host, scriptPath); - } - else { - return arrayFrom(getCompletionEntriesForDirectoryFragment(literalValue, scriptDirectory, extensionOptions, host, scriptPath).values()); - } +interface ExtensionOptions { + readonly extensions: readonly Extension[]; + readonly includeExtensionsOption: IncludeExtensionsOption; +} +function getExtensionOptions(compilerOptions: CompilerOptions, includeExtensionsOption = IncludeExtensionsOption.Exclude): ExtensionOptions { + return { extensions: flatten(getSupportedExtensionsForModuleResolution(compilerOptions)), includeExtensionsOption }; +} +function getCompletionEntriesForRelativeModules(literalValue: string, scriptDirectory: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, scriptPath: Path, includeExtensions: IncludeExtensionsOption) { + const extensionOptions = getExtensionOptions(compilerOptions, includeExtensions); + if (compilerOptions.rootDirs) { + return getCompletionEntriesForDirectoryFragmentWithRootDirs( + compilerOptions.rootDirs, literalValue, scriptDirectory, extensionOptions, compilerOptions, host, scriptPath); } - - function isEmitResolutionKindUsingNodeModules(compilerOptions: CompilerOptions): boolean { - return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs || - getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || - getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext; + else { + return arrayFrom(getCompletionEntriesForDirectoryFragment(literalValue, scriptDirectory, extensionOptions, host, scriptPath).values()); } +} - function isEmitModuleResolutionRespectingExportMaps(compilerOptions: CompilerOptions) { - return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || +function isEmitResolutionKindUsingNodeModules(compilerOptions: CompilerOptions): boolean { + return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs || + getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext; - } +} - function getSupportedExtensionsForModuleResolution(compilerOptions: CompilerOptions): readonly Extension[][] { - const extensions = getSupportedExtensions(compilerOptions); - return isEmitResolutionKindUsingNodeModules(compilerOptions) ? - getSupportedExtensionsWithJsonIfResolveJsonModule(compilerOptions, extensions) : - extensions; - } +function isEmitModuleResolutionRespectingExportMaps(compilerOptions: CompilerOptions) { + return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || + getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext; +} - /** - * Takes a script path and returns paths for all potential folders that could be merged with its - * containing folder via the "rootDirs" compiler option - */ - function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptDirectory: string, ignoreCase: boolean): readonly string[] { - // Make all paths absolute/normalized if they are not already - rootDirs = rootDirs.map(rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory))); +function getSupportedExtensionsForModuleResolution(compilerOptions: CompilerOptions): readonly Extension[][] { + const extensions = getSupportedExtensions(compilerOptions); + return isEmitResolutionKindUsingNodeModules(compilerOptions) ? + getSupportedExtensionsWithJsonIfResolveJsonModule(compilerOptions, extensions) : + extensions; +} - // Determine the path to the directory containing the script relative to the root directory it is contained within - const relativeDirectory = firstDefined(rootDirs, rootDirectory => - containsPath(rootDirectory, scriptDirectory, basePath, ignoreCase) ? scriptDirectory.substr(rootDirectory.length) : undefined)!; // TODO: GH#18217 +/** + * Takes a script path and returns paths for all potential folders that could be merged with its + * containing folder via the "rootDirs" compiler option + */ +function getBaseDirectoriesFromRootDirs(rootDirs: string[], basePath: string, scriptDirectory: string, ignoreCase: boolean): readonly string[] { + // Make all paths absolute/normalized if they are not already + rootDirs = rootDirs.map(rootDirectory => normalizePath(isRootedDiskPath(rootDirectory) ? rootDirectory : combinePaths(basePath, rootDirectory))); + + // Determine the path to the directory containing the script relative to the root directory it is contained within + const relativeDirectory = firstDefined(rootDirs, rootDirectory => + containsPath(rootDirectory, scriptDirectory, basePath, ignoreCase) ? scriptDirectory.substr(rootDirectory.length) : undefined)!; // TODO: GH#18217 + + // Now find a path for each potential directory that is to be merged with the one containing the script + return deduplicate( + [...rootDirs.map(rootDirectory => combinePaths(rootDirectory, relativeDirectory)), scriptDirectory], + equateStringsCaseSensitive, + compareStringsCaseSensitive); +} - // Now find a path for each potential directory that is to be merged with the one containing the script - return deduplicate( - [...rootDirs.map(rootDirectory => combinePaths(rootDirectory, relativeDirectory)), scriptDirectory], - equateStringsCaseSensitive, - compareStringsCaseSensitive); - } +function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptDirectory: string, extensionOptions: ExtensionOptions, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude: string): readonly NameAndKind[] { + const basePath = compilerOptions.project || host.getCurrentDirectory(); + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); + const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptDirectory, ignoreCase); + return flatMap(baseDirectories, baseDirectory => arrayFrom(getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensionOptions, host, exclude).values())); +} - function getCompletionEntriesForDirectoryFragmentWithRootDirs(rootDirs: string[], fragment: string, scriptDirectory: string, extensionOptions: ExtensionOptions, compilerOptions: CompilerOptions, host: LanguageServiceHost, exclude: string): readonly NameAndKind[] { - const basePath = compilerOptions.project || host.getCurrentDirectory(); - const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); - const baseDirectories = getBaseDirectoriesFromRootDirs(rootDirs, basePath, scriptDirectory, ignoreCase); - return flatMap(baseDirectories, baseDirectory => arrayFrom(getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensionOptions, host, exclude).values())); +const enum IncludeExtensionsOption { + Exclude, + Include, + ModuleSpecifierCompletion, +} +/** + * Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename. + */ +function getCompletionEntriesForDirectoryFragment( + fragment: string, + scriptPath: string, + extensionOptions: ExtensionOptions, + host: LanguageServiceHost, + exclude?: string, + result = createNameAndKindSet() +): NameAndKindSet { + if (fragment === undefined) { + fragment = ""; } - const enum IncludeExtensionsOption { - Exclude, - Include, - ModuleSpecifierCompletion, - } + fragment = normalizeSlashes(fragment); + /** - * Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename. + * Remove the basename from the path. Note that we don't use the basename to filter completions; + * the client is responsible for refining completions. */ - function getCompletionEntriesForDirectoryFragment( - fragment: string, - scriptPath: string, - extensionOptions: ExtensionOptions, - host: LanguageServiceHost, - exclude?: string, - result = createNameAndKindSet() - ): NameAndKindSet { - if (fragment === undefined) { - fragment = ""; - } - - fragment = normalizeSlashes(fragment); - - /** - * Remove the basename from the path. Note that we don't use the basename to filter completions; - * the client is responsible for refining completions. - */ - if (!hasTrailingDirectorySeparator(fragment)) { - fragment = getDirectoryPath(fragment); - } + if (!hasTrailingDirectorySeparator(fragment)) { + fragment = getDirectoryPath(fragment); + } - if (fragment === "") { - fragment = "." + directorySeparator; - } + if (fragment === "") { + fragment = "." + directorySeparator; + } - fragment = ensureTrailingDirectorySeparator(fragment); - - const absolutePath = resolvePath(scriptPath, fragment); - const baseDirectory = hasTrailingDirectorySeparator(absolutePath) ? absolutePath : getDirectoryPath(absolutePath); - - // check for a version redirect - const packageJsonPath = findPackageJson(baseDirectory, host); - if (packageJsonPath) { - const packageJson = readJson(packageJsonPath, host as { readFile: (filename: string) => string | undefined }); - const typesVersions = (packageJson as any).typesVersions; - if (typeof typesVersions === "object") { - const versionPaths = getPackageJsonTypesVersionsPaths(typesVersions)?.paths; - if (versionPaths) { - const packageDirectory = getDirectoryPath(packageJsonPath); - const pathInPackage = absolutePath.slice(ensureTrailingDirectorySeparator(packageDirectory).length); - if (addCompletionEntriesFromPaths(result, pathInPackage, packageDirectory, extensionOptions, host, versionPaths)) { - // A true result means one of the `versionPaths` was matched, which will block relative resolution - // to files and folders from here. All reachable paths given the pattern match are already added. - return result; - } + fragment = ensureTrailingDirectorySeparator(fragment); + + const absolutePath = resolvePath(scriptPath, fragment); + const baseDirectory = hasTrailingDirectorySeparator(absolutePath) ? absolutePath : getDirectoryPath(absolutePath); + + // check for a version redirect + const packageJsonPath = findPackageJson(baseDirectory, host); + if (packageJsonPath) { + const packageJson = readJson(packageJsonPath, host as { readFile: (filename: string) => string | undefined }); + const typesVersions = (packageJson as any).typesVersions; + if (typeof typesVersions === "object") { + const versionPaths = getPackageJsonTypesVersionsPaths(typesVersions)?.paths; + if (versionPaths) { + const packageDirectory = getDirectoryPath(packageJsonPath); + const pathInPackage = absolutePath.slice(ensureTrailingDirectorySeparator(packageDirectory).length); + if (addCompletionEntriesFromPaths(result, pathInPackage, packageDirectory, extensionOptions, host, versionPaths)) { + // A true result means one of the `versionPaths` was matched, which will block relative resolution + // to files and folders from here. All reachable paths given the pattern match are already added. + return result; } } } + } - const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); - if (!tryDirectoryExists(host, baseDirectory)) return result; - - // Enumerate the available files if possible - const files = tryReadDirectory(host, baseDirectory, extensionOptions.extensions, /*exclude*/ undefined, /*include*/ ["./*"]); + const ignoreCase = !(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames()); + if (!tryDirectoryExists(host, baseDirectory)) return result; - if (files) { - for (let filePath of files) { - filePath = normalizePath(filePath); - if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { - continue; - } + // Enumerate the available files if possible + const files = tryReadDirectory(host, baseDirectory, extensionOptions.extensions, /*exclude*/ undefined, /*include*/ ["./*"]); - const { name, extension } = getFilenameWithExtensionOption(getBaseFileName(filePath), host.getCompilationSettings(), extensionOptions.includeExtensionsOption); - result.add(nameAndKind(name, ScriptElementKind.scriptElement, extension)); + if (files) { + for (let filePath of files) { + filePath = normalizePath(filePath); + if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { + continue; } + + const { name, extension } = getFilenameWithExtensionOption(getBaseFileName(filePath), host.getCompilationSettings(), extensionOptions.includeExtensionsOption); + result.add(nameAndKind(name, ScriptElementKind.scriptElement, extension)); } + } - // If possible, get folder completion as well - const directories = tryGetDirectories(host, baseDirectory); + // If possible, get folder completion as well + const directories = tryGetDirectories(host, baseDirectory); - if (directories) { - for (const directory of directories) { - const directoryName = getBaseFileName(normalizePath(directory)); - if (directoryName !== "@types") { - result.add(directoryResult(directoryName)); - } + if (directories) { + for (const directory of directories) { + const directoryName = getBaseFileName(normalizePath(directory)); + if (directoryName !== "@types") { + result.add(directoryResult(directoryName)); } } - - return result; } - function getFilenameWithExtensionOption(name: string, compilerOptions: CompilerOptions, includeExtensionsOption: IncludeExtensionsOption): { name: string, extension: Extension | undefined } { - const outputExtension = moduleSpecifiers.tryGetJSExtensionForFile(name, compilerOptions); - if (includeExtensionsOption === IncludeExtensionsOption.Exclude && !fileExtensionIsOneOf(name, [Extension.Json, Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs])) { - return { name: removeFileExtension(name), extension: tryGetExtensionFromPath(name) }; - } - else if ((fileExtensionIsOneOf(name, [Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs]) || includeExtensionsOption === IncludeExtensionsOption.ModuleSpecifierCompletion) && outputExtension) { - return { name: changeExtension(name, outputExtension), extension: outputExtension }; - } - else { - return { name, extension: tryGetExtensionFromPath(name) }; - } + return result; +} + +function getFilenameWithExtensionOption(name: string, compilerOptions: CompilerOptions, includeExtensionsOption: IncludeExtensionsOption): { name: string, extension: Extension | undefined } { + const outputExtension = moduleSpecifiers.tryGetJSExtensionForFile(name, compilerOptions); + if (includeExtensionsOption === IncludeExtensionsOption.Exclude && !fileExtensionIsOneOf(name, [Extension.Json, Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs])) { + return { name: removeFileExtension(name), extension: tryGetExtensionFromPath(name) }; + } + else if ((fileExtensionIsOneOf(name, [Extension.Mts, Extension.Cts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Cjs]) || includeExtensionsOption === IncludeExtensionsOption.ModuleSpecifierCompletion) && outputExtension) { + return { name: changeExtension(name, outputExtension), extension: outputExtension }; } + else { + return { name, extension: tryGetExtensionFromPath(name) }; + } +} - /** @returns whether `fragment` was a match for any `paths` (which should indicate whether any other path completions should be offered) */ - function addCompletionEntriesFromPaths( - result: NameAndKindSet, - fragment: string, - baseDirectory: string, - extensionOptions: ExtensionOptions, - host: LanguageServiceHost, - paths: MapLike - ) { - const getPatternsForKey = (key: string) => paths[key]; - const comparePaths = (a: string, b: string): Comparison => { - const patternA = tryParsePattern(a); - const patternB = tryParsePattern(b); - const lengthA = typeof patternA === "object" ? patternA.prefix.length : a.length; - const lengthB = typeof patternB === "object" ? patternB.prefix.length : b.length; - return compareValues(lengthB, lengthA); - }; - return addCompletionEntriesFromPathsOrExports(result, fragment, baseDirectory, extensionOptions, host, getOwnKeys(paths), getPatternsForKey, comparePaths); - } - - /** @returns whether `fragment` was a match for any `paths` (which should indicate whether any other path completions should be offered) */ - function addCompletionEntriesFromPathsOrExports( - result: NameAndKindSet, - fragment: string, - baseDirectory: string, - extensionOptions: ExtensionOptions, - host: LanguageServiceHost, - keys: readonly string[], - getPatternsForKey: (key: string) => string[] | undefined, - comparePaths: (a: string, b: string) => Comparison, - ) { - let pathResults: { results: NameAndKind[], matchedPattern: boolean }[] = []; - let matchedPath: string | undefined; - for (const key of keys) { - if (key === ".") continue; - const keyWithoutLeadingDotSlash = key.replace(/^\.\//, ""); // remove leading "./" - const patterns = getPatternsForKey(key); - if (patterns) { - const pathPattern = tryParsePattern(keyWithoutLeadingDotSlash); - if (!pathPattern) continue; - const isMatch = typeof pathPattern === "object" && isPatternMatch(pathPattern, fragment); - const isLongestMatch = isMatch && (matchedPath === undefined || comparePaths(key, matchedPath) === Comparison.LessThan); - if (isLongestMatch) { - // If this is a higher priority match than anything we've seen so far, previous results from matches are invalid, e.g. - // for `import {} from "some-package/|"` with a typesVersions: - // { - // "bar/*": ["bar/*"], // <-- 1. We add 'bar', but 'bar/*' doesn't match yet. - // "*": ["dist/*"], // <-- 2. We match here and add files from dist. 'bar' is still ok because it didn't come from a match. - // "foo/*": ["foo/*"] // <-- 3. We matched '*' earlier and added results from dist, but if 'foo/*' also matched, - // } results in dist would not be visible. 'bar' still stands because it didn't come from a match. - // This is especially important if `dist/foo` is a folder, because if we fail to clear results - // added by the '*' match, after typing `"some-package/foo/|"` we would get file results from both - // ./dist/foo and ./foo, when only the latter will actually be resolvable. - // See pathCompletionsTypesVersionsWildcard6.ts. - matchedPath = key; - pathResults = pathResults.filter(r => !r.matchedPattern); - } - if (typeof pathPattern === "string" || matchedPath === undefined || comparePaths(key, matchedPath) !== Comparison.GreaterThan) { - pathResults.push({ - matchedPattern: isMatch, - results: getCompletionsForPathMapping(keyWithoutLeadingDotSlash, patterns, fragment, baseDirectory, extensionOptions, host) - .map(({ name, kind, extension }) => nameAndKind(name, kind, extension)), - }); - } +/** @returns whether `fragment` was a match for any `paths` (which should indicate whether any other path completions should be offered) */ +function addCompletionEntriesFromPaths( + result: NameAndKindSet, + fragment: string, + baseDirectory: string, + extensionOptions: ExtensionOptions, + host: LanguageServiceHost, + paths: MapLike +) { + const getPatternsForKey = (key: string) => paths[key]; + const comparePaths = (a: string, b: string): Comparison => { + const patternA = tryParsePattern(a); + const patternB = tryParsePattern(b); + const lengthA = typeof patternA === "object" ? patternA.prefix.length : a.length; + const lengthB = typeof patternB === "object" ? patternB.prefix.length : b.length; + return compareValues(lengthB, lengthA); + }; + return addCompletionEntriesFromPathsOrExports(result, fragment, baseDirectory, extensionOptions, host, getOwnKeys(paths), getPatternsForKey, comparePaths); +} + +/** @returns whether `fragment` was a match for any `paths` (which should indicate whether any other path completions should be offered) */ +function addCompletionEntriesFromPathsOrExports( + result: NameAndKindSet, + fragment: string, + baseDirectory: string, + extensionOptions: ExtensionOptions, + host: LanguageServiceHost, + keys: readonly string[], + getPatternsForKey: (key: string) => string[] | undefined, + comparePaths: (a: string, b: string) => Comparison, +) { + let pathResults: { results: NameAndKind[], matchedPattern: boolean }[] = []; + let matchedPath: string | undefined; + for (const key of keys) { + if (key === ".") continue; + const keyWithoutLeadingDotSlash = key.replace(/^\.\//, ""); // remove leading "./" + const patterns = getPatternsForKey(key); + if (patterns) { + const pathPattern = tryParsePattern(keyWithoutLeadingDotSlash); + if (!pathPattern) continue; + const isMatch = typeof pathPattern === "object" && isPatternMatch(pathPattern, fragment); + const isLongestMatch = isMatch && (matchedPath === undefined || comparePaths(key, matchedPath) === Comparison.LessThan); + if (isLongestMatch) { + // If this is a higher priority match than anything we've seen so far, previous results from matches are invalid, e.g. + // for `import {} from "some-package/|"` with a typesVersions: + // { + // "bar/*": ["bar/*"], // <-- 1. We add 'bar', but 'bar/*' doesn't match yet. + // "*": ["dist/*"], // <-- 2. We match here and add files from dist. 'bar' is still ok because it didn't come from a match. + // "foo/*": ["foo/*"] // <-- 3. We matched '*' earlier and added results from dist, but if 'foo/*' also matched, + // } results in dist would not be visible. 'bar' still stands because it didn't come from a match. + // This is especially important if `dist/foo` is a folder, because if we fail to clear results + // added by the '*' match, after typing `"some-package/foo/|"` we would get file results from both + // ./dist/foo and ./foo, when only the latter will actually be resolvable. + // See pathCompletionsTypesVersionsWildcard6.ts. + matchedPath = key; + pathResults = pathResults.filter(r => !r.matchedPattern); + } + if (typeof pathPattern === "string" || matchedPath === undefined || comparePaths(key, matchedPath) !== Comparison.GreaterThan) { + pathResults.push({ + matchedPattern: isMatch, + results: getCompletionsForPathMapping(keyWithoutLeadingDotSlash, patterns, fragment, baseDirectory, extensionOptions, host) + .map(({ name, kind, extension }) => nameAndKind(name, kind, extension)), + }); } } - - pathResults.forEach(pathResult => pathResult.results.forEach(r => result.add(r))); - return matchedPath !== undefined; } - /** - * Check all of the declared modules and those in node modules. Possible sources of modules: - * Modules that are found by the type checker - * Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option) - * Modules from node_modules (i.e. those listed in package.json) - * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions - */ - function getCompletionEntriesForNonRelativeModules( - fragment: string, - scriptPath: string, - mode: SourceFile["impliedNodeFormat"], - compilerOptions: CompilerOptions, - host: LanguageServiceHost, - includeExtensionsOption: IncludeExtensionsOption, - typeChecker: TypeChecker, - ): readonly NameAndKind[] { - const { baseUrl, paths } = compilerOptions; - - const result = createNameAndKindSet(); - const extensionOptions = getExtensionOptions(compilerOptions, includeExtensionsOption); - if (baseUrl) { - const projectDir = compilerOptions.project || host.getCurrentDirectory(); - const absolute = normalizePath(combinePaths(projectDir, baseUrl)); - getCompletionEntriesForDirectoryFragment(fragment, absolute, extensionOptions, host, /*exclude*/ undefined, result); - if (paths) { - addCompletionEntriesFromPaths(result, fragment, absolute, extensionOptions, host, paths); - } - } + pathResults.forEach(pathResult => pathResult.results.forEach(r => result.add(r))); + return matchedPath !== undefined; +} - const fragmentDirectory = getFragmentDirectory(fragment); - for (const ambientName of getAmbientModuleCompletions(fragment, fragmentDirectory, typeChecker)) { - result.add(nameAndKind(ambientName, ScriptElementKind.externalModuleName, /*extension*/ undefined)); +/** + * Check all of the declared modules and those in node modules. Possible sources of modules: + * Modules that are found by the type checker + * Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option) + * Modules from node_modules (i.e. those listed in package.json) + * This includes all files that are found in node_modules/moduleName/ with acceptable file extensions + */ +function getCompletionEntriesForNonRelativeModules( + fragment: string, + scriptPath: string, + mode: SourceFile["impliedNodeFormat"], + compilerOptions: CompilerOptions, + host: LanguageServiceHost, + includeExtensionsOption: IncludeExtensionsOption, + typeChecker: TypeChecker, +): readonly NameAndKind[] { + const { baseUrl, paths } = compilerOptions; + + const result = createNameAndKindSet(); + const extensionOptions = getExtensionOptions(compilerOptions, includeExtensionsOption); + if (baseUrl) { + const projectDir = compilerOptions.project || host.getCurrentDirectory(); + const absolute = normalizePath(combinePaths(projectDir, baseUrl)); + getCompletionEntriesForDirectoryFragment(fragment, absolute, extensionOptions, host, /*exclude*/ undefined, result); + if (paths) { + addCompletionEntriesFromPaths(result, fragment, absolute, extensionOptions, host, paths); } + } - getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, fragmentDirectory, extensionOptions, result); + const fragmentDirectory = getFragmentDirectory(fragment); + for (const ambientName of getAmbientModuleCompletions(fragment, fragmentDirectory, typeChecker)) { + result.add(nameAndKind(ambientName, ScriptElementKind.externalModuleName, /*extension*/ undefined)); + } - if (isEmitResolutionKindUsingNodeModules(compilerOptions)) { - // If looking for a global package name, don't just include everything in `node_modules` because that includes dependencies' own dependencies. - // (But do if we didn't find anything, e.g. 'package.json' missing.) - let foundGlobal = false; - if (fragmentDirectory === undefined) { - for (const moduleName of enumerateNodeModulesVisibleToScript(host, scriptPath)) { - const moduleResult = nameAndKind(moduleName, ScriptElementKind.externalModuleName, /*extension*/ undefined); - if (!result.has(moduleResult.name)) { - foundGlobal = true; - result.add(moduleResult); - } + getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, fragmentDirectory, extensionOptions, result); + + if (isEmitResolutionKindUsingNodeModules(compilerOptions)) { + // If looking for a global package name, don't just include everything in `node_modules` because that includes dependencies' own dependencies. + // (But do if we didn't find anything, e.g. 'package.json' missing.) + let foundGlobal = false; + if (fragmentDirectory === undefined) { + for (const moduleName of enumerateNodeModulesVisibleToScript(host, scriptPath)) { + const moduleResult = nameAndKind(moduleName, ScriptElementKind.externalModuleName, /*extension*/ undefined); + if (!result.has(moduleResult.name)) { + foundGlobal = true; + result.add(moduleResult); } } - if (!foundGlobal) { - let ancestorLookup: (directory: string) => void | undefined = ancestor => { - const nodeModules = combinePaths(ancestor, "node_modules"); - if (tryDirectoryExists(host, nodeModules)) { - getCompletionEntriesForDirectoryFragment(fragment, nodeModules, extensionOptions, host, /*exclude*/ undefined, result); + } + if (!foundGlobal) { + let ancestorLookup: (directory: string) => void | undefined = ancestor => { + const nodeModules = combinePaths(ancestor, "node_modules"); + if (tryDirectoryExists(host, nodeModules)) { + getCompletionEntriesForDirectoryFragment(fragment, nodeModules, extensionOptions, host, /*exclude*/ undefined, result); + } + }; + if (fragmentDirectory && isEmitModuleResolutionRespectingExportMaps(compilerOptions)) { + const nodeModulesDirectoryLookup = ancestorLookup; + ancestorLookup = ancestor => { + const components = getPathComponents(fragment); + components.shift(); // shift off empty root + let packagePath = components.shift(); + if (!packagePath) { + return nodeModulesDirectoryLookup(ancestor); } - }; - if (fragmentDirectory && isEmitModuleResolutionRespectingExportMaps(compilerOptions)) { - const nodeModulesDirectoryLookup = ancestorLookup; - ancestorLookup = ancestor => { - const components = getPathComponents(fragment); - components.shift(); // shift off empty root - let packagePath = components.shift(); - if (!packagePath) { + if (startsWith(packagePath, "@")) { + const subName = components.shift(); + if (!subName) { return nodeModulesDirectoryLookup(ancestor); } - if (startsWith(packagePath, "@")) { - const subName = components.shift(); - if (!subName) { - return nodeModulesDirectoryLookup(ancestor); - } - packagePath = combinePaths(packagePath, subName); - } - const packageDirectory = combinePaths(ancestor, "node_modules", packagePath); - const packageFile = combinePaths(packageDirectory, "package.json"); - if (tryFileExists(host, packageFile)) { - const packageJson = readJson(packageFile, host); - const exports = (packageJson as any).exports; - if (exports) { - if (typeof exports !== "object" || exports === null) { // eslint-disable-line no-null/no-null - return; // null exports or entrypoint only, no sub-modules available - } - const keys = getOwnKeys(exports); - const fragmentSubpath = components.join("/") + (components.length && hasTrailingDirectorySeparator(fragment) ? "/" : ""); - const conditions = mode === ModuleKind.ESNext ? ["node", "import", "types"] : ["node", "require", "types"]; - addCompletionEntriesFromPathsOrExports( - result, - fragmentSubpath, - packageDirectory, - extensionOptions, - host, - keys, - key => singleElementArray(getPatternFromFirstMatchingCondition(exports[key], conditions)), - comparePatternKeys); - return; + packagePath = combinePaths(packagePath, subName); + } + const packageDirectory = combinePaths(ancestor, "node_modules", packagePath); + const packageFile = combinePaths(packageDirectory, "package.json"); + if (tryFileExists(host, packageFile)) { + const packageJson = readJson(packageFile, host); + const exports = (packageJson as any).exports; + if (exports) { + if (typeof exports !== "object" || exports === null) { // eslint-disable-line no-null/no-null + return; // null exports or entrypoint only, no sub-modules available } + const keys = getOwnKeys(exports); + const fragmentSubpath = components.join("/") + (components.length && hasTrailingDirectorySeparator(fragment) ? "/" : ""); + const conditions = mode === ModuleKind.ESNext ? ["node", "import", "types"] : ["node", "require", "types"]; + addCompletionEntriesFromPathsOrExports( + result, + fragmentSubpath, + packageDirectory, + extensionOptions, + host, + keys, + key => singleElementArray(getPatternFromFirstMatchingCondition(exports[key], conditions)), + comparePatternKeys); + return; } - return nodeModulesDirectoryLookup(ancestor); - }; - } - forEachAncestorDirectory(scriptPath, ancestorLookup); + } + return nodeModulesDirectoryLookup(ancestor); + }; } + forEachAncestorDirectory(scriptPath, ancestorLookup); } - - return arrayFrom(result.values()); } - function getPatternFromFirstMatchingCondition(target: unknown, conditions: readonly string[]): string | undefined { - if (typeof target === "string") { - return target; - } - if (target && typeof target === "object" && !isArray(target)) { - for (const condition in target) { - if (condition === "default" || conditions.indexOf(condition) > -1 || isApplicableVersionedTypesKey(conditions, condition)) { - const pattern = (target as MapLike)[condition]; - return getPatternFromFirstMatchingCondition(pattern, conditions); - } + return arrayFrom(result.values()); +} + +function getPatternFromFirstMatchingCondition(target: unknown, conditions: readonly string[]): string | undefined { + if (typeof target === "string") { + return target; + } + if (target && typeof target === "object" && !isArray(target)) { + for (const condition in target) { + if (condition === "default" || conditions.indexOf(condition) > -1 || isApplicableVersionedTypesKey(conditions, condition)) { + const pattern = (target as MapLike)[condition]; + return getPatternFromFirstMatchingCondition(pattern, conditions); } } } +} - function getFragmentDirectory(fragment: string): string | undefined { - return containsSlash(fragment) ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined; - } +function getFragmentDirectory(fragment: string): string | undefined { + return containsSlash(fragment) ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined; +} - function getCompletionsForPathMapping( - path: string, - patterns: readonly string[], - fragment: string, - packageDirectory: string, - extensionOptions: ExtensionOptions, - host: LanguageServiceHost, - ): readonly NameAndKind[] { - if (!endsWith(path, "*")) { - // For a path mapping "foo": ["/x/y/z.ts"], add "foo" itself as a completion. - return !stringContains(path, "*") ? justPathMappingName(path, ScriptElementKind.scriptElement) : emptyArray; - } +function getCompletionsForPathMapping( + path: string, + patterns: readonly string[], + fragment: string, + packageDirectory: string, + extensionOptions: ExtensionOptions, + host: LanguageServiceHost, +): readonly NameAndKind[] { + if (!endsWith(path, "*")) { + // For a path mapping "foo": ["/x/y/z.ts"], add "foo" itself as a completion. + return !stringContains(path, "*") ? justPathMappingName(path, ScriptElementKind.scriptElement) : emptyArray; + } - const pathPrefix = path.slice(0, path.length - 1); - const remainingFragment = tryRemovePrefix(fragment, pathPrefix); - if (remainingFragment === undefined) { - const starIsFullPathComponent = path[path.length - 2] === "/"; - return starIsFullPathComponent ? justPathMappingName(pathPrefix, ScriptElementKind.directory) : flatMap(patterns, pattern => - getModulesForPathsPattern("", packageDirectory, pattern, extensionOptions, host)?.map(({ name, ...rest }) => ({ name: pathPrefix + name, ...rest }))); - } - return flatMap(patterns, pattern => getModulesForPathsPattern(remainingFragment, packageDirectory, pattern, extensionOptions, host)); + const pathPrefix = path.slice(0, path.length - 1); + const remainingFragment = tryRemovePrefix(fragment, pathPrefix); + if (remainingFragment === undefined) { + const starIsFullPathComponent = path[path.length - 2] === "/"; + return starIsFullPathComponent ? justPathMappingName(pathPrefix, ScriptElementKind.directory) : flatMap(patterns, pattern => + getModulesForPathsPattern("", packageDirectory, pattern, extensionOptions, host)?.map(({ name, ...rest }) => ({ name: pathPrefix + name, ...rest }))); + } + return flatMap(patterns, pattern => getModulesForPathsPattern(remainingFragment, packageDirectory, pattern, extensionOptions, host)); - function justPathMappingName(name: string, kind: ScriptElementKind.directory | ScriptElementKind.scriptElement): readonly NameAndKind[] { - return startsWith(name, fragment) ? [{ name: removeTrailingDirectorySeparator(name), kind, extension: undefined }] : emptyArray; - } + function justPathMappingName(name: string, kind: ScriptElementKind.directory | ScriptElementKind.scriptElement): readonly NameAndKind[] { + return startsWith(name, fragment) ? [{ name: removeTrailingDirectorySeparator(name), kind, extension: undefined }] : emptyArray; } +} - function getModulesForPathsPattern( - fragment: string, - packageDirectory: string, - pattern: string, - extensionOptions: ExtensionOptions, - host: LanguageServiceHost, - ): readonly NameAndKind[] | undefined { - if (!host.readDirectory) { - return undefined; - } +function getModulesForPathsPattern( + fragment: string, + packageDirectory: string, + pattern: string, + extensionOptions: ExtensionOptions, + host: LanguageServiceHost, +): readonly NameAndKind[] | undefined { + if (!host.readDirectory) { + return undefined; + } - const parsed = tryParsePattern(pattern); - if (parsed === undefined || isString(parsed)) { - return undefined; - } + const parsed = tryParsePattern(pattern); + if (parsed === undefined || isString(parsed)) { + return undefined; + } - // The prefix has two effective parts: the directory path and the base component after the filepath that is not a - // full directory component. For example: directory/path/of/prefix/base* - const normalizedPrefix = resolvePath(parsed.prefix); - const normalizedPrefixDirectory = hasTrailingDirectorySeparator(parsed.prefix) ? normalizedPrefix : getDirectoryPath(normalizedPrefix); - const normalizedPrefixBase = hasTrailingDirectorySeparator(parsed.prefix) ? "" : getBaseFileName(normalizedPrefix); - - const fragmentHasPath = containsSlash(fragment); - const fragmentDirectory = fragmentHasPath ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined; - - // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call - const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + fragmentDirectory) : normalizedPrefixDirectory; - - const normalizedSuffix = normalizePath(parsed.suffix); - // Need to normalize after combining: If we combinePaths("a", "../b"), we want "b" and not "a/../b". - const baseDirectory = normalizePath(combinePaths(packageDirectory, expandedPrefixDirectory)); - const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; - - // If we have a suffix, then we read the directory all the way down to avoid returning completions for - // directories that don't contain files that would match the suffix. A previous comment here was concerned - // about the case where `normalizedSuffix` includes a `?` character, which should be interpreted literally, - // but will match any single character as part of the `include` pattern in `tryReadDirectory`. This is not - // a problem, because (in the extremely unusual circumstance where the suffix has a `?` in it) a `?` - // interpreted as "any character" can only return *too many* results as compared to the literal - // interpretation, so we can filter those superfluous results out via `trimPrefixAndSuffix` as we've always - // done. - const includeGlob = normalizedSuffix ? "**/*" + normalizedSuffix : "./*"; - - const matches = mapDefined(tryReadDirectory(host, baseDirectory, extensionOptions.extensions, /*exclude*/ undefined, [includeGlob]), match => { - const trimmedWithPattern = trimPrefixAndSuffix(match); - if (trimmedWithPattern) { - if (containsSlash(trimmedWithPattern)) { - return directoryResult(getPathComponents(removeLeadingDirectorySeparator(trimmedWithPattern))[1]); - } - const { name, extension } = getFilenameWithExtensionOption(trimmedWithPattern, host.getCompilationSettings(), extensionOptions.includeExtensionsOption); - return nameAndKind(name, ScriptElementKind.scriptElement, extension); + // The prefix has two effective parts: the directory path and the base component after the filepath that is not a + // full directory component. For example: directory/path/of/prefix/base* + const normalizedPrefix = resolvePath(parsed.prefix); + const normalizedPrefixDirectory = hasTrailingDirectorySeparator(parsed.prefix) ? normalizedPrefix : getDirectoryPath(normalizedPrefix); + const normalizedPrefixBase = hasTrailingDirectorySeparator(parsed.prefix) ? "" : getBaseFileName(normalizedPrefix); + + const fragmentHasPath = containsSlash(fragment); + const fragmentDirectory = fragmentHasPath ? hasTrailingDirectorySeparator(fragment) ? fragment : getDirectoryPath(fragment) : undefined; + + // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call + const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + fragmentDirectory) : normalizedPrefixDirectory; + + const normalizedSuffix = normalizePath(parsed.suffix); + // Need to normalize after combining: If we combinePaths("a", "../b"), we want "b" and not "a/../b". + const baseDirectory = normalizePath(combinePaths(packageDirectory, expandedPrefixDirectory)); + const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase; + + // If we have a suffix, then we read the directory all the way down to avoid returning completions for + // directories that don't contain files that would match the suffix. A previous comment here was concerned + // about the case where `normalizedSuffix` includes a `?` character, which should be interpreted literally, + // but will match any single character as part of the `include` pattern in `tryReadDirectory`. This is not + // a problem, because (in the extremely unusual circumstance where the suffix has a `?` in it) a `?` + // interpreted as "any character" can only return *too many* results as compared to the literal + // interpretation, so we can filter those superfluous results out via `trimPrefixAndSuffix` as we've always + // done. + const includeGlob = normalizedSuffix ? "**/*" + normalizedSuffix : "./*"; + + const matches = mapDefined(tryReadDirectory(host, baseDirectory, extensionOptions.extensions, /*exclude*/ undefined, [includeGlob]), match => { + const trimmedWithPattern = trimPrefixAndSuffix(match); + if (trimmedWithPattern) { + if (containsSlash(trimmedWithPattern)) { + return directoryResult(getPathComponents(removeLeadingDirectorySeparator(trimmedWithPattern))[1]); } - }); - - // If we had a suffix, we already recursively searched for all possible files that could match - // it and returned the directories leading to those files. Otherwise, assume any directory could - // have something valid to import. - const directories = normalizedSuffix - ? emptyArray - : mapDefined(tryGetDirectories(host, baseDirectory), dir => dir === "node_modules" ? undefined : directoryResult(dir)); - return [...matches, ...directories]; - - function trimPrefixAndSuffix(path: string): string | undefined { - const inner = withoutStartAndEnd(normalizePath(path), completePrefix, normalizedSuffix); - return inner === undefined ? undefined : removeLeadingDirectorySeparator(inner); + const { name, extension } = getFilenameWithExtensionOption(trimmedWithPattern, host.getCompilationSettings(), extensionOptions.includeExtensionsOption); + return nameAndKind(name, ScriptElementKind.scriptElement, extension); } + }); + + // If we had a suffix, we already recursively searched for all possible files that could match + // it and returned the directories leading to those files. Otherwise, assume any directory could + // have something valid to import. + const directories = normalizedSuffix + ? emptyArray + : mapDefined(tryGetDirectories(host, baseDirectory), dir => dir === "node_modules" ? undefined : directoryResult(dir)); + return [...matches, ...directories]; + + function trimPrefixAndSuffix(path: string): string | undefined { + const inner = withoutStartAndEnd(normalizePath(path), completePrefix, normalizedSuffix); + return inner === undefined ? undefined : removeLeadingDirectorySeparator(inner); } +} - function withoutStartAndEnd(s: string, start: string, end: string): string | undefined { - return startsWith(s, start) && endsWith(s, end) ? s.slice(start.length, s.length - end.length) : undefined; - } - - function removeLeadingDirectorySeparator(path: string): string { - return path[0] === directorySeparator ? path.slice(1) : path; - } +function withoutStartAndEnd(s: string, start: string, end: string): string | undefined { + return startsWith(s, start) && endsWith(s, end) ? s.slice(start.length, s.length - end.length) : undefined; +} - function getAmbientModuleCompletions(fragment: string, fragmentDirectory: string | undefined, checker: TypeChecker): readonly string[] { - // Get modules that the type checker picked up - const ambientModules = checker.getAmbientModules().map(sym => stripQuotes(sym.name)); - const nonRelativeModuleNames = ambientModules.filter(moduleName => startsWith(moduleName, fragment)); +function removeLeadingDirectorySeparator(path: string): string { + return path[0] === directorySeparator ? path.slice(1) : path; +} - // Nested modules of the form "module-name/sub" need to be adjusted to only return the string - // after the last '/' that appears in the fragment because that's where the replacement span - // starts - if (fragmentDirectory !== undefined) { - const moduleNameWithSeparator = ensureTrailingDirectorySeparator(fragmentDirectory); - return nonRelativeModuleNames.map(nonRelativeModuleName => removePrefix(nonRelativeModuleName, moduleNameWithSeparator)); - } - return nonRelativeModuleNames; +function getAmbientModuleCompletions(fragment: string, fragmentDirectory: string | undefined, checker: TypeChecker): readonly string[] { + // Get modules that the type checker picked up + const ambientModules = checker.getAmbientModules().map(sym => stripQuotes(sym.name)); + const nonRelativeModuleNames = ambientModules.filter(moduleName => startsWith(moduleName, fragment)); + + // Nested modules of the form "module-name/sub" need to be adjusted to only return the string + // after the last '/' that appears in the fragment because that's where the replacement span + // starts + if (fragmentDirectory !== undefined) { + const moduleNameWithSeparator = ensureTrailingDirectorySeparator(fragmentDirectory); + return nonRelativeModuleNames.map(nonRelativeModuleName => removePrefix(nonRelativeModuleName, moduleNameWithSeparator)); } + return nonRelativeModuleNames; +} - function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): readonly PathCompletion[] | undefined { - const token = getTokenAtPosition(sourceFile, position); - const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos); - const range = commentRanges && find(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end); - if (!range) { - return undefined; - } - const text = sourceFile.text.slice(range.pos, position); - const match = tripleSlashDirectiveFragmentRegex.exec(text); - if (!match) { - return undefined; - } - - const [, prefix, kind, toComplete] = match; - const scriptPath = getDirectoryPath(sourceFile.path); - const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getExtensionOptions(compilerOptions, IncludeExtensionsOption.Include), host, sourceFile.path) - : kind === "types" ? getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, getFragmentDirectory(toComplete), getExtensionOptions(compilerOptions)) - : Debug.fail(); - return addReplacementSpans(toComplete, range.pos + prefix.length, arrayFrom(names.values())); +function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): readonly PathCompletion[] | undefined { + const token = getTokenAtPosition(sourceFile, position); + const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos); + const range = commentRanges && find(commentRanges, commentRange => position >= commentRange.pos && position <= commentRange.end); + if (!range) { + return undefined; + } + const text = sourceFile.text.slice(range.pos, position); + const match = tripleSlashDirectiveFragmentRegex.exec(text); + if (!match) { + return undefined; } - function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, fragmentDirectory: string | undefined, extensionOptions: ExtensionOptions, result = createNameAndKindSet()): NameAndKindSet { - // Check for typings specified in compiler options - const seen = new Map(); + const [, prefix, kind, toComplete] = match; + const scriptPath = getDirectoryPath(sourceFile.path); + const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getExtensionOptions(compilerOptions, IncludeExtensionsOption.Include), host, sourceFile.path) + : kind === "types" ? getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, getFragmentDirectory(toComplete), getExtensionOptions(compilerOptions)) + : Debug.fail(); + return addReplacementSpans(toComplete, range.pos + prefix.length, arrayFrom(names.values())); +} - const typeRoots = tryAndIgnoreErrors(() => getEffectiveTypeRoots(options, host)) || emptyArray; +function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, fragmentDirectory: string | undefined, extensionOptions: ExtensionOptions, result = createNameAndKindSet()): NameAndKindSet { + // Check for typings specified in compiler options + const seen = new Map(); - for (const root of typeRoots) { - getCompletionEntriesFromDirectories(root); - } + const typeRoots = tryAndIgnoreErrors(() => getEffectiveTypeRoots(options, host)) || emptyArray; - // Also get all @types typings installed in visible node_modules directories - for (const packageJson of findPackageJsons(scriptPath, host)) { - const typesDir = combinePaths(getDirectoryPath(packageJson), "node_modules/@types"); - getCompletionEntriesFromDirectories(typesDir); - } + for (const root of typeRoots) { + getCompletionEntriesFromDirectories(root); + } - return result; + // Also get all @types typings installed in visible node_modules directories + for (const packageJson of findPackageJsons(scriptPath, host)) { + const typesDir = combinePaths(getDirectoryPath(packageJson), "node_modules/@types"); + getCompletionEntriesFromDirectories(typesDir); + } - function getCompletionEntriesFromDirectories(directory: string): void { - if (!tryDirectoryExists(host, directory)) return; + return result; - for (const typeDirectoryName of tryGetDirectories(host, directory)) { - const packageName = unmangleScopedPackageName(typeDirectoryName); - if (options.types && !contains(options.types, packageName)) continue; + function getCompletionEntriesFromDirectories(directory: string): void { + if (!tryDirectoryExists(host, directory)) return; - if (fragmentDirectory === undefined) { - if (!seen.has(packageName)) { - result.add(nameAndKind(packageName, ScriptElementKind.externalModuleName, /*extension*/ undefined)); - seen.set(packageName, true); - } + for (const typeDirectoryName of tryGetDirectories(host, directory)) { + const packageName = unmangleScopedPackageName(typeDirectoryName); + if (options.types && !contains(options.types, packageName)) continue; + + if (fragmentDirectory === undefined) { + if (!seen.has(packageName)) { + result.add(nameAndKind(packageName, ScriptElementKind.externalModuleName, /*extension*/ undefined)); + seen.set(packageName, true); } - else { - const baseDirectory = combinePaths(directory, typeDirectoryName); - const remainingFragment = tryRemoveDirectoryPrefix(fragmentDirectory, packageName, hostGetCanonicalFileName(host)); - if (remainingFragment !== undefined) { - getCompletionEntriesForDirectoryFragment(remainingFragment, baseDirectory, extensionOptions, host, /*exclude*/ undefined, result); - } + } + else { + const baseDirectory = combinePaths(directory, typeDirectoryName); + const remainingFragment = tryRemoveDirectoryPrefix(fragmentDirectory, packageName, hostGetCanonicalFileName(host)); + if (remainingFragment !== undefined) { + getCompletionEntriesForDirectoryFragment(remainingFragment, baseDirectory, extensionOptions, host, /*exclude*/ undefined, result); } } } } +} - function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string): readonly string[] { - if (!host.readFile || !host.fileExists) return emptyArray; - - const result: string[] = []; - for (const packageJson of findPackageJsons(scriptPath, host)) { - const contents = readJson(packageJson, host as { readFile: (filename: string) => string | undefined }); // Cast to assert that readFile is defined - // Provide completions for all non @types dependencies - for (const key of nodeModulesDependencyKeys) { - const dependencies: object | undefined = (contents as any)[key]; - if (!dependencies) continue; - for (const dep in dependencies) { - if (hasProperty(dependencies, dep) && !startsWith(dep, "@types/")) { - result.push(dep); - } +function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string): readonly string[] { + if (!host.readFile || !host.fileExists) return emptyArray; + + const result: string[] = []; + for (const packageJson of findPackageJsons(scriptPath, host)) { + const contents = readJson(packageJson, host as { readFile: (filename: string) => string | undefined }); // Cast to assert that readFile is defined + // Provide completions for all non @types dependencies + for (const key of nodeModulesDependencyKeys) { + const dependencies: object | undefined = (contents as any)[key]; + if (!dependencies) continue; + for (const dep in dependencies) { + if (hasProperty(dependencies, dep) && !startsWith(dep, "@types/")) { + result.push(dep); } } } - return result; } + return result; +} - // Replace everything after the last directory separator that appears - function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan | undefined { - const index = Math.max(text.lastIndexOf(directorySeparator), text.lastIndexOf(altDirectorySeparator)); - const offset = index !== -1 ? index + 1 : 0; - // If the range is an identifier, span is unnecessary. - const length = text.length - offset; - return length === 0 || isIdentifierText(text.substr(offset, length), ScriptTarget.ESNext) ? undefined : createTextSpan(textStart + offset, length); - } +// Replace everything after the last directory separator that appears +function getDirectoryFragmentTextSpan(text: string, textStart: number): TextSpan | undefined { + const index = Math.max(text.lastIndexOf(directorySeparator), text.lastIndexOf(altDirectorySeparator)); + const offset = index !== -1 ? index + 1 : 0; + // If the range is an identifier, span is unnecessary. + const length = text.length - offset; + return length === 0 || isIdentifierText(text.substr(offset, length), ScriptTarget.ESNext) ? undefined : createTextSpan(textStart + offset, length); +} - // Returns true if the path is explicitly relative to the script (i.e. relative to . or ..) - function isPathRelativeToScript(path: string) { - if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) { - const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1; - const slashCharCode = path.charCodeAt(slashIndex); - return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash; - } - return false; +// Returns true if the path is explicitly relative to the script (i.e. relative to . or ..) +function isPathRelativeToScript(path: string) { + if (path && path.length >= 2 && path.charCodeAt(0) === CharacterCodes.dot) { + const slashIndex = path.length >= 3 && path.charCodeAt(1) === CharacterCodes.dot ? 2 : 1; + const slashCharCode = path.charCodeAt(slashIndex); + return slashCharCode === CharacterCodes.slash || slashCharCode === CharacterCodes.backslash; } + return false; +} - /** - * Matches a triple slash reference directive with an incomplete string literal for its path. Used - * to determine if the caret is currently within the string literal and capture the literal fragment - * for completions. - * For example, this matches - * - * /// (); - - export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] { - program.getSemanticDiagnostics(sourceFile, cancellationToken); - const diags: DiagnosticWithLocation[] = []; - const checker = program.getTypeChecker(); - const isCommonJSFile = sourceFile.impliedNodeFormat === ModuleKind.CommonJS || fileExtensionIsOneOf(sourceFile.fileName, [Extension.Cts, Extension.Cjs]) ; - - if (!isCommonJSFile && - sourceFile.commonJsModuleIndicator && - (programContainsEsModules(program) || compilerOptionsIndicateEsModules(program.getCompilerOptions())) && - containsTopLevelCommonjs(sourceFile)) { - diags.push(createDiagnosticForNode(getErrorNodeFromCommonJsIndicator(sourceFile.commonJsModuleIndicator), Diagnostics.File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module)); - } +import { + addRange, AnyValidImportOrReExport, ArrowFunction, AssignmentDeclarationKind, Block, CallExpression, + CancellationToken, codefix, compilerOptionsIndicateEsModules, createDiagnosticForNode, Diagnostics, + DiagnosticWithLocation, Expression, ExpressionStatement, Extension, fileExtensionIsOneOf, forEachReturnStatement, + FunctionDeclaration, FunctionExpression, FunctionFlags, FunctionLikeDeclaration, getAllowSyntheticDefaultImports, + getAssignmentDeclarationKind, getFunctionFlags, getModeForUsageLocation, getResolvedModule, hasInitializer, + hasPropertyAccessExpressionWithName, Identifier, importFromModuleSpecifier, isAsyncFunction, isBinaryExpression, + isBlock, isCallExpression, isExportAssignment, isFunctionLike, isIdentifier, isPropertyAccessExpression, + isRequireCall, isReturnStatement, isSourceFileJS, isStringLiteral, isVariableDeclaration, isVariableStatement, Map, + MethodDeclaration, ModuleKind, Node, NodeFlags, Program, programContainsEsModules, PropertyAccessExpression, Push, + ReturnStatement, skipAlias, some, SourceFile, SyntaxKind, TypeChecker, VariableStatement, +} from "./_namespaces/ts"; - const isJsFile = isSourceFileJS(sourceFile); +const visitedNestedConvertibleFunctions = new Map(); - visitedNestedConvertibleFunctions.clear(); - check(sourceFile); +/** @internal */ +export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] { + program.getSemanticDiagnostics(sourceFile, cancellationToken); + const diags: DiagnosticWithLocation[] = []; + const checker = program.getTypeChecker(); + const isCommonJSFile = sourceFile.impliedNodeFormat === ModuleKind.CommonJS || fileExtensionIsOneOf(sourceFile.fileName, [Extension.Cts, Extension.Cjs]) ; - if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) { - for (const moduleSpecifier of sourceFile.imports) { - const importNode = importFromModuleSpecifier(moduleSpecifier); - const name = importNameForConvertToDefaultImport(importNode); - if (!name) continue; - const module = getResolvedModule(sourceFile, moduleSpecifier.text, getModeForUsageLocation(sourceFile, moduleSpecifier)); - const resolvedFile = module && program.getSourceFile(module.resolvedFileName); - if (resolvedFile && resolvedFile.externalModuleIndicator && resolvedFile.externalModuleIndicator !== true && isExportAssignment(resolvedFile.externalModuleIndicator) && resolvedFile.externalModuleIndicator.isExportEquals) { - diags.push(createDiagnosticForNode(name, Diagnostics.Import_may_be_converted_to_a_default_import)); - } + if (!isCommonJSFile && + sourceFile.commonJsModuleIndicator && + (programContainsEsModules(program) || compilerOptionsIndicateEsModules(program.getCompilerOptions())) && + containsTopLevelCommonjs(sourceFile)) { + diags.push(createDiagnosticForNode(getErrorNodeFromCommonJsIndicator(sourceFile.commonJsModuleIndicator), Diagnostics.File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module)); + } + + const isJsFile = isSourceFileJS(sourceFile); + + visitedNestedConvertibleFunctions.clear(); + check(sourceFile); + + if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) { + for (const moduleSpecifier of sourceFile.imports) { + const importNode = importFromModuleSpecifier(moduleSpecifier); + const name = importNameForConvertToDefaultImport(importNode); + if (!name) continue; + const module = getResolvedModule(sourceFile, moduleSpecifier.text, getModeForUsageLocation(sourceFile, moduleSpecifier)); + const resolvedFile = module && program.getSourceFile(module.resolvedFileName); + if (resolvedFile && resolvedFile.externalModuleIndicator && resolvedFile.externalModuleIndicator !== true && isExportAssignment(resolvedFile.externalModuleIndicator) && resolvedFile.externalModuleIndicator.isExportEquals) { + diags.push(createDiagnosticForNode(name, Diagnostics.Import_may_be_converted_to_a_default_import)); } } + } - addRange(diags, sourceFile.bindSuggestionDiagnostics); - addRange(diags, program.getSuggestionDiagnostics(sourceFile, cancellationToken)); - return diags.sort((d1, d2) => d1.start - d2.start); + addRange(diags, sourceFile.bindSuggestionDiagnostics); + addRange(diags, program.getSuggestionDiagnostics(sourceFile, cancellationToken)); + return diags.sort((d1, d2) => d1.start - d2.start); - function check(node: Node) { - if (isJsFile) { - if (canBeConvertedToClass(node, checker)) { - diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration)); - } + function check(node: Node) { + if (isJsFile) { + if (canBeConvertedToClass(node, checker)) { + diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration)); } - else { - if (isVariableStatement(node) && - node.parent === sourceFile && - node.declarationList.flags & NodeFlags.Const && - node.declarationList.declarations.length === 1) { - const init = node.declarationList.declarations[0].initializer; - if (init && isRequireCall(init, /*checkArgumentIsStringLiteralLike*/ true)) { - diags.push(createDiagnosticForNode(init, Diagnostics.require_call_may_be_converted_to_an_import)); - } - } - - if (codefix.parameterShouldGetTypeFromJSDoc(node)) { - diags.push(createDiagnosticForNode(node.name || node, Diagnostics.JSDoc_types_may_be_moved_to_TypeScript_types)); + } + else { + if (isVariableStatement(node) && + node.parent === sourceFile && + node.declarationList.flags & NodeFlags.Const && + node.declarationList.declarations.length === 1) { + const init = node.declarationList.declarations[0].initializer; + if (init && isRequireCall(init, /*checkArgumentIsStringLiteralLike*/ true)) { + diags.push(createDiagnosticForNode(init, Diagnostics.require_call_may_be_converted_to_an_import)); } } - if (canBeConvertedToAsync(node)) { - addConvertToAsyncFunctionDiagnostics(node, checker, diags); + if (codefix.parameterShouldGetTypeFromJSDoc(node)) { + diags.push(createDiagnosticForNode(node.name || node, Diagnostics.JSDoc_types_may_be_moved_to_TypeScript_types)); } - node.forEachChild(check); } - } - // convertToEsModule only works on top-level, so don't trigger it if commonjs code only appears in nested scopes. - function containsTopLevelCommonjs(sourceFile: SourceFile): boolean { - return sourceFile.statements.some(statement => { - switch (statement.kind) { - case SyntaxKind.VariableStatement: - return (statement as VariableStatement).declarationList.declarations.some(decl => - !!decl.initializer && isRequireCall(propertyAccessLeftHandSide(decl.initializer), /*checkArgumentIsStringLiteralLike*/ true)); - case SyntaxKind.ExpressionStatement: { - const { expression } = statement as ExpressionStatement; - if (!isBinaryExpression(expression)) return isRequireCall(expression, /*checkArgumentIsStringLiteralLike*/ true); - const kind = getAssignmentDeclarationKind(expression); - return kind === AssignmentDeclarationKind.ExportsProperty || kind === AssignmentDeclarationKind.ModuleExports; - } - default: - return false; - } - }); - } - - function propertyAccessLeftHandSide(node: Expression): Expression { - return isPropertyAccessExpression(node) ? propertyAccessLeftHandSide(node.expression) : node; + if (canBeConvertedToAsync(node)) { + addConvertToAsyncFunctionDiagnostics(node, checker, diags); + } + node.forEachChild(check); } +} - function importNameForConvertToDefaultImport(node: AnyValidImportOrReExport): Identifier | undefined { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - const { importClause, moduleSpecifier } = node; - return importClause && !importClause.name && importClause.namedBindings && importClause.namedBindings.kind === SyntaxKind.NamespaceImport && isStringLiteral(moduleSpecifier) - ? importClause.namedBindings.name - : undefined; - case SyntaxKind.ImportEqualsDeclaration: - return node.name; +// convertToEsModule only works on top-level, so don't trigger it if commonjs code only appears in nested scopes. +function containsTopLevelCommonjs(sourceFile: SourceFile): boolean { + return sourceFile.statements.some(statement => { + switch (statement.kind) { + case SyntaxKind.VariableStatement: + return (statement as VariableStatement).declarationList.declarations.some(decl => + !!decl.initializer && isRequireCall(propertyAccessLeftHandSide(decl.initializer), /*checkArgumentIsStringLiteralLike*/ true)); + case SyntaxKind.ExpressionStatement: { + const { expression } = statement as ExpressionStatement; + if (!isBinaryExpression(expression)) return isRequireCall(expression, /*checkArgumentIsStringLiteralLike*/ true); + const kind = getAssignmentDeclarationKind(expression); + return kind === AssignmentDeclarationKind.ExportsProperty || kind === AssignmentDeclarationKind.ModuleExports; + } default: - return undefined; + return false; } - } + }); +} - function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: Push): void { - // need to check function before checking map so that deeper levels of nested callbacks are checked - if (isConvertibleFunction(node, checker) && !visitedNestedConvertibleFunctions.has(getKeyFromNode(node))) { - diags.push(createDiagnosticForNode( - !node.name && isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ? node.parent.name : node, - Diagnostics.This_may_be_converted_to_an_async_function)); - } - } +function propertyAccessLeftHandSide(node: Expression): Expression { + return isPropertyAccessExpression(node) ? propertyAccessLeftHandSide(node.expression) : node; +} - function isConvertibleFunction(node: FunctionLikeDeclaration, checker: TypeChecker) { - return !isAsyncFunction(node) && - node.body && - isBlock(node.body) && - hasReturnStatementWithPromiseHandler(node.body, checker) && - returnsPromise(node, checker); +function importNameForConvertToDefaultImport(node: AnyValidImportOrReExport): Identifier | undefined { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + const { importClause, moduleSpecifier } = node; + return importClause && !importClause.name && importClause.namedBindings && importClause.namedBindings.kind === SyntaxKind.NamespaceImport && isStringLiteral(moduleSpecifier) + ? importClause.namedBindings.name + : undefined; + case SyntaxKind.ImportEqualsDeclaration: + return node.name; + default: + return undefined; } +} - export function returnsPromise(node: FunctionLikeDeclaration, checker: TypeChecker): boolean { - const signature = checker.getSignatureFromDeclaration(node); - const returnType = signature ? checker.getReturnTypeOfSignature(signature) : undefined; - return !!returnType && !!checker.getPromisedTypeOfPromise(returnType); +function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: Push): void { + // need to check function before checking map so that deeper levels of nested callbacks are checked + if (isConvertibleFunction(node, checker) && !visitedNestedConvertibleFunctions.has(getKeyFromNode(node))) { + diags.push(createDiagnosticForNode( + !node.name && isVariableDeclaration(node.parent) && isIdentifier(node.parent.name) ? node.parent.name : node, + Diagnostics.This_may_be_converted_to_an_async_function)); } +} - function getErrorNodeFromCommonJsIndicator(commonJsModuleIndicator: Node): Node { - return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; - } +function isConvertibleFunction(node: FunctionLikeDeclaration, checker: TypeChecker) { + return !isAsyncFunction(node) && + node.body && + isBlock(node.body) && + hasReturnStatementWithPromiseHandler(node.body, checker) && + returnsPromise(node, checker); +} - function hasReturnStatementWithPromiseHandler(body: Block, checker: TypeChecker): boolean { - return !!forEachReturnStatement(body, statement => isReturnStatementWithFixablePromiseHandler(statement, checker)); - } +/** @internal */ +export function returnsPromise(node: FunctionLikeDeclaration, checker: TypeChecker): boolean { + const signature = checker.getSignatureFromDeclaration(node); + const returnType = signature ? checker.getReturnTypeOfSignature(signature) : undefined; + return !!returnType && !!checker.getPromisedTypeOfPromise(returnType); +} - export function isReturnStatementWithFixablePromiseHandler(node: Node, checker: TypeChecker): node is ReturnStatement & { expression: CallExpression } { - return isReturnStatement(node) && !!node.expression && isFixablePromiseHandler(node.expression, checker); - } +function getErrorNodeFromCommonJsIndicator(commonJsModuleIndicator: Node): Node { + return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator; +} - // Should be kept up to date with transformExpression in convertToAsyncFunction.ts - export function isFixablePromiseHandler(node: Node, checker: TypeChecker): boolean { - // ensure outermost call exists and is a promise handler - if (!isPromiseHandler(node) || !hasSupportedNumberOfArguments(node) || !node.arguments.every(arg => isFixablePromiseArgument(arg, checker))) { - return false; - } +function hasReturnStatementWithPromiseHandler(body: Block, checker: TypeChecker): boolean { + return !!forEachReturnStatement(body, statement => isReturnStatementWithFixablePromiseHandler(statement, checker)); +} - // ensure all chained calls are valid - let currentNode = node.expression.expression; - while (isPromiseHandler(currentNode) || isPropertyAccessExpression(currentNode)) { - if (isCallExpression(currentNode)) { - if (!hasSupportedNumberOfArguments(currentNode) || !currentNode.arguments.every(arg => isFixablePromiseArgument(arg, checker))) { - return false; - } - currentNode = currentNode.expression.expression; - } - else { - currentNode = currentNode.expression; +/** @internal */ +export function isReturnStatementWithFixablePromiseHandler(node: Node, checker: TypeChecker): node is ReturnStatement & { expression: CallExpression } { + return isReturnStatement(node) && !!node.expression && isFixablePromiseHandler(node.expression, checker); +} + +// Should be kept up to date with transformExpression in convertToAsyncFunction.ts +/** @internal */ +export function isFixablePromiseHandler(node: Node, checker: TypeChecker): boolean { + // ensure outermost call exists and is a promise handler + if (!isPromiseHandler(node) || !hasSupportedNumberOfArguments(node) || !node.arguments.every(arg => isFixablePromiseArgument(arg, checker))) { + return false; + } + + // ensure all chained calls are valid + let currentNode = node.expression.expression; + while (isPromiseHandler(currentNode) || isPropertyAccessExpression(currentNode)) { + if (isCallExpression(currentNode)) { + if (!hasSupportedNumberOfArguments(currentNode) || !currentNode.arguments.every(arg => isFixablePromiseArgument(arg, checker))) { + return false; } + currentNode = currentNode.expression.expression; + } + else { + currentNode = currentNode.expression; } - return true; } + return true; +} - function isPromiseHandler(node: Node): node is CallExpression & { readonly expression: PropertyAccessExpression } { - return isCallExpression(node) && ( - hasPropertyAccessExpressionWithName(node, "then") || - hasPropertyAccessExpressionWithName(node, "catch") || - hasPropertyAccessExpressionWithName(node, "finally")); - } +function isPromiseHandler(node: Node): node is CallExpression & { readonly expression: PropertyAccessExpression } { + return isCallExpression(node) && ( + hasPropertyAccessExpressionWithName(node, "then") || + hasPropertyAccessExpressionWithName(node, "catch") || + hasPropertyAccessExpressionWithName(node, "finally")); +} - function hasSupportedNumberOfArguments(node: CallExpression & { readonly expression: PropertyAccessExpression }) { - const name = node.expression.name.text; - const maxArguments = name === "then" ? 2 : name === "catch" ? 1 : name === "finally" ? 1 : 0; - if (node.arguments.length > maxArguments) return false; - if (node.arguments.length < maxArguments) return true; - return maxArguments === 1 || some(node.arguments, arg => { - return arg.kind === SyntaxKind.NullKeyword || isIdentifier(arg) && arg.text === "undefined"; - }); - } +function hasSupportedNumberOfArguments(node: CallExpression & { readonly expression: PropertyAccessExpression }) { + const name = node.expression.name.text; + const maxArguments = name === "then" ? 2 : name === "catch" ? 1 : name === "finally" ? 1 : 0; + if (node.arguments.length > maxArguments) return false; + if (node.arguments.length < maxArguments) return true; + return maxArguments === 1 || some(node.arguments, arg => { + return arg.kind === SyntaxKind.NullKeyword || isIdentifier(arg) && arg.text === "undefined"; + }); +} - // should be kept up to date with getTransformationBody in convertToAsyncFunction.ts - function isFixablePromiseArgument(arg: Expression, checker: TypeChecker): boolean { - switch (arg.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - const functionFlags = getFunctionFlags(arg as FunctionDeclaration | FunctionExpression); - if (functionFlags & FunctionFlags.Generator) { - return false; - } - // falls through - case SyntaxKind.ArrowFunction: - visitedNestedConvertibleFunctions.set(getKeyFromNode(arg as FunctionLikeDeclaration), true); - // falls through - case SyntaxKind.NullKeyword: - return true; - case SyntaxKind.Identifier: - case SyntaxKind.PropertyAccessExpression: { - const symbol = checker.getSymbolAtLocation(arg); - if (!symbol) { - return false; - } - return checker.isUndefinedSymbol(symbol) || - some(skipAlias(symbol, checker).declarations, d => isFunctionLike(d) || hasInitializer(d) && !!d.initializer && isFunctionLike(d.initializer)); +// should be kept up to date with getTransformationBody in convertToAsyncFunction.ts +function isFixablePromiseArgument(arg: Expression, checker: TypeChecker): boolean { + switch (arg.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + const functionFlags = getFunctionFlags(arg as FunctionDeclaration | FunctionExpression); + if (functionFlags & FunctionFlags.Generator) { + return false; } - default: + // falls through + case SyntaxKind.ArrowFunction: + visitedNestedConvertibleFunctions.set(getKeyFromNode(arg as FunctionLikeDeclaration), true); + // falls through + case SyntaxKind.NullKeyword: + return true; + case SyntaxKind.Identifier: + case SyntaxKind.PropertyAccessExpression: { + const symbol = checker.getSymbolAtLocation(arg); + if (!symbol) { return false; + } + return checker.isUndefinedSymbol(symbol) || + some(skipAlias(symbol, checker).declarations, d => isFunctionLike(d) || hasInitializer(d) && !!d.initializer && isFunctionLike(d.initializer)); } + default: + return false; } +} - function getKeyFromNode(exp: FunctionLikeDeclaration) { - return `${exp.pos.toString()}:${exp.end.toString()}`; - } - - function canBeConvertedToClass(node: Node, checker: TypeChecker): boolean { - if (node.kind === SyntaxKind.FunctionExpression) { - if (isVariableDeclaration(node.parent) && node.symbol.members?.size) { - return true; - } +function getKeyFromNode(exp: FunctionLikeDeclaration) { + return `${exp.pos.toString()}:${exp.end.toString()}`; +} - const symbol = checker.getSymbolOfExpando(node, /*allowDeclaration*/ false); - return !!(symbol && (symbol.exports?.size || symbol.members?.size)); +function canBeConvertedToClass(node: Node, checker: TypeChecker): boolean { + if (node.kind === SyntaxKind.FunctionExpression) { + if (isVariableDeclaration(node.parent) && node.symbol.members?.size) { + return true; } - if (node.kind === SyntaxKind.FunctionDeclaration) { - return !!node.symbol.members?.size; - } + const symbol = checker.getSymbolOfExpando(node, /*allowDeclaration*/ false); + return !!(symbol && (symbol.exports?.size || symbol.members?.size)); + } - return false; + if (node.kind === SyntaxKind.FunctionDeclaration) { + return !!node.symbol.members?.size; } - export function canBeConvertedToAsync(node: Node): node is FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return true; - default: - return false; - } + return false; +} + +/** @internal */ +export function canBeConvertedToAsync(node: Node): node is FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return true; + default: + return false; } } diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index 6ba2243b750dd..9e627cae13319 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -1,231 +1,283 @@ -/* @internal */ -namespace ts.SymbolDisplay { - const symbolDisplayNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; - - // TODO(drosen): use contextual SemanticMeaning. - export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { - const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location); - if (result !== ScriptElementKind.unknown) { - return result; - } - - const flags = getCombinedLocalAndExportSymbolFlags(symbol); - if (flags & SymbolFlags.Class) { - return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ? - ScriptElementKind.localClassElement : ScriptElementKind.classElement; - } - if (flags & SymbolFlags.Enum) return ScriptElementKind.enumElement; - if (flags & SymbolFlags.TypeAlias) return ScriptElementKind.typeElement; - if (flags & SymbolFlags.Interface) return ScriptElementKind.interfaceElement; - if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement; - if (flags & SymbolFlags.EnumMember) return ScriptElementKind.enumMemberElement; - if (flags & SymbolFlags.Alias) return ScriptElementKind.alias; - if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement; - +import { + addRange, arrayFrom, BinaryExpression, CallExpression, CheckFlags, contains, createPrinter, Debug, displayPart, + EmitHint, emptyArray, EnumMember, ExportAssignment, find, first, firstDefined, forEach, GetAccessorDeclaration, + getCombinedLocalAndExportSymbolFlags, getDeclarationOfKind, getExternalModuleImportEqualsDeclarationExpression, + getMeaningFromLocation, getNameOfDeclaration, getNodeModifiers, getObjectFlags, getParseTreeNode, + getSourceFileOfNode, getTextOfConstantValue, getTextOfIdentifierOrLiteral, getTextOfNode, hasSyntacticModifier, + idText, ImportEqualsDeclaration, isArrowFunction, isBindingElement, isCallExpression, isCallExpressionTarget, + isCallOrNewExpression, isClassExpression, isConstTypeReference, isDeprecatedDeclaration, isEnumConst, + isEnumDeclaration, isExpression, isExternalModuleImportEqualsDeclaration, isFirstDeclarationOfSymbolParameter, + isFunctionBlock, isFunctionExpression, isFunctionLike, isFunctionLikeKind, isIdentifier, isInExpressionContext, + isJsxOpeningLikeElement, isLet, isModuleWithStringLiteralName, isNameOfFunctionDeclaration, isNewExpressionTarget, + isObjectBindingPattern, isTaggedTemplateExpression, isThisInTypeQuery, isVarConst, JSDocTagInfo, + JsxOpeningLikeElement, keywordPart, length, lineBreakPart, ListFormat, mapToDisplayParts, ModifierFlags, + ModuleDeclaration, NewExpression, Node, NodeBuilderFlags, ObjectFlags, operatorPart, Printer, + PropertyAccessExpression, PropertyDeclaration, punctuationPart, ScriptElementKind, ScriptElementKindModifier, + SemanticMeaning, Set, SetAccessorDeclaration, Signature, SignatureDeclaration, SignatureFlags, + signatureToDisplayParts, some, SourceFile, spacePart, Symbol, SymbolDisplayPart, SymbolDisplayPartKind, SymbolFlags, + SymbolFormatFlags, symbolToDisplayParts, SyntaxKind, TaggedTemplateExpression, textOrKeywordPart, textPart, + TransientSymbol, Type, TypeChecker, TypeFormatFlags, TypeParameter, typeToDisplayParts, VariableDeclaration, +} from "./_namespaces/ts"; + +const symbolDisplayNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope; + +// TODO(drosen): use contextual SemanticMeaning. +/** @internal */ +export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { + const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location); + if (result !== ScriptElementKind.unknown) { return result; } - function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { - const roots = typeChecker.getRootSymbols(symbol); - // If this is a method from a mapped type, leave as a method so long as it still has a call signature. - if (roots.length === 1 - && first(roots).flags & SymbolFlags.Method - // Ensure the mapped version is still a method, as opposed to `{ [K in keyof I]: number }`. - && typeChecker.getTypeOfSymbolAtLocation(symbol, location).getNonNullableType().getCallSignatures().length !== 0) { - return ScriptElementKind.memberFunctionElement; - } + const flags = getCombinedLocalAndExportSymbolFlags(symbol); + if (flags & SymbolFlags.Class) { + return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ? + ScriptElementKind.localClassElement : ScriptElementKind.classElement; + } + if (flags & SymbolFlags.Enum) return ScriptElementKind.enumElement; + if (flags & SymbolFlags.TypeAlias) return ScriptElementKind.typeElement; + if (flags & SymbolFlags.Interface) return ScriptElementKind.interfaceElement; + if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement; + if (flags & SymbolFlags.EnumMember) return ScriptElementKind.enumMemberElement; + if (flags & SymbolFlags.Alias) return ScriptElementKind.alias; + if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement; + + return result; +} - if (typeChecker.isUndefinedSymbol(symbol)) { - return ScriptElementKind.variableElement; +function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, location: Node): ScriptElementKind { + const roots = typeChecker.getRootSymbols(symbol); + // If this is a method from a mapped type, leave as a method so long as it still has a call signature. + if (roots.length === 1 + && first(roots).flags & SymbolFlags.Method + // Ensure the mapped version is still a method, as opposed to `{ [K in keyof I]: number }`. + && typeChecker.getTypeOfSymbolAtLocation(symbol, location).getNonNullableType().getCallSignatures().length !== 0) { + return ScriptElementKind.memberFunctionElement; + } + + if (typeChecker.isUndefinedSymbol(symbol)) { + return ScriptElementKind.variableElement; + } + if (typeChecker.isArgumentsSymbol(symbol)) { + return ScriptElementKind.localVariableElement; + } + if (location.kind === SyntaxKind.ThisKeyword && isExpression(location) || isThisInTypeQuery(location)) { + return ScriptElementKind.parameterElement; + } + const flags = getCombinedLocalAndExportSymbolFlags(symbol); + if (flags & SymbolFlags.Variable) { + if (isFirstDeclarationOfSymbolParameter(symbol)) { + return ScriptElementKind.parameterElement; } - if (typeChecker.isArgumentsSymbol(symbol)) { - return ScriptElementKind.localVariableElement; + else if (symbol.valueDeclaration && isVarConst(symbol.valueDeclaration as VariableDeclaration)) { + return ScriptElementKind.constElement; } - if (location.kind === SyntaxKind.ThisKeyword && isExpression(location) || isThisInTypeQuery(location)) { - return ScriptElementKind.parameterElement; + else if (forEach(symbol.declarations, isLet)) { + return ScriptElementKind.letElement; } - const flags = getCombinedLocalAndExportSymbolFlags(symbol); - if (flags & SymbolFlags.Variable) { - if (isFirstDeclarationOfSymbolParameter(symbol)) { - return ScriptElementKind.parameterElement; - } - else if (symbol.valueDeclaration && isVarConst(symbol.valueDeclaration as VariableDeclaration)) { - return ScriptElementKind.constElement; - } - else if (forEach(symbol.declarations, isLet)) { - return ScriptElementKind.letElement; - } - return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localVariableElement : ScriptElementKind.variableElement; - } - if (flags & SymbolFlags.Function) return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localFunctionElement : ScriptElementKind.functionElement; - // FIXME: getter and setter use the same symbol. And it is rare to use only setter without getter, so in most cases the symbol always has getter flag. - // So, even when the location is just on the declaration of setter, this function returns getter. - if (flags & SymbolFlags.GetAccessor) return ScriptElementKind.memberGetAccessorElement; - if (flags & SymbolFlags.SetAccessor) return ScriptElementKind.memberSetAccessorElement; - if (flags & SymbolFlags.Method) return ScriptElementKind.memberFunctionElement; - if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement; - if (flags & SymbolFlags.Signature) return ScriptElementKind.indexSignatureElement; - - if (flags & SymbolFlags.Property) { - if (flags & SymbolFlags.Transient && (symbol as TransientSymbol).checkFlags & CheckFlags.Synthetic) { - // If union property is result of union of non method (property/accessors/variables), it is labeled as property - const unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => { - const rootSymbolFlags = rootSymbol.getFlags(); - if (rootSymbolFlags & (SymbolFlags.PropertyOrAccessor | SymbolFlags.Variable)) { - return ScriptElementKind.memberVariableElement; - } - }); - if (!unionPropertyKind) { - // If this was union of all methods, - // make sure it has call signatures before we can label it as method - const typeOfUnionProperty = typeChecker.getTypeOfSymbolAtLocation(symbol, location); - if (typeOfUnionProperty.getCallSignatures().length) { - return ScriptElementKind.memberFunctionElement; - } + return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localVariableElement : ScriptElementKind.variableElement; + } + if (flags & SymbolFlags.Function) return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localFunctionElement : ScriptElementKind.functionElement; + // FIXME: getter and setter use the same symbol. And it is rare to use only setter without getter, so in most cases the symbol always has getter flag. + // So, even when the location is just on the declaration of setter, this function returns getter. + if (flags & SymbolFlags.GetAccessor) return ScriptElementKind.memberGetAccessorElement; + if (flags & SymbolFlags.SetAccessor) return ScriptElementKind.memberSetAccessorElement; + if (flags & SymbolFlags.Method) return ScriptElementKind.memberFunctionElement; + if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement; + if (flags & SymbolFlags.Signature) return ScriptElementKind.indexSignatureElement; + + if (flags & SymbolFlags.Property) { + if (flags & SymbolFlags.Transient && (symbol as TransientSymbol).checkFlags & CheckFlags.Synthetic) { + // If union property is result of union of non method (property/accessors/variables), it is labeled as property + const unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => { + const rootSymbolFlags = rootSymbol.getFlags(); + if (rootSymbolFlags & (SymbolFlags.PropertyOrAccessor | SymbolFlags.Variable)) { return ScriptElementKind.memberVariableElement; } - return unionPropertyKind; + }); + if (!unionPropertyKind) { + // If this was union of all methods, + // make sure it has call signatures before we can label it as method + const typeOfUnionProperty = typeChecker.getTypeOfSymbolAtLocation(symbol, location); + if (typeOfUnionProperty.getCallSignatures().length) { + return ScriptElementKind.memberFunctionElement; + } + return ScriptElementKind.memberVariableElement; } - - return ScriptElementKind.memberVariableElement; + return unionPropertyKind; } - return ScriptElementKind.unknown; + return ScriptElementKind.memberVariableElement; } - function getNormalizedSymbolModifiers(symbol: Symbol) { - if (symbol.declarations && symbol.declarations.length) { - const [declaration, ...declarations] = symbol.declarations; - // omit deprecated flag if some declarations are not deprecated - const excludeFlags = length(declarations) && isDeprecatedDeclaration(declaration) && some(declarations, d => !isDeprecatedDeclaration(d)) - ? ModifierFlags.Deprecated - : ModifierFlags.None; - const modifiers = getNodeModifiers(declaration, excludeFlags); - if (modifiers) { - return modifiers.split(","); - } + return ScriptElementKind.unknown; +} + +function getNormalizedSymbolModifiers(symbol: Symbol) { + if (symbol.declarations && symbol.declarations.length) { + const [declaration, ...declarations] = symbol.declarations; + // omit deprecated flag if some declarations are not deprecated + const excludeFlags = length(declarations) && isDeprecatedDeclaration(declaration) && some(declarations, d => !isDeprecatedDeclaration(d)) + ? ModifierFlags.Deprecated + : ModifierFlags.None; + const modifiers = getNodeModifiers(declaration, excludeFlags); + if (modifiers) { + return modifiers.split(","); } - return []; } + return []; +} - export function getSymbolModifiers(typeChecker: TypeChecker, symbol: Symbol): string { - if (!symbol) { - return ScriptElementKindModifier.none; - } +/** @internal */ +export function getSymbolModifiers(typeChecker: TypeChecker, symbol: Symbol): string { + if (!symbol) { + return ScriptElementKindModifier.none; + } - const modifiers = new Set(getNormalizedSymbolModifiers(symbol)); - if (symbol.flags & SymbolFlags.Alias) { - const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); - if (resolvedSymbol !== symbol) { - forEach(getNormalizedSymbolModifiers(resolvedSymbol), modifier => { - modifiers.add(modifier); - }); - } + const modifiers = new Set(getNormalizedSymbolModifiers(symbol)); + if (symbol.flags & SymbolFlags.Alias) { + const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); + if (resolvedSymbol !== symbol) { + forEach(getNormalizedSymbolModifiers(resolvedSymbol), modifier => { + modifiers.add(modifier); + }); } - if (symbol.flags & SymbolFlags.Optional) { - modifiers.add(ScriptElementKindModifier.optionalModifier); - } - return modifiers.size > 0 ? arrayFrom(modifiers.values()).join(",") : ScriptElementKindModifier.none; - } - - interface SymbolDisplayPartsDocumentationAndSymbolKind { - displayParts: SymbolDisplayPart[]; - documentation: SymbolDisplayPart[]; - symbolKind: ScriptElementKind; - tags: JSDocTagInfo[] | undefined; - } - - // TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location - export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node | undefined, - location: Node, semanticMeaning = getMeaningFromLocation(location), alias?: Symbol): SymbolDisplayPartsDocumentationAndSymbolKind { - const displayParts: SymbolDisplayPart[] = []; - let documentation: SymbolDisplayPart[] = []; - let tags: JSDocTagInfo[] = []; - const symbolFlags = getCombinedLocalAndExportSymbolFlags(symbol); - let symbolKind = semanticMeaning & SemanticMeaning.Value ? getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location) : ScriptElementKind.unknown; - let hasAddedSymbolInfo = false; - const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isInExpressionContext(location) || isThisInTypeQuery(location); - let type: Type | undefined; - let printer: Printer; - let documentationFromAlias: SymbolDisplayPart[] | undefined; - let tagsFromAlias: JSDocTagInfo[] | undefined; - let hasMultipleSignatures = false; - - if (location.kind === SyntaxKind.ThisKeyword && !isThisExpression) { - return { displayParts: [keywordPart(SyntaxKind.ThisKeyword)], documentation: [], symbolKind: ScriptElementKind.primitiveType, tags: undefined }; - } - - // Class at constructor site need to be shown as constructor apart from property,method, vars - if (symbolKind !== ScriptElementKind.unknown || symbolFlags & SymbolFlags.Class || symbolFlags & SymbolFlags.Alias) { - // If symbol is accessor, they are allowed only if location is at declaration identifier of the accessor - if (symbolKind === ScriptElementKind.memberGetAccessorElement || symbolKind === ScriptElementKind.memberSetAccessorElement) { - const declaration = find(symbol.declarations as ((GetAccessorDeclaration | SetAccessorDeclaration | PropertyDeclaration)[]), declaration => declaration.name === location); - if (declaration) { - switch(declaration.kind){ - case SyntaxKind.GetAccessor: - symbolKind = ScriptElementKind.memberGetAccessorElement; - break; - case SyntaxKind.SetAccessor: - symbolKind = ScriptElementKind.memberSetAccessorElement; - break; - case SyntaxKind.PropertyDeclaration: - symbolKind = ScriptElementKind.memberAccessorVariableElement; - break; - default: - Debug.assertNever(declaration); - } - } - else { - symbolKind = ScriptElementKind.memberVariableElement; - } - } + } + if (symbol.flags & SymbolFlags.Optional) { + modifiers.add(ScriptElementKindModifier.optionalModifier); + } + return modifiers.size > 0 ? arrayFrom(modifiers.values()).join(",") : ScriptElementKindModifier.none; +} - let signature: Signature | undefined; - type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location); +/** @internal */ +export interface SymbolDisplayPartsDocumentationAndSymbolKind { + displayParts: SymbolDisplayPart[]; + documentation: SymbolDisplayPart[]; + symbolKind: ScriptElementKind; + tags: JSDocTagInfo[] | undefined; +} - if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) { - const right = (location.parent as PropertyAccessExpression).name; - // Either the location is on the right of a property access, or on the left and the right is missing - if (right === location || (right && right.getFullWidth() === 0)) { - location = location.parent; - } - } +// TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location +/** @internal */ +export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node | undefined, + location: Node, semanticMeaning = getMeaningFromLocation(location), alias?: Symbol): SymbolDisplayPartsDocumentationAndSymbolKind { + const displayParts: SymbolDisplayPart[] = []; + let documentation: SymbolDisplayPart[] = []; + let tags: JSDocTagInfo[] = []; + const symbolFlags = getCombinedLocalAndExportSymbolFlags(symbol); + let symbolKind = semanticMeaning & SemanticMeaning.Value ? getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location) : ScriptElementKind.unknown; + let hasAddedSymbolInfo = false; + const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isInExpressionContext(location) || isThisInTypeQuery(location); + let type: Type | undefined; + let printer: Printer; + let documentationFromAlias: SymbolDisplayPart[] | undefined; + let tagsFromAlias: JSDocTagInfo[] | undefined; + let hasMultipleSignatures = false; + + if (location.kind === SyntaxKind.ThisKeyword && !isThisExpression) { + return { displayParts: [keywordPart(SyntaxKind.ThisKeyword)], documentation: [], symbolKind: ScriptElementKind.primitiveType, tags: undefined }; + } - // try get the call/construct signature from the type if it matches - let callExpressionLike: CallExpression | NewExpression | JsxOpeningLikeElement | TaggedTemplateExpression | undefined; - if (isCallOrNewExpression(location)) { - callExpressionLike = location; + // Class at constructor site need to be shown as constructor apart from property,method, vars + if (symbolKind !== ScriptElementKind.unknown || symbolFlags & SymbolFlags.Class || symbolFlags & SymbolFlags.Alias) { + // If symbol is accessor, they are allowed only if location is at declaration identifier of the accessor + if (symbolKind === ScriptElementKind.memberGetAccessorElement || symbolKind === ScriptElementKind.memberSetAccessorElement) { + const declaration = find(symbol.declarations as ((GetAccessorDeclaration | SetAccessorDeclaration | PropertyDeclaration)[]), declaration => declaration.name === location); + if (declaration) { + switch(declaration.kind){ + case SyntaxKind.GetAccessor: + symbolKind = ScriptElementKind.memberGetAccessorElement; + break; + case SyntaxKind.SetAccessor: + symbolKind = ScriptElementKind.memberSetAccessorElement; + break; + case SyntaxKind.PropertyDeclaration: + symbolKind = ScriptElementKind.memberAccessorVariableElement; + break; + default: + Debug.assertNever(declaration); + } } - else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) { - callExpressionLike = location.parent as CallExpression | NewExpression; + else { + symbolKind = ScriptElementKind.memberVariableElement; } - else if (location.parent && (isJsxOpeningLikeElement(location.parent) || isTaggedTemplateExpression(location.parent)) && isFunctionLike(symbol.valueDeclaration)) { - callExpressionLike = location.parent; + } + + let signature: Signature | undefined; + type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location); + + if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) { + const right = (location.parent as PropertyAccessExpression).name; + // Either the location is on the right of a property access, or on the left and the right is missing + if (right === location || (right && right.getFullWidth() === 0)) { + location = location.parent; } + } - if (callExpressionLike) { - signature = typeChecker.getResolvedSignature(callExpressionLike); // TODO: GH#18217 + // try get the call/construct signature from the type if it matches + let callExpressionLike: CallExpression | NewExpression | JsxOpeningLikeElement | TaggedTemplateExpression | undefined; + if (isCallOrNewExpression(location)) { + callExpressionLike = location; + } + else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) { + callExpressionLike = location.parent as CallExpression | NewExpression; + } + else if (location.parent && (isJsxOpeningLikeElement(location.parent) || isTaggedTemplateExpression(location.parent)) && isFunctionLike(symbol.valueDeclaration)) { + callExpressionLike = location.parent; + } - const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword); + if (callExpressionLike) { + signature = typeChecker.getResolvedSignature(callExpressionLike); // TODO: GH#18217 - const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures(); + const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword); - if (signature && !contains(allSignatures, signature.target) && !contains(allSignatures, signature)) { - // Get the first signature if there is one -- allSignatures may contain - // either the original signature or its target, so check for either - signature = allSignatures.length ? allSignatures[0] : undefined; - } + const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures(); - if (signature) { - if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) { - // Constructor - symbolKind = ScriptElementKind.constructorImplementationElement; - addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); + if (signature && !contains(allSignatures, signature.target) && !contains(allSignatures, signature)) { + // Get the first signature if there is one -- allSignatures may contain + // either the original signature or its target, so check for either + signature = allSignatures.length ? allSignatures[0] : undefined; + } + + if (signature) { + if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) { + // Constructor + symbolKind = ScriptElementKind.constructorImplementationElement; + addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); + } + else if (symbolFlags & SymbolFlags.Alias) { + symbolKind = ScriptElementKind.alias; + pushSymbolKind(symbolKind); + displayParts.push(spacePart()); + if (useConstructSignatures) { + if (signature.flags & SignatureFlags.Abstract) { + displayParts.push(keywordPart(SyntaxKind.AbstractKeyword)); + displayParts.push(spacePart()); + } + displayParts.push(keywordPart(SyntaxKind.NewKeyword)); + displayParts.push(spacePart()); } - else if (symbolFlags & SymbolFlags.Alias) { - symbolKind = ScriptElementKind.alias; - pushSymbolKind(symbolKind); + addFullSymbolName(symbol); + } + else { + addPrefixForAnyFunctionOrVar(symbol, symbolKind); + } + + switch (symbolKind) { + case ScriptElementKind.jsxAttribute: + case ScriptElementKind.memberVariableElement: + case ScriptElementKind.variableElement: + case ScriptElementKind.constElement: + case ScriptElementKind.letElement: + case ScriptElementKind.parameterElement: + case ScriptElementKind.localVariableElement: + // If it is call or construct signature of lambda's write type name + displayParts.push(punctuationPart(SyntaxKind.ColonToken)); displayParts.push(spacePart()); + if (!(getObjectFlags(type) & ObjectFlags.Anonymous) && type.symbol) { + addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteTypeParametersOrArguments)); + displayParts.push(lineBreakPart()); + } if (useConstructSignatures) { if (signature.flags & SignatureFlags.Abstract) { displayParts.push(keywordPart(SyntaxKind.AbstractKeyword)); @@ -234,536 +286,506 @@ namespace ts.SymbolDisplay { displayParts.push(keywordPart(SyntaxKind.NewKeyword)); displayParts.push(spacePart()); } - addFullSymbolName(symbol); - } - else { - addPrefixForAnyFunctionOrVar(symbol, symbolKind); - } - - switch (symbolKind) { - case ScriptElementKind.jsxAttribute: - case ScriptElementKind.memberVariableElement: - case ScriptElementKind.variableElement: - case ScriptElementKind.constElement: - case ScriptElementKind.letElement: - case ScriptElementKind.parameterElement: - case ScriptElementKind.localVariableElement: - // If it is call or construct signature of lambda's write type name - displayParts.push(punctuationPart(SyntaxKind.ColonToken)); - displayParts.push(spacePart()); - if (!(getObjectFlags(type) & ObjectFlags.Anonymous) && type.symbol) { - addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteTypeParametersOrArguments)); - displayParts.push(lineBreakPart()); - } - if (useConstructSignatures) { - if (signature.flags & SignatureFlags.Abstract) { - displayParts.push(keywordPart(SyntaxKind.AbstractKeyword)); - displayParts.push(spacePart()); - } - displayParts.push(keywordPart(SyntaxKind.NewKeyword)); - displayParts.push(spacePart()); - } - addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature); - break; + addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature); + break; - default: - // Just signature - addSignatureDisplayParts(signature, allSignatures); - } - hasAddedSymbolInfo = true; - hasMultipleSignatures = allSignatures.length > 1; + default: + // Just signature + addSignatureDisplayParts(signature, allSignatures); + } + hasAddedSymbolInfo = true; + hasMultipleSignatures = allSignatures.length > 1; + } + } + else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration + (location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration + // get the signature from the declaration and write it + const functionDeclaration = location.parent as SignatureDeclaration; + // Use function declaration to write the signatures only if the symbol corresponding to this declaration + const locationIsSymbolDeclaration = symbol.declarations && find(symbol.declarations, declaration => + declaration === (location.kind === SyntaxKind.ConstructorKeyword ? functionDeclaration.parent : functionDeclaration)); + + if (locationIsSymbolDeclaration) { + const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures(); + if (!typeChecker.isImplementationOfOverload(functionDeclaration)) { + signature = typeChecker.getSignatureFromDeclaration(functionDeclaration); // TODO: GH#18217 + } + else { + signature = allSignatures[0]; } - } - else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration - (location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration - // get the signature from the declaration and write it - const functionDeclaration = location.parent as SignatureDeclaration; - // Use function declaration to write the signatures only if the symbol corresponding to this declaration - const locationIsSymbolDeclaration = symbol.declarations && find(symbol.declarations, declaration => - declaration === (location.kind === SyntaxKind.ConstructorKeyword ? functionDeclaration.parent : functionDeclaration)); - - if (locationIsSymbolDeclaration) { - const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures(); - if (!typeChecker.isImplementationOfOverload(functionDeclaration)) { - signature = typeChecker.getSignatureFromDeclaration(functionDeclaration); // TODO: GH#18217 - } - else { - signature = allSignatures[0]; - } - if (functionDeclaration.kind === SyntaxKind.Constructor) { - // show (constructor) Type(...) signature - symbolKind = ScriptElementKind.constructorImplementationElement; - addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); - } - else { - // (function/method) symbol(..signature) - addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature && - !(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind); - } - if (signature) { - addSignatureDisplayParts(signature, allSignatures); - } - hasAddedSymbolInfo = true; - hasMultipleSignatures = allSignatures.length > 1; + if (functionDeclaration.kind === SyntaxKind.Constructor) { + // show (constructor) Type(...) signature + symbolKind = ScriptElementKind.constructorImplementationElement; + addPrefixForAnyFunctionOrVar(type.symbol, symbolKind); } + else { + // (function/method) symbol(..signature) + addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature && + !(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind); + } + if (signature) { + addSignatureDisplayParts(signature, allSignatures); + } + hasAddedSymbolInfo = true; + hasMultipleSignatures = allSignatures.length > 1; } } - if (symbolFlags & SymbolFlags.Class && !hasAddedSymbolInfo && !isThisExpression) { - addAliasPrefixIfNecessary(); - if (getDeclarationOfKind(symbol, SyntaxKind.ClassExpression)) { - // Special case for class expressions because we would like to indicate that - // the class name is local to the class body (similar to function expression) - // (local class) class - pushSymbolKind(ScriptElementKind.localClassElement); - } - else { - // Class declaration has name which is not local. - displayParts.push(keywordPart(SyntaxKind.ClassKeyword)); - } - displayParts.push(spacePart()); - addFullSymbolName(symbol); - writeTypeParametersOfSymbol(symbol, sourceFile); - } - if ((symbolFlags & SymbolFlags.Interface) && (semanticMeaning & SemanticMeaning.Type)) { - prefixNextMeaning(); - displayParts.push(keywordPart(SyntaxKind.InterfaceKeyword)); - displayParts.push(spacePart()); - addFullSymbolName(symbol); - writeTypeParametersOfSymbol(symbol, sourceFile); - } - if ((symbolFlags & SymbolFlags.TypeAlias) && (semanticMeaning & SemanticMeaning.Type)) { - prefixNextMeaning(); - displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); - displayParts.push(spacePart()); - addFullSymbolName(symbol); - writeTypeParametersOfSymbol(symbol, sourceFile); - displayParts.push(spacePart()); - displayParts.push(operatorPart(SyntaxKind.EqualsToken)); - displayParts.push(spacePart()); - addRange(displayParts, typeToDisplayParts(typeChecker, isConstTypeReference(location.parent) ? typeChecker.getTypeAtLocation(location.parent) : typeChecker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, TypeFormatFlags.InTypeAlias)); - } - if (symbolFlags & SymbolFlags.Enum) { - prefixNextMeaning(); - if (some(symbol.declarations, d => isEnumDeclaration(d) && isEnumConst(d))) { - displayParts.push(keywordPart(SyntaxKind.ConstKeyword)); - displayParts.push(spacePart()); - } - displayParts.push(keywordPart(SyntaxKind.EnumKeyword)); - displayParts.push(spacePart()); - addFullSymbolName(symbol); - } - if (symbolFlags & SymbolFlags.Module && !isThisExpression) { - prefixNextMeaning(); - const declaration = getDeclarationOfKind(symbol, SyntaxKind.ModuleDeclaration); - const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier; - displayParts.push(keywordPart(isNamespace ? SyntaxKind.NamespaceKeyword : SyntaxKind.ModuleKeyword)); + } + if (symbolFlags & SymbolFlags.Class && !hasAddedSymbolInfo && !isThisExpression) { + addAliasPrefixIfNecessary(); + if (getDeclarationOfKind(symbol, SyntaxKind.ClassExpression)) { + // Special case for class expressions because we would like to indicate that + // the class name is local to the class body (similar to function expression) + // (local class) class + pushSymbolKind(ScriptElementKind.localClassElement); + } + else { + // Class declaration has name which is not local. + displayParts.push(keywordPart(SyntaxKind.ClassKeyword)); + } + displayParts.push(spacePart()); + addFullSymbolName(symbol); + writeTypeParametersOfSymbol(symbol, sourceFile); + } + if ((symbolFlags & SymbolFlags.Interface) && (semanticMeaning & SemanticMeaning.Type)) { + prefixNextMeaning(); + displayParts.push(keywordPart(SyntaxKind.InterfaceKeyword)); + displayParts.push(spacePart()); + addFullSymbolName(symbol); + writeTypeParametersOfSymbol(symbol, sourceFile); + } + if ((symbolFlags & SymbolFlags.TypeAlias) && (semanticMeaning & SemanticMeaning.Type)) { + prefixNextMeaning(); + displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); + displayParts.push(spacePart()); + addFullSymbolName(symbol); + writeTypeParametersOfSymbol(symbol, sourceFile); + displayParts.push(spacePart()); + displayParts.push(operatorPart(SyntaxKind.EqualsToken)); + displayParts.push(spacePart()); + addRange(displayParts, typeToDisplayParts(typeChecker, isConstTypeReference(location.parent) ? typeChecker.getTypeAtLocation(location.parent) : typeChecker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, TypeFormatFlags.InTypeAlias)); + } + if (symbolFlags & SymbolFlags.Enum) { + prefixNextMeaning(); + if (some(symbol.declarations, d => isEnumDeclaration(d) && isEnumConst(d))) { + displayParts.push(keywordPart(SyntaxKind.ConstKeyword)); displayParts.push(spacePart()); - addFullSymbolName(symbol); } - if ((symbolFlags & SymbolFlags.TypeParameter) && (semanticMeaning & SemanticMeaning.Type)) { - prefixNextMeaning(); - displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - displayParts.push(textPart("type parameter")); - displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); - displayParts.push(spacePart()); - addFullSymbolName(symbol); - if (symbol.parent) { - // Class/Interface type parameter - addInPrefix(); - addFullSymbolName(symbol.parent, enclosingDeclaration); - writeTypeParametersOfSymbol(symbol.parent, enclosingDeclaration); - } - else { - // Method/function type parameter - const decl = getDeclarationOfKind(symbol, SyntaxKind.TypeParameter); - if (decl === undefined) return Debug.fail(); - const declaration = decl.parent; - - if (declaration) { - if (isFunctionLikeKind(declaration.kind)) { - addInPrefix(); - const signature = typeChecker.getSignatureFromDeclaration(declaration as SignatureDeclaration)!; // TODO: GH#18217 - if (declaration.kind === SyntaxKind.ConstructSignature) { - displayParts.push(keywordPart(SyntaxKind.NewKeyword)); - displayParts.push(spacePart()); - } - else if (declaration.kind !== SyntaxKind.CallSignature && (declaration as SignatureDeclaration).name) { - addFullSymbolName(declaration.symbol); - } - addRange(displayParts, signatureToDisplayParts(typeChecker, signature, sourceFile, TypeFormatFlags.WriteTypeArgumentsOfSignature)); - } - else if (declaration.kind === SyntaxKind.TypeAliasDeclaration) { - // Type alias type parameter - // For example - // type list = T[]; // Both T will go through same code path - addInPrefix(); - displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); + displayParts.push(keywordPart(SyntaxKind.EnumKeyword)); + displayParts.push(spacePart()); + addFullSymbolName(symbol); + } + if (symbolFlags & SymbolFlags.Module && !isThisExpression) { + prefixNextMeaning(); + const declaration = getDeclarationOfKind(symbol, SyntaxKind.ModuleDeclaration); + const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier; + displayParts.push(keywordPart(isNamespace ? SyntaxKind.NamespaceKeyword : SyntaxKind.ModuleKeyword)); + displayParts.push(spacePart()); + addFullSymbolName(symbol); + } + if ((symbolFlags & SymbolFlags.TypeParameter) && (semanticMeaning & SemanticMeaning.Type)) { + prefixNextMeaning(); + displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); + displayParts.push(textPart("type parameter")); + displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); + displayParts.push(spacePart()); + addFullSymbolName(symbol); + if (symbol.parent) { + // Class/Interface type parameter + addInPrefix(); + addFullSymbolName(symbol.parent, enclosingDeclaration); + writeTypeParametersOfSymbol(symbol.parent, enclosingDeclaration); + } + else { + // Method/function type parameter + const decl = getDeclarationOfKind(symbol, SyntaxKind.TypeParameter); + if (decl === undefined) return Debug.fail(); + const declaration = decl.parent; + + if (declaration) { + if (isFunctionLikeKind(declaration.kind)) { + addInPrefix(); + const signature = typeChecker.getSignatureFromDeclaration(declaration as SignatureDeclaration)!; // TODO: GH#18217 + if (declaration.kind === SyntaxKind.ConstructSignature) { + displayParts.push(keywordPart(SyntaxKind.NewKeyword)); displayParts.push(spacePart()); + } + else if (declaration.kind !== SyntaxKind.CallSignature && (declaration as SignatureDeclaration).name) { addFullSymbolName(declaration.symbol); - writeTypeParametersOfSymbol(declaration.symbol, sourceFile); } + addRange(displayParts, signatureToDisplayParts(typeChecker, signature, sourceFile, TypeFormatFlags.WriteTypeArgumentsOfSignature)); } - } - } - if (symbolFlags & SymbolFlags.EnumMember) { - symbolKind = ScriptElementKind.enumMemberElement; - addPrefixForAnyFunctionOrVar(symbol, "enum member"); - const declaration = symbol.declarations?.[0]; - if (declaration?.kind === SyntaxKind.EnumMember) { - const constantValue = typeChecker.getConstantValue(declaration as EnumMember); - if (constantValue !== undefined) { - displayParts.push(spacePart()); - displayParts.push(operatorPart(SyntaxKind.EqualsToken)); + else if (declaration.kind === SyntaxKind.TypeAliasDeclaration) { + // Type alias type parameter + // For example + // type list = T[]; // Both T will go through same code path + addInPrefix(); + displayParts.push(keywordPart(SyntaxKind.TypeKeyword)); displayParts.push(spacePart()); - displayParts.push(displayPart(getTextOfConstantValue(constantValue), - typeof constantValue === "number" ? SymbolDisplayPartKind.numericLiteral : SymbolDisplayPartKind.stringLiteral)); + addFullSymbolName(declaration.symbol); + writeTypeParametersOfSymbol(declaration.symbol, sourceFile); } } } - // don't use symbolFlags since getAliasedSymbol requires the flag on the symbol itself - if (symbol.flags & SymbolFlags.Alias) { - prefixNextMeaning(); - if (!hasAddedSymbolInfo) { - const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); - if (resolvedSymbol !== symbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) { - const resolvedNode = resolvedSymbol.declarations[0]; - const declarationName = getNameOfDeclaration(resolvedNode); - if (declarationName) { - const isExternalModuleDeclaration = - isModuleWithStringLiteralName(resolvedNode) && - hasSyntacticModifier(resolvedNode, ModifierFlags.Ambient); - const shouldUseAliasName = symbol.name !== "default" && !isExternalModuleDeclaration; - const resolvedInfo = getSymbolDisplayPartsDocumentationAndSymbolKind( - typeChecker, - resolvedSymbol, - getSourceFileOfNode(resolvedNode), - resolvedNode, - declarationName, - semanticMeaning, - shouldUseAliasName ? symbol : resolvedSymbol); - displayParts.push(...resolvedInfo.displayParts); - displayParts.push(lineBreakPart()); - documentationFromAlias = resolvedInfo.documentation; - tagsFromAlias = resolvedInfo.tags; - } - else { - documentationFromAlias = resolvedSymbol.getContextualDocumentationComment(resolvedNode, typeChecker); - tagsFromAlias = resolvedSymbol.getJsDocTags(typeChecker); - } + } + if (symbolFlags & SymbolFlags.EnumMember) { + symbolKind = ScriptElementKind.enumMemberElement; + addPrefixForAnyFunctionOrVar(symbol, "enum member"); + const declaration = symbol.declarations?.[0]; + if (declaration?.kind === SyntaxKind.EnumMember) { + const constantValue = typeChecker.getConstantValue(declaration as EnumMember); + if (constantValue !== undefined) { + displayParts.push(spacePart()); + displayParts.push(operatorPart(SyntaxKind.EqualsToken)); + displayParts.push(spacePart()); + displayParts.push(displayPart(getTextOfConstantValue(constantValue), + typeof constantValue === "number" ? SymbolDisplayPartKind.numericLiteral : SymbolDisplayPartKind.stringLiteral)); + } + } + } + // don't use symbolFlags since getAliasedSymbol requires the flag on the symbol itself + if (symbol.flags & SymbolFlags.Alias) { + prefixNextMeaning(); + if (!hasAddedSymbolInfo) { + const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); + if (resolvedSymbol !== symbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) { + const resolvedNode = resolvedSymbol.declarations[0]; + const declarationName = getNameOfDeclaration(resolvedNode); + if (declarationName) { + const isExternalModuleDeclaration = + isModuleWithStringLiteralName(resolvedNode) && + hasSyntacticModifier(resolvedNode, ModifierFlags.Ambient); + const shouldUseAliasName = symbol.name !== "default" && !isExternalModuleDeclaration; + const resolvedInfo = getSymbolDisplayPartsDocumentationAndSymbolKind( + typeChecker, + resolvedSymbol, + getSourceFileOfNode(resolvedNode), + resolvedNode, + declarationName, + semanticMeaning, + shouldUseAliasName ? symbol : resolvedSymbol); + displayParts.push(...resolvedInfo.displayParts); + displayParts.push(lineBreakPart()); + documentationFromAlias = resolvedInfo.documentation; + tagsFromAlias = resolvedInfo.tags; + } + else { + documentationFromAlias = resolvedSymbol.getContextualDocumentationComment(resolvedNode, typeChecker); + tagsFromAlias = resolvedSymbol.getJsDocTags(typeChecker); } } + } - if (symbol.declarations) { - switch (symbol.declarations[0].kind) { - case SyntaxKind.NamespaceExportDeclaration: - displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); - displayParts.push(spacePart()); - displayParts.push(keywordPart(SyntaxKind.NamespaceKeyword)); - break; - case SyntaxKind.ExportAssignment: - displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); - displayParts.push(spacePart()); - displayParts.push(keywordPart((symbol.declarations[0] as ExportAssignment).isExportEquals ? SyntaxKind.EqualsToken : SyntaxKind.DefaultKeyword)); - break; - case SyntaxKind.ExportSpecifier: - displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); - break; - default: - displayParts.push(keywordPart(SyntaxKind.ImportKeyword)); - } + if (symbol.declarations) { + switch (symbol.declarations[0].kind) { + case SyntaxKind.NamespaceExportDeclaration: + displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); + displayParts.push(spacePart()); + displayParts.push(keywordPart(SyntaxKind.NamespaceKeyword)); + break; + case SyntaxKind.ExportAssignment: + displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); + displayParts.push(spacePart()); + displayParts.push(keywordPart((symbol.declarations[0] as ExportAssignment).isExportEquals ? SyntaxKind.EqualsToken : SyntaxKind.DefaultKeyword)); + break; + case SyntaxKind.ExportSpecifier: + displayParts.push(keywordPart(SyntaxKind.ExportKeyword)); + break; + default: + displayParts.push(keywordPart(SyntaxKind.ImportKeyword)); } - displayParts.push(spacePart()); - addFullSymbolName(symbol); - forEach(symbol.declarations, declaration => { - if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) { - const importEqualsDeclaration = declaration as ImportEqualsDeclaration; - if (isExternalModuleImportEqualsDeclaration(importEqualsDeclaration)) { + } + displayParts.push(spacePart()); + addFullSymbolName(symbol); + forEach(symbol.declarations, declaration => { + if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) { + const importEqualsDeclaration = declaration as ImportEqualsDeclaration; + if (isExternalModuleImportEqualsDeclaration(importEqualsDeclaration)) { + displayParts.push(spacePart()); + displayParts.push(operatorPart(SyntaxKind.EqualsToken)); + displayParts.push(spacePart()); + displayParts.push(keywordPart(SyntaxKind.RequireKeyword)); + displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); + displayParts.push(displayPart(getTextOfNode(getExternalModuleImportEqualsDeclarationExpression(importEqualsDeclaration)), SymbolDisplayPartKind.stringLiteral)); + displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); + } + else { + const internalAliasSymbol = typeChecker.getSymbolAtLocation(importEqualsDeclaration.moduleReference); + if (internalAliasSymbol) { displayParts.push(spacePart()); displayParts.push(operatorPart(SyntaxKind.EqualsToken)); displayParts.push(spacePart()); - displayParts.push(keywordPart(SyntaxKind.RequireKeyword)); - displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - displayParts.push(displayPart(getTextOfNode(getExternalModuleImportEqualsDeclarationExpression(importEqualsDeclaration)), SymbolDisplayPartKind.stringLiteral)); - displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); - } - else { - const internalAliasSymbol = typeChecker.getSymbolAtLocation(importEqualsDeclaration.moduleReference); - if (internalAliasSymbol) { - displayParts.push(spacePart()); - displayParts.push(operatorPart(SyntaxKind.EqualsToken)); - displayParts.push(spacePart()); - addFullSymbolName(internalAliasSymbol, enclosingDeclaration); - } + addFullSymbolName(internalAliasSymbol, enclosingDeclaration); } - return true; } - }); - } - if (!hasAddedSymbolInfo) { - if (symbolKind !== ScriptElementKind.unknown) { - if (type) { - if (isThisExpression) { - prefixNextMeaning(); - displayParts.push(keywordPart(SyntaxKind.ThisKeyword)); + return true; + } + }); + } + if (!hasAddedSymbolInfo) { + if (symbolKind !== ScriptElementKind.unknown) { + if (type) { + if (isThisExpression) { + prefixNextMeaning(); + displayParts.push(keywordPart(SyntaxKind.ThisKeyword)); + } + else { + addPrefixForAnyFunctionOrVar(symbol, symbolKind); + } + // For properties, variables and local vars: show the type + if (symbolKind === ScriptElementKind.memberVariableElement || + symbolKind === ScriptElementKind.memberAccessorVariableElement || + symbolKind === ScriptElementKind.memberGetAccessorElement || + symbolKind === ScriptElementKind.memberSetAccessorElement || + symbolKind === ScriptElementKind.jsxAttribute || + symbolFlags & SymbolFlags.Variable || + symbolKind === ScriptElementKind.localVariableElement || + symbolKind === ScriptElementKind.indexSignatureElement || + isThisExpression) { + displayParts.push(punctuationPart(SyntaxKind.ColonToken)); + displayParts.push(spacePart()); + // If the type is type parameter, format it specially + if (type.symbol && type.symbol.flags & SymbolFlags.TypeParameter && symbolKind !== ScriptElementKind.indexSignatureElement) { + const typeParameterParts = mapToDisplayParts(writer => { + const param = typeChecker.typeParameterToDeclaration(type as TypeParameter, enclosingDeclaration, symbolDisplayNodeBuilderFlags)!; + getPrinter().writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); + }); + addRange(displayParts, typeParameterParts); } else { - addPrefixForAnyFunctionOrVar(symbol, symbolKind); + addRange(displayParts, typeToDisplayParts(typeChecker, type, enclosingDeclaration)); } - // For properties, variables and local vars: show the type - if (symbolKind === ScriptElementKind.memberVariableElement || - symbolKind === ScriptElementKind.memberAccessorVariableElement || - symbolKind === ScriptElementKind.memberGetAccessorElement || - symbolKind === ScriptElementKind.memberSetAccessorElement || - symbolKind === ScriptElementKind.jsxAttribute || - symbolFlags & SymbolFlags.Variable || - symbolKind === ScriptElementKind.localVariableElement || - symbolKind === ScriptElementKind.indexSignatureElement || - isThisExpression) { - displayParts.push(punctuationPart(SyntaxKind.ColonToken)); + if ((symbol as TransientSymbol).target && ((symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) { + const labelDecl = ((symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!; + Debug.assertNode(labelDecl.name, isIdentifier); displayParts.push(spacePart()); - // If the type is type parameter, format it specially - if (type.symbol && type.symbol.flags & SymbolFlags.TypeParameter && symbolKind !== ScriptElementKind.indexSignatureElement) { - const typeParameterParts = mapToDisplayParts(writer => { - const param = typeChecker.typeParameterToDeclaration(type as TypeParameter, enclosingDeclaration, symbolDisplayNodeBuilderFlags)!; - getPrinter().writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); - }); - addRange(displayParts, typeParameterParts); - } - else { - addRange(displayParts, typeToDisplayParts(typeChecker, type, enclosingDeclaration)); - } - if ((symbol as TransientSymbol).target && ((symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration) { - const labelDecl = ((symbol as TransientSymbol).target as TransientSymbol).tupleLabelDeclaration!; - Debug.assertNode(labelDecl.name, isIdentifier); - displayParts.push(spacePart()); - displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - displayParts.push(textPart(idText(labelDecl.name))); - displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); - } + displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); + displayParts.push(textPart(idText(labelDecl.name))); + displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); } - else if (symbolFlags & SymbolFlags.Function || - symbolFlags & SymbolFlags.Method || - symbolFlags & SymbolFlags.Constructor || - symbolFlags & SymbolFlags.Signature || - symbolFlags & SymbolFlags.Accessor || - symbolKind === ScriptElementKind.memberFunctionElement) { - const allSignatures = type.getNonNullableType().getCallSignatures(); - if (allSignatures.length) { - addSignatureDisplayParts(allSignatures[0], allSignatures); - hasMultipleSignatures = allSignatures.length > 1; - } + } + else if (symbolFlags & SymbolFlags.Function || + symbolFlags & SymbolFlags.Method || + symbolFlags & SymbolFlags.Constructor || + symbolFlags & SymbolFlags.Signature || + symbolFlags & SymbolFlags.Accessor || + symbolKind === ScriptElementKind.memberFunctionElement) { + const allSignatures = type.getNonNullableType().getCallSignatures(); + if (allSignatures.length) { + addSignatureDisplayParts(allSignatures[0], allSignatures); + hasMultipleSignatures = allSignatures.length > 1; } } } - else { - symbolKind = getSymbolKind(typeChecker, symbol, location); - } } - - if (documentation.length === 0 && !hasMultipleSignatures) { - documentation = symbol.getContextualDocumentationComment(enclosingDeclaration, typeChecker); + else { + symbolKind = getSymbolKind(typeChecker, symbol, location); } + } - if (documentation.length === 0 && symbolFlags & SymbolFlags.Property) { - // For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo` - // there documentation comments might be attached to the right hand side symbol of their declarations. - // The pattern of such special property access is that the parent symbol is the symbol of the file. - if (symbol.parent && symbol.declarations && forEach(symbol.parent.declarations, declaration => declaration.kind === SyntaxKind.SourceFile)) { - for (const declaration of symbol.declarations) { - if (!declaration.parent || declaration.parent.kind !== SyntaxKind.BinaryExpression) { - continue; - } + if (documentation.length === 0 && !hasMultipleSignatures) { + documentation = symbol.getContextualDocumentationComment(enclosingDeclaration, typeChecker); + } - const rhsSymbol = typeChecker.getSymbolAtLocation((declaration.parent as BinaryExpression).right); - if (!rhsSymbol) { - continue; - } + if (documentation.length === 0 && symbolFlags & SymbolFlags.Property) { + // For some special property access expressions like `exports.foo = foo` or `module.exports.foo = foo` + // there documentation comments might be attached to the right hand side symbol of their declarations. + // The pattern of such special property access is that the parent symbol is the symbol of the file. + if (symbol.parent && symbol.declarations && forEach(symbol.parent.declarations, declaration => declaration.kind === SyntaxKind.SourceFile)) { + for (const declaration of symbol.declarations) { + if (!declaration.parent || declaration.parent.kind !== SyntaxKind.BinaryExpression) { + continue; + } - documentation = rhsSymbol.getDocumentationComment(typeChecker); - tags = rhsSymbol.getJsDocTags(typeChecker); - if (documentation.length > 0) { - break; - } + const rhsSymbol = typeChecker.getSymbolAtLocation((declaration.parent as BinaryExpression).right); + if (!rhsSymbol) { + continue; } - } - } - if (documentation.length === 0 && isIdentifier(location) && symbol.valueDeclaration && isBindingElement(symbol.valueDeclaration)) { - const declaration = symbol.valueDeclaration; - const parent = declaration.parent; - if (isIdentifier(declaration.name) && isObjectBindingPattern(parent)) { - const name = getTextOfIdentifierOrLiteral(declaration.name); - const objectType = typeChecker.getTypeAtLocation(parent); - documentation = firstDefined(objectType.isUnion() ? objectType.types : [objectType], t => { - const prop = t.getProperty(name); - return prop ? prop.getDocumentationComment(typeChecker) : undefined; - }) || emptyArray; + documentation = rhsSymbol.getDocumentationComment(typeChecker); + tags = rhsSymbol.getJsDocTags(typeChecker); + if (documentation.length > 0) { + break; + } } } + } - if (tags.length === 0 && !hasMultipleSignatures) { - tags = symbol.getContextualJsDocTags(enclosingDeclaration, typeChecker); + if (documentation.length === 0 && isIdentifier(location) && symbol.valueDeclaration && isBindingElement(symbol.valueDeclaration)) { + const declaration = symbol.valueDeclaration; + const parent = declaration.parent; + if (isIdentifier(declaration.name) && isObjectBindingPattern(parent)) { + const name = getTextOfIdentifierOrLiteral(declaration.name); + const objectType = typeChecker.getTypeAtLocation(parent); + documentation = firstDefined(objectType.isUnion() ? objectType.types : [objectType], t => { + const prop = t.getProperty(name); + return prop ? prop.getDocumentationComment(typeChecker) : undefined; + }) || emptyArray; } + } - if (documentation.length === 0 && documentationFromAlias) { - documentation = documentationFromAlias; - } + if (tags.length === 0 && !hasMultipleSignatures) { + tags = symbol.getContextualJsDocTags(enclosingDeclaration, typeChecker); + } - if (tags.length === 0 && tagsFromAlias) { - tags = tagsFromAlias; - } + if (documentation.length === 0 && documentationFromAlias) { + documentation = documentationFromAlias; + } - return { displayParts, documentation, symbolKind, tags: tags.length === 0 ? undefined : tags }; + if (tags.length === 0 && tagsFromAlias) { + tags = tagsFromAlias; + } - function getPrinter() { - if (!printer) { - printer = createPrinter({ removeComments: true }); - } - return printer; - } + return { displayParts, documentation, symbolKind, tags: tags.length === 0 ? undefined : tags }; - function prefixNextMeaning() { - if (displayParts.length) { - displayParts.push(lineBreakPart()); - } - addAliasPrefixIfNecessary(); + function getPrinter() { + if (!printer) { + printer = createPrinter({ removeComments: true }); } + return printer; + } - function addAliasPrefixIfNecessary() { - if (alias) { - pushSymbolKind(ScriptElementKind.alias); - displayParts.push(spacePart()); - } + function prefixNextMeaning() { + if (displayParts.length) { + displayParts.push(lineBreakPart()); } + addAliasPrefixIfNecessary(); + } - function addInPrefix() { - displayParts.push(spacePart()); - displayParts.push(keywordPart(SyntaxKind.InKeyword)); + function addAliasPrefixIfNecessary() { + if (alias) { + pushSymbolKind(ScriptElementKind.alias); displayParts.push(spacePart()); } + } - function addFullSymbolName(symbolToDisplay: Symbol, enclosingDeclaration?: Node) { - let indexInfos; + function addInPrefix() { + displayParts.push(spacePart()); + displayParts.push(keywordPart(SyntaxKind.InKeyword)); + displayParts.push(spacePart()); + } - if (alias && symbolToDisplay === symbol) { - symbolToDisplay = alias; - } - if (symbolKind === ScriptElementKind.indexSignatureElement) { - indexInfos = typeChecker.getIndexInfosOfIndexSymbol(symbolToDisplay); - } + function addFullSymbolName(symbolToDisplay: Symbol, enclosingDeclaration?: Node) { + let indexInfos; - let fullSymbolDisplayParts: SymbolDisplayPart[] = []; - if (symbolToDisplay.flags & SymbolFlags.Signature && indexInfos) { - if (symbolToDisplay.parent) { - fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbolToDisplay.parent); - } - fullSymbolDisplayParts.push(punctuationPart(SyntaxKind.OpenBracketToken)); - //Needed to handle more than one type of index - indexInfos.forEach((info, i) => { - //Needed to handle template literals - fullSymbolDisplayParts.push(...typeToDisplayParts(typeChecker, info.keyType)); - if (i !== indexInfos.length - 1) { - fullSymbolDisplayParts.push(spacePart()); - fullSymbolDisplayParts.push(punctuationPart(SyntaxKind.BarToken)); - fullSymbolDisplayParts.push(spacePart()); - } - }); - fullSymbolDisplayParts.push(punctuationPart(SyntaxKind.CloseBracketToken)); - } - else { - fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbolToDisplay, enclosingDeclaration || sourceFile, /*meaning*/ undefined, - SymbolFormatFlags.WriteTypeParametersOrArguments | SymbolFormatFlags.UseOnlyExternalAliasing | SymbolFormatFlags.AllowAnyNodeKind); - } - addRange(displayParts, fullSymbolDisplayParts); - if (symbol.flags & SymbolFlags.Optional) { - displayParts.push(punctuationPart(SyntaxKind.QuestionToken)); - } + if (alias && symbolToDisplay === symbol) { + symbolToDisplay = alias; + } + if (symbolKind === ScriptElementKind.indexSignatureElement) { + indexInfos = typeChecker.getIndexInfosOfIndexSymbol(symbolToDisplay); } - function addPrefixForAnyFunctionOrVar(symbol: Symbol, symbolKind: string) { - prefixNextMeaning(); - if (symbolKind) { - pushSymbolKind(symbolKind); - if (symbol && !some(symbol.declarations, d => isArrowFunction(d) || (isFunctionExpression(d) || isClassExpression(d)) && !d.name)) { - displayParts.push(spacePart()); - addFullSymbolName(symbol); - } + let fullSymbolDisplayParts: SymbolDisplayPart[] = []; + if (symbolToDisplay.flags & SymbolFlags.Signature && indexInfos) { + if (symbolToDisplay.parent) { + fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbolToDisplay.parent); } + fullSymbolDisplayParts.push(punctuationPart(SyntaxKind.OpenBracketToken)); + //Needed to handle more than one type of index + indexInfos.forEach((info, i) => { + //Needed to handle template literals + fullSymbolDisplayParts.push(...typeToDisplayParts(typeChecker, info.keyType)); + if (i !== indexInfos.length - 1) { + fullSymbolDisplayParts.push(spacePart()); + fullSymbolDisplayParts.push(punctuationPart(SyntaxKind.BarToken)); + fullSymbolDisplayParts.push(spacePart()); + } + }); + fullSymbolDisplayParts.push(punctuationPart(SyntaxKind.CloseBracketToken)); + } + else { + fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbolToDisplay, enclosingDeclaration || sourceFile, /*meaning*/ undefined, + SymbolFormatFlags.WriteTypeParametersOrArguments | SymbolFormatFlags.UseOnlyExternalAliasing | SymbolFormatFlags.AllowAnyNodeKind); } + addRange(displayParts, fullSymbolDisplayParts); + if (symbol.flags & SymbolFlags.Optional) { + displayParts.push(punctuationPart(SyntaxKind.QuestionToken)); + } + } - function pushSymbolKind(symbolKind: string) { - switch (symbolKind) { - case ScriptElementKind.variableElement: - case ScriptElementKind.functionElement: - case ScriptElementKind.letElement: - case ScriptElementKind.constElement: - case ScriptElementKind.constructorImplementationElement: - displayParts.push(textOrKeywordPart(symbolKind)); - return; - default: - displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - displayParts.push(textOrKeywordPart(symbolKind)); - displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); - return; + function addPrefixForAnyFunctionOrVar(symbol: Symbol, symbolKind: string) { + prefixNextMeaning(); + if (symbolKind) { + pushSymbolKind(symbolKind); + if (symbol && !some(symbol.declarations, d => isArrowFunction(d) || (isFunctionExpression(d) || isClassExpression(d)) && !d.name)) { + displayParts.push(spacePart()); + addFullSymbolName(symbol); } } + } - function addSignatureDisplayParts(signature: Signature, allSignatures: readonly Signature[], flags = TypeFormatFlags.None) { - addRange(displayParts, signatureToDisplayParts(typeChecker, signature, enclosingDeclaration, flags | TypeFormatFlags.WriteTypeArgumentsOfSignature)); - if (allSignatures.length > 1) { - displayParts.push(spacePart()); + function pushSymbolKind(symbolKind: string) { + switch (symbolKind) { + case ScriptElementKind.variableElement: + case ScriptElementKind.functionElement: + case ScriptElementKind.letElement: + case ScriptElementKind.constElement: + case ScriptElementKind.constructorImplementationElement: + displayParts.push(textOrKeywordPart(symbolKind)); + return; + default: displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - displayParts.push(operatorPart(SyntaxKind.PlusToken)); - displayParts.push(displayPart((allSignatures.length - 1).toString(), SymbolDisplayPartKind.numericLiteral)); - displayParts.push(spacePart()); - displayParts.push(textPart(allSignatures.length === 2 ? "overload" : "overloads")); + displayParts.push(textOrKeywordPart(symbolKind)); displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); - } - documentation = signature.getDocumentationComment(typeChecker); - tags = signature.getJsDocTags(); - - if (allSignatures.length > 1 && documentation.length === 0 && tags.length === 0) { - documentation = allSignatures[0].getDocumentationComment(typeChecker); - tags = allSignatures[0].getJsDocTags().filter(tag => tag.name !== "deprecated"); // should only include @deprecated JSDoc tag on the first overload (#49368) - } + return; + } + } + function addSignatureDisplayParts(signature: Signature, allSignatures: readonly Signature[], flags = TypeFormatFlags.None) { + addRange(displayParts, signatureToDisplayParts(typeChecker, signature, enclosingDeclaration, flags | TypeFormatFlags.WriteTypeArgumentsOfSignature)); + if (allSignatures.length > 1) { + displayParts.push(spacePart()); + displayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); + displayParts.push(operatorPart(SyntaxKind.PlusToken)); + displayParts.push(displayPart((allSignatures.length - 1).toString(), SymbolDisplayPartKind.numericLiteral)); + displayParts.push(spacePart()); + displayParts.push(textPart(allSignatures.length === 2 ? "overload" : "overloads")); + displayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); } + documentation = signature.getDocumentationComment(typeChecker); + tags = signature.getJsDocTags(); - function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined) { - const typeParameterParts = mapToDisplayParts(writer => { - const params = typeChecker.symbolToTypeParameterDeclarations(symbol, enclosingDeclaration, symbolDisplayNodeBuilderFlags); - getPrinter().writeList(ListFormat.TypeParameters, params, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); - }); - addRange(displayParts, typeParameterParts); + if (allSignatures.length > 1 && documentation.length === 0 && tags.length === 0) { + documentation = allSignatures[0].getDocumentationComment(typeChecker); + tags = allSignatures[0].getJsDocTags().filter(tag => tag.name !== "deprecated"); // should only include @deprecated JSDoc tag on the first overload (#49368) } + } - function isLocalVariableOrFunction(symbol: Symbol) { - if (symbol.parent) { - return false; // This is exported symbol + function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined) { + const typeParameterParts = mapToDisplayParts(writer => { + const params = typeChecker.symbolToTypeParameterDeclarations(symbol, enclosingDeclaration, symbolDisplayNodeBuilderFlags); + getPrinter().writeList(ListFormat.TypeParameters, params, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); + }); + addRange(displayParts, typeParameterParts); + } +} + +function isLocalVariableOrFunction(symbol: Symbol) { + if (symbol.parent) { + return false; // This is exported symbol + } + + return forEach(symbol.declarations, declaration => { + // Function expressions are local + if (declaration.kind === SyntaxKind.FunctionExpression) { + return true; } - return forEach(symbol.declarations, declaration => { - // Function expressions are local - if (declaration.kind === SyntaxKind.FunctionExpression) { - return true; - } + if (declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.FunctionDeclaration) { + return false; + } - if (declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.FunctionDeclaration) { + // If the parent is not sourceFile or module block it is local variable + for (let parent = declaration.parent; !isFunctionBlock(parent); parent = parent.parent) { + // Reached source file or module block + if (parent.kind === SyntaxKind.SourceFile || parent.kind === SyntaxKind.ModuleBlock) { return false; } + } - // If the parent is not sourceFile or module block it is local variable - for (let parent = declaration.parent; !isFunctionBlock(parent); parent = parent.parent) { - // Reached source file or module block - if (parent.kind === SyntaxKind.SourceFile || parent.kind === SyntaxKind.ModuleBlock) { - return false; - } - } - - // parent is in function block - return true; - }); - } + // parent is in function block + return true; + }); } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 23f15afe8f8ef..36c1e11d525e1 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -1,1619 +1,1670 @@ -/* @internal */ -namespace ts.textChanges { +import { + addToSeen, ArrowFunction, BindingElement, CharacterCodes, ClassElement, ClassExpression, ClassLikeDeclaration, + CommentRange, concatenate, ConstructorDeclaration, contains, createNodeFactory, createPrinter, createRange, + createSourceFile, createTextChange, createTextRangeFromSpan, createTextSpan, createTextSpanFromRange, + createTextWriter, Debug, DeclarationStatement, EmitHint, EmitTextWriter, endsWith, Expression, factory, + FileTextChanges, filter, find, findChildOfKind, findLastIndex, findNextToken, findPrecedingToken, first, + firstOrUndefined, flatMap, flatMapToMutable, formatting, FunctionDeclaration, FunctionExpression, getAncestor, + getFirstNonSpaceCharacterPosition, getFormatCodeSettingsForWriting, getJSDocCommentRanges, getLeadingCommentRanges, + getLineAndCharacterOfPosition, getLineOfLocalPosition, getLineStartPositionForPosition, getNewLineKind, + getNewLineOrDefaultFromHost, getNodeId, getPrecedingNonSpaceCharacterPosition, getScriptKindFromFileName, + getShebang, getStartPositionOfLine, getTokenAtPosition, getTouchingToken, getTrailingCommentRanges, group, HasJSDoc, + hasJSDocNodes, ImportClause, ImportSpecifier, indexOfNode, InterfaceDeclaration, intersperse, isAnyImportSyntax, + isArray, isArrowFunction, isCallExpression, isClassElement, isClassOrTypeElement, isExpressionStatement, + isFunctionDeclaration, isFunctionExpression, isFunctionLike, isIdentifier, isImportClause, isImportDeclaration, + isImportSpecifier, isInComment, isInJSXText, isInString, isInTemplateString, isInterfaceDeclaration, + isJsonSourceFile, isLineBreak, isNamedImports, isObjectLiteralExpression, isParameter, isPinnedComment, + isPrologueDirective, isPropertyDeclaration, isPropertySignature, isRecognizedTripleSlashComment, isStatement, + isStatementButNotDeclaration, isString, isStringLiteral, isSuperCall, isVariableDeclaration, isWhiteSpaceLike, + isWhiteSpaceSingleLine, JSDoc, JSDocComment, JSDocParameterTag, JSDocReturnTag, JSDocTag, JSDocTypeTag, + LanguageServiceHost, last, lastOrUndefined, length, Map, mapDefined, MethodSignature, Modifier, NamedImportBindings, + NamedImports, NamespaceImport, Node, NodeArray, NodeFactoryFlags, nodeIsSynthesized, nullTransformationContext, + ObjectLiteralElementLike, ObjectLiteralExpression, ParameterDeclaration, positionsAreOnSameLine, PrintHandlers, + PrologueDirective, PropertyAssignment, PropertyDeclaration, PropertySignature, rangeContainsPosition, + rangeContainsRangeExclusive, rangeOfNode, rangeOfTypeParameters, rangeStartPositionsAreOnSameLine, removeSuffix, + ScriptKind, ScriptTarget, Set, setTextRangePosEnd, SignatureDeclaration, singleOrUndefined, skipTrivia, SourceFile, + SourceFileLike, stableSort, Statement, stringContainsAt, Symbol, SyntaxKind, TextChange, TextRange, textSpanEnd, + Token, tokenToString, TransformationContext, TypeLiteralNode, TypeNode, TypeParameterDeclaration, UserPreferences, + VariableDeclaration, VariableStatement, visitEachChild, visitNodes, Visitor, +} from "./_namespaces/ts"; + +/** + * Currently for simplicity we store recovered positions on the node itself. + * It can be changed to side-table later if we decide that current design is too invasive. + */ +function getPos(n: TextRange): number { + const result = (n as any).__pos; + Debug.assert(typeof result === "number"); + return result; +} + +function setPos(n: TextRange, pos: number): void { + Debug.assert(typeof pos === "number"); + (n as any).__pos = pos; +} + +function getEnd(n: TextRange): number { + const result = (n as any).__end; + Debug.assert(typeof result === "number"); + return result; +} + +function setEnd(n: TextRange, end: number): void { + Debug.assert(typeof end === "number"); + (n as any).__end = end; +} + +/** @internal */ +export interface ConfigurableStart { + leadingTriviaOption?: LeadingTriviaOption; +} +/** @internal */ +export interface ConfigurableEnd { + trailingTriviaOption?: TrailingTriviaOption; +} +/** @internal */ +export enum LeadingTriviaOption { + /** Exclude all leading trivia (use getStart()) */ + Exclude, + /** Include leading trivia and, + * if there are no line breaks between the node and the previous token, + * include all trivia between the node and the previous token + */ + IncludeAll, + /** + * Include attached JSDoc comments + */ + JSDoc, /** - * Currently for simplicity we store recovered positions on the node itself. - * It can be changed to side-table later if we decide that current design is too invasive. + * Only delete trivia on the same line as getStart(). + * Used to avoid deleting leading comments */ - function getPos(n: TextRange): number { - const result = (n as any).__pos; - Debug.assert(typeof result === "number"); - return result; + StartLine, +} + +/** @internal */ +export enum TrailingTriviaOption { + /** Exclude all trailing trivia (use getEnd()) */ + Exclude, + /** Doesn't include whitespace, but does strip comments */ + ExcludeWhitespace, + /** Include trailing trivia */ + Include, +} + +function skipWhitespacesAndLineBreaks(text: string, start: number) { + return skipTrivia(text, start, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); +} + +function hasCommentsBeforeLineBreak(text: string, start: number) { + let i = start; + while (i < text.length) { + const ch = text.charCodeAt(i); + if (isWhiteSpaceSingleLine(ch)) { + i++; + continue; + } + return ch === CharacterCodes.slash; } + return false; +} + +/** + * Usually node.pos points to a position immediately after the previous token. + * If this position is used as a beginning of the span to remove - it might lead to removing the trailing trivia of the previous node, i.e: + * const x; // this is x + * ^ - pos for the next variable declaration will point here + * const y; // this is y + * ^ - end for previous variable declaration + * Usually leading trivia of the variable declaration 'y' should not include trailing trivia (whitespace, comment 'this is x' and newline) from the preceding + * variable declaration and trailing trivia for 'y' should include (whitespace, comment 'this is y', newline). + * By default when removing nodes we adjust start and end positions to respect specification of the trivia above. + * If pos\end should be interpreted literally (that is, withouth including leading and trailing trivia), `leadingTriviaOption` should be set to `LeadingTriviaOption.Exclude` + * and `trailingTriviaOption` to `TrailingTriviaOption.Exclude`. + * + * @internal + */ +export interface ConfigurableStartEnd extends ConfigurableStart, ConfigurableEnd {} + +const useNonAdjustedPositions: ConfigurableStartEnd = { + leadingTriviaOption: LeadingTriviaOption.Exclude, + trailingTriviaOption: TrailingTriviaOption.Exclude, +}; + +/** @internal */ +export interface InsertNodeOptions { + /** + * Text to be inserted before the new node + */ + prefix?: string; + /** + * Text to be inserted after the new node + */ + suffix?: string; + /** + * Text of inserted node will be formatted with this indentation, otherwise indentation will be inferred from the old node + */ + indentation?: number; + /** + * Text of inserted node will be formatted with this delta, otherwise delta will be inferred from the new node kind + */ + delta?: number; +} + +/** @internal */ +export interface ReplaceWithMultipleNodesOptions extends InsertNodeOptions { + readonly joiner?: string; +} + +enum ChangeKind { + Remove, + ReplaceWithSingleNode, + ReplaceWithMultipleNodes, + Text, +} + +type Change = ReplaceWithSingleNode | ReplaceWithMultipleNodes | RemoveNode | ChangeText; + +interface BaseChange { + readonly sourceFile: SourceFile; + readonly range: TextRange; +} + +/** @internal */ +export interface ChangeNodeOptions extends ConfigurableStartEnd, InsertNodeOptions {} +interface ReplaceWithSingleNode extends BaseChange { + readonly kind: ChangeKind.ReplaceWithSingleNode; + readonly node: Node; + readonly options?: InsertNodeOptions; +} + +interface RemoveNode extends BaseChange { + readonly kind: ChangeKind.Remove; + readonly node?: never; + readonly options?: never; +} + +interface ReplaceWithMultipleNodes extends BaseChange { + readonly kind: ChangeKind.ReplaceWithMultipleNodes; + readonly nodes: readonly Node[]; + readonly options?: ReplaceWithMultipleNodesOptions; +} + +interface ChangeText extends BaseChange { + readonly kind: ChangeKind.Text; + readonly text: string; +} + +function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd): TextRange { + return { pos: getAdjustedStartPosition(sourceFile, startNode, options), end: getAdjustedEndPosition(sourceFile, endNode, options) }; +} - function setPos(n: TextRange, pos: number): void { - Debug.assert(typeof pos === "number"); - (n as any).__pos = pos; +function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd, hasTrailingComment = false) { + const { leadingTriviaOption } = options; + if (leadingTriviaOption === LeadingTriviaOption.Exclude) { + return node.getStart(sourceFile); + } + if (leadingTriviaOption === LeadingTriviaOption.StartLine) { + const startPos = node.getStart(sourceFile); + const pos = getLineStartPositionForPosition(startPos, sourceFile); + return rangeContainsPosition(node, pos) ? pos : startPos; + } + if (leadingTriviaOption === LeadingTriviaOption.JSDoc) { + const JSDocComments = getJSDocCommentRanges(node, sourceFile.text); + if (JSDocComments?.length) { + return getLineStartPositionForPosition(JSDocComments[0].pos, sourceFile); + } + } + const fullStart = node.getFullStart(); + const start = node.getStart(sourceFile); + if (fullStart === start) { + return start; + } + const fullStartLine = getLineStartPositionForPosition(fullStart, sourceFile); + const startLine = getLineStartPositionForPosition(start, sourceFile); + if (startLine === fullStartLine) { + // full start and start of the node are on the same line + // a, b; + // ^ ^ + // | start + // fullstart + // when b is replaced - we usually want to keep the leading trvia + // when b is deleted - we delete it + return leadingTriviaOption === LeadingTriviaOption.IncludeAll ? fullStart : start; } - function getEnd(n: TextRange): number { - const result = (n as any).__end; - Debug.assert(typeof result === "number"); - return result; + // if node has a trailing comments, use comment end position as the text has already been included. + if (hasTrailingComment) { + // Check first for leading comments as if the node is the first import, we want to exclude the trivia; + // otherwise we get the trailing comments. + const comment = getLeadingCommentRanges(sourceFile.text, fullStart)?.[0] || getTrailingCommentRanges(sourceFile.text, fullStart)?.[0]; + if (comment) { + return skipTrivia(sourceFile.text, comment.end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ true); + } } - function setEnd(n: TextRange, end: number): void { - Debug.assert(typeof end === "number"); - (n as any).__end = end; + // get start position of the line following the line that contains fullstart position + // (but only if the fullstart isn't the very beginning of the file) + const nextLineStart = fullStart > 0 ? 1 : 0; + let adjustedStartPosition = getStartPositionOfLine(getLineOfLocalPosition(sourceFile, fullStartLine) + nextLineStart, sourceFile); + // skip whitespaces/newlines + adjustedStartPosition = skipWhitespacesAndLineBreaks(sourceFile.text, adjustedStartPosition); + return getStartPositionOfLine(getLineOfLocalPosition(sourceFile, adjustedStartPosition), sourceFile); +} + +/** Return the end position of a multiline comment of it is on another line; otherwise returns `undefined`; */ +function getEndPositionOfMultilineTrailingComment(sourceFile: SourceFile, node: Node, options: ConfigurableEnd): number | undefined { + const { end } = node; + const { trailingTriviaOption } = options; + if (trailingTriviaOption === TrailingTriviaOption.Include) { + // If the trailing comment is a multiline comment that extends to the next lines, + // return the end of the comment and track it for the next nodes to adjust. + const comments = getTrailingCommentRanges(sourceFile.text, end); + if (comments) { + const nodeEndLine = getLineOfLocalPosition(sourceFile, node.end); + for (const comment of comments) { + // Single line can break the loop as trivia will only be this line. + // Comments on subsequest lines are also ignored. + if (comment.kind === SyntaxKind.SingleLineCommentTrivia || getLineOfLocalPosition(sourceFile, comment.pos) > nodeEndLine) { + break; + } + + // Get the end line of the comment and compare against the end line of the node. + // If the comment end line position and the multiline comment extends to multiple lines, + // then is safe to return the end position. + const commentEndLine = getLineOfLocalPosition(sourceFile, comment.end); + if (commentEndLine > nodeEndLine) { + return skipTrivia(sourceFile.text, comment.end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ true); + } + } + } } - export interface ConfigurableStart { - leadingTriviaOption?: LeadingTriviaOption; + return undefined; +} + +function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd): number { + const { end } = node; + const { trailingTriviaOption } = options; + if (trailingTriviaOption === TrailingTriviaOption.Exclude) { + return end; } - export interface ConfigurableEnd { - trailingTriviaOption?: TrailingTriviaOption; + if (trailingTriviaOption === TrailingTriviaOption.ExcludeWhitespace) { + const comments = concatenate(getTrailingCommentRanges(sourceFile.text, end), getLeadingCommentRanges(sourceFile.text, end)); + const realEnd = comments?.[comments.length - 1]?.end; + if (realEnd) { + return realEnd; + } + return end; } - export enum LeadingTriviaOption { - /** Exclude all leading trivia (use getStart()) */ - Exclude, - /** Include leading trivia and, - * if there are no line breaks between the node and the previous token, - * include all trivia between the node and the previous token - */ - IncludeAll, - /** - * Include attached JSDoc comments - */ - JSDoc, - /** - * Only delete trivia on the same line as getStart(). - * Used to avoid deleting leading comments - */ - StartLine, + const multilineEndPosition = getEndPositionOfMultilineTrailingComment(sourceFile, node, options); + if (multilineEndPosition) { + return multilineEndPosition; } - export enum TrailingTriviaOption { - /** Exclude all trailing trivia (use getEnd()) */ - Exclude, - /** Doesn't include whitespace, but does strip comments */ - ExcludeWhitespace, - /** Include trailing trivia */ - Include, + const newEnd = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true); + + return newEnd !== end && (trailingTriviaOption === TrailingTriviaOption.Include || isLineBreak(sourceFile.text.charCodeAt(newEnd - 1))) + ? newEnd + : end; +} + +/** + * Checks if 'candidate' argument is a legal separator in the list that contains 'node' as an element + */ +function isSeparator(node: Node, candidate: Node | undefined): candidate is Token { + return !!candidate && !!node.parent && (candidate.kind === SyntaxKind.CommaToken || (candidate.kind === SyntaxKind.SemicolonToken && node.parent.kind === SyntaxKind.ObjectLiteralExpression)); +} + +/** @internal */ +export interface TextChangesContext { + host: LanguageServiceHost; + formatContext: formatting.FormatContext; + preferences: UserPreferences; +} + +/** @internal */ +export type TypeAnnotatable = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature; + +/** @internal */ +export type ThisTypeAnnotatable = FunctionDeclaration | FunctionExpression; + +/** @internal */ +export function isThisTypeAnnotatable(containingFunction: SignatureDeclaration): containingFunction is ThisTypeAnnotatable { + return isFunctionExpression(containingFunction) || isFunctionDeclaration(containingFunction); +} + +/** @internal */ +export class ChangeTracker { + private readonly changes: Change[] = []; + private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: readonly (Statement | SyntaxKind.NewLineTrivia)[] }[] = []; + private readonly classesWithNodesInsertedAtStart = new Map(); // Set implemented as Map + private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray }[] = []; + + public static fromContext(context: TextChangesContext): ChangeTracker { + return new ChangeTracker(getNewLineOrDefaultFromHost(context.host, context.formatContext.options), context.formatContext); } - function skipWhitespacesAndLineBreaks(text: string, start: number) { - return skipTrivia(text, start, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + public static with(context: TextChangesContext, cb: (tracker: ChangeTracker) => void): FileTextChanges[] { + const tracker = ChangeTracker.fromContext(context); + cb(tracker); + return tracker.getChanges(); } - function hasCommentsBeforeLineBreak(text: string, start: number) { - let i = start; - while (i < text.length) { - const ch = text.charCodeAt(i); - if (isWhiteSpaceSingleLine(ch)) { - i++; - continue; - } - return ch === CharacterCodes.slash; + /** Public for tests only. Other callers should use `ChangeTracker.with`. */ + constructor(private readonly newLineCharacter: string, private readonly formatContext: formatting.FormatContext) {} + + public pushRaw(sourceFile: SourceFile, change: FileTextChanges) { + Debug.assertEqual(sourceFile.fileName, change.fileName); + for (const c of change.textChanges) { + this.changes.push({ + kind: ChangeKind.Text, + sourceFile, + text: c.newText, + range: createTextRangeFromSpan(c.span), + }); } - return false; } - /** - * Usually node.pos points to a position immediately after the previous token. - * If this position is used as a beginning of the span to remove - it might lead to removing the trailing trivia of the previous node, i.e: - * const x; // this is x - * ^ - pos for the next variable declaration will point here - * const y; // this is y - * ^ - end for previous variable declaration - * Usually leading trivia of the variable declaration 'y' should not include trailing trivia (whitespace, comment 'this is x' and newline) from the preceding - * variable declaration and trailing trivia for 'y' should include (whitespace, comment 'this is y', newline). - * By default when removing nodes we adjust start and end positions to respect specification of the trivia above. - * If pos\end should be interpreted literally (that is, withouth including leading and trailing trivia), `leadingTriviaOption` should be set to `LeadingTriviaOption.Exclude` - * and `trailingTriviaOption` to `TrailingTriviaOption.Exclude`. - */ - export interface ConfigurableStartEnd extends ConfigurableStart, ConfigurableEnd {} - - const useNonAdjustedPositions: ConfigurableStartEnd = { - leadingTriviaOption: LeadingTriviaOption.Exclude, - trailingTriviaOption: TrailingTriviaOption.Exclude, - }; - - export interface InsertNodeOptions { - /** - * Text to be inserted before the new node - */ - prefix?: string; - /** - * Text to be inserted after the new node - */ - suffix?: string; - /** - * Text of inserted node will be formatted with this indentation, otherwise indentation will be inferred from the old node - */ - indentation?: number; - /** - * Text of inserted node will be formatted with this delta, otherwise delta will be inferred from the new node kind - */ - delta?: number; + public deleteRange(sourceFile: SourceFile, range: TextRange): void { + this.changes.push({ kind: ChangeKind.Remove, sourceFile, range }); } - export interface ReplaceWithMultipleNodesOptions extends InsertNodeOptions { - readonly joiner?: string; + delete(sourceFile: SourceFile, node: Node | NodeArray): void { + this.deletedNodes.push({ sourceFile, node }); } - enum ChangeKind { - Remove, - ReplaceWithSingleNode, - ReplaceWithMultipleNodes, - Text, + /** Stop! Consider using `delete` instead, which has logic for deleting nodes from delimited lists. */ + public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { + this.deleteRange(sourceFile, getAdjustedRange(sourceFile, node, node, options)); } - type Change = ReplaceWithSingleNode | ReplaceWithMultipleNodes | RemoveNode | ChangeText; + public deleteNodes(sourceFile: SourceFile, nodes: readonly Node[], options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }, hasTrailingComment: boolean): void { + // When deleting multiple nodes we need to track if the end position is including multiline trailing comments. + for (const node of nodes) { + const pos = getAdjustedStartPosition(sourceFile, node, options, hasTrailingComment); + const end = getAdjustedEndPosition(sourceFile, node, options); - interface BaseChange { - readonly sourceFile: SourceFile; - readonly range: TextRange; - } + this.deleteRange(sourceFile, { pos, end }); - export interface ChangeNodeOptions extends ConfigurableStartEnd, InsertNodeOptions {} - interface ReplaceWithSingleNode extends BaseChange { - readonly kind: ChangeKind.ReplaceWithSingleNode; - readonly node: Node; - readonly options?: InsertNodeOptions; + hasTrailingComment = !!getEndPositionOfMultilineTrailingComment(sourceFile, node, options); + } } - interface RemoveNode extends BaseChange { - readonly kind: ChangeKind.Remove; - readonly node?: never; - readonly options?: never; + public deleteModifier(sourceFile: SourceFile, modifier: Modifier): void { + this.deleteRange(sourceFile, { pos: modifier.getStart(sourceFile), end: skipTrivia(sourceFile.text, modifier.end, /*stopAfterLineBreak*/ true) }); } - interface ReplaceWithMultipleNodes extends BaseChange { - readonly kind: ChangeKind.ReplaceWithMultipleNodes; - readonly nodes: readonly Node[]; - readonly options?: ReplaceWithMultipleNodesOptions; + public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options); + const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); + this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); } - interface ChangeText extends BaseChange { - readonly kind: ChangeKind.Text; - readonly text: string; + public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options); + const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options); + this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); } - function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd): TextRange { - return { pos: getAdjustedStartPosition(sourceFile, startNode, options), end: getAdjustedEndPosition(sourceFile, endNode, options) }; + public replaceRange(sourceFile: SourceFile, range: TextRange, newNode: Node, options: InsertNodeOptions = {}): void { + this.changes.push({ kind: ChangeKind.ReplaceWithSingleNode, sourceFile, range, options, node: newNode }); } - function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd, hasTrailingComment = false) { - const { leadingTriviaOption } = options; - if (leadingTriviaOption === LeadingTriviaOption.Exclude) { - return node.getStart(sourceFile); - } - if (leadingTriviaOption === LeadingTriviaOption.StartLine) { - const startPos = node.getStart(sourceFile); - const pos = getLineStartPositionForPosition(startPos, sourceFile); - return rangeContainsPosition(node, pos) ? pos : startPos; - } - if (leadingTriviaOption === LeadingTriviaOption.JSDoc) { - const JSDocComments = getJSDocCommentRanges(node, sourceFile.text); - if (JSDocComments?.length) { - return getLineStartPositionForPosition(JSDocComments[0].pos, sourceFile); - } - } - const fullStart = node.getFullStart(); - const start = node.getStart(sourceFile); - if (fullStart === start) { - return start; - } - const fullStartLine = getLineStartPositionForPosition(fullStart, sourceFile); - const startLine = getLineStartPositionForPosition(start, sourceFile); - if (startLine === fullStartLine) { - // full start and start of the node are on the same line - // a, b; - // ^ ^ - // | start - // fullstart - // when b is replaced - we usually want to keep the leading trvia - // when b is deleted - we delete it - return leadingTriviaOption === LeadingTriviaOption.IncludeAll ? fullStart : start; - } - - // if node has a trailing comments, use comment end position as the text has already been included. - if (hasTrailingComment) { - // Check first for leading comments as if the node is the first import, we want to exclude the trivia; - // otherwise we get the trailing comments. - const comment = getLeadingCommentRanges(sourceFile.text, fullStart)?.[0] || getTrailingCommentRanges(sourceFile.text, fullStart)?.[0]; - if (comment) { - return skipTrivia(sourceFile.text, comment.end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ true); - } - } + public replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options: ChangeNodeOptions = useNonAdjustedPositions): void { + this.replaceRange(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, options), newNode, options); + } - // get start position of the line following the line that contains fullstart position - // (but only if the fullstart isn't the very beginning of the file) - const nextLineStart = fullStart > 0 ? 1 : 0; - let adjustedStartPosition = getStartPositionOfLine(getLineOfLocalPosition(sourceFile, fullStartLine) + nextLineStart, sourceFile); - // skip whitespaces/newlines - adjustedStartPosition = skipWhitespacesAndLineBreaks(sourceFile.text, adjustedStartPosition); - return getStartPositionOfLine(getLineOfLocalPosition(sourceFile, adjustedStartPosition), sourceFile); - } - - /** Return the end position of a multiline comment of it is on another line; otherwise returns `undefined`; */ - function getEndPositionOfMultilineTrailingComment(sourceFile: SourceFile, node: Node, options: ConfigurableEnd): number | undefined { - const { end } = node; - const { trailingTriviaOption } = options; - if (trailingTriviaOption === TrailingTriviaOption.Include) { - // If the trailing comment is a multiline comment that extends to the next lines, - // return the end of the comment and track it for the next nodes to adjust. - const comments = getTrailingCommentRanges(sourceFile.text, end); - if (comments) { - const nodeEndLine = getLineOfLocalPosition(sourceFile, node.end); - for (const comment of comments) { - // Single line can break the loop as trivia will only be this line. - // Comments on subsequest lines are also ignored. - if (comment.kind === SyntaxKind.SingleLineCommentTrivia || getLineOfLocalPosition(sourceFile, comment.pos) > nodeEndLine) { - break; - } - - // Get the end line of the comment and compare against the end line of the node. - // If the comment end line position and the multiline comment extends to multiple lines, - // then is safe to return the end position. - const commentEndLine = getLineOfLocalPosition(sourceFile, comment.end); - if (commentEndLine > nodeEndLine) { - return skipTrivia(sourceFile.text, comment.end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ true); - } - } - } - } + public replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options: ChangeNodeOptions = useNonAdjustedPositions): void { + this.replaceRange(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNode, options); + } - return undefined; + private replaceRangeWithNodes(sourceFile: SourceFile, range: TextRange, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions & ConfigurableStartEnd = {}): void { + this.changes.push({ kind: ChangeKind.ReplaceWithMultipleNodes, sourceFile, range, options, nodes: newNodes }); } - function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd): number { - const { end } = node; - const { trailingTriviaOption } = options; - if (trailingTriviaOption === TrailingTriviaOption.Exclude) { - return end; - } - if (trailingTriviaOption === TrailingTriviaOption.ExcludeWhitespace) { - const comments = concatenate(getTrailingCommentRanges(sourceFile.text, end), getLeadingCommentRanges(sourceFile.text, end)); - const realEnd = comments?.[comments.length - 1]?.end; - if (realEnd) { - return realEnd; - } - return end; - } + public replaceNodeWithNodes(sourceFile: SourceFile, oldNode: Node, newNodes: readonly Node[], options: ChangeNodeOptions = useNonAdjustedPositions): void { + this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, options), newNodes, options); + } - const multilineEndPosition = getEndPositionOfMultilineTrailingComment(sourceFile, node, options); - if (multilineEndPosition) { - return multilineEndPosition; - } + public replaceNodeWithText(sourceFile: SourceFile, oldNode: Node, text: string): void { + this.replaceRangeWithText(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, useNonAdjustedPositions), text); + } - const newEnd = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true); + public replaceNodeRangeWithNodes(sourceFile: SourceFile, startNode: Node, endNode: Node, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions & ConfigurableStartEnd = useNonAdjustedPositions): void { + this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNodes, options); + } - return newEnd !== end && (trailingTriviaOption === TrailingTriviaOption.Include || isLineBreak(sourceFile.text.charCodeAt(newEnd - 1))) - ? newEnd - : end; + public nodeHasTrailingComment(sourceFile: SourceFile, oldNode: Node, configurableEnd: ConfigurableEnd = useNonAdjustedPositions): boolean { + return !!getEndPositionOfMultilineTrailingComment(sourceFile, oldNode, configurableEnd); } - /** - * Checks if 'candidate' argument is a legal separator in the list that contains 'node' as an element - */ - function isSeparator(node: Node, candidate: Node | undefined): candidate is Token { - return !!candidate && !!node.parent && (candidate.kind === SyntaxKind.CommaToken || (candidate.kind === SyntaxKind.SemicolonToken && node.parent.kind === SyntaxKind.ObjectLiteralExpression)); + private nextCommaToken(sourceFile: SourceFile, node: Node): Node | undefined { + const next = findNextToken(node, node.parent, sourceFile); + return next && next.kind === SyntaxKind.CommaToken ? next : undefined; } - export interface TextChangesContext { - host: LanguageServiceHost; - formatContext: formatting.FormatContext; - preferences: UserPreferences; + public replacePropertyAssignment(sourceFile: SourceFile, oldNode: PropertyAssignment, newNode: PropertyAssignment): void { + const suffix = this.nextCommaToken(sourceFile, oldNode) ? "" : ("," + this.newLineCharacter); + this.replaceNode(sourceFile, oldNode, newNode, { suffix }); } - export type TypeAnnotatable = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature; + public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}): void { + this.replaceRange(sourceFile, createRange(pos), newNode, options); + } - export type ThisTypeAnnotatable = FunctionDeclaration | FunctionExpression; + private insertNodesAt(sourceFile: SourceFile, pos: number, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions = {}): void { + this.replaceRangeWithNodes(sourceFile, createRange(pos), newNodes, options); + } - export function isThisTypeAnnotatable(containingFunction: SignatureDeclaration): containingFunction is ThisTypeAnnotatable { - return isFunctionExpression(containingFunction) || isFunctionDeclaration(containingFunction); + public insertNodeAtTopOfFile(sourceFile: SourceFile, newNode: Statement, blankLineBetween: boolean): void { + this.insertAtTopOfFile(sourceFile, newNode, blankLineBetween); } - export class ChangeTracker { - private readonly changes: Change[] = []; - private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: readonly (Statement | SyntaxKind.NewLineTrivia)[] }[] = []; - private readonly classesWithNodesInsertedAtStart = new Map(); // Set implemented as Map - private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray }[] = []; + public insertNodesAtTopOfFile(sourceFile: SourceFile, newNodes: readonly Statement[], blankLineBetween: boolean): void { + this.insertAtTopOfFile(sourceFile, newNodes, blankLineBetween); + } - public static fromContext(context: TextChangesContext): ChangeTracker { - return new ChangeTracker(getNewLineOrDefaultFromHost(context.host, context.formatContext.options), context.formatContext); + private insertAtTopOfFile(sourceFile: SourceFile, insert: Statement | readonly Statement[], blankLineBetween: boolean): void { + const pos = getInsertionPositionAtSourceFileTop(sourceFile); + const options = { + prefix: pos === 0 ? undefined : this.newLineCharacter, + suffix: (isLineBreak(sourceFile.text.charCodeAt(pos)) ? "" : this.newLineCharacter) + (blankLineBetween ? this.newLineCharacter : ""), + }; + if (isArray(insert)) { + this.insertNodesAt(sourceFile, pos, insert, options); + } + else { + this.insertNodeAt(sourceFile, pos, insert, options); } + } - public static with(context: TextChangesContext, cb: (tracker: ChangeTracker) => void): FileTextChanges[] { - const tracker = ChangeTracker.fromContext(context); - cb(tracker); - return tracker.getChanges(); + public insertFirstParameter(sourceFile: SourceFile, parameters: NodeArray, newParam: ParameterDeclaration): void { + const p0 = firstOrUndefined(parameters); + if (p0) { + this.insertNodeBefore(sourceFile, p0, newParam); + } + else { + this.insertNodeAt(sourceFile, parameters.pos, newParam); } + } + + public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, blankLineBetween = false, options: ConfigurableStartEnd = {}): void { + this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, options), newNode, this.getOptionsForInsertNodeBefore(before, newNode, blankLineBetween)); + } + + public insertModifierAt(sourceFile: SourceFile, pos: number, modifier: SyntaxKind, options: InsertNodeOptions = {}): void { + this.insertNodeAt(sourceFile, pos, factory.createToken(modifier), options); + } + + public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void { + return this.insertModifierAt(sourceFile, before.getStart(sourceFile), modifier, { suffix: " " }); + } - /** Public for tests only. Other callers should use `ChangeTracker.with`. */ - constructor(private readonly newLineCharacter: string, private readonly formatContext: formatting.FormatContext) {} + public insertCommentBeforeLine(sourceFile: SourceFile, lineNumber: number, position: number, commentText: string): void { + const lineStartPosition = getStartPositionOfLine(lineNumber, sourceFile); + const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition); + // First try to see if we can put the comment on the previous line. + // We need to make sure that we are not in the middle of a string literal or a comment. + // If so, we do not want to separate the node from its comment if we can. + // Otherwise, add an extra new line immediately before the error span. + const insertAtLineStart = isValidLocationToAddComment(sourceFile, startPosition); + const token = getTouchingToken(sourceFile, insertAtLineStart ? startPosition : position); + const indent = sourceFile.text.slice(lineStartPosition, startPosition); + const text = `${insertAtLineStart ? "" : this.newLineCharacter}//${commentText}${this.newLineCharacter}${indent}`; + this.insertText(sourceFile, token.getStart(sourceFile), text); + } - public pushRaw(sourceFile: SourceFile, change: FileTextChanges) { - Debug.assertEqual(sourceFile.fileName, change.fileName); - for (const c of change.textChanges) { - this.changes.push({ - kind: ChangeKind.Text, - sourceFile, - text: c.newText, - range: createTextRangeFromSpan(c.span), + public insertJsdocCommentBefore(sourceFile: SourceFile, node: HasJSDoc, tag: JSDoc): void { + const fnStart = node.getStart(sourceFile); + if (node.jsDoc) { + for (const jsdoc of node.jsDoc) { + this.deleteRange(sourceFile, { + pos: getLineStartPositionForPosition(jsdoc.getStart(sourceFile), sourceFile), + end: getAdjustedEndPosition(sourceFile, jsdoc, /*options*/ {}) }); } } + const startPosition = getPrecedingNonSpaceCharacterPosition(sourceFile.text, fnStart - 1); + const indent = sourceFile.text.slice(startPosition, fnStart); + this.insertNodeAt(sourceFile, fnStart, tag, { suffix: this.newLineCharacter + indent }); + } - public deleteRange(sourceFile: SourceFile, range: TextRange): void { - this.changes.push({ kind: ChangeKind.Remove, sourceFile, range }); - } + private createJSDocText(sourceFile: SourceFile, node: HasJSDoc) { + const comments = flatMap(node.jsDoc, jsDoc => + isString(jsDoc.comment) ? factory.createJSDocText(jsDoc.comment) : jsDoc.comment) as JSDocComment[]; + const jsDoc = singleOrUndefined(node.jsDoc); + return jsDoc && positionsAreOnSameLine(jsDoc.pos, jsDoc.end, sourceFile) && length(comments) === 0 ? undefined : + factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))); + } - delete(sourceFile: SourceFile, node: Node | NodeArray): void { - this.deletedNodes.push({ sourceFile, node }); - } + public replaceJSDocComment(sourceFile: SourceFile, node: HasJSDoc, tags: readonly JSDocTag[]) { + this.insertJsdocCommentBefore(sourceFile, updateJSDocHost(node), factory.createJSDocComment(this.createJSDocText(sourceFile, node), factory.createNodeArray(tags))); + } - /** Stop! Consider using `delete` instead, which has logic for deleting nodes from delimited lists. */ - public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { - this.deleteRange(sourceFile, getAdjustedRange(sourceFile, node, node, options)); - } + public addJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void { + const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags); + const unmergedNewTags = newTags.filter(newTag => !oldTags.some((tag, i) => { + const merged = tryMergeJsdocTags(tag, newTag); + if (merged) oldTags[i] = merged; + return !!merged; + })); + this.replaceJSDocComment(sourceFile, parent, [...oldTags, ...unmergedNewTags]); + } + + public filterJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, predicate: (tag: JSDocTag) => boolean): void { + this.replaceJSDocComment(sourceFile, parent, filter(flatMapToMutable(parent.jsDoc, j => j.tags), predicate)); + } - public deleteNodes(sourceFile: SourceFile, nodes: readonly Node[], options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }, hasTrailingComment: boolean): void { - // When deleting multiple nodes we need to track if the end position is including multiline trailing comments. - for (const node of nodes) { - const pos = getAdjustedStartPosition(sourceFile, node, options, hasTrailingComment); - const end = getAdjustedEndPosition(sourceFile, node, options); + public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string): void { + this.changes.push({ kind: ChangeKind.Text, sourceFile, range, text }); + } - this.deleteRange(sourceFile, { pos, end }); + public insertText(sourceFile: SourceFile, pos: number, text: string): void { + this.replaceRangeWithText(sourceFile, createRange(pos), text); + } - hasTrailingComment = !!getEndPositionOfMultilineTrailingComment(sourceFile, node, options); + /** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */ + public tryInsertTypeAnnotation(sourceFile: SourceFile, node: TypeAnnotatable, type: TypeNode): boolean { + let endNode: Node | undefined; + if (isFunctionLike(node)) { + endNode = findChildOfKind(node, SyntaxKind.CloseParenToken, sourceFile); + if (!endNode) { + if (!isArrowFunction(node)) return false; // Function missing parentheses, give up + // If no `)`, is an arrow function `x => x`, so use the end of the first parameter + endNode = first(node.parameters); } } - - public deleteModifier(sourceFile: SourceFile, modifier: Modifier): void { - this.deleteRange(sourceFile, { pos: modifier.getStart(sourceFile), end: skipTrivia(sourceFile.text, modifier.end, /*stopAfterLineBreak*/ true) }); + else { + endNode = (node.kind === SyntaxKind.VariableDeclaration ? node.exclamationToken : node.questionToken) ?? node.name; } - public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { - const startPosition = getAdjustedStartPosition(sourceFile, startNode, options); - const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); - this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); - } + this.insertNodeAt(sourceFile, endNode.end, type, { prefix: ": " }); + return true; + } - public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { - const startPosition = getAdjustedStartPosition(sourceFile, startNode, options); - const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options); - this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); - } + public tryInsertThisTypeAnnotation(sourceFile: SourceFile, node: ThisTypeAnnotatable, type: TypeNode): void { + const start = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile)!.getStart(sourceFile) + 1; + const suffix = node.parameters.length ? ", " : ""; - public replaceRange(sourceFile: SourceFile, range: TextRange, newNode: Node, options: InsertNodeOptions = {}): void { - this.changes.push({ kind: ChangeKind.ReplaceWithSingleNode, sourceFile, range, options, node: newNode }); - } + this.insertNodeAt(sourceFile, start, type, { prefix: "this: ", suffix }); + } - public replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options: ChangeNodeOptions = useNonAdjustedPositions): void { - this.replaceRange(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, options), newNode, options); - } + public insertTypeParameters(sourceFile: SourceFile, node: SignatureDeclaration, typeParameters: readonly TypeParameterDeclaration[]): void { + // If no `(`, is an arrow function `x => x`, so use the pos of the first parameter + const start = (findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile) || first(node.parameters)).getStart(sourceFile); + this.insertNodesAt(sourceFile, start, typeParameters, { prefix: "<", suffix: ">", joiner: ", " }); + } - public replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options: ChangeNodeOptions = useNonAdjustedPositions): void { - this.replaceRange(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNode, options); + private getOptionsForInsertNodeBefore(before: Node, inserted: Node, blankLineBetween: boolean): InsertNodeOptions { + if (isStatement(before) || isClassElement(before)) { + return { suffix: blankLineBetween ? this.newLineCharacter + this.newLineCharacter : this.newLineCharacter }; } - - private replaceRangeWithNodes(sourceFile: SourceFile, range: TextRange, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions & ConfigurableStartEnd = {}): void { - this.changes.push({ kind: ChangeKind.ReplaceWithMultipleNodes, sourceFile, range, options, nodes: newNodes }); + else if (isVariableDeclaration(before)) { // insert `x = 1, ` into `const x = 1, y = 2; + return { suffix: ", " }; } - - public replaceNodeWithNodes(sourceFile: SourceFile, oldNode: Node, newNodes: readonly Node[], options: ChangeNodeOptions = useNonAdjustedPositions): void { - this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, options), newNodes, options); + else if (isParameter(before)) { + return isParameter(inserted) ? { suffix: ", " } : {}; } - - public replaceNodeWithText(sourceFile: SourceFile, oldNode: Node, text: string): void { - this.replaceRangeWithText(sourceFile, getAdjustedRange(sourceFile, oldNode, oldNode, useNonAdjustedPositions), text); + else if (isStringLiteral(before) && isImportDeclaration(before.parent) || isNamedImports(before)) { + return { suffix: ", " }; } - - public replaceNodeRangeWithNodes(sourceFile: SourceFile, startNode: Node, endNode: Node, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions & ConfigurableStartEnd = useNonAdjustedPositions): void { - this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNodes, options); + else if (isImportSpecifier(before)) { + return { suffix: "," + (blankLineBetween ? this.newLineCharacter : " ") }; } + return Debug.failBadSyntaxKind(before); // We haven't handled this kind of node yet -- add it + } - public nodeHasTrailingComment(sourceFile: SourceFile, oldNode: Node, configurableEnd: ConfigurableEnd = useNonAdjustedPositions): boolean { - return !!getEndPositionOfMultilineTrailingComment(sourceFile, oldNode, configurableEnd); + public insertNodeAtConstructorStart(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { + const firstStatement = firstOrUndefined(ctr.body!.statements); + if (!firstStatement || !ctr.body!.multiLine) { + this.replaceConstructorBody(sourceFile, ctr, [newStatement, ...ctr.body!.statements]); } - - private nextCommaToken(sourceFile: SourceFile, node: Node): Node | undefined { - const next = findNextToken(node, node.parent, sourceFile); - return next && next.kind === SyntaxKind.CommaToken ? next : undefined; + else { + this.insertNodeBefore(sourceFile, firstStatement, newStatement); } + } - public replacePropertyAssignment(sourceFile: SourceFile, oldNode: PropertyAssignment, newNode: PropertyAssignment): void { - const suffix = this.nextCommaToken(sourceFile, oldNode) ? "" : ("," + this.newLineCharacter); - this.replaceNode(sourceFile, oldNode, newNode, { suffix }); + public insertNodeAtConstructorStartAfterSuperCall(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { + const superCallStatement = find(ctr.body!.statements, stmt => isExpressionStatement(stmt) && isSuperCall(stmt.expression)); + if (!superCallStatement || !ctr.body!.multiLine) { + this.replaceConstructorBody(sourceFile, ctr, [...ctr.body!.statements, newStatement]); } - - public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}): void { - this.replaceRange(sourceFile, createRange(pos), newNode, options); + else { + this.insertNodeAfter(sourceFile, superCallStatement, newStatement); } + } - private insertNodesAt(sourceFile: SourceFile, pos: number, newNodes: readonly Node[], options: ReplaceWithMultipleNodesOptions = {}): void { - this.replaceRangeWithNodes(sourceFile, createRange(pos), newNodes, options); + public insertNodeAtConstructorEnd(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { + const lastStatement = lastOrUndefined(ctr.body!.statements); + if (!lastStatement || !ctr.body!.multiLine) { + this.replaceConstructorBody(sourceFile, ctr, [...ctr.body!.statements, newStatement]); } - - public insertNodeAtTopOfFile(sourceFile: SourceFile, newNode: Statement, blankLineBetween: boolean): void { - this.insertAtTopOfFile(sourceFile, newNode, blankLineBetween); + else { + this.insertNodeAfter(sourceFile, lastStatement, newStatement); } + } - public insertNodesAtTopOfFile(sourceFile: SourceFile, newNodes: readonly Statement[], blankLineBetween: boolean): void { - this.insertAtTopOfFile(sourceFile, newNodes, blankLineBetween); - } + private replaceConstructorBody(sourceFile: SourceFile, ctr: ConstructorDeclaration, statements: readonly Statement[]): void { + this.replaceNode(sourceFile, ctr.body!, factory.createBlock(statements, /*multiLine*/ true)); + } - private insertAtTopOfFile(sourceFile: SourceFile, insert: Statement | readonly Statement[], blankLineBetween: boolean): void { - const pos = getInsertionPositionAtSourceFileTop(sourceFile); - const options = { - prefix: pos === 0 ? undefined : this.newLineCharacter, - suffix: (isLineBreak(sourceFile.text.charCodeAt(pos)) ? "" : this.newLineCharacter) + (blankLineBetween ? this.newLineCharacter : ""), - }; - if (isArray(insert)) { - this.insertNodesAt(sourceFile, pos, insert, options); - } - else { - this.insertNodeAt(sourceFile, pos, insert, options); - } - } + public insertNodeAtEndOfScope(sourceFile: SourceFile, scope: Node, newNode: Node): void { + const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {}); + this.insertNodeAt(sourceFile, pos, newNode, { + prefix: isLineBreak(sourceFile.text.charCodeAt(scope.getLastToken()!.pos)) ? this.newLineCharacter : this.newLineCharacter + this.newLineCharacter, + suffix: this.newLineCharacter + }); + } - public insertFirstParameter(sourceFile: SourceFile, parameters: NodeArray, newParam: ParameterDeclaration): void { - const p0 = firstOrUndefined(parameters); - if (p0) { - this.insertNodeBefore(sourceFile, p0, newParam); - } - else { - this.insertNodeAt(sourceFile, parameters.pos, newParam); - } - } + public insertMemberAtStart(sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode, newElement: ClassElement | PropertySignature | MethodSignature): void { + this.insertNodeAtStartWorker(sourceFile, node, newElement); + } - public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, blankLineBetween = false, options: ConfigurableStartEnd = {}): void { - this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, options), newNode, this.getOptionsForInsertNodeBefore(before, newNode, blankLineBetween)); - } + public insertNodeAtObjectStart(sourceFile: SourceFile, obj: ObjectLiteralExpression, newElement: ObjectLiteralElementLike): void { + this.insertNodeAtStartWorker(sourceFile, obj, newElement); + } - public insertModifierAt(sourceFile: SourceFile, pos: number, modifier: SyntaxKind, options: InsertNodeOptions = {}): void { - this.insertNodeAt(sourceFile, pos, factory.createToken(modifier), options); - } + private insertNodeAtStartWorker(sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode, newElement: ClassElement | ObjectLiteralElementLike | PropertySignature | MethodSignature): void { + const indentation = this.guessIndentationFromExistingMembers(sourceFile, node) ?? this.computeIndentationForNewMember(sourceFile, node); + this.insertNodeAt(sourceFile, getMembersOrProperties(node).pos, newElement, this.getInsertNodeAtStartInsertOptions(sourceFile, node, indentation)); + } - public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void { - return this.insertModifierAt(sourceFile, before.getStart(sourceFile), modifier, { suffix: " " }); - } + /** + * Tries to guess the indentation from the existing members of a class/interface/object. All members must be on + * new lines and must share the same indentation. + */ + private guessIndentationFromExistingMembers(sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode) { + let indentation: number | undefined; + let lastRange: TextRange = node; + for (const member of getMembersOrProperties(node)) { + if (rangeStartPositionsAreOnSameLine(lastRange, member, sourceFile)) { + // each indented member must be on a new line + return undefined; + } + const memberStart = member.getStart(sourceFile); + const memberIndentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(memberStart, sourceFile), memberStart, sourceFile, this.formatContext.options); + if (indentation === undefined) { + indentation = memberIndentation; + } + else if (memberIndentation !== indentation) { + // indentation of multiple members is not consistent + return undefined; + } + lastRange = member; + } + return indentation; + } - public insertCommentBeforeLine(sourceFile: SourceFile, lineNumber: number, position: number, commentText: string): void { - const lineStartPosition = getStartPositionOfLine(lineNumber, sourceFile); - const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition); - // First try to see if we can put the comment on the previous line. - // We need to make sure that we are not in the middle of a string literal or a comment. - // If so, we do not want to separate the node from its comment if we can. - // Otherwise, add an extra new line immediately before the error span. - const insertAtLineStart = isValidLocationToAddComment(sourceFile, startPosition); - const token = getTouchingToken(sourceFile, insertAtLineStart ? startPosition : position); - const indent = sourceFile.text.slice(lineStartPosition, startPosition); - const text = `${insertAtLineStart ? "" : this.newLineCharacter}//${commentText}${this.newLineCharacter}${indent}`; - this.insertText(sourceFile, token.getStart(sourceFile), text); - } + private computeIndentationForNewMember(sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode) { + const nodeStart = node.getStart(sourceFile); + return formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(nodeStart, sourceFile), nodeStart, sourceFile, this.formatContext.options) + + (this.formatContext.options.indentSize ?? 4); + } - public insertJsdocCommentBefore(sourceFile: SourceFile, node: HasJSDoc, tag: JSDoc): void { - const fnStart = node.getStart(sourceFile); - if (node.jsDoc) { - for (const jsdoc of node.jsDoc) { - this.deleteRange(sourceFile, { - pos: getLineStartPositionForPosition(jsdoc.getStart(sourceFile), sourceFile), - end: getAdjustedEndPosition(sourceFile, jsdoc, /*options*/ {}) - }); - } - } - const startPosition = getPrecedingNonSpaceCharacterPosition(sourceFile.text, fnStart - 1); - const indent = sourceFile.text.slice(startPosition, fnStart); - this.insertNodeAt(sourceFile, fnStart, tag, { suffix: this.newLineCharacter + indent }); - } + private getInsertNodeAtStartInsertOptions(sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode, indentation: number): InsertNodeOptions { + // Rules: + // - Always insert leading newline. + // - For object literals: + // - Add a trailing comma if there are existing members in the node, or the source file is not a JSON file + // (because trailing commas are generally illegal in a JSON file). + // - Add a leading comma if the source file is not a JSON file, there are existing insertions, + // and the node is empty (because we didn't add a trailing comma per the previous rule). + // - Only insert a trailing newline if body is single-line and there are no other insertions for the node. + // NOTE: This is handled in `finishClassesWithNodesInsertedAtStart`. + + const members = getMembersOrProperties(node); + const isEmpty = members.length === 0; + const isFirstInsertion = addToSeen(this.classesWithNodesInsertedAtStart, getNodeId(node), { node, sourceFile }); + const insertTrailingComma = isObjectLiteralExpression(node) && (!isJsonSourceFile(sourceFile) || !isEmpty); + const insertLeadingComma = isObjectLiteralExpression(node) && isJsonSourceFile(sourceFile) && isEmpty && !isFirstInsertion; + return { + indentation, + prefix: (insertLeadingComma ? "," : "") + this.newLineCharacter, + suffix: insertTrailingComma ? "," : isInterfaceDeclaration(node) && isEmpty ? ";" : "" + }; + } - private createJSDocText(sourceFile: SourceFile, node: HasJSDoc) { - const comments = flatMap(node.jsDoc, jsDoc => - isString(jsDoc.comment) ? factory.createJSDocText(jsDoc.comment) : jsDoc.comment) as JSDocComment[]; - const jsDoc = singleOrUndefined(node.jsDoc); - return jsDoc && positionsAreOnSameLine(jsDoc.pos, jsDoc.end, sourceFile) && length(comments) === 0 ? undefined : - factory.createNodeArray(intersperse(comments, factory.createJSDocText("\n"))); - } + public insertNodeAfterComma(sourceFile: SourceFile, after: Node, newNode: Node): void { + const endPosition = this.insertNodeAfterWorker(sourceFile, this.nextCommaToken(sourceFile, after) || after, newNode); + this.insertNodeAt(sourceFile, endPosition, newNode, this.getInsertNodeAfterOptions(sourceFile, after)); + } - public replaceJSDocComment(sourceFile: SourceFile, node: HasJSDoc, tags: readonly JSDocTag[]) { - this.insertJsdocCommentBefore(sourceFile, updateJSDocHost(node), factory.createJSDocComment(this.createJSDocText(sourceFile, node), factory.createNodeArray(tags))); - } + public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node): void { + const endPosition = this.insertNodeAfterWorker(sourceFile, after, newNode); + this.insertNodeAt(sourceFile, endPosition, newNode, this.getInsertNodeAfterOptions(sourceFile, after)); + } - public addJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, newTags: readonly JSDocTag[]): void { - const oldTags = flatMapToMutable(parent.jsDoc, j => j.tags); - const unmergedNewTags = newTags.filter(newTag => !oldTags.some((tag, i) => { - const merged = tryMergeJsdocTags(tag, newTag); - if (merged) oldTags[i] = merged; - return !!merged; - })); - this.replaceJSDocComment(sourceFile, parent, [...oldTags, ...unmergedNewTags]); - } + public insertNodeAtEndOfList(sourceFile: SourceFile, list: NodeArray, newNode: Node): void { + this.insertNodeAt(sourceFile, list.end, newNode, { prefix: ", " }); + } - public filterJSDocTags(sourceFile: SourceFile, parent: HasJSDoc, predicate: (tag: JSDocTag) => boolean): void { - this.replaceJSDocComment(sourceFile, parent, filter(flatMapToMutable(parent.jsDoc, j => j.tags), predicate)); - } + public insertNodesAfter(sourceFile: SourceFile, after: Node, newNodes: readonly Node[]): void { + const endPosition = this.insertNodeAfterWorker(sourceFile, after, first(newNodes)); + this.insertNodesAt(sourceFile, endPosition, newNodes, this.getInsertNodeAfterOptions(sourceFile, after)); + } - public replaceRangeWithText(sourceFile: SourceFile, range: TextRange, text: string): void { - this.changes.push({ kind: ChangeKind.Text, sourceFile, range, text }); + private insertNodeAfterWorker(sourceFile: SourceFile, after: Node, newNode: Node): number { + if (needSemicolonBetween(after, newNode)) { + // check if previous statement ends with semicolon + // if not - insert semicolon to preserve the code from changing the meaning due to ASI + if (sourceFile.text.charCodeAt(after.end - 1) !== CharacterCodes.semicolon) { + this.replaceRange(sourceFile, createRange(after.end), factory.createToken(SyntaxKind.SemicolonToken)); + } } + const endPosition = getAdjustedEndPosition(sourceFile, after, {}); + return endPosition; + } - public insertText(sourceFile: SourceFile, pos: number, text: string): void { - this.replaceRangeWithText(sourceFile, createRange(pos), text); - } + private getInsertNodeAfterOptions(sourceFile: SourceFile, after: Node): InsertNodeOptions { + const options = this.getInsertNodeAfterOptionsWorker(after); + return { + ...options, + prefix: after.end === sourceFile.end && isStatement(after) ? (options.prefix ? `\n${options.prefix}` : "\n") : options.prefix, + }; + } - /** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */ - public tryInsertTypeAnnotation(sourceFile: SourceFile, node: TypeAnnotatable, type: TypeNode): boolean { - let endNode: Node | undefined; - if (isFunctionLike(node)) { - endNode = findChildOfKind(node, SyntaxKind.CloseParenToken, sourceFile); - if (!endNode) { - if (!isArrowFunction(node)) return false; // Function missing parentheses, give up - // If no `)`, is an arrow function `x => x`, so use the end of the first parameter - endNode = first(node.parameters); - } - } - else { - endNode = (node.kind === SyntaxKind.VariableDeclaration ? node.exclamationToken : node.questionToken) ?? node.name; - } + private getInsertNodeAfterOptionsWorker(node: Node): InsertNodeOptions { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + return { prefix: this.newLineCharacter, suffix: this.newLineCharacter }; - this.insertNodeAt(sourceFile, endNode.end, type, { prefix: ": " }); - return true; - } + case SyntaxKind.VariableDeclaration: + case SyntaxKind.StringLiteral: + case SyntaxKind.Identifier: + return { prefix: ", " }; - public tryInsertThisTypeAnnotation(sourceFile: SourceFile, node: ThisTypeAnnotatable, type: TypeNode): void { - const start = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile)!.getStart(sourceFile) + 1; - const suffix = node.parameters.length ? ", " : ""; + case SyntaxKind.PropertyAssignment: + return { suffix: "," + this.newLineCharacter }; - this.insertNodeAt(sourceFile, start, type, { prefix: "this: ", suffix }); - } + case SyntaxKind.ExportKeyword: + return { prefix: " " }; - public insertTypeParameters(sourceFile: SourceFile, node: SignatureDeclaration, typeParameters: readonly TypeParameterDeclaration[]): void { - // If no `(`, is an arrow function `x => x`, so use the pos of the first parameter - const start = (findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile) || first(node.parameters)).getStart(sourceFile); - this.insertNodesAt(sourceFile, start, typeParameters, { prefix: "<", suffix: ">", joiner: ", " }); - } + case SyntaxKind.Parameter: + return {}; - private getOptionsForInsertNodeBefore(before: Node, inserted: Node, blankLineBetween: boolean): InsertNodeOptions { - if (isStatement(before) || isClassElement(before)) { - return { suffix: blankLineBetween ? this.newLineCharacter + this.newLineCharacter : this.newLineCharacter }; - } - else if (isVariableDeclaration(before)) { // insert `x = 1, ` into `const x = 1, y = 2; - return { suffix: ", " }; - } - else if (isParameter(before)) { - return isParameter(inserted) ? { suffix: ", " } : {}; - } - else if (isStringLiteral(before) && isImportDeclaration(before.parent) || isNamedImports(before)) { - return { suffix: ", " }; - } - else if (isImportSpecifier(before)) { - return { suffix: "," + (blankLineBetween ? this.newLineCharacter : " ") }; - } - return Debug.failBadSyntaxKind(before); // We haven't handled this kind of node yet -- add it + default: + Debug.assert(isStatement(node) || isClassOrTypeElement(node)); // Else we haven't handled this kind of node yet -- add it + return { suffix: this.newLineCharacter }; } + } - public insertNodeAtConstructorStart(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { - const firstStatement = firstOrUndefined(ctr.body!.statements); - if (!firstStatement || !ctr.body!.multiLine) { - this.replaceConstructorBody(sourceFile, ctr, [newStatement, ...ctr.body!.statements]); + public insertName(sourceFile: SourceFile, node: FunctionExpression | ClassExpression | ArrowFunction, name: string): void { + Debug.assert(!node.name); + if (node.kind === SyntaxKind.ArrowFunction) { + const arrow = findChildOfKind(node, SyntaxKind.EqualsGreaterThanToken, sourceFile)!; + const lparen = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); + if (lparen) { + // `() => {}` --> `function f() {}` + this.insertNodesAt(sourceFile, lparen.getStart(sourceFile), [factory.createToken(SyntaxKind.FunctionKeyword), factory.createIdentifier(name)], { joiner: " " }); + deleteNode(this, sourceFile, arrow); } else { - this.insertNodeBefore(sourceFile, firstStatement, newStatement); + // `x => {}` -> `function f(x) {}` + this.insertText(sourceFile, first(node.parameters).getStart(sourceFile), `function ${name}(`); + // Replacing full range of arrow to get rid of the leading space -- replace ` =>` with `)` + this.replaceRange(sourceFile, arrow, factory.createToken(SyntaxKind.CloseParenToken)); } - } - public insertNodeAtConstructorStartAfterSuperCall(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { - const superCallStatement = find(ctr.body!.statements, stmt => isExpressionStatement(stmt) && isSuperCall(stmt.expression)); - if (!superCallStatement || !ctr.body!.multiLine) { - this.replaceConstructorBody(sourceFile, ctr, [...ctr.body!.statements, newStatement]); - } - else { - this.insertNodeAfter(sourceFile, superCallStatement, newStatement); + if (node.body.kind !== SyntaxKind.Block) { + // `() => 0` => `function f() { return 0; }` + this.insertNodesAt(sourceFile, node.body.getStart(sourceFile), [factory.createToken(SyntaxKind.OpenBraceToken), factory.createToken(SyntaxKind.ReturnKeyword)], { joiner: " ", suffix: " " }); + this.insertNodesAt(sourceFile, node.body.end, [factory.createToken(SyntaxKind.SemicolonToken), factory.createToken(SyntaxKind.CloseBraceToken)], { joiner: " " }); } } - - public insertNodeAtConstructorEnd(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement): void { - const lastStatement = lastOrUndefined(ctr.body!.statements); - if (!lastStatement || !ctr.body!.multiLine) { - this.replaceConstructorBody(sourceFile, ctr, [...ctr.body!.statements, newStatement]); - } - else { - this.insertNodeAfter(sourceFile, lastStatement, newStatement); - } + else { + const pos = findChildOfKind(node, node.kind === SyntaxKind.FunctionExpression ? SyntaxKind.FunctionKeyword : SyntaxKind.ClassKeyword, sourceFile)!.end; + this.insertNodeAt(sourceFile, pos, factory.createIdentifier(name), { prefix: " " }); } + } - private replaceConstructorBody(sourceFile: SourceFile, ctr: ConstructorDeclaration, statements: readonly Statement[]): void { - this.replaceNode(sourceFile, ctr.body!, factory.createBlock(statements, /*multiLine*/ true)); - } + public insertExportModifier(sourceFile: SourceFile, node: DeclarationStatement | VariableStatement): void { + this.insertText(sourceFile, node.getStart(sourceFile), "export "); + } - public insertNodeAtEndOfScope(sourceFile: SourceFile, scope: Node, newNode: Node): void { - const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {}); - this.insertNodeAt(sourceFile, pos, newNode, { - prefix: isLineBreak(sourceFile.text.charCodeAt(scope.getLastToken()!.pos)) ? this.newLineCharacter : this.newLineCharacter + this.newLineCharacter, - suffix: this.newLineCharacter - }); + public insertImportSpecifierAtIndex(sourceFile: SourceFile, importSpecifier: ImportSpecifier, namedImports: NamedImports, index: number) { + const prevSpecifier = namedImports.elements[index - 1]; + if (prevSpecifier) { + this.insertNodeInListAfter(sourceFile, prevSpecifier, importSpecifier); } - - public insertMemberAtStart(sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode, newElement: ClassElement | PropertySignature | MethodSignature): void { - this.insertNodeAtStartWorker(sourceFile, node, newElement); + else { + this.insertNodeBefore( + sourceFile, + namedImports.elements[0], + importSpecifier, + !positionsAreOnSameLine(namedImports.elements[0].getStart(), namedImports.parent.parent.getStart(), sourceFile)); } + } - public insertNodeAtObjectStart(sourceFile: SourceFile, obj: ObjectLiteralExpression, newElement: ObjectLiteralElementLike): void { - this.insertNodeAtStartWorker(sourceFile, obj, newElement); + /** + * This function should be used to insert nodes in lists when nodes don't carry separators as the part of the node range, + * i.e. arguments in arguments lists, parameters in parameter lists etc. + * Note that separators are part of the node in statements and class elements. + */ + public insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node, containingList = formatting.SmartIndenter.getContainingList(after, sourceFile)): void { + if (!containingList) { + Debug.fail("node is not a list element"); + return; } - - private insertNodeAtStartWorker(sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode, newElement: ClassElement | ObjectLiteralElementLike | PropertySignature | MethodSignature): void { - const indentation = this.guessIndentationFromExistingMembers(sourceFile, node) ?? this.computeIndentationForNewMember(sourceFile, node); - this.insertNodeAt(sourceFile, getMembersOrProperties(node).pos, newElement, this.getInsertNodeAtStartInsertOptions(sourceFile, node, indentation)); + const index = indexOfNode(containingList, after); + if (index < 0) { + return; } - - /** - * Tries to guess the indentation from the existing members of a class/interface/object. All members must be on - * new lines and must share the same indentation. - */ - private guessIndentationFromExistingMembers(sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode) { - let indentation: number | undefined; - let lastRange: TextRange = node; - for (const member of getMembersOrProperties(node)) { - if (rangeStartPositionsAreOnSameLine(lastRange, member, sourceFile)) { - // each indented member must be on a new line - return undefined; - } - const memberStart = member.getStart(sourceFile); - const memberIndentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(memberStart, sourceFile), memberStart, sourceFile, this.formatContext.options); - if (indentation === undefined) { - indentation = memberIndentation; - } - else if (memberIndentation !== indentation) { - // indentation of multiple members is not consistent - return undefined; + const end = after.getEnd(); + if (index !== containingList.length - 1) { + // any element except the last one + // use next sibling as an anchor + const nextToken = getTokenAtPosition(sourceFile, after.end); + if (nextToken && isSeparator(after, nextToken)) { + // for list + // a, b, c + // create change for adding 'e' after 'a' as + // - find start of next element after a (it is b) + // - use next element start as start and end position in final change + // - build text of change by formatting the text of node + whitespace trivia of b + + // in multiline case it will work as + // a, + // b, + // c, + // result - '*' denotes leading trivia that will be inserted after new text (displayed as '#') + // a, + // insertedtext# + // ###b, + // c, + const nextNode = containingList[index + 1]; + const startPos = skipWhitespacesAndLineBreaks(sourceFile.text, nextNode.getFullStart()); + + // write separator and leading trivia of the next element as suffix + const suffix = `${tokenToString(nextToken.kind)}${sourceFile.text.substring(nextToken.end, startPos)}`; + this.insertNodesAt(sourceFile, startPos, [newNode], { suffix }); + } + } + else { + const afterStart = after.getStart(sourceFile); + const afterStartLinePosition = getLineStartPositionForPosition(afterStart, sourceFile); + + let separator: SyntaxKind.CommaToken | SyntaxKind.SemicolonToken | undefined; + let multilineList = false; + + // insert element after the last element in the list that has more than one item + // pick the element preceding the after element to: + // - pick the separator + // - determine if list is a multiline + if (containingList.length === 1) { + // if list has only one element then we'll format is as multiline if node has comment in trailing trivia, or as singleline otherwise + // i.e. var x = 1 // this is x + // | new element will be inserted at this position + separator = SyntaxKind.CommaToken; + } + else { + // element has more than one element, pick separator from the list + const tokenBeforeInsertPosition = findPrecedingToken(after.pos, sourceFile); + separator = isSeparator(after, tokenBeforeInsertPosition) ? tokenBeforeInsertPosition.kind : SyntaxKind.CommaToken; + // determine if list is multiline by checking lines of after element and element that precedes it. + const afterMinusOneStartLinePosition = getLineStartPositionForPosition(containingList[index - 1].getStart(sourceFile), sourceFile); + multilineList = afterMinusOneStartLinePosition !== afterStartLinePosition; + } + if (hasCommentsBeforeLineBreak(sourceFile.text, after.end)) { + // in this case we'll always treat containing list as multiline + multilineList = true; + } + if (multilineList) { + // insert separator immediately following the 'after' node to preserve comments in trailing trivia + this.replaceRange(sourceFile, createRange(end), factory.createToken(separator)); + // use the same indentation as 'after' item + const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, this.formatContext.options); + // insert element before the line break on the line that contains 'after' element + let insertPos = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ false); + // find position before "\n" or "\r\n" + while (insertPos !== end && isLineBreak(sourceFile.text.charCodeAt(insertPos - 1))) { + insertPos--; } - lastRange = member; + this.replaceRange(sourceFile, createRange(insertPos), newNode, { indentation, prefix: this.newLineCharacter }); + } + else { + this.replaceRange(sourceFile, createRange(end), newNode, { prefix: `${tokenToString(separator)} ` }); } - return indentation; } + } - private computeIndentationForNewMember(sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode) { - const nodeStart = node.getStart(sourceFile); - return formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(nodeStart, sourceFile), nodeStart, sourceFile, this.formatContext.options) - + (this.formatContext.options.indentSize ?? 4); - } + public parenthesizeExpression(sourceFile: SourceFile, expression: Expression) { + this.replaceRange(sourceFile, rangeOfNode(expression), factory.createParenthesizedExpression(expression)); + } - private getInsertNodeAtStartInsertOptions(sourceFile: SourceFile, node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode, indentation: number): InsertNodeOptions { - // Rules: - // - Always insert leading newline. - // - For object literals: - // - Add a trailing comma if there are existing members in the node, or the source file is not a JSON file - // (because trailing commas are generally illegal in a JSON file). - // - Add a leading comma if the source file is not a JSON file, there are existing insertions, - // and the node is empty (because we didn't add a trailing comma per the previous rule). - // - Only insert a trailing newline if body is single-line and there are no other insertions for the node. - // NOTE: This is handled in `finishClassesWithNodesInsertedAtStart`. + private finishClassesWithNodesInsertedAtStart(): void { + this.classesWithNodesInsertedAtStart.forEach(({ node, sourceFile }) => { + const [openBraceEnd, closeBraceEnd] = getClassOrObjectBraceEnds(node, sourceFile); + if (openBraceEnd !== undefined && closeBraceEnd !== undefined) { + const isEmpty = getMembersOrProperties(node).length === 0; + const isSingleLine = positionsAreOnSameLine(openBraceEnd, closeBraceEnd, sourceFile); + if (isEmpty && isSingleLine && openBraceEnd !== closeBraceEnd - 1) { + // For `class C { }` remove the whitespace inside the braces. + this.deleteRange(sourceFile, createRange(openBraceEnd, closeBraceEnd - 1)); + } + if (isSingleLine) { + this.insertText(sourceFile, closeBraceEnd - 1, this.newLineCharacter); + } + } + }); + } - const members = getMembersOrProperties(node); - const isEmpty = members.length === 0; - const isFirstInsertion = addToSeen(this.classesWithNodesInsertedAtStart, getNodeId(node), { node, sourceFile }); - const insertTrailingComma = isObjectLiteralExpression(node) && (!isJsonSourceFile(sourceFile) || !isEmpty); - const insertLeadingComma = isObjectLiteralExpression(node) && isJsonSourceFile(sourceFile) && isEmpty && !isFirstInsertion; - return { - indentation, - prefix: (insertLeadingComma ? "," : "") + this.newLineCharacter, - suffix: insertTrailingComma ? "," : isInterfaceDeclaration(node) && isEmpty ? ";" : "" - }; + private finishDeleteDeclarations(): void { + const deletedNodesInLists = new Set(); // Stores nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`. + for (const { sourceFile, node } of this.deletedNodes) { + if (!this.deletedNodes.some(d => d.sourceFile === sourceFile && rangeContainsRangeExclusive(d.node, node))) { + if (isArray(node)) { + this.deleteRange(sourceFile, rangeOfTypeParameters(sourceFile, node)); + } + else { + deleteDeclaration.deleteDeclaration(this, deletedNodesInLists, sourceFile, node); + } + } } - public insertNodeAfterComma(sourceFile: SourceFile, after: Node, newNode: Node): void { - const endPosition = this.insertNodeAfterWorker(sourceFile, this.nextCommaToken(sourceFile, after) || after, newNode); - this.insertNodeAt(sourceFile, endPosition, newNode, this.getInsertNodeAfterOptions(sourceFile, after)); - } + deletedNodesInLists.forEach(node => { + const sourceFile = node.getSourceFile(); + const list = formatting.SmartIndenter.getContainingList(node, sourceFile)!; + if (node !== last(list)) return; - public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node): void { - const endPosition = this.insertNodeAfterWorker(sourceFile, after, newNode); - this.insertNodeAt(sourceFile, endPosition, newNode, this.getInsertNodeAfterOptions(sourceFile, after)); - } + const lastNonDeletedIndex = findLastIndex(list, n => !deletedNodesInLists.has(n), list.length - 2); + if (lastNonDeletedIndex !== -1) { + this.deleteRange(sourceFile, { pos: list[lastNonDeletedIndex].end, end: startPositionToDeleteNodeInList(sourceFile, list[lastNonDeletedIndex + 1]) }); + } + }); + } - public insertNodeAtEndOfList(sourceFile: SourceFile, list: NodeArray, newNode: Node): void { - this.insertNodeAt(sourceFile, list.end, newNode, { prefix: ", " }); - } - - public insertNodesAfter(sourceFile: SourceFile, after: Node, newNodes: readonly Node[]): void { - const endPosition = this.insertNodeAfterWorker(sourceFile, after, first(newNodes)); - this.insertNodesAt(sourceFile, endPosition, newNodes, this.getInsertNodeAfterOptions(sourceFile, after)); - } - - private insertNodeAfterWorker(sourceFile: SourceFile, after: Node, newNode: Node): number { - if (needSemicolonBetween(after, newNode)) { - // check if previous statement ends with semicolon - // if not - insert semicolon to preserve the code from changing the meaning due to ASI - if (sourceFile.text.charCodeAt(after.end - 1) !== CharacterCodes.semicolon) { - this.replaceRange(sourceFile, createRange(after.end), factory.createToken(SyntaxKind.SemicolonToken)); - } - } - const endPosition = getAdjustedEndPosition(sourceFile, after, {}); - return endPosition; - } - - private getInsertNodeAfterOptions(sourceFile: SourceFile, after: Node): InsertNodeOptions { - const options = this.getInsertNodeAfterOptionsWorker(after); - return { - ...options, - prefix: after.end === sourceFile.end && isStatement(after) ? (options.prefix ? `\n${options.prefix}` : "\n") : options.prefix, - }; - } - - private getInsertNodeAfterOptionsWorker(node: Node): InsertNodeOptions { - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - return { prefix: this.newLineCharacter, suffix: this.newLineCharacter }; - - case SyntaxKind.VariableDeclaration: - case SyntaxKind.StringLiteral: - case SyntaxKind.Identifier: - return { prefix: ", " }; - - case SyntaxKind.PropertyAssignment: - return { suffix: "," + this.newLineCharacter }; - - case SyntaxKind.ExportKeyword: - return { prefix: " " }; - - case SyntaxKind.Parameter: - return {}; + /** + * Note: after calling this, the TextChanges object must be discarded! + * @param validate only for tests + * The reason we must validate as part of this method is that `getNonFormattedText` changes the node's positions, + * so we can only call this once and can't get the non-formatted text separately. + */ + public getChanges(validate?: ValidateNonFormattedText): FileTextChanges[] { + this.finishDeleteDeclarations(); + this.finishClassesWithNodesInsertedAtStart(); + const changes = changesToText.getTextChangesFromChanges(this.changes, this.newLineCharacter, this.formatContext, validate); + for (const { oldFile, fileName, statements } of this.newFiles) { + changes.push(changesToText.newFileChanges(oldFile, fileName, statements, this.newLineCharacter, this.formatContext)); + } + return changes; + } - default: - Debug.assert(isStatement(node) || isClassOrTypeElement(node)); // Else we haven't handled this kind of node yet -- add it - return { suffix: this.newLineCharacter }; - } - } + public createNewFile(oldFile: SourceFile | undefined, fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[]): void { + this.newFiles.push({ oldFile, fileName, statements }); + } +} - public insertName(sourceFile: SourceFile, node: FunctionExpression | ClassExpression | ArrowFunction, name: string): void { - Debug.assert(!node.name); - if (node.kind === SyntaxKind.ArrowFunction) { - const arrow = findChildOfKind(node, SyntaxKind.EqualsGreaterThanToken, sourceFile)!; - const lparen = findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile); - if (lparen) { - // `() => {}` --> `function f() {}` - this.insertNodesAt(sourceFile, lparen.getStart(sourceFile), [factory.createToken(SyntaxKind.FunctionKeyword), factory.createIdentifier(name)], { joiner: " " }); - deleteNode(this, sourceFile, arrow); - } - else { - // `x => {}` -> `function f(x) {}` - this.insertText(sourceFile, first(node.parameters).getStart(sourceFile), `function ${name}(`); - // Replacing full range of arrow to get rid of the leading space -- replace ` =>` with `)` - this.replaceRange(sourceFile, arrow, factory.createToken(SyntaxKind.CloseParenToken)); - } +function updateJSDocHost(parent: HasJSDoc): HasJSDoc { + if (parent.kind !== SyntaxKind.ArrowFunction) { + return parent; + } + const jsDocNode = parent.parent.kind === SyntaxKind.PropertyDeclaration ? + parent.parent as HasJSDoc : + parent.parent.parent as HasJSDoc; + jsDocNode.jsDoc = parent.jsDoc; + jsDocNode.jsDocCache = parent.jsDocCache; + return jsDocNode; +} - if (node.body.kind !== SyntaxKind.Block) { - // `() => 0` => `function f() { return 0; }` - this.insertNodesAt(sourceFile, node.body.getStart(sourceFile), [factory.createToken(SyntaxKind.OpenBraceToken), factory.createToken(SyntaxKind.ReturnKeyword)], { joiner: " ", suffix: " " }); - this.insertNodesAt(sourceFile, node.body.end, [factory.createToken(SyntaxKind.SemicolonToken), factory.createToken(SyntaxKind.CloseBraceToken)], { joiner: " " }); - } - } - else { - const pos = findChildOfKind(node, node.kind === SyntaxKind.FunctionExpression ? SyntaxKind.FunctionKeyword : SyntaxKind.ClassKeyword, sourceFile)!.end; - this.insertNodeAt(sourceFile, pos, factory.createIdentifier(name), { prefix: " " }); - } - } +function tryMergeJsdocTags(oldTag: JSDocTag, newTag: JSDocTag): JSDocTag | undefined { + if (oldTag.kind !== newTag.kind) { + return undefined; + } + switch (oldTag.kind) { + case SyntaxKind.JSDocParameterTag: { + const oldParam = oldTag as JSDocParameterTag; + const newParam = newTag as JSDocParameterTag; + return isIdentifier(oldParam.name) && isIdentifier(newParam.name) && oldParam.name.escapedText === newParam.name.escapedText + ? factory.createJSDocParameterTag(/*tagName*/ undefined, newParam.name, /*isBracketed*/ false, newParam.typeExpression, newParam.isNameFirst, oldParam.comment) + : undefined; + } + case SyntaxKind.JSDocReturnTag: + return factory.createJSDocReturnTag(/*tagName*/ undefined, (newTag as JSDocReturnTag).typeExpression, oldTag.comment); + case SyntaxKind.JSDocTypeTag: + return factory.createJSDocTypeTag(/*tagName*/ undefined, (newTag as JSDocTypeTag).typeExpression, oldTag.comment); + } +} - public insertExportModifier(sourceFile: SourceFile, node: DeclarationStatement | VariableStatement): void { - this.insertText(sourceFile, node.getStart(sourceFile), "export "); - } +// find first non-whitespace position in the leading trivia of the node +function startPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node): number { + return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.IncludeAll }), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); +} - public insertImportSpecifierAtIndex(sourceFile: SourceFile, importSpecifier: ImportSpecifier, namedImports: NamedImports, index: number) { - const prevSpecifier = namedImports.elements[index - 1]; - if (prevSpecifier) { - this.insertNodeInListAfter(sourceFile, prevSpecifier, importSpecifier); +function endPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node, prevNode: Node | undefined, nextNode: Node): number { + const end = startPositionToDeleteNodeInList(sourceFile, nextNode); + if (prevNode === undefined || positionsAreOnSameLine(getAdjustedEndPosition(sourceFile, node, {}), end, sourceFile)) { + return end; + } + const token = findPrecedingToken(nextNode.getStart(sourceFile), sourceFile); + if (isSeparator(node, token)) { + const prevToken = findPrecedingToken(node.getStart(sourceFile), sourceFile); + if (isSeparator(prevNode, prevToken)) { + const pos = skipTrivia(sourceFile.text, token.getEnd(), /*stopAfterLineBreak*/ true, /*stopAtComments*/ true); + if (positionsAreOnSameLine(prevToken.getStart(sourceFile), token.getStart(sourceFile), sourceFile)) { + return isLineBreak(sourceFile.text.charCodeAt(pos - 1)) ? pos - 1 : pos; } - else { - this.insertNodeBefore( - sourceFile, - namedImports.elements[0], - importSpecifier, - !positionsAreOnSameLine(namedImports.elements[0].getStart(), namedImports.parent.parent.getStart(), sourceFile)); + if (isLineBreak(sourceFile.text.charCodeAt(pos))) { + return pos; } } + } + return end; +} - /** - * This function should be used to insert nodes in lists when nodes don't carry separators as the part of the node range, - * i.e. arguments in arguments lists, parameters in parameter lists etc. - * Note that separators are part of the node in statements and class elements. - */ - public insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node, containingList = formatting.SmartIndenter.getContainingList(after, sourceFile)): void { - if (!containingList) { - Debug.fail("node is not a list element"); - return; - } - const index = indexOfNode(containingList, after); - if (index < 0) { - return; - } - const end = after.getEnd(); - if (index !== containingList.length - 1) { - // any element except the last one - // use next sibling as an anchor - const nextToken = getTokenAtPosition(sourceFile, after.end); - if (nextToken && isSeparator(after, nextToken)) { - // for list - // a, b, c - // create change for adding 'e' after 'a' as - // - find start of next element after a (it is b) - // - use next element start as start and end position in final change - // - build text of change by formatting the text of node + whitespace trivia of b - - // in multiline case it will work as - // a, - // b, - // c, - // result - '*' denotes leading trivia that will be inserted after new text (displayed as '#') - // a, - // insertedtext# - // ###b, - // c, - const nextNode = containingList[index + 1]; - const startPos = skipWhitespacesAndLineBreaks(sourceFile.text, nextNode.getFullStart()); - - // write separator and leading trivia of the next element as suffix - const suffix = `${tokenToString(nextToken.kind)}${sourceFile.text.substring(nextToken.end, startPos)}`; - this.insertNodesAt(sourceFile, startPos, [newNode], { suffix }); - } - } - else { - const afterStart = after.getStart(sourceFile); - const afterStartLinePosition = getLineStartPositionForPosition(afterStart, sourceFile); - - let separator: SyntaxKind.CommaToken | SyntaxKind.SemicolonToken | undefined; - let multilineList = false; - - // insert element after the last element in the list that has more than one item - // pick the element preceding the after element to: - // - pick the separator - // - determine if list is a multiline - if (containingList.length === 1) { - // if list has only one element then we'll format is as multiline if node has comment in trailing trivia, or as singleline otherwise - // i.e. var x = 1 // this is x - // | new element will be inserted at this position - separator = SyntaxKind.CommaToken; - } - else { - // element has more than one element, pick separator from the list - const tokenBeforeInsertPosition = findPrecedingToken(after.pos, sourceFile); - separator = isSeparator(after, tokenBeforeInsertPosition) ? tokenBeforeInsertPosition.kind : SyntaxKind.CommaToken; - // determine if list is multiline by checking lines of after element and element that precedes it. - const afterMinusOneStartLinePosition = getLineStartPositionForPosition(containingList[index - 1].getStart(sourceFile), sourceFile); - multilineList = afterMinusOneStartLinePosition !== afterStartLinePosition; - } - if (hasCommentsBeforeLineBreak(sourceFile.text, after.end)) { - // in this case we'll always treat containing list as multiline - multilineList = true; - } - if (multilineList) { - // insert separator immediately following the 'after' node to preserve comments in trailing trivia - this.replaceRange(sourceFile, createRange(end), factory.createToken(separator)); - // use the same indentation as 'after' item - const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, this.formatContext.options); - // insert element before the line break on the line that contains 'after' element - let insertPos = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ false); - // find position before "\n" or "\r\n" - while (insertPos !== end && isLineBreak(sourceFile.text.charCodeAt(insertPos - 1))) { - insertPos--; - } - this.replaceRange(sourceFile, createRange(insertPos), newNode, { indentation, prefix: this.newLineCharacter }); - } - else { - this.replaceRange(sourceFile, createRange(end), newNode, { prefix: `${tokenToString(separator)} ` }); - } - } - } +function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, sourceFile: SourceFile): [number | undefined, number | undefined] { + const open = findChildOfKind(cls, SyntaxKind.OpenBraceToken, sourceFile); + const close = findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile); + return [open?.end, close?.end]; +} +function getMembersOrProperties(node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode): NodeArray { + return isObjectLiteralExpression(node) ? node.properties : node.members; +} - public parenthesizeExpression(sourceFile: SourceFile, expression: Expression) { - this.replaceRange(sourceFile, rangeOfNode(expression), factory.createParenthesizedExpression(expression)); - } +/** @internal */ +export type ValidateNonFormattedText = (node: Node, text: string) => void; - private finishClassesWithNodesInsertedAtStart(): void { - this.classesWithNodesInsertedAtStart.forEach(({ node, sourceFile }) => { - const [openBraceEnd, closeBraceEnd] = getClassOrObjectBraceEnds(node, sourceFile); - if (openBraceEnd !== undefined && closeBraceEnd !== undefined) { - const isEmpty = getMembersOrProperties(node).length === 0; - const isSingleLine = positionsAreOnSameLine(openBraceEnd, closeBraceEnd, sourceFile); - if (isEmpty && isSingleLine && openBraceEnd !== closeBraceEnd - 1) { - // For `class C { }` remove the whitespace inside the braces. - this.deleteRange(sourceFile, createRange(openBraceEnd, closeBraceEnd - 1)); - } - if (isSingleLine) { - this.insertText(sourceFile, closeBraceEnd - 1, this.newLineCharacter); - } - } - }); - } +/** @internal */ +export function getNewFileText(statements: readonly Statement[], scriptKind: ScriptKind, newLineCharacter: string, formatContext: formatting.FormatContext): string { + return changesToText.newFileChangesWorker(/*oldFile*/ undefined, scriptKind, statements, newLineCharacter, formatContext); +} - private finishDeleteDeclarations(): void { - const deletedNodesInLists = new Set(); // Stores nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`. - for (const { sourceFile, node } of this.deletedNodes) { - if (!this.deletedNodes.some(d => d.sourceFile === sourceFile && rangeContainsRangeExclusive(d.node, node))) { - if (isArray(node)) { - this.deleteRange(sourceFile, rangeOfTypeParameters(sourceFile, node)); - } - else { - deleteDeclaration.deleteDeclaration(this, deletedNodesInLists, sourceFile, node); - } +namespace changesToText { + export function getTextChangesFromChanges(changes: readonly Change[], newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): FileTextChanges[] { + return mapDefined(group(changes, c => c.sourceFile.path), changesInFile => { + const sourceFile = changesInFile[0].sourceFile; + // order changes by start position + // If the start position is the same, put the shorter range first, since an empty range (x, x) may precede (x, y) but not vice-versa. + const normalized = stableSort(changesInFile, (a, b) => (a.range.pos - b.range.pos) || (a.range.end - b.range.end)); + // verify that change intervals do not overlap, except possibly at end points. + for (let i = 0; i < normalized.length - 1; i++) { + Debug.assert(normalized[i].range.end <= normalized[i + 1].range.pos, "Changes overlap", () => + `${JSON.stringify(normalized[i].range)} and ${JSON.stringify(normalized[i + 1].range)}`); + } + + const textChanges = mapDefined(normalized, c => { + const span = createTextSpanFromRange(c.range); + const newText = computeNewText(c, sourceFile, newLineCharacter, formatContext, validate); + + // Filter out redundant changes. + if (span.length === newText.length && stringContainsAt(sourceFile.text, newText, span.start)) { + return undefined; } - } - deletedNodesInLists.forEach(node => { - const sourceFile = node.getSourceFile(); - const list = formatting.SmartIndenter.getContainingList(node, sourceFile)!; - if (node !== last(list)) return; - - const lastNonDeletedIndex = findLastIndex(list, n => !deletedNodesInLists.has(n), list.length - 2); - if (lastNonDeletedIndex !== -1) { - this.deleteRange(sourceFile, { pos: list[lastNonDeletedIndex].end, end: startPositionToDeleteNodeInList(sourceFile, list[lastNonDeletedIndex + 1]) }); - } + return createTextChange(span, newText); }); - } - /** - * Note: after calling this, the TextChanges object must be discarded! - * @param validate only for tests - * The reason we must validate as part of this method is that `getNonFormattedText` changes the node's positions, - * so we can only call this once and can't get the non-formatted text separately. - */ - public getChanges(validate?: ValidateNonFormattedText): FileTextChanges[] { - this.finishDeleteDeclarations(); - this.finishClassesWithNodesInsertedAtStart(); - const changes = changesToText.getTextChangesFromChanges(this.changes, this.newLineCharacter, this.formatContext, validate); - for (const { oldFile, fileName, statements } of this.newFiles) { - changes.push(changesToText.newFileChanges(oldFile, fileName, statements, this.newLineCharacter, this.formatContext)); - } - return changes; - } + return textChanges.length > 0 ? { fileName: sourceFile.fileName, textChanges } : undefined; + }); + } - public createNewFile(oldFile: SourceFile | undefined, fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[]): void { - this.newFiles.push({ oldFile, fileName, statements }); - } + export function newFileChanges(oldFile: SourceFile | undefined, fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], newLineCharacter: string, formatContext: formatting.FormatContext): FileTextChanges { + const text = newFileChangesWorker(oldFile, getScriptKindFromFileName(fileName), statements, newLineCharacter, formatContext); + return { fileName, textChanges: [createTextChange(createTextSpan(0, 0), text)], isNewFile: true }; } - function updateJSDocHost(parent: HasJSDoc): HasJSDoc { - if (parent.kind !== SyntaxKind.ArrowFunction) { - return parent; - } - const jsDocNode = parent.parent.kind === SyntaxKind.PropertyDeclaration ? - parent.parent as HasJSDoc : - parent.parent.parent as HasJSDoc; - jsDocNode.jsDoc = parent.jsDoc; - jsDocNode.jsDocCache = parent.jsDocCache; - return jsDocNode; + export function newFileChangesWorker(oldFile: SourceFile | undefined, scriptKind: ScriptKind, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], newLineCharacter: string, formatContext: formatting.FormatContext): string { + // TODO: this emits the file, parses it back, then formats it that -- may be a less roundabout way to do this + const nonFormattedText = statements.map(s => s === SyntaxKind.NewLineTrivia ? "" : getNonformattedText(s, oldFile, newLineCharacter).text).join(newLineCharacter); + const sourceFile = createSourceFile("any file name", nonFormattedText, ScriptTarget.ESNext, /*setParentNodes*/ true, scriptKind); + const changes = formatting.formatDocument(sourceFile, formatContext); + return applyChanges(nonFormattedText, changes) + newLineCharacter; } - function tryMergeJsdocTags(oldTag: JSDocTag, newTag: JSDocTag): JSDocTag | undefined { - if (oldTag.kind !== newTag.kind) { - return undefined; + function computeNewText(change: Change, sourceFile: SourceFile, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string { + if (change.kind === ChangeKind.Remove) { + return ""; } - switch (oldTag.kind) { - case SyntaxKind.JSDocParameterTag: { - const oldParam = oldTag as JSDocParameterTag; - const newParam = newTag as JSDocParameterTag; - return isIdentifier(oldParam.name) && isIdentifier(newParam.name) && oldParam.name.escapedText === newParam.name.escapedText - ? factory.createJSDocParameterTag(/*tagName*/ undefined, newParam.name, /*isBracketed*/ false, newParam.typeExpression, newParam.isNameFirst, oldParam.comment) - : undefined; - } - case SyntaxKind.JSDocReturnTag: - return factory.createJSDocReturnTag(/*tagName*/ undefined, (newTag as JSDocReturnTag).typeExpression, oldTag.comment); - case SyntaxKind.JSDocTypeTag: - return factory.createJSDocTypeTag(/*tagName*/ undefined, (newTag as JSDocTypeTag).typeExpression, oldTag.comment); + if (change.kind === ChangeKind.Text) { + return change.text; } - } - // find first non-whitespace position in the leading trivia of the node - function startPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node): number { - return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.IncludeAll }), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + const { options = {}, range: { pos } } = change; + const format = (n: Node) => getFormattedTextOfNode(n, sourceFile, pos, options, newLineCharacter, formatContext, validate); + const text = change.kind === ChangeKind.ReplaceWithMultipleNodes + ? change.nodes.map(n => removeSuffix(format(n), newLineCharacter)).join(change.options?.joiner || newLineCharacter) + : format(change.node); + // strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line + const noIndent = (options.indentation !== undefined || getLineStartPositionForPosition(pos, sourceFile) === pos) ? text : text.replace(/^\s+/, ""); + return (options.prefix || "") + noIndent + + ((!options.suffix || endsWith(noIndent, options.suffix)) + ? "" : options.suffix); } - function endPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node, prevNode: Node | undefined, nextNode: Node): number { - const end = startPositionToDeleteNodeInList(sourceFile, nextNode); - if (prevNode === undefined || positionsAreOnSameLine(getAdjustedEndPosition(sourceFile, node, {}), end, sourceFile)) { - return end; + /** Note: this may mutate `nodeIn`. */ + function getFormattedTextOfNode(nodeIn: Node, sourceFile: SourceFile, pos: number, { indentation, prefix, delta }: InsertNodeOptions, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string { + const { node, text } = getNonformattedText(nodeIn, sourceFile, newLineCharacter); + if (validate) validate(node, text); + const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); + const initialIndentation = + indentation !== undefined + ? indentation + : formatting.SmartIndenter.getIndentation(pos, sourceFile, formatOptions, prefix === newLineCharacter || getLineStartPositionForPosition(pos, sourceFile) === pos); + if (delta === undefined) { + delta = formatting.SmartIndenter.shouldIndentChildNode(formatOptions, nodeIn) ? (formatOptions.indentSize || 0) : 0; } - const token = findPrecedingToken(nextNode.getStart(sourceFile), sourceFile); - if (isSeparator(node, token)) { - const prevToken = findPrecedingToken(node.getStart(sourceFile), sourceFile); - if (isSeparator(prevNode, prevToken)) { - const pos = skipTrivia(sourceFile.text, token.getEnd(), /*stopAfterLineBreak*/ true, /*stopAtComments*/ true); - if (positionsAreOnSameLine(prevToken.getStart(sourceFile), token.getStart(sourceFile), sourceFile)) { - return isLineBreak(sourceFile.text.charCodeAt(pos - 1)) ? pos - 1 : pos; - } - if (isLineBreak(sourceFile.text.charCodeAt(pos))) { - return pos; - } + + const file: SourceFileLike = { + text, + getLineAndCharacterOfPosition(pos) { + return getLineAndCharacterOfPosition(this, pos); } - } - return end; + }; + const changes = formatting.formatNodeGivenIndentation(node, file, sourceFile.languageVariant, initialIndentation, delta, { ...formatContext, options: formatOptions }); + return applyChanges(text, changes); } - function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, sourceFile: SourceFile): [number | undefined, number | undefined] { - const open = findChildOfKind(cls, SyntaxKind.OpenBraceToken, sourceFile); - const close = findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile); - return [open?.end, close?.end]; - } - function getMembersOrProperties(node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode): NodeArray { - return isObjectLiteralExpression(node) ? node.properties : node.members; + /** Note: output node may be mutated input node. */ + export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } { + const writer = createWriter(newLineCharacter); + const newLine = getNewLineKind(newLineCharacter); + createPrinter({ + newLine, + neverAsciiEscape: true, + preserveSourceNewlines: true, + terminateUnterminatedLiterals: true + }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer); + return { text: writer.getText(), node: assignPositionsToNode(node) }; } +} - export type ValidateNonFormattedText = (node: Node, text: string) => void; - - export function getNewFileText(statements: readonly Statement[], scriptKind: ScriptKind, newLineCharacter: string, formatContext: formatting.FormatContext): string { - return changesToText.newFileChangesWorker(/*oldFile*/ undefined, scriptKind, statements, newLineCharacter, formatContext); +/** @internal */ +export function applyChanges(text: string, changes: readonly TextChange[]): string { + for (let i = changes.length - 1; i >= 0; i--) { + const { span, newText } = changes[i]; + text = `${text.substring(0, span.start)}${newText}${text.substring(textSpanEnd(span))}`; } + return text; +} - namespace changesToText { - export function getTextChangesFromChanges(changes: readonly Change[], newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): FileTextChanges[] { - return mapDefined(group(changes, c => c.sourceFile.path), changesInFile => { - const sourceFile = changesInFile[0].sourceFile; - // order changes by start position - // If the start position is the same, put the shorter range first, since an empty range (x, x) may precede (x, y) but not vice-versa. - const normalized = stableSort(changesInFile, (a, b) => (a.range.pos - b.range.pos) || (a.range.end - b.range.end)); - // verify that change intervals do not overlap, except possibly at end points. - for (let i = 0; i < normalized.length - 1; i++) { - Debug.assert(normalized[i].range.end <= normalized[i + 1].range.pos, "Changes overlap", () => - `${JSON.stringify(normalized[i].range)} and ${JSON.stringify(normalized[i + 1].range)}`); - } +function isTrivia(s: string) { + return skipTrivia(s, 0) === s.length; +} - const textChanges = mapDefined(normalized, c => { - const span = createTextSpanFromRange(c.range); - const newText = computeNewText(c, sourceFile, newLineCharacter, formatContext, validate); +// A transformation context that won't perform parenthesization, as some parenthesization rules +// are more aggressive than is strictly necessary. +const textChangesTransformationContext: TransformationContext = { + ...nullTransformationContext, + factory: createNodeFactory( + nullTransformationContext.factory.flags | NodeFactoryFlags.NoParenthesizerRules, + nullTransformationContext.factory.baseFactory), +}; + +/** @internal */ +export function assignPositionsToNode(node: Node): Node { + const visited = visitEachChild(node, assignPositionsToNode, textChangesTransformationContext, assignPositionsToNodeArray, assignPositionsToNode); + // create proxy node for non synthesized nodes + const newNode = nodeIsSynthesized(visited) ? visited : Object.create(visited) as Node; + setTextRangePosEnd(newNode, getPos(node), getEnd(node)); + return newNode; +} - // Filter out redundant changes. - if (span.length === newText.length && stringContainsAt(sourceFile.text, newText, span.start)) { - return undefined; - } +function assignPositionsToNodeArray(nodes: NodeArray, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number) { + const visited = visitNodes(nodes, visitor, test, start, count); + if (!visited) { + return visited; + } + // clone nodearray if necessary + const nodeArray = visited === nodes ? factory.createNodeArray(visited.slice(0)) : visited; + setTextRangePosEnd(nodeArray, getPos(nodes), getEnd(nodes)); + return nodeArray; +} - return createTextChange(span, newText); - }); +/** @internal */ +export interface TextChangesWriter extends EmitTextWriter, PrintHandlers {} - return textChanges.length > 0 ? { fileName: sourceFile.fileName, textChanges } : undefined; - }); - } +/** @internal */ +export function createWriter(newLine: string): TextChangesWriter { + let lastNonTriviaPosition = 0; - export function newFileChanges(oldFile: SourceFile | undefined, fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], newLineCharacter: string, formatContext: formatting.FormatContext): FileTextChanges { - const text = newFileChangesWorker(oldFile, getScriptKindFromFileName(fileName), statements, newLineCharacter, formatContext); - return { fileName, textChanges: [createTextChange(createTextSpan(0, 0), text)], isNewFile: true }; + const writer = createTextWriter(newLine); + const onBeforeEmitNode: PrintHandlers["onBeforeEmitNode"] = node => { + if (node) { + setPos(node, lastNonTriviaPosition); } - - export function newFileChangesWorker(oldFile: SourceFile | undefined, scriptKind: ScriptKind, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], newLineCharacter: string, formatContext: formatting.FormatContext): string { - // TODO: this emits the file, parses it back, then formats it that -- may be a less roundabout way to do this - const nonFormattedText = statements.map(s => s === SyntaxKind.NewLineTrivia ? "" : getNonformattedText(s, oldFile, newLineCharacter).text).join(newLineCharacter); - const sourceFile = createSourceFile("any file name", nonFormattedText, ScriptTarget.ESNext, /*setParentNodes*/ true, scriptKind); - const changes = formatting.formatDocument(sourceFile, formatContext); - return applyChanges(nonFormattedText, changes) + newLineCharacter; + }; + const onAfterEmitNode: PrintHandlers["onAfterEmitNode"] = node => { + if (node) { + setEnd(node, lastNonTriviaPosition); } - - function computeNewText(change: Change, sourceFile: SourceFile, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string { - if (change.kind === ChangeKind.Remove) { - return ""; - } - if (change.kind === ChangeKind.Text) { - return change.text; - } - - const { options = {}, range: { pos } } = change; - const format = (n: Node) => getFormattedTextOfNode(n, sourceFile, pos, options, newLineCharacter, formatContext, validate); - const text = change.kind === ChangeKind.ReplaceWithMultipleNodes - ? change.nodes.map(n => removeSuffix(format(n), newLineCharacter)).join(change.options?.joiner || newLineCharacter) - : format(change.node); - // strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line - const noIndent = (options.indentation !== undefined || getLineStartPositionForPosition(pos, sourceFile) === pos) ? text : text.replace(/^\s+/, ""); - return (options.prefix || "") + noIndent - + ((!options.suffix || endsWith(noIndent, options.suffix)) - ? "" : options.suffix); - } - - /** Note: this may mutate `nodeIn`. */ - function getFormattedTextOfNode(nodeIn: Node, sourceFile: SourceFile, pos: number, { indentation, prefix, delta }: InsertNodeOptions, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string { - const { node, text } = getNonformattedText(nodeIn, sourceFile, newLineCharacter); - if (validate) validate(node, text); - const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); - const initialIndentation = - indentation !== undefined - ? indentation - : formatting.SmartIndenter.getIndentation(pos, sourceFile, formatOptions, prefix === newLineCharacter || getLineStartPositionForPosition(pos, sourceFile) === pos); - if (delta === undefined) { - delta = formatting.SmartIndenter.shouldIndentChildNode(formatOptions, nodeIn) ? (formatOptions.indentSize || 0) : 0; - } - - const file: SourceFileLike = { - text, - getLineAndCharacterOfPosition(pos) { - return getLineAndCharacterOfPosition(this, pos); - } - }; - const changes = formatting.formatNodeGivenIndentation(node, file, sourceFile.languageVariant, initialIndentation, delta, { ...formatContext, options: formatOptions }); - return applyChanges(text, changes); + }; + const onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"] = nodes => { + if (nodes) { + setPos(nodes, lastNonTriviaPosition); } - - /** Note: output node may be mutated input node. */ - export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } { - const writer = createWriter(newLineCharacter); - const newLine = getNewLineKind(newLineCharacter); - createPrinter({ - newLine, - neverAsciiEscape: true, - preserveSourceNewlines: true, - terminateUnterminatedLiterals: true - }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer); - return { text: writer.getText(), node: assignPositionsToNode(node) }; + }; + const onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"] = nodes => { + if (nodes) { + setEnd(nodes, lastNonTriviaPosition); } - } + }; + const onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"] = node => { + if (node) { + setPos(node, lastNonTriviaPosition); + } + }; + const onAfterEmitToken: PrintHandlers["onAfterEmitToken"] = node => { + if (node) { + setEnd(node, lastNonTriviaPosition); + } + }; - export function applyChanges(text: string, changes: readonly TextChange[]): string { - for (let i = changes.length - 1; i >= 0; i--) { - const { span, newText } = changes[i]; - text = `${text.substring(0, span.start)}${newText}${text.substring(textSpanEnd(span))}`; + function setLastNonTriviaPosition(s: string, force: boolean) { + if (force || !isTrivia(s)) { + lastNonTriviaPosition = writer.getTextPos(); + let i = 0; + while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) { + i++; + } + // trim trailing whitespaces + lastNonTriviaPosition -= i; } - return text; } - function isTrivia(s: string) { - return skipTrivia(s, 0) === s.length; + function write(s: string): void { + writer.write(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeComment(s: string): void { + writer.writeComment(s); + } + function writeKeyword(s: string): void { + writer.writeKeyword(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeOperator(s: string): void { + writer.writeOperator(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writePunctuation(s: string): void { + writer.writePunctuation(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeTrailingSemicolon(s: string): void { + writer.writeTrailingSemicolon(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeParameter(s: string): void { + writer.writeParameter(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeProperty(s: string): void { + writer.writeProperty(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeSpace(s: string): void { + writer.writeSpace(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeStringLiteral(s: string): void { + writer.writeStringLiteral(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeSymbol(s: string, sym: Symbol): void { + writer.writeSymbol(s, sym); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeLine(force?: boolean): void { + writer.writeLine(force); + } + function increaseIndent(): void { + writer.increaseIndent(); + } + function decreaseIndent(): void { + writer.decreaseIndent(); + } + function getText(): string { + return writer.getText(); + } + function rawWrite(s: string): void { + writer.rawWrite(s); + setLastNonTriviaPosition(s, /*force*/ false); + } + function writeLiteral(s: string): void { + writer.writeLiteral(s); + setLastNonTriviaPosition(s, /*force*/ true); + } + function getTextPos(): number { + return writer.getTextPos(); + } + function getLine(): number { + return writer.getLine(); + } + function getColumn(): number { + return writer.getColumn(); + } + function getIndent(): number { + return writer.getIndent(); + } + function isAtStartOfLine(): boolean { + return writer.isAtStartOfLine(); + } + function clear(): void { + writer.clear(); + lastNonTriviaPosition = 0; } - // A transformation context that won't perform parenthesization, as some parenthesization rules - // are more aggressive than is strictly necessary. - const textChangesTransformationContext: TransformationContext = { - ...nullTransformationContext, - factory: createNodeFactory( - nullTransformationContext.factory.flags | NodeFactoryFlags.NoParenthesizerRules, - nullTransformationContext.factory.baseFactory), + return { + onBeforeEmitNode, + onAfterEmitNode, + onBeforeEmitNodeArray, + onAfterEmitNodeArray, + onBeforeEmitToken, + onAfterEmitToken, + write, + writeComment, + writeKeyword, + writeOperator, + writePunctuation, + writeTrailingSemicolon, + writeParameter, + writeProperty, + writeSpace, + writeStringLiteral, + writeSymbol, + writeLine, + increaseIndent, + decreaseIndent, + getText, + rawWrite, + writeLiteral, + getTextPos, + getLine, + getColumn, + getIndent, + isAtStartOfLine, + hasTrailingComment: () => writer.hasTrailingComment(), + hasTrailingWhitespace: () => writer.hasTrailingWhitespace(), + clear }; +} - export function assignPositionsToNode(node: Node): Node { - const visited = visitEachChild(node, assignPositionsToNode, textChangesTransformationContext, assignPositionsToNodeArray, assignPositionsToNode); - // create proxy node for non synthesized nodes - const newNode = nodeIsSynthesized(visited) ? visited : Object.create(visited) as Node; - setTextRangePosEnd(newNode, getPos(node), getEnd(node)); - return newNode; - } - - function assignPositionsToNodeArray(nodes: NodeArray, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number) { - const visited = visitNodes(nodes, visitor, test, start, count); - if (!visited) { - return visited; +function getInsertionPositionAtSourceFileTop(sourceFile: SourceFile): number { + let lastPrologue: PrologueDirective | undefined; + for (const node of sourceFile.statements) { + if (isPrologueDirective(node)) { + lastPrologue = node; + } + else { + break; } - // clone nodearray if necessary - const nodeArray = visited === nodes ? factory.createNodeArray(visited.slice(0)) : visited; - setTextRangePosEnd(nodeArray, getPos(nodes), getEnd(nodes)); - return nodeArray; } - interface TextChangesWriter extends EmitTextWriter, PrintHandlers {} + let position = 0; + const text = sourceFile.text; + if (lastPrologue) { + position = lastPrologue.end; + advancePastLineBreak(); + return position; + } - export function createWriter(newLine: string): TextChangesWriter { - let lastNonTriviaPosition = 0; + const shebang = getShebang(text); + if (shebang !== undefined) { + position = shebang.length; + advancePastLineBreak(); + } - const writer = createTextWriter(newLine); - const onBeforeEmitNode: PrintHandlers["onBeforeEmitNode"] = node => { - if (node) { - setPos(node, lastNonTriviaPosition); - } - }; - const onAfterEmitNode: PrintHandlers["onAfterEmitNode"] = node => { - if (node) { - setEnd(node, lastNonTriviaPosition); - } - }; - const onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"] = nodes => { - if (nodes) { - setPos(nodes, lastNonTriviaPosition); - } - }; - const onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"] = nodes => { - if (nodes) { - setEnd(nodes, lastNonTriviaPosition); - } - }; - const onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"] = node => { - if (node) { - setPos(node, lastNonTriviaPosition); - } - }; - const onAfterEmitToken: PrintHandlers["onAfterEmitToken"] = node => { - if (node) { - setEnd(node, lastNonTriviaPosition); - } - }; + const ranges = getLeadingCommentRanges(text, position); + if (!ranges) return position; - function setLastNonTriviaPosition(s: string, force: boolean) { - if (force || !isTrivia(s)) { - lastNonTriviaPosition = writer.getTextPos(); - let i = 0; - while (isWhiteSpaceLike(s.charCodeAt(s.length - i - 1))) { - i++; - } - // trim trailing whitespaces - lastNonTriviaPosition -= i; + // Find the first attached comment to the first node and add before it + let lastComment: { range: CommentRange; pinnedOrTripleSlash: boolean; } | undefined; + let firstNodeLine: number | undefined; + for (const range of ranges) { + if (range.kind === SyntaxKind.MultiLineCommentTrivia) { + if (isPinnedComment(text, range.pos)) { + lastComment = { range, pinnedOrTripleSlash: true }; + continue; } } - - function write(s: string): void { - writer.write(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeComment(s: string): void { - writer.writeComment(s); - } - function writeKeyword(s: string): void { - writer.writeKeyword(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeOperator(s: string): void { - writer.writeOperator(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writePunctuation(s: string): void { - writer.writePunctuation(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeTrailingSemicolon(s: string): void { - writer.writeTrailingSemicolon(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeParameter(s: string): void { - writer.writeParameter(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeProperty(s: string): void { - writer.writeProperty(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeSpace(s: string): void { - writer.writeSpace(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeStringLiteral(s: string): void { - writer.writeStringLiteral(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeSymbol(s: string, sym: Symbol): void { - writer.writeSymbol(s, sym); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeLine(force?: boolean): void { - writer.writeLine(force); - } - function increaseIndent(): void { - writer.increaseIndent(); - } - function decreaseIndent(): void { - writer.decreaseIndent(); - } - function getText(): string { - return writer.getText(); - } - function rawWrite(s: string): void { - writer.rawWrite(s); - setLastNonTriviaPosition(s, /*force*/ false); - } - function writeLiteral(s: string): void { - writer.writeLiteral(s); - setLastNonTriviaPosition(s, /*force*/ true); - } - function getTextPos(): number { - return writer.getTextPos(); - } - function getLine(): number { - return writer.getLine(); - } - function getColumn(): number { - return writer.getColumn(); - } - function getIndent(): number { - return writer.getIndent(); - } - function isAtStartOfLine(): boolean { - return writer.isAtStartOfLine(); - } - function clear(): void { - writer.clear(); - lastNonTriviaPosition = 0; + else if (isRecognizedTripleSlashComment(text, range.pos, range.end)) { + lastComment = { range, pinnedOrTripleSlash: true }; + continue; } - return { - onBeforeEmitNode, - onAfterEmitNode, - onBeforeEmitNodeArray, - onAfterEmitNodeArray, - onBeforeEmitToken, - onAfterEmitToken, - write, - writeComment, - writeKeyword, - writeOperator, - writePunctuation, - writeTrailingSemicolon, - writeParameter, - writeProperty, - writeSpace, - writeStringLiteral, - writeSymbol, - writeLine, - increaseIndent, - decreaseIndent, - getText, - rawWrite, - writeLiteral, - getTextPos, - getLine, - getColumn, - getIndent, - isAtStartOfLine, - hasTrailingComment: () => writer.hasTrailingComment(), - hasTrailingWhitespace: () => writer.hasTrailingWhitespace(), - clear - }; - } + if (lastComment) { + // Always insert after pinned or triple slash comments + if (lastComment.pinnedOrTripleSlash) break; - function getInsertionPositionAtSourceFileTop(sourceFile: SourceFile): number { - let lastPrologue: PrologueDirective | undefined; - for (const node of sourceFile.statements) { - if (isPrologueDirective(node)) { - lastPrologue = node; - } - else { - break; - } + // There was a blank line between the last comment and this comment. + // This comment is not part of the copyright comments + const commentLine = sourceFile.getLineAndCharacterOfPosition(range.pos).line; + const lastCommentEndLine = sourceFile.getLineAndCharacterOfPosition(lastComment.range.end).line; + if (commentLine >= lastCommentEndLine + 2) break; } - let position = 0; - const text = sourceFile.text; - if (lastPrologue) { - position = lastPrologue.end; - advancePastLineBreak(); - return position; + if (sourceFile.statements.length) { + if (firstNodeLine === undefined) firstNodeLine = sourceFile.getLineAndCharacterOfPosition(sourceFile.statements[0].getStart()).line; + const commentEndLine = sourceFile.getLineAndCharacterOfPosition(range.end).line; + if (firstNodeLine < commentEndLine + 2) break; } + lastComment = { range, pinnedOrTripleSlash: false }; + } - const shebang = getShebang(text); - if (shebang !== undefined) { - position = shebang.length; - advancePastLineBreak(); - } + if (lastComment) { + position = lastComment.range.end; + advancePastLineBreak(); + } + return position; - const ranges = getLeadingCommentRanges(text, position); - if (!ranges) return position; + function advancePastLineBreak() { + if (position < text.length) { + const charCode = text.charCodeAt(position); + if (isLineBreak(charCode)) { + position++; - // Find the first attached comment to the first node and add before it - let lastComment: { range: CommentRange; pinnedOrTripleSlash: boolean; } | undefined; - let firstNodeLine: number | undefined; - for (const range of ranges) { - if (range.kind === SyntaxKind.MultiLineCommentTrivia) { - if (isPinnedComment(text, range.pos)) { - lastComment = { range, pinnedOrTripleSlash: true }; - continue; + if (position < text.length && charCode === CharacterCodes.carriageReturn && text.charCodeAt(position) === CharacterCodes.lineFeed) { + position++; } } - else if (isRecognizedTripleSlashComment(text, range.pos, range.end)) { - lastComment = { range, pinnedOrTripleSlash: true }; - continue; - } - - if (lastComment) { - // Always insert after pinned or triple slash comments - if (lastComment.pinnedOrTripleSlash) break; - - // There was a blank line between the last comment and this comment. - // This comment is not part of the copyright comments - const commentLine = sourceFile.getLineAndCharacterOfPosition(range.pos).line; - const lastCommentEndLine = sourceFile.getLineAndCharacterOfPosition(lastComment.range.end).line; - if (commentLine >= lastCommentEndLine + 2) break; - } - - if (sourceFile.statements.length) { - if (firstNodeLine === undefined) firstNodeLine = sourceFile.getLineAndCharacterOfPosition(sourceFile.statements[0].getStart()).line; - const commentEndLine = sourceFile.getLineAndCharacterOfPosition(range.end).line; - if (firstNodeLine < commentEndLine + 2) break; - } - lastComment = { range, pinnedOrTripleSlash: false }; } + } +} - if (lastComment) { - position = lastComment.range.end; - advancePastLineBreak(); - } - return position; +/** @internal */ +export function isValidLocationToAddComment(sourceFile: SourceFile, position: number) { + return !isInComment(sourceFile, position) && !isInString(sourceFile, position) && !isInTemplateString(sourceFile, position) && !isInJSXText(sourceFile, position); +} - function advancePastLineBreak() { - if (position < text.length) { - const charCode = text.charCodeAt(position); - if (isLineBreak(charCode)) { - position++; +function needSemicolonBetween(a: Node, b: Node): boolean { + return (isPropertySignature(a) || isPropertyDeclaration(a)) && isClassOrTypeElement(b) && b.name!.kind === SyntaxKind.ComputedPropertyName + || isStatementButNotDeclaration(a) && isStatementButNotDeclaration(b); // TODO: only if b would start with a `(` or `[` +} - if (position < text.length && charCode === CharacterCodes.carriageReturn && text.charCodeAt(position) === CharacterCodes.lineFeed) { - position++; - } +namespace deleteDeclaration { + export function deleteDeclaration(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: Node): void { + switch (node.kind) { + case SyntaxKind.Parameter: { + const oldFunction = node.parent; + if (isArrowFunction(oldFunction) && + oldFunction.parameters.length === 1 && + !findChildOfKind(oldFunction, SyntaxKind.OpenParenToken, sourceFile)) { + // Lambdas with exactly one parameter are special because, after removal, there + // must be an empty parameter list (i.e. `()`) and this won't necessarily be the + // case if the parameter is simply removed (e.g. in `x => 1`). + changes.replaceNodeWithText(sourceFile, node, "()"); } + else { + deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); + } + break; } - } - } - export function isValidLocationToAddComment(sourceFile: SourceFile, position: number) { - return !isInComment(sourceFile, position) && !isInString(sourceFile, position) && !isInTemplateString(sourceFile, position) && !isInJSXText(sourceFile, position); - } - - function needSemicolonBetween(a: Node, b: Node): boolean { - return (isPropertySignature(a) || isPropertyDeclaration(a)) && isClassOrTypeElement(b) && b.name!.kind === SyntaxKind.ComputedPropertyName - || isStatementButNotDeclaration(a) && isStatementButNotDeclaration(b); // TODO: only if b would start with a `(` or `[` - } + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + const isFirstImport = sourceFile.imports.length && node === first(sourceFile.imports).parent || node === find(sourceFile.statements, isAnyImportSyntax); + // For first import, leave header comment in place, otherwise only delete JSDoc comments + deleteNode(changes, sourceFile, node, { + leadingTriviaOption: isFirstImport ? LeadingTriviaOption.Exclude : hasJSDocNodes(node) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine, + }); + break; - namespace deleteDeclaration { - export function deleteDeclaration(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: Node): void { - switch (node.kind) { - case SyntaxKind.Parameter: { - const oldFunction = node.parent; - if (isArrowFunction(oldFunction) && - oldFunction.parameters.length === 1 && - !findChildOfKind(oldFunction, SyntaxKind.OpenParenToken, sourceFile)) { - // Lambdas with exactly one parameter are special because, after removal, there - // must be an empty parameter list (i.e. `()`) and this won't necessarily be the - // case if the parameter is simply removed (e.g. in `x => 1`). - changes.replaceNodeWithText(sourceFile, node, "()"); - } - else { - deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - } - break; + case SyntaxKind.BindingElement: + const pattern = (node as BindingElement).parent; + const preserveComma = pattern.kind === SyntaxKind.ArrayBindingPattern && node !== last(pattern.elements); + if (preserveComma) { + deleteNode(changes, sourceFile, node); } + else { + deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); + } + break; - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - const isFirstImport = sourceFile.imports.length && node === first(sourceFile.imports).parent || node === find(sourceFile.statements, isAnyImportSyntax); - // For first import, leave header comment in place, otherwise only delete JSDoc comments - deleteNode(changes, sourceFile, node, { - leadingTriviaOption: isFirstImport ? LeadingTriviaOption.Exclude : hasJSDocNodes(node) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine, - }); - break; - - case SyntaxKind.BindingElement: - const pattern = (node as BindingElement).parent; - const preserveComma = pattern.kind === SyntaxKind.ArrayBindingPattern && node !== last(pattern.elements); - if (preserveComma) { - deleteNode(changes, sourceFile, node); - } - else { - deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - } - break; + case SyntaxKind.VariableDeclaration: + deleteVariableDeclaration(changes, deletedNodesInLists, sourceFile, node as VariableDeclaration); + break; - case SyntaxKind.VariableDeclaration: - deleteVariableDeclaration(changes, deletedNodesInLists, sourceFile, node as VariableDeclaration); - break; + case SyntaxKind.TypeParameter: + deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); + break; - case SyntaxKind.TypeParameter: + case SyntaxKind.ImportSpecifier: + const namedImports = (node as ImportSpecifier).parent; + if (namedImports.elements.length === 1) { + deleteImportBinding(changes, sourceFile, namedImports); + } + else { deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - break; - - case SyntaxKind.ImportSpecifier: - const namedImports = (node as ImportSpecifier).parent; - if (namedImports.elements.length === 1) { - deleteImportBinding(changes, sourceFile, namedImports); - } - else { - deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - } - break; - - case SyntaxKind.NamespaceImport: - deleteImportBinding(changes, sourceFile, node as NamespaceImport); - break; + } + break; - case SyntaxKind.SemicolonToken: - deleteNode(changes, sourceFile, node, { trailingTriviaOption: TrailingTriviaOption.Exclude }); - break; + case SyntaxKind.NamespaceImport: + deleteImportBinding(changes, sourceFile, node as NamespaceImport); + break; - case SyntaxKind.FunctionKeyword: - deleteNode(changes, sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.Exclude }); - break; + case SyntaxKind.SemicolonToken: + deleteNode(changes, sourceFile, node, { trailingTriviaOption: TrailingTriviaOption.Exclude }); + break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.FunctionDeclaration: - deleteNode(changes, sourceFile, node, { leadingTriviaOption: hasJSDocNodes(node) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine }); - break; + case SyntaxKind.FunctionKeyword: + deleteNode(changes, sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.Exclude }); + break; - default: - if (!node.parent) { - // a misbehaving client can reach here with the SourceFile node - deleteNode(changes, sourceFile, node); - } - else if (isImportClause(node.parent) && node.parent.name === node) { - deleteDefaultImport(changes, sourceFile, node.parent); - } - else if (isCallExpression(node.parent) && contains(node.parent.arguments, node)) { - deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - } - else { - deleteNode(changes, sourceFile, node); - } - } - } + case SyntaxKind.ClassDeclaration: + case SyntaxKind.FunctionDeclaration: + deleteNode(changes, sourceFile, node, { leadingTriviaOption: hasJSDocNodes(node) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine }); + break; - function deleteDefaultImport(changes: ChangeTracker, sourceFile: SourceFile, importClause: ImportClause): void { - if (!importClause.namedBindings) { - // Delete the whole import - deleteNode(changes, sourceFile, importClause.parent); - } - else { - // import |d,| * as ns from './file' - const start = importClause.name!.getStart(sourceFile); - const nextToken = getTokenAtPosition(sourceFile, importClause.name!.end); - if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { - // shift first non-whitespace position after comma to the start position of the node - const end = skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true); - changes.deleteRange(sourceFile, { pos: start, end }); + default: + if (!node.parent) { + // a misbehaving client can reach here with the SourceFile node + deleteNode(changes, sourceFile, node); + } + else if (isImportClause(node.parent) && node.parent.name === node) { + deleteDefaultImport(changes, sourceFile, node.parent); + } + else if (isCallExpression(node.parent) && contains(node.parent.arguments, node)) { + deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); } else { - deleteNode(changes, sourceFile, importClause.name!); + deleteNode(changes, sourceFile, node); } - } } + } - function deleteImportBinding(changes: ChangeTracker, sourceFile: SourceFile, node: NamedImportBindings): void { - if (node.parent.name) { - // Delete named imports while preserving the default import - // import d|, * as ns| from './file' - // import d|, { a }| from './file' - const previousToken = Debug.checkDefined(getTokenAtPosition(sourceFile, node.pos - 1)); - changes.deleteRange(sourceFile, { pos: previousToken.getStart(sourceFile), end: node.end }); + function deleteDefaultImport(changes: ChangeTracker, sourceFile: SourceFile, importClause: ImportClause): void { + if (!importClause.namedBindings) { + // Delete the whole import + deleteNode(changes, sourceFile, importClause.parent); + } + else { + // import |d,| * as ns from './file' + const start = importClause.name!.getStart(sourceFile); + const nextToken = getTokenAtPosition(sourceFile, importClause.name!.end); + if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { + // shift first non-whitespace position after comma to the start position of the node + const end = skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true); + changes.deleteRange(sourceFile, { pos: start, end }); } else { - // Delete the entire import declaration - // |import * as ns from './file'| - // |import { a } from './file'| - const importDecl = getAncestor(node, SyntaxKind.ImportDeclaration)!; - deleteNode(changes, sourceFile, importDecl); + deleteNode(changes, sourceFile, importClause.name!); } } + } - function deleteVariableDeclaration(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: VariableDeclaration): void { - const { parent } = node; + function deleteImportBinding(changes: ChangeTracker, sourceFile: SourceFile, node: NamedImportBindings): void { + if (node.parent.name) { + // Delete named imports while preserving the default import + // import d|, * as ns| from './file' + // import d|, { a }| from './file' + const previousToken = Debug.checkDefined(getTokenAtPosition(sourceFile, node.pos - 1)); + changes.deleteRange(sourceFile, { pos: previousToken.getStart(sourceFile), end: node.end }); + } + else { + // Delete the entire import declaration + // |import * as ns from './file'| + // |import { a } from './file'| + const importDecl = getAncestor(node, SyntaxKind.ImportDeclaration)!; + deleteNode(changes, sourceFile, importDecl); + } + } - if (parent.kind === SyntaxKind.CatchClause) { - // TODO: There's currently no unused diagnostic for this, could be a suggestion - changes.deleteNodeRange(sourceFile, findChildOfKind(parent, SyntaxKind.OpenParenToken, sourceFile)!, findChildOfKind(parent, SyntaxKind.CloseParenToken, sourceFile)!); - return; - } + function deleteVariableDeclaration(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: VariableDeclaration): void { + const { parent } = node; - if (parent.declarations.length !== 1) { - deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); - return; - } + if (parent.kind === SyntaxKind.CatchClause) { + // TODO: There's currently no unused diagnostic for this, could be a suggestion + changes.deleteNodeRange(sourceFile, findChildOfKind(parent, SyntaxKind.OpenParenToken, sourceFile)!, findChildOfKind(parent, SyntaxKind.CloseParenToken, sourceFile)!); + return; + } - const gp = parent.parent; - switch (gp.kind) { - case SyntaxKind.ForOfStatement: - case SyntaxKind.ForInStatement: - changes.replaceNode(sourceFile, node, factory.createObjectLiteralExpression()); - break; + if (parent.declarations.length !== 1) { + deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); + return; + } - case SyntaxKind.ForStatement: - deleteNode(changes, sourceFile, parent); - break; + const gp = parent.parent; + switch (gp.kind) { + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForInStatement: + changes.replaceNode(sourceFile, node, factory.createObjectLiteralExpression()); + break; - case SyntaxKind.VariableStatement: - deleteNode(changes, sourceFile, gp, { leadingTriviaOption: hasJSDocNodes(gp) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine }); - break; + case SyntaxKind.ForStatement: + deleteNode(changes, sourceFile, parent); + break; - default: - Debug.assertNever(gp); - } + case SyntaxKind.VariableStatement: + deleteNode(changes, sourceFile, gp, { leadingTriviaOption: hasJSDocNodes(gp) ? LeadingTriviaOption.JSDoc : LeadingTriviaOption.StartLine }); + break; + + default: + Debug.assertNever(gp); } } +} - /** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */ - // Exported for tests only! (TODO: improve tests to not need this) - export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { - const startPosition = getAdjustedStartPosition(sourceFile, node, options); - const endPosition = getAdjustedEndPosition(sourceFile, node, options); - changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); - } +// Exported for tests only! (TODO: improve tests to not need this) +/** + * Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. + * + * @internal + */ +export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { + const startPosition = getAdjustedStartPosition(sourceFile, node, options); + const endPosition = getAdjustedEndPosition(sourceFile, node, options); + changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); +} - function deleteNodeInList(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: Node): void { - const containingList = Debug.checkDefined(formatting.SmartIndenter.getContainingList(node, sourceFile)); - const index = indexOfNode(containingList, node); - Debug.assert(index !== -1); - if (containingList.length === 1) { - deleteNode(changes, sourceFile, node); - return; - } +function deleteNodeInList(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: Node): void { + const containingList = Debug.checkDefined(formatting.SmartIndenter.getContainingList(node, sourceFile)); + const index = indexOfNode(containingList, node); + Debug.assert(index !== -1); + if (containingList.length === 1) { + deleteNode(changes, sourceFile, node); + return; + } - // Note: We will only delete a comma *after* a node. This will leave a trailing comma if we delete the last node. - // That's handled in the end by `finishTrailingCommaAfterDeletingNodesInList`. - Debug.assert(!deletedNodesInLists.has(node), "Deleting a node twice"); - deletedNodesInLists.add(node); + // Note: We will only delete a comma *after* a node. This will leave a trailing comma if we delete the last node. + // That's handled in the end by `finishTrailingCommaAfterDeletingNodesInList`. + Debug.assert(!deletedNodesInLists.has(node), "Deleting a node twice"); + deletedNodesInLists.add(node); - changes.deleteRange(sourceFile, { - pos: startPositionToDeleteNodeInList(sourceFile, node), - end: index === containingList.length - 1 ? getAdjustedEndPosition(sourceFile, node, {}) : endPositionToDeleteNodeInList(sourceFile, node, containingList[index - 1], containingList[index + 1]), - }); - } + changes.deleteRange(sourceFile, { + pos: startPositionToDeleteNodeInList(sourceFile, node), + end: index === containingList.length - 1 ? getAdjustedEndPosition(sourceFile, node, {}) : endPositionToDeleteNodeInList(sourceFile, node, containingList[index - 1], containingList[index + 1]), + }); } diff --git a/src/services/transform.ts b/src/services/transform.ts index c0d87f67b6f1e..f76326ea3fd2a 100644 --- a/src/services/transform.ts +++ b/src/services/transform.ts @@ -1,16 +1,19 @@ -namespace ts { - /** - * Transform one or more nodes using the supplied transformers. - * @param source A single `Node` or an array of `Node` objects. - * @param transformers An array of `TransformerFactory` callbacks used to process the transformation. - * @param compilerOptions Optional compiler options. - */ - export function transform(source: T | T[], transformers: TransformerFactory[], compilerOptions?: CompilerOptions) { - const diagnostics: DiagnosticWithLocation[] = []; - compilerOptions = fixupCompilerOptions(compilerOptions!, diagnostics); // TODO: GH#18217 - const nodes = isArray(source) ? source : [source]; - const result = transformNodes(/*resolver*/ undefined, /*emitHost*/ undefined, factory, compilerOptions, nodes, transformers, /*allowDtsFiles*/ true); - result.diagnostics = concatenate(result.diagnostics, diagnostics); - return result; - } -} \ No newline at end of file +import { + CompilerOptions, concatenate, DiagnosticWithLocation, factory, fixupCompilerOptions, isArray, Node, + TransformationResult, TransformerFactory, transformNodes, +} from "./_namespaces/ts"; + +/** + * Transform one or more nodes using the supplied transformers. + * @param source A single `Node` or an array of `Node` objects. + * @param transformers An array of `TransformerFactory` callbacks used to process the transformation. + * @param compilerOptions Optional compiler options. + */ +export function transform(source: T | T[], transformers: TransformerFactory[], compilerOptions?: CompilerOptions): TransformationResult { + const diagnostics: DiagnosticWithLocation[] = []; + compilerOptions = fixupCompilerOptions(compilerOptions!, diagnostics); // TODO: GH#18217 + const nodes = isArray(source) ? source : [source]; + const result = transformNodes(/*resolver*/ undefined, /*emitHost*/ undefined, factory, compilerOptions, nodes, transformers, /*allowDtsFiles*/ true); + result.diagnostics = concatenate(result.diagnostics, diagnostics); + return result; +} diff --git a/src/services/transpile.ts b/src/services/transpile.ts index b2d8951e43b7e..47c881cae053f 100644 --- a/src/services/transpile.ts +++ b/src/services/transpile.ts @@ -1,153 +1,162 @@ -namespace ts { - export interface TranspileOptions { - compilerOptions?: CompilerOptions; - fileName?: string; - reportDiagnostics?: boolean; - moduleName?: string; - renamedDependencies?: MapLike; - transformers?: CustomTransformers; +import { + addRange, cloneCompilerOptions, CommandLineOptionOfCustomType, CompilerHost, CompilerOptions, + createCompilerDiagnosticForInvalidCustomType, createProgram, createSourceFile, CustomTransformers, Debug, + Diagnostic, fileExtensionIs, filter, forEachEntry, getDefaultCompilerOptions, getEmitScriptTarget, getEntries, + getImpliedNodeFormatForFile, getNewLineCharacter, getSetExternalModuleIndicator, hasProperty, isString, Map, + MapLike, normalizePath, optionDeclarations, parseCustomTypeOption, toPath, transpileOptionValueCompilerOptions, +} from "./_namespaces/ts"; + +export interface TranspileOptions { + compilerOptions?: CompilerOptions; + fileName?: string; + reportDiagnostics?: boolean; + moduleName?: string; + renamedDependencies?: MapLike; + transformers?: CustomTransformers; +} + +export interface TranspileOutput { + outputText: string; + diagnostics?: Diagnostic[]; + sourceMapText?: string; +} + +/* + * This function will compile source text from 'input' argument using specified compiler options. + * If not options are provided - it will use a set of default compiler options. + * Extra compiler options that will unconditionally be used by this function are: + * - isolatedModules = true + * - allowNonTsExtensions = true + * - noLib = true + * - noResolve = true + */ +export function transpileModule(input: string, transpileOptions: TranspileOptions): TranspileOutput { + const diagnostics: Diagnostic[] = []; + + const options: CompilerOptions = transpileOptions.compilerOptions ? fixupCompilerOptions(transpileOptions.compilerOptions, diagnostics) : {}; + + // mix in default options + const defaultOptions = getDefaultCompilerOptions(); + for (const key in defaultOptions) { + if (hasProperty(defaultOptions, key) && options[key] === undefined) { + options[key] = defaultOptions[key]; + } } - export interface TranspileOutput { - outputText: string; - diagnostics?: Diagnostic[]; - sourceMapText?: string; + for (const option of transpileOptionValueCompilerOptions) { + options[option.name] = option.transpileOptionValue; } - /* - * This function will compile source text from 'input' argument using specified compiler options. - * If not options are provided - it will use a set of default compiler options. - * Extra compiler options that will unconditionally be used by this function are: - * - isolatedModules = true - * - allowNonTsExtensions = true - * - noLib = true - * - noResolve = true - */ - export function transpileModule(input: string, transpileOptions: TranspileOptions): TranspileOutput { - const diagnostics: Diagnostic[] = []; - - const options: CompilerOptions = transpileOptions.compilerOptions ? fixupCompilerOptions(transpileOptions.compilerOptions, diagnostics) : {}; - - // mix in default options - const defaultOptions = getDefaultCompilerOptions(); - for (const key in defaultOptions) { - if (hasProperty(defaultOptions, key) && options[key] === undefined) { - options[key] = defaultOptions[key]; - } - } + // transpileModule does not write anything to disk so there is no need to verify that there are no conflicts between input and output paths. + options.suppressOutputPathCheck = true; - for (const option of transpileOptionValueCompilerOptions) { - options[option.name] = option.transpileOptionValue; - } + // Filename can be non-ts file. + options.allowNonTsExtensions = true; - // transpileModule does not write anything to disk so there is no need to verify that there are no conflicts between input and output paths. - options.suppressOutputPathCheck = true; - - // Filename can be non-ts file. - options.allowNonTsExtensions = true; - - const newLine = getNewLineCharacter(options); - // Create a compilerHost object to allow the compiler to read and write files - const compilerHost: CompilerHost = { - getSourceFile: (fileName) => fileName === normalizePath(inputFileName) ? sourceFile : undefined, - writeFile: (name, text) => { - if (fileExtensionIs(name, ".map")) { - Debug.assertEqual(sourceMapText, undefined, "Unexpected multiple source map outputs, file:", name); - sourceMapText = text; - } - else { - Debug.assertEqual(outputText, undefined, "Unexpected multiple outputs, file:", name); - outputText = text; - } - }, - getDefaultLibFileName: () => "lib.d.ts", - useCaseSensitiveFileNames: () => false, - getCanonicalFileName: fileName => fileName, - getCurrentDirectory: () => "", - getNewLine: () => newLine, - fileExists: (fileName): boolean => fileName === inputFileName, - readFile: () => "", - directoryExists: () => true, - getDirectories: () => [] - }; - - // if jsx is specified then treat file as .tsx - const inputFileName = transpileOptions.fileName || (transpileOptions.compilerOptions && transpileOptions.compilerOptions.jsx ? "module.tsx" : "module.ts"); - const sourceFile = createSourceFile( - inputFileName, - input, - { - languageVersion: getEmitScriptTarget(options), - impliedNodeFormat: getImpliedNodeFormatForFile(toPath(inputFileName, "", compilerHost.getCanonicalFileName), /*cache*/ undefined, compilerHost, options), - setExternalModuleIndicator: getSetExternalModuleIndicator(options) + const newLine = getNewLineCharacter(options); + // Create a compilerHost object to allow the compiler to read and write files + const compilerHost: CompilerHost = { + getSourceFile: (fileName) => fileName === normalizePath(inputFileName) ? sourceFile : undefined, + writeFile: (name, text) => { + if (fileExtensionIs(name, ".map")) { + Debug.assertEqual(sourceMapText, undefined, "Unexpected multiple source map outputs, file:", name); + sourceMapText = text; + } + else { + Debug.assertEqual(outputText, undefined, "Unexpected multiple outputs, file:", name); + outputText = text; } - ); - if (transpileOptions.moduleName) { - sourceFile.moduleName = transpileOptions.moduleName; + }, + getDefaultLibFileName: () => "lib.d.ts", + useCaseSensitiveFileNames: () => false, + getCanonicalFileName: fileName => fileName, + getCurrentDirectory: () => "", + getNewLine: () => newLine, + fileExists: (fileName): boolean => fileName === inputFileName, + readFile: () => "", + directoryExists: () => true, + getDirectories: () => [] + }; + + // if jsx is specified then treat file as .tsx + const inputFileName = transpileOptions.fileName || (transpileOptions.compilerOptions && transpileOptions.compilerOptions.jsx ? "module.tsx" : "module.ts"); + const sourceFile = createSourceFile( + inputFileName, + input, + { + languageVersion: getEmitScriptTarget(options), + impliedNodeFormat: getImpliedNodeFormatForFile(toPath(inputFileName, "", compilerHost.getCanonicalFileName), /*cache*/ undefined, compilerHost, options), + setExternalModuleIndicator: getSetExternalModuleIndicator(options) } + ); + if (transpileOptions.moduleName) { + sourceFile.moduleName = transpileOptions.moduleName; + } - if (transpileOptions.renamedDependencies) { - sourceFile.renamedDependencies = new Map(getEntries(transpileOptions.renamedDependencies)); - } + if (transpileOptions.renamedDependencies) { + sourceFile.renamedDependencies = new Map(getEntries(transpileOptions.renamedDependencies)); + } - // Output - let outputText: string | undefined; - let sourceMapText: string | undefined; + // Output + let outputText: string | undefined; + let sourceMapText: string | undefined; - const program = createProgram([inputFileName], options, compilerHost); + const program = createProgram([inputFileName], options, compilerHost); - if (transpileOptions.reportDiagnostics) { - addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile)); - addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics()); - } - // Emit - program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transpileOptions.transformers); + if (transpileOptions.reportDiagnostics) { + addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile)); + addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics()); + } + // Emit + program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transpileOptions.transformers); - if (outputText === undefined) return Debug.fail("Output generation failed"); + if (outputText === undefined) return Debug.fail("Output generation failed"); - return { outputText, diagnostics, sourceMapText }; - } + return { outputText, diagnostics, sourceMapText }; +} - /* - * This is a shortcut function for transpileModule - it accepts transpileOptions as parameters and returns only outputText part of the result. - */ - export function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string { - const output = transpileModule(input, { compilerOptions, fileName, reportDiagnostics: !!diagnostics, moduleName }); - // addRange correctly handles cases when wither 'from' or 'to' argument is missing - addRange(diagnostics, output.diagnostics); - return output.outputText; - } +/* + * This is a shortcut function for transpileModule - it accepts transpileOptions as parameters and returns only outputText part of the result. + */ +export function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string { + const output = transpileModule(input, { compilerOptions, fileName, reportDiagnostics: !!diagnostics, moduleName }); + // addRange correctly handles cases when wither 'from' or 'to' argument is missing + addRange(diagnostics, output.diagnostics); + return output.outputText; +} - let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[]; +let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[]; - /** JS users may pass in string values for enum compiler options (such as ModuleKind), so convert. */ - /*@internal*/ - export function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions { - // Lazily create this value to fix module loading errors. - commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || - filter(optionDeclarations, o => typeof o.type === "object" && !forEachEntry(o.type, v => typeof v !== "number")) as CommandLineOptionOfCustomType[]; +/** + * JS users may pass in string values for enum compiler options (such as ModuleKind), so convert. + * + * @internal + */ +export function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions { + // Lazily create this value to fix module loading errors. + commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || + filter(optionDeclarations, o => typeof o.type === "object" && !forEachEntry(o.type, v => typeof v !== "number")) as CommandLineOptionOfCustomType[]; - options = cloneCompilerOptions(options); + options = cloneCompilerOptions(options); - for (const opt of commandLineOptionsStringToEnum) { - if (!hasProperty(options, opt.name)) { - continue; - } + for (const opt of commandLineOptionsStringToEnum) { + if (!hasProperty(options, opt.name)) { + continue; + } - const value = options[opt.name]; - // Value should be a key of opt.type - if (isString(value)) { - // If value is not a string, this will fail - options[opt.name] = parseCustomTypeOption(opt, value, diagnostics); - } - else { - if (!forEachEntry(opt.type, v => v === value)) { - // Supplied value isn't a valid enum value. - diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt)); - } + const value = options[opt.name]; + // Value should be a key of opt.type + if (isString(value)) { + // If value is not a string, this will fail + options[opt.name] = parseCustomTypeOption(opt, value, diagnostics); + } + else { + if (!forEachEntry(opt.type, v => v === value)) { + // Supplied value isn't a valid enum value. + diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt)); } } - - return options; } + + return options; } diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 2099fe308c911..42c124fdd8156 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -1,141 +1,10 @@ { "extends": "../tsconfig-base", "compilerOptions": { - "outFile": "../../built/local/services.js" }, "references": [ { "path": "../compiler" }, { "path": "../jsTyping" } ], - "files": [ - "types.ts", - "utilities.ts", - "exportInfoMap.ts", - "classifier.ts", - "classifier2020.ts", - "stringCompletions.ts", - "completions.ts", - "documentHighlights.ts", - "documentRegistry.ts", - "importTracker.ts", - "findAllReferences.ts", - "callHierarchy.ts", - "getEditsForFileRename.ts", - "goToDefinition.ts", - "jsDoc.ts", - "navigateTo.ts", - "navigationBar.ts", - "organizeImports.ts", - "getEditsForFileRename.ts", - "outliningElementsCollector.ts", - "patternMatcher.ts", - "preProcess.ts", - "rename.ts", - "smartSelection.ts", - "signatureHelp.ts", - "inlayHints.ts", - "sourcemaps.ts", - "suggestionDiagnostics.ts", - "symbolDisplay.ts", - "transpile.ts", - "formatting/formattingContext.ts", - "formatting/formattingScanner.ts", - "formatting/rule.ts", - "formatting/rules.ts", - "formatting/rulesMap.ts", - "formatting/formatting.ts", - "formatting/smartIndenter.ts", - "textChanges.ts", - "codeFixProvider.ts", - "refactorProvider.ts", - "codefixes/addConvertToUnknownForNonOverlappingTypes.ts", - "codefixes/addEmptyExportDeclaration.ts", - "codefixes/addMissingAsync.ts", - "codefixes/addMissingAwait.ts", - "codefixes/addMissingConst.ts", - "codefixes/addMissingDeclareProperty.ts", - "codefixes/addMissingInvocationForDecorator.ts", - "codefixes/addNameToNamelessParameter.ts", - "codefixes/addOptionalPropertyUndefined.ts", - "codefixes/annotateWithTypeFromJSDoc.ts", - "codefixes/convertFunctionToEs6Class.ts", - "codefixes/convertToAsyncFunction.ts", - "codefixes/convertToEsModule.ts", - "codefixes/correctQualifiedNameToIndexedAccessType.ts", - "codefixes/convertToTypeOnlyExport.ts", - "codefixes/convertToTypeOnlyImport.ts", - "codefixes/convertLiteralTypeToMappedType.ts", - "codefixes/fixClassIncorrectlyImplementsInterface.ts", - "codefixes/importFixes.ts", - "codefixes/fixAddMissingConstraint.ts", - "codefixes/fixOverrideModifier.ts", - "codefixes/fixNoPropertyAccessFromIndexSignature.ts", - "codefixes/fixImplicitThis.ts", - "codefixes/fixImportNonExportedMember.ts", - "codefixes/fixIncorrectNamedTupleSyntax.ts", - "codefixes/fixSpelling.ts", - "codefixes/returnValueCorrect.ts", - "codefixes/fixAddMissingMember.ts", - "codefixes/fixAddMissingNewOperator.ts", - "codefixes/fixCannotFindModule.ts", - "codefixes/fixClassDoesntImplementInheritedAbstractMember.ts", - "codefixes/fixClassSuperMustPrecedeThisAccess.ts", - "codefixes/fixConstructorForDerivedNeedSuperCall.ts", - "codefixes/fixEnableExperimentalDecorators.ts", - "codefixes/fixEnableJsxFlag.ts", - "codefixes/fixNaNEquality.ts", - "codefixes/fixModuleAndTargetOptions.ts", - "codefixes/fixPropertyAssignment.ts", - "codefixes/fixExtendsInterfaceBecomesImplements.ts", - "codefixes/fixForgottenThisPropertyAccess.ts", - "codefixes/fixInvalidJsxCharacters.ts", - "codefixes/fixUnmatchedParameter.ts", - "codefixes/fixUnreferenceableDecoratorMetadata.ts", - "codefixes/fixUnusedIdentifier.ts", - "codefixes/fixUnreachableCode.ts", - "codefixes/fixUnusedLabel.ts", - "codefixes/fixJSDocTypes.ts", - "codefixes/fixMissingCallParentheses.ts", - "codefixes/fixAwaitInSyncFunction.ts", - "codefixes/fixPropertyOverrideAccessor.ts", - "codefixes/inferFromUsage.ts", - "codefixes/fixReturnTypeInAsyncFunction.ts", - "codefixes/disableJsDiagnostics.ts", - "codefixes/helpers.ts", - "codefixes/generateAccessors.ts", - "codefixes/fixInvalidImportSyntax.ts", - "codefixes/fixStrictClassInitialization.ts", - "codefixes/requireInTs.ts", - "codefixes/useDefaultImport.ts", - "codefixes/useBigintLiteral.ts", - "codefixes/fixAddModuleReferTypeMissingTypeof.ts", - "codefixes/wrapJsxInFragment.ts", - "codefixes/convertToMappedObjectType.ts", - "codefixes/removeAccidentalCallParentheses.ts", - "codefixes/removeUnnecessaryAwait.ts", - "codefixes/splitTypeOnlyImport.ts", - "codefixes/convertConstToLet.ts", - "codefixes/fixExpectedComma.ts", - "codefixes/fixAddVoidToPromise.ts", - "refactors/convertExport.ts", - "refactors/convertImport.ts", - "refactors/convertToOptionalChainExpression.ts", - "refactors/convertOverloadListToSingleSignature.ts", - "refactors/extractSymbol.ts", - "refactors/extractType.ts", - "refactors/generateGetAccessorAndSetAccessor.ts", - "refactors/helpers.ts", - "refactors/moveToNewFile.ts", - "refactors/addOrRemoveBracesToArrowFunction.ts", - "refactors/convertParamsToDestructuredObject.ts", - "refactors/convertStringOrTemplateLiteral.ts", - "refactors/convertArrowFunctionOrFunctionExpression.ts", - "refactors/inferFunctionReturnType.ts", - "services.ts", - "breakpoints.ts", - "transform.ts", - "shims.ts", - "globalThisShim.ts", - "exportAsModule.ts" - ] + "include": ["**/*"] } diff --git a/src/services/types.ts b/src/services/types.ts index fdd692b5b2233..044ac4132caee 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -1,13 +1,24 @@ -namespace ts { +import { + CancellationToken, CompilerHost, CompilerOptions, CustomTransformers, Diagnostic, DiagnosticWithLocation, + DocumentHighlights, DocumentPositionMapper, EmitOutput, ESMap, ExportInfoMap, FileReference, + GetEffectiveTypeRootsHost, HasChangedAutomaticTypeDirectiveNames, HasInvalidatedResolutions, LineAndCharacter, + MinimalResolutionCacheHost, ModuleKind, ModuleResolutionCache, ModuleResolutionInfo, ModuleSpecifierCache, + ParsedCommandLine, Path, Program, ProjectReference, ResolvedModule, ResolvedModuleWithFailedLookupLocations, + ResolvedProjectReference, ResolvedTypeReferenceDirective, ScriptKind, Set, SourceFile, SourceFileLike, SourceMapper, + Symbol, SymlinkCache, TextChangeRange, textChanges, TextRange, TextSpan, UserPreferences, +} from "./_namespaces/ts"; + +declare module "../compiler/types" { + // Module transform: converted from interface augmentation export interface Node { getSourceFile(): SourceFile; getChildCount(sourceFile?: SourceFile): number; getChildAt(index: number, sourceFile?: SourceFile): Node; getChildren(sourceFile?: SourceFile): Node[]; - /* @internal */ + /** @internal */ getChildren(sourceFile?: SourceFileLike): Node[]; // eslint-disable-line @typescript-eslint/unified-signatures getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; - /* @internal */ + /** @internal */ getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number; // eslint-disable-line @typescript-eslint/unified-signatures getFullStart(): number; getEnd(): number; @@ -17,23 +28,32 @@ namespace ts { getFullText(sourceFile?: SourceFile): string; getText(sourceFile?: SourceFile): string; getFirstToken(sourceFile?: SourceFile): Node | undefined; - /* @internal */ + /** @internal */ getFirstToken(sourceFile?: SourceFileLike): Node | undefined; // eslint-disable-line @typescript-eslint/unified-signatures getLastToken(sourceFile?: SourceFile): Node | undefined; - /* @internal */ + /** @internal */ getLastToken(sourceFile?: SourceFileLike): Node | undefined; // eslint-disable-line @typescript-eslint/unified-signatures // See ts.forEachChild for documentation. forEachChild(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray) => T | undefined): T | undefined; } +} +declare module "../compiler/types" { + // Module transform: converted from interface augmentation export interface Identifier { readonly text: string; } +} +declare module "../compiler/types" { + // Module transform: converted from interface augmentation export interface PrivateIdentifier { readonly text: string; } +} +declare module "../compiler/types" { + // Module transform: converted from interface augmentation export interface Symbol { readonly name: string; getFlags(): SymbolFlags; @@ -41,13 +61,16 @@ namespace ts { getName(): string; getDeclarations(): Declaration[] | undefined; getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[]; - /* @internal */ + /** @internal */ getContextualDocumentationComment(context: Node | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] getJsDocTags(checker?: TypeChecker): JSDocTagInfo[]; - /* @internal */ + /** @internal */ getContextualJsDocTags(context: Node | undefined, checker: TypeChecker | undefined): JSDocTagInfo[]; } +} +declare module "../compiler/types" { + // Module transform: converted from interface augmentation export interface Type { getFlags(): TypeFlags; getSymbol(): Symbol | undefined; @@ -60,8 +83,8 @@ namespace ts { getNumberIndexType(): Type | undefined; getBaseTypes(): BaseType[] | undefined; getNonNullableType(): Type; - /*@internal*/ getNonOptionalType(): Type; - /*@internal*/ isNullableType(): boolean; + /** @internal */ getNonOptionalType(): Type; + /** @internal */ isNullableType(): boolean; getConstraint(): Type | undefined; getDefault(): Type | undefined; @@ -76,11 +99,17 @@ namespace ts { isClass(): this is InterfaceType; isIndexType(): this is IndexType; } +} +declare module "../compiler/types" { + // Module transform: converted from interface augmentation export interface TypeReference { typeArguments?: readonly Type[]; } +} +declare module "../compiler/types" { + // Module transform: converted from interface augmentation export interface Signature { getDeclaration(): SignatureDeclaration; getTypeParameters(): TypeParameter[] | undefined; @@ -90,13 +119,16 @@ namespace ts { getDocumentationComment(typeChecker: TypeChecker | undefined): SymbolDisplayPart[]; getJsDocTags(): JSDocTagInfo[]; } +} +declare module "../compiler/types" { + // Module transform: converted from interface augmentation export interface SourceFile { - /* @internal */ version: string; - /* @internal */ scriptSnapshot: IScriptSnapshot | undefined; - /* @internal */ nameTable: UnderscoreEscapedMap | undefined; + /** @internal */ version: string; + /** @internal */ scriptSnapshot: IScriptSnapshot | undefined; + /** @internal */ nameTable: UnderscoreEscapedMap | undefined; - /* @internal */ getNamedDeclarations(): ESMap; + /** @internal */ getNamedDeclarations(): ESMap; getLineAndCharacterOfPosition(pos: number): LineAndCharacter; getLineEndOfPosition(pos: number): number; @@ -104,1575 +136,1581 @@ namespace ts { getPositionOfLineAndCharacter(line: number, character: number): number; update(newText: string, textChangeRange: TextChangeRange): SourceFile; - /* @internal */ sourceMapper?: DocumentPositionMapper; + /** @internal */ sourceMapper?: DocumentPositionMapper; } +} +declare module "../compiler/types" { + // Module transform: converted from interface augmentation export interface SourceFileLike { getLineAndCharacterOfPosition(pos: number): LineAndCharacter; } +} +declare module "../compiler/types" { + // Module transform: converted from interface augmentation export interface SourceMapSource { getLineAndCharacterOfPosition(pos: number): LineAndCharacter; } +} + +/** + * Represents an immutable snapshot of a script at a specified time.Once acquired, the + * snapshot is observably immutable. i.e. the same calls with the same parameters will return + * the same values. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export interface IScriptSnapshot { + /** Gets a portion of the script snapshot specified by [start, end). */ + getText(start: number, end: number): string; + + /** Gets the length of this script snapshot. */ + getLength(): number; /** - * Represents an immutable snapshot of a script at a specified time.Once acquired, the - * snapshot is observably immutable. i.e. the same calls with the same parameters will return - * the same values. + * Gets the TextChangeRange that describe how the text changed between this text and + * an older version. This information is used by the incremental parser to determine + * what sections of the script need to be re-parsed. 'undefined' can be returned if the + * change range cannot be determined. However, in that case, incremental parsing will + * not happen and the entire document will be re - parsed. */ - // eslint-disable-next-line @typescript-eslint/naming-convention - export interface IScriptSnapshot { - /** Gets a portion of the script snapshot specified by [start, end). */ - getText(start: number, end: number): string; - - /** Gets the length of this script snapshot. */ - getLength(): number; - - /** - * Gets the TextChangeRange that describe how the text changed between this text and - * an older version. This information is used by the incremental parser to determine - * what sections of the script need to be re-parsed. 'undefined' can be returned if the - * change range cannot be determined. However, in that case, incremental parsing will - * not happen and the entire document will be re - parsed. - */ - getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined; - - /** Releases all resources held by this script snapshot */ - dispose?(): void; - } + getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined; - export namespace ScriptSnapshot { - class StringScriptSnapshot implements IScriptSnapshot { + /** Releases all resources held by this script snapshot */ + dispose?(): void; +} - constructor(private text: string) { - } +export namespace ScriptSnapshot { + class StringScriptSnapshot implements IScriptSnapshot { - public getText(start: number, end: number): string { - return start === 0 && end === this.text.length - ? this.text - : this.text.substring(start, end); - } + constructor(private text: string) { + } - public getLength(): number { - return this.text.length; - } + public getText(start: number, end: number): string { + return start === 0 && end === this.text.length + ? this.text + : this.text.substring(start, end); + } - public getChangeRange(): TextChangeRange | undefined { - // Text-based snapshots do not support incremental parsing. Return undefined - // to signal that to the caller. - return undefined; - } + public getLength(): number { + return this.text.length; } - export function fromString(text: string): IScriptSnapshot { - return new StringScriptSnapshot(text); + public getChangeRange(): TextChangeRange | undefined { + // Text-based snapshots do not support incremental parsing. Return undefined + // to signal that to the caller. + return undefined; } } - export interface PreProcessedFileInfo { - referencedFiles: FileReference[]; - typeReferenceDirectives: FileReference[]; - libReferenceDirectives: FileReference[]; - importedFiles: FileReference[]; - ambientExternalModules?: string[]; - isLibFile: boolean; + export function fromString(text: string): IScriptSnapshot { + return new StringScriptSnapshot(text); } +} - export interface HostCancellationToken { - isCancellationRequested(): boolean; - } +export interface PreProcessedFileInfo { + referencedFiles: FileReference[]; + typeReferenceDirectives: FileReference[]; + libReferenceDirectives: FileReference[]; + importedFiles: FileReference[]; + ambientExternalModules?: string[]; + isLibFile: boolean; +} - export interface InstallPackageOptions { - fileName: Path; - packageName: string; - } +export interface HostCancellationToken { + isCancellationRequested(): boolean; +} - /* @internal */ - export const enum PackageJsonDependencyGroup { - Dependencies = 1 << 0, - DevDependencies = 1 << 1, - PeerDependencies = 1 << 2, - OptionalDependencies = 1 << 3, - All = Dependencies | DevDependencies | PeerDependencies | OptionalDependencies, - } +export interface InstallPackageOptions { + fileName: Path; + packageName: string; +} - /* @internal */ - export interface ProjectPackageJsonInfo { - fileName: string; - parseable: boolean; - dependencies?: ESMap; - devDependencies?: ESMap; - peerDependencies?: ESMap; - optionalDependencies?: ESMap; - get(dependencyName: string, inGroups?: PackageJsonDependencyGroup): string | undefined; - has(dependencyName: string, inGroups?: PackageJsonDependencyGroup): boolean; - } +/** @internal */ +export const enum PackageJsonDependencyGroup { + Dependencies = 1 << 0, + DevDependencies = 1 << 1, + PeerDependencies = 1 << 2, + OptionalDependencies = 1 << 3, + All = Dependencies | DevDependencies | PeerDependencies | OptionalDependencies, +} - /* @internal */ - export interface FormattingHost { - getNewLine?(): string; - } +/** @internal */ +export interface ProjectPackageJsonInfo { + fileName: string; + parseable: boolean; + dependencies?: ESMap; + devDependencies?: ESMap; + peerDependencies?: ESMap; + optionalDependencies?: ESMap; + get(dependencyName: string, inGroups?: PackageJsonDependencyGroup): string | undefined; + has(dependencyName: string, inGroups?: PackageJsonDependencyGroup): boolean; +} - /* @internal */ - export const enum PackageJsonAutoImportPreference { - Off, - On, - Auto, - } +/** @internal */ +export interface FormattingHost { + getNewLine?(): string; +} - export interface PerformanceEvent { - kind: "UpdateGraph" | "CreatePackageJsonAutoImportProvider"; - durationMs: number; - } +/** @internal */ +export const enum PackageJsonAutoImportPreference { + Off, + On, + Auto, +} - export enum LanguageServiceMode { - Semantic, - PartialSemantic, - Syntactic, - } +export interface PerformanceEvent { + kind: "UpdateGraph" | "CreatePackageJsonAutoImportProvider"; + durationMs: number; +} - export interface IncompleteCompletionsCache { - get(): CompletionInfo | undefined; - set(response: CompletionInfo): void; - clear(): void; - } +export enum LanguageServiceMode { + Semantic, + PartialSemantic, + Syntactic, +} - // - // Public interface of the host of a language service instance. - // - export interface LanguageServiceHost extends GetEffectiveTypeRootsHost, MinimalResolutionCacheHost { - getCompilationSettings(): CompilerOptions; - getNewLine?(): string; - getProjectVersion?(): string; - getScriptFileNames(): string[]; - getScriptKind?(fileName: string): ScriptKind; - getScriptVersion(fileName: string): string; - getScriptSnapshot(fileName: string): IScriptSnapshot | undefined; - getProjectReferences?(): readonly ProjectReference[] | undefined; - getLocalizedDiagnosticMessages?(): any; - getCancellationToken?(): HostCancellationToken; - getCurrentDirectory(): string; - getDefaultLibFileName(options: CompilerOptions): string; - log?(s: string): void; - trace?(s: string): void; - error?(s: string): void; - useCaseSensitiveFileNames?(): boolean; - - /* - * LS host can optionally implement these methods to support completions for module specifiers. - * Without these methods, only completions for ambient modules will be provided. - */ - readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; - realpath?(path: string): string; - - /* - * Unlike `realpath and `readDirectory`, `readFile` and `fileExists` are now _required_ - * to properly acquire and setup source files under module: node16+ modes. - */ - readFile(path: string, encoding?: string): string | undefined; - fileExists(path: string): boolean; - - /* - * LS host can optionally implement these methods to support automatic updating when new type libraries are installed - */ - getTypeRootsVersion?(): number; - - /* - * LS host can optionally implement this method if it wants to be completely in charge of module name resolution. - * if implementation is omitted then language service will use built-in module resolution logic and get answers to - * host specific questions using 'getScriptSnapshot'. - * - * If this is implemented, `getResolvedModuleWithFailedLookupLocationsFromCache` should be too. - */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModule | undefined)[]; - getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined; - resolveTypeReferenceDirectives?(typeDirectiveNames: string[] | FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; - /* @internal */ hasInvalidatedResolutions?: HasInvalidatedResolutions; - /* @internal */ hasChangedAutomaticTypeDirectiveNames?: HasChangedAutomaticTypeDirectiveNames; - /* @internal */ getGlobalTypingsCacheLocation?(): string | undefined; - /* @internal */ getSymlinkCache?(files?: readonly SourceFile[]): SymlinkCache; - /* Lets the Program from a AutoImportProviderProject use its host project's ModuleResolutionCache */ - /* @internal */ getModuleResolutionCache?(): ModuleResolutionCache | undefined; - - /* - * Required for full import and type reference completions. - * These should be unprefixed names. E.g. `getDirectories("/foo/bar")` should return `["a", "b"]`, not `["/foo/bar/a", "/foo/bar/b"]`. - */ - getDirectories?(directoryName: string): string[]; - - /** - * Gets a set of custom transformers to use during emit. - */ - getCustomTransformers?(): CustomTransformers | undefined; - - isKnownTypesPackageName?(name: string): boolean; - installPackage?(options: InstallPackageOptions): Promise; - writeFile?(fileName: string, content: string): void; - - /* @internal */ getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; - /* @internal */ getSourceFileLike?(fileName: string): SourceFileLike | undefined; - /* @internal */ getPackageJsonsVisibleToFile?(fileName: string, rootDir?: string): readonly ProjectPackageJsonInfo[]; - /* @internal */ getNearestAncestorDirectoryWithPackageJson?(fileName: string): string | undefined; - /* @internal */ getPackageJsonsForAutoImport?(rootDir?: string): readonly ProjectPackageJsonInfo[]; - /* @internal */ getCachedExportInfoMap?(): ExportInfoMap; - /* @internal */ getModuleSpecifierCache?(): ModuleSpecifierCache; - /* @internal */ setCompilerHost?(host: CompilerHost): void; - /* @internal */ useSourceOfProjectReferenceRedirect?(): boolean; - /* @internal */ getPackageJsonAutoImportProvider?(): Program | undefined; - /* @internal */ sendPerformanceEvent?(kind: PerformanceEvent["kind"], durationMs: number): void; - getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; - /* @internal */ onReleaseParsedCommandLine?(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, optionOptions: CompilerOptions): void; - /* @internal */ getIncompleteCompletionsCache?(): IncompleteCompletionsCache; - } +export interface IncompleteCompletionsCache { + get(): CompletionInfo | undefined; + set(response: CompletionInfo): void; + clear(): void; +} - /* @internal */ - export const emptyOptions = {}; +// +// Public interface of the host of a language service instance. +// +export interface LanguageServiceHost extends GetEffectiveTypeRootsHost, MinimalResolutionCacheHost { + getCompilationSettings(): CompilerOptions; + getNewLine?(): string; + getProjectVersion?(): string; + getScriptFileNames(): string[]; + getScriptKind?(fileName: string): ScriptKind; + getScriptVersion(fileName: string): string; + getScriptSnapshot(fileName: string): IScriptSnapshot | undefined; + getProjectReferences?(): readonly ProjectReference[] | undefined; + getLocalizedDiagnosticMessages?(): any; + getCancellationToken?(): HostCancellationToken; + getCurrentDirectory(): string; + getDefaultLibFileName(options: CompilerOptions): string; + log?(s: string): void; + trace?(s: string): void; + error?(s: string): void; + useCaseSensitiveFileNames?(): boolean; + + /* + * LS host can optionally implement these methods to support completions for module specifiers. + * Without these methods, only completions for ambient modules will be provided. + */ + readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; + realpath?(path: string): string; - export type WithMetadata = T & { metadata?: unknown; }; + /* + * Unlike `realpath and `readDirectory`, `readFile` and `fileExists` are now _required_ + * to properly acquire and setup source files under module: node16+ modes. + */ + readFile(path: string, encoding?: string): string | undefined; + fileExists(path: string): boolean; - export const enum SemanticClassificationFormat { - Original = "original", - TwentyTwenty = "2020" - } + /* + * LS host can optionally implement these methods to support automatic updating when new type libraries are installed + */ + getTypeRootsVersion?(): number; + + /* + * LS host can optionally implement this method if it wants to be completely in charge of module name resolution. + * if implementation is omitted then language service will use built-in module resolution logic and get answers to + * host specific questions using 'getScriptSnapshot'. + * + * If this is implemented, `getResolvedModuleWithFailedLookupLocationsFromCache` should be too. + */ + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, resolutionInfo?: ModuleResolutionInfo): (ResolvedModule | undefined)[]; + getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined; + resolveTypeReferenceDirectives?(typeDirectiveNames: string[] | FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; + /** @internal */ hasInvalidatedResolutions?: HasInvalidatedResolutions; + /** @internal */ hasChangedAutomaticTypeDirectiveNames?: HasChangedAutomaticTypeDirectiveNames; + /** @internal */ getGlobalTypingsCacheLocation?(): string | undefined; + /** @internal */ getSymlinkCache?(files?: readonly SourceFile[]): SymlinkCache; + /* Lets the Program from a AutoImportProviderProject use its host project's ModuleResolutionCache */ + /** @internal */ getModuleResolutionCache?(): ModuleResolutionCache | undefined; + + /* + * Required for full import and type reference completions. + * These should be unprefixed names. E.g. `getDirectories("/foo/bar")` should return `["a", "b"]`, not `["/foo/bar/a", "/foo/bar/b"]`. + */ + getDirectories?(directoryName: string): string[]; - // - // Public services of a language service instance associated - // with a language service host instance - // - export interface LanguageService { - /** This is used as a part of restarting the language service. */ - cleanupSemanticCache(): void; - - /** - * Gets errors indicating invalid syntax in a file. - * - * In English, "this cdeo have, erorrs" is syntactically invalid because it has typos, - * grammatical errors, and misplaced punctuation. Likewise, examples of syntax - * errors in TypeScript are missing parentheses in an `if` statement, mismatched - * curly braces, and using a reserved keyword as a variable name. - * - * These diagnostics are inexpensive to compute and don't require knowledge of - * other files. Note that a non-empty result increases the likelihood of false positives - * from `getSemanticDiagnostics`. - * - * While these represent the majority of syntax-related diagnostics, there are some - * that require the type system, which will be present in `getSemanticDiagnostics`. - * - * @param fileName A path to the file you want syntactic diagnostics for - */ - getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[]; - - /** - * Gets warnings or errors indicating type system issues in a given file. - * Requesting semantic diagnostics may start up the type system and - * run deferred work, so the first call may take longer than subsequent calls. - * - * Unlike the other get*Diagnostics functions, these diagnostics can potentially not - * include a reference to a source file. Specifically, the first time this is called, - * it will return global diagnostics with no associated location. - * - * To contrast the differences between semantic and syntactic diagnostics, consider the - * sentence: "The sun is green." is syntactically correct; those are real English words with - * correct sentence structure. However, it is semantically invalid, because it is not true. - * - * @param fileName A path to the file you want semantic diagnostics for - */ - getSemanticDiagnostics(fileName: string): Diagnostic[]; - - /** - * Gets suggestion diagnostics for a specific file. These diagnostics tend to - * proactively suggest refactors, as opposed to diagnostics that indicate - * potentially incorrect runtime behavior. - * - * @param fileName A path to the file you want semantic diagnostics for - */ - getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[]; - - // TODO: Rename this to getProgramDiagnostics to better indicate that these are any - // diagnostics present for the program level, and not just 'options' diagnostics. - - /** - * Gets global diagnostics related to the program configuration and compiler options. - */ - getCompilerOptionsDiagnostics(): Diagnostic[]; - - /** @deprecated Use getEncodedSyntacticClassifications instead. */ - getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; - getSyntacticClassifications(fileName: string, span: TextSpan, format: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[]; - - /** @deprecated Use getEncodedSemanticClassifications instead. */ - getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; - getSemanticClassifications(fileName: string, span: TextSpan, format: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[]; - - /** Encoded as triples of [start, length, ClassificationType]. */ - getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications; - - /** - * Gets semantic highlights information for a particular file. Has two formats, an older - * version used by VS and a format used by VS Code. - * - * @param fileName The path to the file - * @param position A text span to return results within - * @param format Which format to use, defaults to "original" - * @returns a number array encoded as triples of [start, length, ClassificationType, ...]. - */ - getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications; - - /** - * Gets completion entries at a particular position in a file. - * - * @param fileName The path to the file - * @param position A zero-based index of the character where you want the entries - * @param options An object describing how the request was triggered and what kinds - * of code actions can be returned with the completions. - * @param formattingSettings settings needed for calling formatting functions. - */ - getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined, formattingSettings?: FormatCodeSettings): WithMetadata | undefined; - - /** - * Gets the extended details for a completion entry retrieved from `getCompletionsAtPosition`. - * - * @param fileName The path to the file - * @param position A zero based index of the character where you want the entries - * @param entryName The `name` from an existing completion which came from `getCompletionsAtPosition` - * @param formatOptions How should code samples in the completions be formatted, can be undefined for backwards compatibility - * @param source `source` property from the completion entry - * @param preferences User settings, can be undefined for backwards compatibility - * @param data `data` property from the completion entry - */ - getCompletionEntryDetails( - fileName: string, - position: number, - entryName: string, - formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, - source: string | undefined, - preferences: UserPreferences | undefined, - data: CompletionEntryData | undefined, - ): CompletionEntryDetails | undefined; - - getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): Symbol | undefined; - - /** - * Gets semantic information about the identifier at a particular position in a - * file. Quick info is what you typically see when you hover in an editor. - * - * @param fileName The path to the file - * @param position A zero-based index of the character where you want the quick info - */ - getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined; - - getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan | undefined; - - getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined; - - getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): SignatureHelpItems | undefined; - - getRenameInfo(fileName: string, position: number, preferences: UserPreferences): RenameInfo; - /** @deprecated Use the signature with `UserPreferences` instead. */ - getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo; - - findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): readonly RenameLocation[] | undefined; - - getSmartSelectionRange(fileName: string, position: number): SelectionRange; - - /*@internal*/ - // eslint-disable-next-line @typescript-eslint/unified-signatures - getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly: false, stopAtAlias: boolean): readonly DefinitionInfo[] | undefined; - /*@internal*/ - // eslint-disable-next-line @typescript-eslint/unified-signatures - getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly: boolean, stopAtAlias: false): readonly DefinitionInfo[] | undefined; - getDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined; - getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined; - getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined; - getImplementationAtPosition(fileName: string, position: number): readonly ImplementationLocation[] | undefined; - - getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined; - findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined; - getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] | undefined; - getFileReferences(fileName: string): ReferenceEntry[]; - - /** @deprecated */ - getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined; - - getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; - getNavigationBarItems(fileName: string): NavigationBarItem[]; - getNavigationTree(fileName: string): NavigationTree; - - prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined; - provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; - provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; - - provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences | undefined): InlayHint[] - - getOutliningSpans(fileName: string): OutliningSpan[]; - getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; - getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; - getIndentationAtPosition(fileName: string, position: number, options: EditorOptions | EditorSettings): number; - - getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; - getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; - getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; - - getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined; - - isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; - /** - * This will return a defined result if the position is after the `>` of the opening tag, or somewhere in the text, of a JSXElement with no closing tag. - * Editors should call this after `>` is typed. - */ - getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined; - - getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined; - - toLineColumnOffset?(fileName: string, position: number): LineAndCharacter; - /** @internal */ - getSourceMapper(): SourceMapper; - /** @internal */ - clearSourceMapperCache(): void; + /** + * Gets a set of custom transformers to use during emit. + */ + getCustomTransformers?(): CustomTransformers | undefined; + + isKnownTypesPackageName?(name: string): boolean; + installPackage?(options: InstallPackageOptions): Promise; + writeFile?(fileName: string, content: string): void; + + /** @internal */ getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; + /** @internal */ getSourceFileLike?(fileName: string): SourceFileLike | undefined; + /** @internal */ getPackageJsonsVisibleToFile?(fileName: string, rootDir?: string): readonly ProjectPackageJsonInfo[]; + /** @internal */ getNearestAncestorDirectoryWithPackageJson?(fileName: string): string | undefined; + /** @internal */ getPackageJsonsForAutoImport?(rootDir?: string): readonly ProjectPackageJsonInfo[]; + /** @internal */ getCachedExportInfoMap?(): ExportInfoMap; + /** @internal */ getModuleSpecifierCache?(): ModuleSpecifierCache; + /** @internal */ setCompilerHost?(host: CompilerHost): void; + /** @internal */ useSourceOfProjectReferenceRedirect?(): boolean; + /** @internal */ getPackageJsonAutoImportProvider?(): Program | undefined; + /** @internal */ sendPerformanceEvent?(kind: PerformanceEvent["kind"], durationMs: number): void; + getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; + /** @internal */ onReleaseParsedCommandLine?(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, optionOptions: CompilerOptions): void; + /** @internal */ getIncompleteCompletionsCache?(): IncompleteCompletionsCache; +} - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences): readonly CodeFixAction[]; - getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences): CombinedCodeActions; +/** @internal */ +export const emptyOptions = {}; - applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; - applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; - applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; - /** @deprecated `fileName` will be ignored */ - applyCodeActionCommand(fileName: string, action: CodeActionCommand): Promise; - /** @deprecated `fileName` will be ignored */ - applyCodeActionCommand(fileName: string, action: CodeActionCommand[]): Promise; - /** @deprecated `fileName` will be ignored */ - applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise; +export type WithMetadata = T & { metadata?: unknown; }; - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; - getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; - organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; - getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; +export const enum SemanticClassificationFormat { + Original = "original", + TwentyTwenty = "2020" +} - getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput; +// +// Public services of a language service instance associated +// with a language service host instance +// +export interface LanguageService { + /** This is used as a part of restarting the language service. */ + cleanupSemanticCache(): void; - getProgram(): Program | undefined; - /*@internal*/ getCurrentProgram(): Program | undefined; + /** + * Gets errors indicating invalid syntax in a file. + * + * In English, "this cdeo have, erorrs" is syntactically invalid because it has typos, + * grammatical errors, and misplaced punctuation. Likewise, examples of syntax + * errors in TypeScript are missing parentheses in an `if` statement, mismatched + * curly braces, and using a reserved keyword as a variable name. + * + * These diagnostics are inexpensive to compute and don't require knowledge of + * other files. Note that a non-empty result increases the likelihood of false positives + * from `getSemanticDiagnostics`. + * + * While these represent the majority of syntax-related diagnostics, there are some + * that require the type system, which will be present in `getSemanticDiagnostics`. + * + * @param fileName A path to the file you want syntactic diagnostics for + */ + getSyntacticDiagnostics(fileName: string): DiagnosticWithLocation[]; - /* @internal */ getNonBoundSourceFile(fileName: string): SourceFile; - /* @internal */ getAutoImportProvider(): Program | undefined; + /** + * Gets warnings or errors indicating type system issues in a given file. + * Requesting semantic diagnostics may start up the type system and + * run deferred work, so the first call may take longer than subsequent calls. + * + * Unlike the other get*Diagnostics functions, these diagnostics can potentially not + * include a reference to a source file. Specifically, the first time this is called, + * it will return global diagnostics with no associated location. + * + * To contrast the differences between semantic and syntactic diagnostics, consider the + * sentence: "The sun is green." is syntactically correct; those are real English words with + * correct sentence structure. However, it is semantically invalid, because it is not true. + * + * @param fileName A path to the file you want semantic diagnostics for + */ + getSemanticDiagnostics(fileName: string): Diagnostic[]; - /// Returns true if a suitable symbol was found in the project. - /// May set isDefinition properties in `referencedSymbols` to false. - /// May add elements to `knownSymbolSpans`. - /* @internal */ updateIsDefinitionOfReferencedSymbols(referencedSymbols: readonly ReferencedSymbol[], knownSymbolSpans: Set): boolean; + /** + * Gets suggestion diagnostics for a specific file. These diagnostics tend to + * proactively suggest refactors, as opposed to diagnostics that indicate + * potentially incorrect runtime behavior. + * + * @param fileName A path to the file you want semantic diagnostics for + */ + getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[]; - toggleLineComment(fileName: string, textRange: TextRange): TextChange[]; - toggleMultilineComment(fileName: string, textRange: TextRange): TextChange[]; - commentSelection(fileName: string, textRange: TextRange): TextChange[]; - uncommentSelection(fileName: string, textRange: TextRange): TextChange[]; + // TODO: Rename this to getProgramDiagnostics to better indicate that these are any + // diagnostics present for the program level, and not just 'options' diagnostics. - dispose(): void; - } + /** + * Gets global diagnostics related to the program configuration and compiler options. + */ + getCompilerOptionsDiagnostics(): Diagnostic[]; - export interface JsxClosingTagInfo { - readonly newText: string; - } + /** @deprecated Use getEncodedSyntacticClassifications instead. */ + getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; + getSyntacticClassifications(fileName: string, span: TextSpan, format: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[]; - export interface CombinedCodeFixScope { type: "file"; fileName: string; } + /** @deprecated Use getEncodedSemanticClassifications instead. */ + getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; + getSemanticClassifications(fileName: string, span: TextSpan, format: SemanticClassificationFormat): ClassifiedSpan[] | ClassifiedSpan2020[]; - export const enum OrganizeImportsMode { - All = "All", - SortAndCombine = "SortAndCombine", - RemoveUnused = "RemoveUnused", - } + /** Encoded as triples of [start, length, ClassificationType]. */ + getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications; - export interface OrganizeImportsArgs extends CombinedCodeFixScope { - /** @deprecated Use `mode` instead */ - skipDestructiveCodeActions?: boolean; - mode?: OrganizeImportsMode; - } + /** + * Gets semantic highlights information for a particular file. Has two formats, an older + * version used by VS and a format used by VS Code. + * + * @param fileName The path to the file + * @param position A text span to return results within + * @param format Which format to use, defaults to "original" + * @returns a number array encoded as triples of [start, length, ClassificationType, ...]. + */ + getEncodedSemanticClassifications(fileName: string, span: TextSpan, format?: SemanticClassificationFormat): Classifications; - export type CompletionsTriggerCharacter = "." | '"' | "'" | "`" | "/" | "@" | "<" | "#" | " "; + /** + * Gets completion entries at a particular position in a file. + * + * @param fileName The path to the file + * @param position A zero-based index of the character where you want the entries + * @param options An object describing how the request was triggered and what kinds + * of code actions can be returned with the completions. + * @param formattingSettings settings needed for calling formatting functions. + */ + getCompletionsAtPosition(fileName: string, position: number, options: GetCompletionsAtPositionOptions | undefined, formattingSettings?: FormatCodeSettings): WithMetadata | undefined; - export const enum CompletionTriggerKind { - /** Completion was triggered by typing an identifier, manual invocation (e.g Ctrl+Space) or via API. */ - Invoked = 1, + /** + * Gets the extended details for a completion entry retrieved from `getCompletionsAtPosition`. + * + * @param fileName The path to the file + * @param position A zero based index of the character where you want the entries + * @param entryName The `name` from an existing completion which came from `getCompletionsAtPosition` + * @param formatOptions How should code samples in the completions be formatted, can be undefined for backwards compatibility + * @param source `source` property from the completion entry + * @param preferences User settings, can be undefined for backwards compatibility + * @param data `data` property from the completion entry + */ + getCompletionEntryDetails( + fileName: string, + position: number, + entryName: string, + formatOptions: FormatCodeOptions | FormatCodeSettings | undefined, + source: string | undefined, + preferences: UserPreferences | undefined, + data: CompletionEntryData | undefined, + ): CompletionEntryDetails | undefined; + + getCompletionEntrySymbol(fileName: string, position: number, name: string, source: string | undefined): Symbol | undefined; - /** Completion was triggered by a trigger character. */ - TriggerCharacter = 2, + /** + * Gets semantic information about the identifier at a particular position in a + * file. Quick info is what you typically see when you hover in an editor. + * + * @param fileName The path to the file + * @param position A zero-based index of the character where you want the quick info + */ + getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined; - /** Completion was re-triggered as the current completion list is incomplete. */ - TriggerForIncompleteCompletions = 3, - } + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan | undefined; - export interface GetCompletionsAtPositionOptions extends UserPreferences { - /** - * If the editor is asking for completions because a certain character was typed - * (as opposed to when the user explicitly requested them) this should be set. - */ - triggerCharacter?: CompletionsTriggerCharacter; - triggerKind?: CompletionTriggerKind; - /** @deprecated Use includeCompletionsForModuleExports */ - includeExternalModuleExports?: boolean; - /** @deprecated Use includeCompletionsWithInsertText */ - includeInsertTextCompletions?: boolean; - } + getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan | undefined; - export type SignatureHelpTriggerCharacter = "," | "(" | "<"; - export type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")"; + getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): SignatureHelpItems | undefined; - export interface SignatureHelpItemsOptions { - triggerReason?: SignatureHelpTriggerReason; - } + getRenameInfo(fileName: string, position: number, preferences: UserPreferences): RenameInfo; + /** @deprecated Use the signature with `UserPreferences` instead. */ + getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo; - export type SignatureHelpTriggerReason = - | SignatureHelpInvokedReason - | SignatureHelpCharacterTypedReason - | SignatureHelpRetriggeredReason; + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): readonly RenameLocation[] | undefined; - /** - * Signals that the user manually requested signature help. - * The language service will unconditionally attempt to provide a result. - */ - export interface SignatureHelpInvokedReason { - kind: "invoked"; - triggerCharacter?: undefined; - } + getSmartSelectionRange(fileName: string, position: number): SelectionRange; + + /** @internal */ + // eslint-disable-next-line @typescript-eslint/unified-signatures + getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly: false, stopAtAlias: boolean): readonly DefinitionInfo[] | undefined; + /** @internal */ + // eslint-disable-next-line @typescript-eslint/unified-signatures + getDefinitionAtPosition(fileName: string, position: number, searchOtherFilesOnly: boolean, stopAtAlias: false): readonly DefinitionInfo[] | undefined; + getDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined; + getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined; + getTypeDefinitionAtPosition(fileName: string, position: number): readonly DefinitionInfo[] | undefined; + getImplementationAtPosition(fileName: string, position: number): readonly ImplementationLocation[] | undefined; + + getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined; + findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined; + getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] | undefined; + getFileReferences(fileName: string): ReferenceEntry[]; + + /** @deprecated */ + getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined; + + getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; + getNavigationBarItems(fileName: string): NavigationBarItem[]; + getNavigationTree(fileName: string): NavigationTree; + + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined; + provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[]; + provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[]; + + provideInlayHints(fileName: string, span: TextSpan, preferences: UserPreferences | undefined): InlayHint[] + getOutliningSpans(fileName: string): OutliningSpan[]; + getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; + getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; + getIndentationAtPosition(fileName: string, position: number, options: EditorOptions | EditorSettings): number; + + getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + + getDocCommentTemplateAtPosition(fileName: string, position: number, options?: DocCommentTemplateOptions): TextInsertion | undefined; + + isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; /** - * Signals that the signature help request came from a user typing a character. - * Depending on the character and the syntactic context, the request may or may not be served a result. + * This will return a defined result if the position is after the `>` of the opening tag, or somewhere in the text, of a JSXElement with no closing tag. + * Editors should call this after `>` is typed. */ - export interface SignatureHelpCharacterTypedReason { - kind: "characterTyped"; - /** - * Character that was responsible for triggering signature help. - */ - triggerCharacter: SignatureHelpTriggerCharacter; - } + getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined; + + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined; + + toLineColumnOffset?(fileName: string, position: number): LineAndCharacter; + /** @internal */ + getSourceMapper(): SourceMapper; + /** @internal */ + clearSourceMapperCache(): void; + + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: readonly number[], formatOptions: FormatCodeSettings, preferences: UserPreferences): readonly CodeFixAction[]; + getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings, preferences: UserPreferences): CombinedCodeActions; + + applyCodeActionCommand(action: CodeActionCommand, formatSettings?: FormatCodeSettings): Promise; + applyCodeActionCommand(action: CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[], formatSettings?: FormatCodeSettings): Promise; + /** @deprecated `fileName` will be ignored */ + applyCodeActionCommand(fileName: string, action: CodeActionCommand): Promise; + /** @deprecated `fileName` will be ignored */ + applyCodeActionCommand(fileName: string, action: CodeActionCommand[]): Promise; + /** @deprecated `fileName` will be ignored */ + applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise; + + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; + organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; + getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; + + getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput; + + getProgram(): Program | undefined; + /** @internal */ getCurrentProgram(): Program | undefined; + + /** @internal */ getNonBoundSourceFile(fileName: string): SourceFile; + /** @internal */ getAutoImportProvider(): Program | undefined; + + /// Returns true if a suitable symbol was found in the project. + /// May set isDefinition properties in `referencedSymbols` to false. + /// May add elements to `knownSymbolSpans`. + /** @internal */ updateIsDefinitionOfReferencedSymbols(referencedSymbols: readonly ReferencedSymbol[], knownSymbolSpans: Set): boolean; + + toggleLineComment(fileName: string, textRange: TextRange): TextChange[]; + toggleMultilineComment(fileName: string, textRange: TextRange): TextChange[]; + commentSelection(fileName: string, textRange: TextRange): TextChange[]; + uncommentSelection(fileName: string, textRange: TextRange): TextChange[]; + + dispose(): void; +} + +export interface JsxClosingTagInfo { + readonly newText: string; +} + +export interface CombinedCodeFixScope { type: "file"; fileName: string; } + +export const enum OrganizeImportsMode { + All = "All", + SortAndCombine = "SortAndCombine", + RemoveUnused = "RemoveUnused", +} + +export interface OrganizeImportsArgs extends CombinedCodeFixScope { + /** @deprecated Use `mode` instead */ + skipDestructiveCodeActions?: boolean; + mode?: OrganizeImportsMode; +} + +export type CompletionsTriggerCharacter = "." | '"' | "'" | "`" | "/" | "@" | "<" | "#" | " "; + +export const enum CompletionTriggerKind { + /** Completion was triggered by typing an identifier, manual invocation (e.g Ctrl+Space) or via API. */ + Invoked = 1, + + /** Completion was triggered by a trigger character. */ + TriggerCharacter = 2, + /** Completion was re-triggered as the current completion list is incomplete. */ + TriggerForIncompleteCompletions = 3, +} + +export interface GetCompletionsAtPositionOptions extends UserPreferences { /** - * Signals that this signature help request came from typing a character or moving the cursor. - * This should only occur if a signature help session was already active and the editor needs to see if it should adjust. - * The language service will unconditionally attempt to provide a result. - * `triggerCharacter` can be `undefined` for a retrigger caused by a cursor move. + * If the editor is asking for completions because a certain character was typed + * (as opposed to when the user explicitly requested them) this should be set. */ - export interface SignatureHelpRetriggeredReason { - kind: "retrigger"; - /** - * Character that was responsible for triggering signature help. - */ - triggerCharacter?: SignatureHelpRetriggerCharacter; - } + triggerCharacter?: CompletionsTriggerCharacter; + triggerKind?: CompletionTriggerKind; + /** @deprecated Use includeCompletionsForModuleExports */ + includeExternalModuleExports?: boolean; + /** @deprecated Use includeCompletionsWithInsertText */ + includeInsertTextCompletions?: boolean; +} - export interface ApplyCodeActionCommandResult { - successMessage: string; - } +export type SignatureHelpTriggerCharacter = "," | "(" | "<"; +export type SignatureHelpRetriggerCharacter = SignatureHelpTriggerCharacter | ")"; - export interface Classifications { - spans: number[]; - endOfLineState: EndOfLineState; - } +export interface SignatureHelpItemsOptions { + triggerReason?: SignatureHelpTriggerReason; +} - export interface ClassifiedSpan { - textSpan: TextSpan; - classificationType: ClassificationTypeNames; - } +export type SignatureHelpTriggerReason = + | SignatureHelpInvokedReason + | SignatureHelpCharacterTypedReason + | SignatureHelpRetriggeredReason; + +/** + * Signals that the user manually requested signature help. + * The language service will unconditionally attempt to provide a result. + */ +export interface SignatureHelpInvokedReason { + kind: "invoked"; + triggerCharacter?: undefined; +} - export interface ClassifiedSpan2020 { - textSpan: TextSpan; - classificationType: number; - } +/** + * Signals that the signature help request came from a user typing a character. + * Depending on the character and the syntactic context, the request may or may not be served a result. + */ +export interface SignatureHelpCharacterTypedReason { + kind: "characterTyped"; + /** + * Character that was responsible for triggering signature help. + */ + triggerCharacter: SignatureHelpTriggerCharacter; +} +/** + * Signals that this signature help request came from typing a character or moving the cursor. + * This should only occur if a signature help session was already active and the editor needs to see if it should adjust. + * The language service will unconditionally attempt to provide a result. + * `triggerCharacter` can be `undefined` for a retrigger caused by a cursor move. + */ +export interface SignatureHelpRetriggeredReason { + kind: "retrigger"; /** - * Navigation bar interface designed for visual studio's dual-column layout. - * This does not form a proper tree. - * The navbar is returned as a list of top-level items, each of which has a list of child items. - * Child items always have an empty array for their `childItems`. + * Character that was responsible for triggering signature help. */ - export interface NavigationBarItem { - text: string; - kind: ScriptElementKind; - kindModifiers: string; - spans: TextSpan[]; - childItems: NavigationBarItem[]; - indent: number; - bolded: boolean; - grayed: boolean; - } + triggerCharacter?: SignatureHelpRetriggerCharacter; +} + +export interface ApplyCodeActionCommandResult { + successMessage: string; +} + +export interface Classifications { + spans: number[]; + endOfLineState: EndOfLineState; +} +export interface ClassifiedSpan { + textSpan: TextSpan; + classificationType: ClassificationTypeNames; +} + +export interface ClassifiedSpan2020 { + textSpan: TextSpan; + classificationType: number; +} + +/** + * Navigation bar interface designed for visual studio's dual-column layout. + * This does not form a proper tree. + * The navbar is returned as a list of top-level items, each of which has a list of child items. + * Child items always have an empty array for their `childItems`. + */ +export interface NavigationBarItem { + text: string; + kind: ScriptElementKind; + kindModifiers: string; + spans: TextSpan[]; + childItems: NavigationBarItem[]; + indent: number; + bolded: boolean; + grayed: boolean; +} + +/** + * Node in a tree of nested declarations in a file. + * The top node is always a script or module node. + */ +export interface NavigationTree { + /** Name of the declaration, or a short description, e.g. "". */ + text: string; + kind: ScriptElementKind; + /** ScriptElementKindModifier separated by commas, e.g. "public,abstract" */ + kindModifiers: string; /** - * Node in a tree of nested declarations in a file. - * The top node is always a script or module node. + * Spans of the nodes that generated this declaration. + * There will be more than one if this is the result of merging. */ - export interface NavigationTree { - /** Name of the declaration, or a short description, e.g. "". */ - text: string; - kind: ScriptElementKind; - /** ScriptElementKindModifier separated by commas, e.g. "public,abstract" */ - kindModifiers: string; - /** - * Spans of the nodes that generated this declaration. - * There will be more than one if this is the result of merging. - */ - spans: TextSpan[]; - nameSpan: TextSpan | undefined; - /** Present if non-empty */ - childItems?: NavigationTree[]; - } + spans: TextSpan[]; + nameSpan: TextSpan | undefined; + /** Present if non-empty */ + childItems?: NavigationTree[]; +} - export interface CallHierarchyItem { - name: string; - kind: ScriptElementKind; - kindModifiers?: string; - file: string; - span: TextSpan; - selectionSpan: TextSpan; - containerName?: string; - } +export interface CallHierarchyItem { + name: string; + kind: ScriptElementKind; + kindModifiers?: string; + file: string; + span: TextSpan; + selectionSpan: TextSpan; + containerName?: string; +} - export interface CallHierarchyIncomingCall { - from: CallHierarchyItem; - fromSpans: TextSpan[]; - } +export interface CallHierarchyIncomingCall { + from: CallHierarchyItem; + fromSpans: TextSpan[]; +} - export interface CallHierarchyOutgoingCall { - to: CallHierarchyItem; - fromSpans: TextSpan[]; - } +export interface CallHierarchyOutgoingCall { + to: CallHierarchyItem; + fromSpans: TextSpan[]; +} - export const enum InlayHintKind { - Type = "Type", - Parameter = "Parameter", - Enum = "Enum", - } +export const enum InlayHintKind { + Type = "Type", + Parameter = "Parameter", + Enum = "Enum", +} - export interface InlayHint { - text: string; - position: number; - kind: InlayHintKind; - whitespaceBefore?: boolean; - whitespaceAfter?: boolean; - } +export interface InlayHint { + text: string; + position: number; + kind: InlayHintKind; + whitespaceBefore?: boolean; + whitespaceAfter?: boolean; +} - export interface TodoCommentDescriptor { - text: string; - priority: number; - } +export interface TodoCommentDescriptor { + text: string; + priority: number; +} - export interface TodoComment { - descriptor: TodoCommentDescriptor; - message: string; - position: number; - } +export interface TodoComment { + descriptor: TodoCommentDescriptor; + message: string; + position: number; +} - export interface TextChange { - span: TextSpan; - newText: string; - } +export interface TextChange { + span: TextSpan; + newText: string; +} - export interface FileTextChanges { - fileName: string; - textChanges: readonly TextChange[]; - isNewFile?: boolean; - } +export interface FileTextChanges { + fileName: string; + textChanges: readonly TextChange[]; + isNewFile?: boolean; +} - export interface CodeAction { - /** Description of the code action to display in the UI of the editor */ - description: string; - /** Text changes to apply to each file as part of the code action */ - changes: FileTextChanges[]; - /** - * If the user accepts the code fix, the editor should send the action back in a `applyAction` request. - * This allows the language service to have side effects (e.g. installing dependencies) upon a code fix. - */ - commands?: CodeActionCommand[]; - } +export interface CodeAction { + /** Description of the code action to display in the UI of the editor */ + description: string; + /** Text changes to apply to each file as part of the code action */ + changes: FileTextChanges[]; + /** + * If the user accepts the code fix, the editor should send the action back in a `applyAction` request. + * This allows the language service to have side effects (e.g. installing dependencies) upon a code fix. + */ + commands?: CodeActionCommand[]; +} - export interface CodeFixAction extends CodeAction { - /** Short name to identify the fix, for use by telemetry. */ - fixName: string; - /** - * If present, one may call 'getCombinedCodeFix' with this fixId. - * This may be omitted to indicate that the code fix can't be applied in a group. - */ - fixId?: {}; - fixAllDescription?: string; - } +export interface CodeFixAction extends CodeAction { + /** Short name to identify the fix, for use by telemetry. */ + fixName: string; + /** + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. + */ + fixId?: {}; + fixAllDescription?: string; +} - export interface CombinedCodeActions { - changes: readonly FileTextChanges[]; - commands?: readonly CodeActionCommand[]; - } +export interface CombinedCodeActions { + changes: readonly FileTextChanges[]; + commands?: readonly CodeActionCommand[]; +} - // Publicly, this type is just `{}`. Internally it is a union of all the actions we use. - // See `commands?: {}[]` in protocol.ts - export type CodeActionCommand = InstallPackageAction; +// Publicly, this type is just `{}`. Internally it is a union of all the actions we use. +// See `commands?: {}[]` in protocol.ts +export type CodeActionCommand = InstallPackageAction; - export interface InstallPackageAction { - /* @internal */ readonly type: "install package"; - /* @internal */ readonly file: string; - /* @internal */ readonly packageName: string; - } +export interface InstallPackageAction { + /** @internal */ readonly type: "install package"; + /** @internal */ readonly file: string; + /** @internal */ readonly packageName: string; +} +/** + * A set of one or more available refactoring actions, grouped under a parent refactoring. + */ +export interface ApplicableRefactorInfo { /** - * A set of one or more available refactoring actions, grouped under a parent refactoring. + * The programmatic name of the refactoring */ - export interface ApplicableRefactorInfo { - /** - * The programmatic name of the refactoring - */ - name: string; - /** - * A description of this refactoring category to show to the user. - * If the refactoring gets inlined (see below), this text will not be visible. - */ - description: string; - /** - * Inlineable refactorings can have their actions hoisted out to the top level - * of a context menu. Non-inlineanable refactorings should always be shown inside - * their parent grouping. - * - * If not specified, this value is assumed to be 'true' - */ - inlineable?: boolean; - - actions: RefactorActionInfo[]; - } + name: string; + /** + * A description of this refactoring category to show to the user. + * If the refactoring gets inlined (see below), this text will not be visible. + */ + description: string; + /** + * Inlineable refactorings can have their actions hoisted out to the top level + * of a context menu. Non-inlineanable refactorings should always be shown inside + * their parent grouping. + * + * If not specified, this value is assumed to be 'true' + */ + inlineable?: boolean; + + actions: RefactorActionInfo[]; +} +/** + * Represents a single refactoring action - for example, the "Extract Method..." refactor might + * offer several actions, each corresponding to a surround class or closure to extract into. + */ +export interface RefactorActionInfo { /** - * Represents a single refactoring action - for example, the "Extract Method..." refactor might - * offer several actions, each corresponding to a surround class or closure to extract into. + * The programmatic name of the refactoring action */ - export interface RefactorActionInfo { - /** - * The programmatic name of the refactoring action - */ - name: string; - - /** - * A description of this refactoring action to show to the user. - * If the parent refactoring is inlined away, this will be the only text shown, - * so this description should make sense by itself if the parent is inlineable=true - */ - description: string; - - /** - * A message to show to the user if the refactoring cannot be applied in - * the current context. - */ - notApplicableReason?: string; - - /** - * The hierarchical dotted name of the refactor action. - */ - kind?: string; - } + name: string; /** - * A set of edits to make in response to a refactor action, plus an optional - * location where renaming should be invoked from + * A description of this refactoring action to show to the user. + * If the parent refactoring is inlined away, this will be the only text shown, + * so this description should make sense by itself if the parent is inlineable=true */ - export interface RefactorEditInfo { - edits: FileTextChanges[]; - renameFilename?: string ; - renameLocation?: number; - commands?: CodeActionCommand[]; - } + description: string; - export type RefactorTriggerReason = "implicit" | "invoked"; + /** + * A message to show to the user if the refactoring cannot be applied in + * the current context. + */ + notApplicableReason?: string; - export interface TextInsertion { - newText: string; - /** The position in newText the caret should point to after the insertion. */ - caretOffset: number; - } + /** + * The hierarchical dotted name of the refactor action. + */ + kind?: string; +} - export interface DocumentSpan { - textSpan: TextSpan; - fileName: string; - - /** - * If the span represents a location that was remapped (e.g. via a .d.ts.map file), - * then the original filename and span will be specified here - */ - originalTextSpan?: TextSpan; - originalFileName?: string; - - /** - * If DocumentSpan.textSpan is the span for name of the declaration, - * then this is the span for relevant declaration - */ - contextSpan?: TextSpan; - originalContextSpan?: TextSpan; - } +/** + * A set of edits to make in response to a refactor action, plus an optional + * location where renaming should be invoked from + */ +export interface RefactorEditInfo { + edits: FileTextChanges[]; + renameFilename?: string ; + renameLocation?: number; + commands?: CodeActionCommand[]; +} - export interface RenameLocation extends DocumentSpan { - readonly prefixText?: string; - readonly suffixText?: string; - } +export type RefactorTriggerReason = "implicit" | "invoked"; - export interface ReferenceEntry extends DocumentSpan { - isWriteAccess: boolean; - isInString?: true; - } +export interface TextInsertion { + newText: string; + /** The position in newText the caret should point to after the insertion. */ + caretOffset: number; +} - export interface ImplementationLocation extends DocumentSpan { - kind: ScriptElementKind; - displayParts: SymbolDisplayPart[]; - } +export interface DocumentSpan { + textSpan: TextSpan; + fileName: string; - export const enum HighlightSpanKind { - none = "none", - definition = "definition", - reference = "reference", - writtenReference = "writtenReference", - } + /** + * If the span represents a location that was remapped (e.g. via a .d.ts.map file), + * then the original filename and span will be specified here + */ + originalTextSpan?: TextSpan; + originalFileName?: string; - export interface HighlightSpan { - fileName?: string; - isInString?: true; - textSpan: TextSpan; - contextSpan?: TextSpan; - kind: HighlightSpanKind; - } + /** + * If DocumentSpan.textSpan is the span for name of the declaration, + * then this is the span for relevant declaration + */ + contextSpan?: TextSpan; + originalContextSpan?: TextSpan; +} - export interface NavigateToItem { - name: string; - kind: ScriptElementKind; - kindModifiers: string; - matchKind: "exact" | "prefix" | "substring" | "camelCase"; - isCaseSensitive: boolean; - fileName: string; - textSpan: TextSpan; - containerName: string; - containerKind: ScriptElementKind; - } +export interface RenameLocation extends DocumentSpan { + readonly prefixText?: string; + readonly suffixText?: string; +} - export enum IndentStyle { - None = 0, - Block = 1, - Smart = 2, - } +export interface ReferenceEntry extends DocumentSpan { + isWriteAccess: boolean; + isInString?: true; +} - export enum SemicolonPreference { - Ignore = "ignore", - Insert = "insert", - Remove = "remove", - } +export interface ImplementationLocation extends DocumentSpan { + kind: ScriptElementKind; + displayParts: SymbolDisplayPart[]; +} - /** @deprecated - consider using EditorSettings instead */ - export interface EditorOptions { - BaseIndentSize?: number; - IndentSize: number; - TabSize: number; - NewLineCharacter: string; - ConvertTabsToSpaces: boolean; - IndentStyle: IndentStyle; - } +export const enum HighlightSpanKind { + none = "none", + definition = "definition", + reference = "reference", + writtenReference = "writtenReference", +} - // TODO: GH#18217 These are frequently asserted as defined - export interface EditorSettings { - baseIndentSize?: number; - indentSize?: number; - tabSize?: number; - newLineCharacter?: string; - convertTabsToSpaces?: boolean; - indentStyle?: IndentStyle; - trimTrailingWhitespace?: boolean; - } +export interface HighlightSpan { + fileName?: string; + isInString?: true; + textSpan: TextSpan; + contextSpan?: TextSpan; + kind: HighlightSpanKind; +} - /** @deprecated - consider using FormatCodeSettings instead */ - export interface FormatCodeOptions extends EditorOptions { - InsertSpaceAfterCommaDelimiter: boolean; - InsertSpaceAfterSemicolonInForStatements: boolean; - InsertSpaceBeforeAndAfterBinaryOperators: boolean; - InsertSpaceAfterConstructor?: boolean; - InsertSpaceAfterKeywordsInControlFlowStatements: boolean; - InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean; - InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; - InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean; - InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; - InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean; - InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; - InsertSpaceAfterTypeAssertion?: boolean; - InsertSpaceBeforeFunctionParenthesis?: boolean; - PlaceOpenBraceOnNewLineForFunctions: boolean; - PlaceOpenBraceOnNewLineForControlBlocks: boolean; - insertSpaceBeforeTypeAnnotation?: boolean; - } +export interface NavigateToItem { + name: string; + kind: ScriptElementKind; + kindModifiers: string; + matchKind: "exact" | "prefix" | "substring" | "camelCase"; + isCaseSensitive: boolean; + fileName: string; + textSpan: TextSpan; + containerName: string; + containerKind: ScriptElementKind; +} - export interface FormatCodeSettings extends EditorSettings { - readonly insertSpaceAfterCommaDelimiter?: boolean; - readonly insertSpaceAfterSemicolonInForStatements?: boolean; - readonly insertSpaceBeforeAndAfterBinaryOperators?: boolean; - readonly insertSpaceAfterConstructor?: boolean; - readonly insertSpaceAfterKeywordsInControlFlowStatements?: boolean; - readonly insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingEmptyBraces?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean; - readonly insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; - readonly insertSpaceAfterTypeAssertion?: boolean; - readonly insertSpaceBeforeFunctionParenthesis?: boolean; - readonly placeOpenBraceOnNewLineForFunctions?: boolean; - readonly placeOpenBraceOnNewLineForControlBlocks?: boolean; - readonly insertSpaceBeforeTypeAnnotation?: boolean; - readonly indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; - readonly semicolons?: SemicolonPreference; - } +export enum IndentStyle { + None = 0, + Block = 1, + Smart = 2, +} - export function getDefaultFormatCodeSettings(newLineCharacter?: string): FormatCodeSettings { - return { - indentSize: 4, - tabSize: 4, - newLineCharacter: newLineCharacter || "\n", - convertTabsToSpaces: true, - indentStyle: IndentStyle.Smart, - insertSpaceAfterConstructor: false, - insertSpaceAfterCommaDelimiter: true, - insertSpaceAfterSemicolonInForStatements: true, - insertSpaceBeforeAndAfterBinaryOperators: true, - insertSpaceAfterKeywordsInControlFlowStatements: true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, - insertSpaceBeforeFunctionParenthesis: false, - placeOpenBraceOnNewLineForFunctions: false, - placeOpenBraceOnNewLineForControlBlocks: false, - semicolons: SemicolonPreference.Ignore, - trimTrailingWhitespace: true - }; - } +export enum SemicolonPreference { + Ignore = "ignore", + Insert = "insert", + Remove = "remove", +} - /* @internal */ - export const testFormatSettings = getDefaultFormatCodeSettings("\n"); - - export interface DefinitionInfo extends DocumentSpan { - kind: ScriptElementKind; - name: string; - containerKind: ScriptElementKind; - containerName: string; - unverified?: boolean; - /** @internal - * Initially, this value is determined syntactically, but it is updated by the checker to cover - * cases like declarations that are exported in subsequent statements. As a result, the value - * may be "incomplete" if this span has yet to be checked. - */ - isLocal?: boolean; - /* @internal */ isAmbient?: boolean; - /* @internal */ failedAliasResolution?: boolean; - } +/** @deprecated - consider using EditorSettings instead */ +export interface EditorOptions { + BaseIndentSize?: number; + IndentSize: number; + TabSize: number; + NewLineCharacter: string; + ConvertTabsToSpaces: boolean; + IndentStyle: IndentStyle; +} - export interface DefinitionInfoAndBoundSpan { - definitions?: readonly DefinitionInfo[]; - textSpan: TextSpan; - } +// TODO: GH#18217 These are frequently asserted as defined +export interface EditorSettings { + baseIndentSize?: number; + indentSize?: number; + tabSize?: number; + newLineCharacter?: string; + convertTabsToSpaces?: boolean; + indentStyle?: IndentStyle; + trimTrailingWhitespace?: boolean; +} - export interface ReferencedSymbolDefinitionInfo extends DefinitionInfo { - displayParts: SymbolDisplayPart[]; - } +/** @deprecated - consider using FormatCodeSettings instead */ +export interface FormatCodeOptions extends EditorOptions { + InsertSpaceAfterCommaDelimiter: boolean; + InsertSpaceAfterSemicolonInForStatements: boolean; + InsertSpaceBeforeAndAfterBinaryOperators: boolean; + InsertSpaceAfterConstructor?: boolean; + InsertSpaceAfterKeywordsInControlFlowStatements: boolean; + InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; + InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean; + InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + InsertSpaceAfterTypeAssertion?: boolean; + InsertSpaceBeforeFunctionParenthesis?: boolean; + PlaceOpenBraceOnNewLineForFunctions: boolean; + PlaceOpenBraceOnNewLineForControlBlocks: boolean; + insertSpaceBeforeTypeAnnotation?: boolean; +} - export interface ReferencedSymbol { - definition: ReferencedSymbolDefinitionInfo; - references: ReferencedSymbolEntry[]; - } +export interface FormatCodeSettings extends EditorSettings { + readonly insertSpaceAfterCommaDelimiter?: boolean; + readonly insertSpaceAfterSemicolonInForStatements?: boolean; + readonly insertSpaceBeforeAndAfterBinaryOperators?: boolean; + readonly insertSpaceAfterConstructor?: boolean; + readonly insertSpaceAfterKeywordsInControlFlowStatements?: boolean; + readonly insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingEmptyBraces?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + readonly insertSpaceAfterTypeAssertion?: boolean; + readonly insertSpaceBeforeFunctionParenthesis?: boolean; + readonly placeOpenBraceOnNewLineForFunctions?: boolean; + readonly placeOpenBraceOnNewLineForControlBlocks?: boolean; + readonly insertSpaceBeforeTypeAnnotation?: boolean; + readonly indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; + readonly semicolons?: SemicolonPreference; +} - export interface ReferencedSymbolEntry extends ReferenceEntry { - isDefinition?: boolean; - } +export function getDefaultFormatCodeSettings(newLineCharacter?: string): FormatCodeSettings { + return { + indentSize: 4, + tabSize: 4, + newLineCharacter: newLineCharacter || "\n", + convertTabsToSpaces: true, + indentStyle: IndentStyle.Smart, + insertSpaceAfterConstructor: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceBeforeFunctionParenthesis: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + semicolons: SemicolonPreference.Ignore, + trimTrailingWhitespace: true + }; +} - export enum SymbolDisplayPartKind { - aliasName, - className, - enumName, - fieldName, - interfaceName, - keyword, - lineBreak, - numericLiteral, - stringLiteral, - localName, - methodName, - moduleName, - operator, - parameterName, - propertyName, - punctuation, - space, - text, - typeParameterName, - enumMemberName, - functionName, - regularExpressionLiteral, - link, - linkName, - linkText, - } +/** @internal */ +export const testFormatSettings = getDefaultFormatCodeSettings("\n"); + +export interface DefinitionInfo extends DocumentSpan { + kind: ScriptElementKind; + name: string; + containerKind: ScriptElementKind; + containerName: string; + unverified?: boolean; + /** @internal + * Initially, this value is determined syntactically, but it is updated by the checker to cover + * cases like declarations that are exported in subsequent statements. As a result, the value + * may be "incomplete" if this span has yet to be checked. + */ + isLocal?: boolean; + /** @internal */ isAmbient?: boolean; + /** @internal */ failedAliasResolution?: boolean; +} - export interface SymbolDisplayPart { - text: string; - kind: string; - } +export interface DefinitionInfoAndBoundSpan { + definitions?: readonly DefinitionInfo[]; + textSpan: TextSpan; +} - export interface JSDocLinkDisplayPart extends SymbolDisplayPart { - target: DocumentSpan; - } +export interface ReferencedSymbolDefinitionInfo extends DefinitionInfo { + displayParts: SymbolDisplayPart[]; +} - export interface JSDocTagInfo { - name: string; - text?: SymbolDisplayPart[]; - } +export interface ReferencedSymbol { + definition: ReferencedSymbolDefinitionInfo; + references: ReferencedSymbolEntry[]; +} - export interface QuickInfo { - kind: ScriptElementKind; - kindModifiers: string; - textSpan: TextSpan; - displayParts?: SymbolDisplayPart[]; - documentation?: SymbolDisplayPart[]; - tags?: JSDocTagInfo[]; - } +export interface ReferencedSymbolEntry extends ReferenceEntry { + isDefinition?: boolean; +} - export type RenameInfo = RenameInfoSuccess | RenameInfoFailure; - export interface RenameInfoSuccess { - canRename: true; - /** - * File or directory to rename. - * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. - */ - fileToRename?: string; - displayName: string; - fullDisplayName: string; - kind: ScriptElementKind; - kindModifiers: string; - triggerSpan: TextSpan; - } - export interface RenameInfoFailure { - canRename: false; - localizedErrorMessage: string; - } +export enum SymbolDisplayPartKind { + aliasName, + className, + enumName, + fieldName, + interfaceName, + keyword, + lineBreak, + numericLiteral, + stringLiteral, + localName, + methodName, + moduleName, + operator, + parameterName, + propertyName, + punctuation, + space, + text, + typeParameterName, + enumMemberName, + functionName, + regularExpressionLiteral, + link, + linkName, + linkText, +} + +export interface SymbolDisplayPart { + text: string; + kind: string; +} + +export interface JSDocLinkDisplayPart extends SymbolDisplayPart { + target: DocumentSpan; +} +export interface JSDocTagInfo { + name: string; + text?: SymbolDisplayPart[]; +} + +export interface QuickInfo { + kind: ScriptElementKind; + kindModifiers: string; + textSpan: TextSpan; + displayParts?: SymbolDisplayPart[]; + documentation?: SymbolDisplayPart[]; + tags?: JSDocTagInfo[]; +} + +export type RenameInfo = RenameInfoSuccess | RenameInfoFailure; +export interface RenameInfoSuccess { + canRename: true; /** - * @deprecated Use `UserPreferences` instead. + * File or directory to rename. + * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. */ - export interface RenameInfoOptions { - readonly allowRenameOfImportPath?: boolean; - } + fileToRename?: string; + displayName: string; + fullDisplayName: string; + kind: ScriptElementKind; + kindModifiers: string; + triggerSpan: TextSpan; +} +export interface RenameInfoFailure { + canRename: false; + localizedErrorMessage: string; +} - export interface DocCommentTemplateOptions { - readonly generateReturnInDocTemplate?: boolean; - } +/** + * @deprecated Use `UserPreferences` instead. + */ +export interface RenameInfoOptions { + readonly allowRenameOfImportPath?: boolean; +} - export interface SignatureHelpParameter { - name: string; - documentation: SymbolDisplayPart[]; - displayParts: SymbolDisplayPart[]; - isOptional: boolean; - isRest?: boolean; - } +export interface DocCommentTemplateOptions { + readonly generateReturnInDocTemplate?: boolean; +} - export interface SelectionRange { - textSpan: TextSpan; - parent?: SelectionRange; - } +export interface SignatureHelpParameter { + name: string; + documentation: SymbolDisplayPart[]; + displayParts: SymbolDisplayPart[]; + isOptional: boolean; + isRest?: boolean; +} + +export interface SelectionRange { + textSpan: TextSpan; + parent?: SelectionRange; +} + +/** + * Represents a single signature to show in signature help. + * The id is used for subsequent calls into the language service to ask questions about the + * signature help item in the context of any documents that have been updated. i.e. after + * an edit has happened, while signature help is still active, the host can ask important + * questions like 'what parameter is the user currently contained within?'. + */ +export interface SignatureHelpItem { + isVariadic: boolean; + prefixDisplayParts: SymbolDisplayPart[]; + suffixDisplayParts: SymbolDisplayPart[]; + separatorDisplayParts: SymbolDisplayPart[]; + parameters: SignatureHelpParameter[]; + documentation: SymbolDisplayPart[]; + tags: JSDocTagInfo[]; +} + +/** + * Represents a set of signature help items, and the preferred item that should be selected. + */ +export interface SignatureHelpItems { + items: SignatureHelpItem[]; + applicableSpan: TextSpan; + selectedItemIndex: number; + argumentIndex: number; + argumentCount: number; +} +// Do not change existing values, as they exist in telemetry. +export const enum CompletionInfoFlags { + None = 0, + MayIncludeAutoImports = 1 << 0, + IsImportStatementCompletion = 1 << 1, + IsContinuation = 1 << 2, + ResolvedModuleSpecifiers = 1 << 3, + ResolvedModuleSpecifiersBeyondLimit = 1 << 4, + MayIncludeMethodSnippets = 1 << 5, +} + +export interface CompletionInfo { + /** For performance telemetry. */ + flags?: CompletionInfoFlags; + /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ + isGlobalCompletion: boolean; + isMemberCompletion: boolean; /** - * Represents a single signature to show in signature help. - * The id is used for subsequent calls into the language service to ask questions about the - * signature help item in the context of any documents that have been updated. i.e. after - * an edit has happened, while signature help is still active, the host can ask important - * questions like 'what parameter is the user currently contained within?'. + * In the absence of `CompletionEntry["replacementSpan"]`, the editor may choose whether to use + * this span or its default one. If `CompletionEntry["replacementSpan"]` is defined, that span + * must be used to commit that completion entry. */ - export interface SignatureHelpItem { - isVariadic: boolean; - prefixDisplayParts: SymbolDisplayPart[]; - suffixDisplayParts: SymbolDisplayPart[]; - separatorDisplayParts: SymbolDisplayPart[]; - parameters: SignatureHelpParameter[]; - documentation: SymbolDisplayPart[]; - tags: JSDocTagInfo[]; - } - + optionalReplacementSpan?: TextSpan; /** - * Represents a set of signature help items, and the preferred item that should be selected. + * true when the current location also allows for a new identifier */ - export interface SignatureHelpItems { - items: SignatureHelpItem[]; - applicableSpan: TextSpan; - selectedItemIndex: number; - argumentIndex: number; - argumentCount: number; - } - - // Do not change existing values, as they exist in telemetry. - export const enum CompletionInfoFlags { - None = 0, - MayIncludeAutoImports = 1 << 0, - IsImportStatementCompletion = 1 << 1, - IsContinuation = 1 << 2, - ResolvedModuleSpecifiers = 1 << 3, - ResolvedModuleSpecifiersBeyondLimit = 1 << 4, - MayIncludeMethodSnippets = 1 << 5, - } + isNewIdentifierLocation: boolean; + /** + * Indicates to client to continue requesting completions on subsequent keystrokes. + */ + isIncomplete?: true; + entries: CompletionEntry[]; +} - export interface CompletionInfo { - /** For performance telemetry. */ - flags?: CompletionInfoFlags; - /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ - isGlobalCompletion: boolean; - isMemberCompletion: boolean; - /** - * In the absence of `CompletionEntry["replacementSpan"]`, the editor may choose whether to use - * this span or its default one. If `CompletionEntry["replacementSpan"]` is defined, that span - * must be used to commit that completion entry. - */ - optionalReplacementSpan?: TextSpan; - /** - * true when the current location also allows for a new identifier - */ - isNewIdentifierLocation: boolean; - /** - * Indicates to client to continue requesting completions on subsequent keystrokes. - */ - isIncomplete?: true; - entries: CompletionEntry[]; - } +export interface CompletionEntryDataAutoImport { + /** + * The name of the property or export in the module's symbol table. Differs from the completion name + * in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default. + */ + exportName: string; + moduleSpecifier?: string; + /** The file name declaring the export's module symbol, if it was an external module */ + fileName?: string; + /** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */ + ambientModuleName?: string; + /** True if the export was found in the package.json AutoImportProvider */ + isPackageJsonImport?: true; +} - export interface CompletionEntryDataAutoImport { - /** - * The name of the property or export in the module's symbol table. Differs from the completion name - * in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default. - */ - exportName: string; - moduleSpecifier?: string; - /** The file name declaring the export's module symbol, if it was an external module */ - fileName?: string; - /** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */ - ambientModuleName?: string; - /** True if the export was found in the package.json AutoImportProvider */ - isPackageJsonImport?: true; - } +export interface CompletionEntryDataUnresolved extends CompletionEntryDataAutoImport { + /** The key in the `ExportMapCache` where the completion entry's `SymbolExportInfo[]` is found */ + exportMapKey: string; +} - export interface CompletionEntryDataUnresolved extends CompletionEntryDataAutoImport { - /** The key in the `ExportMapCache` where the completion entry's `SymbolExportInfo[]` is found */ - exportMapKey: string; - } +export interface CompletionEntryDataResolved extends CompletionEntryDataAutoImport { + moduleSpecifier: string; +} - export interface CompletionEntryDataResolved extends CompletionEntryDataAutoImport { - moduleSpecifier: string; - } +export type CompletionEntryData = CompletionEntryDataUnresolved | CompletionEntryDataResolved; - export type CompletionEntryData = CompletionEntryDataUnresolved | CompletionEntryDataResolved; - - // see comments in protocol.ts - export interface CompletionEntry { - name: string; - kind: ScriptElementKind; - kindModifiers?: string; // see ScriptElementKindModifier, comma separated - sortText: string; - insertText?: string; - isSnippet?: true; - /** - * An optional span that indicates the text to be replaced by this completion item. - * If present, this span should be used instead of the default one. - * It will be set if the required span differs from the one generated by the default replacement behavior. - */ - replacementSpan?: TextSpan; - hasAction?: true; - source?: string; - sourceDisplay?: SymbolDisplayPart[]; - labelDetails?: CompletionEntryLabelDetails; - isRecommended?: true; - isFromUncheckedFile?: true; - isPackageJsonImport?: true; - isImportStatementCompletion?: true; - /** - * A property to be sent back to TS Server in the CompletionDetailsRequest, along with `name`, - * that allows TS Server to look up the symbol represented by the completion item, disambiguating - * items with the same name. Currently only defined for auto-import completions, but the type is - * `unknown` in the protocol, so it can be changed as needed to support other kinds of completions. - * The presence of this property should generally not be used to assume that this completion entry - * is an auto-import. - */ - data?: CompletionEntryData; - } +// see comments in protocol.ts +export interface CompletionEntry { + name: string; + kind: ScriptElementKind; + kindModifiers?: string; // see ScriptElementKindModifier, comma separated + sortText: string; + insertText?: string; + isSnippet?: true; + /** + * An optional span that indicates the text to be replaced by this completion item. + * If present, this span should be used instead of the default one. + * It will be set if the required span differs from the one generated by the default replacement behavior. + */ + replacementSpan?: TextSpan; + hasAction?: true; + source?: string; + sourceDisplay?: SymbolDisplayPart[]; + labelDetails?: CompletionEntryLabelDetails; + isRecommended?: true; + isFromUncheckedFile?: true; + isPackageJsonImport?: true; + isImportStatementCompletion?: true; + /** + * A property to be sent back to TS Server in the CompletionDetailsRequest, along with `name`, + * that allows TS Server to look up the symbol represented by the completion item, disambiguating + * items with the same name. Currently only defined for auto-import completions, but the type is + * `unknown` in the protocol, so it can be changed as needed to support other kinds of completions. + * The presence of this property should generally not be used to assume that this completion entry + * is an auto-import. + */ + data?: CompletionEntryData; +} - export interface CompletionEntryLabelDetails { - detail?: string; - description?: string; - } +export interface CompletionEntryLabelDetails { + detail?: string; + description?: string; +} - export interface CompletionEntryDetails { - name: string; - kind: ScriptElementKind; - kindModifiers: string; // see ScriptElementKindModifier, comma separated - displayParts: SymbolDisplayPart[]; - documentation?: SymbolDisplayPart[]; - tags?: JSDocTagInfo[]; - codeActions?: CodeAction[]; - /** @deprecated Use `sourceDisplay` instead. */ - source?: SymbolDisplayPart[]; - sourceDisplay?: SymbolDisplayPart[]; - } +export interface CompletionEntryDetails { + name: string; + kind: ScriptElementKind; + kindModifiers: string; // see ScriptElementKindModifier, comma separated + displayParts: SymbolDisplayPart[]; + documentation?: SymbolDisplayPart[]; + tags?: JSDocTagInfo[]; + codeActions?: CodeAction[]; + /** @deprecated Use `sourceDisplay` instead. */ + source?: SymbolDisplayPart[]; + sourceDisplay?: SymbolDisplayPart[]; +} - export interface OutliningSpan { - /** The span of the document to actually collapse. */ - textSpan: TextSpan; +export interface OutliningSpan { + /** The span of the document to actually collapse. */ + textSpan: TextSpan; - /** The span of the document to display when the user hovers over the collapsed span. */ - hintSpan: TextSpan; + /** The span of the document to display when the user hovers over the collapsed span. */ + hintSpan: TextSpan; - /** The text to display in the editor for the collapsed region. */ - bannerText: string; + /** The text to display in the editor for the collapsed region. */ + bannerText: string; - /** - * Whether or not this region should be automatically collapsed when - * the 'Collapse to Definitions' command is invoked. - */ - autoCollapse: boolean; + /** + * Whether or not this region should be automatically collapsed when + * the 'Collapse to Definitions' command is invoked. + */ + autoCollapse: boolean; - /** - * Classification of the contents of the span - */ - kind: OutliningSpanKind; - } + /** + * Classification of the contents of the span + */ + kind: OutliningSpanKind; +} - export const enum OutliningSpanKind { - /** Single or multi-line comments */ - Comment = "comment", +export const enum OutliningSpanKind { + /** Single or multi-line comments */ + Comment = "comment", - /** Sections marked by '// #region' and '// #endregion' comments */ - Region = "region", + /** Sections marked by '// #region' and '// #endregion' comments */ + Region = "region", - /** Declarations and expressions */ - Code = "code", + /** Declarations and expressions */ + Code = "code", - /** Contiguous blocks of import declarations */ - Imports = "imports" - } + /** Contiguous blocks of import declarations */ + Imports = "imports" +} - export const enum OutputFileType { - JavaScript, - SourceMap, - Declaration - } +export const enum OutputFileType { + JavaScript, + SourceMap, + Declaration +} - export const enum EndOfLineState { - None, - InMultiLineCommentTrivia, - InSingleQuoteStringLiteral, - InDoubleQuoteStringLiteral, - InTemplateHeadOrNoSubstitutionTemplate, - InTemplateMiddleOrTail, - InTemplateSubstitutionPosition, - } +export const enum EndOfLineState { + None, + InMultiLineCommentTrivia, + InSingleQuoteStringLiteral, + InDoubleQuoteStringLiteral, + InTemplateHeadOrNoSubstitutionTemplate, + InTemplateMiddleOrTail, + InTemplateSubstitutionPosition, +} - export enum TokenClass { - Punctuation, - Keyword, - Operator, - Comment, - Whitespace, - Identifier, - NumberLiteral, - BigIntLiteral, - StringLiteral, - RegExpLiteral, - } +export enum TokenClass { + Punctuation, + Keyword, + Operator, + Comment, + Whitespace, + Identifier, + NumberLiteral, + BigIntLiteral, + StringLiteral, + RegExpLiteral, +} - export interface ClassificationResult { - finalLexState: EndOfLineState; - entries: ClassificationInfo[]; - } +export interface ClassificationResult { + finalLexState: EndOfLineState; + entries: ClassificationInfo[]; +} - export interface ClassificationInfo { - length: number; - classification: TokenClass; - } +export interface ClassificationInfo { + length: number; + classification: TokenClass; +} - export interface Classifier { - /** - * Gives lexical classifications of tokens on a line without any syntactic context. - * For instance, a token consisting of the text 'string' can be either an identifier - * named 'string' or the keyword 'string', however, because this classifier is not aware, - * it relies on certain heuristics to give acceptable results. For classifications where - * speed trumps accuracy, this function is preferable; however, for true accuracy, the - * syntactic classifier is ideal. In fact, in certain editing scenarios, combining the - * lexical, syntactic, and semantic classifiers may issue the best user experience. - * - * @param text The text of a line to classify. - * @param lexState The state of the lexical classifier at the end of the previous line. - * @param syntacticClassifierAbsent Whether the client is *not* using a syntactic classifier. - * If there is no syntactic classifier (syntacticClassifierAbsent=true), - * certain heuristics may be used in its place; however, if there is a - * syntactic classifier (syntacticClassifierAbsent=false), certain - * classifications which may be incorrectly categorized will be given - * back as Identifiers in order to allow the syntactic classifier to - * subsume the classification. - * @deprecated Use getLexicalClassifications instead. - */ - getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): ClassificationResult; - getEncodedLexicalClassifications(text: string, endOfLineState: EndOfLineState, syntacticClassifierAbsent: boolean): Classifications; - } +export interface Classifier { + /** + * Gives lexical classifications of tokens on a line without any syntactic context. + * For instance, a token consisting of the text 'string' can be either an identifier + * named 'string' or the keyword 'string', however, because this classifier is not aware, + * it relies on certain heuristics to give acceptable results. For classifications where + * speed trumps accuracy, this function is preferable; however, for true accuracy, the + * syntactic classifier is ideal. In fact, in certain editing scenarios, combining the + * lexical, syntactic, and semantic classifiers may issue the best user experience. + * + * @param text The text of a line to classify. + * @param lexState The state of the lexical classifier at the end of the previous line. + * @param syntacticClassifierAbsent Whether the client is *not* using a syntactic classifier. + * If there is no syntactic classifier (syntacticClassifierAbsent=true), + * certain heuristics may be used in its place; however, if there is a + * syntactic classifier (syntacticClassifierAbsent=false), certain + * classifications which may be incorrectly categorized will be given + * back as Identifiers in order to allow the syntactic classifier to + * subsume the classification. + * @deprecated Use getLexicalClassifications instead. + */ + getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): ClassificationResult; + getEncodedLexicalClassifications(text: string, endOfLineState: EndOfLineState, syntacticClassifierAbsent: boolean): Classifications; +} - export const enum ScriptElementKind { - unknown = "", - warning = "warning", +export const enum ScriptElementKind { + unknown = "", + warning = "warning", - /** predefined type (void) or keyword (class) */ - keyword = "keyword", + /** predefined type (void) or keyword (class) */ + keyword = "keyword", - /** top level script node */ - scriptElement = "script", + /** top level script node */ + scriptElement = "script", - /** module foo {} */ - moduleElement = "module", + /** module foo {} */ + moduleElement = "module", - /** class X {} */ - classElement = "class", + /** class X {} */ + classElement = "class", - /** var x = class X {} */ - localClassElement = "local class", + /** var x = class X {} */ + localClassElement = "local class", - /** interface Y {} */ - interfaceElement = "interface", + /** interface Y {} */ + interfaceElement = "interface", - /** type T = ... */ - typeElement = "type", + /** type T = ... */ + typeElement = "type", - /** enum E */ - enumElement = "enum", - enumMemberElement = "enum member", + /** enum E */ + enumElement = "enum", + enumMemberElement = "enum member", - /** - * Inside module and script only - * const v = .. - */ - variableElement = "var", + /** + * Inside module and script only + * const v = .. + */ + variableElement = "var", - /** Inside function */ - localVariableElement = "local var", + /** Inside function */ + localVariableElement = "local var", - /** - * Inside module and script only - * function f() { } - */ - functionElement = "function", + /** + * Inside module and script only + * function f() { } + */ + functionElement = "function", - /** Inside function */ - localFunctionElement = "local function", + /** Inside function */ + localFunctionElement = "local function", - /** class X { [public|private]* foo() {} } */ - memberFunctionElement = "method", + /** class X { [public|private]* foo() {} } */ + memberFunctionElement = "method", - /** class X { [public|private]* [get|set] foo:number; } */ - memberGetAccessorElement = "getter", - memberSetAccessorElement = "setter", + /** class X { [public|private]* [get|set] foo:number; } */ + memberGetAccessorElement = "getter", + memberSetAccessorElement = "setter", - /** - * class X { [public|private]* foo:number; } - * interface Y { foo:number; } - */ - memberVariableElement = "property", + /** + * class X { [public|private]* foo:number; } + * interface Y { foo:number; } + */ + memberVariableElement = "property", - /** class X { [public|private]* accessor foo: number; } */ - memberAccessorVariableElement = "accessor", + /** class X { [public|private]* accessor foo: number; } */ + memberAccessorVariableElement = "accessor", - /** - * class X { constructor() { } } - * class X { static { } } - */ - constructorImplementationElement = "constructor", + /** + * class X { constructor() { } } + * class X { static { } } + */ + constructorImplementationElement = "constructor", - /** interface Y { ():number; } */ - callSignatureElement = "call", + /** interface Y { ():number; } */ + callSignatureElement = "call", - /** interface Y { []:number; } */ - indexSignatureElement = "index", + /** interface Y { []:number; } */ + indexSignatureElement = "index", - /** interface Y { new():Y; } */ - constructSignatureElement = "construct", + /** interface Y { new():Y; } */ + constructSignatureElement = "construct", - /** function foo(*Y*: string) */ - parameterElement = "parameter", + /** function foo(*Y*: string) */ + parameterElement = "parameter", - typeParameterElement = "type parameter", + typeParameterElement = "type parameter", - primitiveType = "primitive type", + primitiveType = "primitive type", - label = "label", + label = "label", - alias = "alias", + alias = "alias", - constElement = "const", + constElement = "const", - letElement = "let", + letElement = "let", - directory = "directory", + directory = "directory", - externalModuleName = "external module name", + externalModuleName = "external module name", - /** - * - * @deprecated - */ - jsxAttribute = "JSX attribute", + /** + * + * @deprecated + */ + jsxAttribute = "JSX attribute", - /** String literal */ - string = "string", + /** String literal */ + string = "string", - /** Jsdoc @link: in `{@link C link text}`, the before and after text "{@link " and "}" */ - link = "link", + /** Jsdoc @link: in `{@link C link text}`, the before and after text "{@link " and "}" */ + link = "link", - /** Jsdoc @link: in `{@link C link text}`, the entity name "C" */ - linkName = "link name", + /** Jsdoc @link: in `{@link C link text}`, the entity name "C" */ + linkName = "link name", - /** Jsdoc @link: in `{@link C link text}`, the link text "link text" */ - linkText = "link text", - } + /** Jsdoc @link: in `{@link C link text}`, the link text "link text" */ + linkText = "link text", +} - export const enum ScriptElementKindModifier { - none = "", - publicMemberModifier = "public", - privateMemberModifier = "private", - protectedMemberModifier = "protected", - exportedModifier = "export", - ambientModifier = "declare", - staticModifier = "static", - abstractModifier = "abstract", - optionalModifier = "optional", - - deprecatedModifier = "deprecated", - - dtsModifier = ".d.ts", - tsModifier = ".ts", - tsxModifier = ".tsx", - jsModifier = ".js", - jsxModifier = ".jsx", - jsonModifier = ".json", - dmtsModifier = ".d.mts", - mtsModifier = ".mts", - mjsModifier = ".mjs", - dctsModifier = ".d.cts", - ctsModifier = ".cts", - cjsModifier = ".cjs", - } +export const enum ScriptElementKindModifier { + none = "", + publicMemberModifier = "public", + privateMemberModifier = "private", + protectedMemberModifier = "protected", + exportedModifier = "export", + ambientModifier = "declare", + staticModifier = "static", + abstractModifier = "abstract", + optionalModifier = "optional", + + deprecatedModifier = "deprecated", + + dtsModifier = ".d.ts", + tsModifier = ".ts", + tsxModifier = ".tsx", + jsModifier = ".js", + jsxModifier = ".jsx", + jsonModifier = ".json", + dmtsModifier = ".d.mts", + mtsModifier = ".mts", + mjsModifier = ".mjs", + dctsModifier = ".d.cts", + ctsModifier = ".cts", + cjsModifier = ".cjs", +} - export const enum ClassificationTypeNames { - comment = "comment", - identifier = "identifier", - keyword = "keyword", - numericLiteral = "number", - bigintLiteral = "bigint", - operator = "operator", - stringLiteral = "string", - whiteSpace = "whitespace", - text = "text", - - punctuation = "punctuation", - - className = "class name", - enumName = "enum name", - interfaceName = "interface name", - moduleName = "module name", - typeParameterName = "type parameter name", - typeAliasName = "type alias name", - parameterName = "parameter name", - docCommentTagName = "doc comment tag name", - jsxOpenTagName = "jsx open tag name", - jsxCloseTagName = "jsx close tag name", - jsxSelfClosingTagName = "jsx self closing tag name", - jsxAttribute = "jsx attribute", - jsxText = "jsx text", - jsxAttributeStringLiteralValue = "jsx attribute string literal value", - } +export const enum ClassificationTypeNames { + comment = "comment", + identifier = "identifier", + keyword = "keyword", + numericLiteral = "number", + bigintLiteral = "bigint", + operator = "operator", + stringLiteral = "string", + whiteSpace = "whitespace", + text = "text", + + punctuation = "punctuation", + + className = "class name", + enumName = "enum name", + interfaceName = "interface name", + moduleName = "module name", + typeParameterName = "type parameter name", + typeAliasName = "type alias name", + parameterName = "parameter name", + docCommentTagName = "doc comment tag name", + jsxOpenTagName = "jsx open tag name", + jsxCloseTagName = "jsx close tag name", + jsxSelfClosingTagName = "jsx self closing tag name", + jsxAttribute = "jsx attribute", + jsxText = "jsx text", + jsxAttributeStringLiteralValue = "jsx attribute string literal value", +} - export const enum ClassificationType { - comment = 1, - identifier = 2, - keyword = 3, - numericLiteral = 4, - operator = 5, - stringLiteral = 6, - regularExpressionLiteral = 7, - whiteSpace = 8, - text = 9, - punctuation = 10, - className = 11, - enumName = 12, - interfaceName = 13, - moduleName = 14, - typeParameterName = 15, - typeAliasName = 16, - parameterName = 17, - docCommentTagName = 18, - jsxOpenTagName = 19, - jsxCloseTagName = 20, - jsxSelfClosingTagName = 21, - jsxAttribute = 22, - jsxText = 23, - jsxAttributeStringLiteralValue = 24, - bigintLiteral = 25, - } +export const enum ClassificationType { + comment = 1, + identifier = 2, + keyword = 3, + numericLiteral = 4, + operator = 5, + stringLiteral = 6, + regularExpressionLiteral = 7, + whiteSpace = 8, + text = 9, + punctuation = 10, + className = 11, + enumName = 12, + interfaceName = 13, + moduleName = 14, + typeParameterName = 15, + typeAliasName = 16, + parameterName = 17, + docCommentTagName = 18, + jsxOpenTagName = 19, + jsxCloseTagName = 20, + jsxSelfClosingTagName = 21, + jsxAttribute = 22, + jsxText = 23, + jsxAttributeStringLiteralValue = 24, + bigintLiteral = 25, +} - /** @internal */ - export interface CodeFixRegistration { - errorCodes: readonly number[]; - getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined; - fixIds?: readonly string[]; - getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; - } +/** @internal */ +export interface CodeFixRegistration { + errorCodes: readonly number[]; + getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined; + fixIds?: readonly string[]; + getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; +} - /** @internal */ - export interface CodeFixContextBase extends textChanges.TextChangesContext { - sourceFile: SourceFile; - program: Program; - cancellationToken: CancellationToken; - preferences: UserPreferences; - } +/** @internal */ +export interface CodeFixContextBase extends textChanges.TextChangesContext { + sourceFile: SourceFile; + program: Program; + cancellationToken: CancellationToken; + preferences: UserPreferences; +} - /** @internal */ - export interface CodeFixAllContext extends CodeFixContextBase { - fixId: {}; - } +/** @internal */ +export interface CodeFixAllContext extends CodeFixContextBase { + fixId: {}; +} - /** @internal */ - export interface CodeFixContext extends CodeFixContextBase { - errorCode: number; - span: TextSpan; - } +/** @internal */ +export interface CodeFixContext extends CodeFixContextBase { + errorCode: number; + span: TextSpan; +} - /** @internal */ - export interface Refactor { - /** List of action kinds a refactor can provide. - * Used to skip unnecessary calculation when specific refactors are requested. */ - kinds?: string[]; +/** @internal */ +export interface Refactor { + /** List of action kinds a refactor can provide. + * Used to skip unnecessary calculation when specific refactors are requested. */ + kinds?: string[]; - /** Compute the associated code actions */ - getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined; + /** Compute the associated code actions */ + getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined; - /** Compute (quickly) which actions are available here */ - getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[]; - } + /** Compute (quickly) which actions are available here */ + getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[]; +} - /** @internal */ - export interface RefactorContext extends textChanges.TextChangesContext { - file: SourceFile; - startPosition: number; - endPosition?: number; - program: Program; - cancellationToken?: CancellationToken; - preferences: UserPreferences; - triggerReason?: RefactorTriggerReason; - kind?: string; - } +/** @internal */ +export interface RefactorContext extends textChanges.TextChangesContext { + file: SourceFile; + startPosition: number; + endPosition?: number; + program: Program; + cancellationToken?: CancellationToken; + preferences: UserPreferences; + triggerReason?: RefactorTriggerReason; + kind?: string; +} - export interface InlayHintsContext { - file: SourceFile; - program: Program; - cancellationToken: CancellationToken; - host: LanguageServiceHost; - span: TextSpan; - preferences: UserPreferences; - } +export interface InlayHintsContext { + file: SourceFile; + program: Program; + cancellationToken: CancellationToken; + host: LanguageServiceHost; + span: TextSpan; + preferences: UserPreferences; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 76d63dd114e97..be9740a77ef00 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1,3431 +1,3734 @@ -/* @internal */ // Don't expose that we use this -// Based on lib.es6.d.ts -interface PromiseConstructor { - new (executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => void): Promise; - reject(reason: any): Promise; - all(values: (T | PromiseLike)[]): Promise; -} -/* @internal */ -declare var Promise: PromiseConstructor; // eslint-disable-line no-var - -/* @internal */ -namespace ts { - // These utilities are common to multiple language service features. - //#region - export const scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); - - export const enum SemanticMeaning { - None = 0x0, - Value = 0x1, - Type = 0x2, - Namespace = 0x4, - All = Value | Type | Namespace - } - - export function getMeaningFromDeclaration(node: Node): SemanticMeaning { - switch (node.kind) { - case SyntaxKind.VariableDeclaration: - return isInJSFile(node) && getJSDocEnumTag(node) ? SemanticMeaning.All : SemanticMeaning.Value; - - case SyntaxKind.Parameter: - case SyntaxKind.BindingElement: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.CatchClause: - case SyntaxKind.JsxAttribute: - return SemanticMeaning.Value; +import { + __String, addEmitFlags, addSyntheticLeadingComment, addSyntheticTrailingComment, AnyImportOrRequireStatement, + assertType, AssignmentDeclarationKind, BinaryExpression, binarySearchKey, BindingElement, BreakOrContinueStatement, + CallExpression, canHaveModifiers, CaseClause, cast, CatchClause, CharacterCodes, ClassDeclaration, ClassExpression, + clone, codefix, combinePaths, CommentKind, CommentRange, compareTextSpans, compareValues, Comparison, + CompilerOptions, ConditionalExpression, contains, createPrinter, createRange, createScanner, createTextSpan, + createTextSpanFromBounds, Debug, Declaration, Decorator, defaultMaximumTruncationLength, DeleteExpression, + Diagnostic, DiagnosticMessage, DiagnosticWithLocation, directoryProbablyExists, DisplayPartsSymbolWriter, + DocumentPosition, DocumentSpan, DoStatement, ElementAccessExpression, EmitFlags, EmitHint, emptyArray, + EndOfFileToken, ensureScriptKind, EqualityOperator, escapeString, ExportAssignment, ExportDeclaration, Expression, + ExpressionStatement, factory, FileTextChanges, filter, find, findAncestor, findConfigFile, first, firstDefined, + firstOrUndefined, forEachAncestorDirectory, forEachChild, forEachLeadingCommentRange, forEachTrailingCommentRange, + FormatCodeSettings, formatStringFromArgs, formatting, FormattingHost, ForOfStatement, FunctionDeclaration, + FunctionExpression, FunctionLikeDeclaration, getAssignmentDeclarationKind, getCombinedNodeFlagsAlwaysIncludeJSDoc, + getDirectoryPath, getEmitScriptTarget, getExternalModuleImportEqualsDeclarationExpression, getIndentString, + getJSDocEnumTag, getLastChild, getLineAndCharacterOfPosition, getLineStarts, getLocaleSpecificMessage, + getModuleInstanceState, getNameOfDeclaration, getNodeId, getPackageNameFromTypesPackageName, getPathComponents, + getRootDeclaration, getSourceFileOfNode, getSpanOfTokenAtPosition, getSymbolId, getTextOfIdentifierOrLiteral, + getTextOfNode, getTypesPackageName, hasSyntacticModifier, HeritageClause, Identifier, identifierIsThisKeyword, + identity, idText, IfStatement, ImportClause, ImportDeclaration, ImportSpecifier, ImportTypeNode, indexOfNode, + IndexSignatureDeclaration, InternalSymbolName, isAmbientModule, isAnyImportSyntax, isArray, isArrayBindingPattern, + isArrayTypeNode, isAsExpression, isAwaitExpression, isBinaryExpression, isBindingElement, + isBreakOrContinueStatement, isCallExpression, isCallOrNewExpression, isClassDeclaration, isClassExpression, + isClassStaticBlockDeclaration, isConditionalTypeNode, IScriptSnapshot, isDeclaration, isDeclarationName, + isDecorator, isDeleteExpression, isElementAccessExpression, isEntityName, isEnumDeclaration, isExportAssignment, + isExportDeclaration, isExportSpecifier, isExpression, isExpressionNode, isExternalModule, + isExternalModuleImportEqualsDeclaration, isExternalModuleReference, isFileLevelUniqueName, isForInStatement, + isForOfStatement, isFunctionBlock, isFunctionDeclaration, isFunctionExpression, isFunctionLike, + isGetAccessorDeclaration, isGlobalScopeAugmentation, isHeritageClause, isIdentifier, isImportCall, isImportClause, + isImportDeclaration, isImportEqualsDeclaration, isImportOrExportSpecifier, isImportSpecifier, isInferTypeNode, + isInJSFile, isInterfaceDeclaration, isInternalModuleImportEqualsDeclaration, isJSDoc, isJSDocCommentContainingNode, + isJSDocLink, isJSDocLinkCode, isJSDocLinkLike, isJSDocMemberName, isJSDocNameReference, isJSDocTag, + isJSDocTemplateTag, isJSDocTypeAlias, isJsxElement, isJsxExpression, isJsxOpeningLikeElement, isJsxText, isKeyword, + isLabeledStatement, isLet, isLiteralTypeNode, isMappedTypeNode, isModifier, isModuleBlock, isModuleDeclaration, + isNamedDeclaration, isNamedExports, isNamedImports, isNamespaceExport, isNamespaceImport, isNewExpression, + isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isOptionalChain, isOptionalChainRoot, + isParameter, isPartOfTypeNode, isPrivateIdentifier, isPropertyAccessExpression, isPropertyNameLiteral, + isQualifiedName, isRequireCall, isRequireVariableStatement, isRightSideOfQualifiedNameOrPropertyAccess, + isRootedDiskPath, isSetAccessorDeclaration, isSourceFile, isSourceFileJS, isStringDoubleQuoted, isStringLiteral, + isStringLiteralLike, isStringOrNumericLiteralLike, isStringTextContainingNode, isSyntaxList, + isTaggedTemplateExpression, isTemplateLiteralKind, isToken, isTypeAliasDeclaration, isTypeElement, isTypeNode, + isTypeOfExpression, isTypeOperatorNode, isTypeParameterDeclaration, isTypeReferenceNode, isVarConst, + isVariableDeclarationList, isVoidExpression, isWhiteSpaceLike, isWhiteSpaceSingleLine, isYieldExpression, + IterationStatement, JSDocLink, JSDocLinkCode, JSDocLinkDisplayPart, JSDocLinkPlain, JSDocTypedefTag, JsTyping, + JsxEmit, JsxOpeningLikeElement, LabeledStatement, LanguageServiceHost, last, lastOrUndefined, LiteralExpression, + map, Map, maybeBind, Modifier, ModifierFlags, ModuleDeclaration, ModuleInstanceState, ModuleResolutionKind, + ModuleSpecifierResolutionHost, moduleSpecifiers, Mutable, NewExpression, NewLineKind, Node, NodeArray, + NodeBuilderFlags, NodeFlags, nodeIsMissing, nodeIsPresent, nodeIsSynthesized, noop, normalizePath, + NoSubstitutionTemplateLiteral, notImplemented, nullTransformationContext, NumericLiteral, or, OrganizeImports, + PackageJsonDependencyGroup, pathIsRelative, PrefixUnaryExpression, Program, ProjectPackageJsonInfo, + PropertyAccessExpression, PropertyAssignment, PropertyName, QualifiedName, RefactorContext, Scanner, + ScriptElementKind, ScriptElementKindModifier, ScriptKind, ScriptTarget, SemicolonPreference, setConfigFileInOptions, + setOriginalNode, setTextRange, Signature, SignatureDeclaration, singleOrUndefined, skipAlias, skipOuterExpressions, + some, SourceFile, SourceFileLike, SourceMapper, SpreadElement, stableSort, startsWith, stringContains, + StringLiteral, StringLiteralLike, stringToToken, stripQuotes, Symbol, SymbolAccessibility, SymbolDisplayPart, + SymbolDisplayPartKind, SymbolFlags, SymbolFormatFlags, SymbolTracker, SyntaxKind, SyntaxList, + TaggedTemplateExpression, TemplateExpression, TemplateLiteralToken, TemplateSpan, TextChange, textChanges, + TextRange, TextSpan, textSpanContainsPosition, textSpanContainsTextSpan, textSpanEnd, Token, tokenToString, + TransientSymbol, tryCast, Type, TypeChecker, TypeFormatFlags, TypeNode, TypeOfExpression, TypeQueryNode, + unescapeLeadingUnderscores, UserPreferences, VariableDeclaration, visitEachChild, VoidExpression, YieldExpression, +} from "./_namespaces/ts"; + +// These utilities are common to multiple language service features. +//#region +/** @internal */ +export const scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); + +/** @internal */ +export const enum SemanticMeaning { + None = 0x0, + Value = 0x1, + Type = 0x2, + Namespace = 0x4, + All = Value | Type | Namespace +} - case SyntaxKind.TypeParameter: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.TypeLiteral: - return SemanticMeaning.Type; +/** @internal */ +export function getMeaningFromDeclaration(node: Node): SemanticMeaning { + switch (node.kind) { + case SyntaxKind.VariableDeclaration: + return isInJSFile(node) && getJSDocEnumTag(node) ? SemanticMeaning.All : SemanticMeaning.Value; + + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.CatchClause: + case SyntaxKind.JsxAttribute: + return SemanticMeaning.Value; - case SyntaxKind.JSDocTypedefTag: - // If it has no name node, it shares the name with the value declaration below it. - return (node as JSDocTypedefTag).name === undefined ? SemanticMeaning.Value | SemanticMeaning.Type : SemanticMeaning.Type; + case SyntaxKind.TypeParameter: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.TypeLiteral: + return SemanticMeaning.Type; - case SyntaxKind.EnumMember: - case SyntaxKind.ClassDeclaration: - return SemanticMeaning.Value | SemanticMeaning.Type; + case SyntaxKind.JSDocTypedefTag: + // If it has no name node, it shares the name with the value declaration below it. + return (node as JSDocTypedefTag).name === undefined ? SemanticMeaning.Value | SemanticMeaning.Type : SemanticMeaning.Type; - case SyntaxKind.ModuleDeclaration: - if (isAmbientModule(node as ModuleDeclaration)) { - return SemanticMeaning.Namespace | SemanticMeaning.Value; - } - else if (getModuleInstanceState(node as ModuleDeclaration) === ModuleInstanceState.Instantiated) { - return SemanticMeaning.Namespace | SemanticMeaning.Value; - } - else { - return SemanticMeaning.Namespace; - } + case SyntaxKind.EnumMember: + case SyntaxKind.ClassDeclaration: + return SemanticMeaning.Value | SemanticMeaning.Type; - case SyntaxKind.EnumDeclaration: - case SyntaxKind.NamedImports: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.ExportAssignment: - case SyntaxKind.ExportDeclaration: - return SemanticMeaning.All; - - // An external module can be a Value - case SyntaxKind.SourceFile: + case SyntaxKind.ModuleDeclaration: + if (isAmbientModule(node as ModuleDeclaration)) { return SemanticMeaning.Namespace | SemanticMeaning.Value; - } - - return SemanticMeaning.All; - } + } + else if (getModuleInstanceState(node as ModuleDeclaration) === ModuleInstanceState.Instantiated) { + return SemanticMeaning.Namespace | SemanticMeaning.Value; + } + else { + return SemanticMeaning.Namespace; + } - export function getMeaningFromLocation(node: Node): SemanticMeaning { - node = getAdjustedReferenceLocation(node); - const parent = node.parent; - if (node.kind === SyntaxKind.SourceFile) { - return SemanticMeaning.Value; - } - else if (isExportAssignment(parent) - || isExportSpecifier(parent) - || isExternalModuleReference(parent) - || isImportSpecifier(parent) - || isImportClause(parent) - || isImportEqualsDeclaration(parent) && node === parent.name) { - return SemanticMeaning.All; - } - else if (isInRightSideOfInternalImportEqualsDeclaration(node)) { - return getMeaningFromRightHandSideOfImportEquals(node as Identifier); - } - else if (isDeclarationName(node)) { - return getMeaningFromDeclaration(parent); - } - else if (isEntityName(node) && findAncestor(node, or(isJSDocNameReference, isJSDocLinkLike, isJSDocMemberName))) { + case SyntaxKind.EnumDeclaration: + case SyntaxKind.NamedImports: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportAssignment: + case SyntaxKind.ExportDeclaration: return SemanticMeaning.All; - } - else if (isTypeReference(node)) { - return SemanticMeaning.Type; - } - else if (isNamespaceReference(node)) { - return SemanticMeaning.Namespace; - } - else if (isTypeParameterDeclaration(parent)) { - Debug.assert(isJSDocTemplateTag(parent.parent)); // Else would be handled by isDeclarationName - return SemanticMeaning.Type; - } - else if (isLiteralTypeNode(parent)) { - // This might be T["name"], which is actually referencing a property and not a type. So allow both meanings. - return SemanticMeaning.Type | SemanticMeaning.Value; - } - else { - return SemanticMeaning.Value; - } - } - function getMeaningFromRightHandSideOfImportEquals(node: Node): SemanticMeaning { - // import a = |b|; // Namespace - // import a = |b.c|; // Value, type, namespace - // import a = |b.c|.d; // Namespace - const name = node.kind === SyntaxKind.QualifiedName ? node : isQualifiedName(node.parent) && node.parent.right === node ? node.parent : undefined; - return name && name.parent.kind === SyntaxKind.ImportEqualsDeclaration ? SemanticMeaning.All : SemanticMeaning.Namespace; + // An external module can be a Value + case SyntaxKind.SourceFile: + return SemanticMeaning.Namespace | SemanticMeaning.Value; } - export function isInRightSideOfInternalImportEqualsDeclaration(node: Node) { - while (node.parent.kind === SyntaxKind.QualifiedName) { - node = node.parent; - } - return isInternalModuleImportEqualsDeclaration(node.parent) && node.parent.moduleReference === node; - } + return SemanticMeaning.All; +} - function isNamespaceReference(node: Node): boolean { - return isQualifiedNameNamespaceReference(node) || isPropertyAccessNamespaceReference(node); +/** @internal */ +export function getMeaningFromLocation(node: Node): SemanticMeaning { + node = getAdjustedReferenceLocation(node); + const parent = node.parent; + if (node.kind === SyntaxKind.SourceFile) { + return SemanticMeaning.Value; + } + else if (isExportAssignment(parent) + || isExportSpecifier(parent) + || isExternalModuleReference(parent) + || isImportSpecifier(parent) + || isImportClause(parent) + || isImportEqualsDeclaration(parent) && node === parent.name) { + return SemanticMeaning.All; } + else if (isInRightSideOfInternalImportEqualsDeclaration(node)) { + return getMeaningFromRightHandSideOfImportEquals(node as Identifier); + } + else if (isDeclarationName(node)) { + return getMeaningFromDeclaration(parent); + } + else if (isEntityName(node) && findAncestor(node, or(isJSDocNameReference, isJSDocLinkLike, isJSDocMemberName))) { + return SemanticMeaning.All; + } + else if (isTypeReference(node)) { + return SemanticMeaning.Type; + } + else if (isNamespaceReference(node)) { + return SemanticMeaning.Namespace; + } + else if (isTypeParameterDeclaration(parent)) { + Debug.assert(isJSDocTemplateTag(parent.parent)); // Else would be handled by isDeclarationName + return SemanticMeaning.Type; + } + else if (isLiteralTypeNode(parent)) { + // This might be T["name"], which is actually referencing a property and not a type. So allow both meanings. + return SemanticMeaning.Type | SemanticMeaning.Value; + } + else { + return SemanticMeaning.Value; + } +} - function isQualifiedNameNamespaceReference(node: Node): boolean { - let root = node; - let isLastClause = true; - if (root.parent.kind === SyntaxKind.QualifiedName) { - while (root.parent && root.parent.kind === SyntaxKind.QualifiedName) { - root = root.parent; - } - - isLastClause = (root as QualifiedName).right === node; - } +function getMeaningFromRightHandSideOfImportEquals(node: Node): SemanticMeaning { + // import a = |b|; // Namespace + // import a = |b.c|; // Value, type, namespace + // import a = |b.c|.d; // Namespace + const name = node.kind === SyntaxKind.QualifiedName ? node : isQualifiedName(node.parent) && node.parent.right === node ? node.parent : undefined; + return name && name.parent.kind === SyntaxKind.ImportEqualsDeclaration ? SemanticMeaning.All : SemanticMeaning.Namespace; +} - return root.parent.kind === SyntaxKind.TypeReference && !isLastClause; +/** @internal */ +export function isInRightSideOfInternalImportEqualsDeclaration(node: Node) { + while (node.parent.kind === SyntaxKind.QualifiedName) { + node = node.parent; } + return isInternalModuleImportEqualsDeclaration(node.parent) && node.parent.moduleReference === node; +} - function isPropertyAccessNamespaceReference(node: Node): boolean { - let root = node; - let isLastClause = true; - if (root.parent.kind === SyntaxKind.PropertyAccessExpression) { - while (root.parent && root.parent.kind === SyntaxKind.PropertyAccessExpression) { - root = root.parent; - } - - isLastClause = (root as PropertyAccessExpression).name === node; - } +function isNamespaceReference(node: Node): boolean { + return isQualifiedNameNamespaceReference(node) || isPropertyAccessNamespaceReference(node); +} - if (!isLastClause && root.parent.kind === SyntaxKind.ExpressionWithTypeArguments && root.parent.parent.kind === SyntaxKind.HeritageClause) { - const decl = root.parent.parent.parent; - return (decl.kind === SyntaxKind.ClassDeclaration && (root.parent.parent as HeritageClause).token === SyntaxKind.ImplementsKeyword) || - (decl.kind === SyntaxKind.InterfaceDeclaration && (root.parent.parent as HeritageClause).token === SyntaxKind.ExtendsKeyword); +function isQualifiedNameNamespaceReference(node: Node): boolean { + let root = node; + let isLastClause = true; + if (root.parent.kind === SyntaxKind.QualifiedName) { + while (root.parent && root.parent.kind === SyntaxKind.QualifiedName) { + root = root.parent; } - return false; + isLastClause = (root as QualifiedName).right === node; } - function isTypeReference(node: Node): boolean { - if (isRightSideOfQualifiedNameOrPropertyAccess(node)) { - node = node.parent; - } - - switch (node.kind) { - case SyntaxKind.ThisKeyword: - return !isExpressionNode(node); - case SyntaxKind.ThisType: - return true; - } + return root.parent.kind === SyntaxKind.TypeReference && !isLastClause; +} - switch (node.parent.kind) { - case SyntaxKind.TypeReference: - return true; - case SyntaxKind.ImportType: - return !(node.parent as ImportTypeNode).isTypeOf; - case SyntaxKind.ExpressionWithTypeArguments: - return isPartOfTypeNode(node.parent); +function isPropertyAccessNamespaceReference(node: Node): boolean { + let root = node; + let isLastClause = true; + if (root.parent.kind === SyntaxKind.PropertyAccessExpression) { + while (root.parent && root.parent.kind === SyntaxKind.PropertyAccessExpression) { + root = root.parent; } - return false; + isLastClause = (root as PropertyAccessExpression).name === node; } - export function isCallExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isCallExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); + if (!isLastClause && root.parent.kind === SyntaxKind.ExpressionWithTypeArguments && root.parent.parent.kind === SyntaxKind.HeritageClause) { + const decl = root.parent.parent.parent; + return (decl.kind === SyntaxKind.ClassDeclaration && (root.parent.parent as HeritageClause).token === SyntaxKind.ImplementsKeyword) || + (decl.kind === SyntaxKind.InterfaceDeclaration && (root.parent.parent as HeritageClause).token === SyntaxKind.ExtendsKeyword); } - export function isNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); - } + return false; +} - export function isCallOrNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isCallOrNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); +function isTypeReference(node: Node): boolean { + if (isRightSideOfQualifiedNameOrPropertyAccess(node)) { + node = node.parent; } - export function isTaggedTemplateTag(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isTaggedTemplateExpression, selectTagOfTaggedTemplateExpression, includeElementAccess, skipPastOuterExpressions); + switch (node.kind) { + case SyntaxKind.ThisKeyword: + return !isExpressionNode(node); + case SyntaxKind.ThisType: + return true; } - export function isDecoratorTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isDecorator, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); + switch (node.parent.kind) { + case SyntaxKind.TypeReference: + return true; + case SyntaxKind.ImportType: + return !(node.parent as ImportTypeNode).isTypeOf; + case SyntaxKind.ExpressionWithTypeArguments: + return isPartOfTypeNode(node.parent); } - export function isJsxOpeningLikeElementTagName(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { - return isCalleeWorker(node, isJsxOpeningLikeElement, selectTagNameOfJsxOpeningLikeElement, includeElementAccess, skipPastOuterExpressions); - } + return false; +} - function selectExpressionOfCallOrNewExpressionOrDecorator(node: CallExpression | NewExpression | Decorator) { - return node.expression; - } +/** @internal */ +export function isCallExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isCallExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); +} - function selectTagOfTaggedTemplateExpression(node: TaggedTemplateExpression) { - return node.tag; - } +/** @internal */ +export function isNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); +} - function selectTagNameOfJsxOpeningLikeElement(node: JsxOpeningLikeElement) { - return node.tagName; +/** @internal */ +export function isCallOrNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isCallOrNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); +} + +/** @internal */ +export function isTaggedTemplateTag(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isTaggedTemplateExpression, selectTagOfTaggedTemplateExpression, includeElementAccess, skipPastOuterExpressions); +} + +/** @internal */ +export function isDecoratorTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isDecorator, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions); +} + +/** @internal */ +export function isJsxOpeningLikeElementTagName(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean { + return isCalleeWorker(node, isJsxOpeningLikeElement, selectTagNameOfJsxOpeningLikeElement, includeElementAccess, skipPastOuterExpressions); +} + +function selectExpressionOfCallOrNewExpressionOrDecorator(node: CallExpression | NewExpression | Decorator) { + return node.expression; +} + +function selectTagOfTaggedTemplateExpression(node: TaggedTemplateExpression) { + return node.tag; +} + +function selectTagNameOfJsxOpeningLikeElement(node: JsxOpeningLikeElement) { + return node.tagName; +} + +function isCalleeWorker(node: Node, pred: (node: Node) => node is T, calleeSelector: (node: T) => Expression, includeElementAccess: boolean, skipPastOuterExpressions: boolean) { + let target = includeElementAccess ? climbPastPropertyOrElementAccess(node) : climbPastPropertyAccess(node); + if (skipPastOuterExpressions) { + target = skipOuterExpressions(target); } + return !!target && !!target.parent && pred(target.parent) && calleeSelector(target.parent) === target; +} + +/** @internal */ +export function climbPastPropertyAccess(node: Node) { + return isRightSideOfPropertyAccess(node) ? node.parent : node; +} - function isCalleeWorker(node: Node, pred: (node: Node) => node is T, calleeSelector: (node: T) => Expression, includeElementAccess: boolean, skipPastOuterExpressions: boolean) { - let target = includeElementAccess ? climbPastPropertyOrElementAccess(node) : climbPastPropertyAccess(node); - if (skipPastOuterExpressions) { - target = skipOuterExpressions(target); +/** @internal */ +export function climbPastPropertyOrElementAccess(node: Node) { + return isRightSideOfPropertyAccess(node) || isArgumentExpressionOfElementAccess(node) ? node.parent : node; +} + +/** @internal */ +export function getTargetLabel(referenceNode: Node, labelName: string): Identifier | undefined { + while (referenceNode) { + if (referenceNode.kind === SyntaxKind.LabeledStatement && (referenceNode as LabeledStatement).label.escapedText === labelName) { + return (referenceNode as LabeledStatement).label; } - return !!target && !!target.parent && pred(target.parent) && calleeSelector(target.parent) === target; + referenceNode = referenceNode.parent; } + return undefined; +} - export function climbPastPropertyAccess(node: Node) { - return isRightSideOfPropertyAccess(node) ? node.parent : node; +/** @internal */ +export function hasPropertyAccessExpressionWithName(node: CallExpression, funcName: string): boolean { + if (!isPropertyAccessExpression(node.expression)) { + return false; } - export function climbPastPropertyOrElementAccess(node: Node) { - return isRightSideOfPropertyAccess(node) || isArgumentExpressionOfElementAccess(node) ? node.parent : node; - } + return node.expression.name.text === funcName; +} - export function getTargetLabel(referenceNode: Node, labelName: string): Identifier | undefined { - while (referenceNode) { - if (referenceNode.kind === SyntaxKind.LabeledStatement && (referenceNode as LabeledStatement).label.escapedText === labelName) { - return (referenceNode as LabeledStatement).label; - } - referenceNode = referenceNode.parent; - } - return undefined; - } +/** @internal */ +export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } { + return isIdentifier(node) && tryCast(node.parent, isBreakOrContinueStatement)?.label === node; +} - export function hasPropertyAccessExpressionWithName(node: CallExpression, funcName: string): boolean { - if (!isPropertyAccessExpression(node.expression)) { - return false; - } +/** @internal */ +export function isLabelOfLabeledStatement(node: Node): node is Identifier { + return isIdentifier(node) && tryCast(node.parent, isLabeledStatement)?.label === node; +} - return node.expression.name.text === funcName; - } +/** @internal */ +export function isLabelName(node: Node): boolean { + return isLabelOfLabeledStatement(node) || isJumpStatementTarget(node); +} - export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } { - return isIdentifier(node) && tryCast(node.parent, isBreakOrContinueStatement)?.label === node; - } +/** @internal */ +export function isTagName(node: Node): boolean { + return tryCast(node.parent, isJSDocTag)?.tagName === node; +} - export function isLabelOfLabeledStatement(node: Node): node is Identifier { - return isIdentifier(node) && tryCast(node.parent, isLabeledStatement)?.label === node; - } +/** @internal */ +export function isRightSideOfQualifiedName(node: Node) { + return tryCast(node.parent, isQualifiedName)?.right === node; +} - export function isLabelName(node: Node): boolean { - return isLabelOfLabeledStatement(node) || isJumpStatementTarget(node); - } +/** @internal */ +export function isRightSideOfPropertyAccess(node: Node) { + return tryCast(node.parent, isPropertyAccessExpression)?.name === node; +} - export function isTagName(node: Node): boolean { - return tryCast(node.parent, isJSDocTag)?.tagName === node; - } +/** @internal */ +export function isArgumentExpressionOfElementAccess(node: Node) { + return tryCast(node.parent, isElementAccessExpression)?.argumentExpression === node; +} - export function isRightSideOfQualifiedName(node: Node) { - return tryCast(node.parent, isQualifiedName)?.right === node; - } +/** @internal */ +export function isNameOfModuleDeclaration(node: Node) { + return tryCast(node.parent, isModuleDeclaration)?.name === node; +} - export function isRightSideOfPropertyAccess(node: Node) { - return tryCast(node.parent, isPropertyAccessExpression)?.name === node; - } +/** @internal */ +export function isNameOfFunctionDeclaration(node: Node): boolean { + return isIdentifier(node) && tryCast(node.parent, isFunctionLike)?.name === node; +} - export function isArgumentExpressionOfElementAccess(node: Node) { - return tryCast(node.parent, isElementAccessExpression)?.argumentExpression === node; +/** @internal */ +export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: StringLiteral | NumericLiteral | NoSubstitutionTemplateLiteral): boolean { + switch (node.parent.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.EnumMember: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.ModuleDeclaration: + return getNameOfDeclaration(node.parent as Declaration) === node; + case SyntaxKind.ElementAccessExpression: + return (node.parent as ElementAccessExpression).argumentExpression === node; + case SyntaxKind.ComputedPropertyName: + return true; + case SyntaxKind.LiteralType: + return node.parent.parent.kind === SyntaxKind.IndexedAccessType; + default: + return false; } +} - export function isNameOfModuleDeclaration(node: Node) { - return tryCast(node.parent, isModuleDeclaration)?.name === node; - } +/** @internal */ +export function isExpressionOfExternalModuleImportEqualsDeclaration(node: Node) { + return isExternalModuleImportEqualsDeclaration(node.parent.parent) && + getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node; +} - export function isNameOfFunctionDeclaration(node: Node): boolean { - return isIdentifier(node) && tryCast(node.parent, isFunctionLike)?.name === node; +/** @internal */ +export function getContainerNode(node: Node): Declaration | undefined { + if (isJSDocTypeAlias(node)) { + // This doesn't just apply to the node immediately under the comment, but to everything in its parent's scope. + // node.parent = the JSDoc comment, node.parent.parent = the node having the comment. + // Then we get parent again in the loop. + node = node.parent.parent; } - export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: StringLiteral | NumericLiteral | NoSubstitutionTemplateLiteral): boolean { - switch (node.parent.kind) { - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.EnumMember: + while (true) { + node = node.parent; + if (!node) { + return undefined; + } + switch (node.kind) { + case SyntaxKind.SourceFile: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: case SyntaxKind.ModuleDeclaration: - return getNameOfDeclaration(node.parent as Declaration) === node; - case SyntaxKind.ElementAccessExpression: - return (node.parent as ElementAccessExpression).argumentExpression === node; - case SyntaxKind.ComputedPropertyName: - return true; - case SyntaxKind.LiteralType: - return node.parent.parent.kind === SyntaxKind.IndexedAccessType; - default: - return false; + return node as Declaration; } } +} - export function isExpressionOfExternalModuleImportEqualsDeclaration(node: Node) { - return isExternalModuleImportEqualsDeclaration(node.parent.parent) && - getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node; +/** @internal */ +export function getNodeKind(node: Node): ScriptElementKind { + switch (node.kind) { + case SyntaxKind.SourceFile: + return isExternalModule(node as SourceFile) ? ScriptElementKind.moduleElement : ScriptElementKind.scriptElement; + case SyntaxKind.ModuleDeclaration: + return ScriptElementKind.moduleElement; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return ScriptElementKind.classElement; + case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement; + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocTypedefTag: + return ScriptElementKind.typeElement; + case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement; + case SyntaxKind.VariableDeclaration: + return getKindOfVariableDeclaration(node as VariableDeclaration); + case SyntaxKind.BindingElement: + return getKindOfVariableDeclaration(getRootDeclaration(node) as VariableDeclaration); + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + return ScriptElementKind.functionElement; + case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement; + case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return ScriptElementKind.memberFunctionElement; + case SyntaxKind.PropertyAssignment: + const { initializer } = node as PropertyAssignment; + return isFunctionLike(initializer) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.SpreadAssignment: + return ScriptElementKind.memberVariableElement; + case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement; + case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement; + case SyntaxKind.CallSignature: return ScriptElementKind.callSignatureElement; + case SyntaxKind.Constructor: + case SyntaxKind.ClassStaticBlockDeclaration: + return ScriptElementKind.constructorImplementationElement; + case SyntaxKind.TypeParameter: return ScriptElementKind.typeParameterElement; + case SyntaxKind.EnumMember: return ScriptElementKind.enumMemberElement; + case SyntaxKind.Parameter: return hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier) ? ScriptElementKind.memberVariableElement : ScriptElementKind.parameterElement; + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.NamespaceImport: + case SyntaxKind.NamespaceExport: + return ScriptElementKind.alias; + case SyntaxKind.BinaryExpression: + const kind = getAssignmentDeclarationKind(node as BinaryExpression); + const { right } = node as BinaryExpression; + switch (kind) { + case AssignmentDeclarationKind.ObjectDefinePropertyValue: + case AssignmentDeclarationKind.ObjectDefinePropertyExports: + case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: + case AssignmentDeclarationKind.None: + return ScriptElementKind.unknown; + case AssignmentDeclarationKind.ExportsProperty: + case AssignmentDeclarationKind.ModuleExports: + const rightKind = getNodeKind(right); + return rightKind === ScriptElementKind.unknown ? ScriptElementKind.constElement : rightKind; + case AssignmentDeclarationKind.PrototypeProperty: + return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; + case AssignmentDeclarationKind.ThisProperty: + return ScriptElementKind.memberVariableElement; // property + case AssignmentDeclarationKind.Property: + // static method / property + return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; + case AssignmentDeclarationKind.Prototype: + return ScriptElementKind.localClassElement; + default: { + assertType(kind); + return ScriptElementKind.unknown; + } + } + case SyntaxKind.Identifier: + return isImportClause(node.parent) ? ScriptElementKind.alias : ScriptElementKind.unknown; + case SyntaxKind.ExportAssignment: + const scriptKind = getNodeKind((node as ExportAssignment).expression); + // If the expression didn't come back with something (like it does for an identifiers) + return scriptKind === ScriptElementKind.unknown ? ScriptElementKind.constElement : scriptKind; + default: + return ScriptElementKind.unknown; } - export function getContainerNode(node: Node): Declaration | undefined { - if (isJSDocTypeAlias(node)) { - // This doesn't just apply to the node immediately under the comment, but to everything in its parent's scope. - // node.parent = the JSDoc comment, node.parent.parent = the node having the comment. - // Then we get parent again in the loop. - node = node.parent.parent; - } + function getKindOfVariableDeclaration(v: VariableDeclaration): ScriptElementKind { + return isVarConst(v) + ? ScriptElementKind.constElement + : isLet(v) + ? ScriptElementKind.letElement + : ScriptElementKind.variableElement; + } +} - while (true) { - node = node.parent; - if (!node) { - return undefined; - } - switch (node.kind) { - case SyntaxKind.SourceFile: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ModuleDeclaration: - return node as Declaration; - } - } +/** @internal */ +export function isThis(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ThisKeyword: + // case SyntaxKind.ThisType: TODO: GH#9267 + return true; + case SyntaxKind.Identifier: + // 'this' as a parameter + return identifierIsThisKeyword(node as Identifier) && node.parent.kind === SyntaxKind.Parameter; + default: + return false; } +} - export function getNodeKind(node: Node): ScriptElementKind { - switch (node.kind) { - case SyntaxKind.SourceFile: - return isExternalModule(node as SourceFile) ? ScriptElementKind.moduleElement : ScriptElementKind.scriptElement; - case SyntaxKind.ModuleDeclaration: - return ScriptElementKind.moduleElement; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return ScriptElementKind.classElement; - case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement; - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocTypedefTag: - return ScriptElementKind.typeElement; - case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement; - case SyntaxKind.VariableDeclaration: - return getKindOfVariableDeclaration(node as VariableDeclaration); - case SyntaxKind.BindingElement: - return getKindOfVariableDeclaration(getRootDeclaration(node) as VariableDeclaration); - case SyntaxKind.ArrowFunction: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - return ScriptElementKind.functionElement; - case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement; - case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return ScriptElementKind.memberFunctionElement; - case SyntaxKind.PropertyAssignment: - const { initializer } = node as PropertyAssignment; - return isFunctionLike(initializer) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.SpreadAssignment: - return ScriptElementKind.memberVariableElement; - case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement; - case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement; - case SyntaxKind.CallSignature: return ScriptElementKind.callSignatureElement; - case SyntaxKind.Constructor: - case SyntaxKind.ClassStaticBlockDeclaration: - return ScriptElementKind.constructorImplementationElement; - case SyntaxKind.TypeParameter: return ScriptElementKind.typeParameterElement; - case SyntaxKind.EnumMember: return ScriptElementKind.enumMemberElement; - case SyntaxKind.Parameter: return hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier) ? ScriptElementKind.memberVariableElement : ScriptElementKind.parameterElement; - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.NamespaceImport: - case SyntaxKind.NamespaceExport: - return ScriptElementKind.alias; - case SyntaxKind.BinaryExpression: - const kind = getAssignmentDeclarationKind(node as BinaryExpression); - const { right } = node as BinaryExpression; - switch (kind) { - case AssignmentDeclarationKind.ObjectDefinePropertyValue: - case AssignmentDeclarationKind.ObjectDefinePropertyExports: - case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: - case AssignmentDeclarationKind.None: - return ScriptElementKind.unknown; - case AssignmentDeclarationKind.ExportsProperty: - case AssignmentDeclarationKind.ModuleExports: - const rightKind = getNodeKind(right); - return rightKind === ScriptElementKind.unknown ? ScriptElementKind.constElement : rightKind; - case AssignmentDeclarationKind.PrototypeProperty: - return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; - case AssignmentDeclarationKind.ThisProperty: - return ScriptElementKind.memberVariableElement; // property - case AssignmentDeclarationKind.Property: - // static method / property - return isFunctionExpression(right) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; - case AssignmentDeclarationKind.Prototype: - return ScriptElementKind.localClassElement; - default: { - assertType(kind); - return ScriptElementKind.unknown; - } - } - case SyntaxKind.Identifier: - return isImportClause(node.parent) ? ScriptElementKind.alias : ScriptElementKind.unknown; - case SyntaxKind.ExportAssignment: - const scriptKind = getNodeKind((node as ExportAssignment).expression); - // If the expression didn't come back with something (like it does for an identifiers) - return scriptKind === ScriptElementKind.unknown ? ScriptElementKind.constElement : scriptKind; - default: - return ScriptElementKind.unknown; - } +// Matches the beginning of a triple slash directive +const tripleSlashDirectivePrefixRegex = /^\/\/\/\s*= range.end; +} - export function rangeContainsPosition(r: TextRange, pos: number): boolean { - return r.pos <= pos && pos <= r.end; - } +/** @internal */ +export function rangeContainsStartEnd(range: TextRange, start: number, end: number): boolean { + return range.pos <= start && range.end >= end; +} - export function rangeContainsPositionExclusive(r: TextRange, pos: number) { - return r.pos < pos && pos < r.end; - } +/** @internal */ +export function rangeOverlapsWithStartEnd(r1: TextRange, start: number, end: number) { + return startEndOverlapsWithStartEnd(r1.pos, r1.end, start, end); +} - export function startEndContainsRange(start: number, end: number, range: TextRange): boolean { - return start <= range.pos && end >= range.end; - } +/** @internal */ +export function nodeOverlapsWithStartEnd(node: Node, sourceFile: SourceFile, start: number, end: number) { + return startEndOverlapsWithStartEnd(node.getStart(sourceFile), node.end, start, end); +} - export function rangeContainsStartEnd(range: TextRange, start: number, end: number): boolean { - return range.pos <= start && range.end >= end; - } +/** @internal */ +export function startEndOverlapsWithStartEnd(start1: number, end1: number, start2: number, end2: number) { + const start = Math.max(start1, start2); + const end = Math.min(end1, end2); + return start < end; +} - export function rangeOverlapsWithStartEnd(r1: TextRange, start: number, end: number) { - return startEndOverlapsWithStartEnd(r1.pos, r1.end, start, end); - } +/** + * Assumes `candidate.start <= position` holds. + * + * @internal + */ +export function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean { + Debug.assert(candidate.pos <= position); + return position < candidate.end || !isCompletedNode(candidate, sourceFile); +} - export function nodeOverlapsWithStartEnd(node: Node, sourceFile: SourceFile, start: number, end: number) { - return startEndOverlapsWithStartEnd(node.getStart(sourceFile), node.end, start, end); +function isCompletedNode(n: Node | undefined, sourceFile: SourceFile): boolean { + if (n === undefined || nodeIsMissing(n)) { + return false; } - export function startEndOverlapsWithStartEnd(start1: number, end1: number, start2: number, end2: number) { - const start = Math.max(start1, start2); - const end = Math.min(end1, end2); - return start < end; - } + switch (n.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.TypeLiteral: + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + case SyntaxKind.CaseBlock: + case SyntaxKind.NamedImports: + case SyntaxKind.NamedExports: + return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile); + case SyntaxKind.CatchClause: + return isCompletedNode((n as CatchClause).block, sourceFile); + case SyntaxKind.NewExpression: + if (!(n as NewExpression).arguments) { + return true; + } + // falls through - /** - * Assumes `candidate.start <= position` holds. - */ - export function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean { - Debug.assert(candidate.pos <= position); - return position < candidate.end || !isCompletedNode(candidate, sourceFile); - } + case SyntaxKind.CallExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.ParenthesizedType: + return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); - function isCompletedNode(n: Node | undefined, sourceFile: SourceFile): boolean { - if (n === undefined || nodeIsMissing(n)) { - return false; - } + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + return isCompletedNode((n as SignatureDeclaration).type, sourceFile); - switch (n.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.TypeLiteral: - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - case SyntaxKind.CaseBlock: - case SyntaxKind.NamedImports: - case SyntaxKind.NamedExports: - return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile); - case SyntaxKind.CatchClause: - return isCompletedNode((n as CatchClause).block, sourceFile); - case SyntaxKind.NewExpression: - if (!(n as NewExpression).arguments) { - return true; - } - // falls through + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ArrowFunction: + if ((n as FunctionLikeDeclaration).body) { + return isCompletedNode((n as FunctionLikeDeclaration).body, sourceFile); + } - case SyntaxKind.CallExpression: - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.ParenthesizedType: - return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); + if ((n as FunctionLikeDeclaration).type) { + return isCompletedNode((n as FunctionLikeDeclaration).type, sourceFile); + } - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - return isCompletedNode((n as SignatureDeclaration).type, sourceFile); + // Even though type parameters can be unclosed, we can get away with + // having at least a closing paren. + return hasChildOfKind(n, SyntaxKind.CloseParenToken, sourceFile); - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ArrowFunction: - if ((n as FunctionLikeDeclaration).body) { - return isCompletedNode((n as FunctionLikeDeclaration).body, sourceFile); - } + case SyntaxKind.ModuleDeclaration: + return !!(n as ModuleDeclaration).body && isCompletedNode((n as ModuleDeclaration).body, sourceFile); - if ((n as FunctionLikeDeclaration).type) { - return isCompletedNode((n as FunctionLikeDeclaration).type, sourceFile); - } + case SyntaxKind.IfStatement: + if ((n as IfStatement).elseStatement) { + return isCompletedNode((n as IfStatement).elseStatement, sourceFile); + } + return isCompletedNode((n as IfStatement).thenStatement, sourceFile); - // Even though type parameters can be unclosed, we can get away with - // having at least a closing paren. - return hasChildOfKind(n, SyntaxKind.CloseParenToken, sourceFile); + case SyntaxKind.ExpressionStatement: + return isCompletedNode((n as ExpressionStatement).expression, sourceFile) || + hasChildOfKind(n, SyntaxKind.SemicolonToken, sourceFile); - case SyntaxKind.ModuleDeclaration: - return !!(n as ModuleDeclaration).body && isCompletedNode((n as ModuleDeclaration).body, sourceFile); + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.ComputedPropertyName: + case SyntaxKind.TupleType: + return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile); - case SyntaxKind.IfStatement: - if ((n as IfStatement).elseStatement) { - return isCompletedNode((n as IfStatement).elseStatement, sourceFile); - } - return isCompletedNode((n as IfStatement).thenStatement, sourceFile); - - case SyntaxKind.ExpressionStatement: - return isCompletedNode((n as ExpressionStatement).expression, sourceFile) || - hasChildOfKind(n, SyntaxKind.SemicolonToken, sourceFile); - - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.ElementAccessExpression: - case SyntaxKind.ComputedPropertyName: - case SyntaxKind.TupleType: - return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile); - - case SyntaxKind.IndexSignature: - if ((n as IndexSignatureDeclaration).type) { - return isCompletedNode((n as IndexSignatureDeclaration).type, sourceFile); - } + case SyntaxKind.IndexSignature: + if ((n as IndexSignatureDeclaration).type) { + return isCompletedNode((n as IndexSignatureDeclaration).type, sourceFile); + } - return hasChildOfKind(n, SyntaxKind.CloseBracketToken, sourceFile); + return hasChildOfKind(n, SyntaxKind.CloseBracketToken, sourceFile); - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - // there is no such thing as terminator token for CaseClause/DefaultClause so for simplicity always consider them non-completed - return false; + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + // there is no such thing as terminator token for CaseClause/DefaultClause so for simplicity always consider them non-completed + return false; - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.WhileStatement: - return isCompletedNode((n as IterationStatement).statement, sourceFile); - case SyntaxKind.DoStatement: - // rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')'; - return hasChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile) - ? nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile) - : isCompletedNode((n as DoStatement).statement, sourceFile); - - case SyntaxKind.TypeQuery: - return isCompletedNode((n as TypeQueryNode).exprName, sourceFile); - - case SyntaxKind.TypeOfExpression: - case SyntaxKind.DeleteExpression: - case SyntaxKind.VoidExpression: - case SyntaxKind.YieldExpression: - case SyntaxKind.SpreadElement: - const unaryWordExpression = n as (TypeOfExpression | DeleteExpression | VoidExpression | YieldExpression | SpreadElement); - return isCompletedNode(unaryWordExpression.expression, sourceFile); - - case SyntaxKind.TaggedTemplateExpression: - return isCompletedNode((n as TaggedTemplateExpression).template, sourceFile); - case SyntaxKind.TemplateExpression: - const lastSpan = lastOrUndefined((n as TemplateExpression).templateSpans); - return isCompletedNode(lastSpan, sourceFile); - case SyntaxKind.TemplateSpan: - return nodeIsPresent((n as TemplateSpan).literal); - - case SyntaxKind.ExportDeclaration: - case SyntaxKind.ImportDeclaration: - return nodeIsPresent((n as ExportDeclaration | ImportDeclaration).moduleSpecifier); - - case SyntaxKind.PrefixUnaryExpression: - return isCompletedNode((n as PrefixUnaryExpression).operand, sourceFile); - case SyntaxKind.BinaryExpression: - return isCompletedNode((n as BinaryExpression).right, sourceFile); - case SyntaxKind.ConditionalExpression: - return isCompletedNode((n as ConditionalExpression).whenFalse, sourceFile); + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WhileStatement: + return isCompletedNode((n as IterationStatement).statement, sourceFile); + case SyntaxKind.DoStatement: + // rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')'; + return hasChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile) + ? nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile) + : isCompletedNode((n as DoStatement).statement, sourceFile); + + case SyntaxKind.TypeQuery: + return isCompletedNode((n as TypeQueryNode).exprName, sourceFile); + + case SyntaxKind.TypeOfExpression: + case SyntaxKind.DeleteExpression: + case SyntaxKind.VoidExpression: + case SyntaxKind.YieldExpression: + case SyntaxKind.SpreadElement: + const unaryWordExpression = n as (TypeOfExpression | DeleteExpression | VoidExpression | YieldExpression | SpreadElement); + return isCompletedNode(unaryWordExpression.expression, sourceFile); + + case SyntaxKind.TaggedTemplateExpression: + return isCompletedNode((n as TaggedTemplateExpression).template, sourceFile); + case SyntaxKind.TemplateExpression: + const lastSpan = lastOrUndefined((n as TemplateExpression).templateSpans); + return isCompletedNode(lastSpan, sourceFile); + case SyntaxKind.TemplateSpan: + return nodeIsPresent((n as TemplateSpan).literal); + + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ImportDeclaration: + return nodeIsPresent((n as ExportDeclaration | ImportDeclaration).moduleSpecifier); + + case SyntaxKind.PrefixUnaryExpression: + return isCompletedNode((n as PrefixUnaryExpression).operand, sourceFile); + case SyntaxKind.BinaryExpression: + return isCompletedNode((n as BinaryExpression).right, sourceFile); + case SyntaxKind.ConditionalExpression: + return isCompletedNode((n as ConditionalExpression).whenFalse, sourceFile); + + default: + return true; + } +} - default: - return true; +/* + * Checks if node ends with 'expectedLastToken'. + * If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'. + */ +function nodeEndsWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean { + const children = n.getChildren(sourceFile); + if (children.length) { + const lastChild = last(children); + if (lastChild.kind === expectedLastToken) { + return true; + } + else if (lastChild.kind === SyntaxKind.SemicolonToken && children.length !== 1) { + return children[children.length - 2].kind === expectedLastToken; } } + return false; +} - /* - * Checks if node ends with 'expectedLastToken'. - * If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'. - */ - function nodeEndsWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean { - const children = n.getChildren(sourceFile); - if (children.length) { - const lastChild = last(children); - if (lastChild.kind === expectedLastToken) { - return true; - } - else if (lastChild.kind === SyntaxKind.SemicolonToken && children.length !== 1) { - return children[children.length - 2].kind === expectedLastToken; - } - } - return false; +/** @internal */ +export function findListItemInfo(node: Node): ListItemInfo | undefined { + const list = findContainingList(node); + + // It is possible at this point for syntaxList to be undefined, either if + // node.parent had no list child, or if none of its list children contained + // the span of node. If this happens, return undefined. The caller should + // handle this case. + if (!list) { + return undefined; } - export function findListItemInfo(node: Node): ListItemInfo | undefined { - const list = findContainingList(node); + const children = list.getChildren(); + const listItemIndex = indexOfNode(children, node); - // It is possible at this point for syntaxList to be undefined, either if - // node.parent had no list child, or if none of its list children contained - // the span of node. If this happens, return undefined. The caller should - // handle this case. - if (!list) { - return undefined; - } + return { + listItemIndex, + list + }; +} - const children = list.getChildren(); - const listItemIndex = indexOfNode(children, node); +/** @internal */ +export function hasChildOfKind(n: Node, kind: SyntaxKind, sourceFile: SourceFile): boolean { + return !!findChildOfKind(n, kind, sourceFile); +} - return { - listItemIndex, - list - }; - } +/** @internal */ +export function findChildOfKind(n: Node, kind: T["kind"], sourceFile: SourceFileLike): T | undefined { + return find(n.getChildren(sourceFile), (c): c is T => c.kind === kind); +} - export function hasChildOfKind(n: Node, kind: SyntaxKind, sourceFile: SourceFile): boolean { - return !!findChildOfKind(n, kind, sourceFile); - } +/** @internal */ +export function findContainingList(node: Node): SyntaxList | undefined { + // The node might be a list element (nonsynthetic) or a comma (synthetic). Either way, it will + // be parented by the container of the SyntaxList, not the SyntaxList itself. + // In order to find the list item index, we first need to locate SyntaxList itself and then search + // for the position of the relevant node (or comma). + const syntaxList = find(node.parent.getChildren(), (c): c is SyntaxList => isSyntaxList(c) && rangeContainsRange(c, node)); + // Either we didn't find an appropriate list, or the list must contain us. + Debug.assert(!syntaxList || contains(syntaxList.getChildren(), node)); + return syntaxList; +} - export function findChildOfKind(n: Node, kind: T["kind"], sourceFile: SourceFileLike): T | undefined { - return find(n.getChildren(sourceFile), (c): c is T => c.kind === kind); - } +function isDefaultModifier(node: Node) { + return node.kind === SyntaxKind.DefaultKeyword; +} - export function findContainingList(node: Node): SyntaxList | undefined { - // The node might be a list element (nonsynthetic) or a comma (synthetic). Either way, it will - // be parented by the container of the SyntaxList, not the SyntaxList itself. - // In order to find the list item index, we first need to locate SyntaxList itself and then search - // for the position of the relevant node (or comma). - const syntaxList = find(node.parent.getChildren(), (c): c is SyntaxList => isSyntaxList(c) && rangeContainsRange(c, node)); - // Either we didn't find an appropriate list, or the list must contain us. - Debug.assert(!syntaxList || contains(syntaxList.getChildren(), node)); - return syntaxList; - } +function isClassKeyword(node: Node) { + return node.kind === SyntaxKind.ClassKeyword; +} - function isDefaultModifier(node: Node) { - return node.kind === SyntaxKind.DefaultKeyword; - } +function isFunctionKeyword(node: Node) { + return node.kind === SyntaxKind.FunctionKeyword; +} - function isClassKeyword(node: Node) { - return node.kind === SyntaxKind.ClassKeyword; +function getAdjustedLocationForClass(node: ClassDeclaration | ClassExpression) { + if (isNamedDeclaration(node)) { + return node.name; } - - function isFunctionKeyword(node: Node) { - return node.kind === SyntaxKind.FunctionKeyword; + if (isClassDeclaration(node)) { + // for class and function declarations, use the `default` modifier + // when the declaration is unnamed. + const defaultModifier = node.modifiers && find(node.modifiers, isDefaultModifier); + if (defaultModifier) return defaultModifier; } - - function getAdjustedLocationForClass(node: ClassDeclaration | ClassExpression) { - if (isNamedDeclaration(node)) { - return node.name; - } - if (isClassDeclaration(node)) { - // for class and function declarations, use the `default` modifier - // when the declaration is unnamed. - const defaultModifier = node.modifiers && find(node.modifiers, isDefaultModifier); - if (defaultModifier) return defaultModifier; - } - if (isClassExpression(node)) { - // for class expressions, use the `class` keyword when the class is unnamed - const classKeyword = find(node.getChildren(), isClassKeyword); - if (classKeyword) return classKeyword; - } + if (isClassExpression(node)) { + // for class expressions, use the `class` keyword when the class is unnamed + const classKeyword = find(node.getChildren(), isClassKeyword); + if (classKeyword) return classKeyword; } +} - function getAdjustedLocationForFunction(node: FunctionDeclaration | FunctionExpression) { - if (isNamedDeclaration(node)) { - return node.name; - } - if (isFunctionDeclaration(node)) { - // for class and function declarations, use the `default` modifier - // when the declaration is unnamed. - const defaultModifier = find(node.modifiers, isDefaultModifier); - if (defaultModifier) return defaultModifier; - } - if (isFunctionExpression(node)) { - // for function expressions, use the `function` keyword when the function is unnamed - const functionKeyword = find(node.getChildren(), isFunctionKeyword); - if (functionKeyword) return functionKeyword; - } +function getAdjustedLocationForFunction(node: FunctionDeclaration | FunctionExpression) { + if (isNamedDeclaration(node)) { + return node.name; } - - function getAncestorTypeNode(node: Node) { - let lastTypeNode: TypeNode | undefined; - findAncestor(node, a => { - if (isTypeNode(a)) { - lastTypeNode = a; - } - return !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent); - }); - return lastTypeNode; + if (isFunctionDeclaration(node)) { + // for class and function declarations, use the `default` modifier + // when the declaration is unnamed. + const defaultModifier = find(node.modifiers, isDefaultModifier); + if (defaultModifier) return defaultModifier; + } + if (isFunctionExpression(node)) { + // for function expressions, use the `function` keyword when the function is unnamed + const functionKeyword = find(node.getChildren(), isFunctionKeyword); + if (functionKeyword) return functionKeyword; } +} - export function getContextualTypeFromParentOrAncestorTypeNode(node: Expression, checker: TypeChecker): Type | undefined { - if (node.flags & (NodeFlags.JSDoc & ~NodeFlags.JavaScriptFile)) return undefined; +function getAncestorTypeNode(node: Node) { + let lastTypeNode: TypeNode | undefined; + findAncestor(node, a => { + if (isTypeNode(a)) { + lastTypeNode = a; + } + return !isQualifiedName(a.parent) && !isTypeNode(a.parent) && !isTypeElement(a.parent); + }); + return lastTypeNode; +} - const contextualType = getContextualTypeFromParent(node, checker); - if (contextualType) return contextualType; +/** @internal */ +export function getContextualTypeFromParentOrAncestorTypeNode(node: Expression, checker: TypeChecker): Type | undefined { + if (node.flags & (NodeFlags.JSDoc & ~NodeFlags.JavaScriptFile)) return undefined; - const ancestorTypeNode = getAncestorTypeNode(node); - return ancestorTypeNode && checker.getTypeAtLocation(ancestorTypeNode); - } + const contextualType = getContextualTypeFromParent(node, checker); + if (contextualType) return contextualType; - function getAdjustedLocationForDeclaration(node: Node, forRename: boolean) { - if (!forRename) { - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return getAdjustedLocationForClass(node as ClassDeclaration | ClassExpression); - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - return getAdjustedLocationForFunction(node as FunctionDeclaration | FunctionExpression); - case SyntaxKind.Constructor: - return node; - } - } - if (isNamedDeclaration(node)) { - return node.name; + const ancestorTypeNode = getAncestorTypeNode(node); + return ancestorTypeNode && checker.getTypeAtLocation(ancestorTypeNode); +} + +function getAdjustedLocationForDeclaration(node: Node, forRename: boolean) { + if (!forRename) { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return getAdjustedLocationForClass(node as ClassDeclaration | ClassExpression); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + return getAdjustedLocationForFunction(node as FunctionDeclaration | FunctionExpression); + case SyntaxKind.Constructor: + return node; } } + if (isNamedDeclaration(node)) { + return node.name; + } +} - function getAdjustedLocationForImportDeclaration(node: ImportDeclaration, forRename: boolean) { - if (node.importClause) { - if (node.importClause.name && node.importClause.namedBindings) { - // do not adjust if we have both a name and named bindings - return; - } - - // /**/import [|name|] from ...; - // import /**/type [|name|] from ...; - if (node.importClause.name) { - return node.importClause.name; - } - - // /**/import { [|name|] } from ...; - // /**/import { propertyName as [|name|] } from ...; - // /**/import * as [|name|] from ...; - // import /**/type { [|name|] } from ...; - // import /**/type { propertyName as [|name|] } from ...; - // import /**/type * as [|name|] from ...; - if (node.importClause.namedBindings) { - if (isNamedImports(node.importClause.namedBindings)) { - // do nothing if there is more than one binding - const onlyBinding = singleOrUndefined(node.importClause.namedBindings.elements); - if (!onlyBinding) { - return; - } - return onlyBinding.name; - } - else if (isNamespaceImport(node.importClause.namedBindings)) { - return node.importClause.namedBindings.name; - } - } +function getAdjustedLocationForImportDeclaration(node: ImportDeclaration, forRename: boolean) { + if (node.importClause) { + if (node.importClause.name && node.importClause.namedBindings) { + // do not adjust if we have both a name and named bindings + return; } - if (!forRename) { - // /**/import "[|module|]"; - // /**/import ... from "[|module|]"; - // import /**/type ... from "[|module|]"; - return node.moduleSpecifier; + + // /**/import [|name|] from ...; + // import /**/type [|name|] from ...; + if (node.importClause.name) { + return node.importClause.name; } - } - function getAdjustedLocationForExportDeclaration(node: ExportDeclaration, forRename: boolean) { - if (node.exportClause) { - // /**/export { [|name|] } ... - // /**/export { propertyName as [|name|] } ... - // /**/export * as [|name|] ... - // export /**/type { [|name|] } from ... - // export /**/type { propertyName as [|name|] } from ... - // export /**/type * as [|name|] ... - if (isNamedExports(node.exportClause)) { + // /**/import { [|name|] } from ...; + // /**/import { propertyName as [|name|] } from ...; + // /**/import * as [|name|] from ...; + // import /**/type { [|name|] } from ...; + // import /**/type { propertyName as [|name|] } from ...; + // import /**/type * as [|name|] from ...; + if (node.importClause.namedBindings) { + if (isNamedImports(node.importClause.namedBindings)) { // do nothing if there is more than one binding - const onlyBinding = singleOrUndefined(node.exportClause.elements); + const onlyBinding = singleOrUndefined(node.importClause.namedBindings.elements); if (!onlyBinding) { return; } - return node.exportClause.elements[0].name; + return onlyBinding.name; } - else if (isNamespaceExport(node.exportClause)) { - return node.exportClause.name; + else if (isNamespaceImport(node.importClause.namedBindings)) { + return node.importClause.namedBindings.name; } } - if (!forRename) { - // /**/export * from "[|module|]"; - // export /**/type * from "[|module|]"; - return node.moduleSpecifier; - } - } - - function getAdjustedLocationForHeritageClause(node: HeritageClause) { - // /**/extends [|name|] - // /**/implements [|name|] - if (node.types.length === 1) { - return node.types[0].expression; - } - - // /**/extends name1, name2 ... - // /**/implements name1, name2 ... - } - - function getAdjustedLocation(node: Node, forRename: boolean): Node { - const { parent } = node; - // /**/ [|name|] ... - // /**/ [|name|] ... - // /**/ [|name|] ... - // /**/import [|name|] = ... - // - // NOTE: If the node is a modifier, we don't adjust its location if it is the `default` modifier as that is handled - // specially by `getSymbolAtLocation`. - if (isModifier(node) && (forRename || node.kind !== SyntaxKind.DefaultKeyword) ? canHaveModifiers(parent) && contains(parent.modifiers, node) : - node.kind === SyntaxKind.ClassKeyword ? isClassDeclaration(parent) || isClassExpression(node) : - node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) || isFunctionExpression(node) : - node.kind === SyntaxKind.InterfaceKeyword ? isInterfaceDeclaration(parent) : - node.kind === SyntaxKind.EnumKeyword ? isEnumDeclaration(parent) : - node.kind === SyntaxKind.TypeKeyword ? isTypeAliasDeclaration(parent) : - node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) : - node.kind === SyntaxKind.ImportKeyword ? isImportEqualsDeclaration(parent) : - node.kind === SyntaxKind.GetKeyword ? isGetAccessorDeclaration(parent) : - node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(parent)) { - const location = getAdjustedLocationForDeclaration(parent, forRename); - if (location) { - return location; - } - } - // /**/ [|name|] ... - if ((node.kind === SyntaxKind.VarKeyword || node.kind === SyntaxKind.ConstKeyword || node.kind === SyntaxKind.LetKeyword) && - isVariableDeclarationList(parent) && parent.declarations.length === 1) { - const decl = parent.declarations[0]; - if (isIdentifier(decl.name)) { - return decl.name; + } + if (!forRename) { + // /**/import "[|module|]"; + // /**/import ... from "[|module|]"; + // import /**/type ... from "[|module|]"; + return node.moduleSpecifier; + } +} + +function getAdjustedLocationForExportDeclaration(node: ExportDeclaration, forRename: boolean) { + if (node.exportClause) { + // /**/export { [|name|] } ... + // /**/export { propertyName as [|name|] } ... + // /**/export * as [|name|] ... + // export /**/type { [|name|] } from ... + // export /**/type { propertyName as [|name|] } from ... + // export /**/type * as [|name|] ... + if (isNamedExports(node.exportClause)) { + // do nothing if there is more than one binding + const onlyBinding = singleOrUndefined(node.exportClause.elements); + if (!onlyBinding) { + return; } + return node.exportClause.elements[0].name; } - if (node.kind === SyntaxKind.TypeKeyword) { - // import /**/type [|name|] from ...; - // import /**/type { [|name|] } from ...; - // import /**/type { propertyName as [|name|] } from ...; - // import /**/type ... from "[|module|]"; - if (isImportClause(parent) && parent.isTypeOnly) { - const location = getAdjustedLocationForImportDeclaration(parent.parent, forRename); - if (location) { - return location; - } - } - // export /**/type { [|name|] } from ...; - // export /**/type { propertyName as [|name|] } from ...; - // export /**/type * from "[|module|]"; - // export /**/type * as ... from "[|module|]"; - if (isExportDeclaration(parent) && parent.isTypeOnly) { - const location = getAdjustedLocationForExportDeclaration(parent, forRename); - if (location) { - return location; - } - } + else if (isNamespaceExport(node.exportClause)) { + return node.exportClause.name; } - // import { propertyName /**/as [|name|] } ... - // import * /**/as [|name|] ... - // export { propertyName /**/as [|name|] } ... - // export * /**/as [|name|] ... - if (node.kind === SyntaxKind.AsKeyword) { - if (isImportSpecifier(parent) && parent.propertyName || - isExportSpecifier(parent) && parent.propertyName || - isNamespaceImport(parent) || - isNamespaceExport(parent)) { - return parent.name; - } - if (isExportDeclaration(parent) && parent.exportClause && isNamespaceExport(parent.exportClause)) { - return parent.exportClause.name; + } + if (!forRename) { + // /**/export * from "[|module|]"; + // export /**/type * from "[|module|]"; + return node.moduleSpecifier; + } +} + +function getAdjustedLocationForHeritageClause(node: HeritageClause) { + // /**/extends [|name|] + // /**/implements [|name|] + if (node.types.length === 1) { + return node.types[0].expression; + } + + // /**/extends name1, name2 ... + // /**/implements name1, name2 ... +} + +function getAdjustedLocation(node: Node, forRename: boolean): Node { + const { parent } = node; + // /**/ [|name|] ... + // /**/ [|name|] ... + // /**/ [|name|] ... + // /**/import [|name|] = ... + // + // NOTE: If the node is a modifier, we don't adjust its location if it is the `default` modifier as that is handled + // specially by `getSymbolAtLocation`. + if (isModifier(node) && (forRename || node.kind !== SyntaxKind.DefaultKeyword) ? canHaveModifiers(parent) && contains(parent.modifiers, node) : + node.kind === SyntaxKind.ClassKeyword ? isClassDeclaration(parent) || isClassExpression(node) : + node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) || isFunctionExpression(node) : + node.kind === SyntaxKind.InterfaceKeyword ? isInterfaceDeclaration(parent) : + node.kind === SyntaxKind.EnumKeyword ? isEnumDeclaration(parent) : + node.kind === SyntaxKind.TypeKeyword ? isTypeAliasDeclaration(parent) : + node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) : + node.kind === SyntaxKind.ImportKeyword ? isImportEqualsDeclaration(parent) : + node.kind === SyntaxKind.GetKeyword ? isGetAccessorDeclaration(parent) : + node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(parent)) { + const location = getAdjustedLocationForDeclaration(parent, forRename); + if (location) { + return location; + } + } + // /**/ [|name|] ... + if ((node.kind === SyntaxKind.VarKeyword || node.kind === SyntaxKind.ConstKeyword || node.kind === SyntaxKind.LetKeyword) && + isVariableDeclarationList(parent) && parent.declarations.length === 1) { + const decl = parent.declarations[0]; + if (isIdentifier(decl.name)) { + return decl.name; + } + } + if (node.kind === SyntaxKind.TypeKeyword) { + // import /**/type [|name|] from ...; + // import /**/type { [|name|] } from ...; + // import /**/type { propertyName as [|name|] } from ...; + // import /**/type ... from "[|module|]"; + if (isImportClause(parent) && parent.isTypeOnly) { + const location = getAdjustedLocationForImportDeclaration(parent.parent, forRename); + if (location) { + return location; } } - // /**/import [|name|] from ...; - // /**/import { [|name|] } from ...; - // /**/import { propertyName as [|name|] } from ...; - // /**/import ... from "[|module|]"; - // /**/import "[|module|]"; - if (node.kind === SyntaxKind.ImportKeyword && isImportDeclaration(parent)) { - const location = getAdjustedLocationForImportDeclaration(parent, forRename); + // export /**/type { [|name|] } from ...; + // export /**/type { propertyName as [|name|] } from ...; + // export /**/type * from "[|module|]"; + // export /**/type * as ... from "[|module|]"; + if (isExportDeclaration(parent) && parent.isTypeOnly) { + const location = getAdjustedLocationForExportDeclaration(parent, forRename); if (location) { return location; } } - if (node.kind === SyntaxKind.ExportKeyword) { - // /**/export { [|name|] } ...; - // /**/export { propertyName as [|name|] } ...; - // /**/export * from "[|module|]"; - // /**/export * as ... from "[|module|]"; - if (isExportDeclaration(parent)) { - const location = getAdjustedLocationForExportDeclaration(parent, forRename); - if (location) { - return location; - } - } - // NOTE: We don't adjust the location of the `default` keyword as that is handled specially by `getSymbolAtLocation`. - // /**/export default [|name|]; - // /**/export = [|name|]; - if (isExportAssignment(parent)) { - return skipOuterExpressions(parent.expression); - } + } + // import { propertyName /**/as [|name|] } ... + // import * /**/as [|name|] ... + // export { propertyName /**/as [|name|] } ... + // export * /**/as [|name|] ... + if (node.kind === SyntaxKind.AsKeyword) { + if (isImportSpecifier(parent) && parent.propertyName || + isExportSpecifier(parent) && parent.propertyName || + isNamespaceImport(parent) || + isNamespaceExport(parent)) { + return parent.name; + } + if (isExportDeclaration(parent) && parent.exportClause && isNamespaceExport(parent.exportClause)) { + return parent.exportClause.name; + } + } + // /**/import [|name|] from ...; + // /**/import { [|name|] } from ...; + // /**/import { propertyName as [|name|] } from ...; + // /**/import ... from "[|module|]"; + // /**/import "[|module|]"; + if (node.kind === SyntaxKind.ImportKeyword && isImportDeclaration(parent)) { + const location = getAdjustedLocationForImportDeclaration(parent, forRename); + if (location) { + return location; } - // import name = /**/require("[|module|]"); - if (node.kind === SyntaxKind.RequireKeyword && isExternalModuleReference(parent)) { - return parent.expression; - } - // import ... /**/from "[|module|]"; - // export ... /**/from "[|module|]"; - if (node.kind === SyntaxKind.FromKeyword && (isImportDeclaration(parent) || isExportDeclaration(parent)) && parent.moduleSpecifier) { - return parent.moduleSpecifier; - } - // class ... /**/extends [|name|] ... - // class ... /**/implements [|name|] ... - // class ... /**/implements name1, name2 ... - // interface ... /**/extends [|name|] ... - // interface ... /**/extends name1, name2 ... - if ((node.kind === SyntaxKind.ExtendsKeyword || node.kind === SyntaxKind.ImplementsKeyword) && isHeritageClause(parent) && parent.token === node.kind) { - const location = getAdjustedLocationForHeritageClause(parent); + } + if (node.kind === SyntaxKind.ExportKeyword) { + // /**/export { [|name|] } ...; + // /**/export { propertyName as [|name|] } ...; + // /**/export * from "[|module|]"; + // /**/export * as ... from "[|module|]"; + if (isExportDeclaration(parent)) { + const location = getAdjustedLocationForExportDeclaration(parent, forRename); if (location) { return location; } } - if (node.kind === SyntaxKind.ExtendsKeyword) { - // ... ... - if (isTypeParameterDeclaration(parent) && parent.constraint && isTypeReferenceNode(parent.constraint)) { - return parent.constraint.typeName; - } - // ... T /**/extends [|U|] ? ... - if (isConditionalTypeNode(parent) && isTypeReferenceNode(parent.extendsType)) { - return parent.extendsType.typeName; + // NOTE: We don't adjust the location of the `default` keyword as that is handled specially by `getSymbolAtLocation`. + // /**/export default [|name|]; + // /**/export = [|name|]; + if (isExportAssignment(parent)) { + return skipOuterExpressions(parent.expression); + } + } + // import name = /**/require("[|module|]"); + if (node.kind === SyntaxKind.RequireKeyword && isExternalModuleReference(parent)) { + return parent.expression; + } + // import ... /**/from "[|module|]"; + // export ... /**/from "[|module|]"; + if (node.kind === SyntaxKind.FromKeyword && (isImportDeclaration(parent) || isExportDeclaration(parent)) && parent.moduleSpecifier) { + return parent.moduleSpecifier; + } + // class ... /**/extends [|name|] ... + // class ... /**/implements [|name|] ... + // class ... /**/implements name1, name2 ... + // interface ... /**/extends [|name|] ... + // interface ... /**/extends name1, name2 ... + if ((node.kind === SyntaxKind.ExtendsKeyword || node.kind === SyntaxKind.ImplementsKeyword) && isHeritageClause(parent) && parent.token === node.kind) { + const location = getAdjustedLocationForHeritageClause(parent); + if (location) { + return location; + } + } + if (node.kind === SyntaxKind.ExtendsKeyword) { + // ... ... + if (isTypeParameterDeclaration(parent) && parent.constraint && isTypeReferenceNode(parent.constraint)) { + return parent.constraint.typeName; + } + // ... T /**/extends [|U|] ? ... + if (isConditionalTypeNode(parent) && isTypeReferenceNode(parent.extendsType)) { + return parent.extendsType.typeName; + } + } + // ... T extends /**/infer [|U|] ? ... + if (node.kind === SyntaxKind.InferKeyword && isInferTypeNode(parent)) { + return parent.typeParameter.name; + } + // { [ [|K|] /**/in keyof T]: ... } + if (node.kind === SyntaxKind.InKeyword && isTypeParameterDeclaration(parent) && isMappedTypeNode(parent.parent)) { + return parent.name; + } + // /**/keyof [|T|] + if (node.kind === SyntaxKind.KeyOfKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.KeyOfKeyword && + isTypeReferenceNode(parent.type)) { + return parent.type.typeName; + } + // /**/readonly [|name|][] + if (node.kind === SyntaxKind.ReadonlyKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.ReadonlyKeyword && + isArrayTypeNode(parent.type) && isTypeReferenceNode(parent.type.elementType)) { + return parent.type.elementType.typeName; + } + if (!forRename) { + // /**/new [|name|] + // /**/void [|name|] + // /**/void obj.[|name|] + // /**/typeof [|name|] + // /**/typeof obj.[|name|] + // /**/await [|name|] + // /**/await obj.[|name|] + // /**/yield [|name|] + // /**/yield obj.[|name|] + // /**/delete obj.[|name|] + if (node.kind === SyntaxKind.NewKeyword && isNewExpression(parent) || + node.kind === SyntaxKind.VoidKeyword && isVoidExpression(parent) || + node.kind === SyntaxKind.TypeOfKeyword && isTypeOfExpression(parent) || + node.kind === SyntaxKind.AwaitKeyword && isAwaitExpression(parent) || + node.kind === SyntaxKind.YieldKeyword && isYieldExpression(parent) || + node.kind === SyntaxKind.DeleteKeyword && isDeleteExpression(parent)) { + if (parent.expression) { + return skipOuterExpressions(parent.expression); } } - // ... T extends /**/infer [|U|] ? ... - if (node.kind === SyntaxKind.InferKeyword && isInferTypeNode(parent)) { - return parent.typeParameter.name; + // left /**/in [|name|] + // left /**/instanceof [|name|] + if ((node.kind === SyntaxKind.InKeyword || node.kind === SyntaxKind.InstanceOfKeyword) && isBinaryExpression(parent) && parent.operatorToken === node) { + return skipOuterExpressions(parent.right); } - // { [ [|K|] /**/in keyof T]: ... } - if (node.kind === SyntaxKind.InKeyword && isTypeParameterDeclaration(parent) && isMappedTypeNode(parent.parent)) { - return parent.name; - } - // /**/keyof [|T|] - if (node.kind === SyntaxKind.KeyOfKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.KeyOfKeyword && - isTypeReferenceNode(parent.type)) { + // left /**/as [|name|] + if (node.kind === SyntaxKind.AsKeyword && isAsExpression(parent) && isTypeReferenceNode(parent.type)) { return parent.type.typeName; } - // /**/readonly [|name|][] - if (node.kind === SyntaxKind.ReadonlyKeyword && isTypeOperatorNode(parent) && parent.operator === SyntaxKind.ReadonlyKeyword && - isArrayTypeNode(parent.type) && isTypeReferenceNode(parent.type.elementType)) { - return parent.type.elementType.typeName; - } - if (!forRename) { - // /**/new [|name|] - // /**/void [|name|] - // /**/void obj.[|name|] - // /**/typeof [|name|] - // /**/typeof obj.[|name|] - // /**/await [|name|] - // /**/await obj.[|name|] - // /**/yield [|name|] - // /**/yield obj.[|name|] - // /**/delete obj.[|name|] - if (node.kind === SyntaxKind.NewKeyword && isNewExpression(parent) || - node.kind === SyntaxKind.VoidKeyword && isVoidExpression(parent) || - node.kind === SyntaxKind.TypeOfKeyword && isTypeOfExpression(parent) || - node.kind === SyntaxKind.AwaitKeyword && isAwaitExpression(parent) || - node.kind === SyntaxKind.YieldKeyword && isYieldExpression(parent) || - node.kind === SyntaxKind.DeleteKeyword && isDeleteExpression(parent)) { - if (parent.expression) { - return skipOuterExpressions(parent.expression); - } - } - // left /**/in [|name|] - // left /**/instanceof [|name|] - if ((node.kind === SyntaxKind.InKeyword || node.kind === SyntaxKind.InstanceOfKeyword) && isBinaryExpression(parent) && parent.operatorToken === node) { - return skipOuterExpressions(parent.right); - } - // left /**/as [|name|] - if (node.kind === SyntaxKind.AsKeyword && isAsExpression(parent) && isTypeReferenceNode(parent.type)) { - return parent.type.typeName; - } - // for (... /**/in [|name|]) - // for (... /**/of [|name|]) - if (node.kind === SyntaxKind.InKeyword && isForInStatement(parent) || - node.kind === SyntaxKind.OfKeyword && isForOfStatement(parent)) { - return skipOuterExpressions(parent.expression); - } + // for (... /**/in [|name|]) + // for (... /**/of [|name|]) + if (node.kind === SyntaxKind.InKeyword && isForInStatement(parent) || + node.kind === SyntaxKind.OfKeyword && isForOfStatement(parent)) { + return skipOuterExpressions(parent.expression); } - return node; - } - - /** - * Adjusts the location used for "find references" and "go to definition" when the cursor was not - * on a property name. - */ - export function getAdjustedReferenceLocation(node: Node): Node { - return getAdjustedLocation(node, /*forRename*/ false); } + return node; +} - /** - * Adjusts the location used for "rename" when the cursor was not on a property name. - */ - export function getAdjustedRenameLocation(node: Node): Node { - return getAdjustedLocation(node, /*forRename*/ true); - } +/** + * Adjusts the location used for "find references" and "go to definition" when the cursor was not + * on a property name. + * + * @internal + */ +export function getAdjustedReferenceLocation(node: Node): Node { + return getAdjustedLocation(node, /*forRename*/ false); +} - /** - * Gets the token whose text has range [start, end) and - * position >= start and (position < end or (position === end && token is literal or keyword or identifier)) - */ - export function getTouchingPropertyName(sourceFile: SourceFile, position: number): Node { - return getTouchingToken(sourceFile, position, n => isPropertyNameLiteral(n) || isKeyword(n.kind) || isPrivateIdentifier(n)); - } +/** + * Adjusts the location used for "rename" when the cursor was not on a property name. + * + * @internal + */ +export function getAdjustedRenameLocation(node: Node): Node { + return getAdjustedLocation(node, /*forRename*/ true); +} - /** - * Returns the token if position is in [start, end). - * If position === end, returns the preceding token if includeItemAtEndPosition(previousToken) === true - */ - export function getTouchingToken(sourceFile: SourceFile, position: number, includePrecedingTokenAtEndPosition?: (n: Node) => boolean): Node { - return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includePrecedingTokenAtEndPosition, /*includeEndPosition*/ false); - } - - /** Returns a token if position is in [start-of-leading-trivia, end) */ - export function getTokenAtPosition(sourceFile: SourceFile, position: number): Node { - return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includePrecedingTokenAtEndPosition*/ undefined, /*includeEndPosition*/ false); - } - - /** Get the token whose text contains the position */ - function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includePrecedingTokenAtEndPosition: ((n: Node) => boolean) | undefined, includeEndPosition: boolean): Node { - let current: Node = sourceFile; - let foundToken: Node | undefined; - outer: while (true) { - // find the child that contains 'position' - - const children = current.getChildren(sourceFile); - const i = binarySearchKey(children, position, (_, i) => i, (middle, _) => { - // This last callback is more of a selector than a comparator - - // `EqualTo` causes the `middle` result to be returned - // `GreaterThan` causes recursion on the left of the middle - // `LessThan` causes recursion on the right of the middle - - // Let's say you have 3 nodes, spanning positons - // pos: 1, end: 3 - // pos: 3, end: 3 - // pos: 3, end: 5 - // and you're looking for the token at positon 3 - all 3 of these nodes are overlapping with position 3. - // In fact, there's a _good argument_ that node 2 shouldn't even be allowed to exist - depending on if - // the start or end of the ranges are considered inclusive, it's either wholly subsumed by the first or the last node. - // Unfortunately, such nodes do exist. :( - See fourslash/completionsImport_tsx.tsx - empty jsx attributes create - // a zero-length node. - // What also you may not expect is that which node we return depends on the includePrecedingTokenAtEndPosition flag. - // Specifically, if includePrecedingTokenAtEndPosition is set, we return the 1-3 node, while if it's unset, we - // return the 3-5 node. (The zero length node is never correct.) This is because the includePrecedingTokenAtEndPosition - // flag causes us to return the first node whose end position matches the position and which produces and acceptable token - // kind. Meanwhile, if includePrecedingTokenAtEndPosition is unset, we look for the first node whose start is <= the - // position and whose end is greater than the position. - - - // There are more sophisticated end tests later, but this one is very fast - // and allows us to skip a bunch of work - const end = children[middle].getEnd(); - if (end < position) { - return Comparison.LessThan; - } +/** + * Gets the token whose text has range [start, end) and + * position >= start and (position < end or (position === end && token is literal or keyword or identifier)) + * + * @internal + */ +export function getTouchingPropertyName(sourceFile: SourceFile, position: number): Node { + return getTouchingToken(sourceFile, position, n => isPropertyNameLiteral(n) || isKeyword(n.kind) || isPrivateIdentifier(n)); +} - const start = allowPositionInLeadingTrivia ? children[middle].getFullStart() : children[middle].getStart(sourceFile, /*includeJsDoc*/ true); - if (start > position) { - return Comparison.GreaterThan; - } +/** + * Returns the token if position is in [start, end). + * If position === end, returns the preceding token if includeItemAtEndPosition(previousToken) === true + * + * @internal + */ +export function getTouchingToken(sourceFile: SourceFile, position: number, includePrecedingTokenAtEndPosition?: (n: Node) => boolean): Node { + return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includePrecedingTokenAtEndPosition, /*includeEndPosition*/ false); +} - // first element whose start position is before the input and whose end position is after or equal to the input - if (nodeContainsPosition(children[middle], start, end)) { - if (children[middle - 1]) { - // we want the _first_ element that contains the position, so left-recur if the prior node also contains the position - if (nodeContainsPosition(children[middle - 1])) { - return Comparison.GreaterThan; - } - } - return Comparison.EqualTo; - } +/** + * Returns a token if position is in [start-of-leading-trivia, end) + * + * @internal + */ +export function getTokenAtPosition(sourceFile: SourceFile, position: number): Node { + return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includePrecedingTokenAtEndPosition*/ undefined, /*includeEndPosition*/ false); +} - // this complex condition makes us left-recur around a zero-length node when includePrecedingTokenAtEndPosition is set, rather than right-recur on it - if (includePrecedingTokenAtEndPosition && start === position && children[middle - 1] && children[middle - 1].getEnd() === position && nodeContainsPosition(children[middle - 1])) { - return Comparison.GreaterThan; - } +/** Get the token whose text contains the position */ +function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includePrecedingTokenAtEndPosition: ((n: Node) => boolean) | undefined, includeEndPosition: boolean): Node { + let current: Node = sourceFile; + let foundToken: Node | undefined; + outer: while (true) { + // find the child that contains 'position' + + const children = current.getChildren(sourceFile); + const i = binarySearchKey(children, position, (_, i) => i, (middle, _) => { + // This last callback is more of a selector than a comparator - + // `EqualTo` causes the `middle` result to be returned + // `GreaterThan` causes recursion on the left of the middle + // `LessThan` causes recursion on the right of the middle + + // Let's say you have 3 nodes, spanning positons + // pos: 1, end: 3 + // pos: 3, end: 3 + // pos: 3, end: 5 + // and you're looking for the token at positon 3 - all 3 of these nodes are overlapping with position 3. + // In fact, there's a _good argument_ that node 2 shouldn't even be allowed to exist - depending on if + // the start or end of the ranges are considered inclusive, it's either wholly subsumed by the first or the last node. + // Unfortunately, such nodes do exist. :( - See fourslash/completionsImport_tsx.tsx - empty jsx attributes create + // a zero-length node. + // What also you may not expect is that which node we return depends on the includePrecedingTokenAtEndPosition flag. + // Specifically, if includePrecedingTokenAtEndPosition is set, we return the 1-3 node, while if it's unset, we + // return the 3-5 node. (The zero length node is never correct.) This is because the includePrecedingTokenAtEndPosition + // flag causes us to return the first node whose end position matches the position and which produces and acceptable token + // kind. Meanwhile, if includePrecedingTokenAtEndPosition is unset, we look for the first node whose start is <= the + // position and whose end is greater than the position. + + + // There are more sophisticated end tests later, but this one is very fast + // and allows us to skip a bunch of work + const end = children[middle].getEnd(); + if (end < position) { return Comparison.LessThan; - }); - - if (foundToken) { - return foundToken; } - if (i >= 0 && children[i]) { - current = children[i]; - continue outer; - } - - return current; - } - function nodeContainsPosition(node: Node, start?: number, end?: number) { - end ??= node.getEnd(); - if (end < position) { - return false; - } - start ??= allowPositionInLeadingTrivia ? node.getFullStart() : node.getStart(sourceFile, /*includeJsDoc*/ true); + const start = allowPositionInLeadingTrivia ? children[middle].getFullStart() : children[middle].getStart(sourceFile, /*includeJsDoc*/ true); if (start > position) { - // If this child begins after position, then all subsequent children will as well. - return false; + return Comparison.GreaterThan; } - if (position < end || (position === end && (node.kind === SyntaxKind.EndOfFileToken || includeEndPosition))) { - return true; - } - else if (includePrecedingTokenAtEndPosition && end === position) { - const previousToken = findPrecedingToken(position, sourceFile, node); - if (previousToken && includePrecedingTokenAtEndPosition(previousToken)) { - foundToken = previousToken; - return true; - } - } - return false; - } - } - - /** - * Returns the first token where position is in [start, end), - * excluding `JsxText` tokens containing only whitespace. - */ - export function findFirstNonJsxWhitespaceToken(sourceFile: SourceFile, position: number): Node | undefined { - let tokenAtPosition = getTokenAtPosition(sourceFile, position); - while (isWhiteSpaceOnlyJsxText(tokenAtPosition)) { - const nextToken = findNextToken(tokenAtPosition, tokenAtPosition.parent, sourceFile); - if (!nextToken) return; - tokenAtPosition = nextToken; - } - return tokenAtPosition; - } - - /** - * The token on the left of the position is the token that strictly includes the position - * or sits to the left of the cursor if it is on a boundary. For example - * - * fo|o -> will return foo - * foo |bar -> will return foo - * - */ - export function findTokenOnLeftOfPosition(file: SourceFile, position: number): Node | undefined { - // Ideally, getTokenAtPosition should return a token. However, it is currently - // broken, so we do a check to make sure the result was indeed a token. - const tokenAtPosition = getTokenAtPosition(file, position); - if (isToken(tokenAtPosition) && position > tokenAtPosition.getStart(file) && position < tokenAtPosition.getEnd()) { - return tokenAtPosition; - } - - return findPrecedingToken(position, file); - } - export function findNextToken(previousToken: Node, parent: Node, sourceFile: SourceFileLike): Node | undefined { - return find(parent); - - function find(n: Node): Node | undefined { - if (isToken(n) && n.pos === previousToken.end) { - // this is token that starts at the end of previous token - return it - return n; - } - return firstDefined(n.getChildren(sourceFile), child => { - const shouldDiveInChildNode = - // previous token is enclosed somewhere in the child - (child.pos <= previousToken.pos && child.end > previousToken.end) || - // previous token ends exactly at the beginning of child - (child.pos === previousToken.end); - return shouldDiveInChildNode && nodeHasTokens(child, sourceFile) ? find(child) : undefined; - }); - } - } - - /** - * Finds the rightmost token satisfying `token.end <= position`, - * excluding `JsxText` tokens containing only whitespace. - */ - export function findPrecedingToken(position: number, sourceFile: SourceFileLike, startNode: Node, excludeJsdoc?: boolean): Node | undefined; - export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, excludeJsdoc?: boolean): Node | undefined; - export function findPrecedingToken(position: number, sourceFile: SourceFileLike, startNode?: Node, excludeJsdoc?: boolean): Node | undefined { - const result = find((startNode || sourceFile) as Node); - Debug.assert(!(result && isWhiteSpaceOnlyJsxText(result))); - return result; - - function find(n: Node): Node | undefined { - if (isNonWhitespaceToken(n) && n.kind !== SyntaxKind.EndOfFileToken) { - return n; - } - - const children = n.getChildren(sourceFile); - const i = binarySearchKey(children, position, (_, i) => i, (middle, _) => { - // This last callback is more of a selector than a comparator - - // `EqualTo` causes the `middle` result to be returned - // `GreaterThan` causes recursion on the left of the middle - // `LessThan` causes recursion on the right of the middle - if (position < children[middle].end) { - // first element whose end position is greater than the input position - if (!children[middle - 1] || position >= children[middle - 1].end) { - return Comparison.EqualTo; - } - return Comparison.GreaterThan; - } - return Comparison.LessThan; - }); - if (i >= 0 && children[i]) { - const child = children[i]; - // Note that the span of a node's tokens is [node.getStart(...), node.end). - // Given that `position < child.end` and child has constituent tokens, we distinguish these cases: - // 1) `position` precedes `child`'s tokens or `child` has no tokens (ie: in a comment or whitespace preceding `child`): - // we need to find the last token in a previous child. - // 2) `position` is within the same span: we recurse on `child`. - if (position < child.end) { - const start = child.getStart(sourceFile, /*includeJsDoc*/ !excludeJsdoc); - const lookInPreviousChild = - (start >= position) || // cursor in the leading trivia - !nodeHasTokens(child, sourceFile) || - isWhiteSpaceOnlyJsxText(child); - - if (lookInPreviousChild) { - // actual start of the node is past the position - previous token should be at the end of previous child - const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i, sourceFile, n.kind); - return candidate && findRightmostToken(candidate, sourceFile); - } - else { - // candidate should be in this node - return find(child); + // first element whose start position is before the input and whose end position is after or equal to the input + if (nodeContainsPosition(children[middle], start, end)) { + if (children[middle - 1]) { + // we want the _first_ element that contains the position, so left-recur if the prior node also contains the position + if (nodeContainsPosition(children[middle - 1])) { + return Comparison.GreaterThan; } } + return Comparison.EqualTo; } - Debug.assert(startNode !== undefined || n.kind === SyntaxKind.SourceFile || n.kind === SyntaxKind.EndOfFileToken || isJSDocCommentContainingNode(n)); + // this complex condition makes us left-recur around a zero-length node when includePrecedingTokenAtEndPosition is set, rather than right-recur on it + if (includePrecedingTokenAtEndPosition && start === position && children[middle - 1] && children[middle - 1].getEnd() === position && nodeContainsPosition(children[middle - 1])) { + return Comparison.GreaterThan; + } + return Comparison.LessThan; + }); - // Here we know that none of child token nodes embrace the position, - // the only known case is when position is at the end of the file. - // Try to find the rightmost token in the file without filtering. - // Namely we are skipping the check: 'position < node.end' - const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length, sourceFile, n.kind); - return candidate && findRightmostToken(candidate, sourceFile); + if (foundToken) { + return foundToken; + } + if (i >= 0 && children[i]) { + current = children[i]; + continue outer; } - } - function isNonWhitespaceToken(n: Node): boolean { - return isToken(n) && !isWhiteSpaceOnlyJsxText(n); + return current; } - function findRightmostToken(n: Node, sourceFile: SourceFileLike): Node | undefined { - if (isNonWhitespaceToken(n)) { - return n; + function nodeContainsPosition(node: Node, start?: number, end?: number) { + end ??= node.getEnd(); + if (end < position) { + return false; + } + start ??= allowPositionInLeadingTrivia ? node.getFullStart() : node.getStart(sourceFile, /*includeJsDoc*/ true); + if (start > position) { + // If this child begins after position, then all subsequent children will as well. + return false; } + if (position < end || (position === end && (node.kind === SyntaxKind.EndOfFileToken || includeEndPosition))) { + return true; + } + else if (includePrecedingTokenAtEndPosition && end === position) { + const previousToken = findPrecedingToken(position, sourceFile, node); + if (previousToken && includePrecedingTokenAtEndPosition(previousToken)) { + foundToken = previousToken; + return true; + } + } + return false; + } +} - const children = n.getChildren(sourceFile); - if (children.length === 0) { +/** + * Returns the first token where position is in [start, end), + * excluding `JsxText` tokens containing only whitespace. + * + * @internal + */ +export function findFirstNonJsxWhitespaceToken(sourceFile: SourceFile, position: number): Node | undefined { + let tokenAtPosition = getTokenAtPosition(sourceFile, position); + while (isWhiteSpaceOnlyJsxText(tokenAtPosition)) { + const nextToken = findNextToken(tokenAtPosition, tokenAtPosition.parent, sourceFile); + if (!nextToken) return; + tokenAtPosition = nextToken; + } + return tokenAtPosition; +} + +/** + * The token on the left of the position is the token that strictly includes the position + * or sits to the left of the cursor if it is on a boundary. For example + * + * fo|o -> will return foo + * foo |bar -> will return foo + * + * + * @internal + */ +export function findTokenOnLeftOfPosition(file: SourceFile, position: number): Node | undefined { + // Ideally, getTokenAtPosition should return a token. However, it is currently + // broken, so we do a check to make sure the result was indeed a token. + const tokenAtPosition = getTokenAtPosition(file, position); + if (isToken(tokenAtPosition) && position > tokenAtPosition.getStart(file) && position < tokenAtPosition.getEnd()) { + return tokenAtPosition; + } + + return findPrecedingToken(position, file); +} + +/** @internal */ +export function findNextToken(previousToken: Node, parent: Node, sourceFile: SourceFileLike): Node | undefined { + return find(parent); + + function find(n: Node): Node | undefined { + if (isToken(n) && n.pos === previousToken.end) { + // this is token that starts at the end of previous token - return it return n; } - - const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length, sourceFile, n.kind); - return candidate && findRightmostToken(candidate, sourceFile); + return firstDefined(n.getChildren(sourceFile), child => { + const shouldDiveInChildNode = + // previous token is enclosed somewhere in the child + (child.pos <= previousToken.pos && child.end > previousToken.end) || + // previous token ends exactly at the beginning of child + (child.pos === previousToken.end); + return shouldDiveInChildNode && nodeHasTokens(child, sourceFile) ? find(child) : undefined; + }); } +} - /** - * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens. - */ - function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number, sourceFile: SourceFileLike, parentKind: SyntaxKind): Node | undefined { - for (let i = exclusiveStartPosition - 1; i >= 0; i--) { - const child = children[i]; +/** + * Finds the rightmost token satisfying `token.end <= position`, + * excluding `JsxText` tokens containing only whitespace. + * + * @internal + */ +export function findPrecedingToken(position: number, sourceFile: SourceFileLike, startNode: Node, excludeJsdoc?: boolean): Node | undefined; +/** @internal */ +export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, excludeJsdoc?: boolean): Node | undefined; +/** @internal */ +export function findPrecedingToken(position: number, sourceFile: SourceFileLike, startNode?: Node, excludeJsdoc?: boolean): Node | undefined { + const result = find((startNode || sourceFile) as Node); + Debug.assert(!(result && isWhiteSpaceOnlyJsxText(result))); + return result; + + function find(n: Node): Node | undefined { + if (isNonWhitespaceToken(n) && n.kind !== SyntaxKind.EndOfFileToken) { + return n; + } - if (isWhiteSpaceOnlyJsxText(child)) { - if (i === 0 && (parentKind === SyntaxKind.JsxText || parentKind === SyntaxKind.JsxSelfClosingElement)) { - Debug.fail("`JsxText` tokens should not be the first child of `JsxElement | JsxSelfClosingElement`"); + const children = n.getChildren(sourceFile); + const i = binarySearchKey(children, position, (_, i) => i, (middle, _) => { + // This last callback is more of a selector than a comparator - + // `EqualTo` causes the `middle` result to be returned + // `GreaterThan` causes recursion on the left of the middle + // `LessThan` causes recursion on the right of the middle + if (position < children[middle].end) { + // first element whose end position is greater than the input position + if (!children[middle - 1] || position >= children[middle - 1].end) { + return Comparison.EqualTo; } + return Comparison.GreaterThan; } - else if (nodeHasTokens(children[i], sourceFile)) { - return children[i]; + return Comparison.LessThan; + }); + if (i >= 0 && children[i]) { + const child = children[i]; + // Note that the span of a node's tokens is [node.getStart(...), node.end). + // Given that `position < child.end` and child has constituent tokens, we distinguish these cases: + // 1) `position` precedes `child`'s tokens or `child` has no tokens (ie: in a comment or whitespace preceding `child`): + // we need to find the last token in a previous child. + // 2) `position` is within the same span: we recurse on `child`. + if (position < child.end) { + const start = child.getStart(sourceFile, /*includeJsDoc*/ !excludeJsdoc); + const lookInPreviousChild = + (start >= position) || // cursor in the leading trivia + !nodeHasTokens(child, sourceFile) || + isWhiteSpaceOnlyJsxText(child); + + if (lookInPreviousChild) { + // actual start of the node is past the position - previous token should be at the end of previous child + const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i, sourceFile, n.kind); + return candidate && findRightmostToken(candidate, sourceFile); + } + else { + // candidate should be in this node + return find(child); + } } } - } - export function isInString(sourceFile: SourceFile, position: number, previousToken = findPrecedingToken(position, sourceFile)): boolean { - if (previousToken && isStringTextContainingNode(previousToken)) { - const start = previousToken.getStart(sourceFile); - const end = previousToken.getEnd(); + Debug.assert(startNode !== undefined || n.kind === SyntaxKind.SourceFile || n.kind === SyntaxKind.EndOfFileToken || isJSDocCommentContainingNode(n)); - // To be "in" one of these literals, the position has to be: - // 1. entirely within the token text. - // 2. at the end position of an unterminated token. - // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). - if (start < position && position < end) { - return true; - } + // Here we know that none of child token nodes embrace the position, + // the only known case is when position is at the end of the file. + // Try to find the rightmost token in the file without filtering. + // Namely we are skipping the check: 'position < node.end' + const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length, sourceFile, n.kind); + return candidate && findRightmostToken(candidate, sourceFile); + } +} - if (position === end) { - return !!(previousToken as LiteralExpression).isUnterminated; - } - } +function isNonWhitespaceToken(n: Node): boolean { + return isToken(n) && !isWhiteSpaceOnlyJsxText(n); +} - return false; +function findRightmostToken(n: Node, sourceFile: SourceFileLike): Node | undefined { + if (isNonWhitespaceToken(n)) { + return n; } - /** - * returns true if the position is in between the open and close elements of an JSX expression. - */ - export function isInsideJsxElementOrAttribute(sourceFile: SourceFile, position: number) { - const token = getTokenAtPosition(sourceFile, position); + const children = n.getChildren(sourceFile); + if (children.length === 0) { + return n; + } - if (!token) { - return false; - } + const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length, sourceFile, n.kind); + return candidate && findRightmostToken(candidate, sourceFile); +} - if (token.kind === SyntaxKind.JsxText) { - return true; - } +/** + * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens. + */ +function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number, sourceFile: SourceFileLike, parentKind: SyntaxKind): Node | undefined { + for (let i = exclusiveStartPosition - 1; i >= 0; i--) { + const child = children[i]; - //
Hello |
- if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxText) { - return true; + if (isWhiteSpaceOnlyJsxText(child)) { + if (i === 0 && (parentKind === SyntaxKind.JsxText || parentKind === SyntaxKind.JsxSelfClosingElement)) { + Debug.fail("`JsxText` tokens should not be the first child of `JsxElement | JsxSelfClosingElement`"); + } } - - //
{ |
or
- if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxExpression) { - return true; + else if (nodeHasTokens(children[i], sourceFile)) { + return children[i]; } + } +} - //
{ - // | - // } < /div> - if (token && token.kind === SyntaxKind.CloseBraceToken && token.parent.kind === SyntaxKind.JsxExpression) { +/** @internal */ +export function isInString(sourceFile: SourceFile, position: number, previousToken = findPrecedingToken(position, sourceFile)): boolean { + if (previousToken && isStringTextContainingNode(previousToken)) { + const start = previousToken.getStart(sourceFile); + const end = previousToken.getEnd(); + + // To be "in" one of these literals, the position has to be: + // 1. entirely within the token text. + // 2. at the end position of an unterminated token. + // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). + if (start < position && position < end) { return true; } - //
|
- if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxClosingElement) { - return true; + if (position === end) { + return !!(previousToken as LiteralExpression).isUnterminated; } + } + + return false; +} + +/** + * + * @internal + */ +export function isInsideJsxElementOrAttribute(sourceFile: SourceFile, position: number) { + const token = getTokenAtPosition(sourceFile, position); + if (!token) { return false; } - function isWhiteSpaceOnlyJsxText(node: Node): boolean { - return isJsxText(node) && node.containsOnlyTriviaWhiteSpaces; + if (token.kind === SyntaxKind.JsxText) { + return true; } - export function isInTemplateString(sourceFile: SourceFile, position: number) { - const token = getTokenAtPosition(sourceFile, position); - return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile); + //
Hello |
+ if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxText) { + return true; } - export function isInJSXText(sourceFile: SourceFile, position: number) { - const token = getTokenAtPosition(sourceFile, position); - if (isJsxText(token)) { - return true; - } - if (token.kind === SyntaxKind.OpenBraceToken && isJsxExpression(token.parent) && isJsxElement(token.parent.parent)) { - return true; - } - if (token.kind === SyntaxKind.LessThanToken && isJsxOpeningLikeElement(token.parent) && isJsxElement(token.parent.parent)) { - return true; - } - return false; + //
{ |
or
+ if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxExpression) { + return true; } - export function isInsideJsxElement(sourceFile: SourceFile, position: number): boolean { - function isInsideJsxElementTraversal(node: Node): boolean { - while (node) { - if (node.kind >= SyntaxKind.JsxSelfClosingElement && node.kind <= SyntaxKind.JsxExpression - || node.kind === SyntaxKind.JsxText - || node.kind === SyntaxKind.LessThanToken - || node.kind === SyntaxKind.GreaterThanToken - || node.kind === SyntaxKind.Identifier - || node.kind === SyntaxKind.CloseBraceToken - || node.kind === SyntaxKind.OpenBraceToken - || node.kind === SyntaxKind.SlashToken) { - node = node.parent; - } - else if (node.kind === SyntaxKind.JsxElement) { - if (position > node.getStart(sourceFile)) return true; + //
{ + // | + // } < /div> + if (token && token.kind === SyntaxKind.CloseBraceToken && token.parent.kind === SyntaxKind.JsxExpression) { + return true; + } - node = node.parent; - } - else { - return false; - } - } + //
|
+ if (token.kind === SyntaxKind.LessThanToken && token.parent.kind === SyntaxKind.JsxClosingElement) { + return true; + } - return false; - } + return false; +} - return isInsideJsxElementTraversal(getTokenAtPosition(sourceFile, position)); - } +function isWhiteSpaceOnlyJsxText(node: Node): boolean { + return isJsxText(node) && node.containsOnlyTriviaWhiteSpaces; +} - export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind.OpenBraceToken | SyntaxKind.OpenParenToken | SyntaxKind.OpenBracketToken, sourceFile: SourceFile) { - const closeTokenText = tokenToString(token.kind)!; - const matchingTokenText = tokenToString(matchingTokenKind)!; - const tokenFullStart = token.getFullStart(); - // Text-scan based fast path - can be bamboozled by comments and other trivia, but often provides - // a good, fast approximation without too much extra work in the cases where it fails. - const bestGuessIndex = sourceFile.text.lastIndexOf(matchingTokenText, tokenFullStart); - if (bestGuessIndex === -1) { - return undefined; // if the token text doesn't appear in the file, there can't be a match - super fast bail - } - // we can only use the textual result directly if we didn't have to count any close tokens within the range - if (sourceFile.text.lastIndexOf(closeTokenText, tokenFullStart - 1) < bestGuessIndex) { - const nodeAtGuess = findPrecedingToken(bestGuessIndex + 1, sourceFile); - if (nodeAtGuess && nodeAtGuess.kind === matchingTokenKind) { - return nodeAtGuess; - } - } - const tokenKind = token.kind; - let remainingMatchingTokens = 0; - while (true) { - const preceding = findPrecedingToken(token.getFullStart(), sourceFile); - if (!preceding) { - return undefined; - } - token = preceding; +/** @internal */ +export function isInTemplateString(sourceFile: SourceFile, position: number) { + const token = getTokenAtPosition(sourceFile, position); + return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile); +} - if (token.kind === matchingTokenKind) { - if (remainingMatchingTokens === 0) { - return token; - } +/** @internal */ +export function isInJSXText(sourceFile: SourceFile, position: number) { + const token = getTokenAtPosition(sourceFile, position); + if (isJsxText(token)) { + return true; + } + if (token.kind === SyntaxKind.OpenBraceToken && isJsxExpression(token.parent) && isJsxElement(token.parent.parent)) { + return true; + } + if (token.kind === SyntaxKind.LessThanToken && isJsxOpeningLikeElement(token.parent) && isJsxElement(token.parent.parent)) { + return true; + } + return false; +} - remainingMatchingTokens--; +/** @internal */ +export function isInsideJsxElement(sourceFile: SourceFile, position: number): boolean { + function isInsideJsxElementTraversal(node: Node): boolean { + while (node) { + if (node.kind >= SyntaxKind.JsxSelfClosingElement && node.kind <= SyntaxKind.JsxExpression + || node.kind === SyntaxKind.JsxText + || node.kind === SyntaxKind.LessThanToken + || node.kind === SyntaxKind.GreaterThanToken + || node.kind === SyntaxKind.Identifier + || node.kind === SyntaxKind.CloseBraceToken + || node.kind === SyntaxKind.OpenBraceToken + || node.kind === SyntaxKind.SlashToken) { + node = node.parent; + } + else if (node.kind === SyntaxKind.JsxElement) { + if (position > node.getStart(sourceFile)) return true; + + node = node.parent; } - else if (token.kind === tokenKind) { - remainingMatchingTokens++; + else { + return false; } } - } - export function removeOptionality(type: Type, isOptionalExpression: boolean, isOptionalChain: boolean) { - return isOptionalExpression ? type.getNonNullableType() : - isOptionalChain ? type.getNonOptionalType() : - type; + return false; } - export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile, checker: TypeChecker): boolean { - const info = getPossibleTypeArgumentsInfo(token, sourceFile); - return info !== undefined && (isPartOfTypeNode(info.called) || - getPossibleGenericSignatures(info.called, info.nTypeArguments, checker).length !== 0 || - isPossiblyTypeArgumentPosition(info.called, sourceFile, checker)); - } + return isInsideJsxElementTraversal(getTokenAtPosition(sourceFile, position)); +} - export function getPossibleGenericSignatures(called: Expression, typeArgumentCount: number, checker: TypeChecker): readonly Signature[] { - let type = checker.getTypeAtLocation(called); - if (isOptionalChain(called.parent)) { - type = removeOptionality(type, isOptionalChainRoot(called.parent), /*isOptionalChain*/ true); +/** @internal */ +export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind.OpenBraceToken | SyntaxKind.OpenParenToken | SyntaxKind.OpenBracketToken, sourceFile: SourceFile) { + const closeTokenText = tokenToString(token.kind)!; + const matchingTokenText = tokenToString(matchingTokenKind)!; + const tokenFullStart = token.getFullStart(); + // Text-scan based fast path - can be bamboozled by comments and other trivia, but often provides + // a good, fast approximation without too much extra work in the cases where it fails. + const bestGuessIndex = sourceFile.text.lastIndexOf(matchingTokenText, tokenFullStart); + if (bestGuessIndex === -1) { + return undefined; // if the token text doesn't appear in the file, there can't be a match - super fast bail + } + // we can only use the textual result directly if we didn't have to count any close tokens within the range + if (sourceFile.text.lastIndexOf(closeTokenText, tokenFullStart - 1) < bestGuessIndex) { + const nodeAtGuess = findPrecedingToken(bestGuessIndex + 1, sourceFile); + if (nodeAtGuess && nodeAtGuess.kind === matchingTokenKind) { + return nodeAtGuess; + } + } + const tokenKind = token.kind; + let remainingMatchingTokens = 0; + while (true) { + const preceding = findPrecedingToken(token.getFullStart(), sourceFile); + if (!preceding) { + return undefined; } + token = preceding; - const signatures = isNewExpression(called.parent) ? type.getConstructSignatures() : type.getCallSignatures(); - return signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= typeArgumentCount); - } + if (token.kind === matchingTokenKind) { + if (remainingMatchingTokens === 0) { + return token; + } - export interface PossibleTypeArgumentInfo { - readonly called: Identifier; - readonly nTypeArguments: number; + remainingMatchingTokens--; + } + else if (token.kind === tokenKind) { + remainingMatchingTokens++; + } } +} + +/** @internal */ +export function removeOptionality(type: Type, isOptionalExpression: boolean, isOptionalChain: boolean) { + return isOptionalExpression ? type.getNonNullableType() : + isOptionalChain ? type.getNonOptionalType() : + type; +} - export interface PossibleProgramFileInfo { - ProgramFiles?: string[]; +/** @internal */ +export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile, checker: TypeChecker): boolean { + const info = getPossibleTypeArgumentsInfo(token, sourceFile); + return info !== undefined && (isPartOfTypeNode(info.called) || + getPossibleGenericSignatures(info.called, info.nTypeArguments, checker).length !== 0 || + isPossiblyTypeArgumentPosition(info.called, sourceFile, checker)); +} + +/** @internal */ +export function getPossibleGenericSignatures(called: Expression, typeArgumentCount: number, checker: TypeChecker): readonly Signature[] { + let type = checker.getTypeAtLocation(called); + if (isOptionalChain(called.parent)) { + type = removeOptionality(type, isOptionalChainRoot(called.parent), /*isOptionalChain*/ true); } - // Get info for an expression like `f <` that may be the start of type arguments. - export function getPossibleTypeArgumentsInfo(tokenIn: Node | undefined, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined { - // This is a rare case, but one that saves on a _lot_ of work if true - if the source file has _no_ `<` character, - // then there obviously can't be any type arguments - no expensive brace-matching backwards scanning required + const signatures = isNewExpression(called.parent) ? type.getConstructSignatures() : type.getCallSignatures(); + return signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= typeArgumentCount); +} - if (sourceFile.text.lastIndexOf("<", tokenIn ? tokenIn.pos : sourceFile.text.length) === -1) { - return undefined; - } +/** @internal */ +export interface PossibleTypeArgumentInfo { + readonly called: Identifier; + readonly nTypeArguments: number; +} + +/** @internal */ +export interface PossibleProgramFileInfo { + ProgramFiles?: string[]; +} + +// Get info for an expression like `f <` that may be the start of type arguments. +/** @internal */ +export function getPossibleTypeArgumentsInfo(tokenIn: Node | undefined, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined { + // This is a rare case, but one that saves on a _lot_ of work if true - if the source file has _no_ `<` character, + // then there obviously can't be any type arguments - no expensive brace-matching backwards scanning required + + if (sourceFile.text.lastIndexOf("<", tokenIn ? tokenIn.pos : sourceFile.text.length) === -1) { + return undefined; + } - let token: Node | undefined = tokenIn; - // This function determines if the node could be type argument position - // Since during editing, when type argument list is not complete, - // the tree could be of any shape depending on the tokens parsed before current node, - // scanning of the previous identifier followed by "<" before current node would give us better result - // Note that we also balance out the already provided type arguments, arrays, object literals while doing so - let remainingLessThanTokens = 0; - let nTypeArguments = 0; - while (token) { - switch (token.kind) { - case SyntaxKind.LessThanToken: - // Found the beginning of the generic argument expression + let token: Node | undefined = tokenIn; + // This function determines if the node could be type argument position + // Since during editing, when type argument list is not complete, + // the tree could be of any shape depending on the tokens parsed before current node, + // scanning of the previous identifier followed by "<" before current node would give us better result + // Note that we also balance out the already provided type arguments, arrays, object literals while doing so + let remainingLessThanTokens = 0; + let nTypeArguments = 0; + while (token) { + switch (token.kind) { + case SyntaxKind.LessThanToken: + // Found the beginning of the generic argument expression + token = findPrecedingToken(token.getFullStart(), sourceFile); + if (token && token.kind === SyntaxKind.QuestionDotToken) { token = findPrecedingToken(token.getFullStart(), sourceFile); - if (token && token.kind === SyntaxKind.QuestionDotToken) { - token = findPrecedingToken(token.getFullStart(), sourceFile); - } - if (!token || !isIdentifier(token)) return undefined; - if (!remainingLessThanTokens) { - return isDeclarationName(token) ? undefined : { called: token, nTypeArguments }; - } - remainingLessThanTokens--; - break; + } + if (!token || !isIdentifier(token)) return undefined; + if (!remainingLessThanTokens) { + return isDeclarationName(token) ? undefined : { called: token, nTypeArguments }; + } + remainingLessThanTokens--; + break; - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - remainingLessThanTokens = + 3; - break; + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + remainingLessThanTokens = + 3; + break; - case SyntaxKind.GreaterThanGreaterThanToken: - remainingLessThanTokens = + 2; - break; + case SyntaxKind.GreaterThanGreaterThanToken: + remainingLessThanTokens = + 2; + break; - case SyntaxKind.GreaterThanToken: - remainingLessThanTokens++; - break; + case SyntaxKind.GreaterThanToken: + remainingLessThanTokens++; + break; - case SyntaxKind.CloseBraceToken: - // This can be object type, skip until we find the matching open brace token - // Skip until the matching open brace token - token = findPrecedingMatchingToken(token, SyntaxKind.OpenBraceToken, sourceFile); - if (!token) return undefined; - break; + case SyntaxKind.CloseBraceToken: + // This can be object type, skip until we find the matching open brace token + // Skip until the matching open brace token + token = findPrecedingMatchingToken(token, SyntaxKind.OpenBraceToken, sourceFile); + if (!token) return undefined; + break; - case SyntaxKind.CloseParenToken: - // This can be object type, skip until we find the matching open brace token - // Skip until the matching open brace token - token = findPrecedingMatchingToken(token, SyntaxKind.OpenParenToken, sourceFile); - if (!token) return undefined; - break; + case SyntaxKind.CloseParenToken: + // This can be object type, skip until we find the matching open brace token + // Skip until the matching open brace token + token = findPrecedingMatchingToken(token, SyntaxKind.OpenParenToken, sourceFile); + if (!token) return undefined; + break; - case SyntaxKind.CloseBracketToken: - // This can be object type, skip until we find the matching open brace token - // Skip until the matching open brace token - token = findPrecedingMatchingToken(token, SyntaxKind.OpenBracketToken, sourceFile); - if (!token) return undefined; - break; + case SyntaxKind.CloseBracketToken: + // This can be object type, skip until we find the matching open brace token + // Skip until the matching open brace token + token = findPrecedingMatchingToken(token, SyntaxKind.OpenBracketToken, sourceFile); + if (!token) return undefined; + break; - // Valid tokens in a type name. Skip. - case SyntaxKind.CommaToken: - nTypeArguments++; - break; + // Valid tokens in a type name. Skip. + case SyntaxKind.CommaToken: + nTypeArguments++; + break; - case SyntaxKind.EqualsGreaterThanToken: - // falls through - - case SyntaxKind.Identifier: - case SyntaxKind.StringLiteral: - case SyntaxKind.NumericLiteral: - case SyntaxKind.BigIntLiteral: - case SyntaxKind.TrueKeyword: - case SyntaxKind.FalseKeyword: - // falls through - - case SyntaxKind.TypeOfKeyword: - case SyntaxKind.ExtendsKeyword: - case SyntaxKind.KeyOfKeyword: - case SyntaxKind.DotToken: - case SyntaxKind.BarToken: - case SyntaxKind.QuestionToken: - case SyntaxKind.ColonToken: - break; + case SyntaxKind.EqualsGreaterThanToken: + // falls through - default: - if (isTypeNode(token)) { - break; - } + case SyntaxKind.Identifier: + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + // falls through - // Invalid token in type - return undefined; - } + case SyntaxKind.TypeOfKeyword: + case SyntaxKind.ExtendsKeyword: + case SyntaxKind.KeyOfKeyword: + case SyntaxKind.DotToken: + case SyntaxKind.BarToken: + case SyntaxKind.QuestionToken: + case SyntaxKind.ColonToken: + break; + + default: + if (isTypeNode(token)) { + break; + } - token = findPrecedingToken(token.getFullStart(), sourceFile); + // Invalid token in type + return undefined; } - return undefined; + token = findPrecedingToken(token.getFullStart(), sourceFile); } - /** - * Returns true if the cursor at position in sourceFile is within a comment. - * - * @param tokenAtPosition Must equal `getTokenAtPosition(sourceFile, position)` - * @param predicate Additional predicate to test on the comment range. - */ - export function isInComment(sourceFile: SourceFile, position: number, tokenAtPosition?: Node): CommentRange | undefined { - return formatting.getRangeOfEnclosingComment(sourceFile, position, /*precedingToken*/ undefined, tokenAtPosition); - } + return undefined; +} + +/** + * Returns true if the cursor at position in sourceFile is within a comment. + * + * @param tokenAtPosition Must equal `getTokenAtPosition(sourceFile, position)` + * @param predicate Additional predicate to test on the comment range. + * + * @internal + */ +export function isInComment(sourceFile: SourceFile, position: number, tokenAtPosition?: Node): CommentRange | undefined { + return formatting.getRangeOfEnclosingComment(sourceFile, position, /*precedingToken*/ undefined, tokenAtPosition); +} - export function hasDocComment(sourceFile: SourceFile, position: number): boolean { - const token = getTokenAtPosition(sourceFile, position); - return !!findAncestor(token, isJSDoc); +/** @internal */ +export function hasDocComment(sourceFile: SourceFile, position: number): boolean { + const token = getTokenAtPosition(sourceFile, position); + return !!findAncestor(token, isJSDoc); +} + +function nodeHasTokens(n: Node, sourceFile: SourceFileLike): boolean { + // If we have a token or node that has a non-zero width, it must have tokens. + // Note: getWidth() does not take trivia into account. + return n.kind === SyntaxKind.EndOfFileToken ? !!(n as EndOfFileToken).jsDoc : n.getWidth(sourceFile) !== 0; +} + +/** @internal */ +export function getNodeModifiers(node: Node, excludeFlags = ModifierFlags.None): string { + const result: string[] = []; + const flags = isDeclaration(node) + ? getCombinedNodeFlagsAlwaysIncludeJSDoc(node) & ~excludeFlags + : ModifierFlags.None; + + if (flags & ModifierFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier); + if (flags & ModifierFlags.Protected) result.push(ScriptElementKindModifier.protectedMemberModifier); + if (flags & ModifierFlags.Public) result.push(ScriptElementKindModifier.publicMemberModifier); + if (flags & ModifierFlags.Static || isClassStaticBlockDeclaration(node)) result.push(ScriptElementKindModifier.staticModifier); + if (flags & ModifierFlags.Abstract) result.push(ScriptElementKindModifier.abstractModifier); + if (flags & ModifierFlags.Export) result.push(ScriptElementKindModifier.exportedModifier); + if (flags & ModifierFlags.Deprecated) result.push(ScriptElementKindModifier.deprecatedModifier); + if (node.flags & NodeFlags.Ambient) result.push(ScriptElementKindModifier.ambientModifier); + if (node.kind === SyntaxKind.ExportAssignment) result.push(ScriptElementKindModifier.exportedModifier); + + return result.length > 0 ? result.join(",") : ScriptElementKindModifier.none; +} + +/** @internal */ +export function getTypeArgumentOrTypeParameterList(node: Node): NodeArray | undefined { + if (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.CallExpression) { + return (node as CallExpression).typeArguments; } - function nodeHasTokens(n: Node, sourceFile: SourceFileLike): boolean { - // If we have a token or node that has a non-zero width, it must have tokens. - // Note: getWidth() does not take trivia into account. - return n.kind === SyntaxKind.EndOfFileToken ? !!(n as EndOfFileToken).jsDoc : n.getWidth(sourceFile) !== 0; + if (isFunctionLike(node) || node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.InterfaceDeclaration) { + return (node as FunctionLikeDeclaration).typeParameters; } - export function getNodeModifiers(node: Node, excludeFlags = ModifierFlags.None): string { - const result: string[] = []; - const flags = isDeclaration(node) - ? getCombinedNodeFlagsAlwaysIncludeJSDoc(node) & ~excludeFlags - : ModifierFlags.None; + return undefined; +} - if (flags & ModifierFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier); - if (flags & ModifierFlags.Protected) result.push(ScriptElementKindModifier.protectedMemberModifier); - if (flags & ModifierFlags.Public) result.push(ScriptElementKindModifier.publicMemberModifier); - if (flags & ModifierFlags.Static || isClassStaticBlockDeclaration(node)) result.push(ScriptElementKindModifier.staticModifier); - if (flags & ModifierFlags.Abstract) result.push(ScriptElementKindModifier.abstractModifier); - if (flags & ModifierFlags.Export) result.push(ScriptElementKindModifier.exportedModifier); - if (flags & ModifierFlags.Deprecated) result.push(ScriptElementKindModifier.deprecatedModifier); - if (node.flags & NodeFlags.Ambient) result.push(ScriptElementKindModifier.ambientModifier); - if (node.kind === SyntaxKind.ExportAssignment) result.push(ScriptElementKindModifier.exportedModifier); +/** @internal */ +export function isComment(kind: SyntaxKind): boolean { + return kind === SyntaxKind.SingleLineCommentTrivia || kind === SyntaxKind.MultiLineCommentTrivia; +} - return result.length > 0 ? result.join(",") : ScriptElementKindModifier.none; +/** @internal */ +export function isStringOrRegularExpressionOrTemplateLiteral(kind: SyntaxKind): boolean { + if (kind === SyntaxKind.StringLiteral + || kind === SyntaxKind.RegularExpressionLiteral + || isTemplateLiteralKind(kind)) { + return true; } + return false; +} - export function getTypeArgumentOrTypeParameterList(node: Node): NodeArray | undefined { - if (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.CallExpression) { - return (node as CallExpression).typeArguments; - } +/** @internal */ +export function isPunctuation(kind: SyntaxKind): boolean { + return SyntaxKind.FirstPunctuation <= kind && kind <= SyntaxKind.LastPunctuation; +} - if (isFunctionLike(node) || node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.InterfaceDeclaration) { - return (node as FunctionLikeDeclaration).typeParameters; - } +/** @internal */ +export function isInsideTemplateLiteral(node: TemplateLiteralToken, position: number, sourceFile: SourceFile): boolean { + return isTemplateLiteralKind(node.kind) + && (node.getStart(sourceFile) < position && position < node.end) || (!!node.isUnterminated && position === node.end); +} - return undefined; +/** @internal */ +export function isAccessibilityModifier(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + return true; } - export function isComment(kind: SyntaxKind): boolean { - return kind === SyntaxKind.SingleLineCommentTrivia || kind === SyntaxKind.MultiLineCommentTrivia; - } + return false; +} + +/** @internal */ +export function cloneCompilerOptions(options: CompilerOptions): CompilerOptions { + const result = clone(options); + setConfigFileInOptions(result, options && options.configFile); + return result; +} - export function isStringOrRegularExpressionOrTemplateLiteral(kind: SyntaxKind): boolean { - if (kind === SyntaxKind.StringLiteral - || kind === SyntaxKind.RegularExpressionLiteral - || isTemplateLiteralKind(kind)) { +/** @internal */ +export function isArrayLiteralOrObjectLiteralDestructuringPattern(node: Node) { + if (node.kind === SyntaxKind.ArrayLiteralExpression || + node.kind === SyntaxKind.ObjectLiteralExpression) { + // [a,b,c] from: + // [a, b, c] = someExpression; + if (node.parent.kind === SyntaxKind.BinaryExpression && + (node.parent as BinaryExpression).left === node && + (node.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { return true; } - return false; - } - export function isPunctuation(kind: SyntaxKind): boolean { - return SyntaxKind.FirstPunctuation <= kind && kind <= SyntaxKind.LastPunctuation; - } + // [a, b, c] from: + // for([a, b, c] of expression) + if (node.parent.kind === SyntaxKind.ForOfStatement && + (node.parent as ForOfStatement).initializer === node) { + return true; + } - export function isInsideTemplateLiteral(node: TemplateLiteralToken, position: number, sourceFile: SourceFile): boolean { - return isTemplateLiteralKind(node.kind) - && (node.getStart(sourceFile) < position && position < node.end) || (!!node.isUnterminated && position === node.end); + // [a, b, c] of + // [x, [a, b, c] ] = someExpression + // or + // {x, a: {a, b, c} } = someExpression + if (isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent.kind === SyntaxKind.PropertyAssignment ? node.parent.parent : node.parent)) { + return true; + } } - export function isAccessibilityModifier(kind: SyntaxKind) { - switch (kind) { - case SyntaxKind.PublicKeyword: - case SyntaxKind.PrivateKeyword: - case SyntaxKind.ProtectedKeyword: - return true; - } + return false; +} - return false; - } +/** @internal */ +export function isInReferenceComment(sourceFile: SourceFile, position: number): boolean { + return isInReferenceCommentWorker(sourceFile, position, /*shouldBeReference*/ true); +} + +/** @internal */ +export function isInNonReferenceComment(sourceFile: SourceFile, position: number): boolean { + return isInReferenceCommentWorker(sourceFile, position, /*shouldBeReference*/ false); +} + +function isInReferenceCommentWorker(sourceFile: SourceFile, position: number, shouldBeReference: boolean): boolean { + const range = isInComment(sourceFile, position, /*tokenAtPosition*/ undefined); + return !!range && shouldBeReference === tripleSlashDirectivePrefixRegex.test(sourceFile.text.substring(range.pos, range.end)); +} + +/** @internal */ +export function getReplacementSpanForContextToken(contextToken: Node | undefined) { + if (!contextToken) return undefined; - export function cloneCompilerOptions(options: CompilerOptions): CompilerOptions { - const result = clone(options); - setConfigFileInOptions(result, options && options.configFile); - return result; + switch (contextToken.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return createTextSpanFromStringLiteralLikeContent(contextToken as StringLiteralLike); + default: + return createTextSpanFromNode(contextToken); } +} - export function isArrayLiteralOrObjectLiteralDestructuringPattern(node: Node) { - if (node.kind === SyntaxKind.ArrayLiteralExpression || - node.kind === SyntaxKind.ObjectLiteralExpression) { - // [a,b,c] from: - // [a, b, c] = someExpression; - if (node.parent.kind === SyntaxKind.BinaryExpression && - (node.parent as BinaryExpression).left === node && - (node.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - return true; - } +/** @internal */ +export function createTextSpanFromNode(node: Node, sourceFile?: SourceFile, endNode?: Node): TextSpan { + return createTextSpanFromBounds(node.getStart(sourceFile), (endNode || node).getEnd()); +} - // [a, b, c] from: - // for([a, b, c] of expression) - if (node.parent.kind === SyntaxKind.ForOfStatement && - (node.parent as ForOfStatement).initializer === node) { - return true; - } +/** @internal */ +export function createTextSpanFromStringLiteralLikeContent(node: StringLiteralLike) { + if (node.isUnterminated) return undefined; + return createTextSpanFromBounds(node.getStart() + 1, node.getEnd() - 1); +} - // [a, b, c] of - // [x, [a, b, c] ] = someExpression - // or - // {x, a: {a, b, c} } = someExpression - if (isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent.kind === SyntaxKind.PropertyAssignment ? node.parent.parent : node.parent)) { - return true; - } - } +/** @internal */ +export function createTextRangeFromNode(node: Node, sourceFile: SourceFile): TextRange { + return createRange(node.getStart(sourceFile), node.end); +} - return false; - } +/** @internal */ +export function createTextSpanFromRange(range: TextRange): TextSpan { + return createTextSpanFromBounds(range.pos, range.end); +} - export function isInReferenceComment(sourceFile: SourceFile, position: number): boolean { - return isInReferenceCommentWorker(sourceFile, position, /*shouldBeReference*/ true); - } +/** @internal */ +export function createTextRangeFromSpan(span: TextSpan): TextRange { + return createRange(span.start, span.start + span.length); +} - export function isInNonReferenceComment(sourceFile: SourceFile, position: number): boolean { - return isInReferenceCommentWorker(sourceFile, position, /*shouldBeReference*/ false); - } +/** @internal */ +export function createTextChangeFromStartLength(start: number, length: number, newText: string): TextChange { + return createTextChange(createTextSpan(start, length), newText); +} - function isInReferenceCommentWorker(sourceFile: SourceFile, position: number, shouldBeReference: boolean): boolean { - const range = isInComment(sourceFile, position, /*tokenAtPosition*/ undefined); - return !!range && shouldBeReference === tripleSlashDirectivePrefixRegex.test(sourceFile.text.substring(range.pos, range.end)); - } +/** @internal */ +export function createTextChange(span: TextSpan, newText: string): TextChange { + return { span, newText }; +} - export function getReplacementSpanForContextToken(contextToken: Node | undefined) { - if (!contextToken) return undefined; +/** @internal */ +export const typeKeywords: readonly SyntaxKind[] = [ + SyntaxKind.AnyKeyword, + SyntaxKind.AssertsKeyword, + SyntaxKind.BigIntKeyword, + SyntaxKind.BooleanKeyword, + SyntaxKind.FalseKeyword, + SyntaxKind.InferKeyword, + SyntaxKind.KeyOfKeyword, + SyntaxKind.NeverKeyword, + SyntaxKind.NullKeyword, + SyntaxKind.NumberKeyword, + SyntaxKind.ObjectKeyword, + SyntaxKind.ReadonlyKeyword, + SyntaxKind.StringKeyword, + SyntaxKind.SymbolKeyword, + SyntaxKind.TrueKeyword, + SyntaxKind.VoidKeyword, + SyntaxKind.UndefinedKeyword, + SyntaxKind.UniqueKeyword, + SyntaxKind.UnknownKeyword, +]; + +/** @internal */ +export function isTypeKeyword(kind: SyntaxKind): boolean { + return contains(typeKeywords, kind); +} - switch (contextToken.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return createTextSpanFromStringLiteralLikeContent(contextToken as StringLiteralLike); - default: - return createTextSpanFromNode(contextToken); - } - } +/** @internal */ +export function isTypeKeywordToken(node: Node): node is Token { + return node.kind === SyntaxKind.TypeKeyword; +} - export function createTextSpanFromNode(node: Node, sourceFile?: SourceFile, endNode?: Node): TextSpan { - return createTextSpanFromBounds(node.getStart(sourceFile), (endNode || node).getEnd()); - } +/** @internal */ +export function isTypeKeywordTokenOrIdentifier(node: Node) { + return isTypeKeywordToken(node) || isIdentifier(node) && node.text === "type"; +} + +/** + * True if the symbol is for an external module, as opposed to a namespace. + * + * @internal + */ +export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean { + return !!(moduleSymbol.flags & SymbolFlags.Module) && moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote; +} + +/** + * Returns `true` the first time it encounters a node and `false` afterwards. + * + * @internal + */ +export type NodeSeenTracker = (node: T) => boolean; +/** @internal */ +export function nodeSeenTracker(): NodeSeenTracker { + const seen: true[] = []; + return node => { + const id = getNodeId(node); + return !seen[id] && (seen[id] = true); + }; +} + +/** @internal */ +export function getSnapshotText(snap: IScriptSnapshot): string { + return snap.getText(0, snap.getLength()); +} - export function createTextSpanFromStringLiteralLikeContent(node: StringLiteralLike) { - if (node.isUnterminated) return undefined; - return createTextSpanFromBounds(node.getStart() + 1, node.getEnd() - 1); +/** @internal */ +export function repeatString(str: string, count: number): string { + let result = ""; + for (let i = 0; i < count; i++) { + result += str; } + return result; +} + +/** @internal */ +export function skipConstraint(type: Type): Type { + return type.isTypeParameter() ? type.getConstraint() || type : type; +} + +/** @internal */ +export function getNameFromPropertyName(name: PropertyName): string | undefined { + return name.kind === SyntaxKind.ComputedPropertyName + // treat computed property names where expression is string/numeric literal as just string/numeric literal + ? isStringOrNumericLiteralLike(name.expression) ? name.expression.text : undefined + : isPrivateIdentifier(name) ? idText(name) : getTextOfIdentifierOrLiteral(name); +} + +/** @internal */ +export function programContainsModules(program: Program): boolean { + return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!(s.externalModuleIndicator || s.commonJsModuleIndicator)); +} +/** @internal */ +export function programContainsEsModules(program: Program): boolean { + return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!s.externalModuleIndicator); +} +/** @internal */ +export function compilerOptionsIndicateEsModules(compilerOptions: CompilerOptions): boolean { + return !!compilerOptions.module || getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015 || !!compilerOptions.noEmit; +} - export function createTextRangeFromNode(node: Node, sourceFile: SourceFile): TextRange { - return createRange(node.getStart(sourceFile), node.end); - } +/** @internal */ +export function createModuleSpecifierResolutionHost(program: Program, host: LanguageServiceHost): ModuleSpecifierResolutionHost { + // Mix in `getSymlinkCache` from Program when host doesn't have it + // in order for non-Project hosts to have a symlinks cache. + return { + fileExists: fileName => program.fileExists(fileName), + getCurrentDirectory: () => host.getCurrentDirectory(), + readFile: maybeBind(host, host.readFile), + useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), + getSymlinkCache: maybeBind(host, host.getSymlinkCache) || program.getSymlinkCache, + getModuleSpecifierCache: maybeBind(host, host.getModuleSpecifierCache), + getPackageJsonInfoCache: () => program.getModuleResolutionCache()?.getPackageJsonInfoCache(), + getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation), + redirectTargetsMap: program.redirectTargetsMap, + getProjectReferenceRedirect: fileName => program.getProjectReferenceRedirect(fileName), + isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName), + getNearestAncestorDirectoryWithPackageJson: maybeBind(host, host.getNearestAncestorDirectoryWithPackageJson), + getFileIncludeReasons: () => program.getFileIncludeReasons(), + }; +} - export function createTextSpanFromRange(range: TextRange): TextSpan { - return createTextSpanFromBounds(range.pos, range.end); - } +/** @internal */ +export function getModuleSpecifierResolverHost(program: Program, host: LanguageServiceHost): SymbolTracker["moduleResolverHost"] { + return { + ...createModuleSpecifierResolutionHost(program, host), + getCommonSourceDirectory: () => program.getCommonSourceDirectory(), + }; +} - export function createTextRangeFromSpan(span: TextSpan): TextRange { - return createRange(span.start, span.start + span.length); - } +/** @internal */ +export function moduleResolutionRespectsExports(moduleResolution: ModuleResolutionKind): boolean { + return moduleResolution >= ModuleResolutionKind.Node16 && moduleResolution <= ModuleResolutionKind.NodeNext; +} - export function createTextChangeFromStartLength(start: number, length: number, newText: string): TextChange { - return createTextChange(createTextSpan(start, length), newText); - } +/** @internal */ +export function moduleResolutionUsesNodeModules(moduleResolution: ModuleResolutionKind): boolean { + return moduleResolution === ModuleResolutionKind.NodeJs || moduleResolution >= ModuleResolutionKind.Node16 && moduleResolution <= ModuleResolutionKind.NodeNext; +} - export function createTextChange(span: TextSpan, newText: string): TextChange { - return { span, newText }; - } +/** @internal */ +export function makeImportIfNecessary(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string, quotePreference: QuotePreference): ImportDeclaration | undefined { + return defaultImport || namedImports && namedImports.length ? makeImport(defaultImport, namedImports, moduleSpecifier, quotePreference) : undefined; +} - export const typeKeywords: readonly SyntaxKind[] = [ - SyntaxKind.AnyKeyword, - SyntaxKind.AssertsKeyword, - SyntaxKind.BigIntKeyword, - SyntaxKind.BooleanKeyword, - SyntaxKind.FalseKeyword, - SyntaxKind.InferKeyword, - SyntaxKind.KeyOfKeyword, - SyntaxKind.NeverKeyword, - SyntaxKind.NullKeyword, - SyntaxKind.NumberKeyword, - SyntaxKind.ObjectKeyword, - SyntaxKind.ReadonlyKeyword, - SyntaxKind.StringKeyword, - SyntaxKind.SymbolKeyword, - SyntaxKind.TrueKeyword, - SyntaxKind.VoidKeyword, - SyntaxKind.UndefinedKeyword, - SyntaxKind.UniqueKeyword, - SyntaxKind.UnknownKeyword, - ]; +/** @internal */ +export function makeImport(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string | Expression, quotePreference: QuotePreference, isTypeOnly?: boolean): ImportDeclaration { + return factory.createImportDeclaration( + /*modifiers*/ undefined, + defaultImport || namedImports + ? factory.createImportClause(!!isTypeOnly, defaultImport, namedImports && namedImports.length ? factory.createNamedImports(namedImports) : undefined) + : undefined, + typeof moduleSpecifier === "string" ? makeStringLiteral(moduleSpecifier, quotePreference) : moduleSpecifier, + /*assertClause*/ undefined); +} - export function isTypeKeyword(kind: SyntaxKind): boolean { - return contains(typeKeywords, kind); - } +/** @internal */ +export function makeStringLiteral(text: string, quotePreference: QuotePreference): StringLiteral { + return factory.createStringLiteral(text, quotePreference === QuotePreference.Single); +} - export function isTypeKeywordToken(node: Node): node is Token { - return node.kind === SyntaxKind.TypeKeyword; - } +/** @internal */ +export const enum QuotePreference { Single, Double } - export function isTypeKeywordTokenOrIdentifier(node: Node) { - return isTypeKeywordToken(node) || isIdentifier(node) && node.text === "type"; - } +/** @internal */ +export function quotePreferenceFromString(str: StringLiteral, sourceFile: SourceFile): QuotePreference { + return isStringDoubleQuoted(str, sourceFile) ? QuotePreference.Double : QuotePreference.Single; +} - /** True if the symbol is for an external module, as opposed to a namespace. */ - export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean { - return !!(moduleSymbol.flags & SymbolFlags.Module) && moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote; +/** @internal */ +export function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference { + if (preferences.quotePreference && preferences.quotePreference !== "auto") { + return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double; } - - /** Returns `true` the first time it encounters a node and `false` afterwards. */ - export type NodeSeenTracker = (node: T) => boolean; - export function nodeSeenTracker(): NodeSeenTracker { - const seen: true[] = []; - return node => { - const id = getNodeId(node); - return !seen[id] && (seen[id] = true); - }; + else { + // ignore synthetic import added when importHelpers: true + const firstModuleSpecifier = sourceFile.imports && + find(sourceFile.imports, n => isStringLiteral(n) && !nodeIsSynthesized(n.parent)) as StringLiteral; + return firstModuleSpecifier ? quotePreferenceFromString(firstModuleSpecifier, sourceFile) : QuotePreference.Double; } +} - export function getSnapshotText(snap: IScriptSnapshot): string { - return snap.getText(0, snap.getLength()); +/** @internal */ +export function getQuoteFromPreference(qp: QuotePreference): string { + switch (qp) { + case QuotePreference.Single: return "'"; + case QuotePreference.Double: return '"'; + default: return Debug.assertNever(qp); } +} - export function repeatString(str: string, count: number): string { - let result = ""; - for (let i = 0; i < count; i++) { - result += str; - } - return result; - } +/** @internal */ +export function symbolNameNoDefault(symbol: Symbol): string | undefined { + const escaped = symbolEscapedNameNoDefault(symbol); + return escaped === undefined ? undefined : unescapeLeadingUnderscores(escaped); +} - export function skipConstraint(type: Type): Type { - return type.isTypeParameter() ? type.getConstraint() || type : type; +/** @internal */ +export function symbolEscapedNameNoDefault(symbol: Symbol): __String | undefined { + if (symbol.escapedName !== InternalSymbolName.Default) { + return symbol.escapedName; } - export function getNameFromPropertyName(name: PropertyName): string | undefined { - return name.kind === SyntaxKind.ComputedPropertyName - // treat computed property names where expression is string/numeric literal as just string/numeric literal - ? isStringOrNumericLiteralLike(name.expression) ? name.expression.text : undefined - : isPrivateIdentifier(name) ? idText(name) : getTextOfIdentifierOrLiteral(name); - } + return firstDefined(symbol.declarations, decl => { + const name = getNameOfDeclaration(decl); + return name && name.kind === SyntaxKind.Identifier ? name.escapedText : undefined; + }); +} - export function programContainsModules(program: Program): boolean { - return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!(s.externalModuleIndicator || s.commonJsModuleIndicator)); - } - export function programContainsEsModules(program: Program): boolean { - return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!s.externalModuleIndicator); - } - export function compilerOptionsIndicateEsModules(compilerOptions: CompilerOptions): boolean { - return !!compilerOptions.module || getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015 || !!compilerOptions.noEmit; - } +/** @internal */ +export function isModuleSpecifierLike(node: Node): node is StringLiteralLike { + return isStringLiteralLike(node) && ( + isExternalModuleReference(node.parent) || + isImportDeclaration(node.parent) || + isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ false) && node.parent.arguments[0] === node || + isImportCall(node.parent) && node.parent.arguments[0] === node); +} - export function createModuleSpecifierResolutionHost(program: Program, host: LanguageServiceHost): ModuleSpecifierResolutionHost { - // Mix in `getSymlinkCache` from Program when host doesn't have it - // in order for non-Project hosts to have a symlinks cache. - return { - fileExists: fileName => program.fileExists(fileName), - getCurrentDirectory: () => host.getCurrentDirectory(), - readFile: maybeBind(host, host.readFile), - useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), - getSymlinkCache: maybeBind(host, host.getSymlinkCache) || program.getSymlinkCache, - getModuleSpecifierCache: maybeBind(host, host.getModuleSpecifierCache), - getPackageJsonInfoCache: () => program.getModuleResolutionCache()?.getPackageJsonInfoCache(), - getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation), - redirectTargetsMap: program.redirectTargetsMap, - getProjectReferenceRedirect: fileName => program.getProjectReferenceRedirect(fileName), - isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName), - getNearestAncestorDirectoryWithPackageJson: maybeBind(host, host.getNearestAncestorDirectoryWithPackageJson), - getFileIncludeReasons: () => program.getFileIncludeReasons(), - }; - } +/** @internal */ +export type ObjectBindingElementWithoutPropertyName = BindingElement & { name: Identifier }; - export function getModuleSpecifierResolverHost(program: Program, host: LanguageServiceHost): SymbolTracker["moduleResolverHost"] { - return { - ...createModuleSpecifierResolutionHost(program, host), - getCommonSourceDirectory: () => program.getCommonSourceDirectory(), - }; - } +/** @internal */ +export function isObjectBindingElementWithoutPropertyName(bindingElement: Node): bindingElement is ObjectBindingElementWithoutPropertyName { + return isBindingElement(bindingElement) && + isObjectBindingPattern(bindingElement.parent) && + isIdentifier(bindingElement.name) && + !bindingElement.propertyName; +} - export function moduleResolutionRespectsExports(moduleResolution: ModuleResolutionKind): boolean { - return moduleResolution >= ModuleResolutionKind.Node16 && moduleResolution <= ModuleResolutionKind.NodeNext; - } +/** @internal */ +export function getPropertySymbolFromBindingElement(checker: TypeChecker, bindingElement: ObjectBindingElementWithoutPropertyName): Symbol | undefined { + const typeOfPattern = checker.getTypeAtLocation(bindingElement.parent); + return typeOfPattern && checker.getPropertyOfType(typeOfPattern, bindingElement.name.text); +} - export function moduleResolutionUsesNodeModules(moduleResolution: ModuleResolutionKind): boolean { - return moduleResolution === ModuleResolutionKind.NodeJs || moduleResolution >= ModuleResolutionKind.Node16 && moduleResolution <= ModuleResolutionKind.NodeNext; - } +/** @internal */ +export function getParentNodeInSpan(node: Node | undefined, file: SourceFile, span: TextSpan): Node | undefined { + if (!node) return undefined; - export function makeImportIfNecessary(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string, quotePreference: QuotePreference): ImportDeclaration | undefined { - return defaultImport || namedImports && namedImports.length ? makeImport(defaultImport, namedImports, moduleSpecifier, quotePreference) : undefined; - } + while (node.parent) { + if (isSourceFile(node.parent) || !spanContainsNode(span, node.parent, file)) { + return node; + } - export function makeImport(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string | Expression, quotePreference: QuotePreference, isTypeOnly?: boolean): ImportDeclaration { - return factory.createImportDeclaration( - /*modifiers*/ undefined, - defaultImport || namedImports - ? factory.createImportClause(!!isTypeOnly, defaultImport, namedImports && namedImports.length ? factory.createNamedImports(namedImports) : undefined) - : undefined, - typeof moduleSpecifier === "string" ? makeStringLiteral(moduleSpecifier, quotePreference) : moduleSpecifier, - /*assertClause*/ undefined); + node = node.parent; } +} - export function makeStringLiteral(text: string, quotePreference: QuotePreference): StringLiteral { - return factory.createStringLiteral(text, quotePreference === QuotePreference.Single); - } +function spanContainsNode(span: TextSpan, node: Node, file: SourceFile): boolean { + return textSpanContainsPosition(span, node.getStart(file)) && + node.getEnd() <= textSpanEnd(span); +} - export const enum QuotePreference { Single, Double } +/** @internal */ +export function findModifier(node: Node, kind: Modifier["kind"]): Modifier | undefined { + return canHaveModifiers(node) ? find(node.modifiers, (m): m is Modifier => m.kind === kind) : undefined; +} - export function quotePreferenceFromString(str: StringLiteral, sourceFile: SourceFile): QuotePreference { - return isStringDoubleQuoted(str, sourceFile) ? QuotePreference.Double : QuotePreference.Single; +/** @internal */ +export function insertImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, imports: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[], blankLineBetween: boolean): void { + const decl = isArray(imports) ? imports[0] : imports; + const importKindPredicate: (node: Node) => node is AnyImportOrRequireStatement = decl.kind === SyntaxKind.VariableStatement ? isRequireVariableStatement : isAnyImportSyntax; + const existingImportStatements = filter(sourceFile.statements, importKindPredicate); + const sortedNewImports = isArray(imports) ? stableSort(imports, OrganizeImports.compareImportsOrRequireStatements) : [imports]; + if (!existingImportStatements.length) { + changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween); + } + else if (existingImportStatements && OrganizeImports.importsAreSorted(existingImportStatements)) { + for (const newImport of sortedNewImports) { + const insertionIndex = OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport); + if (insertionIndex === 0) { + // If the first import is top-of-file, insert after the leading comment which is likely the header. + const options = existingImportStatements[0] === sourceFile.statements[0] ? + { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude } : {}; + changes.insertNodeBefore(sourceFile, existingImportStatements[0], newImport, /*blankLineBetween*/ false, options); + } + else { + const prevImport = existingImportStatements[insertionIndex - 1]; + changes.insertNodeAfter(sourceFile, prevImport, newImport); + } + } } - - export function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference { - if (preferences.quotePreference && preferences.quotePreference !== "auto") { - return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double; + else { + const lastExistingImport = lastOrUndefined(existingImportStatements); + if (lastExistingImport) { + changes.insertNodesAfter(sourceFile, lastExistingImport, sortedNewImports); } else { - // ignore synthetic import added when importHelpers: true - const firstModuleSpecifier = sourceFile.imports && - find(sourceFile.imports, n => isStringLiteral(n) && !nodeIsSynthesized(n.parent)) as StringLiteral; - return firstModuleSpecifier ? quotePreferenceFromString(firstModuleSpecifier, sourceFile) : QuotePreference.Double; + changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween); } } +} - export function getQuoteFromPreference(qp: QuotePreference): string { - switch (qp) { - case QuotePreference.Single: return "'"; - case QuotePreference.Double: return '"'; - default: return Debug.assertNever(qp); - } - } +/** @internal */ +export function getTypeKeywordOfTypeOnlyImport(importClause: ImportClause, sourceFile: SourceFile): Token { + Debug.assert(importClause.isTypeOnly); + return cast(importClause.getChildAt(0, sourceFile), isTypeKeywordToken); +} - export function symbolNameNoDefault(symbol: Symbol): string | undefined { - const escaped = symbolEscapedNameNoDefault(symbol); - return escaped === undefined ? undefined : unescapeLeadingUnderscores(escaped); - } +/** @internal */ +export function textSpansEqual(a: TextSpan | undefined, b: TextSpan | undefined): boolean { + return !!a && !!b && a.start === b.start && a.length === b.length; +} +/** @internal */ +export function documentSpansEqual(a: DocumentSpan, b: DocumentSpan): boolean { + return a.fileName === b.fileName && textSpansEqual(a.textSpan, b.textSpan); +} - export function symbolEscapedNameNoDefault(symbol: Symbol): __String | undefined { - if (symbol.escapedName !== InternalSymbolName.Default) { - return symbol.escapedName; +/** + * Iterates through 'array' by index and performs the callback on each element of array until the callback + * returns a truthy value, then returns that value. + * If no such value is found, the callback is applied to each element of array and undefined is returned. + * + * @internal + */ +export function forEachUnique(array: readonly T[] | undefined, callback: (element: T, index: number) => U): U | undefined { + if (array) { + for (let i = 0; i < array.length; i++) { + if (array.indexOf(array[i]) === i) { + const result = callback(array[i], i); + if (result) { + return result; + } + } } - - return firstDefined(symbol.declarations, decl => { - const name = getNameOfDeclaration(decl); - return name && name.kind === SyntaxKind.Identifier ? name.escapedText : undefined; - }); } + return undefined; +} - export function isModuleSpecifierLike(node: Node): node is StringLiteralLike { - return isStringLiteralLike(node) && ( - isExternalModuleReference(node.parent) || - isImportDeclaration(node.parent) || - isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ false) && node.parent.arguments[0] === node || - isImportCall(node.parent) && node.parent.arguments[0] === node); +/** @internal */ +export function isTextWhiteSpaceLike(text: string, startPos: number, endPos: number): boolean { + for (let i = startPos; i < endPos; i++) { + if (!isWhiteSpaceLike(text.charCodeAt(i))) { + return false; + } } - export type ObjectBindingElementWithoutPropertyName = BindingElement & { name: Identifier }; + return true; +} - export function isObjectBindingElementWithoutPropertyName(bindingElement: Node): bindingElement is ObjectBindingElementWithoutPropertyName { - return isBindingElement(bindingElement) && - isObjectBindingPattern(bindingElement.parent) && - isIdentifier(bindingElement.name) && - !bindingElement.propertyName; - } +/** @internal */ +export function getMappedLocation(location: DocumentPosition, sourceMapper: SourceMapper, fileExists: ((path: string) => boolean) | undefined): DocumentPosition | undefined { + const mapsTo = sourceMapper.tryGetSourcePosition(location); + return mapsTo && (!fileExists || fileExists(normalizePath(mapsTo.fileName)) ? mapsTo : undefined); +} - export function getPropertySymbolFromBindingElement(checker: TypeChecker, bindingElement: ObjectBindingElementWithoutPropertyName): Symbol | undefined { - const typeOfPattern = checker.getTypeAtLocation(bindingElement.parent); - return typeOfPattern && checker.getPropertyOfType(typeOfPattern, bindingElement.name.text); - } +/** @internal */ +export function getMappedDocumentSpan(documentSpan: DocumentSpan, sourceMapper: SourceMapper, fileExists?: (path: string) => boolean): DocumentSpan | undefined { + const { fileName, textSpan } = documentSpan; + const newPosition = getMappedLocation({ fileName, pos: textSpan.start }, sourceMapper, fileExists); + if (!newPosition) return undefined; + const newEndPosition = getMappedLocation({ fileName, pos: textSpan.start + textSpan.length }, sourceMapper, fileExists); + const newLength = newEndPosition + ? newEndPosition.pos - newPosition.pos + : textSpan.length; // This shouldn't happen + return { + fileName: newPosition.fileName, + textSpan: { + start: newPosition.pos, + length: newLength, + }, + originalFileName: documentSpan.fileName, + originalTextSpan: documentSpan.textSpan, + contextSpan: getMappedContextSpan(documentSpan, sourceMapper, fileExists), + originalContextSpan: documentSpan.contextSpan + }; +} - export function getParentNodeInSpan(node: Node | undefined, file: SourceFile, span: TextSpan): Node | undefined { - if (!node) return undefined; +/** @internal */ +export function getMappedContextSpan(documentSpan: DocumentSpan, sourceMapper: SourceMapper, fileExists?: (path: string) => boolean): TextSpan | undefined { + const contextSpanStart = documentSpan.contextSpan && getMappedLocation( + { fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start }, + sourceMapper, + fileExists + ); + const contextSpanEnd = documentSpan.contextSpan && getMappedLocation( + { fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start + documentSpan.contextSpan.length }, + sourceMapper, + fileExists + ); + return contextSpanStart && contextSpanEnd ? + { start: contextSpanStart.pos, length: contextSpanEnd.pos - contextSpanStart.pos } : + undefined; +} - while (node.parent) { - if (isSourceFile(node.parent) || !spanContainsNode(span, node.parent, file)) { - return node; - } +// #endregion - node = node.parent; - } - } +// Display-part writer helpers +// #region +/** @internal */ +export function isFirstDeclarationOfSymbolParameter(symbol: Symbol) { + const declaration = symbol.declarations ? firstOrUndefined(symbol.declarations) : undefined; + return !!findAncestor(declaration, n => + isParameter(n) ? true : isBindingElement(n) || isObjectBindingPattern(n) || isArrayBindingPattern(n) ? false : "quit"); +} - function spanContainsNode(span: TextSpan, node: Node, file: SourceFile): boolean { - return textSpanContainsPosition(span, node.getStart(file)) && - node.getEnd() <= textSpanEnd(span); +const displayPartWriter = getDisplayPartWriter(); +function getDisplayPartWriter(): DisplayPartsSymbolWriter { + const absoluteMaximumLength = defaultMaximumTruncationLength * 10; // A hard cutoff to avoid overloading the messaging channel in worst-case scenarios + let displayParts: SymbolDisplayPart[]; + let lineStart: boolean; + let indent: number; + let length: number; + + resetWriter(); + const unknownWrite = (text: string) => writeKind(text, SymbolDisplayPartKind.text); + return { + displayParts: () => { + const finalText = displayParts.length && displayParts[displayParts.length - 1].text; + if (length > absoluteMaximumLength && finalText && finalText !== "...") { + if (!isWhiteSpaceLike(finalText.charCodeAt(finalText.length - 1))) { + displayParts.push(displayPart(" ", SymbolDisplayPartKind.space)); + } + displayParts.push(displayPart("...", SymbolDisplayPartKind.punctuation)); + } + return displayParts; + }, + writeKeyword: text => writeKind(text, SymbolDisplayPartKind.keyword), + writeOperator: text => writeKind(text, SymbolDisplayPartKind.operator), + writePunctuation: text => writeKind(text, SymbolDisplayPartKind.punctuation), + writeTrailingSemicolon: text => writeKind(text, SymbolDisplayPartKind.punctuation), + writeSpace: text => writeKind(text, SymbolDisplayPartKind.space), + writeStringLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), + writeParameter: text => writeKind(text, SymbolDisplayPartKind.parameterName), + writeProperty: text => writeKind(text, SymbolDisplayPartKind.propertyName), + writeLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), + writeSymbol, + writeLine, + write: unknownWrite, + writeComment: unknownWrite, + getText: () => "", + getTextPos: () => 0, + getColumn: () => 0, + getLine: () => 0, + isAtStartOfLine: () => false, + hasTrailingWhitespace: () => false, + hasTrailingComment: () => false, + rawWrite: notImplemented, + getIndent: () => indent, + increaseIndent: () => { indent++; }, + decreaseIndent: () => { indent--; }, + clear: resetWriter, + trackSymbol: () => false, + reportInaccessibleThisError: noop, + reportInaccessibleUniqueSymbolError: noop, + reportPrivateInBaseOfClassExpression: noop, + }; + + function writeIndent() { + if (length > absoluteMaximumLength) return; + if (lineStart) { + const indentString = getIndentString(indent); + if (indentString) { + length += indentString.length; + displayParts.push(displayPart(indentString, SymbolDisplayPartKind.space)); + } + lineStart = false; + } + } + + function writeKind(text: string, kind: SymbolDisplayPartKind) { + if (length > absoluteMaximumLength) return; + writeIndent(); + length += text.length; + displayParts.push(displayPart(text, kind)); + } + + function writeSymbol(text: string, symbol: Symbol) { + if (length > absoluteMaximumLength) return; + writeIndent(); + length += text.length; + displayParts.push(symbolPart(text, symbol)); + } + + function writeLine() { + if (length > absoluteMaximumLength) return; + length += 1; + displayParts.push(lineBreakPart()); + lineStart = true; + } + + function resetWriter() { + displayParts = []; + lineStart = true; + indent = 0; + length = 0; } +} - export function findModifier(node: Node, kind: Modifier["kind"]): Modifier | undefined { - return canHaveModifiers(node) ? find(node.modifiers, (m): m is Modifier => m.kind === kind) : undefined; - } +/** @internal */ +export function symbolPart(text: string, symbol: Symbol) { + return displayPart(text, displayPartKind(symbol)); - export function insertImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, imports: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[], blankLineBetween: boolean): void { - const decl = isArray(imports) ? imports[0] : imports; - const importKindPredicate: (node: Node) => node is AnyImportOrRequireStatement = decl.kind === SyntaxKind.VariableStatement ? isRequireVariableStatement : isAnyImportSyntax; - const existingImportStatements = filter(sourceFile.statements, importKindPredicate); - const sortedNewImports = isArray(imports) ? stableSort(imports, OrganizeImports.compareImportsOrRequireStatements) : [imports]; - if (!existingImportStatements.length) { - changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween); - } - else if (existingImportStatements && OrganizeImports.importsAreSorted(existingImportStatements)) { - for (const newImport of sortedNewImports) { - const insertionIndex = OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport); - if (insertionIndex === 0) { - // If the first import is top-of-file, insert after the leading comment which is likely the header. - const options = existingImportStatements[0] === sourceFile.statements[0] ? - { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude } : {}; - changes.insertNodeBefore(sourceFile, existingImportStatements[0], newImport, /*blankLineBetween*/ false, options); - } - else { - const prevImport = existingImportStatements[insertionIndex - 1]; - changes.insertNodeAfter(sourceFile, prevImport, newImport); - } - } - } - else { - const lastExistingImport = lastOrUndefined(existingImportStatements); - if (lastExistingImport) { - changes.insertNodesAfter(sourceFile, lastExistingImport, sortedNewImports); - } - else { - changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween); - } + function displayPartKind(symbol: Symbol): SymbolDisplayPartKind { + const flags = symbol.flags; + + if (flags & SymbolFlags.Variable) { + return isFirstDeclarationOfSymbolParameter(symbol) ? SymbolDisplayPartKind.parameterName : SymbolDisplayPartKind.localName; } - } + if (flags & SymbolFlags.Property) return SymbolDisplayPartKind.propertyName; + if (flags & SymbolFlags.GetAccessor) return SymbolDisplayPartKind.propertyName; + if (flags & SymbolFlags.SetAccessor) return SymbolDisplayPartKind.propertyName; + if (flags & SymbolFlags.EnumMember) return SymbolDisplayPartKind.enumMemberName; + if (flags & SymbolFlags.Function) return SymbolDisplayPartKind.functionName; + if (flags & SymbolFlags.Class) return SymbolDisplayPartKind.className; + if (flags & SymbolFlags.Interface) return SymbolDisplayPartKind.interfaceName; + if (flags & SymbolFlags.Enum) return SymbolDisplayPartKind.enumName; + if (flags & SymbolFlags.Module) return SymbolDisplayPartKind.moduleName; + if (flags & SymbolFlags.Method) return SymbolDisplayPartKind.methodName; + if (flags & SymbolFlags.TypeParameter) return SymbolDisplayPartKind.typeParameterName; + if (flags & SymbolFlags.TypeAlias) return SymbolDisplayPartKind.aliasName; + if (flags & SymbolFlags.Alias) return SymbolDisplayPartKind.aliasName; - export function getTypeKeywordOfTypeOnlyImport(importClause: ImportClause, sourceFile: SourceFile): Token { - Debug.assert(importClause.isTypeOnly); - return cast(importClause.getChildAt(0, sourceFile), isTypeKeywordToken); + return SymbolDisplayPartKind.text; } +} - export function textSpansEqual(a: TextSpan | undefined, b: TextSpan | undefined): boolean { - return !!a && !!b && a.start === b.start && a.length === b.length; - } - export function documentSpansEqual(a: DocumentSpan, b: DocumentSpan): boolean { - return a.fileName === b.fileName && textSpansEqual(a.textSpan, b.textSpan); - } +/** @internal */ +export function displayPart(text: string, kind: SymbolDisplayPartKind): SymbolDisplayPart { + return { text, kind: SymbolDisplayPartKind[kind] }; +} - /** - * Iterates through 'array' by index and performs the callback on each element of array until the callback - * returns a truthy value, then returns that value. - * If no such value is found, the callback is applied to each element of array and undefined is returned. - */ - export function forEachUnique(array: readonly T[] | undefined, callback: (element: T, index: number) => U): U | undefined { - if (array) { - for (let i = 0; i < array.length; i++) { - if (array.indexOf(array[i]) === i) { - const result = callback(array[i], i); - if (result) { - return result; - } - } - } - } - return undefined; - } +/** @internal */ +export function spacePart() { + return displayPart(" ", SymbolDisplayPartKind.space); +} - export function isTextWhiteSpaceLike(text: string, startPos: number, endPos: number): boolean { - for (let i = startPos; i < endPos; i++) { - if (!isWhiteSpaceLike(text.charCodeAt(i))) { - return false; - } - } +/** @internal */ +export function keywordPart(kind: SyntaxKind) { + return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.keyword); +} - return true; - } +/** @internal */ +export function punctuationPart(kind: SyntaxKind) { + return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.punctuation); +} - export function getMappedLocation(location: DocumentPosition, sourceMapper: SourceMapper, fileExists: ((path: string) => boolean) | undefined): DocumentPosition | undefined { - const mapsTo = sourceMapper.tryGetSourcePosition(location); - return mapsTo && (!fileExists || fileExists(normalizePath(mapsTo.fileName)) ? mapsTo : undefined); - } - - export function getMappedDocumentSpan(documentSpan: DocumentSpan, sourceMapper: SourceMapper, fileExists?: (path: string) => boolean): DocumentSpan | undefined { - const { fileName, textSpan } = documentSpan; - const newPosition = getMappedLocation({ fileName, pos: textSpan.start }, sourceMapper, fileExists); - if (!newPosition) return undefined; - const newEndPosition = getMappedLocation({ fileName, pos: textSpan.start + textSpan.length }, sourceMapper, fileExists); - const newLength = newEndPosition - ? newEndPosition.pos - newPosition.pos - : textSpan.length; // This shouldn't happen - return { - fileName: newPosition.fileName, - textSpan: { - start: newPosition.pos, - length: newLength, - }, - originalFileName: documentSpan.fileName, - originalTextSpan: documentSpan.textSpan, - contextSpan: getMappedContextSpan(documentSpan, sourceMapper, fileExists), - originalContextSpan: documentSpan.contextSpan - }; - } - - export function getMappedContextSpan(documentSpan: DocumentSpan, sourceMapper: SourceMapper, fileExists?: (path: string) => boolean): TextSpan | undefined { - const contextSpanStart = documentSpan.contextSpan && getMappedLocation( - { fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start }, - sourceMapper, - fileExists - ); - const contextSpanEnd = documentSpan.contextSpan && getMappedLocation( - { fileName: documentSpan.fileName, pos: documentSpan.contextSpan.start + documentSpan.contextSpan.length }, - sourceMapper, - fileExists - ); - return contextSpanStart && contextSpanEnd ? - { start: contextSpanStart.pos, length: contextSpanEnd.pos - contextSpanStart.pos } : - undefined; - } - - // #endregion - - // Display-part writer helpers - // #region - export function isFirstDeclarationOfSymbolParameter(symbol: Symbol) { - const declaration = symbol.declarations ? firstOrUndefined(symbol.declarations) : undefined; - return !!findAncestor(declaration, n => - isParameter(n) ? true : isBindingElement(n) || isObjectBindingPattern(n) || isArrayBindingPattern(n) ? false : "quit"); - } - - const displayPartWriter = getDisplayPartWriter(); - function getDisplayPartWriter(): DisplayPartsSymbolWriter { - const absoluteMaximumLength = defaultMaximumTruncationLength * 10; // A hard cutoff to avoid overloading the messaging channel in worst-case scenarios - let displayParts: SymbolDisplayPart[]; - let lineStart: boolean; - let indent: number; - let length: number; - - resetWriter(); - const unknownWrite = (text: string) => writeKind(text, SymbolDisplayPartKind.text); - return { - displayParts: () => { - const finalText = displayParts.length && displayParts[displayParts.length - 1].text; - if (length > absoluteMaximumLength && finalText && finalText !== "...") { - if (!isWhiteSpaceLike(finalText.charCodeAt(finalText.length - 1))) { - displayParts.push(displayPart(" ", SymbolDisplayPartKind.space)); - } - displayParts.push(displayPart("...", SymbolDisplayPartKind.punctuation)); - } - return displayParts; - }, - writeKeyword: text => writeKind(text, SymbolDisplayPartKind.keyword), - writeOperator: text => writeKind(text, SymbolDisplayPartKind.operator), - writePunctuation: text => writeKind(text, SymbolDisplayPartKind.punctuation), - writeTrailingSemicolon: text => writeKind(text, SymbolDisplayPartKind.punctuation), - writeSpace: text => writeKind(text, SymbolDisplayPartKind.space), - writeStringLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), - writeParameter: text => writeKind(text, SymbolDisplayPartKind.parameterName), - writeProperty: text => writeKind(text, SymbolDisplayPartKind.propertyName), - writeLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), - writeSymbol, - writeLine, - write: unknownWrite, - writeComment: unknownWrite, - getText: () => "", - getTextPos: () => 0, - getColumn: () => 0, - getLine: () => 0, - isAtStartOfLine: () => false, - hasTrailingWhitespace: () => false, - hasTrailingComment: () => false, - rawWrite: notImplemented, - getIndent: () => indent, - increaseIndent: () => { indent++; }, - decreaseIndent: () => { indent--; }, - clear: resetWriter, - trackSymbol: () => false, - reportInaccessibleThisError: noop, - reportInaccessibleUniqueSymbolError: noop, - reportPrivateInBaseOfClassExpression: noop, - }; - - function writeIndent() { - if (length > absoluteMaximumLength) return; - if (lineStart) { - const indentString = getIndentString(indent); - if (indentString) { - length += indentString.length; - displayParts.push(displayPart(indentString, SymbolDisplayPartKind.space)); - } - lineStart = false; - } - } +/** @internal */ +export function operatorPart(kind: SyntaxKind) { + return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.operator); +} - function writeKind(text: string, kind: SymbolDisplayPartKind) { - if (length > absoluteMaximumLength) return; - writeIndent(); - length += text.length; - displayParts.push(displayPart(text, kind)); - } +/** @internal */ +export function parameterNamePart(text: string) { + return displayPart(text, SymbolDisplayPartKind.parameterName); +} - function writeSymbol(text: string, symbol: Symbol) { - if (length > absoluteMaximumLength) return; - writeIndent(); - length += text.length; - displayParts.push(symbolPart(text, symbol)); - } +/** @internal */ +export function propertyNamePart(text: string) { + return displayPart(text, SymbolDisplayPartKind.propertyName); +} - function writeLine() { - if (length > absoluteMaximumLength) return; - length += 1; - displayParts.push(lineBreakPart()); - lineStart = true; - } +/** @internal */ +export function textOrKeywordPart(text: string) { + const kind = stringToToken(text); + return kind === undefined + ? textPart(text) + : keywordPart(kind); +} - function resetWriter() { - displayParts = []; - lineStart = true; - indent = 0; - length = 0; - } - } +/** @internal */ +export function textPart(text: string) { + return displayPart(text, SymbolDisplayPartKind.text); +} - export function symbolPart(text: string, symbol: Symbol) { - return displayPart(text, displayPartKind(symbol)); +/** @internal */ +export function typeAliasNamePart(text: string) { + return displayPart(text, SymbolDisplayPartKind.aliasName); +} - function displayPartKind(symbol: Symbol): SymbolDisplayPartKind { - const flags = symbol.flags; +/** @internal */ +export function typeParameterNamePart(text: string) { + return displayPart(text, SymbolDisplayPartKind.typeParameterName); +} - if (flags & SymbolFlags.Variable) { - return isFirstDeclarationOfSymbolParameter(symbol) ? SymbolDisplayPartKind.parameterName : SymbolDisplayPartKind.localName; - } - if (flags & SymbolFlags.Property) return SymbolDisplayPartKind.propertyName; - if (flags & SymbolFlags.GetAccessor) return SymbolDisplayPartKind.propertyName; - if (flags & SymbolFlags.SetAccessor) return SymbolDisplayPartKind.propertyName; - if (flags & SymbolFlags.EnumMember) return SymbolDisplayPartKind.enumMemberName; - if (flags & SymbolFlags.Function) return SymbolDisplayPartKind.functionName; - if (flags & SymbolFlags.Class) return SymbolDisplayPartKind.className; - if (flags & SymbolFlags.Interface) return SymbolDisplayPartKind.interfaceName; - if (flags & SymbolFlags.Enum) return SymbolDisplayPartKind.enumName; - if (flags & SymbolFlags.Module) return SymbolDisplayPartKind.moduleName; - if (flags & SymbolFlags.Method) return SymbolDisplayPartKind.methodName; - if (flags & SymbolFlags.TypeParameter) return SymbolDisplayPartKind.typeParameterName; - if (flags & SymbolFlags.TypeAlias) return SymbolDisplayPartKind.aliasName; - if (flags & SymbolFlags.Alias) return SymbolDisplayPartKind.aliasName; +/** @internal */ +export function linkTextPart(text: string) { + return displayPart(text, SymbolDisplayPartKind.linkText); +} - return SymbolDisplayPartKind.text; - } - } +/** @internal */ +export function linkNamePart(text: string, target: Declaration): JSDocLinkDisplayPart { + return { + text, + kind: SymbolDisplayPartKind[SymbolDisplayPartKind.linkName], + target: { + fileName: getSourceFileOfNode(target).fileName, + textSpan: createTextSpanFromNode(target), + }, + }; +} - export function displayPart(text: string, kind: SymbolDisplayPartKind): SymbolDisplayPart { - return { text, kind: SymbolDisplayPartKind[kind] }; - } +/** @internal */ +export function linkPart(text: string) { + return displayPart(text, SymbolDisplayPartKind.link); +} - export function spacePart() { - return displayPart(" ", SymbolDisplayPartKind.space); +/** @internal */ +export function buildLinkParts(link: JSDocLink | JSDocLinkCode | JSDocLinkPlain, checker?: TypeChecker): SymbolDisplayPart[] { + const prefix = isJSDocLink(link) ? "link" + : isJSDocLinkCode(link) ? "linkcode" + : "linkplain"; + const parts = [linkPart(`{@${prefix} `)]; + if (!link.name) { + if (link.text) { + parts.push(linkTextPart(link.text)); + } + } + else { + const symbol = checker?.getSymbolAtLocation(link.name); + const suffix = findLinkNameEnd(link.text); + const name = getTextOfNode(link.name) + link.text.slice(0, suffix); + const text = skipSeparatorFromLinkText(link.text.slice(suffix)); + const decl = symbol?.valueDeclaration || symbol?.declarations?.[0]; + if (decl) { + parts.push(linkNamePart(name, decl)); + if (text) parts.push(linkTextPart(text)); + } + else { + parts.push(linkTextPart(name + (suffix || text.indexOf("://") === 0 ? "" : " ") + text)); + } } + parts.push(linkPart("}")); + return parts; +} - export function keywordPart(kind: SyntaxKind) { - return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.keyword); +function skipSeparatorFromLinkText(text: string) { + let pos = 0; + if (text.charCodeAt(pos++) === CharacterCodes.bar) { + while (pos < text.length && text.charCodeAt(pos) === CharacterCodes.space) pos++; + return text.slice(pos); } + return text; +} - export function punctuationPart(kind: SyntaxKind) { - return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.punctuation); - } +function findLinkNameEnd(text: string) { + if (text.indexOf("()") === 0) return 2; + if (text[0] !== "<") return 0; + let brackets = 0; + let i = 0; + while (i < text.length) { + if (text[i] === "<") brackets++; + if (text[i] === ">") brackets--; + i++; + if (!brackets) return i; + } + return 0; +} - export function operatorPart(kind: SyntaxKind) { - return displayPart(tokenToString(kind)!, SymbolDisplayPartKind.operator); - } +const carriageReturnLineFeed = "\r\n"; +/** + * The default is CRLF. + * + * @internal + */ +export function getNewLineOrDefaultFromHost(host: FormattingHost, formatSettings?: FormatCodeSettings) { + return formatSettings?.newLineCharacter || + host.getNewLine?.() || + carriageReturnLineFeed; +} - export function parameterNamePart(text: string) { - return displayPart(text, SymbolDisplayPartKind.parameterName); - } +/** @internal */ +export function lineBreakPart() { + return displayPart("\n", SymbolDisplayPartKind.lineBreak); +} - export function propertyNamePart(text: string) { - return displayPart(text, SymbolDisplayPartKind.propertyName); +/** @internal */ +export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbolWriter) => void): SymbolDisplayPart[] { + try { + writeDisplayParts(displayPartWriter); + return displayPartWriter.displayParts(); } - - export function textOrKeywordPart(text: string) { - const kind = stringToToken(text); - return kind === undefined - ? textPart(text) - : keywordPart(kind); + finally { + displayPartWriter.clear(); } +} - export function textPart(text: string) { - return displayPart(text, SymbolDisplayPartKind.text); - } +/** @internal */ +export function typeToDisplayParts(typechecker: TypeChecker, type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None): SymbolDisplayPart[] { + return mapToDisplayParts(writer => { + typechecker.writeType(type, enclosingDeclaration, flags | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer); + }); +} - export function typeAliasNamePart(text: string) { - return displayPart(text, SymbolDisplayPartKind.aliasName); - } +/** @internal */ +export function symbolToDisplayParts(typeChecker: TypeChecker, symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.None): SymbolDisplayPart[] { + return mapToDisplayParts(writer => { + typeChecker.writeSymbol(symbol, enclosingDeclaration, meaning, flags | SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope, writer); + }); +} - export function typeParameterNamePart(text: string) { - return displayPart(text, SymbolDisplayPartKind.typeParameterName); - } +/** @internal */ +export function signatureToDisplayParts(typechecker: TypeChecker, signature: Signature, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None): SymbolDisplayPart[] { + flags |= TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.WriteTypeArgumentsOfSignature | TypeFormatFlags.OmitParameterModifiers; + return mapToDisplayParts(writer => { + typechecker.writeSignature(signature, enclosingDeclaration, flags, /*signatureKind*/ undefined, writer); + }); +} - export function linkTextPart(text: string) { - return displayPart(text, SymbolDisplayPartKind.linkText); - } +/** @internal */ +export function nodeToDisplayParts(node: Node, enclosingDeclaration: Node): SymbolDisplayPart[] { + const file = enclosingDeclaration.getSourceFile(); + return mapToDisplayParts(writer => { + const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); + printer.writeNode(EmitHint.Unspecified, node, file, writer); + }); +} - export function linkNamePart(text: string, target: Declaration): JSDocLinkDisplayPart { - return { - text, - kind: SymbolDisplayPartKind[SymbolDisplayPartKind.linkName], - target: { - fileName: getSourceFileOfNode(target).fileName, - textSpan: createTextSpanFromNode(target), - }, - }; - } +/** @internal */ +export function isImportOrExportSpecifierName(location: Node): location is Identifier { + return !!location.parent && isImportOrExportSpecifier(location.parent) && location.parent.propertyName === location; +} - export function linkPart(text: string) { - return displayPart(text, SymbolDisplayPartKind.link); - } +/** @internal */ +export function getScriptKind(fileName: string, host: LanguageServiceHost): ScriptKind { + // First check to see if the script kind was specified by the host. Chances are the host + // may override the default script kind for the file extension. + return ensureScriptKind(fileName, host.getScriptKind && host.getScriptKind(fileName)); +} - export function buildLinkParts(link: JSDocLink | JSDocLinkCode | JSDocLinkPlain, checker?: TypeChecker): SymbolDisplayPart[] { - const prefix = isJSDocLink(link) ? "link" - : isJSDocLinkCode(link) ? "linkcode" - : "linkplain"; - const parts = [linkPart(`{@${prefix} `)]; - if (!link.name) { - if (link.text) { - parts.push(linkTextPart(link.text)); - } +/** @internal */ +export function getSymbolTarget(symbol: Symbol, checker: TypeChecker): Symbol { + let next: Symbol = symbol; + while (isAliasSymbol(next) || (isTransientSymbol(next) && next.target)) { + if (isTransientSymbol(next) && next.target) { + next = next.target; } else { - const symbol = checker?.getSymbolAtLocation(link.name); - const suffix = findLinkNameEnd(link.text); - const name = getTextOfNode(link.name) + link.text.slice(0, suffix); - const text = skipSeparatorFromLinkText(link.text.slice(suffix)); - const decl = symbol?.valueDeclaration || symbol?.declarations?.[0]; - if (decl) { - parts.push(linkNamePart(name, decl)); - if (text) parts.push(linkTextPart(text)); - } - else { - parts.push(linkTextPart(name + (suffix || text.indexOf("://") === 0 ? "" : " ") + text)); - } + next = skipAlias(next, checker); } - parts.push(linkPart("}")); - return parts; } + return next; +} - function skipSeparatorFromLinkText(text: string) { - let pos = 0; - if (text.charCodeAt(pos++) === CharacterCodes.bar) { - while (pos < text.length && text.charCodeAt(pos) === CharacterCodes.space) pos++; - return text.slice(pos); - } - return text; - } +function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol { + return (symbol.flags & SymbolFlags.Transient) !== 0; +} - function findLinkNameEnd(text: string) { - if (text.indexOf("()") === 0) return 2; - if (text[0] !== "<") return 0; - let brackets = 0; - let i = 0; - while (i < text.length) { - if (text[i] === "<") brackets++; - if (text[i] === ">") brackets--; - i++; - if (!brackets) return i; - } - return 0; - } +function isAliasSymbol(symbol: Symbol): boolean { + return (symbol.flags & SymbolFlags.Alias) !== 0; +} - const carriageReturnLineFeed = "\r\n"; - /** - * The default is CRLF. - */ - export function getNewLineOrDefaultFromHost(host: FormattingHost, formatSettings?: FormatCodeSettings) { - return formatSettings?.newLineCharacter || - host.getNewLine?.() || - carriageReturnLineFeed; - } +/** @internal */ +export function getUniqueSymbolId(symbol: Symbol, checker: TypeChecker) { + return getSymbolId(skipAlias(symbol, checker)); +} - export function lineBreakPart() { - return displayPart("\n", SymbolDisplayPartKind.lineBreak); +/** @internal */ +export function getFirstNonSpaceCharacterPosition(text: string, position: number) { + while (isWhiteSpaceLike(text.charCodeAt(position))) { + position += 1; } + return position; +} - export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbolWriter) => void): SymbolDisplayPart[] { - try { - writeDisplayParts(displayPartWriter); - return displayPartWriter.displayParts(); - } - finally { - displayPartWriter.clear(); - } +/** @internal */ +export function getPrecedingNonSpaceCharacterPosition(text: string, position: number) { + while (position > -1 && isWhiteSpaceSingleLine(text.charCodeAt(position))) { + position -= 1; } + return position + 1; +} - export function typeToDisplayParts(typechecker: TypeChecker, type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None): SymbolDisplayPart[] { - return mapToDisplayParts(writer => { - typechecker.writeType(type, enclosingDeclaration, flags | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer); - }); - } +/** + * Creates a deep, memberwise clone of a node with no source map location. + * + * WARNING: This is an expensive operation and is only intended to be used in refactorings + * and code fixes (because those are triggered by explicit user actions). + * + * @internal + */ +export function getSynthesizedDeepClone(node: T, includeTrivia = true): T { + const clone = node && getSynthesizedDeepCloneWorker(node); + if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); + return clone; +} - export function symbolToDisplayParts(typeChecker: TypeChecker, symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.None): SymbolDisplayPart[] { - return mapToDisplayParts(writer => { - typeChecker.writeSymbol(symbol, enclosingDeclaration, meaning, flags | SymbolFormatFlags.UseAliasDefinedOutsideCurrentScope, writer); - }); +/** @internal */ +export function getSynthesizedDeepCloneWithReplacements( + node: T, + includeTrivia: boolean, + replaceNode: (node: Node) => Node | undefined +): T { + let clone = replaceNode(node); + if (clone) { + setOriginalNode(clone, node); } - - export function signatureToDisplayParts(typechecker: TypeChecker, signature: Signature, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.None): SymbolDisplayPart[] { - flags |= TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.WriteTypeArgumentsOfSignature | TypeFormatFlags.OmitParameterModifiers; - return mapToDisplayParts(writer => { - typechecker.writeSignature(signature, enclosingDeclaration, flags, /*signatureKind*/ undefined, writer); - }); + else { + clone = getSynthesizedDeepCloneWorker(node as NonNullable, replaceNode); } - export function nodeToDisplayParts(node: Node, enclosingDeclaration: Node): SymbolDisplayPart[] { - const file = enclosingDeclaration.getSourceFile(); - return mapToDisplayParts(writer => { - const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); - printer.writeNode(EmitHint.Unspecified, node, file, writer); - }); - } + if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); + return clone as T; +} - export function isImportOrExportSpecifierName(location: Node): location is Identifier { - return !!location.parent && isImportOrExportSpecifier(location.parent) && location.parent.propertyName === location; - } +function getSynthesizedDeepCloneWorker(node: T, replaceNode?: (node: Node) => Node | undefined): T { + const nodeClone: (n: T) => T = replaceNode + ? n => getSynthesizedDeepCloneWithReplacements(n, /*includeTrivia*/ true, replaceNode) + : getSynthesizedDeepClone; + const nodesClone: (ns: NodeArray) => NodeArray = replaceNode + ? ns => ns && getSynthesizedDeepClonesWithReplacements(ns, /*includeTrivia*/ true, replaceNode) + : ns => ns && getSynthesizedDeepClones(ns); + const visited = + visitEachChild(node, nodeClone, nullTransformationContext, nodesClone, nodeClone); + + if (visited === node) { + // This only happens for leaf nodes - internal nodes always see their children change. + const clone = + isStringLiteral(node) ? setOriginalNode(factory.createStringLiteralFromNode(node), node) as Node as T : + isNumericLiteral(node) ? setOriginalNode(factory.createNumericLiteral(node.text, node.numericLiteralFlags), node) as Node as T : + factory.cloneNode(node); + return setTextRange(clone, node); + } + + // PERF: As an optimization, rather than calling factory.cloneNode, we'll update + // the new node created by visitEachChild with the extra changes factory.cloneNode + // would have made. + (visited as Mutable).parent = undefined!; + return visited; +} - export function getScriptKind(fileName: string, host: LanguageServiceHost): ScriptKind { - // First check to see if the script kind was specified by the host. Chances are the host - // may override the default script kind for the file extension. - return ensureScriptKind(fileName, host.getScriptKind && host.getScriptKind(fileName)); - } +/** @internal */ +export function getSynthesizedDeepClones(nodes: NodeArray, includeTrivia?: boolean): NodeArray; +/** @internal */ +export function getSynthesizedDeepClones(nodes: NodeArray | undefined, includeTrivia?: boolean): NodeArray | undefined; +/** @internal */ +export function getSynthesizedDeepClones(nodes: NodeArray | undefined, includeTrivia = true): NodeArray | undefined { + return nodes && factory.createNodeArray(nodes.map(n => getSynthesizedDeepClone(n, includeTrivia)), nodes.hasTrailingComma); +} - export function getSymbolTarget(symbol: Symbol, checker: TypeChecker): Symbol { - let next: Symbol = symbol; - while (isAliasSymbol(next) || (isTransientSymbol(next) && next.target)) { - if (isTransientSymbol(next) && next.target) { - next = next.target; - } - else { - next = skipAlias(next, checker); - } - } - return next; - } +/** @internal */ +export function getSynthesizedDeepClonesWithReplacements( + nodes: NodeArray, + includeTrivia: boolean, + replaceNode: (node: Node) => Node | undefined +): NodeArray { + return factory.createNodeArray(nodes.map(n => getSynthesizedDeepCloneWithReplacements(n, includeTrivia, replaceNode)), nodes.hasTrailingComma); +} - function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol { - return (symbol.flags & SymbolFlags.Transient) !== 0; - } +/** + * Sets EmitFlags to suppress leading and trailing trivia on the node. + * + * @internal + */ +export function suppressLeadingAndTrailingTrivia(node: Node) { + suppressLeadingTrivia(node); + suppressTrailingTrivia(node); +} - function isAliasSymbol(symbol: Symbol): boolean { - return (symbol.flags & SymbolFlags.Alias) !== 0; - } +/** + * Sets EmitFlags to suppress leading trivia on the node. + * + * @internal + */ +export function suppressLeadingTrivia(node: Node) { + addEmitFlagsRecursively(node, EmitFlags.NoLeadingComments, getFirstChild); +} - export function getUniqueSymbolId(symbol: Symbol, checker: TypeChecker) { - return getSymbolId(skipAlias(symbol, checker)); - } +/** + * Sets EmitFlags to suppress trailing trivia on the node. + * + * @internal + */ +export function suppressTrailingTrivia(node: Node) { + addEmitFlagsRecursively(node, EmitFlags.NoTrailingComments, getLastChild); +} - export function getFirstNonSpaceCharacterPosition(text: string, position: number) { - while (isWhiteSpaceLike(text.charCodeAt(position))) { - position += 1; - } - return position; +/** @internal */ +export function copyComments(sourceNode: Node, targetNode: Node) { + const sourceFile = sourceNode.getSourceFile(); + const text = sourceFile.text; + if (hasLeadingLineBreak(sourceNode, text)) { + copyLeadingComments(sourceNode, targetNode, sourceFile); } - - export function getPrecedingNonSpaceCharacterPosition(text: string, position: number) { - while (position > -1 && isWhiteSpaceSingleLine(text.charCodeAt(position))) { - position -= 1; - } - return position + 1; + else { + copyTrailingAsLeadingComments(sourceNode, targetNode, sourceFile); } + copyTrailingComments(sourceNode, targetNode, sourceFile); +} - /** - * Creates a deep, memberwise clone of a node with no source map location. - * - * WARNING: This is an expensive operation and is only intended to be used in refactorings - * and code fixes (because those are triggered by explicit user actions). - */ - export function getSynthesizedDeepClone(node: T, includeTrivia = true): T { - const clone = node && getSynthesizedDeepCloneWorker(node); - if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); - return clone; +function hasLeadingLineBreak(node: Node, text: string) { + const start = node.getFullStart(); + const end = node.getStart(); + for (let i = start; i < end; i++) { + if (text.charCodeAt(i) === CharacterCodes.lineFeed) return true; } + return false; +} - export function getSynthesizedDeepCloneWithReplacements( - node: T, - includeTrivia: boolean, - replaceNode: (node: Node) => Node | undefined - ): T { - let clone = replaceNode(node); - if (clone) { - setOriginalNode(clone, node); - } - else { - clone = getSynthesizedDeepCloneWorker(node as NonNullable, replaceNode); - } +function addEmitFlagsRecursively(node: Node, flag: EmitFlags, getChild: (n: Node) => Node | undefined) { + addEmitFlags(node, flag); + const child = getChild(node); + if (child) addEmitFlagsRecursively(child, flag, getChild); +} - if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone); - return clone as T; - } +function getFirstChild(node: Node): Node | undefined { + return node.forEachChild(child => child); +} - function getSynthesizedDeepCloneWorker(node: T, replaceNode?: (node: Node) => Node | undefined): T { - const nodeClone: (n: T) => T = replaceNode - ? n => getSynthesizedDeepCloneWithReplacements(n, /*includeTrivia*/ true, replaceNode) - : getSynthesizedDeepClone; - const nodesClone: (ns: NodeArray) => NodeArray = replaceNode - ? ns => ns && getSynthesizedDeepClonesWithReplacements(ns, /*includeTrivia*/ true, replaceNode) - : ns => ns && getSynthesizedDeepClones(ns); - const visited = - visitEachChild(node, nodeClone, nullTransformationContext, nodesClone, nodeClone); +/** @internal */ +export function getUniqueName(baseName: string, sourceFile: SourceFile): string { + let nameText = baseName; + for (let i = 1; !isFileLevelUniqueName(sourceFile, nameText); i++) { + nameText = `${baseName}_${i}`; + } + return nameText; +} - if (visited === node) { - // This only happens for leaf nodes - internal nodes always see their children change. - const clone = - isStringLiteral(node) ? setOriginalNode(factory.createStringLiteralFromNode(node), node) as Node as T : - isNumericLiteral(node) ? setOriginalNode(factory.createNumericLiteral(node.text, node.numericLiteralFlags), node) as Node as T : - factory.cloneNode(node); - return setTextRange(clone, node); +/** + * @return The index of the (only) reference to the extracted symbol. We want the cursor + * to be on the reference, rather than the declaration, because it's closer to where the + * user was before extracting it. + * + * @internal + */ +export function getRenameLocation(edits: readonly FileTextChanges[], renameFilename: string, name: string, preferLastLocation: boolean): number { + let delta = 0; + let lastPos = -1; + for (const { fileName, textChanges } of edits) { + Debug.assert(fileName === renameFilename); + for (const change of textChanges) { + const { span, newText } = change; + const index = indexInTextChange(newText, escapeString(name)); + if (index !== -1) { + lastPos = span.start + delta + index; + + // If the reference comes first, return immediately. + if (!preferLastLocation) { + return lastPos; + } + } + delta += newText.length - span.length; } - - // PERF: As an optimization, rather than calling factory.cloneNode, we'll update - // the new node created by visitEachChild with the extra changes factory.cloneNode - // would have made. - (visited as Mutable).parent = undefined!; - return visited; } - export function getSynthesizedDeepClones(nodes: NodeArray, includeTrivia?: boolean): NodeArray; - export function getSynthesizedDeepClones(nodes: NodeArray | undefined, includeTrivia?: boolean): NodeArray | undefined; - export function getSynthesizedDeepClones(nodes: NodeArray | undefined, includeTrivia = true): NodeArray | undefined { - return nodes && factory.createNodeArray(nodes.map(n => getSynthesizedDeepClone(n, includeTrivia)), nodes.hasTrailingComma); - } + // If the declaration comes first, return the position of the last occurrence. + Debug.assert(preferLastLocation); + Debug.assert(lastPos >= 0); + return lastPos; +} - export function getSynthesizedDeepClonesWithReplacements( - nodes: NodeArray, - includeTrivia: boolean, - replaceNode: (node: Node) => Node | undefined - ): NodeArray { - return factory.createNodeArray(nodes.map(n => getSynthesizedDeepCloneWithReplacements(n, includeTrivia, replaceNode)), nodes.hasTrailingComma); - } +/** @internal */ +export function copyLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { + forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); +} - /** - * Sets EmitFlags to suppress leading and trailing trivia on the node. - */ - export function suppressLeadingAndTrailingTrivia(node: Node) { - suppressLeadingTrivia(node); - suppressTrailingTrivia(node); - } - /** - * Sets EmitFlags to suppress leading trivia on the node. - */ - export function suppressLeadingTrivia(node: Node) { - addEmitFlagsRecursively(node, EmitFlags.NoLeadingComments, getFirstChild); - } +/** @internal */ +export function copyTrailingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { + forEachTrailingCommentRange(sourceFile.text, sourceNode.end, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticTrailingComment)); +} - /** - * Sets EmitFlags to suppress trailing trivia on the node. - */ - export function suppressTrailingTrivia(node: Node) { - addEmitFlagsRecursively(node, EmitFlags.NoTrailingComments, getLastChild); - } +/** + * This function copies the trailing comments for the token that comes before `sourceNode`, as leading comments of `targetNode`. + * This is useful because sometimes a comment that refers to `sourceNode` will be a leading comment for `sourceNode`, according to the + * notion of trivia ownership, and instead will be a trailing comment for the token before `sourceNode`, e.g.: + * `function foo(\* not leading comment for a *\ a: string) {}` + * The comment refers to `a` but belongs to the `(` token, but we might want to copy it. + * + * @internal + */ +export function copyTrailingAsLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { + forEachTrailingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); +} - export function copyComments(sourceNode: Node, targetNode: Node) { - const sourceFile = sourceNode.getSourceFile(); - const text = sourceFile.text; - if (hasLeadingLineBreak(sourceNode, text)) { - copyLeadingComments(sourceNode, targetNode, sourceFile); +function getAddCommentsFunction(targetNode: Node, sourceFile: SourceFile, commentKind: CommentKind | undefined, hasTrailingNewLine: boolean | undefined, cb: (node: Node, kind: CommentKind, text: string, hasTrailingNewLine?: boolean) => void) { + return (pos: number, end: number, kind: CommentKind, htnl: boolean) => { + if (kind === SyntaxKind.MultiLineCommentTrivia) { + // Remove leading /* + pos += 2; + // Remove trailing */ + end -= 2; } else { - copyTrailingAsLeadingComments(sourceNode, targetNode, sourceFile); + // Remove leading // + pos += 2; } - copyTrailingComments(sourceNode, targetNode, sourceFile); - } + cb(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl); + }; +} - function hasLeadingLineBreak(node: Node, text: string) { - const start = node.getFullStart(); - const end = node.getStart(); - for (let i = start; i < end; i++) { - if (text.charCodeAt(i) === CharacterCodes.lineFeed) return true; - } - return false; +function indexInTextChange(change: string, name: string): number { + if (startsWith(change, name)) return 0; + // Add a " " to avoid references inside words + let idx = change.indexOf(" " + name); + if (idx === -1) idx = change.indexOf("." + name); + if (idx === -1) idx = change.indexOf('"' + name); + return idx === -1 ? -1 : idx + 1; +} + +/** @internal */ +export function needsParentheses(expression: Expression): boolean { + return isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.CommaToken + || isObjectLiteralExpression(expression) + || isAsExpression(expression) && isObjectLiteralExpression(expression.expression); +} + +/** @internal */ +export function getContextualTypeFromParent(node: Expression, checker: TypeChecker): Type | undefined { + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.NewExpression: + return checker.getContextualType(parent as NewExpression); + case SyntaxKind.BinaryExpression: { + const { left, operatorToken, right } = parent as BinaryExpression; + return isEqualityOperatorKind(operatorToken.kind) + ? checker.getTypeAtLocation(node === right ? left : right) + : checker.getContextualType(node); + } + case SyntaxKind.CaseClause: + return (parent as CaseClause).expression === node ? getSwitchedType(parent as CaseClause, checker) : undefined; + default: + return checker.getContextualType(node); } +} + +/** @internal */ +export function quote(sourceFile: SourceFile, preferences: UserPreferences, text: string): string { + // Editors can pass in undefined or empty string - we want to infer the preference in those cases. + const quotePreference = getQuotePreference(sourceFile, preferences); + const quoted = JSON.stringify(text); + return quotePreference === QuotePreference.Single ? `'${stripQuotes(quoted).replace(/'/g, "\\'").replace(/\\"/g, '"')}'` : quoted; +} - function addEmitFlagsRecursively(node: Node, flag: EmitFlags, getChild: (n: Node) => Node | undefined) { - addEmitFlags(node, flag); - const child = getChild(node); - if (child) addEmitFlagsRecursively(child, flag, getChild); +/** @internal */ +export function isEqualityOperatorKind(kind: SyntaxKind): kind is EqualityOperator { + switch (kind) { + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + return true; + default: + return false; } +} - function getFirstChild(node: Node): Node | undefined { - return node.forEachChild(child => child); +/** @internal */ +export function isStringLiteralOrTemplate(node: Node): node is StringLiteralLike | TemplateExpression | TaggedTemplateExpression { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateExpression: + case SyntaxKind.TaggedTemplateExpression: + return true; + default: + return false; } +} + +/** @internal */ +export function hasIndexSignature(type: Type): boolean { + return !!type.getStringIndexType() || !!type.getNumberIndexType(); +} + +/** @internal */ +export function getSwitchedType(caseClause: CaseClause, checker: TypeChecker): Type | undefined { + return checker.getTypeAtLocation(caseClause.parent.parent.expression); +} - export function getUniqueName(baseName: string, sourceFile: SourceFile): string { - let nameText = baseName; - for (let i = 1; !isFileLevelUniqueName(sourceFile, nameText); i++) { - nameText = `${baseName}_${i}`; - } - return nameText; - } +/** @internal */ +export const ANONYMOUS = "anonymous function"; + +/** @internal */ +export function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined { + const checker = program.getTypeChecker(); + let typeIsAccessible = true; + const notAccessible = () => typeIsAccessible = false; + const res = checker.typeToTypeNode(type, enclosingScope, NodeBuilderFlags.NoTruncation, { + trackSymbol: (symbol, declaration, meaning) => { + typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible; + return !typeIsAccessible; + }, + reportInaccessibleThisError: notAccessible, + reportPrivateInBaseOfClassExpression: notAccessible, + reportInaccessibleUniqueSymbolError: notAccessible, + moduleResolverHost: getModuleSpecifierResolverHost(program, host) + }); + return typeIsAccessible ? res : undefined; +} - /** - * @return The index of the (only) reference to the extracted symbol. We want the cursor - * to be on the reference, rather than the declaration, because it's closer to where the - * user was before extracting it. - */ - export function getRenameLocation(edits: readonly FileTextChanges[], renameFilename: string, name: string, preferLastLocation: boolean): number { - let delta = 0; - let lastPos = -1; - for (const { fileName, textChanges } of edits) { - Debug.assert(fileName === renameFilename); - for (const change of textChanges) { - const { span, newText } = change; - const index = indexInTextChange(newText, escapeString(name)); - if (index !== -1) { - lastPos = span.start + delta + index; - - // If the reference comes first, return immediately. - if (!preferLastLocation) { - return lastPos; - } - } - delta += newText.length - span.length; - } - } +function syntaxRequiresTrailingCommaOrSemicolonOrASI(kind: SyntaxKind) { + return kind === SyntaxKind.CallSignature + || kind === SyntaxKind.ConstructSignature + || kind === SyntaxKind.IndexSignature + || kind === SyntaxKind.PropertySignature + || kind === SyntaxKind.MethodSignature; +} - // If the declaration comes first, return the position of the last occurrence. - Debug.assert(preferLastLocation); - Debug.assert(lastPos >= 0); - return lastPos; - } +function syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(kind: SyntaxKind) { + return kind === SyntaxKind.FunctionDeclaration + || kind === SyntaxKind.Constructor + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor; +} - export function copyLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { - forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); - } +function syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(kind: SyntaxKind) { + return kind === SyntaxKind.ModuleDeclaration; +} +/** @internal */ +export function syntaxRequiresTrailingSemicolonOrASI(kind: SyntaxKind) { + return kind === SyntaxKind.VariableStatement + || kind === SyntaxKind.ExpressionStatement + || kind === SyntaxKind.DoStatement + || kind === SyntaxKind.ContinueStatement + || kind === SyntaxKind.BreakStatement + || kind === SyntaxKind.ReturnStatement + || kind === SyntaxKind.ThrowStatement + || kind === SyntaxKind.DebuggerStatement + || kind === SyntaxKind.PropertyDeclaration + || kind === SyntaxKind.TypeAliasDeclaration + || kind === SyntaxKind.ImportDeclaration + || kind === SyntaxKind.ImportEqualsDeclaration + || kind === SyntaxKind.ExportDeclaration + || kind === SyntaxKind.NamespaceExportDeclaration + || kind === SyntaxKind.ExportAssignment; +} - export function copyTrailingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { - forEachTrailingCommentRange(sourceFile.text, sourceNode.end, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticTrailingComment)); - } +/** @internal */ +export const syntaxMayBeASICandidate = or( + syntaxRequiresTrailingCommaOrSemicolonOrASI, + syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI, + syntaxRequiresTrailingModuleBlockOrSemicolonOrASI, + syntaxRequiresTrailingSemicolonOrASI); - /** - * This function copies the trailing comments for the token that comes before `sourceNode`, as leading comments of `targetNode`. - * This is useful because sometimes a comment that refers to `sourceNode` will be a leading comment for `sourceNode`, according to the - * notion of trivia ownership, and instead will be a trailing comment for the token before `sourceNode`, e.g.: - * `function foo(\* not leading comment for a *\ a: string) {}` - * The comment refers to `a` but belongs to the `(` token, but we might want to copy it. - */ - export function copyTrailingAsLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { - forEachTrailingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); +function nodeIsASICandidate(node: Node, sourceFile: SourceFileLike): boolean { + const lastToken = node.getLastToken(sourceFile); + if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) { + return false; } - function getAddCommentsFunction(targetNode: Node, sourceFile: SourceFile, commentKind: CommentKind | undefined, hasTrailingNewLine: boolean | undefined, cb: (node: Node, kind: CommentKind, text: string, hasTrailingNewLine?: boolean) => void) { - return (pos: number, end: number, kind: CommentKind, htnl: boolean) => { - if (kind === SyntaxKind.MultiLineCommentTrivia) { - // Remove leading /* - pos += 2; - // Remove trailing */ - end -= 2; - } - else { - // Remove leading // - pos += 2; - } - cb(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl); - }; - } - - function indexInTextChange(change: string, name: string): number { - if (startsWith(change, name)) return 0; - // Add a " " to avoid references inside words - let idx = change.indexOf(" " + name); - if (idx === -1) idx = change.indexOf("." + name); - if (idx === -1) idx = change.indexOf('"' + name); - return idx === -1 ? -1 : idx + 1; - } - - /* @internal */ - export function needsParentheses(expression: Expression): boolean { - return isBinaryExpression(expression) && expression.operatorToken.kind === SyntaxKind.CommaToken - || isObjectLiteralExpression(expression) - || isAsExpression(expression) && isObjectLiteralExpression(expression.expression); - } - - export function getContextualTypeFromParent(node: Expression, checker: TypeChecker): Type | undefined { - const { parent } = node; - switch (parent.kind) { - case SyntaxKind.NewExpression: - return checker.getContextualType(parent as NewExpression); - case SyntaxKind.BinaryExpression: { - const { left, operatorToken, right } = parent as BinaryExpression; - return isEqualityOperatorKind(operatorToken.kind) - ? checker.getTypeAtLocation(node === right ? left : right) - : checker.getContextualType(node); - } - case SyntaxKind.CaseClause: - return (parent as CaseClause).expression === node ? getSwitchedType(parent as CaseClause, checker) : undefined; - default: - return checker.getContextualType(node); + if (syntaxRequiresTrailingCommaOrSemicolonOrASI(node.kind)) { + if (lastToken && lastToken.kind === SyntaxKind.CommaToken) { + return false; } } - - export function quote(sourceFile: SourceFile, preferences: UserPreferences, text: string): string { - // Editors can pass in undefined or empty string - we want to infer the preference in those cases. - const quotePreference = getQuotePreference(sourceFile, preferences); - const quoted = JSON.stringify(text); - return quotePreference === QuotePreference.Single ? `'${stripQuotes(quoted).replace(/'/g, "\\'").replace(/\\"/g, '"')}'` : quoted; - } - - export function isEqualityOperatorKind(kind: SyntaxKind): kind is EqualityOperator { - switch (kind) { - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - return true; - default: - return false; + else if (syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(node.kind)) { + const lastChild = last(node.getChildren(sourceFile)); + if (lastChild && isModuleBlock(lastChild)) { + return false; } } - - export function isStringLiteralOrTemplate(node: Node): node is StringLiteralLike | TemplateExpression | TaggedTemplateExpression { - switch (node.kind) { - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateExpression: - case SyntaxKind.TaggedTemplateExpression: - return true; - default: - return false; + else if (syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(node.kind)) { + const lastChild = last(node.getChildren(sourceFile)); + if (lastChild && isFunctionBlock(lastChild)) { + return false; } } + else if (!syntaxRequiresTrailingSemicolonOrASI(node.kind)) { + return false; + } - export function hasIndexSignature(type: Type): boolean { - return !!type.getStringIndexType() || !!type.getNumberIndexType(); + // See comment in parser’s `parseDoStatement` + if (node.kind === SyntaxKind.DoStatement) { + return true; } - export function getSwitchedType(caseClause: CaseClause, checker: TypeChecker): Type | undefined { - return checker.getTypeAtLocation(caseClause.parent.parent.expression); + const topNode = findAncestor(node, ancestor => !ancestor.parent)!; + const nextToken = findNextToken(node, topNode, sourceFile); + if (!nextToken || nextToken.kind === SyntaxKind.CloseBraceToken) { + return true; } - export const ANONYMOUS = "anonymous function"; + const startLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line; + const endLine = sourceFile.getLineAndCharacterOfPosition(nextToken.getStart(sourceFile)).line; + return startLine !== endLine; +} - export function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined { - const checker = program.getTypeChecker(); - let typeIsAccessible = true; - const notAccessible = () => typeIsAccessible = false; - const res = checker.typeToTypeNode(type, enclosingScope, NodeBuilderFlags.NoTruncation, { - trackSymbol: (symbol, declaration, meaning) => { - typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible; - return !typeIsAccessible; - }, - reportInaccessibleThisError: notAccessible, - reportPrivateInBaseOfClassExpression: notAccessible, - reportInaccessibleUniqueSymbolError: notAccessible, - moduleResolverHost: getModuleSpecifierResolverHost(program, host) - }); - return typeIsAccessible ? res : undefined; - } - - function syntaxRequiresTrailingCommaOrSemicolonOrASI(kind: SyntaxKind) { - return kind === SyntaxKind.CallSignature - || kind === SyntaxKind.ConstructSignature - || kind === SyntaxKind.IndexSignature - || kind === SyntaxKind.PropertySignature - || kind === SyntaxKind.MethodSignature; - } - - function syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(kind: SyntaxKind) { - return kind === SyntaxKind.FunctionDeclaration - || kind === SyntaxKind.Constructor - || kind === SyntaxKind.MethodDeclaration - || kind === SyntaxKind.GetAccessor - || kind === SyntaxKind.SetAccessor; - } - - function syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(kind: SyntaxKind) { - return kind === SyntaxKind.ModuleDeclaration; - } - - export function syntaxRequiresTrailingSemicolonOrASI(kind: SyntaxKind) { - return kind === SyntaxKind.VariableStatement - || kind === SyntaxKind.ExpressionStatement - || kind === SyntaxKind.DoStatement - || kind === SyntaxKind.ContinueStatement - || kind === SyntaxKind.BreakStatement - || kind === SyntaxKind.ReturnStatement - || kind === SyntaxKind.ThrowStatement - || kind === SyntaxKind.DebuggerStatement - || kind === SyntaxKind.PropertyDeclaration - || kind === SyntaxKind.TypeAliasDeclaration - || kind === SyntaxKind.ImportDeclaration - || kind === SyntaxKind.ImportEqualsDeclaration - || kind === SyntaxKind.ExportDeclaration - || kind === SyntaxKind.NamespaceExportDeclaration - || kind === SyntaxKind.ExportAssignment; - } - - export const syntaxMayBeASICandidate = or( - syntaxRequiresTrailingCommaOrSemicolonOrASI, - syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI, - syntaxRequiresTrailingModuleBlockOrSemicolonOrASI, - syntaxRequiresTrailingSemicolonOrASI); - - function nodeIsASICandidate(node: Node, sourceFile: SourceFileLike): boolean { - const lastToken = node.getLastToken(sourceFile); - if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) { - return false; +/** @internal */ +export function positionIsASICandidate(pos: number, context: Node, sourceFile: SourceFileLike): boolean { + const contextAncestor = findAncestor(context, ancestor => { + if (ancestor.end !== pos) { + return "quit"; } + return syntaxMayBeASICandidate(ancestor.kind); + }); - if (syntaxRequiresTrailingCommaOrSemicolonOrASI(node.kind)) { - if (lastToken && lastToken.kind === SyntaxKind.CommaToken) { - return false; + return !!contextAncestor && nodeIsASICandidate(contextAncestor, sourceFile); +} + +/** @internal */ +export function probablyUsesSemicolons(sourceFile: SourceFile): boolean { + let withSemicolon = 0; + let withoutSemicolon = 0; + const nStatementsToObserve = 5; + forEachChild(sourceFile, function visit(node): boolean | undefined { + if (syntaxRequiresTrailingSemicolonOrASI(node.kind)) { + const lastToken = node.getLastToken(sourceFile); + if (lastToken?.kind === SyntaxKind.SemicolonToken) { + withSemicolon++; } - } - else if (syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(node.kind)) { - const lastChild = last(node.getChildren(sourceFile)); - if (lastChild && isModuleBlock(lastChild)) { - return false; + else { + withoutSemicolon++; } } - else if (syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(node.kind)) { - const lastChild = last(node.getChildren(sourceFile)); - if (lastChild && isFunctionBlock(lastChild)) { - return false; + else if (syntaxRequiresTrailingCommaOrSemicolonOrASI(node.kind)) { + const lastToken = node.getLastToken(sourceFile); + if (lastToken?.kind === SyntaxKind.SemicolonToken) { + withSemicolon++; + } + else if (lastToken && lastToken.kind !== SyntaxKind.CommaToken) { + const lastTokenLine = getLineAndCharacterOfPosition(sourceFile, lastToken.getStart(sourceFile)).line; + const nextTokenLine = getLineAndCharacterOfPosition(sourceFile, getSpanOfTokenAtPosition(sourceFile, lastToken.end).start).line; + // Avoid counting missing semicolon in single-line objects: + // `function f(p: { x: string /*no semicolon here is insignificant*/ }) {` + if (lastTokenLine !== nextTokenLine) { + withoutSemicolon++; + } } - } - else if (!syntaxRequiresTrailingSemicolonOrASI(node.kind)) { - return false; - } - - // See comment in parser’s `parseDoStatement` - if (node.kind === SyntaxKind.DoStatement) { - return true; } - const topNode = findAncestor(node, ancestor => !ancestor.parent)!; - const nextToken = findNextToken(node, topNode, sourceFile); - if (!nextToken || nextToken.kind === SyntaxKind.CloseBraceToken) { + if (withSemicolon + withoutSemicolon >= nStatementsToObserve) { return true; } - const startLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line; - const endLine = sourceFile.getLineAndCharacterOfPosition(nextToken.getStart(sourceFile)).line; - return startLine !== endLine; - } - - export function positionIsASICandidate(pos: number, context: Node, sourceFile: SourceFileLike): boolean { - const contextAncestor = findAncestor(context, ancestor => { - if (ancestor.end !== pos) { - return "quit"; - } - return syntaxMayBeASICandidate(ancestor.kind); - }); + return forEachChild(node, visit); + }); - return !!contextAncestor && nodeIsASICandidate(contextAncestor, sourceFile); + // One statement missing a semicolon isn't sufficient evidence to say the user + // doesn’t want semicolons, because they may not even be done writing that statement. + if (withSemicolon === 0 && withoutSemicolon <= 1) { + return true; } - export function probablyUsesSemicolons(sourceFile: SourceFile): boolean { - let withSemicolon = 0; - let withoutSemicolon = 0; - const nStatementsToObserve = 5; - forEachChild(sourceFile, function visit(node): boolean | undefined { - if (syntaxRequiresTrailingSemicolonOrASI(node.kind)) { - const lastToken = node.getLastToken(sourceFile); - if (lastToken?.kind === SyntaxKind.SemicolonToken) { - withSemicolon++; - } - else { - withoutSemicolon++; - } - } - else if (syntaxRequiresTrailingCommaOrSemicolonOrASI(node.kind)) { - const lastToken = node.getLastToken(sourceFile); - if (lastToken?.kind === SyntaxKind.SemicolonToken) { - withSemicolon++; - } - else if (lastToken && lastToken.kind !== SyntaxKind.CommaToken) { - const lastTokenLine = getLineAndCharacterOfPosition(sourceFile, lastToken.getStart(sourceFile)).line; - const nextTokenLine = getLineAndCharacterOfPosition(sourceFile, getSpanOfTokenAtPosition(sourceFile, lastToken.end).start).line; - // Avoid counting missing semicolon in single-line objects: - // `function f(p: { x: string /*no semicolon here is insignificant*/ }) {` - if (lastTokenLine !== nextTokenLine) { - withoutSemicolon++; - } - } - } - - if (withSemicolon + withoutSemicolon >= nStatementsToObserve) { - return true; - } + // If even 2/5 places have a semicolon, the user probably wants semicolons + return withSemicolon / withoutSemicolon > 1 / nStatementsToObserve; +} - return forEachChild(node, visit); - }); +/** @internal */ +export function tryGetDirectories(host: Pick, directoryName: string): string[] { + return tryIOAndConsumeErrors(host, host.getDirectories, directoryName) || []; +} - // One statement missing a semicolon isn't sufficient evidence to say the user - // doesn’t want semicolons, because they may not even be done writing that statement. - if (withSemicolon === 0 && withoutSemicolon <= 1) { - return true; - } +/** @internal */ +export function tryReadDirectory(host: Pick, path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[]): readonly string[] { + return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include) || emptyArray; +} - // If even 2/5 places have a semicolon, the user probably wants semicolons - return withSemicolon / withoutSemicolon > 1 / nStatementsToObserve; - } +/** @internal */ +export function tryFileExists(host: Pick, path: string): boolean { + return tryIOAndConsumeErrors(host, host.fileExists, path); +} - export function tryGetDirectories(host: Pick, directoryName: string): string[] { - return tryIOAndConsumeErrors(host, host.getDirectories, directoryName) || []; - } +/** @internal */ +export function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean { + return tryAndIgnoreErrors(() => directoryProbablyExists(path, host)) || false; +} - export function tryReadDirectory(host: Pick, path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[]): readonly string[] { - return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include) || emptyArray; +/** @internal */ +export function tryAndIgnoreErrors(cb: () => T): T | undefined { + try { + return cb(); } - - export function tryFileExists(host: Pick, path: string): boolean { - return tryIOAndConsumeErrors(host, host.fileExists, path); + catch { + return undefined; } +} - export function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean { - return tryAndIgnoreErrors(() => directoryProbablyExists(path, host)) || false; - } +/** @internal */ +export function tryIOAndConsumeErrors(host: unknown, toApply: ((...a: any[]) => T) | undefined, ...args: any[]) { + return tryAndIgnoreErrors(() => toApply && toApply.apply(host, args)); +} - export function tryAndIgnoreErrors(cb: () => T): T | undefined { - try { - return cb(); +/** @internal */ +export function findPackageJsons(startDirectory: string, host: Pick, stopDirectory?: string): string[] { + const paths: string[] = []; + forEachAncestorDirectory(startDirectory, ancestor => { + if (ancestor === stopDirectory) { + return true; } - catch { - return undefined; + const currentConfigPath = combinePaths(ancestor, "package.json"); + if (tryFileExists(host, currentConfigPath)) { + paths.push(currentConfigPath); } - } + }); + return paths; +} - export function tryIOAndConsumeErrors(host: unknown, toApply: ((...a: any[]) => T) | undefined, ...args: any[]) { - return tryAndIgnoreErrors(() => toApply && toApply.apply(host, args)); - } +/** @internal */ +export function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined { + let packageJson: string | undefined; + forEachAncestorDirectory(directory, ancestor => { + if (ancestor === "node_modules") return true; + packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json"); + if (packageJson) { + return true; // break out + } + }); + return packageJson; +} - export function findPackageJsons(startDirectory: string, host: Pick, stopDirectory?: string): string[] { - const paths: string[] = []; - forEachAncestorDirectory(startDirectory, ancestor => { - if (ancestor === stopDirectory) { - return true; - } - const currentConfigPath = combinePaths(ancestor, "package.json"); - if (tryFileExists(host, currentConfigPath)) { - paths.push(currentConfigPath); - } - }); - return paths; +/** @internal */ +export function getPackageJsonsVisibleToFile(fileName: string, host: LanguageServiceHost): readonly ProjectPackageJsonInfo[] { + if (!host.fileExists) { + return []; } - export function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined { - let packageJson: string | undefined; - forEachAncestorDirectory(directory, ancestor => { - if (ancestor === "node_modules") return true; - packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json"); - if (packageJson) { - return true; // break out + const packageJsons: ProjectPackageJsonInfo[] = []; + forEachAncestorDirectory(getDirectoryPath(fileName), ancestor => { + const packageJsonFileName = combinePaths(ancestor, "package.json"); + if (host.fileExists(packageJsonFileName)) { + const info = createPackageJsonInfo(packageJsonFileName, host); + if (info) { + packageJsons.push(info); } - }); - return packageJson; - } - - export function getPackageJsonsVisibleToFile(fileName: string, host: LanguageServiceHost): readonly ProjectPackageJsonInfo[] { - if (!host.fileExists) { - return []; } + }); - const packageJsons: ProjectPackageJsonInfo[] = []; - forEachAncestorDirectory(getDirectoryPath(fileName), ancestor => { - const packageJsonFileName = combinePaths(ancestor, "package.json"); - if (host.fileExists(packageJsonFileName)) { - const info = createPackageJsonInfo(packageJsonFileName, host); - if (info) { - packageJsons.push(info); - } - } - }); + return packageJsons; +} - return packageJsons; +/** @internal */ +export function createPackageJsonInfo(fileName: string, host: { readFile?(fileName: string): string | undefined }): ProjectPackageJsonInfo | undefined { + if (!host.readFile) { + return undefined; } - export function createPackageJsonInfo(fileName: string, host: { readFile?(fileName: string): string | undefined }): ProjectPackageJsonInfo | undefined { - if (!host.readFile) { - return undefined; - } - - type PackageJsonRaw = Record | undefined>; - const dependencyKeys = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"] as const; - const stringContent = host.readFile(fileName) || ""; - const content = tryParseJson(stringContent) as PackageJsonRaw | undefined; - const info: Pick = {}; - if (content) { - for (const key of dependencyKeys) { - const dependencies = content[key]; - if (!dependencies) { - continue; - } - const dependencyMap = new Map(); - for (const packageName in dependencies) { - dependencyMap.set(packageName, dependencies[packageName]); - } - info[key] = dependencyMap; - } - } - - const dependencyGroups = [ - [PackageJsonDependencyGroup.Dependencies, info.dependencies], - [PackageJsonDependencyGroup.DevDependencies, info.devDependencies], - [PackageJsonDependencyGroup.OptionalDependencies, info.optionalDependencies], - [PackageJsonDependencyGroup.PeerDependencies, info.peerDependencies], - ] as const; - - return { - ...info, - parseable: !!content, - fileName, - get, - has(dependencyName, inGroups) { - return !!get(dependencyName, inGroups); - }, - }; - - function get(dependencyName: string, inGroups = PackageJsonDependencyGroup.All) { - for (const [group, deps] of dependencyGroups) { - if (deps && (inGroups & group)) { - const dep = deps.get(dependencyName); - if (dep !== undefined) { - return dep; - } + type PackageJsonRaw = Record | undefined>; + const dependencyKeys = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"] as const; + const stringContent = host.readFile(fileName) || ""; + const content = tryParseJson(stringContent) as PackageJsonRaw | undefined; + const info: Pick = {}; + if (content) { + for (const key of dependencyKeys) { + const dependencies = content[key]; + if (!dependencies) { + continue; + } + const dependencyMap = new Map(); + for (const packageName in dependencies) { + dependencyMap.set(packageName, dependencies[packageName]); + } + info[key] = dependencyMap; + } + } + + const dependencyGroups = [ + [PackageJsonDependencyGroup.Dependencies, info.dependencies], + [PackageJsonDependencyGroup.DevDependencies, info.devDependencies], + [PackageJsonDependencyGroup.OptionalDependencies, info.optionalDependencies], + [PackageJsonDependencyGroup.PeerDependencies, info.peerDependencies], + ] as const; + + return { + ...info, + parseable: !!content, + fileName, + get, + has(dependencyName, inGroups) { + return !!get(dependencyName, inGroups); + }, + }; + + function get(dependencyName: string, inGroups = PackageJsonDependencyGroup.All) { + for (const [group, deps] of dependencyGroups) { + if (deps && (inGroups & group)) { + const dep = deps.get(dependencyName); + if (dep !== undefined) { + return dep; } } } } +} - export interface PackageJsonImportFilter { - allowsImportingAmbientModule: (moduleSymbol: Symbol, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost) => boolean; - allowsImportingSourceFile: (sourceFile: SourceFile, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost) => boolean; - /** - * Use for a specific module specifier that has already been resolved. - * Use `allowsImportingAmbientModule` or `allowsImportingSourceFile` to resolve - * the best module specifier for a given module _and_ determine if it’s importable. - */ - allowsImportingSpecifier: (moduleSpecifier: string) => boolean; - } +/** @internal */ +export interface PackageJsonImportFilter { + allowsImportingAmbientModule: (moduleSymbol: Symbol, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost) => boolean; + allowsImportingSourceFile: (sourceFile: SourceFile, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost) => boolean; + /** + * Use for a specific module specifier that has already been resolved. + * Use `allowsImportingAmbientModule` or `allowsImportingSourceFile` to resolve + * the best module specifier for a given module _and_ determine if it’s importable. + */ + allowsImportingSpecifier: (moduleSpecifier: string) => boolean; +} - export function createPackageJsonImportFilter(fromFile: SourceFile, preferences: UserPreferences, host: LanguageServiceHost): PackageJsonImportFilter { - const packageJsons = ( - (host.getPackageJsonsVisibleToFile && host.getPackageJsonsVisibleToFile(fromFile.fileName)) || getPackageJsonsVisibleToFile(fromFile.fileName, host) - ).filter(p => p.parseable); +/** @internal */ +export function createPackageJsonImportFilter(fromFile: SourceFile, preferences: UserPreferences, host: LanguageServiceHost): PackageJsonImportFilter { + const packageJsons = ( + (host.getPackageJsonsVisibleToFile && host.getPackageJsonsVisibleToFile(fromFile.fileName)) || getPackageJsonsVisibleToFile(fromFile.fileName, host) + ).filter(p => p.parseable); - let usesNodeCoreModules: boolean | undefined; - return { allowsImportingAmbientModule, allowsImportingSourceFile, allowsImportingSpecifier }; + let usesNodeCoreModules: boolean | undefined; + return { allowsImportingAmbientModule, allowsImportingSourceFile, allowsImportingSpecifier }; - function moduleSpecifierIsCoveredByPackageJson(specifier: string) { - const packageName = getNodeModuleRootSpecifier(specifier); - for (const packageJson of packageJsons) { - if (packageJson.has(packageName) || packageJson.has(getTypesPackageName(packageName))) { - return true; - } + function moduleSpecifierIsCoveredByPackageJson(specifier: string) { + const packageName = getNodeModuleRootSpecifier(specifier); + for (const packageJson of packageJsons) { + if (packageJson.has(packageName) || packageJson.has(getTypesPackageName(packageName))) { + return true; } - return false; } + return false; + } - function allowsImportingAmbientModule(moduleSymbol: Symbol, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): boolean { - if (!packageJsons.length || !moduleSymbol.valueDeclaration) { - return true; - } + function allowsImportingAmbientModule(moduleSymbol: Symbol, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): boolean { + if (!packageJsons.length || !moduleSymbol.valueDeclaration) { + return true; + } - const declaringSourceFile = moduleSymbol.valueDeclaration.getSourceFile(); - const declaringNodeModuleName = getNodeModulesPackageNameFromFileName(declaringSourceFile.fileName, moduleSpecifierResolutionHost); - if (typeof declaringNodeModuleName === "undefined") { - return true; - } + const declaringSourceFile = moduleSymbol.valueDeclaration.getSourceFile(); + const declaringNodeModuleName = getNodeModulesPackageNameFromFileName(declaringSourceFile.fileName, moduleSpecifierResolutionHost); + if (typeof declaringNodeModuleName === "undefined") { + return true; + } - const declaredModuleSpecifier = stripQuotes(moduleSymbol.getName()); - if (isAllowedCoreNodeModulesImport(declaredModuleSpecifier)) { - return true; - } + const declaredModuleSpecifier = stripQuotes(moduleSymbol.getName()); + if (isAllowedCoreNodeModulesImport(declaredModuleSpecifier)) { + return true; + } + + return moduleSpecifierIsCoveredByPackageJson(declaringNodeModuleName) + || moduleSpecifierIsCoveredByPackageJson(declaredModuleSpecifier); + } - return moduleSpecifierIsCoveredByPackageJson(declaringNodeModuleName) - || moduleSpecifierIsCoveredByPackageJson(declaredModuleSpecifier); + function allowsImportingSourceFile(sourceFile: SourceFile, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): boolean { + if (!packageJsons.length) { + return true; } - function allowsImportingSourceFile(sourceFile: SourceFile, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): boolean { - if (!packageJsons.length) { - return true; - } + const moduleSpecifier = getNodeModulesPackageNameFromFileName(sourceFile.fileName, moduleSpecifierResolutionHost); + if (!moduleSpecifier) { + return true; + } - const moduleSpecifier = getNodeModulesPackageNameFromFileName(sourceFile.fileName, moduleSpecifierResolutionHost); - if (!moduleSpecifier) { - return true; - } + return moduleSpecifierIsCoveredByPackageJson(moduleSpecifier); + } - return moduleSpecifierIsCoveredByPackageJson(moduleSpecifier); + function allowsImportingSpecifier(moduleSpecifier: string) { + if (!packageJsons.length || isAllowedCoreNodeModulesImport(moduleSpecifier)) { + return true; + } + if (pathIsRelative(moduleSpecifier) || isRootedDiskPath(moduleSpecifier)) { + return true; } + return moduleSpecifierIsCoveredByPackageJson(moduleSpecifier); + } - function allowsImportingSpecifier(moduleSpecifier: string) { - if (!packageJsons.length || isAllowedCoreNodeModulesImport(moduleSpecifier)) { - return true; + function isAllowedCoreNodeModulesImport(moduleSpecifier: string) { + // If we’re in JavaScript, it can be difficult to tell whether the user wants to import + // from Node core modules or not. We can start by seeing if the user is actually using + // any node core modules, as opposed to simply having @types/node accidentally as a + // dependency of a dependency. + if (isSourceFileJS(fromFile) && JsTyping.nodeCoreModules.has(moduleSpecifier)) { + if (usesNodeCoreModules === undefined) { + usesNodeCoreModules = consumesNodeCoreModules(fromFile); } - if (pathIsRelative(moduleSpecifier) || isRootedDiskPath(moduleSpecifier)) { + if (usesNodeCoreModules) { return true; } - return moduleSpecifierIsCoveredByPackageJson(moduleSpecifier); } + return false; + } - function isAllowedCoreNodeModulesImport(moduleSpecifier: string) { - // If we’re in JavaScript, it can be difficult to tell whether the user wants to import - // from Node core modules or not. We can start by seeing if the user is actually using - // any node core modules, as opposed to simply having @types/node accidentally as a - // dependency of a dependency. - if (isSourceFileJS(fromFile) && JsTyping.nodeCoreModules.has(moduleSpecifier)) { - if (usesNodeCoreModules === undefined) { - usesNodeCoreModules = consumesNodeCoreModules(fromFile); - } - if (usesNodeCoreModules) { - return true; - } - } - return false; + function getNodeModulesPackageNameFromFileName(importedFileName: string, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): string | undefined { + if (!stringContains(importedFileName, "node_modules")) { + return undefined; } + const specifier = moduleSpecifiers.getNodeModulesPackageName( + host.getCompilationSettings(), + fromFile, + importedFileName, + moduleSpecifierResolutionHost, + preferences, + ); - function getNodeModulesPackageNameFromFileName(importedFileName: string, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost): string | undefined { - if (!stringContains(importedFileName, "node_modules")) { - return undefined; - } - const specifier = moduleSpecifiers.getNodeModulesPackageName( - host.getCompilationSettings(), - fromFile, - importedFileName, - moduleSpecifierResolutionHost, - preferences, - ); - - if (!specifier) { - return undefined; - } - // Paths here are not node_modules, so we don’t care about them; - // returning anything will trigger a lookup in package.json. - if (!pathIsRelative(specifier) && !isRootedDiskPath(specifier)) { - return getNodeModuleRootSpecifier(specifier); - } + if (!specifier) { + return undefined; } - - function getNodeModuleRootSpecifier(fullSpecifier: string): string { - const components = getPathComponents(getPackageNameFromTypesPackageName(fullSpecifier)).slice(1); - // Scoped packages - if (startsWith(components[0], "@")) { - return `${components[0]}/${components[1]}`; - } - return components[0]; + // Paths here are not node_modules, so we don’t care about them; + // returning anything will trigger a lookup in package.json. + if (!pathIsRelative(specifier) && !isRootedDiskPath(specifier)) { + return getNodeModuleRootSpecifier(specifier); } } - function tryParseJson(text: string) { - try { - return JSON.parse(text); - } - catch { - return undefined; + function getNodeModuleRootSpecifier(fullSpecifier: string): string { + const components = getPathComponents(getPackageNameFromTypesPackageName(fullSpecifier)).slice(1); + // Scoped packages + if (startsWith(components[0], "@")) { + return `${components[0]}/${components[1]}`; } + return components[0]; } +} - export function consumesNodeCoreModules(sourceFile: SourceFile): boolean { - return some(sourceFile.imports, ({ text }) => JsTyping.nodeCoreModules.has(text)); +function tryParseJson(text: string) { + try { + return JSON.parse(text); } - - export function isInsideNodeModules(fileOrDirectory: string): boolean { - return contains(getPathComponents(fileOrDirectory), "node_modules"); + catch { + return undefined; } +} + +/** @internal */ +export function consumesNodeCoreModules(sourceFile: SourceFile): boolean { + return some(sourceFile.imports, ({ text }) => JsTyping.nodeCoreModules.has(text)); +} + +/** @internal */ +export function isInsideNodeModules(fileOrDirectory: string): boolean { + return contains(getPathComponents(fileOrDirectory), "node_modules"); +} - export function isDiagnosticWithLocation(diagnostic: Diagnostic): diagnostic is DiagnosticWithLocation { - return diagnostic.file !== undefined && diagnostic.start !== undefined && diagnostic.length !== undefined; +/** @internal */ +export function isDiagnosticWithLocation(diagnostic: Diagnostic): diagnostic is DiagnosticWithLocation { + return diagnostic.file !== undefined && diagnostic.start !== undefined && diagnostic.length !== undefined; +} + +/** @internal */ +export function findDiagnosticForNode(node: Node, sortedFileDiagnostics: readonly Diagnostic[]): DiagnosticWithLocation | undefined { + const span: Partial = createTextSpanFromNode(node); + const index = binarySearchKey(sortedFileDiagnostics, span, identity, compareTextSpans); + if (index >= 0) { + const diagnostic = sortedFileDiagnostics[index]; + Debug.assertEqual(diagnostic.file, node.getSourceFile(), "Diagnostics proided to 'findDiagnosticForNode' must be from a single SourceFile"); + return cast(diagnostic, isDiagnosticWithLocation); } +} - export function findDiagnosticForNode(node: Node, sortedFileDiagnostics: readonly Diagnostic[]): DiagnosticWithLocation | undefined { - const span: Partial = createTextSpanFromNode(node); - const index = binarySearchKey(sortedFileDiagnostics, span, identity, compareTextSpans); - if (index >= 0) { - const diagnostic = sortedFileDiagnostics[index]; - Debug.assertEqual(diagnostic.file, node.getSourceFile(), "Diagnostics proided to 'findDiagnosticForNode' must be from a single SourceFile"); - return cast(diagnostic, isDiagnosticWithLocation); - } +/** @internal */ +export function getDiagnosticsWithinSpan(span: TextSpan, sortedFileDiagnostics: readonly Diagnostic[]): readonly DiagnosticWithLocation[] { + let index = binarySearchKey(sortedFileDiagnostics, span.start, diag => diag.start, compareValues); + if (index < 0) { + index = ~index; + } + while (sortedFileDiagnostics[index - 1]?.start === span.start) { + index--; } - export function getDiagnosticsWithinSpan(span: TextSpan, sortedFileDiagnostics: readonly Diagnostic[]): readonly DiagnosticWithLocation[] { - let index = binarySearchKey(sortedFileDiagnostics, span.start, diag => diag.start, compareValues); - if (index < 0) { - index = ~index; - } - while (sortedFileDiagnostics[index - 1]?.start === span.start) { - index--; + const result: DiagnosticWithLocation[] = []; + const end = textSpanEnd(span); + while (true) { + const diagnostic = tryCast(sortedFileDiagnostics[index], isDiagnosticWithLocation); + if (!diagnostic || diagnostic.start > end) { + break; } - - const result: DiagnosticWithLocation[] = []; - const end = textSpanEnd(span); - while (true) { - const diagnostic = tryCast(sortedFileDiagnostics[index], isDiagnosticWithLocation); - if (!diagnostic || diagnostic.start > end) { - break; - } - if (textSpanContainsTextSpan(span, diagnostic)) { - result.push(diagnostic); - } - index++; + if (textSpanContainsTextSpan(span, diagnostic)) { + result.push(diagnostic); } - - return result; + index++; } - /* @internal */ - export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan { - return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition); - } + return result; +} - /* @internal */ - export function getFixableErrorSpanExpression(sourceFile: SourceFile, span: TextSpan): Expression | undefined { - const token = getTokenAtPosition(sourceFile, span.start); - // Checker has already done work to determine that await might be possible, and has attached - // related info to the node, so start by finding the expression that exactly matches up - // with the diagnostic range. - const expression = findAncestor(token, node => { - if (node.getStart(sourceFile) < span.start || node.getEnd() > textSpanEnd(span)) { - return "quit"; - } - return isExpression(node) && textSpansEqual(span, createTextSpanFromNode(node, sourceFile)); - }) as Expression | undefined; +/** @internal */ +export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan { + return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition); +} - return expression; - } +/** @internal */ +export function getFixableErrorSpanExpression(sourceFile: SourceFile, span: TextSpan): Expression | undefined { + const token = getTokenAtPosition(sourceFile, span.start); + // Checker has already done work to determine that await might be possible, and has attached + // related info to the node, so start by finding the expression that exactly matches up + // with the diagnostic range. + const expression = findAncestor(token, node => { + if (node.getStart(sourceFile) < span.start || node.getEnd() > textSpanEnd(span)) { + return "quit"; + } + return isExpression(node) && textSpansEqual(span, createTextSpanFromNode(node, sourceFile)); + }) as Expression | undefined; - /** - * If the provided value is an array, the mapping function is applied to each element; otherwise, the mapping function is applied - * to the provided value itself. - */ - export function mapOneOrMany(valueOrArray: T | readonly T[], f: (x: T, i: number) => U): U | U[]; - export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U): U | U[] | undefined; - export function mapOneOrMany(valueOrArray: T | readonly T[], f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U; - export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U | undefined; - export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U | U[] = identity): U | U[] | undefined { - return valueOrArray ? isArray(valueOrArray) ? resultSelector(map(valueOrArray, f)) : f(valueOrArray, 0) : undefined; - } + return expression; +} - /** - * If the provided value is an array, the first element of the array is returned; otherwise, the provided value is returned instead. - */ - export function firstOrOnly(valueOrArray: T | readonly T[]): T { - return isArray(valueOrArray) ? first(valueOrArray) : valueOrArray; - } +/** + * If the provided value is an array, the mapping function is applied to each element; otherwise, the mapping function is applied + * to the provided value itself. + * + * @internal + */ +export function mapOneOrMany(valueOrArray: T | readonly T[], f: (x: T, i: number) => U): U | U[]; +/** @internal */ +export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U): U | U[] | undefined; +/** @internal */ +export function mapOneOrMany(valueOrArray: T | readonly T[], f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U; +/** @internal */ +export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U | undefined; +/** @internal */ +export function mapOneOrMany(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U | U[] = identity): U | U[] | undefined { + return valueOrArray ? isArray(valueOrArray) ? resultSelector(map(valueOrArray, f)) : f(valueOrArray, 0) : undefined; +} - export function getNamesForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined): string | [lowercase: string, capitalized: string] { - if (needsNameFromDeclaration(symbol)) { - const fromDeclaration = getDefaultLikeExportNameFromDeclaration(symbol); - if (fromDeclaration) return fromDeclaration; - const fileNameCase = codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*preferCapitalized*/ false); - const capitalized = codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*preferCapitalized*/ true); - if (fileNameCase === capitalized) return fileNameCase; - return [fileNameCase, capitalized]; - } - return symbol.name; - } +/** + * If the provided value is an array, the first element of the array is returned; otherwise, the provided value is returned instead. + * + * @internal + */ +export function firstOrOnly(valueOrArray: T | readonly T[]): T { + return isArray(valueOrArray) ? first(valueOrArray) : valueOrArray; +} - export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined, preferCapitalized?: boolean) { - if (needsNameFromDeclaration(symbol)) { - // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase. - return getDefaultLikeExportNameFromDeclaration(symbol) - || codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, !!preferCapitalized); - } - return symbol.name; +/** @internal */ +export function getNamesForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined): string | [lowercase: string, capitalized: string] { + if (needsNameFromDeclaration(symbol)) { + const fromDeclaration = getDefaultLikeExportNameFromDeclaration(symbol); + if (fromDeclaration) return fromDeclaration; + const fileNameCase = codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*preferCapitalized*/ false); + const capitalized = codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*preferCapitalized*/ true); + if (fileNameCase === capitalized) return fileNameCase; + return [fileNameCase, capitalized]; + } + return symbol.name; +} +/** @internal */ +export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined, preferCapitalized?: boolean) { + if (needsNameFromDeclaration(symbol)) { + // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase. + return getDefaultLikeExportNameFromDeclaration(symbol) + || codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, !!preferCapitalized); } + return symbol.name; - function needsNameFromDeclaration(symbol: Symbol) { - return !(symbol.flags & SymbolFlags.Transient) && (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default); - } +} - function getDefaultLikeExportNameFromDeclaration(symbol: Symbol) { - return firstDefined(symbol.declarations, d => isExportAssignment(d) ? tryCast(skipOuterExpressions(d.expression), isIdentifier)?.text : undefined); - } +function needsNameFromDeclaration(symbol: Symbol) { + return !(symbol.flags & SymbolFlags.Transient) && (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default); +} - function getSymbolParentOrFail(symbol: Symbol) { - return Debug.checkDefined( - symbol.parent, - `Symbol parent was undefined. Flags: ${Debug.formatSymbolFlags(symbol.flags)}. ` + - `Declarations: ${symbol.declarations?.map(d => { - const kind = Debug.formatSyntaxKind(d.kind); - const inJS = isInJSFile(d); - const { expression } = d as any; - return (inJS ? "[JS]" : "") + kind + (expression ? ` (expression: ${Debug.formatSyntaxKind(expression.kind)})` : ""); - }).join(", ")}.`); - } +function getDefaultLikeExportNameFromDeclaration(symbol: Symbol) { + return firstDefined(symbol.declarations, d => isExportAssignment(d) ? tryCast(skipOuterExpressions(d.expression), isIdentifier)?.text : undefined); +} - /** - * Useful to check whether a string contains another string at a specific index - * without allocating another string or traversing the entire contents of the outer string. - * - * This function is useful in place of either of the following: - * - * ```ts - * // Allocates - * haystack.substr(startIndex, needle.length) === needle - * - * // Full traversal - * haystack.indexOf(needle, startIndex) === startIndex - * ``` - * - * @param haystack The string that potentially contains `needle`. - * @param needle The string whose content might sit within `haystack`. - * @param startIndex The index within `haystack` to start searching for `needle`. - */ - export function stringContainsAt(haystack: string, needle: string, startIndex: number) { - const needleLength = needle.length; - if (needleLength + startIndex > haystack.length) { - return false; - } - for (let i = 0; i < needleLength; i++) { - if (needle.charCodeAt(i) !== haystack.charCodeAt(i + startIndex)) return false; - } - return true; - } +function getSymbolParentOrFail(symbol: Symbol) { + return Debug.checkDefined( + symbol.parent, + `Symbol parent was undefined. Flags: ${Debug.formatSymbolFlags(symbol.flags)}. ` + + `Declarations: ${symbol.declarations?.map(d => { + const kind = Debug.formatSyntaxKind(d.kind); + const inJS = isInJSFile(d); + const { expression } = d as any; + return (inJS ? "[JS]" : "") + kind + (expression ? ` (expression: ${Debug.formatSyntaxKind(expression.kind)})` : ""); + }).join(", ")}.`); +} - export function startsWithUnderscore(name: string): boolean { - return name.charCodeAt(0) === CharacterCodes._; +/** + * Useful to check whether a string contains another string at a specific index + * without allocating another string or traversing the entire contents of the outer string. + * + * This function is useful in place of either of the following: + * + * ```ts + * // Allocates + * haystack.substr(startIndex, needle.length) === needle + * + * // Full traversal + * haystack.indexOf(needle, startIndex) === startIndex + * ``` + * + * @param haystack The string that potentially contains `needle`. + * @param needle The string whose content might sit within `haystack`. + * @param startIndex The index within `haystack` to start searching for `needle`. + * + * @internal + */ +export function stringContainsAt(haystack: string, needle: string, startIndex: number) { + const needleLength = needle.length; + if (needleLength + startIndex > haystack.length) { + return false; } - - export function isGlobalDeclaration(declaration: Declaration) { - return !isNonGlobalDeclaration(declaration); + for (let i = 0; i < needleLength; i++) { + if (needle.charCodeAt(i) !== haystack.charCodeAt(i + startIndex)) return false; } + return true; +} - export function isNonGlobalDeclaration(declaration: Declaration) { - const sourceFile = declaration.getSourceFile(); - // If the file is not a module, the declaration is global - if (!sourceFile.externalModuleIndicator && !sourceFile.commonJsModuleIndicator) { - return false; - } - // If the file is a module written in TypeScript, it still might be in a `declare global` augmentation - return isInJSFile(declaration) || !findAncestor(declaration, isGlobalScopeAugmentation); - } +/** @internal */ +export function startsWithUnderscore(name: string): boolean { + return name.charCodeAt(0) === CharacterCodes._; +} - export function isDeprecatedDeclaration(decl: Declaration) { - return !!(getCombinedNodeFlagsAlwaysIncludeJSDoc(decl) & ModifierFlags.Deprecated); - } +/** @internal */ +export function isGlobalDeclaration(declaration: Declaration) { + return !isNonGlobalDeclaration(declaration); +} - export function shouldUseUriStyleNodeCoreModules(file: SourceFile, program: Program): boolean { - const decisionFromFile = firstDefined(file.imports, node => { - if (JsTyping.nodeCoreModules.has(node.text)) { - return startsWith(node.text, "node:"); - } - }); - return decisionFromFile ?? program.usesUriStyleNodeCoreModules; +/** @internal */ +export function isNonGlobalDeclaration(declaration: Declaration) { + const sourceFile = declaration.getSourceFile(); + // If the file is not a module, the declaration is global + if (!sourceFile.externalModuleIndicator && !sourceFile.commonJsModuleIndicator) { + return false; } + // If the file is a module written in TypeScript, it still might be in a `declare global` augmentation + return isInJSFile(declaration) || !findAncestor(declaration, isGlobalScopeAugmentation); +} - export function getNewLineKind(newLineCharacter: string): NewLineKind { - return newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed; - } +/** @internal */ +export function isDeprecatedDeclaration(decl: Declaration) { + return !!(getCombinedNodeFlagsAlwaysIncludeJSDoc(decl) & ModifierFlags.Deprecated); +} - export type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string]; - export function diagnosticToString(diag: DiagnosticAndArguments): string { - return isArray(diag) - ? formatStringFromArgs(getLocaleSpecificMessage(diag[0]), diag.slice(1) as readonly string[]) - : getLocaleSpecificMessage(diag); - } +/** @internal */ +export function shouldUseUriStyleNodeCoreModules(file: SourceFile, program: Program): boolean { + const decisionFromFile = firstDefined(file.imports, node => { + if (JsTyping.nodeCoreModules.has(node.text)) { + return startsWith(node.text, "node:"); + } + }); + return decisionFromFile ?? program.usesUriStyleNodeCoreModules; +} - /** - * Get format code settings for a code writing context (e.g. when formatting text changes or completions code). - */ - export function getFormatCodeSettingsForWriting({ options }: formatting.FormatContext, sourceFile: SourceFile): FormatCodeSettings { - const shouldAutoDetectSemicolonPreference = !options.semicolons || options.semicolons === SemicolonPreference.Ignore; - const shouldRemoveSemicolons = options.semicolons === SemicolonPreference.Remove || shouldAutoDetectSemicolonPreference && !probablyUsesSemicolons(sourceFile); - return { - ...options, - semicolons: shouldRemoveSemicolons ? SemicolonPreference.Remove : SemicolonPreference.Ignore, - }; - } +/** @internal */ +export function getNewLineKind(newLineCharacter: string): NewLineKind { + return newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed; +} - export function jsxModeNeedsExplicitImport(jsx: JsxEmit | undefined) { - return jsx === JsxEmit.React || jsx === JsxEmit.ReactNative; - } +/** @internal */ +export type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string]; +/** @internal */ +export function diagnosticToString(diag: DiagnosticAndArguments): string { + return isArray(diag) + ? formatStringFromArgs(getLocaleSpecificMessage(diag[0]), diag.slice(1) as readonly string[]) + : getLocaleSpecificMessage(diag); +} - export function isSourceFileFromLibrary(program: Program, node: SourceFile) { - return program.isSourceFileFromExternalLibrary(node) || program.isSourceFileDefaultLibrary(node); - } +/** + * Get format code settings for a code writing context (e.g. when formatting text changes or completions code). + * + * @internal + */ +export function getFormatCodeSettingsForWriting({ options }: formatting.FormatContext, sourceFile: SourceFile): FormatCodeSettings { + const shouldAutoDetectSemicolonPreference = !options.semicolons || options.semicolons === SemicolonPreference.Ignore; + const shouldRemoveSemicolons = options.semicolons === SemicolonPreference.Remove || shouldAutoDetectSemicolonPreference && !probablyUsesSemicolons(sourceFile); + return { + ...options, + semicolons: shouldRemoveSemicolons ? SemicolonPreference.Remove : SemicolonPreference.Ignore, + }; +} + +/** @internal */ +export function jsxModeNeedsExplicitImport(jsx: JsxEmit | undefined) { + return jsx === JsxEmit.React || jsx === JsxEmit.ReactNative; +} - // #endregion +/** @internal */ +export function isSourceFileFromLibrary(program: Program, node: SourceFile) { + return program.isSourceFileFromExternalLibrary(node) || program.isSourceFileDefaultLibrary(node); } diff --git a/src/testRunner/_namespaces/FourSlash.ts b/src/testRunner/_namespaces/FourSlash.ts new file mode 100644 index 0000000000000..b81535c48ffd8 --- /dev/null +++ b/src/testRunner/_namespaces/FourSlash.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the FourSlash namespace. */ + +export * from "../../harness/_namespaces/FourSlash"; diff --git a/src/testRunner/_namespaces/Harness.Parallel.Host.ts b/src/testRunner/_namespaces/Harness.Parallel.Host.ts new file mode 100644 index 0000000000000..8104cc9ecfdf6 --- /dev/null +++ b/src/testRunner/_namespaces/Harness.Parallel.Host.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the Harness.Parallel.Host namespace. */ + +export * from "../parallel/host"; diff --git a/src/testRunner/_namespaces/Harness.Parallel.Worker.ts b/src/testRunner/_namespaces/Harness.Parallel.Worker.ts new file mode 100644 index 0000000000000..ecca4b9659e6e --- /dev/null +++ b/src/testRunner/_namespaces/Harness.Parallel.Worker.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the Harness.Parallel.Worker namespace. */ + +export * from "../parallel/worker"; diff --git a/src/testRunner/_namespaces/Harness.Parallel.ts b/src/testRunner/_namespaces/Harness.Parallel.ts new file mode 100644 index 0000000000000..10087db7557c3 --- /dev/null +++ b/src/testRunner/_namespaces/Harness.Parallel.ts @@ -0,0 +1,7 @@ +/* Generated file to emulate the Harness.Parallel namespace. */ + +export * from "../parallel/shared"; +import * as Host from "./Harness.Parallel.Host"; +export { Host }; +import * as Worker from "./Harness.Parallel.Worker"; +export { Worker }; diff --git a/src/testRunner/_namespaces/Harness.ts b/src/testRunner/_namespaces/Harness.ts new file mode 100644 index 0000000000000..a9e4e0904b3fb --- /dev/null +++ b/src/testRunner/_namespaces/Harness.ts @@ -0,0 +1,19 @@ +/* Generated file to emulate the Harness namespace. */ + +export * from "../../harness/_namespaces/Harness"; +export * from "../../loggedIO/_namespaces/Harness"; + +import * as Parallel from "./Harness.Parallel"; +export { Parallel }; + +export * from "../fourslashRunner"; +export * from "../compilerRunner"; +export * from "../externalCompileRunner"; +export * from "../test262Runner"; +export * from "../runner"; + +// If running as emitted CJS, don't start executing the tests here; instead start in runner.ts. +// If running bundled, we want this to be here so that esbuild places the tests after runner.ts. +if (!__filename.endsWith("Harness.js")) { + require("../tests"); +} diff --git a/src/testRunner/_namespaces/Playback.ts b/src/testRunner/_namespaces/Playback.ts new file mode 100644 index 0000000000000..90e7eae5f16b9 --- /dev/null +++ b/src/testRunner/_namespaces/Playback.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the Playback namespace. */ + +export * from "../../loggedIO/_namespaces/Playback"; diff --git a/src/testRunner/_namespaces/RWC.ts b/src/testRunner/_namespaces/RWC.ts new file mode 100644 index 0000000000000..a2d6ebf4fa866 --- /dev/null +++ b/src/testRunner/_namespaces/RWC.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the RWC namespace. */ + +export * from "../rwcRunner"; diff --git a/src/testRunner/_namespaces/Utils.ts b/src/testRunner/_namespaces/Utils.ts new file mode 100644 index 0000000000000..f7bd754263d74 --- /dev/null +++ b/src/testRunner/_namespaces/Utils.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the Utils namespace. */ + +export * from "../../harness/_namespaces/Utils"; diff --git a/src/testRunner/_namespaces/compiler.ts b/src/testRunner/_namespaces/compiler.ts new file mode 100644 index 0000000000000..62e194d59712a --- /dev/null +++ b/src/testRunner/_namespaces/compiler.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the compiler namespace. */ + +export * from "../../harness/_namespaces/compiler"; diff --git a/src/testRunner/_namespaces/documents.ts b/src/testRunner/_namespaces/documents.ts new file mode 100644 index 0000000000000..bf76b1332fc47 --- /dev/null +++ b/src/testRunner/_namespaces/documents.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the documents namespace. */ + +export * from "../../harness/_namespaces/documents"; diff --git a/src/testRunner/_namespaces/evaluator.ts b/src/testRunner/_namespaces/evaluator.ts new file mode 100644 index 0000000000000..9710863c9b2ec --- /dev/null +++ b/src/testRunner/_namespaces/evaluator.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the evaluator namespace. */ + +export * from "../../harness/_namespaces/evaluator"; diff --git a/src/testRunner/_namespaces/fakes.ts b/src/testRunner/_namespaces/fakes.ts new file mode 100644 index 0000000000000..1b6c51d409411 --- /dev/null +++ b/src/testRunner/_namespaces/fakes.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the fakes namespace. */ + +export * from "../../harness/_namespaces/fakes"; diff --git a/src/testRunner/_namespaces/project.ts b/src/testRunner/_namespaces/project.ts new file mode 100644 index 0000000000000..c9d999adbfff1 --- /dev/null +++ b/src/testRunner/_namespaces/project.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the project namespace. */ + +export * from "../projectsRunner"; diff --git a/src/testRunner/_namespaces/ts.projectSystem.ts b/src/testRunner/_namespaces/ts.projectSystem.ts new file mode 100644 index 0000000000000..2723fee96d7bc --- /dev/null +++ b/src/testRunner/_namespaces/ts.projectSystem.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the ts.projectSystem namespace. */ + +export * from "../unittests/tsserver/helpers"; diff --git a/src/testRunner/_namespaces/ts.server.ts b/src/testRunner/_namespaces/ts.server.ts new file mode 100644 index 0000000000000..225d9d4e6aa24 --- /dev/null +++ b/src/testRunner/_namespaces/ts.server.ts @@ -0,0 +1,8 @@ +/* Generated file to emulate the ts.server namespace. */ + +export * from "../../jsTyping/_namespaces/ts.server"; +export * from "../../server/_namespaces/ts.server"; +export * from "../../webServer/_namespaces/ts.server"; +export * from "../../typingsInstallerCore/_namespaces/ts.server"; +export * from "../../harness/_namespaces/ts.server"; +export * from "../../loggedIO/_namespaces/ts.server"; diff --git a/src/testRunner/_namespaces/ts.ts b/src/testRunner/_namespaces/ts.ts new file mode 100644 index 0000000000000..d763d9b8291a2 --- /dev/null +++ b/src/testRunner/_namespaces/ts.ts @@ -0,0 +1,22 @@ +/* Generated file to emulate the ts namespace. */ + +export * from "../../compiler/_namespaces/ts"; +export * from "../../executeCommandLine/_namespaces/ts"; +export * from "../../services/_namespaces/ts"; +export * from "../../jsTyping/_namespaces/ts"; +export * from "../../server/_namespaces/ts"; +export * from "../../webServer/_namespaces/ts"; +export * from "../../typingsInstallerCore/_namespaces/ts"; +export * from "../../deprecatedCompat/_namespaces/ts"; +export * from "../../harness/_namespaces/ts"; +export * from "../../loggedIO/_namespaces/ts"; +import * as tscWatch from "./ts.tscWatch"; +export { tscWatch }; +import * as server from "./ts.server"; +export { server }; +import * as projectSystem from "./ts.projectSystem"; +export { projectSystem }; +export * from "../unittests/helpers"; +export * from "../unittests/services/extract/helpers"; +export * from "../unittests/tsbuild/helpers"; +export * from "../unittests/tsc/helpers"; diff --git a/src/testRunner/_namespaces/ts.tscWatch.ts b/src/testRunner/_namespaces/ts.tscWatch.ts new file mode 100644 index 0000000000000..aaa9390cb9e73 --- /dev/null +++ b/src/testRunner/_namespaces/ts.tscWatch.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the ts.tscWatch namespace. */ + +export * from "../unittests/tscWatch/helpers"; diff --git a/src/testRunner/_namespaces/vfs.ts b/src/testRunner/_namespaces/vfs.ts new file mode 100644 index 0000000000000..5fe2e7d9362b5 --- /dev/null +++ b/src/testRunner/_namespaces/vfs.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the vfs namespace. */ + +export * from "../../harness/_namespaces/vfs"; diff --git a/src/testRunner/_namespaces/vpath.ts b/src/testRunner/_namespaces/vpath.ts new file mode 100644 index 0000000000000..9ae8ad3777f57 --- /dev/null +++ b/src/testRunner/_namespaces/vpath.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the vpath namespace. */ + +export * from "../../harness/_namespaces/vpath"; diff --git a/src/testRunner/compilerRef.ts b/src/testRunner/compilerRef.ts deleted file mode 100644 index b76e4e72fbb88..0000000000000 --- a/src/testRunner/compilerRef.ts +++ /dev/null @@ -1,2 +0,0 @@ -// empty ref to compiler so it can be referenced by unittests -namespace compiler {} \ No newline at end of file diff --git a/src/testRunner/compilerRunner.ts b/src/testRunner/compilerRunner.ts index ced86a57713c4..2133aad726734 100644 --- a/src/testRunner/compilerRunner.ts +++ b/src/testRunner/compilerRunner.ts @@ -1,343 +1,350 @@ -namespace Harness { - export const enum CompilerTestType { - Conformance, - Regressions, - Test262 - } +import * as vpath from "./_namespaces/vpath"; +import * as ts from "./_namespaces/ts"; +import * as compiler from "./_namespaces/compiler"; +import * as Utils from "./_namespaces/Utils"; +import { + Baseline, Compiler, FileBasedTest, FileBasedTestConfiguration, getFileBasedTestConfigurationDescription, + getFileBasedTestConfigurations, IO, RunnerBase, TestCaseParser, TestRunnerKind, +} from "./_namespaces/Harness"; + +export const enum CompilerTestType { + Conformance, + Regressions, + Test262 +} - interface CompilerFileBasedTest extends FileBasedTest { - readonly content?: string; - } +interface CompilerFileBasedTest extends FileBasedTest { + readonly content?: string; +} - export class CompilerBaselineRunner extends RunnerBase { - private basePath = "tests/cases"; - private testSuiteName: TestRunnerKind; - private emit: boolean; +export class CompilerBaselineRunner extends RunnerBase { + private basePath = "tests/cases"; + private testSuiteName: TestRunnerKind; + private emit: boolean; - public options: string | undefined; + public options: string | undefined; - constructor(public testType: CompilerTestType) { - super(); - this.emit = true; - if (testType === CompilerTestType.Conformance) { - this.testSuiteName = "conformance"; - } - else if (testType === CompilerTestType.Regressions) { - this.testSuiteName = "compiler"; - } - else if (testType === CompilerTestType.Test262) { - this.testSuiteName = "test262"; - } - else { - this.testSuiteName = "compiler"; // default to this for historical reasons - } - this.basePath += "/" + this.testSuiteName; + constructor(public testType: CompilerTestType) { + super(); + this.emit = true; + if (testType === CompilerTestType.Conformance) { + this.testSuiteName = "conformance"; } - - public kind() { - return this.testSuiteName; + else if (testType === CompilerTestType.Regressions) { + this.testSuiteName = "compiler"; } - - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true }).map(CompilerTest.getConfigurations); + else if (testType === CompilerTestType.Test262) { + this.testSuiteName = "test262"; + } + else { + this.testSuiteName = "compiler"; // default to this for historical reasons } + this.basePath += "/" + this.testSuiteName; + } - public initializeTests() { - describe(this.testSuiteName + " tests", () => { - describe("Setup compiler for compiler baselines", () => { - this.parseOptions(); - }); + public kind() { + return this.testSuiteName; + } - // this will set up a series of describe/it blocks to run between the setup and cleanup phases - const files = this.tests.length > 0 ? this.tests : IO.enumerateTestFiles(this); - files.forEach(test => { - const file = typeof test === "string" ? test : test.file; - this.checkTestCodeOutput(vpath.normalizeSeparators(file), typeof test === "string" ? CompilerTest.getConfigurations(test) : test); - }); + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true }).map(CompilerTest.getConfigurations); + } + + public initializeTests() { + describe(this.testSuiteName + " tests", () => { + describe("Setup compiler for compiler baselines", () => { + this.parseOptions(); }); - } - public checkTestCodeOutput(fileName: string, test?: CompilerFileBasedTest) { - if (test && ts.some(test.configurations)) { - test.configurations.forEach(configuration => { - describe(`${this.testSuiteName} tests for ${fileName}${configuration ? ` (${getFileBasedTestConfigurationDescription(configuration)})` : ``}`, () => { - this.runSuite(fileName, test, configuration); - }); - }); - } - else { - describe(`${this.testSuiteName} tests for ${fileName}`, () => { - this.runSuite(fileName, test); - }); - } - } + // this will set up a series of describe/it blocks to run between the setup and cleanup phases + const files = this.tests.length > 0 ? this.tests : IO.enumerateTestFiles(this); + files.forEach(test => { + const file = typeof test === "string" ? test : test.file; + this.checkTestCodeOutput(vpath.normalizeSeparators(file), typeof test === "string" ? CompilerTest.getConfigurations(test) : test); + }); + }); + } - private runSuite(fileName: string, test?: CompilerFileBasedTest, configuration?: FileBasedTestConfiguration) { - // Mocha holds onto the closure environment of the describe callback even after the test is done. - // Everything declared here should be cleared out in the "after" callback. - let compilerTest!: CompilerTest; - before(() => { - let payload; - if (test && test.content) { - const rootDir = test.file.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(test.file) + "/"; - payload = TestCaseParser.makeUnitsFromTest(test.content, test.file, rootDir); - } - compilerTest = new CompilerTest(fileName, payload, configuration); + public checkTestCodeOutput(fileName: string, test?: CompilerFileBasedTest) { + if (test && ts.some(test.configurations)) { + test.configurations.forEach(configuration => { + describe(`${this.testSuiteName} tests for ${fileName}${configuration ? ` (${getFileBasedTestConfigurationDescription(configuration)})` : ``}`, () => { + this.runSuite(fileName, test, configuration); + }); }); - it(`Correct errors for ${fileName}`, () => compilerTest.verifyDiagnostics()); - it(`Correct module resolution tracing for ${fileName}`, () => compilerTest.verifyModuleResolution()); - it(`Correct sourcemap content for ${fileName}`, () => compilerTest.verifySourceMapRecord()); - it(`Correct JS output for ${fileName}`, () => (this.emit && compilerTest.verifyJavaScriptOutput())); - it(`Correct Sourcemap output for ${fileName}`, () => compilerTest.verifySourceMapOutput()); - it(`Correct type/symbol baselines for ${fileName}`, () => compilerTest.verifyTypesAndSymbols()); - after(() => { - compilerTest = undefined!; + } + else { + describe(`${this.testSuiteName} tests for ${fileName}`, () => { + this.runSuite(fileName, test); }); } + } + + private runSuite(fileName: string, test?: CompilerFileBasedTest, configuration?: FileBasedTestConfiguration) { + // Mocha holds onto the closure environment of the describe callback even after the test is done. + // Everything declared here should be cleared out in the "after" callback. + let compilerTest!: CompilerTest; + before(() => { + let payload; + if (test && test.content) { + const rootDir = test.file.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(test.file) + "/"; + payload = TestCaseParser.makeUnitsFromTest(test.content, test.file, rootDir); + } + compilerTest = new CompilerTest(fileName, payload, configuration); + }); + it(`Correct errors for ${fileName}`, () => compilerTest.verifyDiagnostics()); + it(`Correct module resolution tracing for ${fileName}`, () => compilerTest.verifyModuleResolution()); + it(`Correct sourcemap content for ${fileName}`, () => compilerTest.verifySourceMapRecord()); + it(`Correct JS output for ${fileName}`, () => (this.emit && compilerTest.verifyJavaScriptOutput())); + it(`Correct Sourcemap output for ${fileName}`, () => compilerTest.verifySourceMapOutput()); + it(`Correct type/symbol baselines for ${fileName}`, () => compilerTest.verifyTypesAndSymbols()); + after(() => { + compilerTest = undefined!; + }); + } - private parseOptions() { - if (this.options && this.options.length > 0) { - this.emit = false; - - const opts = this.options.split(","); - for (const opt of opts) { - switch (opt) { - case "emit": - this.emit = true; - break; - default: - throw new Error("unsupported flag"); - } + private parseOptions() { + if (this.options && this.options.length > 0) { + this.emit = false; + + const opts = this.options.split(","); + for (const opt of opts) { + switch (opt) { + case "emit": + this.emit = true; + break; + default: + throw new Error("unsupported flag"); } } } } +} - class CompilerTest { - private static varyBy: readonly string[] = [ - "module", - "moduleResolution", - "moduleDetection", - "target", - "jsx", - "removeComments", - "importHelpers", - "importHelpers", - "downlevelIteration", - "isolatedModules", - "strict", - "noImplicitAny", - "strictNullChecks", - "strictFunctionTypes", - "strictBindCallApply", - "strictPropertyInitialization", - "noImplicitThis", - "alwaysStrict", - "allowSyntheticDefaultImports", - "esModuleInterop", - "emitDecoratorMetadata", - "skipDefaultLibCheck", - "preserveConstEnums", - "skipLibCheck", - "exactOptionalPropertyTypes", - "useDefineForClassFields", - "useUnknownInCatchVariables", - "noUncheckedIndexedAccess", - "noPropertyAccessFromIndexSignature", - ]; - private fileName: string; - private justName: string; - private configuredName: string; - private lastUnit: TestCaseParser.TestUnitData; - private harnessSettings: TestCaseParser.CompilerSettings; - private hasNonDtsFiles: boolean; - private result: compiler.CompilationResult; - private options: ts.CompilerOptions; - private tsConfigFiles: Compiler.TestFile[]; - // equivalent to the files that will be passed on the command line - private toBeCompiled: Compiler.TestFile[]; - // equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files) - private otherFiles: Compiler.TestFile[]; - - constructor(fileName: string, testCaseContent?: TestCaseParser.TestCaseContent, configurationOverrides?: TestCaseParser.CompilerSettings) { - this.fileName = fileName; - this.justName = vpath.basename(fileName); - this.configuredName = this.justName; - if (configurationOverrides) { - let configuredName = ""; - const keys = Object - .keys(configurationOverrides) - .sort(); - for (const key of keys) { - if (configuredName) { - configuredName += ","; - } - configuredName += `${key.toLowerCase()}=${configurationOverrides[key].toLowerCase()}`; - } +class CompilerTest { + private static varyBy: readonly string[] = [ + "module", + "moduleResolution", + "moduleDetection", + "target", + "jsx", + "removeComments", + "importHelpers", + "importHelpers", + "downlevelIteration", + "isolatedModules", + "strict", + "noImplicitAny", + "strictNullChecks", + "strictFunctionTypes", + "strictBindCallApply", + "strictPropertyInitialization", + "noImplicitThis", + "alwaysStrict", + "allowSyntheticDefaultImports", + "esModuleInterop", + "emitDecoratorMetadata", + "skipDefaultLibCheck", + "preserveConstEnums", + "skipLibCheck", + "exactOptionalPropertyTypes", + "useDefineForClassFields", + "useUnknownInCatchVariables", + "noUncheckedIndexedAccess", + "noPropertyAccessFromIndexSignature", + ]; + private fileName: string; + private justName: string; + private configuredName: string; + private lastUnit: TestCaseParser.TestUnitData; + private harnessSettings: TestCaseParser.CompilerSettings; + private hasNonDtsFiles: boolean; + private result: compiler.CompilationResult; + private options: ts.CompilerOptions; + private tsConfigFiles: Compiler.TestFile[]; + // equivalent to the files that will be passed on the command line + private toBeCompiled: Compiler.TestFile[]; + // equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files) + private otherFiles: Compiler.TestFile[]; + + constructor(fileName: string, testCaseContent?: TestCaseParser.TestCaseContent, configurationOverrides?: TestCaseParser.CompilerSettings) { + this.fileName = fileName; + this.justName = vpath.basename(fileName); + this.configuredName = this.justName; + if (configurationOverrides) { + let configuredName = ""; + const keys = Object + .keys(configurationOverrides) + .sort(); + for (const key of keys) { if (configuredName) { - const extname = vpath.extname(this.justName); - const basename = vpath.basename(this.justName, extname, /*ignoreCase*/ true); - this.configuredName = `${basename}(${configuredName})${extname}`; + configuredName += ","; } + configuredName += `${key.toLowerCase()}=${configurationOverrides[key].toLowerCase()}`; } - - const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/"; - - if (testCaseContent === undefined) { - testCaseContent = TestCaseParser.makeUnitsFromTest(IO.readFile(fileName)!, fileName, rootDir); + if (configuredName) { + const extname = vpath.extname(this.justName); + const basename = vpath.basename(this.justName, extname, /*ignoreCase*/ true); + this.configuredName = `${basename}(${configuredName})${extname}`; } + } - if (configurationOverrides) { - testCaseContent = { ...testCaseContent, settings: { ...testCaseContent.settings, ...configurationOverrides } }; - } + const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/"; - const units = testCaseContent.testUnitData; - this.harnessSettings = testCaseContent.settings; - let tsConfigOptions: ts.CompilerOptions | undefined; - this.tsConfigFiles = []; - if (testCaseContent.tsConfig) { - assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`); - assert.equal(testCaseContent.tsConfig.raw.exclude, undefined, `exclude in tsconfig is not currently supported`); + if (testCaseContent === undefined) { + testCaseContent = TestCaseParser.makeUnitsFromTest(IO.readFile(fileName)!, fileName, rootDir); + } - tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options); - this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData!, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath))); - } - else { - const baseUrl = this.harnessSettings.baseUrl; - if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) { - this.harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir); - } - } + if (configurationOverrides) { + testCaseContent = { ...testCaseContent, settings: { ...testCaseContent.settings, ...configurationOverrides } }; + } - this.lastUnit = units[units.length - 1]; - this.hasNonDtsFiles = units.some(unit => !ts.fileExtensionIs(unit.name, ts.Extension.Dts)); - // We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test) - // If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references, - // otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another. - this.toBeCompiled = []; - this.otherFiles = []; - - if (testCaseContent.settings.noImplicitReferences || /require\(/.test(this.lastUnit.content) || /reference\spath/.test(this.lastUnit.content)) { - this.toBeCompiled.push(this.createHarnessTestFile(this.lastUnit, rootDir)); - units.forEach(unit => { - if (unit.name !== this.lastUnit.name) { - this.otherFiles.push(this.createHarnessTestFile(unit, rootDir)); - } - }); - } - else { - this.toBeCompiled = units.map(unit => { - return this.createHarnessTestFile(unit, rootDir); - }); - } + const units = testCaseContent.testUnitData; + this.harnessSettings = testCaseContent.settings; + let tsConfigOptions: ts.CompilerOptions | undefined; + this.tsConfigFiles = []; + if (testCaseContent.tsConfig) { + assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`); + assert.equal(testCaseContent.tsConfig.raw.exclude, undefined, `exclude in tsconfig is not currently supported`); - if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) { - tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath); - tsConfigOptions.configFile!.fileName = tsConfigOptions.configFilePath; + tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options); + this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData!, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath))); + } + else { + const baseUrl = this.harnessSettings.baseUrl; + if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) { + this.harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir); } - - this.result = Compiler.compileFiles( - this.toBeCompiled, - this.otherFiles, - this.harnessSettings, - /*options*/ tsConfigOptions, - /*currentDirectory*/ this.harnessSettings.currentDirectory, - testCaseContent.symlinks - ); - - this.options = this.result.options; } - public static getConfigurations(file: string): CompilerFileBasedTest { - // also see `parseCompilerTestConfigurations` in tests/webTestServer.ts - const content = IO.readFile(file)!; - const settings = TestCaseParser.extractCompilerSettings(content); - const configurations = getFileBasedTestConfigurations(settings, CompilerTest.varyBy); - return { file, configurations, content }; + this.lastUnit = units[units.length - 1]; + this.hasNonDtsFiles = units.some(unit => !ts.fileExtensionIs(unit.name, ts.Extension.Dts)); + // We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test) + // If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references, + // otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another. + this.toBeCompiled = []; + this.otherFiles = []; + + if (testCaseContent.settings.noImplicitReferences || /require\(/.test(this.lastUnit.content) || /reference\spath/.test(this.lastUnit.content)) { + this.toBeCompiled.push(this.createHarnessTestFile(this.lastUnit, rootDir)); + units.forEach(unit => { + if (unit.name !== this.lastUnit.name) { + this.otherFiles.push(this.createHarnessTestFile(unit, rootDir)); + } + }); } - - public verifyDiagnostics() { - // check errors - Compiler.doErrorBaseline( - this.configuredName, - this.tsConfigFiles.concat(this.toBeCompiled, this.otherFiles), - this.result.diagnostics, - !!this.options.pretty); + else { + this.toBeCompiled = units.map(unit => { + return this.createHarnessTestFile(unit, rootDir); + }); } - public verifyModuleResolution() { - if (this.options.traceResolution) { - Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".trace.json"), - JSON.stringify(this.result.traces.map(Utils.sanitizeTraceResolutionLogEntry), undefined, 4)); - } + if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) { + tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath); + tsConfigOptions.configFile!.fileName = tsConfigOptions.configFilePath; } - public verifySourceMapRecord() { - if (this.options.sourceMap || this.options.inlineSourceMap || this.options.declarationMap) { - const record = Utils.removeTestPathPrefixes(this.result.getSourceMapRecord()!); - const baseline = (this.options.noEmitOnError && this.result.diagnostics.length !== 0) || record === undefined - // Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required. - ? null // eslint-disable-line no-null/no-null - : record; - Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".sourcemap.txt"), baseline); - } + this.result = Compiler.compileFiles( + this.toBeCompiled, + this.otherFiles, + this.harnessSettings, + /*options*/ tsConfigOptions, + /*currentDirectory*/ this.harnessSettings.currentDirectory, + testCaseContent.symlinks + ); + + this.options = this.result.options; + } + + public static getConfigurations(file: string): CompilerFileBasedTest { + // also see `parseCompilerTestConfigurations` in tests/webTestServer.ts + const content = IO.readFile(file)!; + const settings = TestCaseParser.extractCompilerSettings(content); + const configurations = getFileBasedTestConfigurations(settings, CompilerTest.varyBy); + return { file, configurations, content }; + } + + public verifyDiagnostics() { + // check errors + Compiler.doErrorBaseline( + this.configuredName, + this.tsConfigFiles.concat(this.toBeCompiled, this.otherFiles), + this.result.diagnostics, + !!this.options.pretty); + } + + public verifyModuleResolution() { + if (this.options.traceResolution) { + Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".trace.json"), + JSON.stringify(this.result.traces.map(Utils.sanitizeTraceResolutionLogEntry), undefined, 4)); } + } - public verifyJavaScriptOutput() { - if (this.hasNonDtsFiles) { - Compiler.doJsEmitBaseline( - this.configuredName, - this.fileName, - this.options, - this.result, - this.tsConfigFiles, - this.toBeCompiled, - this.otherFiles, - this.harnessSettings); - } + public verifySourceMapRecord() { + if (this.options.sourceMap || this.options.inlineSourceMap || this.options.declarationMap) { + const record = Utils.removeTestPathPrefixes(this.result.getSourceMapRecord()!); + const baseline = (this.options.noEmitOnError && this.result.diagnostics.length !== 0) || record === undefined + // Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required. + ? null // eslint-disable-line no-null/no-null + : record; + Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".sourcemap.txt"), baseline); } + } - public verifySourceMapOutput() { - Compiler.doSourcemapBaseline( + public verifyJavaScriptOutput() { + if (this.hasNonDtsFiles) { + Compiler.doJsEmitBaseline( this.configuredName, + this.fileName, this.options, this.result, + this.tsConfigFiles, + this.toBeCompiled, + this.otherFiles, this.harnessSettings); } + } - public verifyTypesAndSymbols() { - if (this.fileName.indexOf("APISample") >= 0) { - return; - } - - const noTypesAndSymbols = - this.harnessSettings.noTypesAndSymbols && - this.harnessSettings.noTypesAndSymbols.toLowerCase() === "true"; - if (noTypesAndSymbols) { - return; - } + public verifySourceMapOutput() { + Compiler.doSourcemapBaseline( + this.configuredName, + this.options, + this.result, + this.harnessSettings); + } - Compiler.doTypeAndSymbolBaseline( - this.configuredName, - this.result.program!, - this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program!.getSourceFile(file.unitName)), - /*opts*/ undefined, - /*multifile*/ undefined, - /*skipTypeBaselines*/ undefined, - /*skipSymbolBaselines*/ undefined, - !!ts.length(this.result.diagnostics) - ); + public verifyTypesAndSymbols() { + if (this.fileName.indexOf("APISample") >= 0) { + return; } - private makeUnitName(name: string, root: string) { - const path = ts.toPath(name, root, ts.identity); - const pathStart = ts.toPath(IO.getCurrentDirectory(), "", ts.identity); - return pathStart ? path.replace(pathStart, "/") : path; + const noTypesAndSymbols = + this.harnessSettings.noTypesAndSymbols && + this.harnessSettings.noTypesAndSymbols.toLowerCase() === "true"; + if (noTypesAndSymbols) { + return; } - private createHarnessTestFile(lastUnit: TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Compiler.TestFile { - return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions }; - } + Compiler.doTypeAndSymbolBaseline( + this.configuredName, + this.result.program!, + this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program!.getSourceFile(file.unitName)), + /*opts*/ undefined, + /*multifile*/ undefined, + /*skipTypeBaselines*/ undefined, + /*skipSymbolBaselines*/ undefined, + !!ts.length(this.result.diagnostics) + ); + } + + private makeUnitName(name: string, root: string) { + const path = ts.toPath(name, root, ts.identity); + const pathStart = ts.toPath(IO.getCurrentDirectory(), "", ts.identity); + return pathStart ? path.replace(pathStart, "/") : path; + } + + private createHarnessTestFile(lastUnit: TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Compiler.TestFile { + return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions }; } } diff --git a/src/testRunner/documentsRef.ts b/src/testRunner/documentsRef.ts deleted file mode 100644 index d3d92746b4ed7..0000000000000 --- a/src/testRunner/documentsRef.ts +++ /dev/null @@ -1,2 +0,0 @@ -// empty ref to documents so it can be referenced by unittests -namespace documents {} \ No newline at end of file diff --git a/src/testRunner/evaluatorRef.ts b/src/testRunner/evaluatorRef.ts deleted file mode 100644 index cc81ec2402db5..0000000000000 --- a/src/testRunner/evaluatorRef.ts +++ /dev/null @@ -1,2 +0,0 @@ -// empty ref to evaluator so it can be referenced by unittests -namespace evaluator {} \ No newline at end of file diff --git a/src/testRunner/externalCompileRunner.ts b/src/testRunner/externalCompileRunner.ts index 92acf6532b088..766b939ab4b0d 100644 --- a/src/testRunner/externalCompileRunner.ts +++ b/src/testRunner/externalCompileRunner.ts @@ -1,323 +1,324 @@ -namespace Harness { - const fs: typeof import("fs") = require("fs"); - const path: typeof import("path") = require("path"); - const del: typeof import("del") = require("del"); - - interface ExecResult { - stdout: Buffer; - stderr: Buffer; - status: number | null; - } +import * as del from "del"; +import * as fs from "fs"; +import * as path from "path"; + +import * as ts from "./_namespaces/ts"; +import { Baseline, IO, isWorker, RunnerBase, TestRunnerKind } from "./_namespaces/Harness"; + +interface ExecResult { + stdout: Buffer; + stderr: Buffer; + status: number | null; +} + +interface UserConfig { + types: string[]; + cloneUrl: string; + branch?: string; + path?: string; +} - interface UserConfig { - types: string[]; - cloneUrl: string; - branch?: string; - path?: string; +abstract class ExternalCompileRunnerBase extends RunnerBase { + abstract testDir: string; + abstract report(result: ExecResult): string | null; + enumerateTestFiles() { + return IO.getDirectories(this.testDir); } + /** Setup the runner's tests so that they are ready to be executed by the harness + * The first test should be a describe/it block that sets up the harness's compiler instance appropriately + */ + initializeTests(): void { + // Read in and evaluate the test list + const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const cls = this; + describe(`${this.kind()} code samples`, function (this: Mocha.Suite) { + this.timeout(600_000); // 10 minutes + for (const test of testList) { + cls.runTest(typeof test === "string" ? test : test.file); + } + }); + } + private runTest(directoryName: string) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const cls = this; + const timeout = 600_000; // 10 minutes + describe(directoryName, function (this: Mocha.Suite) { + this.timeout(timeout); + const cp: typeof import("child_process") = require("child_process"); - abstract class ExternalCompileRunnerBase extends RunnerBase { - abstract testDir: string; - abstract report(result: ExecResult): string | null; - enumerateTestFiles() { - return IO.getDirectories(this.testDir); - } - /** Setup the runner's tests so that they are ready to be executed by the harness - * The first test should be a describe/it block that sets up the harness's compiler instance appropriately - */ - initializeTests(): void { - // Read in and evaluate the test list - const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const cls = this; - describe(`${this.kind()} code samples`, function (this: Mocha.Suite) { - this.timeout(600_000); // 10 minutes - for (const test of testList) { - cls.runTest(typeof test === "string" ? test : test.file); + it("should build successfully", () => { + let cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directoryName); + const originalCwd = cwd; + const stdio = isWorker ? "pipe" : "inherit"; + let types: string[] | undefined; + if (fs.existsSync(path.join(cwd, "test.json"))) { + const config = JSON.parse(fs.readFileSync(path.join(cwd, "test.json"), { encoding: "utf8" })) as UserConfig; + ts.Debug.assert(!!config.types, "Bad format from test.json: Types field must be present."); + ts.Debug.assert(!!config.cloneUrl, "Bad format from test.json: cloneUrl field must be present."); + const submoduleDir = path.join(cwd, directoryName); + if (!fs.existsSync(submoduleDir)) { + exec("git", ["--work-tree", submoduleDir, "clone", "-b", config.branch || "master", config.cloneUrl, path.join(submoduleDir, ".git")], { cwd }); + } + else { + exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "checkout", config.branch || "master"], { cwd: submoduleDir }); + exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "reset", "HEAD", "--hard"], { cwd: submoduleDir }); + exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "clean", "-f"], { cwd: submoduleDir }); + exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "pull", "-f"], { cwd: submoduleDir }); + } + + types = config.types; + + cwd = config.path ? path.join(cwd, config.path) : submoduleDir; } - }); - } - private runTest(directoryName: string) { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const cls = this; - const timeout = 600_000; // 10 minutes - describe(directoryName, function (this: Mocha.Suite) { - this.timeout(timeout); - const cp: typeof import("child_process") = require("child_process"); - - it("should build successfully", () => { - let cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directoryName); - const originalCwd = cwd; - const stdio = isWorker ? "pipe" : "inherit"; - let types: string[] | undefined; - if (fs.existsSync(path.join(cwd, "test.json"))) { - const config = JSON.parse(fs.readFileSync(path.join(cwd, "test.json"), { encoding: "utf8" })) as UserConfig; - ts.Debug.assert(!!config.types, "Bad format from test.json: Types field must be present."); - ts.Debug.assert(!!config.cloneUrl, "Bad format from test.json: cloneUrl field must be present."); - const submoduleDir = path.join(cwd, directoryName); - if (!fs.existsSync(submoduleDir)) { - exec("git", ["--work-tree", submoduleDir, "clone", "-b", config.branch || "master", config.cloneUrl, path.join(submoduleDir, ".git")], { cwd }); - } - else { - exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "checkout", config.branch || "master"], { cwd: submoduleDir }); - exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "reset", "HEAD", "--hard"], { cwd: submoduleDir }); - exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "clean", "-f"], { cwd: submoduleDir }); - exec("git", ["--git-dir", path.join(submoduleDir, ".git"), "--work-tree", submoduleDir, "pull", "-f"], { cwd: submoduleDir }); - } - - types = config.types; - - cwd = config.path ? path.join(cwd, config.path) : submoduleDir; + const npmVersionText = exec("npm", ["--version"], { cwd, stdio: "pipe" })?.trim(); + const npmVersion = npmVersionText ? ts.Version.tryParse(npmVersionText.trim()) : undefined; + const isV7OrLater = !!npmVersion && npmVersion.major >= 7; + if (fs.existsSync(path.join(cwd, "package.json"))) { + if (fs.existsSync(path.join(cwd, "package-lock.json"))) { + fs.unlinkSync(path.join(cwd, "package-lock.json")); } - const npmVersionText = exec("npm", ["--version"], { cwd, stdio: "pipe" })?.trim(); - const npmVersion = npmVersionText ? ts.Version.tryParse(npmVersionText.trim()) : undefined; - const isV7OrLater = !!npmVersion && npmVersion.major >= 7; - if (fs.existsSync(path.join(cwd, "package.json"))) { - if (fs.existsSync(path.join(cwd, "package-lock.json"))) { - fs.unlinkSync(path.join(cwd, "package-lock.json")); - } - if (fs.existsSync(path.join(cwd, "node_modules"))) { - del.sync(path.join(cwd, "node_modules"), { force: true }); - } - exec("npm", ["i", "--ignore-scripts", ...(isV7OrLater ? ["--legacy-peer-deps"] : [])], { cwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure + if (fs.existsSync(path.join(cwd, "node_modules"))) { + del.sync(path.join(cwd, "node_modules"), { force: true }); } - const args = [path.join(IO.getWorkspaceRoot(), "built/local/tsc.js")]; - if (types) { - args.push("--types", types.join(",")); - // Also actually install those types (for, eg, the js projects which need node) - if (types.length) { - exec("npm", ["i", ...types.map(t => `@types/${t}`), "--no-save", "--ignore-scripts", ...(isV7OrLater ? ["--legacy-peer-deps"] : [])], { cwd: originalCwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure - } + exec("npm", ["i", "--ignore-scripts", ...(isV7OrLater ? ["--legacy-peer-deps"] : [])], { cwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure + } + const args = [path.join(IO.getWorkspaceRoot(), "built/local/tsc.js")]; + if (types) { + args.push("--types", types.join(",")); + // Also actually install those types (for, eg, the js projects which need node) + if (types.length) { + exec("npm", ["i", ...types.map(t => `@types/${t}`), "--no-save", "--ignore-scripts", ...(isV7OrLater ? ["--legacy-peer-deps"] : [])], { cwd: originalCwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure } - args.push("--noEmit"); - Baseline.runBaseline(`${cls.kind()}/${directoryName}.log`, cls.report(cp.spawnSync(`node`, args, { cwd, timeout, shell: true }))); - - function exec(command: string, args: string[], options: { cwd: string, timeout?: number, stdio?: import("child_process").StdioOptions }): string | undefined { - const res = cp.spawnSync(isWorker ? `${command} 2>&1` : command, args, { shell: true, stdio, ...options }); - if (res.status !== 0) { - throw new Error(`${command} ${args.join(" ")} for ${directoryName} failed: ${res.stdout && res.stdout.toString()}`); - } - return options.stdio === "pipe" ? res.stdout.toString("utf8") : undefined; + } + args.push("--noEmit"); + Baseline.runBaseline(`${cls.kind()}/${directoryName}.log`, cls.report(cp.spawnSync(`node`, args, { cwd, timeout, shell: true }))); + + function exec(command: string, args: string[], options: { cwd: string, timeout?: number, stdio?: import("child_process").StdioOptions }): string | undefined { + const res = cp.spawnSync(isWorker ? `${command} 2>&1` : command, args, { shell: true, stdio, ...options }); + if (res.status !== 0) { + throw new Error(`${command} ${args.join(" ")} for ${directoryName} failed: ${res.stdout && res.stdout.toString()}`); } - }); + return options.stdio === "pipe" ? res.stdout.toString("utf8") : undefined; + } }); - } + }); } +} - export class UserCodeRunner extends ExternalCompileRunnerBase { - readonly testDir = "tests/cases/user/"; - kind(): TestRunnerKind { - return "user"; - } - report(result: ExecResult) { - // eslint-disable-next-line no-null/no-null - return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} +export class UserCodeRunner extends ExternalCompileRunnerBase { + readonly testDir = "tests/cases/user/"; + kind(): TestRunnerKind { + return "user"; + } + report(result: ExecResult) { + // eslint-disable-next-line no-null/no-null + return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} Standard output: ${sortErrors(stripAbsoluteImportPaths(result.stdout.toString().replace(/\r\n/g, "\n")))} Standard error: ${stripAbsoluteImportPaths(result.stderr.toString().replace(/\r\n/g, "\n"))}`; - } } +} - export class DockerfileRunner extends ExternalCompileRunnerBase { - readonly testDir = "tests/cases/docker/"; - kind(): TestRunnerKind { - return "docker"; - } - initializeTests(): void { - // Read in and evaluate the test list - const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const cls = this; - describe(`${this.kind()} code samples`, function (this: Mocha.Suite) { - this.timeout(cls.timeout); // 20 minutes - before(() => { - cls.exec("docker", ["build", ".", "-t", "typescript/typescript"], { cwd: IO.getWorkspaceRoot() }); // cached because workspace is hashed to determine cacheability - }); - for (const test of testList) { - const directory = typeof test === "string" ? test : test.file; - const cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directory); - it(`should build ${directory} successfully`, () => { - const imageName = `tstest/${directory}`; - cls.exec("docker", ["build", "--no-cache", ".", "-t", imageName], { cwd }); // --no-cache so the latest version of the repos referenced is always fetched - const cp: typeof import("child_process") = require("child_process"); - Baseline.runBaseline(`${cls.kind()}/${directory}.log`, cls.report(cp.spawnSync(`docker`, ["run", imageName], { cwd, timeout: cls.timeout, shell: true }))); - }); - } +export class DockerfileRunner extends ExternalCompileRunnerBase { + readonly testDir = "tests/cases/docker/"; + kind(): TestRunnerKind { + return "docker"; + } + initializeTests(): void { + // Read in and evaluate the test list + const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles(); + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const cls = this; + describe(`${this.kind()} code samples`, function (this: Mocha.Suite) { + this.timeout(cls.timeout); // 20 minutes + before(() => { + cls.exec("docker", ["build", ".", "-t", "typescript/typescript"], { cwd: IO.getWorkspaceRoot() }); // cached because workspace is hashed to determine cacheability }); - } - - private timeout = 1_200_000; // 20 minutes; - private exec(command: string, args: string[], options: { cwd: string }): void { - const cp: typeof import("child_process") = require("child_process"); - const stdio = isWorker ? "pipe" : "inherit"; - const res = cp.spawnSync(isWorker ? `${command} 2>&1` : command, args, { timeout: this.timeout, shell: true, stdio, ...options }); - if (res.status !== 0) { - throw new Error(`${command} ${args.join(" ")} for ${options.cwd} failed: ${res.stdout && res.stdout.toString()}`); + for (const test of testList) { + const directory = typeof test === "string" ? test : test.file; + const cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directory); + it(`should build ${directory} successfully`, () => { + const imageName = `tstest/${directory}`; + cls.exec("docker", ["build", "--no-cache", ".", "-t", imageName], { cwd }); // --no-cache so the latest version of the repos referenced is always fetched + const cp: typeof import("child_process") = require("child_process"); + Baseline.runBaseline(`${cls.kind()}/${directory}.log`, cls.report(cp.spawnSync(`docker`, ["run", imageName], { cwd, timeout: cls.timeout, shell: true }))); + }); } + }); + } + + private timeout = 1_200_000; // 20 minutes; + private exec(command: string, args: string[], options: { cwd: string }): void { + const cp: typeof import("child_process") = require("child_process"); + const stdio = isWorker ? "pipe" : "inherit"; + const res = cp.spawnSync(isWorker ? `${command} 2>&1` : command, args, { timeout: this.timeout, shell: true, stdio, ...options }); + if (res.status !== 0) { + throw new Error(`${command} ${args.join(" ")} for ${options.cwd} failed: ${res.stdout && res.stdout.toString()}`); } - report(result: ExecResult) { - // eslint-disable-next-line no-null/no-null - return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} + } + report(result: ExecResult) { + // eslint-disable-next-line no-null/no-null + return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} Standard output: ${sanitizeDockerfileOutput(result.stdout.toString())} Standard error: ${sanitizeDockerfileOutput(result.stderr.toString())}`; - } } +} - function sanitizeDockerfileOutput(result: string): string { - return [ - normalizeNewlines, - stripANSIEscapes, - stripRushStageNumbers, - stripWebpackHash, - sanitizeVersionSpecifiers, - sanitizeTimestamps, - sanitizeSizes, - sanitizeUnimportantGulpOutput, - stripAbsoluteImportPaths, - ].reduce((result, f) => f(result), result); - } +function sanitizeDockerfileOutput(result: string): string { + return [ + normalizeNewlines, + stripANSIEscapes, + stripRushStageNumbers, + stripWebpackHash, + sanitizeVersionSpecifiers, + sanitizeTimestamps, + sanitizeSizes, + sanitizeUnimportantGulpOutput, + stripAbsoluteImportPaths, + ].reduce((result, f) => f(result), result); +} - function normalizeNewlines(result: string): string { - return result.replace(/\r\n/g, "\n"); - } +function normalizeNewlines(result: string): string { + return result.replace(/\r\n/g, "\n"); +} - function stripANSIEscapes(result: string): string { - return result.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ""); - } +function stripANSIEscapes(result: string): string { + return result.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ""); +} - function stripRushStageNumbers(result: string): string { - return result.replace(/\d+ of \d+:/g, "XX of XX:"); - } +function stripRushStageNumbers(result: string): string { + return result.replace(/\d+ of \d+:/g, "XX of XX:"); +} - function stripWebpackHash(result: string): string { - return result.replace(/Hash: \w+/g, "Hash: [redacted]"); - } +function stripWebpackHash(result: string): string { + return result.replace(/Hash: \w+/g, "Hash: [redacted]"); +} - function sanitizeSizes(result: string): string { - return result.replace(/\d+(\.\d+)? ((Ki|M)B|bytes)/g, "X KiB"); - } +function sanitizeSizes(result: string): string { + return result.replace(/\d+(\.\d+)? ((Ki|M)B|bytes)/g, "X KiB"); +} - /** - * Gulp's output order within a `parallel` block is nondeterministic (and there's no way to configure it to execute in series), - * so we purge as much of the gulp output as we can - */ - function sanitizeUnimportantGulpOutput(result: string): string { - return result.replace(/^.*(\] (Starting)|(Finished)).*$/gm, "") // "gulp" task start/end messages (nondeterministic order) - .replace(/^.*(\] . (finished)|(started)).*$/gm, "") // "just" task start/end messages (nondeterministic order) - .replace(/^.*\] Respawned to PID: \d+.*$/gm, "") // PID of child is OS and system-load dependent (likely stableish in a container but still dangerous) - .replace(/\n+/g, "\n") - .replace(/\/tmp\/yarn--.*?\/node/g, ""); - } +/** + * Gulp's output order within a `parallel` block is nondeterministic (and there's no way to configure it to execute in series), + * so we purge as much of the gulp output as we can + */ +function sanitizeUnimportantGulpOutput(result: string): string { + return result.replace(/^.*(\] (Starting)|(Finished)).*$/gm, "") // "gulp" task start/end messages (nondeterministic order) + .replace(/^.*(\] . (finished)|(started)).*$/gm, "") // "just" task start/end messages (nondeterministic order) + .replace(/^.*\] Respawned to PID: \d+.*$/gm, "") // PID of child is OS and system-load dependent (likely stableish in a container but still dangerous) + .replace(/\n+/g, "\n") + .replace(/\/tmp\/yarn--.*?\/node/g, ""); +} - function sanitizeTimestamps(result: string): string { - return result.replace(/\[\d?\d:\d\d:\d\d (A|P)M\]/g, "[XX:XX:XX XM]") - .replace(/\[\d?\d:\d\d:\d\d\]/g, "[XX:XX:XX]") - .replace(/\/\d+-\d+-[\d_TZ]+-debug.log/g, "\/XXXX-XX-XXXXXXXXX-debug.log") - .replace(/\d+\/\d+\/\d+ \d+:\d+:\d+ (AM|PM)/g, "XX/XX/XX XX:XX:XX XM") - .replace(/\d+(\.\d+)? sec(onds?)?/g, "? seconds") - .replace(/\d+(\.\d+)? min(utes?)?/g, "") - .replace(/\d+(\.\d+)? ?m?s/g, "?s") - .replace(/ \(\?s\)/g, ""); - } +function sanitizeTimestamps(result: string): string { + return result.replace(/\[\d?\d:\d\d:\d\d (A|P)M\]/g, "[XX:XX:XX XM]") + .replace(/\[\d?\d:\d\d:\d\d\]/g, "[XX:XX:XX]") + .replace(/\/\d+-\d+-[\d_TZ]+-debug.log/g, "\/XXXX-XX-XXXXXXXXX-debug.log") + .replace(/\d+\/\d+\/\d+ \d+:\d+:\d+ (AM|PM)/g, "XX/XX/XX XX:XX:XX XM") + .replace(/\d+(\.\d+)? sec(onds?)?/g, "? seconds") + .replace(/\d+(\.\d+)? min(utes?)?/g, "") + .replace(/\d+(\.\d+)? ?m?s/g, "?s") + .replace(/ \(\?s\)/g, ""); +} - function sanitizeVersionSpecifiers(result: string): string { - return result - .replace(/\d+.\d+.\d+-insiders.\d\d\d\d\d\d\d\d/g, "X.X.X-insiders.xxxxxxxx") - .replace(/Rush Multi-Project Build Tool (\d+)\.\d+\.\d+/g, "Rush Multi-Project Build Tool $1.X.X") - .replace(/([@v\()])\d+\.\d+\.\d+/g, "$1X.X.X") - .replace(/webpack (\d+)\.\d+\.\d+/g, "webpack $1.X.X") - .replace(/Webpack version: (\d+)\.\d+\.\d+/g, "Webpack version: $1.X.X"); - } +function sanitizeVersionSpecifiers(result: string): string { + return result + .replace(/\d+.\d+.\d+-insiders.\d\d\d\d\d\d\d\d/g, "X.X.X-insiders.xxxxxxxx") + .replace(/Rush Multi-Project Build Tool (\d+)\.\d+\.\d+/g, "Rush Multi-Project Build Tool $1.X.X") + .replace(/([@v\()])\d+\.\d+\.\d+/g, "$1X.X.X") + .replace(/webpack (\d+)\.\d+\.\d+/g, "webpack $1.X.X") + .replace(/Webpack version: (\d+)\.\d+\.\d+/g, "Webpack version: $1.X.X"); +} - /** - * Import types and some other error messages use absolute paths in errors as they have no context to be written relative to; - * This is problematic for error baselines, so we grep for them and strip them out. - */ - function stripAbsoluteImportPaths(result: string) { - const workspaceRegexp = new RegExp(IO.getWorkspaceRoot().replace(/\\/g, "\\\\"), "g"); - return result - .replace(/import\(".*?\/tests\/cases\/user\//g, `import("/`) - .replace(/Module '".*?\/tests\/cases\/user\//g, `Module '"/`) - .replace(workspaceRegexp, "../../.."); - } +/** + * Import types and some other error messages use absolute paths in errors as they have no context to be written relative to; + * This is problematic for error baselines, so we grep for them and strip them out. + */ +function stripAbsoluteImportPaths(result: string) { + const workspaceRegexp = new RegExp(IO.getWorkspaceRoot().replace(/\\/g, "\\\\"), "g"); + return result + .replace(/import\(".*?\/tests\/cases\/user\//g, `import("/`) + .replace(/Module '".*?\/tests\/cases\/user\//g, `Module '"/`) + .replace(workspaceRegexp, "../../.."); +} - function sortErrors(result: string) { - return ts.flatten(splitBy(result.split("\n"), s => /^\S+/.test(s)).sort(compareErrorStrings)).join("\n"); - } +function sortErrors(result: string) { + return ts.flatten(splitBy(result.split("\n"), s => /^\S+/.test(s)).sort(compareErrorStrings)).join("\n"); +} - const errorRegexp = /^(.+\.[tj]sx?)\((\d+),(\d+)\)(: error TS.*)/; - function compareErrorStrings(a: string[], b: string[]) { - ts.Debug.assertGreaterThanOrEqual(a.length, 1); - ts.Debug.assertGreaterThanOrEqual(b.length, 1); - const matchA = a[0].match(errorRegexp); - if (!matchA) { - return -1; - } - const matchB = b[0].match(errorRegexp); - if (!matchB) { - return 1; - } - const [, errorFileA, lineNumberStringA, columnNumberStringA, remainderA] = matchA; - const [, errorFileB, lineNumberStringB, columnNumberStringB, remainderB] = matchB; - return ts.comparePathsCaseSensitive(errorFileA, errorFileB) || - ts.compareValues(parseInt(lineNumberStringA), parseInt(lineNumberStringB)) || - ts.compareValues(parseInt(columnNumberStringA), parseInt(columnNumberStringB)) || - ts.compareStringsCaseSensitive(remainderA, remainderB) || - ts.compareStringsCaseSensitive(a.slice(1).join("\n"), b.slice(1).join("\n")); +const errorRegexp = /^(.+\.[tj]sx?)\((\d+),(\d+)\)(: error TS.*)/; +function compareErrorStrings(a: string[], b: string[]) { + ts.Debug.assertGreaterThanOrEqual(a.length, 1); + ts.Debug.assertGreaterThanOrEqual(b.length, 1); + const matchA = a[0].match(errorRegexp); + if (!matchA) { + return -1; + } + const matchB = b[0].match(errorRegexp); + if (!matchB) { + return 1; } + const [, errorFileA, lineNumberStringA, columnNumberStringA, remainderA] = matchA; + const [, errorFileB, lineNumberStringB, columnNumberStringB, remainderB] = matchB; + return ts.comparePathsCaseSensitive(errorFileA, errorFileB) || + ts.compareValues(parseInt(lineNumberStringA), parseInt(lineNumberStringB)) || + ts.compareValues(parseInt(columnNumberStringA), parseInt(columnNumberStringB)) || + ts.compareStringsCaseSensitive(remainderA, remainderB) || + ts.compareStringsCaseSensitive(a.slice(1).join("\n"), b.slice(1).join("\n")); +} - export class DefinitelyTypedRunner extends ExternalCompileRunnerBase { - readonly testDir = "../DefinitelyTyped/types/"; - workingDirectory = this.testDir; - kind(): TestRunnerKind { - return "dt"; - } - report(result: ExecResult) { - // eslint-disable-next-line no-null/no-null - return !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} +export class DefinitelyTypedRunner extends ExternalCompileRunnerBase { + readonly testDir = "../DefinitelyTyped/types/"; + workingDirectory = this.testDir; + kind(): TestRunnerKind { + return "dt"; + } + report(result: ExecResult) { + // eslint-disable-next-line no-null/no-null + return !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status} Standard output: ${result.stdout.toString().replace(/\r\n/g, "\n")} Standard error: ${result.stderr.toString().replace(/\r\n/g, "\n")}`; - } } +} - /** - * Split an array into multiple arrays whenever `isStart` returns true. - * @example - * splitBy([1,2,3,4,5,6], isOdd) - * ==> [[1, 2], [3, 4], [5, 6]] - * where - * const isOdd = n => !!(n % 2) - */ - function splitBy(xs: T[], isStart: (x: T) => boolean): T[][] { - const result = []; - let group: T[] = []; - for (const x of xs) { - if (isStart(x)) { - if (group.length) { - result.push(group); - } - group = [x]; - } - else { - group.push(x); +/** + * Split an array into multiple arrays whenever `isStart` returns true. + * @example + * splitBy([1,2,3,4,5,6], isOdd) + * ==> [[1, 2], [3, 4], [5, 6]] + * where + * const isOdd = n => !!(n % 2) + */ +function splitBy(xs: T[], isStart: (x: T) => boolean): T[][] { + const result = []; + let group: T[] = []; + for (const x of xs) { + if (isStart(x)) { + if (group.length) { + result.push(group); } + group = [x]; } - if (group.length) { - result.push(group); + else { + group.push(x); } - return result; } + if (group.length) { + result.push(group); + } + return result; } diff --git a/src/testRunner/fakesRef.ts b/src/testRunner/fakesRef.ts deleted file mode 100644 index b19d4cc8c80ed..0000000000000 --- a/src/testRunner/fakesRef.ts +++ /dev/null @@ -1,2 +0,0 @@ -// empty ref to fakes so it can be referenced by unittests -namespace fakes {} \ No newline at end of file diff --git a/src/testRunner/fourslashRef.ts b/src/testRunner/fourslashRef.ts deleted file mode 100644 index 23a50810a46af..0000000000000 --- a/src/testRunner/fourslashRef.ts +++ /dev/null @@ -1,2 +0,0 @@ -// empty ref to FourSlash so it can be referenced by unittests -namespace FourSlash {} \ No newline at end of file diff --git a/src/testRunner/fourslashRunner.ts b/src/testRunner/fourslashRunner.ts index b996542a0a55e..31312e37c6a20 100644 --- a/src/testRunner/fourslashRunner.ts +++ b/src/testRunner/fourslashRunner.ts @@ -1,72 +1,74 @@ -namespace Harness { - export class FourSlashRunner extends RunnerBase { - protected basePath: string; - protected testSuiteName: TestRunnerKind; +import * as FourSlash from "./_namespaces/FourSlash"; +import * as ts from "./_namespaces/ts"; +import { IO, RunnerBase, TestRunnerKind } from "./_namespaces/Harness"; - constructor(private testType: FourSlash.FourSlashTestType) { - super(); - switch (testType) { - case FourSlash.FourSlashTestType.Native: - this.basePath = "tests/cases/fourslash"; - this.testSuiteName = "fourslash"; - break; - case FourSlash.FourSlashTestType.Shims: - this.basePath = "tests/cases/fourslash/shims"; - this.testSuiteName = "fourslash-shims"; - break; - case FourSlash.FourSlashTestType.ShimsWithPreprocess: - this.basePath = "tests/cases/fourslash/shims-pp"; - this.testSuiteName = "fourslash-shims-pp"; - break; - case FourSlash.FourSlashTestType.Server: - this.basePath = "tests/cases/fourslash/server"; - this.testSuiteName = "fourslash-server"; - break; - default: - throw ts.Debug.assertNever(testType); - } - } +export class FourSlashRunner extends RunnerBase { + protected basePath: string; + protected testSuiteName: TestRunnerKind; - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false }); + constructor(private testType: FourSlash.FourSlashTestType) { + super(); + switch (testType) { + case FourSlash.FourSlashTestType.Native: + this.basePath = "tests/cases/fourslash"; + this.testSuiteName = "fourslash"; + break; + case FourSlash.FourSlashTestType.Shims: + this.basePath = "tests/cases/fourslash/shims"; + this.testSuiteName = "fourslash-shims"; + break; + case FourSlash.FourSlashTestType.ShimsWithPreprocess: + this.basePath = "tests/cases/fourslash/shims-pp"; + this.testSuiteName = "fourslash-shims-pp"; + break; + case FourSlash.FourSlashTestType.Server: + this.basePath = "tests/cases/fourslash/server"; + this.testSuiteName = "fourslash-server"; + break; + default: + throw ts.Debug.assertNever(testType); } + } - public kind() { - return this.testSuiteName; - } + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false }); + } + + public kind() { + return this.testSuiteName; + } - public initializeTests() { - if (this.tests.length === 0) { - this.tests = IO.enumerateTestFiles(this); - } + public initializeTests() { + if (this.tests.length === 0) { + this.tests = IO.enumerateTestFiles(this); + } - describe(this.testSuiteName + " tests", () => { - this.tests.forEach(test => { - const file = typeof test === "string" ? test : test.file; - describe(file, () => { - let fn = ts.normalizeSlashes(file); - const justName = fn.replace(/^.*[\\\/]/, ""); + describe(this.testSuiteName + " tests", () => { + this.tests.forEach(test => { + const file = typeof test === "string" ? test : test.file; + describe(file, () => { + let fn = ts.normalizeSlashes(file); + const justName = fn.replace(/^.*[\\\/]/, ""); - // Convert to relative path - const testIndex = fn.indexOf("tests/"); - if (testIndex >= 0) fn = fn.substr(testIndex); + // Convert to relative path + const testIndex = fn.indexOf("tests/"); + if (testIndex >= 0) fn = fn.substr(testIndex); - if (justName !== "fourslash.ts") { - it(this.testSuiteName + " test " + justName + " runs correctly", () => { - FourSlash.runFourSlashTest(this.basePath, this.testType, fn); - }); - } - }); + if (justName !== "fourslash.ts") { + it(this.testSuiteName + " test " + justName + " runs correctly", () => { + FourSlash.runFourSlashTest(this.basePath, this.testType, fn); + }); + } }); }); - } + }); } +} - export class GeneratedFourslashRunner extends FourSlashRunner { - constructor(testType: FourSlash.FourSlashTestType) { - super(testType); - this.basePath += "/generated/"; - } +export class GeneratedFourslashRunner extends FourSlashRunner { + constructor(testType: FourSlash.FourSlashTestType) { + super(testType); + this.basePath += "/generated/"; } } diff --git a/src/testRunner/parallel/host.ts b/src/testRunner/parallel/host.ts index 6c11b5a28996c..c0e38a3eb2721 100644 --- a/src/testRunner/parallel/host.ts +++ b/src/testRunner/parallel/host.ts @@ -1,629 +1,637 @@ -namespace Harness.Parallel.Host { - export function start() { - const Mocha = require("mocha") as typeof import("mocha"); - const Base = Mocha.reporters.Base; - const color = Base.color; - const cursor = Base.cursor; - const ms = require("ms") as typeof import("ms"); - const readline = require("readline") as typeof import("readline"); - const os = require("os") as typeof import("os"); - const tty = require("tty") as typeof import("tty"); - const isatty = tty.isatty(1) && tty.isatty(2); - const path = require("path") as typeof import("path"); - const { fork } = require("child_process") as typeof import("child_process"); - const { statSync } = require("fs") as typeof import("fs"); - - // NOTE: paths for module and types for FailedTestReporter _do not_ line up due to our use of --outFile for run.js - const FailedTestReporter = require(Utils.findUpFile("scripts/failed-tests.cjs")) as typeof import("../../../scripts/failed-tests.cjs"); - - const perfdataFileNameFragment = ".parallelperf"; - const perfData = readSavedPerfData(configOption); - const newTasks: Task[] = []; - let tasks: Task[] = []; - let unknownValue: string | undefined; - let totalCost = 0; - - class RemoteSuite extends Mocha.Suite { - suiteMap = new ts.Map(); - constructor(title: string) { - super(title); - this.pending = false; - this.delayed = false; - } - addSuite(suite: RemoteSuite) { - super.addSuite(suite); - this.suiteMap.set(suite.title, suite); - return this; - } - addTest(test: RemoteTest) { - return super.addTest(test); - } +import * as Utils from "../_namespaces/Utils"; +import * as ts from "../_namespaces/ts"; +import { + configOption, globalTimeout, IO, keepFailed, lightMode, noColors, runners, runUnitTests, stackTraceLimit, + taskConfigsFolder, TestConfig, TestRunnerKind, workerCount, +} from "../_namespaces/Harness"; +import { + ErrorInfo, ParallelClientMessage, ParallelHostMessage, shimNoopTestInterface, Task, TaskTimeout, TestInfo, +} from "../_namespaces/Harness.Parallel"; + +export function start() { + const Mocha = require("mocha") as typeof import("mocha"); + const Base = Mocha.reporters.Base; + const color = Base.color; + const cursor = Base.cursor; + const ms = require("ms") as typeof import("ms"); + const readline = require("readline") as typeof import("readline"); + const os = require("os") as typeof import("os"); + const tty = require("tty") as typeof import("tty"); + const isatty = tty.isatty(1) && tty.isatty(2); + const path = require("path") as typeof import("path"); + const { fork } = require("child_process") as typeof import("child_process"); + const { statSync } = require("fs") as typeof import("fs"); + + // NOTE: paths for module and types for FailedTestReporter _do not_ line up due to our use of --outFile for run.js + const FailedTestReporter = require(Utils.findUpFile("scripts/failed-tests.cjs")) as typeof import("../../../scripts/failed-tests.cjs"); + + const perfdataFileNameFragment = ".parallelperf"; + const perfData = readSavedPerfData(configOption); + const newTasks: Task[] = []; + let tasks: Task[] = []; + let unknownValue: string | undefined; + let totalCost = 0; + + class RemoteSuite extends Mocha.Suite { + suiteMap = new ts.Map(); + constructor(title: string) { + super(title); + this.pending = false; + this.delayed = false; } - - class RemoteTest extends Mocha.Test { - info: ErrorInfo | TestInfo; - constructor(info: ErrorInfo | TestInfo) { - super(info.name[info.name.length - 1]); - this.info = info; - this.state = "error" in info ? "failed" : "passed"; // eslint-disable-line local/no-in-operator - this.pending = false; - } + addSuite(suite: RemoteSuite) { + super.addSuite(suite); + this.suiteMap.set(suite.title, suite); + return this; } - - interface Worker { - process: import("child_process").ChildProcess; - accumulatedOutput: string; - currentTasks?: { file: string }[]; - timer?: any; + addTest(test: RemoteTest) { + return super.addTest(test); } + } - interface ProgressBarsOptions { - open: string; - close: string; - complete: string; - incomplete: string; - width: number; - noColors: boolean; + class RemoteTest extends Mocha.Test { + info: ErrorInfo | TestInfo; + constructor(info: ErrorInfo | TestInfo) { + super(info.name[info.name.length - 1]); + this.info = info; + this.state = "error" in info ? "failed" : "passed"; // eslint-disable-line local/no-in-operator + this.pending = false; } + } - interface ProgressBar { - lastN?: number; - title?: string; - progressColor?: string; - text?: string; - } + interface Worker { + process: import("child_process").ChildProcess; + accumulatedOutput: string; + currentTasks?: { file: string }[]; + timer?: any; + } - class ProgressBars { - public readonly _options: Readonly; - private _enabled: boolean; - private _lineCount: number; - private _progressBars: ProgressBar[]; - constructor(options?: Partial) { - if (!options) options = {}; - const open = options.open || "["; - const close = options.close || "]"; - const complete = options.complete || "▬"; - const incomplete = options.incomplete || Base.symbols.dot; - const maxWidth = Base.window.width - open.length - close.length - 34; - const width = minMax(options.width || maxWidth, 10, maxWidth); - this._options = { - open, - complete, - incomplete, - close, - width, - noColors: options.noColors || false - }; - - this._progressBars = []; - this._lineCount = 0; - this._enabled = false; + interface ProgressBarsOptions { + open: string; + close: string; + complete: string; + incomplete: string; + width: number; + noColors: boolean; + } + + interface ProgressBar { + lastN?: number; + title?: string; + progressColor?: string; + text?: string; + } + + class ProgressBars { + public readonly _options: Readonly; + private _enabled: boolean; + private _lineCount: number; + private _progressBars: ProgressBar[]; + constructor(options?: Partial) { + if (!options) options = {}; + const open = options.open || "["; + const close = options.close || "]"; + const complete = options.complete || "▬"; + const incomplete = options.incomplete || Base.symbols.dot; + const maxWidth = Base.window.width - open.length - close.length - 34; + const width = minMax(options.width || maxWidth, 10, maxWidth); + this._options = { + open, + complete, + incomplete, + close, + width, + noColors: options.noColors || false + }; + + this._progressBars = []; + this._lineCount = 0; + this._enabled = false; + } + enable() { + if (!this._enabled) { + process.stdout.write(os.EOL); + this._enabled = true; } - enable() { - if (!this._enabled) { - process.stdout.write(os.EOL); - this._enabled = true; - } + } + disable() { + if (this._enabled) { + process.stdout.write(os.EOL); + this._enabled = false; } - disable() { - if (this._enabled) { - process.stdout.write(os.EOL); - this._enabled = false; - } + } + update(index: number, percentComplete: number, color: string, title: string | undefined, titleColor?: string) { + percentComplete = minMax(percentComplete, 0, 1); + + const progressBar = this._progressBars[index] || (this._progressBars[index] = {}); + const width = this._options.width; + const n = Math.floor(width * percentComplete); + const i = width - n; + if (n === progressBar.lastN && title === progressBar.title && color === progressBar.progressColor) { + return; } - update(index: number, percentComplete: number, color: string, title: string | undefined, titleColor?: string) { - percentComplete = minMax(percentComplete, 0, 1); - - const progressBar = this._progressBars[index] || (this._progressBars[index] = {}); - const width = this._options.width; - const n = Math.floor(width * percentComplete); - const i = width - n; - if (n === progressBar.lastN && title === progressBar.title && color === progressBar.progressColor) { - return; - } - - progressBar.lastN = n; - progressBar.title = title; - progressBar.progressColor = color; - let progress = " "; - progress += this._color("progress", this._options.open); - progress += this._color(color, fill(this._options.complete, n)); - progress += this._color("progress", fill(this._options.incomplete, i)); - progress += this._color("progress", this._options.close); + progressBar.lastN = n; + progressBar.title = title; + progressBar.progressColor = color; - if (title) { - progress += this._color(titleColor || "progress", " " + title); - } + let progress = " "; + progress += this._color("progress", this._options.open); + progress += this._color(color, fill(this._options.complete, n)); + progress += this._color("progress", fill(this._options.incomplete, i)); + progress += this._color("progress", this._options.close); - if (progressBar.text !== progress) { - progressBar.text = progress; - this._render(index); - } + if (title) { + progress += this._color(titleColor || "progress", " " + title); } - private _render(index: number) { - if (!this._enabled || !isatty) { - return; - } - cursor.hide(); - readline.moveCursor(process.stdout, -process.stdout.columns, -this._lineCount); - let lineCount = 0; - const numProgressBars = this._progressBars.length; - for (let i = 0; i < numProgressBars; i++) { - if (i === index) { - readline.clearLine(process.stdout, 1); - process.stdout.write(this._progressBars[i].text + os.EOL); - } - else { - readline.moveCursor(process.stdout, -process.stdout.columns, +1); - } + if (progressBar.text !== progress) { + progressBar.text = progress; + this._render(index); + } + } + private _render(index: number) { + if (!this._enabled || !isatty) { + return; + } - lineCount++; + cursor.hide(); + readline.moveCursor(process.stdout, -process.stdout.columns, -this._lineCount); + let lineCount = 0; + const numProgressBars = this._progressBars.length; + for (let i = 0; i < numProgressBars; i++) { + if (i === index) { + readline.clearLine(process.stdout, 1); + process.stdout.write(this._progressBars[i].text + os.EOL); + } + else { + readline.moveCursor(process.stdout, -process.stdout.columns, +1); } - this._lineCount = lineCount; - cursor.show(); + lineCount++; } - private _color(type: string, text: string) { - return type && !this._options.noColors ? color(type, text) : text; - } - } - function perfdataFileName(target?: string) { - return `${perfdataFileNameFragment}${target ? `.${target}` : ""}.json`; + this._lineCount = lineCount; + cursor.show(); } - - function readSavedPerfData(target?: string): { [testHash: string]: number } | undefined { - const perfDataContents = IO.readFile(perfdataFileName(target)); - if (perfDataContents) { - return JSON.parse(perfDataContents); - } - return undefined; + private _color(type: string, text: string) { + return type && !this._options.noColors ? color(type, text) : text; } + } + + function perfdataFileName(target?: string) { + return `${perfdataFileNameFragment}${target ? `.${target}` : ""}.json`; + } - function hashName(runner: TestRunnerKind | "unittest", test: string) { - return `tsrunner-${runner}://${test}`; + function readSavedPerfData(target?: string): { [testHash: string]: number } | undefined { + const perfDataContents = IO.readFile(perfdataFileName(target)); + if (perfDataContents) { + return JSON.parse(perfDataContents); } + return undefined; + } + + function hashName(runner: TestRunnerKind | "unittest", test: string) { + return `tsrunner-${runner}://${test}`; + } - function startDelayed(perfData: { [testHash: string]: number } | undefined, totalCost: number) { - console.log(`Discovered ${tasks.length} unittest suites` + (newTasks.length ? ` and ${newTasks.length} new suites.` : ".")); - console.log("Discovering runner-based tests..."); - const discoverStart = +(new Date()); - for (const runner of runners) { - for (const test of runner.getTestFiles()) { - const file = typeof test === "string" ? test : test.file; - let size: number; - if (!perfData) { + function startDelayed(perfData: { [testHash: string]: number } | undefined, totalCost: number) { + console.log(`Discovered ${tasks.length} unittest suites` + (newTasks.length ? ` and ${newTasks.length} new suites.` : ".")); + console.log("Discovering runner-based tests..."); + const discoverStart = +(new Date()); + for (const runner of runners) { + for (const test of runner.getTestFiles()) { + const file = typeof test === "string" ? test : test.file; + let size: number; + if (!perfData) { + try { + size = statSync(path.join(runner.workingDirectory, file)).size; + } + catch { + // May be a directory try { - size = statSync(path.join(runner.workingDirectory, file)).size; + size = IO.listFiles(path.join(runner.workingDirectory, file), /.*/g, { recursive: true }).reduce((acc, elem) => acc + statSync(elem).size, 0); } catch { - // May be a directory - try { - size = IO.listFiles(path.join(runner.workingDirectory, file), /.*/g, { recursive: true }).reduce((acc, elem) => acc + statSync(elem).size, 0); - } - catch { - // Unknown test kind, just return 0 and let the historical analysis take over after one run - size = 0; - } - } - } - else { - const hashedName = hashName(runner.kind(), file); - size = perfData[hashedName]; - if (size === undefined) { + // Unknown test kind, just return 0 and let the historical analysis take over after one run size = 0; - unknownValue = hashedName; - newTasks.push({ runner: runner.kind(), file, size }); - continue; } } - tasks.push({ runner: runner.kind(), file, size }); - totalCost += size; } + else { + const hashedName = hashName(runner.kind(), file); + size = perfData[hashedName]; + if (size === undefined) { + size = 0; + unknownValue = hashedName; + newTasks.push({ runner: runner.kind(), file, size }); + continue; + } + } + tasks.push({ runner: runner.kind(), file, size }); + totalCost += size; } - tasks.sort((a, b) => a.size - b.size); - tasks = tasks.concat(newTasks); - const batchCount = workerCount; - const packfraction = 0.9; - const chunkSize = 1000; // ~1KB or 1s for sending batches near the end of a test - const batchSize = (totalCost / workerCount) * packfraction; // Keep spare tests for unittest thread in reserve - console.log(`Discovered ${tasks.length} test files in ${+(new Date()) - discoverStart}ms.`); - console.log(`Starting to run tests using ${workerCount} threads...`); - - const totalFiles = tasks.length; - let passingFiles = 0; - let failingFiles = 0; - let errorResults: ErrorInfo[] = []; - let passingResults: { name: string[] }[] = []; - let totalPassing = 0; - const startDate = new Date(); - - const progressBars = new ProgressBars({ noColors: Harness.noColors }); // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier - const progressUpdateInterval = 1 / progressBars._options.width; - let nextProgress = progressUpdateInterval; - - const newPerfData: { [testHash: string]: number } = {}; - - const workers: Worker[] = []; - let closedWorkers = 0; - for (let i = 0; i < workerCount; i++) { - // TODO: Just send the config over the IPC channel or in the command line arguments - const config: TestConfig = { light: lightMode, listenForWork: true, runUnitTests: Harness.runUnitTests, stackTraceLimit: Harness.stackTraceLimit, timeout: globalTimeout }; // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier - const configPath = ts.combinePaths(taskConfigsFolder, `task-config${i}.json`); - IO.writeFile(configPath, JSON.stringify(config)); - const worker: Worker = { - process: fork(__filename, [`--config="${configPath}"`], { stdio: ["pipe", "pipe", "pipe", "ipc"] }), - accumulatedOutput: "", - currentTasks: undefined, - timer: undefined - }; - const appendOutput = (d: Buffer) => { - worker.accumulatedOutput += d.toString(); - console.log(`[Worker ${i}]`, d.toString()); - }; - worker.process.stderr!.on("data", appendOutput); - worker.process.stdout!.on("data", appendOutput); - const killChild = (timeout: TaskTimeout) => { - worker.process.kill(); - console.error(`Worker exceeded ${timeout.duration}ms timeout ${worker.currentTasks && worker.currentTasks.length ? `while running test '${worker.currentTasks[0].file}'.` : `during test setup.`}`); - return process.exit(2); - }; - worker.process.on("error", err => { - console.error("Unexpected error in child process:"); - console.error(err); - return process.exit(2); - }); - worker.process.on("exit", (code, _signal) => { - if (code !== 0) { - console.error(`Test worker process exited with nonzero exit code! Output: + } + tasks.sort((a, b) => a.size - b.size); + tasks = tasks.concat(newTasks); + const batchCount = workerCount; + const packfraction = 0.9; + const chunkSize = 1000; // ~1KB or 1s for sending batches near the end of a test + const batchSize = (totalCost / workerCount) * packfraction; // Keep spare tests for unittest thread in reserve + console.log(`Discovered ${tasks.length} test files in ${+(new Date()) - discoverStart}ms.`); + console.log(`Starting to run tests using ${workerCount} threads...`); + + const totalFiles = tasks.length; + let passingFiles = 0; + let failingFiles = 0; + let errorResults: ErrorInfo[] = []; + let passingResults: { name: string[] }[] = []; + let totalPassing = 0; + const startDate = new Date(); + + const progressBars = new ProgressBars({ noColors }); + const progressUpdateInterval = 1 / progressBars._options.width; + let nextProgress = progressUpdateInterval; + + const newPerfData: { [testHash: string]: number } = {}; + + const workers: Worker[] = []; + let closedWorkers = 0; + for (let i = 0; i < workerCount; i++) { + // TODO: Just send the config over the IPC channel or in the command line arguments + const config: TestConfig = { light: lightMode, listenForWork: true, runUnitTests, stackTraceLimit, timeout: globalTimeout }; + const configPath = ts.combinePaths(taskConfigsFolder, `task-config${i}.json`); + IO.writeFile(configPath, JSON.stringify(config)); + const worker: Worker = { + process: fork(process.argv[1], [`--config="${configPath}"`], { stdio: ["pipe", "pipe", "pipe", "ipc"] }), + accumulatedOutput: "", + currentTasks: undefined, + timer: undefined + }; + const appendOutput = (d: Buffer) => { + worker.accumulatedOutput += d.toString(); + console.log(`[Worker ${i}]`, d.toString()); + }; + worker.process.stderr!.on("data", appendOutput); + worker.process.stdout!.on("data", appendOutput); + const killChild = (timeout: TaskTimeout) => { + worker.process.kill(); + console.error(`Worker exceeded ${timeout.duration}ms timeout ${worker.currentTasks && worker.currentTasks.length ? `while running test '${worker.currentTasks[0].file}'.` : `during test setup.`}`); + return process.exit(2); + }; + worker.process.on("error", err => { + console.error("Unexpected error in child process:"); + console.error(err); + return process.exit(2); + }); + worker.process.on("exit", (code, _signal) => { + if (code !== 0) { + console.error(`Test worker process exited with nonzero exit code! Output: ${worker.accumulatedOutput}`); - return process.exit(2); - } - }); - worker.process.on("message", (data: ParallelClientMessage) => { - switch (data.type) { - case "error": { - console.error(`Test worker encountered unexpected error${data.payload.name ? ` during the execution of test ${data.payload.name}` : ""} and was forced to close: + return process.exit(2); + } + }); + worker.process.on("message", (data: ParallelClientMessage) => { + switch (data.type) { + case "error": { + console.error(`Test worker encountered unexpected error${data.payload.name ? ` during the execution of test ${data.payload.name}` : ""} and was forced to close: Message: ${data.payload.error} Stack: ${data.payload.stack}`); - return process.exit(2); + return process.exit(2); + } + case "timeout": { + if (worker.timer) { + // eslint-disable-next-line no-restricted-globals + clearTimeout(worker.timer); } - case "timeout": { - if (worker.timer) { - // eslint-disable-next-line no-restricted-globals - clearTimeout(worker.timer); - } - if (data.payload.duration === "reset") { - worker.timer = undefined; - } - else { - // eslint-disable-next-line no-restricted-globals - worker.timer = setTimeout(killChild, data.payload.duration, data.payload); - } - break; + if (data.payload.duration === "reset") { + worker.timer = undefined; } - case "progress": - case "result": { - if (worker.currentTasks) { - worker.currentTasks.shift(); - } - totalPassing += data.payload.passing; - if (data.payload.errors.length) { - errorResults = errorResults.concat(data.payload.errors); - passingResults = passingResults.concat(data.payload.passes); - failingFiles++; - } - else { - passingResults = passingResults.concat(data.payload.passes); - passingFiles++; - } - newPerfData[hashName(data.payload.task.runner, data.payload.task.file)] = data.payload.duration; + else { + // eslint-disable-next-line no-restricted-globals + worker.timer = setTimeout(killChild, data.payload.duration, data.payload); + } + break; + } + case "progress": + case "result": { + if (worker.currentTasks) { + worker.currentTasks.shift(); + } + totalPassing += data.payload.passing; + if (data.payload.errors.length) { + errorResults = errorResults.concat(data.payload.errors); + passingResults = passingResults.concat(data.payload.passes); + failingFiles++; + } + else { + passingResults = passingResults.concat(data.payload.passes); + passingFiles++; + } + newPerfData[hashName(data.payload.task.runner, data.payload.task.file)] = data.payload.duration; - const progress = (failingFiles + passingFiles) / totalFiles; - if (progress >= nextProgress) { - while (nextProgress < progress) { - nextProgress += progressUpdateInterval; - } - updateProgress(progress, errorResults.length ? `${errorResults.length} failing` : `${totalPassing} passing`, errorResults.length ? "fail" : undefined); + const progress = (failingFiles + passingFiles) / totalFiles; + if (progress >= nextProgress) { + while (nextProgress < progress) { + nextProgress += progressUpdateInterval; } + updateProgress(progress, errorResults.length ? `${errorResults.length} failing` : `${totalPassing} passing`, errorResults.length ? "fail" : undefined); + } - if (data.type === "result") { - if (tasks.length === 0) { - // No more tasks to distribute - worker.process.send({ type: "close" }); - closedWorkers++; - if (closedWorkers === workerCount) { - outputFinalResult(); - } - return; - } - // Send tasks in blocks if the tasks are small - const taskList = [tasks.pop()!]; - while (tasks.length && taskList.reduce((p, c) => p + c.size, 0) < chunkSize) { - taskList.push(tasks.pop()!); - } - worker.currentTasks = taskList; - if (taskList.length === 1) { - worker.process.send({ type: "test", payload: taskList[0] } as ParallelHostMessage); // TODO: GH#18217 - } - else { - worker.process.send({ type: "batch", payload: taskList } as ParallelHostMessage); // TODO: GH#18217 + if (data.type === "result") { + if (tasks.length === 0) { + // No more tasks to distribute + worker.process.send({ type: "close" }); + closedWorkers++; + if (closedWorkers === workerCount) { + outputFinalResult(); } + return; + } + // Send tasks in blocks if the tasks are small + const taskList = [tasks.pop()!]; + while (tasks.length && taskList.reduce((p, c) => p + c.size, 0) < chunkSize) { + taskList.push(tasks.pop()!); + } + worker.currentTasks = taskList; + if (taskList.length === 1) { + worker.process.send({ type: "test", payload: taskList[0] } as ParallelHostMessage); // TODO: GH#18217 + } + else { + worker.process.send({ type: "batch", payload: taskList } as ParallelHostMessage); // TODO: GH#18217 } } } - }); - workers.push(worker); - } + } + }); + workers.push(worker); + } - // It's only really worth doing an initial batching if there are a ton of files to go through (and they have estimates) - if (totalFiles > 1000 && batchSize > 0) { - console.log("Batching initial test lists..."); - const batches: { runner: TestRunnerKind | "unittest", file: string, size: number }[][] = new Array(batchCount); - const doneBatching = new Array(batchCount); - let scheduledTotal = 0; - batcher: while (true) { - for (let i = 0; i < batchCount; i++) { - if (tasks.length <= workerCount) { // Keep a small reserve even in the suboptimally packed case - console.log(`Suboptimal packing detected: no tests remain to be stolen. Reduce packing fraction from ${packfraction} to fix.`); - break batcher; - } - if (doneBatching[i]) { - continue; - } - if (!batches[i]) { - batches[i] = []; - } - const total = batches[i].reduce((p, c) => p + c.size, 0); - if (total >= batchSize) { - doneBatching[i] = true; - continue; - } - const task = tasks.pop()!; - batches[i].push(task); - scheduledTotal += task.size; + // It's only really worth doing an initial batching if there are a ton of files to go through (and they have estimates) + if (totalFiles > 1000 && batchSize > 0) { + console.log("Batching initial test lists..."); + const batches: { runner: TestRunnerKind | "unittest", file: string, size: number }[][] = new Array(batchCount); + const doneBatching = new Array(batchCount); + let scheduledTotal = 0; + batcher: while (true) { + for (let i = 0; i < batchCount; i++) { + if (tasks.length <= workerCount) { // Keep a small reserve even in the suboptimally packed case + console.log(`Suboptimal packing detected: no tests remain to be stolen. Reduce packing fraction from ${packfraction} to fix.`); + break batcher; } - for (let j = 0; j < batchCount; j++) { - if (!doneBatching[j]) { - continue batcher; - } + if (doneBatching[i]) { + continue; } - break; - } - const prefix = `Batched into ${batchCount} groups`; - if (unknownValue) { - console.log(`${prefix}. Unprofiled tests including ${unknownValue} will be run first.`); - } - else { - console.log(`${prefix} with approximate total ${perfData ? "time" : "file sizes"} of ${perfData ? ms(batchSize) : `${Math.floor(batchSize)} bytes`} in each group. (${(scheduledTotal / totalCost * 100).toFixed(1)}% of total tests batched)`); - } - for (const worker of workers) { - const payload = batches.pop(); - if (payload) { - worker.currentTasks = payload; - worker.process.send({ type: "batch", payload }); + if (!batches[i]) { + batches[i] = []; } - else { // Out of batches, send off just one test - const payload = tasks.pop()!; - ts.Debug.assert(!!payload); // The reserve kept above should ensure there is always an initial task available, even in suboptimal scenarios - worker.currentTasks = [payload]; - worker.process.send({ type: "test", payload }); + const total = batches[i].reduce((p, c) => p + c.size, 0); + if (total >= batchSize) { + doneBatching[i] = true; + continue; } + const task = tasks.pop()!; + batches[i].push(task); + scheduledTotal += task.size; } + for (let j = 0; j < batchCount; j++) { + if (!doneBatching[j]) { + continue batcher; + } + } + break; + } + const prefix = `Batched into ${batchCount} groups`; + if (unknownValue) { + console.log(`${prefix}. Unprofiled tests including ${unknownValue} will be run first.`); } else { - for (let i = 0; i < workerCount; i++) { - const task = tasks.pop()!; - workers[i].currentTasks = [task]; - workers[i].process.send({ type: "test", payload: task }); + console.log(`${prefix} with approximate total ${perfData ? "time" : "file sizes"} of ${perfData ? ms(batchSize) : `${Math.floor(batchSize)} bytes`} in each group. (${(scheduledTotal / totalCost * 100).toFixed(1)}% of total tests batched)`); + } + for (const worker of workers) { + const payload = batches.pop(); + if (payload) { + worker.currentTasks = payload; + worker.process.send({ type: "batch", payload }); + } + else { // Out of batches, send off just one test + const payload = tasks.pop()!; + ts.Debug.assert(!!payload); // The reserve kept above should ensure there is always an initial task available, even in suboptimal scenarios + worker.currentTasks = [payload]; + worker.process.send({ type: "test", payload }); } } + } + else { + for (let i = 0; i < workerCount; i++) { + const task = tasks.pop()!; + workers[i].currentTasks = [task]; + workers[i].process.send({ type: "test", payload: task }); + } + } - progressBars.enable(); - updateProgress(0); - let duration: number; - let endDate: Date; - - function completeBar() { - const isPartitionFail = failingFiles !== 0; - const summaryColor = isPartitionFail ? "fail" : "green"; - const summarySymbol = isPartitionFail ? Base.symbols.err : Base.symbols.ok; + progressBars.enable(); + updateProgress(0); + let duration: number; + let endDate: Date; - const summaryTests = (isPartitionFail ? totalPassing + "/" + (errorResults.length + totalPassing) : totalPassing) + " passing"; - const summaryDuration = "(" + ms(duration) + ")"; - const savedUseColors = Base.useColors; - Base.useColors = !noColors; + function completeBar() { + const isPartitionFail = failingFiles !== 0; + const summaryColor = isPartitionFail ? "fail" : "green"; + const summarySymbol = isPartitionFail ? Base.symbols.err : Base.symbols.ok; - const summary = color(summaryColor, summarySymbol + " " + summaryTests) + " " + color("light", summaryDuration); - Base.useColors = savedUseColors; + const summaryTests = (isPartitionFail ? totalPassing + "/" + (errorResults.length + totalPassing) : totalPassing) + " passing"; + const summaryDuration = "(" + ms(duration) + ")"; + const savedUseColors = Base.useColors; + Base.useColors = !noColors; - updateProgress(1, summary); - } + const summary = color(summaryColor, summarySymbol + " " + summaryTests) + " " + color("light", summaryDuration); + Base.useColors = savedUseColors; - function updateProgress(percentComplete: number, title?: string, titleColor?: string) { - let progressColor = "pending"; - if (failingFiles) { - progressColor = "fail"; - } + updateProgress(1, summary); + } - progressBars.update( - 0, - percentComplete, - progressColor, - title, - titleColor - ); + function updateProgress(percentComplete: number, title?: string, titleColor?: string) { + let progressColor = "pending"; + if (failingFiles) { + progressColor = "fail"; } - function outputFinalResult() { - function patchStats(stats: Mocha.Stats) { - Object.defineProperties(stats, { - start: { - configurable: true, enumerable: true, - get() { return startDate; }, - set(_: Date) { /*do nothing*/ } - }, - end: { - configurable: true, enumerable: true, - get() { return endDate; }, - set(_: Date) { /*do nothing*/ } - }, - duration: { - configurable: true, enumerable: true, - get() { return duration; }, - set(_: number) { /*do nothing*/ } - } - }); - } + progressBars.update( + 0, + percentComplete, + progressColor, + title, + titleColor + ); + } - function rebuildSuite(failures: ErrorInfo[], passes: TestInfo[]) { - const root = new RemoteSuite(""); - for (const result of [...failures, ...passes] as (ErrorInfo | TestInfo)[]) { - getSuite(root, result.name.slice(0, -1)).addTest(new RemoteTest(result)); - } - return root; - function getSuite(parent: RemoteSuite, titlePath: string[]): Mocha.Suite { - const title = titlePath[0]; - let suite = parent.suiteMap.get(title); - if (!suite) parent.addSuite(suite = new RemoteSuite(title)); - return titlePath.length === 1 ? suite : getSuite(suite, titlePath.slice(1)); + function outputFinalResult() { + function patchStats(stats: Mocha.Stats) { + Object.defineProperties(stats, { + start: { + configurable: true, enumerable: true, + get() { return startDate; }, + set(_: Date) { /*do nothing*/ } + }, + end: { + configurable: true, enumerable: true, + get() { return endDate; }, + set(_: Date) { /*do nothing*/ } + }, + duration: { + configurable: true, enumerable: true, + get() { return duration; }, + set(_: number) { /*do nothing*/ } } - } + }); + } - function rebuildError(result: ErrorInfo) { - const error = new Error(result.error); - error.stack = result.stack; - return error; + function rebuildSuite(failures: ErrorInfo[], passes: TestInfo[]) { + const root = new RemoteSuite(""); + for (const result of [...failures, ...passes] as (ErrorInfo | TestInfo)[]) { + getSuite(root, result.name.slice(0, -1)).addTest(new RemoteTest(result)); } - - function replaySuite(runner: Mocha.Runner, suite: RemoteSuite) { - runner.emit("suite", suite); - for (const test of suite.tests) { - replayTest(runner, test as RemoteTest); - } - for (const child of suite.suites) { - replaySuite(runner, child as RemoteSuite); - } - runner.emit("suite end", suite); + return root; + function getSuite(parent: RemoteSuite, titlePath: string[]): Mocha.Suite { + const title = titlePath[0]; + let suite = parent.suiteMap.get(title); + if (!suite) parent.addSuite(suite = new RemoteSuite(title)); + return titlePath.length === 1 ? suite : getSuite(suite, titlePath.slice(1)); } + } - function replayTest(runner: Mocha.Runner, test: RemoteTest) { - runner.emit("test", test); - if (test.isFailed()) { - runner.emit("fail", test, "error" in test.info ? rebuildError(test.info) : new Error("Unknown error")); // eslint-disable-line local/no-in-operator - } - else { - runner.emit("pass", test); - } - runner.emit("test end", test); - } + function rebuildError(result: ErrorInfo) { + const error = new Error(result.error); + error.stack = result.stack; + return error; + } - endDate = new Date(); - duration = +endDate - +startDate; - completeBar(); - progressBars.disable(); - - const replayRunner = new Mocha.Runner(new Mocha.Suite(""), { delay: false }); - replayRunner.started = true; - const createStatsCollector = require("mocha/lib/stats-collector"); - createStatsCollector(replayRunner); // manually init stats collector like mocha.run would - - const consoleReporter = new Base(replayRunner); - patchStats(consoleReporter.stats); - - let xunitReporter: import("mocha").reporters.XUnit | undefined; - let failedTestReporter: import("../../../scripts/failed-tests.cjs") | undefined; - if (process.env.CI === "true") { - xunitReporter = new Mocha.reporters.XUnit(replayRunner, { - reporterOptions: { - suiteName: "Tests", - output: "./TEST-results.xml" - } - }); - patchStats(xunitReporter.stats); - xunitReporter.write(`\n`); + function replaySuite(runner: Mocha.Runner, suite: RemoteSuite) { + runner.emit("suite", suite); + for (const test of suite.tests) { + replayTest(runner, test as RemoteTest); } - else { - failedTestReporter = new FailedTestReporter(replayRunner, { - reporterOptions: { - file: path.resolve(".failed-tests"), - keepFailed: Harness.keepFailed // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier - } - }); + for (const child of suite.suites) { + replaySuite(runner, child as RemoteSuite); } + runner.emit("suite end", suite); + } - const savedUseColors = Base.useColors; - if (noColors) Base.useColors = false; - replayRunner.started = true; - replayRunner.emit("start"); - replaySuite(replayRunner, rebuildSuite(errorResults, passingResults)); - replayRunner.emit("end"); - consoleReporter.epilogue(); - if (noColors) Base.useColors = savedUseColors; - - // eslint-disable-next-line no-null/no-null - IO.writeFile(perfdataFileName(configOption), JSON.stringify(newPerfData, null, 4)); - - if (xunitReporter) { - xunitReporter.done(errorResults.length, failures => process.exit(failures)); - } - else if (failedTestReporter) { - failedTestReporter.done(errorResults.length, failures => process.exit(failures)); + function replayTest(runner: Mocha.Runner, test: RemoteTest) { + runner.emit("test", test); + if (test.isFailed()) { + runner.emit("fail", test, "error" in test.info ? rebuildError(test.info) : new Error("Unknown error")); // eslint-disable-line local/no-in-operator } else { - process.exit(errorResults.length); + runner.emit("pass", test); } + runner.emit("test end", test); } - } - function fill(ch: string, size: number) { - let s = ""; - while (s.length < size) { - s += ch; + endDate = new Date(); + duration = +endDate - +startDate; + completeBar(); + progressBars.disable(); + + const replayRunner = new Mocha.Runner(new Mocha.Suite(""), { delay: false }); + replayRunner.started = true; + const createStatsCollector = require("mocha/lib/stats-collector"); + createStatsCollector(replayRunner); // manually init stats collector like mocha.run would + + const consoleReporter = new Base(replayRunner); + patchStats(consoleReporter.stats); + + let xunitReporter: import("mocha").reporters.XUnit | undefined; + let failedTestReporter: import("../../../scripts/failed-tests.cjs") | undefined; + if (process.env.CI === "true") { + xunitReporter = new Mocha.reporters.XUnit(replayRunner, { + reporterOptions: { + suiteName: "Tests", + output: "./TEST-results.xml" + } + }); + patchStats(xunitReporter.stats); + xunitReporter.write(`\n`); } + else { + failedTestReporter = new FailedTestReporter(replayRunner, { + reporterOptions: { + file: path.resolve(".failed-tests"), + keepFailed, + } + }); + } + + const savedUseColors = Base.useColors; + if (noColors) Base.useColors = false; + replayRunner.started = true; + replayRunner.emit("start"); + replaySuite(replayRunner, rebuildSuite(errorResults, passingResults)); + replayRunner.emit("end"); + consoleReporter.epilogue(); + if (noColors) Base.useColors = savedUseColors; - return s.length > size ? s.substr(0, size) : s; + // eslint-disable-next-line no-null/no-null + IO.writeFile(perfdataFileName(configOption), JSON.stringify(newPerfData, null, 4)); + + if (xunitReporter) { + xunitReporter.done(errorResults.length, failures => process.exit(failures)); + } + else if (failedTestReporter) { + failedTestReporter.done(errorResults.length, failures => process.exit(failures)); + } + else { + process.exit(errorResults.length); + } } + } - function minMax(value: number, min: number, max: number) { - if (value < min) return min; - if (value > max) return max; - return value; + function fill(ch: string, size: number) { + let s = ""; + while (s.length < size) { + s += ch; } - function shimDiscoveryInterface(context: Mocha.MochaGlobals) { - shimNoopTestInterface(context); + return s.length > size ? s.substr(0, size) : s; + } - const perfData = readSavedPerfData(configOption); - context.describe = addSuite as Mocha.SuiteFunction; - context.it = addSuite as Mocha.TestFunction; + function minMax(value: number, min: number, max: number) { + if (value < min) return min; + if (value > max) return max; + return value; + } - function addSuite(title: string) { - // Note, sub-suites are not indexed (we assume such granularity is not required) - let size = 0; - if (perfData) { - size = perfData[hashName("unittest", title)]; - if (size === undefined) { - newTasks.push({ runner: "unittest", file: title, size: 0 }); - unknownValue = title; - return; - } + function shimDiscoveryInterface(context: Mocha.MochaGlobals) { + shimNoopTestInterface(context); + + const perfData = readSavedPerfData(configOption); + context.describe = addSuite as Mocha.SuiteFunction; + context.it = addSuite as Mocha.TestFunction; + + function addSuite(title: string) { + // Note, sub-suites are not indexed (we assume such granularity is not required) + let size = 0; + if (perfData) { + size = perfData[hashName("unittest", title)]; + if (size === undefined) { + newTasks.push({ runner: "unittest", file: title, size: 0 }); + unknownValue = title; + return; } - tasks.push({ runner: "unittest", file: title, size }); - totalCost += size; } + tasks.push({ runner: "unittest", file: title, size }); + totalCost += size; } + } - if (runUnitTests) { - shimDiscoveryInterface(global); - } - else { - shimNoopTestInterface(global); - } - - // eslint-disable-next-line no-restricted-globals - setTimeout(() => startDelayed(perfData, totalCost), 0); // Do real startup on next tick, so all unit tests have been collected + if (runUnitTests) { + shimDiscoveryInterface(global); } + else { + shimNoopTestInterface(global); + } + + // eslint-disable-next-line no-restricted-globals + setTimeout(() => startDelayed(perfData, totalCost), 0); // Do real startup on next tick, so all unit tests have been collected } diff --git a/src/testRunner/parallel/shared.ts b/src/testRunner/parallel/shared.ts index bfcef09b4de3f..3d40c4c772ae6 100644 --- a/src/testRunner/parallel/shared.ts +++ b/src/testRunner/parallel/shared.ts @@ -1,88 +1,89 @@ -namespace Harness.Parallel { - export interface RunnerTask { - runner: TestRunnerKind; - file: string; - size: number; - } - - export interface UnitTestTask { - runner: "unittest"; - file: string; - size: number; - } - - export type Task = RunnerTask | UnitTestTask; - - export interface TestInfo { - name: string[]; - } - - export interface ErrorInfo { - name: string[]; - error: string; - stack: string; - } - - export interface TaskTimeout { - duration: number | "reset"; - } - - export interface TaskResult { - passing: number; - errors: ErrorInfo[]; - passes: TestInfo[]; - duration: number; - task: Task; - } - - export interface ParallelTestMessage { - type: "test"; - payload: Task; - } - - export interface ParallelBatchMessage { - type: "batch"; - payload: Task[]; - } - - export interface ParallelCloseMessage { - type: "close"; - } - - export type ParallelHostMessage = ParallelTestMessage | ParallelCloseMessage | ParallelBatchMessage; - - export interface ParallelErrorMessage { - type: "error"; - payload: { error: string, stack: string, name?: string[] }; - } - - export interface ParallelResultMessage { - type: "result"; - payload: TaskResult; - } - - export interface ParallelBatchProgressMessage { - type: "progress"; - payload: TaskResult; - } - - export interface ParallelTimeoutChangeMessage { - type: "timeout"; - payload: TaskTimeout; - } - - export type ParallelClientMessage = ParallelErrorMessage | ParallelResultMessage | ParallelBatchProgressMessage | ParallelTimeoutChangeMessage; - - export function shimNoopTestInterface(global: Mocha.MochaGlobals) { - global.before = ts.noop; - global.after = ts.noop; - global.beforeEach = ts.noop; - global.afterEach = ts.noop; - global.describe = global.context = ((_: any, __: any) => { /*empty*/ }) as Mocha.SuiteFunction; - global.describe.skip = global.xdescribe = global.xcontext = ts.noop as Mocha.PendingSuiteFunction; - global.describe.only = ts.noop as Mocha.ExclusiveSuiteFunction; - global.it = global.specify = ((_: any, __: any) => { /*empty*/ }) as Mocha.TestFunction; - global.it.skip = global.xit = global.xspecify = ts.noop as Mocha.PendingTestFunction; - global.it.only = ts.noop as Mocha.ExclusiveTestFunction; - } +import * as ts from "../_namespaces/ts"; +import { TestRunnerKind } from "../_namespaces/Harness"; + +export interface RunnerTask { + runner: TestRunnerKind; + file: string; + size: number; +} + +export interface UnitTestTask { + runner: "unittest"; + file: string; + size: number; +} + +export type Task = RunnerTask | UnitTestTask; + +export interface TestInfo { + name: string[]; +} + +export interface ErrorInfo { + name: string[]; + error: string; + stack: string; +} + +export interface TaskTimeout { + duration: number | "reset"; +} + +export interface TaskResult { + passing: number; + errors: ErrorInfo[]; + passes: TestInfo[]; + duration: number; + task: Task; +} + +export interface ParallelTestMessage { + type: "test"; + payload: Task; +} + +export interface ParallelBatchMessage { + type: "batch"; + payload: Task[]; +} + +export interface ParallelCloseMessage { + type: "close"; +} + +export type ParallelHostMessage = ParallelTestMessage | ParallelCloseMessage | ParallelBatchMessage; + +export interface ParallelErrorMessage { + type: "error"; + payload: { error: string, stack: string, name?: string[] }; +} + +export interface ParallelResultMessage { + type: "result"; + payload: TaskResult; +} + +export interface ParallelBatchProgressMessage { + type: "progress"; + payload: TaskResult; +} + +export interface ParallelTimeoutChangeMessage { + type: "timeout"; + payload: TaskTimeout; +} + +export type ParallelClientMessage = ParallelErrorMessage | ParallelResultMessage | ParallelBatchProgressMessage | ParallelTimeoutChangeMessage; + +export function shimNoopTestInterface(global: Mocha.MochaGlobals) { + global.before = ts.noop; + global.after = ts.noop; + global.beforeEach = ts.noop; + global.afterEach = ts.noop; + global.describe = global.context = ((_: any, __: any) => { /*empty*/ }) as Mocha.SuiteFunction; + global.describe.skip = global.xdescribe = global.xcontext = ts.noop as Mocha.PendingSuiteFunction; + global.describe.only = ts.noop as Mocha.ExclusiveSuiteFunction; + global.it = global.specify = ((_: any, __: any) => { /*empty*/ }) as Mocha.TestFunction; + global.it.skip = global.xit = global.xspecify = ts.noop as Mocha.PendingTestFunction; + global.it.only = ts.noop as Mocha.ExclusiveTestFunction; } diff --git a/src/testRunner/parallel/worker.ts b/src/testRunner/parallel/worker.ts index ed91b3c285fd6..57538ea26b81c 100644 --- a/src/testRunner/parallel/worker.ts +++ b/src/testRunner/parallel/worker.ts @@ -1,318 +1,323 @@ -namespace Harness.Parallel.Worker { - export function start() { - function hookUncaughtExceptions() { - if (!exceptionsHooked) { - process.on("uncaughtException", handleUncaughtException); - process.on("unhandledRejection", handleUncaughtException); - exceptionsHooked = true; - } +import * as ts from "../_namespaces/ts"; +import { + ErrorInfo, ParallelClientMessage, ParallelHostMessage, RunnerTask, shimNoopTestInterface, Task, TaskResult, + TestInfo, UnitTestTask, +} from "../_namespaces/Harness.Parallel"; +import { createRunner, globalTimeout, RunnerBase, runUnitTests } from "../_namespaces/Harness"; + +export function start() { + function hookUncaughtExceptions() { + if (!exceptionsHooked) { + process.on("uncaughtException", handleUncaughtException); + process.on("unhandledRejection", handleUncaughtException); + exceptionsHooked = true; } + } - function unhookUncaughtExceptions() { - if (exceptionsHooked) { - process.removeListener("uncaughtException", handleUncaughtException); - process.removeListener("unhandledRejection", handleUncaughtException); - exceptionsHooked = false; - } + function unhookUncaughtExceptions() { + if (exceptionsHooked) { + process.removeListener("uncaughtException", handleUncaughtException); + process.removeListener("unhandledRejection", handleUncaughtException); + exceptionsHooked = false; } + } - let exceptionsHooked = false; - hookUncaughtExceptions(); + let exceptionsHooked = false; + hookUncaughtExceptions(); - // Capitalization is aligned with the global `Mocha` namespace for typespace/namespace references. - const Mocha = require("mocha") as typeof import("mocha"); + // Capitalization is aligned with the global `Mocha` namespace for typespace/namespace references. + const Mocha = require("mocha") as typeof import("mocha"); - /** - * Mixin helper. - * @param base The base class constructor. - * @param mixins The mixins to apply to the constructor. - */ - function mixin any>(base: T, ...mixins: ((klass: T) => T)[]) { - for (const mixin of mixins) { - base = mixin(base); - } - return base; + /** + * Mixin helper. + * @param base The base class constructor. + * @param mixins The mixins to apply to the constructor. + */ + function mixin any>(base: T, ...mixins: ((klass: T) => T)[]) { + for (const mixin of mixins) { + base = mixin(base); } + return base; + } - /** - * Mixes in overrides for `resetTimeout` and `clearTimeout` to support parallel test execution in a worker. - */ - function Timeout(base: T) { - return class extends (base as typeof Mocha.Runnable) { - resetTimeout() { - this.clearTimeout(); - if (this.timeout() > 0) { - sendMessage({ type: "timeout", payload: { duration: this.timeout() || 1e9 } }); - this.timer = true; - } - } - clearTimeout() { - if (this.timer) { - sendMessage({ type: "timeout", payload: { duration: "reset" } }); - this.timer = false; - } + /** + * Mixes in overrides for `resetTimeout` and `clearTimeout` to support parallel test execution in a worker. + */ + function Timeout(base: T) { + return class extends (base as typeof Mocha.Runnable) { + resetTimeout() { + this.clearTimeout(); + if (this.timeout() > 0) { + sendMessage({ type: "timeout", payload: { duration: this.timeout() || 1e9 } }); + this.timer = true; } - } as T; - } - - /** - * Mixes in an override for `clone` to support parallel test execution in a worker. - */ - function Clone(base: T) { - return class extends (base as new (...args: any[]) => { clone(): any; }) { - clone() { - const cloned = super.clone(); - Object.setPrototypeOf(cloned, this.constructor.prototype); - return cloned; + } + clearTimeout() { + if (this.timer) { + sendMessage({ type: "timeout", payload: { duration: "reset" } }); + this.timer = false; } - } as T; - } + } + } as T; + } - /** - * A `Mocha.Suite` subclass to support parallel test execution in a worker. - */ - class Suite extends mixin(Mocha.Suite, Clone) { - _createHook(title: string, fn?: Mocha.Func | Mocha.AsyncFunc) { - const hook = super._createHook(title, fn); - Object.setPrototypeOf(hook, Hook.prototype); - return hook; + /** + * Mixes in an override for `clone` to support parallel test execution in a worker. + */ + function Clone(base: T) { + return class extends (base as new (...args: any[]) => { clone(): any; }) { + clone() { + const cloned = super.clone(); + Object.setPrototypeOf(cloned, this.constructor.prototype); + return cloned; } - } + } as T; + } - /** - * A `Mocha.Hook` subclass to support parallel test execution in a worker. - */ - class Hook extends mixin(Mocha.Hook, Timeout) { + /** + * A `Mocha.Suite` subclass to support parallel test execution in a worker. + */ + class Suite extends mixin(Mocha.Suite, Clone) { + _createHook(title: string, fn?: Mocha.Func | Mocha.AsyncFunc) { + const hook = super._createHook(title, fn); + Object.setPrototypeOf(hook, Hook.prototype); + return hook; } + } - /** - * A `Mocha.Test` subclass to support parallel test execution in a worker. - */ - class Test extends mixin(Mocha.Test, Timeout, Clone) { - } + /** + * A `Mocha.Hook` subclass to support parallel test execution in a worker. + */ + class Hook extends mixin(Mocha.Hook, Timeout) { + } - /** - * Shims a 'bdd'-style test interface to support parallel test execution in a worker. - * @param rootSuite The root suite. - * @param context The test context (usually the NodeJS `global` object). - */ - function shimTestInterface(rootSuite: Mocha.Suite, context: Mocha.MochaGlobals) { - const suites = [rootSuite]; - context.before = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].beforeAll(title as string, fn); - context.after = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].afterAll(title as string, fn); - context.beforeEach = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].beforeEach(title as string, fn); - context.afterEach = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].afterEach(title as string, fn); - context.describe = context.context = ((title: string, fn: (this: Mocha.Suite) => void) => addSuite(title, fn)) as Mocha.SuiteFunction; - context.describe.skip = context.xdescribe = context.xcontext = (title: string) => addSuite(title, /*fn*/ undefined); - context.describe.only = (title: string, fn?: (this: Mocha.Suite) => void) => addSuite(title, fn); - context.it = context.specify = ((title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => addTest(title, fn)) as Mocha.TestFunction; - context.it.skip = context.xit = context.xspecify = (title: string | Mocha.Func | Mocha.AsyncFunc) => addTest(typeof title === "function" ? title.name : title, /*fn*/ undefined); - context.it.only = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => addTest(title, fn); - - function addSuite(title: string, fn: ((this: Mocha.Suite) => void) | undefined): Mocha.Suite { - const suite = new Suite(title, suites[0].ctx); - suites[0].addSuite(suite); - suite.pending = !fn; - suites.unshift(suite); - if (fn) { - fn.call(suite); - } - suites.shift(); - return suite; - } + /** + * A `Mocha.Test` subclass to support parallel test execution in a worker. + */ + class Test extends mixin(Mocha.Test, Timeout, Clone) { + } - function addTest(title: string | Mocha.Func | Mocha.AsyncFunc, fn: Mocha.Func | Mocha.AsyncFunc | undefined): Mocha.Test { - if (typeof title === "function") { - fn = title; - title = fn.name; - } - const test = new Test(title, suites[0].pending ? undefined : fn); - suites[0].addTest(test); - return test; + /** + * Shims a 'bdd'-style test interface to support parallel test execution in a worker. + * @param rootSuite The root suite. + * @param context The test context (usually the NodeJS `global` object). + */ + function shimTestInterface(rootSuite: Mocha.Suite, context: Mocha.MochaGlobals) { + const suites = [rootSuite]; + context.before = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].beforeAll(title as string, fn); + context.after = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].afterAll(title as string, fn); + context.beforeEach = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].beforeEach(title as string, fn); + context.afterEach = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => suites[0].afterEach(title as string, fn); + context.describe = context.context = ((title: string, fn: (this: Mocha.Suite) => void) => addSuite(title, fn)) as Mocha.SuiteFunction; + context.describe.skip = context.xdescribe = context.xcontext = (title: string) => addSuite(title, /*fn*/ undefined); + context.describe.only = (title: string, fn?: (this: Mocha.Suite) => void) => addSuite(title, fn); + context.it = context.specify = ((title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => addTest(title, fn)) as Mocha.TestFunction; + context.it.skip = context.xit = context.xspecify = (title: string | Mocha.Func | Mocha.AsyncFunc) => addTest(typeof title === "function" ? title.name : title, /*fn*/ undefined); + context.it.only = (title: string | Mocha.Func | Mocha.AsyncFunc, fn?: Mocha.Func | Mocha.AsyncFunc) => addTest(title, fn); + + function addSuite(title: string, fn: ((this: Mocha.Suite) => void) | undefined): Mocha.Suite { + const suite = new Suite(title, suites[0].ctx); + suites[0].addSuite(suite); + suite.pending = !fn; + suites.unshift(suite); + if (fn) { + fn.call(suite); } + suites.shift(); + return suite; } - /** - * Run the tests in the requested task. - */ - function runTests(task: Task, fn: (payload: TaskResult) => void) { - if (task.runner === "unittest") { - return executeUnitTests(task, fn); - } - else { - return runFileTests(task, fn); + function addTest(title: string | Mocha.Func | Mocha.AsyncFunc, fn: Mocha.Func | Mocha.AsyncFunc | undefined): Mocha.Test { + if (typeof title === "function") { + fn = title; + title = fn.name; } + const test = new Test(title, suites[0].pending ? undefined : fn); + suites[0].addTest(test); + return test; } + } - function executeUnitTests(task: UnitTestTask, fn: (payload: TaskResult) => void) { - if (!unitTestSuiteMap && unitTestSuite.suites.length) { - unitTestSuiteMap = new ts.Map(); - for (const suite of unitTestSuite.suites) { - unitTestSuiteMap.set(suite.title, suite); - } - } - if (!unitTestTestMap && unitTestSuite.tests.length) { - unitTestTestMap = new ts.Map(); - for (const test of unitTestSuite.tests) { - unitTestTestMap.set(test.title, test); - } - } + /** + * Run the tests in the requested task. + */ + function runTests(task: Task, fn: (payload: TaskResult) => void) { + if (task.runner === "unittest") { + return executeUnitTests(task, fn); + } + else { + return runFileTests(task, fn); + } + } - if (!unitTestSuiteMap && !unitTestTestMap) { - throw new Error(`Asked to run unit test ${task.file}, but no unit tests were discovered!`); + function executeUnitTests(task: UnitTestTask, fn: (payload: TaskResult) => void) { + if (!unitTestSuiteMap && unitTestSuite.suites.length) { + unitTestSuiteMap = new ts.Map(); + for (const suite of unitTestSuite.suites) { + unitTestSuiteMap.set(suite.title, suite); } - - let suite = unitTestSuiteMap.get(task.file); - const test = unitTestTestMap.get(task.file); - if (!suite && !test) { - throw new Error(`Unit test with name "${task.file}" was asked to be run, but such a test does not exist!`); + } + if (!unitTestTestMap && unitTestSuite.tests.length) { + unitTestTestMap = new ts.Map(); + for (const test of unitTestSuite.tests) { + unitTestTestMap.set(test.title, test); } + } - const root = new Suite("", new Mocha.Context()); - root.timeout(globalTimeout || 40_000); - if (suite) { - root.addSuite(suite); - Object.setPrototypeOf(suite.ctx, root.ctx); - } - else if (test) { - const newSuite = new Suite("", new Mocha.Context()); - newSuite.addTest(test); - root.addSuite(newSuite); - Object.setPrototypeOf(newSuite.ctx, root.ctx); - Object.setPrototypeOf(test.ctx, root.ctx); - test.parent = newSuite; - suite = newSuite; - } + if (!unitTestSuiteMap && !unitTestTestMap) { + throw new Error(`Asked to run unit test ${task.file}, but no unit tests were discovered!`); + } - runSuite(task, suite!, payload => { - suite!.parent = unitTestSuite; - Object.setPrototypeOf(suite!.ctx, unitTestSuite.ctx); - fn(payload); - }); + let suite = unitTestSuiteMap.get(task.file); + const test = unitTestTestMap.get(task.file); + if (!suite && !test) { + throw new Error(`Unit test with name "${task.file}" was asked to be run, but such a test does not exist!`); } - function runFileTests(task: RunnerTask, fn: (result: TaskResult) => void) { - let instance = runners.get(task.runner); - if (!instance) runners.set(task.runner, instance = createRunner(task.runner)); - instance.tests = [task.file]; + const root = new Suite("", new Mocha.Context()); + root.timeout(globalTimeout || 40_000); + if (suite) { + root.addSuite(suite); + Object.setPrototypeOf(suite.ctx, root.ctx); + } + else if (test) { + const newSuite = new Suite("", new Mocha.Context()); + newSuite.addTest(test); + root.addSuite(newSuite); + Object.setPrototypeOf(newSuite.ctx, root.ctx); + Object.setPrototypeOf(test.ctx, root.ctx); + test.parent = newSuite; + suite = newSuite; + } - const suite = new Suite("", new Mocha.Context()); - suite.timeout(globalTimeout || 40_000); + runSuite(task, suite!, payload => { + suite!.parent = unitTestSuite; + Object.setPrototypeOf(suite!.ctx, unitTestSuite.ctx); + fn(payload); + }); + } - shimTestInterface(suite, global); - instance.initializeTests(); + function runFileTests(task: RunnerTask, fn: (result: TaskResult) => void) { + let instance = runners.get(task.runner); + if (!instance) runners.set(task.runner, instance = createRunner(task.runner)); + instance.tests = [task.file]; - runSuite(task, suite, fn); - } + const suite = new Suite("", new Mocha.Context()); + suite.timeout(globalTimeout || 40_000); - function runSuite(task: Task, suite: Mocha.Suite, fn: (result: TaskResult) => void) { - const errors: ErrorInfo[] = []; - const passes: TestInfo[] = []; - const start = +new Date(); - const runner = new Mocha.Runner(suite, { delay: false }); - - runner - .on("start", () => { - unhookUncaughtExceptions(); // turn off global uncaught handling - }) - .on("pass", (test: Mocha.Test) => { - passes.push({ name: test.titlePath() }); - }) - .on("fail", (test: Mocha.Test | Mocha.Hook, err: any) => { - errors.push({ name: test.titlePath(), error: err.message, stack: err.stack }); - }) - .on("end", () => { - hookUncaughtExceptions(); - runner.dispose(); - }) - .run(() => { - fn({ task, errors, passes, passing: passes.length, duration: +new Date() - start }); - }); - } + shimTestInterface(suite, global); + instance.initializeTests(); - /** - * Validates a message received from the host is well-formed. - */ - function validateHostMessage(message: ParallelHostMessage) { - switch (message.type) { - case "test": return validateTest(message.payload); - case "batch": return validateBatch(message.payload); - case "close": return true; - default: return false; - } - } + runSuite(task, suite, fn); + } - /** - * Validates a test task is well formed. - */ - function validateTest(task: Task) { - return !!task && !!task.runner && !!task.file; - } + function runSuite(task: Task, suite: Mocha.Suite, fn: (result: TaskResult) => void) { + const errors: ErrorInfo[] = []; + const passes: TestInfo[] = []; + const start = +new Date(); + const runner = new Mocha.Runner(suite, { delay: false }); + + runner + .on("start", () => { + unhookUncaughtExceptions(); // turn off global uncaught handling + }) + .on("pass", (test: Mocha.Test) => { + passes.push({ name: test.titlePath() }); + }) + .on("fail", (test: Mocha.Test | Mocha.Hook, err: any) => { + errors.push({ name: test.titlePath(), error: err.message, stack: err.stack }); + }) + .on("end", () => { + hookUncaughtExceptions(); + runner.dispose(); + }) + .run(() => { + fn({ task, errors, passes, passing: passes.length, duration: +new Date() - start }); + }); + } - /** - * Validates a batch of test tasks are well formed. - */ - function validateBatch(tasks: Task[]) { - return !!tasks && Array.isArray(tasks) && tasks.length > 0 && tasks.every(validateTest); + /** + * Validates a message received from the host is well-formed. + */ + function validateHostMessage(message: ParallelHostMessage) { + switch (message.type) { + case "test": return validateTest(message.payload); + case "batch": return validateBatch(message.payload); + case "close": return true; + default: return false; } + } - function processHostMessage(message: ParallelHostMessage) { - if (!validateHostMessage(message)) { - console.log("Invalid message:", message); - return; - } + /** + * Validates a test task is well formed. + */ + function validateTest(task: Task) { + return !!task && !!task.runner && !!task.file; + } - switch (message.type) { - case "test": return processTest(message.payload, /*last*/ true); - case "batch": return processBatch(message.payload); - case "close": return process.exit(0); - } - } + /** + * Validates a batch of test tasks are well formed. + */ + function validateBatch(tasks: Task[]) { + return !!tasks && Array.isArray(tasks) && tasks.length > 0 && tasks.every(validateTest); + } - function processTest(task: Task, last: boolean, fn?: () => void) { - runTests(task, payload => { - sendMessage(last ? { type: "result", payload } : { type: "progress", payload }); - if (fn) fn(); - }); + function processHostMessage(message: ParallelHostMessage) { + if (!validateHostMessage(message)) { + console.log("Invalid message:", message); + return; } - function processBatch(tasks: Task[], fn?: () => void) { - const next = () => { - const task = tasks.shift(); - if (task) return processTest(task, tasks.length === 0, next); - if (fn) fn(); - }; - next(); + switch (message.type) { + case "test": return processTest(message.payload, /*last*/ true); + case "batch": return processBatch(message.payload); + case "close": return process.exit(0); } + } - function handleUncaughtException(err: any) { - const error = err instanceof Error ? err : new Error("" + err); - sendMessage({ type: "error", payload: { error: error.message, stack: error.stack! } }); - } + function processTest(task: Task, last: boolean, fn?: () => void) { + runTests(task, payload => { + sendMessage(last ? { type: "result", payload } : { type: "progress", payload }); + if (fn) fn(); + }); + } - function sendMessage(message: ParallelClientMessage) { - process.send!(message); - } + function processBatch(tasks: Task[], fn?: () => void) { + const next = () => { + const task = tasks.shift(); + if (task) return processTest(task, tasks.length === 0, next); + if (fn) fn(); + }; + next(); + } - // A cache of test harness Runner instances. - const runners = new ts.Map(); + function handleUncaughtException(err: any) { + const error = err instanceof Error ? err : new Error("" + err); + sendMessage({ type: "error", payload: { error: error.message, stack: error.stack! } }); + } - // The root suite for all unit tests. - let unitTestSuite: Suite; - let unitTestSuiteMap: ts.ESMap; - // (Unit) Tests directly within the root suite - let unitTestTestMap: ts.ESMap; + function sendMessage(message: ParallelClientMessage) { + process.send!(message); + } - if (runUnitTests) { - unitTestSuite = new Suite("", new Mocha.Context()); - unitTestSuite.timeout(globalTimeout || 40_000); - shimTestInterface(unitTestSuite, global); - } - else { - // ensure unit tests do not get run - shimNoopTestInterface(global); - } + // A cache of test harness Runner instances. + const runners = new ts.Map(); + + // The root suite for all unit tests. + let unitTestSuite: Suite; + let unitTestSuiteMap: ts.ESMap; + // (Unit) Tests directly within the root suite + let unitTestTestMap: ts.ESMap; - process.on("message", processHostMessage); + if (runUnitTests) { + unitTestSuite = new Suite("", new Mocha.Context()); + unitTestSuite.timeout(globalTimeout || 40_000); + shimTestInterface(unitTestSuite, global); } + else { + // ensure unit tests do not get run + shimNoopTestInterface(global); + } + + process.on("message", processHostMessage); } diff --git a/src/testRunner/playbackRef.ts b/src/testRunner/playbackRef.ts deleted file mode 100644 index 8fcb9965e0923..0000000000000 --- a/src/testRunner/playbackRef.ts +++ /dev/null @@ -1,2 +0,0 @@ -// empty ref to Playback so it can be referenced by unittests -namespace Playback {} \ No newline at end of file diff --git a/src/testRunner/projectsRunner.ts b/src/testRunner/projectsRunner.ts index 580fca81c222f..6c1cfddaa07b2 100644 --- a/src/testRunner/projectsRunner.ts +++ b/src/testRunner/projectsRunner.ts @@ -1,466 +1,472 @@ -namespace project { - // Test case is json of below type in tests/cases/project/ - interface ProjectRunnerTestCase { - scenario: string; - projectRoot: string; // project where it lives - this also is the current directory when compiling - inputFiles: readonly string[]; // list of input files to be given to program - resolveMapRoot?: boolean; // should we resolve this map root and give compiler the absolute disk path as map root? - resolveSourceRoot?: boolean; // should we resolve this source root and give compiler the absolute disk path as map root? - baselineCheck?: boolean; // Verify the baselines of output files, if this is false, we will write to output to the disk but there is no verification of baselines - runTest?: boolean; // Run the resulting test - bug?: string; // If there is any bug associated with this test case - } - - interface ProjectRunnerTestCaseResolutionInfo extends ProjectRunnerTestCase { - // Apart from actual test case the results of the resolution - resolvedInputFiles: readonly string[]; // List of files that were asked to read by compiler - emittedFiles: readonly string[]; // List of files that were emitted by the compiler - } +import * as ts from "./_namespaces/ts"; +import * as documents from "./_namespaces/documents"; +import * as Harness from "./_namespaces/Harness"; +import * as fakes from "./_namespaces/fakes"; +import * as vfs from "./_namespaces/vfs"; +import * as vpath from "./_namespaces/vpath"; +import * as Utils from "./_namespaces/Utils"; + +// Test case is json of below type in tests/cases/project/ +interface ProjectRunnerTestCase { + scenario: string; + projectRoot: string; // project where it lives - this also is the current directory when compiling + inputFiles: readonly string[]; // list of input files to be given to program + resolveMapRoot?: boolean; // should we resolve this map root and give compiler the absolute disk path as map root? + resolveSourceRoot?: boolean; // should we resolve this source root and give compiler the absolute disk path as map root? + baselineCheck?: boolean; // Verify the baselines of output files, if this is false, we will write to output to the disk but there is no verification of baselines + runTest?: boolean; // Run the resulting test + bug?: string; // If there is any bug associated with this test case +} - interface CompileProjectFilesResult { - configFileSourceFiles: readonly ts.SourceFile[]; - moduleKind: ts.ModuleKind; - program?: ts.Program; - compilerOptions?: ts.CompilerOptions; - errors: readonly ts.Diagnostic[]; - sourceMapData?: readonly ts.SourceMapEmitResult[]; - } +interface ProjectRunnerTestCaseResolutionInfo extends ProjectRunnerTestCase { + // Apart from actual test case the results of the resolution + resolvedInputFiles: readonly string[]; // List of files that were asked to read by compiler + emittedFiles: readonly string[]; // List of files that were emitted by the compiler +} - interface BatchCompileProjectTestCaseResult extends CompileProjectFilesResult { - outputFiles?: readonly documents.TextDocument[]; - } +interface CompileProjectFilesResult { + configFileSourceFiles: readonly ts.SourceFile[]; + moduleKind: ts.ModuleKind; + program?: ts.Program; + compilerOptions?: ts.CompilerOptions; + errors: readonly ts.Diagnostic[]; + sourceMapData?: readonly ts.SourceMapEmitResult[]; +} - export class ProjectRunner extends Harness.RunnerBase { - public enumerateTestFiles() { - const all = this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true }); - if (Harness.shards === 1) { - return all; - } - return all.filter((_val, idx) => idx % Harness.shards === (Harness.shardId - 1)); - } +interface BatchCompileProjectTestCaseResult extends CompileProjectFilesResult { + outputFiles?: readonly documents.TextDocument[]; +} - public kind(): Harness.TestRunnerKind { - return "project"; +export class ProjectRunner extends Harness.RunnerBase { + public enumerateTestFiles() { + const all = this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true }); + if (Harness.shards === 1) { + return all; } + return all.filter((_val, idx) => idx % Harness.shards === (Harness.shardId - 1)); + } - public initializeTests() { - describe("projects tests", () => { - const tests = this.tests.length === 0 ? this.enumerateTestFiles() : this.tests; - for (const test of tests) { - this.runProjectTestCase(typeof test === "string" ? test : test.file); - } - }); - } + public kind(): Harness.TestRunnerKind { + return "project"; + } - private runProjectTestCase(testCaseFileName: string) { - for (const { name, payload } of ProjectTestCase.getConfigurations(testCaseFileName)) { - describe("Compiling project for " + payload.testCase.scenario + ": testcase " + testCaseFileName + (name ? ` (${name})` : ``), () => { - let projectTestCase: ProjectTestCase | undefined; - before(() => { - projectTestCase = new ProjectTestCase(testCaseFileName, payload); - }); - it(`Correct module resolution tracing for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyResolution()); - it(`Correct errors for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyDiagnostics()); - it(`Correct JS output for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyJavaScriptOutput()); - // NOTE: This check was commented out in previous code. Leaving this here to eventually be restored if needed. - // it(`Correct sourcemap content for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifySourceMapRecord()); - it(`Correct declarations for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyDeclarations()); - after(() => { - projectTestCase = undefined; - }); - }); + public initializeTests() { + describe("projects tests", () => { + const tests = this.tests.length === 0 ? this.enumerateTestFiles() : this.tests; + for (const test of tests) { + this.runProjectTestCase(typeof test === "string" ? test : test.file); } - } + }); } - class ProjectCompilerHost extends fakes.CompilerHost { - private _testCase: ProjectRunnerTestCase & ts.CompilerOptions; - private _projectParseConfigHost: ProjectParseConfigHost | undefined; - - constructor(sys: fakes.System | vfs.FileSystem, compilerOptions: ts.CompilerOptions, _testCaseJustName: string, testCase: ProjectRunnerTestCase & ts.CompilerOptions, _moduleKind: ts.ModuleKind) { - super(sys, compilerOptions); - this._testCase = testCase; + private runProjectTestCase(testCaseFileName: string) { + for (const { name, payload } of ProjectTestCase.getConfigurations(testCaseFileName)) { + describe("Compiling project for " + payload.testCase.scenario + ": testcase " + testCaseFileName + (name ? ` (${name})` : ``), () => { + let projectTestCase: ProjectTestCase | undefined; + before(() => { + projectTestCase = new ProjectTestCase(testCaseFileName, payload); + }); + it(`Correct module resolution tracing for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyResolution()); + it(`Correct errors for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyDiagnostics()); + it(`Correct JS output for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyJavaScriptOutput()); + // NOTE: This check was commented out in previous code. Leaving this here to eventually be restored if needed. + // it(`Correct sourcemap content for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifySourceMapRecord()); + it(`Correct declarations for ${testCaseFileName}`, () => projectTestCase && projectTestCase.verifyDeclarations()); + after(() => { + projectTestCase = undefined; + }); + }); } + } +} - public get parseConfigHost(): fakes.ParseConfigHost { - return this._projectParseConfigHost || (this._projectParseConfigHost = new ProjectParseConfigHost(this.sys, this._testCase)); - } +class ProjectCompilerHost extends fakes.CompilerHost { + private _testCase: ProjectRunnerTestCase & ts.CompilerOptions; + private _projectParseConfigHost: ProjectParseConfigHost | undefined; - public getDefaultLibFileName(_options: ts.CompilerOptions) { - return vpath.resolve(this.getDefaultLibLocation(), "lib.es5.d.ts"); - } + constructor(sys: fakes.System | vfs.FileSystem, compilerOptions: ts.CompilerOptions, _testCaseJustName: string, testCase: ProjectRunnerTestCase & ts.CompilerOptions, _moduleKind: ts.ModuleKind) { + super(sys, compilerOptions); + this._testCase = testCase; } - class ProjectParseConfigHost extends fakes.ParseConfigHost { - private _testCase: ProjectRunnerTestCase & ts.CompilerOptions; - - constructor(sys: fakes.System, testCase: ProjectRunnerTestCase & ts.CompilerOptions) { - super(sys); - this._testCase = testCase; - } + public get parseConfigHost(): fakes.ParseConfigHost { + return this._projectParseConfigHost || (this._projectParseConfigHost = new ProjectParseConfigHost(this.sys, this._testCase)); + } - public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { - const result = super.readDirectory(path, extensions, excludes, includes, depth); - const projectRoot = vpath.resolve(vfs.srcFolder, this._testCase.projectRoot); - return result.map(item => vpath.relative( - projectRoot, - vpath.resolve(projectRoot, item), - this.vfs.ignoreCase - )); - } + public getDefaultLibFileName(_options: ts.CompilerOptions) { + return vpath.resolve(this.getDefaultLibLocation(), "lib.es5.d.ts"); } +} - interface ProjectTestConfiguration { - name: string; - payload: ProjectTestPayload; +class ProjectParseConfigHost extends fakes.ParseConfigHost { + private _testCase: ProjectRunnerTestCase & ts.CompilerOptions; + + constructor(sys: fakes.System, testCase: ProjectRunnerTestCase & ts.CompilerOptions) { + super(sys); + this._testCase = testCase; } - interface ProjectTestPayload { - testCase: ProjectRunnerTestCase & ts.CompilerOptions; - moduleKind: ts.ModuleKind; - vfs: vfs.FileSystem; + public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { + const result = super.readDirectory(path, extensions, excludes, includes, depth); + const projectRoot = vpath.resolve(vfs.srcFolder, this._testCase.projectRoot); + return result.map(item => vpath.relative( + projectRoot, + vpath.resolve(projectRoot, item), + this.vfs.ignoreCase + )); } +} - class ProjectTestCase { - private testCase: ProjectRunnerTestCase & ts.CompilerOptions; - private testCaseJustName: string; - private sys: fakes.System; - private compilerOptions: ts.CompilerOptions; - private compilerResult: BatchCompileProjectTestCaseResult; - - constructor(testCaseFileName: string, { testCase, moduleKind, vfs }: ProjectTestPayload) { - this.testCase = testCase; - this.testCaseJustName = testCaseFileName.replace(/^.*[\\\/]/, "").replace(/\.json/, ""); - this.compilerOptions = createCompilerOptions(testCase, moduleKind); - this.sys = new fakes.System(vfs); - - let configFileName: string | undefined; - let inputFiles = testCase.inputFiles; - if (this.compilerOptions.project) { - // Parse project - configFileName = ts.normalizePath(ts.combinePaths(this.compilerOptions.project, "tsconfig.json")); - assert(!inputFiles || inputFiles.length === 0, "cannot specify input files and project option together"); - } - else if (!inputFiles || inputFiles.length === 0) { - configFileName = ts.findConfigFile("", path => this.sys.fileExists(path)); - } +interface ProjectTestConfiguration { + name: string; + payload: ProjectTestPayload; +} - let errors: ts.Diagnostic[] | undefined; - const configFileSourceFiles: ts.SourceFile[] = []; - if (configFileName) { - const result = ts.readJsonConfigFile(configFileName, path => this.sys.readFile(path)); - configFileSourceFiles.push(result); - const configParseHost = new ProjectParseConfigHost(this.sys, this.testCase); - const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), this.compilerOptions); - inputFiles = configParseResult.fileNames; - this.compilerOptions = configParseResult.options; - errors = [...result.parseDiagnostics, ...configParseResult.errors]; - } +interface ProjectTestPayload { + testCase: ProjectRunnerTestCase & ts.CompilerOptions; + moduleKind: ts.ModuleKind; + vfs: vfs.FileSystem; +} - const compilerHost = new ProjectCompilerHost(this.sys, this.compilerOptions, this.testCaseJustName, this.testCase, moduleKind); - const projectCompilerResult = this.compileProjectFiles(moduleKind, configFileSourceFiles, () => inputFiles, compilerHost, this.compilerOptions); - - this.compilerResult = { - configFileSourceFiles, - moduleKind, - program: projectCompilerResult.program, - compilerOptions: this.compilerOptions, - sourceMapData: projectCompilerResult.sourceMapData, - outputFiles: compilerHost.outputs, - errors: errors ? ts.concatenate(errors, projectCompilerResult.errors) : projectCompilerResult.errors, - }; +class ProjectTestCase { + private testCase: ProjectRunnerTestCase & ts.CompilerOptions; + private testCaseJustName: string; + private sys: fakes.System; + private compilerOptions: ts.CompilerOptions; + private compilerResult: BatchCompileProjectTestCaseResult; + + constructor(testCaseFileName: string, { testCase, moduleKind, vfs }: ProjectTestPayload) { + this.testCase = testCase; + this.testCaseJustName = testCaseFileName.replace(/^.*[\\\/]/, "").replace(/\.json/, ""); + this.compilerOptions = createCompilerOptions(testCase, moduleKind); + this.sys = new fakes.System(vfs); + + let configFileName: string | undefined; + let inputFiles = testCase.inputFiles; + if (this.compilerOptions.project) { + // Parse project + configFileName = ts.normalizePath(ts.combinePaths(this.compilerOptions.project, "tsconfig.json")); + assert(!inputFiles || inputFiles.length === 0, "cannot specify input files and project option together"); } - - private get vfs() { - return this.sys.vfs; + else if (!inputFiles || inputFiles.length === 0) { + configFileName = ts.findConfigFile("", path => this.sys.fileExists(path)); } - public static getConfigurations(testCaseFileName: string): ProjectTestConfiguration[] { - let testCase: ProjectRunnerTestCase & ts.CompilerOptions; + let errors: ts.Diagnostic[] | undefined; + const configFileSourceFiles: ts.SourceFile[] = []; + if (configFileName) { + const result = ts.readJsonConfigFile(configFileName, path => this.sys.readFile(path)); + configFileSourceFiles.push(result); + const configParseHost = new ProjectParseConfigHost(this.sys, this.testCase); + const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), this.compilerOptions); + inputFiles = configParseResult.fileNames; + this.compilerOptions = configParseResult.options; + errors = [...result.parseDiagnostics, ...configParseResult.errors]; + } - let testFileText: string | undefined; - try { - testFileText = Harness.IO.readFile(testCaseFileName); - } - catch (e) { - assert(false, "Unable to open testcase file: " + testCaseFileName + ": " + e.message); - } + const compilerHost = new ProjectCompilerHost(this.sys, this.compilerOptions, this.testCaseJustName, this.testCase, moduleKind); + const projectCompilerResult = this.compileProjectFiles(moduleKind, configFileSourceFiles, () => inputFiles, compilerHost, this.compilerOptions); + + this.compilerResult = { + configFileSourceFiles, + moduleKind, + program: projectCompilerResult.program, + compilerOptions: this.compilerOptions, + sourceMapData: projectCompilerResult.sourceMapData, + outputFiles: compilerHost.outputs, + errors: errors ? ts.concatenate(errors, projectCompilerResult.errors) : projectCompilerResult.errors, + }; + } - try { - testCase = JSON.parse(testFileText!) as ProjectRunnerTestCase & ts.CompilerOptions; - } - catch (e) { - throw assert(false, "Testcase: " + testCaseFileName + " does not contain valid json format: " + e.message); - } + private get vfs() { + return this.sys.vfs; + } - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false); - fs.mountSync(vpath.resolve(Harness.IO.getWorkspaceRoot(), "tests"), vpath.combine(vfs.srcFolder, "tests"), vfs.createResolver(Harness.IO)); - fs.mkdirpSync(vpath.combine(vfs.srcFolder, testCase.projectRoot)); - fs.chdir(vpath.combine(vfs.srcFolder, testCase.projectRoot)); - fs.makeReadonly(); + public static getConfigurations(testCaseFileName: string): ProjectTestConfiguration[] { + let testCase: ProjectRunnerTestCase & ts.CompilerOptions; - return [ - { name: `@module: commonjs`, payload: { testCase, moduleKind: ts.ModuleKind.CommonJS, vfs: fs } }, - { name: `@module: amd`, payload: { testCase, moduleKind: ts.ModuleKind.AMD, vfs: fs } } - ]; + let testFileText: string | undefined; + try { + testFileText = Harness.IO.readFile(testCaseFileName); + } + catch (e) { + assert(false, "Unable to open testcase file: " + testCaseFileName + ": " + e.message); } - public verifyResolution() { - const cwd = this.vfs.cwd(); - const ignoreCase = this.vfs.ignoreCase; - const resolutionInfo: ProjectRunnerTestCaseResolutionInfo & ts.CompilerOptions = JSON.parse(JSON.stringify(this.testCase)); - resolutionInfo.resolvedInputFiles = this.compilerResult.program!.getSourceFiles() - .map(({ fileName: input }) => - vpath.beneath(vfs.builtFolder, input, this.vfs.ignoreCase) || vpath.beneath(vfs.testLibFolder, input, this.vfs.ignoreCase) ? Utils.removeTestPathPrefixes(input) : - vpath.isAbsolute(input) ? vpath.relative(cwd, input, ignoreCase) : - input); - - resolutionInfo.emittedFiles = this.compilerResult.outputFiles! - .map(output => output.meta.get("fileName") || output.file) - .map(output => Utils.removeTestPathPrefixes(vpath.isAbsolute(output) ? vpath.relative(cwd, output, ignoreCase) : output)); - - const content = JSON.stringify(resolutionInfo, undefined, " "); - Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".json", content); + try { + testCase = JSON.parse(testFileText!) as ProjectRunnerTestCase & ts.CompilerOptions; + } + catch (e) { + throw assert(false, "Testcase: " + testCaseFileName + " does not contain valid json format: " + e.message); } - public verifyDiagnostics() { - if (this.compilerResult.errors.length) { - Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".errors.txt", getErrorsBaseline(this.compilerResult)); - } + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false); + fs.mountSync(vpath.resolve(Harness.IO.getWorkspaceRoot(), "tests"), vpath.combine(vfs.srcFolder, "tests"), vfs.createResolver(Harness.IO)); + fs.mkdirpSync(vpath.combine(vfs.srcFolder, testCase.projectRoot)); + fs.chdir(vpath.combine(vfs.srcFolder, testCase.projectRoot)); + fs.makeReadonly(); + + return [ + { name: `@module: commonjs`, payload: { testCase, moduleKind: ts.ModuleKind.CommonJS, vfs: fs } }, + { name: `@module: amd`, payload: { testCase, moduleKind: ts.ModuleKind.AMD, vfs: fs } } + ]; + } + + public verifyResolution() { + const cwd = this.vfs.cwd(); + const ignoreCase = this.vfs.ignoreCase; + const resolutionInfo: ProjectRunnerTestCaseResolutionInfo & ts.CompilerOptions = JSON.parse(JSON.stringify(this.testCase)); + resolutionInfo.resolvedInputFiles = this.compilerResult.program!.getSourceFiles() + .map(({ fileName: input }) => + vpath.beneath(vfs.builtFolder, input, this.vfs.ignoreCase) || vpath.beneath(vfs.testLibFolder, input, this.vfs.ignoreCase) ? Utils.removeTestPathPrefixes(input) : + vpath.isAbsolute(input) ? vpath.relative(cwd, input, ignoreCase) : + input); + + resolutionInfo.emittedFiles = this.compilerResult.outputFiles! + .map(output => output.meta.get("fileName") || output.file) + .map(output => Utils.removeTestPathPrefixes(vpath.isAbsolute(output) ? vpath.relative(cwd, output, ignoreCase) : output)); + + const content = JSON.stringify(resolutionInfo, undefined, " "); + Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".json", content); + } + + public verifyDiagnostics() { + if (this.compilerResult.errors.length) { + Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".errors.txt", getErrorsBaseline(this.compilerResult)); } + } - public verifyJavaScriptOutput() { - if (this.testCase.baselineCheck) { - const errs: Error[] = []; - let nonSubfolderDiskFiles = 0; - for (const output of this.compilerResult.outputFiles!) { - try { - // convert file name to rooted name - // if filename is not rooted - concat it with project root and then expand project root relative to current directory - const fileName = output.meta.get("fileName") || output.file; - const diskFileName = vpath.isAbsolute(fileName) ? fileName : vpath.resolve(this.vfs.cwd(), fileName); - - // compute file name relative to current directory (expanded project root) - let diskRelativeName = vpath.relative(this.vfs.cwd(), diskFileName, this.vfs.ignoreCase); - if (vpath.isAbsolute(diskRelativeName) || diskRelativeName.startsWith("../")) { - // If the generated output file resides in the parent folder or is rooted path, - // we need to instead create files that can live in the project reference folder - // but make sure extension of these files matches with the fileName the compiler asked to write - diskRelativeName = `diskFile${nonSubfolderDiskFiles}${vpath.extname(fileName, [".js.map", ".js", ".d.ts"], this.vfs.ignoreCase)}`; - nonSubfolderDiskFiles++; - } - - const content = Utils.removeTestPathPrefixes(output.text, /*retainTrailingDirectorySeparator*/ true); - Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + diskRelativeName, content as string | null); // TODO: GH#18217 - } - catch (e) { - errs.push(e); + public verifyJavaScriptOutput() { + if (this.testCase.baselineCheck) { + const errs: Error[] = []; + let nonSubfolderDiskFiles = 0; + for (const output of this.compilerResult.outputFiles!) { + try { + // convert file name to rooted name + // if filename is not rooted - concat it with project root and then expand project root relative to current directory + const fileName = output.meta.get("fileName") || output.file; + const diskFileName = vpath.isAbsolute(fileName) ? fileName : vpath.resolve(this.vfs.cwd(), fileName); + + // compute file name relative to current directory (expanded project root) + let diskRelativeName = vpath.relative(this.vfs.cwd(), diskFileName, this.vfs.ignoreCase); + if (vpath.isAbsolute(diskRelativeName) || diskRelativeName.startsWith("../")) { + // If the generated output file resides in the parent folder or is rooted path, + // we need to instead create files that can live in the project reference folder + // but make sure extension of these files matches with the fileName the compiler asked to write + diskRelativeName = `diskFile${nonSubfolderDiskFiles}${vpath.extname(fileName, [".js.map", ".js", ".d.ts"], this.vfs.ignoreCase)}`; + nonSubfolderDiskFiles++; } - } - if (errs.length) { - throw Error(errs.join("\n ")); + const content = Utils.removeTestPathPrefixes(output.text, /*retainTrailingDirectorySeparator*/ true); + Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + diskRelativeName, content as string | null); // TODO: GH#18217 + } + catch (e) { + errs.push(e); } } - } - public verifySourceMapRecord() { - // NOTE: This check was commented out in previous code. Leaving this here to eventually be restored if needed. - // if (compilerResult.sourceMapData) { - // Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".sourcemap.txt", () => { - // return Harness.SourceMapRecorder.getSourceMapRecord(compilerResult.sourceMapData, compilerResult.program, - // ts.filter(compilerResult.outputFiles, outputFile => Harness.Compiler.isJS(outputFile.emittedFileName))); - // }); - // } + if (errs.length) { + throw Error(errs.join("\n ")); + } } + } - public verifyDeclarations() { - if (!this.compilerResult.errors.length && this.testCase.declaration) { - const dTsCompileResult = this.compileDeclarations(this.compilerResult); - if (dTsCompileResult && dTsCompileResult.errors.length) { - Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".dts.errors.txt", getErrorsBaseline(dTsCompileResult)); - } + public verifySourceMapRecord() { + // NOTE: This check was commented out in previous code. Leaving this here to eventually be restored if needed. + // if (compilerResult.sourceMapData) { + // Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".sourcemap.txt", () => { + // return Harness.SourceMapRecorder.getSourceMapRecord(compilerResult.sourceMapData, compilerResult.program, + // ts.filter(compilerResult.outputFiles, outputFile => Harness.Compiler.isJS(outputFile.emittedFileName))); + // }); + // } + } + + public verifyDeclarations() { + if (!this.compilerResult.errors.length && this.testCase.declaration) { + const dTsCompileResult = this.compileDeclarations(this.compilerResult); + if (dTsCompileResult && dTsCompileResult.errors.length) { + Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".dts.errors.txt", getErrorsBaseline(dTsCompileResult)); } } + } - // Project baselines verified go in project/testCaseName/moduleKind/ - private getBaselineFolder(moduleKind: ts.ModuleKind) { - return "project/" + this.testCaseJustName + "/" + moduleNameToString(moduleKind) + "/"; - } + // Project baselines verified go in project/testCaseName/moduleKind/ + private getBaselineFolder(moduleKind: ts.ModuleKind) { + return "project/" + this.testCaseJustName + "/" + moduleNameToString(moduleKind) + "/"; + } - private cleanProjectUrl(url: string) { - let diskProjectPath = ts.normalizeSlashes(Harness.IO.resolvePath(this.testCase.projectRoot)!); - let projectRootUrl = "file:///" + diskProjectPath; - const normalizedProjectRoot = ts.normalizeSlashes(this.testCase.projectRoot); - diskProjectPath = diskProjectPath.substr(0, diskProjectPath.lastIndexOf(normalizedProjectRoot)); - projectRootUrl = projectRootUrl.substr(0, projectRootUrl.lastIndexOf(normalizedProjectRoot)); - if (url && url.length) { - if (url.indexOf(projectRootUrl) === 0) { - // replace the disk specific project url path into project root url - url = "file:///" + url.substr(projectRootUrl.length); - } - else if (url.indexOf(diskProjectPath) === 0) { - // Replace the disk specific path into the project root path - url = url.substr(diskProjectPath.length); - if (url.charCodeAt(0) !== ts.CharacterCodes.slash) { - url = "/" + url; - } + private cleanProjectUrl(url: string) { + let diskProjectPath = ts.normalizeSlashes(Harness.IO.resolvePath(this.testCase.projectRoot)!); + let projectRootUrl = "file:///" + diskProjectPath; + const normalizedProjectRoot = ts.normalizeSlashes(this.testCase.projectRoot); + diskProjectPath = diskProjectPath.substr(0, diskProjectPath.lastIndexOf(normalizedProjectRoot)); + projectRootUrl = projectRootUrl.substr(0, projectRootUrl.lastIndexOf(normalizedProjectRoot)); + if (url && url.length) { + if (url.indexOf(projectRootUrl) === 0) { + // replace the disk specific project url path into project root url + url = "file:///" + url.substr(projectRootUrl.length); + } + else if (url.indexOf(diskProjectPath) === 0) { + // Replace the disk specific path into the project root path + url = url.substr(diskProjectPath.length); + if (url.charCodeAt(0) !== ts.CharacterCodes.slash) { + url = "/" + url; } } - - return url; } - private compileProjectFiles(moduleKind: ts.ModuleKind, configFileSourceFiles: readonly ts.SourceFile[], - getInputFiles: () => readonly string[], - compilerHost: ts.CompilerHost, - compilerOptions: ts.CompilerOptions): CompileProjectFilesResult { + return url; + } - const program = ts.createProgram(getInputFiles(), compilerOptions, compilerHost); - const errors = ts.getPreEmitDiagnostics(program); + private compileProjectFiles(moduleKind: ts.ModuleKind, configFileSourceFiles: readonly ts.SourceFile[], + getInputFiles: () => readonly string[], + compilerHost: ts.CompilerHost, + compilerOptions: ts.CompilerOptions): CompileProjectFilesResult { - const { sourceMaps: sourceMapData, diagnostics: emitDiagnostics } = program.emit(); + const program = ts.createProgram(getInputFiles(), compilerOptions, compilerHost); + const errors = ts.getPreEmitDiagnostics(program); - // Clean up source map data that will be used in baselining - if (sourceMapData) { - for (const data of sourceMapData) { - data.sourceMap = { - ...data.sourceMap, - sources: data.sourceMap.sources.map(source => this.cleanProjectUrl(source)), - sourceRoot: data.sourceMap.sourceRoot && this.cleanProjectUrl(data.sourceMap.sourceRoot) - }; - } + const { sourceMaps: sourceMapData, diagnostics: emitDiagnostics } = program.emit(); + + // Clean up source map data that will be used in baselining + if (sourceMapData) { + for (const data of sourceMapData) { + data.sourceMap = { + ...data.sourceMap, + sources: data.sourceMap.sources.map(source => this.cleanProjectUrl(source)), + sourceRoot: data.sourceMap.sourceRoot && this.cleanProjectUrl(data.sourceMap.sourceRoot) + }; } + } + + return { + configFileSourceFiles, + moduleKind, + program, + errors: ts.concatenate(errors, emitDiagnostics), + sourceMapData + }; + } - return { - configFileSourceFiles, - moduleKind, - program, - errors: ts.concatenate(errors, emitDiagnostics), - sourceMapData - }; + private compileDeclarations(compilerResult: BatchCompileProjectTestCaseResult) { + if (!compilerResult.program) { + return; } - private compileDeclarations(compilerResult: BatchCompileProjectTestCaseResult) { - if (!compilerResult.program) { - return; + const compilerOptions = compilerResult.program.getCompilerOptions(); + const allInputFiles: documents.TextDocument[] = []; + const rootFiles: string[] = []; + ts.forEach(compilerResult.program.getSourceFiles(), sourceFile => { + if (sourceFile.isDeclarationFile) { + if (!vpath.isDefaultLibrary(sourceFile.fileName)) { + allInputFiles.unshift(new documents.TextDocument(sourceFile.fileName, sourceFile.text)); + } + rootFiles.unshift(sourceFile.fileName); } - - const compilerOptions = compilerResult.program.getCompilerOptions(); - const allInputFiles: documents.TextDocument[] = []; - const rootFiles: string[] = []; - ts.forEach(compilerResult.program.getSourceFiles(), sourceFile => { - if (sourceFile.isDeclarationFile) { - if (!vpath.isDefaultLibrary(sourceFile.fileName)) { - allInputFiles.unshift(new documents.TextDocument(sourceFile.fileName, sourceFile.text)); - } - rootFiles.unshift(sourceFile.fileName); + else if (!(compilerOptions.outFile || compilerOptions.out)) { + let emitOutputFilePathWithoutExtension: string | undefined; + if (compilerOptions.outDir) { + let sourceFilePath = ts.getNormalizedAbsolutePath(sourceFile.fileName, compilerResult.program!.getCurrentDirectory()); + sourceFilePath = sourceFilePath.replace(compilerResult.program!.getCommonSourceDirectory(), ""); + emitOutputFilePathWithoutExtension = ts.removeFileExtension(ts.combinePaths(compilerOptions.outDir, sourceFilePath)); + } + else { + emitOutputFilePathWithoutExtension = ts.removeFileExtension(sourceFile.fileName); } - else if (!(compilerOptions.outFile || compilerOptions.out)) { - let emitOutputFilePathWithoutExtension: string | undefined; - if (compilerOptions.outDir) { - let sourceFilePath = ts.getNormalizedAbsolutePath(sourceFile.fileName, compilerResult.program!.getCurrentDirectory()); - sourceFilePath = sourceFilePath.replace(compilerResult.program!.getCommonSourceDirectory(), ""); - emitOutputFilePathWithoutExtension = ts.removeFileExtension(ts.combinePaths(compilerOptions.outDir, sourceFilePath)); - } - else { - emitOutputFilePathWithoutExtension = ts.removeFileExtension(sourceFile.fileName); - } - const outputDtsFileName = emitOutputFilePathWithoutExtension + ts.Extension.Dts; - const file = findOutputDtsFile(outputDtsFileName); - if (file) { - allInputFiles.unshift(file); - rootFiles.unshift(file.meta.get("fileName") || file.file); - } + const outputDtsFileName = emitOutputFilePathWithoutExtension + ts.Extension.Dts; + const file = findOutputDtsFile(outputDtsFileName); + if (file) { + allInputFiles.unshift(file); + rootFiles.unshift(file.meta.get("fileName") || file.file); } - else { - const outputDtsFileName = ts.removeFileExtension(compilerOptions.outFile || compilerOptions.out!) + ts.Extension.Dts; - const outputDtsFile = findOutputDtsFile(outputDtsFileName)!; - if (!ts.contains(allInputFiles, outputDtsFile)) { - allInputFiles.unshift(outputDtsFile); - rootFiles.unshift(outputDtsFile.meta.get("fileName") || outputDtsFile.file); - } + } + else { + const outputDtsFileName = ts.removeFileExtension(compilerOptions.outFile || compilerOptions.out!) + ts.Extension.Dts; + const outputDtsFile = findOutputDtsFile(outputDtsFileName)!; + if (!ts.contains(allInputFiles, outputDtsFile)) { + allInputFiles.unshift(outputDtsFile); + rootFiles.unshift(outputDtsFile.meta.get("fileName") || outputDtsFile.file); } - }); + } + }); - const _vfs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { - documents: allInputFiles, - cwd: vpath.combine(vfs.srcFolder, this.testCase.projectRoot) - }); + const _vfs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { + documents: allInputFiles, + cwd: vpath.combine(vfs.srcFolder, this.testCase.projectRoot) + }); - // Dont allow config files since we are compiling existing source options - const compilerHost = new ProjectCompilerHost(_vfs, compilerResult.compilerOptions!, this.testCaseJustName, this.testCase, compilerResult.moduleKind); - return this.compileProjectFiles(compilerResult.moduleKind, compilerResult.configFileSourceFiles, () => rootFiles, compilerHost, compilerResult.compilerOptions!); + // Dont allow config files since we are compiling existing source options + const compilerHost = new ProjectCompilerHost(_vfs, compilerResult.compilerOptions!, this.testCaseJustName, this.testCase, compilerResult.moduleKind); + return this.compileProjectFiles(compilerResult.moduleKind, compilerResult.configFileSourceFiles, () => rootFiles, compilerHost, compilerResult.compilerOptions!); - function findOutputDtsFile(fileName: string) { - return ts.forEach(compilerResult.outputFiles, outputFile => outputFile.meta.get("fileName") === fileName ? outputFile : undefined); - } + function findOutputDtsFile(fileName: string) { + return ts.forEach(compilerResult.outputFiles, outputFile => outputFile.meta.get("fileName") === fileName ? outputFile : undefined); } } +} - function moduleNameToString(moduleKind: ts.ModuleKind) { - return moduleKind === ts.ModuleKind.AMD ? "amd" : - moduleKind === ts.ModuleKind.CommonJS ? "node" : "none"; - } +function moduleNameToString(moduleKind: ts.ModuleKind) { + return moduleKind === ts.ModuleKind.AMD ? "amd" : + moduleKind === ts.ModuleKind.CommonJS ? "node" : "none"; +} - function getErrorsBaseline(compilerResult: CompileProjectFilesResult) { - const inputSourceFiles = compilerResult.configFileSourceFiles.slice(); - if (compilerResult.program) { - for (const sourceFile of compilerResult.program.getSourceFiles()) { - if (!Harness.isDefaultLibraryFile(sourceFile.fileName)) { - inputSourceFiles.push(sourceFile); - } +function getErrorsBaseline(compilerResult: CompileProjectFilesResult) { + const inputSourceFiles = compilerResult.configFileSourceFiles.slice(); + if (compilerResult.program) { + for (const sourceFile of compilerResult.program.getSourceFiles()) { + if (!Harness.isDefaultLibraryFile(sourceFile.fileName)) { + inputSourceFiles.push(sourceFile); } } - - const inputFiles = inputSourceFiles.map(sourceFile => ({ - unitName: ts.isRootedDiskPath(sourceFile.fileName) ? - Harness.RunnerBase.removeFullPaths(sourceFile.fileName) : - sourceFile.fileName, - content: sourceFile.text - })); - - return Harness.Compiler.getErrorBaseline(inputFiles, compilerResult.errors); } - function createCompilerOptions(testCase: ProjectRunnerTestCase & ts.CompilerOptions, moduleKind: ts.ModuleKind) { - // Set the special options that depend on other testcase options - const compilerOptions: ts.CompilerOptions = { - noErrorTruncation: false, - skipDefaultLibCheck: false, - moduleResolution: ts.ModuleResolutionKind.Classic, - module: moduleKind, - newLine: ts.NewLineKind.CarriageReturnLineFeed, - mapRoot: testCase.resolveMapRoot && testCase.mapRoot - ? vpath.resolve(vfs.srcFolder, testCase.mapRoot) - : testCase.mapRoot, - - sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot - ? vpath.resolve(vfs.srcFolder, testCase.sourceRoot) - : testCase.sourceRoot - }; + const inputFiles = inputSourceFiles.map(sourceFile => ({ + unitName: ts.isRootedDiskPath(sourceFile.fileName) ? + Harness.RunnerBase.removeFullPaths(sourceFile.fileName) : + sourceFile.fileName, + content: sourceFile.text + })); + + return Harness.Compiler.getErrorBaseline(inputFiles, compilerResult.errors); +} - // Set the values specified using json - const optionNameMap = ts.arrayToMap(ts.optionDeclarations, option => option.name); - for (const name in testCase) { - if (name !== "mapRoot" && name !== "sourceRoot") { - const option = optionNameMap.get(name); - if (option) { - const optType = option.type; - let value = testCase[name] as any; - if (!ts.isString(optType)) { - const key = value.toLowerCase(); - const optTypeValue = optType.get(key); - if (optTypeValue) { - value = optTypeValue; - } +function createCompilerOptions(testCase: ProjectRunnerTestCase & ts.CompilerOptions, moduleKind: ts.ModuleKind) { + // Set the special options that depend on other testcase options + const compilerOptions: ts.CompilerOptions = { + noErrorTruncation: false, + skipDefaultLibCheck: false, + moduleResolution: ts.ModuleResolutionKind.Classic, + module: moduleKind, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + mapRoot: testCase.resolveMapRoot && testCase.mapRoot + ? vpath.resolve(vfs.srcFolder, testCase.mapRoot) + : testCase.mapRoot, + + sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot + ? vpath.resolve(vfs.srcFolder, testCase.sourceRoot) + : testCase.sourceRoot + }; + + // Set the values specified using json + const optionNameMap = ts.arrayToMap(ts.optionDeclarations, option => option.name); + for (const name in testCase) { + if (name !== "mapRoot" && name !== "sourceRoot") { + const option = optionNameMap.get(name); + if (option) { + const optType = option.type; + let value = testCase[name] as any; + if (!ts.isString(optType)) { + const key = value.toLowerCase(); + const optTypeValue = optType.get(key); + if (optTypeValue) { + value = optTypeValue; } - compilerOptions[option.name] = value; } + compilerOptions[option.name] = value; } } - - return compilerOptions; } + + return compilerOptions; } diff --git a/src/testRunner/runner.ts b/src/testRunner/runner.ts index 4427390d7f398..f84e6df70b7ac 100644 --- a/src/testRunner/runner.ts +++ b/src/testRunner/runner.ts @@ -1,284 +1,304 @@ -namespace Harness { - /* eslint-disable prefer-const */ - export let runners: RunnerBase[] = []; - export let iterations = 1; - /* eslint-enable prefer-const */ +import * as vpath from "./_namespaces/vpath"; +import * as ts from "./_namespaces/ts"; +import * as FourSlash from "./_namespaces/FourSlash"; +import * as project from "./_namespaces/project"; +import * as RWC from "./_namespaces/RWC"; +import { + CompilerBaselineRunner, CompilerTestType, DefinitelyTypedRunner, DockerfileRunner, FourSlashRunner, + GeneratedFourslashRunner, IO, Parallel, RunnerBase, setLightMode, setShardId, setShards, Test262BaselineRunner, + TestRunnerKind, UserCodeRunner, +} from "./_namespaces/Harness"; - function runTests(runners: RunnerBase[]) { - for (let i = iterations; i > 0; i--) { - const seen = new Map(); - const dupes: [string, string][] = []; - for (const runner of runners) { - if (runner instanceof CompilerBaselineRunner || runner instanceof FourSlashRunner) { - for (const sf of runner.enumerateTestFiles()) { - const full = typeof sf === "string" ? sf : sf.file; - const base = vpath.basename(full).toLowerCase(); - // allow existing dupes in fourslash/shims and fourslash/server - if (seen.has(base) && !/fourslash\/(shim|server)/.test(full)) { - dupes.push([seen.get(base)!, full]); - } - else { - seen.set(base, full); - } +/* eslint-disable prefer-const */ +export let runners: RunnerBase[] = []; +export let iterations = 1; +/* eslint-enable prefer-const */ + +function runTests(runners: RunnerBase[]) { + for (let i = iterations; i > 0; i--) { + const seen = new Map(); + const dupes: [string, string][] = []; + for (const runner of runners) { + if (runner instanceof CompilerBaselineRunner || runner instanceof FourSlashRunner) { + for (const sf of runner.enumerateTestFiles()) { + const full = typeof sf === "string" ? sf : sf.file; + const base = vpath.basename(full).toLowerCase(); + // allow existing dupes in fourslash/shims and fourslash/server + if (seen.has(base) && !/fourslash\/(shim|server)/.test(full)) { + dupes.push([seen.get(base)!, full]); + } + else { + seen.set(base, full); } } - runner.initializeTests(); } - if (dupes.length) { - throw new Error(`${dupes.length} Tests with duplicate baseline names: + runner.initializeTests(); + } + if (dupes.length) { + throw new Error(`${dupes.length} Tests with duplicate baseline names: ${JSON.stringify(dupes, undefined, 2)}`); - } } } +} - function tryGetConfig(args: string[]) { - const prefix = "--config="; - const configPath = ts.forEach(args, arg => arg.lastIndexOf(prefix, 0) === 0 && arg.substr(prefix.length)); - // strip leading and trailing quotes from the path (necessary on Windows since shell does not do it automatically) - return configPath && configPath.replace(/(^[\"'])|([\"']$)/g, ""); - } +function tryGetConfig(args: string[]) { + const prefix = "--config="; + const configPath = ts.forEach(args, arg => arg.lastIndexOf(prefix, 0) === 0 && arg.substr(prefix.length)); + // strip leading and trailing quotes from the path (necessary on Windows since shell does not do it automatically) + return configPath && configPath.replace(/(^[\"'])|([\"']$)/g, ""); +} - export function createRunner(kind: TestRunnerKind): RunnerBase { - switch (kind) { - case "conformance": - return new CompilerBaselineRunner(CompilerTestType.Conformance); - case "compiler": - return new CompilerBaselineRunner(CompilerTestType.Regressions); - case "fourslash": - return new FourSlashRunner(FourSlash.FourSlashTestType.Native); - case "fourslash-shims": - return new FourSlashRunner(FourSlash.FourSlashTestType.Shims); - case "fourslash-shims-pp": - return new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess); - case "fourslash-server": - return new FourSlashRunner(FourSlash.FourSlashTestType.Server); - case "project": - return new project.ProjectRunner(); - case "rwc": - return new RWC.RWCRunner(); - case "test262": - return new Test262BaselineRunner(); - case "user": - return new UserCodeRunner(); - case "dt": - return new DefinitelyTypedRunner(); - case "docker": - return new DockerfileRunner(); - } - return ts.Debug.fail(`Unknown runner kind ${kind}`); +export function createRunner(kind: TestRunnerKind): RunnerBase { + switch (kind) { + case "conformance": + return new CompilerBaselineRunner(CompilerTestType.Conformance); + case "compiler": + return new CompilerBaselineRunner(CompilerTestType.Regressions); + case "fourslash": + return new FourSlashRunner(FourSlash.FourSlashTestType.Native); + case "fourslash-shims": + return new FourSlashRunner(FourSlash.FourSlashTestType.Shims); + case "fourslash-shims-pp": + return new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess); + case "fourslash-server": + return new FourSlashRunner(FourSlash.FourSlashTestType.Server); + case "project": + return new project.ProjectRunner(); + case "rwc": + return new RWC.RWCRunner(); + case "test262": + return new Test262BaselineRunner(); + case "user": + return new UserCodeRunner(); + case "dt": + return new DefinitelyTypedRunner(); + case "docker": + return new DockerfileRunner(); } + return ts.Debug.fail(`Unknown runner kind ${kind}`); +} - // users can define tests to run in mytest.config that will override cmd line args, otherwise use cmd line args (test.config), otherwise no options +// users can define tests to run in mytest.config that will override cmd line args, otherwise use cmd line args (test.config), otherwise no options - const mytestconfigFileName = "mytest.config"; - const testconfigFileName = "test.config"; +const mytestconfigFileName = "mytest.config"; +const testconfigFileName = "test.config"; - const customConfig = tryGetConfig(IO.args()); - const testConfigContent = - customConfig && IO.fileExists(customConfig) - ? IO.readFile(customConfig)! - : IO.fileExists(mytestconfigFileName) - ? IO.readFile(mytestconfigFileName)! - : IO.fileExists(testconfigFileName) ? IO.readFile(testconfigFileName)! : ""; +const customConfig = tryGetConfig(IO.args()); +const testConfigContent = + customConfig && IO.fileExists(customConfig) + ? IO.readFile(customConfig)! + : IO.fileExists(mytestconfigFileName) + ? IO.readFile(mytestconfigFileName)! + : IO.fileExists(testconfigFileName) ? IO.readFile(testconfigFileName)! : ""; - export let taskConfigsFolder: string; - export let workerCount: number; - export let runUnitTests: boolean | undefined; - export let stackTraceLimit: number | "full" | undefined; - export let noColors = false; - export let keepFailed = false; +export let taskConfigsFolder: string; +export let workerCount: number; +export let runUnitTests: boolean | undefined; +export let stackTraceLimit: number | "full" | undefined; +export let noColors = false; +export let keepFailed = false; - export interface TestConfig { - light?: boolean; - taskConfigsFolder?: string; - listenForWork?: boolean; - workerCount?: number; - stackTraceLimit?: number | "full"; - test?: string[]; - runners?: string[]; - runUnitTests?: boolean; - noColors?: boolean; - timeout?: number; - keepFailed?: boolean; - shardId?: number; - shards?: number; - } +export interface TestConfig { + light?: boolean; + taskConfigsFolder?: string; + listenForWork?: boolean; + workerCount?: number; + stackTraceLimit?: number | "full"; + test?: string[]; + runners?: string[]; + runUnitTests?: boolean; + noColors?: boolean; + timeout?: number; + keepFailed?: boolean; + shardId?: number; + shards?: number; +} - export interface TaskSet { - runner: TestRunnerKind; - files: string[]; - } +export interface TaskSet { + runner: TestRunnerKind; + files: string[]; +} - export let configOption: string; - export let globalTimeout: number; - function handleTestConfig() { - if (testConfigContent !== "") { - const testConfig = JSON.parse(testConfigContent) as TestConfig; - if (testConfig.light) { - setLightMode(true); - } - if (testConfig.timeout) { - globalTimeout = testConfig.timeout; - } - runUnitTests = testConfig.runUnitTests; - if (testConfig.workerCount) { - workerCount = +testConfig.workerCount; - } - if (testConfig.taskConfigsFolder) { - taskConfigsFolder = testConfig.taskConfigsFolder; - } - if (testConfig.noColors !== undefined) { - noColors = testConfig.noColors; - } - if (testConfig.keepFailed) { - keepFailed = true; - } - if (testConfig.shardId) { - setShardId(testConfig.shardId); - } - if (testConfig.shards) { - setShards(testConfig.shards); - } +export let configOption: string; +export let globalTimeout: number; +function handleTestConfig() { + if (testConfigContent !== "") { + const testConfig = JSON.parse(testConfigContent) as TestConfig; + if (testConfig.light) { + setLightMode(true); + } + if (testConfig.timeout) { + globalTimeout = testConfig.timeout; + } + runUnitTests = testConfig.runUnitTests; + if (testConfig.workerCount) { + workerCount = +testConfig.workerCount; + } + if (testConfig.taskConfigsFolder) { + taskConfigsFolder = testConfig.taskConfigsFolder; + } + if (testConfig.noColors !== undefined) { + noColors = testConfig.noColors; + } + if (testConfig.keepFailed) { + keepFailed = true; + } + if (testConfig.shardId) { + setShardId(testConfig.shardId); + } + if (testConfig.shards) { + setShards(testConfig.shards); + } - if (testConfig.stackTraceLimit === "full") { - (Error as any).stackTraceLimit = Infinity; - stackTraceLimit = testConfig.stackTraceLimit; - } - else if ((+testConfig.stackTraceLimit! | 0) > 0) { - (Error as any).stackTraceLimit = +testConfig.stackTraceLimit! | 0; - stackTraceLimit = +testConfig.stackTraceLimit! | 0; - } - if (testConfig.listenForWork) { - return true; - } + if (testConfig.stackTraceLimit === "full") { + (Error as any).stackTraceLimit = Infinity; + stackTraceLimit = testConfig.stackTraceLimit; + } + else if ((+testConfig.stackTraceLimit! | 0) > 0) { + (Error as any).stackTraceLimit = +testConfig.stackTraceLimit! | 0; + stackTraceLimit = +testConfig.stackTraceLimit! | 0; + } + if (testConfig.listenForWork) { + return true; + } - const runnerConfig = testConfig.runners || testConfig.test; - if (runnerConfig && runnerConfig.length > 0) { - if (testConfig.runners) { - runUnitTests = runnerConfig.indexOf("unittest") !== -1; + const runnerConfig = testConfig.runners || testConfig.test; + if (runnerConfig && runnerConfig.length > 0) { + if (testConfig.runners) { + runUnitTests = runnerConfig.indexOf("unittest") !== -1; + } + for (const option of runnerConfig) { + if (!option) { + continue; } - for (const option of runnerConfig) { - if (!option) { - continue; - } - if (!configOption) { - configOption = option; - } - else { - configOption += "+" + option; - } + if (!configOption) { + configOption = option; + } + else { + configOption += "+" + option; + } - switch (option) { - case "compiler": - runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); - runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); - break; - case "conformance": - runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); - break; - case "project": - runners.push(new project.ProjectRunner()); - break; - case "fourslash": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); - break; - case "fourslash-shims": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); - break; - case "fourslash-shims-pp": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); - break; - case "fourslash-server": - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); - break; - case "fourslash-generated": - runners.push(new GeneratedFourslashRunner(FourSlash.FourSlashTestType.Native)); - break; - case "rwc": - runners.push(new RWC.RWCRunner()); - break; - case "test262": - runners.push(new Test262BaselineRunner()); - break; - case "user": - runners.push(new UserCodeRunner()); - break; - case "dt": - runners.push(new DefinitelyTypedRunner()); - break; - case "docker": - runners.push(new DockerfileRunner()); - break; - } + switch (option) { + case "compiler": + runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); + runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); + break; + case "conformance": + runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); + break; + case "project": + runners.push(new project.ProjectRunner()); + break; + case "fourslash": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); + break; + case "fourslash-shims": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); + break; + case "fourslash-shims-pp": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); + break; + case "fourslash-server": + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); + break; + case "fourslash-generated": + runners.push(new GeneratedFourslashRunner(FourSlash.FourSlashTestType.Native)); + break; + case "rwc": + runners.push(new RWC.RWCRunner()); + break; + case "test262": + runners.push(new Test262BaselineRunner()); + break; + case "user": + runners.push(new UserCodeRunner()); + break; + case "dt": + runners.push(new DefinitelyTypedRunner()); + break; + case "docker": + runners.push(new DockerfileRunner()); + break; } } } + } - if (runners.length === 0) { - // compiler - runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); - runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); + if (runners.length === 0) { + // compiler + runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance)); + runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions)); - runners.push(new project.ProjectRunner()); + runners.push(new project.ProjectRunner()); - // language services - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); - runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); - // runners.push(new GeneratedFourslashRunner()); + // language services + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native)); + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims)); + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess)); + runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server)); + // runners.push(new GeneratedFourslashRunner()); - // CRON-only tests - if (process.env.TRAVIS_EVENT_TYPE === "cron") { - runners.push(new UserCodeRunner()); - runners.push(new DockerfileRunner()); - } - } - if (runUnitTests === undefined) { - runUnitTests = runners.length !== 1; // Don't run unit tests when running only one runner if unit tests were not explicitly asked for + // CRON-only tests + if (process.env.TRAVIS_EVENT_TYPE === "cron") { + runners.push(new UserCodeRunner()); + runners.push(new DockerfileRunner()); } - return false; } + if (runUnitTests === undefined) { + runUnitTests = runners.length !== 1; // Don't run unit tests when running only one runner if unit tests were not explicitly asked for + } + return false; +} - function beginTests() { - ts.Debug.loggingHost = { - log(_level, s) { - console.log(s || ""); - } - }; - - if (ts.Debug.isDebugging) { - ts.Debug.enableDebugInfo(); +function beginTests() { + ts.Debug.loggingHost = { + log(_level, s) { + console.log(s || ""); } + }; - // run tests in en-US by default. - let savedUILocale: string | undefined; - beforeEach(() => { - savedUILocale = ts.getUILocale(); - ts.setUILocale("en-US"); - }); - afterEach(() => ts.setUILocale(savedUILocale)); + if (ts.Debug.isDebugging) { + ts.Debug.enableDebugInfo(); + } - runTests(runners); + // run tests in en-US by default. + let savedUILocale: string | undefined; + beforeEach(() => { + savedUILocale = ts.getUILocale(); + ts.setUILocale("en-US"); + }); + afterEach(() => ts.setUILocale(savedUILocale)); - if (!runUnitTests) { - // patch `describe` to skip unit tests - (global as any).describe = ts.noop; - } + runTests(runners); + + if (!runUnitTests) { + // patch `describe` to skip unit tests + (global as any).describe = ts.noop; } +} - export let isWorker: boolean; - function startTestEnvironment() { - isWorker = handleTestConfig(); - if (isWorker) { - return Parallel.Worker.start(); - } - else if (taskConfigsFolder && workerCount && workerCount > 1) { - return Parallel.Host.start(); - } - beginTests(); +export let isWorker: boolean; +function startTestEnvironment() { + // For debugging convenience. + (globalThis as any).ts = ts; + + isWorker = handleTestConfig(); + if (isWorker) { + return Parallel.Worker.start(); + } + else if (taskConfigsFolder && workerCount && workerCount > 1) { + return Parallel.Host.start(); } + beginTests(); +} + +startTestEnvironment(); + +// This brings in all of the unittests. - startTestEnvironment(); +// If running as emitted CJS, we want to start the tests here after startTestEnvironment. +// If running bundled, we will do this in Harness.ts. +if (__filename.endsWith("runner.js")) { + require("./tests"); } diff --git a/src/testRunner/rwcRunner.ts b/src/testRunner/rwcRunner.ts index 29e86382aff34..a60657574ed0a 100644 --- a/src/testRunner/rwcRunner.ts +++ b/src/testRunner/rwcRunner.ts @@ -1,235 +1,239 @@ +import * as Playback from "./_namespaces/Playback"; +import * as Harness from "./_namespaces/Harness"; +import * as compiler from "./_namespaces/compiler"; +import * as ts from "./_namespaces/ts"; +import * as vpath from "./_namespaces/vpath"; + // In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`. -namespace RWC { - function runWithIOLog(ioLog: Playback.IoLog, fn: (oldIO: Harness.IO) => void) { - const oldIO = Harness.IO; +function runWithIOLog(ioLog: Playback.IoLog, fn: (oldIO: Harness.IO) => void) { + const oldIO = Harness.IO; - const wrappedIO = Playback.wrapIO(oldIO); - wrappedIO.startReplayFromData(ioLog); - Harness.setHarnessIO(wrappedIO); + const wrappedIO = Playback.wrapIO(oldIO); + wrappedIO.startReplayFromData(ioLog); + Harness.setHarnessIO(wrappedIO); - try { - fn(oldIO); - } - finally { - wrappedIO.endReplay(); - Harness.setHarnessIO(oldIO); - } + try { + fn(oldIO); + } + finally { + wrappedIO.endReplay(); + Harness.setHarnessIO(oldIO); } +} - export function runRWCTest(jsonPath: string) { - describe("Testing a rwc project: " + jsonPath, () => { - let inputFiles: Harness.Compiler.TestFile[] = []; - let otherFiles: Harness.Compiler.TestFile[] = []; - let tsconfigFiles: Harness.Compiler.TestFile[] = []; - let compilerResult: compiler.CompilationResult; - let compilerOptions: ts.CompilerOptions; - const baselineOpts: Harness.Baseline.BaselineOptions = { - Subfolder: "rwc", - Baselinefolder: "internal/baselines" - }; - const baseName = ts.getBaseFileName(jsonPath); - let currentDirectory: string; - let useCustomLibraryFile: boolean; - let caseSensitive: boolean; - after(() => { - // Mocha holds onto the closure environment of the describe callback even after the test is done. - // Therefore we have to clean out large objects after the test is done. - inputFiles = []; - otherFiles = []; - tsconfigFiles = []; - compilerResult = undefined!; - compilerOptions = undefined!; - currentDirectory = undefined!; - // useCustomLibraryFile is a flag specified in the json object to indicate whether to use built/local/lib.d.ts - // or to use lib.d.ts inside the json object. If the flag is true, use the lib.d.ts inside json file - // otherwise use the lib.d.ts from built/local - useCustomLibraryFile = false; - caseSensitive = false; - }); +export function runRWCTest(jsonPath: string) { + describe("Testing a rwc project: " + jsonPath, () => { + let inputFiles: Harness.Compiler.TestFile[] = []; + let otherFiles: Harness.Compiler.TestFile[] = []; + let tsconfigFiles: Harness.Compiler.TestFile[] = []; + let compilerResult: compiler.CompilationResult; + let compilerOptions: ts.CompilerOptions; + const baselineOpts: Harness.Baseline.BaselineOptions = { + Subfolder: "rwc", + Baselinefolder: "internal/baselines" + }; + const baseName = ts.getBaseFileName(jsonPath); + let currentDirectory: string; + let useCustomLibraryFile: boolean; + let caseSensitive: boolean; + after(() => { + // Mocha holds onto the closure environment of the describe callback even after the test is done. + // Therefore we have to clean out large objects after the test is done. + inputFiles = []; + otherFiles = []; + tsconfigFiles = []; + compilerResult = undefined!; + compilerOptions = undefined!; + currentDirectory = undefined!; + // useCustomLibraryFile is a flag specified in the json object to indicate whether to use built/local/lib.d.ts + // or to use lib.d.ts inside the json object. If the flag is true, use the lib.d.ts inside json file + // otherwise use the lib.d.ts from built/local + useCustomLibraryFile = false; + caseSensitive = false; + }); - it("can compile", function (this: Mocha.Context) { - this.timeout(800_000); // Allow long timeouts for RWC compilations - let opts!: ts.ParsedCommandLine; - - const ioLog: Playback.IoLog = Playback.newStyleLogIntoOldStyleLog(JSON.parse(Harness.IO.readFile(`internal/cases/rwc/${jsonPath}/test.json`)!), Harness.IO, `internal/cases/rwc/${baseName}`); - currentDirectory = ioLog.currentDirectory; - useCustomLibraryFile = !!ioLog.useCustomLibraryFile; - runWithIOLog(ioLog, () => { - opts = ts.parseCommandLine(ioLog.arguments, fileName => Harness.IO.readFile(fileName)); - assert.equal(opts.errors.length, 0); - - // To provide test coverage of output javascript file, - // we will set noEmitOnError flag to be false. - opts.options.noEmitOnError = false; - }); - let fileNames = opts.fileNames; - - runWithIOLog(ioLog, () => { - const tsconfigFile = ts.forEach(ioLog.filesRead, f => vpath.isTsConfigFile(f.path) ? f : undefined); - if (tsconfigFile) { - const tsconfigFileContents = getHarnessCompilerInputUnit(tsconfigFile.path); - tsconfigFiles.push({ unitName: tsconfigFile.path, content: tsconfigFileContents.content }); - const parsedTsconfigFileContents = ts.parseJsonText(tsconfigFile.path, tsconfigFileContents.content); - const configParseHost: ts.ParseConfigHost = { - useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), - fileExists: Harness.IO.fileExists, - readDirectory: Harness.IO.readDirectory, - readFile: Harness.IO.readFile - }; - const configParseResult = ts.parseJsonSourceFileConfigFileContent(parsedTsconfigFileContents, configParseHost, ts.getDirectoryPath(tsconfigFile.path)); - fileNames = configParseResult.fileNames; - opts.options = ts.extend(opts.options, configParseResult.options); - ts.setConfigFileInOptions(opts.options, configParseResult.options.configFile); - } + it("can compile", function (this: Mocha.Context) { + this.timeout(800_000); // Allow long timeouts for RWC compilations + let opts!: ts.ParsedCommandLine; - // Deduplicate files so they are only printed once in baselines (they are deduplicated within the compiler already) - const uniqueNames = new ts.Map(); - for (const fileName of fileNames) { - // Must maintain order, build result list while checking map - const normalized = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!); - if (!uniqueNames.has(normalized)) { - uniqueNames.set(normalized, true); - // Load the file - inputFiles.push(getHarnessCompilerInputUnit(fileName)); - } - } + const ioLog: Playback.IoLog = Playback.newStyleLogIntoOldStyleLog(JSON.parse(Harness.IO.readFile(`internal/cases/rwc/${jsonPath}/test.json`)!), Harness.IO, `internal/cases/rwc/${baseName}`); + currentDirectory = ioLog.currentDirectory; + useCustomLibraryFile = !!ioLog.useCustomLibraryFile; + runWithIOLog(ioLog, () => { + opts = ts.parseCommandLine(ioLog.arguments, fileName => Harness.IO.readFile(fileName)); + assert.equal(opts.errors.length, 0); - // Add files to compilation - for (const fileRead of ioLog.filesRead) { - const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileRead.path)!); - if (!uniqueNames.has(unitName) && !Harness.isDefaultLibraryFile(fileRead.path)) { - uniqueNames.set(unitName, true); - otherFiles.push(getHarnessCompilerInputUnit(fileRead.path)); - } - else if (!opts.options.noLib && Harness.isDefaultLibraryFile(fileRead.path) && !uniqueNames.has(unitName) && useCustomLibraryFile) { - // If useCustomLibraryFile is true, we will use lib.d.ts from json object - // otherwise use the lib.d.ts from built/local - // Majority of RWC code will be using built/local/lib.d.ts instead of - // lib.d.ts inside json file. However, some RWC cases will still use - // their own version of lib.d.ts because they have customized lib.d.ts - uniqueNames.set(unitName, true); - inputFiles.push(getHarnessCompilerInputUnit(fileRead.path)); - } - } - }); + // To provide test coverage of output javascript file, + // we will set noEmitOnError flag to be false. + opts.options.noEmitOnError = false; + }); + let fileNames = opts.fileNames; + + runWithIOLog(ioLog, () => { + const tsconfigFile = ts.forEach(ioLog.filesRead, f => vpath.isTsConfigFile(f.path) ? f : undefined); + if (tsconfigFile) { + const tsconfigFileContents = getHarnessCompilerInputUnit(tsconfigFile.path); + tsconfigFiles.push({ unitName: tsconfigFile.path, content: tsconfigFileContents.content }); + const parsedTsconfigFileContents = ts.parseJsonText(tsconfigFile.path, tsconfigFileContents.content); + const configParseHost: ts.ParseConfigHost = { + useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), + fileExists: Harness.IO.fileExists, + readDirectory: Harness.IO.readDirectory, + readFile: Harness.IO.readFile + }; + const configParseResult = ts.parseJsonSourceFileConfigFileContent(parsedTsconfigFileContents, configParseHost, ts.getDirectoryPath(tsconfigFile.path)); + fileNames = configParseResult.fileNames; + opts.options = ts.extend(opts.options, configParseResult.options); + ts.setConfigFileInOptions(opts.options, configParseResult.options.configFile); + } - if (useCustomLibraryFile) { - // do not use lib since we already read it in above - opts.options.lib = undefined; - opts.options.noLib = true; + // Deduplicate files so they are only printed once in baselines (they are deduplicated within the compiler already) + const uniqueNames = new ts.Map(); + for (const fileName of fileNames) { + // Must maintain order, build result list while checking map + const normalized = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!); + if (!uniqueNames.has(normalized)) { + uniqueNames.set(normalized, true); + // Load the file + inputFiles.push(getHarnessCompilerInputUnit(fileName)); + } } - caseSensitive = ioLog.useCaseSensitiveFileNames || false; - // Emit the results - compilerResult = Harness.Compiler.compileFiles( - inputFiles, - otherFiles, - { useCaseSensitiveFileNames: "" + caseSensitive }, - opts.options, - // Since each RWC json file specifies its current directory in its json file, we need - // to pass this information in explicitly instead of acquiring it from the process. - currentDirectory); - compilerOptions = compilerResult.options; - - function getHarnessCompilerInputUnit(fileName: string): Harness.Compiler.TestFile { - const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!); - let content: string; - try { - content = Harness.IO.readFile(unitName)!; + // Add files to compilation + for (const fileRead of ioLog.filesRead) { + const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileRead.path)!); + if (!uniqueNames.has(unitName) && !Harness.isDefaultLibraryFile(fileRead.path)) { + uniqueNames.set(unitName, true); + otherFiles.push(getHarnessCompilerInputUnit(fileRead.path)); } - catch (e) { - content = Harness.IO.readFile(fileName)!; + else if (!opts.options.noLib && Harness.isDefaultLibraryFile(fileRead.path) && !uniqueNames.has(unitName) && useCustomLibraryFile) { + // If useCustomLibraryFile is true, we will use lib.d.ts from json object + // otherwise use the lib.d.ts from built/local + // Majority of RWC code will be using built/local/lib.d.ts instead of + // lib.d.ts inside json file. However, some RWC cases will still use + // their own version of lib.d.ts because they have customized lib.d.ts + uniqueNames.set(unitName, true); + inputFiles.push(getHarnessCompilerInputUnit(fileRead.path)); } - return { unitName, content }; } }); + if (useCustomLibraryFile) { + // do not use lib since we already read it in above + opts.options.lib = undefined; + opts.options.noLib = true; + } - it("has the expected emitted code", function (this: Mocha.Context) { - this.timeout(100_000); // Allow longer timeouts for RWC js verification - Harness.Baseline.runMultifileBaseline(baseName, "", () => { - return Harness.Compiler.iterateOutputs(compilerResult.js.values()); - }, baselineOpts, [".js", ".jsx"]); - }); + caseSensitive = ioLog.useCaseSensitiveFileNames || false; + // Emit the results + compilerResult = Harness.Compiler.compileFiles( + inputFiles, + otherFiles, + { useCaseSensitiveFileNames: "" + caseSensitive }, + opts.options, + // Since each RWC json file specifies its current directory in its json file, we need + // to pass this information in explicitly instead of acquiring it from the process. + currentDirectory); + compilerOptions = compilerResult.options; + + function getHarnessCompilerInputUnit(fileName: string): Harness.Compiler.TestFile { + const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!); + let content: string; + try { + content = Harness.IO.readFile(unitName)!; + } + catch (e) { + content = Harness.IO.readFile(fileName)!; + } + return { unitName, content }; + } + }); - it("has the expected declaration file content", () => { - Harness.Baseline.runMultifileBaseline(baseName, "", () => { - if (!compilerResult.dts.size) { - return null; // eslint-disable-line no-null/no-null - } - return Harness.Compiler.iterateOutputs(compilerResult.dts.values()); - }, baselineOpts, [".d.ts"]); - }); + it("has the expected emitted code", function (this: Mocha.Context) { + this.timeout(100_000); // Allow longer timeouts for RWC js verification + Harness.Baseline.runMultifileBaseline(baseName, "", () => { + return Harness.Compiler.iterateOutputs(compilerResult.js.values()); + }, baselineOpts, [".js", ".jsx"]); + }); - it("has the expected source maps", () => { - Harness.Baseline.runMultifileBaseline(baseName, "", () => { - if (!compilerResult.maps.size) { - return null; // eslint-disable-line no-null/no-null - } + it("has the expected declaration file content", () => { + Harness.Baseline.runMultifileBaseline(baseName, "", () => { + if (!compilerResult.dts.size) { + return null; // eslint-disable-line no-null/no-null + } - return Harness.Compiler.iterateOutputs(compilerResult.maps.values()); - }, baselineOpts, [".map"]); - }); + return Harness.Compiler.iterateOutputs(compilerResult.dts.values()); + }, baselineOpts, [".d.ts"]); + }); + + it("has the expected source maps", () => { + Harness.Baseline.runMultifileBaseline(baseName, "", () => { + if (!compilerResult.maps.size) { + return null; // eslint-disable-line no-null/no-null + } - it("has the expected errors", () => { - Harness.Baseline.runMultifileBaseline(baseName, ".errors.txt", () => { + return Harness.Compiler.iterateOutputs(compilerResult.maps.values()); + }, baselineOpts, [".map"]); + }); + + it("has the expected errors", () => { + Harness.Baseline.runMultifileBaseline(baseName, ".errors.txt", () => { + if (compilerResult.diagnostics.length === 0) { + return null; // eslint-disable-line no-null/no-null + } + // Do not include the library in the baselines to avoid noise + const baselineFiles = tsconfigFiles.concat(inputFiles, otherFiles).filter(f => !Harness.isDefaultLibraryFile(f.unitName)); + const errors = compilerResult.diagnostics.filter(e => !e.file || !Harness.isDefaultLibraryFile(e.file.fileName)); + return Harness.Compiler.iterateErrorBaseline(baselineFiles, errors, { caseSensitive, currentDirectory }); + }, baselineOpts); + }); + + // Ideally, a generated declaration file will have no errors. But we allow generated + // declaration file errors as part of the baseline. + it("has the expected errors in generated declaration files", () => { + if (compilerOptions.declaration && !compilerResult.diagnostics.length) { + Harness.Baseline.runMultifileBaseline(baseName, ".dts.errors.txt", () => { if (compilerResult.diagnostics.length === 0) { return null; // eslint-disable-line no-null/no-null } - // Do not include the library in the baselines to avoid noise - const baselineFiles = tsconfigFiles.concat(inputFiles, otherFiles).filter(f => !Harness.isDefaultLibraryFile(f.unitName)); - const errors = compilerResult.diagnostics.filter(e => !e.file || !Harness.isDefaultLibraryFile(e.file.fileName)); - return Harness.Compiler.iterateErrorBaseline(baselineFiles, errors, { caseSensitive, currentDirectory }); - }, baselineOpts); - }); - // Ideally, a generated declaration file will have no errors. But we allow generated - // declaration file errors as part of the baseline. - it("has the expected errors in generated declaration files", () => { - if (compilerOptions.declaration && !compilerResult.diagnostics.length) { - Harness.Baseline.runMultifileBaseline(baseName, ".dts.errors.txt", () => { - if (compilerResult.diagnostics.length === 0) { - return null; // eslint-disable-line no-null/no-null - } - - const declContext = Harness.Compiler.prepareDeclarationCompilationContext( - inputFiles, otherFiles, compilerResult, /*harnessSettings*/ undefined!, compilerOptions, currentDirectory // TODO: GH#18217 - ); - // Reset compilerResult before calling into `compileDeclarationFiles` so the memory from the original compilation can be freed - const links = compilerResult.symlinks; - compilerResult = undefined!; - const declFileCompilationResult = Harness.Compiler.compileDeclarationFiles(declContext, links)!; - - return Harness.Compiler.iterateErrorBaseline(tsconfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.diagnostics, { caseSensitive, currentDirectory }); - }, baselineOpts); - } - }); + const declContext = Harness.Compiler.prepareDeclarationCompilationContext( + inputFiles, otherFiles, compilerResult, /*harnessSettings*/ undefined!, compilerOptions, currentDirectory // TODO: GH#18217 + ); + // Reset compilerResult before calling into `compileDeclarationFiles` so the memory from the original compilation can be freed + const links = compilerResult.symlinks; + compilerResult = undefined!; + const declFileCompilationResult = Harness.Compiler.compileDeclarationFiles(declContext, links)!; + + return Harness.Compiler.iterateErrorBaseline(tsconfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.diagnostics, { caseSensitive, currentDirectory }); + }, baselineOpts); + } }); - } + }); +} - export class RWCRunner extends Harness.RunnerBase { - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return Harness.IO.getDirectories("internal/cases/rwc/"); - } +export class RWCRunner extends Harness.RunnerBase { + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return Harness.IO.getDirectories("internal/cases/rwc/"); + } - public kind(): Harness.TestRunnerKind { - return "rwc"; - } + public kind(): Harness.TestRunnerKind { + return "rwc"; + } - /** Setup the runner's tests so that they are ready to be executed by the harness - * The first test should be a describe/it block that sets up the harness's compiler instance appropriately - */ - public initializeTests(): void { - // Read in and evaluate the test list - for (const test of this.tests && this.tests.length ? this.tests : this.getTestFiles()) { - this.runTest(typeof test === "string" ? test : test.file); - } + /** Setup the runner's tests so that they are ready to be executed by the harness + * The first test should be a describe/it block that sets up the harness's compiler instance appropriately + */ + public initializeTests(): void { + // Read in and evaluate the test list + for (const test of this.tests && this.tests.length ? this.tests : this.getTestFiles()) { + this.runTest(typeof test === "string" ? test : test.file); } + } - private runTest(jsonFileName: string) { - runRWCTest(jsonFileName); - } + private runTest(jsonFileName: string) { + runRWCTest(jsonFileName); } } diff --git a/src/testRunner/test262Runner.ts b/src/testRunner/test262Runner.ts index 728d3fb322790..87aee2bf57401 100644 --- a/src/testRunner/test262Runner.ts +++ b/src/testRunner/test262Runner.ts @@ -1,110 +1,113 @@ -namespace Harness { - // In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`. - export class Test262BaselineRunner extends RunnerBase { - private static readonly basePath = "internal/cases/test262"; - private static readonly helpersFilePath = "tests/cases/test262-harness/helpers.d.ts"; - private static readonly helperFile: Compiler.TestFile = { - unitName: Test262BaselineRunner.helpersFilePath, - content: IO.readFile(Test262BaselineRunner.helpersFilePath)!, - }; - private static readonly testFileExtensionRegex = /\.js$/; - private static readonly options: ts.CompilerOptions = { - allowNonTsExtensions: true, - target: ts.ScriptTarget.Latest, - module: ts.ModuleKind.CommonJS - }; - private static readonly baselineOptions: Baseline.BaselineOptions = { - Subfolder: "test262", - Baselinefolder: "internal/baselines" - }; +import * as ts from "./_namespaces/ts"; +import * as compiler from "./_namespaces/compiler"; +import * as Utils from "./_namespaces/Utils"; +import { Baseline, Compiler, IO, RunnerBase, TestCaseParser, TestRunnerKind } from "./_namespaces/Harness"; - private static getTestFilePath(filename: string): string { - return Test262BaselineRunner.basePath + "/" + filename; - } - - private runTest(filePath: string) { - describe("test262 test for " + filePath, () => { - // Mocha holds onto the closure environment of the describe callback even after the test is done. - // Everything declared here should be cleared out in the "after" callback. - let testState: { - filename: string; - compilerResult: compiler.CompilationResult; - inputFiles: Compiler.TestFile[]; - }; +// In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`. +export class Test262BaselineRunner extends RunnerBase { + private static readonly basePath = "internal/cases/test262"; + private static readonly helpersFilePath = "tests/cases/test262-harness/helpers.d.ts"; + private static readonly helperFile: Compiler.TestFile = { + unitName: Test262BaselineRunner.helpersFilePath, + content: IO.readFile(Test262BaselineRunner.helpersFilePath)!, + }; + private static readonly testFileExtensionRegex = /\.js$/; + private static readonly options: ts.CompilerOptions = { + allowNonTsExtensions: true, + target: ts.ScriptTarget.Latest, + module: ts.ModuleKind.CommonJS + }; + private static readonly baselineOptions: Baseline.BaselineOptions = { + Subfolder: "test262", + Baselinefolder: "internal/baselines" + }; - before(() => { - const content = IO.readFile(filePath)!; - const testFilename = ts.removeFileExtension(filePath).replace(/\//g, "_") + ".test"; - const testCaseContent = TestCaseParser.makeUnitsFromTest(content, testFilename); + private static getTestFilePath(filename: string): string { + return Test262BaselineRunner.basePath + "/" + filename; + } - const inputFiles: Compiler.TestFile[] = testCaseContent.testUnitData.map(unit => { - const unitName = Test262BaselineRunner.getTestFilePath(unit.name); - return { unitName, content: unit.content }; - }); + private runTest(filePath: string) { + describe("test262 test for " + filePath, () => { + // Mocha holds onto the closure environment of the describe callback even after the test is done. + // Everything declared here should be cleared out in the "after" callback. + let testState: { + filename: string; + compilerResult: compiler.CompilationResult; + inputFiles: Compiler.TestFile[]; + }; - // Emit the results - testState = { - filename: testFilename, - inputFiles, - compilerResult: undefined!, // TODO: GH#18217 - }; + before(() => { + const content = IO.readFile(filePath)!; + const testFilename = ts.removeFileExtension(filePath).replace(/\//g, "_") + ".test"; + const testCaseContent = TestCaseParser.makeUnitsFromTest(content, testFilename); - testState.compilerResult = Compiler.compileFiles( - [Test262BaselineRunner.helperFile].concat(inputFiles), - /*otherFiles*/ [], - /* harnessOptions */ undefined, - Test262BaselineRunner.options, - /* currentDirectory */ undefined); + const inputFiles: Compiler.TestFile[] = testCaseContent.testUnitData.map(unit => { + const unitName = Test262BaselineRunner.getTestFilePath(unit.name); + return { unitName, content: unit.content }; }); - after(() => { - testState = undefined!; - }); + // Emit the results + testState = { + filename: testFilename, + inputFiles, + compilerResult: undefined!, // TODO: GH#18217 + }; - it("has the expected emitted code", () => { - const files = Array.from(testState.compilerResult.js.values()).filter(f => f.file !== Test262BaselineRunner.helpersFilePath); - Baseline.runBaseline(testState.filename + ".output.js", Compiler.collateOutputs(files), Test262BaselineRunner.baselineOptions); - }); + testState.compilerResult = Compiler.compileFiles( + [Test262BaselineRunner.helperFile].concat(inputFiles), + /*otherFiles*/ [], + /* harnessOptions */ undefined, + Test262BaselineRunner.options, + /* currentDirectory */ undefined); + }); - it("has the expected errors", () => { - const errors = testState.compilerResult.diagnostics; - // eslint-disable-next-line no-null/no-null - const baseline = errors.length === 0 ? null : Compiler.getErrorBaseline(testState.inputFiles, errors); - Baseline.runBaseline(testState.filename + ".errors.txt", baseline, Test262BaselineRunner.baselineOptions); - }); + after(() => { + testState = undefined!; + }); - it("satisfies invariants", () => { - const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename)); - Utils.assertInvariants(sourceFile, /*parent:*/ undefined); - }); + it("has the expected emitted code", () => { + const files = Array.from(testState.compilerResult.js.values()).filter(f => f.file !== Test262BaselineRunner.helpersFilePath); + Baseline.runBaseline(testState.filename + ".output.js", Compiler.collateOutputs(files), Test262BaselineRunner.baselineOptions); + }); - it("has the expected AST", () => { - const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename))!; - Baseline.runBaseline(testState.filename + ".AST.txt", Utils.sourceFileToJSON(sourceFile), Test262BaselineRunner.baselineOptions); - }); + it("has the expected errors", () => { + const errors = testState.compilerResult.diagnostics; + // eslint-disable-next-line no-null/no-null + const baseline = errors.length === 0 ? null : Compiler.getErrorBaseline(testState.inputFiles, errors); + Baseline.runBaseline(testState.filename + ".errors.txt", baseline, Test262BaselineRunner.baselineOptions); }); - } - public kind(): TestRunnerKind { - return "test262"; - } + it("satisfies invariants", () => { + const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename)); + Utils.assertInvariants(sourceFile, /*parent:*/ undefined); + }); - public enumerateTestFiles() { - // see also: `enumerateTestFiles` in tests/webTestServer.ts - return ts.map(this.enumerateFiles(Test262BaselineRunner.basePath, Test262BaselineRunner.testFileExtensionRegex, { recursive: true }), ts.normalizePath); - } + it("has the expected AST", () => { + const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename))!; + Baseline.runBaseline(testState.filename + ".AST.txt", Utils.sourceFileToJSON(sourceFile), Test262BaselineRunner.baselineOptions); + }); + }); + } - public initializeTests() { - // this will set up a series of describe/it blocks to run between the setup and cleanup phases - if (this.tests.length === 0) { - const testFiles = this.getTestFiles(); - testFiles.forEach(fn => { - this.runTest(fn); - }); - } - else { - this.tests.forEach(test => this.runTest(typeof test === "string" ? test : test.file)); - } + public kind(): TestRunnerKind { + return "test262"; + } + + public enumerateTestFiles() { + // see also: `enumerateTestFiles` in tests/webTestServer.ts + return ts.map(this.enumerateFiles(Test262BaselineRunner.basePath, Test262BaselineRunner.testFileExtensionRegex, { recursive: true }), ts.normalizePath); + } + + public initializeTests() { + // this will set up a series of describe/it blocks to run between the setup and cleanup phases + if (this.tests.length === 0) { + const testFiles = this.getTestFiles(); + testFiles.forEach(fn => { + this.runTest(fn); + }); + } + else { + this.tests.forEach(test => this.runTest(typeof test === "string" ? test : test.file)); } } } \ No newline at end of file diff --git a/src/testRunner/tests.ts b/src/testRunner/tests.ts new file mode 100644 index 0000000000000..8f0af1d9927db --- /dev/null +++ b/src/testRunner/tests.ts @@ -0,0 +1,189 @@ +import "./unittests/asserts"; +import "./unittests/base64"; +import "./unittests/builder"; +import "./unittests/comments"; +import "./unittests/compilerCore"; +import "./unittests/convertToBase64"; +import "./unittests/customTransforms"; +import "./unittests/factory"; +import "./unittests/incrementalParser"; +import "./unittests/jsDocParsing"; +import "./unittests/jsonParserRecovery"; +import "./unittests/moduleResolution"; +import "./unittests/parsePseudoBigInt"; +import "./unittests/paths"; +import "./unittests/printer"; +import "./unittests/programApi"; +import "./unittests/publicApi"; +import "./unittests/reuseProgramStructure"; +import "./unittests/semver"; +import "./unittests/transform"; +import "./unittests/typeParameterIsPossiblyReferenced"; +import "./unittests/config/commandLineParsing"; +import "./unittests/config/configurationExtension"; +import "./unittests/config/convertCompilerOptionsFromJson"; +import "./unittests/config/convertTypeAcquisitionFromJson"; +import "./unittests/config/initializeTSConfig"; +import "./unittests/config/matchFiles"; +import "./unittests/config/projectReferences"; +import "./unittests/config/showConfig"; +import "./unittests/config/tsconfigParsing"; +import "./unittests/config/tsconfigParsingWatchOptions"; +import "./unittests/evaluation/arraySpread"; +import "./unittests/evaluation/asyncArrow"; +import "./unittests/evaluation/asyncGenerator"; +import "./unittests/evaluation/autoAccessors"; +import "./unittests/evaluation/awaiter"; +import "./unittests/evaluation/destructuring"; +import "./unittests/evaluation/externalModules"; +import "./unittests/evaluation/forAwaitOf"; +import "./unittests/evaluation/forOf"; +import "./unittests/evaluation/generator"; +import "./unittests/evaluation/optionalCall"; +import "./unittests/evaluation/objectRest"; +import "./unittests/evaluation/superInStaticInitializer"; +import "./unittests/evaluation/templateLiteral"; +import "./unittests/evaluation/updateExpressionInModule"; +import "./unittests/services/cancellableLanguageServiceOperations"; +import "./unittests/services/colorization"; +import "./unittests/services/convertToAsyncFunction"; +import "./unittests/services/documentRegistry"; +import "./unittests/services/extract/constants"; +import "./unittests/services/extract/functions"; +import "./unittests/services/extract/symbolWalker"; +import "./unittests/services/extract/ranges"; +import "./unittests/services/hostNewLineSupport"; +import "./unittests/services/languageService"; +import "./unittests/services/organizeImports"; +import "./unittests/services/patternMatcher"; +import "./unittests/services/preProcessFile"; +import "./unittests/services/textChanges"; +import "./unittests/services/transpile"; +import "./unittests/tsbuild/amdModulesWithOut"; +import "./unittests/tsbuild/clean"; +import "./unittests/tsbuild/commandLine"; +import "./unittests/tsbuild/configFileErrors"; +import "./unittests/tsbuild/configFileExtends"; +import "./unittests/tsbuild/containerOnlyReferenced"; +import "./unittests/tsbuild/declarationEmit"; +import "./unittests/tsbuild/demo"; +import "./unittests/tsbuild/emitDeclarationOnly"; +import "./unittests/tsbuild/emptyFiles"; +import "./unittests/tsbuild/exitCodeOnBogusFile"; +import "./unittests/tsbuild/graphOrdering"; +import "./unittests/tsbuild/inferredTypeFromTransitiveModule"; +import "./unittests/tsbuild/javascriptProjectEmit"; +import "./unittests/tsbuild/lateBoundSymbol"; +import "./unittests/tsbuild/moduleResolution"; +import "./unittests/tsbuild/moduleSpecifiers"; +import "./unittests/tsbuild/noEmit"; +import "./unittests/tsbuild/noEmitOnError"; +import "./unittests/tsbuild/outFile"; +import "./unittests/tsbuild/outputPaths"; +import "./unittests/tsbuild/publicApi"; +import "./unittests/tsbuild/referencesWithRootDirInParent"; +import "./unittests/tsbuild/resolveJsonModule"; +import "./unittests/tsbuild/sample"; +import "./unittests/tsbuild/transitiveReferences"; +import "./unittests/tsbuildWatch/configFileErrors"; +import "./unittests/tsbuildWatch/demo"; +import "./unittests/tsbuildWatch/moduleResolution"; +import "./unittests/tsbuildWatch/noEmit"; +import "./unittests/tsbuildWatch/noEmitOnError"; +import "./unittests/tsbuildWatch/programUpdates"; +import "./unittests/tsbuildWatch/projectsBuilding"; +import "./unittests/tsbuildWatch/publicApi"; +import "./unittests/tsbuildWatch/reexport"; +import "./unittests/tsbuildWatch/watchEnvironment"; +import "./unittests/tsc/cancellationToken"; +import "./unittests/tsc/composite"; +import "./unittests/tsc/declarationEmit"; +import "./unittests/tsc/forceConsistentCasingInFileNames"; +import "./unittests/tsc/incremental"; +import "./unittests/tsc/listFilesOnly"; +import "./unittests/tsc/projectReferences"; +import "./unittests/tsc/redirect"; +import "./unittests/tsc/runWithoutArgs"; +import "./unittests/tscWatch/consoleClearing"; +import "./unittests/tscWatch/emit"; +import "./unittests/tscWatch/nodeNextWatch"; +import "./unittests/tscWatch/emitAndErrorUpdates"; +import "./unittests/tscWatch/forceConsistentCasingInFileNames"; +import "./unittests/tscWatch/incremental"; +import "./unittests/tscWatch/moduleResolution"; +import "./unittests/tscWatch/programUpdates"; +import "./unittests/tscWatch/projectsWithReferences"; +import "./unittests/tscWatch/resolutionCache"; +import "./unittests/tscWatch/sourceOfProjectReferenceRedirect"; +import "./unittests/tscWatch/watchApi"; +import "./unittests/tscWatch/watchEnvironment"; +import "./unittests/tsserver/applyChangesToOpenFiles"; +import "./unittests/tsserver/autoImportProvider"; +import "./unittests/tsserver/auxiliaryProject"; +import "./unittests/tsserver/cachingFileSystemInformation"; +import "./unittests/tsserver/cancellationToken"; +import "./unittests/tsserver/compileOnSave"; +import "./unittests/tsserver/completions"; +import "./unittests/tsserver/completionsIncomplete"; +import "./unittests/tsserver/configFileSearch"; +import "./unittests/tsserver/configuredProjects"; +import "./unittests/tsserver/declarationFileMaps"; +import "./unittests/tsserver/documentRegistry"; +import "./unittests/tsserver/duplicatePackages"; +import "./unittests/tsserver/dynamicFiles"; +import "./unittests/tsserver/events/largeFileReferenced"; +import "./unittests/tsserver/events/projectLanguageServiceState"; +import "./unittests/tsserver/events/projectLoading"; +import "./unittests/tsserver/events/projectUpdatedInBackground"; +import "./unittests/tsserver/exportMapCache"; +import "./unittests/tsserver/externalProjects"; +import "./unittests/tsserver/forceConsistentCasingInFileNames"; +import "./unittests/tsserver/formatSettings"; +import "./unittests/tsserver/getApplicableRefactors"; +import "./unittests/tsserver/getEditsForFileRename"; +import "./unittests/tsserver/getExportReferences"; +import "./unittests/tsserver/getFileReferences"; +import "./unittests/tsserver/importHelpers"; +import "./unittests/tsserver/inlayHints"; +import "./unittests/tsserver/inferredProjects"; +import "./unittests/tsserver/jsdocTag"; +import "./unittests/tsserver/languageService"; +import "./unittests/tsserver/maxNodeModuleJsDepth"; +import "./unittests/tsserver/metadataInResponse"; +import "./unittests/tsserver/moduleResolution"; +import "./unittests/tsserver/moduleSpecifierCache"; +import "./unittests/tsserver/navTo"; +import "./unittests/tsserver/occurences"; +import "./unittests/tsserver/openFile"; +import "./unittests/tsserver/packageJsonInfo"; +import "./unittests/tsserver/partialSemanticServer"; +import "./unittests/tsserver/plugins"; +import "./unittests/tsserver/projectErrors"; +import "./unittests/tsserver/projectReferenceCompileOnSave"; +import "./unittests/tsserver/projectReferenceErrors"; +import "./unittests/tsserver/projectReferences"; +import "./unittests/tsserver/projectReferencesSourcemap"; +import "./unittests/tsserver/projects"; +import "./unittests/tsserver/projectsWithReferences"; +import "./unittests/tsserver/refactors"; +import "./unittests/tsserver/reload"; +import "./unittests/tsserver/reloadProjects"; +import "./unittests/tsserver/rename"; +import "./unittests/tsserver/resolutionCache"; +import "./unittests/tsserver/session"; +import "./unittests/tsserver/skipLibCheck"; +import "./unittests/tsserver/smartSelection"; +import "./unittests/tsserver/symlinkCache"; +import "./unittests/tsserver/symLinks"; +import "./unittests/tsserver/syntacticServer"; +import "./unittests/tsserver/syntaxOperations"; +import "./unittests/tsserver/textStorage"; +import "./unittests/tsserver/telemetry"; +import "./unittests/tsserver/typeAquisition"; +import "./unittests/tsserver/typeOnlyImportChains"; +import "./unittests/tsserver/typeReferenceDirectives"; +import "./unittests/tsserver/typingsInstaller"; +import "./unittests/tsserver/versionCache"; +import "./unittests/tsserver/watchEnvironment"; +import "./unittests/tsserver/webServer"; +import "./unittests/debugDeprecation"; diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 5210426aad95f..ffcb8601da878 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -1,10 +1,6 @@ { - "extends": "../tsconfig-noncomposite-base", + "extends": "../tsconfig-base", "compilerOptions": { - "outFile": "../../built/local/run.js", - "composite": false, - "declaration": false, - "declarationMap": false, "types": [ "node", "mocha", "chai" ], @@ -14,236 +10,16 @@ ] }, "references": [ - { "path": "../compiler", "prepend": true }, - { "path": "../executeCommandLine", "prepend": true }, - { "path": "../services", "prepend": true }, - { "path": "../jsTyping", "prepend": true }, - { "path": "../server", "prepend": true }, - { "path": "../webServer", "prepend": true }, - { "path": "../typingsInstallerCore", "prepend": true }, - { "path": "../deprecatedCompat", "prepend": true }, - { "path": "../harness", "prepend": true }, - { "path": "../loggedIO", "prepend": true } + { "path": "../compiler" }, + { "path": "../executeCommandLine" }, + { "path": "../services" }, + { "path": "../jsTyping" }, + { "path": "../server" }, + { "path": "../webServer" }, + { "path": "../typingsInstallerCore" }, + { "path": "../deprecatedCompat" }, + { "path": "../harness" }, + { "path": "../loggedIO" } ], - - "files": [ - "compilerRef.ts", - "evaluatorRef.ts", - "fakesRef.ts", - "vpathRef.ts", - "vfsRef.ts", - "fourslashRef.ts", - "playbackRef.ts", - "utilsRef.ts", - "documentsRef.ts", - - "fourslashRunner.ts", - "compilerRunner.ts", - "projectsRunner.ts", - "rwcRunner.ts", - "externalCompileRunner.ts", - "test262Runner.ts", - - "parallel/host.ts", - "parallel/worker.ts", - "parallel/shared.ts", - - "runner.ts", - - "unittests/services/extract/helpers.ts", - "unittests/tsbuild/helpers.ts", - "unittests/tsc/helpers.ts", - "unittests/tscWatch/helpers.ts", - "unittests/tsserver/helpers.ts", - - "unittests/asserts.ts", - "unittests/base64.ts", - "unittests/builder.ts", - "unittests/comments.ts", - "unittests/compilerCore.ts", - "unittests/convertToBase64.ts", - "unittests/customTransforms.ts", - "unittests/factory.ts", - "unittests/incrementalParser.ts", - "unittests/jsDocParsing.ts", - "unittests/jsonParserRecovery.ts", - "unittests/moduleResolution.ts", - "unittests/parsePseudoBigInt.ts", - "unittests/paths.ts", - "unittests/printer.ts", - "unittests/programApi.ts", - "unittests/publicApi.ts", - "unittests/reuseProgramStructure.ts", - "unittests/semver.ts", - "unittests/transform.ts", - "unittests/typeParameterIsPossiblyReferenced.ts", - "unittests/config/commandLineParsing.ts", - "unittests/config/configurationExtension.ts", - "unittests/config/convertCompilerOptionsFromJson.ts", - "unittests/config/convertTypeAcquisitionFromJson.ts", - "unittests/config/initializeTSConfig.ts", - "unittests/config/matchFiles.ts", - "unittests/config/projectReferences.ts", - "unittests/config/showConfig.ts", - "unittests/config/tsconfigParsing.ts", - "unittests/config/tsconfigParsingWatchOptions.ts", - "unittests/evaluation/arraySpread.ts", - "unittests/evaluation/asyncArrow.ts", - "unittests/evaluation/asyncGenerator.ts", - "unittests/evaluation/autoAccessors.ts", - "unittests/evaluation/awaiter.ts", - "unittests/evaluation/destructuring.ts", - "unittests/evaluation/externalModules.ts", - "unittests/evaluation/forAwaitOf.ts", - "unittests/evaluation/forOf.ts", - "unittests/evaluation/generator.ts", - "unittests/evaluation/optionalCall.ts", - "unittests/evaluation/objectRest.ts", - "unittests/evaluation/superInStaticInitializer.ts", - "unittests/evaluation/templateLiteral.ts", - "unittests/evaluation/updateExpressionInModule.ts", - "unittests/services/cancellableLanguageServiceOperations.ts", - "unittests/services/colorization.ts", - "unittests/services/convertToAsyncFunction.ts", - "unittests/services/documentRegistry.ts", - "unittests/services/extract/constants.ts", - "unittests/services/extract/functions.ts", - "unittests/services/extract/symbolWalker.ts", - "unittests/services/extract/ranges.ts", - "unittests/services/hostNewLineSupport.ts", - "unittests/services/languageService.ts", - "unittests/services/organizeImports.ts", - "unittests/services/patternMatcher.ts", - "unittests/services/preProcessFile.ts", - "unittests/services/textChanges.ts", - "unittests/services/transpile.ts", - "unittests/tsbuild/amdModulesWithOut.ts", - "unittests/tsbuild/clean.ts", - "unittests/tsbuild/commandLine.ts", - "unittests/tsbuild/configFileErrors.ts", - "unittests/tsbuild/configFileExtends.ts", - "unittests/tsbuild/containerOnlyReferenced.ts", - "unittests/tsbuild/declarationEmit.ts", - "unittests/tsbuild/demo.ts", - "unittests/tsbuild/emitDeclarationOnly.ts", - "unittests/tsbuild/emptyFiles.ts", - "unittests/tsbuild/exitCodeOnBogusFile.ts", - "unittests/tsbuild/graphOrdering.ts", - "unittests/tsbuild/inferredTypeFromTransitiveModule.ts", - "unittests/tsbuild/javascriptProjectEmit.ts", - "unittests/tsbuild/lateBoundSymbol.ts", - "unittests/tsbuild/moduleResolution.ts", - "unittests/tsbuild/moduleSpecifiers.ts", - "unittests/tsbuild/noEmit.ts", - "unittests/tsbuild/noEmitOnError.ts", - "unittests/tsbuild/outFile.ts", - "unittests/tsbuild/outputPaths.ts", - "unittests/tsbuild/publicApi.ts", - "unittests/tsbuild/referencesWithRootDirInParent.ts", - "unittests/tsbuild/resolveJsonModule.ts", - "unittests/tsbuild/sample.ts", - "unittests/tsbuild/transitiveReferences.ts", - "unittests/tsbuildWatch/configFileErrors.ts", - "unittests/tsbuildWatch/demo.ts", - "unittests/tsbuildWatch/moduleResolution.ts", - "unittests/tsbuildWatch/noEmit.ts", - "unittests/tsbuildWatch/noEmitOnError.ts", - "unittests/tsbuildWatch/programUpdates.ts", - "unittests/tsbuildWatch/projectsBuilding.ts", - "unittests/tsbuildWatch/publicApi.ts", - "unittests/tsbuildWatch/reexport.ts", - "unittests/tsbuildWatch/watchEnvironment.ts", - "unittests/tsc/cancellationToken.ts", - "unittests/tsc/composite.ts", - "unittests/tsc/declarationEmit.ts", - "unittests/tsc/forceConsistentCasingInFileNames.ts", - "unittests/tsc/incremental.ts", - "unittests/tsc/listFilesOnly.ts", - "unittests/tsc/projectReferences.ts", - "unittests/tsc/redirect.ts", - "unittests/tsc/runWithoutArgs.ts", - "unittests/tscWatch/consoleClearing.ts", - "unittests/tscWatch/emit.ts", - "unittests/tscWatch/nodeNextWatch.ts", - "unittests/tscWatch/emitAndErrorUpdates.ts", - "unittests/tscWatch/forceConsistentCasingInFileNames.ts", - "unittests/tscWatch/incremental.ts", - "unittests/tscWatch/moduleResolution.ts", - "unittests/tscWatch/programUpdates.ts", - "unittests/tscWatch/projectsWithReferences.ts", - "unittests/tscWatch/resolutionCache.ts", - "unittests/tscWatch/sourceOfProjectReferenceRedirect.ts", - "unittests/tscWatch/watchApi.ts", - "unittests/tscWatch/watchEnvironment.ts", - "unittests/tsserver/applyChangesToOpenFiles.ts", - "unittests/tsserver/autoImportProvider.ts", - "unittests/tsserver/auxiliaryProject.ts", - "unittests/tsserver/cachingFileSystemInformation.ts", - "unittests/tsserver/cancellationToken.ts", - "unittests/tsserver/compileOnSave.ts", - "unittests/tsserver/completions.ts", - "unittests/tsserver/completionsIncomplete.ts", - "unittests/tsserver/configFileSearch.ts", - "unittests/tsserver/configuredProjects.ts", - "unittests/tsserver/declarationFileMaps.ts", - "unittests/tsserver/documentRegistry.ts", - "unittests/tsserver/duplicatePackages.ts", - "unittests/tsserver/dynamicFiles.ts", - "unittests/tsserver/events/largeFileReferenced.ts", - "unittests/tsserver/events/projectLanguageServiceState.ts", - "unittests/tsserver/events/projectLoading.ts", - "unittests/tsserver/events/projectUpdatedInBackground.ts", - "unittests/tsserver/exportMapCache.ts", - "unittests/tsserver/externalProjects.ts", - "unittests/tsserver/forceConsistentCasingInFileNames.ts", - "unittests/tsserver/formatSettings.ts", - "unittests/tsserver/getApplicableRefactors.ts", - "unittests/tsserver/getEditsForFileRename.ts", - "unittests/tsserver/getExportReferences.ts", - "unittests/tsserver/getFileReferences.ts", - "unittests/tsserver/importHelpers.ts", - "unittests/tsserver/inlayHints.ts", - "unittests/tsserver/inferredProjects.ts", - "unittests/tsserver/jsdocTag.ts", - "unittests/tsserver/languageService.ts", - "unittests/tsserver/maxNodeModuleJsDepth.ts", - "unittests/tsserver/metadataInResponse.ts", - "unittests/tsserver/moduleResolution.ts", - "unittests/tsserver/moduleSpecifierCache.ts", - "unittests/tsserver/navTo.ts", - "unittests/tsserver/occurences.ts", - "unittests/tsserver/openFile.ts", - "unittests/tsserver/packageJsonInfo.ts", - "unittests/tsserver/partialSemanticServer.ts", - "unittests/tsserver/plugins.ts", - "unittests/tsserver/projectErrors.ts", - "unittests/tsserver/projectReferenceCompileOnSave.ts", - "unittests/tsserver/projectReferenceErrors.ts", - "unittests/tsserver/projectReferences.ts", - "unittests/tsserver/projectReferencesSourcemap.ts", - "unittests/tsserver/projects.ts", - "unittests/tsserver/projectsWithReferences.ts", - "unittests/tsserver/refactors.ts", - "unittests/tsserver/reload.ts", - "unittests/tsserver/reloadProjects.ts", - "unittests/tsserver/rename.ts", - "unittests/tsserver/resolutionCache.ts", - "unittests/tsserver/session.ts", - "unittests/tsserver/skipLibCheck.ts", - "unittests/tsserver/smartSelection.ts", - "unittests/tsserver/symlinkCache.ts", - "unittests/tsserver/symLinks.ts", - "unittests/tsserver/syntacticServer.ts", - "unittests/tsserver/syntaxOperations.ts", - "unittests/tsserver/textStorage.ts", - "unittests/tsserver/telemetry.ts", - "unittests/tsserver/typeAquisition.ts", - "unittests/tsserver/typeOnlyImportChains.ts", - "unittests/tsserver/typeReferenceDirectives.ts", - "unittests/tsserver/typingsInstaller.ts", - "unittests/tsserver/versionCache.ts", - "unittests/tsserver/watchEnvironment.ts", - "unittests/tsserver/webServer.ts", - "unittests/debugDeprecation.ts" - ] + "include": ["**/*"] } diff --git a/src/testRunner/unittests/asserts.ts b/src/testRunner/unittests/asserts.ts index a0cde51a0f841..23d8e20a0df95 100644 --- a/src/testRunner/unittests/asserts.ts +++ b/src/testRunner/unittests/asserts.ts @@ -1,12 +1,12 @@ -namespace ts { - describe("unittests:: assert", () => { - it("deepEqual", () => { - assert.throws(() => assert.deepEqual(factory.createNodeArray([factory.createIdentifier("A")]), factory.createNodeArray([factory.createIdentifier("B")]))); - assert.throws(() => assert.deepEqual(factory.createNodeArray([], /*hasTrailingComma*/ true), factory.createNodeArray([], /*hasTrailingComma*/ false))); - assert.deepEqual(factory.createNodeArray([factory.createIdentifier("A")], /*hasTrailingComma*/ true), factory.createNodeArray([factory.createIdentifier("A")], /*hasTrailingComma*/ true)); - }); - it("assertNever on string has correct error", () => { - assert.throws(() => Debug.assertNever("hi" as never), "Debug Failure. Illegal value: \"hi\""); - }); +import * as ts from "../_namespaces/ts"; + +describe("unittests:: assert", () => { + it("deepEqual", () => { + assert.throws(() => assert.deepEqual(ts.factory.createNodeArray([ts.factory.createIdentifier("A")]), ts.factory.createNodeArray([ts.factory.createIdentifier("B")]))); + assert.throws(() => assert.deepEqual(ts.factory.createNodeArray([], /*hasTrailingComma*/ true), ts.factory.createNodeArray([], /*hasTrailingComma*/ false))); + assert.deepEqual(ts.factory.createNodeArray([ts.factory.createIdentifier("A")], /*hasTrailingComma*/ true), ts.factory.createNodeArray([ts.factory.createIdentifier("A")], /*hasTrailingComma*/ true)); }); -} + it("assertNever on string has correct error", () => { + assert.throws(() => ts.Debug.assertNever("hi" as never), "Debug Failure. Illegal value: \"hi\""); + }); +}); diff --git a/src/testRunner/unittests/base64.ts b/src/testRunner/unittests/base64.ts index 1dc51a8fbf5ae..c952da51ef486 100644 --- a/src/testRunner/unittests/base64.ts +++ b/src/testRunner/unittests/base64.ts @@ -1,22 +1,22 @@ -namespace ts { - describe("unittests:: base64", () => { - describe("base64decode", () => { - it("can decode input strings correctly without needing a host implementation", () => { - const tests = [ - // "a", - // "this is a test", - // " !\"#$ %&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", - "日本語", - "🐱", - "\x00\x01", - "\t\n\r\\\"\'\u0062", - "====", - "", - ]; - for (const test of tests) { - assert.equal(base64decode({}, convertToBase64(test)), test); - } - }); +import * as ts from "../_namespaces/ts"; + +describe("unittests:: base64", () => { + describe("base64decode", () => { + it("can decode input strings correctly without needing a host implementation", () => { + const tests = [ + // "a", + // "this is a test", + // " !\"#$ %&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", + "日本語", + "🐱", + "\x00\x01", + "\t\n\r\\\"\'\u0062", + "====", + "", + ]; + for (const test of tests) { + assert.equal(ts.base64decode({}, ts.convertToBase64(test)), test); + } }); }); -} +}); diff --git a/src/testRunner/unittests/builder.ts b/src/testRunner/unittests/builder.ts index bf0415f13598c..6494c6f3bed65 100644 --- a/src/testRunner/unittests/builder.ts +++ b/src/testRunner/unittests/builder.ts @@ -1,130 +1,130 @@ -namespace ts { - describe("unittests:: builder", () => { - it("emits dependent files", () => { - const files: NamedSourceText[] = [ - { name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") }, - { name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, - { name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") }, - ]; - - let program = newProgram(files, ["/a.ts"], {}); - const assertChanges = makeAssertChanges(() => program); - - assertChanges(["/c.js", "/b.js", "/a.js"]); - - program = updateProgramFile(program, "/a.ts", "//comment"); - assertChanges(["/a.js"]); - - program = updateProgramFile(program, "/b.ts", "export const b = c + 1;"); - assertChanges(["/b.js", "/a.js"]); - - program = updateProgramFile(program, "/c.ts", "export const c = 1;"); - assertChanges(["/c.js", "/b.js"]); - }); - - it("if emitting all files, emits the changed file first", () => { - const files: NamedSourceText[] = [ - { name: "/a.ts", text: SourceText.New("", "", "namespace A { export const x = 0; }") }, - { name: "/b.ts", text: SourceText.New("", "", "namespace B { export const x = 0; }") }, - ]; - - let program = newProgram(files, ["/a.ts", "/b.ts"], {}); - const assertChanges = makeAssertChanges(() => program); - - assertChanges(["/a.js", "/b.js"]); - - program = updateProgramFile(program, "/a.ts", "namespace A { export const x = 1; }"); - assertChanges(["/a.js", "/b.js"]); - - program = updateProgramFile(program, "/b.ts", "namespace B { export const x = 1; }"); - assertChanges(["/b.js", "/a.js"]); - }); - - it("keeps the file in affected files if cancellation token throws during the operation", () => { - const files: NamedSourceText[] = [ - { name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") }, - { name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, - { name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") }, - { name: "/d.ts", text: SourceText.New("", "", "export const dd = 0;") }, - { name: "/e.ts", text: SourceText.New("", "", "export const ee = 0;") }, - ]; - - let program = newProgram(files, ["/d.ts", "/e.ts", "/a.ts"], {}); - const assertChanges = makeAssertChangesWithCancellationToken(() => program); - // No cancellation - assertChanges(["/d.js", "/e.js", "/c.js", "/b.js", "/a.js"]); - - // cancel when emitting a.ts - program = updateProgramFile(program, "/a.ts", "export function foo() { }"); - assertChanges(["/a.js"], 0); - // Change d.ts and verify previously pending a.ts is emitted as well - program = updateProgramFile(program, "/d.ts", "export function bar() { }"); - assertChanges(["/a.js", "/d.js"]); - - // Cancel when emitting b.js - program = updateProgramFile(program, "/b.ts", "export class b { foo() { c + 1; } }"); - program = updateProgramFile(program, "/d.ts", "export function bar2() { }"); - assertChanges(["/d.js", "/b.js", "/a.js"], 1); - // Change e.ts and verify previously b.js as well as a.js get emitted again since previous change was consumed completely but not d.ts - program = updateProgramFile(program, "/e.ts", "export function bar3() { }"); - assertChanges(["/b.js", "/a.js", "/e.js"]); - }); +import * as ts from "../_namespaces/ts"; + +describe("unittests:: builder", () => { + it("emits dependent files", () => { + const files: ts.NamedSourceText[] = [ + { name: "/a.ts", text: ts.SourceText.New("", 'import { b } from "./b";', "") }, + { name: "/b.ts", text: ts.SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, + { name: "/c.ts", text: ts.SourceText.New("", "", "export const c = 0;") }, + ]; + + let program = ts.newProgram(files, ["/a.ts"], {}); + const assertChanges = makeAssertChanges(() => program); + + assertChanges(["/c.js", "/b.js", "/a.js"]); + + program = updateProgramFile(program, "/a.ts", "//comment"); + assertChanges(["/a.js"]); + + program = updateProgramFile(program, "/b.ts", "export const b = c + 1;"); + assertChanges(["/b.js", "/a.js"]); + + program = updateProgramFile(program, "/c.ts", "export const c = 1;"); + assertChanges(["/c.js", "/b.js"]); + }); + + it("if emitting all files, emits the changed file first", () => { + const files: ts.NamedSourceText[] = [ + { name: "/a.ts", text: ts.SourceText.New("", "", "namespace A { export const x = 0; }") }, + { name: "/b.ts", text: ts.SourceText.New("", "", "namespace B { export const x = 0; }") }, + ]; + + let program = ts.newProgram(files, ["/a.ts", "/b.ts"], {}); + const assertChanges = makeAssertChanges(() => program); + + assertChanges(["/a.js", "/b.js"]); + + program = updateProgramFile(program, "/a.ts", "namespace A { export const x = 1; }"); + assertChanges(["/a.js", "/b.js"]); + + program = updateProgramFile(program, "/b.ts", "namespace B { export const x = 1; }"); + assertChanges(["/b.js", "/a.js"]); }); - function makeAssertChanges(getProgram: () => Program): (fileNames: readonly string[]) => void { - const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; - let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; - return fileNames => { - const program = getProgram(); - builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); - const outputFileNames: string[] = []; - // eslint-disable-next-line no-empty - while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName))) { + it("keeps the file in affected files if cancellation token throws during the operation", () => { + const files: ts.NamedSourceText[] = [ + { name: "/a.ts", text: ts.SourceText.New("", 'import { b } from "./b";', "") }, + { name: "/b.ts", text: ts.SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, + { name: "/c.ts", text: ts.SourceText.New("", "", "export const c = 0;") }, + { name: "/d.ts", text: ts.SourceText.New("", "", "export const dd = 0;") }, + { name: "/e.ts", text: ts.SourceText.New("", "", "export const ee = 0;") }, + ]; + + let program = ts.newProgram(files, ["/d.ts", "/e.ts", "/a.ts"], {}); + const assertChanges = makeAssertChangesWithCancellationToken(() => program); + // No cancellation + assertChanges(["/d.js", "/e.js", "/c.js", "/b.js", "/a.js"]); + + // cancel when emitting a.ts + program = updateProgramFile(program, "/a.ts", "export function foo() { }"); + assertChanges(["/a.js"], 0); + // Change d.ts and verify previously pending a.ts is emitted as well + program = updateProgramFile(program, "/d.ts", "export function bar() { }"); + assertChanges(["/a.js", "/d.js"]); + + // Cancel when emitting b.js + program = updateProgramFile(program, "/b.ts", "export class b { foo() { c + 1; } }"); + program = updateProgramFile(program, "/d.ts", "export function bar2() { }"); + assertChanges(["/d.js", "/b.js", "/a.js"], 1); + // Change e.ts and verify previously b.js as well as a.js get emitted again since previous change was consumed completely but not d.ts + program = updateProgramFile(program, "/e.ts", "export function bar3() { }"); + assertChanges(["/b.js", "/a.js", "/e.js"]); + }); +}); + +function makeAssertChanges(getProgram: () => ts.Program): (fileNames: readonly string[]) => void { + const host: ts.BuilderProgramHost = { useCaseSensitiveFileNames: ts.returnTrue }; + let builderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram | undefined; + return fileNames => { + const program = getProgram(); + builderProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); + const outputFileNames: string[] = []; + // eslint-disable-next-line no-empty + while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName))) { + } + assert.deepEqual(outputFileNames, fileNames); + }; +} + +function makeAssertChangesWithCancellationToken(getProgram: () => ts.Program): (fileNames: readonly string[], cancelAfterEmitLength?: number) => void { + const host: ts.BuilderProgramHost = { useCaseSensitiveFileNames: ts.returnTrue }; + let builderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram | undefined; + let cancel = false; + const cancellationToken: ts.CancellationToken = { + isCancellationRequested: () => cancel, + throwIfCancellationRequested: () => { + if (cancel) { + throw new ts.OperationCanceledException(); } - assert.deepEqual(outputFileNames, fileNames); - }; - } - - function makeAssertChangesWithCancellationToken(getProgram: () => Program): (fileNames: readonly string[], cancelAfterEmitLength?: number) => void { - const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; - let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; - let cancel = false; - const cancellationToken: CancellationToken = { - isCancellationRequested: () => cancel, - throwIfCancellationRequested: () => { - if (cancel) { - throw new OperationCanceledException(); + }, + }; + return (fileNames, cancelAfterEmitLength?: number) => { + cancel = false; + let operationWasCancelled = false; + const program = getProgram(); + builderProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); + const outputFileNames: string[] = []; + try { + do { + assert.isFalse(cancel); + if (outputFileNames.length === cancelAfterEmitLength) { + cancel = true; } - }, - }; - return (fileNames, cancelAfterEmitLength?: number) => { - cancel = false; - let operationWasCancelled = false; - const program = getProgram(); - builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); - const outputFileNames: string[] = []; - try { - do { - assert.isFalse(cancel); - if (outputFileNames.length === cancelAfterEmitLength) { - cancel = true; - } - } while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName), cancellationToken)); - } - catch (e) { - assert.isFalse(operationWasCancelled); - assert(e instanceof OperationCanceledException, e.toString()); - operationWasCancelled = true; - } - assert.equal(cancel, operationWasCancelled); - assert.equal(operationWasCancelled, fileNames.length > cancelAfterEmitLength!); - assert.deepEqual(outputFileNames, fileNames.slice(0, cancelAfterEmitLength)); - }; - } - - function updateProgramFile(program: ProgramWithSourceTexts, fileName: string, fileContent: string): ProgramWithSourceTexts { - return updateProgram(program, program.getRootFileNames(), program.getCompilerOptions(), files => { - updateProgramText(files, fileName, fileContent); - }); - } + } while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName), cancellationToken)); + } + catch (e) { + assert.isFalse(operationWasCancelled); + assert(e instanceof ts.OperationCanceledException, e.toString()); + operationWasCancelled = true; + } + assert.equal(cancel, operationWasCancelled); + assert.equal(operationWasCancelled, fileNames.length > cancelAfterEmitLength!); + assert.deepEqual(outputFileNames, fileNames.slice(0, cancelAfterEmitLength)); + }; +} + +function updateProgramFile(program: ts.ProgramWithSourceTexts, fileName: string, fileContent: string): ts.ProgramWithSourceTexts { + return ts.updateProgram(program, program.getRootFileNames(), program.getCompilerOptions(), files => { + ts.updateProgramText(files, fileName, fileContent); + }); } diff --git a/src/testRunner/unittests/comments.ts b/src/testRunner/unittests/comments.ts index 59f3020c112bc..74d6919285616 100644 --- a/src/testRunner/unittests/comments.ts +++ b/src/testRunner/unittests/comments.ts @@ -1,32 +1,32 @@ -namespace ts { - describe("comment parsing", () => { - const withShebang = `#! node +import * as ts from "../_namespaces/ts"; + +describe("comment parsing", () => { + const withShebang = `#! node /** comment */ // another one ;`; - const noShebang = `/** comment */ + const noShebang = `/** comment */ // another one ;`; - const withTrailing = `;/* comment */ + const withTrailing = `;/* comment */ // another one `; - it("skips shebang", () => { - const result = getLeadingCommentRanges(withShebang, 0); - assert.isDefined(result); - assert.strictEqual(result!.length, 2); - }); + it("skips shebang", () => { + const result = ts.getLeadingCommentRanges(withShebang, 0); + assert.isDefined(result); + assert.strictEqual(result!.length, 2); + }); - it("treats all comments at start of file as leading comments", () => { - const result = getLeadingCommentRanges(noShebang, 0); - assert.isDefined(result); - assert.strictEqual(result!.length, 2); - }); + it("treats all comments at start of file as leading comments", () => { + const result = ts.getLeadingCommentRanges(noShebang, 0); + assert.isDefined(result); + assert.strictEqual(result!.length, 2); + }); - it("returns leading comments if position is not 0", () => { - const result = getLeadingCommentRanges(withTrailing, 1); - assert.isDefined(result); - assert.strictEqual(result!.length, 1); - assert.strictEqual(result![0].kind, SyntaxKind.SingleLineCommentTrivia); - }); + it("returns leading comments if position is not 0", () => { + const result = ts.getLeadingCommentRanges(withTrailing, 1); + assert.isDefined(result); + assert.strictEqual(result!.length, 1); + assert.strictEqual(result![0].kind, ts.SyntaxKind.SingleLineCommentTrivia); }); -} +}); diff --git a/src/testRunner/unittests/compilerCore.ts b/src/testRunner/unittests/compilerCore.ts index 49c9601a39241..1b7f82118016f 100644 --- a/src/testRunner/unittests/compilerCore.ts +++ b/src/testRunner/unittests/compilerCore.ts @@ -1,195 +1,195 @@ -namespace ts { - describe("unittests:: compilerCore", () => { - describe("equalOwnProperties", () => { - it("correctly equates objects", () => { - assert.isTrue(equalOwnProperties({}, {})); - assert.isTrue(equalOwnProperties({ a: 1 }, { a: 1 })); - assert.isTrue(equalOwnProperties({ a: 1, b: 2 }, { b: 2, a: 1 })); - }); - it("correctly identifies unmatched objects", () => { - assert.isFalse(equalOwnProperties({}, { a: 1 }), "missing left property"); - assert.isFalse(equalOwnProperties({ a: 1 }, {}), "missing right property"); - assert.isFalse(equalOwnProperties({ a: 1 }, { a: 2 }), "differing property"); - }); - it("correctly identifies undefined vs hasOwnProperty", () => { - assert.isFalse(equalOwnProperties({}, { a: undefined }), "missing left property"); - assert.isFalse(equalOwnProperties({ a: undefined }, {}), "missing right property"); - }); - it("truthiness", () => { - const trythyTest = (l: any, r: any) => !!l === !!r; - assert.isFalse(equalOwnProperties({}, { a: 1 }, trythyTest), "missing left truthy property"); - assert.isFalse(equalOwnProperties({}, { a: 0 }, trythyTest), "missing left falsey property"); - assert.isFalse(equalOwnProperties({ a: 1 }, {}, trythyTest), "missing right truthy property"); - assert.isFalse(equalOwnProperties({ a: 0 }, {}, trythyTest), "missing right falsey property"); - assert.isTrue(equalOwnProperties({ a: 1 }, { a: "foo" }, trythyTest), "valid equality"); - }); - it("all equal", () => { - assert.isFalse(equalOwnProperties({}, { a: 1 }, () => true), "missing left property"); - assert.isFalse(equalOwnProperties({ a: 1 }, {}, () => true), "missing right property"); - assert.isTrue(equalOwnProperties({ a: 1 }, { a: 2 }, () => true), "valid equality"); - }); +import * as ts from "../_namespaces/ts"; + +describe("unittests:: compilerCore", () => { + describe("equalOwnProperties", () => { + it("correctly equates objects", () => { + assert.isTrue(ts.equalOwnProperties({}, {})); + assert.isTrue(ts.equalOwnProperties({ a: 1 }, { a: 1 })); + assert.isTrue(ts.equalOwnProperties({ a: 1, b: 2 }, { b: 2, a: 1 })); }); - describe("customSet", () => { - it("mutation", () => { - const set = createSet(x => x % 2, (x, y) => (x % 4) === (y % 4)); - assert.equal(set.size, 0); + it("correctly identifies unmatched objects", () => { + assert.isFalse(ts.equalOwnProperties({}, { a: 1 }), "missing left property"); + assert.isFalse(ts.equalOwnProperties({ a: 1 }, {}), "missing right property"); + assert.isFalse(ts.equalOwnProperties({ a: 1 }, { a: 2 }), "differing property"); + }); + it("correctly identifies undefined vs hasOwnProperty", () => { + assert.isFalse(ts.equalOwnProperties({}, { a: undefined }), "missing left property"); + assert.isFalse(ts.equalOwnProperties({ a: undefined }, {}), "missing right property"); + }); + it("truthiness", () => { + const trythyTest = (l: any, r: any) => !!l === !!r; + assert.isFalse(ts.equalOwnProperties({}, { a: 1 }, trythyTest), "missing left truthy property"); + assert.isFalse(ts.equalOwnProperties({}, { a: 0 }, trythyTest), "missing left falsey property"); + assert.isFalse(ts.equalOwnProperties({ a: 1 }, {}, trythyTest), "missing right truthy property"); + assert.isFalse(ts.equalOwnProperties({ a: 0 }, {}, trythyTest), "missing right falsey property"); + assert.isTrue(ts.equalOwnProperties({ a: 1 }, { a: "foo" }, trythyTest), "valid equality"); + }); + it("all equal", () => { + assert.isFalse(ts.equalOwnProperties({}, { a: 1 }, () => true), "missing left property"); + assert.isFalse(ts.equalOwnProperties({ a: 1 }, {}, () => true), "missing right property"); + assert.isTrue(ts.equalOwnProperties({ a: 1 }, { a: 2 }, () => true), "valid equality"); + }); + }); + describe("customSet", () => { + it("mutation", () => { + const set = ts.createSet(x => x % 2, (x, y) => (x % 4) === (y % 4)); + assert.equal(set.size, 0); - const newSet = set.add(0); - assert.strictEqual(newSet, set); - assert.equal(set.size, 1); + const newSet = set.add(0); + assert.strictEqual(newSet, set); + assert.equal(set.size, 1); - set.add(1); - assert.equal(set.size, 2); + set.add(1); + assert.equal(set.size, 2); - set.add(2); // Collision with 0 - assert.equal(set.size, 3); + set.add(2); // Collision with 0 + assert.equal(set.size, 3); - set.add(3); // Collision with 1 - assert.equal(set.size, 4); + set.add(3); // Collision with 1 + assert.equal(set.size, 4); - set.add(4); // Already present as 0 - assert.equal(set.size, 4); + set.add(4); // Already present as 0 + assert.equal(set.size, 4); - set.add(5); // Already present as 1 - assert.equal(set.size, 4); - - assert.isTrue(set.has(6)); - assert.isTrue(set.has(7)); - - assert.isTrue(set.delete(8)); - assert.equal(set.size, 3); - assert.isFalse(set.has(8)); - assert.isFalse(set.delete(8)); + set.add(5); // Already present as 1 + assert.equal(set.size, 4); - assert.isTrue(set.delete(9)); - assert.equal(set.size, 2); + assert.isTrue(set.has(6)); + assert.isTrue(set.has(7)); - assert.isTrue(set.delete(10)); - assert.equal(set.size, 1); + assert.isTrue(set.delete(8)); + assert.equal(set.size, 3); + assert.isFalse(set.has(8)); + assert.isFalse(set.delete(8)); - assert.isTrue(set.delete(11)); - assert.equal(set.size, 0); - }); - it("resizing", () => { - const set = createSet(x => x % 2, (x, y) => x === y); - const elementCount = 100; + assert.isTrue(set.delete(9)); + assert.equal(set.size, 2); - for (let i = 0; i < elementCount; i++) { - assert.isFalse(set.has(i)); - set.add(i); - assert.isTrue(set.has(i)); - assert.equal(set.size, i + 1); - } + assert.isTrue(set.delete(10)); + assert.equal(set.size, 1); - for (let i = 0; i < elementCount; i++) { - assert.isTrue(set.has(i)); - set.delete(i); - assert.isFalse(set.has(i)); - assert.equal(set.size, elementCount - (i + 1)); - } - }); - it("clear", () => { - const set = createSet(x => x % 2, (x, y) => (x % 4) === (y % 4)); - for (let j = 0; j < 2; j++) { - for (let i = 0; i < 100; i++) { - set.add(i); - } - assert.equal(set.size, 4); - - set.clear(); - assert.equal(set.size, 0); - assert.isFalse(set.has(0)); - } - }); - it("forEach", () => { - const set = createSet(x => x % 2, (x, y) => (x % 4) === (y % 4)); + assert.isTrue(set.delete(11)); + assert.equal(set.size, 0); + }); + it("resizing", () => { + const set = ts.createSet(x => x % 2, (x, y) => x === y); + const elementCount = 100; + + for (let i = 0; i < elementCount; i++) { + assert.isFalse(set.has(i)); + set.add(i); + assert.isTrue(set.has(i)); + assert.equal(set.size, i + 1); + } + + for (let i = 0; i < elementCount; i++) { + assert.isTrue(set.has(i)); + set.delete(i); + assert.isFalse(set.has(i)); + assert.equal(set.size, elementCount - (i + 1)); + } + }); + it("clear", () => { + const set = ts.createSet(x => x % 2, (x, y) => (x % 4) === (y % 4)); + for (let j = 0; j < 2; j++) { for (let i = 0; i < 100; i++) { set.add(i); } + assert.equal(set.size, 4); - const values: number[] = []; - const keys: number[] = []; - set.forEach((value, key) => { - values.push(value); - keys.push(key); - }); - - assert.equal(values.length, 4); - - values.sort(); - keys.sort(); - - // NB: first equal value wins (i.e. not [96, 97, 98, 99]) - const expected = [0, 1, 2, 3]; - assert.deepEqual(values, expected); - assert.deepEqual(keys, expected); - }); - it("iteration", () => { - const set = createSet(x => x % 2, (x, y) => (x % 4) === (y % 4)); - for (let i = 0; i < 4; i++) { - set.add(i); - } - - const expected = [0, 1, 2, 3]; - let actual: number[]; - - actual = arrayFrom(set.keys()); - actual.sort(); - assert.deepEqual(actual, expected); - - actual = arrayFrom(set.values()); - actual.sort(); - assert.deepEqual(actual, expected); - - const actualTuple = arrayFrom(set.entries()); - assert.isFalse(actualTuple.some(([v, k]) => v !== k)); - actual = actualTuple.map(([v, _]) => v); - actual.sort(); - assert.deepEqual(actual, expected); + set.clear(); + assert.equal(set.size, 0); + assert.isFalse(set.has(0)); + } + }); + it("forEach", () => { + const set = ts.createSet(x => x % 2, (x, y) => (x % 4) === (y % 4)); + for (let i = 0; i < 100; i++) { + set.add(i); + } + + const values: number[] = []; + const keys: number[] = []; + set.forEach((value, key) => { + values.push(value); + keys.push(key); }); - it("string hash code", () => { - interface Thing { - x: number; - y: string; - } - - const set = createSet(t => t.y, (t, u) => t.x === u.x && t.y === u.y); - - const thing1: Thing = { - x: 1, - y: "a", - }; - - const thing2: Thing = { - x: 2, - y: "b", - }; - const thing3: Thing = { - x: 3, - y: "a", // Collides with thing1 - }; + assert.equal(values.length, 4); - set.add(thing1); - set.add(thing2); - set.add(thing3); + values.sort(); + keys.sort(); - assert.equal(set.size, 3); - - assert.isTrue(set.has(thing1)); - assert.isTrue(set.has(thing2)); - assert.isTrue(set.has(thing3)); - - assert.isFalse(set.has({ - x: 4, - y: "a", // Collides with thing1 - })); - - assert.isFalse(set.has({ - x: 5, - y: "c", // No collision - })); - }); + // NB: first equal value wins (i.e. not [96, 97, 98, 99]) + const expected = [0, 1, 2, 3]; + assert.deepEqual(values, expected); + assert.deepEqual(keys, expected); + }); + it("iteration", () => { + const set = ts.createSet(x => x % 2, (x, y) => (x % 4) === (y % 4)); + for (let i = 0; i < 4; i++) { + set.add(i); + } + + const expected = [0, 1, 2, 3]; + let actual: number[]; + + actual = ts.arrayFrom(set.keys()); + actual.sort(); + assert.deepEqual(actual, expected); + + actual = ts.arrayFrom(set.values()); + actual.sort(); + assert.deepEqual(actual, expected); + + const actualTuple = ts.arrayFrom(set.entries()); + assert.isFalse(actualTuple.some(([v, k]) => v !== k)); + actual = actualTuple.map(([v, _]) => v); + actual.sort(); + assert.deepEqual(actual, expected); + }); + it("string hash code", () => { + interface Thing { + x: number; + y: string; + } + + const set = ts.createSet(t => t.y, (t, u) => t.x === u.x && t.y === u.y); + + const thing1: Thing = { + x: 1, + y: "a", + }; + + const thing2: Thing = { + x: 2, + y: "b", + }; + + const thing3: Thing = { + x: 3, + y: "a", // Collides with thing1 + }; + + set.add(thing1); + set.add(thing2); + set.add(thing3); + + assert.equal(set.size, 3); + + assert.isTrue(set.has(thing1)); + assert.isTrue(set.has(thing2)); + assert.isTrue(set.has(thing3)); + + assert.isFalse(set.has({ + x: 4, + y: "a", // Collides with thing1 + })); + + assert.isFalse(set.has({ + x: 5, + y: "c", // No collision + })); }); }); -} +}); diff --git a/src/testRunner/unittests/config/commandLineParsing.ts b/src/testRunner/unittests/config/commandLineParsing.ts index f0afc9baffc55..fbad23d8e029b 100644 --- a/src/testRunner/unittests/config/commandLineParsing.ts +++ b/src/testRunner/unittests/config/commandLineParsing.ts @@ -1,246 +1,247 @@ -namespace ts { - describe("unittests:: config:: commandLineParsing:: parseCommandLine", () => { - function assertParseResult(subScenario: string, commandLine: string[], workerDiagnostic?: () => ParseCommandLineWorkerDiagnostics) { - it(subScenario, () => { - const baseline: string[] = []; - baseline.push(commandLine.join(" ")); - const parsed = parseCommandLineWorker(workerDiagnostic?.() || compilerOptionsDidYouMeanDiagnostics, commandLine); - baseline.push("CompilerOptions::"); - baseline.push(JSON.stringify(parsed.options, /*replacer*/ undefined, " ")); - baseline.push("WatchOptions::"); - baseline.push(JSON.stringify(parsed.watchOptions, /*replacer*/ undefined, " ")); - baseline.push("FileNames::"); - baseline.push(parsed.fileNames.join()); - baseline.push("Errors::"); - baseline.push(formatDiagnostics(parsed.errors, { - getCurrentDirectory: () => "/", - getCanonicalFileName: identity, - getNewLine: () => "\n", - })); - Harness.Baseline.runBaseline(`config/commandLineParsing/parseCommandLine/${subScenario}.js`, baseline.join("\n")); - }); +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; + +describe("unittests:: config:: commandLineParsing:: parseCommandLine", () => { + function assertParseResult(subScenario: string, commandLine: string[], workerDiagnostic?: () => ts.ParseCommandLineWorkerDiagnostics) { + it(subScenario, () => { + const baseline: string[] = []; + baseline.push(commandLine.join(" ")); + const parsed = ts.parseCommandLineWorker(workerDiagnostic?.() || ts.compilerOptionsDidYouMeanDiagnostics, commandLine); + baseline.push("CompilerOptions::"); + baseline.push(JSON.stringify(parsed.options, /*replacer*/ undefined, " ")); + baseline.push("WatchOptions::"); + baseline.push(JSON.stringify(parsed.watchOptions, /*replacer*/ undefined, " ")); + baseline.push("FileNames::"); + baseline.push(parsed.fileNames.join()); + baseline.push("Errors::"); + baseline.push(ts.formatDiagnostics(parsed.errors, { + getCurrentDirectory: () => "/", + getCanonicalFileName: ts.identity, + getNewLine: () => "\n", + })); + Harness.Baseline.runBaseline(`config/commandLineParsing/parseCommandLine/${subScenario}.js`, baseline.join("\n")); + }); + } + + // --lib es6 0.ts + assertParseResult("Parse single option of library flag", ["--lib", "es6", "0.ts"]); + assertParseResult("Handles may only be used with --build flags", ["--clean", "--dry", "--force", "--verbose"]); + // --declarations --allowTS + assertParseResult("Handles did you mean for misspelt flags", ["--declarations", "--allowTS"]); + // --lib es5,es2015.symbol.wellknown 0.ts + assertParseResult("Parse multiple options of library flags", ["--lib", "es5,es2015.symbol.wellknown", "0.ts"]); + // --lib es5,invalidOption 0.ts + assertParseResult("Parse invalid option of library flags", ["--lib", "es5,invalidOption", "0.ts"]); + // 0.ts --jsx + assertParseResult("Parse empty options of --jsx", ["0.ts", "--jsx"]); + // 0.ts -- + assertParseResult("Parse empty options of --module", ["0.ts", "--module"]); + // 0.ts --newLine + assertParseResult("Parse empty options of --newLine", ["0.ts", "--newLine"]); + // 0.ts --target + assertParseResult("Parse empty options of --target", ["0.ts", "--target"]); + // 0.ts --moduleResolution + assertParseResult("Parse empty options of --moduleResolution", ["0.ts", "--moduleResolution"]); + // 0.ts --lib + assertParseResult("Parse empty options of --lib", ["0.ts", "--lib"]); + // 0.ts --lib + // This test is an error because the empty string is falsey + assertParseResult("Parse empty string of --lib", ["0.ts", "--lib", ""]); + // 0.ts --lib + assertParseResult("Parse immediately following command line argument of --lib", ["0.ts", "--lib", "--sourcemap"]); + // --lib es5, es7 0.ts + assertParseResult("Parse --lib option with extra comma", ["--lib", "es5,", "es7", "0.ts"]); + // --lib es5, es7 0.ts + assertParseResult("Parse --lib option with trailing white-space", ["--lib", "es5, ", "es7", "0.ts"]); + // --lib es5,es2015.symbol.wellknown --target es5 0.ts + assertParseResult("Parse multiple compiler flags with input files at the end", ["--lib", "es5,es2015.symbol.wellknown", "--target", "es5", "0.ts"]); + // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown + assertParseResult("Parse multiple compiler flags with input files in the middle", ["--module", "commonjs", "--target", "es5", "0.ts", "--lib", "es5,es2015.symbol.wellknown"]); + // --module commonjs --target es5 --lib es5 0.ts --library es2015.array,es2015.symbol.wellknown + assertParseResult("Parse multiple library compiler flags ", ["--module", "commonjs", "--target", "es5", "--lib", "es5", "0.ts", "--lib", "es2015.core, es2015.symbol.wellknown "]); + assertParseResult("Parse explicit boolean flag value", ["--strictNullChecks", "false", "0.ts"]); + assertParseResult("Parse non boolean argument after boolean flag", ["--noImplicitAny", "t", "0.ts"]); + assertParseResult("Parse implicit boolean flag value", ["--strictNullChecks"]); + assertParseResult("parse --incremental", ["--incremental", "0.ts"]); + assertParseResult("parse --tsBuildInfoFile", ["--tsBuildInfoFile", "build.tsbuildinfo", "0.ts"]); + + describe("parses command line null for tsconfig only option", () => { + interface VerifyNull { + subScenario: string, + optionName: string; + nonNullValue?: string; + workerDiagnostic?: () => ts.ParseCommandLineWorkerDiagnostics; } - - // --lib es6 0.ts - assertParseResult("Parse single option of library flag", ["--lib", "es6", "0.ts"]); - assertParseResult("Handles may only be used with --build flags", ["--clean", "--dry", "--force", "--verbose"]); - // --declarations --allowTS - assertParseResult("Handles did you mean for misspelt flags", ["--declarations", "--allowTS"]); - // --lib es5,es2015.symbol.wellknown 0.ts - assertParseResult("Parse multiple options of library flags", ["--lib", "es5,es2015.symbol.wellknown", "0.ts"]); - // --lib es5,invalidOption 0.ts - assertParseResult("Parse invalid option of library flags", ["--lib", "es5,invalidOption", "0.ts"]); - // 0.ts --jsx - assertParseResult("Parse empty options of --jsx", ["0.ts", "--jsx"]); - // 0.ts -- - assertParseResult("Parse empty options of --module", ["0.ts", "--module"]); - // 0.ts --newLine - assertParseResult("Parse empty options of --newLine", ["0.ts", "--newLine"]); - // 0.ts --target - assertParseResult("Parse empty options of --target", ["0.ts", "--target"]); - // 0.ts --moduleResolution - assertParseResult("Parse empty options of --moduleResolution", ["0.ts", "--moduleResolution"]); - // 0.ts --lib - assertParseResult("Parse empty options of --lib", ["0.ts", "--lib"]); - // 0.ts --lib - // This test is an error because the empty string is falsey - assertParseResult("Parse empty string of --lib", ["0.ts", "--lib", ""]); - // 0.ts --lib - assertParseResult("Parse immediately following command line argument of --lib", ["0.ts", "--lib", "--sourcemap"]); - // --lib es5, es7 0.ts - assertParseResult("Parse --lib option with extra comma", ["--lib", "es5,", "es7", "0.ts"]); - // --lib es5, es7 0.ts - assertParseResult("Parse --lib option with trailing white-space", ["--lib", "es5, ", "es7", "0.ts"]); - // --lib es5,es2015.symbol.wellknown --target es5 0.ts - assertParseResult("Parse multiple compiler flags with input files at the end", ["--lib", "es5,es2015.symbol.wellknown", "--target", "es5", "0.ts"]); - // --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown - assertParseResult("Parse multiple compiler flags with input files in the middle", ["--module", "commonjs", "--target", "es5", "0.ts", "--lib", "es5,es2015.symbol.wellknown"]); - // --module commonjs --target es5 --lib es5 0.ts --library es2015.array,es2015.symbol.wellknown - assertParseResult("Parse multiple library compiler flags ", ["--module", "commonjs", "--target", "es5", "--lib", "es5", "0.ts", "--lib", "es2015.core, es2015.symbol.wellknown "]); - assertParseResult("Parse explicit boolean flag value", ["--strictNullChecks", "false", "0.ts"]); - assertParseResult("Parse non boolean argument after boolean flag", ["--noImplicitAny", "t", "0.ts"]); - assertParseResult("Parse implicit boolean flag value", ["--strictNullChecks"]); - assertParseResult("parse --incremental", ["--incremental", "0.ts"]); - assertParseResult("parse --tsBuildInfoFile", ["--tsBuildInfoFile", "build.tsbuildinfo", "0.ts"]); - - describe("parses command line null for tsconfig only option", () => { - interface VerifyNull { - subScenario: string, - optionName: string; - nonNullValue?: string; - workerDiagnostic?: () => ParseCommandLineWorkerDiagnostics; - } - function verifyNull({ subScenario, optionName, nonNullValue, workerDiagnostic }: VerifyNull) { - describe(subScenario, () => { - assertParseResult( - `${subScenario} allows setting it to null`, - [`--${optionName}`, "null", "0.ts"], - workerDiagnostic - ); - if (nonNullValue) { - assertParseResult( - `${subScenario} errors if non null value is passed`, - [`--${optionName}`, nonNullValue, "0.ts"], - workerDiagnostic - ); - } - + function verifyNull({ subScenario, optionName, nonNullValue, workerDiagnostic }: VerifyNull) { + describe(subScenario, () => { + assertParseResult( + `${subScenario} allows setting it to null`, + [`--${optionName}`, "null", "0.ts"], + workerDiagnostic + ); + if (nonNullValue) { assertParseResult( - `${subScenario} errors if its followed by another option`, - ["0.ts", "--strictNullChecks", `--${optionName}`], + `${subScenario} errors if non null value is passed`, + [`--${optionName}`, nonNullValue, "0.ts"], workerDiagnostic ); + } - assertParseResult( - `${subScenario} errors if its last option`, - ["0.ts", `--${optionName}`], - workerDiagnostic - ); - }); - } - - interface VerifyNullNonIncludedOption { - subScenario: string, - type: () => "string" | "number" | ESMap; - nonNullValue?: string; - } - function verifyNullNonIncludedOption({ subScenario, type, nonNullValue }: VerifyNullNonIncludedOption) { - verifyNull({ - subScenario, - optionName: "optionName", - nonNullValue, - workerDiagnostic: () => { - const optionDeclarations: CommandLineOption[] = [ - ...compilerOptionsDidYouMeanDiagnostics.optionDeclarations, - { - name: "optionName", - type: type(), - isTSConfigOnly: true, - category: Diagnostics.Backwards_Compatibility, - description: Diagnostics.Enable_project_compilation, - defaultValueDescription: undefined, - } - ]; - return { - ...compilerOptionsDidYouMeanDiagnostics, - optionDeclarations, - getOptionsNameMap: () => createOptionNameMap(optionDeclarations) - }; - } - }); - } - - describe("option of type boolean", () => { assertParseResult( - "allows setting option type boolean to false", - ["--composite", "false", "0.ts"], + `${subScenario} errors if its followed by another option`, + ["0.ts", "--strictNullChecks", `--${optionName}`], + workerDiagnostic ); - verifyNull({ - subScenario: "option of type boolean", - optionName: "composite", - nonNullValue: "true", - }); + assertParseResult( + `${subScenario} errors if its last option`, + ["0.ts", `--${optionName}`], + workerDiagnostic + ); }); + } + interface VerifyNullNonIncludedOption { + subScenario: string, + type: () => "string" | "number" | ts.ESMap; + nonNullValue?: string; + } + function verifyNullNonIncludedOption({ subScenario, type, nonNullValue }: VerifyNullNonIncludedOption) { verifyNull({ - subScenario: "option of type object", - optionName: "paths", + subScenario, + optionName: "optionName", + nonNullValue, + workerDiagnostic: () => { + const optionDeclarations: ts.CommandLineOption[] = [ + ...ts.compilerOptionsDidYouMeanDiagnostics.optionDeclarations, + { + name: "optionName", + type: type(), + isTSConfigOnly: true, + category: ts.Diagnostics.Backwards_Compatibility, + description: ts.Diagnostics.Enable_project_compilation, + defaultValueDescription: undefined, + } + ]; + return { + ...ts.compilerOptionsDidYouMeanDiagnostics, + optionDeclarations, + getOptionsNameMap: () => ts.createOptionNameMap(optionDeclarations) + }; + } }); + } + + describe("option of type boolean", () => { + assertParseResult( + "allows setting option type boolean to false", + ["--composite", "false", "0.ts"], + ); verifyNull({ - subScenario: "option of type list", - optionName: "rootDirs", - nonNullValue: "abc,xyz", - }); - verifyNullNonIncludedOption({ - subScenario: "option of type string", - type: () => "string", - nonNullValue: "hello" + subScenario: "option of type boolean", + optionName: "composite", + nonNullValue: "true", }); + }); - verifyNullNonIncludedOption({ - subScenario: "option of type number", - type: () => "number", - nonNullValue: "10" - }); + verifyNull({ + subScenario: "option of type object", + optionName: "paths", + }); - verifyNullNonIncludedOption({ - subScenario: "option of type custom map", - type: () => new Map(getEntries({ - node: ModuleResolutionKind.NodeJs, - classic: ModuleResolutionKind.Classic, - })), - nonNullValue: "node" - }); + verifyNull({ + subScenario: "option of type list", + optionName: "rootDirs", + nonNullValue: "abc,xyz", + }); + verifyNullNonIncludedOption({ + subScenario: "option of type string", + type: () => "string", + nonNullValue: "hello" }); - assertParseResult("allows tsconfig only option to be set to null", ["--composite", "null", "-tsBuildInfoFile", "null", "0.ts"]); - - describe("Watch options", () => { - assertParseResult("parse --watchFile", ["--watchFile", "UseFsEvents", "0.ts"]); - assertParseResult("parse --watchDirectory", ["--watchDirectory", "FixedPollingInterval", "0.ts"]); - assertParseResult("parse --fallbackPolling", ["--fallbackPolling", "PriorityInterval", "0.ts"]); - assertParseResult("parse --synchronousWatchDirectory", ["--synchronousWatchDirectory", "0.ts"]); - assertParseResult("errors on missing argument to --fallbackPolling", ["0.ts", "--fallbackPolling"]); - assertParseResult("parse --excludeDirectories", ["--excludeDirectories", "**/temp", "0.ts"]); - assertParseResult("errors on invalid excludeDirectories", ["--excludeDirectories", "**/../*", "0.ts"]); - assertParseResult("parse --excludeFiles", ["--excludeFiles", "**/temp/*.ts", "0.ts"]); - assertParseResult("errors on invalid excludeFiles", ["--excludeFiles", "**/../*", "0.ts"]); + verifyNullNonIncludedOption({ + subScenario: "option of type number", + type: () => "number", + nonNullValue: "10" }); - }); - describe("unittests:: config:: commandLineParsing:: parseBuildOptions", () => { - function assertParseResult(subScenario: string, commandLine: string[]) { - it(subScenario, () => { - const baseline: string[] = []; - baseline.push(commandLine.join(" ")); - const parsed = parseBuildCommand(commandLine); - baseline.push("buildOptions::"); - baseline.push(JSON.stringify(parsed.buildOptions, /*replacer*/ undefined, " ")); - baseline.push("WatchOptions::"); - baseline.push(JSON.stringify(parsed.watchOptions, /*replacer*/ undefined, " ")); - baseline.push("Projects::"); - baseline.push(parsed.projects.join()); - baseline.push("Errors::"); - baseline.push(formatDiagnostics(parsed.errors, { - getCurrentDirectory: () => "/", - getCanonicalFileName: identity, - getNewLine: () => "\n", - })); - Harness.Baseline.runBaseline(`config/commandLineParsing/parseBuildOptions/${subScenario}.js`, baseline.join("\n")); - }); - } - assertParseResult("parse build without any options ", []); - assertParseResult("Parse multiple options", ["--verbose", "--force", "tests"]); - assertParseResult("Parse option with invalid option", ["--verbose", "--invalidOption"]); - assertParseResult("Parse multiple flags with input projects at the end", ["--force", "--verbose", "src", "tests"]); - assertParseResult("Parse multiple flags with input projects in the middle", ["--force", "src", "tests", "--verbose"]); - assertParseResult("Parse multiple flags with input projects in the beginning", ["src", "tests", "--force", "--verbose"]); - assertParseResult("parse build with --incremental", ["--incremental", "tests"]); - assertParseResult("parse build with --locale en-us", ["--locale", "en-us", "src"]); - assertParseResult("parse build with --tsBuildInfoFile", ["--tsBuildInfoFile", "build.tsbuildinfo", "tests"]); - assertParseResult("reports other common may not be used with --build flags", ["--strict"]); - - describe("Combining options that make no sense together", () => { - function verifyInvalidCombination(flag1: keyof BuildOptions, flag2: keyof BuildOptions) { - assertParseResult(`--${flag1} and --${flag2} together is invalid`, [`--${flag1}`, `--${flag2}`]); - } - verifyInvalidCombination("clean", "force"); - verifyInvalidCombination("clean", "verbose"); - verifyInvalidCombination("clean", "watch"); - verifyInvalidCombination("watch", "dry"); + verifyNullNonIncludedOption({ + subScenario: "option of type custom map", + type: () => new ts.Map(ts.getEntries({ + node: ts.ModuleResolutionKind.NodeJs, + classic: ts.ModuleResolutionKind.Classic, + })), + nonNullValue: "node" }); + }); - describe("Watch options", () => { - assertParseResult("parse --watchFile", ["--watchFile", "UseFsEvents", "--verbose"]); - assertParseResult("parse --watchDirectory", ["--watchDirectory", "FixedPollingInterval", "--verbose"]); - assertParseResult("parse --fallbackPolling", ["--fallbackPolling", "PriorityInterval", "--verbose"]); - assertParseResult("parse --synchronousWatchDirectory", ["--synchronousWatchDirectory", "--verbose"]); - assertParseResult("errors on missing argument", ["--verbose", "--fallbackPolling"]); - assertParseResult("errors on invalid excludeDirectories", ["--excludeDirectories", "**/../*"]); - assertParseResult("parse --excludeFiles", ["--excludeFiles", "**/temp/*.ts"]); - assertParseResult("errors on invalid excludeFiles", ["--excludeFiles", "**/../*"]); + assertParseResult("allows tsconfig only option to be set to null", ["--composite", "null", "-tsBuildInfoFile", "null", "0.ts"]); + + describe("Watch options", () => { + assertParseResult("parse --watchFile", ["--watchFile", "UseFsEvents", "0.ts"]); + assertParseResult("parse --watchDirectory", ["--watchDirectory", "FixedPollingInterval", "0.ts"]); + assertParseResult("parse --fallbackPolling", ["--fallbackPolling", "PriorityInterval", "0.ts"]); + assertParseResult("parse --synchronousWatchDirectory", ["--synchronousWatchDirectory", "0.ts"]); + assertParseResult("errors on missing argument to --fallbackPolling", ["0.ts", "--fallbackPolling"]); + assertParseResult("parse --excludeDirectories", ["--excludeDirectories", "**/temp", "0.ts"]); + assertParseResult("errors on invalid excludeDirectories", ["--excludeDirectories", "**/../*", "0.ts"]); + assertParseResult("parse --excludeFiles", ["--excludeFiles", "**/temp/*.ts", "0.ts"]); + assertParseResult("errors on invalid excludeFiles", ["--excludeFiles", "**/../*", "0.ts"]); + }); +}); + +describe("unittests:: config:: commandLineParsing:: parseBuildOptions", () => { + function assertParseResult(subScenario: string, commandLine: string[]) { + it(subScenario, () => { + const baseline: string[] = []; + baseline.push(commandLine.join(" ")); + const parsed = ts.parseBuildCommand(commandLine); + baseline.push("buildOptions::"); + baseline.push(JSON.stringify(parsed.buildOptions, /*replacer*/ undefined, " ")); + baseline.push("WatchOptions::"); + baseline.push(JSON.stringify(parsed.watchOptions, /*replacer*/ undefined, " ")); + baseline.push("Projects::"); + baseline.push(parsed.projects.join()); + baseline.push("Errors::"); + baseline.push(ts.formatDiagnostics(parsed.errors, { + getCurrentDirectory: () => "/", + getCanonicalFileName: ts.identity, + getNewLine: () => "\n", + })); + Harness.Baseline.runBaseline(`config/commandLineParsing/parseBuildOptions/${subScenario}.js`, baseline.join("\n")); }); + } + assertParseResult("parse build without any options ", []); + assertParseResult("Parse multiple options", ["--verbose", "--force", "tests"]); + assertParseResult("Parse option with invalid option", ["--verbose", "--invalidOption"]); + assertParseResult("Parse multiple flags with input projects at the end", ["--force", "--verbose", "src", "tests"]); + assertParseResult("Parse multiple flags with input projects in the middle", ["--force", "src", "tests", "--verbose"]); + assertParseResult("Parse multiple flags with input projects in the beginning", ["src", "tests", "--force", "--verbose"]); + assertParseResult("parse build with --incremental", ["--incremental", "tests"]); + assertParseResult("parse build with --locale en-us", ["--locale", "en-us", "src"]); + assertParseResult("parse build with --tsBuildInfoFile", ["--tsBuildInfoFile", "build.tsbuildinfo", "tests"]); + assertParseResult("reports other common may not be used with --build flags", ["--strict"]); + + describe("Combining options that make no sense together", () => { + function verifyInvalidCombination(flag1: keyof ts.BuildOptions, flag2: keyof ts.BuildOptions) { + assertParseResult(`--${flag1} and --${flag2} together is invalid`, [`--${flag1}`, `--${flag2}`]); + } + verifyInvalidCombination("clean", "force"); + verifyInvalidCombination("clean", "verbose"); + verifyInvalidCombination("clean", "watch"); + verifyInvalidCombination("watch", "dry"); + }); + + describe("Watch options", () => { + assertParseResult("parse --watchFile", ["--watchFile", "UseFsEvents", "--verbose"]); + assertParseResult("parse --watchDirectory", ["--watchDirectory", "FixedPollingInterval", "--verbose"]); + assertParseResult("parse --fallbackPolling", ["--fallbackPolling", "PriorityInterval", "--verbose"]); + assertParseResult("parse --synchronousWatchDirectory", ["--synchronousWatchDirectory", "--verbose"]); + assertParseResult("errors on missing argument", ["--verbose", "--fallbackPolling"]); + assertParseResult("errors on invalid excludeDirectories", ["--excludeDirectories", "**/../*"]); + assertParseResult("parse --excludeFiles", ["--excludeFiles", "**/temp/*.ts"]); + assertParseResult("errors on invalid excludeFiles", ["--excludeFiles", "**/../*"]); }); -} +}); diff --git a/src/testRunner/unittests/config/configurationExtension.ts b/src/testRunner/unittests/config/configurationExtension.ts index ad906411ffbca..8434eb350cf60 100644 --- a/src/testRunner/unittests/config/configurationExtension.ts +++ b/src/testRunner/unittests/config/configurationExtension.ts @@ -1,354 +1,356 @@ -namespace ts { - function createFileSystem(ignoreCase: boolean, cwd: string, root: string) { - return new vfs.FileSystem(ignoreCase, { - cwd, - files: { - [root]: { - "dev/node_modules/config-box/package.json": JSON.stringify({ - name: "config-box", - version: "1.0.0", - tsconfig: "./strict.json" - }), - "dev/node_modules/config-box/strict.json": JSON.stringify({ - compilerOptions: { - strict: true, - } - }), - "dev/node_modules/config-box/unstrict.json": JSON.stringify({ - compilerOptions: { - strict: false, - } - }), - "dev/tsconfig.extendsBox.json": JSON.stringify({ - extends: "config-box", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsStrict.json": JSON.stringify({ - extends: "config-box/strict", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsUnStrict.json": JSON.stringify({ - extends: "config-box/unstrict", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsStrictExtension.json": JSON.stringify({ - extends: "config-box/strict.json", - files: [ - "main.ts", - ] - }), - "dev/node_modules/config-box-implied/package.json": JSON.stringify({ - name: "config-box-implied", - version: "1.0.0", - }), - "dev/node_modules/config-box-implied/tsconfig.json": JSON.stringify({ - compilerOptions: { - strict: true, - } - }), - "dev/node_modules/config-box-implied/unstrict/tsconfig.json": JSON.stringify({ - compilerOptions: { - strict: false, - } - }), - "dev/tsconfig.extendsBoxImplied.json": JSON.stringify({ - extends: "config-box-implied", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsBoxImpliedUnstrict.json": JSON.stringify({ - extends: "config-box-implied/unstrict", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsBoxImpliedUnstrictExtension.json": JSON.stringify({ - extends: "config-box-implied/unstrict/tsconfig", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.extendsBoxImpliedPath.json": JSON.stringify({ - extends: "config-box-implied/tsconfig.json", - files: [ - "main.ts", - ] - }), - "dev/tsconfig.json": JSON.stringify({ - extends: "./configs/base", - files: [ - "main.ts", - "supplemental.ts" - ] - }), - "dev/tsconfig.nostrictnull.json": JSON.stringify({ - extends: "./tsconfig", - compilerOptions: { - strictNullChecks: false - } - }), - "dev/configs/base.json": JSON.stringify({ - compilerOptions: { - allowJs: true, - noImplicitAny: true, - strictNullChecks: true - } - }), - "dev/configs/tests.json": JSON.stringify({ - compilerOptions: { - preserveConstEnums: true, - removeComments: false, - sourceMap: true - }, - exclude: [ - "../tests/baselines", - "../tests/scenarios" - ], - include: [ - "../tests/**/*.ts" - ] - }), - "dev/circular.json": JSON.stringify({ - extends: "./circular2", - compilerOptions: { - module: "amd" - } - }), - "dev/circular2.json": JSON.stringify({ - extends: "./circular", - compilerOptions: { - module: "commonjs" - } - }), - "dev/missing.json": JSON.stringify({ - extends: "./missing2", - compilerOptions: { - types: [] - } - }), - "dev/failure.json": JSON.stringify({ - extends: "./failure2.json", - compilerOptions: { - typeRoots: [] - } - }), - "dev/failure2.json": JSON.stringify({ - excludes: ["*.js"] - }), - "dev/configs/first.json": JSON.stringify({ - extends: "./base", - compilerOptions: { - module: "commonjs" - }, - files: ["../main.ts"] - }), - "dev/configs/second.json": JSON.stringify({ - extends: "./base", - compilerOptions: { - module: "amd" - }, - include: ["../supplemental.*"] - }), - "dev/configs/third.json": JSON.stringify({ - extends: "./second", - compilerOptions: { - module: null // eslint-disable-line no-null/no-null - }, - include: ["../supplemental.*"] - }), - "dev/configs/fourth.json": JSON.stringify({ - extends: "./third", - compilerOptions: { - module: "system" - }, - include: null, // eslint-disable-line no-null/no-null - files: ["../main.ts"] - }), - "dev/configs/fifth.json": JSON.stringify({ - extends: "./fourth", - include: ["../tests/utils.ts"], - files: [] - }), - "dev/extends.json": JSON.stringify({ extends: 42 }), - "dev/extends2.json": JSON.stringify({ extends: "configs/base" }), - "dev/main.ts": "", - "dev/supplemental.ts": "", - "dev/tests/unit/spec.ts": "", - "dev/tests/utils.ts": "", - "dev/tests/scenarios/first.json": "", - "dev/tests/baselines/first/output.ts": "" - } +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; +import * as fakes from "../../_namespaces/fakes"; + +function createFileSystem(ignoreCase: boolean, cwd: string, root: string) { + return new vfs.FileSystem(ignoreCase, { + cwd, + files: { + [root]: { + "dev/node_modules/config-box/package.json": JSON.stringify({ + name: "config-box", + version: "1.0.0", + tsconfig: "./strict.json" + }), + "dev/node_modules/config-box/strict.json": JSON.stringify({ + compilerOptions: { + strict: true, + } + }), + "dev/node_modules/config-box/unstrict.json": JSON.stringify({ + compilerOptions: { + strict: false, + } + }), + "dev/tsconfig.extendsBox.json": JSON.stringify({ + extends: "config-box", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsStrict.json": JSON.stringify({ + extends: "config-box/strict", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsUnStrict.json": JSON.stringify({ + extends: "config-box/unstrict", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsStrictExtension.json": JSON.stringify({ + extends: "config-box/strict.json", + files: [ + "main.ts", + ] + }), + "dev/node_modules/config-box-implied/package.json": JSON.stringify({ + name: "config-box-implied", + version: "1.0.0", + }), + "dev/node_modules/config-box-implied/tsconfig.json": JSON.stringify({ + compilerOptions: { + strict: true, + } + }), + "dev/node_modules/config-box-implied/unstrict/tsconfig.json": JSON.stringify({ + compilerOptions: { + strict: false, + } + }), + "dev/tsconfig.extendsBoxImplied.json": JSON.stringify({ + extends: "config-box-implied", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsBoxImpliedUnstrict.json": JSON.stringify({ + extends: "config-box-implied/unstrict", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsBoxImpliedUnstrictExtension.json": JSON.stringify({ + extends: "config-box-implied/unstrict/tsconfig", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.extendsBoxImpliedPath.json": JSON.stringify({ + extends: "config-box-implied/tsconfig.json", + files: [ + "main.ts", + ] + }), + "dev/tsconfig.json": JSON.stringify({ + extends: "./configs/base", + files: [ + "main.ts", + "supplemental.ts" + ] + }), + "dev/tsconfig.nostrictnull.json": JSON.stringify({ + extends: "./tsconfig", + compilerOptions: { + strictNullChecks: false + } + }), + "dev/configs/base.json": JSON.stringify({ + compilerOptions: { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true + } + }), + "dev/configs/tests.json": JSON.stringify({ + compilerOptions: { + preserveConstEnums: true, + removeComments: false, + sourceMap: true + }, + exclude: [ + "../tests/baselines", + "../tests/scenarios" + ], + include: [ + "../tests/**/*.ts" + ] + }), + "dev/circular.json": JSON.stringify({ + extends: "./circular2", + compilerOptions: { + module: "amd" + } + }), + "dev/circular2.json": JSON.stringify({ + extends: "./circular", + compilerOptions: { + module: "commonjs" + } + }), + "dev/missing.json": JSON.stringify({ + extends: "./missing2", + compilerOptions: { + types: [] + } + }), + "dev/failure.json": JSON.stringify({ + extends: "./failure2.json", + compilerOptions: { + typeRoots: [] + } + }), + "dev/failure2.json": JSON.stringify({ + excludes: ["*.js"] + }), + "dev/configs/first.json": JSON.stringify({ + extends: "./base", + compilerOptions: { + module: "commonjs" + }, + files: ["../main.ts"] + }), + "dev/configs/second.json": JSON.stringify({ + extends: "./base", + compilerOptions: { + module: "amd" + }, + include: ["../supplemental.*"] + }), + "dev/configs/third.json": JSON.stringify({ + extends: "./second", + compilerOptions: { + module: null // eslint-disable-line no-null/no-null + }, + include: ["../supplemental.*"] + }), + "dev/configs/fourth.json": JSON.stringify({ + extends: "./third", + compilerOptions: { + module: "system" + }, + include: null, // eslint-disable-line no-null/no-null + files: ["../main.ts"] + }), + "dev/configs/fifth.json": JSON.stringify({ + extends: "./fourth", + include: ["../tests/utils.ts"], + files: [] + }), + "dev/extends.json": JSON.stringify({ extends: 42 }), + "dev/extends2.json": JSON.stringify({ extends: "configs/base" }), + "dev/main.ts": "", + "dev/supplemental.ts": "", + "dev/tests/unit/spec.ts": "", + "dev/tests/utils.ts": "", + "dev/tests/scenarios/first.json": "", + "dev/tests/baselines/first/output.ts": "" } - }); - } + } + }); +} - const caseInsensitiveBasePath = "c:/dev/"; - const caseInsensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ true, caseInsensitiveBasePath, "c:/")); +const caseInsensitiveBasePath = "c:/dev/"; +const caseInsensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ true, caseInsensitiveBasePath, "c:/")); - const caseSensitiveBasePath = "/dev/"; - const caseSensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ false, caseSensitiveBasePath, "/")); +const caseSensitiveBasePath = "/dev/"; +const caseSensitiveHost = new fakes.ParseConfigHost(createFileSystem(/*ignoreCase*/ false, caseSensitiveBasePath, "/")); - function verifyDiagnostics(actual: Diagnostic[], expected: { code: number; messageText: string; }[]) { - assert.isTrue(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`); - for (let i = 0; i < actual.length; i++) { - const actualError = actual[i]; - const expectedError = expected[i]; - assert.equal(actualError.code, expectedError.code, "Error code mismatch"); - assert.equal(actualError.category, DiagnosticCategory.Error, "Category mismatch"); // Should always be error - assert.equal(flattenDiagnosticMessageText(actualError.messageText, "\n"), expectedError.messageText); - } +function verifyDiagnostics(actual: ts.Diagnostic[], expected: { code: number; messageText: string; }[]) { + assert.isTrue(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`); + for (let i = 0; i < actual.length; i++) { + const actualError = actual[i]; + const expectedError = expected[i]; + assert.equal(actualError.code, expectedError.code, "Error code mismatch"); + assert.equal(actualError.category, ts.DiagnosticCategory.Error, "Category mismatch"); // Should always be error + assert.equal(ts.flattenDiagnosticMessageText(actualError.messageText, "\n"), expectedError.messageText); } +} - describe("unittests:: config:: configurationExtension", () => { - forEach<[string, string, fakes.ParseConfigHost], void>([ - ["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost], - ["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost] - ], ([testName, basePath, host]) => { - function getParseCommandLine(entry: string) { - const {config, error} = readConfigFile(entry, name => host.readFile(name)); - assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); - return parseJsonConfigFileContent(config, host, basePath, {}, entry); - } +describe("unittests:: config:: configurationExtension", () => { + ts.forEach<[string, string, fakes.ParseConfigHost], void>([ + ["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost], + ["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost] + ], ([testName, basePath, host]) => { + function getParseCommandLine(entry: string) { + const {config, error} = ts.readConfigFile(entry, name => host.readFile(name)); + assert(config && !error, ts.flattenDiagnosticMessageText(error && error.messageText, "\n")); + return ts.parseJsonConfigFileContent(config, host, basePath, {}, entry); + } - function getParseCommandLineJsonSourceFile(entry: string) { - const jsonSourceFile = readJsonConfigFile(entry, name => host.readFile(name)); - assert(jsonSourceFile.endOfFileToken && !jsonSourceFile.parseDiagnostics.length, flattenDiagnosticMessageText(jsonSourceFile.parseDiagnostics[0] && jsonSourceFile.parseDiagnostics[0].messageText, "\n")); - return { - jsonSourceFile, - parsed: parseJsonSourceFileConfigFileContent(jsonSourceFile, host, basePath, {}, entry) - }; - } + function getParseCommandLineJsonSourceFile(entry: string) { + const jsonSourceFile = ts.readJsonConfigFile(entry, name => host.readFile(name)); + assert(jsonSourceFile.endOfFileToken && !jsonSourceFile.parseDiagnostics.length, ts.flattenDiagnosticMessageText(jsonSourceFile.parseDiagnostics[0] && jsonSourceFile.parseDiagnostics[0].messageText, "\n")); + return { + jsonSourceFile, + parsed: ts.parseJsonSourceFileConfigFileContent(jsonSourceFile, host, basePath, {}, entry) + }; + } - function testSuccess(name: string, entry: string, expected: CompilerOptions, expectedFiles: string[]) { - expected.configFilePath = entry; - it(name, () => { - const parsed = getParseCommandLine(entry); - assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); - assert.deepEqual(parsed.options, expected); - assert.deepEqual(parsed.fileNames, expectedFiles); - }); + function testSuccess(name: string, entry: string, expected: ts.CompilerOptions, expectedFiles: string[]) { + expected.configFilePath = entry; + it(name, () => { + const parsed = getParseCommandLine(entry); + assert(!parsed.errors.length, ts.flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); + assert.deepEqual(parsed.options, expected); + assert.deepEqual(parsed.fileNames, expectedFiles); + }); - it(name + " with jsonSourceFile", () => { - const { parsed, jsonSourceFile } = getParseCommandLineJsonSourceFile(entry); - assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); - assert.deepEqual(parsed.options, expected); - assert.equal(parsed.options.configFile, jsonSourceFile); - assert.deepEqual(parsed.fileNames, expectedFiles); - }); - } + it(name + " with jsonSourceFile", () => { + const { parsed, jsonSourceFile } = getParseCommandLineJsonSourceFile(entry); + assert(!parsed.errors.length, ts.flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); + assert.deepEqual(parsed.options, expected); + assert.equal(parsed.options.configFile, jsonSourceFile); + assert.deepEqual(parsed.fileNames, expectedFiles); + }); + } - function testFailure(name: string, entry: string, expectedDiagnostics: { code: number; messageText: string; }[]) { - it(name, () => { - const parsed = getParseCommandLine(entry); - verifyDiagnostics(parsed.errors, expectedDiagnostics); - }); + function testFailure(name: string, entry: string, expectedDiagnostics: { code: number; messageText: string; }[]) { + it(name, () => { + const parsed = getParseCommandLine(entry); + verifyDiagnostics(parsed.errors, expectedDiagnostics); + }); - it(name + " with jsonSourceFile", () => { - const { parsed } = getParseCommandLineJsonSourceFile(entry); - verifyDiagnostics(parsed.errors, expectedDiagnostics); - }); - } + it(name + " with jsonSourceFile", () => { + const { parsed } = getParseCommandLineJsonSourceFile(entry); + verifyDiagnostics(parsed.errors, expectedDiagnostics); + }); + } - describe(testName, () => { - testSuccess("can resolve an extension with a base extension", "tsconfig.json", { - allowJs: true, - noImplicitAny: true, - strictNullChecks: true, - }, [ - combinePaths(basePath, "main.ts"), - combinePaths(basePath, "supplemental.ts"), - ]); + describe(testName, () => { + testSuccess("can resolve an extension with a base extension", "tsconfig.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true, + }, [ + ts.combinePaths(basePath, "main.ts"), + ts.combinePaths(basePath, "supplemental.ts"), + ]); - testSuccess("can resolve an extension with a base extension that overrides options", "tsconfig.nostrictnull.json", { - allowJs: true, - noImplicitAny: true, - strictNullChecks: false, - }, [ - combinePaths(basePath, "main.ts"), - combinePaths(basePath, "supplemental.ts"), - ]); + testSuccess("can resolve an extension with a base extension that overrides options", "tsconfig.nostrictnull.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: false, + }, [ + ts.combinePaths(basePath, "main.ts"), + ts.combinePaths(basePath, "supplemental.ts"), + ]); - testFailure("can report errors on circular imports", "circular.json", [ - { - code: 18000, - messageText: `Circularity detected while resolving configuration: ${[combinePaths(basePath, "circular.json"), combinePaths(basePath, "circular2.json"), combinePaths(basePath, "circular.json")].join(" -> ")}` - } - ]); + testFailure("can report errors on circular imports", "circular.json", [ + { + code: 18000, + messageText: `Circularity detected while resolving configuration: ${[ts.combinePaths(basePath, "circular.json"), ts.combinePaths(basePath, "circular2.json"), ts.combinePaths(basePath, "circular.json")].join(" -> ")}` + } + ]); - testFailure("can report missing configurations", "missing.json", [{ - code: 6053, - messageText: `File './missing2' not found.` - }]); + testFailure("can report missing configurations", "missing.json", [{ + code: 6053, + messageText: `File './missing2' not found.` + }]); - testFailure("can report errors in extended configs", "failure.json", [{ - code: 6114, - messageText: `Unknown option 'excludes'. Did you mean 'exclude'?` - }]); + testFailure("can report errors in extended configs", "failure.json", [{ + code: 6114, + messageText: `Unknown option 'excludes'. Did you mean 'exclude'?` + }]); - testFailure("can error when 'extends' is not a string", "extends.json", [{ - code: 5024, - messageText: `Compiler option 'extends' requires a value of type string.` - }]); + testFailure("can error when 'extends' is not a string", "extends.json", [{ + code: 5024, + messageText: `Compiler option 'extends' requires a value of type string.` + }]); - testSuccess("can overwrite compiler options using extended 'null'", "configs/third.json", { - allowJs: true, - noImplicitAny: true, - strictNullChecks: true, - module: undefined // Technically, this is distinct from the key never being set; but within the compiler we don't make the distinction - }, [ - combinePaths(basePath, "supplemental.ts") - ]); + testSuccess("can overwrite compiler options using extended 'null'", "configs/third.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true, + module: undefined // Technically, this is distinct from the key never being set; but within the compiler we don't make the distinction + }, [ + ts.combinePaths(basePath, "supplemental.ts") + ]); - testSuccess("can overwrite top-level options using extended 'null'", "configs/fourth.json", { - allowJs: true, - noImplicitAny: true, - strictNullChecks: true, - module: ModuleKind.System - }, [ - combinePaths(basePath, "main.ts") - ]); + testSuccess("can overwrite top-level options using extended 'null'", "configs/fourth.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true, + module: ts.ModuleKind.System + }, [ + ts.combinePaths(basePath, "main.ts") + ]); - testSuccess("can overwrite top-level files using extended []", "configs/fifth.json", { - allowJs: true, - noImplicitAny: true, - strictNullChecks: true, - module: ModuleKind.System - }, [ - combinePaths(basePath, "tests/utils.ts") - ]); + testSuccess("can overwrite top-level files using extended []", "configs/fifth.json", { + allowJs: true, + noImplicitAny: true, + strictNullChecks: true, + module: ts.ModuleKind.System + }, [ + ts.combinePaths(basePath, "tests/utils.ts") + ]); - describe("finding extended configs from node_modules", () => { - testSuccess("can lookup via tsconfig field", "tsconfig.extendsBox.json", { strict: true }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via package-relative path", "tsconfig.extendsStrict.json", { strict: true }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via non-redirected-to package-relative path", "tsconfig.extendsUnStrict.json", { strict: false }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via package-relative path with extension", "tsconfig.extendsStrictExtension.json", { strict: true }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via an implicit tsconfig", "tsconfig.extendsBoxImplied.json", { strict: true }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via an implicit tsconfig in a package-relative directory", "tsconfig.extendsBoxImpliedUnstrict.json", { strict: false }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via an implicit tsconfig in a package-relative directory with name", "tsconfig.extendsBoxImpliedUnstrictExtension.json", { strict: false }, [combinePaths(basePath, "main.ts")]); - testSuccess("can lookup via an implicit tsconfig in a package-relative directory with extension", "tsconfig.extendsBoxImpliedPath.json", { strict: true }, [combinePaths(basePath, "main.ts")]); - }); + describe("finding extended configs from node_modules", () => { + testSuccess("can lookup via tsconfig field", "tsconfig.extendsBox.json", { strict: true }, [ts.combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via package-relative path", "tsconfig.extendsStrict.json", { strict: true }, [ts.combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via non-redirected-to package-relative path", "tsconfig.extendsUnStrict.json", { strict: false }, [ts.combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via package-relative path with extension", "tsconfig.extendsStrictExtension.json", { strict: true }, [ts.combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via an implicit tsconfig", "tsconfig.extendsBoxImplied.json", { strict: true }, [ts.combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via an implicit tsconfig in a package-relative directory", "tsconfig.extendsBoxImpliedUnstrict.json", { strict: false }, [ts.combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via an implicit tsconfig in a package-relative directory with name", "tsconfig.extendsBoxImpliedUnstrictExtension.json", { strict: false }, [ts.combinePaths(basePath, "main.ts")]); + testSuccess("can lookup via an implicit tsconfig in a package-relative directory with extension", "tsconfig.extendsBoxImpliedPath.json", { strict: true }, [ts.combinePaths(basePath, "main.ts")]); + }); - it("adds extendedSourceFiles only once", () => { - const sourceFile = readJsonConfigFile("configs/fourth.json", (path) => host.readFile(path)); - const dir = combinePaths(basePath, "configs"); - const expected = [ - combinePaths(dir, "third.json"), - combinePaths(dir, "second.json"), - combinePaths(dir, "base.json"), - ]; - parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); - assert.deepEqual(sourceFile.extendedSourceFiles, expected); - parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); - assert.deepEqual(sourceFile.extendedSourceFiles, expected); - }); + it("adds extendedSourceFiles only once", () => { + const sourceFile = ts.readJsonConfigFile("configs/fourth.json", (path) => host.readFile(path)); + const dir = ts.combinePaths(basePath, "configs"); + const expected = [ + ts.combinePaths(dir, "third.json"), + ts.combinePaths(dir, "second.json"), + ts.combinePaths(dir, "base.json"), + ]; + ts.parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); + assert.deepEqual(sourceFile.extendedSourceFiles, expected); + ts.parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); + assert.deepEqual(sourceFile.extendedSourceFiles, expected); }); }); }); -} +}); diff --git a/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts index 97ae30d24083e..0310c01f74b73 100644 --- a/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts +++ b/src/testRunner/unittests/config/convertCompilerOptionsFromJson.ts @@ -1,643 +1,646 @@ -namespace ts { - describe("unittests:: config:: convertCompilerOptionsFromJson", () => { - const formatDiagnosticHost: FormatDiagnosticsHost = { - getCurrentDirectory: () => "/apath/", - getCanonicalFileName: createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ true), - getNewLine: () => "\n" - }; - - interface ExpectedResultWithParsingSuccess { - compilerOptions: CompilerOptions; - errors: readonly Diagnostic[]; +import * as ts from "../../_namespaces/ts"; +import * as fakes from "../../_namespaces/fakes"; +import * as vfs from "../../_namespaces/vfs"; + +describe("unittests:: config:: convertCompilerOptionsFromJson", () => { + const formatDiagnosticHost: ts.FormatDiagnosticsHost = { + getCurrentDirectory: () => "/apath/", + getCanonicalFileName: ts.createGetCanonicalFileName(/*useCaseSensitiveFileNames*/ true), + getNewLine: () => "\n" + }; + + interface ExpectedResultWithParsingSuccess { + compilerOptions: ts.CompilerOptions; + errors: readonly ts.Diagnostic[]; + } + + interface ExpectedResultWithParsingFailure { + compilerOptions: ts.CompilerOptions; + hasParseErrors: true; + } + + type ExpectedResult = ExpectedResultWithParsingSuccess | ExpectedResultWithParsingFailure; + + function isExpectedResultWithParsingFailure(expectedResult: ExpectedResult): expectedResult is ExpectedResultWithParsingFailure { + return !!(expectedResult as ExpectedResultWithParsingFailure).hasParseErrors; + } + + function assertCompilerOptions(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { + assertCompilerOptionsWithJson(json, configFileName, expectedResult); + assertCompilerOptionsWithJsonNode(json, configFileName, expectedResult); + } + + function assertCompilerOptionsWithJson(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { + const { options: actualCompilerOptions, errors: actualErrors } = ts.convertCompilerOptionsFromJson(json.compilerOptions, "/apath/", configFileName); + + const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); + const expectedCompilerOptions = JSON.stringify({ ...expectedResult.compilerOptions, configFilePath: configFileName }); + assert.equal(parsedCompilerOptions, expectedCompilerOptions); + + verifyErrors(actualErrors, expectedResult.errors, /*ignoreLocation*/ true); + } + + function assertCompilerOptionsWithJsonNode(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { + assertCompilerOptionsWithJsonText(JSON.stringify(json), configFileName, expectedResult); + } + + function assertCompilerOptionsWithJsonText(fileText: string, configFileName: string, expectedResult: ExpectedResult) { + const result = ts.parseJsonText(configFileName, fileText); + assert(!!result.endOfFileToken); + assert.equal(!!result.parseDiagnostics.length, isExpectedResultWithParsingFailure(expectedResult)); + const host: ts.ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); + const { options: actualCompilerOptions, errors: actualParseErrors } = ts.parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); + expectedResult.compilerOptions.configFilePath = configFileName; + + const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); + const expectedCompilerOptions = JSON.stringify(expectedResult.compilerOptions); + assert.equal(parsedCompilerOptions, expectedCompilerOptions); + assert.equal(actualCompilerOptions.configFile, result); + + if (!isExpectedResultWithParsingFailure(expectedResult)) { + verifyErrors(actualParseErrors.filter(error => error.code !== ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code), expectedResult.errors); } - - interface ExpectedResultWithParsingFailure { - compilerOptions: CompilerOptions; - hasParseErrors: true; - } - - type ExpectedResult = ExpectedResultWithParsingSuccess | ExpectedResultWithParsingFailure; - - function isExpectedResultWithParsingFailure(expectedResult: ExpectedResult): expectedResult is ExpectedResultWithParsingFailure { - return !!(expectedResult as ExpectedResultWithParsingFailure).hasParseErrors; - } - - function assertCompilerOptions(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { - assertCompilerOptionsWithJson(json, configFileName, expectedResult); - assertCompilerOptionsWithJsonNode(json, configFileName, expectedResult); - } - - function assertCompilerOptionsWithJson(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { - const { options: actualCompilerOptions, errors: actualErrors } = convertCompilerOptionsFromJson(json.compilerOptions, "/apath/", configFileName); - - const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); - const expectedCompilerOptions = JSON.stringify({ ...expectedResult.compilerOptions, configFilePath: configFileName }); - assert.equal(parsedCompilerOptions, expectedCompilerOptions); - - verifyErrors(actualErrors, expectedResult.errors, /*ignoreLocation*/ true); - } - - function assertCompilerOptionsWithJsonNode(json: any, configFileName: string, expectedResult: ExpectedResultWithParsingSuccess) { - assertCompilerOptionsWithJsonText(JSON.stringify(json), configFileName, expectedResult); + } + + function verifyErrors(actualErrors: ts.Diagnostic[], expectedErrors: readonly ts.Diagnostic[], ignoreLocation?: boolean) { + assert.isTrue(expectedErrors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedErrors.map(getDiagnosticString), undefined, " ")}. Actual error: ${JSON.stringify(actualErrors.map(getDiagnosticString), undefined, " ")}.`); + for (let i = 0; i < actualErrors.length; i++) { + const actualError = actualErrors[i]; + const expectedError = expectedErrors[i]; + + assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); + assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); + if (!ignoreLocation) { + assert(actualError.file); + assert.isDefined(actualError.start); + assert(actualError.length); + } } - function assertCompilerOptionsWithJsonText(fileText: string, configFileName: string, expectedResult: ExpectedResult) { - const result = parseJsonText(configFileName, fileText); - assert(!!result.endOfFileToken); - assert.equal(!!result.parseDiagnostics.length, isExpectedResultWithParsingFailure(expectedResult)); - const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); - const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); - expectedResult.compilerOptions.configFilePath = configFileName; - - const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); - const expectedCompilerOptions = JSON.stringify(expectedResult.compilerOptions); - assert.equal(parsedCompilerOptions, expectedCompilerOptions); - assert.equal(actualCompilerOptions.configFile, result); - - if (!isExpectedResultWithParsingFailure(expectedResult)) { - verifyErrors(actualParseErrors.filter(error => error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code), expectedResult.errors); + function getDiagnosticString(diagnostic: ts.Diagnostic) { + if (ignoreLocation) { + const { file, ...rest } = diagnostic; + diagnostic = { file: undefined, ...rest }; } + return ts.formatDiagnostic(diagnostic, formatDiagnosticHost); } + } - function verifyErrors(actualErrors: Diagnostic[], expectedErrors: readonly Diagnostic[], ignoreLocation?: boolean) { - assert.isTrue(expectedErrors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedErrors.map(getDiagnosticString), undefined, " ")}. Actual error: ${JSON.stringify(actualErrors.map(getDiagnosticString), undefined, " ")}.`); - for (let i = 0; i < actualErrors.length; i++) { - const actualError = actualErrors[i]; - const expectedError = expectedErrors[i]; - - assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); - assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); - if (!ignoreLocation) { - assert(actualError.file); - assert.isDefined(actualError.start); - assert(actualError.length); + // tsconfig.json tests + it("Convert correctly format tsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: ["es5", "es2015.core", "es2015.symbol"] } + }, "tsconfig.json", + { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] + }, + errors: [] } + ); + }); - function getDiagnosticString(diagnostic: Diagnostic) { - if (ignoreLocation) { - const { file, ...rest } = diagnostic; - diagnostic = { file: undefined, ...rest }; + it("Convert correctly format tsconfig.json with allowJs is false to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + allowJs: false, + lib: ["es5", "es2015.core", "es2015.symbol"] } - return formatDiagnostic(diagnostic, formatDiagnosticHost); + }, "tsconfig.json", + { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + allowJs: false, + lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] + }, + errors: [] } - } - - // tsconfig.json tests - it("Convert correctly format tsconfig.json to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: ["es5", "es2015.core", "es2015.symbol"] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] - }, - errors: [] - } - ); - }); + ); + }); - it("Convert correctly format tsconfig.json with allowJs is false to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - allowJs: false, - lib: ["es5", "es2015.core", "es2015.symbol"] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - allowJs: false, - lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] - }, - errors: [] + it("Convert incorrect option of jsx to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + jsx: "" } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--jsx' option must be: 'preserve', 'react-native', 'react'.", + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of jsx to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - jsx: "" - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--jsx' option must be: 'preserve', 'react-native', 'react'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert incorrect option of module to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "", + target: "es5", + noImplicitAny: false, + sourceMap: false, } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext'.", + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of module to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "", - target: "es5", - noImplicitAny: false, - sourceMap: false, - } - }, "tsconfig.json", - { - compilerOptions: { - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert incorrect option of newLine to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + newLine: "", + target: "es5", + noImplicitAny: false, + sourceMap: false, } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--newLine' option must be: 'crlf', 'lf'.", + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of newLine to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - newLine: "", - target: "es5", - noImplicitAny: false, - sourceMap: false, - } - }, "tsconfig.json", - { - compilerOptions: { - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--newLine' option must be: 'crlf', 'lf'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert incorrect option of target to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + target: "", + noImplicitAny: false, + sourceMap: false, } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'esnext'.", + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of target to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - target: "", - noImplicitAny: false, - sourceMap: false, - } - }, "tsconfig.json", - { - compilerOptions: { - noImplicitAny: false, - sourceMap: false, - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'esnext'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert incorrect option of module-resolution to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + moduleResolution: "", + noImplicitAny: false, + sourceMap: false, } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--moduleResolution' option must be: 'node', 'classic'.", + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of module-resolution to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - moduleResolution: "", - noImplicitAny: false, - sourceMap: false, - } - }, "tsconfig.json", - { - compilerOptions: { - noImplicitAny: false, - sourceMap: false, - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--moduleResolution' option must be: 'node', 'classic'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert incorrect option of libs to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: ["es5", "es2015.core", "incorrectLib"] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts"] + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert incorrect option of libs to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: ["es5", "es2015.core", "incorrectLib"] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts"] - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert empty string option of libs to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: ["es5", ""] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: ["lib.es5.d.ts"] + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert empty string option of libs to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: ["es5", ""] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: ["lib.es5.d.ts"] - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert empty string option of libs to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: [""] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: [] + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert empty string option of libs to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: [""] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: [] - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert trailing-whitespace string option of libs to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: [" "] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: [] + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); - it("Convert trailing-whitespace string option of libs to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: [" "] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: [] - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'esnext.array', 'esnext.symbol', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise'.", - code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, - category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category - }] + it("Convert empty option of libs to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: [] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: [] + }, + errors: [] + } + ); + }); - it("Convert empty option of libs to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: [] - } - }, "tsconfig.json", - { - compilerOptions: { - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: [] - }, - errors: [] + it("Convert empty string option of moduleSuffixes to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + moduleSuffixes: [".ios", ""] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + moduleSuffixes: [".ios", ""] + }, + errors: [] + } + ); + }); - it("Convert empty string option of moduleSuffixes to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - moduleSuffixes: [".ios", ""] - } - }, "tsconfig.json", - { - compilerOptions: { - moduleSuffixes: [".ios", ""] - }, - errors: [] + it("Convert empty string option of moduleSuffixes to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + moduleSuffixes: [""] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + moduleSuffixes: [""] + }, + errors: [] + } + ); + }); - it("Convert empty string option of moduleSuffixes to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - moduleSuffixes: [""] - } - }, "tsconfig.json", - { - compilerOptions: { - moduleSuffixes: [""] - }, - errors: [] + it("Convert trailing-whitespace string option of moduleSuffixes to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + moduleSuffixes: [" "] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + moduleSuffixes: [" "] + }, + errors: [] + } + ); + }); - it("Convert trailing-whitespace string option of moduleSuffixes to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - moduleSuffixes: [" "] - } - }, "tsconfig.json", - { - compilerOptions: { - moduleSuffixes: [" "] - }, - errors: [] + it("Convert empty option of moduleSuffixes to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + moduleSuffixes: [] } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + moduleSuffixes: [] + }, + errors: [] + } + ); + }); - it("Convert empty option of moduleSuffixes to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - moduleSuffixes: [] - } - }, "tsconfig.json", - { - compilerOptions: { - moduleSuffixes: [] - }, - errors: [] + it("Convert incorrectly format tsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + modu: "commonjs", } - ); - }); + }, "tsconfig.json", + { + compilerOptions: {}, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Unknown compiler option 'modu'.", + code: ts.Diagnostics.Unknown_compiler_option_0.code, + category: ts.Diagnostics.Unknown_compiler_option_0.category + }] + } + ); + }); - it("Convert incorrectly format tsconfig.json to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - modu: "commonjs", - } - }, "tsconfig.json", - { - compilerOptions: {}, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Unknown compiler option 'modu'.", - code: Diagnostics.Unknown_compiler_option_0.code, - category: Diagnostics.Unknown_compiler_option_0.category - }] - } - ); - }); + it("Convert default tsconfig.json to compiler-options ", () => { + assertCompilerOptions({}, "tsconfig.json", + { + compilerOptions: {}, + errors: [] + } + ); + }); - it("Convert default tsconfig.json to compiler-options ", () => { - assertCompilerOptions({}, "tsconfig.json", - { - compilerOptions: {}, - errors: [] + it("Convert negative numbers in tsconfig.json ", () => { + assertCompilerOptions( + { + compilerOptions: { + allowJs: true, + maxNodeModuleJsDepth: -1 } - ); - }); + }, "tsconfig.json", + { + compilerOptions: { + allowJs: true, + maxNodeModuleJsDepth: -1 + }, + errors: [] + } + ); + }); - it("Convert negative numbers in tsconfig.json ", () => { - assertCompilerOptions( - { - compilerOptions: { - allowJs: true, - maxNodeModuleJsDepth: -1 - } - }, "tsconfig.json", - { - compilerOptions: { - allowJs: true, - maxNodeModuleJsDepth: -1 - }, - errors: [] + // jsconfig.json + it("Convert correctly format jsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + lib: ["es5", "es2015.core", "es2015.symbol"] } - ); - }); + }, "jsconfig.json", + { + compilerOptions: { + allowJs: true, + maxNodeModuleJsDepth: 2, + allowSyntheticDefaultImports: true, + skipLibCheck: true, + noEmit: true, + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] + }, + errors: [] + } + ); + }); - // jsconfig.json - it("Convert correctly format jsconfig.json to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - lib: ["es5", "es2015.core", "es2015.symbol"] - } - }, "jsconfig.json", - { - compilerOptions: { - allowJs: true, - maxNodeModuleJsDepth: 2, - allowSyntheticDefaultImports: true, - skipLibCheck: true, - noEmit: true, - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] - }, - errors: [] + it("Convert correctly format jsconfig.json with allowJs is false to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: false, + sourceMap: false, + allowJs: false, + lib: ["es5", "es2015.core", "es2015.symbol"] } - ); - }); + }, "jsconfig.json", + { + compilerOptions: { + allowJs: false, + maxNodeModuleJsDepth: 2, + allowSyntheticDefaultImports: true, + skipLibCheck: true, + noEmit: true, + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] + }, + errors: [] + } + ); + }); - it("Convert correctly format jsconfig.json with allowJs is false to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: false, - sourceMap: false, - allowJs: false, - lib: ["es5", "es2015.core", "es2015.symbol"] - } - }, "jsconfig.json", - { - compilerOptions: { - allowJs: false, - maxNodeModuleJsDepth: 2, - allowSyntheticDefaultImports: true, - skipLibCheck: true, - noEmit: true, - module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, - noImplicitAny: false, - sourceMap: false, - lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"] - }, - errors: [] + it("Convert incorrectly format jsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + compilerOptions: { + modu: "commonjs", } - ); - }); - - it("Convert incorrectly format jsconfig.json to compiler-options ", () => { - assertCompilerOptions( - { - compilerOptions: { - modu: "commonjs", - } - }, "jsconfig.json", + }, "jsconfig.json", + { + compilerOptions: { - compilerOptions: - { - allowJs: true, - maxNodeModuleJsDepth: 2, - allowSyntheticDefaultImports: true, - skipLibCheck: true, - noEmit: true - }, - errors: [{ - file: undefined, - start: 0, - length: 0, - messageText: "Unknown compiler option 'modu'.", - code: Diagnostics.Unknown_compiler_option_0.code, - category: Diagnostics.Unknown_compiler_option_0.category - }] - } - ); - }); + allowJs: true, + maxNodeModuleJsDepth: 2, + allowSyntheticDefaultImports: true, + skipLibCheck: true, + noEmit: true + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Unknown compiler option 'modu'.", + code: ts.Diagnostics.Unknown_compiler_option_0.code, + category: ts.Diagnostics.Unknown_compiler_option_0.category + }] + } + ); + }); - it("Convert default jsconfig.json to compiler-options ", () => { - assertCompilerOptions({}, "jsconfig.json", + it("Convert default jsconfig.json to compiler-options ", () => { + assertCompilerOptions({}, "jsconfig.json", + { + compilerOptions: { - compilerOptions: - { - allowJs: true, - maxNodeModuleJsDepth: 2, - allowSyntheticDefaultImports: true, - skipLibCheck: true, - noEmit: true - }, - errors: [] - } - ); - }); + allowJs: true, + maxNodeModuleJsDepth: 2, + allowSyntheticDefaultImports: true, + skipLibCheck: true, + noEmit: true + }, + errors: [] + } + ); + }); - it("Convert tsconfig options when there are multiple invalid strings", () => { - assertCompilerOptionsWithJsonText(`{ + it("Convert tsconfig options when there are multiple invalid strings", () => { + assertCompilerOptionsWithJsonText(`{ "compilerOptions": { "target": "<%- options.useTsWithBabel ? 'esnext' : 'es5' %>", "module": "esnext", @@ -657,123 +660,122 @@ namespace ts { } } `, - "tsconfig.json", - { - compilerOptions: { - target: undefined, - module: ModuleKind.ESNext, - experimentalDecorators: true, - }, - hasParseErrors: true - }); + "tsconfig.json", + { + compilerOptions: { + target: undefined, + module: ts.ModuleKind.ESNext, + experimentalDecorators: true, + }, + hasParseErrors: true }); + }); - it("Convert a tsconfig file with stray trailing characters", () => { - assertCompilerOptionsWithJsonText(`{ + it("Convert a tsconfig file with stray trailing characters", () => { + assertCompilerOptionsWithJsonText(`{ "compilerOptions": { "target": "esnext" } } blah`, "tsconfig.json", { - compilerOptions: { - target: ScriptTarget.ESNext - }, - hasParseErrors: true, - errors: [{ - ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, - messageText: "The root value of a 'tsconfig.json' file must be an object.", - file: undefined, - start: 0, - length: 0 - }] - }); + compilerOptions: { + target: ts.ScriptTarget.ESNext + }, + hasParseErrors: true, + errors: [{ + ...ts.Diagnostics.The_root_value_of_a_0_file_must_be_an_object, + messageText: "The root value of a 'tsconfig.json' file must be an object.", + file: undefined, + start: 0, + length: 0 + }] }); + }); - it("Convert a tsconfig file with stray leading characters", () => { - assertCompilerOptionsWithJsonText(`blah { + it("Convert a tsconfig file with stray leading characters", () => { + assertCompilerOptionsWithJsonText(`blah { "compilerOptions": { "target": "esnext" } }`, "tsconfig.json", { - compilerOptions: { - target: ScriptTarget.ESNext - }, - hasParseErrors: true, - errors: [{ - ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, - messageText: "The root value of a 'tsconfig.json' file must be an object.", - file: undefined, - start: 0, - length: 0 - }] - }); + compilerOptions: { + target: ts.ScriptTarget.ESNext + }, + hasParseErrors: true, + errors: [{ + ...ts.Diagnostics.The_root_value_of_a_0_file_must_be_an_object, + messageText: "The root value of a 'tsconfig.json' file must be an object.", + file: undefined, + start: 0, + length: 0 + }] }); + }); - it("Convert a tsconfig file as an array", () => { - assertCompilerOptionsWithJsonText(`[{ + it("Convert a tsconfig file as an array", () => { + assertCompilerOptionsWithJsonText(`[{ "compilerOptions": { "target": "esnext" } }]`, "tsconfig.json", { - compilerOptions: { - target: ScriptTarget.ESNext - }, - errors: [{ - ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, - messageText: "The root value of a 'tsconfig.json' file must be an object.", - file: undefined, - start: 0, - length: 0 - }] - }); + compilerOptions: { + target: ts.ScriptTarget.ESNext + }, + errors: [{ + ...ts.Diagnostics.The_root_value_of_a_0_file_must_be_an_object, + messageText: "The root value of a 'tsconfig.json' file must be an object.", + file: undefined, + start: 0, + length: 0 + }] }); + }); - it("raises an error if you've set a compiler flag in the root without including 'compilerOptions'", () => { - assertCompilerOptionsWithJsonText(`{ + it("raises an error if you've set a compiler flag in the root without including 'compilerOptions'", () => { + assertCompilerOptionsWithJsonText(`{ "module": "esnext", }`, "tsconfig.json", { - compilerOptions: {}, - errors: [{ - ...Diagnostics._0_should_be_set_inside_the_compilerOptions_object_of_the_config_json_file, - messageText: "'module' should be set inside the 'compilerOptions' object of the config json file.", - file: undefined, - start: 0, - length: 0 - }] - }); + compilerOptions: {}, + errors: [{ + ...ts.Diagnostics._0_should_be_set_inside_the_compilerOptions_object_of_the_config_json_file, + messageText: "'module' should be set inside the 'compilerOptions' object of the config json file.", + file: undefined, + start: 0, + length: 0 + }] }); + }); - it("does not raise an error if you've set a compiler flag in the root when you have included 'compilerOptions'", () => { - assertCompilerOptionsWithJsonText(`{ + it("does not raise an error if you've set a compiler flag in the root when you have included 'compilerOptions'", () => { + assertCompilerOptionsWithJsonText(`{ "target": "esnext", "compilerOptions": { "module": "esnext" } }`, "tsconfig.json", { - compilerOptions: { - module: ModuleKind.ESNext - }, - errors: [] - }); + compilerOptions: { + module: ts.ModuleKind.ESNext + }, + errors: [] }); + }); - it("Don't crash when root expression is not object at all", () => { - assertCompilerOptionsWithJsonText(`42`, "tsconfig.json", { - compilerOptions: {}, - errors: [{ - ...Diagnostics.The_root_value_of_a_0_file_must_be_an_object, - messageText: "The root value of a 'tsconfig.json' file must be an object.", - file: undefined, - start: 0, - length: 0 - }] - }); + it("Don't crash when root expression is not object at all", () => { + assertCompilerOptionsWithJsonText(`42`, "tsconfig.json", { + compilerOptions: {}, + errors: [{ + ...ts.Diagnostics.The_root_value_of_a_0_file_must_be_an_object, + messageText: "The root value of a 'tsconfig.json' file must be an object.", + file: undefined, + start: 0, + length: 0 + }] }); + }); - it("Allow trailing comments", () => { - assertCompilerOptionsWithJsonText(`{} // no options`, "tsconfig.json", { - compilerOptions: {}, - errors: [] - }); + it("Allow trailing comments", () => { + assertCompilerOptionsWithJsonText(`{} // no options`, "tsconfig.json", { + compilerOptions: {}, + errors: [] }); }); -} +}); diff --git a/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts b/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts index a985c4838dd24..4f091721ddedc 100644 --- a/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts +++ b/src/testRunner/unittests/config/convertTypeAcquisitionFromJson.ts @@ -1,239 +1,241 @@ -namespace ts { - interface ExpectedResult { typeAcquisition: TypeAcquisition; errors: Diagnostic[]; } - describe("unittests:: config:: convertTypeAcquisitionFromJson", () => { - function assertTypeAcquisition(json: any, configFileName: string, expectedResult: ExpectedResult) { - assertTypeAcquisitionWithJson(json, configFileName, expectedResult); - assertTypeAcquisitionWithJsonNode(json, configFileName, expectedResult); - } +import * as ts from "../../_namespaces/ts"; +import * as fakes from "../../_namespaces/fakes"; +import * as vfs from "../../_namespaces/vfs"; - function verifyAcquisition(actualTypeAcquisition: TypeAcquisition | undefined, expectedResult: ExpectedResult) { - const parsedTypeAcquisition = JSON.stringify(actualTypeAcquisition); - const expectedTypeAcquisition = JSON.stringify(expectedResult.typeAcquisition); - assert.equal(parsedTypeAcquisition, expectedTypeAcquisition); - } +interface ExpectedResult { typeAcquisition: ts.TypeAcquisition; errors: ts.Diagnostic[]; } +describe("unittests:: config:: convertTypeAcquisitionFromJson", () => { + function assertTypeAcquisition(json: any, configFileName: string, expectedResult: ExpectedResult) { + assertTypeAcquisitionWithJson(json, configFileName, expectedResult); + assertTypeAcquisitionWithJsonNode(json, configFileName, expectedResult); + } - function verifyErrors(actualErrors: Diagnostic[], expectedResult: ExpectedResult, hasLocation?: boolean) { - const expectedErrors = expectedResult.errors; - assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); - for (let i = 0; i < actualErrors.length; i++) { - const actualError = actualErrors[i]; - const expectedError = expectedErrors[i]; - assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); - assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); - if (hasLocation) { - assert(actualError.file); - assert(actualError.start); - assert(actualError.length); - } + function verifyAcquisition(actualTypeAcquisition: ts.TypeAcquisition | undefined, expectedResult: ExpectedResult) { + const parsedTypeAcquisition = JSON.stringify(actualTypeAcquisition); + const expectedTypeAcquisition = JSON.stringify(expectedResult.typeAcquisition); + assert.equal(parsedTypeAcquisition, expectedTypeAcquisition); + } + + function verifyErrors(actualErrors: ts.Diagnostic[], expectedResult: ExpectedResult, hasLocation?: boolean) { + const expectedErrors = expectedResult.errors; + assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); + for (let i = 0; i < actualErrors.length; i++) { + const actualError = actualErrors[i]; + const expectedError = expectedErrors[i]; + assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); + assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); + if (hasLocation) { + assert(actualError.file); + assert(actualError.start); + assert(actualError.length); } } + } - function assertTypeAcquisitionWithJson(json: any, configFileName: string, expectedResult: ExpectedResult) { - const jsonOptions = json.typeAcquisition || json.typingOptions; - const { options: actualTypeAcquisition, errors: actualErrors } = convertTypeAcquisitionFromJson(jsonOptions, "/apath/", configFileName); - verifyAcquisition(actualTypeAcquisition, expectedResult); - verifyErrors(actualErrors, expectedResult); - } + function assertTypeAcquisitionWithJson(json: any, configFileName: string, expectedResult: ExpectedResult) { + const jsonOptions = json.typeAcquisition || json.typingOptions; + const { options: actualTypeAcquisition, errors: actualErrors } = ts.convertTypeAcquisitionFromJson(jsonOptions, "/apath/", configFileName); + verifyAcquisition(actualTypeAcquisition, expectedResult); + verifyErrors(actualErrors, expectedResult); + } - function assertTypeAcquisitionWithJsonNode(json: any, configFileName: string, expectedResult: ExpectedResult) { - const fileText = JSON.stringify(json); - const result = parseJsonText(configFileName, fileText); - assert(!result.parseDiagnostics.length); - assert(!!result.endOfFileToken); - const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); - const { typeAcquisition: actualTypeAcquisition, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); - verifyAcquisition(actualTypeAcquisition, expectedResult); + function assertTypeAcquisitionWithJsonNode(json: any, configFileName: string, expectedResult: ExpectedResult) { + const fileText = JSON.stringify(json); + const result = ts.parseJsonText(configFileName, fileText); + assert(!result.parseDiagnostics.length); + assert(!!result.endOfFileToken); + const host: ts.ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: "/apath/" })); + const { typeAcquisition: actualTypeAcquisition, errors: actualParseErrors } = ts.parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); + verifyAcquisition(actualTypeAcquisition, expectedResult); - const actualErrors = filter(actualParseErrors, error => error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); - verifyErrors(actualErrors, expectedResult, /*hasLocation*/ true); - } + const actualErrors = ts.filter(actualParseErrors, error => error.code !== ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); + verifyErrors(actualErrors, expectedResult, /*hasLocation*/ true); + } - // tsconfig.json - it("Convert deprecated typingOptions.enableAutoDiscovery format tsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition( - { - typingOptions: - { - enableAutoDiscovery: true, - include: ["0.d.ts", "1.d.ts"], - exclude: ["0.js", "1.js"] - } - }, - "tsconfig.json", - { - typeAcquisition: - { - enable: true, - include: ["0.d.ts", "1.d.ts"], - exclude: ["0.js", "1.js"] - }, - errors: [] as Diagnostic[] + // tsconfig.json + it("Convert deprecated typingOptions.enableAutoDiscovery format tsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition( + { + typingOptions: + { + enableAutoDiscovery: true, + include: ["0.d.ts", "1.d.ts"], + exclude: ["0.js", "1.js"] } - ); - }); + }, + "tsconfig.json", + { + typeAcquisition: + { + enable: true, + include: ["0.d.ts", "1.d.ts"], + exclude: ["0.js", "1.js"] + }, + errors: [] as ts.Diagnostic[] + } + ); + }); - it("Convert correctly format tsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition( - { - typeAcquisition: - { - enable: true, - include: ["0.d.ts", "1.d.ts"], - exclude: ["0.js", "1.js"] - } + it("Convert correctly format tsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: + { + enable: true, + include: ["0.d.ts", "1.d.ts"], + exclude: ["0.js", "1.js"] + } + }, + "tsconfig.json", + { + typeAcquisition: + { + enable: true, + include: ["0.d.ts", "1.d.ts"], + exclude: ["0.js", "1.js"] }, - "tsconfig.json", - { - typeAcquisition: - { - enable: true, - include: ["0.d.ts", "1.d.ts"], - exclude: ["0.js", "1.js"] - }, - errors: [] as Diagnostic[] - }); - }); + errors: [] as ts.Diagnostic[] + }); + }); - it("Convert incorrect format tsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition( + it("Convert incorrect format tsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: { - typeAcquisition: - { - enableAutoDiscovy: true, - } - }, "tsconfig.json", + enableAutoDiscovy: true, + } + }, "tsconfig.json", + { + typeAcquisition: { - typeAcquisition: - { - enable: false, - include: [], - exclude: [] - }, - errors: [ - { - category: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.category, - code: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.code, - file: undefined, - start: 0, - length: 0, - messageText: undefined!, // TODO: GH#18217 - } - ] - }); - }); + enable: false, + include: [], + exclude: [] + }, + errors: [ + { + category: ts.Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.category, + code: ts.Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.code, + file: undefined, + start: 0, + length: 0, + messageText: undefined!, // TODO: GH#18217 + } + ] + }); + }); - it("Convert default tsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition({}, "tsconfig.json", - { - typeAcquisition: - { - enable: false, - include: [], - exclude: [] - }, - errors: [] as Diagnostic[] - }); - }); + it("Convert default tsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition({}, "tsconfig.json", + { + typeAcquisition: + { + enable: false, + include: [], + exclude: [] + }, + errors: [] as ts.Diagnostic[] + }); + }); - it("Convert tsconfig.json with only enable property to typeAcquisition ", () => { - assertTypeAcquisition( + it("Convert tsconfig.json with only enable property to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: { - typeAcquisition: - { - enable: true - } - }, "tsconfig.json", + enable: true + } + }, "tsconfig.json", + { + typeAcquisition: { - typeAcquisition: - { - enable: true, - include: [], - exclude: [] - }, - errors: [] as Diagnostic[] - }); - }); + enable: true, + include: [], + exclude: [] + }, + errors: [] as ts.Diagnostic[] + }); + }); - // jsconfig.json - it("Convert jsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition( - { - typeAcquisition: - { - enable: false, - include: ["0.d.ts"], - exclude: ["0.js"] - } - }, "jsconfig.json", + // jsconfig.json + it("Convert jsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: + { + enable: false, + include: ["0.d.ts"], + exclude: ["0.js"] + } + }, "jsconfig.json", + { + typeAcquisition: { - typeAcquisition: - { - enable: false, - include: ["0.d.ts"], - exclude: ["0.js"] - }, - errors: [] as Diagnostic[] - }); - }); + enable: false, + include: ["0.d.ts"], + exclude: ["0.js"] + }, + errors: [] as ts.Diagnostic[] + }); + }); - it("Convert default jsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition({ }, "jsconfig.json", - { - typeAcquisition: - { - enable: true, - include: [], - exclude: [] - }, - errors: [] as Diagnostic[] - }); - }); + it("Convert default jsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition({ }, "jsconfig.json", + { + typeAcquisition: + { + enable: true, + include: [], + exclude: [] + }, + errors: [] as ts.Diagnostic[] + }); + }); - it("Convert incorrect format jsconfig.json to typeAcquisition ", () => { - assertTypeAcquisition( + it("Convert incorrect format jsconfig.json to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: { - typeAcquisition: - { - enableAutoDiscovy: true, - } - }, "jsconfig.json", + enableAutoDiscovy: true, + } + }, "jsconfig.json", + { + typeAcquisition: { - typeAcquisition: - { - enable: true, - include: [], - exclude: [] - }, - errors: [ - { - category: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.category, - code: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.code, - file: undefined, - start: 0, - length: 0, - messageText: undefined!, // TODO: GH#18217 - } - ] - }); - }); + enable: true, + include: [], + exclude: [] + }, + errors: [ + { + category: ts.Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.category, + code: ts.Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.code, + file: undefined, + start: 0, + length: 0, + messageText: undefined!, // TODO: GH#18217 + } + ] + }); + }); - it("Convert jsconfig.json with only enable property to typeAcquisition ", () => { - assertTypeAcquisition( + it("Convert jsconfig.json with only enable property to typeAcquisition ", () => { + assertTypeAcquisition( + { + typeAcquisition: { - typeAcquisition: - { - enable: false - } - }, "jsconfig.json", + enable: false + } + }, "jsconfig.json", + { + typeAcquisition: { - typeAcquisition: - { - enable: false, - include: [], - exclude: [] - }, - errors: [] as Diagnostic[] - }); - }); + enable: false, + include: [], + exclude: [] + }, + errors: [] as ts.Diagnostic[] + }); }); -} +}); diff --git a/src/testRunner/unittests/config/initializeTSConfig.ts b/src/testRunner/unittests/config/initializeTSConfig.ts index d92c906f46180..fee5b10b6bdf4 100644 --- a/src/testRunner/unittests/config/initializeTSConfig.ts +++ b/src/testRunner/unittests/config/initializeTSConfig.ts @@ -1,37 +1,38 @@ -namespace ts { - describe("unittests:: config:: initTSConfig", () => { - function initTSConfigCorrectly(name: string, commandLinesArgs: string[]) { - describe(name, () => { - const commandLine = parseCommandLine(commandLinesArgs); - const initResult = generateTSConfig(commandLine.options, commandLine.fileNames, "\n"); - const outputFileName = `config/initTSConfig/${name.replace(/[^a-z0-9\-. ]/ig, "")}/tsconfig.json`; - - it(`Correct output for ${outputFileName}`, () => { - Harness.Baseline.runBaseline(outputFileName, initResult, { PrintDiff: true }); - }); +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; + +describe("unittests:: config:: initTSConfig", () => { + function initTSConfigCorrectly(name: string, commandLinesArgs: string[]) { + describe(name, () => { + const commandLine = ts.parseCommandLine(commandLinesArgs); + const initResult = ts.generateTSConfig(commandLine.options, commandLine.fileNames, "\n"); + const outputFileName = `config/initTSConfig/${name.replace(/[^a-z0-9\-. ]/ig, "")}/tsconfig.json`; + + it(`Correct output for ${outputFileName}`, () => { + Harness.Baseline.runBaseline(outputFileName, initResult, { PrintDiff: true }); }); - } + }); + } - initTSConfigCorrectly("Default initialized TSConfig", ["--init"]); + initTSConfigCorrectly("Default initialized TSConfig", ["--init"]); - initTSConfigCorrectly("Initialized TSConfig with files options", ["--init", "file0.st", "file1.ts", "file2.ts"]); + initTSConfigCorrectly("Initialized TSConfig with files options", ["--init", "file0.st", "file1.ts", "file2.ts"]); - initTSConfigCorrectly("Initialized TSConfig with boolean value compiler options", ["--init", "--noUnusedLocals"]); + initTSConfigCorrectly("Initialized TSConfig with boolean value compiler options", ["--init", "--noUnusedLocals"]); - initTSConfigCorrectly("Initialized TSConfig with enum value compiler options", ["--init", "--target", "es5", "--jsx", "react"]); + initTSConfigCorrectly("Initialized TSConfig with enum value compiler options", ["--init", "--target", "es5", "--jsx", "react"]); - initTSConfigCorrectly("Initialized TSConfig with list compiler options", ["--init", "--types", "jquery,mocha"]); + initTSConfigCorrectly("Initialized TSConfig with list compiler options", ["--init", "--types", "jquery,mocha"]); - initTSConfigCorrectly("Initialized TSConfig with list compiler options with enum value", ["--init", "--lib", "es5,es2015.core"]); + initTSConfigCorrectly("Initialized TSConfig with list compiler options with enum value", ["--init", "--lib", "es5,es2015.core"]); - initTSConfigCorrectly("Initialized TSConfig with incorrect compiler option", ["--init", "--someNonExistOption"]); + initTSConfigCorrectly("Initialized TSConfig with incorrect compiler option", ["--init", "--someNonExistOption"]); - initTSConfigCorrectly("Initialized TSConfig with incorrect compiler option value", ["--init", "--lib", "nonExistLib,es5,es2015.promise"]); + initTSConfigCorrectly("Initialized TSConfig with incorrect compiler option value", ["--init", "--lib", "nonExistLib,es5,es2015.promise"]); - initTSConfigCorrectly("Initialized TSConfig with advanced options", ["--init", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]); + initTSConfigCorrectly("Initialized TSConfig with advanced options", ["--init", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]); - initTSConfigCorrectly("Initialized TSConfig with --help", ["--init", "--help"]); + initTSConfigCorrectly("Initialized TSConfig with --help", ["--init", "--help"]); - initTSConfigCorrectly("Initialized TSConfig with --watch", ["--init", "--watch"]); - }); -} + initTSConfigCorrectly("Initialized TSConfig with --watch", ["--init", "--watch"]); +}); diff --git a/src/testRunner/unittests/config/matchFiles.ts b/src/testRunner/unittests/config/matchFiles.ts index 951d87be80f24..18589d09fb015 100644 --- a/src/testRunner/unittests/config/matchFiles.ts +++ b/src/testRunner/unittests/config/matchFiles.ts @@ -1,1542 +1,1544 @@ -namespace ts { - const caseInsensitiveBasePath = "c:/dev/"; - const caseInsensitiveTsconfigPath = "c:/dev/tsconfig.json"; - const caseInsensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { - "c:/dev/a.ts": "", - "c:/dev/a.d.ts": "", - "c:/dev/a.js": "", - "c:/dev/b.ts": "", - "c:/dev/b.js": "", - "c:/dev/c.d.ts": "", - "c:/dev/z/a.ts": "", - "c:/dev/z/abz.ts": "", - "c:/dev/z/aba.ts": "", - "c:/dev/z/b.ts": "", - "c:/dev/z/bbz.ts": "", - "c:/dev/z/bba.ts": "", - "c:/dev/x/a.ts": "", - "c:/dev/x/aa.ts": "", - "c:/dev/x/b.ts": "", - "c:/dev/x/y/a.ts": "", - "c:/dev/x/y/b.ts": "", - "c:/dev/js/a.js": "", - "c:/dev/js/b.js": "", - "c:/dev/js/d.min.js": "", - "c:/dev/js/ab.min.js": "", - "c:/ext/ext.ts": "", - "c:/ext/b/a..b.ts": "", - }})); +import * as ts from "../../_namespaces/ts"; +import * as fakes from "../../_namespaces/fakes"; +import * as vfs from "../../_namespaces/vfs"; - const caseSensitiveBasePath = "/dev/"; - const caseSensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { - "/dev/a.ts": "", - "/dev/a.d.ts": "", - "/dev/a.js": "", - "/dev/b.ts": "", - "/dev/b.js": "", - "/dev/A.ts": "", - "/dev/B.ts": "", - "/dev/c.d.ts": "", - "/dev/z/a.ts": "", - "/dev/z/abz.ts": "", - "/dev/z/aba.ts": "", - "/dev/z/b.ts": "", - "/dev/z/bbz.ts": "", - "/dev/z/bba.ts": "", - "/dev/x/a.ts": "", - "/dev/x/b.ts": "", - "/dev/x/y/a.ts": "", - "/dev/x/y/b.ts": "", - "/dev/q/a/c/b/d.ts": "", - "/dev/js/a.js": "", - "/dev/js/b.js": "", - }})); +const caseInsensitiveBasePath = "c:/dev/"; +const caseInsensitiveTsconfigPath = "c:/dev/tsconfig.json"; +const caseInsensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + "c:/dev/a.ts": "", + "c:/dev/a.d.ts": "", + "c:/dev/a.js": "", + "c:/dev/b.ts": "", + "c:/dev/b.js": "", + "c:/dev/c.d.ts": "", + "c:/dev/z/a.ts": "", + "c:/dev/z/abz.ts": "", + "c:/dev/z/aba.ts": "", + "c:/dev/z/b.ts": "", + "c:/dev/z/bbz.ts": "", + "c:/dev/z/bba.ts": "", + "c:/dev/x/a.ts": "", + "c:/dev/x/aa.ts": "", + "c:/dev/x/b.ts": "", + "c:/dev/x/y/a.ts": "", + "c:/dev/x/y/b.ts": "", + "c:/dev/js/a.js": "", + "c:/dev/js/b.js": "", + "c:/dev/js/d.min.js": "", + "c:/dev/js/ab.min.js": "", + "c:/ext/ext.ts": "", + "c:/ext/b/a..b.ts": "", +}})); - const caseInsensitiveMixedExtensionHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { - "c:/dev/a.ts": "", - "c:/dev/a.d.ts": "", - "c:/dev/a.js": "", - "c:/dev/b.tsx": "", - "c:/dev/b.d.ts": "", - "c:/dev/b.jsx": "", - "c:/dev/c.tsx": "", - "c:/dev/c.js": "", - "c:/dev/d.js": "", - "c:/dev/e.jsx": "", - "c:/dev/f.other": "", - }})); +const caseSensitiveBasePath = "/dev/"; +const caseSensitiveHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { + "/dev/a.ts": "", + "/dev/a.d.ts": "", + "/dev/a.js": "", + "/dev/b.ts": "", + "/dev/b.js": "", + "/dev/A.ts": "", + "/dev/B.ts": "", + "/dev/c.d.ts": "", + "/dev/z/a.ts": "", + "/dev/z/abz.ts": "", + "/dev/z/aba.ts": "", + "/dev/z/b.ts": "", + "/dev/z/bbz.ts": "", + "/dev/z/bba.ts": "", + "/dev/x/a.ts": "", + "/dev/x/b.ts": "", + "/dev/x/y/a.ts": "", + "/dev/x/y/b.ts": "", + "/dev/q/a/c/b/d.ts": "", + "/dev/js/a.js": "", + "/dev/js/b.js": "", +}})); - const caseInsensitiveCommonFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { - "c:/dev/a.ts": "", - "c:/dev/a.d.ts": "", - "c:/dev/a.js": "", - "c:/dev/b.ts": "", - "c:/dev/x/a.ts": "", - "c:/dev/node_modules/a.ts": "", - "c:/dev/bower_components/a.ts": "", - "c:/dev/jspm_packages/a.ts": "", - }})); +const caseInsensitiveMixedExtensionHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + "c:/dev/a.ts": "", + "c:/dev/a.d.ts": "", + "c:/dev/a.js": "", + "c:/dev/b.tsx": "", + "c:/dev/b.d.ts": "", + "c:/dev/b.jsx": "", + "c:/dev/c.tsx": "", + "c:/dev/c.js": "", + "c:/dev/d.js": "", + "c:/dev/e.jsx": "", + "c:/dev/f.other": "", +}})); - const caseInsensitiveDottedFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { - "c:/dev/x/d.ts": "", - "c:/dev/x/y/d.ts": "", - "c:/dev/x/y/.e.ts": "", - "c:/dev/x/.y/a.ts": "", - "c:/dev/.z/.b.ts": "", - "c:/dev/.z/c.ts": "", - "c:/dev/w/.u/e.ts": "", - "c:/dev/g.min.js/.g/g.ts": "", - }})); +const caseInsensitiveCommonFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + "c:/dev/a.ts": "", + "c:/dev/a.d.ts": "", + "c:/dev/a.js": "", + "c:/dev/b.ts": "", + "c:/dev/x/a.ts": "", + "c:/dev/node_modules/a.ts": "", + "c:/dev/bower_components/a.ts": "", + "c:/dev/jspm_packages/a.ts": "", +}})); - const caseInsensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { - "c:/dev/xylophone.ts": "", - "c:/dev/Yosemite.ts": "", - "c:/dev/zebra.ts": "", - }})); +const caseInsensitiveDottedFoldersHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + "c:/dev/x/d.ts": "", + "c:/dev/x/y/d.ts": "", + "c:/dev/x/y/.e.ts": "", + "c:/dev/x/.y/a.ts": "", + "c:/dev/.z/.b.ts": "", + "c:/dev/.z/c.ts": "", + "c:/dev/w/.u/e.ts": "", + "c:/dev/g.min.js/.g/g.ts": "", +}})); - const caseSensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { - "/dev/xylophone.ts": "", - "/dev/Yosemite.ts": "", - "/dev/zebra.ts": "", - }})); +const caseInsensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: caseInsensitiveBasePath, files: { + "c:/dev/xylophone.ts": "", + "c:/dev/Yosemite.ts": "", + "c:/dev/zebra.ts": "", +}})); - function assertParsed(actual: ParsedCommandLine, expected: ParsedCommandLine): void { - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); - } +const caseSensitiveOrderingDiffersWithCaseHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: caseSensitiveBasePath, files: { + "/dev/xylophone.ts": "", + "/dev/Yosemite.ts": "", + "/dev/zebra.ts": "", +}})); - function validateMatches(expected: ParsedCommandLine, json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[]) { - { - const jsonText = JSON.stringify(json); - const result = parseJsonText(caseInsensitiveTsconfigPath, jsonText); - const actual = parseJsonSourceFileConfigFileContent(result, host, basePath, existingOptions, configFileName, resolutionStack); - for (const error of expected.errors) { - if (error.file) { - error.file = result; - } +function assertParsed(actual: ts.ParsedCommandLine, expected: ts.ParsedCommandLine): void { + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); +} + +function validateMatches(expected: ts.ParsedCommandLine, json: any, host: ts.ParseConfigHost, basePath: string, existingOptions?: ts.CompilerOptions, configFileName?: string, resolutionStack?: ts.Path[]) { + { + const jsonText = JSON.stringify(json); + const result = ts.parseJsonText(caseInsensitiveTsconfigPath, jsonText); + const actual = ts.parseJsonSourceFileConfigFileContent(result, host, basePath, existingOptions, configFileName, resolutionStack); + for (const error of expected.errors) { + if (error.file) { + error.file = result; } - assertParsed(actual, expected); - } - { - const actual = parseJsonConfigFileContent(json, host, basePath, existingOptions, configFileName, resolutionStack); - expected.errors = expected.errors.map((error): Diagnostic => ({ - category: error.category, - code: error.code, - file: undefined, - length: undefined, - messageText: error.messageText, - start: undefined, - reportsUnnecessary: undefined, - reportsDeprecated: undefined - })); - assertParsed(actual, expected); } + assertParsed(actual, expected); } - - function createDiagnosticForConfigFile(json: any, start: number, length: number, diagnosticMessage: DiagnosticMessage, arg0: string) { - const text = JSON.stringify(json); - const file = { - fileName: caseInsensitiveTsconfigPath, - kind: SyntaxKind.SourceFile, - text - } as SourceFile; - return createFileDiagnostic(file, start, length, diagnosticMessage, arg0); + { + const actual = ts.parseJsonConfigFileContent(json, host, basePath, existingOptions, configFileName, resolutionStack); + expected.errors = expected.errors.map((error): ts.Diagnostic => ({ + category: error.category, + code: error.code, + file: undefined, + length: undefined, + messageText: error.messageText, + start: undefined, + reportsUnnecessary: undefined, + reportsDeprecated: undefined + })); + assertParsed(actual, expected); } +} + +function createDiagnosticForConfigFile(json: any, start: number, length: number, diagnosticMessage: ts.DiagnosticMessage, arg0: string) { + const text = JSON.stringify(json); + const file = { + fileName: caseInsensitiveTsconfigPath, + kind: ts.SyntaxKind.SourceFile, + text + } as ts.SourceFile; + return ts.createFileDiagnostic(file, start, length, diagnosticMessage, arg0); +} + +describe("unittests:: config:: matchFiles", () => { + it("with defaults", () => { + const json = {}; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/x/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); + + describe("with literal file list", () => { + it("without exclusions", () => { + const json = { + files: [ + "a.ts", + "b.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("missing files are still present", () => { + const json = { + files: [ + "z.ts", + "x.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/z.ts", + "c:/dev/x.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("are not removed due to excludes", () => { + const json = { + files: [ + "a.ts", + "b.ts" + ], + exclude: [ + "b.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + }); - describe("unittests:: config:: matchFiles", () => { - it("with defaults", () => { - const json = {}; - const expected: ParsedCommandLine = { + describe("with literal include list", () => { + it("without exclusions", () => { + const json = { + include: [ + "a.ts", + "b.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with non .ts file extensions are excluded", () => { + const json = { + include: [ + "a.js", + "b.js" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [ + ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + ], + fileNames: [], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + }); + it("with missing files are excluded", () => { + const json = { + include: [ + "z.ts", + "x.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [ + ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + ], + fileNames: [], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + }); + it("with literal excludes", () => { + const json = { + include: [ + "a.ts", + "b.ts" + ], + exclude: [ + "b.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with wildcard excludes", () => { + const json = { + include: [ + "a.ts", + "b.ts", + "z/a.ts", + "z/abz.ts", + "z/aba.ts", + "x/b.ts" + ], + exclude: [ + "*.ts", + "z/??z.ts", + "*/b.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/z/a.ts", + "c:/dev/z/aba.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with recursive excludes", () => { + const json = { + include: [ + "a.ts", + "b.ts", + "x/a.ts", + "x/b.ts", + "x/y/a.ts", + "x/y/b.ts" + ], + exclude: [ + "**/b.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/x/a.ts", + "c:/dev/x/y/a.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with case sensitive exclude", () => { + const json = { + include: [ + "B.ts" + ], + exclude: [ + "**/b.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "/dev/B.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + }); + it("with common package folders and no exclusions", () => { + const json = { + include: [ + "a.ts", + "b.ts", + "node_modules/a.ts", + "bower_components/a.ts", + "jspm_packages/a.ts" + ] + }; + const expected: ts.ParsedCommandLine = { options: {}, errors: [], fileNames: [ "c:/dev/a.ts", "c:/dev/b.ts", - "c:/dev/x/a.ts" + "c:/dev/node_modules/a.ts", + "c:/dev/bower_components/a.ts", + "c:/dev/jspm_packages/a.ts" ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, + wildcardDirectories: {}, }; validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - - describe("with literal file list", () => { - it("without exclusions", () => { - const json = { - files: [ - "a.ts", - "b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("missing files are still present", () => { - const json = { - files: [ - "z.ts", - "x.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/z.ts", - "c:/dev/x.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("are not removed due to excludes", () => { - const json = { - files: [ - "a.ts", - "b.ts" - ], - exclude: [ - "b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); + it("with common package folders and exclusions", () => { + const json = { + include: [ + "a.ts", + "b.ts", + "node_modules/a.ts", + "bower_components/a.ts", + "jspm_packages/a.ts" + ], + exclude: [ + "a.ts", + "b.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/node_modules/a.ts", + "c:/dev/bower_components/a.ts", + "c:/dev/jspm_packages/a.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); + }); + it("with common package folders and empty exclude", () => { + const json = { + include: [ + "a.ts", + "b.ts", + "node_modules/a.ts", + "bower_components/a.ts", + "jspm_packages/a.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/node_modules/a.ts", + "c:/dev/bower_components/a.ts", + "c:/dev/jspm_packages/a.ts" + ], + wildcardDirectories: {}, + }; + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); + }); - describe("with literal include list", () => { - it("without exclusions", () => { - const json = { - include: [ - "a.ts", - "b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("with non .ts file extensions are excluded", () => { - const json = { - include: [ - "a.js", - "b.js" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") - ], - fileNames: [], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - it("with missing files are excluded", () => { - const json = { - include: [ - "z.ts", - "x.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") - ], - fileNames: [], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - it("with literal excludes", () => { - const json = { - include: [ - "a.ts", - "b.ts" - ], - exclude: [ - "b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("with wildcard excludes", () => { - const json = { - include: [ - "a.ts", - "b.ts", - "z/a.ts", - "z/abz.ts", - "z/aba.ts", - "x/b.ts" - ], - exclude: [ - "*.ts", - "z/??z.ts", - "*/b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/z/a.ts", - "c:/dev/z/aba.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("with recursive excludes", () => { - const json = { - include: [ - "a.ts", - "b.ts", - "x/a.ts", - "x/b.ts", - "x/y/a.ts", - "x/y/b.ts" - ], - exclude: [ - "**/b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts", - "c:/dev/x/y/a.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("with case sensitive exclude", () => { - const json = { - include: [ - "B.ts" - ], - exclude: [ - "**/b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "/dev/B.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); - }); - it("with common package folders and no exclusions", () => { + describe("with wildcard include list", () => { + it("is sorted in include order, then in alphabetical order", () => { + const json = { + include: [ + "z/*.ts", + "x/*.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/z/a.ts", + "c:/dev/z/aba.ts", + "c:/dev/z/abz.ts", + "c:/dev/z/b.ts", + "c:/dev/z/bba.ts", + "c:/dev/z/bbz.ts", + "c:/dev/x/a.ts", + "c:/dev/x/aa.ts", + "c:/dev/x/b.ts" + ], + wildcardDirectories: { + "c:/dev/z": ts.WatchDirectoryFlags.None, + "c:/dev/x": ts.WatchDirectoryFlags.None + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("same named declarations are excluded", () => { + const json = { + include: [ + "*.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/c.d.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.None + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("`*` matches only ts files", () => { + const json = { + include: [ + "*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/c.d.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.None + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("`?` matches only a single character", () => { + const json = { + include: [ + "x/?.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/x/a.ts", + "c:/dev/x/b.ts" + ], + wildcardDirectories: { + "c:/dev/x": ts.WatchDirectoryFlags.None + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with recursive directory", () => { + const json = { + include: [ + "**/a.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/x/a.ts", + "c:/dev/x/y/a.ts", + "c:/dev/z/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with multiple recursive directories", () => { + const json = { + include: [ + "x/y/**/a.ts", + "x/**/a.ts", + "z/**/a.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/x/y/a.ts", + "c:/dev/x/a.ts", + "c:/dev/z/a.ts" + ], + wildcardDirectories: { + "c:/dev/x": ts.WatchDirectoryFlags.Recursive, + "c:/dev/z": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("case sensitive", () => { + const json = { + include: [ + "**/A.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "/dev/A.ts" + ], + wildcardDirectories: { + "/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + }); + it("with missing files are excluded", () => { + const json = { + include: [ + "*/z.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [ + ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + ], + fileNames: [], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + }); + it("always include literal files", () => { + const json = { + files: [ + "a.ts" + ], + include: [ + "*/z.ts" + ], + exclude: [ + "**/a.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("exclude folders", () => { + const json = { + include: [ + "**/*" + ], + exclude: [ + "z", + "x" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/c.d.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + describe("with common package folders", () => { + it("and no exclusions", () => { const json = { include: [ - "a.ts", - "b.ts", - "node_modules/a.ts", - "bower_components/a.ts", - "jspm_packages/a.ts" + "**/a.ts" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, errors: [], fileNames: [ "c:/dev/a.ts", - "c:/dev/b.ts", - "c:/dev/node_modules/a.ts", - "c:/dev/bower_components/a.ts", - "c:/dev/jspm_packages/a.ts" + "c:/dev/x/a.ts" ], - wildcardDirectories: {}, + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, }; validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("with common package folders and exclusions", () => { + it("and exclusions", () => { const json = { include: [ - "a.ts", - "b.ts", - "node_modules/a.ts", - "bower_components/a.ts", - "jspm_packages/a.ts" + "**/?.ts" ], exclude: [ - "a.ts", - "b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/node_modules/a.ts", - "c:/dev/bower_components/a.ts", - "c:/dev/jspm_packages/a.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("with common package folders and empty exclude", () => { - const json = { - include: [ - "a.ts", - "b.ts", - "node_modules/a.ts", - "bower_components/a.ts", - "jspm_packages/a.ts" + "a.ts" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/dev/a.ts", "c:/dev/b.ts", - "c:/dev/node_modules/a.ts", - "c:/dev/bower_components/a.ts", - "c:/dev/jspm_packages/a.ts" - ], - wildcardDirectories: {}, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - }); - - describe("with wildcard include list", () => { - it("is sorted in include order, then in alphabetical order", () => { - const json = { - include: [ - "z/*.ts", - "x/*.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/z/a.ts", - "c:/dev/z/aba.ts", - "c:/dev/z/abz.ts", - "c:/dev/z/b.ts", - "c:/dev/z/bba.ts", - "c:/dev/z/bbz.ts", - "c:/dev/x/a.ts", - "c:/dev/x/aa.ts", - "c:/dev/x/b.ts" + "c:/dev/x/a.ts" ], wildcardDirectories: { - "c:/dev/z": WatchDirectoryFlags.None, - "c:/dev/x": WatchDirectoryFlags.None + "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("same named declarations are excluded", () => { + it("and empty exclude", () => { const json = { include: [ - "*.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts", - "c:/dev/c.d.ts" + "**/a.ts" ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.None - }, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("`*` matches only ts files", () => { - const json = { - include: [ - "*" - ] + exclude: [] as string[] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, errors: [], fileNames: [ "c:/dev/a.ts", - "c:/dev/b.ts", - "c:/dev/c.d.ts" + "c:/dev/x/a.ts" ], wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.None + "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("`?` matches only a single character", () => { - const json = { - include: [ - "x/?.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/x/a.ts", - "c:/dev/x/b.ts" - ], - wildcardDirectories: { - "c:/dev/x": WatchDirectoryFlags.None - }, - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("with recursive directory", () => { + it("and explicit recursive include", () => { const json = { include: [ - "**/a.ts" + "**/a.ts", + "**/node_modules/a.ts" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, errors: [], fileNames: [ "c:/dev/a.ts", "c:/dev/x/a.ts", - "c:/dev/x/y/a.ts", - "c:/dev/z/a.ts" + "c:/dev/node_modules/a.ts" ], wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive + "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("with multiple recursive directories", () => { + it("and wildcard include", () => { const json = { include: [ - "x/y/**/a.ts", - "x/**/a.ts", - "z/**/a.ts" + "*/a.ts" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/dev/x/y/a.ts", - "c:/dev/x/a.ts", - "c:/dev/z/a.ts" + "c:/dev/x/a.ts" ], wildcardDirectories: { - "c:/dev/x": WatchDirectoryFlags.Recursive, - "c:/dev/z": WatchDirectoryFlags.Recursive + "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("case sensitive", () => { + it("and explicit wildcard include", () => { const json = { include: [ - "**/A.ts" + "*/a.ts", + "node_modules/a.ts" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "/dev/A.ts" - ], - wildcardDirectories: { - "/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); - }); - it("with missing files are excluded", () => { - const json = { - include: [ - "*/z.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + "c:/dev/x/a.ts", + "c:/dev/node_modules/a.ts" ], - fileNames: [], wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive + "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); - it("always include literal files", () => { + }); + it("exclude .js files when allowJs=false", () => { + const json = { + compilerOptions: { + allowJs: false + }, + include: [ + "js/*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: { + allowJs: false + }, + errors: [ + ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + ], + fileNames: [], + wildcardDirectories: { + "c:/dev/js": ts.WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + }); + it("include .js files when allowJs=true", () => { + const json = { + compilerOptions: { + allowJs: true + }, + include: [ + "js/*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: { + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/js/a.js", + "c:/dev/js/b.js" + ], + wildcardDirectories: { + "c:/dev/js": ts.WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("include explicitly listed .min.js files when allowJs=true", () => { + const json = { + compilerOptions: { + allowJs: true + }, + include: [ + "js/*.min.js" + ] + }; + const expected: ts.ParsedCommandLine = { + options: { + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/js/ab.min.js", + "c:/dev/js/d.min.js" + ], + wildcardDirectories: { + "c:/dev/js": ts.WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("include paths outside of the project", () => { + const json = { + include: [ + "*", + "c:/ext/*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.ts", + "c:/dev/c.d.ts", + "c:/ext/ext.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.None, + "c:/ext": ts.WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("include paths outside of the project using relative paths", () => { + const json = { + include: [ + "*", + "../ext/*" + ], + exclude: [ + "**" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/ext/ext.ts" + ], + wildcardDirectories: { + "c:/ext": ts.WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("exclude paths outside of the project using relative paths", () => { + const json = { + include: [ + "c:/**/*" + ], + exclude: [ + "../**" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [ + ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude))] + , + fileNames: [], + wildcardDirectories: {} + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); + }); + it("include files with .. in their name", () => { + const json = { + include: [ + "c:/ext/b/a..b.ts" + ], + exclude: [ + "**" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/ext/b/a..b.ts" + ], + wildcardDirectories: {} + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("exclude files with .. in their name", () => { + const json = { + include: [ + "c:/ext/**/*" + ], + exclude: [ + "c:/ext/b/a..b.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/ext/ext.ts", + ], + wildcardDirectories: { + "c:/ext": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + it("with jsx=none, allowJs=false", () => { + const json = { + compilerOptions: { + allowJs: false + } + }; + const expected: ts.ParsedCommandLine = { + options: { + allowJs: false + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("with jsx=preserve, allowJs=false", () => { + const json = { + compilerOptions: { + jsx: "preserve", + allowJs: false + } + }; + const expected: ts.ParsedCommandLine = { + options: { + jsx: ts.JsxEmit.Preserve, + allowJs: false + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("with jsx=react-native, allowJs=false", () => { + const json = { + compilerOptions: { + jsx: "react-native", + allowJs: false + } + }; + const expected: ts.ParsedCommandLine = { + options: { + jsx: ts.JsxEmit.ReactNative, + allowJs: false + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("with jsx=none, allowJs=true", () => { + const json = { + compilerOptions: { + allowJs: true + } + }; + const expected: ts.ParsedCommandLine = { + options: { + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + "c:/dev/d.js", + "c:/dev/e.jsx", + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("with jsx=preserve, allowJs=true", () => { + const json = { + compilerOptions: { + jsx: "preserve", + allowJs: true + } + }; + const expected: ts.ParsedCommandLine = { + options: { + jsx: ts.JsxEmit.Preserve, + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + "c:/dev/d.js", + "c:/dev/e.jsx", + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("with jsx=react-native, allowJs=true", () => { + const json = { + compilerOptions: { + jsx: "react-native", + allowJs: true + } + }; + const expected: ts.ParsedCommandLine = { + options: { + jsx: ts.JsxEmit.ReactNative, + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/b.tsx", + "c:/dev/c.tsx", + "c:/dev/d.js", + "c:/dev/e.jsx", + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + }); + it("exclude .min.js files using wildcards", () => { + const json = { + compilerOptions: { + allowJs: true + }, + include: [ + "js/*.min.js" + ], + exclude: [ + "js/a*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: { + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/js/d.min.js" + ], + wildcardDirectories: { + "c:/dev/js": ts.WatchDirectoryFlags.None + } + }; + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + }); + + describe("with trailing recursive directory", () => { + it("in includes", () => { const json = { - files: [ - "a.ts" - ], include: [ - "*/z.ts" - ], - exclude: [ - "**/a.ts" + "**" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts" + errors: [ + createDiagnosticForConfigFile(json, 12, 4, ts.Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**"), + ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, + fileNames: [], + wildcardDirectories: {} }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); - it("exclude folders", () => { + it("in excludes", () => { const json = { include: [ "**/*" ], exclude: [ - "z", - "x" + "**" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts", - "c:/dev/c.d.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - describe("with common package folders", () => { - it("and no exclusions", () => { - const json = { - include: [ - "**/a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("and exclusions", () => { - const json = { - include: [ - "**/?.ts" - ], - exclude: [ - "a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/b.ts", - "c:/dev/x/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("and empty exclude", () => { - const json = { - include: [ - "**/a.ts" - ], - exclude: [] as string[] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("and explicit recursive include", () => { - const json = { - include: [ - "**/a.ts", - "**/node_modules/a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts", - "c:/dev/node_modules/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("and wildcard include", () => { - const json = { - include: [ - "*/a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/x/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - it("and explicit wildcard include", () => { - const json = { - include: [ - "*/a.ts", - "node_modules/a.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/x/a.ts", - "c:/dev/node_modules/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - }, - }; - validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - }); - }); - it("exclude .js files when allowJs=false", () => { - const json = { - compilerOptions: { - allowJs: false - }, - include: [ - "js/*" - ] - }; - const expected: ParsedCommandLine = { - options: { - allowJs: false - }, errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude)) ], fileNames: [], - wildcardDirectories: { - "c:/dev/js": WatchDirectoryFlags.None - } + wildcardDirectories: {} }; validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); - it("include .js files when allowJs=true", () => { - const json = { - compilerOptions: { - allowJs: true - }, - include: [ - "js/*" - ] - }; - const expected: ParsedCommandLine = { - options: { - allowJs: true - }, - errors: [], - fileNames: [ - "c:/dev/js/a.js", - "c:/dev/js/b.js" - ], - wildcardDirectories: { - "c:/dev/js": WatchDirectoryFlags.None - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("include explicitly listed .min.js files when allowJs=true", () => { - const json = { - compilerOptions: { - allowJs: true - }, - include: [ - "js/*.min.js" - ] - }; - const expected: ParsedCommandLine = { - options: { - allowJs: true - }, - errors: [], - fileNames: [ - "c:/dev/js/ab.min.js", - "c:/dev/js/d.min.js" - ], - wildcardDirectories: { - "c:/dev/js": WatchDirectoryFlags.None - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("include paths outside of the project", () => { + }); + describe("with multiple recursive directory patterns", () => { + it("in includes", () => { const json = { include: [ - "*", - "c:/ext/*" + "**/x/**/*" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.ts", - "c:/dev/c.d.ts", - "c:/ext/ext.ts" + "c:/dev/x/a.ts", + "c:/dev/x/aa.ts", + "c:/dev/x/b.ts", + "c:/dev/x/y/a.ts", + "c:/dev/x/y/b.ts", ], wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.None, - "c:/ext": WatchDirectoryFlags.None + "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); - it("include paths outside of the project using relative paths", () => { + it("in excludes", () => { const json = { include: [ - "*", - "../ext/*" + "**/a.ts" ], exclude: [ - "**" + "**/x/**" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/ext/ext.ts" + "c:/dev/a.ts", + "c:/dev/z/a.ts" ], wildcardDirectories: { - "c:/ext": WatchDirectoryFlags.None + "c:/dev": ts.WatchDirectoryFlags.Recursive } }; validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - it("exclude paths outside of the project using relative paths", () => { + }); + + describe("with parent directory symbols after a recursive directory pattern", () => { + it("in includes immediately after", () => { const json = { include: [ - "c:/**/*" - ], - exclude: [ - "../**" + "**/../*" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude))] - , + createDiagnosticForConfigFile(json, 12, 9, ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/../*"), + ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") + ], fileNames: [], wildcardDirectories: {} }; validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); - it("include files with .. in their name", () => { + + it("in includes after a subdirectory", () => { const json = { include: [ - "c:/ext/b/a..b.ts" - ], - exclude: [ - "**" + "**/y/../*" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, - errors: [], - fileNames: [ - "c:/ext/b/a..b.ts" + errors: [ + createDiagnosticForConfigFile(json, 12, 11, ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/../*"), + ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") ], + fileNames: [], wildcardDirectories: {} }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); - it("exclude files with .. in their name", () => { + + it("in excludes immediately after", () => { const json = { include: [ - "c:/ext/**/*" + "**/a.ts" ], exclude: [ - "c:/ext/b/a..b.ts" + "**/.." ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, - errors: [], - fileNames: [ - "c:/ext/ext.ts", - ], - wildcardDirectories: { - "c:/ext": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - it("with jsx=none, allowJs=false", () => { - const json = { - compilerOptions: { - allowJs: false - } - }; - const expected: ParsedCommandLine = { - options: { - allowJs: false - }, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", + errors: [ + createDiagnosticForConfigFile(json, 34, 7, ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/..") ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - }); - it("with jsx=preserve, allowJs=false", () => { - const json = { - compilerOptions: { - jsx: "preserve", - allowJs: false - } - }; - const expected: ParsedCommandLine = { - options: { - jsx: JsxEmit.Preserve, - allowJs: false - }, - errors: [], fileNames: [ "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", + "c:/dev/x/a.ts", + "c:/dev/x/y/a.ts", + "c:/dev/z/a.ts" ], wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive + "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - it("with jsx=react-native, allowJs=false", () => { + + it("in excludes after a subdirectory", () => { const json = { - compilerOptions: { - jsx: "react-native", - allowJs: false - } - }; - const expected: ParsedCommandLine = { - options: { - jsx: JsxEmit.ReactNative, - allowJs: false - }, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", + include: [ + "**/a.ts" ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - }); - it("with jsx=none, allowJs=true", () => { - const json = { - compilerOptions: { - allowJs: true - } + exclude: [ + "**/y/.." + ] }; - const expected: ParsedCommandLine = { - options: { - allowJs: true - }, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", - "c:/dev/d.js", - "c:/dev/e.jsx", + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [ + createDiagnosticForConfigFile(json, 34, 9, ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/..") ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - }); - it("with jsx=preserve, allowJs=true", () => { - const json = { - compilerOptions: { - jsx: "preserve", - allowJs: true - } - }; - const expected: ParsedCommandLine = { - options: { - jsx: JsxEmit.Preserve, - allowJs: true - }, - errors: [], fileNames: [ "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", - "c:/dev/d.js", - "c:/dev/e.jsx", + "c:/dev/x/a.ts", + "c:/dev/x/y/a.ts", + "c:/dev/z/a.ts" ], wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive + "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - it("with jsx=react-native, allowJs=true", () => { + }); + + describe("with implicit globbification", () => { + it("Expands 'z' to 'z/**/*'", () => { const json = { - compilerOptions: { - jsx: "react-native", - allowJs: true - } + include: ["z"] }; - const expected: ParsedCommandLine = { - options: { - jsx: JsxEmit.ReactNative, - allowJs: true - }, + const expected: ts.ParsedCommandLine = { + options: {}, errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/b.tsx", - "c:/dev/c.tsx", - "c:/dev/d.js", - "c:/dev/e.jsx", - ], + fileNames: [ "a.ts", "aba.ts", "abz.ts", "b.ts", "bba.ts", "bbz.ts" ].map(x => `c:/dev/z/${x}`), wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive + "c:/dev/z": ts.WatchDirectoryFlags.Recursive } }; - validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); - it("exclude .min.js files using wildcards", () => { + }); + }); + + describe("with files or folders that begin with a .", () => { + it("that are not explicitly included", () => { + const json = { + include: [ + "x/**/*", + "w/*/*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/x/d.ts", + "c:/dev/x/y/d.ts", + ], + wildcardDirectories: { + "c:/dev/x": ts.WatchDirectoryFlags.Recursive, + "c:/dev/w": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); + }); + describe("that are explicitly included", () => { + it("without wildcards", () => { const json = { - compilerOptions: { - allowJs: true - }, include: [ - "js/*.min.js" - ], - exclude: [ - "js/a*" + "x/.y/a.ts", + "c:/dev/.z/.b.ts" ] }; - const expected: ParsedCommandLine = { - options: { - allowJs: true - }, + const expected: ts.ParsedCommandLine = { + options: {}, errors: [], fileNames: [ - "c:/dev/js/d.min.js" + "c:/dev/x/.y/a.ts", + "c:/dev/.z/.b.ts" ], - wildcardDirectories: { - "c:/dev/js": WatchDirectoryFlags.None - } + wildcardDirectories: {} }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - - describe("with trailing recursive directory", () => { - it("in includes", () => { - const json = { - include: [ - "**" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createDiagnosticForConfigFile(json, 12, 4, Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**"), - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") - ], - fileNames: [], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - it("in excludes", () => { - const json = { - include: [ - "**/*" - ], - exclude: [ - "**" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude)) - ], - fileNames: [], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - }); - describe("with multiple recursive directory patterns", () => { - it("in includes", () => { - const json = { - include: [ - "**/x/**/*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/x/a.ts", - "c:/dev/x/aa.ts", - "c:/dev/x/b.ts", - "c:/dev/x/y/a.ts", - "c:/dev/x/y/b.ts", - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - it("in excludes", () => { - const json = { - include: [ - "**/a.ts" - ], - exclude: [ - "**/x/**" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/z/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - }); - - describe("with parent directory symbols after a recursive directory pattern", () => { - it("in includes immediately after", () => { - const json = { - include: [ - "**/../*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createDiagnosticForConfigFile(json, 12, 9, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/../*"), - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") - ], - fileNames: [], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - - it("in includes after a subdirectory", () => { - const json = { - include: [ - "**/y/../*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createDiagnosticForConfigFile(json, 12, 11, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/../*"), - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") - ], - fileNames: [], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - - it("in excludes immediately after", () => { - const json = { - include: [ - "**/a.ts" - ], - exclude: [ - "**/.." - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createDiagnosticForConfigFile(json, 34, 7, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/..") - ], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts", - "c:/dev/x/y/a.ts", - "c:/dev/z/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - - it("in excludes after a subdirectory", () => { - const json = { - include: [ - "**/a.ts" - ], - exclude: [ - "**/y/.." - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createDiagnosticForConfigFile(json, 34, 9, Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/..") - ], - fileNames: [ - "c:/dev/a.ts", - "c:/dev/x/a.ts", - "c:/dev/x/y/a.ts", - "c:/dev/z/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); - }); - - describe("with implicit globbification", () => { - it("Expands 'z' to 'z/**/*'", () => { - const json = { - include: ["z"] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ "a.ts", "aba.ts", "abz.ts", "b.ts", "bba.ts", "bbz.ts" ].map(x => `c:/dev/z/${x}`), - wildcardDirectories: { - "c:/dev/z": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); - }); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); }); - }); - - describe("with files or folders that begin with a .", () => { - it("that are not explicitly included", () => { + it("with recursive wildcards that match directories", () => { const json = { include: [ - "x/**/*", - "w/*/*" + "**/.*/*" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "c:/dev/x/d.ts", - "c:/dev/x/y/d.ts", + "c:/dev/.z/c.ts", + "c:/dev/g.min.js/.g/g.ts", + "c:/dev/w/.u/e.ts", + "c:/dev/x/.y/a.ts" ], wildcardDirectories: { - "c:/dev/x": WatchDirectoryFlags.Recursive, - "c:/dev/w": WatchDirectoryFlags.Recursive + "c:/dev": ts.WatchDirectoryFlags.Recursive } }; validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); }); - describe("that are explicitly included", () => { - it("without wildcards", () => { - const json = { - include: [ - "x/.y/a.ts", - "c:/dev/.z/.b.ts" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/x/.y/a.ts", - "c:/dev/.z/.b.ts" - ], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); - }); - it("with recursive wildcards that match directories", () => { - const json = { - include: [ - "**/.*/*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/.z/c.ts", - "c:/dev/g.min.js/.g/g.ts", - "c:/dev/w/.u/e.ts", - "c:/dev/x/.y/a.ts" - ], - wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); - }); - it("with recursive wildcards that match nothing", () => { - const json = { - include: [ - "x/**/.y/*", - ".z/**/.*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [], - fileNames: [ - "c:/dev/x/.y/a.ts", - "c:/dev/.z/.b.ts" - ], - wildcardDirectories: { - "c:/dev/.z": WatchDirectoryFlags.Recursive, - "c:/dev/x": WatchDirectoryFlags.Recursive - } - }; - validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); - }); - it("with wildcard excludes that implicitly exclude dotted files", () => { - const json = { - include: [ - "**/.*/*" - ], - exclude: [ - "**/*" - ] - }; - const expected: ParsedCommandLine = { - options: {}, - errors: [ - createCompilerDiagnostic(Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, - caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude)) - ], - fileNames: [], - wildcardDirectories: {} - }; - validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - }); - }); - }); - - describe("exclude or include patterns which start with **", () => { - it("can exclude dirs whose pattern starts with **", () => { + it("with recursive wildcards that match nothing", () => { const json = { - exclude: [ - "**/x" + include: [ + "x/**/.y/*", + ".z/**/.*" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, errors: [], fileNames: [ - "/dev/A.ts", - "/dev/B.ts", - "/dev/a.ts", - "/dev/b.ts", - "/dev/c.d.ts", - "/dev/q/a/c/b/d.ts", - "/dev/z/a.ts", - "/dev/z/aba.ts", - "/dev/z/abz.ts", - "/dev/z/b.ts", - "/dev/z/bba.ts", - "/dev/z/bbz.ts", + "c:/dev/x/.y/a.ts", + "c:/dev/.z/.b.ts" ], wildcardDirectories: { - "/dev": WatchDirectoryFlags.Recursive + "c:/dev/.z": ts.WatchDirectoryFlags.Recursive, + "c:/dev/x": ts.WatchDirectoryFlags.Recursive } }; - validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); }); - it("can include dirs whose pattern starts with **", () => { + it("with wildcard excludes that implicitly exclude dotted files", () => { const json = { include: [ - "**/x", - "**/a/**/b" + "**/.*/*" + ], + exclude: [ + "**/*" ] }; - const expected: ParsedCommandLine = { + const expected: ts.ParsedCommandLine = { options: {}, - errors: [], - fileNames: [ - "/dev/x/a.ts", - "/dev/x/b.ts", - "/dev/x/y/a.ts", - "/dev/x/y/b.ts", - "/dev/q/a/c/b/d.ts", + errors: [ + ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, + caseInsensitiveTsconfigPath, JSON.stringify(json.include), JSON.stringify(json.exclude)) ], - wildcardDirectories: { - "/dev": WatchDirectoryFlags.Recursive - } + fileNames: [], + wildcardDirectories: {} }; - validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); }); + }); - it("can include files in the same order on multiple platforms", () => { - function getExpected(basePath: string): ParsedCommandLine { - return { - options: {}, - errors: [], - fileNames: [ - `${basePath}Yosemite.ts`, // capital always comes before lowercase letters - `${basePath}xylophone.ts`, - `${basePath}zebra.ts` - ], - wildcardDirectories: { - [basePath.slice(0, basePath.length - 1)]: WatchDirectoryFlags.Recursive - }, - }; - } - const json = {}; - validateMatches(getExpected(caseSensitiveBasePath), json, caseSensitiveOrderingDiffersWithCaseHost, caseSensitiveBasePath); - validateMatches(getExpected(caseInsensitiveBasePath), json, caseInsensitiveOrderingDiffersWithCaseHost, caseInsensitiveBasePath); + describe("exclude or include patterns which start with **", () => { + it("can exclude dirs whose pattern starts with **", () => { + const json = { + exclude: [ + "**/x" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "/dev/A.ts", + "/dev/B.ts", + "/dev/a.ts", + "/dev/b.ts", + "/dev/c.d.ts", + "/dev/q/a/c/b/d.ts", + "/dev/z/a.ts", + "/dev/z/aba.ts", + "/dev/z/abz.ts", + "/dev/z/b.ts", + "/dev/z/bba.ts", + "/dev/z/bbz.ts", + ], + wildcardDirectories: { + "/dev": ts.WatchDirectoryFlags.Recursive + } + }; + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); }); - - it("when recursive symlinked directories are present", () => { - const fs = new vfs.FileSystem(/*ignoreCase*/ true, { - cwd: caseInsensitiveBasePath, files: { - "c:/dev/index.ts": "" + it("can include dirs whose pattern starts with **", () => { + const json = { + include: [ + "**/x", + "**/a/**/b" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "/dev/x/a.ts", + "/dev/x/b.ts", + "/dev/x/y/a.ts", + "/dev/x/y/b.ts", + "/dev/q/a/c/b/d.ts", + ], + wildcardDirectories: { + "/dev": ts.WatchDirectoryFlags.Recursive } - }); - fs.mkdirpSync("c:/dev/a/b/c"); - fs.symlinkSync("c:/dev/A", "c:/dev/a/self"); - fs.symlinkSync("c:/dev/a", "c:/dev/a/b/parent"); - fs.symlinkSync("c:/dev/a", "c:/dev/a/b/c/grandparent"); - const host = new fakes.ParseConfigHost(fs); - const json = {}; - const expected: ParsedCommandLine = { + }; + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); + }); + }); + + it("can include files in the same order on multiple platforms", () => { + function getExpected(basePath: string): ts.ParsedCommandLine { + return { options: {}, errors: [], fileNames: [ - "c:/dev/index.ts" + `${basePath}Yosemite.ts`, // capital always comes before lowercase letters + `${basePath}xylophone.ts`, + `${basePath}zebra.ts` ], wildcardDirectories: { - "c:/dev": WatchDirectoryFlags.Recursive + [basePath.slice(0, basePath.length - 1)]: ts.WatchDirectoryFlags.Recursive }, }; - validateMatches(expected, json, host, caseInsensitiveBasePath); + } + const json = {}; + validateMatches(getExpected(caseSensitiveBasePath), json, caseSensitiveOrderingDiffersWithCaseHost, caseSensitiveBasePath); + validateMatches(getExpected(caseInsensitiveBasePath), json, caseInsensitiveOrderingDiffersWithCaseHost, caseInsensitiveBasePath); + }); + + it("when recursive symlinked directories are present", () => { + const fs = new vfs.FileSystem(/*ignoreCase*/ true, { + cwd: caseInsensitiveBasePath, files: { + "c:/dev/index.ts": "" + } }); + fs.mkdirpSync("c:/dev/a/b/c"); + fs.symlinkSync("c:/dev/A", "c:/dev/a/self"); + fs.symlinkSync("c:/dev/a", "c:/dev/a/b/parent"); + fs.symlinkSync("c:/dev/a", "c:/dev/a/b/c/grandparent"); + const host = new fakes.ParseConfigHost(fs); + const json = {}; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/index.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + }, + }; + validateMatches(expected, json, host, caseInsensitiveBasePath); }); -} +}); diff --git a/src/testRunner/unittests/config/projectReferences.ts b/src/testRunner/unittests/config/projectReferences.ts index a70329a8db8a2..e45fbb59abd04 100644 --- a/src/testRunner/unittests/config/projectReferences.ts +++ b/src/testRunner/unittests/config/projectReferences.ts @@ -1,352 +1,354 @@ -namespace ts { - interface TestProjectSpecification { - configFileName?: string; - references?: readonly (string | ProjectReference)[]; - files: { [fileName: string]: string }; - outputFiles?: { [fileName: string]: string }; - config?: object; - options?: Partial; - } - interface TestSpecification { - [path: string]: TestProjectSpecification; - } +import * as ts from "../../_namespaces/ts"; +import * as fakes from "../../_namespaces/fakes"; +import * as vfs from "../../_namespaces/vfs"; - function assertHasError(message: string, errors: readonly Diagnostic[], diag: DiagnosticMessage) { - if (!errors.some(e => e.code === diag.code)) { - const errorString = errors.map(e => ` ${e.file ? e.file.fileName : "[global]"}: ${e.messageText}`).join("\r\n"); - assert(false, `${message}: Did not find any diagnostic for ${diag.message} in:\r\n${errorString}`); - } +interface TestProjectSpecification { + configFileName?: string; + references?: readonly (string | ts.ProjectReference)[]; + files: { [fileName: string]: string }; + outputFiles?: { [fileName: string]: string }; + config?: object; + options?: Partial; +} +interface TestSpecification { + [path: string]: TestProjectSpecification; +} + +function assertHasError(message: string, errors: readonly ts.Diagnostic[], diag: ts.DiagnosticMessage) { + if (!errors.some(e => e.code === diag.code)) { + const errorString = errors.map(e => ` ${e.file ? e.file.fileName : "[global]"}: ${e.messageText}`).join("\r\n"); + assert(false, `${message}: Did not find any diagnostic for ${diag.message} in:\r\n${errorString}`); } +} - function assertNoErrors(message: string, errors: readonly Diagnostic[]) { - if (errors && errors.length > 0) { - assert(false, `${message}: Expected no errors, but found:\r\n${errors.map(e => ` ${e.messageText}`).join("\r\n")}`); - } +function assertNoErrors(message: string, errors: readonly ts.Diagnostic[]) { + if (errors && errors.length > 0) { + assert(false, `${message}: Expected no errors, but found:\r\n${errors.map(e => ` ${e.messageText}`).join("\r\n")}`); } +} - function combineAllPaths(...paths: string[]) { - let result = paths[0]; - for (let i = 1; i < paths.length; i++) { - result = combinePaths(result, paths[i]); - } - return result; +function combineAllPaths(...paths: string[]) { + let result = paths[0]; + for (let i = 1; i < paths.length; i++) { + result = ts.combinePaths(result, paths[i]); } + return result; +} - const emptyModule = "export { };"; +const emptyModule = "export { };"; - /** - * Produces the text of a source file which imports all of the - * specified module names - */ - function moduleImporting(...names: string[]) { - return names.map((n, i) => `import * as mod_${i} from ${n}`).join("\r\n"); - } +/** + * Produces the text of a source file which imports all of the + * specified module names + */ +function moduleImporting(...names: string[]) { + return names.map((n, i) => `import * as mod_${i} from ${n}`).join("\r\n"); +} - function testProjectReferences(spec: TestSpecification, entryPointConfigFileName: string, checkResult: (prog: Program, host: fakes.CompilerHost) => void) { - const files = new Map(); - for (const key in spec) { - const sp = spec[key]; - const configFileName = combineAllPaths("/", key, sp.configFileName || "tsconfig.json"); - const options = { - compilerOptions: { - composite: true, - outDir: "bin", - ...sp.options - }, - references: (sp.references || []).map(r => { - if (typeof r === "string") { - return { path: r }; - } - return r; - }), - ...sp.config - }; - const configContent = JSON.stringify(options); - const outDir = options.compilerOptions.outDir; - files.set(configFileName, configContent); - for (const sourceFile of Object.keys(sp.files)) { - files.set(sourceFile, sp.files[sourceFile]); - } - if (sp.outputFiles) { - for (const outFile of Object.keys(sp.outputFiles)) { - files.set(combineAllPaths("/", key, outDir, outFile), sp.outputFiles[outFile]); +function testProjectReferences(spec: TestSpecification, entryPointConfigFileName: string, checkResult: (prog: ts.Program, host: fakes.CompilerHost) => void) { + const files = new ts.Map(); + for (const key in spec) { + const sp = spec[key]; + const configFileName = combineAllPaths("/", key, sp.configFileName || "tsconfig.json"); + const options = { + compilerOptions: { + composite: true, + outDir: "bin", + ...sp.options + }, + references: (sp.references || []).map(r => { + if (typeof r === "string") { + return { path: r }; } + return r; + }), + ...sp.config + }; + const configContent = JSON.stringify(options); + const outDir = options.compilerOptions.outDir; + files.set(configFileName, configContent); + for (const sourceFile of Object.keys(sp.files)) { + files.set(sourceFile, sp.files[sourceFile]); + } + if (sp.outputFiles) { + for (const outFile of Object.keys(sp.outputFiles)) { + files.set(combineAllPaths("/", key, outDir, outFile), sp.outputFiles[outFile]); } } + } - const vfsys = new vfs.FileSystem(false, { files: { "/lib.d.ts": TestFSWithWatch.libFile.content } }); - files.forEach((v, k) => { - vfsys.mkdirpSync(getDirectoryPath(k)); - vfsys.writeFileSync(k, v); - }); - const host = new fakes.CompilerHost(new fakes.System(vfsys)); + const vfsys = new vfs.FileSystem(false, { files: { "/lib.d.ts": ts.TestFSWithWatch.libFile.content } }); + files.forEach((v, k) => { + vfsys.mkdirpSync(ts.getDirectoryPath(k)); + vfsys.writeFileSync(k, v); + }); + const host = new fakes.CompilerHost(new fakes.System(vfsys)); - const { config, error } = readConfigFile(entryPointConfigFileName, name => host.readFile(name)); + const { config, error } = ts.readConfigFile(entryPointConfigFileName, name => host.readFile(name)); - // We shouldn't have any errors about invalid tsconfig files in these tests - assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); - const file = parseJsonConfigFileContent(config, parseConfigHostFromCompilerHostLike(host), getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName); - file.options.configFilePath = entryPointConfigFileName; - const prog = createProgram({ - rootNames: file.fileNames, - options: file.options, - host, - projectReferences: file.projectReferences - }); - checkResult(prog, host); - } + // We shouldn't have any errors about invalid tsconfig files in these tests + assert(config && !error, ts.flattenDiagnosticMessageText(error && error.messageText, "\n")); + const file = ts.parseJsonConfigFileContent(config, ts.parseConfigHostFromCompilerHostLike(host), ts.getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName); + file.options.configFilePath = entryPointConfigFileName; + const prog = ts.createProgram({ + rootNames: file.fileNames, + options: file.options, + host, + projectReferences: file.projectReferences + }); + checkResult(prog, host); +} - describe("unittests:: config:: project-references meta check", () => { - it("default setup was created correctly", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: [] - }, - "/reference": { - files: { "/secondary/b.ts": moduleImporting("../primary/a") }, - references: ["../primary"] - } - }; - testProjectReferences(spec, "/primary/tsconfig.json", prog => { - assert.isTrue(!!prog, "Program should exist"); - assertNoErrors("Sanity check should not produce errors", prog.getOptionsDiagnostics()); - }); +describe("unittests:: config:: project-references meta check", () => { + it("default setup was created correctly", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: [] + }, + "/reference": { + files: { "/secondary/b.ts": moduleImporting("../primary/a") }, + references: ["../primary"] + } + }; + testProjectReferences(spec, "/primary/tsconfig.json", prog => { + assert.isTrue(!!prog, "Program should exist"); + assertNoErrors("Sanity check should not produce errors", prog.getOptionsDiagnostics()); }); }); +}); - /** - * Validate that we enforce the basic settings constraints for referenced projects - */ - describe("unittests:: config:: project-references constraint checking for settings", () => { - it("errors when declaration = false", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: [], - options: { - declaration: false - } +/** + * Validate that we enforce the basic settings constraints for referenced projects + */ +describe("unittests:: config:: project-references constraint checking for settings", () => { + it("errors when declaration = false", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: [], + options: { + declaration: false } - }; + } + }; - testProjectReferences(spec, "/primary/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertHasError("Reports an error about the wrong decl setting", errs, Diagnostics.Composite_projects_may_not_disable_declaration_emit); - }); + testProjectReferences(spec, "/primary/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertHasError("Reports an error about the wrong decl setting", errs, ts.Diagnostics.Composite_projects_may_not_disable_declaration_emit); }); + }); - it("errors when the referenced project doesn't have composite:true", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: [], - options: { - composite: false - } - }, - "/reference": { - files: { "/secondary/b.ts": moduleImporting("../primary/a") }, - references: ["../primary"], - config: { - files: ["b.ts"] - } + it("errors when the referenced project doesn't have composite:true", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: [], + options: { + composite: false } - }; - testProjectReferences(spec, "/reference/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertHasError("Reports an error about 'composite' not being set", errs, Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true); - }); + }, + "/reference": { + files: { "/secondary/b.ts": moduleImporting("../primary/a") }, + references: ["../primary"], + config: { + files: ["b.ts"] + } + } + }; + testProjectReferences(spec, "/reference/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertHasError("Reports an error about 'composite' not being set", errs, ts.Diagnostics.Referenced_project_0_must_have_setting_composite_Colon_true); }); + }); - it("does not error when the referenced project doesn't have composite:true if its a container project", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: [], - options: { - composite: false - } - }, - "/reference": { - files: { "/secondary/b.ts": moduleImporting("../primary/a") }, - references: ["../primary"], + it("does not error when the referenced project doesn't have composite:true if its a container project", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: [], + options: { + composite: false } - }; - testProjectReferences(spec, "/reference/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertNoErrors("Reports an error about 'composite' not being set", errs); - }); + }, + "/reference": { + files: { "/secondary/b.ts": moduleImporting("../primary/a") }, + references: ["../primary"], + } + }; + testProjectReferences(spec, "/reference/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertNoErrors("Reports an error about 'composite' not being set", errs); }); + }); - it("errors when the file list is not exhaustive", () => { - const spec: TestSpecification = { - "/primary": { - files: { - "/primary/a.ts": "import * as b from './b'", - "/primary/b.ts": "export {}" - }, - config: { - files: ["a.ts"] - } + it("errors when the file list is not exhaustive", () => { + const spec: TestSpecification = { + "/primary": { + files: { + "/primary/a.ts": "import * as b from './b'", + "/primary/b.ts": "export {}" + }, + config: { + files: ["a.ts"] } - }; + } + }; - testProjectReferences(spec, "/primary/tsconfig.json", program => { - const errs = program.getSemanticDiagnostics(program.getSourceFile("/primary/a.ts")); - assertHasError("Reports an error about b.ts not being in the list", errs, Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern); - }); + testProjectReferences(spec, "/primary/tsconfig.json", program => { + const errs = program.getSemanticDiagnostics(program.getSourceFile("/primary/a.ts")); + assertHasError("Reports an error about b.ts not being in the list", errs, ts.Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern); }); + }); - it("errors when the referenced project doesn't exist", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: ["../foo"] - } - }; - testProjectReferences(spec, "/primary/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertHasError("Reports an error about a missing file", errs, Diagnostics.File_0_not_found); - }); + it("errors when the referenced project doesn't exist", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: ["../foo"] + } + }; + testProjectReferences(spec, "/primary/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertHasError("Reports an error about a missing file", errs, ts.Diagnostics.File_0_not_found); }); + }); - it("errors when a prepended project reference doesn't set outFile", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": emptyModule }, - references: [{ path: "../someProj", prepend: true }] - }, - "/someProj": { - files: { "/someProj/b.ts": "const x = 100;" } - } - }; - testProjectReferences(spec, "/primary/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertHasError("Reports an error about outFile not being set", errs, Diagnostics.Cannot_prepend_project_0_because_it_does_not_have_outFile_set); - }); + it("errors when a prepended project reference doesn't set outFile", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": emptyModule }, + references: [{ path: "../someProj", prepend: true }] + }, + "/someProj": { + files: { "/someProj/b.ts": "const x = 100;" } + } + }; + testProjectReferences(spec, "/primary/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertHasError("Reports an error about outFile not being set", errs, ts.Diagnostics.Cannot_prepend_project_0_because_it_does_not_have_outFile_set); }); + }); - it("errors when a prepended project reference output doesn't exist", () => { - const spec: TestSpecification = { - "/primary": { - files: { "/primary/a.ts": "const y = x;" }, - references: [{ path: "../someProj", prepend: true }] - }, - "/someProj": { - files: { "/someProj/b.ts": "const x = 100;" }, - options: { outFile: "foo.js" } - } - }; - testProjectReferences(spec, "/primary/tsconfig.json", program => { - const errs = program.getOptionsDiagnostics(); - assertHasError("Reports an error about outFile being missing", errs, Diagnostics.Output_file_0_from_project_1_does_not_exist); - }); + it("errors when a prepended project reference output doesn't exist", () => { + const spec: TestSpecification = { + "/primary": { + files: { "/primary/a.ts": "const y = x;" }, + references: [{ path: "../someProj", prepend: true }] + }, + "/someProj": { + files: { "/someProj/b.ts": "const x = 100;" }, + options: { outFile: "foo.js" } + } + }; + testProjectReferences(spec, "/primary/tsconfig.json", program => { + const errs = program.getOptionsDiagnostics(); + assertHasError("Reports an error about outFile being missing", errs, ts.Diagnostics.Output_file_0_from_project_1_does_not_exist); }); }); +}); - /** - * Path mapping behavior - */ - describe("unittests:: config:: project-references path mapping", () => { - it("redirects to the output .d.ts file", () => { - const spec: TestSpecification = { - "/alpha": { - files: { "/alpha/a.ts": "export const m: number = 3;" }, - references: [], - outputFiles: { "a.d.ts": emptyModule } - }, - "/beta": { - files: { "/beta/b.ts": "import { m } from '../alpha/a'" }, - references: ["../alpha"] - } - }; - testProjectReferences(spec, "/beta/tsconfig.json", program => { - assertNoErrors("File setup should be correct", program.getOptionsDiagnostics()); - assertHasError("Found a type error", program.getSemanticDiagnostics(), Diagnostics.Module_0_has_no_exported_member_1); - }); +/** + * Path mapping behavior + */ +describe("unittests:: config:: project-references path mapping", () => { + it("redirects to the output .d.ts file", () => { + const spec: TestSpecification = { + "/alpha": { + files: { "/alpha/a.ts": "export const m: number = 3;" }, + references: [], + outputFiles: { "a.d.ts": emptyModule } + }, + "/beta": { + files: { "/beta/b.ts": "import { m } from '../alpha/a'" }, + references: ["../alpha"] + } + }; + testProjectReferences(spec, "/beta/tsconfig.json", program => { + assertNoErrors("File setup should be correct", program.getOptionsDiagnostics()); + assertHasError("Found a type error", program.getSemanticDiagnostics(), ts.Diagnostics.Module_0_has_no_exported_member_1); }); }); +}); - describe("unittests:: config:: project-references nice-behavior", () => { - it("issues a nice error when the input file is missing", () => { - const spec: TestSpecification = { - "/alpha": { - files: { "/alpha/a.ts": "export const m: number = 3;" }, - references: [] - }, - "/beta": { - files: { "/beta/b.ts": "import { m } from '../alpha/a'" }, - references: ["../alpha"] - } - }; - testProjectReferences(spec, "/beta/tsconfig.json", program => { - assertHasError("Issues a useful error", program.getSemanticDiagnostics(), Diagnostics.Output_file_0_has_not_been_built_from_source_file_1); - }); +describe("unittests:: config:: project-references nice-behavior", () => { + it("issues a nice error when the input file is missing", () => { + const spec: TestSpecification = { + "/alpha": { + files: { "/alpha/a.ts": "export const m: number = 3;" }, + references: [] + }, + "/beta": { + files: { "/beta/b.ts": "import { m } from '../alpha/a'" }, + references: ["../alpha"] + } + }; + testProjectReferences(spec, "/beta/tsconfig.json", program => { + assertHasError("Issues a useful error", program.getSemanticDiagnostics(), ts.Diagnostics.Output_file_0_has_not_been_built_from_source_file_1); }); + }); - it("issues a nice error when the input file is missing when module reference is not relative", () => { - const spec: TestSpecification = { - "/alpha": { - files: { "/alpha/a.ts": "export const m: number = 3;" }, - references: [] - }, - "/beta": { - files: { "/beta/b.ts": "import { m } from '@alpha/a'" }, - references: ["../alpha"], - options: { - baseUrl: "./", - paths: { - "@alpha/*": ["/alpha/*"] - } + it("issues a nice error when the input file is missing when module reference is not relative", () => { + const spec: TestSpecification = { + "/alpha": { + files: { "/alpha/a.ts": "export const m: number = 3;" }, + references: [] + }, + "/beta": { + files: { "/beta/b.ts": "import { m } from '@alpha/a'" }, + references: ["../alpha"], + options: { + baseUrl: "./", + paths: { + "@alpha/*": ["/alpha/*"] } } - }; - testProjectReferences(spec, "/beta/tsconfig.json", program => { - assertHasError("Issues a useful error", program.getSemanticDiagnostics(), Diagnostics.Output_file_0_has_not_been_built_from_source_file_1); - }); + } + }; + testProjectReferences(spec, "/beta/tsconfig.json", program => { + assertHasError("Issues a useful error", program.getSemanticDiagnostics(), ts.Diagnostics.Output_file_0_has_not_been_built_from_source_file_1); }); }); +}); - /** - * 'composite' behavior - */ - describe("unittests:: config:: project-references behavior changes under composite: true", () => { - it("doesn't infer the rootDir from source paths", () => { - const spec: TestSpecification = { - "/alpha": { - files: { "/alpha/src/a.ts": "export const m: number = 3;" }, - options: { - declaration: true, - outDir: "bin" - }, - references: [] - } - }; - testProjectReferences(spec, "/alpha/tsconfig.json", (program, host) => { - program.emit(); - assert.deepEqual(host.outputs.map(e => e.file).sort(), ["/alpha/bin/src/a.d.ts", "/alpha/bin/src/a.js", "/alpha/bin/tsconfig.tsbuildinfo"]); - }); +/** + * 'composite' behavior + */ +describe("unittests:: config:: project-references behavior changes under composite: true", () => { + it("doesn't infer the rootDir from source paths", () => { + const spec: TestSpecification = { + "/alpha": { + files: { "/alpha/src/a.ts": "export const m: number = 3;" }, + options: { + declaration: true, + outDir: "bin" + }, + references: [] + } + }; + testProjectReferences(spec, "/alpha/tsconfig.json", (program, host) => { + program.emit(); + assert.deepEqual(host.outputs.map(e => e.file).sort(), ["/alpha/bin/src/a.d.ts", "/alpha/bin/src/a.js", "/alpha/bin/tsconfig.tsbuildinfo"]); }); }); +}); - describe("unittests:: config:: project-references errors when a file in a composite project occurs outside the root", () => { - it("Errors when a file is outside the rootdir", () => { - const spec: TestSpecification = { - "/alpha": { - files: { "/alpha/src/a.ts": "import * from '../../beta/b'", "/beta/b.ts": "export { }" }, - options: { - declaration: true, - outDir: "bin" - }, - references: [] - } - }; - testProjectReferences(spec, "/alpha/tsconfig.json", (program) => { - const semanticDiagnostics = program.getSemanticDiagnostics(program.getSourceFile("/alpha/src/a.ts")); - assertHasError("Issues an error about the rootDir", semanticDiagnostics, Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files); - assertHasError("Issues an error about the fileList", semanticDiagnostics, Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern); - }); +describe("unittests:: config:: project-references errors when a file in a composite project occurs outside the root", () => { + it("Errors when a file is outside the rootdir", () => { + const spec: TestSpecification = { + "/alpha": { + files: { "/alpha/src/a.ts": "import * from '../../beta/b'", "/beta/b.ts": "export { }" }, + options: { + declaration: true, + outDir: "bin" + }, + references: [] + } + }; + testProjectReferences(spec, "/alpha/tsconfig.json", (program) => { + const semanticDiagnostics = program.getSemanticDiagnostics(program.getSourceFile("/alpha/src/a.ts")); + assertHasError("Issues an error about the rootDir", semanticDiagnostics, ts.Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files); + assertHasError("Issues an error about the fileList", semanticDiagnostics, ts.Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern); }); }); -} +}); diff --git a/src/testRunner/unittests/config/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts index 530d3c7e56904..438bee2f111ee 100644 --- a/src/testRunner/unittests/config/showConfig.ts +++ b/src/testRunner/unittests/config/showConfig.ts @@ -1,193 +1,194 @@ -namespace ts { - describe("unittests:: config:: showConfig", () => { - function showTSConfigCorrectly(name: string, commandLinesArgs: string[], configJson?: object) { - describe(name, () => { - const outputFileName = `config/showConfig/${name.replace(/[^a-z0-9\-./ ]/ig, "")}/tsconfig.json`; - - it(`Correct output for ${outputFileName}`, () => { - const cwd = `/${name}`; - const configPath = combinePaths(cwd, "tsconfig.json"); - const configContents = configJson ? JSON.stringify(configJson) : undefined; - const configParseHost: ParseConfigFileHost = { - fileExists: path => - comparePaths(getNormalizedAbsolutePath(path, cwd), configPath) === Comparison.EqualTo ? true : false, - getCurrentDirectory() { return cwd; }, - useCaseSensitiveFileNames: true, - onUnRecoverableConfigFileDiagnostic: d => { - throw new Error(flattenDiagnosticMessageText(d.messageText, "\n")); - }, - readDirectory() { return []; }, - readFile: path => - comparePaths(getNormalizedAbsolutePath(path, cwd), configPath) === Comparison.EqualTo ? configContents : undefined, - }; - let commandLine = parseCommandLine(commandLinesArgs); - if (commandLine.options.project) { - const result = getParsedCommandLineOfConfigFile(commandLine.options.project, commandLine.options, configParseHost); - if (result) { - commandLine = result; - } +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; + +describe("unittests:: config:: showConfig", () => { + function showTSConfigCorrectly(name: string, commandLinesArgs: string[], configJson?: object) { + describe(name, () => { + const outputFileName = `config/showConfig/${name.replace(/[^a-z0-9\-./ ]/ig, "")}/tsconfig.json`; + + it(`Correct output for ${outputFileName}`, () => { + const cwd = `/${name}`; + const configPath = ts.combinePaths(cwd, "tsconfig.json"); + const configContents = configJson ? JSON.stringify(configJson) : undefined; + const configParseHost: ts.ParseConfigFileHost = { + fileExists: path => + ts.comparePaths(ts.getNormalizedAbsolutePath(path, cwd), configPath) === ts.Comparison.EqualTo ? true : false, + getCurrentDirectory() { return cwd; }, + useCaseSensitiveFileNames: true, + onUnRecoverableConfigFileDiagnostic: d => { + throw new Error(ts.flattenDiagnosticMessageText(d.messageText, "\n")); + }, + readDirectory() { return []; }, + readFile: path => + ts.comparePaths(ts.getNormalizedAbsolutePath(path, cwd), configPath) === ts.Comparison.EqualTo ? configContents : undefined, + }; + let commandLine = ts.parseCommandLine(commandLinesArgs); + if (commandLine.options.project) { + const result = ts.getParsedCommandLineOfConfigFile(commandLine.options.project, commandLine.options, configParseHost); + if (result) { + commandLine = result; } - const initResult = convertToTSConfig(commandLine, configPath, configParseHost); + } + const initResult = ts.convertToTSConfig(commandLine, configPath, configParseHost); - // eslint-disable-next-line no-null/no-null - Harness.Baseline.runBaseline(outputFileName, JSON.stringify(initResult, null, 4) + "\n"); - }); + // eslint-disable-next-line no-null/no-null + Harness.Baseline.runBaseline(outputFileName, JSON.stringify(initResult, null, 4) + "\n"); }); - } - - showTSConfigCorrectly("Default initialized TSConfig", ["--showConfig"]); + }); + } - showTSConfigCorrectly("Show TSConfig with files options", ["--showConfig", "file0.ts", "file1.ts", "file2.ts"]); + showTSConfigCorrectly("Default initialized TSConfig", ["--showConfig"]); - showTSConfigCorrectly("Show TSConfig with boolean value compiler options", ["--showConfig", "--noUnusedLocals"]); + showTSConfigCorrectly("Show TSConfig with files options", ["--showConfig", "file0.ts", "file1.ts", "file2.ts"]); - showTSConfigCorrectly("Show TSConfig with enum value compiler options", ["--showConfig", "--target", "es5", "--jsx", "react"]); + showTSConfigCorrectly("Show TSConfig with boolean value compiler options", ["--showConfig", "--noUnusedLocals"]); - showTSConfigCorrectly("Show TSConfig with list compiler options", ["--showConfig", "--types", "jquery,mocha"]); + showTSConfigCorrectly("Show TSConfig with enum value compiler options", ["--showConfig", "--target", "es5", "--jsx", "react"]); - showTSConfigCorrectly("Show TSConfig with list compiler options with enum value", ["--showConfig", "--lib", "es5,es2015.core"]); + showTSConfigCorrectly("Show TSConfig with list compiler options", ["--showConfig", "--types", "jquery,mocha"]); - showTSConfigCorrectly("Show TSConfig with incorrect compiler option", ["--showConfig", "--someNonExistOption"]); + showTSConfigCorrectly("Show TSConfig with list compiler options with enum value", ["--showConfig", "--lib", "es5,es2015.core"]); - showTSConfigCorrectly("Show TSConfig with incorrect compiler option value", ["--showConfig", "--lib", "nonExistLib,es5,es2015.promise"]); + showTSConfigCorrectly("Show TSConfig with incorrect compiler option", ["--showConfig", "--someNonExistOption"]); - showTSConfigCorrectly("Show TSConfig with advanced options", ["--showConfig", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]); + showTSConfigCorrectly("Show TSConfig with incorrect compiler option value", ["--showConfig", "--lib", "nonExistLib,es5,es2015.promise"]); - showTSConfigCorrectly("Show TSConfig with compileOnSave and more", ["-p", "tsconfig.json"], { - compilerOptions: { - esModuleInterop: true, - target: "es5", - module: "commonjs", - strict: true, - }, - compileOnSave: true, - exclude: [ - "dist" - ], - files: [], - include: [ - "src/*" - ], - references: [ - { path: "./test" } - ], - }); + showTSConfigCorrectly("Show TSConfig with advanced options", ["--showConfig", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]); - // Regression test for https://github.com/Microsoft/TypeScript/issues/28836 - showTSConfigCorrectly("Show TSConfig with paths and more", ["-p", "tsconfig.json"], { - compilerOptions: { - allowJs: true, - outDir: "./lib", - esModuleInterop: true, - module: "commonjs", - moduleResolution: "node", - target: "ES2017", - sourceMap: true, - baseUrl: ".", - paths: { - "@root/*": ["./*"], - "@configs/*": ["src/configs/*"], - "@common/*": ["src/common/*"], - "*": [ - "node_modules/*", - "src/types/*" - ] - }, - experimentalDecorators: true, - emitDecoratorMetadata: true, - resolveJsonModule: true - }, - include: [ - "./src/**/*" - ] - }); + showTSConfigCorrectly("Show TSConfig with compileOnSave and more", ["-p", "tsconfig.json"], { + compilerOptions: { + esModuleInterop: true, + target: "es5", + module: "commonjs", + strict: true, + }, + compileOnSave: true, + exclude: [ + "dist" + ], + files: [], + include: [ + "src/*" + ], + references: [ + { path: "./test" } + ], + }); - showTSConfigCorrectly("Show TSConfig with watch options", ["-p", "tsconfig.json"], { - watchOptions: { - watchFile: "DynamicPriorityPolling" + // Regression test for https://github.com/Microsoft/TypeScript/issues/28836 + showTSConfigCorrectly("Show TSConfig with paths and more", ["-p", "tsconfig.json"], { + compilerOptions: { + allowJs: true, + outDir: "./lib", + esModuleInterop: true, + module: "commonjs", + moduleResolution: "node", + target: "ES2017", + sourceMap: true, + baseUrl: ".", + paths: { + "@root/*": ["./*"], + "@configs/*": ["src/configs/*"], + "@common/*": ["src/common/*"], + "*": [ + "node_modules/*", + "src/types/*" + ] }, - include: [ - "./src/**/*" - ] - }); - - // Bulk validation of all option declarations - for (const option of optionDeclarations) { - baselineOption(option, /*isCompilerOptions*/ true); - } + experimentalDecorators: true, + emitDecoratorMetadata: true, + resolveJsonModule: true + }, + include: [ + "./src/**/*" + ] + }); - for (const option of optionsForWatch) { - baselineOption(option, /*isCompilerOptions*/ false); - } + showTSConfigCorrectly("Show TSConfig with watch options", ["-p", "tsconfig.json"], { + watchOptions: { + watchFile: "DynamicPriorityPolling" + }, + include: [ + "./src/**/*" + ] + }); - function baselineOption(option: CommandLineOption, isCompilerOptions: boolean) { - if (option.name === "project") return; - let args: string[]; - let optionValue: object | undefined; - switch (option.type) { - case "boolean": { - if (option.isTSConfigOnly) { - args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: true }; - } - else { - args = [`--${option.name}`]; - } - break; + // Bulk validation of all option declarations + for (const option of ts.optionDeclarations) { + baselineOption(option, /*isCompilerOptions*/ true); + } + + for (const option of ts.optionsForWatch) { + baselineOption(option, /*isCompilerOptions*/ false); + } + + function baselineOption(option: ts.CommandLineOption, isCompilerOptions: boolean) { + if (option.name === "project") return; + let args: string[]; + let optionValue: object | undefined; + switch (option.type) { + case "boolean": { + if (option.isTSConfigOnly) { + args = ["-p", "tsconfig.json"]; + optionValue = { [option.name]: true }; } - case "list": { - if (option.isTSConfigOnly) { - args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: [] }; - } - else { - args = [`--${option.name}`]; - } - break; + else { + args = [`--${option.name}`]; } - case "string": { - if (option.isTSConfigOnly) { - args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: "someString" }; - } - else { - args = [`--${option.name}`, "someString"]; - } - break; + break; + } + case "list": { + if (option.isTSConfigOnly) { + args = ["-p", "tsconfig.json"]; + optionValue = { [option.name]: [] }; } - case "number": { - if (option.isTSConfigOnly) { - args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: 0 }; - } - else { - args = [`--${option.name}`, "0"]; - } - break; + else { + args = [`--${option.name}`]; } - case "object": { + break; + } + case "string": { + if (option.isTSConfigOnly) { args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: {} }; - break; + optionValue = { [option.name]: "someString" }; } - default: { - const iterResult = option.type.keys().next(); - if (iterResult.done) return Debug.fail("Expected 'option.type' to have entries"); - const val = iterResult.value; - if (option.isTSConfigOnly) { - args = ["-p", "tsconfig.json"]; - optionValue = { [option.name]: val }; - } - else { - args = [`--${option.name}`, val]; - } - break; + else { + args = [`--${option.name}`, "someString"]; } + break; + } + case "number": { + if (option.isTSConfigOnly) { + args = ["-p", "tsconfig.json"]; + optionValue = { [option.name]: 0 }; + } + else { + args = [`--${option.name}`, "0"]; + } + break; + } + case "object": { + args = ["-p", "tsconfig.json"]; + optionValue = { [option.name]: {} }; + break; + } + default: { + const iterResult = option.type.keys().next(); + if (iterResult.done) return ts.Debug.fail("Expected 'option.type' to have entries"); + const val = iterResult.value; + if (option.isTSConfigOnly) { + args = ["-p", "tsconfig.json"]; + optionValue = { [option.name]: val }; + } + else { + args = [`--${option.name}`, val]; + } + break; } - - const configObject = optionValue && - (isCompilerOptions ? { compilerOptions: optionValue } : { watchOptions: optionValue }); - showTSConfigCorrectly(`Shows tsconfig for single option/${option.name}`, args, configObject); } - }); -} + + const configObject = optionValue && + (isCompilerOptions ? { compilerOptions: optionValue } : { watchOptions: optionValue }); + showTSConfigCorrectly(`Shows tsconfig for single option/${option.name}`, args, configObject); + } +}); diff --git a/src/testRunner/unittests/config/tsconfigParsing.ts b/src/testRunner/unittests/config/tsconfigParsing.ts index 9a5e46322e44d..fc1d5c467939b 100644 --- a/src/testRunner/unittests/config/tsconfigParsing.ts +++ b/src/testRunner/unittests/config/tsconfigParsing.ts @@ -1,104 +1,107 @@ -namespace ts { - describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", () => { - function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) { - const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); - assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject)); +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; +import * as fakes from "../../_namespaces/fakes"; + +describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", () => { + function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: ts.Diagnostic[] }) { + const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); + assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject)); + } + + function assertParseErrorWithExcludesKeyword(jsonText: string) { + { + const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); + const parsedCommand = ts.parseJsonConfigFileContent(parsed.config, ts.sys, "tests/cases/unittests"); + assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && + parsedCommand.errors[0].code === ts.Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); } - - function assertParseErrorWithExcludesKeyword(jsonText: string) { - { - const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); - const parsedCommand = parseJsonConfigFileContent(parsed.config, sys, "tests/cases/unittests"); - assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && - parsedCommand.errors[0].code === Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); - } - { - const parsed = parseJsonText("/apath/tsconfig.json", jsonText); - const parsedCommand = parseJsonSourceFileConfigFileContent(parsed, sys, "tests/cases/unittests"); - assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && - parsedCommand.errors[0].code === Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); - } + { + const parsed = ts.parseJsonText("/apath/tsconfig.json", jsonText); + const parsedCommand = ts.parseJsonSourceFileConfigFileContent(parsed, ts.sys, "tests/cases/unittests"); + assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && + parsedCommand.errors[0].code === ts.Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); } - - function getParsedCommandJson(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { - const parsed = parseConfigFileTextToJson(configFileName, jsonText); - const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); - const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); - return parseJsonConfigFileContent(parsed.config, host, basePath, /*existingOptions*/ undefined, configFileName); + } + + function getParsedCommandJson(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { + const parsed = ts.parseConfigFileTextToJson(configFileName, jsonText); + const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); + const host: ts.ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); + return ts.parseJsonConfigFileContent(parsed.config, host, basePath, /*existingOptions*/ undefined, configFileName); + } + + function getParsedCommandJsonNode(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { + const parsed = ts.parseJsonText(configFileName, jsonText); + const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); + const host: ts.ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); + return ts.parseJsonSourceFileConfigFileContent(parsed, host, basePath, /*existingOptions*/ undefined, configFileName); + } + + function assertParseFileList(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedFileList: string[]) { + { + const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); + assert.isTrue(ts.arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); } - - function getParsedCommandJsonNode(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { - const parsed = parseJsonText(configFileName, jsonText); - const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); - const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); - return parseJsonSourceFileConfigFileContent(parsed, host, basePath, /*existingOptions*/ undefined, configFileName); + { + const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); + assert.isTrue(ts.arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); } + } - function assertParseFileList(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedFileList: string[]) { - { - const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); - assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); - } - { - const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); - assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); - } + function assertParseFileDiagnostics(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedDiagnosticCode: number, noLocation?: boolean) { + { + const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); } - - function assertParseFileDiagnostics(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedDiagnosticCode: number, noLocation?: boolean) { - { - const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); - assert.isTrue(parsed.errors.length >= 0); - assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); - } - { - const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); - assert.isTrue(parsed.errors.length >= 0); - assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); - if (!noLocation) { - assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode && e.file && e.start && e.length).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)} with location information`); - } + { + const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); + if (!noLocation) { + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode && e.file && e.start && e.length).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)} with location information`); } } + } - function assertParseFileDiagnosticsExclusion(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedExcludedDiagnosticCode: number) { - { - const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); - assert.isTrue(parsed.errors.length >= 0); - assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`); - } - { - const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); - assert.isTrue(parsed.errors.length >= 0); - assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`); - } + function assertParseFileDiagnosticsExclusion(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedExcludedDiagnosticCode: number) { + { + const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`); + } + { + const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`); } + } - it("returns empty config for file with only whitespaces", () => { - assertParseResult("", { config : {} }); - assertParseResult(" ", { config : {} }); - }); + it("returns empty config for file with only whitespaces", () => { + assertParseResult("", { config : {} }); + assertParseResult(" ", { config : {} }); + }); - it("returns empty config for file with comments only", () => { - assertParseResult("// Comment", { config: {} }); - assertParseResult("/* Comment*/", { config: {} }); - }); + it("returns empty config for file with comments only", () => { + assertParseResult("// Comment", { config: {} }); + assertParseResult("/* Comment*/", { config: {} }); + }); - it("returns empty config when config is empty object", () => { - assertParseResult("{}", { config: {} }); - }); + it("returns empty config when config is empty object", () => { + assertParseResult("{}", { config: {} }); + }); - it("returns config object without comments", () => { - assertParseResult( - `{ // Excluded files + it("returns config object without comments", () => { + assertParseResult( + `{ // Excluded files "exclude": [ // Exclude d.ts "file.d.ts" ] }`, { config: { exclude: ["file.d.ts"] } }); - assertParseResult( - `{ + assertParseResult( + `{ /* Excluded Files */ @@ -106,74 +109,74 @@ namespace ts { /* multiline comments can be in the middle of a line */"file.d.ts" ] }`, { config: { exclude: ["file.d.ts"] } }); - }); + }); - it("keeps string content untouched", () => { - assertParseResult( - `{ + it("keeps string content untouched", () => { + assertParseResult( + `{ "exclude": [ "xx//file.d.ts" ] }`, { config: { exclude: ["xx//file.d.ts"] } }); - assertParseResult( - `{ + assertParseResult( + `{ "exclude": [ "xx/*file.d.ts*/" ] }`, { config: { exclude: ["xx/*file.d.ts*/"] } }); - }); + }); - it("handles escaped characters in strings correctly", () => { - assertParseResult( - `{ + it("handles escaped characters in strings correctly", () => { + assertParseResult( + `{ "exclude": [ "xx\\"//files" ] }`, { config: { exclude: ["xx\"//files"] } }); - assertParseResult( - `{ + assertParseResult( + `{ "exclude": [ "xx\\\\" // end of line comment ] }`, { config: { exclude: ["xx\\"] } }); - }); - - it("returns object with error when json is invalid", () => { - const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", "invalid"); - assert.deepEqual(parsed.config, {}); - const expected = createCompilerDiagnostic(Diagnostics._0_expected, "{"); - const error = parsed.error!; - assert.equal(error.messageText, expected.messageText); - assert.equal(error.category, expected.category); - assert.equal(error.code, expected.code); - assert.equal(error.start, 0); - assert.equal(error.length, "invalid".length); - }); - - it("returns object when users correctly specify library", () => { - assertParseResult( - `{ + }); + + it("returns object with error when json is invalid", () => { + const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", "invalid"); + assert.deepEqual(parsed.config, {}); + const expected = ts.createCompilerDiagnostic(ts.Diagnostics._0_expected, "{"); + const error = parsed.error!; + assert.equal(error.messageText, expected.messageText); + assert.equal(error.category, expected.category); + assert.equal(error.code, expected.code); + assert.equal(error.start, 0); + assert.equal(error.length, "invalid".length); + }); + + it("returns object when users correctly specify library", () => { + assertParseResult( + `{ "compilerOptions": { "lib": ["es5"] } }`, { - config: { compilerOptions: { lib: ["es5"] } } - }); + config: { compilerOptions: { lib: ["es5"] } } + }); - assertParseResult( - `{ + assertParseResult( + `{ "compilerOptions": { "lib": ["es5", "es6"] } }`, { - config: { compilerOptions: { lib: ["es5", "es6"] } } - }); - }); + config: { compilerOptions: { lib: ["es5", "es6"] } } + }); + }); - it("returns error when tsconfig have excludes", () => { - assertParseErrorWithExcludesKeyword( - `{ + it("returns error when tsconfig have excludes", () => { + assertParseErrorWithExcludesKeyword( + `{ "compilerOptions": { "lib": ["es5"] }, @@ -181,86 +184,86 @@ namespace ts { "foge.ts" ] }`); - }); - - it("ignore dotted files and folders", () => { - assertParseFileList( - `{}`, - "tsconfig.json", - "/apath", - ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"], - ["/apath/test.ts"] - ); - }); - - it("allow dotted files and folders when explicitly requested", () => { - assertParseFileList( - `{ + }); + + it("ignore dotted files and folders", () => { + assertParseFileList( + `{}`, + "tsconfig.json", + "/apath", + ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"], + ["/apath/test.ts"] + ); + }); + + it("allow dotted files and folders when explicitly requested", () => { + assertParseFileList( + `{ "files": ["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"] }`, - "tsconfig.json", - "/apath", - ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"], - ["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"] - ); - }); - - it("exclude outDir unless overridden", () => { - const tsconfigWithoutExclude = - `{ + "tsconfig.json", + "/apath", + ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"], + ["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"] + ); + }); + + it("exclude outDir unless overridden", () => { + const tsconfigWithoutExclude = + `{ "compilerOptions": { "outDir": "bin" } }`; - const tsconfigWithExclude = - `{ + const tsconfigWithExclude = + `{ "compilerOptions": { "outDir": "bin" }, "exclude": [ "obj" ] }`; - const rootDir = "/"; - const allFiles = ["/bin/a.ts", "/b.ts"]; - const expectedFiles = ["/b.ts"]; - assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles); - assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles); - }); - - it("exclude declarationDir unless overridden", () => { - const tsconfigWithoutExclude = - `{ + const rootDir = "/"; + const allFiles = ["/bin/a.ts", "/b.ts"]; + const expectedFiles = ["/b.ts"]; + assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles); + assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles); + }); + + it("exclude declarationDir unless overridden", () => { + const tsconfigWithoutExclude = + `{ "compilerOptions": { "declarationDir": "declarations" } }`; - const tsconfigWithExclude = - `{ + const tsconfigWithExclude = + `{ "compilerOptions": { "declarationDir": "declarations" }, "exclude": [ "types" ] }`; - const rootDir = "/"; - const allFiles = ["/declarations/a.d.ts", "/a.ts"]; - const expectedFiles = ["/a.ts"]; - - assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles); - assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles); - }); - - it("implicitly exclude common package folders", () => { - assertParseFileList( - `{}`, - "tsconfig.json", - "/", - ["/node_modules/a.ts", "/bower_components/b.ts", "/jspm_packages/c.ts", "/d.ts", "/folder/e.ts"], - ["/d.ts", "/folder/e.ts"] - ); - }); - - it("parse and re-emit tsconfig.json file with diagnostics", () => { - const content = `{ + const rootDir = "/"; + const allFiles = ["/declarations/a.d.ts", "/a.ts"]; + const expectedFiles = ["/a.ts"]; + + assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles); + assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles); + }); + + it("implicitly exclude common package folders", () => { + assertParseFileList( + `{}`, + "tsconfig.json", + "/", + ["/node_modules/a.ts", "/bower_components/b.ts", "/jspm_packages/c.ts", "/d.ts", "/folder/e.ts"], + ["/d.ts", "/folder/e.ts"] + ); + }); + + it("parse and re-emit tsconfig.json file with diagnostics", () => { + const content = `{ "compilerOptions": { "allowJs": true // Some comments @@ -268,167 +271,166 @@ namespace ts { } "files": ["file1.ts"] }`; - const result = parseJsonText("config.json", content); - const diagnostics = result.parseDiagnostics; - const configJsonObject = convertToObject(result, diagnostics); - const expectedResult = { - compilerOptions: { - allowJs: true, - outDir: "bin" - }, - files: ["file1.ts"] - }; - assert.isTrue(diagnostics.length === 2); - assert.equal(JSON.stringify(configJsonObject), JSON.stringify(expectedResult)); - }); - - it("generates errors for empty files list", () => { - const content = `{ + const result = ts.parseJsonText("config.json", content); + const diagnostics = result.parseDiagnostics; + const configJsonObject = ts.convertToObject(result, diagnostics); + const expectedResult = { + compilerOptions: { + allowJs: true, + outDir: "bin" + }, + files: ["file1.ts"] + }; + assert.isTrue(diagnostics.length === 2); + assert.equal(JSON.stringify(configJsonObject), JSON.stringify(expectedResult)); + }); + + it("generates errors for empty files list", () => { + const content = `{ "files": [] }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.The_files_list_in_config_file_0_is_empty.code); - }); - - it("generates errors for empty files list when no references are provided", () => { - const content = `{ + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + ts.Diagnostics.The_files_list_in_config_file_0_is_empty.code); + }); + + it("generates errors for empty files list when no references are provided", () => { + const content = `{ "files": [], "references": [] }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.The_files_list_in_config_file_0_is_empty.code); - }); - - it("does not generate errors for empty files list when one or more references are provided", () => { - const content = `{ + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + ts.Diagnostics.The_files_list_in_config_file_0_is_empty.code); + }); + + it("does not generate errors for empty files list when one or more references are provided", () => { + const content = `{ "files": [], "references": [{ "path": "/apath" }] }`; - assertParseFileDiagnosticsExclusion(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.The_files_list_in_config_file_0_is_empty.code); - }); - - it("generates errors for directory with no .ts files", () => { - const content = `{ + assertParseFileDiagnosticsExclusion(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + ts.Diagnostics.The_files_list_in_config_file_0_is_empty.code); + }); + + it("generates errors for directory with no .ts files", () => { + const content = `{ }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.js"], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, - /*noLocation*/ true); - }); - - it("generates errors for empty directory", () => { - const content = `{ + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.js"], + ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); + }); + + it("generates errors for empty directory", () => { + const content = `{ "compilerOptions": { "allowJs": true } }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - [], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, - /*noLocation*/ true); - }); - - it("generates errors for empty include", () => { - const content = `{ + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + [], + ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); + }); + + it("generates errors for empty include", () => { + const content = `{ "include": [] }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, - /*noLocation*/ true); - }); - - it("generates errors for includes with outDir", () => { - const content = `{ + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); + }); + + it("generates errors for includes with outDir", () => { + const content = `{ "compilerOptions": { "outDir": "./" }, "include": ["**/*"] }`; - assertParseFileDiagnostics(content, - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, - /*noLocation*/ true); - }); + assertParseFileDiagnostics(content, + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); + }); - it("generates errors for when invalid comment type present in tsconfig", () => { - const jsonText = `{ + it("generates errors for when invalid comment type present in tsconfig", () => { + const jsonText = `{ "compilerOptions": { ## this comment does cause issues "types" : [ ] } }`; - const parsed = getParsedCommandJsonNode(jsonText, "/apath/tsconfig.json", "tests/cases/unittests", ["/apath/a.ts"]); - assert.isTrue(parsed.errors.length >= 0); - }); - - it("generates errors when files is not string", () => { - assertParseFileDiagnostics( - JSON.stringify({ - files: [{ - compilerOptions: { - experimentalDecorators: true, - allowJs: true - } - }] - }), - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code, - /*noLocation*/ true); - }); - - it("generates errors when include is not string", () => { - assertParseFileDiagnostics( - JSON.stringify({ - include: [ - ["./**/*.ts"] - ] - }), - "/apath/tsconfig.json", - "tests/cases/unittests", - ["/apath/a.ts"], - Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code, - /*noLocation*/ true); - }); - - it("parses wildcard directories even when parent directories have dots", () => { - const parsed = parseConfigFileTextToJson("/foo.bar/tsconfig.json", JSON.stringify({ - include: ["src"] - })); - - const parsedCommand = parseJsonConfigFileContent(parsed.config, sys, "/foo.bar"); - assert.deepEqual(parsedCommand.wildcardDirectories, { "/foo.bar/src": WatchDirectoryFlags.Recursive }); - }); - - it("correctly parses wild card directories from implicit glob when two keys differ only in directory seperator", () => { - const parsed = parseConfigFileTextToJson("/foo.bar/tsconfig.json", JSON.stringify({ - include: ["./", "./**/*.json"] - })); - - const parsedCommand = parseJsonConfigFileContent(parsed.config, sys, "/foo"); - assert.deepEqual(parsedCommand.wildcardDirectories, { "/foo": WatchDirectoryFlags.Recursive }); - }); + const parsed = getParsedCommandJsonNode(jsonText, "/apath/tsconfig.json", "tests/cases/unittests", ["/apath/a.ts"]); + assert.isTrue(parsed.errors.length >= 0); + }); + + it("generates errors when files is not string", () => { + assertParseFileDiagnostics( + JSON.stringify({ + files: [{ + compilerOptions: { + experimentalDecorators: true, + allowJs: true + } + }] + }), + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + ts.Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code, + /*noLocation*/ true); + }); + + it("generates errors when include is not string", () => { + assertParseFileDiagnostics( + JSON.stringify({ + include: [ + ["./**/*.ts"] + ] + }), + "/apath/tsconfig.json", + "tests/cases/unittests", + ["/apath/a.ts"], + ts.Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code, + /*noLocation*/ true); + }); + + it("parses wildcard directories even when parent directories have dots", () => { + const parsed = ts.parseConfigFileTextToJson("/foo.bar/tsconfig.json", JSON.stringify({ + include: ["src"] + })); + + const parsedCommand = ts.parseJsonConfigFileContent(parsed.config, ts.sys, "/foo.bar"); + assert.deepEqual(parsedCommand.wildcardDirectories, { "/foo.bar/src": ts.WatchDirectoryFlags.Recursive }); + }); + + it("correctly parses wild card directories from implicit glob when two keys differ only in directory seperator", () => { + const parsed = ts.parseConfigFileTextToJson("/foo.bar/tsconfig.json", JSON.stringify({ + include: ["./", "./**/*.json"] + })); + + const parsedCommand = ts.parseJsonConfigFileContent(parsed.config, ts.sys, "/foo"); + assert.deepEqual(parsedCommand.wildcardDirectories, { "/foo": ts.WatchDirectoryFlags.Recursive }); }); -} +}); diff --git a/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts b/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts index a8179558baa2f..0a64f23e25aca 100644 --- a/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts +++ b/src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts @@ -1,173 +1,176 @@ -namespace ts { - describe("unittests:: config:: tsconfigParsingWatchOptions:: parseConfigFileTextToJson", () => { - function createParseConfigHost(additionalFiles?: vfs.FileSet) { - return new fakes.ParseConfigHost( - new vfs.FileSystem( - /*ignoreCase*/ false, - { - cwd: "/", - files: { "/": {}, "/a.ts": "", ...additionalFiles } - } - ) - ); - } - function getParsedCommandJson(json: object, additionalFiles?: vfs.FileSet, existingWatchOptions?: WatchOptions) { - return parseJsonConfigFileContent( - json, - createParseConfigHost(additionalFiles), - "/", - /*existingOptions*/ undefined, - "tsconfig.json", - /*resolutionStack*/ undefined, - /*extraFileExtensions*/ undefined, - /*extendedConfigCache*/ undefined, - existingWatchOptions, - ); - } +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; +import * as fakes from "../../_namespaces/fakes"; +import * as Harness from "../../_namespaces/Harness"; - function getParsedCommandJsonNode(json: object, additionalFiles?: vfs.FileSet, existingWatchOptions?: WatchOptions) { - const parsed = parseJsonText("tsconfig.json", JSON.stringify(json)); - return parseJsonSourceFileConfigFileContent( - parsed, - createParseConfigHost(additionalFiles), - "/", - /*existingOptions*/ undefined, - "tsconfig.json", - /*resolutionStack*/ undefined, - /*extraFileExtensions*/ undefined, - /*extendedConfigCache*/ undefined, - existingWatchOptions, - ); - } +describe("unittests:: config:: tsconfigParsingWatchOptions:: parseConfigFileTextToJson", () => { + function createParseConfigHost(additionalFiles?: vfs.FileSet) { + return new fakes.ParseConfigHost( + new vfs.FileSystem( + /*ignoreCase*/ false, + { + cwd: "/", + files: { "/": {}, "/a.ts": "", ...additionalFiles } + } + ) + ); + } + function getParsedCommandJson(json: object, additionalFiles?: vfs.FileSet, existingWatchOptions?: ts.WatchOptions) { + return ts.parseJsonConfigFileContent( + json, + createParseConfigHost(additionalFiles), + "/", + /*existingOptions*/ undefined, + "tsconfig.json", + /*resolutionStack*/ undefined, + /*extraFileExtensions*/ undefined, + /*extendedConfigCache*/ undefined, + existingWatchOptions, + ); + } - interface VerifyWatchOptions { - json: object; - additionalFiles?: vfs.FileSet; - existingWatchOptions?: WatchOptions | undefined; - } + function getParsedCommandJsonNode(json: object, additionalFiles?: vfs.FileSet, existingWatchOptions?: ts.WatchOptions) { + const parsed = ts.parseJsonText("tsconfig.json", JSON.stringify(json)); + return ts.parseJsonSourceFileConfigFileContent( + parsed, + createParseConfigHost(additionalFiles), + "/", + /*existingOptions*/ undefined, + "tsconfig.json", + /*resolutionStack*/ undefined, + /*extraFileExtensions*/ undefined, + /*extendedConfigCache*/ undefined, + existingWatchOptions, + ); + } - function verifyWatchOptions(subScenario: string, scenario: () => VerifyWatchOptions[]) { - describe(subScenario, () => { - it("with json api", () => { - const baseline: string[] = []; - for (const { json, additionalFiles, existingWatchOptions } of scenario()) { - addToBaseLine(baseline, json, getParsedCommandJson(json, additionalFiles, existingWatchOptions)); - } - runBaseline(`${subScenario} with json api`, baseline); - }); + interface VerifyWatchOptions { + json: object; + additionalFiles?: vfs.FileSet; + existingWatchOptions?: ts.WatchOptions | undefined; + } - it("with json source file api", () => { - const baseline: string[] = []; - for (const { json, additionalFiles, existingWatchOptions, } of scenario()) { - addToBaseLine(baseline, json, getParsedCommandJsonNode(json, additionalFiles, existingWatchOptions)); - } - runBaseline(`${subScenario} with jsonSourceFile api`, baseline); - }); + function verifyWatchOptions(subScenario: string, scenario: () => VerifyWatchOptions[]) { + describe(subScenario, () => { + it("with json api", () => { + const baseline: string[] = []; + for (const { json, additionalFiles, existingWatchOptions } of scenario()) { + addToBaseLine(baseline, json, getParsedCommandJson(json, additionalFiles, existingWatchOptions)); + } + runBaseline(`${subScenario} with json api`, baseline); }); - function addToBaseLine(baseline: string[], json: object, parsed: ParsedCommandLine) { - baseline.push(`Input:: ${JSON.stringify(json, /*replacer*/ undefined, " ")}`); - baseline.push(`Result: WatchOptions::`); - baseline.push(JSON.stringify(parsed.watchOptions, /*replacer*/ undefined, " ")); - baseline.push(`Result: Errors::`); - baseline.push(formatDiagnosticsWithColorAndContext(parsed.errors, { - getCurrentDirectory: () => "/", - getCanonicalFileName: identity, - getNewLine: () => "\n" - })); - } - function runBaseline(subScenario: string, baseline: readonly string[]) { - Harness.Baseline.runBaseline(`config/tsconfigParsingWatchOptions/${subScenario}.js`, baseline.join("\n")); - } + + it("with json source file api", () => { + const baseline: string[] = []; + for (const { json, additionalFiles, existingWatchOptions, } of scenario()) { + addToBaseLine(baseline, json, getParsedCommandJsonNode(json, additionalFiles, existingWatchOptions)); + } + runBaseline(`${subScenario} with jsonSourceFile api`, baseline); + }); + }); + function addToBaseLine(baseline: string[], json: object, parsed: ts.ParsedCommandLine) { + baseline.push(`Input:: ${JSON.stringify(json, /*replacer*/ undefined, " ")}`); + baseline.push(`Result: WatchOptions::`); + baseline.push(JSON.stringify(parsed.watchOptions, /*replacer*/ undefined, " ")); + baseline.push(`Result: Errors::`); + baseline.push(ts.formatDiagnosticsWithColorAndContext(parsed.errors, { + getCurrentDirectory: () => "/", + getCanonicalFileName: ts.identity, + getNewLine: () => "\n" + })); + } + function runBaseline(subScenario: string, baseline: readonly string[]) { + Harness.Baseline.runBaseline(`config/tsconfigParsingWatchOptions/${subScenario}.js`, baseline.join("\n")); } + } - verifyWatchOptions("no watchOptions specified option", () => [{ - json: {}, - }]); + verifyWatchOptions("no watchOptions specified option", () => [{ + json: {}, + }]); - verifyWatchOptions("empty watchOptions specified option", () => [{ - json: { watchOptions: {} }, - }]); + verifyWatchOptions("empty watchOptions specified option", () => [{ + json: { watchOptions: {} }, + }]); - verifyWatchOptions("when extending config file without watchOptions", () => [ - { - json: { - extends: "./base.json", - watchOptions: { watchFile: "UseFsEvents" } - }, - additionalFiles: { "/base.json": "{}" } + verifyWatchOptions("when extending config file without watchOptions", () => [ + { + json: { + extends: "./base.json", + watchOptions: { watchFile: "UseFsEvents" } }, - { - json: { extends: "./base.json", }, - additionalFiles: { "/base.json": "{}" } - } - ]); + additionalFiles: { "/base.json": "{}" } + }, + { + json: { extends: "./base.json", }, + additionalFiles: { "/base.json": "{}" } + } + ]); - verifyWatchOptions("when extending config file with watchOptions", () => [ - { - json: { - extends: "./base.json", + verifyWatchOptions("when extending config file with watchOptions", () => [ + { + json: { + extends: "./base.json", + watchOptions: { + watchFile: "UseFsEvents", + } + }, + additionalFiles: { + "/base.json": JSON.stringify({ watchOptions: { - watchFile: "UseFsEvents", + watchFile: "UseFsEventsOnParentDirectory", + watchDirectory: "FixedPollingInterval" } - }, - additionalFiles: { - "/base.json": JSON.stringify({ - watchOptions: { - watchFile: "UseFsEventsOnParentDirectory", - watchDirectory: "FixedPollingInterval" - } - }) - } + }) + } + }, + { + json: { + extends: "./base.json", }, - { - json: { - extends: "./base.json", - }, - additionalFiles: { - "/base.json": JSON.stringify({ - watchOptions: { - watchFile: "UseFsEventsOnParentDirectory", - watchDirectory: "FixedPollingInterval" - } - }) - } + additionalFiles: { + "/base.json": JSON.stringify({ + watchOptions: { + watchFile: "UseFsEventsOnParentDirectory", + watchDirectory: "FixedPollingInterval" + } + }) } - ]); + } + ]); - verifyWatchOptions("different options", () => [ - { - json: { watchOptions: { watchFile: "UseFsEvents" } }, - }, - { - json: { watchOptions: { watchDirectory: "UseFsEvents" } }, - }, - { - json: { watchOptions: { fallbackPolling: "DynamicPriority" } }, - }, - { - json: { watchOptions: { synchronousWatchDirectory: true } }, - }, - { - json: { watchOptions: { excludeDirectories: ["**/temp"] } }, - }, - { - json: { watchOptions: { excludeFiles: ["**/temp/*.ts"] } }, - }, - { - json: { watchOptions: { excludeDirectories: ["**/../*"] } }, - }, - { - json: { watchOptions: { excludeFiles: ["**/../*"] } }, - }, - ]); + verifyWatchOptions("different options", () => [ + { + json: { watchOptions: { watchFile: "UseFsEvents" } }, + }, + { + json: { watchOptions: { watchDirectory: "UseFsEvents" } }, + }, + { + json: { watchOptions: { fallbackPolling: "DynamicPriority" } }, + }, + { + json: { watchOptions: { synchronousWatchDirectory: true } }, + }, + { + json: { watchOptions: { excludeDirectories: ["**/temp"] } }, + }, + { + json: { watchOptions: { excludeFiles: ["**/temp/*.ts"] } }, + }, + { + json: { watchOptions: { excludeDirectories: ["**/../*"] } }, + }, + { + json: { watchOptions: { excludeFiles: ["**/../*"] } }, + }, + ]); - verifyWatchOptions("watch options extending passed in watch options", () => [ - { - json: { watchOptions: { watchFile: "UseFsEvents" } }, - }, - { - json: {}, - }, - ]); - }); -} + verifyWatchOptions("watch options extending passed in watch options", () => [ + { + json: { watchOptions: { watchFile: "UseFsEvents" } }, + }, + { + json: {}, + }, + ]); +}); diff --git a/src/testRunner/unittests/convertToBase64.ts b/src/testRunner/unittests/convertToBase64.ts index 37e38da8da7dc..68a2f0e6818b5 100644 --- a/src/testRunner/unittests/convertToBase64.ts +++ b/src/testRunner/unittests/convertToBase64.ts @@ -1,33 +1,33 @@ -namespace ts { - describe("unittests:: convertToBase64", () => { - function runTest(input: string): void { - const actual = convertToBase64(input); - const expected = sys.base64encode!(input); - assert.equal(actual, expected, "Encoded string using convertToBase64 does not match buffer.toString('base64')"); - } +import * as ts from "../_namespaces/ts"; - if (Buffer) { - it("Converts ASCII charaters correctly", () => { - runTest(" !\"#$ %&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); - }); +describe("unittests:: convertToBase64", () => { + function runTest(input: string): void { + const actual = ts.convertToBase64(input); + const expected = ts.sys.base64encode!(input); + assert.equal(actual, expected, "Encoded string using convertToBase64 does not match buffer.toString('base64')"); + } - it("Converts escape sequences correctly", () => { - runTest("\t\n\r\\\"\'\u0062"); - }); + if (Buffer) { + it("Converts ASCII charaters correctly", () => { + runTest(" !\"#$ %&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); + }); - it("Converts simple unicode characters correctly", () => { - runTest("ΠΣ ٵپ औठ ⺐⺠"); - }); + it("Converts escape sequences correctly", () => { + runTest("\t\n\r\\\"\'\u0062"); + }); - it("Converts simple code snippet correctly", () => { - runTest(`/// + it("Converts simple unicode characters correctly", () => { + runTest("ΠΣ ٵپ औठ ⺐⺠"); + }); + + it("Converts simple code snippet correctly", () => { + runTest(`/// var x: string = "string"; console.log(x);`); - }); + }); - it("Converts simple code snippet with unicode characters correctly", () => { - runTest(`var Π = 3.1415; console.log(Π);`); - }); - } - }); -} + it("Converts simple code snippet with unicode characters correctly", () => { + runTest(`var Π = 3.1415; console.log(Π);`); + }); + } +}); diff --git a/src/testRunner/unittests/customTransforms.ts b/src/testRunner/unittests/customTransforms.ts index 0f65aba6e3408..2579aa008bb96 100644 --- a/src/testRunner/unittests/customTransforms.ts +++ b/src/testRunner/unittests/customTransforms.ts @@ -1,168 +1,169 @@ -namespace ts { - describe("unittests:: customTransforms", () => { - function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: CustomTransformers, options: CompilerOptions = {}) { - it(name, () => { - const roots = sources.map(source => createSourceFile(source.file, source.text, ScriptTarget.ES2015)); - const fileMap = arrayToMap(roots, file => file.fileName); - const outputs = new Map(); - const host: CompilerHost = { - getSourceFile: (fileName) => fileMap.get(fileName), - getDefaultLibFileName: () => "lib.d.ts", - getCurrentDirectory: () => "", - getDirectories: () => [], - getCanonicalFileName: (fileName) => fileName, - useCaseSensitiveFileNames: () => true, - getNewLine: () => "\n", - fileExists: (fileName) => fileMap.has(fileName), - readFile: (fileName) => fileMap.has(fileName) ? fileMap.get(fileName)!.text : undefined, - writeFile: (fileName, text) => outputs.set(fileName, text), - }; +import * as ts from "../_namespaces/ts"; +import * as Harness from "../_namespaces/Harness"; - const program = createProgram(arrayFrom(fileMap.keys()), { newLine: NewLineKind.LineFeed, ...options }, host); - program.emit(/*targetSourceFile*/ undefined, host.writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, customTransformers); - let content = ""; - for (const [file, text] of arrayFrom(outputs.entries())) { - if (content) content += "\n\n"; - content += `// [${file}]\n`; - content += text; - } - Harness.Baseline.runBaseline(`customTransforms/${name}.js`, content); - }); - } +describe("unittests:: customTransforms", () => { + function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: ts.CustomTransformers, options: ts.CompilerOptions = {}) { + it(name, () => { + const roots = sources.map(source => ts.createSourceFile(source.file, source.text, ts.ScriptTarget.ES2015)); + const fileMap = ts.arrayToMap(roots, file => file.fileName); + const outputs = new ts.Map(); + const host: ts.CompilerHost = { + getSourceFile: (fileName) => fileMap.get(fileName), + getDefaultLibFileName: () => "lib.d.ts", + getCurrentDirectory: () => "", + getDirectories: () => [], + getCanonicalFileName: (fileName) => fileName, + useCaseSensitiveFileNames: () => true, + getNewLine: () => "\n", + fileExists: (fileName) => fileMap.has(fileName), + readFile: (fileName) => fileMap.has(fileName) ? fileMap.get(fileName)!.text : undefined, + writeFile: (fileName, text) => outputs.set(fileName, text), + }; - const sources = [{ - file: "source.ts", - text: ` + const program = ts.createProgram(ts.arrayFrom(fileMap.keys()), { newLine: ts.NewLineKind.LineFeed, ...options }, host); + program.emit(/*targetSourceFile*/ undefined, host.writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, customTransformers); + let content = ""; + for (const [file, text] of ts.arrayFrom(outputs.entries())) { + if (content) content += "\n\n"; + content += `// [${file}]\n`; + content += text; + } + Harness.Baseline.runBaseline(`customTransforms/${name}.js`, content); + }); + } + + const sources = [{ + file: "source.ts", + text: ` function f1() { } class c() { } enum e { } // leading function f2() { } // trailing ` - }]; + }]; - const before: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - return visitFunction(node as FunctionDeclaration); - default: - return visitEachChild(node, visit, context); - } - } - function visitFunction(node: FunctionDeclaration) { - addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, "@before", /*hasTrailingNewLine*/ true); - return node; + const before: ts.TransformerFactory = context => { + return file => ts.visitEachChild(file, visit, context); + function visit(node: ts.Node): ts.VisitResult { + switch (node.kind) { + case ts.SyntaxKind.FunctionDeclaration: + return visitFunction(node as ts.FunctionDeclaration); + default: + return ts.visitEachChild(node, visit, context); } - }; + } + function visitFunction(node: ts.FunctionDeclaration) { + ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, "@before", /*hasTrailingNewLine*/ true); + return node; + } + }; - const after: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.VariableStatement: - return visitVariableStatement(node as VariableStatement); - default: - return visitEachChild(node, visit, context); - } - } - function visitVariableStatement(node: VariableStatement) { - addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, "@after"); - return node; + const after: ts.TransformerFactory = context => { + return file => ts.visitEachChild(file, visit, context); + function visit(node: ts.Node): ts.VisitResult { + switch (node.kind) { + case ts.SyntaxKind.VariableStatement: + return visitVariableStatement(node as ts.VariableStatement); + default: + return ts.visitEachChild(node, visit, context); } - }; + } + function visitVariableStatement(node: ts.VariableStatement) { + ts.addSyntheticLeadingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, "@after"); + return node; + } + }; - emitsCorrectly("before", sources, { before: [before] }); - emitsCorrectly("after", sources, { after: [after] }); - emitsCorrectly("both", sources, { before: [before], after: [after] }); + emitsCorrectly("before", sources, { before: [before] }); + emitsCorrectly("after", sources, { after: [after] }); + emitsCorrectly("both", sources, { before: [before], after: [after] }); - emitsCorrectly("before+decorators", [{ - file: "source.ts", - text: ` + emitsCorrectly("before+decorators", [{ + file: "source.ts", + text: ` declare const dec: any; class B {} @dec export class C { constructor(b: B) { } } 'change' ` - }], {before: [ - context => node => visitNode(node, function visitor(node: Node): Node { - if (isStringLiteral(node) && node.text === "change") return factory.createStringLiteral("changed"); - return visitEachChild(node, visitor, context); - }) - ]}, { - target: ScriptTarget.ES5, - module: ModuleKind.ES2015, - emitDecoratorMetadata: true, - experimentalDecorators: true - }); + }], {before: [ + context => node => ts.visitNode(node, function visitor(node: ts.Node): ts.Node { + if (ts.isStringLiteral(node) && node.text === "change") return ts.factory.createStringLiteral("changed"); + return ts.visitEachChild(node, visitor, context); + }) + ]}, { + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.ES2015, + emitDecoratorMetadata: true, + experimentalDecorators: true + }); - emitsCorrectly("sourceMapExternalSourceFiles", - [ - { - file: "source.ts", - // The text of length 'changed' is made to be on two lines so we know the line map change - text: `\`multi + emitsCorrectly("sourceMapExternalSourceFiles", + [ + { + file: "source.ts", + // The text of length 'changed' is made to be on two lines so we know the line map change + text: `\`multi line\` 'change'` - }, - ], - { - before: [ - context => node => visitNode(node, function visitor(node: Node): Node { - if (isStringLiteral(node) && node.text === "change") { - const text = "'changed'"; - const lineMap = computeLineStarts(text); - setSourceMapRange(node, { - pos: 0, end: text.length, source: { - text, - fileName: "another.html", - lineMap, - getLineAndCharacterOfPosition: pos => computeLineAndCharacterOfPosition(lineMap, pos) - } - }); - return node; - } - return visitEachChild(node, visitor, context); - }) - ] }, - { sourceMap: true } - ); - - emitsCorrectly("skipTriviaExternalSourceFiles", - [ - { - file: "source.ts", - // The source file contains preceding trivia (e.g. whitespace) to try to confuse the `skipSourceTrivia` function. - text: " original;" - }, - ], - { - before: [ - context => { - const transformSourceFile: Transformer = node => visitNode(node, function visitor(node: Node): Node { - if (isIdentifier(node) && node.text === "original") { - const newNode = factory.createIdentifier("changed"); - setSourceMapRange(newNode, { - pos: 0, - end: 7, - // Do not provide a custom skipTrivia function for `source`. - source: createSourceMapSource("another.html", "changed;") - }); - return newNode; + ], + { + before: [ + context => node => ts.visitNode(node, function visitor(node: ts.Node): ts.Node { + if (ts.isStringLiteral(node) && node.text === "change") { + const text = "'changed'"; + const lineMap = ts.computeLineStarts(text); + ts.setSourceMapRange(node, { + pos: 0, end: text.length, source: { + text, + fileName: "another.html", + lineMap, + getLineAndCharacterOfPosition: pos => ts.computeLineAndCharacterOfPosition(lineMap, pos) } - return visitEachChild(node, visitor, context); }); - return { - transformSourceFile, - transformBundle: node => factory.createBundle(map(node.sourceFiles, transformSourceFile), node.prepends), - }; + return node; } - ] + return ts.visitEachChild(node, visitor, context); + }) + ] + }, + { sourceMap: true } + ); + + emitsCorrectly("skipTriviaExternalSourceFiles", + [ + { + file: "source.ts", + // The source file contains preceding trivia (e.g. whitespace) to try to confuse the `skipSourceTrivia` function. + text: " original;" }, - { sourceMap: true, outFile: "source.js" } - ); + ], + { + before: [ + context => { + const transformSourceFile: ts.Transformer = node => ts.visitNode(node, function visitor(node: ts.Node): ts.Node { + if (ts.isIdentifier(node) && node.text === "original") { + const newNode = ts.factory.createIdentifier("changed"); + ts.setSourceMapRange(newNode, { + pos: 0, + end: 7, + // Do not provide a custom skipTrivia function for `source`. + source: ts.createSourceMapSource("another.html", "changed;") + }); + return newNode; + } + return ts.visitEachChild(node, visitor, context); + }); + return { + transformSourceFile, + transformBundle: node => ts.factory.createBundle(ts.map(node.sourceFiles, transformSourceFile), node.prepends), + }; + } + ] + }, + { sourceMap: true, outFile: "source.js" } + ); - }); -} +}); diff --git a/src/testRunner/unittests/debugDeprecation.ts b/src/testRunner/unittests/debugDeprecation.ts index 1687f24d40644..862c386d9481a 100644 --- a/src/testRunner/unittests/debugDeprecation.ts +++ b/src/testRunner/unittests/debugDeprecation.ts @@ -1,97 +1,97 @@ -namespace ts { - describe("unittests:: debugDeprecation", () => { - let loggingHost: LoggingHost | undefined; - beforeEach(() => { - loggingHost = Debug.loggingHost; - }); - afterEach(() => { - Debug.loggingHost = loggingHost; - loggingHost = undefined; - }); - describe("deprecateFunction", () => { - it("silent deprecation", () => { - const deprecation = Debug.deprecate(noop, { - warnAfter: "3.9", - typeScriptVersion: "3.8" - }); - let logWritten = false; - Debug.loggingHost = { - log() { - logWritten = true; - } - }; - deprecation(); - assert.isFalse(logWritten); +import * as ts from "../_namespaces/ts"; + +describe("unittests:: debugDeprecation", () => { + let loggingHost: ts.LoggingHost | undefined; + beforeEach(() => { + loggingHost = ts.Debug.loggingHost; + }); + afterEach(() => { + ts.Debug.loggingHost = loggingHost; + loggingHost = undefined; + }); + describe("deprecateFunction", () => { + it("silent deprecation", () => { + const deprecation = ts.Debug.deprecate(ts.noop, { + warnAfter: "3.9", + typeScriptVersion: "3.8" }); - it("warning deprecation with warnAfter", () => { - const deprecation = Debug.deprecate(noop, { - warnAfter: "3.9", - typeScriptVersion: "3.9" - }); - let logWritten = false; - Debug.loggingHost = { - log() { - logWritten = true; - } - }; - deprecation(); - assert.isTrue(logWritten); + let logWritten = false; + ts.Debug.loggingHost = { + log() { + logWritten = true; + } + }; + deprecation(); + assert.isFalse(logWritten); + }); + it("warning deprecation with warnAfter", () => { + const deprecation = ts.Debug.deprecate(ts.noop, { + warnAfter: "3.9", + typeScriptVersion: "3.9" }); - it("warning deprecation without warnAfter", () => { - const deprecation = Debug.deprecate(noop, { - typeScriptVersion: "3.9" - }); - let logWritten = false; - Debug.loggingHost = { - log() { - logWritten = true; - } - }; - deprecation(); - assert.isTrue(logWritten); + let logWritten = false; + ts.Debug.loggingHost = { + log() { + logWritten = true; + } + }; + deprecation(); + assert.isTrue(logWritten); + }); + it("warning deprecation without warnAfter", () => { + const deprecation = ts.Debug.deprecate(ts.noop, { + typeScriptVersion: "3.9" }); - it("warning deprecation writes once", () => { - const deprecation = Debug.deprecate(noop, { - typeScriptVersion: "3.9" - }); - let logWrites = 0; - Debug.loggingHost = { - log() { - logWrites++; - } - }; - deprecation(); - deprecation(); - assert.equal(logWrites, 1); + let logWritten = false; + ts.Debug.loggingHost = { + log() { + logWritten = true; + } + }; + deprecation(); + assert.isTrue(logWritten); + }); + it("warning deprecation writes once", () => { + const deprecation = ts.Debug.deprecate(ts.noop, { + typeScriptVersion: "3.9" }); - it("error deprecation with errorAfter", () => { - const deprecation = Debug.deprecate(noop, { - warnAfter: "3.8", - errorAfter: "3.9", - typeScriptVersion: "3.9" - }); - let logWritten = false; - Debug.loggingHost = { - log() { - logWritten = true; - } - }; - expect(deprecation).throws(); - assert.isFalse(logWritten); + let logWrites = 0; + ts.Debug.loggingHost = { + log() { + logWrites++; + } + }; + deprecation(); + deprecation(); + assert.equal(logWrites, 1); + }); + it("error deprecation with errorAfter", () => { + const deprecation = ts.Debug.deprecate(ts.noop, { + warnAfter: "3.8", + errorAfter: "3.9", + typeScriptVersion: "3.9" }); - it("error deprecation with error", () => { - const deprecation = Debug.deprecate(noop, { - error: true, - }); - let logWritten = false; - Debug.loggingHost = { - log() { - logWritten = true; - } - }; - expect(deprecation).throws(); - assert.isFalse(logWritten); + let logWritten = false; + ts.Debug.loggingHost = { + log() { + logWritten = true; + } + }; + expect(deprecation).throws(); + assert.isFalse(logWritten); + }); + it("error deprecation with error", () => { + const deprecation = ts.Debug.deprecate(ts.noop, { + error: true, }); + let logWritten = false; + ts.Debug.loggingHost = { + log() { + logWritten = true; + } + }; + expect(deprecation).throws(); + assert.isFalse(logWritten); }); }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/evaluation/arraySpread.ts b/src/testRunner/unittests/evaluation/arraySpread.ts index 61220a62caf40..e8f37ba755dde 100644 --- a/src/testRunner/unittests/evaluation/arraySpread.ts +++ b/src/testRunner/unittests/evaluation/arraySpread.ts @@ -1,3 +1,5 @@ +import * as evaluator from "../../_namespaces/evaluator"; + describe("unittests:: evaluation:: arraySpread", () => { it("array spread preserves side-effects", async () => { const result = evaluator.evaluateTypeScript(` diff --git a/src/testRunner/unittests/evaluation/asyncArrow.ts b/src/testRunner/unittests/evaluation/asyncArrow.ts index 04a7af613fa38..6521e89e2befd 100644 --- a/src/testRunner/unittests/evaluation/asyncArrow.ts +++ b/src/testRunner/unittests/evaluation/asyncArrow.ts @@ -1,3 +1,5 @@ +import * as evaluator from "../../_namespaces/evaluator"; + describe("unittests:: evaluation:: asyncArrowEvaluation", () => { // https://github.com/Microsoft/TypeScript/issues/24722 it("this capture (es5)", async () => { diff --git a/src/testRunner/unittests/evaluation/asyncGenerator.ts b/src/testRunner/unittests/evaluation/asyncGenerator.ts index 8ca531f0759a1..0da25e30698f8 100644 --- a/src/testRunner/unittests/evaluation/asyncGenerator.ts +++ b/src/testRunner/unittests/evaluation/asyncGenerator.ts @@ -1,3 +1,6 @@ +import * as evaluator from "../../_namespaces/evaluator"; +import * as ts from "../../_namespaces/ts"; + describe("unittests:: evaluation:: asyncGeneratorEvaluation", () => { it("return (es5)", async () => { const result = evaluator.evaluateTypeScript(` diff --git a/src/testRunner/unittests/evaluation/autoAccessors.ts b/src/testRunner/unittests/evaluation/autoAccessors.ts index 0873cd061e7f7..078110cb44cd5 100644 --- a/src/testRunner/unittests/evaluation/autoAccessors.ts +++ b/src/testRunner/unittests/evaluation/autoAccessors.ts @@ -1,3 +1,6 @@ +import * as ts from "../../_namespaces/ts"; +import * as evaluator from "../../_namespaces/evaluator"; + describe("unittests:: evaluation:: autoAccessors", () => { const editions = [ { name: "es2022", target: ts.ScriptTarget.ES2022 }, diff --git a/src/testRunner/unittests/evaluation/awaiter.ts b/src/testRunner/unittests/evaluation/awaiter.ts index 65cb4e0ead945..87007d8925934 100644 --- a/src/testRunner/unittests/evaluation/awaiter.ts +++ b/src/testRunner/unittests/evaluation/awaiter.ts @@ -1,3 +1,5 @@ +import * as evaluator from "../../_namespaces/evaluator"; + describe("unittests:: evaluation:: awaiter", () => { // NOTE: This could break if the ECMAScript spec ever changes the timing behavior for Promises (again) it("await (es5)", async () => { diff --git a/src/testRunner/unittests/evaluation/destructuring.ts b/src/testRunner/unittests/evaluation/destructuring.ts index dcff12970b65c..c52c4d4b0a61a 100644 --- a/src/testRunner/unittests/evaluation/destructuring.ts +++ b/src/testRunner/unittests/evaluation/destructuring.ts @@ -1,3 +1,6 @@ +import * as evaluator from "../../_namespaces/evaluator"; +import * as ts from "../../_namespaces/ts"; + describe("unittests:: evaluation:: destructuring", () => { // https://github.com/microsoft/TypeScript/issues/39205 describe("correct order for array destructuring evaluation and initializers", () => { diff --git a/src/testRunner/unittests/evaluation/externalModules.ts b/src/testRunner/unittests/evaluation/externalModules.ts index 18bb0fa043f80..04a0b55abbf34 100644 --- a/src/testRunner/unittests/evaluation/externalModules.ts +++ b/src/testRunner/unittests/evaluation/externalModules.ts @@ -1,3 +1,5 @@ +import * as evaluator from "../../_namespaces/evaluator"; + describe("unittests:: evaluation:: externalModules", () => { // https://github.com/microsoft/TypeScript/issues/35420 it("Correct 'this' in function exported from external module", async () => { diff --git a/src/testRunner/unittests/evaluation/forAwaitOf.ts b/src/testRunner/unittests/evaluation/forAwaitOf.ts index c541d155bfca1..270d1c234e71f 100644 --- a/src/testRunner/unittests/evaluation/forAwaitOf.ts +++ b/src/testRunner/unittests/evaluation/forAwaitOf.ts @@ -1,3 +1,6 @@ +import * as evaluator from "../../_namespaces/evaluator"; +import * as ts from "../../_namespaces/ts"; + describe("unittests:: evaluation:: forAwaitOfEvaluation", () => { it("sync (es5)", async () => { const result = evaluator.evaluateTypeScript(` diff --git a/src/testRunner/unittests/evaluation/forOf.ts b/src/testRunner/unittests/evaluation/forOf.ts index 9efdf16d351cd..5afe159495f54 100644 --- a/src/testRunner/unittests/evaluation/forOf.ts +++ b/src/testRunner/unittests/evaluation/forOf.ts @@ -1,3 +1,6 @@ +import * as evaluator from "../../_namespaces/evaluator"; +import * as ts from "../../_namespaces/ts"; + describe("unittests:: evaluation:: forOfEvaluation", () => { it("es5 over a array with no Symbol", () => { const result = evaluator.evaluateTypeScript(` diff --git a/src/testRunner/unittests/evaluation/generator.ts b/src/testRunner/unittests/evaluation/generator.ts index e97dff75dde79..3d04fe189172c 100644 --- a/src/testRunner/unittests/evaluation/generator.ts +++ b/src/testRunner/unittests/evaluation/generator.ts @@ -1,3 +1,6 @@ +import * as evaluator from "../../_namespaces/evaluator"; +import * as ts from "../../_namespaces/ts"; + describe("unittests:: evaluation:: generatorEvaluation", () => { it("throw before start (es5)", () => { const { gen, output } = evaluator.evaluateTypeScript(` diff --git a/src/testRunner/unittests/evaluation/objectRest.ts b/src/testRunner/unittests/evaluation/objectRest.ts index 272ba51ffbeb5..ebbaa7219a7f6 100644 --- a/src/testRunner/unittests/evaluation/objectRest.ts +++ b/src/testRunner/unittests/evaluation/objectRest.ts @@ -1,3 +1,5 @@ +import * as evaluator from "../../_namespaces/evaluator"; + describe("unittests:: evaluation:: objectRest", () => { // https://github.com/microsoft/TypeScript/issues/31469 it("side effects in property assignment", async () => { diff --git a/src/testRunner/unittests/evaluation/optionalCall.ts b/src/testRunner/unittests/evaluation/optionalCall.ts index f92190dfb72b9..db92162a3e14f 100644 --- a/src/testRunner/unittests/evaluation/optionalCall.ts +++ b/src/testRunner/unittests/evaluation/optionalCall.ts @@ -1,3 +1,5 @@ +import * as evaluator from "../../_namespaces/evaluator"; + describe("unittests:: evaluation:: optionalCall", () => { it("f?.()", async () => { const result = evaluator.evaluateTypeScript(` diff --git a/src/testRunner/unittests/evaluation/superInStaticInitializer.ts b/src/testRunner/unittests/evaluation/superInStaticInitializer.ts index da1dc5fe888d9..b6d858c69e72f 100644 --- a/src/testRunner/unittests/evaluation/superInStaticInitializer.ts +++ b/src/testRunner/unittests/evaluation/superInStaticInitializer.ts @@ -1,3 +1,6 @@ +import * as evaluator from "../../_namespaces/evaluator"; +import * as ts from "../../_namespaces/ts"; + describe("unittests:: evaluation:: superInStaticInitializer", () => { it("super-property-get in es2015", () => { const result = evaluator.evaluateTypeScript(` diff --git a/src/testRunner/unittests/evaluation/templateLiteral.ts b/src/testRunner/unittests/evaluation/templateLiteral.ts index 2a4e23ee879d4..65fabb9d4ccbb 100644 --- a/src/testRunner/unittests/evaluation/templateLiteral.ts +++ b/src/testRunner/unittests/evaluation/templateLiteral.ts @@ -1,3 +1,5 @@ +import * as evaluator from "../../_namespaces/evaluator"; + describe("unittests:: evaluation:: templateLiteral", () => { it("toString() over valueOf()", () => { const result = evaluator.evaluateTypeScript(` diff --git a/src/testRunner/unittests/evaluation/updateExpressionInModule.ts b/src/testRunner/unittests/evaluation/updateExpressionInModule.ts index 443a1fb71a11b..071a975b78856 100644 --- a/src/testRunner/unittests/evaluation/updateExpressionInModule.ts +++ b/src/testRunner/unittests/evaluation/updateExpressionInModule.ts @@ -1,3 +1,6 @@ +import * as evaluator from "../../_namespaces/evaluator"; +import * as ts from "../../_namespaces/ts"; + describe("unittests:: evaluation:: updateExpressionInModule", () => { // only run BigInt tests if BigInt is supported in the host environment const itIfBigInt = typeof BigInt === "function" ? it : it.skip; diff --git a/src/testRunner/unittests/factory.ts b/src/testRunner/unittests/factory.ts index 93c34c520a712..493f951dfca5b 100644 --- a/src/testRunner/unittests/factory.ts +++ b/src/testRunner/unittests/factory.ts @@ -1,120 +1,120 @@ -namespace ts { - describe("unittests:: FactoryAPI", () => { - function assertSyntaxKind(node: Node, expected: SyntaxKind) { - assert.strictEqual(node.kind, expected, `Actual: ${Debug.formatSyntaxKind(node.kind)} Expected: ${Debug.formatSyntaxKind(expected)}`); - } - describe("factory.createExportAssignment", () => { - it("parenthesizes default export if necessary", () => { - function checkExpression(expression: Expression) { - const node = factory.createExportAssignment( - /*modifiers*/ undefined, - /*isExportEquals*/ false, - expression, - ); - assertSyntaxKind(node.expression, SyntaxKind.ParenthesizedExpression); - } +import * as ts from "../_namespaces/ts"; - const clazz = factory.createClassExpression(/*modifiers*/ undefined, "C", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ - factory.createPropertyDeclaration([factory.createToken(SyntaxKind.StaticKeyword)], "prop", /*questionOrExclamationToken*/ undefined, /*type*/ undefined, factory.createStringLiteral("1")), - ]); - checkExpression(clazz); - checkExpression(factory.createPropertyAccessExpression(clazz, "prop")); - - const func = factory.createFunctionExpression(/*modifiers*/ undefined, /*asteriskToken*/ undefined, "fn", /*typeParameters*/ undefined, /*parameters*/ undefined, /*type*/ undefined, factory.createBlock([])); - checkExpression(func); - checkExpression(factory.createCallExpression(func, /*typeArguments*/ undefined, /*argumentsArray*/ undefined)); - checkExpression(factory.createTaggedTemplateExpression(func, /*typeArguments*/ undefined, factory.createNoSubstitutionTemplateLiteral(""))); +describe("unittests:: FactoryAPI", () => { + function assertSyntaxKind(node: ts.Node, expected: ts.SyntaxKind) { + assert.strictEqual(node.kind, expected, `Actual: ${ts.Debug.formatSyntaxKind(node.kind)} Expected: ${ts.Debug.formatSyntaxKind(expected)}`); + } + describe("factory.createExportAssignment", () => { + it("parenthesizes default export if necessary", () => { + function checkExpression(expression: ts.Expression) { + const node = ts.factory.createExportAssignment( + /*modifiers*/ undefined, + /*isExportEquals*/ false, + expression, + ); + assertSyntaxKind(node.expression, ts.SyntaxKind.ParenthesizedExpression); + } - checkExpression(factory.createBinaryExpression(factory.createStringLiteral("a"), SyntaxKind.CommaToken, factory.createStringLiteral("b"))); - checkExpression(factory.createCommaListExpression([factory.createStringLiteral("a"), factory.createStringLiteral("b")])); - }); - }); + const clazz = ts.factory.createClassExpression(/*modifiers*/ undefined, "C", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ + ts.factory.createPropertyDeclaration([ts.factory.createToken(ts.SyntaxKind.StaticKeyword)], "prop", /*questionOrExclamationToken*/ undefined, /*type*/ undefined, ts.factory.createStringLiteral("1")), + ]); + checkExpression(clazz); + checkExpression(ts.factory.createPropertyAccessExpression(clazz, "prop")); - describe("factory.createArrowFunction", () => { - it("parenthesizes concise body if necessary", () => { - function checkBody(body: ConciseBody) { - const node = factory.createArrowFunction( - /*modifiers*/ undefined, - /*typeParameters*/ undefined, - [], - /*type*/ undefined, - /*equalsGreaterThanToken*/ undefined, - body, - ); - assertSyntaxKind(node.body, SyntaxKind.ParenthesizedExpression); - } + const func = ts.factory.createFunctionExpression(/*modifiers*/ undefined, /*asteriskToken*/ undefined, "fn", /*typeParameters*/ undefined, /*parameters*/ undefined, /*type*/ undefined, ts.factory.createBlock([])); + checkExpression(func); + checkExpression(ts.factory.createCallExpression(func, /*typeArguments*/ undefined, /*argumentsArray*/ undefined)); + checkExpression(ts.factory.createTaggedTemplateExpression(func, /*typeArguments*/ undefined, ts.factory.createNoSubstitutionTemplateLiteral(""))); - checkBody(factory.createObjectLiteralExpression()); - checkBody(factory.createPropertyAccessExpression(factory.createObjectLiteralExpression(), "prop")); - checkBody(factory.createAsExpression(factory.createPropertyAccessExpression(factory.createObjectLiteralExpression(), "prop"), factory.createTypeReferenceNode("T", /*typeArguments*/ undefined))); - checkBody(factory.createNonNullExpression(factory.createPropertyAccessExpression(factory.createObjectLiteralExpression(), "prop"))); - checkBody(factory.createCommaListExpression([factory.createStringLiteral("a"), factory.createStringLiteral("b")])); - checkBody(factory.createBinaryExpression(factory.createStringLiteral("a"), SyntaxKind.CommaToken, factory.createStringLiteral("b"))); - }); + checkExpression(ts.factory.createBinaryExpression(ts.factory.createStringLiteral("a"), ts.SyntaxKind.CommaToken, ts.factory.createStringLiteral("b"))); + checkExpression(ts.factory.createCommaListExpression([ts.factory.createStringLiteral("a"), ts.factory.createStringLiteral("b")])); }); + }); - describe("createBinaryExpression", () => { - it("parenthesizes arrow function in RHS if necessary", () => { - const lhs = factory.createIdentifier("foo"); - const rhs = factory.createArrowFunction( + describe("factory.createArrowFunction", () => { + it("parenthesizes concise body if necessary", () => { + function checkBody(body: ts.ConciseBody) { + const node = ts.factory.createArrowFunction( /*modifiers*/ undefined, /*typeParameters*/ undefined, [], /*type*/ undefined, /*equalsGreaterThanToken*/ undefined, - factory.createBlock([]), + body, ); - function checkRhs(operator: BinaryOperator, expectParens: boolean) { - const node = factory.createBinaryExpression(lhs, operator, rhs); - assertSyntaxKind(node.right, expectParens ? SyntaxKind.ParenthesizedExpression : SyntaxKind.ArrowFunction); - } + assertSyntaxKind(node.body, ts.SyntaxKind.ParenthesizedExpression); + } - checkRhs(SyntaxKind.CommaToken, /*expectParens*/ false); - checkRhs(SyntaxKind.EqualsToken, /*expectParens*/ false); - checkRhs(SyntaxKind.PlusEqualsToken, /*expectParens*/ false); - checkRhs(SyntaxKind.BarBarToken, /*expectParens*/ true); - checkRhs(SyntaxKind.AmpersandAmpersandToken, /*expectParens*/ true); - checkRhs(SyntaxKind.QuestionQuestionToken, /*expectParens*/ true); - checkRhs(SyntaxKind.EqualsEqualsToken, /*expectParens*/ true); - checkRhs(SyntaxKind.BarBarEqualsToken, /*expectParens*/ false); - checkRhs(SyntaxKind.AmpersandAmpersandEqualsToken, /*expectParens*/ false); - checkRhs(SyntaxKind.QuestionQuestionEqualsToken, /*expectParens*/ false); - }); + checkBody(ts.factory.createObjectLiteralExpression()); + checkBody(ts.factory.createPropertyAccessExpression(ts.factory.createObjectLiteralExpression(), "prop")); + checkBody(ts.factory.createAsExpression(ts.factory.createPropertyAccessExpression(ts.factory.createObjectLiteralExpression(), "prop"), ts.factory.createTypeReferenceNode("T", /*typeArguments*/ undefined))); + checkBody(ts.factory.createNonNullExpression(ts.factory.createPropertyAccessExpression(ts.factory.createObjectLiteralExpression(), "prop"))); + checkBody(ts.factory.createCommaListExpression([ts.factory.createStringLiteral("a"), ts.factory.createStringLiteral("b")])); + checkBody(ts.factory.createBinaryExpression(ts.factory.createStringLiteral("a"), ts.SyntaxKind.CommaToken, ts.factory.createStringLiteral("b"))); }); + }); + + describe("createBinaryExpression", () => { + it("parenthesizes arrow function in RHS if necessary", () => { + const lhs = ts.factory.createIdentifier("foo"); + const rhs = ts.factory.createArrowFunction( + /*modifiers*/ undefined, + /*typeParameters*/ undefined, + [], + /*type*/ undefined, + /*equalsGreaterThanToken*/ undefined, + ts.factory.createBlock([]), + ); + function checkRhs(operator: ts.BinaryOperator, expectParens: boolean) { + const node = ts.factory.createBinaryExpression(lhs, operator, rhs); + assertSyntaxKind(node.right, expectParens ? ts.SyntaxKind.ParenthesizedExpression : ts.SyntaxKind.ArrowFunction); + } - describe("deprecations", () => { - beforeEach(() => { - Debug.enableDeprecationWarnings = false; - }); + checkRhs(ts.SyntaxKind.CommaToken, /*expectParens*/ false); + checkRhs(ts.SyntaxKind.EqualsToken, /*expectParens*/ false); + checkRhs(ts.SyntaxKind.PlusEqualsToken, /*expectParens*/ false); + checkRhs(ts.SyntaxKind.BarBarToken, /*expectParens*/ true); + checkRhs(ts.SyntaxKind.AmpersandAmpersandToken, /*expectParens*/ true); + checkRhs(ts.SyntaxKind.QuestionQuestionToken, /*expectParens*/ true); + checkRhs(ts.SyntaxKind.EqualsEqualsToken, /*expectParens*/ true); + checkRhs(ts.SyntaxKind.BarBarEqualsToken, /*expectParens*/ false); + checkRhs(ts.SyntaxKind.AmpersandAmpersandEqualsToken, /*expectParens*/ false); + checkRhs(ts.SyntaxKind.QuestionQuestionEqualsToken, /*expectParens*/ false); + }); + }); - afterEach(() => { - Debug.enableDeprecationWarnings = true; - }); + describe("deprecations", () => { + beforeEach(() => { + ts.Debug.enableDeprecationWarnings = false; + }); - // https://github.com/microsoft/TypeScript/issues/50259 - it("deprecated createConstructorDeclaration overload does not throw", () => { - const body = factory.createBlock([]); - assert.doesNotThrow(() => factory.createConstructorDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*parameters*/ [], - body, - )); - }); + afterEach(() => { + ts.Debug.enableDeprecationWarnings = true; + }); - // https://github.com/microsoft/TypeScript/issues/50259 - it("deprecated updateConstructorDeclaration overload does not throw", () => { - const body = factory.createBlock([]); - const ctor = factory.createConstructorDeclaration(/*modifiers*/ undefined, [], body); - assert.doesNotThrow(() => factory.updateConstructorDeclaration( - ctor, - ctor.decorators, - ctor.modifiers, - ctor.parameters, - ctor.body, - )); - }); + // https://github.com/microsoft/TypeScript/issues/50259 + it("deprecated createConstructorDeclaration overload does not throw", () => { + const body = ts.factory.createBlock([]); + assert.doesNotThrow(() => ts.factory.createConstructorDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*parameters*/ [], + body, + )); }); + // https://github.com/microsoft/TypeScript/issues/50259 + it("deprecated updateConstructorDeclaration overload does not throw", () => { + const body = ts.factory.createBlock([]); + const ctor = ts.factory.createConstructorDeclaration(/*modifiers*/ undefined, [], body); + assert.doesNotThrow(() => ts.factory.updateConstructorDeclaration( + ctor, + ctor.decorators, + ctor.modifiers, + ctor.parameters, + ctor.body, + )); + }); }); -} + +}); diff --git a/src/testRunner/unittests/helpers.ts b/src/testRunner/unittests/helpers.ts new file mode 100644 index 0000000000000..7706b16ab1a57 --- /dev/null +++ b/src/testRunner/unittests/helpers.ts @@ -0,0 +1,241 @@ +import * as ts from "../_namespaces/ts"; + +const enum ChangedPart { + references = 1 << 0, + importsAndExports = 1 << 1, + program = 1 << 2 +} + +export const newLine = "\r\n"; + +export interface SourceFileWithText extends ts.SourceFile { + sourceText?: SourceText; +} + +export interface NamedSourceText { + name: string; + text: SourceText; +} + +export interface ProgramWithSourceTexts extends ts.Program { + sourceTexts?: readonly NamedSourceText[]; + host: TestCompilerHost; +} + +export interface TestCompilerHost extends ts.CompilerHost { + getTrace(): string[]; +} + +export class SourceText implements ts.IScriptSnapshot { + private fullText: string | undefined; + + constructor(private references: string, + private importsAndExports: string, + private program: string, + private changedPart: ChangedPart = 0, + private version = 0) { + } + + static New(references: string, importsAndExports: string, program: string): SourceText { + ts.Debug.assert(references !== undefined); + ts.Debug.assert(importsAndExports !== undefined); + ts.Debug.assert(program !== undefined); + return new SourceText(references + newLine, importsAndExports + newLine, program || ""); + } + + public getVersion(): number { + return this.version; + } + + public updateReferences(newReferences: string): SourceText { + ts.Debug.assert(newReferences !== undefined); + return new SourceText(newReferences + newLine, this.importsAndExports, this.program, this.changedPart | ChangedPart.references, this.version + 1); + } + public updateImportsAndExports(newImportsAndExports: string): SourceText { + ts.Debug.assert(newImportsAndExports !== undefined); + return new SourceText(this.references, newImportsAndExports + newLine, this.program, this.changedPart | ChangedPart.importsAndExports, this.version + 1); + } + public updateProgram(newProgram: string): SourceText { + ts.Debug.assert(newProgram !== undefined); + return new SourceText(this.references, this.importsAndExports, newProgram, this.changedPart | ChangedPart.program, this.version + 1); + } + + public getFullText() { + return this.fullText || (this.fullText = this.references + this.importsAndExports + this.program); + } + + public getText(start: number, end: number): string { + return this.getFullText().substring(start, end); + } + + getLength(): number { + return this.getFullText().length; + } + + getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange { + const oldText = oldSnapshot as SourceText; + let oldSpan: ts.TextSpan; + let newLength: number; + switch (oldText.changedPart ^ this.changedPart) { + case ChangedPart.references: + oldSpan = ts.createTextSpan(0, oldText.references.length); + newLength = this.references.length; + break; + case ChangedPart.importsAndExports: + oldSpan = ts.createTextSpan(oldText.references.length, oldText.importsAndExports.length); + newLength = this.importsAndExports.length; + break; + case ChangedPart.program: + oldSpan = ts.createTextSpan(oldText.references.length + oldText.importsAndExports.length, oldText.program.length); + newLength = this.program.length; + break; + default: + return ts.Debug.fail("Unexpected change"); + } + + return ts.createTextChangeRange(oldSpan, newLength); + } +} + +function createSourceFileWithText(fileName: string, sourceText: SourceText, target: ts.ScriptTarget) { + const file = ts.createSourceFile(fileName, sourceText.getFullText(), target) as SourceFileWithText; + file.sourceText = sourceText; + file.version = "" + sourceText.getVersion(); + return file; +} + +export function createTestCompilerHost(texts: readonly NamedSourceText[], target: ts.ScriptTarget, oldProgram?: ProgramWithSourceTexts, useGetSourceFileByPath?: boolean) { + const files = ts.arrayToMap(texts, t => t.name, t => { + if (oldProgram) { + let oldFile = oldProgram.getSourceFile(t.name) as SourceFileWithText; + if (oldFile && oldFile.redirectInfo) { + oldFile = oldFile.redirectInfo.unredirected; + } + if (oldFile && oldFile.sourceText!.getVersion() === t.text.getVersion()) { + return oldFile; + } + } + return createSourceFileWithText(t.name, t.text, target); + }); + const useCaseSensitiveFileNames = ts.sys && ts.sys.useCaseSensitiveFileNames; + const getCanonicalFileName = ts.createGetCanonicalFileName(useCaseSensitiveFileNames); + const trace: string[] = []; + const result: TestCompilerHost = { + trace: s => trace.push(s), + getTrace: () => trace, + getSourceFile: fileName => files.get(fileName), + getDefaultLibFileName: () => "lib.d.ts", + writeFile: ts.notImplemented, + getCurrentDirectory: () => "", + getDirectories: () => [], + getCanonicalFileName, + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, + getNewLine: () => ts.sys ? ts.sys.newLine : newLine, + fileExists: fileName => files.has(fileName), + readFile: fileName => { + const file = files.get(fileName); + return file && file.text; + }, + }; + if (useGetSourceFileByPath) { + const filesByPath = ts.mapEntries(files, (fileName, file) => [ts.toPath(fileName, "", getCanonicalFileName), file]); + result.getSourceFileByPath = (_fileName, path) => filesByPath.get(path); + } + return result; +} + +export function newProgram(texts: NamedSourceText[], rootNames: string[], options: ts.CompilerOptions, useGetSourceFileByPath?: boolean): ProgramWithSourceTexts { + const host = createTestCompilerHost(texts, options.target!, /*oldProgram*/ undefined, useGetSourceFileByPath); + const program = ts.createProgram(rootNames, options, host) as ProgramWithSourceTexts; + program.sourceTexts = texts; + program.host = host; + return program; +} + +export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: readonly string[], options: ts.CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[], useGetSourceFileByPath?: boolean) { + if (!newTexts) { + newTexts = oldProgram.sourceTexts!.slice(0); + } + updater(newTexts); + const host = createTestCompilerHost(newTexts, options.target!, oldProgram, useGetSourceFileByPath); + const program = ts.createProgram(rootNames, options, host, oldProgram) as ProgramWithSourceTexts; + program.sourceTexts = newTexts; + program.host = host; + return program; +} + +export function updateProgramText(files: readonly NamedSourceText[], fileName: string, newProgramText: string) { + const file = ts.find(files, f => f.name === fileName)!; + file.text = file.text.updateProgram(newProgramText); +} + +export function checkResolvedTypeDirective(actual: ts.ResolvedTypeReferenceDirective, expected: ts.ResolvedTypeReferenceDirective) { + assert.equal(actual.resolvedFileName, expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`); + assert.equal(actual.primary, expected.primary, `'primary': expected '${actual.primary}' to be equal to '${expected.primary}'`); + return true; +} + +function checkCache(caption: string, program: ts.Program, fileName: string, expectedContent: ts.ESMap | undefined, getCache: (f: ts.SourceFile) => ts.ModeAwareCache | undefined, entryChecker: (expected: T, original: T) => boolean): void { + const file = program.getSourceFile(fileName); + assert.isTrue(file !== undefined, `cannot find file ${fileName}`); + const cache = getCache(file!); + if (expectedContent === undefined) { + assert.isTrue(cache === undefined, `expected ${caption} to be undefined`); + } + else { + assert.isTrue(cache !== undefined, `expected ${caption} to be set`); + assert.isTrue(mapEqualToCache(expectedContent, cache!, entryChecker), `contents of ${caption} did not match the expected contents.`); + } +} + +/** True if the maps have the same keys and values. */ +function mapEqualToCache(left: ts.ESMap, right: ts.ModeAwareCache, valuesAreEqual?: (left: T, right: T) => boolean): boolean { + if (left as any === right) return true; // given the type mismatch (the tests never pass a cache), this'll never be true + if (!left || !right) return false; + const someInLeftHasNoMatch = ts.forEachEntry(left, (leftValue, leftKey) => { + if (!right.has(leftKey, /*mode*/ undefined)) return true; + const rightValue = right.get(leftKey, /*mode*/ undefined)!; + return !(valuesAreEqual ? valuesAreEqual(leftValue, rightValue) : leftValue === rightValue); + }); + if (someInLeftHasNoMatch) return false; + let someInRightHasNoMatch = false; + right.forEach((_, rightKey) => someInRightHasNoMatch = someInRightHasNoMatch || !left.has(rightKey)); + return !someInRightHasNoMatch; +} + +export function checkResolvedModulesCache(program: ts.Program, fileName: string, expectedContent: ts.ESMap | undefined): void { + checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, ts.checkResolvedModule); +} + +export function checkResolvedTypeDirectivesCache(program: ts.Program, fileName: string, expectedContent: ts.ESMap | undefined): void { + checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective); +} + +export function createResolvedModule(resolvedFileName: string, isExternalLibraryImport = false): ts.ResolvedModuleFull { + return { resolvedFileName, extension: ts.extensionFromPath(resolvedFileName), isExternalLibraryImport }; +} + +export function checkResolvedModule(actual: ts.ResolvedModuleFull | undefined, expected: ts.ResolvedModuleFull | undefined): boolean { + if (!expected) { + if (actual) { + assert.fail(actual, expected, "expected resolved module to be undefined"); + return false; + } + return true; + } + else if (!actual) { + assert.fail(actual, expected, "expected resolved module to be defined"); + return false; + } + + assert.isTrue(actual.resolvedFileName === expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`); + assert.isTrue(actual.extension === expected.extension, `'ext': expected '${actual.extension}' to be equal to '${expected.extension}'`); + assert.isTrue(actual.isExternalLibraryImport === expected.isExternalLibraryImport, `'isExternalLibraryImport': expected '${actual.isExternalLibraryImport}' to be equal to '${expected.isExternalLibraryImport}'`); + return true; +} + +export function checkResolvedModuleWithFailedLookupLocations(actual: ts.ResolvedModuleWithFailedLookupLocations, expectedResolvedModule: ts.ResolvedModuleFull, expectedFailedLookupLocations: string[]): void { + assert.isTrue(actual.resolvedModule !== undefined, "module should be resolved"); + checkResolvedModule(actual.resolvedModule, expectedResolvedModule); + assert.deepEqual(actual.failedLookupLocations, expectedFailedLookupLocations, `Failed lookup locations should match - expected has ${expectedFailedLookupLocations.length}, actual has ${actual.failedLookupLocations.length}`); +} diff --git a/src/testRunner/unittests/incrementalParser.ts b/src/testRunner/unittests/incrementalParser.ts index 07be24b4b5b4a..6c3c896334e31 100644 --- a/src/testRunner/unittests/incrementalParser.ts +++ b/src/testRunner/unittests/incrementalParser.ts @@ -1,844 +1,846 @@ -namespace ts { - function withChange(text: IScriptSnapshot, start: number, length: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { - const contents = getSnapshotText(text); - const newContents = contents.substr(0, start) + newText + contents.substring(start + length); +import * as ts from "../_namespaces/ts"; +import * as Utils from "../_namespaces/Utils"; - return { text: ScriptSnapshot.fromString(newContents), textChangeRange: createTextChangeRange(createTextSpan(start, length), newText.length) }; - } +function withChange(text: ts.IScriptSnapshot, start: number, length: number, newText: string): { text: ts.IScriptSnapshot; textChangeRange: ts.TextChangeRange; } { + const contents = ts.getSnapshotText(text); + const newContents = contents.substr(0, start) + newText + contents.substring(start + length); - function withInsert(text: IScriptSnapshot, start: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { - return withChange(text, start, 0, newText); - } + return { text: ts.ScriptSnapshot.fromString(newContents), textChangeRange: ts.createTextChangeRange(ts.createTextSpan(start, length), newText.length) }; +} - function withDelete(text: IScriptSnapshot, start: number, length: number): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { - return withChange(text, start, length, ""); - } +function withInsert(text: ts.IScriptSnapshot, start: number, newText: string): { text: ts.IScriptSnapshot; textChangeRange: ts.TextChangeRange; } { + return withChange(text, start, 0, newText); +} - function createTree(text: IScriptSnapshot, version: string) { - return createLanguageServiceSourceFile(/*fileName:*/ "", text, ScriptTarget.Latest, version, /*setNodeParents:*/ true); - } +function withDelete(text: ts.IScriptSnapshot, start: number, length: number): { text: ts.IScriptSnapshot; textChangeRange: ts.TextChangeRange; } { + return withChange(text, start, length, ""); +} - function assertSameDiagnostics(file1: SourceFile, file2: SourceFile) { - const diagnostics1 = file1.parseDiagnostics; - const diagnostics2 = file2.parseDiagnostics; - - assert.equal(diagnostics1.length, diagnostics2.length, "diagnostics1.length !== diagnostics2.length"); - for (let i = 0; i < diagnostics1.length; i++) { - const d1 = diagnostics1[i]; - const d2 = diagnostics2[i]; - - assert.equal(d1.file, file1, "d1.file !== file1"); - assert.equal(d2.file, file2, "d2.file !== file2"); - assert.equal(d1.start, d2.start, "d1.start !== d2.start"); - assert.equal(d1.length, d2.length, "d1.length !== d2.length"); - assert.equal(d1.messageText, d2.messageText, "d1.messageText !== d2.messageText"); - assert.equal(d1.category, d2.category, "d1.category !== d2.category"); - assert.equal(d1.code, d2.code, "d1.code !== d2.code"); - } - } +function createTree(text: ts.IScriptSnapshot, version: string) { + return ts.createLanguageServiceSourceFile(/*fileName:*/ "", text, ts.ScriptTarget.Latest, version, /*setNodeParents:*/ true); +} - // NOTE: 'reusedElements' is the expected count of elements reused from the old tree to the new - // tree. It may change as we tweak the parser. If the count increases then that should always - // be a good thing. If it decreases, that's not great (less reusability), but that may be - // unavoidable. If it does decrease an investigation should be done to make sure that things - // are still ok and we're still appropriately reusing most of the tree. - function compareTrees(oldText: IScriptSnapshot, newText: IScriptSnapshot, textChangeRange: TextChangeRange, expectedReusedElements: number, oldTree?: SourceFile) { - oldTree = oldTree || createTree(oldText, /*version:*/ "."); - Utils.assertInvariants(oldTree, /*parent:*/ undefined); +function assertSameDiagnostics(file1: ts.SourceFile, file2: ts.SourceFile) { + const diagnostics1 = file1.parseDiagnostics; + const diagnostics2 = file2.parseDiagnostics; + + assert.equal(diagnostics1.length, diagnostics2.length, "diagnostics1.length !== diagnostics2.length"); + for (let i = 0; i < diagnostics1.length; i++) { + const d1 = diagnostics1[i]; + const d2 = diagnostics2[i]; + + assert.equal(d1.file, file1, "d1.file !== file1"); + assert.equal(d2.file, file2, "d2.file !== file2"); + assert.equal(d1.start, d2.start, "d1.start !== d2.start"); + assert.equal(d1.length, d2.length, "d1.length !== d2.length"); + assert.equal(d1.messageText, d2.messageText, "d1.messageText !== d2.messageText"); + assert.equal(d1.category, d2.category, "d1.category !== d2.category"); + assert.equal(d1.code, d2.code, "d1.code !== d2.code"); + } +} - // Create a tree for the new text, in a non-incremental fashion. - const newTree = createTree(newText, oldTree.version + "."); - Utils.assertInvariants(newTree, /*parent:*/ undefined); +// NOTE: 'reusedElements' is the expected count of elements reused from the old tree to the new +// tree. It may change as we tweak the parser. If the count increases then that should always +// be a good thing. If it decreases, that's not great (less reusability), but that may be +// unavoidable. If it does decrease an investigation should be done to make sure that things +// are still ok and we're still appropriately reusing most of the tree. +function compareTrees(oldText: ts.IScriptSnapshot, newText: ts.IScriptSnapshot, textChangeRange: ts.TextChangeRange, expectedReusedElements: number, oldTree?: ts.SourceFile) { + oldTree = oldTree || createTree(oldText, /*version:*/ "."); + Utils.assertInvariants(oldTree, /*parent:*/ undefined); - // Create a tree for the new text, in an incremental fashion. - const incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", textChangeRange); - Utils.assertInvariants(incrementalNewTree, /*parent:*/ undefined); + // Create a tree for the new text, in a non-incremental fashion. + const newTree = createTree(newText, oldTree.version + "."); + Utils.assertInvariants(newTree, /*parent:*/ undefined); - // We should get the same tree when doign a full or incremental parse. - Utils.assertStructuralEquals(newTree, incrementalNewTree); + // Create a tree for the new text, in an incremental fashion. + const incrementalNewTree = ts.updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", textChangeRange); + Utils.assertInvariants(incrementalNewTree, /*parent:*/ undefined); - // We should also get the exact same set of diagnostics. - assertSameDiagnostics(newTree, incrementalNewTree); + // We should get the same tree when doign a full or incremental parse. + Utils.assertStructuralEquals(newTree, incrementalNewTree); - // There should be no reused nodes between two trees that are fully parsed. - assert.isTrue(reusedElements(oldTree, newTree) === 0); + // We should also get the exact same set of diagnostics. + assertSameDiagnostics(newTree, incrementalNewTree); - assert.equal(newTree.fileName, incrementalNewTree.fileName, "newTree.fileName !== incrementalNewTree.fileName"); - assert.equal(newTree.text, incrementalNewTree.text, "newTree.text !== incrementalNewTree.text"); + // There should be no reused nodes between two trees that are fully parsed. + assert.isTrue(reusedElements(oldTree, newTree) === 0); - if (expectedReusedElements !== -1) { - const actualReusedCount = reusedElements(oldTree, incrementalNewTree); - assert.equal(actualReusedCount, expectedReusedElements, actualReusedCount + " !== " + expectedReusedElements); - } + assert.equal(newTree.fileName, incrementalNewTree.fileName, "newTree.fileName !== incrementalNewTree.fileName"); + assert.equal(newTree.text, incrementalNewTree.text, "newTree.text !== incrementalNewTree.text"); - return { oldTree, newTree, incrementalNewTree }; + if (expectedReusedElements !== -1) { + const actualReusedCount = reusedElements(oldTree, incrementalNewTree); + assert.equal(actualReusedCount, expectedReusedElements, actualReusedCount + " !== " + expectedReusedElements); } - function reusedElements(oldNode: SourceFile, newNode: SourceFile): number { - const allOldElements = collectElements(oldNode); - const allNewElements = collectElements(newNode); + return { oldTree, newTree, incrementalNewTree }; +} - return filter(allOldElements, v => contains(allNewElements, v)).length; - } +function reusedElements(oldNode: ts.SourceFile, newNode: ts.SourceFile): number { + const allOldElements = collectElements(oldNode); + const allNewElements = collectElements(newNode); + + return ts.filter(allOldElements, v => ts.contains(allNewElements, v)).length; +} - function collectElements(node: Node) { - const result: Node[] = []; - visit(node); - return result; +function collectElements(node: ts.Node) { + const result: ts.Node[] = []; + visit(node); + return result; - function visit(node: Node) { - result.push(node); - forEachChild(node, visit); - } + function visit(node: ts.Node) { + result.push(node); + ts.forEachChild(node, visit); } +} + +function deleteCode(source: string, index: number, toDelete: string) { + const repeat = toDelete.length; + let oldTree = createTree(ts.ScriptSnapshot.fromString(source), /*version:*/ "."); + for (let i = 0; i < repeat; i++) { + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, 1); + const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; - function deleteCode(source: string, index: number, toDelete: string) { - const repeat = toDelete.length; - let oldTree = createTree(ScriptSnapshot.fromString(source), /*version:*/ "."); - for (let i = 0; i < repeat; i++) { - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, 1); - const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; - - source = getSnapshotText(newTextAndChange.text); - oldTree = newTree; - } + source = ts.getSnapshotText(newTextAndChange.text); + oldTree = newTree; } +} - function insertCode(source: string, index: number, toInsert: string) { - const repeat = toInsert.length; - let oldTree = createTree(ScriptSnapshot.fromString(source), /*version:*/ "."); - for (let i = 0; i < repeat; i++) { - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + i, toInsert.charAt(i)); - const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; - - source = getSnapshotText(newTextAndChange.text); - oldTree = newTree; - } +function insertCode(source: string, index: number, toInsert: string) { + const repeat = toInsert.length; + let oldTree = createTree(ts.ScriptSnapshot.fromString(source), /*version:*/ "."); + for (let i = 0; i < repeat; i++) { + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + i, toInsert.charAt(i)); + const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; + + source = ts.getSnapshotText(newTextAndChange.text); + oldTree = newTree; } +} - describe("unittests:: Incremental Parser", () => { - it("Inserting into method", () => { - const source = "class C {\r\n" + - " public foo1() { }\r\n" + - " public foo2() {\r\n" + - " return 1;\r\n" + - " }\r\n" + - " public foo3() { }\r\n" + - "}"; - - const oldText = ScriptSnapshot.fromString(source); - const semicolonIndex = source.indexOf(";"); - const newTextAndChange = withInsert(oldText, semicolonIndex, " + 1"); - - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); - }); +describe("unittests:: Incremental Parser", () => { + it("Inserting into method", () => { + const source = "class C {\r\n" + + " public foo1() { }\r\n" + + " public foo2() {\r\n" + + " return 1;\r\n" + + " }\r\n" + + " public foo3() { }\r\n" + + "}"; + + const oldText = ts.ScriptSnapshot.fromString(source); + const semicolonIndex = source.indexOf(";"); + const newTextAndChange = withInsert(oldText, semicolonIndex, " + 1"); + + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); + }); - it("Deleting from method", () => { - const source = "class C {\r\n" + - " public foo1() { }\r\n" + - " public foo2() {\r\n" + - " return 1 + 1;\r\n" + - " }\r\n" + - " public foo3() { }\r\n" + - "}"; + it("Deleting from method", () => { + const source = "class C {\r\n" + + " public foo1() { }\r\n" + + " public foo2() {\r\n" + + " return 1 + 1;\r\n" + + " }\r\n" + + " public foo3() { }\r\n" + + "}"; - const index = source.indexOf("+ 1"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, "+ 1".length); + const index = source.indexOf("+ 1"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, "+ 1".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); + }); - it("Regular expression 1", () => { - const source = "class C { public foo1() { /; } public foo2() { return 1;} public foo3() { } }"; + it("Regular expression 1", () => { + const source = "class C { public foo1() { /; } public foo2() { return 1;} public foo3() { } }"; - const semicolonIndex = source.indexOf(";}"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); + const semicolonIndex = source.indexOf(";}"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Regular expression 2", () => { - const source = "class C { public foo1() { ; } public foo2() { return 1/;} public foo3() { } }"; + it("Regular expression 2", () => { + const source = "class C { public foo1() { ; } public foo2() { return 1/;} public foo3() { } }"; - const semicolonIndex = source.indexOf(";"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); + const semicolonIndex = source.indexOf(";"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Comment 1", () => { - const source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; + it("Comment 1", () => { + const source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; - const semicolonIndex = source.indexOf(";"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); + const semicolonIndex = source.indexOf(";"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Comment 2", () => { - const source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; + it("Comment 2", () => { + const source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, 0, "//"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, 0, "//"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Comment 3", () => { - const source = "//class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; + it("Comment 3", () => { + const source = "//class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, 0, 2); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, 0, 2); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Comment 4", () => { - const source = "class C { public foo1() { /; } public foo2() { */ return 1; } public foo3() { } }"; + it("Comment 4", () => { + const source = "class C { public foo1() { /; } public foo2() { */ return 1; } public foo3() { } }"; - const index = source.indexOf(";"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, "*"); + const index = source.indexOf(";"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index, "*"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Parameter 1", () => { - // Should be able to reuse all the parameters. - const source = "class C {\r\n" + - " public foo2(a, b, c, d) {\r\n" + - " return 1;\r\n" + - " }\r\n" + - "}"; + it("Parameter 1", () => { + // Should be able to reuse all the parameters. + const source = "class C {\r\n" + + " public foo2(a, b, c, d) {\r\n" + + " return 1;\r\n" + + " }\r\n" + + "}"; - const semicolonIndex = source.indexOf(";"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, semicolonIndex, " + 1"); + const semicolonIndex = source.indexOf(";"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, semicolonIndex, " + 1"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8); + }); - it("Type member 1", () => { - // Should be able to reuse most of the type members. - const source = "interface I { a: number; b: string; (c): d; new (e): f; g(): h }"; + it("Type member 1", () => { + // Should be able to reuse most of the type members. + const source = "interface I { a: number; b: string; (c): d; new (e): f; g(): h }"; - const index = source.indexOf(": string"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, "?"); + const index = source.indexOf(": string"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index, "?"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); + }); - it("Enum element 1", () => { - // Should be able to reuse most of the enum elements. - const source = "enum E { a = 1, b = 1 << 1, c = 3, e = 4, f = 5, g = 7, h = 8, i = 9, j = 10 }"; + it("Enum element 1", () => { + // Should be able to reuse most of the enum elements. + const source = "enum E { a = 1, b = 1 << 1, c = 3, e = 4, f = 5, g = 7, h = 8, i = 9, j = 10 }"; - const index = source.indexOf("<<"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, 2, "+"); + const index = source.indexOf("<<"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, index, 2, "+"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 24); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 24); + }); - it("Strict mode 1", () => { - const source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();"; + it("Strict mode 1", () => { + const source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, 0, "'strict';\r\n"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, 0, "'strict';\r\n"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Strict mode 2", () => { - const source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();"; + it("Strict mode 2", () => { + const source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, 0, "'use strict';\r\n"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, 0, "'use strict';\r\n"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Strict mode 3", () => { - const source = "'strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();"; + it("Strict mode 3", () => { + const source = "'strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();"; - const index = source.indexOf("f"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, 0, index); + const index = source.indexOf("f"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, 0, index); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Strict mode 4", () => { - const source = "'use strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();"; + it("Strict mode 4", () => { + const source = "'use strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();"; - const index = source.indexOf("f"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, 0, index); + const index = source.indexOf("f"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, 0, index); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Strict mode 5", () => { - const source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; + it("Strict mode 5", () => { + const source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; - const index = source.indexOf("b"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, 6, "strict"); + const index = source.indexOf("b"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, index, 6, "strict"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 27); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 27); + }); - it("Strict mode 6", () => { - const source = "'use strict';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; + it("Strict mode 6", () => { + const source = "'use strict';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; - const index = source.indexOf("s"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, 6, "blahhh"); + const index = source.indexOf("s"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, index, 6, "blahhh"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 27); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 27); + }); - it("Strict mode 7", () => { - const source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; + it("Strict mode 7", () => { + const source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n"; - const index = source.indexOf("f"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, 0, index); + const index = source.indexOf("f"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, 0, index); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 24); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 24); + }); - it("Parenthesized expression to arrow function 1", () => { - const source = "var v = (a, b, c, d, e)"; + it("Parenthesized expression to arrow function 1", () => { + const source = "var v = (a, b, c, d, e)"; - const index = source.indexOf("a"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 1, ":"); + const index = source.indexOf("a"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 1, ":"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Parenthesized expression to arrow function 2", () => { - const source = "var v = (a, b) = c"; + it("Parenthesized expression to arrow function 2", () => { + const source = "var v = (a, b) = c"; - const index = source.indexOf("= c") + 1; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, ">"); + const index = source.indexOf("= c") + 1; + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index, ">"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Arrow function to parenthesized expression 1", () => { - const source = "var v = (a:, b, c, d, e)"; + it("Arrow function to parenthesized expression 1", () => { + const source = "var v = (a:, b, c, d, e)"; - const index = source.indexOf(":"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, 1); + const index = source.indexOf(":"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, 1); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Arrow function to parenthesized expression 2", () => { - const source = "var v = (a, b) => c"; + it("Arrow function to parenthesized expression 2", () => { + const source = "var v = (a, b) => c"; - const index = source.indexOf(">"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, 1); + const index = source.indexOf(">"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, 1); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Speculative generic lookahead 1", () => { - const source = "var v = Fe"; + it("Speculative generic lookahead 1", () => { + const source = "var v = Fe"; - const index = source.indexOf("b"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 1, ",x"); + const index = source.indexOf("b"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 1, ",x"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + }); - it("Speculative generic lookahead 2", () => { - const source = "var v = Fe"; + it("Speculative generic lookahead 2", () => { + const source = "var v = Fe"; - const index = source.indexOf("b"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 1, ",x"); + const index = source.indexOf("b"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 1, ",x"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + }); - it("Speculative generic lookahead 3", () => { - const source = "var v = Fe"; + it("Speculative generic lookahead 3", () => { + const source = "var v = Fe"; - const index = source.indexOf("b"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 1, ",x"); + const index = source.indexOf("b"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 1, ",x"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + }); - it("Speculative generic lookahead 4", () => { - const source = "var v = Fe"; + it("Speculative generic lookahead 4", () => { + const source = "var v = Fe"; - const index = source.indexOf("b"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 1, ",x"); + const index = source.indexOf("b"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 1, ",x"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + }); - it("Assertion to arrow function", () => { - const source = "var v = (a);"; + it("Assertion to arrow function", () => { + const source = "var v = (a);"; - const index = source.indexOf(";"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, " => 1"); + const index = source.indexOf(";"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index, " => 1"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Arrow function to assertion", () => { - const source = "var v = (a) => 1;"; + it("Arrow function to assertion", () => { + const source = "var v = (a) => 1;"; - const index = source.indexOf(" =>"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, " => 1".length); + const index = source.indexOf(" =>"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, " => 1".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Contextual shift to shift-equals", () => { - const source = "var v = 1 >> = 2"; + it("Contextual shift to shift-equals", () => { + const source = "var v = 1 >> = 2"; - const index = source.indexOf(">> ="); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index + 2, 1); + const index = source.indexOf(">> ="); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index + 2, 1); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Contextual shift-equals to shift", () => { - const source = "var v = 1 >>= 2"; + it("Contextual shift-equals to shift", () => { + const source = "var v = 1 >>= 2"; - const index = source.indexOf(">>="); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index + 2, " "); + const index = source.indexOf(">>="); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index + 2, " "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Contextual shift to generic invocation", () => { - const source = "var v = T>>(2)"; + it("Contextual shift to generic invocation", () => { + const source = "var v = T>>(2)"; - const index = source.indexOf("T"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, "Foo { - const source = "var v = Foo>(2)"; + it("Test generic invocation to contextual shift", () => { + const source = "var v = Foo>(2)"; - const index = source.indexOf("Foo { - const source = "var v = T>>=2;"; + it("Contextual shift to generic type and initializer", () => { + const source = "var v = T>>=2;"; - const index = source.indexOf("="); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, "= ".length, ": Foo { - const source = "var v : Foo>=2;"; + it("Generic type and initializer to contextual shift", () => { + const source = "var v : Foo>=2;"; - const index = source.indexOf(":"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, ": Foo { - const source = "var v = new Dictionary0"; + it("Arithmetic operator to type argument list", () => { + const source = "var v = new Dictionary0"; - const index = source.indexOf("0"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, 1, "()"); + const index = source.indexOf("0"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, index, 1, "()"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Type argument list to arithmetic operator", () => { - const source = "var v = new Dictionary()"; + it("Type argument list to arithmetic operator", () => { + const source = "var v = new Dictionary()"; - const index = source.indexOf("()"); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, index, 2); + const index = source.indexOf("()"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, index, 2); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Yield context 1", () => { - // We're changing from a non-generator to a genarator. We can't reuse statement nodes. - const source = "function foo() {\r\nyield(foo1);\r\n}"; + it("Yield context 1", () => { + // We're changing from a non-generator to a genarator. We can't reuse statement nodes. + const source = "function foo() {\r\nyield(foo1);\r\n}"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("foo"); - const newTextAndChange = withInsert(oldText, index, "*"); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = source.indexOf("foo"); + const newTextAndChange = withInsert(oldText, index, "*"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Yield context 2", () => { - // We're changing from a generator to a non-genarator. We can't reuse statement nodes. - const source = "function *foo() {\r\nyield(foo1);\r\n}"; + it("Yield context 2", () => { + // We're changing from a generator to a non-genarator. We can't reuse statement nodes. + const source = "function *foo() {\r\nyield(foo1);\r\n}"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("*"); - const newTextAndChange = withDelete(oldText, index, "*".length); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = source.indexOf("*"); + const newTextAndChange = withDelete(oldText, index, "*".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Delete semicolon", () => { - const source = "export class Foo {\r\n}\r\n\r\nexport var foo = new Foo();\r\n\r\n export function test(foo: Foo) {\r\n return true;\r\n }\r\n"; + it("Delete semicolon", () => { + const source = "export class Foo {\r\n}\r\n\r\nexport var foo = new Foo();\r\n\r\n export function test(foo: Foo) {\r\n return true;\r\n }\r\n"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.lastIndexOf(";"); - const newTextAndChange = withDelete(oldText, index, 1); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = source.lastIndexOf(";"); + const newTextAndChange = withDelete(oldText, index, 1); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); + }); - it("Edit after empty type parameter list", () => { - const source = "class Dictionary<> { }\r\nvar y;\r\n"; + it("Edit after empty type parameter list", () => { + const source = "class Dictionary<> { }\r\nvar y;\r\n"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.length; - const newTextAndChange = withInsert(oldText, index, "var x;"); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = source.length; + const newTextAndChange = withInsert(oldText, index, "var x;"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 2); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 2); + }); - it("Delete parameter after comment", () => { - const source = "function fn(/* comment! */ a: number, c) { }"; + it("Delete parameter after comment", () => { + const source = "function fn(/* comment! */ a: number, c) { }"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("a:"); - const newTextAndChange = withDelete(oldText, index, "a: number,".length); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = source.indexOf("a:"); + const newTextAndChange = withDelete(oldText, index, "a: number,".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Modifier added to accessor", () => { - const source = - "class C {\ + it("Modifier added to accessor", () => { + const source = + "class C {\ set Bar(bar:string) {}\ }\ var o2 = { set Foo(val:number) { } };"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("set"); - const newTextAndChange = withInsert(oldText, index, "public "); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = source.indexOf("set"); + const newTextAndChange = withInsert(oldText, index, "public "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14); + }); - it("Insert parameter ahead of parameter", () => { - const source = - "alert(100);\ + it("Insert parameter ahead of parameter", () => { + const source = + "alert(100);\ \ class OverloadedMonster {\ constructor();\ constructor(name) { }\ }"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("100"); - const newTextAndChange = withInsert(oldText, index, "'1', "); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = source.indexOf("100"); + const newTextAndChange = withInsert(oldText, index, "'1', "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 7); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 7); + }); - it("Insert declare modifier before module", () => { - const source = - "module mAmbient {\ + it("Insert declare modifier before module", () => { + const source = + "module mAmbient {\ module m3 { }\ }"; - const oldText = ScriptSnapshot.fromString(source); - const index = 0; - const newTextAndChange = withInsert(oldText, index, "declare "); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = 0; + const newTextAndChange = withInsert(oldText, index, "declare "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Insert function above arrow function with comment", () => { - const source = - "\ + it("Insert function above arrow function with comment", () => { + const source = + "\ () =>\ // do something\ 0;"; - const oldText = ScriptSnapshot.fromString(source); - const index = 0; - const newTextAndChange = withInsert(oldText, index, "function Foo() { }"); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = 0; + const newTextAndChange = withInsert(oldText, index, "function Foo() { }"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Finish incomplete regular expression", () => { - const source = "while (true) /3; return;"; + it("Finish incomplete regular expression", () => { + const source = "while (true) /3; return;"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.length - 1; - const newTextAndChange = withInsert(oldText, index, "/"); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = source.length - 1; + const newTextAndChange = withInsert(oldText, index, "/"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Regular expression to divide operation", () => { - const source = "return;\r\nwhile (true) /3/g;"; + it("Regular expression to divide operation", () => { + const source = "return;\r\nwhile (true) /3/g;"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("while"); - const newTextAndChange = withDelete(oldText, index, "while ".length); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = source.indexOf("while"); + const newTextAndChange = withDelete(oldText, index, "while ".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Divide operation to regular expression", () => { - const source = "return;\r\n(true) /3/g;"; + it("Divide operation to regular expression", () => { + const source = "return;\r\n(true) /3/g;"; - const oldText = ScriptSnapshot.fromString(source); - const index = source.indexOf("("); - const newTextAndChange = withInsert(oldText, index, "while "); + const oldText = ts.ScriptSnapshot.fromString(source); + const index = source.indexOf("("); + const newTextAndChange = withInsert(oldText, index, "while "); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Unterminated comment after keyword converted to identifier", () => { - // 'public' as a keyword should be incrementally unusable (because it has an - // unterminated comment). When we convert it to an identifier, that shouldn't - // change anything, and we should still get the same errors. - const source = "return; a.public /*"; + it("Unterminated comment after keyword converted to identifier", () => { + // 'public' as a keyword should be incrementally unusable (because it has an + // unterminated comment). When we convert it to an identifier, that shouldn't + // change anything, and we should still get the same errors. + const source = "return; a.public /*"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, 0, ""); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, 0, ""); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 7); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 7); + }); - it("Class to interface", () => { - const source = "class A { public M1() { } public M2() { } public M3() { } p1 = 0; p2 = 0; p3 = 0 }"; + it("Class to interface", () => { + const source = "class A { public M1() { } public M2() { } public M3() { } p1 = 0; p2 = 0; p3 = 0 }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "class".length, "interface"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "class".length, "interface"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Interface to class", () => { - const source = "interface A { M1?(); M2?(); M3?(); p1?; p2?; p3? }"; + it("Interface to class", () => { + const source = "interface A { M1?(); M2?(); M3?(); p1?; p2?; p3? }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "interface".length, "class"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "interface".length, "class"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Surrounding function declarations with block", () => { - const source = "declare function F1() { } export function F2() { } declare export function F3() { }"; + it("Surrounding function declarations with block", () => { + const source = "declare function F1() { } export function F2() { } declare export function F3() { }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, 0, "{"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, 0, "{"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Removing block around function declarations", () => { - const source = "{ declare function F1() { } export function F2() { } declare export function F3() { }"; + it("Removing block around function declarations", () => { + const source = "{ declare function F1() { } export function F2() { } declare export function F3() { }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, 0, "{".length); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, 0, "{".length); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9); + }); - it("Moving methods from class to object literal", () => { - const source = "class C { public A() { } public B() { } public C() { } }"; + it("Moving methods from class to object literal", () => { + const source = "class C { public A() { } public B() { } public C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Moving methods from object literal to class", () => { - const source = "var v = { public A() { } public B() { } public C() { } }"; + it("Moving methods from object literal to class", () => { + const source = "var v = { public A() { } public B() { } public C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Moving methods from object literal to class in strict mode", () => { - const source = "\"use strict\"; var v = { public A() { } public B() { } public C() { } }"; + it("Moving methods from object literal to class in strict mode", () => { + const source = "\"use strict\"; var v = { public A() { } public B() { } public C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 14, "var v =".length, "class C"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 14, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Do not move constructors from class to object-literal.", () => { - const source = "class C { public constructor() { } public constructor() { } public constructor() { } }"; + it("Do not move constructors from class to object-literal.", () => { + const source = "class C { public constructor() { } public constructor() { } public constructor() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Do not move methods called \"constructor\" from object literal to class", () => { - const source = "var v = { public constructor() { } public constructor() { } public constructor() { } }"; + it("Do not move methods called \"constructor\" from object literal to class", () => { + const source = "var v = { public constructor() { } public constructor() { } public constructor() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Moving index signatures from class to interface", () => { - const source = "class C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; + it("Moving index signatures from class to interface", () => { + const source = "class C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "class".length, "interface"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "class".length, "interface"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); + }); - it("Moving index signatures from class to interface in strict mode", () => { - const source = "\"use strict\"; class C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; + it("Moving index signatures from class to interface in strict mode", () => { + const source = "\"use strict\"; class C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 14, "class".length, "interface"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 14, "class".length, "interface"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); + }); - it("Moving index signatures from interface to class", () => { - const source = "interface C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; + it("Moving index signatures from interface to class", () => { + const source = "interface C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "interface".length, "class"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "interface".length, "class"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); + }); - it("Moving index signatures from interface to class in strict mode", () => { - const source = "\"use strict\"; interface C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; + it("Moving index signatures from interface to class in strict mode", () => { + const source = "\"use strict\"; interface C { public [a: number]: string; public [a: number]: string; public [a: number]: string }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 14, "interface".length, "class"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 14, "interface".length, "class"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18); + }); - it("Moving accessors from class to object literal", () => { - const source = "class C { public get A() { } public get B() { } public get C() { } }"; + it("Moving accessors from class to object literal", () => { + const source = "class C { public get A() { } public get B() { } public get C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "class C".length, "var v ="); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + }); - it("Moving accessors from object literal to class", () => { - const source = "var v = { public get A() { } public get B() { } public get C() { } }"; + it("Moving accessors from object literal to class", () => { + const source = "var v = { public get A() { } public get B() { } public get C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Moving accessors from object literal to class in strict mode", () => { - const source = "\"use strict\"; var v = { public get A() { } public get B() { } public get C() { } }"; + it("Moving accessors from object literal to class in strict mode", () => { + const source = "\"use strict\"; var v = { public get A() { } public get B() { } public get C() { } }"; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 14, "var v =".length, "class C"); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 14, "var v =".length, "class C"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); - }); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); + }); - it("Reuse transformFlags of subtree during bind", () => { - const source = `class Greeter { constructor(element: HTMLElement) { } }`; - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, 15, 0, "\n"); - const { oldTree, incrementalNewTree } = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - bindSourceFile(oldTree, {}); - bindSourceFile(incrementalNewTree, {}); - assert.equal(oldTree.transformFlags, incrementalNewTree.transformFlags); - }); + it("Reuse transformFlags of subtree during bind", () => { + const source = `class Greeter { constructor(element: HTMLElement) { } }`; + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 15, 0, "\n"); + const { oldTree, incrementalNewTree } = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + ts.bindSourceFile(oldTree, {}); + ts.bindSourceFile(incrementalNewTree, {}); + assert.equal(oldTree.transformFlags, incrementalNewTree.transformFlags); + }); - // Simulated typing tests. + // Simulated typing tests. - it("Type extends clause 1", () => { - const source = "interface IFoo { }\r\ninterface Array extends IFoo { }"; + it("Type extends clause 1", () => { + const source = "interface IFoo { }\r\ninterface Array extends IFoo { }"; - const index = source.indexOf("extends"); - deleteCode(source, index, "extends IFoo"); - }); + const index = source.indexOf("extends"); + deleteCode(source, index, "extends IFoo"); + }); - it("Type after incomplete enum 1", () => { - const source = "function foo() {\r\n" + - " function getOccurrencesAtPosition() {\r\n" + - " switch (node) {\r\n" + - " enum \r\n" + - " }\r\n" + - " \r\n" + - " return undefined;\r\n" + - " \r\n" + - " function keywordToReferenceEntry() {\r\n" + - " }\r\n" + - " }\r\n" + - " \r\n" + - " return {\r\n" + - " getEmitOutput: (fileName): Bar => null,\r\n" + - " };\r\n" + - " }"; - - const index = source.indexOf("enum ") + "enum ".length; - insertCode(source, index, "Fo"); - }); + it("Type after incomplete enum 1", () => { + const source = "function foo() {\r\n" + + " function getOccurrencesAtPosition() {\r\n" + + " switch (node) {\r\n" + + " enum \r\n" + + " }\r\n" + + " \r\n" + + " return undefined;\r\n" + + " \r\n" + + " function keywordToReferenceEntry() {\r\n" + + " }\r\n" + + " }\r\n" + + " \r\n" + + " return {\r\n" + + " getEmitOutput: (fileName): Bar => null,\r\n" + + " };\r\n" + + " }"; + + const index = source.indexOf("enum ") + "enum ".length; + insertCode(source, index, "Fo"); + }); - for (const tsIgnoreComment of [ - "// @ts-ignore", - "/* @ts-ignore */", - "/*\n @ts-ignore */" - ]) { - describe(`${tsIgnoreComment} comment directives`, () => { - const textWithIgnoreComment = `const x = 10; + for (const tsIgnoreComment of [ + "// @ts-ignore", + "/* @ts-ignore */", + "/*\n @ts-ignore */" + ]) { + describe(`${tsIgnoreComment} comment directives`, () => { + const textWithIgnoreComment = `const x = 10; function foo() { ${tsIgnoreComment} let y: string = x; @@ -857,103 +859,103 @@ module m3 { }\ foo(); bar(); bar3();`; - verifyScenario("when deleting ts-ignore comment", verifyDelete); - verifyScenario("when inserting ts-ignore comment", verifyInsert); - verifyScenario("when changing ts-ignore comment to blah", verifyChangeToBlah); - verifyScenario("when changing blah comment to ts-ignore", verifyChangeBackToDirective); - verifyScenario("when deleting blah comment", verifyDeletingBlah); - verifyScenario("when changing text that adds another comment", verifyChangeDirectiveType); - verifyScenario("when changing text that keeps the comment but adds more nodes", verifyReuseChange); - - function verifyCommentDirectives(oldText: IScriptSnapshot, newTextAndChange: { text: IScriptSnapshot; textChangeRange: TextChangeRange; }) { - const { incrementalNewTree, newTree } = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); - assert.deepEqual(incrementalNewTree.commentDirectives, newTree.commentDirectives); + verifyScenario("when deleting ts-ignore comment", verifyDelete); + verifyScenario("when inserting ts-ignore comment", verifyInsert); + verifyScenario("when changing ts-ignore comment to blah", verifyChangeToBlah); + verifyScenario("when changing blah comment to ts-ignore", verifyChangeBackToDirective); + verifyScenario("when deleting blah comment", verifyDeletingBlah); + verifyScenario("when changing text that adds another comment", verifyChangeDirectiveType); + verifyScenario("when changing text that keeps the comment but adds more nodes", verifyReuseChange); + + function verifyCommentDirectives(oldText: ts.IScriptSnapshot, newTextAndChange: { text: ts.IScriptSnapshot; textChangeRange: ts.TextChangeRange; }) { + const { incrementalNewTree, newTree } = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + assert.deepEqual(incrementalNewTree.commentDirectives, newTree.commentDirectives); + } + + function verifyScenario(scenario: string, verifyChange: (atIndex: number, singleIgnore?: true) => void) { + it(`${scenario} - 0`, () => { + verifyChange(0); + }); + it(`${scenario} - 1`, () => { + verifyChange(1); + }); + it(`${scenario} - 2`, () => { + verifyChange(2); + }); + it(`${scenario} - with single ts-ignore`, () => { + verifyChange(0, /*singleIgnore*/ true); + }); + } + + function getIndexOfTsIgnoreComment(atIndex: number) { + let index = 0; + for (let i = 0; i <= atIndex; i++) { + index = textWithIgnoreComment.indexOf(tsIgnoreComment, index); } - - function verifyScenario(scenario: string, verifyChange: (atIndex: number, singleIgnore?: true) => void) { - it(`${scenario} - 0`, () => { - verifyChange(0); - }); - it(`${scenario} - 1`, () => { - verifyChange(1); - }); - it(`${scenario} - 2`, () => { - verifyChange(2); - }); - it(`${scenario} - with single ts-ignore`, () => { - verifyChange(0, /*singleIgnore*/ true); - }); - } - - function getIndexOfTsIgnoreComment(atIndex: number) { - let index = 0; - for (let i = 0; i <= atIndex; i++) { - index = textWithIgnoreComment.indexOf(tsIgnoreComment, index); - } - return index; - } - - function textWithIgnoreCommentFrom(text: string, singleIgnore: true | undefined) { - if (!singleIgnore) return text; - const splits = text.split(tsIgnoreComment); - if (splits.length > 2) { - const tail = splits[splits.length - 2] + splits[splits.length - 1]; - splits.length = splits.length - 2; - return splits.join(tsIgnoreComment) + tail; - } - else { - return splits.join(tsIgnoreComment); - } - } - - function verifyDelete(atIndex: number, singleIgnore?: true) { - const index = getIndexOfTsIgnoreComment(atIndex); - const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); - const newTextAndChange = withDelete(oldText, index, tsIgnoreComment.length); - verifyCommentDirectives(oldText, newTextAndChange); - } - - function verifyInsert(atIndex: number, singleIgnore?: true) { - const index = getIndexOfTsIgnoreComment(atIndex); - const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + textWithIgnoreComment.slice(index + tsIgnoreComment.length), singleIgnore); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withInsert(oldText, index, tsIgnoreComment); - verifyCommentDirectives(oldText, newTextAndChange); - } - - function verifyChangeToBlah(atIndex: number, singleIgnore?: true) { - const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("@"); - const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); - const newTextAndChange = withChange(oldText, index, 1, "blah "); - verifyCommentDirectives(oldText, newTextAndChange); + return index; + } + + function textWithIgnoreCommentFrom(text: string, singleIgnore: true | undefined) { + if (!singleIgnore) return text; + const splits = text.split(tsIgnoreComment); + if (splits.length > 2) { + const tail = splits[splits.length - 2] + splits[splits.length - 1]; + splits.length = splits.length - 2; + return splits.join(tsIgnoreComment) + tail; } - - function verifyChangeBackToDirective(atIndex: number, singleIgnore?: true) { - const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("@"); - const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + "blah " + textWithIgnoreComment.slice(index + 1), singleIgnore); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withChange(oldText, index, "blah ".length, "@"); - verifyCommentDirectives(oldText, newTextAndChange); - } - - function verifyDeletingBlah(atIndex: number, singleIgnore?: true) { - const tsIgnoreIndex = getIndexOfTsIgnoreComment(atIndex); - const index = tsIgnoreIndex + tsIgnoreComment.indexOf("@"); - const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + "blah " + textWithIgnoreComment.slice(index + 1), singleIgnore); - const oldText = ScriptSnapshot.fromString(source); - const newTextAndChange = withDelete(oldText, tsIgnoreIndex, tsIgnoreComment.length + "blah".length); - verifyCommentDirectives(oldText, newTextAndChange); + else { + return splits.join(tsIgnoreComment); } - - function verifyChangeDirectiveType(atIndex: number, singleIgnore?: true) { - const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("ignore"); - const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); - const newTextAndChange = withChange(oldText, index, "ignore".length, "expect-error"); - verifyCommentDirectives(oldText, newTextAndChange); - } - - function verifyReuseChange(atIndex: number, singleIgnore?: true) { - const source = `const x = 10; + } + + function verifyDelete(atIndex: number, singleIgnore?: true) { + const index = getIndexOfTsIgnoreComment(atIndex); + const oldText = ts.ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); + const newTextAndChange = withDelete(oldText, index, tsIgnoreComment.length); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyInsert(atIndex: number, singleIgnore?: true) { + const index = getIndexOfTsIgnoreComment(atIndex); + const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + textWithIgnoreComment.slice(index + tsIgnoreComment.length), singleIgnore); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withInsert(oldText, index, tsIgnoreComment); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyChangeToBlah(atIndex: number, singleIgnore?: true) { + const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("@"); + const oldText = ts.ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); + const newTextAndChange = withChange(oldText, index, 1, "blah "); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyChangeBackToDirective(atIndex: number, singleIgnore?: true) { + const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("@"); + const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + "blah " + textWithIgnoreComment.slice(index + 1), singleIgnore); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, index, "blah ".length, "@"); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyDeletingBlah(atIndex: number, singleIgnore?: true) { + const tsIgnoreIndex = getIndexOfTsIgnoreComment(atIndex); + const index = tsIgnoreIndex + tsIgnoreComment.indexOf("@"); + const source = textWithIgnoreCommentFrom(textWithIgnoreComment.slice(0, index) + "blah " + textWithIgnoreComment.slice(index + 1), singleIgnore); + const oldText = ts.ScriptSnapshot.fromString(source); + const newTextAndChange = withDelete(oldText, tsIgnoreIndex, tsIgnoreComment.length + "blah".length); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyChangeDirectiveType(atIndex: number, singleIgnore?: true) { + const index = getIndexOfTsIgnoreComment(atIndex) + tsIgnoreComment.indexOf("ignore"); + const oldText = ts.ScriptSnapshot.fromString(textWithIgnoreCommentFrom(textWithIgnoreComment, singleIgnore)); + const newTextAndChange = withChange(oldText, index, "ignore".length, "expect-error"); + verifyCommentDirectives(oldText, newTextAndChange); + } + + function verifyReuseChange(atIndex: number, singleIgnore?: true) { + const source = `const x = 10; function foo1() { const x1 = 10; ${tsIgnoreComment} @@ -978,16 +980,15 @@ module m3 { }\ foo1(); foo2(); foo3();`; - const oldText = ScriptSnapshot.fromString(textWithIgnoreCommentFrom(source, singleIgnore)); - const start = source.indexOf(`const x${atIndex + 1}`); - const letStr = `let y${atIndex + 1}: string = x;`; - const end = source.indexOf(letStr) + letStr.length; - const oldSubStr = source.slice(start, end); - const newText = oldSubStr.replace(letStr, `let yn : string = x;`); - const newTextAndChange = withChange(oldText, start, end - start, newText); - verifyCommentDirectives(oldText, newTextAndChange); - } - }); - } - }); -} + const oldText = ts.ScriptSnapshot.fromString(textWithIgnoreCommentFrom(source, singleIgnore)); + const start = source.indexOf(`const x${atIndex + 1}`); + const letStr = `let y${atIndex + 1}: string = x;`; + const end = source.indexOf(letStr) + letStr.length; + const oldSubStr = source.slice(start, end); + const newText = oldSubStr.replace(letStr, `let yn : string = x;`); + const newTextAndChange = withChange(oldText, start, end - start, newText); + verifyCommentDirectives(oldText, newTextAndChange); + } + }); + } +}); diff --git a/src/testRunner/unittests/jsDocParsing.ts b/src/testRunner/unittests/jsDocParsing.ts index 0e544e7f77c95..21f461439c2ee 100644 --- a/src/testRunner/unittests/jsDocParsing.ts +++ b/src/testRunner/unittests/jsDocParsing.ts @@ -1,317 +1,320 @@ -namespace ts { - describe("unittests:: JSDocParsing", () => { - describe("TypeExpressions", () => { - function parsesCorrectly(name: string, content: string) { - it(name, () => { - const typeAndDiagnostics = parseJSDocTypeExpressionForTests(content); - assert.isTrue(typeAndDiagnostics && typeAndDiagnostics.diagnostics.length === 0, "no errors issued"); - - Harness.Baseline.runBaseline("JSDocParsing/TypeExpressions.parsesCorrectly." + name + ".json", - Utils.sourceFileToJSON(typeAndDiagnostics!.jsDocTypeExpression.type)); - }); - } - - function parsesIncorrectly(name: string, content: string) { - it(name, () => { - const type = parseJSDocTypeExpressionForTests(content); - assert.isTrue(!type || type.diagnostics.length > 0); - }); - } - - describe("parseCorrectly", () => { - parsesCorrectly("unknownType", "{?}"); - parsesCorrectly("allType", "{*}"); - parsesCorrectly("nullableType", "{?number}"); - parsesCorrectly("nullableType2", "{number?}"); - parsesCorrectly("nonNullableType", "{!number}"); - parsesCorrectly("nonNullableType2", "{number!}"); - parsesCorrectly("recordType1", "{{}}"); - parsesCorrectly("recordType2", "{{foo}}"); - parsesCorrectly("recordType3", "{{foo: number}}"); - parsesCorrectly("recordType4", "{{foo, bar}}"); - parsesCorrectly("recordType5", "{{foo: number, bar}}"); - parsesCorrectly("recordType6", "{{foo, bar: number}}"); - parsesCorrectly("recordType7", "{{foo: number, bar: number}}"); - parsesCorrectly("recordType8", "{{function}}"); - parsesCorrectly("trailingCommaInRecordType", "{{a,}}"); - parsesCorrectly("callSignatureInRecordType", "{{(): number}}"); - parsesCorrectly("methodInRecordType", "{{foo(): number}}"); - parsesCorrectly("unionType", "{(number|string)}"); - parsesCorrectly("unionTypeWithLeadingOperator", "{( | number | string )}"); - parsesCorrectly("unionTypeWithOneElementAndLeadingOperator", "{( | number )}"); - parsesCorrectly("topLevelNoParenUnionType", "{number|string}"); - parsesCorrectly("functionType1", "{function()}"); - parsesCorrectly("functionType2", "{function(string, boolean)}"); - parsesCorrectly("functionReturnType1", "{function(string, boolean)}"); - parsesCorrectly("thisType1", "{function(this:a.b)}"); - parsesCorrectly("newType1", "{function(new:a.b)}"); - parsesCorrectly("variadicType", "{...number}"); - parsesCorrectly("optionalType", "{number=}"); - parsesCorrectly("optionalNullable", "{?=}"); - parsesCorrectly("typeReference1", "{a.}"); - parsesCorrectly("typeReference2", "{a.}"); - parsesCorrectly("typeReference3", "{a.function}"); - parsesCorrectly("arrayType1", "{a[]}"); - parsesCorrectly("arrayType2", "{a[][]}"); - parsesCorrectly("arrayType3", "{(a[][])=}"); - parsesCorrectly("keyword1", "{var}"); - parsesCorrectly("keyword2", "{null}"); - parsesCorrectly("keyword3", "{undefined}"); - parsesCorrectly("tupleType0", "{[]}"); - parsesCorrectly("tupleType1", "{[number]}"); - parsesCorrectly("tupleType2", "{[number,string]}"); - parsesCorrectly("tupleType3", "{[number,string,boolean]}"); - parsesCorrectly("tupleTypeWithTrailingComma", "{[number,]}"); - parsesCorrectly("typeOfType", "{typeof M}"); - parsesCorrectly("tsConstructorType", "{new () => string}"); - parsesCorrectly("tsFunctionType", "{() => string}"); - parsesCorrectly("typeArgumentsNotFollowingDot", "{a<>}"); - parsesCorrectly("functionTypeWithTrailingComma", "{function(a,)}"); +import * as ts from "../_namespaces/ts"; +import * as Harness from "../_namespaces/Harness"; +import * as Utils from "../_namespaces/Utils"; + +describe("unittests:: JSDocParsing", () => { + describe("TypeExpressions", () => { + function parsesCorrectly(name: string, content: string) { + it(name, () => { + const typeAndDiagnostics = ts.parseJSDocTypeExpressionForTests(content); + assert.isTrue(typeAndDiagnostics && typeAndDiagnostics.diagnostics.length === 0, "no errors issued"); + + Harness.Baseline.runBaseline("JSDocParsing/TypeExpressions.parsesCorrectly." + name + ".json", + Utils.sourceFileToJSON(typeAndDiagnostics!.jsDocTypeExpression.type)); }); + } - describe("parsesIncorrectly", () => { - parsesIncorrectly("emptyType", "{}"); - parsesIncorrectly("unionTypeWithTrailingBar", "{(a|)}"); - parsesIncorrectly("unionTypeWithoutTypes", "{()}"); - parsesIncorrectly("nullableTypeWithoutType", "{!}"); - parsesIncorrectly("thisWithoutType", "{this:}"); - parsesIncorrectly("newWithoutType", "{new:}"); - parsesIncorrectly("variadicWithoutType", "{...}"); - parsesIncorrectly("optionalWithoutType", "{=}"); - parsesIncorrectly("allWithType", "{*foo}"); - parsesIncorrectly("namedParameter", "{function(a: number)}"); - parsesIncorrectly("tupleTypeWithComma", "{[,]}"); - parsesIncorrectly("tupleTypeWithLeadingComma", "{[,number]}"); + function parsesIncorrectly(name: string, content: string) { + it(name, () => { + const type = ts.parseJSDocTypeExpressionForTests(content); + assert.isTrue(!type || type.diagnostics.length > 0); }); + } + + describe("parseCorrectly", () => { + parsesCorrectly("unknownType", "{?}"); + parsesCorrectly("allType", "{*}"); + parsesCorrectly("nullableType", "{?number}"); + parsesCorrectly("nullableType2", "{number?}"); + parsesCorrectly("nonNullableType", "{!number}"); + parsesCorrectly("nonNullableType2", "{number!}"); + parsesCorrectly("recordType1", "{{}}"); + parsesCorrectly("recordType2", "{{foo}}"); + parsesCorrectly("recordType3", "{{foo: number}}"); + parsesCorrectly("recordType4", "{{foo, bar}}"); + parsesCorrectly("recordType5", "{{foo: number, bar}}"); + parsesCorrectly("recordType6", "{{foo, bar: number}}"); + parsesCorrectly("recordType7", "{{foo: number, bar: number}}"); + parsesCorrectly("recordType8", "{{function}}"); + parsesCorrectly("trailingCommaInRecordType", "{{a,}}"); + parsesCorrectly("callSignatureInRecordType", "{{(): number}}"); + parsesCorrectly("methodInRecordType", "{{foo(): number}}"); + parsesCorrectly("unionType", "{(number|string)}"); + parsesCorrectly("unionTypeWithLeadingOperator", "{( | number | string )}"); + parsesCorrectly("unionTypeWithOneElementAndLeadingOperator", "{( | number )}"); + parsesCorrectly("topLevelNoParenUnionType", "{number|string}"); + parsesCorrectly("functionType1", "{function()}"); + parsesCorrectly("functionType2", "{function(string, boolean)}"); + parsesCorrectly("functionReturnType1", "{function(string, boolean)}"); + parsesCorrectly("thisType1", "{function(this:a.b)}"); + parsesCorrectly("newType1", "{function(new:a.b)}"); + parsesCorrectly("variadicType", "{...number}"); + parsesCorrectly("optionalType", "{number=}"); + parsesCorrectly("optionalNullable", "{?=}"); + parsesCorrectly("typeReference1", "{a.}"); + parsesCorrectly("typeReference2", "{a.}"); + parsesCorrectly("typeReference3", "{a.function}"); + parsesCorrectly("arrayType1", "{a[]}"); + parsesCorrectly("arrayType2", "{a[][]}"); + parsesCorrectly("arrayType3", "{(a[][])=}"); + parsesCorrectly("keyword1", "{var}"); + parsesCorrectly("keyword2", "{null}"); + parsesCorrectly("keyword3", "{undefined}"); + parsesCorrectly("tupleType0", "{[]}"); + parsesCorrectly("tupleType1", "{[number]}"); + parsesCorrectly("tupleType2", "{[number,string]}"); + parsesCorrectly("tupleType3", "{[number,string,boolean]}"); + parsesCorrectly("tupleTypeWithTrailingComma", "{[number,]}"); + parsesCorrectly("typeOfType", "{typeof M}"); + parsesCorrectly("tsConstructorType", "{new () => string}"); + parsesCorrectly("tsFunctionType", "{() => string}"); + parsesCorrectly("typeArgumentsNotFollowingDot", "{a<>}"); + parsesCorrectly("functionTypeWithTrailingComma", "{function(a,)}"); }); - describe("DocComments", () => { - function parsesCorrectly(name: string, content: string) { - it(name, () => { - const comment = parseIsolatedJSDocComment(content)!; - if (!comment) { - Debug.fail("Comment failed to parse entirely"); - } - if (comment.diagnostics.length > 0) { - Debug.fail("Comment has at least one diagnostic: " + comment.diagnostics[0].messageText); - } - - Harness.Baseline.runBaseline("JSDocParsing/DocComments.parsesCorrectly." + name + ".json", - JSON.stringify(comment.jsDoc, - (_, v) => v && v.pos !== undefined ? JSON.parse(Utils.sourceFileToJSON(v)) : v, 4)); - }); - } - - function parsesIncorrectly(name: string, content: string) { - it(name, () => { - const type = parseIsolatedJSDocComment(content); - assert.isTrue(!type || type.diagnostics.length > 0); - }); - } - - describe("parsesIncorrectly", () => { - parsesIncorrectly("multipleTypes", - `/** + describe("parsesIncorrectly", () => { + parsesIncorrectly("emptyType", "{}"); + parsesIncorrectly("unionTypeWithTrailingBar", "{(a|)}"); + parsesIncorrectly("unionTypeWithoutTypes", "{()}"); + parsesIncorrectly("nullableTypeWithoutType", "{!}"); + parsesIncorrectly("thisWithoutType", "{this:}"); + parsesIncorrectly("newWithoutType", "{new:}"); + parsesIncorrectly("variadicWithoutType", "{...}"); + parsesIncorrectly("optionalWithoutType", "{=}"); + parsesIncorrectly("allWithType", "{*foo}"); + parsesIncorrectly("namedParameter", "{function(a: number)}"); + parsesIncorrectly("tupleTypeWithComma", "{[,]}"); + parsesIncorrectly("tupleTypeWithLeadingComma", "{[,number]}"); + }); + }); + + describe("DocComments", () => { + function parsesCorrectly(name: string, content: string) { + it(name, () => { + const comment = ts.parseIsolatedJSDocComment(content)!; + if (!comment) { + ts.Debug.fail("Comment failed to parse entirely"); + } + if (comment.diagnostics.length > 0) { + ts.Debug.fail("Comment has at least one diagnostic: " + comment.diagnostics[0].messageText); + } + + Harness.Baseline.runBaseline("JSDocParsing/DocComments.parsesCorrectly." + name + ".json", + JSON.stringify(comment.jsDoc, + (_, v) => v && v.pos !== undefined ? JSON.parse(Utils.sourceFileToJSON(v)) : v, 4)); + }); + } + + function parsesIncorrectly(name: string, content: string) { + it(name, () => { + const type = ts.parseIsolatedJSDocComment(content); + assert.isTrue(!type || type.diagnostics.length > 0); + }); + } + + describe("parsesIncorrectly", () => { + parsesIncorrectly("multipleTypes", + `/** * @type {number} * @type {string} */`); - parsesIncorrectly("multipleReturnTypes", - `/** + parsesIncorrectly("multipleReturnTypes", + `/** * @return {number} * @return {string} */`); - parsesIncorrectly("noTypeParameters", - `/** + parsesIncorrectly("noTypeParameters", + `/** * @template */`); - parsesIncorrectly("trailingTypeParameterComma", - `/** + parsesIncorrectly("trailingTypeParameterComma", + `/** * @template T, */`); - parsesIncorrectly("paramWithoutName", - `/** + parsesIncorrectly("paramWithoutName", + `/** * @param {number} */`); - parsesIncorrectly("paramWithoutTypeOrName", - `/** + parsesIncorrectly("paramWithoutTypeOrName", + `/** * @param */`); - parsesIncorrectly("noType", - `/** + parsesIncorrectly("noType", + `/** * @type */`); - parsesIncorrectly("@augments with no type", - `/** + parsesIncorrectly("@augments with no type", + `/** * @augments */`); - }); + }); - describe("parsesCorrectly", () => { - parsesCorrectly("threeAsterisks", "/*** */"); - parsesCorrectly("emptyComment", "/***/"); - parsesCorrectly("noLeadingAsterisk", - `/** + describe("parsesCorrectly", () => { + parsesCorrectly("threeAsterisks", "/*** */"); + parsesCorrectly("emptyComment", "/***/"); + parsesCorrectly("noLeadingAsterisk", + `/** @type {number} */`); - parsesCorrectly("noReturnType", - `/** + parsesCorrectly("noReturnType", + `/** * @return */`); - parsesCorrectly("leadingAsterisk", - `/** + parsesCorrectly("leadingAsterisk", + `/** * @type {number} */`); - parsesCorrectly("asteriskAfterPreamble", "/** * @type {number} */"); + parsesCorrectly("asteriskAfterPreamble", "/** * @type {number} */"); - parsesCorrectly("typeTag", - `/** + parsesCorrectly("typeTag", + `/** * @type {number} */`); - parsesCorrectly("returnTag1", - `/** + parsesCorrectly("returnTag1", + `/** * @return {number} */`); - parsesCorrectly("returnTag2", - `/** + parsesCorrectly("returnTag2", + `/** * @return {number} Description text follows */`); - parsesCorrectly("returnsTag1", - `/** + parsesCorrectly("returnsTag1", + `/** * @returns {number} */`); - parsesCorrectly("oneParamTag", - `/** + parsesCorrectly("oneParamTag", + `/** * @param {number} name1 */`); - parsesCorrectly("twoParamTag2", - `/** + parsesCorrectly("twoParamTag2", + `/** * @param {number} name1 * @param {number} name2 */`); - parsesCorrectly("paramTag1", - `/** + parsesCorrectly("paramTag1", + `/** * @param {number} name1 Description text follows */`); - parsesCorrectly("paramTagBracketedName1", - `/** + parsesCorrectly("paramTagBracketedName1", + `/** * @param {number} [name1] Description text follows */`); - parsesCorrectly("paramTagBracketedName2", - `/** + parsesCorrectly("paramTagBracketedName2", + `/** * @param {number} [ name1 = 1] Description text follows */`); - parsesCorrectly("twoParamTagOnSameLine", - `/** + parsesCorrectly("twoParamTagOnSameLine", + `/** * @param {number} name1 @param {number} name2 */`); - parsesCorrectly("paramTagNameThenType1", - `/** + parsesCorrectly("paramTagNameThenType1", + `/** * @param name1 {number} */`); - parsesCorrectly("paramTagNameThenType2", - `/** + parsesCorrectly("paramTagNameThenType2", + `/** * @param name1 {number} Description */`); - parsesCorrectly("argSynonymForParamTag", - `/** + parsesCorrectly("argSynonymForParamTag", + `/** * @arg {number} name1 Description */`); - parsesCorrectly("argumentSynonymForParamTag", - `/** + parsesCorrectly("argumentSynonymForParamTag", + `/** * @argument {number} name1 Description */`); - parsesCorrectly("templateTag", - `/** + parsesCorrectly("templateTag", + `/** * @template T */`); - parsesCorrectly("templateTag2", - `/** + parsesCorrectly("templateTag2", + `/** * @template K,V */`); - parsesCorrectly("templateTag3", - `/** + parsesCorrectly("templateTag3", + `/** * @template K ,V */`); - parsesCorrectly("templateTag4", - `/** + parsesCorrectly("templateTag4", + `/** * @template K, V */`); - parsesCorrectly("templateTag5", - `/** + parsesCorrectly("templateTag5", + `/** * @template K , V */`); - parsesCorrectly("templateTag6", - `/** + parsesCorrectly("templateTag6", + `/** * @template K , V Description of type parameters. */`); - parsesCorrectly("paramWithoutType", - `/** + parsesCorrectly("paramWithoutType", + `/** * @param foo */`); - parsesCorrectly("typedefTagWithChildrenTags", - `/** + parsesCorrectly("typedefTagWithChildrenTags", + `/** * @typedef People * @type {Object} * @property {number} age * @property {string} name */`); - parsesCorrectly("less-than and greater-than characters", - `/** + parsesCorrectly("less-than and greater-than characters", + `/** * @param x hi < > still part of the previous comment */`); - parsesCorrectly("Nested @param tags", - `/** + parsesCorrectly("Nested @param tags", + `/** * @param {object} o Doc doc * @param {string} o.f Doc for f */`); - parsesCorrectly("@link tags", - `/** + parsesCorrectly("@link tags", + `/** * {@link first } * Inside {@link link text} thing * @param foo See also {@link A.Reference} @@ -330,8 +333,8 @@ oh.no * }, because of the intermediate asterisks. * @author Alfa Romero See my home page: {@link https://example.com} */`); - parsesCorrectly("authorTag", - `/** + parsesCorrectly("authorTag", + `/** * @author John Doe * @author John Doe unexpected comment * @author 108 <108@actionbutton.net> Video Games Forever @@ -351,59 +354,58 @@ oh.no * want to keep commenting down here, I dunno. */`); - parsesCorrectly("consecutive newline tokens", - `/** + parsesCorrectly("consecutive newline tokens", + `/** * @example * Some\n\n * text\r\n * with newlines. */`); - parsesCorrectly("Chained tags, no leading whitespace", `/**@a @b @c@d*/`); - parsesCorrectly("Initial star is not a tag", `/***@a*/`); - parsesCorrectly("Initial star space is not a tag", `/*** @a*/`); - parsesCorrectly("Initial email address is not a tag", `/**bill@example.com*/`); - parsesCorrectly("no space before @ is not a new tag", - `/** + parsesCorrectly("Chained tags, no leading whitespace", `/**@a @b @c@d*/`); + parsesCorrectly("Initial star is not a tag", `/***@a*/`); + parsesCorrectly("Initial star space is not a tag", `/*** @a*/`); + parsesCorrectly("Initial email address is not a tag", `/**bill@example.com*/`); + parsesCorrectly("no space before @ is not a new tag", + `/** * @param this (@is@) * @param fine its@fine @zerowidth *@singlestar **@doublestar */`); - parsesCorrectly("@@ does not start a new tag", - `/** + parsesCorrectly("@@ does not start a new tag", + `/** * @param this is (@@fine@@and) is one comment */`); - }); }); - describe("getFirstToken", () => { - it("gets jsdoc", () => { - const root = createSourceFile("foo.ts", "/** comment */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ true); - assert.isDefined(root); - assert.equal(root.kind, SyntaxKind.SourceFile); - const first = root.getFirstToken(); - assert.isDefined(first); - assert.equal(first!.kind, SyntaxKind.VarKeyword); - }); + }); + describe("getFirstToken", () => { + it("gets jsdoc", () => { + const root = ts.createSourceFile("foo.ts", "/** comment */var a = true;", ts.ScriptTarget.ES5, /*setParentNodes*/ true); + assert.isDefined(root); + assert.equal(root.kind, ts.SyntaxKind.SourceFile); + const first = root.getFirstToken(); + assert.isDefined(first); + assert.equal(first!.kind, ts.SyntaxKind.VarKeyword); }); - describe("getLastToken", () => { - it("gets jsdoc", () => { - const root = createSourceFile("foo.ts", "var a = true;/** comment */", ScriptTarget.ES5, /*setParentNodes*/ true); - assert.isDefined(root); - const last = root.getLastToken(); - assert.isDefined(last); - assert.equal(last!.kind, SyntaxKind.EndOfFileToken); - }); + }); + describe("getLastToken", () => { + it("gets jsdoc", () => { + const root = ts.createSourceFile("foo.ts", "var a = true;/** comment */", ts.ScriptTarget.ES5, /*setParentNodes*/ true); + assert.isDefined(root); + const last = root.getLastToken(); + assert.isDefined(last); + assert.equal(last!.kind, ts.SyntaxKind.EndOfFileToken); }); - describe("getStart", () => { - it("runs when node with JSDoc but no parent pointers", () => { - const root = createSourceFile("foo.ts", "/** */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ false); - root.statements[0].getStart(root, /*includeJsdocComment*/ true); - }); + }); + describe("getStart", () => { + it("runs when node with JSDoc but no parent pointers", () => { + const root = ts.createSourceFile("foo.ts", "/** */var a = true;", ts.ScriptTarget.ES5, /*setParentNodes*/ false); + root.statements[0].getStart(root, /*includeJsdocComment*/ true); }); - describe("parseIsolatedJSDocComment", () => { - it("doesn't create a 1-element array with missing type parameter in jsDoc", () => { - const doc = parseIsolatedJSDocComment("/**\n @template\n*/"); - assert.equal((doc?.jsDoc.tags?.[0] as JSDocTemplateTag).typeParameters.length, 0); - }); + }); + describe("parseIsolatedJSDocComment", () => { + it("doesn't create a 1-element array with missing type parameter in jsDoc", () => { + const doc = ts.parseIsolatedJSDocComment("/**\n @template\n*/"); + assert.equal((doc?.jsDoc.tags?.[0] as ts.JSDocTemplateTag).typeParameters.length, 0); }); }); -} +}); diff --git a/src/testRunner/unittests/jsonParserRecovery.ts b/src/testRunner/unittests/jsonParserRecovery.ts index 43007a687d349..3eaf92790ffca 100644 --- a/src/testRunner/unittests/jsonParserRecovery.ts +++ b/src/testRunner/unittests/jsonParserRecovery.ts @@ -1,26 +1,28 @@ -namespace ts { - describe("unittests:: jsonParserRecovery", () => { - function parsesToValidSourceFileWithErrors(name: string, text: string) { - it(name, () => { - const file = parseJsonText(name, text); - assert(file.parseDiagnostics.length, "Should have parse errors"); - Harness.Baseline.runBaseline( - `jsonParserRecovery/${name.replace(/[^a-z0-9_-]/ig, "_")}.errors.txt`, - Harness.Compiler.getErrorBaseline([{ - content: text, - unitName: name - }], file.parseDiagnostics)); +import * as ts from "../_namespaces/ts"; +import * as Harness from "../_namespaces/Harness"; - // Will throw if parse tree does not cover full input text - file.getChildren(); - }); - } +describe("unittests:: jsonParserRecovery", () => { + function parsesToValidSourceFileWithErrors(name: string, text: string) { + it(name, () => { + const file = ts.parseJsonText(name, text); + assert(file.parseDiagnostics.length, "Should have parse errors"); + Harness.Baseline.runBaseline( + `jsonParserRecovery/${name.replace(/[^a-z0-9_-]/ig, "_")}.errors.txt`, + Harness.Compiler.getErrorBaseline([{ + content: text, + unitName: name + }], file.parseDiagnostics)); - parsesToValidSourceFileWithErrors("trailing identifier", "{} blah"); - parsesToValidSourceFileWithErrors("TypeScript code", "interface Foo {} blah"); - parsesToValidSourceFileWithErrors("Two comma-separated objects", "{}, {}"); - parsesToValidSourceFileWithErrors("Two objects", "{} {}"); - parsesToValidSourceFileWithErrors("JSX", ` + // Will throw if parse tree does not cover full input text + file.getChildren(); + }); + } + + parsesToValidSourceFileWithErrors("trailing identifier", "{} blah"); + parsesToValidSourceFileWithErrors("TypeScript code", "interface Foo {} blah"); + parsesToValidSourceFileWithErrors("Two comma-separated objects", "{}, {}"); + parsesToValidSourceFileWithErrors("Two objects", "{} {}"); + parsesToValidSourceFileWithErrors("JSX", ` interface Test {} const Header = () => ( @@ -35,5 +37,4 @@ namespace ts {
)`); - }); -} +}); diff --git a/src/testRunner/unittests/moduleResolution.ts b/src/testRunner/unittests/moduleResolution.ts index 1f85f97827c16..7f2c76fedd37c 100644 --- a/src/testRunner/unittests/moduleResolution.ts +++ b/src/testRunner/unittests/moduleResolution.ts @@ -1,1512 +1,1486 @@ -namespace ts { - export function checkResolvedModule(actual: ResolvedModuleFull | undefined, expected: ResolvedModuleFull | undefined): boolean { - if (!expected) { - if (actual) { - assert.fail(actual, expected, "expected resolved module to be undefined"); - return false; +import * as ts from "../_namespaces/ts"; +import * as Harness from "../_namespaces/Harness"; +import { checkResolvedModule, checkResolvedModuleWithFailedLookupLocations, createResolvedModule } from "./helpers"; + +interface File { + name: string; + content?: string; + symlinks?: string[]; +} + +function createModuleResolutionHost(hasDirectoryExists: boolean, ...files: File[]): ts.ModuleResolutionHost { + const map = new ts.Map(); + for (const file of files) { + map.set(file.name, file); + if (file.symlinks) { + for (const symlink of file.symlinks) { + map.set(symlink, file); } - return true; - } - else if (!actual) { - assert.fail(actual, expected, "expected resolved module to be defined"); - return false; } - - assert.isTrue(actual.resolvedFileName === expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`); - assert.isTrue(actual.extension === expected.extension, `'ext': expected '${actual.extension}' to be equal to '${expected.extension}'`); - assert.isTrue(actual.isExternalLibraryImport === expected.isExternalLibraryImport, `'isExternalLibraryImport': expected '${actual.isExternalLibraryImport}' to be equal to '${expected.isExternalLibraryImport}'`); - return true; } - export function checkResolvedModuleWithFailedLookupLocations(actual: ResolvedModuleWithFailedLookupLocations, expectedResolvedModule: ResolvedModuleFull, expectedFailedLookupLocations: string[]): void { - assert.isTrue(actual.resolvedModule !== undefined, "module should be resolved"); - checkResolvedModule(actual.resolvedModule, expectedResolvedModule); - assert.deepEqual(actual.failedLookupLocations, expectedFailedLookupLocations, `Failed lookup locations should match - expected has ${expectedFailedLookupLocations.length}, actual has ${actual.failedLookupLocations.length}`); + if (hasDirectoryExists) { + const directories = new ts.Map(); + for (const f of files) { + let name = ts.getDirectoryPath(f.name); + while (true) { + directories.set(name, name); + const baseName = ts.getDirectoryPath(name); + if (baseName === name) { + break; + } + name = baseName; + } + } + return { + readFile, + realpath, + directoryExists: path => directories.has(path), + fileExists: path => { + assert.isTrue(directories.has(ts.getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`); + return map.has(path); + }, + useCaseSensitiveFileNames: true + }; } - - export function createResolvedModule(resolvedFileName: string, isExternalLibraryImport = false): ResolvedModuleFull { - return { resolvedFileName, extension: extensionFromPath(resolvedFileName), isExternalLibraryImport }; + else { + return { readFile, realpath, fileExists: path => map.has(path), useCaseSensitiveFileNames: true }; } - - interface File { - name: string; - content?: string; - symlinks?: string[]; + function readFile(path: string): string | undefined { + const file = map.get(path); + return file && file.content; + } + function realpath(path: string): string { + return map.get(path)!.name; } +} - function createModuleResolutionHost(hasDirectoryExists: boolean, ...files: File[]): ModuleResolutionHost { - const map = new Map(); - for (const file of files) { - map.set(file.name, file); - if (file.symlinks) { - for (const symlink of file.symlinks) { - map.set(symlink, file); - } - } +describe("unittests:: moduleResolution:: Node module resolution - relative paths", () => { + // node module resolution does _not_ implicitly append these extensions to an extensionless path (though will still attempt to load them if explicitly) + const nonImplicitExtensions = [ts.Extension.Mts, ts.Extension.Dmts, ts.Extension.Mjs, ts.Extension.Cts, ts.Extension.Dcts, ts.Extension.Cjs]; + const autoExtensions = ts.filter(ts.supportedTSExtensionsFlat, e => nonImplicitExtensions.indexOf(e) === -1); + function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void { + for (const ext of autoExtensions) { + test(ext, /*hasDirectoryExists*/ false); + test(ext, /*hasDirectoryExists*/ true); } - if (hasDirectoryExists) { - const directories = new Map(); - for (const f of files) { - let name = getDirectoryPath(f.name); - while (true) { - directories.set(name, name); - const baseName = getDirectoryPath(name); - if (baseName === name) { - break; - } - name = baseName; + function test(ext: string, hasDirectoryExists: boolean) { + const containingFile = { name: containingFileName }; + const moduleFile = { name: moduleFileNameNoExt + ext }; + const resolution = ts.nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); + checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name)); + + const failedLookupLocations: string[] = []; + const dir = ts.getDirectoryPath(containingFileName); + for (const e of autoExtensions) { + if (e === ext) { + break; } - } - return { - readFile, - realpath, - directoryExists: path => directories.has(path), - fileExists: path => { - assert.isTrue(directories.has(getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`); - return map.has(path); - }, - useCaseSensitiveFileNames: true - }; - } - else { - return { readFile, realpath, fileExists: path => map.has(path), useCaseSensitiveFileNames: true }; - } - function readFile(path: string): string | undefined { - const file = map.get(path); - return file && file.content; - } - function realpath(path: string): string { - return map.get(path)!.name; - } - } - - describe("unittests:: moduleResolution:: Node module resolution - relative paths", () => { - // node module resolution does _not_ implicitly append these extensions to an extensionless path (though will still attempt to load them if explicitly) - const nonImplicitExtensions = [Extension.Mts, Extension.Dmts, Extension.Mjs, Extension.Cts, Extension.Dcts, Extension.Cjs]; - const autoExtensions = filter(supportedTSExtensionsFlat, e => nonImplicitExtensions.indexOf(e) === -1); - function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void { - for (const ext of autoExtensions) { - test(ext, /*hasDirectoryExists*/ false); - test(ext, /*hasDirectoryExists*/ true); - } - - function test(ext: string, hasDirectoryExists: boolean) { - const containingFile = { name: containingFileName }; - const moduleFile = { name: moduleFileNameNoExt + ext }; - const resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); - checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name)); - - const failedLookupLocations: string[] = []; - const dir = getDirectoryPath(containingFileName); - for (const e of autoExtensions) { - if (e === ext) { - break; - } - else { - failedLookupLocations.push(normalizePath(getRootLength(moduleName) === 0 ? combinePaths(dir, moduleName) : moduleName) + e); - } + else { + failedLookupLocations.push(ts.normalizePath(ts.getRootLength(moduleName) === 0 ? ts.combinePaths(dir, moduleName) : moduleName) + e); } + } - assert.deepEqual(resolution.failedLookupLocations, failedLookupLocations); + assert.deepEqual(resolution.failedLookupLocations, failedLookupLocations); - } } + } - it("module name that starts with './' resolved as relative file name", () => { - testLoadAsFile("/foo/bar/baz.ts", "/foo/bar/foo", "./foo"); - }); + it("module name that starts with './' resolved as relative file name", () => { + testLoadAsFile("/foo/bar/baz.ts", "/foo/bar/foo", "./foo"); + }); - it("module name that starts with '../' resolved as relative file name", () => { - testLoadAsFile("/foo/bar/baz.ts", "/foo/foo", "../foo"); - }); + it("module name that starts with '../' resolved as relative file name", () => { + testLoadAsFile("/foo/bar/baz.ts", "/foo/foo", "../foo"); + }); - it("module name that starts with '/' script extension resolved as relative file name", () => { - testLoadAsFile("/foo/bar/baz.ts", "/foo", "/foo"); - }); + it("module name that starts with '/' script extension resolved as relative file name", () => { + testLoadAsFile("/foo/bar/baz.ts", "/foo", "/foo"); + }); - it("module name that starts with 'c:/' script extension resolved as relative file name", () => { - testLoadAsFile("c:/foo/bar/baz.ts", "c:/foo", "c:/foo"); - }); + it("module name that starts with 'c:/' script extension resolved as relative file name", () => { + testLoadAsFile("c:/foo/bar/baz.ts", "c:/foo", "c:/foo"); + }); - function testLoadingFromPackageJson(containingFileName: string, packageJsonFileName: string, fieldRef: string, moduleFileName: string, moduleName: string): void { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const containingFile = { name: containingFileName }; - const packageJson = { name: packageJsonFileName, content: JSON.stringify({ typings: fieldRef }) }; - const moduleFile = { name: moduleFileName }; - const resolution = nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile)); - checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name)); - // expect three failed lookup location - attempt to load module as file with all supported extensions - assert.equal(resolution.failedLookupLocations.length, supportedTSExtensions[0].length); - assert.deepEqual(resolution.affectingLocations, [packageJsonFileName]); - } + function testLoadingFromPackageJson(containingFileName: string, packageJsonFileName: string, fieldRef: string, moduleFileName: string, moduleName: string): void { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const containingFile = { name: containingFileName }; + const packageJson = { name: packageJsonFileName, content: JSON.stringify({ typings: fieldRef }) }; + const moduleFile = { name: moduleFileName }; + const resolution = ts.nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile)); + checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name)); + // expect three failed lookup location - attempt to load module as file with all supported extensions + assert.equal(resolution.failedLookupLocations.length, ts.supportedTSExtensions[0].length); + assert.deepEqual(resolution.affectingLocations, [packageJsonFileName]); } + } - it("module name as directory - load from 'typings'", () => { - testLoadingFromPackageJson("/a/b/c/d.ts", "/a/b/c/bar/package.json", "c/d/e.d.ts", "/a/b/c/bar/c/d/e.d.ts", "./bar"); - testLoadingFromPackageJson("/a/b/c/d.ts", "/a/bar/package.json", "e.d.ts", "/a/bar/e.d.ts", "../../bar"); - testLoadingFromPackageJson("/a/b/c/d.ts", "/bar/package.json", "e.d.ts", "/bar/e.d.ts", "/bar"); - testLoadingFromPackageJson("c:/a/b/c/d.ts", "c:/bar/package.json", "e.d.ts", "c:/bar/e.d.ts", "c:/bar"); - }); + it("module name as directory - load from 'typings'", () => { + testLoadingFromPackageJson("/a/b/c/d.ts", "/a/b/c/bar/package.json", "c/d/e.d.ts", "/a/b/c/bar/c/d/e.d.ts", "./bar"); + testLoadingFromPackageJson("/a/b/c/d.ts", "/a/bar/package.json", "e.d.ts", "/a/bar/e.d.ts", "../../bar"); + testLoadingFromPackageJson("/a/b/c/d.ts", "/bar/package.json", "e.d.ts", "/bar/e.d.ts", "/bar"); + testLoadingFromPackageJson("c:/a/b/c/d.ts", "c:/bar/package.json", "e.d.ts", "c:/bar/e.d.ts", "c:/bar"); + }); - function testTypingsIgnored(typings: any): void { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); + function testTypingsIgnored(typings: any): void { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - function test(hasDirectoryExists: boolean) { - const containingFile = { name: "/a/b.ts" }; - const packageJson = { name: "/node_modules/b/package.json", content: JSON.stringify({ typings }) }; - const moduleFile = { name: "/a/b.d.ts" }; + function test(hasDirectoryExists: boolean) { + const containingFile = { name: "/a/b.ts" }; + const packageJson = { name: "/node_modules/b/package.json", content: JSON.stringify({ typings }) }; + const moduleFile = { name: "/a/b.d.ts" }; - const indexPath = "/node_modules/b/index.d.ts"; - const indexFile = { name: indexPath }; + const indexPath = "/node_modules/b/index.d.ts"; + const indexFile = { name: indexPath }; - const resolution = nodeModuleNameResolver("b", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile, indexFile)); + const resolution = ts.nodeModuleNameResolver("b", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, moduleFile, indexFile)); - checkResolvedModule(resolution.resolvedModule, createResolvedModule(indexPath, /*isExternalLibraryImport*/ true)); - } + checkResolvedModule(resolution.resolvedModule, createResolvedModule(indexPath, /*isExternalLibraryImport*/ true)); } + } - it("module name as directory - handle invalid 'typings'", () => { - testTypingsIgnored(["a", "b"]); - testTypingsIgnored({ a: "b" }); - testTypingsIgnored(/*typings*/ true); - testTypingsIgnored(/*typings*/ null); // eslint-disable-line no-null/no-null - testTypingsIgnored(/*typings*/ undefined); + it("module name as directory - handle invalid 'typings'", () => { + testTypingsIgnored(["a", "b"]); + testTypingsIgnored({ a: "b" }); + testTypingsIgnored(/*typings*/ true); + testTypingsIgnored(/*typings*/ null); // eslint-disable-line no-null/no-null + testTypingsIgnored(/*typings*/ undefined); + }); + it("module name as directory - load index.d.ts", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const containingFile = { name: "/a/b/c.ts" }; + const packageJson = { name: "/a/b/foo/package.json", content: JSON.stringify({ main: "/c/d" }) }; + const indexFile = { name: "/a/b/foo/index.d.ts" }; + const resolution = ts.nodeModuleNameResolver("./foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, indexFile)); + checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(indexFile.name), [ + "/a/b/foo.ts", + "/a/b/foo.tsx", + "/a/b/foo.d.ts", + "/c/d", + "/c/d.ts", + "/c/d.tsx", + "/c/d.d.ts", + "/c/d/index.ts", + "/c/d/index.tsx", + "/c/d/index.d.ts", + "/a/b/foo/index.ts", + "/a/b/foo/index.tsx", + ]); + } + }); +}); + +describe("unittests:: moduleResolution:: Node module resolution - non-relative paths", () => { + it("computes correct commonPrefix for moduleName cache", () => { + const resolutionCache = ts.createModuleResolutionCache("/", (f) => f); + let cache = resolutionCache.getOrCreateCacheForModuleName("a", /*mode*/ undefined); + cache.set("/sub", { + resolvedModule: { + originalPath: undefined, + resolvedFileName: "/sub/node_modules/a/index.ts", + isExternalLibraryImport: true, + extension: ts.Extension.Ts, + }, + failedLookupLocations: [], + affectingLocations: [], + resolutionDiagnostics: [], }); - it("module name as directory - load index.d.ts", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const containingFile = { name: "/a/b/c.ts" }; - const packageJson = { name: "/a/b/foo/package.json", content: JSON.stringify({ main: "/c/d" }) }; - const indexFile = { name: "/a/b/foo/index.d.ts" }; - const resolution = nodeModuleNameResolver("./foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, packageJson, indexFile)); - checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(indexFile.name), [ - "/a/b/foo.ts", - "/a/b/foo.tsx", - "/a/b/foo.d.ts", - "/c/d", - "/c/d.ts", - "/c/d.tsx", - "/c/d.d.ts", - "/c/d/index.ts", - "/c/d/index.tsx", - "/c/d/index.d.ts", - "/a/b/foo/index.ts", - "/a/b/foo/index.tsx", - ]); - } + assert.isDefined(cache.get("/sub")); + assert.isUndefined(cache.get("/")); + + cache = resolutionCache.getOrCreateCacheForModuleName("b", /*mode*/ undefined); + cache.set("/sub/dir/foo", { + resolvedModule: { + originalPath: undefined, + resolvedFileName: "/sub/directory/node_modules/b/index.ts", + isExternalLibraryImport: true, + extension: ts.Extension.Ts, + }, + failedLookupLocations: [], + affectingLocations: [], + resolutionDiagnostics: [], }); - }); - - describe("unittests:: moduleResolution:: Node module resolution - non-relative paths", () => { - it("computes correct commonPrefix for moduleName cache", () => { - const resolutionCache = createModuleResolutionCache("/", (f) => f); - let cache = resolutionCache.getOrCreateCacheForModuleName("a", /*mode*/ undefined); - cache.set("/sub", { - resolvedModule: { - originalPath: undefined, - resolvedFileName: "/sub/node_modules/a/index.ts", - isExternalLibraryImport: true, - extension: Extension.Ts, - }, - failedLookupLocations: [], - affectingLocations: [], - resolutionDiagnostics: [], - }); - assert.isDefined(cache.get("/sub")); - assert.isUndefined(cache.get("/")); - - cache = resolutionCache.getOrCreateCacheForModuleName("b", /*mode*/ undefined); - cache.set("/sub/dir/foo", { - resolvedModule: { - originalPath: undefined, - resolvedFileName: "/sub/directory/node_modules/b/index.ts", - isExternalLibraryImport: true, - extension: Extension.Ts, - }, - failedLookupLocations: [], - affectingLocations: [], - resolutionDiagnostics: [], - }); - assert.isDefined(cache.get("/sub/dir/foo")); - assert.isDefined(cache.get("/sub/dir")); - assert.isDefined(cache.get("/sub")); - assert.isUndefined(cache.get("/")); - - cache = resolutionCache.getOrCreateCacheForModuleName("c", /*mode*/ undefined); - cache.set("/foo/bar", { - resolvedModule: { - originalPath: undefined, - resolvedFileName: "/bar/node_modules/c/index.ts", - isExternalLibraryImport: true, - extension: Extension.Ts, - }, - failedLookupLocations: [], - affectingLocations: [], - resolutionDiagnostics: [], - }); - assert.isDefined(cache.get("/foo/bar")); - assert.isDefined(cache.get("/foo")); - assert.isDefined(cache.get("/")); - - cache = resolutionCache.getOrCreateCacheForModuleName("d", /*mode*/ undefined); - cache.set("/foo", { - resolvedModule: { - originalPath: undefined, - resolvedFileName: "/foo/index.ts", - isExternalLibraryImport: true, - extension: Extension.Ts, - }, - failedLookupLocations: [], - affectingLocations: [], - resolutionDiagnostics: [], - }); - assert.isDefined(cache.get("/foo")); - assert.isUndefined(cache.get("/")); - - cache = resolutionCache.getOrCreateCacheForModuleName("e", /*mode*/ undefined); - cache.set("c:/foo", { - resolvedModule: { - originalPath: undefined, - resolvedFileName: "d:/bar/node_modules/e/index.ts", - isExternalLibraryImport: true, - extension: Extension.Ts, - }, - failedLookupLocations: [], - affectingLocations: [], - resolutionDiagnostics: [], - }); - assert.isDefined(cache.get("c:/foo")); - assert.isDefined(cache.get("c:/")); - assert.isUndefined(cache.get("d:/")); - - cache = resolutionCache.getOrCreateCacheForModuleName("f", /*mode*/ undefined); - cache.set("/foo/bar/baz", { - resolvedModule: undefined, - failedLookupLocations: [], - affectingLocations: [], - resolutionDiagnostics: [], - }); - assert.isDefined(cache.get("/foo/bar/baz")); - assert.isDefined(cache.get("/foo/bar")); - assert.isDefined(cache.get("/foo")); - assert.isDefined(cache.get("/")); + assert.isDefined(cache.get("/sub/dir/foo")); + assert.isDefined(cache.get("/sub/dir")); + assert.isDefined(cache.get("/sub")); + assert.isUndefined(cache.get("/")); + + cache = resolutionCache.getOrCreateCacheForModuleName("c", /*mode*/ undefined); + cache.set("/foo/bar", { + resolvedModule: { + originalPath: undefined, + resolvedFileName: "/bar/node_modules/c/index.ts", + isExternalLibraryImport: true, + extension: ts.Extension.Ts, + }, + failedLookupLocations: [], + affectingLocations: [], + resolutionDiagnostics: [], }); + assert.isDefined(cache.get("/foo/bar")); + assert.isDefined(cache.get("/foo")); + assert.isDefined(cache.get("/")); + + cache = resolutionCache.getOrCreateCacheForModuleName("d", /*mode*/ undefined); + cache.set("/foo", { + resolvedModule: { + originalPath: undefined, + resolvedFileName: "/foo/index.ts", + isExternalLibraryImport: true, + extension: ts.Extension.Ts, + }, + failedLookupLocations: [], + affectingLocations: [], + resolutionDiagnostics: [], + }); + assert.isDefined(cache.get("/foo")); + assert.isUndefined(cache.get("/")); + + cache = resolutionCache.getOrCreateCacheForModuleName("e", /*mode*/ undefined); + cache.set("c:/foo", { + resolvedModule: { + originalPath: undefined, + resolvedFileName: "d:/bar/node_modules/e/index.ts", + isExternalLibraryImport: true, + extension: ts.Extension.Ts, + }, + failedLookupLocations: [], + affectingLocations: [], + resolutionDiagnostics: [], + }); + assert.isDefined(cache.get("c:/foo")); + assert.isDefined(cache.get("c:/")); + assert.isUndefined(cache.get("d:/")); + + cache = resolutionCache.getOrCreateCacheForModuleName("f", /*mode*/ undefined); + cache.set("/foo/bar/baz", { + resolvedModule: undefined, + failedLookupLocations: [], + affectingLocations: [], + resolutionDiagnostics: [], + }); + assert.isDefined(cache.get("/foo/bar/baz")); + assert.isDefined(cache.get("/foo/bar")); + assert.isDefined(cache.get("/foo")); + assert.isDefined(cache.get("/")); + }); - it("load module as file - ts files not loaded", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const containingFile = { name: "/a/b/c/d/e.ts" }; - const moduleFile = { name: "/a/b/node_modules/foo.ts" }; - const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); - checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [ - "/a/b/c/d/node_modules/foo/package.json", - "/a/b/c/d/node_modules/foo.ts", - "/a/b/c/d/node_modules/foo.tsx", - "/a/b/c/d/node_modules/foo.d.ts", + it("load module as file - ts files not loaded", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - "/a/b/c/d/node_modules/foo/index.ts", - "/a/b/c/d/node_modules/foo/index.tsx", - "/a/b/c/d/node_modules/foo/index.d.ts", + function test(hasDirectoryExists: boolean) { + const containingFile = { name: "/a/b/c/d/e.ts" }; + const moduleFile = { name: "/a/b/node_modules/foo.ts" }; + const resolution = ts.nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); + checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [ + "/a/b/c/d/node_modules/foo/package.json", + "/a/b/c/d/node_modules/foo.ts", + "/a/b/c/d/node_modules/foo.tsx", + "/a/b/c/d/node_modules/foo.d.ts", - "/a/b/c/d/node_modules/@types/foo/package.json", - "/a/b/c/d/node_modules/@types/foo.d.ts", + "/a/b/c/d/node_modules/foo/index.ts", + "/a/b/c/d/node_modules/foo/index.tsx", + "/a/b/c/d/node_modules/foo/index.d.ts", - "/a/b/c/d/node_modules/@types/foo/index.d.ts", + "/a/b/c/d/node_modules/@types/foo/package.json", + "/a/b/c/d/node_modules/@types/foo.d.ts", - "/a/b/c/node_modules/foo/package.json", - "/a/b/c/node_modules/foo.ts", - "/a/b/c/node_modules/foo.tsx", - "/a/b/c/node_modules/foo.d.ts", + "/a/b/c/d/node_modules/@types/foo/index.d.ts", - "/a/b/c/node_modules/foo/index.ts", - "/a/b/c/node_modules/foo/index.tsx", - "/a/b/c/node_modules/foo/index.d.ts", + "/a/b/c/node_modules/foo/package.json", + "/a/b/c/node_modules/foo.ts", + "/a/b/c/node_modules/foo.tsx", + "/a/b/c/node_modules/foo.d.ts", - "/a/b/c/node_modules/@types/foo/package.json", - "/a/b/c/node_modules/@types/foo.d.ts", + "/a/b/c/node_modules/foo/index.ts", + "/a/b/c/node_modules/foo/index.tsx", + "/a/b/c/node_modules/foo/index.d.ts", - "/a/b/c/node_modules/@types/foo/index.d.ts", - "/a/b/node_modules/foo/package.json", - ]); - } - }); + "/a/b/c/node_modules/@types/foo/package.json", + "/a/b/c/node_modules/@types/foo.d.ts", - it("load module as file", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); + "/a/b/c/node_modules/@types/foo/index.d.ts", + "/a/b/node_modules/foo/package.json", + ]); + } + }); - function test(hasDirectoryExists: boolean) { - const containingFile = { name: "/a/b/c/d/e.ts" }; - const moduleFile = { name: "/a/b/node_modules/foo.d.ts" }; - const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); - checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true)); - } - }); + it("load module as file", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - it("load module as directory", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); + function test(hasDirectoryExists: boolean) { + const containingFile = { name: "/a/b/c/d/e.ts" }; + const moduleFile = { name: "/a/b/node_modules/foo.d.ts" }; + const resolution = ts.nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); + checkResolvedModule(resolution.resolvedModule, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true)); + } + }); - function test(hasDirectoryExists: boolean) { - const containingFile: File = { name: "/a/node_modules/b/c/node_modules/d/e.ts" }; - const moduleFile: File = { name: "/a/node_modules/foo/index.d.ts" }; - const resolution = nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); - checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [ - "/a/node_modules/b/c/node_modules/d/node_modules/foo/package.json", - "/a/node_modules/b/c/node_modules/d/node_modules/foo.ts", - "/a/node_modules/b/c/node_modules/d/node_modules/foo.tsx", - "/a/node_modules/b/c/node_modules/d/node_modules/foo.d.ts", + it("load module as directory", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.ts", - "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.tsx", - "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.d.ts", + function test(hasDirectoryExists: boolean) { + const containingFile: File = { name: "/a/node_modules/b/c/node_modules/d/e.ts" }; + const moduleFile: File = { name: "/a/node_modules/foo/index.d.ts" }; + const resolution = ts.nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(hasDirectoryExists, containingFile, moduleFile)); + checkResolvedModuleWithFailedLookupLocations(resolution, createResolvedModule(moduleFile.name, /*isExternalLibraryImport*/ true), [ + "/a/node_modules/b/c/node_modules/d/node_modules/foo/package.json", + "/a/node_modules/b/c/node_modules/d/node_modules/foo.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/foo.tsx", + "/a/node_modules/b/c/node_modules/d/node_modules/foo.d.ts", - "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/package.json", - "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo.d.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.tsx", + "/a/node_modules/b/c/node_modules/d/node_modules/foo/index.d.ts", - "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/index.d.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/package.json", + "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo.d.ts", - "/a/node_modules/b/c/node_modules/foo/package.json", - "/a/node_modules/b/c/node_modules/foo.ts", - "/a/node_modules/b/c/node_modules/foo.tsx", - "/a/node_modules/b/c/node_modules/foo.d.ts", + "/a/node_modules/b/c/node_modules/d/node_modules/@types/foo/index.d.ts", - "/a/node_modules/b/c/node_modules/foo/index.ts", - "/a/node_modules/b/c/node_modules/foo/index.tsx", - "/a/node_modules/b/c/node_modules/foo/index.d.ts", + "/a/node_modules/b/c/node_modules/foo/package.json", + "/a/node_modules/b/c/node_modules/foo.ts", + "/a/node_modules/b/c/node_modules/foo.tsx", + "/a/node_modules/b/c/node_modules/foo.d.ts", - "/a/node_modules/b/c/node_modules/@types/foo/package.json", - "/a/node_modules/b/c/node_modules/@types/foo.d.ts", + "/a/node_modules/b/c/node_modules/foo/index.ts", + "/a/node_modules/b/c/node_modules/foo/index.tsx", + "/a/node_modules/b/c/node_modules/foo/index.d.ts", - "/a/node_modules/b/c/node_modules/@types/foo/index.d.ts", + "/a/node_modules/b/c/node_modules/@types/foo/package.json", + "/a/node_modules/b/c/node_modules/@types/foo.d.ts", - "/a/node_modules/b/node_modules/foo/package.json", - "/a/node_modules/b/node_modules/foo.ts", - "/a/node_modules/b/node_modules/foo.tsx", - "/a/node_modules/b/node_modules/foo.d.ts", + "/a/node_modules/b/c/node_modules/@types/foo/index.d.ts", - "/a/node_modules/b/node_modules/foo/index.ts", - "/a/node_modules/b/node_modules/foo/index.tsx", - "/a/node_modules/b/node_modules/foo/index.d.ts", + "/a/node_modules/b/node_modules/foo/package.json", + "/a/node_modules/b/node_modules/foo.ts", + "/a/node_modules/b/node_modules/foo.tsx", + "/a/node_modules/b/node_modules/foo.d.ts", - "/a/node_modules/b/node_modules/@types/foo/package.json", - "/a/node_modules/b/node_modules/@types/foo.d.ts", + "/a/node_modules/b/node_modules/foo/index.ts", + "/a/node_modules/b/node_modules/foo/index.tsx", + "/a/node_modules/b/node_modules/foo/index.d.ts", - "/a/node_modules/b/node_modules/@types/foo/index.d.ts", + "/a/node_modules/b/node_modules/@types/foo/package.json", + "/a/node_modules/b/node_modules/@types/foo.d.ts", - "/a/node_modules/foo/package.json", - "/a/node_modules/foo.ts", - "/a/node_modules/foo.tsx", - "/a/node_modules/foo.d.ts", + "/a/node_modules/b/node_modules/@types/foo/index.d.ts", - "/a/node_modules/foo/index.ts", - "/a/node_modules/foo/index.tsx" - ]); - } - }); + "/a/node_modules/foo/package.json", + "/a/node_modules/foo.ts", + "/a/node_modules/foo.tsx", + "/a/node_modules/foo.d.ts", - testPreserveSymlinks(/*preserveSymlinks*/ false); - testPreserveSymlinks(/*preserveSymlinks*/ true); - function testPreserveSymlinks(preserveSymlinks: boolean) { - it(`preserveSymlinks: ${preserveSymlinks}`, () => { - const realFileName = "/linked/index.d.ts"; - const symlinkFileName = "/app/node_modules/linked/index.d.ts"; - const host = createModuleResolutionHost( - /*hasDirectoryExists*/ true, - { name: realFileName, symlinks: [symlinkFileName] }, - { name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' }, - ); - const resolution = nodeModuleNameResolver("linked", "/app/app.ts", { preserveSymlinks }, host); - const resolvedFileName = preserveSymlinks ? symlinkFileName : realFileName; - checkResolvedModule(resolution.resolvedModule, createResolvedModule(resolvedFileName, /*isExternalLibraryImport*/ true)); - }); + "/a/node_modules/foo/index.ts", + "/a/node_modules/foo/index.tsx" + ]); } + }); - it("uses originalPath for caching", () => { - const host = createModuleResolutionHost( - /*hasDirectoryExists*/ true, - { - name: "/modules/a.ts", - symlinks: ["/sub/node_modules/a/index.ts"], - }, - { - name: "/sub/node_modules/a/package.json", - content: '{"version": "0.0.0", "main": "./index"}' - } - ); - const compilerOptions: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs }; - const cache = createModuleResolutionCache("/", (f) => f); - let resolution = resolveModuleName("a", "/sub/dir/foo.ts", compilerOptions, host, cache); - checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true)); - - resolution = resolveModuleName("a", "/sub/foo.ts", compilerOptions, host, cache); - checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true)); - - resolution = resolveModuleName("a", "/foo.ts", compilerOptions, host, cache); - assert.isUndefined(resolution.resolvedModule, "lookup in parent directory doesn't hit the cache"); - }); - - it("preserves originalPath on cache hit", () => { + testPreserveSymlinks(/*preserveSymlinks*/ false); + testPreserveSymlinks(/*preserveSymlinks*/ true); + function testPreserveSymlinks(preserveSymlinks: boolean) { + it(`preserveSymlinks: ${preserveSymlinks}`, () => { + const realFileName = "/linked/index.d.ts"; + const symlinkFileName = "/app/node_modules/linked/index.d.ts"; const host = createModuleResolutionHost( /*hasDirectoryExists*/ true, - { name: "/linked/index.d.ts", symlinks: ["/app/node_modules/linked/index.d.ts"] }, + { name: realFileName, symlinks: [symlinkFileName] }, { name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' }, ); - const cache = createModuleResolutionCache("/", (f) => f); - const compilerOptions: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs }; - checkResolution(resolveModuleName("linked", "/app/src/app.ts", compilerOptions, host, cache)); - checkResolution(resolveModuleName("linked", "/app/lib/main.ts", compilerOptions, host, cache)); - - function checkResolution(resolution: ResolvedModuleWithFailedLookupLocations) { - checkResolvedModule(resolution.resolvedModule, createResolvedModule("/linked/index.d.ts", /*isExternalLibraryImport*/ true)); - assert.strictEqual(resolution.resolvedModule!.originalPath, "/app/node_modules/linked/index.d.ts"); - } + const resolution = ts.nodeModuleNameResolver("linked", "/app/app.ts", { preserveSymlinks }, host); + const resolvedFileName = preserveSymlinks ? symlinkFileName : realFileName; + checkResolvedModule(resolution.resolvedModule, createResolvedModule(resolvedFileName, /*isExternalLibraryImport*/ true)); }); - }); + } - describe("unittests:: moduleResolution:: Relative imports", () => { - function test(files: ESMap, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { - const options: CompilerOptions = { module: ModuleKind.CommonJS }; - const host: CompilerHost = { - getSourceFile: (fileName: string, languageVersion: ScriptTarget) => { - const path = normalizePath(combinePaths(currentDirectory, fileName)); - const file = files.get(path); - return file ? createSourceFile(fileName, file, languageVersion) : undefined; - }, - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => currentDirectory, - getDirectories: () => [], - getCanonicalFileName: fileName => fileName.toLowerCase(), - getNewLine: () => "\r\n", - useCaseSensitiveFileNames: () => false, - fileExists: fileName => { - const path = normalizePath(combinePaths(currentDirectory, fileName)); - return files.has(path); - }, - readFile: notImplemented, - }; + it("uses originalPath for caching", () => { + const host = createModuleResolutionHost( + /*hasDirectoryExists*/ true, + { + name: "/modules/a.ts", + symlinks: ["/sub/node_modules/a/index.ts"], + }, + { + name: "/sub/node_modules/a/package.json", + content: '{"version": "0.0.0", "main": "./index"}' + } + ); + const compilerOptions: ts.CompilerOptions = { moduleResolution: ts.ModuleResolutionKind.NodeJs }; + const cache = ts.createModuleResolutionCache("/", (f) => f); + let resolution = ts.resolveModuleName("a", "/sub/dir/foo.ts", compilerOptions, host, cache); + checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true)); - const program = createProgram(rootFiles, options, host); + resolution = ts.resolveModuleName("a", "/sub/foo.ts", compilerOptions, host, cache); + checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true)); - assert.equal(program.getSourceFiles().length, expectedFilesCount); - const syntacticDiagnostics = program.getSyntacticDiagnostics(); - assert.equal(syntacticDiagnostics.length, 0, `expect no syntactic diagnostics, got: ${JSON.stringify(Harness.Compiler.minimalDiagnosticsToString(syntacticDiagnostics))}`); - const semanticDiagnostics = program.getSemanticDiagnostics(); - assert.equal(semanticDiagnostics.length, 0, `expect no semantic diagnostics, got: ${JSON.stringify(Harness.Compiler.minimalDiagnosticsToString(semanticDiagnostics))}`); + resolution = ts.resolveModuleName("a", "/foo.ts", compilerOptions, host, cache); + assert.isUndefined(resolution.resolvedModule, "lookup in parent directory doesn't hit the cache"); + }); - // try to get file using a relative name - for (const relativeFileName of relativeNamesToCheck) { - assert.isTrue(program.getSourceFile(relativeFileName) !== undefined, `expected to get file by relative name, got undefined`); - } + it("preserves originalPath on cache hit", () => { + const host = createModuleResolutionHost( + /*hasDirectoryExists*/ true, + { name: "/linked/index.d.ts", symlinks: ["/app/node_modules/linked/index.d.ts"] }, + { name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' }, + ); + const cache = ts.createModuleResolutionCache("/", (f) => f); + const compilerOptions: ts.CompilerOptions = { moduleResolution: ts.ModuleResolutionKind.NodeJs }; + checkResolution(ts.resolveModuleName("linked", "/app/src/app.ts", compilerOptions, host, cache)); + checkResolution(ts.resolveModuleName("linked", "/app/lib/main.ts", compilerOptions, host, cache)); + + function checkResolution(resolution: ts.ResolvedModuleWithFailedLookupLocations) { + checkResolvedModule(resolution.resolvedModule, createResolvedModule("/linked/index.d.ts", /*isExternalLibraryImport*/ true)); + assert.strictEqual(resolution.resolvedModule!.originalPath, "/app/node_modules/linked/index.d.ts"); + } + }); +}); + +describe("unittests:: moduleResolution:: Relative imports", () => { + function test(files: ts.ESMap, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { + const options: ts.CompilerOptions = { module: ts.ModuleKind.CommonJS }; + const host: ts.CompilerHost = { + getSourceFile: (fileName: string, languageVersion: ts.ScriptTarget) => { + const path = ts.normalizePath(ts.combinePaths(currentDirectory, fileName)); + const file = files.get(path); + return file ? ts.createSourceFile(fileName, file, languageVersion) : undefined; + }, + getDefaultLibFileName: () => "lib.d.ts", + writeFile: ts.notImplemented, + getCurrentDirectory: () => currentDirectory, + getDirectories: () => [], + getCanonicalFileName: fileName => fileName.toLowerCase(), + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => false, + fileExists: fileName => { + const path = ts.normalizePath(ts.combinePaths(currentDirectory, fileName)); + return files.has(path); + }, + readFile: ts.notImplemented, + }; + + const program = ts.createProgram(rootFiles, options, host); + + assert.equal(program.getSourceFiles().length, expectedFilesCount); + const syntacticDiagnostics = program.getSyntacticDiagnostics(); + assert.equal(syntacticDiagnostics.length, 0, `expect no syntactic diagnostics, got: ${JSON.stringify(Harness.Compiler.minimalDiagnosticsToString(syntacticDiagnostics))}`); + const semanticDiagnostics = program.getSemanticDiagnostics(); + assert.equal(semanticDiagnostics.length, 0, `expect no semantic diagnostics, got: ${JSON.stringify(Harness.Compiler.minimalDiagnosticsToString(semanticDiagnostics))}`); + + // try to get file using a relative name + for (const relativeFileName of relativeNamesToCheck) { + assert.isTrue(program.getSourceFile(relativeFileName) !== undefined, `expected to get file by relative name, got undefined`); } + } - it("should find all modules", () => { - const files = new Map(getEntries({ - "/a/b/c/first/shared.ts": ` + it("should find all modules", () => { + const files = new ts.Map(ts.getEntries({ + "/a/b/c/first/shared.ts": ` class A {} export = A`, - "/a/b/c/first/second/class_a.ts": ` + "/a/b/c/first/second/class_a.ts": ` import Shared = require('../shared'); import C = require('../../third/class_c'); class B {} export = B;`, - "/a/b/c/third/class_c.ts": ` + "/a/b/c/third/class_c.ts": ` import Shared = require('../first/shared'); class C {} export = C; ` - })); - test(files, "/a/b/c/first/second", ["class_a.ts"], 3, ["../../../c/third/class_c.ts"]); - }); - - it("should find modules in node_modules", () => { - const files = new Map(getEntries({ - "/parent/node_modules/mod/index.d.ts": "export var x", - "/parent/app/myapp.ts": `import {x} from "mod"` - })); - test(files, "/parent/app", ["myapp.ts"], 2, []); - }); + })); + test(files, "/a/b/c/first/second", ["class_a.ts"], 3, ["../../../c/third/class_c.ts"]); + }); - it("should find file referenced via absolute and relative names", () => { - const files = new Map(getEntries({ - "/a/b/c.ts": `/// `, - "/a/b/b.ts": "var x" - })); - test(files, "/a/b", ["c.ts", "/a/b/b.ts"], 2, []); - }); + it("should find modules in node_modules", () => { + const files = new ts.Map(ts.getEntries({ + "/parent/node_modules/mod/index.d.ts": "export var x", + "/parent/app/myapp.ts": `import {x} from "mod"` + })); + test(files, "/parent/app", ["myapp.ts"], 2, []); }); - describe("unittests:: moduleResolution:: Files with different casing with forceConsistentCasingInFileNames", () => { - let library: SourceFile; - function test( - files: ESMap, - options: CompilerOptions, - currentDirectory: string, - useCaseSensitiveFileNames: boolean, - rootFiles: string[], - expectedDiagnostics: (program: Program) => readonly Diagnostic[] - ): void { - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - if (!useCaseSensitiveFileNames) { - const oldFiles = files; - files = new Map(); - oldFiles.forEach((file, fileName) => { - files.set(getCanonicalFileName(fileName), file); - }); - } + it("should find file referenced via absolute and relative names", () => { + const files = new ts.Map(ts.getEntries({ + "/a/b/c.ts": `/// `, + "/a/b/b.ts": "var x" + })); + test(files, "/a/b", ["c.ts", "/a/b/b.ts"], 2, []); + }); +}); + +describe("unittests:: moduleResolution:: Files with different casing with forceConsistentCasingInFileNames", () => { + let library: ts.SourceFile; + function test( + files: ts.ESMap, + options: ts.CompilerOptions, + currentDirectory: string, + useCaseSensitiveFileNames: boolean, + rootFiles: string[], + expectedDiagnostics: (program: ts.Program) => readonly ts.Diagnostic[] + ): void { + const getCanonicalFileName = ts.createGetCanonicalFileName(useCaseSensitiveFileNames); + if (!useCaseSensitiveFileNames) { + const oldFiles = files; + files = new ts.Map(); + oldFiles.forEach((file, fileName) => { + files.set(getCanonicalFileName(fileName), file); + }); + } - const host: CompilerHost = { - getSourceFile: (fileName: string, languageVersion: ScriptTarget) => { - if (fileName === "lib.d.ts") { - if (!library) { - library = createSourceFile("lib.d.ts", "", ScriptTarget.ES5); - } - return library; + const host: ts.CompilerHost = { + getSourceFile: (fileName: string, languageVersion: ts.ScriptTarget) => { + if (fileName === "lib.d.ts") { + if (!library) { + library = ts.createSourceFile("lib.d.ts", "", ts.ScriptTarget.ES5); } - const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); - const file = files.get(path); - return file ? createSourceFile(fileName, file, languageVersion) : undefined; - }, - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => currentDirectory, - getDirectories: () => [], - getCanonicalFileName, - getNewLine: () => "\r\n", - useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, - fileExists: fileName => { - const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); - return files.has(path); - }, - readFile: notImplemented, - }; - const program = createProgram(rootFiles, options, host); - const diagnostics = sortAndDeduplicateDiagnostics([...program.getSemanticDiagnostics(), ...program.getOptionsDiagnostics()]); - assert.deepEqual(diagnostics, sortAndDeduplicateDiagnostics(expectedDiagnostics(program))); - } + return library; + } + const path = getCanonicalFileName(ts.normalizePath(ts.combinePaths(currentDirectory, fileName))); + const file = files.get(path); + return file ? ts.createSourceFile(fileName, file, languageVersion) : undefined; + }, + getDefaultLibFileName: () => "lib.d.ts", + writeFile: ts.notImplemented, + getCurrentDirectory: () => currentDirectory, + getDirectories: () => [], + getCanonicalFileName, + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, + fileExists: fileName => { + const path = getCanonicalFileName(ts.normalizePath(ts.combinePaths(currentDirectory, fileName))); + return files.has(path); + }, + readFile: ts.notImplemented, + }; + const program = ts.createProgram(rootFiles, options, host); + const diagnostics = ts.sortAndDeduplicateDiagnostics([...program.getSemanticDiagnostics(), ...program.getOptionsDiagnostics()]); + assert.deepEqual(diagnostics, ts.sortAndDeduplicateDiagnostics(expectedDiagnostics(program))); + } - it("should succeed when the same file is referenced using absolute and relative names", () => { - const files = new Map(getEntries({ - "/a/b/c.ts": `/// `, - "/a/b/d.ts": "var x" - })); - test( - files, - { module: ModuleKind.AMD }, - "/a/b", - /*useCaseSensitiveFileNames*/ false, - ["c.ts", "/a/b/d.ts"], - () => emptyArray - ); - }); + it("should succeed when the same file is referenced using absolute and relative names", () => { + const files = new ts.Map(ts.getEntries({ + "/a/b/c.ts": `/// `, + "/a/b/d.ts": "var x" + })); + test( + files, + { module: ts.ModuleKind.AMD }, + "/a/b", + /*useCaseSensitiveFileNames*/ false, + ["c.ts", "/a/b/d.ts"], + () => ts.emptyArray + ); + }); - it("should fail when two files used in program differ only in casing (tripleslash references)", () => { - const files = new Map(getEntries({ - "/a/b/c.ts": `/// `, - "/a/b/d.ts": "var x" - })); - test( - files, - { module: ModuleKind.AMD, forceConsistentCasingInFileNames: true }, - "/a/b", - /*useCaseSensitiveFileNames*/ false, - ["c.ts", "d.ts"], - program => [{ - ...tscWatch.getDiagnosticOfFileFromProgram( - program, - "c.ts", - `/// `.indexOf(`D.ts`), - "D.ts".length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, - ["D.ts", "d.ts"], - [ - tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [ - tscWatch.getDiagnosticMessageChain(Diagnostics.Referenced_via_0_from_file_1, ["D.ts", "c.ts"]), - tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) - ] - ) - ], - ) - ), - relatedInformation: undefined, - }] - ); - }); + it("should fail when two files used in program differ only in casing (tripleslash references)", () => { + const files = new ts.Map(ts.getEntries({ + "/a/b/c.ts": `/// `, + "/a/b/d.ts": "var x" + })); + test( + files, + { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, + "/a/b", + /*useCaseSensitiveFileNames*/ false, + ["c.ts", "d.ts"], + program => [{ + ...ts.tscWatch.getDiagnosticOfFileFromProgram( + program, + "c.ts", + `/// `.indexOf(`D.ts`), + "D.ts".length, + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + ["D.ts", "d.ts"], + [ + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.The_file_is_in_the_program_because_Colon, + ts.emptyArray, + [ + ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Referenced_via_0_from_file_1, ["D.ts", "c.ts"]), + ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation) + ] + ) + ], + ) + ), + relatedInformation: undefined, + }] + ); + }); - it("should fail when two files used in program differ only in casing (imports)", () => { - const files = new Map(getEntries({ - "/a/b/c.ts": `import {x} from "D"`, - "/a/b/d.ts": "export var x" - })); - test( - files, - { module: ModuleKind.AMD, forceConsistentCasingInFileNames: true }, - "/a/b", - /*useCaseSensitiveFileNames*/ false, - ["c.ts", "d.ts"], - program => [{ - ...tscWatch.getDiagnosticOfFileFromProgram( - program, - "c.ts", - `import {x} from "D"`.indexOf(`"D"`), - `"D"`.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, - ["/a/b/D.ts", "d.ts"], - [ - tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [ - tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]), - tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) - ] - ) - ], - ) - ), - relatedInformation: undefined, - }] - ); - }); + it("should fail when two files used in program differ only in casing (imports)", () => { + const files = new ts.Map(ts.getEntries({ + "/a/b/c.ts": `import {x} from "D"`, + "/a/b/d.ts": "export var x" + })); + test( + files, + { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, + "/a/b", + /*useCaseSensitiveFileNames*/ false, + ["c.ts", "d.ts"], + program => [{ + ...ts.tscWatch.getDiagnosticOfFileFromProgram( + program, + "c.ts", + `import {x} from "D"`.indexOf(`"D"`), + `"D"`.length, + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + ["/a/b/D.ts", "d.ts"], + [ + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.The_file_is_in_the_program_because_Colon, + ts.emptyArray, + [ + ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]), + ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation) + ] + ) + ], + ) + ), + relatedInformation: undefined, + }] + ); + }); - it("should fail when two files used in program differ only in casing (imports, relative module names)", () => { - const files = new Map(getEntries({ - "moduleA.ts": `import {x} from "./ModuleB"`, - "moduleB.ts": "export var x" - })); - test( - files, - { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, - "", - /*useCaseSensitiveFileNames*/ false, - ["moduleA.ts", "moduleB.ts"], - program => [{ - ...tscWatch.getDiagnosticOfFileFromProgram( + it("should fail when two files used in program differ only in casing (imports, relative module names)", () => { + const files = new ts.Map(ts.getEntries({ + "moduleA.ts": `import {x} from "./ModuleB"`, + "moduleB.ts": "export var x" + })); + test( + files, + { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, + "", + /*useCaseSensitiveFileNames*/ false, + ["moduleA.ts", "moduleB.ts"], + program => [{ + ...ts.tscWatch.getDiagnosticOfFileFromProgram( + program, + "moduleA.ts", + `import {x} from "./ModuleB"`.indexOf(`"./ModuleB"`), + `"./ModuleB"`.length, + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + ["ModuleB.ts", "moduleB.ts"], + [ + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.The_file_is_in_the_program_because_Colon, + ts.emptyArray, + [ + ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./ModuleB"`, "moduleA.ts"]), + ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation) + ] + ) + ], + ) + ), + relatedInformation: undefined + }] + ); + }); + + it("should fail when two files exist on disk that differs only in casing", () => { + const files = new ts.Map(ts.getEntries({ + "/a/b/c.ts": `import {x} from "D"`, + "/a/b/D.ts": "export var x", + "/a/b/d.ts": "export var y" + })); + test( + files, + { module: ts.ModuleKind.AMD }, + "/a/b", + /*useCaseSensitiveFileNames*/ true, + ["c.ts", "d.ts"], + program => [{ + ...ts.tscWatch.getDiagnosticOfFileFromProgram( + program, + "c.ts", + `import {x} from "D"`.indexOf(`"D"`), + `"D"`.length, + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + ["/a/b/D.ts", "d.ts"], + [ + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.The_file_is_in_the_program_because_Colon, + ts.emptyArray, + [ + ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]), + ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation) + ] + ) + ], + ) + ), + relatedInformation: undefined + }] + ); + }); + + it("should fail when module name in 'require' calls has inconsistent casing", () => { + const files = new ts.Map(ts.getEntries({ + "moduleA.ts": `import a = require("./ModuleC")`, + "moduleB.ts": `import a = require("./moduleC")`, + "moduleC.ts": "export var x" + })); + test( + files, + { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, + "", + /*useCaseSensitiveFileNames*/ false, + ["moduleA.ts", "moduleB.ts", "moduleC.ts"], + program => { + const importInA = { + ...ts.tscWatch.getDiagnosticOfFileFromProgram( program, "moduleA.ts", - `import {x} from "./ModuleB"`.indexOf(`"./ModuleB"`), - `"./ModuleB"`.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, - ["ModuleB.ts", "moduleB.ts"], - [ - tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [ - tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleB"`, "moduleA.ts"]), - tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) - ] - ) - ], - ) + `import a = require("./ModuleC")`.indexOf(`"./ModuleC"`), + `"./ModuleC"`.length, + ts.Diagnostics.File_is_included_via_import_here, ), - relatedInformation: undefined - }] - ); - }); - - it("should fail when two files exist on disk that differs only in casing", () => { - const files = new Map(getEntries({ - "/a/b/c.ts": `import {x} from "D"`, - "/a/b/D.ts": "export var x", - "/a/b/d.ts": "export var y" - })); - test( - files, - { module: ModuleKind.AMD }, - "/a/b", - /*useCaseSensitiveFileNames*/ true, - ["c.ts", "d.ts"], - program => [{ - ...tscWatch.getDiagnosticOfFileFromProgram( + reportsUnnecessary: undefined, + reportsDeprecated: undefined + }; + const importInB = { + ...ts.tscWatch.getDiagnosticOfFileFromProgram( program, - "c.ts", - `import {x} from "D"`.indexOf(`"D"`), - `"D"`.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, - ["/a/b/D.ts", "d.ts"], - [ - tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [ - tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"D"`, "c.ts"]), - tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation) - ] - ) - ], - ) + "moduleB.ts", + `import a = require("./moduleC")`.indexOf(`"./moduleC"`), + `"./moduleC"`.length, + ts.Diagnostics.File_is_included_via_import_here, ), - relatedInformation: undefined - }] - ); - }); + reportsUnnecessary: undefined, + reportsDeprecated: undefined + }; + const importHereInA = ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "moduleA.ts"]); + const importHereInB = ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "moduleB.ts"]); + const details = [ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.The_file_is_in_the_program_because_Colon, + ts.emptyArray, + [importHereInA, importHereInB, ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Root_file_specified_for_compilation)] + )]; + return [ + { + ...ts.tscWatch.getDiagnosticOfFileFrom( + importInA.file, + importInA.start, + importInA.length, + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, + ["ModuleC.ts", "moduleC.ts" ], + details, + ) + ), + relatedInformation: [importInB] + }, + { + ...ts.tscWatch.getDiagnosticOfFileFrom( + importInB.file, + importInB.start, + importInB.length, + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, + ["moduleC.ts", "ModuleC.ts"], + details, + ) + ), + relatedInformation: [importInA] + } + ]; + } + ); + }); - it("should fail when module name in 'require' calls has inconsistent casing", () => { - const files = new Map(getEntries({ - "moduleA.ts": `import a = require("./ModuleC")`, - "moduleB.ts": `import a = require("./moduleC")`, - "moduleC.ts": "export var x" - })); - test( - files, - { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, - "", - /*useCaseSensitiveFileNames*/ false, - ["moduleA.ts", "moduleB.ts", "moduleC.ts"], - program => { - const importInA = { - ...tscWatch.getDiagnosticOfFileFromProgram( + it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => { + const files = new ts.Map(ts.getEntries({ + "/a/B/c/moduleA.ts": `import a = require("./ModuleC")`, + "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, + "/a/B/c/moduleC.ts": "export var x", + "/a/B/c/moduleD.ts": ` +import a = require("./moduleA"); +import b = require("./moduleB"); + ` + })); + test( + files, + { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, + "/a/B/c", + /*useCaseSensitiveFileNames*/ false, + ["moduleD.ts"], + program => [{ + ...ts.tscWatch.getDiagnosticOfFileFromProgram( + program, + "moduleB.ts", + `import a = require("./moduleC")`.indexOf(`"./moduleC"`), + `"./moduleC"`.length, + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, + ["/a/B/c/moduleC.ts", "/a/B/c/ModuleC.ts"], + [ + ts.tscWatch.getDiagnosticMessageChain( + ts.Diagnostics.The_file_is_in_the_program_because_Colon, + ts.emptyArray, + [ + ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "/a/B/c/moduleA.ts"]), + ts.tscWatch.getDiagnosticMessageChain(ts.Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "/a/B/c/moduleB.ts"]) + ] + ) + ], + ) + ), + relatedInformation: [ + { + ...ts.tscWatch.getDiagnosticOfFileFromProgram( program, "moduleA.ts", `import a = require("./ModuleC")`.indexOf(`"./ModuleC"`), `"./ModuleC"`.length, - Diagnostics.File_is_included_via_import_here, - ), - reportsUnnecessary: undefined, - reportsDeprecated: undefined - }; - const importInB = { - ...tscWatch.getDiagnosticOfFileFromProgram( - program, - "moduleB.ts", - `import a = require("./moduleC")`.indexOf(`"./moduleC"`), - `"./moduleC"`.length, - Diagnostics.File_is_included_via_import_here, + ts.Diagnostics.File_is_included_via_import_here, ), reportsUnnecessary: undefined, reportsDeprecated: undefined - }; - const importHereInA = tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "moduleA.ts"]); - const importHereInB = tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "moduleB.ts"]); - const details = [tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [importHereInA, importHereInB, tscWatch.getDiagnosticMessageChain(Diagnostics.Root_file_specified_for_compilation)] - )]; - return [ - { - ...tscWatch.getDiagnosticOfFileFrom( - importInA.file, - importInA.start, - importInA.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, - ["ModuleC.ts", "moduleC.ts" ], - details, - ) - ), - relatedInformation: [importInB] - }, - { - ...tscWatch.getDiagnosticOfFileFrom( - importInB.file, - importInB.start, - importInB.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, - ["moduleC.ts", "ModuleC.ts"], - details, - ) - ), - relatedInformation: [importInA] - } - ]; - } - ); - }); - - it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => { - const files = new Map(getEntries({ - "/a/B/c/moduleA.ts": `import a = require("./ModuleC")`, - "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, - "/a/B/c/moduleC.ts": "export var x", - "/a/B/c/moduleD.ts": ` -import a = require("./moduleA"); -import b = require("./moduleB"); - ` - })); - test( - files, - { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, - "/a/B/c", - /*useCaseSensitiveFileNames*/ false, - ["moduleD.ts"], - program => [{ - ...tscWatch.getDiagnosticOfFileFromProgram( - program, - "moduleB.ts", - `import a = require("./moduleC")`.indexOf(`"./moduleC"`), - `"./moduleC"`.length, - tscWatch.getDiagnosticMessageChain( - Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, - ["/a/B/c/moduleC.ts", "/a/B/c/ModuleC.ts"], - [ - tscWatch.getDiagnosticMessageChain( - Diagnostics.The_file_is_in_the_program_because_Colon, - emptyArray, - [ - tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./ModuleC"`, "/a/B/c/moduleA.ts"]), - tscWatch.getDiagnosticMessageChain(Diagnostics.Imported_via_0_from_file_1, [`"./moduleC"`, "/a/B/c/moduleB.ts"]) - ] - ) - ], - ) - ), - relatedInformation: [ - { - ...tscWatch.getDiagnosticOfFileFromProgram( - program, - "moduleA.ts", - `import a = require("./ModuleC")`.indexOf(`"./ModuleC"`), - `"./ModuleC"`.length, - Diagnostics.File_is_included_via_import_here, - ), - reportsUnnecessary: undefined, - reportsDeprecated: undefined - } - ] - }] - ); - }); - it("should not fail when module names in 'require' calls has consistent casing and current directory has uppercase chars", () => { - const files = new Map(getEntries({ - "/a/B/c/moduleA.ts": `import a = require("./moduleC")`, - "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, - "/a/B/c/moduleC.ts": "export var x", - "/a/B/c/moduleD.ts": ` + } + ] + }] + ); + }); + it("should not fail when module names in 'require' calls has consistent casing and current directory has uppercase chars", () => { + const files = new ts.Map(ts.getEntries({ + "/a/B/c/moduleA.ts": `import a = require("./moduleC")`, + "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, + "/a/B/c/moduleC.ts": "export var x", + "/a/B/c/moduleD.ts": ` import a = require("./moduleA"); import b = require("./moduleB"); ` - })); - test( - files, - { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, - "/a/B/c", - /*useCaseSensitiveFileNames*/ false, - ["moduleD.ts"], - () => emptyArray - ); - }); + })); + test( + files, + { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, + "/a/B/c", + /*useCaseSensitiveFileNames*/ false, + ["moduleD.ts"], + () => ts.emptyArray + ); + }); - it("should succeed when the two files in program differ only in drive letter in their names", () => { - const files = new Map(getEntries({ - "d:/someFolder/moduleA.ts": `import a = require("D:/someFolder/moduleC")`, - "d:/someFolder/moduleB.ts": `import a = require("./moduleC")`, - "D:/someFolder/moduleC.ts": "export const x = 10", - })); - test( - files, - { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, - "d:/someFolder", - /*useCaseSensitiveFileNames*/ false, - ["d:/someFolder/moduleA.ts", "d:/someFolder/moduleB.ts"], - () => emptyArray - ); - }); + it("should succeed when the two files in program differ only in drive letter in their names", () => { + const files = new ts.Map(ts.getEntries({ + "d:/someFolder/moduleA.ts": `import a = require("D:/someFolder/moduleC")`, + "d:/someFolder/moduleB.ts": `import a = require("./moduleC")`, + "D:/someFolder/moduleC.ts": "export const x = 10", + })); + test( + files, + { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, + "d:/someFolder", + /*useCaseSensitiveFileNames*/ false, + ["d:/someFolder/moduleA.ts", "d:/someFolder/moduleB.ts"], + () => ts.emptyArray + ); }); +}); - describe("unittests:: moduleResolution:: baseUrl augmented module resolution", () => { +describe("unittests:: moduleResolution:: baseUrl augmented module resolution", () => { - it("module resolution without path mappings/rootDirs", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); + it("module resolution without path mappings/rootDirs", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - function test(hasDirectoryExists: boolean) { - const file1: File = { name: "/root/folder1/file1.ts" }; - const file2: File = { name: "/root/folder2/file2.ts" }; - const file3: File = { name: "/root/folder2/file3.ts" }; - const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3); - for (const moduleResolution of [ ModuleResolutionKind.NodeJs, ModuleResolutionKind.Classic ]) { - const options: CompilerOptions = { moduleResolution, baseUrl: "/root" }; - { - const result = resolveModuleName("folder2/file2", file1.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file2.name), []); - } - { - const result = resolveModuleName("./file3", file2.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file3.name), []); - } - { - const result = resolveModuleName("/root/folder1/file1", file2.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file1.name), []); - } + function test(hasDirectoryExists: boolean) { + const file1: File = { name: "/root/folder1/file1.ts" }; + const file2: File = { name: "/root/folder2/file2.ts" }; + const file3: File = { name: "/root/folder2/file3.ts" }; + const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3); + for (const moduleResolution of [ ts.ModuleResolutionKind.NodeJs, ts.ModuleResolutionKind.Classic ]) { + const options: ts.CompilerOptions = { moduleResolution, baseUrl: "/root" }; + { + const result = ts.resolveModuleName("folder2/file2", file1.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file2.name), []); } - } - // add failure tests - }); - - it("node + baseUrl", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const main: File = { name: "/root/a/b/main.ts" }; - const m1: File = { name: "/root/m1.ts" }; // load file as module - const m2: File = { name: "/root/m2/index.d.ts" }; // load folder as module - const m3: File = { name: "/root/m3/package.json", content: JSON.stringify({ typings: "dist/typings.d.ts" }) }; - const m3Typings: File = { name: "/root/m3/dist/typings.d.ts" }; - const m4: File = { name: "/root/node_modules/m4.ts" }; // fallback to node - - const options: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs, baseUrl: "/root" }; - const host = createModuleResolutionHost(hasDirectoryExists, main, m1, m2, m3, m3Typings, m4); - - check("m1", main, m1); - check("m2", main, m2); - check("m3", main, m3Typings); - check("m4", main, m4, /*isExternalLibraryImport*/ true); - - function check(name: string, caller: File, expected: File, isExternalLibraryImport = false) { - const result = resolveModuleName(name, caller.name, options, host); - checkResolvedModule(result.resolvedModule, createResolvedModule(expected.name, isExternalLibraryImport)); + { + const result = ts.resolveModuleName("./file3", file2.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file3.name), []); + } + { + const result = ts.resolveModuleName("/root/folder1/file1", file2.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(file1.name), []); } } - }); + } + // add failure tests + }); - it("classic + baseUrl", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); + it("node + baseUrl", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const main: File = { name: "/root/a/b/main.ts" }; + const m1: File = { name: "/root/m1.ts" }; // load file as module + const m2: File = { name: "/root/m2/index.d.ts" }; // load folder as module + const m3: File = { name: "/root/m3/package.json", content: JSON.stringify({ typings: "dist/typings.d.ts" }) }; + const m3Typings: File = { name: "/root/m3/dist/typings.d.ts" }; + const m4: File = { name: "/root/node_modules/m4.ts" }; // fallback to node + + const options: ts.CompilerOptions = { moduleResolution: ts.ModuleResolutionKind.NodeJs, baseUrl: "/root" }; + const host = createModuleResolutionHost(hasDirectoryExists, main, m1, m2, m3, m3Typings, m4); + + check("m1", main, m1); + check("m2", main, m2); + check("m3", main, m3Typings); + check("m4", main, m4, /*isExternalLibraryImport*/ true); + + function check(name: string, caller: File, expected: File, isExternalLibraryImport = false) { + const result = ts.resolveModuleName(name, caller.name, options, host); + checkResolvedModule(result.resolvedModule, createResolvedModule(expected.name, isExternalLibraryImport)); + } + } + }); - function test(hasDirectoryExists: boolean) { - const main: File = { name: "/root/a/b/main.ts" }; - const m1: File = { name: "/root/x/m1.ts" }; // load from base url - const m2: File = { name: "/m2.ts" }; // fallback to classic + it("classic + baseUrl", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); - const options: CompilerOptions = { moduleResolution: ModuleResolutionKind.Classic, baseUrl: "/root/x", jsx: JsxEmit.React }; - const host = createModuleResolutionHost(hasDirectoryExists, main, m1, m2); + function test(hasDirectoryExists: boolean) { + const main: File = { name: "/root/a/b/main.ts" }; + const m1: File = { name: "/root/x/m1.ts" }; // load from base url + const m2: File = { name: "/m2.ts" }; // fallback to classic - check("m1", main, m1); - check("m2", main, m2); + const options: ts.CompilerOptions = { moduleResolution: ts.ModuleResolutionKind.Classic, baseUrl: "/root/x", jsx: ts.JsxEmit.React }; + const host = createModuleResolutionHost(hasDirectoryExists, main, m1, m2); - function check(name: string, caller: File, expected: File) { - const result = resolveModuleName(name, caller.name, options, host); - checkResolvedModule(result.resolvedModule, createResolvedModule(expected.name)); - } - } - }); + check("m1", main, m1); + check("m2", main, m2); - it("node + baseUrl + path mappings", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const main: File = { name: "/root/folder1/main.ts" }; - - const file1: File = { name: "/root/folder1/file1.ts" }; - const file2: File = { name: "/root/generated/folder1/file2.ts" }; // load remapped file as module - const file3: File = { name: "/root/generated/folder2/file3/index.d.ts" }; // load folder a module - const file4Typings: File = { name: "/root/generated/folder2/file4/package.json", content: JSON.stringify({ typings: "dist/types.d.ts" }) }; - const file4: File = { name: "/root/generated/folder2/file4/dist/types.d.ts" }; // load file pointed by typings - const file5: File = { name: "/root/someanotherfolder/file5/index.d.ts" }; // load remapped module from folder - const file6: File = { name: "/root/node_modules/file6.ts" }; // fallback to node - const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3, file4, file4Typings, file5, file6); - - const options: CompilerOptions = { - moduleResolution: ModuleResolutionKind.NodeJs, - baseUrl: "/root", - jsx: JsxEmit.React, - paths: { - "*": [ - "*", - "generated/*" - ], - "somefolder/*": [ - "someanotherfolder/*" - ], - "/rooted/*": [ - "generated/*" - ] - } - }; - check("folder1/file1", file1, []); - check("folder1/file2", file2, [ - // first try the '*' - "/root/folder1/file2.ts", - "/root/folder1/file2.tsx", - "/root/folder1/file2.d.ts", - "/root/folder1/file2/package.json", - - "/root/folder1/file2/index.ts", - "/root/folder1/file2/index.tsx", - "/root/folder1/file2/index.d.ts", - // then first attempt on 'generated/*' was successful - ]); - check("/rooted/folder1/file2", file2, []); - check("folder2/file3", file3, [ - // first try '*' - "/root/folder2/file3.ts", - "/root/folder2/file3.tsx", - "/root/folder2/file3.d.ts", - "/root/folder2/file3/package.json", - - "/root/folder2/file3/index.ts", - "/root/folder2/file3/index.tsx", - "/root/folder2/file3/index.d.ts", - - // then use remapped location - "/root/generated/folder2/file3.ts", - "/root/generated/folder2/file3.tsx", - "/root/generated/folder2/file3.d.ts", - "/root/generated/folder2/file3/package.json", - - "/root/generated/folder2/file3/index.ts", - "/root/generated/folder2/file3/index.tsx", - // success on index.d.ts - ]); - check("folder2/file4", file4, [ - // first try '*' - "/root/folder2/file4.ts", - "/root/folder2/file4.tsx", - "/root/folder2/file4.d.ts", - "/root/folder2/file4/package.json", - - "/root/folder2/file4/index.ts", - "/root/folder2/file4/index.tsx", - "/root/folder2/file4/index.d.ts", - - // try to load from file from remapped location - "/root/generated/folder2/file4.ts", - "/root/generated/folder2/file4.tsx", - "/root/generated/folder2/file4.d.ts", - // success on loading as from folder - ]); - check("somefolder/file5", file5, [ - // load from remapped location - // first load from fle - "/root/someanotherfolder/file5.ts", - "/root/someanotherfolder/file5.tsx", - "/root/someanotherfolder/file5.d.ts", - - // load from folder - "/root/someanotherfolder/file5/package.json", - "/root/someanotherfolder/file5/index.ts", - "/root/someanotherfolder/file5/index.tsx", - // success on index.d.ts - ]); - check("file6", file6, [ - // first try * - // load from file - "/root/file6.ts", - "/root/file6.tsx", - "/root/file6.d.ts", - - // load from folder - "/root/file6/package.json", - "/root/file6/index.ts", - "/root/file6/index.tsx", - "/root/file6/index.d.ts", - - // then try 'generated/*' - // load from file - "/root/generated/file6.ts", - "/root/generated/file6.tsx", - "/root/generated/file6.d.ts", - - // load from folder - "/root/generated/file6/package.json", - "/root/generated/file6/index.ts", - "/root/generated/file6/index.tsx", - "/root/generated/file6/index.d.ts", - - // fallback to standard node behavior - "/root/folder1/node_modules/file6/package.json", - - // load from file - "/root/folder1/node_modules/file6.ts", - "/root/folder1/node_modules/file6.tsx", - "/root/folder1/node_modules/file6.d.ts", - - // load from folder - "/root/folder1/node_modules/file6/index.ts", - "/root/folder1/node_modules/file6/index.tsx", - "/root/folder1/node_modules/file6/index.d.ts", - - "/root/folder1/node_modules/@types/file6/package.json", - "/root/folder1/node_modules/@types/file6.d.ts", - "/root/folder1/node_modules/@types/file6/index.d.ts", - - "/root/node_modules/file6/package.json", - // success on /root/node_modules/file6.ts - ], /*isExternalLibraryImport*/ true); - - function check(name: string, expected: File, expectedFailedLookups: string[], isExternalLibraryImport = false) { - const result = resolveModuleName(name, main.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name, isExternalLibraryImport), expectedFailedLookups); - } + function check(name: string, caller: File, expected: File) { + const result = ts.resolveModuleName(name, caller.name, options, host); + checkResolvedModule(result.resolvedModule, createResolvedModule(expected.name)); } - }); - - it ("classic + baseUrl + path mappings", () => { - // classic mode does not use directoryExists - test(/*hasDirectoryExists*/ false); - - function test(hasDirectoryExists: boolean) { - const main: File = { name: "/root/folder1/main.ts" }; - - const file1: File = { name: "/root/folder1/file1.ts" }; - const file2: File = { name: "/root/generated/folder1/file2.ts" }; - const file3: File = { name: "/folder1/file3.ts" }; // fallback to classic - const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3); - - const options: CompilerOptions = { - moduleResolution: ModuleResolutionKind.Classic, - baseUrl: "/root", - jsx: JsxEmit.React, - paths: { - "*": [ - "*", - "generated/*" - ], - "somefolder/*": [ - "someanotherfolder/*" - ], - "/rooted/*": [ - "generated/*" - ] - } - }; - check("folder1/file1", file1, []); - check("folder1/file2", file2, [ - // first try '*' - "/root/folder1/file2.ts", - "/root/folder1/file2.tsx", - "/root/folder1/file2.d.ts", - // success when using 'generated/*' - ]); - check("/rooted/folder1/file2", file2, []); - check("folder1/file3", file3, [ - // first try '*' - "/root/folder1/file3.ts", - "/root/folder1/file3.tsx", - "/root/folder1/file3.d.ts", - // then try 'generated/*' - "/root/generated/folder1/file3.ts", - "/root/generated/folder1/file3.tsx", - "/root/generated/folder1/file3.d.ts", - // fallback to classic - "/root/folder1/folder1/file3.ts", - "/root/folder1/folder1/file3.tsx", - "/root/folder1/folder1/file3.d.ts", - "/root/folder1/file3.ts", - "/root/folder1/file3.tsx", - "/root/folder1/file3.d.ts", - ]); - - function check(name: string, expected: File, expectedFailedLookups: string[]) { - const result = resolveModuleName(name, main.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); - } - } - }); + } + }); - it ("node + rootDirs", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const file1: File = { name: "/root/folder1/file1.ts" }; - const file1_1: File = { name: "/root/folder1/file1_1/index.d.ts" }; // eslint-disable-line @typescript-eslint/naming-convention - const file2: File = { name: "/root/generated/folder1/file2.ts" }; - const file3: File = { name: "/root/generated/folder2/file3.ts" }; - const host = createModuleResolutionHost(hasDirectoryExists, file1, file1_1, file2, file3); - const options: CompilerOptions = { - moduleResolution: ModuleResolutionKind.NodeJs, - rootDirs: [ - "/root", - "/root/generated/" + it("node + baseUrl + path mappings", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const main: File = { name: "/root/folder1/main.ts" }; + + const file1: File = { name: "/root/folder1/file1.ts" }; + const file2: File = { name: "/root/generated/folder1/file2.ts" }; // load remapped file as module + const file3: File = { name: "/root/generated/folder2/file3/index.d.ts" }; // load folder a module + const file4Typings: File = { name: "/root/generated/folder2/file4/package.json", content: JSON.stringify({ typings: "dist/types.d.ts" }) }; + const file4: File = { name: "/root/generated/folder2/file4/dist/types.d.ts" }; // load file pointed by typings + const file5: File = { name: "/root/someanotherfolder/file5/index.d.ts" }; // load remapped module from folder + const file6: File = { name: "/root/node_modules/file6.ts" }; // fallback to node + const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3, file4, file4Typings, file5, file6); + + const options: ts.CompilerOptions = { + moduleResolution: ts.ModuleResolutionKind.NodeJs, + baseUrl: "/root", + jsx: ts.JsxEmit.React, + paths: { + "*": [ + "*", + "generated/*" + ], + "somefolder/*": [ + "someanotherfolder/*" + ], + "/rooted/*": [ + "generated/*" ] - }; - check("./file2", file1, file2, [ - // first try current location - // load from file - "/root/folder1/file2.ts", - "/root/folder1/file2.tsx", - "/root/folder1/file2.d.ts", - // load from folder - "/root/folder1/file2/package.json", - "/root/folder1/file2/index.ts", - "/root/folder1/file2/index.tsx", - "/root/folder1/file2/index.d.ts", - // success after using alternative rootDir entry - ]); - check("../folder1/file1", file3, file1, [ - // first try current location - // load from file - "/root/generated/folder1/file1.ts", - "/root/generated/folder1/file1.tsx", - "/root/generated/folder1/file1.d.ts", - // load from module - "/root/generated/folder1/file1/package.json", - "/root/generated/folder1/file1/index.ts", - "/root/generated/folder1/file1/index.tsx", - "/root/generated/folder1/file1/index.d.ts", - // success after using alternative rootDir entry - ]); - check("../folder1/file1_1", file3, file1_1, [ - // first try current location - // load from file - "/root/generated/folder1/file1_1.ts", - "/root/generated/folder1/file1_1.tsx", - "/root/generated/folder1/file1_1.d.ts", - // load from folder - "/root/generated/folder1/file1_1/package.json", - "/root/generated/folder1/file1_1/index.ts", - "/root/generated/folder1/file1_1/index.tsx", - "/root/generated/folder1/file1_1/index.d.ts", - // try alternative rootDir entry - // load from file - "/root/folder1/file1_1.ts", - "/root/folder1/file1_1.tsx", - "/root/folder1/file1_1.d.ts", - // load from directory - "/root/folder1/file1_1/package.json", - "/root/folder1/file1_1/index.ts", - "/root/folder1/file1_1/index.tsx", - // success on loading '/root/folder1/file1_1/index.d.ts' - ]); - - function check(name: string, container: File, expected: File, expectedFailedLookups: string[]) { - const result = resolveModuleName(name, container.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); } + }; + check("folder1/file1", file1, []); + check("folder1/file2", file2, [ + // first try the '*' + "/root/folder1/file2.ts", + "/root/folder1/file2.tsx", + "/root/folder1/file2.d.ts", + "/root/folder1/file2/package.json", + + "/root/folder1/file2/index.ts", + "/root/folder1/file2/index.tsx", + "/root/folder1/file2/index.d.ts", + // then first attempt on 'generated/*' was successful + ]); + check("/rooted/folder1/file2", file2, []); + check("folder2/file3", file3, [ + // first try '*' + "/root/folder2/file3.ts", + "/root/folder2/file3.tsx", + "/root/folder2/file3.d.ts", + "/root/folder2/file3/package.json", + + "/root/folder2/file3/index.ts", + "/root/folder2/file3/index.tsx", + "/root/folder2/file3/index.d.ts", + + // then use remapped location + "/root/generated/folder2/file3.ts", + "/root/generated/folder2/file3.tsx", + "/root/generated/folder2/file3.d.ts", + "/root/generated/folder2/file3/package.json", + + "/root/generated/folder2/file3/index.ts", + "/root/generated/folder2/file3/index.tsx", + // success on index.d.ts + ]); + check("folder2/file4", file4, [ + // first try '*' + "/root/folder2/file4.ts", + "/root/folder2/file4.tsx", + "/root/folder2/file4.d.ts", + "/root/folder2/file4/package.json", + + "/root/folder2/file4/index.ts", + "/root/folder2/file4/index.tsx", + "/root/folder2/file4/index.d.ts", + + // try to load from file from remapped location + "/root/generated/folder2/file4.ts", + "/root/generated/folder2/file4.tsx", + "/root/generated/folder2/file4.d.ts", + // success on loading as from folder + ]); + check("somefolder/file5", file5, [ + // load from remapped location + // first load from fle + "/root/someanotherfolder/file5.ts", + "/root/someanotherfolder/file5.tsx", + "/root/someanotherfolder/file5.d.ts", + + // load from folder + "/root/someanotherfolder/file5/package.json", + "/root/someanotherfolder/file5/index.ts", + "/root/someanotherfolder/file5/index.tsx", + // success on index.d.ts + ]); + check("file6", file6, [ + // first try * + // load from file + "/root/file6.ts", + "/root/file6.tsx", + "/root/file6.d.ts", + + // load from folder + "/root/file6/package.json", + "/root/file6/index.ts", + "/root/file6/index.tsx", + "/root/file6/index.d.ts", + + // then try 'generated/*' + // load from file + "/root/generated/file6.ts", + "/root/generated/file6.tsx", + "/root/generated/file6.d.ts", + + // load from folder + "/root/generated/file6/package.json", + "/root/generated/file6/index.ts", + "/root/generated/file6/index.tsx", + "/root/generated/file6/index.d.ts", + + // fallback to standard node behavior + "/root/folder1/node_modules/file6/package.json", + + // load from file + "/root/folder1/node_modules/file6.ts", + "/root/folder1/node_modules/file6.tsx", + "/root/folder1/node_modules/file6.d.ts", + + // load from folder + "/root/folder1/node_modules/file6/index.ts", + "/root/folder1/node_modules/file6/index.tsx", + "/root/folder1/node_modules/file6/index.d.ts", + + "/root/folder1/node_modules/@types/file6/package.json", + "/root/folder1/node_modules/@types/file6.d.ts", + "/root/folder1/node_modules/@types/file6/index.d.ts", + + "/root/node_modules/file6/package.json", + // success on /root/node_modules/file6.ts + ], /*isExternalLibraryImport*/ true); + + function check(name: string, expected: File, expectedFailedLookups: string[], isExternalLibraryImport = false) { + const result = ts.resolveModuleName(name, main.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name, isExternalLibraryImport), expectedFailedLookups); } - }); + } + }); - it ("classic + rootDirs", () => { - test(/*hasDirectoryExists*/ false); - - function test(hasDirectoryExists: boolean) { - const file1: File = { name: "/root/folder1/file1.ts" }; - const file2: File = { name: "/root/generated/folder1/file2.ts" }; - const file3: File = { name: "/root/generated/folder2/file3.ts" }; - const file4: File = { name: "/folder1/file1_1.ts" }; - const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3, file4); - const options: CompilerOptions = { - moduleResolution: ModuleResolutionKind.Classic, - jsx: JsxEmit.React, - rootDirs: [ - "/root", - "/root/generated/" + it ("classic + baseUrl + path mappings", () => { + // classic mode does not use directoryExists + test(/*hasDirectoryExists*/ false); + + function test(hasDirectoryExists: boolean) { + const main: File = { name: "/root/folder1/main.ts" }; + + const file1: File = { name: "/root/folder1/file1.ts" }; + const file2: File = { name: "/root/generated/folder1/file2.ts" }; + const file3: File = { name: "/folder1/file3.ts" }; // fallback to classic + const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3); + + const options: ts.CompilerOptions = { + moduleResolution: ts.ModuleResolutionKind.Classic, + baseUrl: "/root", + jsx: ts.JsxEmit.React, + paths: { + "*": [ + "*", + "generated/*" + ], + "somefolder/*": [ + "someanotherfolder/*" + ], + "/rooted/*": [ + "generated/*" ] - }; - check("./file2", file1, file2, [ - // first load from current location - "/root/folder1/file2.ts", - "/root/folder1/file2.tsx", - "/root/folder1/file2.d.ts", - // then try alternative rootDir entry - ]); - check("../folder1/file1", file3, file1, [ - // first load from current location - "/root/generated/folder1/file1.ts", - "/root/generated/folder1/file1.tsx", - "/root/generated/folder1/file1.d.ts", - // then try alternative rootDir entry - ]); - check("folder1/file1_1", file3, file4, [ - // current location - "/root/generated/folder2/folder1/file1_1.ts", - "/root/generated/folder2/folder1/file1_1.tsx", - "/root/generated/folder2/folder1/file1_1.d.ts", - // other entry in rootDirs - "/root/generated/folder1/file1_1.ts", - "/root/generated/folder1/file1_1.tsx", - "/root/generated/folder1/file1_1.d.ts", - // fallback - "/root/folder1/file1_1.ts", - "/root/folder1/file1_1.tsx", - "/root/folder1/file1_1.d.ts", - // found one - ]); - - function check(name: string, container: File, expected: File, expectedFailedLookups: string[]) { - const result = resolveModuleName(name, container.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); } + }; + check("folder1/file1", file1, []); + check("folder1/file2", file2, [ + // first try '*' + "/root/folder1/file2.ts", + "/root/folder1/file2.tsx", + "/root/folder1/file2.d.ts", + // success when using 'generated/*' + ]); + check("/rooted/folder1/file2", file2, []); + check("folder1/file3", file3, [ + // first try '*' + "/root/folder1/file3.ts", + "/root/folder1/file3.tsx", + "/root/folder1/file3.d.ts", + // then try 'generated/*' + "/root/generated/folder1/file3.ts", + "/root/generated/folder1/file3.tsx", + "/root/generated/folder1/file3.d.ts", + // fallback to classic + "/root/folder1/folder1/file3.ts", + "/root/folder1/folder1/file3.tsx", + "/root/folder1/folder1/file3.d.ts", + "/root/folder1/file3.ts", + "/root/folder1/file3.tsx", + "/root/folder1/file3.d.ts", + ]); + + function check(name: string, expected: File, expectedFailedLookups: string[]) { + const result = ts.resolveModuleName(name, main.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); } - }); + } + }); - it ("nested node module", () => { - test(/*hasDirectoryExists*/ false); - test(/*hasDirectoryExists*/ true); - - function test(hasDirectoryExists: boolean) { - const app: File = { name: "/root/src/app.ts" }; - const libsPackage: File = { name: "/root/src/libs/guid/package.json", content: JSON.stringify({ typings: "dist/guid.d.ts" }) }; - const libsTypings: File = { name: "/root/src/libs/guid/dist/guid.d.ts" }; - const host = createModuleResolutionHost(hasDirectoryExists, app, libsPackage, libsTypings); - const options: CompilerOptions = { - moduleResolution: ModuleResolutionKind.NodeJs, - baseUrl: "/root", - paths: { - "libs/guid": [ "src/libs/guid" ] - } - }; - const result = resolveModuleName("libs/guid", app.name, options, host); - checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(libsTypings.name), [ - // first try to load module as file - "/root/src/libs/guid.ts", - "/root/src/libs/guid.tsx", - "/root/src/libs/guid.d.ts", - ]); + it ("node + rootDirs", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const file1: File = { name: "/root/folder1/file1.ts" }; + const file1_1: File = { name: "/root/folder1/file1_1/index.d.ts" }; // eslint-disable-line @typescript-eslint/naming-convention + const file2: File = { name: "/root/generated/folder1/file2.ts" }; + const file3: File = { name: "/root/generated/folder2/file3.ts" }; + const host = createModuleResolutionHost(hasDirectoryExists, file1, file1_1, file2, file3); + const options: ts.CompilerOptions = { + moduleResolution: ts.ModuleResolutionKind.NodeJs, + rootDirs: [ + "/root", + "/root/generated/" + ] + }; + check("./file2", file1, file2, [ + // first try current location + // load from file + "/root/folder1/file2.ts", + "/root/folder1/file2.tsx", + "/root/folder1/file2.d.ts", + // load from folder + "/root/folder1/file2/package.json", + "/root/folder1/file2/index.ts", + "/root/folder1/file2/index.tsx", + "/root/folder1/file2/index.d.ts", + // success after using alternative rootDir entry + ]); + check("../folder1/file1", file3, file1, [ + // first try current location + // load from file + "/root/generated/folder1/file1.ts", + "/root/generated/folder1/file1.tsx", + "/root/generated/folder1/file1.d.ts", + // load from module + "/root/generated/folder1/file1/package.json", + "/root/generated/folder1/file1/index.ts", + "/root/generated/folder1/file1/index.tsx", + "/root/generated/folder1/file1/index.d.ts", + // success after using alternative rootDir entry + ]); + check("../folder1/file1_1", file3, file1_1, [ + // first try current location + // load from file + "/root/generated/folder1/file1_1.ts", + "/root/generated/folder1/file1_1.tsx", + "/root/generated/folder1/file1_1.d.ts", + // load from folder + "/root/generated/folder1/file1_1/package.json", + "/root/generated/folder1/file1_1/index.ts", + "/root/generated/folder1/file1_1/index.tsx", + "/root/generated/folder1/file1_1/index.d.ts", + // try alternative rootDir entry + // load from file + "/root/folder1/file1_1.ts", + "/root/folder1/file1_1.tsx", + "/root/folder1/file1_1.d.ts", + // load from directory + "/root/folder1/file1_1/package.json", + "/root/folder1/file1_1/index.ts", + "/root/folder1/file1_1/index.tsx", + // success on loading '/root/folder1/file1_1/index.d.ts' + ]); + + function check(name: string, container: File, expected: File, expectedFailedLookups: string[]) { + const result = ts.resolveModuleName(name, container.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); } - }); + } }); - describe("unittests:: moduleResolution:: ModuleResolutionHost.directoryExists", () => { - it("No 'fileExists' calls if containing directory is missing", () => { - const host: ModuleResolutionHost = { - readFile: notImplemented, - fileExists: notImplemented, - directoryExists: _ => false + it ("classic + rootDirs", () => { + test(/*hasDirectoryExists*/ false); + + function test(hasDirectoryExists: boolean) { + const file1: File = { name: "/root/folder1/file1.ts" }; + const file2: File = { name: "/root/generated/folder1/file2.ts" }; + const file3: File = { name: "/root/generated/folder2/file3.ts" }; + const file4: File = { name: "/folder1/file1_1.ts" }; + const host = createModuleResolutionHost(hasDirectoryExists, file1, file2, file3, file4); + const options: ts.CompilerOptions = { + moduleResolution: ts.ModuleResolutionKind.Classic, + jsx: ts.JsxEmit.React, + rootDirs: [ + "/root", + "/root/generated/" + ] }; - - const result = resolveModuleName("someName", "/a/b/c/d", { moduleResolution: ModuleResolutionKind.NodeJs }, host); - assert(!result.resolvedModule); - }); + check("./file2", file1, file2, [ + // first load from current location + "/root/folder1/file2.ts", + "/root/folder1/file2.tsx", + "/root/folder1/file2.d.ts", + // then try alternative rootDir entry + ]); + check("../folder1/file1", file3, file1, [ + // first load from current location + "/root/generated/folder1/file1.ts", + "/root/generated/folder1/file1.tsx", + "/root/generated/folder1/file1.d.ts", + // then try alternative rootDir entry + ]); + check("folder1/file1_1", file3, file4, [ + // current location + "/root/generated/folder2/folder1/file1_1.ts", + "/root/generated/folder2/folder1/file1_1.tsx", + "/root/generated/folder2/folder1/file1_1.d.ts", + // other entry in rootDirs + "/root/generated/folder1/file1_1.ts", + "/root/generated/folder1/file1_1.tsx", + "/root/generated/folder1/file1_1.d.ts", + // fallback + "/root/folder1/file1_1.ts", + "/root/folder1/file1_1.tsx", + "/root/folder1/file1_1.d.ts", + // found one + ]); + + function check(name: string, container: File, expected: File, expectedFailedLookups: string[]) { + const result = ts.resolveModuleName(name, container.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(expected.name), expectedFailedLookups); + } + } }); - describe("unittests:: moduleResolution:: Type reference directive resolution: ", () => { - function testWorker(hasDirectoryExists: boolean, typesRoot: string | undefined, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) { - const host = createModuleResolutionHost(hasDirectoryExists, ...[initialFile, targetFile].concat(...otherFiles)); - const result = resolveTypeReferenceDirective(typeDirective, initialFile.name, typesRoot ? { typeRoots: [typesRoot] } : {}, host); - assert(result.resolvedTypeReferenceDirective!.resolvedFileName !== undefined, "expected type directive to be resolved"); - assert.equal(result.resolvedTypeReferenceDirective!.resolvedFileName, targetFile.name, "unexpected result of type reference resolution"); - assert.equal(result.resolvedTypeReferenceDirective!.primary, primary, "unexpected 'primary' value"); + it ("nested node module", () => { + test(/*hasDirectoryExists*/ false); + test(/*hasDirectoryExists*/ true); + + function test(hasDirectoryExists: boolean) { + const app: File = { name: "/root/src/app.ts" }; + const libsPackage: File = { name: "/root/src/libs/guid/package.json", content: JSON.stringify({ typings: "dist/guid.d.ts" }) }; + const libsTypings: File = { name: "/root/src/libs/guid/dist/guid.d.ts" }; + const host = createModuleResolutionHost(hasDirectoryExists, app, libsPackage, libsTypings); + const options: ts.CompilerOptions = { + moduleResolution: ts.ModuleResolutionKind.NodeJs, + baseUrl: "/root", + paths: { + "libs/guid": [ "src/libs/guid" ] + } + }; + const result = ts.resolveModuleName("libs/guid", app.name, options, host); + checkResolvedModuleWithFailedLookupLocations(result, createResolvedModule(libsTypings.name), [ + // first try to load module as file + "/root/src/libs/guid.ts", + "/root/src/libs/guid.tsx", + "/root/src/libs/guid.d.ts", + ]); } + }); +}); + +describe("unittests:: moduleResolution:: ModuleResolutionHost.directoryExists", () => { + it("No 'fileExists' calls if containing directory is missing", () => { + const host: ts.ModuleResolutionHost = { + readFile: ts.notImplemented, + fileExists: ts.notImplemented, + directoryExists: _ => false + }; + + const result = ts.resolveModuleName("someName", "/a/b/c/d", { moduleResolution: ts.ModuleResolutionKind.NodeJs }, host); + assert(!result.resolvedModule); + }); +}); + +describe("unittests:: moduleResolution:: Type reference directive resolution: ", () => { + function testWorker(hasDirectoryExists: boolean, typesRoot: string | undefined, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) { + const host = createModuleResolutionHost(hasDirectoryExists, ...[initialFile, targetFile].concat(...otherFiles)); + const result = ts.resolveTypeReferenceDirective(typeDirective, initialFile.name, typesRoot ? { typeRoots: [typesRoot] } : {}, host); + assert(result.resolvedTypeReferenceDirective!.resolvedFileName !== undefined, "expected type directive to be resolved"); + assert.equal(result.resolvedTypeReferenceDirective!.resolvedFileName, targetFile.name, "unexpected result of type reference resolution"); + assert.equal(result.resolvedTypeReferenceDirective!.primary, primary, "unexpected 'primary' value"); + } - function test(typesRoot: string, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) { - testWorker(/*hasDirectoryExists*/ false, typesRoot, typeDirective, primary, initialFile, targetFile, ...otherFiles); - } + function test(typesRoot: string, typeDirective: string, primary: boolean, initialFile: File, targetFile: File, ...otherFiles: File[]) { + testWorker(/*hasDirectoryExists*/ false, typesRoot, typeDirective, primary, initialFile, targetFile, ...otherFiles); + } - it("Can be resolved from primary location", () => { - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/types/lib/index.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/types/lib/typings/lib.d.ts" }; - const packageFile = { name: "/root/src/types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2, packageFile); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/node_modules/lib/index.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/node_modules/lib/typings/lib.d.ts" }; - const packageFile = { name: "/root/src/node_modules/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/node_modules/@types/lib/index.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/src/node_modules/@types/lib/typings/lib.d.ts" }; - const packageFile = { name: "/root/src/node_modules/@types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); - } - }); - it("Can be resolved from secondary location", () => { - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/node_modules/lib.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/node_modules/lib/index.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/node_modules/lib/typings/lib.d.ts" }; - const packageFile = { name: "/root/node_modules/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/node_modules/@types/lib/index.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); - } - { - const f1 = { name: "/root/src/app.ts" }; - const f2 = { name: "/root/node_modules/@types/lib/typings/lib.d.ts" }; - const packageFile = { name: "/root/node_modules/@types/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); - } - }); - it("Primary resolution overrides secondary resolutions", () => { - { - const f1 = { name: "/root/src/a/b/c/app.ts" }; - const f2 = { name: "/root/src/types/lib/index.d.ts" }; - const f3 = { name: "/root/src/a/b/node_modules/lib.d.ts" }; - test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2, f3); - } - }); - it("Reused program keeps errors", () => { - const f1 = { name: "/root/src/a/b/c/d/e/app.ts", content: `/// ` }; - const f2 = { name: "/root/src/a/b/c/d/node_modules/lib/index.d.ts", content: `declare var x: number;` }; - const f3 = { name: "/root/src/a/b/c/d/f/g/app.ts", content: `/// ` }; - const f4 = { name: "/root/src/a/b/c/d/f/node_modules/lib/index.d.ts", content: `declare var x: number;` }; - const files = [f1, f2, f3, f4]; - - const names = map(files, f => f.name); - const sourceFiles = arrayToMap(map(files, f => createSourceFile(f.name, f.content, ScriptTarget.ES2015)), f => f.fileName); - const compilerHost: CompilerHost = { - fileExists: fileName => sourceFiles.has(fileName), - getSourceFile: fileName => sourceFiles.get(fileName), - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => "/", - getDirectories: () => [], - getCanonicalFileName: f => f.toLowerCase(), - getNewLine: () => "\r\n", - useCaseSensitiveFileNames: () => false, - readFile: fileName => { - const file = sourceFiles.get(fileName); - return file && file.text; - }, - }; - const program1 = createProgram(names, {}, compilerHost); - const diagnostics1 = program1.getOptionsDiagnostics(); - assert.equal(diagnostics1.length, 1, "expected one diagnostic"); - - const program2 = createProgram(names, {}, compilerHost, program1); - assert.isTrue(program2.structureIsReused === StructureIsReused.Completely); - const diagnostics2 = program2.getOptionsDiagnostics(); - assert.equal(diagnostics2.length, 1, "expected one diagnostic"); - assert.deepEqual(diagnostics1[0].messageText, diagnostics2[0].messageText, "expected one diagnostic"); - }); + it("Can be resolved from primary location", () => { + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/types/lib/index.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/types/lib/typings/lib.d.ts" }; + const packageFile = { name: "/root/src/types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2, packageFile); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/node_modules/lib/index.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/node_modules/lib/typings/lib.d.ts" }; + const packageFile = { name: "/root/src/node_modules/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/node_modules/@types/lib/index.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/src/node_modules/@types/lib/typings/lib.d.ts" }; + const packageFile = { name: "/root/src/node_modules/@types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); + } + }); + it("Can be resolved from secondary location", () => { + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/node_modules/lib.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/node_modules/lib/index.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/node_modules/lib/typings/lib.d.ts" }; + const packageFile = { name: "/root/node_modules/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/node_modules/@types/lib/index.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2); + } + { + const f1 = { name: "/root/src/app.ts" }; + const f2 = { name: "/root/node_modules/@types/lib/typings/lib.d.ts" }; + const packageFile = { name: "/root/node_modules/@types/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ false, f1, f2, packageFile); + } + }); + it("Primary resolution overrides secondary resolutions", () => { + { + const f1 = { name: "/root/src/a/b/c/app.ts" }; + const f2 = { name: "/root/src/types/lib/index.d.ts" }; + const f3 = { name: "/root/src/a/b/node_modules/lib.d.ts" }; + test(/*typesRoot*/"/root/src/types", /* typeDirective */"lib", /*primary*/ true, f1, f2, f3); + } + }); + it("Reused program keeps errors", () => { + const f1 = { name: "/root/src/a/b/c/d/e/app.ts", content: `/// ` }; + const f2 = { name: "/root/src/a/b/c/d/node_modules/lib/index.d.ts", content: `declare var x: number;` }; + const f3 = { name: "/root/src/a/b/c/d/f/g/app.ts", content: `/// ` }; + const f4 = { name: "/root/src/a/b/c/d/f/node_modules/lib/index.d.ts", content: `declare var x: number;` }; + const files = [f1, f2, f3, f4]; + + const names = ts.map(files, f => f.name); + const sourceFiles = ts.arrayToMap(ts.map(files, f => ts.createSourceFile(f.name, f.content, ts.ScriptTarget.ES2015)), f => f.fileName); + const compilerHost: ts.CompilerHost = { + fileExists: fileName => sourceFiles.has(fileName), + getSourceFile: fileName => sourceFiles.get(fileName), + getDefaultLibFileName: () => "lib.d.ts", + writeFile: ts.notImplemented, + getCurrentDirectory: () => "/", + getDirectories: () => [], + getCanonicalFileName: f => f.toLowerCase(), + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => false, + readFile: fileName => { + const file = sourceFiles.get(fileName); + return file && file.text; + }, + }; + const program1 = ts.createProgram(names, {}, compilerHost); + const diagnostics1 = program1.getOptionsDiagnostics(); + assert.equal(diagnostics1.length, 1, "expected one diagnostic"); + + const program2 = ts.createProgram(names, {}, compilerHost, program1); + assert.isTrue(program2.structureIsReused === ts.StructureIsReused.Completely); + const diagnostics2 = program2.getOptionsDiagnostics(); + assert.equal(diagnostics2.length, 1, "expected one diagnostic"); + assert.deepEqual(diagnostics1[0].messageText, diagnostics2[0].messageText, "expected one diagnostic"); + }); - it("Modules in the same .d.ts file are preferred to external files", () => { - const f = { - name: "/a/b/c/c/app.d.ts", - content: ` + it("Modules in the same .d.ts file are preferred to external files", () => { + const f = { + name: "/a/b/c/c/app.d.ts", + content: ` declare module "fs" { export interface Stat { id: number } } @@ -1514,28 +1488,28 @@ import b = require("./moduleB"); import { Stat } from "fs"; export function foo(): Stat; }` - }; - const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015); - const compilerHost: CompilerHost = { - fileExists: fileName => fileName === file.fileName, - getSourceFile: fileName => fileName === file.fileName ? file : undefined, - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => "/", - getDirectories: () => [], - getCanonicalFileName: f => f.toLowerCase(), - getNewLine: () => "\r\n", - useCaseSensitiveFileNames: () => false, - readFile: fileName => fileName === file.fileName ? file.text : undefined, - resolveModuleNames: notImplemented, - }; - createProgram([f.name], {}, compilerHost); - }); + }; + const file = ts.createSourceFile(f.name, f.content, ts.ScriptTarget.ES2015); + const compilerHost: ts.CompilerHost = { + fileExists: fileName => fileName === file.fileName, + getSourceFile: fileName => fileName === file.fileName ? file : undefined, + getDefaultLibFileName: () => "lib.d.ts", + writeFile: ts.notImplemented, + getCurrentDirectory: () => "/", + getDirectories: () => [], + getCanonicalFileName: f => f.toLowerCase(), + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => false, + readFile: fileName => fileName === file.fileName ? file.text : undefined, + resolveModuleNames: ts.notImplemented, + }; + ts.createProgram([f.name], {}, compilerHost); + }); - it("Modules in .ts file are not checked in the same file", () => { - const f = { - name: "/a/b/c/c/app.ts", - content: ` + it("Modules in .ts file are not checked in the same file", () => { + const f = { + name: "/a/b/c/c/app.ts", + content: ` declare module "fs" { export interface Stat { id: number } } @@ -1543,35 +1517,34 @@ import b = require("./moduleB"); import { Stat } from "fs"; export function foo(): Stat; }` - }; - const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015); - const compilerHost: CompilerHost = { - fileExists: fileName => fileName === file.fileName, - getSourceFile: fileName => fileName === file.fileName ? file : undefined, - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => "/", - getDirectories: () => [], - getCanonicalFileName: f => f.toLowerCase(), - getNewLine: () => "\r\n", - useCaseSensitiveFileNames: () => false, - readFile: fileName => fileName === file.fileName ? file.text : undefined, - resolveModuleNames(moduleNames: string[], _containingFile: string) { - assert.deepEqual(moduleNames, ["fs"]); - return [undefined!]; // TODO: GH#18217 - } - }; - createProgram([f.name], {}, compilerHost); + }; + const file = ts.createSourceFile(f.name, f.content, ts.ScriptTarget.ES2015); + const compilerHost: ts.CompilerHost = { + fileExists: fileName => fileName === file.fileName, + getSourceFile: fileName => fileName === file.fileName ? file : undefined, + getDefaultLibFileName: () => "lib.d.ts", + writeFile: ts.notImplemented, + getCurrentDirectory: () => "/", + getDirectories: () => [], + getCanonicalFileName: f => f.toLowerCase(), + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => false, + readFile: fileName => fileName === file.fileName ? file.text : undefined, + resolveModuleNames(moduleNames: string[], _containingFile: string) { + assert.deepEqual(moduleNames, ["fs"]); + return [undefined!]; // TODO: GH#18217 + } + }; + ts.createProgram([f.name], {}, compilerHost); + }); + describe("can be resolved when typeReferenceDirective is relative and in a sibling folder", () => { + const initialFile = { name: "/root/src/background/app.ts" }; + const targetFile = { name: "/root/src/typedefs/filesystem.d.ts" }; + it("when host doesnt have directoryExists", () => { + testWorker(/*hasDirectoryExists*/ false, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile); }); - describe("can be resolved when typeReferenceDirective is relative and in a sibling folder", () => { - const initialFile = { name: "/root/src/background/app.ts" }; - const targetFile = { name: "/root/src/typedefs/filesystem.d.ts" }; - it("when host doesnt have directoryExists", () => { - testWorker(/*hasDirectoryExists*/ false, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile); - }); - it("when host has directoryExists", () => { - testWorker(/*hasDirectoryExists*/ true, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile); - }); + it("when host has directoryExists", () => { + testWorker(/*hasDirectoryExists*/ true, /*typesRoot*/ undefined, /*typeDirective*/ "../typedefs/filesystem", /*primary*/ false, initialFile, targetFile); }); }); -} +}); diff --git a/src/testRunner/unittests/parsePseudoBigInt.ts b/src/testRunner/unittests/parsePseudoBigInt.ts index db1a841dc2b74..10f5e098362f9 100644 --- a/src/testRunner/unittests/parsePseudoBigInt.ts +++ b/src/testRunner/unittests/parsePseudoBigInt.ts @@ -1,71 +1,71 @@ -namespace ts { - describe("unittests:: BigInt literal base conversions", () => { - describe("parsePseudoBigInt", () => { - const testNumbers: number[] = []; - for (let i = 0; i < 1e3; i++) testNumbers.push(i); - for (let bits = 0; bits <= 52; bits++) { - testNumbers.push(2 ** bits, 2 ** bits - 1); - } - it("can strip base-10 strings", () => { - for (const testNumber of testNumbers) { - for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { - assert.equal( - parsePseudoBigInt("0".repeat(leadingZeros) + testNumber + "n"), - String(testNumber) - ); - } +import * as ts from "../_namespaces/ts"; + +describe("unittests:: BigInt literal base conversions", () => { + describe("parsePseudoBigInt", () => { + const testNumbers: number[] = []; + for (let i = 0; i < 1e3; i++) testNumbers.push(i); + for (let bits = 0; bits <= 52; bits++) { + testNumbers.push(2 ** bits, 2 ** bits - 1); + } + it("can strip base-10 strings", () => { + for (const testNumber of testNumbers) { + for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { + assert.equal( + ts.parsePseudoBigInt("0".repeat(leadingZeros) + testNumber + "n"), + String(testNumber) + ); } - }); - it("can parse binary literals", () => { - for (const testNumber of testNumbers) { - for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { - const binary = "0".repeat(leadingZeros) + testNumber.toString(2) + "n"; - for (const prefix of ["0b", "0B"]) { - assert.equal(parsePseudoBigInt(prefix + binary), String(testNumber)); - } + } + }); + it("can parse binary literals", () => { + for (const testNumber of testNumbers) { + for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { + const binary = "0".repeat(leadingZeros) + testNumber.toString(2) + "n"; + for (const prefix of ["0b", "0B"]) { + assert.equal(ts.parsePseudoBigInt(prefix + binary), String(testNumber)); } } - }); - it("can parse octal literals", () => { - for (const testNumber of testNumbers) { - for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { - const octal = "0".repeat(leadingZeros) + testNumber.toString(8) + "n"; - for (const prefix of ["0o", "0O"]) { - assert.equal(parsePseudoBigInt(prefix + octal), String(testNumber)); - } + } + }); + it("can parse octal literals", () => { + for (const testNumber of testNumbers) { + for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { + const octal = "0".repeat(leadingZeros) + testNumber.toString(8) + "n"; + for (const prefix of ["0o", "0O"]) { + assert.equal(ts.parsePseudoBigInt(prefix + octal), String(testNumber)); } } - }); - it("can parse hex literals", () => { - for (const testNumber of testNumbers) { - for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { - const hex = "0".repeat(leadingZeros) + testNumber.toString(16) + "n"; - for (const prefix of ["0x", "0X"]) { - for (const hexCase of [hex.toLowerCase(), hex.toUpperCase()]) { - assert.equal(parsePseudoBigInt(prefix + hexCase), String(testNumber)); - } + } + }); + it("can parse hex literals", () => { + for (const testNumber of testNumbers) { + for (let leadingZeros = 0; leadingZeros < 10; leadingZeros++) { + const hex = "0".repeat(leadingZeros) + testNumber.toString(16) + "n"; + for (const prefix of ["0x", "0X"]) { + for (const hexCase of [hex.toLowerCase(), hex.toUpperCase()]) { + assert.equal(ts.parsePseudoBigInt(prefix + hexCase), String(testNumber)); } } } - }); - it("can parse large literals", () => { - assert.equal( - parsePseudoBigInt("123456789012345678901234567890n"), - "123456789012345678901234567890" - ); - assert.equal( - parsePseudoBigInt("0b1100011101110100100001111111101101100001101110011111000001110111001001110001111110000101011010010n"), - "123456789012345678901234567890" - ); - assert.equal( - parsePseudoBigInt("0o143564417755415637016711617605322n"), - "123456789012345678901234567890" - ); - assert.equal( - parsePseudoBigInt("0x18ee90ff6c373e0ee4e3f0ad2n"), - "123456789012345678901234567890" - ); - }); + } + }); + it("can parse large literals", () => { + assert.equal( + ts.parsePseudoBigInt("123456789012345678901234567890n"), + "123456789012345678901234567890" + ); + assert.equal( + ts.parsePseudoBigInt("0b1100011101110100100001111111101101100001101110011111000001110111001001110001111110000101011010010n"), + "123456789012345678901234567890" + ); + assert.equal( + ts.parsePseudoBigInt("0o143564417755415637016711617605322n"), + "123456789012345678901234567890" + ); + assert.equal( + ts.parsePseudoBigInt("0x18ee90ff6c373e0ee4e3f0ad2n"), + "123456789012345678901234567890" + ); }); }); -} +}); diff --git a/src/testRunner/unittests/paths.ts b/src/testRunner/unittests/paths.ts index cf2bde3acbee2..fcd8de8ebee75 100644 --- a/src/testRunner/unittests/paths.ts +++ b/src/testRunner/unittests/paths.ts @@ -1,3 +1,5 @@ +import * as ts from "../_namespaces/ts"; + describe("unittests:: core paths", () => { it("normalizeSlashes", () => { assert.strictEqual(ts.normalizeSlashes("a"), "a"); diff --git a/src/testRunner/unittests/printer.ts b/src/testRunner/unittests/printer.ts index 3e15cbea574ae..103c0e69dac69 100644 --- a/src/testRunner/unittests/printer.ts +++ b/src/testRunner/unittests/printer.ts @@ -1,21 +1,25 @@ -namespace ts { - describe("unittests:: PrinterAPI", () => { - function makePrintsCorrectly(prefix: string) { - return function printsCorrectly(name: string, options: PrinterOptions, printCallback: (printer: Printer) => string) { - it(name, () => { - Harness.Baseline.runBaseline(`printerApi/${prefix}.${name}.js`, - printCallback(createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed, ...options }))); - }); - }; - } +import * as ts from "../_namespaces/ts"; +import * as Harness from "../_namespaces/Harness"; +import * as fakes from "../_namespaces/fakes"; +import * as vfs from "../_namespaces/vfs"; - describe("printFile", () => { - const printsCorrectly = makePrintsCorrectly("printsFileCorrectly"); - describe("comment handling", () => { - // Avoid eagerly creating the sourceFile so that `createSourceFile` doesn't run unless one of these tests is run. - let sourceFile: SourceFile; - before(() => { - sourceFile = createSourceFile("source.ts", ` +describe("unittests:: PrinterAPI", () => { + function makePrintsCorrectly(prefix: string) { + return function printsCorrectly(name: string, options: ts.PrinterOptions, printCallback: (printer: ts.Printer) => string) { + it(name, () => { + Harness.Baseline.runBaseline(`printerApi/${prefix}.${name}.js`, + printCallback(ts.createPrinter({ newLine: ts.NewLineKind.CarriageReturnLineFeed, ...options }))); + }); + }; + } + + describe("printFile", () => { + const printsCorrectly = makePrintsCorrectly("printsFileCorrectly"); + describe("comment handling", () => { + // Avoid eagerly creating the sourceFile so that `createSourceFile` doesn't run unless one of these tests is run. + let sourceFile: ts.SourceFile; + before(() => { + sourceFile = ts.createSourceFile("source.ts", ` interface A { // comment1 readonly prop?: T; @@ -49,266 +53,265 @@ namespace ts { // comment10 function functionWithDefaultArgValue(argument: string = "defaultValue"): void { } - `, ScriptTarget.ES2015); - }); - printsCorrectly("default", {}, printer => printer.printFile(sourceFile)); - printsCorrectly("removeComments", { removeComments: true }, printer => printer.printFile(sourceFile)); + `, ts.ScriptTarget.ES2015); }); + printsCorrectly("default", {}, printer => printer.printFile(sourceFile)); + printsCorrectly("removeComments", { removeComments: true }, printer => printer.printFile(sourceFile)); + }); - // https://github.com/microsoft/TypeScript/issues/14948 - // eslint-disable-next-line no-template-curly-in-string - printsCorrectly("templateLiteral", {}, printer => printer.printFile(createSourceFile("source.ts", "let greeting = `Hi ${name}, how are you?`;", ScriptTarget.ES2017))); + // https://github.com/microsoft/TypeScript/issues/14948 + // eslint-disable-next-line no-template-curly-in-string + printsCorrectly("templateLiteral", {}, printer => printer.printFile(ts.createSourceFile("source.ts", "let greeting = `Hi ${name}, how are you?`;", ts.ScriptTarget.ES2017))); - // https://github.com/microsoft/TypeScript/issues/18071 - printsCorrectly("regularExpressionLiteral", {}, printer => printer.printFile(createSourceFile("source.ts", "let regex = /abc/;", ScriptTarget.ES2017))); + // https://github.com/microsoft/TypeScript/issues/18071 + printsCorrectly("regularExpressionLiteral", {}, printer => printer.printFile(ts.createSourceFile("source.ts", "let regex = /abc/;", ts.ScriptTarget.ES2017))); - // https://github.com/microsoft/TypeScript/issues/22239 - printsCorrectly("importStatementRemoveComments", { removeComments: true }, printer => printer.printFile(createSourceFile("source.ts", "import {foo} from 'foo';", ScriptTarget.ESNext))); - printsCorrectly("classHeritageClauses", {}, printer => printer.printFile(createSourceFile( - "source.ts", - `class A extends B implements C implements D {}`, - ScriptTarget.ES2017 - ))); + // https://github.com/microsoft/TypeScript/issues/22239 + printsCorrectly("importStatementRemoveComments", { removeComments: true }, printer => printer.printFile(ts.createSourceFile("source.ts", "import {foo} from 'foo';", ts.ScriptTarget.ESNext))); + printsCorrectly("classHeritageClauses", {}, printer => printer.printFile(ts.createSourceFile( + "source.ts", + `class A extends B implements C implements D {}`, + ts.ScriptTarget.ES2017 + ))); - // https://github.com/microsoft/TypeScript/issues/35093 - printsCorrectly("definiteAssignmentAssertions", {}, printer => printer.printFile(createSourceFile( - "source.ts", - `class A { + // https://github.com/microsoft/TypeScript/issues/35093 + printsCorrectly("definiteAssignmentAssertions", {}, printer => printer.printFile(ts.createSourceFile( + "source.ts", + `class A { prop!: string; } let x!: string;`, - ScriptTarget.ES2017 - ))); + ts.ScriptTarget.ES2017 + ))); - // https://github.com/microsoft/TypeScript/issues/35054 - printsCorrectly("jsx attribute escaping", {}, printer => { - return printer.printFile(createSourceFile( - "source.ts", - String.raw``, - ScriptTarget.ESNext, - /*setParentNodes*/ undefined, - ScriptKind.TSX - )); - }); + // https://github.com/microsoft/TypeScript/issues/35054 + printsCorrectly("jsx attribute escaping", {}, printer => { + return printer.printFile(ts.createSourceFile( + "source.ts", + String.raw``, + ts.ScriptTarget.ESNext, + /*setParentNodes*/ undefined, + ts.ScriptKind.TSX + )); }); + }); - describe("No duplicate ref directives when emiting .d.ts->.d.ts", () => { - it("without statements", () => { - const host = new fakes.CompilerHost(new vfs.FileSystem(true, { - files: { - "/test.d.ts": `/// \n/// { - const host = new fakes.CompilerHost(new vfs.FileSystem(true, { - files: { - "/test.d.ts": `/// \n/// .d.ts", () => { + it("without statements", () => { + const host = new fakes.CompilerHost(new vfs.FileSystem(true, { + files: { + "/test.d.ts": `/// \n/// { + const host = new fakes.CompilerHost(new vfs.FileSystem(true, { + files: { + "/test.d.ts": `/// \n/// { - const printsCorrectly = makePrintsCorrectly("printsBundleCorrectly"); - let bundle: Bundle; - before(() => { - bundle = factory.createBundle([ - createSourceFile("a.ts", ` + describe("printBundle", () => { + const printsCorrectly = makePrintsCorrectly("printsBundleCorrectly"); + let bundle: ts.Bundle; + before(() => { + bundle = ts.factory.createBundle([ + ts.createSourceFile("a.ts", ` /*! [a.ts] */ // comment0 const a = 1; - `, ScriptTarget.ES2015), - createSourceFile("b.ts", ` + `, ts.ScriptTarget.ES2015), + ts.createSourceFile("b.ts", ` /*! [b.ts] */ // comment1 const b = 2; - `, ScriptTarget.ES2015) - ]); - }); - printsCorrectly("default", {}, printer => printer.printBundle(bundle)); - printsCorrectly("removeComments", { removeComments: true }, printer => printer.printBundle(bundle)); + `, ts.ScriptTarget.ES2015) + ]); }); + printsCorrectly("default", {}, printer => printer.printBundle(bundle)); + printsCorrectly("removeComments", { removeComments: true }, printer => printer.printBundle(bundle)); + }); - describe("printNode", () => { - const printsCorrectly = makePrintsCorrectly("printsNodeCorrectly"); - printsCorrectly("class", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createClassDeclaration( - /*modifiers*/ undefined, - /*name*/ factory.createIdentifier("C"), - /*typeParameters*/ undefined, - /*heritageClauses*/ undefined, - [factory.createPropertyDeclaration( - factory.createNodeArray([factory.createToken(SyntaxKind.PublicKeyword)]), - factory.createIdentifier("prop"), - /*questionToken*/ undefined, - /*type*/ undefined, - /*initializer*/ undefined - )] - ), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); + describe("printNode", () => { + const printsCorrectly = makePrintsCorrectly("printsNodeCorrectly"); + printsCorrectly("class", {}, printer => printer.printNode( + ts.EmitHint.Unspecified, + ts.factory.createClassDeclaration( + /*modifiers*/ undefined, + /*name*/ ts.factory.createIdentifier("C"), + /*typeParameters*/ undefined, + /*heritageClauses*/ undefined, + [ts.factory.createPropertyDeclaration( + ts.factory.createNodeArray([ts.factory.createToken(ts.SyntaxKind.PublicKeyword)]), + ts.factory.createIdentifier("prop"), + /*questionToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + )] + ), + ts.createSourceFile("source.ts", "", ts.ScriptTarget.ES2015) + )); - printsCorrectly("namespaceExportDeclaration", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createNamespaceExportDeclaration("B"), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); + printsCorrectly("namespaceExportDeclaration", {}, printer => printer.printNode( + ts.EmitHint.Unspecified, + ts.factory.createNamespaceExportDeclaration("B"), + ts.createSourceFile("source.ts", "", ts.ScriptTarget.ES2015) + )); - printsCorrectly("newExpressionWithPropertyAccessOnCallExpression", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createNewExpression( - factory.createPropertyAccessExpression( - factory.createCallExpression(factory.createIdentifier("f"), /*typeArguments*/ undefined, /*argumentsArray*/ undefined), - "x"), - /*typeArguments*/ undefined, - /*argumentsArray*/ undefined - ), - createSourceFile("source.ts", "", ScriptTarget.ESNext)) - ); + printsCorrectly("newExpressionWithPropertyAccessOnCallExpression", {}, printer => printer.printNode( + ts.EmitHint.Unspecified, + ts.factory.createNewExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createCallExpression(ts.factory.createIdentifier("f"), /*typeArguments*/ undefined, /*argumentsArray*/ undefined), + "x"), + /*typeArguments*/ undefined, + /*argumentsArray*/ undefined + ), + ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext)) + ); - printsCorrectly("newExpressionOnConditionalExpression", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createNewExpression( - factory.createConditionalExpression( - factory.createIdentifier("x"), factory.createToken(SyntaxKind.QuestionToken), - factory.createIdentifier("y"), factory.createToken(SyntaxKind.ColonToken), - factory.createIdentifier("z")), - /*typeArguments*/ undefined, - /*argumentsArray*/ undefined - ), - createSourceFile("source.ts", "", ScriptTarget.ESNext)) - ); + printsCorrectly("newExpressionOnConditionalExpression", {}, printer => printer.printNode( + ts.EmitHint.Unspecified, + ts.factory.createNewExpression( + ts.factory.createConditionalExpression( + ts.factory.createIdentifier("x"), ts.factory.createToken(ts.SyntaxKind.QuestionToken), + ts.factory.createIdentifier("y"), ts.factory.createToken(ts.SyntaxKind.ColonToken), + ts.factory.createIdentifier("z")), + /*typeArguments*/ undefined, + /*argumentsArray*/ undefined + ), + ts.createSourceFile("source.ts", "", ts.ScriptTarget.ESNext)) + ); - printsCorrectly("emptyGlobalAugmentation", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createModuleDeclaration( - /*modifiers*/ [factory.createToken(SyntaxKind.DeclareKeyword)], - factory.createIdentifier("global"), - factory.createModuleBlock(emptyArray), - NodeFlags.GlobalAugmentation), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); + printsCorrectly("emptyGlobalAugmentation", {}, printer => printer.printNode( + ts.EmitHint.Unspecified, + ts.factory.createModuleDeclaration( + /*modifiers*/ [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], + ts.factory.createIdentifier("global"), + ts.factory.createModuleBlock(ts.emptyArray), + ts.NodeFlags.GlobalAugmentation), + ts.createSourceFile("source.ts", "", ts.ScriptTarget.ES2015) + )); - printsCorrectly("emptyGlobalAugmentationWithNoDeclareKeyword", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createModuleDeclaration( - /*modifiers*/ undefined, - factory.createIdentifier("global"), - factory.createModuleBlock(emptyArray), - NodeFlags.GlobalAugmentation), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); + printsCorrectly("emptyGlobalAugmentationWithNoDeclareKeyword", {}, printer => printer.printNode( + ts.EmitHint.Unspecified, + ts.factory.createModuleDeclaration( + /*modifiers*/ undefined, + ts.factory.createIdentifier("global"), + ts.factory.createModuleBlock(ts.emptyArray), + ts.NodeFlags.GlobalAugmentation), + ts.createSourceFile("source.ts", "", ts.ScriptTarget.ES2015) + )); - // https://github.com/Microsoft/TypeScript/issues/15971 - printsCorrectly("classWithOptionalMethodAndProperty", {}, printer => printer.printNode( - EmitHint.Unspecified, - factory.createClassDeclaration( - /*modifiers*/ [factory.createToken(SyntaxKind.DeclareKeyword)], - /*name*/ factory.createIdentifier("X"), - /*typeParameters*/ undefined, - /*heritageClauses*/ undefined, - [ - factory.createMethodDeclaration( - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ factory.createIdentifier("method"), - /*questionToken*/ factory.createToken(SyntaxKind.QuestionToken), - /*typeParameters*/ undefined, - [], - /*type*/ factory.createKeywordTypeNode(SyntaxKind.VoidKeyword), - /*body*/ undefined - ), - factory.createPropertyDeclaration( - /*modifiers*/ undefined, - /*name*/ factory.createIdentifier("property"), - /*questionToken*/ factory.createToken(SyntaxKind.QuestionToken), - /*type*/ factory.createKeywordTypeNode(SyntaxKind.StringKeyword), - /*initializer*/ undefined - ), - ] - ), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); - - // https://github.com/Microsoft/TypeScript/issues/15651 - printsCorrectly("functionTypes", {}, printer => printer.printNode( - EmitHint.Unspecified, - setEmitFlags(factory.createTupleTypeNode([ - factory.createFunctionTypeNode( - /*typeArguments*/ undefined, - [factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - factory.createIdentifier("args") - )], - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ), - factory.createFunctionTypeNode( - [factory.createTypeParameterDeclaration(/*modifiers*/ undefined, "T")], - [factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - factory.createIdentifier("args") - )], - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + // https://github.com/Microsoft/TypeScript/issues/15971 + printsCorrectly("classWithOptionalMethodAndProperty", {}, printer => printer.printNode( + ts.EmitHint.Unspecified, + ts.factory.createClassDeclaration( + /*modifiers*/ [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], + /*name*/ ts.factory.createIdentifier("X"), + /*typeParameters*/ undefined, + /*heritageClauses*/ undefined, + [ + ts.factory.createMethodDeclaration( + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ ts.factory.createIdentifier("method"), + /*questionToken*/ ts.factory.createToken(ts.SyntaxKind.QuestionToken), + /*typeParameters*/ undefined, + [], + /*type*/ ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword), + /*body*/ undefined ), - factory.createFunctionTypeNode( - /*typeArguments*/ undefined, - [factory.createParameterDeclaration( - /*modifiers*/ undefined, - factory.createToken(SyntaxKind.DotDotDotToken), - factory.createIdentifier("args") - )], - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ), - factory.createFunctionTypeNode( - /*typeArguments*/ undefined, - [factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - factory.createIdentifier("args"), - factory.createToken(SyntaxKind.QuestionToken) - )], - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ), - factory.createFunctionTypeNode( - /*typeArguments*/ undefined, - [factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - factory.createIdentifier("args"), - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - )], - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) - ), - factory.createFunctionTypeNode( - /*typeArguments*/ undefined, - [factory.createParameterDeclaration( - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - factory.createObjectBindingPattern([]) - )], - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) + ts.factory.createPropertyDeclaration( + /*modifiers*/ undefined, + /*name*/ ts.factory.createIdentifier("property"), + /*questionToken*/ ts.factory.createToken(ts.SyntaxKind.QuestionToken), + /*type*/ ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + /*initializer*/ undefined ), - ]), EmitFlags.SingleLine), - createSourceFile("source.ts", "", ScriptTarget.ES2015) - )); - }); + ] + ), + ts.createSourceFile("source.ts", "", ts.ScriptTarget.ES2015) + )); + + // https://github.com/Microsoft/TypeScript/issues/15651 + printsCorrectly("functionTypes", {}, printer => printer.printNode( + ts.EmitHint.Unspecified, + ts.setEmitFlags(ts.factory.createTupleTypeNode([ + ts.factory.createFunctionTypeNode( + /*typeArguments*/ undefined, + [ts.factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + ts.factory.createIdentifier("args") + )], + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ), + ts.factory.createFunctionTypeNode( + [ts.factory.createTypeParameterDeclaration(/*modifiers*/ undefined, "T")], + [ts.factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + ts.factory.createIdentifier("args") + )], + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ), + ts.factory.createFunctionTypeNode( + /*typeArguments*/ undefined, + [ts.factory.createParameterDeclaration( + /*modifiers*/ undefined, + ts.factory.createToken(ts.SyntaxKind.DotDotDotToken), + ts.factory.createIdentifier("args") + )], + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ), + ts.factory.createFunctionTypeNode( + /*typeArguments*/ undefined, + [ts.factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + ts.factory.createIdentifier("args"), + ts.factory.createToken(ts.SyntaxKind.QuestionToken) + )], + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ), + ts.factory.createFunctionTypeNode( + /*typeArguments*/ undefined, + [ts.factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + ts.factory.createIdentifier("args"), + /*questionToken*/ undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + )], + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ), + ts.factory.createFunctionTypeNode( + /*typeArguments*/ undefined, + [ts.factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + ts.factory.createObjectBindingPattern([]) + )], + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ), + ]), ts.EmitFlags.SingleLine), + ts.createSourceFile("source.ts", "", ts.ScriptTarget.ES2015) + )); }); -} +}); diff --git a/src/testRunner/unittests/programApi.ts b/src/testRunner/unittests/programApi.ts index e15ec0bf434d0..ac8bd74f564ba 100644 --- a/src/testRunner/unittests/programApi.ts +++ b/src/testRunner/unittests/programApi.ts @@ -1,223 +1,227 @@ -namespace ts { - function verifyMissingFilePaths(missingPaths: readonly Path[], expected: readonly string[]) { - assert.isDefined(missingPaths); - const map = new Set(expected); - for (const missing of missingPaths) { - const value = map.has(missing); - assert.isTrue(value, `${missing} to be ${value === undefined ? "not present" : "present only once"}, in actual: ${missingPaths} expected: ${expected}`); - map.delete(missing); - } - const notFound = arrayFrom(mapDefinedIterator(map.keys(), k => map.has(k) ? k : undefined)); - assert.equal(notFound.length, 0, `Not found ${notFound} in actual: ${missingPaths} expected: ${expected}`); +import * as ts from "../_namespaces/ts"; +import * as documents from "../_namespaces/documents"; +import * as fakes from "../_namespaces/fakes"; +import * as vfs from "../_namespaces/vfs"; +import * as Harness from "../_namespaces/Harness"; + +function verifyMissingFilePaths(missingPaths: readonly ts.Path[], expected: readonly string[]) { + assert.isDefined(missingPaths); + const map = new ts.Set(expected); + for (const missing of missingPaths) { + const value = map.has(missing); + assert.isTrue(value, `${missing} to be ${value === undefined ? "not present" : "present only once"}, in actual: ${missingPaths} expected: ${expected}`); + map.delete(missing); } + const notFound = ts.arrayFrom(ts.mapDefinedIterator(map.keys(), k => map.has(k) ? k : undefined)); + assert.equal(notFound.length, 0, `Not found ${notFound} in actual: ${missingPaths} expected: ${expected}`); +} - describe("unittests:: programApi:: Program.getMissingFilePaths", () => { +describe("unittests:: programApi:: Program.getMissingFilePaths", () => { - const options: CompilerOptions = { - noLib: true, - }; + const options: ts.CompilerOptions = { + noLib: true, + }; + + const emptyFileName = "empty.ts"; + const emptyFileRelativePath = "./" + emptyFileName; + + const emptyFile = new documents.TextDocument(emptyFileName, ""); + + const referenceFileName = "reference.ts"; + const referenceFileRelativePath = "./" + referenceFileName; + + const referenceFile = new documents.TextDocument(referenceFileName, + "/// \n" + // Absolute + "/// \n" + // Relative + "/// \n" + // Unqualified + "/// \n" // No extension + ); + + const testCompilerHost = new fakes.CompilerHost( + vfs.createFromFileSystem( + Harness.IO, + /*ignoreCase*/ true, + { documents: [emptyFile, referenceFile], cwd: "d:\\pretend\\" }), + { newLine: ts.NewLineKind.LineFeed }); + + it("handles no missing root files", () => { + const program = ts.createProgram([emptyFileRelativePath], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, []); + }); + + it("handles missing root file", () => { + const program = ts.createProgram(["./nonexistent.ts"], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path + }); + + it("handles multiple missing root files", () => { + const program = ts.createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); + }); + + it("handles a mix of present and missing root files", () => { + const program = ts.createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); + }); + + it("handles repeatedly specified root files", () => { + const program = ts.createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); + }); - const emptyFileName = "empty.ts"; - const emptyFileRelativePath = "./" + emptyFileName; - - const emptyFile = new documents.TextDocument(emptyFileName, ""); - - const referenceFileName = "reference.ts"; - const referenceFileRelativePath = "./" + referenceFileName; - - const referenceFile = new documents.TextDocument(referenceFileName, - "/// \n" + // Absolute - "/// \n" + // Relative - "/// \n" + // Unqualified - "/// \n" // No extension - ); - - const testCompilerHost = new fakes.CompilerHost( - vfs.createFromFileSystem( - Harness.IO, - /*ignoreCase*/ true, - { documents: [emptyFile, referenceFile], cwd: "d:\\pretend\\" }), - { newLine: NewLineKind.LineFeed }); - - it("handles no missing root files", () => { - const program = createProgram([emptyFileRelativePath], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, []); - }); - - it("handles missing root file", () => { - const program = createProgram(["./nonexistent.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path - }); - - it("handles multiple missing root files", () => { - const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); - }); - - it("handles a mix of present and missing root files", () => { - const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); - }); - - it("handles repeatedly specified root files", () => { - const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); - }); - - it("normalizes file paths", () => { - const program0 = createProgram(["./nonexistent.ts", "./NONEXISTENT.ts"], options, testCompilerHost); - const program1 = createProgram(["./NONEXISTENT.ts", "./nonexistent.ts"], options, testCompilerHost); - const missing0 = program0.getMissingFilePaths(); - const missing1 = program1.getMissingFilePaths(); - assert.equal(missing0.length, 1); - assert.deepEqual(missing0, missing1); - }); - - it("handles missing triple slash references", () => { - const program = createProgram([referenceFileRelativePath], options, testCompilerHost); - const missing = program.getMissingFilePaths(); - verifyMissingFilePaths(missing, [ - // From absolute reference - "d:/imaginary/nonexistent1.ts", - - // From relative reference - "d:/pretend/nonexistent2.ts", - - // From unqualified reference - "d:/pretend/nonexistent3.ts", - - // From no-extension reference - "d:/pretend/nonexistent4.d.ts", - "d:/pretend/nonexistent4.ts", - "d:/pretend/nonexistent4.tsx" - ]); - }); - - it("should not have missing file paths", () => { - const testSource = ` + it("normalizes file paths", () => { + const program0 = ts.createProgram(["./nonexistent.ts", "./NONEXISTENT.ts"], options, testCompilerHost); + const program1 = ts.createProgram(["./NONEXISTENT.ts", "./nonexistent.ts"], options, testCompilerHost); + const missing0 = program0.getMissingFilePaths(); + const missing1 = program1.getMissingFilePaths(); + assert.equal(missing0.length, 1); + assert.deepEqual(missing0, missing1); + }); + + it("handles missing triple slash references", () => { + const program = ts.createProgram([referenceFileRelativePath], options, testCompilerHost); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, [ + // From absolute reference + "d:/imaginary/nonexistent1.ts", + + // From relative reference + "d:/pretend/nonexistent2.ts", + + // From unqualified reference + "d:/pretend/nonexistent3.ts", + + // From no-extension reference + "d:/pretend/nonexistent4.d.ts", + "d:/pretend/nonexistent4.ts", + "d:/pretend/nonexistent4.tsx" + ]); + }); + + it("should not have missing file paths", () => { + const testSource = ` class Foo extends HTMLElement { bar: string = 'baz'; }`; - const host: CompilerHost = { - getSourceFile: (fileName: string, languageVersion: ScriptTarget, _onError?: (message: string) => void) => { - return fileName === "test.ts" ? createSourceFile(fileName, testSource, languageVersion) : undefined; - }, - getDefaultLibFileName: () => "", - writeFile: (_fileName, _content) => { throw new Error("unsupported"); }, - getCurrentDirectory: () => sys.getCurrentDirectory(), - getCanonicalFileName: fileName => sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(), - getNewLine: () => sys.newLine, - useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, - fileExists: fileName => fileName === "test.ts", - readFile: fileName => fileName === "test.ts" ? testSource : undefined, - resolveModuleNames: (_moduleNames: string[], _containingFile: string) => { throw new Error("unsupported"); }, - getDirectories: _path => { throw new Error("unsupported"); }, - }; - - const program = createProgram(["test.ts"], { module: ModuleKind.ES2015 }, host); - assert(program.getSourceFiles().length === 1, "expected 'getSourceFiles' length to be 1"); - assert(program.getMissingFilePaths().length === 0, "expected 'getMissingFilePaths' length to be 0"); - assert((program.getFileProcessingDiagnostics()?.length || 0) === 0, "expected 'getFileProcessingDiagnostics' length to be 0"); - }); + const host: ts.CompilerHost = { + getSourceFile: (fileName: string, languageVersion: ts.ScriptTarget, _onError?: (message: string) => void) => { + return fileName === "test.ts" ? ts.createSourceFile(fileName, testSource, languageVersion) : undefined; + }, + getDefaultLibFileName: () => "", + writeFile: (_fileName, _content) => { throw new Error("unsupported"); }, + getCurrentDirectory: () => ts.sys.getCurrentDirectory(), + getCanonicalFileName: fileName => ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(), + getNewLine: () => ts.sys.newLine, + useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames, + fileExists: fileName => fileName === "test.ts", + readFile: fileName => fileName === "test.ts" ? testSource : undefined, + resolveModuleNames: (_moduleNames: string[], _containingFile: string) => { throw new Error("unsupported"); }, + getDirectories: _path => { throw new Error("unsupported"); }, + }; + + const program = ts.createProgram(["test.ts"], { module: ts.ModuleKind.ES2015 }, host); + assert(program.getSourceFiles().length === 1, "expected 'getSourceFiles' length to be 1"); + assert(program.getMissingFilePaths().length === 0, "expected 'getMissingFilePaths' length to be 0"); + assert((program.getFileProcessingDiagnostics()?.length || 0) === 0, "expected 'getFileProcessingDiagnostics' length to be 0"); + }); +}); + +describe("unittests:: Program.isSourceFileFromExternalLibrary", () => { + it("works on redirect files", () => { + // In this example '/node_modules/foo/index.d.ts' will redirect to '/node_modules/bar/node_modules/foo/index.d.ts'. + const a = new documents.TextDocument("/a.ts", 'import * as bar from "bar"; import * as foo from "foo";'); + const bar = new documents.TextDocument("/node_modules/bar/index.d.ts", 'import * as foo from "foo";'); + const fooPackageJsonText = '{ "name": "foo", "version": "1.2.3" }'; + const fooIndexText = "export const x: number;"; + const barFooPackage = new documents.TextDocument("/node_modules/bar/node_modules/foo/package.json", fooPackageJsonText); + const barFooIndex = new documents.TextDocument("/node_modules/bar/node_modules/foo/index.d.ts", fooIndexText); + const fooPackage = new documents.TextDocument("/node_modules/foo/package.json", fooPackageJsonText); + const fooIndex = new documents.TextDocument("/node_modules/foo/index.d.ts", fooIndexText); + + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [a, bar, barFooPackage, barFooIndex, fooPackage, fooIndex], cwd: "/" }); + const program = ts.createProgram(["/a.ts"], ts.emptyOptions, new fakes.CompilerHost(fs, { newLine: ts.NewLineKind.LineFeed })); + assertIsExternal(program, [a, bar, barFooIndex, fooIndex], f => f !== a); }); - describe("unittests:: Program.isSourceFileFromExternalLibrary", () => { - it("works on redirect files", () => { - // In this example '/node_modules/foo/index.d.ts' will redirect to '/node_modules/bar/node_modules/foo/index.d.ts'. - const a = new documents.TextDocument("/a.ts", 'import * as bar from "bar"; import * as foo from "foo";'); - const bar = new documents.TextDocument("/node_modules/bar/index.d.ts", 'import * as foo from "foo";'); - const fooPackageJsonText = '{ "name": "foo", "version": "1.2.3" }'; - const fooIndexText = "export const x: number;"; - const barFooPackage = new documents.TextDocument("/node_modules/bar/node_modules/foo/package.json", fooPackageJsonText); - const barFooIndex = new documents.TextDocument("/node_modules/bar/node_modules/foo/index.d.ts", fooIndexText); - const fooPackage = new documents.TextDocument("/node_modules/foo/package.json", fooPackageJsonText); - const fooIndex = new documents.TextDocument("/node_modules/foo/index.d.ts", fooIndexText); - - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [a, bar, barFooPackage, barFooIndex, fooPackage, fooIndex], cwd: "/" }); - const program = createProgram(["/a.ts"], emptyOptions, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); - assertIsExternal(program, [a, bar, barFooIndex, fooIndex], f => f !== a); - }); - - it('works on `/// `', () => { - const a = new documents.TextDocument("/a.ts", '/// '); - const fooIndex = new documents.TextDocument("/node_modules/foo/index.d.ts", "declare const foo: number;"); - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [a, fooIndex], cwd: "/" }); - const program = createProgram(["/a.ts"], emptyOptions, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); - assertIsExternal(program, [a, fooIndex], f => f !== a); - }); - - function assertIsExternal(program: Program, files: readonly documents.TextDocument[], isExternalExpected: (file: documents.TextDocument) => boolean): void { - for (const file of files) { - const actual = program.isSourceFileFromExternalLibrary(program.getSourceFile(file.file)!); - const expected = isExternalExpected(file); - assert.equal(actual, expected, `Expected ${file.file} isSourceFileFromExternalLibrary to be ${expected}, got ${actual}`); - } - } + it('works on `/// `', () => { + const a = new documents.TextDocument("/a.ts", '/// '); + const fooIndex = new documents.TextDocument("/node_modules/foo/index.d.ts", "declare const foo: number;"); + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [a, fooIndex], cwd: "/" }); + const program = ts.createProgram(["/a.ts"], ts.emptyOptions, new fakes.CompilerHost(fs, { newLine: ts.NewLineKind.LineFeed })); + assertIsExternal(program, [a, fooIndex], f => f !== a); }); - describe("unittests:: Program.getNodeCount / Program.getIdentifierCount", () => { - it("works on projects that have .json files", () => { - const main = new documents.TextDocument("/main.ts", 'export { version } from "./package.json";'); - const pkg = new documents.TextDocument("/package.json", '{"version": "1.0.0"}'); + function assertIsExternal(program: ts.Program, files: readonly documents.TextDocument[], isExternalExpected: (file: documents.TextDocument) => boolean): void { + for (const file of files) { + const actual = program.isSourceFileFromExternalLibrary(program.getSourceFile(file.file)!); + const expected = isExternalExpected(file); + assert.equal(actual, expected, `Expected ${file.file} isSourceFileFromExternalLibrary to be ${expected}, got ${actual}`); + } + } +}); + +describe("unittests:: Program.getNodeCount / Program.getIdentifierCount", () => { + it("works on projects that have .json files", () => { + const main = new documents.TextDocument("/main.ts", 'export { version } from "./package.json";'); + const pkg = new documents.TextDocument("/package.json", '{"version": "1.0.0"}'); - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, pkg], cwd: "/" }); - const program = createProgram(["/main.ts"], { resolveJsonModule: true }, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, pkg], cwd: "/" }); + const program = ts.createProgram(["/main.ts"], { resolveJsonModule: true }, new fakes.CompilerHost(fs, { newLine: ts.NewLineKind.LineFeed })); - const json = program.getSourceFile("/package.json")!; - assert.equal(json.scriptKind, ScriptKind.JSON); - assert.isNumber(json.nodeCount); - assert.isNumber(json.identifierCount); + const json = program.getSourceFile("/package.json")!; + assert.equal(json.scriptKind, ts.ScriptKind.JSON); + assert.isNumber(json.nodeCount); + assert.isNumber(json.identifierCount); - assert.isNotNaN(program.getNodeCount()); - assert.isNotNaN(program.getIdentifierCount()); - }); + assert.isNotNaN(program.getNodeCount()); + assert.isNotNaN(program.getIdentifierCount()); + }); +}); + +describe("unittests:: programApi:: Program.getTypeChecker / Program.getSemanticDiagnostics", () => { + it("does not produce errors on `as const` it would not normally produce on the command line", () => { + const main = new documents.TextDocument("/main.ts", "0 as const"); + + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main], cwd: "/" }); + const program = ts.createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: ts.NewLineKind.LineFeed })); + const typeChecker = program.getTypeChecker(); + const sourceFile = program.getSourceFile("main.ts")!; + typeChecker.getTypeAtLocation(((sourceFile.statements[0] as ts.ExpressionStatement).expression as ts.AsExpression).type); + const diag = program.getSemanticDiagnostics(); + assert.isEmpty(diag); }); + it("getSymbolAtLocation does not cause additional error to be added on module resolution failure", () => { + const main = new documents.TextDocument("/main.ts", "import \"./module\";"); + const mod = new documents.TextDocument("/module.d.ts", "declare const foo: any;"); - describe("unittests:: programApi:: Program.getTypeChecker / Program.getSemanticDiagnostics", () => { - it("does not produce errors on `as const` it would not normally produce on the command line", () => { - const main = new documents.TextDocument("/main.ts", "0 as const"); - - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main], cwd: "/" }); - const program = createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); - const typeChecker = program.getTypeChecker(); - const sourceFile = program.getSourceFile("main.ts")!; - typeChecker.getTypeAtLocation(((sourceFile.statements[0] as ExpressionStatement).expression as AsExpression).type); - const diag = program.getSemanticDiagnostics(); - assert.isEmpty(diag); - }); - it("getSymbolAtLocation does not cause additional error to be added on module resolution failure", () => { - const main = new documents.TextDocument("/main.ts", "import \"./module\";"); - const mod = new documents.TextDocument("/module.d.ts", "declare const foo: any;"); - - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" }); - const program = createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); - - const sourceFile = program.getSourceFile("main.ts")!; - const typeChecker = program.getTypeChecker(); - typeChecker.getSymbolAtLocation((sourceFile.statements[0] as ImportDeclaration).moduleSpecifier); - assert.isEmpty(program.getSemanticDiagnostics()); - }); + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" }); + const program = ts.createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: ts.NewLineKind.LineFeed })); + + const sourceFile = program.getSourceFile("main.ts")!; + const typeChecker = program.getTypeChecker(); + typeChecker.getSymbolAtLocation((sourceFile.statements[0] as ts.ImportDeclaration).moduleSpecifier); + assert.isEmpty(program.getSemanticDiagnostics()); }); +}); - describe("unittests:: programApi:: CompilerOptions relative paths", () => { - it("resolves relative paths by getCurrentDirectory", () => { - const main = new documents.TextDocument("/main.ts", "import \"module\";"); - const mod = new documents.TextDocument("/lib/module.ts", "declare const foo: any;"); +describe("unittests:: programApi:: CompilerOptions relative paths", () => { + it("resolves relative paths by getCurrentDirectory", () => { + const main = new documents.TextDocument("/main.ts", "import \"module\";"); + const mod = new documents.TextDocument("/lib/module.ts", "declare const foo: any;"); - const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" }); - const program = createProgram(["./main.ts"], { - paths: { "*": ["./lib/*"] } - }, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed })); + const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" }); + const program = ts.createProgram(["./main.ts"], { + paths: { "*": ["./lib/*"] } + }, new fakes.CompilerHost(fs, { newLine: ts.NewLineKind.LineFeed })); - assert.isEmpty(program.getConfigFileParsingDiagnostics()); - assert.isEmpty(program.getGlobalDiagnostics()); - assert.isEmpty(program.getSemanticDiagnostics()); - }); + assert.isEmpty(program.getConfigFileParsingDiagnostics()); + assert.isEmpty(program.getGlobalDiagnostics()); + assert.isEmpty(program.getSemanticDiagnostics()); }); -} +}); diff --git a/src/testRunner/unittests/publicApi.ts b/src/testRunner/unittests/publicApi.ts index 00d8994520f54..54004b9a89a6f 100644 --- a/src/testRunner/unittests/publicApi.ts +++ b/src/testRunner/unittests/publicApi.ts @@ -1,3 +1,10 @@ +import * as Harness from "../_namespaces/Harness"; +import * as vfs from "../_namespaces/vfs"; +import * as fakes from "../_namespaces/fakes"; +import * as ts from "../_namespaces/ts"; +import * as compiler from "../_namespaces/compiler"; +import * as documents from "../_namespaces/documents"; + describe("unittests:: Public APIs", () => { function verifyApi(fileName: string) { const builtFile = `built/local/${fileName}`; diff --git a/src/testRunner/unittests/reuseProgramStructure.ts b/src/testRunner/unittests/reuseProgramStructure.ts index eda2135323d9c..28fd3300f5502 100644 --- a/src/testRunner/unittests/reuseProgramStructure.ts +++ b/src/testRunner/unittests/reuseProgramStructure.ts @@ -1,688 +1,440 @@ -namespace ts { +import * as ts from "../_namespaces/ts"; - const enum ChangedPart { - references = 1 << 0, - importsAndExports = 1 << 1, - program = 1 << 2 - } - - const newLine = "\r\n"; - - interface SourceFileWithText extends SourceFile { - sourceText?: SourceText; - } - - export interface NamedSourceText { - name: string; - text: SourceText; - } - - export interface ProgramWithSourceTexts extends Program { - sourceTexts?: readonly NamedSourceText[]; - host: TestCompilerHost; - } - - interface TestCompilerHost extends CompilerHost { - getTrace(): string[]; - } - - export class SourceText implements IScriptSnapshot { - private fullText: string | undefined; - - constructor(private references: string, - private importsAndExports: string, - private program: string, - private changedPart: ChangedPart = 0, - private version = 0) { - } - - static New(references: string, importsAndExports: string, program: string): SourceText { - Debug.assert(references !== undefined); - Debug.assert(importsAndExports !== undefined); - Debug.assert(program !== undefined); - return new SourceText(references + newLine, importsAndExports + newLine, program || ""); - } - - public getVersion(): number { - return this.version; - } - - public updateReferences(newReferences: string): SourceText { - Debug.assert(newReferences !== undefined); - return new SourceText(newReferences + newLine, this.importsAndExports, this.program, this.changedPart | ChangedPart.references, this.version + 1); - } - public updateImportsAndExports(newImportsAndExports: string): SourceText { - Debug.assert(newImportsAndExports !== undefined); - return new SourceText(this.references, newImportsAndExports + newLine, this.program, this.changedPart | ChangedPart.importsAndExports, this.version + 1); - } - public updateProgram(newProgram: string): SourceText { - Debug.assert(newProgram !== undefined); - return new SourceText(this.references, this.importsAndExports, newProgram, this.changedPart | ChangedPart.program, this.version + 1); - } - - public getFullText() { - return this.fullText || (this.fullText = this.references + this.importsAndExports + this.program); - } - - public getText(start: number, end: number): string { - return this.getFullText().substring(start, end); - } - - getLength(): number { - return this.getFullText().length; - } - - getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange { - const oldText = oldSnapshot as SourceText; - let oldSpan: TextSpan; - let newLength: number; - switch (oldText.changedPart ^ this.changedPart) { - case ChangedPart.references: - oldSpan = createTextSpan(0, oldText.references.length); - newLength = this.references.length; - break; - case ChangedPart.importsAndExports: - oldSpan = createTextSpan(oldText.references.length, oldText.importsAndExports.length); - newLength = this.importsAndExports.length; - break; - case ChangedPart.program: - oldSpan = createTextSpan(oldText.references.length + oldText.importsAndExports.length, oldText.program.length); - newLength = this.program.length; - break; - default: - return Debug.fail("Unexpected change"); - } - - return createTextChangeRange(oldSpan, newLength); - } - } +import { checkResolvedModulesCache, checkResolvedTypeDirectivesCache, createTestCompilerHost, NamedSourceText, newLine, newProgram, ProgramWithSourceTexts, SourceText, TestCompilerHost, updateProgram, updateProgramText } from "./helpers"; - function createSourceFileWithText(fileName: string, sourceText: SourceText, target: ScriptTarget) { - const file = createSourceFile(fileName, sourceText.getFullText(), target) as SourceFileWithText; - file.sourceText = sourceText; - file.version = "" + sourceText.getVersion(); - return file; - } - - export function createTestCompilerHost(texts: readonly NamedSourceText[], target: ScriptTarget, oldProgram?: ProgramWithSourceTexts, useGetSourceFileByPath?: boolean) { - const files = arrayToMap(texts, t => t.name, t => { - if (oldProgram) { - let oldFile = oldProgram.getSourceFile(t.name) as SourceFileWithText; - if (oldFile && oldFile.redirectInfo) { - oldFile = oldFile.redirectInfo.unredirected; - } - if (oldFile && oldFile.sourceText!.getVersion() === t.text.getVersion()) { - return oldFile; - } - } - return createSourceFileWithText(t.name, t.text, target); - }); - const useCaseSensitiveFileNames = sys && sys.useCaseSensitiveFileNames; - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - const trace: string[] = []; - const result: TestCompilerHost = { - trace: s => trace.push(s), - getTrace: () => trace, - getSourceFile: fileName => files.get(fileName), - getDefaultLibFileName: () => "lib.d.ts", - writeFile: notImplemented, - getCurrentDirectory: () => "", - getDirectories: () => [], - getCanonicalFileName, - useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, - getNewLine: () => sys ? sys.newLine : newLine, - fileExists: fileName => files.has(fileName), - readFile: fileName => { - const file = files.get(fileName); - return file && file.text; - }, - }; - if (useGetSourceFileByPath) { - const filesByPath = mapEntries(files, (fileName, file) => [toPath(fileName, "", getCanonicalFileName), file]); - result.getSourceFileByPath = (_fileName, path) => filesByPath.get(path); - } - return result; - } - - export function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions, useGetSourceFileByPath?: boolean): ProgramWithSourceTexts { - const host = createTestCompilerHost(texts, options.target!, /*oldProgram*/ undefined, useGetSourceFileByPath); - const program = createProgram(rootNames, options, host) as ProgramWithSourceTexts; - program.sourceTexts = texts; - program.host = host; - return program; - } - - export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: readonly string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[], useGetSourceFileByPath?: boolean) { - if (!newTexts) { - newTexts = oldProgram.sourceTexts!.slice(0); - } - updater(newTexts); - const host = createTestCompilerHost(newTexts, options.target!, oldProgram, useGetSourceFileByPath); - const program = createProgram(rootNames, options, host, oldProgram) as ProgramWithSourceTexts; - program.sourceTexts = newTexts; - program.host = host; - return program; - } - - export function updateProgramText(files: readonly NamedSourceText[], fileName: string, newProgramText: string) { - const file = find(files, f => f.name === fileName)!; - file.text = file.text.updateProgram(newProgramText); - } - - function checkResolvedTypeDirective(actual: ResolvedTypeReferenceDirective, expected: ResolvedTypeReferenceDirective) { - assert.equal(actual.resolvedFileName, expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`); - assert.equal(actual.primary, expected.primary, `'primary': expected '${actual.primary}' to be equal to '${expected.primary}'`); - return true; - } - - function checkCache(caption: string, program: Program, fileName: string, expectedContent: ESMap | undefined, getCache: (f: SourceFile) => ModeAwareCache | undefined, entryChecker: (expected: T, original: T) => boolean): void { - const file = program.getSourceFile(fileName); - assert.isTrue(file !== undefined, `cannot find file ${fileName}`); - const cache = getCache(file!); - if (expectedContent === undefined) { - assert.isTrue(cache === undefined, `expected ${caption} to be undefined`); - } - else { - assert.isTrue(cache !== undefined, `expected ${caption} to be set`); - assert.isTrue(mapEqualToCache(expectedContent, cache!, entryChecker), `contents of ${caption} did not match the expected contents.`); - } - } - - /** True if the maps have the same keys and values. */ - function mapEqualToCache(left: ESMap, right: ModeAwareCache, valuesAreEqual?: (left: T, right: T) => boolean): boolean { - if (left as any === right) return true; // given the type mismatch (the tests never pass a cache), this'll never be true - if (!left || !right) return false; - const someInLeftHasNoMatch = forEachEntry(left, (leftValue, leftKey) => { - if (!right.has(leftKey, /*mode*/ undefined)) return true; - const rightValue = right.get(leftKey, /*mode*/ undefined)!; - return !(valuesAreEqual ? valuesAreEqual(leftValue, rightValue) : leftValue === rightValue); - }); - if (someInLeftHasNoMatch) return false; - let someInRightHasNoMatch = false; - right.forEach((_, rightKey) => someInRightHasNoMatch = someInRightHasNoMatch || !left.has(rightKey)); - return !someInRightHasNoMatch; - } - - function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: ESMap | undefined): void { - checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, checkResolvedModule); - } - - function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: ESMap | undefined): void { - checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective); - } - - describe("unittests:: Reuse program structure:: General", () => { - const target = ScriptTarget.Latest; - const files: NamedSourceText[] = [ - { - name: "a.ts", text: SourceText.New( - ` +describe("unittests:: Reuse program structure:: General", () => { + const target = ts.ScriptTarget.Latest; + const files: NamedSourceText[] = [ + { + name: "a.ts", text: SourceText.New( + ` /// /// /// `, "", `var x = 1`) - }, - { name: "b.ts", text: SourceText.New(`/// `, "", `var y = 2`) }, - { name: "c.ts", text: SourceText.New("", "", `var z = 1;`) }, - { name: "types/typerefs/index.d.ts", text: SourceText.New("", "", `declare let z: number;`) }, - ]; - - it("successful if change does not affect imports", () => { - const program1 = newProgram(files, ["a.ts"], { target }); - const program2 = updateProgram(program1, ["a.ts"], { target }, files => { - files[0].text = files[0].text.updateProgram("var x = 100"); - }); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); - const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); - const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts")); - assert.equal(program1Diagnostics.length, program2Diagnostics.length); + }, + { name: "b.ts", text: SourceText.New(`/// `, "", `var y = 2`) }, + { name: "c.ts", text: SourceText.New("", "", `var z = 1;`) }, + { name: "types/typerefs/index.d.ts", text: SourceText.New("", "", `declare let z: number;`) }, + ]; + + it("successful if change does not affect imports", () => { + const program1 = newProgram(files, ["a.ts"], { target }); + const program2 = updateProgram(program1, ["a.ts"], { target }, files => { + files[0].text = files[0].text.updateProgram("var x = 100"); }); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Completely); + const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); + const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts")); + assert.equal(program1Diagnostics.length, program2Diagnostics.length); + }); - it("successful if change does not affect type reference directives", () => { - const program1 = newProgram(files, ["a.ts"], { target }); - const program2 = updateProgram(program1, ["a.ts"], { target }, files => { - files[0].text = files[0].text.updateProgram("var x = 100"); - }); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); - const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); - const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts")); - assert.equal(program1Diagnostics.length, program2Diagnostics.length); + it("successful if change does not affect type reference directives", () => { + const program1 = newProgram(files, ["a.ts"], { target }); + const program2 = updateProgram(program1, ["a.ts"], { target }, files => { + files[0].text = files[0].text.updateProgram("var x = 100"); }); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Completely); + const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); + const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts")); + assert.equal(program1Diagnostics.length, program2Diagnostics.length); + }); - it("successful if change affects a single module of a package", () => { - const files = [ - { name: "/a.ts", text: SourceText.New("", "import {b} from 'b'", "var a = b;") }, - { name: "/node_modules/b/index.d.ts", text: SourceText.New("", "export * from './internal';", "") }, - { name: "/node_modules/b/internal.d.ts", text: SourceText.New("", "", "export const b = 1;") }, - { name: "/node_modules/b/package.json", text: SourceText.New("", "", JSON.stringify({ name: "b", version: "1.2.3" })) }, - ]; + it("successful if change affects a single module of a package", () => { + const files = [ + { name: "/a.ts", text: SourceText.New("", "import {b} from 'b'", "var a = b;") }, + { name: "/node_modules/b/index.d.ts", text: SourceText.New("", "export * from './internal';", "") }, + { name: "/node_modules/b/internal.d.ts", text: SourceText.New("", "", "export const b = 1;") }, + { name: "/node_modules/b/package.json", text: SourceText.New("", "", JSON.stringify({ name: "b", version: "1.2.3" })) }, + ]; - const options: CompilerOptions = { target, moduleResolution: ModuleResolutionKind.NodeJs }; - const program1 = newProgram(files, ["/a.ts"], options); - const program2 = updateProgram(program1, ["/a.ts"], options, files => { - files[2].text = files[2].text.updateProgram("export const b = 2;"); - }); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); - const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); - const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts")); - assert.equal(program1Diagnostics.length, program2Diagnostics.length); + const options: ts.CompilerOptions = { target, moduleResolution: ts.ModuleResolutionKind.NodeJs }; + const program1 = newProgram(files, ["/a.ts"], options); + const program2 = updateProgram(program1, ["/a.ts"], options, files => { + files[2].text = files[2].text.updateProgram("export const b = 2;"); }); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Completely); + const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("a.ts")); + const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("a.ts")); + assert.equal(program1Diagnostics.length, program2Diagnostics.length); + }); - it("fails if change affects tripleslash references", () => { - const program1 = newProgram(files, ["a.ts"], { target }); - const program2 = updateProgram(program1, ["a.ts"], { target }, files => { - const newReferences = `/// + it("fails if change affects tripleslash references", () => { + const program1 = newProgram(files, ["a.ts"], { target }); + const program2 = updateProgram(program1, ["a.ts"], { target }, files => { + const newReferences = `/// /// `; - files[0].text = files[0].text.updateReferences(newReferences); - }); - assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); + files[0].text = files[0].text.updateReferences(newReferences); }); + assert.equal(program2.structureIsReused, ts.StructureIsReused.SafeModules); + }); - it("fails if change affects type references", () => { - const program1 = newProgram(files, ["a.ts"], { types: ["a"] }); - const program2 = updateProgram(program1, ["a.ts"], { types: ["b"] }, noop); - assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); - }); + it("fails if change affects type references", () => { + const program1 = newProgram(files, ["a.ts"], { types: ["a"] }); + const program2 = updateProgram(program1, ["a.ts"], { types: ["b"] }, ts.noop); + assert.equal(program2.structureIsReused, ts.StructureIsReused.SafeModules); + }); - it("succeeds if change doesn't affect type references", () => { - const program1 = newProgram(files, ["a.ts"], { types: ["a"] }); - const program2 = updateProgram(program1, ["a.ts"], { types: ["a"] }, noop); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); - }); + it("succeeds if change doesn't affect type references", () => { + const program1 = newProgram(files, ["a.ts"], { types: ["a"] }); + const program2 = updateProgram(program1, ["a.ts"], { types: ["a"] }, ts.noop); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Completely); + }); - it("fails if change affects imports", () => { - const program1 = newProgram(files, ["a.ts"], { target }); - const program2 = updateProgram(program1, ["a.ts"], { target }, files => { - files[2].text = files[2].text.updateImportsAndExports("import x from 'b'"); - }); - assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); + it("fails if change affects imports", () => { + const program1 = newProgram(files, ["a.ts"], { target }); + const program2 = updateProgram(program1, ["a.ts"], { target }, files => { + files[2].text = files[2].text.updateImportsAndExports("import x from 'b'"); }); + assert.equal(program2.structureIsReused, ts.StructureIsReused.SafeModules); + }); - it("fails if change affects type directives", () => { - const program1 = newProgram(files, ["a.ts"], { target }); - const program2 = updateProgram(program1, ["a.ts"], { target }, files => { - const newReferences = ` + it("fails if change affects type directives", () => { + const program1 = newProgram(files, ["a.ts"], { target }); + const program2 = updateProgram(program1, ["a.ts"], { target }, files => { + const newReferences = ` /// /// /// `; - files[0].text = files[0].text.updateReferences(newReferences); - }); - assert.equal(program2.structureIsReused, StructureIsReused.SafeModules); + files[0].text = files[0].text.updateReferences(newReferences); }); + assert.equal(program2.structureIsReused, ts.StructureIsReused.SafeModules); + }); - it("fails if module kind changes", () => { - const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS }); - const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.AMD }, noop); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - }); + it("fails if module kind changes", () => { + const program1 = newProgram(files, ["a.ts"], { target, module: ts.ModuleKind.CommonJS }); + const program2 = updateProgram(program1, ["a.ts"], { target, module: ts.ModuleKind.AMD }, ts.noop); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Not); + }); - it("succeeds if rootdir changes", () => { - const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/b" }); - const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/c" }, noop); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); - }); + it("succeeds if rootdir changes", () => { + const program1 = newProgram(files, ["a.ts"], { target, module: ts.ModuleKind.CommonJS, rootDir: "/a/b" }); + const program2 = updateProgram(program1, ["a.ts"], { target, module: ts.ModuleKind.CommonJS, rootDir: "/a/c" }, ts.noop); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Completely); + }); - it("fails if config path changes", () => { - const program1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/b/tsconfig.json" }); - const program2 = updateProgram(program1, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/c/tsconfig.json" }, noop); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - }); + it("fails if config path changes", () => { + const program1 = newProgram(files, ["a.ts"], { target, module: ts.ModuleKind.CommonJS, configFilePath: "/a/b/tsconfig.json" }); + const program2 = updateProgram(program1, ["a.ts"], { target, module: ts.ModuleKind.CommonJS, configFilePath: "/a/c/tsconfig.json" }, ts.noop); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Not); + }); - it("succeeds if missing files remain missing", () => { - const options: CompilerOptions = { target, noLib: true }; + it("succeeds if missing files remain missing", () => { + const options: ts.CompilerOptions = { target, noLib: true }; - const program1 = newProgram(files, ["a.ts"], options); - assert.notDeepEqual(emptyArray, program1.getMissingFilePaths()); + const program1 = newProgram(files, ["a.ts"], options); + assert.notDeepEqual(ts.emptyArray, program1.getMissingFilePaths()); - const program2 = updateProgram(program1, ["a.ts"], options, noop); - assert.deepEqual(program1.getMissingFilePaths(), program2.getMissingFilePaths()); + const program2 = updateProgram(program1, ["a.ts"], options, ts.noop); + assert.deepEqual(program1.getMissingFilePaths(), program2.getMissingFilePaths()); - assert.equal(program2.structureIsReused, StructureIsReused.Completely,); - }); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Completely,); + }); - it("fails if missing file is created", () => { - const options: CompilerOptions = { target, noLib: true }; + it("fails if missing file is created", () => { + const options: ts.CompilerOptions = { target, noLib: true }; - const program1 = newProgram(files, ["a.ts"], options); - assert.notDeepEqual(emptyArray, program1.getMissingFilePaths()); + const program1 = newProgram(files, ["a.ts"], options); + assert.notDeepEqual(ts.emptyArray, program1.getMissingFilePaths()); - const newTexts: NamedSourceText[] = files.concat([{ name: "non-existing-file.ts", text: SourceText.New("", "", `var x = 1`) }]); - const program2 = updateProgram(program1, ["a.ts"], options, noop, newTexts); - assert.lengthOf(program2.getMissingFilePaths(), 0); + const newTexts: NamedSourceText[] = files.concat([{ name: "non-existing-file.ts", text: SourceText.New("", "", `var x = 1`) }]); + const program2 = updateProgram(program1, ["a.ts"], options, ts.noop, newTexts); + assert.lengthOf(program2.getMissingFilePaths(), 0); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - }); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Not); + }); - it("resolution cache follows imports", () => { - (Error as any).stackTraceLimit = Infinity; + it("resolution cache follows imports", () => { + (Error as any).stackTraceLimit = Infinity; - const files = [ - { name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") }, - { name: "b.ts", text: SourceText.New("", "", "var y = 2") }, - ]; - const options: CompilerOptions = { target }; + const files = [ + { name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") }, + { name: "b.ts", text: SourceText.New("", "", "var y = 2") }, + ]; + const options: ts.CompilerOptions = { target }; - const program1 = newProgram(files, ["a.ts"], options); - checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") }))); - checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); + const program1 = newProgram(files, ["a.ts"], options); + checkResolvedModulesCache(program1, "a.ts", new ts.Map(ts.getEntries({ b: ts.createResolvedModule("b.ts") }))); + checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); - const program2 = updateProgram(program1, ["a.ts"], options, files => { - files[0].text = files[0].text.updateProgram("var x = 2"); - }); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); + const program2 = updateProgram(program1, ["a.ts"], options, files => { + files[0].text = files[0].text.updateProgram("var x = 2"); + }); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Completely); - // content of resolution cache should not change - checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") }))); - checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); + // content of resolution cache should not change + checkResolvedModulesCache(program1, "a.ts", new ts.Map(ts.getEntries({ b: ts.createResolvedModule("b.ts") }))); + checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); - // imports has changed - program is not reused - const program3 = updateProgram(program2, ["a.ts"], options, files => { - files[0].text = files[0].text.updateImportsAndExports(""); - }); - assert.equal(program3.structureIsReused, StructureIsReused.SafeModules); - checkResolvedModulesCache(program3, "a.ts", /*expectedContent*/ undefined); + // imports has changed - program is not reused + const program3 = updateProgram(program2, ["a.ts"], options, files => { + files[0].text = files[0].text.updateImportsAndExports(""); + }); + assert.equal(program3.structureIsReused, ts.StructureIsReused.SafeModules); + checkResolvedModulesCache(program3, "a.ts", /*expectedContent*/ undefined); - const program4 = updateProgram(program3, ["a.ts"], options, files => { - const newImports = `import x from 'b' + const program4 = updateProgram(program3, ["a.ts"], options, files => { + const newImports = `import x from 'b' import y from 'c' `; - files[0].text = files[0].text.updateImportsAndExports(newImports); - }); - assert.equal(program4.structureIsReused, StructureIsReused.SafeModules); - checkResolvedModulesCache(program4, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts"), c: undefined }))); + files[0].text = files[0].text.updateImportsAndExports(newImports); }); + assert.equal(program4.structureIsReused, ts.StructureIsReused.SafeModules); + checkResolvedModulesCache(program4, "a.ts", new ts.Map(ts.getEntries({ b: ts.createResolvedModule("b.ts"), c: undefined }))); + }); - it("set the resolvedImports after re-using an ambient external module declaration", () => { - const files = [ - { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";') }, - { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, - ]; - const options: CompilerOptions = { target, typeRoots: ["/types"] }; - const program1 = newProgram(files, ["/a.ts"], options); - const program2 = updateProgram(program1, ["/a.ts"], options, files => { - files[0].text = files[0].text.updateProgram('import * as aa from "a";'); - }); - assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use"); + it("set the resolvedImports after re-using an ambient external module declaration", () => { + const files = [ + { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";') }, + { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, + ]; + const options: ts.CompilerOptions = { target, typeRoots: ["/types"] }; + const program1 = newProgram(files, ["/a.ts"], options); + const program2 = updateProgram(program1, ["/a.ts"], options, files => { + files[0].text = files[0].text.updateProgram('import * as aa from "a";'); }); + assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use"); + }); - it("works with updated SourceFiles", () => { - // adapted repro from https://github.com/Microsoft/TypeScript/issues/26166 - const files = [ - { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";a;') }, - { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, - ]; - const host = createTestCompilerHost(files, target); - const options: CompilerOptions = { target, typeRoots: ["/types"] }; - const program1 = createProgram(["/a.ts"], options, host); - let sourceFile = program1.getSourceFile("/a.ts")!; - assert.isDefined(sourceFile, "'/a.ts' is included in the program"); - sourceFile = updateSourceFile(sourceFile, "'use strict';" + sourceFile.text, { newLength: "'use strict';".length, span: { start: 0, length: 0 } }); - assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are updated"); - const updateHost: TestCompilerHost = { - ...host, - getSourceFile(fileName) { - return fileName === sourceFile.fileName ? sourceFile : program1.getSourceFile(fileName); - } - }; - const program2 = createProgram(["/a.ts"], options, updateHost, program1); - assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use"); - assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are not altered"); - }); + it("works with updated SourceFiles", () => { + // adapted repro from https://github.com/Microsoft/TypeScript/issues/26166 + const files = [ + { name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";a;') }, + { name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') }, + ]; + const host = createTestCompilerHost(files, target); + const options: ts.CompilerOptions = { target, typeRoots: ["/types"] }; + const program1 = ts.createProgram(["/a.ts"], options, host); + let sourceFile = program1.getSourceFile("/a.ts")!; + assert.isDefined(sourceFile, "'/a.ts' is included in the program"); + sourceFile = ts.updateSourceFile(sourceFile, "'use strict';" + sourceFile.text, { newLength: "'use strict';".length, span: { start: 0, length: 0 } }); + assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are updated"); + const updateHost: TestCompilerHost = { + ...host, + getSourceFile(fileName) { + return fileName === sourceFile.fileName ? sourceFile : program1.getSourceFile(fileName); + } + }; + const program2 = ts.createProgram(["/a.ts"], options, updateHost, program1); + assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a", /*mode*/ undefined), "'a' is not an unresolved module after re-use"); + assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are not altered"); + }); - it("resolved type directives cache follows type directives", () => { - const files = [ - { name: "/a.ts", text: SourceText.New("/// ", "", "var x = $") }, - { name: "/types/typedefs/index.d.ts", text: SourceText.New("", "", "declare var $: number") }, - ]; - const options: CompilerOptions = { target, typeRoots: ["/types"] }; + it("resolved type directives cache follows type directives", () => { + const files = [ + { name: "/a.ts", text: SourceText.New("/// ", "", "var x = $") }, + { name: "/types/typedefs/index.d.ts", text: SourceText.New("", "", "declare var $: number") }, + ]; + const options: ts.CompilerOptions = { target, typeRoots: ["/types"] }; - const program1 = newProgram(files, ["/a.ts"], options); - checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); - checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); + const program1 = newProgram(files, ["/a.ts"], options); + checkResolvedTypeDirectivesCache(program1, "/a.ts", new ts.Map(ts.getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); + checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); - const program2 = updateProgram(program1, ["/a.ts"], options, files => { - files[0].text = files[0].text.updateProgram("var x = 2"); - }); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); + const program2 = updateProgram(program1, ["/a.ts"], options, files => { + files[0].text = files[0].text.updateProgram("var x = 2"); + }); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Completely); - // content of resolution cache should not change - checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); - checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); + // content of resolution cache should not change + checkResolvedTypeDirectivesCache(program1, "/a.ts", new ts.Map(ts.getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); + checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); - // type reference directives has changed - program is not reused - const program3 = updateProgram(program2, ["/a.ts"], options, files => { - files[0].text = files[0].text.updateReferences(""); - }); + // type reference directives has changed - program is not reused + const program3 = updateProgram(program2, ["/a.ts"], options, files => { + files[0].text = files[0].text.updateReferences(""); + }); - assert.equal(program3.structureIsReused, StructureIsReused.SafeModules); - checkResolvedTypeDirectivesCache(program3, "/a.ts", /*expectedContent*/ undefined); + assert.equal(program3.structureIsReused, ts.StructureIsReused.SafeModules); + checkResolvedTypeDirectivesCache(program3, "/a.ts", /*expectedContent*/ undefined); - const program4 = updateProgram(program3, ["/a.ts"], options, files => { - const newReferences = `/// + const program4 = updateProgram(program3, ["/a.ts"], options, files => { + const newReferences = `/// /// `; - files[0].text = files[0].text.updateReferences(newReferences); - }); - assert.equal(program4.structureIsReused, StructureIsReused.SafeModules); - checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); - }); - - it("fetches imports after npm install", () => { - const file1Ts = { name: "file1.ts", text: SourceText.New("", `import * as a from "a";`, "const myX: number = a.x;") }; - const file2Ts = { name: "file2.ts", text: SourceText.New("", "", "") }; - const indexDTS = { name: "node_modules/a/index.d.ts", text: SourceText.New("", "export declare let x: number;", "") }; - const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.NodeJs }; - const rootFiles = [file1Ts, file2Ts]; - const filesAfterNpmInstall = [file1Ts, file2Ts, indexDTS]; - - const initialProgram = newProgram(rootFiles, rootFiles.map(f => f.name), options); - { - assert.deepEqual(initialProgram.host.getTrace(), - [ - "======== Resolving module 'a' from 'file1.ts'. ========", - "Explicitly specified module resolution kind: 'NodeJs'.", - "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", - "File 'node_modules/a/package.json' does not exist.", - "File 'node_modules/a.ts' does not exist.", - "File 'node_modules/a.tsx' does not exist.", - "File 'node_modules/a.d.ts' does not exist.", - "File 'node_modules/a/index.ts' does not exist.", - "File 'node_modules/a/index.tsx' does not exist.", - "File 'node_modules/a/index.d.ts' does not exist.", - "File 'node_modules/@types/a/package.json' does not exist.", - "File 'node_modules/@types/a.d.ts' does not exist.", - "File 'node_modules/@types/a/index.d.ts' does not exist.", - "Loading module 'a' from 'node_modules' folder, target file type 'JavaScript'.", - "File 'node_modules/a/package.json' does not exist according to earlier cached lookups.", - "File 'node_modules/a.js' does not exist.", - "File 'node_modules/a.jsx' does not exist.", - "File 'node_modules/a/index.js' does not exist.", - "File 'node_modules/a/index.jsx' does not exist.", - "======== Module name 'a' was not resolved. ========" - ], - "initialProgram: execute module resolution normally."); - - const initialProgramDiagnostics = initialProgram.getSemanticDiagnostics(initialProgram.getSourceFile("file1.ts")); - assert.lengthOf(initialProgramDiagnostics, 1, `initialProgram: import should fail.`); - } - - const afterNpmInstallProgram = updateProgram(initialProgram, rootFiles.map(f => f.name), options, f => { - f[1].text = f[1].text.updateReferences(`/// `); - }, filesAfterNpmInstall); - { - assert.deepEqual(afterNpmInstallProgram.host.getTrace(), - [ - "======== Resolving module 'a' from 'file1.ts'. ========", - "Explicitly specified module resolution kind: 'NodeJs'.", - "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", - "File 'node_modules/a/package.json' does not exist.", - "File 'node_modules/a.ts' does not exist.", - "File 'node_modules/a.tsx' does not exist.", - "File 'node_modules/a.d.ts' does not exist.", - "File 'node_modules/a/index.ts' does not exist.", - "File 'node_modules/a/index.tsx' does not exist.", - "File 'node_modules/a/index.d.ts' exist - use it as a name resolution result.", - "======== Module name 'a' was successfully resolved to 'node_modules/a/index.d.ts'. ========" - ], - "afterNpmInstallProgram: execute module resolution normally."); - - const afterNpmInstallProgramDiagnostics = afterNpmInstallProgram.getSemanticDiagnostics(afterNpmInstallProgram.getSourceFile("file1.ts")); - assert.lengthOf(afterNpmInstallProgramDiagnostics, 0, `afterNpmInstallProgram: program is well-formed with import.`); - } + files[0].text = files[0].text.updateReferences(newReferences); }); + assert.equal(program4.structureIsReused, ts.StructureIsReused.SafeModules); + checkResolvedTypeDirectivesCache(program1, "/a.ts", new ts.Map(ts.getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); + }); - it("can reuse ambient module declarations from non-modified files", () => { - const files = [ - { name: "/a/b/app.ts", text: SourceText.New("", "import * as fs from 'fs'", "") }, - { name: "/a/b/node.d.ts", text: SourceText.New("", "", "declare module 'fs' {}") } - ]; - const options = { target: ScriptTarget.ES2015, traceResolution: true }; - const program = newProgram(files, files.map(f => f.name), options); - assert.deepEqual(program.host.getTrace(), + it("fetches imports after npm install", () => { + const file1Ts = { name: "file1.ts", text: SourceText.New("", `import * as a from "a";`, "const myX: number = a.x;") }; + const file2Ts = { name: "file2.ts", text: SourceText.New("", "", "") }; + const indexDTS = { name: "node_modules/a/index.d.ts", text: SourceText.New("", "export declare let x: number;", "") }; + const options: ts.CompilerOptions = { target: ts.ScriptTarget.ES2015, traceResolution: true, moduleResolution: ts.ModuleResolutionKind.NodeJs }; + const rootFiles = [file1Ts, file2Ts]; + const filesAfterNpmInstall = [file1Ts, file2Ts, indexDTS]; + + const initialProgram = newProgram(rootFiles, rootFiles.map(f => f.name), options); + { + assert.deepEqual(initialProgram.host.getTrace(), [ - "======== Resolving module 'fs' from '/a/b/app.ts'. ========", - "Module resolution kind is not specified, using 'Classic'.", - "File '/a/b/fs.ts' does not exist.", - "File '/a/b/fs.tsx' does not exist.", - "File '/a/b/fs.d.ts' does not exist.", - "File '/a/fs.ts' does not exist.", - "File '/a/fs.tsx' does not exist.", - "File '/a/fs.d.ts' does not exist.", - "File '/fs.ts' does not exist.", - "File '/fs.tsx' does not exist.", - "File '/fs.d.ts' does not exist.", - "File '/a/b/node_modules/@types/fs/package.json' does not exist.", - "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", - "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/a/node_modules/@types/fs/package.json' does not exist.", - "File '/a/node_modules/@types/fs.d.ts' does not exist.", - "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/node_modules/@types/fs/package.json' does not exist.", - "File '/node_modules/@types/fs.d.ts' does not exist.", - "File '/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/a/b/fs.js' does not exist.", - "File '/a/b/fs.jsx' does not exist.", - "File '/a/fs.js' does not exist.", - "File '/a/fs.jsx' does not exist.", - "File '/fs.js' does not exist.", - "File '/fs.jsx' does not exist.", - "======== Module name 'fs' was not resolved. ========", - ], "should look for 'fs'"); - - const program2 = updateProgram(program, program.getRootFileNames(), options, f => { - f[0].text = f[0].text.updateProgram("var x = 1;"); - }); - assert.deepEqual(program2.host.getTrace(), [ - "Module 'fs' was resolved as ambient module declared in '/a/b/node.d.ts' since this file was not modified." - ], "should reuse 'fs' since node.d.ts was not changed"); + "======== Resolving module 'a' from 'file1.ts'. ========", + "Explicitly specified module resolution kind: 'NodeJs'.", + "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", + "File 'node_modules/a/package.json' does not exist.", + "File 'node_modules/a.ts' does not exist.", + "File 'node_modules/a.tsx' does not exist.", + "File 'node_modules/a.d.ts' does not exist.", + "File 'node_modules/a/index.ts' does not exist.", + "File 'node_modules/a/index.tsx' does not exist.", + "File 'node_modules/a/index.d.ts' does not exist.", + "File 'node_modules/@types/a/package.json' does not exist.", + "File 'node_modules/@types/a.d.ts' does not exist.", + "File 'node_modules/@types/a/index.d.ts' does not exist.", + "Loading module 'a' from 'node_modules' folder, target file type 'JavaScript'.", + "File 'node_modules/a/package.json' does not exist according to earlier cached lookups.", + "File 'node_modules/a.js' does not exist.", + "File 'node_modules/a.jsx' does not exist.", + "File 'node_modules/a/index.js' does not exist.", + "File 'node_modules/a/index.jsx' does not exist.", + "======== Module name 'a' was not resolved. ========" + ], + "initialProgram: execute module resolution normally."); + + const initialProgramDiagnostics = initialProgram.getSemanticDiagnostics(initialProgram.getSourceFile("file1.ts")); + assert.lengthOf(initialProgramDiagnostics, 1, `initialProgram: import should fail.`); + } - const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => { - f[0].text = f[0].text.updateProgram("var y = 1;"); - f[1].text = f[1].text.updateProgram("declare var process: any"); - }); - assert.deepEqual(program3.host.getTrace(), + const afterNpmInstallProgram = updateProgram(initialProgram, rootFiles.map(f => f.name), options, f => { + f[1].text = f[1].text.updateReferences(`/// `); + }, filesAfterNpmInstall); + { + assert.deepEqual(afterNpmInstallProgram.host.getTrace(), [ - "======== Resolving module 'fs' from '/a/b/app.ts'. ========", - "Module resolution kind is not specified, using 'Classic'.", - "File '/a/b/fs.ts' does not exist.", - "File '/a/b/fs.tsx' does not exist.", - "File '/a/b/fs.d.ts' does not exist.", - "File '/a/fs.ts' does not exist.", - "File '/a/fs.tsx' does not exist.", - "File '/a/fs.d.ts' does not exist.", - "File '/fs.ts' does not exist.", - "File '/fs.tsx' does not exist.", - "File '/fs.d.ts' does not exist.", - "File '/a/b/node_modules/@types/fs/package.json' does not exist.", - "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", - "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/a/node_modules/@types/fs/package.json' does not exist.", - "File '/a/node_modules/@types/fs.d.ts' does not exist.", - "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/node_modules/@types/fs/package.json' does not exist.", - "File '/node_modules/@types/fs.d.ts' does not exist.", - "File '/node_modules/@types/fs/index.d.ts' does not exist.", - "File '/a/b/fs.js' does not exist.", - "File '/a/b/fs.jsx' does not exist.", - "File '/a/fs.js' does not exist.", - "File '/a/fs.jsx' does not exist.", - "File '/fs.js' does not exist.", - "File '/fs.jsx' does not exist.", - "======== Module name 'fs' was not resolved. ========", - ], "should look for 'fs' again since node.d.ts was changed"); + "======== Resolving module 'a' from 'file1.ts'. ========", + "Explicitly specified module resolution kind: 'NodeJs'.", + "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", + "File 'node_modules/a/package.json' does not exist.", + "File 'node_modules/a.ts' does not exist.", + "File 'node_modules/a.tsx' does not exist.", + "File 'node_modules/a.d.ts' does not exist.", + "File 'node_modules/a/index.ts' does not exist.", + "File 'node_modules/a/index.tsx' does not exist.", + "File 'node_modules/a/index.d.ts' exist - use it as a name resolution result.", + "======== Module name 'a' was successfully resolved to 'node_modules/a/index.d.ts'. ========" + ], + "afterNpmInstallProgram: execute module resolution normally."); + + const afterNpmInstallProgramDiagnostics = afterNpmInstallProgram.getSemanticDiagnostics(afterNpmInstallProgram.getSourceFile("file1.ts")); + assert.lengthOf(afterNpmInstallProgramDiagnostics, 0, `afterNpmInstallProgram: program is well-formed with import.`); + } + }); + + it("can reuse ambient module declarations from non-modified files", () => { + const files = [ + { name: "/a/b/app.ts", text: SourceText.New("", "import * as fs from 'fs'", "") }, + { name: "/a/b/node.d.ts", text: SourceText.New("", "", "declare module 'fs' {}") } + ]; + const options = { target: ts.ScriptTarget.ES2015, traceResolution: true }; + const program = newProgram(files, files.map(f => f.name), options); + assert.deepEqual(program.host.getTrace(), + [ + "======== Resolving module 'fs' from '/a/b/app.ts'. ========", + "Module resolution kind is not specified, using 'Classic'.", + "File '/a/b/fs.ts' does not exist.", + "File '/a/b/fs.tsx' does not exist.", + "File '/a/b/fs.d.ts' does not exist.", + "File '/a/fs.ts' does not exist.", + "File '/a/fs.tsx' does not exist.", + "File '/a/fs.d.ts' does not exist.", + "File '/fs.ts' does not exist.", + "File '/fs.tsx' does not exist.", + "File '/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/package.json' does not exist.", + "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/node_modules/@types/fs/package.json' does not exist.", + "File '/a/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/node_modules/@types/fs/package.json' does not exist.", + "File '/node_modules/@types/fs.d.ts' does not exist.", + "File '/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/b/fs.js' does not exist.", + "File '/a/b/fs.jsx' does not exist.", + "File '/a/fs.js' does not exist.", + "File '/a/fs.jsx' does not exist.", + "File '/fs.js' does not exist.", + "File '/fs.jsx' does not exist.", + "======== Module name 'fs' was not resolved. ========", + ], "should look for 'fs'"); + + const program2 = updateProgram(program, program.getRootFileNames(), options, f => { + f[0].text = f[0].text.updateProgram("var x = 1;"); }); + assert.deepEqual(program2.host.getTrace(), [ + "Module 'fs' was resolved as ambient module declared in '/a/b/node.d.ts' since this file was not modified." + ], "should reuse 'fs' since node.d.ts was not changed"); - it("can reuse module resolutions from non-modified files", () => { - const files = [ - { name: "a1.ts", text: SourceText.New("", "", "let x = 1;") }, - { name: "a2.ts", text: SourceText.New("", "", "let x = 1;") }, - { name: "b1.ts", text: SourceText.New("", "export class B { x: number; }", "") }, - { name: "b2.ts", text: SourceText.New("", "export class B { x: number; }", "") }, - { name: "node_modules/@types/typerefs1/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, - { name: "node_modules/@types/typerefs2/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, - { - name: "f1.ts", - text: - SourceText.New( - `/// ${newLine}/// ${newLine}/// `, - `import { B } from './b1';${newLine}export let BB = B;`, - "declare module './b1' { interface B { y: string; } }") - }, - { - name: "f2.ts", - text: SourceText.New( - `/// ${newLine}/// `, - `import { B } from './b2';${newLine}import { BB } from './f1';`, - "(new BB).x; (new BB).y;") - }, - ]; + const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => { + f[0].text = f[0].text.updateProgram("var y = 1;"); + f[1].text = f[1].text.updateProgram("declare var process: any"); + }); + assert.deepEqual(program3.host.getTrace(), + [ + "======== Resolving module 'fs' from '/a/b/app.ts'. ========", + "Module resolution kind is not specified, using 'Classic'.", + "File '/a/b/fs.ts' does not exist.", + "File '/a/b/fs.tsx' does not exist.", + "File '/a/b/fs.d.ts' does not exist.", + "File '/a/fs.ts' does not exist.", + "File '/a/fs.tsx' does not exist.", + "File '/a/fs.d.ts' does not exist.", + "File '/fs.ts' does not exist.", + "File '/fs.tsx' does not exist.", + "File '/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/package.json' does not exist.", + "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/node_modules/@types/fs/package.json' does not exist.", + "File '/a/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/node_modules/@types/fs/package.json' does not exist.", + "File '/node_modules/@types/fs.d.ts' does not exist.", + "File '/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/b/fs.js' does not exist.", + "File '/a/b/fs.jsx' does not exist.", + "File '/a/fs.js' does not exist.", + "File '/a/fs.jsx' does not exist.", + "File '/fs.js' does not exist.", + "File '/fs.jsx' does not exist.", + "======== Module name 'fs' was not resolved. ========", + ], "should look for 'fs' again since node.d.ts was changed"); + }); - const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.Classic }; - const program1 = newProgram(files, files.map(f => f.name), options); - let expectedErrors = 0; + it("can reuse module resolutions from non-modified files", () => { + const files = [ + { name: "a1.ts", text: SourceText.New("", "", "let x = 1;") }, + { name: "a2.ts", text: SourceText.New("", "", "let x = 1;") }, + { name: "b1.ts", text: SourceText.New("", "export class B { x: number; }", "") }, + { name: "b2.ts", text: SourceText.New("", "export class B { x: number; }", "") }, + { name: "node_modules/@types/typerefs1/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, + { name: "node_modules/@types/typerefs2/index.d.ts", text: SourceText.New("", "", "declare let z: string;") }, { - assert.deepEqual(program1.host.getTrace(), - [ - "======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs1/package.json' does not exist.", - "File 'node_modules/@types/typerefs1/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs1' was successfully resolved to 'node_modules/@types/typerefs1/index.d.ts', primary: true. ========", - "======== Resolving module './b1' from 'f1.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b1.ts' exist - use it as a name resolution result.", - "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", - "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs2/package.json' does not exist.", - "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "======== Resolving module './b2' from 'f2.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b2.ts' exist - use it as a name resolution result.", - "======== Module name './b2' was successfully resolved to 'b2.ts'. ========", - "======== Resolving module './f1' from 'f2.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'f1.ts' exist - use it as a name resolution result.", - "======== Module name './f1' was successfully resolved to 'f1.ts'. ========" - ], - "program1: execute module resolution normally."); - - const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("f2.ts")); - assert.lengthOf(program1Diagnostics, expectedErrors, `initial program should be well-formed`); - } - const indexOfF1 = 6; - const program2 = updateProgram(program1, program1.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateReferences(`/// ${newLine}/// `); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); - + name: "f1.ts", + text: + SourceText.New( + `/// ${newLine}/// ${newLine}/// `, + `import { B } from './b1';${newLine}export let BB = B;`, + "declare module './b1' { interface B { y: string; } }") + }, { - const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("f2.ts")); - assert.lengthOf(program2Diagnostics, expectedErrors, `removing no-default-lib shouldn't affect any types used.`); + name: "f2.ts", + text: SourceText.New( + `/// ${newLine}/// `, + `import { B } from './b2';${newLine}import { BB } from './f1';`, + "(new BB).x; (new BB).y;") + }, + ]; - assert.deepEqual(program2.host.getTrace(), [ + const options: ts.CompilerOptions = { target: ts.ScriptTarget.ES2015, traceResolution: true, moduleResolution: ts.ModuleResolutionKind.Classic }; + const program1 = newProgram(files, files.map(f => f.name), options); + let expectedErrors = 0; + { + assert.deepEqual(program1.host.getTrace(), + [ "======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========", "Resolving with primary search path 'node_modules/@types'.", "File 'node_modules/@types/typerefs1/package.json' does not exist.", @@ -697,488 +449,526 @@ namespace ts { "File 'node_modules/@types/typerefs2/package.json' does not exist.", "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", - "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'." - ], "program2: reuse module resolutions in f2 since it is unchanged"); - } + "======== Resolving module './b2' from 'f2.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b2.ts' exist - use it as a name resolution result.", + "======== Module name './b2' was successfully resolved to 'b2.ts'. ========", + "======== Resolving module './f1' from 'f2.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'f1.ts' exist - use it as a name resolution result.", + "======== Module name './f1' was successfully resolved to 'f1.ts'. ========" + ], + "program1: execute module resolution normally."); - const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateReferences(`/// `); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); + const program1Diagnostics = program1.getSemanticDiagnostics(program1.getSourceFile("f2.ts")); + assert.lengthOf(program1Diagnostics, expectedErrors, `initial program should be well-formed`); + } + const indexOfF1 = 6; + const program2 = updateProgram(program1, program1.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateReferences(`/// ${newLine}/// `); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - { - const program3Diagnostics = program3.getSemanticDiagnostics(program3.getSourceFile("f2.ts")); - assert.lengthOf(program3Diagnostics, expectedErrors, `typerefs2 was unused, so diagnostics should be unaffected.`); + { + const program2Diagnostics = program2.getSemanticDiagnostics(program2.getSourceFile("f2.ts")); + assert.lengthOf(program2Diagnostics, expectedErrors, `removing no-default-lib shouldn't affect any types used.`); - assert.deepEqual(program3.host.getTrace(), [ - "======== Resolving module './b1' from 'f1.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b1.ts' exist - use it as a name resolution result.", - "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", - "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs2/package.json' does not exist.", - "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", - "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'." - ], "program3: reuse module resolutions in f2 since it is unchanged"); - } + assert.deepEqual(program2.host.getTrace(), [ + "======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs1/package.json' does not exist.", + "File 'node_modules/@types/typerefs1/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs1' was successfully resolved to 'node_modules/@types/typerefs1/index.d.ts', primary: true. ========", + "======== Resolving module './b1' from 'f1.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b1.ts' exist - use it as a name resolution result.", + "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", + "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs2/package.json' does not exist.", + "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", + "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", + "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'." + ], "program2: reuse module resolutions in f2 since it is unchanged"); + } + const program3 = updateProgram(program2, program2.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateReferences(`/// `); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - const program4 = updateProgram(program3, program3.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateReferences(""); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); + { + const program3Diagnostics = program3.getSemanticDiagnostics(program3.getSourceFile("f2.ts")); + assert.lengthOf(program3Diagnostics, expectedErrors, `typerefs2 was unused, so diagnostics should be unaffected.`); + + assert.deepEqual(program3.host.getTrace(), [ + "======== Resolving module './b1' from 'f1.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b1.ts' exist - use it as a name resolution result.", + "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", + "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs2/package.json' does not exist.", + "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", + "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", + "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'." + ], "program3: reuse module resolutions in f2 since it is unchanged"); + } - { - const program4Diagnostics = program4.getSemanticDiagnostics(program4.getSourceFile("f2.ts")); - assert.lengthOf(program4Diagnostics, expectedErrors, `a1.ts was unused, so diagnostics should be unaffected.`); - assert.deepEqual(program4.host.getTrace(), [ - "======== Resolving module './b1' from 'f1.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b1.ts' exist - use it as a name resolution result.", - "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", - "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs2/package.json' does not exist.", - "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", - "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", - ], "program_4: reuse module resolutions in f2 since it is unchanged"); - } + const program4 = updateProgram(program3, program3.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateReferences(""); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - const program5 = updateProgram(program4, program4.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateImportsAndExports(`import { B } from './b1';`); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); + { + const program4Diagnostics = program4.getSemanticDiagnostics(program4.getSourceFile("f2.ts")); + assert.lengthOf(program4Diagnostics, expectedErrors, `a1.ts was unused, so diagnostics should be unaffected.`); + + assert.deepEqual(program4.host.getTrace(), [ + "======== Resolving module './b1' from 'f1.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b1.ts' exist - use it as a name resolution result.", + "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", + "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs2/package.json' does not exist.", + "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", + "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", + "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", + ], "program_4: reuse module resolutions in f2 since it is unchanged"); + } - { - const program5Diagnostics = program5.getSemanticDiagnostics(program5.getSourceFile("f2.ts")); - assert.lengthOf(program5Diagnostics, ++expectedErrors, `import of BB in f1 fails. BB is of type any. Add one error`); + const program5 = updateProgram(program4, program4.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateImportsAndExports(`import { B } from './b1';`); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - assert.deepEqual(program5.host.getTrace(), [ - "======== Resolving module './b1' from 'f1.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b1.ts' exist - use it as a name resolution result.", - "======== Module name './b1' was successfully resolved to 'b1.ts'. ========" - ], "program_5: exports do not affect program structure, so f2's resolutions are silently reused."); - } + { + const program5Diagnostics = program5.getSemanticDiagnostics(program5.getSourceFile("f2.ts")); + assert.lengthOf(program5Diagnostics, ++expectedErrors, `import of BB in f1 fails. BB is of type any. Add one error`); - const program6 = updateProgram(program5, program5.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateProgram(""); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); + assert.deepEqual(program5.host.getTrace(), [ + "======== Resolving module './b1' from 'f1.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b1.ts' exist - use it as a name resolution result.", + "======== Module name './b1' was successfully resolved to 'b1.ts'. ========" + ], "program_5: exports do not affect program structure, so f2's resolutions are silently reused."); + } - { - const program6Diagnostics = program6.getSemanticDiagnostics(program6.getSourceFile("f2.ts")); - assert.lengthOf(program6Diagnostics, expectedErrors, `import of BB in f1 fails.`); + const program6 = updateProgram(program5, program5.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateProgram(""); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - assert.deepEqual(program6.host.getTrace(), [ - "======== Resolving module './b1' from 'f1.ts'. ========", - "Explicitly specified module resolution kind: 'Classic'.", - "File 'b1.ts' exist - use it as a name resolution result.", - "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", - "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs2/package.json' does not exist.", - "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", - "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", - ], "program_6: reuse module resolutions in f2 since it is unchanged"); - } + { + const program6Diagnostics = program6.getSemanticDiagnostics(program6.getSourceFile("f2.ts")); + assert.lengthOf(program6Diagnostics, expectedErrors, `import of BB in f1 fails.`); + + assert.deepEqual(program6.host.getTrace(), [ + "======== Resolving module './b1' from 'f1.ts'. ========", + "Explicitly specified module resolution kind: 'Classic'.", + "File 'b1.ts' exist - use it as a name resolution result.", + "======== Module name './b1' was successfully resolved to 'b1.ts'. ========", + "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs2/package.json' does not exist.", + "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", + "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", + "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", + ], "program_6: reuse module resolutions in f2 since it is unchanged"); + } - const program7 = updateProgram(program6, program6.getRootFileNames(), options, f => { - const newSourceText = f[indexOfF1].text.updateImportsAndExports(""); - f[indexOfF1] = { name: "f1.ts", text: newSourceText }; - }); + const program7 = updateProgram(program6, program6.getRootFileNames(), options, f => { + const newSourceText = f[indexOfF1].text.updateImportsAndExports(""); + f[indexOfF1] = { name: "f1.ts", text: newSourceText }; + }); - { - const program7Diagnostics = program7.getSemanticDiagnostics(program7.getSourceFile("f2.ts")); - assert.lengthOf(program7Diagnostics, expectedErrors, `removing import is noop with respect to program, so no change in diagnostics.`); + { + const program7Diagnostics = program7.getSemanticDiagnostics(program7.getSourceFile("f2.ts")); + assert.lengthOf(program7Diagnostics, expectedErrors, `removing import is noop with respect to program, so no change in diagnostics.`); + + assert.deepEqual(program7.host.getTrace(), [ + "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", + "Resolving with primary search path 'node_modules/@types'.", + "File 'node_modules/@types/typerefs2/package.json' does not exist.", + "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", + "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", + "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", + ], "program_7 should reuse module resolutions in f2 since it is unchanged"); + } + }); - assert.deepEqual(program7.host.getTrace(), [ - "======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========", - "Resolving with primary search path 'node_modules/@types'.", - "File 'node_modules/@types/typerefs2/package.json' does not exist.", - "File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.", - "======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========", - "Reusing resolution of module './b2' from 'f2.ts' of old program, it was successfully resolved to 'b2.ts'.", - "Reusing resolution of module './f1' from 'f2.ts' of old program, it was successfully resolved to 'f1.ts'.", - ], "program_7 should reuse module resolutions in f2 since it is unchanged"); - } - }); + describe("redirects", () => { + const axIndex = "/node_modules/a/node_modules/x/index.d.ts"; + const axPackage = "/node_modules/a/node_modules/x/package.json"; + const bxIndex = "/node_modules/b/node_modules/x/index.d.ts"; + const bxPackage = "/node_modules/b/node_modules/x/package.json"; + const root = "/a.ts"; + const compilerOptions = { target, moduleResolution: ts.ModuleResolutionKind.NodeJs }; - describe("redirects", () => { - const axIndex = "/node_modules/a/node_modules/x/index.d.ts"; - const axPackage = "/node_modules/a/node_modules/x/package.json"; - const bxIndex = "/node_modules/b/node_modules/x/index.d.ts"; - const bxPackage = "/node_modules/b/node_modules/x/package.json"; - const root = "/a.ts"; - const compilerOptions = { target, moduleResolution: ModuleResolutionKind.NodeJs }; - - function createRedirectProgram(useGetSourceFileByPath: boolean, options?: { bText: string, bVersion: string }): ProgramWithSourceTexts { - const files: NamedSourceText[] = [ - { - name: "/node_modules/a/index.d.ts", - text: SourceText.New("", 'import X from "x";', "export function a(x: X): void;"), - }, - { - name: axIndex, - text: SourceText.New("", "", "export default class X { private x: number; }"), - }, - { - name: axPackage, - text: SourceText.New("", "", JSON.stringify({ name: "x", version: "1.2.3" })), - }, - { - name: "/node_modules/b/index.d.ts", - text: SourceText.New("", 'import X from "x";', "export const b: X;"), - }, - { - name: bxIndex, - text: SourceText.New("", "", options ? options.bText : "export default class X { private x: number; }"), - }, - { - name: bxPackage, - text: SourceText.New("", "", JSON.stringify({ name: "x", version: options ? options.bVersion : "1.2.3" })), - }, - { - name: root, - text: SourceText.New("", 'import { a } from "a"; import { b } from "b";', "a(b)"), - }, - ]; - - return newProgram(files, [root], compilerOptions, useGetSourceFileByPath); - } + function createRedirectProgram(useGetSourceFileByPath: boolean, options?: { bText: string, bVersion: string }): ProgramWithSourceTexts { + const files: NamedSourceText[] = [ + { + name: "/node_modules/a/index.d.ts", + text: SourceText.New("", 'import X from "x";', "export function a(x: X): void;"), + }, + { + name: axIndex, + text: SourceText.New("", "", "export default class X { private x: number; }"), + }, + { + name: axPackage, + text: SourceText.New("", "", JSON.stringify({ name: "x", version: "1.2.3" })), + }, + { + name: "/node_modules/b/index.d.ts", + text: SourceText.New("", 'import X from "x";', "export const b: X;"), + }, + { + name: bxIndex, + text: SourceText.New("", "", options ? options.bText : "export default class X { private x: number; }"), + }, + { + name: bxPackage, + text: SourceText.New("", "", JSON.stringify({ name: "x", version: options ? options.bVersion : "1.2.3" })), + }, + { + name: root, + text: SourceText.New("", 'import { a } from "a"; import { b } from "b";', "a(b)"), + }, + ]; - function updateRedirectProgram(program: ProgramWithSourceTexts, updater: (files: NamedSourceText[]) => void, useGetSourceFileByPath: boolean): ProgramWithSourceTexts { - return updateProgram(program, [root], compilerOptions, updater, /*newTexts*/ undefined, useGetSourceFileByPath); - } + return newProgram(files, [root], compilerOptions, useGetSourceFileByPath); + } - function verifyRedirects(useGetSourceFileByPath: boolean) { - it("No changes -> redirect not broken", () => { - const program1 = createRedirectProgram(useGetSourceFileByPath); - - const program2 = updateRedirectProgram(program1, files => { - updateProgramText(files, root, "const x = 1;"); - }, useGetSourceFileByPath); - assert.equal(program2.structureIsReused, StructureIsReused.Completely); - assert.lengthOf(program2.getSemanticDiagnostics(), 0); - }); - - it("Target changes -> redirect broken", () => { - const program1 = createRedirectProgram(useGetSourceFileByPath); - assert.lengthOf(program1.getSemanticDiagnostics(), 0); - - const program2 = updateRedirectProgram(program1, files => { - updateProgramText(files, axIndex, "export default class X { private x: number; private y: number; }"); - updateProgramText(files, axPackage, JSON.stringify('{ name: "x", version: "1.2.4" }')); - }, useGetSourceFileByPath); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - assert.lengthOf(program2.getSemanticDiagnostics(), 1); - }); - - it("Underlying changes -> redirect broken", () => { - const program1 = createRedirectProgram(useGetSourceFileByPath); - - const program2 = updateRedirectProgram(program1, files => { - updateProgramText(files, bxIndex, "export default class X { private x: number; private y: number; }"); - updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.4" })); - }, useGetSourceFileByPath); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - assert.lengthOf(program2.getSemanticDiagnostics(), 1); - }); - - it("Previously duplicate packages -> program structure not reused", () => { - const program1 = createRedirectProgram(useGetSourceFileByPath, { bVersion: "1.2.4", bText: "export = class X { private x: number; }" }); - - const program2 = updateRedirectProgram(program1, files => { - updateProgramText(files, bxIndex, "export default class X { private x: number; }"); - updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.3" })); - }, useGetSourceFileByPath); - assert.equal(program2.structureIsReused, StructureIsReused.Not); - assert.deepEqual(program2.getSemanticDiagnostics(), []); - }); - } + function updateRedirectProgram(program: ProgramWithSourceTexts, updater: (files: NamedSourceText[]) => void, useGetSourceFileByPath: boolean): ProgramWithSourceTexts { + return updateProgram(program, [root], compilerOptions, updater, /*newTexts*/ undefined, useGetSourceFileByPath); + } + + function verifyRedirects(useGetSourceFileByPath: boolean) { + it("No changes -> redirect not broken", () => { + const program1 = createRedirectProgram(useGetSourceFileByPath); + + const program2 = updateRedirectProgram(program1, files => { + updateProgramText(files, root, "const x = 1;"); + }, useGetSourceFileByPath); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Completely); + assert.lengthOf(program2.getSemanticDiagnostics(), 0); + }); + + it("Target changes -> redirect broken", () => { + const program1 = createRedirectProgram(useGetSourceFileByPath); + assert.lengthOf(program1.getSemanticDiagnostics(), 0); + + const program2 = updateRedirectProgram(program1, files => { + updateProgramText(files, axIndex, "export default class X { private x: number; private y: number; }"); + updateProgramText(files, axPackage, JSON.stringify('{ name: "x", version: "1.2.4" }')); + }, useGetSourceFileByPath); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Not); + assert.lengthOf(program2.getSemanticDiagnostics(), 1); + }); + + it("Underlying changes -> redirect broken", () => { + const program1 = createRedirectProgram(useGetSourceFileByPath); - describe("when host implements getSourceFile", () => { - verifyRedirects(/*useGetSourceFileByPath*/ false); + const program2 = updateRedirectProgram(program1, files => { + updateProgramText(files, bxIndex, "export default class X { private x: number; private y: number; }"); + updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.4" })); + }, useGetSourceFileByPath); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Not); + assert.lengthOf(program2.getSemanticDiagnostics(), 1); }); - describe("when host implements getSourceFileByPath", () => { - verifyRedirects(/*useGetSourceFileByPath*/ true); + + it("Previously duplicate packages -> program structure not reused", () => { + const program1 = createRedirectProgram(useGetSourceFileByPath, { bVersion: "1.2.4", bText: "export = class X { private x: number; }" }); + + const program2 = updateRedirectProgram(program1, files => { + updateProgramText(files, bxIndex, "export default class X { private x: number; }"); + updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.3" })); + }, useGetSourceFileByPath); + assert.equal(program2.structureIsReused, ts.StructureIsReused.Not); + assert.deepEqual(program2.getSemanticDiagnostics(), []); }); + } + + describe("when host implements getSourceFile", () => { + verifyRedirects(/*useGetSourceFileByPath*/ false); + }); + describe("when host implements getSourceFileByPath", () => { + verifyRedirects(/*useGetSourceFileByPath*/ true); }); }); +}); - describe("unittests:: Reuse program structure:: host is optional", () => { - it("should work if host is not provided", () => { - createProgram([], {}); - }); +describe("unittests:: Reuse program structure:: host is optional", () => { + it("should work if host is not provided", () => { + ts.createProgram([], {}); }); +}); + +type File = ts.TestFSWithWatch.File; +import createTestSystem = ts.TestFSWithWatch.createWatchedSystem; +import libFile = ts.TestFSWithWatch.libFile; + +describe("unittests:: Reuse program structure:: isProgramUptoDate", () => { + function getWhetherProgramIsUptoDate( + program: ts.Program, + newRootFileNames: string[], + newOptions: ts.CompilerOptions + ) { + return ts.isProgramUptoDate( + program, newRootFileNames, newOptions, + path => program.getSourceFileByPath(path)!.version, /*fileExists*/ ts.returnFalse, + /*hasInvalidatedResolutions*/ ts.returnFalse, + /*hasChangedAutomaticTypeDirectiveNames*/ undefined, + /*getParsedCommandLine*/ ts.returnUndefined, + /*projectReferences*/ undefined + ); + } - type File = TestFSWithWatch.File; - import createTestSystem = TestFSWithWatch.createWatchedSystem; - import libFile = TestFSWithWatch.libFile; + function duplicate(options: ts.CompilerOptions): ts.CompilerOptions; + function duplicate(fileNames: string[]): string[]; + function duplicate(filesOrOptions: ts.CompilerOptions | string[]) { + return JSON.parse(JSON.stringify(filesOrOptions)); + } - describe("unittests:: Reuse program structure:: isProgramUptoDate", () => { - function getWhetherProgramIsUptoDate( - program: Program, + describe("should return true when there is no change in compiler options and", () => { + function verifyProgramIsUptoDate( + program: ts.Program, newRootFileNames: string[], - newOptions: CompilerOptions + newOptions: ts.CompilerOptions ) { - return isProgramUptoDate( - program, newRootFileNames, newOptions, - path => program.getSourceFileByPath(path)!.version, /*fileExists*/ returnFalse, - /*hasInvalidatedResolutions*/ returnFalse, - /*hasChangedAutomaticTypeDirectiveNames*/ undefined, - /*getParsedCommandLine*/ returnUndefined, - /*projectReferences*/ undefined - ); + const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions); + assert.isTrue(actual); } - function duplicate(options: CompilerOptions): CompilerOptions; - function duplicate(fileNames: string[]): string[]; - function duplicate(filesOrOptions: CompilerOptions | string[]) { - return JSON.parse(JSON.stringify(filesOrOptions)); + function verifyProgramWithoutConfigFile(system: ts.System, rootFiles: string[], options: ts.CompilerOptions) { + const program = ts.createWatchProgram(ts.createWatchCompilerHostOfFilesAndCompilerOptions({ + rootFiles, + options, + watchOptions: undefined, + system + })).getCurrentProgram().getProgram(); + verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); } - describe("should return true when there is no change in compiler options and", () => { - function verifyProgramIsUptoDate( - program: Program, - newRootFileNames: string[], - newOptions: CompilerOptions - ) { - const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions); - assert.isTrue(actual); - } - - function verifyProgramWithoutConfigFile(system: System, rootFiles: string[], options: CompilerOptions) { - const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ - rootFiles, - options, - watchOptions: undefined, - system - })).getCurrentProgram().getProgram(); - verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); - } + function verifyProgramWithConfigFile(system: ts.System, configFileName: string) { + const program = ts.createWatchProgram(ts.createWatchCompilerHostOfConfigFile({ + configFileName, + system + })).getCurrentProgram().getProgram(); + const { fileNames, options } = ts.parseConfigFileWithSystem(configFileName, {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, ts.notImplemented)!; // TODO: GH#18217 + verifyProgramIsUptoDate(program, fileNames, options); + } - function verifyProgramWithConfigFile(system: System, configFileName: string) { - const program = createWatchProgram(createWatchCompilerHostOfConfigFile({ - configFileName, - system - })).getCurrentProgram().getProgram(); - const { fileNames, options } = parseConfigFileWithSystem(configFileName, {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, notImplemented)!; // TODO: GH#18217 - verifyProgramIsUptoDate(program, fileNames, options); - } + function verifyProgram(files: File[], rootFiles: string[], options: ts.CompilerOptions, configFile: string) { + const system = createTestSystem(files); + verifyProgramWithoutConfigFile(system, rootFiles, options); + verifyProgramWithConfigFile(system, configFile); + } - function verifyProgram(files: File[], rootFiles: string[], options: CompilerOptions, configFile: string) { - const system = createTestSystem(files); - verifyProgramWithoutConfigFile(system, rootFiles, options); - verifyProgramWithConfigFile(system, configFile); - } + it("has empty options", () => { + const file1: File = { + path: "/a/b/file1.ts", + content: "let x = 1" + }; + const file2: File = { + path: "/a/b/file2.ts", + content: "let y = 1" + }; + const configFile: File = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + verifyProgram([file1, file2, libFile, configFile], [file1.path, file2.path], {}, configFile.path); + }); - it("has empty options", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: "let x = 1" - }; - const file2: File = { - path: "/a/b/file2.ts", - content: "let y = 1" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: "{}" - }; - verifyProgram([file1, file2, libFile, configFile], [file1.path, file2.path], {}, configFile.path); - }); + it("has lib specified in the options", () => { + const compilerOptions: ts.CompilerOptions = { lib: ["es5", "es2015.promise"] }; + const app: File = { + path: "/src/app.ts", + content: "var x: Promise;" + }; + const configFile: File = { + path: "/src/tsconfig.json", + content: JSON.stringify({ compilerOptions }) + }; + const es5Lib: File = { + path: "/compiler/lib.es5.d.ts", + content: "declare const eval: any" + }; + const es2015Promise: File = { + path: "/compiler/lib.es2015.promise.d.ts", + content: "declare class Promise {}" + }; - it("has lib specified in the options", () => { - const compilerOptions: CompilerOptions = { lib: ["es5", "es2015.promise"] }; - const app: File = { - path: "/src/app.ts", - content: "var x: Promise;" - }; - const configFile: File = { - path: "/src/tsconfig.json", - content: JSON.stringify({ compilerOptions }) - }; - const es5Lib: File = { - path: "/compiler/lib.es5.d.ts", - content: "declare const eval: any" - }; - const es2015Promise: File = { - path: "/compiler/lib.es2015.promise.d.ts", - content: "declare class Promise {}" - }; - - verifyProgram([app, configFile, es5Lib, es2015Promise], [app.path], compilerOptions, configFile.path); - }); + verifyProgram([app, configFile, es5Lib, es2015Promise], [app.path], compilerOptions, configFile.path); + }); - it("has paths specified in the options", () => { - const compilerOptions: CompilerOptions = { - baseUrl: ".", - paths: { - "*": [ - "packages/mail/data/*", - "packages/styles/*", - "*" - ] - } - }; - const app: File = { - path: "/src/packages/framework/app.ts", - content: 'import classc from "module1/lib/file1";\ + it("has paths specified in the options", () => { + const compilerOptions: ts.CompilerOptions = { + baseUrl: ".", + paths: { + "*": [ + "packages/mail/data/*", + "packages/styles/*", + "*" + ] + } + }; + const app: File = { + path: "/src/packages/framework/app.ts", + content: 'import classc from "module1/lib/file1";\ import classD from "module3/file3";\ let x = new classc();\ let y = new classD();' - }; - const module1: File = { - path: "/src/packages/mail/data/module1/lib/file1.ts", - content: 'import classc from "module2/file2";export default classc;', - }; - const module2: File = { - path: "/src/packages/mail/data/module1/lib/module2/file2.ts", - content: 'class classc { method2() { return "hello"; } }\nexport default classc', - }; - const module3: File = { - path: "/src/packages/styles/module3/file3.ts", - content: "class classD { method() { return 10; } }\nexport default classD;" - }; - const configFile: File = { - path: "/src/tsconfig.json", - content: JSON.stringify({ compilerOptions }) - }; - - verifyProgram([app, module1, module2, module3, libFile, configFile], [app.path], compilerOptions, configFile.path); - }); + }; + const module1: File = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: File = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: File = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const configFile: File = { + path: "/src/tsconfig.json", + content: JSON.stringify({ compilerOptions }) + }; - it("has include paths specified in tsconfig file", () => { - const compilerOptions: CompilerOptions = { - baseUrl: ".", - paths: { - "*": [ - "packages/mail/data/*", - "packages/styles/*", - "*" - ] - } - }; - const app: File = { - path: "/src/packages/framework/app.ts", - content: 'import classc from "module1/lib/file1";\ + verifyProgram([app, module1, module2, module3, libFile, configFile], [app.path], compilerOptions, configFile.path); + }); + + it("has include paths specified in tsconfig file", () => { + const compilerOptions: ts.CompilerOptions = { + baseUrl: ".", + paths: { + "*": [ + "packages/mail/data/*", + "packages/styles/*", + "*" + ] + } + }; + const app: File = { + path: "/src/packages/framework/app.ts", + content: 'import classc from "module1/lib/file1";\ import classD from "module3/file3";\ let x = new classc();\ let y = new classD();' - }; - const module1: File = { - path: "/src/packages/mail/data/module1/lib/file1.ts", - content: 'import classc from "module2/file2";export default classc;', - }; - const module2: File = { - path: "/src/packages/mail/data/module1/lib/module2/file2.ts", - content: 'class classc { method2() { return "hello"; } }\nexport default classc', - }; - const module3: File = { - path: "/src/packages/styles/module3/file3.ts", - content: "class classD { method() { return 10; } }\nexport default classD;" - }; - const configFile: File = { - path: "/src/tsconfig.json", - content: JSON.stringify({ compilerOptions, include: ["packages/**/*.ts"] }) - }; - verifyProgramWithConfigFile(createTestSystem([app, module1, module2, module3, libFile, configFile]), configFile.path); - }); - it("has the same root file names", () => { - const module1: File = { - path: "/src/packages/mail/data/module1/lib/file1.ts", - content: 'import classc from "module2/file2";export default classc;', - }; - const module2: File = { - path: "/src/packages/mail/data/module1/lib/module2/file2.ts", - content: 'class classc { method2() { return "hello"; } }\nexport default classc', - }; - const module3: File = { - path: "/src/packages/styles/module3/file3.ts", - content: "class classD { method() { return 10; } }\nexport default classD;" - }; - const rootFiles = [module1.path, module2.path, module3.path]; - const system = createTestSystem([module1, module2, module3]); - const options = {}; - const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ - rootFiles, - options, - watchOptions: undefined, - system - })).getCurrentProgram().getProgram(); - verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); - }); + }; + const module1: File = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: File = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: File = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const configFile: File = { + path: "/src/tsconfig.json", + content: JSON.stringify({ compilerOptions, include: ["packages/**/*.ts"] }) + }; + verifyProgramWithConfigFile(createTestSystem([app, module1, module2, module3, libFile, configFile]), configFile.path); + }); + it("has the same root file names", () => { + const module1: File = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: File = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: File = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const rootFiles = [module1.path, module2.path, module3.path]; + const system = createTestSystem([module1, module2, module3]); + const options = {}; + const program = ts.createWatchProgram(ts.createWatchCompilerHostOfFilesAndCompilerOptions({ + rootFiles, + options, + watchOptions: undefined, + system + })).getCurrentProgram().getProgram(); + verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); + }); + }); + describe("should return false when there is no change in compiler options but", () => { + function verifyProgramIsNotUptoDate( + program: ts.Program, + newRootFileNames: string[], + newOptions: ts.CompilerOptions + ) { + const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions); + assert.isFalse(actual); + } + it("has more root file names", () => { + const module1: File = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: File = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: File = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const rootFiles = [module1.path, module2.path]; + const newRootFiles = [module1.path, module2.path, module3.path]; + const system = createTestSystem([module1, module2, module3]); + const options = {}; + const program = ts.createWatchProgram(ts.createWatchCompilerHostOfFilesAndCompilerOptions({ + rootFiles, + options, + watchOptions: undefined, + system + })).getCurrentProgram().getProgram(); + verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options)); }); - describe("should return false when there is no change in compiler options but", () => { - function verifyProgramIsNotUptoDate( - program: Program, - newRootFileNames: string[], - newOptions: CompilerOptions - ) { - const actual = getWhetherProgramIsUptoDate(program, newRootFileNames, newOptions); - assert.isFalse(actual); - } - it("has more root file names", () => { - const module1: File = { - path: "/src/packages/mail/data/module1/lib/file1.ts", - content: 'import classc from "module2/file2";export default classc;', - }; - const module2: File = { - path: "/src/packages/mail/data/module1/lib/module2/file2.ts", - content: 'class classc { method2() { return "hello"; } }\nexport default classc', - }; - const module3: File = { - path: "/src/packages/styles/module3/file3.ts", - content: "class classD { method() { return 10; } }\nexport default classD;" - }; - const rootFiles = [module1.path, module2.path]; - const newRootFiles = [module1.path, module2.path, module3.path]; - const system = createTestSystem([module1, module2, module3]); - const options = {}; - const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ - rootFiles, - options, - watchOptions: undefined, - system - })).getCurrentProgram().getProgram(); - verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options)); - }); - it("has one root file replaced by another", () => { - const module1: File = { - path: "/src/packages/mail/data/module1/lib/file1.ts", - content: 'import classc from "module2/file2";export default classc;', - }; - const module2: File = { - path: "/src/packages/mail/data/module1/lib/module2/file2.ts", - content: 'class classc { method2() { return "hello"; } }\nexport default classc', - }; - const module3: File = { - path: "/src/packages/styles/module3/file3.ts", - content: "class classD { method() { return 10; } }\nexport default classD;" - }; - const rootFiles = [module1.path, module2.path]; - const newRootFiles = [module2.path, module3.path]; - const system = createTestSystem([module1, module2, module3]); - const options = {}; - const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions({ - rootFiles, - options, - watchOptions: undefined, - system - })).getCurrentProgram().getProgram(); - verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options)); - }); + it("has one root file replaced by another", () => { + const module1: File = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: File = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: File = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const rootFiles = [module1.path, module2.path]; + const newRootFiles = [module2.path, module3.path]; + const system = createTestSystem([module1, module2, module3]); + const options = {}; + const program = ts.createWatchProgram(ts.createWatchCompilerHostOfFilesAndCompilerOptions({ + rootFiles, + options, + watchOptions: undefined, + system + })).getCurrentProgram().getProgram(); + verifyProgramIsNotUptoDate(program, duplicate(newRootFiles), duplicate(options)); }); }); -} +}); diff --git a/src/testRunner/unittests/semver.ts b/src/testRunner/unittests/semver.ts index 272704c8d0366..241a2f7209e0b 100644 --- a/src/testRunner/unittests/semver.ts +++ b/src/testRunner/unittests/semver.ts @@ -1,926 +1,927 @@ -namespace ts { - import theory = Utils.theory; - describe("unittests:: semver", () => { - describe("Version", () => { - function assertVersion(version: Version, [major, minor, patch, prerelease, build]: [number, number, number, string[]?, string[]?]) { - assert.strictEqual(version.major, major); - assert.strictEqual(version.minor, minor); - assert.strictEqual(version.patch, patch); - assert.deepEqual(version.prerelease, prerelease || emptyArray); - assert.deepEqual(version.build, build || emptyArray); - } - describe("new", () => { - it("text", () => { - assertVersion(new Version("1.2.3-pre.4+build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]); - }); - it("parts", () => { - assertVersion(new Version(1, 2, 3, "pre.4", "build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]); - assertVersion(new Version(1, 2, 3, ["pre", "4"], ["build", "5"]), [1, 2, 3, ["pre", "4"], ["build", "5"]]); - assertVersion(new Version(1, 2, 3), [1, 2, 3]); - assertVersion(new Version(1, 2), [1, 2, 0]); - assertVersion(new Version(1), [1, 0, 0]); - }); - }); - it("toString", () => { - assert.strictEqual(new Version(1, 2, 3, "pre.4", "build.5").toString(), "1.2.3-pre.4+build.5"); - assert.strictEqual(new Version(1, 2, 3, "pre.4").toString(), "1.2.3-pre.4"); - assert.strictEqual(new Version(1, 2, 3, /*prerelease*/ undefined, "build.5").toString(), "1.2.3+build.5"); - assert.strictEqual(new Version(1, 2, 3).toString(), "1.2.3"); - assert.strictEqual(new Version(1, 2).toString(), "1.2.0"); - assert.strictEqual(new Version(1).toString(), "1.0.0"); - }); - it("compareTo", () => { - // https://semver.org/#spec-item-11 - // > Precedence is determined by the first difference when comparing each of these - // > identifiers from left to right as follows: Major, minor, and patch versions are - // > always compared numerically. - assert.strictEqual(new Version("1.0.0").compareTo(new Version("2.0.0")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.1.0")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.1")), Comparison.LessThan); - assert.strictEqual(new Version("2.0.0").compareTo(new Version("1.0.0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.1.0").compareTo(new Version("1.0.0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.1").compareTo(new Version("1.0.0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.0")), Comparison.EqualTo); - - // https://semver.org/#spec-item-11 - // > When major, minor, and patch are equal, a pre-release version has lower - // > precedence than a normal version. - assert.strictEqual(new Version("1.0.0").compareTo(new Version("1.0.0-pre")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.1-pre").compareTo(new Version("1.0.0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-pre").compareTo(new Version("1.0.0")), Comparison.LessThan); - - // https://semver.org/#spec-item-11 - // > identifiers consisting of only digits are compared numerically - assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-1")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-1").compareTo(new Version("1.0.0-0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-2").compareTo(new Version("1.0.0-10")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-10").compareTo(new Version("1.0.0-2")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-0")), Comparison.EqualTo); - - // https://semver.org/#spec-item-11 - // > identifiers with letters or hyphens are compared lexically in ASCII sort order. - assert.strictEqual(new Version("1.0.0-a").compareTo(new Version("1.0.0-b")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-a-2").compareTo(new Version("1.0.0-a-10")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-b").compareTo(new Version("1.0.0-a")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-a").compareTo(new Version("1.0.0-a")), Comparison.EqualTo); - assert.strictEqual(new Version("1.0.0-A").compareTo(new Version("1.0.0-a")), Comparison.LessThan); - - // https://semver.org/#spec-item-11 - // > Numeric identifiers always have lower precedence than non-numeric identifiers. - assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-alpha")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-0")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-0").compareTo(new Version("1.0.0-0")), Comparison.EqualTo); - assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-alpha")), Comparison.EqualTo); - - // https://semver.org/#spec-item-11 - // > A larger set of pre-release fields has a higher precedence than a smaller set, if all - // > of the preceding identifiers are equal. - assert.strictEqual(new Version("1.0.0-alpha").compareTo(new Version("1.0.0-alpha.0")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-alpha.0").compareTo(new Version("1.0.0-alpha")), Comparison.GreaterThan); - - // https://semver.org/#spec-item-11 - // > Precedence for two pre-release versions with the same major, minor, and patch version - // > MUST be determined by comparing each dot separated identifier from left to right until - // > a difference is found [...] - assert.strictEqual(new Version("1.0.0-a.0.b.1").compareTo(new Version("1.0.0-a.0.b.2")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-a.0.b.1").compareTo(new Version("1.0.0-b.0.a.1")), Comparison.LessThan); - assert.strictEqual(new Version("1.0.0-a.0.b.2").compareTo(new Version("1.0.0-a.0.b.1")), Comparison.GreaterThan); - assert.strictEqual(new Version("1.0.0-b.0.a.1").compareTo(new Version("1.0.0-a.0.b.1")), Comparison.GreaterThan); - - // https://semver.org/#spec-item-11 - // > Build metadata does not figure into precedence - assert.strictEqual(new Version("1.0.0+build").compareTo(new Version("1.0.0")), Comparison.EqualTo); +import * as ts from "../_namespaces/ts"; +import * as Utils from "../_namespaces/Utils"; + +import theory = Utils.theory; +describe("unittests:: semver", () => { + describe("Version", () => { + function assertVersion(version: ts.Version, [major, minor, patch, prerelease, build]: [number, number, number, string[]?, string[]?]) { + assert.strictEqual(version.major, major); + assert.strictEqual(version.minor, minor); + assert.strictEqual(version.patch, patch); + assert.deepEqual(version.prerelease, prerelease || ts.emptyArray); + assert.deepEqual(version.build, build || ts.emptyArray); + } + describe("new", () => { + it("text", () => { + assertVersion(new ts.Version("1.2.3-pre.4+build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]); }); - it("increment", () => { - assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("major"), [2, 0, 0]); - assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("minor"), [1, 3, 0]); - assertVersion(new Version(1, 2, 3, "pre.4", "build.5").increment("patch"), [1, 2, 4]); + it("parts", () => { + assertVersion(new ts.Version(1, 2, 3, "pre.4", "build.5"), [1, 2, 3, ["pre", "4"], ["build", "5"]]); + assertVersion(new ts.Version(1, 2, 3, ["pre", "4"], ["build", "5"]), [1, 2, 3, ["pre", "4"], ["build", "5"]]); + assertVersion(new ts.Version(1, 2, 3), [1, 2, 3]); + assertVersion(new ts.Version(1, 2), [1, 2, 0]); + assertVersion(new ts.Version(1), [1, 0, 0]); }); }); - describe("VersionRange", () => { - it("major wildcard types treated the same", () => { - const versionStrings = [ - "", - "*", - "*.*", - "*.*.*", - "x", - "x.x", - "x.x.x", - "X", - "X.X", - "X.X.X", - ]; - for (let i = 0; i < versionStrings.length; i++) { - for (let j = i + 1; j < versionStrings.length; j++) { - const left = new VersionRange(versionStrings[i]); - const right = new VersionRange(versionStrings[j]); - assert.strictEqual(left.toString(), right.toString()); - } - } - }); - - it("minor wildcard types treated the same", () => { - const versionStrings = [ - "1.*", - "1.*.*", - "1.x", - "1.x.x", - "1.X", - "1.X.X", - ]; - for (let i = 0; i < versionStrings.length; i++) { - for (let j = i + 1; j < versionStrings.length; j++) { - const left = new VersionRange(versionStrings[i]); - const right = new VersionRange(versionStrings[j]); - assert.strictEqual(left.toString(), right.toString()); - } + it("toString", () => { + assert.strictEqual(new ts.Version(1, 2, 3, "pre.4", "build.5").toString(), "1.2.3-pre.4+build.5"); + assert.strictEqual(new ts.Version(1, 2, 3, "pre.4").toString(), "1.2.3-pre.4"); + assert.strictEqual(new ts.Version(1, 2, 3, /*prerelease*/ undefined, "build.5").toString(), "1.2.3+build.5"); + assert.strictEqual(new ts.Version(1, 2, 3).toString(), "1.2.3"); + assert.strictEqual(new ts.Version(1, 2).toString(), "1.2.0"); + assert.strictEqual(new ts.Version(1).toString(), "1.0.0"); + }); + it("compareTo", () => { + // https://semver.org/#spec-item-11 + // > Precedence is determined by the first difference when comparing each of these + // > identifiers from left to right as follows: Major, minor, and patch versions are + // > always compared numerically. + assert.strictEqual(new ts.Version("1.0.0").compareTo(new ts.Version("2.0.0")), ts.Comparison.LessThan); + assert.strictEqual(new ts.Version("1.0.0").compareTo(new ts.Version("1.1.0")), ts.Comparison.LessThan); + assert.strictEqual(new ts.Version("1.0.0").compareTo(new ts.Version("1.0.1")), ts.Comparison.LessThan); + assert.strictEqual(new ts.Version("2.0.0").compareTo(new ts.Version("1.0.0")), ts.Comparison.GreaterThan); + assert.strictEqual(new ts.Version("1.1.0").compareTo(new ts.Version("1.0.0")), ts.Comparison.GreaterThan); + assert.strictEqual(new ts.Version("1.0.1").compareTo(new ts.Version("1.0.0")), ts.Comparison.GreaterThan); + assert.strictEqual(new ts.Version("1.0.0").compareTo(new ts.Version("1.0.0")), ts.Comparison.EqualTo); + + // https://semver.org/#spec-item-11 + // > When major, minor, and patch are equal, a pre-release version has lower + // > precedence than a normal version. + assert.strictEqual(new ts.Version("1.0.0").compareTo(new ts.Version("1.0.0-pre")), ts.Comparison.GreaterThan); + assert.strictEqual(new ts.Version("1.0.1-pre").compareTo(new ts.Version("1.0.0")), ts.Comparison.GreaterThan); + assert.strictEqual(new ts.Version("1.0.0-pre").compareTo(new ts.Version("1.0.0")), ts.Comparison.LessThan); + + // https://semver.org/#spec-item-11 + // > identifiers consisting of only digits are compared numerically + assert.strictEqual(new ts.Version("1.0.0-0").compareTo(new ts.Version("1.0.0-1")), ts.Comparison.LessThan); + assert.strictEqual(new ts.Version("1.0.0-1").compareTo(new ts.Version("1.0.0-0")), ts.Comparison.GreaterThan); + assert.strictEqual(new ts.Version("1.0.0-2").compareTo(new ts.Version("1.0.0-10")), ts.Comparison.LessThan); + assert.strictEqual(new ts.Version("1.0.0-10").compareTo(new ts.Version("1.0.0-2")), ts.Comparison.GreaterThan); + assert.strictEqual(new ts.Version("1.0.0-0").compareTo(new ts.Version("1.0.0-0")), ts.Comparison.EqualTo); + + // https://semver.org/#spec-item-11 + // > identifiers with letters or hyphens are compared lexically in ASCII sort order. + assert.strictEqual(new ts.Version("1.0.0-a").compareTo(new ts.Version("1.0.0-b")), ts.Comparison.LessThan); + assert.strictEqual(new ts.Version("1.0.0-a-2").compareTo(new ts.Version("1.0.0-a-10")), ts.Comparison.GreaterThan); + assert.strictEqual(new ts.Version("1.0.0-b").compareTo(new ts.Version("1.0.0-a")), ts.Comparison.GreaterThan); + assert.strictEqual(new ts.Version("1.0.0-a").compareTo(new ts.Version("1.0.0-a")), ts.Comparison.EqualTo); + assert.strictEqual(new ts.Version("1.0.0-A").compareTo(new ts.Version("1.0.0-a")), ts.Comparison.LessThan); + + // https://semver.org/#spec-item-11 + // > Numeric identifiers always have lower precedence than non-numeric identifiers. + assert.strictEqual(new ts.Version("1.0.0-0").compareTo(new ts.Version("1.0.0-alpha")), ts.Comparison.LessThan); + assert.strictEqual(new ts.Version("1.0.0-alpha").compareTo(new ts.Version("1.0.0-0")), ts.Comparison.GreaterThan); + assert.strictEqual(new ts.Version("1.0.0-0").compareTo(new ts.Version("1.0.0-0")), ts.Comparison.EqualTo); + assert.strictEqual(new ts.Version("1.0.0-alpha").compareTo(new ts.Version("1.0.0-alpha")), ts.Comparison.EqualTo); + + // https://semver.org/#spec-item-11 + // > A larger set of pre-release fields has a higher precedence than a smaller set, if all + // > of the preceding identifiers are equal. + assert.strictEqual(new ts.Version("1.0.0-alpha").compareTo(new ts.Version("1.0.0-alpha.0")), ts.Comparison.LessThan); + assert.strictEqual(new ts.Version("1.0.0-alpha.0").compareTo(new ts.Version("1.0.0-alpha")), ts.Comparison.GreaterThan); + + // https://semver.org/#spec-item-11 + // > Precedence for two pre-release versions with the same major, minor, and patch version + // > MUST be determined by comparing each dot separated identifier from left to right until + // > a difference is found [...] + assert.strictEqual(new ts.Version("1.0.0-a.0.b.1").compareTo(new ts.Version("1.0.0-a.0.b.2")), ts.Comparison.LessThan); + assert.strictEqual(new ts.Version("1.0.0-a.0.b.1").compareTo(new ts.Version("1.0.0-b.0.a.1")), ts.Comparison.LessThan); + assert.strictEqual(new ts.Version("1.0.0-a.0.b.2").compareTo(new ts.Version("1.0.0-a.0.b.1")), ts.Comparison.GreaterThan); + assert.strictEqual(new ts.Version("1.0.0-b.0.a.1").compareTo(new ts.Version("1.0.0-a.0.b.1")), ts.Comparison.GreaterThan); + + // https://semver.org/#spec-item-11 + // > Build metadata does not figure into precedence + assert.strictEqual(new ts.Version("1.0.0+build").compareTo(new ts.Version("1.0.0")), ts.Comparison.EqualTo); + }); + it("increment", () => { + assertVersion(new ts.Version(1, 2, 3, "pre.4", "build.5").increment("major"), [2, 0, 0]); + assertVersion(new ts.Version(1, 2, 3, "pre.4", "build.5").increment("minor"), [1, 3, 0]); + assertVersion(new ts.Version(1, 2, 3, "pre.4", "build.5").increment("patch"), [1, 2, 4]); + }); + }); + describe("VersionRange", () => { + it("major wildcard types treated the same", () => { + const versionStrings = [ + "", + "*", + "*.*", + "*.*.*", + "x", + "x.x", + "x.x.x", + "X", + "X.X", + "X.X.X", + ]; + for (let i = 0; i < versionStrings.length; i++) { + for (let j = i + 1; j < versionStrings.length; j++) { + const left = new ts.VersionRange(versionStrings[i]); + const right = new ts.VersionRange(versionStrings[j]); + assert.strictEqual(left.toString(), right.toString()); } - }); + } + }); - it("patch wildcard types treated the same", () => { - const versionStrings = [ - "1.2.*", - "1.2.x", - "1.2.X", - ]; - for (let i = 0; i < versionStrings.length; i++) { - for (let j = i + 1; j < versionStrings.length; j++) { - const left = new VersionRange(versionStrings[i]); - const right = new VersionRange(versionStrings[j]); - assert.strictEqual(left.toString(), right.toString()); - } + it("minor wildcard types treated the same", () => { + const versionStrings = [ + "1.*", + "1.*.*", + "1.x", + "1.x.x", + "1.X", + "1.X.X", + ]; + for (let i = 0; i < versionStrings.length; i++) { + for (let j = i + 1; j < versionStrings.length; j++) { + const left = new ts.VersionRange(versionStrings[i]); + const right = new ts.VersionRange(versionStrings[j]); + assert.strictEqual(left.toString(), right.toString()); } - }); - - function assertVersionRange(version: string, good: string[], bad: string[]): () => void { - return () => { - const range = VersionRange.tryParse(version)!; - assert(range); - for (const g of good) { - assert.isTrue(range.test(g), g); - } - for (const b of bad) { - assert.isFalse(range.test(b), b); - } - }; } + }); - it("< works", assertVersionRange("<3.8.0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); - it("<= works", assertVersionRange("<=3.8.0", ["3.6", "3.7", "3.8"], ["3.9", "4.0"])); - it("> works", assertVersionRange(">3.8.0", ["3.9", "4.0"], ["3.6", "3.7", "3.8"])); - it(">= works", assertVersionRange(">=3.8.0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); - - it("< works with prerelease", assertVersionRange("<3.8.0-0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); - it("<= works with prerelease", assertVersionRange("<=3.8.0-0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); - it("> works with prerelease", assertVersionRange(">3.8.0-0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); - it(">= works with prerelease", assertVersionRange(">=3.8.0-0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); - - function assertRange(rangeText: string, versionText: string, inRange: boolean) { - const range = new VersionRange(rangeText); - const version = new Version(versionText); - assert.strictEqual(range.test(version), inRange, `Expected version '${version}' ${inRange ? `to be` : `to not be`} in range '${rangeText}' (${range})`); + it("patch wildcard types treated the same", () => { + const versionStrings = [ + "1.2.*", + "1.2.x", + "1.2.X", + ]; + for (let i = 0; i < versionStrings.length; i++) { + for (let j = i + 1; j < versionStrings.length; j++) { + const left = new ts.VersionRange(versionStrings[i]); + const right = new ts.VersionRange(versionStrings[j]); + assert.strictEqual(left.toString(), right.toString()); + } } - - theory("comparators", assertRange, [ - // empty (matches everything) - ["", "2.0.0", true], - ["", "2.0.0-0", true], - ["", "1.1.0", true], - ["", "1.1.0-0", true], - ["", "1.0.1", true], - ["", "1.0.1-0", true], - ["", "1.0.0", true], - ["", "1.0.0-0", true], - ["", "0.0.0", true], - ["", "0.0.0-0", true], - - // wildcard major (matches everything) - ["*", "2.0.0", true], - ["*", "2.0.0-0", true], - ["*", "1.1.0", true], - ["*", "1.1.0-0", true], - ["*", "1.0.1", true], - ["*", "1.0.1-0", true], - ["*", "1.0.0", true], - ["*", "1.0.0-0", true], - ["*", "0.0.0", true], - ["*", "0.0.0-0", true], - - // wildcard minor - ["1", "2.0.0", false], - ["1", "2.0.0-0", false], - ["1", "1.1.0", true], - ["1", "1.1.0-0", true], - ["1", "1.0.1", true], - ["1", "1.0.1-0", true], - ["1", "1.0.0", true], - ["1", "1.0.0-0", true], - ["1", "0.0.0", false], - ["1", "0.0.0-0", false], - - // wildcard patch - ["1.1", "2.0.0", false], - ["1.1", "2.0.0-0", false], - ["1.1", "1.1.0", true], - ["1.1", "1.1.0-0", true], - ["1.1", "1.0.1", false], - ["1.1", "1.0.1-0", false], - ["1.1", "1.0.0", false], - ["1.1", "1.0.0-0", false], - ["1.1", "0.0.0", false], - ["1.1", "0.0.0-0", false], - ["1.0", "2.0.0", false], - ["1.0", "2.0.0-0", false], - ["1.0", "1.1.0", false], - ["1.0", "1.1.0-0", false], - ["1.0", "1.0.1", true], - ["1.0", "1.0.1-0", true], - ["1.0", "1.0.0", true], - ["1.0", "1.0.0-0", true], - ["1.0", "0.0.0", false], - ["1.0", "0.0.0-0", false], - - // exact - ["1.1.0", "2.0.0", false], - ["1.1.0", "2.0.0-0", false], - ["1.1.0", "1.1.0", true], - ["1.1.0", "1.1.0-0", false], - ["1.1.0", "1.0.1", false], - ["1.1.0", "1.0.1-0", false], - ["1.1.0", "1.0.0-0", false], - ["1.1.0", "1.0.0", false], - ["1.1.0", "0.0.0", false], - ["1.1.0", "0.0.0-0", false], - ["1.1.0-0", "2.0.0", false], - ["1.1.0-0", "2.0.0-0", false], - ["1.1.0-0", "1.1.0", false], - ["1.1.0-0", "1.1.0-0", true], - ["1.1.0-0", "1.0.1", false], - ["1.1.0-0", "1.0.1-0", false], - ["1.1.0-0", "1.0.0-0", false], - ["1.1.0-0", "1.0.0", false], - ["1.1.0-0", "0.0.0", false], - ["1.1.0-0", "0.0.0-0", false], - ["1.0.1", "2.0.0", false], - ["1.0.1", "2.0.0-0", false], - ["1.0.1", "1.1.0", false], - ["1.0.1", "1.1.0-0", false], - ["1.0.1", "1.0.1", true], - ["1.0.1", "1.0.1-0", false], - ["1.0.1", "1.0.0-0", false], - ["1.0.1", "1.0.0", false], - ["1.0.1", "0.0.0", false], - ["1.0.1", "0.0.0-0", false], - ["1.0.1-0", "2.0.0", false], - ["1.0.1-0", "2.0.0-0", false], - ["1.0.1-0", "1.1.0", false], - ["1.0.1-0", "1.1.0-0", false], - ["1.0.1-0", "1.0.1", false], - ["1.0.1-0", "1.0.1-0", true], - ["1.0.1-0", "1.0.0-0", false], - ["1.0.1-0", "1.0.0", false], - ["1.0.1-0", "0.0.0", false], - ["1.0.1-0", "0.0.0-0", false], - ["1.0.0", "2.0.0", false], - ["1.0.0", "2.0.0-0", false], - ["1.0.0", "1.1.0", false], - ["1.0.0", "1.1.0-0", false], - ["1.0.0", "1.0.1", false], - ["1.0.0", "1.0.1-0", false], - ["1.0.0", "1.0.0-0", false], - ["1.0.0", "1.0.0", true], - ["1.0.0", "0.0.0", false], - ["1.0.0", "0.0.0-0", false], - ["1.0.0-0", "2.0.0", false], - ["1.0.0-0", "2.0.0-0", false], - ["1.0.0-0", "1.1.0", false], - ["1.0.0-0", "1.1.0-0", false], - ["1.0.0-0", "1.0.1", false], - ["1.0.0-0", "1.0.1-0", false], - ["1.0.0-0", "1.0.0", false], - ["1.0.0-0", "1.0.0-0", true], - - // = wildcard major (matches everything) - ["=*", "2.0.0", true], - ["=*", "2.0.0-0", true], - ["=*", "1.1.0", true], - ["=*", "1.1.0-0", true], - ["=*", "1.0.1", true], - ["=*", "1.0.1-0", true], - ["=*", "1.0.0", true], - ["=*", "1.0.0-0", true], - ["=*", "0.0.0", true], - ["=*", "0.0.0-0", true], - - // = wildcard minor - ["=1", "2.0.0", false], - ["=1", "2.0.0-0", false], - ["=1", "1.1.0", true], - ["=1", "1.1.0-0", true], - ["=1", "1.0.1", true], - ["=1", "1.0.1-0", true], - ["=1", "1.0.0", true], - ["=1", "1.0.0-0", true], - ["=1", "0.0.0", false], - ["=1", "0.0.0-0", false], - - // = wildcard patch - ["=1.1", "2.0.0", false], - ["=1.1", "2.0.0-0", false], - ["=1.1", "1.1.0", true], - ["=1.1", "1.1.0-0", true], - ["=1.1", "1.0.1", false], - ["=1.1", "1.0.1-0", false], - ["=1.1", "1.0.0", false], - ["=1.1", "1.0.0-0", false], - ["=1.1", "0.0.0", false], - ["=1.1", "0.0.0-0", false], - ["=1.0", "2.0.0", false], - ["=1.0", "2.0.0-0", false], - ["=1.0", "1.1.0", false], - ["=1.0", "1.1.0-0", false], - ["=1.0", "1.0.1", true], - ["=1.0", "1.0.1-0", true], - ["=1.0", "1.0.0", true], - ["=1.0", "1.0.0-0", true], - ["=1.0", "0.0.0", false], - ["=1.0", "0.0.0-0", false], - - // = exact - ["=1.1.0", "2.0.0", false], - ["=1.1.0", "2.0.0-0", false], - ["=1.1.0", "1.1.0", true], - ["=1.1.0", "1.1.0-0", false], - ["=1.1.0", "1.0.1", false], - ["=1.1.0", "1.0.1-0", false], - ["=1.1.0", "1.0.0-0", false], - ["=1.1.0", "1.0.0", false], - ["=1.1.0", "0.0.0", false], - ["=1.1.0", "0.0.0-0", false], - ["=1.1.0-0", "2.0.0", false], - ["=1.1.0-0", "2.0.0-0", false], - ["=1.1.0-0", "1.1.0", false], - ["=1.1.0-0", "1.1.0-0", true], - ["=1.1.0-0", "1.0.1", false], - ["=1.1.0-0", "1.0.1-0", false], - ["=1.1.0-0", "1.0.0-0", false], - ["=1.1.0-0", "1.0.0", false], - ["=1.1.0-0", "0.0.0", false], - ["=1.1.0-0", "0.0.0-0", false], - ["=1.0.1", "2.0.0", false], - ["=1.0.1", "2.0.0-0", false], - ["=1.0.1", "1.1.0", false], - ["=1.0.1", "1.1.0-0", false], - ["=1.0.1", "1.0.1", true], - ["=1.0.1", "1.0.1-0", false], - ["=1.0.1", "1.0.0-0", false], - ["=1.0.1", "1.0.0", false], - ["=1.0.1", "0.0.0", false], - ["=1.0.1", "0.0.0-0", false], - ["=1.0.1-0", "2.0.0", false], - ["=1.0.1-0", "2.0.0-0", false], - ["=1.0.1-0", "1.1.0", false], - ["=1.0.1-0", "1.1.0-0", false], - ["=1.0.1-0", "1.0.1", false], - ["=1.0.1-0", "1.0.1-0", true], - ["=1.0.1-0", "1.0.0-0", false], - ["=1.0.1-0", "1.0.0", false], - ["=1.0.1-0", "0.0.0", false], - ["=1.0.1-0", "0.0.0-0", false], - ["=1.0.0", "2.0.0", false], - ["=1.0.0", "2.0.0-0", false], - ["=1.0.0", "1.1.0", false], - ["=1.0.0", "1.1.0-0", false], - ["=1.0.0", "1.0.1", false], - ["=1.0.0", "1.0.1-0", false], - ["=1.0.0", "1.0.0-0", false], - ["=1.0.0", "1.0.0", true], - ["=1.0.0", "0.0.0", false], - ["=1.0.0", "0.0.0-0", false], - ["=1.0.0-0", "2.0.0", false], - ["=1.0.0-0", "2.0.0-0", false], - ["=1.0.0-0", "1.1.0", false], - ["=1.0.0-0", "1.1.0-0", false], - ["=1.0.0-0", "1.0.1", false], - ["=1.0.0-0", "1.0.1-0", false], - ["=1.0.0-0", "1.0.0", false], - ["=1.0.0-0", "1.0.0-0", true], - - // > wildcard major (matches nothing) - [">*", "2.0.0", false], - [">*", "2.0.0-0", false], - [">*", "1.1.0", false], - [">*", "1.1.0-0", false], - [">*", "1.0.1", false], - [">*", "1.0.1-0", false], - [">*", "1.0.0", false], - [">*", "1.0.0-0", false], - [">*", "0.0.0", false], - [">*", "0.0.0-0", false], - - // > wildcard minor - [">1", "2.0.0", true], - [">1", "2.0.0-0", true], - [">1", "1.1.0", false], - [">1", "1.1.0-0", false], - [">1", "1.0.1", false], - [">1", "1.0.1-0", false], - [">1", "1.0.0", false], - [">1", "1.0.0-0", false], - [">1", "0.0.0", false], - [">1", "0.0.0-0", false], - - // > wildcard patch - [">1.1", "2.0.0", true], - [">1.1", "2.0.0-0", true], - [">1.1", "1.1.0", false], - [">1.1", "1.1.0-0", false], - [">1.1", "1.0.1", false], - [">1.1", "1.0.1-0", false], - [">1.1", "1.0.0", false], - [">1.1", "1.0.0-0", false], - [">1.1", "0.0.0", false], - [">1.1", "0.0.0-0", false], - [">1.0", "2.0.0", true], - [">1.0", "2.0.0-0", true], - [">1.0", "1.1.0", true], - [">1.0", "1.1.0-0", true], - [">1.0", "1.0.1", false], - [">1.0", "1.0.1-0", false], - [">1.0", "1.0.0", false], - [">1.0", "1.0.0-0", false], - [">1.0", "0.0.0", false], - [">1.0", "0.0.0-0", false], - - // > exact - [">1.1.0", "2.0.0", true], - [">1.1.0", "2.0.0-0", true], - [">1.1.0", "1.1.0", false], - [">1.1.0", "1.1.0-0", false], - [">1.1.0", "1.0.1", false], - [">1.1.0", "1.0.1-0", false], - [">1.1.0", "1.0.0", false], - [">1.1.0", "1.0.0-0", false], - [">1.1.0", "0.0.0", false], - [">1.1.0", "0.0.0-0", false], - [">1.1.0-0", "2.0.0", true], - [">1.1.0-0", "2.0.0-0", true], - [">1.1.0-0", "1.1.0", true], - [">1.1.0-0", "1.1.0-0", false], - [">1.1.0-0", "1.0.1", false], - [">1.1.0-0", "1.0.1-0", false], - [">1.1.0-0", "1.0.0", false], - [">1.1.0-0", "1.0.0-0", false], - [">1.1.0-0", "0.0.0", false], - [">1.1.0-0", "0.0.0-0", false], - [">1.0.1", "2.0.0", true], - [">1.0.1", "2.0.0-0", true], - [">1.0.1", "1.1.0", true], - [">1.0.1", "1.1.0-0", true], - [">1.0.1", "1.0.1", false], - [">1.0.1", "1.0.1-0", false], - [">1.0.1", "1.0.0", false], - [">1.0.1", "1.0.0-0", false], - [">1.0.1", "0.0.0", false], - [">1.0.1", "0.0.0-0", false], - [">1.0.1-0", "2.0.0", true], - [">1.0.1-0", "2.0.0-0", true], - [">1.0.1-0", "1.1.0", true], - [">1.0.1-0", "1.1.0-0", true], - [">1.0.1-0", "1.0.1", true], - [">1.0.1-0", "1.0.1-0", false], - [">1.0.1-0", "1.0.0", false], - [">1.0.1-0", "1.0.0-0", false], - [">1.0.1-0", "0.0.0", false], - [">1.0.1-0", "0.0.0-0", false], - [">1.0.0", "2.0.0", true], - [">1.0.0", "2.0.0-0", true], - [">1.0.0", "1.1.0", true], - [">1.0.0", "1.1.0-0", true], - [">1.0.0", "1.0.1", true], - [">1.0.0", "1.0.1-0", true], - [">1.0.0", "1.0.0", false], - [">1.0.0", "1.0.0-0", false], - [">1.0.0", "0.0.0", false], - [">1.0.0", "0.0.0-0", false], - [">1.0.0-0", "2.0.0", true], - [">1.0.0-0", "2.0.0-0", true], - [">1.0.0-0", "1.1.0", true], - [">1.0.0-0", "1.1.0-0", true], - [">1.0.0-0", "1.0.1", true], - [">1.0.0-0", "1.0.1-0", true], - [">1.0.0-0", "1.0.0", true], - [">1.0.0-0", "1.0.0-0", false], - [">1.0.0-0", "0.0.0", false], - [">1.0.0-0", "0.0.0-0", false], - - // >= wildcard major (matches everything) - [">=*", "2.0.0", true], - [">=*", "2.0.0-0", true], - [">=*", "1.1.0", true], - [">=*", "1.1.0-0", true], - [">=*", "1.0.1", true], - [">=*", "1.0.1-0", true], - [">=*", "1.0.0", true], - [">=*", "1.0.0-0", true], - [">=*", "0.0.0", true], - [">=*", "0.0.0-0", true], - - // >= wildcard minor - [">=1", "2.0.0", true], - [">=1", "2.0.0-0", true], - [">=1", "1.1.0", true], - [">=1", "1.1.0-0", true], - [">=1", "1.0.1", true], - [">=1", "1.0.1-0", true], - [">=1", "1.0.0", true], - [">=1", "1.0.0-0", true], - [">=1", "0.0.0", false], - [">=1", "0.0.0-0", false], - - // >= wildcard patch - [">=1.1", "2.0.0", true], - [">=1.1", "2.0.0-0", true], - [">=1.1", "1.1.0", true], - [">=1.1", "1.1.0-0", true], - [">=1.1", "1.0.1", false], - [">=1.1", "1.0.1-0", false], - [">=1.1", "1.0.0", false], - [">=1.1", "1.0.0-0", false], - [">=1.1", "0.0.0", false], - [">=1.1", "0.0.0-0", false], - [">=1.0", "2.0.0", true], - [">=1.0", "2.0.0-0", true], - [">=1.0", "1.1.0", true], - [">=1.0", "1.1.0-0", true], - [">=1.0", "1.0.1", true], - [">=1.0", "1.0.1-0", true], - [">=1.0", "1.0.0", true], - [">=1.0", "1.0.0-0", true], - [">=1.0", "0.0.0", false], - [">=1.0", "0.0.0-0", false], - - // >= exact - [">=1.1.0", "2.0.0", true], - [">=1.1.0", "2.0.0-0", true], - [">=1.1.0", "1.1.0", true], - [">=1.1.0", "1.1.0-0", false], - [">=1.1.0", "1.0.1", false], - [">=1.1.0", "1.0.1-0", false], - [">=1.1.0", "1.0.0", false], - [">=1.1.0", "1.0.0-0", false], - [">=1.1.0", "0.0.0", false], - [">=1.1.0", "0.0.0-0", false], - [">=1.1.0-0", "2.0.0", true], - [">=1.1.0-0", "2.0.0-0", true], - [">=1.1.0-0", "1.1.0", true], - [">=1.1.0-0", "1.1.0-0", true], - [">=1.1.0-0", "1.0.1", false], - [">=1.1.0-0", "1.0.1-0", false], - [">=1.1.0-0", "1.0.0", false], - [">=1.1.0-0", "1.0.0-0", false], - [">=1.1.0-0", "0.0.0", false], - [">=1.1.0-0", "0.0.0-0", false], - [">=1.0.1", "2.0.0", true], - [">=1.0.1", "2.0.0-0", true], - [">=1.0.1", "1.1.0", true], - [">=1.0.1", "1.1.0-0", true], - [">=1.0.1", "1.0.1", true], - [">=1.0.1", "1.0.1-0", false], - [">=1.0.1", "1.0.0", false], - [">=1.0.1", "1.0.0-0", false], - [">=1.0.1", "0.0.0", false], - [">=1.0.1", "0.0.0-0", false], - [">=1.0.1-0", "2.0.0", true], - [">=1.0.1-0", "2.0.0-0", true], - [">=1.0.1-0", "1.1.0", true], - [">=1.0.1-0", "1.1.0-0", true], - [">=1.0.1-0", "1.0.1", true], - [">=1.0.1-0", "1.0.1-0", true], - [">=1.0.1-0", "1.0.0", false], - [">=1.0.1-0", "1.0.0-0", false], - [">=1.0.1-0", "0.0.0", false], - [">=1.0.1-0", "0.0.0-0", false], - [">=1.0.0", "2.0.0", true], - [">=1.0.0", "2.0.0-0", true], - [">=1.0.0", "1.1.0", true], - [">=1.0.0", "1.1.0-0", true], - [">=1.0.0", "1.0.1", true], - [">=1.0.0", "1.0.1-0", true], - [">=1.0.0", "1.0.0", true], - [">=1.0.0", "1.0.0-0", false], - [">=1.0.0", "0.0.0", false], - [">=1.0.0", "0.0.0-0", false], - [">=1.0.0-0", "2.0.0", true], - [">=1.0.0-0", "2.0.0-0", true], - [">=1.0.0-0", "1.1.0", true], - [">=1.0.0-0", "1.1.0-0", true], - [">=1.0.0-0", "1.0.1", true], - [">=1.0.0-0", "1.0.1-0", true], - [">=1.0.0-0", "1.0.0", true], - [">=1.0.0-0", "1.0.0-0", true], - [">=1.0.0-0", "0.0.0", false], - [">=1.0.0-0", "0.0.0-0", false], - - // < wildcard major (matches nothing) - ["<*", "2.0.0", false], - ["<*", "2.0.0-0", false], - ["<*", "1.1.0", false], - ["<*", "1.1.0-0", false], - ["<*", "1.0.1", false], - ["<*", "1.0.1-0", false], - ["<*", "1.0.0", false], - ["<*", "1.0.0-0", false], - ["<*", "0.0.0", false], - ["<*", "0.0.0-0", false], - - // < wildcard minor - ["<1", "2.0.0", false], - ["<1", "2.0.0-0", false], - ["<1", "1.1.0", false], - ["<1", "1.1.0-0", false], - ["<1", "1.0.1", false], - ["<1", "1.0.1-0", false], - ["<1", "1.0.0", false], - ["<1", "1.0.0-0", false], - ["<1", "0.0.0", true], - ["<1", "0.0.0-0", true], - - // < wildcard patch - ["<1.1", "2.0.0", false], - ["<1.1", "2.0.0-0", false], - ["<1.1", "1.1.0", false], - ["<1.1", "1.1.0-0", false], - ["<1.1", "1.0.1", true], - ["<1.1", "1.0.1-0", true], - ["<1.1", "1.0.0", true], - ["<1.1", "1.0.0-0", true], - ["<1.1", "0.0.0", true], - ["<1.1", "0.0.0-0", true], - ["<1.0", "2.0.0", false], - ["<1.0", "2.0.0-0", false], - ["<1.0", "1.1.0", false], - ["<1.0", "1.1.0-0", false], - ["<1.0", "1.0.1", false], - ["<1.0", "1.0.1-0", false], - ["<1.0", "1.0.0", false], - ["<1.0", "1.0.0-0", false], - ["<1.0", "0.0.0", true], - ["<1.0", "0.0.0-0", true], - - // < exact - ["<1.1.0", "2.0.0", false], - ["<1.1.0", "2.0.0-0", false], - ["<1.1.0", "1.1.0", false], - ["<1.1.0", "1.1.0-0", true], - ["<1.1.0", "1.0.1", true], - ["<1.1.0", "1.0.1-0", true], - ["<1.1.0", "1.0.0", true], - ["<1.1.0", "1.0.0-0", true], - ["<1.1.0", "0.0.0", true], - ["<1.1.0", "0.0.0-0", true], - ["<1.1.0-0", "2.0.0", false], - ["<1.1.0-0", "2.0.0-0", false], - ["<1.1.0-0", "1.1.0", false], - ["<1.1.0-0", "1.1.0-0", false], - ["<1.1.0-0", "1.0.1", true], - ["<1.1.0-0", "1.0.1-0", true], - ["<1.1.0-0", "1.0.0", true], - ["<1.1.0-0", "1.0.0-0", true], - ["<1.1.0-0", "0.0.0", true], - ["<1.1.0-0", "0.0.0-0", true], - ["<1.0.1", "2.0.0", false], - ["<1.0.1", "2.0.0-0", false], - ["<1.0.1", "1.1.0", false], - ["<1.0.1", "1.1.0-0", false], - ["<1.0.1", "1.0.1", false], - ["<1.0.1", "1.0.1-0", true], - ["<1.0.1", "1.0.0", true], - ["<1.0.1", "1.0.0-0", true], - ["<1.0.1", "0.0.0", true], - ["<1.0.1", "0.0.0-0", true], - ["<1.0.1-0", "2.0.0", false], - ["<1.0.1-0", "2.0.0-0", false], - ["<1.0.1-0", "1.1.0", false], - ["<1.0.1-0", "1.1.0-0", false], - ["<1.0.1-0", "1.0.1", false], - ["<1.0.1-0", "1.0.1-0", false], - ["<1.0.1-0", "1.0.0", true], - ["<1.0.1-0", "1.0.0-0", true], - ["<1.0.1-0", "0.0.0", true], - ["<1.0.1-0", "0.0.0-0", true], - ["<1.0.0", "2.0.0", false], - ["<1.0.0", "2.0.0-0", false], - ["<1.0.0", "1.1.0", false], - ["<1.0.0", "1.1.0-0", false], - ["<1.0.0", "1.0.1", false], - ["<1.0.0", "1.0.1-0", false], - ["<1.0.0", "1.0.0", false], - ["<1.0.0", "1.0.0-0", true], - ["<1.0.0", "0.0.0", true], - ["<1.0.0", "0.0.0-0", true], - ["<1.0.0-0", "2.0.0", false], - ["<1.0.0-0", "2.0.0-0", false], - ["<1.0.0-0", "1.1.0", false], - ["<1.0.0-0", "1.1.0-0", false], - ["<1.0.0-0", "1.0.1", false], - ["<1.0.0-0", "1.0.1-0", false], - ["<1.0.0-0", "1.0.0", false], - ["<1.0.0-0", "1.0.0-0", false], - ["<1.0.0-0", "0.0.0", true], - ["<1.0.0-0", "0.0.0-0", true], - - // <= wildcard major (matches everything) - ["<=*", "2.0.0", true], - ["<=*", "2.0.0-0", true], - ["<=*", "1.1.0", true], - ["<=*", "1.1.0-0", true], - ["<=*", "1.0.1", true], - ["<=*", "1.0.1-0", true], - ["<=*", "1.0.0", true], - ["<=*", "1.0.0-0", true], - ["<=*", "0.0.0", true], - ["<=*", "0.0.0-0", true], - - // <= wildcard minor - ["<=1", "2.0.0", false], - ["<=1", "2.0.0-0", false], - ["<=1", "1.1.0", true], - ["<=1", "1.1.0-0", true], - ["<=1", "1.0.1", true], - ["<=1", "1.0.1-0", true], - ["<=1", "1.0.0", true], - ["<=1", "1.0.0-0", true], - ["<=1", "0.0.0", true], - ["<=1", "0.0.0-0", true], - - // <= wildcard patch - ["<=1.1", "2.0.0", false], - ["<=1.1", "2.0.0-0", false], - ["<=1.1", "1.1.0", true], - ["<=1.1", "1.1.0-0", true], - ["<=1.1", "1.0.1", true], - ["<=1.1", "1.0.1-0", true], - ["<=1.1", "1.0.0", true], - ["<=1.1", "1.0.0-0", true], - ["<=1.1", "0.0.0", true], - ["<=1.1", "0.0.0-0", true], - ["<=1.0", "2.0.0", false], - ["<=1.0", "2.0.0-0", false], - ["<=1.0", "1.1.0", false], - ["<=1.0", "1.1.0-0", false], - ["<=1.0", "1.0.1", true], - ["<=1.0", "1.0.1-0", true], - ["<=1.0", "1.0.0", true], - ["<=1.0", "1.0.0-0", true], - ["<=1.0", "0.0.0", true], - ["<=1.0", "0.0.0-0", true], - - // <= exact - ["<=1.1.0", "2.0.0", false], - ["<=1.1.0", "2.0.0-0", false], - ["<=1.1.0", "1.1.0", true], - ["<=1.1.0", "1.1.0-0", true], - ["<=1.1.0", "1.0.1", true], - ["<=1.1.0", "1.0.1-0", true], - ["<=1.1.0", "1.0.0", true], - ["<=1.1.0", "1.0.0-0", true], - ["<=1.1.0", "0.0.0", true], - ["<=1.1.0", "0.0.0-0", true], - ["<=1.1.0-0", "2.0.0", false], - ["<=1.1.0-0", "2.0.0-0", false], - ["<=1.1.0-0", "1.1.0", false], - ["<=1.1.0-0", "1.1.0-0", true], - ["<=1.1.0-0", "1.0.1", true], - ["<=1.1.0-0", "1.0.1-0", true], - ["<=1.1.0-0", "1.0.0", true], - ["<=1.1.0-0", "1.0.0-0", true], - ["<=1.1.0-0", "0.0.0", true], - ["<=1.1.0-0", "0.0.0-0", true], - ["<=1.0.1", "2.0.0", false], - ["<=1.0.1", "2.0.0-0", false], - ["<=1.0.1", "1.1.0", false], - ["<=1.0.1", "1.1.0-0", false], - ["<=1.0.1", "1.0.1", true], - ["<=1.0.1", "1.0.1-0", true], - ["<=1.0.1", "1.0.0", true], - ["<=1.0.1", "1.0.0-0", true], - ["<=1.0.1", "0.0.0", true], - ["<=1.0.1", "0.0.0-0", true], - ["<=1.0.1-0", "2.0.0", false], - ["<=1.0.1-0", "2.0.0-0", false], - ["<=1.0.1-0", "1.1.0", false], - ["<=1.0.1-0", "1.1.0-0", false], - ["<=1.0.1-0", "1.0.1", false], - ["<=1.0.1-0", "1.0.1-0", true], - ["<=1.0.1-0", "1.0.0", true], - ["<=1.0.1-0", "1.0.0-0", true], - ["<=1.0.1-0", "0.0.0", true], - ["<=1.0.1-0", "0.0.0-0", true], - ["<=1.0.0", "2.0.0", false], - ["<=1.0.0", "2.0.0-0", false], - ["<=1.0.0", "1.1.0", false], - ["<=1.0.0", "1.1.0-0", false], - ["<=1.0.0", "1.0.1", false], - ["<=1.0.0", "1.0.1-0", false], - ["<=1.0.0", "1.0.0", true], - ["<=1.0.0", "1.0.0-0", true], - ["<=1.0.0", "0.0.0", true], - ["<=1.0.0", "0.0.0-0", true], - ["<=1.0.0-0", "2.0.0", false], - ["<=1.0.0-0", "2.0.0-0", false], - ["<=1.0.0-0", "1.1.0", false], - ["<=1.0.0-0", "1.1.0-0", false], - ["<=1.0.0-0", "1.0.1", false], - ["<=1.0.0-0", "1.0.1-0", false], - ["<=1.0.0-0", "1.0.0", false], - ["<=1.0.0-0", "1.0.0-0", true], - ["<=1.0.0-0", "0.0.0", true], - ["<=1.0.0-0", "0.0.0-0", true], - - // https://github.com/microsoft/TypeScript/issues/50909 - [">4.8", "4.9.0-beta", true], - [">=4.9", "4.9.0-beta", true], - ["<4.9", "4.9.0-beta", false], - ["<=4.8", "4.9.0-beta", false], - ]); - theory("conjunctions", assertRange, [ - [">1.0.0 <2.0.0", "1.0.1", true], - [">1.0.0 <2.0.0", "2.0.0", false], - [">1.0.0 <2.0.0", "1.0.0", false], - [">1 >2", "3.0.0", true], - ]); - theory("disjunctions", assertRange, [ - [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "1.0.0", true], - [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "2.0.0", false], - [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "3.0.0", true], - ]); - theory("hyphen", assertRange, [ - ["1.0.0 - 2.0.0", "1.0.0", true], - ["1.0.0 - 2.0.0", "2.0.0", true], - ["1.0.0 - 2.0.0", "3.0.0", false], - ]); - theory("tilde", assertRange, [ - ["~0", "0.0.0", true], - ["~0", "0.1.0", true], - ["~0", "0.1.2", true], - ["~0", "0.1.9", true], - ["~0", "1.0.0", false], - ["~0.1", "0.1.0", true], - ["~0.1", "0.1.2", true], - ["~0.1", "0.1.9", true], - ["~0.1", "0.2.0", false], - ["~0.1.2", "0.1.2", true], - ["~0.1.2", "0.1.9", true], - ["~0.1.2", "0.2.0", false], - ["~1", "1.0.0", true], - ["~1", "1.2.0", true], - ["~1", "1.2.3", true], - ["~1", "1.2.0", true], - ["~1", "1.2.3", true], - ["~1", "0.0.0", false], - ["~1", "2.0.0", false], - ["~1.2", "1.2.0", true], - ["~1.2", "1.2.3", true], - ["~1.2", "1.1.0", false], - ["~1.2", "1.3.0", false], - ["~1.2.3", "1.2.3", true], - ["~1.2.3", "1.2.9", true], - ["~1.2.3", "1.1.0", false], - ["~1.2.3", "1.3.0", false], - ]); - theory("caret", assertRange, [ - ["^0", "0.0.0", true], - ["^0", "0.1.0", true], - ["^0", "0.9.0", true], - ["^0", "0.1.2", true], - ["^0", "0.1.9", true], - ["^0", "1.0.0", false], - ["^0.1", "0.1.0", true], - ["^0.1", "0.1.2", true], - ["^0.1", "0.1.9", true], - ["^0.1.2", "0.1.2", true], - ["^0.1.2", "0.1.9", true], - ["^0.1.2", "0.0.0", false], - ["^0.1.2", "0.2.0", false], - ["^0.1.2", "1.0.0", false], - ["^1", "1.0.0", true], - ["^1", "1.2.0", true], - ["^1", "1.2.3", true], - ["^1", "1.9.0", true], - ["^1", "0.0.0", false], - ["^1", "2.0.0", false], - ["^1.2", "1.2.0", true], - ["^1.2", "1.2.3", true], - ["^1.2", "1.9.0", true], - ["^1.2", "1.1.0", false], - ["^1.2", "2.0.0", false], - ["^1.2.3", "1.2.3", true], - ["^1.2.3", "1.9.0", true], - ["^1.2.3", "1.2.2", false], - ["^1.2.3", "2.0.0", false], - ]); }); + + function assertVersionRange(version: string, good: string[], bad: string[]): () => void { + return () => { + const range = ts.VersionRange.tryParse(version)!; + assert(range); + for (const g of good) { + assert.isTrue(range.test(g), g); + } + for (const b of bad) { + assert.isFalse(range.test(b), b); + } + }; + } + + it("< works", assertVersionRange("<3.8.0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); + it("<= works", assertVersionRange("<=3.8.0", ["3.6", "3.7", "3.8"], ["3.9", "4.0"])); + it("> works", assertVersionRange(">3.8.0", ["3.9", "4.0"], ["3.6", "3.7", "3.8"])); + it(">= works", assertVersionRange(">=3.8.0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); + + it("< works with prerelease", assertVersionRange("<3.8.0-0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); + it("<= works with prerelease", assertVersionRange("<=3.8.0-0", ["3.6", "3.7"], ["3.8", "3.9", "4.0"])); + it("> works with prerelease", assertVersionRange(">3.8.0-0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); + it(">= works with prerelease", assertVersionRange(">=3.8.0-0", ["3.8", "3.9", "4.0"], ["3.6", "3.7"])); + + function assertRange(rangeText: string, versionText: string, inRange: boolean) { + const range = new ts.VersionRange(rangeText); + const version = new ts.Version(versionText); + assert.strictEqual(range.test(version), inRange, `Expected version '${version}' ${inRange ? `to be` : `to not be`} in range '${rangeText}' (${range})`); + } + + theory("comparators", assertRange, [ + // empty (matches everything) + ["", "2.0.0", true], + ["", "2.0.0-0", true], + ["", "1.1.0", true], + ["", "1.1.0-0", true], + ["", "1.0.1", true], + ["", "1.0.1-0", true], + ["", "1.0.0", true], + ["", "1.0.0-0", true], + ["", "0.0.0", true], + ["", "0.0.0-0", true], + + // wildcard major (matches everything) + ["*", "2.0.0", true], + ["*", "2.0.0-0", true], + ["*", "1.1.0", true], + ["*", "1.1.0-0", true], + ["*", "1.0.1", true], + ["*", "1.0.1-0", true], + ["*", "1.0.0", true], + ["*", "1.0.0-0", true], + ["*", "0.0.0", true], + ["*", "0.0.0-0", true], + + // wildcard minor + ["1", "2.0.0", false], + ["1", "2.0.0-0", false], + ["1", "1.1.0", true], + ["1", "1.1.0-0", true], + ["1", "1.0.1", true], + ["1", "1.0.1-0", true], + ["1", "1.0.0", true], + ["1", "1.0.0-0", true], + ["1", "0.0.0", false], + ["1", "0.0.0-0", false], + + // wildcard patch + ["1.1", "2.0.0", false], + ["1.1", "2.0.0-0", false], + ["1.1", "1.1.0", true], + ["1.1", "1.1.0-0", true], + ["1.1", "1.0.1", false], + ["1.1", "1.0.1-0", false], + ["1.1", "1.0.0", false], + ["1.1", "1.0.0-0", false], + ["1.1", "0.0.0", false], + ["1.1", "0.0.0-0", false], + ["1.0", "2.0.0", false], + ["1.0", "2.0.0-0", false], + ["1.0", "1.1.0", false], + ["1.0", "1.1.0-0", false], + ["1.0", "1.0.1", true], + ["1.0", "1.0.1-0", true], + ["1.0", "1.0.0", true], + ["1.0", "1.0.0-0", true], + ["1.0", "0.0.0", false], + ["1.0", "0.0.0-0", false], + + // exact + ["1.1.0", "2.0.0", false], + ["1.1.0", "2.0.0-0", false], + ["1.1.0", "1.1.0", true], + ["1.1.0", "1.1.0-0", false], + ["1.1.0", "1.0.1", false], + ["1.1.0", "1.0.1-0", false], + ["1.1.0", "1.0.0-0", false], + ["1.1.0", "1.0.0", false], + ["1.1.0", "0.0.0", false], + ["1.1.0", "0.0.0-0", false], + ["1.1.0-0", "2.0.0", false], + ["1.1.0-0", "2.0.0-0", false], + ["1.1.0-0", "1.1.0", false], + ["1.1.0-0", "1.1.0-0", true], + ["1.1.0-0", "1.0.1", false], + ["1.1.0-0", "1.0.1-0", false], + ["1.1.0-0", "1.0.0-0", false], + ["1.1.0-0", "1.0.0", false], + ["1.1.0-0", "0.0.0", false], + ["1.1.0-0", "0.0.0-0", false], + ["1.0.1", "2.0.0", false], + ["1.0.1", "2.0.0-0", false], + ["1.0.1", "1.1.0", false], + ["1.0.1", "1.1.0-0", false], + ["1.0.1", "1.0.1", true], + ["1.0.1", "1.0.1-0", false], + ["1.0.1", "1.0.0-0", false], + ["1.0.1", "1.0.0", false], + ["1.0.1", "0.0.0", false], + ["1.0.1", "0.0.0-0", false], + ["1.0.1-0", "2.0.0", false], + ["1.0.1-0", "2.0.0-0", false], + ["1.0.1-0", "1.1.0", false], + ["1.0.1-0", "1.1.0-0", false], + ["1.0.1-0", "1.0.1", false], + ["1.0.1-0", "1.0.1-0", true], + ["1.0.1-0", "1.0.0-0", false], + ["1.0.1-0", "1.0.0", false], + ["1.0.1-0", "0.0.0", false], + ["1.0.1-0", "0.0.0-0", false], + ["1.0.0", "2.0.0", false], + ["1.0.0", "2.0.0-0", false], + ["1.0.0", "1.1.0", false], + ["1.0.0", "1.1.0-0", false], + ["1.0.0", "1.0.1", false], + ["1.0.0", "1.0.1-0", false], + ["1.0.0", "1.0.0-0", false], + ["1.0.0", "1.0.0", true], + ["1.0.0", "0.0.0", false], + ["1.0.0", "0.0.0-0", false], + ["1.0.0-0", "2.0.0", false], + ["1.0.0-0", "2.0.0-0", false], + ["1.0.0-0", "1.1.0", false], + ["1.0.0-0", "1.1.0-0", false], + ["1.0.0-0", "1.0.1", false], + ["1.0.0-0", "1.0.1-0", false], + ["1.0.0-0", "1.0.0", false], + ["1.0.0-0", "1.0.0-0", true], + + // = wildcard major (matches everything) + ["=*", "2.0.0", true], + ["=*", "2.0.0-0", true], + ["=*", "1.1.0", true], + ["=*", "1.1.0-0", true], + ["=*", "1.0.1", true], + ["=*", "1.0.1-0", true], + ["=*", "1.0.0", true], + ["=*", "1.0.0-0", true], + ["=*", "0.0.0", true], + ["=*", "0.0.0-0", true], + + // = wildcard minor + ["=1", "2.0.0", false], + ["=1", "2.0.0-0", false], + ["=1", "1.1.0", true], + ["=1", "1.1.0-0", true], + ["=1", "1.0.1", true], + ["=1", "1.0.1-0", true], + ["=1", "1.0.0", true], + ["=1", "1.0.0-0", true], + ["=1", "0.0.0", false], + ["=1", "0.0.0-0", false], + + // = wildcard patch + ["=1.1", "2.0.0", false], + ["=1.1", "2.0.0-0", false], + ["=1.1", "1.1.0", true], + ["=1.1", "1.1.0-0", true], + ["=1.1", "1.0.1", false], + ["=1.1", "1.0.1-0", false], + ["=1.1", "1.0.0", false], + ["=1.1", "1.0.0-0", false], + ["=1.1", "0.0.0", false], + ["=1.1", "0.0.0-0", false], + ["=1.0", "2.0.0", false], + ["=1.0", "2.0.0-0", false], + ["=1.0", "1.1.0", false], + ["=1.0", "1.1.0-0", false], + ["=1.0", "1.0.1", true], + ["=1.0", "1.0.1-0", true], + ["=1.0", "1.0.0", true], + ["=1.0", "1.0.0-0", true], + ["=1.0", "0.0.0", false], + ["=1.0", "0.0.0-0", false], + + // = exact + ["=1.1.0", "2.0.0", false], + ["=1.1.0", "2.0.0-0", false], + ["=1.1.0", "1.1.0", true], + ["=1.1.0", "1.1.0-0", false], + ["=1.1.0", "1.0.1", false], + ["=1.1.0", "1.0.1-0", false], + ["=1.1.0", "1.0.0-0", false], + ["=1.1.0", "1.0.0", false], + ["=1.1.0", "0.0.0", false], + ["=1.1.0", "0.0.0-0", false], + ["=1.1.0-0", "2.0.0", false], + ["=1.1.0-0", "2.0.0-0", false], + ["=1.1.0-0", "1.1.0", false], + ["=1.1.0-0", "1.1.0-0", true], + ["=1.1.0-0", "1.0.1", false], + ["=1.1.0-0", "1.0.1-0", false], + ["=1.1.0-0", "1.0.0-0", false], + ["=1.1.0-0", "1.0.0", false], + ["=1.1.0-0", "0.0.0", false], + ["=1.1.0-0", "0.0.0-0", false], + ["=1.0.1", "2.0.0", false], + ["=1.0.1", "2.0.0-0", false], + ["=1.0.1", "1.1.0", false], + ["=1.0.1", "1.1.0-0", false], + ["=1.0.1", "1.0.1", true], + ["=1.0.1", "1.0.1-0", false], + ["=1.0.1", "1.0.0-0", false], + ["=1.0.1", "1.0.0", false], + ["=1.0.1", "0.0.0", false], + ["=1.0.1", "0.0.0-0", false], + ["=1.0.1-0", "2.0.0", false], + ["=1.0.1-0", "2.0.0-0", false], + ["=1.0.1-0", "1.1.0", false], + ["=1.0.1-0", "1.1.0-0", false], + ["=1.0.1-0", "1.0.1", false], + ["=1.0.1-0", "1.0.1-0", true], + ["=1.0.1-0", "1.0.0-0", false], + ["=1.0.1-0", "1.0.0", false], + ["=1.0.1-0", "0.0.0", false], + ["=1.0.1-0", "0.0.0-0", false], + ["=1.0.0", "2.0.0", false], + ["=1.0.0", "2.0.0-0", false], + ["=1.0.0", "1.1.0", false], + ["=1.0.0", "1.1.0-0", false], + ["=1.0.0", "1.0.1", false], + ["=1.0.0", "1.0.1-0", false], + ["=1.0.0", "1.0.0-0", false], + ["=1.0.0", "1.0.0", true], + ["=1.0.0", "0.0.0", false], + ["=1.0.0", "0.0.0-0", false], + ["=1.0.0-0", "2.0.0", false], + ["=1.0.0-0", "2.0.0-0", false], + ["=1.0.0-0", "1.1.0", false], + ["=1.0.0-0", "1.1.0-0", false], + ["=1.0.0-0", "1.0.1", false], + ["=1.0.0-0", "1.0.1-0", false], + ["=1.0.0-0", "1.0.0", false], + ["=1.0.0-0", "1.0.0-0", true], + + // > wildcard major (matches nothing) + [">*", "2.0.0", false], + [">*", "2.0.0-0", false], + [">*", "1.1.0", false], + [">*", "1.1.0-0", false], + [">*", "1.0.1", false], + [">*", "1.0.1-0", false], + [">*", "1.0.0", false], + [">*", "1.0.0-0", false], + [">*", "0.0.0", false], + [">*", "0.0.0-0", false], + + // > wildcard minor + [">1", "2.0.0", true], + [">1", "2.0.0-0", true], + [">1", "1.1.0", false], + [">1", "1.1.0-0", false], + [">1", "1.0.1", false], + [">1", "1.0.1-0", false], + [">1", "1.0.0", false], + [">1", "1.0.0-0", false], + [">1", "0.0.0", false], + [">1", "0.0.0-0", false], + + // > wildcard patch + [">1.1", "2.0.0", true], + [">1.1", "2.0.0-0", true], + [">1.1", "1.1.0", false], + [">1.1", "1.1.0-0", false], + [">1.1", "1.0.1", false], + [">1.1", "1.0.1-0", false], + [">1.1", "1.0.0", false], + [">1.1", "1.0.0-0", false], + [">1.1", "0.0.0", false], + [">1.1", "0.0.0-0", false], + [">1.0", "2.0.0", true], + [">1.0", "2.0.0-0", true], + [">1.0", "1.1.0", true], + [">1.0", "1.1.0-0", true], + [">1.0", "1.0.1", false], + [">1.0", "1.0.1-0", false], + [">1.0", "1.0.0", false], + [">1.0", "1.0.0-0", false], + [">1.0", "0.0.0", false], + [">1.0", "0.0.0-0", false], + + // > exact + [">1.1.0", "2.0.0", true], + [">1.1.0", "2.0.0-0", true], + [">1.1.0", "1.1.0", false], + [">1.1.0", "1.1.0-0", false], + [">1.1.0", "1.0.1", false], + [">1.1.0", "1.0.1-0", false], + [">1.1.0", "1.0.0", false], + [">1.1.0", "1.0.0-0", false], + [">1.1.0", "0.0.0", false], + [">1.1.0", "0.0.0-0", false], + [">1.1.0-0", "2.0.0", true], + [">1.1.0-0", "2.0.0-0", true], + [">1.1.0-0", "1.1.0", true], + [">1.1.0-0", "1.1.0-0", false], + [">1.1.0-0", "1.0.1", false], + [">1.1.0-0", "1.0.1-0", false], + [">1.1.0-0", "1.0.0", false], + [">1.1.0-0", "1.0.0-0", false], + [">1.1.0-0", "0.0.0", false], + [">1.1.0-0", "0.0.0-0", false], + [">1.0.1", "2.0.0", true], + [">1.0.1", "2.0.0-0", true], + [">1.0.1", "1.1.0", true], + [">1.0.1", "1.1.0-0", true], + [">1.0.1", "1.0.1", false], + [">1.0.1", "1.0.1-0", false], + [">1.0.1", "1.0.0", false], + [">1.0.1", "1.0.0-0", false], + [">1.0.1", "0.0.0", false], + [">1.0.1", "0.0.0-0", false], + [">1.0.1-0", "2.0.0", true], + [">1.0.1-0", "2.0.0-0", true], + [">1.0.1-0", "1.1.0", true], + [">1.0.1-0", "1.1.0-0", true], + [">1.0.1-0", "1.0.1", true], + [">1.0.1-0", "1.0.1-0", false], + [">1.0.1-0", "1.0.0", false], + [">1.0.1-0", "1.0.0-0", false], + [">1.0.1-0", "0.0.0", false], + [">1.0.1-0", "0.0.0-0", false], + [">1.0.0", "2.0.0", true], + [">1.0.0", "2.0.0-0", true], + [">1.0.0", "1.1.0", true], + [">1.0.0", "1.1.0-0", true], + [">1.0.0", "1.0.1", true], + [">1.0.0", "1.0.1-0", true], + [">1.0.0", "1.0.0", false], + [">1.0.0", "1.0.0-0", false], + [">1.0.0", "0.0.0", false], + [">1.0.0", "0.0.0-0", false], + [">1.0.0-0", "2.0.0", true], + [">1.0.0-0", "2.0.0-0", true], + [">1.0.0-0", "1.1.0", true], + [">1.0.0-0", "1.1.0-0", true], + [">1.0.0-0", "1.0.1", true], + [">1.0.0-0", "1.0.1-0", true], + [">1.0.0-0", "1.0.0", true], + [">1.0.0-0", "1.0.0-0", false], + [">1.0.0-0", "0.0.0", false], + [">1.0.0-0", "0.0.0-0", false], + + // >= wildcard major (matches everything) + [">=*", "2.0.0", true], + [">=*", "2.0.0-0", true], + [">=*", "1.1.0", true], + [">=*", "1.1.0-0", true], + [">=*", "1.0.1", true], + [">=*", "1.0.1-0", true], + [">=*", "1.0.0", true], + [">=*", "1.0.0-0", true], + [">=*", "0.0.0", true], + [">=*", "0.0.0-0", true], + + // >= wildcard minor + [">=1", "2.0.0", true], + [">=1", "2.0.0-0", true], + [">=1", "1.1.0", true], + [">=1", "1.1.0-0", true], + [">=1", "1.0.1", true], + [">=1", "1.0.1-0", true], + [">=1", "1.0.0", true], + [">=1", "1.0.0-0", true], + [">=1", "0.0.0", false], + [">=1", "0.0.0-0", false], + + // >= wildcard patch + [">=1.1", "2.0.0", true], + [">=1.1", "2.0.0-0", true], + [">=1.1", "1.1.0", true], + [">=1.1", "1.1.0-0", true], + [">=1.1", "1.0.1", false], + [">=1.1", "1.0.1-0", false], + [">=1.1", "1.0.0", false], + [">=1.1", "1.0.0-0", false], + [">=1.1", "0.0.0", false], + [">=1.1", "0.0.0-0", false], + [">=1.0", "2.0.0", true], + [">=1.0", "2.0.0-0", true], + [">=1.0", "1.1.0", true], + [">=1.0", "1.1.0-0", true], + [">=1.0", "1.0.1", true], + [">=1.0", "1.0.1-0", true], + [">=1.0", "1.0.0", true], + [">=1.0", "1.0.0-0", true], + [">=1.0", "0.0.0", false], + [">=1.0", "0.0.0-0", false], + + // >= exact + [">=1.1.0", "2.0.0", true], + [">=1.1.0", "2.0.0-0", true], + [">=1.1.0", "1.1.0", true], + [">=1.1.0", "1.1.0-0", false], + [">=1.1.0", "1.0.1", false], + [">=1.1.0", "1.0.1-0", false], + [">=1.1.0", "1.0.0", false], + [">=1.1.0", "1.0.0-0", false], + [">=1.1.0", "0.0.0", false], + [">=1.1.0", "0.0.0-0", false], + [">=1.1.0-0", "2.0.0", true], + [">=1.1.0-0", "2.0.0-0", true], + [">=1.1.0-0", "1.1.0", true], + [">=1.1.0-0", "1.1.0-0", true], + [">=1.1.0-0", "1.0.1", false], + [">=1.1.0-0", "1.0.1-0", false], + [">=1.1.0-0", "1.0.0", false], + [">=1.1.0-0", "1.0.0-0", false], + [">=1.1.0-0", "0.0.0", false], + [">=1.1.0-0", "0.0.0-0", false], + [">=1.0.1", "2.0.0", true], + [">=1.0.1", "2.0.0-0", true], + [">=1.0.1", "1.1.0", true], + [">=1.0.1", "1.1.0-0", true], + [">=1.0.1", "1.0.1", true], + [">=1.0.1", "1.0.1-0", false], + [">=1.0.1", "1.0.0", false], + [">=1.0.1", "1.0.0-0", false], + [">=1.0.1", "0.0.0", false], + [">=1.0.1", "0.0.0-0", false], + [">=1.0.1-0", "2.0.0", true], + [">=1.0.1-0", "2.0.0-0", true], + [">=1.0.1-0", "1.1.0", true], + [">=1.0.1-0", "1.1.0-0", true], + [">=1.0.1-0", "1.0.1", true], + [">=1.0.1-0", "1.0.1-0", true], + [">=1.0.1-0", "1.0.0", false], + [">=1.0.1-0", "1.0.0-0", false], + [">=1.0.1-0", "0.0.0", false], + [">=1.0.1-0", "0.0.0-0", false], + [">=1.0.0", "2.0.0", true], + [">=1.0.0", "2.0.0-0", true], + [">=1.0.0", "1.1.0", true], + [">=1.0.0", "1.1.0-0", true], + [">=1.0.0", "1.0.1", true], + [">=1.0.0", "1.0.1-0", true], + [">=1.0.0", "1.0.0", true], + [">=1.0.0", "1.0.0-0", false], + [">=1.0.0", "0.0.0", false], + [">=1.0.0", "0.0.0-0", false], + [">=1.0.0-0", "2.0.0", true], + [">=1.0.0-0", "2.0.0-0", true], + [">=1.0.0-0", "1.1.0", true], + [">=1.0.0-0", "1.1.0-0", true], + [">=1.0.0-0", "1.0.1", true], + [">=1.0.0-0", "1.0.1-0", true], + [">=1.0.0-0", "1.0.0", true], + [">=1.0.0-0", "1.0.0-0", true], + [">=1.0.0-0", "0.0.0", false], + [">=1.0.0-0", "0.0.0-0", false], + + // < wildcard major (matches nothing) + ["<*", "2.0.0", false], + ["<*", "2.0.0-0", false], + ["<*", "1.1.0", false], + ["<*", "1.1.0-0", false], + ["<*", "1.0.1", false], + ["<*", "1.0.1-0", false], + ["<*", "1.0.0", false], + ["<*", "1.0.0-0", false], + ["<*", "0.0.0", false], + ["<*", "0.0.0-0", false], + + // < wildcard minor + ["<1", "2.0.0", false], + ["<1", "2.0.0-0", false], + ["<1", "1.1.0", false], + ["<1", "1.1.0-0", false], + ["<1", "1.0.1", false], + ["<1", "1.0.1-0", false], + ["<1", "1.0.0", false], + ["<1", "1.0.0-0", false], + ["<1", "0.0.0", true], + ["<1", "0.0.0-0", true], + + // < wildcard patch + ["<1.1", "2.0.0", false], + ["<1.1", "2.0.0-0", false], + ["<1.1", "1.1.0", false], + ["<1.1", "1.1.0-0", false], + ["<1.1", "1.0.1", true], + ["<1.1", "1.0.1-0", true], + ["<1.1", "1.0.0", true], + ["<1.1", "1.0.0-0", true], + ["<1.1", "0.0.0", true], + ["<1.1", "0.0.0-0", true], + ["<1.0", "2.0.0", false], + ["<1.0", "2.0.0-0", false], + ["<1.0", "1.1.0", false], + ["<1.0", "1.1.0-0", false], + ["<1.0", "1.0.1", false], + ["<1.0", "1.0.1-0", false], + ["<1.0", "1.0.0", false], + ["<1.0", "1.0.0-0", false], + ["<1.0", "0.0.0", true], + ["<1.0", "0.0.0-0", true], + + // < exact + ["<1.1.0", "2.0.0", false], + ["<1.1.0", "2.0.0-0", false], + ["<1.1.0", "1.1.0", false], + ["<1.1.0", "1.1.0-0", true], + ["<1.1.0", "1.0.1", true], + ["<1.1.0", "1.0.1-0", true], + ["<1.1.0", "1.0.0", true], + ["<1.1.0", "1.0.0-0", true], + ["<1.1.0", "0.0.0", true], + ["<1.1.0", "0.0.0-0", true], + ["<1.1.0-0", "2.0.0", false], + ["<1.1.0-0", "2.0.0-0", false], + ["<1.1.0-0", "1.1.0", false], + ["<1.1.0-0", "1.1.0-0", false], + ["<1.1.0-0", "1.0.1", true], + ["<1.1.0-0", "1.0.1-0", true], + ["<1.1.0-0", "1.0.0", true], + ["<1.1.0-0", "1.0.0-0", true], + ["<1.1.0-0", "0.0.0", true], + ["<1.1.0-0", "0.0.0-0", true], + ["<1.0.1", "2.0.0", false], + ["<1.0.1", "2.0.0-0", false], + ["<1.0.1", "1.1.0", false], + ["<1.0.1", "1.1.0-0", false], + ["<1.0.1", "1.0.1", false], + ["<1.0.1", "1.0.1-0", true], + ["<1.0.1", "1.0.0", true], + ["<1.0.1", "1.0.0-0", true], + ["<1.0.1", "0.0.0", true], + ["<1.0.1", "0.0.0-0", true], + ["<1.0.1-0", "2.0.0", false], + ["<1.0.1-0", "2.0.0-0", false], + ["<1.0.1-0", "1.1.0", false], + ["<1.0.1-0", "1.1.0-0", false], + ["<1.0.1-0", "1.0.1", false], + ["<1.0.1-0", "1.0.1-0", false], + ["<1.0.1-0", "1.0.0", true], + ["<1.0.1-0", "1.0.0-0", true], + ["<1.0.1-0", "0.0.0", true], + ["<1.0.1-0", "0.0.0-0", true], + ["<1.0.0", "2.0.0", false], + ["<1.0.0", "2.0.0-0", false], + ["<1.0.0", "1.1.0", false], + ["<1.0.0", "1.1.0-0", false], + ["<1.0.0", "1.0.1", false], + ["<1.0.0", "1.0.1-0", false], + ["<1.0.0", "1.0.0", false], + ["<1.0.0", "1.0.0-0", true], + ["<1.0.0", "0.0.0", true], + ["<1.0.0", "0.0.0-0", true], + ["<1.0.0-0", "2.0.0", false], + ["<1.0.0-0", "2.0.0-0", false], + ["<1.0.0-0", "1.1.0", false], + ["<1.0.0-0", "1.1.0-0", false], + ["<1.0.0-0", "1.0.1", false], + ["<1.0.0-0", "1.0.1-0", false], + ["<1.0.0-0", "1.0.0", false], + ["<1.0.0-0", "1.0.0-0", false], + ["<1.0.0-0", "0.0.0", true], + ["<1.0.0-0", "0.0.0-0", true], + + // <= wildcard major (matches everything) + ["<=*", "2.0.0", true], + ["<=*", "2.0.0-0", true], + ["<=*", "1.1.0", true], + ["<=*", "1.1.0-0", true], + ["<=*", "1.0.1", true], + ["<=*", "1.0.1-0", true], + ["<=*", "1.0.0", true], + ["<=*", "1.0.0-0", true], + ["<=*", "0.0.0", true], + ["<=*", "0.0.0-0", true], + + // <= wildcard minor + ["<=1", "2.0.0", false], + ["<=1", "2.0.0-0", false], + ["<=1", "1.1.0", true], + ["<=1", "1.1.0-0", true], + ["<=1", "1.0.1", true], + ["<=1", "1.0.1-0", true], + ["<=1", "1.0.0", true], + ["<=1", "1.0.0-0", true], + ["<=1", "0.0.0", true], + ["<=1", "0.0.0-0", true], + + // <= wildcard patch + ["<=1.1", "2.0.0", false], + ["<=1.1", "2.0.0-0", false], + ["<=1.1", "1.1.0", true], + ["<=1.1", "1.1.0-0", true], + ["<=1.1", "1.0.1", true], + ["<=1.1", "1.0.1-0", true], + ["<=1.1", "1.0.0", true], + ["<=1.1", "1.0.0-0", true], + ["<=1.1", "0.0.0", true], + ["<=1.1", "0.0.0-0", true], + ["<=1.0", "2.0.0", false], + ["<=1.0", "2.0.0-0", false], + ["<=1.0", "1.1.0", false], + ["<=1.0", "1.1.0-0", false], + ["<=1.0", "1.0.1", true], + ["<=1.0", "1.0.1-0", true], + ["<=1.0", "1.0.0", true], + ["<=1.0", "1.0.0-0", true], + ["<=1.0", "0.0.0", true], + ["<=1.0", "0.0.0-0", true], + + // <= exact + ["<=1.1.0", "2.0.0", false], + ["<=1.1.0", "2.0.0-0", false], + ["<=1.1.0", "1.1.0", true], + ["<=1.1.0", "1.1.0-0", true], + ["<=1.1.0", "1.0.1", true], + ["<=1.1.0", "1.0.1-0", true], + ["<=1.1.0", "1.0.0", true], + ["<=1.1.0", "1.0.0-0", true], + ["<=1.1.0", "0.0.0", true], + ["<=1.1.0", "0.0.0-0", true], + ["<=1.1.0-0", "2.0.0", false], + ["<=1.1.0-0", "2.0.0-0", false], + ["<=1.1.0-0", "1.1.0", false], + ["<=1.1.0-0", "1.1.0-0", true], + ["<=1.1.0-0", "1.0.1", true], + ["<=1.1.0-0", "1.0.1-0", true], + ["<=1.1.0-0", "1.0.0", true], + ["<=1.1.0-0", "1.0.0-0", true], + ["<=1.1.0-0", "0.0.0", true], + ["<=1.1.0-0", "0.0.0-0", true], + ["<=1.0.1", "2.0.0", false], + ["<=1.0.1", "2.0.0-0", false], + ["<=1.0.1", "1.1.0", false], + ["<=1.0.1", "1.1.0-0", false], + ["<=1.0.1", "1.0.1", true], + ["<=1.0.1", "1.0.1-0", true], + ["<=1.0.1", "1.0.0", true], + ["<=1.0.1", "1.0.0-0", true], + ["<=1.0.1", "0.0.0", true], + ["<=1.0.1", "0.0.0-0", true], + ["<=1.0.1-0", "2.0.0", false], + ["<=1.0.1-0", "2.0.0-0", false], + ["<=1.0.1-0", "1.1.0", false], + ["<=1.0.1-0", "1.1.0-0", false], + ["<=1.0.1-0", "1.0.1", false], + ["<=1.0.1-0", "1.0.1-0", true], + ["<=1.0.1-0", "1.0.0", true], + ["<=1.0.1-0", "1.0.0-0", true], + ["<=1.0.1-0", "0.0.0", true], + ["<=1.0.1-0", "0.0.0-0", true], + ["<=1.0.0", "2.0.0", false], + ["<=1.0.0", "2.0.0-0", false], + ["<=1.0.0", "1.1.0", false], + ["<=1.0.0", "1.1.0-0", false], + ["<=1.0.0", "1.0.1", false], + ["<=1.0.0", "1.0.1-0", false], + ["<=1.0.0", "1.0.0", true], + ["<=1.0.0", "1.0.0-0", true], + ["<=1.0.0", "0.0.0", true], + ["<=1.0.0", "0.0.0-0", true], + ["<=1.0.0-0", "2.0.0", false], + ["<=1.0.0-0", "2.0.0-0", false], + ["<=1.0.0-0", "1.1.0", false], + ["<=1.0.0-0", "1.1.0-0", false], + ["<=1.0.0-0", "1.0.1", false], + ["<=1.0.0-0", "1.0.1-0", false], + ["<=1.0.0-0", "1.0.0", false], + ["<=1.0.0-0", "1.0.0-0", true], + ["<=1.0.0-0", "0.0.0", true], + ["<=1.0.0-0", "0.0.0-0", true], + + // https://github.com/microsoft/TypeScript/issues/50909 + [">4.8", "4.9.0-beta", true], + [">=4.9", "4.9.0-beta", true], + ["<4.9", "4.9.0-beta", false], + ["<=4.8", "4.9.0-beta", false], + ]); + theory("conjunctions", assertRange, [ + [">1.0.0 <2.0.0", "1.0.1", true], + [">1.0.0 <2.0.0", "2.0.0", false], + [">1.0.0 <2.0.0", "1.0.0", false], + [">1 >2", "3.0.0", true], + ]); + theory("disjunctions", assertRange, [ + [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "1.0.0", true], + [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "2.0.0", false], + [">=1.0.0 <2.0.0 || >=3.0.0 <4.0.0", "3.0.0", true], + ]); + theory("hyphen", assertRange, [ + ["1.0.0 - 2.0.0", "1.0.0", true], + ["1.0.0 - 2.0.0", "2.0.0", true], + ["1.0.0 - 2.0.0", "3.0.0", false], + ]); + theory("tilde", assertRange, [ + ["~0", "0.0.0", true], + ["~0", "0.1.0", true], + ["~0", "0.1.2", true], + ["~0", "0.1.9", true], + ["~0", "1.0.0", false], + ["~0.1", "0.1.0", true], + ["~0.1", "0.1.2", true], + ["~0.1", "0.1.9", true], + ["~0.1", "0.2.0", false], + ["~0.1.2", "0.1.2", true], + ["~0.1.2", "0.1.9", true], + ["~0.1.2", "0.2.0", false], + ["~1", "1.0.0", true], + ["~1", "1.2.0", true], + ["~1", "1.2.3", true], + ["~1", "1.2.0", true], + ["~1", "1.2.3", true], + ["~1", "0.0.0", false], + ["~1", "2.0.0", false], + ["~1.2", "1.2.0", true], + ["~1.2", "1.2.3", true], + ["~1.2", "1.1.0", false], + ["~1.2", "1.3.0", false], + ["~1.2.3", "1.2.3", true], + ["~1.2.3", "1.2.9", true], + ["~1.2.3", "1.1.0", false], + ["~1.2.3", "1.3.0", false], + ]); + theory("caret", assertRange, [ + ["^0", "0.0.0", true], + ["^0", "0.1.0", true], + ["^0", "0.9.0", true], + ["^0", "0.1.2", true], + ["^0", "0.1.9", true], + ["^0", "1.0.0", false], + ["^0.1", "0.1.0", true], + ["^0.1", "0.1.2", true], + ["^0.1", "0.1.9", true], + ["^0.1.2", "0.1.2", true], + ["^0.1.2", "0.1.9", true], + ["^0.1.2", "0.0.0", false], + ["^0.1.2", "0.2.0", false], + ["^0.1.2", "1.0.0", false], + ["^1", "1.0.0", true], + ["^1", "1.2.0", true], + ["^1", "1.2.3", true], + ["^1", "1.9.0", true], + ["^1", "0.0.0", false], + ["^1", "2.0.0", false], + ["^1.2", "1.2.0", true], + ["^1.2", "1.2.3", true], + ["^1.2", "1.9.0", true], + ["^1.2", "1.1.0", false], + ["^1.2", "2.0.0", false], + ["^1.2.3", "1.2.3", true], + ["^1.2.3", "1.9.0", true], + ["^1.2.3", "1.2.2", false], + ["^1.2.3", "2.0.0", false], + ]); }); -} +}); diff --git a/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts index 26ad16d8ebcef..85fc11da92614 100644 --- a/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts +++ b/src/testRunner/unittests/services/cancellableLanguageServiceOperations.ts @@ -1,95 +1,96 @@ -namespace ts { - describe("unittests:: services:: cancellableLanguageServiceOperations", () => { - const file = ` +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; + +describe("unittests:: services:: cancellableLanguageServiceOperations", () => { + const file = ` function foo(): void; function foo(x: T): T; function foo(x?: T): T | void {} foo(f); `; - it("can cancel signature help mid-request", () => { - verifyOperationCancelledAfter(file, 4, service => // Two calls are top-level in services, one is the root type, and the second should be for the parameter type - service.getSignatureHelpItems("file.ts", file.lastIndexOf("f"), emptyOptions)!, r => assert.exists(r.items[0]) - ); - }); + it("can cancel signature help mid-request", () => { + verifyOperationCancelledAfter(file, 4, service => // Two calls are top-level in services, one is the root type, and the second should be for the parameter type + service.getSignatureHelpItems("file.ts", file.lastIndexOf("f"), ts.emptyOptions)!, r => assert.exists(r.items[0]) + ); + }); - it("can cancel find all references mid-request", () => { - verifyOperationCancelledAfter(file, 3, service => // Two calls are top-level in services, one is the root type - service.findReferences("file.ts", file.lastIndexOf("o"))!, r => assert.exists(r[0].definition) - ); - }); + it("can cancel find all references mid-request", () => { + verifyOperationCancelledAfter(file, 3, service => // Two calls are top-level in services, one is the root type + service.findReferences("file.ts", file.lastIndexOf("o"))!, r => assert.exists(r[0].definition) + ); + }); - it("can cancel quick info mid-request", () => { - verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for quickinfo, so the first check is within the checker - service.getQuickInfoAtPosition("file.ts", file.lastIndexOf("o"))!, r => assert.exists(r.displayParts) - ); - }); + it("can cancel quick info mid-request", () => { + verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for quickinfo, so the first check is within the checker + service.getQuickInfoAtPosition("file.ts", file.lastIndexOf("o"))!, r => assert.exists(r.displayParts) + ); + }); - it("can cancel completion entry details mid-request", () => { - const options: FormatCodeSettings = { - indentSize: 4, - tabSize: 4, - newLineCharacter: "\n", - convertTabsToSpaces: true, - indentStyle: IndentStyle.Smart, - insertSpaceAfterConstructor: false, - insertSpaceAfterCommaDelimiter: true, - insertSpaceAfterSemicolonInForStatements: true, - insertSpaceBeforeAndAfterBinaryOperators: true, - insertSpaceAfterKeywordsInControlFlowStatements: true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, - insertSpaceBeforeFunctionParenthesis: false, - placeOpenBraceOnNewLineForFunctions: false, - placeOpenBraceOnNewLineForControlBlocks: false, - }; - verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for completion entry details, so the first check is within the checker - service.getCompletionEntryDetails("file.ts", file.lastIndexOf("f"), "foo", options, /*source*/ undefined, {}, /*data*/ undefined)!, r => assert.exists(r.displayParts) - ); - }); + it("can cancel completion entry details mid-request", () => { + const options: ts.FormatCodeSettings = { + indentSize: 4, + tabSize: 4, + newLineCharacter: "\n", + convertTabsToSpaces: true, + indentStyle: ts.IndentStyle.Smart, + insertSpaceAfterConstructor: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceBeforeFunctionParenthesis: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + }; + verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for completion entry details, so the first check is within the checker + service.getCompletionEntryDetails("file.ts", file.lastIndexOf("f"), "foo", options, /*source*/ undefined, {}, /*data*/ undefined)!, r => assert.exists(r.displayParts) + ); + }); - it("can cancel suggestion diagnostics mid-request", () => { - verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for suggestion diagnostics, so the first check is within the checker - service.getSuggestionDiagnostics("file.js"), r => assert.notEqual(r.length, 0), "file.js", "function foo() { let a = 10; }", { allowJs: true } - ); - }); + it("can cancel suggestion diagnostics mid-request", () => { + verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for suggestion diagnostics, so the first check is within the checker + service.getSuggestionDiagnostics("file.js"), r => assert.notEqual(r.length, 0), "file.js", "function foo() { let a = 10; }", { allowJs: true } + ); }); +}); - function verifyOperationCancelledAfter(content: string, cancelAfter: number, operation: (service: LanguageService) => T, validator: (arg: T) => void, fileName?: string, fileContent?: string, options?: CompilerOptions) { - let checks = 0; - const token: HostCancellationToken = { - isCancellationRequested() { - checks++; - const result = checks >= cancelAfter; - if (result) { - checks = -Infinity; // Cancel just once, then disable cancellation, effectively - } - return result; +function verifyOperationCancelledAfter(content: string, cancelAfter: number, operation: (service: ts.LanguageService) => T, validator: (arg: T) => void, fileName?: string, fileContent?: string, options?: ts.CompilerOptions) { + let checks = 0; + const token: ts.HostCancellationToken = { + isCancellationRequested() { + checks++; + const result = checks >= cancelAfter; + if (result) { + checks = -Infinity; // Cancel just once, then disable cancellation, effectively } - }; - const adapter = new Harness.LanguageService.NativeLanguageServiceAdapter(token, options); - const host = adapter.getHost(); - host.addScript(fileName || "file.ts", fileContent || content, /*isRootFile*/ true); - const service = adapter.getLanguageService(); - assertCancelled(() => operation(service)); - validator(operation(service)); - } - - /** - * We don't just use `assert.throws` because it doesn't validate instances for thrown objects which do not inherit from `Error` - */ - function assertCancelled(cb: () => void) { - let caught: any; - try { - cb(); - } - catch (e) { - caught = e; + return result; } - assert.exists(caught, "Expected operation to be cancelled, but was not"); - assert.instanceOf(caught, OperationCanceledException); + }; + const adapter = new Harness.LanguageService.NativeLanguageServiceAdapter(token, options); + const host = adapter.getHost(); + host.addScript(fileName || "file.ts", fileContent || content, /*isRootFile*/ true); + const service = adapter.getLanguageService(); + assertCancelled(() => operation(service)); + validator(operation(service)); +} + +/** + * We don't just use `assert.throws` because it doesn't validate instances for thrown objects which do not inherit from `Error` + */ +function assertCancelled(cb: () => void) { + let caught: any; + try { + cb(); + } + catch (e) { + caught = e; } + assert.exists(caught, "Expected operation to be cancelled, but was not"); + assert.instanceOf(caught, ts.OperationCanceledException); } diff --git a/src/testRunner/unittests/services/colorization.ts b/src/testRunner/unittests/services/colorization.ts index 4a89ccad56e92..0afe3176f37c3 100644 --- a/src/testRunner/unittests/services/colorization.ts +++ b/src/testRunner/unittests/services/colorization.ts @@ -1,3 +1,6 @@ +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; + // lots of tests use quoted code /* eslint-disable no-template-curly-in-string */ diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index a8bd6e6d520c0..0df0a9691ccf9 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -1,7 +1,9 @@ -namespace ts { - const libFile: TestFSWithWatch.File = { - path: "/a/lib/lib.d.ts", - content: `/// +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; + +const libFile: ts.TestFSWithWatch.File = { + path: "/a/lib/lib.d.ts", + content: `/// interface Boolean {} interface Function {} interface IArguments {} @@ -260,207 +262,207 @@ declare var Promise: PromiseConstructor; interface RegExp {} interface String { charAt: any; } interface Array {}` - }; +}; - const moduleFile: TestFSWithWatch.File = { - path: "/module.ts", - content: +const moduleFile: ts.TestFSWithWatch.File = { + path: "/module.ts", + content: `export function fn(res: any): any { return res; }` - }; - - type WithSkipAndOnly = ((...args: T) => void) & { - skip: (...args: T) => void; - only: (...args: T) => void; - }; - - function createTestWrapper(fn: (it: Mocha.PendingTestFunction, ...args: T) => void): WithSkipAndOnly { - wrapped.skip = (...args: T) => fn(it.skip, ...args); - wrapped.only = (...args: T) => fn(it.only, ...args); - return wrapped; - function wrapped(...args: T) { - return fn(it, ...args); - } +}; + +type WithSkipAndOnly = ((...args: T) => void) & { + skip: (...args: T) => void; + only: (...args: T) => void; +}; + +function createTestWrapper(fn: (it: Mocha.PendingTestFunction, ...args: T) => void): WithSkipAndOnly { + wrapped.skip = (...args: T) => fn(it.skip, ...args); + wrapped.only = (...args: T) => fn(it.only, ...args); + return wrapped; + function wrapped(...args: T) { + return fn(it, ...args); } +} - const enum ConvertToAsyncTestFlags { - None, - IncludeLib = 1 << 0, - IncludeModule = 1 << 1, - ExpectSuggestionDiagnostic = 1 << 2, - ExpectNoSuggestionDiagnostic = 1 << 3, - ExpectAction = 1 << 4, - ExpectNoAction = 1 << 5, - - ExpectSuccess = ExpectSuggestionDiagnostic | ExpectAction, - ExpectFailed = ExpectNoSuggestionDiagnostic | ExpectNoAction, +const enum ConvertToAsyncTestFlags { + None, + IncludeLib = 1 << 0, + IncludeModule = 1 << 1, + ExpectSuggestionDiagnostic = 1 << 2, + ExpectNoSuggestionDiagnostic = 1 << 3, + ExpectAction = 1 << 4, + ExpectNoAction = 1 << 5, + + ExpectSuccess = ExpectSuggestionDiagnostic | ExpectAction, + ExpectFailed = ExpectNoSuggestionDiagnostic | ExpectNoAction, +} + +function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: string, text: string, baselineFolder: string, flags: ConvertToAsyncTestFlags) { + const includeLib = !!(flags & ConvertToAsyncTestFlags.IncludeLib); + const includeModule = !!(flags & ConvertToAsyncTestFlags.IncludeModule); + const expectSuggestionDiagnostic = !!(flags & ConvertToAsyncTestFlags.ExpectSuggestionDiagnostic); + const expectNoSuggestionDiagnostic = !!(flags & ConvertToAsyncTestFlags.ExpectNoSuggestionDiagnostic); + const expectAction = !!(flags & ConvertToAsyncTestFlags.ExpectAction); + const expectNoAction = !!(flags & ConvertToAsyncTestFlags.ExpectNoAction); + const expectFailure = expectNoSuggestionDiagnostic || expectNoAction; + ts.Debug.assert(!(expectSuggestionDiagnostic && expectNoSuggestionDiagnostic), "Cannot combine both 'ExpectSuggestionDiagnostic' and 'ExpectNoSuggestionDiagnostic'"); + ts.Debug.assert(!(expectAction && expectNoAction), "Cannot combine both 'ExpectAction' and 'ExpectNoAction'"); + + const t = ts.extractTest(text); + const selectionRange = t.ranges.get("selection")!; + if (!selectionRange) { + throw new Error(`Test ${caption} does not specify selection range`); } - function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: string, text: string, baselineFolder: string, flags: ConvertToAsyncTestFlags) { - const includeLib = !!(flags & ConvertToAsyncTestFlags.IncludeLib); - const includeModule = !!(flags & ConvertToAsyncTestFlags.IncludeModule); - const expectSuggestionDiagnostic = !!(flags & ConvertToAsyncTestFlags.ExpectSuggestionDiagnostic); - const expectNoSuggestionDiagnostic = !!(flags & ConvertToAsyncTestFlags.ExpectNoSuggestionDiagnostic); - const expectAction = !!(flags & ConvertToAsyncTestFlags.ExpectAction); - const expectNoAction = !!(flags & ConvertToAsyncTestFlags.ExpectNoAction); - const expectFailure = expectNoSuggestionDiagnostic || expectNoAction; - Debug.assert(!(expectSuggestionDiagnostic && expectNoSuggestionDiagnostic), "Cannot combine both 'ExpectSuggestionDiagnostic' and 'ExpectNoSuggestionDiagnostic'"); - Debug.assert(!(expectAction && expectNoAction), "Cannot combine both 'ExpectAction' and 'ExpectNoAction'"); - - const t = extractTest(text); - const selectionRange = t.ranges.get("selection")!; - if (!selectionRange) { - throw new Error(`Test ${caption} does not specify selection range`); + const extensions = expectFailure ? [ts.Extension.Ts] : [ts.Extension.Ts, ts.Extension.Js]; + + extensions.forEach(extension => + it(`${caption} [${extension}]`, () => runBaseline(extension))); + + function runBaseline(extension: ts.Extension) { + const path = "/a" + extension; + const languageService = makeLanguageService({ path, content: t.source }, includeLib, includeModule); + const program = languageService.getProgram()!; + + if (hasSyntacticDiagnostics(program)) { + // Don't bother generating JS baselines for inputs that aren't valid JS. + assert.equal(ts.Extension.Js, extension, "Syntactic diagnostics found in non-JS file"); + return; + } + + const f = { + path, + content: t.source + }; + + const sourceFile = program.getSourceFile(path)!; + const context: ts.CodeFixContext = { + errorCode: 80006, + span: { start: selectionRange.pos, length: selectionRange.end - selectionRange.pos }, + sourceFile, + program, + cancellationToken: { throwIfCancellationRequested: ts.noop, isCancellationRequested: ts.returnFalse }, + preferences: ts.emptyOptions, + host: ts.notImplementedHost, + formatContext: ts.formatting.getFormatContext(ts.testFormatSettings, ts.notImplementedHost) + }; + + const diagnostics = languageService.getSuggestionDiagnostics(f.path); + const diagnostic = ts.find(diagnostics, diagnostic => diagnostic.messageText === ts.Diagnostics.This_may_be_converted_to_an_async_function.message && + diagnostic.start === context.span.start && diagnostic.length === context.span.length); + const actions = ts.codefix.getFixes(context); + const action = ts.find(actions, action => action.description === ts.Diagnostics.Convert_to_async_function.message); + + let outputText: string | null; + if (action?.changes.length) { + const data: string[] = []; + data.push(`// ==ORIGINAL==`); + data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/")); + const changes = action.changes; + assert.lengthOf(changes, 1); + + data.push(`// ==ASYNC FUNCTION::${action.description}==`); + const newText = ts.textChanges.applyChanges(sourceFile.text, changes[0].textChanges); + data.push(newText); + + const diagProgram = makeLanguageService({ path, content: newText }, includeLib, includeModule).getProgram()!; + assert.isFalse(hasSyntacticDiagnostics(diagProgram)); + outputText = data.join(ts.newLineCharacter); + } + else { + // eslint-disable-next-line no-null/no-null + outputText = null; } - const extensions = expectFailure ? [Extension.Ts] : [Extension.Ts, Extension.Js]; - - extensions.forEach(extension => - it(`${caption} [${extension}]`, () => runBaseline(extension))); - - function runBaseline(extension: Extension) { - const path = "/a" + extension; - const languageService = makeLanguageService({ path, content: t.source }, includeLib, includeModule); - const program = languageService.getProgram()!; - - if (hasSyntacticDiagnostics(program)) { - // Don't bother generating JS baselines for inputs that aren't valid JS. - assert.equal(Extension.Js, extension, "Syntactic diagnostics found in non-JS file"); - return; - } - - const f = { - path, - content: t.source - }; - - const sourceFile = program.getSourceFile(path)!; - const context: CodeFixContext = { - errorCode: 80006, - span: { start: selectionRange.pos, length: selectionRange.end - selectionRange.pos }, - sourceFile, - program, - cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, - preferences: emptyOptions, - host: notImplementedHost, - formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost) - }; - - const diagnostics = languageService.getSuggestionDiagnostics(f.path); - const diagnostic = find(diagnostics, diagnostic => diagnostic.messageText === Diagnostics.This_may_be_converted_to_an_async_function.message && - diagnostic.start === context.span.start && diagnostic.length === context.span.length); - const actions = codefix.getFixes(context); - const action = find(actions, action => action.description === Diagnostics.Convert_to_async_function.message); - - let outputText: string | null; - if (action?.changes.length) { - const data: string[] = []; - data.push(`// ==ORIGINAL==`); - data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/")); - const changes = action.changes; - assert.lengthOf(changes, 1); - - data.push(`// ==ASYNC FUNCTION::${action.description}==`); - const newText = textChanges.applyChanges(sourceFile.text, changes[0].textChanges); - data.push(newText); - - const diagProgram = makeLanguageService({ path, content: newText }, includeLib, includeModule).getProgram()!; - assert.isFalse(hasSyntacticDiagnostics(diagProgram)); - outputText = data.join(newLineCharacter); - } - else { - // eslint-disable-next-line no-null/no-null - outputText = null; - } - - Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, outputText); - - if (expectNoSuggestionDiagnostic) { - assert.isUndefined(diagnostic, "Expected code fix to not provide a suggestion diagnostic"); - } - else if (expectSuggestionDiagnostic) { - assert.exists(diagnostic, "Expected code fix to provide a suggestion diagnostic"); - } - - if (expectNoAction) { - assert.isNotTrue(!!action?.changes.length, "Expected code fix to not provide an action"); - assert.isNotTrue(typeof outputText === "string", "Expected code fix to not apply changes"); - } - else if (expectAction) { - assert.isTrue(!!action?.changes.length, "Expected code fix to provide an action"); - assert.isTrue(typeof outputText === "string", "Expected code fix to apply changes"); - } + Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, outputText); + + if (expectNoSuggestionDiagnostic) { + assert.isUndefined(diagnostic, "Expected code fix to not provide a suggestion diagnostic"); + } + else if (expectSuggestionDiagnostic) { + assert.exists(diagnostic, "Expected code fix to provide a suggestion diagnostic"); } - function makeLanguageService(file: TestFSWithWatch.File, includeLib?: boolean, includeModule?: boolean) { - const files = [file]; - if (includeLib) { - files.push(libFile); // libFile is expensive to parse repeatedly - only test when required - } - if (includeModule) { - files.push(moduleFile); - } - const host = projectSystem.createServerHost(files); - const projectService = projectSystem.createProjectService(host); - projectService.openClientFile(file.path); - return first(projectService.inferredProjects).getLanguageService(); + if (expectNoAction) { + assert.isNotTrue(!!action?.changes.length, "Expected code fix to not provide an action"); + assert.isNotTrue(typeof outputText === "string", "Expected code fix to not apply changes"); } + else if (expectAction) { + assert.isTrue(!!action?.changes.length, "Expected code fix to provide an action"); + assert.isTrue(typeof outputText === "string", "Expected code fix to apply changes"); + } + } - function hasSyntacticDiagnostics(program: Program) { - const diags = program.getSyntacticDiagnostics(); - return length(diags) > 0; + function makeLanguageService(file: ts.TestFSWithWatch.File, includeLib?: boolean, includeModule?: boolean) { + const files = [file]; + if (includeLib) { + files.push(libFile); // libFile is expensive to parse repeatedly - only test when required } + if (includeModule) { + files.push(moduleFile); + } + const host = ts.projectSystem.createServerHost(files); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(file.path); + return ts.first(projectService.inferredProjects).getLanguageService(); } - const _testConvertToAsyncFunction = createTestWrapper((it, caption: string, text: string) => { - testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectSuccess); - }); + function hasSyntacticDiagnostics(program: ts.Program) { + const diags = program.getSyntacticDiagnostics(); + return ts.length(diags) > 0; + } +} - const _testConvertToAsyncFunctionFailed = createTestWrapper((it, caption: string, text: string) => { - testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectFailed); - }); +const _testConvertToAsyncFunction = createTestWrapper((it, caption: string, text: string) => { + testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectSuccess); +}); - const _testConvertToAsyncFunctionFailedSuggestion = createTestWrapper((it, caption: string, text: string) => { - testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectNoSuggestionDiagnostic | ConvertToAsyncTestFlags.ExpectAction); - }); +const _testConvertToAsyncFunctionFailed = createTestWrapper((it, caption: string, text: string) => { + testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectFailed); +}); - const _testConvertToAsyncFunctionFailedAction = createTestWrapper((it, caption: string, text: string) => { - testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectSuggestionDiagnostic | ConvertToAsyncTestFlags.ExpectNoAction); - }); +const _testConvertToAsyncFunctionFailedSuggestion = createTestWrapper((it, caption: string, text: string) => { + testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectNoSuggestionDiagnostic | ConvertToAsyncTestFlags.ExpectAction); +}); - const _testConvertToAsyncFunctionWithModule = createTestWrapper((it, caption: string, text: string) => { - testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.IncludeModule | ConvertToAsyncTestFlags.ExpectSuccess); - }); +const _testConvertToAsyncFunctionFailedAction = createTestWrapper((it, caption: string, text: string) => { + testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.ExpectSuggestionDiagnostic | ConvertToAsyncTestFlags.ExpectNoAction); +}); - describe("unittests:: services:: convertToAsyncFunction", () => { - _testConvertToAsyncFunction("convertToAsyncFunction_basic", ` +const _testConvertToAsyncFunctionWithModule = createTestWrapper((it, caption: string, text: string) => { + testConvertToAsyncFunction(it, caption, text, "convertToAsyncFunction", ConvertToAsyncTestFlags.IncludeLib | ConvertToAsyncTestFlags.IncludeModule | ConvertToAsyncTestFlags.ExpectSuccess); +}); + +describe("unittests:: services:: convertToAsyncFunction", () => { + _testConvertToAsyncFunction("convertToAsyncFunction_basic", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(result => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_arrayBindingPattern", ` + _testConvertToAsyncFunction("convertToAsyncFunction_arrayBindingPattern", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(([result]) => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_objectBindingPattern", ` + _testConvertToAsyncFunction("convertToAsyncFunction_objectBindingPattern", ` function [#|f|](): Promise{ return fetch('https://typescriptlang.org').then(({ result }) => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_arrayBindingPatternRename", ` + _testConvertToAsyncFunction("convertToAsyncFunction_arrayBindingPatternRename", ` function [#|f|](): Promise{ const result = getResult(); return fetch('https://typescriptlang.org').then(([result]) => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_objectBindingPatternRename", ` + _testConvertToAsyncFunction("convertToAsyncFunction_objectBindingPatternRename", ` function [#|f|](): Promise{ const result = getResult(); return fetch('https://typescriptlang.org').then(({ result }) => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_basicNoReturnTypeAnnotation", ` + _testConvertToAsyncFunction("convertToAsyncFunction_basicNoReturnTypeAnnotation", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(result => { console.log(result) }); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_basicWithComments", ` + _testConvertToAsyncFunction("convertToAsyncFunction_basicWithComments", ` function [#|f|](): Promise{ /* Note - some of these comments are removed during the refactor. This is not ideal. */ @@ -469,23 +471,23 @@ function [#|f|](): Promise{ // m }`); - _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunction", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunction", ` [#|():Promise => {|] return fetch('https://typescriptlang.org').then(result => console.log(result)); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunctionNoAnnotation", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ArrowFunctionNoAnnotation", ` [#|() => {|] return fetch('https://typescriptlang.org').then(result => console.log(result)); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_Catch", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Catch", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => { console.log(result); }).catch(err => { console.log(err); }); }`); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchAndRej", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchAndRej", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }).catch(err => { console.log(err) }); }`); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchAndRejRef", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchAndRejRef", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res, rej).catch(catch_err) } @@ -498,7 +500,7 @@ function rej(rejection){ function catch_err(err){ console.log(err); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchRef", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchRef", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res).catch(catch_err) } @@ -509,48 +511,48 @@ function catch_err(err){ console.log(err); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchNoBrackets", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchNoBrackets", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result)).catch(err => console.log(err)); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs1", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs1", ` function [#|f|](): Promise { return fetch('https://typescriptlang.org').then( _ => { console.log("done"); }); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs2", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs2", ` function [#|f|](): Promise { return fetch('https://typescriptlang.org').then( () => console.log("done") ); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs3", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs3", ` function [#|f|](): Promise { return fetch('https://typescriptlang.org').then( () => console.log("almost done") ).then( () => console.log("done") ); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs4", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_IgnoreArgs4", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(res); } function res(){ console.log("done"); }` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Method", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Method", ` class Parser { [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result)); } }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleCatches", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleCatches", ` function [#|f|](): Promise { return fetch('https://typescriptlang.org').then(res => console.log(res)).catch(err => console.log("err", err)).catch(err2 => console.log("err2", err2)); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThens", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThens", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res).then(res2); } @@ -560,8 +562,8 @@ function res(result){ function res2(result2){ console.log(result2); }` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThensSameVarName", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleThensSameVarName", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res).then(res2); } @@ -572,64 +574,64 @@ function res2(result){ return result.bodyUsed; } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_NoRes", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_NoRes", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(null, rejection => console.log("rejected:", rejection)); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_NoRes2", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_NoRes2", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(undefined).catch(rej => console.log(rej)); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_NoRes3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_NoRes3", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').catch(rej => console.log(rej)); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_NoRes4", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_NoRes4", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(undefined, rejection => console.log("rejected:", rejection)); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_NoCatchHandler", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_NoCatchHandler", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(x => x.statusText).catch(undefined); } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestion", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestion", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org'); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseDotAll", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseDotAll", ` function [#|f|]():Promise{ return Promise.all([fetch('https://typescriptlang.org'), fetch('https://microsoft.com'), fetch('https://youtube.com')]).then(function(vals){ vals.forEach(console.log); }); } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionNoPromise", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionNoPromise", ` function [#|f|]():void{ } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Rej", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Rej", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => { console.log(result); }, rejection => { console.log("rejected:", rejection); }); } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_RejRef", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_RejRef", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res, rej); } @@ -640,15 +642,15 @@ function rej(err){ console.log(err); } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_RejNoBrackets", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_RejNoBrackets", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result), rejection => console.log("rejected:", rejection)); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRef", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRef", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res); } @@ -656,9 +658,9 @@ function res(result){ return result.ok; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRef1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRef1", ` class Foo { public [#|method|](): Promise { return fetch('a').then(this.foo); @@ -670,7 +672,7 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRef2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRef2", ` class Foo { public [#|method|](): Promise { return fetch('a').then(this.foo); @@ -680,7 +682,7 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRef3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRef3", ` const res = (result) => { return result.ok; } @@ -688,17 +690,17 @@ function [#|f|](): Promise { return fetch('https://typescriptlang.org').then(res); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef1", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef1", ` const res = 1; function [#|f|]() { return fetch('https://typescriptlang.org').then(res); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef2", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef2", ` class Foo { private foo = 1; public [#|method|](): Promise { @@ -706,17 +708,17 @@ class Foo { } } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef3", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef3", ` const res = undefined; function [#|f|]() { return fetch('https://typescriptlang.org').then(res); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef4", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NoSuggestionResRef4", ` class Foo { private foo = undefined; public [#|method|](): Promise { @@ -724,9 +726,9 @@ class Foo { } } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRefNoReturnVal", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRefNoReturnVal", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(res); } @@ -734,9 +736,9 @@ function res(result){ console.log(result); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_ResRefNoReturnVal1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ResRefNoReturnVal1", ` class Foo { public [#|method|](): Promise { return fetch('a').then(this.foo); @@ -748,33 +750,33 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_NoBrackets", ` + _testConvertToAsyncFunction("convertToAsyncFunction_NoBrackets", ` function [#|f|]():Promise { return fetch('https://typescriptlang.org').then(result => console.log(result)); } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally1", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally1", ` function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").then(res => console.log(res)).catch(rej => console.log("error", rej)).finally(console.log("finally!")); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally2", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally2", ` function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").then(res => console.log(res)).finally(console.log("finally!")); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally3", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Finally3", ` function [#|finallyTest|](): Promise { return fetch("https://typescriptlang.org").finally(console.log("finally!")); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromise", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromise", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { var blob2 = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); @@ -784,8 +786,8 @@ function [#|innerPromise|](): Promise { }); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRet", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRet", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); @@ -794,9 +796,9 @@ function [#|innerPromise|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding1", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(({ blob }) => blob.byteOffset).catch(({ message }) => 'Error ' + message); @@ -805,9 +807,9 @@ function [#|innerPromise|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding2", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); @@ -816,9 +818,9 @@ function [#|innerPromise|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding3", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(({ blob }) => blob.byteOffset).catch(({ message }) => 'Error ' + message); @@ -827,9 +829,9 @@ function [#|innerPromise|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding4", ` + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseRetBinding4", ` function [#|innerPromise|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(({ blob }: { blob: { byteOffset: number } }) => [0, blob.byteOffset]).catch(({ message }: Error) => ['Error ', message]); @@ -838,24 +840,24 @@ function [#|innerPromise|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn01", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn01", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org").then(resp => console.log(resp)); return blob; } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn02", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn02", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); blob.then(resp => console.log(resp)); return blob; } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn03", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn03", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org") let blob2 = blob.then(resp => console.log(resp)); @@ -867,8 +869,8 @@ function err (rej) { console.log(rej) } ` - ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn04", ` + ); + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn04", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org").then(res => console.log(res)), blob2 = fetch("https://microsoft.com").then(res => res.ok).catch(err); return blob; @@ -877,26 +879,26 @@ function err (rej) { console.log(rej) } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn05", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn05", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org").then(res => console.log(res)); blob.then(x => x); return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn06", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn06", ` function [#|f|]() { var blob = fetch("https://typescriptlang.org"); return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn07", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn07", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); let blob2 = fetch("https://microsoft.com"); @@ -905,9 +907,9 @@ function [#|f|]() { return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn08", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn08", ` function [#|f|]() { let blob = fetch("https://typescriptlang.org"); if (!blob.ok){ @@ -917,9 +919,9 @@ function [#|f|]() { return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn09", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn09", ` function [#|f|]() { let blob3; let blob = fetch("https://typescriptlang.org"); @@ -930,10 +932,10 @@ function [#|f|]() { return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn10", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn10", ` function [#|f|]() { let blob3; let blob = fetch("https://typescriptlang.org"); @@ -945,19 +947,19 @@ function [#|f|]() { return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn11", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_VarReturn11", ` function [#|f|]() { let blob; return blob; } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Param1", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_Param1", ` function [#|f|]() { return my_print(fetch("https://typescriptlang.org").then(res => console.log(res))); } @@ -969,9 +971,9 @@ function my_print (resp) { } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Param2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Param2", ` function [#|f|]() { return my_print(fetch("https://typescriptlang.org").then(res => console.log(res))).catch(err => console.log("Error!", err)); } @@ -984,9 +986,9 @@ function my_print (resp): Promise { ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns1", ` function [#|f|](): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); if (x.ok) { @@ -997,9 +999,9 @@ function [#|f|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_MultipleReturns2", ` function [#|f|](): Promise { let x = fetch("https://microsoft.com").then(res => console.log("Microsoft:", res)); if (x.ok) { @@ -1011,10 +1013,10 @@ function [#|f|](): Promise { }); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_SeperateLines", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_SeperateLines", ` function [#|f|](): Promise { var blob = fetch("https://typescriptlang.org") blob.then(resp => { @@ -1027,10 +1029,10 @@ function [#|f|](): Promise { return blob; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerVarNameConflict", ` + _testConvertToAsyncFunction("convertToAsyncFunction_InnerVarNameConflict", ` function [#|f|](): Promise { return fetch("https://typescriptlang.org").then(resp => { var blob = resp.blob().then(blob => blob.byteOffset).catch(err => 'Error'); @@ -1039,8 +1041,8 @@ function [#|f|](): Promise { }); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseSimple", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_InnerPromiseSimple", ` function [#|f|](): Promise { return fetch("https://typescriptlang.org").then(resp => { return resp.blob().then(blob => blob.byteOffset); @@ -1049,8 +1051,8 @@ function [#|f|](): Promise { }); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen1", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen1", ` function [#|f|]() { return Promise.resolve().then(function () { return Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { @@ -1059,9 +1061,9 @@ function [#|f|]() { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen2", ` function [#|f|]() { return Promise.resolve().then(function () { return Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { @@ -1070,9 +1072,9 @@ function [#|f|]() { }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen3", ` function [#|f|]() { return Promise.resolve().then(() => Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { @@ -1080,9 +1082,9 @@ function [#|f|]() { }).then(res => res.toString())])); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen4", ` + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseAllAndThen4", ` function [#|f|]() { return Promise.resolve().then(() => Promise.all([fetch("https://typescriptlang.org"), fetch("https://microsoft.com"), Promise.resolve().then(function () { @@ -1090,8 +1092,8 @@ function [#|f|]() { })]).then(res => res.toString())); } ` - ); - _testConvertToAsyncFunction("convertToAsyncFunction_Scope1", ` + ); + _testConvertToAsyncFunction("convertToAsyncFunction_Scope1", ` function [#|f|]() { var var1: Response, var2; return fetch('https://typescriptlang.org').then( _ => @@ -1108,7 +1110,7 @@ function [#|f|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_Conditionals", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Conditionals", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res => { if (res.ok) { @@ -1125,9 +1127,9 @@ function [#|f|](){ }); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThen", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThen", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1140,9 +1142,9 @@ function rej(reject){ return reject; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes01", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes01", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1155,9 +1157,9 @@ function rej(reject): number { return 3; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes01NoAnnotations", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1170,10 +1172,10 @@ function rej(reject){ return 3; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes02", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes02", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res => 0).catch(rej => 1).then(res); } @@ -1182,9 +1184,9 @@ function res(result): number { return 5; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMatchingTypes02NoAnnotations", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res => 0).catch(rej => 1).then(res); } @@ -1193,9 +1195,9 @@ function res(result){ return 5; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes01", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes01", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1208,9 +1210,9 @@ function rej(reject){ return "Error"; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes02", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes02", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1223,9 +1225,9 @@ function rej(reject): Response{ return reject; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes02NoAnnotations", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1238,10 +1240,10 @@ function rej(reject){ return reject; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes03", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes03", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).then(res); } @@ -1254,9 +1256,9 @@ function rej(reject){ return Promise.resolve(1); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes04", ` + _testConvertToAsyncFunction("convertToAsyncFunction_CatchFollowedByThenMismatchTypes04", ` interface a { name: string; age: number; @@ -1279,9 +1281,9 @@ function rej(reject): a{ return {name: "myName", age: 27}; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_ParameterNameCollision", ` + _testConvertToAsyncFunction("convertToAsyncFunction_ParameterNameCollision", ` async function foo(x: T): Promise { return x; } @@ -1290,44 +1292,44 @@ function [#|bar|](x: T): Promise { return foo(x).then(foo) } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Return1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Return1", ` function [#|f|](p: Promise) { return p.catch((error: Error) => { return Promise.reject(error); }); }` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Return2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Return2", ` function [#|f|](p: Promise) { return p.catch((error: Error) => Promise.reject(error)); }` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Return3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Return3", ` function [#|f|](p: Promise) { return p.catch(function (error: Error) { return Promise.reject(error); }); }` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_LocalReturn", ` + _testConvertToAsyncFunction("convertToAsyncFunction_LocalReturn", ` function [#|f|]() { let x = fetch("https://typescriptlang.org").then(res => console.log(res)); return x.catch(err => console.log("Error!", err)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_PromiseCallInner", ` + _testConvertToAsyncFunction("convertToAsyncFunction_PromiseCallInner", ` function [#|f|]() { return fetch(Promise.resolve(1).then(res => "https://typescriptlang.org")).catch(err => console.log(err)); } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchFollowedByCall", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_CatchFollowedByCall", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res).catch(rej).toString(); } @@ -1340,26 +1342,26 @@ function rej(reject){ return reject; } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Scope2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Scope2", ` function [#|f|](){ var i:number; return fetch("https://typescriptlang.org").then(i => i.ok).then(res => i+1).catch(err => i-1) } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Loop", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Loop", ` function [#|f|](){ return fetch("https://typescriptlang.org").then(res => { for(let i=0; i<10; i++){ console.log(res); }}) } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Conditional2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Conditional2", ` function [#|f|](){ var res = 100; if (res > 50) { @@ -1374,9 +1376,9 @@ function res_func(result){ console.log(result); } ` - ); + ); - _testConvertToAsyncFunction("convertToAsyncFunction_Scope3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_Scope3", ` function [#|f|]() { var obj; return fetch("https://typescriptlang.org").then(function (res) { @@ -1388,9 +1390,9 @@ function [#|f|]() { }); } ` - ); + ); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NestedFunctionWrongLocation", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_NestedFunctionWrongLocation", ` function [#|f|]() { function fn2(){ function fn3(){ @@ -1402,7 +1404,7 @@ function [#|f|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_NestedFunctionRightLocation", ` + _testConvertToAsyncFunction("convertToAsyncFunction_NestedFunctionRightLocation", ` function f() { function fn2(){ function [#|fn3|](){ @@ -1414,56 +1416,56 @@ function f() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_UntypedFunction", ` + _testConvertToAsyncFunction("convertToAsyncFunction_UntypedFunction", ` function [#|f|]() { return Promise.resolve().then(res => console.log(res)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_TernaryConditional", ` + _testConvertToAsyncFunction("convertToAsyncFunction_TernaryConditional", ` function [#|f|]() { let i; return Promise.resolve().then(res => res ? i = res : i = 100); } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_ResRejNoArgsArrow", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_ResRejNoArgsArrow", ` function [#|f|]() { return Promise.resolve().then(() => 1, () => "a"); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpression", ` + _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpression", ` const [#|foo|] = function () { return fetch('https://typescriptlang.org').then(result => { console.log(result) }); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionWithName", ` + _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionWithName", ` const foo = function [#|f|]() { return fetch('https://typescriptlang.org').then(result => { console.log(result) }); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionAssignedToBindingPattern", ` + _testConvertToAsyncFunction("convertToAsyncFunction_simpleFunctionExpressionAssignedToBindingPattern", ` const { length } = [#|function|] () { return fetch('https://typescriptlang.org').then(result => { console.log(result) }); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParams", ` + _testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParams", ` function [#|f|]() { return Promise.resolve().then(x => 1).catch(x => "a").then(x => !!x); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParamsBindingPattern", ` + _testConvertToAsyncFunction("convertToAsyncFunction_catchBlockUniqueParamsBindingPattern", ` function [#|f|]() { return Promise.resolve().then(() => ({ x: 3 })).catch(() => ({ x: "a" })).then(({ x }) => !!x); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_bindingPattern", ` + _testConvertToAsyncFunction("convertToAsyncFunction_bindingPattern", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(res); } @@ -1472,7 +1474,7 @@ function res({ status, trailer }){ } `); - _testConvertToAsyncFunction("convertToAsyncFunction_bindingPatternNameCollision", ` + _testConvertToAsyncFunction("convertToAsyncFunction_bindingPatternNameCollision", ` function [#|f|]() { const result = 'https://typescriptlang.org'; return fetch(result).then(res); @@ -1482,19 +1484,19 @@ function res({ status, trailer }){ } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_thenArgumentNotFunction", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_thenArgumentNotFunction", ` function [#|f|]() { return Promise.resolve().then(f ? (x => x) : (y => y)); } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_thenArgumentNotFunctionNotLastInChain", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_thenArgumentNotFunctionNotLastInChain", ` function [#|f|]() { return Promise.resolve().then(f ? (x => x) : (y => y)).then(q => q); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_runEffectfulContinuation", ` + _testConvertToAsyncFunction("convertToAsyncFunction_runEffectfulContinuation", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(res).then(_ => console.log("done")); } @@ -1503,31 +1505,31 @@ function res(result) { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromise", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromise", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(s => Promise.resolve(s.statusText.length)).then(x => console.log(x + 5)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromiseInBlock", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromiseInBlock", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(s => { return Promise.resolve(s.statusText.length) }).then(x => x + 5); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsFixablePromise", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsFixablePromise", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(s => Promise.resolve(s.statusText).then(st => st.length)).then(x => console.log(x + 5)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromiseLastInChain", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsPromiseLastInChain", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(s => Promise.resolve(s.statusText.length)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsRejectedPromiseInTryBlock", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackReturnsRejectedPromiseInTryBlock", ` function [#|f|]() { return Promise.resolve(1) .then(x => Promise.reject(x)) @@ -1535,13 +1537,13 @@ function [#|f|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_nestedPromises", ` + _testConvertToAsyncFunction("convertToAsyncFunction_nestedPromises", ` function [#|f|]() { return fetch('https://typescriptlang.org').then(x => Promise.resolve(3).then(y => Promise.resolve(x.statusText.length + y))); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_noArgs1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_noArgs1", ` function delay(millis: number): Promise { throw "no" } @@ -1555,7 +1557,7 @@ function [#|main2|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_noArgs2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_noArgs2", ` function delay(millis: number): Promise { throw "no" } @@ -1569,20 +1571,20 @@ function [#|main2|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_exportModifier", ` + _testConvertToAsyncFunction("convertToAsyncFunction_exportModifier", ` export function [#|foo|]() { return fetch('https://typescriptlang.org').then(s => console.log(s)); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_OutermostOnlySuccess", ` + _testConvertToAsyncFunction("convertToAsyncFunction_OutermostOnlySuccess", ` function [#|foo|]() { return fetch('a').then(() => { return fetch('b').then(() => 'c'); }) } `); - _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethod", ` + _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethod", ` function decorator() { return (target: any, key: any, descriptor: PropertyDescriptor) => descriptor; } @@ -1594,7 +1596,7 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithSingleLineComment", ` + _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithSingleLineComment", ` function decorator() { return (target: any, key: any, descriptor: PropertyDescriptor) => descriptor; } @@ -1607,7 +1609,7 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithMultipleLineComment", ` + _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithMultipleLineComment", ` function decorator() { return (target: any, key: any, descriptor: PropertyDescriptor) => descriptor; } @@ -1622,7 +1624,7 @@ class Foo { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithModifier", ` + _testConvertToAsyncFunction("convertToAsyncFunction_decoratedMethodWithModifier", ` function decorator() { return (target: any, key: any, descriptor: PropertyDescriptor) => descriptor; } @@ -1634,7 +1636,7 @@ class Foo { } `); - _testConvertToAsyncFunctionFailedSuggestion("convertToAsyncFunction_OutermostOnlyFailure", ` + _testConvertToAsyncFunctionFailedSuggestion("convertToAsyncFunction_OutermostOnlyFailure", ` function foo() { return fetch('a').then([#|() => {|] return fetch('b').then(() => 'c'); @@ -1642,7 +1644,7 @@ function foo() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument1", ` type APIResponse = { success: true, data: T } | { success: false }; function wrapResponse(response: T): APIResponse { @@ -1654,7 +1656,7 @@ function [#|get|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument2", ` type APIResponse = { success: true, data: T } | { success: false }; function wrapResponse(response: T): APIResponse { @@ -1666,7 +1668,7 @@ function [#|get|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument3", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenTypeArgument3", ` type APIResponse = { success: true, data: T } | { success: false }; function wrapResponse(response: T): APIResponse { @@ -1681,7 +1683,7 @@ function [#|get|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_catchTypeArgument1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_catchTypeArgument1", ` type APIResponse = { success: true, data: T } | { success: false }; function [#|get|]() { @@ -1691,12 +1693,12 @@ function [#|get|]() { } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction_threeArguments", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction_threeArguments", ` function [#|f|]() { return Promise.resolve().then(undefined, undefined, () => 1); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_callbackArgument", ` + _testConvertToAsyncFunction("convertToAsyncFunction_callbackArgument", ` function foo(props: any): void { return props; } @@ -1709,26 +1711,26 @@ function [#|f|]() { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch1", ` + _testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch1", ` function [#|f|]() { return Promise.resolve().catch(); } `); - _testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch2", ` + _testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch2", ` function [#|f|]() { return Promise.resolve(0).then(x => x).catch(); } `); - _testConvertToAsyncFunctionWithModule("convertToAsyncFunction_importedFunction", ` + _testConvertToAsyncFunctionWithModule("convertToAsyncFunction_importedFunction", ` import { fn } from "./module"; function [#|f|]() { return Promise.resolve(0).then(fn); } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInFunctionsWithNonFixableReturnStatements1", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInFunctionsWithNonFixableReturnStatements1", ` function f(x: number): Promise; function f(): void; function [#|f|](x?: number): Promise | void { @@ -1737,7 +1739,7 @@ function [#|f|](x?: number): Promise | void { } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInFunctionsWithNonFixableReturnStatements2", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInFunctionsWithNonFixableReturnStatements2", ` function f(x: number): Promise; function f(): number; function [#|f|](x?: number): Promise | number { @@ -1746,7 +1748,7 @@ function [#|f|](x?: number): Promise | number { } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInGetters", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionInGetters", ` class Foo { get [#|m|](): Promise { return Promise.resolve(1).then(n => n); @@ -1754,7 +1756,7 @@ class Foo { } `); - _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionForGeneratorCallbacks", ` + _testConvertToAsyncFunctionFailed("convertToAsyncFunction__NoSuggestionForGeneratorCallbacks", ` function [#|foo|](p: Promise) { return p.then(function* (strings) { for (const s of strings) { @@ -1764,52 +1766,52 @@ function [#|foo|](p: Promise) { } `); - _testConvertToAsyncFunction("convertToAsyncFunction_thenNoArguments", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenNoArguments", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_catchNoArguments", ` + _testConvertToAsyncFunction("convertToAsyncFunction_catchNoArguments", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().catch(); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_chainedThenCatchThen", ` + _testConvertToAsyncFunction("convertToAsyncFunction_chainedThenCatchThen", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(x => Promise.resolve(x + 1)).catch(() => 1).then(y => y + 2); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_finally", ` + _testConvertToAsyncFunction("convertToAsyncFunction_finally", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().finally(() => console.log("done")); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_finallyNoArguments", ` + _testConvertToAsyncFunction("convertToAsyncFunction_finallyNoArguments", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().finally(); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_finallyNull", ` + _testConvertToAsyncFunction("convertToAsyncFunction_finallyNull", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().finally(null); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_finallyUndefined", ` + _testConvertToAsyncFunction("convertToAsyncFunction_finallyUndefined", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().finally(undefined); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_thenFinally", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenFinally", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(x => x + 1).finally(() => console.log("done")); }`); - _testConvertToAsyncFunction("convertToAsyncFunction_thenFinallyThen", ` + _testConvertToAsyncFunction("convertToAsyncFunction_thenFinallyThen", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(x => Promise.resolve(x + 1)).finally(() => console.log("done")).then(y => y + 2); }`); - _testConvertToAsyncFunctionFailedAction("convertToAsyncFunction_returnInBranch", ` + _testConvertToAsyncFunctionFailedAction("convertToAsyncFunction_returnInBranch", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(() => { @@ -1822,7 +1824,7 @@ function [#|f|](): Promise { }); } `); - _testConvertToAsyncFunctionFailedAction("convertToAsyncFunction_partialReturnInBranch", ` + _testConvertToAsyncFunctionFailedAction("convertToAsyncFunction_partialReturnInBranch", ` declare function foo(): Promise; function [#|f|](): Promise { return foo().then(() => { @@ -1835,5 +1837,4 @@ function [#|f|](): Promise { }); } `); - }); -} +}); diff --git a/src/testRunner/unittests/services/documentRegistry.ts b/src/testRunner/unittests/services/documentRegistry.ts index e7c80486e975a..6281f73125276 100644 --- a/src/testRunner/unittests/services/documentRegistry.ts +++ b/src/testRunner/unittests/services/documentRegistry.ts @@ -1,3 +1,5 @@ +import * as ts from "../../_namespaces/ts"; + describe("unittests:: services:: DocumentRegistry", () => { it("documents are shared between projects", () => { const documentRegistry = ts.createDocumentRegistry(); diff --git a/src/testRunner/unittests/services/extract/constants.ts b/src/testRunner/unittests/services/extract/constants.ts index 05c3aa5ac52bb..55a30e21b83b0 100644 --- a/src/testRunner/unittests/services/extract/constants.ts +++ b/src/testRunner/unittests/services/extract/constants.ts @@ -1,59 +1,60 @@ -namespace ts { - describe("unittests:: services:: extract:: extractConstants", () => { - testExtractConstant("extractConstant_TopLevel", - `let x = [#|1|];`); +import * as ts from "../../../_namespaces/ts"; - testExtractConstant("extractConstant_Namespace", - `namespace N { +describe("unittests:: services:: extract:: extractConstants", () => { + testExtractConstant("extractConstant_TopLevel", + `let x = [#|1|];`); + + testExtractConstant("extractConstant_Namespace", + `namespace N { let x = [#|1|]; }`); - testExtractConstant("extractConstant_Class", - `class C { + testExtractConstant("extractConstant_Class", + `class C { x = [#|1|]; }`); - testExtractConstant("extractConstant_Method", - `class C { + testExtractConstant("extractConstant_Method", + `class C { M() { let x = [#|1|]; } }`); - testExtractConstant("extractConstant_Function", - `function F() { + testExtractConstant("extractConstant_Function", + `function F() { let x = [#|1|]; }`); - testExtractConstant("extractConstant_ExpressionStatement", - `[#|"hello";|]`); + testExtractConstant("extractConstant_ExpressionStatement", + `[#|"hello";|]`); - testExtractConstant("extractConstant_ExpressionStatementExpression", - `[#|"hello"|];`); + testExtractConstant("extractConstant_ExpressionStatementExpression", + `[#|"hello"|];`); - testExtractConstant("extractConstant_ExpressionStatementInNestedScope", ` + testExtractConstant("extractConstant_ExpressionStatementInNestedScope", ` let i = 0; function F() { [#|i++|]; } `); - testExtractConstant("extractConstant_ExpressionStatementConsumesLocal", ` + testExtractConstant("extractConstant_ExpressionStatementConsumesLocal", ` function F() { let i = 0; [#|i++|]; } `); - testExtractConstant("extractConstant_BlockScopes_NoDependencies", - `for (let i = 0; i < 10; i++) { + testExtractConstant("extractConstant_BlockScopes_NoDependencies", + `for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { let x = [#|1|]; } }`); - testExtractConstant("extractConstant_ClassInsertionPosition1", - `class C { + testExtractConstant("extractConstant_ClassInsertionPosition1", + `class C { a = 1; b = 2; M1() { } @@ -63,8 +64,8 @@ function F() { } }`); - testExtractConstant("extractConstant_ClassInsertionPosition2", - `class C { + testExtractConstant("extractConstant_ClassInsertionPosition2", + `class C { a = 1; M1() { } b = 2; @@ -74,8 +75,8 @@ function F() { } }`); - testExtractConstant("extractConstant_ClassInsertionPosition3", - `class C { + testExtractConstant("extractConstant_ClassInsertionPosition3", + `class C { M1() { } a = 1; b = 2; @@ -85,36 +86,36 @@ function F() { } }`); - testExtractConstant("extractConstant_Parameters", - `function F() { + testExtractConstant("extractConstant_Parameters", + `function F() { let w = 1; let x = [#|w + 1|]; }`); - testExtractConstant("extractConstant_TypeParameters", - `function F(t: T) { + testExtractConstant("extractConstant_TypeParameters", + `function F(t: T) { let x = [#|t + 1|]; }`); - testExtractConstant("extractConstant_RepeatedSubstitution", - `namespace X { + testExtractConstant("extractConstant_RepeatedSubstitution", + `namespace X { export const j = 10; export const y = [#|j * j|]; }`); - testExtractConstant("extractConstant_VariableList_const", - `const a = 1, b = [#|a + 1|];`); + testExtractConstant("extractConstant_VariableList_const", + `const a = 1, b = [#|a + 1|];`); - // NOTE: this test isn't normative - it just documents our sub-optimal behavior. - testExtractConstant("extractConstant_VariableList_let", - `let a = 1, b = [#|a + 1|];`); + // NOTE: this test isn't normative - it just documents our sub-optimal behavior. + testExtractConstant("extractConstant_VariableList_let", + `let a = 1, b = [#|a + 1|];`); - // NOTE: this test isn't normative - it just documents our sub-optimal behavior. - testExtractConstant("extractConstant_VariableList_MultipleLines", - `const /*About A*/a = 1, + // NOTE: this test isn't normative - it just documents our sub-optimal behavior. + testExtractConstant("extractConstant_VariableList_MultipleLines", + `const /*About A*/a = 1, /*About B*/b = [#|a + 1|];`); - testExtractConstant("extractConstant_BlockScopeMismatch", ` + testExtractConstant("extractConstant_BlockScopeMismatch", ` for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { const x = [#|i + 1|]; @@ -122,14 +123,14 @@ for (let i = 0; i < 10; i++) { } `); - testExtractConstant("extractConstant_StatementInsertionPosition1", ` + testExtractConstant("extractConstant_StatementInsertionPosition1", ` const i = 0; for (let j = 0; j < 10; j++) { const x = [#|i + 1|]; } `); - testExtractConstant("extractConstant_StatementInsertionPosition2", ` + testExtractConstant("extractConstant_StatementInsertionPosition2", ` const i = 0; function F() { for (let j = 0; j < 10; j++) { @@ -138,13 +139,13 @@ function F() { } `); - testExtractConstant("extractConstant_StatementInsertionPosition3", ` + testExtractConstant("extractConstant_StatementInsertionPosition3", ` for (let j = 0; j < 10; j++) { const x = [#|2 + 1|]; } `); - testExtractConstant("extractConstant_StatementInsertionPosition4", ` + testExtractConstant("extractConstant_StatementInsertionPosition4", ` function F() { for (let j = 0; j < 10; j++) { const x = [#|2 + 1|]; @@ -152,7 +153,7 @@ function F() { } `); - testExtractConstant("extractConstant_StatementInsertionPosition5", ` + testExtractConstant("extractConstant_StatementInsertionPosition5", ` function F0() { function F1() { function F2(x = [#|2 + 1|]) { @@ -161,13 +162,13 @@ function F0() { } `); - testExtractConstant("extractConstant_StatementInsertionPosition6", ` + testExtractConstant("extractConstant_StatementInsertionPosition6", ` class C { x = [#|2 + 1|]; } `); - testExtractConstant("extractConstant_StatementInsertionPosition7", ` + testExtractConstant("extractConstant_StatementInsertionPosition7", ` const i = 0; class C { M() { @@ -178,25 +179,25 @@ class C { } `); - testExtractConstant("extractConstant_TripleSlash", ` + testExtractConstant("extractConstant_TripleSlash", ` /// const x = [#|2 + 1|]; `); - testExtractConstant("extractConstant_PinnedComment", ` + testExtractConstant("extractConstant_PinnedComment", ` /*! Copyright */ const x = [#|2 + 1|]; `); - testExtractConstant("extractConstant_Directive", ` + testExtractConstant("extractConstant_Directive", ` "strict"; const x = [#|2 + 1|]; `); - testExtractConstant("extractConstant_MultipleHeaders", ` + testExtractConstant("extractConstant_MultipleHeaders", ` /*! Copyright */ /// @@ -206,22 +207,22 @@ const x = [#|2 + 1|]; const x = [#|2 + 1|]; `); - testExtractConstant("extractConstant_PinnedCommentAndDocComment", ` + testExtractConstant("extractConstant_PinnedCommentAndDocComment", ` /*! Copyright */ /* About x */ const x = [#|2 + 1|]; `); - testExtractConstant("extractConstant_ArrowFunction_Block", ` + testExtractConstant("extractConstant_ArrowFunction_Block", ` const f = () => { return [#|2 + 1|]; };`); - testExtractConstant("extractConstant_ArrowFunction_Expression", - `const f = () => [#|2 + 1|];`); + testExtractConstant("extractConstant_ArrowFunction_Expression", + `const f = () => [#|2 + 1|];`); - testExtractConstant("extractConstant_PreserveTrivia", ` + testExtractConstant("extractConstant_PreserveTrivia", ` // a var q = /*b*/ //c /*d*/ [#|1 /*e*/ //f @@ -229,15 +230,15 @@ var q = /*b*/ //c /*j*/ 2|] /*k*/ //l /*m*/; /*n*/ //o`); - testExtractConstantFailed("extractConstant_Void", ` + testExtractConstantFailed("extractConstant_Void", ` function f(): void { } [#|f();|]`); - testExtractConstantFailed("extractConstant_Never", ` + testExtractConstantFailed("extractConstant_Never", ` function f(): never { } [#|f();|]`); - testExtractConstant("extractConstant_This_Constructor", ` + testExtractConstant("extractConstant_This_Constructor", ` class C { constructor() { [#|this.m2()|]; @@ -245,7 +246,7 @@ class C { m2() { return 1; } }`); - testExtractConstant("extractConstant_This_Method", ` + testExtractConstant("extractConstant_This_Method", ` class C { m1() { [#|this.m2()|]; @@ -253,7 +254,7 @@ class C { m2() { return 1; } }`); - testExtractConstant("extractConstant_This_Property", ` + testExtractConstant("extractConstant_This_Property", ` namespace N { // Force this test to be TS-only class C { x = 1; @@ -261,44 +262,43 @@ namespace N { // Force this test to be TS-only } }`); - // TODO (https://github.com/Microsoft/TypeScript/issues/20727): the extracted constant should have a type annotation. - testExtractConstant("extractConstant_ContextualType", ` + // TODO (https://github.com/Microsoft/TypeScript/issues/20727): the extracted constant should have a type annotation. + testExtractConstant("extractConstant_ContextualType", ` interface I { a: 1 | 2 | 3 } let i: I = [#|{ a: 1 }|]; `); - testExtractConstant("extractConstant_ContextualType_Lambda", ` + testExtractConstant("extractConstant_ContextualType_Lambda", ` const myObj: { member(x: number, y: string): void } = { member: [#|(x, y) => x + y|], } `); - testExtractConstant("extractConstant_CaseClauseExpression", ` + testExtractConstant("extractConstant_CaseClauseExpression", ` switch (1) { case [#|1|]: break; } `); - testExtractConstant("extractConstant_PropertyName", - `[#|x.y|].z();`); + testExtractConstant("extractConstant_PropertyName", + `[#|x.y|].z();`); - testExtractConstant("extractConstant_PropertyName_ExistingName", - `let y; + testExtractConstant("extractConstant_PropertyName_ExistingName", + `let y; [#|x.y|].z();`); - testExtractConstant("extractConstant_PropertyName_Keyword", - `[#|x.if|].z();`); + testExtractConstant("extractConstant_PropertyName_Keyword", + `[#|x.if|].z();`); - testExtractConstant("extractConstant_PropertyName_PrivateIdentifierKeyword", - `[#|this.#if|].z();`); - }); + testExtractConstant("extractConstant_PropertyName_PrivateIdentifierKeyword", + `[#|this.#if|].z();`); +}); - function testExtractConstant(caption: string, text: string) { - testExtractSymbol(caption, text, "extractConstant", Diagnostics.Extract_constant); - } +function testExtractConstant(caption: string, text: string) { + ts.testExtractSymbol(caption, text, "extractConstant", ts.Diagnostics.Extract_constant); +} - function testExtractConstantFailed(caption: string, text: string) { - testExtractSymbolFailed(caption, text, Diagnostics.Extract_constant); - } +function testExtractConstantFailed(caption: string, text: string) { + ts.testExtractSymbolFailed(caption, text, ts.Diagnostics.Extract_constant); } diff --git a/src/testRunner/unittests/services/extract/functions.ts b/src/testRunner/unittests/services/extract/functions.ts index c0e0a4b21f5ec..06f2943970e9e 100644 --- a/src/testRunner/unittests/services/extract/functions.ts +++ b/src/testRunner/unittests/services/extract/functions.ts @@ -1,7 +1,8 @@ -namespace ts { - describe("unittests:: services:: extract:: extractFunctions", () => { - testExtractFunction("extractFunction1", - `namespace A { +import * as ts from "../../../_namespaces/ts"; + +describe("unittests:: services:: extract:: extractFunctions", () => { + testExtractFunction("extractFunction1", + `namespace A { let x = 1; function foo() { } @@ -16,8 +17,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction2", - `namespace A { + testExtractFunction("extractFunction2", + `namespace A { let x = 1; function foo() { } @@ -30,8 +31,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction3", - `namespace A { + testExtractFunction("extractFunction3", + `namespace A { function foo() { } namespace B { @@ -43,8 +44,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction4", - `namespace A { + testExtractFunction("extractFunction4", + `namespace A { function foo() { } namespace B { @@ -58,8 +59,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction5", - `namespace A { + testExtractFunction("extractFunction5", + `namespace A { let x = 1; export function foo() { } @@ -74,8 +75,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction6", - `namespace A { + testExtractFunction("extractFunction6", + `namespace A { let x = 1; export function foo() { } @@ -90,8 +91,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction7", - `namespace A { + testExtractFunction("extractFunction7", + `namespace A { let x = 1; export namespace C { export function foo() { @@ -108,8 +109,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction9", - `namespace A { + testExtractFunction("extractFunction9", + `namespace A { export interface I { x: number }; namespace B { function a() { @@ -118,8 +119,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction10", - `namespace A { + testExtractFunction("extractFunction10", + `namespace A { export interface I { x: number }; class C { a() { @@ -129,8 +130,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction11", - `namespace A { + testExtractFunction("extractFunction11", + `namespace A { let y = 1; class C { a() { @@ -142,8 +143,8 @@ namespace ts { } } }`); - testExtractFunction("extractFunction12", - `namespace A { + testExtractFunction("extractFunction12", + `namespace A { let y = 1; class C { b() {} @@ -157,13 +158,13 @@ namespace ts { } } }`); - // The "b" type parameters aren't used and shouldn't be passed to the extracted function. - // Type parameters should be in syntactic order (i.e. in order or character offset from BOF). - // In all cases, we could use type inference, rather than passing explicit type arguments. - // Note the inclusion of arrow functions to ensure that some type parameters are not from - // targetable scopes. - testExtractFunction("extractFunction13", - `(u1a: U1a, u1b: U1b) => { + // The "b" type parameters aren't used and shouldn't be passed to the extracted function. + // Type parameters should be in syntactic order (i.e. in order or character offset from BOF). + // In all cases, we could use type inference, rather than passing explicit type arguments. + // Note the inclusion of arrow functions to ensure that some type parameters are not from + // targetable scopes. + testExtractFunction("extractFunction13", + `(u1a: U1a, u1b: U1b) => { function F1(t1a: T1a, t1b: T1b) { (u2a: U2a, u2b: U2b) => { function F2(t2a: T2a, t2b: T2b) { @@ -178,107 +179,107 @@ namespace ts { } } }`); - // This test is descriptive, rather than normative. The current implementation - // doesn't handle type parameter shadowing. - testExtractFunction("extractFunction14", - `function F(t1: T) { + // This test is descriptive, rather than normative. The current implementation + // doesn't handle type parameter shadowing. + testExtractFunction("extractFunction14", + `function F(t1: T) { function G(t2: T) { [#|t1.toString(); t2.toString();|] } }`); - // Confirm that the constraint is preserved. - testExtractFunction("extractFunction15", - `function F(t1: T) { + // Confirm that the constraint is preserved. + testExtractFunction("extractFunction15", + `function F(t1: T) { function G(t2: U) { [#|t2.toString();|] } }`, /*includeLib*/ true); - // Confirm that the contextual type of an extracted expression counts as a use. - testExtractFunction("extractFunction16", - `function F() { + // Confirm that the contextual type of an extracted expression counts as a use. + testExtractFunction("extractFunction16", + `function F() { const array: T[] = [#|[]|]; }`, /*includeLib*/ true); - // Class type parameter - testExtractFunction("extractFunction17", - `class C { + // Class type parameter + testExtractFunction("extractFunction17", + `class C { M(t1: T1, t2: T2) { [#|t1.toString()|]; } }`); - // Function type parameter - testExtractFunction("extractFunction18", - `class C { + // Function type parameter + testExtractFunction("extractFunction18", + `class C { M(t1: T1, t2: T2) { [#|t1.toString()|]; } }`); - // Coupled constraints - testExtractFunction("extractFunction19", - `function F(v: V) { + // Coupled constraints + testExtractFunction("extractFunction19", + `function F(v: V) { [#|v.toString()|]; }`, /*includeLib*/ true); - testExtractFunction("extractFunction20", - `const _ = class { + testExtractFunction("extractFunction20", + `const _ = class { a() { [#|let a1 = { x: 1 }; return a1.x + 10;|] } }`); - // Write + void return - testExtractFunction("extractFunction21", - `function foo() { + // Write + void return + testExtractFunction("extractFunction21", + `function foo() { let x = 10; [#|x++; return;|] }`); - // Return in finally block - testExtractFunction("extractFunction22", - `function test() { + // Return in finally block + testExtractFunction("extractFunction22", + `function test() { try { } finally { [#|return 1;|] } }`); - // Extraction position - namespace - testExtractFunction("extractFunction23", - `namespace NS { + // Extraction position - namespace + testExtractFunction("extractFunction23", + `namespace NS { function M1() { } function M2() { [#|return 1;|] } function M3() { } }`); - // Extraction position - function - testExtractFunction("extractFunction24", - `function Outer() { + // Extraction position - function + testExtractFunction("extractFunction24", + `function Outer() { function M1() { } function M2() { [#|return 1;|] } function M3() { } }`); - // Extraction position - file - testExtractFunction("extractFunction25", - `function M1() { } + // Extraction position - file + testExtractFunction("extractFunction25", + `function M1() { } function M2() { [#|return 1;|] } function M3() { }`); - // Extraction position - class without ctor - testExtractFunction("extractFunction26", - `class C { + // Extraction position - class without ctor + testExtractFunction("extractFunction26", + `class C { M1() { } M2() { [#|return 1;|] } M3() { } }`); - // Extraction position - class with ctor in middle - testExtractFunction("extractFunction27", - `class C { + // Extraction position - class with ctor in middle + testExtractFunction("extractFunction27", + `class C { M1() { } M2() { [#|return 1;|] @@ -286,9 +287,9 @@ function M3() { }`); constructor() { } M3() { } }`); - // Extraction position - class with ctor at end - testExtractFunction("extractFunction28", - `class C { + // Extraction position - class with ctor at end + testExtractFunction("extractFunction28", + `class C { M1() { } M2() { [#|return 1;|] @@ -296,9 +297,9 @@ function M3() { }`); M3() { } constructor() { } }`); - // Shorthand property names - testExtractFunction("extractFunction29", - `interface UnaryExpression { + // Shorthand property names + testExtractFunction("extractFunction29", + `interface UnaryExpression { kind: "Unary"; operator: string; operand: any; @@ -315,14 +316,14 @@ function parseUnaryExpression(operator: string): UnaryExpression { function parsePrimaryExpression(): any { throw "Not implemented"; }`); - // Type parameter as declared type - testExtractFunction("extractFunction30", - `function F() { + // Type parameter as declared type + testExtractFunction("extractFunction30", + `function F() { [#|let t: T;|] }`); - // Return in nested function - testExtractFunction("extractFunction31", - `namespace N { + // Return in nested function + testExtractFunction("extractFunction31", + `namespace N { export const value = 1; @@ -333,9 +334,9 @@ function parsePrimaryExpression(): any { }|] } }`); - // Return in nested class - testExtractFunction("extractFunction32", - `namespace N { + // Return in nested class + testExtractFunction("extractFunction32", + `namespace N { export const value = 1; @@ -347,83 +348,83 @@ function parsePrimaryExpression(): any { }|] } }`); - // Selection excludes leading trivia of declaration - testExtractFunction("extractFunction33", - `function F() { + // Selection excludes leading trivia of declaration + testExtractFunction("extractFunction33", + `function F() { [#|function G() { }|] }`); - // Arrow function - testExtractFunction("extractFunction34", - `const F = () => { + // Arrow function + testExtractFunction("extractFunction34", + `const F = () => { [#|function G() { }|] };`); - testExtractFunction("extractFunction_RepeatedSubstitution", - `namespace X { + testExtractFunction("extractFunction_RepeatedSubstitution", + `namespace X { export const j = 10; export const y = [#|j * j|]; }`); - testExtractFunction("extractFunction_VariableDeclaration_Var", ` + testExtractFunction("extractFunction_VariableDeclaration_Var", ` [#|var x = 1; "hello"|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Let_Type", ` + testExtractFunction("extractFunction_VariableDeclaration_Let_Type", ` [#|let x: number = 1; "hello";|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Let_NoType", ` + testExtractFunction("extractFunction_VariableDeclaration_Let_NoType", ` [#|let x = 1; "hello";|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Const_Type", ` + testExtractFunction("extractFunction_VariableDeclaration_Const_Type", ` [#|const x: number = 1; "hello";|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Const_NoType", ` + testExtractFunction("extractFunction_VariableDeclaration_Const_NoType", ` [#|const x = 1; "hello";|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Multiple1", ` + testExtractFunction("extractFunction_VariableDeclaration_Multiple1", ` [#|const x = 1, y: string = "a";|] x; y; `); - testExtractFunction("extractFunction_VariableDeclaration_Multiple2", ` + testExtractFunction("extractFunction_VariableDeclaration_Multiple2", ` [#|const x = 1, y = "a"; const z = 3;|] x; y; z; `); - testExtractFunction("extractFunction_VariableDeclaration_Multiple3", ` + testExtractFunction("extractFunction_VariableDeclaration_Multiple3", ` [#|const x = 1, y: string = "a"; let z = 3;|] x; y; z; `); - testExtractFunction("extractFunction_VariableDeclaration_ConsumedTwice", ` + testExtractFunction("extractFunction_VariableDeclaration_ConsumedTwice", ` [#|const x: number = 1; "hello";|] x; x; `); - testExtractFunction("extractFunction_VariableDeclaration_DeclaredTwice", ` + testExtractFunction("extractFunction_VariableDeclaration_DeclaredTwice", ` [#|var x = 1; var x = 2;|] x; `); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Var", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Var", ` function f() { let a = 1; [#|var x = 1; @@ -431,7 +432,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_NoType", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_NoType", ` function f() { let a = 1; [#|let x = 1; @@ -439,7 +440,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_Type", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_Type", ` function f() { let a = 1; [#|let x: number = 1; @@ -447,9 +448,9 @@ function f() { a; x; }`); - // We propagate numericLiteralFlags, but it's not consumed by the emitter, - // so everything comes out decimal. It would be nice to improve this. - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType1", ` + // We propagate numericLiteralFlags, but it's not consumed by the emitter, + // so everything comes out decimal. It would be nice to improve this. + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType1", ` function f() { let a = 1; [#|let x: 0o10 | 10 | 0b10 = 10; @@ -457,7 +458,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType2", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType2", ` function f() { let a = 1; [#|let x: "a" | 'b' = 'a'; @@ -465,9 +466,9 @@ function f() { a; x; }`); - // We propagate numericLiteralFlags, but it's not consumed by the emitter, - // so everything comes out decimal. It would be nice to improve this. - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType1", ` + // We propagate numericLiteralFlags, but it's not consumed by the emitter, + // so everything comes out decimal. It would be nice to improve this. + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_LiteralType1", ` function f() { let a = 1; [#|let x: 0o10 | 10 | 0b10 = 10; @@ -475,7 +476,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_TypeWithComments", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Let_TypeWithComments", ` function f() { let a = 1; [#|let x: /*A*/ "a" /*B*/ | /*C*/ 'b' /*D*/ = 'a'; @@ -483,7 +484,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Const_NoType", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Const_NoType", ` function f() { let a = 1; [#|const x = 1; @@ -491,7 +492,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Const_Type", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Const_Type", ` function f() { let a = 1; [#|const x: number = 1; @@ -499,7 +500,7 @@ function f() { a; x; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed1", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed1", ` function f() { let a = 1; [#|const x = 1; @@ -508,7 +509,7 @@ function f() { a; x; y; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed2", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed2", ` function f() { let a = 1; [#|var x = 1; @@ -517,7 +518,7 @@ function f() { a; x; y; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed3", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_Mixed3", ` function f() { let a = 1; [#|let x: number = 1; @@ -526,7 +527,7 @@ function f() { a; x; y; }`); - testExtractFunction("extractFunction_VariableDeclaration_Writes_UnionUndefined", ` + testExtractFunction("extractFunction_VariableDeclaration_Writes_UnionUndefined", ` function f() { let a = 1; [#|let x: number | undefined = 1; @@ -536,13 +537,13 @@ function f() { a; x; y; z; }`); - testExtractFunction("extractFunction_VariableDeclaration_ShorthandProperty", ` + testExtractFunction("extractFunction_VariableDeclaration_ShorthandProperty", ` function f() { [#|let x;|] return { x }; }`); - testExtractFunction("extractFunction_PreserveTrivia", ` + testExtractFunction("extractFunction_PreserveTrivia", ` // a var q = /*b*/ //c /*d*/ [#|1 /*e*/ //f @@ -550,20 +551,19 @@ var q = /*b*/ //c /*j*/ 2|] /*k*/ //l /*m*/; /*n*/ //o`); - testExtractFunction("extractFunction_NamelessClass", ` + testExtractFunction("extractFunction_NamelessClass", ` export default class { M() { [#|1 + 1|]; } }`); - testExtractFunction("extractFunction_NoDeclarations", ` + testExtractFunction("extractFunction_NoDeclarations", ` function F() { [#|arguments.length|]; // arguments has no declaration }`); - }); +}); - function testExtractFunction(caption: string, text: string, includeLib?: boolean) { - testExtractSymbol(caption, text, "extractFunction", Diagnostics.Extract_function, includeLib); - } +function testExtractFunction(caption: string, text: string, includeLib?: boolean) { + ts.testExtractSymbol(caption, text, "extractFunction", ts.Diagnostics.Extract_function, includeLib); } diff --git a/src/testRunner/unittests/services/extract/helpers.ts b/src/testRunner/unittests/services/extract/helpers.ts index 660c903bd2bf3..959b8447c73e6 100644 --- a/src/testRunner/unittests/services/extract/helpers.ts +++ b/src/testRunner/unittests/services/extract/helpers.ts @@ -1,179 +1,180 @@ -namespace ts { - interface Range { - pos: number; - end: number; - name: string; - } +import * as ts from "../../../_namespaces/ts"; +import * as Harness from "../../../_namespaces/Harness"; - interface Test { - source: string; - ranges: ESMap; - } +interface Range { + pos: number; + end: number; + name: string; +} - export function extractTest(source: string): Test { - const activeRanges: Range[] = []; - let text = ""; - let lastPos = 0; - let pos = 0; - const ranges = new Map(); - - while (pos < source.length) { - if (source.charCodeAt(pos) === CharacterCodes.openBracket && - (source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) { - const saved = pos; - pos += 2; - const s = pos; - consumeIdentifier(); - const e = pos; - if (source.charCodeAt(pos) === CharacterCodes.bar) { - pos++; - text += source.substring(lastPos, saved); - const name = s === e - ? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted" - : source.substring(s, e); - activeRanges.push({ name, pos: text.length, end: undefined! }); // TODO: GH#18217 - lastPos = pos; - continue; - } - else { - pos = saved; - } - } - else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) { - text += source.substring(lastPos, pos); - activeRanges[activeRanges.length - 1].end = text.length; - const range = activeRanges.pop()!; - if (hasProperty(ranges, range.name)) { - throw new Error(`Duplicate name of range ${range.name}`); - } - ranges.set(range.name, range); - pos += 2; +interface Test { + source: string; + ranges: ts.ESMap; +} + +export function extractTest(source: string): Test { + const activeRanges: Range[] = []; + let text = ""; + let lastPos = 0; + let pos = 0; + const ranges = new ts.Map(); + + while (pos < source.length) { + if (source.charCodeAt(pos) === ts.CharacterCodes.openBracket && + (source.charCodeAt(pos + 1) === ts.CharacterCodes.hash || source.charCodeAt(pos + 1) === ts.CharacterCodes.$)) { + const saved = pos; + pos += 2; + const s = pos; + consumeIdentifier(); + const e = pos; + if (source.charCodeAt(pos) === ts.CharacterCodes.bar) { + pos++; + text += source.substring(lastPos, saved); + const name = s === e + ? source.charCodeAt(saved + 1) === ts.CharacterCodes.hash ? "selection" : "extracted" + : source.substring(s, e); + activeRanges.push({ name, pos: text.length, end: undefined! }); // TODO: GH#18217 lastPos = pos; continue; } - pos++; + else { + pos = saved; + } } - text += source.substring(lastPos, pos); - - function consumeIdentifier() { - while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) { - pos++; + else if (source.charCodeAt(pos) === ts.CharacterCodes.bar && source.charCodeAt(pos + 1) === ts.CharacterCodes.closeBracket) { + text += source.substring(lastPos, pos); + activeRanges[activeRanges.length - 1].end = text.length; + const range = activeRanges.pop()!; + if (ts.hasProperty(ranges, range.name)) { + throw new Error(`Duplicate name of range ${range.name}`); } + ranges.set(range.name, range); + pos += 2; + lastPos = pos; + continue; } - return { source: text, ranges }; + pos++; } + text += source.substring(lastPos, pos); - export const newLineCharacter = "\n"; - - export const notImplementedHost: LanguageServiceHost = { - getCompilationSettings: notImplemented, - getScriptFileNames: notImplemented, - getScriptVersion: notImplemented, - getScriptSnapshot: notImplemented, - getDefaultLibFileName: notImplemented, - getCurrentDirectory: notImplemented, - readFile: notImplemented, - fileExists: notImplemented - }; - - export function testExtractSymbol(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage, includeLib?: boolean) { - const t = extractTest(text); - const selectionRange = t.ranges.get("selection")!; - if (!selectionRange) { - throw new Error(`Test ${caption} does not specify selection range`); + function consumeIdentifier() { + while (ts.isIdentifierPart(source.charCodeAt(pos), ts.ScriptTarget.Latest)) { + pos++; } + } + return { source: text, ranges }; +} - [Extension.Ts, Extension.Js].forEach(extension => - it(`${caption} [${extension}]`, () => runBaseline(extension))); +export const newLineCharacter = "\n"; + +export const notImplementedHost: ts.LanguageServiceHost = { + getCompilationSettings: ts.notImplemented, + getScriptFileNames: ts.notImplemented, + getScriptVersion: ts.notImplemented, + getScriptSnapshot: ts.notImplemented, + getDefaultLibFileName: ts.notImplemented, + getCurrentDirectory: ts.notImplemented, + readFile: ts.notImplemented, + fileExists: ts.notImplemented +}; + +export function testExtractSymbol(caption: string, text: string, baselineFolder: string, description: ts.DiagnosticMessage, includeLib?: boolean) { + const t = extractTest(text); + const selectionRange = t.ranges.get("selection")!; + if (!selectionRange) { + throw new Error(`Test ${caption} does not specify selection range`); + } - function runBaseline(extension: Extension) { - const path = "/a" + extension; - const { program } = makeProgram({ path, content: t.source }, includeLib); + [ts.Extension.Ts, ts.Extension.Js].forEach(extension => + it(`${caption} [${extension}]`, () => runBaseline(extension))); - if (hasSyntacticDiagnostics(program)) { - // Don't bother generating JS baselines for inputs that aren't valid JS. - assert.equal(Extension.Js, extension, "Syntactic diagnostics found in non-JS file"); - return; - } + function runBaseline(extension: ts.Extension) { + const path = "/a" + extension; + const { program } = makeProgram({ path, content: t.source }, includeLib); - const sourceFile = program.getSourceFile(path)!; - const context: RefactorContext = { - cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, - program, - file: sourceFile, - startPosition: selectionRange.pos, - endPosition: selectionRange.end, - host: notImplementedHost, - formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost), - preferences: emptyOptions, - }; - const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange)); - assert.equal(rangeToExtract.errors, undefined, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); - const infos = refactor.extractSymbol.getRefactorActionsToExtractSymbol(context); - const actions = find(infos, info => info.description === description.message)!.actions; - - const data: string[] = []; - data.push(`// ==ORIGINAL==`); - data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/")); - for (const action of actions) { - const { renameLocation, edits } = refactor.extractSymbol.getRefactorEditsToExtractSymbol(context, action.name)!; - assert.lengthOf(edits, 1); - data.push(`// ==SCOPE::${action.description}==`); - const newText = textChanges.applyChanges(sourceFile.text, edits[0].textChanges); - const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation); - data.push(newTextWithRename); - - const { program: diagProgram } = makeProgram({ path, content: newText }, includeLib); - assert.isFalse(hasSyntacticDiagnostics(diagProgram)); - } - Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, data.join(newLineCharacter)); + if (hasSyntacticDiagnostics(program)) { + // Don't bother generating JS baselines for inputs that aren't valid JS. + assert.equal(ts.Extension.Js, extension, "Syntactic diagnostics found in non-JS file"); + return; } - function makeProgram(f: {path: string, content: string }, includeLib?: boolean) { - const host = projectSystem.createServerHost(includeLib ? [f, projectSystem.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required - const projectService = projectSystem.createProjectService(host); - projectService.openClientFile(f.path); - const program = projectService.inferredProjects[0].getLanguageService().getProgram()!; - const autoImportProvider = projectService.inferredProjects[0].getLanguageService().getAutoImportProvider(); - return { program, autoImportProvider }; + const sourceFile = program.getSourceFile(path)!; + const context: ts.RefactorContext = { + cancellationToken: { throwIfCancellationRequested: ts.noop, isCancellationRequested: ts.returnFalse }, + program, + file: sourceFile, + startPosition: selectionRange.pos, + endPosition: selectionRange.end, + host: notImplementedHost, + formatContext: ts.formatting.getFormatContext(ts.testFormatSettings, notImplementedHost), + preferences: ts.emptyOptions, + }; + const rangeToExtract = ts.refactor.extractSymbol.getRangeToExtract(sourceFile, ts.createTextSpanFromRange(selectionRange)); + assert.equal(rangeToExtract.errors, undefined, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); + const infos = ts.refactor.extractSymbol.getRefactorActionsToExtractSymbol(context); + const actions = ts.find(infos, info => info.description === description.message)!.actions; + + const data: string[] = []; + data.push(`// ==ORIGINAL==`); + data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/")); + for (const action of actions) { + const { renameLocation, edits } = ts.refactor.extractSymbol.getRefactorEditsToExtractSymbol(context, action.name)!; + assert.lengthOf(edits, 1); + data.push(`// ==SCOPE::${action.description}==`); + const newText = ts.textChanges.applyChanges(sourceFile.text, edits[0].textChanges); + const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation); + data.push(newTextWithRename); + + const { program: diagProgram } = makeProgram({ path, content: newText }, includeLib); + assert.isFalse(hasSyntacticDiagnostics(diagProgram)); } + Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, data.join(newLineCharacter)); + } - function hasSyntacticDiagnostics(program: Program) { - const diags = program.getSyntacticDiagnostics(); - return length(diags) > 0; - } + function makeProgram(f: {path: string, content: string }, includeLib?: boolean) { + const host = ts.projectSystem.createServerHost(includeLib ? [f, ts.projectSystem.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(f.path); + const program = projectService.inferredProjects[0].getLanguageService().getProgram()!; + const autoImportProvider = projectService.inferredProjects[0].getLanguageService().getAutoImportProvider(); + return { program, autoImportProvider }; } - export function testExtractSymbolFailed(caption: string, text: string, description: DiagnosticMessage) { - it(caption, () => { - const t = extractTest(text); - const selectionRange = t.ranges.get("selection"); - if (!selectionRange) { - throw new Error(`Test ${caption} does not specify selection range`); - } - const f = { - path: "/a.ts", - content: t.source - }; - const host = projectSystem.createServerHost([f, projectSystem.libFile]); - const projectService = projectSystem.createProjectService(host); - projectService.openClientFile(f.path); - const program = projectService.inferredProjects[0].getLanguageService().getProgram()!; - const sourceFile = program.getSourceFile(f.path)!; - const context: RefactorContext = { - cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, - program, - file: sourceFile, - startPosition: selectionRange.pos, - endPosition: selectionRange.end, - host: notImplementedHost, - formatContext: formatting.getFormatContext(testFormatSettings, notImplementedHost), - preferences: emptyOptions, - }; - const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromRange(selectionRange)); - assert.isUndefined(rangeToExtract.errors, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); - const infos = refactor.extractSymbol.getRefactorActionsToExtractSymbol(context); - assert.isUndefined(find(infos, info => info.description === description.message)); - }); + function hasSyntacticDiagnostics(program: ts.Program) { + const diags = program.getSyntacticDiagnostics(); + return ts.length(diags) > 0; } } + +export function testExtractSymbolFailed(caption: string, text: string, description: ts.DiagnosticMessage) { + it(caption, () => { + const t = extractTest(text); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${caption} does not specify selection range`); + } + const f = { + path: "/a.ts", + content: t.source + }; + const host = ts.projectSystem.createServerHost([f, ts.projectSystem.libFile]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(f.path); + const program = projectService.inferredProjects[0].getLanguageService().getProgram()!; + const sourceFile = program.getSourceFile(f.path)!; + const context: ts.RefactorContext = { + cancellationToken: { throwIfCancellationRequested: ts.noop, isCancellationRequested: ts.returnFalse }, + program, + file: sourceFile, + startPosition: selectionRange.pos, + endPosition: selectionRange.end, + host: notImplementedHost, + formatContext: ts.formatting.getFormatContext(ts.testFormatSettings, notImplementedHost), + preferences: ts.emptyOptions, + }; + const rangeToExtract = ts.refactor.extractSymbol.getRangeToExtract(sourceFile, ts.createTextSpanFromRange(selectionRange)); + assert.isUndefined(rangeToExtract.errors, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); + const infos = ts.refactor.extractSymbol.getRefactorActionsToExtractSymbol(context); + assert.isUndefined(ts.find(infos, info => info.description === description.message)); + }); +} diff --git a/src/testRunner/unittests/services/extract/ranges.ts b/src/testRunner/unittests/services/extract/ranges.ts index 38d47f028402e..71d35599130c9 100644 --- a/src/testRunner/unittests/services/extract/ranges.ts +++ b/src/testRunner/unittests/services/extract/ranges.ts @@ -1,113 +1,114 @@ -namespace ts { - function testExtractRangeFailed(caption: string, s: string, expectedErrors: string[]) { - return it(caption, () => { - const t = extractTest(s); - const file = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true); - const selectionRange = t.ranges.get("selection"); - if (!selectionRange) { - throw new Error(`Test ${s} does not specify selection range`); - } - const result = refactor.extractSymbol.getRangeToExtract(file, createTextSpanFromRange(selectionRange), /*userRequested*/ false); - assert(result.targetRange === undefined, "failure expected"); - const sortedErrors = result.errors.map(e => e.messageText as string).sort(); - assert.deepEqual(sortedErrors, expectedErrors.sort(), "unexpected errors"); - }); - } +import * as ts from "../../../_namespaces/ts"; - function testExtractRange(caption: string, s: string) { - return it(caption, () => { - const t = extractTest(s); - const f = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true); - const selectionRange = t.ranges.get("selection"); - if (!selectionRange) { - throw new Error(`Test ${s} does not specify selection range`); - } - const result = refactor.extractSymbol.getRangeToExtract(f, createTextSpanFromRange(selectionRange)); - const expectedRange = t.ranges.get("extracted"); - if (expectedRange) { - let pos: number, end: number; - const targetRange = result.targetRange!; - if (isArray(targetRange.range)) { - pos = targetRange.range[0].getStart(f); - end = last(targetRange.range).getEnd(); - } - else { - pos = targetRange.range.getStart(f); - end = targetRange.range.getEnd(); - } - assert.equal(pos, expectedRange.pos, "incorrect pos of range"); - assert.equal(end, expectedRange.end, "incorrect end of range"); +function testExtractRangeFailed(caption: string, s: string, expectedErrors: string[]) { + return it(caption, () => { + const t = ts.extractTest(s); + const file = ts.createSourceFile("a.ts", t.source, ts.ScriptTarget.Latest, /*setParentNodes*/ true); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${s} does not specify selection range`); + } + const result = ts.refactor.extractSymbol.getRangeToExtract(file, ts.createTextSpanFromRange(selectionRange), /*userRequested*/ false); + assert(result.targetRange === undefined, "failure expected"); + const sortedErrors = result.errors.map(e => e.messageText as string).sort(); + assert.deepEqual(sortedErrors, expectedErrors.sort(), "unexpected errors"); + }); +} + +function testExtractRange(caption: string, s: string) { + return it(caption, () => { + const t = ts.extractTest(s); + const f = ts.createSourceFile("a.ts", t.source, ts.ScriptTarget.Latest, /*setParentNodes*/ true); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${s} does not specify selection range`); + } + const result = ts.refactor.extractSymbol.getRangeToExtract(f, ts.createTextSpanFromRange(selectionRange)); + const expectedRange = t.ranges.get("extracted"); + if (expectedRange) { + let pos: number, end: number; + const targetRange = result.targetRange!; + if (ts.isArray(targetRange.range)) { + pos = targetRange.range[0].getStart(f); + end = ts.last(targetRange.range).getEnd(); } else { - assert.isTrue(!result.targetRange, `expected range to extract to be undefined`); + pos = targetRange.range.getStart(f); + end = targetRange.range.getEnd(); } - }); - } + assert.equal(pos, expectedRange.pos, "incorrect pos of range"); + assert.equal(end, expectedRange.end, "incorrect end of range"); + } + else { + assert.isTrue(!result.targetRange, `expected range to extract to be undefined`); + } + }); +} - describe("unittests:: services:: extract:: extractRanges", () => { - describe("get extract range from selection", () => { - testExtractRange("extractRange1", ` +describe("unittests:: services:: extract:: extractRanges", () => { + describe("get extract range from selection", () => { + testExtractRange("extractRange1", ` [#| [$|var x = 1; var y = 2;|]|] `); - testExtractRange("extractRange2", ` + testExtractRange("extractRange2", ` [$|[#|var x = 1; var y = 2|];|] `); - testExtractRange("extractRange3", ` + testExtractRange("extractRange3", ` [#|var x = [$|1|]|]; var y = 2; `); - testExtractRange("extractRange4", ` + testExtractRange("extractRange4", ` var x = [$|10[#|00|]|]; `); - testExtractRange("extractRange5", ` + testExtractRange("extractRange5", ` [$|va[#|r foo = 1; var y = 200|]0;|] `); - testExtractRange("extractRange6", ` + testExtractRange("extractRange6", ` var x = [$|fo[#|o.bar.baz()|]|]; `); - testExtractRange("extractRange7", ` + testExtractRange("extractRange7", ` if ([#|[#extracted|a && b && c && d|]|]) { } `); - testExtractRange("extractRange8", ` + testExtractRange("extractRange8", ` if [#|(a && b && c && d|]) { } `); - testExtractRange("extractRange9", ` + testExtractRange("extractRange9", ` if ([$|a[#|a && b && c && d|]d|]) { } `); - testExtractRange("extractRange10", ` + testExtractRange("extractRange10", ` if (a && b && c && d) { [#| [$|var x = 1; console.log(x);|] |] } `); - testExtractRange("extractRange11", ` + testExtractRange("extractRange11", ` [#| if (a) { return 100; } |] `); - testExtractRange("extractRange12", ` + testExtractRange("extractRange12", ` function foo() { [#| [$|if (a) { } return 100|] |] } `); - testExtractRange("extractRange13", ` + testExtractRange("extractRange13", ` [#| [$|l1: if (x) { break l1; }|]|] `); - testExtractRange("extractRange14", ` + testExtractRange("extractRange14", ` [#| [$|l2: { @@ -116,21 +117,21 @@ namespace ts { break l2; }|]|] `); - testExtractRange("extractRange15", ` + testExtractRange("extractRange15", ` while (true) { [#| if(x) { } break; |] } `); - testExtractRange("extractRange16", ` + testExtractRange("extractRange16", ` while (true) { [#| if(x) { } continue; |] } `); - testExtractRange("extractRange17", ` + testExtractRange("extractRange17", ` l3: { [#| @@ -139,7 +140,7 @@ namespace ts { break l3; |] } `); - testExtractRange("extractRange18", ` + testExtractRange("extractRange18", ` function f() { while (true) { [#| @@ -149,7 +150,7 @@ namespace ts { } } `); - testExtractRange("extractRange19", ` + testExtractRange("extractRange19", ` function f() { while (true) { [#| @@ -160,13 +161,13 @@ namespace ts { } } `); - testExtractRange("extractRange20", ` + testExtractRange("extractRange20", ` function f() { return [#| [$|1 + 2|] |]+ 3; } } `); - testExtractRange("extractRange21", ` + testExtractRange("extractRange21", ` function f(x: number) { [#|[$|try { x++; @@ -177,26 +178,26 @@ namespace ts { } `); - // Variable statements - testExtractRange("extractRange22", `[#|let x = [$|1|];|]`); - testExtractRange("extractRange23", `[#|let x = [$|1|], y;|]`); - testExtractRange("extractRange24", `[#|[$|let x = 1, y = 1;|]|]`); + // Variable statements + testExtractRange("extractRange22", `[#|let x = [$|1|];|]`); + testExtractRange("extractRange23", `[#|let x = [$|1|], y;|]`); + testExtractRange("extractRange24", `[#|[$|let x = 1, y = 1;|]|]`); - // Variable declarations - testExtractRange("extractRange25", `let [#|x = [$|1|]|];`); - testExtractRange("extractRange26", `let [#|x = [$|1|]|], y = 2;`); - testExtractRange("extractRange27", `let x = 1, [#|y = [$|2|]|];`); + // Variable declarations + testExtractRange("extractRange25", `let [#|x = [$|1|]|];`); + testExtractRange("extractRange26", `let [#|x = [$|1|]|], y = 2;`); + testExtractRange("extractRange27", `let x = 1, [#|y = [$|2|]|];`); - // Return statements - testExtractRange("extractRange28", `[#|return [$|1|];|]`); + // Return statements + testExtractRange("extractRange28", `[#|return [$|1|];|]`); - // For statements - testExtractRange("extractRange29", `for ([#|var i = [$|1|]|]; i < 2; i++) {}`); - testExtractRange("extractRange30", `for (var i = [#|[$|1|]|]; i < 2; i++) {}`); - }); + // For statements + testExtractRange("extractRange29", `for ([#|var i = [$|1|]|]; i < 2; i++) {}`); + testExtractRange("extractRange30", `for (var i = [#|[$|1|]|]; i < 2; i++) {}`); + }); - testExtractRangeFailed("extractRangeFailed1", - ` + testExtractRangeFailed("extractRangeFailed1", + ` namespace A { function f() { [#| @@ -208,10 +209,10 @@ function f() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); - testExtractRangeFailed("extractRangeFailed2", - ` + testExtractRangeFailed("extractRangeFailed2", + ` namespace A { function f() { while (true) { @@ -225,10 +226,10 @@ function f() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); - testExtractRangeFailed("extractRangeFailed3", - ` + testExtractRangeFailed("extractRangeFailed3", + ` namespace A { function f() { while (true) { @@ -242,10 +243,10 @@ function f() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); - testExtractRangeFailed("extractRangeFailed4", - ` + testExtractRangeFailed("extractRangeFailed4", + ` namespace A { function f() { l1: { @@ -259,10 +260,10 @@ function f() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange.message]); - testExtractRangeFailed("extractRangeFailed5", - ` + testExtractRangeFailed("extractRangeFailed5", + ` namespace A { function f() { [#| @@ -278,10 +279,10 @@ function f2() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); - testExtractRangeFailed("extractRangeFailed6", - ` + testExtractRangeFailed("extractRangeFailed6", + ` namespace A { function f() { [#| @@ -297,10 +298,10 @@ function f2() { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]); - testExtractRangeFailed("extractRangeFailed7", - ` + testExtractRangeFailed("extractRangeFailed7", + ` function test(x: number) { while (x) { x--; @@ -308,10 +309,10 @@ while (x) { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); - testExtractRangeFailed("extractRangeFailed8", - ` + testExtractRangeFailed("extractRangeFailed8", + ` function test(x: number) { switch (x) { case 1: @@ -319,23 +320,23 @@ switch (x) { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); - testExtractRangeFailed("extractRangeFailed9", - `var x = ([#||]1 + 2);`, - [refactor.extractSymbol.Messages.cannotExtractEmpty.message]); + testExtractRangeFailed("extractRangeFailed9", + `var x = ([#||]1 + 2);`, + [ts.refactor.extractSymbol.Messages.cannotExtractEmpty.message]); - testExtractRangeFailed("extractRangeFailed10", - ` + testExtractRangeFailed("extractRangeFailed10", + ` function f() { return 1 + [#|2 + 3|]; } } `, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRange.message]); - testExtractRangeFailed("extractRangeFailed11", - ` + testExtractRangeFailed("extractRangeFailed11", + ` function f(x: number) { while (true) { [#|try { @@ -347,62 +348,61 @@ switch (x) { } } `, - [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]); - testExtractRangeFailed("extractRangeFailed12", - `let [#|x|];`, - [refactor.extractSymbol.Messages.statementOrExpressionExpected.message]); + testExtractRangeFailed("extractRangeFailed12", + `let [#|x|];`, + [ts.refactor.extractSymbol.Messages.statementOrExpressionExpected.message]); - testExtractRangeFailed("extractRangeFailed13", - `[#|return;|]`, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + testExtractRangeFailed("extractRangeFailed13", + `[#|return;|]`, + [ts.refactor.extractSymbol.Messages.cannotExtractRange.message]); - testExtractRangeFailed("extractRangeFailed14", - ` + testExtractRangeFailed("extractRangeFailed14", + ` switch(1) { case [#|1: break;|] } `, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRange.message]); - testExtractRangeFailed("extractRangeFailed15", - ` + testExtractRangeFailed("extractRangeFailed15", + ` switch(1) { case [#|1: break|]; } `, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRange.message]); - // Documentation only - it would be nice if the result were [$|1|] - testExtractRangeFailed("extractRangeFailed16", - ` + // Documentation only - it would be nice if the result were [$|1|] + testExtractRangeFailed("extractRangeFailed16", + ` switch(1) { [#|case 1|]: break; } `, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRange.message]); - // Documentation only - it would be nice if the result were [$|1|] - testExtractRangeFailed("extractRangeFailed17", - ` + // Documentation only - it would be nice if the result were [$|1|] + testExtractRangeFailed("extractRangeFailed17", + ` switch(1) { [#|case 1:|] break; } `, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + [ts.refactor.extractSymbol.Messages.cannotExtractRange.message]); - testExtractRangeFailed("extractRangeFailed18", - `[#|{ 1;|] }`, - [refactor.extractSymbol.Messages.cannotExtractRange.message]); + testExtractRangeFailed("extractRangeFailed18", + `[#|{ 1;|] }`, + [ts.refactor.extractSymbol.Messages.cannotExtractRange.message]); - testExtractRangeFailed("extractRangeFailed19", - `[#|/** @type {number} */|] const foo = 1;`, - [refactor.extractSymbol.Messages.cannotExtractJSDoc.message]); + testExtractRangeFailed("extractRangeFailed19", + `[#|/** @type {number} */|] const foo = 1;`, + [ts.refactor.extractSymbol.Messages.cannotExtractJSDoc.message]); - testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.cannotExtractIdentifier.message]); - }); -} + testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [ts.refactor.extractSymbol.Messages.cannotExtractIdentifier.message]); +}); diff --git a/src/testRunner/unittests/services/extract/symbolWalker.ts b/src/testRunner/unittests/services/extract/symbolWalker.ts index 58d9dcb577f39..0f005bc712b8a 100644 --- a/src/testRunner/unittests/services/extract/symbolWalker.ts +++ b/src/testRunner/unittests/services/extract/symbolWalker.ts @@ -1,45 +1,46 @@ -namespace ts { - describe("unittests:: services:: extract:: Symbol Walker", () => { - function test(description: string, source: string, verifier: (file: SourceFile, checker: TypeChecker) => void) { - it(description, () => { - const result = Harness.Compiler.compileFiles([{ - unitName: "main.ts", - content: source - }], [], {}, {}, "/"); - const file = result.program!.getSourceFile("main.ts")!; - const checker = result.program!.getTypeChecker(); - verifier(file, checker); - }); - } +import * as ts from "../../../_namespaces/ts"; +import * as Harness from "../../../_namespaces/Harness"; + +describe("unittests:: services:: extract:: Symbol Walker", () => { + function test(description: string, source: string, verifier: (file: ts.SourceFile, checker: ts.TypeChecker) => void) { + it(description, () => { + const result = Harness.Compiler.compileFiles([{ + unitName: "main.ts", + content: source + }], [], {}, {}, "/"); + const file = result.program!.getSourceFile("main.ts")!; + const checker = result.program!.getTypeChecker(); + verifier(file, checker); + }); + } - test("can be created", ` + test("can be created", ` interface Bar { x: number; y: number; history: Bar[]; } export default function foo(a: number, b: Bar): void {}`, (file, checker) => { - let foundCount = 0; - let stdLibRefSymbols = 0; - const expectedSymbols = ["default", "a", "b", "Bar", "x", "y", "history"]; - const walker = checker.getSymbolWalker(symbol => { - const isStdLibSymbol = forEach(symbol.declarations, d => { - return getSourceFileOfNode(d).hasNoDefaultLib; - }); - if (isStdLibSymbol) { - stdLibRefSymbols++; - return false; // Don't traverse into the stdlib. That's unnecessary for this test. - } - assert.equal(symbol.name, expectedSymbols[foundCount]); - foundCount++; - return true; + let foundCount = 0; + let stdLibRefSymbols = 0; + const expectedSymbols = ["default", "a", "b", "Bar", "x", "y", "history"]; + const walker = checker.getSymbolWalker(symbol => { + const isStdLibSymbol = ts.forEach(symbol.declarations, d => { + return ts.getSourceFileOfNode(d).hasNoDefaultLib; }); - const symbols = checker.getExportsOfModule(file.symbol); - for (const symbol of symbols) { - walker.walkSymbol(symbol); + if (isStdLibSymbol) { + stdLibRefSymbols++; + return false; // Don't traverse into the stdlib. That's unnecessary for this test. } - assert.equal(foundCount, expectedSymbols.length); - assert.equal(stdLibRefSymbols, 1); // Expect 1 stdlib entry symbol - the implicit Array referenced by Bar.history + assert.equal(symbol.name, expectedSymbols[foundCount]); + foundCount++; + return true; }); + const symbols = checker.getExportsOfModule(file.symbol); + for (const symbol of symbols) { + walker.walkSymbol(symbol); + } + assert.equal(foundCount, expectedSymbols.length); + assert.equal(stdLibRefSymbols, 1); // Expect 1 stdlib entry symbol - the implicit Array referenced by Bar.history }); -} +}); diff --git a/src/testRunner/unittests/services/hostNewLineSupport.ts b/src/testRunner/unittests/services/hostNewLineSupport.ts index 762b8e3ddc159..d18b593d99125 100644 --- a/src/testRunner/unittests/services/hostNewLineSupport.ts +++ b/src/testRunner/unittests/services/hostNewLineSupport.ts @@ -1,77 +1,78 @@ -namespace ts { - describe("unittests:: services:: hostNewLineSupport", () => { - function testLSWithFiles(settings: CompilerOptions, files: Harness.Compiler.TestFile[]) { - function snapFor(path: string): IScriptSnapshot | undefined { - if (path === "lib.d.ts") { - return ScriptSnapshot.fromString(""); - } - const result = find(files, f => f.unitName === path); - return result && ScriptSnapshot.fromString(result.content); +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; + +describe("unittests:: services:: hostNewLineSupport", () => { + function testLSWithFiles(settings: ts.CompilerOptions, files: Harness.Compiler.TestFile[]) { + function snapFor(path: string): ts.IScriptSnapshot | undefined { + if (path === "lib.d.ts") { + return ts.ScriptSnapshot.fromString(""); } - const lshost: LanguageServiceHost = { - getCompilationSettings: () => settings, - getScriptFileNames: () => map(files, f => f.unitName), - getScriptVersion: () => "1", - getScriptSnapshot: name => snapFor(name), - getDefaultLibFileName: () => "lib.d.ts", - getCurrentDirectory: () => "", - readFile: name => { - const snap = snapFor(name); - if (!snap) return undefined; - return snap.getText(0, snap.getLength()); - }, - fileExists: name => !!snapFor(name), - }; - return createLanguageService(lshost); + const result = ts.find(files, f => f.unitName === path); + return result && ts.ScriptSnapshot.fromString(result.content); } + const lshost: ts.LanguageServiceHost = { + getCompilationSettings: () => settings, + getScriptFileNames: () => ts.map(files, f => f.unitName), + getScriptVersion: () => "1", + getScriptSnapshot: name => snapFor(name), + getDefaultLibFileName: () => "lib.d.ts", + getCurrentDirectory: () => "", + readFile: name => { + const snap = snapFor(name); + if (!snap) return undefined; + return snap.getText(0, snap.getLength()); + }, + fileExists: name => !!snapFor(name), + }; + return ts.createLanguageService(lshost); + } - function verifyNewLines(content: string, options: CompilerOptions) { - const ls = testLSWithFiles(options, [{ - content, - fileOptions: {}, - unitName: "input.ts" - }]); - const result = ls.getEmitOutput("input.ts"); - assert(!result.emitSkipped, "emit was skipped"); - assert(result.outputFiles.length === 1, "a number of files other than 1 was output"); - assert(result.outputFiles[0].name === "input.js", `Expected output file name input.js, but got ${result.outputFiles[0].name}`); - assert(result.outputFiles[0].text.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /\r\n/ : /[^\r]\n/), "expected to find appropriate newlines"); - assert(!result.outputFiles[0].text.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /[^\r]\n/ : /\r\n/), "expected not to find inappropriate newlines"); - } + function verifyNewLines(content: string, options: ts.CompilerOptions) { + const ls = testLSWithFiles(options, [{ + content, + fileOptions: {}, + unitName: "input.ts" + }]); + const result = ls.getEmitOutput("input.ts"); + assert(!result.emitSkipped, "emit was skipped"); + assert(result.outputFiles.length === 1, "a number of files other than 1 was output"); + assert(result.outputFiles[0].name === "input.js", `Expected output file name input.js, but got ${result.outputFiles[0].name}`); + assert(result.outputFiles[0].text.match(options.newLine === ts.NewLineKind.CarriageReturnLineFeed ? /\r\n/ : /[^\r]\n/), "expected to find appropriate newlines"); + assert(!result.outputFiles[0].text.match(options.newLine === ts.NewLineKind.CarriageReturnLineFeed ? /[^\r]\n/ : /\r\n/), "expected not to find inappropriate newlines"); + } - function verifyBothNewLines(content: string) { - verifyNewLines(content, { newLine: NewLineKind.CarriageReturnLineFeed }); - verifyNewLines(content, { newLine: NewLineKind.LineFeed }); - } + function verifyBothNewLines(content: string) { + verifyNewLines(content, { newLine: ts.NewLineKind.CarriageReturnLineFeed }); + verifyNewLines(content, { newLine: ts.NewLineKind.LineFeed }); + } - function verifyOutliningSpanNewLines(content: string, options: CompilerOptions) { - const ls = testLSWithFiles(options, [{ - content, - fileOptions: {}, - unitName: "input.ts" - }]); - const span = ls.getOutliningSpans("input.ts")[0]; - const textAfterSpanCollapse = content.substring(span.textSpan.start + span.textSpan.length); - assert(textAfterSpanCollapse.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /\r\n/ : /[^\r]\n/), "expected to find appropriate newlines"); - assert(!textAfterSpanCollapse.match(options.newLine === NewLineKind.CarriageReturnLineFeed ? /[^\r]\n/ : /\r\n/), "expected not to find inappropriate newlines"); - } + function verifyOutliningSpanNewLines(content: string, options: ts.CompilerOptions) { + const ls = testLSWithFiles(options, [{ + content, + fileOptions: {}, + unitName: "input.ts" + }]); + const span = ls.getOutliningSpans("input.ts")[0]; + const textAfterSpanCollapse = content.substring(span.textSpan.start + span.textSpan.length); + assert(textAfterSpanCollapse.match(options.newLine === ts.NewLineKind.CarriageReturnLineFeed ? /\r\n/ : /[^\r]\n/), "expected to find appropriate newlines"); + assert(!textAfterSpanCollapse.match(options.newLine === ts.NewLineKind.CarriageReturnLineFeed ? /[^\r]\n/ : /\r\n/), "expected not to find inappropriate newlines"); + } - it("should exist and respect provided compiler options", () => { - verifyBothNewLines(` + it("should exist and respect provided compiler options", () => { + verifyBothNewLines(` function foo() { return 2 + 2; } `); - }); + }); - it("should respect CRLF line endings around outlining spans", () => { - verifyOutliningSpanNewLines("// comment not included\r\n// #region name\r\nlet x: string = \"x\";\r\n// #endregion name\r\n", - { newLine: NewLineKind.CarriageReturnLineFeed }); - }); + it("should respect CRLF line endings around outlining spans", () => { + verifyOutliningSpanNewLines("// comment not included\r\n// #region name\r\nlet x: string = \"x\";\r\n// #endregion name\r\n", + { newLine: ts.NewLineKind.CarriageReturnLineFeed }); + }); - it("should respect LF line endings around outlining spans", () => { - verifyOutliningSpanNewLines("// comment not included\n// #region name\nlet x: string = \"x\";\n// #endregion name\n\n", - { newLine: NewLineKind.LineFeed }); - }); + it("should respect LF line endings around outlining spans", () => { + verifyOutliningSpanNewLines("// comment not included\n// #region name\nlet x: string = \"x\";\n// #endregion name\n\n", + { newLine: ts.NewLineKind.LineFeed }); }); -} +}); diff --git a/src/testRunner/unittests/services/languageService.ts b/src/testRunner/unittests/services/languageService.ts index b93215f4df2cc..27b75cadfe429 100644 --- a/src/testRunner/unittests/services/languageService.ts +++ b/src/testRunner/unittests/services/languageService.ts @@ -1,9 +1,10 @@ -namespace ts { - const _chai: typeof import("chai") = require("chai"); - const expect: typeof _chai.expect = _chai.expect; - describe("unittests:: services:: languageService", () => { - const files: {[index: string]: string} = { - "foo.ts": `import Vue from "./vue"; +import { expect } from "chai"; + +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: services:: languageService", () => { + const files: {[index: string]: string} = { + "foo.ts": `import Vue from "./vue"; import Component from "./vue-class-component"; import { vueTemplateHtml } from "./variables"; @@ -12,263 +13,262 @@ import { vueTemplateHtml } from "./variables"; }) class Carousel extends Vue { }`, - "variables.ts": `export const vueTemplateHtml = \`
\`;`, - "vue.d.ts": `export namespace Vue { export type Config = { template: string }; }`, - "vue-class-component.d.ts": `import Vue from "./vue"; + "variables.ts": `export const vueTemplateHtml = \`
\`;`, + "vue.d.ts": `export namespace Vue { export type Config = { template: string }; }`, + "vue-class-component.d.ts": `import Vue from "./vue"; export function Component(x: Config): any;` - }; + }; - function createLanguageService() { - return ts.createLanguageService({ - getCompilationSettings() { - return {}; - }, - getScriptFileNames() { - return ["foo.ts", "variables.ts", "vue.d.ts", "vue-class-component.d.ts"]; - }, - getScriptVersion(_fileName) { - return ""; - }, - getScriptSnapshot(fileName) { - if (fileName === ".ts") { - return ScriptSnapshot.fromString(""); - } - return ScriptSnapshot.fromString(files[fileName] || ""); - }, - getCurrentDirectory: () => ".", - getDefaultLibFileName(options) { - return getDefaultLibFilePath(options); - }, - fileExists: name => !!files[name], - readFile: name => files[name] - }); - } - // Regression test for GH #18245 - bug in single line comment writer caused a debug assertion when attempting - // to write an alias to a module's default export was referrenced across files and had no default export - it("should be able to create a language service which can respond to deinition requests without throwing", () => { - const languageService = createLanguageService(); - const definitions = languageService.getDefinitionAtPosition("foo.ts", 160); // 160 is the latter `vueTemplateHtml` position - expect(definitions).to.exist; // eslint-disable-line @typescript-eslint/no-unused-expressions + function createLanguageService() { + return ts.createLanguageService({ + getCompilationSettings() { + return {}; + }, + getScriptFileNames() { + return ["foo.ts", "variables.ts", "vue.d.ts", "vue-class-component.d.ts"]; + }, + getScriptVersion(_fileName) { + return ""; + }, + getScriptSnapshot(fileName) { + if (fileName === ".ts") { + return ts.ScriptSnapshot.fromString(""); + } + return ts.ScriptSnapshot.fromString(files[fileName] || ""); + }, + getCurrentDirectory: () => ".", + getDefaultLibFileName(options) { + return ts.getDefaultLibFilePath(options); + }, + fileExists: name => !!files[name], + readFile: name => files[name] }); + } + // Regression test for GH #18245 - bug in single line comment writer caused a debug assertion when attempting + // to write an alias to a module's default export was referrenced across files and had no default export + it("should be able to create a language service which can respond to deinition requests without throwing", () => { + const languageService = createLanguageService(); + const definitions = languageService.getDefinitionAtPosition("foo.ts", 160); // 160 is the latter `vueTemplateHtml` position + expect(definitions).to.exist; // eslint-disable-line @typescript-eslint/no-unused-expressions + }); - it("getEmitOutput on language service has way to force dts emit", () => { - const languageService = createLanguageService(); - assert.deepEqual( - languageService.getEmitOutput( - "foo.ts", - /*emitOnlyDtsFiles*/ true - ), - { - emitSkipped: true, - diagnostics: emptyArray, - outputFiles: emptyArray, - } - ); + it("getEmitOutput on language service has way to force dts emit", () => { + const languageService = createLanguageService(); + assert.deepEqual( + languageService.getEmitOutput( + "foo.ts", + /*emitOnlyDtsFiles*/ true + ), + { + emitSkipped: true, + diagnostics: ts.emptyArray, + outputFiles: ts.emptyArray, + } + ); - assert.deepEqual( - languageService.getEmitOutput( - "foo.ts", - /*emitOnlyDtsFiles*/ true, - /*forceDtsEmit*/ true - ), - { - emitSkipped: false, - diagnostics: emptyArray, - outputFiles: [{ - name: "foo.d.ts", - text: "export {};\r\n", - writeByteOrderMark: false - }], - } - ); - }); + assert.deepEqual( + languageService.getEmitOutput( + "foo.ts", + /*emitOnlyDtsFiles*/ true, + /*forceDtsEmit*/ true + ), + { + emitSkipped: false, + diagnostics: ts.emptyArray, + outputFiles: [{ + name: "foo.d.ts", + text: "export {};\r\n", + writeByteOrderMark: false + }], + } + ); + }); - describe("detects program upto date correctly", () => { - function verifyProgramUptoDate(useProjectVersion: boolean) { - let projectVersion = "1"; - const files = new Map(); - files.set("/project/root.ts", { version: "1", text: `import { foo } from "./other"` }); - files.set("/project/other.ts", { version: "1", text: `export function foo() { }` }); - files.set("/lib/lib.d.ts", { version: "1", text: projectSystem.libFile.content }); - const host: LanguageServiceHost = { - useCaseSensitiveFileNames: returnTrue, - getCompilationSettings: getDefaultCompilerOptions, - fileExists: path => files.has(path), - readFile: path => files.get(path)?.text, - getProjectVersion: !useProjectVersion ? undefined : () => projectVersion, - getScriptFileNames: () => ["/project/root.ts"], - getScriptVersion: path => files.get(path)?.version || "", - getScriptSnapshot: path => { - const text = files.get(path)?.text; - return text ? ScriptSnapshot.fromString(text) : undefined; - }, - getCurrentDirectory: () => "/project", - getDefaultLibFileName: () => "/lib/lib.d.ts" - }; - const ls = ts.createLanguageService(host); - const program1 = ls.getProgram()!; - const program2 = ls.getProgram()!; - assert.strictEqual(program1, program2); - verifyProgramFiles(program1); + describe("detects program upto date correctly", () => { + function verifyProgramUptoDate(useProjectVersion: boolean) { + let projectVersion = "1"; + const files = new ts.Map(); + files.set("/project/root.ts", { version: "1", text: `import { foo } from "./other"` }); + files.set("/project/other.ts", { version: "1", text: `export function foo() { }` }); + files.set("/lib/lib.d.ts", { version: "1", text: ts.projectSystem.libFile.content }); + const host: ts.LanguageServiceHost = { + useCaseSensitiveFileNames: ts.returnTrue, + getCompilationSettings: ts.getDefaultCompilerOptions, + fileExists: path => files.has(path), + readFile: path => files.get(path)?.text, + getProjectVersion: !useProjectVersion ? undefined : () => projectVersion, + getScriptFileNames: () => ["/project/root.ts"], + getScriptVersion: path => files.get(path)?.version || "", + getScriptSnapshot: path => { + const text = files.get(path)?.text; + return text ? ts.ScriptSnapshot.fromString(text) : undefined; + }, + getCurrentDirectory: () => "/project", + getDefaultLibFileName: () => "/lib/lib.d.ts" + }; + const ls = ts.createLanguageService(host); + const program1 = ls.getProgram()!; + const program2 = ls.getProgram()!; + assert.strictEqual(program1, program2); + verifyProgramFiles(program1); - // Change other - projectVersion = "2"; - files.set("/project/other.ts", { version: "2", text: `export function foo() { } export function bar() { }` }); - const program3 = ls.getProgram()!; - assert.notStrictEqual(program2, program3); - verifyProgramFiles(program3); + // Change other + projectVersion = "2"; + files.set("/project/other.ts", { version: "2", text: `export function foo() { } export function bar() { }` }); + const program3 = ls.getProgram()!; + assert.notStrictEqual(program2, program3); + verifyProgramFiles(program3); - // change root - projectVersion = "3"; - files.set("/project/root.ts", { version: "2", text: `import { foo, bar } from "./other"` }); - const program4 = ls.getProgram()!; - assert.notStrictEqual(program3, program4); - verifyProgramFiles(program4); + // change root + projectVersion = "3"; + files.set("/project/root.ts", { version: "2", text: `import { foo, bar } from "./other"` }); + const program4 = ls.getProgram()!; + assert.notStrictEqual(program3, program4); + verifyProgramFiles(program4); - function verifyProgramFiles(program: Program) { - assert.deepEqual( - program.getSourceFiles().map(f => f.fileName), - ["/lib/lib.d.ts", "/project/other.ts", "/project/root.ts"] - ); - } + function verifyProgramFiles(program: ts.Program) { + assert.deepEqual( + program.getSourceFiles().map(f => f.fileName), + ["/lib/lib.d.ts", "/project/other.ts", "/project/root.ts"] + ); } - it("when host implements getProjectVersion", () => { - verifyProgramUptoDate(/*useProjectVersion*/ true); - }); - it("when host does not implement getProjectVersion", () => { - verifyProgramUptoDate(/*useProjectVersion*/ false); - }); + } + it("when host implements getProjectVersion", () => { + verifyProgramUptoDate(/*useProjectVersion*/ true); + }); + it("when host does not implement getProjectVersion", () => { + verifyProgramUptoDate(/*useProjectVersion*/ false); }); + }); - describe("detects program upto date when new file is added to the referenced project", () => { - function setup(useSourceOfProjectReferenceRedirect: (() => boolean) | undefined) { - const config1: TestFSWithWatch.File = { - path: `${tscWatch.projectRoot}/projects/project1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - exclude: ["temp"] - }) - }; - const class1: TestFSWithWatch.File = { - path: `${tscWatch.projectRoot}/projects/project1/class1.ts`, - content: `class class1 {}` - }; - const class1Dts: TestFSWithWatch.File = { - path: `${tscWatch.projectRoot}/projects/project1/class1.d.ts`, - content: `declare class class1 {}` - }; - const config2: TestFSWithWatch.File = { - path: `${tscWatch.projectRoot}/projects/project2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - references: [ - { path: "../project1" } - ] - }) - }; - const class2: TestFSWithWatch.File = { - path: `${tscWatch.projectRoot}/projects/project2/class2.ts`, - content: `class class2 {}` - }; - const system = projectSystem.createServerHost([config1, class1, class1Dts, config2, class2, projectSystem.libFile]); - const result = getParsedCommandLineOfConfigFile(`${tscWatch.projectRoot}/projects/project2/tsconfig.json`, /*optionsToExtend*/ undefined, { - useCaseSensitiveFileNames: true, - fileExists: path => system.fileExists(path), - readFile: path => system.readFile(path), - getCurrentDirectory: () => system.getCurrentDirectory(), - readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), - onUnRecoverableConfigFileDiagnostic: noop, - })!; - const host: LanguageServiceHost = { - useCaseSensitiveFileNames: returnTrue, - useSourceOfProjectReferenceRedirect, - getCompilationSettings: () => result.options, - fileExists: path => system.fileExists(path), - readFile: path => system.readFile(path), - getScriptFileNames: () => result.fileNames, - getScriptVersion: path => { - const text = system.readFile(path); - return text !== undefined ? system.createHash(path) : ""; + describe("detects program upto date when new file is added to the referenced project", () => { + function setup(useSourceOfProjectReferenceRedirect: (() => boolean) | undefined) { + const config1: ts.TestFSWithWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true }, - getScriptSnapshot: path => { - const text = system.readFile(path); - return text ? ScriptSnapshot.fromString(text) : undefined; + exclude: ["temp"] + }) + }; + const class1: ts.TestFSWithWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/class1.ts`, + content: `class class1 {}` + }; + const class1Dts: ts.TestFSWithWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/class1.d.ts`, + content: `declare class class1 {}` + }; + const config2: ts.TestFSWithWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true }, - readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), - getCurrentDirectory: () => system.getCurrentDirectory(), - getDefaultLibFileName: () => projectSystem.libFile.path, - getProjectReferences: () => result.projectReferences, - }; - const ls = ts.createLanguageService(host); - return { system, ls, class1, class1Dts, class2 }; - } - it("detects program upto date when new file is added to the referenced project", () => { - const { ls, system, class1, class2 } = setup(returnTrue); - assert.deepEqual( - ls.getProgram()!.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1.path, class2.path] - ); - // Add new file to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - system.writeFile(class3, `class class3 {}`); - const program = ls.getProgram()!; - assert.deepEqual( - program.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1.path, class3, class2.path] - ); - // Add excluded file to referenced project - system.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - assert.strictEqual(ls.getProgram(), program); - // Add output from new class to referenced project - system.writeFile(`${tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`); - assert.strictEqual(ls.getProgram(), program); - }); + references: [ + { path: "../project1" } + ] + }) + }; + const class2: ts.TestFSWithWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project2/class2.ts`, + content: `class class2 {}` + }; + const system = ts.projectSystem.createServerHost([config1, class1, class1Dts, config2, class2, ts.projectSystem.libFile]); + const result = ts.getParsedCommandLineOfConfigFile(`${ts.tscWatch.projectRoot}/projects/project2/tsconfig.json`, /*optionsToExtend*/ undefined, { + useCaseSensitiveFileNames: true, + fileExists: path => system.fileExists(path), + readFile: path => system.readFile(path), + getCurrentDirectory: () => system.getCurrentDirectory(), + readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), + onUnRecoverableConfigFileDiagnostic: ts.noop, + })!; + const host: ts.LanguageServiceHost = { + useCaseSensitiveFileNames: ts.returnTrue, + useSourceOfProjectReferenceRedirect, + getCompilationSettings: () => result.options, + fileExists: path => system.fileExists(path), + readFile: path => system.readFile(path), + getScriptFileNames: () => result.fileNames, + getScriptVersion: path => { + const text = system.readFile(path); + return text !== undefined ? system.createHash(path) : ""; + }, + getScriptSnapshot: path => { + const text = system.readFile(path); + return text ? ts.ScriptSnapshot.fromString(text) : undefined; + }, + readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), + getCurrentDirectory: () => system.getCurrentDirectory(), + getDefaultLibFileName: () => ts.projectSystem.libFile.path, + getProjectReferences: () => result.projectReferences, + }; + const ls = ts.createLanguageService(host); + return { system, ls, class1, class1Dts, class2 }; + } + it("detects program upto date when new file is added to the referenced project", () => { + const { ls, system, class1, class2 } = setup(ts.returnTrue); + assert.deepEqual( + ls.getProgram()!.getSourceFiles().map(f => f.fileName), + [ts.projectSystem.libFile.path, class1.path, class2.path] + ); + // Add new file to referenced project + const class3 = `${ts.tscWatch.projectRoot}/projects/project1/class3.ts`; + system.writeFile(class3, `class class3 {}`); + const program = ls.getProgram()!; + assert.deepEqual( + program.getSourceFiles().map(f => f.fileName), + [ts.projectSystem.libFile.path, class1.path, class3, class2.path] + ); + // Add excluded file to referenced project + system.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + assert.strictEqual(ls.getProgram(), program); + // Add output from new class to referenced project + system.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`); + assert.strictEqual(ls.getProgram(), program); + }); - it("detects program upto date when new file is added to the referenced project without useSourceOfProjectReferenceRedirect", () => { - const { ls, system, class1Dts, class2 } = setup(/*useSourceOfProjectReferenceRedirect*/ undefined); - const program1 = ls.getProgram()!; - assert.deepEqual( - program1.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1Dts.path, class2.path] - ); - // Add new file to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - system.writeFile(class3, `class class3 {}`); - assert.notStrictEqual(ls.getProgram(), program1); - assert.deepEqual( - ls.getProgram()!.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1Dts.path, class2.path] - ); - // Add class3 output - const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; - system.writeFile(class3Dts, `declare class class3 {}`); - const program2 = ls.getProgram()!; - assert.deepEqual( - program2.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path] - ); - // Add excluded file to referenced project - system.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - assert.strictEqual(ls.getProgram(), program2); - // Delete output from new class to referenced project - system.deleteFile(class3Dts); - assert.deepEqual( - ls.getProgram()!.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1Dts.path, class2.path] - ); - // Write output again - system.writeFile(class3Dts, `declare class class3 {}`); - assert.deepEqual( - ls.getProgram()!.getSourceFiles().map(f => f.fileName), - [projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path] - ); - }); + it("detects program upto date when new file is added to the referenced project without useSourceOfProjectReferenceRedirect", () => { + const { ls, system, class1Dts, class2 } = setup(/*useSourceOfProjectReferenceRedirect*/ undefined); + const program1 = ls.getProgram()!; + assert.deepEqual( + program1.getSourceFiles().map(f => f.fileName), + [ts.projectSystem.libFile.path, class1Dts.path, class2.path] + ); + // Add new file to referenced project + const class3 = `${ts.tscWatch.projectRoot}/projects/project1/class3.ts`; + system.writeFile(class3, `class class3 {}`); + assert.notStrictEqual(ls.getProgram(), program1); + assert.deepEqual( + ls.getProgram()!.getSourceFiles().map(f => f.fileName), + [ts.projectSystem.libFile.path, class1Dts.path, class2.path] + ); + // Add class3 output + const class3Dts = `${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`; + system.writeFile(class3Dts, `declare class class3 {}`); + const program2 = ls.getProgram()!; + assert.deepEqual( + program2.getSourceFiles().map(f => f.fileName), + [ts.projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path] + ); + // Add excluded file to referenced project + system.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + assert.strictEqual(ls.getProgram(), program2); + // Delete output from new class to referenced project + system.deleteFile(class3Dts); + assert.deepEqual( + ls.getProgram()!.getSourceFiles().map(f => f.fileName), + [ts.projectSystem.libFile.path, class1Dts.path, class2.path] + ); + // Write output again + system.writeFile(class3Dts, `declare class class3 {}`); + assert.deepEqual( + ls.getProgram()!.getSourceFiles().map(f => f.fileName), + [ts.projectSystem.libFile.path, class1Dts.path, class3Dts, class2.path] + ); }); }); -} +}); diff --git a/src/testRunner/unittests/services/organizeImports.ts b/src/testRunner/unittests/services/organizeImports.ts index 4ddd1b608d003..05fcabe6622e2 100644 --- a/src/testRunner/unittests/services/organizeImports.ts +++ b/src/testRunner/unittests/services/organizeImports.ts @@ -1,366 +1,368 @@ -namespace ts { - describe("unittests:: services:: organizeImports", () => { - describe("Sort imports", () => { - it("Sort - non-relative vs non-relative", () => { - assertSortsBefore( - `import y from "lib1";`, - `import x from "lib2";`); - }); - - it("Sort - relative vs relative", () => { - assertSortsBefore( - `import y from "./lib1";`, - `import x from "./lib2";`); - }); +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; + +describe("unittests:: services:: organizeImports", () => { + describe("Sort imports", () => { + it("Sort - non-relative vs non-relative", () => { + assertSortsBefore( + `import y from "lib1";`, + `import x from "lib2";`); + }); - it("Sort - relative vs non-relative", () => { - assertSortsBefore( - `import y from "lib";`, - `import x from "./lib";`); - }); + it("Sort - relative vs relative", () => { + assertSortsBefore( + `import y from "./lib1";`, + `import x from "./lib2";`); + }); - it("Sort - case-insensitive", () => { - assertSortsBefore( - `import y from "a";`, - `import x from "Z";`); - assertSortsBefore( - `import y from "A";`, - `import x from "z";`); - }); + it("Sort - relative vs non-relative", () => { + assertSortsBefore( + `import y from "lib";`, + `import x from "./lib";`); + }); - function assertSortsBefore(importString1: string, importString2: string) { - const [{moduleSpecifier: moduleSpecifier1}, {moduleSpecifier: moduleSpecifier2}] = parseImports(importString1, importString2); - assert.equal(OrganizeImports.compareModuleSpecifiers(moduleSpecifier1, moduleSpecifier2), Comparison.LessThan); - assert.equal(OrganizeImports.compareModuleSpecifiers(moduleSpecifier2, moduleSpecifier1), Comparison.GreaterThan); - } + it("Sort - case-insensitive", () => { + assertSortsBefore( + `import y from "a";`, + `import x from "Z";`); + assertSortsBefore( + `import y from "A";`, + `import x from "z";`); }); - describe("Coalesce imports", () => { - it("No imports", () => { - assert.isEmpty(OrganizeImports.coalesceImports([])); - }); + function assertSortsBefore(importString1: string, importString2: string) { + const [{moduleSpecifier: moduleSpecifier1}, {moduleSpecifier: moduleSpecifier2}] = parseImports(importString1, importString2); + assert.equal(ts.OrganizeImports.compareModuleSpecifiers(moduleSpecifier1, moduleSpecifier2), ts.Comparison.LessThan); + assert.equal(ts.OrganizeImports.compareModuleSpecifiers(moduleSpecifier2, moduleSpecifier1), ts.Comparison.GreaterThan); + } + }); - it("Sort specifiers - case-insensitive", () => { - const sortedImports = parseImports(`import { default as M, a as n, B, y, Z as O } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports(`import { a as n, B, default as M, y, Z as O } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + describe("Coalesce imports", () => { + it("No imports", () => { + assert.isEmpty(ts.OrganizeImports.coalesceImports([])); + }); - it("Combine side-effect-only imports", () => { - const sortedImports = parseImports( - `import "lib";`, - `import "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports(`import "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Sort specifiers - case-insensitive", () => { + const sortedImports = parseImports(`import { default as M, a as n, B, y, Z as O } from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports(`import { a as n, B, default as M, y, Z as O } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine namespace imports", () => { - const sortedImports = parseImports( - `import * as x from "lib";`, - `import * as y from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine side-effect-only imports", () => { + const sortedImports = parseImports( + `import "lib";`, + `import "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports(`import "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine default imports", () => { - const sortedImports = parseImports( - `import x from "lib";`, - `import y from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports(`import { default as x, default as y } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine namespace imports", () => { + const sortedImports = parseImports( + `import * as x from "lib";`, + `import * as y from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine property imports", () => { - const sortedImports = parseImports( - `import { x } from "lib";`, - `import { y as z } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports(`import { x, y as z } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine default imports", () => { + const sortedImports = parseImports( + `import x from "lib";`, + `import y from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports(`import { default as x, default as y } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine side-effect-only import with namespace import", () => { - const sortedImports = parseImports( - `import "lib";`, - `import * as x from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine property imports", () => { + const sortedImports = parseImports( + `import { x } from "lib";`, + `import { y as z } from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports(`import { x, y as z } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine side-effect-only import with default import", () => { - const sortedImports = parseImports( - `import "lib";`, - `import x from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine side-effect-only import with namespace import", () => { + const sortedImports = parseImports( + `import "lib";`, + `import * as x from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine side-effect-only import with property import", () => { - const sortedImports = parseImports( - `import "lib";`, - `import { x } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine side-effect-only import with default import", () => { + const sortedImports = parseImports( + `import "lib";`, + `import x from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine namespace import with default import", () => { - const sortedImports = parseImports( - `import * as x from "lib";`, - `import y from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports( - `import y, * as x from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine side-effect-only import with property import", () => { + const sortedImports = parseImports( + `import "lib";`, + `import { x } from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine namespace import with property import", () => { - const sortedImports = parseImports( - `import * as x from "lib";`, - `import { y } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine namespace import with default import", () => { + const sortedImports = parseImports( + `import * as x from "lib";`, + `import y from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports( + `import y, * as x from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine default import with property import", () => { - const sortedImports = parseImports( - `import x from "lib";`, - `import { y } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports( - `import x, { y } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine namespace import with property import", () => { + const sortedImports = parseImports( + `import * as x from "lib";`, + `import { y } from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine many imports", () => { - const sortedImports = parseImports( - `import "lib";`, - `import * as y from "lib";`, - `import w from "lib";`, - `import { b } from "lib";`, - `import "lib";`, - `import * as x from "lib";`, - `import z from "lib";`, - `import { a } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports( - `import "lib";`, - `import * as x from "lib";`, - `import * as y from "lib";`, - `import { a, b, default as w, default as z } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine default import with property import", () => { + const sortedImports = parseImports( + `import x from "lib";`, + `import { y } from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports( + `import x, { y } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - // This is descriptive, rather than normative - it("Combine two namespace imports with one default import", () => { - const sortedImports = parseImports( - `import * as x from "lib";`, - `import * as y from "lib";`, - `import z from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = sortedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine many imports", () => { + const sortedImports = parseImports( + `import "lib";`, + `import * as y from "lib";`, + `import w from "lib";`, + `import { b } from "lib";`, + `import "lib";`, + `import * as x from "lib";`, + `import z from "lib";`, + `import { a } from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports( + `import "lib";`, + `import * as x from "lib";`, + `import * as y from "lib";`, + `import { a, b, default as w, default as z } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine type-only imports separately from other imports", () => { - const sortedImports = parseImports( - `import type { x } from "lib";`, - `import type { y } from "lib";`, - `import { z } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports( - `import { z } from "lib";`, - `import type { x, y } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + // This is descriptive, rather than normative + it("Combine two namespace imports with one default import", () => { + const sortedImports = parseImports( + `import * as x from "lib";`, + `import * as y from "lib";`, + `import z from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = sortedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Do not combine type-only default, namespace, or named imports with each other", () => { - const sortedImports = parseImports( - `import type { x } from "lib";`, - `import type * as y from "lib";`, - `import type z from "lib";`); - // Default import could be rewritten as a named import to combine with `x`, - // but seems of debatable merit. - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = actualCoalescedImports; - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Combine type-only imports separately from other imports", () => { + const sortedImports = parseImports( + `import type { x } from "lib";`, + `import type { y } from "lib";`, + `import { z } from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports( + `import { z } from "lib";`, + `import type { x, y } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); }); - describe("Coalesce exports", () => { - it("No exports", () => { - assert.isEmpty(OrganizeImports.coalesceExports([])); - }); + it("Do not combine type-only default, namespace, or named imports with each other", () => { + const sortedImports = parseImports( + `import type { x } from "lib";`, + `import type * as y from "lib";`, + `import type z from "lib";`); + // Default import could be rewritten as a named import to combine with `x`, + // but seems of debatable merit. + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = actualCoalescedImports; + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); + }); - it("Sort specifiers - case-insensitive", () => { - const sortedExports = parseExports(`export { default as M, a as n, B, y, Z as O } from "lib";`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports(`export { a as n, B, default as M, y, Z as O } from "lib";`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + describe("Coalesce exports", () => { + it("No exports", () => { + assert.isEmpty(ts.OrganizeImports.coalesceExports([])); + }); - it("Sort specifiers - type-only", () => { - const sortedImports = parseImports(`import { type z, y, type x, c, type b, a } from "lib";`); - const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); - const expectedCoalescedImports = parseImports(`import { a, c, y, type b, type x, type z } from "lib";`); - assertListEqual(actualCoalescedImports, expectedCoalescedImports); - }); + it("Sort specifiers - case-insensitive", () => { + const sortedExports = parseExports(`export { default as M, a as n, B, y, Z as O } from "lib";`); + const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports(`export { a as n, B, default as M, y, Z as O } from "lib";`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine namespace re-exports", () => { - const sortedExports = parseExports( - `export * from "lib";`, - `export * from "lib";`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports(`export * from "lib";`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Sort specifiers - type-only", () => { + const sortedImports = parseImports(`import { type z, y, type x, c, type b, a } from "lib";`); + const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports); + const expectedCoalescedImports = parseImports(`import { a, c, y, type b, type x, type z } from "lib";`); + assertListEqual(actualCoalescedImports, expectedCoalescedImports); + }); - it("Combine property exports", () => { - const sortedExports = parseExports( - `export { x };`, - `export { y as z };`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports(`export { x, y as z };`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine namespace re-exports", () => { + const sortedExports = parseExports( + `export * from "lib";`, + `export * from "lib";`); + const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports(`export * from "lib";`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine property re-exports", () => { - const sortedExports = parseExports( - `export { x } from "lib";`, - `export { y as z } from "lib";`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports(`export { x, y as z } from "lib";`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine property exports", () => { + const sortedExports = parseExports( + `export { x };`, + `export { y as z };`); + const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports(`export { x, y as z };`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine namespace re-export with property re-export", () => { - const sortedExports = parseExports( - `export * from "lib";`, - `export { y } from "lib";`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = sortedExports; - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine property re-exports", () => { + const sortedExports = parseExports( + `export { x } from "lib";`, + `export { y as z } from "lib";`); + const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports(`export { x, y as z } from "lib";`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine many exports", () => { - const sortedExports = parseExports( - `export { x };`, - `export { y as w, z as default };`, - `export { w as q };`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports( - `export { w as q, x, y as w, z as default };`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine namespace re-export with property re-export", () => { + const sortedExports = parseExports( + `export * from "lib";`, + `export { y } from "lib";`); + const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = sortedExports; + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine many re-exports", () => { - const sortedExports = parseExports( - `export { x as a, y } from "lib";`, - `export * from "lib";`, - `export { z as b } from "lib";`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports( - `export * from "lib";`, - `export { x as a, y, z as b } from "lib";`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine many exports", () => { + const sortedExports = parseExports( + `export { x };`, + `export { y as w, z as default };`, + `export { w as q };`); + const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports( + `export { w as q, x, y as w, z as default };`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Keep type-only exports separate", () => { - const sortedExports = parseExports( - `export { x };`, - `export type { y };`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = sortedExports; - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Combine many re-exports", () => { + const sortedExports = parseExports( + `export { x as a, y } from "lib";`, + `export * from "lib";`, + `export { z as b } from "lib";`); + const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports( + `export * from "lib";`, + `export { x as a, y, z as b } from "lib";`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); - it("Combine type-only exports", () => { - const sortedExports = parseExports( - `export type { x };`, - `export type { y };`); - const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); - const expectedCoalescedExports = parseExports( - `export type { x, y };`); - assertListEqual(actualCoalescedExports, expectedCoalescedExports); - }); + it("Keep type-only exports separate", () => { + const sortedExports = parseExports( + `export { x };`, + `export type { y };`); + const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = sortedExports; + assertListEqual(actualCoalescedExports, expectedCoalescedExports); + }); + + it("Combine type-only exports", () => { + const sortedExports = parseExports( + `export type { x };`, + `export type { y };`); + const actualCoalescedExports = ts.OrganizeImports.coalesceExports(sortedExports); + const expectedCoalescedExports = parseExports( + `export type { x, y };`); + assertListEqual(actualCoalescedExports, expectedCoalescedExports); }); + }); - describe("Baselines", () => { + describe("Baselines", () => { - const libFile = { - path: "/lib.ts", - content: ` + const libFile = { + path: "/lib.ts", + content: ` export function F1(); export default function F2(); `, - }; + }; - const reactLibFile = { - path: "/react.ts", - content: ` + const reactLibFile = { + path: "/react.ts", + content: ` export const React = { createElement: (_type, _props, _children) => {}, }; export const Other = 1; `, - }; + }; - // Don't bother to actually emit a baseline for this. - it("NoImports", () => { - const testFile = { - path: "/a.ts", - content: "function F() { }", - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + // Don't bother to actually emit a baseline for this. + it("NoImports", () => { + const testFile = { + path: "/a.ts", + content: "function F() { }", + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, ts.testFormatSettings, ts.emptyOptions); + assert.isEmpty(changes); + }); - it("doesn't crash on shorthand ambient module", () => { - const testFile = { - path: "/a.ts", - content: "declare module '*';", - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + it("doesn't crash on shorthand ambient module", () => { + const testFile = { + path: "/a.ts", + content: "declare module '*';", + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, ts.testFormatSettings, ts.emptyOptions); + assert.isEmpty(changes); + }); - it("doesn't return any changes when the text would be identical", () => { - const testFile = { - path: "/a.ts", - content: `import { f } from 'foo';\nf();` - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + it("doesn't return any changes when the text would be identical", () => { + const testFile = { + path: "/a.ts", + content: `import { f } from 'foo';\nf();` + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, ts.testFormatSettings, ts.emptyOptions); + assert.isEmpty(changes); + }); - testOrganizeImports("Renamed_used", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("Renamed_used", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1 as EffOne, F2 as EffTwo } from "lib"; EffOne(); `, - }, - libFile); + }, + libFile); - testOrganizeImports("Simple", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("Simple", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; import * as NS from "lib"; import D from "lib"; @@ -370,29 +372,29 @@ D(); F1(); F2(); `, - }, - libFile); + }, + libFile); - testOrganizeImports("Unused_Some", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("Unused_Some", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; import * as NS from "lib"; import D from "lib"; D(); `, - }, - libFile); + }, + libFile); - describe("skipDestructiveCodeActions=true", () => { - testOrganizeImports("Syntax_Error_Body_skipDestructiveCodeActions", - /*skipDestructiveCodeActions*/ true, - { - path: "/test.ts", - content: ` + describe("skipDestructiveCodeActions=true", () => { + testOrganizeImports("Syntax_Error_Body_skipDestructiveCodeActions", + /*skipDestructiveCodeActions*/ true, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; import * as NS from "lib"; import D from "lib"; @@ -400,15 +402,15 @@ import D from "lib"; class class class; D; `, - }, - libFile); - }); - - testOrganizeImports("Syntax_Error_Imports_skipDestructiveCodeActions", - /*skipDestructiveCodeActions*/ true, - { - path: "/test.ts", - content: ` + }, + libFile); + }); + + testOrganizeImports("Syntax_Error_Imports_skipDestructiveCodeActions", + /*skipDestructiveCodeActions*/ true, + { + path: "/test.ts", + content: ` import { F1, F2 class class class; } from "lib"; import * as NS from "lib"; class class class; @@ -416,15 +418,15 @@ import D from "lib"; D; `, - }, - libFile); - - describe("skipDestructiveCodeActions=false", () => { - testOrganizeImports("Syntax_Error_Body", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + libFile); + + describe("skipDestructiveCodeActions=false", () => { + testOrganizeImports("Syntax_Error_Body", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; import * as NS from "lib"; import D from "lib"; @@ -432,14 +434,14 @@ import D from "lib"; class class class; D; `, - }, - libFile); - - testOrganizeImports("Syntax_Error_Imports", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + libFile); + + testOrganizeImports("Syntax_Error_Imports", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 class class class; } from "lib"; import * as NS from "lib"; class class class; @@ -447,49 +449,49 @@ import D from "lib"; D; `, - }, - libFile); - }); - - it("doesn't return any changes when the text would be identical", () => { - const testFile = { - path: "/a.ts", - content: `import { f } from 'foo';\nf();` - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); + }, + libFile); }); - testOrganizeImports("Unused_All", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + it("doesn't return any changes when the text would be identical", () => { + const testFile = { + path: "/a.ts", + content: `import { f } from 'foo';\nf();` + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, ts.testFormatSettings, ts.emptyOptions); + assert.isEmpty(changes); + }); + + testOrganizeImports("Unused_All", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; import * as NS from "lib"; import D from "lib"; `, - }, - libFile); + }, + libFile); - it("Unused_Empty", () => { - const testFile = { - path: "/test.ts", - content: ` + it("Unused_Empty", () => { + const testFile = { + path: "/test.ts", + content: ` import { } from "lib"; `, - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, ts.testFormatSettings, ts.emptyOptions); + assert.isEmpty(changes); + }); - testOrganizeImports("Unused_false_positive_module_augmentation", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.d.ts", - content: ` + testOrganizeImports("Unused_false_positive_module_augmentation", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.d.ts", + content: ` import foo from 'foo'; import { Caseless } from 'caseless'; @@ -499,13 +501,13 @@ declare module 'caseless' { test(name: KeyType): boolean; } }` - }); + }); - testOrganizeImports("Unused_preserve_imports_for_module_augmentation_in_non_declaration_file", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("Unused_preserve_imports_for_module_augmentation_in_non_declaration_file", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import foo from 'foo'; import { Caseless } from 'caseless'; @@ -515,39 +517,39 @@ declare module 'caseless' { test(name: KeyType): boolean; } }` - }); + }); - it("Unused_false_positive_shorthand_assignment", () => { - const testFile = { - path: "/test.ts", - content: ` + it("Unused_false_positive_shorthand_assignment", () => { + const testFile = { + path: "/test.ts", + content: ` import { x } from "a"; const o = { x }; ` - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, ts.testFormatSettings, ts.emptyOptions); + assert.isEmpty(changes); + }); - it("Unused_false_positive_export_shorthand", () => { - const testFile = { - path: "/test.ts", - content: ` + it("Unused_false_positive_export_shorthand", () => { + const testFile = { + path: "/test.ts", + content: ` import { x } from "a"; export { x }; ` - }; - const languageService = makeLanguageService(testFile); - const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); - assert.isEmpty(changes); - }); + }; + const languageService = makeLanguageService(testFile); + const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, ts.testFormatSettings, ts.emptyOptions); + assert.isEmpty(changes); + }); - testOrganizeImports("MoveToTop", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("MoveToTop", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; F1(); F2(); @@ -556,15 +558,15 @@ NS.F1(); import D from "lib"; D(); `, - }, - libFile); - - /* eslint-disable no-template-curly-in-string */ - testOrganizeImports("MoveToTop_Invalid", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + libFile); + + /* eslint-disable no-template-curly-in-string */ + testOrganizeImports("MoveToTop_Invalid", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; F1(); F2(); @@ -575,115 +577,115 @@ import a from ${"`${'lib'}`"}; import D from "lib"; D(); `, - }, - libFile); - /* eslint-enable no-template-curly-in-string */ - - testOrganizeImports("TypeOnly", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + libFile); + /* eslint-enable no-template-curly-in-string */ + + testOrganizeImports("TypeOnly", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { X } from "lib"; import type Y from "lib"; import { Z } from "lib"; import type { A, B } from "lib"; export { A, B, X, Y, Z };` - }); + }); - testOrganizeImports("CoalesceMultipleModules", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("CoalesceMultipleModules", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { d } from "lib1"; import { b } from "lib1"; import { c } from "lib2"; import { a } from "lib2"; a + b + c + d; `, - }, - { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, - { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); - - testOrganizeImports("CoalesceTrivia", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, + { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); + + testOrganizeImports("CoalesceTrivia", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` /*A*/import /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I /*J*/import /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R F1(); F2(); `, - }, - libFile); + }, + libFile); - testOrganizeImports("SortTrivia", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("SortTrivia", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` /*A*/import /*B*/ "lib2" /*C*/;/*D*/ //E /*F*/import /*G*/ "lib1" /*H*/;/*I*/ //J `, - }, - { path: "/lib1.ts", content: "" }, - { path: "/lib2.ts", content: "" }); - - testOrganizeImports("UnusedTrivia1", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + { path: "/lib1.ts", content: "" }, + { path: "/lib2.ts", content: "" }); + + testOrganizeImports("UnusedTrivia1", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` /*A*/import /*B*/ { /*C*/ F1 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I `, - }, - libFile); + }, + libFile); - testOrganizeImports("UnusedTrivia2", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("UnusedTrivia2", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` /*A*/import /*B*/ { /*C*/ F1 /*D*/, /*E*/ F2 /*F*/ } /*G*/ from /*H*/ "lib" /*I*/;/*J*/ //K F1(); `, - }, - libFile); + }, + libFile); - testOrganizeImports("UnusedHeaderComment", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("UnusedHeaderComment", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` // Header import { F1 } from "lib"; `, - }, - libFile); + }, + libFile); - testOrganizeImports("SortHeaderComment", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("SortHeaderComment", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` // Header import "lib2"; import "lib1"; `, - }, - { path: "/lib1.ts", content: "" }, - { path: "/lib2.ts", content: "" }); - - testOrganizeImports("AmbientModule", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + { path: "/lib1.ts", content: "" }, + { path: "/lib2.ts", content: "" }); + + testOrganizeImports("AmbientModule", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` declare module "mod" { import { F1 } from "lib"; import * as NS from "lib"; @@ -692,14 +694,14 @@ declare module "mod" { function F(f1: {} = F1, f2: {} = F2) {} } `, - }, - libFile); + }, + libFile); - testOrganizeImports("TopLevelAndAmbientModule", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("TopLevelAndAmbientModule", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import D from "lib"; declare module "mod" { @@ -715,168 +717,168 @@ import "lib"; D(); `, - }, - libFile); + }, + libFile); - testOrganizeImports("JsxFactoryUsedJsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.jsx", - content: ` + testOrganizeImports("JsxFactoryUsedJsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.jsx", + content: ` import { React, Other } from "react";
; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxFactoryUsedJs", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.js", - content: ` + testOrganizeImports("JsxFactoryUsedJs", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.js", + content: ` import { React, Other } from "react";
; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxFactoryUsedTsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.tsx", - content: ` + testOrganizeImports("JsxFactoryUsedTsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.tsx", + content: ` import { React, Other } from "react";
; `, - }, - reactLibFile); - - // TS files are not JSX contexts, so the parser does not treat - // `
` as a JSX element. - testOrganizeImports("JsxFactoryUsedTs", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + }, + reactLibFile); + + // TS files are not JSX contexts, so the parser does not treat + // `
` as a JSX element. + testOrganizeImports("JsxFactoryUsedTs", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { React, Other } from "react";
; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxFactoryUnusedJsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.jsx", - content: ` + testOrganizeImports("JsxFactoryUnusedJsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.jsx", + content: ` import { React, Other } from "react"; `, - }, - reactLibFile); - - // Note: Since the file extension does not end with "x", the jsx compiler option - // will not be enabled. The import should be retained regardless. - testOrganizeImports("JsxFactoryUnusedJs", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.js", - content: ` + }, + reactLibFile); + + // Note: Since the file extension does not end with "x", the jsx compiler option + // will not be enabled. The import should be retained regardless. + testOrganizeImports("JsxFactoryUnusedJs", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.js", + content: ` import { React, Other } from "react"; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxFactoryUnusedTsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.tsx", - content: ` + testOrganizeImports("JsxFactoryUnusedTsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.tsx", + content: ` import { React, Other } from "react"; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxFactoryUnusedTs", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.ts", - content: ` + testOrganizeImports("JsxFactoryUnusedTs", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.ts", + content: ` import { React, Other } from "react"; `, - }, - reactLibFile); + }, + reactLibFile); - testOrganizeImports("JsxPragmaTsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.tsx", - content: `/** @jsx jsx */ + testOrganizeImports("JsxPragmaTsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.tsx", + content: `/** @jsx jsx */ import { Global, jsx } from '@emotion/core'; import * as React from 'react'; export const App: React.FunctionComponent = _ =>

Hello!

`, - }, - { - path: "/@emotion/core/index.d.ts", - content: `import { createElement } from 'react' + }, + { + path: "/@emotion/core/index.d.ts", + content: `import { createElement } from 'react' export const jsx: typeof createElement; export function Global(props: any): ReactElement;` - }, - { - path: reactLibFile.path, - content: `${reactLibFile.content} + }, + { + path: reactLibFile.path, + content: `${reactLibFile.content} export namespace React { interface FunctionComponent { } } ` - } - ); + } + ); - testOrganizeImports("JsxFragmentPragmaTsx", - /*skipDestructiveCodeActions*/ false, - { - path: "/test.tsx", - content: `/** @jsx h */ + testOrganizeImports("JsxFragmentPragmaTsx", + /*skipDestructiveCodeActions*/ false, + { + path: "/test.tsx", + content: `/** @jsx h */ /** @jsxFrag frag */ import { h, frag } from "@foo/core"; const elem = <>
Foo
; `, - }, - { - path: "/@foo/core/index.d.ts", - content: `export function h(): void; + }, + { + path: "/@foo/core/index.d.ts", + content: `export function h(): void; export function frag(): void; ` - } - ); + } + ); - describe("Exports", () => { + describe("Exports", () => { - testOrganizeExports("MoveToTop", - { - path: "/test.ts", - content: ` + testOrganizeExports("MoveToTop", + { + path: "/test.ts", + content: ` export { F1, F2 } from "lib"; 1; export * from "lib"; 2; `, - }, - libFile); + }, + libFile); - /* eslint-disable no-template-curly-in-string */ - testOrganizeExports("MoveToTop_Invalid", - { - path: "/test.ts", - content: ` + /* eslint-disable no-template-curly-in-string */ + testOrganizeExports("MoveToTop_Invalid", + { + path: "/test.ts", + content: ` export { F1, F2 } from "lib"; 1; export * from "lib"; @@ -886,14 +888,14 @@ export { a } from ${"`${'lib'}`"}; export { D } from "lib"; 3; `, - }, - libFile); - /* eslint-enable no-template-curly-in-string */ + }, + libFile); + /* eslint-enable no-template-curly-in-string */ - testOrganizeExports("MoveToTop_WithImportsFirst", - { - path: "/test.ts", - content: ` + testOrganizeExports("MoveToTop_WithImportsFirst", + { + path: "/test.ts", + content: ` import { F1, F2 } from "lib"; 1; export { F1, F2 } from "lib"; @@ -904,13 +906,13 @@ export * from "lib"; 4; F1(); F2(); NS.F1(); `, - }, - libFile); + }, + libFile); - testOrganizeExports("MoveToTop_WithExportsFirst", - { - path: "/test.ts", - content: ` + testOrganizeExports("MoveToTop_WithExportsFirst", + { + path: "/test.ts", + content: ` export { F1, F2 } from "lib"; 1; import { F1, F2 } from "lib"; @@ -921,72 +923,72 @@ import * as NS from "lib"; 4; F1(); F2(); NS.F1(); `, - }, - libFile); + }, + libFile); - testOrganizeExports("CoalesceMultipleModules", - { - path: "/test.ts", - content: ` + testOrganizeExports("CoalesceMultipleModules", + { + path: "/test.ts", + content: ` export { d } from "lib1"; export { b } from "lib1"; export { c } from "lib2"; export { a } from "lib2"; `, - }, - { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, - { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); + }, + { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, + { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); - testOrganizeExports("CoalesceTrivia", - { - path: "/test.ts", - content: ` + testOrganizeExports("CoalesceTrivia", + { + path: "/test.ts", + content: ` /*A*/export /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I /*J*/export /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R `, - }, - libFile); + }, + libFile); - testOrganizeExports("SortTrivia", - { - path: "/test.ts", - content: ` + testOrganizeExports("SortTrivia", + { + path: "/test.ts", + content: ` /*A*/export /*B*/ * /*C*/ from /*D*/ "lib2" /*E*/;/*F*/ //G /*H*/export /*I*/ * /*J*/ from /*K*/ "lib1" /*L*/;/*M*/ //N `, - }, - { path: "/lib1.ts", content: "" }, - { path: "/lib2.ts", content: "" }); + }, + { path: "/lib1.ts", content: "" }, + { path: "/lib2.ts", content: "" }); - testOrganizeExports("SortHeaderComment", - { - path: "/test.ts", - content: ` + testOrganizeExports("SortHeaderComment", + { + path: "/test.ts", + content: ` // Header export * from "lib2"; export * from "lib1"; `, - }, - { path: "/lib1.ts", content: "" }, - { path: "/lib2.ts", content: "" }); + }, + { path: "/lib1.ts", content: "" }, + { path: "/lib2.ts", content: "" }); - testOrganizeExports("AmbientModule", - { - path: "/test.ts", - content: ` + testOrganizeExports("AmbientModule", + { + path: "/test.ts", + content: ` declare module "mod" { export { F1 } from "lib"; export * from "lib"; export { F2 } from "lib"; } `, - }, - libFile); + }, + libFile); - testOrganizeExports("TopLevelAndAmbientModule", - { - path: "/test.ts", - content: ` + testOrganizeExports("TopLevelAndAmbientModule", + { + path: "/test.ts", + content: ` export { D } from "lib"; declare module "mod" { @@ -998,143 +1000,142 @@ declare module "mod" { export { E } from "lib"; export * from "lib"; `, - }, - libFile); - }); - - function testOrganizeExports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { - testOrganizeImports(`${testName}.exports`, /*skipDestructiveCodeActions*/ true, testFile, ...otherFiles); - } - - function testOrganizeImports(testName: string, skipDestructiveCodeActions: boolean, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { - it(testName, () => runBaseline(`organizeImports/${testName}.ts`, skipDestructiveCodeActions, testFile, ...otherFiles)); - } + }, + libFile); + }); - function runBaseline(baselinePath: string, skipDestructiveCodeActions: boolean, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { - const { path: testPath, content: testContent } = testFile; - const languageService = makeLanguageService(testFile, ...otherFiles); - const changes = languageService.organizeImports({ skipDestructiveCodeActions, type: "file", fileName: testPath }, testFormatSettings, emptyOptions); - assert.equal(changes.length, 1); - assert.equal(changes[0].fileName, testPath); - - const newText = textChanges.applyChanges(testContent, changes[0].textChanges); - Harness.Baseline.runBaseline(baselinePath, [ - "// ==ORIGINAL==", - testContent, - "// ==ORGANIZED==", - newText, - ].join(newLineCharacter)); - } + function testOrganizeExports(testName: string, testFile: ts.TestFSWithWatch.File, ...otherFiles: ts.TestFSWithWatch.File[]) { + testOrganizeImports(`${testName}.exports`, /*skipDestructiveCodeActions*/ true, testFile, ...otherFiles); + } - function makeLanguageService(...files: TestFSWithWatch.File[]) { - const host = projectSystem.createServerHost(files); - const projectService = projectSystem.createProjectService(host, { useSingleInferredProject: true }); - projectService.setCompilerOptionsForInferredProjects({ jsx: files.some(f => f.path.endsWith("x")) ? JsxEmit.React : JsxEmit.None }); - files.forEach(f => projectService.openClientFile(f.path)); - return projectService.inferredProjects[0].getLanguageService(); - } - }); + function testOrganizeImports(testName: string, skipDestructiveCodeActions: boolean, testFile: ts.TestFSWithWatch.File, ...otherFiles: ts.TestFSWithWatch.File[]) { + it(testName, () => runBaseline(`organizeImports/${testName}.ts`, skipDestructiveCodeActions, testFile, ...otherFiles)); + } - function parseImports(...importStrings: string[]): readonly ImportDeclaration[] { - const sourceFile = createSourceFile("a.ts", importStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS); - const imports = filter(sourceFile.statements, isImportDeclaration); - assert.equal(imports.length, importStrings.length); - return imports; + function runBaseline(baselinePath: string, skipDestructiveCodeActions: boolean, testFile: ts.TestFSWithWatch.File, ...otherFiles: ts.TestFSWithWatch.File[]) { + const { path: testPath, content: testContent } = testFile; + const languageService = makeLanguageService(testFile, ...otherFiles); + const changes = languageService.organizeImports({ skipDestructiveCodeActions, type: "file", fileName: testPath }, ts.testFormatSettings, ts.emptyOptions); + assert.equal(changes.length, 1); + assert.equal(changes[0].fileName, testPath); + + const newText = ts.textChanges.applyChanges(testContent, changes[0].textChanges); + Harness.Baseline.runBaseline(baselinePath, [ + "// ==ORIGINAL==", + testContent, + "// ==ORGANIZED==", + newText, + ].join(ts.newLineCharacter)); } - function parseExports(...exportStrings: string[]): readonly ExportDeclaration[] { - const sourceFile = createSourceFile("a.ts", exportStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS); - const exports = filter(sourceFile.statements, isExportDeclaration); - assert.equal(exports.length, exportStrings.length); - return exports; + function makeLanguageService(...files: ts.TestFSWithWatch.File[]) { + const host = ts.projectSystem.createServerHost(files); + const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true }); + projectService.setCompilerOptionsForInferredProjects({ jsx: files.some(f => f.path.endsWith("x")) ? ts.JsxEmit.React : ts.JsxEmit.None }); + files.forEach(f => projectService.openClientFile(f.path)); + return projectService.inferredProjects[0].getLanguageService(); } + }); - function assertEqual(node1?: Node, node2?: Node) { - if (node1 === undefined) { - assert.isUndefined(node2); - return; - } - else if (node2 === undefined) { - assert.isUndefined(node1); // Guaranteed to fail - return; - } + function parseImports(...importStrings: string[]): readonly ts.ImportDeclaration[] { + const sourceFile = ts.createSourceFile("a.ts", importStrings.join("\n"), ts.ScriptTarget.ES2015, /*setParentNodes*/ true, ts.ScriptKind.TS); + const imports = ts.filter(sourceFile.statements, ts.isImportDeclaration); + assert.equal(imports.length, importStrings.length); + return imports; + } - assert.equal(node1.kind, node2.kind); - - switch (node1.kind) { - case SyntaxKind.ImportDeclaration: - const decl1 = node1 as ImportDeclaration; - const decl2 = node2 as ImportDeclaration; - assertEqual(decl1.importClause, decl2.importClause); - assertEqual(decl1.moduleSpecifier, decl2.moduleSpecifier); - break; - case SyntaxKind.ImportClause: - const clause1 = node1 as ImportClause; - const clause2 = node2 as ImportClause; - assertEqual(clause1.name, clause2.name); - assertEqual(clause1.namedBindings, clause2.namedBindings); - break; - case SyntaxKind.NamespaceImport: - const nsi1 = node1 as NamespaceImport; - const nsi2 = node2 as NamespaceImport; - assertEqual(nsi1.name, nsi2.name); - break; - case SyntaxKind.NamedImports: - const ni1 = node1 as NamedImports; - const ni2 = node2 as NamedImports; - assertListEqual(ni1.elements, ni2.elements); - break; - case SyntaxKind.ImportSpecifier: - const is1 = node1 as ImportSpecifier; - const is2 = node2 as ImportSpecifier; - assertEqual(is1.name, is2.name); - assertEqual(is1.propertyName, is2.propertyName); - break; - case SyntaxKind.ExportDeclaration: - const ed1 = node1 as ExportDeclaration; - const ed2 = node2 as ExportDeclaration; - assertEqual(ed1.exportClause, ed2.exportClause); - assertEqual(ed1.moduleSpecifier, ed2.moduleSpecifier); - break; - case SyntaxKind.NamedExports: - const ne1 = node1 as NamedExports; - const ne2 = node2 as NamedExports; - assertListEqual(ne1.elements, ne2.elements); - break; - case SyntaxKind.ExportSpecifier: - const es1 = node1 as ExportSpecifier; - const es2 = node2 as ExportSpecifier; - assertEqual(es1.name, es2.name); - assertEqual(es1.propertyName, es2.propertyName); - break; - case SyntaxKind.Identifier: - const id1 = node1 as Identifier; - const id2 = node2 as Identifier; - assert.equal(id1.text, id2.text); - break; - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - const sl1 = node1 as LiteralLikeNode; - const sl2 = node2 as LiteralLikeNode; - assert.equal(sl1.text, sl2.text); - break; - default: - assert.equal(node1.getText(), node2.getText()); - break; - } + function parseExports(...exportStrings: string[]): readonly ts.ExportDeclaration[] { + const sourceFile = ts.createSourceFile("a.ts", exportStrings.join("\n"), ts.ScriptTarget.ES2015, /*setParentNodes*/ true, ts.ScriptKind.TS); + const exports = ts.filter(sourceFile.statements, ts.isExportDeclaration); + assert.equal(exports.length, exportStrings.length); + return exports; + } + + function assertEqual(node1?: ts.Node, node2?: ts.Node) { + if (node1 === undefined) { + assert.isUndefined(node2); + return; + } + else if (node2 === undefined) { + assert.isUndefined(node1); // Guaranteed to fail + return; } - function assertListEqual(list1: readonly Node[], list2: readonly Node[]) { - if (list1 === undefined || list2 === undefined) { - assert.isUndefined(list1); - assert.isUndefined(list2); - return; - } + assert.equal(node1.kind, node2.kind); + + switch (node1.kind) { + case ts.SyntaxKind.ImportDeclaration: + const decl1 = node1 as ts.ImportDeclaration; + const decl2 = node2 as ts.ImportDeclaration; + assertEqual(decl1.importClause, decl2.importClause); + assertEqual(decl1.moduleSpecifier, decl2.moduleSpecifier); + break; + case ts.SyntaxKind.ImportClause: + const clause1 = node1 as ts.ImportClause; + const clause2 = node2 as ts.ImportClause; + assertEqual(clause1.name, clause2.name); + assertEqual(clause1.namedBindings, clause2.namedBindings); + break; + case ts.SyntaxKind.NamespaceImport: + const nsi1 = node1 as ts.NamespaceImport; + const nsi2 = node2 as ts.NamespaceImport; + assertEqual(nsi1.name, nsi2.name); + break; + case ts.SyntaxKind.NamedImports: + const ni1 = node1 as ts.NamedImports; + const ni2 = node2 as ts.NamedImports; + assertListEqual(ni1.elements, ni2.elements); + break; + case ts.SyntaxKind.ImportSpecifier: + const is1 = node1 as ts.ImportSpecifier; + const is2 = node2 as ts.ImportSpecifier; + assertEqual(is1.name, is2.name); + assertEqual(is1.propertyName, is2.propertyName); + break; + case ts.SyntaxKind.ExportDeclaration: + const ed1 = node1 as ts.ExportDeclaration; + const ed2 = node2 as ts.ExportDeclaration; + assertEqual(ed1.exportClause, ed2.exportClause); + assertEqual(ed1.moduleSpecifier, ed2.moduleSpecifier); + break; + case ts.SyntaxKind.NamedExports: + const ne1 = node1 as ts.NamedExports; + const ne2 = node2 as ts.NamedExports; + assertListEqual(ne1.elements, ne2.elements); + break; + case ts.SyntaxKind.ExportSpecifier: + const es1 = node1 as ts.ExportSpecifier; + const es2 = node2 as ts.ExportSpecifier; + assertEqual(es1.name, es2.name); + assertEqual(es1.propertyName, es2.propertyName); + break; + case ts.SyntaxKind.Identifier: + const id1 = node1 as ts.Identifier; + const id2 = node2 as ts.Identifier; + assert.equal(id1.text, id2.text); + break; + case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.NoSubstitutionTemplateLiteral: + const sl1 = node1 as ts.LiteralLikeNode; + const sl2 = node2 as ts.LiteralLikeNode; + assert.equal(sl1.text, sl2.text); + break; + default: + assert.equal(node1.getText(), node2.getText()); + break; + } + } - assert.equal(list1.length, list2.length); - for (let i = 0; i < list1.length; i++) { - assertEqual(list1[i], list2[i]); - } + function assertListEqual(list1: readonly ts.Node[], list2: readonly ts.Node[]) { + if (list1 === undefined || list2 === undefined) { + assert.isUndefined(list1); + assert.isUndefined(list2); + return; } - }); -} + + assert.equal(list1.length, list2.length); + for (let i = 0; i < list1.length; i++) { + assertEqual(list1[i], list2[i]); + } + } +}); diff --git a/src/testRunner/unittests/services/patternMatcher.ts b/src/testRunner/unittests/services/patternMatcher.ts index 9f3cbf4b6d7c6..d48dd97220b59 100644 --- a/src/testRunner/unittests/services/patternMatcher.ts +++ b/src/testRunner/unittests/services/patternMatcher.ts @@ -1,3 +1,5 @@ +import * as ts from "../../_namespaces/ts"; + describe("unittests:: services:: PatternMatcher", () => { describe("BreakIntoCharacterSpans", () => { it("EmptyIdentifier", () => { diff --git a/src/testRunner/unittests/services/preProcessFile.ts b/src/testRunner/unittests/services/preProcessFile.ts index a0ff91a26b1b4..3ff1a365d359c 100644 --- a/src/testRunner/unittests/services/preProcessFile.ts +++ b/src/testRunner/unittests/services/preProcessFile.ts @@ -1,3 +1,5 @@ +import * as ts from "../../_namespaces/ts"; + describe("unittests:: services:: PreProcessFile:", () => { function test(sourceText: string, readImportFile: boolean, detectJavaScriptImports: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void { const resultPreProcess = ts.preProcessFile(sourceText, readImportFile, detectJavaScriptImports); diff --git a/src/testRunner/unittests/services/textChanges.ts b/src/testRunner/unittests/services/textChanges.ts index 2feaebd530288..9c47393c81961 100644 --- a/src/testRunner/unittests/services/textChanges.ts +++ b/src/testRunner/unittests/services/textChanges.ts @@ -1,65 +1,67 @@ +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; + // Some tests have trailing whitespace -namespace ts { - describe("unittests:: services:: textChanges", () => { - function findChild(name: string, n: Node) { - return find(n)!; +describe("unittests:: services:: textChanges", () => { + function findChild(name: string, n: ts.Node) { + return find(n)!; - function find(node: Node): Node | undefined { - if (isDeclaration(node) && node.name && isIdentifier(node.name) && node.name.escapedText === name) { - return node; - } - else { - return forEachChild(node, find); - } + function find(node: ts.Node): ts.Node | undefined { + if (ts.isDeclaration(node) && node.name && ts.isIdentifier(node.name) && node.name.escapedText === name) { + return node; + } + else { + return ts.forEachChild(node, find); } } + } - const printerOptions = { newLine: NewLineKind.LineFeed }; - const newLineCharacter = getNewLineCharacter(printerOptions); + const printerOptions = { newLine: ts.NewLineKind.LineFeed }; + const newLineCharacter = ts.getNewLineCharacter(printerOptions); - function getRuleProvider(placeOpenBraceOnNewLineForFunctions: boolean): formatting.FormatContext { - return formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...testFormatSettings, placeOpenBraceOnNewLineForFunctions: true } : testFormatSettings, notImplementedHost); - } + function getRuleProvider(placeOpenBraceOnNewLineForFunctions: boolean): ts.formatting.FormatContext { + return ts.formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...ts.testFormatSettings, placeOpenBraceOnNewLineForFunctions: true } : ts.testFormatSettings, ts.notImplementedHost); + } - // validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed. - function verifyPositions(node: Node, text: string): void { - const nodeList = flattenNodes(node); - const sourceFile = createSourceFile("f.ts", text, ScriptTarget.ES2015); - const parsedNodeList = flattenNodes(sourceFile.statements[0]); - zipWith(nodeList, parsedNodeList, (left, right) => { - Debug.assert(left.pos === right.pos); - Debug.assert(left.end === right.end); - }); + // validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed. + function verifyPositions(node: ts.Node, text: string): void { + const nodeList = flattenNodes(node); + const sourceFile = ts.createSourceFile("f.ts", text, ts.ScriptTarget.ES2015); + const parsedNodeList = flattenNodes(sourceFile.statements[0]); + ts.zipWith(nodeList, parsedNodeList, (left, right) => { + ts.Debug.assert(left.pos === right.pos); + ts.Debug.assert(left.end === right.end); + }); - function flattenNodes(n: Node) { - const data: (Node | NodeArray)[] = []; - walk(n); - return data; + function flattenNodes(n: ts.Node) { + const data: (ts.Node | ts.NodeArray)[] = []; + walk(n); + return data; - function walk(n: Node | NodeArray): void { - data.push(n); - return isArray(n) ? forEach(n, walk) : forEachChild(n, walk, walk); - } + function walk(n: ts.Node | ts.NodeArray): void { + data.push(n); + return ts.isArray(n) ? ts.forEach(n, walk) : ts.forEachChild(n, walk, walk); } } + } - function runSingleFileTest(caption: string, placeOpenBraceOnNewLineForFunctions: boolean, text: string, validateNodes: boolean, testBlock: (sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker) => void) { - it(caption, () => { - const sourceFile = createSourceFile("source.ts", text, ScriptTarget.ES2015, /*setParentNodes*/ true); - const rulesProvider = getRuleProvider(placeOpenBraceOnNewLineForFunctions); - const changeTracker = new textChanges.ChangeTracker(newLineCharacter, rulesProvider); - testBlock(sourceFile, changeTracker); - const changes = changeTracker.getChanges(validateNodes ? verifyPositions : undefined); - assert.equal(changes.length, 1); - assert.equal(changes[0].fileName, sourceFile.fileName); - const modified = textChanges.applyChanges(sourceFile.text, changes[0].textChanges); - Harness.Baseline.runBaseline(`textChanges/${caption}.js`, `===ORIGINAL===${newLineCharacter}${text}${newLineCharacter}===MODIFIED===${newLineCharacter}${modified}`); - }); - } + function runSingleFileTest(caption: string, placeOpenBraceOnNewLineForFunctions: boolean, text: string, validateNodes: boolean, testBlock: (sourceFile: ts.SourceFile, changeTracker: ts.textChanges.ChangeTracker) => void) { + it(caption, () => { + const sourceFile = ts.createSourceFile("source.ts", text, ts.ScriptTarget.ES2015, /*setParentNodes*/ true); + const rulesProvider = getRuleProvider(placeOpenBraceOnNewLineForFunctions); + const changeTracker = new ts.textChanges.ChangeTracker(newLineCharacter, rulesProvider); + testBlock(sourceFile, changeTracker); + const changes = changeTracker.getChanges(validateNodes ? verifyPositions : undefined); + assert.equal(changes.length, 1); + assert.equal(changes[0].fileName, sourceFile.fileName); + const modified = ts.textChanges.applyChanges(sourceFile.text, changes[0].textChanges); + Harness.Baseline.runBaseline(`textChanges/${caption}.js`, `===ORIGINAL===${newLineCharacter}${text}${newLineCharacter}===MODIFIED===${newLineCharacter}${modified}`); + }); + } - { - const text = ` + { + const text = ` namespace M { namespace M2 @@ -80,32 +82,32 @@ namespace M } } }`; - runSingleFileTest("extractMethodLike", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - const statements = (findChild("foo", sourceFile) as FunctionDeclaration).body!.statements.slice(1); - const newFunction = factory.createFunctionDeclaration( - /*modifiers*/ undefined, - /*asteriskToken*/ undefined, - /*name*/ "bar", - /*typeParameters*/ undefined, - /*parameters*/ emptyArray, - /*type*/ factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), - /*body */ factory.createBlock(statements) - ); + runSingleFileTest("extractMethodLike", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + const statements = (findChild("foo", sourceFile) as ts.FunctionDeclaration).body!.statements.slice(1); + const newFunction = ts.factory.createFunctionDeclaration( + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ "bar", + /*typeParameters*/ undefined, + /*parameters*/ ts.emptyArray, + /*type*/ ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + /*body */ ts.factory.createBlock(statements) + ); - changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction); + changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction); - // replace statements with return statement - const newStatement = factory.createReturnStatement( - factory.createCallExpression( - /*expression*/ newFunction.name!, - /*typeArguments*/ undefined, - /*argumentsArray*/ emptyArray - )); - changeTracker.replaceNodeRange(sourceFile, statements[0], last(statements), newStatement, { suffix: newLineCharacter }); - }); - } - { - const text = ` + // replace statements with return statement + const newStatement = ts.factory.createReturnStatement( + ts.factory.createCallExpression( + /*expression*/ newFunction.name!, + /*typeArguments*/ undefined, + /*argumentsArray*/ ts.emptyArray + )); + changeTracker.replaceNodeRange(sourceFile, statements[0], ts.last(statements), newStatement, { suffix: newLineCharacter }); + }); + } + { + const text = ` function foo() { return 1; } @@ -114,19 +116,19 @@ function bar() { return 2; } `; - runSingleFileTest("deleteRange1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.deleteRange(sourceFile, { pos: text.indexOf("function foo"), end: text.indexOf("function bar") }); - }); - } - function findVariableStatementContaining(name: string, sourceFile: SourceFile): VariableStatement { - return cast(findVariableDeclarationContaining(name, sourceFile).parent.parent, isVariableStatement); - } - function findVariableDeclarationContaining(name: string, sourceFile: SourceFile): VariableDeclaration { - return cast(findChild(name, sourceFile), isVariableDeclaration); - } - const { deleteNode } = textChanges; - { - const text = ` + runSingleFileTest("deleteRange1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteRange(sourceFile, { pos: text.indexOf("function foo"), end: text.indexOf("function bar") }); + }); + } + function findVariableStatementContaining(name: string, sourceFile: ts.SourceFile): ts.VariableStatement { + return ts.cast(findVariableDeclarationContaining(name, sourceFile).parent.parent, ts.isVariableStatement); + } + function findVariableDeclarationContaining(name: string, sourceFile: ts.SourceFile): ts.VariableDeclaration { + return ts.cast(findChild(name, sourceFile), ts.isVariableDeclaration); + } + const { deleteNode } = ts.textChanges; + { + const text = ` var x = 1; // some comment - 1 /** * comment 2 @@ -134,24 +136,24 @@ var x = 1; // some comment - 1 var y = 2; // comment 3 var z = 3; // comment 4 `; - runSingleFileTest("deleteNode1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile)); - }); - runSingleFileTest("deleteNode2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude }); - }); - runSingleFileTest("deleteNode3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - runSingleFileTest("deleteNode4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - runSingleFileTest("deleteNode5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("x", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNode1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile)); + }); + runSingleFileTest("deleteNode2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: ts.textChanges.LeadingTriviaOption.Exclude }); + }); + runSingleFileTest("deleteNode3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { trailingTriviaOption: ts.textChanges.TrailingTriviaOption.Exclude }); + }); + runSingleFileTest("deleteNode4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: ts.textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: ts.textChanges.TrailingTriviaOption.Exclude }); + }); + runSingleFileTest("deleteNode5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("x", sourceFile)); + }); + } + { + const text = ` // comment 1 var x = 1; // comment 2 // comment 3 @@ -160,53 +162,53 @@ var z = 3; // comment 5 // comment 6 var a = 4; // comment 7 `; - runSingleFileTest("deleteNodeRange1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile)); - }); - runSingleFileTest("deleteNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), - { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude }); - }); - runSingleFileTest("deleteNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), - { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - runSingleFileTest("deleteNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), - { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - } - function createTestVariableDeclaration(name: string) { - return factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, /*type*/ undefined, factory.createObjectLiteralExpression([factory.createPropertyAssignment("p1", factory.createNumericLiteral(1))], /*multiline*/ true)); - } - function createTestClass() { - return factory.createClassDeclaration( - [ - factory.createToken(SyntaxKind.PublicKeyword) - ], - "class1", - /*typeParameters*/ undefined, - [ - factory.createHeritageClause( - SyntaxKind.ImplementsKeyword, - [ - factory.createExpressionWithTypeArguments(factory.createIdentifier("interface1"), /*typeArguments*/ undefined) - ] - ) - ], - [ - factory.createPropertyDeclaration( - /*modifiers*/ undefined, - "property1", - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword), - /*initializer*/ undefined - ) - ] - ); - } - { - const text = ` + runSingleFileTest("deleteNodeRange1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile)); + }); + runSingleFileTest("deleteNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), + { leadingTriviaOption: ts.textChanges.LeadingTriviaOption.Exclude }); + }); + runSingleFileTest("deleteNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), + { trailingTriviaOption: ts.textChanges.TrailingTriviaOption.Exclude }); + }); + runSingleFileTest("deleteNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), + { leadingTriviaOption: ts.textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: ts.textChanges.TrailingTriviaOption.Exclude }); + }); + } + function createTestVariableDeclaration(name: string) { + return ts.factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, /*type*/ undefined, ts.factory.createObjectLiteralExpression([ts.factory.createPropertyAssignment("p1", ts.factory.createNumericLiteral(1))], /*multiline*/ true)); + } + function createTestClass() { + return ts.factory.createClassDeclaration( + [ + ts.factory.createToken(ts.SyntaxKind.PublicKeyword) + ], + "class1", + /*typeParameters*/ undefined, + [ + ts.factory.createHeritageClause( + ts.SyntaxKind.ImplementsKeyword, + [ + ts.factory.createExpressionWithTypeArguments(ts.factory.createIdentifier("interface1"), /*typeArguments*/ undefined) + ] + ) + ], + [ + ts.factory.createPropertyDeclaration( + /*modifiers*/ undefined, + "property1", + /*questionToken*/ undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword), + /*initializer*/ undefined + ) + ] + ); + } + { + const text = ` // comment 1 var x = 1; // comment 2 // comment 3 @@ -214,31 +216,31 @@ var y = 2; // comment 4 var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; - runSingleFileTest("replaceRange", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter }); - }); - runSingleFileTest("replaceRangeWithForcedIndentation", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter, indentation: 8, delta: 0 }); - }); + runSingleFileTest("replaceRange", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter }); + }); + runSingleFileTest("replaceRangeWithForcedIndentation", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter, indentation: 8, delta: 0 }); + }); - runSingleFileTest("replaceRangeNoLineBreakBefore", /*placeOpenBraceOnNewLineForFunctions*/ true, `const x = 1, y = "2";`, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = createTestVariableDeclaration("z1"); - changeTracker.replaceRange(sourceFile, { pos: sourceFile.text.indexOf("y"), end: sourceFile.text.indexOf(";") }, newNode); - }); - } - { - const text = ` + runSingleFileTest("replaceRangeNoLineBreakBefore", /*placeOpenBraceOnNewLineForFunctions*/ true, `const x = 1, y = "2";`, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = createTestVariableDeclaration("z1"); + changeTracker.replaceRange(sourceFile, { pos: sourceFile.text.indexOf("y"), end: sourceFile.text.indexOf(";") }, newNode); + }); + } + { + const text = ` namespace A { const x = 1, y = "2"; } `; - runSingleFileTest("replaceNode1NoLineBreakBefore", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = createTestVariableDeclaration("z1"); - changeTracker.replaceNode(sourceFile, findChild("y", sourceFile), newNode); - }); - } - { - const text = ` + runSingleFileTest("replaceNode1NoLineBreakBefore", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = createTestVariableDeclaration("z1"); + changeTracker.replaceNode(sourceFile, findChild("y", sourceFile), newNode); + }); + } + { + const text = ` // comment 1 var x = 1; // comment 2 // comment 3 @@ -246,24 +248,24 @@ var y = 2; // comment 4 var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; - runSingleFileTest("replaceNode1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); - }); - runSingleFileTest("replaceNode2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter }); - }); - runSingleFileTest("replaceNode3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter }); - }); - runSingleFileTest("replaceNode4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - runSingleFileTest("replaceNode5", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - } - { - const text = ` + runSingleFileTest("replaceNode1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); + }); + runSingleFileTest("replaceNode2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: ts.textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter }); + }); + runSingleFileTest("replaceNode3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { trailingTriviaOption: ts.textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter }); + }); + runSingleFileTest("replaceNode4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: ts.textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: ts.textChanges.TrailingTriviaOption.Exclude }); + }); + runSingleFileTest("replaceNode5", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { leadingTriviaOption: ts.textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: ts.textChanges.TrailingTriviaOption.Exclude }); + }); + } + { + const text = ` // comment 1 var x = 1; // comment 2 // comment 3 @@ -271,21 +273,21 @@ var y = 2; // comment 4 var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; - runSingleFileTest("replaceNodeRange1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { suffix: newLineCharacter }); - }); - runSingleFileTest("replaceNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter }); - }); - runSingleFileTest("replaceNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter }); - }); - runSingleFileTest("replaceNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); - }); - } - { - const text = ` + runSingleFileTest("replaceNodeRange1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { suffix: newLineCharacter }); + }); + runSingleFileTest("replaceNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: ts.textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter }); + }); + runSingleFileTest("replaceNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { trailingTriviaOption: ts.textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter }); + }); + runSingleFileTest("replaceNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: ts.textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: ts.textChanges.TrailingTriviaOption.Exclude }); + }); + } + { + const text = ` // comment 1 var x = 1; // comment 2 // comment 3 @@ -293,15 +295,15 @@ var y; // comment 4 var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; - runSingleFileTest("insertNodeBefore3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); - }); - runSingleFileTest("insertNodeAfterVariableDeclaration", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableDeclarationContaining("y", sourceFile), createTestVariableDeclaration("z1")); - }); - } - { - const text = ` + runSingleFileTest("insertNodeBefore3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); + }); + runSingleFileTest("insertNodeAfterVariableDeclaration", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findVariableDeclarationContaining("y", sourceFile), createTestVariableDeclaration("z1")); + }); + } + { + const text = ` namespace M { // comment 1 var x = 1; // comment 2 @@ -311,107 +313,107 @@ namespace M { // comment 6 var a = 4; // comment 7 }`; - runSingleFileTest("insertNodeBefore1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); - }); - runSingleFileTest("insertNodeBefore2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass()); - }); - runSingleFileTest("insertNodeAfter1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); - }); - runSingleFileTest("insertNodeAfter2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass()); - }); - } + runSingleFileTest("insertNodeBefore1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); + }); + runSingleFileTest("insertNodeBefore2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass()); + }); + runSingleFileTest("insertNodeAfter1", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass()); + }); + runSingleFileTest("insertNodeAfter2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass()); + }); + } - function findConstructor(sourceFile: SourceFile): ConstructorDeclaration { - const classDecl = sourceFile.statements[0] as ClassDeclaration; - return find(classDecl.members, (m): m is ConstructorDeclaration => isConstructorDeclaration(m) && !!m.body)!; - } - function createTestSuperCall() { - const superCall = factory.createCallExpression( - factory.createSuper(), - /*typeArguments*/ undefined, - /*argumentsArray*/ emptyArray - ); - return factory.createExpressionStatement(superCall); - } + function findConstructor(sourceFile: ts.SourceFile): ts.ConstructorDeclaration { + const classDecl = sourceFile.statements[0] as ts.ClassDeclaration; + return ts.find(classDecl.members, (m): m is ts.ConstructorDeclaration => ts.isConstructorDeclaration(m) && !!m.body)!; + } + function createTestSuperCall() { + const superCall = ts.factory.createCallExpression( + ts.factory.createSuper(), + /*typeArguments*/ undefined, + /*argumentsArray*/ ts.emptyArray + ); + return ts.factory.createExpressionStatement(superCall); + } - { - const text1 = ` + { + const text1 = ` class A { constructor() { } } `; - runSingleFileTest("insertNodeAtConstructorStart", /*placeOpenBraceOnNewLineForFunctions*/ false, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAtConstructorStart(sourceFile, findConstructor(sourceFile), createTestSuperCall()); - }); - const text2 = ` + runSingleFileTest("insertNodeAtConstructorStart", /*placeOpenBraceOnNewLineForFunctions*/ false, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAtConstructorStart(sourceFile, findConstructor(sourceFile), createTestSuperCall()); + }); + const text2 = ` class A { constructor() { var x = 1; } } `; - runSingleFileTest("insertNodeAfter4", /*placeOpenBraceOnNewLineForFunctions*/ false, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall()); - }); - const text3 = ` + runSingleFileTest("insertNodeAfter4", /*placeOpenBraceOnNewLineForFunctions*/ false, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall()); + }); + const text3 = ` class A { constructor() { } } `; - runSingleFileTest("insertNodeAtConstructorStart-block with newline", /*placeOpenBraceOnNewLineForFunctions*/ false, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAtConstructorStart(sourceFile, findConstructor(sourceFile), createTestSuperCall()); - }); - } - { - const text = `var a = 1, b = 2, c = 3;`; - runSingleFileTest("deleteNodeInList1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = `var a = 1,b = 2,c = 3;`; - runSingleFileTest("deleteNodeInList1_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList2_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList3_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("insertNodeAtConstructorStart-block with newline", /*placeOpenBraceOnNewLineForFunctions*/ false, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAtConstructorStart(sourceFile, findConstructor(sourceFile), createTestSuperCall()); + }); + } + { + const text = `var a = 1, b = 2, c = 3;`; + runSingleFileTest("deleteNodeInList1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = `var a = 1,b = 2,c = 3;`; + runSingleFileTest("deleteNodeInList1_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList2_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList3_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` namespace M { var a = 1, b = 2, c = 3; }`; - runSingleFileTest("deleteNodeInList4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList6", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeInList4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList6", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` namespace M { var a = 1, // comment 1 // comment 2 @@ -419,352 +421,351 @@ namespace M { // comment 4 c = 3; // comment 5 }`; - runSingleFileTest("deleteNodeInList4_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList5_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList6_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeInList4_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList5_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList6_1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` function foo(a: number, b: string, c = true) { return 1; }`; - runSingleFileTest("deleteNodeInList7", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList8", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList9", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeInList7", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList8", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList9", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` function foo(a: number,b: string,c = true) { return 1; }`; - runSingleFileTest("deleteNodeInList10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeInList10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` function foo( a: number, b: string, c = true) { return 1; }`; - runSingleFileTest("deleteNodeInList13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("a", sourceFile)); - }); - runSingleFileTest("deleteNodeInList14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("b", sourceFile)); - }); - runSingleFileTest("deleteNodeInList15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.delete(sourceFile, findChild("c", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeInList13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.delete(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` const x = 1, y = 2;`; - runSingleFileTest("insertNodeInListAfter1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - runSingleFileTest("insertNodeInListAfter2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, ts.factory.createNumericLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), ts.factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, ts.factory.createNumericLiteral(1))); + }); + } + { + const text = ` const /*x*/ x = 1, /*y*/ y = 2;`; - runSingleFileTest("insertNodeInListAfter3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - runSingleFileTest("insertNodeInListAfter4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, ts.factory.createNumericLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), ts.factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, ts.factory.createNumericLiteral(1))); + }); + } + { + const text = ` const x = 1;`; - runSingleFileTest("insertNodeInListAfter5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, ts.factory.createNumericLiteral(1))); + }); + } + { + const text = ` const x = 1, y = 2;`; - runSingleFileTest("insertNodeInListAfter6", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - runSingleFileTest("insertNodeInListAfter7", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter6", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, ts.factory.createNumericLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter7", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), ts.factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, ts.factory.createNumericLiteral(1))); + }); + } + { + const text = ` const /*x*/ x = 1, /*y*/ y = 2;`; - runSingleFileTest("insertNodeInListAfter8", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - runSingleFileTest("insertNodeInListAfter9", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, factory.createNumericLiteral(1))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter8", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, ts.factory.createNumericLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter9", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), ts.factory.createVariableDeclaration("z", /*exclamationToken*/ undefined, /*type*/ undefined, ts.factory.createNumericLiteral(1))); + }); + } + { + const text = ` import { x } from "bar"`; - runSingleFileTest("insertNodeInListAfter10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter10", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createImportSpecifier(/*isTypeOnly*/ false, ts.factory.createIdentifier("b"), ts.factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x // this is x } from "bar"`; - runSingleFileTest("insertNodeInListAfter11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter11", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createImportSpecifier(/*isTypeOnly*/ false, ts.factory.createIdentifier("b"), ts.factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x } from "bar"`; - runSingleFileTest("insertNodeInListAfter12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line local/boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter12", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line local/boolean-trivia + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, ts.factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x // this is x } from "bar"`; - runSingleFileTest("insertNodeInListAfter13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line local/boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter13", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line local/boolean-trivia + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, ts.factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x0, x } from "bar"`; - runSingleFileTest("insertNodeInListAfter14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter14", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createImportSpecifier(/*isTypeOnly*/ false, ts.factory.createIdentifier("b"), ts.factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x0, x // this is x } from "bar"`; - runSingleFileTest("insertNodeInListAfter15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("b"), factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter15", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createImportSpecifier(/*isTypeOnly*/ false, ts.factory.createIdentifier("b"), ts.factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x0, x } from "bar"`; - runSingleFileTest("insertNodeInListAfter16", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line local/boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter16", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line local/boolean-trivia + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, ts.factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x0, x // this is x } from "bar"`; - runSingleFileTest("insertNodeInListAfter17", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line local/boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInListAfter17", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line local/boolean-trivia + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, ts.factory.createIdentifier("a"))); + }); + } + { + const text = ` import { x0, x } from "bar"`; - runSingleFileTest("insertNodeInListAfter18", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + runSingleFileTest("insertNodeInListAfter18", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line local/boolean-trivia + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), ts.factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, ts.factory.createIdentifier("a"))); + }); + } + { + const runTest = (name: string, text: string) => runSingleFileTest(name, /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + for (const specifier of ["x3", "x4", "x5"]) { // eslint-disable-next-line local/boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier("a"))); - }); - } - { - const runTest = (name: string, text: string) => runSingleFileTest(name, /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - for (const specifier of ["x3", "x4", "x5"]) { - // eslint-disable-next-line local/boolean-trivia - changeTracker.insertNodeInListAfter(sourceFile, findChild("x2", sourceFile), factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, factory.createIdentifier(specifier))); - } - }); + changeTracker.insertNodeInListAfter(sourceFile, findChild("x2", sourceFile), ts.factory.createImportSpecifier(/*isTypeOnly*/ false, undefined, ts.factory.createIdentifier(specifier))); + } + }); - const crlfText = "import {\r\nx1,\r\nx2\r\n} from \"bar\";"; - runTest("insertNodeInListAfter19", crlfText); + const crlfText = "import {\r\nx1,\r\nx2\r\n} from \"bar\";"; + runTest("insertNodeInListAfter19", crlfText); - const lfText = "import {\nx1,\nx2\n} from \"bar\";"; - runTest("insertNodeInListAfter20", lfText); - } - { - const text = ` + const lfText = "import {\nx1,\nx2\n} from \"bar\";"; + runTest("insertNodeInListAfter20", lfText); + } + { + const text = ` class A { x; }`; - runSingleFileTest("insertNodeAfterMultipleNodes", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNodes = []; - for (let i = 0; i < 11 /*error doesn't occur with fewer nodes*/; ++i) { - newNodes.push( - // eslint-disable-next-line local/boolean-trivia - factory.createPropertyDeclaration(undefined, i + "", undefined, undefined, undefined)); - } - const insertAfter = findChild("x", sourceFile); - for (const newNode of newNodes) { - changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode); - } - }); - } - { - const text = ` + runSingleFileTest("insertNodeAfterMultipleNodes", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNodes = []; + for (let i = 0; i < 11 /*error doesn't occur with fewer nodes*/; ++i) { + newNodes.push( + // eslint-disable-next-line local/boolean-trivia + ts.factory.createPropertyDeclaration(undefined, i + "", undefined, undefined, undefined)); + } + const insertAfter = findChild("x", sourceFile); + for (const newNode of newNodes) { + changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode); + } + }); + } + { + const text = ` class A { x } `; - runSingleFileTest("insertNodeAfterInClass1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line local/boolean-trivia - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), factory.createPropertyDeclaration(undefined, "a", undefined, factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined)); - }); - } - { - const text = ` + runSingleFileTest("insertNodeAfterInClass1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line local/boolean-trivia + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), ts.factory.createPropertyDeclaration(undefined, "a", undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword), undefined)); + }); + } + { + const text = ` class A { x; } `; - runSingleFileTest("insertNodeAfterInClass2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - // eslint-disable-next-line local/boolean-trivia - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), factory.createPropertyDeclaration(undefined, "a", undefined, factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined)); - }); - } - { - const text = ` + runSingleFileTest("insertNodeAfterInClass2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + // eslint-disable-next-line local/boolean-trivia + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), ts.factory.createPropertyDeclaration(undefined, "a", undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword), undefined)); + }); + } + { + const text = ` class A { x; y = 1; } `; - runSingleFileTest("deleteNodeAfterInClass1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findChild("x", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeAfterInClass1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findChild("x", sourceFile)); + }); + } + { + const text = ` class A { x y = 1; } `; - runSingleFileTest("deleteNodeAfterInClass2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findChild("x", sourceFile)); - }); - } - { - const text = ` + runSingleFileTest("deleteNodeAfterInClass2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + deleteNode(changeTracker, sourceFile, findChild("x", sourceFile)); + }); + } + { + const text = ` class A { x = foo } `; - runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = factory.createPropertyDeclaration( - /*modifiers*/ undefined, - factory.createComputedPropertyName(factory.createNumericLiteral(1)), - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), - /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = ts.factory.createPropertyDeclaration( + /*modifiers*/ undefined, + ts.factory.createComputedPropertyName(ts.factory.createNumericLiteral(1)), + /*questionToken*/ undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + /*initializer*/ undefined); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); + }); + } + { + const text = ` class A { x() { } } `; - runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = factory.createPropertyDeclaration( - /*modifiers*/ undefined, - factory.createComputedPropertyName(factory.createNumericLiteral(1)), - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), - /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = ts.factory.createPropertyDeclaration( + /*modifiers*/ undefined, + ts.factory.createComputedPropertyName(ts.factory.createNumericLiteral(1)), + /*questionToken*/ undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + /*initializer*/ undefined); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); + }); + } + { + const text = ` interface A { x } `; - runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = factory.createPropertyDeclaration( - /*modifiers*/ undefined, - factory.createComputedPropertyName(factory.createNumericLiteral(1)), - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), - /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = ts.factory.createPropertyDeclaration( + /*modifiers*/ undefined, + ts.factory.createComputedPropertyName(ts.factory.createNumericLiteral(1)), + /*questionToken*/ undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + /*initializer*/ undefined); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); + }); + } + { + const text = ` interface A { x() } `; - runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = factory.createPropertyDeclaration( - /*modifiers*/ undefined, - factory.createComputedPropertyName(factory.createNumericLiteral(1)), - /*questionToken*/ undefined, - factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), - /*initializer*/ undefined); - changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); - }); - } - { - const text = ` + runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = ts.factory.createPropertyDeclaration( + /*modifiers*/ undefined, + ts.factory.createComputedPropertyName(ts.factory.createNumericLiteral(1)), + /*questionToken*/ undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + /*initializer*/ undefined); + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode); + }); + } + { + const text = ` let x = foo `; - runSingleFileTest("insertNodeInStatementListAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - const newNode = factory.createExpressionStatement(factory.createParenthesizedExpression(factory.createNumericLiteral(1))); - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), newNode); - }); - } - }); -} + runSingleFileTest("insertNodeInStatementListAfterNodeWithoutSeparator1", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = ts.factory.createExpressionStatement(ts.factory.createParenthesizedExpression(ts.factory.createNumericLiteral(1))); + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), newNode); + }); + } +}); diff --git a/src/testRunner/unittests/services/transpile.ts b/src/testRunner/unittests/services/transpile.ts index e0cfa72f53eb5..4eed8f72ac042 100644 --- a/src/testRunner/unittests/services/transpile.ts +++ b/src/testRunner/unittests/services/transpile.ts @@ -1,503 +1,504 @@ -namespace ts { - describe("unittests:: services:: Transpile", () => { +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; - interface TranspileTestSettings { - options?: TranspileOptions; - noSetFileName?: boolean; - } +describe("unittests:: services:: Transpile", () => { - function transpilesCorrectly(name: string, input: string, testSettings: TranspileTestSettings) { - describe(name, () => { - let transpileResult: TranspileOutput; - let oldTranspileResult: string; - let oldTranspileDiagnostics: Diagnostic[]; + interface TranspileTestSettings { + options?: ts.TranspileOptions; + noSetFileName?: boolean; + } - const transpileOptions: TranspileOptions = testSettings.options || {}; - if (!transpileOptions.compilerOptions) { - transpileOptions.compilerOptions = { }; - } - if (transpileOptions.compilerOptions.target === undefined) { - transpileOptions.compilerOptions.target = ScriptTarget.ES3; - } + function transpilesCorrectly(name: string, input: string, testSettings: TranspileTestSettings) { + describe(name, () => { + let transpileResult: ts.TranspileOutput; + let oldTranspileResult: string; + let oldTranspileDiagnostics: ts.Diagnostic[]; - if (transpileOptions.compilerOptions.newLine === undefined) { - // use \r\n as default new line - transpileOptions.compilerOptions.newLine = NewLineKind.CarriageReturnLineFeed; - } - - transpileOptions.compilerOptions.sourceMap = true; - - let unitName = transpileOptions.fileName; - if (!unitName) { - unitName = transpileOptions.compilerOptions.jsx ? "file.tsx" : "file.ts"; - if (!testSettings.noSetFileName) { - transpileOptions.fileName = unitName; - } - } + const transpileOptions: ts.TranspileOptions = testSettings.options || {}; + if (!transpileOptions.compilerOptions) { + transpileOptions.compilerOptions = { }; + } + if (transpileOptions.compilerOptions.target === undefined) { + transpileOptions.compilerOptions.target = ts.ScriptTarget.ES3; + } - transpileOptions.reportDiagnostics = true; + if (transpileOptions.compilerOptions.newLine === undefined) { + // use \r\n as default new line + transpileOptions.compilerOptions.newLine = ts.NewLineKind.CarriageReturnLineFeed; + } - const justName = "transpile/" + name.replace(/[^a-z0-9\-. ]/ig, "") + (transpileOptions.compilerOptions.jsx ? Extension.Tsx : Extension.Ts); - const toBeCompiled = [{ - unitName, - content: input - }]; - const canUseOldTranspile = !transpileOptions.renamedDependencies; + transpileOptions.compilerOptions.sourceMap = true; - before(() => { - transpileResult = transpileModule(input, transpileOptions); + let unitName = transpileOptions.fileName; + if (!unitName) { + unitName = transpileOptions.compilerOptions.jsx ? "file.tsx" : "file.ts"; + if (!testSettings.noSetFileName) { + transpileOptions.fileName = unitName; + } + } - if (canUseOldTranspile) { - oldTranspileDiagnostics = []; - oldTranspileResult = transpile(input, transpileOptions.compilerOptions, transpileOptions.fileName, oldTranspileDiagnostics, transpileOptions.moduleName); - } - }); + transpileOptions.reportDiagnostics = true; - after(() => { - transpileResult = undefined!; - oldTranspileResult = undefined!; - oldTranspileDiagnostics = undefined!; - }); + const justName = "transpile/" + name.replace(/[^a-z0-9\-. ]/ig, "") + (transpileOptions.compilerOptions.jsx ? ts.Extension.Tsx : ts.Extension.Ts); + const toBeCompiled = [{ + unitName, + content: input + }]; + const canUseOldTranspile = !transpileOptions.renamedDependencies; - /* eslint-disable no-null/no-null */ - it("Correct errors for " + justName, () => { - Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".errors.txt"), - transpileResult.diagnostics!.length === 0 ? null : Harness.Compiler.getErrorBaseline(toBeCompiled, transpileResult.diagnostics!)); - }); + before(() => { + transpileResult = ts.transpileModule(input, transpileOptions); if (canUseOldTranspile) { - it("Correct errors (old transpile) for " + justName, () => { - Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.errors.txt"), - oldTranspileDiagnostics.length === 0 ? null : Harness.Compiler.getErrorBaseline(toBeCompiled, oldTranspileDiagnostics)); - }); + oldTranspileDiagnostics = []; + oldTranspileResult = ts.transpile(input, transpileOptions.compilerOptions, transpileOptions.fileName, oldTranspileDiagnostics, transpileOptions.moduleName); } - /* eslint-enable no-null/no-null */ + }); - it("Correct output for " + justName, () => { - Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, Extension.Js), transpileResult.outputText); - }); + after(() => { + transpileResult = undefined!; + oldTranspileResult = undefined!; + oldTranspileDiagnostics = undefined!; + }); - if (canUseOldTranspile) { - it("Correct output (old transpile) for " + justName, () => { - Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.js"), oldTranspileResult); - }); - } + /* eslint-disable no-null/no-null */ + it("Correct errors for " + justName, () => { + Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".errors.txt"), + transpileResult.diagnostics!.length === 0 ? null : Harness.Compiler.getErrorBaseline(toBeCompiled, transpileResult.diagnostics!)); }); - } - transpilesCorrectly("Generates no diagnostics with valid inputs", `var x = 0;`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + if (canUseOldTranspile) { + it("Correct errors (old transpile) for " + justName, () => { + Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.errors.txt"), + oldTranspileDiagnostics.length === 0 ? null : Harness.Compiler.getErrorBaseline(toBeCompiled, oldTranspileDiagnostics)); + }); + } + /* eslint-enable no-null/no-null */ - transpilesCorrectly("Generates no diagnostics for missing file references", `/// -var x = 0;`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + it("Correct output for " + justName, () => { + Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ts.Extension.Js), transpileResult.outputText); + }); - transpilesCorrectly("Generates no diagnostics for missing module imports", `import {a} from "module2";`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } + if (canUseOldTranspile) { + it("Correct output (old transpile) for " + justName, () => { + Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.js"), oldTranspileResult); + }); + } }); + } - transpilesCorrectly("Generates expected syntactic diagnostics", `a b`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + transpilesCorrectly("Generates no diagnostics with valid inputs", `var x = 0;`, { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS } } + }); - transpilesCorrectly("Does not generate semantic diagnostics", `var x: string = 0;`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + transpilesCorrectly("Generates no diagnostics for missing file references", `/// +var x = 0;`, { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS } } + }); - transpilesCorrectly("Generates module output", `var x = 0;`, { - options: { compilerOptions: { module: ModuleKind.AMD } } - }); + transpilesCorrectly("Generates no diagnostics for missing module imports", `import {a} from "module2";`, { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS } } + }); - transpilesCorrectly("Uses correct newLine character", `var x = 0;`, { - options: { compilerOptions: { module: ModuleKind.CommonJS, newLine: NewLineKind.LineFeed } } - }); + transpilesCorrectly("Generates expected syntactic diagnostics", `a b`, { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS } } + }); - transpilesCorrectly("Sets module name", "var x = 1;", { - options: { compilerOptions: { module: ModuleKind.System, newLine: NewLineKind.LineFeed }, moduleName: "NamedModule" } - }); + transpilesCorrectly("Does not generate semantic diagnostics", `var x: string = 0;`, { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS } } + }); - transpilesCorrectly("No extra errors for file without extension", `"use strict";\r\nvar x = 0;`, { - options: { compilerOptions: { module: ModuleKind.CommonJS }, fileName: "file" } - }); + transpilesCorrectly("Generates module output", `var x = 0;`, { + options: { compilerOptions: { module: ts.ModuleKind.AMD } } + }); - transpilesCorrectly("Rename dependencies - System", - `import {foo} from "SomeName";\n` + - `declare function use(a: any);\n` + - `use(foo);`, { - options: { compilerOptions: { module: ModuleKind.System, newLine: NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } - }); + transpilesCorrectly("Uses correct newLine character", `var x = 0;`, { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS, newLine: ts.NewLineKind.LineFeed } } + }); - transpilesCorrectly("Rename dependencies - AMD", - `import {foo} from "SomeName";\n` + - `declare function use(a: any);\n` + - `use(foo);`, { - options: { compilerOptions: { module: ModuleKind.AMD, newLine: NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } - }); + transpilesCorrectly("Sets module name", "var x = 1;", { + options: { compilerOptions: { module: ts.ModuleKind.System, newLine: ts.NewLineKind.LineFeed }, moduleName: "NamedModule" } + }); - transpilesCorrectly("Rename dependencies - UMD", - `import {foo} from "SomeName";\n` + - `declare function use(a: any);\n` + - `use(foo);`, { - options: { compilerOptions: { module: ModuleKind.UMD, newLine: NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } - }); + transpilesCorrectly("No extra errors for file without extension", `"use strict";\r\nvar x = 0;`, { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS }, fileName: "file" } + }); - transpilesCorrectly("Transpile with emit decorators and emit metadata", - `import {db} from './db';\n` + - `function someDecorator(target) {\n` + - ` return target;\n` + - `} \n` + - `@someDecorator\n` + - `class MyClass {\n` + - ` db: db;\n` + - ` constructor(db: db) {\n` + - ` this.db = db;\n` + - ` this.db.doSomething(); \n` + - ` }\n` + - `}\n` + - `export {MyClass}; \n`, { - options: { - compilerOptions: { - module: ModuleKind.CommonJS, - newLine: NewLineKind.LineFeed, - noEmitHelpers: true, - emitDecoratorMetadata: true, - experimentalDecorators: true, - target: ScriptTarget.ES5, - } + transpilesCorrectly("Rename dependencies - System", + `import {foo} from "SomeName";\n` + + `declare function use(a: any);\n` + + `use(foo);`, { + options: { compilerOptions: { module: ts.ModuleKind.System, newLine: ts.NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } + }); + + transpilesCorrectly("Rename dependencies - AMD", + `import {foo} from "SomeName";\n` + + `declare function use(a: any);\n` + + `use(foo);`, { + options: { compilerOptions: { module: ts.ModuleKind.AMD, newLine: ts.NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } + }); + + transpilesCorrectly("Rename dependencies - UMD", + `import {foo} from "SomeName";\n` + + `declare function use(a: any);\n` + + `use(foo);`, { + options: { compilerOptions: { module: ts.ModuleKind.UMD, newLine: ts.NewLineKind.LineFeed }, renamedDependencies: { SomeName: "SomeOtherName" } } + }); + + transpilesCorrectly("Transpile with emit decorators and emit metadata", + `import {db} from './db';\n` + + `function someDecorator(target) {\n` + + ` return target;\n` + + `} \n` + + `@someDecorator\n` + + `class MyClass {\n` + + ` db: db;\n` + + ` constructor(db: db) {\n` + + ` this.db = db;\n` + + ` this.db.doSomething(); \n` + + ` }\n` + + `}\n` + + `export {MyClass}; \n`, { + options: { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + newLine: ts.NewLineKind.LineFeed, + noEmitHelpers: true, + emitDecoratorMetadata: true, + experimentalDecorators: true, + target: ts.ScriptTarget.ES5, } - }); - - transpilesCorrectly("Supports backslashes in file name", "var x", { - options: { fileName: "a\\b.ts" } + } }); - transpilesCorrectly("transpile file as 'tsx' if 'jsx' is specified", `var x =
`, { - options: { compilerOptions: { jsx: JsxEmit.React, newLine: NewLineKind.LineFeed } } - }); + transpilesCorrectly("Supports backslashes in file name", "var x", { + options: { fileName: "a\\b.ts" } + }); - transpilesCorrectly("transpile .js files", "const a = 10;", { - options: { compilerOptions: { newLine: NewLineKind.LineFeed, module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("transpile file as 'tsx' if 'jsx' is specified", `var x =
`, { + options: { compilerOptions: { jsx: ts.JsxEmit.React, newLine: ts.NewLineKind.LineFeed } } + }); - transpilesCorrectly("Supports urls in file name", "var x", { - options: { fileName: "http://somewhere/directory//directory2/file.ts" } - }); + transpilesCorrectly("transpile .js files", "const a = 10;", { + options: { compilerOptions: { newLine: ts.NewLineKind.LineFeed, module: ts.ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Accepts string as enum values for compile-options", "export const x = 0", { - options: { - compilerOptions: { - module: "es6" as any as ModuleKind, - // Capitalization and spaces ignored - target: " Es6 " as any as ScriptTarget - } + transpilesCorrectly("Supports urls in file name", "var x", { + options: { fileName: "http://somewhere/directory//directory2/file.ts" } + }); + + transpilesCorrectly("Accepts string as enum values for compile-options", "export const x = 0", { + options: { + compilerOptions: { + module: "es6" as any as ts.ModuleKind, + // Capitalization and spaces ignored + target: " Es6 " as any as ts.ScriptTarget } - }); + } + }); - transpilesCorrectly("Report an error when compiler-options module-kind is out-of-range", "", { - options: { compilerOptions: { module: 123 as any as ModuleKind } } - }); + transpilesCorrectly("Report an error when compiler-options module-kind is out-of-range", "", { + options: { compilerOptions: { module: 123 as any as ts.ModuleKind } } + }); - transpilesCorrectly("Report an error when compiler-options target-script is out-of-range", "", { - options: { compilerOptions: { module: 123 as any as ModuleKind } } - }); + transpilesCorrectly("Report an error when compiler-options target-script is out-of-range", "", { + options: { compilerOptions: { module: 123 as any as ts.ModuleKind } } + }); - transpilesCorrectly("Support options with lib values", "const a = 10;", { - options: { compilerOptions: { lib: ["es6", "dom"], module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Support options with lib values", "const a = 10;", { + options: { compilerOptions: { lib: ["es6", "dom"], module: ts.ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Support options with types values", "const a = 10;", { - options: { compilerOptions: { types: ["jquery", "typescript"], module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Support options with types values", "const a = 10;", { + options: { compilerOptions: { types: ["jquery", "typescript"], module: ts.ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'allowJs'", "x;", { - options: { compilerOptions: { allowJs: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'allowJs'", "x;", { + options: { compilerOptions: { allowJs: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'allowSyntheticDefaultImports'", "x;", { - options: { compilerOptions: { allowSyntheticDefaultImports: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'allowSyntheticDefaultImports'", "x;", { + options: { compilerOptions: { allowSyntheticDefaultImports: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'allowUnreachableCode'", "x;", { - options: { compilerOptions: { allowUnreachableCode: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'allowUnreachableCode'", "x;", { + options: { compilerOptions: { allowUnreachableCode: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'allowUnusedLabels'", "x;", { - options: { compilerOptions: { allowUnusedLabels: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'allowUnusedLabels'", "x;", { + options: { compilerOptions: { allowUnusedLabels: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'alwaysStrict'", "x;", { - options: { compilerOptions: { alwaysStrict: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'alwaysStrict'", "x;", { + options: { compilerOptions: { alwaysStrict: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'baseUrl'", "x;", { - options: { compilerOptions: { baseUrl: "./folder/baseUrl" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'baseUrl'", "x;", { + options: { compilerOptions: { baseUrl: "./folder/baseUrl" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'charset'", "x;", { - options: { compilerOptions: { charset: "en-us" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'charset'", "x;", { + options: { compilerOptions: { charset: "en-us" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'declaration'", "x;", { - options: { compilerOptions: { declaration: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'declaration'", "x;", { + options: { compilerOptions: { declaration: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'declarationDir'", "x;", { - options: { compilerOptions: { declarationDir: "out/declarations" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'declarationDir'", "x;", { + options: { compilerOptions: { declarationDir: "out/declarations" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'emitBOM'", "x;", { - options: { compilerOptions: { emitBOM: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'emitBOM'", "x;", { + options: { compilerOptions: { emitBOM: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'emitDecoratorMetadata'", "x;", { - options: { compilerOptions: { emitDecoratorMetadata: true, experimentalDecorators: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'emitDecoratorMetadata'", "x;", { + options: { compilerOptions: { emitDecoratorMetadata: true, experimentalDecorators: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'experimentalDecorators'", "x;", { - options: { compilerOptions: { experimentalDecorators: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'experimentalDecorators'", "x;", { + options: { compilerOptions: { experimentalDecorators: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'forceConsistentCasingInFileNames'", "x;", { - options: { compilerOptions: { forceConsistentCasingInFileNames: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'forceConsistentCasingInFileNames'", "x;", { + options: { compilerOptions: { forceConsistentCasingInFileNames: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'isolatedModules'", "x;", { - options: { compilerOptions: { isolatedModules: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'isolatedModules'", "x;", { + options: { compilerOptions: { isolatedModules: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'jsx'", "x;", { - options: { compilerOptions: { jsx: 1 }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'jsx'", "x;", { + options: { compilerOptions: { jsx: 1 }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'lib'", "x;", { - options: { compilerOptions: { lib: ["es2015", "dom"] }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'lib'", "x;", { + options: { compilerOptions: { lib: ["es2015", "dom"] }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'locale'", "x;", { - options: { compilerOptions: { locale: "en-us" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'locale'", "x;", { + options: { compilerOptions: { locale: "en-us" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'module'", "x;", { - options: { compilerOptions: { module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'module'", "x;", { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'moduleResolution'", "x;", { - options: { compilerOptions: { moduleResolution: ModuleResolutionKind.NodeJs }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'moduleResolution'", "x;", { + options: { compilerOptions: { moduleResolution: ts.ModuleResolutionKind.NodeJs }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'newLine'", "x;", { - options: { compilerOptions: { newLine: NewLineKind.CarriageReturnLineFeed }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'newLine'", "x;", { + options: { compilerOptions: { newLine: ts.NewLineKind.CarriageReturnLineFeed }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noEmit'", "x;", { - options: { compilerOptions: { noEmit: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noEmit'", "x;", { + options: { compilerOptions: { noEmit: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noEmitHelpers'", "x;", { - options: { compilerOptions: { noEmitHelpers: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noEmitHelpers'", "x;", { + options: { compilerOptions: { noEmitHelpers: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noEmitOnError'", "x;", { - options: { compilerOptions: { noEmitOnError: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noEmitOnError'", "x;", { + options: { compilerOptions: { noEmitOnError: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noErrorTruncation'", "x;", { - options: { compilerOptions: { noErrorTruncation: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noErrorTruncation'", "x;", { + options: { compilerOptions: { noErrorTruncation: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noFallthroughCasesInSwitch'", "x;", { - options: { compilerOptions: { noFallthroughCasesInSwitch: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noFallthroughCasesInSwitch'", "x;", { + options: { compilerOptions: { noFallthroughCasesInSwitch: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noImplicitAny'", "x;", { - options: { compilerOptions: { noImplicitAny: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noImplicitAny'", "x;", { + options: { compilerOptions: { noImplicitAny: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noImplicitReturns'", "x;", { - options: { compilerOptions: { noImplicitReturns: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noImplicitReturns'", "x;", { + options: { compilerOptions: { noImplicitReturns: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noImplicitThis'", "x;", { - options: { compilerOptions: { noImplicitThis: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noImplicitThis'", "x;", { + options: { compilerOptions: { noImplicitThis: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noImplicitUseStrict'", "x;", { - options: { compilerOptions: { noImplicitUseStrict: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noImplicitUseStrict'", "x;", { + options: { compilerOptions: { noImplicitUseStrict: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noLib'", "x;", { - options: { compilerOptions: { noLib: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noLib'", "x;", { + options: { compilerOptions: { noLib: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'noResolve'", "x;", { - options: { compilerOptions: { noResolve: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'noResolve'", "x;", { + options: { compilerOptions: { noResolve: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'out'", "x;", { - options: { compilerOptions: { out: "./out" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'out'", "x;", { + options: { compilerOptions: { out: "./out" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'outDir'", "x;", { - options: { compilerOptions: { outDir: "./outDir" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'outDir'", "x;", { + options: { compilerOptions: { outDir: "./outDir" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'outFile'", "x;", { - options: { compilerOptions: { outFile: "./outFile" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'outFile'", "x;", { + options: { compilerOptions: { outFile: "./outFile" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'paths'", "x;", { - options: { compilerOptions: { paths: { "*": ["./generated*"] } }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'paths'", "x;", { + options: { compilerOptions: { paths: { "*": ["./generated*"] } }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'preserveConstEnums'", "x;", { - options: { compilerOptions: { preserveConstEnums: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'preserveConstEnums'", "x;", { + options: { compilerOptions: { preserveConstEnums: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'reactNamespace'", "x;", { - options: { compilerOptions: { reactNamespace: "react" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'reactNamespace'", "x;", { + options: { compilerOptions: { reactNamespace: "react" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'jsxFactory'", "x;", { - options: { compilerOptions: { jsxFactory: "createElement" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'jsxFactory'", "x;", { + options: { compilerOptions: { jsxFactory: "createElement" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'jsxFragmentFactory'", "x;", { - options: { compilerOptions: { jsxFactory: "x", jsxFragmentFactory: "frag" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'jsxFragmentFactory'", "x;", { + options: { compilerOptions: { jsxFactory: "x", jsxFragmentFactory: "frag" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'removeComments'", "x;", { - options: { compilerOptions: { removeComments: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'removeComments'", "x;", { + options: { compilerOptions: { removeComments: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'rootDir'", "x;", { - options: { compilerOptions: { rootDir: "./rootDir" }, fileName: "./rootDir/input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'rootDir'", "x;", { + options: { compilerOptions: { rootDir: "./rootDir" }, fileName: "./rootDir/input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'rootDirs'", "x;", { - options: { compilerOptions: { rootDirs: ["./a", "./b"] }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'rootDirs'", "x;", { + options: { compilerOptions: { rootDirs: ["./a", "./b"] }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'skipLibCheck'", "x;", { - options: { compilerOptions: { skipLibCheck: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'skipLibCheck'", "x;", { + options: { compilerOptions: { skipLibCheck: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'skipDefaultLibCheck'", "x;", { - options: { compilerOptions: { skipDefaultLibCheck: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'skipDefaultLibCheck'", "x;", { + options: { compilerOptions: { skipDefaultLibCheck: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'strictNullChecks'", "x;", { - options: { compilerOptions: { strictNullChecks: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'strictNullChecks'", "x;", { + options: { compilerOptions: { strictNullChecks: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'stripInternal'", "x;", { - options: { compilerOptions: { stripInternal: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'stripInternal'", "x;", { + options: { compilerOptions: { stripInternal: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'suppressExcessPropertyErrors'", "x;", { - options: { compilerOptions: { suppressExcessPropertyErrors: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'suppressExcessPropertyErrors'", "x;", { + options: { compilerOptions: { suppressExcessPropertyErrors: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'suppressImplicitAnyIndexErrors'", "x;", { - options: { compilerOptions: { suppressImplicitAnyIndexErrors: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'suppressImplicitAnyIndexErrors'", "x;", { + options: { compilerOptions: { suppressImplicitAnyIndexErrors: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'target'", "x;", { - options: { compilerOptions: { target: 2 }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'target'", "x;", { + options: { compilerOptions: { target: 2 }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'types'", "x;", { - options: { compilerOptions: { types: ["jquery", "jasmine"] }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'types'", "x;", { + options: { compilerOptions: { types: ["jquery", "jasmine"] }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'typeRoots'", "x;", { - options: { compilerOptions: { typeRoots: ["./folder"] }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'typeRoots'", "x;", { + options: { compilerOptions: { typeRoots: ["./folder"] }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'incremental'", "x;", { - options: { compilerOptions: { incremental: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'incremental'", "x;", { + options: { compilerOptions: { incremental: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'composite'", "x;", { - options: { compilerOptions: { composite: true }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'composite'", "x;", { + options: { compilerOptions: { composite: true }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Supports setting 'tsbuildinfo'", "x;", { - options: { compilerOptions: { incremental: true, tsBuildInfoFile: "./folder/config.tsbuildinfo" }, fileName: "input.js", reportDiagnostics: true } - }); + transpilesCorrectly("Supports setting 'tsbuildinfo'", "x;", { + options: { compilerOptions: { incremental: true, tsBuildInfoFile: "./folder/config.tsbuildinfo" }, fileName: "input.js", reportDiagnostics: true } + }); - transpilesCorrectly("Correctly serialize metadata when transpile with CommonJS option", - `import * as ng from "angular2/core";` + - `declare function foo(...args: any[]);` + - `@foo` + - `export class MyClass1 {` + - ` constructor(private _elementRef: ng.ElementRef){}` + - `}`, { - options: { - compilerOptions: { - target: ScriptTarget.ES5, - module: ModuleKind.CommonJS, - moduleResolution: ModuleResolutionKind.NodeJs, - emitDecoratorMetadata: true, - experimentalDecorators: true, - isolatedModules: true, - } + transpilesCorrectly("Correctly serialize metadata when transpile with CommonJS option", + `import * as ng from "angular2/core";` + + `declare function foo(...args: any[]);` + + `@foo` + + `export class MyClass1 {` + + ` constructor(private _elementRef: ng.ElementRef){}` + + `}`, { + options: { + compilerOptions: { + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.CommonJS, + moduleResolution: ts.ModuleResolutionKind.NodeJs, + emitDecoratorMetadata: true, + experimentalDecorators: true, + isolatedModules: true, } } - ); - - transpilesCorrectly("Correctly serialize metadata when transpile with System option", - `import * as ng from "angular2/core";` + - `declare function foo(...args: any[]);` + - `@foo` + - `export class MyClass1 {` + - ` constructor(private _elementRef: ng.ElementRef){}` + - `}`, { - options: { - compilerOptions: { - target: ScriptTarget.ES5, - module: ModuleKind.System, - moduleResolution: ModuleResolutionKind.NodeJs, - emitDecoratorMetadata: true, - experimentalDecorators: true, - isolatedModules: true, - } + } + ); + + transpilesCorrectly("Correctly serialize metadata when transpile with System option", + `import * as ng from "angular2/core";` + + `declare function foo(...args: any[]);` + + `@foo` + + `export class MyClass1 {` + + ` constructor(private _elementRef: ng.ElementRef){}` + + `}`, { + options: { + compilerOptions: { + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.System, + moduleResolution: ts.ModuleResolutionKind.NodeJs, + emitDecoratorMetadata: true, + experimentalDecorators: true, + isolatedModules: true, } } - ); + } + ); - transpilesCorrectly("Supports readonly keyword for arrays", "let x: readonly string[];", { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + transpilesCorrectly("Supports readonly keyword for arrays", "let x: readonly string[];", { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS } } + }); - transpilesCorrectly("Supports 'as const' arrays", `([] as const).forEach(k => console.log(k));`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - }); + transpilesCorrectly("Supports 'as const' arrays", `([] as const).forEach(k => console.log(k));`, { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS } } + }); - transpilesCorrectly("Infer correct file extension", `const fn = (a: T) => a`, { - noSetFileName: true - }); + transpilesCorrectly("Infer correct file extension", `const fn = (a: T) => a`, { + noSetFileName: true + }); - transpilesCorrectly("Export star as ns conflict does not crash", ` + transpilesCorrectly("Export star as ns conflict does not crash", ` var a; export { a as alias }; export * as alias from './file';`, { - noSetFileName: true - }); + noSetFileName: true + }); - transpilesCorrectly("Elides import equals referenced only by export type", - `import IFoo = Namespace.IFoo;` + - `export type { IFoo };`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - } - ); + transpilesCorrectly("Elides import equals referenced only by export type", + `import IFoo = Namespace.IFoo;` + + `export type { IFoo };`, { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS } } + } + ); - transpilesCorrectly("Elides import equals referenced only by type only export specifier", - `import IFoo = Namespace.IFoo;` + - `export { type IFoo };`, { - options: { compilerOptions: { module: ModuleKind.CommonJS } } - } - ); - }); -} + transpilesCorrectly("Elides import equals referenced only by type only export specifier", + `import IFoo = Namespace.IFoo;` + + `export { type IFoo };`, { + options: { compilerOptions: { module: ts.ModuleKind.CommonJS } } + } + ); +}); diff --git a/src/testRunner/unittests/transform.ts b/src/testRunner/unittests/transform.ts index dadf9ecd6ae71..00af9287364ef 100644 --- a/src/testRunner/unittests/transform.ts +++ b/src/testRunner/unittests/transform.ts @@ -1,448 +1,454 @@ -namespace ts { - describe("unittests:: TransformAPI", () => { - function replaceUndefinedWithVoid0(context: TransformationContext) { - const previousOnSubstituteNode = context.onSubstituteNode; - context.enableSubstitution(SyntaxKind.Identifier); - context.onSubstituteNode = (hint, node) => { - node = previousOnSubstituteNode(hint, node); - if (hint === EmitHint.Expression && isIdentifier(node) && node.escapedText === "undefined") { - node = factory.createPartiallyEmittedExpression( - addSyntheticTrailingComment( - setTextRange( - factory.createVoidZero(), - node), - SyntaxKind.MultiLineCommentTrivia, "undefined")); - } - return node; - }; - return (file: SourceFile) => file; - } - function replaceNumberWith2(context: TransformationContext) { - function visitor(node: Node): Node { - if (isNumericLiteral(node)) { - return factory.createNumericLiteral("2"); - } - return visitEachChild(node, visitor, context); +import * as ts from "../_namespaces/ts"; +import * as Harness from "../_namespaces/Harness"; +import * as evaluator from "../_namespaces/evaluator"; +import * as vfs from "../_namespaces/vfs"; +import * as documents from "../_namespaces/documents"; +import * as fakes from "../_namespaces/fakes"; + +describe("unittests:: TransformAPI", () => { + function replaceUndefinedWithVoid0(context: ts.TransformationContext) { + const previousOnSubstituteNode = context.onSubstituteNode; + context.enableSubstitution(ts.SyntaxKind.Identifier); + context.onSubstituteNode = (hint, node) => { + node = previousOnSubstituteNode(hint, node); + if (hint === ts.EmitHint.Expression && ts.isIdentifier(node) && node.escapedText === "undefined") { + node = ts.factory.createPartiallyEmittedExpression( + ts.addSyntheticTrailingComment( + ts.setTextRange( + ts.factory.createVoidZero(), + node), + ts.SyntaxKind.MultiLineCommentTrivia, "undefined")); } - return (file: SourceFile) => visitNode(file, visitor); - } - - function replaceIdentifiersNamedOldNameWithNewName(context: TransformationContext) { - const previousOnSubstituteNode = context.onSubstituteNode; - context.enableSubstitution(SyntaxKind.Identifier); - context.onSubstituteNode = (hint, node) => { - node = previousOnSubstituteNode(hint, node); - if (isIdentifier(node) && node.escapedText === "oldName") { - node = setTextRange(factory.createIdentifier("newName"), node); - } - return node; - }; - return (file: SourceFile) => file; - } - - function replaceIdentifiersNamedOldNameWithNewName2(context: TransformationContext) { - const visitor: Visitor = (node) => { - if (isIdentifier(node) && node.text === "oldName") { - return factory.createIdentifier("newName"); - } - return visitEachChild(node, visitor, context); - }; - return (node: SourceFile) => visitNode(node, visitor); - } - - function createTaggedTemplateLiteral(): Transformer { - return sourceFile => factory.updateSourceFile(sourceFile, [ - factory.createExpressionStatement( - factory.createTaggedTemplateExpression( - factory.createIdentifier("$tpl"), - /*typeArguments*/ undefined, - factory.createNoSubstitutionTemplateLiteral("foo", "foo"))) - ]); + return node; + }; + return (file: ts.SourceFile) => file; + } + function replaceNumberWith2(context: ts.TransformationContext) { + function visitor(node: ts.Node): ts.Node { + if (ts.isNumericLiteral(node)) { + return ts.factory.createNumericLiteral("2"); + } + return ts.visitEachChild(node, visitor, context); } + return (file: ts.SourceFile) => ts.visitNode(file, visitor); + } + + function replaceIdentifiersNamedOldNameWithNewName(context: ts.TransformationContext) { + const previousOnSubstituteNode = context.onSubstituteNode; + context.enableSubstitution(ts.SyntaxKind.Identifier); + context.onSubstituteNode = (hint, node) => { + node = previousOnSubstituteNode(hint, node); + if (ts.isIdentifier(node) && node.escapedText === "oldName") { + node = ts.setTextRange(ts.factory.createIdentifier("newName"), node); + } + return node; + }; + return (file: ts.SourceFile) => file; + } + + function replaceIdentifiersNamedOldNameWithNewName2(context: ts.TransformationContext) { + const visitor: ts.Visitor = (node) => { + if (ts.isIdentifier(node) && node.text === "oldName") { + return ts.factory.createIdentifier("newName"); + } + return ts.visitEachChild(node, visitor, context); + }; + return (node: ts.SourceFile) => ts.visitNode(node, visitor); + } + + function createTaggedTemplateLiteral(): ts.Transformer { + return sourceFile => ts.factory.updateSourceFile(sourceFile, [ + ts.factory.createExpressionStatement( + ts.factory.createTaggedTemplateExpression( + ts.factory.createIdentifier("$tpl"), + /*typeArguments*/ undefined, + ts.factory.createNoSubstitutionTemplateLiteral("foo", "foo"))) + ]); + } + + function transformSourceFile(sourceText: string, transformers: ts.TransformerFactory[]) { + const transformed = ts.transform(ts.createSourceFile("source.ts", sourceText, ts.ScriptTarget.ES2015), transformers); + const printer = ts.createPrinter({ newLine: ts.NewLineKind.CarriageReturnLineFeed }, { + onEmitNode: transformed.emitNodeWithNotification, + substituteNode: transformed.substituteNode + }); + const result = printer.printBundle(ts.factory.createBundle(transformed.transformed)); + transformed.dispose(); + return result; + } + + function testBaseline(testName: string, test: () => string) { + it(testName, () => { + Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${testName}.js`, test()); + }); + } - function transformSourceFile(sourceText: string, transformers: TransformerFactory[]) { - const transformed = transform(createSourceFile("source.ts", sourceText, ScriptTarget.ES2015), transformers); - const printer = createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed }, { - onEmitNode: transformed.emitNodeWithNotification, - substituteNode: transformed.substituteNode + function testBaselineAndEvaluate(testName: string, test: () => string, onEvaluate: (exports: any) => void) { + describe(testName, () => { + let sourceText!: string; + before(() => { + sourceText = test(); }); - const result = printer.printBundle(factory.createBundle(transformed.transformed)); - transformed.dispose(); - return result; - } - - function testBaseline(testName: string, test: () => string) { - it(testName, () => { - Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${testName}.js`, test()); + after(() => { + sourceText = undefined!; }); - } - - function testBaselineAndEvaluate(testName: string, test: () => string, onEvaluate: (exports: any) => void) { - describe(testName, () => { - let sourceText!: string; - before(() => { - sourceText = test(); - }); - after(() => { - sourceText = undefined!; - }); - it("compare baselines", () => { - Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${testName}.js`, sourceText); - }); - it("evaluate", () => { - onEvaluate(evaluator.evaluateJavaScript(sourceText)); - }); + it("compare baselines", () => { + Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${testName}.js`, sourceText); + }); + it("evaluate", () => { + onEvaluate(evaluator.evaluateJavaScript(sourceText)); }); - } - - testBaseline("substitution", () => { - return transformSourceFile(`var a = undefined;`, [replaceUndefinedWithVoid0]); }); + } - testBaseline("types", () => { - return transformSourceFile(`let a: () => void`, [ - context => file => visitNode(file, function visitor(node: Node): VisitResult { - return visitEachChild(node, visitor, context); - }) - ]); - }); + testBaseline("substitution", () => { + return transformSourceFile(`var a = undefined;`, [replaceUndefinedWithVoid0]); + }); - testBaseline("transformDefiniteAssignmentAssertions", () => { - return transformSourceFile(`let a!: () => void`, [ - context => file => visitNode(file, function visitor(node: Node): VisitResult { - if (node.kind === SyntaxKind.VoidKeyword) { - return factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword); - } - return visitEachChild(node, visitor, context); - }) - ]); - }); + testBaseline("types", () => { + return transformSourceFile(`let a: () => void`, [ + context => file => ts.visitNode(file, function visitor(node: ts.Node): ts.VisitResult { + return ts.visitEachChild(node, visitor, context); + }) + ]); + }); - testBaseline("fromTranspileModule", () => { - return transpileModule(`var oldName = undefined;`, { - transformers: { - before: [replaceUndefinedWithVoid0], - after: [replaceIdentifiersNamedOldNameWithNewName] - }, - compilerOptions: { - newLine: NewLineKind.CarriageReturnLineFeed + testBaseline("transformDefiniteAssignmentAssertions", () => { + return transformSourceFile(`let a!: () => void`, [ + context => file => ts.visitNode(file, function visitor(node: ts.Node): ts.VisitResult { + if (node.kind === ts.SyntaxKind.VoidKeyword) { + return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword); } - }).outputText; - }); + return ts.visitEachChild(node, visitor, context); + }) + ]); + }); - testBaseline("transformTaggedTemplateLiteral", () => { - return transpileModule("", { - transformers: { - before: [createTaggedTemplateLiteral], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed - } - }).outputText; - }); + testBaseline("fromTranspileModule", () => { + return ts.transpileModule(`var oldName = undefined;`, { + transformers: { + before: [replaceUndefinedWithVoid0], + after: [replaceIdentifiersNamedOldNameWithNewName] + }, + compilerOptions: { + newLine: ts.NewLineKind.CarriageReturnLineFeed + } + }).outputText; + }); - testBaseline("issue27854", () => { - return transpileModule(`oldName<{ a: string; }>\` ... \`;`, { - transformers: { - before: [replaceIdentifiersNamedOldNameWithNewName2] - }, - compilerOptions: { - newLine: NewLineKind.CarriageReturnLineFeed, - target: ScriptTarget.Latest - } - }).outputText; - }); + testBaseline("transformTaggedTemplateLiteral", () => { + return ts.transpileModule("", { + transformers: { + before: [createTaggedTemplateLiteral], + }, + compilerOptions: { + target: ts.ScriptTarget.ES5, + newLine: ts.NewLineKind.CarriageReturnLineFeed + } + }).outputText; + }); - testBaseline("issue44068", () => { - return transformSourceFile(` + testBaseline("issue27854", () => { + return ts.transpileModule(`oldName<{ a: string; }>\` ... \`;`, { + transformers: { + before: [replaceIdentifiersNamedOldNameWithNewName2] + }, + compilerOptions: { + newLine: ts.NewLineKind.CarriageReturnLineFeed, + target: ts.ScriptTarget.Latest + } + }).outputText; + }); + + testBaseline("issue44068", () => { + return transformSourceFile(` const FirstVar = null; const SecondVar = null; `, [ - context => file => { - const firstVarName = (file.statements[0] as VariableStatement) - .declarationList.declarations[0].name as Identifier; - const secondVarName = (file.statements[0] as VariableStatement) - .declarationList.declarations[0].name as Identifier; - - return context.factory.updateSourceFile(file, file.statements.concat([ - context.factory.createExpressionStatement( - context.factory.createArrayLiteralExpression([firstVarName, secondVarName]) - ), - ])); - } - ]); - }); + context => file => { + const firstVarName = (file.statements[0] as ts.VariableStatement) + .declarationList.declarations[0].name as ts.Identifier; + const secondVarName = (file.statements[0] as ts.VariableStatement) + .declarationList.declarations[0].name as ts.Identifier; + + return context.factory.updateSourceFile(file, file.statements.concat([ + context.factory.createExpressionStatement( + context.factory.createArrayLiteralExpression([firstVarName, secondVarName]) + ), + ])); + } + ]); + }); - testBaseline("rewrittenNamespace", () => { - return transpileModule(`namespace Reflect { const x = 1; }`, { - transformers: { - before: [forceNamespaceRewrite], - }, - compilerOptions: { - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + testBaseline("rewrittenNamespace", () => { + return ts.transpileModule(`namespace Reflect { const x = 1; }`, { + transformers: { + before: [forceNamespaceRewrite], + }, + compilerOptions: { + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - testBaseline("rewrittenNamespaceFollowingClass", () => { - return transpileModule(` + testBaseline("rewrittenNamespaceFollowingClass", () => { + return ts.transpileModule(` class C { foo = 10; static bar = 20 } namespace C { export let x = 10; } `, { - transformers: { - before: [forceNamespaceRewrite], - }, - compilerOptions: { - target: ScriptTarget.ESNext, - newLine: NewLineKind.CarriageReturnLineFeed, - useDefineForClassFields: false, - } - }).outputText; - }); + transformers: { + before: [forceNamespaceRewrite], + }, + compilerOptions: { + target: ts.ScriptTarget.ESNext, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + useDefineForClassFields: false, + } + }).outputText; + }); - testBaseline("transformTypesInExportDefault", () => { - return transpileModule(` + testBaseline("transformTypesInExportDefault", () => { + return ts.transpileModule(` export default (foo: string) => { return 1; } `, { - transformers: { - before: [replaceNumberWith2], - }, - compilerOptions: { - target: ScriptTarget.ESNext, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [replaceNumberWith2], + }, + compilerOptions: { + target: ts.ScriptTarget.ESNext, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); + + testBaseline("synthesizedClassAndNamespaceCombination", () => { + return ts.transpileModule("", { + transformers: { + before: [replaceWithClassAndNamespace], + }, + compilerOptions: { + target: ts.ScriptTarget.ESNext, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + + function replaceWithClassAndNamespace() { + return (sourceFile: ts.SourceFile) => { + // TODO(rbuckton): Does this need to be parented? + const result = ts.factory.updateSourceFile( + sourceFile, + ts.factory.createNodeArray([ + ts.factory.createClassDeclaration(/*modifiers*/ undefined, "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, /*members*/ undefined!), // TODO: GH#18217 + ts.factory.createModuleDeclaration(/*modifiers*/ undefined, ts.factory.createIdentifier("Foo"), ts.factory.createModuleBlock([ts.factory.createEmptyStatement()])) + ]) + ); + return result; + }; + } + }); + + function forceNamespaceRewrite(context: ts.TransformationContext) { + return (sourceFile: ts.SourceFile): ts.SourceFile => { + return visitNode(sourceFile); - testBaseline("synthesizedClassAndNamespaceCombination", () => { - return transpileModule("", { - transformers: { - before: [replaceWithClassAndNamespace], - }, - compilerOptions: { - target: ScriptTarget.ESNext, - newLine: NewLineKind.CarriageReturnLineFeed, + function visitNode(node: T): T { + if (node.kind === ts.SyntaxKind.ModuleBlock) { + const block = node as T & ts.ModuleBlock; + const statements = ts.factory.createNodeArray([...block.statements]); + return ts.factory.updateModuleBlock(block, statements) as typeof block; } - }).outputText; - - function replaceWithClassAndNamespace() { - return (sourceFile: SourceFile) => { - // TODO(rbuckton): Does this need to be parented? - const result = factory.updateSourceFile( - sourceFile, - factory.createNodeArray([ - factory.createClassDeclaration(/*modifiers*/ undefined, "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, /*members*/ undefined!), // TODO: GH#18217 - factory.createModuleDeclaration(/*modifiers*/ undefined, factory.createIdentifier("Foo"), factory.createModuleBlock([factory.createEmptyStatement()])) - ]) - ); - return result; - }; + return ts.visitEachChild(node, visitNode, context); } - }); + }; + } + + testBaseline("transformAwayExportStar", () => { + return ts.transpileModule("export * from './helper';", { + transformers: { + before: [expandExportStar], + }, + compilerOptions: { + target: ts.ScriptTarget.ESNext, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; - function forceNamespaceRewrite(context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { + function expandExportStar(context: ts.TransformationContext) { + return (sourceFile: ts.SourceFile): ts.SourceFile => { return visitNode(sourceFile); - function visitNode(node: T): T { - if (node.kind === SyntaxKind.ModuleBlock) { - const block = node as T & ModuleBlock; - const statements = factory.createNodeArray([...block.statements]); - return factory.updateModuleBlock(block, statements) as typeof block; + function visitNode(node: T): T { + if (node.kind === ts.SyntaxKind.ExportDeclaration) { + const ed = node as ts.Node as ts.ExportDeclaration; + const exports = [{ name: "x" }]; + const exportSpecifiers = exports.map(e => ts.factory.createExportSpecifier(/*isTypeOnly*/ false, e.name, e.name)); + const exportClause = ts.factory.createNamedExports(exportSpecifiers); + const newEd = ts.factory.updateExportDeclaration(ed, ed.modifiers, ed.isTypeOnly, exportClause, ed.moduleSpecifier, ed.assertClause); + + return newEd as ts.Node as T; } - return visitEachChild(node, visitNode, context); + return ts.visitEachChild(node, visitNode, context); } }; } + }); - testBaseline("transformAwayExportStar", () => { - return transpileModule("export * from './helper';", { - transformers: { - before: [expandExportStar], - }, - compilerOptions: { - target: ScriptTarget.ESNext, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - - function expandExportStar(context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile); - - function visitNode(node: T): T { - if (node.kind === SyntaxKind.ExportDeclaration) { - const ed = node as Node as ExportDeclaration; - const exports = [{ name: "x" }]; - const exportSpecifiers = exports.map(e => factory.createExportSpecifier(/*isTypeOnly*/ false, e.name, e.name)); - const exportClause = factory.createNamedExports(exportSpecifiers); - const newEd = factory.updateExportDeclaration(ed, ed.modifiers, ed.isTypeOnly, exportClause, ed.moduleSpecifier, ed.assertClause); - - return newEd as Node as T; - } - return visitEachChild(node, visitNode, context); - } - }; + // https://github.com/Microsoft/TypeScript/issues/19618 + testBaseline("transformAddImportStar", () => { + return ts.transpileModule("", { + transformers: { + before: [transformAddImportStar], + }, + compilerOptions: { + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.System, + newLine: ts.NewLineKind.CarriageReturnLineFeed, } - }); - - // https://github.com/Microsoft/TypeScript/issues/19618 - testBaseline("transformAddImportStar", () => { - return transpileModule("", { - transformers: { - before: [transformAddImportStar], - }, - compilerOptions: { - target: ScriptTarget.ES5, - module: ModuleKind.System, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; + }).outputText; - function transformAddImportStar(_context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile); - }; - function visitNode(sf: SourceFile) { - // produce `import * as i0 from './comp'; - const importStar = factory.createImportDeclaration( - /*modifiers*/ undefined, - /*importClause*/ factory.createImportClause( - /*isTypeOnly*/ false, - /*name*/ undefined, - factory.createNamespaceImport(factory.createIdentifier("i0")) - ), - /*moduleSpecifier*/ factory.createStringLiteral("./comp1"), - /*assertClause*/ undefined); - return factory.updateSourceFile(sf, [importStar]); - } + function transformAddImportStar(_context: ts.TransformationContext) { + return (sourceFile: ts.SourceFile): ts.SourceFile => { + return visitNode(sourceFile); + }; + function visitNode(sf: ts.SourceFile) { + // produce `import * as i0 from './comp'; + const importStar = ts.factory.createImportDeclaration( + /*modifiers*/ undefined, + /*importClause*/ ts.factory.createImportClause( + /*isTypeOnly*/ false, + /*name*/ undefined, + ts.factory.createNamespaceImport(ts.factory.createIdentifier("i0")) + ), + /*moduleSpecifier*/ ts.factory.createStringLiteral("./comp1"), + /*assertClause*/ undefined); + return ts.factory.updateSourceFile(sf, [importStar]); } - }); + } + }); - // https://github.com/Microsoft/TypeScript/issues/17384 - testBaseline("transformAddDecoratedNode", () => { - return transpileModule("", { - transformers: { - before: [transformAddDecoratedNode], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; + // https://github.com/Microsoft/TypeScript/issues/17384 + testBaseline("transformAddDecoratedNode", () => { + return ts.transpileModule("", { + transformers: { + before: [transformAddDecoratedNode], + }, + compilerOptions: { + target: ts.ScriptTarget.ES5, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; - function transformAddDecoratedNode(_context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile); - }; - function visitNode(sf: SourceFile) { - // produce `class Foo { @Bar baz() {} }`; - const classDecl = factory.createClassDeclaration(/*modifiers*/ undefined, "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ - factory.createMethodDeclaration([factory.createDecorator(factory.createIdentifier("Bar"))], /**/ undefined, "baz", /**/ undefined, /**/ undefined, [], /**/ undefined, factory.createBlock([])) - ]); - return factory.updateSourceFile(sf, [classDecl]); - } + function transformAddDecoratedNode(_context: ts.TransformationContext) { + return (sourceFile: ts.SourceFile): ts.SourceFile => { + return visitNode(sourceFile); + }; + function visitNode(sf: ts.SourceFile) { + // produce `class Foo { @Bar baz() {} }`; + const classDecl = ts.factory.createClassDeclaration(/*modifiers*/ undefined, "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ + ts.factory.createMethodDeclaration([ts.factory.createDecorator(ts.factory.createIdentifier("Bar"))], /**/ undefined, "baz", /**/ undefined, /**/ undefined, [], /**/ undefined, ts.factory.createBlock([])) + ]); + return ts.factory.updateSourceFile(sf, [classDecl]); } - }); + } + }); - testBaseline("transformDeclarationFile", () => { - return baselineDeclarationTransform(`var oldName = undefined;`, { - transformers: { - afterDeclarations: [replaceIdentifiersNamedOldNameWithNewName] - }, - compilerOptions: { - newLine: NewLineKind.CarriageReturnLineFeed, - declaration: true - } - }); + testBaseline("transformDeclarationFile", () => { + return baselineDeclarationTransform(`var oldName = undefined;`, { + transformers: { + afterDeclarations: [replaceIdentifiersNamedOldNameWithNewName] + }, + compilerOptions: { + newLine: ts.NewLineKind.CarriageReturnLineFeed, + declaration: true + } }); + }); - // https://github.com/microsoft/TypeScript/issues/33295 - testBaseline("transformParameterProperty", () => { - return transpileModule("", { - transformers: { - before: [transformAddParameterProperty], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - - function transformAddParameterProperty(_context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile); - }; - function visitNode(sf: SourceFile) { - // produce `class Foo { constructor(@Dec private x) {} }`; - // The decorator is required to trigger ts.ts transformations. - const classDecl = factory.createClassDeclaration([], "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ - factory.createConstructorDeclaration(/*modifiers*/ undefined, [ - factory.createParameterDeclaration([factory.createDecorator(factory.createIdentifier("Dec")), factory.createModifier(SyntaxKind.PrivateKeyword)], /*dotDotDotToken*/ undefined, "x")], factory.createBlock([])) - ]); - return factory.updateSourceFile(sf, [classDecl]); - } + // https://github.com/microsoft/TypeScript/issues/33295 + testBaseline("transformParameterProperty", () => { + return ts.transpileModule("", { + transformers: { + before: [transformAddParameterProperty], + }, + compilerOptions: { + target: ts.ScriptTarget.ES5, + newLine: ts.NewLineKind.CarriageReturnLineFeed, } - }); + }).outputText; - function baselineDeclarationTransform(text: string, opts: TranspileOptions) { - const fs = vfs.createFromFileSystem(Harness.IO, /*caseSensitive*/ true, { documents: [new documents.TextDocument("/.src/index.ts", text)] }); - const host = new fakes.CompilerHost(fs, opts.compilerOptions); - const program = createProgram(["/.src/index.ts"], opts.compilerOptions!, host); - program.emit(program.getSourceFile("/.src/index.ts"), (p, s, bom) => host.writeFile(p, s, bom), /*cancellationToken*/ undefined, /*onlyDts*/ true, opts.transformers); - return fs.readFileSync("/.src/index.d.ts").toString(); + function transformAddParameterProperty(_context: ts.TransformationContext) { + return (sourceFile: ts.SourceFile): ts.SourceFile => { + return visitNode(sourceFile); + }; + function visitNode(sf: ts.SourceFile) { + // produce `class Foo { constructor(@Dec private x) {} }`; + // The decorator is required to trigger ts.ts transformations. + const classDecl = ts.factory.createClassDeclaration([], "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ + ts.factory.createConstructorDeclaration(/*modifiers*/ undefined, [ + ts.factory.createParameterDeclaration([ts.factory.createDecorator(ts.factory.createIdentifier("Dec")), ts.factory.createModifier(ts.SyntaxKind.PrivateKeyword)], /*dotDotDotToken*/ undefined, "x")], ts.factory.createBlock([])) + ]); + return ts.factory.updateSourceFile(sf, [classDecl]); + } } + }); - function addSyntheticComment(nodeFilter: (node: Node) => boolean) { - return (context: TransformationContext) => { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile, rootTransform, isSourceFile); - }; - function rootTransform(node: T): VisitResult { - if (nodeFilter(node)) { - setEmitFlags(node, EmitFlags.NoLeadingComments); - setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "comment", pos: -1, end: -1, hasTrailingNewLine: true }]); - } - return visitEachChild(node, rootTransform, context); - } + function baselineDeclarationTransform(text: string, opts: ts.TranspileOptions) { + const fs = vfs.createFromFileSystem(Harness.IO, /*caseSensitive*/ true, { documents: [new documents.TextDocument("/.src/index.ts", text)] }); + const host = new fakes.CompilerHost(fs, opts.compilerOptions); + const program = ts.createProgram(["/.src/index.ts"], opts.compilerOptions!, host); + program.emit(program.getSourceFile("/.src/index.ts"), (p, s, bom) => host.writeFile(p, s, bom), /*cancellationToken*/ undefined, /*onlyDts*/ true, opts.transformers); + return fs.readFileSync("/.src/index.d.ts").toString(); + } + + function addSyntheticComment(nodeFilter: (node: ts.Node) => boolean) { + return (context: ts.TransformationContext) => { + return (sourceFile: ts.SourceFile): ts.SourceFile => { + return ts.visitNode(sourceFile, rootTransform, ts.isSourceFile); }; - } + function rootTransform(node: T): ts.VisitResult { + if (nodeFilter(node)) { + ts.setEmitFlags(node, ts.EmitFlags.NoLeadingComments); + ts.setSyntheticLeadingComments(node, [{ kind: ts.SyntaxKind.MultiLineCommentTrivia, text: "comment", pos: -1, end: -1, hasTrailingNewLine: true }]); + } + return ts.visitEachChild(node, rootTransform, context); + } + }; + } - // https://github.com/Microsoft/TypeScript/issues/24096 - testBaseline("transformAddCommentToArrowReturnValue", () => { - return transpileModule(`const foo = () => + // https://github.com/Microsoft/TypeScript/issues/24096 + testBaseline("transformAddCommentToArrowReturnValue", () => { + return ts.transpileModule(`const foo = () => void 0 `, { - transformers: { - before: [addSyntheticComment(isVoidExpression)], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [addSyntheticComment(ts.isVoidExpression)], + }, + compilerOptions: { + target: ts.ScriptTarget.ES5, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - // https://github.com/Microsoft/TypeScript/issues/17594 - testBaseline("transformAddCommentToExportedVar", () => { - return transpileModule(`export const exportedDirectly = 1; + // https://github.com/Microsoft/TypeScript/issues/17594 + testBaseline("transformAddCommentToExportedVar", () => { + return ts.transpileModule(`export const exportedDirectly = 1; const exportedSeparately = 2; export {exportedSeparately}; `, { - transformers: { - before: [addSyntheticComment(isVariableStatement)], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [addSyntheticComment(ts.isVariableStatement)], + }, + compilerOptions: { + target: ts.ScriptTarget.ES5, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - // https://github.com/Microsoft/TypeScript/issues/17594 - testBaseline("transformAddCommentToImport", () => { - return transpileModule(` + // https://github.com/Microsoft/TypeScript/issues/17594 + testBaseline("transformAddCommentToImport", () => { + return ts.transpileModule(` // Previous comment on import. import {Value} from 'somewhere'; import * as X from 'somewhere'; @@ -451,19 +457,19 @@ export { /* specifier comment */ X, Y} from 'somewhere'; export * from 'somewhere'; export {Value}; `, { - transformers: { - before: [addSyntheticComment(n => isImportDeclaration(n) || isExportDeclaration(n) || isImportSpecifier(n) || isExportSpecifier(n))], - }, - compilerOptions: { - target: ScriptTarget.ES5, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [addSyntheticComment(n => ts.isImportDeclaration(n) || ts.isExportDeclaration(n) || ts.isImportSpecifier(n) || ts.isExportSpecifier(n))], + }, + compilerOptions: { + target: ts.ScriptTarget.ES5, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - // https://github.com/Microsoft/TypeScript/issues/17594 - testBaseline("transformAddCommentToProperties", () => { - return transpileModule(` + // https://github.com/Microsoft/TypeScript/issues/17594 + testBaseline("transformAddCommentToProperties", () => { + return ts.transpileModule(` // class comment. class Clazz { // original comment 1. @@ -474,18 +480,18 @@ class Clazz { constructor(readonly field = 1) {} } `, { - transformers: { - before: [addSyntheticComment(n => isPropertyDeclaration(n) || isParameterPropertyDeclaration(n, n.parent) || isClassDeclaration(n) || isConstructorDeclaration(n))], - }, - compilerOptions: { - target: ScriptTarget.ES2015, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [addSyntheticComment(n => ts.isPropertyDeclaration(n) || ts.isParameterPropertyDeclaration(n, n.parent) || ts.isClassDeclaration(n) || ts.isConstructorDeclaration(n))], + }, + compilerOptions: { + target: ts.ScriptTarget.ES2015, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - testBaseline("transformAddCommentToNamespace", () => { - return transpileModule(` + testBaseline("transformAddCommentToNamespace", () => { + return ts.transpileModule(` // namespace comment. namespace Foo { export const x = 1; @@ -495,169 +501,168 @@ namespace Foo { export const y = 1; } `, { - transformers: { - before: [addSyntheticComment(n => isModuleDeclaration(n))], - }, - compilerOptions: { - target: ScriptTarget.ES2015, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [addSyntheticComment(n => ts.isModuleDeclaration(n))], + }, + compilerOptions: { + target: ts.ScriptTarget.ES2015, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - testBaseline("transformUpdateModuleMember", () => { - return transpileModule(` + testBaseline("transformUpdateModuleMember", () => { + return ts.transpileModule(` module MyModule { const myVariable = 1; function foo(param: string) {} } `, { - transformers: { - before: [renameVariable], - }, - compilerOptions: { - target: ScriptTarget.ES2015, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - - function renameVariable(context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile, rootTransform, isSourceFile); - }; - function rootTransform(node: T): Node { - if (isVariableDeclaration(node)) { - return factory.updateVariableDeclaration(node, factory.createIdentifier("newName"), /*exclamationToken*/ undefined, /*type*/ undefined, node.initializer); - } - return visitEachChild(node, rootTransform, context); - } + transformers: { + before: [renameVariable], + }, + compilerOptions: { + target: ts.ScriptTarget.ES2015, + newLine: ts.NewLineKind.CarriageReturnLineFeed, } - }); + }).outputText; - // https://github.com/Microsoft/TypeScript/issues/24709 - testBaseline("issue24709", () => { - const fs = vfs.createFromFileSystem(Harness.IO, /*caseSensitive*/ true); - const transformed = transform(createSourceFile("source.ts", "class X { echo(x: string) { return x; } }", ScriptTarget.ES3), [transformSourceFile]); - const transformedSourceFile = transformed.transformed[0]; - transformed.dispose(); - const host = new fakes.CompilerHost(fs); - host.getSourceFile = () => transformedSourceFile; - const program = createProgram(["source.ts"], { - target: ScriptTarget.ES3, - module: ModuleKind.None, - noLib: true - }, host); - program.emit(transformedSourceFile, (_p, s, b) => host.writeFile("source.js", s, b)); - return host.readFile("source.js")!.toString(); - - function transformSourceFile(context: TransformationContext) { - const visitor: Visitor = (node) => { - if (isMethodDeclaration(node)) { - return factory.updateMethodDeclaration( - node, - node.modifiers, - node.asteriskToken, - factory.createIdentifier("foobar"), - node.questionToken, - node.typeParameters, - node.parameters, - node.type, - node.body, - ); - } - return visitEachChild(node, visitor, context); + function renameVariable(context: ts.TransformationContext) { + return (sourceFile: ts.SourceFile): ts.SourceFile => { + return ts.visitNode(sourceFile, rootTransform, ts.isSourceFile); }; - return (node: SourceFile) => visitNode(node, visitor); - } - - }); - - testBaselineAndEvaluate("templateSpans", () => { - return transpileModule("const x = String.raw`\n\nhello`; exports.stringLength = x.trim().length;", { - compilerOptions: { - target: ScriptTarget.ESNext, - newLine: NewLineKind.CarriageReturnLineFeed, - }, - transformers: { - before: [transformSourceFile] + function rootTransform(node: T): ts.Node { + if (ts.isVariableDeclaration(node)) { + return ts.factory.updateVariableDeclaration(node, ts.factory.createIdentifier("newName"), /*exclamationToken*/ undefined, /*type*/ undefined, node.initializer); + } + return ts.visitEachChild(node, rootTransform, context); } - }).outputText; + } + }); - function transformSourceFile(context: TransformationContext): Transformer { - function visitor(node: Node): VisitResult { - if (isNoSubstitutionTemplateLiteral(node)) { - return factory.createNoSubstitutionTemplateLiteral(node.text, node.rawText); - } - else { - return visitEachChild(node, visitor, context); - } + // https://github.com/Microsoft/TypeScript/issues/24709 + testBaseline("issue24709", () => { + const fs = vfs.createFromFileSystem(Harness.IO, /*caseSensitive*/ true); + const transformed = ts.transform(ts.createSourceFile("source.ts", "class X { echo(x: string) { return x; } }", ts.ScriptTarget.ES3), [transformSourceFile]); + const transformedSourceFile = transformed.transformed[0]; + transformed.dispose(); + const host = new fakes.CompilerHost(fs); + host.getSourceFile = () => transformedSourceFile; + const program = ts.createProgram(["source.ts"], { + target: ts.ScriptTarget.ES3, + module: ts.ModuleKind.None, + noLib: true + }, host); + program.emit(transformedSourceFile, (_p, s, b) => host.writeFile("source.js", s, b)); + return host.readFile("source.js")!.toString(); + + function transformSourceFile(context: ts.TransformationContext) { + const visitor: ts.Visitor = (node) => { + if (ts.isMethodDeclaration(node)) { + return ts.factory.updateMethodDeclaration( + node, + node.modifiers, + node.asteriskToken, + ts.factory.createIdentifier("foobar"), + node.questionToken, + node.typeParameters, + node.parameters, + node.type, + node.body, + ); } - return sourceFile => visitNode(sourceFile, visitor, isSourceFile); + return ts.visitEachChild(node, visitor, context); + }; + return (node: ts.SourceFile) => ts.visitNode(node, visitor); + } + + }); + + testBaselineAndEvaluate("templateSpans", () => { + return ts.transpileModule("const x = String.raw`\n\nhello`; exports.stringLength = x.trim().length;", { + compilerOptions: { + target: ts.ScriptTarget.ESNext, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + }, + transformers: { + before: [transformSourceFile] } - }, exports => { - assert.equal(exports.stringLength, 5); - }); + }).outputText; - function addStaticFieldWithComment(context: TransformationContext) { - return (sourceFile: SourceFile): SourceFile => { - return visitNode(sourceFile, rootTransform, isSourceFile); - }; - function rootTransform(node: T): Node { - if (isClassLike(node)) { - const newMembers = [factory.createPropertyDeclaration([factory.createModifier(SyntaxKind.StaticKeyword)], "newField", /* questionOrExclamationToken */ undefined, /* type */ undefined, factory.createStringLiteral("x"))]; - setSyntheticLeadingComments(newMembers[0], [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "comment", pos: -1, end: -1, hasTrailingNewLine: true }]); - return isClassDeclaration(node) ? - factory.updateClassDeclaration( - node, - node.modifiers, - node.name, - node.typeParameters, - node.heritageClauses, - newMembers) : - factory.updateClassExpression( - node, - node.modifiers, - node.name, - node.typeParameters, - node.heritageClauses, - newMembers); + function transformSourceFile(context: ts.TransformationContext): ts.Transformer { + function visitor(node: ts.Node): ts.VisitResult { + if (ts.isNoSubstitutionTemplateLiteral(node)) { + return ts.factory.createNoSubstitutionTemplateLiteral(node.text, node.rawText); } - return visitEachChild(node, rootTransform, context); + else { + return ts.visitEachChild(node, visitor, context); + } + } + return sourceFile => ts.visitNode(sourceFile, visitor, ts.isSourceFile); + } + }, exports => { + assert.equal(exports.stringLength, 5); + }); + + function addStaticFieldWithComment(context: ts.TransformationContext) { + return (sourceFile: ts.SourceFile): ts.SourceFile => { + return ts.visitNode(sourceFile, rootTransform, ts.isSourceFile); + }; + function rootTransform(node: T): ts.Node { + if (ts.isClassLike(node)) { + const newMembers = [ts.factory.createPropertyDeclaration([ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)], "newField", /* questionOrExclamationToken */ undefined, /* type */ undefined, ts.factory.createStringLiteral("x"))]; + ts.setSyntheticLeadingComments(newMembers[0], [{ kind: ts.SyntaxKind.MultiLineCommentTrivia, text: "comment", pos: -1, end: -1, hasTrailingNewLine: true }]); + return ts.isClassDeclaration(node) ? + ts.factory.updateClassDeclaration( + node, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + newMembers) : + ts.factory.updateClassExpression( + node, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + newMembers); } + return ts.visitEachChild(node, rootTransform, context); } + } - testBaseline("transformSyntheticCommentOnStaticFieldInClassDeclaration", () => { - return transpileModule(` + testBaseline("transformSyntheticCommentOnStaticFieldInClassDeclaration", () => { + return ts.transpileModule(` declare const Decorator: any; @Decorator class MyClass { } `, { - transformers: { - before: [addStaticFieldWithComment], - }, - compilerOptions: { - target: ScriptTarget.ES2015, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); + transformers: { + before: [addStaticFieldWithComment], + }, + compilerOptions: { + target: ts.ScriptTarget.ES2015, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; + }); - testBaseline("transformSyntheticCommentOnStaticFieldInClassExpression", () => { - return transpileModule(` + testBaseline("transformSyntheticCommentOnStaticFieldInClassExpression", () => { + return ts.transpileModule(` const MyClass = class { }; `, { - transformers: { - before: [addStaticFieldWithComment], - }, - compilerOptions: { - target: ScriptTarget.ES2015, - newLine: NewLineKind.CarriageReturnLineFeed, - } - }).outputText; - }); - + transformers: { + before: [addStaticFieldWithComment], + }, + compilerOptions: { + target: ts.ScriptTarget.ES2015, + newLine: ts.NewLineKind.CarriageReturnLineFeed, + } + }).outputText; }); -} + +}); diff --git a/src/testRunner/unittests/tsbuild/amdModulesWithOut.ts b/src/testRunner/unittests/tsbuild/amdModulesWithOut.ts index 3e3a81491b127..d137e70ecc46a 100644 --- a/src/testRunner/unittests/tsbuild/amdModulesWithOut.ts +++ b/src/testRunner/unittests/tsbuild/amdModulesWithOut.ts @@ -1,112 +1,114 @@ -namespace ts { - describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => { - let outFileFs: vfs.FileSystem; - before(() => { - outFileFs = loadProjectFromDisk("tests/projects/amdModulesWithOut"); - }); - after(() => { - outFileFs = undefined!; - }); +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; + +describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => { + let outFileFs: vfs.FileSystem; + before(() => { + outFileFs = ts.loadProjectFromDisk("tests/projects/amdModulesWithOut"); + }); + after(() => { + outFileFs = undefined!; + }); - interface VerifyOutFileScenarioInput { - subScenario: string; - modifyFs?: (fs: vfs.FileSystem) => void; - modifyAgainFs?: (fs: vfs.FileSystem) => void; - } + interface VerifyOutFileScenarioInput { + subScenario: string; + modifyFs?: (fs: vfs.FileSystem) => void; + modifyAgainFs?: (fs: vfs.FileSystem) => void; + } - function verifyOutFileScenario({ + function verifyOutFileScenario({ + subScenario, + modifyFs, + modifyAgainFs + }: VerifyOutFileScenarioInput) { + ts.verifyTscWithEdits({ + scenario: "amdModulesWithOut", subScenario, + fs: () => outFileFs, + commandLineArgs: ["--b", "/src/app", "--verbose"], + baselineSourceMap: true, modifyFs, - modifyAgainFs - }: VerifyOutFileScenarioInput) { - verifyTscWithEdits({ - scenario: "amdModulesWithOut", - subScenario, - fs: () => outFileFs, - commandLineArgs: ["--b", "/src/app", "--verbose"], - baselineSourceMap: true, - modifyFs, - edits: [ - { - subScenario: "incremental-declaration-doesnt-change", - modifyFs: fs => appendText(fs, "/src/lib/file1.ts", "console.log(x);") - }, - ...(modifyAgainFs ? [{ - subScenario: "incremental-headers-change-without-dts-changes", - modifyFs: modifyAgainFs - }] : emptyArray), - ] - }); - } + edits: [ + { + subScenario: "incremental-declaration-doesnt-change", + modifyFs: fs => ts.appendText(fs, "/src/lib/file1.ts", "console.log(x);") + }, + ...(modifyAgainFs ? [{ + subScenario: "incremental-headers-change-without-dts-changes", + modifyFs: modifyAgainFs + }] : ts.emptyArray), + ] + }); + } - describe("Prepend output with .tsbuildinfo", () => { - verifyOutFileScenario({ - subScenario: "modules and globals mixed in amd", - }); + describe("Prepend output with .tsbuildinfo", () => { + verifyOutFileScenario({ + subScenario: "modules and globals mixed in amd", + }); - // Prologues - describe("Prologues", () => { - verifyOutFileScenario({ - subScenario: "multiple prologues in all projects", - modifyFs: fs => { - enableStrict(fs, "/src/lib/tsconfig.json"); - addTestPrologue(fs, "/src/lib/file0.ts", `"myPrologue"`); - addTestPrologue(fs, "/src/lib/file2.ts", `"myPrologueFile"`); - addTestPrologue(fs, "/src/lib/global.ts", `"myPrologue3"`); - enableStrict(fs, "/src/app/tsconfig.json"); - addTestPrologue(fs, "/src/app/file3.ts", `"myPrologue"`); - addTestPrologue(fs, "/src/app/file4.ts", `"myPrologue2";`); - }, - modifyAgainFs: fs => addTestPrologue(fs, "/src/lib/file1.ts", `"myPrologue5"`) - }); + // Prologues + describe("Prologues", () => { + verifyOutFileScenario({ + subScenario: "multiple prologues in all projects", + modifyFs: fs => { + ts.enableStrict(fs, "/src/lib/tsconfig.json"); + ts.addTestPrologue(fs, "/src/lib/file0.ts", `"myPrologue"`); + ts.addTestPrologue(fs, "/src/lib/file2.ts", `"myPrologueFile"`); + ts.addTestPrologue(fs, "/src/lib/global.ts", `"myPrologue3"`); + ts.enableStrict(fs, "/src/app/tsconfig.json"); + ts.addTestPrologue(fs, "/src/app/file3.ts", `"myPrologue"`); + ts.addTestPrologue(fs, "/src/app/file4.ts", `"myPrologue2";`); + }, + modifyAgainFs: fs => ts.addTestPrologue(fs, "/src/lib/file1.ts", `"myPrologue5"`) }); + }); - // Shebang - describe("Shebang", () => { - // changes declaration because its emitted in .d.ts file - verifyOutFileScenario({ - subScenario: "shebang in all projects", - modifyFs: fs => { - addShebang(fs, "lib", "file0"); - addShebang(fs, "lib", "file1"); - addShebang(fs, "app", "file3"); - }, - }); + // Shebang + describe("Shebang", () => { + // changes declaration because its emitted in .d.ts file + verifyOutFileScenario({ + subScenario: "shebang in all projects", + modifyFs: fs => { + ts.addShebang(fs, "lib", "file0"); + ts.addShebang(fs, "lib", "file1"); + ts.addShebang(fs, "app", "file3"); + }, }); + }); - // emitHelpers - describe("emitHelpers", () => { - verifyOutFileScenario({ - subScenario: "multiple emitHelpers in all projects", - modifyFs: fs => { - addSpread(fs, "lib", "file0"); - addRest(fs, "lib", "file1"); - addRest(fs, "app", "file3"); - addSpread(fs, "app", "file4"); - }, - modifyAgainFs: fs => removeRest(fs, "lib", "file1") - }); + // emitHelpers + describe("emitHelpers", () => { + verifyOutFileScenario({ + subScenario: "multiple emitHelpers in all projects", + modifyFs: fs => { + ts.addSpread(fs, "lib", "file0"); + ts.addRest(fs, "lib", "file1"); + ts.addRest(fs, "app", "file3"); + ts.addSpread(fs, "app", "file4"); + }, + modifyAgainFs: fs => ts.removeRest(fs, "lib", "file1") }); + }); - // triple slash refs - describe("triple slash refs", () => { - // changes declaration because its emitted in .d.ts file - verifyOutFileScenario({ - subScenario: "triple slash refs in all projects", - modifyFs: fs => { - addTripleSlashRef(fs, "lib", "file0"); - addTripleSlashRef(fs, "app", "file4"); - } - }); + // triple slash refs + describe("triple slash refs", () => { + // changes declaration because its emitted in .d.ts file + verifyOutFileScenario({ + subScenario: "triple slash refs in all projects", + modifyFs: fs => { + ts.addTripleSlashRef(fs, "lib", "file0"); + ts.addTripleSlashRef(fs, "app", "file4"); + } }); + }); - describe("stripInternal", () => { - function stripInternalScenario(fs: vfs.FileSystem) { - const internal = "/*@internal*/"; - replaceText(fs, "/src/app/tsconfig.json", `"composite": true,`, `"composite": true, + describe("stripInternal", () => { + function stripInternalScenario(fs: vfs.FileSystem) { + const internal = "/*@internal*/"; + ts.replaceText(fs, "/src/app/tsconfig.json", `"composite": true,`, `"composite": true, "stripInternal": true,`); - replaceText(fs, "/src/lib/file0.ts", "const", `${internal} const`); - appendText(fs, "/src/lib/file1.ts", ` + ts.replaceText(fs, "/src/lib/file0.ts", "const", `${internal} const`); + ts.appendText(fs, "/src/lib/file1.ts", ` export class normalC { ${internal} constructor() { } ${internal} prop: string; @@ -132,33 +134,32 @@ ${internal} export import internalImport = internalNamespace.someClass; ${internal} export type internalType = internalC; ${internal} export const internalConst = 10; ${internal} export enum internalEnum { a, b, c }`); - } + } - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "stripInternal", - modifyFs: stripInternalScenario, - modifyAgainFs: fs => replaceText(fs, "/src/lib/file1.ts", `export const`, `/*@internal*/ export const`), - }); + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "stripInternal", + modifyFs: stripInternalScenario, + modifyAgainFs: fs => ts.replaceText(fs, "/src/lib/file1.ts", `export const`, `/*@internal*/ export const`), }); + }); - describe("when the module resolution finds original source file", () => { - function modifyFs(fs: vfs.FileSystem) { - // Make lib to output to parent dir - replaceText(fs, "/src/lib/tsconfig.json", `"outFile": "module.js"`, `"outFile": "../module.js", "rootDir": "../"`); - // Change reference to file1 module to resolve to lib/file1 - replaceText(fs, "/src/app/file3.ts", "file1", "lib/file1"); - } + describe("when the module resolution finds original source file", () => { + function modifyFs(fs: vfs.FileSystem) { + // Make lib to output to parent dir + ts.replaceText(fs, "/src/lib/tsconfig.json", `"outFile": "module.js"`, `"outFile": "../module.js", "rootDir": "../"`); + // Change reference to file1 module to resolve to lib/file1 + ts.replaceText(fs, "/src/app/file3.ts", "file1", "lib/file1"); + } - verifyTsc({ - scenario: "amdModulesWithOut", - subScenario: "when the module resolution finds original source file", - fs: () => outFileFs, - commandLineArgs: ["-b", "/src/app", "--verbose"], - modifyFs, - baselineSourceMap: true, - }); + ts.verifyTsc({ + scenario: "amdModulesWithOut", + subScenario: "when the module resolution finds original source file", + fs: () => outFileFs, + commandLineArgs: ["-b", "/src/app", "--verbose"], + modifyFs, + baselineSourceMap: true, }); }); }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/clean.ts b/src/testRunner/unittests/tsbuild/clean.ts index 90bb298529028..c3182026a1965 100644 --- a/src/testRunner/unittests/tsbuild/clean.ts +++ b/src/testRunner/unittests/tsbuild/clean.ts @@ -1,16 +1,16 @@ -namespace ts { - describe("unittests:: tsbuild - clean", () => { - verifyTsc({ - scenario: "clean", - subScenario: `file name and output name clashing`, - commandLineArgs: ["--b", "/src/tsconfig.json", "-clean"], - fs: () => loadProjectFromFiles({ - "/src/index.js": "", - "/src/bar.ts": "", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { allowJs: true }, - }), +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsbuild - clean", () => { + ts.verifyTsc({ + scenario: "clean", + subScenario: `file name and output name clashing`, + commandLineArgs: ["--b", "/src/tsconfig.json", "-clean"], + fs: () => ts.loadProjectFromFiles({ + "/src/index.js": "", + "/src/bar.ts": "", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { allowJs: true }, }), - }); + }), }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuild/commandLine.ts b/src/testRunner/unittests/tsbuild/commandLine.ts index aea4c74b431e1..4fe827ff86db0 100644 --- a/src/testRunner/unittests/tsbuild/commandLine.ts +++ b/src/testRunner/unittests/tsbuild/commandLine.ts @@ -1,427 +1,427 @@ -namespace ts { - describe("unittests:: tsbuild:: commandLine::", () => { - describe("different options::", () => { - function withOptionChange(subScenario: string, ...options: readonly string[]): TestTscEdit { - return { - subScenario, - modifyFs: noop, - commandLineArgs: ["--b", "/src/project", "--verbose", ...options] - }; - } - function noChangeWithSubscenario(subScenario: string): TestTscEdit { - return { ...noChangeRun, subScenario }; - } - function withOptionChangeAndDiscrepancyExplanation(subScenario: string, option: string): TestTscEdit { - return { - ...withOptionChange(subScenario, option), - discrepancyExplanation: () => [ - `Clean build tsbuildinfo will have compilerOptions with composite and ${option.replace(/\-/g, "")}`, - `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option composite only`, - ] - }; - } - function withEmitDeclarationOnlyChangeAndDiscrepancyExplanation(subScenario: string): TestTscEdit { - const edit = withOptionChangeAndDiscrepancyExplanation(subScenario, "--emitDeclarationOnly"); - const discrepancyExplanation = edit.discrepancyExplanation!; - edit.discrepancyExplanation = () => [ - ...discrepancyExplanation(), - `Clean build info does not have js section because its fresh build`, - `Incremental build info has js section from old build` - ]; - return edit; - } - function withOptionChangeAndExportExplanation(subScenario: string, ...options: readonly string[]): TestTscEdit { - return { - ...withOptionChange(subScenario, ...options), - discrepancyExplanation: noChangeWithExportsDiscrepancyRun.discrepancyExplanation, - }; - } - function nochangeWithIncrementalDeclarationFromBeforeExplaination(): TestTscEdit { - return { - ...noChangeRun, - discrepancyExplanation: () => [ - `Clean build tsbuildinfo will have compilerOptions {}`, - `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option declaration and declarationMap`, - ], - }; - } - function nochangeWithIncrementalOutDeclarationFromBeforeExplaination(): TestTscEdit { - const edit = nochangeWithIncrementalDeclarationFromBeforeExplaination(); - const discrepancyExplanation = edit.discrepancyExplanation!; - edit.discrepancyExplanation = () => [ - ...discrepancyExplanation(), - `Clean build does not have dts bundle section`, - `Incremental build contains the dts build section from before`, - ]; - return edit; - } - function localChange(): TestTscEdit { - return { - subScenario: "local change", - modifyFs: fs => replaceText(fs, "/src/project/a.ts", "Local = 1", "Local = 10"), - }; - } - function fs(options: CompilerOptions) { - return loadProjectFromFiles({ - "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: compilerOptionsToConfigJson(options) }), - "/src/project/a.ts": `export const a = 10;const aLocal = 10;`, - "/src/project/b.ts": `export const b = 10;const bLocal = 10;`, - "/src/project/c.ts": `import { a } from "./a";export const c = a;`, - "/src/project/d.ts": `import { b } from "./b";export const d = b;`, - }); - } - verifyTscWithEdits({ - scenario: "commandLine", - subScenario: "different options", - fs: () => fs({ composite: true }), - commandLineArgs: ["--b", "/src/project", "--verbose"], - edits: [ - withOptionChange("with sourceMap", "--sourceMap"), - noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), - withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), - noChangeRun, - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"), - withOptionChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything", "--emitDeclarationOnly"), - noChangeRun, - localChange(), - withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), - withOptionChange("with inlineSourceMap", "--inlineSourceMap"), - withOptionChange("with sourceMap", "--sourceMap"), - ], - baselinePrograms: true, - }); - verifyTscWithEdits({ - scenario: "commandLine", - subScenario: "different options with outFile", - fs: () => fs({ composite: true, outFile: "../outFile.js", module: ModuleKind.AMD }), - commandLineArgs: ["--b", "/src/project", "--verbose"], - edits: [ - withOptionChange("with sourceMap", "--sourceMap"), - noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), - withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), - noChangeRun, - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"), - withEmitDeclarationOnlyChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything"), - noChangeRun, - localChange(), - withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), - withOptionChange("with inlineSourceMap", "--inlineSourceMap"), - withOptionChange("with sourceMap", "--sourceMap"), - ], - baselinePrograms: true, - }); - verifyTscWithEdits({ - scenario: "commandLine", - subScenario: "different options with incremental", - fs: () => fs({ incremental: true }), - commandLineArgs: ["--b", "/src/project", "--verbose"], - edits: [ - withOptionChangeAndExportExplanation("with sourceMap", "--sourceMap"), - withOptionChangeAndExportExplanation("should re-emit only js so they dont contain sourcemap"), - withOptionChange("with declaration, emit Dts and should not emit js", "--declaration"), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - nochangeWithIncrementalDeclarationFromBeforeExplaination(), - localChange(), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - nochangeWithIncrementalDeclarationFromBeforeExplaination(), - withOptionChange("with inlineSourceMap", "--inlineSourceMap"), - withOptionChange("with sourceMap", "--sourceMap"), - noChangeWithSubscenario("emit js files"), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - withOptionChange("with declaration and declarationMap, should not re-emit", "--declaration", "--declarationMap"), +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsbuild:: commandLine::", () => { + describe("different options::", () => { + function withOptionChange(subScenario: string, ...options: readonly string[]): ts.TestTscEdit { + return { + subScenario, + modifyFs: ts.noop, + commandLineArgs: ["--b", "/src/project", "--verbose", ...options] + }; + } + function noChangeWithSubscenario(subScenario: string): ts.TestTscEdit { + return { ...ts.noChangeRun, subScenario }; + } + function withOptionChangeAndDiscrepancyExplanation(subScenario: string, option: string): ts.TestTscEdit { + return { + ...withOptionChange(subScenario, option), + discrepancyExplanation: () => [ + `Clean build tsbuildinfo will have compilerOptions with composite and ${option.replace(/\-/g, "")}`, + `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option composite only`, + ] + }; + } + function withEmitDeclarationOnlyChangeAndDiscrepancyExplanation(subScenario: string): ts.TestTscEdit { + const edit = withOptionChangeAndDiscrepancyExplanation(subScenario, "--emitDeclarationOnly"); + const discrepancyExplanation = edit.discrepancyExplanation!; + edit.discrepancyExplanation = () => [ + ...discrepancyExplanation(), + `Clean build info does not have js section because its fresh build`, + `Incremental build info has js section from old build` + ]; + return edit; + } + function withOptionChangeAndExportExplanation(subScenario: string, ...options: readonly string[]): ts.TestTscEdit { + return { + ...withOptionChange(subScenario, ...options), + discrepancyExplanation: ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation, + }; + } + function nochangeWithIncrementalDeclarationFromBeforeExplaination(): ts.TestTscEdit { + return { + ...ts.noChangeRun, + discrepancyExplanation: () => [ + `Clean build tsbuildinfo will have compilerOptions {}`, + `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option declaration and declarationMap`, ], - baselinePrograms: true, - }); - verifyTscWithEdits({ - scenario: "commandLine", - subScenario: "different options with incremental with outFile", - fs: () => fs({ incremental: true, outFile: "../outFile.js", module: ModuleKind.AMD }), - commandLineArgs: ["--b", "/src/project", "--verbose"], - edits: [ - withOptionChange("with sourceMap", "--sourceMap"), - noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), - withOptionChange("with declaration, emit Dts and should not emit js", "--declaration"), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - nochangeWithIncrementalOutDeclarationFromBeforeExplaination(), - localChange(), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - nochangeWithIncrementalOutDeclarationFromBeforeExplaination(), - withOptionChange("with inlineSourceMap", "--inlineSourceMap"), - withOptionChange("with sourceMap", "--sourceMap"), - noChangeWithSubscenario("emit js files"), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - withOptionChange("with declaration and declarationMap, should not re-emit", "--declaration", "--declarationMap"), - ], - baselinePrograms: true, + }; + } + function nochangeWithIncrementalOutDeclarationFromBeforeExplaination(): ts.TestTscEdit { + const edit = nochangeWithIncrementalDeclarationFromBeforeExplaination(); + const discrepancyExplanation = edit.discrepancyExplanation!; + edit.discrepancyExplanation = () => [ + ...discrepancyExplanation(), + `Clean build does not have dts bundle section`, + `Incremental build contains the dts build section from before`, + ]; + return edit; + } + function localChange(): ts.TestTscEdit { + return { + subScenario: "local change", + modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "Local = 1", "Local = 10"), + }; + } + function fs(options: ts.CompilerOptions) { + return ts.loadProjectFromFiles({ + "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: ts.compilerOptionsToConfigJson(options) }), + "/src/project/a.ts": `export const a = 10;const aLocal = 10;`, + "/src/project/b.ts": `export const b = 10;const bLocal = 10;`, + "/src/project/c.ts": `import { a } from "./a";export const c = a;`, + "/src/project/d.ts": `import { b } from "./b";export const d = b;`, }); + } + ts.verifyTscWithEdits({ + scenario: "commandLine", + subScenario: "different options", + fs: () => fs({ composite: true }), + commandLineArgs: ["--b", "/src/project", "--verbose"], + edits: [ + withOptionChange("with sourceMap", "--sourceMap"), + noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), + withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), + ts.noChangeRun, + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"), + withOptionChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything", "--emitDeclarationOnly"), + ts.noChangeRun, + localChange(), + withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), + withOptionChange("with inlineSourceMap", "--inlineSourceMap"), + withOptionChange("with sourceMap", "--sourceMap"), + ], + baselinePrograms: true, }); - describe("emitDeclarationOnly::", () => { - function fs(options: CompilerOptions) { - return loadProjectFromFiles({ - "/src/project1/src/tsconfig.json": JSON.stringify({ - compilerOptions: compilerOptionsToConfigJson(options), - }), - "/src/project1/src/a.ts": `export const a = 10;const aLocal = 10;`, - "/src/project1/src/b.ts": `export const b = 10;const bLocal = 10;`, - "/src/project1/src/c.ts": `import { a } from "./a";export const c = a;`, - "/src/project1/src/d.ts": `import { b } from "./b";export const d = b;`, - "/src/project2/src/tsconfig.json": JSON.stringify({ - compilerOptions: compilerOptionsToConfigJson(options), - references: [{ path: "../../project1/src" }], - }), - "/src/project2/src/e.ts": `export const e = 10;`, - "/src/project2/src/f.ts": `import { a } from "${options.outFile ? "a" : "../../project1/src/a"}"; export const f = a;`, - "/src/project2/src/g.ts": `import { b } from "${options.outFile ? "b" : "../../project1/src/b"}"; export const g = b;`, - }); - } - function verifyWithIncremental(options: CompilerOptions) { - verifyTscWithEdits({ - scenario: "commandLine", - subScenario: subScenario("emitDeclarationOnly on commandline"), - fs: () => fs(options), - commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly"], - edits: [ - noChangeRun, - { - subScenario: "local change", - modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), - }, - { - subScenario: "non local change", - modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"), - }, - { - subScenario: "emit js files", - modifyFs: noop, - commandLineArgs: ["--b", "/src/project2/src", "--verbose"], - }, - { - ...noChangeRun, - discrepancyExplanation: () => [ - `Clean build tsbuildinfo for both projects will have compilerOptions with composite and emitDeclarationOnly`, - `Incremental build will detect that it doesnt need to rebuild so tsbuild info for projects is from before which has option composite only`, - ] - }, - { - subScenario: "js emit with change without emitDeclarationOnly", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"), - commandLineArgs: ["--b", "/src/project2/src", "--verbose"], - }, - { - subScenario: "local change", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"), - // --out without composite doesnt emit buildInfo without emitting program so it wouldnt have project2 tsbuildInfo so no mismatch - discrepancyExplanation: options.incremental && options.outFile ? undefined : () => [ - `Clean build tsbuildinfo for project2 will have compilerOptions with composite and emitDeclarationOnly`, - `Incremental build will detect that it doesnt need to rebuild project2 so tsbuildinfo for it is from before which has option composite only`, - ], - }, - { - subScenario: "non local change", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"), - }, - { - subScenario: "js emit with change without emitDeclarationOnly", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"), - commandLineArgs: ["--b", "/src/project2/src", "--verbose"], - }, - ], - baselinePrograms: true, - }); - verifyTscWithEdits({ - scenario: "commandLine", - subScenario: subScenario("emitDeclarationOnly false on commandline"), - fs: () => fs({ ...options, emitDeclarationOnly: true }), - commandLineArgs: ["--b", "/src/project2/src", "--verbose"], - edits: [ - noChangeRun, - { - subScenario: "change", - modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), - }, - { - subScenario: "emit js files", - modifyFs: noop, - commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], - }, - { - ...noChangeRun, - discrepancyExplanation: () => [ - `Clean build tsbuildinfo for both projects will have compilerOptions with composite and emitDeclarationOnly`, - `Incremental build will detect that it doesnt need to rebuild so tsbuild info for projects is from before which has option composite as true but emitDeclrationOnly as false`, - ] - }, - { - subScenario: "no change run with js emit", - modifyFs: noop, - commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], - }, - { - subScenario: "js emit with change", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"), - commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], - }, - ], - baselinePrograms: true, - }); - function subScenario(text: string) { - return `${text}${options.composite ? "" : " with declaration and incremental"}${options.outFile ? " with outFile" : ""}`; - } - } - verifyWithIncremental({ composite: true }); - verifyWithIncremental({ incremental: true, declaration: true }); - verifyWithIncremental({ composite: true, outFile: "../outFile.js", module: ModuleKind.AMD }); - verifyWithIncremental({ incremental: true, declaration: true, outFile: "../outFile.js", module: ModuleKind.AMD }); - - verifyTscWithEdits({ + ts.verifyTscWithEdits({ + scenario: "commandLine", + subScenario: "different options with outFile", + fs: () => fs({ composite: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }), + commandLineArgs: ["--b", "/src/project", "--verbose"], + edits: [ + withOptionChange("with sourceMap", "--sourceMap"), + noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), + withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), + ts.noChangeRun, + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"), + withEmitDeclarationOnlyChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything"), + ts.noChangeRun, + localChange(), + withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), + withOptionChange("with inlineSourceMap", "--inlineSourceMap"), + withOptionChange("with sourceMap", "--sourceMap"), + ], + baselinePrograms: true, + }); + ts.verifyTscWithEdits({ + scenario: "commandLine", + subScenario: "different options with incremental", + fs: () => fs({ incremental: true }), + commandLineArgs: ["--b", "/src/project", "--verbose"], + edits: [ + withOptionChangeAndExportExplanation("with sourceMap", "--sourceMap"), + withOptionChangeAndExportExplanation("should re-emit only js so they dont contain sourcemap"), + withOptionChange("with declaration, emit Dts and should not emit js", "--declaration"), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + nochangeWithIncrementalDeclarationFromBeforeExplaination(), + localChange(), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + nochangeWithIncrementalDeclarationFromBeforeExplaination(), + withOptionChange("with inlineSourceMap", "--inlineSourceMap"), + withOptionChange("with sourceMap", "--sourceMap"), + noChangeWithSubscenario("emit js files"), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + withOptionChange("with declaration and declarationMap, should not re-emit", "--declaration", "--declarationMap"), + ], + baselinePrograms: true, + }); + ts.verifyTscWithEdits({ + scenario: "commandLine", + subScenario: "different options with incremental with outFile", + fs: () => fs({ incremental: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }), + commandLineArgs: ["--b", "/src/project", "--verbose"], + edits: [ + withOptionChange("with sourceMap", "--sourceMap"), + noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), + withOptionChange("with declaration, emit Dts and should not emit js", "--declaration"), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + nochangeWithIncrementalOutDeclarationFromBeforeExplaination(), + localChange(), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + nochangeWithIncrementalOutDeclarationFromBeforeExplaination(), + withOptionChange("with inlineSourceMap", "--inlineSourceMap"), + withOptionChange("with sourceMap", "--sourceMap"), + noChangeWithSubscenario("emit js files"), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + withOptionChange("with declaration and declarationMap, should not re-emit", "--declaration", "--declarationMap"), + ], + baselinePrograms: true, + }); + }); + describe("emitDeclarationOnly::", () => { + function fs(options: ts.CompilerOptions) { + return ts.loadProjectFromFiles({ + "/src/project1/src/tsconfig.json": JSON.stringify({ + compilerOptions: ts.compilerOptionsToConfigJson(options), + }), + "/src/project1/src/a.ts": `export const a = 10;const aLocal = 10;`, + "/src/project1/src/b.ts": `export const b = 10;const bLocal = 10;`, + "/src/project1/src/c.ts": `import { a } from "./a";export const c = a;`, + "/src/project1/src/d.ts": `import { b } from "./b";export const d = b;`, + "/src/project2/src/tsconfig.json": JSON.stringify({ + compilerOptions: ts.compilerOptionsToConfigJson(options), + references: [{ path: "../../project1/src" }], + }), + "/src/project2/src/e.ts": `export const e = 10;`, + "/src/project2/src/f.ts": `import { a } from "${options.outFile ? "a" : "../../project1/src/a"}"; export const f = a;`, + "/src/project2/src/g.ts": `import { b } from "${options.outFile ? "b" : "../../project1/src/b"}"; export const g = b;`, + }); + } + function verifyWithIncremental(options: ts.CompilerOptions) { + ts.verifyTscWithEdits({ scenario: "commandLine", - subScenario: "emitDeclarationOnly on commandline with declaration", - fs: () => fs({ declaration: true }), + subScenario: subScenario("emitDeclarationOnly on commandline"), + fs: () => fs(options), commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly"], edits: [ - noChangeRun, + ts.noChangeRun, { subScenario: "local change", - modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), + modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), }, { subScenario: "non local change", - modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"), + modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"), }, { subScenario: "emit js files", - modifyFs: noop, - commandLineArgs: ["--b", "/src/project2/src", "--verbose"], - }, - noChangeRun, - { - subScenario: "js emit with change without emitDeclarationOnly", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"), + modifyFs: ts.noop, commandLineArgs: ["--b", "/src/project2/src", "--verbose"], }, { - subScenario: "local change", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"), - }, - { - subScenario: "non local change", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"), - }, - { - subScenario: "js emit with change without emitDeclarationOnly", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"), - commandLineArgs: ["--b", "/src/project2/src", "--verbose"], - }, - ], - baselinePrograms: true, - }); - - verifyTscWithEdits({ - scenario: "commandLine", - subScenario: "emitDeclarationOnly false on commandline with declaration", - fs: () => fs({ declaration: true, emitDeclarationOnly: true }), - commandLineArgs: ["--b", "/src/project2/src", "--verbose"], - edits: [ - noChangeRun, - { - subScenario: "change", - modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), - }, - { - subScenario: "emit js files", - modifyFs: noop, - commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], - }, - noChangeRun, - { - subScenario: "no change run with js emit", - modifyFs: noop, - commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], - }, - { - subScenario: "js emit with change", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"), - commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], - }, - ], - baselinePrograms: true, - }); - - verifyTscWithEdits({ - scenario: "commandLine", - subScenario: "emitDeclarationOnly on commandline with declaration with outFile", - fs: () => fs({ declaration: true, outFile: "../outFile.js", module: ModuleKind.AMD }), - commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly"], - edits: [ - noChangeRun, - { - subScenario: "local change", - modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), + ...ts.noChangeRun, + discrepancyExplanation: () => [ + `Clean build tsbuildinfo for both projects will have compilerOptions with composite and emitDeclarationOnly`, + `Incremental build will detect that it doesnt need to rebuild so tsbuild info for projects is from before which has option composite only`, + ] }, - { - subScenario: "non local change", - modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"), - }, - { - subScenario: "emit js files", - modifyFs: noop, - commandLineArgs: ["--b", "/src/project2/src", "--verbose"], - }, - noChangeRun, { subScenario: "js emit with change without emitDeclarationOnly", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"), + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"), commandLineArgs: ["--b", "/src/project2/src", "--verbose"], }, { subScenario: "local change", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"), + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"), + // --out without composite doesnt emit buildInfo without emitting program so it wouldnt have project2 tsbuildInfo so no mismatch + discrepancyExplanation: options.incremental && options.outFile ? undefined : () => [ + `Clean build tsbuildinfo for project2 will have compilerOptions with composite and emitDeclarationOnly`, + `Incremental build will detect that it doesnt need to rebuild project2 so tsbuildinfo for it is from before which has option composite only`, + ], }, { subScenario: "non local change", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"), + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"), }, { subScenario: "js emit with change without emitDeclarationOnly", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"), + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"), commandLineArgs: ["--b", "/src/project2/src", "--verbose"], }, ], baselinePrograms: true, }); - - verifyTscWithEdits({ + ts.verifyTscWithEdits({ scenario: "commandLine", - subScenario: "emitDeclarationOnly false on commandline with declaration with outFile", - fs: () => fs({ declaration: true, emitDeclarationOnly: true, outFile: "../outFile.js", module: ModuleKind.AMD }), + subScenario: subScenario("emitDeclarationOnly false on commandline"), + fs: () => fs({ ...options, emitDeclarationOnly: true }), commandLineArgs: ["--b", "/src/project2/src", "--verbose"], edits: [ - noChangeRun, + ts.noChangeRun, { subScenario: "change", - modifyFs: fs => appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), + modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), }, { subScenario: "emit js files", - modifyFs: noop, + modifyFs: ts.noop, commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], }, - noChangeRun, + { + ...ts.noChangeRun, + discrepancyExplanation: () => [ + `Clean build tsbuildinfo for both projects will have compilerOptions with composite and emitDeclarationOnly`, + `Incremental build will detect that it doesnt need to rebuild so tsbuild info for projects is from before which has option composite as true but emitDeclrationOnly as false`, + ] + }, { subScenario: "no change run with js emit", - modifyFs: noop, + modifyFs: ts.noop, commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], }, { subScenario: "js emit with change", - modifyFs: fs => appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"), + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"), commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], }, ], baselinePrograms: true, }); + function subScenario(text: string) { + return `${text}${options.composite ? "" : " with declaration and incremental"}${options.outFile ? " with outFile" : ""}`; + } + } + verifyWithIncremental({ composite: true }); + verifyWithIncremental({ incremental: true, declaration: true }); + verifyWithIncremental({ composite: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }); + verifyWithIncremental({ incremental: true, declaration: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }); + + ts.verifyTscWithEdits({ + scenario: "commandLine", + subScenario: "emitDeclarationOnly on commandline with declaration", + fs: () => fs({ declaration: true }), + commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly"], + edits: [ + ts.noChangeRun, + { + subScenario: "local change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), + }, + { + subScenario: "non local change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"), + }, + { + subScenario: "emit js files", + modifyFs: ts.noop, + commandLineArgs: ["--b", "/src/project2/src", "--verbose"], + }, + ts.noChangeRun, + { + subScenario: "js emit with change without emitDeclarationOnly", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"), + commandLineArgs: ["--b", "/src/project2/src", "--verbose"], + }, + { + subScenario: "local change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"), + }, + { + subScenario: "non local change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"), + }, + { + subScenario: "js emit with change without emitDeclarationOnly", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"), + commandLineArgs: ["--b", "/src/project2/src", "--verbose"], + }, + ], + baselinePrograms: true, + }); + + ts.verifyTscWithEdits({ + scenario: "commandLine", + subScenario: "emitDeclarationOnly false on commandline with declaration", + fs: () => fs({ declaration: true, emitDeclarationOnly: true }), + commandLineArgs: ["--b", "/src/project2/src", "--verbose"], + edits: [ + ts.noChangeRun, + { + subScenario: "change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), + }, + { + subScenario: "emit js files", + modifyFs: ts.noop, + commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], + }, + ts.noChangeRun, + { + subScenario: "no change run with js emit", + modifyFs: ts.noop, + commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], + }, + { + subScenario: "js emit with change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"), + commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], + }, + ], + baselinePrograms: true, + }); + + ts.verifyTscWithEdits({ + scenario: "commandLine", + subScenario: "emitDeclarationOnly on commandline with declaration with outFile", + fs: () => fs({ declaration: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }), + commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly"], + edits: [ + ts.noChangeRun, + { + subScenario: "local change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), + }, + { + subScenario: "non local change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "export const aaa = 10;"), + }, + { + subScenario: "emit js files", + modifyFs: ts.noop, + commandLineArgs: ["--b", "/src/project2/src", "--verbose"], + }, + ts.noChangeRun, + { + subScenario: "js emit with change without emitDeclarationOnly", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const alocal = 10;"), + commandLineArgs: ["--b", "/src/project2/src", "--verbose"], + }, + { + subScenario: "local change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const aaaa = 10;"), + }, + { + subScenario: "non local change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const aaaaa = 10;"), + }, + { + subScenario: "js emit with change without emitDeclarationOnly", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "export const a2 = 10;"), + commandLineArgs: ["--b", "/src/project2/src", "--verbose"], + }, + ], + baselinePrograms: true, + }); + + ts.verifyTscWithEdits({ + scenario: "commandLine", + subScenario: "emitDeclarationOnly false on commandline with declaration with outFile", + fs: () => fs({ declaration: true, emitDeclarationOnly: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }), + commandLineArgs: ["--b", "/src/project2/src", "--verbose"], + edits: [ + ts.noChangeRun, + { + subScenario: "change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/a.ts", "const aa = 10;"), + }, + { + subScenario: "emit js files", + modifyFs: ts.noop, + commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], + }, + ts.noChangeRun, + { + subScenario: "no change run with js emit", + modifyFs: ts.noop, + commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], + }, + { + subScenario: "js emit with change", + modifyFs: fs => ts.appendText(fs, "/src/project1/src/b.ts", "const blocal = 10;"), + commandLineArgs: ["--b", "/src/project2/src", "--verbose", "--emitDeclarationOnly", "false"], + }, + ], + baselinePrograms: true, }); }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuild/configFileErrors.ts b/src/testRunner/unittests/tsbuild/configFileErrors.ts index 1a9002a9910d8..7ed7ad9b8a9f5 100644 --- a/src/testRunner/unittests/tsbuild/configFileErrors.ts +++ b/src/testRunner/unittests/tsbuild/configFileErrors.ts @@ -1,21 +1,23 @@ -namespace ts { - describe("unittests:: tsbuild:: configFileErrors:: when tsconfig extends the missing file", () => { - verifyTsc({ - scenario: "configFileErrors", - subScenario: "when tsconfig extends the missing file", - fs: () => loadProjectFromDisk("tests/projects/missingExtendedConfig"), - commandLineArgs: ["--b", "/src/tsconfig.json"], - }); +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; + +describe("unittests:: tsbuild:: configFileErrors:: when tsconfig extends the missing file", () => { + ts.verifyTsc({ + scenario: "configFileErrors", + subScenario: "when tsconfig extends the missing file", + fs: () => ts.loadProjectFromDisk("tests/projects/missingExtendedConfig"), + commandLineArgs: ["--b", "/src/tsconfig.json"], }); +}); - describe("unittests:: tsbuild:: configFileErrors:: reports syntax errors in config file", () => { - verifyTscWithEdits({ - scenario: "configFileErrors", - subScenario: "reports syntax errors in config file", - fs: () => loadProjectFromFiles({ - "/src/a.ts": "export function foo() { }", - "/src/b.ts": "export function bar() { }", - "/src/tsconfig.json": Utils.dedent` +describe("unittests:: tsbuild:: configFileErrors:: reports syntax errors in config file", () => { + ts.verifyTscWithEdits({ + scenario: "configFileErrors", + subScenario: "reports syntax errors in config file", + fs: () => ts.loadProjectFromFiles({ + "/src/a.ts": "export function foo() { }", + "/src/b.ts": "export function bar() { }", + "/src/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true, @@ -25,34 +27,33 @@ namespace ts { "b.ts" ] }` - }), - commandLineArgs: ["--b", "/src/tsconfig.json"], - edits: [ - { - modifyFs: fs => replaceText(fs, "/src/tsconfig.json", ",", `, + }), + commandLineArgs: ["--b", "/src/tsconfig.json"], + edits: [ + { + modifyFs: fs => ts.replaceText(fs, "/src/tsconfig.json", ",", `, "declaration": true,`), - subScenario: "reports syntax errors after change to config file", - discrepancyExplanation: () => [ - "During incremental build, tsbuildinfo is not emitted, so declaration option is not present", - "Clean build has declaration option in tsbuildinfo", - ], - }, - { - modifyFs: fs => appendText(fs, "/src/a.ts", "export function fooBar() { }"), - subScenario: "reports syntax errors after change to ts file", - }, - noChangeRun, - { - modifyFs: fs => fs.writeFileSync( - "/src/tsconfig.json", - JSON.stringify({ - compilerOptions: { composite: true, declaration: true }, - files: ["a.ts", "b.ts"] - }) - ), - subScenario: "builds after fixing config file errors" - }, - ] - }); + subScenario: "reports syntax errors after change to config file", + discrepancyExplanation: () => [ + "During incremental build, tsbuildinfo is not emitted, so declaration option is not present", + "Clean build has declaration option in tsbuildinfo", + ], + }, + { + modifyFs: fs => ts.appendText(fs, "/src/a.ts", "export function fooBar() { }"), + subScenario: "reports syntax errors after change to ts file", + }, + ts.noChangeRun, + { + modifyFs: fs => fs.writeFileSync( + "/src/tsconfig.json", + JSON.stringify({ + compilerOptions: { composite: true, declaration: true }, + files: ["a.ts", "b.ts"] + }) + ), + subScenario: "builds after fixing config file errors" + }, + ] }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/configFileExtends.ts b/src/testRunner/unittests/tsbuild/configFileExtends.ts index b00a213b9d3fc..85d868dc063b2 100644 --- a/src/testRunner/unittests/tsbuild/configFileExtends.ts +++ b/src/testRunner/unittests/tsbuild/configFileExtends.ts @@ -1,52 +1,52 @@ -namespace ts { - describe("unittests:: tsbuild:: configFileExtends:: when tsconfig extends another config", () => { - function getConfigExtendsWithIncludeFs() { - return loadProjectFromFiles({ - "/src/tsconfig.json": JSON.stringify({ - references: [ - { path: "./shared/tsconfig.json" }, - { path: "./webpack/tsconfig.json" } - ], - files: [] - }), - "/src/shared/tsconfig-base.json": JSON.stringify({ - include: ["./typings-base/"] - }), - "/src/shared/typings-base/globals.d.ts": `type Unrestricted = any;`, - "/src/shared/tsconfig.json": JSON.stringify({ - extends: "./tsconfig-base.json", - compilerOptions: { - composite: true, - outDir: "../target-tsc-build/", - rootDir: ".." - }, - files: ["./index.ts"] - }), - "/src/shared/index.ts": `export const a: Unrestricted = 1;`, - "/src/webpack/tsconfig.json": JSON.stringify({ - extends: "../shared/tsconfig-base.json", - compilerOptions: { - composite: true, - outDir: "../target-tsc-build/", - rootDir: ".." - }, - files: ["./index.ts"], - references: [{ path: "../shared/tsconfig.json" }] - }), - "/src/webpack/index.ts": `export const b: Unrestricted = 1;`, - }); - } - verifyTsc({ - scenario: "configFileExtends", - subScenario: "when building solution with projects extends config with include", - fs: getConfigExtendsWithIncludeFs, - commandLineArgs: ["--b", "/src/tsconfig.json", "--v", "--listFiles"], - }); - verifyTsc({ - scenario: "configFileExtends", - subScenario: "when building project uses reference and both extend config with include", - fs: getConfigExtendsWithIncludeFs, - commandLineArgs: ["--b", "/src/webpack/tsconfig.json", "--v", "--listFiles"], +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsbuild:: configFileExtends:: when tsconfig extends another config", () => { + function getConfigExtendsWithIncludeFs() { + return ts.loadProjectFromFiles({ + "/src/tsconfig.json": JSON.stringify({ + references: [ + { path: "./shared/tsconfig.json" }, + { path: "./webpack/tsconfig.json" } + ], + files: [] + }), + "/src/shared/tsconfig-base.json": JSON.stringify({ + include: ["./typings-base/"] + }), + "/src/shared/typings-base/globals.d.ts": `type Unrestricted = any;`, + "/src/shared/tsconfig.json": JSON.stringify({ + extends: "./tsconfig-base.json", + compilerOptions: { + composite: true, + outDir: "../target-tsc-build/", + rootDir: ".." + }, + files: ["./index.ts"] + }), + "/src/shared/index.ts": `export const a: Unrestricted = 1;`, + "/src/webpack/tsconfig.json": JSON.stringify({ + extends: "../shared/tsconfig-base.json", + compilerOptions: { + composite: true, + outDir: "../target-tsc-build/", + rootDir: ".." + }, + files: ["./index.ts"], + references: [{ path: "../shared/tsconfig.json" }] + }), + "/src/webpack/index.ts": `export const b: Unrestricted = 1;`, }); + } + ts.verifyTsc({ + scenario: "configFileExtends", + subScenario: "when building solution with projects extends config with include", + fs: getConfigExtendsWithIncludeFs, + commandLineArgs: ["--b", "/src/tsconfig.json", "--v", "--listFiles"], + }); + ts.verifyTsc({ + scenario: "configFileExtends", + subScenario: "when building project uses reference and both extend config with include", + fs: getConfigExtendsWithIncludeFs, + commandLineArgs: ["--b", "/src/webpack/tsconfig.json", "--v", "--listFiles"], }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts b/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts index 595bc509744d5..45e4be8380647 100644 --- a/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts +++ b/src/testRunner/unittests/tsbuild/containerOnlyReferenced.ts @@ -1,42 +1,42 @@ -namespace ts { - describe("unittests:: tsbuild:: when containerOnly project is referenced", () => { - verifyTscWithEdits({ - scenario: "containerOnlyReferenced", - subScenario: "verify that subsequent builds after initial build doesnt build anything", - fs: () => loadProjectFromDisk("tests/projects/containerOnlyReferenced"), - commandLineArgs: ["--b", "/src", "--verbose"], - edits: noChangeOnlyRuns - }); +import * as ts from "../../_namespaces/ts"; - verifyTscWithEdits({ - scenario: "containerOnlyReferenced", - subScenario: "when solution is referenced indirectly", - fs: () => loadProjectFromFiles({ - "/src/project1/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true }, - references: [], - }), - "/src/project2/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true }, - references: [], - }), - "/src/project2/src/b.ts": "export const b = 10;", - "/src/project3/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true }, - references: [{ path: "../project1", }, { path: "../project2" }], - }), - "/src/project3/src/c.ts": "export const c = 10;", - "/src/project4/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true }, - references: [{ path: "../project3" }] - }), - "/src/project4/src/d.ts": "export const d = 10;", +describe("unittests:: tsbuild:: when containerOnly project is referenced", () => { + ts.verifyTscWithEdits({ + scenario: "containerOnlyReferenced", + subScenario: "verify that subsequent builds after initial build doesnt build anything", + fs: () => ts.loadProjectFromDisk("tests/projects/containerOnlyReferenced"), + commandLineArgs: ["--b", "/src", "--verbose"], + edits: ts.noChangeOnlyRuns + }); + + ts.verifyTscWithEdits({ + scenario: "containerOnlyReferenced", + subScenario: "when solution is referenced indirectly", + fs: () => ts.loadProjectFromFiles({ + "/src/project1/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true }, + references: [], + }), + "/src/project2/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true }, + references: [], + }), + "/src/project2/src/b.ts": "export const b = 10;", + "/src/project3/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true }, + references: [{ path: "../project1", }, { path: "../project2" }], + }), + "/src/project3/src/c.ts": "export const c = 10;", + "/src/project4/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true }, + references: [{ path: "../project3" }] }), - commandLineArgs: ["--b", "/src/project4", "--verbose", "--explainFiles"], - edits: [{ - subScenario: "modify project3 file", - modifyFs: fs => replaceText(fs, "/src/project3/src/c.ts", "c = ", "cc = "), - }], - }); + "/src/project4/src/d.ts": "export const d = 10;", + }), + commandLineArgs: ["--b", "/src/project4", "--verbose", "--explainFiles"], + edits: [{ + subScenario: "modify project3 file", + modifyFs: fs => ts.replaceText(fs, "/src/project3/src/c.ts", "c = ", "cc = "), + }], }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/declarationEmit.ts b/src/testRunner/unittests/tsbuild/declarationEmit.ts index 122912e026d8c..e7dc2b9f50791 100644 --- a/src/testRunner/unittests/tsbuild/declarationEmit.ts +++ b/src/testRunner/unittests/tsbuild/declarationEmit.ts @@ -1,39 +1,42 @@ -namespace ts { - describe("unittests:: tsbuild:: declarationEmit", () => { - function getFiles(): vfs.FileSet { - return { - "/src/solution/tsconfig.base.json": JSON.stringify({ - compilerOptions: { - rootDir: "./", - outDir: "lib" - } - }), - "/src/solution/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true }, - references: [{ path: "./src" }], - include: [] - }), - "/src/solution/src/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true }, - references: [{ path: "./subProject" }, { path: "./subProject2" }], - include: [] - }), - "/src/solution/src/subProject/tsconfig.json": JSON.stringify({ - extends: "../../tsconfig.base.json", - compilerOptions: { composite: true }, - references: [{ path: "../common" }], - include: ["./index.ts"] - }), - "/src/solution/src/subProject/index.ts": Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; +import * as Utils from "../../_namespaces/Utils"; + +describe("unittests:: tsbuild:: declarationEmit", () => { + function getFiles(): vfs.FileSet { + return { + "/src/solution/tsconfig.base.json": JSON.stringify({ + compilerOptions: { + rootDir: "./", + outDir: "lib" + } + }), + "/src/solution/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true }, + references: [{ path: "./src" }], + include: [] + }), + "/src/solution/src/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true }, + references: [{ path: "./subProject" }, { path: "./subProject2" }], + include: [] + }), + "/src/solution/src/subProject/tsconfig.json": JSON.stringify({ + extends: "../../tsconfig.base.json", + compilerOptions: { composite: true }, + references: [{ path: "../common" }], + include: ["./index.ts"] + }), + "/src/solution/src/subProject/index.ts": Utils.dedent` import { Nominal } from '../common/nominal'; export type MyNominal = Nominal;`, - "/src/solution/src/subProject2/tsconfig.json": JSON.stringify({ - extends: "../../tsconfig.base.json", - compilerOptions: { composite: true }, - references: [{ path: "../subProject" }], - include: ["./index.ts"] - }), - "/src/solution/src/subProject2/index.ts": Utils.dedent` + "/src/solution/src/subProject2/tsconfig.json": JSON.stringify({ + extends: "../../tsconfig.base.json", + compilerOptions: { composite: true }, + references: [{ path: "../subProject" }], + include: ["./index.ts"] + }), + "/src/solution/src/subProject2/index.ts": Utils.dedent` import { MyNominal } from '../subProject/index'; const variable = { key: 'value' as MyNominal, @@ -41,78 +44,77 @@ const variable = { export function getVar(): keyof typeof variable { return 'key'; }`, - "/src/solution/src/common/tsconfig.json": JSON.stringify({ - extends: "../../tsconfig.base.json", - compilerOptions: { composite: true }, - include: ["./nominal.ts"] - }), - "/src/solution/src/common/nominal.ts": Utils.dedent` + "/src/solution/src/common/tsconfig.json": JSON.stringify({ + extends: "../../tsconfig.base.json", + compilerOptions: { composite: true }, + include: ["./nominal.ts"] + }), + "/src/solution/src/common/nominal.ts": Utils.dedent` /// export declare type Nominal = MyNominal;`, - "/src/solution/src/common/types.d.ts": Utils.dedent` + "/src/solution/src/common/types.d.ts": Utils.dedent` declare type MyNominal = T & { specialKey: Name; };`, - }; - } - verifyTsc({ - scenario: "declarationEmit", - subScenario: "when declaration file is referenced through triple slash", - fs: () => loadProjectFromFiles(getFiles()), - commandLineArgs: ["--b", "/src/solution/tsconfig.json", "--verbose"] - }); + }; + } + ts.verifyTsc({ + scenario: "declarationEmit", + subScenario: "when declaration file is referenced through triple slash", + fs: () => ts.loadProjectFromFiles(getFiles()), + commandLineArgs: ["--b", "/src/solution/tsconfig.json", "--verbose"] + }); - verifyTsc({ - scenario: "declarationEmit", - subScenario: "when declaration file is referenced through triple slash but uses no references", - fs: () => loadProjectFromFiles({ - ...getFiles(), - "/src/solution/tsconfig.json": JSON.stringify({ - extends: "./tsconfig.base.json", - compilerOptions: { composite: true }, - include: ["./src/**/*.ts"] - }), + ts.verifyTsc({ + scenario: "declarationEmit", + subScenario: "when declaration file is referenced through triple slash but uses no references", + fs: () => ts.loadProjectFromFiles({ + ...getFiles(), + "/src/solution/tsconfig.json": JSON.stringify({ + extends: "./tsconfig.base.json", + compilerOptions: { composite: true }, + include: ["./src/**/*.ts"] }), - commandLineArgs: ["--b", "/src/solution/tsconfig.json", "--verbose"] - }); + }), + commandLineArgs: ["--b", "/src/solution/tsconfig.json", "--verbose"] + }); - verifyTsc({ - scenario: "declarationEmit", - subScenario: "when declaration file used inferred type from referenced project", - fs: () => loadProjectFromFiles({ - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - composite: true, - baseUrl: ".", - paths: { "@fluentui/*": ["packages/*/src"] } - } - }), - "/src/packages/pkg1/src/index.ts": Utils.dedent` + ts.verifyTsc({ + scenario: "declarationEmit", + subScenario: "when declaration file used inferred type from referenced project", + fs: () => ts.loadProjectFromFiles({ + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + composite: true, + baseUrl: ".", + paths: { "@fluentui/*": ["packages/*/src"] } + } + }), + "/src/packages/pkg1/src/index.ts": Utils.dedent` export interface IThing { a: string; } export interface IThings { thing1: IThing; }`, - "/src/packages/pkg1/tsconfig.json": JSON.stringify({ - extends: "../../tsconfig", - compilerOptions: { outDir: "lib" }, - include: ["src"] - }), - "/src/packages/pkg2/src/index.ts": Utils.dedent` + "/src/packages/pkg1/tsconfig.json": JSON.stringify({ + extends: "../../tsconfig", + compilerOptions: { outDir: "lib" }, + include: ["src"] + }), + "/src/packages/pkg2/src/index.ts": Utils.dedent` import { IThings } from '@fluentui/pkg1'; export function fn4() { const a: IThings = { thing1: { a: 'b' } }; return a.thing1; }`, - "/src/packages/pkg2/tsconfig.json": JSON.stringify({ - extends: "../../tsconfig", - compilerOptions: { outDir: "lib" }, - include: ["src"], - references: [{ path: "../pkg1" }] - }), + "/src/packages/pkg2/tsconfig.json": JSON.stringify({ + extends: "../../tsconfig", + compilerOptions: { outDir: "lib" }, + include: ["src"], + references: [{ path: "../pkg1" }] }), - commandLineArgs: ["--b", "/src/packages/pkg2/tsconfig.json", "--verbose"] - }); + }), + commandLineArgs: ["--b", "/src/packages/pkg2/tsconfig.json", "--verbose"] }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/demo.ts b/src/testRunner/unittests/tsbuild/demo.ts index 18bc1eea89a3b..18666a748df61 100644 --- a/src/testRunner/unittests/tsbuild/demo.ts +++ b/src/testRunner/unittests/tsbuild/demo.ts @@ -1,49 +1,50 @@ -namespace ts { - describe("unittests:: tsbuild:: on demo project", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/demo"); - }); +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; - after(() => { - projFs = undefined!; // Release the contents - }); +describe("unittests:: tsbuild:: on demo project", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = ts.loadProjectFromDisk("tests/projects/demo"); + }); - verifyTsc({ - scenario: "demo", - subScenario: "in master branch with everything setup correctly and reports no error", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"] - }); + after(() => { + projFs = undefined!; // Release the contents + }); - verifyTsc({ - scenario: "demo", - subScenario: "in circular branch reports the error about it by stopping build", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], - modifyFs: fs => replaceText( - fs, - "/src/core/tsconfig.json", - "}", - `}, + ts.verifyTsc({ + scenario: "demo", + subScenario: "in master branch with everything setup correctly and reports no error", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"] + }); + + ts.verifyTsc({ + scenario: "demo", + subScenario: "in circular branch reports the error about it by stopping build", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], + modifyFs: fs => ts.replaceText( + fs, + "/src/core/tsconfig.json", + "}", + `}, "references": [ { "path": "../zoo" } ]` - ) - }); - verifyTsc({ - scenario: "demo", - subScenario: "in bad-ref branch reports the error about files not in rootDir at the import location", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], - modifyFs: fs => prependText( - fs, - "/src/core/utilities.ts", - `import * as A from '../animals'; + ) + }); + ts.verifyTsc({ + scenario: "demo", + subScenario: "in bad-ref branch reports the error about files not in rootDir at the import location", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], + modifyFs: fs => ts.prependText( + fs, + "/src/core/utilities.ts", + `import * as A from '../animals'; ` - ) - }); + ) }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/emitDeclarationOnly.ts b/src/testRunner/unittests/tsbuild/emitDeclarationOnly.ts index 9994b24d32c2a..f7726fa349950 100644 --- a/src/testRunner/unittests/tsbuild/emitDeclarationOnly.ts +++ b/src/testRunner/unittests/tsbuild/emitDeclarationOnly.ts @@ -1,52 +1,53 @@ -namespace ts { - describe("unittests:: tsbuild:: on project with emitDeclarationOnly set to true", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/emitDeclarationOnly"); - }); - after(() => { - projFs = undefined!; - }); +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; - function verifyEmitDeclarationOnly(disableMap?: true) { - verifyTscWithEdits({ - subScenario: `only dts output in circular import project with emitDeclarationOnly${disableMap ? "" : " and declarationMap"}`, - fs: () => projFs, - scenario: "emitDeclarationOnly", - commandLineArgs: ["--b", "/src", "--verbose"], - modifyFs: disableMap ? - (fs => replaceText(fs, "/src/tsconfig.json", `"declarationMap": true,`, "")) : - undefined, - edits: [{ - subScenario: "incremental-declaration-changes", - modifyFs: fs => replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"), - }], - }); - } - verifyEmitDeclarationOnly(); - verifyEmitDeclarationOnly(/*disableMap*/ true); +describe("unittests:: tsbuild:: on project with emitDeclarationOnly set to true", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = ts.loadProjectFromDisk("tests/projects/emitDeclarationOnly"); + }); + after(() => { + projFs = undefined!; + }); - verifyTscWithEdits({ - subScenario: `only dts output in non circular imports project with emitDeclarationOnly`, + function verifyEmitDeclarationOnly(disableMap?: true) { + ts.verifyTscWithEdits({ + subScenario: `only dts output in circular import project with emitDeclarationOnly${disableMap ? "" : " and declarationMap"}`, fs: () => projFs, scenario: "emitDeclarationOnly", commandLineArgs: ["--b", "/src", "--verbose"], - modifyFs: fs => { - fs.rimrafSync("/src/src/index.ts"); - replaceText(fs, "/src/src/a.ts", `import { B } from "./b";`, `export class B { prop = "hello"; }`); - }, - edits: [ - { - subScenario: "incremental-declaration-doesnt-change", - modifyFs: fs => replaceText(fs, "/src/src/a.ts", "export interface A {", `class C { } + modifyFs: disableMap ? + (fs => ts.replaceText(fs, "/src/tsconfig.json", `"declarationMap": true,`, "")) : + undefined, + edits: [{ + subScenario: "incremental-declaration-changes", + modifyFs: fs => ts.replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"), + }], + }); + } + verifyEmitDeclarationOnly(); + verifyEmitDeclarationOnly(/*disableMap*/ true); + + ts.verifyTscWithEdits({ + subScenario: `only dts output in non circular imports project with emitDeclarationOnly`, + fs: () => projFs, + scenario: "emitDeclarationOnly", + commandLineArgs: ["--b", "/src", "--verbose"], + modifyFs: fs => { + fs.rimrafSync("/src/src/index.ts"); + ts.replaceText(fs, "/src/src/a.ts", `import { B } from "./b";`, `export class B { prop = "hello"; }`); + }, + edits: [ + { + subScenario: "incremental-declaration-doesnt-change", + modifyFs: fs => ts.replaceText(fs, "/src/src/a.ts", "export interface A {", `class C { } export interface A {`), - }, - { - subScenario: "incremental-declaration-changes", - modifyFs: fs => replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"), - }, - ], - }); + }, + { + subScenario: "incremental-declaration-changes", + modifyFs: fs => ts.replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"), + }, + ], }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/emptyFiles.ts b/src/testRunner/unittests/tsbuild/emptyFiles.ts index ffd9429a34411..49d18ace609fd 100644 --- a/src/testRunner/unittests/tsbuild/emptyFiles.ts +++ b/src/testRunner/unittests/tsbuild/emptyFiles.ts @@ -1,25 +1,26 @@ -namespace ts { - describe("unittests:: tsbuild - empty files option in tsconfig", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/empty-files"); - }); - after(() => { - projFs = undefined!; - }); +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; - verifyTsc({ - scenario: "emptyFiles", - subScenario: "has empty files diagnostic when files is empty and no references are provided", - fs: () => projFs, - commandLineArgs: ["--b", "/src/no-references"], - }); +describe("unittests:: tsbuild - empty files option in tsconfig", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = ts.loadProjectFromDisk("tests/projects/empty-files"); + }); + after(() => { + projFs = undefined!; + }); + + ts.verifyTsc({ + scenario: "emptyFiles", + subScenario: "has empty files diagnostic when files is empty and no references are provided", + fs: () => projFs, + commandLineArgs: ["--b", "/src/no-references"], + }); - verifyTsc({ - scenario: "emptyFiles", - subScenario: "does not have empty files diagnostic when files is empty and references are provided", - fs: () => projFs, - commandLineArgs: ["--b", "/src/with-references"], - }); + ts.verifyTsc({ + scenario: "emptyFiles", + subScenario: "does not have empty files diagnostic when files is empty and references are provided", + fs: () => projFs, + commandLineArgs: ["--b", "/src/with-references"], }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/exitCodeOnBogusFile.ts b/src/testRunner/unittests/tsbuild/exitCodeOnBogusFile.ts index 487671dfae147..f75d4a191d9fe 100644 --- a/src/testRunner/unittests/tsbuild/exitCodeOnBogusFile.ts +++ b/src/testRunner/unittests/tsbuild/exitCodeOnBogusFile.ts @@ -1,11 +1,11 @@ -namespace ts { - // https://github.com/microsoft/TypeScript/issues/33849 - describe("unittests:: tsbuild:: exitCodeOnBogusFile:: test exit code", () => { - verifyTsc({ - scenario: "exitCodeOnBogusFile", - subScenario: `test exit code`, - fs: () => loadProjectFromFiles({}), - commandLineArgs: ["-b", "bogus.json"] - }); +import * as ts from "../../_namespaces/ts"; + +// https://github.com/microsoft/TypeScript/issues/33849 +describe("unittests:: tsbuild:: exitCodeOnBogusFile:: test exit code", () => { + ts.verifyTsc({ + scenario: "exitCodeOnBogusFile", + subScenario: `test exit code`, + fs: () => ts.loadProjectFromFiles({}), + commandLineArgs: ["-b", "bogus.json"] }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/graphOrdering.ts b/src/testRunner/unittests/tsbuild/graphOrdering.ts index 79ddd80d67bde..06fd1f4b512a7 100644 --- a/src/testRunner/unittests/tsbuild/graphOrdering.ts +++ b/src/testRunner/unittests/tsbuild/graphOrdering.ts @@ -1,91 +1,93 @@ -namespace ts { - describe("unittests:: tsbuild - graph-ordering", () => { - let host: fakes.SolutionBuilderHost | undefined; - const deps: [string, string][] = [ - ["A", "B"], - ["B", "C"], - ["A", "C"], - ["B", "D"], - ["C", "D"], - ["C", "E"], - ["F", "E"], - ["H", "I"], - ["I", "J"], - ["J", "H"], - ["J", "E"] - ]; +import * as ts from "../../_namespaces/ts"; +import * as fakes from "../../_namespaces/fakes"; +import * as vfs from "../../_namespaces/vfs"; - before(() => { - const fs = new vfs.FileSystem(false); - host = fakes.SolutionBuilderHost.create(fs); - writeProjects(fs, ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"], deps); - }); +describe("unittests:: tsbuild - graph-ordering", () => { + let host: fakes.SolutionBuilderHost | undefined; + const deps: [string, string][] = [ + ["A", "B"], + ["B", "C"], + ["A", "C"], + ["B", "D"], + ["C", "D"], + ["C", "E"], + ["F", "E"], + ["H", "I"], + ["I", "J"], + ["J", "H"], + ["J", "E"] + ]; - after(() => { - host = undefined; - }); + before(() => { + const fs = new vfs.FileSystem(false); + host = fakes.SolutionBuilderHost.create(fs); + writeProjects(fs, ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"], deps); + }); - it("orders the graph correctly - specify two roots", () => { - checkGraphOrdering(["A", "G"], ["D", "E", "C", "B", "A", "G"]); - }); + after(() => { + host = undefined; + }); - it("orders the graph correctly - multiple parts of the same graph in various orders", () => { - checkGraphOrdering(["A"], ["D", "E", "C", "B", "A"]); - checkGraphOrdering(["A", "C", "D"], ["D", "E", "C", "B", "A"]); - checkGraphOrdering(["D", "C", "A"], ["D", "E", "C", "B", "A"]); - }); + it("orders the graph correctly - specify two roots", () => { + checkGraphOrdering(["A", "G"], ["D", "E", "C", "B", "A", "G"]); + }); - it("orders the graph correctly - other orderings", () => { - checkGraphOrdering(["F"], ["E", "F"]); - checkGraphOrdering(["E"], ["E"]); - checkGraphOrdering(["F", "C", "A"], ["E", "F", "D", "C", "B", "A"]); - }); + it("orders the graph correctly - multiple parts of the same graph in various orders", () => { + checkGraphOrdering(["A"], ["D", "E", "C", "B", "A"]); + checkGraphOrdering(["A", "C", "D"], ["D", "E", "C", "B", "A"]); + checkGraphOrdering(["D", "C", "A"], ["D", "E", "C", "B", "A"]); + }); - it("returns circular order", () => { - checkGraphOrdering(["H"], ["E", "J", "I", "H"], /*circular*/ true); - checkGraphOrdering(["A", "H"], ["D", "E", "C", "B", "A", "J", "I", "H"], /*circular*/ true); - }); + it("orders the graph correctly - other orderings", () => { + checkGraphOrdering(["F"], ["E", "F"]); + checkGraphOrdering(["E"], ["E"]); + checkGraphOrdering(["F", "C", "A"], ["E", "F", "D", "C", "B", "A"]); + }); + + it("returns circular order", () => { + checkGraphOrdering(["H"], ["E", "J", "I", "H"], /*circular*/ true); + checkGraphOrdering(["A", "H"], ["D", "E", "C", "B", "A", "J", "I", "H"], /*circular*/ true); + }); - function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[], circular?: true) { - const builder = createSolutionBuilder(host!, rootNames.map(getProjectFileName), { dry: true, force: false, verbose: false }); - const buildOrder = builder.getBuildOrder(); - assert.equal(isCircularBuildOrder(buildOrder), !!circular); - const buildQueue = getBuildOrderFromAnyBuildOrder(buildOrder); - assert.deepEqual(buildQueue, expectedBuildSet.map(getProjectFileName)); + function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[], circular?: true) { + const builder = ts.createSolutionBuilder(host!, rootNames.map(getProjectFileName), { dry: true, force: false, verbose: false }); + const buildOrder = builder.getBuildOrder(); + assert.equal(ts.isCircularBuildOrder(buildOrder), !!circular); + const buildQueue = ts.getBuildOrderFromAnyBuildOrder(buildOrder); + assert.deepEqual(buildQueue, expectedBuildSet.map(getProjectFileName)); - if (!circular) { - for (const dep of deps) { - const child = getProjectFileName(dep[0]); - if (buildQueue.indexOf(child) < 0) continue; - const parent = getProjectFileName(dep[1]); - assert.isAbove(buildQueue.indexOf(child), buildQueue.indexOf(parent), `Expecting child ${child} to be built after parent ${parent}`); - } + if (!circular) { + for (const dep of deps) { + const child = getProjectFileName(dep[0]); + if (buildQueue.indexOf(child) < 0) continue; + const parent = getProjectFileName(dep[1]); + assert.isAbove(buildQueue.indexOf(child), buildQueue.indexOf(parent), `Expecting child ${child} to be built after parent ${parent}`); } } + } - function getProjectFileName(proj: string) { - return `/project/${proj}/tsconfig.json` as ResolvedConfigFileName; - } + function getProjectFileName(proj: string) { + return `/project/${proj}/tsconfig.json` as ts.ResolvedConfigFileName; + } - function writeProjects(fileSystem: vfs.FileSystem, projectNames: string[], deps: [string, string][]): string[] { - const projFileNames: string[] = []; - for (const dep of deps) { - if (projectNames.indexOf(dep[0]) < 0) throw new Error(`Invalid dependency - project ${dep[0]} does not exist`); - if (projectNames.indexOf(dep[1]) < 0) throw new Error(`Invalid dependency - project ${dep[1]} does not exist`); - } - for (const proj of projectNames) { - fileSystem.mkdirpSync(`/project/${proj}`); - fileSystem.writeFileSync(`/project/${proj}/${proj}.ts`, "export {}"); - const configFileName = getProjectFileName(proj); - const configContent = JSON.stringify({ - compilerOptions: { composite: true }, - files: [`./${proj}.ts`], - references: deps.filter(d => d[0] === proj).map(d => ({ path: `../${d[1]}` })) - }, undefined, 2); - fileSystem.writeFileSync(configFileName, configContent); - projFileNames.push(configFileName); - } - return projFileNames; + function writeProjects(fileSystem: vfs.FileSystem, projectNames: string[], deps: [string, string][]): string[] { + const projFileNames: string[] = []; + for (const dep of deps) { + if (projectNames.indexOf(dep[0]) < 0) throw new Error(`Invalid dependency - project ${dep[0]} does not exist`); + if (projectNames.indexOf(dep[1]) < 0) throw new Error(`Invalid dependency - project ${dep[1]} does not exist`); } - }); -} + for (const proj of projectNames) { + fileSystem.mkdirpSync(`/project/${proj}`); + fileSystem.writeFileSync(`/project/${proj}/${proj}.ts`, "export {}"); + const configFileName = getProjectFileName(proj); + const configContent = JSON.stringify({ + compilerOptions: { composite: true }, + files: [`./${proj}.ts`], + references: deps.filter(d => d[0] === proj).map(d => ({ path: `../${d[1]}` })) + }, undefined, 2); + fileSystem.writeFileSync(configFileName, configContent); + projFileNames.push(configFileName); + } + return projFileNames; + } +}); diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index 6c21bc2df27a7..09f38eecd9fb5 100644 --- a/src/testRunner/unittests/tsbuild/helpers.ts +++ b/src/testRunner/unittests/tsbuild/helpers.ts @@ -1,89 +1,94 @@ -namespace ts { - export function errorDiagnostic(message: fakes.ExpectedDiagnosticMessage): fakes.ExpectedErrorDiagnostic { - return { message }; - } +import * as ts from "../../_namespaces/ts"; +import * as fakes from "../../_namespaces/fakes"; +import * as vfs from "../../_namespaces/vfs"; +import * as Harness from "../../_namespaces/Harness"; +import * as vpath from "../../_namespaces/vpath"; + +export function errorDiagnostic(message: fakes.ExpectedDiagnosticMessage): fakes.ExpectedErrorDiagnostic { + return { message }; +} - export function getExpectedDiagnosticForProjectsInBuild(...projects: string[]): fakes.ExpectedDiagnostic { - return [Diagnostics.Projects_in_this_build_Colon_0, projects.map(p => "\r\n * " + p).join("")]; - } +export function getExpectedDiagnosticForProjectsInBuild(...projects: string[]): fakes.ExpectedDiagnostic { + return [ts.Diagnostics.Projects_in_this_build_Colon_0, projects.map(p => "\r\n * " + p).join("")]; +} - export function changeCompilerVersion(host: fakes.SolutionBuilderHost) { - const originalReadFile = host.readFile; - host.readFile = path => { - const value = originalReadFile.call(host, path); - if (!value || !isBuildInfoFile(path)) return value; - const buildInfo = getBuildInfo(path, value); - if (!buildInfo) return value; - buildInfo.version = fakes.version; - return getBuildInfoText(buildInfo); - }; - } +export function changeCompilerVersion(host: fakes.SolutionBuilderHost) { + const originalReadFile = host.readFile; + host.readFile = path => { + const value = originalReadFile.call(host, path); + if (!value || !ts.isBuildInfoFile(path)) return value; + const buildInfo = ts.getBuildInfo(path, value); + if (!buildInfo) return value; + buildInfo.version = fakes.version; + return ts.getBuildInfoText(buildInfo); + }; +} - export function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) { - if (!fs.statSync(path).isFile()) { - throw new Error(`File ${path} does not exist`); - } - const old = fs.readFileSync(path, "utf-8"); - if (old.indexOf(oldText) < 0) { - throw new Error(`Text "${oldText}" does not exist in file ${path}`); - } - const newContent = old.replace(oldText, newText); - fs.writeFileSync(path, newContent, "utf-8"); +export function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); } - - export function prependText(fs: vfs.FileSystem, path: string, additionalContent: string) { - if (!fs.statSync(path).isFile()) { - throw new Error(`File ${path} does not exist`); - } - const old = fs.readFileSync(path, "utf-8"); - fs.writeFileSync(path, `${additionalContent}${old}`, "utf-8"); + const old = fs.readFileSync(path, "utf-8"); + if (old.indexOf(oldText) < 0) { + throw new Error(`Text "${oldText}" does not exist in file ${path}`); } + const newContent = old.replace(oldText, newText); + fs.writeFileSync(path, newContent, "utf-8"); +} - export function appendText(fs: vfs.FileSystem, path: string, additionalContent: string) { - if (!fs.statSync(path).isFile()) { - throw new Error(`File ${path} does not exist`); - } - const old = fs.readFileSync(path, "utf-8"); - fs.writeFileSync(path, `${old}${additionalContent}`); +export function prependText(fs: vfs.FileSystem, path: string, additionalContent: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); } + const old = fs.readFileSync(path, "utf-8"); + fs.writeFileSync(path, `${additionalContent}${old}`, "utf-8"); +} - export function indexOf(fs: vfs.FileSystem, path: string, searchStr: string) { - if (!fs.statSync(path).isFile()) { - throw new Error(`File ${path} does not exist`); - } - const content = fs.readFileSync(path, "utf-8"); - return content.indexOf(searchStr); +export function appendText(fs: vfs.FileSystem, path: string, additionalContent: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); } + const old = fs.readFileSync(path, "utf-8"); + fs.writeFileSync(path, `${old}${additionalContent}`); +} - export function lastIndexOf(fs: vfs.FileSystem, path: string, searchStr: string) { - if (!fs.statSync(path).isFile()) { - throw new Error(`File ${path} does not exist`); - } - const content = fs.readFileSync(path, "utf-8"); - return content.lastIndexOf(searchStr); +export function indexOf(fs: vfs.FileSystem, path: string, searchStr: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); } + const content = fs.readFileSync(path, "utf-8"); + return content.indexOf(searchStr); +} - export function expectedLocationIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation { - return { - file, - start: indexOf(fs, file, searchStr), - length: searchStr.length - }; +export function lastIndexOf(fs: vfs.FileSystem, path: string, searchStr: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); } + const content = fs.readFileSync(path, "utf-8"); + return content.lastIndexOf(searchStr); +} - export function expectedLocationLastIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation { - return { - file, - start: lastIndexOf(fs, file, searchStr), - length: searchStr.length - }; - } +export function expectedLocationIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation { + return { + file, + start: indexOf(fs, file, searchStr), + length: searchStr.length + }; +} + +export function expectedLocationLastIndexOf(fs: vfs.FileSystem, file: string, searchStr: string): fakes.ExpectedDiagnosticLocation { + return { + file, + start: lastIndexOf(fs, file, searchStr), + length: searchStr.length + }; +} - export const libContent = `${TestFSWithWatch.libFile.content} +export const libContent = `${ts.TestFSWithWatch.libFile.content} interface ReadonlyArray {} declare const console: { log(msg: any): void; };`; - export const symbolLibContent = ` +export const symbolLibContent = ` interface SymbolConstructor { readonly species: symbol; readonly toStringTag: symbol; @@ -94,672 +99,671 @@ interface Symbol { } `; - /** - * Load project from disk into /src folder - */ - export function loadProjectFromDisk( - root: string, - libContentToAppend?: string - ): vfs.FileSystem { - const resolver = vfs.createResolver(Harness.IO); - const fs = new vfs.FileSystem(/*ignoreCase*/ true, { - files: { - ["/src"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), root), resolver) - }, - cwd: "/", - meta: { defaultLibLocation: "/lib" }, - }); - addLibAndMakeReadonly(fs, libContentToAppend); - return fs; - } +/** + * Load project from disk into /src folder + */ +export function loadProjectFromDisk( + root: string, + libContentToAppend?: string +): vfs.FileSystem { + const resolver = vfs.createResolver(Harness.IO); + const fs = new vfs.FileSystem(/*ignoreCase*/ true, { + files: { + ["/src"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), root), resolver) + }, + cwd: "/", + meta: { defaultLibLocation: "/lib" }, + }); + addLibAndMakeReadonly(fs, libContentToAppend); + return fs; +} - /** - * All the files must be in /src - */ - export function loadProjectFromFiles( - files: vfs.FileSet, - libContentToAppend?: string - ): vfs.FileSystem { - const fs = new vfs.FileSystem(/*ignoreCase*/ true, { - files, - cwd: "/", - meta: { defaultLibLocation: "/lib" }, - }); - addLibAndMakeReadonly(fs, libContentToAppend); - return fs; - } +/** + * All the files must be in /src + */ +export function loadProjectFromFiles( + files: vfs.FileSet, + libContentToAppend?: string +): vfs.FileSystem { + const fs = new vfs.FileSystem(/*ignoreCase*/ true, { + files, + cwd: "/", + meta: { defaultLibLocation: "/lib" }, + }); + addLibAndMakeReadonly(fs, libContentToAppend); + return fs; +} - function addLibAndMakeReadonly(fs: vfs.FileSystem, libContentToAppend?: string) { - fs.mkdirSync("/lib"); - fs.writeFileSync("/lib/lib.d.ts", libContentToAppend ? `${libContent}${libContentToAppend}` : libContent); - fs.makeReadonly(); - } +function addLibAndMakeReadonly(fs: vfs.FileSystem, libContentToAppend?: string) { + fs.mkdirSync("/lib"); + fs.writeFileSync("/lib/lib.d.ts", libContentToAppend ? `${libContent}${libContentToAppend}` : libContent); + fs.makeReadonly(); +} - export function verifyOutputsPresent(fs: vfs.FileSystem, outputs: readonly string[]) { - for (const output of outputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } +export function verifyOutputsPresent(fs: vfs.FileSystem, outputs: readonly string[]) { + for (const output of outputs) { + assert(fs.existsSync(output), `Expect file ${output} to exist`); } +} - export function verifyOutputsAbsent(fs: vfs.FileSystem, outputs: readonly string[]) { - for (const output of outputs) { - assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`); - } +export function verifyOutputsAbsent(fs: vfs.FileSystem, outputs: readonly string[]) { + for (const output of outputs) { + assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`); } +} - export function generateSourceMapBaselineFiles(sys: System & { writtenFiles: ReadonlyCollection; }) { - const mapFileNames = mapDefinedIterator(sys.writtenFiles.keys(), f => f.endsWith(".map") ? f : undefined); - while (true) { - const result = mapFileNames.next(); - if (result.done) break; - const mapFile = result.value; - const text = Harness.SourceMapRecorder.getSourceMapRecordWithSystem(sys, mapFile); - sys.writeFile(`${mapFile}.baseline.txt`, text); - } +export function generateSourceMapBaselineFiles(sys: ts.System & { writtenFiles: ts.ReadonlyCollection; }) { + const mapFileNames = ts.mapDefinedIterator(sys.writtenFiles.keys(), f => f.endsWith(".map") ? f : undefined); + while (true) { + const result = mapFileNames.next(); + if (result.done) break; + const mapFile = result.value; + const text = Harness.SourceMapRecorder.getSourceMapRecordWithSystem(sys, mapFile); + sys.writeFile(`${mapFile}.baseline.txt`, text); } +} - function generateBundleFileSectionInfo(sys: System, originalReadCall: System["readFile"], baselineRecorder: Harness.Compiler.WriterAggregator, bundleFileInfo: BundleFileInfo | undefined, outFile: string | undefined) { - if (!length(bundleFileInfo && bundleFileInfo.sections) && !outFile) return; // Nothing to baseline - - const content = outFile && sys.fileExists(outFile) ? originalReadCall.call(sys, outFile, "utf8")! : ""; - baselineRecorder.WriteLine("======================================================================"); - baselineRecorder.WriteLine(`File:: ${outFile}`); - for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) { - baselineRecorder.WriteLine("----------------------------------------------------------------------"); - writeSectionHeader(section); - if (section.kind !== BundleFileSectionKind.Prepend) { - writeTextOfSection(section.pos, section.end); - } - else if (section.texts.length > 0) { - Debug.assert(section.pos === first(section.texts).pos); - Debug.assert(section.end === last(section.texts).end); - for (const text of section.texts) { - baselineRecorder.WriteLine(">>--------------------------------------------------------------------"); - writeSectionHeader(text); - writeTextOfSection(text.pos, text.end); - } - } - else { - Debug.assert(section.pos === section.end); - } +function generateBundleFileSectionInfo(sys: ts.System, originalReadCall: ts.System["readFile"], baselineRecorder: Harness.Compiler.WriterAggregator, bundleFileInfo: ts.BundleFileInfo | undefined, outFile: string | undefined) { + if (!ts.length(bundleFileInfo && bundleFileInfo.sections) && !outFile) return; // Nothing to baseline + + const content = outFile && sys.fileExists(outFile) ? originalReadCall.call(sys, outFile, "utf8")! : ""; + baselineRecorder.WriteLine("======================================================================"); + baselineRecorder.WriteLine(`File:: ${outFile}`); + for (const section of bundleFileInfo ? bundleFileInfo.sections : ts.emptyArray) { + baselineRecorder.WriteLine("----------------------------------------------------------------------"); + writeSectionHeader(section); + if (section.kind !== ts.BundleFileSectionKind.Prepend) { + writeTextOfSection(section.pos, section.end); } - baselineRecorder.WriteLine("======================================================================"); - - function writeTextOfSection(pos: number, end: number) { - const textLines = content.substring(pos, end).split(/\r?\n/); - for (const line of textLines) { - baselineRecorder.WriteLine(line); + else if (section.texts.length > 0) { + ts.Debug.assert(section.pos === ts.first(section.texts).pos); + ts.Debug.assert(section.end === ts.last(section.texts).end); + for (const text of section.texts) { + baselineRecorder.WriteLine(">>--------------------------------------------------------------------"); + writeSectionHeader(text); + writeTextOfSection(text.pos, text.end); } } - - function writeSectionHeader(section: BundleFileSection) { - baselineRecorder.WriteLine(`${section.kind}: (${section.pos}-${section.end})${section.data ? ":: " + section.data : ""}${section.kind === BundleFileSectionKind.Prepend ? " texts:: " + section.texts.length : ""}`); + else { + ts.Debug.assert(section.pos === section.end); } } + baselineRecorder.WriteLine("======================================================================"); - type ReadableProgramBuildInfoDiagnostic = string | [string, readonly ReusableDiagnostic[]]; - type ReadableBuilderFileEmit = string & { __readableBuilderFileEmit: any; }; - type ReadableProgramBuilderInfoFilePendingEmit = [original: string | [string], emitKind: ReadableBuilderFileEmit]; - type ReadableProgramBuildInfoEmitSignature = string | [string, EmitSignature | []]; - type ReadableProgramBuildInfoFileInfo = Omit & { - impliedFormat: string | undefined; - original: T | undefined; - }; - type ReadableProgramMultiFileEmitBuildInfo = Omit & { - fileNamesList: readonly (readonly string[])[] | undefined; - fileInfos: MapLike>; - referencedMap: MapLike | undefined; - exportedModulesMap: MapLike | undefined; - semanticDiagnosticsPerFile: readonly ReadableProgramBuildInfoDiagnostic[] | undefined; - affectedFilesPendingEmit: readonly ReadableProgramBuilderInfoFilePendingEmit[] | undefined; - changeFileSet: readonly string[] | undefined; - emitSignatures: readonly ReadableProgramBuildInfoEmitSignature[] | undefined; - }; - type ReadableProgramBuildInfoBundlePendingEmit = [emitKind: ReadableBuilderFileEmit, original: ProgramBuildInfoBundlePendingEmit]; - type ReadableProgramBundleEmitBuildInfo = Omit & { - fileInfos: MapLike>; - pendingEmit: ReadableProgramBuildInfoBundlePendingEmit | undefined; - }; + function writeTextOfSection(pos: number, end: number) { + const textLines = content.substring(pos, end).split(/\r?\n/); + for (const line of textLines) { + baselineRecorder.WriteLine(line); + } + } - type ReadableProgramBuildInfo = ReadableProgramMultiFileEmitBuildInfo | ReadableProgramBundleEmitBuildInfo; + function writeSectionHeader(section: ts.BundleFileSection) { + baselineRecorder.WriteLine(`${section.kind}: (${section.pos}-${section.end})${section.data ? ":: " + section.data : ""}${section.kind === ts.BundleFileSectionKind.Prepend ? " texts:: " + section.texts.length : ""}`); + } +} - function isReadableProgramBundleEmitBuildInfo(info: ReadableProgramBuildInfo | undefined): info is ReadableProgramBundleEmitBuildInfo { - return !!info && !!outFile(info.options || {}); +type ReadableProgramBuildInfoDiagnostic = string | [string, readonly ts.ReusableDiagnostic[]]; +type ReadableBuilderFileEmit = string & { __readableBuilderFileEmit: any; }; +type ReadableProgramBuilderInfoFilePendingEmit = [original: string | [string], emitKind: ReadableBuilderFileEmit]; +type ReadableProgramBuildInfoEmitSignature = string | [string, ts.EmitSignature | []]; +type ReadableProgramBuildInfoFileInfo = Omit & { + impliedFormat: string | undefined; + original: T | undefined; +}; +type ReadableProgramMultiFileEmitBuildInfo = Omit & { + fileNamesList: readonly (readonly string[])[] | undefined; + fileInfos: ts.MapLike>; + referencedMap: ts.MapLike | undefined; + exportedModulesMap: ts.MapLike | undefined; + semanticDiagnosticsPerFile: readonly ReadableProgramBuildInfoDiagnostic[] | undefined; + affectedFilesPendingEmit: readonly ReadableProgramBuilderInfoFilePendingEmit[] | undefined; + changeFileSet: readonly string[] | undefined; + emitSignatures: readonly ReadableProgramBuildInfoEmitSignature[] | undefined; +}; +type ReadableProgramBuildInfoBundlePendingEmit = [emitKind: ReadableBuilderFileEmit, original: ts.ProgramBuildInfoBundlePendingEmit]; +type ReadableProgramBundleEmitBuildInfo = Omit & { + fileInfos: ts.MapLike>; + pendingEmit: ReadableProgramBuildInfoBundlePendingEmit | undefined; +}; + +type ReadableProgramBuildInfo = ReadableProgramMultiFileEmitBuildInfo | ReadableProgramBundleEmitBuildInfo; + +function isReadableProgramBundleEmitBuildInfo(info: ReadableProgramBuildInfo | undefined): info is ReadableProgramBundleEmitBuildInfo { + return !!info && !!ts.outFile(info.options || {}); +} +type ReadableBuildInfo = Omit & { program: ReadableProgramBuildInfo | undefined; size: number; }; +function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string, buildInfo: ts.BuildInfo) { + let program: ReadableProgramBuildInfo | undefined; + let fileNamesList: string[][] | undefined; + if (buildInfo.program && ts.isProgramBundleEmitBuildInfo(buildInfo.program)) { + const fileInfos: ReadableProgramBundleEmitBuildInfo["fileInfos"] = {}; + buildInfo.program?.fileInfos?.forEach((fileInfo, index) => + fileInfos[toFileName(index + 1 as ts.ProgramBuildInfoFileId)] = ts.isString(fileInfo) ? + fileInfo : + toReadableFileInfo(fileInfo, ts.identity) + ); + const pendingEmit = buildInfo.program.pendingEmit; + program = { + ...buildInfo.program, + fileInfos, + pendingEmit: pendingEmit === undefined ? + undefined : + [ + toReadableBuilderFileEmit(ts.toProgramEmitPending(pendingEmit, buildInfo.program.options)), + pendingEmit + ], + }; } - type ReadableBuildInfo = Omit & { program: ReadableProgramBuildInfo | undefined; size: number; }; - function generateBuildInfoProgramBaseline(sys: System, buildInfoPath: string, buildInfo: BuildInfo) { - let program: ReadableProgramBuildInfo | undefined; - let fileNamesList: string[][] | undefined; - if (buildInfo.program && isProgramBundleEmitBuildInfo(buildInfo.program)) { - const fileInfos: ReadableProgramBundleEmitBuildInfo["fileInfos"] = {}; - buildInfo.program?.fileInfos?.forEach((fileInfo, index) => - fileInfos[toFileName(index + 1 as ProgramBuildInfoFileId)] = isString(fileInfo) ? - fileInfo : - toReadableFileInfo(fileInfo, identity) - ); - const pendingEmit = buildInfo.program.pendingEmit; - program = { - ...buildInfo.program, - fileInfos, - pendingEmit: pendingEmit === undefined ? - undefined : - [ - toReadableBuilderFileEmit(toProgramEmitPending(pendingEmit, buildInfo.program.options)), - pendingEmit - ], - }; - } - else if (buildInfo.program) { - const fileInfos: ReadableProgramMultiFileEmitBuildInfo["fileInfos"] = {}; - buildInfo.program?.fileInfos?.forEach((fileInfo, index) => fileInfos[toFileName(index + 1 as ProgramBuildInfoFileId)] = toReadableFileInfo(fileInfo, toBuilderStateFileInfoForMultiEmit)); - fileNamesList = buildInfo.program.fileIdsList?.map(fileIdsListId => fileIdsListId.map(toFileName)); - const fullEmitForOptions = buildInfo.program.affectedFilesPendingEmit ? getBuilderFileEmit(buildInfo.program.options || {}) : undefined; - program = buildInfo.program && { - fileNames: buildInfo.program.fileNames, - fileNamesList, - fileInfos: buildInfo.program.fileInfos ? fileInfos : undefined!, - options: buildInfo.program.options, - referencedMap: toMapOfReferencedSet(buildInfo.program.referencedMap), - exportedModulesMap: toMapOfReferencedSet(buildInfo.program.exportedModulesMap), - semanticDiagnosticsPerFile: buildInfo.program.semanticDiagnosticsPerFile?.map(d => - isNumber(d) ? - toFileName(d) : - [toFileName(d[0]), d[1]] - ), - affectedFilesPendingEmit: buildInfo.program.affectedFilesPendingEmit?.map(value => toReadableProgramBuilderInfoFilePendingEmit(value, fullEmitForOptions!)), - changeFileSet: buildInfo.program.changeFileSet?.map(toFileName), - emitSignatures: buildInfo.program.emitSignatures?.map(s => - isNumber(s) ? - toFileName(s) : - [toFileName(s[0]), s[1]] - ), - latestChangedDtsFile: buildInfo.program.latestChangedDtsFile, - }; - } - const version = buildInfo.version === ts.version ? fakes.version : buildInfo.version; - const result: ReadableBuildInfo = { - // Baseline fixed order for bundle - bundle: buildInfo.bundle && { - ...buildInfo.bundle, - js: buildInfo.bundle.js && { - sections: buildInfo.bundle.js.sections, - hash: buildInfo.bundle.js.hash, - mapHash: buildInfo.bundle.js.mapHash, - sources: buildInfo.bundle.js.sources, - }, - dts: buildInfo.bundle.dts && { - sections: buildInfo.bundle.dts.sections, - hash: buildInfo.bundle.dts.hash, - mapHash: buildInfo.bundle.dts.mapHash, - sources: buildInfo.bundle.dts.sources, - }, - }, - program, - version, - size: getBuildInfoText({ ...buildInfo, version }).length, + else if (buildInfo.program) { + const fileInfos: ReadableProgramMultiFileEmitBuildInfo["fileInfos"] = {}; + buildInfo.program?.fileInfos?.forEach((fileInfo, index) => fileInfos[toFileName(index + 1 as ts.ProgramBuildInfoFileId)] = toReadableFileInfo(fileInfo, ts.toBuilderStateFileInfoForMultiEmit)); + fileNamesList = buildInfo.program.fileIdsList?.map(fileIdsListId => fileIdsListId.map(toFileName)); + const fullEmitForOptions = buildInfo.program.affectedFilesPendingEmit ? ts.getBuilderFileEmit(buildInfo.program.options || {}) : undefined; + program = buildInfo.program && { + fileNames: buildInfo.program.fileNames, + fileNamesList, + fileInfos: buildInfo.program.fileInfos ? fileInfos : undefined!, + options: buildInfo.program.options, + referencedMap: toMapOfReferencedSet(buildInfo.program.referencedMap), + exportedModulesMap: toMapOfReferencedSet(buildInfo.program.exportedModulesMap), + semanticDiagnosticsPerFile: buildInfo.program.semanticDiagnosticsPerFile?.map(d => + ts.isNumber(d) ? + toFileName(d) : + [toFileName(d[0]), d[1]] + ), + affectedFilesPendingEmit: buildInfo.program.affectedFilesPendingEmit?.map(value => toReadableProgramBuilderInfoFilePendingEmit(value, fullEmitForOptions!)), + changeFileSet: buildInfo.program.changeFileSet?.map(toFileName), + emitSignatures: buildInfo.program.emitSignatures?.map(s => + ts.isNumber(s) ? + toFileName(s) : + [toFileName(s[0]), s[1]] + ), + latestChangedDtsFile: buildInfo.program.latestChangedDtsFile, }; - // For now its just JSON.stringify - sys.writeFile(`${buildInfoPath}.readable.baseline.txt`, JSON.stringify(result, /*replacer*/ undefined, 2)); + } + const version = buildInfo.version === ts.version ? fakes.version : buildInfo.version; + const result: ReadableBuildInfo = { + // Baseline fixed order for bundle + bundle: buildInfo.bundle && { + ...buildInfo.bundle, + js: buildInfo.bundle.js && { + sections: buildInfo.bundle.js.sections, + hash: buildInfo.bundle.js.hash, + mapHash: buildInfo.bundle.js.mapHash, + sources: buildInfo.bundle.js.sources, + }, + dts: buildInfo.bundle.dts && { + sections: buildInfo.bundle.dts.sections, + hash: buildInfo.bundle.dts.hash, + mapHash: buildInfo.bundle.dts.mapHash, + sources: buildInfo.bundle.dts.sources, + }, + }, + program, + version, + size: ts.getBuildInfoText({ ...buildInfo, version }).length, + }; + // For now its just JSON.stringify + sys.writeFile(`${buildInfoPath}.readable.baseline.txt`, JSON.stringify(result, /*replacer*/ undefined, 2)); - function toFileName(fileId: ProgramBuildInfoFileId) { - return buildInfo.program!.fileNames[fileId - 1]; - } + function toFileName(fileId: ts.ProgramBuildInfoFileId) { + return buildInfo.program!.fileNames[fileId - 1]; + } - function toFileNames(fileIdsListId: ProgramBuildInfoFileIdListId) { - return fileNamesList![fileIdsListId - 1]; - } + function toFileNames(fileIdsListId: ts.ProgramBuildInfoFileIdListId) { + return fileNamesList![fileIdsListId - 1]; + } - function toReadableFileInfo(original: T, toFileInfo: (fileInfo: T) => BuilderState.FileInfo): ReadableProgramBuildInfoFileInfo { - const info = toFileInfo(original); - return { - original: isString(original) ? undefined : original, - ...info, - impliedFormat: info.impliedFormat && getNameOfCompilerOptionValue(info.impliedFormat, moduleOptionDeclaration.type), - }; - } + function toReadableFileInfo(original: T, toFileInfo: (fileInfo: T) => ts.BuilderState.FileInfo): ReadableProgramBuildInfoFileInfo { + const info = toFileInfo(original); + return { + original: ts.isString(original) ? undefined : original, + ...info, + impliedFormat: info.impliedFormat && ts.getNameOfCompilerOptionValue(info.impliedFormat, ts.moduleOptionDeclaration.type), + }; + } - function toMapOfReferencedSet(referenceMap: ProgramBuildInfoReferencedMap | undefined): MapLike | undefined { - if (!referenceMap) return undefined; - const result: MapLike = {}; - for (const [fileNamesKey, fileNamesListKey] of referenceMap) { - result[toFileName(fileNamesKey)] = toFileNames(fileNamesListKey); - } - return result; + function toMapOfReferencedSet(referenceMap: ts.ProgramBuildInfoReferencedMap | undefined): ts.MapLike | undefined { + if (!referenceMap) return undefined; + const result: ts.MapLike = {}; + for (const [fileNamesKey, fileNamesListKey] of referenceMap) { + result[toFileName(fileNamesKey)] = toFileNames(fileNamesListKey); } + return result; + } - function toReadableProgramBuilderInfoFilePendingEmit(value: ProgramBuilderInfoFilePendingEmit, fullEmitForOptions: BuilderFileEmit): ReadableProgramBuilderInfoFilePendingEmit { - return [ - isNumber(value) ? toFileName(value) : [toFileName(value[0])], - toReadableBuilderFileEmit(toBuilderFileEmit(value, fullEmitForOptions)), - ]; - } + function toReadableProgramBuilderInfoFilePendingEmit(value: ts.ProgramBuilderInfoFilePendingEmit, fullEmitForOptions: ts.BuilderFileEmit): ReadableProgramBuilderInfoFilePendingEmit { + return [ + ts.isNumber(value) ? toFileName(value) : [toFileName(value[0])], + toReadableBuilderFileEmit(ts.toBuilderFileEmit(value, fullEmitForOptions)), + ]; + } - function toReadableBuilderFileEmit(emit: BuilderFileEmit | undefined): ReadableBuilderFileEmit { - let result = ""; - if (emit) { - if (emit & BuilderFileEmit.Js) addFlags("Js"); - if (emit & BuilderFileEmit.JsMap) addFlags("JsMap"); - if (emit & BuilderFileEmit.JsInlineMap) addFlags("JsInlineMap"); - if (emit & BuilderFileEmit.Dts) addFlags("Dts"); - if (emit & BuilderFileEmit.DtsMap) addFlags("DtsMap"); - } - return (result || "None") as ReadableBuilderFileEmit; - function addFlags(flag: string) { - result = result ? `${result} | ${flag}` : flag; - } + function toReadableBuilderFileEmit(emit: ts.BuilderFileEmit | undefined): ReadableBuilderFileEmit { + let result = ""; + if (emit) { + if (emit & ts.BuilderFileEmit.Js) addFlags("Js"); + if (emit & ts.BuilderFileEmit.JsMap) addFlags("JsMap"); + if (emit & ts.BuilderFileEmit.JsInlineMap) addFlags("JsInlineMap"); + if (emit & ts.BuilderFileEmit.Dts) addFlags("Dts"); + if (emit & ts.BuilderFileEmit.DtsMap) addFlags("DtsMap"); + } + return (result || "None") as ReadableBuilderFileEmit; + function addFlags(flag: string) { + result = result ? `${result} | ${flag}` : flag; } } +} - export function toPathWithSystem(sys: System, fileName: string): Path { - return toPath(fileName, sys.getCurrentDirectory(), createGetCanonicalFileName(sys.useCaseSensitiveFileNames)); - } +export function toPathWithSystem(sys: ts.System, fileName: string): ts.Path { + return ts.toPath(fileName, sys.getCurrentDirectory(), ts.createGetCanonicalFileName(sys.useCaseSensitiveFileNames)); +} - export function baselineBuildInfo( - options: CompilerOptions, - sys: TscCompileSystem | tscWatch.WatchedSystem, - originalReadCall?: System["readFile"], - ) { - const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options); - if (!buildInfoPath || !sys.writtenFiles!.has(toPathWithSystem(sys, buildInfoPath))) return; - if (!sys.fileExists(buildInfoPath)) return; - - const buildInfo = getBuildInfo(buildInfoPath, (originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!); - if (!buildInfo) return sys.writeFile(`${buildInfoPath}.baseline.txt`, "Error reading valid buildinfo file"); - generateBuildInfoProgramBaseline(sys, buildInfoPath, buildInfo); - - if (!outFile(options)) return; - const { jsFilePath, declarationFilePath } = getOutputPathsForBundle(options, /*forceDts*/ false); - const bundle = buildInfo.bundle; - if (!bundle || (!length(bundle.js && bundle.js.sections) && !length(bundle.dts && bundle.dts.sections))) return; - - // Write the baselines: - const baselineRecorder = new Harness.Compiler.WriterAggregator(); - generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.js, jsFilePath); - generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.dts, declarationFilePath); - baselineRecorder.Close(); - const text = baselineRecorder.lines.join("\r\n"); - sys.writeFile(`${buildInfoPath}.baseline.txt`, text); - } - interface VerifyTscEditDiscrepanciesInput { - index: number; - scenario: TestTscCompile["scenario"]; - subScenario: TestTscCompile["subScenario"]; - baselines: string[] | undefined; - commandLineArgs: TestTscCompile["commandLineArgs"]; - modifyFs: TestTscCompile["modifyFs"]; - editFs: TestTscEdit["modifyFs"]; - baseFs: vfs.FileSystem; - newSys: TscCompileSystem; - discrepancyExplanation: TestTscEdit["discrepancyExplanation"]; - } - function verifyTscEditDiscrepancies({ - index, scenario, subScenario, commandLineArgs, - discrepancyExplanation, baselines, - modifyFs, editFs, baseFs, newSys - }: VerifyTscEditDiscrepanciesInput): string[] | undefined { - const sys = testTscCompile({ - scenario, - subScenario, - fs: () => baseFs.makeReadonly(), - commandLineArgs, - modifyFs: fs => { - if (modifyFs) modifyFs(fs); - editFs(fs); - }, - disableUseFileVersionAsSignature: true, - }); - let headerAdded = false; - for (const outputFile of arrayFrom(sys.writtenFiles.keys())) { - const cleanBuildText = sys.readFile(outputFile); - const incrementalBuildText = newSys.readFile(outputFile); - if (isBuildInfoFile(outputFile)) { - // Check only presence and absence and not text as we will do that for readable baseline - if (!sys.fileExists(`${outputFile}.readable.baseline.txt`)) addBaseline(`Readable baseline not present in clean build:: File:: ${outputFile}`); - if (!newSys.fileExists(`${outputFile}.readable.baseline.txt`)) addBaseline(`Readable baseline not present in incremental build:: File:: ${outputFile}`); - verifyPresenceAbsence(incrementalBuildText, cleanBuildText, `Incremental and clean tsbuildinfo file presence differs:: File:: ${outputFile}`); - } - else if (!fileExtensionIs(outputFile, ".tsbuildinfo.readable.baseline.txt")) { - verifyTextEqual(incrementalBuildText, cleanBuildText, `File: ${outputFile}`); - } - else if (incrementalBuildText !== cleanBuildText) { - // Verify build info without affectedFilesPendingEmit - const { buildInfo: incrementalBuildInfo, readableBuildInfo: incrementalReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText); - const { buildInfo: cleanBuildInfo, readableBuildInfo: cleanReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText); - verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, `TsBuild info text without affectedFilesPendingEmit:: ${outputFile}::`); - // Verify file info sigantures +export function baselineBuildInfo( + options: ts.CompilerOptions, + sys: ts.TscCompileSystem | ts.tscWatch.WatchedSystem, + originalReadCall?: ts.System["readFile"], +) { + const buildInfoPath = ts.getTsBuildInfoEmitOutputFilePath(options); + if (!buildInfoPath || !sys.writtenFiles!.has(toPathWithSystem(sys, buildInfoPath))) return; + if (!sys.fileExists(buildInfoPath)) return; + + const buildInfo = ts.getBuildInfo(buildInfoPath, (originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!); + if (!buildInfo) return sys.writeFile(`${buildInfoPath}.baseline.txt`, "Error reading valid buildinfo file"); + generateBuildInfoProgramBaseline(sys, buildInfoPath, buildInfo); + + if (!ts.outFile(options)) return; + const { jsFilePath, declarationFilePath } = ts.getOutputPathsForBundle(options, /*forceDts*/ false); + const bundle = buildInfo.bundle; + if (!bundle || (!ts.length(bundle.js && bundle.js.sections) && !ts.length(bundle.dts && bundle.dts.sections))) return; + + // Write the baselines: + const baselineRecorder = new Harness.Compiler.WriterAggregator(); + generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.js, jsFilePath); + generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.dts, declarationFilePath); + baselineRecorder.Close(); + const text = baselineRecorder.lines.join("\r\n"); + sys.writeFile(`${buildInfoPath}.baseline.txt`, text); +} +interface VerifyTscEditDiscrepanciesInput { + index: number; + scenario: ts.TestTscCompile["scenario"]; + subScenario: ts.TestTscCompile["subScenario"]; + baselines: string[] | undefined; + commandLineArgs: ts.TestTscCompile["commandLineArgs"]; + modifyFs: ts.TestTscCompile["modifyFs"]; + editFs: TestTscEdit["modifyFs"]; + baseFs: vfs.FileSystem; + newSys: ts.TscCompileSystem; + discrepancyExplanation: TestTscEdit["discrepancyExplanation"]; +} +function verifyTscEditDiscrepancies({ + index, scenario, subScenario, commandLineArgs, + discrepancyExplanation, baselines, + modifyFs, editFs, baseFs, newSys +}: VerifyTscEditDiscrepanciesInput): string[] | undefined { + const sys = ts.testTscCompile({ + scenario, + subScenario, + fs: () => baseFs.makeReadonly(), + commandLineArgs, + modifyFs: fs => { + if (modifyFs) modifyFs(fs); + editFs(fs); + }, + disableUseFileVersionAsSignature: true, + }); + let headerAdded = false; + for (const outputFile of ts.arrayFrom(sys.writtenFiles.keys())) { + const cleanBuildText = sys.readFile(outputFile); + const incrementalBuildText = newSys.readFile(outputFile); + if (ts.isBuildInfoFile(outputFile)) { + // Check only presence and absence and not text as we will do that for readable baseline + if (!sys.fileExists(`${outputFile}.readable.baseline.txt`)) addBaseline(`Readable baseline not present in clean build:: File:: ${outputFile}`); + if (!newSys.fileExists(`${outputFile}.readable.baseline.txt`)) addBaseline(`Readable baseline not present in incremental build:: File:: ${outputFile}`); + verifyPresenceAbsence(incrementalBuildText, cleanBuildText, `Incremental and clean tsbuildinfo file presence differs:: File:: ${outputFile}`); + } + else if (!ts.fileExtensionIs(outputFile, ".tsbuildinfo.readable.baseline.txt")) { + verifyTextEqual(incrementalBuildText, cleanBuildText, `File: ${outputFile}`); + } + else if (incrementalBuildText !== cleanBuildText) { + // Verify build info without affectedFilesPendingEmit + const { buildInfo: incrementalBuildInfo, readableBuildInfo: incrementalReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText); + const { buildInfo: cleanBuildInfo, readableBuildInfo: cleanReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText); + verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, `TsBuild info text without affectedFilesPendingEmit:: ${outputFile}::`); + // Verify file info sigantures + verifyMapLike( + incrementalReadableBuildInfo?.program?.fileInfos as ReadableProgramMultiFileEmitBuildInfo["fileInfos"], + cleanReadableBuildInfo?.program?.fileInfos as ReadableProgramMultiFileEmitBuildInfo["fileInfos"], + (key, incrementalFileInfo, cleanFileInfo) => { + if (incrementalFileInfo.signature !== cleanFileInfo.signature && incrementalFileInfo.signature !== incrementalFileInfo.version) { + return [ + `Incremental signature is neither dts signature nor file version for File:: ${key}`, + `Incremental:: ${JSON.stringify(incrementalFileInfo, /*replacer*/ undefined, 2)}`, + `Clean:: ${JSON.stringify(cleanFileInfo, /*replacer*/ undefined, 2)}` + ]; + } + }, + `FileInfos:: File:: ${outputFile}` + ); + if (!isReadableProgramBundleEmitBuildInfo(incrementalReadableBuildInfo?.program)) { + ts.Debug.assert(!isReadableProgramBundleEmitBuildInfo(cleanReadableBuildInfo?.program)); + // Verify exportedModulesMap verifyMapLike( - incrementalReadableBuildInfo?.program?.fileInfos as ReadableProgramMultiFileEmitBuildInfo["fileInfos"], - cleanReadableBuildInfo?.program?.fileInfos as ReadableProgramMultiFileEmitBuildInfo["fileInfos"], - (key, incrementalFileInfo, cleanFileInfo) => { - if (incrementalFileInfo.signature !== cleanFileInfo.signature && incrementalFileInfo.signature !== incrementalFileInfo.version) { + incrementalReadableBuildInfo?.program?.exportedModulesMap, + cleanReadableBuildInfo?.program?.exportedModulesMap, + (key, incrementalReferenceSet, cleanReferenceSet) => { + if (!ts.arrayIsEqualTo(incrementalReferenceSet, cleanReferenceSet) && !ts.arrayIsEqualTo(incrementalReferenceSet, (incrementalReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key])) { return [ - `Incremental signature is neither dts signature nor file version for File:: ${key}`, - `Incremental:: ${JSON.stringify(incrementalFileInfo, /*replacer*/ undefined, 2)}`, - `Clean:: ${JSON.stringify(cleanFileInfo, /*replacer*/ undefined, 2)}` + `Incremental Reference set is neither from dts nor files reference map for File:: ${key}::`, + `Incremental:: ${JSON.stringify(incrementalReferenceSet, /*replacer*/ undefined, 2)}`, + `Clean:: ${JSON.stringify(cleanReferenceSet, /*replacer*/ undefined, 2)}`, + `IncrementalReferenceMap:: ${JSON.stringify((incrementalReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key], /*replacer*/ undefined, 2)}`, + `CleanReferenceMap:: ${JSON.stringify((cleanReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key], /*replacer*/ undefined, 2)}`, ]; } }, - `FileInfos:: File:: ${outputFile}` + `exportedModulesMap:: File:: ${outputFile}` ); - if (!isReadableProgramBundleEmitBuildInfo(incrementalReadableBuildInfo?.program)) { - Debug.assert(!isReadableProgramBundleEmitBuildInfo(cleanReadableBuildInfo?.program)); - // Verify exportedModulesMap - verifyMapLike( - incrementalReadableBuildInfo?.program?.exportedModulesMap, - cleanReadableBuildInfo?.program?.exportedModulesMap, - (key, incrementalReferenceSet, cleanReferenceSet) => { - if (!arrayIsEqualTo(incrementalReferenceSet, cleanReferenceSet) && !arrayIsEqualTo(incrementalReferenceSet, (incrementalReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key])) { - return [ - `Incremental Reference set is neither from dts nor files reference map for File:: ${key}::`, - `Incremental:: ${JSON.stringify(incrementalReferenceSet, /*replacer*/ undefined, 2)}`, - `Clean:: ${JSON.stringify(cleanReferenceSet, /*replacer*/ undefined, 2)}`, - `IncrementalReferenceMap:: ${JSON.stringify((incrementalReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key], /*replacer*/ undefined, 2)}`, - `CleanReferenceMap:: ${JSON.stringify((cleanReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).referencedMap![key], /*replacer*/ undefined, 2)}`, - ]; - } - }, - `exportedModulesMap:: File:: ${outputFile}` - ); - // Verify that incrementally pending affected file emit are in clean build since clean build can contain more files compared to incremental depending of noEmitOnError option - if (incrementalReadableBuildInfo?.program?.affectedFilesPendingEmit) { - if (cleanReadableBuildInfo?.program?.affectedFilesPendingEmit === undefined) { + // Verify that incrementally pending affected file emit are in clean build since clean build can contain more files compared to incremental depending of noEmitOnError option + if (incrementalReadableBuildInfo?.program?.affectedFilesPendingEmit) { + if (cleanReadableBuildInfo?.program?.affectedFilesPendingEmit === undefined) { + addBaseline( + `Incremental build contains affectedFilesPendingEmit, clean build does not have it: ${outputFile}::`, + `Incremental buildInfoText:: ${incrementalBuildText}`, + `Clean buildInfoText:: ${cleanBuildText}` + ); + } + let expectedIndex = 0; + incrementalReadableBuildInfo.program.affectedFilesPendingEmit.forEach(([actualFileOrArray]) => { + const actualFile = ts.isString(actualFileOrArray) ? actualFileOrArray : actualFileOrArray[0]; + expectedIndex = ts.findIndex( + (cleanReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).affectedFilesPendingEmit, + ([expectedFileOrArray]) => actualFile === (ts.isString(expectedFileOrArray) ? expectedFileOrArray : expectedFileOrArray[0]), + expectedIndex + ); + if (expectedIndex === -1) { addBaseline( - `Incremental build contains affectedFilesPendingEmit, clean build does not have it: ${outputFile}::`, + `Incremental build contains ${actualFile} file as pending emit, clean build does not have it: ${outputFile}::`, `Incremental buildInfoText:: ${incrementalBuildText}`, `Clean buildInfoText:: ${cleanBuildText}` ); } - let expectedIndex = 0; - incrementalReadableBuildInfo.program.affectedFilesPendingEmit.forEach(([actualFileOrArray]) => { - const actualFile = isString(actualFileOrArray) ? actualFileOrArray : actualFileOrArray[0]; - expectedIndex = findIndex( - (cleanReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).affectedFilesPendingEmit, - ([expectedFileOrArray]) => actualFile === (isString(expectedFileOrArray) ? expectedFileOrArray : expectedFileOrArray[0]), - expectedIndex - ); - if (expectedIndex === -1) { - addBaseline( - `Incremental build contains ${actualFile} file as pending emit, clean build does not have it: ${outputFile}::`, - `Incremental buildInfoText:: ${incrementalBuildText}`, - `Clean buildInfoText:: ${cleanBuildText}` - ); - } - expectedIndex++; - }); - } + expectedIndex++; + }); } } } - if (!headerAdded && discrepancyExplanation) addBaseline("*** Supplied discrepancy explanation but didnt file any difference"); - return baselines; + } + if (!headerAdded && discrepancyExplanation) addBaseline("*** Supplied discrepancy explanation but didnt file any difference"); + return baselines; - function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, message: string) { - if (incrementalText !== cleanText) writeNotEqual(incrementalText, cleanText, message); - } + function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, message: string) { + if (incrementalText !== cleanText) writeNotEqual(incrementalText, cleanText, message); + } - function verifyMapLike(incremental: MapLike | undefined, clean: MapLike | undefined, verifyValue: (key: string, incrementalValue: T, cleanValue: T) => string[] | undefined, message: string) { - verifyPresenceAbsence(incremental, clean, `Incremental and clean do not match:: ${message}`); - if (!incremental || !clean) return; - const incrementalMap = new Map(getEntries(incremental)); - const cleanMap = new Map(getEntries(clean)); - if (incrementalMap.size !== cleanMap.size) { + function verifyMapLike(incremental: ts.MapLike | undefined, clean: ts.MapLike | undefined, verifyValue: (key: string, incrementalValue: T, cleanValue: T) => string[] | undefined, message: string) { + verifyPresenceAbsence(incremental, clean, `Incremental and clean do not match:: ${message}`); + if (!incremental || !clean) return; + const incrementalMap = new ts.Map(ts.getEntries(incremental)); + const cleanMap = new ts.Map(ts.getEntries(clean)); + if (incrementalMap.size !== cleanMap.size) { + addBaseline( + `Incremental and clean size of maps do not match:: ${message}`, + `Incremental: ${JSON.stringify(incremental, /*replacer*/ undefined, 2)}`, + `Clean: ${JSON.stringify(clean, /*replacer*/ undefined, 2)}`, + ); + return; + } + cleanMap.forEach((cleanValue, key) => { + const incrementalValue = incrementalMap.get(key); + if (!incrementalValue) { addBaseline( - `Incremental and clean size of maps do not match:: ${message}`, + `Incremental does not contain ${key} which is present in clean:: ${message}`, `Incremental: ${JSON.stringify(incremental, /*replacer*/ undefined, 2)}`, `Clean: ${JSON.stringify(clean, /*replacer*/ undefined, 2)}`, ); - return; - } - cleanMap.forEach((cleanValue, key) => { - const incrementalValue = incrementalMap.get(key); - if (!incrementalValue) { - addBaseline( - `Incremental does not contain ${key} which is present in clean:: ${message}`, - `Incremental: ${JSON.stringify(incremental, /*replacer*/ undefined, 2)}`, - `Clean: ${JSON.stringify(clean, /*replacer*/ undefined, 2)}`, - ); - } - else { - const result = verifyValue(key, incrementalMap.get(key)!, cleanValue); - if (result) addBaseline(...result); - } - }); - } - - function verifyPresenceAbsence(actual: T | undefined, expected: T | undefined, message: string) { - if (expected === undefined) { - if (actual === undefined) return; } else { - if (actual !== undefined) return; + const result = verifyValue(key, incrementalMap.get(key)!, cleanValue); + if (result) addBaseline(...result); } - writeNotEqual(actual, expected, message); - } + }); + } - function writeNotEqual(actual: T | undefined, expected: T | undefined, message: string) { - addBaseline( - message, - "CleanBuild:", - isString(expected) ? expected : JSON.stringify(expected), - "IncrementalBuild:", - isString(actual) ? actual : JSON.stringify(actual), - ); + function verifyPresenceAbsence(actual: T | undefined, expected: T | undefined, message: string) { + if (expected === undefined) { + if (actual === undefined) return; + } + else { + if (actual !== undefined) return; } + writeNotEqual(actual, expected, message); + } - function addBaseline(...text: string[]) { - if (!baselines || !headerAdded) { - (baselines ||= []).push(`${index}:: ${subScenario}`, ...(discrepancyExplanation?.()|| ["*** Needs explanation"])); - headerAdded = true; - } - baselines.push(...text); + function writeNotEqual(actual: T | undefined, expected: T | undefined, message: string) { + addBaseline( + message, + "CleanBuild:", + ts.isString(expected) ? expected : JSON.stringify(expected), + "IncrementalBuild:", + ts.isString(actual) ? actual : JSON.stringify(actual), + ); + } + + function addBaseline(...text: string[]) { + if (!baselines || !headerAdded) { + (baselines ||= []).push(`${index}:: ${subScenario}`, ...(discrepancyExplanation?.()|| ["*** Needs explanation"])); + headerAdded = true; } + baselines.push(...text); } +} - function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): { - buildInfo: string | undefined; - readableBuildInfo?: ReadableBuildInfo; - } { - if (!text) return { buildInfo: text }; - const readableBuildInfo = JSON.parse(text) as ReadableBuildInfo; - let sanitizedFileInfos: MapLike | ReadableProgramBuildInfoFileInfo, "signature" | "original"> & { signature: undefined; original: undefined; }> | undefined; - if (readableBuildInfo.program?.fileInfos) { - sanitizedFileInfos = {}; - for (const id in readableBuildInfo.program.fileInfos) { - if (hasProperty(readableBuildInfo.program.fileInfos, id)) { - const info = readableBuildInfo.program.fileInfos[id]; - sanitizedFileInfos[id] = isString(info) ? info : { ...info, signature: undefined, original: undefined }; - } +function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): { + buildInfo: string | undefined; + readableBuildInfo?: ReadableBuildInfo; +} { + if (!text) return { buildInfo: text }; + const readableBuildInfo = JSON.parse(text) as ReadableBuildInfo; + let sanitizedFileInfos: ts.MapLike | ReadableProgramBuildInfoFileInfo, "signature" | "original"> & { signature: undefined; original: undefined; }> | undefined; + if (readableBuildInfo.program?.fileInfos) { + sanitizedFileInfos = {}; + for (const id in readableBuildInfo.program.fileInfos) { + if (ts.hasProperty(readableBuildInfo.program.fileInfos, id)) { + const info = readableBuildInfo.program.fileInfos[id]; + sanitizedFileInfos[id] = ts.isString(info) ? info : { ...info, signature: undefined, original: undefined }; } } - return { - buildInfo: JSON.stringify({ - ...readableBuildInfo, - program: readableBuildInfo.program && { - ...readableBuildInfo.program, - fileNames: undefined, - fileNamesList: undefined, - fileInfos: sanitizedFileInfos, - // Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter - options: { ...readableBuildInfo.program.options, noEmit: undefined }, - exportedModulesMap: undefined, - affectedFilesPendingEmit: undefined, - latestChangedDtsFile: readableBuildInfo.program.latestChangedDtsFile ? "FakeFileName" : undefined, - }, - size: undefined, // Size doesnt need to be equal - }, /*replacer*/ undefined, 2), - readableBuildInfo, - }; } + return { + buildInfo: JSON.stringify({ + ...readableBuildInfo, + program: readableBuildInfo.program && { + ...readableBuildInfo.program, + fileNames: undefined, + fileNamesList: undefined, + fileInfos: sanitizedFileInfos, + // Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter + options: { ...readableBuildInfo.program.options, noEmit: undefined }, + exportedModulesMap: undefined, + affectedFilesPendingEmit: undefined, + latestChangedDtsFile: readableBuildInfo.program.latestChangedDtsFile ? "FakeFileName" : undefined, + }, + size: undefined, // Size doesnt need to be equal + }, /*replacer*/ undefined, 2), + readableBuildInfo, + }; +} - export enum CleanBuildDescrepancy { - CleanFileTextDifferent, - CleanFilePresent, - } +export enum CleanBuildDescrepancy { + CleanFileTextDifferent, + CleanFilePresent, +} - export interface TestTscEdit { - modifyFs: (fs: vfs.FileSystem) => void; - subScenario: string; - commandLineArgs?: readonly string[]; - /** An array of lines to be printed in order when a discrepancy is detected */ - discrepancyExplanation?: () => readonly string[]; - } +export interface TestTscEdit { + modifyFs: (fs: vfs.FileSystem) => void; + subScenario: string; + commandLineArgs?: readonly string[]; + /** An array of lines to be printed in order when a discrepancy is detected */ + discrepancyExplanation?: () => readonly string[]; +} - export interface VerifyTscWithEditsInput extends TestTscCompile { - edits: TestTscEdit[]; - } +export interface VerifyTscWithEditsInput extends ts.TestTscCompile { + edits: TestTscEdit[]; +} - /** - * Verify non watch tsc invokcation after each edit - */ - export function verifyTscWithEdits({ - subScenario, fs, scenario, commandLineArgs, - baselineSourceMap, modifyFs, baselineReadFileCalls, baselinePrograms, - edits - }: VerifyTscWithEditsInput) { - describe(`tsc ${commandLineArgs.join(" ")} ${scenario}:: ${subScenario} serializedEdits`, () => { - let sys: TscCompileSystem; - let baseFs: vfs.FileSystem; - let editsSys: TscCompileSystem[]; - before(() => { - Debug.assert(!!edits.length, `${scenario}/${subScenario}:: No incremental scenarios, you probably want to use verifyTsc instead.`); - baseFs = fs().makeReadonly(); - sys = testTscCompile({ +/** + * Verify non watch tsc invokcation after each edit + */ +export function verifyTscWithEdits({ + subScenario, fs, scenario, commandLineArgs, + baselineSourceMap, modifyFs, baselineReadFileCalls, baselinePrograms, + edits +}: VerifyTscWithEditsInput) { + describe(`tsc ${commandLineArgs.join(" ")} ${scenario}:: ${subScenario} serializedEdits`, () => { + let sys: ts.TscCompileSystem; + let baseFs: vfs.FileSystem; + let editsSys: ts.TscCompileSystem[]; + before(() => { + ts.Debug.assert(!!edits.length, `${scenario}/${subScenario}:: No incremental scenarios, you probably want to use verifyTsc instead.`); + baseFs = fs().makeReadonly(); + sys = ts.testTscCompile({ + scenario, + subScenario, + fs: () => baseFs, + commandLineArgs, + modifyFs, + baselineSourceMap, + baselineReadFileCalls, + baselinePrograms + }); + edits.forEach(( + { modifyFs, subScenario: editScenario, commandLineArgs: editCommandLineArgs }, + index + ) => { + (editsSys || (editsSys = [])).push(ts.testTscCompile({ scenario, - subScenario, - fs: () => baseFs, - commandLineArgs, + subScenario: editScenario || subScenario, + diffWithInitial: true, + fs: () => index === 0 ? sys.vfs : editsSys[index - 1].vfs, + commandLineArgs: editCommandLineArgs || commandLineArgs, modifyFs, baselineSourceMap, baselineReadFileCalls, baselinePrograms + })); + }); + }); + after(() => { + baseFs = undefined!; + sys = undefined!; + editsSys = undefined!; + }); + ts.verifyTscBaseline(() => ({ + baseLine: () => { + const { file, text } = sys.baseLine(); + const texts: string[] = [text]; + editsSys.forEach((sys, index) => { + const incrementalScenario = edits[index]; + texts.push(""); + texts.push(`Change:: ${incrementalScenario.subScenario}`); + texts.push(sys.baseLine().text); }); - edits.forEach(( - { modifyFs, subScenario: editScenario, commandLineArgs: editCommandLineArgs }, - index - ) => { - (editsSys || (editsSys = [])).push(testTscCompile({ - scenario, - subScenario: editScenario || subScenario, - diffWithInitial: true, - fs: () => index === 0 ? sys.vfs : editsSys[index - 1].vfs, - commandLineArgs: editCommandLineArgs || commandLineArgs, - modifyFs, - baselineSourceMap, - baselineReadFileCalls, - baselinePrograms - })); + return { file, text: texts.join("\r\n") }; + } + })); + it("tsc invocation after edit and clean build correctness", () => { + let baselines: string[] | undefined; + for (let index = 0; index < edits.length; index++) { + baselines = verifyTscEditDiscrepancies({ + index, + scenario, + subScenario: edits[index].subScenario, + baselines, + baseFs, + newSys: editsSys[index], + commandLineArgs: edits[index].commandLineArgs || commandLineArgs, + discrepancyExplanation: edits[index].discrepancyExplanation, + editFs: fs => { + for (let i = 0; i <= index; i++) { + edits[i].modifyFs(fs); + } + }, + modifyFs }); - }); - after(() => { - baseFs = undefined!; - sys = undefined!; - editsSys = undefined!; - }); - verifyTscBaseline(() => ({ - baseLine: () => { - const { file, text } = sys.baseLine(); - const texts: string[] = [text]; - editsSys.forEach((sys, index) => { - const incrementalScenario = edits[index]; - texts.push(""); - texts.push(`Change:: ${incrementalScenario.subScenario}`); - texts.push(sys.baseLine().text); - }); - return { file, text: texts.join("\r\n") }; - } - })); - it("tsc invocation after edit and clean build correctness", () => { - let baselines: string[] | undefined; - for (let index = 0; index < edits.length; index++) { - baselines = verifyTscEditDiscrepancies({ - index, - scenario, - subScenario: edits[index].subScenario, - baselines, - baseFs, - newSys: editsSys[index], - commandLineArgs: edits[index].commandLineArgs || commandLineArgs, - discrepancyExplanation: edits[index].discrepancyExplanation, - editFs: fs => { - for (let i = 0; i <= index; i++) { - edits[i].modifyFs(fs); - } - }, - modifyFs - }); - } - Harness.Baseline.runBaseline( - `${isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${subScenario.split(" ").join("-")}-discrepancies.js`, - baselines ? baselines.join("\r\n") : null // eslint-disable-line no-null/no-null - ); - }); + } + Harness.Baseline.runBaseline( + `${ts.isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${subScenario.split(" ").join("-")}-discrepancies.js`, + baselines ? baselines.join("\r\n") : null // eslint-disable-line no-null/no-null + ); }); - } + }); +} - export function enableStrict(fs: vfs.FileSystem, path: string) { - replaceText(fs, path, `"strict": false`, `"strict": true`); - } +export function enableStrict(fs: vfs.FileSystem, path: string) { + replaceText(fs, path, `"strict": false`, `"strict": true`); +} - export function addTestPrologue(fs: vfs.FileSystem, path: string, prologue: string) { - prependText(fs, path, `${prologue} +export function addTestPrologue(fs: vfs.FileSystem, path: string, prologue: string) { + prependText(fs, path, `${prologue} `); - } +} - export function addShebang(fs: vfs.FileSystem, project: string, file: string) { - prependText(fs, `src/${project}/${file}.ts`, `#!someshebang ${project} ${file} +export function addShebang(fs: vfs.FileSystem, project: string, file: string) { + prependText(fs, `src/${project}/${file}.ts`, `#!someshebang ${project} ${file} `); - } +} - export function restContent(project: string, file: string) { - return `function for${project}${file}Rest() { +export function restContent(project: string, file: string) { + return `function for${project}${file}Rest() { const { b, ...rest } = { a: 10, b: 30, yy: 30 }; }`; - } +} - function nonrestContent(project: string, file: string) { - return `function for${project}${file}Rest() { }`; - } +function nonrestContent(project: string, file: string) { + return `function for${project}${file}Rest() { }`; +} - export function addRest(fs: vfs.FileSystem, project: string, file: string) { - appendText(fs, `src/${project}/${file}.ts`, restContent(project, file)); - } +export function addRest(fs: vfs.FileSystem, project: string, file: string) { + appendText(fs, `src/${project}/${file}.ts`, restContent(project, file)); +} - export function removeRest(fs: vfs.FileSystem, project: string, file: string) { - replaceText(fs, `src/${project}/${file}.ts`, restContent(project, file), nonrestContent(project, file)); - } +export function removeRest(fs: vfs.FileSystem, project: string, file: string) { + replaceText(fs, `src/${project}/${file}.ts`, restContent(project, file), nonrestContent(project, file)); +} - export function addStubFoo(fs: vfs.FileSystem, project: string, file: string) { - appendText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file)); - } +export function addStubFoo(fs: vfs.FileSystem, project: string, file: string) { + appendText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file)); +} - export function changeStubToRest(fs: vfs.FileSystem, project: string, file: string) { - replaceText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file), restContent(project, file)); - } +export function changeStubToRest(fs: vfs.FileSystem, project: string, file: string) { + replaceText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file), restContent(project, file)); +} - export function addSpread(fs: vfs.FileSystem, project: string, file: string) { - const path = `src/${project}/${file}.ts`; - const content = fs.readFileSync(path, "utf8"); - fs.writeFileSync(path, `${content} +export function addSpread(fs: vfs.FileSystem, project: string, file: string) { + const path = `src/${project}/${file}.ts`; + const content = fs.readFileSync(path, "utf8"); + fs.writeFileSync(path, `${content} function ${project}${file}Spread(...b: number[]) { } const ${project}${file}_ar = [20, 30]; ${project}${file}Spread(10, ...${project}${file}_ar);`); - replaceText(fs, `src/${project}/tsconfig.json`, `"strict": false,`, `"strict": false, + replaceText(fs, `src/${project}/tsconfig.json`, `"strict": false,`, `"strict": false, "downlevelIteration": true,`); - } +} - export function getTripleSlashRef(project: string) { - return `/src/${project}/tripleRef.d.ts`; - } +export function getTripleSlashRef(project: string) { + return `/src/${project}/tripleRef.d.ts`; +} - export function addTripleSlashRef(fs: vfs.FileSystem, project: string, file: string) { - fs.writeFileSync(getTripleSlashRef(project), `declare class ${project}${file} { }`); - prependText(fs, `src/${project}/${file}.ts`, `/// +export function addTripleSlashRef(fs: vfs.FileSystem, project: string, file: string) { + fs.writeFileSync(getTripleSlashRef(project), `declare class ${project}${file} { }`); + prependText(fs, `src/${project}/${file}.ts`, `/// const ${file}Const = new ${project}${file}(); `); - } } diff --git a/src/testRunner/unittests/tsbuild/inferredTypeFromTransitiveModule.ts b/src/testRunner/unittests/tsbuild/inferredTypeFromTransitiveModule.ts index 2f8cc57d11175..4f3db7518f649 100644 --- a/src/testRunner/unittests/tsbuild/inferredTypeFromTransitiveModule.ts +++ b/src/testRunner/unittests/tsbuild/inferredTypeFromTransitiveModule.ts @@ -1,89 +1,90 @@ -namespace ts { - describe("unittests:: tsbuild:: inferredTypeFromTransitiveModule::", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/inferredTypeFromTransitiveModule"); - }); - after(() => { - projFs = undefined!; - }); +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; - verifyTscWithEdits({ - scenario: "inferredTypeFromTransitiveModule", - subScenario: "inferred type from transitive module", - fs: () => projFs, - commandLineArgs: ["--b", "/src", "--verbose"], - edits: [ - { - subScenario: "incremental-declaration-changes", - modifyFs: changeBarParam, - }, - { - subScenario: "incremental-declaration-changes", - modifyFs: changeBarParamBack, - }, - ], - }); +describe("unittests:: tsbuild:: inferredTypeFromTransitiveModule::", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = ts.loadProjectFromDisk("tests/projects/inferredTypeFromTransitiveModule"); + }); + after(() => { + projFs = undefined!; + }); - verifyTscWithEdits({ - subScenario: "inferred type from transitive module with isolatedModules", - fs: () => projFs, - scenario: "inferredTypeFromTransitiveModule", - commandLineArgs: ["--b", "/src", "--verbose"], - modifyFs: changeToIsolatedModules, - edits: [ - { - subScenario: "incremental-declaration-changes", - modifyFs: changeBarParam - }, - { - subScenario: "incremental-declaration-changes", - modifyFs: changeBarParamBack, - }, - ] - }); + ts.verifyTscWithEdits({ + scenario: "inferredTypeFromTransitiveModule", + subScenario: "inferred type from transitive module", + fs: () => projFs, + commandLineArgs: ["--b", "/src", "--verbose"], + edits: [ + { + subScenario: "incremental-declaration-changes", + modifyFs: changeBarParam, + }, + { + subScenario: "incremental-declaration-changes", + modifyFs: changeBarParamBack, + }, + ], + }); + + ts.verifyTscWithEdits({ + subScenario: "inferred type from transitive module with isolatedModules", + fs: () => projFs, + scenario: "inferredTypeFromTransitiveModule", + commandLineArgs: ["--b", "/src", "--verbose"], + modifyFs: changeToIsolatedModules, + edits: [ + { + subScenario: "incremental-declaration-changes", + modifyFs: changeBarParam + }, + { + subScenario: "incremental-declaration-changes", + modifyFs: changeBarParamBack, + }, + ] + }); - verifyTscWithEdits({ - scenario: "inferredTypeFromTransitiveModule", - subScenario: "reports errors in files affected by change in signature with isolatedModules", - fs: () => projFs, - commandLineArgs: ["--b", "/src", "--verbose"], - modifyFs: fs => { - changeToIsolatedModules(fs); - appendText(fs, "/src/lazyIndex.ts", ` + ts.verifyTscWithEdits({ + scenario: "inferredTypeFromTransitiveModule", + subScenario: "reports errors in files affected by change in signature with isolatedModules", + fs: () => projFs, + commandLineArgs: ["--b", "/src", "--verbose"], + modifyFs: fs => { + changeToIsolatedModules(fs); + ts.appendText(fs, "/src/lazyIndex.ts", ` import { default as bar } from './bar'; bar("hello");`); + }, + edits: [ + { + subScenario: "incremental-declaration-changes", + modifyFs: changeBarParam + }, + { + subScenario: "incremental-declaration-changes", + modifyFs: changeBarParamBack, + }, + { + subScenario: "incremental-declaration-changes", + modifyFs: changeBarParam }, - edits: [ - { - subScenario: "incremental-declaration-changes", - modifyFs: changeBarParam - }, - { - subScenario: "incremental-declaration-changes", - modifyFs: changeBarParamBack, - }, - { - subScenario: "incremental-declaration-changes", - modifyFs: changeBarParam - }, - { - subScenario: "Fix Error", - modifyFs: fs => replaceText(fs, "/src/lazyIndex.ts", `bar("hello")`, "bar()") - }, - ] - }); + { + subScenario: "Fix Error", + modifyFs: fs => ts.replaceText(fs, "/src/lazyIndex.ts", `bar("hello")`, "bar()") + }, + ] }); +}); - function changeToIsolatedModules(fs: vfs.FileSystem) { - replaceText(fs, "/src/tsconfig.json", `"incremental": true`, `"incremental": true, "isolatedModules": true`); - } +function changeToIsolatedModules(fs: vfs.FileSystem) { + ts.replaceText(fs, "/src/tsconfig.json", `"incremental": true`, `"incremental": true, "isolatedModules": true`); +} - function changeBarParam(fs: vfs.FileSystem) { - replaceText(fs, "/src/bar.ts", "param: string", ""); - } +function changeBarParam(fs: vfs.FileSystem) { + ts.replaceText(fs, "/src/bar.ts", "param: string", ""); +} - function changeBarParamBack(fs: vfs.FileSystem) { - replaceText(fs, "/src/bar.ts", "foobar()", "foobar(param: string)"); - } +function changeBarParamBack(fs: vfs.FileSystem) { + ts.replaceText(fs, "/src/bar.ts", "foobar()", "foobar(param: string)"); } diff --git a/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts b/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts index 6a0f6d5bc156c..499ca98a60464 100644 --- a/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts +++ b/src/testRunner/unittests/tsbuild/javascriptProjectEmit.ts @@ -1,17 +1,19 @@ -namespace ts { - describe("unittests:: tsbuild:: javascriptProjectEmit::", () => { - verifyTsc({ - scenario: "javascriptProjectEmit", - subScenario: `loads js-based projects and emits them correctly`, - fs: () => loadProjectFromFiles({ - "/src/common/nominal.js": Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; + +describe("unittests:: tsbuild:: javascriptProjectEmit::", () => { + ts.verifyTsc({ + scenario: "javascriptProjectEmit", + subScenario: `loads js-based projects and emits them correctly`, + fs: () => ts.loadProjectFromFiles({ + "/src/common/nominal.js": Utils.dedent` /** * @template T, Name * @typedef {T & {[Symbol.species]: Name}} Nominal */ module.exports = {}; `, - "/src/common/tsconfig.json": Utils.dedent` + "/src/common/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -19,14 +21,14 @@ namespace ts { }, "include": ["nominal.js"] }`, - "/src/sub-project/index.js": Utils.dedent` + "/src/sub-project/index.js": Utils.dedent` import { Nominal } from '../common/nominal'; /** * @typedef {Nominal} MyNominal */ `, - "/src/sub-project/tsconfig.json": Utils.dedent` + "/src/sub-project/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -37,7 +39,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/sub-project-2/index.js": Utils.dedent` + "/src/sub-project-2/index.js": Utils.dedent` import { MyNominal } from '../sub-project/index'; const variable = { @@ -51,7 +53,7 @@ namespace ts { return 'key'; } `, - "/src/sub-project-2/tsconfig.json": Utils.dedent` + "/src/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -62,7 +64,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/tsconfig.json": Utils.dedent` + "/src/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true @@ -73,7 +75,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": Utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, @@ -84,21 +86,21 @@ namespace ts { "declaration": true } }`, - }, symbolLibContent), - commandLineArgs: ["-b", "/src"] - }); + }, ts.symbolLibContent), + commandLineArgs: ["-b", "/src"] + }); - verifyTscWithEdits({ - scenario: "javascriptProjectEmit", - subScenario: `modifies outfile js projects and concatenates them correctly`, - fs: () => loadProjectFromFiles({ - "/src/common/nominal.js": Utils.dedent` + ts.verifyTscWithEdits({ + scenario: "javascriptProjectEmit", + subScenario: `modifies outfile js projects and concatenates them correctly`, + fs: () => ts.loadProjectFromFiles({ + "/src/common/nominal.js": Utils.dedent` /** * @template T, Name * @typedef {T & {[Symbol.species]: Name}} Nominal */ `, - "/src/common/tsconfig.json": Utils.dedent` + "/src/common/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -108,13 +110,13 @@ namespace ts { }, "include": ["nominal.js"] }`, - "/src/sub-project/index.js": Utils.dedent` + "/src/sub-project/index.js": Utils.dedent` /** * @typedef {Nominal} MyNominal */ const c = /** @type {*} */(null); `, - "/src/sub-project/tsconfig.json": Utils.dedent` + "/src/sub-project/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -127,7 +129,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/sub-project-2/index.js": Utils.dedent` + "/src/sub-project-2/index.js": Utils.dedent` const variable = { key: /** @type {MyNominal} */('value'), }; @@ -139,7 +141,7 @@ namespace ts { return 'key'; } `, - "/src/sub-project-2/tsconfig.json": Utils.dedent` + "/src/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -152,7 +154,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/tsconfig.json": Utils.dedent` + "/src/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true, @@ -164,7 +166,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": Utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, @@ -174,27 +176,27 @@ namespace ts { "declaration": true } }`, - }, symbolLibContent), - commandLineArgs: ["-b", "/src"], - edits: [{ - subScenario: "incremental-declaration-doesnt-change", - modifyFs: fs => replaceText(fs, "/src/sub-project/index.js", "null", "undefined") - }] - }); + }, ts.symbolLibContent), + commandLineArgs: ["-b", "/src"], + edits: [{ + subScenario: "incremental-declaration-doesnt-change", + modifyFs: fs => ts.replaceText(fs, "/src/sub-project/index.js", "null", "undefined") + }] + }); - verifyTsc({ - scenario: "javascriptProjectEmit", - subScenario: `loads js-based projects with non-moved json files and emits them correctly`, - fs: () => loadProjectFromFiles({ - "/src/common/obj.json": Utils.dedent` + ts.verifyTsc({ + scenario: "javascriptProjectEmit", + subScenario: `loads js-based projects with non-moved json files and emits them correctly`, + fs: () => ts.loadProjectFromFiles({ + "/src/common/obj.json": Utils.dedent` { "val": 42 }`, - "/src/common/index.ts": Utils.dedent` + "/src/common/index.ts": Utils.dedent` import x = require("./obj.json"); export = x; `, - "/src/common/tsconfig.json": Utils.dedent` + "/src/common/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -203,12 +205,12 @@ namespace ts { }, "include": ["index.ts", "obj.json"] }`, - "/src/sub-project/index.js": Utils.dedent` + "/src/sub-project/index.js": Utils.dedent` import mod from '../common'; export const m = mod; `, - "/src/sub-project/tsconfig.json": Utils.dedent` + "/src/sub-project/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -219,7 +221,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/sub-project-2/index.js": Utils.dedent` + "/src/sub-project-2/index.js": Utils.dedent` import { m } from '../sub-project/index'; const variable = { @@ -230,7 +232,7 @@ namespace ts { return variable; } `, - "/src/sub-project-2/tsconfig.json": Utils.dedent` + "/src/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../tsconfig.base.json", "compilerOptions": { @@ -241,7 +243,7 @@ namespace ts { ], "include": ["./index.js"] }`, - "/src/tsconfig.json": Utils.dedent` + "/src/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true @@ -252,7 +254,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": Utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, @@ -265,8 +267,7 @@ namespace ts { "declaration": true } }`, - }, symbolLibContent), - commandLineArgs: ["-b", "/src"] - }); + }, ts.symbolLibContent), + commandLineArgs: ["-b", "/src"] }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/lateBoundSymbol.ts b/src/testRunner/unittests/tsbuild/lateBoundSymbol.ts index aeb075f5ab501..417be59e441e5 100644 --- a/src/testRunner/unittests/tsbuild/lateBoundSymbol.ts +++ b/src/testRunner/unittests/tsbuild/lateBoundSymbol.ts @@ -1,20 +1,20 @@ -namespace ts { - describe("unittests:: tsbuild:: lateBoundSymbol:: interface is merged and contains late bound member", () => { - verifyTscWithEdits({ - subScenario: "interface is merged and contains late bound member", - fs: () => loadProjectFromDisk("tests/projects/lateBoundSymbol"), - scenario: "lateBoundSymbol", - commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], - edits: [ - { - subScenario: "incremental-declaration-doesnt-change", - modifyFs: fs => replaceText(fs, "/src/src/main.ts", "const x = 10;", ""), - }, - { - subScenario: "incremental-declaration-doesnt-change", - modifyFs: fs => appendText(fs, "/src/src/main.ts", "const x = 10;"), - }, - ] - }); +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsbuild:: lateBoundSymbol:: interface is merged and contains late bound member", () => { + ts.verifyTscWithEdits({ + subScenario: "interface is merged and contains late bound member", + fs: () => ts.loadProjectFromDisk("tests/projects/lateBoundSymbol"), + scenario: "lateBoundSymbol", + commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"], + edits: [ + { + subScenario: "incremental-declaration-doesnt-change", + modifyFs: fs => ts.replaceText(fs, "/src/src/main.ts", "const x = 10;", ""), + }, + { + subScenario: "incremental-declaration-doesnt-change", + modifyFs: fs => ts.appendText(fs, "/src/src/main.ts", "const x = 10;"), + }, + ] }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/moduleResolution.ts b/src/testRunner/unittests/tsbuild/moduleResolution.ts index 772a30262a042..496131f2c45a0 100644 --- a/src/testRunner/unittests/tsbuild/moduleResolution.ts +++ b/src/testRunner/unittests/tsbuild/moduleResolution.ts @@ -1,121 +1,122 @@ -namespace ts.tscWatch { - describe("unittests:: tsbuild:: moduleResolution:: handles the modules and options from referenced project correctly", () => { - function sys(optionsToExtend?: CompilerOptions) { - return createWatchedSystem([ - { - path: `${projectRoot}/packages/pkg1/index.ts`, - content: Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; + +describe("unittests:: tsbuild:: moduleResolution:: handles the modules and options from referenced project correctly", () => { + function sys(optionsToExtend?: ts.CompilerOptions) { + return ts.tscWatch.createWatchedSystem([ + { + path: `${ts.tscWatch.projectRoot}/packages/pkg1/index.ts`, + content: Utils.dedent` import type { TheNum } from 'pkg2' export const theNum: TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { outDir: "build", ...optionsToExtend }, - references: [{ path: "../pkg2" }] - }) - }, - { - path: `${projectRoot}/packages/pkg2/const.ts`, - content: `export type TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg2/index.ts`, - content: `export type { TheNum } from 'const';` - }, - { - path: `${projectRoot}/packages/pkg2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "build", - baseUrl: ".", - ...optionsToExtend - } - }) - }, - { - path: `${projectRoot}/packages/pkg2/package.json`, - content: JSON.stringify({ - name: "pkg2", - version: "1.0.0", - main: "build/index.js", - }) - }, - { - path: `${projectRoot}/node_modules/pkg2`, - symLink: `${projectRoot}/packages/pkg2`, - }, - libFile - ], { currentDirectory: projectRoot }); - } + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { outDir: "build", ...optionsToExtend }, + references: [{ path: "../pkg2" }] + }) + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/const.ts`, + content: `export type TheNum = 42;` + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/index.ts`, + content: `export type { TheNum } from 'const';` + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "build", + baseUrl: ".", + ...optionsToExtend + } + }) + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, + content: JSON.stringify({ + name: "pkg2", + version: "1.0.0", + main: "build/index.js", + }) + }, + { + path: `${ts.tscWatch.projectRoot}/node_modules/pkg2`, + symLink: `${ts.tscWatch.projectRoot}/packages/pkg2`, + }, + ts.tscWatch.libFile + ], { currentDirectory: ts.tscWatch.projectRoot }); + } - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: `resolves specifier in output declaration file from referenced project correctly`, - sys, - commandLineArgs: ["-b", "packages/pkg1", "--verbose", "--traceResolution"], - changes: emptyArray - }); + ts.tscWatch.verifyTscWatch({ + scenario: "moduleResolution", + subScenario: `resolves specifier in output declaration file from referenced project correctly`, + sys, + commandLineArgs: ["-b", "packages/pkg1", "--verbose", "--traceResolution"], + changes: ts.emptyArray + }); - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: `resolves specifier in output declaration file from referenced project correctly with preserveSymlinks`, - sys: () => sys({ preserveSymlinks: true }), - commandLineArgs: ["-b", "packages/pkg1", "--verbose", "--traceResolution"], - changes: emptyArray - }); + ts.tscWatch.verifyTscWatch({ + scenario: "moduleResolution", + subScenario: `resolves specifier in output declaration file from referenced project correctly with preserveSymlinks`, + sys: () => sys({ preserveSymlinks: true }), + commandLineArgs: ["-b", "packages/pkg1", "--verbose", "--traceResolution"], + changes: ts.emptyArray + }); - verifyTsc({ - scenario: "moduleResolution", - subScenario: `type reference resolution uses correct options for different resolution options referenced project`, - fs: () => loadProjectFromFiles({ - "/src/packages/pkg1_index.ts": `export const theNum: TheNum = "type1";`, - "/src/packages/pkg1.tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true, typeRoots: ["./typeroot1"] }, - files: ["./pkg1_index.ts"] - }), - "/src/packages/typeroot1/sometype/index.d.ts": Utils.dedent`declare type TheNum = "type1";`, - "/src/packages/pkg2_index.ts": `export const theNum: TheNum2 = "type2";`, - "/src/packages/pkg2.tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true, typeRoots: ["./typeroot2"] }, - files: ["./pkg2_index.ts"] - }), - "/src/packages/typeroot2/sometype/index.d.ts": Utils.dedent`declare type TheNum2 = "type2";`, + ts.verifyTsc({ + scenario: "moduleResolution", + subScenario: `type reference resolution uses correct options for different resolution options referenced project`, + fs: () => ts.loadProjectFromFiles({ + "/src/packages/pkg1_index.ts": `export const theNum: TheNum = "type1";`, + "/src/packages/pkg1.tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true, typeRoots: ["./typeroot1"] }, + files: ["./pkg1_index.ts"] + }), + "/src/packages/typeroot1/sometype/index.d.ts": Utils.dedent`declare type TheNum = "type1";`, + "/src/packages/pkg2_index.ts": `export const theNum: TheNum2 = "type2";`, + "/src/packages/pkg2.tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true, typeRoots: ["./typeroot2"] }, + files: ["./pkg2_index.ts"] }), - commandLineArgs: ["-b", "/src/packages/pkg1.tsconfig.json", "/src/packages/pkg2.tsconfig.json", "--verbose", "--traceResolution"], - }); + "/src/packages/typeroot2/sometype/index.d.ts": Utils.dedent`declare type TheNum2 = "type2";`, + }), + commandLineArgs: ["-b", "/src/packages/pkg1.tsconfig.json", "/src/packages/pkg2.tsconfig.json", "--verbose", "--traceResolution"], }); +}); - describe("unittests:: tsbuild:: moduleResolution:: impliedNodeFormat differs between projects for shared file", () => { - verifyTscWithEdits({ - scenario: "moduleResolution", - subScenario: "impliedNodeFormat differs between projects for shared file", - fs: () => loadProjectFromFiles({ - "/src/projects/a/src/index.ts": "", - "/src/projects/a/tsconfig.json": JSON.stringify({ - compilerOptions: { strict: true } - }), - "/src/projects/b/src/index.ts": Utils.dedent` +describe("unittests:: tsbuild:: moduleResolution:: impliedNodeFormat differs between projects for shared file", () => { + ts.verifyTscWithEdits({ + scenario: "moduleResolution", + subScenario: "impliedNodeFormat differs between projects for shared file", + fs: () => ts.loadProjectFromFiles({ + "/src/projects/a/src/index.ts": "", + "/src/projects/a/tsconfig.json": JSON.stringify({ + compilerOptions: { strict: true } + }), + "/src/projects/b/src/index.ts": Utils.dedent` import pg from "pg"; pg.foo(); `, - "/src/projects/b/tsconfig.json": JSON.stringify({ - compilerOptions: { strict: true, module: "node16" } - }), - "/src/projects/b/package.json": JSON.stringify({ - name: "b", - type: "module" - }), - "/src/projects/node_modules/@types/pg/index.d.ts": "export function foo(): void;", - "/src/projects/node_modules/@types/pg/package.json": JSON.stringify({ - name: "@types/pg", - types: "index.d.ts", - }), + "/src/projects/b/tsconfig.json": JSON.stringify({ + compilerOptions: { strict: true, module: "node16" } + }), + "/src/projects/b/package.json": JSON.stringify({ + name: "b", + type: "module" + }), + "/src/projects/node_modules/@types/pg/index.d.ts": "export function foo(): void;", + "/src/projects/node_modules/@types/pg/package.json": JSON.stringify({ + name: "@types/pg", + types: "index.d.ts", }), - modifyFs: fs => fs.writeFileSync("/lib/lib.es2022.full.d.ts", libFile.content), - commandLineArgs: ["-b", "/src/projects/a", "/src/projects/b", "--verbose", "--traceResolution", "--explainFiles"], - edits: noChangeOnlyRuns - }); + }), + modifyFs: fs => fs.writeFileSync("/lib/lib.es2022.full.d.ts", ts.tscWatch.libFile.content), + commandLineArgs: ["-b", "/src/projects/a", "/src/projects/b", "--verbose", "--traceResolution", "--explainFiles"], + edits: ts.noChangeOnlyRuns }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts b/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts index d8caa8997e784..04d3f714a6c46 100644 --- a/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts +++ b/src/testRunner/unittests/tsbuild/moduleSpecifiers.ts @@ -1,16 +1,18 @@ -namespace ts { - // https://github.com/microsoft/TypeScript/issues/31696 - describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers to referenced projects resolve correctly", () => { - verifyTsc({ - scenario: "moduleSpecifiers", - subScenario: `synthesized module specifiers resolve correctly`, - fs: () => loadProjectFromFiles({ - "/src/solution/common/nominal.ts": Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; + +// https://github.com/microsoft/TypeScript/issues/31696 +describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers to referenced projects resolve correctly", () => { + ts.verifyTsc({ + scenario: "moduleSpecifiers", + subScenario: `synthesized module specifiers resolve correctly`, + fs: () => ts.loadProjectFromFiles({ + "/src/solution/common/nominal.ts": Utils.dedent` export declare type Nominal = T & { [Symbol.species]: Name; }; `, - "/src/solution/common/tsconfig.json": Utils.dedent` + "/src/solution/common/tsconfig.json": Utils.dedent` { "extends": "../../tsconfig.base.json", "compilerOptions": { @@ -18,12 +20,12 @@ namespace ts { }, "include": ["nominal.ts"] }`, - "/src/solution/sub-project/index.ts": Utils.dedent` + "/src/solution/sub-project/index.ts": Utils.dedent` import { Nominal } from '../common/nominal'; export type MyNominal = Nominal; `, - "/src/solution/sub-project/tsconfig.json": Utils.dedent` + "/src/solution/sub-project/tsconfig.json": Utils.dedent` { "extends": "../../tsconfig.base.json", "compilerOptions": { @@ -34,7 +36,7 @@ namespace ts { ], "include": ["./index.ts"] }`, - "/src/solution/sub-project-2/index.ts": Utils.dedent` + "/src/solution/sub-project-2/index.ts": Utils.dedent` import { MyNominal } from '../sub-project/index'; const variable = { @@ -45,7 +47,7 @@ namespace ts { return 'key'; } `, - "/src/solution/sub-project-2/tsconfig.json": Utils.dedent` + "/src/solution/sub-project-2/tsconfig.json": Utils.dedent` { "extends": "../../tsconfig.base.json", "compilerOptions": { @@ -56,7 +58,7 @@ namespace ts { ], "include": ["./index.ts"] }`, - "/src/solution/tsconfig.json": Utils.dedent` + "/src/solution/tsconfig.json": Utils.dedent` { "compilerOptions": { "composite": true @@ -67,7 +69,7 @@ namespace ts { ], "include": [] }`, - "/src/tsconfig.base.json": Utils.dedent` + "/src/tsconfig.base.json": Utils.dedent` { "compilerOptions": { "skipLibCheck": true, @@ -75,7 +77,7 @@ namespace ts { "outDir": "lib", } }`, - "/src/tsconfig.json": Utils.dedent`{ + "/src/tsconfig.json": Utils.dedent`{ "compilerOptions": { "composite": true }, @@ -84,35 +86,35 @@ namespace ts { ], "include": [] }` - }, symbolLibContent), - commandLineArgs: ["-b", "/src", "--verbose"] - }); + }, ts.symbolLibContent), + commandLineArgs: ["-b", "/src", "--verbose"] }); +}); - // https://github.com/microsoft/TypeScript/issues/44434 but with `module: node16`, some `exports` maps blocking direct access, and no `baseUrl` - describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers across referenced projects resolve correctly", () => { - verifyTsc({ - scenario: "moduleSpecifiers", - subScenario: `synthesized module specifiers across projects resolve correctly`, - fs: () => loadProjectFromFiles({ - "/src/src-types/index.ts": Utils.dedent` +// https://github.com/microsoft/TypeScript/issues/44434 but with `module: node16`, some `exports` maps blocking direct access, and no `baseUrl` +describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers across referenced projects resolve correctly", () => { + ts.verifyTsc({ + scenario: "moduleSpecifiers", + subScenario: `synthesized module specifiers across projects resolve correctly`, + fs: () => ts.loadProjectFromFiles({ + "/src/src-types/index.ts": Utils.dedent` export * from './dogconfig.js';`, - "/src/src-types/dogconfig.ts": Utils.dedent` + "/src/src-types/dogconfig.ts": Utils.dedent` export interface DogConfig { name: string; }`, - "/src/src-dogs/index.ts": Utils.dedent` + "/src/src-dogs/index.ts": Utils.dedent` export * from 'src-types'; export * from './lassie/lassiedog.js'; `, - "/src/src-dogs/dogconfig.ts": Utils.dedent` + "/src/src-dogs/dogconfig.ts": Utils.dedent` import { DogConfig } from 'src-types'; export const DOG_CONFIG: DogConfig = { name: 'Default dog', }; `, - "/src/src-dogs/dog.ts": Utils.dedent` + "/src/src-dogs/dog.ts": Utils.dedent` import { DogConfig } from 'src-types'; import { DOG_CONFIG } from './dogconfig.js'; @@ -123,7 +125,7 @@ namespace ts { } } `, - "/src/src-dogs/lassie/lassiedog.ts": Utils.dedent` + "/src/src-dogs/lassie/lassiedog.ts": Utils.dedent` import { Dog } from '../dog.js'; import { LASSIE_CONFIG } from './lassieconfig.js'; @@ -131,29 +133,29 @@ namespace ts { protected static getDogConfig = () => LASSIE_CONFIG; } `, - "/src/src-dogs/lassie/lassieconfig.ts": Utils.dedent` + "/src/src-dogs/lassie/lassieconfig.ts": Utils.dedent` import { DogConfig } from 'src-types'; export const LASSIE_CONFIG: DogConfig = { name: 'Lassie' }; `, - "/src/tsconfig-base.json": Utils.dedent` + "/src/tsconfig-base.json": Utils.dedent` { "compilerOptions": { "declaration": true, "module": "node16" } }`, - "/src/src-types/package.json": Utils.dedent` + "/src/src-types/package.json": Utils.dedent` { "type": "module", "exports": "./index.js" }`, - "/src/src-dogs/package.json": Utils.dedent` + "/src/src-dogs/package.json": Utils.dedent` { "type": "module", "exports": "./index.js" }`, - "/src/src-types/tsconfig.json": Utils.dedent` + "/src/src-types/tsconfig.json": Utils.dedent` { "extends": "../tsconfig-base.json", "compilerOptions": { @@ -163,7 +165,7 @@ namespace ts { "**/*" ] }`, - "/src/src-dogs/tsconfig.json": Utils.dedent` + "/src/src-dogs/tsconfig.json": Utils.dedent` { "extends": "../tsconfig-base.json", "compilerOptions": { @@ -176,13 +178,12 @@ namespace ts { "**/*" ] }`, - }, ""), - modifyFs: fs => { - fs.writeFileSync("/lib/lib.es2022.full.d.ts", tscWatch.libFile.content); - fs.symlinkSync("/src", "/src/src-types/node_modules"); - fs.symlinkSync("/src", "/src/src-dogs/node_modules"); - }, - commandLineArgs: ["-b", "src/src-types", "src/src-dogs", "--verbose"] - }); + }, ""), + modifyFs: fs => { + fs.writeFileSync("/lib/lib.es2022.full.d.ts", ts.tscWatch.libFile.content); + fs.symlinkSync("/src", "/src/src-types/node_modules"); + fs.symlinkSync("/src", "/src/src-dogs/node_modules"); + }, + commandLineArgs: ["-b", "src/src-types", "src/src-dogs", "--verbose"] }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/noEmit.ts b/src/testRunner/unittests/tsbuild/noEmit.ts index 6348ffaf61172..19cb8a5049647 100644 --- a/src/testRunner/unittests/tsbuild/noEmit.ts +++ b/src/testRunner/unittests/tsbuild/noEmit.ts @@ -1,34 +1,34 @@ -namespace ts { - describe("unittests:: tsbuild:: noEmit", () => { - function verifyNoEmitWorker(subScenario: string, aTsContent: string, commandLineArgs: readonly string[]) { - verifyTscWithEdits({ - scenario: "noEmit", - subScenario, - fs: () => loadProjectFromFiles({ - "/src/a.ts": aTsContent, - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { noEmit: true } - }) - }), - commandLineArgs, - edits: [ - noChangeRun, - { - subScenario: "Fix error", - modifyFs: fs => fs.writeFileSync("/src/a.ts", `const a = "hello"`), - }, - noChangeRun, - ], - baselinePrograms: true, - }); - } +import * as ts from "../../_namespaces/ts"; - function verifyNoEmit(subScenario: string, aTsContent: string) { - verifyNoEmitWorker(subScenario, aTsContent, ["--b", "/src/tsconfig.json", "-v"]); - verifyNoEmitWorker(`${subScenario} with incremental`, aTsContent, ["--b", "/src/tsconfig.json", "-v", "--incremental"]); - } +describe("unittests:: tsbuild:: noEmit", () => { + function verifyNoEmitWorker(subScenario: string, aTsContent: string, commandLineArgs: readonly string[]) { + ts.verifyTscWithEdits({ + scenario: "noEmit", + subScenario, + fs: () => ts.loadProjectFromFiles({ + "/src/a.ts": aTsContent, + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { noEmit: true } + }) + }), + commandLineArgs, + edits: [ + ts.noChangeRun, + { + subScenario: "Fix error", + modifyFs: fs => fs.writeFileSync("/src/a.ts", `const a = "hello"`), + }, + ts.noChangeRun, + ], + baselinePrograms: true, + }); + } - verifyNoEmit("syntax errors", `const a = "hello`); - verifyNoEmit("semantic errors", `const a: number = "hello"`); - }); -} \ No newline at end of file + function verifyNoEmit(subScenario: string, aTsContent: string) { + verifyNoEmitWorker(subScenario, aTsContent, ["--b", "/src/tsconfig.json", "-v"]); + verifyNoEmitWorker(`${subScenario} with incremental`, aTsContent, ["--b", "/src/tsconfig.json", "-v", "--incremental"]); + } + + verifyNoEmit("syntax errors", `const a = "hello`); + verifyNoEmit("semantic errors", `const a: number = "hello"`); +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuild/noEmitOnError.ts b/src/testRunner/unittests/tsbuild/noEmitOnError.ts index 6a176d3d65b45..c61f45a1cd8ec 100644 --- a/src/testRunner/unittests/tsbuild/noEmitOnError.ts +++ b/src/testRunner/unittests/tsbuild/noEmitOnError.ts @@ -1,87 +1,88 @@ -namespace ts { - describe("unittests:: tsbuild - with noEmitOnError", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/noEmitOnError"); - }); - after(() => { - projFs = undefined!; - }); +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; - verifyTscWithEdits({ - scenario: "noEmitOnError", - subScenario: "syntax errors", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.json"], - edits: [ - noChangeRun, - { - subScenario: "Fix error", - modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; +describe("unittests:: tsbuild - with noEmitOnError", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = ts.loadProjectFromDisk("tests/projects/noEmitOnError"); + }); + after(() => { + projFs = undefined!; + }); + + ts.verifyTscWithEdits({ + scenario: "noEmitOnError", + subScenario: "syntax errors", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.json"], + edits: [ + ts.noChangeRun, + { + subScenario: "Fix error", + modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a = { lastName: 'sdsd' };`, "utf-8"), - }, - noChangeRun, - ], - baselinePrograms: true, - }); + }, + ts.noChangeRun, + ], + baselinePrograms: true, + }); - verifyTscWithEdits({ - scenario: "noEmitOnError", - subScenario: "syntax errors with incremental", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.json", "--incremental"], - edits: [ - noChangeRun, - { - subScenario: "Fix error", - modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + ts.verifyTscWithEdits({ + scenario: "noEmitOnError", + subScenario: "syntax errors with incremental", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.json", "--incremental"], + edits: [ + ts.noChangeRun, + { + subScenario: "Fix error", + modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a = { lastName: 'sdsd' };`, "utf-8"), - }, - noChangeRun, - ], - baselinePrograms: true, - }); + }, + ts.noChangeRun, + ], + baselinePrograms: true, + }); - verifyTscWithEdits({ - scenario: "noEmitOnError", - subScenario: "semantic errors", - fs: () => projFs, - modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + ts.verifyTscWithEdits({ + scenario: "noEmitOnError", + subScenario: "semantic errors", + fs: () => projFs, + modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a: string = 10;`, "utf-8"), - commandLineArgs: ["--b", "/src/tsconfig.json"], - edits: [ - noChangeRun, - { - subScenario: "Fix error", - modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + commandLineArgs: ["--b", "/src/tsconfig.json"], + edits: [ + ts.noChangeRun, + { + subScenario: "Fix error", + modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a: string = "hello";`, "utf-8"), - }, - noChangeRun, - ], - baselinePrograms: true, - }); + }, + ts.noChangeRun, + ], + baselinePrograms: true, + }); - verifyTscWithEdits({ - scenario: "noEmitOnError", - subScenario: "semantic errors with incremental", - fs: () => projFs, - modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + ts.verifyTscWithEdits({ + scenario: "noEmitOnError", + subScenario: "semantic errors with incremental", + fs: () => projFs, + modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a: string = 10;`, "utf-8"), - commandLineArgs: ["--b", "/src/tsconfig.json", "--incremental"], - edits: [ - noChangeWithExportsDiscrepancyRun, - { - subScenario: "Fix error", - modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + commandLineArgs: ["--b", "/src/tsconfig.json", "--incremental"], + edits: [ + ts.noChangeWithExportsDiscrepancyRun, + { + subScenario: "Fix error", + modifyFs: fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a: string = "hello";`, "utf-8"), - }, - noChangeRun, - ], - baselinePrograms: true, - }); + }, + ts.noChangeRun, + ], + baselinePrograms: true, }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/outFile.ts b/src/testRunner/unittests/tsbuild/outFile.ts index 3bfe0010fa543..08a80e200ec28 100644 --- a/src/testRunner/unittests/tsbuild/outFile.ts +++ b/src/testRunner/unittests/tsbuild/outFile.ts @@ -1,394 +1,397 @@ -namespace ts { - describe("unittests:: tsbuild:: outFile::", () => { - let outFileFs: vfs.FileSystem; - let outFileWithBuildFs: vfs.FileSystem; - before(() => { - outFileFs = loadProjectFromDisk("tests/projects/outfile-concat"); - }); - after(() => { - outFileFs = undefined!; - outFileWithBuildFs = undefined!; - }); +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; +import * as fakes from "../../_namespaces/fakes"; + +describe("unittests:: tsbuild:: outFile::", () => { + let outFileFs: vfs.FileSystem; + let outFileWithBuildFs: vfs.FileSystem; + before(() => { + outFileFs = ts.loadProjectFromDisk("tests/projects/outfile-concat"); + }); + after(() => { + outFileFs = undefined!; + outFileWithBuildFs = undefined!; + }); - interface VerifyOutFileScenarioInput { - subScenario: string; - modifyFs?: (fs: vfs.FileSystem) => void; - modifyAgainFs?: (fs: vfs.FileSystem) => void; - ignoreDtsChanged?: true; - ignoreDtsUnchanged?: true; - baselineOnly?: true; - additionalCommandLineArgs?: string[]; - } + interface VerifyOutFileScenarioInput { + subScenario: string; + modifyFs?: (fs: vfs.FileSystem) => void; + modifyAgainFs?: (fs: vfs.FileSystem) => void; + ignoreDtsChanged?: true; + ignoreDtsUnchanged?: true; + baselineOnly?: true; + additionalCommandLineArgs?: string[]; + } - function verifyOutFileScenario({ + function verifyOutFileScenario({ + subScenario, + modifyFs, + modifyAgainFs, + ignoreDtsChanged, + ignoreDtsUnchanged, + baselineOnly, + additionalCommandLineArgs, + }: VerifyOutFileScenarioInput) { + const edits: ts.TestTscEdit[] = []; + if (!ignoreDtsChanged) { + edits.push({ + subScenario: "incremental-declaration-changes", + modifyFs: fs => ts.replaceText(fs, "/src/first/first_PART1.ts", "Hello", "Hola"), + }); + } + if (!ignoreDtsUnchanged) { + edits.push({ + subScenario: "incremental-declaration-doesnt-change", + modifyFs: fs => ts.appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"), + }); + } + if (modifyAgainFs) { + edits.push({ + subScenario: "incremental-headers-change-without-dts-changes", + modifyFs: modifyAgainFs + }); + } + const input: ts.VerifyTscWithEditsInput = { subScenario, + fs: () => outFileFs, + scenario: "outfile-concat", + commandLineArgs: ["--b", "/src/third", "--verbose", ...(additionalCommandLineArgs || [])], + baselineSourceMap: true, modifyFs, - modifyAgainFs, - ignoreDtsChanged, - ignoreDtsUnchanged, - baselineOnly, - additionalCommandLineArgs, - }: VerifyOutFileScenarioInput) { - const edits: TestTscEdit[] = []; - if (!ignoreDtsChanged) { - edits.push({ - subScenario: "incremental-declaration-changes", - modifyFs: fs => replaceText(fs, "/src/first/first_PART1.ts", "Hello", "Hola"), - }); - } - if (!ignoreDtsUnchanged) { - edits.push({ - subScenario: "incremental-declaration-doesnt-change", - modifyFs: fs => appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"), - }); - } - if (modifyAgainFs) { - edits.push({ - subScenario: "incremental-headers-change-without-dts-changes", - modifyFs: modifyAgainFs - }); - } - const input: VerifyTscWithEditsInput = { - subScenario, - fs: () => outFileFs, - scenario: "outfile-concat", - commandLineArgs: ["--b", "/src/third", "--verbose", ...(additionalCommandLineArgs || [])], - baselineSourceMap: true, - modifyFs, - baselineReadFileCalls: !baselineOnly, - edits, - }; - return edits.length ? - verifyTscWithEdits(input) : - verifyTsc(input); - } + baselineReadFileCalls: !baselineOnly, + edits, + }; + return edits.length ? + ts.verifyTscWithEdits(input) : + ts.verifyTsc(input); + } - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "baseline sectioned sourcemaps", - }); + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "baseline sectioned sourcemaps", + }); - verifyOutFileScenario({ - subScenario: "explainFiles", - additionalCommandLineArgs: ["--explainFiles"], - baselineOnly: true - }); + verifyOutFileScenario({ + subScenario: "explainFiles", + additionalCommandLineArgs: ["--explainFiles"], + baselineOnly: true + }); - // Verify baseline with build info + dts unChanged - verifyOutFileScenario({ - subScenario: "when final project is not composite but uses project references", - modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify baseline with build info + dts unChanged + verifyOutFileScenario({ + subScenario: "when final project is not composite but uses project references", + modifyFs: fs => ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""), + ignoreDtsChanged: true, + baselineOnly: true + }); - // Verify baseline with build info - verifyOutFileScenario({ - subScenario: "when final project is not composite but incremental", - modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, `"incremental": true,`), - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - baselineOnly: true - }); + // Verify baseline with build info + verifyOutFileScenario({ + subScenario: "when final project is not composite but incremental", + modifyFs: fs => ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, `"incremental": true,`), + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + baselineOnly: true + }); - // Verify baseline with build info - verifyOutFileScenario({ - subScenario: "when final project specifies tsBuildInfoFile", - modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, `"composite": true, + // Verify baseline with build info + verifyOutFileScenario({ + subScenario: "when final project specifies tsBuildInfoFile", + modifyFs: fs => ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, `"composite": true, "tsBuildInfoFile": "./thirdjs/output/third.tsbuildinfo",`), - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - baselineOnly: true - }); + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + baselineOnly: true + }); - function getOutFileFsAfterBuild() { - if (outFileWithBuildFs) return outFileWithBuildFs; - const fs = outFileFs.shadow(); - const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }); - const host = createSolutionBuilderHostForBaseline(sys as TscCompileSystem); - const builder = createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: true }); - builder.build(); - fs.makeReadonly(); - return outFileWithBuildFs = fs; - } + function getOutFileFsAfterBuild() { + if (outFileWithBuildFs) return outFileWithBuildFs; + const fs = outFileFs.shadow(); + const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }); + const host = ts.createSolutionBuilderHostForBaseline(sys as ts.TscCompileSystem); + const builder = ts.createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: true }); + builder.build(); + fs.makeReadonly(); + return outFileWithBuildFs = fs; + } - verifyTscWithEdits({ - scenario: "outFile", - subScenario: "clean projects", - fs: getOutFileFsAfterBuild, - commandLineArgs: ["--b", "/src/third", "--clean"], - edits: noChangeOnlyRuns - }); + ts.verifyTscWithEdits({ + scenario: "outFile", + subScenario: "clean projects", + fs: getOutFileFsAfterBuild, + commandLineArgs: ["--b", "/src/third", "--clean"], + edits: ts.noChangeOnlyRuns + }); - verifyTsc({ - scenario: "outFile", - subScenario: "verify buildInfo absence results in new build", - fs: getOutFileFsAfterBuild, - commandLineArgs: ["--b", "/src/third", "--verbose"], - modifyFs: fs => fs.unlinkSync("/src/first/bin/first-output.tsbuildinfo"), - }); + ts.verifyTsc({ + scenario: "outFile", + subScenario: "verify buildInfo absence results in new build", + fs: getOutFileFsAfterBuild, + commandLineArgs: ["--b", "/src/third", "--verbose"], + modifyFs: fs => fs.unlinkSync("/src/first/bin/first-output.tsbuildinfo"), + }); - verifyTsc({ - scenario: "outFile", - subScenario: "tsbuildinfo is not generated when incremental is set to false", - fs: () => outFileFs, - commandLineArgs: ["--b", "/src/third", "--verbose"], - modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""), - }); + ts.verifyTsc({ + scenario: "outFile", + subScenario: "tsbuildinfo is not generated when incremental is set to false", + fs: () => outFileFs, + commandLineArgs: ["--b", "/src/third", "--verbose"], + modifyFs: fs => ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""), + }); - verifyTscCompileLike(testTscCompileLike, { - scenario: "outFile", - subScenario: "rebuilds completely when version in tsbuildinfo doesnt match ts version", - fs: getOutFileFsAfterBuild, - commandLineArgs: ["--b", "/src/third", "--verbose"], - compile: sys => { - // Buildinfo will have version which does not match with current ts version - const buildHost = createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion"); - const builder = createSolutionBuilder(buildHost, ["/src/third"], { verbose: true }); - sys.exit(builder.build()); - } - }); + ts.verifyTscCompileLike(ts.testTscCompileLike, { + scenario: "outFile", + subScenario: "rebuilds completely when version in tsbuildinfo doesnt match ts version", + fs: getOutFileFsAfterBuild, + commandLineArgs: ["--b", "/src/third", "--verbose"], + compile: sys => { + // Buildinfo will have version which does not match with current ts version + const buildHost = ts.createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion"); + const builder = ts.createSolutionBuilder(buildHost, ["/src/third"], { verbose: true }); + sys.exit(builder.build()); + } + }); - verifyTscWithEdits({ - scenario: "outFile", - subScenario: "rebuilds completely when command line incremental flag changes between non dts changes", - fs: () => outFileFs, - // Make non composite third project - modifyFs: fs => replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""), - // Build with command line incremental - commandLineArgs: ["--b", "/src/third", "--i", "--verbose"], - edits: [ - { - subScenario: "Make non incremental build with change in file that doesnt affect dts", - modifyFs: fs => appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"), - commandLineArgs: ["--b", "/src/third", "--verbose"], - }, - { - subScenario: "Make incremental build with change in file that doesnt affect dts", - modifyFs: fs => appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"), - commandLineArgs: ["--b", "/src/third", "--verbose", "--incremental"], - } - ] - }); + ts.verifyTscWithEdits({ + scenario: "outFile", + subScenario: "rebuilds completely when command line incremental flag changes between non dts changes", + fs: () => outFileFs, + // Make non composite third project + modifyFs: fs => ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""), + // Build with command line incremental + commandLineArgs: ["--b", "/src/third", "--i", "--verbose"], + edits: [ + { + subScenario: "Make non incremental build with change in file that doesnt affect dts", + modifyFs: fs => ts.appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"), + commandLineArgs: ["--b", "/src/third", "--verbose"], + }, + { + subScenario: "Make incremental build with change in file that doesnt affect dts", + modifyFs: fs => ts.appendText(fs, "/src/first/first_PART1.ts", "console.log(s);"), + commandLineArgs: ["--b", "/src/third", "--verbose", "--incremental"], + } + ] + }); - verifyTscWithEdits({ - scenario: "outFile", - subScenario: "when input file text does not change but its modified time changes", - fs: () => outFileFs, - commandLineArgs: ["--b", "/src/third", "--verbose"], - edits: [ - { - subScenario: "upstream project changes without changing file text", - modifyFs: fs => { - const time = new Date(fs.time()); - fs.utimesSync("/src/first/first_PART1.ts", time, time); - }, + ts.verifyTscWithEdits({ + scenario: "outFile", + subScenario: "when input file text does not change but its modified time changes", + fs: () => outFileFs, + commandLineArgs: ["--b", "/src/third", "--verbose"], + edits: [ + { + subScenario: "upstream project changes without changing file text", + modifyFs: fs => { + const time = new Date(fs.time()); + fs.utimesSync("/src/first/first_PART1.ts", time, time); }, - ] - }); + }, + ] + }); - verifyTscCompileLike(testTscCompileLike, { - scenario: "outFile", - subScenario: "builds till project specified", - fs: () => outFileFs, - commandLineArgs: ["--build", "/src/second/tsconfig.json"], - compile: sys => { - const buildHost = createSolutionBuilderHostForBaseline(sys); - const builder = createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {}); - sys.exit(builder.build("/src/second/tsconfig.json")); - } - }); + ts.verifyTscCompileLike(ts.testTscCompileLike, { + scenario: "outFile", + subScenario: "builds till project specified", + fs: () => outFileFs, + commandLineArgs: ["--build", "/src/second/tsconfig.json"], + compile: sys => { + const buildHost = ts.createSolutionBuilderHostForBaseline(sys); + const builder = ts.createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {}); + sys.exit(builder.build("/src/second/tsconfig.json")); + } + }); - verifyTscCompileLike(testTscCompileLike, { - scenario: "outFile", - subScenario: "cleans till project specified", - fs: getOutFileFsAfterBuild, - commandLineArgs: ["--build", "--clean", "/src/second/tsconfig.json"], - compile: sys => { - const buildHost = createSolutionBuilderHostForBaseline(sys); - const builder = createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], { verbose: true }); - sys.exit(builder.clean("/src/second/tsconfig.json")); - } - }); + ts.verifyTscCompileLike(ts.testTscCompileLike, { + scenario: "outFile", + subScenario: "cleans till project specified", + fs: getOutFileFsAfterBuild, + commandLineArgs: ["--build", "--clean", "/src/second/tsconfig.json"], + compile: sys => { + const buildHost = ts.createSolutionBuilderHostForBaseline(sys); + const builder = ts.createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], { verbose: true }); + sys.exit(builder.clean("/src/second/tsconfig.json")); + } + }); - describe("Prepend output with .tsbuildinfo", () => { - // Prologues - describe("Prologues", () => { - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "strict in all projects", - modifyFs: fs => { - enableStrict(fs, "/src/first/tsconfig.json"); - enableStrict(fs, "/src/second/tsconfig.json"); - enableStrict(fs, "/src/third/tsconfig.json"); - }, - modifyAgainFs: fs => addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue"`) - }); + describe("Prepend output with .tsbuildinfo", () => { + // Prologues + describe("Prologues", () => { + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "strict in all projects", + modifyFs: fs => { + ts.enableStrict(fs, "/src/first/tsconfig.json"); + ts.enableStrict(fs, "/src/second/tsconfig.json"); + ts.enableStrict(fs, "/src/third/tsconfig.json"); + }, + modifyAgainFs: fs => ts.addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue"`) + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "strict in one dependency", - modifyFs: fs => enableStrict(fs, "/src/second/tsconfig.json"), - modifyAgainFs: fs => addTestPrologue(fs, "src/first/first_PART1.ts", `"myPrologue"`), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "strict in one dependency", + modifyFs: fs => ts.enableStrict(fs, "/src/second/tsconfig.json"), + modifyAgainFs: fs => ts.addTestPrologue(fs, "src/first/first_PART1.ts", `"myPrologue"`), + ignoreDtsChanged: true, + baselineOnly: true + }); - // Verify initial + incremental edits - sourcemap verification - verifyOutFileScenario({ - subScenario: "multiple prologues in all projects", - modifyFs: fs => { - enableStrict(fs, "/src/first/tsconfig.json"); - addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue"`); - enableStrict(fs, "/src/second/tsconfig.json"); - addTestPrologue(fs, "/src/second/second_part1.ts", `"myPrologue"`); - addTestPrologue(fs, "/src/second/second_part2.ts", `"myPrologue2";`); - enableStrict(fs, "/src/third/tsconfig.json"); - addTestPrologue(fs, "/src/third/third_part1.ts", `"myPrologue";`); - addTestPrologue(fs, "/src/third/third_part1.ts", `"myPrologue3";`); - }, - modifyAgainFs: fs => addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue5"`) - }); + // Verify initial + incremental edits - sourcemap verification + verifyOutFileScenario({ + subScenario: "multiple prologues in all projects", + modifyFs: fs => { + ts.enableStrict(fs, "/src/first/tsconfig.json"); + ts.addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue"`); + ts.enableStrict(fs, "/src/second/tsconfig.json"); + ts.addTestPrologue(fs, "/src/second/second_part1.ts", `"myPrologue"`); + ts.addTestPrologue(fs, "/src/second/second_part2.ts", `"myPrologue2";`); + ts.enableStrict(fs, "/src/third/tsconfig.json"); + ts.addTestPrologue(fs, "/src/third/third_part1.ts", `"myPrologue";`); + ts.addTestPrologue(fs, "/src/third/third_part1.ts", `"myPrologue3";`); + }, + modifyAgainFs: fs => ts.addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue5"`) + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "multiple prologues in different projects", - modifyFs: fs => { - enableStrict(fs, "/src/first/tsconfig.json"); - addTestPrologue(fs, "/src/second/second_part1.ts", `"myPrologue"`); - addTestPrologue(fs, "/src/second/second_part2.ts", `"myPrologue2";`); - enableStrict(fs, "/src/third/tsconfig.json"); - }, - modifyAgainFs: fs => addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue5"`), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "multiple prologues in different projects", + modifyFs: fs => { + ts.enableStrict(fs, "/src/first/tsconfig.json"); + ts.addTestPrologue(fs, "/src/second/second_part1.ts", `"myPrologue"`); + ts.addTestPrologue(fs, "/src/second/second_part2.ts", `"myPrologue2";`); + ts.enableStrict(fs, "/src/third/tsconfig.json"); + }, + modifyAgainFs: fs => ts.addTestPrologue(fs, "/src/first/first_PART1.ts", `"myPrologue5"`), + ignoreDtsChanged: true, + baselineOnly: true }); + }); - // Shebang - describe("Shebang", () => { - // changes declaration because its emitted in .d.ts file - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "shebang in all projects", - modifyFs: fs => { - addShebang(fs, "first", "first_PART1"); - addShebang(fs, "first", "first_part2"); - addShebang(fs, "second", "second_part1"); - addShebang(fs, "third", "third_part1"); - }, - }); + // Shebang + describe("Shebang", () => { + // changes declaration because its emitted in .d.ts file + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "shebang in all projects", + modifyFs: fs => { + ts.addShebang(fs, "first", "first_PART1"); + ts.addShebang(fs, "first", "first_part2"); + ts.addShebang(fs, "second", "second_part1"); + ts.addShebang(fs, "third", "third_part1"); + }, + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "shebang in only one dependency project", - modifyFs: fs => addShebang(fs, "second", "second_part1"), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "shebang in only one dependency project", + modifyFs: fs => ts.addShebang(fs, "second", "second_part1"), + ignoreDtsChanged: true, + baselineOnly: true }); + }); - // emitHelpers - describe("emitHelpers", () => { - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "emitHelpers in all projects", - modifyFs: fs => { - addRest(fs, "first", "first_PART1"); - addRest(fs, "second", "second_part1"); - addRest(fs, "third", "third_part1"); - }, - modifyAgainFs: fs => removeRest(fs, "first", "first_PART1") - }); + // emitHelpers + describe("emitHelpers", () => { + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "emitHelpers in all projects", + modifyFs: fs => { + ts.addRest(fs, "first", "first_PART1"); + ts.addRest(fs, "second", "second_part1"); + ts.addRest(fs, "third", "third_part1"); + }, + modifyAgainFs: fs => ts.removeRest(fs, "first", "first_PART1") + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "emitHelpers in only one dependency project", - modifyFs: fs => { - addStubFoo(fs, "first", "first_PART1"); - addRest(fs, "second", "second_part1"); - }, - modifyAgainFs: fs => changeStubToRest(fs, "first", "first_PART1"), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "emitHelpers in only one dependency project", + modifyFs: fs => { + ts.addStubFoo(fs, "first", "first_PART1"); + ts.addRest(fs, "second", "second_part1"); + }, + modifyAgainFs: fs => ts.changeStubToRest(fs, "first", "first_PART1"), + ignoreDtsChanged: true, + baselineOnly: true + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "multiple emitHelpers in all projects", - modifyFs: fs => { - addRest(fs, "first", "first_PART1"); - addSpread(fs, "first", "first_part3"); - addRest(fs, "second", "second_part1"); - addSpread(fs, "second", "second_part2"); - addRest(fs, "third", "third_part1"); - addSpread(fs, "third", "third_part1"); - }, - modifyAgainFs: fs => removeRest(fs, "first", "first_PART1"), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "multiple emitHelpers in all projects", + modifyFs: fs => { + ts.addRest(fs, "first", "first_PART1"); + ts.addSpread(fs, "first", "first_part3"); + ts.addRest(fs, "second", "second_part1"); + ts.addSpread(fs, "second", "second_part2"); + ts.addRest(fs, "third", "third_part1"); + ts.addSpread(fs, "third", "third_part1"); + }, + modifyAgainFs: fs => ts.removeRest(fs, "first", "first_PART1"), + ignoreDtsChanged: true, + baselineOnly: true + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "multiple emitHelpers in different projects", - modifyFs: fs => { - addRest(fs, "first", "first_PART1"); - addSpread(fs, "second", "second_part1"); - addRest(fs, "third", "third_part1"); - }, - modifyAgainFs: fs => removeRest(fs, "first", "first_PART1"), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "multiple emitHelpers in different projects", + modifyFs: fs => { + ts.addRest(fs, "first", "first_PART1"); + ts.addSpread(fs, "second", "second_part1"); + ts.addRest(fs, "third", "third_part1"); + }, + modifyAgainFs: fs => ts.removeRest(fs, "first", "first_PART1"), + ignoreDtsChanged: true, + baselineOnly: true }); + }); - // triple slash refs - describe("triple slash refs", () => { - // changes declaration because its emitted in .d.ts file - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "triple slash refs in all projects", - modifyFs: fs => { - addTripleSlashRef(fs, "first", "first_part2"); - addTripleSlashRef(fs, "second", "second_part1"); - addTripleSlashRef(fs, "third", "third_part1"); - } - }); + // triple slash refs + describe("triple slash refs", () => { + // changes declaration because its emitted in .d.ts file + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "triple slash refs in all projects", + modifyFs: fs => { + ts.addTripleSlashRef(fs, "first", "first_part2"); + ts.addTripleSlashRef(fs, "second", "second_part1"); + ts.addTripleSlashRef(fs, "third", "third_part1"); + } + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "triple slash refs in one project", - modifyFs: fs => addTripleSlashRef(fs, "second", "second_part1"), - ignoreDtsChanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "triple slash refs in one project", + modifyFs: fs => ts.addTripleSlashRef(fs, "second", "second_part1"), + ignoreDtsChanged: true, + baselineOnly: true }); + }); - describe("stripInternal", () => { - function disableRemoveComments(fs: vfs.FileSystem, file: string) { - replaceText(fs, file, `"removeComments": true`, `"removeComments": false`); - } + describe("stripInternal", () => { + function disableRemoveComments(fs: vfs.FileSystem, file: string) { + ts.replaceText(fs, file, `"removeComments": true`, `"removeComments": false`); + } - function diableRemoveCommentsInAll(fs: vfs.FileSystem) { - disableRemoveComments(fs, "/src/first/tsconfig.json"); - disableRemoveComments(fs, "/src/second/tsconfig.json"); - disableRemoveComments(fs, "/src/third/tsconfig.json"); - } + function diableRemoveCommentsInAll(fs: vfs.FileSystem) { + disableRemoveComments(fs, "/src/first/tsconfig.json"); + disableRemoveComments(fs, "/src/second/tsconfig.json"); + disableRemoveComments(fs, "/src/third/tsconfig.json"); + } - function stripInternalOfThird(fs: vfs.FileSystem) { - replaceText(fs, "/src/third/tsconfig.json", `"declaration": true,`, `"declaration": true, + function stripInternalOfThird(fs: vfs.FileSystem) { + ts.replaceText(fs, "/src/third/tsconfig.json", `"declaration": true,`, `"declaration": true, "stripInternal": true,`); - } + } - function stripInternalScenario(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) { - const internal: string = jsDocStyle ? `/**@internal*/` : `/*@internal*/`; - if (removeCommentsDisabled) { - diableRemoveCommentsInAll(fs); - } - stripInternalOfThird(fs); - replaceText(fs, "/src/first/first_PART1.ts", "interface", `${internal} interface`); - appendText(fs, "/src/second/second_part1.ts", ` + function stripInternalScenario(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) { + const internal: string = jsDocStyle ? `/**@internal*/` : `/*@internal*/`; + if (removeCommentsDisabled) { + diableRemoveCommentsInAll(fs); + } + stripInternalOfThird(fs); + ts.replaceText(fs, "/src/first/first_PART1.ts", "interface", `${internal} interface`); + ts.appendText(fs, "/src/second/second_part1.ts", ` class normalC { ${internal} constructor() { } ${internal} prop: string; @@ -414,93 +417,93 @@ ${internal} import internalImport = internalNamespace.someClass; ${internal} type internalType = internalC; ${internal} const internalConst = 10; ${internal} enum internalEnum { a, b, c }`); + } + + // Verify initial + incremental edits + verifyOutFileScenario({ + subScenario: "stripInternal", + modifyFs: stripInternalScenario, + modifyAgainFs: fs => ts.replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"), + }); + + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "stripInternal with comments emit enabled", + modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true), + modifyAgainFs: fs => ts.replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"), + ignoreDtsChanged: true, + baselineOnly: true + }); + + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "stripInternal jsdoc style comment", + modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true), + modifyAgainFs: fs => ts.replaceText(fs, "/src/first/first_PART1.ts", `/**@internal*/ interface`, "interface"), + ignoreDtsChanged: true, + baselineOnly: true + }); + + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "stripInternal jsdoc style with comments emit enabled", + modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true, /*jsDocStyle*/ true), + ignoreDtsChanged: true, + baselineOnly: true + }); + + describe("with three levels of project dependency", () => { + function makeOneTwoThreeDependOrder(fs: vfs.FileSystem) { + ts.replaceText(fs, "/src/second/tsconfig.json", "[", `[ + { "path": "../first", "prepend": true }`); + ts.replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../first", "prepend": true },`, ""); + } + + function stripInternalWithDependentOrder(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) { + stripInternalScenario(fs, removeCommentsDisabled, jsDocStyle); + makeOneTwoThreeDependOrder(fs); } // Verify initial + incremental edits verifyOutFileScenario({ - subScenario: "stripInternal", - modifyFs: stripInternalScenario, - modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"), + subScenario: "stripInternal when one-two-three are prepended in order", + modifyFs: stripInternalWithDependentOrder, + modifyAgainFs: fs => ts.replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"), }); // Verify ignore dtsChanged verifyOutFileScenario({ - subScenario: "stripInternal with comments emit enabled", - modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true), - modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"), + subScenario: "stripInternal with comments emit enabled when one-two-three are prepended in order", + modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ true), + modifyAgainFs: fs => ts.replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"), ignoreDtsChanged: true, baselineOnly: true }); // Verify ignore dtsChanged verifyOutFileScenario({ - subScenario: "stripInternal jsdoc style comment", - modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true), - modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/**@internal*/ interface`, "interface"), + subScenario: "stripInternal jsdoc style comment when one-two-three are prepended in order", + modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true), + modifyAgainFs: fs => ts.replaceText(fs, "/src/first/first_PART1.ts", `/**@internal*/ interface`, "interface"), ignoreDtsChanged: true, baselineOnly: true }); // Verify ignore dtsChanged verifyOutFileScenario({ - subScenario: "stripInternal jsdoc style with comments emit enabled", - modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true, /*jsDocStyle*/ true), + subScenario: "stripInternal jsdoc style with comments emit enabled when one-two-three are prepended in order", + modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ true, /*jsDocStyle*/ true), ignoreDtsChanged: true, baselineOnly: true }); + }); - describe("with three levels of project dependency", () => { - function makeOneTwoThreeDependOrder(fs: vfs.FileSystem) { - replaceText(fs, "/src/second/tsconfig.json", "[", `[ - { "path": "../first", "prepend": true }`); - replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../first", "prepend": true },`, ""); - } - - function stripInternalWithDependentOrder(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) { - stripInternalScenario(fs, removeCommentsDisabled, jsDocStyle); - makeOneTwoThreeDependOrder(fs); - } - - // Verify initial + incremental edits - verifyOutFileScenario({ - subScenario: "stripInternal when one-two-three are prepended in order", - modifyFs: stripInternalWithDependentOrder, - modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"), - }); - - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "stripInternal with comments emit enabled when one-two-three are prepended in order", - modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ true), - modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/*@internal*/ interface`, "interface"), - ignoreDtsChanged: true, - baselineOnly: true - }); - - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "stripInternal jsdoc style comment when one-two-three are prepended in order", - modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true), - modifyAgainFs: fs => replaceText(fs, "/src/first/first_PART1.ts", `/**@internal*/ interface`, "interface"), - ignoreDtsChanged: true, - baselineOnly: true - }); - - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "stripInternal jsdoc style with comments emit enabled when one-two-three are prepended in order", - modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ true, /*jsDocStyle*/ true), - ignoreDtsChanged: true, - baselineOnly: true - }); - }); - - // only baseline - verifyOutFileScenario({ - subScenario: "stripInternal baseline when internal is inside another internal", - modifyFs: fs => { - stripInternalOfThird(fs); - prependText(fs, "/src/first/first_PART1.ts", `namespace ts { + // only baseline + verifyOutFileScenario({ + subScenario: "stripInternal baseline when internal is inside another internal", + modifyFs: fs => { + stripInternalOfThird(fs); + ts.prependText(fs, "/src/first/first_PART1.ts", `namespace ts { /* @internal */ /** * Subset of properties from SourceFile that are used in multiple utility functions @@ -528,18 +531,18 @@ ${internal} enum internalEnum { a, b, c }`); someProp: string; } }`); - }, - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - baselineOnly: true - }); + }, + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + baselineOnly: true + }); - // only baseline - verifyOutFileScenario({ - subScenario: "stripInternal when few members of enum are internal", - modifyFs: fs => { - stripInternalOfThird(fs); - prependText(fs, "/src/first/first_PART1.ts", `enum TokenFlags { + // only baseline + verifyOutFileScenario({ + subScenario: "stripInternal when few members of enum are internal", + modifyFs: fs => { + stripInternalOfThird(fs); + ts.prependText(fs, "/src/first/first_PART1.ts", `enum TokenFlags { None = 0, /* @internal */ PrecedingLineBreak = 1 << 0, @@ -562,96 +565,95 @@ ${internal} enum internalEnum { a, b, c }`); NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinaryOrOctalSpecifier | ContainsSeparator } `); - }, - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - baselineOnly: true - }); - - verifyOutFileScenario({ - subScenario: "stripInternal when prepend is completely internal", - baselineOnly: true, - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - modifyFs: fs => { - fs.writeFileSync("/src/first/first_PART1.ts", "/* @internal */ const A = 1;"); - fs.writeFileSync("/src/third/third_part1.ts", "const B = 2;"); - fs.writeFileSync("/src/first/tsconfig.json", JSON.stringify({ - compilerOptions: { - composite: true, - declaration: true, - declarationMap: true, - skipDefaultLibCheck: true, - sourceMap: true, - outFile: "./bin/first-output.js" - }, - files: ["/src/first/first_PART1.ts"] - })); - fs.writeFileSync("/src/third/tsconfig.json", JSON.stringify({ - compilerOptions: { - composite: true, - declaration: true, - declarationMap: false, - stripInternal: true, - sourceMap: true, - outFile: "./thirdjs/output/third-output.js", - }, - references: [{ path: "../first", prepend: true }], - files: ["/src/third/third_part1.ts"] - })); - } - }); + }, + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + baselineOnly: true }); - describe("empty source files", () => { - function makeThirdEmptySourceFile(fs: vfs.FileSystem) { - fs.writeFileSync("/src/third/third_part1.ts", "", "utf8"); + verifyOutFileScenario({ + subScenario: "stripInternal when prepend is completely internal", + baselineOnly: true, + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + modifyFs: fs => { + fs.writeFileSync("/src/first/first_PART1.ts", "/* @internal */ const A = 1;"); + fs.writeFileSync("/src/third/third_part1.ts", "const B = 2;"); + fs.writeFileSync("/src/first/tsconfig.json", JSON.stringify({ + compilerOptions: { + composite: true, + declaration: true, + declarationMap: true, + skipDefaultLibCheck: true, + sourceMap: true, + outFile: "./bin/first-output.js" + }, + files: ["/src/first/first_PART1.ts"] + })); + fs.writeFileSync("/src/third/tsconfig.json", JSON.stringify({ + compilerOptions: { + composite: true, + declaration: true, + declarationMap: false, + stripInternal: true, + sourceMap: true, + outFile: "./thirdjs/output/third-output.js", + }, + references: [{ path: "../first", prepend: true }], + files: ["/src/third/third_part1.ts"] + })); } + }); + }); - // Verify ignore dtsChanged - verifyOutFileScenario({ - subScenario: "when source files are empty in the own file", - modifyFs: makeThirdEmptySourceFile, - ignoreDtsChanged: true, - baselineOnly: true - }); + describe("empty source files", () => { + function makeThirdEmptySourceFile(fs: vfs.FileSystem) { + fs.writeFileSync("/src/third/third_part1.ts", "", "utf8"); + } - // only baseline - verifyOutFileScenario({ - subScenario: "declarationMap and sourceMap disabled", - modifyFs: fs => { - makeThirdEmptySourceFile(fs); - replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""); - replaceText(fs, "/src/third/tsconfig.json", `"sourceMap": true,`, ""); - replaceText(fs, "/src/third/tsconfig.json", `"declarationMap": true,`, ""); - }, - ignoreDtsChanged: true, - ignoreDtsUnchanged: true, - baselineOnly: true - }); + // Verify ignore dtsChanged + verifyOutFileScenario({ + subScenario: "when source files are empty in the own file", + modifyFs: makeThirdEmptySourceFile, + ignoreDtsChanged: true, + baselineOnly: true }); - }); - verifyTsc({ - scenario: "outFile", - subScenario: "non module projects without prepend", - fs: () => outFileFs, - commandLineArgs: ["--b", "/src/third", "--verbose"], - modifyFs: fs => { - // No prepend - replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../first", "prepend": true }`, `{ "path": "../first" }`); - replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../second", "prepend": true }`, `{ "path": "../second" }`); - - // Non Modules - replaceText(fs, "/src/first/tsconfig.json", `"composite": true,`, `"composite": true, "module": "none",`); - replaceText(fs, "/src/second/tsconfig.json", `"composite": true,`, `"composite": true, "module": "none",`); - replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, `"composite": true, "module": "none",`); - - // Own file emit - replaceText(fs, "/src/first/tsconfig.json", `"outFile": "./bin/first-output.js",`, ""); - replaceText(fs, "/src/second/tsconfig.json", `"outFile": "../2/second-output.js",`, ""); - replaceText(fs, "/src/third/tsconfig.json", `"outFile": "./thirdjs/output/third-output.js",`, ""); - }, + // only baseline + verifyOutFileScenario({ + subScenario: "declarationMap and sourceMap disabled", + modifyFs: fs => { + makeThirdEmptySourceFile(fs); + ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, ""); + ts.replaceText(fs, "/src/third/tsconfig.json", `"sourceMap": true,`, ""); + ts.replaceText(fs, "/src/third/tsconfig.json", `"declarationMap": true,`, ""); + }, + ignoreDtsChanged: true, + ignoreDtsUnchanged: true, + baselineOnly: true + }); }); }); -} + + ts.verifyTsc({ + scenario: "outFile", + subScenario: "non module projects without prepend", + fs: () => outFileFs, + commandLineArgs: ["--b", "/src/third", "--verbose"], + modifyFs: fs => { + // No prepend + ts.replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../first", "prepend": true }`, `{ "path": "../first" }`); + ts.replaceText(fs, "/src/third/tsconfig.json", `{ "path": "../second", "prepend": true }`, `{ "path": "../second" }`); + + // Non Modules + ts.replaceText(fs, "/src/first/tsconfig.json", `"composite": true,`, `"composite": true, "module": "none",`); + ts.replaceText(fs, "/src/second/tsconfig.json", `"composite": true,`, `"composite": true, "module": "none",`); + ts.replaceText(fs, "/src/third/tsconfig.json", `"composite": true,`, `"composite": true, "module": "none",`); + + // Own file emit + ts.replaceText(fs, "/src/first/tsconfig.json", `"outFile": "./bin/first-output.js",`, ""); + ts.replaceText(fs, "/src/second/tsconfig.json", `"outFile": "../2/second-output.js",`, ""); + ts.replaceText(fs, "/src/third/tsconfig.json", `"outFile": "./thirdjs/output/third-output.js",`, ""); + }, + }); +}); diff --git a/src/testRunner/unittests/tsbuild/outputPaths.ts b/src/testRunner/unittests/tsbuild/outputPaths.ts index 3bcad56dd68ab..08f24dfcf6067 100644 --- a/src/testRunner/unittests/tsbuild/outputPaths.ts +++ b/src/testRunner/unittests/tsbuild/outputPaths.ts @@ -1,106 +1,107 @@ -namespace ts { - describe("unittests:: tsbuild - output file paths", () => { - const noChangeProject: TestTscEdit = { - modifyFs: noop, - subScenario: "Normal build without change, that does not block emit on error to show files that get emitted", - commandLineArgs: ["-p", "/src/tsconfig.json"], - }; - const edits: TestTscEdit[] = [ - noChangeRun, - noChangeProject, - ]; +import * as ts from "../../_namespaces/ts"; +import * as fakes from "../../_namespaces/fakes"; - function verify(input: Pick, expectedOuptutNames: readonly string[]) { - verifyTscWithEdits({ - scenario: "outputPaths", - commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], - ...input - }); +describe("unittests:: tsbuild - output file paths", () => { + const noChangeProject: ts.TestTscEdit = { + modifyFs: ts.noop, + subScenario: "Normal build without change, that does not block emit on error to show files that get emitted", + commandLineArgs: ["-p", "/src/tsconfig.json"], + }; + const edits: ts.TestTscEdit[] = [ + ts.noChangeRun, + noChangeProject, + ]; - it("verify getOutputFileNames", () => { - const sys = new fakes.System(input.fs().makeReadonly(), { executingFilePath: "/lib/tsc" }) as TscCompileSystem; + function verify(input: Pick, expectedOuptutNames: readonly string[]) { + ts.verifyTscWithEdits({ + scenario: "outputPaths", + commandLineArgs: ["--b", "/src/tsconfig.json", "-v"], + ...input + }); - assert.deepEqual( - getOutputFileNames( - parseConfigFileWithSystem("/src/tsconfig.json", {}, /*extendedConfigCache*/ undefined, {}, sys, noop)!, - "/src/src/index.ts", - /*ignoreCase*/ false - ), - expectedOuptutNames - ); - }); - } + it("verify getOutputFileNames", () => { + const sys = new fakes.System(input.fs().makeReadonly(), { executingFilePath: "/lib/tsc" }) as ts.TscCompileSystem; - verify({ - subScenario: "when rootDir is not specified", - fs: () => loadProjectFromFiles({ - "/src/src/index.ts": "export const x = 10;", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - outDir: "dist" - } - }) - }), - edits, - }, ["/src/dist/index.js"]); + assert.deepEqual( + ts.getOutputFileNames( + ts.parseConfigFileWithSystem("/src/tsconfig.json", {}, /*extendedConfigCache*/ undefined, {}, sys, ts.noop)!, + "/src/src/index.ts", + /*ignoreCase*/ false + ), + expectedOuptutNames + ); + }); + } - verify({ - subScenario: "when rootDir is not specified and is composite", - fs: () => loadProjectFromFiles({ - "/src/src/index.ts": "export const x = 10;", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - outDir: "dist", - composite: true - } - }) - }), - edits, - }, ["/src/dist/src/index.js", "/src/dist/src/index.d.ts"]); + verify({ + subScenario: "when rootDir is not specified", + fs: () => ts.loadProjectFromFiles({ + "/src/src/index.ts": "export const x = 10;", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "dist" + } + }) + }), + edits, + }, ["/src/dist/index.js"]); - verify({ - subScenario: "when rootDir is specified", - fs: () => loadProjectFromFiles({ - "/src/src/index.ts": "export const x = 10;", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - outDir: "dist", - rootDir: "src" - } - }) - }), - edits, - }, ["/src/dist/index.js"]); + verify({ + subScenario: "when rootDir is not specified and is composite", + fs: () => ts.loadProjectFromFiles({ + "/src/src/index.ts": "export const x = 10;", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "dist", + composite: true + } + }) + }), + edits, + }, ["/src/dist/src/index.js", "/src/dist/src/index.d.ts"]); - verify({ - subScenario: "when rootDir is specified but not all files belong to rootDir", - fs: () => loadProjectFromFiles({ - "/src/src/index.ts": "export const x = 10;", - "/src/types/type.ts": "export type t = string;", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - outDir: "dist", - rootDir: "src" - } - }) - }), - edits, - }, ["/src/dist/index.js"]); + verify({ + subScenario: "when rootDir is specified", + fs: () => ts.loadProjectFromFiles({ + "/src/src/index.ts": "export const x = 10;", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "dist", + rootDir: "src" + } + }) + }), + edits, + }, ["/src/dist/index.js"]); - verify({ - subScenario: "when rootDir is specified but not all files belong to rootDir and is composite", - fs: () => loadProjectFromFiles({ - "/src/src/index.ts": "export const x = 10;", - "/src/types/type.ts": "export type t = string;", - "/src/tsconfig.json": JSON.stringify({ - compilerOptions: { - outDir: "dist", - rootDir: "src", - composite: true - } - }) - }), - edits, - }, ["/src/dist/index.js", "/src/dist/index.d.ts"]); - }); -} + verify({ + subScenario: "when rootDir is specified but not all files belong to rootDir", + fs: () => ts.loadProjectFromFiles({ + "/src/src/index.ts": "export const x = 10;", + "/src/types/type.ts": "export type t = string;", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "dist", + rootDir: "src" + } + }) + }), + edits, + }, ["/src/dist/index.js"]); + + verify({ + subScenario: "when rootDir is specified but not all files belong to rootDir and is composite", + fs: () => ts.loadProjectFromFiles({ + "/src/src/index.ts": "export const x = 10;", + "/src/types/type.ts": "export type t = string;", + "/src/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "dist", + rootDir: "src", + composite: true + } + }) + }), + edits, + }, ["/src/dist/index.js", "/src/dist/index.d.ts"]); +}); diff --git a/src/testRunner/unittests/tsbuild/publicApi.ts b/src/testRunner/unittests/tsbuild/publicApi.ts index cb2d35ca9670f..b61ba2f5d8991 100644 --- a/src/testRunner/unittests/tsbuild/publicApi.ts +++ b/src/testRunner/unittests/tsbuild/publicApi.ts @@ -1,122 +1,124 @@ -namespace ts { - describe("unittests:: tsbuild:: Public API with custom transformers when passed to build", () => { - let sys: TscCompileSystem; - before(() => { - const inputFs = loadProjectFromFiles({ - "/src/tsconfig.json": JSON.stringify({ - references: [ - { path: "./shared/tsconfig.json" }, - { path: "./webpack/tsconfig.json" } - ], - files: [] - }), - "/src/shared/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true }, - }), - "/src/shared/index.ts": `export function f1() { } +import * as ts from "../../_namespaces/ts"; +import * as fakes from "../../_namespaces/fakes"; +import * as vfs from "../../_namespaces/vfs"; + +describe("unittests:: tsbuild:: Public API with custom transformers when passed to build", () => { + let sys: ts.TscCompileSystem; + before(() => { + const inputFs = ts.loadProjectFromFiles({ + "/src/tsconfig.json": JSON.stringify({ + references: [ + { path: "./shared/tsconfig.json" }, + { path: "./webpack/tsconfig.json" } + ], + files: [] + }), + "/src/shared/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true }, + }), + "/src/shared/index.ts": `export function f1() { } export class c { } export enum e { } // leading export function f2() { } // trailing`, - "/src/webpack/tsconfig.json": JSON.stringify({ - compilerOptions: { - composite: true, - }, - references: [{ path: "../shared/tsconfig.json" }] - }), - "/src/webpack/index.ts": `export function f2() { } + "/src/webpack/tsconfig.json": JSON.stringify({ + compilerOptions: { + composite: true, + }, + references: [{ path: "../shared/tsconfig.json" }] + }), + "/src/webpack/index.ts": `export function f2() { } export class c2 { } export enum e2 { } // leading export function f22() { } // trailing`, - }).makeReadonly(); - const fs = inputFs.shadow(); + }).makeReadonly(); + const fs = inputFs.shadow(); - // Create system - sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }) as TscCompileSystem; - fakes.patchHostForBuildInfoReadWrite(sys); - const commandLineArgs = ["--b", "/src/tsconfig.json"]; - sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`); - sys.exit = exitCode => sys.exitCode = exitCode; - const writtenFiles = sys.writtenFiles = new Set(); - const originalWriteFile = sys.writeFile; - sys.writeFile = (fileName, content, writeByteOrderMark) => { - const path = toPathWithSystem(sys, fileName); - assert.isFalse(writtenFiles.has(path)); - writtenFiles.add(path); - return originalWriteFile.call(sys, fileName, content, writeByteOrderMark); - }; - const { cb, getPrograms } = commandLineCallbacks(sys, /*originalReadCall*/ undefined); - const buildHost = createSolutionBuilderHost( - sys, - /*createProgram*/ undefined, - createDiagnosticReporter(sys, /*pretty*/ true), - createBuilderStatusReporter(sys, /*pretty*/ true), - (errorCount, filesInError) => sys.write(getErrorSummaryText(errorCount, filesInError, sys.newLine, sys)) - ); - buildHost.afterProgramEmitAndDiagnostics = cb; - buildHost.afterEmitBundle = cb; - const builder = createSolutionBuilder(buildHost, [commandLineArgs[1]], { verbose: true }); - const exitStatus = builder.build(/*project*/ undefined, /*cancellationToken*/ undefined, /*writeFile*/ undefined, getCustomTransformers); - sys.exit(exitStatus); - sys.write(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}\n`); - const baseline: string[] = []; - tscWatch.baselinePrograms(baseline, getPrograms, emptyArray, /*baselineDependencies*/ false); - sys.write(baseline.join("\n")); - fs.makeReadonly(); - sys.baseLine = () => { - const baseFsPatch = inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }); - const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true }); - return { - file: `tsbuild/publicAPI/build-with-custom-transformers.js`, - text: `Input:: + // Create system + sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }) as ts.TscCompileSystem; + fakes.patchHostForBuildInfoReadWrite(sys); + const commandLineArgs = ["--b", "/src/tsconfig.json"]; + sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`); + sys.exit = exitCode => sys.exitCode = exitCode; + const writtenFiles = sys.writtenFiles = new ts.Set(); + const originalWriteFile = sys.writeFile; + sys.writeFile = (fileName, content, writeByteOrderMark) => { + const path = ts.toPathWithSystem(sys, fileName); + assert.isFalse(writtenFiles.has(path)); + writtenFiles.add(path); + return originalWriteFile.call(sys, fileName, content, writeByteOrderMark); + }; + const { cb, getPrograms } = ts.commandLineCallbacks(sys, /*originalReadCall*/ undefined); + const buildHost = ts.createSolutionBuilderHost( + sys, + /*createProgram*/ undefined, + ts.createDiagnosticReporter(sys, /*pretty*/ true), + ts.createBuilderStatusReporter(sys, /*pretty*/ true), + (errorCount, filesInError) => sys.write(ts.getErrorSummaryText(errorCount, filesInError, sys.newLine, sys)) + ); + buildHost.afterProgramEmitAndDiagnostics = cb; + buildHost.afterEmitBundle = cb; + const builder = ts.createSolutionBuilder(buildHost, [commandLineArgs[1]], { verbose: true }); + const exitStatus = builder.build(/*project*/ undefined, /*cancellationToken*/ undefined, /*writeFile*/ undefined, getCustomTransformers); + sys.exit(exitStatus); + sys.write(`exitCode:: ExitStatus.${ts.ExitStatus[sys.exitCode as ts.ExitStatus]}\n`); + const baseline: string[] = []; + ts.tscWatch.baselinePrograms(baseline, getPrograms, ts.emptyArray, /*baselineDependencies*/ false); + sys.write(baseline.join("\n")); + fs.makeReadonly(); + sys.baseLine = () => { + const baseFsPatch = inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }); + const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true }); + return { + file: `tsbuild/publicAPI/build-with-custom-transformers.js`, + text: `Input:: ${baseFsPatch ? vfs.formatPatch(baseFsPatch) : ""} Output:: ${sys.output.join("")} ${patch ? vfs.formatPatch(patch) : ""}` - }; }; + }; - function getCustomTransformers(project: string): CustomTransformers { - const before: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - return visitFunction(node as FunctionDeclaration); - default: - return visitEachChild(node, visit, context); - } + function getCustomTransformers(project: string): ts.CustomTransformers { + const before: ts.TransformerFactory = context => { + return file => ts.visitEachChild(file, visit, context); + function visit(node: ts.Node): ts.VisitResult { + switch (node.kind) { + case ts.SyntaxKind.FunctionDeclaration: + return visitFunction(node as ts.FunctionDeclaration); + default: + return ts.visitEachChild(node, visit, context); } - function visitFunction(node: FunctionDeclaration) { - addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, `@before${project}`, /*hasTrailingNewLine*/ true); - return node; - } - }; + } + function visitFunction(node: ts.FunctionDeclaration) { + ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, `@before${project}`, /*hasTrailingNewLine*/ true); + return node; + } + }; - const after: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.VariableStatement: - return visitVariableStatement(node as VariableStatement); - default: - return visitEachChild(node, visit, context); - } + const after: ts.TransformerFactory = context => { + return file => ts.visitEachChild(file, visit, context); + function visit(node: ts.Node): ts.VisitResult { + switch (node.kind) { + case ts.SyntaxKind.VariableStatement: + return visitVariableStatement(node as ts.VariableStatement); + default: + return ts.visitEachChild(node, visit, context); } - function visitVariableStatement(node: VariableStatement) { - addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, `@after${project}`); - return node; - } - }; - return { before: [before], after: [after] }; - } - }); - after(() => { - sys = undefined!; - }); - verifyTscBaseline(() => sys); + } + function visitVariableStatement(node: ts.VariableStatement) { + ts.addSyntheticLeadingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, `@after${project}`); + return node; + } + }; + return { before: [before], after: [after] }; + } + }); + after(() => { + sys = undefined!; }); -} + ts.verifyTscBaseline(() => sys); +}); diff --git a/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts b/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts index bd78954780aab..1d0d2f131a264 100644 --- a/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts +++ b/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts @@ -1,61 +1,62 @@ -namespace ts { - describe("unittests:: tsbuild:: with rootDir of project reference in parentDirectory", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent"); - }); +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; - after(() => { - projFs = undefined!; // Release the contents - }); +describe("unittests:: tsbuild:: with rootDir of project reference in parentDirectory", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = ts.loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent"); + }); + + after(() => { + projFs = undefined!; // Release the contents + }); - verifyTsc({ - scenario: "projectReferenceWithRootDirInParent", - subScenario: "builds correctly", - fs: () => projFs, - commandLineArgs: ["--b", "/src/src/main", "/src/src/other"], - }); + ts.verifyTsc({ + scenario: "projectReferenceWithRootDirInParent", + subScenario: "builds correctly", + fs: () => projFs, + commandLineArgs: ["--b", "/src/src/main", "/src/src/other"], + }); - verifyTsc({ - scenario: "projectReferenceWithRootDirInParent", - subScenario: "reports error for same tsbuildinfo file because no rootDir in the base", - fs: () => projFs, - commandLineArgs: ["--b", "/src/src/main", "--verbose"], - modifyFs: fs => replaceText(fs, "/src/tsconfig.base.json", `"rootDir": "./src/",`, ""), - }); + ts.verifyTsc({ + scenario: "projectReferenceWithRootDirInParent", + subScenario: "reports error for same tsbuildinfo file because no rootDir in the base", + fs: () => projFs, + commandLineArgs: ["--b", "/src/src/main", "--verbose"], + modifyFs: fs => ts.replaceText(fs, "/src/tsconfig.base.json", `"rootDir": "./src/",`, ""), + }); - verifyTsc({ - scenario: "projectReferenceWithRootDirInParent", - subScenario: "reports error for same tsbuildinfo file", - fs: () => projFs, - commandLineArgs: ["--b", "/src/src/main", "--verbose"], - modifyFs: fs => { - fs.writeFileSync("/src/src/main/tsconfig.json", JSON.stringify({ - compilerOptions: { composite: true, outDir: "../../dist/" }, - references: [{ path: "../other" }] - })); - fs.writeFileSync("/src/src/other/tsconfig.json", JSON.stringify({ - compilerOptions: { composite: true, outDir: "../../dist/" }, - })); - }, - }); + ts.verifyTsc({ + scenario: "projectReferenceWithRootDirInParent", + subScenario: "reports error for same tsbuildinfo file", + fs: () => projFs, + commandLineArgs: ["--b", "/src/src/main", "--verbose"], + modifyFs: fs => { + fs.writeFileSync("/src/src/main/tsconfig.json", JSON.stringify({ + compilerOptions: { composite: true, outDir: "../../dist/" }, + references: [{ path: "../other" }] + })); + fs.writeFileSync("/src/src/other/tsconfig.json", JSON.stringify({ + compilerOptions: { composite: true, outDir: "../../dist/" }, + })); + }, + }); - verifyTsc({ - scenario: "projectReferenceWithRootDirInParent", - subScenario: "reports no error when tsbuildinfo differ", - fs: () => projFs, - commandLineArgs: ["--b", "/src/src/main/tsconfig.main.json", "--verbose"], - modifyFs: fs => { - fs.renameSync("/src/src/main/tsconfig.json", "/src/src/main/tsconfig.main.json"); - fs.renameSync("/src/src/other/tsconfig.json", "/src/src/other/tsconfig.other.json"); - fs.writeFileSync("/src/src/main/tsconfig.main.json", JSON.stringify({ - compilerOptions: { composite: true, outDir: "../../dist/" }, - references: [{ path: "../other/tsconfig.other.json" }] - })); - fs.writeFileSync("/src/src/other/tsconfig.other.json", JSON.stringify({ - compilerOptions: { composite: true, outDir: "../../dist/" }, - })); - }, - }); + ts.verifyTsc({ + scenario: "projectReferenceWithRootDirInParent", + subScenario: "reports no error when tsbuildinfo differ", + fs: () => projFs, + commandLineArgs: ["--b", "/src/src/main/tsconfig.main.json", "--verbose"], + modifyFs: fs => { + fs.renameSync("/src/src/main/tsconfig.json", "/src/src/main/tsconfig.main.json"); + fs.renameSync("/src/src/other/tsconfig.json", "/src/src/other/tsconfig.other.json"); + fs.writeFileSync("/src/src/main/tsconfig.main.json", JSON.stringify({ + compilerOptions: { composite: true, outDir: "../../dist/" }, + references: [{ path: "../other/tsconfig.other.json" }] + })); + fs.writeFileSync("/src/src/other/tsconfig.other.json", JSON.stringify({ + compilerOptions: { composite: true, outDir: "../../dist/" }, + })); + }, }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts index 834d5825377f2..0d765605c8f71 100644 --- a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts +++ b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts @@ -1,82 +1,83 @@ -namespace ts { - describe("unittests:: tsbuild:: with resolveJsonModule option on project resolveJsonModuleAndComposite", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite"); - }); +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; - after(() => { - projFs = undefined!; // Release the contents - }); +describe("unittests:: tsbuild:: with resolveJsonModule option on project resolveJsonModuleAndComposite", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = ts.loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite"); + }); - verifyTsc({ - scenario: "resolveJsonModule", - subScenario: "include only", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig_withInclude.json", "--v", "--explainFiles"], - }); + after(() => { + projFs = undefined!; // Release the contents + }); - verifyTsc({ - scenario: "resolveJsonModule", - subScenario: "include of json along with other include", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig_withIncludeOfJson.json", "--v", "--explainFiles"], - }); + ts.verifyTsc({ + scenario: "resolveJsonModule", + subScenario: "include only", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig_withInclude.json", "--v", "--explainFiles"], + }); + + ts.verifyTsc({ + scenario: "resolveJsonModule", + subScenario: "include of json along with other include", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig_withIncludeOfJson.json", "--v", "--explainFiles"], + }); - verifyTsc({ - scenario: "resolveJsonModule", - subScenario: "include of json along with other include and file name matches ts file", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig_withIncludeOfJson.json", "--v", "--explainFiles"], - modifyFs: fs => { - fs.rimrafSync("/src/src/hello.json"); - fs.writeFileSync("/src/src/index.json", JSON.stringify({ hello: "world" })); - fs.writeFileSync("/src/src/index.ts", `import hello from "./index.json" + ts.verifyTsc({ + scenario: "resolveJsonModule", + subScenario: "include of json along with other include and file name matches ts file", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig_withIncludeOfJson.json", "--v", "--explainFiles"], + modifyFs: fs => { + fs.rimrafSync("/src/src/hello.json"); + fs.writeFileSync("/src/src/index.json", JSON.stringify({ hello: "world" })); + fs.writeFileSync("/src/src/index.ts", `import hello from "./index.json" export default hello.hello`); - }, - }); + }, + }); - verifyTsc({ - scenario: "resolveJsonModule", - subScenario: "files containing json file", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig_withFiles.json", "--v", "--explainFiles"], - }); + ts.verifyTsc({ + scenario: "resolveJsonModule", + subScenario: "files containing json file", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig_withFiles.json", "--v", "--explainFiles"], + }); - verifyTsc({ - scenario: "resolveJsonModule", - subScenario: "include and files", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig_withIncludeAndFiles.json", "--v", "--explainFiles"], - }); + ts.verifyTsc({ + scenario: "resolveJsonModule", + subScenario: "include and files", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig_withIncludeAndFiles.json", "--v", "--explainFiles"], + }); - verifyTscWithEdits({ - scenario: "resolveJsonModule", - subScenario: "sourcemap", - fs: () => projFs, - commandLineArgs: ["--b", "src/tsconfig_withFiles.json", "--verbose", "--explainFiles"], - modifyFs: fs => replaceText(fs, "src/tsconfig_withFiles.json", `"composite": true,`, `"composite": true, "sourceMap": true,`), - edits: noChangeOnlyRuns - }); + ts.verifyTscWithEdits({ + scenario: "resolveJsonModule", + subScenario: "sourcemap", + fs: () => projFs, + commandLineArgs: ["--b", "src/tsconfig_withFiles.json", "--verbose", "--explainFiles"], + modifyFs: fs => ts.replaceText(fs, "src/tsconfig_withFiles.json", `"composite": true,`, `"composite": true, "sourceMap": true,`), + edits: ts.noChangeOnlyRuns + }); - verifyTscWithEdits({ - scenario: "resolveJsonModule", - subScenario: "without outDir", - fs: () => projFs, - commandLineArgs: ["--b", "src/tsconfig_withFiles.json", "--verbose"], - modifyFs: fs => replaceText(fs, "src/tsconfig_withFiles.json", `"outDir": "dist",`, ""), - edits: noChangeOnlyRuns - }); + ts.verifyTscWithEdits({ + scenario: "resolveJsonModule", + subScenario: "without outDir", + fs: () => projFs, + commandLineArgs: ["--b", "src/tsconfig_withFiles.json", "--verbose"], + modifyFs: fs => ts.replaceText(fs, "src/tsconfig_withFiles.json", `"outDir": "dist",`, ""), + edits: ts.noChangeOnlyRuns }); +}); - describe("unittests:: tsbuild:: with resolveJsonModule option on project importJsonFromProjectReference", () => { - verifyTscWithEdits({ - scenario: "resolveJsonModule", - subScenario: "importing json module from project reference", - fs: () => loadProjectFromDisk("tests/projects/importJsonFromProjectReference"), - commandLineArgs: ["--b", "src/tsconfig.json", "--verbose", "--explainFiles"], - edits: noChangeOnlyRuns - }); +describe("unittests:: tsbuild:: with resolveJsonModule option on project importJsonFromProjectReference", () => { + ts.verifyTscWithEdits({ + scenario: "resolveJsonModule", + subScenario: "importing json module from project reference", + fs: () => ts.loadProjectFromDisk("tests/projects/importJsonFromProjectReference"), + commandLineArgs: ["--b", "src/tsconfig.json", "--verbose", "--explainFiles"], + edits: ts.noChangeOnlyRuns }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index a592582f5e1aa..60e8e7b7213fb 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -1,507 +1,511 @@ -namespace ts { - describe("unittests:: tsbuild:: on 'sample1' project", () => { - let projFs: vfs.FileSystem; - let projFsWithBuild: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/sample1"); - }); - - after(() => { - projFs = undefined!; // Release the contents - projFsWithBuild = undefined!; - }); - - function getTsBuildProjectFile(project: string, file: string): tscWatch.File { - return { - path: TestFSWithWatch.getTsBuildProjectFilePath(project, file), - content: projFs.readFileSync(`/src/${project}/${file}`, "utf8")! - }; - } - - function getSampleFsAfterBuild() { - if (projFsWithBuild) return projFsWithBuild; - const fs = projFs.shadow(); - const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }); - const host = createSolutionBuilderHostForBaseline(sys as TscCompileSystem); - const builder = createSolutionBuilder(host, ["/src/tests"], {}); - builder.build(); - fs.makeReadonly(); - return projFsWithBuild = fs; - } - - describe("sanity check of clean build of 'sample1' project", () => { - verifyTsc({ - scenario: "sample1", - subScenario: "builds correctly when outDir is specified", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests"], - modifyFs: fs => fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({ - compilerOptions: { composite: true, declaration: true, sourceMap: true, outDir: "outDir" }, - references: [{ path: "../core" }] - })), - }); - - verifyTsc({ - scenario: "sample1", - subScenario: "builds correctly when declarationDir is specified", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests"], - modifyFs: fs => fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({ - compilerOptions: { composite: true, declaration: true, sourceMap: true, declarationDir: "out/decls" }, - references: [{ path: "../core" }] - })), - }); - - verifyTsc({ - scenario: "sample1", - subScenario: "builds correctly when project is not composite or doesnt have any references", - fs: () => projFs, - commandLineArgs: ["--b", "/src/core", "--verbose"], - modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"composite": true,`, ""), - }); - }); - - describe("dry builds", () => { - verifyTsc({ - scenario: "sample1", - subScenario: "does not write any files in a dry build", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--dry"], - }); - }); - - describe("clean builds", () => { - verifyTscWithEdits({ - scenario: "sample1", - subScenario: "removes all files it built", - fs: getSampleFsAfterBuild, - commandLineArgs: ["--b", "/src/tests", "--clean"], - edits: noChangeOnlyRuns - }); - - verifyTscCompileLike(testTscCompileLike, { - scenario: "sample1", - subScenario: "cleans till project specified", - fs: getSampleFsAfterBuild, - commandLineArgs: ["--b", "/src/logic", "--clean"], - compile: sys => { - const buildHost = createSolutionBuilderHostForBaseline(sys); - const builder = createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {}); - sys.exit(builder.clean("/src/logic")); - } - }); - - verifyTscCompileLike(testTscCompileLike, { - scenario: "sample1", - subScenario: "cleaning project in not build order doesnt throw error", - fs: getSampleFsAfterBuild, - commandLineArgs: ["--b", "/src/logic2", "--clean"], - compile: sys => { - const buildHost = createSolutionBuilderHostForBaseline(sys); - const builder = createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {}); - sys.exit(builder.clean("/src/logic2")); - } - }); - }); - - describe("force builds", () => { - verifyTscWithEdits({ - scenario: "sample1", - subScenario: "always builds under with force option", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--force"], - edits: noChangeOnlyRuns - }); - }); - - describe("can detect when and what to rebuild", () => { - verifyTscWithEdits({ - scenario: "sample1", - subScenario: "can detect when and what to rebuild", - fs: getSampleFsAfterBuild, - commandLineArgs: ["--b", "/src/tests", "--verbose"], - edits: [ - // Update a file in the leaf node (tests), only it should rebuild the last one - { - subScenario: "Only builds the leaf node project", - modifyFs: fs => fs.writeFileSync("/src/tests/index.ts", "const m = 10;"), - }, - // Update a file in the parent (without affecting types), should get fast downstream builds - { - subScenario: "Detects type-only changes in upstream projects", - modifyFs: fs => replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET"), - }, - { - subScenario: "rebuilds when tsconfig changes", - modifyFs: fs => replaceText(fs, "/src/tests/tsconfig.json", `"composite": true`, `"composite": true, "target": "es3"`), - }, - ] - }); - - verifyTscWithEdits({ - scenario: "sample1", - subScenario: "when input file text does not change but its modified time changes", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--verbose"], - edits: [ - { - subScenario: "upstream project changes without changing file text", - modifyFs: fs => { - const time = new Date(fs.time()); - fs.utimesSync("/src/core/index.ts", time, time); - }, - }, - ] - }); - - verifyTscWithEdits({ - scenario: "sample1", - subScenario: "when declarationMap changes", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--verbose"], - edits: [ - { - subScenario: "Disable declarationMap", - modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"declarationMap": true,`, `"declarationMap": false,`), - }, - { - subScenario: "Enable declarationMap", - modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"declarationMap": false,`, `"declarationMap": true,`), +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; +import * as fakes from "../../_namespaces/fakes"; +import * as Harness from "../../_namespaces/Harness"; + +describe("unittests:: tsbuild:: on 'sample1' project", () => { + let projFs: vfs.FileSystem; + let projFsWithBuild: vfs.FileSystem; + before(() => { + projFs = ts.loadProjectFromDisk("tests/projects/sample1"); + }); + + after(() => { + projFs = undefined!; // Release the contents + projFsWithBuild = undefined!; + }); + + function getTsBuildProjectFile(project: string, file: string): ts.tscWatch.File { + return { + path: ts.TestFSWithWatch.getTsBuildProjectFilePath(project, file), + content: projFs.readFileSync(`/src/${project}/${file}`, "utf8")! + }; + } + + function getSampleFsAfterBuild() { + if (projFsWithBuild) return projFsWithBuild; + const fs = projFs.shadow(); + const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }); + const host = ts.createSolutionBuilderHostForBaseline(sys as ts.TscCompileSystem); + const builder = ts.createSolutionBuilder(host, ["/src/tests"], {}); + builder.build(); + fs.makeReadonly(); + return projFsWithBuild = fs; + } + + describe("sanity check of clean build of 'sample1' project", () => { + ts.verifyTsc({ + scenario: "sample1", + subScenario: "builds correctly when outDir is specified", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests"], + modifyFs: fs => fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({ + compilerOptions: { composite: true, declaration: true, sourceMap: true, outDir: "outDir" }, + references: [{ path: "../core" }] + })), + }); + + ts.verifyTsc({ + scenario: "sample1", + subScenario: "builds correctly when declarationDir is specified", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests"], + modifyFs: fs => fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({ + compilerOptions: { composite: true, declaration: true, sourceMap: true, declarationDir: "out/decls" }, + references: [{ path: "../core" }] + })), + }); + + ts.verifyTsc({ + scenario: "sample1", + subScenario: "builds correctly when project is not composite or doesnt have any references", + fs: () => projFs, + commandLineArgs: ["--b", "/src/core", "--verbose"], + modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", `"composite": true,`, ""), + }); + }); + + describe("dry builds", () => { + ts.verifyTsc({ + scenario: "sample1", + subScenario: "does not write any files in a dry build", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--dry"], + }); + }); + + describe("clean builds", () => { + ts.verifyTscWithEdits({ + scenario: "sample1", + subScenario: "removes all files it built", + fs: getSampleFsAfterBuild, + commandLineArgs: ["--b", "/src/tests", "--clean"], + edits: ts.noChangeOnlyRuns + }); + + ts.verifyTscCompileLike(ts.testTscCompileLike, { + scenario: "sample1", + subScenario: "cleans till project specified", + fs: getSampleFsAfterBuild, + commandLineArgs: ["--b", "/src/logic", "--clean"], + compile: sys => { + const buildHost = ts.createSolutionBuilderHostForBaseline(sys); + const builder = ts.createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {}); + sys.exit(builder.clean("/src/logic")); + } + }); + + ts.verifyTscCompileLike(ts.testTscCompileLike, { + scenario: "sample1", + subScenario: "cleaning project in not build order doesnt throw error", + fs: getSampleFsAfterBuild, + commandLineArgs: ["--b", "/src/logic2", "--clean"], + compile: sys => { + const buildHost = ts.createSolutionBuilderHostForBaseline(sys); + const builder = ts.createSolutionBuilder(buildHost, ["/src/third/tsconfig.json"], {}); + sys.exit(builder.clean("/src/logic2")); + } + }); + }); + + describe("force builds", () => { + ts.verifyTscWithEdits({ + scenario: "sample1", + subScenario: "always builds under with force option", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--force"], + edits: ts.noChangeOnlyRuns + }); + }); + + describe("can detect when and what to rebuild", () => { + ts.verifyTscWithEdits({ + scenario: "sample1", + subScenario: "can detect when and what to rebuild", + fs: getSampleFsAfterBuild, + commandLineArgs: ["--b", "/src/tests", "--verbose"], + edits: [ + // Update a file in the leaf node (tests), only it should rebuild the last one + { + subScenario: "Only builds the leaf node project", + modifyFs: fs => fs.writeFileSync("/src/tests/index.ts", "const m = 10;"), + }, + // Update a file in the parent (without affecting types), should get fast downstream builds + { + subScenario: "Detects type-only changes in upstream projects", + modifyFs: fs => ts.replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET"), + }, + { + subScenario: "rebuilds when tsconfig changes", + modifyFs: fs => ts.replaceText(fs, "/src/tests/tsconfig.json", `"composite": true`, `"composite": true, "target": "es3"`), + }, + ] + }); + + ts.verifyTscWithEdits({ + scenario: "sample1", + subScenario: "when input file text does not change but its modified time changes", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--verbose"], + edits: [ + { + subScenario: "upstream project changes without changing file text", + modifyFs: fs => { + const time = new Date(fs.time()); + fs.utimesSync("/src/core/index.ts", time, time); }, - ] - }); - - verifyTsc({ - scenario: "sample1", - subScenario: "indicates that it would skip builds during a dry build", - fs: getSampleFsAfterBuild, - commandLineArgs: ["--b", "/src/tests", "--dry"], - }); - - verifyTsc({ - scenario: "sample1", - subScenario: "rebuilds from start if force option is set", - fs: getSampleFsAfterBuild, - commandLineArgs: ["--b", "/src/tests", "--verbose", "--force"], - }); - - verifyTscWithEdits({ - scenario: "sample1", - subScenario: "tsbuildinfo has error", - fs: () => loadProjectFromFiles({ - "/src/project/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": "{}", - "/src/project/tsconfig.tsbuildinfo": "Some random string", - }), - commandLineArgs: ["--b", "src/project", "-i", "-v"], - edits: [{ - subScenario: "tsbuildinfo written has error", - modifyFs: fs => prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"), - }] - }); - - verifyTscCompileLike(testTscCompileLike, { - scenario: "sample1", - subScenario: "rebuilds completely when version in tsbuildinfo doesnt match ts version", - fs: getSampleFsAfterBuild, - commandLineArgs: ["--b", "/src/tests", "--verbose"], - compile: sys => { - // Buildinfo will have version which does not match with current ts version - const buildHost = createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion"); - const builder = createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true }); - sys.exit(builder.build()); - } - }); - - verifyTscCompileLike(testTscCompileLike, { - scenario: "sample1", - subScenario: "does not rebuild if there is no program and bundle in the ts build info event if version doesnt match ts version", - fs: () => { - const fs = projFs.shadow(); - const host = fakes.SolutionBuilderHost.create(fs, /*options*/ undefined, /*setParentNodes*/ undefined, createAbstractBuilder); - const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); - builder.build(); - fs.makeReadonly(); - return fs; }, - commandLineArgs: ["--b", "/src/tests", "--verbose"], - compile: sys => { - // Buildinfo will have version which does not match with current ts version - const buildHost = createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion"); - const builder = createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true }); - sys.exit(builder.build()); + ] + }); + + ts.verifyTscWithEdits({ + scenario: "sample1", + subScenario: "when declarationMap changes", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--verbose"], + edits: [ + { + subScenario: "Disable declarationMap", + modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", `"declarationMap": true,`, `"declarationMap": false,`), }, - }); - - verifyTscWithEdits({ - scenario: "sample1", - subScenario: "rebuilds when extended config file changes", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--verbose"], - modifyFs: fs => { - fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: { target: "es3" } })); - replaceText(fs, "/src/tests/tsconfig.json", `"references": [`, `"extends": "./tsconfig.base.json", "references": [`); + { + subScenario: "Enable declarationMap", + modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", `"declarationMap": false,`, `"declarationMap": true,`), }, - edits: [{ - subScenario: "incremental-declaration-changes", - modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: {} })) - }] - }); - - verifyTscCompileLike(testTscCompileLike, { - scenario: "sample1", - subScenario: "builds till project specified", - fs: () => projFs, - commandLineArgs: ["--build", "/src/logic/tsconfig.json"], - compile: sys => { - const buildHost = createSolutionBuilderHostForBaseline(sys); - const builder = createSolutionBuilder(buildHost, ["/src/tests"], {}); - sys.exit(builder.build("/src/logic/tsconfig.json")); - } - }); - - verifyTscCompileLike(testTscCompileLike, { - scenario: "sample1", - subScenario: "building project in not build order doesnt throw error", - fs: () => projFs, - commandLineArgs: ["--build", "/src/logic2/tsconfig.json"], - compile: sys => { - const buildHost = createSolutionBuilderHostForBaseline(sys); - const builder = createSolutionBuilder(buildHost, ["/src/tests"], {}); - sys.exit(builder.build("/src/logic2/tsconfig.json")); - } - }); - - it("building using getNextInvalidatedProject", () => { - const coreConfig = getTsBuildProjectFile("core", "tsconfig.json"); - const coreIndex = getTsBuildProjectFile("core", "index.ts"); - const coreDecl = getTsBuildProjectFile("core", "some_decl.d.ts"); - const coreAnotherModule = getTsBuildProjectFile("core", "anotherModule.ts"); - const logicConfig = getTsBuildProjectFile("logic", "tsconfig.json"); - const logicIndex = getTsBuildProjectFile("logic", "index.ts"); - const testsConfig = getTsBuildProjectFile("tests", "tsconfig.json"); - const testsIndex = getTsBuildProjectFile("tests", "index.ts"); - const baseline: string[] = []; - let oldSnap: ReturnType | undefined; - const system = TestFSWithWatch.changeToHostTrackingWrittenFiles( - fakes.patchHostForBuildInfoReadWrite( - tscWatch.createWatchedSystem([ - coreConfig, coreIndex, coreDecl, coreAnotherModule, - logicConfig, logicIndex, - testsConfig, testsIndex, - tscWatch.libFile - ]) - ) - ); - - const host = createSolutionBuilderHostForBaseline(system); - const builder = createSolutionBuilder(host, [testsConfig.path], {}); - baseline.push("Input::"); - baselineState(); - verifyBuildNextResult(); // core - verifyBuildNextResult(); // logic - verifyBuildNextResult();// tests - verifyBuildNextResult(); // All Done - Harness.Baseline.runBaseline(`tsbuild/sample1/building-using-getNextInvalidatedProject.js`, baseline.join("\r\n")); - - function verifyBuildNextResult() { - const project = builder.getNextInvalidatedProject(); - const result = project && project.done(); - baseline.push(`Project Result:: ${JSON.stringify({ project: project?.project, result })}`); - baselineState(); - } - - function baselineState() { - system.serializeOutput(baseline); - system.diff(baseline, oldSnap); - system.writtenFiles.clear(); - oldSnap = system.snap(); - } - }); - - verifyTscCompileLike(testTscCompileLike, { - scenario: "sample1", - subScenario: "building using buildReferencedProject", - fs: () => projFs, - commandLineArgs: ["--build", "/src/logic2/tsconfig.json"], - compile: sys => { - const buildHost = createSolutionBuilderHostForBaseline(sys); - const builder = createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true }); - sys.exit(builder.buildReferences("/src/tests")); - } - }); - }); - - describe("downstream-blocked compilations", () => { - verifyTscWithEdits({ - scenario: "sample1", - subScenario: "does not build downstream projects if upstream projects have errors", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--verbose"], - modifyFs: fs => replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`), - edits: noChangeOnlyRuns - }); - }); - - describe("project invalidation", () => { - it("invalidates projects correctly", () => { - const coreConfig = getTsBuildProjectFile("core", "tsconfig.json"); - const coreIndex = getTsBuildProjectFile("core", "index.ts"); - const coreDecl = getTsBuildProjectFile("core", "some_decl.d.ts"); - const coreAnotherModule = getTsBuildProjectFile("core", "anotherModule.ts"); - const logicConfig = getTsBuildProjectFile("logic", "tsconfig.json"); - const logicIndex = getTsBuildProjectFile("logic", "index.ts"); - const testsConfig = getTsBuildProjectFile("tests", "tsconfig.json"); - const testsIndex = getTsBuildProjectFile("tests", "index.ts"); - const baseline: string[] = []; - let oldSnap: ReturnType | undefined; - const system = TestFSWithWatch.changeToHostTrackingWrittenFiles( - fakes.patchHostForBuildInfoReadWrite( - tscWatch.createWatchedSystem([ - coreConfig, coreIndex, coreDecl, coreAnotherModule, - logicConfig, logicIndex, - testsConfig, testsIndex, - tscWatch.libFile - ]) - ) - ); - - const host = createSolutionBuilderHostForBaseline(system); - const builder = createSolutionBuilder(host, [testsConfig.path], { dry: false, force: false, verbose: false }); + ] + }); + + ts.verifyTsc({ + scenario: "sample1", + subScenario: "indicates that it would skip builds during a dry build", + fs: getSampleFsAfterBuild, + commandLineArgs: ["--b", "/src/tests", "--dry"], + }); + + ts.verifyTsc({ + scenario: "sample1", + subScenario: "rebuilds from start if force option is set", + fs: getSampleFsAfterBuild, + commandLineArgs: ["--b", "/src/tests", "--verbose", "--force"], + }); + + ts.verifyTscWithEdits({ + scenario: "sample1", + subScenario: "tsbuildinfo has error", + fs: () => ts.loadProjectFromFiles({ + "/src/project/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": "{}", + "/src/project/tsconfig.tsbuildinfo": "Some random string", + }), + commandLineArgs: ["--b", "src/project", "-i", "-v"], + edits: [{ + subScenario: "tsbuildinfo written has error", + modifyFs: fs => ts.prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"), + }] + }); + + ts.verifyTscCompileLike(ts.testTscCompileLike, { + scenario: "sample1", + subScenario: "rebuilds completely when version in tsbuildinfo doesnt match ts version", + fs: getSampleFsAfterBuild, + commandLineArgs: ["--b", "/src/tests", "--verbose"], + compile: sys => { + // Buildinfo will have version which does not match with current ts version + const buildHost = ts.createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion"); + const builder = ts.createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true }); + sys.exit(builder.build()); + } + }); + + ts.verifyTscCompileLike(ts.testTscCompileLike, { + scenario: "sample1", + subScenario: "does not rebuild if there is no program and bundle in the ts build info event if version doesnt match ts version", + fs: () => { + const fs = projFs.shadow(); + const host = fakes.SolutionBuilderHost.create(fs, /*options*/ undefined, /*setParentNodes*/ undefined, ts.createAbstractBuilder); + const builder = ts.createSolutionBuilder(host, ["/src/tests"], { verbose: true }); builder.build(); - baselineState("Build of project"); + fs.makeReadonly(); + return fs; + }, + commandLineArgs: ["--b", "/src/tests", "--verbose"], + compile: sys => { + // Buildinfo will have version which does not match with current ts version + const buildHost = ts.createSolutionBuilderHostForBaseline(sys, "FakeTSCurrentVersion"); + const builder = ts.createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true }); + sys.exit(builder.build()); + }, + }); + + ts.verifyTscWithEdits({ + scenario: "sample1", + subScenario: "rebuilds when extended config file changes", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--verbose"], + modifyFs: fs => { + fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: { target: "es3" } })); + ts.replaceText(fs, "/src/tests/tsconfig.json", `"references": [`, `"extends": "./tsconfig.base.json", "references": [`); + }, + edits: [{ + subScenario: "incremental-declaration-changes", + modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: {} })) + }] + }); + + ts.verifyTscCompileLike(ts.testTscCompileLike, { + scenario: "sample1", + subScenario: "builds till project specified", + fs: () => projFs, + commandLineArgs: ["--build", "/src/logic/tsconfig.json"], + compile: sys => { + const buildHost = ts.createSolutionBuilderHostForBaseline(sys); + const builder = ts.createSolutionBuilder(buildHost, ["/src/tests"], {}); + sys.exit(builder.build("/src/logic/tsconfig.json")); + } + }); - // Update a timestamp in the middle project - system.appendFile(logicIndex.path, "function foo() {}"); + ts.verifyTscCompileLike(ts.testTscCompileLike, { + scenario: "sample1", + subScenario: "building project in not build order doesnt throw error", + fs: () => projFs, + commandLineArgs: ["--build", "/src/logic2/tsconfig.json"], + compile: sys => { + const buildHost = ts.createSolutionBuilderHostForBaseline(sys); + const builder = ts.createSolutionBuilder(buildHost, ["/src/tests"], {}); + sys.exit(builder.build("/src/logic2/tsconfig.json")); + } + }); - // Because we haven't reset the build context, the builder should assume there's nothing to do right now - const status = builder.getUpToDateStatusOfProject(logicConfig.path); - baseline.push(`Project should still be upto date: ${UpToDateStatusType[status.type]}`); - verifyInvalidation("non Dts change to logic"); + it("building using getNextInvalidatedProject", () => { + const coreConfig = getTsBuildProjectFile("core", "tsconfig.json"); + const coreIndex = getTsBuildProjectFile("core", "index.ts"); + const coreDecl = getTsBuildProjectFile("core", "some_decl.d.ts"); + const coreAnotherModule = getTsBuildProjectFile("core", "anotherModule.ts"); + const logicConfig = getTsBuildProjectFile("logic", "tsconfig.json"); + const logicIndex = getTsBuildProjectFile("logic", "index.ts"); + const testsConfig = getTsBuildProjectFile("tests", "tsconfig.json"); + const testsIndex = getTsBuildProjectFile("tests", "index.ts"); + const baseline: string[] = []; + let oldSnap: ReturnType | undefined; + const system = ts.TestFSWithWatch.changeToHostTrackingWrittenFiles( + fakes.patchHostForBuildInfoReadWrite( + ts.tscWatch.createWatchedSystem([ + coreConfig, coreIndex, coreDecl, coreAnotherModule, + logicConfig, logicIndex, + testsConfig, testsIndex, + ts.tscWatch.libFile + ]) + ) + ); + + const host = ts.createSolutionBuilderHostForBaseline(system); + const builder = ts.createSolutionBuilder(host, [testsConfig.path], {}); + baseline.push("Input::"); + baselineState(); + verifyBuildNextResult(); // core + verifyBuildNextResult(); // logic + verifyBuildNextResult();// tests + verifyBuildNextResult(); // All Done + Harness.Baseline.runBaseline(`tsbuild/sample1/building-using-getNextInvalidatedProject.js`, baseline.join("\r\n")); + + function verifyBuildNextResult() { + const project = builder.getNextInvalidatedProject(); + const result = project && project.done(); + baseline.push(`Project Result:: ${JSON.stringify({ project: project?.project, result })}`); + baselineState(); + } + + function baselineState() { + system.serializeOutput(baseline); + system.diff(baseline, oldSnap); + system.writtenFiles.clear(); + oldSnap = system.snap(); + } + }); + ts.verifyTscCompileLike(ts.testTscCompileLike, { + scenario: "sample1", + subScenario: "building using buildReferencedProject", + fs: () => projFs, + commandLineArgs: ["--build", "/src/logic2/tsconfig.json"], + compile: sys => { + const buildHost = ts.createSolutionBuilderHostForBaseline(sys); + const builder = ts.createSolutionBuilder(buildHost, ["/src/tests"], { verbose: true }); + sys.exit(builder.buildReferences("/src/tests")); + } + }); + }); + + describe("downstream-blocked compilations", () => { + ts.verifyTscWithEdits({ + scenario: "sample1", + subScenario: "does not build downstream projects if upstream projects have errors", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--verbose"], + modifyFs: fs => ts.replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`), + edits: ts.noChangeOnlyRuns + }); + }); + + describe("project invalidation", () => { + it("invalidates projects correctly", () => { + const coreConfig = getTsBuildProjectFile("core", "tsconfig.json"); + const coreIndex = getTsBuildProjectFile("core", "index.ts"); + const coreDecl = getTsBuildProjectFile("core", "some_decl.d.ts"); + const coreAnotherModule = getTsBuildProjectFile("core", "anotherModule.ts"); + const logicConfig = getTsBuildProjectFile("logic", "tsconfig.json"); + const logicIndex = getTsBuildProjectFile("logic", "index.ts"); + const testsConfig = getTsBuildProjectFile("tests", "tsconfig.json"); + const testsIndex = getTsBuildProjectFile("tests", "index.ts"); + const baseline: string[] = []; + let oldSnap: ReturnType | undefined; + const system = ts.TestFSWithWatch.changeToHostTrackingWrittenFiles( + fakes.patchHostForBuildInfoReadWrite( + ts.tscWatch.createWatchedSystem([ + coreConfig, coreIndex, coreDecl, coreAnotherModule, + logicConfig, logicIndex, + testsConfig, testsIndex, + ts.tscWatch.libFile + ]) + ) + ); + + const host = ts.createSolutionBuilderHostForBaseline(system); + const builder = ts.createSolutionBuilder(host, [testsConfig.path], { dry: false, force: false, verbose: false }); + builder.build(); + baselineState("Build of project"); + + // Update a timestamp in the middle project + system.appendFile(logicIndex.path, "function foo() {}"); + + // Because we haven't reset the build context, the builder should assume there's nothing to do right now + const status = builder.getUpToDateStatusOfProject(logicConfig.path); + baseline.push(`Project should still be upto date: ${ts.UpToDateStatusType[status.type]}`); + verifyInvalidation("non Dts change to logic"); + + // Rebuild this project + system.appendFile(logicIndex.path, `export class cNew {}`); + verifyInvalidation("Dts change to Logic"); + Harness.Baseline.runBaseline(`tsbuild/sample1/invalidates-projects-correctly.js`, baseline.join("\r\n")); + + function verifyInvalidation(heading: string) { // Rebuild this project - system.appendFile(logicIndex.path, `export class cNew {}`); - verifyInvalidation("Dts change to Logic"); - Harness.Baseline.runBaseline(`tsbuild/sample1/invalidates-projects-correctly.js`, baseline.join("\r\n")); - - function verifyInvalidation(heading: string) { - // Rebuild this project - builder.invalidateProject(logicConfig.path as ResolvedConfigFilePath); - builder.getNextInvalidatedProject()?.done(); - baselineState(`${heading}:: After rebuilding logicConfig`); - - // Build downstream projects should update 'tests', but not 'core' - builder.getNextInvalidatedProject()?.done(); - baselineState(`${heading}:: After building next project`); - } - - function baselineState(heading: string) { - baseline.push(heading); - system.serializeOutput(baseline); - system.diff(baseline, oldSnap); - system.writtenFiles.clear(); - oldSnap = system.snap(); - } - }); - }); - - const coreChanges: TestTscEdit[] = [ - { - subScenario: "incremental-declaration-changes", - modifyFs: fs => appendText(fs, "/src/core/index.ts", ` + builder.invalidateProject(logicConfig.path as ts.ResolvedConfigFilePath); + builder.getNextInvalidatedProject()?.done(); + baselineState(`${heading}:: After rebuilding logicConfig`); + + // Build downstream projects should update 'tests', but not 'core' + builder.getNextInvalidatedProject()?.done(); + baselineState(`${heading}:: After building next project`); + } + + function baselineState(heading: string) { + baseline.push(heading); + system.serializeOutput(baseline); + system.diff(baseline, oldSnap); + system.writtenFiles.clear(); + oldSnap = system.snap(); + } + }); + }); + + const coreChanges: ts.TestTscEdit[] = [ + { + subScenario: "incremental-declaration-changes", + modifyFs: fs => ts.appendText(fs, "/src/core/index.ts", ` export class someClass { }`), - }, - { - subScenario: "incremental-declaration-doesnt-change", - modifyFs: fs => appendText(fs, "/src/core/index.ts", ` + }, + { + subScenario: "incremental-declaration-doesnt-change", + modifyFs: fs => ts.appendText(fs, "/src/core/index.ts", ` class someClass2 { }`), - }, - noChangeRun, - ]; - - describe("lists files", () => { - verifyTscWithEdits({ - scenario: "sample1", - subScenario: "listFiles", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--listFiles"], - edits: coreChanges - }); - verifyTscWithEdits({ - scenario: "sample1", - subScenario: "listEmittedFiles", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--listEmittedFiles"], - edits: coreChanges - }); - verifyTscWithEdits({ - scenario: "sample1", - subScenario: "explainFiles", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--explainFiles", "--v"], - edits: coreChanges - }); - }); - - describe("emit output", () => { - verifyTscWithEdits({ - subScenario: "sample", - fs: () => projFs, - scenario: "sample1", - commandLineArgs: ["--b", "/src/tests", "--verbose"], - baselineSourceMap: true, - baselineReadFileCalls: true, - edits: [ - ...coreChanges, - { - subScenario: "when logic config changes declaration dir", - modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"declaration": true,`, `"declaration": true, + }, + ts.noChangeRun, + ]; + + describe("lists files", () => { + ts.verifyTscWithEdits({ + scenario: "sample1", + subScenario: "listFiles", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--listFiles"], + edits: coreChanges + }); + ts.verifyTscWithEdits({ + scenario: "sample1", + subScenario: "listEmittedFiles", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--listEmittedFiles"], + edits: coreChanges + }); + ts.verifyTscWithEdits({ + scenario: "sample1", + subScenario: "explainFiles", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--explainFiles", "--v"], + edits: coreChanges + }); + }); + + describe("emit output", () => { + ts.verifyTscWithEdits({ + subScenario: "sample", + fs: () => projFs, + scenario: "sample1", + commandLineArgs: ["--b", "/src/tests", "--verbose"], + baselineSourceMap: true, + baselineReadFileCalls: true, + edits: [ + ...coreChanges, + { + subScenario: "when logic config changes declaration dir", + modifyFs: fs => ts.replaceText(fs, "/src/logic/tsconfig.json", `"declaration": true,`, `"declaration": true, "declarationDir": "decls",`), - }, - noChangeRun, - ], - }); - - verifyTsc({ - scenario: "sample1", - subScenario: "when logic specifies tsBuildInfoFile", - fs: () => projFs, - modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"composite": true,`, `"composite": true, + }, + ts.noChangeRun, + ], + }); + + ts.verifyTsc({ + scenario: "sample1", + subScenario: "when logic specifies tsBuildInfoFile", + fs: () => projFs, + modifyFs: fs => ts.replaceText(fs, "/src/logic/tsconfig.json", `"composite": true,`, `"composite": true, "tsBuildInfoFile": "ownFile.tsbuildinfo",`), - commandLineArgs: ["--b", "/src/tests", "--verbose"], - baselineSourceMap: true, - baselineReadFileCalls: true - }); - - verifyTscWithEdits({ - subScenario: "when declaration option changes", - fs: () => projFs, - scenario: "sample1", - commandLineArgs: ["--b", "/src/core", "--verbose"], - modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{ + commandLineArgs: ["--b", "/src/tests", "--verbose"], + baselineSourceMap: true, + baselineReadFileCalls: true + }); + + ts.verifyTscWithEdits({ + subScenario: "when declaration option changes", + fs: () => projFs, + scenario: "sample1", + commandLineArgs: ["--b", "/src/core", "--verbose"], + modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{ "compilerOptions": { "incremental": true, "skipDefaultLibCheck": true } }`), - edits: [{ - subScenario: "incremental-declaration-changes", - modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"incremental": true,`, `"incremental": true, "declaration": true,`), - }], - }); - - verifyTscWithEdits({ - subScenario: "when target option changes", - fs: () => projFs, - scenario: "sample1", - commandLineArgs: ["--b", "/src/core", "--verbose"], - modifyFs: fs => { - fs.writeFileSync("/lib/lib.esnext.full.d.ts", `/// + edits: [{ + subScenario: "incremental-declaration-changes", + modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", `"incremental": true,`, `"incremental": true, "declaration": true,`), + }], + }); + + ts.verifyTscWithEdits({ + subScenario: "when target option changes", + fs: () => projFs, + scenario: "sample1", + commandLineArgs: ["--b", "/src/core", "--verbose"], + modifyFs: fs => { + fs.writeFileSync("/lib/lib.esnext.full.d.ts", `/// /// `); - fs.writeFileSync("/lib/lib.esnext.d.ts", libContent); - fs.writeFileSync("/lib/lib.d.ts", `/// + fs.writeFileSync("/lib/lib.esnext.d.ts", ts.libContent); + fs.writeFileSync("/lib/lib.d.ts", `/// /// `); - fs.writeFileSync("/src/core/tsconfig.json", `{ + fs.writeFileSync("/src/core/tsconfig.json", `{ "compilerOptions": { "incremental": true, "listFiles": true, @@ -509,36 +513,36 @@ class someClass2 { }`), "target": "esnext", } }`); - }, - edits: [{ - subScenario: "incremental-declaration-changes", - modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", "esnext", "es5"), - }], - }); - - verifyTscWithEdits({ - subScenario: "when module option changes", - fs: () => projFs, - scenario: "sample1", - commandLineArgs: ["--b", "/src/core", "--verbose"], - modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{ + }, + edits: [{ + subScenario: "incremental-declaration-changes", + modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", "esnext", "es5"), + }], + }); + + ts.verifyTscWithEdits({ + subScenario: "when module option changes", + fs: () => projFs, + scenario: "sample1", + commandLineArgs: ["--b", "/src/core", "--verbose"], + modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{ "compilerOptions": { "incremental": true, "module": "commonjs" } }`), - edits: [{ - subScenario: "incremental-declaration-changes", - modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"module": "commonjs"`, `"module": "amd"`), - }], - }); - - verifyTscWithEdits({ - subScenario: "when esModuleInterop option changes", - fs: () => projFs, - scenario: "sample1", - commandLineArgs: ["--b", "/src/tests", "--verbose"], - modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.json", `{ + edits: [{ + subScenario: "incremental-declaration-changes", + modifyFs: fs => ts.replaceText(fs, "/src/core/tsconfig.json", `"module": "commonjs"`, `"module": "amd"`), + }], + }); + + ts.verifyTscWithEdits({ + subScenario: "when esModuleInterop option changes", + fs: () => projFs, + scenario: "sample1", + commandLineArgs: ["--b", "/src/tests", "--verbose"], + modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.json", `{ "references": [ { "path": "../core" }, { "path": "../logic" } @@ -552,39 +556,38 @@ class someClass2 { }`), "esModuleInterop": false } }`), - edits: [{ - subScenario: "incremental-declaration-changes", - modifyFs: fs => replaceText(fs, "/src/tests/tsconfig.json", `"esModuleInterop": false`, `"esModuleInterop": true`), - }], - }); - - verifyTsc({ - scenario: "sample1", - subScenario: "reports error if input file is missing", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--v"], - modifyFs: fs => { - fs.writeFileSync("/src/core/tsconfig.json", JSON.stringify({ - compilerOptions: { composite: true }, - files: ["anotherModule.ts", "index.ts", "some_decl.d.ts"] - })); - fs.unlinkSync("/src/core/anotherModule.ts"); - } - }); - - verifyTsc({ - scenario: "sample1", - subScenario: "reports error if input file is missing with force", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tests", "--v", "--f"], - modifyFs: fs => { - fs.writeFileSync("/src/core/tsconfig.json", JSON.stringify({ - compilerOptions: { composite: true }, - files: ["anotherModule.ts", "index.ts", "some_decl.d.ts"] - })); - fs.unlinkSync("/src/core/anotherModule.ts"); - } - }); + edits: [{ + subScenario: "incremental-declaration-changes", + modifyFs: fs => ts.replaceText(fs, "/src/tests/tsconfig.json", `"esModuleInterop": false`, `"esModuleInterop": true`), + }], + }); + + ts.verifyTsc({ + scenario: "sample1", + subScenario: "reports error if input file is missing", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--v"], + modifyFs: fs => { + fs.writeFileSync("/src/core/tsconfig.json", JSON.stringify({ + compilerOptions: { composite: true }, + files: ["anotherModule.ts", "index.ts", "some_decl.d.ts"] + })); + fs.unlinkSync("/src/core/anotherModule.ts"); + } + }); + + ts.verifyTsc({ + scenario: "sample1", + subScenario: "reports error if input file is missing with force", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tests", "--v", "--f"], + modifyFs: fs => { + fs.writeFileSync("/src/core/tsconfig.json", JSON.stringify({ + compilerOptions: { composite: true }, + files: ["anotherModule.ts", "index.ts", "some_decl.d.ts"] + })); + fs.unlinkSync("/src/core/anotherModule.ts"); + } }); }); -} +}); diff --git a/src/testRunner/unittests/tsbuild/transitiveReferences.ts b/src/testRunner/unittests/tsbuild/transitiveReferences.ts index 735c13f5976fb..c3374cb37038a 100644 --- a/src/testRunner/unittests/tsbuild/transitiveReferences.ts +++ b/src/testRunner/unittests/tsbuild/transitiveReferences.ts @@ -1,47 +1,48 @@ -namespace ts { - describe("unittests:: tsbuild:: when project reference is referenced transitively", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/transitiveReferences"); - }); - after(() => { - projFs = undefined!; // Release the contents - }); +import * as ts from "../../_namespaces/ts"; +import * as vfs from "../../_namespaces/vfs"; - function modifyFsBTsToNonRelativeImport(fs: vfs.FileSystem, moduleResolution: "node" | "classic") { - fs.writeFileSync("/src/b.ts", `import {A} from 'a'; +describe("unittests:: tsbuild:: when project reference is referenced transitively", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = ts.loadProjectFromDisk("tests/projects/transitiveReferences"); + }); + after(() => { + projFs = undefined!; // Release the contents + }); + + function modifyFsBTsToNonRelativeImport(fs: vfs.FileSystem, moduleResolution: "node" | "classic") { + fs.writeFileSync("/src/b.ts", `import {A} from 'a'; export const b = new A();`); - fs.writeFileSync("/src/tsconfig.b.json", JSON.stringify({ - compilerOptions: { - composite: true, - moduleResolution - }, - files: ["b.ts"], - references: [{ path: "tsconfig.a.json" }] - })); - } + fs.writeFileSync("/src/tsconfig.b.json", JSON.stringify({ + compilerOptions: { + composite: true, + moduleResolution + }, + files: ["b.ts"], + references: [{ path: "tsconfig.a.json" }] + })); + } - verifyTsc({ - scenario: "transitiveReferences", - subScenario: "builds correctly", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], - }); + ts.verifyTsc({ + scenario: "transitiveReferences", + subScenario: "builds correctly", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], + }); - verifyTsc({ - scenario: "transitiveReferences", - subScenario: "builds correctly when the referenced project uses different module resolution", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], - modifyFs: fs => modifyFsBTsToNonRelativeImport(fs, "classic"), - }); + ts.verifyTsc({ + scenario: "transitiveReferences", + subScenario: "builds correctly when the referenced project uses different module resolution", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], + modifyFs: fs => modifyFsBTsToNonRelativeImport(fs, "classic"), + }); - verifyTsc({ - scenario: "transitiveReferences", - subScenario: "reports error about module not found with node resolution with external module name", - fs: () => projFs, - commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], - modifyFs: fs => modifyFsBTsToNonRelativeImport(fs, "node"), - }); + ts.verifyTsc({ + scenario: "transitiveReferences", + subScenario: "reports error about module not found with node resolution with external module name", + fs: () => projFs, + commandLineArgs: ["--b", "/src/tsconfig.c.json", "--listFiles"], + modifyFs: fs => modifyFsBTsToNonRelativeImport(fs, "node"), }); -} +}); diff --git a/src/testRunner/unittests/tsbuildWatch/configFileErrors.ts b/src/testRunner/unittests/tsbuildWatch/configFileErrors.ts index 2d0bb90c4cb12..73e9fd0e5fb7a 100644 --- a/src/testRunner/unittests/tsbuildWatch/configFileErrors.ts +++ b/src/testRunner/unittests/tsbuildWatch/configFileErrors.ts @@ -1,19 +1,21 @@ -namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: configFileErrors:: reports syntax errors in config file", () => { - function build(sys: WatchedSystem) { - sys.checkTimeoutQueueLengthAndRun(1); // build the project - sys.checkTimeoutQueueLength(0); - } - verifyTscWatch({ - scenario: "configFileErrors", - subScenario: "reports syntax errors in config file", - sys: () => createWatchedSystem( - [ - { path: `${projectRoot}/a.ts`, content: "export function foo() { }" }, - { path: `${projectRoot}/b.ts`, content: "export function bar() { }" }, - { - path: `${projectRoot}/tsconfig.json`, - content: Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; + +describe("unittests:: tsbuildWatch:: watchMode:: configFileErrors:: reports syntax errors in config file", () => { + function build(sys: ts.tscWatch.WatchedSystem) { + sys.checkTimeoutQueueLengthAndRun(1); // build the project + sys.checkTimeoutQueueLength(0); + } + ts.tscWatch.verifyTscWatch({ + scenario: "configFileErrors", + subScenario: "reports syntax errors in config file", + sys: () => ts.tscWatch.createWatchedSystem( + [ + { path: `${ts.tscWatch.projectRoot}/a.ts`, content: "export function foo() { }" }, + { path: `${ts.tscWatch.projectRoot}/b.ts`, content: "export function bar() { }" }, + { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: Utils.dedent` { "compilerOptions": { "composite": true, @@ -23,38 +25,37 @@ namespace ts.tscWatch { "b.ts" ] }` - }, - libFile - ], - { currentDirectory: projectRoot } - ), - commandLineArgs: ["--b", "-w"], - changes: [ - { - caption: "reports syntax errors after change to config file", - change: sys => replaceFileText(sys, `${projectRoot}/tsconfig.json`, ",", `, - "declaration": true,`), - timeouts: build, - }, - { - caption: "reports syntax errors after change to ts file", - change: sys => replaceFileText(sys, `${projectRoot}/a.ts`, "foo", "fooBar"), - timeouts: build, }, - { - caption: "reports error when there is no change to tsconfig file", - change: sys => replaceFileText(sys, `${projectRoot}/tsconfig.json`, "", ""), - timeouts: build, - }, - { - caption: "builds after fixing config file errors", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ - compilerOptions: { composite: true, declaration: true }, - files: ["a.ts", "b.ts"] - })), - timeouts: build, - } - ] - }); + ts.tscWatch.libFile + ], + { currentDirectory: ts.tscWatch.projectRoot } + ), + commandLineArgs: ["--b", "-w"], + changes: [ + { + caption: "reports syntax errors after change to config file", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/tsconfig.json`, ",", `, + "declaration": true,`), + timeouts: build, + }, + { + caption: "reports syntax errors after change to ts file", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/a.ts`, "foo", "fooBar"), + timeouts: build, + }, + { + caption: "reports error when there is no change to tsconfig file", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/tsconfig.json`, "", ""), + timeouts: build, + }, + { + caption: "builds after fixing config file errors", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({ + compilerOptions: { composite: true, declaration: true }, + files: ["a.ts", "b.ts"] + })), + timeouts: build, + } + ] }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuildWatch/demo.ts b/src/testRunner/unittests/tsbuildWatch/demo.ts index 76dff70b29d1e..81ab4dda2daeb 100644 --- a/src/testRunner/unittests/tsbuildWatch/demo.ts +++ b/src/testRunner/unittests/tsbuildWatch/demo.ts @@ -1,88 +1,88 @@ -namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: with demo project", () => { - const projectLocation = `${TestFSWithWatch.tsbuildProjectsLocation}/demo`; - let coreFiles: File[]; - let animalFiles: File[]; - let zooFiles: File[]; - let solutionFile: File; - let baseConfig: File; - let allFiles: File[]; - before(() => { - coreFiles = subProjectFiles("core", ["tsconfig.json", "utilities.ts"]); - animalFiles = subProjectFiles("animals", ["tsconfig.json", "animal.ts", "dog.ts", "index.ts"]); - zooFiles = subProjectFiles("zoo", ["tsconfig.json", "zoo.ts"]); - solutionFile = projectFile("tsconfig.json"); - baseConfig = projectFile("tsconfig-base.json"); - allFiles = [...coreFiles, ...animalFiles, ...zooFiles, solutionFile, baseConfig, { path: libFile.path, content: libContent }]; - }); +import * as ts from "../../_namespaces/ts"; - after(() => { - coreFiles = undefined!; - animalFiles = undefined!; - zooFiles = undefined!; - solutionFile = undefined!; - baseConfig = undefined!; - allFiles = undefined!; - }); +describe("unittests:: tsbuildWatch:: watchMode:: with demo project", () => { + const projectLocation = `${ts.TestFSWithWatch.tsbuildProjectsLocation}/demo`; + let coreFiles: ts.tscWatch.File[]; + let animalFiles: ts.tscWatch.File[]; + let zooFiles: ts.tscWatch.File[]; + let solutionFile: ts.tscWatch.File; + let baseConfig: ts.tscWatch.File; + let allFiles: ts.tscWatch.File[]; + before(() => { + coreFiles = subProjectFiles("core", ["tsconfig.json", "utilities.ts"]); + animalFiles = subProjectFiles("animals", ["tsconfig.json", "animal.ts", "dog.ts", "index.ts"]); + zooFiles = subProjectFiles("zoo", ["tsconfig.json", "zoo.ts"]); + solutionFile = projectFile("tsconfig.json"); + baseConfig = projectFile("tsconfig-base.json"); + allFiles = [...coreFiles, ...animalFiles, ...zooFiles, solutionFile, baseConfig, { path: ts.tscWatch.libFile.path, content: ts.libContent }]; + }); + + after(() => { + coreFiles = undefined!; + animalFiles = undefined!; + zooFiles = undefined!; + solutionFile = undefined!; + baseConfig = undefined!; + allFiles = undefined!; + }); - verifyTscWatch({ - scenario: "demo", - subScenario: "updates with circular reference", - commandLineArgs: ["-b", "-w", "-verbose"], - sys: () => { - const sys = createWatchedSystem(allFiles, { currentDirectory: projectLocation }); - sys.writeFile(coreFiles[0].path, coreFiles[0].content.replace( - "}", - `}, + ts.tscWatch.verifyTscWatch({ + scenario: "demo", + subScenario: "updates with circular reference", + commandLineArgs: ["-b", "-w", "-verbose"], + sys: () => { + const sys = ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectLocation }); + sys.writeFile(coreFiles[0].path, coreFiles[0].content.replace( + "}", + `}, "references": [ { "path": "../zoo" } ]` - )); - return sys; - }, - changes: [ - { - caption: "Fix error", - change: sys => sys.writeFile(coreFiles[0].path, coreFiles[0].content), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // build core - sys.checkTimeoutQueueLengthAndRun(1); // build animals, zoo and solution - sys.checkTimeoutQueueLength(0); - }, - } - ] - }); + )); + return sys; + }, + changes: [ + { + caption: "Fix error", + change: sys => sys.writeFile(coreFiles[0].path, coreFiles[0].content), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // build core + sys.checkTimeoutQueueLengthAndRun(1); // build animals, zoo and solution + sys.checkTimeoutQueueLength(0); + }, + } + ] + }); - verifyTscWatch({ - scenario: "demo", - subScenario: "updates with bad reference", - commandLineArgs: ["-b", "-w", "-verbose"], - sys: () => { - const sys = createWatchedSystem(allFiles, { currentDirectory: projectLocation }); - sys.writeFile(coreFiles[1].path, `import * as A from '../animals'; + ts.tscWatch.verifyTscWatch({ + scenario: "demo", + subScenario: "updates with bad reference", + commandLineArgs: ["-b", "-w", "-verbose"], + sys: () => { + const sys = ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectLocation }); + sys.writeFile(coreFiles[1].path, `import * as A from '../animals'; ${coreFiles[1].content}`); - return sys; - }, - changes: [ - { - caption: "Prepend a line", - change: sys => sys.writeFile(coreFiles[1].path, ` + return sys; + }, + changes: [ + { + caption: "Prepend a line", + change: sys => sys.writeFile(coreFiles[1].path, ` import * as A from '../animals'; ${coreFiles[1].content}`), - // build core - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - } - ] - }); + // build core + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + } + ] + }); - function subProjectFiles(subProject: string, fileNames: readonly string[]): File[] { - return fileNames.map(file => projectFile(`${subProject}/${file}`)); - } + function subProjectFiles(subProject: string, fileNames: readonly string[]): ts.tscWatch.File[] { + return fileNames.map(file => projectFile(`${subProject}/${file}`)); + } - function projectFile(fileName: string): File { - return TestFSWithWatch.getTsBuildProjectFile("demo", fileName); - } - }); -} \ No newline at end of file + function projectFile(fileName: string): ts.tscWatch.File { + return ts.TestFSWithWatch.getTsBuildProjectFile("demo", fileName); + } +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuildWatch/moduleResolution.ts b/src/testRunner/unittests/tsbuildWatch/moduleResolution.ts index 01895cd90f9bb..044ec4844aace 100644 --- a/src/testRunner/unittests/tsbuildWatch/moduleResolution.ts +++ b/src/testRunner/unittests/tsbuildWatch/moduleResolution.ts @@ -1,226 +1,227 @@ -namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => { - verifyTscWatch({ - scenario: "moduleResolutionCache", - subScenario: "handles the cache correctly when two projects use different module resolution settings", - sys: () => createWatchedSystem( - [ - { path: `${projectRoot}/project1/index.ts`, content: `import { foo } from "file";` }, - { path: `${projectRoot}/project1/node_modules/file/index.d.ts`, content: "export const foo = 10;" }, - { - path: `${projectRoot}/project1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, types: ["foo", "bar"] }, - files: ["index.ts"] - }) - }, - { path: `${projectRoot}/project2/index.ts`, content: `import { foo } from "file";` }, - { path: `${projectRoot}/project2/file.d.ts`, content: "export const foo = 10;" }, - { - path: `${projectRoot}/project2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, types: ["foo"], moduleResolution: "classic" }, - files: ["index.ts"] - }) - }, - { path: `${projectRoot}/node_modules/@types/foo/index.d.ts`, content: "export const foo = 10;" }, - { path: `${projectRoot}/node_modules/@types/bar/index.d.ts`, content: "export const bar = 10;" }, - { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - files: [], - references: [ - { path: "./project1" }, - { path: "./project2" } - ] - }) - }, - libFile - ], - { currentDirectory: projectRoot } - ), - commandLineArgs: ["--b", "-w", "-v"], - changes: [ - { - caption: "Append text", - change: sys => sys.appendFile(`${projectRoot}/project1/index.ts`, "const bar = 10;"), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // build project1 and solution - sys.checkTimeoutQueueLength(0); - } - }, - ] - }); +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: `resolves specifier in output declaration file from referenced project correctly with cts and mts extensions`, - sys: () => createWatchedSystem([ - { - path: `${projectRoot}/packages/pkg1/package.json`, +describe("unittests:: tsbuildWatch:: watchMode:: moduleResolution", () => { + ts.tscWatch.verifyTscWatch({ + scenario: "moduleResolutionCache", + subScenario: "handles the cache correctly when two projects use different module resolution settings", + sys: () => ts.tscWatch.createWatchedSystem( + [ + { path: `${ts.tscWatch.projectRoot}/project1/index.ts`, content: `import { foo } from "file";` }, + { path: `${ts.tscWatch.projectRoot}/project1/node_modules/file/index.d.ts`, content: "export const foo = 10;" }, + { + path: `${ts.tscWatch.projectRoot}/project1/tsconfig.json`, content: JSON.stringify({ - name: "pkg1", - version: "1.0.0", - main: "build/index.js", - type: "module" + compilerOptions: { composite: true, types: ["foo", "bar"] }, + files: ["index.ts"] }) }, + { path: `${ts.tscWatch.projectRoot}/project2/index.ts`, content: `import { foo } from "file";` }, + { path: `${ts.tscWatch.projectRoot}/project2/file.d.ts`, content: "export const foo = 10;" }, { - path: `${projectRoot}/packages/pkg1/index.ts`, - content: Utils.dedent` - import type { TheNum } from 'pkg2' - export const theNum: TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "build", - module: "node16", - }, - references: [{ path: "../pkg2" }] - }) - }, - { - path: `${projectRoot}/packages/pkg2/const.cts`, - content: `export type TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg2/index.ts`, - content: `export type { TheNum } from './const.cjs';` - }, - { - path: `${projectRoot}/packages/pkg2/tsconfig.json`, + path: `${ts.tscWatch.projectRoot}/project2/tsconfig.json`, content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "build", - module: "node16", - } + compilerOptions: { composite: true, types: ["foo"], moduleResolution: "classic" }, + files: ["index.ts"] }) }, + { path: `${ts.tscWatch.projectRoot}/node_modules/@types/foo/index.d.ts`, content: "export const foo = 10;" }, + { path: `${ts.tscWatch.projectRoot}/node_modules/@types/bar/index.d.ts`, content: "export const bar = 10;" }, { - path: `${projectRoot}/packages/pkg2/package.json`, + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, content: JSON.stringify({ - name: "pkg2", - version: "1.0.0", - main: "build/index.js", - type: "module" + files: [], + references: [ + { path: "./project1" }, + { path: "./project2" } + ] }) }, - { - path: `${projectRoot}/node_modules/pkg2`, - symLink: `${projectRoot}/packages/pkg2`, - }, - { ...libFile, path: `/a/lib/lib.es2022.full.d.ts` } - ], { currentDirectory: projectRoot }), - commandLineArgs: ["-b", "packages/pkg1", "-w", "--verbose", "--traceResolution"], - changes: [ - { - caption: "reports import errors after change to package file", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`), - timeouts: runQueuedTimeoutCallbacks - }, - { - caption: "removes those errors when a package file is changed back", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"commonjs"`, `"module"`), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "reports import errors after change to package file", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`), - timeouts: runQueuedTimeoutCallbacks - }, - { - caption: "removes those errors when a package file is changed to cjs extensions", - change: sys => { - replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `"build/index.js"`, `"build/index.cjs"`); - sys.renameFile(`${projectRoot}/packages/pkg2/index.ts`, `${projectRoot}/packages/pkg2/index.cts`); - }, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); // building pkg2 - sys.runQueuedTimeoutCallbacks(); // building pkg1 + ts.tscWatch.libFile + ], + { currentDirectory: ts.tscWatch.projectRoot } + ), + commandLineArgs: ["--b", "-w", "-v"], + changes: [ + { + caption: "Append text", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/project1/index.ts`, "const bar = 10;"), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // build project1 and solution + sys.checkTimeoutQueueLength(0); + } + }, + ] + }); + + ts.tscWatch.verifyTscWatch({ + scenario: "moduleResolution", + subScenario: `resolves specifier in output declaration file from referenced project correctly with cts and mts extensions`, + sys: () => ts.tscWatch.createWatchedSystem([ + { + path: `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`, + content: JSON.stringify({ + name: "pkg1", + version: "1.0.0", + main: "build/index.js", + type: "module" + }) + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg1/index.ts`, + content: Utils.dedent` + import type { TheNum } from 'pkg2' + export const theNum: TheNum = 42;` + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "build", + module: "node16", }, - }, - ] - }); + references: [{ path: "../pkg2" }] + }) + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/const.cts`, + content: `export type TheNum = 42;` + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/index.ts`, + content: `export type { TheNum } from './const.cjs';` + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "build", + module: "node16", + } + }) + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, + content: JSON.stringify({ + name: "pkg2", + version: "1.0.0", + main: "build/index.js", + type: "module" + }) + }, + { + path: `${ts.tscWatch.projectRoot}/node_modules/pkg2`, + symLink: `${ts.tscWatch.projectRoot}/packages/pkg2`, + }, + { ...ts.tscWatch.libFile, path: `/a/lib/lib.es2022.full.d.ts` } + ], { currentDirectory: ts.tscWatch.projectRoot }), + commandLineArgs: ["-b", "packages/pkg1", "-w", "--verbose", "--traceResolution"], + changes: [ + { + caption: "reports import errors after change to package file", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks + }, + { + caption: "removes those errors when a package file is changed back", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`, `"commonjs"`, `"module"`), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "reports import errors after change to package file", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`, `"module"`, `"commonjs"`), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks + }, + { + caption: "removes those errors when a package file is changed to cjs extensions", + change: sys => { + ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, `"build/index.js"`, `"build/index.cjs"`); + sys.renameFile(`${ts.tscWatch.projectRoot}/packages/pkg2/index.ts`, `${ts.tscWatch.projectRoot}/packages/pkg2/index.cts`); + }, + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); // building pkg2 + sys.runQueuedTimeoutCallbacks(); // building pkg1 + }, + }, + ] + }); - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: `build mode watches for changes to package-json main fields`, - sys: () => createWatchedSystem([ - { - path: `${projectRoot}/packages/pkg1/package.json`, - content: JSON.stringify({ - name: "pkg1", - version: "1.0.0", - main: "build/index.js", - }) - }, - { - path: `${projectRoot}/packages/pkg1/index.ts`, - content: Utils.dedent` + ts.tscWatch.verifyTscWatch({ + scenario: "moduleResolution", + subScenario: `build mode watches for changes to package-json main fields`, + sys: () => ts.tscWatch.createWatchedSystem([ + { + path: `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`, + content: JSON.stringify({ + name: "pkg1", + version: "1.0.0", + main: "build/index.js", + }) + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg1/index.ts`, + content: Utils.dedent` import type { TheNum } from 'pkg2' export const theNum: TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "build", - }, - references: [{ path: "../pkg2" }] - }) - }, - { - path: `${projectRoot}/packages/pkg2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "build", - baseUrl: ".", - } - }) - }, - { - path: `${projectRoot}/packages/pkg2/const.ts`, - content: `export type TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg2/index.ts`, - content: `export type { TheNum } from './const.js';` - }, - { - path: `${projectRoot}/packages/pkg2/other.ts`, - content: `export type TheStr = string;` - }, - { - path: `${projectRoot}/packages/pkg2/package.json`, - content: JSON.stringify({ - name: "pkg2", - version: "1.0.0", - main: "build/index.js", - }) - }, - { - path: `${projectRoot}/node_modules/pkg2`, - symLink: `${projectRoot}/packages/pkg2`, - }, - libFile - ], { currentDirectory: projectRoot }), - commandLineArgs: ["-b", "packages/pkg1", "--verbose", "-w", "--traceResolution"], - changes: [ - { - caption: "reports import errors after change to package file", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "removes those errors when a package file is changed back", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "build", + }, + references: [{ path: "../pkg2" }] + }) + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "build", + baseUrl: ".", + } + }) + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/const.ts`, + content: `export type TheNum = 42;` + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/index.ts`, + content: `export type { TheNum } from './const.js';` + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/other.ts`, + content: `export type TheStr = string;` + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, + content: JSON.stringify({ + name: "pkg2", + version: "1.0.0", + main: "build/index.js", + }) + }, + { + path: `${ts.tscWatch.projectRoot}/node_modules/pkg2`, + symLink: `${ts.tscWatch.projectRoot}/packages/pkg2`, + }, + ts.tscWatch.libFile + ], { currentDirectory: ts.tscWatch.projectRoot }), + commandLineArgs: ["-b", "packages/pkg1", "--verbose", "-w", "--traceResolution"], + changes: [ + { + caption: "reports import errors after change to package file", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "removes those errors when a package file is changed back", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + ] }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuildWatch/noEmit.ts b/src/testRunner/unittests/tsbuildWatch/noEmit.ts index 456bfe3614478..37623cd14ae2a 100644 --- a/src/testRunner/unittests/tsbuildWatch/noEmit.ts +++ b/src/testRunner/unittests/tsbuildWatch/noEmit.ts @@ -1,33 +1,33 @@ -namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: with noEmit", () => { - verifyTscWatch({ - scenario: "noEmit", - subScenario: "does not go in loop when watching when no files are emitted", - commandLineArgs: ["-b", "-w", "-verbose"], - sys: () => createWatchedSystem( - [ - { path: libFile.path, content: libContent }, - { path: `${projectRoot}/a.js`, content: "" }, - { path: `${projectRoot}/b.ts`, content: "" }, - { path: `${projectRoot}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { allowJs: true, noEmit: true } }) }, - ], - { currentDirectory: projectRoot } - ), - changes: [ - { - caption: "No change", - change: sys => sys.writeFile(`${projectRoot}/a.js`, sys.readFile(`${projectRoot}/a.js`)!), - // build project - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - }, - { - caption: "change", - change: sys => sys.writeFile(`${projectRoot}/a.js`, "const x = 10;"), - // build project - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - }, +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsbuildWatch:: watchMode:: with noEmit", () => { + ts.tscWatch.verifyTscWatch({ + scenario: "noEmit", + subScenario: "does not go in loop when watching when no files are emitted", + commandLineArgs: ["-b", "-w", "-verbose"], + sys: () => ts.tscWatch.createWatchedSystem( + [ + { path: ts.tscWatch.libFile.path, content: ts.libContent }, + { path: `${ts.tscWatch.projectRoot}/a.js`, content: "" }, + { path: `${ts.tscWatch.projectRoot}/b.ts`, content: "" }, + { path: `${ts.tscWatch.projectRoot}/tsconfig.json`, content: JSON.stringify({ compilerOptions: { allowJs: true, noEmit: true } }) }, ], - baselineIncremental: true - }); + { currentDirectory: ts.tscWatch.projectRoot } + ), + changes: [ + { + caption: "No change", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/a.js`, sys.readFile(`${ts.tscWatch.projectRoot}/a.js`)!), + // build project + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + }, + { + caption: "change", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/a.js`, "const x = 10;"), + // build project + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + }, + ], + baselineIncremental: true }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuildWatch/noEmitOnError.ts b/src/testRunner/unittests/tsbuildWatch/noEmitOnError.ts index 34ae0e8bcdf0f..c59bdc9d8690b 100644 --- a/src/testRunner/unittests/tsbuildWatch/noEmitOnError.ts +++ b/src/testRunner/unittests/tsbuildWatch/noEmitOnError.ts @@ -1,46 +1,46 @@ -namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: with noEmitOnError", () => { - function change(caption: string, content: string): TscWatchCompileChange { - return { - caption, - change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content), - // build project - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - }; - } +import * as ts from "../../_namespaces/ts"; - const noChange: TscWatchCompileChange = { - caption: "No change", - change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!), +describe("unittests:: tsbuildWatch:: watchMode:: with noEmitOnError", () => { + function change(caption: string, content: string): ts.tscWatch.TscWatchCompileChange { + return { + caption, + change: sys => sys.writeFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content), // build project - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, }; - verifyTscWatch({ - scenario: "noEmitOnError", - subScenario: "does not emit any files on error", - commandLineArgs: ["-b", "-w", "-verbose"], - sys: () => createWatchedSystem( - [ - ...["tsconfig.json", "shared/types/db.ts", "src/main.ts", "src/other.ts"] - .map(f => TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)), - { path: libFile.path, content: libContent } - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError` } - ), - changes: [ - noChange, - change("Fix Syntax error", `import { A } from "../shared/types/db"; + } + + const noChange: ts.tscWatch.TscWatchCompileChange = { + caption: "No change", + change: sys => sys.writeFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!), + // build project + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + }; + ts.tscWatch.verifyTscWatch({ + scenario: "noEmitOnError", + subScenario: "does not emit any files on error", + commandLineArgs: ["-b", "-w", "-verbose"], + sys: () => ts.tscWatch.createWatchedSystem( + [ + ...["tsconfig.json", "shared/types/db.ts", "src/main.ts", "src/other.ts"] + .map(f => ts.TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)), + { path: ts.tscWatch.libFile.path, content: ts.libContent } + ], + { currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError` } + ), + changes: [ + noChange, + change("Fix Syntax error", `import { A } from "../shared/types/db"; const a = { lastName: 'sdsd' };`), - change("Semantic Error", `import { A } from "../shared/types/db"; + change("Semantic Error", `import { A } from "../shared/types/db"; const a: string = 10;`), - noChange, - change("Fix Semantic Error", `import { A } from "../shared/types/db"; + noChange, + change("Fix Semantic Error", `import { A } from "../shared/types/db"; const a: string = "hello";`), - noChange, - ], - baselineIncremental: true - }); + noChange, + ], + baselineIncremental: true }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts index ad777cc7fdc65..3d0e24d006c29 100644 --- a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts +++ b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts @@ -1,280 +1,281 @@ -namespace ts.tscWatch { - import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation; - describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => { - const enum SubProject { - core = "core", - logic = "logic", - tests = "tests", - ui = "ui" +import * as ts from "../../_namespaces/ts"; + +import projectsLocation = ts.TestFSWithWatch.tsbuildProjectsLocation; +describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => { + const enum SubProject { + core = "core", + logic = "logic", + tests = "tests", + ui = "ui" + } + type ReadonlyFile = Readonly; + /** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */ + type SubProjectFiles = [tsconfig: ReadonlyFile, index: ReadonlyFile] | [tsconfig: ReadonlyFile, index: ReadonlyFile, anotherModule: ReadonlyFile, someDecl: ReadonlyFile]; + function projectFilePath(subProject: SubProject, baseFileName: string) { + return `${ts.TestFSWithWatch.getTsBuildProjectFilePath("sample1", subProject)}/${baseFileName.toLowerCase()}`; + } + + function projectFile(subProject: SubProject, baseFileName: string): ts.tscWatch.File { + return ts.TestFSWithWatch.getTsBuildProjectFile("sample1", `${subProject}/${baseFileName}`); + } + + function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles { + const tsconfig = projectFile(subProject, "tsconfig.json"); + const index = projectFile(subProject, "index.ts"); + if (!anotherModuleAndSomeDecl) { + return [tsconfig, index]; } - type ReadonlyFile = Readonly; - /** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */ - type SubProjectFiles = [tsconfig: ReadonlyFile, index: ReadonlyFile] | [tsconfig: ReadonlyFile, index: ReadonlyFile, anotherModule: ReadonlyFile, someDecl: ReadonlyFile]; - function projectFilePath(subProject: SubProject, baseFileName: string) { - return `${TestFSWithWatch.getTsBuildProjectFilePath("sample1", subProject)}/${baseFileName.toLowerCase()}`; - } - - function projectFile(subProject: SubProject, baseFileName: string): File { - return TestFSWithWatch.getTsBuildProjectFile("sample1", `${subProject}/${baseFileName}`); - } - - function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles { - const tsconfig = projectFile(subProject, "tsconfig.json"); - const index = projectFile(subProject, "index.ts"); - if (!anotherModuleAndSomeDecl) { - return [tsconfig, index]; - } - const anotherModule = projectFile(SubProject.core, "anotherModule.ts"); - const someDecl = projectFile(SubProject.core, "some_decl.ts"); - return [tsconfig, index, anotherModule, someDecl]; - } - - function changeFile(fileName: string | (() => string), content: string | (() => string), caption: string): TscWatchCompileChange { - return { - caption, - change: sys => sys.writeFile(isString(fileName) ? fileName : fileName(), isString(content) ? content : content()), - timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds core - }; - } - - function changeCore(content: () => string, caption: string) { - return changeFile(() => core[1].path, content, caption); - } - - let core: SubProjectFiles; - let logic: SubProjectFiles; - let tests: SubProjectFiles; - let ui: SubProjectFiles; - let allFiles: readonly File[]; + const anotherModule = projectFile(SubProject.core, "anotherModule.ts"); + const someDecl = projectFile(SubProject.core, "some_decl.ts"); + return [tsconfig, index, anotherModule, someDecl]; + } + + function changeFile(fileName: string | (() => string), content: string | (() => string), caption: string): ts.tscWatch.TscWatchCompileChange { + return { + caption, + change: sys => sys.writeFile(ts.isString(fileName) ? fileName : fileName(), ts.isString(content) ? content : content()), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Builds core + }; + } + + function changeCore(content: () => string, caption: string) { + return changeFile(() => core[1].path, content, caption); + } + + let core: SubProjectFiles; + let logic: SubProjectFiles; + let tests: SubProjectFiles; + let ui: SubProjectFiles; + let allFiles: readonly ts.tscWatch.File[]; + + before(() => { + core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true); + logic = subProjectFiles(SubProject.logic); + tests = subProjectFiles(SubProject.tests); + ui = subProjectFiles(SubProject.ui); + allFiles = [ts.tscWatch.libFile, ...core, ...logic, ...tests, ...ui]; + }); - before(() => { - core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true); - logic = subProjectFiles(SubProject.logic); - tests = subProjectFiles(SubProject.tests); - ui = subProjectFiles(SubProject.ui); - allFiles = [libFile, ...core, ...logic, ...tests, ...ui]; - }); + after(() => { + core = undefined!; + logic = undefined!; + tests = undefined!; + ui = undefined!; + allFiles = undefined!; + }); - after(() => { - core = undefined!; - logic = undefined!; - tests = undefined!; - ui = undefined!; - allFiles = undefined!; - }); + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "creates solution in watch mode", + commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`], + sys: () => ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), + changes: ts.emptyArray + }); - verifyTscWatch({ + it("verify building references watches only those projects", () => { + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation })); + const host = ts.tscWatch.createSolutionBuilderWithWatchHostForBaseline(sys, cb); + const solutionBuilder = ts.createSolutionBuilderWithWatch(host, [`sample1/${SubProject.tests}`], { watch: true }); + solutionBuilder.buildReferences(`sample1/${SubProject.tests}`); + ts.tscWatch.runWatchBaseline({ scenario: "programUpdates", - subScenario: "creates solution in watch mode", - commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`], - sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), - changes: emptyArray - }); - - it("verify building references watches only those projects", () => { - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem(allFiles, { currentDirectory: projectsLocation })); - const host = createSolutionBuilderWithWatchHostForBaseline(sys, cb); - const solutionBuilder = createSolutionBuilderWithWatch(host, [`sample1/${SubProject.tests}`], { watch: true }); - solutionBuilder.buildReferences(`sample1/${SubProject.tests}`); - runWatchBaseline({ - scenario: "programUpdates", - subScenario: "verify building references watches only those projects", - commandLineArgs: ["--b", "--w"], - sys, - baseline, - oldSnap, - getPrograms, - changes: emptyArray, - watchOrSolution: solutionBuilder - }); + subScenario: "verify building references watches only those projects", + commandLineArgs: ["--b", "--w"], + sys, + baseline, + oldSnap, + getPrograms, + changes: ts.emptyArray, + watchOrSolution: solutionBuilder }); + }); - describe("validates the changes and watched files", () => { - const newFileWithoutExtension = "newFile"; - const newFile: File = { - path: projectFilePath(SubProject.core, `${newFileWithoutExtension}.ts`), - content: `export const newFileConst = 30;` + describe("validates the changes and watched files", () => { + const newFileWithoutExtension = "newFile"; + const newFile: ts.tscWatch.File = { + path: projectFilePath(SubProject.core, `${newFileWithoutExtension}.ts`), + content: `export const newFileConst = 30;` + }; + + function verifyProjectChanges(subScenario: string, allFilesGetter: () => readonly ts.tscWatch.File[]) { + const buildLogicAndTests: ts.tscWatch.TscWatchCompileChange = { + caption: "Build logic and tests", + change: ts.noop, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, }; - function verifyProjectChanges(subScenario: string, allFilesGetter: () => readonly File[]) { - const buildLogicAndTests: TscWatchCompileChange = { - caption: "Build logic and tests", - change: noop, - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - }; - - verifyTscWatch({ - scenario: "programUpdates", - subScenario: `${subScenario}/change builds changes and reports found errors message`, - commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`], - sys: () => createWatchedSystem( - allFilesGetter(), - { currentDirectory: projectsLocation } - ), - changes: [ - changeCore(() => `${core[1].content} + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: `${subScenario}/change builds changes and reports found errors message`, + commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`], + sys: () => ts.tscWatch.createWatchedSystem( + allFilesGetter(), + { currentDirectory: projectsLocation } + ), + changes: [ + changeCore(() => `${core[1].content} export class someClass { }`, "Make change to core"), - buildLogicAndTests, - // Another change requeues and builds it - changeCore(() => core[1].content, "Revert core file"), - buildLogicAndTests, - { - caption: "Make two changes", - change: sys => { - const change1 = `${core[1].content} + buildLogicAndTests, + // Another change requeues and builds it + changeCore(() => core[1].content, "Revert core file"), + buildLogicAndTests, + { + caption: "Make two changes", + change: sys => { + const change1 = `${core[1].content} export class someClass { }`; - sys.writeFile(core[1].path, change1); - assert.equal(sys.writtenFiles.size, 1); - sys.writtenFiles.clear(); - sys.writeFile(core[1].path, `${change1} + sys.writeFile(core[1].path, change1); + assert.equal(sys.writtenFiles.size, 1); + sys.writtenFiles.clear(); + sys.writeFile(core[1].path, `${change1} export class someClass2 { }`); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds core }, - buildLogicAndTests, - ] - }); + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Builds core + }, + buildLogicAndTests, + ] + }); - verifyTscWatch({ - scenario: "programUpdates", - subScenario: `${subScenario}/non local change does not start build of referencing projects`, - commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`], - sys: () => createWatchedSystem( - allFilesGetter(), - { currentDirectory: projectsLocation } - ), - changes: [ - changeCore(() => `${core[1].content} + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: `${subScenario}/non local change does not start build of referencing projects`, + commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`], + sys: () => ts.tscWatch.createWatchedSystem( + allFilesGetter(), + { currentDirectory: projectsLocation } + ), + changes: [ + changeCore(() => `${core[1].content} function foo() { }`, "Make local change to core"), - ] - }); + ] + }); - function changeNewFile(newFileContent: string) { - return changeFile(newFile.path, newFileContent, "Change to new File and build core"); - } - verifyTscWatch({ - scenario: "programUpdates", - subScenario: `${subScenario}/builds when new file is added, and its subsequent updates`, - commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`], - sys: () => createWatchedSystem( - allFilesGetter(), - { currentDirectory: projectsLocation } - ), - changes: [ - changeNewFile(newFile.content), - buildLogicAndTests, - changeNewFile(`${newFile.content} -export class someClass2 { }`), - buildLogicAndTests, - ] - }); + function changeNewFile(newFileContent: string) { + return changeFile(newFile.path, newFileContent, "Change to new File and build core"); } - - describe("with simple project reference graph", () => { - verifyProjectChanges( - "with simple project reference graph", - () => allFiles - ); + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: `${subScenario}/builds when new file is added, and its subsequent updates`, + commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`], + sys: () => ts.tscWatch.createWatchedSystem( + allFilesGetter(), + { currentDirectory: projectsLocation } + ), + changes: [ + changeNewFile(newFile.content), + buildLogicAndTests, + changeNewFile(`${newFile.content} +export class someClass2 { }`), + buildLogicAndTests, + ] }); + } - describe("with circular project reference", () => { - verifyProjectChanges( - "with circular project reference", - () => { - const [coreTsconfig, ...otherCoreFiles] = core; - const circularCoreConfig: File = { - path: coreTsconfig.path, - content: JSON.stringify({ - compilerOptions: { composite: true, declaration: true }, - references: [{ path: "../tests", circular: true }] - }) - }; - return [libFile, circularCoreConfig, ...otherCoreFiles, ...logic, ...tests]; - } - ); - }); + describe("with simple project reference graph", () => { + verifyProjectChanges( + "with simple project reference graph", + () => allFiles + ); }); - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "watches config files that are not present", - commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`], - sys: () => createWatchedSystem( - [libFile, ...core, logic[1], ...tests], - { currentDirectory: projectsLocation } - ), - changes: [ - { - caption: "Write logic tsconfig and build logic", - change: sys => sys.writeFile(logic[0].path, logic[0].content), - timeouts: checkSingleTimeoutQueueLengthAndRun, // Builds logic - }, - { - caption: "Build Tests", - change: noop, - // Build tests - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + describe("with circular project reference", () => { + verifyProjectChanges( + "with circular project reference", + () => { + const [coreTsconfig, ...otherCoreFiles] = core; + const circularCoreConfig: ts.tscWatch.File = { + path: coreTsconfig.path, + content: JSON.stringify({ + compilerOptions: { composite: true, declaration: true }, + references: [{ path: "../tests", circular: true }] + }) + }; + return [ts.tscWatch.libFile, circularCoreConfig, ...otherCoreFiles, ...logic, ...tests]; } - ] + ); }); + }); - describe("when referenced using prepend, builds referencing project even for non local change", () => { - let coreIndex: File; - before(() => { - coreIndex = { - path: core[1].path, - content: `function foo() { return 10; }` - }; - }); - after(() => { - coreIndex = undefined!; - }); - const buildLogic: TscWatchCompileChange = { - caption: "Build logic", - change: noop, - // Builds logic - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "watches config files that are not present", + commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`], + sys: () => ts.tscWatch.createWatchedSystem( + [ts.tscWatch.libFile, ...core, logic[1], ...tests], + { currentDirectory: projectsLocation } + ), + changes: [ + { + caption: "Write logic tsconfig and build logic", + change: sys => sys.writeFile(logic[0].path, logic[0].content), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Builds logic + }, + { + caption: "Build Tests", + change: ts.noop, + // Build tests + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + } + ] + }); + + describe("when referenced using prepend, builds referencing project even for non local change", () => { + let coreIndex: ts.tscWatch.File; + before(() => { + coreIndex = { + path: core[1].path, + content: `function foo() { return 10; }` }; - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "when referenced using prepend builds referencing project even for non local change", - commandLineArgs: ["-b", "-w", `sample1/${SubProject.logic}`], - sys: () => { - const coreTsConfig: File = { - path: core[0].path, - content: JSON.stringify({ - compilerOptions: { composite: true, declaration: true, outFile: "index.js" } - }) - }; - const logicTsConfig: File = { - path: logic[0].path, - content: JSON.stringify({ - compilerOptions: { composite: true, declaration: true, outFile: "index.js" }, - references: [{ path: "../core", prepend: true }] - }) - }; - const logicIndex: File = { - path: logic[1].path, - content: `function bar() { return foo() + 1 };` - }; - return createWatchedSystem([libFile, coreTsConfig, coreIndex, logicTsConfig, logicIndex], { currentDirectory: projectsLocation }); - }, - changes: [ - changeCore(() => `${coreIndex.content} + }); + after(() => { + coreIndex = undefined!; + }); + const buildLogic: ts.tscWatch.TscWatchCompileChange = { + caption: "Build logic", + change: ts.noop, + // Builds logic + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + }; + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "when referenced using prepend builds referencing project even for non local change", + commandLineArgs: ["-b", "-w", `sample1/${SubProject.logic}`], + sys: () => { + const coreTsConfig: ts.tscWatch.File = { + path: core[0].path, + content: JSON.stringify({ + compilerOptions: { composite: true, declaration: true, outFile: "index.js" } + }) + }; + const logicTsConfig: ts.tscWatch.File = { + path: logic[0].path, + content: JSON.stringify({ + compilerOptions: { composite: true, declaration: true, outFile: "index.js" }, + references: [{ path: "../core", prepend: true }] + }) + }; + const logicIndex: ts.tscWatch.File = { + path: logic[1].path, + content: `function bar() { return foo() + 1 };` + }; + return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, coreTsConfig, coreIndex, logicTsConfig, logicIndex], { currentDirectory: projectsLocation }); + }, + changes: [ + changeCore(() => `${coreIndex.content} function myFunc() { return 10; }`, "Make non local change and build core"), - buildLogic, - changeCore(() => `${coreIndex.content} + buildLogic, + changeCore(() => `${coreIndex.content} function myFunc() { return 100; }`, "Make local change and build core"), - buildLogic, - ] - }); + buildLogic, + ] }); + }); - describe("when referenced project change introduces error in the down stream project and then fixes it", () => { - const subProjectLibrary = `${projectsLocation}/sample1/Library`; - const libraryTs: File = { - path: `${subProjectLibrary}/library.ts`, - content: ` + describe("when referenced project change introduces error in the down stream project and then fixes it", () => { + const subProjectLibrary = `${projectsLocation}/sample1/Library`; + const libraryTs: ts.tscWatch.File = { + path: `${subProjectLibrary}/library.ts`, + content: ` interface SomeObject { message: string; @@ -286,461 +287,460 @@ export function createSomeObject(): SomeObject message: "new Object" }; }` - }; - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "when referenced project change introduces error in the down stream project and then fixes it", - commandLineArgs: ["-b", "-w", "App"], - sys: () => { - const libraryTsconfig: File = { - path: `${subProjectLibrary}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true } }) - }; - const subProjectApp = `${projectsLocation}/sample1/App`; - const appTs: File = { - path: `${subProjectApp}/app.ts`, - content: `import { createSomeObject } from "../Library/library"; + }; + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "when referenced project change introduces error in the down stream project and then fixes it", + commandLineArgs: ["-b", "-w", "App"], + sys: () => { + const libraryTsconfig: ts.tscWatch.File = { + path: `${subProjectLibrary}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true } }) + }; + const subProjectApp = `${projectsLocation}/sample1/App`; + const appTs: ts.tscWatch.File = { + path: `${subProjectApp}/app.ts`, + content: `import { createSomeObject } from "../Library/library"; createSomeObject().message;` - }; - const appTsconfig: File = { - path: `${subProjectApp}/tsconfig.json`, - content: JSON.stringify({ references: [{ path: "../Library" }] }) - }; + }; + const appTsconfig: ts.tscWatch.File = { + path: `${subProjectApp}/tsconfig.json`, + content: JSON.stringify({ references: [{ path: "../Library" }] }) + }; - const files = [libFile, libraryTs, libraryTsconfig, appTs, appTsconfig]; - return createWatchedSystem(files, { currentDirectory: `${projectsLocation}/sample1` }); + const files = [ts.tscWatch.libFile, libraryTs, libraryTsconfig, appTs, appTsconfig]; + return ts.tscWatch.createWatchedSystem(files, { currentDirectory: `${projectsLocation}/sample1` }); + }, + changes: [ + { + caption: "Introduce error", + // Change message in library to message2 + change: sys => sys.writeFile(libraryTs.path, libraryTs.content.replace(/message/g, "message2")), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // Build library + sys.checkTimeoutQueueLengthAndRun(1); // Build App + }, + }, + { + caption: "Fix error", + // Revert library changes + change: sys => sys.writeFile(libraryTs.path, libraryTs.content), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // Build library + sys.checkTimeoutQueueLengthAndRun(1); // Build App + }, }, + ] + }); + + }); + + describe("reports errors in all projects on incremental compile", () => { + function verifyIncrementalErrors(subScenario: string, buildOptions: readonly string[]) { + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: `reportErrors/${subScenario}`, + commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`, ...buildOptions], + sys: () => ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), changes: [ { - caption: "Introduce error", - // Change message in library to message2 - change: sys => sys.writeFile(libraryTs.path, libraryTs.content.replace(/message/g, "message2")), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // Build library - sys.checkTimeoutQueueLengthAndRun(1); // Build App - }, + caption: "change logic", + change: sys => sys.writeFile(logic[1].path, `${logic[1].content} +let y: string = 10;`), + // Builds logic + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, }, { - caption: "Fix error", - // Revert library changes - change: sys => sys.writeFile(libraryTs.path, libraryTs.content), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // Build library - sys.checkTimeoutQueueLengthAndRun(1); // Build App - }, - }, + caption: "change core", + change: sys => sys.writeFile(core[1].path, `${core[1].content} +let x: string = 10;`), + // Builds core + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + } ] }); - - }); - - describe("reports errors in all projects on incremental compile", () => { - function verifyIncrementalErrors(subScenario: string, buildOptions: readonly string[]) { - verifyTscWatch({ - scenario: "programUpdates", - subScenario: `reportErrors/${subScenario}`, - commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`, ...buildOptions], - sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), - changes: [ - { - caption: "change logic", - change: sys => sys.writeFile(logic[1].path, `${logic[1].content} -let y: string = 10;`), - // Builds logic - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - }, - { - caption: "change core", - change: sys => sys.writeFile(core[1].path, `${core[1].content} -let x: string = 10;`), - // Builds core - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - } - ] - }); - } - verifyIncrementalErrors("when preserveWatchOutput is not used", emptyArray); - verifyIncrementalErrors("when preserveWatchOutput is passed on command line", ["--preserveWatchOutput"]); - - describe("when declaration emit errors are present", () => { - const solution = "solution"; - const subProject = "app"; - const subProjectLocation = `${projectsLocation}/${solution}/${subProject}`; - const fileWithError: File = { - path: `${subProjectLocation}/fileWithError.ts`, - content: `export var myClassWithError = class { + } + verifyIncrementalErrors("when preserveWatchOutput is not used", ts.emptyArray); + verifyIncrementalErrors("when preserveWatchOutput is passed on command line", ["--preserveWatchOutput"]); + + describe("when declaration emit errors are present", () => { + const solution = "solution"; + const subProject = "app"; + const subProjectLocation = `${projectsLocation}/${solution}/${subProject}`; + const fileWithError: ts.tscWatch.File = { + path: `${subProjectLocation}/fileWithError.ts`, + content: `export var myClassWithError = class { tags() { } private p = 12 };` - }; - const fileWithFixedError: File = { - path: fileWithError.path, - content: fileWithError.content.replace("private p = 12", "") - }; - const fileWithoutError: File = { - path: `${subProjectLocation}/fileWithoutError.ts`, - content: `export class myClass { }` - }; - const tsconfig: File = { - path: `${subProjectLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true } }) - }; + }; + const fileWithFixedError: ts.tscWatch.File = { + path: fileWithError.path, + content: fileWithError.content.replace("private p = 12", "") + }; + const fileWithoutError: ts.tscWatch.File = { + path: `${subProjectLocation}/fileWithoutError.ts`, + content: `export class myClass { }` + }; + const tsconfig: ts.tscWatch.File = { + path: `${subProjectLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true } }) + }; - function incrementalBuild(sys: WatchedSystem) { - sys.checkTimeoutQueueLengthAndRun(1); // Build the app - sys.checkTimeoutQueueLength(0); - } + function incrementalBuild(sys: ts.tscWatch.WatchedSystem) { + sys.checkTimeoutQueueLengthAndRun(1); // Build the app + sys.checkTimeoutQueueLength(0); + } - const fixError: TscWatchCompileChange = { - caption: "Fix error in fileWithError", - // Fix error - change: sys => sys.writeFile(fileWithError.path, fileWithFixedError.content), - timeouts: incrementalBuild - }; + const fixError: ts.tscWatch.TscWatchCompileChange = { + caption: "Fix error in fileWithError", + // Fix error + change: sys => sys.writeFile(fileWithError.path, fileWithFixedError.content), + timeouts: incrementalBuild + }; + + const changeFileWithoutError: ts.tscWatch.TscWatchCompileChange = { + caption: "Change fileWithoutError", + change: sys => sys.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2")), + timeouts: incrementalBuild + }; + + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "reportErrors/declarationEmitErrors/when fixing error files all files are emitted", + commandLineArgs: ["-b", "-w", subProject], + sys: () => ts.tscWatch.createWatchedSystem( + [ts.tscWatch.libFile, fileWithError, fileWithoutError, tsconfig], + { currentDirectory: `${projectsLocation}/${solution}` } + ), + changes: [ + fixError + ] + }); + + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "reportErrors/declarationEmitErrors/when file with no error changes", + commandLineArgs: ["-b", "-w", subProject], + sys: () => ts.tscWatch.createWatchedSystem( + [ts.tscWatch.libFile, fileWithError, fileWithoutError, tsconfig], + { currentDirectory: `${projectsLocation}/${solution}` } + ), + changes: [ + changeFileWithoutError + ] + }); - const changeFileWithoutError: TscWatchCompileChange = { - caption: "Change fileWithoutError", - change: sys => sys.writeFile(fileWithoutError.path, fileWithoutError.content.replace(/myClass/g, "myClass2")), - timeouts: incrementalBuild + describe("when reporting errors on introducing error", () => { + const introduceError: ts.tscWatch.TscWatchCompileChange = { + caption: "Introduce error", + change: sys => sys.writeFile(fileWithError.path, fileWithError.content), + timeouts: incrementalBuild, }; - verifyTscWatch({ + ts.tscWatch.verifyTscWatch({ scenario: "programUpdates", - subScenario: "reportErrors/declarationEmitErrors/when fixing error files all files are emitted", + subScenario: "reportErrors/declarationEmitErrors/introduceError/when fixing errors only changed file is emitted", commandLineArgs: ["-b", "-w", subProject], - sys: () => createWatchedSystem( - [libFile, fileWithError, fileWithoutError, tsconfig], + sys: () => ts.tscWatch.createWatchedSystem( + [ts.tscWatch.libFile, fileWithFixedError, fileWithoutError, tsconfig], { currentDirectory: `${projectsLocation}/${solution}` } ), changes: [ + introduceError, fixError ] }); - verifyTscWatch({ + ts.tscWatch.verifyTscWatch({ scenario: "programUpdates", - subScenario: "reportErrors/declarationEmitErrors/when file with no error changes", + subScenario: "reportErrors/declarationEmitErrors/introduceError/when file with no error changes", commandLineArgs: ["-b", "-w", subProject], - sys: () => createWatchedSystem( - [libFile, fileWithError, fileWithoutError, tsconfig], + sys: () => ts.tscWatch.createWatchedSystem( + [ts.tscWatch.libFile, fileWithFixedError, fileWithoutError, tsconfig], { currentDirectory: `${projectsLocation}/${solution}` } ), changes: [ + introduceError, changeFileWithoutError ] }); - - describe("when reporting errors on introducing error", () => { - const introduceError: TscWatchCompileChange = { - caption: "Introduce error", - change: sys => sys.writeFile(fileWithError.path, fileWithError.content), - timeouts: incrementalBuild, - }; - - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "reportErrors/declarationEmitErrors/introduceError/when fixing errors only changed file is emitted", - commandLineArgs: ["-b", "-w", subProject], - sys: () => createWatchedSystem( - [libFile, fileWithFixedError, fileWithoutError, tsconfig], - { currentDirectory: `${projectsLocation}/${solution}` } - ), - changes: [ - introduceError, - fixError - ] - }); - - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "reportErrors/declarationEmitErrors/introduceError/when file with no error changes", - commandLineArgs: ["-b", "-w", subProject], - sys: () => createWatchedSystem( - [libFile, fileWithFixedError, fileWithoutError, tsconfig], - { currentDirectory: `${projectsLocation}/${solution}` } - ), - changes: [ - introduceError, - changeFileWithoutError - ] - }); - }); }); }); + }); - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "incremental updates in verbose mode", - commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`, "-verbose"], - sys: () => createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), - changes: [ - { - caption: "Make non dts change", - change: sys => sys.writeFile(logic[1].path, `${logic[1].content} + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "incremental updates in verbose mode", + commandLineArgs: ["-b", "-w", `sample1/${SubProject.tests}`, "-verbose"], + sys: () => ts.tscWatch.createWatchedSystem(allFiles, { currentDirectory: projectsLocation }), + changes: [ + { + caption: "Make non dts change", + change: sys => sys.writeFile(logic[1].path, `${logic[1].content} function someFn() { }`), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // build logic and updates tests - sys.checkTimeoutQueueLength(0); - }, + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // build logic and updates tests + sys.checkTimeoutQueueLength(0); }, - { - caption: "Make dts change", - change: sys => sys.writeFile(logic[1].path, `${logic[1].content} + }, + { + caption: "Make dts change", + change: sys => sys.writeFile(logic[1].path, `${logic[1].content} export function someFn() { }`), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // build logic - sys.checkTimeoutQueueLengthAndRun(1); // build tests - }, - } - ], - }); + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // build logic + sys.checkTimeoutQueueLengthAndRun(1); // build tests + }, + } + ], + }); - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "works when noUnusedParameters changes to false", - commandLineArgs: ["-b", "-w"], - sys: () => { - const index: File = { - path: `${projectRoot}/index.ts`, - content: `const fn = (a: string, b: string) => b;` - }; - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - noUnusedParameters: true - } - }) - }; - return createWatchedSystem([index, configFile, libFile], { currentDirectory: projectRoot }); + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "works when noUnusedParameters changes to false", + commandLineArgs: ["-b", "-w"], + sys: () => { + const index: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/index.ts`, + content: `const fn = (a: string, b: string) => b;` + }; + const configFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + noUnusedParameters: true + } + }) + }; + return ts.tscWatch.createWatchedSystem([index, configFile, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Change tsconfig to set noUnusedParameters to false", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({ + compilerOptions: { + noUnusedParameters: false + } + })), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Change tsconfig to set noUnusedParameters to false", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ - compilerOptions: { - noUnusedParameters: false - } - })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "should not trigger recompilation because of program emit", - commandLineArgs: ["-b", "-w", `sample1/${SubProject.core}`, "-verbose"], - sys: () => createWatchedSystem([libFile, ...core], { currentDirectory: projectsLocation }), - changes: [ - noopChange, - { - caption: "Add new file", - change: sys => sys.writeFile(`sample1/${SubProject.core}/file3.ts`, `export const y = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - noopChange, - ] - }); + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "should not trigger recompilation because of program emit", + commandLineArgs: ["-b", "-w", `sample1/${SubProject.core}`, "-verbose"], + sys: () => ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, ...core], { currentDirectory: projectsLocation }), + changes: [ + ts.tscWatch.noopChange, + { + caption: "Add new file", + change: sys => sys.writeFile(`sample1/${SubProject.core}/file3.ts`, `export const y = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + ts.tscWatch.noopChange, + ] + }); - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "should not trigger recompilation because of program emit with outDir specified", - commandLineArgs: ["-b", "-w", `sample1/${SubProject.core}`, "-verbose"], - sys: () => { - const [coreConfig, ...rest] = core; - const newCoreConfig: File = { path: coreConfig.path, content: JSON.stringify({ compilerOptions: { composite: true, outDir: "outDir" } }) }; - return createWatchedSystem([libFile, newCoreConfig, ...rest], { currentDirectory: projectsLocation }); + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "should not trigger recompilation because of program emit with outDir specified", + commandLineArgs: ["-b", "-w", `sample1/${SubProject.core}`, "-verbose"], + sys: () => { + const [coreConfig, ...rest] = core; + const newCoreConfig: ts.tscWatch.File = { path: coreConfig.path, content: JSON.stringify({ compilerOptions: { composite: true, outDir: "outDir" } }) }; + return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, newCoreConfig, ...rest], { currentDirectory: projectsLocation }); + }, + changes: [ + ts.tscWatch.noopChange, + { + caption: "Add new file", + change: sys => sys.writeFile(`sample1/${SubProject.core}/file3.ts`, `export const y = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun }, - changes: [ - noopChange, - { - caption: "Add new file", - change: sys => sys.writeFile(`sample1/${SubProject.core}/file3.ts`, `export const y = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - noopChange - ] - }); + ts.tscWatch.noopChange + ] + }); - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "works with extended source files", - commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json"], - sys: () => { - const alphaExtendedConfigFile: File = { - path: "/a/b/alpha.tsconfig.json", - content: "{}" - }; - const project1Config: File = { - path: "/a/b/project1.tsconfig.json", - content: JSON.stringify({ - extends: "./alpha.tsconfig.json", - compilerOptions: { - composite: true, - }, - files: [commonFile1.path, commonFile2.path] - }) - }; - const bravoExtendedConfigFile: File = { - path: "/a/b/bravo.tsconfig.json", - content: JSON.stringify({ - extends: "./alpha.tsconfig.json" - }) - }; - const otherFile: File = { - path: "/a/b/other.ts", - content: "let z = 0;", - }; - const project2Config: File = { - path: "/a/b/project2.tsconfig.json", - content: JSON.stringify({ - extends: "./bravo.tsconfig.json", - compilerOptions: { - composite: true, - }, - files: [otherFile.path] - }) - }; - return createWatchedSystem([ - libFile, - alphaExtendedConfigFile, project1Config, commonFile1, commonFile2, - bravoExtendedConfigFile, project2Config, otherFile - ], { currentDirectory: "/a/b" }); + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "works with extended source files", + commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json"], + sys: () => { + const alphaExtendedConfigFile: ts.tscWatch.File = { + path: "/a/b/alpha.tsconfig.json", + content: "{}" + }; + const project1Config: ts.tscWatch.File = { + path: "/a/b/project1.tsconfig.json", + content: JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [ts.tscWatch.commonFile1.path, ts.tscWatch.commonFile2.path] + }) + }; + const bravoExtendedConfigFile: ts.tscWatch.File = { + path: "/a/b/bravo.tsconfig.json", + content: JSON.stringify({ + extends: "./alpha.tsconfig.json" + }) + }; + const otherFile: ts.tscWatch.File = { + path: "/a/b/other.ts", + content: "let z = 0;", + }; + const project2Config: ts.tscWatch.File = { + path: "/a/b/project2.tsconfig.json", + content: JSON.stringify({ + extends: "./bravo.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [otherFile.path] + }) + }; + return ts.tscWatch.createWatchedSystem([ + ts.tscWatch.libFile, + alphaExtendedConfigFile, project1Config, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, + bravoExtendedConfigFile, project2Config, otherFile + ], { currentDirectory: "/a/b" }); + }, + changes: [ + { + caption: "Modify alpha config", + change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", JSON.stringify({ + compilerOptions: { strict: true } + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build project1 }, - changes: [ - { - caption: "Modify alpha config", - change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", JSON.stringify({ - compilerOptions: { strict: true } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun // Build project1 - }, - { - caption: "Build project 2", - change: noop, - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 - }, - { - caption: "change bravo config", - change: sys => sys.writeFile("/a/b/bravo.tsconfig.json", JSON.stringify({ - extends: "./alpha.tsconfig.json", - compilerOptions: { strict: false } - })), - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 - }, - { - caption: "project 2 extends alpha", - change: sys => sys.writeFile("/a/b/project2.tsconfig.json", JSON.stringify({ - extends: "./alpha.tsconfig.json", - })), - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 - }, - { - caption: "update aplha config", - change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", "{}"), - timeouts: checkSingleTimeoutQueueLengthAndRun, // build project1 - }, - { - caption: "Build project 2", - change: noop, - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 - }, - ] - }); + { + caption: "Build project 2", + change: ts.noop, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + { + caption: "change bravo config", + change: sys => sys.writeFile("/a/b/bravo.tsconfig.json", JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { strict: false } + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + { + caption: "project 2 extends alpha", + change: sys => sys.writeFile("/a/b/project2.tsconfig.json", JSON.stringify({ + extends: "./alpha.tsconfig.json", + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + { + caption: "update aplha config", + change: sys => sys.writeFile("/a/b/alpha.tsconfig.json", "{}"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // build project1 + }, + { + caption: "Build project 2", + change: ts.noop, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 + }, + ] + }); - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "works correctly when project with extended config is removed", - commandLineArgs: ["-b", "-w", "-v"], - sys: () => { - const alphaExtendedConfigFile: File = { - path: "/a/b/alpha.tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - strict: true - } - }) - }; - const project1Config: File = { - path: "/a/b/project1.tsconfig.json", - content: JSON.stringify({ - extends: "./alpha.tsconfig.json", - compilerOptions: { - composite: true, + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "works correctly when project with extended config is removed", + commandLineArgs: ["-b", "-w", "-v"], + sys: () => { + const alphaExtendedConfigFile: ts.tscWatch.File = { + path: "/a/b/alpha.tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + strict: true + } + }) + }; + const project1Config: ts.tscWatch.File = { + path: "/a/b/project1.tsconfig.json", + content: JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [ts.tscWatch.commonFile1.path, ts.tscWatch.commonFile2.path] + }) + }; + const bravoExtendedConfigFile: ts.tscWatch.File = { + path: "/a/b/bravo.tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + strict: true + } + }) + }; + const otherFile: ts.tscWatch.File = { + path: "/a/b/other.ts", + content: "let z = 0;", + }; + const project2Config: ts.tscWatch.File = { + path: "/a/b/project2.tsconfig.json", + content: JSON.stringify({ + extends: "./bravo.tsconfig.json", + compilerOptions: { + composite: true, + }, + files: [otherFile.path] + }) + }; + const configFile: ts.tscWatch.File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + references: [ + { + path: "./project1.tsconfig.json", }, - files: [commonFile1.path, commonFile2.path] - }) - }; - const bravoExtendedConfigFile: File = { - path: "/a/b/bravo.tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - strict: true - } - }) - }; - const otherFile: File = { - path: "/a/b/other.ts", - content: "let z = 0;", - }; - const project2Config: File = { - path: "/a/b/project2.tsconfig.json", - content: JSON.stringify({ - extends: "./bravo.tsconfig.json", - compilerOptions: { - composite: true, + { + path: "./project2.tsconfig.json", }, - files: [otherFile.path] - }) - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - references: [ - { - path: "./project1.tsconfig.json", - }, - { - path: "./project2.tsconfig.json", - }, - ], - files: [], - }) - }; - return createWatchedSystem([ - libFile, configFile, - alphaExtendedConfigFile, project1Config, commonFile1, commonFile2, - bravoExtendedConfigFile, project2Config, otherFile - ], { currentDirectory: "/a/b" }); - }, - changes: [ - { - caption: "Remove project2 from base config", - change: sys => sys.modifyFile("/a/b/tsconfig.json", JSON.stringify({ - references: [ - { - path: "./project1.tsconfig.json", - }, - ], - files: [], - })), - timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, - } - ] - }); + ], + files: [], + }) + }; + return ts.tscWatch.createWatchedSystem([ + ts.tscWatch.libFile, configFile, + alphaExtendedConfigFile, project1Config, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, + bravoExtendedConfigFile, project2Config, otherFile + ], { currentDirectory: "/a/b" }); + }, + changes: [ + { + caption: "Remove project2 from base config", + change: sys => sys.modifyFile("/a/b/tsconfig.json", JSON.stringify({ + references: [ + { + path: "./project1.tsconfig.json", + }, + ], + files: [], + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout, + } + ] + }); - verifyTscWatch({ - scenario: "programUpdates", - subScenario: "tsbuildinfo has error", - sys: () => createWatchedSystem({ - "/src/project/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": "{}", - "/src/project/tsconfig.tsbuildinfo": "Some random string", - [libFile.path]: libFile.content, - }), - commandLineArgs: ["--b", "src/project", "-i", "-w"], - changes: emptyArray - }); + ts.tscWatch.verifyTscWatch({ + scenario: "programUpdates", + subScenario: "tsbuildinfo has error", + sys: () => ts.tscWatch.createWatchedSystem({ + "/src/project/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": "{}", + "/src/project/tsconfig.tsbuildinfo": "Some random string", + [ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content, + }), + commandLineArgs: ["--b", "src/project", "-i", "-w"], + changes: ts.emptyArray }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuildWatch/projectsBuilding.ts b/src/testRunner/unittests/tsbuildWatch/projectsBuilding.ts index 51f54742c79ab..b79c49a43b4fd 100644 --- a/src/testRunner/unittests/tsbuildWatch/projectsBuilding.ts +++ b/src/testRunner/unittests/tsbuildWatch/projectsBuilding.ts @@ -1,185 +1,185 @@ -namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: projectsBuilding", () => { - function pkgs(cb: (index: number) => T, count: number, startIndex?: number): T[] { - const result: T[] = []; - for (let index = startIndex || 0; count > 0; index++, count--) { - result.push(cb(index)); - } - return result; - } - function createPkgReference(index: number) { - return { path: `./pkg${index}` }; - } - function pkgFiles(index: number): File[] { - return [ - { - path: `${projectRoot}/pkg${index}/index.ts`, - content: `export const pkg${index} = ${index};` - }, - { - path: `${projectRoot}/pkg${index}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true }, - references: index === 0 ? - undefined : - [{ path: `../pkg0` }] - }) - } - ]; +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsbuildWatch:: watchMode:: projectsBuilding", () => { + function pkgs(cb: (index: number) => T, count: number, startIndex?: number): T[] { + const result: T[] = []; + for (let index = startIndex || 0; count > 0; index++, count--) { + result.push(cb(index)); } - function solution(maxPkgs: number): File { - return { - path: `${projectRoot}/tsconfig.json`, + return result; + } + function createPkgReference(index: number) { + return { path: `./pkg${index}` }; + } + function pkgFiles(index: number): ts.tscWatch.File[] { + return [ + { + path: `${ts.tscWatch.projectRoot}/pkg${index}/index.ts`, + content: `export const pkg${index} = ${index};` + }, + { + path: `${ts.tscWatch.projectRoot}/pkg${index}/tsconfig.json`, content: JSON.stringify({ - references: pkgs(createPkgReference, maxPkgs), - files: [], + compilerOptions: { composite: true }, + references: index === 0 ? + undefined : + [{ path: `../pkg0` }] }) - }; - } - function checkBuildPkg(startIndex: number, count: number): TscWatchCompileChange { - return { - caption: `build ${pkgs(index => `pkg${index}`, count, startIndex).join(",")}`, - change: noop, - timeouts: checkSingleTimeoutQueueLengthAndRun, - }; - } - verifyTscWatch({ - scenario: "projectsBuilding", - subScenario: `when there are 3 projects in a solution`, - commandLineArgs: ["-b", "-w", "-v"], - sys: () => createWatchedSystem( - [libFile, ...flatMap(pkgs(pkgFiles, 3), identity), solution(3)], - { currentDirectory: projectRoot } - ), - changes: [ - { - caption: "dts doesn't change", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps - }, - noopChange, - { - caption: "dts change", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `export const someConst = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun // Build pkg0 - }, - checkBuildPkg(1, 2), - noopChange, - ] - }); - verifyTscWatch({ - scenario: "projectsBuilding", - subScenario: `when there are 5 projects in a solution`, - commandLineArgs: ["-b", "-w", "-v"], - sys: () => createWatchedSystem( - [libFile, ...flatMap(pkgs(pkgFiles, 5), identity), solution(5)], - { currentDirectory: projectRoot } - ), - changes: [ - { - caption: "dts doesn't change", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps - }, - noopChange, - { - caption: "dts change", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `export const someConst = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun // Build pkg0 - }, - checkBuildPkg(1, 4), - noopChange, - ] - }); - verifyTscWatch({ - scenario: "projectsBuilding", - subScenario: `when there are 8 projects in a solution`, - commandLineArgs: ["-b", "-w", "-v"], - sys: () => createWatchedSystem( - [libFile, ...flatMap(pkgs(pkgFiles, 8), identity), solution(8)], - { currentDirectory: projectRoot } - ), - changes: [ - { - caption: "dts doesn't change", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps - }, - noopChange, - { - caption: "dts change", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `export const someConst = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun // Build pkg0 - }, - checkBuildPkg(1, 5), - checkBuildPkg(6, 2), - noopChange, - { - caption: "dts change2", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `export const someConst3 = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun // Build pkg0 - }, - checkBuildPkg(1, 5), - { - caption: "change while building", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `const someConst4 = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun // Build pkg0 - }, - checkBuildPkg(6, 2), - noopChange, - ] - }); - verifyTscWatch({ - scenario: "projectsBuilding", - subScenario: `when there are 23 projects in a solution`, - commandLineArgs: ["-b", "-w", "-v"], - sys: () => createWatchedSystem( - [libFile, ...flatMap(pkgs(pkgFiles, 23), identity), solution(23)], - { currentDirectory: projectRoot } - ), - changes: [ - { - caption: "dts doesn't change", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps - }, - noopChange, - { - caption: "dts change", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `export const someConst = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun // Build pkg0 - }, - checkBuildPkg(1, 5), - checkBuildPkg(6, 5), - checkBuildPkg(11, 5), - checkBuildPkg(16, 5), - checkBuildPkg(21, 3), - noopChange, - { - caption: "dts change2", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `export const someConst3 = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun // Build pkg0 - }, - checkBuildPkg(1, 5), - checkBuildPkg(6, 5), - { - caption: "change while building", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `const someConst4 = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun // Build pkg0 - }, - checkBuildPkg(11, 5), - { - caption: "change while building: dts changes", - change: sys => sys.appendFile(`${projectRoot}/pkg0/index.ts`, `export const someConst5 = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun // Build pkg0 - }, - checkBuildPkg(1, 5), - checkBuildPkg(6, 5), - checkBuildPkg(11, 5), - checkBuildPkg(16, 5), - checkBuildPkg(21, 3), - noopChange, - ] - }); + } + ]; + } + function solution(maxPkgs: number): ts.tscWatch.File { + return { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + references: pkgs(createPkgReference, maxPkgs), + files: [], + }) + }; + } + function checkBuildPkg(startIndex: number, count: number): ts.tscWatch.TscWatchCompileChange { + return { + caption: `build ${pkgs(index => `pkg${index}`, count, startIndex).join(",")}`, + change: ts.noop, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }; + } + ts.tscWatch.verifyTscWatch({ + scenario: "projectsBuilding", + subScenario: `when there are 3 projects in a solution`, + commandLineArgs: ["-b", "-w", "-v"], + sys: () => ts.tscWatch.createWatchedSystem( + [ts.tscWatch.libFile, ...ts.flatMap(pkgs(pkgFiles, 3), ts.identity), solution(3)], + { currentDirectory: ts.tscWatch.projectRoot } + ), + changes: [ + { + caption: "dts doesn't change", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps + }, + ts.tscWatch.noopChange, + { + caption: "dts change", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0 + }, + checkBuildPkg(1, 2), + ts.tscWatch.noopChange, + ] + }); + ts.tscWatch.verifyTscWatch({ + scenario: "projectsBuilding", + subScenario: `when there are 5 projects in a solution`, + commandLineArgs: ["-b", "-w", "-v"], + sys: () => ts.tscWatch.createWatchedSystem( + [ts.tscWatch.libFile, ...ts.flatMap(pkgs(pkgFiles, 5), ts.identity), solution(5)], + { currentDirectory: ts.tscWatch.projectRoot } + ), + changes: [ + { + caption: "dts doesn't change", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps + }, + ts.tscWatch.noopChange, + { + caption: "dts change", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0 + }, + checkBuildPkg(1, 4), + ts.tscWatch.noopChange, + ] + }); + ts.tscWatch.verifyTscWatch({ + scenario: "projectsBuilding", + subScenario: `when there are 8 projects in a solution`, + commandLineArgs: ["-b", "-w", "-v"], + sys: () => ts.tscWatch.createWatchedSystem( + [ts.tscWatch.libFile, ...ts.flatMap(pkgs(pkgFiles, 8), ts.identity), solution(8)], + { currentDirectory: ts.tscWatch.projectRoot } + ), + changes: [ + { + caption: "dts doesn't change", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps + }, + ts.tscWatch.noopChange, + { + caption: "dts change", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0 + }, + checkBuildPkg(1, 5), + checkBuildPkg(6, 2), + ts.tscWatch.noopChange, + { + caption: "dts change2", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst3 = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0 + }, + checkBuildPkg(1, 5), + { + caption: "change while building", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst4 = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0 + }, + checkBuildPkg(6, 2), + ts.tscWatch.noopChange, + ] + }); + ts.tscWatch.verifyTscWatch({ + scenario: "projectsBuilding", + subScenario: `when there are 23 projects in a solution`, + commandLineArgs: ["-b", "-w", "-v"], + sys: () => ts.tscWatch.createWatchedSystem( + [ts.tscWatch.libFile, ...ts.flatMap(pkgs(pkgFiles, 23), ts.identity), solution(23)], + { currentDirectory: ts.tscWatch.projectRoot } + ), + changes: [ + { + caption: "dts doesn't change", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst2 = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Build pkg0 and update timestamps + }, + ts.tscWatch.noopChange, + { + caption: "dts change", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0 + }, + checkBuildPkg(1, 5), + checkBuildPkg(6, 5), + checkBuildPkg(11, 5), + checkBuildPkg(16, 5), + checkBuildPkg(21, 3), + ts.tscWatch.noopChange, + { + caption: "dts change2", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst3 = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0 + }, + checkBuildPkg(1, 5), + checkBuildPkg(6, 5), + { + caption: "change while building", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `const someConst4 = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0 + }, + checkBuildPkg(11, 5), + { + caption: "change while building: dts changes", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/pkg0/index.ts`, `export const someConst5 = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun // Build pkg0 + }, + checkBuildPkg(1, 5), + checkBuildPkg(6, 5), + checkBuildPkg(11, 5), + checkBuildPkg(16, 5), + checkBuildPkg(21, 3), + ts.tscWatch.noopChange, + ] }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuildWatch/publicApi.ts b/src/testRunner/unittests/tsbuildWatch/publicApi.ts index 83964c6a2572b..c58862bff6bd4 100644 --- a/src/testRunner/unittests/tsbuildWatch/publicApi.ts +++ b/src/testRunner/unittests/tsbuildWatch/publicApi.ts @@ -1,105 +1,105 @@ -namespace ts.tscWatch { - it("unittests:: tsbuildWatch:: watchMode:: Public API with custom transformers", () => { - const solution: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - references: [ - { path: "./shared/tsconfig.json" }, - { path: "./webpack/tsconfig.json" } - ], - files: [] - }) - }; - const sharedConfig: File = { - path: `${projectRoot}/shared/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true }, - }) - }; - const sharedIndex: File = { - path: `${projectRoot}/shared/index.ts`, - content: `export function f1() { } +import * as ts from "../../_namespaces/ts"; + +it("unittests:: tsbuildWatch:: watchMode:: Public API with custom transformers", () => { + const solution: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + references: [ + { path: "./shared/tsconfig.json" }, + { path: "./webpack/tsconfig.json" } + ], + files: [] + }) + }; + const sharedConfig: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/shared/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true }, + }) + }; + const sharedIndex: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/shared/index.ts`, + content: `export function f1() { } export class c { } export enum e { } // leading export function f2() { } // trailing` - }; - const webpackConfig: File = { - path: `${projectRoot}/webpack/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, }, - references: [{ path: "../shared/tsconfig.json" }] - }) - }; - const webpackIndex: File = { - path: `${projectRoot}/webpack/index.ts`, - content: `export function f2() { } + }; + const webpackConfig: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/webpack/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, }, + references: [{ path: "../shared/tsconfig.json" }] + }) + }; + const webpackIndex: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/webpack/index.ts`, + content: `export function f2() { } export class c2 { } export enum e2 { } // leading export function f22() { } // trailing` - }; - const commandLineArgs = ["--b", "--w"]; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([libFile, solution, sharedConfig, sharedIndex, webpackConfig, webpackIndex], { currentDirectory: projectRoot })); - const buildHost = createSolutionBuilderWithWatchHostForBaseline(sys, cb); - buildHost.getCustomTransformers = getCustomTransformers; - const builder = createSolutionBuilderWithWatch(buildHost, [solution.path], { verbose: true }); - builder.build(); - runWatchBaseline({ - scenario: "publicApi", - subScenario: "with custom transformers", - commandLineArgs, - sys, - baseline, - oldSnap, - getPrograms, - changes: [ - { - caption: "change to shared", - change: sys => sys.prependFile(sharedIndex.path, "export function fooBar() {}"), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // Shared - sys.checkTimeoutQueueLengthAndRun(1); // webpack and solution - sys.checkTimeoutQueueLength(0); - } + }; + const commandLineArgs = ["--b", "--w"]; + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, solution, sharedConfig, sharedIndex, webpackConfig, webpackIndex], { currentDirectory: ts.tscWatch.projectRoot })); + const buildHost = ts.tscWatch.createSolutionBuilderWithWatchHostForBaseline(sys, cb); + buildHost.getCustomTransformers = getCustomTransformers; + const builder = ts.createSolutionBuilderWithWatch(buildHost, [solution.path], { verbose: true }); + builder.build(); + ts.tscWatch.runWatchBaseline({ + scenario: "publicApi", + subScenario: "with custom transformers", + commandLineArgs, + sys, + baseline, + oldSnap, + getPrograms, + changes: [ + { + caption: "change to shared", + change: sys => sys.prependFile(sharedIndex.path, "export function fooBar() {}"), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // Shared + sys.checkTimeoutQueueLengthAndRun(1); // webpack and solution + sys.checkTimeoutQueueLength(0); } - ], - watchOrSolution: builder - }); + } + ], + watchOrSolution: builder + }); - function getCustomTransformers(project: string): CustomTransformers { - const before: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - return visitFunction(node as FunctionDeclaration); - default: - return visitEachChild(node, visit, context); - } + function getCustomTransformers(project: string): ts.CustomTransformers { + const before: ts.TransformerFactory = context => { + return file => ts.visitEachChild(file, visit, context); + function visit(node: ts.Node): ts.VisitResult { + switch (node.kind) { + case ts.SyntaxKind.FunctionDeclaration: + return visitFunction(node as ts.FunctionDeclaration); + default: + return ts.visitEachChild(node, visit, context); } - function visitFunction(node: FunctionDeclaration) { - addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, `@before${project}`, /*hasTrailingNewLine*/ true); - return node; - } - }; + } + function visitFunction(node: ts.FunctionDeclaration) { + ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, `@before${project}`, /*hasTrailingNewLine*/ true); + return node; + } + }; - const after: TransformerFactory = context => { - return file => visitEachChild(file, visit, context); - function visit(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.VariableStatement: - return visitVariableStatement(node as VariableStatement); - default: - return visitEachChild(node, visit, context); - } + const after: ts.TransformerFactory = context => { + return file => ts.visitEachChild(file, visit, context); + function visit(node: ts.Node): ts.VisitResult { + switch (node.kind) { + case ts.SyntaxKind.VariableStatement: + return visitVariableStatement(node as ts.VariableStatement); + default: + return ts.visitEachChild(node, visit, context); } - function visitVariableStatement(node: VariableStatement) { - addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, `@after${project}`); - return node; - } - }; - return { before: [before], after: [after] }; - } - }); -} \ No newline at end of file + } + function visitVariableStatement(node: ts.VariableStatement) { + ts.addSyntheticLeadingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, `@after${project}`); + return node; + } + }; + return { before: [before], after: [after] }; + } +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuildWatch/reexport.ts b/src/testRunner/unittests/tsbuildWatch/reexport.ts index f7c5861f45ca3..23d2857bf01ce 100644 --- a/src/testRunner/unittests/tsbuildWatch/reexport.ts +++ b/src/testRunner/unittests/tsbuildWatch/reexport.ts @@ -1,41 +1,41 @@ -namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchMode:: with reexport when referenced project reexports definitions from another file", () => { - verifyTscWatch({ - scenario: "reexport", - subScenario: "Reports errors correctly", - commandLineArgs: ["-b", "-w", "-verbose", "src"], - sys: () => createWatchedSystem( - [ - ...[ - "src/tsconfig.json", - "src/main/tsconfig.json", "src/main/index.ts", - "src/pure/tsconfig.json", "src/pure/index.ts", "src/pure/session.ts" - ] - .map(f => TestFSWithWatch.getTsBuildProjectFile("reexport", f)), - { path: libFile.path, content: libContent } - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/reexport` } - ), - changes: [ - { - caption: "Introduce error", - change: sys => replaceFileText(sys, `${TestFSWithWatch.tsbuildProjectsLocation}/reexport/src/pure/session.ts`, "// ", ""), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // build src/pure - sys.checkTimeoutQueueLengthAndRun(1); // build src/main and src - sys.checkTimeoutQueueLength(0); - }, +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsbuildWatch:: watchMode:: with reexport when referenced project reexports definitions from another file", () => { + ts.tscWatch.verifyTscWatch({ + scenario: "reexport", + subScenario: "Reports errors correctly", + commandLineArgs: ["-b", "-w", "-verbose", "src"], + sys: () => ts.tscWatch.createWatchedSystem( + [ + ...[ + "src/tsconfig.json", + "src/main/tsconfig.json", "src/main/index.ts", + "src/pure/tsconfig.json", "src/pure/index.ts", "src/pure/session.ts" + ] + .map(f => ts.TestFSWithWatch.getTsBuildProjectFile("reexport", f)), + { path: ts.tscWatch.libFile.path, content: ts.libContent } + ], + { currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/reexport` } + ), + changes: [ + { + caption: "Introduce error", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.TestFSWithWatch.tsbuildProjectsLocation}/reexport/src/pure/session.ts`, "// ", ""), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // build src/pure + sys.checkTimeoutQueueLengthAndRun(1); // build src/main and src + sys.checkTimeoutQueueLength(0); }, - { - caption: "Fix error", - change: sys => replaceFileText(sys, `${TestFSWithWatch.tsbuildProjectsLocation}/reexport/src/pure/session.ts`, "bar: ", "// bar: "), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // build src/pure - sys.checkTimeoutQueueLengthAndRun(1); // build src/main and src - sys.checkTimeoutQueueLength(0); - }, - } - ] - }); + }, + { + caption: "Fix error", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.TestFSWithWatch.tsbuildProjectsLocation}/reexport/src/pure/session.ts`, "bar: ", "// bar: "), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // build src/pure + sys.checkTimeoutQueueLengthAndRun(1); // build src/main and src + sys.checkTimeoutQueueLength(0); + }, + } + ] }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts b/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts index 053719a368094..7021a2ea8a522 100644 --- a/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts @@ -1,111 +1,111 @@ -namespace ts.tscWatch { - describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: with different watch environments", () => { - it("watchFile on same file multiple times because file is part of multiple projects", () => { - const project = `${TestFSWithWatch.tsbuildProjectsLocation}/myproject`; - let maxPkgs = 4; - const configPath = `${project}/tsconfig.json`; - const typing: File = { - path: `${project}/typings/xterm.d.ts`, - content: "export const typing = 10;" - }; +import * as ts from "../../_namespaces/ts"; - const allPkgFiles = pkgs(pkgFiles); - const system = createWatchedSystem([libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project }); - writePkgReferences(system); - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(system); - const host = createSolutionBuilderWithWatchHostForBaseline(sys, cb); - const solutionBuilder = createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true }); - solutionBuilder.build(); - runWatchBaseline({ - scenario: "watchEnvironment", - subScenario: `same file in multiple projects with single watcher per file`, - commandLineArgs: ["--b", "--w"], - sys, - baseline, - oldSnap, - getPrograms, - changes: [ - { - caption: "modify typing file", - change: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); - checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys); - } - }, - { - // Make change - caption: "change pkg references", - change: sys => { - maxPkgs--; - writePkgReferences(sys); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "modify typing file", - change: sys => sys.writeFile(typing.path, typing.content), - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); - checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys); - } - }, - { - // Make change to remove all watches - caption: "change pkg references to remove all watches", - change: sys => { - maxPkgs = 0; - writePkgReferences(sys); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, +describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: with different watch environments", () => { + it("watchFile on same file multiple times because file is part of multiple projects", () => { + const project = `${ts.TestFSWithWatch.tsbuildProjectsLocation}/myproject`; + let maxPkgs = 4; + const configPath = `${project}/tsconfig.json`; + const typing: ts.tscWatch.File = { + path: `${project}/typings/xterm.d.ts`, + content: "export const typing = 10;" + }; + + const allPkgFiles = pkgs(pkgFiles); + const system = ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project }); + writePkgReferences(system); + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(system); + const host = ts.tscWatch.createSolutionBuilderWithWatchHostForBaseline(sys, cb); + const solutionBuilder = ts.createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true }); + solutionBuilder.build(); + ts.tscWatch.runWatchBaseline({ + scenario: "watchEnvironment", + subScenario: `same file in multiple projects with single watcher per file`, + commandLineArgs: ["--b", "--w"], + sys, + baseline, + oldSnap, + getPrograms, + changes: [ + { + caption: "modify typing file", + change: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); + ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys); + } + }, + { + // Make change + caption: "change pkg references", + change: sys => { + maxPkgs--; + writePkgReferences(sys); }, - { - caption: "modify typing file", - change: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`), - timeouts: sys => sys.checkTimeoutQueueLength(0), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "modify typing file", + change: sys => sys.writeFile(typing.path, typing.content), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); + ts.tscWatch.checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys); + } + }, + { + // Make change to remove all watches + caption: "change pkg references to remove all watches", + change: sys => { + maxPkgs = 0; + writePkgReferences(sys); }, - ], - watchOrSolution: solutionBuilder - }); + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "modify typing file", + change: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`), + timeouts: sys => sys.checkTimeoutQueueLength(0), + }, + ], + watchOrSolution: solutionBuilder + }); - function flatArray(arr: T[][]): readonly T[] { - return flatMap(arr, identity); + function flatArray(arr: T[][]): readonly T[] { + return ts.flatMap(arr, ts.identity); + } + function pkgs(cb: (index: number) => T): T[] { + const result: T[] = []; + for (let index = 0; index < maxPkgs; index++) { + result.push(cb(index)); } - function pkgs(cb: (index: number) => T): T[] { - const result: T[] = []; - for (let index = 0; index < maxPkgs; index++) { - result.push(cb(index)); + return result; + } + function createPkgReference(index: number) { + return { path: `./pkg${index}` }; + } + function pkgFiles(index: number): ts.tscWatch.File[] { + return [ + { + path: `${project}/pkg${index}/index.ts`, + content: `export const pkg${index} = ${index};` + }, + { + path: `${project}/pkg${index}/tsconfig.json`, + content: JSON.stringify({ + complerOptions: { composite: true }, + include: [ + "**/*.ts", + "../typings/xterm.d.ts" + ] + }) } - return result; - } - function createPkgReference(index: number) { - return { path: `./pkg${index}` }; - } - function pkgFiles(index: number): File[] { - return [ - { - path: `${project}/pkg${index}/index.ts`, - content: `export const pkg${index} = ${index};` - }, - { - path: `${project}/pkg${index}/tsconfig.json`, - content: JSON.stringify({ - complerOptions: { composite: true }, - include: [ - "**/*.ts", - "../typings/xterm.d.ts" - ] - }) - } - ]; - } - function writePkgReferences(system: TestFSWithWatch.TestServerHost) { - system.writeFile(configPath, JSON.stringify({ - files: [], - include: [], - references: pkgs(createPkgReference) - })); - } - }); + ]; + } + function writePkgReferences(system: ts.TestFSWithWatch.TestServerHost) { + system.writeFile(configPath, JSON.stringify({ + files: [], + include: [], + references: pkgs(createPkgReference) + })); + } }); -} +}); diff --git a/src/testRunner/unittests/tsc/cancellationToken.ts b/src/testRunner/unittests/tsc/cancellationToken.ts index 462b08c9f084f..21783b336aae4 100644 --- a/src/testRunner/unittests/tsc/cancellationToken.ts +++ b/src/testRunner/unittests/tsc/cancellationToken.ts @@ -1,169 +1,171 @@ -namespace ts.tscWatch { - describe("unittests:: tsc:: builder cancellationToken", () => { - verifyCancellation(/*useBuildInfo*/ true, "when emitting buildInfo"); - verifyCancellation(/*useBuildInfo*/ false, "when using state"); - function verifyCancellation(useBuildInfo: boolean, scenario: string) { - it(scenario, () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; +import * as Harness from "../../_namespaces/Harness"; + +describe("unittests:: tsc:: builder cancellationToken", () => { + verifyCancellation(/*useBuildInfo*/ true, "when emitting buildInfo"); + verifyCancellation(/*useBuildInfo*/ false, "when using state"); + function verifyCancellation(useBuildInfo: boolean, scenario: string) { + it(scenario, () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: Utils.dedent` import {B} from './b'; declare var console: any; let b = new B(); console.log(b.c.d);` - }; - const bFile: File = { - path: `${projectRoot}/b.ts`, - content: Utils.dedent` + }; + const bFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/b.ts`, + content: Utils.dedent` import {C} from './c'; export class B { c = new C(); }` - }; - const cFile: File = { - path: `${projectRoot}/c.ts`, - content: Utils.dedent` + }; + const cFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/c.ts`, + content: Utils.dedent` export class C { d = 1; }` - }; - const dFile: File = { - path: `${projectRoot}/d.ts`, - content: "export class D { }" - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { incremental: true, declaration: true } }) - }; - const { sys, baseline, oldSnap: originalSnap } = createBaseline(createWatchedSystem( - [aFile, bFile, cFile, dFile, config, libFile], - { currentDirectory: projectRoot } - )); - sys.exit = exitCode => sys.exitCode = exitCode; - const reportDiagnostic = createDiagnosticReporter(sys, /*pretty*/ true); - const parsedConfig = parseConfigFileWithSystem( - "tsconfig.json", - {}, - /*extendedConfigCache*/ undefined, - /*watchOptionsToExtend*/ undefined, - sys, - reportDiagnostic - )!; - const host = createIncrementalCompilerHost(parsedConfig.options, sys); - let programs: CommandLineProgram[] = emptyArray; - let oldPrograms: CommandLineProgram[] = emptyArray; - let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram = undefined!; - let oldSnap = originalSnap; - let cancel = false; - const cancellationToken: CancellationToken = { - isCancellationRequested: () => cancel, - throwIfCancellationRequested: () => { - if (cancel) { - sys.write(`Cancelled!!\r\n`); - throw new OperationCanceledException(); - } - }, - }; - - // Initial build - baselineBuild(); - - // Cancel on first semantic operation - // Change - oldSnap = applyChange( - sys, - baseline, - sys => sys.appendFile(cFile.path, "export function foo() {}"), - "Add change that affects d.ts" - ); - createIncrementalProgram(); - - // Cancel during semantic diagnostics - cancel = true; - try { - builderProgram.getSemanticDiagnosticsOfNextAffectedFile(cancellationToken); - } - catch (e) { - sys.write(`Operation ws cancelled:: ${e instanceof OperationCanceledException}\r\n`); - } - cancel = false; - builderProgram.emitBuildInfo(); - baselineBuildInfo(builderProgram.getCompilerOptions(), sys); - watchBaseline({ - baseline, - getPrograms: () => programs, - oldPrograms, - sys, - oldSnap, - }); + }; + const dFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/d.ts`, + content: "export class D { }" + }; + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { incremental: true, declaration: true } }) + }; + const { sys, baseline, oldSnap: originalSnap } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem( + [aFile, bFile, cFile, dFile, config, ts.tscWatch.libFile], + { currentDirectory: ts.tscWatch.projectRoot } + )); + sys.exit = exitCode => sys.exitCode = exitCode; + const reportDiagnostic = ts.createDiagnosticReporter(sys, /*pretty*/ true); + const parsedConfig = ts.parseConfigFileWithSystem( + "tsconfig.json", + {}, + /*extendedConfigCache*/ undefined, + /*watchOptionsToExtend*/ undefined, + sys, + reportDiagnostic + )!; + const host = ts.createIncrementalCompilerHost(parsedConfig.options, sys); + let programs: ts.CommandLineProgram[] = ts.emptyArray; + let oldPrograms: ts.CommandLineProgram[] = ts.emptyArray; + let builderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram = undefined!; + let oldSnap = originalSnap; + let cancel = false; + const cancellationToken: ts.CancellationToken = { + isCancellationRequested: () => cancel, + throwIfCancellationRequested: () => { + if (cancel) { + sys.write(`Cancelled!!\r\n`); + throw new ts.OperationCanceledException(); + } + }, + }; - // Normal emit again - noChange("Normal build"); - baselineBuild(); + // Initial build + baselineBuild(); - // Do clean build:: all the emitted files should be same - noChange("Clean build"); - baselineCleanBuild(); + // Cancel on first semantic operation + // Change + oldSnap = ts.tscWatch.applyChange( + sys, + baseline, + sys => sys.appendFile(cFile.path, "export function foo() {}"), + "Add change that affects d.ts" + ); + createIncrementalProgram(); - Harness.Baseline.runBaseline(`tsc/cancellationToken/${scenario.split(" ").join("-")}.js`, baseline.join("\r\n")); + // Cancel during semantic diagnostics + cancel = true; + try { + builderProgram.getSemanticDiagnosticsOfNextAffectedFile(cancellationToken); + } + catch (e) { + sys.write(`Operation ws cancelled:: ${e instanceof ts.OperationCanceledException}\r\n`); + } + cancel = false; + builderProgram.emitBuildInfo(); + ts.baselineBuildInfo(builderProgram.getCompilerOptions(), sys); + ts.tscWatch.watchBaseline({ + baseline, + getPrograms: () => programs, + oldPrograms, + sys, + oldSnap, + }); - function noChange(caption: string) { - oldSnap = applyChange(sys, baseline, noop, caption); - } + // Normal emit again + noChange("Normal build"); + baselineBuild(); - function updatePrograms() { - oldPrograms = programs; - programs = [[builderProgram.getProgram(), builderProgram]]; - } + // Do clean build:: all the emitted files should be same + noChange("Clean build"); + baselineCleanBuild(); - function createIncrementalProgram() { - builderProgram = useBuildInfo ? - ts.createIncrementalProgram({ - rootNames: parsedConfig.fileNames, - options: parsedConfig.options, - host, - }) : - builderProgram = builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram( - parsedConfig.fileNames, - parsedConfig.options, - host, - builderProgram, - /* configFileParsingDiagnostics*/ undefined, - /*projectReferences*/ undefined, - ); - updatePrograms(); - } + Harness.Baseline.runBaseline(`tsc/cancellationToken/${scenario.split(" ").join("-")}.js`, baseline.join("\r\n")); - function emitAndBaseline() { - emitFilesAndReportErrorsAndGetExitStatus(builderProgram, reportDiagnostic); - baselineBuildInfo(builderProgram.getCompilerOptions(), sys); - watchBaseline({ - baseline, - getPrograms: () => programs, - oldPrograms, - sys, - oldSnap, - }); - } + function noChange(caption: string) { + oldSnap = ts.tscWatch.applyChange(sys, baseline, ts.noop, caption); + } - function baselineBuild() { - createIncrementalProgram(); - emitAndBaseline(); - } + function updatePrograms() { + oldPrograms = programs; + programs = [[builderProgram.getProgram(), builderProgram]]; + } - function baselineCleanBuild() { - builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram( + function createIncrementalProgram() { + builderProgram = useBuildInfo ? + ts.createIncrementalProgram({ + rootNames: parsedConfig.fileNames, + options: parsedConfig.options, + host, + }) : + builderProgram = builderProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram( parsedConfig.fileNames, parsedConfig.options, host, - /*oldProgram*/ undefined, + builderProgram, /* configFileParsingDiagnostics*/ undefined, /*projectReferences*/ undefined, ); - updatePrograms(); - emitAndBaseline(); - } - }); - } - }); -} \ No newline at end of file + updatePrograms(); + } + + function emitAndBaseline() { + ts.emitFilesAndReportErrorsAndGetExitStatus(builderProgram, reportDiagnostic); + ts.baselineBuildInfo(builderProgram.getCompilerOptions(), sys); + ts.tscWatch.watchBaseline({ + baseline, + getPrograms: () => programs, + oldPrograms, + sys, + oldSnap, + }); + } + + function baselineBuild() { + createIncrementalProgram(); + emitAndBaseline(); + } + + function baselineCleanBuild() { + builderProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram( + parsedConfig.fileNames, + parsedConfig.options, + host, + /*oldProgram*/ undefined, + /* configFileParsingDiagnostics*/ undefined, + /*projectReferences*/ undefined, + ); + updatePrograms(); + emitAndBaseline(); + } + }); + } +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsc/composite.ts b/src/testRunner/unittests/tsc/composite.ts index 48a5464d59e6a..a71001d59f41a 100644 --- a/src/testRunner/unittests/tsc/composite.ts +++ b/src/testRunner/unittests/tsc/composite.ts @@ -1,11 +1,13 @@ -namespace ts { - describe("unittests:: tsc:: composite::", () => { - verifyTsc({ - scenario: "composite", - subScenario: "when setting composite false on command line", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; + +describe("unittests:: tsc:: composite::", () => { + ts.verifyTsc({ + scenario: "composite", + subScenario: "when setting composite false on command line", + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", @@ -16,16 +18,16 @@ namespace ts { "src/**/*.ts" ] }`, - }), - commandLineArgs: ["--composite", "false", "--p", "src/project"], - }); + }), + commandLineArgs: ["--composite", "false", "--p", "src/project"], + }); - verifyTsc({ - scenario: "composite", - subScenario: "when setting composite null on command line", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` + ts.verifyTsc({ + scenario: "composite", + subScenario: "when setting composite null on command line", + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", @@ -36,16 +38,16 @@ namespace ts { "src/**/*.ts" ] }`, - }), - commandLineArgs: ["--composite", "null", "--p", "src/project"], - }); + }), + commandLineArgs: ["--composite", "null", "--p", "src/project"], + }); - verifyTsc({ - scenario: "composite", - subScenario: "when setting composite false on command line but has tsbuild info in config", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` + ts.verifyTsc({ + scenario: "composite", + subScenario: "when setting composite false on command line but has tsbuild info in config", + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", @@ -57,16 +59,16 @@ namespace ts { "src/**/*.ts" ] }`, - }), - commandLineArgs: ["--composite", "false", "--p", "src/project"], - }); + }), + commandLineArgs: ["--composite", "false", "--p", "src/project"], + }); - verifyTsc({ - scenario: "composite", - subScenario: "when setting composite false and tsbuildinfo as null on command line but has tsbuild info in config", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` + ts.verifyTsc({ + scenario: "composite", + subScenario: "when setting composite false and tsbuildinfo as null on command line but has tsbuild info in config", + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", @@ -78,29 +80,28 @@ namespace ts { "src/**/*.ts" ] }`, - }), - commandLineArgs: ["--composite", "false", "--p", "src/project", "--tsBuildInfoFile", "null"], - }); + }), + commandLineArgs: ["--composite", "false", "--p", "src/project", "--tsBuildInfoFile", "null"], + }); - verifyTscWithEdits({ - scenario: "composite", - subScenario: "converting to modules", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "const x = 10;", - "/src/project/tsconfig.json": JSON.stringify({ - compilerOptions: { - module: "none", - composite: true, - }, - }), + ts.verifyTscWithEdits({ + scenario: "composite", + subScenario: "converting to modules", + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.ts": "const x = 10;", + "/src/project/tsconfig.json": JSON.stringify({ + compilerOptions: { + module: "none", + composite: true, + }, }), - commandLineArgs: ["-p", "/src/project"], - edits: [ - { - subScenario: "convert to modules", - modifyFs: fs => replaceText(fs, "/src/project/tsconfig.json", "none", "es2015"), - } - ] - }); + }), + commandLineArgs: ["-p", "/src/project"], + edits: [ + { + subScenario: "convert to modules", + modifyFs: fs => ts.replaceText(fs, "/src/project/tsconfig.json", "none", "es2015"), + } + ] }); -} +}); diff --git a/src/testRunner/unittests/tsc/declarationEmit.ts b/src/testRunner/unittests/tsc/declarationEmit.ts index f345d4a3c8f61..06ef754312afe 100644 --- a/src/testRunner/unittests/tsc/declarationEmit.ts +++ b/src/testRunner/unittests/tsc/declarationEmit.ts @@ -1,66 +1,68 @@ -namespace ts { - describe("unittests:: tsc:: declarationEmit::", () => { - interface VerifyDeclarationEmitInput { - subScenario: string; - files: TestFSWithWatch.FileOrFolderOrSymLink[]; - rootProject: string; - changeCaseFileTestPath: (path: string) => boolean; - } +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; - function changeCaseFile(file: TestFSWithWatch.FileOrFolderOrSymLink, testPath: (path: string) => boolean, replacePath: (path: string) => string): TestFSWithWatch.FileOrFolderOrSymLink { - return !TestFSWithWatch.isSymLink(file) || !testPath(file.symLink) ? - testPath(file.path) ? { ...file, path: replacePath(file.path) } : file : - { path: testPath(file.path) ? replacePath(file.path) : file.path, symLink: replacePath(file.symLink) }; - } +describe("unittests:: tsc:: declarationEmit::", () => { + interface VerifyDeclarationEmitInput { + subScenario: string; + files: ts.TestFSWithWatch.FileOrFolderOrSymLink[]; + rootProject: string; + changeCaseFileTestPath: (path: string) => boolean; + } - function verifyDeclarationEmit({ subScenario, files, rootProject, changeCaseFileTestPath }: VerifyDeclarationEmitInput) { - describe(subScenario, () => { - tscWatch.verifyTscWatch({ - scenario: "declarationEmit", - subScenario, - sys: () => tscWatch.createWatchedSystem(files, { currentDirectory: tscWatch.projectRoot }), - commandLineArgs: ["-p", rootProject, "--explainFiles"], - changes: emptyArray - }); + function changeCaseFile(file: ts.TestFSWithWatch.FileOrFolderOrSymLink, testPath: (path: string) => boolean, replacePath: (path: string) => string): ts.TestFSWithWatch.FileOrFolderOrSymLink { + return !ts.TestFSWithWatch.isSymLink(file) || !testPath(file.symLink) ? + testPath(file.path) ? { ...file, path: replacePath(file.path) } : file : + { path: testPath(file.path) ? replacePath(file.path) : file.path, symLink: replacePath(file.symLink) }; + } + + function verifyDeclarationEmit({ subScenario, files, rootProject, changeCaseFileTestPath }: VerifyDeclarationEmitInput) { + describe(subScenario, () => { + ts.tscWatch.verifyTscWatch({ + scenario: "declarationEmit", + subScenario, + sys: () => ts.tscWatch.createWatchedSystem(files, { currentDirectory: ts.tscWatch.projectRoot }), + commandLineArgs: ["-p", rootProject, "--explainFiles"], + changes: ts.emptyArray }); + }); - const caseChangeScenario = `${subScenario} moduleCaseChange`; - describe(caseChangeScenario, () => { - tscWatch.verifyTscWatch({ - scenario: "declarationEmit", - subScenario: caseChangeScenario, - sys: () => tscWatch.createWatchedSystem( - files.map(f => changeCaseFile(f, changeCaseFileTestPath, str => str.replace("myproject", "myProject"))), - { currentDirectory: tscWatch.projectRoot } - ), - commandLineArgs: ["-p", rootProject, "--explainFiles"], - changes: emptyArray - }); + const caseChangeScenario = `${subScenario} moduleCaseChange`; + describe(caseChangeScenario, () => { + ts.tscWatch.verifyTscWatch({ + scenario: "declarationEmit", + subScenario: caseChangeScenario, + sys: () => ts.tscWatch.createWatchedSystem( + files.map(f => changeCaseFile(f, changeCaseFileTestPath, str => str.replace("myproject", "myProject"))), + { currentDirectory: ts.tscWatch.projectRoot } + ), + commandLineArgs: ["-p", rootProject, "--explainFiles"], + changes: ts.emptyArray }); - } + }); + } - describe("with symlinks in sibling folders and common package referenced from both folders", () => { - function pluginOneConfig() { - return JSON.stringify({ - compilerOptions: { - target: "es5", - declaration: true, - traceResolution: true - }, - }); - } - function pluginOneIndex() { - return `import pluginTwo from "plugin-two"; // include this to add reference to symlink`; - } - function pluginOneAction() { - return Utils.dedent` + describe("with symlinks in sibling folders and common package referenced from both folders", () => { + function pluginOneConfig() { + return JSON.stringify({ + compilerOptions: { + target: "es5", + declaration: true, + traceResolution: true + }, + }); + } + function pluginOneIndex() { + return `import pluginTwo from "plugin-two"; // include this to add reference to symlink`; + } + function pluginOneAction() { + return Utils.dedent` import { actionCreatorFactory } from "typescript-fsa"; // Include version of shared lib const action = actionCreatorFactory("somekey"); const featureOne = action<{ route: string }>("feature-one"); export const actions = { featureOne };`; - } - function pluginTwoDts() { - return Utils.dedent` + } + function pluginTwoDts() { + return Utils.dedent` declare const _default: { features: { featureOne: { @@ -82,15 +84,15 @@ namespace ts { }; }; export default _default;`; - } - function fsaPackageJson() { - return JSON.stringify({ - name: "typescript-fsa", - version: "3.0.0-beta-2" - }); - } - function fsaIndex() { - return Utils.dedent` + } + function fsaPackageJson() { + return JSON.stringify({ + name: "typescript-fsa", + version: "3.0.0-beta-2" + }); + } + function fsaIndex() { + return Utils.dedent` export interface Action { type: string; payload: Payload; @@ -104,69 +106,69 @@ namespace ts { } export declare function actionCreatorFactory(prefix?: string | null): ActionCreatorFactory; export default actionCreatorFactory;`; - } - - verifyDeclarationEmit({ - subScenario: "when same version is referenced through source and another symlinked package", - rootProject: "plugin-one", - files: [ - { path: `${tscWatch.projectRoot}/plugin-two/index.d.ts`, content: pluginTwoDts() }, - { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, - { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, - { path: `${tscWatch.projectRoot}/plugin-one/tsconfig.json`, content: pluginOneConfig() }, - { path: `${tscWatch.projectRoot}/plugin-one/index.ts`, content: pluginOneIndex() }, - { path: `${tscWatch.projectRoot}/plugin-one/action.ts`, content: pluginOneAction() }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/plugin-two`, symLink: `${tscWatch.projectRoot}/plugin-two` }, - tscWatch.libFile - ], - changeCaseFileTestPath: str => stringContains(str, "/plugin-two"), - }); + } - verifyDeclarationEmit({ - subScenario: "when same version is referenced through source and another symlinked package with indirect link", - rootProject: "plugin-one", - files: [ - { - path: `${tscWatch.projectRoot}/plugin-two/package.json`, - content: JSON.stringify({ - name: "plugin-two", - version: "0.1.3", - main: "dist/commonjs/index.js" - }) - }, - { path: `${tscWatch.projectRoot}/plugin-two/dist/commonjs/index.d.ts`, content: pluginTwoDts() }, - { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, - { path: `${tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, - { path: `${tscWatch.projectRoot}/plugin-one/tsconfig.json`, content: pluginOneConfig() }, - { - path: `${tscWatch.projectRoot}/plugin-one/index.ts`, - content: `${pluginOneIndex()} -${pluginOneAction()}` - }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, - { path: `/temp/yarn/data/link/plugin-two`, symLink: `${tscWatch.projectRoot}/plugin-two` }, - { path: `${tscWatch.projectRoot}/plugin-one/node_modules/plugin-two`, symLink: `/temp/yarn/data/link/plugin-two` }, - tscWatch.libFile - ], - changeCaseFileTestPath: str => stringContains(str, "/plugin-two"), - }); + verifyDeclarationEmit({ + subScenario: "when same version is referenced through source and another symlinked package", + rootProject: "plugin-one", + files: [ + { path: `${ts.tscWatch.projectRoot}/plugin-two/index.d.ts`, content: pluginTwoDts() }, + { path: `${ts.tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, + { path: `${ts.tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, + { path: `${ts.tscWatch.projectRoot}/plugin-one/tsconfig.json`, content: pluginOneConfig() }, + { path: `${ts.tscWatch.projectRoot}/plugin-one/index.ts`, content: pluginOneIndex() }, + { path: `${ts.tscWatch.projectRoot}/plugin-one/action.ts`, content: pluginOneAction() }, + { path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, + { path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, + { path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/plugin-two`, symLink: `${ts.tscWatch.projectRoot}/plugin-two` }, + ts.tscWatch.libFile + ], + changeCaseFileTestPath: str => ts.stringContains(str, "/plugin-two"), }); verifyDeclarationEmit({ - subScenario: "when pkg references sibling package through indirect symlink", - rootProject: "pkg3", + subScenario: "when same version is referenced through source and another symlinked package with indirect link", + rootProject: "plugin-one", files: [ { - path: `${tscWatch.projectRoot}/pkg1/dist/index.d.ts`, - content: Utils.dedent` - export * from './types';` + path: `${ts.tscWatch.projectRoot}/plugin-two/package.json`, + content: JSON.stringify({ + name: "plugin-two", + version: "0.1.3", + main: "dist/commonjs/index.js" + }) }, + { path: `${ts.tscWatch.projectRoot}/plugin-two/dist/commonjs/index.d.ts`, content: pluginTwoDts() }, + { path: `${ts.tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, + { path: `${ts.tscWatch.projectRoot}/plugin-two/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, + { path: `${ts.tscWatch.projectRoot}/plugin-one/tsconfig.json`, content: pluginOneConfig() }, { - path: `${tscWatch.projectRoot}/pkg1/dist/types.d.ts`, - content: Utils.dedent` + path: `${ts.tscWatch.projectRoot}/plugin-one/index.ts`, + content: `${pluginOneIndex()} +${pluginOneAction()}` + }, + { path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/package.json`, content: fsaPackageJson() }, + { path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/typescript-fsa/index.d.ts`, content: fsaIndex() }, + { path: `/temp/yarn/data/link/plugin-two`, symLink: `${ts.tscWatch.projectRoot}/plugin-two` }, + { path: `${ts.tscWatch.projectRoot}/plugin-one/node_modules/plugin-two`, symLink: `/temp/yarn/data/link/plugin-two` }, + ts.tscWatch.libFile + ], + changeCaseFileTestPath: str => ts.stringContains(str, "/plugin-two"), + }); + }); + + verifyDeclarationEmit({ + subScenario: "when pkg references sibling package through indirect symlink", + rootProject: "pkg3", + files: [ + { + path: `${ts.tscWatch.projectRoot}/pkg1/dist/index.d.ts`, + content: Utils.dedent` + export * from './types';` + }, + { + path: `${ts.tscWatch.projectRoot}/pkg1/dist/types.d.ts`, + content: Utils.dedent` export declare type A = { id: string; }; @@ -180,71 +182,70 @@ ${pluginOneAction()}` toString(): string; static create(key: string): MetadataAccessor; }` - }, - { - path: `${tscWatch.projectRoot}/pkg1/package.json`, - content: JSON.stringify({ - name: "@raymondfeng/pkg1", - version: "1.0.0", - main: "dist/index.js", - typings: "dist/index.d.ts" - }) - }, - { - path: `${tscWatch.projectRoot}/pkg2/dist/index.d.ts`, - content: Utils.dedent` + }, + { + path: `${ts.tscWatch.projectRoot}/pkg1/package.json`, + content: JSON.stringify({ + name: "@raymondfeng/pkg1", + version: "1.0.0", + main: "dist/index.js", + typings: "dist/index.d.ts" + }) + }, + { + path: `${ts.tscWatch.projectRoot}/pkg2/dist/index.d.ts`, + content: Utils.dedent` export * from './types';` - }, - { - path: `${tscWatch.projectRoot}/pkg2/dist/types.d.ts`, - content: Utils.dedent` + }, + { + path: `${ts.tscWatch.projectRoot}/pkg2/dist/types.d.ts`, + content: Utils.dedent` export {MetadataAccessor} from '@raymondfeng/pkg1';` - }, - { - path: `${tscWatch.projectRoot}/pkg2/package.json`, - content: JSON.stringify({ - name: "@raymondfeng/pkg2", - version: "1.0.0", - main: "dist/index.js", - typings: "dist/index.d.ts" - }) - }, - { - path: `${tscWatch.projectRoot}/pkg3/src/index.ts`, - content: Utils.dedent` + }, + { + path: `${ts.tscWatch.projectRoot}/pkg2/package.json`, + content: JSON.stringify({ + name: "@raymondfeng/pkg2", + version: "1.0.0", + main: "dist/index.js", + typings: "dist/index.d.ts" + }) + }, + { + path: `${ts.tscWatch.projectRoot}/pkg3/src/index.ts`, + content: Utils.dedent` export * from './keys';` - }, - { - path: `${tscWatch.projectRoot}/pkg3/src/keys.ts`, - content: Utils.dedent` + }, + { + path: `${ts.tscWatch.projectRoot}/pkg3/src/keys.ts`, + content: Utils.dedent` import {MetadataAccessor} from "@raymondfeng/pkg2"; export const ADMIN = MetadataAccessor.create('1');` - }, - { - path: `${tscWatch.projectRoot}/pkg3/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "dist", - rootDir: "src", - target: "es5", - module: "commonjs", - strict: true, - esModuleInterop: true, - declaration: true - } - }) - }, - { - path: `${tscWatch.projectRoot}/pkg2/node_modules/@raymondfeng/pkg1`, - symLink: `${tscWatch.projectRoot}/pkg1` - }, - { - path: `${tscWatch.projectRoot}/pkg3/node_modules/@raymondfeng/pkg2`, - symLink: `${tscWatch.projectRoot}/pkg2` - }, - tscWatch.libFile - ], - changeCaseFileTestPath: str => stringContains(str, "/pkg1"), - }); + }, + { + path: `${ts.tscWatch.projectRoot}/pkg3/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "dist", + rootDir: "src", + target: "es5", + module: "commonjs", + strict: true, + esModuleInterop: true, + declaration: true + } + }) + }, + { + path: `${ts.tscWatch.projectRoot}/pkg2/node_modules/@raymondfeng/pkg1`, + symLink: `${ts.tscWatch.projectRoot}/pkg1` + }, + { + path: `${ts.tscWatch.projectRoot}/pkg3/node_modules/@raymondfeng/pkg2`, + symLink: `${ts.tscWatch.projectRoot}/pkg2` + }, + ts.tscWatch.libFile + ], + changeCaseFileTestPath: str => ts.stringContains(str, "/pkg1"), }); -} +}); diff --git a/src/testRunner/unittests/tsc/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tsc/forceConsistentCasingInFileNames.ts index 8718af088df8a..8b1ddf0961bc5 100644 --- a/src/testRunner/unittests/tsc/forceConsistentCasingInFileNames.ts +++ b/src/testRunner/unittests/tsc/forceConsistentCasingInFileNames.ts @@ -1,18 +1,19 @@ -namespace ts { - describe("unittests:: tsc:: forceConsistentCasingInFileNames::", () => { - verifyTsc({ - scenario: "forceConsistentCasingInFileNames", - subScenario: "with relative and non relative file resolutions", - commandLineArgs: ["/src/project/src/struct.d.ts", "--forceConsistentCasingInFileNames", "--explainFiles"], - fs: () => loadProjectFromFiles({ - "/src/project/src/struct.d.ts": Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; + +describe("unittests:: tsc:: forceConsistentCasingInFileNames::", () => { + ts.verifyTsc({ + scenario: "forceConsistentCasingInFileNames", + subScenario: "with relative and non relative file resolutions", + commandLineArgs: ["/src/project/src/struct.d.ts", "--forceConsistentCasingInFileNames", "--explainFiles"], + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/struct.d.ts": Utils.dedent` import * as xs1 from "fp-ts/lib/Struct"; import * as xs2 from "fp-ts/lib/struct"; import * as xs3 from "./Struct"; import * as xs4 from "./struct"; `, - "/src/project/node_modules/fp-ts/lib/struct.d.ts": `export function foo(): void`, - }), - }); + "/src/project/node_modules/fp-ts/lib/struct.d.ts": `export function foo(): void`, + }), }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsc/helpers.ts b/src/testRunner/unittests/tsc/helpers.ts index 16e91db13425b..a8865794eb7dc 100644 --- a/src/testRunner/unittests/tsc/helpers.ts +++ b/src/testRunner/unittests/tsc/helpers.ts @@ -1,247 +1,250 @@ -namespace ts { - export type TscCompileSystem = fakes.System & { - writtenFiles: Set; - baseLine(): { file: string; text: string; }; - disableUseFileVersionAsSignature?: boolean; - storeFilesChangingSignatureDuringEmit?: boolean; - }; - - export function compilerOptionsToConfigJson(options: CompilerOptions) { - return optionMapToObject(serializeCompilerOptions(options)); - } - - export const noChangeRun: TestTscEdit = { - subScenario: "no-change-run", - modifyFs: noop - }; - export const noChangeWithExportsDiscrepancyRun: TestTscEdit = { - ...noChangeRun, - discrepancyExplanation: () => [ - "Incremental build did not emit and has .ts as signature so exports has all imported modules/referenced files", - "Clean build always uses d.ts for signature for testing thus does not contain non exported modules/referenced files that arent needed" - ] - }; - export const noChangeOnlyRuns = [noChangeRun]; - export const noChangeWithExportsDiscrepancyOnlyRuns = [noChangeWithExportsDiscrepancyRun]; - - export interface TestTscCompile extends TestTscCompileLikeBase { - baselineSourceMap?: boolean; - baselineReadFileCalls?: boolean; - baselinePrograms?: boolean; - baselineDependencies?: boolean; - } +import * as ts from "../../_namespaces/ts"; +import * as fakes from "../../_namespaces/fakes"; +import * as vfs from "../../_namespaces/vfs"; +import * as Harness from "../../_namespaces/Harness"; + +export type TscCompileSystem = fakes.System & { + writtenFiles: ts.Set; + baseLine(): { file: string; text: string; }; + disableUseFileVersionAsSignature?: boolean; + storeFilesChangingSignatureDuringEmit?: boolean; +}; + +export function compilerOptionsToConfigJson(options: ts.CompilerOptions) { + return ts.optionMapToObject(ts.serializeCompilerOptions(options)); +} - export type CommandLineProgram = [Program, BuilderProgram?]; - export interface CommandLineCallbacks { - cb: ExecuteCommandLineCallbacks; - getPrograms: () => readonly CommandLineProgram[]; - } +export const noChangeRun: ts.TestTscEdit = { + subScenario: "no-change-run", + modifyFs: ts.noop +}; +export const noChangeWithExportsDiscrepancyRun: ts.TestTscEdit = { + ...noChangeRun, + discrepancyExplanation: () => [ + "Incremental build did not emit and has .ts as signature so exports has all imported modules/referenced files", + "Clean build always uses d.ts for signature for testing thus does not contain non exported modules/referenced files that arent needed" + ] +}; +export const noChangeOnlyRuns = [noChangeRun]; +export const noChangeWithExportsDiscrepancyOnlyRuns = [noChangeWithExportsDiscrepancyRun]; + +export interface TestTscCompile extends TestTscCompileLikeBase { + baselineSourceMap?: boolean; + baselineReadFileCalls?: boolean; + baselinePrograms?: boolean; + baselineDependencies?: boolean; +} - function isAnyProgram(program: Program | BuilderProgram | ParsedCommandLine): program is Program | BuilderProgram { - return !!(program as Program | BuilderProgram).getCompilerOptions; - } - export function commandLineCallbacks( - sys: TscCompileSystem | tscWatch.WatchedSystem, - originalReadCall?: System["readFile"], - ): CommandLineCallbacks { - let programs: CommandLineProgram[] | undefined; +export type CommandLineProgram = [ts.Program, ts.BuilderProgram?]; +export interface CommandLineCallbacks { + cb: ts.ExecuteCommandLineCallbacks; + getPrograms: () => readonly CommandLineProgram[]; +} - return { - cb: program => { - if (isAnyProgram(program)) { - baselineBuildInfo(program.getCompilerOptions(), sys, originalReadCall); - (programs || (programs = [])).push(isBuilderProgram(program) ? - [program.getProgram(), program] : - [program] - ); - } - else { - baselineBuildInfo(program.options, sys, originalReadCall); - } - }, - getPrograms: () => { - const result = programs || emptyArray; - programs = undefined; - return result; +function isAnyProgram(program: ts.Program | ts.BuilderProgram | ts.ParsedCommandLine): program is ts.Program | ts.BuilderProgram { + return !!(program as ts.Program | ts.BuilderProgram).getCompilerOptions; +} +export function commandLineCallbacks( + sys: TscCompileSystem | ts.tscWatch.WatchedSystem, + originalReadCall?: ts.System["readFile"], +): CommandLineCallbacks { + let programs: CommandLineProgram[] | undefined; + + return { + cb: program => { + if (isAnyProgram(program)) { + ts.baselineBuildInfo(program.getCompilerOptions(), sys, originalReadCall); + (programs || (programs = [])).push(ts.isBuilderProgram(program) ? + [program.getProgram(), program] : + [program] + ); } - }; - } - export interface TestTscCompileLikeBase extends VerifyTscCompileLike { - diffWithInitial?: boolean; - modifyFs?: (fs: vfs.FileSystem) => void; - disableUseFileVersionAsSignature?: boolean; - environmentVariables?: Record; - } + else { + ts.baselineBuildInfo(program.options, sys, originalReadCall); + } + }, + getPrograms: () => { + const result = programs || ts.emptyArray; + programs = undefined; + return result; + } + }; +} +export interface TestTscCompileLikeBase extends VerifyTscCompileLike { + diffWithInitial?: boolean; + modifyFs?: (fs: vfs.FileSystem) => void; + disableUseFileVersionAsSignature?: boolean; + environmentVariables?: Record; +} - export interface TestTscCompileLike extends TestTscCompileLikeBase { - compile: (sys: TscCompileSystem) => void; - additionalBaseline?: (sys: TscCompileSystem) => void; - } - /** - * Initialize FS, run compile function and save baseline - */ - export function testTscCompileLike(input: TestTscCompileLike) { - const initialFs = input.fs(); - const inputFs = initialFs.shadow(); - const { - scenario, subScenario, diffWithInitial, - commandLineArgs, modifyFs, - environmentVariables, - compile: worker, additionalBaseline, - } = input; - if (modifyFs) modifyFs(inputFs); - inputFs.makeReadonly(); - const fs = inputFs.shadow(); - - // Create system - const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc", env: environmentVariables }) as TscCompileSystem; - if (input.disableUseFileVersionAsSignature) sys.disableUseFileVersionAsSignature = true; - sys.storeFilesChangingSignatureDuringEmit = true; - sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`); - sys.exit = exitCode => sys.exitCode = exitCode; - worker(sys); - sys.write(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}\n`); - additionalBaseline?.(sys); - fs.makeReadonly(); - sys.baseLine = () => { - const baseFsPatch = diffWithInitial ? - inputFs.diff(initialFs, { includeChangedFileWithSameContent: true }) : - inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }); - const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true }); - return { - file: `${isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${subScenario.split(" ").join("-")}.js`, - text: `Input:: +export interface TestTscCompileLike extends TestTscCompileLikeBase { + compile: (sys: TscCompileSystem) => void; + additionalBaseline?: (sys: TscCompileSystem) => void; +} +/** + * Initialize FS, run compile function and save baseline + */ +export function testTscCompileLike(input: TestTscCompileLike) { + const initialFs = input.fs(); + const inputFs = initialFs.shadow(); + const { + scenario, subScenario, diffWithInitial, + commandLineArgs, modifyFs, + environmentVariables, + compile: worker, additionalBaseline, + } = input; + if (modifyFs) modifyFs(inputFs); + inputFs.makeReadonly(); + const fs = inputFs.shadow(); + + // Create system + const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc", env: environmentVariables }) as TscCompileSystem; + if (input.disableUseFileVersionAsSignature) sys.disableUseFileVersionAsSignature = true; + sys.storeFilesChangingSignatureDuringEmit = true; + sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`); + sys.exit = exitCode => sys.exitCode = exitCode; + worker(sys); + sys.write(`exitCode:: ExitStatus.${ts.ExitStatus[sys.exitCode as ts.ExitStatus]}\n`); + additionalBaseline?.(sys); + fs.makeReadonly(); + sys.baseLine = () => { + const baseFsPatch = diffWithInitial ? + inputFs.diff(initialFs, { includeChangedFileWithSameContent: true }) : + inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }); + const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true }); + return { + file: `${ts.isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${subScenario.split(" ").join("-")}.js`, + text: `Input:: ${baseFsPatch ? vfs.formatPatch(baseFsPatch) : ""} Output:: ${sys.output.join("")} ${patch ? vfs.formatPatch(patch) : ""}` - }; }; - return sys; + }; + return sys; +} + +function makeSystemReadyForBaseline(sys: TscCompileSystem, versionToWrite?: string) { + if (versionToWrite) { + fakes.patchHostForBuildInfoWrite(sys, versionToWrite); } + else { + fakes.patchHostForBuildInfoReadWrite(sys); + } + const writtenFiles = sys.writtenFiles = new ts.Set(); + const originalWriteFile = sys.writeFile; + sys.writeFile = (fileName, content, writeByteOrderMark) => { + const path = ts.toPathWithSystem(sys, fileName); + // When buildinfo is same for two projects, + // it gives error and doesnt write buildinfo but because buildInfo is written for one project, + // readable baseline will be written two times for those two projects with same contents and is ok + ts.Debug.assert(!writtenFiles.has(path) || ts.endsWith(path, "baseline.txt")); + writtenFiles.add(path); + return originalWriteFile.call(sys, fileName, content, writeByteOrderMark); + }; +} - function makeSystemReadyForBaseline(sys: TscCompileSystem, versionToWrite?: string) { - if (versionToWrite) { - fakes.patchHostForBuildInfoWrite(sys, versionToWrite); - } - else { - fakes.patchHostForBuildInfoReadWrite(sys); - } - const writtenFiles = sys.writtenFiles = new Set(); - const originalWriteFile = sys.writeFile; - sys.writeFile = (fileName, content, writeByteOrderMark) => { - const path = toPathWithSystem(sys, fileName); - // When buildinfo is same for two projects, - // it gives error and doesnt write buildinfo but because buildInfo is written for one project, - // readable baseline will be written two times for those two projects with same contents and is ok - Debug.assert(!writtenFiles.has(path) || endsWith(path, "baseline.txt")); - writtenFiles.add(path); - return originalWriteFile.call(sys, fileName, content, writeByteOrderMark); +export function createSolutionBuilderHostForBaseline( + sys: TscCompileSystem | ts.tscWatch.WatchedSystem, + versionToWrite?: string, + originalRead?: (TscCompileSystem | ts.tscWatch.WatchedSystem)["readFile"] +) { + if (sys instanceof fakes.System) makeSystemReadyForBaseline(sys, versionToWrite); + const { cb } = commandLineCallbacks(sys, originalRead); + const host = ts.createSolutionBuilderHost(sys, + /*createProgram*/ undefined, + ts.createDiagnosticReporter(sys, /*pretty*/ true), + ts.createBuilderStatusReporter(sys, /*pretty*/ true), + ); + host.afterProgramEmitAndDiagnostics = cb; + host.afterEmitBundle = cb; + return host; +} + +/** + * Initialize Fs, execute command line and save baseline + */ +export function testTscCompile(input: TestTscCompile) { + let actualReadFileMap: ts.MapLike | undefined; + let getPrograms: CommandLineCallbacks["getPrograms"] | undefined; + return testTscCompileLike({ + ...input, + compile: commandLineCompile, + additionalBaseline + }); + + function commandLineCompile(sys: TscCompileSystem) { + makeSystemReadyForBaseline(sys); + actualReadFileMap = {}; + const originalReadFile = sys.readFile; + sys.readFile = path => { + // Dont record libs + if (path.startsWith("/src/")) { + actualReadFileMap![path] = (ts.getProperty(actualReadFileMap!, path) || 0) + 1; + } + return originalReadFile.call(sys, path); }; - } - export function createSolutionBuilderHostForBaseline( - sys: TscCompileSystem | tscWatch.WatchedSystem, - versionToWrite?: string, - originalRead?: (TscCompileSystem | tscWatch.WatchedSystem)["readFile"] - ) { - if (sys instanceof fakes.System) makeSystemReadyForBaseline(sys, versionToWrite); - const { cb } = commandLineCallbacks(sys, originalRead); - const host = createSolutionBuilderHost(sys, - /*createProgram*/ undefined, - createDiagnosticReporter(sys, /*pretty*/ true), - createBuilderStatusReporter(sys, /*pretty*/ true), + const result = commandLineCallbacks(sys, originalReadFile); + ts.executeCommandLine( + sys, + result.cb, + input.commandLineArgs, ); - host.afterProgramEmitAndDiagnostics = cb; - host.afterEmitBundle = cb; - return host; + sys.readFile = originalReadFile; + getPrograms = result.getPrograms; } - /** - * Initialize Fs, execute command line and save baseline - */ - export function testTscCompile(input: TestTscCompile) { - let actualReadFileMap: MapLike | undefined; - let getPrograms: CommandLineCallbacks["getPrograms"] | undefined; - return testTscCompileLike({ - ...input, - compile: commandLineCompile, - additionalBaseline - }); - - function commandLineCompile(sys: TscCompileSystem) { - makeSystemReadyForBaseline(sys); - actualReadFileMap = {}; - const originalReadFile = sys.readFile; - sys.readFile = path => { - // Dont record libs - if (path.startsWith("/src/")) { - actualReadFileMap![path] = (getProperty(actualReadFileMap!, path) || 0) + 1; - } - return originalReadFile.call(sys, path); - }; - - const result = commandLineCallbacks(sys, originalReadFile); - executeCommandLine( - sys, - result.cb, - input.commandLineArgs, - ); - sys.readFile = originalReadFile; - getPrograms = result.getPrograms; + function additionalBaseline(sys: TscCompileSystem) { + const { baselineSourceMap, baselineReadFileCalls, baselinePrograms, baselineDependencies } = input; + if (baselinePrograms) { + const baseline: string[] = []; + ts.tscWatch.baselinePrograms(baseline, getPrograms!, ts.emptyArray, baselineDependencies); + sys.write(baseline.join("\n")); } - - function additionalBaseline(sys: TscCompileSystem) { - const { baselineSourceMap, baselineReadFileCalls, baselinePrograms, baselineDependencies } = input; - if (baselinePrograms) { - const baseline: string[] = []; - tscWatch.baselinePrograms(baseline, getPrograms!, emptyArray, baselineDependencies); - sys.write(baseline.join("\n")); - } - if (baselineReadFileCalls) { - sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `); - } - if (baselineSourceMap) generateSourceMapBaselineFiles(sys); - actualReadFileMap = undefined; - getPrograms = undefined; + if (baselineReadFileCalls) { + sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `); } + if (baselineSourceMap) ts.generateSourceMapBaselineFiles(sys); + actualReadFileMap = undefined; + getPrograms = undefined; } +} - export function verifyTscBaseline(sys: () => { baseLine: TscCompileSystem["baseLine"]; }) { - it(`Generates files matching the baseline`, () => { - const { file, text } = sys().baseLine(); - Harness.Baseline.runBaseline(file, text); - }); - } - export interface VerifyTscCompileLike { - scenario: string; - subScenario: string; - commandLineArgs: readonly string[]; - fs: () => vfs.FileSystem; - } +export function verifyTscBaseline(sys: () => { baseLine: TscCompileSystem["baseLine"]; }) { + it(`Generates files matching the baseline`, () => { + const { file, text } = sys().baseLine(); + Harness.Baseline.runBaseline(file, text); + }); +} +export interface VerifyTscCompileLike { + scenario: string; + subScenario: string; + commandLineArgs: readonly string[]; + fs: () => vfs.FileSystem; +} - /** - * Verify by baselining after initializing FS and custom compile - */ - export function verifyTscCompileLike(verifier: (input: T) => { baseLine: TscCompileSystem["baseLine"]; }, input: T) { - describe(`tsc ${input.commandLineArgs.join(" ")} ${input.scenario}:: ${input.subScenario}`, () => { - describe(input.scenario, () => { - describe(input.subScenario, () => { - verifyTscBaseline(() => verifier({ - ...input, - fs: () => input.fs().makeReadonly() - })); - }); +/** + * Verify by baselining after initializing FS and custom compile + */ +export function verifyTscCompileLike(verifier: (input: T) => { baseLine: TscCompileSystem["baseLine"]; }, input: T) { + describe(`tsc ${input.commandLineArgs.join(" ")} ${input.scenario}:: ${input.subScenario}`, () => { + describe(input.scenario, () => { + describe(input.subScenario, () => { + verifyTscBaseline(() => verifier({ + ...input, + fs: () => input.fs().makeReadonly() + })); }); }); - } + }); +} - /** - * Verify by baselining after initializing FS and command line compile - */ - export function verifyTsc(input: TestTscCompile) { - verifyTscCompileLike(testTscCompile, input); - } +/** + * Verify by baselining after initializing FS and command line compile + */ + export function verifyTsc(input: TestTscCompile) { + verifyTscCompileLike(testTscCompile, input); } diff --git a/src/testRunner/unittests/tsc/incremental.ts b/src/testRunner/unittests/tsc/incremental.ts index b0f5b8d933da7..972b7efe84abb 100644 --- a/src/testRunner/unittests/tsc/incremental.ts +++ b/src/testRunner/unittests/tsc/incremental.ts @@ -1,11 +1,14 @@ -namespace ts { - describe("unittests:: tsc:: incremental::", () => { - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "when passing filename for buildinfo on commandline", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; +import * as vfs from "../../_namespaces/vfs"; + +describe("unittests:: tsc:: incremental::", () => { + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "when passing filename for buildinfo on commandline", + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "es5", @@ -15,52 +18,52 @@ namespace ts { "src/**/*.ts" ] }`, - }), - commandLineArgs: ["--incremental", "--p", "src/project", "--tsBuildInfoFile", "src/project/.tsbuildinfo", "--explainFiles"], - edits: noChangeOnlyRuns - }); + }), + commandLineArgs: ["--incremental", "--p", "src/project", "--tsBuildInfoFile", "src/project/.tsbuildinfo", "--explainFiles"], + edits: ts.noChangeOnlyRuns + }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "when passing rootDir from commandline", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "when passing rootDir from commandline", + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "incremental": true, "outDir": "dist", }, }`, - }), - commandLineArgs: ["--p", "src/project", "--rootDir", "src/project/src"], - edits: noChangeOnlyRuns - }); + }), + commandLineArgs: ["--p", "src/project", "--rootDir", "src/project/src"], + edits: ts.noChangeOnlyRuns + }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "with only dts files", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.d.ts": "export const x = 10;", - "/src/project/src/another.d.ts": "export const y = 10;", - "/src/project/tsconfig.json": "{}", - }), - commandLineArgs: ["--incremental", "--p", "src/project"], - edits: [ - noChangeRun, - { - subScenario: "incremental-declaration-doesnt-change", - modifyFs: fs => appendText(fs, "/src/project/src/main.d.ts", "export const xy = 100;") - } - ] - }); + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "with only dts files", + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.d.ts": "export const x = 10;", + "/src/project/src/another.d.ts": "export const y = 10;", + "/src/project/tsconfig.json": "{}", + }), + commandLineArgs: ["--incremental", "--p", "src/project"], + edits: [ + ts.noChangeRun, + { + subScenario: "incremental-declaration-doesnt-change", + modifyFs: fs => ts.appendText(fs, "/src/project/src/main.d.ts", "export const xy = 100;") + } + ] + }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "when passing rootDir is in the tsconfig", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": Utils.dedent` + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "when passing rootDir is in the tsconfig", + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "incremental": true, @@ -68,265 +71,265 @@ namespace ts { "rootDir": "./" }, }`, - }), - commandLineArgs: ["--p", "src/project"], - edits: noChangeOnlyRuns - }); + }), + commandLineArgs: ["--p", "src/project"], + edits: ts.noChangeOnlyRuns + }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "tsbuildinfo has error", - fs: () => loadProjectFromFiles({ - "/src/project/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": "{}", - "/src/project/tsconfig.tsbuildinfo": "Some random string", - }), - commandLineArgs: ["--p", "src/project", "-i"], - edits: [{ - subScenario: "tsbuildinfo written has error", - modifyFs: fs => prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"), - }] + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "tsbuildinfo has error", + fs: () => ts.loadProjectFromFiles({ + "/src/project/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": "{}", + "/src/project/tsconfig.tsbuildinfo": "Some random string", + }), + commandLineArgs: ["--p", "src/project", "-i"], + edits: [{ + subScenario: "tsbuildinfo written has error", + modifyFs: fs => ts.prependText(fs, "/src/project/tsconfig.tsbuildinfo", "Some random string"), + }] + }); + + describe("with noEmitOnError", () => { + let projFs: vfs.FileSystem; + before(() => { + projFs = ts.loadProjectFromDisk("tests/projects/noEmitOnError"); + }); + after(() => { + projFs = undefined!; }); - describe("with noEmitOnError", () => { - let projFs: vfs.FileSystem; - before(() => { - projFs = loadProjectFromDisk("tests/projects/noEmitOnError"); - }); - after(() => { - projFs = undefined!; + function verifyNoEmitOnError(subScenario: string, fixModifyFs: ts.TestTscEdit["modifyFs"], modifyFs?: ts.TestTscEdit["modifyFs"]) { + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario, + fs: () => projFs, + commandLineArgs: ["--incremental", "-p", "src"], + modifyFs, + edits: [ + ts.noChangeWithExportsDiscrepancyRun, + { + subScenario: "incremental-declaration-doesnt-change", + modifyFs: fixModifyFs + }, + ts.noChangeRun, + ], + baselinePrograms: true }); - - function verifyNoEmitOnError(subScenario: string, fixModifyFs: TestTscEdit["modifyFs"], modifyFs?: TestTscEdit["modifyFs"]) { - verifyTscWithEdits({ - scenario: "incremental", - subScenario, - fs: () => projFs, - commandLineArgs: ["--incremental", "-p", "src"], - modifyFs, - edits: [ - noChangeWithExportsDiscrepancyRun, - { - subScenario: "incremental-declaration-doesnt-change", - modifyFs: fixModifyFs - }, - noChangeRun, - ], - baselinePrograms: true - }); - } - verifyNoEmitOnError( - "with noEmitOnError syntax errors", - fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + } + verifyNoEmitOnError( + "with noEmitOnError syntax errors", + fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a = { lastName: 'sdsd' };`, "utf-8") - ); + ); - verifyNoEmitOnError( - "with noEmitOnError semantic errors", - fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + verifyNoEmitOnError( + "with noEmitOnError semantic errors", + fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a: string = "hello";`, "utf-8"), - fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; + fs => fs.writeFileSync("/src/src/main.ts", `import { A } from "../shared/types/db"; const a: string = 10;`, "utf-8"), - ); - }); + ); + }); - describe("when noEmit changes between compilation", () => { - verifyNoEmitChanges({ incremental: true }); - verifyNoEmitChanges({ incremental: true, declaration: true }); - verifyNoEmitChanges({ composite: true }); + describe("when noEmit changes between compilation", () => { + verifyNoEmitChanges({ incremental: true }); + verifyNoEmitChanges({ incremental: true, declaration: true }); + verifyNoEmitChanges({ composite: true }); - function verifyNoEmitChanges(compilerOptions: CompilerOptions) { - const discrepancyExplanation = () => [ - ...noChangeWithExportsDiscrepancyRun.discrepancyExplanation!(), - "Clean build will not have latestChangedDtsFile as there was no emit and emitSignatures as undefined for files", - "Incremental will store the past latestChangedDtsFile and emitSignatures", - ]; - const discrepancyIfNoDtsEmit = getEmitDeclarations(compilerOptions) ? - undefined : - noChangeWithExportsDiscrepancyRun.discrepancyExplanation; - const noChangeRunWithNoEmit: TestTscEdit = { - ...noChangeRun, - subScenario: "No Change run with noEmit", - commandLineArgs: ["--p", "src/project", "--noEmit"], - discrepancyExplanation: compilerOptions.composite ? - discrepancyExplanation : - !compilerOptions.declaration ? - noChangeWithExportsDiscrepancyRun.discrepancyExplanation : - undefined, - }; - const noChangeRunWithEmit: TestTscEdit = { - ...noChangeRun, - subScenario: "No Change run with emit", - commandLineArgs: ["--p", "src/project"], - discrepancyExplanation: discrepancyIfNoDtsEmit, - }; - let optionsString = ""; - for (const key in compilerOptions) { - if (hasProperty(compilerOptions, key)) { - optionsString += ` ${key}`; - } + function verifyNoEmitChanges(compilerOptions: ts.CompilerOptions) { + const discrepancyExplanation = () => [ + ...ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation!(), + "Clean build will not have latestChangedDtsFile as there was no emit and emitSignatures as undefined for files", + "Incremental will store the past latestChangedDtsFile and emitSignatures", + ]; + const discrepancyIfNoDtsEmit = ts.getEmitDeclarations(compilerOptions) ? + undefined : + ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation; + const noChangeRunWithNoEmit: ts.TestTscEdit = { + ...ts.noChangeRun, + subScenario: "No Change run with noEmit", + commandLineArgs: ["--p", "src/project", "--noEmit"], + discrepancyExplanation: compilerOptions.composite ? + discrepancyExplanation : + !compilerOptions.declaration ? + ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation : + undefined, + }; + const noChangeRunWithEmit: ts.TestTscEdit = { + ...ts.noChangeRun, + subScenario: "No Change run with emit", + commandLineArgs: ["--p", "src/project"], + discrepancyExplanation: discrepancyIfNoDtsEmit, + }; + let optionsString = ""; + for (const key in compilerOptions) { + if (ts.hasProperty(compilerOptions, key)) { + optionsString += ` ${key}`; } + } - verifyTscWithEdits({ - scenario: "incremental", - subScenario: `noEmit changes${optionsString}`, - commandLineArgs: ["--p", "src/project"], - fs, - edits: [ - noChangeRunWithNoEmit, - noChangeRunWithNoEmit, - { - subScenario: "Introduce error but still noEmit", - commandLineArgs: ["--p", "src/project", "--noEmit"], - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), - discrepancyExplanation: compilerOptions.composite ? - discrepancyExplanation : - compilerOptions.declaration ? - noChangeWithExportsDiscrepancyRun.discrepancyExplanation : - undefined, - }, - { - subScenario: "Fix error and emit", - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), - discrepancyExplanation: discrepancyIfNoDtsEmit, - }, - noChangeRunWithEmit, - noChangeRunWithNoEmit, - noChangeRunWithNoEmit, - noChangeRunWithEmit, - { - subScenario: "Introduce error and emit", - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), - discrepancyExplanation: discrepancyIfNoDtsEmit, - }, - noChangeRunWithEmit, - noChangeRunWithNoEmit, - noChangeRunWithNoEmit, - noChangeRunWithEmit, - { - subScenario: "Fix error and no emit", - commandLineArgs: ["--p", "src/project", "--noEmit"], - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), - discrepancyExplanation: compilerOptions.composite ? - discrepancyExplanation : - noChangeWithExportsDiscrepancyRun.discrepancyExplanation, - }, - noChangeRunWithEmit, - noChangeRunWithNoEmit, - noChangeRunWithNoEmit, - noChangeRunWithEmit, - ], - }); + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: `noEmit changes${optionsString}`, + commandLineArgs: ["--p", "src/project"], + fs, + edits: [ + noChangeRunWithNoEmit, + noChangeRunWithNoEmit, + { + subScenario: "Introduce error but still noEmit", + commandLineArgs: ["--p", "src/project", "--noEmit"], + modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), + discrepancyExplanation: compilerOptions.composite ? + discrepancyExplanation : + compilerOptions.declaration ? + ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation : + undefined, + }, + { + subScenario: "Fix error and emit", + modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), + discrepancyExplanation: discrepancyIfNoDtsEmit, + }, + noChangeRunWithEmit, + noChangeRunWithNoEmit, + noChangeRunWithNoEmit, + noChangeRunWithEmit, + { + subScenario: "Introduce error and emit", + modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), + discrepancyExplanation: discrepancyIfNoDtsEmit, + }, + noChangeRunWithEmit, + noChangeRunWithNoEmit, + noChangeRunWithNoEmit, + noChangeRunWithEmit, + { + subScenario: "Fix error and no emit", + commandLineArgs: ["--p", "src/project", "--noEmit"], + modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), + discrepancyExplanation: compilerOptions.composite ? + discrepancyExplanation : + ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation, + }, + noChangeRunWithEmit, + noChangeRunWithNoEmit, + noChangeRunWithNoEmit, + noChangeRunWithEmit, + ], + }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: `noEmit changes with initial noEmit${optionsString}`, - commandLineArgs: ["--p", "src/project", "--noEmit"], - fs, - edits: [ - noChangeRunWithEmit, - { - subScenario: "Introduce error with emit", - commandLineArgs: ["--p", "src/project"], - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), - }, - { - subScenario: "Fix error and no emit", - modifyFs: fs => replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), - discrepancyExplanation: compilerOptions.composite ? - discrepancyExplanation : - noChangeWithExportsDiscrepancyRun.discrepancyExplanation, - }, - noChangeRunWithEmit, - ], - }); + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: `noEmit changes with initial noEmit${optionsString}`, + commandLineArgs: ["--p", "src/project", "--noEmit"], + fs, + edits: [ + noChangeRunWithEmit, + { + subScenario: "Introduce error with emit", + commandLineArgs: ["--p", "src/project"], + modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop", "prop1"), + }, + { + subScenario: "Fix error and no emit", + modifyFs: fs => ts.replaceText(fs, "/src/project/src/class.ts", "prop1", "prop"), + discrepancyExplanation: compilerOptions.composite ? + discrepancyExplanation : + ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation, + }, + noChangeRunWithEmit, + ], + }); - function fs() { - return loadProjectFromFiles({ - "/src/project/src/class.ts": Utils.dedent` + function fs() { + return ts.loadProjectFromFiles({ + "/src/project/src/class.ts": Utils.dedent` export class classC { prop = 1; }`, - "/src/project/src/indirectClass.ts": Utils.dedent` + "/src/project/src/indirectClass.ts": Utils.dedent` import { classC } from './class'; export class indirectClass { classC = new classC(); }`, - "/src/project/src/directUse.ts": Utils.dedent` + "/src/project/src/directUse.ts": Utils.dedent` import { indirectClass } from './indirectClass'; new indirectClass().classC.prop;`, - "/src/project/src/indirectUse.ts": Utils.dedent` + "/src/project/src/indirectUse.ts": Utils.dedent` import { indirectClass } from './indirectClass'; new indirectClass().classC.prop;`, - "/src/project/src/noChangeFile.ts": Utils.dedent` + "/src/project/src/noChangeFile.ts": Utils.dedent` export function writeLog(s: string) { }`, - "/src/project/src/noChangeFileWithEmitSpecificError.ts": Utils.dedent` + "/src/project/src/noChangeFileWithEmitSpecificError.ts": Utils.dedent` function someFunc(arguments: boolean, ...rest: any[]) { }`, - "/src/project/tsconfig.json": JSON.stringify({ compilerOptions }), - }); - } + "/src/project/tsconfig.json": JSON.stringify({ compilerOptions }), + }); } - }); + } + }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: `when global file is added, the signatures are updated`, - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": Utils.dedent` + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: `when global file is added, the signatures are updated`, + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.ts": Utils.dedent` /// /// function main() { } `, - "/src/project/src/anotherFileWithSameReferenes.ts": Utils.dedent` + "/src/project/src/anotherFileWithSameReferenes.ts": Utils.dedent` /// /// function anotherFileWithSameReferenes() { } `, - "/src/project/src/filePresent.ts": `function something() { return 10; }`, - "/src/project/tsconfig.json": JSON.stringify({ - compilerOptions: { composite: true, }, - include: ["src/**/*.ts"] - }), + "/src/project/src/filePresent.ts": `function something() { return 10; }`, + "/src/project/tsconfig.json": JSON.stringify({ + compilerOptions: { composite: true, }, + include: ["src/**/*.ts"] }), - commandLineArgs: ["--p", "src/project"], - edits: [ - noChangeRun, - { - subScenario: "Modify main file", - modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`), - }, - { - subScenario: "Modify main file again", - modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`), - }, - { - subScenario: "Add new file and update main file", - modifyFs: fs => { - fs.writeFileSync(`/src/project/src/newFile.ts`, "function foo() { return 20; }"); - prependText(fs, `/src/project/src/main.ts`, `/// + }), + commandLineArgs: ["--p", "src/project"], + edits: [ + ts.noChangeRun, + { + subScenario: "Modify main file", + modifyFs: fs => ts.appendText(fs, `/src/project/src/main.ts`, `something();`), + }, + { + subScenario: "Modify main file again", + modifyFs: fs => ts.appendText(fs, `/src/project/src/main.ts`, `something();`), + }, + { + subScenario: "Add new file and update main file", + modifyFs: fs => { + fs.writeFileSync(`/src/project/src/newFile.ts`, "function foo() { return 20; }"); + ts.prependText(fs, `/src/project/src/main.ts`, `/// `); - appendText(fs, `/src/project/src/main.ts`, `foo();`); - }, + ts.appendText(fs, `/src/project/src/main.ts`, `foo();`); }, - { - subScenario: "Write file that could not be resolved", - modifyFs: fs => fs.writeFileSync(`/src/project/src/fileNotFound.ts`, "function something2() { return 20; }"), - }, - { - subScenario: "Modify main file", - modifyFs: fs => appendText(fs, `/src/project/src/main.ts`, `something();`), - }, - ], - baselinePrograms: true, - }); + }, + { + subScenario: "Write file that could not be resolved", + modifyFs: fs => fs.writeFileSync(`/src/project/src/fileNotFound.ts`, "function something2() { return 20; }"), + }, + { + subScenario: "Modify main file", + modifyFs: fs => ts.appendText(fs, `/src/project/src/main.ts`, `something();`), + }, + ], + baselinePrograms: true, + }); - describe("when synthesized imports are added to files", () => { - function getJsxLibraryContent() { - return ` + describe("when synthesized imports are added to files", () => { + function getJsxLibraryContent() { + return ` export {}; declare global { namespace JSX { @@ -338,124 +341,124 @@ declare global { } } }`; - } - - verifyTsc({ - scenario: "react-jsx-emit-mode", - subScenario: "with no backing types found doesn't crash", - fs: () => loadProjectFromFiles({ - "/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result - "/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition - "/src/project/src/index.tsx": `export const App = () =>
;`, - "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" } }) - }), - commandLineArgs: ["--p", "src/project"] - }); + } - verifyTsc({ - scenario: "react-jsx-emit-mode", - subScenario: "with no backing types found doesn't crash under --strict", - fs: () => loadProjectFromFiles({ - "/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result - "/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition - "/src/project/src/index.tsx": `export const App = () =>
;`, - "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" } }) - }), - commandLineArgs: ["--p", "src/project", "--strict"] - }); + ts.verifyTsc({ + scenario: "react-jsx-emit-mode", + subScenario: "with no backing types found doesn't crash", + fs: () => ts.loadProjectFromFiles({ + "/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result + "/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition + "/src/project/src/index.tsx": `export const App = () =>
;`, + "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" } }) + }), + commandLineArgs: ["--p", "src/project"] }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "when new file is added to the referenced project", - commandLineArgs: ["-i", "-p", `src/projects/project2`], - fs: () => loadProjectFromFiles({ - "/src/projects/project1/tsconfig.json": JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - exclude: ["temp"] - }), - "/src/projects/project1/class1.ts": `class class1 {}`, - "/src/projects/project1/class1.d.ts": `declare class class1 {}`, - "/src/projects/project2/tsconfig.json": JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - references: [ - { path: "../project1" } - ] - }), - "/src/projects/project2/class2.ts": `class class2 {}`, + ts.verifyTsc({ + scenario: "react-jsx-emit-mode", + subScenario: "with no backing types found doesn't crash under --strict", + fs: () => ts.loadProjectFromFiles({ + "/src/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result + "/src/project/node_modules/@types/react/index.d.ts": getJsxLibraryContent(), // doesn't contain a jsx-runtime definition + "/src/project/src/index.tsx": `export const App = () =>
;`, + "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" } }) }), - edits: [ - { - subScenario: "Add class3 to project1 and build it", - modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.ts", `class class3 {}`, "utf-8"), - discrepancyExplanation: () => [ - "Ts buildinfo will not be updated in incremental build so it will have semantic diagnostics cached from previous build", - "But in clean build because of global diagnostics, semantic diagnostics are not queried so not cached in tsbuildinfo", - ], - }, - { - subScenario: "Add output of class3", - modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.d.ts", `declare class class3 {}`, "utf-8"), - }, - { - subScenario: "Add excluded file to project1", - modifyFs: fs => { - fs.mkdirSync("/src/projects/project1/temp"); - fs.writeFileSync("/src/projects/project1/temp/file.d.ts", `declare class file {}`, "utf-8"); - }, + commandLineArgs: ["--p", "src/project", "--strict"] + }); + }); + + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "when new file is added to the referenced project", + commandLineArgs: ["-i", "-p", `src/projects/project2`], + fs: () => ts.loadProjectFromFiles({ + "/src/projects/project1/tsconfig.json": JSON.stringify({ + compilerOptions: { + module: "none", + composite: true }, - { - subScenario: "Delete output for class3", - modifyFs: fs => fs.unlinkSync("/src/projects/project1/class3.d.ts"), - discrepancyExplanation: () => [ - "Ts buildinfo will be updated but will retain lib file errors from previous build and not others because they are emitted because of change which results in clearing their semantic diagnostics cache", - "But in clean build because of global diagnostics, semantic diagnostics are not queried so not cached in tsbuildinfo", - ], + exclude: ["temp"] + }), + "/src/projects/project1/class1.ts": `class class1 {}`, + "/src/projects/project1/class1.d.ts": `declare class class1 {}`, + "/src/projects/project2/tsconfig.json": JSON.stringify({ + compilerOptions: { + module: "none", + composite: true }, - { - subScenario: "Create output for class3", - modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.d.ts", `declare class class3 {}`, "utf-8"), + references: [ + { path: "../project1" } + ] + }), + "/src/projects/project2/class2.ts": `class class2 {}`, + }), + edits: [ + { + subScenario: "Add class3 to project1 and build it", + modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.ts", `class class3 {}`, "utf-8"), + discrepancyExplanation: () => [ + "Ts buildinfo will not be updated in incremental build so it will have semantic diagnostics cached from previous build", + "But in clean build because of global diagnostics, semantic diagnostics are not queried so not cached in tsbuildinfo", + ], + }, + { + subScenario: "Add output of class3", + modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.d.ts", `declare class class3 {}`, "utf-8"), + }, + { + subScenario: "Add excluded file to project1", + modifyFs: fs => { + fs.mkdirSync("/src/projects/project1/temp"); + fs.writeFileSync("/src/projects/project1/temp/file.d.ts", `declare class file {}`, "utf-8"); }, - ] - }); + }, + { + subScenario: "Delete output for class3", + modifyFs: fs => fs.unlinkSync("/src/projects/project1/class3.d.ts"), + discrepancyExplanation: () => [ + "Ts buildinfo will be updated but will retain lib file errors from previous build and not others because they are emitted because of change which results in clearing their semantic diagnostics cache", + "But in clean build because of global diagnostics, semantic diagnostics are not queried so not cached in tsbuildinfo", + ], + }, + { + subScenario: "Create output for class3", + modifyFs: fs => fs.writeFileSync("/src/projects/project1/class3.d.ts", `declare class class3 {}`, "utf-8"), + }, + ] + }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "when project has strict true", - commandLineArgs: ["-noEmit", "-p", `src/project`], - fs: () => loadProjectFromFiles({ - "/src/project/tsconfig.json": JSON.stringify({ - compilerOptions: { - incremental: true, - strict: true, - }, - }), - "/src/project/class1.ts": `export class class1 {}`, + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "when project has strict true", + commandLineArgs: ["-noEmit", "-p", `src/project`], + fs: () => ts.loadProjectFromFiles({ + "/src/project/tsconfig.json": JSON.stringify({ + compilerOptions: { + incremental: true, + strict: true, + }, }), - edits: noChangeOnlyRuns, - baselinePrograms: true - }); + "/src/project/class1.ts": `export class class1 {}`, + }), + edits: ts.noChangeOnlyRuns, + baselinePrograms: true + }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "serializing error chains", - commandLineArgs: ["-p", `src/project`], - fs: () => loadProjectFromFiles({ - "/src/project/tsconfig.json": JSON.stringify({ - compilerOptions: { - incremental: true, - strict: true, - jsx: "react", - module: "esnext", - }, - }), - "/src/project/index.tsx": Utils.dedent` + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "serializing error chains", + commandLineArgs: ["-p", `src/project`], + fs: () => ts.loadProjectFromFiles({ + "/src/project/tsconfig.json": JSON.stringify({ + compilerOptions: { + incremental: true, + strict: true, + jsx: "react", + module: "esnext", + }, + }), + "/src/project/index.tsx": Utils.dedent` declare namespace JSX { interface ElementChildrenAttribute { children: {}; } interface IntrinsicElements { div: {} } @@ -469,15 +472,15 @@ declare global {
)` - }, `\ninterface ReadonlyArray { readonly length: number }`), - edits: noChangeOnlyRuns, - }); + }, `\ninterface ReadonlyArray { readonly length: number }`), + edits: ts.noChangeOnlyRuns, + }); - verifyTsc({ - scenario: "incremental", - subScenario: "ts file with no-default-lib that augments the global scope", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": Utils.dedent` + ts.verifyTsc({ + scenario: "incremental", + subScenario: "ts file with no-default-lib that augments the global scope", + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.ts": Utils.dedent` /// /// @@ -488,7 +491,7 @@ declare global { export {}; `, - "/src/project/tsconfig.json": Utils.dedent` + "/src/project/tsconfig.json": Utils.dedent` { "compilerOptions": { "target": "ESNext", @@ -497,61 +500,61 @@ declare global { "outDir": "dist", }, }`, - }), - commandLineArgs: ["--p", "src/project", "--rootDir", "src/project/src"], - modifyFs: (fs) => { - fs.writeFileSync("/lib/lib.esnext.d.ts", libContent); - } - }); + }), + commandLineArgs: ["--p", "src/project", "--rootDir", "src/project/src"], + modifyFs: (fs) => { + fs.writeFileSync("/lib/lib.esnext.d.ts", ts.libContent); + } + }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "change to type that gets used as global through export in another file", - commandLineArgs: ["-p", `src/project`], - fs: () => loadProjectFromFiles({ - "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { composite: true }, }), - "/src/project/class1.ts": `const a: MagicNumber = 1; + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "change to type that gets used as global through export in another file", + commandLineArgs: ["-p", `src/project`], + fs: () => ts.loadProjectFromFiles({ + "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { composite: true }, }), + "/src/project/class1.ts": `const a: MagicNumber = 1; console.log(a);`, - "/src/project/constants.ts": "export default 1;", - "/src/project/types.d.ts": `type MagicNumber = typeof import('./constants').default`, - }), - edits: [{ - subScenario: "Modify imports used in global file", - modifyFs: fs => fs.writeFileSync("/src/project/constants.ts", "export default 2;"), - }], - }); + "/src/project/constants.ts": "export default 1;", + "/src/project/types.d.ts": `type MagicNumber = typeof import('./constants').default`, + }), + edits: [{ + subScenario: "Modify imports used in global file", + modifyFs: fs => fs.writeFileSync("/src/project/constants.ts", "export default 2;"), + }], + }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "change to type that gets used as global through export in another file through indirect import", - commandLineArgs: ["-p", `src/project`], - fs: () => loadProjectFromFiles({ - "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { composite: true }, }), - "/src/project/class1.ts": `const a: MagicNumber = 1; + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "change to type that gets used as global through export in another file through indirect import", + commandLineArgs: ["-p", `src/project`], + fs: () => ts.loadProjectFromFiles({ + "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { composite: true }, }), + "/src/project/class1.ts": `const a: MagicNumber = 1; console.log(a);`, - "/src/project/constants.ts": "export default 1;", - "/src/project/reexport.ts": `export { default as ConstantNumber } from "./constants"`, - "/src/project/types.d.ts": `type MagicNumber = typeof import('./reexport').ConstantNumber`, - }), - edits: [{ - subScenario: "Modify imports used in global file", - modifyFs: fs => fs.writeFileSync("/src/project/constants.ts", "export default 2;"), - }], - }); + "/src/project/constants.ts": "export default 1;", + "/src/project/reexport.ts": `export { default as ConstantNumber } from "./constants"`, + "/src/project/types.d.ts": `type MagicNumber = typeof import('./reexport').ConstantNumber`, + }), + edits: [{ + subScenario: "Modify imports used in global file", + modifyFs: fs => fs.writeFileSync("/src/project/constants.ts", "export default 2;"), + }], + }); - function verifyModifierChange(declaration: boolean) { - verifyTscWithEdits({ - scenario: "incremental", - subScenario: `change to modifier of class expression field${declaration ? " with declaration emit enabled" : ""}`, - commandLineArgs: ["-p", "src/project", "--incremental"], - fs: () => loadProjectFromFiles({ - "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { declaration } }), - "/src/project/main.ts": Utils.dedent` + function verifyModifierChange(declaration: boolean) { + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: `change to modifier of class expression field${declaration ? " with declaration emit enabled" : ""}`, + commandLineArgs: ["-p", "src/project", "--incremental"], + fs: () => ts.loadProjectFromFiles({ + "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: { declaration } }), + "/src/project/main.ts": Utils.dedent` import MessageablePerson from './MessageablePerson.js'; function logMessage( person: MessageablePerson ) { console.log( person.message ); }`, - "/src/project/MessageablePerson.ts": Utils.dedent` + "/src/project/MessageablePerson.ts": Utils.dedent` const Messageable = () => { return class MessageableClass { public message = 'hello'; @@ -560,260 +563,259 @@ console.log(a);`, const wrapper = () => Messageable(); type MessageablePerson = InstanceType>; export default MessageablePerson;`, - }), - modifyFs: fs => appendText(fs, "/lib/lib.d.ts", Utils.dedent` + }), + modifyFs: fs => ts.appendText(fs, "/lib/lib.d.ts", Utils.dedent` type ReturnType any> = T extends (...args: any) => infer R ? R : any; type InstanceType any> = T extends abstract new (...args: any) => infer R ? R : any;` - ), - edits: [ - { - subScenario: "modify public to protected", - modifyFs: fs => replaceText(fs, "/src/project/MessageablePerson.ts", "public", "protected"), - }, - { - subScenario: "modify protected to public", - modifyFs: fs => replaceText(fs, "/src/project/MessageablePerson.ts", "protected", "public"), - }, - ], - }); - } - verifyModifierChange(/*declaration*/ false); - verifyModifierChange(/*declaration*/ true); - - verifyTscWithEdits({ - scenario: "incremental", - subScenario: `when declarationMap changes`, - fs: () => loadProjectFromFiles({ - "/src/project/tsconfig.json": JSON.stringify({ - compilerOptions: { - noEmitOnError: true, - declaration: true, - composite: true, - } - }), - "/src/project/a.ts": "const x = 10;", - "/src/project/b.ts": "const y = 10;" - }), - commandLineArgs: ["--p", "/src/project"], + ), edits: [ { - subScenario: "error and enable declarationMap", - modifyFs: fs => replaceText(fs, "/src/project/a.ts", "x", "x: 20"), - commandLineArgs: ["--p", "/src/project", "--declarationMap"], - discrepancyExplanation: () => [ - `Clean build does not emit any file so will have emitSignatures with all files since they are not emitted`, - `Incremental build has emitSignatures from before, so it will have a.ts with signature since file.version isnt same`, - `Incremental build will also have emitSignatureDtsMapDiffers for both files since the emitSignatures were without declarationMap but currentOptions have declrationMap`, - ] + subScenario: "modify public to protected", + modifyFs: fs => ts.replaceText(fs, "/src/project/MessageablePerson.ts", "public", "protected"), }, { - subScenario: "fix error declarationMap", - modifyFs: fs => replaceText(fs, "/src/project/a.ts", "x: 20", "x"), - commandLineArgs: ["--p", "/src/project", "--declarationMap"], + subScenario: "modify protected to public", + modifyFs: fs => ts.replaceText(fs, "/src/project/MessageablePerson.ts", "protected", "public"), }, - ] + ], }); + } + verifyModifierChange(/*declaration*/ false); + verifyModifierChange(/*declaration*/ true); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: `when declarationMap changes with outFile`, - fs: () => loadProjectFromFiles({ - "/src/project/tsconfig.json": JSON.stringify({ - compilerOptions: { - noEmitOnError: true, - declaration: true, - composite: true, - outFile: "../outFile.js", - } - }), - "/src/project/a.ts": "const x = 10;", - "/src/project/b.ts": "const y = 10;" + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: `when declarationMap changes`, + fs: () => ts.loadProjectFromFiles({ + "/src/project/tsconfig.json": JSON.stringify({ + compilerOptions: { + noEmitOnError: true, + declaration: true, + composite: true, + } }), - commandLineArgs: ["--p", "/src/project"], - edits: [ - { - subScenario: "error and enable declarationMap", - modifyFs: fs => replaceText(fs, "/src/project/a.ts", "x", "x: 20"), - commandLineArgs: ["--p", "/src/project", "--declarationMap"], - }, - { - subScenario: "fix error declarationMap", - modifyFs: fs => replaceText(fs, "/src/project/a.ts", "x: 20", "x"), - commandLineArgs: ["--p", "/src/project", "--declarationMap"], - }, - ] - }); + "/src/project/a.ts": "const x = 10;", + "/src/project/b.ts": "const y = 10;" + }), + commandLineArgs: ["--p", "/src/project"], + edits: [ + { + subScenario: "error and enable declarationMap", + modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "x", "x: 20"), + commandLineArgs: ["--p", "/src/project", "--declarationMap"], + discrepancyExplanation: () => [ + `Clean build does not emit any file so will have emitSignatures with all files since they are not emitted`, + `Incremental build has emitSignatures from before, so it will have a.ts with signature since file.version isnt same`, + `Incremental build will also have emitSignatureDtsMapDiffers for both files since the emitSignatures were without declarationMap but currentOptions have declrationMap`, + ] + }, + { + subScenario: "fix error declarationMap", + modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "x: 20", "x"), + commandLineArgs: ["--p", "/src/project", "--declarationMap"], + }, + ] + }); - describe("different options::", () => { - function withOptionChange(subScenario: string, ...options: readonly string[]): TestTscEdit { - return { - subScenario, - modifyFs: noop, - commandLineArgs: ["--p", "/src/project", ...options], - }; - } - function noChangeWithSubscenario(subScenario: string): TestTscEdit { - return { ...noChangeRun, subScenario }; - } - function withOptionChangeAndDiscrepancyExplanation(subScenario: string, option: string): TestTscEdit { - return { - ...withOptionChange(subScenario, option), - discrepancyExplanation: () => [ - `Clean build tsbuildinfo will have compilerOptions with composite and ${option.replace(/\-/g, "")}`, - `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option composite only`, - ] - }; - } - function withEmitDeclarationOnlyChangeAndDiscrepancyExplanation(subScenario: string): TestTscEdit { - const edit = withOptionChangeAndDiscrepancyExplanation(subScenario, "--emitDeclarationOnly"); - const discrepancyExplanation = edit.discrepancyExplanation!; - edit.discrepancyExplanation = () => [ - ...discrepancyExplanation(), - `Clean build info does not have js section because its fresh build`, - `Incremental build info has js section from old build` - ]; - return edit; - } - function withOptionChangeAndExportExplanation(subScenario: string, ...options: readonly string[]): TestTscEdit { - return { - ...withOptionChange(subScenario, ...options), - discrepancyExplanation: noChangeWithExportsDiscrepancyRun.discrepancyExplanation, - }; - } - function nochangeWithIncrementalDeclarationFromBeforeExplaination(): TestTscEdit { - return { - ...noChangeRun, - discrepancyExplanation: () => [ - `Clean build tsbuildinfo will have compilerOptions {}`, - `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option declaration and declarationMap`, - ], - }; - } - function nochangeWithIncrementalOutDeclarationFromBeforeExplaination(): TestTscEdit { - const edit = nochangeWithIncrementalDeclarationFromBeforeExplaination(); - const discrepancyExplanation = edit.discrepancyExplanation!; - edit.discrepancyExplanation = () => [ - ...discrepancyExplanation(), - `Clean build does not have dts bundle section`, - `Incremental build contains the dts build section from before`, - ]; - return edit; - } - function localChange(): TestTscEdit { - return { - subScenario: "local change", - modifyFs: fs => replaceText(fs, "/src/project/a.ts", "Local = 1", "Local = 10"), - }; - } - function fs(options: CompilerOptions) { - return loadProjectFromFiles({ - "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: compilerOptionsToConfigJson(options) }), - "/src/project/a.ts": `export const a = 10;const aLocal = 10;`, - "/src/project/b.ts": `export const b = 10;const bLocal = 10;`, - "/src/project/c.ts": `import { a } from "./a";export const c = a;`, - "/src/project/d.ts": `import { b } from "./b";export const d = b;`, - }); - } - function enableDeclarationMap(): TestTscEdit { - return { - subScenario: "declarationMap enabling", - modifyFs: fs => { - const config = JSON.parse(fs.readFileSync("/src/project/tsconfig.json", "utf-8")); - config.compilerOptions.declarationMap = true; - fs.writeFileSync("/src/project/tsconfig.json", JSON.stringify(config)); - }, - }; - } - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "different options", - fs: () => fs({ composite: true }), - commandLineArgs: ["--p", "/src/project"], - edits: [ - withOptionChange("with sourceMap", "--sourceMap"), - noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), - withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), - noChangeRun, - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"), - withOptionChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything", "--emitDeclarationOnly"), - noChangeRun, - localChange(), - withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), - withOptionChange("with inlineSourceMap", "--inlineSourceMap"), - withOptionChange("with sourceMap", "--sourceMap"), - enableDeclarationMap(), - withOptionChange("with sourceMap should not emit d.ts", "--sourceMap"), - ], - baselinePrograms: true, - }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "different options with outFile", - fs: () => fs({ composite: true, outFile: "../outFile.js", module: ModuleKind.AMD }), - commandLineArgs: ["--p", "/src/project"], - edits: [ - withOptionChange("with sourceMap", "--sourceMap"), - noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), - withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), - noChangeRun, - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"), - withEmitDeclarationOnlyChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything"), - noChangeRun, - localChange(), - withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), - withOptionChange("with inlineSourceMap", "--inlineSourceMap"), - withOptionChange("with sourceMap", "--sourceMap"), - enableDeclarationMap(), - withOptionChange("with sourceMap should not emit d.ts", "--sourceMap"), - ], - baselinePrograms: true, - }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "different options with incremental", - fs: () => fs({ incremental: true }), - commandLineArgs: ["--p", "/src/project"], - edits: [ - withOptionChangeAndExportExplanation("with sourceMap", "--sourceMap"), - withOptionChangeAndExportExplanation("should re-emit only js so they dont contain sourcemap"), - withOptionChange("with declaration, emit Dts and should not emit js", "--declaration"), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - nochangeWithIncrementalDeclarationFromBeforeExplaination(), - localChange(), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - nochangeWithIncrementalDeclarationFromBeforeExplaination(), - withOptionChange("with inlineSourceMap", "--inlineSourceMap"), - withOptionChange("with sourceMap", "--sourceMap"), - noChangeWithSubscenario("emit js files"), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - withOptionChange("with declaration and declarationMap, should not re-emit", "--declaration", "--declarationMap"), - ], - baselinePrograms: true, - }); - verifyTscWithEdits({ - scenario: "incremental", - subScenario: "different options with incremental with outFile", - fs: () => fs({ incremental: true, outFile: "../outFile.js", module: ModuleKind.AMD }), - commandLineArgs: ["--p", "/src/project"], - edits: [ - withOptionChange("with sourceMap", "--sourceMap"), - noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), - withOptionChange("with declaration, emit Dts and should not emit js", "--declaration"), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - nochangeWithIncrementalOutDeclarationFromBeforeExplaination(), - localChange(), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - nochangeWithIncrementalOutDeclarationFromBeforeExplaination(), - withOptionChange("with inlineSourceMap", "--inlineSourceMap"), - withOptionChange("with sourceMap", "--sourceMap"), - noChangeWithSubscenario("emit js files"), - withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), - withOptionChange("with declaration and declarationMap, should not re-emit", "--declaration", "--declarationMap"), + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: `when declarationMap changes with outFile`, + fs: () => ts.loadProjectFromFiles({ + "/src/project/tsconfig.json": JSON.stringify({ + compilerOptions: { + noEmitOnError: true, + declaration: true, + composite: true, + outFile: "../outFile.js", + } + }), + "/src/project/a.ts": "const x = 10;", + "/src/project/b.ts": "const y = 10;" + }), + commandLineArgs: ["--p", "/src/project"], + edits: [ + { + subScenario: "error and enable declarationMap", + modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "x", "x: 20"), + commandLineArgs: ["--p", "/src/project", "--declarationMap"], + }, + { + subScenario: "fix error declarationMap", + modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "x: 20", "x"), + commandLineArgs: ["--p", "/src/project", "--declarationMap"], + }, + ] + }); + + describe("different options::", () => { + function withOptionChange(subScenario: string, ...options: readonly string[]): ts.TestTscEdit { + return { + subScenario, + modifyFs: ts.noop, + commandLineArgs: ["--p", "/src/project", ...options], + }; + } + function noChangeWithSubscenario(subScenario: string): ts.TestTscEdit { + return { ...ts.noChangeRun, subScenario }; + } + function withOptionChangeAndDiscrepancyExplanation(subScenario: string, option: string): ts.TestTscEdit { + return { + ...withOptionChange(subScenario, option), + discrepancyExplanation: () => [ + `Clean build tsbuildinfo will have compilerOptions with composite and ${option.replace(/\-/g, "")}`, + `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option composite only`, + ] + }; + } + function withEmitDeclarationOnlyChangeAndDiscrepancyExplanation(subScenario: string): ts.TestTscEdit { + const edit = withOptionChangeAndDiscrepancyExplanation(subScenario, "--emitDeclarationOnly"); + const discrepancyExplanation = edit.discrepancyExplanation!; + edit.discrepancyExplanation = () => [ + ...discrepancyExplanation(), + `Clean build info does not have js section because its fresh build`, + `Incremental build info has js section from old build` + ]; + return edit; + } + function withOptionChangeAndExportExplanation(subScenario: string, ...options: readonly string[]): ts.TestTscEdit { + return { + ...withOptionChange(subScenario, ...options), + discrepancyExplanation: ts.noChangeWithExportsDiscrepancyRun.discrepancyExplanation, + }; + } + function nochangeWithIncrementalDeclarationFromBeforeExplaination(): ts.TestTscEdit { + return { + ...ts.noChangeRun, + discrepancyExplanation: () => [ + `Clean build tsbuildinfo will have compilerOptions {}`, + `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option declaration and declarationMap`, ], - baselinePrograms: true, + }; + } + function nochangeWithIncrementalOutDeclarationFromBeforeExplaination(): ts.TestTscEdit { + const edit = nochangeWithIncrementalDeclarationFromBeforeExplaination(); + const discrepancyExplanation = edit.discrepancyExplanation!; + edit.discrepancyExplanation = () => [ + ...discrepancyExplanation(), + `Clean build does not have dts bundle section`, + `Incremental build contains the dts build section from before`, + ]; + return edit; + } + function localChange(): ts.TestTscEdit { + return { + subScenario: "local change", + modifyFs: fs => ts.replaceText(fs, "/src/project/a.ts", "Local = 1", "Local = 10"), + }; + } + function fs(options: ts.CompilerOptions) { + return ts.loadProjectFromFiles({ + "/src/project/tsconfig.json": JSON.stringify({ compilerOptions: ts.compilerOptionsToConfigJson(options) }), + "/src/project/a.ts": `export const a = 10;const aLocal = 10;`, + "/src/project/b.ts": `export const b = 10;const bLocal = 10;`, + "/src/project/c.ts": `import { a } from "./a";export const c = a;`, + "/src/project/d.ts": `import { b } from "./b";export const d = b;`, }); + } + function enableDeclarationMap(): ts.TestTscEdit { + return { + subScenario: "declarationMap enabling", + modifyFs: fs => { + const config = JSON.parse(fs.readFileSync("/src/project/tsconfig.json", "utf-8")); + config.compilerOptions.declarationMap = true; + fs.writeFileSync("/src/project/tsconfig.json", JSON.stringify(config)); + }, + }; + } + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "different options", + fs: () => fs({ composite: true }), + commandLineArgs: ["--p", "/src/project"], + edits: [ + withOptionChange("with sourceMap", "--sourceMap"), + noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), + withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), + ts.noChangeRun, + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"), + withOptionChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything", "--emitDeclarationOnly"), + ts.noChangeRun, + localChange(), + withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), + withOptionChange("with inlineSourceMap", "--inlineSourceMap"), + withOptionChange("with sourceMap", "--sourceMap"), + enableDeclarationMap(), + withOptionChange("with sourceMap should not emit d.ts", "--sourceMap"), + ], + baselinePrograms: true, + }); + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "different options with outFile", + fs: () => fs({ composite: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }), + commandLineArgs: ["--p", "/src/project"], + edits: [ + withOptionChange("with sourceMap", "--sourceMap"), + noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), + withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), + ts.noChangeRun, + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + noChangeWithSubscenario("should re-emit only dts so they dont contain sourcemap"), + withEmitDeclarationOnlyChangeAndDiscrepancyExplanation("with emitDeclarationOnly should not emit anything"), + ts.noChangeRun, + localChange(), + withOptionChangeAndDiscrepancyExplanation("with declaration should not emit anything", "--declaration"), + withOptionChange("with inlineSourceMap", "--inlineSourceMap"), + withOptionChange("with sourceMap", "--sourceMap"), + enableDeclarationMap(), + withOptionChange("with sourceMap should not emit d.ts", "--sourceMap"), + ], + baselinePrograms: true, + }); + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "different options with incremental", + fs: () => fs({ incremental: true }), + commandLineArgs: ["--p", "/src/project"], + edits: [ + withOptionChangeAndExportExplanation("with sourceMap", "--sourceMap"), + withOptionChangeAndExportExplanation("should re-emit only js so they dont contain sourcemap"), + withOptionChange("with declaration, emit Dts and should not emit js", "--declaration"), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + nochangeWithIncrementalDeclarationFromBeforeExplaination(), + localChange(), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + nochangeWithIncrementalDeclarationFromBeforeExplaination(), + withOptionChange("with inlineSourceMap", "--inlineSourceMap"), + withOptionChange("with sourceMap", "--sourceMap"), + noChangeWithSubscenario("emit js files"), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + withOptionChange("with declaration and declarationMap, should not re-emit", "--declaration", "--declarationMap"), + ], + baselinePrograms: true, + }); + ts.verifyTscWithEdits({ + scenario: "incremental", + subScenario: "different options with incremental with outFile", + fs: () => fs({ incremental: true, outFile: "../outFile.js", module: ts.ModuleKind.AMD }), + commandLineArgs: ["--p", "/src/project"], + edits: [ + withOptionChange("with sourceMap", "--sourceMap"), + noChangeWithSubscenario("should re-emit only js so they dont contain sourcemap"), + withOptionChange("with declaration, emit Dts and should not emit js", "--declaration"), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + nochangeWithIncrementalOutDeclarationFromBeforeExplaination(), + localChange(), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + nochangeWithIncrementalOutDeclarationFromBeforeExplaination(), + withOptionChange("with inlineSourceMap", "--inlineSourceMap"), + withOptionChange("with sourceMap", "--sourceMap"), + noChangeWithSubscenario("emit js files"), + withOptionChange("with declaration and declarationMap", "--declaration", "--declarationMap"), + withOptionChange("with declaration and declarationMap, should not re-emit", "--declaration", "--declarationMap"), + ], + baselinePrograms: true, }); }); -} +}); diff --git a/src/testRunner/unittests/tsc/listFilesOnly.ts b/src/testRunner/unittests/tsc/listFilesOnly.ts index 7352e57b7c5ce..a81b1a7c82e4e 100644 --- a/src/testRunner/unittests/tsc/listFilesOnly.ts +++ b/src/testRunner/unittests/tsc/listFilesOnly.ts @@ -1,44 +1,45 @@ -namespace ts { - describe("unittests:: tsc:: listFilesOnly::", () => { - verifyTsc({ - scenario: "listFilesOnly", - subScenario: "combined with watch", - fs: () => loadProjectFromFiles({ - "/src/test.ts": Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; + +describe("unittests:: tsc:: listFilesOnly::", () => { + ts.verifyTsc({ + scenario: "listFilesOnly", + subScenario: "combined with watch", + fs: () => ts.loadProjectFromFiles({ + "/src/test.ts": Utils.dedent` export const x = 1;`, - }), - commandLineArgs: ["/src/test.ts", "--watch", "--listFilesOnly"] - }); + }), + commandLineArgs: ["/src/test.ts", "--watch", "--listFilesOnly"] + }); - verifyTsc({ - scenario: "listFilesOnly", - subScenario: "loose file", - fs: () => loadProjectFromFiles({ - "/src/test.ts": Utils.dedent` + ts.verifyTsc({ + scenario: "listFilesOnly", + subScenario: "loose file", + fs: () => ts.loadProjectFromFiles({ + "/src/test.ts": Utils.dedent` export const x = 1;`, - }), - commandLineArgs: ["/src/test.ts", "--listFilesOnly"] - }); + }), + commandLineArgs: ["/src/test.ts", "--listFilesOnly"] + }); - verifyTscWithEdits({ - scenario: "listFilesOnly", - subScenario: "combined with incremental", - fs: () => loadProjectFromFiles({ - "/src/test.ts": `export const x = 1;`, - "/src/tsconfig.json": "{}" - }), - commandLineArgs: ["-p", "/src", "--incremental", "--listFilesOnly"], - edits: [ - { - ...noChangeRun, - commandLineArgs: ["-p", "/src", "--incremental"], - }, - noChangeRun, - { - ...noChangeRun, - commandLineArgs: ["-p", "/src", "--incremental"], - } - ] - }); + ts.verifyTscWithEdits({ + scenario: "listFilesOnly", + subScenario: "combined with incremental", + fs: () => ts.loadProjectFromFiles({ + "/src/test.ts": `export const x = 1;`, + "/src/tsconfig.json": "{}" + }), + commandLineArgs: ["-p", "/src", "--incremental", "--listFilesOnly"], + edits: [ + { + ...ts.noChangeRun, + commandLineArgs: ["-p", "/src", "--incremental"], + }, + ts.noChangeRun, + { + ...ts.noChangeRun, + commandLineArgs: ["-p", "/src", "--incremental"], + } + ] }); -} +}); diff --git a/src/testRunner/unittests/tsc/projectReferences.ts b/src/testRunner/unittests/tsc/projectReferences.ts index 765bdfdb53cf1..47b9486d02e0f 100644 --- a/src/testRunner/unittests/tsc/projectReferences.ts +++ b/src/testRunner/unittests/tsc/projectReferences.ts @@ -1,42 +1,42 @@ -namespace ts { - describe("unittests:: tsc:: projectReferences::", () => { - verifyTsc({ - scenario: "projectReferences", - subScenario: "when project contains invalid project reference", - fs: () => loadProjectFromFiles({ - "/src/project/src/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": JSON.stringify({ - compilerOptions: { - module: "amd", - outFile: "theApp.js" - }, - references: [ - { path: "../Util/Dates" } - ] - }), +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsc:: projectReferences::", () => { + ts.verifyTsc({ + scenario: "projectReferences", + subScenario: "when project contains invalid project reference", + fs: () => ts.loadProjectFromFiles({ + "/src/project/src/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": JSON.stringify({ + compilerOptions: { + module: "amd", + outFile: "theApp.js" + }, + references: [ + { path: "../Util/Dates" } + ] }), - commandLineArgs: ["--p", "src/project"], - }); + }), + commandLineArgs: ["--p", "src/project"], + }); - verifyTsc({ - scenario: "projectReferences", - subScenario: "when project references composite project with noEmit", - fs: () => loadProjectFromFiles({ - "/src/utils/index.ts": "export const x = 10;", - "/src/utils/tsconfig.json": JSON.stringify({ - compilerOptions: { - composite: true, - noEmit: true, - } - }), - "/src/project/index.ts": `import { x } from "../utils";`, - "/src/project/tsconfig.json": JSON.stringify({ - references: [ - { path: "../utils" } - ] - }), + ts.verifyTsc({ + scenario: "projectReferences", + subScenario: "when project references composite project with noEmit", + fs: () => ts.loadProjectFromFiles({ + "/src/utils/index.ts": "export const x = 10;", + "/src/utils/tsconfig.json": JSON.stringify({ + compilerOptions: { + composite: true, + noEmit: true, + } + }), + "/src/project/index.ts": `import { x } from "../utils";`, + "/src/project/tsconfig.json": JSON.stringify({ + references: [ + { path: "../utils" } + ] }), - commandLineArgs: ["--p", "src/project"] - }); + }), + commandLineArgs: ["--p", "src/project"] }); -} +}); diff --git a/src/testRunner/unittests/tsc/redirect.ts b/src/testRunner/unittests/tsc/redirect.ts index e131ba821af4f..df174a6dd5487 100644 --- a/src/testRunner/unittests/tsc/redirect.ts +++ b/src/testRunner/unittests/tsc/redirect.ts @@ -1,34 +1,34 @@ -namespace ts { - describe("unittests:: tsc:: redirect::", () => { - verifyTsc({ - scenario: "redirect", - subScenario: "when redirecting ts file", - fs: () => loadProjectFromFiles({ - "/src/project/tsconfig.json": JSON.stringify({ - compilerOptions: { - outDir: "out" - }, - include: [ - "copy1/node_modules/target/*", - "copy2/node_modules/target/*", - ] - }), - "/src/project/copy1/node_modules/target/index.ts": "export const a = 1;", - "/src/project/copy1/node_modules/target/import.ts": `import {} from "./";`, - "/src/project/copy1/node_modules/target/package.json": JSON.stringify({ - name: "target", - version: "1.0.0", - main: "index.js", - }), - "/src/project/copy2/node_modules/target/index.ts": "export const a = 1;", - "/src/project/copy2/node_modules/target/import.ts": `import {} from "./";`, - "/src/project/copy2/node_modules/target/package.json": JSON.stringify({ - name: "target", - version: "1.0.0", - main: "index.js", - }), +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsc:: redirect::", () => { + ts.verifyTsc({ + scenario: "redirect", + subScenario: "when redirecting ts file", + fs: () => ts.loadProjectFromFiles({ + "/src/project/tsconfig.json": JSON.stringify({ + compilerOptions: { + outDir: "out" + }, + include: [ + "copy1/node_modules/target/*", + "copy2/node_modules/target/*", + ] }), - commandLineArgs: ["-p", "src/project"], - }); + "/src/project/copy1/node_modules/target/index.ts": "export const a = 1;", + "/src/project/copy1/node_modules/target/import.ts": `import {} from "./";`, + "/src/project/copy1/node_modules/target/package.json": JSON.stringify({ + name: "target", + version: "1.0.0", + main: "index.js", + }), + "/src/project/copy2/node_modules/target/index.ts": "export const a = 1;", + "/src/project/copy2/node_modules/target/import.ts": `import {} from "./";`, + "/src/project/copy2/node_modules/target/package.json": JSON.stringify({ + name: "target", + version: "1.0.0", + main: "index.js", + }), + }), + commandLineArgs: ["-p", "src/project"], }); -} +}); diff --git a/src/testRunner/unittests/tsc/runWithoutArgs.ts b/src/testRunner/unittests/tsc/runWithoutArgs.ts index 1c4a3c9a0a4f4..6c7af42052327 100644 --- a/src/testRunner/unittests/tsc/runWithoutArgs.ts +++ b/src/testRunner/unittests/tsc/runWithoutArgs.ts @@ -1,27 +1,27 @@ -namespace ts { - describe("unittests:: tsc:: runWithoutArgs::", () => { - verifyTsc({ - scenario: "runWithoutArgs", - subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped", - fs: () => loadProjectFromFiles({}), - commandLineArgs: [], - environmentVariables: { TS_TEST_TERMINAL_WIDTH: "120" } - }); +import * as ts from "../../_namespaces/ts"; - verifyTsc({ - scenario: "runWithoutArgs", - subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped when host can't provide terminal width", - fs: () => loadProjectFromFiles({}), - commandLineArgs: [], - }); +describe("unittests:: tsc:: runWithoutArgs::", () => { + ts.verifyTsc({ + scenario: "runWithoutArgs", + subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped", + fs: () => ts.loadProjectFromFiles({}), + commandLineArgs: [], + environmentVariables: { TS_TEST_TERMINAL_WIDTH: "120" } + }); - verifyTsc({ - scenario: "runWithoutArgs", - subScenario: "does not add color when NO_COLOR is set", - fs: () => loadProjectFromFiles({}), - commandLineArgs: [], - environmentVariables: { NO_COLOR: "true" } - }); + ts.verifyTsc({ + scenario: "runWithoutArgs", + subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped when host can't provide terminal width", + fs: () => ts.loadProjectFromFiles({}), + commandLineArgs: [], + }); + ts.verifyTsc({ + scenario: "runWithoutArgs", + subScenario: "does not add color when NO_COLOR is set", + fs: () => ts.loadProjectFromFiles({}), + commandLineArgs: [], + environmentVariables: { NO_COLOR: "true" } }); -} + +}); diff --git a/src/testRunner/unittests/tscWatch/consoleClearing.ts b/src/testRunner/unittests/tscWatch/consoleClearing.ts index bde7b6c901863..7039748e91638 100644 --- a/src/testRunner/unittests/tscWatch/consoleClearing.ts +++ b/src/testRunner/unittests/tscWatch/consoleClearing.ts @@ -1,66 +1,66 @@ -namespace ts.tscWatch { - describe("unittests:: tsc-watch:: console clearing", () => { - const scenario = "consoleClearing"; - const file: File = { - path: "/f.ts", - content: "" - }; +import * as ts from "../../_namespaces/ts"; - const makeChangeToFile: TscWatchCompileChange[] = [{ - caption: "Comment added to file f", - change: sys => sys.modifyFile(file.path, "//"), - timeouts: runQueuedTimeoutCallbacks, - }]; +describe("unittests:: tsc-watch:: console clearing", () => { + const scenario = "consoleClearing"; + const file: ts.tscWatch.File = { + path: "/f.ts", + content: "" + }; - function checkConsoleClearingUsingCommandLineOptions(subScenario: string, commandLineOptions?: string[]) { - verifyTscWatch({ - scenario, - subScenario, - commandLineArgs: ["--w", file.path, ...commandLineOptions || emptyArray], - sys: () => createWatchedSystem([file, libFile]), - changes: makeChangeToFile, - }); - } + const makeChangeToFile: ts.tscWatch.TscWatchCompileChange[] = [{ + caption: "Comment added to file f", + change: sys => sys.modifyFile(file.path, "//"), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }]; + + function checkConsoleClearingUsingCommandLineOptions(subScenario: string, commandLineOptions?: string[]) { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario, + commandLineArgs: ["--w", file.path, ...commandLineOptions || ts.emptyArray], + sys: () => ts.tscWatch.createWatchedSystem([file, ts.tscWatch.libFile]), + changes: makeChangeToFile, + }); + } - checkConsoleClearingUsingCommandLineOptions("without --diagnostics or --extendedDiagnostics"); - checkConsoleClearingUsingCommandLineOptions("with --diagnostics", ["--diagnostics"]); - checkConsoleClearingUsingCommandLineOptions("with --extendedDiagnostics", ["--extendedDiagnostics"]); - checkConsoleClearingUsingCommandLineOptions("with --preserveWatchOutput", ["--preserveWatchOutput"]); + checkConsoleClearingUsingCommandLineOptions("without --diagnostics or --extendedDiagnostics"); + checkConsoleClearingUsingCommandLineOptions("with --diagnostics", ["--diagnostics"]); + checkConsoleClearingUsingCommandLineOptions("with --extendedDiagnostics", ["--extendedDiagnostics"]); + checkConsoleClearingUsingCommandLineOptions("with --preserveWatchOutput", ["--preserveWatchOutput"]); - describe("when preserveWatchOutput is true in config file", () => { - const compilerOptions: CompilerOptions = { - preserveWatchOutput: true - }; - const configFile: File = { - path: "/tsconfig.json", - content: JSON.stringify({ compilerOptions }) - }; - const files = [file, configFile, libFile]; - it("using createWatchOfConfigFile ", () => { - const baseline = createBaseline(createWatchedSystem(files)); - const watch = createWatchProgram(createWatchCompilerHostOfConfigFileForBaseline({ - system: baseline.sys, - cb: baseline.cb, - configFileName: configFile.path, - })); - // Initially console is cleared if --preserveOutput is not provided since the config file is yet to be parsed - runWatchBaseline({ - scenario, - subScenario: "when preserveWatchOutput is true in config file/createWatchOfConfigFile", - commandLineArgs: ["--w", "-p", configFile.path], - ...baseline, - getPrograms: () => [[watch.getCurrentProgram().getProgram(), watch.getCurrentProgram()]], - changes: makeChangeToFile, - watchOrSolution: watch - }); - }); - verifyTscWatch({ + describe("when preserveWatchOutput is true in config file", () => { + const compilerOptions: ts.CompilerOptions = { + preserveWatchOutput: true + }; + const configFile: ts.tscWatch.File = { + path: "/tsconfig.json", + content: JSON.stringify({ compilerOptions }) + }; + const files = [file, configFile, ts.tscWatch.libFile]; + it("using createWatchOfConfigFile ", () => { + const baseline = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem(files)); + const watch = ts.createWatchProgram(ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({ + system: baseline.sys, + cb: baseline.cb, + configFileName: configFile.path, + })); + // Initially console is cleared if --preserveOutput is not provided since the config file is yet to be parsed + ts.tscWatch.runWatchBaseline({ scenario, - subScenario: "when preserveWatchOutput is true in config file/when createWatchProgram is invoked with configFileParseResult on WatchCompilerHostOfConfigFile", + subScenario: "when preserveWatchOutput is true in config file/createWatchOfConfigFile", commandLineArgs: ["--w", "-p", configFile.path], - sys: () => createWatchedSystem(files), + ...baseline, + getPrograms: () => [[watch.getCurrentProgram().getProgram(), watch.getCurrentProgram()]], changes: makeChangeToFile, + watchOrSolution: watch }); }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "when preserveWatchOutput is true in config file/when createWatchProgram is invoked with configFileParseResult on WatchCompilerHostOfConfigFile", + commandLineArgs: ["--w", "-p", configFile.path], + sys: () => ts.tscWatch.createWatchedSystem(files), + changes: makeChangeToFile, + }); }); -} +}); diff --git a/src/testRunner/unittests/tscWatch/emit.ts b/src/testRunner/unittests/tscWatch/emit.ts index 8c9ec7150b199..2ab59f8d376bd 100644 --- a/src/testRunner/unittests/tscWatch/emit.ts +++ b/src/testRunner/unittests/tscWatch/emit.ts @@ -1,520 +1,520 @@ -namespace ts.tscWatch { - const scenario = "emit"; - describe("unittests:: tsc-watch:: emit with outFile or out setting", () => { - function verifyOutAndOutFileSetting(subScenario: string, out?: string, outFile?: string) { - verifyTscWatch({ - scenario, - subScenario: `emit with outFile or out setting/${subScenario}`, - commandLineArgs: ["--w", "-p", "/a/tsconfig.json"], - sys: () => createWatchedSystem({ - "/a/a.ts": "let x = 1", - "/a/b.ts": "let y = 1", - "/a/tsconfig.json": JSON.stringify({ compilerOptions: { out, outFile } }), - [libFile.path]: libFile.content, - }), - changes: [ - { - caption: "Make change in the file", - change: sys => sys.writeFile("/a/a.ts", "let x = 11"), - timeouts: runQueuedTimeoutCallbacks - }, - { - caption: "Make change in the file again", - change: sys => sys.writeFile("/a/a.ts", "let xy = 11"), - timeouts: runQueuedTimeoutCallbacks - } - ] - }); - } - verifyOutAndOutFileSetting("config does not have out or outFile"); - verifyOutAndOutFileSetting("config has out", "/a/out.js"); - verifyOutAndOutFileSetting("config has outFile", /*out*/ undefined, "/a/out.js"); - - function verifyFilesEmittedOnce(subScenario: string, useOutFile: boolean) { - verifyTscWatch({ - scenario, - subScenario: `emit with outFile or out setting/${subScenario}`, - commandLineArgs: ["--w", "-p", "/a/b/project/tsconfig.json"], - sys: () => { - const file1: File = { - path: "/a/b/output/AnotherDependency/file1.d.ts", - content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }" - }; - const file2: File = { - path: "/a/b/dependencies/file2.d.ts", - content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }" - }; - const file3: File = { - path: "/a/b/project/src/main.ts", - content: "namespace Main { export function fooBar() {} }" - }; - const file4: File = { - path: "/a/b/project/src/main2.ts", - content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }" - }; - const configFile: File = { - path: "/a/b/project/tsconfig.json", - content: JSON.stringify({ - compilerOptions: useOutFile ? - { outFile: "../output/common.js", target: "es5" } : - { outDir: "../output", target: "es5" }, - files: [file1.path, file2.path, file3.path, file4.path] - }) - }; - return createWatchedSystem([file1, file2, file3, file4, libFile, configFile]); - }, - changes: emptyArray - }); - } - verifyFilesEmittedOnce("with --outFile and multiple declaration files in the program", /*useOutFile*/ true); - verifyFilesEmittedOnce("without --outFile and multiple declaration files in the program", /*useOutFile*/ false); - }); - - describe("unittests:: tsc-watch:: emit for configured projects", () => { - const file1Consumer1Path = "/a/b/file1Consumer1.ts"; - const file1Consumer2Path = "/a/b/file1Consumer2.ts"; - const moduleFile1Path = "/a/b/moduleFile1.ts"; - const moduleFile2Path = "/a/b/moduleFile2.ts"; - const globalFilePath = "/a/b/globalFile3.ts"; - const configFilePath = "/a/b/tsconfig.json"; - interface VerifyTscWatchEmit { - subScenario: string; - /** custom config file options */ - configObj?: any; - /** Additional files and folders to add */ - getAdditionalFileOrFolder?: () => File[]; - /** initial list of files to emit if not the default list */ - firstReloadFileList?: string[]; - changes: TscWatchCompileChange[] - } - function verifyTscWatchEmit({ - subScenario, - configObj, - getAdditionalFileOrFolder, - firstReloadFileList, - changes - }: VerifyTscWatchEmit) { - verifyTscWatch({ - scenario, - subScenario: `emit for configured projects/${subScenario}`, - commandLineArgs: ["--w", "-p", configFilePath], - sys: () => { - const moduleFile1: File = { - path: moduleFile1Path, - content: "export function Foo() { };", - }; - - const file1Consumer1: File = { - path: file1Consumer1Path, - content: `import {Foo} from "./moduleFile1"; export var y = 10;`, - }; - - const file1Consumer2: File = { - path: file1Consumer2Path, - content: `import {Foo} from "./moduleFile1"; let z = 10;`, - }; - - const moduleFile2: File = { - path: moduleFile2Path, - content: `export var Foo4 = 10;`, - }; - - const globalFile3: File = { - path: globalFilePath, - content: `interface GlobalFoo { age: number }` - }; - const configFile: File = { - path: configFilePath, - content: JSON.stringify(configObj || {}) - }; - const additionalFiles = getAdditionalFileOrFolder?.() || emptyArray; - const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles]; - return createWatchedSystem(firstReloadFileList ? - map(firstReloadFileList, fileName => find(files, file => file.path === fileName)!) : - files - ); - }, - changes - }); - } - - function modifyModuleFile1Shape(sys: WatchedSystem) { - sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { };`); - } - const changeModuleFile1Shape: TscWatchCompileChange = { - caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { };`", - change: modifyModuleFile1Shape, - timeouts: checkSingleTimeoutQueueLengthAndRun, - }; - - verifyTscWatchEmit({ - subScenario: "should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", - changes: [ - changeModuleFile1Shape, - { - caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`", - change: sys => sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { console.log('hi'); };`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); +import * as ts from "../../_namespaces/ts"; - verifyTscWatchEmit({ - subScenario: "should be up-to-date with the reference map changes", +const scenario = "emit"; +describe("unittests:: tsc-watch:: emit with outFile or out setting", () => { + function verifyOutAndOutFileSetting(subScenario: string, out?: string, outFile?: string) { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `emit with outFile or out setting/${subScenario}`, + commandLineArgs: ["--w", "-p", "/a/tsconfig.json"], + sys: () => ts.tscWatch.createWatchedSystem({ + "/a/a.ts": "let x = 1", + "/a/b.ts": "let y = 1", + "/a/tsconfig.json": JSON.stringify({ compilerOptions: { out, outFile } }), + [ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content, + }), changes: [ { - caption: "Change file1Consumer1 content to `export let y = Foo();`", - change: sys => sys.writeFile(file1Consumer1Path, `export let y = Foo();`), - timeouts: checkSingleTimeoutQueueLengthAndRun, + caption: "Make change in the file", + change: sys => sys.writeFile("/a/a.ts", "let x = 11"), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks }, - changeModuleFile1Shape, { - caption: "Add the import statements back to file1Consumer1", - change: sys => sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`", - change: sys => sys.writeFile(moduleFile1Path, `export let y = Foo();`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Multiple file edits in one go", - // Change file1Consumer1 content to `export let y = Foo();` - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - change: sys => { - sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`); - modifyModuleFile1Shape(sys); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, + caption: "Make change in the file again", + change: sys => sys.writeFile("/a/a.ts", "let xy = 11"), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks } ] }); + } + verifyOutAndOutFileSetting("config does not have out or outFile"); + verifyOutAndOutFileSetting("config has out", "/a/out.js"); + verifyOutAndOutFileSetting("config has outFile", /*out*/ undefined, "/a/out.js"); - verifyTscWatchEmit({ - subScenario: "should be up-to-date with deleted files", - changes: [ - { - caption: "change moduleFile1 shape and delete file1Consumer2", - change: sys => { - modifyModuleFile1Shape(sys); - sys.deleteFile(file1Consumer2Path); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] + function verifyFilesEmittedOnce(subScenario: string, useOutFile: boolean) { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `emit with outFile or out setting/${subScenario}`, + commandLineArgs: ["--w", "-p", "/a/b/project/tsconfig.json"], + sys: () => { + const file1: ts.tscWatch.File = { + path: "/a/b/output/AnotherDependency/file1.d.ts", + content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }" + }; + const file2: ts.tscWatch.File = { + path: "/a/b/dependencies/file2.d.ts", + content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }" + }; + const file3: ts.tscWatch.File = { + path: "/a/b/project/src/main.ts", + content: "namespace Main { export function fooBar() {} }" + }; + const file4: ts.tscWatch.File = { + path: "/a/b/project/src/main2.ts", + content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }" + }; + const configFile: ts.tscWatch.File = { + path: "/a/b/project/tsconfig.json", + content: JSON.stringify({ + compilerOptions: useOutFile ? + { outFile: "../output/common.js", target: "es5" } : + { outDir: "../output", target: "es5" }, + files: [file1.path, file2.path, file3.path, file4.path] + }) + }; + return ts.tscWatch.createWatchedSystem([file1, file2, file3, file4, ts.tscWatch.libFile, configFile]); + }, + changes: ts.emptyArray }); + } + verifyFilesEmittedOnce("with --outFile and multiple declaration files in the program", /*useOutFile*/ true); + verifyFilesEmittedOnce("without --outFile and multiple declaration files in the program", /*useOutFile*/ false); +}); - verifyTscWatchEmit({ - subScenario: "should be up-to-date with newly created files", - changes: [ - { - caption: "change moduleFile1 shape and create file1Consumer3", - change: sys => { - sys.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`); - modifyModuleFile1Shape(sys); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); +describe("unittests:: tsc-watch:: emit for configured projects", () => { + const file1Consumer1Path = "/a/b/file1Consumer1.ts"; + const file1Consumer2Path = "/a/b/file1Consumer2.ts"; + const moduleFile1Path = "/a/b/moduleFile1.ts"; + const moduleFile2Path = "/a/b/moduleFile2.ts"; + const globalFilePath = "/a/b/globalFile3.ts"; + const configFilePath = "/a/b/tsconfig.json"; + interface VerifyTscWatchEmit { + subScenario: string; + /** custom config file options */ + configObj?: any; + /** Additional files and folders to add */ + getAdditionalFileOrFolder?: () => ts.tscWatch.File[]; + /** initial list of files to emit if not the default list */ + firstReloadFileList?: string[]; + changes: ts.tscWatch.TscWatchCompileChange[] + } + function verifyTscWatchEmit({ + subScenario, + configObj, + getAdditionalFileOrFolder, + firstReloadFileList, + changes + }: VerifyTscWatchEmit) { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `emit for configured projects/${subScenario}`, + commandLineArgs: ["--w", "-p", configFilePath], + sys: () => { + const moduleFile1: ts.tscWatch.File = { + path: moduleFile1Path, + content: "export function Foo() { };", + }; - verifyTscWatchEmit({ - subScenario: "should detect changes in non-root files", - configObj: { files: [file1Consumer1Path] }, - changes: [ - changeModuleFile1Shape, - { - caption: "change file1 internal, and verify only file1 is affected", - change: sys => sys.appendFile(moduleFile1Path, "var T1: number;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + const file1Consumer1: ts.tscWatch.File = { + path: file1Consumer1Path, + content: `import {Foo} from "./moduleFile1"; export var y = 10;`, + }; - verifyTscWatchEmit({ - subScenario: "should return all files if a global file changed shape", - changes: [ - { - caption: "change shape of global file", - change: sys => sys.appendFile(globalFilePath, "var T2: string;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + const file1Consumer2: ts.tscWatch.File = { + path: file1Consumer2Path, + content: `import {Foo} from "./moduleFile1"; let z = 10;`, + }; - verifyTscWatchEmit({ - subScenario: "should always return the file itself if '--isolatedModules' is specified", - configObj: { compilerOptions: { isolatedModules: true } }, - changes: [ - changeModuleFile1Shape - ] - }); + const moduleFile2: ts.tscWatch.File = { + path: moduleFile2Path, + content: `export var Foo4 = 10;`, + }; - verifyTscWatchEmit({ - subScenario: "should always return the file itself if '--out' or '--outFile' is specified", - configObj: { compilerOptions: { module: "system", outFile: "/a/b/out.js" } }, - changes: [ - changeModuleFile1Shape - ] + const globalFile3: ts.tscWatch.File = { + path: globalFilePath, + content: `interface GlobalFoo { age: number }` + }; + const configFile: ts.tscWatch.File = { + path: configFilePath, + content: JSON.stringify(configObj || {}) + }; + const additionalFiles = getAdditionalFileOrFolder?.() || ts.emptyArray; + const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.tscWatch.libFile, ...additionalFiles]; + return ts.tscWatch.createWatchedSystem(firstReloadFileList ? + ts.map(firstReloadFileList, fileName => ts.find(files, file => file.path === fileName)!) : + files + ); + }, + changes }); + } - verifyTscWatchEmit({ - subScenario: "should return cascaded affected file list", - getAdditionalFileOrFolder: () => [{ - path: "/a/b/file1Consumer1Consumer1.ts", - content: `import {y} from "./file1Consumer1";` - }], - changes: [ - { - caption: "change file1Consumer1", - change: sys => sys.appendFile(file1Consumer1Path, "export var T: number;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - changeModuleFile1Shape, - { - caption: "change file1Consumer1 and moduleFile1", - change: sys => { - sys.appendFile(file1Consumer1Path, "export var T2: number;"); - sys.writeFile(moduleFile1Path, `export var T2: number;export function Foo() { };`); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + function modifyModuleFile1Shape(sys: ts.tscWatch.WatchedSystem) { + sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { };`); + } + const changeModuleFile1Shape: ts.tscWatch.TscWatchCompileChange = { + caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { };`", + change: modifyModuleFile1Shape, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }; - verifyTscWatchEmit({ - subScenario: "should work fine for files with circular references", - getAdditionalFileOrFolder: () => [ - { - path: "/a/b/file1.ts", - content: `/// -export var t1 = 10;` + verifyTscWatchEmit({ + subScenario: "should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", + changes: [ + changeModuleFile1Shape, + { + caption: "Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`", + change: sys => sys.writeFile(moduleFile1Path, `export var T: number;export function Foo() { console.log('hi'); };`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatchEmit({ + subScenario: "should be up-to-date with the reference map changes", + changes: [ + { + caption: "Change file1Consumer1 content to `export let y = Foo();`", + change: sys => sys.writeFile(file1Consumer1Path, `export let y = Foo();`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + changeModuleFile1Shape, + { + caption: "Add the import statements back to file1Consumer1", + change: sys => sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`", + change: sys => sys.writeFile(moduleFile1Path, `export let y = Foo();`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Multiple file edits in one go", + // Change file1Consumer1 content to `export let y = Foo();` + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + change: sys => { + sys.writeFile(file1Consumer1Path, `import {Foo} from "./moduleFile1";let y = Foo();`); + modifyModuleFile1Shape(sys); }, - { - path: "/a/b/file2.ts", - content: `/// -export var t2 = 10;` - } - ], - firstReloadFileList: [libFile.path, "/a/b/file1.ts", "/a/b/file2.ts", configFilePath], - changes: [ - { - caption: "change file1", - change: sys => sys.appendFile("/a/b/file1.ts", "export var t3 = 10;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatchEmit({ - subScenario: "should detect removed code file", - getAdditionalFileOrFolder: () => [{ - path: "/a/b/referenceFile1.ts", - content: `/// -export var x = Foo();` - }], - firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", moduleFile1Path, configFilePath], - changes: [ - { - caption: "delete moduleFile1", - change: sys => sys.deleteFile(moduleFile1Path), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + verifyTscWatchEmit({ + subScenario: "should be up-to-date with deleted files", + changes: [ + { + caption: "change moduleFile1 shape and delete file1Consumer2", + change: sys => { + modifyModuleFile1Shape(sys); + sys.deleteFile(file1Consumer2Path); + }, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatchEmit({ - subScenario: "should detect non existing code file", - getAdditionalFileOrFolder: () => [{ - path: "/a/b/referenceFile1.ts", - content: `/// -export var x = Foo();` - }], - firstReloadFileList: [libFile.path, "/a/b/referenceFile1.ts", configFilePath], - changes: [ - { - caption: "edit refereceFile1", - change: sys => sys.appendFile("/a/b/referenceFile1.ts", "export var yy = Foo();"), - timeouts: checkSingleTimeoutQueueLengthAndRun, + verifyTscWatchEmit({ + subScenario: "should be up-to-date with newly created files", + changes: [ + { + caption: "change moduleFile1 shape and create file1Consumer3", + change: sys => { + sys.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`); + modifyModuleFile1Shape(sys); }, - { - caption: "create moduleFile2", - change: sys => sys.writeFile(moduleFile2Path, "export var Foo4 = 10;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] }); - describe("unittests:: tsc-watch:: emit file content", () => { - function verifyNewLine(subScenario: string, newLine: string) { - verifyTscWatch({ - scenario, - subScenario: `emit file content/${subScenario}`, - commandLineArgs: ["--w", "/a/app.ts"], - sys: () => createWatchedSystem( - [ - { - path: "/a/app.ts", - content: ["var x = 1;", "var y = 2;"].join(newLine) - }, - libFile - ], - { newLine } - ), - changes: [ - { - caption: "Append a line", - change: sys => sys.appendFile("/a/app.ts", newLine + "var z = 3;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ], - }); - } - verifyNewLine("handles new lines lineFeed", "\n"); - verifyNewLine("handles new lines carriageReturn lineFeed", "\r\n"); + verifyTscWatchEmit({ + subScenario: "should detect changes in non-root files", + configObj: { files: [file1Consumer1Path] }, + changes: [ + changeModuleFile1Shape, + { + caption: "change file1 internal, and verify only file1 is affected", + change: sys => sys.appendFile(moduleFile1Path, "var T1: number;"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "emit file content/should emit specified file", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export function Foo() { return 10; }` - }; + verifyTscWatchEmit({ + subScenario: "should return all files if a global file changed shape", + changes: [ + { + caption: "change shape of global file", + change: sys => sys.appendFile(globalFilePath, "var T2: string;"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - const file2 = { - path: "/a/b/f2.ts", - content: `import {Foo} from "./f1"; export let y = Foo();` - }; + verifyTscWatchEmit({ + subScenario: "should always return the file itself if '--isolatedModules' is specified", + configObj: { compilerOptions: { isolatedModules: true } }, + changes: [ + changeModuleFile1Shape + ] + }); - const file3 = { - path: "/a/b/f3.ts", - content: `import {y} from "./f2"; let x = y;` - }; + verifyTscWatchEmit({ + subScenario: "should always return the file itself if '--out' or '--outFile' is specified", + configObj: { compilerOptions: { module: "system", outFile: "/a/b/out.js" } }, + changes: [ + changeModuleFile1Shape + ] + }); - const configFile = { - path: "/a/b/tsconfig.json", - content: "{}" - }; - return createWatchedSystem([file1, file2, file3, configFile, libFile]); + verifyTscWatchEmit({ + subScenario: "should return cascaded affected file list", + getAdditionalFileOrFolder: () => [{ + path: "/a/b/file1Consumer1Consumer1.ts", + content: `import {y} from "./file1Consumer1";` + }], + changes: [ + { + caption: "change file1Consumer1", + change: sys => sys.appendFile(file1Consumer1Path, "export var T: number;"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "Append content to f1", - change: sys => sys.appendFile("/a/b/f1.ts", "export function foo2() { return 2; }"), - timeouts: checkSingleTimeoutQueueLengthAndRun, + changeModuleFile1Shape, + { + caption: "change file1Consumer1 and moduleFile1", + change: sys => { + sys.appendFile(file1Consumer1Path, "export var T2: number;"); + sys.writeFile(moduleFile1Path, `export var T2: number;export function Foo() { };`); }, - { - caption: "Again Append content to f1", - change: sys => sys.appendFile("/a/b/f1.ts", "export function fooN() { return 2; }"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ], - }); + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "emit file content/elides const enums correctly in incremental compilation", - commandLineArgs: ["-w", "/user/someone/projects/myproject/file3.ts"], - sys: () => { - const currentDirectory = "/user/someone/projects/myproject"; - const file1: File = { - path: `${currentDirectory}/file1.ts`, - content: "export const enum E1 { V = 1 }" - }; - const file2: File = { - path: `${currentDirectory}/file2.ts`, - content: `import { E1 } from "./file1"; export const enum E2 { V = E1.V }` - }; - const file3: File = { - path: `${currentDirectory}/file3.ts`, - content: `import { E2 } from "./file2"; const v: E2 = E2.V;` - }; - return createWatchedSystem([file1, file2, file3, libFile]); + verifyTscWatchEmit({ + subScenario: "should work fine for files with circular references", + getAdditionalFileOrFolder: () => [ + { + path: "/a/b/file1.ts", + content: `/// +export var t1 = 10;` }, - changes: [ - { - caption: "Append content to file3", - change: sys => sys.appendFile("/user/someone/projects/myproject/file3.ts", "function foo2() { return 2; }"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ], - }); + { + path: "/a/b/file2.ts", + content: `/// +export var t2 = 10;` + } + ], + firstReloadFileList: [ts.tscWatch.libFile.path, "/a/b/file1.ts", "/a/b/file2.ts", configFilePath], + changes: [ + { + caption: "change file1", + change: sys => sys.appendFile("/a/b/file1.ts", "export var t3 = 10;"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "emit file content/file is deleted and created as part of change", - commandLineArgs: ["-w"], - sys: () => { - const projectLocation = "/home/username/project"; - const file: File = { - path: `${projectLocation}/app/file.ts`, - content: "var a = 10;" - }; - const configFile: File = { - path: `${projectLocation}/tsconfig.json`, - content: JSON.stringify({ - include: [ - "app/**/*.ts" - ] - }) - }; - const files = [file, configFile, libFile]; - return createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true }); + verifyTscWatchEmit({ + subScenario: "should detect removed code file", + getAdditionalFileOrFolder: () => [{ + path: "/a/b/referenceFile1.ts", + content: `/// +export var x = Foo();` + }], + firstReloadFileList: [ts.tscWatch.libFile.path, "/a/b/referenceFile1.ts", moduleFile1Path, configFilePath], + changes: [ + { + caption: "delete moduleFile1", + change: sys => sys.deleteFile(moduleFile1Path), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + verifyTscWatchEmit({ + subScenario: "should detect non existing code file", + getAdditionalFileOrFolder: () => [{ + path: "/a/b/referenceFile1.ts", + content: `/// +export var x = Foo();` + }], + firstReloadFileList: [ts.tscWatch.libFile.path, "/a/b/referenceFile1.ts", configFilePath], + changes: [ + { + caption: "edit refereceFile1", + change: sys => sys.appendFile("/a/b/referenceFile1.ts", "export var yy = Foo();"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "file is deleted and then created to modify content", - change: sys => sys.appendFile("/home/username/project/app/file.ts", "\nvar b = 10;", { invokeFileDeleteCreateAsPartInsteadOfChange: true }), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + { + caption: "create moduleFile2", + change: sys => sys.writeFile(moduleFile2Path, "export var Foo4 = 10;"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] }); +}); - describe("unittests:: tsc-watch:: emit with when module emit is specified as node", () => { - verifyTscWatch({ +describe("unittests:: tsc-watch:: emit file content", () => { + function verifyNewLine(subScenario: string, newLine: string) { + ts.tscWatch.verifyTscWatch({ scenario, - subScenario: "when module emit is specified as node/when instead of filechanged recursive directory watcher is invoked", - commandLineArgs: ["--w", "--p", "/a/rootFolder/project/tsconfig.json"], - sys: () => { - const configFile: File = { - path: "/a/rootFolder/project/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - module: "none", - allowJs: true, - outDir: "Static/scripts/" - }, - include: [ - "Scripts/**/*" - ], - }) - }; - const file1: File = { - path: "/a/rootFolder/project/Scripts/TypeScript.ts", - content: "var z = 10;" - }; - const file2: File = { - path: "/a/rootFolder/project/Scripts/Javascript.js", - content: "var zz = 10;" - }; - return createWatchedSystem([configFile, file1, file2, libFile]); - }, + subScenario: `emit file content/${subScenario}`, + commandLineArgs: ["--w", "/a/app.ts"], + sys: () => ts.tscWatch.createWatchedSystem( + [ + { + path: "/a/app.ts", + content: ["var x = 1;", "var y = 2;"].join(newLine) + }, + ts.tscWatch.libFile + ], + { newLine } + ), changes: [ { - caption: "Modify typescript file", - change: sys => sys.modifyFile( - "/a/rootFolder/project/Scripts/TypeScript.ts", - "var zz30 = 100;", - { invokeDirectoryWatcherInsteadOfFileChanged: true }, - ), - timeouts: runQueuedTimeoutCallbacks, + caption: "Append a line", + change: sys => sys.appendFile("/a/app.ts", newLine + "var z = 3;"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, } ], }); + } + verifyNewLine("handles new lines lineFeed", "\n"); + verifyNewLine("handles new lines carriageReturn lineFeed", "\r\n"); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "emit file content/should emit specified file", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export function Foo() { return 10; }` + }; + + const file2 = { + path: "/a/b/f2.ts", + content: `import {Foo} from "./f1"; export let y = Foo();` + }; + + const file3 = { + path: "/a/b/f3.ts", + content: `import {y} from "./f2"; let x = y;` + }; + + const configFile = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + return ts.tscWatch.createWatchedSystem([file1, file2, file3, configFile, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "Append content to f1", + change: sys => sys.appendFile("/a/b/f1.ts", "export function foo2() { return 2; }"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Again Append content to f1", + change: sys => sys.appendFile("/a/b/f1.ts", "export function fooN() { return 2; }"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ], + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "emit file content/elides const enums correctly in incremental compilation", + commandLineArgs: ["-w", "/user/someone/projects/myproject/file3.ts"], + sys: () => { + const currentDirectory = "/user/someone/projects/myproject"; + const file1: ts.tscWatch.File = { + path: `${currentDirectory}/file1.ts`, + content: "export const enum E1 { V = 1 }" + }; + const file2: ts.tscWatch.File = { + path: `${currentDirectory}/file2.ts`, + content: `import { E1 } from "./file1"; export const enum E2 { V = E1.V }` + }; + const file3: ts.tscWatch.File = { + path: `${currentDirectory}/file3.ts`, + content: `import { E2 } from "./file2"; const v: E2 = E2.V;` + }; + return ts.tscWatch.createWatchedSystem([file1, file2, file3, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "Append content to file3", + change: sys => sys.appendFile("/user/someone/projects/myproject/file3.ts", "function foo2() { return 2; }"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ], + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "emit file content/file is deleted and created as part of change", + commandLineArgs: ["-w"], + sys: () => { + const projectLocation = "/home/username/project"; + const file: ts.tscWatch.File = { + path: `${projectLocation}/app/file.ts`, + content: "var a = 10;" + }; + const configFile: ts.tscWatch.File = { + path: `${projectLocation}/tsconfig.json`, + content: JSON.stringify({ + include: [ + "app/**/*.ts" + ] + }) + }; + const files = [file, configFile, ts.tscWatch.libFile]; + return ts.tscWatch.createWatchedSystem(files, { currentDirectory: projectLocation, useCaseSensitiveFileNames: true }); + }, + changes: [ + { + caption: "file is deleted and then created to modify content", + change: sys => sys.appendFile("/home/username/project/app/file.ts", "\nvar b = 10;", { invokeFileDeleteCreateAsPartInsteadOfChange: true }), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); +}); + +describe("unittests:: tsc-watch:: emit with when module emit is specified as node", () => { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "when module emit is specified as node/when instead of filechanged recursive directory watcher is invoked", + commandLineArgs: ["--w", "--p", "/a/rootFolder/project/tsconfig.json"], + sys: () => { + const configFile: ts.tscWatch.File = { + path: "/a/rootFolder/project/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + module: "none", + allowJs: true, + outDir: "Static/scripts/" + }, + include: [ + "Scripts/**/*" + ], + }) + }; + const file1: ts.tscWatch.File = { + path: "/a/rootFolder/project/Scripts/TypeScript.ts", + content: "var z = 10;" + }; + const file2: ts.tscWatch.File = { + path: "/a/rootFolder/project/Scripts/Javascript.js", + content: "var zz = 10;" + }; + return ts.tscWatch.createWatchedSystem([configFile, file1, file2, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "Modify typescript file", + change: sys => sys.modifyFile( + "/a/rootFolder/project/Scripts/TypeScript.ts", + "var zz30 = 100;", + { invokeDirectoryWatcherInsteadOfFileChanged: true }, + ), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ], }); -} +}); diff --git a/src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts b/src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts index 789c1d85d9427..40afb4bce56a7 100644 --- a/src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts +++ b/src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts @@ -1,176 +1,177 @@ -namespace ts.tscWatch { - describe("unittests:: tsc-watch:: Emit times and Error updates in builder after program changes", () => { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: `{}` - }; - interface VerifyEmitAndErrorUpdates { - subScenario: string - files: () => File[]; - currentDirectory?: string; - changes: TscWatchCompileChange[]; - } - function verifyEmitAndErrorUpdates({ - subScenario, - files, - currentDirectory, +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsc-watch:: Emit times and Error updates in builder after program changes", () => { + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: `{}` + }; + interface VerifyEmitAndErrorUpdates { + subScenario: string + files: () => ts.tscWatch.File[]; + currentDirectory?: string; + changes: ts.tscWatch.TscWatchCompileChange[]; + } + function verifyEmitAndErrorUpdates({ + subScenario, + files, + currentDirectory, + changes, + }: VerifyEmitAndErrorUpdates) { + ts.tscWatch.verifyTscWatch({ + scenario: "emitAndErrorUpdates", + subScenario: `default/${subScenario}`, + commandLineArgs: ["--w"], + sys: () => ts.tscWatch.createWatchedSystem( + files(), + { currentDirectory: currentDirectory || ts.tscWatch.projectRoot } + ), changes, - }: VerifyEmitAndErrorUpdates) { - verifyTscWatch({ - scenario: "emitAndErrorUpdates", - subScenario: `default/${subScenario}`, - commandLineArgs: ["--w"], - sys: () => createWatchedSystem( - files(), - { currentDirectory: currentDirectory || projectRoot } - ), - changes, - baselineIncremental: true - }); + baselineIncremental: true + }); - verifyTscWatch({ - scenario: "emitAndErrorUpdates", - subScenario: `defaultAndD/${subScenario}`, - commandLineArgs: ["--w", "--d"], - sys: () => createWatchedSystem( - files(), - { currentDirectory: currentDirectory || projectRoot } - ), - changes, - baselineIncremental: true - }); + ts.tscWatch.verifyTscWatch({ + scenario: "emitAndErrorUpdates", + subScenario: `defaultAndD/${subScenario}`, + commandLineArgs: ["--w", "--d"], + sys: () => ts.tscWatch.createWatchedSystem( + files(), + { currentDirectory: currentDirectory || ts.tscWatch.projectRoot } + ), + changes, + baselineIncremental: true + }); - verifyTscWatch({ - scenario: "emitAndErrorUpdates", - subScenario: `isolatedModules/${subScenario}`, - commandLineArgs: ["--w", "--isolatedModules"], - sys: () => createWatchedSystem( - files(), - { currentDirectory: currentDirectory || projectRoot } - ), - changes, - baselineIncremental: true - }); + ts.tscWatch.verifyTscWatch({ + scenario: "emitAndErrorUpdates", + subScenario: `isolatedModules/${subScenario}`, + commandLineArgs: ["--w", "--isolatedModules"], + sys: () => ts.tscWatch.createWatchedSystem( + files(), + { currentDirectory: currentDirectory || ts.tscWatch.projectRoot } + ), + changes, + baselineIncremental: true + }); - verifyTscWatch({ - scenario: "emitAndErrorUpdates", - subScenario: `isolatedModulesAndD/${subScenario}`, - commandLineArgs: ["--w", "--isolatedModules", "--d"], - sys: () => createWatchedSystem( - files(), - { currentDirectory: currentDirectory || projectRoot } - ), - changes, - baselineIncremental: true - }); + ts.tscWatch.verifyTscWatch({ + scenario: "emitAndErrorUpdates", + subScenario: `isolatedModulesAndD/${subScenario}`, + commandLineArgs: ["--w", "--isolatedModules", "--d"], + sys: () => ts.tscWatch.createWatchedSystem( + files(), + { currentDirectory: currentDirectory || ts.tscWatch.projectRoot } + ), + changes, + baselineIncremental: true + }); - verifyTscWatch({ - scenario: "emitAndErrorUpdates", - subScenario: `assumeChangesOnlyAffectDirectDependencies/${subScenario}`, - commandLineArgs: ["--w", "--assumeChangesOnlyAffectDirectDependencies"], - sys: () => createWatchedSystem( - files(), - { currentDirectory: currentDirectory || projectRoot } - ), - changes, - baselineIncremental: true - }); + ts.tscWatch.verifyTscWatch({ + scenario: "emitAndErrorUpdates", + subScenario: `assumeChangesOnlyAffectDirectDependencies/${subScenario}`, + commandLineArgs: ["--w", "--assumeChangesOnlyAffectDirectDependencies"], + sys: () => ts.tscWatch.createWatchedSystem( + files(), + { currentDirectory: currentDirectory || ts.tscWatch.projectRoot } + ), + changes, + baselineIncremental: true + }); - verifyTscWatch({ - scenario: "emitAndErrorUpdates", - subScenario: `assumeChangesOnlyAffectDirectDependenciesAndD/${subScenario}`, - commandLineArgs: ["--w", "--assumeChangesOnlyAffectDirectDependencies", "--d"], - sys: () => createWatchedSystem( - files(), - { currentDirectory: currentDirectory || projectRoot } - ), - changes, - baselineIncremental: true - }); - } + ts.tscWatch.verifyTscWatch({ + scenario: "emitAndErrorUpdates", + subScenario: `assumeChangesOnlyAffectDirectDependenciesAndD/${subScenario}`, + commandLineArgs: ["--w", "--assumeChangesOnlyAffectDirectDependencies", "--d"], + sys: () => ts.tscWatch.createWatchedSystem( + files(), + { currentDirectory: currentDirectory || ts.tscWatch.projectRoot } + ), + changes, + baselineIncremental: true + }); + } - describe("deep import changes", () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `import {B} from './b'; + describe("deep import changes", () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `import {B} from './b'; declare var console: any; let b = new B(); console.log(b.c.d);` - }; + }; - function verifyDeepImportChange(subScenario: string, bFile: File, cFile: File) { - verifyEmitAndErrorUpdates({ - subScenario: `deepImportChanges/${subScenario}`, - files: () => [aFile, bFile, cFile, config, libFile], - changes: [ - { - caption: "Rename property d to d2 of class C to initialize signatures", - change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property d2 to d of class C to revert back to original text", - change: sys => sys.writeFile(cFile.path, cFile.content.replace("d2", "d")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property d to d2 of class C", - change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); - } - describe("updates errors when deep import file changes", () => { - const bFile: File = { - path: `${projectRoot}/b.ts`, - content: `import {C} from './c'; + function verifyDeepImportChange(subScenario: string, bFile: ts.tscWatch.File, cFile: ts.tscWatch.File) { + verifyEmitAndErrorUpdates({ + subScenario: `deepImportChanges/${subScenario}`, + files: () => [aFile, bFile, cFile, config, ts.tscWatch.libFile], + changes: [ + { + caption: "Rename property d to d2 of class C to initialize signatures", + change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property d2 to d of class C to revert back to original text", + change: sys => sys.writeFile(cFile.path, cFile.content.replace("d2", "d")), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property d to d2 of class C", + change: sys => sys.writeFile(cFile.path, cFile.content.replace("d", "d2")), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ], + }); + } + describe("updates errors when deep import file changes", () => { + const bFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/b.ts`, + content: `import {C} from './c'; export class B { c = new C(); }` - }; - const cFile: File = { - path: `${projectRoot}/c.ts`, - content: `export class C + }; + const cFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/c.ts`, + content: `export class C { d = 1; }` - }; - verifyDeepImportChange( - "errors for .ts change", - bFile, - cFile - ); - }); - describe("updates errors when deep import through declaration file changes", () => { - const bFile: File = { - path: `${projectRoot}/b.d.ts`, - content: `import {C} from './c'; + }; + verifyDeepImportChange( + "errors for .ts change", + bFile, + cFile + ); + }); + describe("updates errors when deep import through declaration file changes", () => { + const bFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/b.d.ts`, + content: `import {C} from './c'; export class B { c: C; }` - }; - const cFile: File = { - path: `${projectRoot}/c.d.ts`, - content: `export class C + }; + const cFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/c.d.ts`, + content: `export class C { d: number; }` - }; - verifyDeepImportChange( - "errors for .d.ts change", - bFile, - cFile - ); - }); + }; + verifyDeepImportChange( + "errors for .d.ts change", + bFile, + cFile + ); }); + }); - describe("updates errors in file not exporting a deep multilevel import that changes", () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `export interface Point { + describe("updates errors in file not exporting a deep multilevel import that changes", () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `export interface Point { name: string; c: Coords; } @@ -178,16 +179,16 @@ export interface Coords { x2: number; y: number; }` - }; - const bFile: File = { - path: `${projectRoot}/b.ts`, - content: `import { Point } from "./a"; + }; + const bFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/b.ts`, + content: `import { Point } from "./a"; export interface PointWrapper extends Point { }` - }; - const cFile: File = { - path: `${projectRoot}/c.ts`, - content: `import { PointWrapper } from "./b"; + }; + const cFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/c.ts`, + content: `import { PointWrapper } from "./b"; export function getPoint(): PointWrapper { return { name: "test", @@ -197,62 +198,62 @@ export function getPoint(): PointWrapper { } } };` - }; - const dFile: File = { - path: `${projectRoot}/d.ts`, - content: `import { getPoint } from "./c"; + }; + const dFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/d.ts`, + content: `import { getPoint } from "./c"; getPoint().c.x;` - }; - const eFile: File = { - path: `${projectRoot}/e.ts`, - content: `import "./d";` - }; - verifyEmitAndErrorUpdates({ - subScenario: "file not exporting a deep multilevel import that changes", - files: () => [aFile, bFile, cFile, dFile, eFile, config, libFile], - changes: [ - { - caption: "Rename property x2 to x of interface Coords to initialize signatures", - change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property x to x2 of interface Coords to revert back to original text", - change: sys => sys.writeFile(aFile.path, aFile.content.replace("x: number", "x2: number")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property x2 to x of interface Coords", - change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + }; + const eFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/e.ts`, + content: `import "./d";` + }; + verifyEmitAndErrorUpdates({ + subScenario: "file not exporting a deep multilevel import that changes", + files: () => [aFile, bFile, cFile, dFile, eFile, config, ts.tscWatch.libFile], + changes: [ + { + caption: "Rename property x2 to x of interface Coords to initialize signatures", + change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property x to x2 of interface Coords to revert back to original text", + change: sys => sys.writeFile(aFile.path, aFile.content.replace("x: number", "x2: number")), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property x2 to x of interface Coords", + change: sys => sys.writeFile(aFile.path, aFile.content.replace("x2", "x")), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + ] }); - describe("updates errors when file transitively exported file changes", () => { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - files: ["app.ts"], - compilerOptions: { baseUrl: "." } - }) - }; - const app: File = { - path: `${projectRoot}/app.ts`, - content: `import { Data } from "lib2/public"; + }); + describe("updates errors when file transitively exported file changes", () => { + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts"], + compilerOptions: { baseUrl: "." } + }) + }; + const app: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/app.ts`, + content: `import { Data } from "lib2/public"; export class App { public constructor() { new Data().test(); } }` - }; - const lib2Public: File = { - path: `${projectRoot}/lib2/public.ts`, - content: `export * from "./data";` - }; - const lib2Data: File = { - path: `${projectRoot}/lib2/data.ts`, - content: `import { ITest } from "lib1/public"; + }; + const lib2Public: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/lib2/public.ts`, + content: `export * from "./data";` + }; + const lib2Data: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/lib2/data.ts`, + content: `import { ITest } from "lib1/public"; export class Data { public test() { const result: ITest = { @@ -261,55 +262,55 @@ export class Data { return result; } }` - }; - const lib1Public: File = { - path: `${projectRoot}/lib1/public.ts`, - content: `export * from "./tools/public";` - }; - const lib1ToolsPublic: File = { - path: `${projectRoot}/lib1/tools/public.ts`, - content: `export * from "./tools.interface";` - }; - const lib1ToolsInterface: File = { - path: `${projectRoot}/lib1/tools/tools.interface.ts`, - content: `export interface ITest { + }; + const lib1Public: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/lib1/public.ts`, + content: `export * from "./tools/public";` + }; + const lib1ToolsPublic: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/lib1/tools/public.ts`, + content: `export * from "./tools.interface";` + }; + const lib1ToolsInterface: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/lib1/tools/tools.interface.ts`, + content: `export interface ITest { title: string; }` - }; + }; - function verifyTransitiveExports(subScenario: string, files: readonly File[]) { - verifyEmitAndErrorUpdates({ - subScenario: `transitive exports/${subScenario}`, - files: () => [lib1ToolsInterface, lib1ToolsPublic, app, lib2Public, lib1Public, ...files, config, libFile], - changes: [ - { - caption: "Rename property title to title2 of interface ITest to initialize signatures", - change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property title2 to title of interface ITest to revert back to original text", - change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title2", "title")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rename property title to title2 of interface ITest", - change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); - } - describe("when there are no circular import and exports", () => { - verifyTransitiveExports( - "no circular import/export", - [lib2Data] - ); + function verifyTransitiveExports(subScenario: string, files: readonly ts.tscWatch.File[]) { + verifyEmitAndErrorUpdates({ + subScenario: `transitive exports/${subScenario}`, + files: () => [lib1ToolsInterface, lib1ToolsPublic, app, lib2Public, lib1Public, ...files, config, ts.tscWatch.libFile], + changes: [ + { + caption: "Rename property title to title2 of interface ITest to initialize signatures", + change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property title2 to title of interface ITest to revert back to original text", + change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title2", "title")), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "Rename property title to title2 of interface ITest", + change: sys => sys.writeFile(lib1ToolsInterface.path, lib1ToolsInterface.content.replace("title", "title2")), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ] }); - describe("when there are circular import and exports", () => { - const lib2Data: File = { - path: `${projectRoot}/lib2/data.ts`, - content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2"; + } + describe("when there are no circular import and exports", () => { + verifyTransitiveExports( + "no circular import/export", + [lib2Data] + ); + }); + describe("when there are circular import and exports", () => { + const lib2Data: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/lib2/data.ts`, + content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2"; export class Data { public dat?: Data2; public test() { const result: ITest = { @@ -318,55 +319,54 @@ export class Data { return result; } }` - }; - const lib2Data2: File = { - path: `${projectRoot}/lib2/data2.ts`, - content: `import { Data } from "./data"; + }; + const lib2Data2: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/lib2/data2.ts`, + content: `import { Data } from "./data"; export class Data2 { public dat?: Data; }` - }; - verifyTransitiveExports( - "yes circular import/exports", - [lib2Data, lib2Data2] - ); - }); + }; + verifyTransitiveExports( + "yes circular import/exports", + [lib2Data, lib2Data2] + ); }); + }); - describe("with noEmitOnError", () => { - function change(caption: string, content: string): TscWatchCompileChange { - return { - caption, - change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content), - // build project - timeouts: checkSingleTimeoutQueueLengthAndRun - }; - } - const noChange: TscWatchCompileChange = { - caption: "No change", - change: sys => sys.writeFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!), + describe("with noEmitOnError", () => { + function change(caption: string, content: string): ts.tscWatch.TscWatchCompileChange { + return { + caption, + change: sys => sys.writeFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, content), // build project - timeouts: checkSingleTimeoutQueueLengthAndRun, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun }; - verifyEmitAndErrorUpdates({ - subScenario: "with noEmitOnError", - currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError`, - files: () => ["shared/types/db.ts", "src/main.ts", "src/other.ts", "tsconfig.json"] - .map(f => TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)).concat({ path: libFile.path, content: libContent }), - changes: [ - noChange, - change("Fix Syntax error", `import { A } from "../shared/types/db"; + } + const noChange: ts.tscWatch.TscWatchCompileChange = { + caption: "No change", + change: sys => sys.writeFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`, sys.readFile(`${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError/src/main.ts`)!), + // build project + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }; + verifyEmitAndErrorUpdates({ + subScenario: "with noEmitOnError", + currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/noEmitOnError`, + files: () => ["shared/types/db.ts", "src/main.ts", "src/other.ts", "tsconfig.json"] + .map(f => ts.TestFSWithWatch.getTsBuildProjectFile("noEmitOnError", f)).concat({ path: ts.tscWatch.libFile.path, content: ts.libContent }), + changes: [ + noChange, + change("Fix Syntax error", `import { A } from "../shared/types/db"; const a = { lastName: 'sdsd' };`), - change("Semantic Error", `import { A } from "../shared/types/db"; + change("Semantic Error", `import { A } from "../shared/types/db"; const a: string = 10;`), - noChange, - change("Fix Semantic Error", `import { A } from "../shared/types/db"; + noChange, + change("Fix Semantic Error", `import { A } from "../shared/types/db"; const a: string = "hello";`), - noChange, - ], - }); + noChange, + ], }); }); -} +}); diff --git a/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts index d00ea144ea9d4..f301dc7708b94 100644 --- a/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts +++ b/src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts @@ -1,94 +1,96 @@ -namespace ts.tscWatch { - describe("unittests:: tsc-watch:: forceConsistentCasingInFileNames", () => { - const loggerFile: File = { - path: `${projectRoot}/logger.ts`, - content: `export class logger { }` - }; - const anotherFile: File = { - path: `${projectRoot}/another.ts`, - content: `import { logger } from "./logger"; new logger();` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { forceConsistentCasingInFileNames: true } - }) - }; +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; - function verifyConsistentFileNames({ subScenario, changes }: { subScenario: string; changes: TscWatchCompileChange[]; }) { - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario, - commandLineArgs: ["--w", "--p", tsconfig.path], - sys: () => createWatchedSystem([loggerFile, anotherFile, tsconfig, libFile]), - changes - }); - } +describe("unittests:: tsc-watch:: forceConsistentCasingInFileNames", () => { + const loggerFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/logger.ts`, + content: `export class logger { }` + }; + const anotherFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/another.ts`, + content: `import { logger } from "./logger"; new logger();` + }; + const tsconfig: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { forceConsistentCasingInFileNames: true } + }) + }; - verifyConsistentFileNames({ - subScenario: "when changing module name with different casing", - changes: [ - { - caption: "Change module name from logger to Logger", - change: sys => sys.writeFile(anotherFile.path, anotherFile.content.replace("./logger", "./Logger")), - timeouts: runQueuedTimeoutCallbacks, - } - ] + function verifyConsistentFileNames({ subScenario, changes }: { subScenario: string; changes: ts.tscWatch.TscWatchCompileChange[]; }) { + ts.tscWatch.verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario, + commandLineArgs: ["--w", "--p", tsconfig.path], + sys: () => ts.tscWatch.createWatchedSystem([loggerFile, anotherFile, tsconfig, ts.tscWatch.libFile]), + changes }); + } - verifyConsistentFileNames({ - subScenario: "when renaming file with different casing", - changes: [ - { - caption: "Change name of file from logger to Logger", - change: sys => sys.renameFile(loggerFile.path, `${projectRoot}/Logger.ts`), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + verifyConsistentFileNames({ + subScenario: "when changing module name with different casing", + changes: [ + { + caption: "Change module name from logger to Logger", + change: sys => sys.writeFile(anotherFile.path, anotherFile.content.replace("./logger", "./Logger")), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ] + }); - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario: "when relative information file location changes", - commandLineArgs: ["--w", "--p", ".", "--explainFiles"], - sys: () => { - const moduleA: File = { - path: `${projectRoot}/moduleA.ts`, - content: `import a = require("./ModuleC")` - }; - const moduleB: File = { - path: `${projectRoot}/moduleB.ts`, - content: `import a = require("./moduleC")` - }; - const moduleC: File = { - path: `${projectRoot}/moduleC.ts`, - content: `export const x = 10;` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) - }; - return createWatchedSystem([moduleA, moduleB, moduleC, libFile, tsconfig], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "Prepend a line to moduleA", - change: sys => sys.prependFile(`${projectRoot}/moduleA.ts`, `// some comment + verifyConsistentFileNames({ + subScenario: "when renaming file with different casing", + changes: [ + { + caption: "Change name of file from logger to Logger", + change: sys => sys.renameFile(loggerFile.path, `${ts.tscWatch.projectRoot}/Logger.ts`), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ] + }); + + ts.tscWatch.verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario: "when relative information file location changes", + commandLineArgs: ["--w", "--p", ".", "--explainFiles"], + sys: () => { + const moduleA: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/moduleA.ts`, + content: `import a = require("./ModuleC")` + }; + const moduleB: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/moduleB.ts`, + content: `import a = require("./moduleC")` + }; + const moduleC: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/moduleC.ts`, + content: `export const x = 10;` + }; + const tsconfig: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) + }; + return ts.tscWatch.createWatchedSystem([moduleA, moduleB, moduleC, ts.tscWatch.libFile, tsconfig], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Prepend a line to moduleA", + change: sys => sys.prependFile(`${ts.tscWatch.projectRoot}/moduleA.ts`, `// some comment `), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ], + }); - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario: "jsxImportSource option changed", - commandLineArgs: ["--w", "--p", ".", "--explainFiles"], - sys: () => createWatchedSystem([ - libFile, - { - path: `${projectRoot}/node_modules/react/Jsx-runtime/index.d.ts`, - content: `export namespace JSX { + ts.tscWatch.verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario: "jsxImportSource option changed", + commandLineArgs: ["--w", "--p", ".", "--explainFiles"], + sys: () => ts.tscWatch.createWatchedSystem([ + ts.tscWatch.libFile, + { + path: `${ts.tscWatch.projectRoot}/node_modules/react/Jsx-runtime/index.d.ts`, + content: `export namespace JSX { interface Element {} interface IntrinsicElements { div: { @@ -100,262 +102,261 @@ export function jsx(...args: any[]): void; export function jsxs(...args: any[]): void; export const Fragment: unique symbol; `, - }, - { - path: `${projectRoot}/node_modules/react/package.json`, - content: JSON.stringify({ name: "react", version: "0.0.1" }) - }, - { - path: `${projectRoot}/index.tsx`, - content: `export const App = () =>
;` - }, - { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { jsx: "react-jsx", jsxImportSource: "react", forceConsistentCasingInFileNames: true }, - files: ["node_modules/react/Jsx-Runtime/index.d.ts", "index.tsx"] - }) - } - ], { currentDirectory: projectRoot }), - changes: emptyArray, - }); + }, + { + path: `${ts.tscWatch.projectRoot}/node_modules/react/package.json`, + content: JSON.stringify({ name: "react", version: "0.0.1" }) + }, + { + path: `${ts.tscWatch.projectRoot}/index.tsx`, + content: `export const App = () =>
;` + }, + { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { jsx: "react-jsx", jsxImportSource: "react", forceConsistentCasingInFileNames: true }, + files: ["node_modules/react/Jsx-Runtime/index.d.ts", "index.tsx"] + }) + } + ], { currentDirectory: ts.tscWatch.projectRoot }), + changes: ts.emptyArray, + }); - function verifyWindowsStyleRoot(subScenario: string, windowsStyleRoot: string, projectRootRelative: string) { - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario, - commandLineArgs: ["--w", "--p", `${windowsStyleRoot}/${projectRootRelative}`, "--explainFiles"], - sys: () => { - const moduleA: File = { - path: `${windowsStyleRoot}/${projectRootRelative}/a.ts`, - content: ` + function verifyWindowsStyleRoot(subScenario: string, windowsStyleRoot: string, projectRootRelative: string) { + ts.tscWatch.verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario, + commandLineArgs: ["--w", "--p", `${windowsStyleRoot}/${projectRootRelative}`, "--explainFiles"], + sys: () => { + const moduleA: ts.tscWatch.File = { + path: `${windowsStyleRoot}/${projectRootRelative}/a.ts`, + content: ` export const a = 1; export const b = 2; ` - }; - const moduleB: File = { - path: `${windowsStyleRoot}/${projectRootRelative}/b.ts`, - content: ` + }; + const moduleB: ts.tscWatch.File = { + path: `${windowsStyleRoot}/${projectRootRelative}/b.ts`, + content: ` import { a } from "${windowsStyleRoot.toLocaleUpperCase()}/${projectRootRelative}/a" import { b } from "${windowsStyleRoot.toLocaleLowerCase()}/${projectRootRelative}/a" a;b; ` - }; - const tsconfig: File = { - path: `${windowsStyleRoot}/${projectRootRelative}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) - }; - return createWatchedSystem([moduleA, moduleB, libFile, tsconfig], { windowsStyleRoot, useCaseSensitiveFileNames: false }); - }, - changes: [ - { - caption: "Prepend a line to moduleA", - change: sys => sys.prependFile(`${windowsStyleRoot}/${projectRootRelative}/a.ts`, `// some comment + }; + const tsconfig: ts.tscWatch.File = { + path: `${windowsStyleRoot}/${projectRootRelative}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) + }; + return ts.tscWatch.createWatchedSystem([moduleA, moduleB, ts.tscWatch.libFile, tsconfig], { windowsStyleRoot, useCaseSensitiveFileNames: false }); + }, + changes: [ + { + caption: "Prepend a line to moduleA", + change: sys => sys.prependFile(`${windowsStyleRoot}/${projectRootRelative}/a.ts`, `// some comment `), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); - } + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ], + }); + } - verifyWindowsStyleRoot("when Windows-style drive root is lowercase", "c:/", "project"); - verifyWindowsStyleRoot("when Windows-style drive root is uppercase", "C:/", "project"); + verifyWindowsStyleRoot("when Windows-style drive root is lowercase", "c:/", "project"); + verifyWindowsStyleRoot("when Windows-style drive root is uppercase", "C:/", "project"); - function verifyFileSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) { - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario, - commandLineArgs: ["--w", "--p", ".", "--explainFiles"], - sys: () => { - const moduleA: File = { + function verifyFileSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) { + ts.tscWatch.verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario, + commandLineArgs: ["--w", "--p", ".", "--explainFiles"], + sys: () => { + const moduleA: ts.tscWatch.File = { - path: diskPath, - content: ` + path: diskPath, + content: ` export const a = 1; export const b = 2; ` - }; - const symlinkA: SymLink = { - path: `${projectRoot}/link.ts`, - symLink: targetPath, - }; - const moduleB: File = { - path: `${projectRoot}/b.ts`, - content: ` + }; + const symlinkA: ts.tscWatch.SymLink = { + path: `${ts.tscWatch.projectRoot}/link.ts`, + symLink: targetPath, + }; + const moduleB: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/b.ts`, + content: ` import { a } from "${importedPath}"; import { b } from "./link"; a;b; ` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) - }; - return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "Prepend a line to moduleA", - change: sys => sys.prependFile(diskPath, `// some comment + }; + const tsconfig: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) + }; + return ts.tscWatch.createWatchedSystem([moduleA, symlinkA, moduleB, ts.tscWatch.libFile, tsconfig], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Prepend a line to moduleA", + change: sys => sys.prependFile(diskPath, `// some comment `), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); - } + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ], + }); + } - verifyFileSymlink("when both file symlink target and import match disk", `${projectRoot}/XY.ts`, `${projectRoot}/XY.ts`, `./XY`); - verifyFileSymlink("when file symlink target matches disk but import does not", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./XY`); - verifyFileSymlink("when import matches disk but file symlink target does not", `${projectRoot}/XY.ts`, `${projectRoot}/XY.ts`, `./Xy`); - verifyFileSymlink("when import and file symlink target agree but do not match disk", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./Xy`); - verifyFileSymlink("when import, file symlink target, and disk are all different", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./yX`); + verifyFileSymlink("when both file symlink target and import match disk", `${ts.tscWatch.projectRoot}/XY.ts`, `${ts.tscWatch.projectRoot}/XY.ts`, `./XY`); + verifyFileSymlink("when file symlink target matches disk but import does not", `${ts.tscWatch.projectRoot}/XY.ts`, `${ts.tscWatch.projectRoot}/Xy.ts`, `./XY`); + verifyFileSymlink("when import matches disk but file symlink target does not", `${ts.tscWatch.projectRoot}/XY.ts`, `${ts.tscWatch.projectRoot}/XY.ts`, `./Xy`); + verifyFileSymlink("when import and file symlink target agree but do not match disk", `${ts.tscWatch.projectRoot}/XY.ts`, `${ts.tscWatch.projectRoot}/Xy.ts`, `./Xy`); + verifyFileSymlink("when import, file symlink target, and disk are all different", `${ts.tscWatch.projectRoot}/XY.ts`, `${ts.tscWatch.projectRoot}/Xy.ts`, `./yX`); - function verifyDirSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) { - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario, - commandLineArgs: ["--w", "--p", ".", "--explainFiles"], - sys: () => { - const moduleA: File = { + function verifyDirSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) { + ts.tscWatch.verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario, + commandLineArgs: ["--w", "--p", ".", "--explainFiles"], + sys: () => { + const moduleA: ts.tscWatch.File = { - path: `${diskPath}/a.ts`, - content: ` + path: `${diskPath}/a.ts`, + content: ` export const a = 1; export const b = 2; ` - }; - const symlinkA: SymLink = { - path: `${projectRoot}/link`, - symLink: targetPath, - }; - const moduleB: File = { - path: `${projectRoot}/b.ts`, - content: ` + }; + const symlinkA: ts.tscWatch.SymLink = { + path: `${ts.tscWatch.projectRoot}/link`, + symLink: targetPath, + }; + const moduleB: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/b.ts`, + content: ` import { a } from "${importedPath}/a"; import { b } from "./link/a"; a;b; ` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - // Use outFile because otherwise the real and linked files will have the same output path - content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true, outFile: "out.js", module: "system" } }) - }; - return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "Prepend a line to moduleA", - change: sys => sys.prependFile(`${diskPath}/a.ts`, `// some comment + }; + const tsconfig: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + // Use outFile because otherwise the real and linked files will have the same output path + content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true, outFile: "out.js", module: "system" } }) + }; + return ts.tscWatch.createWatchedSystem([moduleA, symlinkA, moduleB, ts.tscWatch.libFile, tsconfig], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Prepend a line to moduleA", + change: sys => sys.prependFile(`${diskPath}/a.ts`, `// some comment `), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); - } + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ], + }); + } - verifyDirSymlink("when both directory symlink target and import match disk", `${projectRoot}/XY`, `${projectRoot}/XY`, `./XY`); - verifyDirSymlink("when directory symlink target matches disk but import does not", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./XY`); - verifyDirSymlink("when import matches disk but directory symlink target does not", `${projectRoot}/XY`, `${projectRoot}/XY`, `./Xy`); - verifyDirSymlink("when import and directory symlink target agree but do not match disk", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./Xy`); - verifyDirSymlink("when import, directory symlink target, and disk are all different", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./yX`); + verifyDirSymlink("when both directory symlink target and import match disk", `${ts.tscWatch.projectRoot}/XY`, `${ts.tscWatch.projectRoot}/XY`, `./XY`); + verifyDirSymlink("when directory symlink target matches disk but import does not", `${ts.tscWatch.projectRoot}/XY`, `${ts.tscWatch.projectRoot}/Xy`, `./XY`); + verifyDirSymlink("when import matches disk but directory symlink target does not", `${ts.tscWatch.projectRoot}/XY`, `${ts.tscWatch.projectRoot}/XY`, `./Xy`); + verifyDirSymlink("when import and directory symlink target agree but do not match disk", `${ts.tscWatch.projectRoot}/XY`, `${ts.tscWatch.projectRoot}/Xy`, `./Xy`); + verifyDirSymlink("when import, directory symlink target, and disk are all different", `${ts.tscWatch.projectRoot}/XY`, `${ts.tscWatch.projectRoot}/Xy`, `./yX`); - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario: "with nodeNext resolution", - commandLineArgs: ["--w", "--explainFiles"], - sys: () => createWatchedSystem({ - "/Users/name/projects/web/src/bin.ts": `import { foo } from "yargs";`, - "/Users/name/projects/web/node_modules/@types/yargs/index.d.ts": "export function foo(): void;", - "/Users/name/projects/web/node_modules/@types/yargs/index.d.mts": "export function foo(): void;", - "/Users/name/projects/web/node_modules/@types/yargs/package.json": JSON.stringify({ - name: "yargs", - version: "17.0.12", - exports: { - ".": { - types: { - import: "./index.d.mts", - default: "./index.d.ts" - } - }, - } - }), - "/Users/name/projects/web/tsconfig.json": JSON.stringify({ - compilerOptions: { - moduleResolution: "nodenext", - forceConsistentCasingInFileNames: true, - traceResolution: true, - } - }), - [libFile.path]: libFile.content, - }, { currentDirectory: "/Users/name/projects/web" }), - changes: emptyArray, - }); + ts.tscWatch.verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario: "with nodeNext resolution", + commandLineArgs: ["--w", "--explainFiles"], + sys: () => ts.tscWatch.createWatchedSystem({ + "/Users/name/projects/web/src/bin.ts": `import { foo } from "yargs";`, + "/Users/name/projects/web/node_modules/@types/yargs/index.d.ts": "export function foo(): void;", + "/Users/name/projects/web/node_modules/@types/yargs/index.d.mts": "export function foo(): void;", + "/Users/name/projects/web/node_modules/@types/yargs/package.json": JSON.stringify({ + name: "yargs", + version: "17.0.12", + exports: { + ".": { + types: { + import: "./index.d.mts", + default: "./index.d.ts" + } + }, + } + }), + "/Users/name/projects/web/tsconfig.json": JSON.stringify({ + compilerOptions: { + moduleResolution: "nodenext", + forceConsistentCasingInFileNames: true, + traceResolution: true, + } + }), + [ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content, + }, { currentDirectory: "/Users/name/projects/web" }), + changes: ts.emptyArray, + }); - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario: "self name package reference", - commandLineArgs: ["-w", "--explainFiles"], - sys: () => createWatchedSystem({ - "/Users/name/projects/web/package.json": JSON.stringify({ - name: "@this/package", - type: "module", - exports: { - ".": "./dist/index.js" - } - }), - "/Users/name/projects/web/index.ts": Utils.dedent` + ts.tscWatch.verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario: "self name package reference", + commandLineArgs: ["-w", "--explainFiles"], + sys: () => ts.tscWatch.createWatchedSystem({ + "/Users/name/projects/web/package.json": JSON.stringify({ + name: "@this/package", + type: "module", + exports: { + ".": "./dist/index.js" + } + }), + "/Users/name/projects/web/index.ts": Utils.dedent` import * as me from "@this/package"; me.thing(); export function thing(): void {} `, - "/Users/name/projects/web/tsconfig.json": JSON.stringify({ - compilerOptions: { - module: "nodenext", - outDir: "./dist", - declarationDir: "./types", - composite: true, - forceConsistentCasingInFileNames: true, - traceResolution: true, - } - }), - "/a/lib/lib.esnext.full.d.ts": libFile.content, - }, { currentDirectory: "/Users/name/projects/web" }), - changes: emptyArray, - }); + "/Users/name/projects/web/tsconfig.json": JSON.stringify({ + compilerOptions: { + module: "nodenext", + outDir: "./dist", + declarationDir: "./types", + composite: true, + forceConsistentCasingInFileNames: true, + traceResolution: true, + } + }), + "/a/lib/lib.esnext.full.d.ts": ts.tscWatch.libFile.content, + }, { currentDirectory: "/Users/name/projects/web" }), + changes: ts.emptyArray, + }); - verifyTscWatch({ - scenario: "forceConsistentCasingInFileNames", - subScenario: "package json is looked up for file", - commandLineArgs: ["-w", "--explainFiles"], - sys: () => createWatchedSystem({ - "/Users/name/projects/lib-boilerplate/package.json": JSON.stringify({ - name: "lib-boilerplate", - version: "0.0.2", - type: "module", - exports: "./src/index.ts", - }), - "/Users/name/projects/lib-boilerplate/src/index.ts": Utils.dedent` + ts.tscWatch.verifyTscWatch({ + scenario: "forceConsistentCasingInFileNames", + subScenario: "package json is looked up for file", + commandLineArgs: ["-w", "--explainFiles"], + sys: () => ts.tscWatch.createWatchedSystem({ + "/Users/name/projects/lib-boilerplate/package.json": JSON.stringify({ + name: "lib-boilerplate", + version: "0.0.2", + type: "module", + exports: "./src/index.ts", + }), + "/Users/name/projects/lib-boilerplate/src/index.ts": Utils.dedent` export function thing(): void {} `, - "/Users/name/projects/lib-boilerplate/test/basic.spec.ts": Utils.dedent` + "/Users/name/projects/lib-boilerplate/test/basic.spec.ts": Utils.dedent` import { thing } from 'lib-boilerplate' `, - "/Users/name/projects/lib-boilerplate/tsconfig.json": JSON.stringify({ - compilerOptions: { - module: "node16", - target: "es2021", - forceConsistentCasingInFileNames: true, - traceResolution: true, - } - }), - "/a/lib/lib.es2021.full.d.ts": libFile.content, - }, { currentDirectory: "/Users/name/projects/lib-boilerplate" }), - changes: emptyArray, - }); + "/Users/name/projects/lib-boilerplate/tsconfig.json": JSON.stringify({ + compilerOptions: { + module: "node16", + target: "es2021", + forceConsistentCasingInFileNames: true, + traceResolution: true, + } + }), + "/a/lib/lib.es2021.full.d.ts": ts.tscWatch.libFile.content, + }, { currentDirectory: "/Users/name/projects/lib-boilerplate" }), + changes: ts.emptyArray, }); -} +}); diff --git a/src/testRunner/unittests/tscWatch/helpers.ts b/src/testRunner/unittests/tscWatch/helpers.ts index bad2dc7cf4931..c4d059b896a95 100644 --- a/src/testRunner/unittests/tscWatch/helpers.ts +++ b/src/testRunner/unittests/tscWatch/helpers.ts @@ -1,438 +1,440 @@ -namespace ts.tscWatch { - export const projects = `/user/username/projects`; - export const projectRoot = `${projects}/myproject`; - export import WatchedSystem = TestFSWithWatch.TestServerHost; - export type File = TestFSWithWatch.File; - export type SymLink = TestFSWithWatch.SymLink; - export import libFile = TestFSWithWatch.libFile; - export import createWatchedSystem = TestFSWithWatch.createWatchedSystem; - export import checkArray = TestFSWithWatch.checkArray; - export import checkOutputContains = TestFSWithWatch.checkOutputContains; - export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain; - - export const commonFile1: File = { - path: "/a/b/commonFile1.ts", - content: "let x = 1" - }; - export const commonFile2: File = { - path: "/a/b/commonFile2.ts", - content: "let y = 1" - }; - - export function checkProgramActualFiles(program: Program, expectedFiles: readonly string[]) { - checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles); - } - - export function getDiagnosticMessageChain(message: DiagnosticMessage, args?: (string | number)[], next?: DiagnosticMessageChain[]): DiagnosticMessageChain { - let text = getLocaleSpecificMessage(message); - if (args?.length) { - text = formatStringFromArgs(text, args); - } - return { - messageText: text, - category: message.category, - code: message.code, - next - }; - } +import * as ts from "../../_namespaces/ts"; +import * as fakes from "../../_namespaces/fakes"; +import * as Harness from "../../_namespaces/Harness"; + +export const projects = `/user/username/projects`; +export const projectRoot = `${projects}/myproject`; +export import WatchedSystem = ts.TestFSWithWatch.TestServerHost; +export type File = ts.TestFSWithWatch.File; +export type SymLink = ts.TestFSWithWatch.SymLink; +export import libFile = ts.TestFSWithWatch.libFile; +export import createWatchedSystem = ts.TestFSWithWatch.createWatchedSystem; +export import checkArray = ts.TestFSWithWatch.checkArray; +export import checkOutputContains = ts.TestFSWithWatch.checkOutputContains; +export import checkOutputDoesNotContain = ts.TestFSWithWatch.checkOutputDoesNotContain; + +export const commonFile1: File = { + path: "/a/b/commonFile1.ts", + content: "let x = 1" +}; +export const commonFile2: File = { + path: "/a/b/commonFile2.ts", + content: "let y = 1" +}; + +export function checkProgramActualFiles(program: ts.Program, expectedFiles: readonly string[]) { + checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles); +} - function isDiagnosticMessageChain(message: DiagnosticMessage | DiagnosticMessageChain): message is DiagnosticMessageChain { - return !!(message as DiagnosticMessageChain).messageText; +export function getDiagnosticMessageChain(message: ts.DiagnosticMessage, args?: (string | number)[], next?: ts.DiagnosticMessageChain[]): ts.DiagnosticMessageChain { + let text = ts.getLocaleSpecificMessage(message); + if (args?.length) { + text = ts.formatStringFromArgs(text, args); } + return { + messageText: text, + category: message.category, + code: message.code, + next + }; +} - export function getDiagnosticOfFileFrom(file: SourceFile | undefined, start: number | undefined, length: number | undefined, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { - return { - file, - start, - length, - - messageText: isDiagnosticMessageChain(message) ? - message : - getDiagnosticMessageChain(message, args).messageText, - category: message.category, - code: message.code, - }; - } +function isDiagnosticMessageChain(message: ts.DiagnosticMessage | ts.DiagnosticMessageChain): message is ts.DiagnosticMessageChain { + return !!(message as ts.DiagnosticMessageChain).messageText; +} - export function getDiagnosticWithoutFile(message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { - return getDiagnosticOfFileFrom(/*file*/ undefined, /*start*/ undefined, /*length*/ undefined, message, ...args); - } +export function getDiagnosticOfFileFrom(file: ts.SourceFile | undefined, start: number | undefined, length: number | undefined, message: ts.DiagnosticMessage | ts.DiagnosticMessageChain, ...args: (string | number)[]): ts.Diagnostic { + return { + file, + start, + length, + + messageText: isDiagnosticMessageChain(message) ? + message : + getDiagnosticMessageChain(message, args).messageText, + category: message.category, + code: message.code, + }; +} - export function getDiagnosticOfFile(file: SourceFile, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { - return getDiagnosticOfFileFrom(file, start, length, message, ...args); - } +export function getDiagnosticWithoutFile(message: ts.DiagnosticMessage | ts.DiagnosticMessageChain, ...args: (string | number)[]): ts.Diagnostic { + return getDiagnosticOfFileFrom(/*file*/ undefined, /*start*/ undefined, /*length*/ undefined, message, ...args); +} - export function getDiagnosticOfFileFromProgram(program: Program, filePath: string, start: number, length: number, message: DiagnosticMessage | DiagnosticMessageChain, ...args: (string | number)[]): Diagnostic { - return getDiagnosticOfFileFrom(program.getSourceFileByPath(toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase())), - start, length, message, ...args); - } +export function getDiagnosticOfFile(file: ts.SourceFile, start: number, length: number, message: ts.DiagnosticMessage | ts.DiagnosticMessageChain, ...args: (string | number)[]): ts.Diagnostic { + return getDiagnosticOfFileFrom(file, start, length, message, ...args); +} - export function getUnknownCompilerOption(program: Program, configFile: File, option: string) { - const quotedOption = `"${option}"`; - return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option); - } +export function getDiagnosticOfFileFromProgram(program: ts.Program, filePath: string, start: number, length: number, message: ts.DiagnosticMessage | ts.DiagnosticMessageChain, ...args: (string | number)[]): ts.Diagnostic { + return getDiagnosticOfFileFrom(program.getSourceFileByPath(ts.toPath(filePath, program.getCurrentDirectory(), s => s.toLowerCase())), + start, length, message, ...args); +} - export function getUnknownDidYouMeanCompilerOption(program: Program, configFile: File, option: string, didYouMean: string) { - const quotedOption = `"${option}"`; - return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, option, didYouMean); - } +export function getUnknownCompilerOption(program: ts.Program, configFile: File, option: string) { + const quotedOption = `"${option}"`; + return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, ts.Diagnostics.Unknown_compiler_option_0, option); +} - export function getDiagnosticModuleNotFoundOfFile(program: Program, file: File, moduleName: string) { - const quotedModuleName = `"${moduleName}"`; - return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option, moduleName); - } +export function getUnknownDidYouMeanCompilerOption(program: ts.Program, configFile: File, option: string, didYouMean: string) { + const quotedOption = `"${option}"`; + return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, ts.Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, option, didYouMean); +} - export function runQueuedTimeoutCallbacks(sys: WatchedSystem) { - sys.runQueuedTimeoutCallbacks(); - } +export function getDiagnosticModuleNotFoundOfFile(program: ts.Program, file: File, moduleName: string) { + const quotedModuleName = `"${moduleName}"`; + return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, ts.Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option, moduleName); +} - export function checkSingleTimeoutQueueLengthAndRun(sys: WatchedSystem) { - sys.checkTimeoutQueueLengthAndRun(1); - } +export function runQueuedTimeoutCallbacks(sys: WatchedSystem) { + sys.runQueuedTimeoutCallbacks(); +} - export function checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys: WatchedSystem) { - sys.checkTimeoutQueueLengthAndRun(1); - sys.checkTimeoutQueueLength(0); - } +export function checkSingleTimeoutQueueLengthAndRun(sys: WatchedSystem) { + sys.checkTimeoutQueueLengthAndRun(1); +} - export type WatchOrSolution = void | SolutionBuilder | WatchOfConfigFile | WatchOfFilesAndCompilerOptions; - export interface TscWatchCompileChange { - caption: string; - change: (sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles) => void; - timeouts: ( - sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles, - programs: readonly CommandLineProgram[], - watchOrSolution: WatchOrSolution - ) => void; - } - export interface TscWatchCheckOptions { - baselineSourceMap?: boolean; - baselineDependencies?: boolean; - } - export interface TscWatchCompileBase extends TscWatchCheckOptions { - scenario: string; - subScenario: string; - commandLineArgs: readonly string[]; - changes: readonly TscWatchCompileChange[]; - } - export interface TscWatchCompile extends TscWatchCompileBase { - sys: () => WatchedSystem; - } +export function checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys: WatchedSystem) { + sys.checkTimeoutQueueLengthAndRun(1); + sys.checkTimeoutQueueLength(0); +} - export const noopChange: TscWatchCompileChange = { - caption: "No change", - change: noop, - timeouts: sys => sys.checkTimeoutQueueLength(0), - }; +export type WatchOrSolution = void | ts.SolutionBuilder | ts.WatchOfConfigFile | ts.WatchOfFilesAndCompilerOptions; +export interface TscWatchCompileChange { + caption: string; + change: (sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles) => void; + timeouts: ( + sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles, + programs: readonly ts.CommandLineProgram[], + watchOrSolution: WatchOrSolution + ) => void; +} +export interface TscWatchCheckOptions { + baselineSourceMap?: boolean; + baselineDependencies?: boolean; +} +export interface TscWatchCompileBase extends TscWatchCheckOptions { + scenario: string; + subScenario: string; + commandLineArgs: readonly string[]; + changes: readonly TscWatchCompileChange[]; +} +export interface TscWatchCompile extends TscWatchCompileBase { + sys: () => WatchedSystem; +} - export type SystemSnap = ReturnType; - function tscWatchCompile(input: TscWatchCompile) { - it("tsc-watch:: Generates files matching the baseline", () => { - const { sys, baseline, oldSnap } = createBaseline(input.sys()); - const { - scenario, subScenario, - commandLineArgs, changes, - baselineSourceMap, baselineDependencies - } = input; - - if (!isWatch(commandLineArgs)) sys.exit = exitCode => sys.exitCode = exitCode; - const { cb, getPrograms } = commandLineCallbacks(sys); - const watchOrSolution = executeCommandLine( - sys, - cb, - commandLineArgs, - ); - runWatchBaseline({ - scenario, - subScenario, - commandLineArgs, - sys, - baseline, - oldSnap, - getPrograms, - baselineSourceMap, - baselineDependencies, - changes, - watchOrSolution - }); +export const noopChange: TscWatchCompileChange = { + caption: "No change", + change: ts.noop, + timeouts: sys => sys.checkTimeoutQueueLength(0), +}; + +export type SystemSnap = ReturnType; +function tscWatchCompile(input: TscWatchCompile) { + it("tsc-watch:: Generates files matching the baseline", () => { + const { sys, baseline, oldSnap } = createBaseline(input.sys()); + const { + scenario, subScenario, + commandLineArgs, changes, + baselineSourceMap, baselineDependencies + } = input; + + if (!isWatch(commandLineArgs)) sys.exit = exitCode => sys.exitCode = exitCode; + const { cb, getPrograms } = ts.commandLineCallbacks(sys); + const watchOrSolution = ts.executeCommandLine( + sys, + cb, + commandLineArgs, + ); + runWatchBaseline({ + scenario, + subScenario, + commandLineArgs, + sys, + baseline, + oldSnap, + getPrograms, + baselineSourceMap, + baselineDependencies, + changes, + watchOrSolution }); - } + }); +} - export interface BaselineBase { - baseline: string[]; - sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles; - oldSnap: SystemSnap; - } +export interface BaselineBase { + baseline: string[]; + sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles; + oldSnap: SystemSnap; +} - export interface Baseline extends BaselineBase, CommandLineCallbacks { - } +export interface Baseline extends BaselineBase, ts.CommandLineCallbacks { +} - export function createBaseline(system: WatchedSystem, modifySystem?: (sys: WatchedSystem, originalRead: WatchedSystem["readFile"]) => void): Baseline { - const originalRead = system.readFile; - const initialSys = fakes.patchHostForBuildInfoReadWrite(system); - modifySystem?.(initialSys, originalRead); - const sys = TestFSWithWatch.changeToHostTrackingWrittenFiles(initialSys); - const baseline: string[] = []; - baseline.push("Input::"); - sys.diff(baseline); - const { cb, getPrograms } = commandLineCallbacks(sys); - return { sys, baseline, oldSnap: sys.snap(), cb, getPrograms }; - } +export function createBaseline(system: WatchedSystem, modifySystem?: (sys: WatchedSystem, originalRead: WatchedSystem["readFile"]) => void): Baseline { + const originalRead = system.readFile; + const initialSys = fakes.patchHostForBuildInfoReadWrite(system); + modifySystem?.(initialSys, originalRead); + const sys = ts.TestFSWithWatch.changeToHostTrackingWrittenFiles(initialSys); + const baseline: string[] = []; + baseline.push("Input::"); + sys.diff(baseline); + const { cb, getPrograms } = ts.commandLineCallbacks(sys); + return { sys, baseline, oldSnap: sys.snap(), cb, getPrograms }; +} - export function createSolutionBuilderWithWatchHostForBaseline(sys: WatchedSystem, cb: ExecuteCommandLineCallbacks) { - const host = createSolutionBuilderWithWatchHost(sys, - /*createProgram*/ undefined, - createDiagnosticReporter(sys, /*pretty*/ true), - createBuilderStatusReporter(sys, /*pretty*/ true), - createWatchStatusReporter(sys, /*pretty*/ true) - ); - host.afterProgramEmitAndDiagnostics = cb; - host.afterEmitBundle = cb; - return host; - } +export function createSolutionBuilderWithWatchHostForBaseline(sys: WatchedSystem, cb: ts.ExecuteCommandLineCallbacks) { + const host = ts.createSolutionBuilderWithWatchHost(sys, + /*createProgram*/ undefined, + ts.createDiagnosticReporter(sys, /*pretty*/ true), + ts.createBuilderStatusReporter(sys, /*pretty*/ true), + ts.createWatchStatusReporter(sys, /*pretty*/ true) + ); + host.afterProgramEmitAndDiagnostics = cb; + host.afterEmitBundle = cb; + return host; +} - interface CreateWatchCompilerHostOfConfigFileForBaseline extends CreateWatchCompilerHostOfConfigFileInput { - system: WatchedSystem, - cb: ExecuteCommandLineCallbacks; - } +interface CreateWatchCompilerHostOfConfigFileForBaseline extends ts.CreateWatchCompilerHostOfConfigFileInput { + system: WatchedSystem, + cb: ts.ExecuteCommandLineCallbacks; +} - export function createWatchCompilerHostOfConfigFileForBaseline( - input: CreateWatchCompilerHostOfConfigFileForBaseline - ) { - const host = createWatchCompilerHostOfConfigFile({ - ...input, - reportDiagnostic: createDiagnosticReporter(input.system, /*pretty*/ true), - reportWatchStatus: createWatchStatusReporter(input.system, /*pretty*/ true), - }); - updateWatchHostForBaseline(host, input.cb); - return host; - } +export function createWatchCompilerHostOfConfigFileForBaseline( + input: CreateWatchCompilerHostOfConfigFileForBaseline +) { + const host = ts.createWatchCompilerHostOfConfigFile({ + ...input, + reportDiagnostic: ts.createDiagnosticReporter(input.system, /*pretty*/ true), + reportWatchStatus: ts.createWatchStatusReporter(input.system, /*pretty*/ true), + }); + updateWatchHostForBaseline(host, input.cb); + return host; +} - interface CreateWatchCompilerHostOfFilesAndCompilerOptionsForBaseline extends CreateWatchCompilerHostOfFilesAndCompilerOptionsInput { - system: WatchedSystem, - cb: ExecuteCommandLineCallbacks; - } - export function createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline( - input: CreateWatchCompilerHostOfFilesAndCompilerOptionsForBaseline - ) { - const host = createWatchCompilerHostOfFilesAndCompilerOptions({ - ...input, - reportDiagnostic: createDiagnosticReporter(input.system, /*pretty*/ true), - reportWatchStatus: createWatchStatusReporter(input.system, /*pretty*/ true), - }); - updateWatchHostForBaseline(host, input.cb); - return host; - } +interface CreateWatchCompilerHostOfFilesAndCompilerOptionsForBaseline extends ts.CreateWatchCompilerHostOfFilesAndCompilerOptionsInput { + system: WatchedSystem, + cb: ts.ExecuteCommandLineCallbacks; +} +export function createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline( + input: CreateWatchCompilerHostOfFilesAndCompilerOptionsForBaseline +) { + const host = ts.createWatchCompilerHostOfFilesAndCompilerOptions({ + ...input, + reportDiagnostic: ts.createDiagnosticReporter(input.system, /*pretty*/ true), + reportWatchStatus: ts.createWatchStatusReporter(input.system, /*pretty*/ true), + }); + updateWatchHostForBaseline(host, input.cb); + return host; +} - function updateWatchHostForBaseline(host: WatchCompilerHost, cb: ExecuteCommandLineCallbacks) { - const emitFilesAndReportErrors = host.afterProgramCreate!; - host.afterProgramCreate = builderProgram => { - emitFilesAndReportErrors.call(host, builderProgram); - cb(builderProgram); - }; - return host; - } +function updateWatchHostForBaseline(host: ts.WatchCompilerHost, cb: ts.ExecuteCommandLineCallbacks) { + const emitFilesAndReportErrors = host.afterProgramCreate!; + host.afterProgramCreate = builderProgram => { + emitFilesAndReportErrors.call(host, builderProgram); + cb(builderProgram); + }; + return host; +} - export function applyChange(sys: BaselineBase["sys"], baseline: BaselineBase["baseline"], change: TscWatchCompileChange["change"], caption?: TscWatchCompileChange["caption"]) { - const oldSnap = sys.snap(); - baseline.push(`Change::${caption ? " " + caption : ""}`, ""); - change(sys); - baseline.push("Input::"); - sys.diff(baseline, oldSnap); - return sys.snap(); - } +export function applyChange(sys: BaselineBase["sys"], baseline: BaselineBase["baseline"], change: TscWatchCompileChange["change"], caption?: TscWatchCompileChange["caption"]) { + const oldSnap = sys.snap(); + baseline.push(`Change::${caption ? " " + caption : ""}`, ""); + change(sys); + baseline.push("Input::"); + sys.diff(baseline, oldSnap); + return sys.snap(); +} - export interface RunWatchBaseline extends BaselineBase, TscWatchCompileBase { - sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles; - getPrograms: () => readonly CommandLineProgram[]; - watchOrSolution: WatchOrSolution; - } - export function runWatchBaseline({ - scenario, subScenario, commandLineArgs, - getPrograms, sys, baseline, oldSnap, - baselineSourceMap, baselineDependencies, - changes, watchOrSolution - }: RunWatchBaseline) { - baseline.push(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}`); - let programs = watchBaseline({ +export interface RunWatchBaseline extends BaselineBase, TscWatchCompileBase { + sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles; + getPrograms: () => readonly ts.CommandLineProgram[]; + watchOrSolution: WatchOrSolution; +} +export function runWatchBaseline({ + scenario, subScenario, commandLineArgs, + getPrograms, sys, baseline, oldSnap, + baselineSourceMap, baselineDependencies, + changes, watchOrSolution +}: RunWatchBaseline) { + baseline.push(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}`); + let programs = watchBaseline({ + baseline, + getPrograms, + oldPrograms: ts.emptyArray, + sys, + oldSnap, + baselineSourceMap, + baselineDependencies, + }); + + for (const { caption, change, timeouts } of changes) { + oldSnap = applyChange(sys, baseline, change, caption); + timeouts(sys, programs, watchOrSolution); + programs = watchBaseline({ baseline, getPrograms, - oldPrograms: emptyArray, + oldPrograms: programs, sys, oldSnap, baselineSourceMap, baselineDependencies, }); - - for (const { caption, change, timeouts } of changes) { - oldSnap = applyChange(sys, baseline, change, caption); - timeouts(sys, programs, watchOrSolution); - programs = watchBaseline({ - baseline, - getPrograms, - oldPrograms: programs, - sys, - oldSnap, - baselineSourceMap, - baselineDependencies, - }); - } - Harness.Baseline.runBaseline(`${isBuild(commandLineArgs) ? "tsbuild" : "tsc"}${isWatch(commandLineArgs) ? "Watch" : ""}/${scenario}/${subScenario.split(" ").join("-")}.js`, baseline.join("\r\n")); } + Harness.Baseline.runBaseline(`${ts.isBuild(commandLineArgs) ? "tsbuild" : "tsc"}${isWatch(commandLineArgs) ? "Watch" : ""}/${scenario}/${subScenario.split(" ").join("-")}.js`, baseline.join("\r\n")); +} - function isWatch(commandLineArgs: readonly string[]) { - return forEach(commandLineArgs, arg => { - if (arg.charCodeAt(0) !== CharacterCodes.minus) return false; - const option = arg.slice(arg.charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase(); - return option === "watch" || option === "w"; - }); - } +function isWatch(commandLineArgs: readonly string[]) { + return ts.forEach(commandLineArgs, arg => { + if (arg.charCodeAt(0) !== ts.CharacterCodes.minus) return false; + const option = arg.slice(arg.charCodeAt(1) === ts.CharacterCodes.minus ? 2 : 1).toLowerCase(); + return option === "watch" || option === "w"; + }); +} - export interface WatchBaseline extends BaselineBase, TscWatchCheckOptions { - oldPrograms: readonly (CommandLineProgram | undefined)[]; - getPrograms: () => readonly CommandLineProgram[]; - } - export function watchBaseline({ baseline, getPrograms, oldPrograms, sys, oldSnap, baselineSourceMap, baselineDependencies }: WatchBaseline) { - if (baselineSourceMap) generateSourceMapBaselineFiles(sys); - sys.serializeOutput(baseline); - const programs = baselinePrograms(baseline, getPrograms, oldPrograms, baselineDependencies); - sys.serializeWatches(baseline); - baseline.push(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}`, ""); - sys.diff(baseline, oldSnap); - sys.writtenFiles.forEach((value, key) => { - assert.equal(value, 1, `Expected to write file ${key} only once`); - }); - sys.writtenFiles.clear(); - return programs; +export interface WatchBaseline extends BaselineBase, TscWatchCheckOptions { + oldPrograms: readonly (ts.CommandLineProgram | undefined)[]; + getPrograms: () => readonly ts.CommandLineProgram[]; +} +export function watchBaseline({ baseline, getPrograms, oldPrograms, sys, oldSnap, baselineSourceMap, baselineDependencies }: WatchBaseline) { + if (baselineSourceMap) ts.generateSourceMapBaselineFiles(sys); + sys.serializeOutput(baseline); + const programs = baselinePrograms(baseline, getPrograms, oldPrograms, baselineDependencies); + sys.serializeWatches(baseline); + baseline.push(`exitCode:: ExitStatus.${ts.ExitStatus[sys.exitCode as ts.ExitStatus]}`, ""); + sys.diff(baseline, oldSnap); + sys.writtenFiles.forEach((value, key) => { + assert.equal(value, 1, `Expected to write file ${key} only once`); + }); + sys.writtenFiles.clear(); + return programs; +} + +export function baselinePrograms(baseline: string[], getPrograms: () => readonly ts.CommandLineProgram[], oldPrograms: readonly (ts.CommandLineProgram | undefined)[], baselineDependencies: boolean | undefined) { + const programs = getPrograms(); + for (let i = 0; i < programs.length; i++) { + baselineProgram(baseline, programs[i], oldPrograms[i], baselineDependencies); } + return programs; +} - export function baselinePrograms(baseline: string[], getPrograms: () => readonly CommandLineProgram[], oldPrograms: readonly (CommandLineProgram | undefined)[], baselineDependencies: boolean | undefined) { - const programs = getPrograms(); - for (let i = 0; i < programs.length; i++) { - baselineProgram(baseline, programs[i], oldPrograms[i], baselineDependencies); +function baselineProgram(baseline: string[], [program, builderProgram]: ts.CommandLineProgram, oldProgram: ts.CommandLineProgram | undefined, baselineDependencies: boolean | undefined) { + if (program !== oldProgram?.[0]) { + const options = program.getCompilerOptions(); + baseline.push(`Program root files: ${JSON.stringify(program.getRootFileNames())}`); + baseline.push(`Program options: ${JSON.stringify(options)}`); + baseline.push(`Program structureReused: ${(ts as any).StructureIsReused[program.structureIsReused]}`); + baseline.push("Program files::"); + for (const file of program.getSourceFiles()) { + baseline.push(file.fileName); } - return programs; } + else { + baseline.push(`Program: Same as old program`); + } + baseline.push(""); - function baselineProgram(baseline: string[], [program, builderProgram]: CommandLineProgram, oldProgram: CommandLineProgram | undefined, baselineDependencies: boolean | undefined) { - if (program !== oldProgram?.[0]) { - const options = program.getCompilerOptions(); - baseline.push(`Program root files: ${JSON.stringify(program.getRootFileNames())}`); - baseline.push(`Program options: ${JSON.stringify(options)}`); - baseline.push(`Program structureReused: ${(ts as any).StructureIsReused[program.structureIsReused]}`); - baseline.push("Program files::"); + if (!builderProgram) return; + if (builderProgram !== oldProgram?.[1]) { + const state = builderProgram.getState(); + const internalState = state as unknown as ts.BuilderProgramState; + if (state.semanticDiagnosticsPerFile?.size) { + baseline.push("Semantic diagnostics in builder refreshed for::"); for (const file of program.getSourceFiles()) { - baseline.push(file.fileName); + if (!internalState.semanticDiagnosticsFromOldState || !internalState.semanticDiagnosticsFromOldState.has(file.resolvedPath)) { + baseline.push(file.fileName); + } } } else { - baseline.push(`Program: Same as old program`); + baseline.push("No cached semantic diagnostics in the builder::"); } - baseline.push(""); - - if (!builderProgram) return; - if (builderProgram !== oldProgram?.[1]) { - const state = builderProgram.getState(); - const internalState = state as unknown as BuilderProgramState; - if (state.semanticDiagnosticsPerFile?.size) { - baseline.push("Semantic diagnostics in builder refreshed for::"); - for (const file of program.getSourceFiles()) { - if (!internalState.semanticDiagnosticsFromOldState || !internalState.semanticDiagnosticsFromOldState.has(file.resolvedPath)) { - baseline.push(file.fileName); + if (internalState) { + baseline.push(""); + if (internalState.hasCalledUpdateShapeSignature?.size) { + baseline.push("Shape signatures in builder refreshed for::"); + internalState.hasCalledUpdateShapeSignature.forEach((path: ts.Path) => { + const info = state.fileInfos.get(path); + if (info?.version === info?.signature || !info?.signature) { + baseline.push(path + " (used version)"); } - } + else if (internalState.filesChangingSignature?.has(path)) { + baseline.push(path + " (computed .d.ts during emit)"); + } + else { + baseline.push(path + " (computed .d.ts)"); + } + }); } else { - baseline.push("No cached semantic diagnostics in the builder::"); - } - if (internalState) { - baseline.push(""); - if (internalState.hasCalledUpdateShapeSignature?.size) { - baseline.push("Shape signatures in builder refreshed for::"); - internalState.hasCalledUpdateShapeSignature.forEach((path: Path) => { - const info = state.fileInfos.get(path); - if (info?.version === info?.signature || !info?.signature) { - baseline.push(path + " (used version)"); - } - else if (internalState.filesChangingSignature?.has(path)) { - baseline.push(path + " (computed .d.ts during emit)"); - } - else { - baseline.push(path + " (computed .d.ts)"); - } - }); - } - else { - baseline.push("No shapes updated in the builder::"); - } + baseline.push("No shapes updated in the builder::"); } - baseline.push(""); - if (!baselineDependencies) return; - baseline.push("Dependencies for::"); - for (const file of builderProgram.getSourceFiles()) { - baseline.push(`${file.fileName}:`); - for (const depenedency of builderProgram.getAllDependencies(file)) { - baseline.push(` ${depenedency}`); - } - } - } - else { - baseline.push(`BuilderProgram: Same as old builder program`); } baseline.push(""); + if (!baselineDependencies) return; + baseline.push("Dependencies for::"); + for (const file of builderProgram.getSourceFiles()) { + baseline.push(`${file.fileName}:`); + for (const depenedency of builderProgram.getAllDependencies(file)) { + baseline.push(` ${depenedency}`); + } + } } - - export interface VerifyTscWatch extends TscWatchCompile { - baselineIncremental?: boolean; + else { + baseline.push(`BuilderProgram: Same as old builder program`); } - export function verifyTscWatch(input: VerifyTscWatch) { - describe(input.scenario, () => { - describe(input.subScenario, () => { - tscWatchCompile(input); - }); - if (input.baselineIncremental) { - describe(`${input.subScenario} with incremental`, () => { - tscWatchCompile({ - ...input, - subScenario: `${input.subScenario} with incremental`, - commandLineArgs: [...input.commandLineArgs, "--incremental"], - }); - }); - } + baseline.push(""); +} + +export interface VerifyTscWatch extends TscWatchCompile { + baselineIncremental?: boolean; +} +export function verifyTscWatch(input: VerifyTscWatch) { + describe(input.scenario, () => { + describe(input.subScenario, () => { + tscWatchCompile(input); }); - } + if (input.baselineIncremental) { + describe(`${input.subScenario} with incremental`, () => { + tscWatchCompile({ + ...input, + subScenario: `${input.subScenario} with incremental`, + commandLineArgs: [...input.commandLineArgs, "--incremental"], + }); + }); + } + }); +} - export function replaceFileText(sys: WatchedSystem, file: string, searchValue: string | RegExp, replaceValue: string) { - const content = Debug.checkDefined(sys.readFile(file)); - sys.writeFile(file, content.replace(searchValue, replaceValue)); - } +export function replaceFileText(sys: WatchedSystem, file: string, searchValue: string | RegExp, replaceValue: string) { + const content = ts.Debug.checkDefined(sys.readFile(file)); + sys.writeFile(file, content.replace(searchValue, replaceValue)); +} - export function createSolutionBuilder(system: WatchedSystem, rootNames: readonly string[], originalRead?: WatchedSystem["readFile"]) { - const host = createSolutionBuilderHostForBaseline(system, /*versionToWrite*/ undefined, originalRead); - return ts.createSolutionBuilder(host, rootNames, {}); - } +export function createSolutionBuilder(system: WatchedSystem, rootNames: readonly string[], originalRead?: WatchedSystem["readFile"]) { + const host = ts.createSolutionBuilderHostForBaseline(system, /*versionToWrite*/ undefined, originalRead); + return ts.createSolutionBuilder(host, rootNames, {}); +} - export function ensureErrorFreeBuild(host: WatchedSystem, rootNames: readonly string[]) { - // ts build should succeed - solutionBuildWithBaseline(host, rootNames); - assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " ")); - } +export function ensureErrorFreeBuild(host: WatchedSystem, rootNames: readonly string[]) { + // ts build should succeed + solutionBuildWithBaseline(host, rootNames); + assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " ")); +} - export function solutionBuildWithBaseline(sys: WatchedSystem, solutionRoots: readonly string[], originalRead?: WatchedSystem["readFile"]) { - const originalReadFile = sys.readFile; - const originalWrite = sys.write; - const originalWriteFile = sys.writeFile; - const solutionBuilder = createSolutionBuilder(TestFSWithWatch.changeToHostTrackingWrittenFiles( - fakes.patchHostForBuildInfoReadWrite(sys) - ), solutionRoots, originalRead); - solutionBuilder.build(); - sys.readFile = originalReadFile; - sys.write = originalWrite; - sys.writeFile = originalWriteFile; - return sys; - } +export function solutionBuildWithBaseline(sys: WatchedSystem, solutionRoots: readonly string[], originalRead?: WatchedSystem["readFile"]) { + const originalReadFile = sys.readFile; + const originalWrite = sys.write; + const originalWriteFile = sys.writeFile; + const solutionBuilder = createSolutionBuilder(ts.TestFSWithWatch.changeToHostTrackingWrittenFiles( + fakes.patchHostForBuildInfoReadWrite(sys) + ), solutionRoots, originalRead); + solutionBuilder.build(); + sys.readFile = originalReadFile; + sys.write = originalWrite; + sys.writeFile = originalWriteFile; + return sys; +} - export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: TestFSWithWatch.FileOrFolderOrSymLinkMap | readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) { - return solutionBuildWithBaseline(createWatchedSystem(files, params), solutionRoots); - } +export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: ts.TestFSWithWatch.FileOrFolderOrSymLinkMap | readonly ts.TestFSWithWatch.FileOrFolderOrSymLink[], params?: ts.TestFSWithWatch.TestServerHostCreationParameters) { + return solutionBuildWithBaseline(createWatchedSystem(files, params), solutionRoots); } diff --git a/src/testRunner/unittests/tscWatch/incremental.ts b/src/testRunner/unittests/tscWatch/incremental.ts index 8d49e9ba2468c..3de067d433c48 100644 --- a/src/testRunner/unittests/tscWatch/incremental.ts +++ b/src/testRunner/unittests/tscWatch/incremental.ts @@ -1,289 +1,291 @@ -namespace ts.tscWatch { - describe("unittests:: tsc-watch:: emit file --incremental", () => { - const project = "/users/username/projects/project"; +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; - const configFile: File = { - path: `${project}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { incremental: true } }) - }; +describe("unittests:: tsc-watch:: emit file --incremental", () => { + const project = "/users/username/projects/project"; - interface VerifyIncrementalWatchEmitInput { - subScenario: string; - files: () => readonly File[]; - optionsToExtend?: readonly string[]; - modifyFs?: (host: WatchedSystem) => void; - } - function verifyIncrementalWatchEmit(input: VerifyIncrementalWatchEmitInput) { - describe(input.subScenario, () => { - it("with tsc --w", () => { - verifyIncrementalWatchEmitWorker(input, /*incremental*/ false); - }); - it("with tsc", () => { - verifyIncrementalWatchEmitWorker(input, /*incremental*/ true); - }); + const configFile: ts.tscWatch.File = { + path: `${project}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { incremental: true } }) + }; + + interface VerifyIncrementalWatchEmitInput { + subScenario: string; + files: () => readonly ts.tscWatch.File[]; + optionsToExtend?: readonly string[]; + modifyFs?: (host: ts.tscWatch.WatchedSystem) => void; + } + function verifyIncrementalWatchEmit(input: VerifyIncrementalWatchEmitInput) { + describe(input.subScenario, () => { + it("with tsc --w", () => { + verifyIncrementalWatchEmitWorker(input, /*incremental*/ false); }); - } + it("with tsc", () => { + verifyIncrementalWatchEmitWorker(input, /*incremental*/ true); + }); + }); + } + + function verifyIncrementalWatchEmitWorker( + { subScenario, files, optionsToExtend, modifyFs }: VerifyIncrementalWatchEmitInput, + incremental: boolean + ) { + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem(files(), { currentDirectory: project })); + if (incremental) sys.exit = exitCode => sys.exitCode = exitCode; + const argsToPass = [incremental ? "-i" : "-w", ...(optionsToExtend || ts.emptyArray)]; + baseline.push(`${sys.getExecutingFilePath()} ${argsToPass.join(" ")}`); + let oldPrograms: readonly ts.CommandLineProgram[] = ts.emptyArray; + build(oldSnap); - function verifyIncrementalWatchEmitWorker( - { subScenario, files, optionsToExtend, modifyFs }: VerifyIncrementalWatchEmitInput, - incremental: boolean - ) { - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem(files(), { currentDirectory: project })); - if (incremental) sys.exit = exitCode => sys.exitCode = exitCode; - const argsToPass = [incremental ? "-i" : "-w", ...(optionsToExtend || emptyArray)]; - baseline.push(`${sys.getExecutingFilePath()} ${argsToPass.join(" ")}`); - let oldPrograms: readonly CommandLineProgram[] = emptyArray; + if (modifyFs) { + const oldSnap = ts.tscWatch.applyChange(sys, baseline, modifyFs); build(oldSnap); + } - if (modifyFs) { - const oldSnap = applyChange(sys, baseline, modifyFs); - build(oldSnap); - } + Harness.Baseline.runBaseline(`${ts.isBuild(argsToPass) ? "tsbuild/watchMode" : "tscWatch"}/incremental/${subScenario.split(" ").join("-")}-${incremental ? "incremental" : "watch"}.js`, baseline.join("\r\n")); - Harness.Baseline.runBaseline(`${isBuild(argsToPass) ? "tsbuild/watchMode" : "tscWatch"}/incremental/${subScenario.split(" ").join("-")}-${incremental ? "incremental" : "watch"}.js`, baseline.join("\r\n")); + function build(oldSnap: ts.tscWatch.SystemSnap) { + const closer = ts.executeCommandLine( + sys, + cb, + argsToPass, + ); + oldPrograms = ts.tscWatch.watchBaseline({ + baseline, + getPrograms, + oldPrograms, + sys, + oldSnap + }); + if (closer) closer.close(); + } + } - function build(oldSnap: SystemSnap) { - const closer = executeCommandLine( - sys, - cb, - argsToPass, - ); - oldPrograms = watchBaseline({ - baseline, - getPrograms, - oldPrograms, - sys, - oldSnap + describe("non module compilation", () => { + const file1: ts.tscWatch.File = { + path: `${project}/file1.ts`, + content: "const x = 10;" + }; + const file2: ts.tscWatch.File = { + path: `${project}/file2.ts`, + content: "const y = 20;" + }; + describe("own file emit without errors", () => { + function verify(subScenario: string, optionsToExtend?: readonly string[]) { + const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10"); + verifyIncrementalWatchEmit({ + files: () => [ts.tscWatch.libFile, file1, file2, configFile], + optionsToExtend, + subScenario: `own file emit without errors/${subScenario}`, + modifyFs: host => host.writeFile(file2.path, modifiedFile2Content), }); - if (closer) closer.close(); } - } + verify("without commandline options"); + verify("with commandline parameters that are not relative", ["-p", "tsconfig.json"]); + }); - describe("non module compilation", () => { - const file1: File = { - path: `${project}/file1.ts`, - content: "const x = 10;" - }; - const file2: File = { - path: `${project}/file2.ts`, - content: "const y = 20;" - }; - describe("own file emit without errors", () => { - function verify(subScenario: string, optionsToExtend?: readonly string[]) { - const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10"); - verifyIncrementalWatchEmit({ - files: () => [libFile, file1, file2, configFile], - optionsToExtend, - subScenario: `own file emit without errors/${subScenario}`, - modifyFs: host => host.writeFile(file2.path, modifiedFile2Content), - }); - } - verify("without commandline options"); - verify("with commandline parameters that are not relative", ["-p", "tsconfig.json"]); - }); + verifyIncrementalWatchEmit({ + files: () => [ts.tscWatch.libFile, file1, configFile, { + path: file2.path, + content: `const y: string = 20;` + }], + subScenario: "own file emit with errors", + modifyFs: host => host.writeFile(file1.path, file1.content.replace("x", "z")), + }); - verifyIncrementalWatchEmit({ - files: () => [libFile, file1, configFile, { - path: file2.path, - content: `const y: string = 20;` - }], - subScenario: "own file emit with errors", - modifyFs: host => host.writeFile(file1.path, file1.content.replace("x", "z")), - }); + verifyIncrementalWatchEmit({ + files: () => [ts.tscWatch.libFile, file1, file2, { + path: configFile.path, + content: JSON.stringify({ compilerOptions: { incremental: true, outFile: "out.js" } }) + }], + subScenario: "with --out", + }); + }); - verifyIncrementalWatchEmit({ - files: () => [libFile, file1, file2, { - path: configFile.path, - content: JSON.stringify({ compilerOptions: { incremental: true, outFile: "out.js" } }) - }], - subScenario: "with --out", - }); + describe("module compilation", () => { + const file1: ts.tscWatch.File = { + path: `${project}/file1.ts`, + content: "export const x = 10;" + }; + const file2: ts.tscWatch.File = { + path: `${project}/file2.ts`, + content: "export const y = 20;" + }; + const config: ts.tscWatch.File = { + path: configFile.path, + content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd" } }) + }; + + verifyIncrementalWatchEmit({ + files: () => [ts.tscWatch.libFile, file1, file2, config], + subScenario: "module compilation/own file emit without errors", + modifyFs: host => host.writeFile(file2.path, file2.content.replace("y", "z").replace("20", "10")), }); - describe("module compilation", () => { - const file1: File = { - path: `${project}/file1.ts`, - content: "export const x = 10;" - }; - const file2: File = { - path: `${project}/file2.ts`, - content: "export const y = 20;" - }; - const config: File = { - path: configFile.path, - content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd" } }) + describe("own file emit with errors", () => { + const fileModified: ts.tscWatch.File = { + path: file2.path, + content: `export const y: string = 20;` }; verifyIncrementalWatchEmit({ - files: () => [libFile, file1, file2, config], - subScenario: "module compilation/own file emit without errors", - modifyFs: host => host.writeFile(file2.path, file2.content.replace("y", "z").replace("20", "10")), + files: () => [ts.tscWatch.libFile, file1, fileModified, config], + subScenario: "module compilation/own file emit with errors", + modifyFs: host => host.writeFile(file1.path, file1.content.replace("x = 10", "z = 10")), }); - describe("own file emit with errors", () => { - const fileModified: File = { - path: file2.path, - content: `export const y: string = 20;` - }; - - verifyIncrementalWatchEmit({ - files: () => [libFile, file1, fileModified, config], - subScenario: "module compilation/own file emit with errors", - modifyFs: host => host.writeFile(file1.path, file1.content.replace("x = 10", "z = 10")), + it("verify that state is read correctly", () => { + const system = ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, file1, fileModified, config], { currentDirectory: project }); + const reportDiagnostic = ts.createDiagnosticReporter(system); + const parsedConfig = ts.parseConfigFileWithSystem("tsconfig.json", {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, reportDiagnostic)!; + ts.performIncrementalCompilation({ + rootNames: parsedConfig.fileNames, + options: parsedConfig.options, + projectReferences: parsedConfig.projectReferences, + configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(parsedConfig), + reportDiagnostic, + system }); - it("verify that state is read correctly", () => { - const system = createWatchedSystem([libFile, file1, fileModified, config], { currentDirectory: project }); - const reportDiagnostic = createDiagnosticReporter(system); - const parsedConfig = parseConfigFileWithSystem("tsconfig.json", {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, reportDiagnostic)!; - performIncrementalCompilation({ - rootNames: parsedConfig.fileNames, - options: parsedConfig.options, - projectReferences: parsedConfig.projectReferences, - configFileParsingDiagnostics: getConfigFileParsingDiagnostics(parsedConfig), - reportDiagnostic, - system - }); - - const command = parseConfigFileWithSystem("tsconfig.json", {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, noop)!; - const builderProgram = createIncrementalProgram({ - rootNames: command.fileNames, - options: command.options, - projectReferences: command.projectReferences, - configFileParsingDiagnostics: getConfigFileParsingDiagnostics(command), - host: createIncrementalCompilerHost(command.options, system) - }); - - const state = builderProgram.getState(); - assert.equal(state.changedFilesSet!.size, 0, "changes"); - - assert.equal(state.fileInfos.size, 3, "FileInfo size"); - assert.deepEqual(state.fileInfos.get(libFile.path as Path), { - version: system.createHash(libFile.content), - signature: system.createHash(libFile.content), - affectsGlobalScope: true, - impliedFormat: undefined, - }); - assert.deepEqual(state.fileInfos.get(file1.path as Path), { - version: system.createHash(file1.content), - signature: system.createHash(file1.content), - affectsGlobalScope: undefined, - impliedFormat: undefined, - }); - assert.deepEqual(state.fileInfos.get(file2.path as Path), { - version: system.createHash(fileModified.content), - signature: system.createHash(fileModified.content), - affectsGlobalScope: undefined, - impliedFormat: undefined, - }); + const command = ts.parseConfigFileWithSystem("tsconfig.json", {}, /*extendedConfigCache*/ undefined, /*watchOptionsToExtend*/ undefined, system, ts.noop)!; + const builderProgram = ts.createIncrementalProgram({ + rootNames: command.fileNames, + options: command.options, + projectReferences: command.projectReferences, + configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(command), + host: ts.createIncrementalCompilerHost(command.options, system) + }); - assert.deepEqual(state.compilerOptions, { - incremental: true, - module: ModuleKind.AMD, - configFilePath: config.path - }); + const state = builderProgram.getState(); + assert.equal(state.changedFilesSet!.size, 0, "changes"); - assert.equal(arrayFrom(state.referencedMap!.keys()).length, 0); - assert.equal(arrayFrom(state.exportedModulesMap!.keys()).length, 0); + assert.equal(state.fileInfos.size, 3, "FileInfo size"); + assert.deepEqual(state.fileInfos.get(ts.tscWatch.libFile.path as ts.Path), { + version: system.createHash(ts.tscWatch.libFile.content), + signature: system.createHash(ts.tscWatch.libFile.content), + affectsGlobalScope: true, + impliedFormat: undefined, + }); + assert.deepEqual(state.fileInfos.get(file1.path as ts.Path), { + version: system.createHash(file1.content), + signature: system.createHash(file1.content), + affectsGlobalScope: undefined, + impliedFormat: undefined, + }); + assert.deepEqual(state.fileInfos.get(file2.path as ts.Path), { + version: system.createHash(fileModified.content), + signature: system.createHash(fileModified.content), + affectsGlobalScope: undefined, + impliedFormat: undefined, + }); - assert.equal(state.semanticDiagnosticsPerFile!.size, 3); - assert.deepEqual(state.semanticDiagnosticsPerFile!.get(libFile.path as Path), emptyArray); - assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file1.path as Path), emptyArray); - assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file2.path as Path), [{ - file: state.program!.getSourceFileByPath(file2.path as Path)!, - start: 13, - length: 1, - code: Diagnostics.Type_0_is_not_assignable_to_type_1.code, - category: Diagnostics.Type_0_is_not_assignable_to_type_1.category, - messageText: "Type 'number' is not assignable to type 'string'.", - relatedInformation: undefined, - reportsUnnecessary: undefined, - reportsDeprecated: undefined, - source: undefined, - skippedOn: undefined, - }]); + assert.deepEqual(state.compilerOptions, { + incremental: true, + module: ts.ModuleKind.AMD, + configFilePath: config.path }); - }); - verifyIncrementalWatchEmit({ - files: () => [libFile, file1, file2, { - path: configFile.path, - content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd", outFile: "out.js" } }) - }], - subScenario: "module compilation/with --out", + assert.equal(ts.arrayFrom(state.referencedMap!.keys()).length, 0); + assert.equal(ts.arrayFrom(state.exportedModulesMap!.keys()).length, 0); + + assert.equal(state.semanticDiagnosticsPerFile!.size, 3); + assert.deepEqual(state.semanticDiagnosticsPerFile!.get(ts.tscWatch.libFile.path as ts.Path), ts.emptyArray); + assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file1.path as ts.Path), ts.emptyArray); + assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file2.path as ts.Path), [{ + file: state.program!.getSourceFileByPath(file2.path as ts.Path)!, + start: 13, + length: 1, + code: ts.Diagnostics.Type_0_is_not_assignable_to_type_1.code, + category: ts.Diagnostics.Type_0_is_not_assignable_to_type_1.category, + messageText: "Type 'number' is not assignable to type 'string'.", + relatedInformation: undefined, + reportsUnnecessary: undefined, + reportsDeprecated: undefined, + source: undefined, + skippedOn: undefined, + }]); }); }); verifyIncrementalWatchEmit({ - files: () => { - const config: File = { - path: configFile.path, - content: JSON.stringify({ - compilerOptions: { - incremental: true, - target: "es5", - module: "commonjs", - declaration: true, - emitDeclarationOnly: true - } - }) - }; - const aTs: File = { - path: `${project}/a.ts`, - content: `import { B } from "./b"; + files: () => [ts.tscWatch.libFile, file1, file2, { + path: configFile.path, + content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd", outFile: "out.js" } }) + }], + subScenario: "module compilation/with --out", + }); + }); + + verifyIncrementalWatchEmit({ + files: () => { + const config: ts.tscWatch.File = { + path: configFile.path, + content: JSON.stringify({ + compilerOptions: { + incremental: true, + target: "es5", + module: "commonjs", + declaration: true, + emitDeclarationOnly: true + } + }) + }; + const aTs: ts.tscWatch.File = { + path: `${project}/a.ts`, + content: `import { B } from "./b"; export interface A { b: B; } ` - }; - const bTs: File = { - path: `${project}/b.ts`, - content: `import { C } from "./c"; + }; + const bTs: ts.tscWatch.File = { + path: `${project}/b.ts`, + content: `import { C } from "./c"; export interface B { b: C; } ` - }; - const cTs: File = { - path: `${project}/c.ts`, - content: `import { A } from "./a"; + }; + const cTs: ts.tscWatch.File = { + path: `${project}/c.ts`, + content: `import { A } from "./a"; export interface C { a: A; } ` - }; - const indexTs: File = { - path: `${project}/index.ts`, - content: `export { A } from "./a"; + }; + const indexTs: ts.tscWatch.File = { + path: `${project}/index.ts`, + content: `export { A } from "./a"; export { B } from "./b"; export { C } from "./c"; ` - }; - return [libFile, aTs, bTs, cTs, indexTs, config]; - }, - subScenario: "incremental with circular references", - modifyFs: host => host.writeFile(`${project}/a.ts`, `import { B } from "./b"; + }; + return [ts.tscWatch.libFile, aTs, bTs, cTs, indexTs, config]; + }, + subScenario: "incremental with circular references", + modifyFs: host => host.writeFile(`${project}/a.ts`, `import { B } from "./b"; export interface A { b: B; foo: any; } `) - }); + }); - verifyIncrementalWatchEmit({ - subScenario: "when file with ambient global declaration file is deleted", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/globals.d.ts`, content: `declare namespace Config { const value: string;} ` }, - { path: `${project}/index.ts`, content: `console.log(Config.value);` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: { incremental: true, } }) } - ], - modifyFs: host => host.deleteFile(`${project}/globals.d.ts`) - }); + verifyIncrementalWatchEmit({ + subScenario: "when file with ambient global declaration file is deleted", + files: () => [ + { path: ts.tscWatch.libFile.path, content: ts.libContent }, + { path: `${project}/globals.d.ts`, content: `declare namespace Config { const value: string;} ` }, + { path: `${project}/index.ts`, content: `console.log(Config.value);` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: { incremental: true, } }) } + ], + modifyFs: host => host.deleteFile(`${project}/globals.d.ts`) + }); - describe("with option jsxImportSource", () => { - const jsxImportSourceOptions = { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" }; - const jsxLibraryContent = `export namespace JSX { + describe("with option jsxImportSource", () => { + const jsxImportSourceOptions = { module: "commonjs", jsx: "react-jsx", incremental: true, jsxImportSource: "react" }; + const jsxLibraryContent = `export namespace JSX { interface Element {} interface IntrinsicElements { div: { @@ -296,96 +298,95 @@ export function jsxs(...args: any[]): void; export const Fragment: unique symbol; `; - verifyIncrementalWatchEmit({ - subScenario: "jsxImportSource option changed", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent }, - { path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) }, - { path: `${project}/node_modules/preact/jsx-runtime/index.d.ts`, content: jsxLibraryContent.replace("propA", "propB") }, - { path: `${project}/node_modules/preact/package.json`, content: JSON.stringify({ name: "preact", version: "0.0.1" }) }, - { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } - ], - modifyFs: host => host.writeFile(configFile.path, JSON.stringify({ compilerOptions: { ...jsxImportSourceOptions, jsxImportSource: "preact" } })), - optionsToExtend: ["--explainFiles"] - }); - - verifyIncrementalWatchEmit({ - subScenario: "jsxImportSource backing types added", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } - ], - modifyFs: host => { - host.createDirectory(`${project}/node_modules`); - host.createDirectory(`${project}/node_modules/react`); - host.createDirectory(`${project}/node_modules/react/jsx-runtime`); - host.writeFile(`${project}/node_modules/react/jsx-runtime/index.d.ts`, jsxLibraryContent); - host.writeFile(`${project}/node_modules/react/package.json`, JSON.stringify({ name: "react", version: "0.0.1" })); - } - }); + verifyIncrementalWatchEmit({ + subScenario: "jsxImportSource option changed", + files: () => [ + { path: ts.tscWatch.libFile.path, content: ts.libContent }, + { path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent }, + { path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) }, + { path: `${project}/node_modules/preact/jsx-runtime/index.d.ts`, content: jsxLibraryContent.replace("propA", "propB") }, + { path: `${project}/node_modules/preact/package.json`, content: JSON.stringify({ name: "preact", version: "0.0.1" }) }, + { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } + ], + modifyFs: host => host.writeFile(configFile.path, JSON.stringify({ compilerOptions: { ...jsxImportSourceOptions, jsxImportSource: "preact" } })), + optionsToExtend: ["--explainFiles"] + }); - verifyIncrementalWatchEmit({ - subScenario: "jsxImportSource backing types removed", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent }, - { path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) }, - { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } - ], - modifyFs: host => { - host.deleteFile(`${project}/node_modules/react/jsx-runtime/index.d.ts`); - host.deleteFile(`${project}/node_modules/react/package.json`); - } - }); + verifyIncrementalWatchEmit({ + subScenario: "jsxImportSource backing types added", + files: () => [ + { path: ts.tscWatch.libFile.path, content: ts.libContent }, + { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } + ], + modifyFs: host => { + host.createDirectory(`${project}/node_modules`); + host.createDirectory(`${project}/node_modules/react`); + host.createDirectory(`${project}/node_modules/react/jsx-runtime`); + host.writeFile(`${project}/node_modules/react/jsx-runtime/index.d.ts`, jsxLibraryContent); + host.writeFile(`${project}/node_modules/react/package.json`, JSON.stringify({ name: "react", version: "0.0.1" })); + } + }); - verifyIncrementalWatchEmit({ - subScenario: "importHelpers backing types removed", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/node_modules/tslib/index.d.ts`, content: "export function __assign(...args: any[]): any;" }, - { path: `${project}/node_modules/tslib/package.json`, content: JSON.stringify({ name: "tslib", version: "0.0.1" }) }, - { path: `${project}/index.tsx`, content: `export const x = {...{}};` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: { importHelpers: true } }) } - ], - modifyFs: host => { - host.deleteFile(`${project}/node_modules/tslib/index.d.ts`); - host.deleteFile(`${project}/node_modules/tslib/package.json`); - } - }); + verifyIncrementalWatchEmit({ + subScenario: "jsxImportSource backing types removed", + files: () => [ + { path: ts.tscWatch.libFile.path, content: ts.libContent }, + { path: `${project}/node_modules/react/jsx-runtime/index.d.ts`, content: jsxLibraryContent }, + { path: `${project}/node_modules/react/package.json`, content: JSON.stringify({ name: "react", version: "0.0.1" }) }, + { path: `${project}/index.tsx`, content: `export const App = () =>
;` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: jsxImportSourceOptions }) } + ], + modifyFs: host => { + host.deleteFile(`${project}/node_modules/react/jsx-runtime/index.d.ts`); + host.deleteFile(`${project}/node_modules/react/package.json`); + } }); - describe("editing module augmentation", () => { - verifyIncrementalWatchEmit({ - subScenario: "editing module augmentation", - files: () => [ - { path: libFile.path, content: libContent }, - { path: `${project}/node_modules/classnames/index.d.ts`, content: `export interface Result {} export default function classNames(): Result;` }, - { path: `${project}/src/types/classnames.d.ts`, content: `export {}; declare module "classnames" { interface Result { foo } }` }, - { path: `${project}/src/index.ts`, content: `import classNames from "classnames"; classNames().foo;` }, - { path: configFile.path, content: JSON.stringify({ compilerOptions: { module: "commonjs", incremental: true } }) }, - ], - modifyFs: host => { - // delete 'foo' - host.writeFile(`${project}/src/types/classnames.d.ts`, `export {}; declare module "classnames" { interface Result {} }`); - }, - }); + verifyIncrementalWatchEmit({ + subScenario: "importHelpers backing types removed", + files: () => [ + { path: ts.tscWatch.libFile.path, content: ts.libContent }, + { path: `${project}/node_modules/tslib/index.d.ts`, content: "export function __assign(...args: any[]): any;" }, + { path: `${project}/node_modules/tslib/package.json`, content: JSON.stringify({ name: "tslib", version: "0.0.1" }) }, + { path: `${project}/index.tsx`, content: `export const x = {...{}};` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: { importHelpers: true } }) } + ], + modifyFs: host => { + host.deleteFile(`${project}/node_modules/tslib/index.d.ts`); + host.deleteFile(`${project}/node_modules/tslib/package.json`); + } }); + }); - verifyTscWatch({ - scenario: "incremental", - subScenario: "tsbuildinfo has error", - sys: () => createWatchedSystem({ - "/src/project/main.ts": "export const x = 10;", - "/src/project/tsconfig.json": "{}", - "/src/project/tsconfig.tsbuildinfo": "Some random string", - [libFile.path]: libFile.content, - }), - commandLineArgs: ["--p", "src/project", "-i", "-w"], - changes: emptyArray + describe("editing module augmentation", () => { + verifyIncrementalWatchEmit({ + subScenario: "editing module augmentation", + files: () => [ + { path: ts.tscWatch.libFile.path, content: ts.libContent }, + { path: `${project}/node_modules/classnames/index.d.ts`, content: `export interface Result {} export default function classNames(): Result;` }, + { path: `${project}/src/types/classnames.d.ts`, content: `export {}; declare module "classnames" { interface Result { foo } }` }, + { path: `${project}/src/index.ts`, content: `import classNames from "classnames"; classNames().foo;` }, + { path: configFile.path, content: JSON.stringify({ compilerOptions: { module: "commonjs", incremental: true } }) }, + ], + modifyFs: host => { + // delete 'foo' + host.writeFile(`${project}/src/types/classnames.d.ts`, `export {}; declare module "classnames" { interface Result {} }`); + }, }); }); -} + + ts.tscWatch.verifyTscWatch({ + scenario: "incremental", + subScenario: "tsbuildinfo has error", + sys: () => ts.tscWatch.createWatchedSystem({ + "/src/project/main.ts": "export const x = 10;", + "/src/project/tsconfig.json": "{}", + "/src/project/tsconfig.tsbuildinfo": "Some random string", + [ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content, + }), + commandLineArgs: ["--p", "src/project", "-i", "-w"], + changes: ts.emptyArray + }); +}); diff --git a/src/testRunner/unittests/tscWatch/moduleResolution.ts b/src/testRunner/unittests/tscWatch/moduleResolution.ts index 8361da8532cbd..a03b0f8750824 100644 --- a/src/testRunner/unittests/tscWatch/moduleResolution.ts +++ b/src/testRunner/unittests/tscWatch/moduleResolution.ts @@ -1,330 +1,331 @@ -namespace ts.tscWatch { - describe("unittests:: tsc-watch:: moduleResolution", () => { - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: `watches for changes to package-json main fields`, - sys: () => createWatchedSystem([ - { - path: `${projectRoot}/packages/pkg1/package.json`, - content: JSON.stringify({ - name: "pkg1", - version: "1.0.0", - main: "build/index.js", - }) - }, - { - path: `${projectRoot}/packages/pkg1/index.ts`, - content: Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; + +describe("unittests:: tsc-watch:: moduleResolution", () => { + ts.tscWatch.verifyTscWatch({ + scenario: "moduleResolution", + subScenario: `watches for changes to package-json main fields`, + sys: () => ts.tscWatch.createWatchedSystem([ + { + path: `${ts.tscWatch.projectRoot}/packages/pkg1/package.json`, + content: JSON.stringify({ + name: "pkg1", + version: "1.0.0", + main: "build/index.js", + }) + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg1/index.ts`, + content: Utils.dedent` import type { TheNum } from 'pkg2' export const theNum: TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "build", - }, - }) - }, - { - path: `${projectRoot}/packages/pkg2/build/const.d.ts`, - content: `export type TheNum = 42;` - }, - { - path: `${projectRoot}/packages/pkg2/build/index.d.ts`, - content: `export type { TheNum } from './const.js';` - }, - { - path: `${projectRoot}/packages/pkg2/build/other.d.ts`, - content: `export type TheStr = string;` - }, - { - path: `${projectRoot}/packages/pkg2/package.json`, - content: JSON.stringify({ - name: "pkg2", - version: "1.0.0", - main: "build/index.js", - }) - }, - { - path: `${projectRoot}/node_modules/pkg2`, - symLink: `${projectRoot}/packages/pkg2`, - }, - libFile - ], { currentDirectory: projectRoot }), - commandLineArgs: ["--project", "./packages/pkg1/tsconfig.json", "-w", "--traceResolution"], - changes: [ - { - caption: "reports import errors after change to package file", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`), - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); // invalidates failed lookups - sys.runQueuedTimeoutCallbacks(); // actual update + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "build", }, + }) + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/build/const.d.ts`, + content: `export type TheNum = 42;` + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/build/index.d.ts`, + content: `export type { TheNum } from './const.js';` + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/build/other.d.ts`, + content: `export type TheStr = string;` + }, + { + path: `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, + content: JSON.stringify({ + name: "pkg2", + version: "1.0.0", + main: "build/index.js", + }) + }, + { + path: `${ts.tscWatch.projectRoot}/node_modules/pkg2`, + symLink: `${ts.tscWatch.projectRoot}/packages/pkg2`, + }, + ts.tscWatch.libFile + ], { currentDirectory: ts.tscWatch.projectRoot }), + commandLineArgs: ["--project", "./packages/pkg1/tsconfig.json", "-w", "--traceResolution"], + changes: [ + { + caption: "reports import errors after change to package file", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, `index.js`, `other.js`), + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); // invalidates failed lookups + sys.runQueuedTimeoutCallbacks(); // actual update }, - { - caption: "removes those errors when a package file is changed back", - change: sys => replaceFileText(sys, `${projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`), - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); // invalidates failed lookups - sys.runQueuedTimeoutCallbacks(); // actual update - }, + }, + { + caption: "removes those errors when a package file is changed back", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/packages/pkg2/package.json`, `other.js`, `index.js`), + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); // invalidates failed lookups + sys.runQueuedTimeoutCallbacks(); // actual update }, - ] - }); + }, + ] + }); - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: "diagnostics from cache", - sys: () => createWatchedSystem([ - { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - moduleResolution: "nodenext", - outDir: "./dist", - declaration: true, - declarationDir: "./types" - }, - }) - }, - { - path: `${projectRoot}/package.json`, - content: JSON.stringify({ - name: "@this/package", - type: "module", - exports: { - ".": { - default: "./dist/index.js", - types: "./types/index.d.ts" - } + ts.tscWatch.verifyTscWatch({ + scenario: "moduleResolution", + subScenario: "diagnostics from cache", + sys: () => ts.tscWatch.createWatchedSystem([ + { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + moduleResolution: "nodenext", + outDir: "./dist", + declaration: true, + declarationDir: "./types" + }, + }) + }, + { + path: `${ts.tscWatch.projectRoot}/package.json`, + content: JSON.stringify({ + name: "@this/package", + type: "module", + exports: { + ".": { + default: "./dist/index.js", + types: "./types/index.d.ts" } - }) - }, - { - path: `${projectRoot}/index.ts`, - content: Utils.dedent` + } + }) + }, + { + path: `${ts.tscWatch.projectRoot}/index.ts`, + content: Utils.dedent` import * as me from "@this/package"; me.thing() export function thing(): void {} ` - }, - libFile - ], { currentDirectory: projectRoot }), - commandLineArgs: ["-w", "--traceResolution"], - changes: emptyArray - }); + }, + ts.tscWatch.libFile + ], { currentDirectory: ts.tscWatch.projectRoot }), + commandLineArgs: ["-w", "--traceResolution"], + changes: ts.emptyArray + }); - describe("package json file is edited", () => { - function getSys(packageFileContents: string) { - const configFile: File = { - path: `${projectRoot}/src/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - target: "es2016", - module: "Node16", - outDir: "../out" - } - }) - }; - const packageFile: File = { - path: `${projectRoot}/package.json`, - content: packageFileContents - }; - const fileA: File = { - path: `${projectRoot}/src/fileA.ts`, - content: Utils.dedent` + describe("package json file is edited", () => { + function getSys(packageFileContents: string) { + const configFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/src/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + target: "es2016", + module: "Node16", + outDir: "../out" + } + }) + }; + const packageFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/package.json`, + content: packageFileContents + }; + const fileA: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/src/fileA.ts`, + content: Utils.dedent` import { foo } from "./fileB.mjs"; foo(); ` - }; - const fileB: File = { - path: `${projectRoot}/project/src/fileB.mts`, - content: Utils.dedent` + }; + const fileB: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/project/src/fileB.mts`, + content: Utils.dedent` export function foo() { } ` - }; - return createWatchedSystem( - [configFile, fileA, fileB, packageFile, { ...libFile, path: "/a/lib/lib.es2016.full.d.ts" }], - { currentDirectory: projectRoot } - ); - } - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: "package json file is edited", - commandLineArgs: ["--w", "--p", "src", "--extendedDiagnostics", "-traceResolution", "--explainFiles"], - sys: () => getSys(JSON.stringify({ name: "app", version: "1.0.0" })), - changes: [ - { - caption: "Modify package json file to add type module", - change: sys => sys.writeFile(`${projectRoot}/package.json`, JSON.stringify({ - name: "app", version: "1.0.0", type: "module", - })), - timeouts: host => { - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - }, - }, - { - caption: "Modify package.json file to remove type module", - change: sys => sys.writeFile(`${projectRoot}/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })), - timeouts: host => { - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - }, - }, - { - caption: "Delete package.json", - change: sys => sys.deleteFile(`${projectRoot}/package.json`), - timeouts: host => { - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - }, - }, - { - caption: "Modify package json file to add type module", - change: sys => sys.writeFile(`${projectRoot}/package.json`, JSON.stringify({ - name: "app", version: "1.0.0", type: "module", - })), - timeouts: host => { - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - }, - }, - { - caption: "Delete package.json", - change: sys => sys.deleteFile(`${projectRoot}/package.json`), - timeouts: host => { - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - }, - }, - ], - }); - - verifyTscWatch({ - scenario: "moduleResolution", - subScenario: "package json file is edited when package json with type module exists", - commandLineArgs: ["--w", "--p", "src", "--extendedDiagnostics", "-traceResolution", "--explainFiles"], - sys: () => getSys(JSON.stringify({ - name: "app", version: "1.0.0", type: "module", - })), - changes: [ - { - caption: "Modify package.json file to remove type module", - change: sys => sys.writeFile(`${projectRoot}/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })), - timeouts: host => { - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - }, - }, - { - caption: "Modify package json file to add type module", - change: sys => sys.writeFile(`${projectRoot}/package.json`, JSON.stringify({ - name: "app", version: "1.0.0", type: "module", - })), - timeouts: host => { - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - }, - }, - { - caption: "Delete package.json", - change: sys => sys.deleteFile(`${projectRoot}/package.json`), - timeouts: host => { - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - }, - }, - { - caption: "Modify package json file to without type module", - change: sys => sys.writeFile(`${projectRoot}/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })), - timeouts: host => { - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - }, - }, - { - caption: "Delete package.json", - change: sys => sys.deleteFile(`${projectRoot}/package.json`), - timeouts: host => { - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - }, - }, - ], - }); - }); - - verifyTscWatch({ + }; + return ts.tscWatch.createWatchedSystem( + [configFile, fileA, fileB, packageFile, { ...ts.tscWatch.libFile, path: "/a/lib/lib.es2016.full.d.ts" }], + { currentDirectory: ts.tscWatch.projectRoot } + ); + } + ts.tscWatch.verifyTscWatch({ scenario: "moduleResolution", - subScenario: "module resolutions from file are partially used", - sys: () => createWatchedSystem([ + subScenario: "package json file is edited", + commandLineArgs: ["--w", "--p", "src", "--extendedDiagnostics", "-traceResolution", "--explainFiles"], + sys: () => getSys(JSON.stringify({ name: "app", version: "1.0.0" })), + changes: [ { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { moduleResolution: "node16" }, - }) + caption: "Modify package json file to add type module", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({ + name: "app", version: "1.0.0", type: "module", + })), + timeouts: host => { + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + }, }, { - path: `${projectRoot}/index.ts`, - content: Utils.dedent` - import type { ImportInterface } from "pkg" assert { "resolution-mode": "import" }; - import type { RequireInterface } from "pkg1" assert { "resolution-mode": "require" }; - import {x} from "./a"; - ` + caption: "Modify package.json file to remove type module", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })), + timeouts: host => { + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + }, }, { - path: `${projectRoot}/a.ts`, - content: Utils.dedent` - export const x = 10; - ` + caption: "Delete package.json", + change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/package.json`), + timeouts: host => { + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + }, }, { - path: `${projectRoot}/node_modules/pkg/package.json`, - content: JSON.stringify({ - name: "pkg", - version: "0.0.1", - exports: { - import: "./import.js", - require: "./require.js" - } - }) + caption: "Modify package json file to add type module", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({ + name: "app", version: "1.0.0", type: "module", + })), + timeouts: host => { + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + }, + }, + { + caption: "Delete package.json", + change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/package.json`), + timeouts: host => { + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + }, }, + ], + }); + + ts.tscWatch.verifyTscWatch({ + scenario: "moduleResolution", + subScenario: "package json file is edited when package json with type module exists", + commandLineArgs: ["--w", "--p", "src", "--extendedDiagnostics", "-traceResolution", "--explainFiles"], + sys: () => getSys(JSON.stringify({ + name: "app", version: "1.0.0", type: "module", + })), + changes: [ { - path: `${projectRoot}/node_modules/pkg/import.d.ts`, - content: `export interface ImportInterface {}` + caption: "Modify package.json file to remove type module", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })), + timeouts: host => { + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + }, }, { - path: `${projectRoot}/node_modules/pkg/require.d.ts`, - content: `export interface RequireInterface {}` + caption: "Modify package json file to add type module", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({ + name: "app", version: "1.0.0", type: "module", + })), + timeouts: host => { + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + }, }, { - path: `${projectRoot}/node_modules/pkg1/package.json`, - content: JSON.stringify({ - name: "pkg1", - version: "0.0.1", - exports: { - import: "./import.js", - require: "./require.js" - } - }) + caption: "Delete package.json", + change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/package.json`), + timeouts: host => { + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + }, }, { - path: `${projectRoot}/node_modules/pkg1/import.d.ts`, - content: `export interface ImportInterface {}` + caption: "Modify package json file to without type module", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/package.json`, JSON.stringify({ name: "app", version: "1.0.0" })), + timeouts: host => { + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + }, }, - libFile - ], { currentDirectory: projectRoot }), - commandLineArgs: ["-w", "--traceResolution"], - changes: [ { - caption: "modify aFile by adding import", - change: sys => sys.appendFile(`${projectRoot}/a.ts`, `import type { ImportInterface } from "pkg" assert { "resolution-mode": "import" }`), - timeouts: sys => sys.runQueuedTimeoutCallbacks(), - } - ] + caption: "Delete package.json", + change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/package.json`), + timeouts: host => { + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + }, + }, + ], }); }); -} \ No newline at end of file + + ts.tscWatch.verifyTscWatch({ + scenario: "moduleResolution", + subScenario: "module resolutions from file are partially used", + sys: () => ts.tscWatch.createWatchedSystem([ + { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { moduleResolution: "node16" }, + }) + }, + { + path: `${ts.tscWatch.projectRoot}/index.ts`, + content: Utils.dedent` + import type { ImportInterface } from "pkg" assert { "resolution-mode": "import" }; + import type { RequireInterface } from "pkg1" assert { "resolution-mode": "require" }; + import {x} from "./a"; + ` + }, + { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: Utils.dedent` + export const x = 10; + ` + }, + { + path: `${ts.tscWatch.projectRoot}/node_modules/pkg/package.json`, + content: JSON.stringify({ + name: "pkg", + version: "0.0.1", + exports: { + import: "./import.js", + require: "./require.js" + } + }) + }, + { + path: `${ts.tscWatch.projectRoot}/node_modules/pkg/import.d.ts`, + content: `export interface ImportInterface {}` + }, + { + path: `${ts.tscWatch.projectRoot}/node_modules/pkg/require.d.ts`, + content: `export interface RequireInterface {}` + }, + { + path: `${ts.tscWatch.projectRoot}/node_modules/pkg1/package.json`, + content: JSON.stringify({ + name: "pkg1", + version: "0.0.1", + exports: { + import: "./import.js", + require: "./require.js" + } + }) + }, + { + path: `${ts.tscWatch.projectRoot}/node_modules/pkg1/import.d.ts`, + content: `export interface ImportInterface {}` + }, + ts.tscWatch.libFile + ], { currentDirectory: ts.tscWatch.projectRoot }), + commandLineArgs: ["-w", "--traceResolution"], + changes: [ + { + caption: "modify aFile by adding import", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/a.ts`, `import type { ImportInterface } from "pkg" assert { "resolution-mode": "import" }`), + timeouts: sys => sys.runQueuedTimeoutCallbacks(), + } + ] + }); +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tscWatch/nodeNextWatch.ts b/src/testRunner/unittests/tscWatch/nodeNextWatch.ts index a7cb1d4ed949a..744b60a1d07be 100644 --- a/src/testRunner/unittests/tscWatch/nodeNextWatch.ts +++ b/src/testRunner/unittests/tscWatch/nodeNextWatch.ts @@ -1,58 +1,59 @@ -namespace ts.tscWatch { - describe("unittests:: tsc-watch:: nodeNextWatch:: emit when module emit is specified as nodenext", () => { - verifyTscWatch({ - scenario: "nodenext watch emit", - subScenario: "esm-mode file is edited", - commandLineArgs: ["--w", "--p", "/project/tsconfig.json"], - sys: () => { - const configFile: File = { - path: "/project/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - strict: true, - target: "es2020", - module: "nodenext", - moduleResolution: "nodenext", - outDir: "../dist" - } - }) - }; - const packageFile: File = { - path: "/project/package.json", - content: JSON.stringify({ - name: "some-proj", - version: "1.0.0", - description: "", - type: "module", - main: "index.js", - }) - }; - const file1: File = { - path: "/project/src/index.ts", - content: Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; + +describe("unittests:: tsc-watch:: nodeNextWatch:: emit when module emit is specified as nodenext", () => { + ts.tscWatch.verifyTscWatch({ + scenario: "nodenext watch emit", + subScenario: "esm-mode file is edited", + commandLineArgs: ["--w", "--p", "/project/tsconfig.json"], + sys: () => { + const configFile: ts.tscWatch.File = { + path: "/project/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + strict: true, + target: "es2020", + module: "nodenext", + moduleResolution: "nodenext", + outDir: "../dist" + } + }) + }; + const packageFile: ts.tscWatch.File = { + path: "/project/package.json", + content: JSON.stringify({ + name: "some-proj", + version: "1.0.0", + description: "", + type: "module", + main: "index.js", + }) + }; + const file1: ts.tscWatch.File = { + path: "/project/src/index.ts", + content: Utils.dedent` import * as Thing from "thing"; Thing.fn();` - }; - const declFile: File = { - path: "/project/src/deps.d.ts", - content: `declare module "thing";` - }; - return createWatchedSystem([configFile, file1, declFile, packageFile, { ...libFile, path: "/a/lib/lib.es2020.full.d.ts" }]); - }, - changes: [ - { - caption: "Modify typescript file", - change: sys => sys.modifyFile( - "/project/src/index.ts", - Utils.dedent` + }; + const declFile: ts.tscWatch.File = { + path: "/project/src/deps.d.ts", + content: `declare module "thing";` + }; + return ts.tscWatch.createWatchedSystem([configFile, file1, declFile, packageFile, { ...ts.tscWatch.libFile, path: "/a/lib/lib.es2020.full.d.ts" }]); + }, + changes: [ + { + caption: "Modify typescript file", + change: sys => sys.modifyFile( + "/project/src/index.ts", + Utils.dedent` import * as Thing from "thing"; Thing.fn();`, - {}, - ), - timeouts: runQueuedTimeoutCallbacks, - } - ], - }); + {}, + ), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ], }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tscWatch/programUpdates.ts b/src/testRunner/unittests/tscWatch/programUpdates.ts index c71b22c270922..725e652d13072 100644 --- a/src/testRunner/unittests/tscWatch/programUpdates.ts +++ b/src/testRunner/unittests/tscWatch/programUpdates.ts @@ -1,1126 +1,1128 @@ -namespace ts.tscWatch { - describe("unittests:: tsc-watch:: program updates", () => { - const scenario = "programUpdates"; - const configFilePath = "/a/b/tsconfig.json"; - const configFile: File = { - path: configFilePath, - content: `{}` - }; - verifyTscWatch({ - scenario, - subScenario: "create watch without config file", - commandLineArgs: ["-w", "/a/b/c/app.ts"], - sys: () => { - const appFile: File = { - path: "/a/b/c/app.ts", - content: ` +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; + +describe("unittests:: tsc-watch:: program updates", () => { + const scenario = "programUpdates"; + const configFilePath = "/a/b/tsconfig.json"; + const configFile: ts.tscWatch.File = { + path: configFilePath, + content: `{}` + }; + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "create watch without config file", + commandLineArgs: ["-w", "/a/b/c/app.ts"], + sys: () => { + const appFile: ts.tscWatch.File = { + path: "/a/b/c/app.ts", + content: ` import {f} from "./module" console.log(f) ` - }; + }; - const moduleFile: File = { - path: "/a/b/c/module.d.ts", - content: `export let x: number` - }; - return createWatchedSystem([appFile, moduleFile, libFile]); - }, - changes: emptyArray - }); + const moduleFile: ts.tscWatch.File = { + path: "/a/b/c/module.d.ts", + content: `export let x: number` + }; + return ts.tscWatch.createWatchedSystem([appFile, moduleFile, ts.tscWatch.libFile]); + }, + changes: ts.emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "can handle tsconfig file name with difference casing", - commandLineArgs: ["-w", "-p", "/A/B/tsconfig.json"], - sys: () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: configFilePath, - content: JSON.stringify({ - include: ["app.ts"] - }) - }; - return createWatchedSystem([f1, libFile, config], { useCaseSensitiveFileNames: false }); - }, - changes: emptyArray - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "can handle tsconfig file name with difference casing", + commandLineArgs: ["-w", "-p", "/A/B/tsconfig.json"], + sys: () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: configFilePath, + content: JSON.stringify({ + include: ["app.ts"] + }) + }; + return ts.tscWatch.createWatchedSystem([f1, ts.tscWatch.libFile, config], { useCaseSensitiveFileNames: false }); + }, + changes: ts.emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "create configured project without file list", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile: File = { - path: configFilePath, - content: ` + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "create configured project without file list", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile: ts.tscWatch.File = { + path: configFilePath, + content: ` { "compilerOptions": {}, "exclude": [ "e" ] }` - }; - const file1: File = { - path: "/a/b/c/f1.ts", - content: "let x = 1" - }; - const file2: File = { - path: "/a/b/d/f2.ts", - content: "let y = 1" - }; - const file3: File = { - path: "/a/b/e/f3.ts", - content: "let z = 1" - }; - return createWatchedSystem([configFile, libFile, file1, file2, file3]); - }, - changes: emptyArray - }); + }; + const file1: ts.tscWatch.File = { + path: "/a/b/c/f1.ts", + content: "let x = 1" + }; + const file2: ts.tscWatch.File = { + path: "/a/b/d/f2.ts", + content: "let y = 1" + }; + const file3: ts.tscWatch.File = { + path: "/a/b/e/f3.ts", + content: "let z = 1" + }; + return ts.tscWatch.createWatchedSystem([configFile, ts.tscWatch.libFile, file1, file2, file3]); + }, + changes: ts.emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "add new files to a configured program without file list", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => createWatchedSystem([commonFile1, libFile, configFile]), - changes: [ - { - caption: "Create commonFile2", - change: sys => sys.writeFile(commonFile2.path, commonFile2.content), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "add new files to a configured program without file list", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => ts.tscWatch.createWatchedSystem([ts.tscWatch.commonFile1, ts.tscWatch.libFile, configFile]), + changes: [ + { + caption: "Create commonFile2", + change: sys => sys.writeFile(ts.tscWatch.commonFile2.path, ts.tscWatch.commonFile2.content), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "should ignore non-existing files specified in the config file", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile: File = { - path: configFilePath, - content: `{ + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "should ignore non-existing files specified in the config file", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile: ts.tscWatch.File = { + path: configFilePath, + content: `{ "compilerOptions": {}, "files": [ "commonFile1.ts", "commonFile3.ts" ] }` - }; - return createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); - }, - changes: emptyArray - }); + }; + return ts.tscWatch.createWatchedSystem([ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, ts.tscWatch.libFile, configFile]); + }, + changes: ts.emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "handle recreated files correctly", - commandLineArgs: ["-w", "-p", configFilePath, "--explainFiles"], - sys: () => { - return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "handle recreated files correctly", + commandLineArgs: ["-w", "-p", configFilePath, "--explainFiles"], + sys: () => { + return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile]); + }, + changes: [ + { + caption: "change file to ensure signatures are updated", + change: sys => sys.appendFile(ts.tscWatch.commonFile2.path, ";let xy = 10;"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "change file to ensure signatures are updated", - change: sys => sys.appendFile(commonFile2.path, ";let xy = 10;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "delete file2", - change: sys => sys.deleteFile(commonFile2.path), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "recreate file2", - change: sys => sys.writeFile(commonFile2.path, commonFile2.content), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + { + caption: "delete file2", + change: sys => sys.deleteFile(ts.tscWatch.commonFile2.path), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "recreate file2", + change: sys => sys.writeFile(ts.tscWatch.commonFile2.path, ts.tscWatch.commonFile2.content), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "handles the missing files - that were added to program because they were added with tripleSlashRefs", - commandLineArgs: ["-w", "/a/b/commonFile1.ts"], - sys: () => { - const file1: File = { - path: commonFile1.path, - content: `/// + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "handles the missing files - that were added to program because they were added with tripleSlashRefs", + commandLineArgs: ["-w", "/a/b/commonFile1.ts"], + sys: () => { + const file1: ts.tscWatch.File = { + path: ts.tscWatch.commonFile1.path, + content: `/// let x = y` - }; - return createWatchedSystem([file1, libFile]); - }, - changes: [ - { - caption: "create file2", - change: sys => sys.writeFile(commonFile2.path, commonFile2.content), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + }; + return ts.tscWatch.createWatchedSystem([file1, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "create file2", + change: sys => sys.writeFile(ts.tscWatch.commonFile2.path, ts.tscWatch.commonFile2.content), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "should reflect change in config file", - commandLineArgs: ["-w", "-p", configFilePath, "--explainFiles"], - sys: () => { - const configFile: File = { - path: configFilePath, - content: `{ + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "should reflect change in config file", + commandLineArgs: ["-w", "-p", configFilePath, "--explainFiles"], + sys: () => { + const configFile: ts.tscWatch.File = { + path: configFilePath, + content: `{ "compilerOptions": {}, - "files": ["${commonFile1.path}", "${commonFile2.path}"] + "files": ["${ts.tscWatch.commonFile1.path}", "${ts.tscWatch.commonFile2.path}"] }` - }; - return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); + }; + return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile]); + }, + changes: [ + { + caption: "change file to ensure signatures are updated", + change: sys => sys.appendFile(ts.tscWatch.commonFile2.path, ";let xy = 10;"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "change file to ensure signatures are updated", - change: sys => sys.appendFile(commonFile2.path, ";let xy = 10;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Change config", - change: sys => sys.writeFile(configFilePath, `{ + { + caption: "Change config", + change: sys => sys.writeFile(configFilePath, `{ "compilerOptions": {}, - "files": ["${commonFile1.path}"] + "files": ["${ts.tscWatch.commonFile1.path}"] }`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "works correctly when config file is changed but its content havent", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile: File = { - path: configFilePath, - content: `{ + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "works correctly when config file is changed but its content havent", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile: ts.tscWatch.File = { + path: configFilePath, + content: `{ "compilerOptions": {}, - "files": ["${commonFile1.path}", "${commonFile2.path}"] + "files": ["${ts.tscWatch.commonFile1.path}", "${ts.tscWatch.commonFile2.path}"] }` - }; - return createWatchedSystem([libFile, commonFile1, commonFile2, configFile]); - }, - changes: [ - { - caption: "Modify config without changing content", - change: sys => sys.modifyFile(configFilePath, `{ + }; + return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile]); + }, + changes: [ + { + caption: "Modify config without changing content", + change: sys => sys.modifyFile(configFilePath, `{ "compilerOptions": {}, - "files": ["${commonFile1.path}", "${commonFile2.path}"] + "files": ["${ts.tscWatch.commonFile1.path}", "${ts.tscWatch.commonFile2.path}"] }`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "Updates diagnostics when '--noUnusedLabels' changes", - commandLineArgs: ["-w", "-p", "/tsconfig.json"], - sys: () => { - const aTs: File = { - path: "/a.ts", - content: "label: while (1) {}" - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { allowUnusedLabels: true } - }) - }; - return createWatchedSystem([libFile, aTs, tsconfig]); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "Updates diagnostics when '--noUnusedLabels' changes", + commandLineArgs: ["-w", "-p", "/tsconfig.json"], + sys: () => { + const aTs: ts.tscWatch.File = { + path: "/a.ts", + content: "label: while (1) {}" + }; + const tsconfig: ts.tscWatch.File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { allowUnusedLabels: true } + }) + }; + return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, aTs, tsconfig]); + }, + changes: [ + { + caption: "Disable allowUnsusedLabels", + change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ + compilerOptions: { allowUnusedLabels: false } + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun }, - changes: [ - { - caption: "Disable allowUnsusedLabels", - change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ - compilerOptions: { allowUnusedLabels: false } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - { - caption: "Enable allowUnsusedLabels", - change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ - compilerOptions: { allowUnusedLabels: true } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + { + caption: "Enable allowUnsusedLabels", + change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ + compilerOptions: { allowUnusedLabels: true } + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates diagnostics and emit for decorators", - commandLineArgs: ["-w"], - sys: () => { - const aTs: File = { - path: "/a.ts", - content: `import {B} from './b' + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "updates diagnostics and emit for decorators", + commandLineArgs: ["-w"], + sys: () => { + const aTs: ts.tscWatch.File = { + path: "/a.ts", + content: `import {B} from './b' @((_) => {}) export class A { constructor(p: B) {} }`, - }; - const bTs: File = { - path: "/b.ts", - content: `export class B {}`, - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { target: "es6", importsNotUsedAsValues: "error" } - }) - }; - return createWatchedSystem([libFile, aTs, bTs, tsconfig]); - }, - changes: [ - { - caption: "Enable experimentalDecorators", - change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ - compilerOptions: { target: "es6", importsNotUsedAsValues: "error", experimentalDecorators: true } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, + }; + const bTs: ts.tscWatch.File = { + path: "/b.ts", + content: `export class B {}`, + }; + const tsconfig: ts.tscWatch.File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { target: "es6", importsNotUsedAsValues: "error" } + }) + }; + return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, aTs, bTs, tsconfig]); + }, + changes: [ + { + caption: "Enable experimentalDecorators", + change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ + compilerOptions: { target: "es6", importsNotUsedAsValues: "error", experimentalDecorators: true } + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Enable emitDecoratorMetadata", - change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ - compilerOptions: { target: "es6", importsNotUsedAsValues: "error", experimentalDecorators: true, emitDecoratorMetadata: true } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + }, + { + caption: "Enable emitDecoratorMetadata", + change: sys => sys.modifyFile("/tsconfig.json", JSON.stringify({ + compilerOptions: { target: "es6", importsNotUsedAsValues: "error", experimentalDecorators: true, emitDecoratorMetadata: true } + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "files explicitly excluded in config file", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile: File = { - path: configFilePath, - content: `{ + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "files explicitly excluded in config file", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile: ts.tscWatch.File = { + path: configFilePath, + content: `{ "compilerOptions": {}, "exclude": ["/a/c"] }` - }; - const excludedFile1: File = { - path: "/a/c/excluedFile1.ts", - content: `let t = 1;` - }; - return createWatchedSystem([libFile, commonFile1, commonFile2, excludedFile1, configFile]); - }, - changes: emptyArray - }); + }; + const excludedFile1: ts.tscWatch.File = { + path: "/a/c/excluedFile1.ts", + content: `let t = 1;` + }; + return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, excludedFile1, configFile]); + }, + changes: ts.emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "should properly handle module resolution changes in config file", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1: File = { - path: "/a/b/file1.ts", - content: `import { T } from "module1";` - }; - const nodeModuleFile: File = { - path: "/a/b/node_modules/module1.ts", - content: `export interface T {}` - }; - const classicModuleFile: File = { - path: "/a/module1.ts", - content: `export interface T {}` - }; - const configFile: File = { - path: configFilePath, - content: `{ + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "should properly handle module resolution changes in config file", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1: ts.tscWatch.File = { + path: "/a/b/file1.ts", + content: `import { T } from "module1";` + }; + const nodeModuleFile: ts.tscWatch.File = { + path: "/a/b/node_modules/module1.ts", + content: `export interface T {}` + }; + const classicModuleFile: ts.tscWatch.File = { + path: "/a/module1.ts", + content: `export interface T {}` + }; + const configFile: ts.tscWatch.File = { + path: configFilePath, + content: `{ "compilerOptions": { "moduleResolution": "node" }, "files": ["${file1.path}"] }` - }; - return createWatchedSystem([libFile, file1, nodeModuleFile, classicModuleFile, configFile]); - }, - changes: [ - { - caption: "Change module resolution to classic", - change: sys => sys.writeFile(configFile.path, `{ + }; + return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, file1, nodeModuleFile, classicModuleFile, configFile]); + }, + changes: [ + { + caption: "Change module resolution to classic", + change: sys => sys.writeFile(configFile.path, `{ "compilerOptions": { "moduleResolution": "classic" }, "files": ["/a/b/file1.ts"] }`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "should tolerate config file errors and still try to build a project", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile: File = { - path: configFilePath, - content: `{ + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "should tolerate config file errors and still try to build a project", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile: ts.tscWatch.File = { + path: configFilePath, + content: `{ "compilerOptions": { "module": "none", "allowAnything": true }, "someOtherProperty": {} }` - }; - return createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "changes in files are reflected in project structure", - commandLineArgs: ["-w", "/a/b/f1.ts", "--explainFiles"], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: `export let x = 1` - }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` - }; - return createWatchedSystem([file1, file2, file3, libFile]); - }, - changes: [ - { - caption: "Modify f2 to include f3", - // now inferred project should inclule file3 - change: sys => sys.modifyFile("/a/b/f2.ts", `export * from "../c/f3"`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); - - verifyTscWatch({ - scenario, - subScenario: "deleted files affect project structure", - commandLineArgs: ["-w", "/a/b/f1.ts", "--noImplicitAny"], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: `export * from "../c/f3"` - }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` - }; - return createWatchedSystem([file1, file2, file3, libFile]); - }, - changes: [ - { - caption: "Delete f2", - change: sys => sys.deleteFile("/a/b/f2.ts"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + }; + return ts.tscWatch.createWatchedSystem([ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, ts.tscWatch.libFile, configFile]); + }, + changes: ts.emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "deleted files affect project structure-2", - commandLineArgs: ["-w", "/a/b/f1.ts", "/a/c/f3.ts", "--noImplicitAny"], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: `export * from "../c/f3"` - }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` - }; - return createWatchedSystem([file1, file2, file3, libFile]); - }, - changes: [ - { - caption: "Delete f2", - change: sys => sys.deleteFile("/a/b/f2.ts"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "changes in files are reflected in project structure", + commandLineArgs: ["-w", "/a/b/f1.ts", "--explainFiles"], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export let x = 1` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + return ts.tscWatch.createWatchedSystem([file1, file2, file3, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "Modify f2 to include f3", + // now inferred project should inclule file3 + change: sys => sys.modifyFile("/a/b/f2.ts", `export * from "../c/f3"`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "config file includes the file", - commandLineArgs: ["-w", "-p", "/a/c/tsconfig.json"], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "export let x = 5" - }; - const file2 = { - path: "/a/c/f2.ts", - content: `import {x} from "../b/f1"` - }; - const file3 = { - path: "/a/c/f3.ts", - content: "export let y = 1" - }; - const configFile = { - path: "/a/c/tsconfig.json", - content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) - }; - return createWatchedSystem([file1, file2, file3, libFile, configFile]); - }, - changes: emptyArray - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "deleted files affect project structure", + commandLineArgs: ["-w", "/a/b/f1.ts", "--noImplicitAny"], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export * from "../c/f3"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + return ts.tscWatch.createWatchedSystem([file1, file2, file3, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "Delete f2", + change: sys => sys.deleteFile("/a/b/f2.ts"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "change module to none", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "export {}\ndeclare global {}" - }; - return createWatchedSystem([file1, libFile, configFile]); - }, - changes: [{ - caption: "change `module` to 'none'", - timeouts: checkSingleTimeoutQueueLengthAndRun, - change: sys => { - sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: { module: "none" } })); - } - }] - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "deleted files affect project structure-2", + commandLineArgs: ["-w", "/a/b/f1.ts", "/a/c/f3.ts", "--noImplicitAny"], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export * from "../c/f3"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + return ts.tscWatch.createWatchedSystem([file1, file2, file3, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "Delete f2", + change: sys => sys.deleteFile("/a/b/f2.ts"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - it("two watch programs are not affected by each other", () => { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "config file includes the file", + commandLineArgs: ["-w", "-p", "/a/c/tsconfig.json"], + sys: () => { const file1 = { path: "/a/b/f1.ts", - content: ` - export * from "../c/f2"; - export * from "../d/f3";` + content: "export let x = 5" }; const file2 = { path: "/a/c/f2.ts", - content: "export let x = 1;" + content: `import {x} from "../b/f1"` }; const file3 = { - path: "/a/d/f3.ts", - content: "export let y = 1;" - }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([libFile, file1, file2, file3])); - const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ - rootFiles: [file2.path, file3.path], - system: sys, - options: { allowNonTsExtensions: true }, - cb, - watchOptions: undefined - }); - createWatchProgram(host); - baseline.push(`${sys.getExecutingFilePath()} --w ${file2.path} ${file3.path}`); - watchBaseline({ - baseline, - getPrograms, - oldPrograms: emptyArray, - sys, - oldSnap, - }); + path: "/a/c/f3.ts", + content: "export let y = 1" + }; + const configFile = { + path: "/a/c/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) + }; + return ts.tscWatch.createWatchedSystem([file1, file2, file3, ts.tscWatch.libFile, configFile]); + }, + changes: ts.emptyArray + }); - const {cb: cb2, getPrograms: getPrograms2 } = commandLineCallbacks(sys); - const oldSnap2 = sys.snap(); - baseline.push("createing separate watcher"); - createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ - rootFiles:[file1.path], - system: sys, - options: { allowNonTsExtensions: true }, - cb: cb2, - watchOptions: undefined - })); - watchBaseline({ - baseline, - getPrograms: getPrograms2, - oldPrograms: emptyArray, - sys, - oldSnap: oldSnap2, - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "change module to none", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: "export {}\ndeclare global {}" + }; + return ts.tscWatch.createWatchedSystem([file1, ts.tscWatch.libFile, configFile]); + }, + changes: [{ + caption: "change `module` to 'none'", + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + change: sys => { + sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: { module: "none" } })); + } + }] + }); - sys.checkTimeoutQueueLength(0); - baseline.push(`First program is not updated:: ${getPrograms() === emptyArray}`); - baseline.push(`Second program is not updated:: ${getPrograms2() === emptyArray}`); - Harness.Baseline.runBaseline(`tscWatch/${scenario}/two-watch-programs-are-not-affected-by-each-other.js`, baseline.join("\r\n")); + it("two watch programs are not affected by each other", () => { + const file1 = { + path: "/a/b/f1.ts", + content: ` + export * from "../c/f2"; + export * from "../d/f3";` + }; + const file2 = { + path: "/a/c/f2.ts", + content: "export let x = 1;" + }; + const file3 = { + path: "/a/d/f3.ts", + content: "export let y = 1;" + }; + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, file1, file2, file3])); + const host = ts.tscWatch.createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ + rootFiles: [file2.path, file3.path], + system: sys, + options: { allowNonTsExtensions: true }, + cb, + watchOptions: undefined }); - - verifyTscWatch({ - scenario, - subScenario: "can correctly update configured project when set of root files has changed (new file on disk)", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - return createWatchedSystem([file1, libFile, configFile]); - }, - changes: [ - { - caption: "Write f2", - change: sys => sys.writeFile("/a/b/f2.ts", "let y = 1"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] + ts.createWatchProgram(host); + baseline.push(`${sys.getExecutingFilePath()} --w ${file2.path} ${file3.path}`); + ts.tscWatch.watchBaseline({ + baseline, + getPrograms, + oldPrograms: ts.emptyArray, + sys, + oldSnap, }); - verifyTscWatch({ - scenario, - subScenario: "can correctly update configured project when set of root files has changed (new file in list of files)", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: configFilePath, - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) - }; - return createWatchedSystem([file1, file2, libFile, configFile]); - }, - changes: [ - { - caption: "Modify config to make f2 as root too", - change: sys => sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] + const {cb: cb2, getPrograms: getPrograms2 } = ts.commandLineCallbacks(sys); + const oldSnap2 = sys.snap(); + baseline.push("createing separate watcher"); + ts.createWatchProgram(ts.tscWatch.createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ + rootFiles:[file1.path], + system: sys, + options: { allowNonTsExtensions: true }, + cb: cb2, + watchOptions: undefined + })); + ts.tscWatch.watchBaseline({ + baseline, + getPrograms: getPrograms2, + oldPrograms: ts.emptyArray, + sys, + oldSnap: oldSnap2, }); - verifyTscWatch({ - scenario, - subScenario: "correctly parses wild card directories from implicit glob when two keys differ only in directory seperator", - commandLineArgs: ["-w", "--extendedDiagnostics"], - sys: () => { - const file1 = { - path: `${projectRoot}/f1.ts`, - content: "export const x = 1" - }; - const file2 = { - path: `${projectRoot}/f2.ts`, - content: "export const y = 1" - }; - const configFile = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true }, include: ["./", "./**/*.json"] }) - }; - return createWatchedSystem([file1, file2, libFile, configFile], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "Add new file", - change: sys => sys.writeFile(`${projectRoot}/new-file.ts`, "export const z = 1;"), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), - }, - { - caption: "Import new file", - change: sys => sys.prependFile(`${projectRoot}/f1.ts`, `import { z } from "./new-file";`), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), - } - ] - }); + sys.checkTimeoutQueueLength(0); + baseline.push(`First program is not updated:: ${getPrograms() === ts.emptyArray}`); + baseline.push(`Second program is not updated:: ${getPrograms2() === ts.emptyArray}`); + Harness.Baseline.runBaseline(`tscWatch/${scenario}/two-watch-programs-are-not-affected-by-each-other.js`, baseline.join("\r\n")); + }); - verifyTscWatch({ - scenario, - subScenario: "can correctly update configured project when set of root files has changed through include", - commandLineArgs: ["-w", "-p", "."], - sys: () => { - const file1 = { - path: `${projectRoot}/Project/file1.ts`, - content: "export const x = 10;" - }; - const configFile = { - path: `${projectRoot}/Project/tsconfig.json`, - content: JSON.stringify({ include: [".", "./**/*.json"] }) - }; - return createWatchedSystem([file1, libFile, configFile], { currentDirectory: `${projectRoot}/Project` }); - }, - changes: [ - { - caption: "Write file2", - change: sys => sys.writeFile(`${projectRoot}/Project/file2.ts`, "export const y = 10;"), - timeouts: checkSingleTimeoutQueueLengthAndRun - } - ] - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "can correctly update configured project when set of root files has changed (new file on disk)", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + return ts.tscWatch.createWatchedSystem([file1, ts.tscWatch.libFile, configFile]); + }, + changes: [ + { + caption: "Write f2", + change: sys => sys.writeFile("/a/b/f2.ts", "let y = 1"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "can update configured project when set of root files was not changed", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: configFilePath, - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) - }; - return createWatchedSystem([file1, file2, libFile, configFile]); - }, - changes: [ - { - caption: "Modify config to set outFile option", - change: sys => sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "can correctly update configured project when set of root files has changed (new file in list of files)", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: configFilePath, + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) + }; + return ts.tscWatch.createWatchedSystem([file1, file2, ts.tscWatch.libFile, configFile]); + }, + changes: [ + { + caption: "Modify config to make f2 as root too", + change: sys => sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "file in files is deleted", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: configFilePath, - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) - }; - return createWatchedSystem([file1, file2, libFile, configFile]); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "correctly parses wild card directories from implicit glob when two keys differ only in directory seperator", + commandLineArgs: ["-w", "--extendedDiagnostics"], + sys: () => { + const file1 = { + path: `${ts.tscWatch.projectRoot}/f1.ts`, + content: "export const x = 1" + }; + const file2 = { + path: `${ts.tscWatch.projectRoot}/f2.ts`, + content: "export const y = 1" + }; + const configFile = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true }, include: ["./", "./**/*.json"] }) + }; + return ts.tscWatch.createWatchedSystem([file1, file2, ts.tscWatch.libFile, configFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Add new file", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/new-file.ts`, "export const z = 1;"), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), }, - changes: [ - { - caption: "Delete f2", - change: sys => sys.deleteFile("/a/b/f2.ts"), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + { + caption: "Import new file", + change: sys => sys.prependFile(`${ts.tscWatch.projectRoot}/f1.ts`, `import { z } from "./new-file";`), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "config file is deleted", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1;" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 2;" - }; - return createWatchedSystem([file1, file2, libFile, configFile]); - }, - changes: [ - { - caption: "Delete config file", - change: sys => sys.deleteFile(configFilePath), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "can correctly update configured project when set of root files has changed through include", + commandLineArgs: ["-w", "-p", "."], + sys: () => { + const file1 = { + path: `${ts.tscWatch.projectRoot}/Project/file1.ts`, + content: "export const x = 10;" + }; + const configFile = { + path: `${ts.tscWatch.projectRoot}/Project/tsconfig.json`, + content: JSON.stringify({ include: [".", "./**/*.json"] }) + }; + return ts.tscWatch.createWatchedSystem([file1, ts.tscWatch.libFile, configFile], { currentDirectory: `${ts.tscWatch.projectRoot}/Project` }); + }, + changes: [ + { + caption: "Write file2", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/Project/file2.ts`, "export const y = 10;"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "Proper errors document is not contained in project", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const corruptedConfig = { - path: configFilePath, - content: "{" - }; - return createWatchedSystem([file1, libFile, corruptedConfig]); - }, - changes: emptyArray - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "can update configured project when set of root files was not changed", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: configFilePath, + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) + }; + return ts.tscWatch.createWatchedSystem([file1, file2, ts.tscWatch.libFile, configFile]); + }, + changes: [ + { + caption: "Modify config to set outFile option", + change: sys => sys.writeFile(configFilePath, JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "correctly handles changes in lib section of config file", - commandLineArgs: ["-w", "-p", "/src/tsconfig.json"], - sys: () => { - const libES5 = { - path: "/compiler/lib.es5.d.ts", - content: `${libFile.content} -declare const eval: any` - }; - const libES2015Promise = { - path: "/compiler/lib.es2015.promise.d.ts", - content: `declare class Promise {}` - }; - const app = { - path: "/src/app.ts", - content: "var x: Promise;" - }; - const config1 = { - path: "/src/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5" - ] - } - }) - }; - return createWatchedSystem([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); - }, - changes: [ - { - caption: "Change the lib in config", - change: sys => sys.writeFile("/src/tsconfig.json", JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5", - "es2015.promise" - ] - } - }) - ), - timeouts: checkSingleTimeoutQueueLengthAndRun, - } - ] - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "file in files is deleted", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: configFilePath, + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) + }; + return ts.tscWatch.createWatchedSystem([file1, file2, ts.tscWatch.libFile, configFile]); + }, + changes: [ + { + caption: "Delete f2", + change: sys => sys.deleteFile("/a/b/f2.ts"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "should handle non-existing directories in config file", - commandLineArgs: ["-w", "-p", "/a/tsconfig.json"], - sys: () => { - const f = { - path: "/a/src/app.ts", - content: "let x = 1;" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {}, - include: [ - "src/**/*", - "notexistingfolder/*" - ] + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "config file is deleted", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1;" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 2;" + }; + return ts.tscWatch.createWatchedSystem([file1, file2, ts.tscWatch.libFile, configFile]); + }, + changes: [ + { + caption: "Delete config file", + change: sys => sys.deleteFile(configFilePath), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "Proper errors document is not contained in project", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const corruptedConfig = { + path: configFilePath, + content: "{" + }; + return ts.tscWatch.createWatchedSystem([file1, ts.tscWatch.libFile, corruptedConfig]); + }, + changes: ts.emptyArray + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "correctly handles changes in lib section of config file", + commandLineArgs: ["-w", "-p", "/src/tsconfig.json"], + sys: () => { + const libES5 = { + path: "/compiler/lib.es5.d.ts", + content: `${ts.tscWatch.libFile.content} +declare const eval: any` + }; + const libES2015Promise = { + path: "/compiler/lib.es2015.promise.d.ts", + content: `declare class Promise {}` + }; + const app = { + path: "/src/app.ts", + content: "var x: Promise;" + }; + const config1 = { + path: "/src/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5" + ] + } }) - }; - return createWatchedSystem([f, config, libFile]); - }, - changes: emptyArray - }); + }; + return ts.tscWatch.createWatchedSystem([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); + }, + changes: [ + { + caption: "Change the lib in config", + change: sys => sys.writeFile("/src/tsconfig.json", JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5", + "es2015.promise" + ] + } + }) + ), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + } + ] + }); - function runQueuedTimeoutCallbacksTwice(sys: WatchedSystem) { - sys.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions - sys.runQueuedTimeoutCallbacks(); // Actual update - } + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "should handle non-existing directories in config file", + commandLineArgs: ["-w", "-p", "/a/tsconfig.json"], + sys: () => { + const f = { + path: "/a/src/app.ts", + content: "let x = 1;" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {}, + include: [ + "src/**/*", + "notexistingfolder/*" + ] + }) + }; + return ts.tscWatch.createWatchedSystem([f, config, ts.tscWatch.libFile]); + }, + changes: ts.emptyArray + }); - const changeModuleFileToModuleFile1: TscWatchCompileChange = { - caption: "Rename moduleFile to moduleFile1", - change: sys => { - sys.renameFile("/a/b/moduleFile.ts", "/a/b/moduleFile1.ts"); - sys.deleteFile("/a/b/moduleFile.js"); - }, - timeouts: runQueuedTimeoutCallbacksTwice - }; - const changeModuleFile1ToModuleFile: TscWatchCompileChange = { - caption: "Rename moduleFile1 back to moduleFile", - change: sys => sys.renameFile("/a/b/moduleFile1.ts", "/a/b/moduleFile.ts"), - timeouts: runQueuedTimeoutCallbacksTwice, - }; + function runQueuedTimeoutCallbacksTwice(sys: ts.tscWatch.WatchedSystem) { + sys.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions + sys.runQueuedTimeoutCallbacks(); // Actual update + } - verifyTscWatch({ - scenario, - subScenario: "rename a module file and rename back should restore the states for inferred projects", - commandLineArgs: ["-w", "/a/b/file1.ts"], - sys: () => { - const moduleFile = { - path: "/a/b/moduleFile.ts", - content: "export function bar() { };" - }; - const file1 = { - path: "/a/b/file1.ts", - content: 'import * as T from "./moduleFile"; T.bar();' - }; - return createWatchedSystem([moduleFile, file1, libFile]); - }, - changes: [ - changeModuleFileToModuleFile1, - changeModuleFile1ToModuleFile - ] - }); + const changeModuleFileToModuleFile1: ts.tscWatch.TscWatchCompileChange = { + caption: "Rename moduleFile to moduleFile1", + change: sys => { + sys.renameFile("/a/b/moduleFile.ts", "/a/b/moduleFile1.ts"); + sys.deleteFile("/a/b/moduleFile.js"); + }, + timeouts: runQueuedTimeoutCallbacksTwice + }; + const changeModuleFile1ToModuleFile: ts.tscWatch.TscWatchCompileChange = { + caption: "Rename moduleFile1 back to moduleFile", + change: sys => sys.renameFile("/a/b/moduleFile1.ts", "/a/b/moduleFile.ts"), + timeouts: runQueuedTimeoutCallbacksTwice, + }; + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "rename a module file and rename back should restore the states for inferred projects", + commandLineArgs: ["-w", "/a/b/file1.ts"], + sys: () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: 'import * as T from "./moduleFile"; T.bar();' + }; + return ts.tscWatch.createWatchedSystem([moduleFile, file1, ts.tscWatch.libFile]); + }, + changes: [ + changeModuleFileToModuleFile1, + changeModuleFile1ToModuleFile + ] + }); - verifyTscWatch({ - scenario, - subScenario: "rename a module file and rename back should restore the states for configured projects", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const moduleFile = { - path: "/a/b/moduleFile.ts", - content: "export function bar() { };" - }; - const file1 = { - path: "/a/b/file1.ts", - content: 'import * as T from "./moduleFile"; T.bar();' - }; - return createWatchedSystem([moduleFile, file1, configFile, libFile]); - }, - changes: [ - changeModuleFileToModuleFile1, - changeModuleFile1ToModuleFile - ] - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "rename a module file and rename back should restore the states for configured projects", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: 'import * as T from "./moduleFile"; T.bar();' + }; + return ts.tscWatch.createWatchedSystem([moduleFile, file1, configFile, ts.tscWatch.libFile]); + }, + changes: [ + changeModuleFileToModuleFile1, + changeModuleFile1ToModuleFile + ] + }); - verifyTscWatch({ - scenario, - subScenario: "types should load from config file path if config exists", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: configFilePath, - content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } }) - }; - const node = { - path: "/a/b/node_modules/@types/node/index.d.ts", - content: "declare var process: any" - }; - const cwd = { - path: "/a/c" - }; - return createWatchedSystem([f1, config, node, cwd, libFile], { currentDirectory: cwd.path }); - }, - changes: emptyArray - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "types should load from config file path if config exists", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: configFilePath, + content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } }) + }; + const node = { + path: "/a/b/node_modules/@types/node/index.d.ts", + content: "declare var process: any" + }; + const cwd = { + path: "/a/c" + }; + return ts.tscWatch.createWatchedSystem([f1, config, node, cwd, ts.tscWatch.libFile], { currentDirectory: cwd.path }); + }, + changes: ts.emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "add the missing module file for inferred project-should remove the module not found error", - commandLineArgs: ["-w", "/a/b/file1.ts"], - sys: () => { - const file1 = { - path: "/a/b/file1.ts", - content: 'import * as T from "./moduleFile"; T.bar();' - }; - return createWatchedSystem([file1, libFile]); - }, - changes: [ - { - caption: "Create module file", - change: sys => sys.writeFile("/a/b/moduleFile.ts", "export function bar() { }"), - timeouts: runQueuedTimeoutCallbacksTwice, - } - ] - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "add the missing module file for inferred project-should remove the module not found error", + commandLineArgs: ["-w", "/a/b/file1.ts"], + sys: () => { + const file1 = { + path: "/a/b/file1.ts", + content: 'import * as T from "./moduleFile"; T.bar();' + }; + return ts.tscWatch.createWatchedSystem([file1, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "Create module file", + change: sys => sys.writeFile("/a/b/moduleFile.ts", "export function bar() { }"), + timeouts: runQueuedTimeoutCallbacksTwice, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "Configure file diagnostics events are generated when the config file has errors", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile = { - path: configFilePath, - content: `{ + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "Configure file diagnostics events are generated when the config file has errors", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: configFilePath, + content: `{ "compilerOptions": { "foo": "bar", "allowJS": true } }` - }; - return createWatchedSystem([file, configFile, libFile]); - }, - changes: emptyArray - }); + }; + return ts.tscWatch.createWatchedSystem([file, configFile, ts.tscWatch.libFile]); + }, + changes: ts.emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "if config file doesnt have errors, they are not reported", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile = { - path: configFilePath, - content: `{ + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "if config file doesnt have errors, they are not reported", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: configFilePath, + content: `{ "compilerOptions": {} }` - }; - return createWatchedSystem([file, configFile, libFile]); - }, - changes: emptyArray - }); + }; + return ts.tscWatch.createWatchedSystem([file, configFile, ts.tscWatch.libFile]); + }, + changes: ts.emptyArray + }); - verifyTscWatch({ - scenario, - subScenario: "Reports errors when the config file changes", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - return createWatchedSystem([file, configFile, libFile]); - }, - changes: [ - { - caption: "change config file to add error", - change: sys => sys.writeFile(configFilePath, `{ + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "Reports errors when the config file changes", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + return ts.tscWatch.createWatchedSystem([file, configFile, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "change config file to add error", + change: sys => sys.writeFile(configFilePath, `{ "compilerOptions": { "haha": 123 } }`), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "change config file to remove error", - change: sys => sys.writeFile(configFilePath, `{ + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "change config file to remove error", + change: sys => sys.writeFile(configFilePath, `{ "compilerOptions": { } }`), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "non-existing directories listed in config file input array should be tolerated without crashing the server", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const configFile = { - path: configFilePath, - content: `{ + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "non-existing directories listed in config file input array should be tolerated without crashing the server", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const configFile = { + path: configFilePath, + content: `{ "compilerOptions": {}, "include": ["app/*", "test/**/*", "something"] }` - }; - const file1 = { - path: "/a/b/file1.ts", - content: "let t = 10;" - }; - return createWatchedSystem([file1, configFile, libFile]); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "non-existing directories listed in config file input array should be able to handle @types if input file list is empty", - commandLineArgs: ["-w", "-p", "/a/tsconfig.json"], - sys: () => { - const f = { - path: "/a/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compiler: {}, - files: [] - }) - }; - const t1 = { - path: "/a/node_modules/@types/typings/index.d.ts", - content: `export * from "./lib"` - }; - const t2 = { - path: "/a/node_modules/@types/typings/lib.d.ts", - content: `export const x: number` - }; - return createWatchedSystem([f, config, t1, t2, libFile], { currentDirectory: getDirectoryPath(f.path) }); - }, - changes: emptyArray - }); + }; + const file1 = { + path: "/a/b/file1.ts", + content: "let t = 10;" + }; + return ts.tscWatch.createWatchedSystem([file1, configFile, ts.tscWatch.libFile]); + }, + changes: ts.emptyArray + }); - it("should support files without extensions", () => { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "non-existing directories listed in config file input array should be able to handle @types if input file list is empty", + commandLineArgs: ["-w", "-p", "/a/tsconfig.json"], + sys: () => { const f = { - path: "/a/compile", + path: "/a/app.ts", content: "let x = 1" }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([f, libFile])); - const watch = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ - rootFiles: [f.path], - system: sys, - options: { allowNonTsExtensions: true }, - cb, - watchOptions: undefined - })); - runWatchBaseline({ - scenario, - subScenario: "should support files without extensions", - commandLineArgs: ["--w", f.path], - sys, - baseline, - oldSnap, - getPrograms, - changes: emptyArray, - watchOrSolution: watch - }); - }); + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compiler: {}, + files: [] + }) + }; + const t1 = { + path: "/a/node_modules/@types/typings/index.d.ts", + content: `export * from "./lib"` + }; + const t2 = { + path: "/a/node_modules/@types/typings/lib.d.ts", + content: `export const x: number` + }; + return ts.tscWatch.createWatchedSystem([f, config, t1, t2, ts.tscWatch.libFile], { currentDirectory: ts.getDirectoryPath(f.path) }); + }, + changes: ts.emptyArray + }); - verifyTscWatch({ + it("should support files without extensions", () => { + const f = { + path: "/a/compile", + content: "let x = 1" + }; + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([f, ts.tscWatch.libFile])); + const watch = ts.createWatchProgram(ts.tscWatch.createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ + rootFiles: [f.path], + system: sys, + options: { allowNonTsExtensions: true }, + cb, + watchOptions: undefined + })); + ts.tscWatch.runWatchBaseline({ scenario, - subScenario: "Options Diagnostic locations reported correctly with changes in configFile contents when options change", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const file = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile = { - path: configFilePath, - content: ` + subScenario: "should support files without extensions", + commandLineArgs: ["--w", f.path], + sys, + baseline, + oldSnap, + getPrograms, + changes: ts.emptyArray, + watchOrSolution: watch + }); + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "Options Diagnostic locations reported correctly with changes in configFile contents when options change", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: configFilePath, + content: ` { // comment // More comment @@ -1129,234 +1131,234 @@ declare const eval: any` "mapRoot": "./" } }` - }; - return createWatchedSystem([file, libFile, configFile]); - }, - changes: [ - { - caption: "Remove the comment from config file", - change: sys => sys.writeFile(configFilePath, ` + }; + return ts.tscWatch.createWatchedSystem([file, ts.tscWatch.libFile, configFile]); + }, + changes: [ + { + caption: "Remove the comment from config file", + change: sys => sys.writeFile(configFilePath, ` { "compilerOptions": { "inlineSourceMap": true, "mapRoot": "./" } }`), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ] + }); - describe("should not trigger recompilation because of program emit", () => { - function verifyWithOptions(subScenario: string, options: CompilerOptions) { - verifyTscWatch({ - scenario, - subScenario: `should not trigger recompilation because of program emit/${subScenario}`, - commandLineArgs: ["-w", "-p", `${projectRoot}/tsconfig.json`], - sys: () => { - const file1: File = { - path: `${projectRoot}/file1.ts`, - content: "export const c = 30;" - }; - const file2: File = { - path: `${projectRoot}/src/file2.ts`, - content: `import {c} from "file1"; export const d = 30;` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: compilerOptionsToConfigJson(options) - }) - }; - return createWatchedSystem([file1, file2, libFile, tsconfig], { currentDirectory: projectRoot }); + describe("should not trigger recompilation because of program emit", () => { + function verifyWithOptions(subScenario: string, options: ts.CompilerOptions) { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `should not trigger recompilation because of program emit/${subScenario}`, + commandLineArgs: ["-w", "-p", `${ts.tscWatch.projectRoot}/tsconfig.json`], + sys: () => { + const file1: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/file1.ts`, + content: "export const c = 30;" + }; + const file2: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/src/file2.ts`, + content: `import {c} from "file1"; export const d = 30;` + }; + const tsconfig: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: ts.compilerOptionsToConfigJson(options) + }) + }; + return ts.tscWatch.createWatchedSystem([file1, file2, ts.tscWatch.libFile, tsconfig], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + ts.tscWatch.noopChange, + { + caption: "Add new file", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/src/file3.ts`, `export const y = 10;`), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), // To update program and failed lookups }, - changes: [ - noopChange, - { - caption: "Add new file", - change: sys => sys.writeFile(`${projectRoot}/src/file3.ts`, `export const y = 10;`), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), // To update program and failed lookups - }, - noopChange, - ] - }); - } + ts.tscWatch.noopChange, + ] + }); + } - verifyWithOptions( - "without outDir or outFile is specified", - { module: ModuleKind.AMD } - ); - - verifyWithOptions( - "with outFile", - { module: ModuleKind.AMD, outFile: "build/outFile.js" } - ); - - verifyWithOptions( - "when outDir is specified", - { module: ModuleKind.AMD, outDir: "build" } - ); - - verifyWithOptions( - "without outDir or outFile is specified with declaration enabled", - { module: ModuleKind.AMD, declaration: true } - ); - - verifyWithOptions( - "when outDir and declarationDir is specified", - { module: ModuleKind.AMD, outDir: "build", declaration: true, declarationDir: "decls" } - ); - - verifyWithOptions( - "declarationDir is specified", - { module: ModuleKind.AMD, declaration: true, declarationDir: "decls" } - ); - }); + verifyWithOptions( + "without outDir or outFile is specified", + { module: ts.ModuleKind.AMD } + ); + + verifyWithOptions( + "with outFile", + { module: ts.ModuleKind.AMD, outFile: "build/outFile.js" } + ); + + verifyWithOptions( + "when outDir is specified", + { module: ts.ModuleKind.AMD, outDir: "build" } + ); + + verifyWithOptions( + "without outDir or outFile is specified with declaration enabled", + { module: ts.ModuleKind.AMD, declaration: true } + ); + + verifyWithOptions( + "when outDir and declarationDir is specified", + { module: ts.ModuleKind.AMD, outDir: "build", declaration: true, declarationDir: "decls" } + ); + + verifyWithOptions( + "declarationDir is specified", + { module: ts.ModuleKind.AMD, declaration: true, declarationDir: "decls" } + ); + }); - verifyTscWatch({ - scenario, - subScenario: "shouldnt report error about unused function incorrectly when file changes from global to module", - commandLineArgs: ["-w", "/a/b/file.ts", "--noUnusedLocals"], - sys: () => { - const file: File = { - path: "/a/b/file.ts", - content: `function one() {} + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "shouldnt report error about unused function incorrectly when file changes from global to module", + commandLineArgs: ["-w", "/a/b/file.ts", "--noUnusedLocals"], + sys: () => { + const file: ts.tscWatch.File = { + path: "/a/b/file.ts", + content: `function one() {} function two() { return function three() { one(); } }` - }; - return createWatchedSystem([file, libFile]); - }, - changes: [ - { - caption: "Change file to module", - change: sys => sys.writeFile("/a/b/file.ts", `function one() {} + }; + return ts.tscWatch.createWatchedSystem([file, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "Change file to module", + change: sys => sys.writeFile("/a/b/file.ts", `function one() {} export function two() { return function three() { one(); } }`), - timeouts: runQueuedTimeoutCallbacks, - - } - ] - }); + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, - verifyTscWatch({ - scenario, - subScenario: "watched files when file is deleted and new file is added as part of change", - commandLineArgs: ["-w", "-p", "/home/username/project/tsconfig.json"], - sys: () => { - const projectLocation = "/home/username/project"; - const file: File = { - path: `${projectLocation}/src/file1.ts`, - content: "var a = 10;" - }; - const configFile: File = { - path: `${projectLocation}/tsconfig.json`, - content: "{}" - }; - return createWatchedSystem([file, libFile, configFile]); - }, - changes: [ - { - caption: "Rename file1 to file2", - change: sys => sys.renameFile("/home/username/project/src/file1.ts", "/home/username/project/src/file2.ts"), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + } + ] + }); - function changeParameterTypeOfBFile(parameterName: string, toType: string): TscWatchCompileChange { - return { - caption: `Changed ${parameterName} type to ${toType}`, - change: sys => replaceFileText(sys, `${projectRoot}/b.ts`, new RegExp(`${parameterName}\: [a-z]*`), `${parameterName}: ${toType}`), - timeouts: runQueuedTimeoutCallbacks, + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "watched files when file is deleted and new file is added as part of change", + commandLineArgs: ["-w", "-p", "/home/username/project/tsconfig.json"], + sys: () => { + const projectLocation = "/home/username/project"; + const file: ts.tscWatch.File = { + path: `${projectLocation}/src/file1.ts`, + content: "var a = 10;" }; - } + const configFile: ts.tscWatch.File = { + path: `${projectLocation}/tsconfig.json`, + content: "{}" + }; + return ts.tscWatch.createWatchedSystem([file, ts.tscWatch.libFile, configFile]); + }, + changes: [ + { + caption: "Rename file1 to file2", + change: sys => sys.renameFile("/home/username/project/src/file1.ts", "/home/username/project/src/file2.ts"), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates errors correctly when declaration emit is disabled in compiler options", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `import test from './b'; + function changeParameterTypeOfBFile(parameterName: string, toType: string): ts.tscWatch.TscWatchCompileChange { + return { + caption: `Changed ${parameterName} type to ${toType}`, + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/b.ts`, new RegExp(`${parameterName}\: [a-z]*`), `${parameterName}: ${toType}`), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }; + } + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "updates errors correctly when declaration emit is disabled in compiler options", + commandLineArgs: ["-w"], + sys: () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `import test from './b'; test(4, 5);` - }; - const bFile: File = { - path: `${projectRoot}/b.ts`, - content: `function test(x: number, y: number) { + }; + const bFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/b.ts`, + content: `function test(x: number, y: number) { return x + y / 5; } export default test;` - }; - const tsconfigFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "commonjs", - noEmit: true, - strict: true, - } - }) - }; - return createWatchedSystem([aFile, bFile, libFile, tsconfigFile], { currentDirectory: projectRoot }); - }, - changes: [ - changeParameterTypeOfBFile("x", "string"), - changeParameterTypeOfBFile("x", "number"), - changeParameterTypeOfBFile("y", "string"), - changeParameterTypeOfBFile("y", "number"), - ] - }); + }; + const tsconfigFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "commonjs", + noEmit: true, + strict: true, + } + }) + }; + return ts.tscWatch.createWatchedSystem([aFile, bFile, ts.tscWatch.libFile, tsconfigFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + changeParameterTypeOfBFile("x", "string"), + changeParameterTypeOfBFile("x", "number"), + changeParameterTypeOfBFile("y", "string"), + changeParameterTypeOfBFile("y", "number"), + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates errors when strictNullChecks changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `declare function foo(): null | { hello: any }; + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "updates errors when strictNullChecks changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `declare function foo(): null | { hello: any }; foo().hello` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: {} }) - }; - return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); + }; + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: {} }) + }; + return ts.tscWatch.createWatchedSystem([aFile, config, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Enable strict null checks", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { strictNullChecks: true } })), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Enable strict null checks", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { strictNullChecks: true } })), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Set always strict false", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { strict: true, alwaysStrict: false } })), // Avoid changing 'alwaysStrict' or must re-bind - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Disable strict", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: {} })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + { + caption: "Set always strict false", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { strict: true, alwaysStrict: false } })), // Avoid changing 'alwaysStrict' or must re-bind + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "Disable strict", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: {} })), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates errors when noErrorTruncation changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `declare var v: { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "updates errors when noErrorTruncation changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `declare var v: { reallyLongPropertyName1: string | number | boolean | object | symbol | bigint; reallyLongPropertyName2: string | number | boolean | object | symbol | bigint; reallyLongPropertyName3: string | number | boolean | object | symbol | bigint; @@ -1366,587 +1368,586 @@ foo().hello` reallyLongPropertyName7: string | number | boolean | object | symbol | bigint; }; v === 'foo';` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: {} }) - }; - return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); + }; + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: {} }) + }; + return ts.tscWatch.createWatchedSystem([aFile, config, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Enable noErrorTruncation", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { noErrorTruncation: true } })), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Enable noErrorTruncation", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { noErrorTruncation: true } })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates diagnostics and emit when useDefineForClassFields changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `/a.ts`, - content: `class C { get prop() { return 1; } } + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "updates diagnostics and emit when useDefineForClassFields changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: ts.tscWatch.File = { + path: `/a.ts`, + content: `class C { get prop() { return 1; } } class D extends C { prop = 1; }` - }; - const config: File = { - path: `/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { target: "es6" } }) - }; - return createWatchedSystem([aFile, config, libFile]); + }; + const config: ts.tscWatch.File = { + path: `/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { target: "es6" } }) + }; + return ts.tscWatch.createWatchedSystem([aFile, config, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "Enable useDefineForClassFields", + change: sys => sys.writeFile(`/tsconfig.json`, JSON.stringify({ compilerOptions: { target: "es6", useDefineForClassFields: true } })), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Enable useDefineForClassFields", - change: sys => sys.writeFile(`/tsconfig.json`, JSON.stringify({ compilerOptions: { target: "es6", useDefineForClassFields: true } })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates errors and emit when importsNotUsedAsValues changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `export class C {}` - }; - const bFile: File = { - path: `${projectRoot}/b.ts`, - content: `import {C} from './a'; + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "updates errors and emit when importsNotUsedAsValues changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `export class C {}` + }; + const bFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/b.ts`, + content: `import {C} from './a'; export function f(p: C) { return p; }` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: {} }) - }; - return createWatchedSystem([aFile, bFile, config, libFile], { currentDirectory: projectRoot }); + }; + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: {} }) + }; + return ts.tscWatch.createWatchedSystem([aFile, bFile, config, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: 'Set to "remove"', + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "remove" } })), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: 'Set to "remove"', - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "remove" } })), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: 'Set to "error"', - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "error" } })), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: 'Set to "preserve"', - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "preserve" } })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + { + caption: 'Set to "error"', + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "error" } })), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: 'Set to "preserve"', + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { importsNotUsedAsValues: "preserve" } })), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates errors when forceConsistentCasingInFileNames changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `/a.ts`, - content: `export class C {}` - }; - const bFile: File = { - path: `/b.ts`, - content: `import {C} from './a'; import * as A from './A';` - }; - const config: File = { - path: `/tsconfig.json`, - content: JSON.stringify({ compilerOptions: {} }) - }; - return createWatchedSystem([aFile, bFile, config, libFile], { useCaseSensitiveFileNames: false }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "updates errors when forceConsistentCasingInFileNames changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: ts.tscWatch.File = { + path: `/a.ts`, + content: `export class C {}` + }; + const bFile: ts.tscWatch.File = { + path: `/b.ts`, + content: `import {C} from './a'; import * as A from './A';` + }; + const config: ts.tscWatch.File = { + path: `/tsconfig.json`, + content: JSON.stringify({ compilerOptions: {} }) + }; + return ts.tscWatch.createWatchedSystem([aFile, bFile, config, ts.tscWatch.libFile], { useCaseSensitiveFileNames: false }); + }, + changes: [ + { + caption: "Enable forceConsistentCasingInFileNames", + change: sys => sys.writeFile(`/tsconfig.json`, JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } })), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Enable forceConsistentCasingInFileNames", - change: sys => sys.writeFile(`/tsconfig.json`, JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates moduleResolution when resolveJsonModule changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `import * as data from './data.json'` - }; - const jsonFile: File = { - path: `${projectRoot}/data.json`, - content: `{ "foo": 1 }` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { moduleResolution: "node" } }) - }; - return createWatchedSystem([aFile, jsonFile, config, libFile], { currentDirectory: projectRoot }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "updates moduleResolution when resolveJsonModule changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `import * as data from './data.json'` + }; + const jsonFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/data.json`, + content: `{ "foo": 1 }` + }; + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { moduleResolution: "node" } }) + }; + return ts.tscWatch.createWatchedSystem([aFile, jsonFile, config, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Enable resolveJsonModule", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { moduleResolution: "node", resolveJsonModule: true } })), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Enable resolveJsonModule", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, JSON.stringify({ compilerOptions: { moduleResolution: "node", resolveJsonModule: true } })), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates errors when ambient modules of program changes", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `declare module 'a' { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "updates errors when ambient modules of program changes", + commandLineArgs: ["-w"], + sys: () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `declare module 'a' { type foo = number; }` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - return createWatchedSystem([aFile, config, libFile], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "Create b.ts with same content", - // Create bts with same file contents - change: sys => sys.writeFile(`${projectRoot}/b.ts`, `declare module 'a' { + }; + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + return ts.tscWatch.createWatchedSystem([aFile, config, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Create b.ts with same content", + // Create bts with same file contents + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/b.ts`, `declare module 'a' { type foo = number; }`), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Delete b.ts", - change: sys => sys.deleteFile(`${projectRoot}/b.ts`), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "Delete b.ts", + change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/b.ts`), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + ] + }); - describe("updates errors in lib file", () => { - const field = "fullscreen"; - const fieldWithoutReadonly = `interface Document { + describe("updates errors in lib file", () => { + const field = "fullscreen"; + const fieldWithoutReadonly = `interface Document { ${field}: boolean; }`; - const libFileWithDocument: File = { - path: libFile.path, - content: `${libFile.content} + const libFileWithDocument: ts.tscWatch.File = { + path: ts.tscWatch.libFile.path, + content: `${ts.tscWatch.libFile.content} interface Document { readonly ${field}: boolean; }` - }; + }; - function verifyLibFileErrorsWith(subScenario: string, aFile: File) { - function verifyLibErrors(subScenario: string, commandLineOptions: readonly string[]) { - verifyTscWatch({ - scenario, - subScenario: `updates errors in lib file/${subScenario}`, - commandLineArgs: ["-w", aFile.path, ...commandLineOptions], - sys: () => createWatchedSystem([aFile, libFileWithDocument], { currentDirectory: projectRoot }), - changes: [ - { - caption: "Remove document declaration from file", - change: sys => sys.writeFile(aFile.path, aFile.content.replace(fieldWithoutReadonly, "var x: string;")), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: "Rever the file to contain document declaration", - change: sys => sys.writeFile(aFile.path, aFile.content), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); - } - - verifyLibErrors(`${subScenario}/with default options`, emptyArray); - verifyLibErrors(`${subScenario}/with skipLibCheck`, ["--skipLibCheck"]); - verifyLibErrors(`${subScenario}/with skipDefaultLibCheck`, ["--skipDefaultLibCheck"]); + function verifyLibFileErrorsWith(subScenario: string, aFile: ts.tscWatch.File) { + function verifyLibErrors(subScenario: string, commandLineOptions: readonly string[]) { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `updates errors in lib file/${subScenario}`, + commandLineArgs: ["-w", aFile.path, ...commandLineOptions], + sys: () => ts.tscWatch.createWatchedSystem([aFile, libFileWithDocument], { currentDirectory: ts.tscWatch.projectRoot }), + changes: [ + { + caption: "Remove document declaration from file", + change: sys => sys.writeFile(aFile.path, aFile.content.replace(fieldWithoutReadonly, "var x: string;")), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + { + caption: "Rever the file to contain document declaration", + change: sys => sys.writeFile(aFile.path, aFile.content), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + ] + }); } - describe("when non module file changes", () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `${fieldWithoutReadonly} + verifyLibErrors(`${subScenario}/with default options`, ts.emptyArray); + verifyLibErrors(`${subScenario}/with skipLibCheck`, ["--skipLibCheck"]); + verifyLibErrors(`${subScenario}/with skipDefaultLibCheck`, ["--skipDefaultLibCheck"]); + } + + describe("when non module file changes", () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `${fieldWithoutReadonly} var y: number;` - }; - verifyLibFileErrorsWith("when non module file changes", aFile); - }); + }; + verifyLibFileErrorsWith("when non module file changes", aFile); + }); - describe("when module file with global definitions changes", () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `export {} + describe("when module file with global definitions changes", () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `export {} declare global { ${fieldWithoutReadonly} var y: number; }` - }; - verifyLibFileErrorsWith("when module file with global definitions changes", aFile); - }); + }; + verifyLibFileErrorsWith("when module file with global definitions changes", aFile); }); + }); - function changeWhenLibCheckChanges(compilerOptions: CompilerOptions): TscWatchCompileChange { - const configFileContent = JSON.stringify({ compilerOptions }); - return { - caption: `Changing config to ${configFileContent}`, - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, configFileContent), - timeouts: runQueuedTimeoutCallbacks, - }; - } + function changeWhenLibCheckChanges(compilerOptions: ts.CompilerOptions): ts.tscWatch.TscWatchCompileChange { + const configFileContent = JSON.stringify({ compilerOptions }); + return { + caption: `Changing config to ${configFileContent}`, + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, configFileContent), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }; + } - verifyTscWatch({ - scenario, - subScenario: "when skipLibCheck and skipDefaultLibCheck changes", - commandLineArgs: ["-w"], - sys: () => { - const field = "fullscreen"; - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `interface Document { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "when skipLibCheck and skipDefaultLibCheck changes", + commandLineArgs: ["-w"], + sys: () => { + const field = "fullscreen"; + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `interface Document { ${field}: boolean; }` - }; - const bFile: File = { - path: `${projectRoot}/b.d.ts`, - content: `interface Document { + }; + const bFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/b.d.ts`, + content: `interface Document { ${field}: boolean; }` - }; - const libFileWithDocument: File = { - path: libFile.path, - content: `${libFile.content} + }; + const libFileWithDocument: ts.tscWatch.File = { + path: ts.tscWatch.libFile.path, + content: `${ts.tscWatch.libFile.content} interface Document { readonly ${field}: boolean; }` - }; - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - return createWatchedSystem([aFile, bFile, configFile, libFileWithDocument], { currentDirectory: projectRoot }); - }, - changes: [ - changeWhenLibCheckChanges({ skipLibCheck: true }), - changeWhenLibCheckChanges({ skipDefaultLibCheck: true }), - changeWhenLibCheckChanges({}), - changeWhenLibCheckChanges({ skipDefaultLibCheck: true }), - changeWhenLibCheckChanges({ skipLibCheck: true }), - changeWhenLibCheckChanges({}), - ] - }); + }; + const configFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + return ts.tscWatch.createWatchedSystem([aFile, bFile, configFile, libFileWithDocument], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + changeWhenLibCheckChanges({ skipLibCheck: true }), + changeWhenLibCheckChanges({ skipDefaultLibCheck: true }), + changeWhenLibCheckChanges({}), + changeWhenLibCheckChanges({ skipDefaultLibCheck: true }), + changeWhenLibCheckChanges({ skipLibCheck: true }), + changeWhenLibCheckChanges({}), + ] + }); - verifyTscWatch({ - scenario, - subScenario: "reports errors correctly with isolatedModules", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `export const a: string = "";` - }; - const bFile: File = { - path: `${projectRoot}/b.ts`, - content: `import { a } from "./a"; + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "reports errors correctly with isolatedModules", + commandLineArgs: ["-w"], + sys: () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `export const a: string = "";` + }; + const bFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/b.ts`, + content: `import { a } from "./a"; const b: string = a;` - }; - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - isolatedModules: true - } - }) - }; - return createWatchedSystem([aFile, bFile, configFile, libFile], { currentDirectory: projectRoot }); + }; + const configFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + isolatedModules: true + } + }) + }; + return ts.tscWatch.createWatchedSystem([aFile, bFile, configFile, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Change shape of a", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/a.ts`, `export const a: number = 1`), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Change shape of a", - change: sys => sys.writeFile(`${projectRoot}/a.ts`, `export const a: number = 1`), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "reports errors correctly with file not in rootDir", - commandLineArgs: ["-w"], - sys: () => { - const aFile: File = { - path: `${projectRoot}/a.ts`, - content: `import { x } from "../b";` - }; - const bFile: File = { - path: `/user/username/projects/b.ts`, - content: `export const x = 10;` - }; - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - rootDir: ".", - outDir: "lib" - } - }) - }; - return createWatchedSystem([aFile, bFile, configFile, libFile], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "Make changes to file a", - change: sys => sys.writeFile(`${projectRoot}/a.ts`, ` + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "reports errors correctly with file not in rootDir", + commandLineArgs: ["-w"], + sys: () => { + const aFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `import { x } from "../b";` + }; + const bFile: ts.tscWatch.File = { + path: `/user/username/projects/b.ts`, + content: `export const x = 10;` + }; + const configFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + rootDir: ".", + outDir: "lib" + } + }) + }; + return ts.tscWatch.createWatchedSystem([aFile, bFile, configFile, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Make changes to file a", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/a.ts`, ` import { x } from "../b";`), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "updates emit on jsx option change", - commandLineArgs: ["-w"], - sys: () => { - const index: File = { - path: `${projectRoot}/index.tsx`, - content: `declare var React: any;\nconst d =
;` - }; - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - jsx: "preserve" - } - }) - }; - return createWatchedSystem([index, configFile, libFile], { currentDirectory: projectRoot }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "updates emit on jsx option change", + commandLineArgs: ["-w"], + sys: () => { + const index: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/index.tsx`, + content: `declare var React: any;\nconst d =
;` + }; + const configFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + jsx: "preserve" + } + }) + }; + return ts.tscWatch.createWatchedSystem([index, configFile, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Update 'jsx' to 'react'", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/tsconfig.json`, '{ "compilerOptions": { "jsx": "react" } }'), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "Update 'jsx' to 'react'", - change: sys => sys.writeFile(`${projectRoot}/tsconfig.json`, '{ "compilerOptions": { "jsx": "react" } }'), - timeouts: runQueuedTimeoutCallbacks, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "extended source files are watched", - commandLineArgs: ["-w", "-p", configFilePath], - sys: () => { - const firstExtendedConfigFile: File = { - path: "/a/b/first.tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - strict: true - } - }) - }; - const secondExtendedConfigFile: File = { - path: "/a/b/second.tsconfig.json", - content: JSON.stringify({ - extends: "./first.tsconfig.json" - }) - }; - const configFile: File = { - path: configFilePath, - content: JSON.stringify({ - compilerOptions: {}, - files: [commonFile1.path, commonFile2.path] - }) - }; - return createWatchedSystem([ - libFile, commonFile1, commonFile2, configFile, firstExtendedConfigFile, secondExtendedConfigFile - ]); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "extended source files are watched", + commandLineArgs: ["-w", "-p", configFilePath], + sys: () => { + const firstExtendedConfigFile: ts.tscWatch.File = { + path: "/a/b/first.tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + strict: true + } + }) + }; + const secondExtendedConfigFile: ts.tscWatch.File = { + path: "/a/b/second.tsconfig.json", + content: JSON.stringify({ + extends: "./first.tsconfig.json" + }) + }; + const configFile: ts.tscWatch.File = { + path: configFilePath, + content: JSON.stringify({ + compilerOptions: {}, + files: [ts.tscWatch.commonFile1.path, ts.tscWatch.commonFile2.path] + }) + }; + return ts.tscWatch.createWatchedSystem([ + ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile, firstExtendedConfigFile, secondExtendedConfigFile + ]); + }, + changes: [ + { + caption: "Change config to extend another config", + change: sys => sys.modifyFile(configFilePath, JSON.stringify({ + extends: "./second.tsconfig.json", + compilerOptions: {}, + files: [ts.tscWatch.commonFile1.path, ts.tscWatch.commonFile2.path] + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "Change config to extend another config", - change: sys => sys.modifyFile(configFilePath, JSON.stringify({ - extends: "./second.tsconfig.json", - compilerOptions: {}, - files: [commonFile1.path, commonFile2.path] - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Change first extended config", - change: sys => sys.modifyFile("/a/b/first.tsconfig.json", JSON.stringify({ - compilerOptions: { - strict: false, - } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Change second extended config", - change: sys => sys.modifyFile("/a/b/second.tsconfig.json", JSON.stringify({ - extends: "./first.tsconfig.json", - compilerOptions: { - strictNullChecks: true, - } - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Change config to stop extending another config", - change: sys => sys.modifyFile(configFilePath, JSON.stringify({ - compilerOptions: {}, - files: [commonFile1.path, commonFile2.path] - })), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - ] - }); + { + caption: "Change first extended config", + change: sys => sys.modifyFile("/a/b/first.tsconfig.json", JSON.stringify({ + compilerOptions: { + strict: false, + } + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Change second extended config", + change: sys => sys.modifyFile("/a/b/second.tsconfig.json", JSON.stringify({ + extends: "./first.tsconfig.json", + compilerOptions: { + strictNullChecks: true, + } + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Change config to stop extending another config", + change: sys => sys.modifyFile(configFilePath, JSON.stringify({ + compilerOptions: {}, + files: [ts.tscWatch.commonFile1.path, ts.tscWatch.commonFile2.path] + })), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "when creating new file in symlinked folder", - commandLineArgs: ["-w", "-p", ".", "--extendedDiagnostics"], - sys: () => { - const module1: File = { - path: `${projectRoot}/client/folder1/module1.ts`, - content: `export class Module1Class { }` - }; - const module2: File = { - path: `${projectRoot}/folder2/module2.ts`, - content: `import * as M from "folder1/module1";` - }; - const symlink: SymLink = { - path: `${projectRoot}/client/linktofolder2`, - symLink: `${projectRoot}/folder2`, - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - baseUrl: "client", - paths: { "*": ["*"] }, - }, - include: ["client/**/*", "folder2"] - }) - }; - return createWatchedSystem([module1, module2, symlink, config, libFile], { currentDirectory: projectRoot }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "when creating new file in symlinked folder", + commandLineArgs: ["-w", "-p", ".", "--extendedDiagnostics"], + sys: () => { + const module1: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/client/folder1/module1.ts`, + content: `export class Module1Class { }` + }; + const module2: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/folder2/module2.ts`, + content: `import * as M from "folder1/module1";` + }; + const symlink: ts.tscWatch.SymLink = { + path: `${ts.tscWatch.projectRoot}/client/linktofolder2`, + symLink: `${ts.tscWatch.projectRoot}/folder2`, + }; + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + baseUrl: "client", + paths: { "*": ["*"] }, + }, + include: ["client/**/*", "folder2"] + }) + }; + return ts.tscWatch.createWatchedSystem([module1, module2, symlink, config, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Add module3 to folder2", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/client/linktofolder2/module3.ts`, `import * as M from "folder1/module1";`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "Add module3 to folder2", - change: sys => sys.writeFile(`${projectRoot}/client/linktofolder2/module3.ts`, `import * as M from "folder1/module1";`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - ] - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "when new file is added to the referenced project", - commandLineArgs: ["-w", "-p", `${projectRoot}/projects/project2/tsconfig.json`, "--extendedDiagnostics"], - sys: () => { - const config1: File = { - path: `${projectRoot}/projects/project1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - exclude: ["temp"] - }) - }; - const class1: File = { - path: `${projectRoot}/projects/project1/class1.ts`, - content: `class class1 {}` - }; - // Built file - const class1Dt: File = { - path: `${projectRoot}/projects/project1/class1.d.ts`, - content: `declare class class1 {}` - }; - const config2: File = { - path: `${projectRoot}/projects/project2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - references: [ - { path: "../project1" } - ] - }) - }; - const class2: File = { - path: `${projectRoot}/projects/project2/class2.ts`, - content: `class class2 {}` - }; - return createWatchedSystem([config1, class1, config2, class2, libFile, class1Dt]); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "when new file is added to the referenced project", + commandLineArgs: ["-w", "-p", `${ts.tscWatch.projectRoot}/projects/project2/tsconfig.json`, "--extendedDiagnostics"], + sys: () => { + const config1: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true + }, + exclude: ["temp"] + }) + }; + const class1: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/class1.ts`, + content: `class class1 {}` + }; + // Built file + const class1Dt: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/class1.d.ts`, + content: `declare class class1 {}` + }; + const config2: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true + }, + references: [ + { path: "../project1" } + ] + }) + }; + const class2: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project2/class2.ts`, + content: `class class2 {}` + }; + return ts.tscWatch.createWatchedSystem([config1, class1, config2, class2, ts.tscWatch.libFile, class1Dt]); + }, + changes: [ + { + caption: "Add class3 to project1", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.ts`, `class class3 {}`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "Add class3 to project1", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.ts`, `class class3 {}`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add output of class3", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add excluded file to project1", - change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), - timeouts: sys => sys.checkTimeoutQueueLength(0), - }, - { - caption: "Delete output of class3", - change: sys => sys.deleteFile(`${projectRoot}/projects/project1/class3.d.ts`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add output of class3", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - ] - }); + { + caption: "Add output of class3", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add excluded file to project1", + change: sys => sys.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), + timeouts: sys => sys.checkTimeoutQueueLength(0), + }, + { + caption: "Delete output of class3", + change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add output of class3", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "when creating extensionless file", - commandLineArgs: ["-w", "-p", ".", "--extendedDiagnostics"], - sys: () => { - const module1: File = { - path: `${projectRoot}/index.ts`, - content: `` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: `{}` - }; - return createWatchedSystem([module1, config, libFile], { currentDirectory: projectRoot }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "when creating extensionless file", + commandLineArgs: ["-w", "-p", ".", "--extendedDiagnostics"], + sys: () => { + const module1: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/index.ts`, + content: `` + }; + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: `{}` + }; + return ts.tscWatch.createWatchedSystem([module1, config, ts.tscWatch.libFile], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "Create foo in project root", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/foo`, ``), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, }, - changes: [ - { - caption: "Create foo in project root", - change: sys => sys.writeFile(`${projectRoot}/foo`, ``), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - ] - }); + ] }); -} +}); diff --git a/src/testRunner/unittests/tscWatch/projectsWithReferences.ts b/src/testRunner/unittests/tscWatch/projectsWithReferences.ts index cc93958b72d74..6a9d35661408f 100644 --- a/src/testRunner/unittests/tscWatch/projectsWithReferences.ts +++ b/src/testRunner/unittests/tscWatch/projectsWithReferences.ts @@ -1,445 +1,445 @@ -namespace ts.tscWatch { - describe("unittests:: tsc-watch:: projects with references: invoking when references are already built", () => { - verifyTscWatch({ - scenario: "projectsWithReferences", - subScenario: "on sample project", - sys: () => createSystemWithSolutionBuild( - ["tests"], - [ - libFile, - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/index.ts"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/anotherModule.ts"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/some_decl.d.ts"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/index.ts"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/index.ts"), - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/sample1` } - ), - commandLineArgs: ["-w", "-p", "tests"], - changes: [ - { - caption: "local edit in logic ts, and build logic", - change: sys => { - sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/index.ts"), `function foo() { }`); - const solutionBuilder = createSolutionBuilder(sys, ["logic"]); - solutionBuilder.build(); - }, - // not ideal, but currently because of d.ts but no new file is written - // There will be timeout queued even though file contents are same - timeouts: sys => sys.checkTimeoutQueueLength(0), +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsc-watch:: projects with references: invoking when references are already built", () => { + ts.tscWatch.verifyTscWatch({ + scenario: "projectsWithReferences", + subScenario: "on sample project", + sys: () => ts.tscWatch.createSystemWithSolutionBuild( + ["tests"], + [ + ts.tscWatch.libFile, + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/tsconfig.json"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/index.ts"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/anotherModule.ts"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/some_decl.d.ts"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/tsconfig.json"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/index.ts"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/tsconfig.json"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "tests/index.ts"), + ], + { currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/sample1` } + ), + commandLineArgs: ["-w", "-p", "tests"], + changes: [ + { + caption: "local edit in logic ts, and build logic", + change: sys => { + sys.appendFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/index.ts"), `function foo() { }`); + const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["logic"]); + solutionBuilder.build(); }, - { - caption: "non local edit in logic ts, and build logic", - change: sys => { - sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/index.ts"), `export function gfoo() { }`); - const solutionBuilder = createSolutionBuilder(sys, ["logic"]); - solutionBuilder.build(); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun + // not ideal, but currently because of d.ts but no new file is written + // There will be timeout queued even though file contents are same + timeouts: sys => sys.checkTimeoutQueueLength(0), + }, + { + caption: "non local edit in logic ts, and build logic", + change: sys => { + sys.appendFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/index.ts"), `export function gfoo() { }`); + const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["logic"]); + solutionBuilder.build(); }, - { - caption: "change in project reference config file builds correctly", - change: sys => { - sys.writeFile(TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/tsconfig.json"), JSON.stringify({ - compilerOptions: { composite: true, declaration: true, declarationDir: "decls" }, - references: [{ path: "../core" }] - })); - const solutionBuilder = createSolutionBuilder(sys, ["logic"]); - solutionBuilder.build(); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "change in project reference config file builds correctly", + change: sys => { + sys.writeFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("sample1", "logic/tsconfig.json"), JSON.stringify({ + compilerOptions: { composite: true, declaration: true, declarationDir: "decls" }, + references: [{ path: "../core" }] + })); + const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["logic"]); + solutionBuilder.build(); }, - ], - baselineDependencies: true - }); + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + ], + baselineDependencies: true + }); - function changeCompilerOpitonsPaths(sys: WatchedSystem, config: string, newPaths: object) { - const configJson = JSON.parse(sys.readFile(config)!); - configJson.compilerOptions.paths = newPaths; - sys.writeFile(config, JSON.stringify(configJson)); - } + function changeCompilerOpitonsPaths(sys: ts.tscWatch.WatchedSystem, config: string, newPaths: object) { + const configJson = JSON.parse(sys.readFile(config)!); + configJson.compilerOptions.paths = newPaths; + sys.writeFile(config, JSON.stringify(configJson)); + } - verifyTscWatch({ - scenario: "projectsWithReferences", - subScenario: "on transitive references", - sys: () => createSystemWithSolutionBuild( - ["tsconfig.c.json"], - [ - libFile, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "b.ts"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } - ), - commandLineArgs: ["-w", "-p", "tsconfig.c.json"], - changes: [ - { - caption: "non local edit b ts, and build b", - change: sys => { - sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"), `export function gfoo() { }`); - const solutionBuilder = createSolutionBuilder(sys, ["tsconfig.b.json"]); - solutionBuilder.build(); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun + ts.tscWatch.verifyTscWatch({ + scenario: "projectsWithReferences", + subScenario: "on transitive references", + sys: () => ts.tscWatch.createSystemWithSolutionBuild( + ["tsconfig.c.json"], + [ + ts.tscWatch.libFile, + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"), + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json"), + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"), + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"), + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "b.ts"), + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"), + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), + ], + { currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } + ), + commandLineArgs: ["-w", "-p", "tsconfig.c.json"], + changes: [ + { + caption: "non local edit b ts, and build b", + change: sys => { + sys.appendFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"), `export function gfoo() { }`); + const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["tsconfig.b.json"]); + solutionBuilder.build(); }, - { - caption: "edit on config file", - change: sys => { - sys.ensureFileOrFolder({ - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), - content: sys.readFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! - }); - changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./nrefs/*"] }); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit on config file", + change: sys => { + sys.ensureFileOrFolder({ + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), + content: sys.readFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! + }); + changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./nrefs/*"] }); }, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert config file edit", + change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./refs/*"] }), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit in referenced config file", + change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./nrefs/*"] }), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert referenced config file edit", + change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./refs/*"] }), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "deleting referenced config file", + change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json")), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert deleting referenced config file", + change: sys => sys.ensureFileOrFolder(ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json")), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "deleting transitively referenced config file", + change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.a.json")), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert deleting transitively referenced config file", + change: sys => sys.ensureFileOrFolder(ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json")), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + ], + baselineDependencies: true, + }); + + ts.tscWatch.verifyTscWatch({ + scenario: "projectsWithReferences", + subScenario: "when referenced project uses different module resolution", + sys: () => ts.tscWatch.createSystemWithSolutionBuild( + ["tsconfig.c.json"], + [ + ts.tscWatch.libFile, + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"), { - caption: "Revert config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.c.json"), { "@ref/*": ["./refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), + content: JSON.stringify({ + compilerOptions: { composite: true, moduleResolution: "classic" }, + files: ["b.ts"], + references: [{ path: "tsconfig.a.json" }] + }) }, + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"), + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"), { - caption: "edit in referenced config file", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./nrefs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"), + content: `import {A} from "a";export const b = new A();` }, + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"), + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), + ], + { currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } + ), + commandLineArgs: ["-w", "-p", "tsconfig.c.json"], + changes: ts.emptyArray, + baselineDependencies: true, + }); + + ts.tscWatch.verifyTscWatch({ + scenario: "projectsWithReferences", + subScenario: "on transitive references in different folders", + sys: () => ts.tscWatch.createSystemWithSolutionBuild( + ["c"], + [ + ts.tscWatch.libFile, { - caption: "Revert referenced config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), { "@ref/*": ["./refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), + content: JSON.stringify({ + compilerOptions: { composite: true }, + files: ["index.ts"] + }), }, { - caption: "deleting referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json")), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), + content: JSON.stringify({ + compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, + files: ["index.ts"], + references: [{ path: `../a` }] + }), }, { - caption: "Revert deleting referenced config file", - change: sys => sys.ensureFileOrFolder(TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.b.json")), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), + content: JSON.stringify({ + compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } }, + files: ["index.ts"], + references: [{ path: `../b` }] + }), }, { - caption: "deleting transitively referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.a.json")), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"), + content: `export class A {}`, }, { - caption: "Revert deleting transitively referenced config file", - change: sys => sys.ensureFileOrFolder(TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json")), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - ], - baselineDependencies: true, - }); - - verifyTscWatch({ - scenario: "projectsWithReferences", - subScenario: "when referenced project uses different module resolution", - sys: () => createSystemWithSolutionBuild( - ["tsconfig.c.json"], - [ - libFile, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.a.json"), - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "tsconfig.b.json"), - content: JSON.stringify({ - compilerOptions: { composite: true, moduleResolution: "classic" }, - files: ["b.ts"], - references: [{ path: "tsconfig.a.json" }] - }) - }, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "tsconfig.c.json"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "a.ts"), - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b.ts"), - content: `import {A} from "a";export const b = new A();` - }, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "c.ts"), - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } - ), - commandLineArgs: ["-w", "-p", "tsconfig.c.json"], - changes: emptyArray, - baselineDependencies: true, - }); - - verifyTscWatch({ - scenario: "projectsWithReferences", - subScenario: "on transitive references in different folders", - sys: () => createSystemWithSolutionBuild( - ["c"], - [ - libFile, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), - content: JSON.stringify({ - compilerOptions: { composite: true }, - files: ["index.ts"] - }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), - content: JSON.stringify({ - compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, - files: ["index.ts"], - references: [{ path: `../a` }] - }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), - content: JSON.stringify({ - compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } }, - files: ["index.ts"], - references: [{ path: `../b` }] - }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"), - content: `export class A {}`, - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), - content: `import {A} from '@ref/a'; + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), + content: `import {A} from '@ref/a'; export const b = new A();`, - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"), - content: `import {b} from '../b'; + }, + { + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"), + content: `import {b} from '../b'; import {X} from "@ref/a"; b; X;`, - }, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } - ), - commandLineArgs: ["-w", "-p", "c"], - changes: [ - { - caption: "non local edit b ts, and build b", - change: sys => { - sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`); - const solutionBuilder = createSolutionBuilder(sys, ["b"]); - solutionBuilder.build(); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun }, - { - caption: "edit on config file", - change: sys => { - sys.ensureFileOrFolder({ - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), - content: sys.readFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! - }); - changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), + ], + { currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } + ), + commandLineArgs: ["-w", "-p", "c"], + changes: [ + { + caption: "non local edit b ts, and build b", + change: sys => { + sys.appendFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`); + const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["b"]); + solutionBuilder.build(); }, - { - caption: "Revert config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit on config file", + change: sys => { + sys.ensureFileOrFolder({ + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), + content: sys.readFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! + }); + changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }); }, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert config file edit", + change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit in referenced config file", + change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert referenced config file edit", + change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "deleting referenced config file", + change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "Revert deleting referenced config file", + change: sys => sys.writeFile( + ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), + JSON.stringify({ + compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, + files: ["index.ts"], + references: [{ path: `../a` }] + }) + ), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "deleting transitively referenced config file", + change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "Revert deleting transitively referenced config file", + change: sys => sys.writeFile( + ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), + JSON.stringify({ + compilerOptions: { composite: true }, + files: ["index.ts"] + }), + ), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + ], + baselineDependencies: true, + }); + + ts.tscWatch.verifyTscWatch({ + scenario: "projectsWithReferences", + subScenario: "on transitive references in different folders with no files clause", + sys: () => ts.tscWatch.createSystemWithSolutionBuild( + ["c"], + [ + ts.tscWatch.libFile, { - caption: "edit in referenced config file", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), + content: JSON.stringify({ compilerOptions: { composite: true } }), }, { - caption: "Revert referenced config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), + content: JSON.stringify({ + compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, + references: [{ path: `../a` }] + }), }, { - caption: "deleting referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), + content: JSON.stringify({ + compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } }, + references: [{ path: `../b` }] + }), }, { - caption: "Revert deleting referenced config file", - change: sys => sys.writeFile( - TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), - JSON.stringify({ - compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, - files: ["index.ts"], - references: [{ path: `../a` }] - }) - ), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"), + content: `export class A {}`, }, { - caption: "deleting transitively referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), + content: `import {A} from '@ref/a'; +export const b = new A();`, }, { - caption: "Revert deleting transitively referenced config file", - change: sys => sys.writeFile( - TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), - JSON.stringify({ - compilerOptions: { composite: true }, - files: ["index.ts"] - }), - ), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) - }, - ], - baselineDependencies: true, - }); - - verifyTscWatch({ - scenario: "projectsWithReferences", - subScenario: "on transitive references in different folders with no files clause", - sys: () => createSystemWithSolutionBuild( - ["c"], - [ - libFile, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), - content: JSON.stringify({ compilerOptions: { composite: true } }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), - content: JSON.stringify({ - compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, - references: [{ path: `../a` }] - }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), - content: JSON.stringify({ - compilerOptions: { baseUrl: "./", paths: { "@ref/*": ["../refs/*"] } }, - references: [{ path: `../b` }] - }), - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/index.ts"), - content: `export class A {}`, - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), - content: `import {A} from '@ref/a'; -export const b = new A();`, - }, - { - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"), - content: `import {b} from '../b'; + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/index.ts"), + content: `import {b} from '../b'; import {X} from "@ref/a"; b; X;`, - }, - TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } - ), - commandLineArgs: ["-w", "-p", "c"], - changes: [ - { - caption: "non local edit b ts, and build b", - change: sys => { - sys.appendFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`); - const solutionBuilder = createSolutionBuilder(sys, ["b"]); - solutionBuilder.build(); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - { - caption: "edit on config file", - change: sys => { - sys.ensureFileOrFolder({ - path: TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), - content: sys.readFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! - }); - changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }); - }, - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - { - caption: "Revert config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - { - caption: "edit in referenced config file", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - { - caption: "Revert referenced config file edit", - change: sys => changeCompilerOpitonsPaths(sys, TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }), - timeouts: checkSingleTimeoutQueueLengthAndRun - }, - { - caption: "deleting referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) }, - { - caption: "Revert deleting referenced config file", - change: sys => sys.writeFile( - TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), - JSON.stringify({ - compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, - references: [{ path: `../a` }] - }) - ), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + ts.TestFSWithWatch.getTsBuildProjectFile("transitiveReferences", "refs/a.d.ts"), + ], + { currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/transitiveReferences` } + ), + commandLineArgs: ["-w", "-p", "c"], + changes: [ + { + caption: "non local edit b ts, and build b", + change: sys => { + sys.appendFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/index.ts"), `export function gfoo() { }`); + const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["b"]); + solutionBuilder.build(); }, - { - caption: "deleting transitively referenced config file", - change: sys => sys.deleteFile(TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit on config file", + change: sys => { + sys.ensureFileOrFolder({ + path: ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "nrefs/a.d.ts"), + content: sys.readFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "refs/a.d.ts"))! + }); + changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }); }, - { - caption: "Revert deleting transitively referenced config file", - change: sys => sys.writeFile( - TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), - JSON.stringify({ compilerOptions: { composite: true } }), - ), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) - }, - ], - baselineDependencies: true, - }); + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert config file edit", + change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "c/tsconfig.json"), { "@ref/*": ["../refs/*"] }), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "edit in referenced config file", + change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../nrefs/*"] }), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "Revert referenced config file edit", + change: sys => changeCompilerOpitonsPaths(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), { "@ref/*": ["../refs/*"] }), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun + }, + { + caption: "deleting referenced config file", + change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json")), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "Revert deleting referenced config file", + change: sys => sys.writeFile( + ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "b/tsconfig.json"), + JSON.stringify({ + compilerOptions: { composite: true, baseUrl: "./", paths: { "@ref/*": ["../*"] } }, + references: [{ path: `../a` }] + }) + ), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "deleting transitively referenced config file", + change: sys => sys.deleteFile(ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json")), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + { + caption: "Revert deleting transitively referenced config file", + change: sys => sys.writeFile( + ts.TestFSWithWatch.getTsBuildProjectFilePath("transitiveReferences", "a/tsconfig.json"), + JSON.stringify({ compilerOptions: { composite: true } }), + ), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2) + }, + ], + baselineDependencies: true, + }); - verifyTscWatch({ - scenario: "projectsWithReferences", - subScenario: "when declarationMap changes for dependency", - sys: () => createSystemWithSolutionBuild( - ["core"], - [ - libFile, - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/index.ts"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/anotherModule.ts"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "core/some_decl.d.ts"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/index.ts"), - ], - { currentDirectory: `${TestFSWithWatch.tsbuildProjectsLocation}/sample1` } - ), - commandLineArgs: ["-w", "-p", "logic"], - changes: [ - { - caption: "change declration map in core", - change: sys => { - replaceFileText(sys, TestFSWithWatch.getTsBuildProjectFilePath("sample1", "core/tsconfig.json"), `"declarationMap": true,`, `"declarationMap": false,`); - const solutionBuilder = createSolutionBuilder(sys, ["core"]); - solutionBuilder.build(); - }, - timeouts: sys => sys.runQueuedTimeoutCallbacks(0), - }, + ts.tscWatch.verifyTscWatch({ + scenario: "projectsWithReferences", + subScenario: "when declarationMap changes for dependency", + sys: () => ts.tscWatch.createSystemWithSolutionBuild( + ["core"], + [ + ts.tscWatch.libFile, + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/tsconfig.json"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/index.ts"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/anotherModule.ts"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "core/some_decl.d.ts"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/tsconfig.json"), + ts.TestFSWithWatch.getTsBuildProjectFile("sample1", "logic/index.ts"), ], - baselineDependencies: true - }); + { currentDirectory: `${ts.TestFSWithWatch.tsbuildProjectsLocation}/sample1` } + ), + commandLineArgs: ["-w", "-p", "logic"], + changes: [ + { + caption: "change declration map in core", + change: sys => { + ts.tscWatch.replaceFileText(sys, ts.TestFSWithWatch.getTsBuildProjectFilePath("sample1", "core/tsconfig.json"), `"declarationMap": true,`, `"declarationMap": false,`); + const solutionBuilder = ts.tscWatch.createSolutionBuilder(sys, ["core"]); + solutionBuilder.build(); + }, + timeouts: sys => sys.runQueuedTimeoutCallbacks(0), + }, + ], + baselineDependencies: true }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tscWatch/resolutionCache.ts b/src/testRunner/unittests/tscWatch/resolutionCache.ts index ef923e28d3cae..df69419ac6c20 100644 --- a/src/testRunner/unittests/tscWatch/resolutionCache.ts +++ b/src/testRunner/unittests/tscWatch/resolutionCache.ts @@ -1,566 +1,566 @@ -namespace ts.tscWatch { - describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution caching", () => { - const scenario = "resolutionCache"; - it("caching works", () => { - const root = { - path: "/a/d/f0.ts", - content: `import {x} from "f1"` - }; - const imported = { - path: "/a/f1.ts", - content: `foo()` - }; +import * as ts from "../../_namespaces/ts"; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, imported, libFile])); - const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ - rootFiles: [root.path], - system: sys, - options: { module: ModuleKind.AMD }, - cb, - watchOptions: undefined - }); - const originalFileExists = host.fileExists; - const watch = createWatchProgram(host); - let fileExistsIsCalled = false; - runWatchBaseline({ - scenario: "resolutionCache", - subScenario: "caching works", - commandLineArgs: ["--w", root.path], - sys, - baseline, - oldSnap, - getPrograms, - changes: [ - { - caption: "Adding text doesnt re-resole the imports", - change: sys => { - // patch fileExists to make sure that disk is not touched - host.fileExists = notImplemented; - sys.writeFile(root.path, `import {x} from "f1" +describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution caching", () => { + const scenario = "resolutionCache"; + it("caching works", () => { + const root = { + path: "/a/d/f0.ts", + content: `import {x} from "f1"` + }; + const imported = { + path: "/a/f1.ts", + content: `foo()` + }; + + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([root, imported, ts.tscWatch.libFile])); + const host = ts.tscWatch.createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ + rootFiles: [root.path], + system: sys, + options: { module: ts.ModuleKind.AMD }, + cb, + watchOptions: undefined + }); + const originalFileExists = host.fileExists; + const watch = ts.createWatchProgram(host); + let fileExistsIsCalled = false; + ts.tscWatch.runWatchBaseline({ + scenario: "resolutionCache", + subScenario: "caching works", + commandLineArgs: ["--w", root.path], + sys, + baseline, + oldSnap, + getPrograms, + changes: [ + { + caption: "Adding text doesnt re-resole the imports", + change: sys => { + // patch fileExists to make sure that disk is not touched + host.fileExists = ts.notImplemented; + sys.writeFile(root.path, `import {x} from "f1" var x: string = 1;`); - }, - timeouts: runQueuedTimeoutCallbacks }, - { - caption: "Resolves f2", - change: sys => { - host.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsIsCalled = true; - assert.isTrue(fileName.indexOf("/f2.") !== -1); - return originalFileExists.call(host, fileName); - }; - sys.writeFile(root.path, `import {x} from "f2"`); - }, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); - assert.isTrue(fileExistsIsCalled); - }, + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks + }, + { + caption: "Resolves f2", + change: sys => { + host.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsIsCalled = true; + assert.isTrue(fileName.indexOf("/f2.") !== -1); + return originalFileExists.call(host, fileName); + }; + sys.writeFile(root.path, `import {x} from "f2"`); }, - { - caption: "Resolve f1", - change: sys => { - fileExistsIsCalled = false; - host.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsIsCalled = true; - assert.isTrue(fileName.indexOf("/f1.") !== -1); - return originalFileExists.call(host, fileName); - }; - sys.writeFile(root.path, `import {x} from "f1"`); - }, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); - assert.isTrue(fileExistsIsCalled); - } + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); + assert.isTrue(fileExistsIsCalled); }, - ], - watchOrSolution: watch - }); + }, + { + caption: "Resolve f1", + change: sys => { + fileExistsIsCalled = false; + host.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsIsCalled = true; + assert.isTrue(fileName.indexOf("/f1.") !== -1); + return originalFileExists.call(host, fileName); + }; + sys.writeFile(root.path, `import {x} from "f1"`); + }, + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); + assert.isTrue(fileExistsIsCalled); + } + }, + ], + watchOrSolution: watch }); + }); - it("loads missing files from disk", () => { - const root = { - path: `/a/foo.ts`, - content: `import {x} from "bar"` - }; + it("loads missing files from disk", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; - const imported = { - path: `/a/bar.d.ts`, - content: `export const y = 1;` - }; + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;` + }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, libFile])); - const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ - rootFiles: [root.path], - system: sys, - options: { module: ModuleKind.AMD }, - cb, - watchOptions: undefined - }); - const originalFileExists = host.fileExists; - let fileExistsCalledForBar = false; - host.fileExists = fileName => { - if (fileName === "lib.d.ts") { - return false; - } - if (!fileExistsCalledForBar) { - fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([root, ts.tscWatch.libFile])); + const host = ts.tscWatch.createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ + rootFiles: [root.path], + system: sys, + options: { module: ts.ModuleKind.AMD }, + cb, + watchOptions: undefined + }); + const originalFileExists = host.fileExists; + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + + return originalFileExists.call(host, fileName); + }; + + const watch = ts.createWatchProgram(host); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + ts.tscWatch.runWatchBaseline({ + scenario: "resolutionCache", + subScenario: "loads missing files from disk", + commandLineArgs: ["--w", root.path], + sys, + baseline, + oldSnap, + getPrograms, + changes: [{ + caption: "write imported file", + change: sys => { + fileExistsCalledForBar = false; + sys.writeFile(root.path,`import {y} from "bar"`); + sys.writeFile(imported.path, imported.content); + }, + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); } + }], + watchOrSolution: watch + }); + }); - return originalFileExists.call(host, fileName); - }; + it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; + + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;export const x = 10;` + }; - const watch = createWatchProgram(host); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); - runWatchBaseline({ - scenario: "resolutionCache", - subScenario: "loads missing files from disk", - commandLineArgs: ["--w", root.path], - sys, - baseline, - oldSnap, - getPrograms, - changes: [{ - caption: "write imported file", + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([root, imported, ts.tscWatch.libFile])); + const host = ts.tscWatch.createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ + rootFiles: [root.path], + system: sys, + options: { module: ts.ModuleKind.AMD }, + cb, + watchOptions: undefined + }); + const originalFileExists = host.fileExists; + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + return originalFileExists.call(host, fileName); + }; + const watch = ts.createWatchProgram(host); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + ts.tscWatch.runWatchBaseline({ + scenario: "resolutionCache", + subScenario: "should compile correctly when resolved module goes missing and then comes back", + commandLineArgs: ["--w", root.path], + sys, + baseline, + oldSnap, + getPrograms, + changes: [ + { + caption: "Delete imported file", change: sys => { fileExistsCalledForBar = false; - sys.writeFile(root.path,`import {y} from "bar"`); - sys.writeFile(imported.path, imported.content); + sys.deleteFile(imported.path); }, timeouts: sys => { sys.runQueuedTimeoutCallbacks(); assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - } - }], - watchOrSolution: watch - }); - }); - - it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { - const root = { - path: `/a/foo.ts`, - content: `import {x} from "bar"` - }; - - const imported = { - path: `/a/bar.d.ts`, - content: `export const y = 1;export const x = 10;` - }; - - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, imported, libFile])); - const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ - rootFiles: [root.path], - system: sys, - options: { module: ModuleKind.AMD }, - cb, - watchOptions: undefined - }); - const originalFileExists = host.fileExists; - let fileExistsCalledForBar = false; - host.fileExists = fileName => { - if (fileName === "lib.d.ts") { - return false; - } - if (!fileExistsCalledForBar) { - fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; - } - return originalFileExists.call(host, fileName); - }; - const watch = createWatchProgram(host); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); - runWatchBaseline({ - scenario: "resolutionCache", - subScenario: "should compile correctly when resolved module goes missing and then comes back", - commandLineArgs: ["--w", root.path], - sys, - baseline, - oldSnap, - getPrograms, - changes: [ - { - caption: "Delete imported file", - change: sys => { - fileExistsCalledForBar = false; - sys.deleteFile(imported.path); - }, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - }, }, - { - caption: "Create imported file", - change: sys => { - fileExistsCalledForBar = false; - sys.writeFile(imported.path, imported.content); - }, - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // Scheduled invalidation of resolutions - sys.checkTimeoutQueueLengthAndRun(1); // Actual update - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - }, + }, + { + caption: "Create imported file", + change: sys => { + fileExistsCalledForBar = false; + sys.writeFile(imported.path, imported.content); }, - ], - watchOrSolution: watch - }); + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // Scheduled invalidation of resolutions + sys.checkTimeoutQueueLengthAndRun(1); // Actual update + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + }, + }, + ], + watchOrSolution: watch }); + }); - verifyTscWatch({ - scenario, - subScenario: "works when module resolution changes to ambient module", - commandLineArgs: ["-w", "/a/b/foo.ts"], - sys: () => createWatchedSystem([{ - path: "/a/b/foo.ts", - content: `import * as fs from "fs";` - }, libFile], { currentDirectory: "/a/b" }), - changes: [ - { - caption: "npm install node types", - change: sys => { - sys.ensureFileOrFolder({ - path: "/a/b/node_modules/@types/node/package.json", - content: ` + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "works when module resolution changes to ambient module", + commandLineArgs: ["-w", "/a/b/foo.ts"], + sys: () => ts.tscWatch.createWatchedSystem([{ + path: "/a/b/foo.ts", + content: `import * as fs from "fs";` + }, ts.tscWatch.libFile], { currentDirectory: "/a/b" }), + changes: [ + { + caption: "npm install node types", + change: sys => { + sys.ensureFileOrFolder({ + path: "/a/b/node_modules/@types/node/package.json", + content: ` { "main": "" } ` - }); - sys.ensureFileOrFolder({ - path: "/a/b/node_modules/@types/node/index.d.ts", - content: ` + }); + sys.ensureFileOrFolder({ + path: "/a/b/node_modules/@types/node/index.d.ts", + content: ` declare module "fs" { export interface Stats { isFile(): boolean; } }` - }); - }, - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + }); + }, + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "works when included file with ambient module changes", - commandLineArgs: ["--w", "/a/b/foo.ts", "/a/b/bar.d.ts"], - sys: () => { - const root = { - path: "/a/b/foo.ts", - content: ` + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "works when included file with ambient module changes", + commandLineArgs: ["--w", "/a/b/foo.ts", "/a/b/bar.d.ts"], + sys: () => { + const root = { + path: "/a/b/foo.ts", + content: ` import * as fs from "fs"; import * as u from "url"; ` - }; + }; - const file = { - path: "/a/b/bar.d.ts", - content: ` + const file = { + path: "/a/b/bar.d.ts", + content: ` declare module "url" { export interface Url { href?: string; } } ` - }; - return createWatchedSystem([root, file, libFile], { currentDirectory: "/a/b" }); - }, - changes: [ - { - caption: "Add fs definition", - change: sys => sys.appendFile("/a/b/bar.d.ts", ` + }; + return ts.tscWatch.createWatchedSystem([root, file, ts.tscWatch.libFile], { currentDirectory: "/a/b" }); + }, + changes: [ + { + caption: "Add fs definition", + change: sys => sys.appendFile("/a/b/bar.d.ts", ` declare module "fs" { export interface Stats { isFile(): boolean; } } `), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); - - verifyTscWatch({ - scenario, - subScenario: "works when reusing program with files from external library", - commandLineArgs: ["--w", "-p", "/a/b/projects/myProject/src"], - sys: () => { - const configDir = "/a/b/projects/myProject/src/"; - const file1: File = { - path: configDir + "file1.ts", - content: 'import module1 = require("module1");\nmodule1("hello");' - }; - const file2: File = { - path: configDir + "file2.ts", - content: 'import module11 = require("module1");\nmodule11("hello");' - }; - const module1: File = { - path: "/a/b/projects/myProject/node_modules/module1/index.js", - content: "module.exports = options => { return options.toString(); }" - }; - const configFile: File = { - path: configDir + "tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - allowJs: true, - rootDir: ".", - outDir: "../dist", - moduleResolution: "node", - maxNodeModuleJsDepth: 1 - } - }) - }; - return createWatchedSystem([file1, file2, module1, libFile, configFile], { currentDirectory: "/a/b/projects/myProject/" }); - }, - changes: [ - { - caption: "Add new line to file1", - change: sys => sys.appendFile("/a/b/projects/myProject/src/file1.ts", "\n;"), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "works when renaming node_modules folder that already contains @types folder", - commandLineArgs: ["--w", `${projectRoot}/a.ts`], - sys: () => { - const file: File = { - path: `${projectRoot}/a.ts`, - content: `import * as q from "qqq";` - }; - const module: File = { - path: `${projectRoot}/node_modules2/@types/qqq/index.d.ts`, - content: "export {}" - }; - return createWatchedSystem([file, libFile, module], { currentDirectory: projectRoot }); - }, - changes: [ - { - caption: "npm install", - change: sys => sys.renameFolder(`${projectRoot}/node_modules2`, `${projectRoot}/node_modules`), - timeouts: runQueuedTimeoutCallbacks, - } - ] - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "works when reusing program with files from external library", + commandLineArgs: ["--w", "-p", "/a/b/projects/myProject/src"], + sys: () => { + const configDir = "/a/b/projects/myProject/src/"; + const file1: ts.tscWatch.File = { + path: configDir + "file1.ts", + content: 'import module1 = require("module1");\nmodule1("hello");' + }; + const file2: ts.tscWatch.File = { + path: configDir + "file2.ts", + content: 'import module11 = require("module1");\nmodule11("hello");' + }; + const module1: ts.tscWatch.File = { + path: "/a/b/projects/myProject/node_modules/module1/index.js", + content: "module.exports = options => { return options.toString(); }" + }; + const configFile: ts.tscWatch.File = { + path: configDir + "tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + allowJs: true, + rootDir: ".", + outDir: "../dist", + moduleResolution: "node", + maxNodeModuleJsDepth: 1 + } + }) + }; + return ts.tscWatch.createWatchedSystem([file1, file2, module1, ts.tscWatch.libFile, configFile], { currentDirectory: "/a/b/projects/myProject/" }); + }, + changes: [ + { + caption: "Add new line to file1", + change: sys => sys.appendFile("/a/b/projects/myProject/src/file1.ts", "\n;"), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + } + ] + }); - describe("ignores files/folder changes in node_modules that start with '.'", () => { - function verifyIgnore(subScenario: string, commandLineArgs: readonly string[]) { - verifyTscWatch({ - scenario, - subScenario: `ignores changes in node_modules that start with dot/${subScenario}`, - commandLineArgs, - sys: () => { - const file1: File = { - path: `${projectRoot}/test.ts`, - content: `import { x } from "somemodule";` - }; - const file2: File = { - path: `${projectRoot}/node_modules/somemodule/index.d.ts`, - content: `export const x = 10;` - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - return createWatchedSystem([libFile, file1, file2, config]); - }, - changes: [ - { - caption: "npm install file and folder that start with '.'", - change: sys => sys.ensureFileOrFolder({ - path: `${projectRoot}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, - content: JSON.stringify({ something: 10 }) - }), - timeouts: sys => sys.checkTimeoutQueueLength(0), - } - ] - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "works when renaming node_modules folder that already contains @types folder", + commandLineArgs: ["--w", `${ts.tscWatch.projectRoot}/a.ts`], + sys: () => { + const file: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `import * as q from "qqq";` + }; + const module: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules2/@types/qqq/index.d.ts`, + content: "export {}" + }; + return ts.tscWatch.createWatchedSystem([file, ts.tscWatch.libFile, module], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "npm install", + change: sys => sys.renameFolder(`${ts.tscWatch.projectRoot}/node_modules2`, `${ts.tscWatch.projectRoot}/node_modules`), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, } - verifyIgnore("watch without configFile", ["--w", `${projectRoot}/test.ts`]); - verifyIgnore("watch with configFile", ["--w", "-p", `${projectRoot}/tsconfig.json`]); - }); + ] + }); - verifyTscWatch({ - scenario, - subScenario: "when types in compiler option are global and installed at later point", - commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], - sys: () => { - const app: File = { - path: `${projectRoot}/lib/app.ts`, - content: `myapp.component("hello");` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - types: ["@myapp/ts-types"] - } - }) - }; - return createWatchedSystem([app, tsconfig, libFile]); - }, - changes: [ - { - caption: "npm install ts-types", - change: sys => { - sys.ensureFileOrFolder({ - path: `${projectRoot}/node_modules/@myapp/ts-types/package.json`, - content: JSON.stringify({ - version: "1.65.1", - types: "types/somefile.define.d.ts" - }) - }); - sys.ensureFileOrFolder({ - path: `${projectRoot}/node_modules/@myapp/ts-types/types/somefile.define.d.ts`, - content: ` + describe("ignores files/folder changes in node_modules that start with '.'", () => { + function verifyIgnore(subScenario: string, commandLineArgs: readonly string[]) { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `ignores changes in node_modules that start with dot/${subScenario}`, + commandLineArgs, + sys: () => { + const file1: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/test.ts`, + content: `import { x } from "somemodule";` + }; + const file2: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/somemodule/index.d.ts`, + content: `export const x = 10;` + }; + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + return ts.tscWatch.createWatchedSystem([ts.tscWatch.libFile, file1, file2, config]); + }, + changes: [ + { + caption: "npm install file and folder that start with '.'", + change: sys => sys.ensureFileOrFolder({ + path: `${ts.tscWatch.projectRoot}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, + content: JSON.stringify({ something: 10 }) + }), + timeouts: sys => sys.checkTimeoutQueueLength(0), + } + ] + }); + } + verifyIgnore("watch without configFile", ["--w", `${ts.tscWatch.projectRoot}/test.ts`]); + verifyIgnore("watch with configFile", ["--w", "-p", `${ts.tscWatch.projectRoot}/tsconfig.json`]); + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "when types in compiler option are global and installed at later point", + commandLineArgs: ["--w", "-p", `${ts.tscWatch.projectRoot}/tsconfig.json`], + sys: () => { + const app: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/lib/app.ts`, + content: `myapp.component("hello");` + }; + const tsconfig: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + types: ["@myapp/ts-types"] + } + }) + }; + return ts.tscWatch.createWatchedSystem([app, tsconfig, ts.tscWatch.libFile]); + }, + changes: [ + { + caption: "npm install ts-types", + change: sys => { + sys.ensureFileOrFolder({ + path: `${ts.tscWatch.projectRoot}/node_modules/@myapp/ts-types/package.json`, + content: JSON.stringify({ + version: "1.65.1", + types: "types/somefile.define.d.ts" + }) + }); + sys.ensureFileOrFolder({ + path: `${ts.tscWatch.projectRoot}/node_modules/@myapp/ts-types/types/somefile.define.d.ts`, + content: ` declare namespace myapp { function component(str: string): number; }` - }); - }, - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(2); // Scheduled invalidation of resolutions, update that gets cancelled and rescheduled by actual invalidation of resolution - sys.checkTimeoutQueueLengthAndRun(1); // Actual update - }, + }); }, - { - caption: "No change, just check program", - change: noop, - timeouts: (sys, [[oldProgram, oldBuilderProgram]], watchorSolution) => { - sys.checkTimeoutQueueLength(0); - const newProgram = (watchorSolution as WatchOfConfigFile).getProgram(); - assert.strictEqual(newProgram, oldBuilderProgram, "No change so builder program should be same"); - assert.strictEqual(newProgram.getProgram(), oldProgram, "No change so program should be same"); - } + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(2); // Scheduled invalidation of resolutions, update that gets cancelled and rescheduled by actual invalidation of resolution + sys.checkTimeoutQueueLengthAndRun(1); // Actual update + }, + }, + { + caption: "No change, just check program", + change: ts.noop, + timeouts: (sys, [[oldProgram, oldBuilderProgram]], watchorSolution) => { + sys.checkTimeoutQueueLength(0); + const newProgram = (watchorSolution as ts.WatchOfConfigFile).getProgram(); + assert.strictEqual(newProgram, oldBuilderProgram, "No change so builder program should be same"); + assert.strictEqual(newProgram.getProgram(), oldProgram, "No change so program should be same"); } - ] - }); + } + ] + }); - verifyTscWatch({ - scenario, - subScenario: "with modules linked to sibling folder", - commandLineArgs: ["-w"], - sys: () => { - const mainPackageRoot = `${projectRoot}/main`; - const linkedPackageRoot = `${projectRoot}/linked-package`; - const mainFile: File = { - path: `${mainPackageRoot}/index.ts`, - content: "import { Foo } from '@scoped/linked-package'" - }; - const config: File = { - path: `${mainPackageRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." }, - files: ["index.ts"] - }) - }; - const linkedPackageInMain: SymLink = { - path: `${mainPackageRoot}/node_modules/@scoped/linked-package`, - symLink: `${linkedPackageRoot}` - }; - const linkedPackageJson: File = { - path: `${linkedPackageRoot}/package.json`, - content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" }) - }; - const linkedPackageIndex: File = { - path: `${linkedPackageRoot}/dist/index.d.ts`, - content: "export * from './other';" - }; - const linkedPackageOther: File = { - path: `${linkedPackageRoot}/dist/other.d.ts`, - content: 'export declare const Foo = "BAR";' - }; - const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther]; - return createWatchedSystem(files, { currentDirectory: mainPackageRoot }); - }, - changes: emptyArray - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "with modules linked to sibling folder", + commandLineArgs: ["-w"], + sys: () => { + const mainPackageRoot = `${ts.tscWatch.projectRoot}/main`; + const linkedPackageRoot = `${ts.tscWatch.projectRoot}/linked-package`; + const mainFile: ts.tscWatch.File = { + path: `${mainPackageRoot}/index.ts`, + content: "import { Foo } from '@scoped/linked-package'" + }; + const config: ts.tscWatch.File = { + path: `${mainPackageRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." }, + files: ["index.ts"] + }) + }; + const linkedPackageInMain: ts.tscWatch.SymLink = { + path: `${mainPackageRoot}/node_modules/@scoped/linked-package`, + symLink: `${linkedPackageRoot}` + }; + const linkedPackageJson: ts.tscWatch.File = { + path: `${linkedPackageRoot}/package.json`, + content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" }) + }; + const linkedPackageIndex: ts.tscWatch.File = { + path: `${linkedPackageRoot}/dist/index.d.ts`, + content: "export * from './other';" + }; + const linkedPackageOther: ts.tscWatch.File = { + path: `${linkedPackageRoot}/dist/other.d.ts`, + content: 'export declare const Foo = "BAR";' + }; + const files = [ts.tscWatch.libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther]; + return ts.tscWatch.createWatchedSystem(files, { currentDirectory: mainPackageRoot }); + }, + changes: ts.emptyArray + }); - describe("works when installing something in node_modules or @types when there is no notification from fs for index file", () => { - function getNodeAtTypes() { - const nodeAtTypesIndex: File = { - path: `${projectRoot}/node_modules/@types/node/index.d.ts`, - content: `/// ` - }; - const nodeAtTypesBase: File = { - path: `${projectRoot}/node_modules/@types/node/base.d.ts`, - content: `// Base definitions for all NodeJS modules that are not specific to any version of TypeScript: + describe("works when installing something in node_modules or @types when there is no notification from fs for index file", () => { + function getNodeAtTypes() { + const nodeAtTypesIndex: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/@types/node/index.d.ts`, + content: `/// ` + }; + const nodeAtTypesBase: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/@types/node/base.d.ts`, + content: `// Base definitions for all NodeJS modules that are not specific to any version of TypeScript: /// ` - }; - const nodeAtTypes36Base: File = { - path: `${projectRoot}/node_modules/@types/node/ts3.6/base.d.ts`, - content: `/// ` - }; - const nodeAtTypesGlobals: File = { - path: `${projectRoot}/node_modules/@types/node/globals.d.ts`, - content: `declare var process: NodeJS.Process; + }; + const nodeAtTypes36Base: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/@types/node/ts3.6/base.d.ts`, + content: `/// ` + }; + const nodeAtTypesGlobals: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/@types/node/globals.d.ts`, + content: `declare var process: NodeJS.Process; declare namespace NodeJS { interface Process { on(msg: string): void; } }` + }; + return { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals }; + } + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "works when installing something in node_modules or @types when there is no notification from fs for index file", + commandLineArgs: ["--w", `--extendedDiagnostics`], + sys: () => { + const file: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/worker.ts`, + content: `process.on("uncaughtException");` }; - return { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals }; - } - verifyTscWatch({ - scenario, - subScenario: "works when installing something in node_modules or @types when there is no notification from fs for index file", - commandLineArgs: ["--w", `--extendedDiagnostics`], - sys: () => { - const file: File = { - path: `${projectRoot}/worker.ts`, - content: `process.on("uncaughtException");` - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - const { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals } = getNodeAtTypes(); - return createWatchedSystem([file, libFile, tsconfig, nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals], { currentDirectory: projectRoot }); + const tsconfig: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals } = getNodeAtTypes(); + return ts.tscWatch.createWatchedSystem([file, ts.tscWatch.libFile, tsconfig, nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals], { currentDirectory: ts.tscWatch.projectRoot }); + }, + changes: [ + { + caption: "npm ci step one: remove all node_modules files", + change: sys => sys.deleteFolder(`${ts.tscWatch.projectRoot}/node_modules/@types`, /*recursive*/ true), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, }, - changes: [ - { - caption: "npm ci step one: remove all node_modules files", - change: sys => sys.deleteFolder(`${projectRoot}/node_modules/@types`, /*recursive*/ true), - timeouts: runQueuedTimeoutCallbacks, - }, - { - caption: `npm ci step two: create atTypes but something else in the @types folder`, - change: sys => sys.ensureFileOrFolder({ - path: `${projectRoot}/node_modules/@types/mocha/index.d.ts`, - content: `export const foo = 10;` - }), - timeouts: runQueuedTimeoutCallbacks - }, - { - caption: `npm ci step three: create atTypes node folder`, - change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/node_modules/@types/node` }), - timeouts: runQueuedTimeoutCallbacks + { + caption: `npm ci step two: create atTypes but something else in the @types folder`, + change: sys => sys.ensureFileOrFolder({ + path: `${ts.tscWatch.projectRoot}/node_modules/@types/mocha/index.d.ts`, + content: `export const foo = 10;` + }), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks + }, + { + caption: `npm ci step three: create atTypes node folder`, + change: sys => sys.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/node_modules/@types/node` }), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks + }, + { + caption: `npm ci step four: create atTypes write all the files but dont invoke watcher for index.d.ts`, + change: sys => { + const { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals } = getNodeAtTypes(); + sys.ensureFileOrFolder(nodeAtTypesBase); + sys.ensureFileOrFolder(nodeAtTypesIndex, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); + sys.ensureFileOrFolder(nodeAtTypes36Base, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); + sys.ensureFileOrFolder(nodeAtTypesGlobals, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); }, - { - caption: `npm ci step four: create atTypes write all the files but dont invoke watcher for index.d.ts`, - change: sys => { - const { nodeAtTypesIndex, nodeAtTypesBase, nodeAtTypes36Base, nodeAtTypesGlobals } = getNodeAtTypes(); - sys.ensureFileOrFolder(nodeAtTypesBase); - sys.ensureFileOrFolder(nodeAtTypesIndex, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); - sys.ensureFileOrFolder(nodeAtTypes36Base, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); - sys.ensureFileOrFolder(nodeAtTypesGlobals, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); - }, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); // update failed lookups - sys.runQueuedTimeoutCallbacks(); // actual program update - }, + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); // update failed lookups + sys.runQueuedTimeoutCallbacks(); // actual program update }, - ] - }); + }, + ] }); }); -} +}); diff --git a/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts b/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts index 4110dbfe3331b..7be526e395220 100644 --- a/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts +++ b/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts @@ -1,173 +1,173 @@ -namespace ts.tscWatch { - import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile; - describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedirect", () => { - interface VerifyWatchInput { - files: readonly TestFSWithWatch.FileOrFolderOrSymLink[]; - config: string; - subScenario: string; - } +import * as ts from "../../_namespaces/ts"; - function verifyWatch({ files, config, subScenario }: VerifyWatchInput, alreadyBuilt: boolean) { - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline( - createWatchedSystem(files), - alreadyBuilt ? (sys, originalRead) => { - solutionBuildWithBaseline(sys, [config], originalRead); - sys.clearOutput(); - } : undefined - ); - const host = createWatchCompilerHostOfConfigFileForBaseline({ - configFileName: config, - system: sys, - cb, - }); - host.useSourceOfProjectReferenceRedirect = returnTrue; - const watch = createWatchProgram(host); - runWatchBaseline({ - scenario: "sourceOfProjectReferenceRedirect", - subScenario: `${subScenario}${alreadyBuilt ? " when solution is already built" : ""}`, - commandLineArgs: ["--w", "--p", config], - sys, - baseline, - oldSnap, - getPrograms, - changes: emptyArray, - watchOrSolution: watch - }); - } +import getFileFromProject = ts.TestFSWithWatch.getTsBuildProjectFile; +describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedirect", () => { + interface VerifyWatchInput { + files: readonly ts.TestFSWithWatch.FileOrFolderOrSymLink[]; + config: string; + subScenario: string; + } - function verifyScenario(input: () => VerifyWatchInput) { - it("when solution is not built", () => { - verifyWatch(input(), /*alreadyBuilt*/ false); - }); + function verifyWatch({ files, config, subScenario }: VerifyWatchInput, alreadyBuilt: boolean) { + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline( + ts.tscWatch.createWatchedSystem(files), + alreadyBuilt ? (sys, originalRead) => { + ts.tscWatch.solutionBuildWithBaseline(sys, [config], originalRead); + sys.clearOutput(); + } : undefined + ); + const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({ + configFileName: config, + system: sys, + cb, + }); + host.useSourceOfProjectReferenceRedirect = ts.returnTrue; + const watch = ts.createWatchProgram(host); + ts.tscWatch.runWatchBaseline({ + scenario: "sourceOfProjectReferenceRedirect", + subScenario: `${subScenario}${alreadyBuilt ? " when solution is already built" : ""}`, + commandLineArgs: ["--w", "--p", config], + sys, + baseline, + oldSnap, + getPrograms, + changes: ts.emptyArray, + watchOrSolution: watch + }); + } - it("when solution is already built", () => { - verifyWatch(input(), /*alreadyBuilt*/ true); + function verifyScenario(input: () => VerifyWatchInput) { + it("when solution is not built", () => { + verifyWatch(input(), /*alreadyBuilt*/ false); + }); + + it("when solution is already built", () => { + verifyWatch(input(), /*alreadyBuilt*/ true); + }); + } + + describe("with simple project", () => { + verifyScenario(() => { + const baseConfig = getFileFromProject("demo", "tsconfig-base.json"); + const coreTs = getFileFromProject("demo", "core/utilities.ts"); + const coreConfig = getFileFromProject("demo", "core/tsconfig.json"); + const animalTs = getFileFromProject("demo", "animals/animal.ts"); + const dogTs = getFileFromProject("demo", "animals/dog.ts"); + const indexTs = getFileFromProject("demo", "animals/index.ts"); + const animalsConfig = getFileFromProject("demo", "animals/tsconfig.json"); + return { + files: [{ path: ts.tscWatch.libFile.path, content: ts.libContent }, baseConfig, coreTs, coreConfig, animalTs, dogTs, indexTs, animalsConfig], + config: animalsConfig.path, + subScenario: "with simple project" + }; + }); + }); + + describe("when references are monorepo like with symlinks", () => { + interface Packages { + bPackageJson: ts.tscWatch.File; + aTest: ts.tscWatch.File; + bFoo: ts.tscWatch.File; + bBar: ts.tscWatch.File; + bSymlink: ts.tscWatch.SymLink; + subScenario: string; + } + function verifySymlinkScenario(packages: () => Packages) { + describe("when preserveSymlinks is turned off", () => { + verifySymlinkScenarioWorker(packages, {}); + }); + describe("when preserveSymlinks is turned on", () => { + verifySymlinkScenarioWorker(packages, { preserveSymlinks: true }); }); } - describe("with simple project", () => { + function verifySymlinkScenarioWorker(packages: () => Packages, extraOptions: ts.CompilerOptions) { verifyScenario(() => { - const baseConfig = getFileFromProject("demo", "tsconfig-base.json"); - const coreTs = getFileFromProject("demo", "core/utilities.ts"); - const coreConfig = getFileFromProject("demo", "core/tsconfig.json"); - const animalTs = getFileFromProject("demo", "animals/animal.ts"); - const dogTs = getFileFromProject("demo", "animals/dog.ts"); - const indexTs = getFileFromProject("demo", "animals/index.ts"); - const animalsConfig = getFileFromProject("demo", "animals/tsconfig.json"); + const { bPackageJson, aTest, bFoo, bBar, bSymlink, subScenario } = packages(); + const aConfig = config("A", extraOptions, ["../B"]); + const bConfig = config("B", extraOptions); return { - files: [{ path: libFile.path, content: libContent }, baseConfig, coreTs, coreConfig, animalTs, dogTs, indexTs, animalsConfig], - config: animalsConfig.path, - subScenario: "with simple project" + files: [ts.tscWatch.libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink], + config: aConfig.path, + subScenario: `${subScenario}${extraOptions.preserveSymlinks ? " with preserveSymlinks" : ""}` }; }); - }); - - describe("when references are monorepo like with symlinks", () => { - interface Packages { - bPackageJson: File; - aTest: File; - bFoo: File; - bBar: File; - bSymlink: SymLink; - subScenario: string; - } - function verifySymlinkScenario(packages: () => Packages) { - describe("when preserveSymlinks is turned off", () => { - verifySymlinkScenarioWorker(packages, {}); - }); - describe("when preserveSymlinks is turned on", () => { - verifySymlinkScenarioWorker(packages, { preserveSymlinks: true }); - }); - } - - function verifySymlinkScenarioWorker(packages: () => Packages, extraOptions: CompilerOptions) { - verifyScenario(() => { - const { bPackageJson, aTest, bFoo, bBar, bSymlink, subScenario } = packages(); - const aConfig = config("A", extraOptions, ["../B"]); - const bConfig = config("B", extraOptions); - return { - files: [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink], - config: aConfig.path, - subScenario: `${subScenario}${extraOptions.preserveSymlinks ? " with preserveSymlinks" : ""}` - }; - }); - } + } - function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File { - return { - path: `${projectRoot}/packages/${packageName}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "lib", - rootDir: "src", - composite: true, - ...extraOptions - }, - include: ["src"], - ...(references ? { references: references.map(path => ({ path })) } : {}) - }) - }; - } + function config(packageName: string, extraOptions: ts.CompilerOptions, references?: string[]): ts.tscWatch.File { + return { + path: `${ts.tscWatch.projectRoot}/packages/${packageName}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "lib", + rootDir: "src", + composite: true, + ...extraOptions + }, + include: ["src"], + ...(references ? { references: references.map(path => ({ path })) } : {}) + }) + }; + } - function file(packageName: string, fileName: string, content: string): File { - return { - path: `${projectRoot}/packages/${packageName}/src/${fileName}`, - content - }; - } + function file(packageName: string, fileName: string, content: string): ts.tscWatch.File { + return { + path: `${ts.tscWatch.projectRoot}/packages/${packageName}/src/${fileName}`, + content + }; + } - function verifyMonoRepoLike(scope = "") { - describe("when packageJson has types field", () => { - verifySymlinkScenario(() => ({ - bPackageJson: { - path: `${projectRoot}/packages/B/package.json`, - content: JSON.stringify({ - main: "lib/index.js", - types: "lib/index.d.ts" - }) - }, - aTest: file("A", "index.ts", `import { foo } from '${scope}b'; + function verifyMonoRepoLike(scope = "") { + describe("when packageJson has types field", () => { + verifySymlinkScenario(() => ({ + bPackageJson: { + path: `${ts.tscWatch.projectRoot}/packages/B/package.json`, + content: JSON.stringify({ + main: "lib/index.js", + types: "lib/index.d.ts" + }) + }, + aTest: file("A", "index.ts", `import { foo } from '${scope}b'; import { bar } from '${scope}b/lib/bar'; foo(); bar(); `), - bFoo: file("B", "index.ts", `export function foo() { }`), - bBar: file("B", "bar.ts", `export function bar() { }`), - bSymlink: { - path: `${projectRoot}/node_modules/${scope}b`, - symLink: `${projectRoot}/packages/B` - }, - subScenario: `when packageJson has types field${scope ? " with scoped package" : ""}` - })); - }); + bFoo: file("B", "index.ts", `export function foo() { }`), + bBar: file("B", "bar.ts", `export function bar() { }`), + bSymlink: { + path: `${ts.tscWatch.projectRoot}/node_modules/${scope}b`, + symLink: `${ts.tscWatch.projectRoot}/packages/B` + }, + subScenario: `when packageJson has types field${scope ? " with scoped package" : ""}` + })); + }); - describe("when referencing file from subFolder", () => { - verifySymlinkScenario(() => ({ - bPackageJson: { - path: `${projectRoot}/packages/B/package.json`, - content: "{}" - }, - aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo'; + describe("when referencing file from subFolder", () => { + verifySymlinkScenario(() => ({ + bPackageJson: { + path: `${ts.tscWatch.projectRoot}/packages/B/package.json`, + content: "{}" + }, + aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo'; import { bar } from '${scope}b/lib/bar/foo'; foo(); bar(); `), - bFoo: file("B", "foo.ts", `export function foo() { }`), - bBar: file("B", "bar/foo.ts", `export function bar() { }`), - bSymlink: { - path: `${projectRoot}/node_modules/${scope}b`, - symLink: `${projectRoot}/packages/B` - }, - subScenario: `when referencing file from subFolder${scope ? " with scoped package" : ""}` - })); - }); - } - describe("when package is not scoped", () => { - verifyMonoRepoLike(); - }); - describe("when package is scoped", () => { - verifyMonoRepoLike("@issue/"); + bFoo: file("B", "foo.ts", `export function foo() { }`), + bBar: file("B", "bar/foo.ts", `export function bar() { }`), + bSymlink: { + path: `${ts.tscWatch.projectRoot}/node_modules/${scope}b`, + symLink: `${ts.tscWatch.projectRoot}/packages/B` + }, + subScenario: `when referencing file from subFolder${scope ? " with scoped package" : ""}` + })); }); + } + describe("when package is not scoped", () => { + verifyMonoRepoLike(); + }); + describe("when package is scoped", () => { + verifyMonoRepoLike("@issue/"); }); }); -} +}); diff --git a/src/testRunner/unittests/tscWatch/watchApi.ts b/src/testRunner/unittests/tscWatch/watchApi.ts index a755e1882e13e..9f8219610b2f7 100644 --- a/src/testRunner/unittests/tscWatch/watchApi.ts +++ b/src/testRunner/unittests/tscWatch/watchApi.ts @@ -1,707 +1,708 @@ -namespace ts.tscWatch { - describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => { - it("verify that module resolution with json extension works when returned without extension", () => { - const configFileJson: any = { - compilerOptions: { module: "commonjs", resolveJsonModule: true }, - files: ["index.ts"] - }; - const mainFile: File = { - path: `${projectRoot}/index.ts`, - content: "import settings from './settings.json';" - }; - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify(configFileJson) - }; - const settingsJson: File = { - path: `${projectRoot}/settings.json`, - content: JSON.stringify({ content: "Print this" }) +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; + +describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolution", () => { + it("verify that module resolution with json extension works when returned without extension", () => { + const configFileJson: any = { + compilerOptions: { module: "commonjs", resolveJsonModule: true }, + files: ["index.ts"] + }; + const mainFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/index.ts`, + content: "import settings from './settings.json';" + }; + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify(configFileJson) + }; + const settingsJson: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/settings.json`, + content: JSON.stringify({ content: "Print this" }) + }; + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem( + [ts.tscWatch.libFile, mainFile, config, settingsJson], + { currentDirectory: ts.tscWatch.projectRoot }), + ); + const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({ + configFileName: config.path, + system: sys, + cb, + }); + const parsedCommandResult = ts.parseJsonConfigFileContent(configFileJson, sys, config.path); + host.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => { + const result = ts.resolveModuleName(m, containingFile, parsedCommandResult.options, host); + const resolvedModule = result.resolvedModule!; + return { + resolvedFileName: resolvedModule.resolvedFileName, + isExternalLibraryImport: resolvedModule.isExternalLibraryImport, + originalFileName: resolvedModule.originalPath, }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem( - [libFile, mainFile, config, settingsJson], - { currentDirectory: projectRoot }), - ); - const host = createWatchCompilerHostOfConfigFileForBaseline({ - configFileName: config.path, - system: sys, - cb, - }); - const parsedCommandResult = parseJsonConfigFileContent(configFileJson, sys, config.path); - host.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => { - const result = resolveModuleName(m, containingFile, parsedCommandResult.options, host); - const resolvedModule = result.resolvedModule!; - return { - resolvedFileName: resolvedModule.resolvedFileName, - isExternalLibraryImport: resolvedModule.isExternalLibraryImport, - originalFileName: resolvedModule.originalPath, - }; - }); - const watch = createWatchProgram(host); - runWatchBaseline({ - scenario: "watchApi", - subScenario: "verify that module resolution with json extension works when returned without extension", - commandLineArgs: ["--w", "--p", config.path], - sys, - baseline, - oldSnap, - getPrograms, - changes: emptyArray, - watchOrSolution: watch - }); }); - - describe("hasInvalidatedResolutions", () => { - function verifyWatch(subScenario: string, implementHasInvalidatedResolution: boolean) { - it(subScenario, () => { - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem({ - [`${projectRoot}/tsconfig.json`]: JSON.stringify({ - compilerOptions: { traceResolution: true, extendedDiagnostics: true }, - files: ["main.ts"] - }), - [`${projectRoot}/main.ts`]: `import { foo } from "./other";`, - [`${projectRoot}/other.d.ts`]: "export function foo(): void;", - [libFile.path]: libFile.content, - }, { currentDirectory: projectRoot })); - const host = createWatchCompilerHostOfConfigFileForBaseline({ - configFileName: `${projectRoot}/tsconfig.json`, - system: sys, - cb, - }); - host.resolveModuleNames = (moduleNames, containingFile, _reusedNames, _redirectedReference, options) => - moduleNames.map(m => resolveModuleName(m, containingFile, options, host).resolvedModule); - // Invalidate resolutions only when ts file is created - if (implementHasInvalidatedResolution) host.hasInvalidatedResolutions = () => sys.fileExists(`${projectRoot}/other.ts`); - const watch = createWatchProgram(host); - runWatchBaseline({ - scenario: "watchApi", - subScenario, - commandLineArgs: ["--w"], - sys, - baseline, - oldSnap, - getPrograms, - changes: [ - { - caption: "write other with same contents", - change: sys => sys.appendFile(`${projectRoot}/other.d.ts`, ""), - timeouts: sys => sys.runQueuedTimeoutCallbacks(), - }, - { - caption: "change other file", - change: sys => sys.appendFile(`${projectRoot}/other.d.ts`, "export function bar(): void;"), - timeouts: sys => sys.runQueuedTimeoutCallbacks(), - }, - { - caption: "write other with same contents but write ts file", - change: sys => { - sys.appendFile(`${projectRoot}/other.d.ts`, ""); - sys.writeFile(`${projectRoot}/other.ts`, "export function foo() {}"); - }, - timeouts: sys => sys.runQueuedTimeoutCallbacks(), - }, - ], - watchOrSolution: watch - }); - }); - } - verifyWatch("host implements does not implement hasInvalidatedResolutions", /*implementHasInvalidatedResolution*/ false); - verifyWatch("host implements hasInvalidatedResolutions", /*implementHasInvalidatedResolution*/ true); + const watch = ts.createWatchProgram(host); + ts.tscWatch.runWatchBaseline({ + scenario: "watchApi", + subScenario: "verify that module resolution with json extension works when returned without extension", + commandLineArgs: ["--w", "--p", config.path], + sys, + baseline, + oldSnap, + getPrograms, + changes: ts.emptyArray, + watchOrSolution: watch }); }); - describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to watch status reporter", () => { - it("verify that the error count is correctly passed down to the watch status reporter", () => { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { module: "commonjs" }, - files: ["index.ts"] - }) - }; - const mainFile: File = { - path: `${projectRoot}/index.ts`, - content: "let compiler = new Compiler(); for (let i = 0; j < 5; i++) {}" - }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem( - [libFile, mainFile, config], - { currentDirectory: projectRoot }), - ); - const host = createWatchCompilerHostOfConfigFileForBaseline({ - configFileName: config.path, - system: sys, - cb, - }); - const existing = host.onWatchStatusChange!; - let watchedErrorCount; - host.onWatchStatusChange = (diagnostic, newLine, options, errorCount) => { - existing.call(host, diagnostic, newLine, options, errorCount); - watchedErrorCount = errorCount; - }; - const watch = createWatchProgram(host); - assert.equal(watchedErrorCount, 2, "The error count was expected to be 2 for the file change"); - runWatchBaseline({ - scenario: "watchApi", - subScenario: "verify that the error count is correctly passed down to the watch status reporter", - commandLineArgs: ["--w", "--p", config.path], - sys, - baseline, - oldSnap, - getPrograms, - changes: emptyArray, - watchOrSolution: watch + describe("hasInvalidatedResolutions", () => { + function verifyWatch(subScenario: string, implementHasInvalidatedResolution: boolean) { + it(subScenario, () => { + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem({ + [`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({ + compilerOptions: { traceResolution: true, extendedDiagnostics: true }, + files: ["main.ts"] + }), + [`${ts.tscWatch.projectRoot}/main.ts`]: `import { foo } from "./other";`, + [`${ts.tscWatch.projectRoot}/other.d.ts`]: "export function foo(): void;", + [ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content, + }, { currentDirectory: ts.tscWatch.projectRoot })); + const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({ + configFileName: `${ts.tscWatch.projectRoot}/tsconfig.json`, + system: sys, + cb, + }); + host.resolveModuleNames = (moduleNames, containingFile, _reusedNames, _redirectedReference, options) => + moduleNames.map(m => ts.resolveModuleName(m, containingFile, options, host).resolvedModule); + // Invalidate resolutions only when ts file is created + if (implementHasInvalidatedResolution) host.hasInvalidatedResolutions = () => sys.fileExists(`${ts.tscWatch.projectRoot}/other.ts`); + const watch = ts.createWatchProgram(host); + ts.tscWatch.runWatchBaseline({ + scenario: "watchApi", + subScenario, + commandLineArgs: ["--w"], + sys, + baseline, + oldSnap, + getPrograms, + changes: [ + { + caption: "write other with same contents", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/other.d.ts`, ""), + timeouts: sys => sys.runQueuedTimeoutCallbacks(), + }, + { + caption: "change other file", + change: sys => sys.appendFile(`${ts.tscWatch.projectRoot}/other.d.ts`, "export function bar(): void;"), + timeouts: sys => sys.runQueuedTimeoutCallbacks(), + }, + { + caption: "write other with same contents but write ts file", + change: sys => { + sys.appendFile(`${ts.tscWatch.projectRoot}/other.d.ts`, ""); + sys.writeFile(`${ts.tscWatch.projectRoot}/other.ts`, "export function foo() {}"); + }, + timeouts: sys => sys.runQueuedTimeoutCallbacks(), + }, + ], + watchOrSolution: watch + }); }); + } + verifyWatch("host implements does not implement hasInvalidatedResolutions", /*implementHasInvalidatedResolution*/ false); + verifyWatch("host implements hasInvalidatedResolutions", /*implementHasInvalidatedResolution*/ true); + }); +}); + +describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to watch status reporter", () => { + it("verify that the error count is correctly passed down to the watch status reporter", () => { + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { module: "commonjs" }, + files: ["index.ts"] + }) + }; + const mainFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/index.ts`, + content: "let compiler = new Compiler(); for (let i = 0; j < 5; i++) {}" + }; + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem( + [ts.tscWatch.libFile, mainFile, config], + { currentDirectory: ts.tscWatch.projectRoot }), + ); + const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({ + configFileName: config.path, + system: sys, + cb, + }); + const existing = host.onWatchStatusChange!; + let watchedErrorCount; + host.onWatchStatusChange = (diagnostic, newLine, options, errorCount) => { + existing.call(host, diagnostic, newLine, options, errorCount); + watchedErrorCount = errorCount; + }; + const watch = ts.createWatchProgram(host); + assert.equal(watchedErrorCount, 2, "The error count was expected to be 2 for the file change"); + ts.tscWatch.runWatchBaseline({ + scenario: "watchApi", + subScenario: "verify that the error count is correctly passed down to the watch status reporter", + commandLineArgs: ["--w", "--p", config.path], + sys, + baseline, + oldSnap, + getPrograms, + changes: ts.emptyArray, + watchOrSolution: watch }); }); - - describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement setTimeout or clearTimeout", () => { - it("verifies that getProgram gets updated program if new file is added to the program", () => { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - const mainFile: File = { - path: `${projectRoot}/main.ts`, - content: "const x = 10;" - }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([config, mainFile, libFile])); - const host = createWatchCompilerHostOfConfigFileForBaseline({ - configFileName: config.path, - system: sys, - cb, - }); - host.setTimeout = undefined; - host.clearTimeout = undefined; - const watch = createWatchProgram(host); - runWatchBaseline({ - scenario: "watchApi", - subScenario: "without timesouts on host program gets updated", - commandLineArgs: ["--w", "--p", config.path], - sys, - baseline, - oldSnap, - getPrograms, - changes: [{ - caption: "Write a file", - change: sys => sys.writeFile(`${projectRoot}/bar.ts`, "const y =10;"), - timeouts: sys => { - sys.checkTimeoutQueueLength(0); - watch.getProgram(); - } - }], - watchOrSolution: watch - }); +}); + +describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement setTimeout or clearTimeout", () => { + it("verifies that getProgram gets updated program if new file is added to the program", () => { + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const mainFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/main.ts`, + content: "const x = 10;" + }; + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([config, mainFile, ts.tscWatch.libFile])); + const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({ + configFileName: config.path, + system: sys, + cb, + }); + host.setTimeout = undefined; + host.clearTimeout = undefined; + const watch = ts.createWatchProgram(host); + ts.tscWatch.runWatchBaseline({ + scenario: "watchApi", + subScenario: "without timesouts on host program gets updated", + commandLineArgs: ["--w", "--p", config.path], + sys, + baseline, + oldSnap, + getPrograms, + changes: [{ + caption: "Write a file", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/bar.ts`, "const y =10;"), + timeouts: sys => { + sys.checkTimeoutQueueLength(0); + watch.getProgram(); + } + }], + watchOrSolution: watch }); }); - - describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExtensions to process", () => { - it("verifies that extraFileExtensions are supported to get the program with other extensions", () => { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - const mainFile: File = { - path: `${projectRoot}/main.ts`, - content: "const x = 10;" - }; - const otherFile: File = { - path: `${projectRoot}/other.vue`, - content: "" - }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline( - createWatchedSystem([config, mainFile, otherFile, libFile]) - ); - const host = createWatchCompilerHostOfConfigFileForBaseline({ - configFileName: config.path, - optionsToExtend: { allowNonTsExtensions: true }, - extraFileExtensions: [{ extension: ".vue", isMixedContent: true, scriptKind: ScriptKind.Deferred }], - system: sys, - cb, - }); - const watch = createWatchProgram(host); - runWatchBaseline({ - scenario: "watchApi", - subScenario: "extraFileExtensions are supported", - commandLineArgs: ["--w", "--p", config.path], - sys, - baseline, - oldSnap, - getPrograms, - changes: [{ - caption: "Write a file", - change: sys => sys.writeFile(`${projectRoot}/other2.vue`, otherFile.content), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }], - watchOrSolution: watch - }); +}); + +describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExtensions to process", () => { + it("verifies that extraFileExtensions are supported to get the program with other extensions", () => { + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const mainFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/main.ts`, + content: "const x = 10;" + }; + const otherFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/other.vue`, + content: "" + }; + const { sys, baseline, oldSnap, cb, getPrograms } = ts.tscWatch.createBaseline( + ts.tscWatch.createWatchedSystem([config, mainFile, otherFile, ts.tscWatch.libFile]) + ); + const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({ + configFileName: config.path, + optionsToExtend: { allowNonTsExtensions: true }, + extraFileExtensions: [{ extension: ".vue", isMixedContent: true, scriptKind: ts.ScriptKind.Deferred }], + system: sys, + cb, + }); + const watch = ts.createWatchProgram(host); + ts.tscWatch.runWatchBaseline({ + scenario: "watchApi", + subScenario: "extraFileExtensions are supported", + commandLineArgs: ["--w", "--p", config.path], + sys, + baseline, + oldSnap, + getPrograms, + changes: [{ + caption: "Write a file", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/other2.vue`, otherFile.content), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }], + watchOrSolution: watch }); }); +}); + +describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticDiagnosticsBuilderProgram", () => { + function createSystem(configText: string, mainText: string) { + const config: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: configText + }; + const mainFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/main.ts`, + content: mainText + }; + const otherFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/other.ts`, + content: "export const y = 10;" + }; + return { + ...ts.tscWatch.createBaseline(ts.tscWatch.createWatchedSystem([config, mainFile, otherFile, ts.tscWatch.libFile])), + config, + mainFile, + otherFile, + }; + } + + function createWatch( + baseline: string[], + config: ts.tscWatch.File, + sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles, + createProgram: ts.CreateProgram, + optionsToExtend?: ts.CompilerOptions, + ) { + const { cb, getPrograms } = ts.commandLineCallbacks(sys); + baseline.push(`tsc --w${optionsToExtend?.noEmit ? " --noEmit" : ""}`); + const oldSnap = sys.snap(); + const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({ + configFileName: config.path, + optionsToExtend, + createProgram, + system: sys, + cb, + }); + const watch = ts.createWatchProgram(host); + ts.tscWatch.watchBaseline({ + baseline, + getPrograms, + oldPrograms: ts.emptyArray, + sys, + oldSnap, + }); + watch.close(); + } - describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticDiagnosticsBuilderProgram", () => { - function createSystem(configText: string, mainText: string) { - const config: File = { - path: `${projectRoot}/tsconfig.json`, - content: configText - }; - const mainFile: File = { - path: `${projectRoot}/main.ts`, - content: mainText - }; - const otherFile: File = { - path: `${projectRoot}/other.ts`, - content: "export const y = 10;" - }; - return { - ...createBaseline(createWatchedSystem([config, mainFile, otherFile, libFile])), - config, - mainFile, - otherFile, - }; - } - - function createWatch( - baseline: string[], - config: File, - sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles, - createProgram: CreateProgram, - optionsToExtend?: CompilerOptions, - ) { - const { cb, getPrograms } = commandLineCallbacks(sys); - baseline.push(`tsc --w${optionsToExtend?.noEmit ? " --noEmit" : ""}`); - const oldSnap = sys.snap(); - const host = createWatchCompilerHostOfConfigFileForBaseline({ - configFileName: config.path, - optionsToExtend, - createProgram, - system: sys, - cb, - }); - const watch = createWatchProgram(host); - watchBaseline({ - baseline, - getPrograms, - oldPrograms: emptyArray, - sys, - oldSnap, - }); - watch.close(); - } - - function verifyOutputs(baseline: string[], sys: System, emitSys: System) { - baseline.push("Checking if output is same as EmitAndSemanticDiagnosticsBuilderProgram::"); - for (const output of [`${projectRoot}/main.js`, `${projectRoot}/main.d.ts`, `${projectRoot}/other.js`, `${projectRoot}/other.d.ts`, `${projectRoot}/tsconfig.tsbuildinfo`]) { - baseline.push(`Output file text for ${output} is same:: ${sys.readFile(output) === emitSys.readFile(output)}`); - } - baseline.push(""); - } - - function createSystemForBuilderTest(configText: string, mainText: string) { - const result = createSystem(configText, mainText); - const { sys: emitSys, baseline: emitBaseline } = createSystem(configText, mainText); - return { ...result, emitSys, emitBaseline }; - } - - function applyChangeForBuilderTest( - baseline: string[], - emitBaseline: string[], - sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles, - emitSys: TestFSWithWatch.TestServerHostTrackingWrittenFiles, - change: (sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles) => void, - caption: string - ) { - // Change file - applyChange(sys, baseline, change, caption); - applyChange(emitSys, emitBaseline, change, caption); - } - - function verifyBuilder( - baseline: string[], - emitBaseline: string[], - config: File, - sys: TestFSWithWatch.TestServerHostTrackingWrittenFiles, - emitSys: TestFSWithWatch.TestServerHostTrackingWrittenFiles, - createProgram: CreateProgram, - optionsToExtend?: CompilerOptions) { - createWatch(baseline, config, sys, createProgram, optionsToExtend); - createWatch(emitBaseline, config, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, optionsToExtend); - verifyOutputs(baseline, sys, emitSys); + function verifyOutputs(baseline: string[], sys: ts.System, emitSys: ts.System) { + baseline.push("Checking if output is same as EmitAndSemanticDiagnosticsBuilderProgram::"); + for (const output of [`${ts.tscWatch.projectRoot}/main.js`, `${ts.tscWatch.projectRoot}/main.d.ts`, `${ts.tscWatch.projectRoot}/other.js`, `${ts.tscWatch.projectRoot}/other.d.ts`, `${ts.tscWatch.projectRoot}/tsconfig.tsbuildinfo`]) { + baseline.push(`Output file text for ${output} is same:: ${sys.readFile(output) === emitSys.readFile(output)}`); } - - it("verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram and typechecking happens only on affected files", () => { - const { sys, baseline, oldSnap, cb, getPrograms, config, mainFile } = createSystem("{}", "export const x = 10;"); - const host = createWatchCompilerHostOfConfigFileForBaseline({ - configFileName: config.path, - optionsToExtend: { noEmit: true }, - createProgram: createSemanticDiagnosticsBuilderProgram, - system: sys, - cb, - }); - const watch = createWatchProgram(host); - runWatchBaseline({ - scenario: "watchApi", - subScenario: "verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram", - commandLineArgs: ["--w", "--p", config.path], - sys, - baseline, - oldSnap, - getPrograms, - changes: [{ - caption: "Modify a file", - change: sys => sys.appendFile(mainFile.path, "\n// SomeComment"), - timeouts: runQueuedTimeoutCallbacks, - }], - watchOrSolution: watch - }); + baseline.push(""); + } + + function createSystemForBuilderTest(configText: string, mainText: string) { + const result = createSystem(configText, mainText); + const { sys: emitSys, baseline: emitBaseline } = createSystem(configText, mainText); + return { ...result, emitSys, emitBaseline }; + } + + function applyChangeForBuilderTest( + baseline: string[], + emitBaseline: string[], + sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles, + emitSys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles, + change: (sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles) => void, + caption: string + ) { + // Change file + ts.tscWatch.applyChange(sys, baseline, change, caption); + ts.tscWatch.applyChange(emitSys, emitBaseline, change, caption); + } + + function verifyBuilder( + baseline: string[], + emitBaseline: string[], + config: ts.tscWatch.File, + sys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles, + emitSys: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles, + createProgram: ts.CreateProgram, + optionsToExtend?: ts.CompilerOptions) { + createWatch(baseline, config, sys, createProgram, optionsToExtend); + createWatch(emitBaseline, config, emitSys, ts.createEmitAndSemanticDiagnosticsBuilderProgram, optionsToExtend); + verifyOutputs(baseline, sys, emitSys); + } + + it("verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram and typechecking happens only on affected files", () => { + const { sys, baseline, oldSnap, cb, getPrograms, config, mainFile } = createSystem("{}", "export const x = 10;"); + const host = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({ + configFileName: config.path, + optionsToExtend: { noEmit: true }, + createProgram: ts.createSemanticDiagnosticsBuilderProgram, + system: sys, + cb, + }); + const watch = ts.createWatchProgram(host); + ts.tscWatch.runWatchBaseline({ + scenario: "watchApi", + subScenario: "verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram", + commandLineArgs: ["--w", "--p", config.path], + sys, + baseline, + oldSnap, + getPrograms, + changes: [{ + caption: "Modify a file", + change: sys => sys.appendFile(mainFile.path, "\n// SomeComment"), + timeouts: ts.tscWatch.runQueuedTimeoutCallbacks, + }], + watchOrSolution: watch }); + }); - describe("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => { - let baseline: string[]; - let emitBaseline: string[]; - before(() => { - const configText = JSON.stringify({ compilerOptions: { composite: true } }); - const mainText = "export const x = 10;"; - const result = createSystemForBuilderTest(configText, mainText); - baseline = result.baseline; - emitBaseline = result.emitBaseline; - const { sys, config, mainFile, emitSys } = result; + describe("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => { + let baseline: string[]; + let emitBaseline: string[]; + before(() => { + const configText = JSON.stringify({ compilerOptions: { composite: true } }); + const mainText = "export const x = 10;"; + const result = createSystemForBuilderTest(configText, mainText); + baseline = result.baseline; + emitBaseline = result.emitBaseline; + const { sys, config, mainFile, emitSys } = result; - // No Emit - verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram, { noEmit: true }); + // No Emit + verifyBuilder(baseline, emitBaseline, config, sys, emitSys, ts.createEmitAndSemanticDiagnosticsBuilderProgram, { noEmit: true }); - // Emit on both sys should result in same output - verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram); + // Emit on both sys should result in same output + verifyBuilder(baseline, emitBaseline, config, sys, emitSys, ts.createEmitAndSemanticDiagnosticsBuilderProgram); - // Change file - applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.appendFile(mainFile.path, "\n// SomeComment"), "Add comment"); + // Change file + applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.appendFile(mainFile.path, "\n// SomeComment"), "Add comment"); - // Verify noEmit results in same output - verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createSemanticDiagnosticsBuilderProgram, { noEmit: true }); + // Verify noEmit results in same output + verifyBuilder(baseline, emitBaseline, config, sys, emitSys, ts.createSemanticDiagnosticsBuilderProgram, { noEmit: true }); - // Emit on both sys should result in same output - verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createEmitAndSemanticDiagnosticsBuilderProgram); + // Emit on both sys should result in same output + verifyBuilder(baseline, emitBaseline, config, sys, emitSys, ts.createEmitAndSemanticDiagnosticsBuilderProgram); - // Change file - applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.appendFile(mainFile.path, "\n// SomeComment"), "Add comment"); + // Change file + applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.appendFile(mainFile.path, "\n// SomeComment"), "Add comment"); - // Emit on both the builders should result in same files - verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createSemanticDiagnosticsBuilderProgram); - }); - after(() => { - baseline = undefined!; - emitBaseline = undefined!; - }); - it("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => { - Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmit-with-composite-with-semantic-builder.js`, baseline.join("\r\n")); - }); - it("baseline in createEmitAndSemanticDiagnosticsBuilderProgram:: noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => { - Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmit-with-composite-with-emit-builder.js`, emitBaseline.join("\r\n")); - }); + // Emit on both the builders should result in same files + verifyBuilder(baseline, emitBaseline, config, sys, emitSys, ts.createSemanticDiagnosticsBuilderProgram); + }); + after(() => { + baseline = undefined!; + emitBaseline = undefined!; + }); + it("noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => { + Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmit-with-composite-with-semantic-builder.js`, baseline.join("\r\n")); }); + it("baseline in createEmitAndSemanticDiagnosticsBuilderProgram:: noEmit with composite writes the tsbuildinfo with pending affected files correctly", () => { + Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmit-with-composite-with-emit-builder.js`, emitBaseline.join("\r\n")); + }); + }); - describe("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => { - let baseline: string[]; - let emitBaseline: string[]; - before(() => { - const configText = JSON.stringify({ compilerOptions: { composite: true, noEmitOnError: true } }); - const mainText = "export const x: string = 10;"; - const result = createSystemForBuilderTest(configText, mainText); - baseline = result.baseline; - emitBaseline = result.emitBaseline; - const { sys, config, mainFile, emitSys } = result; + describe("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => { + let baseline: string[]; + let emitBaseline: string[]; + before(() => { + const configText = JSON.stringify({ compilerOptions: { composite: true, noEmitOnError: true } }); + const mainText = "export const x: string = 10;"; + const result = createSystemForBuilderTest(configText, mainText); + baseline = result.baseline; + emitBaseline = result.emitBaseline; + const { sys, config, mainFile, emitSys } = result; - // Verify noEmit results in same output - verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createSemanticDiagnosticsBuilderProgram); + // Verify noEmit results in same output + verifyBuilder(baseline, emitBaseline, config, sys, emitSys, ts.createSemanticDiagnosticsBuilderProgram); - // Change file - applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.appendFile(mainFile.path, "\n// SomeComment"), "Add comment"); + // Change file + applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.appendFile(mainFile.path, "\n// SomeComment"), "Add comment"); - // Verify noEmit results in same output - verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createSemanticDiagnosticsBuilderProgram); + // Verify noEmit results in same output + verifyBuilder(baseline, emitBaseline, config, sys, emitSys, ts.createSemanticDiagnosticsBuilderProgram); - // Fix error - const fixed = "export const x = 10;"; - applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.writeFile(mainFile.path, fixed), "Fix error"); + // Fix error + const fixed = "export const x = 10;"; + applyChangeForBuilderTest(baseline, emitBaseline, sys, emitSys, sys => sys.writeFile(mainFile.path, fixed), "Fix error"); - // Emit on both the builders should result in same files - verifyBuilder(baseline, emitBaseline, config, sys, emitSys, createSemanticDiagnosticsBuilderProgram); - }); + // Emit on both the builders should result in same files + verifyBuilder(baseline, emitBaseline, config, sys, emitSys, ts.createSemanticDiagnosticsBuilderProgram); + }); - it("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => { - Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmitOnError-with-composite-with-semantic-builder.js`, baseline.join("\r\n")); - }); - it("baseline in createEmitAndSemanticDiagnosticsBuilderProgram:: noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => { - Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmitOnError-with-composite-with-emit-builder.js`, emitBaseline.join("\r\n")); - }); + it("noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => { + Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmitOnError-with-composite-with-semantic-builder.js`, baseline.join("\r\n")); + }); + it("baseline in createEmitAndSemanticDiagnosticsBuilderProgram:: noEmitOnError with composite writes the tsbuildinfo with pending affected files correctly", () => { + Harness.Baseline.runBaseline(`tscWatch/watchApi/noEmitOnError-with-composite-with-emit-builder.js`, emitBaseline.join("\r\n")); }); + }); - it("SemanticDiagnosticsBuilderProgram emitDtsOnly does not update affected files pending emit", () => { - // Initial - const { sys, baseline, config, mainFile } = createSystem(JSON.stringify({ compilerOptions: { composite: true, noEmitOnError: true } }), "export const x: string = 10;"); - createWatch(baseline, config, sys, createSemanticDiagnosticsBuilderProgram); - - // Fix error and emit - applyChange(sys, baseline, sys => sys.writeFile(mainFile.path, "export const x = 10;"), "Fix error"); - - const { cb, getPrograms } = commandLineCallbacks(sys); - const oldSnap = sys.snap(); - const reportDiagnostic = createDiagnosticReporter(sys, /*pretty*/ true); - const reportWatchStatus = createWatchStatusReporter(sys, /*pretty*/ true); - const host = createWatchCompilerHostOfConfigFile({ - configFileName: config.path, - createProgram: createSemanticDiagnosticsBuilderProgram, - system: sys, - reportDiagnostic, - reportWatchStatus, - }); - host.afterProgramCreate = program => { - const diagnostics = sortAndDeduplicateDiagnostics(program.getSemanticDiagnostics()); - diagnostics.forEach(reportDiagnostic); - // Buildinfo should still have affectedFilesPendingEmit since we are only emitting dts files - program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDts*/ true); - reportWatchStatus( - createCompilerDiagnostic(getWatchErrorSummaryDiagnosticMessage(diagnostics.length), diagnostics.length), - sys.newLine, - program.getCompilerOptions(), - diagnostics.length - ); - cb(program); - }; - createWatchProgram(host); - watchBaseline({ - baseline, - getPrograms, - oldPrograms: emptyArray, - sys, - oldSnap, + it("SemanticDiagnosticsBuilderProgram emitDtsOnly does not update affected files pending emit", () => { + // Initial + const { sys, baseline, config, mainFile } = createSystem(JSON.stringify({ compilerOptions: { composite: true, noEmitOnError: true } }), "export const x: string = 10;"); + createWatch(baseline, config, sys, ts.createSemanticDiagnosticsBuilderProgram); + + // Fix error and emit + ts.tscWatch.applyChange(sys, baseline, sys => sys.writeFile(mainFile.path, "export const x = 10;"), "Fix error"); + + const { cb, getPrograms } = ts.commandLineCallbacks(sys); + const oldSnap = sys.snap(); + const reportDiagnostic = ts.createDiagnosticReporter(sys, /*pretty*/ true); + const reportWatchStatus = ts.createWatchStatusReporter(sys, /*pretty*/ true); + const host = ts.createWatchCompilerHostOfConfigFile({ + configFileName: config.path, + createProgram: ts.createSemanticDiagnosticsBuilderProgram, + system: sys, + reportDiagnostic, + reportWatchStatus, + }); + host.afterProgramCreate = program => { + const diagnostics = ts.sortAndDeduplicateDiagnostics(program.getSemanticDiagnostics()); + diagnostics.forEach(reportDiagnostic); + // Buildinfo should still have affectedFilesPendingEmit since we are only emitting dts files + program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDts*/ true); + reportWatchStatus( + ts.createCompilerDiagnostic(ts.getWatchErrorSummaryDiagnosticMessage(diagnostics.length), diagnostics.length), + sys.newLine, + program.getCompilerOptions(), + diagnostics.length + ); + cb(program); + }; + ts.createWatchProgram(host); + ts.tscWatch.watchBaseline({ + baseline, + getPrograms, + oldPrograms: ts.emptyArray, + sys, + oldSnap, + }); + Harness.Baseline.runBaseline(`tscWatch/watchApi/semantic-builder-emitOnlyDts.js`, baseline.join("\r\n")); + }); +}); + +describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implemented", () => { + function setup(useSourceOfProjectReferenceRedirect?: () => boolean) { + const config1: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true + }, + exclude: ["temp"] + }) + }; + const class1: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/class1.ts`, + content: `class class1 {}` + }; + const class1Dts: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/class1.d.ts`, + content: `declare class class1 {}` + }; + const config2: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true + }, + references: [ + { path: "../project1" } + ] + }) + }; + const class2: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/projects/project2/class2.ts`, + content: `class class2 {}` + }; + const system = ts.tscWatch.createWatchedSystem([config1, class1, class1Dts, config2, class2, ts.tscWatch.libFile]); + const baseline = ts.tscWatch.createBaseline(system); + const compilerHost = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({ + cb: baseline.cb, + system, + configFileName: config2.path, + optionsToExtend: { extendedDiagnostics: true } + }); + compilerHost.useSourceOfProjectReferenceRedirect = useSourceOfProjectReferenceRedirect; + const calledGetParsedCommandLine = new ts.Set(); + compilerHost.getParsedCommandLine = fileName => { + assert.isFalse(calledGetParsedCommandLine.has(fileName), `Already called on ${fileName}`); + calledGetParsedCommandLine.add(fileName); + return ts.getParsedCommandLineOfConfigFile(fileName, /*optionsToExtend*/ undefined, { + useCaseSensitiveFileNames: true, + fileExists: path => system.fileExists(path), + readFile: path => system.readFile(path), + getCurrentDirectory: () => system.getCurrentDirectory(), + readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), + onUnRecoverableConfigFileDiagnostic: ts.noop, }); - Harness.Baseline.runBaseline(`tscWatch/watchApi/semantic-builder-emitOnlyDts.js`, baseline.join("\r\n")); + }; + const watch = ts.createWatchProgram(compilerHost); + return { watch, baseline, config2, calledGetParsedCommandLine }; + } + + it("when new file is added to the referenced project with host implementing getParsedCommandLine", () => { + const { watch, baseline, config2, calledGetParsedCommandLine } = setup(ts.returnTrue); + ts.tscWatch.runWatchBaseline({ + scenario: "watchApi", + subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine", + commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"], + ...baseline, + changes: [ + { + caption: "Add class3 to project1", + change: sys => { + calledGetParsedCommandLine.clear(); + sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.ts`, `class class3 {}`); + }, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add excluded file to project1", + change: sys => sys.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), + timeouts: sys => sys.checkTimeoutQueueLength(0), + }, + { + caption: "Add output of class3", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), + timeouts: sys => sys.checkTimeoutQueueLength(0), + }, + ], + watchOrSolution: watch }); }); - describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implemented", () => { - function setup(useSourceOfProjectReferenceRedirect?: () => boolean) { - const config1: File = { - path: `${projectRoot}/projects/project1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - exclude: ["temp"] - }) - }; - const class1: File = { - path: `${projectRoot}/projects/project1/class1.ts`, - content: `class class1 {}` - }; - const class1Dts: File = { - path: `${projectRoot}/projects/project1/class1.d.ts`, - content: `declare class class1 {}` - }; - const config2: File = { - path: `${projectRoot}/projects/project2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true + it("when new file is added to the referenced project with host implementing getParsedCommandLine without implementing useSourceOfProjectReferenceRedirect", () => { + const { watch, baseline, config2, calledGetParsedCommandLine } = setup(); + ts.tscWatch.runWatchBaseline({ + scenario: "watchApi", + subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine without implementing useSourceOfProjectReferenceRedirect", + commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"], + ...baseline, + changes: [ + { + caption: "Add class3 to project1", + change: sys => { + calledGetParsedCommandLine.clear(); + sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.ts`, `class class3 {}`); }, - references: [ - { path: "../project1" } - ] - }) - }; - const class2: File = { - path: `${projectRoot}/projects/project2/class2.ts`, - content: `class class2 {}` - }; - const system = createWatchedSystem([config1, class1, class1Dts, config2, class2, libFile]); - const baseline = createBaseline(system); - const compilerHost = createWatchCompilerHostOfConfigFileForBaseline({ + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add class3 output to project1", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add excluded file to project1", + change: sys => sys.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), + timeouts: sys => sys.checkTimeoutQueueLength(0), + }, + { + caption: "Delete output of class3", + change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Add output of class3", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + ], + watchOrSolution: watch + }); + }); +}); + +describe("unittests:: tsc-watch:: watchAPI:: when builder emit occurs with emitOnlyDtsFiles", () => { + function verify(subScenario: string, outFile?: string) { + it(subScenario, () => { + const system = ts.tscWatch.createWatchedSystem({ + [`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({ + compilerOptions: { composite: true, noEmitOnError: true, module: "amd", outFile }, + files: ["a.ts", "b.ts"], + }), + [`${ts.tscWatch.projectRoot}/a.ts`]: "export const x = 10;", + [`${ts.tscWatch.projectRoot}/b.ts`]: "export const y: 10 = 20;", + [ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content, + }, { currentDirectory: ts.tscWatch.projectRoot }); + const baseline = ts.tscWatch.createBaseline(system); + const compilerHost = ts.tscWatch.createWatchCompilerHostOfConfigFileForBaseline({ cb: baseline.cb, system, - configFileName: config2.path, + configFileName: `${ts.tscWatch.projectRoot}/tsconfig.json`, optionsToExtend: { extendedDiagnostics: true } }); - compilerHost.useSourceOfProjectReferenceRedirect = useSourceOfProjectReferenceRedirect; - const calledGetParsedCommandLine = new Set(); - compilerHost.getParsedCommandLine = fileName => { - assert.isFalse(calledGetParsedCommandLine.has(fileName), `Already called on ${fileName}`); - calledGetParsedCommandLine.add(fileName); - return getParsedCommandLineOfConfigFile(fileName, /*optionsToExtend*/ undefined, { - useCaseSensitiveFileNames: true, - fileExists: path => system.fileExists(path), - readFile: path => system.readFile(path), - getCurrentDirectory: () => system.getCurrentDirectory(), - readDirectory: (path, extensions, excludes, includes, depth) => system.readDirectory(path, extensions, excludes, includes, depth), - onUnRecoverableConfigFileDiagnostic: noop, - }); - }; - const watch = createWatchProgram(compilerHost); - return { watch, baseline, config2, calledGetParsedCommandLine }; - } - - it("when new file is added to the referenced project with host implementing getParsedCommandLine", () => { - const { watch, baseline, config2, calledGetParsedCommandLine } = setup(returnTrue); - runWatchBaseline({ + const originalEmitProgram = compilerHost.afterProgramCreate; + compilerHost.afterProgramCreate = myAfterProgramCreate; + let callFullEmit = true; + const watch = ts.createWatchProgram(compilerHost); + ts.tscWatch.runWatchBaseline({ scenario: "watchApi", - subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine", - commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"], + subScenario, + commandLineArgs: ["--w", "--extendedDiagnostics"], ...baseline, changes: [ { - caption: "Add class3 to project1", + caption: "Fix error but run emit with emitOnlyDts", change: sys => { - calledGetParsedCommandLine.clear(); - sys.writeFile(`${projectRoot}/projects/project1/class3.ts`, `class class3 {}`); + sys.writeFile(`${ts.tscWatch.projectRoot}/b.ts`, `export const y = 10;`); + callFullEmit = false; }, - timeouts: checkSingleTimeoutQueueLengthAndRun, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, }, { - caption: "Add excluded file to project1", - change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), + caption: "Emit with emitOnlyDts shouldnt emit anything", + change: () => { + const program = watch.getCurrentProgram(); + program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ true); + baseline.cb(program); + }, timeouts: sys => sys.checkTimeoutQueueLength(0), }, { - caption: "Add output of class3", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), + caption: "Emit all files", + change: () => { + const program = watch.getCurrentProgram(); + program.emit(); + baseline.cb(program); + }, timeouts: sys => sys.checkTimeoutQueueLength(0), }, - ], - watchOrSolution: watch - }); - }); - - it("when new file is added to the referenced project with host implementing getParsedCommandLine without implementing useSourceOfProjectReferenceRedirect", () => { - const { watch, baseline, config2, calledGetParsedCommandLine } = setup(); - runWatchBaseline({ - scenario: "watchApi", - subScenario: "when new file is added to the referenced project with host implementing getParsedCommandLine without implementing useSourceOfProjectReferenceRedirect", - commandLineArgs: ["--w", "-p", config2.path, "--extendedDiagnostics"], - ...baseline, - changes: [ { - caption: "Add class3 to project1", - change: sys => { - calledGetParsedCommandLine.clear(); - sys.writeFile(`${projectRoot}/projects/project1/class3.ts`, `class class3 {}`); + caption: "Emit with emitOnlyDts shouldnt emit anything", + change: () => { + const program = watch.getCurrentProgram(); + program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ true); + baseline.cb(program); }, - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add class3 output to project1", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add excluded file to project1", - change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), timeouts: sys => sys.checkTimeoutQueueLength(0), }, { - caption: "Delete output of class3", - change: sys => sys.deleteFile(`${projectRoot}/projects/project1/class3.d.ts`), - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Add output of class3", - change: sys => sys.writeFile(`${projectRoot}/projects/project1/class3.d.ts`, `declare class class3 {}`), - timeouts: checkSingleTimeoutQueueLengthAndRun, + caption: "Emit full should not emit anything", + change: () => { + const program = watch.getCurrentProgram(); + program.emit(); + baseline.cb(program); + }, + timeouts: sys => sys.checkTimeoutQueueLength(0), }, ], watchOrSolution: watch }); - }); - }); - describe("unittests:: tsc-watch:: watchAPI:: when builder emit occurs with emitOnlyDtsFiles", () => { - function verify(subScenario: string, outFile?: string) { - it(subScenario, () => { - const system = createWatchedSystem({ - [`${projectRoot}/tsconfig.json`]: JSON.stringify({ - compilerOptions: { composite: true, noEmitOnError: true, module: "amd", outFile }, - files: ["a.ts", "b.ts"], - }), - [`${projectRoot}/a.ts`]: "export const x = 10;", - [`${projectRoot}/b.ts`]: "export const y: 10 = 20;", - [libFile.path]: libFile.content, - }, { currentDirectory: projectRoot }); - const baseline = createBaseline(system); - const compilerHost = createWatchCompilerHostOfConfigFileForBaseline({ - cb: baseline.cb, - system, - configFileName: `${projectRoot}/tsconfig.json`, - optionsToExtend: { extendedDiagnostics: true } - }); - const originalEmitProgram = compilerHost.afterProgramCreate; - compilerHost.afterProgramCreate = myAfterProgramCreate; - let callFullEmit = true; - const watch = createWatchProgram(compilerHost); - runWatchBaseline({ - scenario: "watchApi", - subScenario, - commandLineArgs: ["--w", "--extendedDiagnostics"], - ...baseline, - changes: [ - { - caption: "Fix error but run emit with emitOnlyDts", - change: sys => { - sys.writeFile(`${projectRoot}/b.ts`, `export const y = 10;`); - callFullEmit = false; - }, - timeouts: checkSingleTimeoutQueueLengthAndRun, - }, - { - caption: "Emit with emitOnlyDts shouldnt emit anything", - change: () => { - const program = watch.getCurrentProgram(); - program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ true); - baseline.cb(program); - }, - timeouts: sys => sys.checkTimeoutQueueLength(0), - }, - { - caption: "Emit all files", - change: () => { - const program = watch.getCurrentProgram(); - program.emit(); - baseline.cb(program); - }, - timeouts: sys => sys.checkTimeoutQueueLength(0), - }, - { - caption: "Emit with emitOnlyDts shouldnt emit anything", - change: () => { - const program = watch.getCurrentProgram(); - program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ true); - baseline.cb(program); - }, - timeouts: sys => sys.checkTimeoutQueueLength(0), - }, - { - caption: "Emit full should not emit anything", - change: () => { - const program = watch.getCurrentProgram(); - program.emit(); - baseline.cb(program); - }, - timeouts: sys => sys.checkTimeoutQueueLength(0), - }, - ], - watchOrSolution: watch - }); - - function myAfterProgramCreate(program: EmitAndSemanticDiagnosticsBuilderProgram) { - if (callFullEmit) { - originalEmitProgram!.call(compilerHost, program); - } - else { - program.getSemanticDiagnostics(); // Get Diagnostics - program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ true); - baseline.cb(program); - } + function myAfterProgramCreate(program: ts.EmitAndSemanticDiagnosticsBuilderProgram) { + if (callFullEmit) { + originalEmitProgram!.call(compilerHost, program); } - }); - } - verify("when emitting with emitOnlyDtsFiles"); - verify("when emitting with emitOnlyDtsFiles with outFile", "outFile.js"); - }); -} + else { + program.getSemanticDiagnostics(); // Get Diagnostics + program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ true); + baseline.cb(program); + } + } + }); + } + verify("when emitting with emitOnlyDtsFiles"); + verify("when emitting with emitOnlyDtsFiles with outFile", "outFile.js"); +}); diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts index 45dd1ff714391..ddeeaa64c5cfb 100644 --- a/src/testRunner/unittests/tscWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -1,691 +1,691 @@ -namespace ts.tscWatch { - import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; - describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => { - const scenario = "watchEnvironment"; - verifyTscWatch({ +import * as ts from "../../_namespaces/ts"; + +import Tsc_WatchDirectory = ts.TestFSWithWatch.Tsc_WatchDirectory; +describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => { + const scenario = "watchEnvironment"; + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "watchFile/using dynamic priority polling", + commandLineArgs: ["--w", `/a/username/project/typescript.ts`], + sys: () => { + const projectFolder = "/a/username/project"; + const file1: ts.tscWatch.File = { + path: `${projectFolder}/typescript.ts`, + content: "var z = 10;" + }; + const environmentVariables = new ts.Map(); + environmentVariables.set("TSC_WATCHFILE", ts.TestFSWithWatch.Tsc_WatchFile.DynamicPolling); + return ts.tscWatch.createWatchedSystem([file1, ts.tscWatch.libFile], { environmentVariables }); + }, + changes: [ + { + caption: "Time spent to Transition libFile and file1 to low priority queue", + change: ts.noop, + timeouts: (sys, programs) => { + const initialProgram = programs[0][0]; + const mediumPollingIntervalThreshold = ts.unchangedPollThresholds[ts.PollingInterval.Medium]; + for (let index = 0; index < mediumPollingIntervalThreshold; index++) { + // Transition libFile and file1 to low priority queue + sys.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(programs[0][0], initialProgram); + } + return; + }, + }, + { + caption: "Make change to file", + // Make a change to file + change: sys => sys.writeFile("/a/username/project/typescript.ts", "var zz30 = 100;"), + // During this timeout the file would be detected as unchanged + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Callbacks: medium priority + high priority queue and scheduled program update", + change: ts.noop, + // Callbacks: medium priority + high priority queue and scheduled program update + // This should detect change in the file + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(3), + }, + { + caption: "Polling queues polled and everything is in the high polling queue", + change: ts.noop, + timeouts: (sys, programs) => { + const initialProgram = programs[0][0]; + const mediumPollingIntervalThreshold = ts.unchangedPollThresholds[ts.PollingInterval.Medium]; + const newThreshold = ts.unchangedPollThresholds[ts.PollingInterval.Low] + mediumPollingIntervalThreshold; + for (let fileUnchangeDetected = 1; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) { + // For high + Medium/low polling interval + sys.checkTimeoutQueueLengthAndRun(2); + assert.deepEqual(programs[0][0], initialProgram); + } + + // Everything goes in high polling interval queue + sys.checkTimeoutQueueLengthAndRun(1); + return; + }, + } + ] + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "watchFile/using fixed chunk size polling", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const configFile: ts.tscWatch.File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + watchOptions: { + watchFile: "FixedChunkSizePolling" + } + }) + }; + const files = [ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile]; + return ts.tscWatch.createWatchedSystem(files); + }, + changes: [ + { + caption: "The timeout is to check the status of all files", + change: ts.noop, + timeouts: (sys, programs) => { + // On each timeout file does not change + const initialProgram = programs[0][0]; + for (let index = 0; index < 4; index++) { + sys.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(programs[0][0], initialProgram); + } + }, + }, + { + caption: "Make change to file but should detect as changed and schedule program update", + // Make a change to file + change: sys => sys.writeFile(ts.tscWatch.commonFile1.path, "var zz30 = 100;"), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, + }, + { + caption: "Callbacks: queue and scheduled program update", + change: ts.noop, + // Callbacks: scheduled program update and queue for the polling + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), + }, + { + caption: "The timeout is to check the status of all files", + change: ts.noop, + timeouts: (sys, programs) => { + // On each timeout file does not change + const initialProgram = programs[0][0]; + sys.checkTimeoutQueueLengthAndRun(1); + assert.deepEqual(programs[0][0], initialProgram); + }, + }, + ] + }); + + describe("tsc-watch when watchDirectories implementation", () => { + function verifyRenamingFileInSubFolder(subScenario: string, tscWatchDirectory: Tsc_WatchDirectory) { + const projectFolder = "/a/username/project"; + const projectSrcFolder = `${projectFolder}/src`; + const configFile: ts.tscWatch.File = { + path: `${projectFolder}/tsconfig.json`, + content: JSON.stringify({ + watchOptions: { + synchronousWatchDirectory: true + } + }) + }; + const file: ts.tscWatch.File = { + path: `${projectSrcFolder}/file1.ts`, + content: "" + }; + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `watchDirectories/${subScenario}`, + commandLineArgs: ["--w", "-p", configFile.path], + sys: () => { + const files = [file, configFile, ts.tscWatch.libFile]; + const environmentVariables = new ts.Map(); + environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); + return ts.tscWatch.createWatchedSystem(files, { environmentVariables }); + }, + changes: [ + { + caption: "Rename file1 to file2", + // Rename the file: + change: sys => sys.renameFile(file.path, file.path.replace("file1.ts", "file2.ts")), + timeouts: sys => { + if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { + // With dynamic polling the fs change would be detected only by running timeouts + sys.runQueuedTimeoutCallbacks(); + } + // Delayed update program + sys.runQueuedTimeoutCallbacks(); + return; + }, + }, + ], + }); + } + + verifyRenamingFileInSubFolder("uses watchFile when renaming file in subfolder", Tsc_WatchDirectory.WatchFile); + + verifyRenamingFileInSubFolder("uses non recursive watchDirectory when renaming file in subfolder", Tsc_WatchDirectory.NonRecursiveWatchDirectory); + + verifyRenamingFileInSubFolder("uses non recursive dynamic polling when renaming file in subfolder", Tsc_WatchDirectory.DynamicPolling); + + ts.tscWatch.verifyTscWatch({ scenario, - subScenario: "watchFile/using dynamic priority polling", - commandLineArgs: ["--w", `/a/username/project/typescript.ts`], + subScenario: "watchDirectories/when there are symlinks to folders in recursive folders", + commandLineArgs: ["--w"], sys: () => { - const projectFolder = "/a/username/project"; - const file1: File = { - path: `${projectFolder}/typescript.ts`, - content: "var z = 10;" + const cwd = "/home/user/projects/myproject"; + const file1: ts.tscWatch.File = { + path: `${cwd}/src/file.ts`, + content: `import * as a from "a"` + }; + const tsconfig: ts.tscWatch.File = { + path: `${cwd}/tsconfig.json`, + content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` + }; + const realA: ts.tscWatch.File = { + path: `${cwd}/node_modules/reala/index.d.ts`, + content: `export {}` + }; + const realB: ts.tscWatch.File = { + path: `${cwd}/node_modules/realb/index.d.ts`, + content: `export {}` + }; + const symLinkA: ts.tscWatch.SymLink = { + path: `${cwd}/node_modules/a`, + symLink: `${cwd}/node_modules/reala` }; - const environmentVariables = new Map(); - environmentVariables.set("TSC_WATCHFILE", TestFSWithWatch.Tsc_WatchFile.DynamicPolling); - return createWatchedSystem([file1, libFile], { environmentVariables }); + const symLinkB: ts.tscWatch.SymLink = { + path: `${cwd}/node_modules/b`, + symLink: `${cwd}/node_modules/realb` + }; + const symLinkBInA: ts.tscWatch.SymLink = { + path: `${cwd}/node_modules/reala/node_modules/b`, + symLink: `${cwd}/node_modules/b` + }; + const symLinkAInB: ts.tscWatch.SymLink = { + path: `${cwd}/node_modules/realb/node_modules/a`, + symLink: `${cwd}/node_modules/a` + }; + const files = [ts.tscWatch.libFile, file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; + const environmentVariables = new ts.Map(); + environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); + return ts.tscWatch.createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); + }, + changes: ts.emptyArray + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "watchDirectories/with non synchronous watch directory", + commandLineArgs: ["--w", "-p", `${ts.tscWatch.projectRoot}/tsconfig.json`], + sys: () => { + const configFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const file1: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/src/file1.ts`, + content: `import { x } from "file2";` + }; + const file2: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/file2/index.d.ts`, + content: `export const x = 10;` + }; + const files = [ts.tscWatch.libFile, file1, file2, configFile]; + return ts.tscWatch.createWatchedSystem(files, { runWithoutRecursiveWatches: true }); }, changes: [ { - caption: "Time spent to Transition libFile and file1 to low priority queue", - change: noop, - timeouts: (sys, programs) => { - const initialProgram = programs[0][0]; - const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; - for (let index = 0; index < mediumPollingIntervalThreshold; index++) { - // Transition libFile and file1 to low priority queue - sys.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(programs[0][0], initialProgram); - } - return; + caption: "Directory watch updates because of file1.js creation", + change: ts.noop, + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for file1.js output + sys.checkTimeoutQueueLength(0); }, }, { - caption: "Make change to file", - // Make a change to file - change: sys => sys.writeFile("/a/username/project/typescript.ts", "var zz30 = 100;"), - // During this timeout the file would be detected as unchanged - timeouts: checkSingleTimeoutQueueLengthAndRun, + caption: "Remove directory node_modules", + // Remove directory node_modules + change: sys => sys.deleteFolder(`${ts.tscWatch.projectRoot}/node_modules`, /*recursive*/ true), + timeouts: sys => { + sys.checkTimeoutQueueLength(3); // 1. Failed lookup invalidation 2. For updating program and 3. for updating child watches + sys.runQueuedTimeoutCallbacks(sys.getNextTimeoutId() - 2); // Update program + }, }, { - caption: "Callbacks: medium priority + high priority queue and scheduled program update", - change: noop, - // Callbacks: medium priority + high priority queue and scheduled program update - // This should detect change in the file - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(3), + caption: "Pending directory watchers and program update", + change: ts.noop, + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers + sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update + sys.checkTimeoutQueueLengthAndRun(1); // Actual program update + sys.checkTimeoutQueueLength(0); + }, }, { - caption: "Polling queues polled and everything is in the high polling queue", - change: noop, - timeouts: (sys, programs) => { - const initialProgram = programs[0][0]; - const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; - const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold; - for (let fileUnchangeDetected = 1; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) { - // For high + Medium/low polling interval - sys.checkTimeoutQueueLengthAndRun(2); - assert.deepEqual(programs[0][0], initialProgram); - } - - // Everything goes in high polling interval queue - sys.checkTimeoutQueueLengthAndRun(1); - return; + caption: "Start npm install", + // npm install + change: sys => sys.createDirectory(`${ts.tscWatch.projectRoot}/node_modules`), + timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure + }, + { + caption: "npm install folder creation of file2", + change: sys => sys.createDirectory(`${ts.tscWatch.projectRoot}/node_modules/file2`), + timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure + }, + { + caption: "npm install index file in file2", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/node_modules/file2/index.d.ts`, `export const x = 10;`), + timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure + }, + { + caption: "Updates the program", + change: ts.noop, + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); + sys.checkTimeoutQueueLength(2); // To Update program and failed lookup update }, - } - ] + }, + { + caption: "Invalidates module resolution cache", + change: ts.noop, + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); + sys.checkTimeoutQueueLength(1); // To Update program + }, + }, + { + caption: "Pending updates", + change: ts.noop, + timeouts: sys => { + sys.runQueuedTimeoutCallbacks(); + sys.checkTimeoutQueueLength(0); + }, + }, + ], }); - verifyTscWatch({ + ts.tscWatch.verifyTscWatch({ scenario, - subScenario: "watchFile/using fixed chunk size polling", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + subScenario: "watchDirectories/with non synchronous watch directory with outDir and declaration enabled", + commandLineArgs: ["--w", "-p", `${ts.tscWatch.projectRoot}/tsconfig.json`], sys: () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - watchOptions: { - watchFile: "FixedChunkSizePolling" - } - }) + const configFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { outDir: "dist", declaration: true } }) + }; + const file1: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/src/file1.ts`, + content: `import { x } from "file2";` }; - const files = [libFile, commonFile1, commonFile2, configFile]; - return createWatchedSystem(files); + const file2: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/file2/index.d.ts`, + content: `export const x = 10;` + }; + const files = [ts.tscWatch.libFile, file1, file2, configFile]; + return ts.tscWatch.createWatchedSystem(files, { runWithoutRecursiveWatches: true }); }, changes: [ + ts.tscWatch.noopChange, { - caption: "The timeout is to check the status of all files", - change: noop, - timeouts: (sys, programs) => { - // On each timeout file does not change - const initialProgram = programs[0][0]; - for (let index = 0; index < 4; index++) { - sys.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(programs[0][0], initialProgram); - } - }, + caption: "Add new file, should schedule and run timeout to update directory watcher", + change: sys => sys.writeFile(`${ts.tscWatch.projectRoot}/src/file3.ts`, `export const y = 10;`), + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Update the child watch }, { - caption: "Make change to file but should detect as changed and schedule program update", - // Make a change to file - change: sys => sys.writeFile(commonFile1.path, "var zz30 = 100;"), - timeouts: checkSingleTimeoutQueueLengthAndRun, + caption: "Actual program update to include new file", + change: ts.noop, + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), // Scheduling failed lookup update and program update }, { - caption: "Callbacks: queue and scheduled program update", - change: noop, - // Callbacks: scheduled program update and queue for the polling - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), + caption: "After program emit with new file, should schedule and run timeout to update directory watcher", + change: ts.noop, + timeouts: ts.tscWatch.checkSingleTimeoutQueueLengthAndRun, // Update the child watch + }, + ts.tscWatch.noopChange, + ], + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "watchDirectories/with non synchronous watch directory renaming a file", + commandLineArgs: ["--w", "-p", `${ts.tscWatch.projectRoot}/tsconfig.json`], + sys: () => { + const configFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { outDir: "dist" } }) + }; + const file1: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/src/file1.ts`, + content: `import { x } from "./file2";` + }; + const file2: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/src/file2.ts`, + content: `export const x = 10;` + }; + const files = [ts.tscWatch.libFile, file1, file2, configFile]; + return ts.tscWatch.createWatchedSystem(files, { runWithoutRecursiveWatches: true }); + }, + changes: [ + ts.tscWatch.noopChange, + { + caption: "rename the file", + change: sys => sys.renameFile(`${ts.tscWatch.projectRoot}/src/file2.ts`, `${ts.tscWatch.projectRoot}/src/renamed.ts`), + timeouts: sys => { + sys.checkTimeoutQueueLength(2); // 1. For updating program and 2. for updating child watches + sys.runQueuedTimeoutCallbacks(1); // Update program + }, }, { - caption: "The timeout is to check the status of all files", - change: noop, - timeouts: (sys, programs) => { - // On each timeout file does not change - const initialProgram = programs[0][0]; - sys.checkTimeoutQueueLengthAndRun(1); - assert.deepEqual(programs[0][0], initialProgram); + caption: "Pending directory watchers and program update", + change: ts.noop, + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers + sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update + sys.checkTimeoutQueueLengthAndRun(1); // Actual program update + sys.checkTimeoutQueueLength(0); }, }, - ] + ], }); + }); - describe("tsc-watch when watchDirectories implementation", () => { - function verifyRenamingFileInSubFolder(subScenario: string, tscWatchDirectory: Tsc_WatchDirectory) { - const projectFolder = "/a/username/project"; - const projectSrcFolder = `${projectFolder}/src`; - const configFile: File = { - path: `${projectFolder}/tsconfig.json`, + describe("handles watch compiler options", () => { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "watchOptions/with watchFile option", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const configFile: ts.tscWatch.File = { + path: "/a/b/tsconfig.json", content: JSON.stringify({ watchOptions: { - synchronousWatchDirectory: true + watchFile: "UseFsEvents" } }) }; - const file: File = { - path: `${projectSrcFolder}/file1.ts`, - content: "" + const files = [ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile]; + return ts.tscWatch.createWatchedSystem(files); + }, + changes: ts.emptyArray + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "watchOptions/with watchDirectory option", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const configFile: ts.tscWatch.File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + watchOptions: { + watchDirectory: "UseFsEvents" + } + }) }; - verifyTscWatch({ + const files = [ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile]; + return ts.tscWatch.createWatchedSystem(files, { runWithoutRecursiveWatches: true }); + }, + changes: ts.emptyArray + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "watchOptions/with fallbackPolling option", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], + sys: () => { + const configFile: ts.tscWatch.File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + watchOptions: { + fallbackPolling: "PriorityInterval" + } + }) + }; + const files = [ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile]; + return ts.tscWatch.createWatchedSystem(files, { runWithoutRecursiveWatches: true, runWithFallbackPolling: true }); + }, + changes: ts.emptyArray + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: "watchOptions/with watchFile as watch options to extend", + commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json", "--watchFile", "UseFsEvents"], + sys: () => { + const configFile: ts.tscWatch.File = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + const files = [ts.tscWatch.libFile, ts.tscWatch.commonFile1, ts.tscWatch.commonFile2, configFile]; + return ts.tscWatch.createWatchedSystem(files); + }, + changes: ts.emptyArray + }); + + describe("exclude options", () => { + function sys(watchOptions: ts.WatchOptions, runWithoutRecursiveWatches?: boolean): ts.tscWatch.WatchedSystem { + const configFile: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ exclude: ["node_modules"], watchOptions }) + }; + const main: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/src/main.ts`, + content: `import { foo } from "bar"; foo();` + }; + const bar: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/bar/index.d.ts`, + content: `export { foo } from "./foo";` + }; + const foo: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/bar/foo.d.ts`, + content: `export function foo(): string;` + }; + const fooBar: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/bar/fooBar.d.ts`, + content: `export function fooBar(): string;` + }; + const temp: ts.tscWatch.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/bar/temp/index.d.ts`, + content: "export function temp(): string;" + }; + const files = [ts.tscWatch.libFile, main, bar, foo, fooBar, temp, configFile]; + return ts.tscWatch.createWatchedSystem(files, { currentDirectory: ts.tscWatch.projectRoot, runWithoutRecursiveWatches }); + } + + function verifyWorker(...additionalFlags: string[]) { + ts.tscWatch.verifyTscWatch({ scenario, - subScenario: `watchDirectories/${subScenario}`, - commandLineArgs: ["--w", "-p", configFile.path], - sys: () => { - const files = [file, configFile, libFile]; - const environmentVariables = new Map(); - environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); - return createWatchedSystem(files, { environmentVariables }); - }, + subScenario: `watchOptions/with excludeFiles option${additionalFlags.join("")}`, + commandLineArgs: ["-w", ...additionalFlags], + sys: () => sys({ excludeFiles: ["node_modules/*"] }), + changes: [ + { + caption: "Change foo", + change: sys => ts.tscWatch.replaceFileText(sys, `${ts.tscWatch.projectRoot}/node_modules/bar/foo.d.ts`, "foo", "fooBar"), + timeouts: sys => sys.checkTimeoutQueueLength(0), + } + ] + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `watchOptions/with excludeDirectories option${additionalFlags.join("")}`, + commandLineArgs: ["-w", ...additionalFlags], + sys: () => sys({ excludeDirectories: ["node_modules"] }), changes: [ { - caption: "Rename file1 to file2", - // Rename the file: - change: sys => sys.renameFile(file.path, file.path.replace("file1.ts", "file2.ts")), + caption: "delete fooBar", + change: sys => sys.deleteFile(`${ts.tscWatch.projectRoot}/node_modules/bar/fooBar.d.ts`), + timeouts: sys => sys.checkTimeoutQueueLength(0), } + ] + }); + + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `watchOptions/with excludeDirectories option with recursive directory watching${additionalFlags.join("")}`, + commandLineArgs: ["-w", ...additionalFlags], + sys: () => sys({ excludeDirectories: ["**/temp"] }, /*runWithoutRecursiveWatches*/ true), + changes: [ + { + caption: "Directory watch updates because of main.js creation", + change: ts.noop, timeouts: sys => { - if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { - // With dynamic polling the fs change would be detected only by running timeouts - sys.runQueuedTimeoutCallbacks(); - } - // Delayed update program - sys.runQueuedTimeoutCallbacks(); - return; + sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for main.js output + sys.checkTimeoutQueueLength(0); }, }, - ], + { + caption: "add new folder to temp", + change: sys => sys.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/node_modules/bar/temp/fooBar/index.d.ts`, content: "export function temp(): string;" }), + timeouts: sys => sys.checkTimeoutQueueLength(0), + } + ] }); } - verifyRenamingFileInSubFolder("uses watchFile when renaming file in subfolder", Tsc_WatchDirectory.WatchFile); - - verifyRenamingFileInSubFolder("uses non recursive watchDirectory when renaming file in subfolder", Tsc_WatchDirectory.NonRecursiveWatchDirectory); - - verifyRenamingFileInSubFolder("uses non recursive dynamic polling when renaming file in subfolder", Tsc_WatchDirectory.DynamicPolling); + verifyWorker(); + verifyWorker("-extendedDiagnostics"); + }); + }); - verifyTscWatch({ - scenario, - subScenario: "watchDirectories/when there are symlinks to folders in recursive folders", - commandLineArgs: ["--w"], - sys: () => { - const cwd = "/home/user/projects/myproject"; - const file1: File = { - path: `${cwd}/src/file.ts`, - content: `import * as a from "a"` - }; - const tsconfig: File = { - path: `${cwd}/tsconfig.json`, - content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` - }; - const realA: File = { - path: `${cwd}/node_modules/reala/index.d.ts`, - content: `export {}` - }; - const realB: File = { - path: `${cwd}/node_modules/realb/index.d.ts`, - content: `export {}` - }; - const symLinkA: SymLink = { - path: `${cwd}/node_modules/a`, - symLink: `${cwd}/node_modules/reala` - }; - const symLinkB: SymLink = { - path: `${cwd}/node_modules/b`, - symLink: `${cwd}/node_modules/realb` - }; - const symLinkBInA: SymLink = { - path: `${cwd}/node_modules/reala/node_modules/b`, - symLink: `${cwd}/node_modules/b` - }; - const symLinkAInB: SymLink = { - path: `${cwd}/node_modules/realb/node_modules/a`, - symLink: `${cwd}/node_modules/a` - }; - const files = [libFile, file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; - const environmentVariables = new Map(); - environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); - return createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); - }, - changes: emptyArray - }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `fsWatch/when using file watching thats when rename occurs when file is still on the disk`, + commandLineArgs: ["-w", "--extendedDiagnostics"], + sys: () => ts.tscWatch.createWatchedSystem( + { + [ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content, + [`${ts.tscWatch.projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, + [`${ts.tscWatch.projectRoot}/foo.ts`]: `export declare function foo(): string;`, + [`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({ + watchOptions: { watchFile: "useFsEvents" }, + files: ["foo.ts", "main.ts"] + }), + }, + { currentDirectory: ts.tscWatch.projectRoot, } + ), + changes: [ + { + caption: "Introduce error such that when callback happens file is already appeared", + // vm's wq generates this kind of event + // Skip delete event so inode changes but when the create's rename occurs file is on disk + change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.ts`, `export declare function foo2(): string;`, { + invokeFileDeleteCreateAsPartInsteadOfChange: true, + ignoreDelete: true, + }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), + }, + { + caption: "Replace file with rename event that fixes error", + change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.ts`, `export declare function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), + }, + ] + }); - verifyTscWatch({ - scenario, - subScenario: "watchDirectories/with non synchronous watch directory", - commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], - sys: () => { - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - const file1: File = { - path: `${projectRoot}/src/file1.ts`, - content: `import { x } from "file2";` - }; - const file2: File = { - path: `${projectRoot}/node_modules/file2/index.d.ts`, - content: `export const x = 10;` - }; - const files = [libFile, file1, file2, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); + describe("with fsWatch on inodes", () => { + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `fsWatch/when using file watching thats on inode`, + commandLineArgs: ["-w", "--extendedDiagnostics"], + sys: () => ts.tscWatch.createWatchedSystem( + { + [ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content, + [`${ts.tscWatch.projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, + [`${ts.tscWatch.projectRoot}/foo.d.ts`]: `export function foo(): string;`, + [`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.d.ts", "main.ts"] }), }, - changes: [ - { - caption: "Directory watch updates because of file1.js creation", - change: noop, - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for file1.js output - sys.checkTimeoutQueueLength(0); - }, - }, - { - caption: "Remove directory node_modules", - // Remove directory node_modules - change: sys => sys.deleteFolder(`${projectRoot}/node_modules`, /*recursive*/ true), - timeouts: sys => { - sys.checkTimeoutQueueLength(3); // 1. Failed lookup invalidation 2. For updating program and 3. for updating child watches - sys.runQueuedTimeoutCallbacks(sys.getNextTimeoutId() - 2); // Update program - }, - }, - { - caption: "Pending directory watchers and program update", - change: noop, - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers - sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update - sys.checkTimeoutQueueLengthAndRun(1); // Actual program update - sys.checkTimeoutQueueLength(0); - }, - }, - { - caption: "Start npm install", - // npm install - change: sys => sys.createDirectory(`${projectRoot}/node_modules`), - timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure - }, - { - caption: "npm install folder creation of file2", - change: sys => sys.createDirectory(`${projectRoot}/node_modules/file2`), - timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure - }, - { - caption: "npm install index file in file2", - change: sys => sys.writeFile(`${projectRoot}/node_modules/file2/index.d.ts`, `export const x = 10;`), - timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure - }, - { - caption: "Updates the program", - change: noop, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); - sys.checkTimeoutQueueLength(2); // To Update program and failed lookup update - }, - }, - { - caption: "Invalidates module resolution cache", - change: noop, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); - sys.checkTimeoutQueueLength(1); // To Update program - }, - }, - { - caption: "Pending updates", - change: noop, - timeouts: sys => { - sys.runQueuedTimeoutCallbacks(); - sys.checkTimeoutQueueLength(0); - }, - }, - ], - }); - - verifyTscWatch({ - scenario, - subScenario: "watchDirectories/with non synchronous watch directory with outDir and declaration enabled", - commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], - sys: () => { - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { outDir: "dist", declaration: true } }) - }; - const file1: File = { - path: `${projectRoot}/src/file1.ts`, - content: `import { x } from "file2";` - }; - const file2: File = { - path: `${projectRoot}/node_modules/file2/index.d.ts`, - content: `export const x = 10;` - }; - const files = [libFile, file1, file2, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); + { + currentDirectory: ts.tscWatch.projectRoot, + inodeWatching: true + } + ), + changes: [ + { + caption: "Replace file with rename event that introduces error", + change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.d.ts`, `export function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), }, - changes: [ - noopChange, - { - caption: "Add new file, should schedule and run timeout to update directory watcher", - change: sys => sys.writeFile(`${projectRoot}/src/file3.ts`, `export const y = 10;`), - timeouts: checkSingleTimeoutQueueLengthAndRun, // Update the child watch - }, - { - caption: "Actual program update to include new file", - change: noop, - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), // Scheduling failed lookup update and program update - }, - { - caption: "After program emit with new file, should schedule and run timeout to update directory watcher", - change: noop, - timeouts: checkSingleTimeoutQueueLengthAndRun, // Update the child watch - }, - noopChange, - ], - }); - - verifyTscWatch({ - scenario, - subScenario: "watchDirectories/with non synchronous watch directory renaming a file", - commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], - sys: () => { - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { outDir: "dist" } }) - }; - const file1: File = { - path: `${projectRoot}/src/file1.ts`, - content: `import { x } from "./file2";` - }; - const file2: File = { - path: `${projectRoot}/src/file2.ts`, - content: `export const x = 10;` - }; - const files = [libFile, file1, file2, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); + { + caption: "Replace file with rename event that fixes error", + change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.d.ts`, `export function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), }, - changes: [ - noopChange, - { - caption: "rename the file", - change: sys => sys.renameFile(`${projectRoot}/src/file2.ts`, `${projectRoot}/src/renamed.ts`), - timeouts: sys => { - sys.checkTimeoutQueueLength(2); // 1. For updating program and 2. for updating child watches - sys.runQueuedTimeoutCallbacks(1); // Update program - }, - }, - { - caption: "Pending directory watchers and program update", - change: noop, - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers - sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update - sys.checkTimeoutQueueLengthAndRun(1); // Actual program update - sys.checkTimeoutQueueLength(0); - }, - }, - ], - }); + ] }); - describe("handles watch compiler options", () => { - verifyTscWatch({ - scenario, - subScenario: "watchOptions/with watchFile option", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], - sys: () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - watchOptions: { - watchFile: "UseFsEvents" - } - }) - }; - const files = [libFile, commonFile1, commonFile2, configFile]; - return createWatchedSystem(files); - }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "watchOptions/with watchDirectory option", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], - sys: () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - watchOptions: { - watchDirectory: "UseFsEvents" - } - }) - }; - const files = [libFile, commonFile1, commonFile2, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); + ts.tscWatch.verifyTscWatch({ + scenario, + subScenario: `fsWatch/when using file watching thats on inode when rename event ends with tilde`, + commandLineArgs: ["-w", "--extendedDiagnostics"], + sys: () => ts.tscWatch.createWatchedSystem( + { + [ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content, + [`${ts.tscWatch.projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, + [`${ts.tscWatch.projectRoot}/foo.d.ts`]: `export function foo(): string;`, + [`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.d.ts", "main.ts"] }), }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "watchOptions/with fallbackPolling option", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], - sys: () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - watchOptions: { - fallbackPolling: "PriorityInterval" - } - }) - }; - const files = [libFile, commonFile1, commonFile2, configFile]; - return createWatchedSystem(files, { runWithoutRecursiveWatches: true, runWithFallbackPolling: true }); + { + currentDirectory: ts.tscWatch.projectRoot, + inodeWatching: true + } + ), + changes: [ + { + caption: "Replace file with rename event that introduces error", + change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.d.ts`, `export function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), }, - changes: emptyArray - }); - - verifyTscWatch({ - scenario, - subScenario: "watchOptions/with watchFile as watch options to extend", - commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json", "--watchFile", "UseFsEvents"], - sys: () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: "{}" - }; - const files = [libFile, commonFile1, commonFile2, configFile]; - return createWatchedSystem(files); + { + caption: "Replace file with rename event that fixes error", + change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.d.ts`, `export function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }), + timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), }, - changes: emptyArray - }); - - describe("exclude options", () => { - function sys(watchOptions: WatchOptions, runWithoutRecursiveWatches?: boolean): WatchedSystem { - const configFile: File = { - path: `${projectRoot}/tsconfig.json`, - content: JSON.stringify({ exclude: ["node_modules"], watchOptions }) - }; - const main: File = { - path: `${projectRoot}/src/main.ts`, - content: `import { foo } from "bar"; foo();` - }; - const bar: File = { - path: `${projectRoot}/node_modules/bar/index.d.ts`, - content: `export { foo } from "./foo";` - }; - const foo: File = { - path: `${projectRoot}/node_modules/bar/foo.d.ts`, - content: `export function foo(): string;` - }; - const fooBar: File = { - path: `${projectRoot}/node_modules/bar/fooBar.d.ts`, - content: `export function fooBar(): string;` - }; - const temp: File = { - path: `${projectRoot}/node_modules/bar/temp/index.d.ts`, - content: "export function temp(): string;" - }; - const files = [libFile, main, bar, foo, fooBar, temp, configFile]; - return createWatchedSystem(files, { currentDirectory: projectRoot, runWithoutRecursiveWatches }); - } - - function verifyWorker(...additionalFlags: string[]) { - verifyTscWatch({ - scenario, - subScenario: `watchOptions/with excludeFiles option${additionalFlags.join("")}`, - commandLineArgs: ["-w", ...additionalFlags], - sys: () => sys({ excludeFiles: ["node_modules/*"] }), - changes: [ - { - caption: "Change foo", - change: sys => replaceFileText(sys, `${projectRoot}/node_modules/bar/foo.d.ts`, "foo", "fooBar"), - timeouts: sys => sys.checkTimeoutQueueLength(0), - } - ] - }); - - verifyTscWatch({ - scenario, - subScenario: `watchOptions/with excludeDirectories option${additionalFlags.join("")}`, - commandLineArgs: ["-w", ...additionalFlags], - sys: () => sys({ excludeDirectories: ["node_modules"] }), - changes: [ - { - caption: "delete fooBar", - change: sys => sys.deleteFile(`${projectRoot}/node_modules/bar/fooBar.d.ts`), - timeouts: sys => sys.checkTimeoutQueueLength(0), } - ] - }); - - verifyTscWatch({ - scenario, - subScenario: `watchOptions/with excludeDirectories option with recursive directory watching${additionalFlags.join("")}`, - commandLineArgs: ["-w", ...additionalFlags], - sys: () => sys({ excludeDirectories: ["**/temp"] }, /*runWithoutRecursiveWatches*/ true), - changes: [ - { - caption: "Directory watch updates because of main.js creation", - change: noop, - timeouts: sys => { - sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for main.js output - sys.checkTimeoutQueueLength(0); - }, - }, - { - caption: "add new folder to temp", - change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/node_modules/bar/temp/fooBar/index.d.ts`, content: "export function temp(): string;" }), - timeouts: sys => sys.checkTimeoutQueueLength(0), - } - ] - }); - } - - verifyWorker(); - verifyWorker("-extendedDiagnostics"); - }); + ] }); - verifyTscWatch({ + ts.tscWatch.verifyTscWatch({ scenario, - subScenario: `fsWatch/when using file watching thats when rename occurs when file is still on the disk`, + subScenario: `fsWatch/when using file watching thats on inode when rename occurs when file is still on the disk`, commandLineArgs: ["-w", "--extendedDiagnostics"], - sys: () => createWatchedSystem( + sys: () => ts.tscWatch.createWatchedSystem( { - [libFile.path]: libFile.content, - [`${projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, - [`${projectRoot}/foo.ts`]: `export declare function foo(): string;`, - [`${projectRoot}/tsconfig.json`]: JSON.stringify({ + [ts.tscWatch.libFile.path]: ts.tscWatch.libFile.content, + [`${ts.tscWatch.projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, + [`${ts.tscWatch.projectRoot}/foo.ts`]: `export declare function foo(): string;`, + [`${ts.tscWatch.projectRoot}/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.ts", "main.ts"] }), }, - { currentDirectory: projectRoot, } + { + currentDirectory: ts.tscWatch.projectRoot, + inodeWatching: true, + } ), changes: [ { caption: "Introduce error such that when callback happens file is already appeared", // vm's wq generates this kind of event // Skip delete event so inode changes but when the create's rename occurs file is on disk - change: sys => sys.modifyFile(`${projectRoot}/foo.ts`, `export declare function foo2(): string;`, { + change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.ts`, `export declare function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, ignoreDelete: true, + skipInodeCheckOnCreate: true }), timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), }, { caption: "Replace file with rename event that fixes error", - change: sys => sys.modifyFile(`${projectRoot}/foo.ts`, `export declare function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, }), + change: sys => sys.modifyFile(`${ts.tscWatch.projectRoot}/foo.ts`, `export declare function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, }), timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), }, ] }); - - describe("with fsWatch on inodes", () => { - verifyTscWatch({ - scenario, - subScenario: `fsWatch/when using file watching thats on inode`, - commandLineArgs: ["-w", "--extendedDiagnostics"], - sys: () => createWatchedSystem( - { - [libFile.path]: libFile.content, - [`${projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, - [`${projectRoot}/foo.d.ts`]: `export function foo(): string;`, - [`${projectRoot}/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.d.ts", "main.ts"] }), - }, - { - currentDirectory: projectRoot, - inodeWatching: true - } - ), - changes: [ - { - caption: "Replace file with rename event that introduces error", - change: sys => sys.modifyFile(`${projectRoot}/foo.d.ts`, `export function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true }), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), - }, - { - caption: "Replace file with rename event that fixes error", - change: sys => sys.modifyFile(`${projectRoot}/foo.d.ts`, `export function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true }), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), - }, - ] - }); - - verifyTscWatch({ - scenario, - subScenario: `fsWatch/when using file watching thats on inode when rename event ends with tilde`, - commandLineArgs: ["-w", "--extendedDiagnostics"], - sys: () => createWatchedSystem( - { - [libFile.path]: libFile.content, - [`${projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, - [`${projectRoot}/foo.d.ts`]: `export function foo(): string;`, - [`${projectRoot}/tsconfig.json`]: JSON.stringify({ watchOptions: { watchFile: "useFsEvents" }, files: ["foo.d.ts", "main.ts"] }), - }, - { - currentDirectory: projectRoot, - inodeWatching: true - } - ), - changes: [ - { - caption: "Replace file with rename event that introduces error", - change: sys => sys.modifyFile(`${projectRoot}/foo.d.ts`, `export function foo2(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), - }, - { - caption: "Replace file with rename event that fixes error", - change: sys => sys.modifyFile(`${projectRoot}/foo.d.ts`, `export function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, useTildeAsSuffixInRenameEventFileName: true }), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), - }, - ] - }); - - verifyTscWatch({ - scenario, - subScenario: `fsWatch/when using file watching thats on inode when rename occurs when file is still on the disk`, - commandLineArgs: ["-w", "--extendedDiagnostics"], - sys: () => createWatchedSystem( - { - [libFile.path]: libFile.content, - [`${projectRoot}/main.ts`]: `import { foo } from "./foo"; foo();`, - [`${projectRoot}/foo.ts`]: `export declare function foo(): string;`, - [`${projectRoot}/tsconfig.json`]: JSON.stringify({ - watchOptions: { watchFile: "useFsEvents" }, - files: ["foo.ts", "main.ts"] - }), - }, - { - currentDirectory: projectRoot, - inodeWatching: true, - } - ), - changes: [ - { - caption: "Introduce error such that when callback happens file is already appeared", - // vm's wq generates this kind of event - // Skip delete event so inode changes but when the create's rename occurs file is on disk - change: sys => sys.modifyFile(`${projectRoot}/foo.ts`, `export declare function foo2(): string;`, { - invokeFileDeleteCreateAsPartInsteadOfChange: true, - ignoreDelete: true, - skipInodeCheckOnCreate: true - }), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), - }, - { - caption: "Replace file with rename event that fixes error", - change: sys => sys.modifyFile(`${projectRoot}/foo.ts`, `export declare function foo(): string;`, { invokeFileDeleteCreateAsPartInsteadOfChange: true, }), - timeouts: sys => sys.checkTimeoutQueueLengthAndRun(1), - }, - ] - }); - }); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts b/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts index 6d5ee91d68d67..f778e72139824 100644 --- a/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts +++ b/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts @@ -1,181 +1,181 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: applyChangesToOpenFiles", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: "{}" - }; - const file3: File = { - path: "/a/b/file3.ts", - content: "let xyz = 1;" - }; - const app: File = { - path: "/a/b/app.ts", - content: "let z = 1;" - }; +import * as ts from "../../_namespaces/ts"; - function fileContentWithComment(file: File) { - return `// some copy right notice +describe("unittests:: tsserver:: applyChangesToOpenFiles", () => { + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + const file3: ts.projectSystem.File = { + path: "/a/b/file3.ts", + content: "let xyz = 1;" + }; + const app: ts.projectSystem.File = { + path: "/a/b/app.ts", + content: "let z = 1;" + }; + + function fileContentWithComment(file: ts.projectSystem.File) { + return `// some copy right notice ${file.content}`; - } + } - function verifyText(service: server.ProjectService, file: string, expected: string) { - const info = service.getScriptInfo(file)!; - const snap = info.getSnapshot(); - // Verified applied in reverse order - assert.equal(snap.getText(0, snap.getLength()), expected, `Text of changed file: ${file}`); - } + function verifyText(service: ts.server.ProjectService, file: string, expected: string) { + const info = service.getScriptInfo(file)!; + const snap = info.getSnapshot(); + // Verified applied in reverse order + assert.equal(snap.getText(0, snap.getLength()), expected, `Text of changed file: ${file}`); + } - function verifyProjectVersion(project: server.Project, expected: number) { - assert.equal(Number(project.getProjectVersion()), expected); - } + function verifyProjectVersion(project: ts.server.Project, expected: number) { + assert.equal(Number(project.getProjectVersion()), expected); + } - interface Verify { - applyChangesToOpen: (session: TestSession) => void; - openFile1Again: (session: TestSession) => void; - } - function verify({ applyChangesToOpen, openFile1Again }: Verify) { - const host = createServerHost([app, file3, commonFile1, commonFile2, libFile, configFile]); - const session = createSession(host); - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { file: app.path } - }); - const service = session.getProjectService(); - const project = service.configuredProjects.get(configFile.path)!; - assert.isDefined(project); - verifyProjectVersion(project, 1); - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { - file: file3.path, - fileContent: fileContentWithComment(file3) - } - }); - verifyProjectVersion(project, 2); + interface Verify { + applyChangesToOpen: (session: ts.projectSystem.TestSession) => void; + openFile1Again: (session: ts.projectSystem.TestSession) => void; + } + function verify({ applyChangesToOpen, openFile1Again }: Verify) { + const host = ts.projectSystem.createServerHost([app, file3, ts.projectSystem.commonFile1, ts.projectSystem.commonFile2, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Open, + arguments: { file: app.path } + }); + const service = session.getProjectService(); + const project = service.configuredProjects.get(configFile.path)!; + assert.isDefined(project); + verifyProjectVersion(project, 1); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Open, + arguments: { + file: file3.path, + fileContent: fileContentWithComment(file3) + } + }); + verifyProjectVersion(project, 2); - // Verify Texts - verifyText(service, commonFile1.path, commonFile1.content); - verifyText(service, commonFile2.path, commonFile2.content); - verifyText(service, app.path, app.content); - verifyText(service, file3.path, fileContentWithComment(file3)); + // Verify Texts + verifyText(service, ts.projectSystem.commonFile1.path, ts.projectSystem.commonFile1.content); + verifyText(service, ts.projectSystem.commonFile2.path, ts.projectSystem.commonFile2.content); + verifyText(service, app.path, app.content); + verifyText(service, file3.path, fileContentWithComment(file3)); - // Apply changes - applyChangesToOpen(session); + // Apply changes + applyChangesToOpen(session); - // Verify again - verifyProjectVersion(project, 3); - // Open file contents - verifyText(service, commonFile1.path, fileContentWithComment(commonFile1)); - verifyText(service, commonFile2.path, fileContentWithComment(commonFile2)); - verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;"); - verifyText(service, file3.path, file3.content); + // Verify again + verifyProjectVersion(project, 3); + // Open file contents + verifyText(service, ts.projectSystem.commonFile1.path, fileContentWithComment(ts.projectSystem.commonFile1)); + verifyText(service, ts.projectSystem.commonFile2.path, fileContentWithComment(ts.projectSystem.commonFile2)); + verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;"); + verifyText(service, file3.path, file3.content); - // Open file1 again - openFile1Again(session); - assert.isTrue(service.getScriptInfo(commonFile1.path)!.isScriptOpen()); + // Open file1 again + openFile1Again(session); + assert.isTrue(service.getScriptInfo(ts.projectSystem.commonFile1.path)!.isScriptOpen()); - // Verify that file1 contents are changed - verifyProjectVersion(project, 4); - verifyText(service, commonFile1.path, commonFile1.content); - verifyText(service, commonFile2.path, fileContentWithComment(commonFile2)); - verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;"); - verifyText(service, file3.path, file3.content); - } + // Verify that file1 contents are changed + verifyProjectVersion(project, 4); + verifyText(service, ts.projectSystem.commonFile1.path, ts.projectSystem.commonFile1.content); + verifyText(service, ts.projectSystem.commonFile2.path, fileContentWithComment(ts.projectSystem.commonFile2)); + verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;"); + verifyText(service, file3.path, file3.content); + } - it("with applyChangedToOpenFiles request", () => { - verify({ - applyChangesToOpen: session => session.executeCommandSeq({ - command: protocol.CommandTypes.ApplyChangedToOpenFiles, - arguments: { - openFiles: [ - { - fileName: commonFile1.path, - content: fileContentWithComment(commonFile1) - }, - { - fileName: commonFile2.path, - content: fileContentWithComment(commonFile2) - } - ], - changedFiles: [ - { - fileName: app.path, - changes: [ - { - span: { start: 0, length: 0 }, - newText: "let zzz = 10;" - }, - { - span: { start: 0, length: 0 }, - newText: "let zz = 10;" - } - ] - } - ], - closedFiles: [ - file3.path - ] - } - }), - openFile1Again: session => session.executeCommandSeq({ - command: protocol.CommandTypes.ApplyChangedToOpenFiles, - arguments: { - openFiles: [{ - fileName: commonFile1.path, - content: commonFile1.content - }] - } - }), - }); + it("with applyChangedToOpenFiles request", () => { + verify({ + applyChangesToOpen: session => session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + openFiles: [ + { + fileName: ts.projectSystem.commonFile1.path, + content: fileContentWithComment(ts.projectSystem.commonFile1) + }, + { + fileName: ts.projectSystem.commonFile2.path, + content: fileContentWithComment(ts.projectSystem.commonFile2) + } + ], + changedFiles: [ + { + fileName: app.path, + changes: [ + { + span: { start: 0, length: 0 }, + newText: "let zzz = 10;" + }, + { + span: { start: 0, length: 0 }, + newText: "let zz = 10;" + } + ] + } + ], + closedFiles: [ + file3.path + ] + } + }), + openFile1Again: session => session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + openFiles: [{ + fileName: ts.projectSystem.commonFile1.path, + content: ts.projectSystem.commonFile1.content + }] + } + }), }); + }); - it("with updateOpen request", () => { - verify({ - applyChangesToOpen: session => session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - openFiles: [ - { - file: commonFile1.path, - fileContent: fileContentWithComment(commonFile1) - }, - { - file: commonFile2.path, - fileContent: fileContentWithComment(commonFile2) - } - ], - changedFiles: [ - { - fileName: app.path, - textChanges: [ - { - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - newText: "let zzz = 10;", - }, - { - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - newText: "let zz = 10;", - } - ] - } - ], - closedFiles: [ - file3.path - ] - } - }), - openFile1Again: session => session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - openFiles: [{ - file: commonFile1.path, - fileContent: commonFile1.content - }] - } - }), - }); + it("with updateOpen request", () => { + verify({ + applyChangesToOpen: session => session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + openFiles: [ + { + file: ts.projectSystem.commonFile1.path, + fileContent: fileContentWithComment(ts.projectSystem.commonFile1) + }, + { + file: ts.projectSystem.commonFile2.path, + fileContent: fileContentWithComment(ts.projectSystem.commonFile2) + } + ], + changedFiles: [ + { + fileName: app.path, + textChanges: [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: "let zzz = 10;", + }, + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: "let zz = 10;", + } + ] + } + ], + closedFiles: [ + file3.path + ] + } + }), + openFile1Again: session => session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + openFiles: [{ + file: ts.projectSystem.commonFile1.path, + fileContent: ts.projectSystem.commonFile1.content + }] + } + }), }); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/autoImportProvider.ts b/src/testRunner/unittests/tsserver/autoImportProvider.ts index f05d06f484237..5d2113bb64ed9 100644 --- a/src/testRunner/unittests/tsserver/autoImportProvider.ts +++ b/src/testRunner/unittests/tsserver/autoImportProvider.ts @@ -1,353 +1,353 @@ -namespace ts.projectSystem { - const angularFormsDts: File = { - path: "/node_modules/@angular/forms/forms.d.ts", - content: "export declare class PatternValidator {}", - }; - const angularFormsPackageJson: File = { - path: "/node_modules/@angular/forms/package.json", - content: `{ "name": "@angular/forms", "typings": "./forms.d.ts" }`, - }; - const angularCoreDts: File = { - path: "/node_modules/@angular/core/core.d.ts", - content: "", - }; - const angularCorePackageJson: File = { - path: "/node_modules/@angular/core/package.json", - content: `{ "name": "@angular/core", "typings": "./core.d.ts" }`, - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: `{ "compilerOptions": { "module": "commonjs" } }`, - }; - const packageJson: File = { - path: "/package.json", - content: `{ "dependencies": { "@angular/forms": "*", "@angular/core": "*" } }` - }; - const indexTs: File = { - path: "/index.ts", - content: "" - }; +import * as ts from "../../_namespaces/ts"; + +const angularFormsDts: ts.projectSystem.File = { + path: "/node_modules/@angular/forms/forms.d.ts", + content: "export declare class PatternValidator {}", +}; +const angularFormsPackageJson: ts.projectSystem.File = { + path: "/node_modules/@angular/forms/package.json", + content: `{ "name": "@angular/forms", "typings": "./forms.d.ts" }`, +}; +const angularCoreDts: ts.projectSystem.File = { + path: "/node_modules/@angular/core/core.d.ts", + content: "", +}; +const angularCorePackageJson: ts.projectSystem.File = { + path: "/node_modules/@angular/core/package.json", + content: `{ "name": "@angular/core", "typings": "./core.d.ts" }`, +}; +const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: `{ "compilerOptions": { "module": "commonjs" } }`, +}; +const packageJson: ts.projectSystem.File = { + path: "/package.json", + content: `{ "dependencies": { "@angular/forms": "*", "@angular/core": "*" } }` +}; +const indexTs: ts.projectSystem.File = { + path: "/index.ts", + content: "" +}; + +describe("unittests:: tsserver:: autoImportProvider", () => { + it("Auto import provider program is not created without dependencies listed in package.json", () => { + const { projectService, session } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + { path: packageJson.path, content: `{ "dependencies": {} }` }, + indexTs + ]); + ts.projectSystem.openFilesForSession([indexTs], session); + assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + }); - describe("unittests:: tsserver:: autoImportProvider", () => { - it("Auto import provider program is not created without dependencies listed in package.json", () => { - const { projectService, session } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - { path: packageJson.path, content: `{ "dependencies": {} }` }, - indexTs - ]); - openFilesForSession([indexTs], session); - assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - }); + it("Auto import provider program is not created if dependencies are already in main program", () => { + const { projectService, session } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + packageJson, + { path: indexTs.path, content: "import '@angular/forms';" } + ]); + ts.projectSystem.openFilesForSession([indexTs], session); + assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + }); - it("Auto import provider program is not created if dependencies are already in main program", () => { - const { projectService, session } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - packageJson, - { path: indexTs.path, content: "import '@angular/forms';" } - ]); - openFilesForSession([indexTs], session); - assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - }); + it("Auto-import program is not created for projects already inside node_modules", () => { + // Simulate browsing typings files inside node_modules: no point creating auto import program + // for the InferredProject that gets created in there. + const { projectService, session } = setup([ + angularFormsDts, + { path: angularFormsPackageJson.path, content: `{ "dependencies": { "@angular/core": "*" } }` }, + { path: "/node_modules/@angular/core/package.json", content: `{ "typings": "./core.d.ts" }` }, + { path: "/node_modules/@angular/core/core.d.ts", content: `export namespace angular {};` }, + ]); + + ts.projectSystem.openFilesForSession([angularFormsDts], session); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 1); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 0); + assert.isUndefined(projectService + .getDefaultProjectForFile(angularFormsDts.path as ts.server.NormalizedPath, /*ensureProject*/ true)! + .getLanguageService() + .getAutoImportProvider()); + }); - it("Auto-import program is not created for projects already inside node_modules", () => { - // Simulate browsing typings files inside node_modules: no point creating auto import program - // for the InferredProject that gets created in there. - const { projectService, session } = setup([ - angularFormsDts, - { path: angularFormsPackageJson.path, content: `{ "dependencies": { "@angular/core": "*" } }` }, - { path: "/node_modules/@angular/core/package.json", content: `{ "typings": "./core.d.ts" }` }, - { path: "/node_modules/@angular/core/core.d.ts", content: `export namespace angular {};` }, - ]); - - openFilesForSession([angularFormsDts], session); - checkNumberOfInferredProjects(projectService, 1); - checkNumberOfConfiguredProjects(projectService, 0); - assert.isUndefined(projectService - .getDefaultProjectForFile(angularFormsDts.path as server.NormalizedPath, /*ensureProject*/ true)! - .getLanguageService() - .getAutoImportProvider()); - }); + it("Auto-importable file is in inferred project until imported", () => { + const { projectService, session, updateFile } = setup([angularFormsDts, angularFormsPackageJson, tsconfig, packageJson, indexTs]); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 0); + ts.projectSystem.openFilesForSession([angularFormsDts], session); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 1); + assert.equal( + projectService.getDefaultProjectForFile(angularFormsDts.path as ts.server.NormalizedPath, /*ensureProject*/ true)?.projectKind, + ts.server.ProjectKind.Inferred); + + updateFile(indexTs.path, "import '@angular/forms'"); + assert.equal( + projectService.getDefaultProjectForFile(angularFormsDts.path as ts.server.NormalizedPath, /*ensureProject*/ true)?.projectKind, + ts.server.ProjectKind.Configured); + + assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + }); - it("Auto-importable file is in inferred project until imported", () => { - const { projectService, session, updateFile } = setup([angularFormsDts, angularFormsPackageJson, tsconfig, packageJson, indexTs]); - checkNumberOfInferredProjects(projectService, 0); - openFilesForSession([angularFormsDts], session); - checkNumberOfInferredProjects(projectService, 1); - assert.equal( - projectService.getDefaultProjectForFile(angularFormsDts.path as server.NormalizedPath, /*ensureProject*/ true)?.projectKind, - server.ProjectKind.Inferred); - - updateFile(indexTs.path, "import '@angular/forms'"); - assert.equal( - projectService.getDefaultProjectForFile(angularFormsDts.path as server.NormalizedPath, /*ensureProject*/ true)?.projectKind, - server.ProjectKind.Configured); - - assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - }); + it("Responds to package.json changes", () => { + const { projectService, session, host } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + { path: "/package.json", content: "{}" }, + indexTs + ]); - it("Responds to package.json changes", () => { - const { projectService, session, host } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - { path: "/package.json", content: "{}" }, - indexTs - ]); + ts.projectSystem.openFilesForSession([indexTs], session); + assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - openFilesForSession([indexTs], session); - assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + host.writeFile(packageJson.path, packageJson.content); + assert.ok(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + }); - host.writeFile(packageJson.path, packageJson.content); - assert.ok(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - }); + it("Reuses autoImportProvider when program structure is unchanged", () => { + const { projectService, session, updateFile } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + packageJson, + indexTs + ]); + + ts.projectSystem.openFilesForSession([indexTs], session); + const autoImportProvider = projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider(); + assert.ok(autoImportProvider); + + updateFile(indexTs.path, "console.log(0)"); + assert.strictEqual( + projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider(), + autoImportProvider); + }); - it("Reuses autoImportProvider when program structure is unchanged", () => { - const { projectService, session, updateFile } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - packageJson, - indexTs - ]); - - openFilesForSession([indexTs], session); - const autoImportProvider = projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider(); - assert.ok(autoImportProvider); - - updateFile(indexTs.path, "console.log(0)"); - assert.strictEqual( - projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider(), - autoImportProvider); - }); + it("Closes AutoImportProviderProject when host project closes", () => { + const { projectService, session } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + packageJson, + indexTs + ]); + + ts.projectSystem.openFilesForSession([indexTs], session); + const hostProject = projectService.configuredProjects.get(tsconfig.path)!; + hostProject.getPackageJsonAutoImportProvider(); + const autoImportProviderProject = hostProject.autoImportProviderHost; + assert.ok(autoImportProviderProject); + + hostProject.close(); + assert.ok(autoImportProviderProject && autoImportProviderProject.isClosed()); + assert.isUndefined(hostProject.autoImportProviderHost); + }); - it("Closes AutoImportProviderProject when host project closes", () => { - const { projectService, session } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - packageJson, - indexTs - ]); - - openFilesForSession([indexTs], session); - const hostProject = projectService.configuredProjects.get(tsconfig.path)!; - hostProject.getPackageJsonAutoImportProvider(); - const autoImportProviderProject = hostProject.autoImportProviderHost; - assert.ok(autoImportProviderProject); - - hostProject.close(); - assert.ok(autoImportProviderProject && autoImportProviderProject.isClosed()); - assert.isUndefined(hostProject.autoImportProviderHost); - }); + it("Does not schedule ensureProjectForOpenFiles on AutoImportProviderProject creation", () => { + const { projectService, session, host } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + indexTs + ]); + + // Create configured project only, ensure !projectService.pendingEnsureProjectForOpenFiles + ts.projectSystem.openFilesForSession([indexTs], session); + const hostProject = projectService.configuredProjects.get(tsconfig.path)!; + projectService.delayEnsureProjectForOpenFiles(); + host.runQueuedTimeoutCallbacks(); + assert.isFalse(projectService.pendingEnsureProjectForOpenFiles); + + // Create auto import provider project, ensure still !projectService.pendingEnsureProjectForOpenFiles + host.writeFile(packageJson.path, packageJson.content); + hostProject.getPackageJsonAutoImportProvider(); + assert.isFalse(projectService.pendingEnsureProjectForOpenFiles); + }); - it("Does not schedule ensureProjectForOpenFiles on AutoImportProviderProject creation", () => { - const { projectService, session, host } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - indexTs - ]); - - // Create configured project only, ensure !projectService.pendingEnsureProjectForOpenFiles - openFilesForSession([indexTs], session); - const hostProject = projectService.configuredProjects.get(tsconfig.path)!; - projectService.delayEnsureProjectForOpenFiles(); - host.runQueuedTimeoutCallbacks(); - assert.isFalse(projectService.pendingEnsureProjectForOpenFiles); - - // Create auto import provider project, ensure still !projectService.pendingEnsureProjectForOpenFiles - host.writeFile(packageJson.path, packageJson.content); - hostProject.getPackageJsonAutoImportProvider(); - assert.isFalse(projectService.pendingEnsureProjectForOpenFiles); - }); + it("Responds to automatic changes in node_modules", () => { + const { projectService, session, host } = setup([ + angularFormsDts, + angularFormsPackageJson, + angularCoreDts, + angularCorePackageJson, + tsconfig, + packageJson, + indexTs + ]); + + ts.projectSystem.openFilesForSession([indexTs], session); + const project = projectService.configuredProjects.get(tsconfig.path)!; + const completionsBefore = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); + assert.isTrue(completionsBefore?.entries.some(c => c.name === "PatternValidator")); + + // Directory watchers only fire for add/remove, not change. + // This is ok since a real `npm install` will always trigger add/remove events. + host.deleteFile(angularFormsDts.path); + host.writeFile(angularFormsDts.path, ""); + + const autoImportProvider = project.getLanguageService().getAutoImportProvider(); + const completionsAfter = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); + assert.equal(autoImportProvider!.getSourceFile(angularFormsDts.path)!.getText(), ""); + assert.isFalse(completionsAfter?.entries.some(c => c.name === "PatternValidator")); + }); - it("Responds to automatic changes in node_modules", () => { - const { projectService, session, host } = setup([ - angularFormsDts, - angularFormsPackageJson, - angularCoreDts, - angularCorePackageJson, - tsconfig, - packageJson, - indexTs - ]); - - openFilesForSession([indexTs], session); - const project = projectService.configuredProjects.get(tsconfig.path)!; - const completionsBefore = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); - assert.isTrue(completionsBefore?.entries.some(c => c.name === "PatternValidator")); - - // Directory watchers only fire for add/remove, not change. - // This is ok since a real `npm install` will always trigger add/remove events. - host.deleteFile(angularFormsDts.path); - host.writeFile(angularFormsDts.path, ""); - - const autoImportProvider = project.getLanguageService().getAutoImportProvider(); - const completionsAfter = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); - assert.equal(autoImportProvider!.getSourceFile(angularFormsDts.path)!.getText(), ""); - assert.isFalse(completionsAfter?.entries.some(c => c.name === "PatternValidator")); - }); + it("Responds to manual changes in node_modules", () => { + const { projectService, session, updateFile } = setup([ + angularFormsDts, + angularFormsPackageJson, + angularCoreDts, + angularCorePackageJson, + tsconfig, + packageJson, + indexTs + ]); + + ts.projectSystem.openFilesForSession([indexTs, angularFormsDts], session); + const project = projectService.configuredProjects.get(tsconfig.path)!; + const completionsBefore = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); + assert.isTrue(completionsBefore?.entries.some(c => c.name === "PatternValidator")); + + updateFile(angularFormsDts.path, "export class ValidatorPattern {}"); + const completionsAfter = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); + assert.isFalse(completionsAfter?.entries.some(c => c.name === "PatternValidator")); + assert.isTrue(completionsAfter?.entries.some(c => c.name === "ValidatorPattern")); + }); - it("Responds to manual changes in node_modules", () => { - const { projectService, session, updateFile } = setup([ - angularFormsDts, - angularFormsPackageJson, - angularCoreDts, - angularCorePackageJson, - tsconfig, - packageJson, - indexTs - ]); - - openFilesForSession([indexTs, angularFormsDts], session); - const project = projectService.configuredProjects.get(tsconfig.path)!; - const completionsBefore = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); - assert.isTrue(completionsBefore?.entries.some(c => c.name === "PatternValidator")); - - updateFile(angularFormsDts.path, "export class ValidatorPattern {}"); - const completionsAfter = project.getLanguageService().getCompletionsAtPosition(indexTs.path, 0, { includeCompletionsForModuleExports: true }); - assert.isFalse(completionsAfter?.entries.some(c => c.name === "PatternValidator")); - assert.isTrue(completionsAfter?.entries.some(c => c.name === "ValidatorPattern")); - }); + it("Recovers from an unparseable package.json", () => { + const { projectService, session, host } = setup([ + angularFormsDts, + angularFormsPackageJson, + tsconfig, + { path: packageJson.path, content: "{" }, + indexTs + ]); - it("Recovers from an unparseable package.json", () => { - const { projectService, session, host } = setup([ - angularFormsDts, - angularFormsPackageJson, - tsconfig, - { path: packageJson.path, content: "{" }, - indexTs - ]); + ts.projectSystem.openFilesForSession([indexTs], session); + assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - openFilesForSession([indexTs], session); - assert.isUndefined(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + host.writeFile(packageJson.path, packageJson.content); + assert.ok(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); + }); - host.writeFile(packageJson.path, packageJson.content); - assert.ok(projectService.configuredProjects.get(tsconfig.path)!.getLanguageService().getAutoImportProvider()); - }); + it("Does not create an auto import provider if there are too many dependencies", () => { + const createPackage = (i: number): ts.projectSystem.File[] => ([ + { path: `/node_modules/package${i}/package.json`, content: `{ "name": "package${i}" }` }, + { path: `/node_modules/package${i}/index.d.ts`, content: `` } + ]); - it("Does not create an auto import provider if there are too many dependencies", () => { - const createPackage = (i: number): File[] => ([ - { path: `/node_modules/package${i}/package.json`, content: `{ "name": "package${i}" }` }, - { path: `/node_modules/package${i}/index.d.ts`, content: `` } - ]); + const packages = []; + for (let i = 0; i < 11; i++) { + packages.push(createPackage(i)); + } - const packages = []; - for (let i = 0; i < 11; i++) { - packages.push(createPackage(i)); - } + const dependencies = packages.reduce((hash, p) => ({ ...hash, [JSON.parse(p[0].content).name]: "*" }), {}); + const packageJson: ts.projectSystem.File = { path: "/package.json", content: JSON.stringify(dependencies) }; + const { projectService, session } = setup([ ...ts.flatten(packages), indexTs, tsconfig, packageJson ]); - const dependencies = packages.reduce((hash, p) => ({ ...hash, [JSON.parse(p[0].content).name]: "*" }), {}); - const packageJson: File = { path: "/package.json", content: JSON.stringify(dependencies) }; - const { projectService, session } = setup([ ...flatten(packages), indexTs, tsconfig, packageJson ]); + ts.projectSystem.openFilesForSession([indexTs], session); + const project = projectService.configuredProjects.get(tsconfig.path)!; + assert.isUndefined(project.getPackageJsonAutoImportProvider()); + }); +}); + +describe("unittests:: tsserver:: autoImportProvider - monorepo", () => { + it("Does not create auto import providers upon opening projects for find-all-references", () => { + const files = [ + // node_modules + angularFormsDts, + angularFormsPackageJson, + + // root + { path: tsconfig.path, content: `{ "references": [{ "path": "packages/a" }, { "path": "packages/b" }] }` }, + { path: packageJson.path, content: `{ "private": true }` }, + + // packages/a + { path: "/packages/a/package.json", content: packageJson.content }, + { path: "/packages/a/tsconfig.json", content: `{ "compilerOptions": { "composite": true }, "references": [{ "path": "../b" }] }` }, + { path: "/packages/a/index.ts", content: "import { B } from '../b';" }, + + // packages/b + { path: "/packages/b/package.json", content: packageJson.content }, + { path: "/packages/b/tsconfig.json", content: `{ "compilerOptions": { "composite": true } }` }, + { path: "/packages/b/index.ts", content: `export class B {}` } + ]; + + const { projectService, session, findAllReferences } = setup(files); + + ts.projectSystem.openFilesForSession([files.find(f => f.path === "/packages/b/index.ts")!], session); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 2); // Solution (no files), B + findAllReferences("/packages/b/index.ts", 1, "export class B".length - 1); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 3); // Solution (no files), A, B + + // Project for A is created - ensure it doesn't have an autoImportProvider + assert.isUndefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getLanguageService().getAutoImportProvider()); + }); - openFilesForSession([indexTs], session); - const project = projectService.configuredProjects.get(tsconfig.path)!; - assert.isUndefined(project.getPackageJsonAutoImportProvider()); - }); + it("Does not close when root files are redirects that don't actually exist", () => { + const files = [ + // packages/a + { path: "/packages/a/package.json", content: `{ "dependencies": { "b": "*" } }` }, + { path: "/packages/a/tsconfig.json", content: `{ "compilerOptions": { "composite": true }, "references": [{ "path": "./node_modules/b" }] }` }, + { path: "/packages/a/index.ts", content: "" }, + + // packages/b + { path: "/packages/a/node_modules/b/package.json", content: `{ "types": "dist/index.d.ts" }` }, + { path: "/packages/a/node_modules/b/tsconfig.json", content: `{ "compilerOptions": { "composite": true, "outDir": "dist" } }` }, + { path: "/packages/a/node_modules/b/index.ts", content: `export class B {}` } + ]; + + const { projectService, session } = setup(files); + ts.projectSystem.openFilesForSession([files[2]], session); + assert.isDefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getPackageJsonAutoImportProvider()); + assert.isDefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getPackageJsonAutoImportProvider()); }); - describe("unittests:: tsserver:: autoImportProvider - monorepo", () => { - it("Does not create auto import providers upon opening projects for find-all-references", () => { - const files = [ - // node_modules - angularFormsDts, - angularFormsPackageJson, - - // root - { path: tsconfig.path, content: `{ "references": [{ "path": "packages/a" }, { "path": "packages/b" }] }` }, - { path: packageJson.path, content: `{ "private": true }` }, - - // packages/a - { path: "/packages/a/package.json", content: packageJson.content }, - { path: "/packages/a/tsconfig.json", content: `{ "compilerOptions": { "composite": true }, "references": [{ "path": "../b" }] }` }, - { path: "/packages/a/index.ts", content: "import { B } from '../b';" }, - - // packages/b - { path: "/packages/b/package.json", content: packageJson.content }, - { path: "/packages/b/tsconfig.json", content: `{ "compilerOptions": { "composite": true } }` }, - { path: "/packages/b/index.ts", content: `export class B {}` } - ]; - - const { projectService, session, findAllReferences } = setup(files); - - openFilesForSession([files.find(f => f.path === "/packages/b/index.ts")!], session); - checkNumberOfConfiguredProjects(projectService, 2); // Solution (no files), B - findAllReferences("/packages/b/index.ts", 1, "export class B".length - 1); - checkNumberOfConfiguredProjects(projectService, 3); // Solution (no files), A, B - - // Project for A is created - ensure it doesn't have an autoImportProvider - assert.isUndefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getLanguageService().getAutoImportProvider()); - }); + it("Can use the same document registry bucket key as main program", () => { + for (const option of ts.sourceFileAffectingCompilerOptions) { + assert( + !ts.hasProperty(ts.server.AutoImportProviderProject.compilerOptionsOverrides, option.name), + `'${option.name}' may cause AutoImportProviderProject not to share source files with main program` + ); + } + }); +}); + +function setup(files: ts.projectSystem.File[]) { + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host); + const projectService = session.getProjectService(); + return { + host, + projectService, + session, + updateFile, + findAllReferences + }; - it("Does not close when root files are redirects that don't actually exist", () => { - const files = [ - // packages/a - { path: "/packages/a/package.json", content: `{ "dependencies": { "b": "*" } }` }, - { path: "/packages/a/tsconfig.json", content: `{ "compilerOptions": { "composite": true }, "references": [{ "path": "./node_modules/b" }] }` }, - { path: "/packages/a/index.ts", content: "" }, - - // packages/b - { path: "/packages/a/node_modules/b/package.json", content: `{ "types": "dist/index.d.ts" }` }, - { path: "/packages/a/node_modules/b/tsconfig.json", content: `{ "compilerOptions": { "composite": true, "outDir": "dist" } }` }, - { path: "/packages/a/node_modules/b/index.ts", content: `export class B {}` } - ]; - - const { projectService, session } = setup(files); - openFilesForSession([files[2]], session); - assert.isDefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getPackageJsonAutoImportProvider()); - assert.isDefined(projectService.configuredProjects.get("/packages/a/tsconfig.json")!.getPackageJsonAutoImportProvider()); + function updateFile(path: string, newText: string) { + ts.Debug.assertIsDefined(files.find(f => f.path === path)); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + openFiles: [{ + fileName: path, + content: newText + }] + } }); + } - it("Can use the same document registry bucket key as main program", () => { - for (const option of sourceFileAffectingCompilerOptions) { - assert( - !hasProperty(server.AutoImportProviderProject.compilerOptionsOverrides, option.name), - `'${option.name}' may cause AutoImportProviderProject not to share source files with main program` - ); + function findAllReferences(file: string, line: number, offset: number) { + ts.Debug.assertIsDefined(files.find(f => f.path === file)); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: { + file, + line, + offset } }); - }); - - function setup(files: File[]) { - const host = createServerHost(files); - const session = createSession(host); - const projectService = session.getProjectService(); - return { - host, - projectService, - session, - updateFile, - findAllReferences - }; - - function updateFile(path: string, newText: string) { - Debug.assertIsDefined(files.find(f => f.path === path)); - session.executeCommandSeq({ - command: protocol.CommandTypes.ApplyChangedToOpenFiles, - arguments: { - openFiles: [{ - fileName: path, - content: newText - }] - } - }); - } - - function findAllReferences(file: string, line: number, offset: number) { - Debug.assertIsDefined(files.find(f => f.path === file)); - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: { - file, - line, - offset - } - }); - } } } diff --git a/src/testRunner/unittests/tsserver/auxiliaryProject.ts b/src/testRunner/unittests/tsserver/auxiliaryProject.ts index 4b5f865c19aeb..58742f0e421aa 100644 --- a/src/testRunner/unittests/tsserver/auxiliaryProject.ts +++ b/src/testRunner/unittests/tsserver/auxiliaryProject.ts @@ -1,47 +1,47 @@ -namespace ts.projectSystem { - const aTs: File = { - path: "/a.ts", - content: `import { B } from "./b";` - }; - const bDts: File = { - path: "/b.d.ts", - content: `export declare class B {}` - }; - const bJs: File = { - path: "/b.js", - content: `export class B {}` - }; - describe("unittests:: tsserver:: auxiliaryProject", () => { - it("AuxiliaryProject does not remove scrips from InferredProject", () => { - const host = createServerHost([aTs, bDts, bJs]); - const session = createSession(host); - const projectService = session.getProjectService(); - openFilesForSession([aTs], session); +import * as ts from "../../_namespaces/ts"; - // Open file is in inferred project - checkNumberOfInferredProjects(projectService, 1); - const inferredProject = projectService.inferredProjects[0]; +const aTs: ts.projectSystem.File = { + path: "/a.ts", + content: `import { B } from "./b";` +}; +const bDts: ts.projectSystem.File = { + path: "/b.d.ts", + content: `export declare class B {}` +}; +const bJs: ts.projectSystem.File = { + path: "/b.js", + content: `export class B {}` +}; +describe("unittests:: tsserver:: auxiliaryProject", () => { + it("AuxiliaryProject does not remove scrips from InferredProject", () => { + const host = ts.projectSystem.createServerHost([aTs, bDts, bJs]); + const session = ts.projectSystem.createSession(host); + const projectService = session.getProjectService(); + ts.projectSystem.openFilesForSession([aTs], session); - // getNoDtsResolutionProject will create an AuxiliaryProject with a.ts and b.js - const auxProject = inferredProject.getNoDtsResolutionProject([aTs.path]); - auxProject.updateGraph(); + // Open file is in inferred project + ts.projectSystem.checkNumberOfInferredProjects(projectService, 1); + const inferredProject = projectService.inferredProjects[0]; - // b.js ScriptInfo is now available because it's contained by the AuxiliaryProject. - // The AuxiliaryProject should never be the default project for anything, so - // the ScriptInfo should still report being an orphan, and getting its default - // project should throw. - const bJsScriptInfo = Debug.checkDefined(projectService.getScriptInfo(bJs.path)); - assert(bJsScriptInfo.isOrphan()); - assert(bJsScriptInfo.isContainedByBackgroundProject()); - assert.deepEqual(bJsScriptInfo.containingProjects, [auxProject]); - assert.throws(() => bJsScriptInfo.getDefaultProject()); + // getNoDtsResolutionProject will create an AuxiliaryProject with a.ts and b.js + const auxProject = inferredProject.getNoDtsResolutionProject([aTs.path]); + auxProject.updateGraph(); - // When b.js is opened in the editor, it should be put into an InferredProject - // even though it's still contained by the AuxiliaryProject. - openFilesForSession([bJs], session); - assert(!bJsScriptInfo.isOrphan()); - assert(bJsScriptInfo.isContainedByBackgroundProject()); - assert.equal(bJsScriptInfo.getDefaultProject().projectKind, server.ProjectKind.Inferred); - }); + // b.js ScriptInfo is now available because it's contained by the AuxiliaryProject. + // The AuxiliaryProject should never be the default project for anything, so + // the ScriptInfo should still report being an orphan, and getting its default + // project should throw. + const bJsScriptInfo = ts.Debug.checkDefined(projectService.getScriptInfo(bJs.path)); + assert(bJsScriptInfo.isOrphan()); + assert(bJsScriptInfo.isContainedByBackgroundProject()); + assert.deepEqual(bJsScriptInfo.containingProjects, [auxProject]); + assert.throws(() => bJsScriptInfo.getDefaultProject()); + + // When b.js is opened in the editor, it should be put into an InferredProject + // even though it's still contained by the AuxiliaryProject. + ts.projectSystem.openFilesForSession([bJs], session); + assert(!bJsScriptInfo.isOrphan()); + assert(bJsScriptInfo.isContainedByBackgroundProject()); + assert.equal(bJsScriptInfo.getDefaultProject().projectKind, ts.server.ProjectKind.Inferred); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts index 0f042ba32abee..5834c4592ca19 100644 --- a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts +++ b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts @@ -1,365 +1,366 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { - enum CalledMapsWithSingleArg { - fileExists = "fileExists", - directoryExists = "directoryExists", - getDirectories = "getDirectories", - readFile = "readFile" - } - enum CalledMapsWithFiveArgs { - readDirectory = "readDirectory" - } - type CalledMaps = CalledMapsWithSingleArg | CalledMapsWithFiveArgs; - type CalledWithFiveArgs = [readonly string[], readonly string[], readonly string[], number]; - function createLoggerTrackingHostCalls(host: TestServerHost) { - const calledMaps: Record> & Record> = { - fileExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.fileExists), - directoryExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.directoryExists), - getDirectories: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.getDirectories), - readFile: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.readFile), - readDirectory: setCallsTrackingWithFiveArgFn(CalledMapsWithFiveArgs.readDirectory) +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectSystem CachingFileSystemInformation", () => { + enum CalledMapsWithSingleArg { + fileExists = "fileExists", + directoryExists = "directoryExists", + getDirectories = "getDirectories", + readFile = "readFile" + } + enum CalledMapsWithFiveArgs { + readDirectory = "readDirectory" + } + type CalledMaps = CalledMapsWithSingleArg | CalledMapsWithFiveArgs; + type CalledWithFiveArgs = [readonly string[], readonly string[], readonly string[], number]; + function createLoggerTrackingHostCalls(host: ts.projectSystem.TestServerHost) { + const calledMaps: Record> & Record> = { + fileExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.fileExists), + directoryExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.directoryExists), + getDirectories: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.getDirectories), + readFile: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.readFile), + readDirectory: setCallsTrackingWithFiveArgFn(CalledMapsWithFiveArgs.readDirectory) + }; + + return logCacheAndClear; + + function setCallsTrackingWithSingleArgFn(prop: CalledMapsWithSingleArg) { + const calledMap = ts.createMultiMap(); + const cb = (host as any)[prop].bind(host); + (host as any)[prop] = (f: string) => { + calledMap.add(f, /*value*/ true); + return cb(f); }; - - return logCacheAndClear; - - function setCallsTrackingWithSingleArgFn(prop: CalledMapsWithSingleArg) { - const calledMap = createMultiMap(); - const cb = (host as any)[prop].bind(host); - (host as any)[prop] = (f: string) => { - calledMap.add(f, /*value*/ true); - return cb(f); - }; - return calledMap; - } - - function setCallsTrackingWithFiveArgFn(prop: CalledMapsWithFiveArgs) { - const calledMap = createMultiMap<[U, V, W, X]>(); - const cb = (host as any)[prop].bind(host); - (host as any)[prop] = (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => { - calledMap.add(f, [arg1!, arg2!, arg3!, arg4!]); // TODO: GH#18217 - return cb(f, arg1, arg2, arg3, arg4); - }; - return calledMap; - } - - function logCacheEntry(logger: Logger, callback: CalledMaps) { - const result = arrayFrom<[string, (true | CalledWithFiveArgs)[]], { key: string, count: number }>(calledMaps[callback].entries(), ([key, arr]) => ({ key, count: arr.length })); - logger.info(`${callback}:: ${JSON.stringify(result)}`); - calledMaps[callback].clear(); - } - - function logCacheAndClear(logger: Logger) { - logCacheEntry(logger, CalledMapsWithSingleArg.fileExists); - logCacheEntry(logger, CalledMapsWithSingleArg.directoryExists); - logCacheEntry(logger, CalledMapsWithSingleArg.getDirectories); - logCacheEntry(logger, CalledMapsWithSingleArg.readFile); - logCacheEntry(logger, CalledMapsWithFiveArgs.readDirectory); - } - } - - function logSemanticDiagnostics(projectService: server.ProjectService, project: server.Project, file: File) { - const diags = project.getLanguageService().getSemanticDiagnostics(file.path); - projectService.logger.info(`getSemanticDiagnostics:: ${file.path}:: ${diags.length}`); - diags.forEach(d => projectService.logger.info(formatDiagnostic(d, project))); + return calledMap; } - it("works using legacy resolution logic", () => { - let rootContent = `import {x} from "f1"`; - const root: File = { - path: "/c/d/f0.ts", - content: rootContent - }; - - const imported: File = { - path: "/c/f1.ts", - content: `foo()` + function setCallsTrackingWithFiveArgFn(prop: CalledMapsWithFiveArgs) { + const calledMap = ts.createMultiMap<[U, V, W, X]>(); + const cb = (host as any)[prop].bind(host); + (host as any)[prop] = (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => { + calledMap.add(f, [arg1!, arg2!, arg3!, arg4!]); // TODO: GH#18217 + return cb(f, arg1, arg2, arg3, arg4); }; + return calledMap; + } - const host = createServerHost([root, imported]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); - projectService.openClientFile(root.path); - const project = projectService.inferredProjects[0]; - const rootScriptInfo = project.getRootScriptInfos()[0]; - assert.equal(rootScriptInfo.fileName, root.path); - - // ensure that imported file was found - logSemanticDiagnostics(projectService, project, imported); - - const logCacheAndClear = createLoggerTrackingHostCalls(host); + function logCacheEntry(logger: ts.projectSystem.Logger, callback: CalledMaps) { + const result = ts.arrayFrom<[string, (true | CalledWithFiveArgs)[]], { key: string, count: number }>(calledMaps[callback].entries(), ([key, arr]) => ({ key, count: arr.length })); + logger.info(`${callback}:: ${JSON.stringify(result)}`); + calledMaps[callback].clear(); + } - // trigger synchronization to make sure that import will be fetched from the cache - // ensure file has correct number of errors after edit - editContent(`import {x} from "f1"; + function logCacheAndClear(logger: ts.projectSystem.Logger) { + logCacheEntry(logger, CalledMapsWithSingleArg.fileExists); + logCacheEntry(logger, CalledMapsWithSingleArg.directoryExists); + logCacheEntry(logger, CalledMapsWithSingleArg.getDirectories); + logCacheEntry(logger, CalledMapsWithSingleArg.readFile); + logCacheEntry(logger, CalledMapsWithFiveArgs.readDirectory); + } + } + + function logSemanticDiagnostics(projectService: ts.server.ProjectService, project: ts.server.Project, file: ts.projectSystem.File) { + const diags = project.getLanguageService().getSemanticDiagnostics(file.path); + projectService.logger.info(`getSemanticDiagnostics:: ${file.path}:: ${diags.length}`); + diags.forEach(d => projectService.logger.info(ts.formatDiagnostic(d, project))); + } + + it("works using legacy resolution logic", () => { + let rootContent = `import {x} from "f1"`; + const root: ts.projectSystem.File = { + path: "/c/d/f0.ts", + content: rootContent + }; + + const imported: ts.projectSystem.File = { + path: "/c/f1.ts", + content: `foo()` + }; + + const host = ts.projectSystem.createServerHost([root, imported]); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true }); + projectService.openClientFile(root.path); + const project = projectService.inferredProjects[0]; + const rootScriptInfo = project.getRootScriptInfos()[0]; + assert.equal(rootScriptInfo.fileName, root.path); + + // ensure that imported file was found + logSemanticDiagnostics(projectService, project, imported); + + const logCacheAndClear = createLoggerTrackingHostCalls(host); + + // trigger synchronization to make sure that import will be fetched from the cache + // ensure file has correct number of errors after edit + editContent(`import {x} from "f1"; var x: string = 1;`); - logSemanticDiagnostics(projectService, project, imported); - logCacheAndClear(projectService.logger); + logSemanticDiagnostics(projectService, project, imported); + logCacheAndClear(projectService.logger); + // trigger synchronization to make sure that the host will try to find 'f2' module on disk + editContent(`import {x} from "f2"`); + try { // trigger synchronization to make sure that the host will try to find 'f2' module on disk - editContent(`import {x} from "f2"`); - try { - // trigger synchronization to make sure that the host will try to find 'f2' module on disk - logSemanticDiagnostics(projectService, project, imported); - } - catch (e) { - projectService.logger.info(e.message); - } - logCacheAndClear(projectService.logger); - - editContent(`import {x} from "f1"`); logSemanticDiagnostics(projectService, project, imported); - logCacheAndClear(projectService.logger); + } + catch (e) { + projectService.logger.info(e.message); + } + logCacheAndClear(projectService.logger); - // setting compiler options discards module resolution cache - projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true, target: ScriptTarget.ES5 }); - logSemanticDiagnostics(projectService, project, imported); - logCacheAndClear(projectService.logger); - baselineTsserverLogs("cachingFileSystemInformation", "works using legacy resolution logic", projectService); + editContent(`import {x} from "f1"`); + logSemanticDiagnostics(projectService, project, imported); + logCacheAndClear(projectService.logger); - function editContent(newContent: string) { - rootScriptInfo.editContent(0, rootContent.length, newContent); - rootContent = newContent; - } - }); + // setting compiler options discards module resolution cache + projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true, target: ts.ScriptTarget.ES5 }); + logSemanticDiagnostics(projectService, project, imported); + logCacheAndClear(projectService.logger); + ts.projectSystem.baselineTsserverLogs("cachingFileSystemInformation", "works using legacy resolution logic", projectService); - it("loads missing files from disk", () => { - const root: File = { - path: "/c/foo.ts", - content: `import {y} from "bar"` - }; - - const imported: File = { - path: "/c/bar.d.ts", - content: `export var y = 1` - }; + function editContent(newContent: string) { + rootScriptInfo.editContent(0, rootContent.length, newContent); + rootContent = newContent; + } + }); - const host = createServerHost([root]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.setCompilerOptionsForInferredProjects({ module: ModuleKind.AMD, noLib: true }); - const logCacheAndClear = createLoggerTrackingHostCalls(host); - projectService.openClientFile(root.path); - const project = projectService.inferredProjects[0]; - const rootScriptInfo = project.getRootScriptInfos()[0]; - assert.equal(rootScriptInfo.fileName, root.path); - - logSemanticDiagnostics(projectService, project, root); - logCacheAndClear(projectService.logger); - - host.writeFile(imported.path, imported.content); - host.runQueuedTimeoutCallbacks(); - logSemanticDiagnostics(projectService, project, root); - logCacheAndClear(projectService.logger); - baselineTsserverLogs("cachingFileSystemInformation", "loads missing files from disk", projectService); - }); + it("loads missing files from disk", () => { + const root: ts.projectSystem.File = { + path: "/c/foo.ts", + content: `import {y} from "bar"` + }; + + const imported: ts.projectSystem.File = { + path: "/c/bar.d.ts", + content: `export var y = 1` + }; + + const host = ts.projectSystem.createServerHost([root]); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true }); + const logCacheAndClear = createLoggerTrackingHostCalls(host); + projectService.openClientFile(root.path); + const project = projectService.inferredProjects[0]; + const rootScriptInfo = project.getRootScriptInfos()[0]; + assert.equal(rootScriptInfo.fileName, root.path); + + logSemanticDiagnostics(projectService, project, root); + logCacheAndClear(projectService.logger); + + host.writeFile(imported.path, imported.content); + host.runQueuedTimeoutCallbacks(); + logSemanticDiagnostics(projectService, project, root); + logCacheAndClear(projectService.logger); + ts.projectSystem.baselineTsserverLogs("cachingFileSystemInformation", "loads missing files from disk", projectService); + }); - it("when calling goto definition of module", () => { - const clientFile: File = { - path: "/a/b/controllers/vessels/client.ts", - content: ` + it("when calling goto definition of module", () => { + const clientFile: ts.projectSystem.File = { + path: "/a/b/controllers/vessels/client.ts", + content: ` import { Vessel } from '~/models/vessel'; const v = new Vessel(); ` - }; - const anotherModuleFile: File = { - path: "/a/b/utils/db.ts", - content: "export class Bookshelf { }" - }; - const moduleFile: File = { - path: "/a/b/models/vessel.ts", - content: ` + }; + const anotherModuleFile: ts.projectSystem.File = { + path: "/a/b/utils/db.ts", + content: "export class Bookshelf { }" + }; + const moduleFile: ts.projectSystem.File = { + path: "/a/b/models/vessel.ts", + content: ` import { Bookshelf } from '~/utils/db'; export class Vessel extends Bookshelf {} ` - }; - const tsconfigFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - target: "es6", - module: "es6", - baseUrl: "./", // all paths are relative to the baseUrl - paths: { - "~/*": ["*"] // resolve any `~/foo/bar` to `/foo/bar` - } - }, - exclude: [ - "api", - "build", - "node_modules", - "public", - "seeds", - "sql_updates", - "tests.build" - ] - }) - }; - const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile]; - const host = createServerHost(projectFiles); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([clientFile], session); - const logCacheAndClear = createLoggerTrackingHostCalls(host); - - // Get definitions shouldnt make host requests - const getDefinitionRequest = makeSessionRequest(protocol.CommandTypes.Definition, { - file: clientFile.path, - position: clientFile.content.indexOf("/vessel") + 1, - line: undefined!, // TODO: GH#18217 - offset: undefined! // TODO: GH#18217 - }); - session.executeCommand(getDefinitionRequest); - logCacheAndClear(session.logger); - - // Open the file should call only file exists on module directory and use cached value for parental directory - openFilesForSession([moduleFile], session); - logCacheAndClear(session.logger); - - baselineTsserverLogs("cachingFileSystemInformation", "when calling goto definition of module", session); + }; + const tsconfigFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + target: "es6", + module: "es6", + baseUrl: "./", // all paths are relative to the baseUrl + paths: { + "~/*": ["*"] // resolve any `~/foo/bar` to `/foo/bar` + } + }, + exclude: [ + "api", + "build", + "node_modules", + "public", + "seeds", + "sql_updates", + "tests.build" + ] + }) + }; + const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile]; + const host = ts.projectSystem.createServerHost(projectFiles); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([clientFile], session); + const logCacheAndClear = createLoggerTrackingHostCalls(host); + + // Get definitions shouldnt make host requests + const getDefinitionRequest = ts.projectSystem.makeSessionRequest(ts.projectSystem.protocol.CommandTypes.Definition, { + file: clientFile.path, + position: clientFile.content.indexOf("/vessel") + 1, + line: undefined!, // TODO: GH#18217 + offset: undefined! // TODO: GH#18217 }); + session.executeCommand(getDefinitionRequest); + logCacheAndClear(session.logger); - describe("WatchDirectories for config file with", () => { - function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { - it(`watchDirectories for config file with case ${useCaseSensitiveFileNames ? "" : "in"}sensitive file system`, () => { - const frontendDir = "/Users/someuser/work/applications/frontend"; - const file1: File = { - path: `${frontendDir}/src/app/utils/Analytic.ts`, - content: "export class SomeClass { };" - }; - const file2: File = { - path: `${frontendDir}/src/app/redux/configureStore.ts`, - content: "export class configureStore { }" - }; - const file3: File = { - path: `${frontendDir}/src/app/utils/Cookie.ts`, - content: "export class Cookie { }" - }; - const es2016LibFile: File = { - path: "/a/lib/lib.es2016.full.d.ts", - content: libFile.content - }; - const typeRoots = ["types", "node_modules/@types"]; - const types = ["node", "jest"]; - const tsconfigFile: File = { - path: `${frontendDir}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - strict: true, - strictNullChecks: true, - target: "es2016", - module: "commonjs", - moduleResolution: "node", - sourceMap: true, - noEmitOnError: true, - experimentalDecorators: true, - emitDecoratorMetadata: true, - types, - noUnusedLocals: true, - outDir: "./compiled", - typeRoots, - baseUrl: ".", - paths: { - "*": [ - "types/*" - ] - } - }, - include: [ - "src/**/*" - ], - exclude: [ - "node_modules", - "compiled" - ] - }) - }; - const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; - const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); - - const logCacheAndClear = createLoggerTrackingHostCalls(host); - - // Create file cookie.ts - host.writeFile(file3.path, file3.content); - host.runQueuedTimeoutCallbacks(); - logCacheAndClear(projectService.logger); - - projectService.openClientFile(file3.path); - logCacheAndClear(projectService.logger); - baselineTsserverLogs("cachingFileSystemInformation", `watchDirectories for config file with case ${useCaseSensitiveFileNames ? "" : "in"}sensitive file system`, projectService); - }); - } - verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); - verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); - }); + // Open the file should call only file exists on module directory and use cached value for parental directory + ts.projectSystem.openFilesForSession([moduleFile], session); + logCacheAndClear(session.logger); + + ts.projectSystem.baselineTsserverLogs("cachingFileSystemInformation", "when calling goto definition of module", session); + }); - describe("Subfolder invalidations correctly include parent folder failed lookup locations", () => { - function runFailedLookupTest(resolution: "Node" | "Classic") { - const projectLocation = "/proj"; - const file1: File = { - path: `${projectLocation}/foo/boo/app.ts`, - content: `import * as debug from "debug"` + describe("WatchDirectories for config file with", () => { + function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { + it(`watchDirectories for config file with case ${useCaseSensitiveFileNames ? "" : "in"}sensitive file system`, () => { + const frontendDir = "/Users/someuser/work/applications/frontend"; + const file1: ts.projectSystem.File = { + path: `${frontendDir}/src/app/utils/Analytic.ts`, + content: "export class SomeClass { };" }; - const file2: File = { - path: `${projectLocation}/foo/boo/moo/app.ts`, - content: `import * as debug from "debug"` + const file2: ts.projectSystem.File = { + path: `${frontendDir}/src/app/redux/configureStore.ts`, + content: "export class configureStore { }" }; - const tsconfig: File = { - path: `${projectLocation}/tsconfig.json`, + const file3: ts.projectSystem.File = { + path: `${frontendDir}/src/app/utils/Cookie.ts`, + content: "export class Cookie { }" + }; + const es2016LibFile: ts.projectSystem.File = { + path: "/a/lib/lib.es2016.full.d.ts", + content: ts.projectSystem.libFile.content + }; + const typeRoots = ["types", "node_modules/@types"]; + const types = ["node", "jest"]; + const tsconfigFile: ts.projectSystem.File = { + path: `${frontendDir}/tsconfig.json`, content: JSON.stringify({ - files: ["foo/boo/app.ts", "foo/boo/moo/app.ts"], - moduleResolution: resolution + compilerOptions: { + strict: true, + strictNullChecks: true, + target: "es2016", + module: "commonjs", + moduleResolution: "node", + sourceMap: true, + noEmitOnError: true, + experimentalDecorators: true, + emitDecoratorMetadata: true, + types, + noUnusedLocals: true, + outDir: "./compiled", + typeRoots, + baseUrl: ".", + paths: { + "*": [ + "types/*" + ] + } + }, + include: [ + "src/**/*" + ], + exclude: [ + "node_modules", + "compiled" + ] }) }; + const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; + const host = ts.projectSystem.createServerHost(projectFiles, { useCaseSensitiveFileNames }); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + projectService.openClientFile(file1.path); - const files = [file1, file2, tsconfig, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(file1.path); + const logCacheAndClear = createLoggerTrackingHostCalls(host); - const project = service.configuredProjects.get(tsconfig.path)!; - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); + // Create file cookie.ts + host.writeFile(file3.path, file3.content); + host.runQueuedTimeoutCallbacks(); + logCacheAndClear(projectService.logger); - const debugTypesFile: File = { - path: `${projectLocation}/node_modules/debug/index.d.ts`, - content: "export {}" - }; - files.push(debugTypesFile); - host.writeFile(debugTypesFile.path, debugTypesFile.content); - host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions - host.runQueuedTimeoutCallbacks(); // Actual update - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), []); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), []); - } - - it("Includes the parent folder FLLs in node module resolution mode", () => { - runFailedLookupTest("Node"); - }); - it("Includes the parent folder FLLs in classic module resolution mode", () => { - runFailedLookupTest("Classic"); + projectService.openClientFile(file3.path); + logCacheAndClear(projectService.logger); + ts.projectSystem.baselineTsserverLogs("cachingFileSystemInformation", `watchDirectories for config file with case ${useCaseSensitiveFileNames ? "" : "in"}sensitive file system`, projectService); }); + } + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); + }); + + describe("Subfolder invalidations correctly include parent folder failed lookup locations", () => { + function runFailedLookupTest(resolution: "Node" | "Classic") { + const projectLocation = "/proj"; + const file1: ts.projectSystem.File = { + path: `${projectLocation}/foo/boo/app.ts`, + content: `import * as debug from "debug"` + }; + const file2: ts.projectSystem.File = { + path: `${projectLocation}/foo/boo/moo/app.ts`, + content: `import * as debug from "debug"` + }; + const tsconfig: ts.projectSystem.File = { + path: `${projectLocation}/tsconfig.json`, + content: JSON.stringify({ + files: ["foo/boo/app.ts", "foo/boo/moo/app.ts"], + moduleResolution: resolution + }) + }; + + const files = [file1, file2, tsconfig, ts.projectSystem.libFile]; + const host = ts.projectSystem.createServerHost(files); + const service = ts.projectSystem.createProjectService(host); + service.openClientFile(file1.path); + + const project = service.configuredProjects.get(tsconfig.path)!; + ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); + + const debugTypesFile: ts.projectSystem.File = { + path: `${projectLocation}/node_modules/debug/index.d.ts`, + content: "export {}" + }; + files.push(debugTypesFile); + host.writeFile(debugTypesFile.path, debugTypesFile.content); + host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions + host.runQueuedTimeoutCallbacks(); // Actual update + ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file1.path).map(diag => diag.messageText), []); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(file2.path).map(diag => diag.messageText), []); + } + + it("Includes the parent folder FLLs in node module resolution mode", () => { + runFailedLookupTest("Node"); }); + it("Includes the parent folder FLLs in classic module resolution mode", () => { + runFailedLookupTest("Classic"); + }); + }); - describe("Verify npm install in directory with tsconfig file works when", () => { - function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) { - const root = "/user/username/rootfolder/otherfolder"; - const getRootedFileOrFolder = (fileOrFolder: File) => { - fileOrFolder.path = root + fileOrFolder.path; - return fileOrFolder; - }; - const app: File = getRootedFileOrFolder({ - path: "/a/b/app.ts", - content: "import _ from 'lodash';" - }); - const tsconfigJson: File = getRootedFileOrFolder({ - path: "/a/b/tsconfig.json", - content: '{ "compilerOptions": { } }' - }); - const packageJson: File = getRootedFileOrFolder({ - path: "/a/b/package.json", - content: ` + describe("Verify npm install in directory with tsconfig file works when", () => { + function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) { + const root = "/user/username/rootfolder/otherfolder"; + const getRootedFileOrFolder = (fileOrFolder: ts.projectSystem.File) => { + fileOrFolder.path = root + fileOrFolder.path; + return fileOrFolder; + }; + const app: ts.projectSystem.File = getRootedFileOrFolder({ + path: "/a/b/app.ts", + content: "import _ from 'lodash';" + }); + const tsconfigJson: ts.projectSystem.File = getRootedFileOrFolder({ + path: "/a/b/tsconfig.json", + content: '{ "compilerOptions": { } }' + }); + const packageJson: ts.projectSystem.File = getRootedFileOrFolder({ + path: "/a/b/package.json", + content: ` { "name": "test", "version": "1.0.0", @@ -381,196 +382,195 @@ namespace ts.projectSystem { "license": "ISC" } ` - }); - const host = createServerHost([app, libFile, tsconfigJson, packageJson]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.setHostConfiguration({ preferences: { includePackageJsonAutoImports: "off" } }); - projectService.openClientFile(app.path); - - let npmInstallComplete = false; - - // Simulate npm install - const filesAndFoldersToAdd: File[] = [ - { path: "/a/b/node_modules" }, - { path: "/a/b/node_modules/.staging/@types" }, - { path: "/a/b/node_modules/.staging/lodash-b0733faa" }, - { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61" }, - { path: "/a/b/node_modules/.staging/typescript-8493ea5d" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/package.json", content: "{\n \"name\": \"symbol-observable\",\n \"version\": \"1.0.4\",\n \"description\": \"Symbol.observable ponyfill\",\n \"license\": \"MIT\",\n \"repository\": \"blesh/symbol-observable\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"engines\": {\n \"node\": \">=0.10.0\"\n },\n \"scripts\": {\n \"test\": \"npm run build && mocha && tsc ./ts-test/test.ts && node ./ts-test/test.js && check-es3-syntax -p lib/ --kill\",\n \"build\": \"babel es --out-dir lib\",\n \"prepublish\": \"npm test\"\n },\n \"files\": [\n \"" }, - { path: "/a/b/node_modules/.staging/lodash-b0733faa/package.json", content: "{\n \"name\": \"lodash\",\n \"version\": \"4.17.4\",\n \"description\": \"Lodash modular utilities.\",\n \"keywords\": \"modules, stdlib, util\",\n \"homepage\": \"https://lodash.com/\",\n \"repository\": \"lodash/lodash\",\n \"icon\": \"https://lodash.com/icon.svg\",\n \"license\": \"MIT\",\n \"main\": \"lodash.js\",\n \"author\": \"John-David Dalton (http://allyoucanleet.com/)\",\n \"contributors\": [\n \"John-David Dalton (http://allyoucanleet.com/)\",\n \"Mathias Bynens \",\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"license\": \"Apache-2.0\",\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"eslint\": \"^4.4.2\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"typings\": \"Rx.d.ts\",\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n }\n}" }, - { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json", content: "{\n \"name\": \"typescript\",\n \"author\": \"Microsoft Corp.\",\n \"homepage\": \"http://typescriptlang.org/\",\n \"version\": \"2.4.2\",\n \"license\": \"Apache-2.0\",\n \"description\": \"TypeScript is a language for application scale JavaScript development\",\n \"keywords\": [\n \"TypeScript\",\n \"Microsoft\",\n \"compiler\",\n \"language\",\n \"javascript\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/Microsoft/TypeScript/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Microsoft/TypeScript.git\"\n },\n \"main\": \"./lib/typescript.js\",\n \"typings\": \"./lib/typescript.d.ts\",\n \"bin\": {\n \"tsc\": \"./bin/tsc\",\n \"tsserver\": \"./bin/tsserver\"\n },\n \"engines\": {\n \"node\": \">=4.2.0\"\n },\n \"devDependencies\": {\n \"@types/browserify\": \"latest\",\n \"@types/chai\": \"latest\",\n \"@types/convert-source-map\": \"latest\",\n \"@types/del\": \"latest\",\n \"@types/glob\": \"latest\",\n \"@types/gulp\": \"latest\",\n \"@types/gulp-concat\": \"latest\",\n \"@types/gulp-help\": \"latest\",\n \"@types/gulp-newer\": \"latest\",\n \"@types/gulp-sourcemaps\": \"latest\",\n \"@types/merge2\": \"latest\",\n \"@types/minimatch\": \"latest\",\n \"@types/minimist\": \"latest\",\n \"@types/mkdirp\": \"latest\",\n \"@types/mocha\": \"latest\",\n \"@types/node\": \"latest\",\n \"@types/q\": \"latest\",\n \"@types/run-sequence\": \"latest\",\n \"@types/through2\": \"latest\",\n \"browserify\": \"latest\",\n \"chai\": \"latest\",\n \"convert-source-map\": \"latest\",\n \"del\": \"latest\",\n \"gulp\": \"latest\",\n \"gulp-clone\": \"latest\",\n \"gulp-concat\": \"latest\",\n \"gulp-help\": \"latest\",\n \"gulp-insert\": \"latest\",\n \"gulp-newer\": \"latest\",\n \"gulp-sourcemaps\": \"latest\",\n \"gulp-typescript\": \"latest\",\n \"into-stream\": \"latest\",\n \"istanbul\": \"latest\",\n \"jake\": \"latest\",\n \"merge2\": \"latest\",\n \"minimist\": \"latest\",\n \"mkdirp\": \"latest\",\n \"mocha\": \"latest\",\n \"mocha-fivemat-progress-reporter\": \"latest\",\n \"q\": \"latest\",\n \"run-sequence\": \"latest\",\n \"sorcery\": \"latest\",\n \"through2\": \"latest\",\n \"travis-fold\": \"latest\",\n \"ts-node\": \"latest\",\n \"eslint\": \"5.16.0\",\n \"typescript\": \"^2.4\"\n },\n \"scripts\": {\n \"pretest\": \"jake tests\",\n \"test\": \"jake runtests-parallel\",\n \"build\": \"npm run build:compiler && npm run build:tests\",\n \"build:compiler\": \"jake local\",\n \"build:tests\": \"jake tests\",\n \"start\": \"node lib/tsc\",\n \"clean\": \"jake clean\",\n \"gulp\": \"gulp\",\n \"jake\": \"jake\",\n \"lint\": \"jake lint\",\n \"setup-hooks\": \"node scripts/link-hooks.js\"\n },\n \"browser\": {\n \"buffer\": false,\n \"fs\": false,\n \"os\": false,\n \"path\": false\n }\n}" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.js", content: "module.exports = require('./lib/index');\n" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.d.ts", content: "declare const observableSymbol: symbol;\nexport default observableSymbol;\n" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib" }, - { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib/index.js", content: "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _ponyfill = require('./ponyfill');\n\nvar _ponyfill2 = _interopRequireDefault(_ponyfill);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar root; /* global window */\n\n\nif (typeof self !== 'undefined') {\n root = self;\n} else if (typeof window !== 'undefined') {\n root = window;\n} else if (typeof global !== 'undefined') {\n root = global;\n} else if (typeof module !== 'undefined') {\n root = module;\n} else {\n root = Function('return this')();\n}\n\nvar result = (0, _ponyfill2['default'])(root);\nexports['default'] = result;" }, - ].map(getRootedFileOrFolder); - verifyAfterPartialOrCompleteNpmInstall(2); - - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/.staging/typescript-8493ea5d/lib" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/add/operator" }, - { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/package.json", content: "{\n \"name\": \"@types/lodash\",\n \"version\": \"4.14.74\",\n \"description\": \"TypeScript definitions for Lo-Dash\",\n \"license\": \"MIT\",\n \"contributors\": [\n {\n \"name\": \"Brian Zengel\",\n \"url\": \"https://github.com/bczengel\"\n },\n {\n \"name\": \"Ilya Mochalov\",\n \"url\": \"https://github.com/chrootsu\"\n },\n {\n \"name\": \"Stepan Mikhaylyuk\",\n \"url\": \"https://github.com/stepancar\"\n },\n {\n \"name\": \"Eric L Anderson\",\n \"url\": \"https://github.com/ericanderson\"\n },\n {\n \"name\": \"AJ Richardson\",\n \"url\": \"https://github.com/aj-r\"\n },\n {\n \"name\": \"Junyoung Clare Jang\",\n \"url\": \"https://github.com/ailrun\"\n }\n ],\n \"main\": \"\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://www.github.com/DefinitelyTyped/DefinitelyTyped.git\"\n },\n \"scripts\": {},\n \"dependencies\": {},\n \"typesPublisherContentHash\": \"12af578ffaf8d86d2df37e591857906a86b983fa9258414326544a0fe6af0de8\",\n \"typeScriptVersion\": \"2.2\"\n}" }, - { path: "/a/b/node_modules/.staging/lodash-b0733faa/index.js", content: "module.exports = require('./lodash');" }, - { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594", content: "" } - ].map(getRootedFileOrFolder)); - // Since we added/removed in .staging no timeout - verifyAfterPartialOrCompleteNpmInstall(0); - - // Remove file "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594" - host.deleteFile(last(filesAndFoldersToAdd).path); - filesAndFoldersToAdd.length--; - verifyAfterPartialOrCompleteNpmInstall(0); - - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/.staging/rxjs-22375c61/bundles" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/operator" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/add/observable/dom" }, - { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/index.d.ts", content: "\n// Stub for lodash\nexport = _;\nexport as namespace _;\ndeclare var _: _.LoDashStatic;\ndeclare namespace _ {\n interface LoDashStatic {\n someProp: string;\n }\n class SomeClass {\n someMethod(): void;\n }\n}" } - ].map(getRootedFileOrFolder)); - verifyAfterPartialOrCompleteNpmInstall(0); - - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/scheduler" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/util" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/symbol" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/testing" }, - { path: "/a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041", content: "{\n \"_args\": [\n [\n {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\"\n ]\n ],\n \"_from\": \"rxjs@>=5.4.2 <6.0.0\",\n \"_id\": \"rxjs@5.4.3\",\n \"_inCache\": true,\n \"_location\": \"/rxjs\",\n \"_nodeVersion\": \"7.7.2\",\n \"_npmOperationalInternal\": {\n \"host\": \"s3://npm-registry-packages\",\n \"tmp\": \"tmp/rxjs-5.4.3.tgz_1502407898166_0.6800217325799167\"\n },\n \"_npmUser\": {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"_npmVersion\": \"5.3.0\",\n \"_phantomChildren\": {},\n \"_requested\": {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"_requiredBy\": [\n \"/\"\n ],\n \"_resolved\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\",\n \"_shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"_shrinkwrap\": null,\n \"_spec\": \"rxjs@^5.4.2\",\n \"_where\": \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"config\": {\n \"commitizen\": {\n \"path\": \"cz-conventional-changelog\"\n }\n },\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n },\n \"description\": \"Reactive Extensions for modern JavaScript\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"eslint\": \"^5.16.0\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"directories\": {},\n \"dist\": {\n \"integrity\": \"sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==\",\n \"shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"tarball\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"keywords\": [\n \"Rx\",\n \"RxJS\",\n \"ReactiveX\",\n \"ReactiveExtensions\",\n \"Streams\",\n \"Observables\",\n \"Observable\",\n \"Stream\",\n \"ES6\",\n \"ES2015\"\n ],\n \"license\": \"Apache-2.0\",\n \"lint-staged\": {\n \"*.@(js)\": [\n \"eslint --fix\",\n \"git add\"\n ],\n \"*.@(ts)\": [\n \"eslint -c .eslintrc --ext .ts . --fix\",\n \"git add\"\n ]\n },\n \"main\": \"Rx.js\",\n \"maintainers\": [\n {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n }\n ],\n \"name\": \"rxjs\",\n \"optionalDependencies\": {},\n \"readme\": \"ERROR: No README data found!\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+ssh://git@github.com/ReactiveX/RxJS.git\"\n },\n \"scripts-info\": {\n \"info\": \"List available script\",\n \"build_all\": \"Build all packages (ES6, CJS, UMD) and generate packages\",\n \"build_cjs\": \"Build CJS package with clean up existing build, copy source into dist\",\n \"build_es6\": \"Build ES6 package with clean up existing build, copy source into dist\",\n \"build_closure_core\": \"Minify Global core build using closure compiler\",\n \"build_global\": \"Build Global package, then minify build\",\n \"build_perf\": \"Build CJS & Global build, run macro performance test\",\n \"build_test\": \"Build CJS package & test spec, execute mocha test runner\",\n \"build_cover\": \"Run lint to current code, build CJS & test spec, execute test coverage\",\n \"build_docs\": \"Build ES6 & global package, create documentation using it\",\n \"build_spec\": \"Build test specs\",\n \"check_circular_dependencies\": \"Check codebase has circular dependencies\",\n \"clean_spec\": \"Clean up existing test spec build output\",\n \"clean_dist_cjs\": \"Clean up existing CJS package output\",\n \"clean_dist_es6\": \"Clean up existing ES6 package output\",\n \"clean_dist_global\": \"Clean up existing Global package output\",\n \"commit\": \"Run git commit wizard\",\n \"compile_dist_cjs\": \"Compile codebase into CJS module\",\n \"compile_module_es6\": \"Compile codebase into ES6\",\n \"cover\": \"Execute test coverage\",\n \"lint_perf\": \"Run lint against performance test suite\",\n \"lint_spec\": \"Run lint against test spec\",\n \"lint_src\": \"Run lint against source\",\n \"lint\": \"Run lint against everything\",\n \"perf\": \"Run macro performance benchmark\",\n \"perf_micro\": \"Run micro performance benchmark\",\n \"test_mocha\": \"Execute mocha test runner against existing test spec build\",\n \"test_browser\": \"Execute mocha test runner on browser against existing test spec build\",\n \"test\": \"Clean up existing test spec build, build test spec and execute mocha test runner\",\n \"tests2png\": \"Generate marble diagram image from test spec\",\n \"watch\": \"Watch codebase, trigger compile when source code changes\"\n },\n \"typings\": \"Rx.d.ts\",\n \"version\": \"5.4.3\"\n}\n" } - ].map(getRootedFileOrFolder)); - verifyAfterPartialOrCompleteNpmInstall(0); - - // remove /a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041 - host.deleteFile(last(filesAndFoldersToAdd).path); - filesAndFoldersToAdd.length--; - // and add few more folders/files - filesAndFoldersToAdd.push(...[ - { path: "/a/b/node_modules/symbol-observable" }, - { path: "/a/b/node_modules/@types" }, - { path: "/a/b/node_modules/@types/lodash" }, - { path: "/a/b/node_modules/lodash" }, - { path: "/a/b/node_modules/rxjs" }, - { path: "/a/b/node_modules/typescript" }, - { path: "/a/b/node_modules/.bin" } - ].map(getRootedFileOrFolder)); - // From the type root update - verifyAfterPartialOrCompleteNpmInstall(2); - - forEach(filesAndFoldersToAdd, f => { - f.path = f.path - .replace("/a/b/node_modules/.staging", "/a/b/node_modules") - .replace(/[\-\.][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w]/g, ""); - }); - - host.deleteFolder(root + "/a/b/node_modules/.staging", /*recursive*/ true); - // npm installation complete, timeout after reload fs - npmInstallComplete = true; - verifyAfterPartialOrCompleteNpmInstall(2); - - baselineTsserverLogs( - "cachingFileSystemInformation", - `npm install works when ${timeoutDuringPartialInstallation ? "timeout occurs inbetween installation" : "timeout occurs after installation"}`, - projectService - ); - - function verifyAfterPartialOrCompleteNpmInstall(timeoutQueueLengthWhenRunningTimeouts: number) { - filesAndFoldersToAdd.forEach(f => host.ensureFileOrFolder(f)); - if (npmInstallComplete || timeoutDuringPartialInstallation) { - if (timeoutQueueLengthWhenRunningTimeouts) { - // Expected project update - host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts + 1); // Scheduled invalidation of resolutions - host.runQueuedTimeoutCallbacks(); // Actual update - } - else { - host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts); - } + }); + const host = ts.projectSystem.createServerHost([app, ts.projectSystem.libFile, tsconfigJson, packageJson]); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + projectService.setHostConfiguration({ preferences: { includePackageJsonAutoImports: "off" } }); + projectService.openClientFile(app.path); + + let npmInstallComplete = false; + + // Simulate npm install + const filesAndFoldersToAdd: ts.projectSystem.File[] = [ + { path: "/a/b/node_modules" }, + { path: "/a/b/node_modules/.staging/@types" }, + { path: "/a/b/node_modules/.staging/lodash-b0733faa" }, + { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61" }, + { path: "/a/b/node_modules/.staging/typescript-8493ea5d" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/package.json", content: "{\n \"name\": \"symbol-observable\",\n \"version\": \"1.0.4\",\n \"description\": \"Symbol.observable ponyfill\",\n \"license\": \"MIT\",\n \"repository\": \"blesh/symbol-observable\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"engines\": {\n \"node\": \">=0.10.0\"\n },\n \"scripts\": {\n \"test\": \"npm run build && mocha && tsc ./ts-test/test.ts && node ./ts-test/test.js && check-es3-syntax -p lib/ --kill\",\n \"build\": \"babel es --out-dir lib\",\n \"prepublish\": \"npm test\"\n },\n \"files\": [\n \"" }, + { path: "/a/b/node_modules/.staging/lodash-b0733faa/package.json", content: "{\n \"name\": \"lodash\",\n \"version\": \"4.17.4\",\n \"description\": \"Lodash modular utilities.\",\n \"keywords\": \"modules, stdlib, util\",\n \"homepage\": \"https://lodash.com/\",\n \"repository\": \"lodash/lodash\",\n \"icon\": \"https://lodash.com/icon.svg\",\n \"license\": \"MIT\",\n \"main\": \"lodash.js\",\n \"author\": \"John-David Dalton (http://allyoucanleet.com/)\",\n \"contributors\": [\n \"John-David Dalton (http://allyoucanleet.com/)\",\n \"Mathias Bynens \",\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"license\": \"Apache-2.0\",\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"eslint\": \"^4.4.2\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"typings\": \"Rx.d.ts\",\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n }\n}" }, + { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json", content: "{\n \"name\": \"typescript\",\n \"author\": \"Microsoft Corp.\",\n \"homepage\": \"http://typescriptlang.org/\",\n \"version\": \"2.4.2\",\n \"license\": \"Apache-2.0\",\n \"description\": \"TypeScript is a language for application scale JavaScript development\",\n \"keywords\": [\n \"TypeScript\",\n \"Microsoft\",\n \"compiler\",\n \"language\",\n \"javascript\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/Microsoft/TypeScript/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Microsoft/TypeScript.git\"\n },\n \"main\": \"./lib/typescript.js\",\n \"typings\": \"./lib/typescript.d.ts\",\n \"bin\": {\n \"tsc\": \"./bin/tsc\",\n \"tsserver\": \"./bin/tsserver\"\n },\n \"engines\": {\n \"node\": \">=4.2.0\"\n },\n \"devDependencies\": {\n \"@types/browserify\": \"latest\",\n \"@types/chai\": \"latest\",\n \"@types/convert-source-map\": \"latest\",\n \"@types/del\": \"latest\",\n \"@types/glob\": \"latest\",\n \"@types/gulp\": \"latest\",\n \"@types/gulp-concat\": \"latest\",\n \"@types/gulp-help\": \"latest\",\n \"@types/gulp-newer\": \"latest\",\n \"@types/gulp-sourcemaps\": \"latest\",\n \"@types/merge2\": \"latest\",\n \"@types/minimatch\": \"latest\",\n \"@types/minimist\": \"latest\",\n \"@types/mkdirp\": \"latest\",\n \"@types/mocha\": \"latest\",\n \"@types/node\": \"latest\",\n \"@types/q\": \"latest\",\n \"@types/run-sequence\": \"latest\",\n \"@types/through2\": \"latest\",\n \"browserify\": \"latest\",\n \"chai\": \"latest\",\n \"convert-source-map\": \"latest\",\n \"del\": \"latest\",\n \"gulp\": \"latest\",\n \"gulp-clone\": \"latest\",\n \"gulp-concat\": \"latest\",\n \"gulp-help\": \"latest\",\n \"gulp-insert\": \"latest\",\n \"gulp-newer\": \"latest\",\n \"gulp-sourcemaps\": \"latest\",\n \"gulp-typescript\": \"latest\",\n \"into-stream\": \"latest\",\n \"istanbul\": \"latest\",\n \"jake\": \"latest\",\n \"merge2\": \"latest\",\n \"minimist\": \"latest\",\n \"mkdirp\": \"latest\",\n \"mocha\": \"latest\",\n \"mocha-fivemat-progress-reporter\": \"latest\",\n \"q\": \"latest\",\n \"run-sequence\": \"latest\",\n \"sorcery\": \"latest\",\n \"through2\": \"latest\",\n \"travis-fold\": \"latest\",\n \"ts-node\": \"latest\",\n \"eslint\": \"5.16.0\",\n \"typescript\": \"^2.4\"\n },\n \"scripts\": {\n \"pretest\": \"jake tests\",\n \"test\": \"jake runtests-parallel\",\n \"build\": \"npm run build:compiler && npm run build:tests\",\n \"build:compiler\": \"jake local\",\n \"build:tests\": \"jake tests\",\n \"start\": \"node lib/tsc\",\n \"clean\": \"jake clean\",\n \"gulp\": \"gulp\",\n \"jake\": \"jake\",\n \"lint\": \"jake lint\",\n \"setup-hooks\": \"node scripts/link-hooks.js\"\n },\n \"browser\": {\n \"buffer\": false,\n \"fs\": false,\n \"os\": false,\n \"path\": false\n }\n}" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.js", content: "module.exports = require('./lib/index');\n" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.d.ts", content: "declare const observableSymbol: symbol;\nexport default observableSymbol;\n" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib" }, + { path: "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib/index.js", content: "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _ponyfill = require('./ponyfill');\n\nvar _ponyfill2 = _interopRequireDefault(_ponyfill);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar root; /* global window */\n\n\nif (typeof self !== 'undefined') {\n root = self;\n} else if (typeof window !== 'undefined') {\n root = window;\n} else if (typeof global !== 'undefined') {\n root = global;\n} else if (typeof module !== 'undefined') {\n root = module;\n} else {\n root = Function('return this')();\n}\n\nvar result = (0, _ponyfill2['default'])(root);\nexports['default'] = result;" }, + ].map(getRootedFileOrFolder); + verifyAfterPartialOrCompleteNpmInstall(2); + + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/.staging/typescript-8493ea5d/lib" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/add/operator" }, + { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/package.json", content: "{\n \"name\": \"@types/lodash\",\n \"version\": \"4.14.74\",\n \"description\": \"TypeScript definitions for Lo-Dash\",\n \"license\": \"MIT\",\n \"contributors\": [\n {\n \"name\": \"Brian Zengel\",\n \"url\": \"https://github.com/bczengel\"\n },\n {\n \"name\": \"Ilya Mochalov\",\n \"url\": \"https://github.com/chrootsu\"\n },\n {\n \"name\": \"Stepan Mikhaylyuk\",\n \"url\": \"https://github.com/stepancar\"\n },\n {\n \"name\": \"Eric L Anderson\",\n \"url\": \"https://github.com/ericanderson\"\n },\n {\n \"name\": \"AJ Richardson\",\n \"url\": \"https://github.com/aj-r\"\n },\n {\n \"name\": \"Junyoung Clare Jang\",\n \"url\": \"https://github.com/ailrun\"\n }\n ],\n \"main\": \"\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://www.github.com/DefinitelyTyped/DefinitelyTyped.git\"\n },\n \"scripts\": {},\n \"dependencies\": {},\n \"typesPublisherContentHash\": \"12af578ffaf8d86d2df37e591857906a86b983fa9258414326544a0fe6af0de8\",\n \"typeScriptVersion\": \"2.2\"\n}" }, + { path: "/a/b/node_modules/.staging/lodash-b0733faa/index.js", content: "module.exports = require('./lodash');" }, + { path: "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594", content: "" } + ].map(getRootedFileOrFolder)); + // Since we added/removed in .staging no timeout + verifyAfterPartialOrCompleteNpmInstall(0); + + // Remove file "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594" + host.deleteFile(ts.last(filesAndFoldersToAdd).path); + filesAndFoldersToAdd.length--; + verifyAfterPartialOrCompleteNpmInstall(0); + + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/.staging/rxjs-22375c61/bundles" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/operator" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/add/observable/dom" }, + { path: "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/index.d.ts", content: "\n// Stub for lodash\nexport = _;\nexport as namespace _;\ndeclare var _: _.LoDashStatic;\ndeclare namespace _ {\n interface LoDashStatic {\n someProp: string;\n }\n class SomeClass {\n someMethod(): void;\n }\n}" } + ].map(getRootedFileOrFolder)); + verifyAfterPartialOrCompleteNpmInstall(0); + + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/scheduler" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/src/util" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/symbol" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/testing" }, + { path: "/a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041", content: "{\n \"_args\": [\n [\n {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\"\n ]\n ],\n \"_from\": \"rxjs@>=5.4.2 <6.0.0\",\n \"_id\": \"rxjs@5.4.3\",\n \"_inCache\": true,\n \"_location\": \"/rxjs\",\n \"_nodeVersion\": \"7.7.2\",\n \"_npmOperationalInternal\": {\n \"host\": \"s3://npm-registry-packages\",\n \"tmp\": \"tmp/rxjs-5.4.3.tgz_1502407898166_0.6800217325799167\"\n },\n \"_npmUser\": {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"_npmVersion\": \"5.3.0\",\n \"_phantomChildren\": {},\n \"_requested\": {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"_requiredBy\": [\n \"/\"\n ],\n \"_resolved\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\",\n \"_shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"_shrinkwrap\": null,\n \"_spec\": \"rxjs@^5.4.2\",\n \"_where\": \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"config\": {\n \"commitizen\": {\n \"path\": \"cz-conventional-changelog\"\n }\n },\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n },\n \"description\": \"Reactive Extensions for modern JavaScript\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"eslint\": \"^5.16.0\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"directories\": {},\n \"dist\": {\n \"integrity\": \"sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==\",\n \"shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"tarball\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"keywords\": [\n \"Rx\",\n \"RxJS\",\n \"ReactiveX\",\n \"ReactiveExtensions\",\n \"Streams\",\n \"Observables\",\n \"Observable\",\n \"Stream\",\n \"ES6\",\n \"ES2015\"\n ],\n \"license\": \"Apache-2.0\",\n \"lint-staged\": {\n \"*.@(js)\": [\n \"eslint --fix\",\n \"git add\"\n ],\n \"*.@(ts)\": [\n \"eslint -c .eslintrc --ext .ts . --fix\",\n \"git add\"\n ]\n },\n \"main\": \"Rx.js\",\n \"maintainers\": [\n {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n }\n ],\n \"name\": \"rxjs\",\n \"optionalDependencies\": {},\n \"readme\": \"ERROR: No README data found!\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+ssh://git@github.com/ReactiveX/RxJS.git\"\n },\n \"scripts-info\": {\n \"info\": \"List available script\",\n \"build_all\": \"Build all packages (ES6, CJS, UMD) and generate packages\",\n \"build_cjs\": \"Build CJS package with clean up existing build, copy source into dist\",\n \"build_es6\": \"Build ES6 package with clean up existing build, copy source into dist\",\n \"build_closure_core\": \"Minify Global core build using closure compiler\",\n \"build_global\": \"Build Global package, then minify build\",\n \"build_perf\": \"Build CJS & Global build, run macro performance test\",\n \"build_test\": \"Build CJS package & test spec, execute mocha test runner\",\n \"build_cover\": \"Run lint to current code, build CJS & test spec, execute test coverage\",\n \"build_docs\": \"Build ES6 & global package, create documentation using it\",\n \"build_spec\": \"Build test specs\",\n \"check_circular_dependencies\": \"Check codebase has circular dependencies\",\n \"clean_spec\": \"Clean up existing test spec build output\",\n \"clean_dist_cjs\": \"Clean up existing CJS package output\",\n \"clean_dist_es6\": \"Clean up existing ES6 package output\",\n \"clean_dist_global\": \"Clean up existing Global package output\",\n \"commit\": \"Run git commit wizard\",\n \"compile_dist_cjs\": \"Compile codebase into CJS module\",\n \"compile_module_es6\": \"Compile codebase into ES6\",\n \"cover\": \"Execute test coverage\",\n \"lint_perf\": \"Run lint against performance test suite\",\n \"lint_spec\": \"Run lint against test spec\",\n \"lint_src\": \"Run lint against source\",\n \"lint\": \"Run lint against everything\",\n \"perf\": \"Run macro performance benchmark\",\n \"perf_micro\": \"Run micro performance benchmark\",\n \"test_mocha\": \"Execute mocha test runner against existing test spec build\",\n \"test_browser\": \"Execute mocha test runner on browser against existing test spec build\",\n \"test\": \"Clean up existing test spec build, build test spec and execute mocha test runner\",\n \"tests2png\": \"Generate marble diagram image from test spec\",\n \"watch\": \"Watch codebase, trigger compile when source code changes\"\n },\n \"typings\": \"Rx.d.ts\",\n \"version\": \"5.4.3\"\n}\n" } + ].map(getRootedFileOrFolder)); + verifyAfterPartialOrCompleteNpmInstall(0); + + // remove /a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041 + host.deleteFile(ts.last(filesAndFoldersToAdd).path); + filesAndFoldersToAdd.length--; + // and add few more folders/files + filesAndFoldersToAdd.push(...[ + { path: "/a/b/node_modules/symbol-observable" }, + { path: "/a/b/node_modules/@types" }, + { path: "/a/b/node_modules/@types/lodash" }, + { path: "/a/b/node_modules/lodash" }, + { path: "/a/b/node_modules/rxjs" }, + { path: "/a/b/node_modules/typescript" }, + { path: "/a/b/node_modules/.bin" } + ].map(getRootedFileOrFolder)); + // From the type root update + verifyAfterPartialOrCompleteNpmInstall(2); + + ts.forEach(filesAndFoldersToAdd, f => { + f.path = f.path + .replace("/a/b/node_modules/.staging", "/a/b/node_modules") + .replace(/[\-\.][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w]/g, ""); + }); + + host.deleteFolder(root + "/a/b/node_modules/.staging", /*recursive*/ true); + // npm installation complete, timeout after reload fs + npmInstallComplete = true; + verifyAfterPartialOrCompleteNpmInstall(2); + + ts.projectSystem.baselineTsserverLogs( + "cachingFileSystemInformation", + `npm install works when ${timeoutDuringPartialInstallation ? "timeout occurs inbetween installation" : "timeout occurs after installation"}`, + projectService + ); + + function verifyAfterPartialOrCompleteNpmInstall(timeoutQueueLengthWhenRunningTimeouts: number) { + filesAndFoldersToAdd.forEach(f => host.ensureFileOrFolder(f)); + if (npmInstallComplete || timeoutDuringPartialInstallation) { + if (timeoutQueueLengthWhenRunningTimeouts) { + // Expected project update + host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts + 1); // Scheduled invalidation of resolutions + host.runQueuedTimeoutCallbacks(); // Actual update } else { - host.checkTimeoutQueueLength(3); + host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts); } } + else { + host.checkTimeoutQueueLength(3); + } } + } - it("timeouts occur inbetween installation", () => { - verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); - }); - it("timeout occurs after installation", () => { - verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); - }); + it("timeouts occur inbetween installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); }); - - it("when node_modules dont receive event for the @types file addition", () => { - const projectLocation = "/user/username/folder/myproject"; - const app: File = { - path: `${projectLocation}/app.ts`, - content: `import * as debug from "debug"` - }; - const tsconfig: File = { - path: `${projectLocation}/tsconfig.json`, - content: "" - }; - - const files = [app, tsconfig, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(app.path); - - const project = service.configuredProjects.get(tsconfig.path)!; - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); - - const debugTypesFile: File = { - path: `${projectLocation}/node_modules/@types/debug/index.d.ts`, - content: "export {}" - }; - files.push(debugTypesFile); - // Do not invoke recursive directory watcher for anything other than node_module/@types - const invoker = host.invokeFsWatchesRecursiveCallbacks; - host.invokeFsWatchesRecursiveCallbacks = (fullPath, eventName, entryFullPath) => { - if (fullPath.endsWith("@types")) { - invoker.call(host, fullPath, eventName, entryFullPath); - } - }; - host.writeFile(debugTypesFile.path, debugTypesFile.content); - host.runQueuedTimeoutCallbacks(); - checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), []); + it("timeout occurs after installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); }); + }); - it("when creating new file in symlinked folder", () => { - const module1: File = { - path: `${tscWatch.projectRoot}/client/folder1/module1.ts`, - content: `export class Module1Class { }` - }; - const module2: File = { - path: `${tscWatch.projectRoot}/folder2/module2.ts`, - content: `import * as M from "folder1/module1";` - }; - const symlink: SymLink = { - path: `${tscWatch.projectRoot}/client/linktofolder2`, - symLink: `${tscWatch.projectRoot}/folder2`, - }; - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - baseUrl: "client", - paths: { "*": ["*"] }, - }, - include: ["client/**/*", "folder2"] - }) - }; - const host = createServerHost([module1, module2, symlink, config, libFile]); - const service = createProjectService(host); - service.openClientFile(`${symlink.path}/module2.ts`); - checkNumberOfProjects(service, { configuredProjects: 1 }); - const project = Debug.checkDefined(service.configuredProjects.get(config.path)); - checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, libFile.path]); - host.writeFile(`${symlink.path}/module3.ts`, `import * as M from "folder1/module1";`); - host.runQueuedTimeoutCallbacks(); - checkNumberOfProjects(service, { configuredProjects: 1 }); - checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, libFile.path, `${symlink.path}/module3.ts`]); - }); + it("when node_modules dont receive event for the @types file addition", () => { + const projectLocation = "/user/username/folder/myproject"; + const app: ts.projectSystem.File = { + path: `${projectLocation}/app.ts`, + content: `import * as debug from "debug"` + }; + const tsconfig: ts.projectSystem.File = { + path: `${projectLocation}/tsconfig.json`, + content: "" + }; + + const files = [app, tsconfig, ts.projectSystem.libFile]; + const host = ts.projectSystem.createServerHost(files); + const service = ts.projectSystem.createProjectService(host); + service.openClientFile(app.path); + + const project = service.configuredProjects.get(tsconfig.path)!; + ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), ["Cannot find module 'debug' or its corresponding type declarations."]); + + const debugTypesFile: ts.projectSystem.File = { + path: `${projectLocation}/node_modules/@types/debug/index.d.ts`, + content: "export {}" + }; + files.push(debugTypesFile); + // Do not invoke recursive directory watcher for anything other than node_module/@types + const invoker = host.invokeFsWatchesRecursiveCallbacks; + host.invokeFsWatchesRecursiveCallbacks = (fullPath, eventName, entryFullPath) => { + if (fullPath.endsWith("@types")) { + invoker.call(host, fullPath, eventName, entryFullPath); + } + }; + host.writeFile(debugTypesFile.path, debugTypesFile.content); + host.runQueuedTimeoutCallbacks(); + ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path)); + assert.deepEqual(project.getLanguageService().getSemanticDiagnostics(app.path).map(diag => diag.messageText), []); }); -} + + it("when creating new file in symlinked folder", () => { + const module1: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/client/folder1/module1.ts`, + content: `export class Module1Class { }` + }; + const module2: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/folder2/module2.ts`, + content: `import * as M from "folder1/module1";` + }; + const symlink: ts.projectSystem.SymLink = { + path: `${ts.tscWatch.projectRoot}/client/linktofolder2`, + symLink: `${ts.tscWatch.projectRoot}/folder2`, + }; + const config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + baseUrl: "client", + paths: { "*": ["*"] }, + }, + include: ["client/**/*", "folder2"] + }) + }; + const host = ts.projectSystem.createServerHost([module1, module2, symlink, config, ts.projectSystem.libFile]); + const service = ts.projectSystem.createProjectService(host); + service.openClientFile(`${symlink.path}/module2.ts`); + ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 }); + const project = ts.Debug.checkDefined(service.configuredProjects.get(config.path)); + ts.projectSystem.checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, ts.projectSystem.libFile.path]); + host.writeFile(`${symlink.path}/module3.ts`, `import * as M from "folder1/module1";`); + host.runQueuedTimeoutCallbacks(); + ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(project, [module1.path, `${symlink.path}/module2.ts`, config.path, ts.projectSystem.libFile.path, `${symlink.path}/module3.ts`]); + }); +}); diff --git a/src/testRunner/unittests/tsserver/cancellationToken.ts b/src/testRunner/unittests/tsserver/cancellationToken.ts index f727263357ec0..5c207e74d8eb5 100644 --- a/src/testRunner/unittests/tsserver/cancellationToken.ts +++ b/src/testRunner/unittests/tsserver/cancellationToken.ts @@ -1,274 +1,274 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: cancellationToken", () => { - // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test - let oldPrepare: AnyFunction; - before(() => { - oldPrepare = (Error as any).prepareStackTrace; - delete (Error as any).prepareStackTrace; - }); +import * as ts from "../../_namespaces/ts"; - after(() => { - (Error as any).prepareStackTrace = oldPrepare; - }); +describe("unittests:: tsserver:: cancellationToken", () => { + // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test + let oldPrepare: ts.AnyFunction; + before(() => { + oldPrepare = (Error as any).prepareStackTrace; + delete (Error as any).prepareStackTrace; + }); + + after(() => { + (Error as any).prepareStackTrace = oldPrepare; + }); + + it("is attached to request", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let xyz = 1;" + }; + const host = ts.projectSystem.createServerHost([f1]); + let expectedRequestId: number; + const cancellationToken: ts.server.ServerCancellationToken = { + isCancellationRequested: () => false, + setRequest: requestId => { + if (expectedRequestId === undefined) { + assert.isTrue(false, "unexpected call"); + } + assert.equal(requestId, expectedRequestId); + }, + resetRequest: ts.noop + }; + + const session = ts.projectSystem.createSession(host, { cancellationToken }); + + expectedRequestId = session.getNextSeq(); + session.executeCommandSeq({ + command: "open", + arguments: { file: f1.path } + } as ts.server.protocol.OpenRequest); - it("is attached to request", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let xyz = 1;" - }; - const host = createServerHost([f1]); - let expectedRequestId: number; - const cancellationToken: server.ServerCancellationToken = { - isCancellationRequested: () => false, - setRequest: requestId => { - if (expectedRequestId === undefined) { - assert.isTrue(false, "unexpected call"); - } - assert.equal(requestId, expectedRequestId); - }, - resetRequest: noop - }; - - const session = createSession(host, { cancellationToken }); - - expectedRequestId = session.getNextSeq(); + expectedRequestId = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + } as ts.server.protocol.GeterrRequest); + + expectedRequestId = session.getNextSeq(); + session.executeCommandSeq({ + command: "occurrences", + arguments: { file: f1.path, line: 1, offset: 6 } + } as ts.server.protocol.OccurrencesRequest); + + expectedRequestId = 2; + host.runQueuedImmediateCallbacks(); + expectedRequestId = 2; + host.runQueuedImmediateCallbacks(); + }); + + it("Geterr is cancellable", () => { + const f1 = { + path: "/a/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {} + }) + }; + + const cancellationToken = new ts.projectSystem.TestServerCancellationToken(); + const host = ts.projectSystem.createServerHost([f1, config]); + const session = ts.projectSystem.createSession(host, { + canUseEvents: true, + eventHandler: ts.noop, + cancellationToken + }); + { session.executeCommandSeq({ command: "open", arguments: { file: f1.path } - } as server.protocol.OpenRequest); - - expectedRequestId = session.getNextSeq(); + } as ts.projectSystem.protocol.OpenRequest); + // send geterr for missing file + session.executeCommandSeq({ + command: "geterr", + arguments: { files: ["/a/missing"] } + } as ts.projectSystem.protocol.GeterrRequest); + // Queued files + assert.equal(host.getOutput().length, 0, "expected 0 message"); + host.checkTimeoutQueueLengthAndRun(1); + // Completed event since file is missing + assert.equal(host.getOutput().length, 1, "expect 1 message"); + verifyRequestCompleted(session.getSeq(), 0); + } + { + const getErrId = session.getNextSeq(); + // send geterr for a valid file session.executeCommandSeq({ command: "geterr", arguments: { files: [f1.path] } - } as server.protocol.GeterrRequest); + } as ts.projectSystem.protocol.GeterrRequest); + + assert.equal(host.getOutput().length, 0, "expect 0 messages"); - expectedRequestId = session.getNextSeq(); + // run new request session.executeCommandSeq({ - command: "occurrences", - arguments: { file: f1.path, line: 1, offset: 6 } - } as server.protocol.OccurrencesRequest); + command: "projectInfo", + arguments: { file: f1.path } + } as ts.projectSystem.protocol.ProjectInfoRequest); + session.clearMessages(); + + // cancel previously issued Geterr + cancellationToken.setRequestToCancel(getErrId); + host.runQueuedTimeoutCallbacks(); + + assert.equal(host.getOutput().length, 1, "expect 1 message"); + verifyRequestCompleted(getErrId, 0); + + cancellationToken.resetToken(); + } + { + const getErrId = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + } as ts.projectSystem.protocol.GeterrRequest); + assert.equal(host.getOutput().length, 0, "expect 0 messages"); - expectedRequestId = 2; + // run first step + host.runQueuedTimeoutCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + const e1 = getMessage(0) as ts.projectSystem.protocol.Event; + assert.equal(e1.event, "syntaxDiag"); + session.clearMessages(); + + cancellationToken.setRequestToCancel(getErrId); host.runQueuedImmediateCallbacks(); - expectedRequestId = 2; + assert.equal(host.getOutput().length, 1, "expect 1 message"); + verifyRequestCompleted(getErrId, 0); + + cancellationToken.resetToken(); + } + { + const getErrId = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + } as ts.projectSystem.protocol.GeterrRequest); + assert.equal(host.getOutput().length, 0, "expect 0 messages"); + + // run first step + host.runQueuedTimeoutCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + const e1 = getMessage(0) as ts.projectSystem.protocol.Event; + assert.equal(e1.event, "syntaxDiag"); + session.clearMessages(); + + // the semanticDiag message host.runQueuedImmediateCallbacks(); - }); + assert.equal(host.getOutput().length, 1); + const e2 = getMessage(0) as ts.projectSystem.protocol.Event; + assert.equal(e2.event, "semanticDiag"); + session.clearMessages(); - it("Geterr is cancellable", () => { - const f1 = { - path: "/a/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {} - }) - }; - - const cancellationToken = new TestServerCancellationToken(); - const host = createServerHost([f1, config]); - const session = createSession(host, { - canUseEvents: true, - eventHandler: noop, - cancellationToken - }); - { - session.executeCommandSeq({ - command: "open", - arguments: { file: f1.path } - } as protocol.OpenRequest); - // send geterr for missing file - session.executeCommandSeq({ - command: "geterr", - arguments: { files: ["/a/missing"] } - } as protocol.GeterrRequest); - // Queued files - assert.equal(host.getOutput().length, 0, "expected 0 message"); - host.checkTimeoutQueueLengthAndRun(1); - // Completed event since file is missing - assert.equal(host.getOutput().length, 1, "expect 1 message"); - verifyRequestCompleted(session.getSeq(), 0); - } - { - const getErrId = session.getNextSeq(); - // send geterr for a valid file - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - } as protocol.GeterrRequest); - - assert.equal(host.getOutput().length, 0, "expect 0 messages"); - - // run new request - session.executeCommandSeq({ - command: "projectInfo", - arguments: { file: f1.path } - } as protocol.ProjectInfoRequest); - session.clearMessages(); - - // cancel previously issued Geterr - cancellationToken.setRequestToCancel(getErrId); - host.runQueuedTimeoutCallbacks(); - - assert.equal(host.getOutput().length, 1, "expect 1 message"); - verifyRequestCompleted(getErrId, 0); - - cancellationToken.resetToken(); - } - { - const getErrId = session.getNextSeq(); - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - } as protocol.GeterrRequest); - assert.equal(host.getOutput().length, 0, "expect 0 messages"); - - // run first step - host.runQueuedTimeoutCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - const e1 = getMessage(0) as protocol.Event; - assert.equal(e1.event, "syntaxDiag"); - session.clearMessages(); - - cancellationToken.setRequestToCancel(getErrId); - host.runQueuedImmediateCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - verifyRequestCompleted(getErrId, 0); - - cancellationToken.resetToken(); - } - { - const getErrId = session.getNextSeq(); - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - } as protocol.GeterrRequest); - assert.equal(host.getOutput().length, 0, "expect 0 messages"); - - // run first step - host.runQueuedTimeoutCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - const e1 = getMessage(0) as protocol.Event; - assert.equal(e1.event, "syntaxDiag"); - session.clearMessages(); - - // the semanticDiag message - host.runQueuedImmediateCallbacks(); - assert.equal(host.getOutput().length, 1); - const e2 = getMessage(0) as protocol.Event; - assert.equal(e2.event, "semanticDiag"); - session.clearMessages(); - - host.runQueuedImmediateCallbacks(1); - assert.equal(host.getOutput().length, 2); - const e3 = getMessage(0) as protocol.Event; - assert.equal(e3.event, "suggestionDiag"); - verifyRequestCompleted(getErrId, 1); - - cancellationToken.resetToken(); - } - { - const getErr1 = session.getNextSeq(); - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - } as protocol.GeterrRequest); - assert.equal(host.getOutput().length, 0, "expect 0 messages"); - // run first step - host.runQueuedTimeoutCallbacks(); - assert.equal(host.getOutput().length, 1, "expect 1 message"); - const e1 = getMessage(0) as protocol.Event; - assert.equal(e1.event, "syntaxDiag"); - session.clearMessages(); - - session.executeCommandSeq({ - command: "geterr", - arguments: { files: [f1.path] } - } as protocol.GeterrRequest); - // make sure that getErr1 is completed - verifyRequestCompleted(getErr1, 0); - } + host.runQueuedImmediateCallbacks(1); + assert.equal(host.getOutput().length, 2); + const e3 = getMessage(0) as ts.projectSystem.protocol.Event; + assert.equal(e3.event, "suggestionDiag"); + verifyRequestCompleted(getErrId, 1); - function verifyRequestCompleted(expectedSeq: number, n: number) { - const event = getMessage(n) as protocol.RequestCompletedEvent; - assert.equal(event.event, "requestCompleted"); - assert.equal(event.body.request_seq, expectedSeq, "expectedSeq"); - session.clearMessages(); - } + cancellationToken.resetToken(); + } + { + const getErr1 = session.getNextSeq(); + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + } as ts.projectSystem.protocol.GeterrRequest); + assert.equal(host.getOutput().length, 0, "expect 0 messages"); + // run first step + host.runQueuedTimeoutCallbacks(); + assert.equal(host.getOutput().length, 1, "expect 1 message"); + const e1 = getMessage(0) as ts.projectSystem.protocol.Event; + assert.equal(e1.event, "syntaxDiag"); + session.clearMessages(); - function getMessage(n: number) { - return JSON.parse(server.extractMessage(host.getOutput()[n])); - } + session.executeCommandSeq({ + command: "geterr", + arguments: { files: [f1.path] } + } as ts.projectSystem.protocol.GeterrRequest); + // make sure that getErr1 is completed + verifyRequestCompleted(getErr1, 0); + } + + function verifyRequestCompleted(expectedSeq: number, n: number) { + const event = getMessage(n) as ts.projectSystem.protocol.RequestCompletedEvent; + assert.equal(event.event, "requestCompleted"); + assert.equal(event.body.request_seq, expectedSeq, "expectedSeq"); + session.clearMessages(); + } + + function getMessage(n: number) { + return JSON.parse(ts.server.extractMessage(host.getOutput()[n])); + } + }); + + it("Lower priority tasks are cancellable", () => { + const f1 = { + path: "/a/app.ts", + content: `{ let x = 1; } var foo = "foo"; var bar = "bar"; var fooBar = "fooBar";` + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {} + }) + }; + const cancellationToken = new ts.projectSystem.TestServerCancellationToken(/*cancelAfterRequest*/ 3); + const host = ts.projectSystem.createServerHost([f1, config]); + const session = ts.projectSystem.createSession(host, { + canUseEvents: true, + eventHandler: ts.noop, + cancellationToken, + throttleWaitMilliseconds: 0 }); + { + session.executeCommandSeq({ + command: "open", + arguments: { file: f1.path } + } as ts.projectSystem.protocol.OpenRequest); - it("Lower priority tasks are cancellable", () => { - const f1 = { - path: "/a/app.ts", - content: `{ let x = 1; } var foo = "foo"; var bar = "bar"; var fooBar = "fooBar";` - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {} - }) - }; - const cancellationToken = new TestServerCancellationToken(/*cancelAfterRequest*/ 3); - const host = createServerHost([f1, config]); - const session = createSession(host, { - canUseEvents: true, - eventHandler: noop, - cancellationToken, - throttleWaitMilliseconds: 0 - }); - { - session.executeCommandSeq({ - command: "open", - arguments: { file: f1.path } - } as protocol.OpenRequest); - - // send navbar request (normal priority) - session.executeCommandSeq({ - command: "navbar", - arguments: { file: f1.path } - } as protocol.NavBarRequest); - - // ensure the nav bar request can be canceled - verifyExecuteCommandSeqIsCancellable({ - command: "navbar", - arguments: { file: f1.path } - } as protocol.NavBarRequest); - - // send outlining spans request (normal priority) - session.executeCommandSeq({ - command: "outliningSpans", - arguments: { file: f1.path } - } as protocol.OutliningSpansRequestFull); - - // ensure the outlining spans request can be canceled - verifyExecuteCommandSeqIsCancellable({ - command: "outliningSpans", - arguments: { file: f1.path } - } as protocol.OutliningSpansRequestFull); - } + // send navbar request (normal priority) + session.executeCommandSeq({ + command: "navbar", + arguments: { file: f1.path } + } as ts.projectSystem.protocol.NavBarRequest); - function verifyExecuteCommandSeqIsCancellable(request: Partial) { - // Set the next request to be cancellable - // The cancellation token will cancel the request the third time - // isCancellationRequested() is called. - cancellationToken.setRequestToCancel(session.getNextSeq()); - let operationCanceledExceptionThrown = false; + // ensure the nav bar request can be canceled + verifyExecuteCommandSeqIsCancellable({ + command: "navbar", + arguments: { file: f1.path } + } as ts.projectSystem.protocol.NavBarRequest); - try { - session.executeCommandSeq(request); - } - catch (e) { - assert(e instanceof OperationCanceledException); - operationCanceledExceptionThrown = true; - } - assert(operationCanceledExceptionThrown, "Operation Canceled Exception not thrown for request: " + JSON.stringify(request)); + // send outlining spans request (normal priority) + session.executeCommandSeq({ + command: "outliningSpans", + arguments: { file: f1.path } + } as ts.projectSystem.protocol.OutliningSpansRequestFull); + + // ensure the outlining spans request can be canceled + verifyExecuteCommandSeqIsCancellable({ + command: "outliningSpans", + arguments: { file: f1.path } + } as ts.projectSystem.protocol.OutliningSpansRequestFull); + } + + function verifyExecuteCommandSeqIsCancellable(request: Partial) { + // Set the next request to be cancellable + // The cancellation token will cancel the request the third time + // isCancellationRequested() is called. + cancellationToken.setRequestToCancel(session.getNextSeq()); + let operationCanceledExceptionThrown = false; + + try { + session.executeCommandSeq(request); } - }); + catch (e) { + assert(e instanceof ts.OperationCanceledException); + operationCanceledExceptionThrown = true; + } + assert(operationCanceledExceptionThrown, "Operation Canceled Exception not thrown for request: " + JSON.stringify(request)); + } }); -} +}); diff --git a/src/testRunner/unittests/tsserver/compileOnSave.ts b/src/testRunner/unittests/tsserver/compileOnSave.ts index ffa2b4b1b937a..327dfdef4265e 100644 --- a/src/testRunner/unittests/tsserver/compileOnSave.ts +++ b/src/testRunner/unittests/tsserver/compileOnSave.ts @@ -1,1051 +1,1051 @@ -namespace ts.projectSystem { - import CommandNames = server.CommandNames; - function createTestTypingsInstaller(host: server.ServerHost) { - return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host); - } +import * as ts from "../../_namespaces/ts"; - describe("unittests:: tsserver:: compileOnSave:: affected list", () => { - function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: File[] }[]) { - const response = session.executeCommand(request).response as server.protocol.CompileOnSaveAffectedFileListSingleProject[]; - const actualResult = response.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); - expectedFileList = expectedFileList.sort((list1, list2) => compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); +import CommandNames = ts.server.CommandNames; +function createTestTypingsInstaller(host: ts.server.ServerHost) { + return new ts.projectSystem.TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host); +} - assert.equal(actualResult.length, expectedFileList.length, `Actual result project number is different from the expected project number`); +describe("unittests:: tsserver:: compileOnSave:: affected list", () => { + function sendAffectedFileRequestAndCheckResult(session: ts.server.Session, request: ts.server.protocol.Request, expectedFileList: { projectFileName: string, files: ts.projectSystem.File[] }[]) { + const response = session.executeCommand(request).response as ts.server.protocol.CompileOnSaveAffectedFileListSingleProject[]; + const actualResult = response.sort((list1, list2) => ts.compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); + expectedFileList = expectedFileList.sort((list1, list2) => ts.compareStringsCaseSensitive(list1.projectFileName, list2.projectFileName)); - for (let i = 0; i < actualResult.length; i++) { - const actualResultSingleProject = actualResult[i]; - const expectedResultSingleProject = expectedFileList[i]; - assert.equal(actualResultSingleProject.projectFileName, expectedResultSingleProject.projectFileName, `Actual result contains different projects than the expected result`); + assert.equal(actualResult.length, expectedFileList.length, `Actual result project number is different from the expected project number`); - const actualResultSingleProjectFileNameList = actualResultSingleProject.fileNames.sort(); - const expectedResultSingleProjectFileNameList = map(expectedResultSingleProject.files, f => f.path).sort(); - assert.isTrue( - arrayIsEqualTo(actualResultSingleProjectFileNameList, expectedResultSingleProjectFileNameList), - `For project ${actualResultSingleProject.projectFileName}, the actual result is ${actualResultSingleProjectFileNameList}, while expected ${expectedResultSingleProjectFileNameList}`); - } + for (let i = 0; i < actualResult.length; i++) { + const actualResultSingleProject = actualResult[i]; + const expectedResultSingleProject = expectedFileList[i]; + assert.equal(actualResultSingleProject.projectFileName, expectedResultSingleProject.projectFileName, `Actual result contains different projects than the expected result`); + + const actualResultSingleProjectFileNameList = actualResultSingleProject.fileNames.sort(); + const expectedResultSingleProjectFileNameList = ts.map(expectedResultSingleProject.files, f => f.path).sort(); + assert.isTrue( + ts.arrayIsEqualTo(actualResultSingleProjectFileNameList, expectedResultSingleProjectFileNameList), + `For project ${actualResultSingleProject.projectFileName}, the actual result is ${actualResultSingleProjectFileNameList}, while expected ${expectedResultSingleProjectFileNameList}`); } + } - describe("for configured projects", () => { - let moduleFile1: File; - let file1Consumer1: File; - let file1Consumer2: File; - let moduleFile2: File; - let globalFile3: File; - let configFile: File; - let changeModuleFile1ShapeRequest1: server.protocol.Request; - let changeModuleFile1InternalRequest1: server.protocol.Request; - // A compile on save affected file request using file1 - let moduleFile1FileListRequest: server.protocol.Request; - - beforeEach(() => { - moduleFile1 = { - path: "/a/b/moduleFile1.ts", - content: "export function Foo() { };" - }; + describe("for configured projects", () => { + let moduleFile1: ts.projectSystem.File; + let file1Consumer1: ts.projectSystem.File; + let file1Consumer2: ts.projectSystem.File; + let moduleFile2: ts.projectSystem.File; + let globalFile3: ts.projectSystem.File; + let configFile: ts.projectSystem.File; + let changeModuleFile1ShapeRequest1: ts.server.protocol.Request; + let changeModuleFile1InternalRequest1: ts.server.protocol.Request; + // A compile on save affected file request using file1 + let moduleFile1FileListRequest: ts.server.protocol.Request; + + beforeEach(() => { + moduleFile1 = { + path: "/a/b/moduleFile1.ts", + content: "export function Foo() { };" + }; - file1Consumer1 = { - path: "/a/b/file1Consumer1.ts", - content: `import {Foo} from "./moduleFile1"; export var y = 10;` - }; + file1Consumer1 = { + path: "/a/b/file1Consumer1.ts", + content: `import {Foo} from "./moduleFile1"; export var y = 10;` + }; - file1Consumer2 = { - path: "/a/b/file1Consumer2.ts", - content: `import {Foo} from "./moduleFile1"; let z = 10;` - }; + file1Consumer2 = { + path: "/a/b/file1Consumer2.ts", + content: `import {Foo} from "./moduleFile1"; let z = 10;` + }; - moduleFile2 = { - path: "/a/b/moduleFile2.ts", - content: `export var Foo4 = 10;` - }; + moduleFile2 = { + path: "/a/b/moduleFile2.ts", + content: `export var Foo4 = 10;` + }; - globalFile3 = { - path: "/a/b/globalFile3.ts", - content: `interface GlobalFoo { age: number }` - }; + globalFile3 = { + path: "/a/b/globalFile3.ts", + content: `interface GlobalFoo { age: number }` + }; - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compileOnSave": true }` - }; - - // Change the content of file1 to `export var T: number;export function Foo() { };` - changeModuleFile1ShapeRequest1 = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: `export var T: number;` - }); + }; - // Change the content of file1 to `export var T: number;export function Foo() { };` - changeModuleFile1InternalRequest1 = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: `var T1: number;` - }); + // Change the content of file1 to `export var T: number;export function Foo() { };` + changeModuleFile1ShapeRequest1 = ts.projectSystem.makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: `export var T: number;` + }); - moduleFile1FileListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path, projectFileName: configFile.path }); + // Change the content of file1 to `export var T: number;export function Foo() { };` + changeModuleFile1InternalRequest1 = ts.projectSystem.makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: `var T1: number;` }); - it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - - openFilesForSession([moduleFile1, file1Consumer1], session); - - // Send an initial compileOnSave request - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - - // Change the content of file1 to `export var T: number;export function Foo() { console.log('hi'); };` - const changeFile1InternalRequest = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 46, - endLine: 1, - endOffset: 46, - insertString: `console.log('hi');` - }); - session.executeCommand(changeFile1InternalRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + moduleFile1FileListRequest = ts.projectSystem.makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path, projectFileName: configFile.path }); + }); + + it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); + + ts.projectSystem.openFilesForSession([moduleFile1, file1Consumer1], session); + + // Send an initial compileOnSave request + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + session.executeCommand(changeModuleFile1ShapeRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + + // Change the content of file1 to `export var T: number;export function Foo() { console.log('hi'); };` + const changeFile1InternalRequest = ts.projectSystem.makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 46, + endLine: 1, + endOffset: 46, + insertString: `console.log('hi');` }); + session.executeCommand(changeFile1InternalRequest); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + }); - it("should be up-to-date with the reference map changes", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + it("should be up-to-date with the reference map changes", () => { + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1, file1Consumer1], session); + ts.projectSystem.openFilesForSession([moduleFile1, file1Consumer1], session); - // Send an initial compileOnSave request - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + // Send an initial compileOnSave request + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - // Change file2 content to `let y = Foo();` - const removeFile1Consumer1ImportRequest = makeSessionRequest(CommandNames.Change, { - file: file1Consumer1.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 28, - insertString: "" - }); - session.executeCommand(removeFile1Consumer1ImportRequest); - session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); - - // Add the import statements back to file2 - const addFile2ImportRequest = makeSessionRequest(CommandNames.Change, { - file: file1Consumer1.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: `import {Foo} from "./moduleFile1";` - }); - session.executeCommand(addFile2ImportRequest); - - // Change the content of file1 to `export var T2: string;export var T: number;export function Foo() { };` - const changeModuleFile1ShapeRequest2 = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: `export var T2: string;` - }); - session.executeCommand(changeModuleFile1ShapeRequest2); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + // Change file2 content to `let y = Foo();` + const removeFile1Consumer1ImportRequest = ts.projectSystem.makeSessionRequest(CommandNames.Change, { + file: file1Consumer1.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 28, + insertString: "" + }); + session.executeCommand(removeFile1Consumer1ImportRequest); + session.executeCommand(changeModuleFile1ShapeRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); + + // Add the import statements back to file2 + const addFile2ImportRequest = ts.projectSystem.makeSessionRequest(CommandNames.Change, { + file: file1Consumer1.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: `import {Foo} from "./moduleFile1";` }); + session.executeCommand(addFile2ImportRequest); + + // Change the content of file1 to `export var T2: string;export var T: number;export function Foo() { };` + const changeModuleFile1ShapeRequest2 = ts.projectSystem.makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: `export var T2: string;` + }); + session.executeCommand(changeModuleFile1ShapeRequest2); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + }); - it("should be up-to-date with changes made in non-open files", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + it("should be up-to-date with changes made in non-open files", () => { + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); + ts.projectSystem.openFilesForSession([moduleFile1], session); - // Send an initial compileOnSave request - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + // Send an initial compileOnSave request + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - host.writeFile(file1Consumer1.path, `let y = 10;`); + host.writeFile(file1Consumer1.path, `let y = 10;`); - session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); - }); + session.executeCommand(changeModuleFile1ShapeRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); + }); - it("should be up-to-date with deleted files", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + it("should be up-to-date with deleted files", () => { + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + ts.projectSystem.openFilesForSession([moduleFile1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - session.executeCommand(changeModuleFile1ShapeRequest1); - // Delete file1Consumer2 - host.deleteFile(file1Consumer2.path); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); - }); + session.executeCommand(changeModuleFile1ShapeRequest1); + // Delete file1Consumer2 + host.deleteFile(file1Consumer2.path); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); + }); - it("should be up-to-date with newly created files", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + it("should be up-to-date with newly created files", () => { + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + ts.projectSystem.openFilesForSession([moduleFile1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - const file1Consumer3: File = { - path: "/a/b/file1Consumer3.ts", - content: `import {Foo} from "./moduleFile1"; let y = Foo();` - }; - host.writeFile(file1Consumer3.path, file1Consumer3.content); - host.runQueuedTimeoutCallbacks(); - session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3] }]); - }); + const file1Consumer3: ts.projectSystem.File = { + path: "/a/b/file1Consumer3.ts", + content: `import {Foo} from "./moduleFile1"; let y = Foo();` + }; + host.writeFile(file1Consumer3.path, file1Consumer3.content); + host.runQueuedTimeoutCallbacks(); + session.executeCommand(changeModuleFile1ShapeRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3] }]); + }); - it("should detect changes in non-root files", () => { - moduleFile1 = { - path: "/a/b/moduleFile1.ts", - content: "export function Foo() { };" - }; + it("should detect changes in non-root files", () => { + moduleFile1 = { + path: "/a/b/moduleFile1.ts", + content: "export function Foo() { };" + }; - file1Consumer1 = { - path: "/a/b/file1Consumer1.ts", - content: `import {Foo} from "./moduleFile1"; let y = Foo();` - }; + file1Consumer1 = { + path: "/a/b/file1Consumer1.ts", + content: `import {Foo} from "./moduleFile1"; let y = Foo();` + }; - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compileOnSave": true, "files": ["${file1Consumer1.path}"] }` - }; - - const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + }; - openFilesForSession([moduleFile1, file1Consumer1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); - // change file1 shape now, and verify both files are affected - session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); + ts.projectSystem.openFilesForSession([moduleFile1, file1Consumer1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); - // change file1 internal, and verify only file1 is affected - session.executeCommand(changeModuleFile1InternalRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); - }); + // change file1 shape now, and verify both files are affected + session.executeCommand(changeModuleFile1ShapeRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); - it("should return all files if a global file changed shape", () => { - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - - openFilesForSession([globalFile3], session); - const changeGlobalFile3ShapeRequest = makeSessionRequest(CommandNames.Change, { - file: globalFile3.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: `var T2: string;` - }); + // change file1 internal, and verify only file1 is affected + session.executeCommand(changeModuleFile1InternalRequest1); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + }); - // check after file1 shape changes - session.executeCommand(changeGlobalFile3ShapeRequest); - const globalFile3FileListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: globalFile3.path }); - sendAffectedFileRequestAndCheckResult(session, globalFile3FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2] }]); + it("should return all files if a global file changed shape", () => { + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); + + ts.projectSystem.openFilesForSession([globalFile3], session); + const changeGlobalFile3ShapeRequest = ts.projectSystem.makeSessionRequest(CommandNames.Change, { + file: globalFile3.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: `var T2: string;` }); - it("should return empty array if CompileOnSave is not enabled", () => { - configFile = { - path: "/a/b/tsconfig.json", - content: `{}` - }; + // check after file1 shape changes + session.executeCommand(changeGlobalFile3ShapeRequest); + const globalFile3FileListRequest = ts.projectSystem.makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: globalFile3.path }); + sendAffectedFileRequestAndCheckResult(session, globalFile3FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2] }]); + }); - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []); - }); + it("should return empty array if CompileOnSave is not enabled", () => { + configFile = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); + ts.projectSystem.openFilesForSession([moduleFile1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []); + }); - it("should return empty array if noEmit is set", () => { - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + it("should return empty array if noEmit is set", () => { + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compileOnSave": true, "compilerOptions": { "noEmit": true } }` - }; + }; - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []); - }); + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); + ts.projectSystem.openFilesForSession([moduleFile1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []); + }); - it("should save when compileOnSave is enabled in base tsconfig.json", () => { - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + it("should save when compileOnSave is enabled in base tsconfig.json", () => { + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "extends": "/a/tsconfig.json" }` - }; + }; - const configFile2: File = { - path: "/a/tsconfig.json", - content: `{ + const configFile2: ts.projectSystem.File = { + path: "/a/tsconfig.json", + content: `{ "compileOnSave": true }` - }; + }; - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1, file1Consumer1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - }); + ts.projectSystem.openFilesForSession([moduleFile1, file1Consumer1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + }); - it("should always return the file itself if '--isolatedModules' is specified", () => { - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + it("should always return the file itself if '--isolatedModules' is specified", () => { + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compileOnSave": true, "compilerOptions": { "isolatedModules": true } }` - }; + }; - const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); - - const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 27, - endLine: 1, - endOffset: 27, - insertString: `Point,` - }); - session.executeCommand(file1ChangeShapeRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); + ts.projectSystem.openFilesForSession([moduleFile1], session); + + const file1ChangeShapeRequest = ts.projectSystem.makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 27, + endLine: 1, + endOffset: 27, + insertString: `Point,` }); + session.executeCommand(file1ChangeShapeRequest); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + }); - it("should always return the file itself if '--out' or '--outFile' is specified", () => { - configFile = { - path: "/a/b/tsconfig.json", - content: `{ + it("should always return the file itself if '--out' or '--outFile' is specified", () => { + configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compileOnSave": true, "compilerOptions": { "module": "system", "outFile": "/a/b/out.js" } }` - }; + }; - const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - openFilesForSession([moduleFile1], session); - - const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { - file: moduleFile1.path, - line: 1, - offset: 27, - endLine: 1, - endOffset: 27, - insertString: `Point,` - }); - session.executeCommand(file1ChangeShapeRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); + ts.projectSystem.openFilesForSession([moduleFile1], session); + + const file1ChangeShapeRequest = ts.projectSystem.makeSessionRequest(CommandNames.Change, { + file: moduleFile1.path, + line: 1, + offset: 27, + endLine: 1, + endOffset: 27, + insertString: `Point,` }); + session.executeCommand(file1ChangeShapeRequest); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + }); - it("should return cascaded affected file list", () => { - const file1Consumer1Consumer1: File = { - path: "/a/b/file1Consumer1Consumer1.ts", - content: `import {y} from "./file1Consumer1";` - }; - const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); - - openFilesForSession([moduleFile1, file1Consumer1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); - - const changeFile1Consumer1ShapeRequest = makeSessionRequest(CommandNames.Change, { - file: file1Consumer1.path, - line: 2, - offset: 1, - endLine: 2, - endOffset: 1, - insertString: `export var T: number;` - }); - session.executeCommand(changeModuleFile1ShapeRequest1); - session.executeCommand(changeFile1Consumer1ShapeRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); + it("should return cascaded affected file list", () => { + const file1Consumer1Consumer1: ts.projectSystem.File = { + path: "/a/b/file1Consumer1Consumer1.ts", + content: `import {y} from "./file1Consumer1";` + }; + const host = ts.projectSystem.createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, ts.projectSystem.libFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); + + ts.projectSystem.openFilesForSession([moduleFile1, file1Consumer1], session); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); + + const changeFile1Consumer1ShapeRequest = ts.projectSystem.makeSessionRequest(CommandNames.Change, { + file: file1Consumer1.path, + line: 2, + offset: 1, + endLine: 2, + endOffset: 1, + insertString: `export var T: number;` }); + session.executeCommand(changeModuleFile1ShapeRequest1); + session.executeCommand(changeFile1Consumer1ShapeRequest); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); + }); - it("should work fine for files with circular references", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: ` + it("should work fine for files with circular references", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/file1.ts", + content: ` /// export var t1 = 10;` - }; - const file2: File = { - path: "/a/b/file2.ts", - content: ` + }; + const file2: ts.projectSystem.File = { + path: "/a/b/file2.ts", + content: ` /// export var t2 = 10;` - }; - const host = createServerHost([file1, file2, configFile]); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + }; + const host = ts.projectSystem.createServerHost([file1, file2, configFile]); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); - openFilesForSession([file1, file2], session); - const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); - sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]); - }); + ts.projectSystem.openFilesForSession([file1, file2], session); + const file1AffectedListRequest = ts.projectSystem.makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); + sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]); + }); - it("should return results for all projects if not specifying projectFileName", () => { - const file1: File = { path: "/a/b/file1.ts", content: "export var t = 10;" }; - const file2: File = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` }; - const file3: File = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` }; - const configFile1: File = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` }; - const configFile2: File = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` }; + it("should return results for all projects if not specifying projectFileName", () => { + const file1: ts.projectSystem.File = { path: "/a/b/file1.ts", content: "export var t = 10;" }; + const file2: ts.projectSystem.File = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` }; + const file3: ts.projectSystem.File = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` }; + const configFile1: ts.projectSystem.File = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` }; + const configFile2: ts.projectSystem.File = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` }; - const host = createServerHost([file1, file2, file3, configFile1, configFile2]); - const session = createSession(host); + const host = ts.projectSystem.createServerHost([file1, file2, file3, configFile1, configFile2]); + const session = ts.projectSystem.createSession(host); - openFilesForSession([file1, file2, file3], session); - const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); + ts.projectSystem.openFilesForSession([file1, file2, file3], session); + const file1AffectedListRequest = ts.projectSystem.makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); - sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [ - { projectFileName: configFile1.path, files: [file1, file2] }, - { projectFileName: configFile2.path, files: [file1, file3] } - ]); - }); + sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [ + { projectFileName: configFile1.path, files: [file1, file2] }, + { projectFileName: configFile2.path, files: [file1, file3] } + ]); + }); - it("should detect removed code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` + it("should detect removed code file", () => { + const referenceFile1: ts.projectSystem.File = { + path: "/a/b/referenceFile1.ts", + content: ` /// export var x = Foo();` - }; - const host = createServerHost([moduleFile1, referenceFile1, configFile]); - const session = createSession(host); - - openFilesForSession([referenceFile1], session); - host.deleteFile(moduleFile1.path); - - const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); - sendAffectedFileRequestAndCheckResult(session, request, [ - { projectFileName: configFile.path, files: [referenceFile1] } - ]); - const requestForMissingFile = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path }); - sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []); - }); + }; + const host = ts.projectSystem.createServerHost([moduleFile1, referenceFile1, configFile]); + const session = ts.projectSystem.createSession(host); + + ts.projectSystem.openFilesForSession([referenceFile1], session); + host.deleteFile(moduleFile1.path); + + const request = ts.projectSystem.makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); + sendAffectedFileRequestAndCheckResult(session, request, [ + { projectFileName: configFile.path, files: [referenceFile1] } + ]); + const requestForMissingFile = ts.projectSystem.makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path }); + sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []); + }); - it("should detect non-existing code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` + it("should detect non-existing code file", () => { + const referenceFile1: ts.projectSystem.File = { + path: "/a/b/referenceFile1.ts", + content: ` /// export var x = Foo();` - }; - const host = createServerHost([referenceFile1, configFile]); - const session = createSession(host); - - openFilesForSession([referenceFile1], session); - const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); - sendAffectedFileRequestAndCheckResult(session, request, [ - { projectFileName: configFile.path, files: [referenceFile1] } - ]); - }); + }; + const host = ts.projectSystem.createServerHost([referenceFile1, configFile]); + const session = ts.projectSystem.createSession(host); + + ts.projectSystem.openFilesForSession([referenceFile1], session); + const request = ts.projectSystem.makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); + sendAffectedFileRequestAndCheckResult(session, request, [ + { projectFileName: configFile.path, files: [referenceFile1] } + ]); }); + }); - describe("for changes in declaration files", () => { - function testDTS(dtsFileContents: string, tsFileContents: string, opts: CompilerOptions, expectDTSEmit: boolean) { - const dtsFile = { - path: "/a/runtime/a.d.ts", - content: dtsFileContents - }; - const f2 = { - path: "/a/b.ts", - content: tsFileContents - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: opts, - compileOnSave: true - }) - }; - const host = createServerHost([dtsFile, f2, config]); - const session = createSession(host); - session.executeCommand({ - seq: 1, - type: "request", - command: "open", - arguments: { file: dtsFile.path } - } as protocol.OpenRequest); - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(config.path)!; - checkProjectRootFiles(project, [dtsFile.path, f2.path]); - session.executeCommand({ - seq: 2, - type: "request", - command: "open", - arguments: { file: f2.path } - } as protocol.OpenRequest); - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); - const { response } = session.executeCommand({ - seq: 3, - type: "request", - command: "compileOnSaveAffectedFileList", - arguments: { file: dtsFile.path } - } as protocol.CompileOnSaveAffectedFileListRequest); - if (expectDTSEmit) { - assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project"); - assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[])[0].fileNames.length, 2, "expected to affect 2 files"); - } - else { - assert.equal((response as protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 0, "expected no output"); - } - - - const { response: response2 } = session.executeCommand({ - seq: 4, - type: "request", - command: "compileOnSaveAffectedFileList", - arguments: { file: f2.path } - } as protocol.CompileOnSaveAffectedFileListRequest); - assert.equal((response2 as protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project"); + describe("for changes in declaration files", () => { + function testDTS(dtsFileContents: string, tsFileContents: string, opts: ts.CompilerOptions, expectDTSEmit: boolean) { + const dtsFile = { + path: "/a/runtime/a.d.ts", + content: dtsFileContents + }; + const f2 = { + path: "/a/b.ts", + content: tsFileContents + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: opts, + compileOnSave: true + }) + }; + const host = ts.projectSystem.createServerHost([dtsFile, f2, config]); + const session = ts.projectSystem.createSession(host); + session.executeCommand({ + seq: 1, + type: "request", + command: "open", + arguments: { file: dtsFile.path } + } as ts.projectSystem.protocol.OpenRequest); + const projectService = session.getProjectService(); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(config.path)!; + ts.projectSystem.checkProjectRootFiles(project, [dtsFile.path, f2.path]); + session.executeCommand({ + seq: 2, + type: "request", + command: "open", + arguments: { file: f2.path } + } as ts.projectSystem.protocol.OpenRequest); + ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 }); + const { response } = session.executeCommand({ + seq: 3, + type: "request", + command: "compileOnSaveAffectedFileList", + arguments: { file: dtsFile.path } + } as ts.projectSystem.protocol.CompileOnSaveAffectedFileListRequest); + if (expectDTSEmit) { + assert.equal((response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project"); + assert.equal((response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[])[0].fileNames.length, 2, "expected to affect 2 files"); + } + else { + assert.equal((response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 0, "expected no output"); } - it("should return empty array if change is made in a global declaration file", () => { - testDTS( - /*dtsFileContents*/ "declare const x: string;", - /*tsFileContents*/ "var y = 1;", - /*opts*/ {}, - /*expectDTSEmit*/ false - ); - }); - it("should return empty array if change is made in a module declaration file", () => { - testDTS( - /*dtsFileContents*/ "export const x: string;", - /*tsFileContents*/ "import { x } from './runtime/a;", - /*opts*/ {}, - /*expectDTSEmit*/ false - ); - }); + const { response: response2 } = session.executeCommand({ + seq: 4, + type: "request", + command: "compileOnSaveAffectedFileList", + arguments: { file: f2.path } + } as ts.projectSystem.protocol.CompileOnSaveAffectedFileListRequest); + assert.equal((response2 as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[]).length, 1, "expected output from 1 project"); + } - it("should return results if change is made in a global declaration file with declaration emit", () => { - testDTS( - /*dtsFileContents*/ "declare const x: string;", - /*tsFileContents*/ "var y = 1;", - /*opts*/ { declaration: true }, - /*expectDTSEmit*/ true - ); - }); + it("should return empty array if change is made in a global declaration file", () => { + testDTS( + /*dtsFileContents*/ "declare const x: string;", + /*tsFileContents*/ "var y = 1;", + /*opts*/ {}, + /*expectDTSEmit*/ false + ); + }); - it("should return results if change is made in a global declaration file with composite enabled", () => { - testDTS( - /*dtsFileContents*/ "declare const x: string;", - /*tsFileContents*/ "var y = 1;", - /*opts*/ { composite: true }, - /*expectDTSEmit*/ true - ); - }); + it("should return empty array if change is made in a module declaration file", () => { + testDTS( + /*dtsFileContents*/ "export const x: string;", + /*tsFileContents*/ "import { x } from './runtime/a;", + /*opts*/ {}, + /*expectDTSEmit*/ false + ); + }); - it("should return results if change is made in a global declaration file with decorator emit enabled", () => { - testDTS( - /*dtsFileContents*/ "declare const x: string;", - /*tsFileContents*/ "var y = 1;", - /*opts*/ { experimentalDecorators: true, emitDecoratorMetadata: true }, - /*expectDTSEmit*/ true - ); - }); + it("should return results if change is made in a global declaration file with declaration emit", () => { + testDTS( + /*dtsFileContents*/ "declare const x: string;", + /*tsFileContents*/ "var y = 1;", + /*opts*/ { declaration: true }, + /*expectDTSEmit*/ true + ); }); - describe("tsserverProjectSystem emit with outFile or out setting", () => { - function test(subScenario: string, opts: CompilerOptions) { - it(subScenario, () => { - const f1 = { - path: "/a/a.ts", - content: "let x = 1" - }; - const f2 = { - path: "/a/b.ts", - content: "let y = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: opts, - compileOnSave: true - }) - }; - const host = createServerHost([f1, f2, config]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([f1], session); - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: f1.path } - }); - baselineTsserverLogs("compileOnSave", subScenario, session); - }); - } - test("compileOnSaveAffectedFileList projectUsesOutFile should not be returned if not set", {}); - test("compileOnSaveAffectedFileList projectUsesOutFile should be true if outFile is set", { outFile: "/a/out.js" }); - test("compileOnSaveAffectedFileList projectUsesOutFile should be true if out is set", { out: "/a/out.js" }); + it("should return results if change is made in a global declaration file with composite enabled", () => { + testDTS( + /*dtsFileContents*/ "declare const x: string;", + /*tsFileContents*/ "var y = 1;", + /*opts*/ { composite: true }, + /*expectDTSEmit*/ true + ); + }); + + it("should return results if change is made in a global declaration file with decorator emit enabled", () => { + testDTS( + /*dtsFileContents*/ "declare const x: string;", + /*tsFileContents*/ "var y = 1;", + /*opts*/ { experimentalDecorators: true, emitDecoratorMetadata: true }, + /*expectDTSEmit*/ true + ); }); }); - describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { - it("should respect line endings", () => { - test("\n"); - test("\r\n"); - - function test(newLine: string) { - const lines = ["var x = 1;", "var y = 2;"]; - const path = "/a/app"; - const f = { - path: path + Extension.Ts, - content: lines.join(newLine) + describe("tsserverProjectSystem emit with outFile or out setting", () => { + function test(subScenario: string, opts: ts.CompilerOptions) { + it(subScenario, () => { + const f1 = { + path: "/a/a.ts", + content: "let x = 1" }; - const host = createServerHost([f], { newLine }); - const session = createSession(host); - const openRequest: server.protocol.OpenRequest = { - seq: 1, - type: "request", - command: server.protocol.CommandTypes.Open, - arguments: { file: f.path } + const f2 = { + path: "/a/b.ts", + content: "let y = 1" }; - session.executeCommand(openRequest); - const emitFileRequest: server.protocol.CompileOnSaveEmitFileRequest = { - seq: 2, - type: "request", - command: server.protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: f.path } + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: opts, + compileOnSave: true + }) }; - session.executeCommand(emitFileRequest); - const emitOutput = host.readFile(path + Extension.Js); - assert.equal(emitOutput, f.content + newLine, "content of emit output should be identical with the input + newline"); - } - }); - - it("should emit specified file", () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export function Foo() { return 10; }` + const host = ts.projectSystem.createServerHost([f1, f2, config]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([f1], session); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: f1.path } + }); + ts.projectSystem.baselineTsserverLogs("compileOnSave", subScenario, session); + }); + } + test("compileOnSaveAffectedFileList projectUsesOutFile should not be returned if not set", {}); + test("compileOnSaveAffectedFileList projectUsesOutFile should be true if outFile is set", { outFile: "/a/out.js" }); + test("compileOnSaveAffectedFileList projectUsesOutFile should be true if out is set", { out: "/a/out.js" }); + }); +}); + +describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { + it("should respect line endings", () => { + test("\n"); + test("\r\n"); + + function test(newLine: string) { + const lines = ["var x = 1;", "var y = 2;"]; + const path = "/a/app"; + const f = { + path: path + ts.Extension.Ts, + content: lines.join(newLine) }; - const file2 = { - path: "/a/b/f2.ts", - content: `import {Foo} from "./f1"; let y = Foo();` + const host = ts.projectSystem.createServerHost([f], { newLine }); + const session = ts.projectSystem.createSession(host); + const openRequest: ts.server.protocol.OpenRequest = { + seq: 1, + type: "request", + command: ts.server.protocol.CommandTypes.Open, + arguments: { file: f.path } }; - const configFile = { - path: "/a/b/tsconfig.json", - content: `{}` + session.executeCommand(openRequest); + const emitFileRequest: ts.server.protocol.CompileOnSaveEmitFileRequest = { + seq: 2, + type: "request", + command: ts.server.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: f.path } }; - const host = createServerHost([file1, file2, configFile, libFile], { newLine: "\r\n" }); - const typingsInstaller = createTestTypingsInstaller(host); - const session = createSession(host, { typingsInstaller }); + session.executeCommand(emitFileRequest); + const emitOutput = host.readFile(path + ts.Extension.Js); + assert.equal(emitOutput, f.content + newLine, "content of emit output should be identical with the input + newline"); + } + }); - openFilesForSession([file1, file2], session); - const compileFileRequest = makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path }); - session.executeCommand(compileFileRequest); + it("should emit specified file", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export function Foo() { return 10; }` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `import {Foo} from "./f1"; let y = Foo();` + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = ts.projectSystem.createServerHost([file1, file2, configFile, ts.projectSystem.libFile], { newLine: "\r\n" }); + const typingsInstaller = createTestTypingsInstaller(host); + const session = ts.projectSystem.createSession(host, { typingsInstaller }); + + ts.projectSystem.openFilesForSession([file1, file2], session); + const compileFileRequest = ts.projectSystem.makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path }); + session.executeCommand(compileFileRequest); + + const expectedEmittedFileName = "/a/b/f1.js"; + assert.isTrue(host.fileExists(expectedEmittedFileName)); + assert.equal(host.readFile(expectedEmittedFileName), `"use strict";\r\nexports.__esModule = true;\r\nexports.Foo = void 0;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`); + }); - const expectedEmittedFileName = "/a/b/f1.js"; - assert.isTrue(host.fileExists(expectedEmittedFileName)); - assert.equal(host.readFile(expectedEmittedFileName), `"use strict";\r\nexports.__esModule = true;\r\nexports.Foo = void 0;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`); + it("shoud not emit js files in external projects", () => { + const file1 = { + path: "/a/b/file1.ts", + content: "consonle.log('file1');" + }; + // file2 has errors. The emitting should not be blocked. + const file2 = { + path: "/a/b/file2.js", + content: "console.log'file2');" + }; + const file3 = { + path: "/a/b/file3.js", + content: "console.log('file3');" + }; + const externalProjectName = "/a/b/externalproject"; + const host = ts.projectSystem.createServerHost([file1, file2, file3, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host); + const projectService = session.getProjectService(); + + projectService.openExternalProject({ + rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]), + options: { + allowJs: true, + outFile: "dist.js", + compileOnSave: true + }, + projectFileName: externalProjectName }); - it("shoud not emit js files in external projects", () => { - const file1 = { - path: "/a/b/file1.ts", - content: "consonle.log('file1');" - }; - // file2 has errors. The emitting should not be blocked. - const file2 = { - path: "/a/b/file2.js", - content: "console.log'file2');" - }; - const file3 = { - path: "/a/b/file3.js", - content: "console.log('file3');" - }; - const externalProjectName = "/a/b/externalproject"; - const host = createServerHost([file1, file2, file3, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); + const emitRequest = ts.projectSystem.makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path }); + session.executeCommand(emitRequest); - projectService.openExternalProject({ - rootFiles: toExternalFiles([file1.path, file2.path]), - options: { - allowJs: true, - outFile: "dist.js", - compileOnSave: true - }, - projectFileName: externalProjectName - }); - - const emitRequest = makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path }); - session.executeCommand(emitRequest); + const expectedOutFileName = "/a/b/dist.js"; + assert.isTrue(host.fileExists(expectedOutFileName)); + const outFileContent = host.readFile(expectedOutFileName)!; + assert.isTrue(outFileContent.indexOf(file1.content) !== -1); + assert.isTrue(outFileContent.indexOf(file2.content) === -1); + assert.isTrue(outFileContent.indexOf(file3.content) === -1); + }); - const expectedOutFileName = "/a/b/dist.js"; - assert.isTrue(host.fileExists(expectedOutFileName)); - const outFileContent = host.readFile(expectedOutFileName)!; - assert.isTrue(outFileContent.indexOf(file1.content) !== -1); - assert.isTrue(outFileContent.indexOf(file2.content) === -1); - assert.isTrue(outFileContent.indexOf(file3.content) === -1); + it("should use project root as current directory so that compile on save results in correct file mapping", () => { + const inputFileName = "Foo.ts"; + const file1 = { + path: `/root/TypeScriptProject3/TypeScriptProject3/${inputFileName}`, + content: "consonle.log('file1');" + }; + const externalProjectName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj"; + const host = ts.projectSystem.createServerHost([file1, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host); + const projectService = session.getProjectService(); + + const outFileName = "bar.js"; + projectService.openExternalProject({ + rootFiles: ts.projectSystem.toExternalFiles([file1.path]), + options: { + outFile: outFileName, + sourceMap: true, + compileOnSave: true + }, + projectFileName: externalProjectName }); - it("should use project root as current directory so that compile on save results in correct file mapping", () => { - const inputFileName = "Foo.ts"; - const file1 = { - path: `/root/TypeScriptProject3/TypeScriptProject3/${inputFileName}`, - content: "consonle.log('file1');" - }; - const externalProjectName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj"; - const host = createServerHost([file1, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); + const emitRequest = ts.projectSystem.makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path }); + session.executeCommand(emitRequest); - const outFileName = "bar.js"; - projectService.openExternalProject({ - rootFiles: toExternalFiles([file1.path]), - options: { - outFile: outFileName, - sourceMap: true, - compileOnSave: true - }, - projectFileName: externalProjectName - }); + // Verify js file + const expectedOutFileName = "/root/TypeScriptProject3/TypeScriptProject3/" + outFileName; + assert.isTrue(host.fileExists(expectedOutFileName)); + const outFileContent = host.readFile(expectedOutFileName)!; + verifyContentHasString(outFileContent, file1.content); + verifyContentHasString(outFileContent, `//# ${"sourceMappingURL"}=${outFileName}.map`); // Sometimes tools can sometimes see this line as a source mapping url comment, so we obfuscate it a little - const emitRequest = makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path }); - session.executeCommand(emitRequest); + // Verify map file + const expectedMapFileName = expectedOutFileName + ".map"; + assert.isTrue(host.fileExists(expectedMapFileName)); + const mapFileContent = host.readFile(expectedMapFileName)!; + verifyContentHasString(mapFileContent, `"sources":["${inputFileName}"]`); - // Verify js file - const expectedOutFileName = "/root/TypeScriptProject3/TypeScriptProject3/" + outFileName; - assert.isTrue(host.fileExists(expectedOutFileName)); - const outFileContent = host.readFile(expectedOutFileName)!; - verifyContentHasString(outFileContent, file1.content); - verifyContentHasString(outFileContent, `//# ${"sourceMappingURL"}=${outFileName}.map`); // Sometimes tools can sometimes see this line as a source mapping url comment, so we obfuscate it a little + function verifyContentHasString(content: string, str: string) { + assert.isTrue(ts.stringContains(content, str), `Expected "${content}" to have "${str}"`); + } + }); - // Verify map file - const expectedMapFileName = expectedOutFileName + ".map"; - assert.isTrue(host.fileExists(expectedMapFileName)); - const mapFileContent = host.readFile(expectedMapFileName)!; - verifyContentHasString(mapFileContent, `"sources":["${inputFileName}"]`); + describe("compile on save emit with and without richResponse", () => { + it("without rich Response", () => { + verify(/*richRepsonse*/ undefined); + }); + it("with rich Response set to false", () => { + verify(/*richRepsonse*/ false); + }); + it("with rich Repsonse", () => { + verify(/*richRepsonse*/ true); + }); - function verifyContentHasString(content: string, str: string) { - assert.isTrue(stringContains(content, str), `Expected "${content}" to have "${str}"`); + function verify(richResponse: boolean | undefined) { + const config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compileOnSave: true, + compilerOptions: { + outDir: "test", + noEmitOnError: true, + declaration: true, + }, + exclude: ["node_modules"] + }) + }; + const file1: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/file1.ts`, + content: "const x = 1;" + }; + const file2: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/file2.ts`, + content: "const y = 2;" + }; + const host = ts.projectSystem.createServerHost([file1, file2, config, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([file1], session); + + const affectedFileResponse = session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: file1.path } + }).response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(affectedFileResponse, [ + { fileNames: [file1.path, file2.path], projectFileName: config.path, projectUsesOutFile: false } + ]); + const file1SaveResponse = session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: file1.path, richResponse } + }).response; + if (richResponse) { + assert.deepEqual(file1SaveResponse, { emitSkipped: false, diagnostics: ts.emptyArray }); } - }); + else { + assert.isTrue(file1SaveResponse); + } + assert.strictEqual(host.readFile(`${ts.tscWatch.projectRoot}/test/file1.d.ts`), "declare const x = 1;\n"); + const file2SaveResponse = session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: file2.path, richResponse } + }).response; + if (richResponse) { + assert.deepEqual(file2SaveResponse, { + emitSkipped: true, + diagnostics: [{ + start: undefined, + end: undefined, + fileName: undefined, + text: ts.formatStringFromArgs(ts.Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file.message, [`${ts.tscWatch.projectRoot}/test/file1.d.ts`]), + code: ts.Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file.code, + category: ts.diagnosticCategoryName(ts.Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file), + reportsUnnecessary: undefined, + reportsDeprecated: undefined, + relatedInformation: undefined, + source: undefined + }] + }); + } + else { + assert.isFalse(file2SaveResponse); + } + assert.isFalse(host.fileExists(`${ts.tscWatch.projectRoot}/test/file2.d.ts`)); + } + }); - describe("compile on save emit with and without richResponse", () => { - it("without rich Response", () => { - verify(/*richRepsonse*/ undefined); - }); - it("with rich Response set to false", () => { - verify(/*richRepsonse*/ false); + describe("compile on save in global files", () => { + describe("when program contains module", () => { + it("when d.ts emit is enabled", () => { + verifyGlobalSave(/*declaration*/ true, /*hasModule*/ true); }); - it("with rich Repsonse", () => { - verify(/*richRepsonse*/ true); + it("when d.ts emit is not enabled", () => { + verifyGlobalSave(/*declaration*/ false, /*hasModule*/ true); }); - - function verify(richResponse: boolean | undefined) { - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compileOnSave: true, - compilerOptions: { - outDir: "test", - noEmitOnError: true, - declaration: true, - }, - exclude: ["node_modules"] - }) - }; - const file1: File = { - path: `${tscWatch.projectRoot}/file1.ts`, - content: "const x = 1;" - }; - const file2: File = { - path: `${tscWatch.projectRoot}/file2.ts`, - content: "const y = 2;" - }; - const host = createServerHost([file1, file2, config, libFile]); - const session = createSession(host); - openFilesForSession([file1], session); - - const affectedFileResponse = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: file1.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(affectedFileResponse, [ - { fileNames: [file1.path, file2.path], projectFileName: config.path, projectUsesOutFile: false } - ]); - const file1SaveResponse = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: file1.path, richResponse } - }).response; - if (richResponse) { - assert.deepEqual(file1SaveResponse, { emitSkipped: false, diagnostics: emptyArray }); - } - else { - assert.isTrue(file1SaveResponse); - } - assert.strictEqual(host.readFile(`${tscWatch.projectRoot}/test/file1.d.ts`), "declare const x = 1;\n"); - const file2SaveResponse = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: file2.path, richResponse } - }).response; - if (richResponse) { - assert.deepEqual(file2SaveResponse, { - emitSkipped: true, - diagnostics: [{ - start: undefined, - end: undefined, - fileName: undefined, - text: formatStringFromArgs(Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file.message, [`${tscWatch.projectRoot}/test/file1.d.ts`]), - code: Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file.code, - category: diagnosticCategoryName(Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file), - reportsUnnecessary: undefined, - reportsDeprecated: undefined, - relatedInformation: undefined, - source: undefined - }] - }); - } - else { - assert.isFalse(file2SaveResponse); - } - assert.isFalse(host.fileExists(`${tscWatch.projectRoot}/test/file2.d.ts`)); - } }); - - describe("compile on save in global files", () => { - describe("when program contains module", () => { - it("when d.ts emit is enabled", () => { - verifyGlobalSave(/*declaration*/ true, /*hasModule*/ true); - }); - it("when d.ts emit is not enabled", () => { - verifyGlobalSave(/*declaration*/ false, /*hasModule*/ true); - }); + describe("when program doesnt have module", () => { + it("when d.ts emit is enabled", () => { + verifyGlobalSave(/*declaration*/ true, /*hasModule*/ false); }); - describe("when program doesnt have module", () => { - it("when d.ts emit is enabled", () => { - verifyGlobalSave(/*declaration*/ true, /*hasModule*/ false); - }); - it("when d.ts emit is not enabled", () => { - verifyGlobalSave(/*declaration*/ false, /*hasModule*/ false); - }); + it("when d.ts emit is not enabled", () => { + verifyGlobalSave(/*declaration*/ false, /*hasModule*/ false); }); - function verifyGlobalSave(declaration: boolean,hasModule: boolean) { - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compileOnSave: true, - compilerOptions: { - declaration, - module: hasModule ? undefined : "none" - }, - }) - }; - const file1: File = { - path: `${tscWatch.projectRoot}/file1.ts`, - content: `const x = 1; + }); + function verifyGlobalSave(declaration: boolean,hasModule: boolean) { + const config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compileOnSave: true, + compilerOptions: { + declaration, + module: hasModule ? undefined : "none" + }, + }) + }; + const file1: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/file1.ts`, + content: `const x = 1; function foo() { return "hello"; }` - }; - const file2: File = { - path: `${tscWatch.projectRoot}/file2.ts`, - content: `const y = 2; + }; + const file2: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/file2.ts`, + content: `const y = 2; function bar() { return "world"; }` - }; - const file3: File = { - path: `${tscWatch.projectRoot}/file3.ts`, - content: "const xy = 3;" - }; - const module: File = { - path: `${tscWatch.projectRoot}/module.ts`, - content: "export const xyz = 4;" - }; - const files = [file1, file2, file3, ...(hasModule ? [module] : emptyArray)]; - const host = createServerHost([...files, config, libFile]); - const session = createSession(host); - openFilesForSession([file1, file2], session); - - const affectedFileResponse = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: file1.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(affectedFileResponse, [ - { fileNames: files.map(f => f.path), projectFileName: config.path, projectUsesOutFile: false } - ]); - verifyFileSave(file1); - verifyFileSave(file2); - verifyFileSave(file3); - if (hasModule) { - verifyFileSave(module); - } + }; + const file3: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/file3.ts`, + content: "const xy = 3;" + }; + const module: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/module.ts`, + content: "export const xyz = 4;" + }; + const files = [file1, file2, file3, ...(hasModule ? [module] : ts.emptyArray)]; + const host = ts.projectSystem.createServerHost([...files, config, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([file1, file2], session); + + const affectedFileResponse = session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: file1.path } + }).response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(affectedFileResponse, [ + { fileNames: files.map(f => f.path), projectFileName: config.path, projectUsesOutFile: false } + ]); + verifyFileSave(file1); + verifyFileSave(file2); + verifyFileSave(file3); + if (hasModule) { + verifyFileSave(module); + } - // Change file1 get affected file list - verifyLocalEdit(file1, "hello", "world"); + // Change file1 get affected file list + verifyLocalEdit(file1, "hello", "world"); - // Change file2 get affected file list = will return only file2 if --declaration otherwise all files - verifyLocalEdit(file2, "world", "hello", /*returnsAllFilesAsAffected*/ !declaration); + // Change file2 get affected file list = will return only file2 if --declaration otherwise all files + verifyLocalEdit(file2, "world", "hello", /*returnsAllFilesAsAffected*/ !declaration); - function verifyFileSave(file: File) { - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: file.path } - }).response; - assert.isTrue(response); + function verifyFileSave(file: ts.projectSystem.File) { + const response = session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: file.path } + }).response; + assert.isTrue(response); + assert.strictEqual( + host.readFile(ts.changeExtension(file.path, ".js")), + file === module ? + `"use strict";\nexports.__esModule = true;\nexports.xyz = void 0;\nexports.xyz = 4;\n` : + `${file.content.replace("const", "var")}\n` + ); + if (declaration) { assert.strictEqual( - host.readFile(changeExtension(file.path, ".js")), - file === module ? - `"use strict";\nexports.__esModule = true;\nexports.xyz = void 0;\nexports.xyz = 4;\n` : - `${file.content.replace("const", "var")}\n` + host.readFile(ts.changeExtension(file.path, ".d.ts")), + (file.content.substr(0, file.content.indexOf(" {") === -1 ? file.content.length : file.content.indexOf(" {")) + .replace("const ", "declare const ") + .replace("function ", "declare function ") + .replace(")", "): string;")) + "\n" ); - if (declaration) { - assert.strictEqual( - host.readFile(changeExtension(file.path, ".d.ts")), - (file.content.substr(0, file.content.indexOf(" {") === -1 ? file.content.length : file.content.indexOf(" {")) - .replace("const ", "declare const ") - .replace("function ", "declare function ") - .replace(")", "): string;")) + "\n" - ); - } } + } - function verifyLocalEdit(file: File, oldText: string, newText: string, returnsAllFilesAsAffected?: boolean) { - // Change file1 get affected file list - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: file.path, - textChanges: [{ - newText, - ...protocolTextSpanFromSubstring(file.content, oldText) - }] + function verifyLocalEdit(file: ts.projectSystem.File, oldText: string, newText: string, returnsAllFilesAsAffected?: boolean) { + // Change file1 get affected file list + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: file.path, + textChanges: [{ + newText, + ...ts.projectSystem.protocolTextSpanFromSubstring(file.content, oldText) }] - } - }); - const affectedFileResponse = session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: file.path } - }).response as protocol.CompileOnSaveAffectedFileListSingleProject[]; - assert.deepEqual(affectedFileResponse, [ - { fileNames: [file.path, ...(returnsAllFilesAsAffected ? files.filter(f => f !== file).map(f => f.path) : emptyArray)], projectFileName: config.path, projectUsesOutFile: false } - ]); - file.content = file.content.replace(oldText, newText); - verifyFileSave(file); - } + }] + } + }); + const affectedFileResponse = session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: file.path } + }).response as ts.projectSystem.protocol.CompileOnSaveAffectedFileListSingleProject[]; + assert.deepEqual(affectedFileResponse, [ + { fileNames: [file.path, ...(returnsAllFilesAsAffected ? files.filter(f => f !== file).map(f => f.path) : ts.emptyArray)], projectFileName: config.path, projectUsesOutFile: false } + ]); + file.content = file.content.replace(oldText, newText); + verifyFileSave(file); } - }); - }); - - describe("unittests:: tsserver:: compileOnSave:: CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => { - function insertString(session: TestSession, file: File) { - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: file.path, - line: 1, - offset: 1, - endLine: 1, - endOffset: 1, - insertString: "let k = 1" - } - }); } + }); +}); + +describe("unittests:: tsserver:: compileOnSave:: CompileOnSaveAffectedFileListRequest with and without projectFileName in request", () => { + function insertString(session: ts.projectSystem.TestSession, file: ts.projectSystem.File) { + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: file.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: "let k = 1" + } + }); + } - function logDirtyOfProjects(session: TestSession) { - session.logger.logs.push(`Project1 is dirty: ${session.getProjectService().configuredProjects.get(`${tscWatch.projectRoot}/app1/tsconfig.json`)!.dirty}`); - session.logger.logs.push(`Project2 is dirty: ${session.getProjectService().configuredProjects.get(`${tscWatch.projectRoot}/app2/tsconfig.json`)!.dirty}`); - } + function logDirtyOfProjects(session: ts.projectSystem.TestSession) { + session.logger.logs.push(`Project1 is dirty: ${session.getProjectService().configuredProjects.get(`${ts.tscWatch.projectRoot}/app1/tsconfig.json`)!.dirty}`); + session.logger.logs.push(`Project2 is dirty: ${session.getProjectService().configuredProjects.get(`${ts.tscWatch.projectRoot}/app2/tsconfig.json`)!.dirty}`); + } - function verify(subScenario: string, commandArgs: protocol.FileRequestArgs) { - it(subScenario, () => { - const core: File = { - path: `${tscWatch.projectRoot}/core/core.ts`, - content: "let z = 10;" - }; - const app1: File = { - path: `${tscWatch.projectRoot}/app1/app.ts`, - content: "let x = 10;" - }; - const app2: File = { - path: `${tscWatch.projectRoot}/app2/app.ts`, - content: "let y = 10;" - }; - const app1Config: File = { - path: `${tscWatch.projectRoot}/app1/tsconfig.json`, - content: JSON.stringify({ - files: ["app.ts", "../core/core.ts"], - compilerOptions: { outFile: "build/output.js" }, - compileOnSave: true - }) - }; - const app2Config: File = { - path: `${tscWatch.projectRoot}/app2/tsconfig.json`, - content: JSON.stringify({ - files: ["app.ts", "../core/core.ts"], - compilerOptions: { outFile: "build/output.js" }, - compileOnSave: true - }) - }; - const files = [libFile, core, app1, app2, app1Config, app2Config]; - const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([app1, app2, core], session); - insertString(session, app1); - insertString(session, app2); - logDirtyOfProjects(session); - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: commandArgs - }); - logDirtyOfProjects(session); - baselineTsserverLogs("compileOnSave", subScenario, session); + function verify(subScenario: string, commandArgs: ts.projectSystem.protocol.FileRequestArgs) { + it(subScenario, () => { + const core: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/core/core.ts`, + content: "let z = 10;" + }; + const app1: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/app1/app.ts`, + content: "let x = 10;" + }; + const app2: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/app2/app.ts`, + content: "let y = 10;" + }; + const app1Config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/app1/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts", "../core/core.ts"], + compilerOptions: { outFile: "build/output.js" }, + compileOnSave: true + }) + }; + const app2Config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/app2/tsconfig.json`, + content: JSON.stringify({ + files: ["app.ts", "../core/core.ts"], + compilerOptions: { outFile: "build/output.js" }, + compileOnSave: true + }) + }; + const files = [ts.projectSystem.libFile, core, app1, app2, app1Config, app2Config]; + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([app1, app2, core], session); + insertString(session, app1); + insertString(session, app2); + logDirtyOfProjects(session); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: commandArgs }); - } - verify("CompileOnSaveAffectedFileListRequest when projectFile is specified", { - file: `${tscWatch.projectRoot}/core/core.ts`, - projectFileName: `${tscWatch.projectRoot}/app1/tsconfig.json` - }); - verify("CompileOnSaveAffectedFileListRequest when projectFile is not specified", { - file: `${tscWatch.projectRoot}/core/core.ts`, + logDirtyOfProjects(session); + ts.projectSystem.baselineTsserverLogs("compileOnSave", subScenario, session); }); + } + verify("CompileOnSaveAffectedFileListRequest when projectFile is specified", { + file: `${ts.tscWatch.projectRoot}/core/core.ts`, + projectFileName: `${ts.tscWatch.projectRoot}/app1/tsconfig.json` }); -} + verify("CompileOnSaveAffectedFileListRequest when projectFile is not specified", { + file: `${ts.tscWatch.projectRoot}/core/core.ts`, + }); +}); diff --git a/src/testRunner/unittests/tsserver/completions.ts b/src/testRunner/unittests/tsserver/completions.ts index 3da4c8fe8ca28..4e046ae6ebadb 100644 --- a/src/testRunner/unittests/tsserver/completions.ts +++ b/src/testRunner/unittests/tsserver/completions.ts @@ -1,267 +1,267 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: completions", () => { - it("works", () => { - const aTs: File = { - path: "/a.ts", - content: "export const foo = 0;", - }; - const bTs: File = { - path: "/b.ts", - content: "foo", - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", - }; +import * as ts from "../../_namespaces/ts"; - const session = createSession(createServerHost([aTs, bTs, tsconfig])); - openFilesForSession([aTs, bTs], session); +describe("unittests:: tsserver:: completions", () => { + it("works", () => { + const aTs: ts.projectSystem.File = { + path: "/a.ts", + content: "export const foo = 0;", + }; + const bTs: ts.projectSystem.File = { + path: "/b.ts", + content: "foo", + }; + const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: "{}", + }; - const requestLocation: protocol.FileLocationRequestArgs = { - file: bTs.path, - line: 1, - offset: 3, - }; + const session = ts.projectSystem.createSession(ts.projectSystem.createServerHost([aTs, bTs, tsconfig])); + ts.projectSystem.openFilesForSession([aTs, bTs], session); - const response = executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { - ...requestLocation, - includeExternalModuleExports: true, - prefix: "foo", - }); - const entry: protocol.CompletionEntry = { - hasAction: true, - insertText: undefined, - isRecommended: undefined, - kind: ScriptElementKind.constElement, - kindModifiers: ScriptElementKindModifier.exportedModifier, - name: "foo", - replacementSpan: undefined, - isPackageJsonImport: undefined, - isImportStatementCompletion: undefined, - sortText: Completions.SortText.AutoImportSuggestions, - source: "/a", - sourceDisplay: undefined, - isSnippet: undefined, - data: { exportName: "foo", fileName: "/a.ts", ambientModuleName: undefined, isPackageJsonImport: undefined }, - labelDetails: undefined, - }; + const requestLocation: ts.projectSystem.protocol.FileLocationRequestArgs = { + file: bTs.path, + line: 1, + offset: 3, + }; + + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.CompletionInfo, { + ...requestLocation, + includeExternalModuleExports: true, + prefix: "foo", + }); + const entry: ts.projectSystem.protocol.CompletionEntry = { + hasAction: true, + insertText: undefined, + isRecommended: undefined, + kind: ts.ScriptElementKind.constElement, + kindModifiers: ts.ScriptElementKindModifier.exportedModifier, + name: "foo", + replacementSpan: undefined, + isPackageJsonImport: undefined, + isImportStatementCompletion: undefined, + sortText: ts.Completions.SortText.AutoImportSuggestions, + source: "/a", + sourceDisplay: undefined, + isSnippet: undefined, + data: { exportName: "foo", fileName: "/a.ts", ambientModuleName: undefined, isPackageJsonImport: undefined }, + labelDetails: undefined, + }; - // `data.exportMapKey` contains a SymbolId so should not be mocked up with an expected value here. - // Just assert that it's a string and then delete it so we can compare everything else with `deepEqual`. - const exportMapKey = (response?.entries[0].data as any)?.exportMapKey; - assert.isString(exportMapKey); - delete (response?.entries[0].data as any).exportMapKey; - assert.deepEqual(response, { - flags: CompletionInfoFlags.MayIncludeAutoImports, - isGlobalCompletion: true, - isIncomplete: undefined, - isMemberCompletion: false, - isNewIdentifierLocation: false, - optionalReplacementSpan: { start: { line: 1, offset: 1 }, end: { line: 1, offset: 4 } }, - entries: [entry], - }); + // `data.exportMapKey` contains a SymbolId so should not be mocked up with an expected value here. + // Just assert that it's a string and then delete it so we can compare everything else with `deepEqual`. + const exportMapKey = (response?.entries[0].data as any)?.exportMapKey; + assert.isString(exportMapKey); + delete (response?.entries[0].data as any).exportMapKey; + assert.deepEqual(response, { + flags: ts.CompletionInfoFlags.MayIncludeAutoImports, + isGlobalCompletion: true, + isIncomplete: undefined, + isMemberCompletion: false, + isNewIdentifierLocation: false, + optionalReplacementSpan: { start: { line: 1, offset: 1 }, end: { line: 1, offset: 4 } }, + entries: [entry], + }); - const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = { - ...requestLocation, - entryNames: [{ name: "foo", source: "/a", data: { exportName: "foo", fileName: "/a.ts", exportMapKey } }], - }; + const detailsRequestArgs: ts.projectSystem.protocol.CompletionDetailsRequestArgs = { + ...requestLocation, + entryNames: [{ name: "foo", source: "/a", data: { exportName: "foo", fileName: "/a.ts", exportMapKey } }], + }; - const detailsResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs); - const detailsCommon: protocol.CompletionEntryDetails & CompletionEntryDetails = { - displayParts: [ - keywordPart(SyntaxKind.ConstKeyword), - spacePart(), - displayPart("foo", SymbolDisplayPartKind.localName), - punctuationPart(SyntaxKind.ColonToken), - spacePart(), - displayPart("0", SymbolDisplayPartKind.stringLiteral), + const detailsResponse = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.CompletionDetails, detailsRequestArgs); + const detailsCommon: ts.projectSystem.protocol.CompletionEntryDetails & ts.CompletionEntryDetails = { + displayParts: [ + ts.keywordPart(ts.SyntaxKind.ConstKeyword), + ts.spacePart(), + ts.displayPart("foo", ts.SymbolDisplayPartKind.localName), + ts.punctuationPart(ts.SyntaxKind.ColonToken), + ts.spacePart(), + ts.displayPart("0", ts.SymbolDisplayPartKind.stringLiteral), + ], + documentation: ts.emptyArray, + kind: ts.ScriptElementKind.constElement, + kindModifiers: ts.ScriptElementKindModifier.exportedModifier, + name: "foo", + source: [{ text: "./a", kind: "text" }], + sourceDisplay: [{ text: "./a", kind: "text" }], + }; + assert.deepEqual(detailsResponse, [ + { + codeActions: [ + { + description: `Add import from "./a"`, + changes: [ + { + fileName: "/b.ts", + textChanges: [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: 'import { foo } from "./a";\n\n', + }, + ], + }, + ], + commands: undefined, + }, ], - documentation: emptyArray, - kind: ScriptElementKind.constElement, - kindModifiers: ScriptElementKindModifier.exportedModifier, - name: "foo", - source: [{ text: "./a", kind: "text" }], - sourceDisplay: [{ text: "./a", kind: "text" }], - }; - assert.deepEqual(detailsResponse, [ - { - codeActions: [ - { - description: `Add import from "./a"`, - changes: [ - { - fileName: "/b.ts", - textChanges: [ - { - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - newText: 'import { foo } from "./a";\n\n', - }, - ], - }, - ], - commands: undefined, - }, - ], - tags: [], - ...detailsCommon, - }, - ]); + tags: [], + ...detailsCommon, + }, + ]); - interface CompletionDetailsFullRequest extends protocol.FileLocationRequest { - readonly command: protocol.CommandTypes.CompletionDetailsFull; - readonly arguments: protocol.CompletionDetailsRequestArgs; - } - interface CompletionDetailsFullResponse extends protocol.Response { - readonly body?: readonly CompletionEntryDetails[]; + interface CompletionDetailsFullRequest extends ts.projectSystem.protocol.FileLocationRequest { + readonly command: ts.projectSystem.protocol.CommandTypes.CompletionDetailsFull; + readonly arguments: ts.projectSystem.protocol.CompletionDetailsRequestArgs; + } + interface CompletionDetailsFullResponse extends ts.projectSystem.protocol.Response { + readonly body?: readonly ts.CompletionEntryDetails[]; + } + const detailsFullResponse = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs); + assert.deepEqual(detailsFullResponse, [ + { + codeActions: [ + { + description: `Add import from "./a"`, + changes: [ + { + fileName: "/b.ts", + textChanges: [ts.createTextChange(ts.createTextSpan(0, 0), 'import { foo } from "./a";\n\n')], + }, + ], + commands: undefined, + } + ], + tags: [], + ...detailsCommon, } - const detailsFullResponse = executeSessionRequest(session, protocol.CommandTypes.CompletionDetailsFull, detailsRequestArgs); - assert.deepEqual(detailsFullResponse, [ - { - codeActions: [ - { - description: `Add import from "./a"`, - changes: [ - { - fileName: "/b.ts", - textChanges: [createTextChange(createTextSpan(0, 0), 'import { foo } from "./a";\n\n')], - }, - ], - commands: undefined, - } - ], - tags: [], - ...detailsCommon, - } - ]); - }); + ]); + }); - it("works when files are included from two different drives of windows", () => { - const projectRoot = "e:/myproject"; - const appPackage: File = { - path: `${projectRoot}/package.json`, - content: JSON.stringify({ - name: "test", - version: "0.1.0", - dependencies: { - "react": "^16.12.0", - "react-router-dom": "^5.1.2", - } - }) - }; - const appFile: File = { - path: `${projectRoot}/src/app.js`, - content: `import React from 'react'; + it("works when files are included from two different drives of windows", () => { + const projectRoot = "e:/myproject"; + const appPackage: ts.projectSystem.File = { + path: `${projectRoot}/package.json`, + content: JSON.stringify({ + name: "test", + version: "0.1.0", + dependencies: { + "react": "^16.12.0", + "react-router-dom": "^5.1.2", + } + }) + }; + const appFile: ts.projectSystem.File = { + path: `${projectRoot}/src/app.js`, + content: `import React from 'react'; import { BrowserRouter as Router, } from "react-router-dom"; ` - }; - const localNodeModules = `${projectRoot}/node_modules`; - const localAtTypes = `${localNodeModules}/@types`; - const localReactPackage: File = { - path: `${localAtTypes}/react/package.json`, - content: JSON.stringify({ - name: "@types/react", - version: "16.9.14", - }) - }; - const localReact: File = { - path: `${localAtTypes}/react/index.d.ts`, - content: `import * as PropTypes from 'prop-types'; + }; + const localNodeModules = `${projectRoot}/node_modules`; + const localAtTypes = `${localNodeModules}/@types`; + const localReactPackage: ts.projectSystem.File = { + path: `${localAtTypes}/react/package.json`, + content: JSON.stringify({ + name: "@types/react", + version: "16.9.14", + }) + }; + const localReact: ts.projectSystem.File = { + path: `${localAtTypes}/react/index.d.ts`, + content: `import * as PropTypes from 'prop-types'; ` - }; - const localReactRouterDomPackage: File = { - path: `${localNodeModules}/react-router-dom/package.json`, - content: JSON.stringify({ - name: "react-router-dom", - version: "5.1.2", - }) - }; - const localReactRouterDom: File = { - path: `${localNodeModules}/react-router-dom/index.js`, - content: `export function foo() {}` - }; - const localPropTypesPackage: File = { - path: `${localAtTypes}/prop-types/package.json`, - content: JSON.stringify({ - name: "@types/prop-types", - version: "15.7.3", - }) - }; - const localPropTypes: File = { - path: `${localAtTypes}/prop-types/index.d.ts`, - content: `export type ReactComponentLike = + }; + const localReactRouterDomPackage: ts.projectSystem.File = { + path: `${localNodeModules}/react-router-dom/package.json`, + content: JSON.stringify({ + name: "react-router-dom", + version: "5.1.2", + }) + }; + const localReactRouterDom: ts.projectSystem.File = { + path: `${localNodeModules}/react-router-dom/index.js`, + content: `export function foo() {}` + }; + const localPropTypesPackage: ts.projectSystem.File = { + path: `${localAtTypes}/prop-types/package.json`, + content: JSON.stringify({ + name: "@types/prop-types", + version: "15.7.3", + }) + }; + const localPropTypes: ts.projectSystem.File = { + path: `${localAtTypes}/prop-types/index.d.ts`, + content: `export type ReactComponentLike = | string | ((props: any, context?: any) => any) | (new (props: any, context?: any) => any); ` - }; + }; - const globalCacheLocation = `c:/typescript`; - const globalAtTypes = `${globalCacheLocation}/node_modules/@types`; - const globalReactRouterDomPackage: File = { - path: `${globalAtTypes}/react-router-dom/package.json`, - content: JSON.stringify({ - name: "@types/react-router-dom", - version: "5.1.2", - }) - }; - const globalReactRouterDom: File = { - path: `${globalAtTypes}/react-router-dom/index.d.ts`, - content: `import * as React from 'react'; + const globalCacheLocation = `c:/typescript`; + const globalAtTypes = `${globalCacheLocation}/node_modules/@types`; + const globalReactRouterDomPackage: ts.projectSystem.File = { + path: `${globalAtTypes}/react-router-dom/package.json`, + content: JSON.stringify({ + name: "@types/react-router-dom", + version: "5.1.2", + }) + }; + const globalReactRouterDom: ts.projectSystem.File = { + path: `${globalAtTypes}/react-router-dom/index.d.ts`, + content: `import * as React from 'react'; export interface BrowserRouterProps { basename?: string; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void); forceRefresh?: boolean; keyLength?: number; }` - }; - const globalReactPackage: File = { - path: `${globalAtTypes}/react/package.json`, - content: localReactPackage.content - }; - const globalReact: File = { - path: `${globalAtTypes}/react/index.d.ts`, - content: localReact.content - }; + }; + const globalReactPackage: ts.projectSystem.File = { + path: `${globalAtTypes}/react/package.json`, + content: localReactPackage.content + }; + const globalReact: ts.projectSystem.File = { + path: `${globalAtTypes}/react/index.d.ts`, + content: localReact.content + }; - const filesInProject = [ - appFile, - localReact, - localPropTypes, - globalReactRouterDom, - globalReact, - ]; - const files = [ - ...filesInProject, - appPackage, libFile, - localReactPackage, - localReactRouterDomPackage, localReactRouterDom, - localPropTypesPackage, - globalReactRouterDomPackage, - globalReactPackage, - ]; + const filesInProject = [ + appFile, + localReact, + localPropTypes, + globalReactRouterDom, + globalReact, + ]; + const files = [ + ...filesInProject, + appPackage, ts.projectSystem.libFile, + localReactPackage, + localReactRouterDomPackage, localReactRouterDom, + localPropTypesPackage, + globalReactRouterDomPackage, + globalReactPackage, + ]; - const host = createServerHost(files, { windowsStyleRoot: "c:/" }); - const session = createSession(host, { - typingsInstaller: new TestTypingsInstaller(globalCacheLocation, /*throttleLimit*/ 5, host), - }); - const service = session.getProjectService(); - openFilesForSession([appFile], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const windowsStyleLibFilePath = "c:/" + libFile.path.substring(1); - checkProjectActualFiles(service.inferredProjects[0], filesInProject.map(f => f.path).concat(windowsStyleLibFilePath)); - session.executeCommandSeq({ - command: protocol.CommandTypes.CompletionInfo, - arguments: { - file: appFile.path, - line: 5, - offset: 1, - includeExternalModuleExports: true, - includeInsertTextCompletions: true - } - }); + const host = ts.projectSystem.createServerHost(files, { windowsStyleRoot: "c:/" }); + const session = ts.projectSystem.createSession(host, { + typingsInstaller: new ts.projectSystem.TestTypingsInstaller(globalCacheLocation, /*throttleLimit*/ 5, host), + }); + const service = session.getProjectService(); + ts.projectSystem.openFilesForSession([appFile], session); + ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 }); + const windowsStyleLibFilePath = "c:/" + ts.projectSystem.libFile.path.substring(1); + ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], filesInProject.map(f => f.path).concat(windowsStyleLibFilePath)); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompletionInfo, + arguments: { + file: appFile.path, + line: 5, + offset: 1, + includeExternalModuleExports: true, + includeInsertTextCompletions: true + } }); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/completionsIncomplete.ts b/src/testRunner/unittests/tsserver/completionsIncomplete.ts index 0bdef496d1de1..e0d3ee5ed4eb6 100644 --- a/src/testRunner/unittests/tsserver/completionsIncomplete.ts +++ b/src/testRunner/unittests/tsserver/completionsIncomplete.ts @@ -1,262 +1,262 @@ -namespace ts.projectSystem { - function createExportingModuleFile(path: string, exportPrefix: string, exportCount: number): File { - return { - path, - content: fill(exportCount, i => `export const ${exportPrefix}_${i} = ${i};`).join("\n"), - }; - } +import * as ts from "../../_namespaces/ts"; - function createExportingModuleFiles(pathPrefix: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): File[] { - return fill(fileCount, fileIndex => createExportingModuleFile( - `${pathPrefix}_${fileIndex}.ts`, - getExportPrefix(fileIndex), - exportCount)); - } +function createExportingModuleFile(path: string, exportPrefix: string, exportCount: number): ts.projectSystem.File { + return { + path, + content: ts.fill(exportCount, i => `export const ${exportPrefix}_${i} = ${i};`).join("\n"), + }; +} - function createNodeModulesPackage(packageName: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): File[] { - const exportingFiles = createExportingModuleFiles(`/node_modules/${packageName}/file`, fileCount, exportCount, getExportPrefix); - return [ - { - path: `/node_modules/${packageName}/package.json`, - content: `{ "types": "index.d.ts" }`, - }, - { - path: `/node_modules/${packageName}/index.d.ts`, - content: exportingFiles - .map(f => `export * from "./${removeFileExtension(convertToRelativePath(f.path, `/node_modules/${packageName}/`, identity))}";`) - .join("\n") + `\nexport default function main(): void;`, - }, - ...exportingFiles, - ]; - } +function createExportingModuleFiles(pathPrefix: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): ts.projectSystem.File[] { + return ts.fill(fileCount, fileIndex => createExportingModuleFile( + `${pathPrefix}_${fileIndex}.ts`, + getExportPrefix(fileIndex), + exportCount)); +} - const indexFile: File = { - path: "/index.ts", - content: "" - }; +function createNodeModulesPackage(packageName: string, fileCount: number, exportCount: number, getExportPrefix: (fileIndex: number) => string): ts.projectSystem.File[] { + const exportingFiles = createExportingModuleFiles(`/node_modules/${packageName}/file`, fileCount, exportCount, getExportPrefix); + return [ + { + path: `/node_modules/${packageName}/package.json`, + content: `{ "types": "index.d.ts" }`, + }, + { + path: `/node_modules/${packageName}/index.d.ts`, + content: exportingFiles + .map(f => `export * from "./${ts.removeFileExtension(ts.convertToRelativePath(f.path, `/node_modules/${packageName}/`, ts.identity))}";`) + .join("\n") + `\nexport default function main(): void;`, + }, + ...exportingFiles, + ]; +} - const tsconfigFile: File = { - path: "/tsconfig.json", - content: `{ "compilerOptions": { "module": "commonjs" } }` - }; +const indexFile: ts.projectSystem.File = { + path: "/index.ts", + content: "" +}; - const packageJsonFile: File = { - path: "/package.json", - content: `{ "dependencies": { "dep-a": "*" } }`, - }; +const tsconfigFile: ts.projectSystem.File = { + path: "/tsconfig.json", + content: `{ "compilerOptions": { "module": "commonjs" } }` +}; - describe("unittests:: tsserver:: completionsIncomplete", () => { - it("works", () => { - const excessFileCount = Completions.moduleSpecifierResolutionLimit + 50; - const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit + excessFileCount, 1, i => `aa_${i}_`); - const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); - openFilesForSession([indexFile], session); +const packageJsonFile: ts.projectSystem.File = { + path: "/package.json", + content: `{ "dependencies": { "dep-a": "*" } }`, +}; - typeToTriggerCompletions(indexFile.path, "a", completions => { - assert(completions.isIncomplete); - assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), Completions.moduleSpecifierResolutionLimit); - assert.lengthOf(completions.entries.filter(entry => entry.source && !(entry.data as any)?.moduleSpecifier), excessFileCount); - }) - .continueTyping("a", completions => { - assert(completions.isIncomplete); - assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), Completions.moduleSpecifierResolutionLimit * 2); - }) - .continueTyping("_", completions => { - assert(!completions.isIncomplete); - assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), exportingFiles.length); - }); +describe("unittests:: tsserver:: completionsIncomplete", () => { + it("works", () => { + const excessFileCount = ts.Completions.moduleSpecifierResolutionLimit + 50; + const exportingFiles = createExportingModuleFiles(`/lib/a`, ts.Completions.moduleSpecifierResolutionLimit + excessFileCount, 1, i => `aa_${i}_`); + const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); + ts.projectSystem.openFilesForSession([indexFile], session); + + typeToTriggerCompletions(indexFile.path, "a", completions => { + assert(completions.isIncomplete); + assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), ts.Completions.moduleSpecifierResolutionLimit); + assert.lengthOf(completions.entries.filter(entry => entry.source && !(entry.data as any)?.moduleSpecifier), excessFileCount); + }) + .continueTyping("a", completions => { + assert(completions.isIncomplete); + assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), ts.Completions.moduleSpecifierResolutionLimit * 2); + }) + .continueTyping("_", completions => { + assert(!completions.isIncomplete); + assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier), exportingFiles.length); }); + }); - it("resolves more when available from module specifier cache (1)", () => { - const exportingFiles = createExportingModuleFiles(`/lib/a`, 50, 50, i => `aa_${i}_`); - const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); - openFilesForSession([indexFile], session); + it("resolves more when available from module specifier cache (1)", () => { + const exportingFiles = createExportingModuleFiles(`/lib/a`, 50, 50, i => `aa_${i}_`); + const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); + ts.projectSystem.openFilesForSession([indexFile], session); - typeToTriggerCompletions(indexFile.path, "a", completions => { - assert(!completions.isIncomplete); - }); + typeToTriggerCompletions(indexFile.path, "a", completions => { + assert(!completions.isIncomplete); }); + }); - it("resolves more when available from module specifier cache (2)", () => { - const excessFileCount = 50; - const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit + excessFileCount, 1, i => `aa_${i}_`); - const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); - openFilesForSession([indexFile], session); + it("resolves more when available from module specifier cache (2)", () => { + const excessFileCount = 50; + const exportingFiles = createExportingModuleFiles(`/lib/a`, ts.Completions.moduleSpecifierResolutionLimit + excessFileCount, 1, i => `aa_${i}_`); + const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles]); + ts.projectSystem.openFilesForSession([indexFile], session); - typeToTriggerCompletions(indexFile.path, "a", completions => assert(completions.isIncomplete)) - .backspace() - .type("a", completions => assert(!completions.isIncomplete)); - }); + typeToTriggerCompletions(indexFile.path, "a", completions => assert(completions.isIncomplete)) + .backspace() + .type("a", completions => assert(!completions.isIncomplete)); + }); - it("ambient module specifier resolutions do not count against the resolution limit", () => { - const ambientFiles = fill(100, (i): File => ({ - path: `/lib/ambient_${i}.ts`, - content: `declare module "ambient_${i}" { export const aa_${i} = ${i}; }`, - })); + it("ambient module specifier resolutions do not count against the resolution limit", () => { + const ambientFiles = ts.fill(100, (i): ts.projectSystem.File => ({ + path: `/lib/ambient_${i}.ts`, + content: `declare module "ambient_${i}" { export const aa_${i} = ${i}; }`, + })); - const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit, 5, i => `aa_${i}_`); - const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...ambientFiles, ...exportingFiles]); - openFilesForSession([indexFile], session); + const exportingFiles = createExportingModuleFiles(`/lib/a`, ts.Completions.moduleSpecifierResolutionLimit, 5, i => `aa_${i}_`); + const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...ambientFiles, ...exportingFiles]); + ts.projectSystem.openFilesForSession([indexFile], session); - typeToTriggerCompletions(indexFile.path, "a", completions => { - assert(!completions.isIncomplete); - assert.lengthOf(completions.entries.filter(e => (e.data as any)?.moduleSpecifier), ambientFiles.length * 5 + exportingFiles.length); - }); + typeToTriggerCompletions(indexFile.path, "a", completions => { + assert(!completions.isIncomplete); + assert.lengthOf(completions.entries.filter(e => (e.data as any)?.moduleSpecifier), ambientFiles.length * 5 + exportingFiles.length); }); + }); - it("works with PackageJsonAutoImportProvider", () => { - const exportingFiles = createExportingModuleFiles(`/lib/a`, Completions.moduleSpecifierResolutionLimit, 1, i => `aa_${i}_`); - const nodeModulesPackage = createNodeModulesPackage("dep-a", 50, 1, i => `depA_${i}_`); - const { typeToTriggerCompletions, assertCompletionDetailsOk, session } = setup([tsconfigFile, packageJsonFile, indexFile, ...exportingFiles, ...nodeModulesPackage]); - openFilesForSession([indexFile], session); + it("works with PackageJsonAutoImportProvider", () => { + const exportingFiles = createExportingModuleFiles(`/lib/a`, ts.Completions.moduleSpecifierResolutionLimit, 1, i => `aa_${i}_`); + const nodeModulesPackage = createNodeModulesPackage("dep-a", 50, 1, i => `depA_${i}_`); + const { typeToTriggerCompletions, assertCompletionDetailsOk, session } = setup([tsconfigFile, packageJsonFile, indexFile, ...exportingFiles, ...nodeModulesPackage]); + ts.projectSystem.openFilesForSession([indexFile], session); - typeToTriggerCompletions(indexFile.path, "a", completions => assert(completions.isIncomplete)) - .continueTyping("_", completions => { - assert(!completions.isIncomplete); - assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier?.startsWith("dep-a")), 50); - assertCompletionDetailsOk( - indexFile.path, - completions.entries.find(entry => (entry.data as any)?.moduleSpecifier?.startsWith("dep-a"))!); - }); - }); + typeToTriggerCompletions(indexFile.path, "a", completions => assert(completions.isIncomplete)) + .continueTyping("_", completions => { + assert(!completions.isIncomplete); + assert.lengthOf(completions.entries.filter(entry => (entry.data as any)?.moduleSpecifier?.startsWith("dep-a")), 50); + assertCompletionDetailsOk( + indexFile.path, + completions.entries.find(entry => (entry.data as any)?.moduleSpecifier?.startsWith("dep-a"))!); + }); + }); - it("works for transient symbols between requests", () => { - const constantsDts: File = { - path: "/lib/foo/constants.d.ts", - content: ` + it("works for transient symbols between requests", () => { + const constantsDts: ts.projectSystem.File = { + path: "/lib/foo/constants.d.ts", + content: ` type Signals = "SIGINT" | "SIGABRT"; declare const exp: {} & { [K in Signals]: K }; export = exp;`, - }; - const exportingFiles = createExportingModuleFiles("/lib/a", Completions.moduleSpecifierResolutionLimit, 1, i => `S${i}`); - const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles, constantsDts]); - openFilesForSession([indexFile], session); + }; + const exportingFiles = createExportingModuleFiles("/lib/a", ts.Completions.moduleSpecifierResolutionLimit, 1, i => `S${i}`); + const { typeToTriggerCompletions, session } = setup([tsconfigFile, indexFile, ...exportingFiles, constantsDts]); + ts.projectSystem.openFilesForSession([indexFile], session); - typeToTriggerCompletions(indexFile.path, "s", completions => { - const sigint = completions.entries.find(e => e.name === "SIGINT"); - assert(sigint); - assert(!(sigint.data as any).moduleSpecifier); - }) - .continueTyping("i", completions => { - const sigint = completions.entries.find(e => e.name === "SIGINT"); - assert((sigint!.data as any).moduleSpecifier); - }); + typeToTriggerCompletions(indexFile.path, "s", completions => { + const sigint = completions.entries.find(e => e.name === "SIGINT"); + assert(sigint); + assert(!(sigint.data as any).moduleSpecifier); + }) + .continueTyping("i", completions => { + const sigint = completions.entries.find(e => e.name === "SIGINT"); + assert((sigint!.data as any).moduleSpecifier); }); }); +}); - function setup(files: File[]) { - const host = createServerHost(files); - const session = createSession(host); - const projectService = session.getProjectService(); - session.executeCommandSeq({ - command: protocol.CommandTypes.Configure, - arguments: { - preferences: { - allowIncompleteCompletions: true, - includeCompletionsForModuleExports: true, - includeCompletionsWithInsertText: true, - includeCompletionsForImportStatements: true, - includePackageJsonAutoImports: "auto", - } +function setup(files: ts.projectSystem.File[]) { + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host); + const projectService = session.getProjectService(); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Configure, + arguments: { + preferences: { + allowIncompleteCompletions: true, + includeCompletionsForModuleExports: true, + includeCompletionsWithInsertText: true, + includeCompletionsForImportStatements: true, + includePackageJsonAutoImports: "auto", } - }); + } + }); - return { host, session, projectService, typeToTriggerCompletions, assertCompletionDetailsOk }; + return { host, session, projectService, typeToTriggerCompletions, assertCompletionDetailsOk }; - function typeToTriggerCompletions(fileName: string, typedCharacters: string, cb?: (completions: protocol.CompletionInfo) => void) { - const project = projectService.getDefaultProjectForFile(server.toNormalizedPath(fileName), /*ensureProject*/ true)!; - return type(typedCharacters, cb, /*isIncompleteContinuation*/ false); + function typeToTriggerCompletions(fileName: string, typedCharacters: string, cb?: (completions: ts.projectSystem.protocol.CompletionInfo) => void) { + const project = projectService.getDefaultProjectForFile(ts.server.toNormalizedPath(fileName), /*ensureProject*/ true)!; + return type(typedCharacters, cb, /*isIncompleteContinuation*/ false); - function type(typedCharacters: string, cb: ((completions: protocol.CompletionInfo) => void) | undefined, isIncompleteContinuation: boolean) { - const file = Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName)); - const { line, character } = getLineAndCharacterOfPosition(file, file.text.length); - const oneBasedEditPosition = { line: line + 1, offset: character + 1 }; - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName, - textChanges: [{ - newText: typedCharacters, - start: oneBasedEditPosition, - end: oneBasedEditPosition, - }], + function type(typedCharacters: string, cb: ((completions: ts.projectSystem.protocol.CompletionInfo) => void) | undefined, isIncompleteContinuation: boolean) { + const file = ts.Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName)); + const { line, character } = ts.getLineAndCharacterOfPosition(file, file.text.length); + const oneBasedEditPosition = { line: line + 1, offset: character + 1 }; + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName, + textChanges: [{ + newText: typedCharacters, + start: oneBasedEditPosition, + end: oneBasedEditPosition, }], - }, - }); + }], + }, + }); - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.CompletionInfo, - arguments: { - file: fileName, - line: oneBasedEditPosition.line, - offset: oneBasedEditPosition.offset, - triggerKind: isIncompleteContinuation - ? protocol.CompletionTriggerKind.TriggerForIncompleteCompletions - : undefined, - } - }).response as protocol.CompletionInfo; + const response = session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompletionInfo, + arguments: { + file: fileName, + line: oneBasedEditPosition.line, + offset: oneBasedEditPosition.offset, + triggerKind: isIncompleteContinuation + ? ts.projectSystem.protocol.CompletionTriggerKind.TriggerForIncompleteCompletions + : undefined, + } + }).response as ts.projectSystem.protocol.CompletionInfo; - cb?.(Debug.checkDefined(response)); - return { - backspace, - continueTyping: (typedCharacters: string, cb: (completions: protocol.CompletionInfo) => void) => { - return type(typedCharacters, cb, !!response.isIncomplete); - }, - }; - } + cb?.(ts.Debug.checkDefined(response)); + return { + backspace, + continueTyping: (typedCharacters: string, cb: (completions: ts.projectSystem.protocol.CompletionInfo) => void) => { + return type(typedCharacters, cb, !!response.isIncomplete); + }, + }; + } - function backspace(n = 1) { - const file = Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName)); - const startLineCharacter = getLineAndCharacterOfPosition(file, file.text.length - n); - const endLineCharacter = getLineAndCharacterOfPosition(file, file.text.length); - const oneBasedStartPosition = { line: startLineCharacter.line + 1, offset: startLineCharacter.character + 1 }; - const oneBasedEndPosition = { line: endLineCharacter.line + 1, offset: endLineCharacter.character + 1 }; - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName, - textChanges: [{ - newText: "", - start: oneBasedStartPosition, - end: oneBasedEndPosition, - }], + function backspace(n = 1) { + const file = ts.Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName)); + const startLineCharacter = ts.getLineAndCharacterOfPosition(file, file.text.length - n); + const endLineCharacter = ts.getLineAndCharacterOfPosition(file, file.text.length); + const oneBasedStartPosition = { line: startLineCharacter.line + 1, offset: startLineCharacter.character + 1 }; + const oneBasedEndPosition = { line: endLineCharacter.line + 1, offset: endLineCharacter.character + 1 }; + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName, + textChanges: [{ + newText: "", + start: oneBasedStartPosition, + end: oneBasedEndPosition, }], - }, - }); + }], + }, + }); - return { - backspace, - type: (typedCharacters: string, cb: (completions: protocol.CompletionInfo) => void) => { - return type(typedCharacters, cb, /*isIncompleteContinuation*/ false); - }, - }; - } + return { + backspace, + type: (typedCharacters: string, cb: (completions: ts.projectSystem.protocol.CompletionInfo) => void) => { + return type(typedCharacters, cb, /*isIncompleteContinuation*/ false); + }, + }; } + } - function assertCompletionDetailsOk(fileName: string, entry: protocol.CompletionEntry) { - const project = projectService.getDefaultProjectForFile(server.toNormalizedPath(fileName), /*ensureProject*/ true)!; - const file = Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName)); - const { line, character } = getLineAndCharacterOfPosition(file, file.text.length - 1); - const details = session.executeCommandSeq({ - command: protocol.CommandTypes.CompletionDetails, - arguments: { - file: fileName, - line: line + 1, - offset: character + 1, - entryNames: [{ - name: entry.name, - source: entry.source, - data: entry.data, - }] - } - }).response as protocol.CompletionEntryDetails[]; + function assertCompletionDetailsOk(fileName: string, entry: ts.projectSystem.protocol.CompletionEntry) { + const project = projectService.getDefaultProjectForFile(ts.server.toNormalizedPath(fileName), /*ensureProject*/ true)!; + const file = ts.Debug.checkDefined(project.getLanguageService(/*ensureSynchronized*/ true).getProgram()?.getSourceFile(fileName)); + const { line, character } = ts.getLineAndCharacterOfPosition(file, file.text.length - 1); + const details = session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompletionDetails, + arguments: { + file: fileName, + line: line + 1, + offset: character + 1, + entryNames: [{ + name: entry.name, + source: entry.source, + data: entry.data, + }] + } + }).response as ts.projectSystem.protocol.CompletionEntryDetails[]; - assert(details[0]); - assert(details[0].codeActions); - assert(details[0].codeActions[0].changes[0].textChanges[0].newText.includes(`"${(entry.data as any).moduleSpecifier}"`)); - return details; - } + assert(details[0]); + assert(details[0].codeActions); + assert(details[0].codeActions[0].changes[0].textChanges[0].newText.includes(`"${(entry.data as any).moduleSpecifier}"`)); + return details; } } diff --git a/src/testRunner/unittests/tsserver/configFileSearch.ts b/src/testRunner/unittests/tsserver/configFileSearch.ts index a81bedb6431e0..8ed168c683b97 100644 --- a/src/testRunner/unittests/tsserver/configFileSearch.ts +++ b/src/testRunner/unittests/tsserver/configFileSearch.ts @@ -1,136 +1,136 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: searching for config file", () => { - it("should stop at projectRootPath if given", () => { - const f1 = { - path: "/a/file1.ts", - content: "" - }; - const configFile = { - path: "/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, configFile]); - const service = createProjectService(host); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a"); - - checkNumberOfConfiguredProjects(service, 0); - checkNumberOfInferredProjects(service, 1); - - service.closeClientFile(f1.path); - service.openClientFile(f1.path); - checkNumberOfConfiguredProjects(service, 1); - checkNumberOfInferredProjects(service, 0); - }); +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: searching for config file", () => { + it("should stop at projectRootPath if given", () => { + const f1 = { + path: "/a/file1.ts", + content: "" + }; + const configFile = { + path: "/tsconfig.json", + content: "{}" + }; + const host = ts.projectSystem.createServerHost([f1, configFile]); + const service = ts.projectSystem.createProjectService(host); + service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a"); + + ts.projectSystem.checkNumberOfConfiguredProjects(service, 0); + ts.projectSystem.checkNumberOfInferredProjects(service, 1); + + service.closeClientFile(f1.path); + service.openClientFile(f1.path); + ts.projectSystem.checkNumberOfConfiguredProjects(service, 1); + ts.projectSystem.checkNumberOfInferredProjects(service, 0); + }); - it("should use projectRootPath when searching for inferred project again", () => { - const projectDir = "/a/b/projects/project"; - const configFileLocation = `${projectDir}/src`; - const f1 = { - path: `${configFileLocation}/file1.ts`, - content: "" - }; - const configFile = { - path: `${configFileLocation}/tsconfig.json`, - content: "{}" - }; - const configFile2 = { - path: "/a/b/projects/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, libFile, configFile, configFile2]); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); - - // Delete config file - should create inferred project and not configured project - host.deleteFile(configFile.path); - host.runQueuedTimeoutCallbacks(); - checkNumberOfProjects(service, { inferredProjects: 1 }); - baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again", service); + it("should use projectRootPath when searching for inferred project again", () => { + const projectDir = "/a/b/projects/project"; + const configFileLocation = `${projectDir}/src`; + const f1 = { + path: `${configFileLocation}/file1.ts`, + content: "" + }; + const configFile = { + path: `${configFileLocation}/tsconfig.json`, + content: "{}" + }; + const configFile2 = { + path: "/a/b/projects/tsconfig.json", + content: "{}" + }; + const host = ts.projectSystem.createServerHost([f1, ts.projectSystem.libFile, configFile, configFile2]); + const service = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); + + // Delete config file - should create inferred project and not configured project + host.deleteFile(configFile.path); + host.runQueuedTimeoutCallbacks(); + ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 }); + ts.projectSystem.baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again", service); + }); + + it("should use projectRootPath when searching for inferred project again 2", () => { + const projectDir = "/a/b/projects/project"; + const configFileLocation = `${projectDir}/src`; + const f1 = { + path: `${configFileLocation}/file1.ts`, + content: "" + }; + const configFile = { + path: `${configFileLocation}/tsconfig.json`, + content: "{}" + }; + const configFile2 = { + path: "/a/b/projects/tsconfig.json", + content: "{}" + }; + const host = ts.projectSystem.createServerHost([f1, ts.projectSystem.libFile, configFile, configFile2]); + const service = ts.projectSystem.createProjectService(host, { + useSingleInferredProject: true, + useInferredProjectPerProjectRoot: true, + logger: ts.projectSystem.createLoggerWithInMemoryLogs(host), }); + service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); - it("should use projectRootPath when searching for inferred project again 2", () => { - const projectDir = "/a/b/projects/project"; - const configFileLocation = `${projectDir}/src`; - const f1 = { - path: `${configFileLocation}/file1.ts`, - content: "" - }; - const configFile = { - path: `${configFileLocation}/tsconfig.json`, - content: "{}" - }; - const configFile2 = { - path: "/a/b/projects/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, libFile, configFile, configFile2]); - const service = createProjectService(host, { - useSingleInferredProject: true, - useInferredProjectPerProjectRoot: true, - logger: createLoggerWithInMemoryLogs(host), - }); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); + // Delete config file - should create inferred project with project root path set + host.deleteFile(configFile.path); + host.runQueuedTimeoutCallbacks(); + ts.projectSystem.baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again 2", service); + }); - // Delete config file - should create inferred project with project root path set - host.deleteFile(configFile.path); + describe("when the opened file is not from project root", () => { + const projectRoot = "/a/b/projects/project"; + const file: ts.projectSystem.File = { + path: `${projectRoot}/src/index.ts`, + content: "let y = 10" + }; + const tsconfig: ts.projectSystem.File = { + path: `${projectRoot}/tsconfig.json`, + content: "{}" + }; + function openClientFile(files: ts.projectSystem.File[]) { + const host = ts.projectSystem.createServerHost(files); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + projectService.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a/b/projects/proj"); + return { host, projectService }; + } + + it("tsconfig for the file exists", () => { + const { host, projectService } = openClientFile([file, ts.projectSystem.libFile, tsconfig]); + + host.deleteFile(tsconfig.path); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again 2", service); - }); - describe("when the opened file is not from project root", () => { - const projectRoot = "/a/b/projects/project"; - const file: File = { - path: `${projectRoot}/src/index.ts`, - content: "let y = 10" - }; - const tsconfig: File = { - path: `${projectRoot}/tsconfig.json`, - content: "{}" - }; - function openClientFile(files: File[]) { - const host = createServerHost(files); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a/b/projects/proj"); - return { host, projectService }; - } - - it("tsconfig for the file exists", () => { - const { host, projectService } = openClientFile([file, libFile, tsconfig]); - - host.deleteFile(tsconfig.path); - host.runQueuedTimeoutCallbacks(); - - host.writeFile(tsconfig.path, tsconfig.content); - host.runQueuedTimeoutCallbacks(); - - baselineTsserverLogs("configFileSearch", "tsconfig for the file exists", projectService); - }); + host.writeFile(tsconfig.path, tsconfig.content); + host.runQueuedTimeoutCallbacks(); - it("tsconfig for the file does not exist", () => { - const { host, projectService } = openClientFile([file, libFile]); + ts.projectSystem.baselineTsserverLogs("configFileSearch", "tsconfig for the file exists", projectService); + }); - host.writeFile(tsconfig.path, tsconfig.content); - host.runQueuedTimeoutCallbacks(); + it("tsconfig for the file does not exist", () => { + const { host, projectService } = openClientFile([file, ts.projectSystem.libFile]); - host.deleteFile(tsconfig.path); - host.runQueuedTimeoutCallbacks(); + host.writeFile(tsconfig.path, tsconfig.content); + host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configFileSearch", "tsconfig for the file does not exist", projectService); - }); - }); + host.deleteFile(tsconfig.path); + host.runQueuedTimeoutCallbacks(); - describe("should not search and watch config files from directories that cannot be watched", () => { - function verifyConfigFileWatch(scenario: string, projectRootPath: string | undefined) { - it(scenario, () => { - const path = `/root/teams/VSCode68/Shared Documents/General/jt-ts-test-workspace/x.js`; - const host = createServerHost([libFile, { path, content: "const x = 10" }], { useCaseSensitiveFileNames: true }); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRootPath); - baselineTsserverLogs("configFileSearch", scenario, service); - }); - } - verifyConfigFileWatch("when projectRootPath is not present", /*projectRootPath*/ undefined); - verifyConfigFileWatch("when projectRootPath is present but file is not from project root", "/a/b"); + ts.projectSystem.baselineTsserverLogs("configFileSearch", "tsconfig for the file does not exist", projectService); }); }); -} + + describe("should not search and watch config files from directories that cannot be watched", () => { + function verifyConfigFileWatch(scenario: string, projectRootPath: string | undefined) { + it(scenario, () => { + const path = `/root/teams/VSCode68/Shared Documents/General/jt-ts-test-workspace/x.js`; + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, { path, content: "const x = 10" }], { useCaseSensitiveFileNames: true }); + const service = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + service.openClientFile(path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRootPath); + ts.projectSystem.baselineTsserverLogs("configFileSearch", scenario, service); + }); + } + verifyConfigFileWatch("when projectRootPath is not present", /*projectRootPath*/ undefined); + verifyConfigFileWatch("when projectRootPath is present but file is not from project root", "/a/b"); + }); +}); diff --git a/src/testRunner/unittests/tsserver/configuredProjects.ts b/src/testRunner/unittests/tsserver/configuredProjects.ts index efe430930b19b..9e3ebe7e5ae3e 100644 --- a/src/testRunner/unittests/tsserver/configuredProjects.ts +++ b/src/testRunner/unittests/tsserver/configuredProjects.ts @@ -1,1260 +1,1260 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: ConfiguredProjects", () => { - it("create configured project without file list", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: ` +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: ConfiguredProjects", () => { + it("create configured project without file list", () => { + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: ` { "compilerOptions": {}, "exclude": [ "e" ] }` - }; - const file1: File = { - path: "/a/b/c/f1.ts", - content: "let x = 1" - }; - const file2: File = { - path: "/a/b/d/f2.ts", - content: "let y = 1" - }; - const file3: File = { - path: "/a/b/e/f3.ts", - content: "let z = 1" - }; - - const host = createServerHost([configFile, libFile, file1, file2, file3]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); - - assert(configFileName, "should find config file"); - assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); - - baselineTsserverLogs("configuredProjects", "create configured project without file list", projectService); - }); + }; + const file1: ts.projectSystem.File = { + path: "/a/b/c/f1.ts", + content: "let x = 1" + }; + const file2: ts.projectSystem.File = { + path: "/a/b/d/f2.ts", + content: "let y = 1" + }; + const file3: ts.projectSystem.File = { + path: "/a/b/e/f3.ts", + content: "let z = 1" + }; + + const host = ts.projectSystem.createServerHost([configFile, ts.projectSystem.libFile, file1, file2, file3]); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); + + assert(configFileName, "should find config file"); + assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); + + ts.projectSystem.baselineTsserverLogs("configuredProjects", "create configured project without file list", projectService); + }); - it("create configured project with the file list", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: ` + it("create configured project with the file list", () => { + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: ` { "compilerOptions": {}, "include": ["*.ts"] }` - }; - const file1: File = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2: File = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const file3: File = { - path: "/a/b/c/f3.ts", - content: "let z = 1" - }; - - const host = createServerHost([configFile, libFile, file1, file2, file3]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); - - assert(configFileName, "should find config file"); - assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); - - baselineTsserverLogs("configuredProjects", "create configured project with the file list", projectService); - }); + }; + const file1: ts.projectSystem.File = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2: ts.projectSystem.File = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const file3: ts.projectSystem.File = { + path: "/a/b/c/f3.ts", + content: "let z = 1" + }; + + const host = ts.projectSystem.createServerHost([configFile, ts.projectSystem.libFile, file1, file2, file3]); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); + + assert(configFileName, "should find config file"); + assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); + + ts.projectSystem.baselineTsserverLogs("configuredProjects", "create configured project with the file list", projectService); + }); - it("add and then remove a config file in a folder with loose files", () => { - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: `{ + it("add and then remove a config file in a folder with loose files", () => { + const configFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: `{ "files": ["commonFile1.ts"] }` - }; - const commonFile1: File = { - path: `${tscWatch.projectRoot}/commonFile1.ts`, - content: "let x = 1" - }; - const commonFile2: File = { - path: `${tscWatch.projectRoot}/commonFile2.ts`, - content: "let y = 1" - }; - - const host = createServerHost([libFile, commonFile1, commonFile2]); - - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(commonFile1.path); - projectService.openClientFile(commonFile2.path); - - // Add a tsconfig file - host.writeFile(configFile.path, configFile.content); - host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles - - // remove the tsconfig file - host.deleteFile(configFile.path); - host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects - - baselineTsserverLogs("configuredProjects", "add and then remove a config file in a folder with loose files", projectService); - }); - - it("add new files to a configured project without file list", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{}` - }; - const host = createServerHost([commonFile1, libFile, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(commonFile1.path); + }; + const commonFile1: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/commonFile1.ts`, + content: "let x = 1" + }; + const commonFile2: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/commonFile2.ts`, + content: "let y = 1" + }; + + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, commonFile1, commonFile2]); + + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + projectService.openClientFile(commonFile1.path); + projectService.openClientFile(commonFile2.path); + + // Add a tsconfig file + host.writeFile(configFile.path, configFile.content); + host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles + + // remove the tsconfig file + host.deleteFile(configFile.path); + host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects + + ts.projectSystem.baselineTsserverLogs("configuredProjects", "add and then remove a config file in a folder with loose files", projectService); + }); - // add a new ts file - host.writeFile(commonFile2.path, commonFile2.content); - host.checkTimeoutQueueLengthAndRun(2); - baselineTsserverLogs("configuredProjects", "add new files to a configured project without file list", projectService); - }); + it("add new files to a configured project without file list", () => { + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = ts.projectSystem.createServerHost([ts.projectSystem.commonFile1, ts.projectSystem.libFile, configFile]); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + projectService.openClientFile(ts.projectSystem.commonFile1.path); + + // add a new ts file + host.writeFile(ts.projectSystem.commonFile2.path, ts.projectSystem.commonFile2.content); + host.checkTimeoutQueueLengthAndRun(2); + ts.projectSystem.baselineTsserverLogs("configuredProjects", "add new files to a configured project without file list", projectService); + }); - it("should ignore non-existing files specified in the config file", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should ignore non-existing files specified in the config file", () => { + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {}, "files": [ "commonFile1.ts", "commonFile3.ts" ] }` - }; - const host = createServerHost([commonFile1, commonFile2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - projectService.openClientFile(commonFile2.path); - - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path]); - checkNumberOfInferredProjects(projectService, 1); - }); - - it("handle recreated files correctly", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{}` - }; - const host = createServerHost([commonFile1, commonFile2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - - // delete commonFile2 - host.deleteFile(commonFile2.path); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectRootFiles(project, [commonFile1.path]); + }; + const host = ts.projectSystem.createServerHost([ts.projectSystem.commonFile1, ts.projectSystem.commonFile2, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(ts.projectSystem.commonFile1.path); + projectService.openClientFile(ts.projectSystem.commonFile2.path); + + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + ts.projectSystem.checkProjectRootFiles(project, [ts.projectSystem.commonFile1.path]); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 1); + }); - // re-add commonFile2 - host.writeFile(commonFile2.path, commonFile2.content); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - }); + it("handle recreated files correctly", () => { + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = ts.projectSystem.createServerHost([ts.projectSystem.commonFile1, ts.projectSystem.commonFile2, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(ts.projectSystem.commonFile1.path); + + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + ts.projectSystem.checkProjectRootFiles(project, [ts.projectSystem.commonFile1.path, ts.projectSystem.commonFile2.path]); + + // delete commonFile2 + host.deleteFile(ts.projectSystem.commonFile2.path); + host.checkTimeoutQueueLengthAndRun(2); + ts.projectSystem.checkProjectRootFiles(project, [ts.projectSystem.commonFile1.path]); + + // re-add commonFile2 + host.writeFile(ts.projectSystem.commonFile2.path, ts.projectSystem.commonFile2.content); + host.checkTimeoutQueueLengthAndRun(2); + ts.projectSystem.checkProjectRootFiles(project, [ts.projectSystem.commonFile1.path, ts.projectSystem.commonFile2.path]); + }); - it("files explicitly excluded in config file", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("files explicitly excluded in config file", () => { + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {}, "exclude": ["/a/c"] }` - }; - const excludedFile1: File = { - path: "/a/c/excluedFile1.ts", - content: `let t = 1;` - }; - - const host = createServerHost([commonFile1, commonFile2, excludedFile1, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(commonFile1.path); - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - projectService.openClientFile(excludedFile1.path); - checkNumberOfInferredProjects(projectService, 1); - }); + }; + const excludedFile1: ts.projectSystem.File = { + path: "/a/c/excluedFile1.ts", + content: `let t = 1;` + }; + + const host = ts.projectSystem.createServerHost([ts.projectSystem.commonFile1, ts.projectSystem.commonFile2, excludedFile1, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(ts.projectSystem.commonFile1.path); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + ts.projectSystem.checkProjectRootFiles(project, [ts.projectSystem.commonFile1.path, ts.projectSystem.commonFile2.path]); + projectService.openClientFile(excludedFile1.path); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 1); + }); - it("should properly handle module resolution changes in config file", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: `import { T } from "module1";` - }; - const nodeModuleFile: File = { - path: "/a/b/node_modules/module1.ts", - content: `export interface T {}` - }; - const classicModuleFile: File = { - path: "/a/module1.ts", - content: `export interface T {}` - }; - const randomFile: File = { - path: "/a/file1.ts", - content: `export interface T {}` - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should properly handle module resolution changes in config file", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/file1.ts", + content: `import { T } from "module1";` + }; + const nodeModuleFile: ts.projectSystem.File = { + path: "/a/b/node_modules/module1.ts", + content: `export interface T {}` + }; + const classicModuleFile: ts.projectSystem.File = { + path: "/a/module1.ts", + content: `export interface T {}` + }; + const randomFile: ts.projectSystem.File = { + path: "/a/file1.ts", + content: `export interface T {}` + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "moduleResolution": "node" }, "files": ["${file1.path}"] }` - }; - const files = [file1, nodeModuleFile, classicModuleFile, configFile, randomFile]; - const host = createServerHost(files); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - projectService.openClientFile(nodeModuleFile.path); - projectService.openClientFile(classicModuleFile.path); - - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - const project = configuredProjectAt(projectService, 0); - const inferredProject0 = projectService.inferredProjects[0]; - checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]); - checkProjectActualFiles(projectService.inferredProjects[0], [classicModuleFile.path]); - - host.writeFile(configFile.path, `{ + }; + const files = [file1, nodeModuleFile, classicModuleFile, configFile, randomFile]; + const host = ts.projectSystem.createServerHost(files); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(file1.path); + projectService.openClientFile(nodeModuleFile.path); + projectService.openClientFile(classicModuleFile.path); + + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + const inferredProject0 = projectService.inferredProjects[0]; + ts.projectSystem.checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [classicModuleFile.path]); + + host.writeFile(configFile.path, `{ "compilerOptions": { "moduleResolution": "classic" }, "files": ["${file1.path}"] }`); - host.checkTimeoutQueueLengthAndRun(2); - - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); // will not remove project 1 - checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); - assert.strictEqual(projectService.inferredProjects[0], inferredProject0); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - const inferredProject1 = projectService.inferredProjects[1]; - checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); - - // Open random file and it will reuse first inferred project - projectService.openClientFile(randomFile.path); - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); - checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); - assert.strictEqual(projectService.inferredProjects[0], inferredProject0); - checkProjectActualFiles(projectService.inferredProjects[0], [randomFile.path]); // Reuses first inferred project - assert.strictEqual(projectService.inferredProjects[1], inferredProject1); - checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); - }); + host.checkTimeoutQueueLengthAndRun(2); + + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); // will not remove project 1 + ts.projectSystem.checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + const inferredProject1 = projectService.inferredProjects[1]; + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); + + // Open random file and it will reuse first inferred project + projectService.openClientFile(randomFile.path); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + ts.projectSystem.checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [randomFile.path]); // Reuses first inferred project + assert.strictEqual(projectService.inferredProjects[1], inferredProject1); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [nodeModuleFile.path]); + }); - it("should keep the configured project when the opened file is referenced by the project but not its root", () => { - const file1: File = { - path: "/a/b/main.ts", - content: "import { objA } from './obj-a';" - }; - const file2: File = { - path: "/a/b/obj-a.ts", - content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should keep the configured project when the opened file is referenced by the project but not its root", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/main.ts", + content: "import { objA } from './obj-a';" + }; + const file2: ts.projectSystem.File = { + path: "/a/b/obj-a.ts", + content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts" ] }` - }; - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - projectService.closeClientFile(file1.path); - projectService.openClientFile(file2.path); - checkNumberOfConfiguredProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); - }); + }; + const host = ts.projectSystem.createServerHost([file1, file2, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(file1.path); + projectService.closeClientFile(file1.path); + projectService.openClientFile(file2.path); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 0); + }); - it("should keep the configured project when the opened file is referenced by the project but not its root", () => { - const file1: File = { - path: "/a/b/main.ts", - content: "import { objA } from './obj-a';" - }; - const file2: File = { - path: "/a/b/obj-a.ts", - content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should keep the configured project when the opened file is referenced by the project but not its root", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/main.ts", + content: "import { objA } from './obj-a';" + }; + const file2: ts.projectSystem.File = { + path: "/a/b/obj-a.ts", + content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts" ] }` - }; - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - projectService.closeClientFile(file1.path); - projectService.openClientFile(file2.path); - checkNumberOfConfiguredProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); - }); + }; + const host = ts.projectSystem.createServerHost([file1, file2, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(file1.path); + projectService.closeClientFile(file1.path); + projectService.openClientFile(file2.path); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 0); + }); - it("should tolerate config file errors and still try to build a project", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should tolerate config file errors and still try to build a project", () => { + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6", "allowAnything": true }, "someOtherProperty": {} }` - }; - const host = createServerHost([commonFile1, commonFile2, libFile, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - checkNumberOfConfiguredProjects(projectService, 1); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [commonFile1.path, commonFile2.path]); - }); + }; + const host = ts.projectSystem.createServerHost([ts.projectSystem.commonFile1, ts.projectSystem.commonFile2, ts.projectSystem.libFile, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(ts.projectSystem.commonFile1.path); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + ts.projectSystem.checkProjectRootFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [ts.projectSystem.commonFile1.path, ts.projectSystem.commonFile2.path]); + }); - it("should reuse same project if file is opened from the configured project that has no open files", () => { - const file1 = { - path: "/a/b/main.ts", - content: "let x =1;" - }; - const file2 = { - path: "/a/b/main2.ts", - content: "let y =1;" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should reuse same project if file is opened from the configured project that has no open files", () => { + const file1 = { + path: "/a/b/main.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/b/main2.ts", + content: "let y =1;" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts", "main2.ts" ] }` - }; - const host = createServerHost([file1, file2, configFile, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - projectService.openClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects.get(configFile.path)!; - assert.isTrue(project.hasOpenRef()); // file1 - - projectService.closeClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - assert.isFalse(project.hasOpenRef()); // No open files - assert.isFalse(project.isClosed()); - - projectService.openClientFile(file2.path); - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - assert.isTrue(project.hasOpenRef()); // file2 - assert.isFalse(project.isClosed()); - }); + }; + const host = ts.projectSystem.createServerHost([file1, file2, configFile, ts.projectSystem.libFile]); + const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(project.hasOpenRef()); // file1 + + projectService.closeClientFile(file1.path); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isFalse(project.hasOpenRef()); // No open files + assert.isFalse(project.isClosed()); + + projectService.openClientFile(file2.path); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isTrue(project.hasOpenRef()); // file2 + assert.isFalse(project.isClosed()); + }); - it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => { - const file1 = { - path: "/a/b/main.ts", - content: "let x =1;" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => { + const file1 = { + path: "/a/b/main.ts", + content: "let x =1;" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts" ] }` - }; - const host = createServerHost([file1, configFile, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - projectService.openClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects.get(configFile.path)!; - assert.isTrue(project.hasOpenRef()); // file1 - - projectService.closeClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - assert.isFalse(project.hasOpenRef()); // No files - assert.isFalse(project.isClosed()); - - projectService.openClientFile(libFile.path); - checkNumberOfConfiguredProjects(projectService, 0); - assert.isFalse(project.hasOpenRef()); // No files + project closed - assert.isTrue(project.isClosed()); - }); - - it("open file become a part of configured project if it is referenced from root file", () => { - const file1 = { - path: `${tscWatch.projectRoot}/a/b/f1.ts`, - content: "export let x = 5" - }; - const file2 = { - path: `${tscWatch.projectRoot}/a/c/f2.ts`, - content: `import {x} from "../b/f1"` - }; - const file3 = { - path: `${tscWatch.projectRoot}/a/c/f3.ts`, - content: "export let y = 1" - }; - const configFile = { - path: `${tscWatch.projectRoot}/a/c/tsconfig.json`, - content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) - }; - - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); - - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); - - host.writeFile(configFile.path, configFile.content); - host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - assert.isTrue(projectService.inferredProjects[1].isOrphan()); - }); - - it("can correctly update configured project when set of root files has changed (new file on disk)", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - - const host = createServerHost([file1, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); - - host.writeFile(file2.path, file2.content); - - host.checkTimeoutQueueLengthAndRun(2); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); - }); - - it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) - }; - - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); - - host.writeFile(configFile.path, JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); - }); - - it("can update configured project when set of root files was not changed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) - }; - - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, configFile.path]); - - host.writeFile(configFile.path, JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); - }); - - it("Open ref of configured project when open file gets added to the project as part of configured file update", () => { - const file1: File = { - path: "/a/b/src/file1.ts", - content: "let x = 1;" - }; - const file2: File = { - path: "/a/b/src/file2.ts", - content: "let y = 1;" - }; - const file3: File = { - path: "/a/b/file3.ts", - content: "let z = 1;" - }; - const file4: File = { - path: "/a/file4.ts", - content: "let z = 1;" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) - }; - - const files = [file1, file2, file3, file4]; - const host = createServerHost(files.concat(configFile)); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); - projectService.openClientFile(file3.path); - projectService.openClientFile(file4.path); - - const configProject1 = projectService.configuredProjects.get(configFile.path)!; - assert.isTrue(configProject1.hasOpenRef()); // file1 and file3 - - host.writeFile(configFile.path, "{}"); - host.runQueuedTimeoutCallbacks(); - - assert.isTrue(configProject1.hasOpenRef()); // file1, file2, file3 - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - - projectService.closeClientFile(file1.path); - projectService.closeClientFile(file2.path); - projectService.closeClientFile(file4.path); - - assert.isTrue(configProject1.hasOpenRef()); // file3 - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - assert.isTrue(projectService.inferredProjects[1].isOrphan()); - - projectService.openClientFile(file4.path); - assert.isTrue(configProject1.hasOpenRef()); // file3 - const inferredProject4 = projectService.inferredProjects[0]; - checkProjectActualFiles(inferredProject4, [file4.path]); + }; + const host = ts.projectSystem.createServerHost([file1, configFile, ts.projectSystem.libFile]); + const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + const project = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(project.hasOpenRef()); // file1 + + projectService.closeClientFile(file1.path); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isFalse(project.hasOpenRef()); // No files + assert.isFalse(project.isClosed()); + + projectService.openClientFile(ts.projectSystem.libFile.path); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 0); + assert.isFalse(project.hasOpenRef()); // No files + project closed + assert.isTrue(project.isClosed()); + }); - projectService.closeClientFile(file3.path); - assert.isFalse(configProject1.hasOpenRef()); // No open files - const inferredProject5 = projectService.inferredProjects[0]; - checkProjectActualFiles(inferredProject4, [file4.path]); - assert.strictEqual(inferredProject5, inferredProject4); + it("open file become a part of configured project if it is referenced from root file", () => { + const file1 = { + path: `${ts.tscWatch.projectRoot}/a/b/f1.ts`, + content: "export let x = 5" + }; + const file2 = { + path: `${ts.tscWatch.projectRoot}/a/c/f2.ts`, + content: `import {x} from "../b/f1"` + }; + const file3 = { + path: `${ts.tscWatch.projectRoot}/a/c/f3.ts`, + content: "export let y = 1" + }; + const configFile = { + path: `${ts.tscWatch.projectRoot}/a/c/tsconfig.json`, + content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) + }; + + const host = ts.projectSystem.createServerHost([file1, file2, file3]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + + projectService.openClientFile(file3.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + + host.writeFile(configFile.path, configFile.content); + host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + assert.isTrue(projectService.inferredProjects[1].isOrphan()); + }); - const file5: File = { - path: "/file5.ts", - content: "let zz = 1;" - }; - host.writeFile(file5.path, file5.content); - projectService.testhost.baselineHost("File5 written"); - projectService.openClientFile(file5.path); + it("can correctly update configured project when set of root files has changed (new file on disk)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + + const host = ts.projectSystem.createServerHost([file1, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, configFile.path]); + + host.writeFile(file2.path, file2.content); + + host.checkTimeoutQueueLengthAndRun(2); + + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + ts.projectSystem.checkProjectRootFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); - baselineTsserverLogs("configuredProjects", "Open ref of configured project when open file gets added to the project as part of configured file update", projectService); - }); + it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) + }; + + const host = ts.projectSystem.createServerHost([file1, file2, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, configFile.path]); + + host.writeFile(configFile.path, JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })); + + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); + ts.projectSystem.checkProjectRootFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); - it("Open ref of configured project when open file gets added to the project as part of configured file update buts its open file references are all closed when the update happens", () => { - const file1: File = { - path: "/a/b/src/file1.ts", - content: "let x = 1;" - }; - const file2: File = { - path: "/a/b/src/file2.ts", - content: "let y = 1;" - }; - const file3: File = { - path: "/a/b/file3.ts", - content: "let z = 1;" - }; - const file4: File = { - path: "/a/file4.ts", - content: "let z = 1;" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) - }; + it("can update configured project when set of root files was not changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) + }; + + const host = ts.projectSystem.createServerHost([file1, file2, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, file2.path, configFile.path]); + + host.writeFile(configFile.path, JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })); + + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + ts.projectSystem.checkProjectRootFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); - const files = [file1, file2, file3]; - const hostFiles = files.concat(file4, configFile); - const host = createServerHost(hostFiles); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); - projectService.openClientFile(file3.path); - - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - const configuredProject = projectService.configuredProjects.get(configFile.path)!; - assert.isTrue(configuredProject.hasOpenRef()); // file1 and file3 - checkProjectActualFiles(configuredProject, [file1.path, file3.path, configFile.path]); - const inferredProject1 = projectService.inferredProjects[0]; - checkProjectActualFiles(inferredProject1, [file2.path]); - - projectService.closeClientFile(file1.path); - projectService.closeClientFile(file3.path); - assert.isFalse(configuredProject.hasOpenRef()); // No files - - host.writeFile(configFile.path, "{}"); - // Time out is not yet run so there is project update pending - assert.isTrue(configuredProject.hasOpenRef()); // Pending update and file2 might get into the project - - projectService.openClientFile(file4.path); - - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); - assert.isTrue(configuredProject.hasOpenRef()); // Pending update and F2 might get into the project - assert.strictEqual(projectService.inferredProjects[0], inferredProject1); - const inferredProject2 = projectService.inferredProjects[1]; - checkProjectActualFiles(inferredProject2, [file4.path]); - - host.runQueuedTimeoutCallbacks(); - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); - assert.isTrue(configuredProject.hasOpenRef()); // file2 - checkProjectActualFiles(configuredProject, [file1.path, file2.path, file3.path, configFile.path]); - assert.strictEqual(projectService.inferredProjects[0], inferredProject1); - assert.isTrue(inferredProject1.isOrphan()); - assert.strictEqual(projectService.inferredProjects[1], inferredProject2); - checkProjectActualFiles(inferredProject2, [file4.path]); - }); + it("Open ref of configured project when open file gets added to the project as part of configured file update", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/src/file1.ts", + content: "let x = 1;" + }; + const file2: ts.projectSystem.File = { + path: "/a/b/src/file2.ts", + content: "let y = 1;" + }; + const file3: ts.projectSystem.File = { + path: "/a/b/file3.ts", + content: "let z = 1;" + }; + const file4: ts.projectSystem.File = { + path: "/a/file4.ts", + content: "let z = 1;" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) + }; + + const files = [file1, file2, file3, file4]; + const host = ts.projectSystem.createServerHost(files.concat(configFile)); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + projectService.openClientFile(file3.path); + projectService.openClientFile(file4.path); + + const configProject1 = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(configProject1.hasOpenRef()); // file1 and file3 + + host.writeFile(configFile.path, "{}"); + host.runQueuedTimeoutCallbacks(); + + assert.isTrue(configProject1.hasOpenRef()); // file1, file2, file3 + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + + projectService.closeClientFile(file1.path); + projectService.closeClientFile(file2.path); + projectService.closeClientFile(file4.path); + + assert.isTrue(configProject1.hasOpenRef()); // file3 + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + assert.isTrue(projectService.inferredProjects[1].isOrphan()); + + projectService.openClientFile(file4.path); + assert.isTrue(configProject1.hasOpenRef()); // file3 + const inferredProject4 = projectService.inferredProjects[0]; + ts.projectSystem.checkProjectActualFiles(inferredProject4, [file4.path]); + + projectService.closeClientFile(file3.path); + assert.isFalse(configProject1.hasOpenRef()); // No open files + const inferredProject5 = projectService.inferredProjects[0]; + ts.projectSystem.checkProjectActualFiles(inferredProject4, [file4.path]); + assert.strictEqual(inferredProject5, inferredProject4); + + const file5: ts.projectSystem.File = { + path: "/file5.ts", + content: "let zz = 1;" + }; + host.writeFile(file5.path, file5.content); + projectService.testhost.baselineHost("File5 written"); + projectService.openClientFile(file5.path); + + ts.projectSystem.baselineTsserverLogs("configuredProjects", "Open ref of configured project when open file gets added to the project as part of configured file update", projectService); + }); - it("files are properly detached when language service is disabled", () => { - const f1 = { - path: "/a/app.js", - content: "var x = 1" - }; - const f2 = { - path: "/a/largefile.js", - content: "" - }; - const f3 = { - path: "/a/lib.js", - content: "var x = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true } }) - }; - const host = createServerHost([f1, f2, f3, config]); - const originalGetFileSize = host.getFileSize; - host.getFileSize = (filePath: string) => - filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); - - const projectService = createProjectService(host); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const project = projectService.configuredProjects.get(config.path)!; - assert.isTrue(project.hasOpenRef()); // f1 - assert.isFalse(project.isClosed()); - - projectService.closeClientFile(f1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config.path), project); - assert.isFalse(project.hasOpenRef()); // No files - assert.isFalse(project.isClosed()); - - for (const f of [f1, f2, f3]) { - // All the script infos should be present and contain the project since it is still alive. - const scriptInfo = projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path))!; - assert.equal(scriptInfo.containingProjects.length, 1, `expect 1 containing projects for '${f.path}'`); - assert.equal(scriptInfo.containingProjects[0], project, `expect configured project to be the only containing project for '${f.path}'`); - } + it("Open ref of configured project when open file gets added to the project as part of configured file update buts its open file references are all closed when the update happens", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/src/file1.ts", + content: "let x = 1;" + }; + const file2: ts.projectSystem.File = { + path: "/a/b/src/file2.ts", + content: "let y = 1;" + }; + const file3: ts.projectSystem.File = { + path: "/a/b/file3.ts", + content: "let z = 1;" + }; + const file4: ts.projectSystem.File = { + path: "/a/file4.ts", + content: "let z = 1;" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: ["src/file1.ts", "file3.ts"] }) + }; + + const files = [file1, file2, file3]; + const hostFiles = files.concat(file4, configFile); + const host = ts.projectSystem.createServerHost(hostFiles); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + projectService.openClientFile(file3.path); + + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + const configuredProject = projectService.configuredProjects.get(configFile.path)!; + assert.isTrue(configuredProject.hasOpenRef()); // file1 and file3 + ts.projectSystem.checkProjectActualFiles(configuredProject, [file1.path, file3.path, configFile.path]); + const inferredProject1 = projectService.inferredProjects[0]; + ts.projectSystem.checkProjectActualFiles(inferredProject1, [file2.path]); + + projectService.closeClientFile(file1.path); + projectService.closeClientFile(file3.path); + assert.isFalse(configuredProject.hasOpenRef()); // No files + + host.writeFile(configFile.path, "{}"); + // Time out is not yet run so there is project update pending + assert.isTrue(configuredProject.hasOpenRef()); // Pending update and file2 might get into the project + + projectService.openClientFile(file4.path); + + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); + assert.isTrue(configuredProject.hasOpenRef()); // Pending update and F2 might get into the project + assert.strictEqual(projectService.inferredProjects[0], inferredProject1); + const inferredProject2 = projectService.inferredProjects[1]; + ts.projectSystem.checkProjectActualFiles(inferredProject2, [file4.path]); + + host.runQueuedTimeoutCallbacks(); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), configuredProject); + assert.isTrue(configuredProject.hasOpenRef()); // file2 + ts.projectSystem.checkProjectActualFiles(configuredProject, [file1.path, file2.path, file3.path, configFile.path]); + assert.strictEqual(projectService.inferredProjects[0], inferredProject1); + assert.isTrue(inferredProject1.isOrphan()); + assert.strictEqual(projectService.inferredProjects[1], inferredProject2); + ts.projectSystem.checkProjectActualFiles(inferredProject2, [file4.path]); + }); - const f4 = { - path: "/aa.js", - content: "var x = 1" - }; - host.writeFile(f4.path, f4.content); - projectService.openClientFile(f4.path); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - assert.isFalse(project.hasOpenRef()); // No files - assert.isTrue(project.isClosed()); - - for (const f of [f1, f2, f3]) { - // All the script infos should not be present since the project is closed and orphan script infos are collected - assert.isUndefined(projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path))); - } - }); + it("files are properly detached when language service is disabled", () => { + const f1 = { + path: "/a/app.js", + content: "var x = 1" + }; + const f2 = { + path: "/a/largefile.js", + content: "" + }; + const f3 = { + path: "/a/lib.js", + content: "var x = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true } }) + }; + const host = ts.projectSystem.createServerHost([f1, f2, f3, config]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); + + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const project = projectService.configuredProjects.get(config.path)!; + assert.isTrue(project.hasOpenRef()); // f1 + assert.isFalse(project.isClosed()); + + projectService.closeClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isFalse(project.hasOpenRef()); // No files + assert.isFalse(project.isClosed()); + + for (const f of [f1, f2, f3]) { + // All the script infos should be present and contain the project since it is still alive. + const scriptInfo = projectService.getScriptInfoForNormalizedPath(ts.server.toNormalizedPath(f.path))!; + assert.equal(scriptInfo.containingProjects.length, 1, `expect 1 containing projects for '${f.path}'`); + assert.equal(scriptInfo.containingProjects[0], project, `expect configured project to be the only containing project for '${f.path}'`); + } + + const f4 = { + path: "/aa.js", + content: "var x = 1" + }; + host.writeFile(f4.path, f4.content); + projectService.openClientFile(f4.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + assert.isFalse(project.hasOpenRef()); // No files + assert.isTrue(project.isClosed()); + + for (const f of [f1, f2, f3]) { + // All the script infos should not be present since the project is closed and orphan script infos are collected + assert.isUndefined(projectService.getScriptInfoForNormalizedPath(ts.server.toNormalizedPath(f.path))); + } + }); - it("syntactic features work even if language service is disabled", () => { - const f1 = { - path: "/a/app.js", - content: "let x = 1;" - }; - const f2 = { - path: "/a/largefile.js", - content: "" - }; - const config = { - path: "/a/jsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, f2, config]); - const originalGetFileSize = host.getFileSize; - host.getFileSize = (filePath: string) => - filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); - const { session, events } = createSessionWithEventTracking(host, server.ProjectLanguageServiceStateEvent); - session.executeCommand({ - seq: 0, - type: "request", - command: "open", - arguments: { file: f1.path } - } as protocol.OpenRequest); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = configuredProjectAt(projectService, 0); - assert.isFalse(project.languageServiceEnabled, "Language service enabled"); - assert.equal(events.length, 1, "should receive event"); - assert.equal(events[0].data.project, project, "project name"); - assert.isFalse(events[0].data.languageServiceEnabled, "Language service state"); - - const options = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath); - const edits = project.getLanguageService().getFormattingEditsForDocument(f1.path, options); - assert.deepEqual(edits, [{ span: createTextSpan(/*start*/ 7, /*length*/ 3), newText: " " }]); - }); + it("syntactic features work even if language service is disabled", () => { + const f1 = { + path: "/a/app.js", + content: "let x = 1;" + }; + const f2 = { + path: "/a/largefile.js", + content: "" + }; + const config = { + path: "/a/jsconfig.json", + content: "{}" + }; + const host = ts.projectSystem.createServerHost([f1, f2, config]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); + const { session, events } = ts.projectSystem.createSessionWithEventTracking(host, ts.server.ProjectLanguageServiceStateEvent); + session.executeCommand({ + seq: 0, + type: "request", + command: "open", + arguments: { file: f1.path } + } as ts.projectSystem.protocol.OpenRequest); + + const projectService = session.getProjectService(); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + assert.isFalse(project.languageServiceEnabled, "Language service enabled"); + assert.equal(events.length, 1, "should receive event"); + assert.equal(events[0].data.project, project, "project name"); + assert.isFalse(events[0].data.languageServiceEnabled, "Language service state"); + + const options = projectService.getFormatCodeOptions(f1.path as ts.server.NormalizedPath); + const edits = project.getLanguageService().getFormattingEditsForDocument(f1.path, options); + assert.deepEqual(edits, [{ span: ts.createTextSpan(/*start*/ 7, /*length*/ 3), newText: " " }]); + }); - it("when multiple projects are open, detects correct default project", () => { - const barConfig: File = { - path: `${tscWatch.projectRoot}/bar/tsconfig.json`, - content: JSON.stringify({ - include: ["index.ts"], - compilerOptions: { - lib: ["dom", "es2017"] - } - }) - }; - const barIndex: File = { - path: `${tscWatch.projectRoot}/bar/index.ts`, - content: ` + it("when multiple projects are open, detects correct default project", () => { + const barConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/bar/tsconfig.json`, + content: JSON.stringify({ + include: ["index.ts"], + compilerOptions: { + lib: ["dom", "es2017"] + } + }) + }; + const barIndex: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/bar/index.ts`, + content: ` export function bar() { console.log("hello world"); }` - }; - const fooConfig: File = { - path: `${tscWatch.projectRoot}/foo/tsconfig.json`, - content: JSON.stringify({ - include: ["index.ts"], - compilerOptions: { - lib: ["es2017"] - } - }) - }; - const fooIndex: File = { - path: `${tscWatch.projectRoot}/foo/index.ts`, - content: ` + }; + const fooConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/foo/tsconfig.json`, + content: JSON.stringify({ + include: ["index.ts"], + compilerOptions: { + lib: ["es2017"] + } + }) + }; + const fooIndex: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/foo/index.ts`, + content: ` import { bar } from "bar"; bar();` - }; - const barSymLink: SymLink = { - path: `${tscWatch.projectRoot}/foo/node_modules/bar`, - symLink: `${tscWatch.projectRoot}/bar` - }; - - const lib2017: File = { - path: `${getDirectoryPath(libFile.path)}/lib.es2017.d.ts`, - content: libFile.content - }; - const libDom: File = { - path: `${getDirectoryPath(libFile.path)}/lib.dom.d.ts`, - content: ` + }; + const barSymLink: ts.projectSystem.SymLink = { + path: `${ts.tscWatch.projectRoot}/foo/node_modules/bar`, + symLink: `${ts.tscWatch.projectRoot}/bar` + }; + + const lib2017: ts.projectSystem.File = { + path: `${ts.getDirectoryPath(ts.projectSystem.libFile.path)}/lib.es2017.d.ts`, + content: ts.projectSystem.libFile.content + }; + const libDom: ts.projectSystem.File = { + path: `${ts.getDirectoryPath(ts.projectSystem.libFile.path)}/lib.dom.d.ts`, + content: ` declare var console: { log(...args: any[]): void; };` - }; - const host = createServerHost([barConfig, barIndex, fooConfig, fooIndex, barSymLink, lib2017, libDom]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([fooIndex, barIndex], session); - verifyGetErrRequest({ session, host, files: [barIndex, fooIndex] }); - baselineTsserverLogs("configuredProjects", "when multiple projects are open detects correct default project", session); - }); + }; + const host = ts.projectSystem.createServerHost([barConfig, barIndex, fooConfig, fooIndex, barSymLink, lib2017, libDom]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([fooIndex, barIndex], session); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [barIndex, fooIndex] }); + ts.projectSystem.baselineTsserverLogs("configuredProjects", "when multiple projects are open detects correct default project", session); + }); - it("when file name starts with ^", () => { - const file: File = { - path: `${tscWatch.projectRoot}/file.ts`, - content: "const x = 10;" - }; - const app: File = { - path: `${tscWatch.projectRoot}/^app.ts`, - content: "const y = 10;" - }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const host = createServerHost([file, app, tsconfig, libFile]); - const service = createProjectService(host); - service.openClientFile(file.path); - }); + it("when file name starts with ^", () => { + const file: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/file.ts`, + content: "const x = 10;" + }; + const app: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/^app.ts`, + content: "const y = 10;" + }; + const tsconfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const host = ts.projectSystem.createServerHost([file, app, tsconfig, ts.projectSystem.libFile]); + const service = ts.projectSystem.createProjectService(host); + service.openClientFile(file.path); + }); - describe("when creating new file", () => { - const foo: File = { - path: `${tscWatch.projectRoot}/src/foo.ts`, - content: "export function foo() { }" - }; - const bar: File = { - path: `${tscWatch.projectRoot}/src/bar.ts`, - content: "export function bar() { }" - }; - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - include: ["./src"] - }) - }; - const fooBar: File = { - path: `${tscWatch.projectRoot}/src/sub/fooBar.ts`, - content: "export function fooBar() { }" - }; - function verifySessionWorker({ withExclude, openFileBeforeCreating }: VerifySession, errorOnNewFileBeforeOldFile: boolean) { - const host = createServerHost([ - foo, bar, libFile, { path: `${tscWatch.projectRoot}/src/sub` }, - withExclude ? - { - path: config.path, - content: JSON.stringify({ - include: ["./src"], - exclude: ["./src/sub"] - }) - } : - config - ]); - const session = createSession(host, { - canUseEvents: true, - logger: createLoggerWithInMemoryLogs(host), - }); - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { - file: foo.path, - fileContent: foo.content, - projectRootPath: tscWatch.projectRoot - } - }); - if (!openFileBeforeCreating) { - host.writeFile(fooBar.path, fooBar.content); - } - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { - file: fooBar.path, - fileContent: fooBar.content, - projectRootPath: tscWatch.projectRoot - } - }); - if (openFileBeforeCreating) { - host.writeFile(fooBar.path, fooBar.content); + describe("when creating new file", () => { + const foo: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/foo.ts`, + content: "export function foo() { }" + }; + const bar: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/bar.ts`, + content: "export function bar() { }" + }; + const config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + include: ["./src"] + }) + }; + const fooBar: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/sub/fooBar.ts`, + content: "export function fooBar() { }" + }; + function verifySessionWorker({ withExclude, openFileBeforeCreating }: VerifySession, errorOnNewFileBeforeOldFile: boolean) { + const host = ts.projectSystem.createServerHost([ + foo, bar, ts.projectSystem.libFile, { path: `${ts.tscWatch.projectRoot}/src/sub` }, + withExclude ? + { + path: config.path, + content: JSON.stringify({ + include: ["./src"], + exclude: ["./src/sub"] + }) + } : + config + ]); + const session = ts.projectSystem.createSession(host, { + canUseEvents: true, + logger: ts.projectSystem.createLoggerWithInMemoryLogs(host), + }); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Open, + arguments: { + file: foo.path, + fileContent: foo.content, + projectRootPath: ts.tscWatch.projectRoot } - verifyGetErrRequest({ - session, - host, - files: errorOnNewFileBeforeOldFile ? - [fooBar, foo] : - [foo, fooBar], - existingTimeouts: withExclude ? 0 : 2 - }); - baselineTsserverLogs("configuredProjects", `creating new file and then open it ${openFileBeforeCreating ? "before" : "after"} watcher is invoked, ask errors on it ${errorOnNewFileBeforeOldFile ? "before" : "after"} old one${withExclude ? " without file being in config" : ""}`, session); + }); + if (!openFileBeforeCreating) { + host.writeFile(fooBar.path, fooBar.content); } - interface VerifySession { - withExclude?: boolean; - openFileBeforeCreating: boolean; + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Open, + arguments: { + file: fooBar.path, + fileContent: fooBar.content, + projectRootPath: ts.tscWatch.projectRoot + } + }); + if (openFileBeforeCreating) { + host.writeFile(fooBar.path, fooBar.content); } - function verifySession(input: VerifySession) { - it("when error on new file are asked before old one", () => { - verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ true); - }); + ts.projectSystem.verifyGetErrRequest({ + session, + host, + files: errorOnNewFileBeforeOldFile ? + [fooBar, foo] : + [foo, fooBar], + existingTimeouts: withExclude ? 0 : 2 + }); + ts.projectSystem.baselineTsserverLogs("configuredProjects", `creating new file and then open it ${openFileBeforeCreating ? "before" : "after"} watcher is invoked, ask errors on it ${errorOnNewFileBeforeOldFile ? "before" : "after"} old one${withExclude ? " without file being in config" : ""}`, session); + } + interface VerifySession { + withExclude?: boolean; + openFileBeforeCreating: boolean; + } + function verifySession(input: VerifySession) { + it("when error on new file are asked before old one", () => { + verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ true); + }); - it("when error on new file are asked after old one", () => { - verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ false); - }); - } - describe("when new file creation directory watcher is invoked before file is opened in editor", () => { + it("when error on new file are asked after old one", () => { + verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ false); + }); + } + describe("when new file creation directory watcher is invoked before file is opened in editor", () => { + verifySession({ + openFileBeforeCreating: false, + }); + describe("when new file is excluded from config", () => { verifySession({ + withExclude: true, openFileBeforeCreating: false, }); - describe("when new file is excluded from config", () => { - verifySession({ - withExclude: true, - openFileBeforeCreating: false, - }); - }); }); + }); - describe("when new file creation directory watcher is invoked after file is opened in editor", () => { + describe("when new file creation directory watcher is invoked after file is opened in editor", () => { + verifySession({ + openFileBeforeCreating: true, + }); + describe("when new file is excluded from config", () => { verifySession({ + withExclude: true, openFileBeforeCreating: true, }); - describe("when new file is excluded from config", () => { - verifySession({ - withExclude: true, - openFileBeforeCreating: true, - }); - }); }); }); + }); - it("when default configured project does not contain the file", () => { - const barConfig: File = { - path: `${tscWatch.projectRoot}/bar/tsconfig.json`, + it("when default configured project does not contain the file", () => { + const barConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/bar/tsconfig.json`, + content: "{}" + }; + const barIndex: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/bar/index.ts`, + content: `import {foo} from "../foo/lib"; +foo();` + }; + const fooBarConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/foobar/tsconfig.json`, + content: barConfig.path + }; + const fooBarIndex: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/foobar/index.ts`, + content: barIndex.content + }; + const fooConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/foo/tsconfig.json`, + content: JSON.stringify({ + include: ["index.ts"], + compilerOptions: { + declaration: true, + outDir: "lib" + } + }) + }; + const fooIndex: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/foo/index.ts`, + content: `export function foo() {}` + }; + const host = ts.projectSystem.createServerHost([barConfig, barIndex, fooBarConfig, fooBarIndex, fooConfig, fooIndex, ts.projectSystem.libFile]); + ts.tscWatch.ensureErrorFreeBuild(host, [fooConfig.path]); + const fooDts = `${ts.tscWatch.projectRoot}/foo/lib/index.d.ts`; + assert.isTrue(host.fileExists(fooDts)); + const session = ts.projectSystem.createSession(host); + const service = session.getProjectService(); + service.openClientFile(barIndex.path); + ts.projectSystem.checkProjectActualFiles(service.configuredProjects.get(barConfig.path)!, [barIndex.path, fooDts, ts.projectSystem.libFile.path, barConfig.path]); + service.openClientFile(fooBarIndex.path); + ts.projectSystem.checkProjectActualFiles(service.configuredProjects.get(fooBarConfig.path)!, [fooBarIndex.path, fooDts, ts.projectSystem.libFile.path, fooBarConfig.path]); + service.openClientFile(fooIndex.path); + ts.projectSystem.checkProjectActualFiles(service.configuredProjects.get(fooConfig.path)!, [fooIndex.path, ts.projectSystem.libFile.path, fooConfig.path]); + service.openClientFile(fooDts); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.GetApplicableRefactors, + arguments: { + file: fooDts, + startLine: 1, + startOffset: 1, + endLine: 1, + endOffset: 1 + } + }); + assert.equal(service.tryGetDefaultProjectForFile(ts.server.toNormalizedPath(fooDts)), service.configuredProjects.get(barConfig.path)); + }); + + describe("watches extended config files", () => { + function getService(additionalFiles?: ts.projectSystem.File[]) { + const alphaExtendedConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/extended/alpha.tsconfig.json`, content: "{}" }; - const barIndex: File = { - path: `${tscWatch.projectRoot}/bar/index.ts`, - content: `import {foo} from "../foo/lib"; -foo();` + const bravoExtendedConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/extended/bravo.tsconfig.json`, + content: JSON.stringify({ + extends: "./alpha.tsconfig.json" + }) }; - const fooBarConfig: File = { - path: `${tscWatch.projectRoot}/foobar/tsconfig.json`, - content: barConfig.path + const aConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/a/tsconfig.json`, + content: JSON.stringify({ + extends: "../extended/alpha.tsconfig.json", + files: ["a.ts"] + }) }; - const fooBarIndex: File = { - path: `${tscWatch.projectRoot}/foobar/index.ts`, - content: barIndex.content + const aFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/a/a.ts`, + content: `let a = 1;` }; - const fooConfig: File = { - path: `${tscWatch.projectRoot}/foo/tsconfig.json`, + const bConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/b/tsconfig.json`, content: JSON.stringify({ - include: ["index.ts"], - compilerOptions: { - declaration: true, - outDir: "lib" - } + extends: "../extended/bravo.tsconfig.json", + files: ["b.ts"] }) }; - const fooIndex: File = { - path: `${tscWatch.projectRoot}/foo/index.ts`, - content: `export function foo() {}` + const bFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/b/b.ts`, + content: `let b = 1;` }; - const host = createServerHost([barConfig, barIndex, fooBarConfig, fooBarIndex, fooConfig, fooIndex, libFile]); - tscWatch.ensureErrorFreeBuild(host, [fooConfig.path]); - const fooDts = `${tscWatch.projectRoot}/foo/lib/index.d.ts`; - assert.isTrue(host.fileExists(fooDts)); - const session = createSession(host); - const service = session.getProjectService(); - service.openClientFile(barIndex.path); - checkProjectActualFiles(service.configuredProjects.get(barConfig.path)!, [barIndex.path, fooDts, libFile.path, barConfig.path]); - service.openClientFile(fooBarIndex.path); - checkProjectActualFiles(service.configuredProjects.get(fooBarConfig.path)!, [fooBarIndex.path, fooDts, libFile.path, fooBarConfig.path]); - service.openClientFile(fooIndex.path); - checkProjectActualFiles(service.configuredProjects.get(fooConfig.path)!, [fooIndex.path, libFile.path, fooConfig.path]); - service.openClientFile(fooDts); - session.executeCommandSeq({ - command: protocol.CommandTypes.GetApplicableRefactors, - arguments: { - file: fooDts, - startLine: 1, - startOffset: 1, - endLine: 1, - endOffset: 1 - } - }); - assert.equal(service.tryGetDefaultProjectForFile(server.toNormalizedPath(fooDts)), service.configuredProjects.get(barConfig.path)); - }); - describe("watches extended config files", () => { - function getService(additionalFiles?: File[]) { - const alphaExtendedConfig: File = { - path: `${tscWatch.projectRoot}/extended/alpha.tsconfig.json`, - content: "{}" - }; - const bravoExtendedConfig: File = { - path: `${tscWatch.projectRoot}/extended/bravo.tsconfig.json`, - content: JSON.stringify({ - extends: "./alpha.tsconfig.json" - }) - }; - const aConfig: File = { - path: `${tscWatch.projectRoot}/a/tsconfig.json`, - content: JSON.stringify({ - extends: "../extended/alpha.tsconfig.json", - files: ["a.ts"] - }) - }; - const aFile: File = { - path: `${tscWatch.projectRoot}/a/a.ts`, - content: `let a = 1;` - }; - const bConfig: File = { - path: `${tscWatch.projectRoot}/b/tsconfig.json`, - content: JSON.stringify({ - extends: "../extended/bravo.tsconfig.json", - files: ["b.ts"] - }) - }; - const bFile: File = { - path: `${tscWatch.projectRoot}/b/b.ts`, - content: `let b = 1;` - }; - - const host = createServerHost([alphaExtendedConfig, aConfig, aFile, bravoExtendedConfig, bConfig, bFile, ...(additionalFiles || emptyArray)]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - return { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig }; - } - - it("should watch the extended configs of multiple projects", () => { - const { host, projectService, aFile, bFile, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService(); + const host = ts.projectSystem.createServerHost([alphaExtendedConfig, aConfig, aFile, bravoExtendedConfig, bConfig, bFile, ...(additionalFiles || ts.emptyArray)]); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + return { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig }; + } - projectService.openClientFile(aFile.path); - projectService.openClientFile(bFile.path); + it("should watch the extended configs of multiple projects", () => { + const { host, projectService, aFile, bFile, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService(); - host.writeFile(alphaExtendedConfig.path, JSON.stringify({ - compilerOptions: { - strict: true - } - })); - host.checkTimeoutQueueLengthAndRun(3); + projectService.openClientFile(aFile.path); + projectService.openClientFile(bFile.path); - host.writeFile(bravoExtendedConfig.path, JSON.stringify({ - extends: "./alpha.tsconfig.json", - compilerOptions: { - strict: false - } - })); - host.checkTimeoutQueueLengthAndRun(2); + host.writeFile(alphaExtendedConfig.path, JSON.stringify({ + compilerOptions: { + strict: true + } + })); + host.checkTimeoutQueueLengthAndRun(3); - host.writeFile(bConfig.path, JSON.stringify({ - extends: "../extended/alpha.tsconfig.json", - })); - host.checkTimeoutQueueLengthAndRun(2); + host.writeFile(bravoExtendedConfig.path, JSON.stringify({ + extends: "./alpha.tsconfig.json", + compilerOptions: { + strict: false + } + })); + host.checkTimeoutQueueLengthAndRun(2); - host.writeFile(alphaExtendedConfig.path, "{}"); - host.checkTimeoutQueueLengthAndRun(3); - baselineTsserverLogs("configuredProjects", "should watch the extended configs of multiple projects", projectService); - }); + host.writeFile(bConfig.path, JSON.stringify({ + extends: "../extended/alpha.tsconfig.json", + })); + host.checkTimeoutQueueLengthAndRun(2); - it("should stop watching the extended configs of closed projects", () => { - const dummy: File = { - path: `${tscWatch.projectRoot}/dummy/dummy.ts`, - content: `let dummy = 1;` - }; - const dummyConfig: File = { - path: `${tscWatch.projectRoot}/dummy/tsconfig.json`, - content: "{}" - }; - const { projectService, aFile, bFile } = getService([dummy, dummyConfig]); - - projectService.openClientFile(aFile.path); - projectService.openClientFile(bFile.path); - projectService.openClientFile(dummy.path); - - projectService.closeClientFile(bFile.path); - projectService.closeClientFile(dummy.path); - projectService.openClientFile(dummy.path); - - - projectService.closeClientFile(aFile.path); - projectService.closeClientFile(dummy.path); - projectService.openClientFile(dummy.path); - baselineTsserverLogs("configuredProjects", "should stop watching the extended configs of closed projects", projectService); - }); + host.writeFile(alphaExtendedConfig.path, "{}"); + host.checkTimeoutQueueLengthAndRun(3); + ts.projectSystem.baselineTsserverLogs("configuredProjects", "should watch the extended configs of multiple projects", projectService); }); - }); - describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories listed in config file input array", () => { - it("should be tolerated without crashing the server", () => { - const configFile = { - path: "/a/b/tsconfig.json", - content: `{ - "compilerOptions": {}, - "include": ["app/*", "test/**/*", "something"] - }` + it("should stop watching the extended configs of closed projects", () => { + const dummy: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/dummy/dummy.ts`, + content: `let dummy = 1;` }; - const file1 = { - path: "/a/b/file1.ts", - content: "let t = 10;" + const dummyConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/dummy/tsconfig.json`, + content: "{}" }; + const { projectService, aFile, bFile } = getService([dummy, dummyConfig]); - const host = createServerHost([file1, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - host.runQueuedTimeoutCallbacks(); + projectService.openClientFile(aFile.path); + projectService.openClientFile(bFile.path); + projectService.openClientFile(dummy.path); - // Since file1 refers to config file as the default project, it needs to be kept alive - checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); - const inferredProject = projectService.inferredProjects[0]; - assert.isTrue(inferredProject.containsFile(file1.path as server.NormalizedPath)); - assert.isFalse(projectService.configuredProjects.get(configFile.path)!.containsFile(file1.path as server.NormalizedPath)); - }); + projectService.closeClientFile(bFile.path); + projectService.closeClientFile(dummy.path); + projectService.openClientFile(dummy.path); - it("should be able to handle @types if input file list is empty", () => { - const f = { - path: "/a/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compiler: {}, - files: [] - }) - }; - const t1 = { - path: "/a/node_modules/@types/typings/index.d.ts", - content: `export * from "./lib"` - }; - const t2 = { - path: "/a/node_modules/@types/typings/lib.d.ts", - content: `export const x: number` - }; - const host = createServerHost([f, config, t1, t2], { currentDirectory: getDirectoryPath(f.path) }); - const projectService = createProjectService(host); - projectService.openClientFile(f.path); - // Since f refers to config file as the default project, it needs to be kept alive - projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); + projectService.closeClientFile(aFile.path); + projectService.closeClientFile(dummy.path); + projectService.openClientFile(dummy.path); + ts.projectSystem.baselineTsserverLogs("configuredProjects", "should stop watching the extended configs of closed projects", projectService); }); + }); +}); - it("should tolerate invalid include files that start in subDirectory", () => { - const f = { - path: `${tscWatch.projectRoot}/src/server/index.ts`, - content: "let x = 1" - }; - const config = { - path: `${tscWatch.projectRoot}/src/server/tsconfig.json`, - content: JSON.stringify({ - compiler: { - module: "commonjs", - outDir: "../../build" - }, - include: [ - "../src/**/*.ts" - ] - }) - }; - const host = createServerHost([f, config, libFile], { useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host); - - projectService.openClientFile(f.path); - // Since f refers to config file as the default project, it needs to be kept alive - projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); - }); +describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories listed in config file input array", () => { + it("should be tolerated without crashing the server", () => { + const configFile = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {}, + "include": ["app/*", "test/**/*", "something"] + }` + }; + const file1 = { + path: "/a/b/file1.ts", + content: "let t = 10;" + }; + + const host = ts.projectSystem.createServerHost([file1, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(file1.path); + host.runQueuedTimeoutCallbacks(); + + // Since file1 refers to config file as the default project, it needs to be kept alive + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); + const inferredProject = projectService.inferredProjects[0]; + assert.isTrue(inferredProject.containsFile(file1.path as ts.server.NormalizedPath)); + assert.isFalse(projectService.configuredProjects.get(configFile.path)!.containsFile(file1.path as ts.server.NormalizedPath)); + }); - it("Changed module resolution reflected when specifying files list", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: 'import classc from "file2"' - }; - const file2a: File = { - path: "/a/file2.ts", - content: "export classc { method2a() { return 10; } }" - }; - const file2: File = { - path: "/a/b/file2.ts", - content: "export classc { method2() { return 10; } }" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: [file1.path], compilerOptions: { module: "amd" } }) - }; - const files = [file1, file2a, configFile, libFile]; - const host = createServerHost(files); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); - - host.writeFile(file2.path, file2.content); - host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions - host.runQueuedTimeoutCallbacks(); // Actual update - - // On next file open the files file2a should be closed and not watched any more - projectService.openClientFile(file2.path); - baselineTsserverLogs("configuredProjects", "changed module resolution reflected when specifying files list", projectService); - }); + it("should be able to handle @types if input file list is empty", () => { + const f = { + path: "/a/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compiler: {}, + files: [] + }) + }; + const t1 = { + path: "/a/node_modules/@types/typings/index.d.ts", + content: `export * from "./lib"` + }; + const t2 = { + path: "/a/node_modules/@types/typings/lib.d.ts", + content: `export const x: number` + }; + const host = ts.projectSystem.createServerHost([f, config, t1, t2], { currentDirectory: ts.getDirectoryPath(f.path) }); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(f.path); + // Since f refers to config file as the default project, it needs to be kept alive + projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); + }); - it("Failed lookup locations uses parent most node_modules directory", () => { - const root = "/user/username/rootfolder"; - const file1: File = { - path: "/a/b/src/file1.ts", - content: 'import { classc } from "module1"' - }; - const module1: File = { - path: "/a/b/node_modules/module1/index.d.ts", - content: `import { class2 } from "module2"; - export classc { method2a(): class2; }` - }; - const module2: File = { - path: "/a/b/node_modules/module2/index.d.ts", - content: "export class2 { method2() { return 10; } }" - }; - const module3: File = { - path: "/a/b/node_modules/module/node_modules/module3/index.d.ts", - content: "export class3 { method2() { return 10; } }" - }; - const configFile: File = { - path: "/a/b/src/tsconfig.json", - content: JSON.stringify({ files: ["file1.ts"] }) - }; - const nonLibFiles = [file1, module1, module2, module3, configFile]; - nonLibFiles.forEach(f => f.path = root + f.path); - const files = nonLibFiles.concat(libFile); - const host = createServerHost(files); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); - baselineTsserverLogs("configuredProjects", "failed lookup locations uses parent most node_modules directory", projectService); - }); + it("should tolerate invalid include files that start in subDirectory", () => { + const f = { + path: `${ts.tscWatch.projectRoot}/src/server/index.ts`, + content: "let x = 1" + }; + const config = { + path: `${ts.tscWatch.projectRoot}/src/server/tsconfig.json`, + content: JSON.stringify({ + compiler: { + module: "commonjs", + outDir: "../../build" + }, + include: [ + "../src/**/*.ts" + ] + }) + }; + const host = ts.projectSystem.createServerHost([f, config, ts.projectSystem.libFile], { useCaseSensitiveFileNames: true }); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(f.path); + // Since f refers to config file as the default project, it needs to be kept alive + projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); }); - describe("unittests:: tsserver:: ConfiguredProjects:: when reading tsconfig file fails", () => { - it("should be tolerated without crashing the server", () => { - const configFile = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "" - }; - const file1 = { - path: `${tscWatch.projectRoot}/file1.ts`, - content: "let t = 10;" - }; + it("Changed module resolution reflected when specifying files list", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/file1.ts", + content: 'import classc from "file2"' + }; + const file2a: ts.projectSystem.File = { + path: "/a/file2.ts", + content: "export classc { method2a() { return 10; } }" + }; + const file2: ts.projectSystem.File = { + path: "/a/b/file2.ts", + content: "export classc { method2() { return 10; } }" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1.path], compilerOptions: { module: "amd" } }) + }; + const files = [file1, file2a, configFile, ts.projectSystem.libFile]; + const host = ts.projectSystem.createServerHost(files); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + projectService.openClientFile(file1.path); + + host.writeFile(file2.path, file2.content); + host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions + host.runQueuedTimeoutCallbacks(); // Actual update + + // On next file open the files file2a should be closed and not watched any more + projectService.openClientFile(file2.path); + ts.projectSystem.baselineTsserverLogs("configuredProjects", "changed module resolution reflected when specifying files list", projectService); + }); - const host = createServerHost([file1, libFile, configFile]); - const { session, events } = createSessionWithEventTracking(host, server.ConfigFileDiagEvent); - const originalReadFile = host.readFile; - host.readFile = f => { - return f === configFile.path ? - undefined : - originalReadFile.call(host, f); - }; - openFilesForSession([file1], session); - - assert.deepEqual(events, [{ - eventName: server.ConfigFileDiagEvent, - data: { - triggerFile: file1.path, - configFileName: configFile.path, - diagnostics: [ - createCompilerDiagnostic(Diagnostics.Cannot_read_file_0, configFile.path) - ] - } - }]); - }); + it("Failed lookup locations uses parent most node_modules directory", () => { + const root = "/user/username/rootfolder"; + const file1: ts.projectSystem.File = { + path: "/a/b/src/file1.ts", + content: 'import { classc } from "module1"' + }; + const module1: ts.projectSystem.File = { + path: "/a/b/node_modules/module1/index.d.ts", + content: `import { class2 } from "module2"; + export classc { method2a(): class2; }` + }; + const module2: ts.projectSystem.File = { + path: "/a/b/node_modules/module2/index.d.ts", + content: "export class2 { method2() { return 10; } }" + }; + const module3: ts.projectSystem.File = { + path: "/a/b/node_modules/module/node_modules/module3/index.d.ts", + content: "export class3 { method2() { return 10; } }" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/src/tsconfig.json", + content: JSON.stringify({ files: ["file1.ts"] }) + }; + const nonLibFiles = [file1, module1, module2, module3, configFile]; + nonLibFiles.forEach(f => f.path = root + f.path); + const files = nonLibFiles.concat(ts.projectSystem.libFile); + const host = ts.projectSystem.createServerHost(files); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + projectService.openClientFile(file1.path); + ts.projectSystem.baselineTsserverLogs("configuredProjects", "failed lookup locations uses parent most node_modules directory", projectService); + }); +}); + +describe("unittests:: tsserver:: ConfiguredProjects:: when reading tsconfig file fails", () => { + it("should be tolerated without crashing the server", () => { + const configFile = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "" + }; + const file1 = { + path: `${ts.tscWatch.projectRoot}/file1.ts`, + content: "let t = 10;" + }; + + const host = ts.projectSystem.createServerHost([file1, ts.projectSystem.libFile, configFile]); + const { session, events } = ts.projectSystem.createSessionWithEventTracking(host, ts.server.ConfigFileDiagEvent); + const originalReadFile = host.readFile; + host.readFile = f => { + return f === configFile.path ? + undefined : + originalReadFile.call(host, f); + }; + ts.projectSystem.openFilesForSession([file1], session); + + assert.deepEqual(events, [{ + eventName: ts.server.ConfigFileDiagEvent, + data: { + triggerFile: file1.path, + configFileName: configFile.path, + diagnostics: [ + ts.createCompilerDiagnostic(ts.Diagnostics.Cannot_read_file_0, configFile.path) + ] + } + }]); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/declarationFileMaps.ts b/src/testRunner/unittests/tsserver/declarationFileMaps.ts index 00118dbbcea95..ccc6b96263725 100644 --- a/src/testRunner/unittests/tsserver/declarationFileMaps.ts +++ b/src/testRunner/unittests/tsserver/declarationFileMaps.ts @@ -1,771 +1,771 @@ -namespace ts.projectSystem { - function documentSpanFromSubstring({ file, text, contextText, options, contextOptions }: DocumentSpanFromSubstring): DocumentSpan { - const contextSpan = contextText !== undefined ? documentSpanFromSubstring({ file, text: contextText, options: contextOptions }) : undefined; - return { - fileName: file.path, - textSpan: textSpanFromSubstring(file.content, text, options), - ...contextSpan && { contextSpan: contextSpan.textSpan } - }; - } - - function renameLocation(input: DocumentSpanFromSubstring): RenameLocation { - return documentSpanFromSubstring(input); - } - - interface MakeReferenceEntry extends DocumentSpanFromSubstring { - isDefinition?: boolean; - isWriteAccess?: boolean; - } - function makeReferencedSymbolEntry({ isDefinition, isWriteAccess, ...rest }: MakeReferenceEntry): ReferencedSymbolEntry { - const result = { - ...documentSpanFromSubstring(rest), - isDefinition, - isWriteAccess: !!isWriteAccess, - isInString: undefined, - }; - if (isDefinition === undefined) { - delete result.isDefinition; - } - return result; - } +import * as ts from "../../_namespaces/ts"; + +function documentSpanFromSubstring({ file, text, contextText, options, contextOptions }: ts.projectSystem.DocumentSpanFromSubstring): ts.DocumentSpan { + const contextSpan = contextText !== undefined ? documentSpanFromSubstring({ file, text: contextText, options: contextOptions }) : undefined; + return { + fileName: file.path, + textSpan: ts.projectSystem.textSpanFromSubstring(file.content, text, options), + ...contextSpan && { contextSpan: contextSpan.textSpan } + }; +} - function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: readonly File[]): void { - openFilesForSession([file], session); - const project = Debug.checkDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false)); - const program = project.getCurrentProgram()!; - const output = getFileEmitOutput(program, Debug.checkDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true); - closeFilesForSession([file], session); +function renameLocation(input: ts.projectSystem.DocumentSpanFromSubstring): ts.RenameLocation { + return documentSpanFromSubstring(input); +} - Debug.assert(!output.emitSkipped); - assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false }))); +interface MakeReferenceEntry extends ts.projectSystem.DocumentSpanFromSubstring { + isDefinition?: boolean; + isWriteAccess?: boolean; +} +function makeReferencedSymbolEntry({ isDefinition, isWriteAccess, ...rest }: MakeReferenceEntry): ts.ReferencedSymbolEntry { + const result = { + ...documentSpanFromSubstring(rest), + isDefinition, + isWriteAccess: !!isWriteAccess, + isInString: undefined, + }; + if (isDefinition === undefined) { + delete result.isDefinition; } + return result; +} - describe("unittests:: tsserver:: with declaration file maps:: project references", () => { - const aTs: File = { - path: "/a/a.ts", - content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};", - }; - const compilerOptions: CompilerOptions = { - outDir: "bin", - declaration: true, - declarationMap: true, - composite: true, - }; - const configContent = JSON.stringify({ compilerOptions }); - const aTsconfig: File = { path: "/a/tsconfig.json", content: configContent }; - - const aDtsMapContent: RawSourceMap = { - version: 3, - file: "a.d.ts", - sourceRoot: "", - sources: ["../a.ts"], - names: [], - mappings: "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC" - }; - const aDtsMap: File = { - path: "/a/bin/a.d.ts.map", - content: JSON.stringify(aDtsMapContent), - }; - const aDts: File = { - path: "/a/bin/a.d.ts", - // ${""} is needed to mangle the sourceMappingURL part or it breaks the build - content: `export declare function fnA(): void;\nexport interface IfaceA {\n}\nexport declare const instanceA: IfaceA;\n//# source${""}MappingURL=a.d.ts.map`, - }; - - const bTs: File = { - path: "/b/b.ts", - content: "export function fnB() {}", - }; - const bTsconfig: File = { path: "/b/tsconfig.json", content: configContent }; - - const bDtsMapContent: RawSourceMap = { - version: 3, - file: "b.d.ts", - sourceRoot: "", - sources: ["../b.ts"], - names: [], - mappings: "AAAA,wBAAgB,GAAG,SAAK", - }; - const bDtsMap: File = { - path: "/b/bin/b.d.ts.map", - content: JSON.stringify(bDtsMapContent), - }; - const bDts: File = { - // ${""} is need to mangle the sourceMappingURL part so it doesn't break the build - path: "/b/bin/b.d.ts", - content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`, - }; - - const dummyFile: File = { - path: "/dummy/dummy.ts", - content: "let a = 10;" - }; - - const userTs: File = { - path: "/user/user.ts", - content: 'import * as a from "../a/bin/a";\nimport * as b from "../b/bin/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', - }; - - const userTsForConfigProject: File = { - path: "/user/user.ts", - content: 'import * as a from "../a/a";\nimport * as b from "../b/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', - }; +function checkDeclarationFiles(file: ts.projectSystem.File, session: ts.projectSystem.TestSession, expectedFiles: readonly ts.projectSystem.File[]): void { + ts.projectSystem.openFilesForSession([file], session); + const project = ts.Debug.checkDefined(session.getProjectService().getDefaultProjectForFile(file.path as ts.server.NormalizedPath, /*ensureProject*/ false)); + const program = project.getCurrentProgram()!; + const output = ts.getFileEmitOutput(program, ts.Debug.checkDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true); + ts.projectSystem.closeFilesForSession([file], session); - const userTsconfig: File = { - path: "/user/tsconfig.json", - content: JSON.stringify({ - file: ["user.ts"], - references: [{ path: "../a" }, { path: "../b" }] - }) - }; - - function makeSampleProjects(addUserTsConfig?: boolean, keepAllFiles?: boolean) { - const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]); - const session = createSession(host); + ts.Debug.assert(!output.emitSkipped); + assert.deepEqual(output.outputFiles, expectedFiles.map((e): ts.OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false }))); +} - checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); - checkDeclarationFiles(bTs, session, [bDtsMap, bDts]); +describe("unittests:: tsserver:: with declaration file maps:: project references", () => { + const aTs: ts.projectSystem.File = { + path: "/a/a.ts", + content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};", + }; + const compilerOptions: ts.CompilerOptions = { + outDir: "bin", + declaration: true, + declarationMap: true, + composite: true, + }; + const configContent = JSON.stringify({ compilerOptions }); + const aTsconfig: ts.projectSystem.File = { path: "/a/tsconfig.json", content: configContent }; + + const aDtsMapContent: ts.RawSourceMap = { + version: 3, + file: "a.d.ts", + sourceRoot: "", + sources: ["../a.ts"], + names: [], + mappings: "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC" + }; + const aDtsMap: ts.projectSystem.File = { + path: "/a/bin/a.d.ts.map", + content: JSON.stringify(aDtsMapContent), + }; + const aDts: ts.projectSystem.File = { + path: "/a/bin/a.d.ts", + // ${""} is needed to mangle the sourceMappingURL part or it breaks the build + content: `export declare function fnA(): void;\nexport interface IfaceA {\n}\nexport declare const instanceA: IfaceA;\n//# source${""}MappingURL=a.d.ts.map`, + }; + + const bTs: ts.projectSystem.File = { + path: "/b/b.ts", + content: "export function fnB() {}", + }; + const bTsconfig: ts.projectSystem.File = { path: "/b/tsconfig.json", content: configContent }; + + const bDtsMapContent: ts.RawSourceMap = { + version: 3, + file: "b.d.ts", + sourceRoot: "", + sources: ["../b.ts"], + names: [], + mappings: "AAAA,wBAAgB,GAAG,SAAK", + }; + const bDtsMap: ts.projectSystem.File = { + path: "/b/bin/b.d.ts.map", + content: JSON.stringify(bDtsMapContent), + }; + const bDts: ts.projectSystem.File = { + // ${""} is need to mangle the sourceMappingURL part so it doesn't break the build + path: "/b/bin/b.d.ts", + content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`, + }; + + const dummyFile: ts.projectSystem.File = { + path: "/dummy/dummy.ts", + content: "let a = 10;" + }; + + const userTs: ts.projectSystem.File = { + path: "/user/user.ts", + content: 'import * as a from "../a/bin/a";\nimport * as b from "../b/bin/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', + }; + + const userTsForConfigProject: ts.projectSystem.File = { + path: "/user/user.ts", + content: 'import * as a from "../a/a";\nimport * as b from "../b/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', + }; + + const userTsconfig: ts.projectSystem.File = { + path: "/user/tsconfig.json", + content: JSON.stringify({ + file: ["user.ts"], + references: [{ path: "../a" }, { path: "../b" }] + }) + }; + + function makeSampleProjects(addUserTsConfig?: boolean, keepAllFiles?: boolean) { + const host = ts.projectSystem.createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]); + const session = ts.projectSystem.createSession(host); + + checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); + checkDeclarationFiles(bTs, session, [bDtsMap, bDts]); + + // Testing what happens if we delete the original sources. + if (!keepAllFiles) { + host.deleteFile(bTs.path); + } - // Testing what happens if we delete the original sources. - if (!keepAllFiles) { - host.deleteFile(bTs.path); - } + ts.projectSystem.openFilesForSession([userTs], session); + const service = session.getProjectService(); + // If config file then userConfig project and bConfig project since it is referenced + ts.projectSystem.checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 2 } : { inferredProjects: 1 }); + return session; + } - openFilesForSession([userTs], session); - const service = session.getProjectService(); - // If config file then userConfig project and bConfig project since it is referenced - checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 2 } : { inferredProjects: 1 }); - return session; - } + function verifyInferredProjectUnchanged(session: ts.projectSystem.TestSession) { + ts.projectSystem.checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]); + } - function verifyInferredProjectUnchanged(session: TestSession) { - checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]); - } + function verifyDummyProject(session: ts.projectSystem.TestSession) { + ts.projectSystem.checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]); + } - function verifyDummyProject(session: TestSession) { - checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]); - } + function verifyOnlyOrphanInferredProject(session: ts.projectSystem.TestSession) { + ts.projectSystem.openFilesForSession([dummyFile], session); + ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); + verifyDummyProject(session); + } - function verifyOnlyOrphanInferredProject(session: TestSession) { - openFilesForSession([dummyFile], session); - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); - verifyDummyProject(session); - } + function verifySingleInferredProject(session: ts.projectSystem.TestSession) { + ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); + verifyInferredProjectUnchanged(session); - function verifySingleInferredProject(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); - verifyInferredProjectUnchanged(session); + // Close user file should close all the projects after opening dummy file + ts.projectSystem.closeFilesForSession([userTs], session); + verifyOnlyOrphanInferredProject(session); + } - // Close user file should close all the projects after opening dummy file - closeFilesForSession([userTs], session); - verifyOnlyOrphanInferredProject(session); - } + function verifyATsConfigProject(session: ts.projectSystem.TestSession) { + ts.projectSystem.checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]); + } - function verifyATsConfigProject(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]); - } + function verifyATsConfigOriginalProject(session: ts.projectSystem.TestSession) { + ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); + verifyInferredProjectUnchanged(session); + verifyATsConfigProject(session); + // Close user file should close all the projects + ts.projectSystem.closeFilesForSession([userTs], session); + verifyOnlyOrphanInferredProject(session); + } - function verifyATsConfigOriginalProject(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); - verifyInferredProjectUnchanged(session); - verifyATsConfigProject(session); - // Close user file should close all the projects - closeFilesForSession([userTs], session); - verifyOnlyOrphanInferredProject(session); - } + function verifyATsConfigWhenOpened(session: ts.projectSystem.TestSession) { + ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); + verifyInferredProjectUnchanged(session); + verifyATsConfigProject(session); - function verifyATsConfigWhenOpened(session: TestSession) { - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); - verifyInferredProjectUnchanged(session); - verifyATsConfigProject(session); + ts.projectSystem.closeFilesForSession([userTs], session); + ts.projectSystem.openFilesForSession([dummyFile], session); + ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); + verifyDummyProject(session); + verifyATsConfigProject(session); // ATsConfig should still be alive + } - closeFilesForSession([userTs], session); - openFilesForSession([dummyFile], session); - checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); - verifyDummyProject(session); - verifyATsConfigProject(session); // ATsConfig should still be alive - } + function verifyUserTsConfigProject(session: ts.projectSystem.TestSession) { + ts.projectSystem.checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aTs.path, userTsconfig.path]); + } - function verifyUserTsConfigProject(session: TestSession) { - checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aTs.path, userTsconfig.path]); - } + it("goToDefinition", () => { + const session = makeSampleProjects(); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.Definition, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, [ + ts.projectSystem.protocolFileSpanWithContextFromSubstring({ + file: aTs, + text: "fnA", + contextText: "export function fnA() {}" + }) + ]); + verifySingleInferredProject(session); + }); - it("goToDefinition", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, [ - protocolFileSpanWithContextFromSubstring({ + it("getDefinitionAndBoundSpan", () => { + const session = makeSampleProjects(); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.DefinitionAndBoundSpan, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + textSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnA"), + definitions: [ + ts.projectSystem.protocolFileSpanWithContextFromSubstring({ file: aTs, text: "fnA", contextText: "export function fnA() {}" }) - ]); - verifySingleInferredProject(session); + ], }); + verifySingleInferredProject(session); + }); - it("getDefinitionAndBoundSpan", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), - definitions: [ - protocolFileSpanWithContextFromSubstring({ - file: aTs, - text: "fnA", - contextText: "export function fnA() {}" - }) - ], - }); - verifySingleInferredProject(session); + it("getDefinitionAndBoundSpan with file navigation", () => { + const session = makeSampleProjects(/*addUserTsConfig*/ true); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.DefinitionAndBoundSpan, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + textSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnA"), + definitions: [ + ts.projectSystem.protocolFileSpanWithContextFromSubstring({ + file: aTs, + text: "fnA", + contextText: "export function fnA() {}" + }) + ], }); + ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + verifyUserTsConfigProject(session); - it("getDefinitionAndBoundSpan with file navigation", () => { - const session = makeSampleProjects(/*addUserTsConfig*/ true); - const response = executeSessionRequest(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), - definitions: [ - protocolFileSpanWithContextFromSubstring({ - file: aTs, - text: "fnA", - contextText: "export function fnA() {}" - }) - ], - }); - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); - verifyUserTsConfigProject(session); + // Navigate to the definition + ts.projectSystem.closeFilesForSession([userTs], session); + ts.projectSystem.openFilesForSession([aTs], session); - // Navigate to the definition - closeFilesForSession([userTs], session); - openFilesForSession([aTs], session); + // UserTs configured project should be alive + ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + verifyUserTsConfigProject(session); + verifyATsConfigProject(session); - // UserTs configured project should be alive - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); - verifyUserTsConfigProject(session); - verifyATsConfigProject(session); + ts.projectSystem.closeFilesForSession([aTs], session); + verifyOnlyOrphanInferredProject(session); + }); - closeFilesForSession([aTs], session); - verifyOnlyOrphanInferredProject(session); - }); + it("goToType", () => { + const session = makeSampleProjects(); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.TypeDefinition, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "instanceA")); + assert.deepEqual(response, [ + ts.projectSystem.protocolFileSpanWithContextFromSubstring({ + file: aTs, + text: "IfaceA", + contextText: "export interface IfaceA {}" + }) + ]); + verifySingleInferredProject(session); + }); - it("goToType", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA")); - assert.deepEqual(response, [ - protocolFileSpanWithContextFromSubstring({ - file: aTs, - text: "IfaceA", - contextText: "export interface IfaceA {}" - }) - ]); - verifySingleInferredProject(session); - }); + it("goToImplementation", () => { + const session = makeSampleProjects(); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.Implementation, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, [ + ts.projectSystem.protocolFileSpanWithContextFromSubstring({ + file: aTs, + text: "fnA", + contextText: "export function fnA() {}" + })]); + verifySingleInferredProject(session); + }); - it("goToImplementation", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Implementation, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, [ - protocolFileSpanWithContextFromSubstring({ - file: aTs, - text: "fnA", - contextText: "export function fnA() {}" - })]); - verifySingleInferredProject(session); - }); + it("goToDefinition -- target does not exist", () => { + const session = makeSampleProjects(); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.CommandNames.Definition, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnB()")); + // bTs does not exist, so stick with bDts + assert.deepEqual(response, [ + ts.projectSystem.protocolFileSpanWithContextFromSubstring({ + file: bDts, + text: "fnB", + contextText: "export declare function fnB(): void;" + }) + ]); + verifySingleInferredProject(session); + }); - it("goToDefinition -- target does not exist", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, CommandNames.Definition, protocolFileLocationFromSubstring(userTs, "fnB()")); - // bTs does not exist, so stick with bDts - assert.deepEqual(response, [ - protocolFileSpanWithContextFromSubstring({ + it("navigateTo", () => { + const session = makeSampleProjects(); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.CommandNames.Navto, { file: userTs.path, searchValue: "fn" }); + assert.deepEqual(response, [ + // Keep the .d.ts file since the .ts file no longer exists + // (otherwise it would be treated as not in the project) + { + ...ts.projectSystem.protocolFileSpanFromSubstring({ file: bDts, - text: "fnB", - contextText: "export declare function fnB(): void;" - }) - ]); - verifySingleInferredProject(session); - }); - - it("navigateTo", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, CommandNames.Navto, { file: userTs.path, searchValue: "fn" }); - assert.deepEqual(response, [ - // Keep the .d.ts file since the .ts file no longer exists - // (otherwise it would be treated as not in the project) - { - ...protocolFileSpanFromSubstring({ - file: bDts, - text: "export declare function fnB(): void;" - }), - name: "fnB", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export,declare", - }, - { - ...protocolFileSpanFromSubstring({ - file: userTs, - text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" - }), - name: "fnUser", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - }, - ]); - - verifySingleInferredProject(session); - }); + text: "export declare function fnB(): void;" + }), + name: "fnB", + matchKind: "prefix", + isCaseSensitive: true, + kind: ts.ScriptElementKind.functionElement, + kindModifiers: "export,declare", + }, + { + ...ts.projectSystem.protocolFileSpanFromSubstring({ + file: userTs, + text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" + }), + name: "fnUser", + matchKind: "prefix", + isCaseSensitive: true, + kind: ts.ScriptElementKind.functionElement, + kindModifiers: "export", + }, + ]); + + verifySingleInferredProject(session); + }); - it("navigateToAll -- when neither file nor project is specified", () => { - const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true); - const response = executeSessionRequest(session, CommandNames.Navto, { file: undefined, searchValue: "fn" }); - assert.deepEqual(response, [ - { - ...protocolFileSpanFromSubstring({ - file: bTs, - text: "export function fnB() {}" - }), - name: "fnB", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - }, - { - ...protocolFileSpanFromSubstring({ - file: aTs, - text: "export function fnA() {}" - }), - name: "fnA", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - }, - { - ...protocolFileSpanFromSubstring({ - file: userTs, - text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" - }), - name: "fnUser", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - } - ]); - }); + it("navigateToAll -- when neither file nor project is specified", () => { + const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.CommandNames.Navto, { file: undefined, searchValue: "fn" }); + assert.deepEqual(response, [ + { + ...ts.projectSystem.protocolFileSpanFromSubstring({ + file: bTs, + text: "export function fnB() {}" + }), + name: "fnB", + matchKind: "prefix", + isCaseSensitive: true, + kind: ts.ScriptElementKind.functionElement, + kindModifiers: "export", + }, + { + ...ts.projectSystem.protocolFileSpanFromSubstring({ + file: aTs, + text: "export function fnA() {}" + }), + name: "fnA", + matchKind: "prefix", + isCaseSensitive: true, + kind: ts.ScriptElementKind.functionElement, + kindModifiers: "export", + }, + { + ...ts.projectSystem.protocolFileSpanFromSubstring({ + file: userTs, + text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" + }), + name: "fnUser", + matchKind: "prefix", + isCaseSensitive: true, + kind: ts.ScriptElementKind.functionElement, + kindModifiers: "export", + } + ]); + }); - it("navigateToAll -- when file is not specified but project is", () => { - const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true); - const response = executeSessionRequest(session, CommandNames.Navto, { projectFileName: bTsconfig.path, file: undefined, searchValue: "fn" }); - assert.deepEqual(response, [ - { - ...protocolFileSpanFromSubstring({ - file: bTs, - text: "export function fnB() {}" - }), - name: "fnB", - matchKind: "prefix", - isCaseSensitive: true, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - } - ]); - }); + it("navigateToAll -- when file is not specified but project is", () => { + const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.CommandNames.Navto, { projectFileName: bTsconfig.path, file: undefined, searchValue: "fn" }); + assert.deepEqual(response, [ + { + ...ts.projectSystem.protocolFileSpanFromSubstring({ + file: bTs, + text: "export function fnB() {}" + }), + name: "fnB", + matchKind: "prefix", + isCaseSensitive: true, + kind: ts.ScriptElementKind.functionElement, + kindModifiers: "export", + } + ]); + }); - const referenceATs = (aTs: File, isDefinition: true | undefined): protocol.ReferencesResponseItem => makeReferenceItem({ - file: aTs, + const referenceATs = (aTs: ts.projectSystem.File, isDefinition: true | undefined): ts.projectSystem.protocol.ReferencesResponseItem => ts.projectSystem.makeReferenceItem({ + file: aTs, + isDefinition, + isWriteAccess: true, + text: "fnA", + contextText: "export function fnA() {}", + lineText: "export function fnA() {}" + }); + const referencesUserTs = (userTs: ts.projectSystem.File, isDefinition: false | undefined): readonly ts.projectSystem.protocol.ReferencesResponseItem[] => [ + ts.projectSystem.makeReferenceItem({ + file: userTs, isDefinition, - isWriteAccess: true, text: "fnA", - contextText: "export function fnA() {}", - lineText: "export function fnA() {}" + lineText: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" + }), + ]; + + it("findAllReferences", () => { + const session = makeSampleProjects(); + + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.References, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + refs: [...referencesUserTs(userTs, /*isDefinition*/ undefined), referenceATs(aTs, /*isDefinition*/ undefined)], + symbolName: "fnA", + symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(userTs.content, "fnA()").offset, + symbolDisplayString: "function fnA(): void", }); - const referencesUserTs = (userTs: File, isDefinition: false | undefined): readonly protocol.ReferencesResponseItem[] => [ - makeReferenceItem({ - file: userTs, - isDefinition, - text: "fnA", - lineText: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" - }), - ]; - - it("findAllReferences", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - refs: [...referencesUserTs(userTs, /*isDefinition*/ undefined), referenceATs(aTs, /*isDefinition*/ undefined)], - symbolName: "fnA", - symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnA()").offset, - symbolDisplayString: "function fnA(): void", - }); - - verifyATsConfigOriginalProject(session); - }); + verifyATsConfigOriginalProject(session); + }); - it("findAllReferences -- starting at definition", () => { - const session = makeSampleProjects(); - openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. - const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(aTs, "fnA")); - assert.deepEqual(response, { - refs: [referenceATs(aTs, /*isDefinition*/ true), ...referencesUserTs(userTs, /*isDefinition*/ false)], - symbolName: "fnA", - symbolStartOffset: protocolLocationFromSubstring(aTs.content, "fnA").offset, - symbolDisplayString: "function fnA(): void", - }); - verifyATsConfigWhenOpened(session); + it("findAllReferences -- starting at definition", () => { + const session = makeSampleProjects(); + ts.projectSystem.openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.References, ts.projectSystem.protocolFileLocationFromSubstring(aTs, "fnA")); + assert.deepEqual(response, { + refs: [referenceATs(aTs, /*isDefinition*/ true), ...referencesUserTs(userTs, /*isDefinition*/ false)], + symbolName: "fnA", + symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(aTs.content, "fnA").offset, + symbolDisplayString: "function fnA(): void", }); + verifyATsConfigWhenOpened(session); + }); - interface ReferencesFullRequest extends protocol.FileLocationRequest { readonly command: protocol.CommandTypes.ReferencesFull; } - interface ReferencesFullResponse extends protocol.Response { readonly body: readonly ReferencedSymbol[]; } + interface ReferencesFullRequest extends ts.projectSystem.protocol.FileLocationRequest { readonly command: ts.projectSystem.protocol.CommandTypes.ReferencesFull; } + interface ReferencesFullResponse extends ts.projectSystem.protocol.Response { readonly body: readonly ts.ReferencedSymbol[]; } - it("findAllReferencesFull", () => { - const session = makeSampleProjects(); + it("findAllReferencesFull", () => { + const session = makeSampleProjects(); - const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(userTs, "fnA()")); + const responseFull = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.ReferencesFull, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(responseFull, [ - { - definition: { - ...documentSpanFromSubstring({ - file: aTs, - text: "fnA", - contextText: "export function fnA() {}" - }), - kind: ScriptElementKind.functionElement, - name: "function fnA(): void", - containerKind: ScriptElementKind.unknown, - containerName: "", - displayParts: [ - keywordPart(SyntaxKind.FunctionKeyword), - spacePart(), - displayPart("fnA", SymbolDisplayPartKind.functionName), - punctuationPart(SyntaxKind.OpenParenToken), - punctuationPart(SyntaxKind.CloseParenToken), - punctuationPart(SyntaxKind.ColonToken), - spacePart(), - keywordPart(SyntaxKind.VoidKeyword), - ], - }, - references: [ - makeReferencedSymbolEntry({ file: userTs, text: "fnA" }), - makeReferencedSymbolEntry({ file: aTs, text: "fnA", isWriteAccess: true, contextText: "export function fnA() {}" }), + assert.deepEqual(responseFull, [ + { + definition: { + ...documentSpanFromSubstring({ + file: aTs, + text: "fnA", + contextText: "export function fnA() {}" + }), + kind: ts.ScriptElementKind.functionElement, + name: "function fnA(): void", + containerKind: ts.ScriptElementKind.unknown, + containerName: "", + displayParts: [ + ts.keywordPart(ts.SyntaxKind.FunctionKeyword), + ts.spacePart(), + ts.displayPart("fnA", ts.SymbolDisplayPartKind.functionName), + ts.punctuationPart(ts.SyntaxKind.OpenParenToken), + ts.punctuationPart(ts.SyntaxKind.CloseParenToken), + ts.punctuationPart(ts.SyntaxKind.ColonToken), + ts.spacePart(), + ts.keywordPart(ts.SyntaxKind.VoidKeyword), ], }, - ]); - verifyATsConfigOriginalProject(session); - }); + references: [ + makeReferencedSymbolEntry({ file: userTs, text: "fnA" }), + makeReferencedSymbolEntry({ file: aTs, text: "fnA", isWriteAccess: true, contextText: "export function fnA() {}" }), + ], + }, + ]); + verifyATsConfigOriginalProject(session); + }); - it("findAllReferencesFull definition is in mapped file", () => { - const aTs: File = { path: "/a/a.ts", content: `function f() {}` }; - const aTsconfig: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }), - }; - const bTs: File = { path: "/b/b.ts", content: `f();` }; - const bTsconfig: File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) }; - const aDts: File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` }; - const aDtsMap: File = { - path: "/bin/a.d.ts.map", - content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }), - }; - - const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap])); - checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); - openFilesForSession([bTs], session); - checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); // configured project of b is alive since a references b - - const responseFull = executeSessionRequest(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()")); - - assert.deepEqual(responseFull, [ - { - definition: { - ...documentSpanFromSubstring({ - file: aTs, - text: "f", - options: { index: 1 }, - contextText: "function f() {}" - }), - containerKind: ScriptElementKind.unknown, - containerName: "", - displayParts: [ - keywordPart(SyntaxKind.FunctionKeyword), - spacePart(), - displayPart("f", SymbolDisplayPartKind.functionName), - punctuationPart(SyntaxKind.OpenParenToken), - punctuationPart(SyntaxKind.CloseParenToken), - punctuationPart(SyntaxKind.ColonToken), - spacePart(), - keywordPart(SyntaxKind.VoidKeyword), - ], - kind: ScriptElementKind.functionElement, - name: "function f(): void", - }, - references: [ - makeReferencedSymbolEntry({ - file: aTs, - text: "f", - options: { index: 1 }, - contextText: "function f() {}", - isWriteAccess: true, - }), - { - fileName: bTs.path, - isInString: undefined, - isWriteAccess: false, - textSpan: { start: 0, length: 1 }, - }, - ], - } - ]); - }); + it("findAllReferencesFull definition is in mapped file", () => { + const aTs: ts.projectSystem.File = { path: "/a/a.ts", content: `function f() {}` }; + const aTsconfig: ts.projectSystem.File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }), + }; + const bTs: ts.projectSystem.File = { path: "/b/b.ts", content: `f();` }; + const bTsconfig: ts.projectSystem.File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) }; + const aDts: ts.projectSystem.File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` }; + const aDtsMap: ts.projectSystem.File = { + path: "/bin/a.d.ts.map", + content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }), + }; - it("findAllReferences -- target does not exist", () => { - const session = makeSampleProjects(); + const session = ts.projectSystem.createSession(ts.projectSystem.createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap])); + checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); + ts.projectSystem.openFilesForSession([bTs], session); + ts.projectSystem.checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); // configured project of b is alive since a references b - const response = executeSessionRequest(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnB()")); - assert.deepEqual(response, { - refs: [ - makeReferenceItem({ - file: bDts, - isWriteAccess: true, - text: "fnB", - contextText: "export declare function fnB(): void;", - lineText: "export declare function fnB(): void;" + const responseFull = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.ReferencesFull, ts.projectSystem.protocolFileLocationFromSubstring(bTs, "f()")); + + assert.deepEqual(responseFull, [ + { + definition: { + ...documentSpanFromSubstring({ + file: aTs, + text: "f", + options: { index: 1 }, + contextText: "function f() {}" }), - makeReferenceItem({ - file: userTs, - text: "fnB", - lineText: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" + containerKind: ts.ScriptElementKind.unknown, + containerName: "", + displayParts: [ + ts.keywordPart(ts.SyntaxKind.FunctionKeyword), + ts.spacePart(), + ts.displayPart("f", ts.SymbolDisplayPartKind.functionName), + ts.punctuationPart(ts.SyntaxKind.OpenParenToken), + ts.punctuationPart(ts.SyntaxKind.CloseParenToken), + ts.punctuationPart(ts.SyntaxKind.ColonToken), + ts.spacePart(), + ts.keywordPart(ts.SyntaxKind.VoidKeyword), + ], + kind: ts.ScriptElementKind.functionElement, + name: "function f(): void", + }, + references: [ + makeReferencedSymbolEntry({ + file: aTs, + text: "f", + options: { index: 1 }, + contextText: "function f() {}", + isWriteAccess: true, }), + { + fileName: bTs.path, + isInString: undefined, + isWriteAccess: false, + textSpan: { start: 0, length: 1 }, + }, ], - symbolName: "fnB", - symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnB()").offset, - symbolDisplayString: "function fnB(): void", - }); - verifySingleInferredProject(session); - }); + } + ]); + }); - const renameATs = (aTs: File): protocol.SpanGroup => ({ - file: aTs.path, - locs: [ - protocolRenameSpanFromSubstring({ - fileText: aTs.content, - text: "fnA", - contextText: "export function fnA() {}" - }) - ], - }); - const renameUserTs = (userTs: File): protocol.SpanGroup => ({ - file: userTs.path, - locs: [ - protocolRenameSpanFromSubstring({ - fileText: userTs.content, - text: "fnA" - }) + it("findAllReferences -- target does not exist", () => { + const session = makeSampleProjects(); + + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.References, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnB()")); + assert.deepEqual(response, { + refs: [ + ts.projectSystem.makeReferenceItem({ + file: bDts, + isWriteAccess: true, + text: "fnB", + contextText: "export declare function fnB(): void;", + lineText: "export declare function fnB(): void;" + }), + ts.projectSystem.makeReferenceItem({ + file: userTs, + text: "fnB", + lineText: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" + }), ], + symbolName: "fnB", + symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(userTs.content, "fnB()").offset, + symbolDisplayString: "function fnB(): void", }); + verifySingleInferredProject(session); + }); - it("renameLocations", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, { - info: { - canRename: true, - displayName: "fnA", - fileToRename: undefined, - fullDisplayName: '"/a/bin/a".fnA', // Ideally this would use the original source's path instead of the declaration file's path. - kind: ScriptElementKind.functionElement, - kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), - triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), - }, - locs: [renameUserTs(userTs), renameATs(aTs)], - }); - verifyATsConfigOriginalProject(session); - }); + const renameATs = (aTs: ts.projectSystem.File): ts.projectSystem.protocol.SpanGroup => ({ + file: aTs.path, + locs: [ + ts.projectSystem.protocolRenameSpanFromSubstring({ + fileText: aTs.content, + text: "fnA", + contextText: "export function fnA() {}" + }) + ], + }); + const renameUserTs = (userTs: ts.projectSystem.File): ts.projectSystem.protocol.SpanGroup => ({ + file: userTs.path, + locs: [ + ts.projectSystem.protocolRenameSpanFromSubstring({ + fileText: userTs.content, + text: "fnA" + }) + ], + }); - it("renameLocations -- starting at definition", () => { - const session = makeSampleProjects(); - openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "fnA")); - assert.deepEqual(response, { - info: { - canRename: true, - displayName: "fnA", - fileToRename: undefined, - fullDisplayName: '"/a/a".fnA', - kind: ScriptElementKind.functionElement, - kindModifiers: ScriptElementKindModifier.exportedModifier, - triggerSpan: protocolTextSpanFromSubstring(aTs.content, "fnA"), - }, - locs: [renameATs(aTs), renameUserTs(userTs)], - }); - verifyATsConfigWhenOpened(session); + it("renameLocations", () => { + const session = makeSampleProjects(); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.Rename, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, { + info: { + canRename: true, + displayName: "fnA", + fileToRename: undefined, + fullDisplayName: '"/a/bin/a".fnA', // Ideally this would use the original source's path instead of the declaration file's path. + kind: ts.ScriptElementKind.functionElement, + kindModifiers: [ts.ScriptElementKindModifier.exportedModifier, ts.ScriptElementKindModifier.ambientModifier].join(","), + triggerSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnA"), + }, + locs: [renameUserTs(userTs), renameATs(aTs)], }); + verifyATsConfigOriginalProject(session); + }); - it("renameLocationsFull", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()")); - assert.deepEqual(response, [ - renameLocation({ file: userTs, text: "fnA" }), - renameLocation({ file: aTs, text: "fnA", contextText: "export function fnA() {}" }), - ]); - verifyATsConfigOriginalProject(session); + it("renameLocations -- starting at definition", () => { + const session = makeSampleProjects(); + ts.projectSystem.openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.Rename, ts.projectSystem.protocolFileLocationFromSubstring(aTs, "fnA")); + assert.deepEqual(response, { + info: { + canRename: true, + displayName: "fnA", + fileToRename: undefined, + fullDisplayName: '"/a/a".fnA', + kind: ts.ScriptElementKind.functionElement, + kindModifiers: ts.ScriptElementKindModifier.exportedModifier, + triggerSpan: ts.projectSystem.protocolTextSpanFromSubstring(aTs.content, "fnA"), + }, + locs: [renameATs(aTs), renameUserTs(userTs)], }); + verifyATsConfigWhenOpened(session); + }); - it("renameLocations -- target does not exist", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnB()")); - assert.deepEqual(response, { - info: { - canRename: true, - displayName: "fnB", - fileToRename: undefined, - fullDisplayName: '"/b/bin/b".fnB', - kind: ScriptElementKind.functionElement, - kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), - triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"), - }, - locs: [ - { - file: bDts.path, - locs: [ - protocolRenameSpanFromSubstring({ - fileText: bDts.content, - text: "fnB", - contextText: "export declare function fnB(): void;" - }) - ], - }, - { - file: userTs.path, - locs: [ - protocolRenameSpanFromSubstring({ - fileText: userTs.content, - text: "fnB" - }) - ], - }, - ], - }); - verifySingleInferredProject(session); - }); + it("renameLocationsFull", () => { + const session = makeSampleProjects(); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.RenameLocationsFull, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()")); + assert.deepEqual(response, [ + renameLocation({ file: userTs, text: "fnA" }), + renameLocation({ file: aTs, text: "fnA", contextText: "export function fnA() {}" }), + ]); + verifyATsConfigOriginalProject(session); + }); - it("getEditsForFileRename", () => { - const session = makeSampleProjects(); - const response = executeSessionRequest(session, protocol.CommandTypes.GetEditsForFileRename, { - oldFilePath: aTs.path, - newFilePath: "/a/aNew.ts", - }); - assert.deepEqual(response, [ + it("renameLocations -- target does not exist", () => { + const session = makeSampleProjects(); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.Rename, ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnB()")); + assert.deepEqual(response, { + info: { + canRename: true, + displayName: "fnB", + fileToRename: undefined, + fullDisplayName: '"/b/bin/b".fnB', + kind: ts.ScriptElementKind.functionElement, + kindModifiers: [ts.ScriptElementKindModifier.exportedModifier, ts.ScriptElementKindModifier.ambientModifier].join(","), + triggerSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnB"), + }, + locs: [ + { + file: bDts.path, + locs: [ + ts.projectSystem.protocolRenameSpanFromSubstring({ + fileText: bDts.content, + text: "fnB", + contextText: "export declare function fnB(): void;" + }) + ], + }, { - fileName: userTs.path, - textChanges: [ - { ...protocolTextSpanFromSubstring(userTs.content, "../a/bin/a"), newText: "../a/bin/aNew" }, + file: userTs.path, + locs: [ + ts.projectSystem.protocolRenameSpanFromSubstring({ + fileText: userTs.content, + text: "fnB" + }) ], }, - ]); - verifySingleInferredProject(session); + ], }); + verifySingleInferredProject(session); + }); - it("getEditsForFileRename when referencing project doesnt include file and its renamed", () => { - const aTs: File = { path: "/a/src/a.ts", content: "" }; - const aTsconfig: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - composite: true, - declaration: true, - declarationMap: true, - outDir: "./build", - } - }), - }; - const bTs: File = { path: "/b/src/b.ts", content: "" }; - const bTsconfig: File = { - path: "/b/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "./build", - }, - include: ["./src"], - references: [{ path: "../a" }], - }), - }; - - const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]); - const session = createSession(host); - openFilesForSession([aTs, bTs], session); - const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { - oldFilePath: aTs.path, - newFilePath: "/a/src/a1.ts", - }); - assert.deepEqual(response, []); // Should not change anything + it("getEditsForFileRename", () => { + const session = makeSampleProjects(); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.GetEditsForFileRename, { + oldFilePath: aTs.path, + newFilePath: "/a/aNew.ts", }); + assert.deepEqual(response, [ + { + fileName: userTs.path, + textChanges: [ + { ...ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "../a/bin/a"), newText: "../a/bin/aNew" }, + ], + }, + ]); + verifySingleInferredProject(session); + }); - it("does not jump to source if inlined sources", () => { - const aDtsInlinedSources: RawSourceMap = { - ...aDtsMapContent, - sourcesContent: [aTs.content] - }; - const aDtsMapInlinedSources: File = { - path: aDtsMap.path, - content: JSON.stringify(aDtsInlinedSources) - }; - const host = createServerHost([aTs, aDtsMapInlinedSources, aDts, bTs, bDtsMap, bDts, userTs, dummyFile]); - const session = createSession(host); - - openFilesForSession([userTs], session); - const service = session.getProjectService(); - // If config file then userConfig project and bConfig project since it is referenced - checkNumberOfProjects(service, { inferredProjects: 1 }); - - // Inlined so does not jump to aTs - assert.deepEqual( - executeSessionRequest( - session, - protocol.CommandTypes.DefinitionAndBoundSpan, - protocolFileLocationFromSubstring(userTs, "fnA()") - ), - { - textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), - definitions: [ - protocolFileSpanWithContextFromSubstring({ - file: aDts, - text: "fnA", - contextText: "export declare function fnA(): void;" - }) - ], - } - ); - - // Not inlined, jumps to bTs - assert.deepEqual( - executeSessionRequest( - session, - protocol.CommandTypes.DefinitionAndBoundSpan, - protocolFileLocationFromSubstring(userTs, "fnB()") - ), - { - textSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"), - definitions: [ - protocolFileSpanWithContextFromSubstring({ - file: bTs, - text: "fnB", - contextText: "export function fnB() {}" - }) - ], + it("getEditsForFileRename when referencing project doesnt include file and its renamed", () => { + const aTs: ts.projectSystem.File = { path: "/a/src/a.ts", content: "" }; + const aTsconfig: ts.projectSystem.File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + composite: true, + declaration: true, + declarationMap: true, + outDir: "./build", } - ); + }), + }; + const bTs: ts.projectSystem.File = { path: "/b/src/b.ts", content: "" }; + const bTsconfig: ts.projectSystem.File = { + path: "/b/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "./build", + }, + include: ["./src"], + references: [{ path: "../a" }], + }), + }; - verifySingleInferredProject(session); + const host = ts.projectSystem.createServerHost([aTs, aTsconfig, bTs, bTsconfig]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([aTs, bTs], session); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.CommandNames.GetEditsForFileRename, { + oldFilePath: aTs.path, + newFilePath: "/a/src/a1.ts", }); + assert.deepEqual(response, []); // Should not change anything }); -} + + it("does not jump to source if inlined sources", () => { + const aDtsInlinedSources: ts.RawSourceMap = { + ...aDtsMapContent, + sourcesContent: [aTs.content] + }; + const aDtsMapInlinedSources: ts.projectSystem.File = { + path: aDtsMap.path, + content: JSON.stringify(aDtsInlinedSources) + }; + const host = ts.projectSystem.createServerHost([aTs, aDtsMapInlinedSources, aDts, bTs, bDtsMap, bDts, userTs, dummyFile]); + const session = ts.projectSystem.createSession(host); + + ts.projectSystem.openFilesForSession([userTs], session); + const service = session.getProjectService(); + // If config file then userConfig project and bConfig project since it is referenced + ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 }); + + // Inlined so does not jump to aTs + assert.deepEqual( + ts.projectSystem.executeSessionRequest( + session, + ts.projectSystem.protocol.CommandTypes.DefinitionAndBoundSpan, + ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnA()") + ), + { + textSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnA"), + definitions: [ + ts.projectSystem.protocolFileSpanWithContextFromSubstring({ + file: aDts, + text: "fnA", + contextText: "export declare function fnA(): void;" + }) + ], + } + ); + + // Not inlined, jumps to bTs + assert.deepEqual( + ts.projectSystem.executeSessionRequest( + session, + ts.projectSystem.protocol.CommandTypes.DefinitionAndBoundSpan, + ts.projectSystem.protocolFileLocationFromSubstring(userTs, "fnB()") + ), + { + textSpan: ts.projectSystem.protocolTextSpanFromSubstring(userTs.content, "fnB"), + definitions: [ + ts.projectSystem.protocolFileSpanWithContextFromSubstring({ + file: bTs, + text: "fnB", + contextText: "export function fnB() {}" + }) + ], + } + ); + + verifySingleInferredProject(session); + }); +}); diff --git a/src/testRunner/unittests/tsserver/documentRegistry.ts b/src/testRunner/unittests/tsserver/documentRegistry.ts index 8c29af51a27a0..fbb291a738a24 100644 --- a/src/testRunner/unittests/tsserver/documentRegistry.ts +++ b/src/testRunner/unittests/tsserver/documentRegistry.ts @@ -1,93 +1,93 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: document registry in project service", () => { - const importModuleContent = `import {a} from "./module1"`; - const file: File = { - path: `${tscWatch.projectRoot}/index.ts`, - content: importModuleContent - }; - const moduleFile: File = { - path: `${tscWatch.projectRoot}/module1.d.ts`, - content: "export const a: number;" - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ files: ["index.ts"] }) - }; +import * as ts from "../../_namespaces/ts"; - function getProject(service: TestProjectService) { - return service.configuredProjects.get(configFile.path)!; - } +describe("unittests:: tsserver:: document registry in project service", () => { + const importModuleContent = `import {a} from "./module1"`; + const file: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/index.ts`, + content: importModuleContent + }; + const moduleFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/module1.d.ts`, + content: "export const a: number;" + }; + const configFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ files: ["index.ts"] }) + }; - function checkProject(service: TestProjectService, moduleIsOrphan: boolean) { - // Update the project - const project = getProject(service); - project.getLanguageService(); - checkProjectActualFiles(project, [file.path, libFile.path, configFile.path, ...(moduleIsOrphan ? [] : [moduleFile.path])]); - const moduleInfo = service.getScriptInfo(moduleFile.path)!; - assert.isDefined(moduleInfo); - assert.equal(moduleInfo.isOrphan(), moduleIsOrphan); - const key = service.documentRegistry.getKeyForCompilationSettings(project.getCompilationSettings()); - assert.deepEqual(service.documentRegistry.getLanguageServiceRefCounts(moduleInfo.path, moduleInfo.scriptKind), [[key, moduleIsOrphan ? undefined : 1]]); - } + function getProject(service: ts.projectSystem.TestProjectService) { + return service.configuredProjects.get(configFile.path)!; + } - function createServiceAndHost() { - const host = createServerHost([file, moduleFile, libFile, configFile]); - const service = createProjectService(host); - service.openClientFile(file.path); - checkProject(service, /*moduleIsOrphan*/ false); - return { host, service }; - } + function checkProject(service: ts.projectSystem.TestProjectService, moduleIsOrphan: boolean) { + // Update the project + const project = getProject(service); + project.getLanguageService(); + ts.projectSystem.checkProjectActualFiles(project, [file.path, ts.projectSystem.libFile.path, configFile.path, ...(moduleIsOrphan ? [] : [moduleFile.path])]); + const moduleInfo = service.getScriptInfo(moduleFile.path)!; + assert.isDefined(moduleInfo); + assert.equal(moduleInfo.isOrphan(), moduleIsOrphan); + const key = service.documentRegistry.getKeyForCompilationSettings(project.getCompilationSettings()); + assert.deepEqual(service.documentRegistry.getLanguageServiceRefCounts(moduleInfo.path, moduleInfo.scriptKind), [[key, moduleIsOrphan ? undefined : 1]]); + } - function changeFileToNotImportModule(service: TestProjectService) { - const info = service.getScriptInfo(file.path)!; - service.applyChangesToFile(info, singleIterator({ span: { start: 0, length: importModuleContent.length }, newText: "" })); - checkProject(service, /*moduleIsOrphan*/ true); - } + function createServiceAndHost() { + const host = ts.projectSystem.createServerHost([file, moduleFile, ts.projectSystem.libFile, configFile]); + const service = ts.projectSystem.createProjectService(host); + service.openClientFile(file.path); + checkProject(service, /*moduleIsOrphan*/ false); + return { host, service }; + } - function changeFileToImportModule(service: TestProjectService) { - const info = service.getScriptInfo(file.path)!; - service.applyChangesToFile(info, singleIterator({ span: { start: 0, length: 0 }, newText: importModuleContent })); - checkProject(service, /*moduleIsOrphan*/ false); - } + function changeFileToNotImportModule(service: ts.projectSystem.TestProjectService) { + const info = service.getScriptInfo(file.path)!; + service.applyChangesToFile(info, ts.singleIterator({ span: { start: 0, length: importModuleContent.length }, newText: "" })); + checkProject(service, /*moduleIsOrphan*/ true); + } - it("Caches the source file if script info is orphan", () => { - const { service } = createServiceAndHost(); - const project = getProject(service); + function changeFileToImportModule(service: ts.projectSystem.TestProjectService) { + const info = service.getScriptInfo(file.path)!; + service.applyChangesToFile(info, ts.singleIterator({ span: { start: 0, length: 0 }, newText: importModuleContent })); + checkProject(service, /*moduleIsOrphan*/ false); + } - const moduleInfo = service.getScriptInfo(moduleFile.path)!; - const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; - assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); + it("Caches the source file if script info is orphan", () => { + const { service } = createServiceAndHost(); + const project = getProject(service); - // edit file - changeFileToNotImportModule(service); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + const moduleInfo = service.getScriptInfo(moduleFile.path)!; + const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; + assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); - // write content back - changeFileToImportModule(service); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); - }); + // edit file + changeFileToNotImportModule(service); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - it("Caches the source file if script info is orphan, and orphan script info changes", () => { - const { host, service } = createServiceAndHost(); - const project = getProject(service); + // write content back + changeFileToImportModule(service); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); + }); + + it("Caches the source file if script info is orphan, and orphan script info changes", () => { + const { host, service } = createServiceAndHost(); + const project = getProject(service); - const moduleInfo = service.getScriptInfo(moduleFile.path)!; - const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; - assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); + const moduleInfo = service.getScriptInfo(moduleFile.path)!; + const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; + assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); - // edit file - changeFileToNotImportModule(service); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + // edit file + changeFileToNotImportModule(service); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - const updatedModuleContent = moduleFile.content + "\nexport const b: number;"; - host.writeFile(moduleFile.path, updatedModuleContent); + const updatedModuleContent = moduleFile.content + "\nexport const b: number;"; + host.writeFile(moduleFile.path, updatedModuleContent); - // write content back - changeFileToImportModule(service); - assert.notEqual(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); - assert.equal(project.getSourceFile(moduleInfo.path), moduleInfo.cacheSourceFile!.sourceFile); - assert.equal(moduleInfo.cacheSourceFile!.sourceFile.text, updatedModuleContent); - }); + // write content back + changeFileToImportModule(service); + assert.notEqual(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); + assert.equal(project.getSourceFile(moduleInfo.path), moduleInfo.cacheSourceFile!.sourceFile); + assert.equal(moduleInfo.cacheSourceFile!.sourceFile.text, updatedModuleContent); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/duplicatePackages.ts b/src/testRunner/unittests/tsserver/duplicatePackages.ts index 366ca5ea379c4..7dac9af67e01a 100644 --- a/src/testRunner/unittests/tsserver/duplicatePackages.ts +++ b/src/testRunner/unittests/tsserver/duplicatePackages.ts @@ -1,54 +1,54 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: duplicate packages", () => { - // Tests that 'moduleSpecifiers.ts' will import from the redirecting file, and not from the file it redirects to, if that can provide a global module specifier. - it("works with import fixes", () => { - const packageContent = "export const foo: number;"; - const packageJsonContent = JSON.stringify({ name: "foo", version: "1.2.3" }); - const aFooIndex: File = { path: "/a/node_modules/foo/index.d.ts", content: packageContent }; - const aFooPackage: File = { path: "/a/node_modules/foo/package.json", content: packageJsonContent }; - const bFooIndex: File = { path: "/b/node_modules/foo/index.d.ts", content: packageContent }; - const bFooPackage: File = { path: "/b/node_modules/foo/package.json", content: packageJsonContent }; +import * as ts from "../../_namespaces/ts"; - const userContent = 'import("foo");\nfoo'; - const aUser: File = { path: "/a/user.ts", content: userContent }; - const bUser: File = { path: "/b/user.ts", content: userContent }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", - }; +describe("unittests:: tsserver:: duplicate packages", () => { + // Tests that 'moduleSpecifiers.ts' will import from the redirecting file, and not from the file it redirects to, if that can provide a global module specifier. + it("works with import fixes", () => { + const packageContent = "export const foo: number;"; + const packageJsonContent = JSON.stringify({ name: "foo", version: "1.2.3" }); + const aFooIndex: ts.projectSystem.File = { path: "/a/node_modules/foo/index.d.ts", content: packageContent }; + const aFooPackage: ts.projectSystem.File = { path: "/a/node_modules/foo/package.json", content: packageJsonContent }; + const bFooIndex: ts.projectSystem.File = { path: "/b/node_modules/foo/index.d.ts", content: packageContent }; + const bFooPackage: ts.projectSystem.File = { path: "/b/node_modules/foo/package.json", content: packageJsonContent }; - const host = createServerHost([aFooIndex, aFooPackage, bFooIndex, bFooPackage, aUser, bUser, tsconfig]); - const session = createSession(host); + const userContent = 'import("foo");\nfoo'; + const aUser: ts.projectSystem.File = { path: "/a/user.ts", content: userContent }; + const bUser: ts.projectSystem.File = { path: "/b/user.ts", content: userContent }; + const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: "{}", + }; - openFilesForSession([aUser, bUser], session); + const host = ts.projectSystem.createServerHost([aFooIndex, aFooPackage, bFooIndex, bFooPackage, aUser, bUser, tsconfig]); + const session = ts.projectSystem.createSession(host); - for (const user of [aUser, bUser]) { - const response = executeSessionRequest(session, protocol.CommandTypes.GetCodeFixes, { - file: user.path, - startLine: 2, - startOffset: 1, - endLine: 2, - endOffset: 4, - errorCodes: [Diagnostics.Cannot_find_name_0.code], - }); - assert.deepEqual(response, [ - { - description: `Add import from "foo"`, - fixName: "import", - changes: [{ - fileName: user.path, - textChanges: [{ - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - newText: 'import { foo } from "foo";\n\n', - }], + ts.projectSystem.openFilesForSession([aUser, bUser], session); + + for (const user of [aUser, bUser]) { + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.GetCodeFixes, { + file: user.path, + startLine: 2, + startOffset: 1, + endLine: 2, + endOffset: 4, + errorCodes: [ts.Diagnostics.Cannot_find_name_0.code], + }); + assert.deepEqual(response, [ + { + description: `Add import from "foo"`, + fixName: "import", + changes: [{ + fileName: user.path, + textChanges: [{ + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, + newText: 'import { foo } from "foo";\n\n', }], - commands: undefined, - fixId: undefined, - fixAllDescription: undefined - }, - ]); - } - }); + }], + commands: undefined, + fixId: undefined, + fixAllDescription: undefined + }, + ]); + } }); -} +}); diff --git a/src/testRunner/unittests/tsserver/dynamicFiles.ts b/src/testRunner/unittests/tsserver/dynamicFiles.ts index 554c4c8c67477..8ef9fd038b405 100644 --- a/src/testRunner/unittests/tsserver/dynamicFiles.ts +++ b/src/testRunner/unittests/tsserver/dynamicFiles.ts @@ -1,260 +1,256 @@ -namespace ts.projectSystem { - export function verifyDynamic(service: server.ProjectService, path: string) { - const info = Debug.checkDefined(service.filenameToScriptInfo.get(path), `Expected ${path} in :: ${JSON.stringify(arrayFrom(service.filenameToScriptInfo.entries(), ([key, f]) => ({ key, fileName: f.fileName, path: f.path })))}`); - assert.isTrue(info.isDynamic); - } +import * as ts from "../../_namespaces/ts"; +import { verifyDynamic } from "./helpers"; - function verifyPathRecognizedAsDynamic(path: string) { - const file: File = { - path, - content: `/// +function verifyPathRecognizedAsDynamic(path: string) { + const file: ts.projectSystem.File = { + path, + content: `/// /// var x = 10;` - }; - const host = createServerHost([libFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file.path, file.content); - verifyDynamic(projectService, projectService.toPath(file.path)); + }; + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(file.path, file.content); + verifyDynamic(projectService, projectService.toPath(file.path)); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - const project = projectService.inferredProjects[0]; - checkProjectRootFiles(project, [file.path]); - checkProjectActualFiles(project, [file.path, libFile.path]); - } + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + ts.projectSystem.checkProjectRootFiles(project, [file.path]); + ts.projectSystem.checkProjectActualFiles(project, [file.path, ts.projectSystem.libFile.path]); +} - describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => { - const untitledFile = "untitled:^Untitled-1"; - it("Can convert positions to locations", () => { - const aTs: File = { path: "/proj/a.ts", content: "" }; - const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" }; - const session = createSession(createServerHost([aTs, tsconfig]), { useInferredProjectPerProjectRoot: true }); +describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => { + const untitledFile = "untitled:^Untitled-1"; + it("Can convert positions to locations", () => { + const aTs: ts.projectSystem.File = { path: "/proj/a.ts", content: "" }; + const tsconfig: ts.projectSystem.File = { path: "/proj/tsconfig.json", content: "{}" }; + const session = ts.projectSystem.createSession(ts.projectSystem.createServerHost([aTs, tsconfig]), { useInferredProjectPerProjectRoot: true }); - openFilesForSession([aTs], session); + ts.projectSystem.openFilesForSession([aTs], session); - executeSessionRequestNoResponse(session, protocol.CommandTypes.Open, { - file: untitledFile, - fileContent: `/// \nlet foo = 1;\nfooo/**/`, - scriptKindName: "TS", - projectRootPath: "/proj", - }); - verifyDynamic(session.getProjectService(), `/proj/untitled:^untitled-1`); - const response = executeSessionRequest(session, protocol.CommandTypes.GetCodeFixes, { - file: untitledFile, - startLine: 3, - startOffset: 1, - endLine: 3, - endOffset: 5, - errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_1.code], - }); - assert.deepEqual(response, [ - { - description: "Change spelling to 'foo'", - fixName: "spelling", - changes: [{ - fileName: untitledFile, - textChanges: [{ - start: { line: 3, offset: 1 }, - end: { line: 3, offset: 5 }, - newText: "foo", - }], - }], - commands: undefined, - fixId: undefined, - fixAllDescription: undefined - }, - ]); + ts.projectSystem.executeSessionRequestNoResponse(session, ts.projectSystem.protocol.CommandTypes.Open, { + file: untitledFile, + fileContent: `/// \nlet foo = 1;\nfooo/**/`, + scriptKindName: "TS", + projectRootPath: "/proj", + }); + verifyDynamic(session.getProjectService(), `/proj/untitled:^untitled-1`); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.GetCodeFixes, { + file: untitledFile, + startLine: 3, + startOffset: 1, + endLine: 3, + endOffset: 5, + errorCodes: [ts.Diagnostics.Cannot_find_name_0_Did_you_mean_1.code], }); + assert.deepEqual(response, [ + { + description: "Change spelling to 'foo'", + fixName: "spelling", + changes: [{ + fileName: untitledFile, + textChanges: [{ + start: { line: 3, offset: 1 }, + end: { line: 3, offset: 5 }, + newText: "foo", + }], + }], + commands: undefined, + fixId: undefined, + fixAllDescription: undefined + }, + ]); + }); - it("opening untitled files", () => { - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const host = createServerHost([config, libFile], { useCaseSensitiveFileNames: true, currentDirectory: tscWatch.projectRoot }); - const service = createProjectService(host); - service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`); + it("opening untitled files", () => { + const config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const host = ts.projectSystem.createServerHost([config, ts.projectSystem.libFile], { useCaseSensitiveFileNames: true, currentDirectory: ts.tscWatch.projectRoot }); + const service = ts.projectSystem.createProjectService(host); + service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, ts.tscWatch.projectRoot); + ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]); + verifyDynamic(service, `${ts.tscWatch.projectRoot}/${untitledFile}`); - const untitled: File = { - path: `${tscWatch.projectRoot}/Untitled-1.ts`, - content: "const x = 10;" - }; - host.writeFile(untitled.path, untitled.content); - host.checkTimeoutQueueLength(0); - service.openClientFile(untitled.path, untitled.content, /*scriptKind*/ undefined, tscWatch.projectRoot); - checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); - checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); + const untitled: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/Untitled-1.ts`, + content: "const x = 10;" + }; + host.writeFile(untitled.path, untitled.content); + host.checkTimeoutQueueLength(0); + service.openClientFile(untitled.path, untitled.content, /*scriptKind*/ undefined, ts.tscWatch.projectRoot); + ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, ts.projectSystem.libFile.path, config.path]); + ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]); - service.closeClientFile(untitledFile); - checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); + service.closeClientFile(untitledFile); + ts.projectSystem.checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, ts.projectSystem.libFile.path, config.path]); + ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]); - service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot); - verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`); - checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, libFile.path, config.path]); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - }); + service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, ts.tscWatch.projectRoot); + verifyDynamic(service, `${ts.tscWatch.projectRoot}/${untitledFile}`); + ts.projectSystem.checkProjectActualFiles(service.configuredProjects.get(config.path)!, [untitled.path, ts.projectSystem.libFile.path, config.path]); + ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]); + }); - it("opening and closing untitled files when projectRootPath is different from currentDirectory", () => { - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const file: File = { - path: `${tscWatch.projectRoot}/file.ts`, - content: "const y = 10" - }; - const host = createServerHost([config, file, libFile], { useCaseSensitiveFileNames: true }); - const service = createProjectService(host, { useInferredProjectPerProjectRoot: true }); - service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, tscWatch.projectRoot); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - verifyDynamic(service, `${tscWatch.projectRoot}/${untitledFile}`); + it("opening and closing untitled files when projectRootPath is different from currentDirectory", () => { + const config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const file: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/file.ts`, + content: "const y = 10" + }; + const host = ts.projectSystem.createServerHost([config, file, ts.projectSystem.libFile], { useCaseSensitiveFileNames: true }); + const service = ts.projectSystem.createProjectService(host, { useInferredProjectPerProjectRoot: true }); + service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, ts.tscWatch.projectRoot); + ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]); + verifyDynamic(service, `${ts.tscWatch.projectRoot}/${untitledFile}`); - // Close untitled file - service.closeClientFile(untitledFile); + // Close untitled file + service.closeClientFile(untitledFile); - // Open file from configured project which should collect inferredProject - service.openClientFile(file.path); - checkNumberOfProjects(service, { configuredProjects: 1 }); - }); + // Open file from configured project which should collect inferredProject + service.openClientFile(file.path); + ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 }); + }); - it("when changing scriptKind of the untitled files", () => { - const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); - const service = createProjectService(host, { useInferredProjectPerProjectRoot: true }); - service.openClientFile(untitledFile, "const x = 10;", ScriptKind.TS, tscWatch.projectRoot); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - const program = service.inferredProjects[0].getCurrentProgram()!; - const sourceFile = program.getSourceFile(untitledFile)!; + it("when changing scriptKind of the untitled files", () => { + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile], { useCaseSensitiveFileNames: true }); + const service = ts.projectSystem.createProjectService(host, { useInferredProjectPerProjectRoot: true }); + service.openClientFile(untitledFile, "const x = 10;", ts.ScriptKind.TS, ts.tscWatch.projectRoot); + ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]); + const program = service.inferredProjects[0].getCurrentProgram()!; + const sourceFile = program.getSourceFile(untitledFile)!; - // Close untitled file - service.closeClientFile(untitledFile); + // Close untitled file + service.closeClientFile(untitledFile); - // Open untitled file with different mode - service.openClientFile(untitledFile, "const x = 10;", ScriptKind.TSX, tscWatch.projectRoot); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(service.inferredProjects[0], [untitledFile, libFile.path]); - const newProgram = service.inferredProjects[0].getCurrentProgram()!; - const newSourceFile = newProgram.getSourceFile(untitledFile)!; - assert.notStrictEqual(newProgram, program); - assert.notStrictEqual(newSourceFile, sourceFile); - }); + // Open untitled file with different mode + service.openClientFile(untitledFile, "const x = 10;", ts.ScriptKind.TSX, ts.tscWatch.projectRoot); + ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [untitledFile, ts.projectSystem.libFile.path]); + const newProgram = service.inferredProjects[0].getCurrentProgram()!; + const newSourceFile = newProgram.getSourceFile(untitledFile)!; + assert.notStrictEqual(newProgram, program); + assert.notStrictEqual(newSourceFile, sourceFile); }); +}); - describe("unittests:: tsserver:: dynamicFiles:: ", () => { - it("dynamic file without external project", () => { - const file: File = { - path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", - content: "var x = 10;" - }; - const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host); - projectService.setCompilerOptionsForInferredProjects({ - module: ModuleKind.CommonJS, - allowJs: true, - allowSyntheticDefaultImports: true, - allowNonTsExtensions: true - }); - projectService.openClientFile(file.path, "var x = 10;"); - - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - const project = projectService.inferredProjects[0]; - checkProjectRootFiles(project, [file.path]); - checkProjectActualFiles(project, [file.path, libFile.path]); - verifyDynamic(projectService, `/${file.path}`); - - assert.strictEqual(projectService.ensureDefaultProjectForFile(server.toNormalizedPath(file.path)), project); - const indexOfX = file.content.indexOf("x"); - assert.deepEqual(project.getLanguageService(/*ensureSynchronized*/ true).getQuickInfoAtPosition(file.path, indexOfX), { - kind: ScriptElementKind.variableElement, - kindModifiers: "", - textSpan: { start: indexOfX, length: 1 }, - displayParts: [ - { text: "var", kind: "keyword" }, - { text: " ", kind: "space" }, - { text: "x", kind: "localName" }, - { text: ":", kind: "punctuation" }, - { text: " ", kind: "space" }, - { text: "number", kind: "keyword" } - ], - documentation: [], - tags: undefined, - }); +describe("unittests:: tsserver:: dynamicFiles:: ", () => { + it("dynamic file without external project", () => { + const file: ts.projectSystem.File = { + path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", + content: "var x = 10;" + }; + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile], { useCaseSensitiveFileNames: true }); + const projectService = ts.projectSystem.createProjectService(host); + projectService.setCompilerOptionsForInferredProjects({ + module: ts.ModuleKind.CommonJS, + allowJs: true, + allowSyntheticDefaultImports: true, + allowNonTsExtensions: true }); + projectService.openClientFile(file.path, "var x = 10;"); - it("dynamic file with reference paths without external project", () => { - verifyPathRecognizedAsDynamic("^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js"); - }); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + ts.projectSystem.checkProjectRootFiles(project, [file.path]); + ts.projectSystem.checkProjectActualFiles(project, [file.path, ts.projectSystem.libFile.path]); + verifyDynamic(projectService, `/${file.path}`); - describe("dynamic file with projectRootPath", () => { - const file: File = { - path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", - content: "var x = 10;" - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const configProjectFile: File = { - path: `${tscWatch.projectRoot}/a.ts`, - content: "let y = 10;" - }; - it("with useInferredProjectPerProjectRoot", () => { - const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); - const session = createSession(host, { useInferredProjectPerProjectRoot: true }); - openFilesForSession([{ file: file.path, projectRootPath: tscWatch.projectRoot }], session); + assert.strictEqual(projectService.ensureDefaultProjectForFile(ts.server.toNormalizedPath(file.path)), project); + const indexOfX = file.content.indexOf("x"); + assert.deepEqual(project.getLanguageService(/*ensureSynchronized*/ true).getQuickInfoAtPosition(file.path, indexOfX), { + kind: ts.ScriptElementKind.variableElement, + kindModifiers: "", + textSpan: { start: indexOfX, length: 1 }, + displayParts: [ + { text: "var", kind: "keyword" }, + { text: " ", kind: "space" }, + { text: "x", kind: "localName" }, + { text: ":", kind: "punctuation" }, + { text: " ", kind: "space" }, + { text: "number", kind: "keyword" } + ], + documentation: [], + tags: undefined, + }); + }); - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]); - verifyDynamic(projectService, `${tscWatch.projectRoot}/${file.path}`); + it("dynamic file with reference paths without external project", () => { + verifyPathRecognizedAsDynamic("^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js"); + }); - session.executeCommandSeq({ - command: protocol.CommandTypes.GetOutliningSpans, - arguments: { - file: file.path - } - }); + describe("dynamic file with projectRootPath", () => { + const file: ts.projectSystem.File = { + path: "^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js", + content: "var x = 10;" + }; + const configFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const configProjectFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: "let y = 10;" + }; + it("with useInferredProjectPerProjectRoot", () => { + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); + const session = ts.projectSystem.createSession(host, { useInferredProjectPerProjectRoot: true }); + ts.projectSystem.openFilesForSession([{ file: file.path, projectRootPath: ts.tscWatch.projectRoot }], session); - // Without project root - const file2Path = file.path.replace("#1", "#2"); - projectService.openClientFile(file2Path, file.content); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file.path, libFile.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file2Path, libFile.path]); - }); + const projectService = session.getProjectService(); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file.path, ts.projectSystem.libFile.path]); + verifyDynamic(projectService, `${ts.tscWatch.projectRoot}/${file.path}`); - it("fails when useInferredProjectPerProjectRoot is false", () => { - const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host); - try { - projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, tscWatch.projectRoot); - } - catch (e) { - assert.strictEqual( - e.message.replace(/\r?\n/, "\n"), - `Debug Failure. False expression.\nVerbose Debug Information: {"fileName":"^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js","currentDirectory":"/user/username/projects/myproject","hostCurrentDirectory":"/","openKeys":[]}\nDynamic files must always be opened with service's current directory or service should support inferred project per projectRootPath.` - ); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.GetOutliningSpans, + arguments: { + file: file.path } - const file2Path = file.path.replace("#1", "#2"); - projectService.openClientFile(file2Path, file.content); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file2Path, libFile.path]); }); + + // Without project root + const file2Path = file.path.replace("#1", "#2"); + projectService.openClientFile(file2Path, file.content); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file.path, ts.projectSystem.libFile.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [file2Path, ts.projectSystem.libFile.path]); }); - describe("verify accepts known schemas as dynamic file", () => { - it("walkThroughSnippet", () => { - verifyPathRecognizedAsDynamic("walkThroughSnippet:/usr/share/code/resources/app/out/vs/workbench/contrib/welcome/walkThrough/browser/editor/^vs_code_editor_walkthrough.md#1.ts"); - }); + it("fails when useInferredProjectPerProjectRoot is false", () => { + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); + const projectService = ts.projectSystem.createProjectService(host); + try { + projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, ts.tscWatch.projectRoot); + } + catch (e) { + assert.strictEqual( + e.message.replace(/\r?\n/, "\n"), + `Debug Failure. False expression.\nVerbose Debug Information: {"fileName":"^walkThroughSnippet:/Users/UserName/projects/someProject/out/someFile#1.js","currentDirectory":"/user/username/projects/myproject","hostCurrentDirectory":"/","openKeys":[]}\nDynamic files must always be opened with service's current directory or service should support inferred project per projectRootPath.` + ); + } + const file2Path = file.path.replace("#1", "#2"); + projectService.openClientFile(file2Path, file.content); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file2Path, ts.projectSystem.libFile.path]); + }); + }); - it("untitled", () => { - verifyPathRecognizedAsDynamic("untitled:/Users/matb/projects/san/^newFile.ts"); - }); + describe("verify accepts known schemas as dynamic file", () => { + it("walkThroughSnippet", () => { + verifyPathRecognizedAsDynamic("walkThroughSnippet:/usr/share/code/resources/app/out/vs/workbench/contrib/welcome/walkThrough/browser/editor/^vs_code_editor_walkthrough.md#1.ts"); + }); + + it("untitled", () => { + verifyPathRecognizedAsDynamic("untitled:/Users/matb/projects/san/^newFile.ts"); }); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts index 1807f104ee2d6..57a295d8cf80a 100644 --- a/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts +++ b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts @@ -1,75 +1,75 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large file", () => { +import * as ts from "../../../_namespaces/ts"; - function getLargeFile(useLargeTsFile: boolean) { - return `src/large.${useLargeTsFile ? "ts" : "js"}`; - } +describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large file", () => { - function createSessionWithEventHandler(files: File[], useLargeTsFile: boolean) { - const largeFile: File = { - path: `${tscWatch.projectRoot}/${getLargeFile(useLargeTsFile)}`, - content: "export var x = 10;", - fileSize: server.maxFileSize + 1 - }; - files.push(largeFile); - const host = createServerHost(files); - const { session, events: largeFileReferencedEvents } = createSessionWithEventTracking(host, server.LargeFileReferencedEvent); + function getLargeFile(useLargeTsFile: boolean) { + return `src/large.${useLargeTsFile ? "ts" : "js"}`; + } - return { session, verifyLargeFile }; + function createSessionWithEventHandler(files: ts.projectSystem.File[], useLargeTsFile: boolean) { + const largeFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/${getLargeFile(useLargeTsFile)}`, + content: "export var x = 10;", + fileSize: ts.server.maxFileSize + 1 + }; + files.push(largeFile); + const host = ts.projectSystem.createServerHost(files); + const { session, events: largeFileReferencedEvents } = ts.projectSystem.createSessionWithEventTracking(host, ts.server.LargeFileReferencedEvent); - function verifyLargeFile(project: server.Project) { - checkProjectActualFiles(project, files.map(f => f.path)); + return { session, verifyLargeFile }; - // large file for non ts file should be empty and for ts file should have content - const service = session.getProjectService(); - const info = service.getScriptInfo(largeFile.path)!; - assert.equal(info.cacheSourceFile!.sourceFile.text, useLargeTsFile ? largeFile.content : ""); + function verifyLargeFile(project: ts.server.Project) { + ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path)); - assert.deepEqual(largeFileReferencedEvents, useLargeTsFile ? emptyArray : [{ - eventName: server.LargeFileReferencedEvent, - data: { file: largeFile.path, fileSize: largeFile.fileSize, maxFileSize: server.maxFileSize } - }]); - } - } + // large file for non ts file should be empty and for ts file should have content + const service = session.getProjectService(); + const info = service.getScriptInfo(largeFile.path)!; + assert.equal(info.cacheSourceFile!.sourceFile.text, useLargeTsFile ? largeFile.content : ""); - function verifyLargeFile(useLargeTsFile: boolean) { - it("when large file is included by tsconfig", () => { - const file: File = { - path: `${tscWatch.projectRoot}/src/file.ts`, - content: "export var y = 10;" - }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ files: ["src/file.ts", getLargeFile(useLargeTsFile)], compilerOptions: { target: 1, allowJs: true } }) - }; - const files = [file, libFile, tsconfig]; - const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); - const service = session.getProjectService(); - openFilesForSession([file], session); - checkNumberOfProjects(service, { configuredProjects: 1 }); - verifyLargeFile(service.configuredProjects.get(tsconfig.path)!); - }); - - it("when large file is included by module resolution", () => { - const file: File = { - path: `${tscWatch.projectRoot}/src/file.ts`, - content: `export var y = 10;import {x} from "./large"` - }; - const files = [file, libFile]; - const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); - const service = session.getProjectService(); - openFilesForSession([file], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - verifyLargeFile(service.inferredProjects[0]); - }); + assert.deepEqual(largeFileReferencedEvents, useLargeTsFile ? ts.emptyArray : [{ + eventName: ts.server.LargeFileReferencedEvent, + data: { file: largeFile.path, fileSize: largeFile.fileSize, maxFileSize: ts.server.maxFileSize } + }]); } + } - describe("large file is ts file", () => { - verifyLargeFile(/*useLargeTsFile*/ true); + function verifyLargeFile(useLargeTsFile: boolean) { + it("when large file is included by tsconfig", () => { + const file: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/file.ts`, + content: "export var y = 10;" + }; + const tsconfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ files: ["src/file.ts", getLargeFile(useLargeTsFile)], compilerOptions: { target: 1, allowJs: true } }) + }; + const files = [file, ts.projectSystem.libFile, tsconfig]; + const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); + const service = session.getProjectService(); + ts.projectSystem.openFilesForSession([file], session); + ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 }); + verifyLargeFile(service.configuredProjects.get(tsconfig.path)!); }); - describe("large file is js file", () => { - verifyLargeFile(/*useLargeTsFile*/ false); + it("when large file is included by module resolution", () => { + const file: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/file.ts`, + content: `export var y = 10;import {x} from "./large"` + }; + const files = [file, ts.projectSystem.libFile]; + const { session, verifyLargeFile } = createSessionWithEventHandler(files, useLargeTsFile); + const service = session.getProjectService(); + ts.projectSystem.openFilesForSession([file], session); + ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 }); + verifyLargeFile(service.inferredProjects[0]); }); + } + + describe("large file is ts file", () => { + verifyLargeFile(/*useLargeTsFile*/ true); + }); + + describe("large file is js file", () => { + verifyLargeFile(/*useLargeTsFile*/ false); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts b/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts index ab54f63075221..7fcb0b56c42bd 100644 --- a/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts +++ b/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts @@ -1,79 +1,79 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", () => { - it("language service disabled events are triggered", () => { - const f1 = { - path: "/a/app.js", - content: "let x = 1;" - }; - const f2 = { - path: "/a/largefile.js", - content: "", - }; - const config = { - path: "/a/jsconfig.json", - content: "{}" - }; - const configWithExclude = { - path: config.path, - content: JSON.stringify({ exclude: ["largefile.js"] }) - }; - const host = createServerHost([f1, f2, config]); - const originalGetFileSize = host.getFileSize; - host.getFileSize = (filePath: string) => - filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); +import * as ts from "../../../_namespaces/ts"; - const { session, events } = createSessionWithEventTracking(host, server.ProjectLanguageServiceStateEvent); - session.executeCommand({ - seq: 0, - type: "request", - command: "open", - arguments: { file: f1.path } - } as protocol.OpenRequest); - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = configuredProjectAt(projectService, 0); - assert.isFalse(project.languageServiceEnabled, "Language service enabled"); - assert.equal(events.length, 1, "should receive event"); - assert.equal(events[0].data.project, project, "project name"); - assert.equal(events[0].data.project.getProjectName(), config.path, "config path"); - assert.isFalse(events[0].data.languageServiceEnabled, "Language service state"); +describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", () => { + it("language service disabled events are triggered", () => { + const f1 = { + path: "/a/app.js", + content: "let x = 1;" + }; + const f2 = { + path: "/a/largefile.js", + content: "", + }; + const config = { + path: "/a/jsconfig.json", + content: "{}" + }; + const configWithExclude = { + path: config.path, + content: JSON.stringify({ exclude: ["largefile.js"] }) + }; + const host = ts.projectSystem.createServerHost([f1, f2, config]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); - host.writeFile(configWithExclude.path, configWithExclude.content); - host.checkTimeoutQueueLengthAndRun(2); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.isTrue(project.languageServiceEnabled, "Language service enabled"); - assert.equal(events.length, 2, "should receive event"); - assert.equal(events[1].data.project, project, "project"); - assert.equal(events[1].data.project.getProjectName(), config.path, "config path"); - assert.isTrue(events[1].data.languageServiceEnabled, "Language service state"); - }); + const { session, events } = ts.projectSystem.createSessionWithEventTracking(host, ts.server.ProjectLanguageServiceStateEvent); + session.executeCommand({ + seq: 0, + type: "request", + command: "open", + arguments: { file: f1.path } + } as ts.projectSystem.protocol.OpenRequest); + const projectService = session.getProjectService(); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + assert.isFalse(project.languageServiceEnabled, "Language service enabled"); + assert.equal(events.length, 1, "should receive event"); + assert.equal(events[0].data.project, project, "project name"); + assert.equal(events[0].data.project.getProjectName(), config.path, "config path"); + assert.isFalse(events[0].data.languageServiceEnabled, "Language service state"); - it("Large file size is determined correctly", () => { - const f1: File = { - path: "/a/app.js", - content: "let x = 1;" - }; - const f2: File = { - path: "/a/largefile.js", - content: "", - fileSize: server.maxProgramSizeForNonTsFiles + 1 - }; - const f3: File = { - path: "/a/extremlylarge.d.ts", - content: "", - fileSize: server.maxProgramSizeForNonTsFiles + 100 - }; - const config = { - path: "/a/jsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, f2, f3, libFile, config]); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(f1.path); - const project = service.configuredProjects.get(config.path)!; - service.logger.info(`languageServiceEnabled: ${project.languageServiceEnabled}`); - service.logger.info(`lastFileExceededProgramSize: ${project.lastFileExceededProgramSize}`); - baselineTsserverLogs("projectLanguageServiceStateEvent", "large file size is determined correctly", service); - }); + host.writeFile(configWithExclude.path, configWithExclude.content); + host.checkTimeoutQueueLengthAndRun(2); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.isTrue(project.languageServiceEnabled, "Language service enabled"); + assert.equal(events.length, 2, "should receive event"); + assert.equal(events[1].data.project, project, "project"); + assert.equal(events[1].data.project.getProjectName(), config.path, "config path"); + assert.isTrue(events[1].data.languageServiceEnabled, "Language service state"); }); -} + + it("Large file size is determined correctly", () => { + const f1: ts.projectSystem.File = { + path: "/a/app.js", + content: "let x = 1;" + }; + const f2: ts.projectSystem.File = { + path: "/a/largefile.js", + content: "", + fileSize: ts.server.maxProgramSizeForNonTsFiles + 1 + }; + const f3: ts.projectSystem.File = { + path: "/a/extremlylarge.d.ts", + content: "", + fileSize: ts.server.maxProgramSizeForNonTsFiles + 100 + }; + const config = { + path: "/a/jsconfig.json", + content: "{}" + }; + const host = ts.projectSystem.createServerHost([f1, f2, f3, ts.projectSystem.libFile, config]); + const service = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + service.openClientFile(f1.path); + const project = service.configuredProjects.get(config.path)!; + service.logger.info(`languageServiceEnabled: ${project.languageServiceEnabled}`); + service.logger.info(`lastFileExceededProgramSize: ${project.lastFileExceededProgramSize}`); + ts.projectSystem.baselineTsserverLogs("projectLanguageServiceStateEvent", "large file size is determined correctly", service); + }); +}); diff --git a/src/testRunner/unittests/tsserver/events/projectLoading.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts index 53a233c8d2ac7..ee8d02415725c 100644 --- a/src/testRunner/unittests/tsserver/events/projectLoading.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -1,232 +1,232 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoadingFinish events", () => { - const aTs: File = { - path: `${tscWatch.projects}/a/a.ts`, - content: "export class A { }" - }; - const configA: File = { - path: `${tscWatch.projects}/a/tsconfig.json`, - content: "{}" - }; - const bTsPath = `${tscWatch.projects}/b/b.ts`; - const configBPath = `${tscWatch.projects}/b/tsconfig.json`; - const files = [libFile, aTs, configA]; - - function verifyProjectLoadingStartAndFinish(createSession: (host: TestServerHost) => { - session: TestSession; - getNumberOfEvents: () => number; - clearEvents: () => void; - verifyProjectLoadEvents: (expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) => void; - }) { - function createSessionToVerifyEvent(files: readonly File[]) { - const host = createServerHost(files); - const originalReadFile = host.readFile; - const { session, getNumberOfEvents, clearEvents, verifyProjectLoadEvents } = createSession(host); - host.readFile = file => { - if (file === configA.path || file === configBPath) { - assert.equal(getNumberOfEvents(), 1, "Event for loading is sent before reading config file"); - } - return originalReadFile.call(host, file); - }; - const service = session.getProjectService(); - return { host, session, verifyEvent, verifyEventWithOpenTs, service, getNumberOfEvents }; - - function verifyEvent(project: server.Project, reason: string) { - verifyProjectLoadEvents([ - { eventName: server.ProjectLoadingStartEvent, data: { project, reason } }, - { eventName: server.ProjectLoadingFinishEvent, data: { project } } - ]); - clearEvents(); +import * as ts from "../../../_namespaces/ts"; + +describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoadingFinish events", () => { + const aTs: ts.projectSystem.File = { + path: `${ts.tscWatch.projects}/a/a.ts`, + content: "export class A { }" + }; + const configA: ts.projectSystem.File = { + path: `${ts.tscWatch.projects}/a/tsconfig.json`, + content: "{}" + }; + const bTsPath = `${ts.tscWatch.projects}/b/b.ts`; + const configBPath = `${ts.tscWatch.projects}/b/tsconfig.json`; + const files = [ts.projectSystem.libFile, aTs, configA]; + + function verifyProjectLoadingStartAndFinish(createSession: (host: ts.projectSystem.TestServerHost) => { + session: ts.projectSystem.TestSession; + getNumberOfEvents: () => number; + clearEvents: () => void; + verifyProjectLoadEvents: (expected: [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]) => void; + }) { + function createSessionToVerifyEvent(files: readonly ts.projectSystem.File[]) { + const host = ts.projectSystem.createServerHost(files); + const originalReadFile = host.readFile; + const { session, getNumberOfEvents, clearEvents, verifyProjectLoadEvents } = createSession(host); + host.readFile = file => { + if (file === configA.path || file === configBPath) { + assert.equal(getNumberOfEvents(), 1, "Event for loading is sent before reading config file"); } + return originalReadFile.call(host, file); + }; + const service = session.getProjectService(); + return { host, session, verifyEvent, verifyEventWithOpenTs, service, getNumberOfEvents }; + + function verifyEvent(project: ts.server.Project, reason: string) { + verifyProjectLoadEvents([ + { eventName: ts.server.ProjectLoadingStartEvent, data: { project, reason } }, + { eventName: ts.server.ProjectLoadingFinishEvent, data: { project } } + ]); + clearEvents(); + } - function verifyEventWithOpenTs(file: File, configPath: string, configuredProjects: number) { - openFilesForSession([file], session); - checkNumberOfProjects(service, { configuredProjects }); - const project = service.configuredProjects.get(configPath)!; - assert.isDefined(project); - verifyEvent(project, `Creating possible configured project for ${file.path} to open`); - } + function verifyEventWithOpenTs(file: ts.projectSystem.File, configPath: string, configuredProjects: number) { + ts.projectSystem.openFilesForSession([file], session); + ts.projectSystem.checkNumberOfProjects(service, { configuredProjects }); + const project = service.configuredProjects.get(configPath)!; + assert.isDefined(project); + verifyEvent(project, `Creating possible configured project for ${file.path} to open`); } + } - it("when project is created by open file", () => { - const bTs: File = { - path: bTsPath, - content: "export class B {}" - }; - const configB: File = { - path: configBPath, - content: "{}" - }; - const { verifyEventWithOpenTs } = createSessionToVerifyEvent(files.concat(bTs, configB)); - verifyEventWithOpenTs(aTs, configA.path, 1); - verifyEventWithOpenTs(bTs, configB.path, 2); - }); + it("when project is created by open file", () => { + const bTs: ts.projectSystem.File = { + path: bTsPath, + content: "export class B {}" + }; + const configB: ts.projectSystem.File = { + path: configBPath, + content: "{}" + }; + const { verifyEventWithOpenTs } = createSessionToVerifyEvent(files.concat(bTs, configB)); + verifyEventWithOpenTs(aTs, configA.path, 1); + verifyEventWithOpenTs(bTs, configB.path, 2); + }); - it("when change is detected in the config file", () => { - const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files); - verifyEventWithOpenTs(aTs, configA.path, 1); + it("when change is detected in the config file", () => { + const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files); + verifyEventWithOpenTs(aTs, configA.path, 1); - host.writeFile(configA.path, configA.content); - host.checkTimeoutQueueLengthAndRun(2); - const project = service.configuredProjects.get(configA.path)!; - verifyEvent(project, `Change in config file detected`); + host.writeFile(configA.path, configA.content); + host.checkTimeoutQueueLengthAndRun(2); + const project = service.configuredProjects.get(configA.path)!; + verifyEvent(project, `Change in config file detected`); + }); + + it("when change is detected in an extended config file", () => { + const bTs: ts.projectSystem.File = { + path: bTsPath, + content: "export class B {}" + }; + const configB: ts.projectSystem.File = { + path: configBPath, + content: JSON.stringify({ + extends: "../a/tsconfig.json", + }) + }; + const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files.concat(bTs, configB)); + verifyEventWithOpenTs(bTs, configB.path, 1); + + host.writeFile(configA.path, configA.content); + host.checkTimeoutQueueLengthAndRun(2); + const project = service.configuredProjects.get(configB.path)!; + verifyEvent(project, `Change in extended config file ${configA.path} detected`); + }); + + describe("when opening original location project", () => { + it("with project references", () => { + verify(); + }); + + it("when disableSourceOfProjectReferenceRedirect is true", () => { + verify(/*disableSourceOfProjectReferenceRedirect*/ true); }); - it("when change is detected in an extended config file", () => { - const bTs: File = { + function verify(disableSourceOfProjectReferenceRedirect?: true) { + const aDTs: ts.projectSystem.File = { + path: `${ts.tscWatch.projects}/a/a.d.ts`, + content: `export declare class A { +} +//# sourceMappingURL=a.d.ts.map +` + }; + const aDTsMap: ts.projectSystem.File = { + path: `${ts.tscWatch.projects}/a/a.d.ts.map`, + content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}` + }; + const bTs: ts.projectSystem.File = { path: bTsPath, - content: "export class B {}" + content: `import {A} from "../a/a"; new A();` }; - const configB: File = { + const configB: ts.projectSystem.File = { path: configBPath, content: JSON.stringify({ - extends: "../a/tsconfig.json", + ...(disableSourceOfProjectReferenceRedirect && { + compilerOptions: { + disableSourceOfProjectReferenceRedirect + } + }), + references: [{ path: "../a" }] }) }; - const { host, verifyEvent, verifyEventWithOpenTs, service } = createSessionToVerifyEvent(files.concat(bTs, configB)); - verifyEventWithOpenTs(bTs, configB.path, 1); - - host.writeFile(configA.path, configA.content); - host.checkTimeoutQueueLengthAndRun(2); - const project = service.configuredProjects.get(configB.path)!; - verifyEvent(project, `Change in extended config file ${configA.path} detected`); - }); - describe("when opening original location project", () => { - it("with project references", () => { - verify(); - }); + const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB)); + verifyEventWithOpenTs(bTs, configB.path, 1); - it("when disableSourceOfProjectReferenceRedirect is true", () => { - verify(/*disableSourceOfProjectReferenceRedirect*/ true); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: { + file: bTs.path, + ...ts.projectSystem.protocolLocationFromSubstring(bTs.content, "A()") + } }); - function verify(disableSourceOfProjectReferenceRedirect?: true) { - const aDTs: File = { - path: `${tscWatch.projects}/a/a.d.ts`, - content: `export declare class A { -} -//# sourceMappingURL=a.d.ts.map -` - }; - const aDTsMap: File = { - path: `${tscWatch.projects}/a/a.d.ts.map`, - content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}` - }; - const bTs: File = { - path: bTsPath, - content: `import {A} from "../a/a"; new A();` - }; - const configB: File = { - path: configBPath, - content: JSON.stringify({ - ...(disableSourceOfProjectReferenceRedirect && { - compilerOptions: { - disableSourceOfProjectReferenceRedirect - } - }), - references: [{ path: "../a" }] - }) - }; - - const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB)); - verifyEventWithOpenTs(bTs, configB.path, 1); - - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: { - file: bTs.path, - ...protocolLocationFromSubstring(bTs.content, "A()") - } - }); - - checkNumberOfProjects(service, { configuredProjects: 2 }); - const project = service.configuredProjects.get(configA.path)!; - assert.isDefined(project); - verifyEvent( - project, - disableSourceOfProjectReferenceRedirect ? - `Creating project for original file: ${aTs.path} for location: ${aDTs.path}` : - `Creating project for original file: ${aTs.path}` - ); - } - }); + ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 2 }); + const project = service.configuredProjects.get(configA.path)!; + assert.isDefined(project); + verifyEvent( + project, + disableSourceOfProjectReferenceRedirect ? + `Creating project for original file: ${aTs.path} for location: ${aDTs.path}` : + `Creating project for original file: ${aTs.path}` + ); + } + }); - describe("with external projects and config files ", () => { - const projectFileName = `${tscWatch.projects}/a/project.csproj`; - - function createSession(lazyConfiguredProjectsFromExternalProject: boolean) { - const { session, service, verifyEvent: verifyEventWorker, getNumberOfEvents } = createSessionToVerifyEvent(files); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([aTs.path, configA.path]), - options: {} - } as protocol.ExternalProject); - checkNumberOfProjects(service, { configuredProjects: 1 }); - return { session, service, verifyEvent, getNumberOfEvents }; - - function verifyEvent() { - const projectA = service.configuredProjects.get(configA.path)!; - assert.isDefined(projectA); - verifyEventWorker(projectA, `Creating configured project in external project: ${projectFileName}`); - } + describe("with external projects and config files ", () => { + const projectFileName = `${ts.tscWatch.projects}/a/project.csproj`; + + function createSession(lazyConfiguredProjectsFromExternalProject: boolean) { + const { session, service, verifyEvent: verifyEventWorker, getNumberOfEvents } = createSessionToVerifyEvent(files); + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + service.openExternalProject({ + projectFileName, + rootFiles: ts.projectSystem.toExternalFiles([aTs.path, configA.path]), + options: {} + } as ts.projectSystem.protocol.ExternalProject); + ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 }); + return { session, service, verifyEvent, getNumberOfEvents }; + + function verifyEvent() { + const projectA = service.configuredProjects.get(configA.path)!; + assert.isDefined(projectA); + verifyEventWorker(projectA, `Creating configured project in external project: ${projectFileName}`); } + } - it("when lazyConfiguredProjectsFromExternalProject is false", () => { - const { verifyEvent } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ false); - verifyEvent(); - }); - - it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => { - const { verifyEvent, getNumberOfEvents, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); - assert.equal(getNumberOfEvents(), 0); - - openFilesForSession([aTs], session); - verifyEvent(); - }); + it("when lazyConfiguredProjectsFromExternalProject is false", () => { + const { verifyEvent } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ false); + verifyEvent(); + }); - it("when lazyConfiguredProjectsFromExternalProject is disabled", () => { - const { verifyEvent, getNumberOfEvents, service } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); - assert.equal(getNumberOfEvents(), 0); + it("when lazyConfiguredProjectsFromExternalProject is true and file is opened", () => { + const { verifyEvent, getNumberOfEvents, session } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); + assert.equal(getNumberOfEvents(), 0); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); - verifyEvent(); - }); + ts.projectSystem.openFilesForSession([aTs], session); + verifyEvent(); }); - } - describe("when using event handler", () => { - verifyProjectLoadingStartAndFinish(host => { - const { session, events } = createSessionWithEventTracking(host, [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]); - return { - session, - getNumberOfEvents: () => events.length, - clearEvents: () => events.length = 0, - verifyProjectLoadEvents: expected => assert.deepEqual(events, expected) - }; + it("when lazyConfiguredProjectsFromExternalProject is disabled", () => { + const { verifyEvent, getNumberOfEvents, service } = createSession(/*lazyConfiguredProjectsFromExternalProject*/ true); + assert.equal(getNumberOfEvents(), 0); + + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); + verifyEvent(); }); }); + } + + describe("when using event handler", () => { + verifyProjectLoadingStartAndFinish(host => { + const { session, events } = ts.projectSystem.createSessionWithEventTracking(host, [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]); + return { + session, + getNumberOfEvents: () => events.length, + clearEvents: () => events.length = 0, + verifyProjectLoadEvents: expected => assert.deepEqual(events, expected) + }; + }); + }); - describe("when using default event handler", () => { - verifyProjectLoadingStartAndFinish(host => { - const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler(host, [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]); - return { - session, - getNumberOfEvents: () => getEvents().length, - clearEvents, - verifyProjectLoadEvents - }; - - function verifyProjectLoadEvents(expected: [server.ProjectLoadingStartEvent, server.ProjectLoadingFinishEvent]) { - const actual = getEvents().map(e => ({ eventName: e.event, data: e.body })); - const mappedExpected = expected.map(e => { - const { project, ...rest } = e.data; - return { eventName: e.eventName, data: { projectName: project.getProjectName(), ...rest } }; - }); - assert.deepEqual(actual, mappedExpected); - } - }); + describe("when using default event handler", () => { + verifyProjectLoadingStartAndFinish(host => { + const { session, getEvents, clearEvents } = ts.projectSystem.createSessionWithDefaultEventHandler(host, [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]); + return { + session, + getNumberOfEvents: () => getEvents().length, + clearEvents, + verifyProjectLoadEvents + }; + + function verifyProjectLoadEvents(expected: [ts.server.ProjectLoadingStartEvent, ts.server.ProjectLoadingFinishEvent]) { + const actual = getEvents().map(e => ({ eventName: e.event, data: e.body })); + const mappedExpected = expected.map(e => { + const { project, ...rest } = e.data; + return { eventName: e.eventName, data: { projectName: project.getProjectName(), ...rest } }; + }); + assert.deepEqual(actual, mappedExpected); + } }); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts index 951c7215872d2..47d51d169c117 100644 --- a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts +++ b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts @@ -1,556 +1,556 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => { - function verifyFiles(caption: string, actual: readonly string[], expected: readonly string[]) { - assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); - const seen = new Map(); - forEach(actual, f => { - assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`); - seen.set(f, true); - assert.isTrue(contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`); - }); - } - - function createVerifyInitialOpen(session: TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: server.ProjectsUpdatedInBackgroundEvent[]) => void) { - return (file: File) => { - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { - file: file.path - } - } as protocol.OpenRequest); - verifyProjectsUpdatedInBackgroundEventHandler([]); +import * as ts from "../../../_namespaces/ts"; + +describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => { + function verifyFiles(caption: string, actual: readonly string[], expected: readonly string[]) { + assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); + const seen = new ts.Map(); + ts.forEach(actual, f => { + assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`); + seen.set(f, true); + assert.isTrue(ts.contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`); + }); + } + + function createVerifyInitialOpen(session: ts.projectSystem.TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: ts.server.ProjectsUpdatedInBackgroundEvent[]) => void) { + return (file: ts.projectSystem.File) => { + session.executeCommandSeq({ + command: ts.server.CommandNames.Open, + arguments: { + file: file.path + } + } as ts.projectSystem.protocol.OpenRequest); + verifyProjectsUpdatedInBackgroundEventHandler([]); + }; + } + + interface ProjectsUpdatedInBackgroundEventVerifier { + session: ts.projectSystem.TestSession; + verifyProjectsUpdatedInBackgroundEventHandler(events: ts.server.ProjectsUpdatedInBackgroundEvent[]): void; + verifyInitialOpen(file: ts.projectSystem.File): void; + } + + function verifyProjectsUpdatedInBackgroundEvent(scenario: string, createSession: (host: ts.projectSystem.TestServerHost, logger?: ts.projectSystem.Logger) => ProjectsUpdatedInBackgroundEventVerifier) { + it("when adding new file", () => { + const commonFile1: ts.projectSystem.File = { + path: "/a/b/file1.ts", + content: "export var x = 10;" }; - } - - interface ProjectsUpdatedInBackgroundEventVerifier { - session: TestSession; - verifyProjectsUpdatedInBackgroundEventHandler(events: server.ProjectsUpdatedInBackgroundEvent[]): void; - verifyInitialOpen(file: File): void; - } + const commonFile2: ts.projectSystem.File = { + path: "/a/b/file2.ts", + content: "export var y = 10;" + }; + const commonFile3: ts.projectSystem.File = { + path: "/a/b/file3.ts", + content: "export var z = 10;" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const openFiles = [commonFile1.path]; + const host = ts.projectSystem.createServerHost([commonFile1, ts.projectSystem.libFile, configFile]); + const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); + verifyInitialOpen(commonFile1); + + host.writeFile(commonFile2.path, commonFile2.content); + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: ts.server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + + host.writeFile(commonFile3.path, commonFile3.content); + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: ts.server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + }); - function verifyProjectsUpdatedInBackgroundEvent(scenario: string, createSession: (host: TestServerHost, logger?: Logger) => ProjectsUpdatedInBackgroundEventVerifier) { - it("when adding new file", () => { - const commonFile1: File = { - path: "/a/b/file1.ts", - content: "export var x = 10;" - }; - const commonFile2: File = { - path: "/a/b/file2.ts", - content: "export var y = 10;" + describe("with --out or --outFile setting", () => { + function verifyEventWithOutSettings(compilerOptions: ts.CompilerOptions = {}) { + const config: ts.projectSystem.File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions + }) }; - const commonFile3: File = { - path: "/a/b/file3.ts", - content: "export var z = 10;" + + const f1: ts.projectSystem.File = { + path: "/a/a.ts", + content: "export let x = 1" }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{}` + const f2: ts.projectSystem.File = { + path: "/a/b.ts", + content: "export let y = 1" }; - const openFiles = [commonFile1.path]; - const host = createServerHost([commonFile1, libFile, configFile]); - const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); - verifyInitialOpen(commonFile1); - host.writeFile(commonFile2.path, commonFile2.content); + const openFiles = [f1.path]; + const files = [f1, config, ts.projectSystem.libFile]; + const host = ts.projectSystem.createServerHost(files); + const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); + verifyInitialOpen(f1); + + host.writeFile(f2.path, f2.content); host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, + eventName: ts.server.ProjectsUpdatedInBackgroundEvent, data: { openFiles } }]); - host.writeFile(commonFile3.path, commonFile3.content); + host.writeFile(f2.path, "export let x = 11"); host.runQueuedTimeoutCallbacks(); verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, + eventName: ts.server.ProjectsUpdatedInBackgroundEvent, data: { openFiles } }]); - }); - - describe("with --out or --outFile setting", () => { - function verifyEventWithOutSettings(compilerOptions: CompilerOptions = {}) { - const config: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions - }) - }; - - const f1: File = { - path: "/a/a.ts", - content: "export let x = 1" - }; - const f2: File = { - path: "/a/b.ts", - content: "export let y = 1" - }; - - const openFiles = [f1.path]; - const files = [f1, config, libFile]; - const host = createServerHost(files); - const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); - verifyInitialOpen(f1); - - host.writeFile(f2.path, f2.content); - host.runQueuedTimeoutCallbacks(); - - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - - host.writeFile(f2.path, "export let x = 11"); - host.runQueuedTimeoutCallbacks(); - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - } + } - it("when both options are not set", () => { - verifyEventWithOutSettings(); - }); + it("when both options are not set", () => { + verifyEventWithOutSettings(); + }); - it("when --out is set", () => { - const outJs = "/a/out.js"; - verifyEventWithOutSettings({ out: outJs }); - }); + it("when --out is set", () => { + const outJs = "/a/out.js"; + verifyEventWithOutSettings({ out: outJs }); + }); - it("when --outFile is set", () => { - const outJs = "/a/out.js"; - verifyEventWithOutSettings({ outFile: outJs }); - }); + it("when --outFile is set", () => { + const outJs = "/a/out.js"; + verifyEventWithOutSettings({ outFile: outJs }); }); + }); - describe("with modules and configured project", () => { - const file1Consumer1Path = "/a/b/file1Consumer1.ts"; - const moduleFile1Path = "/a/b/moduleFile1.ts"; - const configFilePath = "/a/b/tsconfig.json"; - interface InitialStateParams { - /** custom config file options */ - configObj?: any; - /** Additional files and folders to add */ - getAdditionalFileOrFolder?(): File[]; - /** initial list of files to reload in fs and first file in this list being the file to open */ - firstReloadFileList?: string[]; - } - function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { - const moduleFile1: File = { - path: moduleFile1Path, - content: "export function Foo() { };", - }; + describe("with modules and configured project", () => { + const file1Consumer1Path = "/a/b/file1Consumer1.ts"; + const moduleFile1Path = "/a/b/moduleFile1.ts"; + const configFilePath = "/a/b/tsconfig.json"; + interface InitialStateParams { + /** custom config file options */ + configObj?: any; + /** Additional files and folders to add */ + getAdditionalFileOrFolder?(): ts.projectSystem.File[]; + /** initial list of files to reload in fs and first file in this list being the file to open */ + firstReloadFileList?: string[]; + } + function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { + const moduleFile1: ts.projectSystem.File = { + path: moduleFile1Path, + content: "export function Foo() { };", + }; - const file1Consumer1: File = { - path: file1Consumer1Path, - content: `import {Foo} from "./moduleFile1"; export var y = 10;`, - }; + const file1Consumer1: ts.projectSystem.File = { + path: file1Consumer1Path, + content: `import {Foo} from "./moduleFile1"; export var y = 10;`, + }; - const file1Consumer2: File = { - path: "/a/b/file1Consumer2.ts", - content: `import {Foo} from "./moduleFile1"; let z = 10;`, - }; + const file1Consumer2: ts.projectSystem.File = { + path: "/a/b/file1Consumer2.ts", + content: `import {Foo} from "./moduleFile1"; let z = 10;`, + }; - const moduleFile2: File = { - path: "/a/b/moduleFile2.ts", - content: `export var Foo4 = 10;`, - }; + const moduleFile2: ts.projectSystem.File = { + path: "/a/b/moduleFile2.ts", + content: `export var Foo4 = 10;`, + }; - const globalFile3: File = { - path: "/a/b/globalFile3.ts", - content: `interface GlobalFoo { age: number }` - }; + const globalFile3: ts.projectSystem.File = { + path: "/a/b/globalFile3.ts", + content: `interface GlobalFoo { age: number }` + }; - const additionalFiles = getAdditionalFileOrFolder ? getAdditionalFileOrFolder() : []; - const configFile = { - path: configFilePath, - content: JSON.stringify(configObj || { compilerOptions: {} }) - }; + const additionalFiles = getAdditionalFileOrFolder ? getAdditionalFileOrFolder() : []; + const configFile = { + path: configFilePath, + content: JSON.stringify(configObj || { compilerOptions: {} }) + }; - const files: File[] = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, libFile, configFile]; + const files: ts.projectSystem.File[] = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, ts.projectSystem.libFile, configFile]; - const filesToReload = firstReloadFileList && getFiles(firstReloadFileList) || files; - const host = createServerHost([filesToReload[0], configFile]); + const filesToReload = firstReloadFileList && getFiles(firstReloadFileList) || files; + const host = ts.projectSystem.createServerHost([filesToReload[0], configFile]); - // Initial project creation - const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); - const openFiles = [filesToReload[0].path]; - verifyInitialOpen(filesToReload[0]); + // Initial project creation + const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); + const openFiles = [filesToReload[0].path]; + verifyInitialOpen(filesToReload[0]); - // Since this is first event, it will have all the files - filesToReload.forEach(f => host.ensureFileOrFolder(f)); - if (!firstReloadFileList) host.runQueuedTimeoutCallbacks(); // Invalidated module resolutions to schedule project update - verifyProjectsUpdatedInBackgroundEvent(); + // Since this is first event, it will have all the files + filesToReload.forEach(f => host.ensureFileOrFolder(f)); + if (!firstReloadFileList) host.runQueuedTimeoutCallbacks(); // Invalidated module resolutions to schedule project update + verifyProjectsUpdatedInBackgroundEvent(); - return { - host, - moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, - updateContentOfOpenFile, - verifyNoProjectsUpdatedInBackgroundEvent, - verifyProjectsUpdatedInBackgroundEvent - }; + return { + host, + moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, + updateContentOfOpenFile, + verifyNoProjectsUpdatedInBackgroundEvent, + verifyProjectsUpdatedInBackgroundEvent + }; - function getFiles(filelist: string[]) { - return map(filelist, getFile); - } + function getFiles(filelist: string[]) { + return ts.map(filelist, getFile); + } - function getFile(fileName: string) { - return find(files, file => file.path === fileName)!; - } + function getFile(fileName: string) { + return ts.find(files, file => file.path === fileName)!; + } - function verifyNoProjectsUpdatedInBackgroundEvent() { - host.runQueuedTimeoutCallbacks(); - verifyProjectsUpdatedInBackgroundEventHandler([]); - } + function verifyNoProjectsUpdatedInBackgroundEvent() { + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([]); + } - function verifyProjectsUpdatedInBackgroundEvent() { - host.runQueuedTimeoutCallbacks(); - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - } + function verifyProjectsUpdatedInBackgroundEvent() { + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: ts.server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); + } - function updateContentOfOpenFile(file: File, newContent: string) { - session.executeCommandSeq({ - command: server.CommandNames.Change, - arguments: { - file: file.path, - insertString: newContent, - endLine: 1, - endOffset: file.content.length, - line: 1, - offset: 1 - } - }); - file.content = newContent; - } + function updateContentOfOpenFile(file: ts.projectSystem.File, newContent: string) { + session.executeCommandSeq({ + command: ts.server.CommandNames.Change, + arguments: { + file: file.path, + insertString: newContent, + endLine: 1, + endOffset: file.content.length, + line: 1, + offset: 1 + } + }); + file.content = newContent; } + } - it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { - const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); + it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { + const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); - // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { console.log('hi'); };`); - verifyProjectsUpdatedInBackgroundEvent(); - }); + // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { console.log('hi'); };`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - it("should be up-to-date with the reference map changes", () => { - const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyProjectsUpdatedInBackgroundEvent, verifyNoProjectsUpdatedInBackgroundEvent } = getInitialState(); + it("should be up-to-date with the reference map changes", () => { + const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyProjectsUpdatedInBackgroundEvent, verifyNoProjectsUpdatedInBackgroundEvent } = getInitialState(); - // Change file1Consumer1 content to `export let y = Foo();` - updateContentOfOpenFile(file1Consumer1, "export let y = Foo();"); - verifyNoProjectsUpdatedInBackgroundEvent(); + // Change file1Consumer1 content to `export let y = Foo();` + updateContentOfOpenFile(file1Consumer1, "export let y = Foo();"); + verifyNoProjectsUpdatedInBackgroundEvent(); - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); - // Add the import statements back to file1Consumer1 - updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`); - verifyNoProjectsUpdatedInBackgroundEvent(); + // Add the import statements back to file1Consumer1 + updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`); + verifyNoProjectsUpdatedInBackgroundEvent(); - // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` - host.writeFile(moduleFile1.path, `export var T: number;export var T2: string;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` + host.writeFile(moduleFile1.path, `export var T: number;export var T2: string;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); - // Multiple file edits in one go: + // Multiple file edits in one go: - // Change file1Consumer1 content to `export let y = Foo();` - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`); - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); - }); + // Change file1Consumer1 content to `export let y = Foo();` + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`); + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - it("should be up-to-date with deleted files", () => { - const { host, moduleFile1, file1Consumer2, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); + it("should be up-to-date with deleted files", () => { + const { host, moduleFile1, file1Consumer2, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); - // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - // Delete file1Consumer2 - host.deleteFile(file1Consumer2.path); - verifyProjectsUpdatedInBackgroundEvent(); - }); + // Delete file1Consumer2 + host.deleteFile(file1Consumer2.path); + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should be up-to-date with newly created files", () => { + const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent, } = getInitialState(); - it("should be up-to-date with newly created files", () => { - const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent, } = getInitialState(); + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + host.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - host.writeFile("/a/b/file1Consumer3.ts", `import {Foo} from "./moduleFile1"; let y = Foo();`); - verifyProjectsUpdatedInBackgroundEvent(); + it("should detect changes in non-root files", () => { + const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + configObj: { files: [file1Consumer1Path] }, }); - it("should detect changes in non-root files", () => { - const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - configObj: { files: [file1Consumer1Path] }, - }); + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + // change file1 internal, and verify only file1 is affected + host.writeFile(moduleFile1.path, moduleFile1.content + "var T1: number;"); + verifyProjectsUpdatedInBackgroundEvent(); + }); - // change file1 internal, and verify only file1 is affected - host.writeFile(moduleFile1.path, moduleFile1.content + "var T1: number;"); - verifyProjectsUpdatedInBackgroundEvent(); - }); + it("should return all files if a global file changed shape", () => { + const { host, globalFile3, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); - it("should return all files if a global file changed shape", () => { - const { host, globalFile3, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); + host.writeFile(globalFile3.path, globalFile3.content + "var T2: string;"); + verifyProjectsUpdatedInBackgroundEvent(); + }); - host.writeFile(globalFile3.path, globalFile3.content + "var T2: string;"); - verifyProjectsUpdatedInBackgroundEvent(); + it("should always return the file itself if '--isolatedModules' is specified", () => { + const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + configObj: { compilerOptions: { isolatedModules: true } } }); - it("should always return the file itself if '--isolatedModules' is specified", () => { - const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - configObj: { compilerOptions: { isolatedModules: true } } - }); + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + it("should always return the file itself if '--out' or '--outFile' is specified", () => { + const outFilePath = "/a/b/out.js"; + const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + configObj: { compilerOptions: { module: "system", outFile: outFilePath } } }); - it("should always return the file itself if '--out' or '--outFile' is specified", () => { - const outFilePath = "/a/b/out.js"; - const { host, moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - configObj: { compilerOptions: { module: "system", outFile: outFilePath } } - }); + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + it("should return cascaded affected file list", () => { + const file1Consumer1Consumer1: ts.projectSystem.File = { + path: "/a/b/file1Consumer1Consumer1.ts", + content: `import {y} from "./file1Consumer1";` + }; + const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] }); - it("should return cascaded affected file list", () => { - const file1Consumer1Consumer1: File = { - path: "/a/b/file1Consumer1Consumer1.ts", - content: `import {y} from "./file1Consumer1";` - }; - const { host, moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] - }); - - updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;"); - verifyNoProjectsUpdatedInBackgroundEvent(); + updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;"); + verifyNoProjectsUpdatedInBackgroundEvent(); - // Doesnt change the shape of file1Consumer1 - host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); + // Doesnt change the shape of file1Consumer1 + host.writeFile(moduleFile1.path, `export var T: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); - // Change both files before the timeout - updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;"); - host.writeFile(moduleFile1.path, `export var T2: number;export function Foo() { };`); - verifyProjectsUpdatedInBackgroundEvent(); - }); + // Change both files before the timeout + updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;"); + host.writeFile(moduleFile1.path, `export var T2: number;export function Foo() { };`); + verifyProjectsUpdatedInBackgroundEvent(); + }); - it("should work fine for files with circular references", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: ` + it("should work fine for files with circular references", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/file1.ts", + content: ` /// export var t1 = 10;` - }; - const file2: File = { - path: "/a/b/file2.ts", - content: ` + }; + const file2: ts.projectSystem.File = { + path: "/a/b/file2.ts", + content: ` /// export var t2 = 10;` - }; - const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [file1, file2], - firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath] - }); - - host.writeFile(file2.path, file2.content + "export var t3 = 10;"); - verifyProjectsUpdatedInBackgroundEvent(); + }; + const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [file1, file2], + firstReloadFileList: [file1.path, ts.projectSystem.libFile.path, file2.path, configFilePath] }); - it("should detect removed code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` + host.writeFile(file2.path, file2.content + "export var t3 = 10;"); + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should detect removed code file", () => { + const referenceFile1: ts.projectSystem.File = { + path: "/a/b/referenceFile1.ts", + content: ` /// export var x = Foo();` - }; - const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [referenceFile1], - firstReloadFileList: [referenceFile1.path, libFile.path, moduleFile1Path, configFilePath] - }); - - host.deleteFile(moduleFile1Path); - verifyProjectsUpdatedInBackgroundEvent(); + }; + const { host, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [referenceFile1.path, ts.projectSystem.libFile.path, moduleFile1Path, configFilePath] }); - it("should detect non-existing code file", () => { - const referenceFile1: File = { - path: "/a/b/referenceFile1.ts", - content: ` + host.deleteFile(moduleFile1Path); + verifyProjectsUpdatedInBackgroundEvent(); + }); + + it("should detect non-existing code file", () => { + const referenceFile1: ts.projectSystem.File = { + path: "/a/b/referenceFile1.ts", + content: ` /// export var x = Foo();` - }; - const { host, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ - getAdditionalFileOrFolder: () => [referenceFile1], - firstReloadFileList: [referenceFile1.path, libFile.path, configFilePath] - }); - - updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();"); - verifyNoProjectsUpdatedInBackgroundEvent(); - - // Create module File2 and see both files are saved - host.writeFile(moduleFile2.path, moduleFile2.content); - verifyProjectsUpdatedInBackgroundEvent(); + }; + const { host, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [referenceFile1.path, ts.projectSystem.libFile.path, configFilePath] }); - }); - describe("resolution when resolution cache size", () => { - function verifyWithMaxCacheLimit(subScenario: string, useSlashRootAsSomeNotRootFolderInUserDirectory: boolean) { - it(subScenario, () => { - const rootFolder = useSlashRootAsSomeNotRootFolderInUserDirectory ? "/user/username/rootfolder/otherfolder/" : "/"; - const file1: File = { - path: rootFolder + "a/b/project/file1.ts", - content: 'import a from "file2"' - }; - const file2: File = { - path: rootFolder + "a/b/node_modules/file2.d.ts", - content: "export class a { }" - }; - const file3: File = { - path: rootFolder + "a/b/project/file3.ts", - content: "export class c { }" - }; - const configFile: File = { - path: rootFolder + "a/b/project/tsconfig.json", - content: JSON.stringify({ compilerOptions: { typeRoots: [] } }) - }; - - const openFiles = [file1.path]; - const host = createServerHost([file1, file3, libFile, configFile]); - const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host, createLoggerWithInMemoryLogs(host)); - verifyInitialOpen(file1); - - file3.content += "export class d {}"; - host.writeFile(file3.path, file3.content); - host.checkTimeoutQueueLengthAndRun(2); - - // Since this is first event - verifyProjectsUpdatedInBackgroundEventHandler([{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }]); - - host.writeFile(file2.path, file2.content); - host.runQueuedTimeoutCallbacks(); // For invalidation - host.runQueuedTimeoutCallbacks(); // For actual update - - verifyProjectsUpdatedInBackgroundEventHandler(useSlashRootAsSomeNotRootFolderInUserDirectory ? [{ - eventName: server.ProjectsUpdatedInBackgroundEvent, - data: { - openFiles - } - }] : []); - baselineTsserverLogs("projectUpdatedInBackground", `${scenario} and ${subScenario}`, session); - }); - } - verifyWithMaxCacheLimit("project is not at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true); - verifyWithMaxCacheLimit("project is at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false); + updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();"); + verifyNoProjectsUpdatedInBackgroundEvent(); + + // Create module File2 and see both files are saved + host.writeFile(moduleFile2.path, moduleFile2.content); + verifyProjectsUpdatedInBackgroundEvent(); }); - } + }); - describe("when event handler is set in the session", () => { - verifyProjectsUpdatedInBackgroundEvent("when event handler is set in the session", createSessionWithProjectChangedEventHandler); + describe("resolution when resolution cache size", () => { + function verifyWithMaxCacheLimit(subScenario: string, useSlashRootAsSomeNotRootFolderInUserDirectory: boolean) { + it(subScenario, () => { + const rootFolder = useSlashRootAsSomeNotRootFolderInUserDirectory ? "/user/username/rootfolder/otherfolder/" : "/"; + const file1: ts.projectSystem.File = { + path: rootFolder + "a/b/project/file1.ts", + content: 'import a from "file2"' + }; + const file2: ts.projectSystem.File = { + path: rootFolder + "a/b/node_modules/file2.d.ts", + content: "export class a { }" + }; + const file3: ts.projectSystem.File = { + path: rootFolder + "a/b/project/file3.ts", + content: "export class c { }" + }; + const configFile: ts.projectSystem.File = { + path: rootFolder + "a/b/project/tsconfig.json", + content: JSON.stringify({ compilerOptions: { typeRoots: [] } }) + }; - function createSessionWithProjectChangedEventHandler(host: TestServerHost, logger: Logger | undefined): ProjectsUpdatedInBackgroundEventVerifier { - const { session, events: projectChangedEvents } = createSessionWithEventTracking( - host, - server.ProjectsUpdatedInBackgroundEvent, - logger && { logger } - ); - return { - session, - verifyProjectsUpdatedInBackgroundEventHandler, - verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) - }; + const openFiles = [file1.path]; + const host = ts.projectSystem.createServerHost([file1, file3, ts.projectSystem.libFile, configFile]); + const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host, ts.projectSystem.createLoggerWithInMemoryLogs(host)); + verifyInitialOpen(file1); - function eventToString(event: server.ProjectsUpdatedInBackgroundEvent) { - return JSON.stringify(event && { eventName: event.eventName, data: event.data }); - } + file3.content += "export class d {}"; + host.writeFile(file3.path, file3.content); + host.checkTimeoutQueueLengthAndRun(2); - function eventsToString(events: readonly server.ProjectsUpdatedInBackgroundEvent[]) { - return "[" + map(events, eventToString).join(",") + "]"; - } + // Since this is first event + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: ts.server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); - function verifyProjectsUpdatedInBackgroundEventHandler(expectedEvents: readonly server.ProjectsUpdatedInBackgroundEvent[]) { - assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`); - forEach(projectChangedEvents, (actualEvent, i) => { - const expectedEvent = expectedEvents[i]; - assert.strictEqual(actualEvent.eventName, expectedEvent.eventName); - verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles); - }); + host.writeFile(file2.path, file2.content); + host.runQueuedTimeoutCallbacks(); // For invalidation + host.runQueuedTimeoutCallbacks(); // For actual update - // Verified the events, reset them - projectChangedEvents.length = 0; - } + verifyProjectsUpdatedInBackgroundEventHandler(useSlashRootAsSomeNotRootFolderInUserDirectory ? [{ + eventName: ts.server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }] : []); + ts.projectSystem.baselineTsserverLogs("projectUpdatedInBackground", `${scenario} and ${subScenario}`, session); + }); } + verifyWithMaxCacheLimit("project is not at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ true); + verifyWithMaxCacheLimit("project is at root level", /*useSlashRootAsSomeNotRootFolderInUserDirectory*/ false); }); + } + + describe("when event handler is set in the session", () => { + verifyProjectsUpdatedInBackgroundEvent("when event handler is set in the session", createSessionWithProjectChangedEventHandler); + + function createSessionWithProjectChangedEventHandler(host: ts.projectSystem.TestServerHost, logger: ts.projectSystem.Logger | undefined): ProjectsUpdatedInBackgroundEventVerifier { + const { session, events: projectChangedEvents } = ts.projectSystem.createSessionWithEventTracking( + host, + ts.server.ProjectsUpdatedInBackgroundEvent, + logger && { logger } + ); + return { + session, + verifyProjectsUpdatedInBackgroundEventHandler, + verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) + }; - describe("when event handler is not set but session is created with canUseEvents = true", () => { - describe("without noGetErrOnBackgroundUpdate, diagnostics for open files are queued", () => { - verifyProjectsUpdatedInBackgroundEvent("without noGetErrOnBackgroundUpdate", createSessionThatUsesEvents); - }); + function eventToString(event: ts.server.ProjectsUpdatedInBackgroundEvent) { + return JSON.stringify(event && { eventName: event.eventName, data: event.data }); + } - describe("with noGetErrOnBackgroundUpdate, diagnostics for open file are not queued", () => { - verifyProjectsUpdatedInBackgroundEvent("with noGetErrOnBackgroundUpdate", (host, logger) => createSessionThatUsesEvents(host, logger, /*noGetErrOnBackgroundUpdate*/ true)); - }); + function eventsToString(events: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) { + return "[" + ts.map(events, eventToString).join(",") + "]"; + } + function verifyProjectsUpdatedInBackgroundEventHandler(expectedEvents: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) { + assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`); + ts.forEach(projectChangedEvents, (actualEvent, i) => { + const expectedEvent = expectedEvents[i]; + assert.strictEqual(actualEvent.eventName, expectedEvent.eventName); + verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles); + }); - function createSessionThatUsesEvents(host: TestServerHost, logger: Logger | undefined, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier { - const { session, getEvents, clearEvents } = createSessionWithDefaultEventHandler( - host, - server.ProjectsUpdatedInBackgroundEvent, - { noGetErrOnBackgroundUpdate, logger: logger || createHasErrorMessageLogger() } - ); + // Verified the events, reset them + projectChangedEvents.length = 0; + } + } + }); - return { - session, - verifyProjectsUpdatedInBackgroundEventHandler, - verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) - }; + describe("when event handler is not set but session is created with canUseEvents = true", () => { + describe("without noGetErrOnBackgroundUpdate, diagnostics for open files are queued", () => { + verifyProjectsUpdatedInBackgroundEvent("without noGetErrOnBackgroundUpdate", createSessionThatUsesEvents); + }); - function verifyProjectsUpdatedInBackgroundEventHandler(expected: readonly server.ProjectsUpdatedInBackgroundEvent[]) { - const expectedEvents: protocol.ProjectsUpdatedInBackgroundEventBody[] = map(expected, e => { - return { - openFiles: e.data.openFiles - }; - }); - const events = getEvents(); - assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${map(events, e => e.body)} Expected: ${expectedEvents}`); - forEach(events, (actualEvent, i) => { - const expectedEvent = expectedEvents[i]; - verifyFiles("openFiles", actualEvent.body.openFiles, expectedEvent.openFiles); - }); + describe("with noGetErrOnBackgroundUpdate, diagnostics for open file are not queued", () => { + verifyProjectsUpdatedInBackgroundEvent("with noGetErrOnBackgroundUpdate", (host, logger) => createSessionThatUsesEvents(host, logger, /*noGetErrOnBackgroundUpdate*/ true)); + }); - // Verified the events, reset them - clearEvents(); - if (events.length) { - host.checkTimeoutQueueLength(noGetErrOnBackgroundUpdate ? 0 : 1); // Error checking queued only if not noGetErrOnBackgroundUpdate - } + function createSessionThatUsesEvents(host: ts.projectSystem.TestServerHost, logger: ts.projectSystem.Logger | undefined, noGetErrOnBackgroundUpdate?: boolean): ProjectsUpdatedInBackgroundEventVerifier { + const { session, getEvents, clearEvents } = ts.projectSystem.createSessionWithDefaultEventHandler( + host, + ts.server.ProjectsUpdatedInBackgroundEvent, + { noGetErrOnBackgroundUpdate, logger: logger || ts.projectSystem.createHasErrorMessageLogger() } + ); + + return { + session, + verifyProjectsUpdatedInBackgroundEventHandler, + verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) + }; + + function verifyProjectsUpdatedInBackgroundEventHandler(expected: readonly ts.server.ProjectsUpdatedInBackgroundEvent[]) { + const expectedEvents: ts.projectSystem.protocol.ProjectsUpdatedInBackgroundEventBody[] = ts.map(expected, e => { + return { + openFiles: e.data.openFiles + }; + }); + const events = getEvents(); + assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${ts.map(events, e => e.body)} Expected: ${expectedEvents}`); + ts.forEach(events, (actualEvent, i) => { + const expectedEvent = expectedEvents[i]; + verifyFiles("openFiles", actualEvent.body.openFiles, expectedEvent.openFiles); + }); + + // Verified the events, reset them + clearEvents(); + + if (events.length) { + host.checkTimeoutQueueLength(noGetErrOnBackgroundUpdate ? 0 : 1); // Error checking queued only if not noGetErrOnBackgroundUpdate } } - }); + } }); -} +}); diff --git a/src/testRunner/unittests/tsserver/exportMapCache.ts b/src/testRunner/unittests/tsserver/exportMapCache.ts index 184200c07cbe4..4cbb70fd45640 100644 --- a/src/testRunner/unittests/tsserver/exportMapCache.ts +++ b/src/testRunner/unittests/tsserver/exportMapCache.ts @@ -1,147 +1,147 @@ -namespace ts.projectSystem { - const packageJson: File = { - path: "/package.json", - content: `{ "dependencies": { "mobx": "*" } }` - }; - const aTs: File = { - path: "/a.ts", - content: "export const foo = 0;", - }; - const bTs: File = { - path: "/b.ts", - content: "foo", - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", - }; - const ambientDeclaration: File = { - path: "/ambient.d.ts", - content: "declare module 'ambient' {}" - }; - const mobxPackageJson: File = { - path: "/node_modules/mobx/package.json", - content: `{ "name": "mobx", "version": "1.0.0" }` - }; - const mobxDts: File = { - path: "/node_modules/mobx/index.d.ts", - content: "export declare function observable(): unknown;" - }; - const exportEqualsMappedType: File = { - path: "/lib/foo/constants.d.ts", - content: ` +import * as ts from "../../_namespaces/ts"; + +const packageJson: ts.projectSystem.File = { + path: "/package.json", + content: `{ "dependencies": { "mobx": "*" } }` +}; +const aTs: ts.projectSystem.File = { + path: "/a.ts", + content: "export const foo = 0;", +}; +const bTs: ts.projectSystem.File = { + path: "/b.ts", + content: "foo", +}; +const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: "{}", +}; +const ambientDeclaration: ts.projectSystem.File = { + path: "/ambient.d.ts", + content: "declare module 'ambient' {}" +}; +const mobxPackageJson: ts.projectSystem.File = { + path: "/node_modules/mobx/package.json", + content: `{ "name": "mobx", "version": "1.0.0" }` +}; +const mobxDts: ts.projectSystem.File = { + path: "/node_modules/mobx/index.d.ts", + content: "export declare function observable(): unknown;" +}; +const exportEqualsMappedType: ts.projectSystem.File = { + path: "/lib/foo/constants.d.ts", + content: ` type Signals = "SIGINT" | "SIGABRT"; declare const exp: {} & { [K in Signals]: K }; export = exp;`, - }; +}; - describe("unittests:: tsserver:: exportMapCache", () => { - it("caches auto-imports in the same file", () => { - const { exportMapCache } = setup(); - assert.ok(exportMapCache.isUsableByFile(bTs.path as Path)); - assert.ok(!exportMapCache.isEmpty()); - }); +describe("unittests:: tsserver:: exportMapCache", () => { + it("caches auto-imports in the same file", () => { + const { exportMapCache } = setup(); + assert.ok(exportMapCache.isUsableByFile(bTs.path as ts.Path)); + assert.ok(!exportMapCache.isEmpty()); + }); - it("invalidates the cache when new files are added", () => { - const { host, exportMapCache } = setup(); - host.writeFile("/src/a2.ts", aTs.content); - host.runQueuedTimeoutCallbacks(); - assert.ok(!exportMapCache.isUsableByFile(bTs.path as Path)); - assert.ok(exportMapCache.isEmpty()); - }); + it("invalidates the cache when new files are added", () => { + const { host, exportMapCache } = setup(); + host.writeFile("/src/a2.ts", aTs.content); + host.runQueuedTimeoutCallbacks(); + assert.ok(!exportMapCache.isUsableByFile(bTs.path as ts.Path)); + assert.ok(exportMapCache.isEmpty()); + }); - it("invalidates the cache when files are deleted", () => { - const { host, projectService, exportMapCache } = setup(); - projectService.closeClientFile(aTs.path); - host.deleteFile(aTs.path); - host.runQueuedTimeoutCallbacks(); - assert.ok(!exportMapCache.isUsableByFile(bTs.path as Path)); - assert.ok(exportMapCache.isEmpty()); - }); + it("invalidates the cache when files are deleted", () => { + const { host, projectService, exportMapCache } = setup(); + projectService.closeClientFile(aTs.path); + host.deleteFile(aTs.path); + host.runQueuedTimeoutCallbacks(); + assert.ok(!exportMapCache.isUsableByFile(bTs.path as ts.Path)); + assert.ok(exportMapCache.isEmpty()); + }); - it("does not invalidate the cache when package.json is changed inconsequentially", () => { - const { host, exportMapCache, project } = setup(); - host.writeFile("/package.json", `{ "name": "blah", "dependencies": { "mobx": "*" } }`); - host.runQueuedTimeoutCallbacks(); - project.getPackageJsonAutoImportProvider(); - assert.ok(exportMapCache.isUsableByFile(bTs.path as Path)); - assert.ok(!exportMapCache.isEmpty()); - }); + it("does not invalidate the cache when package.json is changed inconsequentially", () => { + const { host, exportMapCache, project } = setup(); + host.writeFile("/package.json", `{ "name": "blah", "dependencies": { "mobx": "*" } }`); + host.runQueuedTimeoutCallbacks(); + project.getPackageJsonAutoImportProvider(); + assert.ok(exportMapCache.isUsableByFile(bTs.path as ts.Path)); + assert.ok(!exportMapCache.isEmpty()); + }); - it("invalidates the cache when package.json change results in AutoImportProvider change", () => { - const { host, exportMapCache, project } = setup(); - host.writeFile("/package.json", `{}`); - host.runQueuedTimeoutCallbacks(); - project.getPackageJsonAutoImportProvider(); - assert.ok(!exportMapCache.isUsableByFile(bTs.path as Path)); - assert.ok(exportMapCache.isEmpty()); - }); + it("invalidates the cache when package.json change results in AutoImportProvider change", () => { + const { host, exportMapCache, project } = setup(); + host.writeFile("/package.json", `{}`); + host.runQueuedTimeoutCallbacks(); + project.getPackageJsonAutoImportProvider(); + assert.ok(!exportMapCache.isUsableByFile(bTs.path as ts.Path)); + assert.ok(exportMapCache.isEmpty()); + }); - it("does not store transient symbols through program updates", () => { - const { exportMapCache, project, session } = setup(); - // SIGINT, exported from /lib/foo/constants.d.ts, is a mapped type property, which will be a transient symbol. - // Transient symbols contain types, which retain the checkers they came from, so are not safe to cache. - // We clear symbols from the cache during updateGraph, leaving only the information about how to re-get them - // (see getters on `CachedSymbolExportInfo`). We can roughly test that this is working by ensuring that - // accessing a transient symbol with two different checkers results in different symbol identities, since - // transient symbols are recreated with every new checker. - const programBefore = project.getCurrentProgram()!; - let sigintPropBefore: readonly SymbolExportInfo[] | undefined; - exportMapCache.search(bTs.path as Path, /*preferCapitalized*/ false, returnTrue, (info, symbolName) => { - if (symbolName === "SIGINT") sigintPropBefore = info; - }); - assert.ok(sigintPropBefore); - assert.ok(sigintPropBefore![0].symbol.flags & SymbolFlags.Transient); - const symbolIdBefore = getSymbolId(sigintPropBefore![0].symbol); + it("does not store transient symbols through program updates", () => { + const { exportMapCache, project, session } = setup(); + // SIGINT, exported from /lib/foo/constants.d.ts, is a mapped type property, which will be a transient symbol. + // Transient symbols contain types, which retain the checkers they came from, so are not safe to cache. + // We clear symbols from the cache during updateGraph, leaving only the information about how to re-get them + // (see getters on `CachedSymbolExportInfo`). We can roughly test that this is working by ensuring that + // accessing a transient symbol with two different checkers results in different symbol identities, since + // transient symbols are recreated with every new checker. + const programBefore = project.getCurrentProgram()!; + let sigintPropBefore: readonly ts.SymbolExportInfo[] | undefined; + exportMapCache.search(bTs.path as ts.Path, /*preferCapitalized*/ false, ts.returnTrue, (info, symbolName) => { + if (symbolName === "SIGINT") sigintPropBefore = info; + }); + assert.ok(sigintPropBefore); + assert.ok(sigintPropBefore![0].symbol.flags & ts.SymbolFlags.Transient); + const symbolIdBefore = ts.getSymbolId(sigintPropBefore![0].symbol); - // Update program without clearing cache - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: bTs.path, - textChanges: [{ - newText: " ", - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 }, - }] + // Update program without clearing cache + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: bTs.path, + textChanges: [{ + newText: " ", + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 }, }] - } - }); - project.getLanguageService(/*ensureSynchronized*/ true); - assert.notEqual(programBefore, project.getCurrentProgram()!); + }] + } + }); + project.getLanguageService(/*ensureSynchronized*/ true); + assert.notEqual(programBefore, project.getCurrentProgram()!); - // Get same info from cache again - let sigintPropAfter: readonly SymbolExportInfo[] | undefined; - exportMapCache.search(bTs.path as Path, /*preferCapitalized*/ false, returnTrue, (info, symbolName) => { - if (symbolName === "SIGINT") sigintPropAfter = info; - }); - assert.ok(sigintPropAfter); - assert.notEqual(symbolIdBefore, getSymbolId(sigintPropAfter![0].symbol)); + // Get same info from cache again + let sigintPropAfter: readonly ts.SymbolExportInfo[] | undefined; + exportMapCache.search(bTs.path as ts.Path, /*preferCapitalized*/ false, ts.returnTrue, (info, symbolName) => { + if (symbolName === "SIGINT") sigintPropAfter = info; }); + assert.ok(sigintPropAfter); + assert.notEqual(symbolIdBefore, ts.getSymbolId(sigintPropAfter![0].symbol)); }); +}); - function setup() { - const host = createServerHost([aTs, bTs, ambientDeclaration, tsconfig, packageJson, mobxPackageJson, mobxDts, exportEqualsMappedType]); - const session = createSession(host); - openFilesForSession([aTs, bTs], session); - const projectService = session.getProjectService(); - const project = configuredProjectAt(projectService, 0); - triggerCompletions(); - const checker = project.getLanguageService().getProgram()!.getTypeChecker(); - return { host, project, projectService, session, exportMapCache: project.getCachedExportInfoMap(), checker, triggerCompletions }; +function setup() { + const host = ts.projectSystem.createServerHost([aTs, bTs, ambientDeclaration, tsconfig, packageJson, mobxPackageJson, mobxDts, exportEqualsMappedType]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([aTs, bTs], session); + const projectService = session.getProjectService(); + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + triggerCompletions(); + const checker = project.getLanguageService().getProgram()!.getTypeChecker(); + return { host, project, projectService, session, exportMapCache: project.getCachedExportInfoMap(), checker, triggerCompletions }; - function triggerCompletions() { - const requestLocation: protocol.FileLocationRequestArgs = { - file: bTs.path, - line: 1, - offset: 3, - }; - executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { - ...requestLocation, - includeExternalModuleExports: true, - prefix: "foo", - }); - } + function triggerCompletions() { + const requestLocation: ts.projectSystem.protocol.FileLocationRequestArgs = { + file: bTs.path, + line: 1, + offset: 3, + }; + ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.CompletionInfo, { + ...requestLocation, + includeExternalModuleExports: true, + prefix: "foo", + }); } } diff --git a/src/testRunner/unittests/tsserver/externalProjects.ts b/src/testRunner/unittests/tsserver/externalProjects.ts index 3bcb5789daaea..cbd3ffced2317 100644 --- a/src/testRunner/unittests/tsserver/externalProjects.ts +++ b/src/testRunner/unittests/tsserver/externalProjects.ts @@ -1,941 +1,942 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: ExternalProjects", () => { - describe("can handle tsconfig file name with difference casing", () => { - function verifyConfigFileCasing(lazyConfiguredProjectsFromExternalProject: boolean) { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - include: [] - }) - }; - - const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false }); - const service = createProjectService(host); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); - service.openExternalProject({ - projectFileName: "/a/b/project.csproj", - rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]), - options: {} - } as protocol.ExternalProject); - service.checkNumberOfProjects({ configuredProjects: 1 }); - const project = service.configuredProjects.get(config.path)!; - if (lazyConfiguredProjectsFromExternalProject) { - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded - checkProjectActualFiles(project, emptyArray); - } - else { - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded - checkProjectActualFiles(project, [upperCaseConfigFilePath]); - } +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; - service.openClientFile(f1.path); - service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); +describe("unittests:: tsserver:: ExternalProjects", () => { + describe("can handle tsconfig file name with difference casing", () => { + function verifyConfigFileCasing(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + include: [] + }) + }; - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated - checkProjectActualFiles(project, [upperCaseConfigFilePath]); - checkProjectActualFiles(service.inferredProjects[0], [f1.path]); + const host = ts.projectSystem.createServerHost([f1, config], { useCaseSensitiveFileNames: false }); + const service = ts.projectSystem.createProjectService(host); + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + const upperCaseConfigFilePath = ts.combinePaths(ts.getDirectoryPath(config.path).toUpperCase(), ts.getBaseFileName(config.path)); + service.openExternalProject({ + projectFileName: "/a/b/project.csproj", + rootFiles: ts.projectSystem.toExternalFiles([f1.path, upperCaseConfigFilePath]), + options: {} + } as ts.projectSystem.protocol.ExternalProject); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project = service.configuredProjects.get(config.path)!; + if (lazyConfiguredProjectsFromExternalProject) { + assert.equal(project.pendingReload, ts.ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded + ts.projectSystem.checkProjectActualFiles(project, ts.emptyArray); + } + else { + assert.equal(project.pendingReload, ts.ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded + ts.projectSystem.checkProjectActualFiles(project, [upperCaseConfigFilePath]); } - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); + service.openClientFile(f1.path); + service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); - }); - - it("load global plugins", () => { - const f1 = { - path: "/a/file1.ts", - content: "let x = [1, 2];" - }; - const p1 = { projectFileName: "/a/proj1.csproj", rootFiles: [toExternalFile(f1.path)], options: {} }; - - const host = createServerHost([f1]); - host.require = (_initialPath, moduleName) => { - assert.equal(moduleName, "myplugin"); - return { - module: () => ({ - create(info: server.PluginCreateInfo) { - const proxy = Harness.LanguageService.makeDefaultProxy(info); - proxy.getSemanticDiagnostics = filename => { - const prev = info.languageService.getSemanticDiagnostics(filename); - const sourceFile: SourceFile = info.project.getSourceFile(toPath(filename, /*basePath*/ undefined, createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; - prev.push({ - category: DiagnosticCategory.Warning, - file: sourceFile, - code: 9999, - length: 3, - messageText: `Plugin diagnostic`, - start: 0 - }); - return prev; - }; - return proxy; - } - }), - error: undefined - }; - }; - const session = createSession(host, { globalPlugins: ["myplugin"] }); - - session.executeCommand({ - seq: 1, - type: "request", - command: "openExternalProjects", - arguments: { projects: [p1] } - } as protocol.OpenExternalProjectsRequest); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); - - const handlerResponse = session.executeCommand({ - seq: 2, - type: "request", - command: "semanticDiagnosticsSync", - arguments: { - file: f1.path, - projectFileName: p1.projectFileName - } - } as protocol.SemanticDiagnosticsSyncRequest); + assert.equal(project.pendingReload, ts.ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated + ts.projectSystem.checkProjectActualFiles(project, [upperCaseConfigFilePath]); + ts.projectSystem.checkProjectActualFiles(service.inferredProjects[0], [f1.path]); + } - assert.isDefined(handlerResponse.response); - const response = handlerResponse.response as protocol.Diagnostic[]; - assert.equal(response.length, 1); - assert.equal(response[0].text, "Plugin diagnostic"); + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ false); }); - it("remove not-listed external projects", () => { - const f1 = { - path: "/a/app.ts", - content: "let x = 1" - }; - const f2 = { - path: "/b/app.ts", - content: "let x = 1" - }; - const f3 = { - path: "/c/app.ts", - content: "let x = 1" - }; - const makeProject = (f: File) => ({ projectFileName: f.path + ".csproj", rootFiles: [toExternalFile(f.path)], options: {} }); - const p1 = makeProject(f1); - const p2 = makeProject(f2); - const p3 = makeProject(f3); - - const host = createServerHost([f1, f2, f3]); - const session = createSession(host); - - session.executeCommand({ - seq: 1, - type: "request", - command: "openExternalProjects", - arguments: { projects: [p1, p2] } - } as protocol.OpenExternalProjectsRequest); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { externalProjects: 2 }); - assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); - assert.equal(projectService.externalProjects[1].getProjectName(), p2.projectFileName); - - session.executeCommand({ - seq: 2, - type: "request", - command: "openExternalProjects", - arguments: { projects: [p1, p3] } - } as protocol.OpenExternalProjectsRequest); - checkNumberOfProjects(projectService, { externalProjects: 2 }); - assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); - assert.equal(projectService.externalProjects[1].getProjectName(), p3.projectFileName); - - session.executeCommand({ - seq: 3, - type: "request", - command: "openExternalProjects", - arguments: { projects: [] } - } as protocol.OpenExternalProjectsRequest); - checkNumberOfProjects(projectService, { externalProjects: 0 }); - - session.executeCommand({ - seq: 3, - type: "request", - command: "openExternalProjects", - arguments: { projects: [p2] } - } as protocol.OpenExternalProjectsRequest); - assert.equal(projectService.externalProjects[0].getProjectName(), p2.projectFileName); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ true); }); + }); - it("should not close external project with no open files", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y =1;" - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, file2]); - const projectService = createProjectService(host); - projectService.openExternalProject({ - rootFiles: toExternalFiles([file1.path, file2.path]), - options: {}, - projectFileName: externalProjectName - }); - - checkNumberOfExternalProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); + it("load global plugins", () => { + const f1 = { + path: "/a/file1.ts", + content: "let x = [1, 2];" + }; + const p1 = { projectFileName: "/a/proj1.csproj", rootFiles: [ts.projectSystem.toExternalFile(f1.path)], options: {} }; + + const host = ts.projectSystem.createServerHost([f1]); + host.require = (_initialPath, moduleName) => { + assert.equal(moduleName, "myplugin"); + return { + module: () => ({ + create(info: ts.server.PluginCreateInfo) { + const proxy = Harness.LanguageService.makeDefaultProxy(info); + proxy.getSemanticDiagnostics = filename => { + const prev = info.languageService.getSemanticDiagnostics(filename); + const sourceFile: ts.SourceFile = info.project.getSourceFile(ts.toPath(filename, /*basePath*/ undefined, ts.createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; + prev.push({ + category: ts.DiagnosticCategory.Warning, + file: sourceFile, + code: 9999, + length: 3, + messageText: `Plugin diagnostic`, + start: 0 + }); + return prev; + }; + return proxy; + } + }), + error: undefined + }; + }; + const session = ts.projectSystem.createSession(host, { globalPlugins: ["myplugin"] }); + + session.executeCommand({ + seq: 1, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p1] } + } as ts.projectSystem.protocol.OpenExternalProjectsRequest); + + const projectService = session.getProjectService(); + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 }); + assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); + + const handlerResponse = session.executeCommand({ + seq: 2, + type: "request", + command: "semanticDiagnosticsSync", + arguments: { + file: f1.path, + projectFileName: p1.projectFileName + } + } as ts.projectSystem.protocol.SemanticDiagnosticsSyncRequest); - // open client file - should not lead to creation of inferred project - projectService.openClientFile(file1.path, file1.content); - checkNumberOfExternalProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); + assert.isDefined(handlerResponse.response); + const response = handlerResponse.response as ts.projectSystem.protocol.Diagnostic[]; + assert.equal(response.length, 1); + assert.equal(response[0].text, "Plugin diagnostic"); + }); - // close client file - external project should still exists - projectService.closeClientFile(file1.path); - checkNumberOfExternalProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); + it("remove not-listed external projects", () => { + const f1 = { + path: "/a/app.ts", + content: "let x = 1" + }; + const f2 = { + path: "/b/app.ts", + content: "let x = 1" + }; + const f3 = { + path: "/c/app.ts", + content: "let x = 1" + }; + const makeProject = (f: ts.projectSystem.File) => ({ projectFileName: f.path + ".csproj", rootFiles: [ts.projectSystem.toExternalFile(f.path)], options: {} }); + const p1 = makeProject(f1); + const p2 = makeProject(f2); + const p3 = makeProject(f3); + + const host = ts.projectSystem.createServerHost([f1, f2, f3]); + const session = ts.projectSystem.createSession(host); + + session.executeCommand({ + seq: 1, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p1, p2] } + } as ts.projectSystem.protocol.OpenExternalProjectsRequest); + + const projectService = session.getProjectService(); + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 2 }); + assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); + assert.equal(projectService.externalProjects[1].getProjectName(), p2.projectFileName); + + session.executeCommand({ + seq: 2, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p1, p3] } + } as ts.projectSystem.protocol.OpenExternalProjectsRequest); + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 2 }); + assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); + assert.equal(projectService.externalProjects[1].getProjectName(), p3.projectFileName); + + session.executeCommand({ + seq: 3, + type: "request", + command: "openExternalProjects", + arguments: { projects: [] } + } as ts.projectSystem.protocol.OpenExternalProjectsRequest); + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 0 }); + + session.executeCommand({ + seq: 3, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p2] } + } as ts.projectSystem.protocol.OpenExternalProjectsRequest); + assert.equal(projectService.externalProjects[0].getProjectName(), p2.projectFileName); + }); - projectService.closeExternalProject(externalProjectName); - checkNumberOfExternalProjects(projectService, 0); - checkNumberOfInferredProjects(projectService, 0); + it("should not close external project with no open files", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y =1;" + }; + const externalProjectName = "externalproject"; + const host = ts.projectSystem.createServerHost([file1, file2]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openExternalProject({ + rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]), + options: {}, + projectFileName: externalProjectName }); - it("external project for dynamic file", () => { - const externalProjectName = "^ScriptDocument1 file1.ts"; - const externalFiles = toExternalFiles(["^ScriptDocument1 file1.ts"]); - const host = createServerHost([]); - const projectService = createProjectService(host); - projectService.openExternalProject({ - rootFiles: externalFiles, - options: {}, - projectFileName: externalProjectName - }); + ts.projectSystem.checkNumberOfExternalProjects(projectService, 1); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 0); - checkNumberOfExternalProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); - verifyDynamic(projectService, "/^scriptdocument1 file1.ts"); + // open client file - should not lead to creation of inferred project + projectService.openClientFile(file1.path, file1.content); + ts.projectSystem.checkNumberOfExternalProjects(projectService, 1); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 0); - externalFiles[0].content = "let x =1;"; - projectService.applyChangesInOpenFiles(arrayIterator(externalFiles)); - }); + // close client file - external project should still exists + projectService.closeClientFile(file1.path); + ts.projectSystem.checkNumberOfExternalProjects(projectService, 1); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 0); - it("when file name starts with ^", () => { - const file: File = { - path: `${tscWatch.projectRoot}/file.ts`, - content: "const x = 10;" - }; - const app: File = { - path: `${tscWatch.projectRoot}/^app.ts`, - content: "const y = 10;" - }; - const host = createServerHost([file, app, libFile]); - const service = createProjectService(host); - service.openExternalProjects([{ - projectFileName: `${tscWatch.projectRoot}/myproject.njsproj`, - rootFiles: [ - toExternalFile(file.path), - toExternalFile(app.path) - ], - options: { }, - }]); + projectService.closeExternalProject(externalProjectName); + ts.projectSystem.checkNumberOfExternalProjects(projectService, 0); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 0); + }); + + it("external project for dynamic file", () => { + const externalProjectName = "^ScriptDocument1 file1.ts"; + const externalFiles = ts.projectSystem.toExternalFiles(["^ScriptDocument1 file1.ts"]); + const host = ts.projectSystem.createServerHost([]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openExternalProject({ + rootFiles: externalFiles, + options: {}, + projectFileName: externalProjectName }); - it("external project that included config files", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f1.ts"] - } - ) - }; - const file2 = { - path: "/a/c/f2.ts", - content: "let y =1;" - }; - const config2 = { - path: "/a/c/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f2.ts"] - } - ) - }; - const file3 = { - path: "/a/d/f3.ts", - content: "let z =1;" - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, file2, file3, config1, config2]); - const projectService = createProjectService(host); - projectService.openExternalProject({ - rootFiles: toExternalFiles([config1.path, config2.path, file3.path]), - options: {}, - projectFileName: externalProjectName - }); + ts.projectSystem.checkNumberOfExternalProjects(projectService, 1); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 0); + ts.projectSystem.verifyDynamic(projectService, "/^scriptdocument1 file1.ts"); + + externalFiles[0].content = "let x =1;"; + projectService.applyChangesInOpenFiles(ts.arrayIterator(externalFiles)); + }); + + it("when file name starts with ^", () => { + const file: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/file.ts`, + content: "const x = 10;" + }; + const app: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/^app.ts`, + content: "const y = 10;" + }; + const host = ts.projectSystem.createServerHost([file, app, ts.projectSystem.libFile]); + const service = ts.projectSystem.createProjectService(host); + service.openExternalProjects([{ + projectFileName: `${ts.tscWatch.projectRoot}/myproject.njsproj`, + rootFiles: [ + ts.projectSystem.toExternalFile(file.path), + ts.projectSystem.toExternalFile(app.path) + ], + options: { }, + }]); + }); - checkNumberOfProjects(projectService, { configuredProjects: 2 }); - const proj1 = projectService.configuredProjects.get(config1.path); - const proj2 = projectService.configuredProjects.get(config2.path); - assert.isDefined(proj1); - assert.isDefined(proj2); - - // open client file - should not lead to creation of inferred project - projectService.openClientFile(file1.path, file1.content); - checkNumberOfProjects(projectService, { configuredProjects: 2 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); - - projectService.openClientFile(file3.path, file3.content); - checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); - - projectService.closeExternalProject(externalProjectName); - // open file 'file1' from configured project keeps project alive - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.isUndefined(projectService.configuredProjects.get(config2.path)); - - projectService.closeClientFile(file3.path); - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.isUndefined(projectService.configuredProjects.get(config2.path)); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - - projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.isUndefined(projectService.configuredProjects.get(config2.path)); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - - projectService.openClientFile(file2.path, file2.content); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.isUndefined(projectService.configuredProjects.get(config1.path)); - assert.isDefined(projectService.configuredProjects.get(config2.path)); + it("external project that included config files", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) + }; + const file2 = { + path: "/a/c/f2.ts", + content: "let y =1;" + }; + const config2 = { + path: "/a/c/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f2.ts"] + } + ) + }; + const file3 = { + path: "/a/d/f3.ts", + content: "let z =1;" + }; + const externalProjectName = "externalproject"; + const host = ts.projectSystem.createServerHost([file1, file2, file3, config1, config2]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openExternalProject({ + rootFiles: ts.projectSystem.toExternalFiles([config1.path, config2.path, file3.path]), + options: {}, + projectFileName: externalProjectName }); - it("external project with included config file opened after configured project", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, configFile]); - const projectService = createProjectService(host); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 2 }); + const proj1 = projectService.configuredProjects.get(config1.path); + const proj2 = projectService.configuredProjects.get(config2.path); + assert.isDefined(proj1); + assert.isDefined(proj2); + + // open client file - should not lead to creation of inferred project + projectService.openClientFile(file1.path, file1.content); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 2 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); + + projectService.openClientFile(file3.path, file3.content); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); + + projectService.closeExternalProject(externalProjectName); + // open file 'file1' from configured project keeps project alive + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); + + projectService.closeClientFile(file3.path); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + + projectService.closeClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + + projectService.openClientFile(file2.path, file2.content); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(config1.path)); + assert.isDefined(projectService.configuredProjects.get(config2.path)); + }); - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + it("external project with included config file opened after configured project", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const externalProjectName = "externalproject"; + const host = ts.projectSystem.createServerHost([file1, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + projectService.openExternalProject({ + rootFiles: ts.projectSystem.toExternalFiles([configFile.path]), + options: {}, + projectFileName: externalProjectName + }); - projectService.openExternalProject({ - rootFiles: toExternalFiles([configFile.path]), - options: {}, - projectFileName: externalProjectName - }); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + projectService.closeClientFile(file1.path); + // configured project is alive since it is opened as part of external project + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); - projectService.closeClientFile(file1.path); - // configured project is alive since it is opened as part of external project - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + projectService.closeExternalProject(externalProjectName); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 0 }); + }); - projectService.closeExternalProject(externalProjectName); - checkNumberOfProjects(projectService, { configuredProjects: 0 }); + it("external project with included config file opened after configured project and then closed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/f2.ts", + content: "let x = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const externalProjectName = "externalproject"; + const host = ts.projectSystem.createServerHost([file1, file2, ts.projectSystem.libFile, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path); + + projectService.openExternalProject({ + rootFiles: ts.projectSystem.toExternalFiles([configFile.path]), + options: {}, + projectFileName: externalProjectName }); - it("external project with included config file opened after configured project and then closed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/f2.ts", - content: "let x = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, file2, libFile, configFile]); - const projectService = createProjectService(host); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(configFile.path); + projectService.closeExternalProject(externalProjectName); + // configured project is alive since file is still open + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - projectService.openExternalProject({ - rootFiles: toExternalFiles([configFile.path]), - options: {}, - projectFileName: externalProjectName - }); + projectService.closeClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + projectService.openClientFile(file2.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(configFile.path)); + }); - projectService.closeExternalProject(externalProjectName); - // configured project is alive since file is still open - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + it("can correctly update external project when set of root files has changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const host = ts.projectSystem.createServerHost([file1, file2]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: ts.projectSystem.toExternalFiles([file1.path]) }); + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [file1.path]); + + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]) }); + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 }); + ts.projectSystem.checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + }); - projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + it("can update external project when set of root files was not changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "m"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: "export let y = 1" + }; + const file3 = { + path: "/a/m.ts", + content: "export let y = 1" + }; + + const host = ts.projectSystem.createServerHost([file1, file2, file3]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ts.ModuleResolutionKind.NodeJs }, rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]) }); + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 }); + ts.projectSystem.checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]); + + projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ts.ModuleResolutionKind.Classic }, rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]) }); + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 }); + ts.projectSystem.checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]); + }); - projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - assert.isUndefined(projectService.configuredProjects.get(configFile.path)); + it("language service disabled state is updated in external projects", () => { + const f1 = { + path: "/a/app.js", + content: "var x = 1" + }; + const f2 = { + path: "/a/largefile.js", + content: "" + }; + const host = ts.projectSystem.createServerHost([f1, f2]); + const originalGetFileSize = host.getFileSize; + host.getFileSize = (filePath: string) => + filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); + + const service = ts.projectSystem.createProjectService(host); + const projectFileName = "/a/proj.csproj"; + + service.openExternalProject({ + projectFileName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path, f2.path]), + options: {} }); + service.checkNumberOfProjects({ externalProjects: 1 }); + assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 1"); - it("can correctly update external project when set of root files has changed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const host = createServerHost([file1, file2]); - const projectService = createProjectService(host); - - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [file1.path]); + service.openExternalProject({ + projectFileName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path]), + options: {} + }); + service.checkNumberOfProjects({ externalProjects: 1 }); + assert.isTrue(service.externalProjects[0].languageServiceEnabled, "language service should be enabled"); - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + service.openExternalProject({ + projectFileName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path, f2.path]), + options: {} }); + service.checkNumberOfProjects({ externalProjects: 1 }); + assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 2"); + }); - it("can update external project when set of root files was not changed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "m"` + describe("deleting config file opened from the external project works", () => { + function verifyDeletingConfigFile(lazyConfiguredProjectsFromExternalProject: boolean) { + const site = { + path: "/user/someuser/project/js/site.js", + content: "" }; - const file2 = { - path: "/a/b/f2.ts", - content: "export let y = 1" + const configFile = { + path: "/user/someuser/project/tsconfig.json", + content: "{}" }; - const file3 = { - path: "/a/m.ts", - content: "export let y = 1" + const projectFileName = "/user/someuser/project/WebApplication6.csproj"; + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, site, configFile]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + + const externalProject: ts.projectSystem.protocol.ExternalProject = { + projectFileName, + rootFiles: [ts.projectSystem.toExternalFile(site.path), ts.projectSystem.toExternalFile(configFile.path)], + options: { allowJs: false }, + typeAcquisition: { include: [] } }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); + projectService.openExternalProjects([externalProject]); + + let knownProjects = projectService.synchronizeProjectList([]); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 }); + + const configProject = ts.projectSystem.configuredProjectAt(projectService, 0); + ts.projectSystem.checkProjectActualFiles(configProject, lazyConfiguredProjectsFromExternalProject ? + ts.emptyArray : // Since no files opened from this project, its not loaded + [configFile.path]); - projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.NodeJs }, rootFiles: toExternalFiles([file1.path, file2.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); - checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]); + host.deleteFile(configFile.path); - projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.Classic }, rootFiles: toExternalFiles([file1.path, file2.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); - checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]); + knownProjects = projectService.synchronizeProjectList(ts.map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039 + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 }); + + externalProject.rootFiles.length = 1; + projectService.openExternalProjects([externalProject]); + + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); + ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [site.path, ts.projectSystem.libFile.path]); + } + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ true); }); + }); - it("language service disabled state is updated in external projects", () => { + describe("correctly handling add/remove tsconfig - 1", () => { + function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { const f1 = { - path: "/a/app.js", - content: "var x = 1" + path: "/a/b/app.ts", + content: "let x = 1;" }; const f2 = { - path: "/a/largefile.js", + path: "/a/b/lib.ts", content: "" }; - const host = createServerHost([f1, f2]); - const originalGetFileSize = host.getFileSize; - host.getFileSize = (filePath: string) => - filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); - - const service = createProjectService(host); - const projectFileName = "/a/proj.csproj"; + const tsconfig = { + path: "/a/b/tsconfig.json", + content: "" + }; + const host = ts.projectSystem.createServerHost([f1, f2]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path, f2.path]), + // open external project + const projectName = "/a/b/proj1"; + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path, f2.path]), options: {} }); - service.checkNumberOfProjects({ externalProjects: 1 }); - assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 1"); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path]), + // rename lib.ts to tsconfig.json + host.renameFile(f2.path, tsconfig.path); + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path, tsconfig.path]), options: {} }); - service.checkNumberOfProjects({ externalProjects: 1 }); - assert.isTrue(service.externalProjects[0].languageServiceEnabled, "language service should be enabled"); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + if (lazyConfiguredProjectsFromExternalProject) { + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), ts.emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path, f2.path]), + // rename tsconfig.json back to lib.ts + host.renameFile(tsconfig.path, f2.path); + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path, f2.path]), options: {} }); - service.checkNumberOfProjects({ externalProjects: 1 }); - assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 2"); - }); - describe("deleting config file opened from the external project works", () => { - function verifyDeletingConfigFile(lazyConfiguredProjectsFromExternalProject: boolean) { - const site = { - path: "/user/someuser/project/js/site.js", - content: "" - }; - const configFile = { - path: "/user/someuser/project/tsconfig.json", - content: "{}" - }; - const projectFileName = "/user/someuser/project/WebApplication6.csproj"; - const host = createServerHost([libFile, site, configFile]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - - const externalProject: protocol.ExternalProject = { - projectFileName, - rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)], - options: { allowJs: false }, - typeAcquisition: { include: [] } - }; - - projectService.openExternalProjects([externalProject]); - - let knownProjects = projectService.synchronizeProjectList([]); - checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 }); - - const configProject = configuredProjectAt(projectService, 0); - checkProjectActualFiles(configProject, lazyConfiguredProjectsFromExternalProject ? - emptyArray : // Since no files opened from this project, its not loaded - [configFile.path]); - - host.deleteFile(configFile.path); - - knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039 - checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 }); - - externalProject.rootFiles.length = 1; - projectService.openExternalProjects([externalProject]); - - checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); - checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); - } - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); + } + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); }); - - describe("correctly handling add/remove tsconfig - 1", () => { - function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const f2 = { - path: "/a/b/lib.ts", - content: "" - }; - const tsconfig = { - path: "/a/b/tsconfig.json", - content: "" - }; - const host = createServerHost([f1, f2]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - - // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, f2.path]), - options: {} - }); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); - - // rename lib.ts to tsconfig.json - host.renameFile(f2.path, tsconfig.path); - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, tsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); - - // rename tsconfig.json back to lib.ts - host.renameFile(tsconfig.path, f2.path); - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, f2.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); - } - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); - }); - - describe("correctly handling add/remove tsconfig - 2", () => { - function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const cLib = { - path: "/a/b/c/lib.ts", - content: "" - }; - const cTsconfig = { - path: "/a/b/c/tsconfig.json", - content: "{}" - }; - const dLib = { - path: "/a/b/d/lib.ts", - content: "" - }; - const dTsconfig = { - path: "/a/b/d/tsconfig.json", - content: "{}" - }; - const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - - // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - - // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 2 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); - checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - - // remove one config file - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, dTsconfig.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]); - - // remove second config file - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path]), - options: {} - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - - // open two config files - // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, - rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), - options: {} - }); - projectService.checkNumberOfProjects({ configuredProjects: 2 }); - if (lazyConfiguredProjectsFromExternalProject) { - checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed - checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); - } - checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); - checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - - // close all projects - no projects should be opened - projectService.closeExternalProject(projectName); - projectService.checkNumberOfProjects({}); - } - - it("when lazyConfiguredProjectsFromExternalProject not set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); - }); - it("when lazyConfiguredProjectsFromExternalProject is set", () => { - verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); - }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); }); + }); - it("correctly handles changes in lib section of config file", () => { - const libES5 = { - path: "/compiler/lib.es5.d.ts", - content: "declare const eval: any" + describe("correctly handling add/remove tsconfig - 2", () => { + function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1;" }; - const libES2015Promise = { - path: "/compiler/lib.es2015.promise.d.ts", - content: "declare class Promise {}" + const cLib = { + path: "/a/b/c/lib.ts", + content: "" }; - const app = { - path: "/src/app.ts", - content: "var x: Promise;" + const cTsconfig = { + path: "/a/b/c/tsconfig.json", + content: "{}" }; - const config1 = { - path: "/src/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5" - ] - } - }) + const dLib = { + path: "/a/b/d/lib.ts", + content: "" }; - const config2 = { - path: config1.path, - content: JSON.stringify( - { - compilerOptions: { - module: "commonjs", - target: "es5", - noImplicitAny: true, - sourceMap: false, - lib: [ - "es5", - "es2015.promise" - ] - } - }) + const dTsconfig = { + path: "/a/b/d/tsconfig.json", + content: "{}" }; - const host = createServerHost([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); - const projectService = createProjectService(host); - projectService.openClientFile(app.path); + const host = ts.projectSystem.createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, app.path, config1.path]); - - host.writeFile(config2.path, config2.content); - host.checkTimeoutQueueLengthAndRun(2); + // open external project + const projectName = "/a/b/proj1"; + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path]), + options: {} + }); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, libES2015Promise.path, app.path, config2.path]); - }); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - it("should handle non-existing directories in config file", () => { - const f = { - path: "/a/src/app.ts", - content: "let x = 1;" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: {}, - include: [ - "src/**/*", - "notexistingfolder/*" - ] - }) - }; - const host = createServerHost([f, config]); - const projectService = createProjectService(host); - projectService.openClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const project = projectService.configuredProjects.get(config.path)!; - assert.isTrue(project.hasOpenRef()); // f + // add two config file as root files + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), + options: {} + }); + projectService.checkNumberOfProjects({ configuredProjects: 2 }); + if (lazyConfiguredProjectsFromExternalProject) { + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), ts.emptyArray); // Configured project created but not loaded till actually needed + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 1), ts.emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); - projectService.closeClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config.path), project); - assert.isFalse(project.hasOpenRef()); // No files - assert.isFalse(project.isClosed()); + // remove one config file + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path, dTsconfig.path]), + options: {} + }); - projectService.openClientFile(f.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config.path), project); - assert.isTrue(project.hasOpenRef()); // f - assert.isFalse(project.isClosed()); - }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]); - it("handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({}) - }; - const projectFileName = "/a/b/project.csproj"; - const host = createServerHost([f1, config]); - const service = createProjectService(host); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } }); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path, config.path]), + // remove second config file + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path]), options: {} - } as protocol.ExternalProject); - service.checkNumberOfProjects({ configuredProjects: 1 }); - const project = service.configuredProjects.get(config.path)!; - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded - checkProjectActualFiles(project, emptyArray); - - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); - assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded - checkProjectActualFiles(project, [config.path, f1.path]); + }); - service.closeExternalProject(projectFileName); - service.checkNumberOfProjects({}); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); - service.openExternalProject({ - projectFileName, - rootFiles: toExternalFiles([f1.path, config.path]), + // open two config files + // add two config file as root files + projectService.openExternalProject({ + projectFileName: projectName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), options: {} - } as protocol.ExternalProject); - service.checkNumberOfProjects({ configuredProjects: 1 }); - const project2 = service.configuredProjects.get(config.path)!; - assert.equal(project2.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded - checkProjectActualFiles(project2, [config.path, f1.path]); + }); + projectService.checkNumberOfProjects({ configuredProjects: 2 }); + if (lazyConfiguredProjectsFromExternalProject) { + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), ts.emptyArray); // Configured project created but not loaded till actually needed + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 1), ts.emptyArray); // Configured project created but not loaded till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); + } + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); + + // close all projects - no projects should be opened + projectService.closeExternalProject(projectName); + projectService.checkNumberOfProjects({}); + } + + it("when lazyConfiguredProjectsFromExternalProject not set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); + }); + it("when lazyConfiguredProjectsFromExternalProject is set", () => { + verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true); }); + }); - it("handles creation of external project with jsconfig before jsconfig creation watcher is invoked", () => { - const projectFileName = `${tscWatch.projectRoot}/WebApplication36.csproj`; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const files = [libFile, tsconfig]; - const host = createServerHost(files); - const service = createProjectService(host); + it("correctly handles changes in lib section of config file", () => { + const libES5 = { + path: "/compiler/lib.es5.d.ts", + content: "declare const eval: any" + }; + const libES2015Promise = { + path: "/compiler/lib.es2015.promise.d.ts", + content: "declare class Promise {}" + }; + const app = { + path: "/src/app.ts", + content: "var x: Promise;" + }; + const config1 = { + path: "/src/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5" + ] + } + }) + }; + const config2 = { + path: config1.path, + content: JSON.stringify( + { + compilerOptions: { + module: "commonjs", + target: "es5", + noImplicitAny: true, + sourceMap: false, + lib: [ + "es5", + "es2015.promise" + ] + } + }) + }; + const host = ts.projectSystem.createServerHost([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(app.path); - // Create external project - service.openExternalProjects([{ - projectFileName, - rootFiles: [{ fileName: tsconfig.path }], - options: { allowJs: false } - }]); - checkNumberOfProjects(service, { configuredProjects: 1 }); - const configProject = service.configuredProjects.get(tsconfig.path.toLowerCase())!; - checkProjectActualFiles(configProject, [tsconfig.path]); - - // write js file, open external project and open it for edit - const jsFilePath = `${tscWatch.projectRoot}/javascript.js`; - host.writeFile(jsFilePath, ""); - service.openExternalProjects([{ - projectFileName, - rootFiles: [{ fileName: tsconfig.path }, { fileName: jsFilePath }], - options: { allowJs: false } - }]); - service.applyChangesInOpenFiles(singleIterator({ fileName: jsFilePath, scriptKind: ScriptKind.JS, content: "" })); - checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); - checkProjectActualFiles(configProject, [tsconfig.path]); - const inferredProject = service.inferredProjects[0]; - checkProjectActualFiles(inferredProject, [libFile.path, jsFilePath]); - - // write jsconfig file - const jsConfig: File = { - path: `${tscWatch.projectRoot}/jsconfig.json`, - content: "{}" - }; - // Dont invoke file creation watchers as the repro suggests - host.ensureFileOrFolder(jsConfig, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [libES5.path, app.path, config1.path]); - // Open external project - service.openExternalProjects([{ - projectFileName, - rootFiles: [{ fileName: jsConfig.path }, { fileName: tsconfig.path }, { fileName: jsFilePath }], - options: { allowJs: false } - }]); - checkNumberOfProjects(service, { configuredProjects: 2, inferredProjects: 1 }); - checkProjectActualFiles(configProject, [tsconfig.path]); - assert.isTrue(inferredProject.isOrphan()); - const jsConfigProject = service.configuredProjects.get(jsConfig.path.toLowerCase())!; - checkProjectActualFiles(jsConfigProject, [jsConfig.path, jsFilePath, libFile.path]); - }); + host.writeFile(config2.path, config2.content); + host.checkTimeoutQueueLengthAndRun(2); - it("does not crash if external file does not exist", () => { - const f1 = { - path: "/a/file1.ts", - content: "let x = [1, 2];", - }; - const p1 = { - projectFileName: "/a/proj1.csproj", - rootFiles: [toExternalFile(f1.path)], - options: {}, - }; + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [libES5.path, libES2015Promise.path, app.path, config2.path]); + }); - const host = createServerHost([f1]); - host.require = (_initialPath, moduleName) => { - assert.equal(moduleName, "myplugin"); - return { - module: () => ({ - create(info: server.PluginCreateInfo) { - return Harness.LanguageService.makeDefaultProxy(info); - }, - getExternalFiles() { - return ["/does/not/exist"]; - }, - }), - error: undefined, - }; - }; - const session = createSession(host, { - globalPlugins: ["myplugin"], - }); - const projectService = session.getProjectService(); - // When the external project is opened, the graph will be updated, - // and in the process getExternalFiles() above will be called. - // Since the external file does not exist, there will not be a script - // info for it. If tsserver does not handle this case, the following - // method call will crash. - projectService.openExternalProject(p1); - checkNumberOfProjects(projectService, { externalProjects: 1 }); + it("should handle non-existing directories in config file", () => { + const f = { + path: "/a/src/app.ts", + content: "let x = 1;" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {}, + include: [ + "src/**/*", + "notexistingfolder/*" + ] + }) + }; + const host = ts.projectSystem.createServerHost([f, config]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const project = projectService.configuredProjects.get(config.path)!; + assert.isTrue(project.hasOpenRef()); // f + + projectService.closeClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isFalse(project.hasOpenRef()); // No files + assert.isFalse(project.isClosed()); + + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.isTrue(project.hasOpenRef()); // f + assert.isFalse(project.isClosed()); + }); + + it("handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({}) + }; + const projectFileName = "/a/b/project.csproj"; + const host = ts.projectSystem.createServerHost([f1, config]); + const service = ts.projectSystem.createProjectService(host); + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } }); + service.openExternalProject({ + projectFileName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path, config.path]), + options: {} + } as ts.projectSystem.protocol.ExternalProject); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project = service.configuredProjects.get(config.path)!; + assert.equal(project.pendingReload, ts.ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded + ts.projectSystem.checkProjectActualFiles(project, ts.emptyArray); + + service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); + assert.equal(project.pendingReload, ts.ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded + ts.projectSystem.checkProjectActualFiles(project, [config.path, f1.path]); + + service.closeExternalProject(projectFileName); + service.checkNumberOfProjects({}); + + service.openExternalProject({ + projectFileName, + rootFiles: ts.projectSystem.toExternalFiles([f1.path, config.path]), + options: {} + } as ts.projectSystem.protocol.ExternalProject); + service.checkNumberOfProjects({ configuredProjects: 1 }); + const project2 = service.configuredProjects.get(config.path)!; + assert.equal(project2.pendingReload, ts.ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded + ts.projectSystem.checkProjectActualFiles(project2, [config.path, f1.path]); + }); + + it("handles creation of external project with jsconfig before jsconfig creation watcher is invoked", () => { + const projectFileName = `${ts.tscWatch.projectRoot}/WebApplication36.csproj`; + const tsconfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const files = [ts.projectSystem.libFile, tsconfig]; + const host = ts.projectSystem.createServerHost(files); + const service = ts.projectSystem.createProjectService(host); + + // Create external project + service.openExternalProjects([{ + projectFileName, + rootFiles: [{ fileName: tsconfig.path }], + options: { allowJs: false } + }]); + ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1 }); + const configProject = service.configuredProjects.get(tsconfig.path.toLowerCase())!; + ts.projectSystem.checkProjectActualFiles(configProject, [tsconfig.path]); + + // write js file, open external project and open it for edit + const jsFilePath = `${ts.tscWatch.projectRoot}/javascript.js`; + host.writeFile(jsFilePath, ""); + service.openExternalProjects([{ + projectFileName, + rootFiles: [{ fileName: tsconfig.path }, { fileName: jsFilePath }], + options: { allowJs: false } + }]); + service.applyChangesInOpenFiles(ts.singleIterator({ fileName: jsFilePath, scriptKind: ts.ScriptKind.JS, content: "" })); + ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(configProject, [tsconfig.path]); + const inferredProject = service.inferredProjects[0]; + ts.projectSystem.checkProjectActualFiles(inferredProject, [ts.projectSystem.libFile.path, jsFilePath]); + + // write jsconfig file + const jsConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/jsconfig.json`, + content: "{}" + }; + // Dont invoke file creation watchers as the repro suggests + host.ensureFileOrFolder(jsConfig, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); + + // Open external project + service.openExternalProjects([{ + projectFileName, + rootFiles: [{ fileName: jsConfig.path }, { fileName: tsconfig.path }, { fileName: jsFilePath }], + options: { allowJs: false } + }]); + ts.projectSystem.checkNumberOfProjects(service, { configuredProjects: 2, inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(configProject, [tsconfig.path]); + assert.isTrue(inferredProject.isOrphan()); + const jsConfigProject = service.configuredProjects.get(jsConfig.path.toLowerCase())!; + ts.projectSystem.checkProjectActualFiles(jsConfigProject, [jsConfig.path, jsFilePath, ts.projectSystem.libFile.path]); + }); + + it("does not crash if external file does not exist", () => { + const f1 = { + path: "/a/file1.ts", + content: "let x = [1, 2];", + }; + const p1 = { + projectFileName: "/a/proj1.csproj", + rootFiles: [ts.projectSystem.toExternalFile(f1.path)], + options: {}, + }; + + const host = ts.projectSystem.createServerHost([f1]); + host.require = (_initialPath, moduleName) => { + assert.equal(moduleName, "myplugin"); + return { + module: () => ({ + create(info: ts.server.PluginCreateInfo) { + return Harness.LanguageService.makeDefaultProxy(info); + }, + getExternalFiles() { + return ["/does/not/exist"]; + }, + }), + error: undefined, + }; + }; + const session = ts.projectSystem.createSession(host, { + globalPlugins: ["myplugin"], }); + const projectService = session.getProjectService(); + // When the external project is opened, the graph will be updated, + // and in the process getExternalFiles() above will be called. + // Since the external file does not exist, there will not be a script + // info for it. If tsserver does not handle this case, the following + // method call will crash. + projectService.openExternalProject(p1); + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 }); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts index 2e834a43ad402..74551041f8f54 100644 --- a/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts +++ b/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts @@ -1,136 +1,136 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => { - it("works when extends is specified with a case insensitive file system", () => { - const rootPath = "/Users/username/dev/project"; - const file1: File = { - path: `${rootPath}/index.ts`, - content: 'import {x} from "file2";', - }; - const file2: File = { - path: `${rootPath}/file2.js`, - content: "", - }; - const file2Dts: File = { - path: `${rootPath}/types/file2/index.d.ts`, - content: "export declare const x: string;", - }; - const tsconfigAll: File = { - path: `${rootPath}/tsconfig.all.json`, - content: JSON.stringify({ - compilerOptions: { - baseUrl: ".", - paths: { file2: ["./file2.js"] }, - typeRoots: ["./types"], - forceConsistentCasingInFileNames: true, - }, - }), - }; - const tsconfig: File = { - path: `${rootPath}/tsconfig.json`, - content: JSON.stringify({ extends: "./tsconfig.all.json" }), - }; +import * as ts from "../../_namespaces/ts"; - const host = createServerHost([file1, file2, file2Dts, libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false }); - const session = createSession(host); +describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => { + it("works when extends is specified with a case insensitive file system", () => { + const rootPath = "/Users/username/dev/project"; + const file1: ts.projectSystem.File = { + path: `${rootPath}/index.ts`, + content: 'import {x} from "file2";', + }; + const file2: ts.projectSystem.File = { + path: `${rootPath}/file2.js`, + content: "", + }; + const file2Dts: ts.projectSystem.File = { + path: `${rootPath}/types/file2/index.d.ts`, + content: "export declare const x: string;", + }; + const tsconfigAll: ts.projectSystem.File = { + path: `${rootPath}/tsconfig.all.json`, + content: JSON.stringify({ + compilerOptions: { + baseUrl: ".", + paths: { file2: ["./file2.js"] }, + typeRoots: ["./types"], + forceConsistentCasingInFileNames: true, + }, + }), + }; + const tsconfig: ts.projectSystem.File = { + path: `${rootPath}/tsconfig.json`, + content: JSON.stringify({ extends: "./tsconfig.all.json" }), + }; - openFilesForSession([file1], session); - const projectService = session.getProjectService(); + const host = ts.projectSystem.createServerHost([file1, file2, file2Dts, ts.projectSystem.libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false }); + const session = ts.projectSystem.createSession(host); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + ts.projectSystem.openFilesForSession([file1], session); + const projectService = session.getProjectService(); - const diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); - }); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + + const diagnostics = ts.projectSystem.configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + }); - it("works when renaming file with different casing", () => { - const loggerFile: File = { - path: `${tscWatch.projectRoot}/Logger.ts`, - content: `export class logger { }` - }; - const anotherFile: File = { - path: `${tscWatch.projectRoot}/another.ts`, - content: `import { logger } from "./Logger"; new logger();` - }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { forceConsistentCasingInFileNames: true } - }) - }; + it("works when renaming file with different casing", () => { + const loggerFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/Logger.ts`, + content: `export class logger { }` + }; + const anotherFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/another.ts`, + content: `import { logger } from "./Logger"; new logger();` + }; + const tsconfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { forceConsistentCasingInFileNames: true } + }) + }; - const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([{ file: loggerFile, projectRootPath: tscWatch.projectRoot }], session); - verifyGetErrRequest({ session, host, files: [loggerFile] }); + const host = ts.projectSystem.createServerHost([loggerFile, anotherFile, tsconfig, ts.projectSystem.libFile, tsconfig]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([{ file: loggerFile, projectRootPath: ts.tscWatch.projectRoot }], session); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [loggerFile] }); - const newLoggerPath = loggerFile.path.toLowerCase(); - host.renameFile(loggerFile.path, newLoggerPath); - closeFilesForSession([loggerFile], session); - openFilesForSession([{ file: newLoggerPath, content: loggerFile.content, projectRootPath: tscWatch.projectRoot }], session); + const newLoggerPath = loggerFile.path.toLowerCase(); + host.renameFile(loggerFile.path, newLoggerPath); + ts.projectSystem.closeFilesForSession([loggerFile], session); + ts.projectSystem.openFilesForSession([{ file: newLoggerPath, content: loggerFile.content, projectRootPath: ts.tscWatch.projectRoot }], session); - // Apply edits for rename - openFilesForSession([{ file: anotherFile, projectRootPath: tscWatch.projectRoot }], session); - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: anotherFile.path, - textChanges: [{ - newText: "./logger", - ...protocolTextSpanFromSubstring( - anotherFile.content, - "./Logger" - ) - }] + // Apply edits for rename + ts.projectSystem.openFilesForSession([{ file: anotherFile, projectRootPath: ts.tscWatch.projectRoot }], session); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: anotherFile.path, + textChanges: [{ + newText: "./logger", + ...ts.projectSystem.protocolTextSpanFromSubstring( + anotherFile.content, + "./Logger" + ) }] - } - }); - - // Check errors in both files - verifyGetErrRequest({ session, host, files: [newLoggerPath, anotherFile] }); - baselineTsserverLogs("forceConsistentCasingInFileNames", "works when renaming file with different casing", session); + }] + } }); - it("when changing module name with different casing", () => { - const loggerFile: File = { - path: `${tscWatch.projectRoot}/Logger.ts`, - content: `export class logger { }` - }; - const anotherFile: File = { - path: `${tscWatch.projectRoot}/another.ts`, - content: `import { logger } from "./Logger"; new logger();` - }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { forceConsistentCasingInFileNames: true } - }) - }; + // Check errors in both files + ts.projectSystem.verifyGetErrRequest({ session, host, files: [newLoggerPath, anotherFile] }); + ts.projectSystem.baselineTsserverLogs("forceConsistentCasingInFileNames", "works when renaming file with different casing", session); + }); - const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([{ file: anotherFile, projectRootPath: tscWatch.projectRoot }], session); - verifyGetErrRequest({ session, host, files: [anotherFile] }); + it("when changing module name with different casing", () => { + const loggerFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/Logger.ts`, + content: `export class logger { }` + }; + const anotherFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/another.ts`, + content: `import { logger } from "./Logger"; new logger();` + }; + const tsconfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { forceConsistentCasingInFileNames: true } + }) + }; - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: anotherFile.path, - textChanges: [{ - newText: "./logger", - ...protocolTextSpanFromSubstring( - anotherFile.content, - "./Logger" - ) - }] - }] - } - }); + const host = ts.projectSystem.createServerHost([loggerFile, anotherFile, tsconfig, ts.projectSystem.libFile, tsconfig]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([{ file: anotherFile, projectRootPath: ts.tscWatch.projectRoot }], session); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [anotherFile] }); - // Check errors in both files - verifyGetErrRequest({ host, session, files: [anotherFile] }); - baselineTsserverLogs("forceConsistentCasingInFileNames", "when changing module name with different casing", session); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: anotherFile.path, + textChanges: [{ + newText: "./logger", + ...ts.projectSystem.protocolTextSpanFromSubstring( + anotherFile.content, + "./Logger" + ) + }] + }] + } }); + + // Check errors in both files + ts.projectSystem.verifyGetErrRequest({ host, session, files: [anotherFile] }); + ts.projectSystem.baselineTsserverLogs("forceConsistentCasingInFileNames", "when changing module name with different casing", session); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/formatSettings.ts b/src/testRunner/unittests/tsserver/formatSettings.ts index 2d09ed8a8a0eb..0fde7703d3e07 100644 --- a/src/testRunner/unittests/tsserver/formatSettings.ts +++ b/src/testRunner/unittests/tsserver/formatSettings.ts @@ -1,39 +1,39 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: format settings", () => { - it("can be set globally", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x;" - }; - const host = createServerHost([f1]); - const projectService = createProjectService(host); - projectService.openClientFile(f1.path); - - const defaultSettings = projectService.getFormatCodeOptions(f1.path as server.NormalizedPath); - - // set global settings - const newGlobalSettings1 = { ...defaultSettings, placeOpenBraceOnNewLineForControlBlocks: !defaultSettings.placeOpenBraceOnNewLineForControlBlocks }; - projectService.setHostConfiguration({ formatOptions: newGlobalSettings1 }); - - // get format options for file - should be equal to new global settings - const s1 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); - assert.deepEqual(s1, newGlobalSettings1, "file settings should be the same with global settings"); - - // set per file format options - const newPerFileSettings = { ...defaultSettings, insertSpaceAfterCommaDelimiter: !defaultSettings.insertSpaceAfterCommaDelimiter }; - projectService.setHostConfiguration({ formatOptions: newPerFileSettings, file: f1.path }); - - // get format options for file - should be equal to new per-file settings - const s2 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); - assert.deepEqual(s2, newPerFileSettings, "file settings should be the same with per-file settings"); - - // set new global settings - they should not affect ones that were set per-file - const newGlobalSettings2 = { ...defaultSettings, insertSpaceAfterSemicolonInForStatements: !defaultSettings.insertSpaceAfterSemicolonInForStatements }; - projectService.setHostConfiguration({ formatOptions: newGlobalSettings2 }); - - // get format options for file - should be equal to new per-file settings - const s3 = projectService.getFormatCodeOptions(server.toNormalizedPath(f1.path)); - assert.deepEqual(s3, newPerFileSettings, "file settings should still be the same with per-file settings"); - }); +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: format settings", () => { + it("can be set globally", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x;" + }; + const host = ts.projectSystem.createServerHost([f1]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(f1.path); + + const defaultSettings = projectService.getFormatCodeOptions(f1.path as ts.server.NormalizedPath); + + // set global settings + const newGlobalSettings1 = { ...defaultSettings, placeOpenBraceOnNewLineForControlBlocks: !defaultSettings.placeOpenBraceOnNewLineForControlBlocks }; + projectService.setHostConfiguration({ formatOptions: newGlobalSettings1 }); + + // get format options for file - should be equal to new global settings + const s1 = projectService.getFormatCodeOptions(ts.server.toNormalizedPath(f1.path)); + assert.deepEqual(s1, newGlobalSettings1, "file settings should be the same with global settings"); + + // set per file format options + const newPerFileSettings = { ...defaultSettings, insertSpaceAfterCommaDelimiter: !defaultSettings.insertSpaceAfterCommaDelimiter }; + projectService.setHostConfiguration({ formatOptions: newPerFileSettings, file: f1.path }); + + // get format options for file - should be equal to new per-file settings + const s2 = projectService.getFormatCodeOptions(ts.server.toNormalizedPath(f1.path)); + assert.deepEqual(s2, newPerFileSettings, "file settings should be the same with per-file settings"); + + // set new global settings - they should not affect ones that were set per-file + const newGlobalSettings2 = { ...defaultSettings, insertSpaceAfterSemicolonInForStatements: !defaultSettings.insertSpaceAfterSemicolonInForStatements }; + projectService.setHostConfiguration({ formatOptions: newGlobalSettings2 }); + + // get format options for file - should be equal to new per-file settings + const s3 = projectService.getFormatCodeOptions(ts.server.toNormalizedPath(f1.path)); + assert.deepEqual(s3, newPerFileSettings, "file settings should still be the same with per-file settings"); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/getApplicableRefactors.ts b/src/testRunner/unittests/tsserver/getApplicableRefactors.ts index bec4a65456051..65497bb2bc3eb 100644 --- a/src/testRunner/unittests/tsserver/getApplicableRefactors.ts +++ b/src/testRunner/unittests/tsserver/getApplicableRefactors.ts @@ -1,12 +1,12 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: getApplicableRefactors", () => { - it("works when taking position", () => { - const aTs: File = { path: "/a.ts", content: "" }; - const session = createSession(createServerHost([aTs])); - openFilesForSession([aTs], session); - const response = executeSessionRequest( - session, protocol.CommandTypes.GetApplicableRefactors, { file: aTs.path, line: 1, offset: 1 }); - assert.deepEqual(response, []); - }); +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: getApplicableRefactors", () => { + it("works when taking position", () => { + const aTs: ts.projectSystem.File = { path: "/a.ts", content: "" }; + const session = ts.projectSystem.createSession(ts.projectSystem.createServerHost([aTs])); + ts.projectSystem.openFilesForSession([aTs], session); + const response = ts.projectSystem.executeSessionRequest( + session, ts.projectSystem.protocol.CommandTypes.GetApplicableRefactors, { file: aTs.path, line: 1, offset: 1 }); + assert.deepEqual(response, []); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/getEditsForFileRename.ts b/src/testRunner/unittests/tsserver/getEditsForFileRename.ts index 5dcd3e96fdb1d..89e3d708beeb0 100644 --- a/src/testRunner/unittests/tsserver/getEditsForFileRename.ts +++ b/src/testRunner/unittests/tsserver/getEditsForFileRename.ts @@ -1,105 +1,105 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: getEditsForFileRename", () => { - it("works for host implementing 'resolveModuleNames' and 'getResolvedModuleWithFailedLookupLocationsFromCache'", () => { - const userTs: File = { - path: "/user.ts", - content: 'import { x } from "./old";', - }; - const newTs: File = { - path: "/new.ts", - content: "export const x = 0;", - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", - }; +import * as ts from "../../_namespaces/ts"; - const host = createServerHost([userTs, newTs, tsconfig]); - const projectService = createProjectService(host); - projectService.openClientFile(userTs.path); - const project = projectService.configuredProjects.get(tsconfig.path)!; +describe("unittests:: tsserver:: getEditsForFileRename", () => { + it("works for host implementing 'resolveModuleNames' and 'getResolvedModuleWithFailedLookupLocationsFromCache'", () => { + const userTs: ts.projectSystem.File = { + path: "/user.ts", + content: 'import { x } from "./old";', + }; + const newTs: ts.projectSystem.File = { + path: "/new.ts", + content: "export const x = 0;", + }; + const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: "{}", + }; - Debug.assert(!!project.resolveModuleNames); + const host = ts.projectSystem.createServerHost([userTs, newTs, tsconfig]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(userTs.path); + const project = projectService.configuredProjects.get(tsconfig.path)!; - const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", testFormatSettings, emptyOptions); - assert.deepEqual(edits, [{ - fileName: "/user.ts", - textChanges: [{ - span: textSpanFromSubstring(userTs.content, "./old"), - newText: "./new", - }], - }]); - }); + ts.Debug.assert(!!project.resolveModuleNames); + + const edits = project.getLanguageService().getEditsForFileRename("/old.ts", "/new.ts", ts.testFormatSettings, ts.emptyOptions); + assert.deepEqual(edits, [{ + fileName: "/user.ts", + textChanges: [{ + span: ts.projectSystem.textSpanFromSubstring(userTs.content, "./old"), + newText: "./new", + }], + }]); + }); - it("works with multiple projects", () => { - const aUserTs: File = { - path: "/a/user.ts", - content: 'import { x } from "./old";', - }; - const aOldTs: File = { - path: "/a/old.ts", - content: "export const x = 0;", - }; - const aTsconfig: File = { - path: "/a/tsconfig.json", - content: JSON.stringify({ files: ["./old.ts", "./user.ts"] }), - }; - const bUserTs: File = { - path: "/b/user.ts", - content: 'import { x } from "../a/old";', - }; - const bTsconfig: File = { - path: "/b/tsconfig.json", - content: "{}", - }; + it("works with multiple projects", () => { + const aUserTs: ts.projectSystem.File = { + path: "/a/user.ts", + content: 'import { x } from "./old";', + }; + const aOldTs: ts.projectSystem.File = { + path: "/a/old.ts", + content: "export const x = 0;", + }; + const aTsconfig: ts.projectSystem.File = { + path: "/a/tsconfig.json", + content: JSON.stringify({ files: ["./old.ts", "./user.ts"] }), + }; + const bUserTs: ts.projectSystem.File = { + path: "/b/user.ts", + content: 'import { x } from "../a/old";', + }; + const bTsconfig: ts.projectSystem.File = { + path: "/b/tsconfig.json", + content: "{}", + }; - const host = createServerHost([aUserTs, aOldTs, aTsconfig, bUserTs, bTsconfig]); - const session = createSession(host); - openFilesForSession([aUserTs, bUserTs], session); + const host = ts.projectSystem.createServerHost([aUserTs, aOldTs, aTsconfig, bUserTs, bTsconfig]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([aUserTs, bUserTs], session); - const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { - oldFilePath: aOldTs.path, - newFilePath: "/a/new.ts", - }); - assert.deepEqual(response, [ - { - fileName: aTsconfig.path, - textChanges: [{ ...protocolTextSpanFromSubstring(aTsconfig.content, "./old.ts"), newText: "new.ts" }], - }, - { - fileName: aUserTs.path, - textChanges: [{ ...protocolTextSpanFromSubstring(aUserTs.content, "./old"), newText: "./new" }], - }, - { - fileName: bUserTs.path, - textChanges: [{ ...protocolTextSpanFromSubstring(bUserTs.content, "../a/old"), newText: "../a/new" }], - }, - ]); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.CommandNames.GetEditsForFileRename, { + oldFilePath: aOldTs.path, + newFilePath: "/a/new.ts", }); + assert.deepEqual(response, [ + { + fileName: aTsconfig.path, + textChanges: [{ ...ts.projectSystem.protocolTextSpanFromSubstring(aTsconfig.content, "./old.ts"), newText: "new.ts" }], + }, + { + fileName: aUserTs.path, + textChanges: [{ ...ts.projectSystem.protocolTextSpanFromSubstring(aUserTs.content, "./old"), newText: "./new" }], + }, + { + fileName: bUserTs.path, + textChanges: [{ ...ts.projectSystem.protocolTextSpanFromSubstring(bUserTs.content, "../a/old"), newText: "../a/new" }], + }, + ]); + }); - it("works with file moved to inferred project", () => { - const aTs: File = { path: "/a.ts", content: 'import {} from "./b";' }; - const cTs: File = { path: "/c.ts", content: "export {};" }; - const tsconfig: File = { path: "/tsconfig.json", content: JSON.stringify({ files: ["./a.ts", "./b.ts"] }) }; + it("works with file moved to inferred project", () => { + const aTs: ts.projectSystem.File = { path: "/a.ts", content: 'import {} from "./b";' }; + const cTs: ts.projectSystem.File = { path: "/c.ts", content: "export {};" }; + const tsconfig: ts.projectSystem.File = { path: "/tsconfig.json", content: JSON.stringify({ files: ["./a.ts", "./b.ts"] }) }; - const host = createServerHost([aTs, cTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs, cTs], session); + const host = ts.projectSystem.createServerHost([aTs, cTs, tsconfig]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([aTs, cTs], session); - const response = executeSessionRequest(session, CommandNames.GetEditsForFileRename, { - oldFilePath: "/b.ts", - newFilePath: cTs.path, - }); - assert.deepEqual(response, [ - { - fileName: "/tsconfig.json", - textChanges: [{ ...protocolTextSpanFromSubstring(tsconfig.content, "./b.ts"), newText: "c.ts" }], - }, - { - fileName: "/a.ts", - textChanges: [{ ...protocolTextSpanFromSubstring(aTs.content, "./b"), newText: "./c" }], - }, - ]); + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.CommandNames.GetEditsForFileRename, { + oldFilePath: "/b.ts", + newFilePath: cTs.path, }); + assert.deepEqual(response, [ + { + fileName: "/tsconfig.json", + textChanges: [{ ...ts.projectSystem.protocolTextSpanFromSubstring(tsconfig.content, "./b.ts"), newText: "c.ts" }], + }, + { + fileName: "/a.ts", + textChanges: [{ ...ts.projectSystem.protocolTextSpanFromSubstring(aTs.content, "./b"), newText: "./c" }], + }, + ]); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/getExportReferences.ts b/src/testRunner/unittests/tsserver/getExportReferences.ts index 2312c7b823d07..f1009a8d56f2d 100644 --- a/src/testRunner/unittests/tsserver/getExportReferences.ts +++ b/src/testRunner/unittests/tsserver/getExportReferences.ts @@ -1,193 +1,193 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: getExportReferences", () => { - const exportVariable = "export const value = 0;"; - const exportArrayDestructured = "export const [valueA, valueB] = [0, 1];"; - const exportObjectDestructured = "export const { valueC, valueD: renamedD } = { valueC: 0, valueD: 1 };"; - const exportNestedObject = "export const { nest: [valueE, { valueF }] } = { nest: [0, { valueF: 1 }] };"; - - const mainTs: File = { - path: "/main.ts", - content: 'import { value, valueA, valueB, valueC, renamedD, valueE, valueF } from "./mod";', - }; - const modTs: File = { - path: "/mod.ts", - content: `${exportVariable} +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: getExportReferences", () => { + const exportVariable = "export const value = 0;"; + const exportArrayDestructured = "export const [valueA, valueB] = [0, 1];"; + const exportObjectDestructured = "export const { valueC, valueD: renamedD } = { valueC: 0, valueD: 1 };"; + const exportNestedObject = "export const { nest: [valueE, { valueF }] } = { nest: [0, { valueF: 1 }] };"; + + const mainTs: ts.projectSystem.File = { + path: "/main.ts", + content: 'import { value, valueA, valueB, valueC, renamedD, valueE, valueF } from "./mod";', + }; + const modTs: ts.projectSystem.File = { + path: "/mod.ts", + content: `${exportVariable} ${exportArrayDestructured} ${exportObjectDestructured} ${exportNestedObject} `, + }; + const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: "{}", + }; + + function makeSampleSession() { + const host = ts.projectSystem.createServerHost([mainTs, modTs, tsconfig]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([mainTs, modTs], session); + return session; + } + + const referenceMainTs = (mainTs: ts.projectSystem.File, text: string): ts.projectSystem.protocol.ReferencesResponseItem => + ts.projectSystem.makeReferenceItem({ + file: mainTs, + isDefinition: false, + isWriteAccess: true, + lineText: mainTs.content, + contextText: mainTs.content, + text, + }); + + const referenceModTs = ( + texts: { text: string; lineText: string; contextText?: string }, + override: Partial = {}, + ): ts.projectSystem.protocol.ReferencesResponseItem => + ts.projectSystem.makeReferenceItem({ + file: modTs, + isDefinition: true, + ...texts, + ...override, + }); + + it("should get const variable declaration references", () => { + const session = makeSampleSession(); + + const response = ts.projectSystem.executeSessionRequest( + session, + ts.projectSystem.protocol.CommandTypes.References, + ts.projectSystem.protocolFileLocationFromSubstring(modTs, "value"), + ); + + const expectResponse = { + refs: [ + referenceModTs({ text: "value", lineText: exportVariable, contextText: exportVariable }), + referenceMainTs(mainTs, "value"), + ], + symbolDisplayString: "const value: 0", + symbolName: "value", + symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(modTs.content, "value").offset, }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}", + + assert.deepEqual(response, expectResponse); + }); + + it("should get array destructuring declaration references", () => { + const session = makeSampleSession(); + const response = ts.projectSystem.executeSessionRequest( + session, + ts.projectSystem.protocol.CommandTypes.References, + ts.projectSystem.protocolFileLocationFromSubstring(modTs, "valueA"), + ); + + const expectResponse = { + refs: [ + referenceModTs({ + text: "valueA", + lineText: exportArrayDestructured, + contextText: exportArrayDestructured, + }), + referenceMainTs(mainTs, "valueA"), + ], + symbolDisplayString: "const valueA: number", + symbolName: "valueA", + symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(modTs.content, "valueA").offset, }; - function makeSampleSession() { - const host = createServerHost([mainTs, modTs, tsconfig]); - const session = createSession(host); - openFilesForSession([mainTs, modTs], session); - return session; - } - - const referenceMainTs = (mainTs: File, text: string): protocol.ReferencesResponseItem => - makeReferenceItem({ - file: mainTs, - isDefinition: false, - isWriteAccess: true, - lineText: mainTs.content, - contextText: mainTs.content, - text, - }); - - const referenceModTs = ( - texts: { text: string; lineText: string; contextText?: string }, - override: Partial = {}, - ): protocol.ReferencesResponseItem => - makeReferenceItem({ - file: modTs, - isDefinition: true, - ...texts, - ...override, - }); - - it("should get const variable declaration references", () => { - const session = makeSampleSession(); - - const response = executeSessionRequest( - session, - protocol.CommandTypes.References, - protocolFileLocationFromSubstring(modTs, "value"), - ); - - const expectResponse = { - refs: [ - referenceModTs({ text: "value", lineText: exportVariable, contextText: exportVariable }), - referenceMainTs(mainTs, "value"), - ], - symbolDisplayString: "const value: 0", - symbolName: "value", - symbolStartOffset: protocolLocationFromSubstring(modTs.content, "value").offset, - }; - - assert.deepEqual(response, expectResponse); - }); + assert.deepEqual(response, expectResponse); + }); - it("should get array destructuring declaration references", () => { - const session = makeSampleSession(); - const response = executeSessionRequest( - session, - protocol.CommandTypes.References, - protocolFileLocationFromSubstring(modTs, "valueA"), - ); - - const expectResponse = { - refs: [ - referenceModTs({ - text: "valueA", - lineText: exportArrayDestructured, - contextText: exportArrayDestructured, + it("should get object destructuring declaration references", () => { + const session = makeSampleSession(); + const response = ts.projectSystem.executeSessionRequest( + session, + ts.projectSystem.protocol.CommandTypes.References, + ts.projectSystem.protocolFileLocationFromSubstring(modTs, "valueC"), + ); + const expectResponse = { + refs: [ + referenceModTs({ + text: "valueC", + lineText: exportObjectDestructured, + contextText: exportObjectDestructured, + }), + referenceMainTs(mainTs, "valueC"), + referenceModTs( + { text: "valueC", lineText: exportObjectDestructured, contextText: "valueC: 0" }, + { + options: { index: 1 }, + isDefinition: false, + isWriteAccess: true, }), - referenceMainTs(mainTs, "valueA"), - ], - symbolDisplayString: "const valueA: number", - symbolName: "valueA", - symbolStartOffset: protocolLocationFromSubstring(modTs.content, "valueA").offset, - }; - - assert.deepEqual(response, expectResponse); - }); + ], + symbolDisplayString: "const valueC: number", + symbolName: "valueC", + symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(modTs.content, "valueC").offset, + }; - it("should get object destructuring declaration references", () => { - const session = makeSampleSession(); - const response = executeSessionRequest( - session, - protocol.CommandTypes.References, - protocolFileLocationFromSubstring(modTs, "valueC"), - ); - const expectResponse = { - refs: [ - referenceModTs({ - text: "valueC", - lineText: exportObjectDestructured, - contextText: exportObjectDestructured, - }), - referenceMainTs(mainTs, "valueC"), - referenceModTs( - { text: "valueC", lineText: exportObjectDestructured, contextText: "valueC: 0" }, - { - options: { index: 1 }, - isDefinition: false, - isWriteAccess: true, - }), - ], - symbolDisplayString: "const valueC: number", - symbolName: "valueC", - symbolStartOffset: protocolLocationFromSubstring(modTs.content, "valueC").offset, - }; - - assert.deepEqual(response, expectResponse); - }); + assert.deepEqual(response, expectResponse); + }); - it("should get object declaration references that renames destructured property", () => { - const session = makeSampleSession(); - const response = executeSessionRequest( - session, - protocol.CommandTypes.References, - protocolFileLocationFromSubstring(modTs, "renamedD"), - ); - - const expectResponse = { - refs: [ - referenceModTs({ - text: "renamedD", - lineText: exportObjectDestructured, - contextText: exportObjectDestructured, - }), - referenceMainTs(mainTs, "renamedD"), - ], - symbolDisplayString: "const renamedD: number", - symbolName: "renamedD", - symbolStartOffset: protocolLocationFromSubstring(modTs.content, "renamedD").offset, - }; - - assert.deepEqual(response, expectResponse); - }); + it("should get object declaration references that renames destructured property", () => { + const session = makeSampleSession(); + const response = ts.projectSystem.executeSessionRequest( + session, + ts.projectSystem.protocol.CommandTypes.References, + ts.projectSystem.protocolFileLocationFromSubstring(modTs, "renamedD"), + ); + + const expectResponse = { + refs: [ + referenceModTs({ + text: "renamedD", + lineText: exportObjectDestructured, + contextText: exportObjectDestructured, + }), + referenceMainTs(mainTs, "renamedD"), + ], + symbolDisplayString: "const renamedD: number", + symbolName: "renamedD", + symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(modTs.content, "renamedD").offset, + }; - it("should get nested object declaration references", () => { - const session = makeSampleSession(); - const response = executeSessionRequest( - session, - protocol.CommandTypes.References, - protocolFileLocationFromSubstring(modTs, "valueF"), - ); - - const expectResponse = { - refs: [ - referenceModTs({ + assert.deepEqual(response, expectResponse); + }); + + it("should get nested object declaration references", () => { + const session = makeSampleSession(); + const response = ts.projectSystem.executeSessionRequest( + session, + ts.projectSystem.protocol.CommandTypes.References, + ts.projectSystem.protocolFileLocationFromSubstring(modTs, "valueF"), + ); + + const expectResponse = { + refs: [ + referenceModTs({ + text: "valueF", + lineText: exportNestedObject, + contextText: exportNestedObject, + }), + referenceMainTs(mainTs, "valueF"), + referenceModTs( + { text: "valueF", lineText: exportNestedObject, - contextText: exportNestedObject, - }), - referenceMainTs(mainTs, "valueF"), - referenceModTs( - { - text: "valueF", - lineText: exportNestedObject, - contextText: "valueF: 1", - }, - { - options: { index: 1 }, - isDefinition: false, - isWriteAccess: true, - }, - ), - ], - symbolDisplayString: "const valueF: number", - symbolName: "valueF", - symbolStartOffset: protocolLocationFromSubstring(modTs.content, "valueF").offset, - }; - - assert.deepEqual(response, expectResponse); - }); + contextText: "valueF: 1", + }, + { + options: { index: 1 }, + isDefinition: false, + isWriteAccess: true, + }, + ), + ], + symbolDisplayString: "const valueF: number", + symbolName: "valueF", + symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(modTs.content, "valueF").offset, + }; + + assert.deepEqual(response, expectResponse); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/getFileReferences.ts b/src/testRunner/unittests/tsserver/getFileReferences.ts index 7e1f9cc8de49a..1db68ae8ea9ad 100644 --- a/src/testRunner/unittests/tsserver/getFileReferences.ts +++ b/src/testRunner/unittests/tsserver/getFileReferences.ts @@ -1,81 +1,81 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: getFileReferences", () => { - const importA = `import "./a";`; - const importCurlyFromA = `import {} from "./a";`; - const importAFromA = `import { a } from "/project/a";`; - const typeofImportA = `type T = typeof import("./a").a;`; +import * as ts from "../../_namespaces/ts"; - const aTs: File = { - path: "/project/a.ts", - content: "export const a = {};", - }; - const bTs: File = { - path: "/project/b.ts", - content: importA, - }; - const cTs: File = { - path: "/project/c.ts", - content: importCurlyFromA - }; - const dTs: File = { - path: "/project/d.ts", - content: [importAFromA, typeofImportA].join("\n") - }; - const tsconfig: File = { - path: "/project/tsconfig.json", - content: "{}", - }; +describe("unittests:: tsserver:: getFileReferences", () => { + const importA = `import "./a";`; + const importCurlyFromA = `import {} from "./a";`; + const importAFromA = `import { a } from "/project/a";`; + const typeofImportA = `type T = typeof import("./a").a;`; - function makeSampleSession() { - const host = createServerHost([aTs, bTs, cTs, dTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs, bTs, cTs, dTs], session); - return session; - } + const aTs: ts.projectSystem.File = { + path: "/project/a.ts", + content: "export const a = {};", + }; + const bTs: ts.projectSystem.File = { + path: "/project/b.ts", + content: importA, + }; + const cTs: ts.projectSystem.File = { + path: "/project/c.ts", + content: importCurlyFromA + }; + const dTs: ts.projectSystem.File = { + path: "/project/d.ts", + content: [importAFromA, typeofImportA].join("\n") + }; + const tsconfig: ts.projectSystem.File = { + path: "/project/tsconfig.json", + content: "{}", + }; - it("should get file references", () => { - const session = makeSampleSession(); + function makeSampleSession() { + const host = ts.projectSystem.createServerHost([aTs, bTs, cTs, dTs, tsconfig]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([aTs, bTs, cTs, dTs], session); + return session; + } - const response = executeSessionRequest( - session, - protocol.CommandTypes.FileReferences, - { file: aTs.path }, - ); + it("should get file references", () => { + const session = makeSampleSession(); - const expectResponse: protocol.FileReferencesResponseBody = { - refs: [ - makeReferenceItem({ file: bTs, text: "./a", lineText: importA, contextText: importA, isWriteAccess: false }), - makeReferenceItem({ file: cTs, text: "./a", lineText: importCurlyFromA, contextText: importCurlyFromA, isWriteAccess: false }), - makeReferenceItem({ file: dTs, text: "/project/a", lineText: importAFromA, contextText: importAFromA, isWriteAccess: false }), - makeReferenceItem({ file: dTs, text: "./a", lineText: typeofImportA, contextText: typeofImportA, isWriteAccess: false }), - ], - symbolName: `"${aTs.path}"`, - }; + const response = ts.projectSystem.executeSessionRequest( + session, + ts.projectSystem.protocol.CommandTypes.FileReferences, + { file: aTs.path }, + ); - assert.deepEqual(response, expectResponse); - }); + const expectResponse: ts.projectSystem.protocol.FileReferencesResponseBody = { + refs: [ + ts.projectSystem.makeReferenceItem({ file: bTs, text: "./a", lineText: importA, contextText: importA, isWriteAccess: false }), + ts.projectSystem.makeReferenceItem({ file: cTs, text: "./a", lineText: importCurlyFromA, contextText: importCurlyFromA, isWriteAccess: false }), + ts.projectSystem.makeReferenceItem({ file: dTs, text: "/project/a", lineText: importAFromA, contextText: importAFromA, isWriteAccess: false }), + ts.projectSystem.makeReferenceItem({ file: dTs, text: "./a", lineText: typeofImportA, contextText: typeofImportA, isWriteAccess: false }), + ], + symbolName: `"${aTs.path}"`, + }; + + assert.deepEqual(response, expectResponse); + }); - it("should skip lineText from file references", () => { - const session = makeSampleSession(); - session.getProjectService().setHostConfiguration({ preferences: { disableLineTextInReferences: true } }); + it("should skip lineText from file references", () => { + const session = makeSampleSession(); + session.getProjectService().setHostConfiguration({ preferences: { disableLineTextInReferences: true } }); - const response = executeSessionRequest( - session, - protocol.CommandTypes.FileReferences, - { file: aTs.path }, - ); + const response = ts.projectSystem.executeSessionRequest( + session, + ts.projectSystem.protocol.CommandTypes.FileReferences, + { file: aTs.path }, + ); - const expectResponse: protocol.FileReferencesResponseBody = { - refs: [ - makeReferenceItem({ file: bTs, text: "./a", lineText: undefined, contextText: importA, isWriteAccess: false }), - makeReferenceItem({ file: cTs, text: "./a", lineText: undefined, contextText: importCurlyFromA, isWriteAccess: false }), - makeReferenceItem({ file: dTs, text: "/project/a", lineText: undefined, contextText: importAFromA, isWriteAccess: false }), - makeReferenceItem({ file: dTs, text: "./a", lineText: undefined, contextText: typeofImportA, isWriteAccess: false }), - ], - symbolName: `"${aTs.path}"`, - }; + const expectResponse: ts.projectSystem.protocol.FileReferencesResponseBody = { + refs: [ + ts.projectSystem.makeReferenceItem({ file: bTs, text: "./a", lineText: undefined, contextText: importA, isWriteAccess: false }), + ts.projectSystem.makeReferenceItem({ file: cTs, text: "./a", lineText: undefined, contextText: importCurlyFromA, isWriteAccess: false }), + ts.projectSystem.makeReferenceItem({ file: dTs, text: "/project/a", lineText: undefined, contextText: importAFromA, isWriteAccess: false }), + ts.projectSystem.makeReferenceItem({ file: dTs, text: "./a", lineText: undefined, contextText: typeofImportA, isWriteAccess: false }), + ], + symbolName: `"${aTs.path}"`, + }; - assert.deepEqual(response, expectResponse); - }); + assert.deepEqual(response, expectResponse); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/helpers.ts b/src/testRunner/unittests/tsserver/helpers.ts index a51a558732142..4c56d91fd867c 100644 --- a/src/testRunner/unittests/tsserver/helpers.ts +++ b/src/testRunner/unittests/tsserver/helpers.ts @@ -1,30 +1,33 @@ -namespace ts.projectSystem { - export import TI = server.typingsInstaller; - export import protocol = server.protocol; - export import CommandNames = server.CommandNames; - - export import TestServerHost = TestFSWithWatch.TestServerHost; - export type File = TestFSWithWatch.File; - export type SymLink = TestFSWithWatch.SymLink; - export type Folder = TestFSWithWatch.Folder; - export import createServerHost = TestFSWithWatch.createServerHost; - export import checkArray = TestFSWithWatch.checkArray; - export import libFile = TestFSWithWatch.libFile; - - export import commonFile1 = tscWatch.commonFile1; - export import commonFile2 = tscWatch.commonFile2; - - const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; - export function mapOutputToJson(s: string) { - return convertToObject( - parseJsonText("json.json", s.replace(outputEventRegex, "")), - [] - ); - } +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; +import * as Utils from "../../_namespaces/Utils"; + +export import TI = ts.server.typingsInstaller; +export import protocol = ts.server.protocol; +export import CommandNames = ts.server.CommandNames; + +export import TestServerHost = ts.TestFSWithWatch.TestServerHost; +export type File = ts.TestFSWithWatch.File; +export type SymLink = ts.TestFSWithWatch.SymLink; +export type Folder = ts.TestFSWithWatch.Folder; +export import createServerHost = ts.TestFSWithWatch.createServerHost; +export import checkArray = ts.TestFSWithWatch.checkArray; +export import libFile = ts.TestFSWithWatch.libFile; + +export import commonFile1 = ts.tscWatch.commonFile1; +export import commonFile2 = ts.tscWatch.commonFile2; + +const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; +export function mapOutputToJson(s: string) { + return ts.convertToObject( + ts.parseJsonText("json.json", s.replace(outputEventRegex, "")), + [] + ); +} - export const customTypesMap = { - path: "/typesMap.json" as Path, - content: `{ +export const customTypesMap = { + path: "/typesMap.json" as ts.Path, + content: `{ "typesMap": { "jquery": { "match": "jquery(-(\\\\.?\\\\d+)+)?(\\\\.intellisense)?(\\\\.min)?\\\\.js$", @@ -44,899 +47,910 @@ namespace ts.projectSystem { "lodash": "lodash" } }` - }; +}; - export interface PostExecAction { - readonly success: boolean; - readonly callback: TI.RequestCompletedAction; - } - - export interface Logger extends server.Logger { - logs: string[]; - host?: TestServerHost; - } +export interface PostExecAction { + readonly success: boolean; + readonly callback: TI.RequestCompletedAction; +} - export function nullLogger(): Logger { - return { - close: noop, - hasLevel: returnFalse, - loggingEnabled: returnFalse, - perftrc: noop, - info: noop, - msg: noop, - startGroup: noop, - endGroup: noop, - getLogFileName: returnUndefined, - logs: [], - }; - } +export interface Logger extends ts.server.Logger { + logs: string[]; + host?: TestServerHost; +} - export function createHasErrorMessageLogger(): Logger { - return { - ...nullLogger(), - msg: (s, type) => Debug.fail(`Error: ${s}, type: ${type}`), - }; - } +export function nullLogger(): Logger { + return { + close: ts.noop, + hasLevel: ts.returnFalse, + loggingEnabled: ts.returnFalse, + perftrc: ts.noop, + info: ts.noop, + msg: ts.noop, + startGroup: ts.noop, + endGroup: ts.noop, + getLogFileName: ts.returnUndefined, + logs: [], + }; +} - function handleLoggerGroup(logger: Logger, host: TestServerHost | undefined): Logger { - let inGroup = false; - let firstInGroup = false; - let seq = 0; - logger.startGroup = () => { - inGroup = true; - firstInGroup = true; - }; - logger.endGroup = () => inGroup = false; - logger.host = host; - const originalInfo = logger.info; - logger.info = s => msg(s, server.Msg.Info, s => originalInfo.call(logger, s)); - return logger; - - function msg(s: string, type = server.Msg.Err, write: (s: string) => void) { - s = `[${nowString()}] ${s}`; - if (!inGroup || firstInGroup) s = padStringRight(type + " " + seq.toString(), " ") + s; - if (Debug.isDebugging) console.log(s); - write(s); - if (!inGroup) seq++; - } +export function createHasErrorMessageLogger(): Logger { + return { + ...nullLogger(), + msg: (s, type) => ts.Debug.fail(`Error: ${s}, type: ${type}`), + }; +} - function padStringRight(str: string, padding: string) { - return (str + padding).slice(0, padding.length); - } +function handleLoggerGroup(logger: Logger, host: TestServerHost | undefined): Logger { + let inGroup = false; + let firstInGroup = false; + let seq = 0; + logger.startGroup = () => { + inGroup = true; + firstInGroup = true; + }; + logger.endGroup = () => inGroup = false; + logger.host = host; + const originalInfo = logger.info; + logger.info = s => msg(s, ts.server.Msg.Info, s => originalInfo.call(logger, s)); + return logger; - function nowString() { - // E.g. "12:34:56.789" - const d = logger.host!.now(); - return `${padLeft(d.getUTCHours().toString(), 2, "0")}:${padLeft(d.getUTCMinutes().toString(), 2, "0")}:${padLeft(d.getUTCSeconds().toString(), 2, "0")}.${padLeft(d.getUTCMilliseconds().toString(), 3, "0")}`; - } + function msg(s: string, type = ts.server.Msg.Err, write: (s: string) => void) { + s = `[${nowString()}] ${s}`; + if (!inGroup || firstInGroup) s = padStringRight(type + " " + seq.toString(), " ") + s; + if (ts.Debug.isDebugging) console.log(s); + write(s); + if (!inGroup) seq++; } - export function createLoggerWritingToConsole(host: TestServerHost): Logger { - return handleLoggerGroup({ - ...nullLogger(), - hasLevel: returnTrue, - loggingEnabled: returnTrue, - perftrc: s => console.log(s), - info: s => console.log(s), - msg: (s, type) => console.log(`${type}:: ${s}`), - }, host); - } - - export function createLoggerWithInMemoryLogs(host: TestServerHost): Logger { - const logger = createHasErrorMessageLogger(); - return handleLoggerGroup({ - ...logger, - hasLevel: returnTrue, - loggingEnabled: returnTrue, - info: s => logger.logs.push( - s.replace(/Elapsed::?\s*\d+(?:\.\d+)?ms/g, "Elapsed:: *ms") - .replace(/\"updateGraphDurationMs\"\:\d+(?:\.\d+)?/g, `"updateGraphDurationMs":*`) - .replace(/\"createAutoImportProviderProgramDurationMs\"\:\d+(?:\.\d+)?/g, `"createAutoImportProviderProgramDurationMs":*`) - .replace(`"version":"${version}"`, `"version":"FakeVersion"`) - .replace(/getCompletionData: Get current token: \d+(?:\.\d+)?/g, `getCompletionData: Get current token: *`) - .replace(/getCompletionData: Is inside comment: \d+(?:\.\d+)?/g, `getCompletionData: Is inside comment: *`) - .replace(/getCompletionData: Get previous token: \d+(?:\.\d+)?/g, `getCompletionData: Get previous token: *`) - .replace(/getCompletionsAtPosition: isCompletionListBlocker: \d+(?:\.\d+)?/g, `getCompletionsAtPosition: isCompletionListBlocker: *`) - .replace(/getCompletionData: Semantic work: \d+(?:\.\d+)?/g, `getCompletionData: Semantic work: *`) - .replace(/getCompletionsAtPosition: getCompletionEntriesFromSymbols: \d+(?:\.\d+)?/g, `getCompletionsAtPosition: getCompletionEntriesFromSymbols: *`) - .replace(/forEachExternalModuleToImportFrom autoImportProvider: \d+(?:\.\d+)?/g, `forEachExternalModuleToImportFrom autoImportProvider: *`) - .replace(/getExportInfoMap: done in \d+(?:\.\d+)?/g, `getExportInfoMap: done in *`) - .replace(/collectAutoImports: \d+(?:\.\d+)?/g, `collectAutoImports: *`) - .replace(/dependencies in \d+(?:\.\d+)?/g, `dependencies in *`) - .replace(/\"exportMapKey\"\:\s*\"[_$a-zA-Z][_$_$a-zA-Z0-9]*\|\d+\|/g, match => match.replace(/\|\d+\|/, `|*|`)) - ) - }, host); - } - - export function baselineTsserverLogs(scenario: string, subScenario: string, sessionOrService: { logger: Logger; }) { - Debug.assert(sessionOrService.logger.logs.length); // Ensure caller used in memory logger - Harness.Baseline.runBaseline(`tsserver/${scenario}/${subScenario.split(" ").join("-")}.js`, sessionOrService.logger.logs.join("\r\n")); - } - - export function appendAllScriptInfos(service: server.ProjectService, logs: string[]) { - logs.push(""); - logs.push(`ScriptInfos:`); - service.filenameToScriptInfo.forEach(info => logs.push(`path: ${info.path} fileName: ${info.fileName}`)); - logs.push(""); + function padStringRight(str: string, padding: string) { + return (str + padding).slice(0, padding.length); } - export function appendProjectFileText(project: server.Project, logs: string[]) { - logs.push(""); - logs.push(`Project: ${project.getProjectName()}`); - project.getCurrentProgram()?.getSourceFiles().forEach(f => { - logs.push(JSON.stringify({ fileName: f.fileName, version: f.version })); - logs.push(f.text); - logs.push(""); - }); - logs.push(""); + function nowString() { + // E.g. "12:34:56.789" + const d = logger.host!.now(); + return `${ts.padLeft(d.getUTCHours().toString(), 2, "0")}:${ts.padLeft(d.getUTCMinutes().toString(), 2, "0")}:${ts.padLeft(d.getUTCSeconds().toString(), 2, "0")}.${ts.padLeft(d.getUTCMilliseconds().toString(), 3, "0")}`; } +} - export class TestTypingsInstaller extends TI.TypingsInstaller implements server.ITypingsInstaller { - protected projectService!: server.ProjectService; - constructor( - readonly globalTypingsCacheLocation: string, - throttleLimit: number, - installTypingHost: server.ServerHost, - readonly typesRegistry = new Map>(), - log?: TI.Log) { - super(installTypingHost, globalTypingsCacheLocation, "/safeList.json" as Path, customTypesMap.path, throttleLimit, log); - } +export function createLoggerWritingToConsole(host: TestServerHost): Logger { + return handleLoggerGroup({ + ...nullLogger(), + hasLevel: ts.returnTrue, + loggingEnabled: ts.returnTrue, + perftrc: s => console.log(s), + info: s => console.log(s), + msg: (s, type) => console.log(`${type}:: ${s}`), + }, host); +} - protected postExecActions: PostExecAction[] = []; +export function createLoggerWithInMemoryLogs(host: TestServerHost): Logger { + const logger = createHasErrorMessageLogger(); + return handleLoggerGroup({ + ...logger, + hasLevel: ts.returnTrue, + loggingEnabled: ts.returnTrue, + info: s => logger.logs.push( + s.replace(/Elapsed::?\s*\d+(?:\.\d+)?ms/g, "Elapsed:: *ms") + .replace(/\"updateGraphDurationMs\"\:\d+(?:\.\d+)?/g, `"updateGraphDurationMs":*`) + .replace(/\"createAutoImportProviderProgramDurationMs\"\:\d+(?:\.\d+)?/g, `"createAutoImportProviderProgramDurationMs":*`) + .replace(`"version":"${ts.version}"`, `"version":"FakeVersion"`) + .replace(/getCompletionData: Get current token: \d+(?:\.\d+)?/g, `getCompletionData: Get current token: *`) + .replace(/getCompletionData: Is inside comment: \d+(?:\.\d+)?/g, `getCompletionData: Is inside comment: *`) + .replace(/getCompletionData: Get previous token: \d+(?:\.\d+)?/g, `getCompletionData: Get previous token: *`) + .replace(/getCompletionsAtPosition: isCompletionListBlocker: \d+(?:\.\d+)?/g, `getCompletionsAtPosition: isCompletionListBlocker: *`) + .replace(/getCompletionData: Semantic work: \d+(?:\.\d+)?/g, `getCompletionData: Semantic work: *`) + .replace(/getCompletionsAtPosition: getCompletionEntriesFromSymbols: \d+(?:\.\d+)?/g, `getCompletionsAtPosition: getCompletionEntriesFromSymbols: *`) + .replace(/forEachExternalModuleToImportFrom autoImportProvider: \d+(?:\.\d+)?/g, `forEachExternalModuleToImportFrom autoImportProvider: *`) + .replace(/getExportInfoMap: done in \d+(?:\.\d+)?/g, `getExportInfoMap: done in *`) + .replace(/collectAutoImports: \d+(?:\.\d+)?/g, `collectAutoImports: *`) + .replace(/dependencies in \d+(?:\.\d+)?/g, `dependencies in *`) + .replace(/\"exportMapKey\"\:\s*\"[_$a-zA-Z][_$_$a-zA-Z0-9]*\|\d+\|/g, match => match.replace(/\|\d+\|/, `|*|`)) + ) + }, host); +} - isKnownTypesPackageName = notImplemented; - installPackage = notImplemented; - inspectValue = notImplemented; +export function baselineTsserverLogs(scenario: string, subScenario: string, sessionOrService: { logger: Logger; }) { + ts.Debug.assert(sessionOrService.logger.logs.length); // Ensure caller used in memory logger + Harness.Baseline.runBaseline(`tsserver/${scenario}/${subScenario.split(" ").join("-")}.js`, sessionOrService.logger.logs.join("\r\n")); +} - executePendingCommands() { - const actionsToRun = this.postExecActions; - this.postExecActions = []; - for (const action of actionsToRun) { - action.callback(action.success); - } - } +export function appendAllScriptInfos(service: ts.server.ProjectService, logs: string[]) { + logs.push(""); + logs.push(`ScriptInfos:`); + service.filenameToScriptInfo.forEach(info => logs.push(`path: ${info.path} fileName: ${info.fileName}`)); + logs.push(""); +} - checkPendingCommands(expectedCount: number) { - assert.equal(this.postExecActions.length, expectedCount, `Expected ${expectedCount} post install actions`); - } +export function appendProjectFileText(project: ts.server.Project, logs: string[]) { + logs.push(""); + logs.push(`Project: ${project.getProjectName()}`); + project.getCurrentProgram()?.getSourceFiles().forEach(f => { + logs.push(JSON.stringify({ fileName: f.fileName, version: f.version })); + logs.push(f.text); + logs.push(""); + }); + logs.push(""); +} - onProjectClosed = noop; +export class TestTypingsInstaller extends TI.TypingsInstaller implements ts.server.ITypingsInstaller { + protected projectService!: ts.server.ProjectService; + constructor( + readonly globalTypingsCacheLocation: string, + throttleLimit: number, + installTypingHost: ts.server.ServerHost, + readonly typesRegistry = new ts.Map>(), + log?: TI.Log) { + super(installTypingHost, globalTypingsCacheLocation, "/safeList.json" as ts.Path, customTypesMap.path, throttleLimit, log); + } - attach(projectService: server.ProjectService) { - this.projectService = projectService; - } + protected postExecActions: PostExecAction[] = []; - getInstallTypingHost() { - return this.installTypingHost; - } + isKnownTypesPackageName = ts.notImplemented; + installPackage = ts.notImplemented; + inspectValue = ts.notImplemented; - installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - this.addPostExecAction("success", cb); + executePendingCommands() { + const actionsToRun = this.postExecActions; + this.postExecActions = []; + for (const action of actionsToRun) { + action.callback(action.success); } + } - sendResponse(response: server.SetTypings | server.InvalidateCachedTypings) { - this.projectService.updateTypingsForProject(response); - } + checkPendingCommands(expectedCount: number) { + assert.equal(this.postExecActions.length, expectedCount, `Expected ${expectedCount} post install actions`); + } - enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray) { - const request = server.createInstallTypingsRequest(project, typeAcquisition, unresolvedImports, this.globalTypingsCacheLocation); - this.install(request); - } + onProjectClosed = ts.noop; - addPostExecAction(stdout: string | string[], cb: TI.RequestCompletedAction) { - const out = isString(stdout) ? stdout : createNpmPackageJsonString(stdout); - const action: PostExecAction = { - success: !!out, - callback: cb - }; - this.postExecActions.push(action); - } + attach(projectService: ts.server.ProjectService) { + this.projectService = projectService; } - function createNpmPackageJsonString(installedTypings: string[]): string { - const dependencies: MapLike = {}; - for (const typing of installedTypings) { - dependencies[typing] = "1.0.0"; - } - return JSON.stringify({ dependencies }); - } - - export function createTypesRegistry(...list: string[]): ESMap> { - const versionMap = { - "latest": "1.3.0", - "ts2.0": "1.0.0", - "ts2.1": "1.0.0", - "ts2.2": "1.2.0", - "ts2.3": "1.3.0", - "ts2.4": "1.3.0", - "ts2.5": "1.3.0", - "ts2.6": "1.3.0", - "ts2.7": "1.3.0" - }; - const map = new Map>(); - for (const l of list) { - map.set(l, versionMap); - } - return map; + getInstallTypingHost() { + return this.installTypingHost; } - export function toExternalFile(fileName: string): protocol.ExternalFile { - return { fileName }; + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + this.addPostExecAction("success", cb); } - export function toExternalFiles(fileNames: string[]) { - return map(fileNames, toExternalFile); + sendResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings) { + this.projectService.updateTypingsForProject(response); } - export function fileStats(nonZeroStats: Partial): server.FileStats { - return { ts: 0, tsSize: 0, tsx: 0, tsxSize: 0, dts: 0, dtsSize: 0, js: 0, jsSize: 0, jsx: 0, jsxSize: 0, deferred: 0, deferredSize: 0, ...nonZeroStats }; + enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray) { + const request = ts.server.createInstallTypingsRequest(project, typeAcquisition, unresolvedImports, this.globalTypingsCacheLocation); + this.install(request); } - export class TestServerEventManager { - private events: server.ProjectServiceEvent[] = []; - readonly session: TestSession; - readonly service: server.ProjectService; - readonly host: TestServerHost; - constructor(files: File[], suppressDiagnosticEvents?: boolean) { - this.host = createServerHost(files); - this.session = createSession(this.host, { - canUseEvents: true, - eventHandler: event => this.events.push(event), - suppressDiagnosticEvents, - }); - this.service = this.session.getProjectService(); - } - - getEvents(): readonly server.ProjectServiceEvent[] { - const events = this.events; - this.events = []; - return events; - } - - getEvent(eventName: T["eventName"]): T["data"] { - let eventData: T["data"] | undefined; - filterMutate(this.events, e => { - if (e.eventName === eventName) { - if (eventData !== undefined) { - assert(false, "more than one event found"); - } - eventData = e.data; - return false; - } - return true; - }); - return Debug.checkDefined(eventData); - } - - hasZeroEvent(eventName: T["eventName"]) { - this.events.forEach(event => assert.notEqual(event.eventName, eventName)); - } - - assertProjectInfoTelemetryEvent(partial: Partial, configFile = "/tsconfig.json"): void { - assert.deepEqual(this.getEvent(server.ProjectInfoTelemetryEvent), { - projectId: sys.createSHA256Hash!(configFile), - fileStats: fileStats({ ts: 1 }), - compilerOptions: {}, - extends: false, - files: false, - include: false, - exclude: false, - compileOnSave: false, - typeAcquisition: { - enable: false, - exclude: false, - include: false, - }, - configFileName: "tsconfig.json", - projectType: "configured", - languageServiceEnabled: true, - version: ts.version, // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier - ...partial, - }); - } + addPostExecAction(stdout: string | string[], cb: TI.RequestCompletedAction) { + const out = ts.isString(stdout) ? stdout : createNpmPackageJsonString(stdout); + const action: PostExecAction = { + success: !!out, + callback: cb + }; + this.postExecActions.push(action); + } +} - assertOpenFileTelemetryEvent(info: server.OpenFileInfo): void { - assert.deepEqual(this.getEvent(server.OpenFileInfoTelemetryEvent), { info }); - } - assertNoOpenFilesTelemetryEvent(): void { - this.hasZeroEvent(server.OpenFileInfoTelemetryEvent); - } +function createNpmPackageJsonString(installedTypings: string[]): string { + const dependencies: ts.MapLike = {}; + for (const typing of installedTypings) { + dependencies[typing] = "1.0.0"; } + return JSON.stringify({ dependencies }); +} - export type TestSessionAndServiceHost = TestFSWithWatch.TestServerHostTrackingWrittenFiles & { - baselineHost(title: string): void; +export function createTypesRegistry(...list: string[]): ts.ESMap> { + const versionMap = { + "latest": "1.3.0", + "ts2.0": "1.0.0", + "ts2.1": "1.0.0", + "ts2.2": "1.2.0", + "ts2.3": "1.3.0", + "ts2.4": "1.3.0", + "ts2.5": "1.3.0", + "ts2.6": "1.3.0", + "ts2.7": "1.3.0" }; - function patchHostTimeouts( - inputHost: TestFSWithWatch.TestServerHostTrackingWrittenFiles, - session: TestSession | TestProjectService - ) { - const host = inputHost as TestSessionAndServiceHost; - const originalCheckTimeoutQueueLength = host.checkTimeoutQueueLength; - const originalRunQueuedTimeoutCallbacks = host.runQueuedTimeoutCallbacks; - const originalRunQueuedImmediateCallbacks = host.runQueuedImmediateCallbacks; - let hostDiff: ReturnType | undefined; - - host.checkTimeoutQueueLengthAndRun = checkTimeoutQueueLengthAndRun; - host.checkTimeoutQueueLength = checkTimeoutQueueLength; - host.runQueuedTimeoutCallbacks = runQueuedTimeoutCallbacks; - host.runQueuedImmediateCallbacks = runQueuedImmediateCallbacks; - host.baselineHost = baselineHost; - return host; - - function checkTimeoutQueueLengthAndRun(expected: number) { - host.baselineHost(`Before checking timeout queue length (${expected}) and running`); - originalCheckTimeoutQueueLength.call(host, expected); - originalRunQueuedTimeoutCallbacks.call(host); - host.baselineHost(`After checking timeout queue length (${expected}) and running`); - } + const map = new ts.Map>(); + for (const l of list) { + map.set(l, versionMap); + } + return map; +} - function checkTimeoutQueueLength(expected: number) { - host.baselineHost(`Checking timeout queue length: ${expected}`); - originalCheckTimeoutQueueLength.call(host, expected); - } +export function toExternalFile(fileName: string): protocol.ExternalFile { + return { fileName }; +} - function runQueuedTimeoutCallbacks(timeoutId?: number) { - host.baselineHost(`Before running timeout callback${timeoutId === undefined ? "s" : timeoutId}`); - originalRunQueuedTimeoutCallbacks.call(host, timeoutId); - host.baselineHost(`After running timeout callback${timeoutId === undefined ? "s" : timeoutId}`); - } +export function toExternalFiles(fileNames: string[]) { + return ts.map(fileNames, toExternalFile); +} - function runQueuedImmediateCallbacks(checkCount?: number) { - host.baselineHost(`Before running immediate callbacks${checkCount === undefined ? "" : ` and checking length (${checkCount})`}`); - originalRunQueuedImmediateCallbacks.call(host, checkCount); - host.baselineHost(`Before running immediate callbacks${checkCount === undefined ? "" : ` and checking length (${checkCount})`}`); - } +export function fileStats(nonZeroStats: Partial): ts.server.FileStats { + return { ts: 0, tsSize: 0, tsx: 0, tsxSize: 0, dts: 0, dtsSize: 0, js: 0, jsSize: 0, jsx: 0, jsxSize: 0, deferred: 0, deferredSize: 0, ...nonZeroStats }; +} - function baselineHost(title: string) { - if (!session.logger.hasLevel(server.LogLevel.verbose)) return; - session.logger.logs.push(title); - host.diff(session.logger.logs, hostDiff); - host.serializeWatches(session.logger.logs); - hostDiff = host.snap(); - host.writtenFiles.clear(); - } +export class TestServerEventManager { + private events: ts.server.ProjectServiceEvent[] = []; + readonly session: TestSession; + readonly service: ts.server.ProjectService; + readonly host: TestServerHost; + constructor(files: File[], suppressDiagnosticEvents?: boolean) { + this.host = createServerHost(files); + this.session = createSession(this.host, { + canUseEvents: true, + eventHandler: event => this.events.push(event), + suppressDiagnosticEvents, + }); + this.service = this.session.getProjectService(); } - export interface TestSessionOptions extends server.SessionOptions { - logger: Logger; + getEvents(): readonly ts.server.ProjectServiceEvent[] { + const events = this.events; + this.events = []; + return events; } - export class TestSession extends server.Session { - private seq = 0; - public events: protocol.Event[] = []; - public testhost: TestSessionAndServiceHost; - public logger: Logger; - - constructor(opts: TestSessionOptions) { - super(opts); - this.logger = opts.logger; - this.testhost = patchHostTimeouts( - TestFSWithWatch.changeToHostTrackingWrittenFiles(this.host as TestServerHost), - this - ); - } - - getProjectService() { - return this.projectService; - } - - public getSeq() { - return this.seq; - } - - public getNextSeq() { - return this.seq + 1; - } - - public executeCommand(request: protocol.Request) { - return this.baseline("response", super.executeCommand(this.baseline("request", request))); - } - - public executeCommandSeq(request: Partial) { - this.seq++; - request.seq = this.seq; - request.type = "request"; - return this.executeCommand(request as T); - } - - public event(body: T, eventName: string) { - this.events.push(server.toEvent(eventName, body)); - super.event(body, eventName); - } - - public clearMessages() { - clear(this.events); - this.testhost.clearOutput(); - } - - private baseline(type: "request" | "response", requestOrResult: T): T { - if (!this.logger.hasLevel(server.LogLevel.verbose)) return requestOrResult; - if (type === "request") this.logger.info(`request:${server.indent(JSON.stringify(requestOrResult, undefined, 2))}`); - this.testhost.baselineHost(type === "request" ? "Before request" : "After request"); - if (type === "response") this.logger.info(`response:${server.indent(JSON.stringify(requestOrResult, undefined, 2))}`); - return requestOrResult; - } + getEvent(eventName: T["eventName"]): T["data"] { + let eventData: T["data"] | undefined; + ts.filterMutate(this.events, e => { + if (e.eventName === eventName) { + if (eventData !== undefined) { + assert(false, "more than one event found"); + } + eventData = e.data; + return false; + } + return true; + }); + return ts.Debug.checkDefined(eventData); } - export function createSession(host: server.ServerHost, opts: Partial = {}) { - if (opts.typingsInstaller === undefined) { - opts.typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/ 5, host); - } - - if (opts.eventHandler !== undefined) { - opts.canUseEvents = true; - } - - const sessionOptions: TestSessionOptions = { - host, - cancellationToken: server.nullCancellationToken, - useSingleInferredProject: false, - useInferredProjectPerProjectRoot: false, - typingsInstaller: undefined!, // TODO: GH#18217 - byteLength: Utils.byteLength, - hrtime: process.hrtime, - logger: opts.logger || createHasErrorMessageLogger(), - canUseEvents: false - }; - - return new TestSession({ ...sessionOptions, ...opts }); + hasZeroEvent(eventName: T["eventName"]) { + this.events.forEach(event => assert.notEqual(event.eventName, eventName)); } - export function createSessionWithEventTracking(host: server.ServerHost, eventNames: T["eventName"] | T["eventName"][], opts: Partial = {}) { - const events: T[] = []; - const session = createSession(host, { - eventHandler: e => { - if (isArray(eventNames) ? eventNames.some(eventName => e.eventName === eventName) : eventNames === e.eventName) { - events.push(e as T); - } + assertProjectInfoTelemetryEvent(partial: Partial, configFile = "/tsconfig.json"): void { + assert.deepEqual(this.getEvent(ts.server.ProjectInfoTelemetryEvent), { + projectId: ts.sys.createSHA256Hash!(configFile), + fileStats: fileStats({ ts: 1 }), + compilerOptions: {}, + extends: false, + files: false, + include: false, + exclude: false, + compileOnSave: false, + typeAcquisition: { + enable: false, + exclude: false, + include: false, }, - ...opts + configFileName: "tsconfig.json", + projectType: "configured", + languageServiceEnabled: true, + version: ts.version, // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier + ...partial, }); - - return { session, events }; } - export function createSessionWithDefaultEventHandler(host: TestServerHost, eventNames: T["event"] | T["event"][], opts: Partial = {}) { - const session = createSession(host, { canUseEvents: true, ...opts }); + assertOpenFileTelemetryEvent(info: ts.server.OpenFileInfo): void { + assert.deepEqual(this.getEvent(ts.server.OpenFileInfoTelemetryEvent), { info }); + } + assertNoOpenFilesTelemetryEvent(): void { + this.hasZeroEvent(ts.server.OpenFileInfoTelemetryEvent); + } +} - return { - session, - getEvents, - clearEvents - }; +export type TestSessionAndServiceHost = ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles & { + baselineHost(title: string): void; +}; +function patchHostTimeouts( + inputHost: ts.TestFSWithWatch.TestServerHostTrackingWrittenFiles, + session: TestSession | TestProjectService +) { + const host = inputHost as TestSessionAndServiceHost; + const originalCheckTimeoutQueueLength = host.checkTimeoutQueueLength; + const originalRunQueuedTimeoutCallbacks = host.runQueuedTimeoutCallbacks; + const originalRunQueuedImmediateCallbacks = host.runQueuedImmediateCallbacks; + let hostDiff: ReturnType | undefined; + + host.checkTimeoutQueueLengthAndRun = checkTimeoutQueueLengthAndRun; + host.checkTimeoutQueueLength = checkTimeoutQueueLength; + host.runQueuedTimeoutCallbacks = runQueuedTimeoutCallbacks; + host.runQueuedImmediateCallbacks = runQueuedImmediateCallbacks; + host.baselineHost = baselineHost; + return host; + + function checkTimeoutQueueLengthAndRun(expected: number) { + host.baselineHost(`Before checking timeout queue length (${expected}) and running`); + originalCheckTimeoutQueueLength.call(host, expected); + originalRunQueuedTimeoutCallbacks.call(host); + host.baselineHost(`After checking timeout queue length (${expected}) and running`); + } + + function checkTimeoutQueueLength(expected: number) { + host.baselineHost(`Checking timeout queue length: ${expected}`); + originalCheckTimeoutQueueLength.call(host, expected); + } + + function runQueuedTimeoutCallbacks(timeoutId?: number) { + host.baselineHost(`Before running timeout callback${timeoutId === undefined ? "s" : timeoutId}`); + originalRunQueuedTimeoutCallbacks.call(host, timeoutId); + host.baselineHost(`After running timeout callback${timeoutId === undefined ? "s" : timeoutId}`); + } + + function runQueuedImmediateCallbacks(checkCount?: number) { + host.baselineHost(`Before running immediate callbacks${checkCount === undefined ? "" : ` and checking length (${checkCount})`}`); + originalRunQueuedImmediateCallbacks.call(host, checkCount); + host.baselineHost(`Before running immediate callbacks${checkCount === undefined ? "" : ` and checking length (${checkCount})`}`); + } + + function baselineHost(title: string) { + if (!session.logger.hasLevel(ts.server.LogLevel.verbose)) return; + session.logger.logs.push(title); + host.diff(session.logger.logs, hostDiff); + host.serializeWatches(session.logger.logs); + hostDiff = host.snap(); + host.writtenFiles.clear(); + } +} - function getEvents() { - return mapDefined(host.getOutput(), s => { - const e = mapOutputToJson(s); - return (isArray(eventNames) ? eventNames.some(eventName => e.event === eventName) : e.event === eventNames) ? e as T : undefined; - }); - } +export interface TestSessionOptions extends ts.server.SessionOptions { + logger: Logger; +} - function clearEvents() { - session.clearMessages(); - } +export class TestSession extends ts.server.Session { + private seq = 0; + public events: protocol.Event[] = []; + public testhost: TestSessionAndServiceHost; + public logger: Logger; + + constructor(opts: TestSessionOptions) { + super(opts); + this.logger = opts.logger; + this.testhost = patchHostTimeouts( + ts.TestFSWithWatch.changeToHostTrackingWrittenFiles(this.host as TestServerHost), + this + ); } - export interface TestProjectServiceOptions extends server.ProjectServiceOptions { - logger: Logger; - } - - export class TestProjectService extends server.ProjectService { - public testhost: TestSessionAndServiceHost; - constructor(host: TestServerHost, public logger: Logger, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, - typingsInstaller: server.ITypingsInstaller, opts: Partial = {}) { - super({ - host, - logger, - session: undefined, - cancellationToken, - useSingleInferredProject, - useInferredProjectPerProjectRoot: false, - typingsInstaller, - typesMapLocation: customTypesMap.path, - ...opts - }); - this.testhost = patchHostTimeouts( - TestFSWithWatch.changeToHostTrackingWrittenFiles(this.host as TestServerHost), - this - ); - this.testhost.baselineHost("Creating project service"); - } + getProjectService() { + return this.projectService; + } - checkNumberOfProjects(count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { - checkNumberOfProjects(this, count); - } + public getSeq() { + return this.seq; } - export function createProjectService(host: TestServerHost, options?: Partial) { - const cancellationToken = options?.cancellationToken || server.nullCancellationToken; - const logger = options?.logger || createHasErrorMessageLogger(); - const useSingleInferredProject = options?.useSingleInferredProject !== undefined ? options.useSingleInferredProject : false; - return new TestProjectService(host, logger, cancellationToken, useSingleInferredProject, options?.typingsInstaller || server.nullTypingsInstaller, options); + public getNextSeq() { + return this.seq + 1; } - export function checkNumberOfConfiguredProjects(projectService: server.ProjectService, expected: number) { - assert.equal(projectService.configuredProjects.size, expected, `expected ${expected} configured project(s)`); + public executeCommand(request: protocol.Request) { + return this.baseline("response", super.executeCommand(this.baseline("request", request))); } - export function checkNumberOfExternalProjects(projectService: server.ProjectService, expected: number) { - assert.equal(projectService.externalProjects.length, expected, `expected ${expected} external project(s)`); + public executeCommandSeq(request: Partial) { + this.seq++; + request.seq = this.seq; + request.type = "request"; + return this.executeCommand(request as T); } - export function checkNumberOfInferredProjects(projectService: server.ProjectService, expected: number) { - assert.equal(projectService.inferredProjects.length, expected, `expected ${expected} inferred project(s)`); + public event(body: T, eventName: string) { + this.events.push(ts.server.toEvent(eventName, body)); + super.event(body, eventName); } - export function checkNumberOfProjects(projectService: server.ProjectService, count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { - checkNumberOfConfiguredProjects(projectService, count.configuredProjects || 0); - checkNumberOfExternalProjects(projectService, count.externalProjects || 0); - checkNumberOfInferredProjects(projectService, count.inferredProjects || 0); + public clearMessages() { + ts.clear(this.events); + this.testhost.clearOutput(); } - export function configuredProjectAt(projectService: server.ProjectService, index: number) { - const values = projectService.configuredProjects.values(); - while (index > 0) { - const iterResult = values.next(); - if (iterResult.done) return Debug.fail("Expected a result."); - index--; - } - const iterResult = values.next(); - if (iterResult.done) return Debug.fail("Expected a result."); - return iterResult.value; + private baseline(type: "request" | "response", requestOrResult: T): T { + if (!this.logger.hasLevel(ts.server.LogLevel.verbose)) return requestOrResult; + if (type === "request") this.logger.info(`request:${ts.server.indent(JSON.stringify(requestOrResult, undefined, 2))}`); + this.testhost.baselineHost(type === "request" ? "Before request" : "After request"); + if (type === "response") this.logger.info(`response:${ts.server.indent(JSON.stringify(requestOrResult, undefined, 2))}`); + return requestOrResult; } +} - export function checkProjectActualFiles(project: server.Project, expectedFiles: readonly string[]) { - checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}:: actual files`, project.getFileNames(), expectedFiles); +export function createSession(host: ts.server.ServerHost, opts: Partial = {}) { + if (opts.typingsInstaller === undefined) { + opts.typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/ 5, host); } - export function checkProjectRootFiles(project: server.Project, expectedFiles: readonly string[]) { - checkArray(`${server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}::, rootFileNames`, project.getRootFiles(), expectedFiles); + if (opts.eventHandler !== undefined) { + opts.canUseEvents = true; } - export function mapCombinedPathsInAncestor(dir: string, path2: string, mapAncestor: (ancestor: string) => boolean) { - dir = normalizePath(dir); - const result: string[] = []; - forEachAncestorDirectory(dir, ancestor => { - if (mapAncestor(ancestor)) { - result.push(combinePaths(ancestor, path2)); + const sessionOptions: TestSessionOptions = { + host, + cancellationToken: ts.server.nullCancellationToken, + useSingleInferredProject: false, + useInferredProjectPerProjectRoot: false, + typingsInstaller: undefined!, // TODO: GH#18217 + byteLength: Utils.byteLength, + hrtime: process.hrtime, + logger: opts.logger || createHasErrorMessageLogger(), + canUseEvents: false + }; + + return new TestSession({ ...sessionOptions, ...opts }); +} + +export function createSessionWithEventTracking(host: ts.server.ServerHost, eventNames: T["eventName"] | T["eventName"][], opts: Partial = {}) { + const events: T[] = []; + const session = createSession(host, { + eventHandler: e => { + if (ts.isArray(eventNames) ? eventNames.some(eventName => e.eventName === eventName) : eventNames === e.eventName) { + events.push(e as T); } + }, + ...opts + }); + + return { session, events }; +} + +export function createSessionWithDefaultEventHandler(host: TestServerHost, eventNames: T["event"] | T["event"][], opts: Partial = {}) { + const session = createSession(host, { canUseEvents: true, ...opts }); + + return { + session, + getEvents, + clearEvents + }; + + function getEvents() { + return ts.mapDefined(host.getOutput(), s => { + const e = mapOutputToJson(s); + return (ts.isArray(eventNames) ? eventNames.some(eventName => e.event === eventName) : e.event === eventNames) ? e as T : undefined; }); - return result; } - export function getRootsToWatchWithAncestorDirectory(dir: string, path2: string) { - return mapCombinedPathsInAncestor(dir, path2, ancestor => ancestor.split(directorySeparator).length > 4); + function clearEvents() { + session.clearMessages(); } +} - export const nodeModules = "node_modules"; - export function getNodeModuleDirectories(dir: string) { - return getRootsToWatchWithAncestorDirectory(dir, nodeModules); - } +export interface TestProjectServiceOptions extends ts.server.ProjectServiceOptions { + logger: Logger; +} - export const nodeModulesAtTypes = "node_modules/@types"; - export function getTypeRootsFromLocation(currentDirectory: string) { - return getRootsToWatchWithAncestorDirectory(currentDirectory, nodeModulesAtTypes); +export class TestProjectService extends ts.server.ProjectService { + public testhost: TestSessionAndServiceHost; + constructor(host: TestServerHost, public logger: Logger, cancellationToken: ts.HostCancellationToken, useSingleInferredProject: boolean, + typingsInstaller: ts.server.ITypingsInstaller, opts: Partial = {}) { + super({ + host, + logger, + session: undefined, + cancellationToken, + useSingleInferredProject, + useInferredProjectPerProjectRoot: false, + typingsInstaller, + typesMapLocation: customTypesMap.path, + ...opts + }); + this.testhost = patchHostTimeouts( + ts.TestFSWithWatch.changeToHostTrackingWrittenFiles(this.host as TestServerHost), + this + ); + this.testhost.baselineHost("Creating project service"); } - export function getConfigFilesToWatch(folder: string) { - return [ - ...getRootsToWatchWithAncestorDirectory(folder, "tsconfig.json"), - ...getRootsToWatchWithAncestorDirectory(folder, "jsconfig.json") - ]; + checkNumberOfProjects(count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { + checkNumberOfProjects(this, count); } +} - export function protocolLocationFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.Location { - const start = nthIndexOf(str, substring, options ? options.index : 0); - Debug.assert(start !== -1); - return protocolToLocation(str)(start); - } +export function createProjectService(host: TestServerHost, options?: Partial) { + const cancellationToken = options?.cancellationToken || ts.server.nullCancellationToken; + const logger = options?.logger || createHasErrorMessageLogger(); + const useSingleInferredProject = options?.useSingleInferredProject !== undefined ? options.useSingleInferredProject : false; + return new TestProjectService(host, logger, cancellationToken, useSingleInferredProject, options?.typingsInstaller || ts.server.nullTypingsInstaller, options); +} - export function protocolToLocation(text: string): (pos: number) => protocol.Location { - const lineStarts = computeLineStarts(text); - return pos => { - const x = computeLineAndCharacterOfPosition(lineStarts, pos); - return { line: x.line + 1, offset: x.character + 1 }; - }; - } +export function checkNumberOfConfiguredProjects(projectService: ts.server.ProjectService, expected: number) { + assert.equal(projectService.configuredProjects.size, expected, `expected ${expected} configured project(s)`); +} - export function protocolTextSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.TextSpan { - const span = textSpanFromSubstring(str, substring, options); - const toLocation = protocolToLocation(str); - return { start: toLocation(span.start), end: toLocation(textSpanEnd(span)) }; - } - - export interface DocumentSpanFromSubstring { - file: File; - text: string; - options?: SpanFromSubstringOptions; - contextText?: string; - contextOptions?: SpanFromSubstringOptions; - } - export function protocolFileSpanFromSubstring({ file, text, options }: DocumentSpanFromSubstring): protocol.FileSpan { - return { file: file.path, ...protocolTextSpanFromSubstring(file.content, text, options) }; - } - - interface FileSpanWithContextFromSubString { - file: File; - text: string; - options?: SpanFromSubstringOptions; - contextText?: string; - contextOptions?: SpanFromSubstringOptions; - } - export function protocolFileSpanWithContextFromSubstring({ contextText, contextOptions, ...rest }: FileSpanWithContextFromSubString): protocol.FileSpanWithContext { - const result = protocolFileSpanFromSubstring(rest); - const contextSpan = contextText !== undefined ? - protocolFileSpanFromSubstring({ file: rest.file, text: contextText, options: contextOptions }) : - undefined; - return contextSpan ? - { - ...result, - contextStart: contextSpan.start, - contextEnd: contextSpan.end - } : - result; - } - - export interface ProtocolTextSpanWithContextFromString { - fileText: string; - text: string; - options?: SpanFromSubstringOptions; - contextText?: string; - contextOptions?: SpanFromSubstringOptions; - } - export function protocolTextSpanWithContextFromSubstring({ fileText, text, options, contextText, contextOptions }: ProtocolTextSpanWithContextFromString): protocol.TextSpanWithContext { - const span = textSpanFromSubstring(fileText, text, options); - const toLocation = protocolToLocation(fileText); - const contextSpan = contextText !== undefined ? textSpanFromSubstring(fileText, contextText, contextOptions) : undefined; - return { - start: toLocation(span.start), - end: toLocation(textSpanEnd(span)), - ...contextSpan && { - contextStart: toLocation(contextSpan.start), - contextEnd: toLocation(textSpanEnd(contextSpan)) - } - }; - } +export function checkNumberOfExternalProjects(projectService: ts.server.ProjectService, expected: number) { + assert.equal(projectService.externalProjects.length, expected, `expected ${expected} external project(s)`); +} - export interface ProtocolRenameSpanFromSubstring extends ProtocolTextSpanWithContextFromString { - prefixSuffixText?: { - readonly prefixText?: string; - readonly suffixText?: string; - }; - } - export function protocolRenameSpanFromSubstring({ prefixSuffixText, ...rest }: ProtocolRenameSpanFromSubstring): protocol.RenameTextSpan { - return { - ...protocolTextSpanWithContextFromSubstring(rest), - ...prefixSuffixText - }; - } +export function checkNumberOfInferredProjects(projectService: ts.server.ProjectService, expected: number) { + assert.equal(projectService.inferredProjects.length, expected, `expected ${expected} inferred project(s)`); +} - export function textSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): TextSpan { - const start = nthIndexOf(str, substring, options ? options.index : 0); - Debug.assert(start !== -1); - return createTextSpan(start, substring.length); - } +export function checkNumberOfProjects(projectService: ts.server.ProjectService, count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { + checkNumberOfConfiguredProjects(projectService, count.configuredProjects || 0); + checkNumberOfExternalProjects(projectService, count.externalProjects || 0); + checkNumberOfInferredProjects(projectService, count.inferredProjects || 0); +} - export function protocolFileLocationFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileLocationRequestArgs { - return { file: file.path, ...protocolLocationFromSubstring(file.content, substring, options) }; +export function configuredProjectAt(projectService: ts.server.ProjectService, index: number) { + const values = projectService.configuredProjects.values(); + while (index > 0) { + const iterResult = values.next(); + if (iterResult.done) return ts.Debug.fail("Expected a result."); + index--; } + const iterResult = values.next(); + if (iterResult.done) return ts.Debug.fail("Expected a result."); + return iterResult.value; +} - export interface SpanFromSubstringOptions { - readonly index: number; - } +export function checkProjectActualFiles(project: ts.server.Project, expectedFiles: readonly string[]) { + checkArray(`${ts.server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}:: actual files`, project.getFileNames(), expectedFiles); +} - function nthIndexOf(str: string, substr: string, n: number): number { - let index = -1; - for (; n >= 0; n--) { - index = str.indexOf(substr, index + 1); - if (index === -1) return -1; +export function checkProjectRootFiles(project: ts.server.Project, expectedFiles: readonly string[]) { + checkArray(`${ts.server.ProjectKind[project.projectKind]} project: ${project.getProjectName()}::, rootFileNames`, project.getRootFiles(), expectedFiles); +} + +export function mapCombinedPathsInAncestor(dir: string, path2: string, mapAncestor: (ancestor: string) => boolean) { + dir = ts.normalizePath(dir); + const result: string[] = []; + ts.forEachAncestorDirectory(dir, ancestor => { + if (mapAncestor(ancestor)) { + result.push(ts.combinePaths(ancestor, path2)); } - return index; - } + }); + return result; +} - /** - * Test server cancellation token used to mock host token cancellation requests. - * The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls - * should be made before canceling the token. The id of the request to cancel should be set with - * setRequestToCancel(); - */ - export class TestServerCancellationToken implements server.ServerCancellationToken { - private currentId: number | undefined = -1; - private requestToCancel = -1; - private isCancellationRequestedCount = 0; +export function getRootsToWatchWithAncestorDirectory(dir: string, path2: string) { + return mapCombinedPathsInAncestor(dir, path2, ancestor => ancestor.split(ts.directorySeparator).length > 4); +} - constructor(private cancelAfterRequest = 0) { - } +export const nodeModules = "node_modules"; +export function getNodeModuleDirectories(dir: string) { + return getRootsToWatchWithAncestorDirectory(dir, nodeModules); +} - setRequest(requestId: number) { - this.currentId = requestId; - } +export const nodeModulesAtTypes = "node_modules/@types"; +export function getTypeRootsFromLocation(currentDirectory: string) { + return getRootsToWatchWithAncestorDirectory(currentDirectory, nodeModulesAtTypes); +} - setRequestToCancel(requestId: number) { - this.resetToken(); - this.requestToCancel = requestId; - } +export function getConfigFilesToWatch(folder: string) { + return [ + ...getRootsToWatchWithAncestorDirectory(folder, "tsconfig.json"), + ...getRootsToWatchWithAncestorDirectory(folder, "jsconfig.json") + ]; +} - resetRequest(requestId: number) { - assert.equal(requestId, this.currentId, "unexpected request id in cancellation"); - this.currentId = undefined; - } +export function protocolLocationFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.Location { + const start = nthIndexOf(str, substring, options ? options.index : 0); + ts.Debug.assert(start !== -1); + return protocolToLocation(str)(start); +} - isCancellationRequested() { - this.isCancellationRequestedCount++; - // If the request id is the request to cancel and isCancellationRequestedCount - // has been met then cancel the request. Ex: cancel the request if it is a - // nav bar request & isCancellationRequested() has already been called three times. - return this.requestToCancel === this.currentId && this.isCancellationRequestedCount >= this.cancelAfterRequest; - } +export function protocolToLocation(text: string): (pos: number) => protocol.Location { + const lineStarts = ts.computeLineStarts(text); + return pos => { + const x = ts.computeLineAndCharacterOfPosition(lineStarts, pos); + return { line: x.line + 1, offset: x.character + 1 }; + }; +} + +export function protocolTextSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.TextSpan { + const span = textSpanFromSubstring(str, substring, options); + const toLocation = protocolToLocation(str); + return { start: toLocation(span.start), end: toLocation(ts.textSpanEnd(span)) }; +} - resetToken() { - this.currentId = -1; - this.isCancellationRequestedCount = 0; - this.requestToCancel = -1; +export interface DocumentSpanFromSubstring { + file: File; + text: string; + options?: SpanFromSubstringOptions; + contextText?: string; + contextOptions?: SpanFromSubstringOptions; +} +export function protocolFileSpanFromSubstring({ file, text, options }: DocumentSpanFromSubstring): protocol.FileSpan { + return { file: file.path, ...protocolTextSpanFromSubstring(file.content, text, options) }; +} + +interface FileSpanWithContextFromSubString { + file: File; + text: string; + options?: SpanFromSubstringOptions; + contextText?: string; + contextOptions?: SpanFromSubstringOptions; +} +export function protocolFileSpanWithContextFromSubstring({ contextText, contextOptions, ...rest }: FileSpanWithContextFromSubString): protocol.FileSpanWithContext { + const result = protocolFileSpanFromSubstring(rest); + const contextSpan = contextText !== undefined ? + protocolFileSpanFromSubstring({ file: rest.file, text: contextText, options: contextOptions }) : + undefined; + return contextSpan ? + { + ...result, + contextStart: contextSpan.start, + contextEnd: contextSpan.end + } : + result; +} + +export interface ProtocolTextSpanWithContextFromString { + fileText: string; + text: string; + options?: SpanFromSubstringOptions; + contextText?: string; + contextOptions?: SpanFromSubstringOptions; +} +export function protocolTextSpanWithContextFromSubstring({ fileText, text, options, contextText, contextOptions }: ProtocolTextSpanWithContextFromString): protocol.TextSpanWithContext { + const span = textSpanFromSubstring(fileText, text, options); + const toLocation = protocolToLocation(fileText); + const contextSpan = contextText !== undefined ? textSpanFromSubstring(fileText, contextText, contextOptions) : undefined; + return { + start: toLocation(span.start), + end: toLocation(ts.textSpanEnd(span)), + ...contextSpan && { + contextStart: toLocation(contextSpan.start), + contextEnd: toLocation(ts.textSpanEnd(contextSpan)) } - } + }; +} - export function makeSessionRequest(command: string, args: T): protocol.Request { - return { - seq: 0, - type: "request", - command, - arguments: args - }; - } +export interface ProtocolRenameSpanFromSubstring extends ProtocolTextSpanWithContextFromString { + prefixSuffixText?: { + readonly prefixText?: string; + readonly suffixText?: string; + }; +} +export function protocolRenameSpanFromSubstring({ prefixSuffixText, ...rest }: ProtocolRenameSpanFromSubstring): protocol.RenameTextSpan { + return { + ...protocolTextSpanWithContextFromSubstring(rest), + ...prefixSuffixText + }; +} - export function executeSessionRequest(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): TResponse["body"] { - return session.executeCommand(makeSessionRequest(command, args)).response as TResponse["body"]; - } +export function textSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): ts.TextSpan { + const start = nthIndexOf(str, substring, options ? options.index : 0); + ts.Debug.assert(start !== -1); + return ts.createTextSpan(start, substring.length); +} - export function executeSessionRequestNoResponse(session: server.Session, command: TRequest["command"], args: TRequest["arguments"]): void { - session.executeCommand(makeSessionRequest(command, args)); +export function protocolFileLocationFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileLocationRequestArgs { + return { file: file.path, ...protocolLocationFromSubstring(file.content, substring, options) }; +} + +export interface SpanFromSubstringOptions { + readonly index: number; +} + +function nthIndexOf(str: string, substr: string, n: number): number { + let index = -1; + for (; n >= 0; n--) { + index = str.indexOf(substr, index + 1); + if (index === -1) return -1; } + return index; +} - export function openFilesForSession(files: readonly (File | { readonly file: File | string, readonly projectRootPath: string, content?: string })[], session: server.Session): void { - for (const file of files) { - session.executeCommand(makeSessionRequest(CommandNames.Open, - "projectRootPath" in file ? { file: typeof file.file === "string" ? file.file : file.file.path, projectRootPath: file.projectRootPath } : { file: file.path })); // eslint-disable-line local/no-in-operator - } +/** + * Test server cancellation token used to mock host token cancellation requests. + * The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls + * should be made before canceling the token. The id of the request to cancel should be set with + * setRequestToCancel(); + */ +export class TestServerCancellationToken implements ts.server.ServerCancellationToken { + private currentId: number | undefined = -1; + private requestToCancel = -1; + private isCancellationRequestedCount = 0; + + constructor(private cancelAfterRequest = 0) { } - export function closeFilesForSession(files: readonly File[], session: server.Session): void { - for (const file of files) { - session.executeCommand(makeSessionRequest(CommandNames.Close, { file: file.path })); - } + setRequest(requestId: number) { + this.currentId = requestId; } - export interface MakeReferenceItem extends DocumentSpanFromSubstring { - isDefinition?: boolean; - isWriteAccess?: boolean; - lineText?: string; + setRequestToCancel(requestId: number) { + this.resetToken(); + this.requestToCancel = requestId; } - export function makeReferenceItem({ isDefinition, isWriteAccess, lineText, ...rest }: MakeReferenceItem): protocol.ReferencesResponseItem { - return { - ...protocolFileSpanWithContextFromSubstring(rest), - isDefinition, - isWriteAccess: isWriteAccess === undefined ? !!isDefinition : isWriteAccess, - lineText, - }; + resetRequest(requestId: number) { + assert.equal(requestId, this.currentId, "unexpected request id in cancellation"); + this.currentId = undefined; } - export interface VerifyGetErrRequestBase { - session: TestSession; - host: TestServerHost; - existingTimeouts?: number; + isCancellationRequested() { + this.isCancellationRequestedCount++; + // If the request id is the request to cancel and isCancellationRequestedCount + // has been met then cancel the request. Ex: cancel the request if it is a + // nav bar request & isCancellationRequested() has already been called three times. + return this.requestToCancel === this.currentId && this.isCancellationRequestedCount >= this.cancelAfterRequest; } - export interface VerifyGetErrRequest extends VerifyGetErrRequestBase { - files: readonly (string | File)[]; - skip?: CheckAllErrors["skip"]; + + resetToken() { + this.currentId = -1; + this.isCancellationRequestedCount = 0; + this.requestToCancel = -1; } - export function verifyGetErrRequest(request: VerifyGetErrRequest) { - const { session, files } = request; - session.executeCommandSeq({ - command: protocol.CommandTypes.Geterr, - arguments: { delay: 0, files: files.map(filePath) } - }); - checkAllErrors(request); +} + +export function makeSessionRequest(command: string, args: T): protocol.Request { + return { + seq: 0, + type: "request", + command, + arguments: args + }; +} + +export function executeSessionRequest(session: ts.server.Session, command: TRequest["command"], args: TRequest["arguments"]): TResponse["body"] { + return session.executeCommand(makeSessionRequest(command, args)).response as TResponse["body"]; +} + +export function executeSessionRequestNoResponse(session: ts.server.Session, command: TRequest["command"], args: TRequest["arguments"]): void { + session.executeCommand(makeSessionRequest(command, args)); +} + +export function openFilesForSession(files: readonly (File | { readonly file: File | string, readonly projectRootPath: string, content?: string })[], session: ts.server.Session): void { + for (const file of files) { + session.executeCommand(makeSessionRequest(CommandNames.Open, + "projectRootPath" in file ? { file: typeof file.file === "string" ? file.file : file.file.path, projectRootPath: file.projectRootPath } : { file: file.path })); // eslint-disable-line local/no-in-operator } +} - interface SkipErrors { semantic?: true; suggestion?: true } - export interface CheckAllErrors extends VerifyGetErrRequestBase { - files: readonly any[]; - skip?: readonly (SkipErrors | undefined)[]; +export function closeFilesForSession(files: readonly File[], session: ts.server.Session): void { + for (const file of files) { + session.executeCommand(makeSessionRequest(CommandNames.Close, { file: file.path })); } - function checkAllErrors({ session, host, existingTimeouts, files, skip }: CheckAllErrors) { - Debug.assert(session.logger.logs.length); - for (let i = 0; i < files.length; i++) { - if (existingTimeouts !== undefined) { - host.checkTimeoutQueueLength(existingTimeouts + 1); - host.runQueuedTimeoutCallbacks(host.getNextTimeoutId() - 1); - } - else { - host.checkTimeoutQueueLengthAndRun(1); - } - if (!skip?.[i]?.semantic) host.runQueuedImmediateCallbacks(1); - if (!skip?.[i]?.suggestion) host.runQueuedImmediateCallbacks(1); +} + +export interface MakeReferenceItem extends DocumentSpanFromSubstring { + isDefinition?: boolean; + isWriteAccess?: boolean; + lineText?: string; +} + +export function makeReferenceItem({ isDefinition, isWriteAccess, lineText, ...rest }: MakeReferenceItem): protocol.ReferencesResponseItem { + return { + ...protocolFileSpanWithContextFromSubstring(rest), + isDefinition, + isWriteAccess: isWriteAccess === undefined ? !!isDefinition : isWriteAccess, + lineText, + }; +} + +export interface VerifyGetErrRequestBase { + session: TestSession; + host: TestServerHost; + existingTimeouts?: number; +} +export interface VerifyGetErrRequest extends VerifyGetErrRequestBase { + files: readonly (string | File)[]; + skip?: CheckAllErrors["skip"]; +} +export function verifyGetErrRequest(request: VerifyGetErrRequest) { + const { session, files } = request; + session.executeCommandSeq({ + command: protocol.CommandTypes.Geterr, + arguments: { delay: 0, files: files.map(filePath) } + }); + checkAllErrors(request); +} + +interface SkipErrors { semantic?: true; suggestion?: true } +export interface CheckAllErrors extends VerifyGetErrRequestBase { + files: readonly any[]; + skip?: readonly (SkipErrors | undefined)[]; +} +function checkAllErrors({ session, host, existingTimeouts, files, skip }: CheckAllErrors) { + ts.Debug.assert(session.logger.logs.length); + for (let i = 0; i < files.length; i++) { + if (existingTimeouts !== undefined) { + host.checkTimeoutQueueLength(existingTimeouts + 1); + host.runQueuedTimeoutCallbacks(host.getNextTimeoutId() - 1); + } + else { + host.checkTimeoutQueueLengthAndRun(1); } + if (!skip?.[i]?.semantic) host.runQueuedImmediateCallbacks(1); + if (!skip?.[i]?.suggestion) host.runQueuedImmediateCallbacks(1); } +} - function filePath(file: string | File) { - return isString(file) ? file : file.path; - } +function filePath(file: string | File) { + return ts.isString(file) ? file : file.path; +} - function verifyErrorsUsingGeterr({scenario, subScenario, allFiles, openFiles, getErrRequest }: VerifyGetErrScenario) { - it("verifies the errors in open file", () => { - const host = createServerHost([...allFiles(), libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession(openFiles(), session); +function verifyErrorsUsingGeterr({scenario, subScenario, allFiles, openFiles, getErrRequest }: VerifyGetErrScenario) { + it("verifies the errors in open file", () => { + const host = createServerHost([...allFiles(), libFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + openFilesForSession(openFiles(), session); - verifyGetErrRequest({ session, host, files: getErrRequest() }); - baselineTsserverLogs(scenario, `${subScenario} getErr`, session); - }); - } + verifyGetErrRequest({ session, host, files: getErrRequest() }); + baselineTsserverLogs(scenario, `${subScenario} getErr`, session); + }); +} - function verifyErrorsUsingGeterrForProject({ scenario, subScenario, allFiles, openFiles, getErrForProjectRequest }: VerifyGetErrScenario) { - it("verifies the errors in projects", () => { - const host = createServerHost([...allFiles(), libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession(openFiles(), session); +function verifyErrorsUsingGeterrForProject({ scenario, subScenario, allFiles, openFiles, getErrForProjectRequest }: VerifyGetErrScenario) { + it("verifies the errors in projects", () => { + const host = createServerHost([...allFiles(), libFile]); + const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + openFilesForSession(openFiles(), session); - for (const expected of getErrForProjectRequest()) { - session.executeCommandSeq({ - command: protocol.CommandTypes.GeterrForProject, - arguments: { delay: 0, file: filePath(expected.project) } - }); - checkAllErrors({ session, host, files: expected.files }); - } - baselineTsserverLogs(scenario, `${subScenario} geterrForProject`, session); - }); - } + for (const expected of getErrForProjectRequest()) { + session.executeCommandSeq({ + command: protocol.CommandTypes.GeterrForProject, + arguments: { delay: 0, file: filePath(expected.project) } + }); + checkAllErrors({ session, host, files: expected.files }); + } + baselineTsserverLogs(scenario, `${subScenario} geterrForProject`, session); + }); +} - function verifyErrorsUsingSyncMethods({ scenario, subScenario, allFiles, openFiles, syncDiagnostics }: VerifyGetErrScenario) { - it("verifies the errors using sync commands", () => { - const host = createServerHost([...allFiles(), libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession(openFiles(), session); - for (const { file, project } of syncDiagnostics()) { - const reqArgs = { file: filePath(file), projectFileName: project && filePath(project) }; - session.executeCommandSeq({ - command: protocol.CommandTypes.SyntacticDiagnosticsSync, - arguments: reqArgs - }); - session.executeCommandSeq({ - command: protocol.CommandTypes.SemanticDiagnosticsSync, - arguments: reqArgs - }); - session.executeCommandSeq({ - command: protocol.CommandTypes.SuggestionDiagnosticsSync, - arguments: reqArgs - }); - } - baselineTsserverLogs(scenario, `${subScenario} gerErr with sync commands`, session); - }); - } +function verifyErrorsUsingSyncMethods({ scenario, subScenario, allFiles, openFiles, syncDiagnostics }: VerifyGetErrScenario) { + it("verifies the errors using sync commands", () => { + const host = createServerHost([...allFiles(), libFile]); + const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + openFilesForSession(openFiles(), session); + for (const { file, project } of syncDiagnostics()) { + const reqArgs = { file: filePath(file), projectFileName: project && filePath(project) }; + session.executeCommandSeq({ + command: protocol.CommandTypes.SyntacticDiagnosticsSync, + arguments: reqArgs + }); + session.executeCommandSeq({ + command: protocol.CommandTypes.SemanticDiagnosticsSync, + arguments: reqArgs + }); + session.executeCommandSeq({ + command: protocol.CommandTypes.SuggestionDiagnosticsSync, + arguments: reqArgs + }); + } + baselineTsserverLogs(scenario, `${subScenario} gerErr with sync commands`, session); + }); +} - export interface GetErrForProjectDiagnostics { - project: string | File; - files: readonly (string | File)[]; - skip?: CheckAllErrors["skip"]; - } - export interface SyncDiagnostics { - file: string | File; - project?: string | File; - } - export interface VerifyGetErrScenario { - scenario: string; - subScenario: string; - allFiles: () => readonly File[]; - openFiles: () => readonly File[]; - getErrRequest: () => VerifyGetErrRequest["files"]; - getErrForProjectRequest: () => readonly GetErrForProjectDiagnostics[]; - syncDiagnostics: () => readonly SyncDiagnostics[]; - } - export function verifyGetErrScenario(scenario: VerifyGetErrScenario) { - verifyErrorsUsingGeterr(scenario); - verifyErrorsUsingGeterrForProject(scenario); - verifyErrorsUsingSyncMethods(scenario); - } +export interface GetErrForProjectDiagnostics { + project: string | File; + files: readonly (string | File)[]; + skip?: CheckAllErrors["skip"]; +} +export interface SyncDiagnostics { + file: string | File; + project?: string | File; +} +export interface VerifyGetErrScenario { + scenario: string; + subScenario: string; + allFiles: () => readonly File[]; + openFiles: () => readonly File[]; + getErrRequest: () => VerifyGetErrRequest["files"]; + getErrForProjectRequest: () => readonly GetErrForProjectDiagnostics[]; + syncDiagnostics: () => readonly SyncDiagnostics[]; +} +export function verifyGetErrScenario(scenario: VerifyGetErrScenario) { + verifyErrorsUsingGeterr(scenario); + verifyErrorsUsingGeterrForProject(scenario); + verifyErrorsUsingSyncMethods(scenario); +} + +export function verifyDynamic(service: ts.server.ProjectService, path: string) { + const info = ts.Debug.checkDefined(service.filenameToScriptInfo.get(path), `Expected ${path} in :: ${JSON.stringify(ts.arrayFrom(service.filenameToScriptInfo.entries(), ([key, f]) => ({ key, fileName: f.fileName, path: f.path })))}`); + assert.isTrue(info.isDynamic); +} + +export function createHostWithSolutionBuild(files: readonly ts.TestFSWithWatch.FileOrFolderOrSymLink[], rootNames: readonly string[]) { + const host = ts.projectSystem.createServerHost(files); + // ts build should succeed + ts.tscWatch.ensureErrorFreeBuild(host, rootNames); + return host; } diff --git a/src/testRunner/unittests/tsserver/importHelpers.ts b/src/testRunner/unittests/tsserver/importHelpers.ts index b1ec5955eaae4..483ce6edda8fe 100644 --- a/src/testRunner/unittests/tsserver/importHelpers.ts +++ b/src/testRunner/unittests/tsserver/importHelpers.ts @@ -1,18 +1,18 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: import helpers", () => { - it("should not crash in tsserver", () => { - const f1 = { - path: "/a/app.ts", - content: "export async function foo() { return 100; }" - }; - const tslib = { - path: "/a/node_modules/tslib/index.d.ts", - content: "" - }; - const host = createServerHost([f1, tslib]); - const service = createProjectService(host); - service.openExternalProject({ projectFileName: "p", rootFiles: [toExternalFile(f1.path)], options: { importHelpers: true } }); - service.checkNumberOfProjects({ externalProjects: 1 }); - }); +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: import helpers", () => { + it("should not crash in tsserver", () => { + const f1 = { + path: "/a/app.ts", + content: "export async function foo() { return 100; }" + }; + const tslib = { + path: "/a/node_modules/tslib/index.d.ts", + content: "" + }; + const host = ts.projectSystem.createServerHost([f1, tslib]); + const service = ts.projectSystem.createProjectService(host); + service.openExternalProject({ projectFileName: "p", rootFiles: [ts.projectSystem.toExternalFile(f1.path)], options: { importHelpers: true } }); + service.checkNumberOfProjects({ externalProjects: 1 }); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/inferredProjects.ts b/src/testRunner/unittests/tsserver/inferredProjects.ts index ce1747ee4fa5c..12e51e98107de 100644 --- a/src/testRunner/unittests/tsserver/inferredProjects.ts +++ b/src/testRunner/unittests/tsserver/inferredProjects.ts @@ -1,454 +1,454 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: Inferred projects", () => { - it("create inferred project", () => { - const appFile: File = { - path: `${tscWatch.projectRoot}/app.ts`, - content: ` +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: Inferred projects", () => { + it("create inferred project", () => { + const appFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/app.ts`, + content: ` import {f} from "./module" console.log(f) ` - }; - - const moduleFile: File = { - path: `${tscWatch.projectRoot}/module.d.ts`, - content: `export let x: number` - }; - const host = createServerHost([appFile, moduleFile, libFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(appFile.path); - baselineTsserverLogs("inferredProjects", "create inferred project", projectService); - }); + }; + + const moduleFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/module.d.ts`, + content: `export let x: number` + }; + const host = ts.projectSystem.createServerHost([appFile, moduleFile, ts.projectSystem.libFile]); + const projectService = ts.projectSystem.createProjectService(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + projectService.openClientFile(appFile.path); + ts.projectSystem.baselineTsserverLogs("inferredProjects", "create inferred project", projectService); + }); - it("should use only one inferred project if 'useOneInferredProject' is set", () => { - const file1 = { - path: `${tscWatch.projectRoot}/a/b/main.ts`, - content: "let x =1;" - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/a/b/tsconfig.json`, - content: `{ + it("should use only one inferred project if 'useOneInferredProject' is set", () => { + const file1 = { + path: `${ts.tscWatch.projectRoot}/a/b/main.ts`, + content: "let x =1;" + }; + const configFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/a/b/tsconfig.json`, + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts" ] }` - }; - const file2 = { - path: `${tscWatch.projectRoot}/a/c/main.ts`, - content: "let x =1;" - }; - - const file3 = { - path: `${tscWatch.projectRoot}/a/d/main.ts`, - content: "let x =1;" - }; - - const host = createServerHost([file1, file2, file3, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); - projectService.openClientFile(file3.path); - - checkNumberOfConfiguredProjects(projectService, 0); - checkNumberOfInferredProjects(projectService, 1); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path, libFile.path]); - - - host.writeFile(configFile.path, configFile.content); - host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles - checkNumberOfConfiguredProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 1); - checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); - }); + }; + const file2 = { + path: `${ts.tscWatch.projectRoot}/a/c/main.ts`, + content: "let x =1;" + }; + + const file3 = { + path: `${ts.tscWatch.projectRoot}/a/d/main.ts`, + content: "let x =1;" + }; + + const host = ts.projectSystem.createServerHost([file1, file2, file3, ts.projectSystem.libFile]); + const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + projectService.openClientFile(file3.path); + + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 0); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 1); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path, ts.projectSystem.libFile.path]); + + + host.writeFile(configFile.path, configFile.content); + host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 1); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, ts.projectSystem.libFile.path]); + }); - it("disable inferred project", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; + it("disable inferred project", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; - const host = createServerHost([file1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); + const host = ts.projectSystem.createServerHost([file1]); + const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); - projectService.openClientFile(file1.path, file1.content); + projectService.openClientFile(file1.path, file1.content); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const proj = projectService.inferredProjects[0]; - assert.isDefined(proj); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); - assert.isFalse(proj.languageServiceEnabled); - }); + assert.isFalse(proj.languageServiceEnabled); + }); - it("project settings for inferred projects", () => { - const file1 = { - path: "/a/b/app.ts", - content: `import {x} from "mod"` - }; - const modFile = { - path: "/a/mod.ts", - content: "export let x: number" - }; - const host = createServerHost([file1, modFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - projectService.openClientFile(modFile.path); - - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - const inferredProjects = projectService.inferredProjects.slice(); - checkProjectActualFiles(inferredProjects[0], [file1.path]); - checkProjectActualFiles(inferredProjects[1], [modFile.path]); - - projectService.setCompilerOptionsForInferredProjects({ moduleResolution: ModuleResolutionKind.Classic }); - host.checkTimeoutQueueLengthAndRun(3); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); - assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); - checkProjectActualFiles(inferredProjects[0], [file1.path, modFile.path]); - assert.isTrue(inferredProjects[1].isOrphan()); - }); + it("project settings for inferred projects", () => { + const file1 = { + path: "/a/b/app.ts", + content: `import {x} from "mod"` + }; + const modFile = { + path: "/a/mod.ts", + content: "export let x: number" + }; + const host = ts.projectSystem.createServerHost([file1, modFile]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.openClientFile(modFile.path); + + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); + const inferredProjects = projectService.inferredProjects.slice(); + ts.projectSystem.checkProjectActualFiles(inferredProjects[0], [file1.path]); + ts.projectSystem.checkProjectActualFiles(inferredProjects[1], [modFile.path]); + + projectService.setCompilerOptionsForInferredProjects({ moduleResolution: ts.ModuleResolutionKind.Classic }); + host.checkTimeoutQueueLengthAndRun(3); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); + ts.projectSystem.checkProjectActualFiles(inferredProjects[0], [file1.path, modFile.path]); + assert.isTrue(inferredProjects[1].isOrphan()); + }); - it("should support files without extensions", () => { - const f = { - path: "/a/compile", - content: "let x = 1" - }; - const host = createServerHost([f]); - const session = createSession(host); - session.executeCommand({ - seq: 1, - type: "request", - command: "compilerOptionsForInferredProjects", - arguments: { - options: { - allowJs: true - } + it("should support files without extensions", () => { + const f = { + path: "/a/compile", + content: "let x = 1" + }; + const host = ts.projectSystem.createServerHost([f]); + const session = ts.projectSystem.createSession(host); + session.executeCommand({ + seq: 1, + type: "request", + command: "compilerOptionsForInferredProjects", + arguments: { + options: { + allowJs: true } - } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); - session.executeCommand({ - seq: 2, - type: "request", - command: "open", - arguments: { - file: f.path, - fileContent: f.content, - scriptKindName: "JS" - } - } as server.protocol.OpenRequest); - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [f.path]); - }); + } + } as ts.server.protocol.SetCompilerOptionsForInferredProjectsRequest); + session.executeCommand({ + seq: 2, + type: "request", + command: "open", + arguments: { + file: f.path, + fileContent: f.content, + scriptKindName: "JS" + } + } as ts.server.protocol.OpenRequest); + const projectService = session.getProjectService(); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [f.path]); + }); - it("inferred projects per project root", () => { - const file1 = { path: "/a/file1.ts", content: "let x = 1;", projectRootPath: "/a" }; - const file2 = { path: "/a/file2.ts", content: "let y = 2;", projectRootPath: "/a" }; - const file3 = { path: "/b/file2.ts", content: "let x = 3;", projectRootPath: "/b" }; - const file4 = { path: "/c/file3.ts", content: "let z = 4;" }; - const host = createServerHost([file1, file2, file3, file4]); - const session = createSession(host, { - useSingleInferredProject: true, - useInferredProjectPerProjectRoot: true - }); - session.executeCommand({ - seq: 1, - type: "request", - command: CommandNames.CompilerOptionsForInferredProjects, - arguments: { - options: { - allowJs: true, - target: ScriptTarget.ESNext - } - } - } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); - session.executeCommand({ - seq: 2, - type: "request", - command: CommandNames.CompilerOptionsForInferredProjects, - arguments: { - options: { - allowJs: true, - target: ScriptTarget.ES2015 - }, - projectRootPath: "/b" - } - } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); - session.executeCommand({ - seq: 3, - type: "request", - command: CommandNames.Open, - arguments: { - file: file1.path, - fileContent: file1.content, - scriptKindName: "JS", - projectRootPath: file1.projectRootPath - } - } as server.protocol.OpenRequest); - session.executeCommand({ - seq: 4, - type: "request", - command: CommandNames.Open, - arguments: { - file: file2.path, - fileContent: file2.content, - scriptKindName: "JS", - projectRootPath: file2.projectRootPath - } - } as server.protocol.OpenRequest); - session.executeCommand({ - seq: 5, - type: "request", - command: CommandNames.Open, - arguments: { - file: file3.path, - fileContent: file3.content, - scriptKindName: "JS", - projectRootPath: file3.projectRootPath - } - } as server.protocol.OpenRequest); - session.executeCommand({ - seq: 6, - type: "request", - command: CommandNames.Open, - arguments: { - file: file4.path, - fileContent: file4.content, - scriptKindName: "JS" - } - } as server.protocol.OpenRequest); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 3 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file1.path, file2.path]); - checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); - assert.equal(projectService.inferredProjects[0].getCompilationSettings().target, ScriptTarget.ESNext); - assert.equal(projectService.inferredProjects[1].getCompilationSettings().target, ScriptTarget.ESNext); - assert.equal(projectService.inferredProjects[2].getCompilationSettings().target, ScriptTarget.ES2015); + it("inferred projects per project root", () => { + const file1 = { path: "/a/file1.ts", content: "let x = 1;", projectRootPath: "/a" }; + const file2 = { path: "/a/file2.ts", content: "let y = 2;", projectRootPath: "/a" }; + const file3 = { path: "/b/file2.ts", content: "let x = 3;", projectRootPath: "/b" }; + const file4 = { path: "/c/file3.ts", content: "let z = 4;" }; + const host = ts.projectSystem.createServerHost([file1, file2, file3, file4]); + const session = ts.projectSystem.createSession(host, { + useSingleInferredProject: true, + useInferredProjectPerProjectRoot: true }); + session.executeCommand({ + seq: 1, + type: "request", + command: ts.projectSystem.CommandNames.CompilerOptionsForInferredProjects, + arguments: { + options: { + allowJs: true, + target: ts.ScriptTarget.ESNext + } + } + } as ts.server.protocol.SetCompilerOptionsForInferredProjectsRequest); + session.executeCommand({ + seq: 2, + type: "request", + command: ts.projectSystem.CommandNames.CompilerOptionsForInferredProjects, + arguments: { + options: { + allowJs: true, + target: ts.ScriptTarget.ES2015 + }, + projectRootPath: "/b" + } + } as ts.server.protocol.SetCompilerOptionsForInferredProjectsRequest); + session.executeCommand({ + seq: 3, + type: "request", + command: ts.projectSystem.CommandNames.Open, + arguments: { + file: file1.path, + fileContent: file1.content, + scriptKindName: "JS", + projectRootPath: file1.projectRootPath + } + } as ts.server.protocol.OpenRequest); + session.executeCommand({ + seq: 4, + type: "request", + command: ts.projectSystem.CommandNames.Open, + arguments: { + file: file2.path, + fileContent: file2.content, + scriptKindName: "JS", + projectRootPath: file2.projectRootPath + } + } as ts.server.protocol.OpenRequest); + session.executeCommand({ + seq: 5, + type: "request", + command: ts.projectSystem.CommandNames.Open, + arguments: { + file: file3.path, + fileContent: file3.content, + scriptKindName: "JS", + projectRootPath: file3.projectRootPath + } + } as ts.server.protocol.OpenRequest); + session.executeCommand({ + seq: 6, + type: "request", + command: ts.projectSystem.CommandNames.Open, + arguments: { + file: file4.path, + fileContent: file4.content, + scriptKindName: "JS" + } + } as ts.server.protocol.OpenRequest); + + const projectService = session.getProjectService(); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 3 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [file1.path, file2.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); + assert.equal(projectService.inferredProjects[0].getCompilationSettings().target, ts.ScriptTarget.ESNext); + assert.equal(projectService.inferredProjects[1].getCompilationSettings().target, ts.ScriptTarget.ESNext); + assert.equal(projectService.inferredProjects[2].getCompilationSettings().target, ts.ScriptTarget.ES2015); + }); - function checkInferredProject(inferredProject: server.InferredProject, actualFiles: File[], target: ScriptTarget) { - checkProjectActualFiles(inferredProject, actualFiles.map(f => f.path)); - assert.equal(inferredProject.getCompilationSettings().target, target); + function checkInferredProject(inferredProject: ts.server.InferredProject, actualFiles: ts.projectSystem.File[], target: ts.ScriptTarget) { + ts.projectSystem.checkProjectActualFiles(inferredProject, actualFiles.map(f => f.path)); + assert.equal(inferredProject.getCompilationSettings().target, target); + } + + function verifyProjectRootWithCaseSensitivity(useCaseSensitiveFileNames: boolean) { + const files: [ts.projectSystem.File, ts.projectSystem.File, ts.projectSystem.File, ts.projectSystem.File] = [ + { path: "/a/file1.ts", content: "let x = 1;" }, + { path: "/A/file2.ts", content: "let y = 2;" }, + { path: "/b/file2.ts", content: "let x = 3;" }, + { path: "/c/file3.ts", content: "let z = 4;" } + ]; + const host = ts.projectSystem.createServerHost(files, { useCaseSensitiveFileNames }); + const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true, useInferredProjectPerProjectRoot: true }); + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + target: ts.ScriptTarget.ESNext + }); + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + target: ts.ScriptTarget.ES2015 + }, "/a"); + + openClientFiles(["/a", "/a", "/b", undefined]); + verifyInferredProjectsState([ + [[files[3]], ts.ScriptTarget.ESNext], + [[files[0], files[1]], ts.ScriptTarget.ES2015], + [[files[2]], ts.ScriptTarget.ESNext] + ]); + closeClientFiles(); + + openClientFiles(["/a", "/A", "/b", undefined]); + if (useCaseSensitiveFileNames) { + verifyInferredProjectsState([ + [[files[3]], ts.ScriptTarget.ESNext], + [[files[0]], ts.ScriptTarget.ES2015], + [[files[1]], ts.ScriptTarget.ESNext], + [[files[2]], ts.ScriptTarget.ESNext] + ]); } - - function verifyProjectRootWithCaseSensitivity(useCaseSensitiveFileNames: boolean) { - const files: [File, File, File, File] = [ - { path: "/a/file1.ts", content: "let x = 1;" }, - { path: "/A/file2.ts", content: "let y = 2;" }, - { path: "/b/file2.ts", content: "let x = 3;" }, - { path: "/c/file3.ts", content: "let z = 4;" } - ]; - const host = createServerHost(files, { useCaseSensitiveFileNames }); - const projectService = createProjectService(host, { useSingleInferredProject: true, useInferredProjectPerProjectRoot: true }); - projectService.setCompilerOptionsForInferredProjects({ - allowJs: true, - target: ScriptTarget.ESNext - }); - projectService.setCompilerOptionsForInferredProjects({ - allowJs: true, - target: ScriptTarget.ES2015 - }, "/a"); - - openClientFiles(["/a", "/a", "/b", undefined]); + else { verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0], files[1]], ScriptTarget.ES2015], - [[files[2]], ScriptTarget.ESNext] + [[files[3]], ts.ScriptTarget.ESNext], + [[files[0], files[1]], ts.ScriptTarget.ES2015], + [[files[2]], ts.ScriptTarget.ESNext] ]); - closeClientFiles(); - - openClientFiles(["/a", "/A", "/b", undefined]); - if (useCaseSensitiveFileNames) { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0]], ScriptTarget.ES2015], - [[files[1]], ScriptTarget.ESNext], - [[files[2]], ScriptTarget.ESNext] - ]); - } - else { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0], files[1]], ScriptTarget.ES2015], - [[files[2]], ScriptTarget.ESNext] - ]); - } - closeClientFiles(); - - projectService.setCompilerOptionsForInferredProjects({ - allowJs: true, - target: ScriptTarget.ES2017 - }, "/A"); - - openClientFiles(["/a", "/a", "/b", undefined]); + } + closeClientFiles(); + + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + target: ts.ScriptTarget.ES2017 + }, "/A"); + + openClientFiles(["/a", "/a", "/b", undefined]); + verifyInferredProjectsState([ + [[files[3]], ts.ScriptTarget.ESNext], + [[files[0], files[1]], useCaseSensitiveFileNames ? ts.ScriptTarget.ES2015 : ts.ScriptTarget.ES2017], + [[files[2]], ts.ScriptTarget.ESNext] + ]); + closeClientFiles(); + + openClientFiles(["/a", "/A", "/b", undefined]); + if (useCaseSensitiveFileNames) { verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0], files[1]], useCaseSensitiveFileNames ? ScriptTarget.ES2015 : ScriptTarget.ES2017], - [[files[2]], ScriptTarget.ESNext] + [[files[3]], ts.ScriptTarget.ESNext], + [[files[0]], ts.ScriptTarget.ES2015], + [[files[1]], ts.ScriptTarget.ES2017], + [[files[2]], ts.ScriptTarget.ESNext] ]); - closeClientFiles(); - - openClientFiles(["/a", "/A", "/b", undefined]); - if (useCaseSensitiveFileNames) { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0]], ScriptTarget.ES2015], - [[files[1]], ScriptTarget.ES2017], - [[files[2]], ScriptTarget.ESNext] - ]); - } - else { - verifyInferredProjectsState([ - [[files[3]], ScriptTarget.ESNext], - [[files[0], files[1]], ScriptTarget.ES2017], - [[files[2]], ScriptTarget.ESNext] - ]); - } - closeClientFiles(); + } + else { + verifyInferredProjectsState([ + [[files[3]], ts.ScriptTarget.ESNext], + [[files[0], files[1]], ts.ScriptTarget.ES2017], + [[files[2]], ts.ScriptTarget.ESNext] + ]); + } + closeClientFiles(); - function openClientFiles(projectRoots: [string | undefined, string | undefined, string | undefined, string | undefined]) { - files.forEach((file, index) => { - projectService.openClientFile(file.path, file.content, ScriptKind.JS, projectRoots[index]); - }); - } + function openClientFiles(projectRoots: [string | undefined, string | undefined, string | undefined, string | undefined]) { + files.forEach((file, index) => { + projectService.openClientFile(file.path, file.content, ts.ScriptKind.JS, projectRoots[index]); + }); + } - function closeClientFiles() { - files.forEach(file => projectService.closeClientFile(file.path)); - } + function closeClientFiles() { + files.forEach(file => projectService.closeClientFile(file.path)); + } - function verifyInferredProjectsState(expected: [File[], ScriptTarget][]) { - checkNumberOfProjects(projectService, { inferredProjects: expected.length }); - projectService.inferredProjects.forEach((p, index) => { - const [actualFiles, target] = expected[index]; - checkInferredProject(p, actualFiles, target); - }); - } + function verifyInferredProjectsState(expected: [ts.projectSystem.File[], ts.ScriptTarget][]) { + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: expected.length }); + projectService.inferredProjects.forEach((p, index) => { + const [actualFiles, target] = expected[index]; + checkInferredProject(p, actualFiles, target); + }); } + } - it("inferred projects per project root with case sensitive system", () => { - verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ true); - }); + it("inferred projects per project root with case sensitive system", () => { + verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ true); + }); - it("inferred projects per project root with case insensitive system", () => { - verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ false); - }); + it("inferred projects per project root with case insensitive system", () => { + verifyProjectRootWithCaseSensitivity(/*useCaseSensitiveFileNames*/ false); + }); - it("should still retain configured project created while opening the file", () => { - const appFile: File = { - path: `${tscWatch.projectRoot}/app.ts`, - content: `const app = 20;` - }; - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const jsFile1: File = { - path: `${tscWatch.projectRoot}/jsFile1.js`, - content: `const jsFile1 = 10;` - }; - const jsFile2: File = { - path: `${tscWatch.projectRoot}/jsFile2.js`, - content: `const jsFile2 = 10;` - }; - const host = createServerHost([appFile, libFile, config, jsFile1, jsFile2]); - const projectService = createProjectService(host); - const originalSet = projectService.configuredProjects.set; - const originalDelete = projectService.configuredProjects.delete; - const configuredCreated = new Map(); - const configuredRemoved = new Map(); - projectService.configuredProjects.set = (key, value) => { - assert.isFalse(configuredCreated.has(key)); - configuredCreated.set(key, true); - return originalSet.call(projectService.configuredProjects, key, value); - }; - projectService.configuredProjects.delete = key => { - assert.isFalse(configuredRemoved.has(key)); - configuredRemoved.set(key, true); - return originalDelete.call(projectService.configuredProjects, key); - }; - - // Do not remove config project when opening jsFile that is not present as part of config project - projectService.openClientFile(jsFile1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [jsFile1.path, libFile.path]); - const project = projectService.configuredProjects.get(config.path)!; - checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]); - checkConfiguredProjectCreatedAndNotDeleted(); - - // Do not remove config project when opening jsFile that is not present as part of config project - projectService.closeClientFile(jsFile1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); - projectService.openClientFile(jsFile2.path); - checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]); - checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]); - checkConfiguredProjectNotCreatedAndNotDeleted(); - - // Do not remove config project when opening jsFile that is not present as part of config project - projectService.openClientFile(jsFile1.path); - checkNumberOfProjects(projectService, { inferredProjects: 2, configuredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, libFile.path]); - checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]); - checkConfiguredProjectNotCreatedAndNotDeleted(); - - // When opening file that doesnt fall back to the config file, we remove the config project - projectService.openClientFile(libFile.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, libFile.path]); - checkConfiguredProjectNotCreatedButDeleted(); - - function checkConfiguredProjectCreatedAndNotDeleted() { - assert.equal(configuredCreated.size, 1); - assert.isTrue(configuredCreated.has(config.path)); - assert.equal(configuredRemoved.size, 0); - configuredCreated.clear(); - } + it("should still retain configured project created while opening the file", () => { + const appFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/app.ts`, + content: `const app = 20;` + }; + const config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const jsFile1: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/jsFile1.js`, + content: `const jsFile1 = 10;` + }; + const jsFile2: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/jsFile2.js`, + content: `const jsFile2 = 10;` + }; + const host = ts.projectSystem.createServerHost([appFile, ts.projectSystem.libFile, config, jsFile1, jsFile2]); + const projectService = ts.projectSystem.createProjectService(host); + const originalSet = projectService.configuredProjects.set; + const originalDelete = projectService.configuredProjects.delete; + const configuredCreated = new ts.Map(); + const configuredRemoved = new ts.Map(); + projectService.configuredProjects.set = (key, value) => { + assert.isFalse(configuredCreated.has(key)); + configuredCreated.set(key, true); + return originalSet.call(projectService.configuredProjects, key, value); + }; + projectService.configuredProjects.delete = key => { + assert.isFalse(configuredRemoved.has(key)); + configuredRemoved.set(key, true); + return originalDelete.call(projectService.configuredProjects, key); + }; + + // Do not remove config project when opening jsFile that is not present as part of config project + projectService.openClientFile(jsFile1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [jsFile1.path, ts.projectSystem.libFile.path]); + const project = projectService.configuredProjects.get(config.path)!; + ts.projectSystem.checkProjectActualFiles(project, [appFile.path, config.path, ts.projectSystem.libFile.path]); + checkConfiguredProjectCreatedAndNotDeleted(); + + // Do not remove config project when opening jsFile that is not present as part of config project + projectService.closeClientFile(jsFile1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); + projectService.openClientFile(jsFile2.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, ts.projectSystem.libFile.path]); + ts.projectSystem.checkProjectActualFiles(project, [appFile.path, config.path, ts.projectSystem.libFile.path]); + checkConfiguredProjectNotCreatedAndNotDeleted(); + + // Do not remove config project when opening jsFile that is not present as part of config project + projectService.openClientFile(jsFile1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2, configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, ts.projectSystem.libFile.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, ts.projectSystem.libFile.path]); + ts.projectSystem.checkProjectActualFiles(project, [appFile.path, config.path, ts.projectSystem.libFile.path]); + checkConfiguredProjectNotCreatedAndNotDeleted(); + + // When opening file that doesnt fall back to the config file, we remove the config project + projectService.openClientFile(ts.projectSystem.libFile.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, ts.projectSystem.libFile.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, ts.projectSystem.libFile.path]); + checkConfiguredProjectNotCreatedButDeleted(); + + function checkConfiguredProjectCreatedAndNotDeleted() { + assert.equal(configuredCreated.size, 1); + assert.isTrue(configuredCreated.has(config.path)); + assert.equal(configuredRemoved.size, 0); + configuredCreated.clear(); + } - function checkConfiguredProjectNotCreatedAndNotDeleted() { - assert.equal(configuredCreated.size, 0); - assert.equal(configuredRemoved.size, 0); - } + function checkConfiguredProjectNotCreatedAndNotDeleted() { + assert.equal(configuredCreated.size, 0); + assert.equal(configuredRemoved.size, 0); + } - function checkConfiguredProjectNotCreatedButDeleted() { - assert.equal(configuredCreated.size, 0); - assert.equal(configuredRemoved.size, 1); - assert.isTrue(configuredRemoved.has(config.path)); - configuredRemoved.clear(); - } - }); + function checkConfiguredProjectNotCreatedButDeleted() { + assert.equal(configuredCreated.size, 0); + assert.equal(configuredRemoved.size, 1); + assert.isTrue(configuredRemoved.has(config.path)); + configuredRemoved.clear(); + } + }); - it("regression test - should infer typeAcquisition for inferred projects when set undefined", () => { - const file1 = { path: "/a/file1.js", content: "" }; - const host = createServerHost([file1]); + it("regression test - should infer typeAcquisition for inferred projects when set undefined", () => { + const file1 = { path: "/a/file1.js", content: "" }; + const host = ts.projectSystem.createServerHost([file1]); - const projectService = createProjectService(host); + const projectService = ts.projectSystem.createProjectService(host); - projectService.openClientFile(file1.path); + projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const inferredProject = projectService.inferredProjects[0]; - checkProjectActualFiles(inferredProject, [file1.path]); - inferredProject.setTypeAcquisition(undefined); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const inferredProject = projectService.inferredProjects[0]; + ts.projectSystem.checkProjectActualFiles(inferredProject, [file1.path]); + inferredProject.setTypeAcquisition(undefined); - const expected = { - enable: true, - include: [], - exclude: [] - }; - assert.deepEqual(inferredProject.getTypeAcquisition(), expected, "typeAcquisition should be inferred for inferred projects"); - }); + const expected = { + enable: true, + include: [], + exclude: [] + }; + assert.deepEqual(inferredProject.getTypeAcquisition(), expected, "typeAcquisition should be inferred for inferred projects"); + }); - it("Setting compiler options for inferred projects when there are no open files should not schedule any refresh", () => { - const host = createServerHost([commonFile1, libFile]); - const projectService = createProjectService(host); - projectService.setCompilerOptionsForInferredProjects({ - allowJs: true, - target: ScriptTarget.ES2015 - }); - host.checkTimeoutQueueLength(0); + it("Setting compiler options for inferred projects when there are no open files should not schedule any refresh", () => { + const host = ts.projectSystem.createServerHost([ts.projectSystem.commonFile1, ts.projectSystem.libFile]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + target: ts.ScriptTarget.ES2015 }); + host.checkTimeoutQueueLength(0); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/inlayHints.ts b/src/testRunner/unittests/tsserver/inlayHints.ts index 837bc9c452e6a..d5e41272ccdcc 100644 --- a/src/testRunner/unittests/tsserver/inlayHints.ts +++ b/src/testRunner/unittests/tsserver/inlayHints.ts @@ -1,63 +1,63 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: inlayHints", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: "{}" - }; - const app: File = { - path: "/a/b/app.ts", - content: "declare function foo(param: any): void;\nfoo(12);" - }; +import * as ts from "../../_namespaces/ts"; - it("with updateOpen request does not corrupt documents", () => { - const host = createServerHost([app, commonFile1, commonFile2, libFile, configFile]); - const session = createSession(host); - session.executeCommandSeq({ - command: protocol.CommandTypes.Open, - arguments: { file: app.path } - }); - session.executeCommandSeq({ - command: protocol.CommandTypes.Configure, - arguments: { - preferences: { - includeInlayParameterNameHints: "all" - } as UserPreferences - } - }); - verifyInlayHintResponse(session); - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ fileName: app.path, textChanges: [{ start: { line: 1, offset: 39 }, end: { line: 1, offset: 39 }, newText: "//" }] }] - } - }); - verifyInlayHintResponse(session); - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ fileName: app.path, textChanges: [{ start: { line: 1, offset: 41 }, end: { line: 1, offset: 41 }, newText: "c" }] }] - } - }); - verifyInlayHintResponse(session); +describe("unittests:: tsserver:: inlayHints", () => { + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + const app: ts.projectSystem.File = { + path: "/a/b/app.ts", + content: "declare function foo(param: any): void;\nfoo(12);" + }; - function verifyInlayHintResponse(session: TestSession) { - verifyParamInlayHint(session.executeCommandSeq({ - command: protocol.CommandTypes.ProvideInlayHints, - arguments: { - file: app.path, - start: 0, - length: app.content.length, - } - }).response as protocol.InlayHintItem[] | undefined); + it("with updateOpen request does not corrupt documents", () => { + const host = ts.projectSystem.createServerHost([app, ts.projectSystem.commonFile1, ts.projectSystem.commonFile2, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Open, + arguments: { file: app.path } + }); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Configure, + arguments: { + preferences: { + includeInlayParameterNameHints: "all" + } as ts.UserPreferences } - - function verifyParamInlayHint(response: protocol.InlayHintItem[] | undefined) { - Debug.assert(response); - Debug.assert(response[0]); - Debug.assertEqual(response[0].text, "param:"); - Debug.assertEqual(response[0].position.line, 2); - Debug.assertEqual(response[0].position.offset, 5); + }); + verifyInlayHintResponse(session); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ fileName: app.path, textChanges: [{ start: { line: 1, offset: 39 }, end: { line: 1, offset: 39 }, newText: "//" }] }] + } + }); + verifyInlayHintResponse(session); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ fileName: app.path, textChanges: [{ start: { line: 1, offset: 41 }, end: { line: 1, offset: 41 }, newText: "c" }] }] } }); + verifyInlayHintResponse(session); + + function verifyInlayHintResponse(session: ts.projectSystem.TestSession) { + verifyParamInlayHint(session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.ProvideInlayHints, + arguments: { + file: app.path, + start: 0, + length: app.content.length, + } + }).response as ts.projectSystem.protocol.InlayHintItem[] | undefined); + } + + function verifyParamInlayHint(response: ts.projectSystem.protocol.InlayHintItem[] | undefined) { + ts.Debug.assert(response); + ts.Debug.assert(response[0]); + ts.Debug.assertEqual(response[0].text, "param:"); + ts.Debug.assertEqual(response[0].position.line, 2); + ts.Debug.assertEqual(response[0].position.offset, 5); + } }); -} +}); diff --git a/src/testRunner/unittests/tsserver/jsdocTag.ts b/src/testRunner/unittests/tsserver/jsdocTag.ts index 7699c107586e2..ec0c44b1e16ca 100644 --- a/src/testRunner/unittests/tsserver/jsdocTag.ts +++ b/src/testRunner/unittests/tsserver/jsdocTag.ts @@ -1,8 +1,9 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: jsdoc @link ", () => { - const config: File = { - path: "/a/tsconfig.json", - content: `{ +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: jsdoc @link ", () => { + const config: ts.projectSystem.File = { + path: "/a/tsconfig.json", + content: `{ "compilerOptions": { "checkJs": true, "noEmit": true @@ -10,134 +11,92 @@ namespace ts.projectSystem { "files": ["someFile1.js"] } ` - }; - function assertQuickInfoJSDoc(file: File, options: { - displayPartsForJSDoc: boolean, - command: protocol.CommandTypes, - tags: string | unknown[] | undefined, - documentation: string | unknown[] - }) { + }; + function assertQuickInfoJSDoc(file: ts.projectSystem.File, options: { + displayPartsForJSDoc: boolean, + command: ts.projectSystem.protocol.CommandTypes, + tags: string | unknown[] | undefined, + documentation: string | unknown[] + }) { - const { command, displayPartsForJSDoc, tags, documentation } = options; - const session = createSession(createServerHost([file, config])); - session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); - openFilesForSession([file], session); - const indexOfX = file.content.indexOf("x"); - const quickInfo = session.executeCommandSeq({ - command: command as protocol.CommandTypes.Quickinfo, - arguments: { - file: file.path, - position: indexOfX, - } as protocol.FileLocationRequestArgs - }).response; - const summaryAndLocation = command === protocol.CommandTypes.Quickinfo ? { - displayString: "var x: number", - start: { - line: 3, - offset: 5, - }, - end: { - line: 3, - offset: 6, - } - } : { - displayParts: [{ - kind: "keyword", - text: "var", - }, { - kind: "space", - text: " ", - }, { - kind: "localName", - text: "x", - }, { - kind: "punctuation", - text: ":", - }, { - kind: "space", - text: " ", - }, { - kind: "keyword", - text: "number", - }], - textSpan: { - length: 1, - start: 38, - } - }; - assert.deepEqual(quickInfo, { - kind: "var", - kindModifiers: "", - ...summaryAndLocation, - documentation, - tags - }); - } + const { command, displayPartsForJSDoc, tags, documentation } = options; + const session = ts.projectSystem.createSession(ts.projectSystem.createServerHost([file, config])); + session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); + ts.projectSystem.openFilesForSession([file], session); + const indexOfX = file.content.indexOf("x"); + const quickInfo = session.executeCommandSeq({ + command: command as ts.projectSystem.protocol.CommandTypes.Quickinfo, + arguments: { + file: file.path, + position: indexOfX, + } as ts.projectSystem.protocol.FileLocationRequestArgs + }).response; + const summaryAndLocation = command === ts.projectSystem.protocol.CommandTypes.Quickinfo ? { + displayString: "var x: number", + start: { + line: 3, + offset: 5, + }, + end: { + line: 3, + offset: 6, + } + } : { + displayParts: [{ + kind: "keyword", + text: "var", + }, { + kind: "space", + text: " ", + }, { + kind: "localName", + text: "x", + }, { + kind: "punctuation", + text: ":", + }, { + kind: "space", + text: " ", + }, { + kind: "keyword", + text: "number", + }], + textSpan: { + length: 1, + start: 38, + } + }; + assert.deepEqual(quickInfo, { + kind: "var", + kindModifiers: "", + ...summaryAndLocation, + documentation, + tags + }); + } - const linkInTag: File = { - path: "/a/someFile1.js", - content: `class C { } + const linkInTag: ts.projectSystem.File = { + path: "/a/someFile1.js", + content: `class C { } /** @wat {@link C} */ var x = 1` - }; - const linkInComment: File = { - path: "/a/someFile1.js", - content: `class C { } + }; + const linkInComment: ts.projectSystem.File = { + path: "/a/someFile1.js", + content: `class C { } /** {@link C} */ var x = 1 ;` - }; + }; - it("for quickinfo, should provide display parts plus a span for a working link in a tag", () => { - assertQuickInfoJSDoc(linkInTag, { - command: protocol.CommandTypes.Quickinfo, - displayPartsForJSDoc: true, - documentation: [], - tags: [{ - name: "wat", - text: [{ - kind: "text", - text: "", - }, { - kind: "link", - text: "{@link ", - }, { - kind: "linkName", - target: { - end: { - line: 1, - offset: 12, - }, - file: "/a/someFile1.js", - start: { - line: 1, - offset: 1, - }, - }, - text: "C", - }, { - kind: "link", - text: "}", - }] - }], - }); - }); - it("for quickinfo, should provide a string for a working link in a tag", () => { - assertQuickInfoJSDoc(linkInTag, { - command: protocol.CommandTypes.Quickinfo, - displayPartsForJSDoc: false, - documentation: "", - tags: [{ - name: "wat", - text: "{@link C}" - }], - }); - }); - it("for quickinfo, should provide display parts for a working link in a comment", () => { - assertQuickInfoJSDoc(linkInComment, { - command: protocol.CommandTypes.Quickinfo, - displayPartsForJSDoc: true, - documentation: [{ + it("for quickinfo, should provide display parts plus a span for a working link in a tag", () => { + assertQuickInfoJSDoc(linkInTag, { + command: ts.projectSystem.protocol.CommandTypes.Quickinfo, + displayPartsForJSDoc: true, + documentation: [], + tags: [{ + name: "wat", + text: [{ kind: "text", text: "", }, { @@ -160,65 +119,69 @@ var x = 1 }, { kind: "link", text: "}", - }], - tags: [], - }); + }] + }], }); - it("for quickinfo, should provide a string for a working link in a comment", () => { - assertQuickInfoJSDoc(linkInComment, { - command: protocol.CommandTypes.Quickinfo, - displayPartsForJSDoc: false, - documentation: "{@link C}", - tags: [], - }); + }); + it("for quickinfo, should provide a string for a working link in a tag", () => { + assertQuickInfoJSDoc(linkInTag, { + command: ts.projectSystem.protocol.CommandTypes.Quickinfo, + displayPartsForJSDoc: false, + documentation: "", + tags: [{ + name: "wat", + text: "{@link C}" + }], }); - - it("for quickinfo-full, should provide display parts plus a span for a working link in a tag", () => { - assertQuickInfoJSDoc(linkInTag, { - command: protocol.CommandTypes.QuickinfoFull, - displayPartsForJSDoc: true, - documentation: [], - tags: [{ - name: "wat", - text: [{ - kind: "text", - text: "", - }, { - kind: "link", - text: "{@link ", - }, { - kind: "linkName", - target: { - fileName: "/a/someFile1.js", - textSpan: { - length: 11, - start: 0 - }, - }, - text: "C", - }, { - kind: "link", - text: "}", - }] - }], - }); + }); + it("for quickinfo, should provide display parts for a working link in a comment", () => { + assertQuickInfoJSDoc(linkInComment, { + command: ts.projectSystem.protocol.CommandTypes.Quickinfo, + displayPartsForJSDoc: true, + documentation: [{ + kind: "text", + text: "", + }, { + kind: "link", + text: "{@link ", + }, { + kind: "linkName", + target: { + end: { + line: 1, + offset: 12, + }, + file: "/a/someFile1.js", + start: { + line: 1, + offset: 1, + }, + }, + text: "C", + }, { + kind: "link", + text: "}", + }], + tags: [], }); - it("for quickinfo-full, should provide a string for a working link in a tag", () => { - assertQuickInfoJSDoc(linkInTag, { - command: protocol.CommandTypes.QuickinfoFull, - displayPartsForJSDoc: false, - documentation: [], - tags: [{ - name: "wat", - text: "{@link C}" - }], - }); + }); + it("for quickinfo, should provide a string for a working link in a comment", () => { + assertQuickInfoJSDoc(linkInComment, { + command: ts.projectSystem.protocol.CommandTypes.Quickinfo, + displayPartsForJSDoc: false, + documentation: "{@link C}", + tags: [], }); - it("for quickinfo-full, should provide display parts plus a span for a working link in a comment", () => { - assertQuickInfoJSDoc(linkInComment, { - command: protocol.CommandTypes.QuickinfoFull, - displayPartsForJSDoc: true, - documentation: [{ + }); + + it("for quickinfo-full, should provide display parts plus a span for a working link in a tag", () => { + assertQuickInfoJSDoc(linkInTag, { + command: ts.projectSystem.protocol.CommandTypes.QuickinfoFull, + displayPartsForJSDoc: true, + documentation: [], + tags: [{ + name: "wat", + text: [{ kind: "text", text: "", }, { @@ -237,195 +200,418 @@ var x = 1 }, { kind: "link", text: "}", - }], - tags: undefined, - }); + }] + }], }); - it("for quickinfo-full, should provide a string for a working link in a comment", () => { - assertQuickInfoJSDoc(linkInComment, { - command: protocol.CommandTypes.QuickinfoFull, - displayPartsForJSDoc: false, - documentation: [{ - kind: "text", - text: "", - }, { - kind: "link", - text: "{@link ", - }, { - kind: "linkName", - target: { - fileName: "/a/someFile1.js", - textSpan: { - length: 11, - start: 0 - }, - }, - text: "C", - }, { - kind: "link", - text: "}", - }], - tags: [], - }); + }); + it("for quickinfo-full, should provide a string for a working link in a tag", () => { + assertQuickInfoJSDoc(linkInTag, { + command: ts.projectSystem.protocol.CommandTypes.QuickinfoFull, + displayPartsForJSDoc: false, + documentation: [], + tags: [{ + name: "wat", + text: "{@link C}" + }], }); - - function assertSignatureHelpJSDoc(options: { - displayPartsForJSDoc: boolean, - command: protocol.CommandTypes, - documentation: string | unknown[], - tags: unknown[] - }) { - const linkInParamTag: File = { - path: "/a/someFile1.js", - content: `class C { } -/** @param y - {@link C} */ -function x(y) { } -x(1)` - }; - - const { command, displayPartsForJSDoc, documentation, tags } = options; - const session = createSession(createServerHost([linkInParamTag, config])); - session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); - openFilesForSession([linkInParamTag], session); - const indexOfX = linkInParamTag.content.lastIndexOf("1"); - const signatureHelp = session.executeCommandSeq({ - command: command as protocol.CommandTypes.SignatureHelp, - arguments: { - triggerReason: { - kind: "invoked" + }); + it("for quickinfo-full, should provide display parts plus a span for a working link in a comment", () => { + assertQuickInfoJSDoc(linkInComment, { + command: ts.projectSystem.protocol.CommandTypes.QuickinfoFull, + displayPartsForJSDoc: true, + documentation: [{ + kind: "text", + text: "", + }, { + kind: "link", + text: "{@link ", + }, { + kind: "linkName", + target: { + fileName: "/a/someFile1.js", + textSpan: { + length: 11, + start: 0 }, - file: linkInParamTag.path, - position: indexOfX, - } as protocol.SignatureHelpRequestArgs - }).response; - const applicableSpan = command === protocol.CommandTypes.SignatureHelp ? { - end: { - line: 4, - offset: 4 }, - start: { - line: 4, - offset: 3 - } - } : { - length: 1, - start: 60 - }; - assert.deepEqual(signatureHelp, { - applicableSpan, - argumentCount: 1, - argumentIndex: 0, - selectedItemIndex: 0, - items: [{ - documentation: [], - isVariadic: false, - parameters: [{ - displayParts: [{ - kind: "parameterName", - text: "y" - }, { - kind: "punctuation", - text: ":" - }, { - kind: "space", - text: " " - }, { - kind: "keyword", - text: "any" - }], - documentation, - isOptional: false, - isRest: false, - name: "y" - }], - prefixDisplayParts: [ - { - kind: "functionName", - text: "x", - }, - { - kind: "punctuation", - text: "(", - }, - ], - separatorDisplayParts: [ - { - kind: "punctuation", - text: ",", - }, - { - kind: "space", - text: " ", - }, - ], - suffixDisplayParts: [ - { - kind: "punctuation", - text: ")", - }, - { - kind: "punctuation", - text: ":", - }, - { - kind: "space", - text: " ", - }, - { - kind: "keyword", - text: "void", - } - ], - tags, - }], - }); - } - it("for signature help, should provide a string for a working link in a comment", () => { - assertSignatureHelpJSDoc({ - command: protocol.CommandTypes.SignatureHelp, - displayPartsForJSDoc: false, - tags: [{ - name: "param", - text: "y - {@link C}" - }], - documentation: [{ + text: "C", + }, { + kind: "link", + text: "}", + }], + tags: undefined, + }); + }); + it("for quickinfo-full, should provide a string for a working link in a comment", () => { + assertQuickInfoJSDoc(linkInComment, { + command: ts.projectSystem.protocol.CommandTypes.QuickinfoFull, + displayPartsForJSDoc: false, + documentation: [{ kind: "text", - text: "- " + text: "", }, { kind: "link", - text: "{@link " + text: "{@link ", }, { kind: "linkName", target: { - file: "/a/someFile1.js", - start: { - line: 1, - offset: 1 + fileName: "/a/someFile1.js", + textSpan: { + length: 11, + start: 0 }, - end: { - line: 1, - offset: 12 - } }, - text: "C" + text: "C", }, { kind: "link", - text: "}" + text: "}", }], - }); + tags: [], }); - it("for signature help, should provide display parts for a working link in a comment", () => { - const tags = [{ + }); + + function assertSignatureHelpJSDoc(options: { + displayPartsForJSDoc: boolean, + command: ts.projectSystem.protocol.CommandTypes, + documentation: string | unknown[], + tags: unknown[] + }) { + const linkInParamTag: ts.projectSystem.File = { + path: "/a/someFile1.js", + content: `class C { } +/** @param y - {@link C} */ +function x(y) { } +x(1)` + }; + + const { command, displayPartsForJSDoc, documentation, tags } = options; + const session = ts.projectSystem.createSession(ts.projectSystem.createServerHost([linkInParamTag, config])); + session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); + ts.projectSystem.openFilesForSession([linkInParamTag], session); + const indexOfX = linkInParamTag.content.lastIndexOf("1"); + const signatureHelp = session.executeCommandSeq({ + command: command as ts.projectSystem.protocol.CommandTypes.SignatureHelp, + arguments: { + triggerReason: { + kind: "invoked" + }, + file: linkInParamTag.path, + position: indexOfX, + } as ts.projectSystem.protocol.SignatureHelpRequestArgs + }).response; + const applicableSpan = command === ts.projectSystem.protocol.CommandTypes.SignatureHelp ? { + end: { + line: 4, + offset: 4 + }, + start: { + line: 4, + offset: 3 + } + } : { + length: 1, + start: 60 + }; + assert.deepEqual(signatureHelp, { + applicableSpan, + argumentCount: 1, + argumentIndex: 0, + selectedItemIndex: 0, + items: [{ + documentation: [], + isVariadic: false, + parameters: [{ + displayParts: [{ + kind: "parameterName", + text: "y" + }, { + kind: "punctuation", + text: ":" + }, { + kind: "space", + text: " " + }, { + kind: "keyword", + text: "any" + }], + documentation, + isOptional: false, + isRest: false, + name: "y" + }], + prefixDisplayParts: [ + { + kind: "functionName", + text: "x", + }, + { + kind: "punctuation", + text: "(", + }, + ], + separatorDisplayParts: [ + { + kind: "punctuation", + text: ",", + }, + { + kind: "space", + text: " ", + }, + ], + suffixDisplayParts: [ + { + kind: "punctuation", + text: ")", + }, + { + kind: "punctuation", + text: ":", + }, + { + kind: "space", + text: " ", + }, + { + kind: "keyword", + text: "void", + } + ], + tags, + }], + }); + } + it("for signature help, should provide a string for a working link in a comment", () => { + assertSignatureHelpJSDoc({ + command: ts.projectSystem.protocol.CommandTypes.SignatureHelp, + displayPartsForJSDoc: false, + tags: [{ + name: "param", + text: "y - {@link C}" + }], + documentation: [{ + kind: "text", + text: "- " + }, { + kind: "link", + text: "{@link " + }, { + kind: "linkName", + target: { + file: "/a/someFile1.js", + start: { + line: 1, + offset: 1 + }, + end: { + line: 1, + offset: 12 + } + }, + text: "C" + }, { + kind: "link", + text: "}" + }], + }); + }); + it("for signature help, should provide display parts for a working link in a comment", () => { + const tags = [{ + name: "param", + text: [{ + kind: "parameterName", + text: "y" + }, { + kind: "space", + text: " " + }, { + kind: "text", + text: "- " + }, { + kind: "link", + text: "{@link " + }, { + kind: "linkName", + target: { + file: "/a/someFile1.js", + start: { + line: 1, + offset: 1 + }, + end: { + line: 1, + offset: 12 + } + }, + text: "C" + }, { + kind: "link", + text: "}" + }] + }]; + assertSignatureHelpJSDoc({ + command: ts.projectSystem.protocol.CommandTypes.SignatureHelp, + displayPartsForJSDoc: true, + tags, + documentation: tags[0].text.slice(2) + }); + }); + it("for signature help-full, should provide a string for a working link in a comment", () => { + assertSignatureHelpJSDoc({ + command: ts.projectSystem.protocol.CommandTypes.SignatureHelpFull, + displayPartsForJSDoc: false, + tags: [{ + name: "param", + text: "y - {@link C}" + }], + documentation: [{ + kind: "text", + text: "- " + }, { + kind: "link", + text: "{@link " + }, { + kind: "linkName", + target: { + fileName: "/a/someFile1.js", + textSpan: { + length: 11, + start: 0 + } + }, + text: "C" + }, { + kind: "link", + text: "}" + }], + }); + }); + it("for signature help-full, should provide display parts for a working link in a comment", () => { + const tags = [{ + name: "param", + text: [{ + kind: "parameterName", + text: "y" + }, { + kind: "space", + text: " " + }, { + kind: "text", + text: "- " + }, { + kind: "link", + text: "{@link " + }, { + kind: "linkName", + target: { + fileName: "/a/someFile1.js", + textSpan: { + length: 11, + start: 0 + } + }, + text: "C" + }, { + kind: "link", + text: "}" + }] + }]; + assertSignatureHelpJSDoc({ + command: ts.projectSystem.protocol.CommandTypes.SignatureHelpFull, + displayPartsForJSDoc: true, + tags, + documentation: tags[0].text.slice(2), + }); + }); + + function assertCompletionsJSDoc(options: { + displayPartsForJSDoc: boolean, + command: ts.projectSystem.protocol.CommandTypes, + tags: unknown[] + }) { + const linkInParamJSDoc: ts.projectSystem.File = { + path: "/a/someFile1.js", + content: `class C { } +/** @param x - see {@link C} */ +function foo (x) { } +foo` + }; + const { command, displayPartsForJSDoc, tags } = options; + const session = ts.projectSystem.createSession(ts.projectSystem.createServerHost([linkInParamJSDoc, config])); + session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); + ts.projectSystem.openFilesForSession([linkInParamJSDoc], session); + const indexOfFoo = linkInParamJSDoc.content.lastIndexOf("fo"); + const completions = session.executeCommandSeq({ + command: command as ts.projectSystem.protocol.CommandTypes.CompletionDetails, + arguments: { + entryNames: ["foo"], + file: linkInParamJSDoc.path, + position: indexOfFoo, + } as ts.projectSystem.protocol.CompletionDetailsRequestArgs + }).response; + assert.deepEqual(completions, [{ + codeActions: undefined, + displayParts: [{ + kind: "keyword", + text: "function", + }, { + kind: "space", + text: " ", + }, { + kind: "functionName", + text: "foo", + }, { + kind: "punctuation", + text: "(", + }, { + kind: "parameterName", + text: "x", + }, { + kind: "punctuation", + text: ":", + }, { + kind: "space", + text: " ", + }, { + kind: "keyword", + text: "any", + }, { + kind: "punctuation", + text: ")", + }, { + kind: "punctuation", + text: ":", + }, { + kind: "space", + text: " ", + }, { + kind: "keyword", + text: "void", + }], + documentation: [], + kind: "function", + kindModifiers: "", + name: "foo", + source: undefined, + sourceDisplay: undefined, + tags, + }]); + } + it("for completions, should provide display parts for a working link in a comment", () => { + assertCompletionsJSDoc({ + command: ts.projectSystem.protocol.CommandTypes.CompletionDetails, + displayPartsForJSDoc: true, + tags: [{ name: "param", text: [{ kind: "parameterName", - text: "y" + text: "x" }, { kind: "space", text: " " }, { kind: "text", - text: "- " + text: "- see " }, { kind: "link", text: "{@link " @@ -433,49 +619,13 @@ x(1)` kind: "linkName", target: { file: "/a/someFile1.js", - start: { + end: { line: 1, - offset: 1 + offset: 12, }, - end: { + start: { line: 1, - offset: 12 - } - }, - text: "C" - }, { - kind: "link", - text: "}" - }] - }]; - assertSignatureHelpJSDoc({ - command: protocol.CommandTypes.SignatureHelp, - displayPartsForJSDoc: true, - tags, - documentation: tags[0].text.slice(2) - }); - }); - it("for signature help-full, should provide a string for a working link in a comment", () => { - assertSignatureHelpJSDoc({ - command: protocol.CommandTypes.SignatureHelpFull, - displayPartsForJSDoc: false, - tags: [{ - name: "param", - text: "y - {@link C}" - }], - documentation: [{ - kind: "text", - text: "- " - }, { - kind: "link", - text: "{@link " - }, { - kind: "linkName", - target: { - fileName: "/a/someFile1.js", - textSpan: { - length: 11, - start: 0 + offset: 1, } }, text: "C" @@ -483,20 +633,34 @@ x(1)` kind: "link", text: "}" }], - }); + }], + }); + }); + it("for completions, should provide a string for a working link in a comment", () => { + assertCompletionsJSDoc({ + command: ts.projectSystem.protocol.CommandTypes.CompletionDetails, + displayPartsForJSDoc: false, + tags: [{ + name: "param", + text: "x - see {@link C}", + }], }); - it("for signature help-full, should provide display parts for a working link in a comment", () => { - const tags = [{ + }); + it("for completions-full, should provide display parts for a working link in a comment", () => { + assertCompletionsJSDoc({ + command: ts.projectSystem.protocol.CommandTypes.CompletionDetailsFull, + displayPartsForJSDoc: true, + tags: [{ name: "param", text: [{ kind: "parameterName", - text: "y" + text: "x" }, { kind: "space", text: " " }, { kind: "text", - text: "- " + text: "- see " }, { kind: "link", text: "{@link " @@ -513,182 +677,18 @@ x(1)` }, { kind: "link", text: "}" - }] - }]; - assertSignatureHelpJSDoc({ - command: protocol.CommandTypes.SignatureHelpFull, - displayPartsForJSDoc: true, - tags, - documentation: tags[0].text.slice(2), - }); - }); - - function assertCompletionsJSDoc(options: { - displayPartsForJSDoc: boolean, - command: protocol.CommandTypes, - tags: unknown[] - }) { - const linkInParamJSDoc: File = { - path: "/a/someFile1.js", - content: `class C { } -/** @param x - see {@link C} */ -function foo (x) { } -foo` - }; - const { command, displayPartsForJSDoc, tags } = options; - const session = createSession(createServerHost([linkInParamJSDoc, config])); - session.getProjectService().setHostConfiguration({ preferences: { displayPartsForJSDoc } }); - openFilesForSession([linkInParamJSDoc], session); - const indexOfFoo = linkInParamJSDoc.content.lastIndexOf("fo"); - const completions = session.executeCommandSeq({ - command: command as protocol.CommandTypes.CompletionDetails, - arguments: { - entryNames: ["foo"], - file: linkInParamJSDoc.path, - position: indexOfFoo, - } as protocol.CompletionDetailsRequestArgs - }).response; - assert.deepEqual(completions, [{ - codeActions: undefined, - displayParts: [{ - kind: "keyword", - text: "function", - }, { - kind: "space", - text: " ", - }, { - kind: "functionName", - text: "foo", - }, { - kind: "punctuation", - text: "(", - }, { - kind: "parameterName", - text: "x", - }, { - kind: "punctuation", - text: ":", - }, { - kind: "space", - text: " ", - }, { - kind: "keyword", - text: "any", - }, { - kind: "punctuation", - text: ")", - }, { - kind: "punctuation", - text: ":", - }, { - kind: "space", - text: " ", - }, { - kind: "keyword", - text: "void", - }], - documentation: [], - kind: "function", - kindModifiers: "", - name: "foo", - source: undefined, - sourceDisplay: undefined, - tags, - }]); - } - it("for completions, should provide display parts for a working link in a comment", () => { - assertCompletionsJSDoc({ - command: protocol.CommandTypes.CompletionDetails, - displayPartsForJSDoc: true, - tags: [{ - name: "param", - text: [{ - kind: "parameterName", - text: "x" - }, { - kind: "space", - text: " " - }, { - kind: "text", - text: "- see " - }, { - kind: "link", - text: "{@link " - }, { - kind: "linkName", - target: { - file: "/a/someFile1.js", - end: { - line: 1, - offset: 12, - }, - start: { - line: 1, - offset: 1, - } - }, - text: "C" - }, { - kind: "link", - text: "}" - }], }], - }); + }], }); - it("for completions, should provide a string for a working link in a comment", () => { - assertCompletionsJSDoc({ - command: protocol.CommandTypes.CompletionDetails, - displayPartsForJSDoc: false, - tags: [{ - name: "param", - text: "x - see {@link C}", - }], - }); - }); - it("for completions-full, should provide display parts for a working link in a comment", () => { - assertCompletionsJSDoc({ - command: protocol.CommandTypes.CompletionDetailsFull, - displayPartsForJSDoc: true, - tags: [{ - name: "param", - text: [{ - kind: "parameterName", - text: "x" - }, { - kind: "space", - text: " " - }, { - kind: "text", - text: "- see " - }, { - kind: "link", - text: "{@link " - }, { - kind: "linkName", - target: { - fileName: "/a/someFile1.js", - textSpan: { - length: 11, - start: 0 - } - }, - text: "C" - }, { - kind: "link", - text: "}" - }], - }], - }); - }); - it("for completions-full, should provide a string for a working link in a comment", () => { - assertCompletionsJSDoc({ - command: protocol.CommandTypes.CompletionDetailsFull, - displayPartsForJSDoc: false, - tags: [{ - name: "param", - text: "x - see {@link C}", - }], - }); + }); + it("for completions-full, should provide a string for a working link in a comment", () => { + assertCompletionsJSDoc({ + command: ts.projectSystem.protocol.CommandTypes.CompletionDetailsFull, + displayPartsForJSDoc: false, + tags: [{ + name: "param", + text: "x - see {@link C}", + }], }); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/languageService.ts b/src/testRunner/unittests/tsserver/languageService.ts index 8e7bf0eedcdaa..5353b73ccdcb4 100644 --- a/src/testRunner/unittests/tsserver/languageService.ts +++ b/src/testRunner/unittests/tsserver/languageService.ts @@ -1,68 +1,69 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: languageService", () => { - it("should work correctly on case-sensitive file systems", () => { - const lib = { - path: "/a/Lib/lib.d.ts", - content: "let x: number" - }; - const f = { - path: "/a/b/app.ts", - content: "let x = 1;" - }; - const host = createServerHost([lib, f], { executingFilePath: "/a/Lib/tsc.js", useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host); - projectService.openClientFile(f.path); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - projectService.inferredProjects[0].getLanguageService().getProgram(); - }); +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; - it("should support multiple projects with the same file under differing `paths` settings", () => { - const files = [ - { - path: "/project/shared.ts", - content: Utils.dedent` +describe("unittests:: tsserver:: languageService", () => { + it("should work correctly on case-sensitive file systems", () => { + const lib = { + path: "/a/Lib/lib.d.ts", + content: "let x: number" + }; + const f = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const host = ts.projectSystem.createServerHost([lib, f], { executingFilePath: "/a/Lib/tsc.js", useCaseSensitiveFileNames: true }); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + projectService.inferredProjects[0].getLanguageService().getProgram(); + }); + + it("should support multiple projects with the same file under differing `paths` settings", () => { + const files = [ + { + path: "/project/shared.ts", + content: Utils.dedent` import {foo_a} from "foo"; ` - }, - { - path: `/project/a/tsconfig.json`, - content: `{ "compilerOptions": { "paths": { "foo": ["./foo.d.ts"] } }, "files": ["./index.ts", "./foo.d.ts"] }` - }, - { - path: `/project/a/foo.d.ts`, - content: Utils.dedent` + }, + { + path: `/project/a/tsconfig.json`, + content: `{ "compilerOptions": { "paths": { "foo": ["./foo.d.ts"] } }, "files": ["./index.ts", "./foo.d.ts"] }` + }, + { + path: `/project/a/foo.d.ts`, + content: Utils.dedent` export const foo_a = 1; ` - }, - { - path: "/project/a/index.ts", - content: `import "../shared";` - }, - { - path: `/project/b/tsconfig.json`, - content: `{ "compilerOptions": { "paths": { "foo": ["./foo.d.ts"] } }, "files": ["./index.ts", "./foo.d.ts"] }` - }, - { - path: `/project/b/foo.d.ts`, - content: Utils.dedent` + }, + { + path: "/project/a/index.ts", + content: `import "../shared";` + }, + { + path: `/project/b/tsconfig.json`, + content: `{ "compilerOptions": { "paths": { "foo": ["./foo.d.ts"] } }, "files": ["./index.ts", "./foo.d.ts"] }` + }, + { + path: `/project/b/foo.d.ts`, + content: Utils.dedent` export const foo_b = 1; ` - }, - { - path: "/project/b/index.ts", - content: `import "../shared";` - } - ]; + }, + { + path: "/project/b/index.ts", + content: `import "../shared";` + } + ]; - const host = createServerHost(files, { executingFilePath: "/project/tsc.js", useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host); - projectService.openClientFile(files[3].path); - projectService.openClientFile(files[6].path); - projectService.checkNumberOfProjects({ configuredProjects: 2 }); - const proj1Diags = projectService.configuredProjects.get(files[1].path)!.getLanguageService().getProgram()!.getSemanticDiagnostics(); - Debug.assertEqual(proj1Diags.length, 0); - const proj2Diags = projectService.configuredProjects.get(files[4].path)!.getLanguageService().getProgram()!.getSemanticDiagnostics(); - Debug.assertEqual(proj2Diags.length, 1); - }); + const host = ts.projectSystem.createServerHost(files, { executingFilePath: "/project/tsc.js", useCaseSensitiveFileNames: true }); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(files[3].path); + projectService.openClientFile(files[6].path); + projectService.checkNumberOfProjects({ configuredProjects: 2 }); + const proj1Diags = projectService.configuredProjects.get(files[1].path)!.getLanguageService().getProgram()!.getSemanticDiagnostics(); + ts.Debug.assertEqual(proj1Diags.length, 0); + const proj2Diags = projectService.configuredProjects.get(files[4].path)!.getLanguageService().getProgram()!.getSemanticDiagnostics(); + ts.Debug.assertEqual(proj2Diags.length, 1); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts b/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts index eb254789d671e..928ec383d4f28 100644 --- a/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts +++ b/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts @@ -1,55 +1,55 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: maxNodeModuleJsDepth for inferred projects", () => { - it("should be set to 2 if the project has js root files", () => { - const file1: File = { - path: "/a/b/file1.js", - content: `var t = require("test"); t.` - }; - const moduleFile: File = { - path: "/a/b/node_modules/test/index.js", - content: `var v = 10; module.exports = v;` - }; - - const host = createServerHost([file1, moduleFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - - let project = projectService.inferredProjects[0]; - let options = project.getCompilationSettings(); - assert.isTrue(options.maxNodeModuleJsDepth === 2); - - // Assert the option sticks - projectService.setCompilerOptionsForInferredProjects({ target: ScriptTarget.ES2016 }); - project = projectService.inferredProjects[0]; - options = project.getCompilationSettings(); - assert.isTrue(options.maxNodeModuleJsDepth === 2); - }); - - it("should return to normal state when all js root files are removed from project", () => { - const file1 = { - path: "/a/file1.ts", - content: "let x =1;" - }; - const file2 = { - path: "/a/file2.js", - content: "let x =1;" - }; - - const host = createServerHost([file1, file2, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - - projectService.openClientFile(file1.path); - checkNumberOfInferredProjects(projectService, 1); - let project = projectService.inferredProjects[0]; - assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); - - projectService.openClientFile(file2.path); - project = projectService.inferredProjects[0]; - assert.isTrue(project.getCompilationSettings().maxNodeModuleJsDepth === 2); - - projectService.closeClientFile(file2.path); - project = projectService.inferredProjects[0]; - assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); - }); +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: maxNodeModuleJsDepth for inferred projects", () => { + it("should be set to 2 if the project has js root files", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/file1.js", + content: `var t = require("test"); t.` + }; + const moduleFile: ts.projectSystem.File = { + path: "/a/b/node_modules/test/index.js", + content: `var v = 10; module.exports = v;` + }; + + const host = ts.projectSystem.createServerHost([file1, moduleFile]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(file1.path); + + let project = projectService.inferredProjects[0]; + let options = project.getCompilationSettings(); + assert.isTrue(options.maxNodeModuleJsDepth === 2); + + // Assert the option sticks + projectService.setCompilerOptionsForInferredProjects({ target: ts.ScriptTarget.ES2016 }); + project = projectService.inferredProjects[0]; + options = project.getCompilationSettings(); + assert.isTrue(options.maxNodeModuleJsDepth === 2); }); -} + + it("should return to normal state when all js root files are removed from project", () => { + const file1 = { + path: "/a/file1.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/file2.js", + content: "let x =1;" + }; + + const host = ts.projectSystem.createServerHost([file1, file2, ts.projectSystem.libFile]); + const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true }); + + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 1); + let project = projectService.inferredProjects[0]; + assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); + + projectService.openClientFile(file2.path); + project = projectService.inferredProjects[0]; + assert.isTrue(project.getCompilationSettings().maxNodeModuleJsDepth === 2); + + projectService.closeClientFile(file2.path); + project = projectService.inferredProjects[0]; + assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); + }); +}); diff --git a/src/testRunner/unittests/tsserver/metadataInResponse.ts b/src/testRunner/unittests/tsserver/metadataInResponse.ts index 8ea9d5ff7f742..35d3081ebc60c 100644 --- a/src/testRunner/unittests/tsserver/metadataInResponse.ts +++ b/src/testRunner/unittests/tsserver/metadataInResponse.ts @@ -1,104 +1,105 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: with metadata in response", () => { - const metadata = "Extra Info"; - function verifyOutput(host: TestServerHost, expectedResponse: protocol.Response) { - const output = host.getOutput().map(mapOutputToJson); - assert.deepEqual(output, [expectedResponse]); - host.clearOutput(); - } +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; - function verifyCommandWithMetadata(session: TestSession, host: TestServerHost, command: Partial, expectedResponseBody: U) { - command.seq = session.getSeq(); - command.type = "request"; - session.onMessage(JSON.stringify(command)); - verifyOutput(host, expectedResponseBody ? - { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: true, body: expectedResponseBody, metadata } : - { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: false, message: "No content available." } - ); - } +describe("unittests:: tsserver:: with metadata in response", () => { + const metadata = "Extra Info"; + function verifyOutput(host: ts.projectSystem.TestServerHost, expectedResponse: ts.projectSystem.protocol.Response) { + const output = host.getOutput().map(ts.projectSystem.mapOutputToJson); + assert.deepEqual(output, [expectedResponse]); + host.clearOutput(); + } - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; - const tsconfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { plugins: [{ name: "myplugin" }] } - }) - }; - function createHostWithPlugin(files: readonly File[]) { - const host = createServerHost(files); - host.require = (_initialPath, moduleName) => { - assert.equal(moduleName, "myplugin"); - return { - module: () => ({ - create(info: server.PluginCreateInfo) { - const proxy = Harness.LanguageService.makeDefaultProxy(info); - proxy.getCompletionsAtPosition = (filename, position, options) => { - const result = info.languageService.getCompletionsAtPosition(filename, position, options); - if (result) { - result.metadata = metadata; - } - return result; - }; - return proxy; - } - }), - error: undefined - }; - }; - return host; - } + function verifyCommandWithMetadata(session: ts.projectSystem.TestSession, host: ts.projectSystem.TestServerHost, command: Partial, expectedResponseBody: U) { + command.seq = session.getSeq(); + command.type = "request"; + session.onMessage(JSON.stringify(command)); + verifyOutput(host, expectedResponseBody ? + { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: true, body: expectedResponseBody, metadata } : + { seq: 0, type: "response", command: command.command!, request_seq: command.seq, success: false, message: "No content available." } + ); + } - describe("With completion requests", () => { - const completionRequestArgs: protocol.CompletionsRequestArgs = { - file: aTs.path, - line: 1, - offset: aTs.content.indexOf("this.") + 1 + "this.".length + const aTs: ts.projectSystem.File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; + const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { plugins: [{ name: "myplugin" }] } + }) + }; + function createHostWithPlugin(files: readonly ts.projectSystem.File[]) { + const host = ts.projectSystem.createServerHost(files); + host.require = (_initialPath, moduleName) => { + assert.equal(moduleName, "myplugin"); + return { + module: () => ({ + create(info: ts.server.PluginCreateInfo) { + const proxy = Harness.LanguageService.makeDefaultProxy(info); + proxy.getCompletionsAtPosition = (filename, position, options) => { + const result = info.languageService.getCompletionsAtPosition(filename, position, options); + if (result) { + result.metadata = metadata; + } + return result; + }; + return proxy; + } + }), + error: undefined }; - const expectedCompletionEntries: readonly protocol.CompletionEntry[] = [ - { name: "foo", kind: ScriptElementKind.memberFunctionElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority }, - { name: "prop", kind: ScriptElementKind.memberVariableElement, kindModifiers: "", sortText: Completions.SortText.LocationPriority } - ]; + }; + return host; + } - it("can pass through metadata when the command returns array", () => { - const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - verifyCommandWithMetadata(session, host, { - command: protocol.CommandTypes.Completions, - arguments: completionRequestArgs - }, expectedCompletionEntries); - }); + describe("With completion requests", () => { + const completionRequestArgs: ts.projectSystem.protocol.CompletionsRequestArgs = { + file: aTs.path, + line: 1, + offset: aTs.content.indexOf("this.") + 1 + "this.".length + }; + const expectedCompletionEntries: readonly ts.projectSystem.protocol.CompletionEntry[] = [ + { name: "foo", kind: ts.ScriptElementKind.memberFunctionElement, kindModifiers: "", sortText: ts.Completions.SortText.LocationPriority }, + { name: "prop", kind: ts.ScriptElementKind.memberVariableElement, kindModifiers: "", sortText: ts.Completions.SortText.LocationPriority } + ]; - it("can pass through metadata when the command returns object", () => { - const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - verifyCommandWithMetadata(session, host, { - command: protocol.CommandTypes.CompletionInfo, - arguments: completionRequestArgs - }, { - flags: 0, - isGlobalCompletion: false, - isMemberCompletion: true, - isNewIdentifierLocation: false, - optionalReplacementSpan: { - start: { line: 1, offset: aTs.content.indexOf("prop;") + 1 }, - end: { line: 1, offset: aTs.content.indexOf("prop;") + 1 + "prop".length } - }, - entries: expectedCompletionEntries - }); - }); + it("can pass through metadata when the command returns array", () => { + const host = createHostWithPlugin([aTs, tsconfig]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([aTs], session); + verifyCommandWithMetadata(session, host, { + command: ts.projectSystem.protocol.CommandTypes.Completions, + arguments: completionRequestArgs + }, expectedCompletionEntries); + }); - it("returns undefined correctly", () => { - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { const x = 0; } }` }; - const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - verifyCommandWithMetadata(session, host, { - command: protocol.CommandTypes.Completions, - arguments: { file: aTs.path, line: 1, offset: aTs.content.indexOf("x") + 1 } - }, /*expectedResponseBody*/ undefined); + it("can pass through metadata when the command returns object", () => { + const host = createHostWithPlugin([aTs, tsconfig]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([aTs], session); + verifyCommandWithMetadata(session, host, { + command: ts.projectSystem.protocol.CommandTypes.CompletionInfo, + arguments: completionRequestArgs + }, { + flags: 0, + isGlobalCompletion: false, + isMemberCompletion: true, + isNewIdentifierLocation: false, + optionalReplacementSpan: { + start: { line: 1, offset: aTs.content.indexOf("prop;") + 1 }, + end: { line: 1, offset: aTs.content.indexOf("prop;") + 1 + "prop".length } + }, + entries: expectedCompletionEntries }); }); + + it("returns undefined correctly", () => { + const aTs: ts.projectSystem.File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { const x = 0; } }` }; + const host = createHostWithPlugin([aTs, tsconfig]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([aTs], session); + verifyCommandWithMetadata(session, host, { + command: ts.projectSystem.protocol.CommandTypes.Completions, + arguments: { file: aTs.path, line: 1, offset: aTs.content.indexOf("x") + 1 } + }, /*expectedResponseBody*/ undefined); + }); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/moduleResolution.ts b/src/testRunner/unittests/tsserver/moduleResolution.ts index 914de1b3a22d3..0ff53167e2662 100644 --- a/src/testRunner/unittests/tsserver/moduleResolution.ts +++ b/src/testRunner/unittests/tsserver/moduleResolution.ts @@ -1,121 +1,122 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: moduleResolution", () => { - describe("package json file is edited", () => { - function setup(packageFileContents: string) { - const configFile: File = { - path: `${tscWatch.projectRoot}/src/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - target: "es2016", - module: "Node16", - outDir: "../out", - traceResolution: true, - } - }) - }; - const packageFile: File = { - path: `${tscWatch.projectRoot}/package.json`, - content: packageFileContents - }; - const fileA: File = { - path: `${tscWatch.projectRoot}/src/fileA.ts`, - content: Utils.dedent` +import * as ts from "../../_namespaces/ts"; +import * as Utils from "../../_namespaces/Utils"; + +describe("unittests:: tsserver:: moduleResolution", () => { + describe("package json file is edited", () => { + function setup(packageFileContents: string) { + const configFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + target: "es2016", + module: "Node16", + outDir: "../out", + traceResolution: true, + } + }) + }; + const packageFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/package.json`, + content: packageFileContents + }; + const fileA: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/fileA.ts`, + content: Utils.dedent` import { foo } from "./fileB.mjs"; foo(); ` - }; - const fileB: File = { - path: `${tscWatch.projectRoot}/src/fileB.mts`, - content: Utils.dedent` + }; + const fileB: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/fileB.mts`, + content: Utils.dedent` export function foo() { } ` - }; - const host = createServerHost([configFile, fileA, fileB, packageFile, { ...libFile, path: "/a/lib/lib.es2016.full.d.ts" }]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([fileA], session); - return { - host, session, packageFile, - verifyErr: () => verifyGetErrRequest({ files: [fileA], session, host }), - }; - } - it("package json file is edited", () => { - const { host, session, packageFile, verifyErr } = setup(JSON.stringify({ name: "app", version: "1.0.0" })); + }; + const host = ts.projectSystem.createServerHost([configFile, fileA, fileB, packageFile, { ...ts.projectSystem.libFile, path: "/a/lib/lib.es2016.full.d.ts" }]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([fileA], session); + return { + host, session, packageFile, + verifyErr: () => ts.projectSystem.verifyGetErrRequest({ files: [fileA], session, host }), + }; + } + it("package json file is edited", () => { + const { host, session, packageFile, verifyErr } = setup(JSON.stringify({ name: "app", version: "1.0.0" })); - session.logger.info("Modify package json file to add type module"); - host.writeFile(packageFile.path, JSON.stringify({ - name: "app", version: "1.0.0", type: "module", - })); - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - verifyErr(); + session.logger.info("Modify package json file to add type module"); + host.writeFile(packageFile.path, JSON.stringify({ + name: "app", version: "1.0.0", type: "module", + })); + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + verifyErr(); - session.logger.info("Modify package json file to remove type module"); - host.writeFile(packageFile.path, packageFile.content); - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - verifyErr(); + session.logger.info("Modify package json file to remove type module"); + host.writeFile(packageFile.path, packageFile.content); + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + verifyErr(); - session.logger.info("Delete package.json"); - host.deleteFile(packageFile.path); - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - verifyErr(); + session.logger.info("Delete package.json"); + host.deleteFile(packageFile.path); + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + verifyErr(); - session.logger.info("Modify package json file to add type module"); - host.writeFile(packageFile.path, JSON.stringify({ - name: "app", version: "1.0.0", type: "module", - })); - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - verifyErr(); + session.logger.info("Modify package json file to add type module"); + host.writeFile(packageFile.path, JSON.stringify({ + name: "app", version: "1.0.0", type: "module", + })); + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + verifyErr(); - session.logger.info("Delete package.json"); - host.deleteFile(packageFile.path); - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - verifyErr(); + session.logger.info("Delete package.json"); + host.deleteFile(packageFile.path); + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + verifyErr(); - baselineTsserverLogs("moduleResolution", "package json file is edited", session); - }); + ts.projectSystem.baselineTsserverLogs("moduleResolution", "package json file is edited", session); + }); - it("package json file is edited when package json with type module exists", () => { - const { host, session, packageFile, verifyErr } = setup(JSON.stringify({ - name: "app", version: "1.0.0", type: "module", - })); + it("package json file is edited when package json with type module exists", () => { + const { host, session, packageFile, verifyErr } = setup(JSON.stringify({ + name: "app", version: "1.0.0", type: "module", + })); - session.logger.info("Modify package json file to remove type module"); - host.writeFile(packageFile.path, JSON.stringify({ name: "app", version: "1.0.0" })); - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - verifyErr(); + session.logger.info("Modify package json file to remove type module"); + host.writeFile(packageFile.path, JSON.stringify({ name: "app", version: "1.0.0" })); + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + verifyErr(); - session.logger.info("Modify package json file to add type module"); - host.writeFile(packageFile.path, packageFile.content); - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - verifyErr(); + session.logger.info("Modify package json file to add type module"); + host.writeFile(packageFile.path, packageFile.content); + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + verifyErr(); - session.logger.info("Delete package.json"); - host.deleteFile(packageFile.path); - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - verifyErr(); + session.logger.info("Delete package.json"); + host.deleteFile(packageFile.path); + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + verifyErr(); - session.logger.info("Modify package json file to without type module"); - host.writeFile(packageFile.path, JSON.stringify({ name: "app", version: "1.0.0" })); - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - verifyErr(); + session.logger.info("Modify package json file to without type module"); + host.writeFile(packageFile.path, JSON.stringify({ name: "app", version: "1.0.0" })); + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + verifyErr(); - session.logger.info("Delete package.json"); - host.deleteFile(packageFile.path); - host.runQueuedTimeoutCallbacks(); // Failed lookup updates - host.runQueuedTimeoutCallbacks(); // Actual update - verifyErr(); + session.logger.info("Delete package.json"); + host.deleteFile(packageFile.path); + host.runQueuedTimeoutCallbacks(); // Failed lookup updates + host.runQueuedTimeoutCallbacks(); // Actual update + verifyErr(); - baselineTsserverLogs("moduleResolution", "package json file is edited when package json with type module exists", session); - }); + ts.projectSystem.baselineTsserverLogs("moduleResolution", "package json file is edited when package json with type module exists", session); }); }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts index b9423ec98dd96..59d05ef1a9b32 100644 --- a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts +++ b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts @@ -1,150 +1,150 @@ -namespace ts.projectSystem { - const packageJson: File = { - path: "/package.json", - content: `{ "dependencies": { "mobx": "*" } }` - }; - const aTs: File = { - path: "/src/a.ts", - content: "export const foo = 0;", - }; - const bTs: File = { - path: "/src/b.ts", - content: "foo", - }; - const cTs: File = { - path: "/src/c.ts", - content: "import ", - }; - const bSymlink: SymLink = { - path: "/src/b-link.ts", - symLink: "./b.ts", - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: `{ "include": ["src"] }`, - }; - const ambientDeclaration: File = { - path: "/src/ambient.d.ts", - content: "declare module 'ambient' {}" - }; - const mobxPackageJson: File = { - path: "/node_modules/mobx/package.json", - content: `{ "name": "mobx", "version": "1.0.0" }` - }; - const mobxDts: File = { - path: "/node_modules/mobx/index.d.ts", - content: "export declare function observable(): unknown;" - }; +import * as ts from "../../_namespaces/ts"; - describe("unittests:: tsserver:: moduleSpecifierCache", () => { - it("caches importability within a file", () => { - const { moduleSpecifierCache } = setup(); - assert.isFalse(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {}, {})?.isBlockedByPackageJsonDependencies); - }); +const packageJson: ts.projectSystem.File = { + path: "/package.json", + content: `{ "dependencies": { "mobx": "*" } }` +}; +const aTs: ts.projectSystem.File = { + path: "/src/a.ts", + content: "export const foo = 0;", +}; +const bTs: ts.projectSystem.File = { + path: "/src/b.ts", + content: "foo", +}; +const cTs: ts.projectSystem.File = { + path: "/src/c.ts", + content: "import ", +}; +const bSymlink: ts.projectSystem.SymLink = { + path: "/src/b-link.ts", + symLink: "./b.ts", +}; +const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: `{ "include": ["src"] }`, +}; +const ambientDeclaration: ts.projectSystem.File = { + path: "/src/ambient.d.ts", + content: "declare module 'ambient' {}" +}; +const mobxPackageJson: ts.projectSystem.File = { + path: "/node_modules/mobx/package.json", + content: `{ "name": "mobx", "version": "1.0.0" }` +}; +const mobxDts: ts.projectSystem.File = { + path: "/node_modules/mobx/index.d.ts", + content: "export declare function observable(): unknown;" +}; - it("caches module specifiers within a file", () => { - const { moduleSpecifierCache, triggerCompletions } = setup(); - // Completion at an import statement will calculate and cache module specifiers - triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 }); - const mobxCache = moduleSpecifierCache.get(cTs.path as Path, mobxDts.path as Path, {}, {}); - assert.deepEqual(mobxCache, { - modulePaths: [{ - path: mobxDts.path, - isInNodeModules: true, - isRedirect: false - }], - moduleSpecifiers: ["mobx"], - isBlockedByPackageJsonDependencies: false, - }); - }); +describe("unittests:: tsserver:: moduleSpecifierCache", () => { + it("caches importability within a file", () => { + const { moduleSpecifierCache } = setup(); + assert.isFalse(moduleSpecifierCache.get(bTs.path as ts.Path, aTs.path as ts.Path, {}, {})?.isBlockedByPackageJsonDependencies); + }); - it("invalidates module specifiers when changes happen in contained node_modules directories", () => { - const { host, session, moduleSpecifierCache, triggerCompletions } = setup(host => createLoggerWithInMemoryLogs(host)); - // Completion at an import statement will calculate and cache module specifiers - triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 }); - host.writeFile("/node_modules/.staging/mobx-12345678/package.json", "{}"); - host.runQueuedTimeoutCallbacks(); - assert.equal(moduleSpecifierCache.count(), 0); - baselineTsserverLogs("moduleSpecifierCache", "invalidates module specifiers when changes happen in contained node_modules directories", session); + it("caches module specifiers within a file", () => { + const { moduleSpecifierCache, triggerCompletions } = setup(); + // Completion at an import statement will calculate and cache module specifiers + triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 }); + const mobxCache = moduleSpecifierCache.get(cTs.path as ts.Path, mobxDts.path as ts.Path, {}, {}); + assert.deepEqual(mobxCache, { + modulePaths: [{ + path: mobxDts.path, + isInNodeModules: true, + isRedirect: false + }], + moduleSpecifiers: ["mobx"], + isBlockedByPackageJsonDependencies: false, }); + }); - it("does not invalidate the cache when new files are added", () => { - const { host, moduleSpecifierCache } = setup(); - host.writeFile("/src/a2.ts", aTs.content); - host.runQueuedTimeoutCallbacks(); - assert.isFalse(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {}, {})?.isBlockedByPackageJsonDependencies); - }); + it("invalidates module specifiers when changes happen in contained node_modules directories", () => { + const { host, session, moduleSpecifierCache, triggerCompletions } = setup(host => ts.projectSystem.createLoggerWithInMemoryLogs(host)); + // Completion at an import statement will calculate and cache module specifiers + triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 }); + host.writeFile("/node_modules/.staging/mobx-12345678/package.json", "{}"); + host.runQueuedTimeoutCallbacks(); + assert.equal(moduleSpecifierCache.count(), 0); + ts.projectSystem.baselineTsserverLogs("moduleSpecifierCache", "invalidates module specifiers when changes happen in contained node_modules directories", session); + }); - it("invalidates the cache when symlinks are added or removed", () => { - const { host, moduleSpecifierCache } = setup(); - host.renameFile(bSymlink.path, "/src/b-link2.ts"); - host.runQueuedTimeoutCallbacks(); - assert.equal(moduleSpecifierCache.count(), 0); - }); + it("does not invalidate the cache when new files are added", () => { + const { host, moduleSpecifierCache } = setup(); + host.writeFile("/src/a2.ts", aTs.content); + host.runQueuedTimeoutCallbacks(); + assert.isFalse(moduleSpecifierCache.get(bTs.path as ts.Path, aTs.path as ts.Path, {}, {})?.isBlockedByPackageJsonDependencies); + }); - it("invalidates the cache when local package.json changes", () => { - const { host, moduleSpecifierCache } = setup(); - host.writeFile("/package.json", `{}`); - host.runQueuedTimeoutCallbacks(); - assert.equal(moduleSpecifierCache.count(), 0); - }); + it("invalidates the cache when symlinks are added or removed", () => { + const { host, moduleSpecifierCache } = setup(); + host.renameFile(bSymlink.path, "/src/b-link2.ts"); + host.runQueuedTimeoutCallbacks(); + assert.equal(moduleSpecifierCache.count(), 0); + }); - it("invalidates the cache when module resolution settings change", () => { - const { host, moduleSpecifierCache } = setup(); - host.writeFile(tsconfig.path, `{ "compilerOptions": { "moduleResolution": "classic" }, "include": ["src"] }`); - host.runQueuedTimeoutCallbacks(); - assert.equal(moduleSpecifierCache.count(), 0); - }); + it("invalidates the cache when local package.json changes", () => { + const { host, moduleSpecifierCache } = setup(); + host.writeFile("/package.json", `{}`); + host.runQueuedTimeoutCallbacks(); + assert.equal(moduleSpecifierCache.count(), 0); + }); - it("invalidates the cache when user preferences change", () => { - const { moduleSpecifierCache, session, triggerCompletions } = setup(); - const preferences: UserPreferences = { importModuleSpecifierPreference: "project-relative" }; + it("invalidates the cache when module resolution settings change", () => { + const { host, moduleSpecifierCache } = setup(); + host.writeFile(tsconfig.path, `{ "compilerOptions": { "moduleResolution": "classic" }, "include": ["src"] }`); + host.runQueuedTimeoutCallbacks(); + assert.equal(moduleSpecifierCache.count(), 0); + }); - assert.ok(getWithPreferences({})); - executeSessionRequest(session, protocol.CommandTypes.Configure, { preferences }); - // Nothing changes yet - assert.ok(getWithPreferences({})); - assert.isUndefined(getWithPreferences(preferences)); - // Completions will request (getting nothing) and set the cache with new preferences - triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); - assert.isUndefined(getWithPreferences({})); - assert.ok(getWithPreferences(preferences)); + it("invalidates the cache when user preferences change", () => { + const { moduleSpecifierCache, session, triggerCompletions } = setup(); + const preferences: ts.UserPreferences = { importModuleSpecifierPreference: "project-relative" }; - // Test other affecting preference - executeSessionRequest(session, protocol.CommandTypes.Configure, { - preferences: { importModuleSpecifierEnding: "js" }, - }); - triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); - assert.isUndefined(getWithPreferences(preferences)); + assert.ok(getWithPreferences({})); + ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.Configure, { preferences }); + // Nothing changes yet + assert.ok(getWithPreferences({})); + assert.isUndefined(getWithPreferences(preferences)); + // Completions will request (getting nothing) and set the cache with new preferences + triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); + assert.isUndefined(getWithPreferences({})); + assert.ok(getWithPreferences(preferences)); - function getWithPreferences(preferences: UserPreferences) { - return moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, preferences, {}); - } + // Test other affecting preference + ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.Configure, { + preferences: { importModuleSpecifierEnding: "js" }, }); + triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); + assert.isUndefined(getWithPreferences(preferences)); + + function getWithPreferences(preferences: ts.UserPreferences) { + return moduleSpecifierCache.get(bTs.path as ts.Path, aTs.path as ts.Path, preferences, {}); + } }); +}); - function setup(createLogger?: (host: TestServerHost) => Logger) { - const host = createServerHost([aTs, bTs, cTs, bSymlink, ambientDeclaration, tsconfig, packageJson, mobxPackageJson, mobxDts]); - const session = createSession(host, createLogger && { logger: createLogger(host) }); - openFilesForSession([aTs, bTs, cTs], session); - const projectService = session.getProjectService(); - const project = configuredProjectAt(projectService, 0); - executeSessionRequest(session, protocol.CommandTypes.Configure, { - preferences: { - includeCompletionsForImportStatements: true, - includeCompletionsForModuleExports: true, - includeCompletionsWithInsertText: true, - includeCompletionsWithSnippetText: true, - }, - }); - triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); +function setup(createLogger?: (host: ts.projectSystem.TestServerHost) => ts.projectSystem.Logger) { + const host = ts.projectSystem.createServerHost([aTs, bTs, cTs, bSymlink, ambientDeclaration, tsconfig, packageJson, mobxPackageJson, mobxDts]); + const session = ts.projectSystem.createSession(host, createLogger && { logger: createLogger(host) }); + ts.projectSystem.openFilesForSession([aTs, bTs, cTs], session); + const projectService = session.getProjectService(); + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.Configure, { + preferences: { + includeCompletionsForImportStatements: true, + includeCompletionsForModuleExports: true, + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: true, + }, + }); + triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); - return { host, project, projectService, session, moduleSpecifierCache: project.getModuleSpecifierCache(), triggerCompletions }; + return { host, project, projectService, session, moduleSpecifierCache: project.getModuleSpecifierCache(), triggerCompletions }; - function triggerCompletions(requestLocation: protocol.FileLocationRequestArgs) { - executeSessionRequest(session, protocol.CommandTypes.CompletionInfo, { - ...requestLocation, - }); - } + function triggerCompletions(requestLocation: ts.projectSystem.protocol.FileLocationRequestArgs) { + ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.CompletionInfo, { + ...requestLocation, + }); } } diff --git a/src/testRunner/unittests/tsserver/navTo.ts b/src/testRunner/unittests/tsserver/navTo.ts index e484afaeab9c5..1d181b45510b6 100644 --- a/src/testRunner/unittests/tsserver/navTo.ts +++ b/src/testRunner/unittests/tsserver/navTo.ts @@ -1,52 +1,53 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: navigate-to for javascript project", () => { - function findNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { - return find(items, item => item.name === itemName && item.kind === itemKind); - } +import * as ts from "../../_namespaces/ts"; - function containsNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { - return findNavToItem(items, itemName, itemKind) !== undefined; - } +describe("unittests:: tsserver:: navigate-to for javascript project", () => { + function findNavToItem(items: ts.projectSystem.protocol.NavtoItem[], itemName: string, itemKind: string) { + return ts.find(items, item => item.name === itemName && item.kind === itemKind); + } + + function containsNavToItem(items: ts.projectSystem.protocol.NavtoItem[], itemName: string, itemKind: string) { + return findNavToItem(items, itemName, itemKind) !== undefined; + } - it("should not include type symbols", () => { - const file1: File = { - path: "/a/b/file1.js", - content: "function foo() {}" - }; - const configFile: File = { - path: "/a/b/jsconfig.json", - content: "{}" - }; - const host = createServerHost([file1, configFile, libFile]); - const session = createSession(host); - openFilesForSession([file1], session); + it("should not include type symbols", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/file1.js", + content: "function foo() {}" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/jsconfig.json", + content: "{}" + }; + const host = ts.projectSystem.createServerHost([file1, configFile, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([file1], session); - // Try to find some interface type defined in lib.d.ts - const libTypeNavToRequest = makeSessionRequest(CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path }); - const items = session.executeCommand(libTypeNavToRequest).response as protocol.NavtoItem[]; - assert.isFalse(containsNavToItem(items, "Document", "interface"), `Found lib.d.ts symbol in JavaScript project nav to request result.`); + // Try to find some interface type defined in lib.d.ts + const libTypeNavToRequest = ts.projectSystem.makeSessionRequest(ts.projectSystem.CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path }); + const items = session.executeCommand(libTypeNavToRequest).response as ts.projectSystem.protocol.NavtoItem[]; + assert.isFalse(containsNavToItem(items, "Document", "interface"), `Found lib.d.ts symbol in JavaScript project nav to request result.`); - const localFunctionNavToRequst = makeSessionRequest(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); - const items2 = session.executeCommand(localFunctionNavToRequst).response as protocol.NavtoItem[]; - assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`); - }); + const localFunctionNavToRequst = ts.projectSystem.makeSessionRequest(ts.projectSystem.CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); + const items2 = session.executeCommand(localFunctionNavToRequst).response as ts.projectSystem.protocol.NavtoItem[]; + assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`); + }); - it("should de-duplicate symbols", () => { - const configFile1: File = { - path: "/a/tsconfig.json", - content: `{ + it("should de-duplicate symbols", () => { + const configFile1: ts.projectSystem.File = { + path: "/a/tsconfig.json", + content: `{ "compilerOptions": { "composite": true } }` - }; - const file1: File = { - path: "/a/index.ts", - content: "export const abcdef = 1;" - }; - const configFile2: File = { - path: "/b/tsconfig.json", - content: `{ + }; + const file1: ts.projectSystem.File = { + path: "/a/index.ts", + content: "export const abcdef = 1;" + }; + const configFile2: ts.projectSystem.File = { + path: "/b/tsconfig.json", + content: `{ "compilerOptions": { "composite": true }, @@ -54,45 +55,45 @@ namespace ts.projectSystem { { "path": "../a" } ] }` - }; - const file2: File = { - path: "/b/index.ts", - content: `import a = require("../a"); + }; + const file2: ts.projectSystem.File = { + path: "/b/index.ts", + content: `import a = require("../a"); export const ghijkl = a.abcdef;` - }; - const host = createServerHost([configFile1, file1, configFile2, file2]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([file1, file2], session); + }; + const host = ts.projectSystem.createServerHost([configFile1, file1, configFile2, file2]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([file1, file2], session); - const request = makeSessionRequest(CommandNames.Navto, { searchValue: "abcdef", file: file1.path }); - session.executeCommand(request).response as protocol.NavtoItem[]; + const request = ts.projectSystem.makeSessionRequest(ts.projectSystem.CommandNames.Navto, { searchValue: "abcdef", file: file1.path }); + session.executeCommand(request).response as ts.projectSystem.protocol.NavtoItem[]; - baselineTsserverLogs("navTo", "should de-duplicate symbols", session); - }); + ts.projectSystem.baselineTsserverLogs("navTo", "should de-duplicate symbols", session); + }); - it("should de-duplicate symbols when searching all projects", () => { - const solutionConfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - references: [{ path: "./a" }, { path: "./b" }], - files: [], - }) - }; - const configFile1: File = { - path: "/a/tsconfig.json", - content: `{ + it("should de-duplicate symbols when searching all projects", () => { + const solutionConfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: JSON.stringify({ + references: [{ path: "./a" }, { path: "./b" }], + files: [], + }) + }; + const configFile1: ts.projectSystem.File = { + path: "/a/tsconfig.json", + content: `{ "compilerOptions": { "composite": true } }` - }; - const file1: File = { - path: "/a/index.ts", - content: "export const abcdef = 1;" - }; - const configFile2: File = { - path: "/b/tsconfig.json", - content: `{ + }; + const file1: ts.projectSystem.File = { + path: "/a/index.ts", + content: "export const abcdef = 1;" + }; + const configFile2: ts.projectSystem.File = { + path: "/b/tsconfig.json", + content: `{ "compilerOptions": { "composite": true }, @@ -100,40 +101,39 @@ export const ghijkl = a.abcdef;` { "path": "../a" } ] }` - }; - const file2: File = { - path: "/b/index.ts", - content: `import a = require("../a"); + }; + const file2: ts.projectSystem.File = { + path: "/b/index.ts", + content: `import a = require("../a"); export const ghijkl = a.abcdef;` - }; - const host = createServerHost([configFile1, file1, configFile2, file2, solutionConfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([file1], session); + }; + const host = ts.projectSystem.createServerHost([configFile1, file1, configFile2, file2, solutionConfig]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([file1], session); - const request = makeSessionRequest(CommandNames.Navto, { searchValue: "abcdef" }); - session.executeCommand(request).response as protocol.NavtoItem[]; - baselineTsserverLogs("navTo", "should de-duplicate symbols when searching all projects", session); - }); + const request = ts.projectSystem.makeSessionRequest(ts.projectSystem.CommandNames.Navto, { searchValue: "abcdef" }); + session.executeCommand(request).response as ts.projectSystem.protocol.NavtoItem[]; + ts.projectSystem.baselineTsserverLogs("navTo", "should de-duplicate symbols when searching all projects", session); + }); - it("should work with Deprecated", () => { - const file1: File = { - path: "/a/b/file1.js", - content: "/** @deprecated */\nfunction foo () {}" - }; - const configFile: File = { - path: "/a/b/jsconfig.json", - content: "{}" - }; - const host = createServerHost([file1, configFile, libFile]); - const session = createSession(host); - openFilesForSession([file1], session); + it("should work with Deprecated", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/file1.js", + content: "/** @deprecated */\nfunction foo () {}" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/jsconfig.json", + content: "{}" + }; + const host = ts.projectSystem.createServerHost([file1, configFile, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([file1], session); - // Try to find some interface type defined in lib.d.ts - const libTypeNavToRequest = makeSessionRequest(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); - const items = session.executeCommand(libTypeNavToRequest).response as protocol.NavtoItem[]; - const fooItem = findNavToItem(items, "foo", "function"); - assert.isNotNull(fooItem, `Cannot find function symbol "foo".`); - assert.isTrue(fooItem?.kindModifiers?.includes("deprecated")); - }); + // Try to find some interface type defined in lib.d.ts + const libTypeNavToRequest = ts.projectSystem.makeSessionRequest(ts.projectSystem.CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); + const items = session.executeCommand(libTypeNavToRequest).response as ts.projectSystem.protocol.NavtoItem[]; + const fooItem = findNavToItem(items, "foo", "function"); + assert.isNotNull(fooItem, `Cannot find function symbol "foo".`); + assert.isTrue(fooItem?.kindModifiers?.includes("deprecated")); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/occurences.ts b/src/testRunner/unittests/tsserver/occurences.ts index 25dfb3c9e90f2..d797525916245 100644 --- a/src/testRunner/unittests/tsserver/occurences.ts +++ b/src/testRunner/unittests/tsserver/occurences.ts @@ -1,47 +1,47 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: occurrence highlight on string", () => { - it("should be marked if only on string values", () => { - const file1: File = { - path: "/a/b/file1.ts", - content: `let t1 = "div";\nlet t2 = "div";\nlet t3 = { "div": 123 };\nlet t4 = t3["div"];` - }; +import * as ts from "../../_namespaces/ts"; - const host = createServerHost([file1]); - const session = createSession(host); - const projectService = session.getProjectService(); +describe("unittests:: tsserver:: occurrence highlight on string", () => { + it("should be marked if only on string values", () => { + const file1: ts.projectSystem.File = { + path: "/a/b/file1.ts", + content: `let t1 = "div";\nlet t2 = "div";\nlet t3 = { "div": 123 };\nlet t4 = t3["div"];` + }; - projectService.openClientFile(file1.path); - { - const highlightRequest = makeSessionRequest( - CommandNames.Occurrences, - { file: file1.path, line: 1, offset: 11 } - ); - const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; - const firstOccurence = highlightResponse[0]; - assert.isTrue(firstOccurence.isInString, "Highlights should be marked with isInString"); - } + const host = ts.projectSystem.createServerHost([file1]); + const session = ts.projectSystem.createSession(host); + const projectService = session.getProjectService(); - { - const highlightRequest = makeSessionRequest( - CommandNames.Occurrences, - { file: file1.path, line: 3, offset: 13 } - ); - const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; - assert.isTrue(highlightResponse.length === 2); - const firstOccurence = highlightResponse[0]; - assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on property name"); - } + projectService.openClientFile(file1.path); + { + const highlightRequest = ts.projectSystem.makeSessionRequest( + ts.projectSystem.CommandNames.Occurrences, + { file: file1.path, line: 1, offset: 11 } + ); + const highlightResponse = session.executeCommand(highlightRequest).response as ts.projectSystem.protocol.OccurrencesResponseItem[]; + const firstOccurence = highlightResponse[0]; + assert.isTrue(firstOccurence.isInString, "Highlights should be marked with isInString"); + } - { - const highlightRequest = makeSessionRequest( - CommandNames.Occurrences, - { file: file1.path, line: 4, offset: 14 } - ); - const highlightResponse = session.executeCommand(highlightRequest).response as protocol.OccurrencesResponseItem[]; - assert.isTrue(highlightResponse.length === 2); - const firstOccurence = highlightResponse[0]; - assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on indexer"); - } - }); + { + const highlightRequest = ts.projectSystem.makeSessionRequest( + ts.projectSystem.CommandNames.Occurrences, + { file: file1.path, line: 3, offset: 13 } + ); + const highlightResponse = session.executeCommand(highlightRequest).response as ts.projectSystem.protocol.OccurrencesResponseItem[]; + assert.isTrue(highlightResponse.length === 2); + const firstOccurence = highlightResponse[0]; + assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on property name"); + } + + { + const highlightRequest = ts.projectSystem.makeSessionRequest( + ts.projectSystem.CommandNames.Occurrences, + { file: file1.path, line: 4, offset: 14 } + ); + const highlightResponse = session.executeCommand(highlightRequest).response as ts.projectSystem.protocol.OccurrencesResponseItem[]; + assert.isTrue(highlightResponse.length === 2); + const firstOccurence = highlightResponse[0]; + assert.isUndefined(firstOccurence.isInString, "Highlights should not be marked with isInString if on indexer"); + } }); -} +}); diff --git a/src/testRunner/unittests/tsserver/openFile.ts b/src/testRunner/unittests/tsserver/openFile.ts index de791a3e495a5..ae8e284d616b3 100644 --- a/src/testRunner/unittests/tsserver/openFile.ts +++ b/src/testRunner/unittests/tsserver/openFile.ts @@ -1,138 +1,139 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: Open-file", () => { - it("can be reloaded with empty content", () => { - const f = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const projectFileName = "externalProject"; - const host = createServerHost([f]); - const projectService = createProjectService(host); - // create a project - projectService.openExternalProject({ projectFileName, rootFiles: [toExternalFile(f.path)], options: {} }); - projectService.checkNumberOfProjects({ externalProjects: 1 }); - - const p = projectService.externalProjects[0]; - // force to load the content of the file - p.updateGraph(); - - const scriptInfo = p.getScriptInfo(f.path)!; - checkSnapLength(scriptInfo.getSnapshot(), f.content.length); - - // open project and replace its content with empty string - projectService.openClientFile(f.path, ""); - checkSnapLength(scriptInfo.getSnapshot(), 0); +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: Open-file", () => { + it("can be reloaded with empty content", () => { + const f = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const projectFileName = "externalProject"; + const host = ts.projectSystem.createServerHost([f]); + const projectService = ts.projectSystem.createProjectService(host); + // create a project + projectService.openExternalProject({ projectFileName, rootFiles: [ts.projectSystem.toExternalFile(f.path)], options: {} }); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + + const p = projectService.externalProjects[0]; + // force to load the content of the file + p.updateGraph(); + + const scriptInfo = p.getScriptInfo(f.path)!; + checkSnapLength(scriptInfo.getSnapshot(), f.content.length); + + // open project and replace its content with empty string + projectService.openClientFile(f.path, ""); + checkSnapLength(scriptInfo.getSnapshot(), 0); + }); + function checkSnapLength(snap: ts.IScriptSnapshot, expectedLength: number) { + assert.equal(snap.getLength(), expectedLength, "Incorrect snapshot size"); + } + + function verifyOpenFileWorks(useCaseSensitiveFileNames: boolean) { + const file1: ts.projectSystem.File = { + path: "/a/b/src/app.ts", + content: "let x = 10;" + }; + const file2: ts.projectSystem.File = { + path: "/a/B/lib/module2.ts", + content: "let z = 10;" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: "" + }; + const configFile2: ts.projectSystem.File = { + path: "/a/tsconfig.json", + content: "" + }; + const host = ts.projectSystem.createServerHost([file1, file2, configFile, configFile2], { + useCaseSensitiveFileNames }); - function checkSnapLength(snap: IScriptSnapshot, expectedLength: number) { - assert.equal(snap.getLength(), expectedLength, "Incorrect snapshot size"); - } + const service = ts.projectSystem.createProjectService(host); - function verifyOpenFileWorks(useCaseSensitiveFileNames: boolean) { - const file1: File = { - path: "/a/b/src/app.ts", - content: "let x = 10;" - }; - const file2: File = { - path: "/a/B/lib/module2.ts", - content: "let z = 10;" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: "" - }; - const configFile2: File = { - path: "/a/tsconfig.json", - content: "" - }; - const host = createServerHost([file1, file2, configFile, configFile2], { - useCaseSensitiveFileNames - }); - const service = createProjectService(host); - - // Open file1 -> configFile - verifyConfigFileName(file1, "/a", configFile); - verifyConfigFileName(file1, "/a/b", configFile); - verifyConfigFileName(file1, "/a/B", configFile); - - // Open file2 use root "/a/b" - verifyConfigFileName(file2, "/a", useCaseSensitiveFileNames ? configFile2 : configFile); - verifyConfigFileName(file2, "/a/b", useCaseSensitiveFileNames ? configFile2 : configFile); - verifyConfigFileName(file2, "/a/B", useCaseSensitiveFileNames ? undefined : configFile); - - function verifyConfigFileName(file: File, projectRoot: string, expectedConfigFile: File | undefined) { - const { configFileName } = service.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRoot); - assert.equal(configFileName, expectedConfigFile && expectedConfigFile.path); - service.closeClientFile(file.path); - } + // Open file1 -> configFile + verifyConfigFileName(file1, "/a", configFile); + verifyConfigFileName(file1, "/a/b", configFile); + verifyConfigFileName(file1, "/a/B", configFile); + + // Open file2 use root "/a/b" + verifyConfigFileName(file2, "/a", useCaseSensitiveFileNames ? configFile2 : configFile); + verifyConfigFileName(file2, "/a/b", useCaseSensitiveFileNames ? configFile2 : configFile); + verifyConfigFileName(file2, "/a/B", useCaseSensitiveFileNames ? undefined : configFile); + + function verifyConfigFileName(file: ts.projectSystem.File, projectRoot: string, expectedConfigFile: ts.projectSystem.File | undefined) { + const { configFileName } = service.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRoot); + assert.equal(configFileName, expectedConfigFile && expectedConfigFile.path); + service.closeClientFile(file.path); } - it("works when project root is used with case-sensitive system", () => { - verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ true); - }); + } + it("works when project root is used with case-sensitive system", () => { + verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ true); + }); - it("works when project root is used with case-insensitive system", () => { - verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ false); - }); + it("works when project root is used with case-insensitive system", () => { + verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ false); + }); - it("uses existing project even if project refresh is pending", () => { - const projectFolder = "/user/someuser/projects/myproject"; - const aFile: File = { - path: `${projectFolder}/src/a.ts`, - content: "export const x = 0;" - }; - const configFile: File = { - path: `${projectFolder}/tsconfig.json`, - content: "{}" - }; - const files = [aFile, configFile, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(aFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); - verifyProject(); - - const bFile: File = { - path: `${projectFolder}/src/b.ts`, - content: `export {}; declare module "./a" { export const y: number; }` - }; - files.push(bFile); - host.writeFile(bFile.path, bFile.content); - service.openClientFile(bFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); - verifyProject(); - - function verifyProject() { - assert.isDefined(service.configuredProjects.get(configFile.path)); - const project = service.configuredProjects.get(configFile.path)!; - checkProjectActualFiles(project, files.map(f => f.path)); - } - }); + it("uses existing project even if project refresh is pending", () => { + const projectFolder = "/user/someuser/projects/myproject"; + const aFile: ts.projectSystem.File = { + path: `${projectFolder}/src/a.ts`, + content: "export const x = 0;" + }; + const configFile: ts.projectSystem.File = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const files = [aFile, configFile, ts.projectSystem.libFile]; + const host = ts.projectSystem.createServerHost(files); + const service = ts.projectSystem.createProjectService(host); + service.openClientFile(aFile.path, /*fileContent*/ undefined, ts.ScriptKind.TS, projectFolder); + verifyProject(); - it("can open same file again", () => { - const projectFolder = "/user/someuser/projects/myproject"; - const aFile: File = { - path: `${projectFolder}/src/a.ts`, - content: "export const x = 0;" - }; - const configFile: File = { - path: `${projectFolder}/tsconfig.json`, - content: "{}" - }; - const files = [aFile, configFile, libFile]; - const host = createServerHost(files); - const service = createProjectService(host); - verifyProject(aFile.content); - verifyProject(`${aFile.content}export const y = 10;`); - - function verifyProject(aFileContent: string) { - service.openClientFile(aFile.path, aFileContent, ScriptKind.TS, projectFolder); - const project = service.configuredProjects.get(configFile.path)!; - checkProjectActualFiles(project, files.map(f => f.path)); - assert.equal(project.getCurrentProgram()?.getSourceFile(aFile.path)!.text, aFileContent); - } - }); + const bFile: ts.projectSystem.File = { + path: `${projectFolder}/src/b.ts`, + content: `export {}; declare module "./a" { export const y: number; }` + }; + files.push(bFile); + host.writeFile(bFile.path, bFile.content); + service.openClientFile(bFile.path, /*fileContent*/ undefined, ts.ScriptKind.TS, projectFolder); + verifyProject(); + + function verifyProject() { + assert.isDefined(service.configuredProjects.get(configFile.path)); + const project = service.configuredProjects.get(configFile.path)!; + ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path)); + } + }); - it("when file makes edits to add/remove comment directives, they are handled correcrly", () => { - const file: File = { - path: `${tscWatch.projectRoot}/file.ts`, - content: `const x = 10; + it("can open same file again", () => { + const projectFolder = "/user/someuser/projects/myproject"; + const aFile: ts.projectSystem.File = { + path: `${projectFolder}/src/a.ts`, + content: "export const x = 0;" + }; + const configFile: ts.projectSystem.File = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const files = [aFile, configFile, ts.projectSystem.libFile]; + const host = ts.projectSystem.createServerHost(files); + const service = ts.projectSystem.createProjectService(host); + verifyProject(aFile.content); + verifyProject(`${aFile.content}export const y = 10;`); + + function verifyProject(aFileContent: string) { + service.openClientFile(aFile.path, aFileContent, ts.ScriptKind.TS, projectFolder); + const project = service.configuredProjects.get(configFile.path)!; + ts.projectSystem.checkProjectActualFiles(project, files.map(f => f.path)); + assert.equal(project.getCurrentProgram()?.getSourceFile(aFile.path)!.text, aFileContent); + } + }); + + it("when file makes edits to add/remove comment directives, they are handled correcrly", () => { + const file: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/file.ts`, + content: `const x = 10; function foo() { // @ts-ignore let y: string = x; @@ -145,43 +146,42 @@ function bar() { } foo(); bar();` - }; - const host = createServerHost([file, libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([file], session); - verifyGetErrRequest({ session, host, files: [file] }); - - // Remove first ts-ignore and check only first error is reported - const tsIgnoreComment = `// @ts-ignore`; - const locationOfTsIgnore = protocolTextSpanFromSubstring(file.content, tsIgnoreComment); - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: file.path, - textChanges: [{ - newText: " ", - ...locationOfTsIgnore - }] + }; + const host = ts.projectSystem.createServerHost([file, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([file], session); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [file] }); + + // Remove first ts-ignore and check only first error is reported + const tsIgnoreComment = `// @ts-ignore`; + const locationOfTsIgnore = ts.projectSystem.protocolTextSpanFromSubstring(file.content, tsIgnoreComment); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: file.path, + textChanges: [{ + newText: " ", + ...locationOfTsIgnore }] - } - }); - verifyGetErrRequest({ session, host, files: [file] }); - // Revert the change and no errors should be reported - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: file.path, - textChanges: [{ - newText: tsIgnoreComment, - ...locationOfTsIgnore - }] + }] + } + }); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [file] }); + // Revert the change and no errors should be reported + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: file.path, + textChanges: [{ + newText: tsIgnoreComment, + ...locationOfTsIgnore }] - } - }); - verifyGetErrRequest({ session, host, files: [file] }); - baselineTsserverLogs("openfile", "when file makes edits to add/remove comment directives, they are handled correcrly", session); + }] + } }); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [file] }); + ts.projectSystem.baselineTsserverLogs("openfile", "when file makes edits to add/remove comment directives, they are handled correcrly", session); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/packageJsonInfo.ts b/src/testRunner/unittests/tsserver/packageJsonInfo.ts index b931d984c1275..d6bf2116a8c5a 100644 --- a/src/testRunner/unittests/tsserver/packageJsonInfo.ts +++ b/src/testRunner/unittests/tsserver/packageJsonInfo.ts @@ -1,112 +1,112 @@ -namespace ts.projectSystem { - const tsConfig: File = { - path: "/tsconfig.json", - content: "{}" - }; - const packageJsonContent = { - dependencies: { - redux: "*" - }, - peerDependencies: { - react: "*" - }, - optionalDependencies: { - typescript: "*" - }, - devDependencies: { - webpack: "*" - } - }; - const packageJson: File = { - path: "/package.json", - content: JSON.stringify(packageJsonContent, undefined, 2) - }; +import * as ts from "../../_namespaces/ts"; - describe("unittests:: tsserver:: packageJsonInfo", () => { - it("detects new package.json files that are added, caches them, and watches them", () => { - // Initialize project without package.json - const { projectService, host } = setup([tsConfig]); - assert.isUndefined(projectService.packageJsonCache.getInDirectory("/" as Path)); +const tsConfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: "{}" +}; +const packageJsonContent = { + dependencies: { + redux: "*" + }, + peerDependencies: { + react: "*" + }, + optionalDependencies: { + typescript: "*" + }, + devDependencies: { + webpack: "*" + } +}; +const packageJson: ts.projectSystem.File = { + path: "/package.json", + content: JSON.stringify(packageJsonContent, undefined, 2) +}; - // Add package.json - host.writeFile(packageJson.path, packageJson.content); - let packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.ok(packageJsonInfo); - assert.ok(packageJsonInfo.dependencies); - assert.ok(packageJsonInfo.devDependencies); - assert.ok(packageJsonInfo.peerDependencies); - assert.ok(packageJsonInfo.optionalDependencies); +describe("unittests:: tsserver:: packageJsonInfo", () => { + it("detects new package.json files that are added, caches them, and watches them", () => { + // Initialize project without package.json + const { projectService, host } = setup([tsConfig]); + assert.isUndefined(projectService.packageJsonCache.getInDirectory("/" as ts.Path)); - // Edit package.json - host.writeFile(packageJson.path, JSON.stringify({ - ...packageJsonContent, - dependencies: undefined - })); - packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.isUndefined(packageJsonInfo.dependencies); - }); + // Add package.json + host.writeFile(packageJson.path, packageJson.content); + let packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as ts.Path)!; + assert.ok(packageJsonInfo); + assert.ok(packageJsonInfo.dependencies); + assert.ok(packageJsonInfo.devDependencies); + assert.ok(packageJsonInfo.peerDependencies); + assert.ok(packageJsonInfo.optionalDependencies); - it("finds package.json on demand, watches for deletion, and removes them from cache", () => { - // Initialize project with package.json - const { projectService, host } = setup(); - projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); - assert.ok(projectService.packageJsonCache.getInDirectory("/" as Path)); + // Edit package.json + host.writeFile(packageJson.path, JSON.stringify({ + ...packageJsonContent, + dependencies: undefined + })); + packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as ts.Path)!; + assert.isUndefined(packageJsonInfo.dependencies); + }); - // Delete package.json - host.deleteFile(packageJson.path); - assert.isUndefined(projectService.packageJsonCache.getInDirectory("/" as Path)); - }); + it("finds package.json on demand, watches for deletion, and removes them from cache", () => { + // Initialize project with package.json + const { projectService, host } = setup(); + projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as ts.Path); + assert.ok(projectService.packageJsonCache.getInDirectory("/" as ts.Path)); - it("finds multiple package.json files when present", () => { - // Initialize project with package.json at root - const { projectService, host } = setup(); - // Add package.json in /src - host.writeFile("/src/package.json", packageJson.content); - assert.lengthOf(projectService.getPackageJsonsVisibleToFile("/a.ts" as Path), 1); - assert.lengthOf(projectService.getPackageJsonsVisibleToFile("/src/b.ts" as Path), 2); - }); + // Delete package.json + host.deleteFile(packageJson.path); + assert.isUndefined(projectService.packageJsonCache.getInDirectory("/" as ts.Path)); + }); - it("handles errors in json parsing of package.json", () => { - const packageJsonContent = `{ "mod" }`; - const { projectService, host } = setup([tsConfig, { path: packageJson.path, content: packageJsonContent }]); - projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); - const packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.isFalse(packageJsonInfo.parseable); + it("finds multiple package.json files when present", () => { + // Initialize project with package.json at root + const { projectService, host } = setup(); + // Add package.json in /src + host.writeFile("/src/package.json", packageJson.content); + assert.lengthOf(projectService.getPackageJsonsVisibleToFile("/a.ts" as ts.Path), 1); + assert.lengthOf(projectService.getPackageJsonsVisibleToFile("/src/b.ts" as ts.Path), 2); + }); - host.writeFile(packageJson.path, packageJson.content); - projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); - const packageJsonInfo2 = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.ok(packageJsonInfo2); - assert.ok(packageJsonInfo2.dependencies); - assert.ok(packageJsonInfo2.devDependencies); - assert.ok(packageJsonInfo2.peerDependencies); - assert.ok(packageJsonInfo2.optionalDependencies); - }); + it("handles errors in json parsing of package.json", () => { + const packageJsonContent = `{ "mod" }`; + const { projectService, host } = setup([tsConfig, { path: packageJson.path, content: packageJsonContent }]); + projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as ts.Path); + const packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as ts.Path)!; + assert.isFalse(packageJsonInfo.parseable); - it("handles empty package.json", () => { - const packageJsonContent = ""; - const { projectService, host } = setup([tsConfig, { path: packageJson.path, content: packageJsonContent }]); - projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); - const packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.isFalse(packageJsonInfo.parseable); + host.writeFile(packageJson.path, packageJson.content); + projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as ts.Path); + const packageJsonInfo2 = projectService.packageJsonCache.getInDirectory("/" as ts.Path)!; + assert.ok(packageJsonInfo2); + assert.ok(packageJsonInfo2.dependencies); + assert.ok(packageJsonInfo2.devDependencies); + assert.ok(packageJsonInfo2.peerDependencies); + assert.ok(packageJsonInfo2.optionalDependencies); + }); + + it("handles empty package.json", () => { + const packageJsonContent = ""; + const { projectService, host } = setup([tsConfig, { path: packageJson.path, content: packageJsonContent }]); + projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as ts.Path); + const packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as ts.Path)!; + assert.isFalse(packageJsonInfo.parseable); - host.writeFile(packageJson.path, packageJson.content); - projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path); - const packageJsonInfo2 = projectService.packageJsonCache.getInDirectory("/" as Path)!; - assert.ok(packageJsonInfo2); - assert.ok(packageJsonInfo2.dependencies); - assert.ok(packageJsonInfo2.devDependencies); - assert.ok(packageJsonInfo2.peerDependencies); - assert.ok(packageJsonInfo2.optionalDependencies); - }); + host.writeFile(packageJson.path, packageJson.content); + projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as ts.Path); + const packageJsonInfo2 = projectService.packageJsonCache.getInDirectory("/" as ts.Path)!; + assert.ok(packageJsonInfo2); + assert.ok(packageJsonInfo2.dependencies); + assert.ok(packageJsonInfo2.devDependencies); + assert.ok(packageJsonInfo2.peerDependencies); + assert.ok(packageJsonInfo2.optionalDependencies); }); +}); - function setup(files: readonly File[] = [tsConfig, packageJson]) { - const host = createServerHost(files); - const session = createSession(host); - const projectService = session.getProjectService(); - projectService.openClientFile(files[0].path); - const project = configuredProjectAt(projectService, 0); - return { host, session, project, projectService }; - } +function setup(files: readonly ts.projectSystem.File[] = [tsConfig, packageJson]) { + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host); + const projectService = session.getProjectService(); + projectService.openClientFile(files[0].path); + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + return { host, session, project, projectService }; } diff --git a/src/testRunner/unittests/tsserver/partialSemanticServer.ts b/src/testRunner/unittests/tsserver/partialSemanticServer.ts index 2a7a4157c6ff1..60d4adb1ae445 100644 --- a/src/testRunner/unittests/tsserver/partialSemanticServer.ts +++ b/src/testRunner/unittests/tsserver/partialSemanticServer.ts @@ -1,226 +1,226 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: Semantic operations on partialSemanticServer", () => { - function setup() { - const file1: File = { - path: `${tscWatch.projectRoot}/a.ts`, - content: `import { y, cc } from "./b"; +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: Semantic operations on partialSemanticServer", () => { + function setup() { + const file1: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `import { y, cc } from "./b"; import { something } from "something"; class c { prop = "hello"; foo() { return this.prop; } }` - }; - const file2: File = { - path: `${tscWatch.projectRoot}/b.ts`, - content: `export { cc } from "./c"; + }; + const file2: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/b.ts`, + content: `export { cc } from "./c"; import { something } from "something"; export const y = 10;` - }; - const file3: File = { - path: `${tscWatch.projectRoot}/c.ts`, - content: `export const cc = 10;` - }; - const something: File = { - path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`, - content: "export const something = 10;" - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const host = createServerHost([file1, file2, file3, something, libFile, configFile]); - const session = createSession(host, { - serverMode: LanguageServiceMode.PartialSemantic, - useSingleInferredProject: true, - logger: createLoggerWithInMemoryLogs(host), + }; + const file3: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/c.ts`, + content: `export const cc = 10;` + }; + const something: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/something/index.d.ts`, + content: "export const something = 10;" + }; + const configFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const host = ts.projectSystem.createServerHost([file1, file2, file3, something, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host, { + serverMode: ts.LanguageServiceMode.PartialSemantic, + useSingleInferredProject: true, + logger: ts.projectSystem.createLoggerWithInMemoryLogs(host), + }); + return { host, session, file1, file2, file3, something, configFile }; + } + + it("open files are added to inferred project even if config file is present and semantic operations succeed", () => { + const { session, file1, file2 } = setup(); + const service = session.getProjectService(); + ts.projectSystem.openFilesForSession([file1], session); + const project = service.inferredProjects[0]; + verifyCompletions(); + + ts.projectSystem.openFilesForSession([file2], session); + ts.projectSystem.checkNumberOfProjects(service, { inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(project, [ts.projectSystem.libFile.path, file1.path, file2.path]); + verifyCompletions(); + + ts.projectSystem.baselineTsserverLogs("partialSemanticServer", "files are added to inferred project", session); + + function verifyCompletions() { + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Completions, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(file1, "prop", { index: 1 }) }); - return { host, session, file1, file2, file3, something, configFile }; } + }); - it("open files are added to inferred project even if config file is present and semantic operations succeed", () => { - const { session, file1, file2 } = setup(); - const service = session.getProjectService(); - openFilesForSession([file1], session); - const project = service.inferredProjects[0]; - verifyCompletions(); - - openFilesForSession([file2], session); - checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); - verifyCompletions(); - - baselineTsserverLogs("partialSemanticServer", "files are added to inferred project", session); - - function verifyCompletions() { - session.executeCommandSeq({ - command: protocol.CommandTypes.Completions, - arguments: protocolFileLocationFromSubstring(file1, "prop", { index: 1 }) - }); - } - }); - - it("throws on unsupported commands", () => { - const { session, file1 } = setup(); - const service = session.getProjectService(); - openFilesForSession([file1], session); - const request: protocol.SemanticDiagnosticsSyncRequest = { - type: "request", - seq: 1, - command: protocol.CommandTypes.SemanticDiagnosticsSync, - arguments: { file: file1.path } - }; - try { - session.executeCommand(request); - } - catch (e) { - session.logger.info(e.message); - } - - const project = service.inferredProjects[0]; - try { - project.getLanguageService().getSemanticDiagnostics(file1.path); - } - catch (e) { - session.logger.info(e.message); - } - baselineTsserverLogs("partialSemanticServer", "throws unsupported commands", session); - }); + it("throws on unsupported commands", () => { + const { session, file1 } = setup(); + const service = session.getProjectService(); + ts.projectSystem.openFilesForSession([file1], session); + const request: ts.projectSystem.protocol.SemanticDiagnosticsSyncRequest = { + type: "request", + seq: 1, + command: ts.projectSystem.protocol.CommandTypes.SemanticDiagnosticsSync, + arguments: { file: file1.path } + }; + try { + session.executeCommand(request); + } + catch (e) { + session.logger.info(e.message); + } - it("allows syntactic diagnostic commands", () => { - const file1: File = { - path: `${tscWatch.projectRoot}/a.ts`, - content: `if (a < (b + c) { }` - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: `{}` - }; - const expectedErrorMessage = "')' expected."; - - const host = createServerHost([file1, libFile, configFile]); - const session = createSession(host, { - serverMode: LanguageServiceMode.PartialSemantic, - useSingleInferredProject: true, - logger: createLoggerWithInMemoryLogs(host) - }); + const project = service.inferredProjects[0]; + try { + project.getLanguageService().getSemanticDiagnostics(file1.path); + } + catch (e) { + session.logger.info(e.message); + } + ts.projectSystem.baselineTsserverLogs("partialSemanticServer", "throws unsupported commands", session); + }); - const service = session.getProjectService(); - openFilesForSession([file1], session); - const request: protocol.SyntacticDiagnosticsSyncRequest = { - type: "request", - seq: 1, - command: protocol.CommandTypes.SyntacticDiagnosticsSync, - arguments: { file: file1.path } - }; - const response = session.executeCommandSeq(request).response as protocol.SyntacticDiagnosticsSyncResponse["body"]; - assert.isDefined(response); - assert.equal(response!.length, 1); - assert.equal((response![0] as protocol.Diagnostic).text, expectedErrorMessage); - - const project = service.inferredProjects[0]; - const diagnostics = project.getLanguageService().getSyntacticDiagnostics(file1.path); - assert.isTrue(diagnostics.length === 1); - assert.equal(diagnostics[0].messageText, expectedErrorMessage); - - verifyGetErrRequest({ session, host, files: [file1], skip: [{ semantic: true, suggestion: true }] }); - baselineTsserverLogs("partialSemanticServer", "syntactic diagnostics are returned with no error", session); + it("allows syntactic diagnostic commands", () => { + const file1: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `if (a < (b + c) { }` + }; + const configFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: `{}` + }; + const expectedErrorMessage = "')' expected."; + + const host = ts.projectSystem.createServerHost([file1, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host, { + serverMode: ts.LanguageServiceMode.PartialSemantic, + useSingleInferredProject: true, + logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); - it("should not include auto type reference directives", () => { - const { host, session, file1 } = setup(); - const atTypes: File = { - path: `/node_modules/@types/somemodule/index.d.ts`, - content: "export const something = 10;" - }; - host.ensureFileOrFolder(atTypes); - openFilesForSession([file1], session); - baselineTsserverLogs("partialSemanticServer", "should not include auto type reference directives", session); - }); + const service = session.getProjectService(); + ts.projectSystem.openFilesForSession([file1], session); + const request: ts.projectSystem.protocol.SyntacticDiagnosticsSyncRequest = { + type: "request", + seq: 1, + command: ts.projectSystem.protocol.CommandTypes.SyntacticDiagnosticsSync, + arguments: { file: file1.path } + }; + const response = session.executeCommandSeq(request).response as ts.projectSystem.protocol.SyntacticDiagnosticsSyncResponse["body"]; + assert.isDefined(response); + assert.equal(response!.length, 1); + assert.equal((response![0] as ts.projectSystem.protocol.Diagnostic).text, expectedErrorMessage); + + const project = service.inferredProjects[0]; + const diagnostics = project.getLanguageService().getSyntacticDiagnostics(file1.path); + assert.isTrue(diagnostics.length === 1); + assert.equal(diagnostics[0].messageText, expectedErrorMessage); + + ts.projectSystem.verifyGetErrRequest({ session, host, files: [file1], skip: [{ semantic: true, suggestion: true }] }); + ts.projectSystem.baselineTsserverLogs("partialSemanticServer", "syntactic diagnostics are returned with no error", session); + }); + + it("should not include auto type reference directives", () => { + const { host, session, file1 } = setup(); + const atTypes: ts.projectSystem.File = { + path: `/node_modules/@types/somemodule/index.d.ts`, + content: "export const something = 10;" + }; + host.ensureFileOrFolder(atTypes); + ts.projectSystem.openFilesForSession([file1], session); + ts.projectSystem.baselineTsserverLogs("partialSemanticServer", "should not include auto type reference directives", session); + }); - it("should not include referenced files from unopened files", () => { - const file1: File = { - path: `${tscWatch.projectRoot}/a.ts`, - content: `/// -/// + it("should not include referenced files from unopened files", () => { + const file1: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/a.ts`, + content: `/// +/// function fooA() { }` - }; - const file2: File = { - path: `${tscWatch.projectRoot}/b.ts`, - content: `/// -/// + }; + const file2: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/b.ts`, + content: `/// +/// function fooB() { }` - }; - const file3: File = { - path: `${tscWatch.projectRoot}/c.ts`, - content: `function fooC() { }` - }; - const something: File = { - path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`, - content: "function something() {}" - }; - const configFile: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - const host = createServerHost([file1, file2, file3, something, libFile, configFile]); - const session = createSession(host, { - serverMode: LanguageServiceMode.PartialSemantic, - useSingleInferredProject: true, - logger: createLoggerWithInMemoryLogs(host), - }); - openFilesForSession([file1], session); - baselineTsserverLogs("partialSemanticServer", "should not include referenced files from unopened files", session); + }; + const file3: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/c.ts`, + content: `function fooC() { }` + }; + const something: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/something/index.d.ts`, + content: "function something() {}" + }; + const configFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + const host = ts.projectSystem.createServerHost([file1, file2, file3, something, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host, { + serverMode: ts.LanguageServiceMode.PartialSemantic, + useSingleInferredProject: true, + logger: ts.projectSystem.createLoggerWithInMemoryLogs(host), }); + ts.projectSystem.openFilesForSession([file1], session); + ts.projectSystem.baselineTsserverLogs("partialSemanticServer", "should not include referenced files from unopened files", session); + }); - it("should not crash when external module name resolution is reused", () => { - const { session, file1, file2, file3 } = setup(); - openFilesForSession([file1], session); + it("should not crash when external module name resolution is reused", () => { + const { session, file1, file2, file3 } = setup(); + ts.projectSystem.openFilesForSession([file1], session); - // Close the file that contains non relative external module name and open some file that doesnt have non relative external module import - closeFilesForSession([file1], session); - openFilesForSession([file3], session); + // Close the file that contains non relative external module name and open some file that doesnt have non relative external module import + ts.projectSystem.closeFilesForSession([file1], session); + ts.projectSystem.openFilesForSession([file3], session); - // Open file with non relative external module name - openFilesForSession([file2], session); - baselineTsserverLogs("partialSemanticServer", "should not crash when external module name resolution is reused", session); - }); + // Open file with non relative external module name + ts.projectSystem.openFilesForSession([file2], session); + ts.projectSystem.baselineTsserverLogs("partialSemanticServer", "should not crash when external module name resolution is reused", session); + }); - it("should not create autoImportProvider or handle package jsons", () => { - const angularFormsDts: File = { - path: "/node_modules/@angular/forms/forms.d.ts", - content: "export declare class PatternValidator {}", - }; - const angularFormsPackageJson: File = { - path: "/node_modules/@angular/forms/package.json", - content: `{ "name": "@angular/forms", "typings": "./forms.d.ts" }`, - }; - const tsconfig: File = { - path: "/tsconfig.json", - content: `{ "compilerOptions": { "module": "commonjs" } }`, - }; - const packageJson: File = { - path: "/package.json", - content: `{ "dependencies": { "@angular/forms": "*", "@angular/core": "*" } }` - }; - const indexTs: File = { - path: "/index.ts", - content: "" - }; - const host = createServerHost([angularFormsDts, angularFormsPackageJson, tsconfig, packageJson, indexTs, libFile]); - const session = createSession(host, { serverMode: LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); - const service = session.getProjectService(); - openFilesForSession([indexTs], session); - const project = service.inferredProjects[0]; - assert.isFalse(project.autoImportProviderHost); - assert.isUndefined(project.getPackageJsonAutoImportProvider()); - assert.deepEqual(project.getPackageJsonsForAutoImport(), emptyArray); - }); + it("should not create autoImportProvider or handle package jsons", () => { + const angularFormsDts: ts.projectSystem.File = { + path: "/node_modules/@angular/forms/forms.d.ts", + content: "export declare class PatternValidator {}", + }; + const angularFormsPackageJson: ts.projectSystem.File = { + path: "/node_modules/@angular/forms/package.json", + content: `{ "name": "@angular/forms", "typings": "./forms.d.ts" }`, + }; + const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: `{ "compilerOptions": { "module": "commonjs" } }`, + }; + const packageJson: ts.projectSystem.File = { + path: "/package.json", + content: `{ "dependencies": { "@angular/forms": "*", "@angular/core": "*" } }` + }; + const indexTs: ts.projectSystem.File = { + path: "/index.ts", + content: "" + }; + const host = ts.projectSystem.createServerHost([angularFormsDts, angularFormsPackageJson, tsconfig, packageJson, indexTs, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { serverMode: ts.LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); + const service = session.getProjectService(); + ts.projectSystem.openFilesForSession([indexTs], session); + const project = service.inferredProjects[0]; + assert.isFalse(project.autoImportProviderHost); + assert.isUndefined(project.getPackageJsonAutoImportProvider()); + assert.deepEqual(project.getPackageJsonsForAutoImport(), ts.emptyArray); + }); - it("should support go-to-definition on module specifiers", () => { - const { session, file1 } = setup(); - openFilesForSession([file1], session); - session.executeCommandSeq({ - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: protocolFileLocationFromSubstring(file1, `"./b"`) - }); - baselineTsserverLogs("partialSemanticServer", "should support go-to-definition on module specifiers", session); + it("should support go-to-definition on module specifiers", () => { + const { session, file1 } = setup(); + ts.projectSystem.openFilesForSession([file1], session); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(file1, `"./b"`) }); + ts.projectSystem.baselineTsserverLogs("partialSemanticServer", "should support go-to-definition on module specifiers", session); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/plugins.ts b/src/testRunner/unittests/tsserver/plugins.ts index 0db19f7478b19..2c27ea4687ad3 100644 --- a/src/testRunner/unittests/tsserver/plugins.ts +++ b/src/testRunner/unittests/tsserver/plugins.ts @@ -1,149 +1,150 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: plugins loading", () => { - const testProtocolCommand = "testProtocolCommand"; - const testProtocolCommandRequest = "testProtocolCommandRequest"; - const testProtocolCommandResponse = "testProtocolCommandResponse"; +import * as ts from "../../_namespaces/ts"; +import * as Harness from "../../_namespaces/Harness"; - function createHostWithPlugin(files: readonly File[]) { - const host = createServerHost(files); - const pluginsLoaded: string[] = []; - const protocolHandlerRequests: [string, string][] = []; - host.require = (_initialPath, moduleName) => { - pluginsLoaded.push(moduleName); - return { - module: () => ({ - create(info: server.PluginCreateInfo) { - info.session?.addProtocolHandler(testProtocolCommand, request => { - protocolHandlerRequests.push([request.command, request.arguments]); - return { - response: testProtocolCommandResponse - }; - }); - return Harness.LanguageService.makeDefaultProxy(info); - } - }), - error: undefined - }; - }; - return { host, pluginsLoaded, protocolHandlerRequests }; - } +describe("unittests:: tsserver:: plugins loading", () => { + const testProtocolCommand = "testProtocolCommand"; + const testProtocolCommandRequest = "testProtocolCommandRequest"; + const testProtocolCommandResponse = "testProtocolCommandResponse"; - it("With local plugins", () => { - const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; - const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; - const tsconfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - plugins: [ - ...[...expectedToLoad, ...notToLoad].map(name => ({ name })), - { transform: "some-transform" } - ] + function createHostWithPlugin(files: readonly ts.projectSystem.File[]) { + const host = ts.projectSystem.createServerHost(files); + const pluginsLoaded: string[] = []; + const protocolHandlerRequests: [string, string][] = []; + host.require = (_initialPath, moduleName) => { + pluginsLoaded.push(moduleName); + return { + module: () => ({ + create(info: ts.server.PluginCreateInfo) { + info.session?.addProtocolHandler(testProtocolCommand, request => { + protocolHandlerRequests.push([request.command, request.arguments]); + return { + response: testProtocolCommandResponse + }; + }); + return Harness.LanguageService.makeDefaultProxy(info); } - }) + }), + error: undefined }; - const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, libFile]); - const service = createProjectService(host); - service.openClientFile(aTs.path); - assert.deepEqual(pluginsLoaded, expectedToLoad); - }); + }; + return { host, pluginsLoaded, protocolHandlerRequests }; + } - it("With global plugins", () => { - const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; - const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; - const tsconfig: File = { - path: "/tsconfig.json", - content: "{}" - }; - const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, libFile]); - const service = createProjectService(host, { globalPlugins: [...expectedToLoad, ...notToLoad] }); - service.openClientFile(aTs.path); - assert.deepEqual(pluginsLoaded, expectedToLoad); - }); - - it("With session and custom protocol message", () => { - const pluginName = "some-plugin"; - const expectedToLoad = [pluginName]; - const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; - const tsconfig: File = { - path: "/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { - plugins: [ - { name: pluginName } - ] - } - }) - }; + it("With local plugins", () => { + const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; + const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; + const aTs: ts.projectSystem.File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; + const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + plugins: [ + ...[...expectedToLoad, ...notToLoad].map(name => ({ name })), + { transform: "some-transform" } + ] + } + }) + }; + const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, ts.projectSystem.libFile]); + const service = ts.projectSystem.createProjectService(host); + service.openClientFile(aTs.path); + assert.deepEqual(pluginsLoaded, expectedToLoad); + }); - const { host, pluginsLoaded, protocolHandlerRequests } = createHostWithPlugin([aTs, tsconfig, libFile]); - const session = createSession(host); + it("With global plugins", () => { + const expectedToLoad = ["@myscoped/plugin", "unscopedPlugin"]; + const notToLoad = ["../myPlugin", "myPlugin/../malicious"]; + const aTs: ts.projectSystem.File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; + const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: "{}" + }; + const { host, pluginsLoaded } = createHostWithPlugin([aTs, tsconfig, ts.projectSystem.libFile]); + const service = ts.projectSystem.createProjectService(host, { globalPlugins: [...expectedToLoad, ...notToLoad] }); + service.openClientFile(aTs.path); + assert.deepEqual(pluginsLoaded, expectedToLoad); + }); - const service = createProjectService(host, { session }); - service.openClientFile(aTs.path); - assert.deepEqual(pluginsLoaded, expectedToLoad); + it("With session and custom protocol message", () => { + const pluginName = "some-plugin"; + const expectedToLoad = [pluginName]; + const aTs: ts.projectSystem.File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { return this.prop; } }` }; + const tsconfig: ts.projectSystem.File = { + path: "/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + plugins: [ + { name: pluginName } + ] + } + }) + }; - const resp = session.executeCommandSeq({ - command: testProtocolCommand, - arguments: testProtocolCommandRequest - }); + const { host, pluginsLoaded, protocolHandlerRequests } = createHostWithPlugin([aTs, tsconfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host); - assert.strictEqual(protocolHandlerRequests.length, 1); - const [command, args] = protocolHandlerRequests[0]; - assert.strictEqual(command, testProtocolCommand); - assert.strictEqual(args, testProtocolCommandRequest); + const service = ts.projectSystem.createProjectService(host, { session }); + service.openClientFile(aTs.path); + assert.deepEqual(pluginsLoaded, expectedToLoad); - const expectedResp: server.HandlerResponse = { - response: testProtocolCommandResponse - }; - assert.deepEqual(resp, expectedResp); + const resp = session.executeCommandSeq({ + command: testProtocolCommand, + arguments: testProtocolCommandRequest }); - it("gets external files with config file reload", () => { - const aTs: File = { path: `${tscWatch.projectRoot}/a.ts`, content: `export const x = 10;` }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - plugins: [{ name: "some-plugin" }] - } - }) - }; - - const externalFiles: MapLike = { - "some-plugin": ["someFile.txt"], - "some-other-plugin": ["someOtherFile.txt"], - }; + assert.strictEqual(protocolHandlerRequests.length, 1); + const [command, args] = protocolHandlerRequests[0]; + assert.strictEqual(command, testProtocolCommand); + assert.strictEqual(args, testProtocolCommandRequest); - const host = createServerHost([aTs, tsconfig, libFile]); - host.require = (_initialPath, moduleName) => { - session.logger.logs.push(`Require:: ${moduleName}`); - return { - module: (): server.PluginModule => { - session.logger.logs.push(`PluginFactory Invoke`); - return { - create: Harness.LanguageService.makeDefaultProxy, - getExternalFiles: () => externalFiles[moduleName] - }; - }, - error: undefined - }; - }; - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([aTs], session); - session.logger.logs.push(`ExternalFiles:: ${JSON.stringify(session.getProjectService().configuredProjects.get(tsconfig.path)!.getExternalFiles())}`); + const expectedResp: ts.server.HandlerResponse = { + response: testProtocolCommandResponse + }; + assert.deepEqual(resp, expectedResp); + }); - host.writeFile(tsconfig.path, JSON.stringify({ + it("gets external files with config file reload", () => { + const aTs: ts.projectSystem.File = { path: `${ts.tscWatch.projectRoot}/a.ts`, content: `export const x = 10;` }; + const tsconfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { - plugins: [{ name: "some-other-plugin" }] + plugins: [{ name: "some-plugin" }] } - })); - host.runQueuedTimeoutCallbacks(); - session.logger.logs.push(`ExternalFiles:: ${JSON.stringify(session.getProjectService().configuredProjects.get(tsconfig.path)!.getExternalFiles())}`); + }) + }; - baselineTsserverLogs("plugins", "gets external files with config file reload", session); - }); + const externalFiles: ts.MapLike = { + "some-plugin": ["someFile.txt"], + "some-other-plugin": ["someOtherFile.txt"], + }; + + const host = ts.projectSystem.createServerHost([aTs, tsconfig, ts.projectSystem.libFile]); + host.require = (_initialPath, moduleName) => { + session.logger.logs.push(`Require:: ${moduleName}`); + return { + module: (): ts.server.PluginModule => { + session.logger.logs.push(`PluginFactory Invoke`); + return { + create: Harness.LanguageService.makeDefaultProxy, + getExternalFiles: () => externalFiles[moduleName] + }; + }, + error: undefined + }; + }; + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([aTs], session); + session.logger.logs.push(`ExternalFiles:: ${JSON.stringify(session.getProjectService().configuredProjects.get(tsconfig.path)!.getExternalFiles())}`); + + host.writeFile(tsconfig.path, JSON.stringify({ + compilerOptions: { + plugins: [{ name: "some-other-plugin" }] + } + })); + host.runQueuedTimeoutCallbacks(); + session.logger.logs.push(`ExternalFiles:: ${JSON.stringify(session.getProjectService().configuredProjects.get(tsconfig.path)!.getExternalFiles())}`); + + ts.projectSystem.baselineTsserverLogs("plugins", "gets external files with config file reload", session); }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsserver/projectErrors.ts b/src/testRunner/unittests/tsserver/projectErrors.ts index 750b968b6c0f8..49c06511870ed 100644 --- a/src/testRunner/unittests/tsserver/projectErrors.ts +++ b/src/testRunner/unittests/tsserver/projectErrors.ts @@ -1,891 +1,891 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: Project Errors", () => { - function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: readonly string[]): void { - assert.isTrue(projectFiles !== undefined, "missing project files"); - checkProjectErrorsWorker(projectFiles.projectErrors, expectedErrors); - } - - function checkProjectErrorsWorker(errors: readonly Diagnostic[], expectedErrors: readonly string[]): void { - assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); - if (expectedErrors.length) { - for (let i = 0; i < errors.length; i++) { - const actualMessage = flattenDiagnosticMessageText(errors[i].messageText, "\n"); - const expectedMessage = expectedErrors[i]; - assert.isTrue(actualMessage.indexOf(expectedMessage) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); - } +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: Project Errors", () => { + function checkProjectErrors(projectFiles: ts.server.ProjectFilesWithTSDiagnostics, expectedErrors: readonly string[]): void { + assert.isTrue(projectFiles !== undefined, "missing project files"); + checkProjectErrorsWorker(projectFiles.projectErrors, expectedErrors); + } + + function checkProjectErrorsWorker(errors: readonly ts.Diagnostic[], expectedErrors: readonly string[]): void { + assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); + if (expectedErrors.length) { + for (let i = 0; i < errors.length; i++) { + const actualMessage = ts.flattenDiagnosticMessageText(errors[i].messageText, "\n"); + const expectedMessage = expectedErrors[i]; + assert.isTrue(actualMessage.indexOf(expectedMessage) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); } } + } - function checkDiagnosticsWithLinePos(errors: server.protocol.DiagnosticWithLinePosition[], expectedErrors: string[]) { - assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); - if (expectedErrors.length) { - zipWith(errors, expectedErrors, ({ message: actualMessage }, expectedMessage) => { - assert.isTrue(startsWith(actualMessage, actualMessage), `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); - }); - } + function checkDiagnosticsWithLinePos(errors: ts.server.protocol.DiagnosticWithLinePosition[], expectedErrors: string[]) { + assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); + if (expectedErrors.length) { + ts.zipWith(errors, expectedErrors, ({ message: actualMessage }, expectedMessage) => { + assert.isTrue(ts.startsWith(actualMessage, actualMessage), `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); + }); } + } + + it("external project - diagnostics for missing files", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/applib.ts", + content: "" + }; + const host = ts.projectSystem.createServerHost([file1, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host); + const projectService = session.getProjectService(); + const projectFileName = "/a/b/test.csproj"; + const compilerOptionsRequest: ts.server.protocol.CompilerOptionsDiagnosticsRequest = { + type: "request", + command: ts.server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName } + }; + + { + projectService.openExternalProject({ + projectFileName, + options: {}, + rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]) + }); - it("external project - diagnostics for missing files", () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const file2 = { - path: "/a/b/applib.ts", - content: "" - }; - const host = createServerHost([file1, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); - const projectFileName = "/a/b/test.csproj"; - const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = { - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName } - }; - - { - projectService.openExternalProject({ - projectFileName, - options: {}, - rootFiles: toExternalFiles([file1.path, file2.path]) - }); - - checkNumberOfProjects(projectService, { externalProjects: 1 }); - const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; - // only file1 exists - expect error - checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]); - } - host.renameFile(file1.path, file2.path); - { - // only file2 exists - expect error - checkNumberOfProjects(projectService, { externalProjects: 1 }); - const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; - checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]); - } - - host.writeFile(file1.path, file1.content); - { - // both files exist - expect no errors - checkNumberOfProjects(projectService, { externalProjects: 1 }); - const diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; - checkDiagnosticsWithLinePos(diags, []); - } - }); - - it("configured projects - diagnostics for missing files", () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const file2 = { - path: "/a/b/applib.ts", - content: "" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) - }; - const host = createServerHost([file1, config, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); - openFilesForSession([file1], session); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = configuredProjectAt(projectService, 0); - const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = { - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName: project.getProjectName() } - }; - let diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 }); + const diags = session.executeCommand(compilerOptionsRequest).response as ts.server.protocol.DiagnosticWithLinePosition[]; + // only file1 exists - expect error checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]); + } + host.renameFile(file1.path, file2.path); + { + // only file2 exists - expect error + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 }); + const diags = session.executeCommand(compilerOptionsRequest).response as ts.server.protocol.DiagnosticWithLinePosition[]; + checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]); + } - host.writeFile(file2.path, file2.content); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - diags = session.executeCommand(compilerOptionsRequest).response as server.protocol.DiagnosticWithLinePosition[]; + host.writeFile(file1.path, file1.content); + { + // both files exist - expect no errors + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 }); + const diags = session.executeCommand(compilerOptionsRequest).response as ts.server.protocol.DiagnosticWithLinePosition[]; checkDiagnosticsWithLinePos(diags, []); - }); - - it("configured projects - diagnostics for corrupted config 1", () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const file2 = { - path: "/a/b/lib.ts", - content: "" - }; - const correctConfig = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) - }; - const corruptedConfig = { - path: correctConfig.path, - content: correctConfig.content.substr(1) - }; - const host = createServerHost([file1, file2, corruptedConfig]); - const projectService = createProjectService(host); + } + }); - projectService.openClientFile(file1.path); - { - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; - assert.isTrue(configuredProject !== undefined, "should find configured project"); - checkProjectErrors(configuredProject, []); - const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); - checkProjectErrorsWorker(projectErrors, [ - "'{' expected." - ]); - assert.isNotNull(projectErrors[0].file); - assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path); - } - // fix config and trigger watcher - host.writeFile(correctConfig.path, correctConfig.content); - { - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; - assert.isTrue(configuredProject !== undefined, "should find configured project"); - checkProjectErrors(configuredProject, []); - const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); - checkProjectErrorsWorker(projectErrors, []); - } - }); + it("configured projects - diagnostics for missing files", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/applib.ts", + content: "" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1, file2].map(f => ts.getBaseFileName(f.path)) }) + }; + const host = ts.projectSystem.createServerHost([file1, config, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host); + const projectService = session.getProjectService(); + ts.projectSystem.openFilesForSession([file1], session); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + const compilerOptionsRequest: ts.server.protocol.CompilerOptionsDiagnosticsRequest = { + type: "request", + command: ts.server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName: project.getProjectName() } + }; + let diags = session.executeCommand(compilerOptionsRequest).response as ts.server.protocol.DiagnosticWithLinePosition[]; + checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]); + + host.writeFile(file2.path, file2.content); + + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + diags = session.executeCommand(compilerOptionsRequest).response as ts.server.protocol.DiagnosticWithLinePosition[]; + checkDiagnosticsWithLinePos(diags, []); + }); - it("configured projects - diagnostics for corrupted config 2", () => { - const file1 = { - path: "/a/b/app.ts", - content: "" - }; - const file2 = { - path: "/a/b/lib.ts", - content: "" - }; - const correctConfig = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) - }; - const corruptedConfig = { - path: correctConfig.path, - content: correctConfig.content.substr(1) - }; - const host = createServerHost([file1, file2, correctConfig]); - const projectService = createProjectService(host); + it("configured projects - diagnostics for corrupted config 1", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/lib.ts", + content: "" + }; + const correctConfig = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1, file2].map(f => ts.getBaseFileName(f.path)) }) + }; + const corruptedConfig = { + path: correctConfig.path, + content: correctConfig.content.substr(1) + }; + const host = ts.projectSystem.createServerHost([file1, file2, corruptedConfig]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + { + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const configuredProject = ts.find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, []); + const projectErrors = ts.projectSystem.configuredProjectAt(projectService, 0).getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, [ + "'{' expected." + ]); + assert.isNotNull(projectErrors[0].file); + assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path); + } + // fix config and trigger watcher + host.writeFile(correctConfig.path, correctConfig.content); + { + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const configuredProject = ts.find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, []); + const projectErrors = ts.projectSystem.configuredProjectAt(projectService, 0).getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, []); + } + }); - projectService.openClientFile(file1.path); - { - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; - assert.isTrue(configuredProject !== undefined, "should find configured project"); - checkProjectErrors(configuredProject, []); - const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); - checkProjectErrorsWorker(projectErrors, []); - } - // break config and trigger watcher - host.writeFile(corruptedConfig.path, corruptedConfig.content); - { - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - const configuredProject = find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; - assert.isTrue(configuredProject !== undefined, "should find configured project"); - checkProjectErrors(configuredProject, []); - const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); - checkProjectErrorsWorker(projectErrors, [ - "'{' expected." - ]); - assert.isNotNull(projectErrors[0].file); - assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path); - } - }); + it("configured projects - diagnostics for corrupted config 2", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const file2 = { + path: "/a/b/lib.ts", + content: "" + }; + const correctConfig = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1, file2].map(f => ts.getBaseFileName(f.path)) }) + }; + const corruptedConfig = { + path: correctConfig.path, + content: correctConfig.content.substr(1) + }; + const host = ts.projectSystem.createServerHost([file1, file2, correctConfig]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + { + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const configuredProject = ts.find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, []); + const projectErrors = ts.projectSystem.configuredProjectAt(projectService, 0).getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, []); + } + // break config and trigger watcher + host.writeFile(corruptedConfig.path, corruptedConfig.content); + { + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const configuredProject = ts.find(projectService.synchronizeProjectList([]), f => f.info!.projectName === corruptedConfig.path)!; + assert.isTrue(configuredProject !== undefined, "should find configured project"); + checkProjectErrors(configuredProject, []); + const projectErrors = ts.projectSystem.configuredProjectAt(projectService, 0).getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, [ + "'{' expected." + ]); + assert.isNotNull(projectErrors[0].file); + assert.equal(projectErrors[0].file!.fileName, corruptedConfig.path); + } + }); +}); + +describe("unittests:: tsserver:: Project Errors are reported as appropriate", () => { + it("document is not contained in project", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const corruptedConfig = { + path: "/a/b/tsconfig.json", + content: "{" + }; + const host = ts.projectSystem.createServerHost([file1, corruptedConfig]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + + const project = projectService.findProject(corruptedConfig.path)!; + ts.projectSystem.checkProjectRootFiles(project, [file1.path]); }); - describe("unittests:: tsserver:: Project Errors are reported as appropriate", () => { - it("document is not contained in project", () => { - const file1 = { - path: "/a/b/app.ts", - content: "" + describe("when opening new file that doesnt exist on disk yet", () => { + function verifyNonExistentFile(useProjectRoot: boolean) { + const folderPath = "/user/someuser/projects/someFolder"; + const fileInRoot: ts.projectSystem.File = { + path: `/src/somefile.d.ts`, + content: "class c { }" }; - const corruptedConfig = { - path: "/a/b/tsconfig.json", - content: "{" + const fileInProjectRoot: ts.projectSystem.File = { + path: `${folderPath}/src/somefile.d.ts`, + content: "class c { }" }; - const host = createServerHost([file1, corruptedConfig]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - - const project = projectService.findProject(corruptedConfig.path)!; - checkProjectRootFiles(project, [file1.path]); - }); + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, fileInRoot, fileInProjectRoot]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host), useInferredProjectPerProjectRoot: true }); - describe("when opening new file that doesnt exist on disk yet", () => { - function verifyNonExistentFile(useProjectRoot: boolean) { - const folderPath = "/user/someuser/projects/someFolder"; - const fileInRoot: File = { - path: `/src/somefile.d.ts`, - content: "class c { }" - }; - const fileInProjectRoot: File = { - path: `${folderPath}/src/somefile.d.ts`, - content: "class c { }" - }; - const host = createServerHost([libFile, fileInRoot, fileInProjectRoot]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host), useInferredProjectPerProjectRoot: true }); - - const untitledFile = "untitled:Untitled-1"; - const refPathNotFound1 = "../../../../../../typings/@epic/Core.d.ts"; - const refPathNotFound2 = "./src/somefile.d.ts"; - const fileContent = `/// + const untitledFile = "untitled:Untitled-1"; + const refPathNotFound1 = "../../../../../../typings/@epic/Core.d.ts"; + const refPathNotFound2 = "./src/somefile.d.ts"; + const fileContent = `/// /// `; - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { - file: untitledFile, - fileContent, - scriptKindName: "TS", - projectRootPath: useProjectRoot ? folderPath : undefined - } - }); - appendAllScriptInfos(session.getProjectService(), session.logger.logs); - - // Since this is not js project so no typings are queued - host.checkTimeoutQueueLength(0); - verifyGetErrRequest({ session, host, files: [untitledFile] }); - baselineTsserverLogs("projectErrors", `when opening new file that doesnt exist on disk yet ${useProjectRoot ? "with projectRoot" : "without projectRoot"}`, session); - } - - it("has projectRoot", () => { - verifyNonExistentFile(/*useProjectRoot*/ true); + session.executeCommandSeq({ + command: ts.server.CommandNames.Open, + arguments: { + file: untitledFile, + fileContent, + scriptKindName: "TS", + projectRootPath: useProjectRoot ? folderPath : undefined + } }); + ts.projectSystem.appendAllScriptInfos(session.getProjectService(), session.logger.logs); - it("does not have projectRoot", () => { - verifyNonExistentFile(/*useProjectRoot*/ false); - }); - }); + // Since this is not js project so no typings are queued + host.checkTimeoutQueueLength(0); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [untitledFile] }); + ts.projectSystem.baselineTsserverLogs("projectErrors", `when opening new file that doesnt exist on disk yet ${useProjectRoot ? "with projectRoot" : "without projectRoot"}`, session); + } - it("folder rename updates project structure and reports no errors", () => { - const projectDir = "/a/b/projects/myproject"; - const app: File = { - path: `${projectDir}/bar/app.ts`, - content: "class Bar implements foo.Foo { getFoo() { return ''; } get2() { return 1; } }" - }; - const foo: File = { - path: `${projectDir}/foo/foo.ts`, - content: "declare namespace foo { interface Foo { get2(): number; getFoo(): string; } }" - }; - const configFile: File = { - path: `${projectDir}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { module: "none", targer: "es5" }, exclude: ["node_modules"] }) - }; - const host = createServerHost([app, foo, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + it("has projectRoot", () => { + verifyNonExistentFile(/*useProjectRoot*/ true); + }); - session.executeCommandSeq({ - command: server.CommandNames.Open, - arguments: { file: app.path, } - }); - verifyGetErrRequest({ session, host, files: [app] }); + it("does not have projectRoot", () => { + verifyNonExistentFile(/*useProjectRoot*/ false); + }); + }); - host.renameFolder(`${projectDir}/foo`, `${projectDir}/foo2`); - host.runQueuedTimeoutCallbacks(); - host.runQueuedTimeoutCallbacks(); - verifyGetErrRequest({ session, host, files: [app] }); - baselineTsserverLogs("projectErrors", `folder rename updates project structure and reports no errors`, session); + it("folder rename updates project structure and reports no errors", () => { + const projectDir = "/a/b/projects/myproject"; + const app: ts.projectSystem.File = { + path: `${projectDir}/bar/app.ts`, + content: "class Bar implements foo.Foo { getFoo() { return ''; } get2() { return 1; } }" + }; + const foo: ts.projectSystem.File = { + path: `${projectDir}/foo/foo.ts`, + content: "declare namespace foo { interface Foo { get2(): number; getFoo(): string; } }" + }; + const configFile: ts.projectSystem.File = { + path: `${projectDir}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { module: "none", targer: "es5" }, exclude: ["node_modules"] }) + }; + const host = ts.projectSystem.createServerHost([app, foo, configFile]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + + session.executeCommandSeq({ + command: ts.server.CommandNames.Open, + arguments: { file: app.path, } }); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [app] }); - it("Getting errors before opening file", () => { - const file: File = { - path: "/a/b/project/file.ts", - content: "let x: number = false;" - }; - const host = createServerHost([file, libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - session.executeCommandSeq({ - command: server.CommandNames.Geterr, - arguments: { - delay: 0, - files: [file.path] - } - }); + host.renameFolder(`${projectDir}/foo`, `${projectDir}/foo2`); + host.runQueuedTimeoutCallbacks(); + host.runQueuedTimeoutCallbacks(); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [app] }); + ts.projectSystem.baselineTsserverLogs("projectErrors", `folder rename updates project structure and reports no errors`, session); + }); - host.checkTimeoutQueueLengthAndRun(1); - baselineTsserverLogs("projectErrors", "getting errors before opening file", session); + it("Getting errors before opening file", () => { + const file: ts.projectSystem.File = { + path: "/a/b/project/file.ts", + content: "let x: number = false;" + }; + const host = ts.projectSystem.createServerHost([file, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + session.executeCommandSeq({ + command: ts.server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [file.path] + } }); - it("Reports errors correctly when file referenced by inferred project root, is opened right after closing the root file", () => { - const app: File = { - path: `${tscWatch.projectRoot}/src/client/app.js`, - content: "" - }; - const serverUtilities: File = { - path: `${tscWatch.projectRoot}/src/server/utilities.js`, - content: `function getHostName() { return "hello"; } export { getHostName };` - }; - const backendTest: File = { - path: `${tscWatch.projectRoot}/test/backend/index.js`, - content: `import { getHostName } from '../../src/server/utilities';export default getHostName;` - }; - const files = [libFile, app, serverUtilities, backendTest]; - const host = createServerHost(files); - const session = createSession(host, { useInferredProjectPerProjectRoot: true, canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([{ file: app, projectRootPath: tscWatch.projectRoot }], session); - openFilesForSession([{ file: backendTest, projectRootPath: tscWatch.projectRoot }], session); - verifyGetErrRequest({ session, host, files: [backendTest.path, app.path] }); - closeFilesForSession([backendTest], session); - openFilesForSession([{ file: serverUtilities.path, projectRootPath: tscWatch.projectRoot }], session); - verifyGetErrRequest({ session, host, files: [serverUtilities.path, app.path] }); - baselineTsserverLogs("projectErrors", `reports errors correctly when file referenced by inferred project root, is opened right after closing the root file`, session); - }); + host.checkTimeoutQueueLengthAndRun(1); + ts.projectSystem.baselineTsserverLogs("projectErrors", "getting errors before opening file", session); + }); + + it("Reports errors correctly when file referenced by inferred project root, is opened right after closing the root file", () => { + const app: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/client/app.js`, + content: "" + }; + const serverUtilities: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/server/utilities.js`, + content: `function getHostName() { return "hello"; } export { getHostName };` + }; + const backendTest: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/test/backend/index.js`, + content: `import { getHostName } from '../../src/server/utilities';export default getHostName;` + }; + const files = [ts.projectSystem.libFile, app, serverUtilities, backendTest]; + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host, { useInferredProjectPerProjectRoot: true, canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([{ file: app, projectRootPath: ts.tscWatch.projectRoot }], session); + ts.projectSystem.openFilesForSession([{ file: backendTest, projectRootPath: ts.tscWatch.projectRoot }], session); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [backendTest.path, app.path] }); + ts.projectSystem.closeFilesForSession([backendTest], session); + ts.projectSystem.openFilesForSession([{ file: serverUtilities.path, projectRootPath: ts.tscWatch.projectRoot }], session); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [serverUtilities.path, app.path] }); + ts.projectSystem.baselineTsserverLogs("projectErrors", `reports errors correctly when file referenced by inferred project root, is opened right after closing the root file`, session); + }); - it("Correct errors when resolution resolves to file that has same ambient module and is also module", () => { - const projectRootPath = "/users/username/projects/myproject"; - const aFile: File = { - path: `${projectRootPath}/src/a.ts`, - content: `import * as myModule from "@custom/plugin"; + it("Correct errors when resolution resolves to file that has same ambient module and is also module", () => { + const projectRootPath = "/users/username/projects/myproject"; + const aFile: ts.projectSystem.File = { + path: `${projectRootPath}/src/a.ts`, + content: `import * as myModule from "@custom/plugin"; function foo() { // hello }` - }; - const config: File = { - path: `${projectRootPath}/tsconfig.json`, - content: JSON.stringify({ include: ["src"] }) - }; - const plugin: File = { - path: `${projectRootPath}/node_modules/@custom/plugin/index.d.ts`, - content: `import './proposed'; + }; + const config: ts.projectSystem.File = { + path: `${projectRootPath}/tsconfig.json`, + content: JSON.stringify({ include: ["src"] }) + }; + const plugin: ts.projectSystem.File = { + path: `${projectRootPath}/node_modules/@custom/plugin/index.d.ts`, + content: `import './proposed'; declare module '@custom/plugin' { export const version: string; }` - }; - const pluginProposed: File = { - path: `${projectRootPath}/node_modules/@custom/plugin/proposed.d.ts`, - content: `declare module '@custom/plugin' { + }; + const pluginProposed: ts.projectSystem.File = { + path: `${projectRootPath}/node_modules/@custom/plugin/proposed.d.ts`, + content: `declare module '@custom/plugin' { export const bar = 10; }` - }; - const files = [libFile, aFile, config, plugin, pluginProposed]; - const host = createServerHost(files); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([aFile], session); - - checkErrors(); - - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: aFile.path, - line: 3, - offset: 8, - endLine: 3, - endOffset: 8, - insertString: "o" - } - }); - checkErrors(); - baselineTsserverLogs("projectErrors", `correct errors when resolution resolves to file that has same ambient module and is also module`, session); - - function checkErrors() { - host.checkTimeoutQueueLength(0); - verifyGetErrRequest({ session, host, files: [aFile] }); + }; + const files = [ts.projectSystem.libFile, aFile, config, plugin, pluginProposed]; + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([aFile], session); + + checkErrors(); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: aFile.path, + line: 3, + offset: 8, + endLine: 3, + endOffset: 8, + insertString: "o" } }); + checkErrors(); + ts.projectSystem.baselineTsserverLogs("projectErrors", `correct errors when resolution resolves to file that has same ambient module and is also module`, session); + + function checkErrors() { + host.checkTimeoutQueueLength(0); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [aFile] }); + } + }); - describe("when semantic error returns includes global error", () => { - const file: File = { - path: `${tscWatch.projectRoot}/ui.ts`, - content: `const x = async (_action: string) => { + describe("when semantic error returns includes global error", () => { + const file: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/ui.ts`, + content: `const x = async (_action: string) => { };` - }; - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - verifyGetErrScenario({ - scenario: "projectErrors", - subScenario: "when semantic error returns includes global error", - allFiles: () => [libFile, file, config], - openFiles: () => [file], - getErrRequest: () => [file], - getErrForProjectRequest: () => [{ project: file, files: [file] }], - syncDiagnostics: () => [{ file, project: config }], - }); + }; + const config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + ts.projectSystem.verifyGetErrScenario({ + scenario: "projectErrors", + subScenario: "when semantic error returns includes global error", + allFiles: () => [ts.projectSystem.libFile, file, config], + openFiles: () => [file], + getErrRequest: () => [file], + getErrForProjectRequest: () => [{ project: file, files: [file] }], + syncDiagnostics: () => [{ file, project: config }], }); }); - - describe("unittests:: tsserver:: Project Errors for Configure file diagnostics events", () => { - it("are generated when the config file has errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ +}); + +describe("unittests:: tsserver:: Project Errors for Configure file diagnostics events", () => { + it("are generated when the config file has errors", () => { + const file: ts.projectSystem.File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "foo": "bar", "allowJS": true } }` - }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([file], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file has errors", session); - }); + }; + const host = ts.projectSystem.createServerHost([file, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([file], session); + ts.projectSystem.baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file has errors", session); + }); - it("are generated when the config file doesn't have errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("are generated when the config file doesn't have errors", () => { + const file: ts.projectSystem.File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {} }` - }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([file], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file doesnt have errors", session); - }); + }; + const host = ts.projectSystem.createServerHost([file, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([file], session); + ts.projectSystem.baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file doesnt have errors", session); + }); - it("are generated when the config file changes", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: `{ + it("are generated when the config file changes", () => { + const file: ts.projectSystem.File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {} }` - }; + }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([file], session); + const host = ts.projectSystem.createServerHost([file, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([file], session); - configFile.content = `{ + configFile.content = `{ "compilerOptions": { "haha": 123 } }`; - host.writeFile(configFile.path, configFile.content); - host.runQueuedTimeoutCallbacks(); + host.writeFile(configFile.path, configFile.content); + host.runQueuedTimeoutCallbacks(); - configFile.content = `{ + configFile.content = `{ "compilerOptions": {} }`; - host.writeFile(configFile.path, configFile.content); - host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file changes", session); - }); + host.writeFile(configFile.path, configFile.content); + host.runQueuedTimeoutCallbacks(); + ts.projectSystem.baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file changes", session); + }); - it("are not generated when the config file does not include file opened and config file has errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const file2: File = { - path: "/a/b/test.ts", - content: "let x = 10" - }; - const file3: File = { - path: "/a/b/test2.ts", - content: "let xy = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("are not generated when the config file does not include file opened and config file has errors", () => { + const file: ts.projectSystem.File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const file2: ts.projectSystem.File = { + path: "/a/b/test.ts", + content: "let x = 10" + }; + const file3: ts.projectSystem.File = { + path: "/a/b/test2.ts", + content: "let xy = 10" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "foo": "bar", "allowJS": true }, "files": ["app.ts"] }` - }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([file2], session); - openFilesForSession([file], session); - // We generate only if project is created when opening file from the project - openFilesForSession([file3], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file does not include file opened and config file has errors", session); - }); + }; + const host = ts.projectSystem.createServerHost([file, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([file2], session); + ts.projectSystem.openFilesForSession([file], session); + // We generate only if project is created when opening file from the project + ts.projectSystem.openFilesForSession([file3], session); + ts.projectSystem.baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file does not include file opened and config file has errors", session); + }); - it("are not generated when the config file has errors but suppressDiagnosticEvents is true", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("are not generated when the config file has errors but suppressDiagnosticEvents is true", () => { + const file: ts.projectSystem.File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "foo": "bar", "allowJS": true } }` - }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, suppressDiagnosticEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([file], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file has errors but suppressDiagnosticEvents is true", session); - }); + }; + const host = ts.projectSystem.createServerHost([file, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, suppressDiagnosticEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([file], session); + ts.projectSystem.baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file has errors but suppressDiagnosticEvents is true", session); + }); - it("are not generated when the config file does not include file opened and doesnt contain any errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const file2: File = { - path: "/a/b/test.ts", - content: "let x = 10" - }; - const file3: File = { - path: "/a/b/test2.ts", - content: "let xy = 10" - }; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("are not generated when the config file does not include file opened and doesnt contain any errors", () => { + const file: ts.projectSystem.File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const file2: ts.projectSystem.File = { + path: "/a/b/test.ts", + content: "let x = 10" + }; + const file3: ts.projectSystem.File = { + path: "/a/b/test2.ts", + content: "let xy = 10" + }; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "files": ["app.ts"] }` - }; - - const host = createServerHost([file, file2, file3, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([file2], session); - openFilesForSession([file], session); - // We generate only if project is created when opening file from the project - openFilesForSession([file3], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file does not include file opened and doesnt contain any errors", session); - }); + }; + + const host = ts.projectSystem.createServerHost([file, file2, file3, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([file2], session); + ts.projectSystem.openFilesForSession([file], session); + // We generate only if project is created when opening file from the project + ts.projectSystem.openFilesForSession([file3], session); + ts.projectSystem.baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file does not include file opened and doesnt contain any errors", session); + }); - it("contains the project reference errors", () => { - const file: File = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const noSuchTsconfig = "no-such-tsconfig.json"; - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("contains the project reference errors", () => { + const file: ts.projectSystem.File = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const noSuchTsconfig = "no-such-tsconfig.json"; + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "files": ["app.ts"], "references": [{"path":"./${noSuchTsconfig}"}] }` - }; + }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([file], session); - baselineTsserverLogs("projectErrors", "configFileDiagnostic events contains the project reference errors", session); - }); + const host = ts.projectSystem.createServerHost([file, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([file], session); + ts.projectSystem.baselineTsserverLogs("projectErrors", "configFileDiagnostic events contains the project reference errors", session); + }); +}); + +describe("unittests:: tsserver:: Project Errors dont include overwrite emit error", () => { + it("for inferred project", () => { + const f1 = { + path: "/a/b/f1.js", + content: "function test1() { }" + }; + const host = ts.projectSystem.createServerHost([f1, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([f1], session); + + const projectService = session.getProjectService(); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const projectName = projectService.inferredProjects[0].getProjectName(); + + const diags = session.executeCommand({ + type: "request", + command: ts.server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName: projectName } + } as ts.server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly ts.projectSystem.protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diags.length === 0); + + session.executeCommand({ + type: "request", + command: ts.server.CommandNames.CompilerOptionsForInferredProjects, + seq: 3, + arguments: { options: { module: ts.ModuleKind.CommonJS } } + } as ts.server.protocol.SetCompilerOptionsForInferredProjectsRequest); + const diagsAfterUpdate = session.executeCommand({ + type: "request", + command: ts.server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 4, + arguments: { projectFileName: projectName } + } as ts.server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly ts.projectSystem.protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diagsAfterUpdate.length === 0); }); - describe("unittests:: tsserver:: Project Errors dont include overwrite emit error", () => { - it("for inferred project", () => { - const f1 = { - path: "/a/b/f1.js", - content: "function test1() { }" - }; - const host = createServerHost([f1, libFile]); - const session = createSession(host); - openFilesForSession([f1], session); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const projectName = projectService.inferredProjects[0].getProjectName(); - - const diags = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName: projectName } - } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diags.length === 0); - - session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsForInferredProjects, - seq: 3, - arguments: { options: { module: ModuleKind.CommonJS } } - } as server.protocol.SetCompilerOptionsForInferredProjectsRequest); - const diagsAfterUpdate = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 4, - arguments: { projectFileName: projectName } - } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diagsAfterUpdate.length === 0); - }); - - it("for external project", () => { - const f1 = { - path: "/a/b/f1.js", - content: "function test1() { }" - }; - const host = createServerHost([f1, libFile]); - const session = createSession(host); - const projectService = session.getProjectService(); - const projectFileName = "/a/b/project.csproj"; - const externalFiles = toExternalFiles([f1.path]); - projectService.openExternalProject({ + it("for external project", () => { + const f1 = { + path: "/a/b/f1.js", + content: "function test1() { }" + }; + const host = ts.projectSystem.createServerHost([f1, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host); + const projectService = session.getProjectService(); + const projectFileName = "/a/b/project.csproj"; + const externalFiles = ts.projectSystem.toExternalFiles([f1.path]); + projectService.openExternalProject({ + projectFileName, + rootFiles: externalFiles, + options: {} + } as ts.projectSystem.protocol.ExternalProject); + + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 }); + + const diags = session.executeCommand({ + type: "request", + command: ts.server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName } + } as ts.server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly ts.server.protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diags.length === 0); + + session.executeCommand({ + type: "request", + command: ts.server.CommandNames.OpenExternalProject, + seq: 3, + arguments: { projectFileName, rootFiles: externalFiles, - options: {} - } as protocol.ExternalProject); - - checkNumberOfProjects(projectService, { externalProjects: 1 }); - - const diags = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 2, - arguments: { projectFileName } - } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diags.length === 0); - - session.executeCommand({ - type: "request", - command: server.CommandNames.OpenExternalProject, - seq: 3, - arguments: { - projectFileName, - rootFiles: externalFiles, - options: { module: ModuleKind.CommonJS } - } - } as server.protocol.OpenExternalProjectRequest); - const diagsAfterUpdate = session.executeCommand({ - type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, - seq: 4, - arguments: { projectFileName } - } as server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diagsAfterUpdate.length === 0); - }); + options: { module: ts.ModuleKind.CommonJS } + } + } as ts.server.protocol.OpenExternalProjectRequest); + const diagsAfterUpdate = session.executeCommand({ + type: "request", + command: ts.server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 4, + arguments: { projectFileName } + } as ts.server.protocol.CompilerOptionsDiagnosticsRequest).response as readonly ts.server.protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diagsAfterUpdate.length === 0); }); - - describe("unittests:: tsserver:: Project Errors reports Options Diagnostic locations correctly with changes in configFile contents", () => { - it("when options change", () => { - const file = { - path: "/a/b/app.ts", - content: "let x = 10" - }; - const configFileContentBeforeComment = `{`; - const configFileContentComment = ` +}); + +describe("unittests:: tsserver:: Project Errors reports Options Diagnostic locations correctly with changes in configFile contents", () => { + it("when options change", () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFileContentBeforeComment = `{`; + const configFileContentComment = ` // comment`; - const configFileContentAfterComment = ` + const configFileContentAfterComment = ` "compilerOptions": { "inlineSourceMap": true, "mapRoot": "./" } }`; - const configFileContentWithComment = configFileContentBeforeComment + configFileContentComment + configFileContentAfterComment; - const configFileContentWithoutCommentLine = configFileContentBeforeComment + configFileContentAfterComment; - - const configFile = { - path: "/a/b/tsconfig.json", - content: configFileContentWithComment - }; - const host = createServerHost([file, libFile, configFile]); - const session = createSession(host); - openFilesForSession([file], session); - - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const projectName = configuredProjectAt(projectService, 0).getProjectName(); - - const diags = session.executeCommand({ - type: "request", - command: server.CommandNames.SemanticDiagnosticsSync, - seq: 2, - arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } - } as server.protocol.SemanticDiagnosticsSyncRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diags.length === 3); - - configFile.content = configFileContentWithoutCommentLine; - host.writeFile(configFile.path, configFile.content); - - const diagsAfterEdit = session.executeCommand({ - type: "request", - command: server.CommandNames.SemanticDiagnosticsSync, - seq: 2, - arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } - } as server.protocol.SemanticDiagnosticsSyncRequest).response as readonly server.protocol.DiagnosticWithLinePosition[]; - assert.isTrue(diagsAfterEdit.length === 3); - - verifyDiagnostic(diags[0], diagsAfterEdit[0]); - verifyDiagnostic(diags[1], diagsAfterEdit[1]); - verifyDiagnostic(diags[2], diagsAfterEdit[2]); - - function verifyDiagnostic(beforeEditDiag: server.protocol.DiagnosticWithLinePosition, afterEditDiag: server.protocol.DiagnosticWithLinePosition) { - assert.equal(beforeEditDiag.message, afterEditDiag.message); - assert.equal(beforeEditDiag.code, afterEditDiag.code); - assert.equal(beforeEditDiag.category, afterEditDiag.category); - assert.equal(beforeEditDiag.startLocation.line, afterEditDiag.startLocation.line + 1); - assert.equal(beforeEditDiag.startLocation.offset, afterEditDiag.startLocation.offset); - assert.equal(beforeEditDiag.endLocation.line, afterEditDiag.endLocation.line + 1); - assert.equal(beforeEditDiag.endLocation.offset, afterEditDiag.endLocation.offset); - } - }); + const configFileContentWithComment = configFileContentBeforeComment + configFileContentComment + configFileContentAfterComment; + const configFileContentWithoutCommentLine = configFileContentBeforeComment + configFileContentAfterComment; + + const configFile = { + path: "/a/b/tsconfig.json", + content: configFileContentWithComment + }; + const host = ts.projectSystem.createServerHost([file, ts.projectSystem.libFile, configFile]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([file], session); + + const projectService = session.getProjectService(); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const projectName = ts.projectSystem.configuredProjectAt(projectService, 0).getProjectName(); + + const diags = session.executeCommand({ + type: "request", + command: ts.server.CommandNames.SemanticDiagnosticsSync, + seq: 2, + arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } + } as ts.server.protocol.SemanticDiagnosticsSyncRequest).response as readonly ts.server.protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diags.length === 3); + + configFile.content = configFileContentWithoutCommentLine; + host.writeFile(configFile.path, configFile.content); + + const diagsAfterEdit = session.executeCommand({ + type: "request", + command: ts.server.CommandNames.SemanticDiagnosticsSync, + seq: 2, + arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } + } as ts.server.protocol.SemanticDiagnosticsSyncRequest).response as readonly ts.server.protocol.DiagnosticWithLinePosition[]; + assert.isTrue(diagsAfterEdit.length === 3); + + verifyDiagnostic(diags[0], diagsAfterEdit[0]); + verifyDiagnostic(diags[1], diagsAfterEdit[1]); + verifyDiagnostic(diags[2], diagsAfterEdit[2]); + + function verifyDiagnostic(beforeEditDiag: ts.server.protocol.DiagnosticWithLinePosition, afterEditDiag: ts.server.protocol.DiagnosticWithLinePosition) { + assert.equal(beforeEditDiag.message, afterEditDiag.message); + assert.equal(beforeEditDiag.code, afterEditDiag.code); + assert.equal(beforeEditDiag.category, afterEditDiag.category); + assert.equal(beforeEditDiag.startLocation.line, afterEditDiag.startLocation.line + 1); + assert.equal(beforeEditDiag.startLocation.offset, afterEditDiag.startLocation.offset); + assert.equal(beforeEditDiag.endLocation.line, afterEditDiag.endLocation.line + 1); + assert.equal(beforeEditDiag.endLocation.offset, afterEditDiag.endLocation.offset); + } }); +}); - describe("unittests:: tsserver:: Project Errors with config file change", () => { - it("Updates diagnostics when '--noUnusedLabels' changes", () => { - const aTs: File = { path: "/a.ts", content: "label: while (1) {}" }; - const options = (allowUnusedLabels: boolean) => `{ "compilerOptions": { "allowUnusedLabels": ${allowUnusedLabels} } }`; - const tsconfig: File = { path: "/tsconfig.json", content: options(/*allowUnusedLabels*/ true) }; - - const host = createServerHost([aTs, tsconfig]); - const session = createSession(host); - openFilesForSession([aTs], session); - - host.modifyFile(tsconfig.path, options(/*allowUnusedLabels*/ false)); - host.runQueuedTimeoutCallbacks(); - - const response = executeSessionRequest(session, protocol.CommandTypes.SemanticDiagnosticsSync, { file: aTs.path }) as protocol.Diagnostic[] | undefined; - assert.deepEqual(response, [ - { - start: { line: 1, offset: 1 }, - end: { line: 1, offset: 1 + "label".length }, - text: "Unused label.", - category: "error", - code: Diagnostics.Unused_label.code, - relatedInformation: undefined, - reportsUnnecessary: true, - reportsDeprecated: undefined, - source: undefined, - }, - ]); - }); +describe("unittests:: tsserver:: Project Errors with config file change", () => { + it("Updates diagnostics when '--noUnusedLabels' changes", () => { + const aTs: ts.projectSystem.File = { path: "/a.ts", content: "label: while (1) {}" }; + const options = (allowUnusedLabels: boolean) => `{ "compilerOptions": { "allowUnusedLabels": ${allowUnusedLabels} } }`; + const tsconfig: ts.projectSystem.File = { path: "/tsconfig.json", content: options(/*allowUnusedLabels*/ true) }; + + const host = ts.projectSystem.createServerHost([aTs, tsconfig]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([aTs], session); + + host.modifyFile(tsconfig.path, options(/*allowUnusedLabels*/ false)); + host.runQueuedTimeoutCallbacks(); + + const response = ts.projectSystem.executeSessionRequest(session, ts.projectSystem.protocol.CommandTypes.SemanticDiagnosticsSync, { file: aTs.path }) as ts.projectSystem.protocol.Diagnostic[] | undefined; + assert.deepEqual(response, [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 1 + "label".length }, + text: "Unused label.", + category: "error", + code: ts.Diagnostics.Unused_label.code, + relatedInformation: undefined, + reportsUnnecessary: true, + reportsDeprecated: undefined, + source: undefined, + }, + ]); }); +}); - describe("unittests:: tsserver:: Project Errors with resolveJsonModule", () => { - function createSessionForTest({ include }: { include: readonly string[]; }) { - const test: File = { - path: `${tscWatch.projectRoot}/src/test.ts`, - content: `import * as blabla from "./blabla.json"; +describe("unittests:: tsserver:: Project Errors with resolveJsonModule", () => { + function createSessionForTest({ include }: { include: readonly string[]; }) { + const test: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/test.ts`, + content: `import * as blabla from "./blabla.json"; declare var console: any; console.log(blabla);` - }; - const blabla: File = { - path: `${tscWatch.projectRoot}/src/blabla.json`, - content: "{}" - }; - const tsconfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - resolveJsonModule: true, - composite: true - }, - include - }) - }; - - const host = createServerHost([test, blabla, libFile, tsconfig]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([test], session); - return { host, session, test, blabla, tsconfig }; - } - - it("should not report incorrect error when json is root file found by tsconfig", () => { - const { host, session, test } = createSessionForTest({ - include: ["./src/*.ts", "./src/*.json"] - }); - verifyGetErrRequest({ session, host, files: [test] }); - baselineTsserverLogs("projectErrors", `should not report incorrect error when json is root file found by tsconfig`, session); + }; + const blabla: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/blabla.json`, + content: "{}" + }; + const tsconfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + resolveJsonModule: true, + composite: true + }, + include + }) + }; + + const host = ts.projectSystem.createServerHost([test, blabla, ts.projectSystem.libFile, tsconfig]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([test], session); + return { host, session, test, blabla, tsconfig }; + } + + it("should not report incorrect error when json is root file found by tsconfig", () => { + const { host, session, test } = createSessionForTest({ + include: ["./src/*.ts", "./src/*.json"] }); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [test] }); + ts.projectSystem.baselineTsserverLogs("projectErrors", `should not report incorrect error when json is root file found by tsconfig`, session); + }); - it("should report error when json is not root file found by tsconfig", () => { - const { host, session, test } = createSessionForTest({ - include: ["./src/*.ts"] - }); - verifyGetErrRequest({ session, host, files: [test] }); - baselineTsserverLogs("projectErrors", `should report error when json is not root file found by tsconfig`, session); + it("should report error when json is not root file found by tsconfig", () => { + const { host, session, test } = createSessionForTest({ + include: ["./src/*.ts"] }); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [test] }); + ts.projectSystem.baselineTsserverLogs("projectErrors", `should report error when json is not root file found by tsconfig`, session); }); - - describe("unittests:: tsserver:: Project Errors with npm install when", () => { - function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) { - const main: File = { - path: `${tscWatch.projectRoot}/src/main.ts`, - content: "import * as _a from '@angular/core';" - }; - const config: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: "{}" - }; - // Move things from staging to node_modules without triggering watch - const moduleFile: File = { - path: `${tscWatch.projectRoot}/node_modules/@angular/core/index.d.ts`, - content: `export const y = 10;` - }; - const projectFiles = [main, libFile, config]; - const host = createServerHost(projectFiles); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([{ file: main, projectRootPath: tscWatch.projectRoot }], session); - verifyGetErrRequest({ session, host, files: [main] }); - - let npmInstallComplete = false; - - // Simulate npm install - let filesAndFoldersToAdd: (File | Folder)[] = [ - { path: `${tscWatch.projectRoot}/node_modules` }, // This should queue update - { path: `${tscWatch.projectRoot}/node_modules/.staging` }, - { path: `${tscWatch.projectRoot}/node_modules/.staging/@babel` }, - { path: `${tscWatch.projectRoot}/node_modules/.staging/@babel/helper-plugin-utils-a06c629f` }, - { path: `${tscWatch.projectRoot}/node_modules/.staging/core-js-db53158d` }, - ]; - verifyWhileNpmInstall(3); - - filesAndFoldersToAdd = [ - { path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/platform-browser-dynamic-5efaaa1a` }, - { path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/cli-c1e44b05/models/analytics.d.ts`, content: `export const x = 10;` }, - { path: `${tscWatch.projectRoot}/node_modules/.staging/@angular/core-0963aebf/index.d.ts`, content: `export const y = 10;` }, - ]; - // Since we added/removed in .staging no timeout - verifyWhileNpmInstall(0); - - filesAndFoldersToAdd = []; - host.ensureFileOrFolder(moduleFile, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true, /*ignoreParentWatch*/ true); - // Since we added/removed in .staging no timeout - verifyWhileNpmInstall(0); - - // Remove staging folder to remove errors - host.deleteFolder(`${tscWatch.projectRoot}/node_modules/.staging`, /*recursive*/ true); - npmInstallComplete = true; - projectFiles.push(moduleFile); - // Additional watch for watching script infos from node_modules - verifyWhileNpmInstall(3); - - baselineTsserverLogs("projectErrors", `npm install when timeout occurs ${timeoutDuringPartialInstallation ? "inbetween" : "after"} installation`, session); - - function verifyWhileNpmInstall(timeouts: number) { - filesAndFoldersToAdd.forEach(f => host.ensureFileOrFolder(f)); - if (npmInstallComplete || timeoutDuringPartialInstallation) { - host.checkTimeoutQueueLengthAndRun(timeouts); // Invalidation of failed lookups - if (timeouts) { - host.checkTimeoutQueueLengthAndRun(timeouts - 1); // Actual update - } +}); + +describe("unittests:: tsserver:: Project Errors with npm install when", () => { + function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) { + const main: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/main.ts`, + content: "import * as _a from '@angular/core';" + }; + const config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: "{}" + }; + // Move things from staging to node_modules without triggering watch + const moduleFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/node_modules/@angular/core/index.d.ts`, + content: `export const y = 10;` + }; + const projectFiles = [main, ts.projectSystem.libFile, config]; + const host = ts.projectSystem.createServerHost(projectFiles); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([{ file: main, projectRootPath: ts.tscWatch.projectRoot }], session); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [main] }); + + let npmInstallComplete = false; + + // Simulate npm install + let filesAndFoldersToAdd: (ts.projectSystem.File | ts.projectSystem.Folder)[] = [ + { path: `${ts.tscWatch.projectRoot}/node_modules` }, // This should queue update + { path: `${ts.tscWatch.projectRoot}/node_modules/.staging` }, + { path: `${ts.tscWatch.projectRoot}/node_modules/.staging/@babel` }, + { path: `${ts.tscWatch.projectRoot}/node_modules/.staging/@babel/helper-plugin-utils-a06c629f` }, + { path: `${ts.tscWatch.projectRoot}/node_modules/.staging/core-js-db53158d` }, + ]; + verifyWhileNpmInstall(3); + + filesAndFoldersToAdd = [ + { path: `${ts.tscWatch.projectRoot}/node_modules/.staging/@angular/platform-browser-dynamic-5efaaa1a` }, + { path: `${ts.tscWatch.projectRoot}/node_modules/.staging/@angular/cli-c1e44b05/models/analytics.d.ts`, content: `export const x = 10;` }, + { path: `${ts.tscWatch.projectRoot}/node_modules/.staging/@angular/core-0963aebf/index.d.ts`, content: `export const y = 10;` }, + ]; + // Since we added/removed in .staging no timeout + verifyWhileNpmInstall(0); + + filesAndFoldersToAdd = []; + host.ensureFileOrFolder(moduleFile, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true, /*ignoreParentWatch*/ true); + // Since we added/removed in .staging no timeout + verifyWhileNpmInstall(0); + + // Remove staging folder to remove errors + host.deleteFolder(`${ts.tscWatch.projectRoot}/node_modules/.staging`, /*recursive*/ true); + npmInstallComplete = true; + projectFiles.push(moduleFile); + // Additional watch for watching script infos from node_modules + verifyWhileNpmInstall(3); + + ts.projectSystem.baselineTsserverLogs("projectErrors", `npm install when timeout occurs ${timeoutDuringPartialInstallation ? "inbetween" : "after"} installation`, session); + + function verifyWhileNpmInstall(timeouts: number) { + filesAndFoldersToAdd.forEach(f => host.ensureFileOrFolder(f)); + if (npmInstallComplete || timeoutDuringPartialInstallation) { + host.checkTimeoutQueueLengthAndRun(timeouts); // Invalidation of failed lookups + if (timeouts) { + host.checkTimeoutQueueLengthAndRun(timeouts - 1); // Actual update } - else { - host.checkTimeoutQueueLength(timeouts ? 3 : 2); - } - verifyGetErrRequest({ session, host, files: [main], existingTimeouts: !npmInstallComplete && !timeoutDuringPartialInstallation ? timeouts ? 3 : 2 : undefined }); } + else { + host.checkTimeoutQueueLength(timeouts ? 3 : 2); + } + ts.projectSystem.verifyGetErrRequest({ session, host, files: [main], existingTimeouts: !npmInstallComplete && !timeoutDuringPartialInstallation ? timeouts ? 3 : 2 : undefined }); } + } - it("timeouts occur inbetween installation", () => { - verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); - }); + it("timeouts occur inbetween installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); + }); - it("timeout occurs after installation", () => { - verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); - }); + it("timeout occurs after installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts index 671813de33fc6..f2a6941347b18 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts @@ -1,1736 +1,1736 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: with project references and compile on save", () => { - const dependecyLocation = `${tscWatch.projectRoot}/dependency`; - const usageLocation = `${tscWatch.projectRoot}/usage`; - const dependencyTs: File = { - path: `${dependecyLocation}/fns.ts`, - content: `export function fn1() { } +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: with project references and compile on save", () => { + const dependecyLocation = `${ts.tscWatch.projectRoot}/dependency`; + const usageLocation = `${ts.tscWatch.projectRoot}/usage`; + const dependencyTs: ts.projectSystem.File = { + path: `${dependecyLocation}/fns.ts`, + content: `export function fn1() { } export function fn2() { } ` - }; - const dependencyConfig: File = { - path: `${dependecyLocation}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, declarationDir: "../decls" }, - compileOnSave: true - }) - }; - const usageTs: File = { - path: `${usageLocation}/usage.ts`, - content: `import { + }; + const dependencyConfig: ts.projectSystem.File = { + path: `${dependecyLocation}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, declarationDir: "../decls" }, + compileOnSave: true + }) + }; + const usageTs: ts.projectSystem.File = { + path: `${usageLocation}/usage.ts`, + content: `import { fn1, fn2, } from '../decls/fns' fn1(); fn2(); ` - }; - const usageConfig: File = { - path: `${usageLocation}/tsconfig.json`, - content: JSON.stringify({ - compileOnSave: true, - references: [{ path: "../dependency" }] - }) - }; + }; + const usageConfig: ts.projectSystem.File = { + path: `${usageLocation}/tsconfig.json`, + content: JSON.stringify({ + compileOnSave: true, + references: [{ path: "../dependency" }] + }) + }; + + const localChange = "function fn3() { }"; + const change = `export ${localChange}`; + + describe("when dependency project is not open", () => { + describe("Of usageTs", () => { + it("with initial file open, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }); - const localChange = "function fn3() { }"; - const change = `export ${localChange}`; - - describe("when dependency project is not open", () => { - describe("Of usageTs", () => { - it("with initial file open, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage", session); - }); - it("with initial file open, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage with project", session); - }); - it("with local change to dependency, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage and local change to dependency", session); - }); - it("with local change to dependency, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage with project and local change to dependency", session); - }); - it("with local change to usage, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage and local change to usage", session); - }); - it("with local change to usage, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage with project and local change to usage", session); - }); - it("with change to dependency, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage and change to depenedency", session); - }); - it("with change to dependency, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage with project and change to depenedency", session); - }); - it("with change to usage, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage and change to usage", session); - }); - it("with change to usage, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage with project and change to usage", session); + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage", session); }); + it("with initial file open, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); - describe("Of dependencyTs in usage project", () => { - it("with initial file open, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency", session); - }); - it("with initial file open, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency with project", session); - }); - it("with local change to dependency, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency and local change to dependency", session); - }); - it("with local change to dependency, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency with project and local change to dependency", session); - }); - it("with local change to usage, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency and local change to usage", session); - }); - it("with local change to usage, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency with project and local change to usage", session); - }); - it("with change to dependency, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency and change to dependency", session); - }); - it("with change to dependency, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency with project and change to dependency", session); - }); - it("with change to usage, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency and change to usage", session); - }); - it("with change to usage, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency with project and change to usage", session); + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage with project", session); + }); + it("with local change to dependency, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage and local change to dependency", session); + }); + it("with local change to dependency, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage with project and local change to dependency", session); + }); + it("with local change to usage, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage and local change to usage", session); + }); + it("with local change to usage, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage with project and local change to usage", session); + }); + it("with change to dependency, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage and change to depenedency", session); + }); + it("with change to dependency, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage with project and change to depenedency", session); + }); + it("with change to usage, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage and change to usage", session); + }); + it("with change to usage, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on usage with project and change to usage", session); }); }); - describe("when the depedency file is open", () => { - describe("Of usageTs", () => { - it("with initial file open, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage", session); - }); - it("with initial file open, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage with project", session); - }); - it("with local change to dependency, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and local change to dependency", session); - }); - it("with local change to dependency, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and local change to dependency with file", session); - }); - it("with local change to usage, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and local change to usage", session); - }); - it("with local change to usage, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and local change to usage with project", session); - }); - it("with change to dependency, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and change to dependency", session); - }); - it("with change to dependency, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage with project and change to dependency", session); - }); - it("with change to usage, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and change to usage", session); - }); - it("with change to usage, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: usageTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage with project and change to usage", session); + describe("Of dependencyTs in usage project", () => { + it("with initial file open, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency", session); }); + it("with initial file open, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); - describe("Of dependencyTs in usage project", () => { - it("with initial file open, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with usage project", session); - }); - it("with local change to dependency, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with usage project and local change to dependency", session); - }); - it("with local change to usage, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with usage project and local change to usage", session); - }); - it("with change to dependency, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with usage project and change to dependency", session); - }); - it("with change to usage, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with usage project and change to usage", session); + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency with project", session); }); + it("with local change to dependency, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); - describe("Of dependencyTs", () => { - it("with initial file open, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency", session); - }); - it("with initial file open, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with project", session); - }); - it("with local change to dependency, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency and local change to dependency", session); - }); - it("with local change to dependency, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with project and local change to dependency", session); - }); - it("with local change to usage, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency and local change to usage", session); - }); - it("with local change to usage, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: localChange - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with project and local change to usage", session); - }); - it("with change to dependency, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency and change to dependency", session); - }); - it("with change to dependency, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(dependencyTs.content); - const location = toLocation(dependencyTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with project and change to dependency", session); - }); - it("with change to usage, without specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency and change to usage", session); - }); - it("with change to usage, with specifying project file", () => { - const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([usageTs, dependencyTs], session); - - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path } - }); - const toLocation = protocolToLocation(usageTs.content); - const location = toLocation(usageTs.content.length); - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: usageTs.path, - ...location, - endLine: location.line, - endOffset: location.offset, - insertString: change - } - }); - - // Verify CompileOnSaveAffectedFileList - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveAffectedFileList, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - - // Verify CompileOnSaveEmit - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - - // Verify EmitOutput - session.executeCommandSeq({ - command: protocol.CommandTypes.EmitOutput, - arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with project and change to usage", session); + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency and local change to dependency", session); + }); + it("with local change to dependency, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${localChange}`); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency with project and local change to dependency", session); + }); + it("with local change to usage, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency and local change to usage", session); + }); + it("with local change to usage, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency with project and local change to usage", session); + }); + it("with change to dependency, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency and change to dependency", session); + }); + it("with change to dependency, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + host.writeFile(dependencyTs.path, `${dependencyTs.content}${change}`); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency with project and change to dependency", session); + }); + it("with change to usage, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency and change to usage", session); + }); + it("with change to usage, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "when dependency project is not open and save on dependency with project and change to usage", session); }); }); }); - describe("unittests:: tsserver:: with project references and compile on save with external projects", () => { - it("compile on save emits same output as project build", () => { - const tsbaseJson: File = { - path: `${tscWatch.projectRoot}/tsbase.json`, - content: JSON.stringify({ - compileOnSave: true, - compilerOptions: { - module: "none", - composite: true + describe("when the depedency file is open", () => { + describe("Of usageTs", () => { + it("with initial file open, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage", session); + }); + it("with initial file open, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage with project", session); + }); + it("with local change to dependency, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and local change to dependency", session); + }); + it("with local change to dependency, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and local change to dependency with file", session); + }); + it("with local change to usage, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and local change to usage", session); + }); + it("with local change to usage, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange } - }) - }; - const buttonClass = `${tscWatch.projectRoot}/buttonClass`; - const buttonConfig: File = { - path: `${buttonClass}/tsconfig.json`, - content: JSON.stringify({ - extends: "../tsbase.json", - compilerOptions: { - outFile: "Source.js" - }, - files: ["Source.ts"] - }) - }; - const buttonSource: File = { - path: `${buttonClass}/Source.ts`, - content: `module Hmi { + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and local change to usage with project", session); + }); + it("with change to dependency, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and change to dependency", session); + }); + it("with change to dependency, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage with project and change to dependency", session); + }); + it("with change to usage, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage and change to usage", session); + }); + it("with change to usage, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: usageTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on usage with project and change to usage", session); + }); + }); + + describe("Of dependencyTs in usage project", () => { + it("with initial file open, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with usage project", session); + }); + it("with local change to dependency, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with usage project and local change to dependency", session); + }); + it("with local change to usage, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with usage project and local change to usage", session); + }); + it("with change to dependency, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with usage project and change to dependency", session); + }); + it("with change to usage, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with usage project and change to usage", session); + }); + }); + + describe("Of dependencyTs", () => { + it("with initial file open, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency", session); + }); + it("with initial file open, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with project", session); + }); + it("with local change to dependency, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency and local change to dependency", session); + }); + it("with local change to dependency, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with project and local change to dependency", session); + }); + it("with local change to usage, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency and local change to usage", session); + }); + it("with local change to usage, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: localChange + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with project and local change to usage", session); + }); + it("with change to dependency, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency and change to dependency", session); + }); + it("with change to dependency, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(dependencyTs.content); + const location = toLocation(dependencyTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with project and change to dependency", session); + }); + it("with change to usage, without specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency and change to usage", session); + }); + it("with change to usage, with specifying project file", () => { + const host = ts.projectSystem.createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([usageTs, dependencyTs], session); + + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path } + }); + const toLocation = ts.projectSystem.protocolToLocation(usageTs.content); + const location = toLocation(usageTs.content.length); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: usageTs.path, + ...location, + endLine: location.line, + endOffset: location.offset, + insertString: change + } + }); + + // Verify CompileOnSaveAffectedFileList + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveAffectedFileList, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + + // Verify CompileOnSaveEmit + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + + // Verify EmitOutput + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.EmitOutput, + arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } + }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "save on dependency with project and change to usage", session); + }); + }); + }); +}); + +describe("unittests:: tsserver:: with project references and compile on save with external projects", () => { + it("compile on save emits same output as project build", () => { + const tsbaseJson: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsbase.json`, + content: JSON.stringify({ + compileOnSave: true, + compilerOptions: { + module: "none", + composite: true + } + }) + }; + const buttonClass = `${ts.tscWatch.projectRoot}/buttonClass`; + const buttonConfig: ts.projectSystem.File = { + path: `${buttonClass}/tsconfig.json`, + content: JSON.stringify({ + extends: "../tsbase.json", + compilerOptions: { + outFile: "Source.js" + }, + files: ["Source.ts"] + }) + }; + const buttonSource: ts.projectSystem.File = { + path: `${buttonClass}/Source.ts`, + content: `module Hmi { export class Button { public static myStaticFunction() { } } }` - }; - - const siblingClass = `${tscWatch.projectRoot}/SiblingClass`; - const siblingConfig: File = { - path: `${siblingClass}/tsconfig.json`, - content: JSON.stringify({ - extends: "../tsbase.json", - references: [{ - path: "../buttonClass/" - }], - compilerOptions: { - outFile: "Source.js" - }, - files: ["Source.ts"] - }) - }; - const siblingSource: File = { - path: `${siblingClass}/Source.ts`, - content: `module Hmi { + }; + + const siblingClass = `${ts.tscWatch.projectRoot}/SiblingClass`; + const siblingConfig: ts.projectSystem.File = { + path: `${siblingClass}/tsconfig.json`, + content: JSON.stringify({ + extends: "../tsbase.json", + references: [{ + path: "../buttonClass/" + }], + compilerOptions: { + outFile: "Source.js" + }, + files: ["Source.ts"] + }) + }; + const siblingSource: ts.projectSystem.File = { + path: `${siblingClass}/Source.ts`, + content: `module Hmi { export class Sibling { public mySiblingFunction() { } } }` - }; - const host = createServerHost([libFile, tsbaseJson, buttonConfig, buttonSource, siblingConfig, siblingSource], { useCaseSensitiveFileNames: true }); + }; + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, tsbaseJson, buttonConfig, buttonSource, siblingConfig, siblingSource], { useCaseSensitiveFileNames: true }); - // ts build should succeed - tscWatch.ensureErrorFreeBuild(host, [siblingConfig.path]); + // ts build should succeed + ts.tscWatch.ensureErrorFreeBuild(host, [siblingConfig.path]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([siblingSource], session); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([siblingSource], session); - session.executeCommandSeq({ - command: protocol.CommandTypes.CompileOnSaveEmitFile, - arguments: { - file: siblingSource.path, - projectFileName: siblingConfig.path - } - }); - baselineTsserverLogs("projectReferenceCompileOnSave", "compile on save emits same output as project build with external project", session); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompileOnSaveEmitFile, + arguments: { + file: siblingSource.path, + projectFileName: siblingConfig.path + } }); + ts.projectSystem.baselineTsserverLogs("projectReferenceCompileOnSave", "compile on save emits same output as project build with external project", session); }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts index 7368f54b9f2ad..1dc9e6e020173 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceErrors.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceErrors.ts @@ -1,83 +1,84 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: with project references and error reporting", () => { - const dependecyLocation = `${tscWatch.projectRoot}/dependency`; - const usageLocation = `${tscWatch.projectRoot}/usage`; +import * as ts from "../../_namespaces/ts"; - function verifyUsageAndDependency(scenario: string, dependencyTs: File, dependencyConfig: File, usageTs: File, usageConfig: File) { - function usageProjectDiagnostics(): GetErrForProjectDiagnostics { - return { project: usageTs, files: [usageTs, dependencyTs] }; - } +describe("unittests:: tsserver:: with project references and error reporting", () => { + const dependecyLocation = `${ts.tscWatch.projectRoot}/dependency`; + const usageLocation = `${ts.tscWatch.projectRoot}/usage`; - function dependencyProjectDiagnostics(): GetErrForProjectDiagnostics { - return { project: dependencyTs, files: [dependencyTs] }; - } + function verifyUsageAndDependency(scenario: string, dependencyTs: ts.projectSystem.File, dependencyConfig: ts.projectSystem.File, usageTs: ts.projectSystem.File, usageConfig: ts.projectSystem.File) { + function usageProjectDiagnostics(): ts.projectSystem.GetErrForProjectDiagnostics { + return { project: usageTs, files: [usageTs, dependencyTs] }; + } + + function dependencyProjectDiagnostics(): ts.projectSystem.GetErrForProjectDiagnostics { + return { project: dependencyTs, files: [dependencyTs] }; + } - describe("when dependency project is not open", () => { - verifyGetErrScenario({ - scenario: "projectReferenceErrors", - subScenario: `${scenario} when dependency project is not open`, - allFiles: () => [dependencyTs, dependencyConfig, usageTs, usageConfig], - openFiles: () => [usageTs], - getErrRequest: () => [usageTs], - getErrForProjectRequest: () => [ - usageProjectDiagnostics(), - { - project: dependencyTs, - files: [dependencyTs, usageTs] - } - ], - syncDiagnostics: () => [ - // Without project - { file: usageTs }, - { file: dependencyTs }, - // With project - { file: usageTs, project: usageConfig }, - { file: dependencyTs, project: usageConfig }, - ], - }); + describe("when dependency project is not open", () => { + ts.projectSystem.verifyGetErrScenario({ + scenario: "projectReferenceErrors", + subScenario: `${scenario} when dependency project is not open`, + allFiles: () => [dependencyTs, dependencyConfig, usageTs, usageConfig], + openFiles: () => [usageTs], + getErrRequest: () => [usageTs], + getErrForProjectRequest: () => [ + usageProjectDiagnostics(), + { + project: dependencyTs, + files: [dependencyTs, usageTs] + } + ], + syncDiagnostics: () => [ + // Without project + { file: usageTs }, + { file: dependencyTs }, + // With project + { file: usageTs, project: usageConfig }, + { file: dependencyTs, project: usageConfig }, + ], }); + }); - describe("when the depedency file is open", () => { - verifyGetErrScenario({ - scenario: "projectReferenceErrors", - subScenario: `${scenario} when the depedency file is open`, - allFiles: () => [dependencyTs, dependencyConfig, usageTs, usageConfig], - openFiles: () => [usageTs, dependencyTs], - getErrRequest: () => [usageTs, dependencyTs], - getErrForProjectRequest: () => [ - usageProjectDiagnostics(), - dependencyProjectDiagnostics() - ], - syncDiagnostics: () => [ - // Without project - { file: usageTs }, - { file: dependencyTs }, - // With project - { file: usageTs, project: usageConfig }, - { file: dependencyTs, project: usageConfig }, - { file: dependencyTs, project: dependencyConfig }, - ], - }); + describe("when the depedency file is open", () => { + ts.projectSystem.verifyGetErrScenario({ + scenario: "projectReferenceErrors", + subScenario: `${scenario} when the depedency file is open`, + allFiles: () => [dependencyTs, dependencyConfig, usageTs, usageConfig], + openFiles: () => [usageTs, dependencyTs], + getErrRequest: () => [usageTs, dependencyTs], + getErrForProjectRequest: () => [ + usageProjectDiagnostics(), + dependencyProjectDiagnostics() + ], + syncDiagnostics: () => [ + // Without project + { file: usageTs }, + { file: dependencyTs }, + // With project + { file: usageTs, project: usageConfig }, + { file: dependencyTs, project: usageConfig }, + { file: dependencyTs, project: dependencyConfig }, + ], }); - } + }); + } - describe("with module scenario", () => { - const dependencyTs: File = { - path: `${dependecyLocation}/fns.ts`, - content: `export function fn1() { } + describe("with module scenario", () => { + const dependencyTs: ts.projectSystem.File = { + path: `${dependecyLocation}/fns.ts`, + content: `export function fn1() { } export function fn2() { } // Introduce error for fnErr import in main // export function fnErr() { } // Error in dependency ts file export let x: string = 10;` - }; - const dependencyConfig: File = { - path: `${dependecyLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true, declarationDir: "../decls" } }) - }; - const usageTs: File = { - path: `${usageLocation}/usage.ts`, - content: `import { + }; + const dependencyConfig: ts.projectSystem.File = { + path: `${dependecyLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true, declarationDir: "../decls" } }) + }; + const usageTs: ts.projectSystem.File = { + path: `${usageLocation}/usage.ts`, + content: `import { fn1, fn2, fnErr @@ -86,46 +87,45 @@ fn1(); fn2(); fnErr(); ` - }; - const usageConfig: File = { - path: `${usageLocation}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true }, - references: [{ path: "../dependency" }] - }) - }; - verifyUsageAndDependency("with module scenario", dependencyTs, dependencyConfig, usageTs, usageConfig); - }); + }; + const usageConfig: ts.projectSystem.File = { + path: `${usageLocation}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true }, + references: [{ path: "../dependency" }] + }) + }; + verifyUsageAndDependency("with module scenario", dependencyTs, dependencyConfig, usageTs, usageConfig); + }); - describe("with non module --out", () => { - const dependencyTs: File = { - path: `${dependecyLocation}/fns.ts`, - content: `function fn1() { } + describe("with non module --out", () => { + const dependencyTs: ts.projectSystem.File = { + path: `${dependecyLocation}/fns.ts`, + content: `function fn1() { } function fn2() { } // Introduce error for fnErr import in main // function fnErr() { } // Error in dependency ts file let x: string = 10;` - }; - const dependencyConfig: File = { - path: `${dependecyLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true, outFile: "../dependency.js" } }) - }; - const usageTs: File = { - path: `${usageLocation}/usage.ts`, - content: `fn1(); + }; + const dependencyConfig: ts.projectSystem.File = { + path: `${dependecyLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true, outFile: "../dependency.js" } }) + }; + const usageTs: ts.projectSystem.File = { + path: `${usageLocation}/usage.ts`, + content: `fn1(); fn2(); fnErr(); ` - }; - const usageConfig: File = { - path: `${usageLocation}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, outFile: "../usage.js" }, - references: [{ path: "../dependency" }] - }) - }; - verifyUsageAndDependency("with non module", dependencyTs, dependencyConfig, usageTs, usageConfig); - }); + }; + const usageConfig: ts.projectSystem.File = { + path: `${usageLocation}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, outFile: "../usage.js" }, + references: [{ path: "../dependency" }] + }) + }; + verifyUsageAndDependency("with non module", dependencyTs, dependencyConfig, usageTs, usageConfig); }); -} +}); diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 694b279395294..d03e2167deb99 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1,471 +1,466 @@ -namespace ts.projectSystem { - export function createHostWithSolutionBuild(files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], rootNames: readonly string[]) { - const host = createServerHost(files); - // ts build should succeed - tscWatch.ensureErrorFreeBuild(host, rootNames); - return host; - } - - describe("unittests:: tsserver:: with project references and tsbuild", () => { - describe("with container project", () => { - function getProjectFiles(project: string): [File, File] { - return [ - TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile(project, "index.ts"), - ]; - } - - const project = "container"; - const containerLib = getProjectFiles("container/lib"); - const containerExec = getProjectFiles("container/exec"); - const containerCompositeExec = getProjectFiles("container/compositeExec"); - const containerConfig = TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"); - const files = [libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig]; - - it("does not error on container only project", () => { - const host = createHostWithSolutionBuild(files, [containerConfig.path]); - - // Open external project for the folder - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - const service = session.getProjectService(); - service.openExternalProjects([{ - projectFileName: TestFSWithWatch.getTsBuildProjectFilePath(project, project), - rootFiles: files.map(f => ({ fileName: f.path })), - options: {} - }]); - files.forEach(f => { - const args: protocol.FileRequestArgs = { - file: f.path, - projectFileName: endsWith(f.path, "tsconfig.json") ? f.path : undefined - }; - session.executeCommandSeq({ - command: protocol.CommandTypes.SyntacticDiagnosticsSync, - arguments: args - }); - session.executeCommandSeq({ - command: protocol.CommandTypes.SemanticDiagnosticsSync, - arguments: args - }); +import * as ts from "../../_namespaces/ts"; +import { createHostWithSolutionBuild } from "./helpers"; + +describe("unittests:: tsserver:: with project references and tsbuild", () => { + describe("with container project", () => { + function getProjectFiles(project: string): [ts.projectSystem.File, ts.projectSystem.File] { + return [ + ts.TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"), + ts.TestFSWithWatch.getTsBuildProjectFile(project, "index.ts"), + ]; + } + + const project = "container"; + const containerLib = getProjectFiles("container/lib"); + const containerExec = getProjectFiles("container/exec"); + const containerCompositeExec = getProjectFiles("container/compositeExec"); + const containerConfig = ts.TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"); + const files = [ts.projectSystem.libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig]; + + it("does not error on container only project", () => { + const host = createHostWithSolutionBuild(files, [containerConfig.path]); + + // Open external project for the folder + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + const service = session.getProjectService(); + service.openExternalProjects([{ + projectFileName: ts.TestFSWithWatch.getTsBuildProjectFilePath(project, project), + rootFiles: files.map(f => ({ fileName: f.path })), + options: {} + }]); + files.forEach(f => { + const args: ts.projectSystem.protocol.FileRequestArgs = { + file: f.path, + projectFileName: ts.endsWith(f.path, "tsconfig.json") ? f.path : undefined + }; + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.SyntacticDiagnosticsSync, + arguments: args }); - const containerProject = service.configuredProjects.get(containerConfig.path)!; - session.executeCommandSeq({ - command: protocol.CommandTypes.CompilerOptionsDiagnosticsFull, - arguments: { projectFileName: containerProject.projectName } + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.SemanticDiagnosticsSync, + arguments: args }); - baselineTsserverLogs("projectReferences", `does not error on container only project`, session); }); - - it("can successfully find references with --out options", () => { - const host = createHostWithSolutionBuild(files, [containerConfig.path]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([containerCompositeExec[1]], session); - const myConstStart = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); - session.executeCommandSeq({ - command: protocol.CommandTypes.Rename, - arguments: { file: containerCompositeExec[1].path, ...myConstStart } - }); - - baselineTsserverLogs("projectReferences", `can successfully find references with out option`, session); + const containerProject = service.configuredProjects.get(containerConfig.path)!; + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.CompilerOptionsDiagnosticsFull, + arguments: { projectFileName: containerProject.projectName } }); + ts.projectSystem.baselineTsserverLogs("projectReferences", `does not error on container only project`, session); + }); - it("ancestor and project ref management", () => { - const tempFile: File = { - path: `/user/username/projects/temp/temp.ts`, - content: "let x = 10" - }; - const host = createHostWithSolutionBuild(files.concat([tempFile]), [containerConfig.path]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([containerCompositeExec[1]], session); - const service = session.getProjectService(); - - // Open temp file and verify all projects alive - openFilesForSession([tempFile], session); - - // Ref projects are loaded after as part of this command - const locationOfMyConst = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); - session.executeCommandSeq({ - command: protocol.CommandTypes.Rename, - arguments: { - file: containerCompositeExec[1].path, - ...locationOfMyConst - } - }); + it("can successfully find references with --out options", () => { + const host = createHostWithSolutionBuild(files, [containerConfig.path]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([containerCompositeExec[1]], session); + const myConstStart = ts.projectSystem.protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Rename, + arguments: { file: containerCompositeExec[1].path, ...myConstStart } + }); - // Open temp file and verify all projects alive - service.closeClientFile(tempFile.path); - openFilesForSession([tempFile], session); + ts.projectSystem.baselineTsserverLogs("projectReferences", `can successfully find references with out option`, session); + }); - // Close all files and open temp file, only inferred project should be alive - service.closeClientFile(containerCompositeExec[1].path); - service.closeClientFile(tempFile.path); - openFilesForSession([tempFile], session); - baselineTsserverLogs("projectReferences", `ancestor and project ref management`, session); + it("ancestor and project ref management", () => { + const tempFile: ts.projectSystem.File = { + path: `/user/username/projects/temp/temp.ts`, + content: "let x = 10" + }; + const host = createHostWithSolutionBuild(files.concat([tempFile]), [containerConfig.path]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([containerCompositeExec[1]], session); + const service = session.getProjectService(); + + // Open temp file and verify all projects alive + ts.projectSystem.openFilesForSession([tempFile], session); + + // Ref projects are loaded after as part of this command + const locationOfMyConst = ts.projectSystem.protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Rename, + arguments: { + file: containerCompositeExec[1].path, + ...locationOfMyConst + } }); - }); - describe("when root file is file from referenced project", () => { - function verify(disableSourceOfProjectReferenceRedirect: boolean) { - const projectLocation = `/user/username/projects/project`; - const commonConfig: File = { - path: `${projectLocation}/src/common/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - declarationMap: true, - outDir: "../../out", - baseUrl: "..", - disableSourceOfProjectReferenceRedirect - }, - include: ["./**/*"] - }) - }; - const keyboardTs: File = { - path: `${projectLocation}/src/common/input/keyboard.ts`, - content: `function bar() { return "just a random function so .d.ts location doesnt match"; } -export function evaluateKeyboardEvent() { }` - }; - const keyboardTestTs: File = { - path: `${projectLocation}/src/common/input/keyboard.test.ts`, - content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; -function testEvaluateKeyboardEvent() { - return evaluateKeyboardEvent(); -} -` - }; - const srcConfig: File = { - path: `${projectLocation}/src/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - declarationMap: true, - outDir: "../out", - baseUrl: ".", - paths: { - "common/*": ["./common/*"], - }, - tsBuildInfoFile: "../out/src.tsconfig.tsbuildinfo", - disableSourceOfProjectReferenceRedirect - }, - include: ["./**/*"], - references: [ - { path: "./common" } - ] - }) - }; - const terminalTs: File = { - path: `${projectLocation}/src/terminal.ts`, - content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; -function foo() { - return evaluateKeyboardEvent(); -} -` - }; - const host = createHostWithSolutionBuild( - [commonConfig, keyboardTs, keyboardTestTs, srcConfig, terminalTs, libFile], - [srcConfig.path] - ); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([keyboardTs, terminalTs], session); - - const searchStr = "evaluateKeyboardEvent"; - const importStr = `import { evaluateKeyboardEvent } from 'common/input/keyboard';`; - const result = session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(keyboardTs, searchStr) - }).response as protocol.ReferencesResponseBody; - assert.deepEqual(result, { - refs: [ - makeReferenceItem({ - file: keyboardTs, - text: searchStr, - contextText: `export function evaluateKeyboardEvent() { }`, - isDefinition: true, - lineText: `export function evaluateKeyboardEvent() { }` - }), - makeReferenceItem({ - file: keyboardTestTs, - text: searchStr, - contextText: importStr, - isDefinition: false, - isWriteAccess: true, - lineText: importStr - }), - makeReferenceItem({ - file: keyboardTestTs, - text: searchStr, - options: { index: 1 }, - isDefinition: false, - lineText: ` return evaluateKeyboardEvent();` - }), - makeReferenceItem({ - file: terminalTs, - text: searchStr, - contextText: importStr, - isDefinition: false, - isWriteAccess: true, - lineText: importStr - }), - makeReferenceItem({ - file: terminalTs, - text: searchStr, - options: { index: 1 }, - isDefinition: false, - lineText: ` return evaluateKeyboardEvent();` - }), - ], - symbolName: searchStr, - symbolStartOffset: protocolLocationFromSubstring(keyboardTs.content, searchStr).offset, - symbolDisplayString: "function evaluateKeyboardEvent(): void" - }); - baselineTsserverLogs("projectReferences", `root file is file from referenced project${disableSourceOfProjectReferenceRedirect ? " and using declaration maps" : ""}`, session); - } + // Open temp file and verify all projects alive + service.closeClientFile(tempFile.path); + ts.projectSystem.openFilesForSession([tempFile], session); - it(`when using declaration file maps to navigate between projects`, () => { - verify(/*disableSourceOfProjectReferenceRedirect*/ true); - }); - it(`when using original source files in the project`, () => { - verify(/*disableSourceOfProjectReferenceRedirect*/ false); - }); + // Close all files and open temp file, only inferred project should be alive + service.closeClientFile(containerCompositeExec[1].path); + service.closeClientFile(tempFile.path); + ts.projectSystem.openFilesForSession([tempFile], session); + ts.projectSystem.baselineTsserverLogs("projectReferences", `ancestor and project ref management`, session); }); + }); - it("reusing d.ts files from composite and non composite projects", () => { - const configA: File = { - path: `${tscWatch.projectRoot}/compositea/tsconfig.json`, + describe("when root file is file from referenced project", () => { + function verify(disableSourceOfProjectReferenceRedirect: boolean) { + const projectLocation = `/user/username/projects/project`; + const commonConfig: ts.projectSystem.File = { + path: `${projectLocation}/src/common/tsconfig.json`, content: JSON.stringify({ compilerOptions: { composite: true, - outDir: "../dist/", - rootDir: "../", - baseUrl: "../", - paths: { "@ref/*": ["./dist/*"] } - } + declarationMap: true, + outDir: "../../out", + baseUrl: "..", + disableSourceOfProjectReferenceRedirect + }, + include: ["./**/*"] }) }; - const aTs: File = { - path: `${tscWatch.projectRoot}/compositea/a.ts`, - content: `import { b } from "@ref/compositeb/b";` - }; - const a2Ts: File = { - path: `${tscWatch.projectRoot}/compositea/a2.ts`, - content: `export const x = 10;` - }; - const configB: File = { - path: `${tscWatch.projectRoot}/compositeb/tsconfig.json`, - content: configA.content - }; - const bTs: File = { - path: `${tscWatch.projectRoot}/compositeb/b.ts`, - content: "export function b() {}" + const keyboardTs: ts.projectSystem.File = { + path: `${projectLocation}/src/common/input/keyboard.ts`, + content: `function bar() { return "just a random function so .d.ts location doesnt match"; } +export function evaluateKeyboardEvent() { }` }; - const bDts: File = { - path: `${tscWatch.projectRoot}/dist/compositeb/b.d.ts`, - content: "export declare function b(): void;" + const keyboardTestTs: ts.projectSystem.File = { + path: `${projectLocation}/src/common/input/keyboard.test.ts`, + content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; +function testEvaluateKeyboardEvent() { + return evaluateKeyboardEvent(); +} +` }; - const configC: File = { - path: `${tscWatch.projectRoot}/compositec/tsconfig.json`, + const srcConfig: ts.projectSystem.File = { + path: `${projectLocation}/src/tsconfig.json`, content: JSON.stringify({ compilerOptions: { composite: true, - outDir: "../dist/", - rootDir: "../", - baseUrl: "../", - paths: { "@ref/*": ["./*"] } + declarationMap: true, + outDir: "../out", + baseUrl: ".", + paths: { + "common/*": ["./common/*"], + }, + tsBuildInfoFile: "../out/src.tsconfig.tsbuildinfo", + disableSourceOfProjectReferenceRedirect }, - references: [{ path: "../compositeb" }] + include: ["./**/*"], + references: [ + { path: "./common" } + ] }) }; - const cTs: File = { - path: `${tscWatch.projectRoot}/compositec/c.ts`, - content: aTs.content + const terminalTs: ts.projectSystem.File = { + path: `${projectLocation}/src/terminal.ts`, + content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; +function foo() { + return evaluateKeyboardEvent(); +} +` }; - const files = [libFile, aTs, a2Ts, configA, bDts, bTs, configB, cTs, configC]; - const host = createServerHost(files); - const service = createProjectService(host); - service.openClientFile(aTs.path); - service.checkNumberOfProjects({ configuredProjects: 1 }); - - // project A referencing b.d.ts without project reference - const projectA = service.configuredProjects.get(configA.path)!; - assert.isDefined(projectA); - checkProjectActualFiles(projectA, [aTs.path, a2Ts.path, bDts.path, libFile.path, configA.path]); - - // reuses b.d.ts but sets the path and resolved path since projectC has project references - // as the real resolution was to b.ts - service.openClientFile(cTs.path); - service.checkNumberOfProjects({ configuredProjects: 2 }); - const projectC = service.configuredProjects.get(configC.path)!; - checkProjectActualFiles(projectC, [cTs.path, bTs.path, libFile.path, configC.path]); - - // Now new project for project A tries to reuse b but there is no filesByName mapping for b's source location - host.writeFile(a2Ts.path, `${a2Ts.content}export const y = 30;`); - assert.isTrue(projectA.dirty); - projectA.updateGraph(); + const host = createHostWithSolutionBuild( + [commonConfig, keyboardTs, keyboardTestTs, srcConfig, terminalTs, ts.projectSystem.libFile], + [srcConfig.path] + ); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([keyboardTs, terminalTs], session); + + const searchStr = "evaluateKeyboardEvent"; + const importStr = `import { evaluateKeyboardEvent } from 'common/input/keyboard';`; + const result = session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(keyboardTs, searchStr) + }).response as ts.projectSystem.protocol.ReferencesResponseBody; + assert.deepEqual(result, { + refs: [ + ts.projectSystem.makeReferenceItem({ + file: keyboardTs, + text: searchStr, + contextText: `export function evaluateKeyboardEvent() { }`, + isDefinition: true, + lineText: `export function evaluateKeyboardEvent() { }` + }), + ts.projectSystem.makeReferenceItem({ + file: keyboardTestTs, + text: searchStr, + contextText: importStr, + isDefinition: false, + isWriteAccess: true, + lineText: importStr + }), + ts.projectSystem.makeReferenceItem({ + file: keyboardTestTs, + text: searchStr, + options: { index: 1 }, + isDefinition: false, + lineText: ` return evaluateKeyboardEvent();` + }), + ts.projectSystem.makeReferenceItem({ + file: terminalTs, + text: searchStr, + contextText: importStr, + isDefinition: false, + isWriteAccess: true, + lineText: importStr + }), + ts.projectSystem.makeReferenceItem({ + file: terminalTs, + text: searchStr, + options: { index: 1 }, + isDefinition: false, + lineText: ` return evaluateKeyboardEvent();` + }), + ], + symbolName: searchStr, + symbolStartOffset: ts.projectSystem.protocolLocationFromSubstring(keyboardTs.content, searchStr).offset, + symbolDisplayString: "function evaluateKeyboardEvent(): void" + }); + ts.projectSystem.baselineTsserverLogs("projectReferences", `root file is file from referenced project${disableSourceOfProjectReferenceRedirect ? " and using declaration maps" : ""}`, session); + } + + it(`when using declaration file maps to navigate between projects`, () => { + verify(/*disableSourceOfProjectReferenceRedirect*/ true); }); + it(`when using original source files in the project`, () => { + verify(/*disableSourceOfProjectReferenceRedirect*/ false); + }); + }); - describe("when references are monorepo like with symlinks", () => { - interface Packages { - bPackageJson: File; - aTest: File; - bFoo: File; - bBar: File; - bSymlink: SymLink; - } - function verifySymlinkScenario(scenario: string, packages: () => Packages) { - describe(`${scenario}: when solution is not built`, () => { - it("with preserveSymlinks turned off", () => { - verifySession(scenario, packages(), /*alreadyBuilt*/ false, {}); - }); - - it("with preserveSymlinks turned on", () => { - verifySession(scenario, packages(), /*alreadyBuilt*/ false, { preserveSymlinks: true }); - }); + it("reusing d.ts files from composite and non composite projects", () => { + const configA: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/compositea/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "../dist/", + rootDir: "../", + baseUrl: "../", + paths: { "@ref/*": ["./dist/*"] } + } + }) + }; + const aTs: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/compositea/a.ts`, + content: `import { b } from "@ref/compositeb/b";` + }; + const a2Ts: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/compositea/a2.ts`, + content: `export const x = 10;` + }; + const configB: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/compositeb/tsconfig.json`, + content: configA.content + }; + const bTs: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/compositeb/b.ts`, + content: "export function b() {}" + }; + const bDts: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/dist/compositeb/b.d.ts`, + content: "export declare function b(): void;" + }; + const configC: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/compositec/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "../dist/", + rootDir: "../", + baseUrl: "../", + paths: { "@ref/*": ["./*"] } + }, + references: [{ path: "../compositeb" }] + }) + }; + const cTs: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/compositec/c.ts`, + content: aTs.content + }; + const files = [ts.projectSystem.libFile, aTs, a2Ts, configA, bDts, bTs, configB, cTs, configC]; + const host = ts.projectSystem.createServerHost(files); + const service = ts.projectSystem.createProjectService(host); + service.openClientFile(aTs.path); + service.checkNumberOfProjects({ configuredProjects: 1 }); + + // project A referencing b.d.ts without project reference + const projectA = service.configuredProjects.get(configA.path)!; + assert.isDefined(projectA); + ts.projectSystem.checkProjectActualFiles(projectA, [aTs.path, a2Ts.path, bDts.path, ts.projectSystem.libFile.path, configA.path]); + + // reuses b.d.ts but sets the path and resolved path since projectC has project references + // as the real resolution was to b.ts + service.openClientFile(cTs.path); + service.checkNumberOfProjects({ configuredProjects: 2 }); + const projectC = service.configuredProjects.get(configC.path)!; + ts.projectSystem.checkProjectActualFiles(projectC, [cTs.path, bTs.path, ts.projectSystem.libFile.path, configC.path]); + + // Now new project for project A tries to reuse b but there is no filesByName mapping for b's source location + host.writeFile(a2Ts.path, `${a2Ts.content}export const y = 30;`); + assert.isTrue(projectA.dirty); + projectA.updateGraph(); + }); + + describe("when references are monorepo like with symlinks", () => { + interface Packages { + bPackageJson: ts.projectSystem.File; + aTest: ts.projectSystem.File; + bFoo: ts.projectSystem.File; + bBar: ts.projectSystem.File; + bSymlink: ts.projectSystem.SymLink; + } + function verifySymlinkScenario(scenario: string, packages: () => Packages) { + describe(`${scenario}: when solution is not built`, () => { + it("with preserveSymlinks turned off", () => { + verifySession(scenario, packages(), /*alreadyBuilt*/ false, {}); }); - describe(`${scenario}: when solution is already built`, () => { - it("with preserveSymlinks turned off", () => { - verifySession(scenario, packages(), /*alreadyBuilt*/ true, {}); - }); + it("with preserveSymlinks turned on", () => { + verifySession(scenario, packages(), /*alreadyBuilt*/ false, { preserveSymlinks: true }); + }); + }); - it("with preserveSymlinks turned on", () => { - verifySession(scenario, packages(), /*alreadyBuilt*/ true, { preserveSymlinks: true }); - }); + describe(`${scenario}: when solution is already built`, () => { + it("with preserveSymlinks turned off", () => { + verifySession(scenario, packages(), /*alreadyBuilt*/ true, {}); }); - } - function verifySession(scenario: string, { bPackageJson, aTest, bFoo, bBar, bSymlink }: Packages, alreadyBuilt: boolean, extraOptions: CompilerOptions) { - const aConfig = config("A", extraOptions, ["../B"]); - const bConfig = config("B", extraOptions); - const files = [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink]; - const host = alreadyBuilt ? - createHostWithSolutionBuild(files, [aConfig.path]) : - createServerHost(files); - - // Create symlink in node module - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([aTest], session); - verifyGetErrRequest({ session, host, files: [aTest] }); - session.executeCommandSeq({ - command: protocol.CommandTypes.UpdateOpen, - arguments: { - changedFiles: [{ - fileName: aTest.path, - textChanges: [{ - newText: "\n", - start: { line: 5, offset: 1 }, - end: { line: 5, offset: 1 } - }] - }] - } + it("with preserveSymlinks turned on", () => { + verifySession(scenario, packages(), /*alreadyBuilt*/ true, { preserveSymlinks: true }); }); - verifyGetErrRequest({ session, host, files: [aTest] }); - baselineTsserverLogs("projectReferences", `monorepo like with symlinks ${scenario} and solution is ${alreadyBuilt ? "built" : "not built"}${extraOptions.preserveSymlinks ? " with preserveSymlinks" : ""}`, session); - } + }); + } + + function verifySession(scenario: string, { bPackageJson, aTest, bFoo, bBar, bSymlink }: Packages, alreadyBuilt: boolean, extraOptions: ts.CompilerOptions) { + const aConfig = config("A", extraOptions, ["../B"]); + const bConfig = config("B", extraOptions); + const files = [ts.projectSystem.libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink]; + const host = alreadyBuilt ? + createHostWithSolutionBuild(files, [aConfig.path]) : + ts.projectSystem.createServerHost(files); + + // Create symlink in node module + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([aTest], session); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [aTest] }); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.UpdateOpen, + arguments: { + changedFiles: [{ + fileName: aTest.path, + textChanges: [{ + newText: "\n", + start: { line: 5, offset: 1 }, + end: { line: 5, offset: 1 } + }] + }] + } + }); + ts.projectSystem.verifyGetErrRequest({ session, host, files: [aTest] }); + ts.projectSystem.baselineTsserverLogs("projectReferences", `monorepo like with symlinks ${scenario} and solution is ${alreadyBuilt ? "built" : "not built"}${extraOptions.preserveSymlinks ? " with preserveSymlinks" : ""}`, session); + } - function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File { - return { - path: `${tscWatch.projectRoot}/packages/${packageName}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - outDir: "lib", - rootDir: "src", - composite: true, - ...extraOptions - }, - include: ["src"], - ...(references ? { references: references.map(path => ({ path })) } : {}) - }) - }; - } + function config(packageName: string, extraOptions: ts.CompilerOptions, references?: string[]): ts.projectSystem.File { + return { + path: `${ts.tscWatch.projectRoot}/packages/${packageName}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + outDir: "lib", + rootDir: "src", + composite: true, + ...extraOptions + }, + include: ["src"], + ...(references ? { references: references.map(path => ({ path })) } : {}) + }) + }; + } - function file(packageName: string, fileName: string, content: string): File { - return { - path: `${tscWatch.projectRoot}/packages/${packageName}/src/${fileName}`, - content - }; - } + function file(packageName: string, fileName: string, content: string): ts.projectSystem.File { + return { + path: `${ts.tscWatch.projectRoot}/packages/${packageName}/src/${fileName}`, + content + }; + } - function verifyMonoRepoLike(scope = "") { - verifySymlinkScenario(`when packageJson has types field and has index.ts${scope ? " with scoped package" : ""}`, () => ({ - bPackageJson: { - path: `${tscWatch.projectRoot}/packages/B/package.json`, - content: JSON.stringify({ - main: "lib/index.js", - types: "lib/index.d.ts" - }) - }, - aTest: file("A", "index.ts", `import { foo } from '${scope}b'; + function verifyMonoRepoLike(scope = "") { + verifySymlinkScenario(`when packageJson has types field and has index.ts${scope ? " with scoped package" : ""}`, () => ({ + bPackageJson: { + path: `${ts.tscWatch.projectRoot}/packages/B/package.json`, + content: JSON.stringify({ + main: "lib/index.js", + types: "lib/index.d.ts" + }) + }, + aTest: file("A", "index.ts", `import { foo } from '${scope}b'; import { bar } from '${scope}b/lib/bar'; foo(); bar(); `), - bFoo: file("B", "index.ts", `export function foo() { }`), - bBar: file("B", "bar.ts", `export function bar() { }`), - bSymlink: { - path: `${tscWatch.projectRoot}/node_modules/${scope}b`, - symLink: `${tscWatch.projectRoot}/packages/B` - } - })); - - verifySymlinkScenario(`when referencing file from subFolder${scope ? " with scoped package" : ""}`, () => ({ - bPackageJson: { - path: `${tscWatch.projectRoot}/packages/B/package.json`, - content: "{}" - }, - aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo'; + bFoo: file("B", "index.ts", `export function foo() { }`), + bBar: file("B", "bar.ts", `export function bar() { }`), + bSymlink: { + path: `${ts.tscWatch.projectRoot}/node_modules/${scope}b`, + symLink: `${ts.tscWatch.projectRoot}/packages/B` + } + })); + + verifySymlinkScenario(`when referencing file from subFolder${scope ? " with scoped package" : ""}`, () => ({ + bPackageJson: { + path: `${ts.tscWatch.projectRoot}/packages/B/package.json`, + content: "{}" + }, + aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo'; import { bar } from '${scope}b/lib/bar/foo'; foo(); bar(); `), - bFoo: file("B", "foo.ts", `export function foo() { }`), - bBar: file("B", "bar/foo.ts", `export function bar() { }`), - bSymlink: { - path: `${tscWatch.projectRoot}/node_modules/${scope}b`, - symLink: `${tscWatch.projectRoot}/packages/B` - } - })); - } + bFoo: file("B", "foo.ts", `export function foo() { }`), + bBar: file("B", "bar/foo.ts", `export function bar() { }`), + bSymlink: { + path: `${ts.tscWatch.projectRoot}/node_modules/${scope}b`, + symLink: `${ts.tscWatch.projectRoot}/packages/B` + } + })); + } - describe("when package is not scoped", () => { - verifyMonoRepoLike(); - }); - describe("when package is scoped", () => { - verifyMonoRepoLike("@issue/"); - }); + describe("when package is not scoped", () => { + verifyMonoRepoLike(); }); + describe("when package is scoped", () => { + verifyMonoRepoLike("@issue/"); + }); + }); - it("when the referenced projects have allowJs and emitDeclarationOnly", () => { - const compositeConfig: File = { - path: `${tscWatch.projectRoot}/packages/emit-composite/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - allowJs: true, - emitDeclarationOnly: true, - outDir: "lib", - rootDir: "src" - }, - include: ["src"] - }) - }; - const compositePackageJson: File = { - path: `${tscWatch.projectRoot}/packages/emit-composite/package.json`, - content: JSON.stringify({ - name: "emit-composite", - version: "1.0.0", - main: "src/index.js", - typings: "lib/index.d.ts" - }) - }; - const compositeIndex: File = { - path: `${tscWatch.projectRoot}/packages/emit-composite/src/index.js`, - content: `const testModule = require('./testModule'); + it("when the referenced projects have allowJs and emitDeclarationOnly", () => { + const compositeConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/packages/emit-composite/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + allowJs: true, + emitDeclarationOnly: true, + outDir: "lib", + rootDir: "src" + }, + include: ["src"] + }) + }; + const compositePackageJson: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/packages/emit-composite/package.json`, + content: JSON.stringify({ + name: "emit-composite", + version: "1.0.0", + main: "src/index.js", + typings: "lib/index.d.ts" + }) + }; + const compositeIndex: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/packages/emit-composite/src/index.js`, + content: `const testModule = require('./testModule'); module.exports = { ...testModule }` - }; - const compositeTestModule: File = { - path: `${tscWatch.projectRoot}/packages/emit-composite/src/testModule.js`, - content: `/** + }; + const compositeTestModule: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/packages/emit-composite/src/testModule.js`, + content: `/** * @param {string} arg */ const testCompositeFunction = (arg) => { @@ -473,1121 +468,1120 @@ module.exports = { module.exports = { testCompositeFunction }` - }; - const consumerConfig: File = { - path: `${tscWatch.projectRoot}/packages/consumer/tsconfig.json`, - content: JSON.stringify({ - include: ["src"], - references: [{ path: "../emit-composite" }] - }) - }; - const consumerIndex: File = { - path: `${tscWatch.projectRoot}/packages/consumer/src/index.ts`, - content: `import { testCompositeFunction } from 'emit-composite'; + }; + const consumerConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/packages/consumer/tsconfig.json`, + content: JSON.stringify({ + include: ["src"], + references: [{ path: "../emit-composite" }] + }) + }; + const consumerIndex: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/packages/consumer/src/index.ts`, + content: `import { testCompositeFunction } from 'emit-composite'; testCompositeFunction('why hello there'); testCompositeFunction('why hello there', 42);` - }; - const symlink: SymLink = { - path: `${tscWatch.projectRoot}/node_modules/emit-composite`, - symLink: `${tscWatch.projectRoot}/packages/emit-composite` - }; - const host = createServerHost([libFile, compositeConfig, compositePackageJson, compositeIndex, compositeTestModule, consumerConfig, consumerIndex, symlink], { useCaseSensitiveFileNames: true }); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([consumerIndex], session); - verifyGetErrRequest({ host, session, files: [consumerIndex] }); - baselineTsserverLogs("projectReferences", `when the referenced projects have allowJs and emitDeclarationOnly`, session); - }); + }; + const symlink: ts.projectSystem.SymLink = { + path: `${ts.tscWatch.projectRoot}/node_modules/emit-composite`, + symLink: `${ts.tscWatch.projectRoot}/packages/emit-composite` + }; + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, compositeConfig, compositePackageJson, compositeIndex, compositeTestModule, consumerConfig, consumerIndex, symlink], { useCaseSensitiveFileNames: true }); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([consumerIndex], session); + ts.projectSystem.verifyGetErrRequest({ host, session, files: [consumerIndex] }); + ts.projectSystem.baselineTsserverLogs("projectReferences", `when the referenced projects have allowJs and emitDeclarationOnly`, session); + }); - it("when finding local reference doesnt load ancestor/sibling projects", () => { - const solutionLocation = "/user/username/projects/solution"; - const solution: File = { - path: `${solutionLocation}/tsconfig.json`, - content: JSON.stringify({ - files: [], - include: [], - references: [ - { path: "./compiler" }, - { path: "./services" }, - ] - }) - }; - const compilerConfig: File = { - path: `${solutionLocation}/compiler/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - module: "none" - }, - files: ["./types.ts", "./program.ts"] - }) - }; - const typesFile: File = { - path: `${solutionLocation}/compiler/types.ts`, - content: ` + it("when finding local reference doesnt load ancestor/sibling projects", () => { + const solutionLocation = "/user/username/projects/solution"; + const solution: ts.projectSystem.File = { + path: `${solutionLocation}/tsconfig.json`, + content: JSON.stringify({ + files: [], + include: [], + references: [ + { path: "./compiler" }, + { path: "./services" }, + ] + }) + }; + const compilerConfig: ts.projectSystem.File = { + path: `${solutionLocation}/compiler/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + module: "none" + }, + files: ["./types.ts", "./program.ts"] + }) + }; + const typesFile: ts.projectSystem.File = { + path: `${solutionLocation}/compiler/types.ts`, + content: ` namespace ts { export interface Program { getSourceFiles(): string[]; } }` - }; - const programFile: File = { - path: `${solutionLocation}/compiler/program.ts`, - content: ` + }; + const programFile: ts.projectSystem.File = { + path: `${solutionLocation}/compiler/program.ts`, + content: ` namespace ts { export const program: Program = { getSourceFiles: () => [getSourceFile()] }; function getSourceFile() { return "something"; } }` - }; - const servicesConfig: File = { - path: `${solutionLocation}/services/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true - }, - files: ["./services.ts"], - references: [ - { path: "../compiler" } - ] - }) - }; - const servicesFile: File = { - path: `${solutionLocation}/services/services.ts`, - content: ` + }; + const servicesConfig: ts.projectSystem.File = { + path: `${solutionLocation}/services/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true + }, + files: ["./services.ts"], + references: [ + { path: "../compiler" } + ] + }) + }; + const servicesFile: ts.projectSystem.File = { + path: `${solutionLocation}/services/services.ts`, + content: ` namespace ts { const result = program.getSourceFiles(); }` - }; - - const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; - const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([programFile], session); - - // Find all references for getSourceFile - // Shouldnt load more projects - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(programFile, "getSourceFile", { index: 1 }) - }); + }; + + const files = [ts.projectSystem.libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, ts.projectSystem.libFile]; + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([programFile], session); + + // Find all references for getSourceFile + // Shouldnt load more projects + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(programFile, "getSourceFile", { index: 1 }) + }); - // Find all references for getSourceFiles - // Should load more projects - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles") - }); - baselineTsserverLogs("projectReferences", `finding local reference doesnt load ancestor/sibling projects`, session); + // Find all references for getSourceFiles + // Should load more projects + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(programFile, "getSourceFiles") }); + ts.projectSystem.baselineTsserverLogs("projectReferences", `finding local reference doesnt load ancestor/sibling projects`, session); + }); - it("when finding references in overlapping projects", () => { - const solutionLocation = "/user/username/projects/solution"; - const solutionConfig: File = { - path: `${solutionLocation}/tsconfig.json`, - content: JSON.stringify({ - files: [], - include: [], - references: [ - { path: "./a" }, - { path: "./b" }, - { path: "./c" }, - { path: "./d" }, - ] - }) - }; - const aConfig: File = { - path: `${solutionLocation}/a/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - module: "none" - }, - files: ["./index.ts"] - }) - }; - const aFile: File = { - path: `${solutionLocation}/a/index.ts`, - content: ` + it("when finding references in overlapping projects", () => { + const solutionLocation = "/user/username/projects/solution"; + const solutionConfig: ts.projectSystem.File = { + path: `${solutionLocation}/tsconfig.json`, + content: JSON.stringify({ + files: [], + include: [], + references: [ + { path: "./a" }, + { path: "./b" }, + { path: "./c" }, + { path: "./d" }, + ] + }) + }; + const aConfig: ts.projectSystem.File = { + path: `${solutionLocation}/a/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + module: "none" + }, + files: ["./index.ts"] + }) + }; + const aFile: ts.projectSystem.File = { + path: `${solutionLocation}/a/index.ts`, + content: ` export interface I { M(): void; }` - }; + }; - const bConfig: File = { - path: `${solutionLocation}/b/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true - }, - files: ["./index.ts"], - references: [ - { path: "../a" } - ] - }) - }; - const bFile: File = { - path: `${solutionLocation}/b/index.ts`, - content: ` + const bConfig: ts.projectSystem.File = { + path: `${solutionLocation}/b/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true + }, + files: ["./index.ts"], + references: [ + { path: "../a" } + ] + }) + }; + const bFile: ts.projectSystem.File = { + path: `${solutionLocation}/b/index.ts`, + content: ` import { I } from "../a"; export class B implements I { M() {} }` - }; + }; - const cConfig: File = { - path: `${solutionLocation}/c/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true - }, - files: ["./index.ts"], - references: [ - { path: "../b" } - ] - }) - }; - const cFile: File = { - path: `${solutionLocation}/c/index.ts`, - content: ` + const cConfig: ts.projectSystem.File = { + path: `${solutionLocation}/c/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true + }, + files: ["./index.ts"], + references: [ + { path: "../b" } + ] + }) + }; + const cFile: ts.projectSystem.File = { + path: `${solutionLocation}/c/index.ts`, + content: ` import { I } from "../a"; import { B } from "../b"; export const C: I = new B(); ` - }; + }; - const dConfig: File = { - path: `${solutionLocation}/d/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true - }, - files: ["./index.ts"], - references: [ - { path: "../c" } - ] - }) - }; - const dFile: File = { - path: `${solutionLocation}/d/index.ts`, - content: ` + const dConfig: ts.projectSystem.File = { + path: `${solutionLocation}/d/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true + }, + files: ["./index.ts"], + references: [ + { path: "../c" } + ] + }) + }; + const dFile: ts.projectSystem.File = { + path: `${solutionLocation}/d/index.ts`, + content: ` import { I } from "../a"; import { C } from "../c"; export const D: I = C; ` - }; + }; - const files = [libFile, solutionConfig, aConfig, aFile, bConfig, bFile, cConfig, cFile, dConfig, dFile, libFile]; - const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([bFile], session); + const files = [ts.projectSystem.libFile, solutionConfig, aConfig, aFile, bConfig, bFile, cConfig, cFile, dConfig, dFile, ts.projectSystem.libFile]; + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([bFile], session); - // The first search will trigger project loads - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(bFile, "I", { index: 1 }) - }); - - // The second search starts with the projects already loaded - // Formerly, this would search some projects multiple times - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(bFile, "I", { index: 1 }) - }); - - baselineTsserverLogs("projectReferences", `finding references in overlapping projects`, session); + // The first search will trigger project loads + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(bFile, "I", { index: 1 }) }); - describe("special handling of localness of the definitions for findAllRefs", () => { - function verify(scenario: string, definition: string, usage: string, referenceTerm: string) { - it(scenario, () => { - const solutionLocation = "/user/username/projects/solution"; - const solution: File = { - path: `${solutionLocation}/tsconfig.json`, - content: JSON.stringify({ - files: [], - references: [ - { path: "./api" }, - { path: "./app" }, - ] - }) - }; - const apiConfig: File = { - path: `${solutionLocation}/api/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "dist", - rootDir: "src", - }, - include: ["src"], - references: [{ path: "../shared" }] - }) - }; - const apiFile: File = { - path: `${solutionLocation}/api/src/server.ts`, - content: `import * as shared from "../../shared/dist"; -${usage}` - }; - const appConfig: File = { - path: `${solutionLocation}/app/tsconfig.json`, - content: apiConfig.content - }; - const appFile: File = { - path: `${solutionLocation}/app/src/app.ts`, - content: apiFile.content - }; - const sharedConfig: File = { - path: `${solutionLocation}/shared/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "dist", - rootDir: "src", - }, - include: ["src"] - }) - }; - const sharedFile: File = { - path: `${solutionLocation}/shared/src/index.ts`, - content: definition - }; - const host = createServerHost([libFile, solution, libFile, apiConfig, apiFile, appConfig, appFile, sharedConfig, sharedFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([apiFile], session); - - // Find all references - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(apiFile, referenceTerm) - }); - - baselineTsserverLogs("projectReferences", `special handling of localness ${scenario}`, session); - }); - } - - verify( - "when using arrow function assignment", - `export const dog = () => { };`, - `shared.dog();`, - "dog" - ); - - verify( - "when using arrow function as object literal property types", - `export const foo = { bar: () => { } };`, - `shared.foo.bar();`, - "bar" - ); - - verify( - "when using object literal property", - `export const foo = { baz: "BAZ" };`, - `shared.foo.baz;`, - "baz" - ); - - verify( - "when using method of class expression", - `export const foo = class { fly() {} };`, - `const instance = new shared.foo(); -instance.fly();`, - "fly" - ); - - - verify( - // when using arrow function as object literal property is loaded through indirect assignment with original declaration local to project is treated as local - "when using arrow function as object literal property", - `const local = { bar: () => { } }; -export const foo = local;`, - `shared.foo.bar();`, - "bar" - ); + // The second search starts with the projects already loaded + // Formerly, this would search some projects multiple times + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(bFile, "I", { index: 1 }) }); - it("when disableSolutionSearching is true, solution and siblings are not loaded", () => { - const solutionLocation = "/user/username/projects/solution"; - const solution: File = { - path: `${solutionLocation}/tsconfig.json`, - content: JSON.stringify({ - files: [], - include: [], - references: [ - { path: "./compiler" }, - { path: "./services" }, - ] - }) - }; - const compilerConfig: File = { - path: `${solutionLocation}/compiler/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - module: "none", - disableSolutionSearching: true - }, - files: ["./types.ts", "./program.ts"] - }) - }; - const typesFile: File = { - path: `${solutionLocation}/compiler/types.ts`, - content: ` + ts.projectSystem.baselineTsserverLogs("projectReferences", `finding references in overlapping projects`, session); + }); + + describe("special handling of localness of the definitions for findAllRefs", () => { + function verify(scenario: string, definition: string, usage: string, referenceTerm: string) { + it(scenario, () => { + const solutionLocation = "/user/username/projects/solution"; + const solution: ts.projectSystem.File = { + path: `${solutionLocation}/tsconfig.json`, + content: JSON.stringify({ + files: [], + references: [ + { path: "./api" }, + { path: "./app" }, + ] + }) + }; + const apiConfig: ts.projectSystem.File = { + path: `${solutionLocation}/api/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "dist", + rootDir: "src", + }, + include: ["src"], + references: [{ path: "../shared" }] + }) + }; + const apiFile: ts.projectSystem.File = { + path: `${solutionLocation}/api/src/server.ts`, + content: `import * as shared from "../../shared/dist"; +${usage}` + }; + const appConfig: ts.projectSystem.File = { + path: `${solutionLocation}/app/tsconfig.json`, + content: apiConfig.content + }; + const appFile: ts.projectSystem.File = { + path: `${solutionLocation}/app/src/app.ts`, + content: apiFile.content + }; + const sharedConfig: ts.projectSystem.File = { + path: `${solutionLocation}/shared/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "dist", + rootDir: "src", + }, + include: ["src"] + }) + }; + const sharedFile: ts.projectSystem.File = { + path: `${solutionLocation}/shared/src/index.ts`, + content: definition + }; + const host = ts.projectSystem.createServerHost([ts.projectSystem.libFile, solution, ts.projectSystem.libFile, apiConfig, apiFile, appConfig, appFile, sharedConfig, sharedFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([apiFile], session); + + // Find all references + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(apiFile, referenceTerm) + }); + + ts.projectSystem.baselineTsserverLogs("projectReferences", `special handling of localness ${scenario}`, session); + }); + } + + verify( + "when using arrow function assignment", + `export const dog = () => { };`, + `shared.dog();`, + "dog" + ); + + verify( + "when using arrow function as object literal property types", + `export const foo = { bar: () => { } };`, + `shared.foo.bar();`, + "bar" + ); + + verify( + "when using object literal property", + `export const foo = { baz: "BAZ" };`, + `shared.foo.baz;`, + "baz" + ); + + verify( + "when using method of class expression", + `export const foo = class { fly() {} };`, + `const instance = new shared.foo(); +instance.fly();`, + "fly" + ); + + + verify( + // when using arrow function as object literal property is loaded through indirect assignment with original declaration local to project is treated as local + "when using arrow function as object literal property", + `const local = { bar: () => { } }; +export const foo = local;`, + `shared.foo.bar();`, + "bar" + ); + }); + + it("when disableSolutionSearching is true, solution and siblings are not loaded", () => { + const solutionLocation = "/user/username/projects/solution"; + const solution: ts.projectSystem.File = { + path: `${solutionLocation}/tsconfig.json`, + content: JSON.stringify({ + files: [], + include: [], + references: [ + { path: "./compiler" }, + { path: "./services" }, + ] + }) + }; + const compilerConfig: ts.projectSystem.File = { + path: `${solutionLocation}/compiler/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + module: "none", + disableSolutionSearching: true + }, + files: ["./types.ts", "./program.ts"] + }) + }; + const typesFile: ts.projectSystem.File = { + path: `${solutionLocation}/compiler/types.ts`, + content: ` namespace ts { export interface Program { getSourceFiles(): string[]; } }` - }; - const programFile: File = { - path: `${solutionLocation}/compiler/program.ts`, - content: ` + }; + const programFile: ts.projectSystem.File = { + path: `${solutionLocation}/compiler/program.ts`, + content: ` namespace ts { export const program: Program = { getSourceFiles: () => [getSourceFile()] }; function getSourceFile() { return "something"; } }` - }; - const servicesConfig: File = { - path: `${solutionLocation}/services/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true - }, - files: ["./services.ts"], - references: [ - { path: "../compiler" } - ] - }) - }; - const servicesFile: File = { - path: `${solutionLocation}/services/services.ts`, - content: ` + }; + const servicesConfig: ts.projectSystem.File = { + path: `${solutionLocation}/services/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true + }, + files: ["./services.ts"], + references: [ + { path: "../compiler" } + ] + }) + }; + const servicesFile: ts.projectSystem.File = { + path: `${solutionLocation}/services/services.ts`, + content: ` namespace ts { const result = program.getSourceFiles(); }` - }; - - const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; - const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([programFile], session); - - // Find all references - // No new solutions/projects loaded - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles") - }); - baselineTsserverLogs("projectReferences", `with disableSolutionSearching solution and siblings are not loaded`, session); + }; + + const files = [ts.projectSystem.libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, ts.projectSystem.libFile]; + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([programFile], session); + + // Find all references + // No new solutions/projects loaded + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(programFile, "getSourceFiles") }); + ts.projectSystem.baselineTsserverLogs("projectReferences", `with disableSolutionSearching solution and siblings are not loaded`, session); + }); - describe("when default project is solution project", () => { - interface Setup { - scenario: string; - solutionOptions?: CompilerOptions; - solutionFiles?: string[]; - configRefs: string[]; - additionalFiles: readonly File[]; - } - const main: File = { - path: `${tscWatch.projectRoot}/src/main.ts`, - content: `import { foo } from 'helpers/functions'; + describe("when default project is solution project", () => { + interface Setup { + scenario: string; + solutionOptions?: ts.CompilerOptions; + solutionFiles?: string[]; + configRefs: string[]; + additionalFiles: readonly ts.projectSystem.File[]; + } + const main: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/main.ts`, + content: `import { foo } from 'helpers/functions'; export { foo };` - }; - const helper: File = { - path: `${tscWatch.projectRoot}/src/helpers/functions.ts`, - content: `export const foo = 1;` - }; - const mainDts: File = { - path: `${tscWatch.projectRoot}/target/src/main.d.ts`, - content: `import { foo } from 'helpers/functions'; + }; + const helper: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/src/helpers/functions.ts`, + content: `export const foo = 1;` + }; + const mainDts: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/target/src/main.d.ts`, + content: `import { foo } from 'helpers/functions'; export { foo }; //# sourceMappingURL=main.d.ts.map` - }; - const mainDtsMap: File = { - path: `${tscWatch.projectRoot}/target/src/main.d.ts.map`, - content: `{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAC,GAAG,EAAC,CAAC"}` - }; - const helperDts: File = { - path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts`, - content: `export declare const foo = 1; + }; + const mainDtsMap: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/target/src/main.d.ts.map`, + content: `{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAC,GAAG,EAAC,CAAC"}` + }; + const helperDts: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/target/src/helpers/functions.d.ts`, + content: `export declare const foo = 1; //# sourceMappingURL=functions.d.ts.map` + }; + const helperDtsMap: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/target/src/helpers/functions.d.ts.map`, + content: `{"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../src/helpers/functions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,GAAG,IAAI,CAAC"}` + }; + const tsconfigIndirect3: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/indirect3/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + baseUrl: "../target/src/" + }, + }) + }; + const fileResolvingToMainDts: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/indirect3/main.ts`, + content: `import { foo } from 'main'; +foo; +export function bar() {}` + }; + const tsconfigSrcPath = `${ts.tscWatch.projectRoot}/tsconfig-src.json`; + const tsconfigPath = `${ts.tscWatch.projectRoot}/tsconfig.json`; + const dummyFilePath = "/dummy/dummy.ts"; + function setup({ solutionFiles, solutionOptions, configRefs, additionalFiles }: Setup) { + const tsconfigSrc: ts.projectSystem.File = { + path: tsconfigSrcPath, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "./target/", + baseUrl: "./src/" + }, + include: ["./src/**/*"] + }) + }; + const tsconfig: ts.projectSystem.File = { + path: tsconfigPath, + content: JSON.stringify({ + ... (solutionOptions ? { compilerOptions: solutionOptions } : {}), + references: configRefs.map(path => ({ path })), + files: solutionFiles || [] + }) }; - const helperDtsMap: File = { - path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts.map`, - content: `{"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../src/helpers/functions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,GAAG,IAAI,CAAC"}` + const dummyFile: ts.projectSystem.File = { + path: dummyFilePath, + content: "let a = 10;" }; - const tsconfigIndirect3: File = { - path: `${tscWatch.projectRoot}/indirect3/tsconfig.json`, + const host = ts.projectSystem.createServerHost([ + tsconfigSrc, tsconfig, main, helper, + ts.projectSystem.libFile, dummyFile, + mainDts, mainDtsMap, helperDts, helperDtsMap, + tsconfigIndirect3, fileResolvingToMainDts, + ...additionalFiles]); + const session = ts.projectSystem.createSession(host, { canUseEvents: true, logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + const service = session.getProjectService(); + service.openClientFile(main.path); + return { session, service, host }; + } + + function verifySolutionScenario(input: Setup) { + const { session, service, host } = setup(input); + + const info = service.getScriptInfoForPath(main.path as ts.Path)!; + session.logger.startGroup(); + session.logger.info(`getDefaultProject for ${main.path}: ${info.getDefaultProject().projectName}`); + session.logger.info(`findDefaultConfiguredProject for ${main.path}: ${service.findDefaultConfiguredProject(info)!.projectName}`); + session.logger.endGroup(); + + // Verify errors + ts.projectSystem.verifyGetErrRequest({ session, host, files: [main] }); + + // Verify collection of script infos + service.openClientFile(dummyFilePath); + + service.closeClientFile(main.path); + service.closeClientFile(dummyFilePath); + service.openClientFile(dummyFilePath); + + service.openClientFile(main.path); + service.closeClientFile(dummyFilePath); + service.openClientFile(dummyFilePath); + + // Verify Reload projects + service.reloadProjects(); + + // Find all refs + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(main, "foo", { index: 1 }) + }).response as ts.projectSystem.protocol.ReferencesResponseBody; + + service.closeClientFile(main.path); + service.closeClientFile(dummyFilePath); + + // Verify when declaration map references the file + service.openClientFile(fileResolvingToMainDts.path); + + // Find all refs from dts include + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(fileResolvingToMainDts, "foo") + }).response as ts.projectSystem.protocol.ReferencesResponseBody; + ts.projectSystem.baselineTsserverLogs("projectReferences", input.scenario, session); + } + + function getIndirectProject(postfix: string, optionsToExtend?: ts.CompilerOptions) { + const tsconfigIndirect: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig-indirect${postfix}.json`, content: JSON.stringify({ compilerOptions: { - baseUrl: "../target/src/" + composite: true, + outDir: "./target/", + baseUrl: "./src/", + ...optionsToExtend }, + files: [`./indirect${postfix}/main.ts`], + references: [{ path: "./tsconfig-src.json" }] }) }; - const fileResolvingToMainDts: File = { - path: `${tscWatch.projectRoot}/indirect3/main.ts`, - content: `import { foo } from 'main'; -foo; -export function bar() {}` + const indirect: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/indirect${postfix}/main.ts`, + content: fileResolvingToMainDts.content }; - const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`; - const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`; - const dummyFilePath = "/dummy/dummy.ts"; - function setup({ solutionFiles, solutionOptions, configRefs, additionalFiles }: Setup) { - const tsconfigSrc: File = { - path: tsconfigSrcPath, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "./target/", - baseUrl: "./src/" - }, - include: ["./src/**/*"] - }) - }; - const tsconfig: File = { - path: tsconfigPath, - content: JSON.stringify({ - ... (solutionOptions ? { compilerOptions: solutionOptions } : {}), - references: configRefs.map(path => ({ path })), - files: solutionFiles || [] - }) - }; - const dummyFile: File = { - path: dummyFilePath, - content: "let a = 10;" - }; - const host = createServerHost([ - tsconfigSrc, tsconfig, main, helper, - libFile, dummyFile, - mainDts, mainDtsMap, helperDts, helperDtsMap, - tsconfigIndirect3, fileResolvingToMainDts, - ...additionalFiles]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - const service = session.getProjectService(); - service.openClientFile(main.path); - return { session, service, host }; - } + return { tsconfigIndirect, indirect }; + } - function verifySolutionScenario(input: Setup) { - const { session, service, host } = setup(input); + function verifyDisableReferencedProjectLoad(input: Setup) { + const { session, service } = setup(input); - const info = service.getScriptInfoForPath(main.path as Path)!; - session.logger.startGroup(); - session.logger.info(`getDefaultProject for ${main.path}: ${info.getDefaultProject().projectName}`); - session.logger.info(`findDefaultConfiguredProject for ${main.path}: ${service.findDefaultConfiguredProject(info)!.projectName}`); - session.logger.endGroup(); + const info = service.getScriptInfoForPath(main.path as ts.Path)!; + session.logger.startGroup(); + session.logger.info(`getDefaultProject for ${main.path}: ${info.getDefaultProject().projectName}`); + session.logger.info(`findDefaultConfiguredProject for ${main.path}: ${service.findDefaultConfiguredProject(info)?.projectName}`); + session.logger.endGroup(); - // Verify errors - verifyGetErrRequest({ session, host, files: [main] }); + // Verify collection of script infos + service.openClientFile(dummyFilePath); - // Verify collection of script infos - service.openClientFile(dummyFilePath); + service.closeClientFile(main.path); + service.closeClientFile(dummyFilePath); + service.openClientFile(dummyFilePath); - service.closeClientFile(main.path); - service.closeClientFile(dummyFilePath); - service.openClientFile(dummyFilePath); + service.openClientFile(main.path); - service.openClientFile(main.path); - service.closeClientFile(dummyFilePath); - service.openClientFile(dummyFilePath); + // Verify Reload projects + service.reloadProjects(); + ts.projectSystem.baselineTsserverLogs("projectReferences", input.scenario, session); + } - // Verify Reload projects - service.reloadProjects(); + it("when project is directly referenced by solution", () => { + verifySolutionScenario({ + scenario: "project is directly referenced by solution", + configRefs: ["./tsconfig-src.json"], + additionalFiles: ts.emptyArray, + }); + }); - // Find all refs - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(main, "foo", { index: 1 }) - }).response as protocol.ReferencesResponseBody; + it("when project is indirectly referenced by solution", () => { + const { tsconfigIndirect, indirect } = getIndirectProject("1"); + const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); + verifySolutionScenario({ + scenario: "project is indirectly referenced by solution", + configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], + additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], + }); + }); - service.closeClientFile(main.path); - service.closeClientFile(dummyFilePath); + it("disables looking into the child project if disableReferencedProjectLoad is set", () => { + verifyDisableReferencedProjectLoad({ + scenario: "disables looking into the child project if disableReferencedProjectLoad is set", + solutionOptions: { disableReferencedProjectLoad: true }, + configRefs: ["./tsconfig-src.json"], + additionalFiles: ts.emptyArray, + }); + }); - // Verify when declaration map references the file - service.openClientFile(fileResolvingToMainDts.path); + it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => { + const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); + verifyDisableReferencedProjectLoad({ + scenario: "disables looking into the child project if disableReferencedProjectLoad is set in indirect project", + configRefs: ["./tsconfig-indirect1.json"], + additionalFiles: [tsconfigIndirect, indirect], + }); + }); - // Find all refs from dts include - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(fileResolvingToMainDts, "foo") - }).response as protocol.ReferencesResponseBody; - baselineTsserverLogs("projectReferences", input.scenario, session); - } + it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => { + const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); + const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); + verifyDisableReferencedProjectLoad({ + scenario: "disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", + configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], + additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], + }); + }); - function getIndirectProject(postfix: string, optionsToExtend?: CompilerOptions) { - const tsconfigIndirect: File = { - path: `${tscWatch.projectRoot}/tsconfig-indirect${postfix}.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "./target/", - baseUrl: "./src/", - ...optionsToExtend - }, - files: [`./indirect${postfix}/main.ts`], - references: [{ path: "./tsconfig-src.json" }] - }) - }; - const indirect: File = { - path: `${tscWatch.projectRoot}/indirect${postfix}/main.ts`, + describe("when solution is project that contains its own files", () => { + it("when the project found is not solution but references open file through project reference", () => { + const ownMain: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/own/main.ts`, content: fileResolvingToMainDts.content }; - return { tsconfigIndirect, indirect }; - } - - function verifyDisableReferencedProjectLoad(input: Setup) { - const { session, service } = setup(input); - - const info = service.getScriptInfoForPath(main.path as Path)!; - session.logger.startGroup(); - session.logger.info(`getDefaultProject for ${main.path}: ${info.getDefaultProject().projectName}`); - session.logger.info(`findDefaultConfiguredProject for ${main.path}: ${service.findDefaultConfiguredProject(info)?.projectName}`); - session.logger.endGroup(); - - // Verify collection of script infos - service.openClientFile(dummyFilePath); - - service.closeClientFile(main.path); - service.closeClientFile(dummyFilePath); - service.openClientFile(dummyFilePath); - - service.openClientFile(main.path); - - // Verify Reload projects - service.reloadProjects(); - baselineTsserverLogs("projectReferences", input.scenario, session); - } - - it("when project is directly referenced by solution", () => { verifySolutionScenario({ - scenario: "project is directly referenced by solution", + scenario: "solution with its own files and project found is not solution but references open file through project reference", + solutionFiles: [`./own/main.ts`], + solutionOptions: { + outDir: "./target/", + baseUrl: "./src/" + }, configRefs: ["./tsconfig-src.json"], - additionalFiles: emptyArray, + additionalFiles: [ownMain], }); }); it("when project is indirectly referenced by solution", () => { + const ownMain: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/own/main.ts`, + content: `import { bar } from 'main'; +bar;` + }; const { tsconfigIndirect, indirect } = getIndirectProject("1"); const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); verifySolutionScenario({ - scenario: "project is indirectly referenced by solution", + scenario: "solution with its own files and project is indirectly referenced by solution", + solutionFiles: [`./own/main.ts`], + solutionOptions: { + outDir: "./target/", + baseUrl: "./indirect1/" + }, configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], - additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], + additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], }); }); it("disables looking into the child project if disableReferencedProjectLoad is set", () => { + const ownMain: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/own/main.ts`, + content: fileResolvingToMainDts.content + }; verifyDisableReferencedProjectLoad({ - scenario: "disables looking into the child project if disableReferencedProjectLoad is set", - solutionOptions: { disableReferencedProjectLoad: true }, + scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set", + solutionFiles: [`./own/main.ts`], + solutionOptions: { + outDir: "./target/", + baseUrl: "./src/", + disableReferencedProjectLoad: true + }, configRefs: ["./tsconfig-src.json"], - additionalFiles: emptyArray, + additionalFiles: [ownMain], }); }); it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => { + const ownMain: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/own/main.ts`, + content: `import { bar } from 'main'; +bar;` + }; const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); verifyDisableReferencedProjectLoad({ - scenario: "disables looking into the child project if disableReferencedProjectLoad is set in indirect project", + scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set in indirect project", + solutionFiles: [`./own/main.ts`], + solutionOptions: { + outDir: "./target/", + baseUrl: "./indirect1/", + }, configRefs: ["./tsconfig-indirect1.json"], - additionalFiles: [tsconfigIndirect, indirect], + additionalFiles: [tsconfigIndirect, indirect, ownMain], }); }); it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => { + const ownMain: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/own/main.ts`, + content: `import { bar } from 'main'; +bar;` + }; const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); verifyDisableReferencedProjectLoad({ - scenario: "disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", + scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", + solutionFiles: [`./own/main.ts`], + solutionOptions: { + outDir: "./target/", + baseUrl: "./indirect1/", + }, configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], - additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], - }); - }); - - describe("when solution is project that contains its own files", () => { - it("when the project found is not solution but references open file through project reference", () => { - const ownMain: File = { - path: `${tscWatch.projectRoot}/own/main.ts`, - content: fileResolvingToMainDts.content - }; - verifySolutionScenario({ - scenario: "solution with its own files and project found is not solution but references open file through project reference", - solutionFiles: [`./own/main.ts`], - solutionOptions: { - outDir: "./target/", - baseUrl: "./src/" - }, - configRefs: ["./tsconfig-src.json"], - additionalFiles: [ownMain], - }); - }); - - it("when project is indirectly referenced by solution", () => { - const ownMain: File = { - path: `${tscWatch.projectRoot}/own/main.ts`, - content: `import { bar } from 'main'; -bar;` - }; - const { tsconfigIndirect, indirect } = getIndirectProject("1"); - const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); - verifySolutionScenario({ - scenario: "solution with its own files and project is indirectly referenced by solution", - solutionFiles: [`./own/main.ts`], - solutionOptions: { - outDir: "./target/", - baseUrl: "./indirect1/" - }, - configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], - additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], - }); - }); - - it("disables looking into the child project if disableReferencedProjectLoad is set", () => { - const ownMain: File = { - path: `${tscWatch.projectRoot}/own/main.ts`, - content: fileResolvingToMainDts.content - }; - verifyDisableReferencedProjectLoad({ - scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set", - solutionFiles: [`./own/main.ts`], - solutionOptions: { - outDir: "./target/", - baseUrl: "./src/", - disableReferencedProjectLoad: true - }, - configRefs: ["./tsconfig-src.json"], - additionalFiles: [ownMain], - }); - }); - - it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => { - const ownMain: File = { - path: `${tscWatch.projectRoot}/own/main.ts`, - content: `import { bar } from 'main'; -bar;` - }; - const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); - verifyDisableReferencedProjectLoad({ - scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set in indirect project", - solutionFiles: [`./own/main.ts`], - solutionOptions: { - outDir: "./target/", - baseUrl: "./indirect1/", - }, - configRefs: ["./tsconfig-indirect1.json"], - additionalFiles: [tsconfigIndirect, indirect, ownMain], - }); - }); - - it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => { - const ownMain: File = { - path: `${tscWatch.projectRoot}/own/main.ts`, - content: `import { bar } from 'main'; -bar;` - }; - const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); - const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); - verifyDisableReferencedProjectLoad({ - scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", - solutionFiles: [`./own/main.ts`], - solutionOptions: { - outDir: "./target/", - baseUrl: "./indirect1/", - }, - configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], - additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], - }); + additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], }); }); }); + }); - describe("when new file is added to the referenced project", () => { - function setup(extendOptionsProject2?: CompilerOptions) { - const config1: File = { - path: `${tscWatch.projectRoot}/projects/project1/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true - }, - exclude: ["temp"] - }) - }; - const class1: File = { - path: `${tscWatch.projectRoot}/projects/project1/class1.ts`, - content: `class class1 {}` - }; - const class1Dts: File = { - path: `${tscWatch.projectRoot}/projects/project1/class1.d.ts`, - content: `declare class class1 {}` - }; - const config2: File = { - path: `${tscWatch.projectRoot}/projects/project2/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - module: "none", - composite: true, - ...(extendOptionsProject2 || {}) - }, - references: [ - { path: "../project1" } - ] - }) - }; - const class2: File = { - path: `${tscWatch.projectRoot}/projects/project2/class2.ts`, - content: `class class2 {}` - }; - const host = createServerHost([config1, class1, class1Dts, config2, class2, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([class2], session); - return { host, session, class1 }; - } - - it("when referenced project is not open", () => { - const { host, session } = setup(); - - // Add new class to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - host.writeFile(class3, `class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - - // Add excluded file to referenced project - host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - host.checkTimeoutQueueLengthAndRun(0); - - // Add output from new class to referenced project - const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(0); - baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is not open`, session); - }); + describe("when new file is added to the referenced project", () => { + function setup(extendOptionsProject2?: ts.CompilerOptions) { + const config1: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true + }, + exclude: ["temp"] + }) + }; + const class1: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/class1.ts`, + content: `class class1 {}` + }; + const class1Dts: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/projects/project1/class1.d.ts`, + content: `declare class class1 {}` + }; + const config2: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/projects/project2/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "none", + composite: true, + ...(extendOptionsProject2 || {}) + }, + references: [ + { path: "../project1" } + ] + }) + }; + const class2: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/projects/project2/class2.ts`, + content: `class class2 {}` + }; + const host = ts.projectSystem.createServerHost([config1, class1, class1Dts, config2, class2, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([class2], session); + return { host, session, class1 }; + } + + it("when referenced project is not open", () => { + const { host, session } = setup(); + + // Add new class to referenced project + const class3 = `${ts.tscWatch.projectRoot}/projects/project1/class3.ts`; + host.writeFile(class3, `class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + + // Add excluded file to referenced project + host.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + host.checkTimeoutQueueLengthAndRun(0); + + // Add output from new class to referenced project + const class3Dts = `${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`; + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(0); + ts.projectSystem.baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is not open`, session); + }); - it("when referenced project is open", () => { - const { host, session, class1 } = setup(); - openFilesForSession([class1], session); - - // Add new class to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - host.writeFile(class3, `class class3 {}`); - host.checkTimeoutQueueLengthAndRun(3); - // Add excluded file to referenced project - host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - host.checkTimeoutQueueLengthAndRun(0); - // Add output from new class to referenced project - const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(0); - baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is open`, session); - }); + it("when referenced project is open", () => { + const { host, session, class1 } = setup(); + ts.projectSystem.openFilesForSession([class1], session); + + // Add new class to referenced project + const class3 = `${ts.tscWatch.projectRoot}/projects/project1/class3.ts`; + host.writeFile(class3, `class class3 {}`); + host.checkTimeoutQueueLengthAndRun(3); + // Add excluded file to referenced project + host.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + host.checkTimeoutQueueLengthAndRun(0); + // Add output from new class to referenced project + const class3Dts = `${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`; + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(0); + ts.projectSystem.baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is open`, session); + }); - it("when referenced project is not open with disableSourceOfProjectReferenceRedirect", () => { - const { host, session } = setup({ disableSourceOfProjectReferenceRedirect: true }); - - // Add new class to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - host.writeFile(class3, `class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - // Add output of new class to referenced project - const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - // Add excluded file to referenced project - host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - host.checkTimeoutQueueLengthAndRun(0); - // Delete output from new class to referenced project - host.deleteFile(class3Dts); - host.checkTimeoutQueueLengthAndRun(2); - // Write back output of new class to referenced project - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is not open with disableSourceOfProjectReferenceRedirect`, session); - }); + it("when referenced project is not open with disableSourceOfProjectReferenceRedirect", () => { + const { host, session } = setup({ disableSourceOfProjectReferenceRedirect: true }); + + // Add new class to referenced project + const class3 = `${ts.tscWatch.projectRoot}/projects/project1/class3.ts`; + host.writeFile(class3, `class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + // Add output of new class to referenced project + const class3Dts = `${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`; + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + // Add excluded file to referenced project + host.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + host.checkTimeoutQueueLengthAndRun(0); + // Delete output from new class to referenced project + host.deleteFile(class3Dts); + host.checkTimeoutQueueLengthAndRun(2); + // Write back output of new class to referenced project + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + ts.projectSystem.baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is not open with disableSourceOfProjectReferenceRedirect`, session); + }); - it("when referenced project is open with disableSourceOfProjectReferenceRedirect", () => { - const { host, session, class1 } = setup({ disableSourceOfProjectReferenceRedirect: true }); - openFilesForSession([class1], session); - - // Add new class to referenced project - const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; - host.writeFile(class3, `class class3 {}`); - host.checkTimeoutQueueLengthAndRun(3); - // Add output of new class to referenced project - const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - // Add excluded file to referenced project - host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); - host.checkTimeoutQueueLengthAndRun(0); - // Delete output from new class to referenced project - host.deleteFile(class3Dts); - host.checkTimeoutQueueLengthAndRun(2); - // Write back output of new class to referenced project - host.writeFile(class3Dts, `declare class class3 {}`); - host.checkTimeoutQueueLengthAndRun(2); - baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is open with disableSourceOfProjectReferenceRedirect`, session); - }); + it("when referenced project is open with disableSourceOfProjectReferenceRedirect", () => { + const { host, session, class1 } = setup({ disableSourceOfProjectReferenceRedirect: true }); + ts.projectSystem.openFilesForSession([class1], session); + + // Add new class to referenced project + const class3 = `${ts.tscWatch.projectRoot}/projects/project1/class3.ts`; + host.writeFile(class3, `class class3 {}`); + host.checkTimeoutQueueLengthAndRun(3); + // Add output of new class to referenced project + const class3Dts = `${ts.tscWatch.projectRoot}/projects/project1/class3.d.ts`; + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + // Add excluded file to referenced project + host.ensureFileOrFolder({ path: `${ts.tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); + host.checkTimeoutQueueLengthAndRun(0); + // Delete output from new class to referenced project + host.deleteFile(class3Dts); + host.checkTimeoutQueueLengthAndRun(2); + // Write back output of new class to referenced project + host.writeFile(class3Dts, `declare class class3 {}`); + host.checkTimeoutQueueLengthAndRun(2); + ts.projectSystem.baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is open with disableSourceOfProjectReferenceRedirect`, session); }); + }); - describe("auto import with referenced project", () => { - function verifyAutoImport(built: boolean, disableSourceOfProjectReferenceRedirect?: boolean) { - const solnConfig: File = { - path: `${tscWatch.projectRoot}/tsconfig.json`, - content: JSON.stringify({ - files: [], - references: [ - { path: "shared/src/library" }, - { path: "app/src/program" } - ] - }) - }; - const sharedConfig: File = { - path: `${tscWatch.projectRoot}/shared/src/library/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "../../bld/library" - } - }) - }; - const sharedIndex: File = { - path: `${tscWatch.projectRoot}/shared/src/library/index.ts`, - content: `export function foo() {}` - }; - const sharedPackage: File = { - path: `${tscWatch.projectRoot}/shared/package.json`, - content: JSON.stringify({ - name: "shared", - version: "1.0.0", - main: "bld/library/index.js", - types: "bld/library/index.d.ts" - }) - }; - const appConfig: File = { - path: `${tscWatch.projectRoot}/app/src/program/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { - composite: true, - outDir: "../../bld/program", - disableSourceOfProjectReferenceRedirect - }, - references: [ - { path: "../../../shared/src/library" } - ] - }) - }; - const appBar: File = { - path: `${tscWatch.projectRoot}/app/src/program/bar.ts`, - content: `import {foo} from "shared";` - }; - const appIndex: File = { - path: `${tscWatch.projectRoot}/app/src/program/index.ts`, - content: `foo` - }; - const sharedSymlink: SymLink = { - path: `${tscWatch.projectRoot}/node_modules/shared`, - symLink: `${tscWatch.projectRoot}/shared` - }; - const files = [solnConfig, sharedConfig, sharedIndex, sharedPackage, appConfig, appBar, appIndex, sharedSymlink, libFile]; - const host = createServerHost(files); - if (built) { - tscWatch.solutionBuildWithBaseline(host, [solnConfig.path]); - host.clearOutput(); - } - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([appIndex], session); - session.executeCommandSeq({ - command: protocol.CommandTypes.GetCodeFixes, - arguments: { - file: appIndex.path, - startLine: 1, - startOffset: 1, - endLine: 1, - endOffset: 4, - errorCodes: [Diagnostics.Cannot_find_name_0.code], + describe("auto import with referenced project", () => { + function verifyAutoImport(built: boolean, disableSourceOfProjectReferenceRedirect?: boolean) { + const solnConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/tsconfig.json`, + content: JSON.stringify({ + files: [], + references: [ + { path: "shared/src/library" }, + { path: "app/src/program" } + ] + }) + }; + const sharedConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/shared/src/library/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "../../bld/library" } - }); - baselineTsserverLogs("projectReferences", `auto import with referenced project${built ? " when built" : ""}${disableSourceOfProjectReferenceRedirect ? " with disableSourceOfProjectReferenceRedirect": ""}`, session); + }) + }; + const sharedIndex: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/shared/src/library/index.ts`, + content: `export function foo() {}` + }; + const sharedPackage: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/shared/package.json`, + content: JSON.stringify({ + name: "shared", + version: "1.0.0", + main: "bld/library/index.js", + types: "bld/library/index.d.ts" + }) + }; + const appConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/app/src/program/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + composite: true, + outDir: "../../bld/program", + disableSourceOfProjectReferenceRedirect + }, + references: [ + { path: "../../../shared/src/library" } + ] + }) + }; + const appBar: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/app/src/program/bar.ts`, + content: `import {foo} from "shared";` + }; + const appIndex: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/app/src/program/index.ts`, + content: `foo` + }; + const sharedSymlink: ts.projectSystem.SymLink = { + path: `${ts.tscWatch.projectRoot}/node_modules/shared`, + symLink: `${ts.tscWatch.projectRoot}/shared` + }; + const files = [solnConfig, sharedConfig, sharedIndex, sharedPackage, appConfig, appBar, appIndex, sharedSymlink, ts.projectSystem.libFile]; + const host = ts.projectSystem.createServerHost(files); + if (built) { + ts.tscWatch.solutionBuildWithBaseline(host, [solnConfig.path]); + host.clearOutput(); } - - it("when project is built", () => { - verifyAutoImport(/*built*/ true); - }); - it("when project is not built", () => { - verifyAutoImport(/*built*/ false); - }); - it("when disableSourceOfProjectReferenceRedirect is true", () => { - verifyAutoImport(/*built*/ true, /*disableSourceOfProjectReferenceRedirect*/ true); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([appIndex], session); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.GetCodeFixes, + arguments: { + file: appIndex.path, + startLine: 1, + startOffset: 1, + endLine: 1, + endOffset: 4, + errorCodes: [ts.Diagnostics.Cannot_find_name_0.code], + } }); + ts.projectSystem.baselineTsserverLogs("projectReferences", `auto import with referenced project${built ? " when built" : ""}${disableSourceOfProjectReferenceRedirect ? " with disableSourceOfProjectReferenceRedirect": ""}`, session); + } + + it("when project is built", () => { + verifyAutoImport(/*built*/ true); + }); + it("when project is not built", () => { + verifyAutoImport(/*built*/ false); + }); + it("when disableSourceOfProjectReferenceRedirect is true", () => { + verifyAutoImport(/*built*/ true, /*disableSourceOfProjectReferenceRedirect*/ true); }); + }); - it("when files from two projects are open and one project references", () => { - function getPackageAndFile(packageName: string, references?: string[], optionsToExtend?: CompilerOptions): [file: File, config: File] { - const file: File = { - path: `${tscWatch.projectRoot}/${packageName}/src/file1.ts`, - content: `export const ${packageName}Const = 10;` - }; - const config: File = { - path: `${tscWatch.projectRoot}/${packageName}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, ...optionsToExtend || {} }, - references: references?.map(path => ({ path: `../${path}` })) - }) - }; - return [file, config]; - } - const [mainFile, mainConfig] = getPackageAndFile("main", ["core", "indirect", "noCoreRef1", "indirectDisabledChildLoad1", "indirectDisabledChildLoad2", "refToCoreRef3", "indirectNoCoreRef"]); - const [coreFile, coreConfig] = getPackageAndFile("core"); - const [noCoreRef1File, noCoreRef1Config] = getPackageAndFile("noCoreRef1"); - const [indirectFile, indirectConfig] = getPackageAndFile("indirect", ["coreRef1"]); - const [coreRef1File, coreRef1Config] = getPackageAndFile("coreRef1", ["core"]); - const [indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config] = getPackageAndFile("indirectDisabledChildLoad1", ["coreRef2"], { disableReferencedProjectLoad: true }); - const [coreRef2File, coreRef2Config] = getPackageAndFile("coreRef2", ["core"]); - const [indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config] = getPackageAndFile("indirectDisabledChildLoad2", ["coreRef3"], { disableReferencedProjectLoad: true }); - const [coreRef3File, coreRef3Config] = getPackageAndFile("coreRef3", ["core"]); - const [refToCoreRef3File, refToCoreRef3Config] = getPackageAndFile("refToCoreRef3", ["coreRef3"]); - const [indirectNoCoreRefFile, indirectNoCoreRefConfig] = getPackageAndFile("indirectNoCoreRef", ["noCoreRef2"]); - const [noCoreRef2File, noCoreRef2Config] = getPackageAndFile("noCoreRef2"); - - const host = createServerHost([ - libFile, mainFile, mainConfig, coreFile, coreConfig, noCoreRef1File, noCoreRef1Config, - indirectFile, indirectConfig, coreRef1File, coreRef1Config, - indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config, coreRef2File, coreRef2Config, - indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config, coreRef3File, coreRef3Config, - refToCoreRef3File, refToCoreRef3Config, - indirectNoCoreRefFile, indirectNoCoreRefConfig, noCoreRef2File, noCoreRef2Config - ], { useCaseSensitiveFileNames: true }); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([mainFile, coreFile], session); - - // Find all refs in coreFile - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(coreFile, `coreConst`) - }); - baselineTsserverLogs("projectReferences", `when files from two projects are open and one project references`, session); + it("when files from two projects are open and one project references", () => { + function getPackageAndFile(packageName: string, references?: string[], optionsToExtend?: ts.CompilerOptions): [file: ts.projectSystem.File, config: ts.projectSystem.File] { + const file: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/${packageName}/src/file1.ts`, + content: `export const ${packageName}Const = 10;` + }; + const config: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/${packageName}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, ...optionsToExtend || {} }, + references: references?.map(path => ({ path: `../${path}` })) + }) + }; + return [file, config]; + } + const [mainFile, mainConfig] = getPackageAndFile("main", ["core", "indirect", "noCoreRef1", "indirectDisabledChildLoad1", "indirectDisabledChildLoad2", "refToCoreRef3", "indirectNoCoreRef"]); + const [coreFile, coreConfig] = getPackageAndFile("core"); + const [noCoreRef1File, noCoreRef1Config] = getPackageAndFile("noCoreRef1"); + const [indirectFile, indirectConfig] = getPackageAndFile("indirect", ["coreRef1"]); + const [coreRef1File, coreRef1Config] = getPackageAndFile("coreRef1", ["core"]); + const [indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config] = getPackageAndFile("indirectDisabledChildLoad1", ["coreRef2"], { disableReferencedProjectLoad: true }); + const [coreRef2File, coreRef2Config] = getPackageAndFile("coreRef2", ["core"]); + const [indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config] = getPackageAndFile("indirectDisabledChildLoad2", ["coreRef3"], { disableReferencedProjectLoad: true }); + const [coreRef3File, coreRef3Config] = getPackageAndFile("coreRef3", ["core"]); + const [refToCoreRef3File, refToCoreRef3Config] = getPackageAndFile("refToCoreRef3", ["coreRef3"]); + const [indirectNoCoreRefFile, indirectNoCoreRefConfig] = getPackageAndFile("indirectNoCoreRef", ["noCoreRef2"]); + const [noCoreRef2File, noCoreRef2Config] = getPackageAndFile("noCoreRef2"); + + const host = ts.projectSystem.createServerHost([ + ts.projectSystem.libFile, mainFile, mainConfig, coreFile, coreConfig, noCoreRef1File, noCoreRef1Config, + indirectFile, indirectConfig, coreRef1File, coreRef1Config, + indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config, coreRef2File, coreRef2Config, + indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config, coreRef3File, coreRef3Config, + refToCoreRef3File, refToCoreRef3Config, + indirectNoCoreRefFile, indirectNoCoreRefConfig, noCoreRef2File, noCoreRef2Config + ], { useCaseSensitiveFileNames: true }); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([mainFile, coreFile], session); + + // Find all refs in coreFile + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(coreFile, `coreConst`) }); + ts.projectSystem.baselineTsserverLogs("projectReferences", `when files from two projects are open and one project references`, session); + }); - describe("find refs to decl in other proj", () => { - const indexA: File = { - path: `${tscWatch.projectRoot}/a/index.ts`, - content: `import { B } from "../b/lib"; + describe("find refs to decl in other proj", () => { + const indexA: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/a/index.ts`, + content: `import { B } from "../b/lib"; const b: B = new B();` - }; + }; - const configB: File = { - path: `${tscWatch.projectRoot}/b/tsconfig.json`, - content: `{ + const configB: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/b/tsconfig.json`, + content: `{ "compilerOptions": { "declarationMap": true, "outDir": "lib", "composite": true } }` - }; + }; - const indexB: File = { - path: `${tscWatch.projectRoot}/b/index.ts`, - content: `export class B { + const indexB: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/b/index.ts`, + content: `export class B { M() {} }` - }; + }; - const helperB: File = { - path: `${tscWatch.projectRoot}/b/helper.ts`, - content: `import { B } from "."; + const helperB: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/b/helper.ts`, + content: `import { B } from "."; const b: B = new B();` - }; + }; - const dtsB: File = { - path: `${tscWatch.projectRoot}/b/lib/index.d.ts`, - content: `export declare class B { + const dtsB: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/b/lib/index.d.ts`, + content: `export declare class B { M(): void; } //# sourceMappingURL=index.d.ts.map` + }; + + const dtsMapB: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/b/lib/index.d.ts.map`, + content: `{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;IACV,CAAC;CACJ"}` + }; + + function baselineDisableReferencedProjectLoad( + projectAlreadyLoaded: boolean, + disableReferencedProjectLoad: boolean, + disableSourceOfProjectReferenceRedirect: boolean, + dtsMapPresent: boolean) { + + // Mangled to stay under windows path length limit + const subScenario = + `when proj ${projectAlreadyLoaded ? "is" : "is not"} loaded` + + ` and refd proj loading is ${disableReferencedProjectLoad ? "disabled" : "enabled"}` + + ` and proj ref redirects are ${disableSourceOfProjectReferenceRedirect ? "disabled" : "enabled"}` + + ` and a decl map is ${dtsMapPresent ? "present" : "missing"}`; + const compilerOptions: ts.CompilerOptions = { + disableReferencedProjectLoad, + disableSourceOfProjectReferenceRedirect, + composite: true }; - const dtsMapB: File = { - path: `${tscWatch.projectRoot}/b/lib/index.d.ts.map`, - content: `{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;IACV,CAAC;CACJ"}` - }; - - function baselineDisableReferencedProjectLoad( - projectAlreadyLoaded: boolean, - disableReferencedProjectLoad: boolean, - disableSourceOfProjectReferenceRedirect: boolean, - dtsMapPresent: boolean) { - - // Mangled to stay under windows path length limit - const subScenario = - `when proj ${projectAlreadyLoaded ? "is" : "is not"} loaded` + - ` and refd proj loading is ${disableReferencedProjectLoad ? "disabled" : "enabled"}` + - ` and proj ref redirects are ${disableSourceOfProjectReferenceRedirect ? "disabled" : "enabled"}` + - ` and a decl map is ${dtsMapPresent ? "present" : "missing"}`; - const compilerOptions: CompilerOptions = { - disableReferencedProjectLoad, - disableSourceOfProjectReferenceRedirect, - composite: true - }; - - it(subScenario, () => { - const configA: File = { - path: `${tscWatch.projectRoot}/a/tsconfig.json`, - content: `{ + it(subScenario, () => { + const configA: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/a/tsconfig.json`, + content: `{ "compilerOptions": ${JSON.stringify(compilerOptions)}, "references": [{ "path": "../b" }] }` - }; + }; - const host = createServerHost([configA, indexA, configB, indexB, helperB, dtsB, ...(dtsMapPresent ? [dtsMapB] : [])]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([indexA, ...(projectAlreadyLoaded ? [helperB] : [])], session); + const host = ts.projectSystem.createServerHost([configA, indexA, configB, indexB, helperB, dtsB, ...(dtsMapPresent ? [dtsMapB] : [])]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([indexA, ...(projectAlreadyLoaded ? [helperB] : [])], session); - session.executeCommandSeq({ - command: protocol.CommandTypes.References, - arguments: protocolFileLocationFromSubstring(indexA, `B`, { index: 1 }) - }); - baselineTsserverLogs("projectReferences", `find refs to decl in other proj ${subScenario}`, session); + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.References, + arguments: ts.projectSystem.protocolFileLocationFromSubstring(indexA, `B`, { index: 1 }) }); - } - - /* eslint-disable local/boolean-trivia */ - - // Pre-loaded = A file from project B is already open when FAR is invoked - // dRPL = Project A has disableReferencedProjectLoad - // dSOPRR = Project A has disableSourceOfProjectReferenceRedirect - // Map = The declaration map file b/lib/index.d.ts.map exists - // B refs = files under directory b in which references are found (all scenarios find all references in a/index.ts) - - // Pre-loaded | dRPL | dSOPRR | Map | B state | Notes | B refs | Notes - // -----------+--------+--------+----------+------------+--------------+---------------------+--------------------------------------------------- - baselineDisableReferencedProjectLoad(true, true, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project - baselineDisableReferencedProjectLoad(true, true, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded - baselineDisableReferencedProjectLoad(true, true, false, true); // Pre-loaded | | index.ts, helper.ts | - baselineDisableReferencedProjectLoad(true, true, false, false); // Pre-loaded | | index.ts, helper.ts | - baselineDisableReferencedProjectLoad(true, false, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project - baselineDisableReferencedProjectLoad(true, false, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded - baselineDisableReferencedProjectLoad(true, false, false, true); // Pre-loaded | | index.ts, helper.ts | - baselineDisableReferencedProjectLoad(true, false, false, false); // Pre-loaded | | index.ts, helper.ts | - baselineDisableReferencedProjectLoad(false, true, true, true); // Not loaded | | lib/index.d.ts | Even though map is present - baselineDisableReferencedProjectLoad(false, true, true, false); // Not loaded | | lib/index.d.ts | - baselineDisableReferencedProjectLoad(false, true, false, true); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a - baselineDisableReferencedProjectLoad(false, true, false, false); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a - baselineDisableReferencedProjectLoad(false, false, true, true); // Loaded | Via map | index.ts, helper.ts | Via map and newly loaded project - baselineDisableReferencedProjectLoad(false, false, true, false); // Not loaded | | lib/index.d.ts | - baselineDisableReferencedProjectLoad(false, false, false, true); // Loaded | Via redirect | index.ts, helper.ts | - baselineDisableReferencedProjectLoad(false, false, false, false); // Loaded | Via redirect | index.ts, helper.ts | - - /* eslint-enable local/boolean-trivia */ - }); + ts.projectSystem.baselineTsserverLogs("projectReferences", `find refs to decl in other proj ${subScenario}`, session); + }); + } + + /* eslint-disable local/boolean-trivia */ + + // Pre-loaded = A file from project B is already open when FAR is invoked + // dRPL = Project A has disableReferencedProjectLoad + // dSOPRR = Project A has disableSourceOfProjectReferenceRedirect + // Map = The declaration map file b/lib/index.d.ts.map exists + // B refs = files under directory b in which references are found (all scenarios find all references in a/index.ts) + + // Pre-loaded | dRPL | dSOPRR | Map | B state | Notes | B refs | Notes + // -----------+--------+--------+----------+------------+--------------+---------------------+--------------------------------------------------- + baselineDisableReferencedProjectLoad(true, true, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project + baselineDisableReferencedProjectLoad(true, true, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded + baselineDisableReferencedProjectLoad(true, true, false, true); // Pre-loaded | | index.ts, helper.ts | + baselineDisableReferencedProjectLoad(true, true, false, false); // Pre-loaded | | index.ts, helper.ts | + baselineDisableReferencedProjectLoad(true, false, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project + baselineDisableReferencedProjectLoad(true, false, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded + baselineDisableReferencedProjectLoad(true, false, false, true); // Pre-loaded | | index.ts, helper.ts | + baselineDisableReferencedProjectLoad(true, false, false, false); // Pre-loaded | | index.ts, helper.ts | + baselineDisableReferencedProjectLoad(false, true, true, true); // Not loaded | | lib/index.d.ts | Even though map is present + baselineDisableReferencedProjectLoad(false, true, true, false); // Not loaded | | lib/index.d.ts | + baselineDisableReferencedProjectLoad(false, true, false, true); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a + baselineDisableReferencedProjectLoad(false, true, false, false); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a + baselineDisableReferencedProjectLoad(false, false, true, true); // Loaded | Via map | index.ts, helper.ts | Via map and newly loaded project + baselineDisableReferencedProjectLoad(false, false, true, false); // Not loaded | | lib/index.d.ts | + baselineDisableReferencedProjectLoad(false, false, false, true); // Loaded | Via redirect | index.ts, helper.ts | + baselineDisableReferencedProjectLoad(false, false, false, false); // Loaded | Via redirect | index.ts, helper.ts | + + /* eslint-enable local/boolean-trivia */ }); -} +}); diff --git a/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts b/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts index d951b02058c2c..fd644f629165c 100644 --- a/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts +++ b/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts @@ -1,25 +1,26 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: with project references and tsbuild source map", () => { - const dependecyLocation = `${tscWatch.projectRoot}/dependency`; - const dependecyDeclsLocation = `${tscWatch.projectRoot}/decls`; - const mainLocation = `${tscWatch.projectRoot}/main`; - const dependencyTs: File = { - path: `${dependecyLocation}/FnS.ts`, - content: `export function fn1() { } +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: with project references and tsbuild source map", () => { + const dependecyLocation = `${ts.tscWatch.projectRoot}/dependency`; + const dependecyDeclsLocation = `${ts.tscWatch.projectRoot}/decls`; + const mainLocation = `${ts.tscWatch.projectRoot}/main`; + const dependencyTs: ts.projectSystem.File = { + path: `${dependecyLocation}/FnS.ts`, + content: `export function fn1() { } export function fn2() { } export function fn3() { } export function fn4() { } export function fn5() { } ` - }; - const dependencyConfig: File = { - path: `${dependecyLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } }) - }; - - const mainTs: File = { - path: `${mainLocation}/main.ts`, - content: `import { + }; + const dependencyConfig: ts.projectSystem.File = { + path: `${dependecyLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } }) + }; + + const mainTs: ts.projectSystem.File = { + path: `${mainLocation}/main.ts`, + content: `import { fn1, fn2, fn3, @@ -33,3009 +34,3008 @@ fn3(); fn4(); fn5(); ` + }; + const mainConfig: ts.projectSystem.File = { + path: `${mainLocation}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, declarationMap: true }, + references: [{ path: "../dependency" }] + }) + }; + + const randomFile: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/random/random.ts`, + content: "let a = 10;" + }; + const randomConfig: ts.projectSystem.File = { + path: `${ts.tscWatch.projectRoot}/random/tsconfig.json`, + content: "{}" + }; + const dtsLocation = `${dependecyDeclsLocation}/FnS.d.ts`; + const dtsPath = dtsLocation.toLowerCase() as ts.Path; + const dtsMapLocation = `${dependecyDeclsLocation}/FnS.d.ts.map`; + const dtsMapPath = dtsMapLocation.toLowerCase() as ts.Path; + + const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, ts.projectSystem.libFile, randomFile, randomConfig]; + + function changeDtsFile(host: ts.projectSystem.TestServerHost) { + host.writeFile( + dtsLocation, + host.readFile(dtsLocation)!.replace( + "//# sourceMappingURL=FnS.d.ts.map", + `export declare function fn6(): void; +//# sourceMappingURL=FnS.d.ts.map` + ) + ); + } + + function changeDtsMapFile(host: ts.projectSystem.TestServerHost) { + host.writeFile( + dtsMapLocation, + `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` + ); + } + + function goToDefFromMainTs(fn: number): Partial { + return { + command: ts.projectSystem.protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, line: fn + 8, offset: 1 } }; - const mainConfig: File = { - path: `${mainLocation}/tsconfig.json`, - content: JSON.stringify({ - compilerOptions: { composite: true, declarationMap: true }, - references: [{ path: "../dependency" }] - }) - }; + } - const randomFile: File = { - path: `${tscWatch.projectRoot}/random/random.ts`, - content: "let a = 10;" - }; - const randomConfig: File = { - path: `${tscWatch.projectRoot}/random/tsconfig.json`, - content: "{}" + function renameFromDependencyTs(fn: number): Partial { + return { + command: ts.projectSystem.protocol.CommandTypes.Rename, + arguments: { file: dependencyTs.path, line: fn, offset: 17 } }; - const dtsLocation = `${dependecyDeclsLocation}/FnS.d.ts`; - const dtsPath = dtsLocation.toLowerCase() as Path; - const dtsMapLocation = `${dependecyDeclsLocation}/FnS.d.ts.map`; - const dtsMapPath = dtsMapLocation.toLowerCase() as Path; - - const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; - - function changeDtsFile(host: TestServerHost) { - host.writeFile( - dtsLocation, - host.readFile(dtsLocation)!.replace( - "//# sourceMappingURL=FnS.d.ts.map", - `export declare function fn6(): void; -//# sourceMappingURL=FnS.d.ts.map` - ) - ); + } + + function renameFromDependencyTsWithDependencyChange(fn: number) { + return renameFromDependencyTs(fn + 1); + } + + function verifyDocumentPositionMapper( + session: ts.projectSystem.TestSession, + dependencyMap: ts.server.ScriptInfo | undefined, + documentPositionMapper: ts.server.ScriptInfo["documentPositionMapper"], + equal: boolean, + debugInfo?: string, + ) { + assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap, `${debugInfo} dependencyMap`); + if (dependencyMap) { + verifyEquality(dependencyMap.documentPositionMapper, documentPositionMapper, equal, `${debugInfo} DocumentPositionMapper`); } - - function changeDtsMapFile(host: TestServerHost) { - host.writeFile( - dtsMapLocation, - `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` - ); - } - - function goToDefFromMainTs(fn: number): Partial { - return { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, line: fn + 8, offset: 1 } - }; + } + + function verifyDocumentPositionMapperEqual( + session: ts.projectSystem.TestSession, + dependencyMap: ts.server.ScriptInfo | undefined, + documentPositionMapper: ts.server.ScriptInfo["documentPositionMapper"], + debugInfo?: string, + ) { + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, /*equal*/ true, debugInfo); + } + + function verifyEquality(actual: T, expected: T, equal: boolean, debugInfo?: string) { + if (equal) { + assert.strictEqual(actual, expected, debugInfo); } - - function renameFromDependencyTs(fn: number): Partial { - return { - command: protocol.CommandTypes.Rename, - arguments: { file: dependencyTs.path, line: fn, offset: 17 } - }; - } - - function renameFromDependencyTsWithDependencyChange(fn: number) { - return renameFromDependencyTs(fn + 1); + else { + assert.notStrictEqual(actual, expected, debugInfo); } - - function verifyDocumentPositionMapper( - session: TestSession, - dependencyMap: server.ScriptInfo | undefined, - documentPositionMapper: server.ScriptInfo["documentPositionMapper"], - equal: boolean, - debugInfo?: string, - ) { - assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap, `${debugInfo} dependencyMap`); - if (dependencyMap) { - verifyEquality(dependencyMap.documentPositionMapper, documentPositionMapper, equal, `${debugInfo} DocumentPositionMapper`); - } - } - - function verifyDocumentPositionMapperEqual( - session: TestSession, - dependencyMap: server.ScriptInfo | undefined, - documentPositionMapper: server.ScriptInfo["documentPositionMapper"], - debugInfo?: string, - ) { - verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, /*equal*/ true, debugInfo); - } - - function verifyEquality(actual: T, expected: T, equal: boolean, debugInfo?: string) { - if (equal) { - assert.strictEqual(actual, expected, debugInfo); - } - else { - assert.notStrictEqual(actual, expected, debugInfo); - } - } - - function verifyAllFnAction( - session: TestSession, - action: (fn: number) => Partial, - existingDependencyMap: server.ScriptInfo | undefined, - existingDocumentPositionMapper: server.ScriptInfo["documentPositionMapper"], - existingMapEqual: boolean, - existingDocumentPositionMapperEqual: boolean, - skipMapPathInDtsInfo?: boolean - ) { - let sourceMapPath: server.ScriptInfo["sourceMapFilePath"] | undefined; - let dependencyMap: server.ScriptInfo | undefined; - let documentPositionMapper: server.ScriptInfo["documentPositionMapper"]; - for (let fn = 1; fn <= 5; fn++) { - const fnAction = action(fn); - session.executeCommandSeq(fnAction); - const debugInfo = `${JSON.stringify(fnAction)}:: ${fn}`; - const dtsInfo = session.getProjectService().getScriptInfoForPath(dtsPath); - const dtsMapInfo = session.getProjectService().getScriptInfoForPath(dtsMapPath); - - if (fn === 1) { - if (dtsInfo) { - if (!skipMapPathInDtsInfo) { - if (dtsMapInfo) { - assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, `${debugInfo} sourceMapFilePath`); - } - else { - assert.isNotString(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); - assert.isNotFalse(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); - assert.isDefined(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); - } + } + + function verifyAllFnAction( + session: ts.projectSystem.TestSession, + action: (fn: number) => Partial, + existingDependencyMap: ts.server.ScriptInfo | undefined, + existingDocumentPositionMapper: ts.server.ScriptInfo["documentPositionMapper"], + existingMapEqual: boolean, + existingDocumentPositionMapperEqual: boolean, + skipMapPathInDtsInfo?: boolean + ) { + let sourceMapPath: ts.server.ScriptInfo["sourceMapFilePath"] | undefined; + let dependencyMap: ts.server.ScriptInfo | undefined; + let documentPositionMapper: ts.server.ScriptInfo["documentPositionMapper"]; + for (let fn = 1; fn <= 5; fn++) { + const fnAction = action(fn); + session.executeCommandSeq(fnAction); + const debugInfo = `${JSON.stringify(fnAction)}:: ${fn}`; + const dtsInfo = session.getProjectService().getScriptInfoForPath(dtsPath); + const dtsMapInfo = session.getProjectService().getScriptInfoForPath(dtsMapPath); + + if (fn === 1) { + if (dtsInfo) { + if (!skipMapPathInDtsInfo) { + if (dtsMapInfo) { + assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, `${debugInfo} sourceMapFilePath`); + } + else { + assert.isNotString(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); + assert.isNotFalse(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); + assert.isDefined(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); } } - verifyEquality(dtsMapInfo, existingDependencyMap, existingMapEqual, `${debugInfo} dependencyMap`); - verifyEquality(existingDocumentPositionMapper, dtsMapInfo?.documentPositionMapper, existingDocumentPositionMapperEqual, `${debugInfo} DocumentPositionMapper`); - sourceMapPath = dtsInfo?.sourceMapFilePath; - dependencyMap = dtsMapInfo; - documentPositionMapper = dependencyMap?.documentPositionMapper; - } - else { - assert.equal(dtsInfo?.sourceMapFilePath, sourceMapPath, `${debugInfo} sourceMapFilePath`); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper, debugInfo); } + verifyEquality(dtsMapInfo, existingDependencyMap, existingMapEqual, `${debugInfo} dependencyMap`); + verifyEquality(existingDocumentPositionMapper, dtsMapInfo?.documentPositionMapper, existingDocumentPositionMapperEqual, `${debugInfo} DocumentPositionMapper`); + sourceMapPath = dtsInfo?.sourceMapFilePath; + dependencyMap = dtsMapInfo; + documentPositionMapper = dependencyMap?.documentPositionMapper; + } + else { + assert.equal(dtsInfo?.sourceMapFilePath, sourceMapPath, `${debugInfo} sourceMapFilePath`); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper, debugInfo); } } + } - function verifyScriptInfoCollectionWith( - session: TestSession, - openFiles: readonly File[], - ) { - const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); + function verifyScriptInfoCollectionWith( + session: ts.projectSystem.TestSession, + openFiles: readonly ts.projectSystem.File[], + ) { + const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); - // Collecting at this point retains dependency.d.ts and map - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); + // Collecting at this point retains dependency.d.ts and map + ts.projectSystem.closeFilesForSession([randomFile], session); + ts.projectSystem.openFilesForSession([randomFile], session); - // If map is not collected, document position mapper shouldnt change - if (session.getProjectService().filenameToScriptInfo.has(dtsMapPath)) { - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - } - - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles, randomFile], session); - openFilesForSession([randomFile], session); + // If map is not collected, document position mapper shouldnt change + if (session.getProjectService().filenameToScriptInfo.has(dtsMapPath)) { + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); } - type OnHostCreate = (host: TestServerHost) => void; - type CreateSessionFn = (onHostCreate?: OnHostCreate) => { host: TestServerHost; session: TestSession; }; - function setupWith(createSession: CreateSessionFn, openFiles: readonly File[], onHostCreate: OnHostCreate | undefined) { - const result = createSession(onHostCreate); - openFilesForSession(openFiles, result.session); - return result; - } + // Closing open file, removes dependencies too + ts.projectSystem.closeFilesForSession([...openFiles, randomFile], session); + ts.projectSystem.openFilesForSession([randomFile], session); + } + + type OnHostCreate = (host: ts.projectSystem.TestServerHost) => void; + type CreateSessionFn = (onHostCreate?: OnHostCreate) => { host: ts.projectSystem.TestServerHost; session: ts.projectSystem.TestSession; }; + function setupWith(createSession: CreateSessionFn, openFiles: readonly ts.projectSystem.File[], onHostCreate: OnHostCreate | undefined) { + const result = createSession(onHostCreate); + ts.projectSystem.openFilesForSession(openFiles, result.session); + return result; + } + + function setupWithMainTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { + return setupWith(createSession, [mainTs, randomFile], onHostCreate); + } + + function setupWithDependencyTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { + return setupWith(createSession, [dependencyTs, randomFile], onHostCreate); + } + + function setupWithMainTsAndDependencyTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { + return setupWith(createSession, [mainTs, dependencyTs, randomFile], onHostCreate); + } + + function createSessionWithoutProjectReferences(onHostCreate?: OnHostCreate) { + const host = ts.projectSystem.createHostWithSolutionBuild(files, [mainConfig.path]); + // Erase project reference + host.writeFile(mainConfig.path, JSON.stringify({ + compilerOptions: { composite: true, declarationMap: true } + })); + onHostCreate?.(host); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + return { host, session }; + } + + function createSessionWithProjectReferences(onHostCreate?: OnHostCreate) { + const host = ts.projectSystem.createHostWithSolutionBuild(files, [mainConfig.path]); + onHostCreate?.(host); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + return { host, session }; + } + + function createSessionWithDisabledProjectReferences(onHostCreate?: OnHostCreate) { + const host = ts.projectSystem.createHostWithSolutionBuild(files, [mainConfig.path]); + // Erase project reference + host.writeFile(mainConfig.path, JSON.stringify({ + compilerOptions: { + composite: true, + declarationMap: true, + disableSourceOfProjectReferenceRedirect: true + }, + references: [{ path: "../dependency" }] + })); + onHostCreate?.(host); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + return { host, session }; + } + + function getDocumentPositionMapper(session: ts.projectSystem.TestSession) { + const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); + const documentPositionMapper = dependencyMap?.documentPositionMapper; + return { dependencyMap, documentPositionMapper }; + } + + function makeChangeToMainTs(session: ts.projectSystem.TestSession) { + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: mainTs.path, + line: 14, + offset: 1, + endLine: 14, + endOffset: 1, + insertString: "const x = 10;" + } + }); + } + + function makeChangeToDependencyTs(session: ts.projectSystem.TestSession) { + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + line: 6, + offset: 1, + endLine: 6, + endOffset: 1, + insertString: "const x = 10;" + } + }); + } - function setupWithMainTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { - return setupWith(createSession, [mainTs, randomFile], onHostCreate); - } - function setupWithDependencyTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { - return setupWith(createSession, [dependencyTs, randomFile], onHostCreate); + describe("from project that uses dependency: goToDef", () => { + function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { + const result = setup(onHostCreate); + result.session.executeCommandSeq(goToDefFromMainTs(1)); + return { ...result, ...getDocumentPositionMapper(result.session) }; } - function setupWithMainTsAndDependencyTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { - return setupWith(createSession, [mainTs, dependencyTs, randomFile], onHostCreate); - } + describe("when main tsconfig doesnt have project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTs(createSessionWithoutProjectReferences, onHostCreate); + } - function createSessionWithoutProjectReferences(onHostCreate?: OnHostCreate) { - const host = createHostWithSolutionBuild(files, [mainConfig.path]); - // Erase project reference - host.writeFile(mainConfig.path, JSON.stringify({ - compilerOptions: { composite: true, declarationMap: true } - })); - onHostCreate?.(host); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - return { host, session }; - } + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - function createSessionWithProjectReferences(onHostCreate?: OnHostCreate) { - const host = createHostWithSolutionBuild(files, [mainConfig.path]); - onHostCreate?.(host); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - return { host, session }; - } + it("can go to definition correctly", () => { + const { session } = setup(); + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/can go to definition correctly", session); + }); - function createSessionWithDisabledProjectReferences(onHostCreate?: OnHostCreate) { - const host = createHostWithSolutionBuild(files, [mainConfig.path]); - // Erase project reference - host.writeFile(mainConfig.path, JSON.stringify({ - compilerOptions: { - composite: true, - declarationMap: true, - disableSourceOfProjectReferenceRedirect: true - }, - references: [{ path: "../dependency" }] - })); - onHostCreate?.(host); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - return { host, session }; - } + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - function getDocumentPositionMapper(session: TestSession) { - const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); - const documentPositionMapper = dependencyMap?.documentPositionMapper; - return { dependencyMap, documentPositionMapper }; - } + // change + makeChangeToMainTs(session); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - function makeChangeToMainTs(session: TestSession) { - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: mainTs.path, - line: 14, - offset: 1, - endLine: 14, - endOffset: 1, - insertString: "const x = 10;" - } + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/usage file changes with timeout before request", session); }); - } - - function makeChangeToDependencyTs(session: TestSession) { - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, - line: 6, - offset: 1, - endLine: 6, - endOffset: 1, - insertString: "const x = 10;" - } + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/usage file changes", session); }); - } + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - describe("from project that uses dependency: goToDef", () => { - function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { - const result = setup(onHostCreate); - result.session.executeCommandSeq(goToDefFromMainTs(1)); - return { ...result, ...getDocumentPositionMapper(result.session) }; - } + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - describe("when main tsconfig doesnt have project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTs(createSessionWithoutProjectReferences, onHostCreate); - } + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts changes with timeout before request", session); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts changes", session); + }); - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - it("can go to definition correctly", () => { - const { session } = setup(); - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/can go to definition correctly", session); - }); + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/usage file changes with timeout before request", session); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/usage file changes", session); - }); + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap changes with timeout before request", session); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap changes", session); + }); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts changes with timeout before request", session); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts changes", session); + it(`with depedency files map file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsMapLocation)); + + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap not present", session); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); }); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap changes with timeout before request", session); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap changes", session); - }); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap created", session); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap deleted", session); + }); - it(`with depedency files map file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsMapLocation)); - - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap not present", session); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap created", session); + it(`with depedency .d.ts file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsLocation)); + + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts not present", session); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // The dependency file is deleted when orphan files are collected - host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dtsMap deleted", session); - }); + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts created", session); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts deleted", session); + }); + }); + describe("when main tsconfig has project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTs(createSessionWithProjectReferences, onHostCreate); + } - it(`with depedency .d.ts file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsLocation)); - - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts not present", session); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts created", session); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - // The dependency file is deleted when orphan files are collected - host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configHasNoReference/dependency dts deleted", session); - }); + it("can go to definition correctly", () => { + const { session } = setup(); + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/can go to definition correctly", session); }); - describe("when main tsconfig has project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTs(createSessionWithProjectReferences, onHostCreate); - } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - it("can go to definition correctly", () => { - const { session } = setup(); - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/can go to definition correctly", session); - }); + // change + makeChangeToMainTs(session); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/usage file changes with timeout before request", session); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/usage file changes", session); - }); + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/usage file changes with timeout before request", session); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/usage file changes", session); + }); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts changes with timeout before request", session); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts changes", session); - }); + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap changes with timeout before request", session); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap changes", session); - }); + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - it(`with depedency files map file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsMapLocation)); - - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap not present", session); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap created", session); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts changes with timeout before request", session); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts changes", session); + }); - // The dependency file is deleted when orphan files are collected + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap changes with timeout before request", session); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap changes", session); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsMapLocation)); + + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap not present", session); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap deleted", session); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsLocation)); - - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts not present", session); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts created", session); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap created", session); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dtsMap deleted", session); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsLocation)); + + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts not present", session); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts deleted", session); }); - it(`when defining project source changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // change - // Make change, without rebuild of solution - host.writeFile(dependencyTs.path, `function fooBar() { } + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts created", session); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency dts deleted", session); + }); + it(`when defining project source changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + // Make change, without rebuild of solution + host.writeFile(dependencyTs.path, `function fooBar() { } ${dependencyTs.content}`); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency source changes with timeout before request", session); - }); - it(`when defining project source changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session } = setupWithAction(); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // change - // Make change, without rebuild of solution - host.writeFile(dependencyTs.path, `function fooBar() { } + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency source changes with timeout before request", session); + }); + it(`when defining project source changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session } = setupWithAction(); + + // change + // Make change, without rebuild of solution + host.writeFile(dependencyTs.path, `function fooBar() { } ${dependencyTs.content}`); - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency source changes", session); - }); + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/dependency source changes", session); + }); - it("when projects are not built", () => { - const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([mainTs, randomFile], session); - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/when projects are not built", session); - }); + it("when projects are not built", () => { + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([mainTs, randomFile], session); + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/configWithReference/when projects are not built", session); }); - describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTs(createSessionWithDisabledProjectReferences, onHostCreate); - } + }); + describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTs(createSessionWithDisabledProjectReferences, onHostCreate); + } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - it("can go to definition correctly", () => { - const { session } = setup(); - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/can go to definition correctly", session); - }); + it("can go to definition correctly", () => { + const { session } = setup(); + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/can go to definition correctly", session); + }); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/usage file changes with timeout before request", session); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/usage file changes", session); - }); + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts changes with timeout before request", session); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts changes", session); - }); + // change + makeChangeToMainTs(session); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap changes with timeout before request", session); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap changes", session); - }); + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/usage file changes with timeout before request", session); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/usage file changes", session); + }); - it(`with depedency files map file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsMapLocation)); - - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap not present", session); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap created", session); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts changes with timeout before request", session); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts changes", session); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // The dependency file is deleted when orphan files are collected + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap changes with timeout before request", session); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap changes", session); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsMapLocation)); + + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap not present", session); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap deleted", session); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsLocation)); - - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts not present", session); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts created", session); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap created", session); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dtsMap deleted", session); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsLocation)); + + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts not present", session); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs]); - baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts deleted", session); }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts created", session); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "usageProject/disabledSourceRef/dependency dts deleted", session); }); }); + }); - describe("from defining project: rename", () => { - function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { - const result = setup(onHostCreate); - result.session.executeCommandSeq(renameFromDependencyTs(1)); - return { ...result, ...getDocumentPositionMapper(result.session) }; + describe("from defining project: rename", () => { + function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { + const result = setup(onHostCreate); + result.session.executeCommandSeq(renameFromDependencyTs(1)); + return { ...result, ...getDocumentPositionMapper(result.session) }; + } + + describe("when main tsconfig doesnt have project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithDependencyTs(createSessionWithoutProjectReferences, onHostCreate); } - describe("when main tsconfig doesnt have project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithDependencyTs(createSessionWithoutProjectReferences, onHostCreate); - } + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + it("rename locations from dependency", () => { + const { session } = setup(); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/rename locations", session); + }); - it("rename locations from dependency", () => { - const { session } = setup(); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/rename locations", session); - }); + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/usage file changes with timeout before request", session); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/usage file changes", session); - }); + // change + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts changes with timeout before request", session); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts changes", session); - }); + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/usage file changes with timeout before request", session); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/usage file changes", session); + }); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap changes with timeout before request", session); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap changes", session); - }); + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - it(`with depedency files map file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsMapLocation)); - - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap not present", session); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap created", session); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts changes with timeout before request", session); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts changes", session); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // The dependency file is deleted when orphan files are collected + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap changes with timeout before request", session); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap changes", session); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsMapLocation)); + + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap not present", session); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap deleted", session); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsLocation)); - - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts not present", session); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts created", session); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap created", session); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dtsMap deleted", session); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsLocation)); + + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts not present", session); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts deleted", session); }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts created", session); }); - describe("when main tsconfig has project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithDependencyTs(createSessionWithProjectReferences, onHostCreate); - } + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configHasNoReference/dependency dts deleted", session); + }); + }); + describe("when main tsconfig has project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithDependencyTs(createSessionWithProjectReferences, onHostCreate); + } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - it("rename locations from dependency", () => { - const { session } = setup(); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/rename locations", session); - }); + it("rename locations from dependency", () => { + const { session } = setup(); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/rename locations", session); + }); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/usage file changes with timeout before request", session); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/usage file changes", session); - }); + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts changes with timeout before request", session); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts changes", session); - }); + // change + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap changes with timeout before request", session); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap changes", session); - }); + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/usage file changes with timeout before request", session); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/usage file changes", session); + }); - it(`with depedency files map file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsMapLocation)); - - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap not present", session); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap created", session); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts changes with timeout before request", session); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts changes", session); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // The dependency file is deleted when orphan files are collected + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap changes with timeout before request", session); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap changes", session); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsMapLocation)); + + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap not present", session); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap deleted", session); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsLocation)); - - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts not present", session); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts created", session); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap created", session); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dtsMap deleted", session); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsLocation)); + + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts not present", session); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts deleted", session); }); - it(`when defining project source changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - // Make change, without rebuild of solution - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts created", session); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency dts deleted", session); + }); + it(`when defining project source changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } `} - }); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - renameFromDependencyTsWithDependencyChange, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency source changes with timeout before request", session); }); - it(`when defining project source changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { session } = setupWithAction(); - - // change - // Make change, without rebuild of solution - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + renameFromDependencyTsWithDependencyChange, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency source changes with timeout before request", session); + }); + it(`when defining project source changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { session } = setupWithAction(); + + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } `} - }); - - // action - verifyAllFnAction( - session, - renameFromDependencyTsWithDependencyChange, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency source changes", session); }); - it("when projects are not built", () => { - const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([dependencyTs, randomFile], session); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/when projects are not built", session); - }); + // action + verifyAllFnAction( + session, + renameFromDependencyTsWithDependencyChange, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/dependency source changes", session); }); - describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithDependencyTs(createSessionWithDisabledProjectReferences, onHostCreate); - } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + it("when projects are not built", () => { + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([dependencyTs, randomFile], session); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/configWithReference/when projects are not built", session); + }); + }); + describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithDependencyTs(createSessionWithDisabledProjectReferences, onHostCreate); + } - it("rename locations from dependency", () => { - const { session } = setup(); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/rename locations", session); - }); + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/usage file changes with timeout before request", session); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/usage file changes", session); - }); + it("rename locations from dependency", () => { + const { session } = setup(); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/rename locations", session); + }); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts changes with timeout before request", session); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts changes", session); - }); + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap changes with timeout before request", session); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap changes", session); - }); + // change + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - it(`with depedency files map file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsMapLocation)); - - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap not present", session); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap created", session); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/usage file changes with timeout before request", session); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/usage file changes", session); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // The dependency file is deleted when orphan files are collected + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts changes with timeout before request", session); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts changes", session); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap changes with timeout before request", session); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap changes", session); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsMapLocation)); + + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap not present", session); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap deleted", session); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsLocation)); - - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts not present", session); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts created", session); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap created", session); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dtsMap deleted", session); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsLocation)); + + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts not present", session); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts deleted", session); }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts created", session); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependency/disabledSourceRef/dependency dts deleted", session); }); }); + }); - describe("when opening depedency and usage project: goToDef and rename", () => { - function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { - const result = setup(onHostCreate); - result.session.executeCommandSeq(goToDefFromMainTs(1)); - result.session.executeCommandSeq(renameFromDependencyTs(1)); - return { ...result, ...getDocumentPositionMapper(result.session) }; + describe("when opening depedency and usage project: goToDef and rename", () => { + function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { + const result = setup(onHostCreate); + result.session.executeCommandSeq(goToDefFromMainTs(1)); + result.session.executeCommandSeq(renameFromDependencyTs(1)); + return { ...result, ...getDocumentPositionMapper(result.session) }; + } + + describe("when main tsconfig doesnt have project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTsAndDependencyTs(createSessionWithoutProjectReferences, onHostCreate); } - describe("when main tsconfig doesnt have project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTsAndDependencyTs(createSessionWithoutProjectReferences, onHostCreate); - } + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + it("goto Definition in usage and rename locations from defining project", () => { + const { session } = setup(); + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/goToDef and rename locations", session); + }); - it("goto Definition in usage and rename locations from defining project", () => { - const { session } = setup(); - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/goToDef and rename locations", session); - }); + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/usage file changes with timeout before request", session); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/usage file changes", session); - }); + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts changes with timeout before request", session); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts changes", session); - }); + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/usage file changes with timeout before request", session); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/usage file changes", session); + }); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap changes with timeout before request", session); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap changes", session); - }); + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - it(`with depedency files map file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsMapLocation)); - - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap not present", session); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap created", session); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts changes with timeout before request", session); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts changes", session); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // The dependency file is deleted when orphan files are collected + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap changes with timeout before request", session); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap changes", session); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsMapLocation)); + + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap not present", session); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap deleted", session); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsLocation)); - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts not present", session); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts created", session); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap created", session); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dtsMap deleted", session); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsLocation)); + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts not present", session); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts deleted", session); }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts created", session); }); - describe("when main tsconfig has project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTsAndDependencyTs(createSessionWithProjectReferences, onHostCreate); - } + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configHasNoReference/dependency dts deleted", session); + }); + }); + describe("when main tsconfig has project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTsAndDependencyTs(createSessionWithProjectReferences, onHostCreate); + } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - it("goto Definition in usage and rename locations from defining project", () => { - const { session } = setup(); - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/gotoDef and rename locations", session); - }); + it("goto Definition in usage and rename locations from defining project", () => { + const { session } = setup(); + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/gotoDef and rename locations", session); + }); - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/usage file changes with timeout before request", session); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/usage file changes", session); - }); + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts changes with timeout before request", session); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts changes", session); - }); + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap changes with timeout before request", session); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap changes", session); - }); + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/usage file changes with timeout before request", session); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/usage file changes", session); + }); - it(`with depedency files map file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsMapLocation)); - - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap not present", session); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true, - /*skipMapPathInDtsInfo*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap created", session); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - // The dependency file is deleted when orphan files are collected + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts changes with timeout before request", session); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts changes", session); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap changes with timeout before request", session); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap changes", session); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsMapLocation)); + + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap not present", session); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false, - /*skipMapPathInDtsInfo*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap deleted", session); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsLocation)); - - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts not present", session); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts created", session); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true, + /*skipMapPathInDtsInfo*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap created", session); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false, + /*skipMapPathInDtsInfo*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dtsMap deleted", session); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsLocation)); + + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts not present", session); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts deleted", session); }); - it(`when defining project source changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - // Make change, without rebuild of solution - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts created", session); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency dts deleted", session); + }); + it(`when defining project source changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } `} - }); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTsWithDependencyChange, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency source changes with timeout before request", session); }); - it(`when defining project source changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - // Make change, without rebuild of solution - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTsWithDependencyChange, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency source changes with timeout before request", session); + }); + it(`when defining project source changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: ts.projectSystem.protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } `} - }); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTsWithDependencyChange, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency source changes", session); }); - it("when projects are not built", () => { - const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([mainTs, dependencyTs, randomFile], session); - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/when projects are not built", session); - }); + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTsWithDependencyChange, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/dependency source changes", session); }); - describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { - function setup(onHostCreate?: OnHostCreate) { - return setupWithMainTsAndDependencyTs(createSessionWithDisabledProjectReferences, onHostCreate); - } - function setupWithAction(onHostCreate?: OnHostCreate) { - return setupWithActionWith(setup, onHostCreate); - } + it("when projects are not built", () => { + const host = ts.projectSystem.createServerHost(files); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([mainTs, dependencyTs, randomFile], session); + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/configWithReference/when projects are not built", session); + }); + }); + describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTsAndDependencyTs(createSessionWithDisabledProjectReferences, onHostCreate); + } - it("goto Definition in usage and rename locations from defining project", () => { - const { session } = setup(); - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/gotoDef and rename locations", session); - }); + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } - // Edit - it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/usage file changes with timeout before request", session); - }); - it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - makeChangeToMainTs(session); - makeChangeToDependencyTs(session); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/usage file changes", session); - }); + it("goto Definition in usage and rename locations from defining project", () => { + const { session } = setup(); + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/gotoDef and rename locations", session); + }); - // Edit dts to add new fn - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts changes with timeout before request", session); - }); - it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts changes", session); - }); + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - // Edit map file to represent added new line - it(`when dependency file's map changes, when timeout occurs before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - host.runQueuedTimeoutCallbacks(); - verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap changes with timeout before request", session); - }); - it(`when dependency file's map changes, when timeout does not occur before request`, () => { - // Create DocumentPositionMapper - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); - - // change - changeDtsMapFile(host); - - // action - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ false - ); - const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap changes", session); - }); + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); - it(`with depedency files map file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsMapLocation)); - - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap not present", session); - }); - it(`with depedency files map file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsMapLocation)!; - host.deleteFile(dtsMapLocation); - }); - - host.writeFile(dtsMapLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap created", session); - }); - it(`with depedency files map file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/usage file changes with timeout before request", session); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/usage file changes", session); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts changes with timeout before request", session); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts changes", session); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap changes with timeout before request", session); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap changes", session); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency files map file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsMapLocation)); + + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap not present", session); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; host.deleteFile(dtsMapLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap deleted", session); }); - it(`with depedency .d.ts file, when file is not present`, () => { - const { session } = setup(host => host.deleteFile(dtsLocation)); - - verifyAllFnAction( - session, - goToDefFromMainTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - /*existingDependencyMap*/ undefined, - /*existingDocumentPositionMapper*/ undefined, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts not present", session); - }); - it(`with depedency .d.ts file, when file is created after actions on projects`, () => { - let fileContents: string; - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { - fileContents = host.readFile(dtsLocation)!; - host.deleteFile(dtsLocation); - }); - - host.writeFile(dtsLocation, fileContents!); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ false, - /*existingDocumentPositionMapperEqual*/ false - ); - const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); - verifyAllFnAction( - session, - renameFromDependencyTs, - newDependencyMap, - newDocumentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts created", session); - }); - it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { - const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap created", session); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dtsMap deleted", session); + }); - // The dependency file is deleted when orphan files are collected + it(`with depedency .d.ts file, when file is not present`, () => { + const { session } = setup(host => host.deleteFile(dtsLocation)); + + verifyAllFnAction( + session, + goToDefFromMainTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts not present", session); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; host.deleteFile(dtsLocation); - verifyAllFnAction( - session, - goToDefFromMainTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyAllFnAction( - session, - renameFromDependencyTs, - dependencyMap, - documentPositionMapper, - /*existingMapEqual*/ true, - /*existingDocumentPositionMapperEqual*/ true - ); - verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); - baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts deleted", session); }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + renameFromDependencyTs, + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts created", session); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + goToDefFromMainTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + renameFromDependencyTs, + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyScriptInfoCollectionWith(session, [mainTs, dependencyTs]); + ts.projectSystem.baselineTsserverLogs("projectReferencesSourcemap", "dependencyAndUsage/disabledSourceRef/dependency dts deleted", session); }); }); }); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index 6a79ea852f739..a683a561738fa 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -1,1311 +1,1312 @@ -namespace ts.projectSystem { - describe("unittests:: tsserver:: Projects", () => { - it("handles the missing files - that were added to program because they were added with /// { - const file1: File = { - path: "/a/b/commonFile1.ts", - content: `/// +import * as ts from "../../_namespaces/ts"; + +describe("unittests:: tsserver:: Projects", () => { + it("handles the missing files - that were added to program because they were added with /// { + const file1: ts.projectSystem.File = { + path: "/a/b/commonFile1.ts", + content: `/// let x = y` - }; - const host = createServerHost([file1, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openFilesForSession([file1], session); - - const getErrRequest = makeSessionRequest( - server.CommandNames.SemanticDiagnosticsSync, - { file: file1.path } - ); - - // Two errors: CommonFile2 not found and cannot find name y - session.executeCommand(getErrRequest); - - host.writeFile(commonFile2.path, commonFile2.content); - host.runQueuedTimeoutCallbacks(); - session.executeCommand(getErrRequest); - baselineTsserverLogs("projects", "handles the missing files added with tripleslash ref", session); - }); + }; + const host = ts.projectSystem.createServerHost([file1, ts.projectSystem.libFile]); + const session = ts.projectSystem.createSession(host, { logger: ts.projectSystem.createLoggerWithInMemoryLogs(host) }); + ts.projectSystem.openFilesForSession([file1], session); + + const getErrRequest = ts.projectSystem.makeSessionRequest( + ts.server.CommandNames.SemanticDiagnosticsSync, + { file: file1.path } + ); + + // Two errors: CommonFile2 not found and cannot find name y + session.executeCommand(getErrRequest); + + host.writeFile(ts.projectSystem.commonFile2.path, ts.projectSystem.commonFile2.content); + host.runQueuedTimeoutCallbacks(); + session.executeCommand(getErrRequest); + ts.projectSystem.baselineTsserverLogs("projects", "handles the missing files added with tripleslash ref", session); + }); - it("should create new inferred projects for files excluded from a configured project", () => { - const configFile: File = { - path: "/a/b/tsconfig.json", - content: `{ + it("should create new inferred projects for files excluded from a configured project", () => { + const configFile: ts.projectSystem.File = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {}, - "files": ["${commonFile1.path}", "${commonFile2.path}"] + "files": ["${ts.projectSystem.commonFile1.path}", "${ts.projectSystem.commonFile2.path}"] }` - }; - const files = [commonFile1, commonFile2, configFile]; - const host = createServerHost(files); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - configFile.content = `{ + }; + const files = [ts.projectSystem.commonFile1, ts.projectSystem.commonFile2, configFile]; + const host = ts.projectSystem.createServerHost(files); + const projectService = ts.projectSystem.createProjectService(host); + projectService.openClientFile(ts.projectSystem.commonFile1.path); + + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + ts.projectSystem.checkProjectRootFiles(project, [ts.projectSystem.commonFile1.path, ts.projectSystem.commonFile2.path]); + configFile.content = `{ "compilerOptions": {}, - "files": ["${commonFile1.path}"] + "files": ["${ts.projectSystem.commonFile1.path}"] }`; - host.writeFile(configFile.path, configFile.content); + host.writeFile(configFile.path, configFile.content); - checkNumberOfConfiguredProjects(projectService, 1); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - host.checkTimeoutQueueLengthAndRun(2); // Update the configured project + refresh inferred projects - checkNumberOfConfiguredProjects(projectService, 1); - checkProjectRootFiles(project, [commonFile1.path]); + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + ts.projectSystem.checkProjectRootFiles(project, [ts.projectSystem.commonFile1.path, ts.projectSystem.commonFile2.path]); + host.checkTimeoutQueueLengthAndRun(2); // Update the configured project + refresh inferred projects + ts.projectSystem.checkNumberOfConfiguredProjects(projectService, 1); + ts.projectSystem.checkProjectRootFiles(project, [ts.projectSystem.commonFile1.path]); - projectService.openClientFile(commonFile2.path); - checkNumberOfInferredProjects(projectService, 1); - }); + projectService.openClientFile(ts.projectSystem.commonFile2.path); + ts.projectSystem.checkNumberOfInferredProjects(projectService, 1); + }); - it("should disable features when the files are too large", () => { - const file1 = { - path: "/a/b/f1.js", - content: "let x =1;", - fileSize: 10 * 1024 * 1024 - }; - const file2 = { - path: "/a/b/f2.js", - content: "let y =1;", - fileSize: 6 * 1024 * 1024 - }; - const file3 = { - path: "/a/b/f3.js", - content: "let y =1;", - fileSize: 6 * 1024 * 1024 - }; + it("should disable features when the files are too large", () => { + const file1 = { + path: "/a/b/f1.js", + content: "let x =1;", + fileSize: 10 * 1024 * 1024 + }; + const file2 = { + path: "/a/b/f2.js", + content: "let y =1;", + fileSize: 6 * 1024 * 1024 + }; + const file3 = { + path: "/a/b/f3.js", + content: "let y =1;", + fileSize: 6 * 1024 * 1024 + }; + + const proj1name = "proj1", proj2name = "proj2", proj3name = "proj3"; + + const host = ts.projectSystem.createServerHost([file1, file2, file3]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openExternalProject({ rootFiles: ts.projectSystem.toExternalFiles([file1.path]), options: {}, projectFileName: proj1name }); + const proj1 = projectService.findProject(proj1name)!; + assert.isTrue(proj1.languageServiceEnabled); + + projectService.openExternalProject({ rootFiles: ts.projectSystem.toExternalFiles([file2.path]), options: {}, projectFileName: proj2name }); + const proj2 = projectService.findProject(proj2name)!; + assert.isTrue(proj2.languageServiceEnabled); + + projectService.openExternalProject({ rootFiles: ts.projectSystem.toExternalFiles([file3.path]), options: {}, projectFileName: proj3name }); + const proj3 = projectService.findProject(proj3name)!; + assert.isFalse(proj3.languageServiceEnabled); + }); - const proj1name = "proj1", proj2name = "proj2", proj3name = "proj3"; + it("should not crash when opening a file in a project with a disabled language service", () => { + const file1 = { + path: "/a/b/f1.js", + content: "let x =1;", + fileSize: 50 * 1024 * 1024 + }; + const file2 = { + path: "/a/b/f2.js", + content: "let x =1;", + fileSize: 100 + }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); + const projName = "proj1"; - projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path]), options: {}, projectFileName: proj1name }); - const proj1 = projectService.findProject(proj1name)!; - assert.isTrue(proj1.languageServiceEnabled); + const host = ts.projectSystem.createServerHost([file1, file2]); + const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true, eventHandler: ts.noop }); - projectService.openExternalProject({ rootFiles: toExternalFiles([file2.path]), options: {}, projectFileName: proj2name }); - const proj2 = projectService.findProject(proj2name)!; - assert.isTrue(proj2.languageServiceEnabled); + projectService.openExternalProject({ rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]), options: {}, projectFileName: projName }); + const proj1 = projectService.findProject(projName)!; + assert.isFalse(proj1.languageServiceEnabled); - projectService.openExternalProject({ rootFiles: toExternalFiles([file3.path]), options: {}, projectFileName: proj3name }); - const proj3 = projectService.findProject(proj3name)!; - assert.isFalse(proj3.languageServiceEnabled); - }); + assert.doesNotThrow(() => projectService.openClientFile(file2.path)); + }); - it("should not crash when opening a file in a project with a disabled language service", () => { + describe("ignoreConfigFiles", () => { + it("external project including config file", () => { const file1 = { - path: "/a/b/f1.js", - content: "let x =1;", - fileSize: 50 * 1024 * 1024 + path: "/a/b/f1.ts", + content: "let x =1;" }; - const file2 = { - path: "/a/b/f2.js", - content: "let x =1;", - fileSize: 100 + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) }; - const projName = "proj1"; - - const host = createServerHost([file1, file2]); - const projectService = createProjectService(host, { useSingleInferredProject: true, eventHandler: noop }); - - projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path, file2.path]), options: {}, projectFileName: projName }); - const proj1 = projectService.findProject(projName)!; - assert.isFalse(proj1.languageServiceEnabled); - - assert.doesNotThrow(() => projectService.openClientFile(file2.path)); - }); - - describe("ignoreConfigFiles", () => { - it("external project including config file", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f1.ts"] - } - ) - }; - - const externalProjectName = "externalproject"; - const host = createServerHost([file1, config1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); - projectService.openExternalProject({ - rootFiles: toExternalFiles([file1.path, config1.path]), - options: {}, - projectFileName: externalProjectName - }); - - checkNumberOfProjects(projectService, { externalProjects: 1 }); - const proj = projectService.externalProjects[0]; - assert.isDefined(proj); - - assert.isTrue(proj.fileExists(file1.path)); - }); - - it("loose file included in config file (openClientFile)", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f1.ts"] - } - ) - }; - - const host = createServerHost([file1, config1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); - projectService.openClientFile(file1.path, file1.content); - - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const proj = projectService.inferredProjects[0]; - assert.isDefined(proj); - - assert.isTrue(proj.fileExists(file1.path)); + const externalProjectName = "externalproject"; + const host = ts.projectSystem.createServerHost([file1, config1]); + const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); + projectService.openExternalProject({ + rootFiles: ts.projectSystem.toExternalFiles([file1.path, config1.path]), + options: {}, + projectFileName: externalProjectName }); - it("loose file included in config file (applyCodeChanges)", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f1.ts"] - } - ) - }; + ts.projectSystem.checkNumberOfProjects(projectService, { externalProjects: 1 }); + const proj = projectService.externalProjects[0]; + assert.isDefined(proj); - const host = createServerHost([file1, config1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); - projectService.applyChangesInOpenFiles(singleIterator({ fileName: file1.path, content: file1.content })); - - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const proj = projectService.inferredProjects[0]; - assert.isDefined(proj); - - assert.isTrue(proj.fileExists(file1.path)); - }); + assert.isTrue(proj.fileExists(file1.path)); }); - it("reload regular file after closing", () => { - const f1 = { - path: "/a/b/app.ts", - content: "x." + it("loose file included in config file (openClientFile)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" }; - const f2 = { - path: "/a/b/lib.ts", - content: "let x: number;" + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) }; - const host = createServerHost([f1, f2, libFile]); - const service = createProjectService(host); - service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: toExternalFiles([f1.path, f2.path]), options: {} }); - - service.openClientFile(f1.path); - service.openClientFile(f2.path, "let x: string"); + const host = ts.projectSystem.createServerHost([file1, config1]); + const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); + projectService.openClientFile(file1.path, file1.content); - service.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); - const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, emptyOptions)!; - // should contain completions for string - assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'"); - assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'"); - - service.closeClientFile(f2.path); - const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, emptyOptions)!; - // should contain completions for string - assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'"); - assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'"); + assert.isTrue(proj.fileExists(file1.path)); }); - it("clear mixed content file after closing", () => { - const f1 = { - path: "/a/b/app.ts", - content: " " + it("loose file included in config file (applyCodeChanges)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" }; - const f2 = { - path: "/a/b/lib.html", - content: "" + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) }; - const host = createServerHost([f1, f2, libFile]); - const service = createProjectService(host); - service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} }); - - service.openClientFile(f1.path); - service.openClientFile(f2.path, "let somelongname: string"); + const host = ts.projectSystem.createServerHost([file1, config1]); + const projectService = ts.projectSystem.createProjectService(host, { useSingleInferredProject: true, syntaxOnly: true }); + projectService.applyChangesInOpenFiles(ts.singleIterator({ fileName: file1.path, content: file1.content })); - service.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); - const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, emptyOptions)!; - assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'"); - - service.closeClientFile(f2.path); - const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, emptyOptions)!; - assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'"); - const sf2 = service.externalProjects[0].getLanguageService().getProgram()!.getSourceFile(f2.path)!; - assert.equal(sf2.text, ""); + assert.isTrue(proj.fileExists(file1.path)); }); + }); - it("changes in closed files are reflected in project structure", () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: `export let x = 1` - }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` - }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const inferredProject0 = projectService.inferredProjects[0]; - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); - - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProject0); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); - const inferredProject1 = projectService.inferredProjects[1]; - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); - - host.writeFile(file2.path, `export * from "../c/f3"`); // now inferred project should inclule file3 - host.checkTimeoutQueueLengthAndRun(2); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProject0); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); - assert.strictEqual(projectService.inferredProjects[1], inferredProject1); - assert.isTrue(inferredProject1.isOrphan()); - }); + it("reload regular file after closing", () => { + const f1 = { + path: "/a/b/app.ts", + content: "x." + }; + const f2 = { + path: "/a/b/lib.ts", + content: "let x: number;" + }; + + const host = ts.projectSystem.createServerHost([f1, f2, ts.projectSystem.libFile]); + const service = ts.projectSystem.createProjectService(host); + service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: ts.projectSystem.toExternalFiles([f1.path, f2.path]), options: {} }); + + service.openClientFile(f1.path); + service.openClientFile(f2.path, "let x: string"); + + service.checkNumberOfProjects({ externalProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, ts.projectSystem.libFile.path]); + + const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, ts.emptyOptions)!; + // should contain completions for string + assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'"); + assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'"); + + service.closeClientFile(f2.path); + const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, ts.emptyOptions)!; + // should contain completions for string + assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'"); + assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'"); + }); - it("deleted files affect project structure", () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: `export * from "../c/f3"` - }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` - }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); + it("clear mixed content file after closing", () => { + const f1 = { + path: "/a/b/app.ts", + content: " " + }; + const f2 = { + path: "/a/b/lib.html", + content: "" + }; + + const host = ts.projectSystem.createServerHost([f1, f2, ts.projectSystem.libFile]); + const service = ts.projectSystem.createProjectService(host); + service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} }); + + service.openClientFile(f1.path); + service.openClientFile(f2.path, "let somelongname: string"); + + service.checkNumberOfProjects({ externalProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, ts.projectSystem.libFile.path]); + + const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, ts.emptyOptions)!; + assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'"); + + service.closeClientFile(f2.path); + const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, ts.emptyOptions)!; + assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'"); + const sf2 = service.externalProjects[0].getLanguageService().getProgram()!.getSourceFile(f2.path)!; + assert.equal(sf2.text, ""); + }); - projectService.openClientFile(file1.path); + it("changes in closed files are reflected in project structure", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export let x = 1` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + const host = ts.projectSystem.createServerHost([file1, file2, file3]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const inferredProject0 = projectService.inferredProjects[0]; + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); + + projectService.openClientFile(file3.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); + const inferredProject1 = projectService.inferredProjects[1]; + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + + host.writeFile(file2.path, `export * from "../c/f3"`); // now inferred project should inclule file3 + host.checkTimeoutQueueLengthAndRun(2); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProject0); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); + assert.strictEqual(projectService.inferredProjects[1], inferredProject1); + assert.isTrue(inferredProject1.isOrphan()); + }); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); + it("deleted files affect project structure", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export * from "../c/f3"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + const host = ts.projectSystem.createServerHost([file1, file2, file3]); + const projectService = ts.projectSystem.createProjectService(host); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); + projectService.openClientFile(file1.path); - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); - host.deleteFile(file2.path); - host.checkTimeoutQueueLengthAndRun(2); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); + projectService.openClientFile(file3.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); - }); + host.deleteFile(file2.path); + host.checkTimeoutQueueLengthAndRun(2); - it("ignores files excluded by a custom safe type list", () => { - const file1 = { - path: "/a/b/f1.js", - content: "export let x = 5" - }; - const office = { - path: "/lib/duckquack-3.min.js", - content: "whoa do @@ not parse me ok thanks!!!" - }; - const host = createServerHost([file1, office, customTypesMap]); - const projectService = createProjectService(host); - try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, office.path]) }); - const proj = projectService.externalProjects[0]; - assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); - assert.deepEqual(proj.getTypeAcquisition().include, ["duck-types"]); - } - finally { - projectService.resetSafeList(); - } - }); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); - it("file with name constructor.js doesnt cause issue with typeAcquisition when safe type list", () => { - const file1 = { - path: "/a/b/f1.js", - content: `export let x = 5; import { s } from "s"` - }; - const constructorFile = { - path: "/a/b/constructor.js", - content: "const x = 10;" - }; - const bliss = { - path: "/a/b/bliss.js", - content: "export function is() { return true; }" - }; - const host = createServerHost([file1, libFile, constructorFile, bliss, customTypesMap]); - let request: string | undefined; - const cachePath = "/a/data"; - const typingsInstaller: server.ITypingsInstaller = { - isKnownTypesPackageName: returnFalse, - installPackage: notImplemented, - enqueueInstallTypingsRequest: (proj, typeAcquisition, unresolvedImports) => { - assert.isUndefined(request); - request = JSON.stringify(server.createInstallTypingsRequest(proj, typeAcquisition, unresolvedImports || server.emptyArray, cachePath)); - }, - attach: noop, - onProjectClosed: noop, - globalTypingsCacheLocation: cachePath - }; + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + }); - const projectName = "project"; - const projectService = createProjectService(host, { typingsInstaller }); - projectService.openExternalProject({ projectFileName: projectName, options: {}, rootFiles: toExternalFiles([file1.path, constructorFile.path, bliss.path]) }); - assert.equal(request, JSON.stringify({ - projectName, - fileNames: [libFile.path, file1.path, constructorFile.path, bliss.path], - compilerOptions: { allowNonTsExtensions: true, noEmitForJsFiles: true }, - typeAcquisition: { include: ["blissfuljs"], exclude: [], enable: true }, - unresolvedImports: ["s"], - projectRootPath: "/", - cachePath, - kind: "discover" - })); - const response = JSON.parse(request!); - request = undefined; - projectService.updateTypingsForProject({ - kind: "action::set", - projectName: response.projectName, - typeAcquisition: response.typeAcquisition, - compilerOptions: response.compilerOptions, - typings: emptyArray, - unresolvedImports: response.unresolvedImports, - }); + it("ignores files excluded by a custom safe type list", () => { + const file1 = { + path: "/a/b/f1.js", + content: "export let x = 5" + }; + const office = { + path: "/lib/duckquack-3.min.js", + content: "whoa do @@ not parse me ok thanks!!!" + }; + const host = ts.projectSystem.createServerHost([file1, office, ts.projectSystem.customTypesMap]); + const projectService = ts.projectSystem.createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: ts.projectSystem.toExternalFiles([file1.path, office.path]) }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + assert.deepEqual(proj.getTypeAcquisition().include, ["duck-types"]); + } + finally { + projectService.resetSafeList(); + } + }); - host.checkTimeoutQueueLength(0); - assert.isUndefined(request); + it("file with name constructor.js doesnt cause issue with typeAcquisition when safe type list", () => { + const file1 = { + path: "/a/b/f1.js", + content: `export let x = 5; import { s } from "s"` + }; + const constructorFile = { + path: "/a/b/constructor.js", + content: "const x = 10;" + }; + const bliss = { + path: "/a/b/bliss.js", + content: "export function is() { return true; }" + }; + const host = ts.projectSystem.createServerHost([file1, ts.projectSystem.libFile, constructorFile, bliss, ts.projectSystem.customTypesMap]); + let request: string | undefined; + const cachePath = "/a/data"; + const typingsInstaller: ts.server.ITypingsInstaller = { + isKnownTypesPackageName: ts.returnFalse, + installPackage: ts.notImplemented, + enqueueInstallTypingsRequest: (proj, typeAcquisition, unresolvedImports) => { + assert.isUndefined(request); + request = JSON.stringify(ts.server.createInstallTypingsRequest(proj, typeAcquisition, unresolvedImports || ts.server.emptyArray, cachePath)); + }, + attach: ts.noop, + onProjectClosed: ts.noop, + globalTypingsCacheLocation: cachePath + }; + + const projectName = "project"; + const projectService = ts.projectSystem.createProjectService(host, { typingsInstaller }); + projectService.openExternalProject({ projectFileName: projectName, options: {}, rootFiles: ts.projectSystem.toExternalFiles([file1.path, constructorFile.path, bliss.path]) }); + assert.equal(request, JSON.stringify({ + projectName, + fileNames: [ts.projectSystem.libFile.path, file1.path, constructorFile.path, bliss.path], + compilerOptions: { allowNonTsExtensions: true, noEmitForJsFiles: true }, + typeAcquisition: { include: ["blissfuljs"], exclude: [], enable: true }, + unresolvedImports: ["s"], + projectRootPath: "/", + cachePath, + kind: "discover" + })); + const response = JSON.parse(request!); + request = undefined; + projectService.updateTypingsForProject({ + kind: "action::set", + projectName: response.projectName, + typeAcquisition: response.typeAcquisition, + compilerOptions: response.compilerOptions, + typings: ts.emptyArray, + unresolvedImports: response.unresolvedImports, }); - it("ignores files excluded by the default type list", () => { - const file1 = { - path: "/a/b/f1.js", - content: "export let x = 5" - }; - const minFile = { - path: "/c/moment.min.js", - content: "unspecified" - }; - const kendoFile1 = { - path: "/q/lib/kendo/kendo.all.min.js", - content: "unspecified" - }; - const kendoFile2 = { - path: "/q/lib/kendo/kendo.ui.min.js", - content: "unspecified" - }; - const kendoFile3 = { - path: "/q/lib/kendo-ui/kendo.all.js", - content: "unspecified" - }; - const officeFile1 = { - path: "/scripts/Office/1/excel-15.debug.js", - content: "unspecified" - }; - const officeFile2 = { - path: "/scripts/Office/1/powerpoint.js", - content: "unspecified" - }; - const files = [file1, minFile, kendoFile1, kendoFile2, kendoFile3, officeFile1, officeFile2]; - const host = createServerHost(files); - const projectService = createProjectService(host); - try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles(files.map(f => f.path)) }); - const proj = projectService.externalProjects[0]; - assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); - assert.deepEqual(proj.getTypeAcquisition().include, ["kendo-ui", "office"]); - } - finally { - projectService.resetSafeList(); - } - }); + host.checkTimeoutQueueLength(0); + assert.isUndefined(request); + }); - it("removes version numbers correctly", () => { - const testData: [string, string][] = [ - ["jquery-max", "jquery-max"], - ["jquery.min", "jquery"], - ["jquery-min.4.2.3", "jquery"], - ["jquery.min.4.2.1", "jquery"], - ["minimum", "minimum"], - ["min", "min"], - ["min.3.2", "min"], - ["jquery", "jquery"] - ]; - for (const t of testData) { - assert.equal(removeMinAndVersionNumbers(t[0]), t[1], t[0]); - } - }); + it("ignores files excluded by the default type list", () => { + const file1 = { + path: "/a/b/f1.js", + content: "export let x = 5" + }; + const minFile = { + path: "/c/moment.min.js", + content: "unspecified" + }; + const kendoFile1 = { + path: "/q/lib/kendo/kendo.all.min.js", + content: "unspecified" + }; + const kendoFile2 = { + path: "/q/lib/kendo/kendo.ui.min.js", + content: "unspecified" + }; + const kendoFile3 = { + path: "/q/lib/kendo-ui/kendo.all.js", + content: "unspecified" + }; + const officeFile1 = { + path: "/scripts/Office/1/excel-15.debug.js", + content: "unspecified" + }; + const officeFile2 = { + path: "/scripts/Office/1/powerpoint.js", + content: "unspecified" + }; + const files = [file1, minFile, kendoFile1, kendoFile2, kendoFile3, officeFile1, officeFile2]; + const host = ts.projectSystem.createServerHost(files); + const projectService = ts.projectSystem.createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: ts.projectSystem.toExternalFiles(files.map(f => f.path)) }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + assert.deepEqual(proj.getTypeAcquisition().include, ["kendo-ui", "office"]); + } + finally { + projectService.resetSafeList(); + } + }); - it("ignores files excluded by a legacy safe type list", () => { - const file1 = { - path: "/a/b/bliss.js", - content: "let x = 5" - }; - const file2 = { - path: "/a/b/foo.js", - content: "" - }; - const file3 = { - path: "/a/b/Bacon.js", - content: "let y = 5" - }; - const host = createServerHost([file1, file2, file3, customTypesMap]); - const projectService = createProjectService(host); - try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } }); - const proj = projectService.externalProjects[0]; - assert.deepEqual(proj.getFileNames(), [file2.path]); - } - finally { - projectService.resetSafeList(); - } - }); + it("removes version numbers correctly", () => { + const testData: [string, string][] = [ + ["jquery-max", "jquery-max"], + ["jquery.min", "jquery"], + ["jquery-min.4.2.3", "jquery"], + ["jquery.min.4.2.1", "jquery"], + ["minimum", "minimum"], + ["min", "min"], + ["min.3.2", "min"], + ["jquery", "jquery"] + ]; + for (const t of testData) { + assert.equal(ts.removeMinAndVersionNumbers(t[0]), t[1], t[0]); + } + }); - it("correctly migrate files between projects", () => { - const file1 = { - path: "/a/b/f1.ts", - content: ` + it("ignores files excluded by a legacy safe type list", () => { + const file1 = { + path: "/a/b/bliss.js", + content: "let x = 5" + }; + const file2 = { + path: "/a/b/foo.js", + content: "" + }; + const file3 = { + path: "/a/b/Bacon.js", + content: "let y = 5" + }; + const host = ts.projectSystem.createServerHost([file1, file2, file3, ts.projectSystem.customTypesMap]); + const projectService = ts.projectSystem.createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: ts.projectSystem.toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(), [file2.path]); + } + finally { + projectService.resetSafeList(); + } + }); + + it("correctly migrate files between projects", () => { + const file1 = { + path: "/a/b/f1.ts", + content: ` export * from "../c/f2"; export * from "../d/f3";` - }; - const file2 = { - path: "/a/c/f2.ts", - content: "export let x = 1;" - }; - const file3 = { - path: "/a/d/f3.ts", - content: "export let y = 1;" - }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); - - projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); - let inferredProjects = projectService.inferredProjects.slice(); - - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); - checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); - inferredProjects = projectService.inferredProjects.slice(); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - assert.notStrictEqual(projectService.inferredProjects[0], inferredProjects[0]); - assert.notStrictEqual(projectService.inferredProjects[0], inferredProjects[1]); - checkProjectRootFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); - inferredProjects = projectService.inferredProjects.slice(); - - projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 3 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); - checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); - inferredProjects = projectService.inferredProjects.slice(); - - projectService.closeClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 3 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); - assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); - assert.strictEqual(projectService.inferredProjects[2], inferredProjects[2]); - assert.isTrue(projectService.inferredProjects[0].isOrphan()); - checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); - assert.isTrue(projectService.inferredProjects[2].isOrphan()); - - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - assert.strictEqual(projectService.inferredProjects[0], inferredProjects[2]); - assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); - checkProjectActualFiles(projectService.inferredProjects[0], [file3.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); - }); + }; + const file2 = { + path: "/a/c/f2.ts", + content: "export let x = 1;" + }; + const file3 = { + path: "/a/d/f3.ts", + content: "export let y = 1;" + }; + const host = ts.projectSystem.createServerHost([file1, file2, file3]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file2.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); + let inferredProjects = projectService.inferredProjects.slice(); + + projectService.openClientFile(file3.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + inferredProjects = projectService.inferredProjects.slice(); + + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + assert.notStrictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.notStrictEqual(projectService.inferredProjects[0], inferredProjects[1]); + ts.projectSystem.checkProjectRootFiles(projectService.inferredProjects[0], [file1.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); + inferredProjects = projectService.inferredProjects.slice(); + + projectService.closeClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 3 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); + inferredProjects = projectService.inferredProjects.slice(); + + projectService.closeClientFile(file3.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 3 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[0]); + assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); + assert.strictEqual(projectService.inferredProjects[2], inferredProjects[2]); + assert.isTrue(projectService.inferredProjects[0].isOrphan()); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + assert.isTrue(projectService.inferredProjects[2].isOrphan()); + + projectService.openClientFile(file3.path); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); + assert.strictEqual(projectService.inferredProjects[0], inferredProjects[2]); + assert.strictEqual(projectService.inferredProjects[1], inferredProjects[1]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file3.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + }); - it("regression test for crash in acquireOrUpdateDocument", () => { - const tsFile = { - fileName: "/a/b/file1.ts", - path: "/a/b/file1.ts", - content: "" - }; - const jsFile = { - path: "/a/b/file1.js", - content: "var x = 10;", - fileName: "/a/b/file1.js", - scriptKind: "JS" as const - }; + it("regression test for crash in acquireOrUpdateDocument", () => { + const tsFile = { + fileName: "/a/b/file1.ts", + path: "/a/b/file1.ts", + content: "" + }; + const jsFile = { + path: "/a/b/file1.js", + content: "var x = 10;", + fileName: "/a/b/file1.js", + scriptKind: "JS" as const + }; + + const host = ts.projectSystem.createServerHost([]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.applyChangesInOpenFiles(ts.singleIterator(tsFile)); + const projs = projectService.synchronizeProjectList([]); + projectService.findProject(projs[0].info!.projectName)!.getLanguageService().getNavigationBarItems(tsFile.fileName); + projectService.synchronizeProjectList([projs[0].info!]); + projectService.applyChangesInOpenFiles(ts.singleIterator(jsFile)); + }); - const host = createServerHost([]); - const projectService = createProjectService(host); - projectService.applyChangesInOpenFiles(singleIterator(tsFile)); - const projs = projectService.synchronizeProjectList([]); - projectService.findProject(projs[0].info!.projectName)!.getLanguageService().getNavigationBarItems(tsFile.fileName); - projectService.synchronizeProjectList([projs[0].info!]); - projectService.applyChangesInOpenFiles(singleIterator(jsFile)); - }); + it("config file is deleted", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1;" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 2;" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const host = ts.projectSystem.createServerHost([file1, file2, config]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + projectService.openClientFile(file2.path); + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + host.deleteFile(config.path); + host.checkTimeoutQueueLengthAndRun(1); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + }); - it("config file is deleted", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1;" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 2;" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - const host = createServerHost([file1, file2, config]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); - - projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); - - host.deleteFile(config.path); - host.checkTimeoutQueueLengthAndRun(1); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + it("loading files with correct priority", () => { + const f1 = { + path: "/a/main.ts", + content: "let x = 1" + }; + const f2 = { + path: "/a/main.js", + content: "var y = 1" + }; + const f3 = { + path: "/main.js", + content: "var y = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { allowJs: true } + }) + }; + const host = ts.projectSystem.createServerHost([f1, f2, f3, config]); + const projectService = ts.projectSystem.createProjectService(host); + projectService.setHostConfiguration({ + extraFileExtensions: [ + { extension: ".js", isMixedContent: false }, + { extension: ".html", isMixedContent: true } + ] }); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [f1.path, config.path]); + + // Since f2 refers to config file as the default project, it needs to be kept alive + projectService.closeClientFile(f1.path); + projectService.openClientFile(f2.path); + projectService.checkNumberOfProjects({ inferredProjects: 1, configuredProjects: 1 }); + assert.isDefined(projectService.configuredProjects.get(config.path)); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]); + + // Should close configured project with next file open + projectService.closeClientFile(f2.path); + projectService.openClientFile(f3.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(config.path)); + ts.projectSystem.checkProjectActualFiles(projectService.inferredProjects[0], [f3.path]); + }); - it("loading files with correct priority", () => { - const f1 = { - path: "/a/main.ts", - content: "let x = 1" - }; - const f2 = { - path: "/a/main.js", - content: "var y = 1" - }; - const f3 = { - path: "/main.js", - content: "var y = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { allowJs: true } - }) - }; - const host = createServerHost([f1, f2, f3, config]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ - extraFileExtensions: [ - { extension: ".js", isMixedContent: false }, - { extension: ".html", isMixedContent: true } - ] - }); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]); - - // Since f2 refers to config file as the default project, it needs to be kept alive - projectService.closeClientFile(f1.path); - projectService.openClientFile(f2.path); - projectService.checkNumberOfProjects({ inferredProjects: 1, configuredProjects: 1 }); - assert.isDefined(projectService.configuredProjects.get(config.path)); - checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]); - - // Should close configured project with next file open - projectService.closeClientFile(f2.path); - projectService.openClientFile(f3.path); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - assert.isUndefined(projectService.configuredProjects.get(config.path)); - checkProjectActualFiles(projectService.inferredProjects[0], [f3.path]); - }); + it("tsconfig script block support", () => { + const file1 = { + path: "/a/b/f1.ts", + content: ` ` + }; + const file2 = { + path: "/a/b/f2.html", + content: `var hello = "hello";` + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true } }) + }; + const host = ts.projectSystem.createServerHost([file1, file2, config]); + const session = ts.projectSystem.createSession(host); + ts.projectSystem.openFilesForSession([file1], session); + const projectService = session.getProjectService(); + + // HTML file will not be included in any projects yet + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const configuredProj = ts.projectSystem.configuredProjectAt(projectService, 0); + ts.projectSystem.checkProjectActualFiles(configuredProj, [file1.path, config.path]); + + // Specify .html extension as mixed content + const extraFileExtensions = [{ extension: ".html", scriptKind: ts.ScriptKind.JS, isMixedContent: true }]; + const configureHostRequest = ts.projectSystem.makeSessionRequest(ts.projectSystem.CommandNames.Configure, { extraFileExtensions }); + session.executeCommand(configureHostRequest); + + // The configured project should now be updated to include html file + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(ts.projectSystem.configuredProjectAt(projectService, 0), configuredProj, "Same configured project should be updated"); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + // Open HTML file + projectService.applyChangesInOpenFiles(ts.singleIterator({ + fileName: file2.path, + hasMixedContent: true, + scriptKind: ts.ScriptKind.JS, + content: `var hello = "hello";` + })); + // Now HTML file is included in the project + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + // Check identifiers defined in HTML content are available in .ts file + const project = ts.projectSystem.configuredProjectAt(projectService, 0); + let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, ts.emptyOptions); + assert(completions && ts.some(completions.entries, e => e.name === "hello"), `expected entry hello to be in completion list`); + + // Close HTML file + projectService.applyChangesInOpenFiles( + /*openFiles*/ undefined, + /*changedFiles*/ undefined, + /*closedFiles*/[file2.path]); + + // HTML file is still included in project + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); + ts.projectSystem.checkProjectActualFiles(ts.projectSystem.configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + // Check identifiers defined in HTML content are not available in .ts file + completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, ts.emptyOptions); + assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`); + }); - it("tsconfig script block support", () => { - const file1 = { - path: "/a/b/f1.ts", - content: ` ` - }; - const file2 = { - path: "/a/b/f2.html", - content: `var hello = "hello";` - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true } }) - }; - const host = createServerHost([file1, file2, config]); - const session = createSession(host); - openFilesForSession([file1], session); - const projectService = session.getProjectService(); + it("no tsconfig script block diagnostic errors", () => { - // HTML file will not be included in any projects yet - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const configuredProj = configuredProjectAt(projectService, 0); - checkProjectActualFiles(configuredProj, [file1.path, config.path]); + // #1. Ensure no diagnostic errors when allowJs is true + const file1 = { + path: "/a/b/f1.ts", + content: ` ` + }; + const file2 = { + path: "/a/b/f2.html", + content: `var hello = "hello";` + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true } }) + }; - // Specify .html extension as mixed content - const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; - const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); - session.executeCommand(configureHostRequest); + let host = ts.projectSystem.createServerHost([file1, file2, config1, ts.projectSystem.libFile], { executingFilePath: ts.combinePaths(ts.getDirectoryPath(ts.projectSystem.libFile.path), "tsc.js") }); + let session = ts.projectSystem.createSession(host); - // The configured project should now be updated to include html file - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(configuredProjectAt(projectService, 0), configuredProj, "Same configured project should be updated"); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); - - // Open HTML file - projectService.applyChangesInOpenFiles(singleIterator({ - fileName: file2.path, - hasMixedContent: true, - scriptKind: ScriptKind.JS, - content: `var hello = "hello";` - })); - // Now HTML file is included in the project - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); - - // Check identifiers defined in HTML content are available in .ts file - const project = configuredProjectAt(projectService, 0); - let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, emptyOptions); - assert(completions && some(completions.entries, e => e.name === "hello"), `expected entry hello to be in completion list`); - - // Close HTML file - projectService.applyChangesInOpenFiles( - /*openFiles*/ undefined, - /*changedFiles*/ undefined, - /*closedFiles*/[file2.path]); - - // HTML file is still included in project - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); - - // Check identifiers defined in HTML content are not available in .ts file - completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, emptyOptions); - assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`); - }); + // Specify .html extension as mixed content in a configure host request + const extraFileExtensions = [{ extension: ".html", scriptKind: ts.ScriptKind.JS, isMixedContent: true }]; + const configureHostRequest = ts.projectSystem.makeSessionRequest(ts.projectSystem.CommandNames.Configure, { extraFileExtensions }); + session.executeCommand(configureHostRequest); - it("no tsconfig script block diagnostic errors", () => { + ts.projectSystem.openFilesForSession([file1], session); + let projectService = session.getProjectService(); - // #1. Ensure no diagnostic errors when allowJs is true - const file1 = { - path: "/a/b/f1.ts", - content: ` ` - }; - const file2 = { - path: "/a/b/f2.html", - content: `var hello = "hello";` - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true } }) - }; + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); - let host = createServerHost([file1, file2, config1, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - let session = createSession(host); + let diagnostics = ts.projectSystem.configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - // Specify .html extension as mixed content in a configure host request - const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; - const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); - session.executeCommand(configureHostRequest); + // #2. Ensure no errors when allowJs is false + const config2 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: false } }) + }; - openFilesForSession([file1], session); - let projectService = session.getProjectService(); + host = ts.projectSystem.createServerHost([file1, file2, config2, ts.projectSystem.libFile], { executingFilePath: ts.combinePaths(ts.getDirectoryPath(ts.projectSystem.libFile.path), "tsc.js") }); + session = ts.projectSystem.createSession(host); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + session.executeCommand(configureHostRequest); - let diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + ts.projectSystem.openFilesForSession([file1], session); + projectService = session.getProjectService(); - // #2. Ensure no errors when allowJs is false - const config2 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: false } }) - }; + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); - host = createServerHost([file1, file2, config2, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - session = createSession(host); + diagnostics = ts.projectSystem.configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - session.executeCommand(configureHostRequest); + // #3. Ensure no errors when compiler options aren't specified + const config3 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({}) + }; - openFilesForSession([file1], session); - projectService = session.getProjectService(); + host = ts.projectSystem.createServerHost([file1, file2, config3, ts.projectSystem.libFile], { executingFilePath: ts.combinePaths(ts.getDirectoryPath(ts.projectSystem.libFile.path), "tsc.js") }); + session = ts.projectSystem.createSession(host); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + session.executeCommand(configureHostRequest); - diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + ts.projectSystem.openFilesForSession([file1], session); + projectService = session.getProjectService(); - // #3. Ensure no errors when compiler options aren't specified - const config3 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({}) - }; + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); - host = createServerHost([file1, file2, config3, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - session = createSession(host); + diagnostics = ts.projectSystem.configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - session.executeCommand(configureHostRequest); + // #4. Ensure no errors when files are explicitly specified in tsconfig + const config4 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true }, files: [file1.path, file2.path] }) + }; - openFilesForSession([file1], session); - projectService = session.getProjectService(); + host = ts.projectSystem.createServerHost([file1, file2, config4, ts.projectSystem.libFile], { executingFilePath: ts.combinePaths(ts.getDirectoryPath(ts.projectSystem.libFile.path), "tsc.js") }); + session = ts.projectSystem.createSession(host); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + session.executeCommand(configureHostRequest); - diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + ts.projectSystem.openFilesForSession([file1], session); + projectService = session.getProjectService(); - // #4. Ensure no errors when files are explicitly specified in tsconfig - const config4 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true }, files: [file1.path, file2.path] }) - }; + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); - host = createServerHost([file1, file2, config4, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - session = createSession(host); + diagnostics = ts.projectSystem.configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - session.executeCommand(configureHostRequest); + // #4. Ensure no errors when files are explicitly excluded in tsconfig + const config5 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true }, exclude: [file2.path] }) + }; - openFilesForSession([file1], session); - projectService = session.getProjectService(); + host = ts.projectSystem.createServerHost([file1, file2, config5, ts.projectSystem.libFile], { executingFilePath: ts.combinePaths(ts.getDirectoryPath(ts.projectSystem.libFile.path), "tsc.js") }); + session = ts.projectSystem.createSession(host); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + session.executeCommand(configureHostRequest); - diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + ts.projectSystem.openFilesForSession([file1], session); + projectService = session.getProjectService(); - // #4. Ensure no errors when files are explicitly excluded in tsconfig - const config5 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true }, exclude: [file2.path] }) - }; + ts.projectSystem.checkNumberOfProjects(projectService, { configuredProjects: 1 }); - host = createServerHost([file1, file2, config5, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - session = createSession(host); + diagnostics = ts.projectSystem.configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + }); - session.executeCommand(configureHostRequest); + it("project structure update is deferred if files are not added\removed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `import {x} from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: "export let x = 1" + }; + const host = ts.projectSystem.createServerHost([file1, file2]); + const projectService = ts.projectSystem.createProjectService(host); + + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + projectService.applyChangesInOpenFiles( + /*openFiles*/ undefined, + /*changedFiles*/ts.singleIterator({ fileName: file1.path, changes: ts.singleIterator({ span: ts.createTextSpan(0, file1.path.length), newText: "let y = 1" }) }), + /*closedFiles*/ undefined); + + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 1 }); + projectService.ensureInferredProjectsUpToDate_TestOnly(); + ts.projectSystem.checkNumberOfProjects(projectService, { inferredProjects: 2 }); + }); - openFilesForSession([file1], session); - projectService = session.getProjectService(); + it("files with mixed content are handled correctly", () => { + const file1 = { + path: "/a/b/f1.html", + content: `